README.TXT

The \Mssdk\Samples\COM directory contains samples for the following
Platform SDK components:

* Base COM
* COM Tutorial Samples
* DCOM include security
* Microsoft Transaction Server
* ActiveX Controls
* OLE 2
* OLE Automation
* Active Documents

The build environment (headers, libraries, and minimum build
tools) for the Platform SDK must be installed in order to build
any of the samples in the \Mssdk\Samples directory. The build
environment can be installed from the readme.htm or by running
setup.exe on the root of the Platform SDK compact disc. The most
recent version of the build environment is also available at
http://www.microsoft.com/msdn/sdk/platform.htm.

Some components may also require you to install runtimes, depending
on the platform on which you are trying to run the sample.
Redistributables are available in the \Mssdk\Redist subdirectory
on the Platform SDK compact disc.

Documentation for the components listed above can be installed
from the Platform SDK compact disc, or can be viewed online at
http://premium.microsoft.com/msdn/library/. This is a free service;
registration is necessary.

If you do not see any subdirectories in the \Mssdk\Samples\COM
node, you'll need to install the COM and ActiveX components
from the Platform SDK compact disc or download and install them
from http://www.microsoft.com/msdn/sdk/platform.htm.


ABOUT.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       about.cxx
//
//  Contents:   implementation for a simple about dialog box
//
//  Classes:    CAbout
//
//  Functions:
//
//  History:    6-08-94   stevebl   Created
//
//----------------------------------------------------------------------------

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

//+---------------------------------------------------------------------------
//
//  Member:     CAbout::DialogProc
//
//  Synopsis:   dialog proc for the About dialog box
//
//  Arguments:  [uMsg]   - message
//              [wParam] - first message parameter
//              [lParam] - second message parameter
//
//  History:    4-12-94   stevebl   Created for MFract
//              6-08-94   stevebl   Stolen from MFract
//
//----------------------------------------------------------------------------

BOOL CAbout::DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_INITDIALOG:
        return(TRUE);
    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK
            || LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hwndDlg, TRUE);
            return(TRUE);
        }
    }
    return(FALSE);
}


ABOUT.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       about.h
//
//  Contents:   definition for an about dialog box class
//
//  Classes:    CAbout
//
//  Functions:
//
//  History:    6-08-94   stevebl   Created
//
//----------------------------------------------------------------------------

#ifndef __ABOUT_H__
#define __ABOUT_H__

#include <cdialog.h>

#ifdef __cplusplus

//+---------------------------------------------------------------------------
//
//  Class:      CAbout
//
//  Purpose:    implements the about dialog box
//
//  Interface:  DialogProc -- dialog procedure
//
//  History:    6-08-94   stevebl   Created
//
//----------------------------------------------------------------------------

class CAbout: public CHlprDialog
{
public:
    BOOL DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
};

#endif //__cplusplus

#endif //__ABOUT_H__


BITMAPS.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       bitmaps.cxx
//
//  Contents:   bitmap helper functions
//
//  Classes:
//
//  Functions:  DDBChangeColor
//              LoadAndStretch
//
//  History:    6-24-94   stevebl   Created
//
//----------------------------------------------------------------------------

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

//+---------------------------------------------------------------------------
//
//  Function:   LoadAndStretch
//
//  Synopsis:   Loads a bitmap from our resources, and creates a bitmap
//              "array."  The red portions of the bitmap are considered
//              clear.  Each resource consists of two bitmaps side by
//              side:  an "open folder" and a "closed folder".  These
//              bitmaps are turned into 4 bitmaps:  two selected, and
//              two unselected bitmaps.
//
//  Arguments:  [hinst]        - instance containing the bitmap
//              [hbmpDest]     - destination bitmap array
//              [lpszResource] - name of the bitmap resource
//              [cxBitmap]     - width to stretch to
//              [cyBitmap]     - height to stretch to
//              [crHigh]       - color of the highlighted background
//              [crNorm]       - color of the normal background
//
//  Returns:    TRUE on success.
//              FALSE on failure.
//
//  History:    6-24-94   stevebl   Stolen and modified from original DFView
//
//----------------------------------------------------------------------------

BOOL LoadAndStretch (
    HINSTANCE hinst,
    HBITMAP hbmpDest,
    LPTSTR lpszResource,
    UINT cxBitmap,
    UINT cyBitmap,
    COLORREF crHigh,
    COLORREF crNorm)
{
    HBITMAP hbmpOld1, hbmpOld2, hbmp;
    BITMAP bm;
    int i;
    HDC hdcDest = CreateCompatibleDC(NULL);
    HDC hdcSrc = CreateCompatibleDC(NULL);

    if (NULL == hdcDest || NULL == hdcSrc)
    {
        DeleteDC(hdcSrc);
        DeleteDC(hdcDest);
        return(FALSE);
    }
    for (i=0;  i < 2;  i++)
    {
        hbmp = LoadBitmap(hinst, lpszResource);
        if (NULL == hbmp)
        {
            DeleteDC(hdcSrc);
            DeleteDC(hdcDest);
            return FALSE;
        }

        DDBChangeColor(hbmp, RGB (255,0,0), ((i == 0) ? crHigh : crNorm));
        GetObject(hbmp, sizeof (bm), &bm);

        hbmpOld1 = (HBITMAP) SelectObject(hdcDest, hbmpDest);
        hbmpOld2 = (HBITMAP) SelectObject(hdcSrc, hbmp);

        SetStretchBltMode(hdcDest, COLORONCOLOR);
        StretchBlt(
            hdcDest,
            i * cxBitmap * 2,
            0,
            cxBitmap * 2,
            cyBitmap,
            hdcSrc,
            0,
            0,
            bm.bmWidth,
            bm.bmHeight,
            SRCCOPY);

        SelectObject(hdcDest, hbmpOld1);
        SelectObject(hdcSrc,  hbmpOld2);
        DeleteObject(hbmp);
    }

    DeleteDC(hdcSrc);
    DeleteDC(hdcDest);
    return TRUE;
}

//+---------------------------------------------------------------------------
//
//  Function:   DDBChangeColor
//
//  Synopsis:   Change a particular color in a bitmap to another color.
//              Strategy is to create a monochrome mask, where each pixel
//              in the source bitmap that matches the color we're converting
//              from is set to white (1), and all other colors to black.
//              We then Blt this mask into the original bitmap, with a ROP
//              code that does:
//
//                (~Mask&Source) | (~Mask&Pattern)
//
//              where Pattern is the color we're changing to.
//
//              In other words, wherever the Mask is 1, we want to put the pattern;
//              wherever the Mask is 0, we want to leave the source alone.  By
//              using a Truth Table, you'll find that this ROP code is equivalent
//              to DSPDxax or ROP code 0x00E20746.
//
//              For info on figuring out ROP codes given a set of boolean ops,
//              check out the Windows 3.0 SDK, Reference Volume 2, chapter 11.
//
//  Arguments:  [hBitmap] - bitmap handle
//              [crFrom]  - color to change
//              [crTo]    - new color
//
//  Returns:    TRUE on success.  FALSE on failur
//
//  History:    6-24-94   stevebl   Stolen from original DFView code
//
//----------------------------------------------------------------------------

BOOL DDBChangeColor (HBITMAP hBitmap, COLORREF crFrom, COLORREF crTo)
{
    register int cx, cy;
    BITMAP       bm;
    HDC          hdcBmp, hdcMask;
    HBITMAP      hbmMask, hbmOld1, hbmOld2;
    HBRUSH       hBrush, hbrOld;

    if (!hBitmap)
          return FALSE;

    GetObject (hBitmap, sizeof (bm), &bm);
    cx = bm.bmWidth;
    cy = bm.bmHeight;

    hbmMask = CreateBitmap(cx, cy, 1, 1, NULL);
    hdcMask = CreateCompatibleDC(NULL);
    hdcBmp = CreateCompatibleDC(NULL);
    hBrush = CreateSolidBrush(crTo);

    if (!hdcMask || !hdcBmp || !hBrush || !hbmMask)
    {
        DeleteObject(hbmMask);
        DeleteObject(hBrush);
        DeleteDC(hdcMask);
        DeleteDC(hdcBmp);
        return FALSE;
    }

    hbmOld1 = (HBITMAP) SelectObject (hdcBmp,  hBitmap);
    hbmOld2 = (HBITMAP) SelectObject (hdcMask, hbmMask);
    hbrOld  = (HBRUSH) SelectObject (hdcBmp, hBrush);

    SetBkColor(hdcBmp, crFrom);
    BitBlt(hdcMask, 0, 0, cx, cy, hdcBmp,  0, 0, SRCCOPY);
    SetBkColor(hdcBmp, RGB(255,255,255));
    BitBlt(hdcBmp,  0, 0, cx, cy, hdcMask, 0, 0, DSPDxax);

    SelectObject(hdcBmp,  hbrOld);
    SelectObject(hdcBmp,  hbmOld1);
    SelectObject(hdcMask, hbmOld2);
    DeleteDC(hdcBmp);
    DeleteDC(hdcMask);
    DeleteObject(hBrush);
    DeleteObject(hbmMask);

    return TRUE;
}


BITMAPS.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       bitmaps.h
//
//  Contents:   bitmap helper functions
//
//  Classes:
//
//  Functions:  DDBChangeColor
//              LoadAndStretch
//
//  History:    6-24-94   stevebl   Created
//
//----------------------------------------------------------------------------

#ifndef __BITMAPS_H__
#define __BITMAPS_H__

#define DSPDxax 0x00E20746L

BOOL DDBChangeColor (
    HBITMAP hBitmap,
    COLORREF crFrom,
    COLORREF crTo);

BOOL LoadAndStretch (
    HINSTANCE hinst,
    HBITMAP hbmpDest,
    LPTSTR lpszResource,
    UINT cxBitmap,
    UINT cyBitmap,
    COLORREF crHigh,
    COLORREF crNorm);

#endif //__BITMAPS_H__


DFV.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       DFV.h
//
//  Contents:   globally defined constants, IDs and structures
//
//  History:    6-08-94   stevebl   Created
//
//----------------------------------------------------------------------------

#ifndef __DFV_H__
#define __DFV_H__

#include <windows.h>
#include <ole2.h>
#include "message.h"

// String Constants
#define VER_FILEDESCRIPTION_STR     "Windows NT Docfile Viewer"
#define VER_INTERNALNAME_STR        "DocFile Viewer"
#define VER_ORIGINALFILENAME_STR    "DFVIEW.exe"
#define HELPFILE_STR                "OLETools.hlp"
#define MAIN_WINDOW_CLASS_NAME      "MainDFVWindow"
#define MAIN_WINDOW_CLASS_MENU      MainDFVMenu
#define MAIN_WINDOW_CLASS_MENU_STR  "MainDFVMenu"
#define STREAM_VIEW_CLASS_NAME      "Stream Viewer"

// Menu Command Identifiers
#define IDM_EXIT                    1000
#define IDM_OPEN                    1001
#define IDM_CLOSE                   1002
#define IDM_EXPAND                  1100
#define IDM_EXPANDBRANCH            1101
#define IDM_EXPANDALL               1102
#define IDM_COLLAPSE                1103
#define IDM_COLLAPSEALL             1104
#define IDM_TOGGLE                  1105
#define IDM_HELP                    1200
#define IDM_ABOUT                   1201

// Listbox Identifiers
#define IDC_LISTBOX                 4000
#define IDC_STREAMVIEW              4001

// Bitmap Identifiers
#define BMP_STREAM                  3000
#define BMP_STORAGE                 3001
// String Identifiers
#define IDS_ERROR                   2000
#define IDS_NOHELPFILE              2001
#define IDS_OLEINCOMPATIBLE         2002
#define IDS_OLEINITFAILED           2003
#define IDS_ENUMSTATSTGFAILED       2004
#define IDS_OPENSTORAGEFAILED       2005
#define IDS_OPENSTREAMFAILED        2006
#define IDS_ENUMELEMENTSFAILED      2007
#define IDS_INSERTSTRINGFAILED      2008
#define IDS_LOADBITMAPSFAILED       2009
#define IDS_STGOPENSTORAGEFAILED    2010
#define IDS_STMSTATFAILED           2011
#define IDS_STMSEEKFAILED           2012
#define IDS_STMREADFAILED           2013
#define IDS_STMTITLETEXT            2014
#define IDS_OUTOFMEMORY             2015

#endif // __DFV_H__


MESSAGE.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       message.cxx
//
//  Contents:   Helper functions for popup message boxes.
//
//  Classes:
//
//  Functions:  MessageBoxFromSringIds
//
//  History:    6-24-94   stevebl   Created
//
//----------------------------------------------------------------------------

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

//+---------------------------------------------------------------------------
//
//  Function:   MessageBoxFromStringIds
//
//  Synopsis:   Displays a simple message box taking its text from a string
//              table instead of from user allocated strings.
//
//  Arguments:  [hwndOwner]      - window handle for the message box's owner
//              [hinst]          - instance associated with the string table
//              [idText]         - string id for the box's text string
//              [idTitle]        - string id for the box's title string
//              [fuStyle]        - style of message box
//                                 (see Windows function MessageBox for styles)
//
//  Returns:    result from MessageBox
//
//  History:    6-24-94   stevebl   Created
//
//  Notes:      Each string is limited to MAX_STRING_LENGTH characters.
//
//----------------------------------------------------------------------------

int MessageBoxFromStringIds(
    const HWND hwndOwner,
    const HINSTANCE hinst,
    const UINT idText,
    const UINT idTitle,
    const UINT fuStyle)
{
    int iReturn = 0;
    TCHAR szTitle[MAX_STRING_LENGTH];
    TCHAR szText[MAX_STRING_LENGTH];
    if (LoadString(hinst, idTitle, szTitle, MAX_STRING_LENGTH))
    {
        if (LoadString(hinst, idText, szText, MAX_STRING_LENGTH))
        {
            iReturn = MessageBox(
                hwndOwner,
                szText,
                szTitle,
                fuStyle);
        }
    }
    return(iReturn);
}

//+---------------------------------------------------------------------------
//
//  Function:   MessageBoxFromStringIdsAndArgs
//
//  Synopsis:   Creates a message box from a pair of string IDs similar
//              to MessageBoxFromStringIds.  The principle difference
//              is that idFormat is an ID for a string which is a printf
//              format string suitable for passing to wsprintf.
//              The variable argument list is combined with the format
//              string to create the text of the message box.
//
//  Arguments:  [hwndOwner] - window handle for the message box's owner
//              [hinst]     - instance associated with the string table
//              [idFormat]  - string id for the format of the box's text
//              [idTitle]   - string id for the box's title string
//              [fuStyle]   - style of the dialog box
//              [...]       - argument list for text format string
//
//  Returns:    result from MessageBox
//
//  History:    6-24-94   stevebl   Created
//
//  Notes:      Neither the composed text string nor the title must be
//              longer than MAX_STRING_LENGTH characters.
//
//----------------------------------------------------------------------------

int MessageBoxFromStringIdsAndArgs(
    const HWND hwndOwner,
    const HINSTANCE hinst,
    const UINT idFormat,
    const UINT idTitle,
    const UINT fuStyle, ...)
{
    int iReturn = 0;
    va_list arglist;
    va_start(arglist, fuStyle);
    TCHAR szTitle[MAX_STRING_LENGTH];
    TCHAR szText[MAX_STRING_LENGTH];
    TCHAR szFormat[MAX_STRING_LENGTH];
    if (LoadString(hinst, idTitle, szTitle, MAX_STRING_LENGTH))
    {
        if (LoadString(hinst, idFormat, szFormat, MAX_STRING_LENGTH))
        {
            wvsprintf(szText, szFormat, arglist);
            iReturn = MessageBox(
                hwndOwner,
                szText,
                szTitle,
                fuStyle);
        }
    }
    return(iReturn);
}


MESSAGE.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       message.h
//
//  Contents:   Helper functions for popup message boxes.
//
//  Classes:
//
//  Functions:  MessageBoxFromStringIds
//
//  History:    6-24-94   stevebl   Created
//
//----------------------------------------------------------------------------

#ifndef __MESSAGE_H__
#define __MESSAGE_H__

#define MAX_STRING_LENGTH           256

int MessageBoxFromStringIds(
    const HWND hwndOwner,
    const HINSTANCE hinst,
    const UINT idText,
    const UINT idTitle,
    const UINT fuStyle);

int MessageBoxFromStringIdsAndArgs(
    const HWND hwndOwner,
    const HINSTANCE hinst,
    const UINT idText,
    const UINT idTitle,
    const UINT fuStyle,
    ...);

#endif //__MESSAGE_H__


MWCLASS.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       mwclass.cxx
//
//  Contents:   implementation for the main window class
//
//  Classes:    CMainWindow
//
//  Functions:  Exists
//
//  History:    6-08-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include "dfv.h"
#include "mwclass.h"
#include "about.h"
#include "bitmaps.h"
#include "strmvwr.h"
#include <assert.h>

#define BITMAPWIDTH 16
#define BITMAPHEIGHT 16
#define INDENT BITMAPWIDTH
#define ROOTSTGSTR OLESTR(" \\ - ")

//+---------------------------------------------------------------------------
//
//  Member:     CMainWindow::CMainWindow
//
//  Synopsis:   constructor
//
//  History:    6-27-94   stevebl   Created
//
//----------------------------------------------------------------------------

CMainWindow::CMainWindow()
{
    _hlb = NULL;
    _hbmpStorage = _hbmpStream = NULL;
}

//+---------------------------------------------------------------------------
//
//  Member:     CMainWindow::~CMainWindow
//
//  Synopsis:   destructor
//
//  History:    6-27-94   stevebl   Created
//
//  Notes:      Destruction of the main window indicates that the app should
//              quit.
//
//----------------------------------------------------------------------------

CMainWindow::~CMainWindow()
{
    DeleteObject(_hbmpStorage);
    DeleteObject(_hbmpStream);
    PostQuitMessage(0);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMainWindow::InitInstance
//
//  Synopsis:   Instantiates an instance of the docfile viewer window.
//
//  Arguments:  [hInstance] - instance of the app
//              [nCmdShow]  - command to pass to ShowWindow
//
//  Returns:    TRUE on success
//              FALSE on failure
//
//  History:    6-27-94   stevebl   Created
//
//  Notes:      This method must be called only once, immediately after
//              class construction.
//
//----------------------------------------------------------------------------

BOOL CMainWindow::InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    // Note, the Create method sets the _hwnd member for me so I don't
    // need to set it myself.
    if (!Create(
        TEXT(MAIN_WINDOW_CLASS_NAME),
        TEXT(VER_INTERNALNAME_STR),
        WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX
            | WS_MAXIMIZEBOX | WS_THICKFRAME,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        NULL,
        NULL,
        hInstance))
    {
        return(FALSE);
    }

    ShowWindow(_hwnd, nCmdShow);
    UpdateWindow(_hwnd);

    // build a path to where the help file should be
    // (it should be in the same directory as the .EXE)
    // first get the exe's path
    DWORD cch = GetModuleFileName(hInstance, _szHelpFile, MAX_PATH);
    // then back up past the name of the application
    while (cch > 0 &&
        _szHelpFile[cch - 1] != TEXT('\\') &&
        _szHelpFile[cch - 1] != TEXT('/') &&
        _szHelpFile[cch - 1] != TEXT(':'))
    {
        cch--;
    }
    // finally copy the help file's name at the end of the path
    lstrcpy(&_szHelpFile[cch], TEXT(HELPFILE_STR));
    return(TRUE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMainWindow::WindowProc
//
//  Synopsis:   main window procedure
//
//  Arguments:  [uMsg]   - Windows message
//              [wParam] - first message parameter
//              [lParam] - second message parameter
//
//  History:    6-27-94   stevebl   Created
//
//  Notes:      See CHlprWindow for a description of how this method gets
//              called by the global WinProc.
//
//----------------------------------------------------------------------------

LRESULT CMainWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_CREATE:
        {
            RECT rc;
            GetClientRect(_hwnd, &rc);
            // record the width before we create the list box
            _wWidth = (WORD) rc.right;

            HDC hdc = GetDC(_hwnd);
            GetTextMetrics(hdc, &_tm);
            ReleaseDC(_hwnd, hdc);

            // create the child listbox
            _hlb = CreateWindow(
                TEXT("listbox"),
                TEXT(""),
                LBS_OWNERDRAWFIXED |
                    WS_CHILD |
                    WS_VISIBLE |
                    WS_VSCROLL |
                    LBS_NOTIFY |
                    LBS_NOINTEGRALHEIGHT |
                    LBS_WANTKEYBOARDINPUT,
                0,
                0,
                rc.right,
                rc.bottom,
                _hwnd,
                (HMENU) IDC_LISTBOX,
                _hInstance,
                NULL);
            if (NULL == _hlb)
            {
                // abort window creation
                return(-1);
            }
            SetFocus(_hlb);

            if (!LoadBitmaps())
            {
                MessageBoxFromStringIds(
                    _hwnd,
                    _hInstance,
                    IDS_LOADBITMAPSFAILED,
                    IDS_ERROR,
                    MB_OK | MB_ICONEXCLAMATION);
                return(-1);
            }
            _crTextHigh = GetSysColor(COLOR_HIGHLIGHTTEXT);
            _crTextNorm = GetSysColor(COLOR_WINDOWTEXT);
            _crBkHigh = GetSysColor(COLOR_HIGHLIGHT);
            _crBkNorm = GetSysColor(COLOR_WINDOW);

            // Fill in the File's OPENFILENAME structure
            _szFileName[0] = 0;
            _szFileTitle[0] = 0;
            _ofnFile.lStructSize = sizeof(OPENFILENAME);
            _ofnFile.hwndOwner = _hwnd;
            _ofnFile.lpstrFilter = TEXT("All Files (*.*)\0*.*\0Doc Files (*.DFL;*.OL?)\0*.DFL;*.OL;\0");
            _ofnFile.lpstrCustomFilter = NULL;
            _ofnFile.nMaxCustFilter = 0;
            _ofnFile.nFilterIndex = 1;
            _ofnFile.lpstrFile = _szFileName;
            _ofnFile.nMaxFile = MAX_PATH;
            _ofnFile.lpstrInitialDir = NULL;
            _ofnFile.lpstrFileTitle = _szFileTitle;
            _ofnFile.nMaxFileTitle = MAX_PATH;
            _ofnFile.lpstrTitle = TEXT("Open DocFile");
            _ofnFile.lpstrDefExt = NULL;
            _ofnFile.Flags = OFN_HIDEREADONLY;
        }
        break;
    case WM_SETFOCUS:
        {
            if (_hlb)
            {
                SetFocus(_hlb);
            }
        break;
        }
    case WM_MEASUREITEM:
        {
            LPMEASUREITEMSTRUCT lpmis = (LPMEASUREITEMSTRUCT) lParam;
            if (BITMAPHEIGHT < _tm.tmHeight + _tm.tmExternalLeading)
            {
                lpmis->itemHeight = _tm.tmHeight + _tm.tmExternalLeading;
            }
            else
            {
                lpmis->itemHeight = BITMAPHEIGHT;
            }
            lpmis->itemWidth = _wWidth;
            return(TRUE);
        }
    case WM_DELETEITEM:
        if (IDC_LISTBOX == wParam)
        {
            DeleteElement((LISTBOXELEMENT *)((LPDELETEITEMSTRUCT)lParam)->itemData);
        }
        break;
    case WM_DRAWITEM:
        {
            LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT) lParam;
            if (-1 == lpdis->itemID)
            {
                // no items in the listbox
                break;
            }
            // draw the item
            switch (lpdis->itemAction)
            {
            case ODA_SELECT:
            case ODA_DRAWENTIRE:
                {
                    // draw the listbox item

                    LISTBOXELEMENT * plbe = (LISTBOXELEMENT *) lpdis->itemData;
                    assert(plbe);

                    // compute the starting point
                    int x = lpdis->rcItem.left + plbe->uIndent * INDENT;
                    int y = lpdis->rcItem.top;

                    // paint the bitmap
                    HDC hMemDC = CreateCompatibleDC(lpdis->hDC);
                    HBITMAP hbmp;
                    switch (plbe->uType)
                    {
                    case LBE_STORAGE:
                        hbmp = _hbmpStorage;
                        break;
                    case LBE_STREAM:
                        hbmp = _hbmpStream;
                        break;
                    default:
                        assert(!"Unsupported type");
                        hbmp = _hbmpStream;
                        break;
                    }
                    HBITMAP hbmpOld = (HBITMAP) SelectObject(hMemDC, hbmp);
                    int nBitmap = ((lpdis->itemState & ODS_SELECTED) ? 0 : 2) +
                        plbe->fOpen;
                    BitBlt(
                        lpdis->hDC,
                        x,
                        y,
                        BITMAPWIDTH,
                        _cyBitmap,
                        hMemDC,
                        nBitmap * BITMAPWIDTH, 0, SRCCOPY);
                    SelectObject(hMemDC, hbmpOld);
                    DeleteDC(hMemDC);
                    x += BITMAPWIDTH;

                    // paint the text
                    SIZE sizeText;
                    RECT rcItem = lpdis->rcItem;
                    rcItem.left = x;

                    // Note that pwcsName is an OLEStr and
                    // is therefore always wide under WIN32.
                    GetTextExtentPoint32W(
                        lpdis->hDC,
                        plbe->pwcsName,
                        wcslen(plbe->pwcsName),
                        &sizeText);
                    rcItem.right = x + sizeText.cx;
                    COLORREF crOldText = SetTextColor(
                        lpdis->hDC,
                        (lpdis->itemState & ODS_SELECTED) ? _crTextHigh : _crTextNorm);
                    COLORREF crOldBack = SetBkColor(
                        lpdis->hDC,
                        (lpdis->itemState & ODS_SELECTED) ? _crBkHigh : _crBkNorm);
                    ExtTextOutW(
                        lpdis->hDC,
                        x,
                        y,
                        ETO_OPAQUE | ETO_CLIPPED,
                        &rcItem,
                        plbe->pwcsName,
                        wcslen(plbe->pwcsName),
                        NULL);
                    SetTextColor(lpdis->hDC, crOldText);
                    SetBkColor(lpdis->hDC, crOldBack);
                }
                break;
            case ODA_FOCUS:
                break;
            }
            return(TRUE);
        }
    case WM_VKEYTOITEM:
        if ((HWND)lParam == _hlb)
        {
            if (LOWORD(wParam) == VK_RETURN)
            {
                if(SendMessage(_hlb, LB_GETCOUNT, 0, 0))
                {
                    Toggle(SendMessage(_hlb, LB_GETCURSEL, 0, 0));
                    InvalidateRect(_hlb, NULL, FALSE);
                }
                return(-2);
            }
            return(-1);
        }
        break;
    case WM_SIZE:
        {
            _wWidth = LOWORD(lParam);
            WORD wHeight = HIWORD(lParam);
            if (_hlb)
            {
                SetWindowPos(_hlb, NULL, 0, 0, _wWidth, wHeight, SWP_NOMOVE | SWP_NOZORDER);
            }
            break;
        }
    case WM_COMMAND:
        if ((LOWORD(wParam) == IDC_LISTBOX) && ((HWND) lParam == _hlb))
        {
            switch (HIWORD(wParam))
            {
                case LBN_DBLCLK:
                    {
                        if(SendMessage(_hlb, LB_GETCOUNT, 0, 0))
                        {
                            Toggle(SendMessage(_hlb, LB_GETCURSEL, 0, 0));
                            InvalidateRect(_hlb, NULL, FALSE);
                        }
                    }
                    break;
                default:
                    break;
            }
        }
        return DoMenu(wParam, lParam);
    case WM_QUIT:
    case WM_CLOSE:
    default:
        return(DefWindowProc(_hwnd, uMsg, wParam, lParam));
    }
    return(FALSE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMainWindow::DoMenu
//
//  Synopsis:   implements the main menu commands
//
//  Arguments:  [wParam] - first window parameter
//              [lParam] - second window parameter
//
//  History:    6-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

LRESULT CMainWindow::DoMenu(WPARAM wParam, LPARAM lParam)
{
    switch (LOWORD(wParam))
    {
    case IDM_OPEN:
        if (!GetOpenFileName((LPOPENFILENAME)&_ofnFile))
        {
            return(FALSE);
        }
        OpenFile();
        break;
    case IDM_CLOSE:
        SendMessage(_hlb, LB_RESETCONTENT, 0, 0);
        break;
    case IDM_EXPAND:
        if(SendMessage(_hlb, LB_GETCOUNT, 0, 0))
        {
            Expand(SendMessage(_hlb, LB_GETCURSEL, 0, 0));
            InvalidateRect(_hlb, NULL, FALSE);
        }
        break;
    case IDM_EXPANDBRANCH:
        if(SendMessage(_hlb, LB_GETCOUNT, 0, 0))
        {
            ExpandAll(SendMessage(_hlb, LB_GETCURSEL, 0, 0));
            InvalidateRect(_hlb, NULL, FALSE);
        }
        break;
    case IDM_EXPANDALL:
        if(SendMessage(_hlb, LB_GETCOUNT, 0, 0))
        {
            ExpandAll(0);
            InvalidateRect(_hlb, NULL, FALSE);
        }
        break;
    case IDM_COLLAPSE:
        if(SendMessage(_hlb, LB_GETCOUNT, 0, 0))
        {
            Collapse(SendMessage(_hlb, LB_GETCURSEL, 0, 0));
            InvalidateRect(_hlb, NULL, FALSE);
        }
        break;
    case IDM_COLLAPSEALL:
        if(SendMessage(_hlb, LB_GETCOUNT, 0, 0))
        {
            Collapse(0);
            SendMessage(_hlb, LB_SETCURSEL, 0, 0);
            InvalidateRect(_hlb, NULL, FALSE);
        }
        break;
    case IDM_TOGGLE:
        if (SendMessage(_hlb, LB_GETCOUNT, 0, 0))
        {
            Toggle(SendMessage(_hlb, LB_GETCURSEL, 0, 0));
            InvalidateRect(_hlb, NULL, FALSE);
        }
    case IDM_EXIT:
        SendMessage(_hwnd, WM_CLOSE, 0, 0);
        break;
    case IDM_HELP:
        if (!Exists(_szHelpFile))
        {
            MessageBoxFromStringIds(
                _hwnd,
                _hInstance,
                IDS_NOHELPFILE,
                IDS_ERROR,
                MB_OK | MB_ICONEXCLAMATION);
        }
        else
        {
            WinHelp(_hwnd, _szHelpFile, HELP_CONTEXT, 3);
        }
        break;
    case IDM_ABOUT:
        {
            CAbout dlgAbout;
            dlgAbout.ShowDialog(_hInstance, MAKEINTRESOURCE(IDM_ABOUT), _hwnd);
        }
        break;
    default:
        return(DefWindowProc(_hwnd, WM_COMMAND, wParam, lParam));
    }
    return(FALSE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMainWindow::Expand
//
//  Synopsis:   Expands a level of the tree.
//
//  Arguments:  [lIndex] - index of the item that needs to be expanded
//
//  History:    6-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

void CMainWindow::Expand(long lIndex)
{
    LISTBOXELEMENT * pElement = (LISTBOXELEMENT *) SendMessage(_hlb, LB_GETITEMDATA, lIndex, 0);
    if (pElement->fOpen)
    {
        // this element has already been expanded
        return;
    }
    SendMessage(_hlb, WM_SETREDRAW, FALSE, 0);
    switch (pElement->uType)
    {
    case LBE_STORAGE:
        {
            // create entries for all of its children
            IStorage * pstg = (IStorage *) pElement->pInterface;
            IEnumSTATSTG * penumStatStg;
            STATSTG statstg;
            HRESULT hr = pstg->EnumElements(0, NULL, 0, &penumStatStg);
            if (SUCCEEDED(hr))
            {
                pElement->fOpen = TRUE;
                pElement->nChildren = 0;
                while (hr != S_FALSE)
                {
                    hr = penumStatStg->Next(1, &statstg, NULL);
                    if (FAILED(hr))
                    {
                        MessageBoxFromStringIdsAndArgs(
                            _hwnd,
                            _hInstance,
                            IDS_ENUMSTATSTGFAILED,
                            IDS_ERROR,
                            MB_OK | MB_ICONEXCLAMATION,
                            hr);
                    }
                    else if (S_OK == hr)
                    {
                        // retrieved another member from the list
                        LISTBOXELEMENT * plbe;
                        switch (statstg.type)
                        {
                        case STGTY_STORAGE:
                            {
                                IStorage * pstg2;
                                hr = pstg->OpenStorage(
                                    statstg.pwcsName,
                                    NULL,
                                    STGM_READ | STGM_SHARE_EXCLUSIVE,
                                    NULL,
                                    0,
                                    &pstg2);
                                if (SUCCEEDED(hr))
                                {
                                    plbe = CreateStorageElement(pstg2);
                                }
                                else
                                {
                                    MessageBoxFromStringIdsAndArgs(
                                        _hwnd,
                                        _hInstance,
                                        IDS_OPENSTORAGEFAILED,
                                        IDS_ERROR,
                                        MB_OK | MB_ICONEXCLAMATION,
                                        hr);
                                    plbe = NULL;
                                }
                            }
                            break;
                        case STGTY_STREAM:
                            {
                                IStream * pstm;
                                hr = pstg->OpenStream(
                                    statstg.pwcsName,
                                    0,
                                    STGM_READ | STGM_SHARE_EXCLUSIVE,
                                    0,
                                    &pstm);
                                if (SUCCEEDED(hr))
                                {
                                    plbe = CreateStreamElement(pstm);
                                }
                                else
                                {
                                    MessageBoxFromStringIdsAndArgs(
                                        _hwnd,
                                        _hInstance,
                                        IDS_OPENSTREAMFAILED,
                                        IDS_ERROR,
                                        MB_OK | MB_ICONEXCLAMATION,
                                        hr);
                                    plbe = NULL;
                                }
                            }
                            break;
                        }
                        if (plbe)
                        {
                            plbe->pwcsName = statstg.pwcsName;
                            plbe->uIndent = pElement->uIndent + 1;
                            pElement->nChildren++;
                            if (SendMessage(_hlb, LB_INSERTSTRING, lIndex + pElement->nChildren, (LPARAM) plbe) == LB_ERRSPACE)
                            {
                                MessageBoxFromStringIds(
                                    _hwnd,
                                    _hInstance,
                                    IDS_INSERTSTRINGFAILED,
                                    IDS_ERROR,
                                    MB_OK | MB_ICONEXCLAMATION);
                                DeleteElement(plbe);
                                pElement->nChildren--;
                            }
                        }
                    }
                }
                penumStatStg->Release();
            }
            else
            {
                MessageBoxFromStringIdsAndArgs(
                    _hwnd,
                    _hInstance,
                    IDS_ENUMELEMENTSFAILED,
                    IDS_ERROR,
                    MB_OK | MB_ICONEXCLAMATION,
                    hr);
            }
        }
        break;
    case LBE_STREAM:
        // streams are only expanded if they are the currently selected list item
        if (SendMessage(_hlb, LB_GETCURSEL, 0, 0) == lIndex)
        {
            // open stream
            CStreamView * psv = new CStreamView(_hlb, pElement);
            if (psv)
            {
                pElement->fOpen = psv->InitInstance(_hInstance, SW_SHOWNORMAL);
                if (pElement->fOpen)
                {
                    pElement->hwndView = psv->GetHwnd();
                    SetFocus(pElement->hwndView);
                }
            }
        }
        break;
    }
    SendMessage(_hlb, WM_SETREDRAW, TRUE, 0);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMainWindow::Collapse
//
//  Synopsis:   collapses a level of the tree
//
//  Arguments:  [lIndex] - index of the item to be collapsed
//
//  History:    6-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

void CMainWindow::Collapse(long lIndex)
{
    LISTBOXELEMENT * pElement = (LISTBOXELEMENT *) SendMessage(_hlb, LB_GETITEMDATA, lIndex, 0);
    if (pElement->fOpen)
    {
        switch (pElement->uType)
        {
        case LBE_STORAGE:
            while (pElement->nChildren--)
            {
                Collapse(lIndex + 1);
                SendMessage(_hlb, LB_DELETESTRING, lIndex + 1, 0);
            }
            break;
        case LBE_STREAM:
            if (pElement->fOpen)
            {
                DestroyWindow(pElement->hwndView);
            }
            break;
        }
        pElement->fOpen = FALSE;
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CMainWindow::ExpandAll
//
//  Synopsis:   expands the current item and all its children
//
//  Arguments:  [lIndex] - index of the item to be expanded
//
//  History:    6-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

void CMainWindow::ExpandAll(long lIndex)
{
    LISTBOXELEMENT * pElement = (LISTBOXELEMENT *) SendMessage(_hlb, LB_GETITEMDATA, lIndex, 0);
    Expand(lIndex);
    if (pElement->uType == LBE_STORAGE)
    {
        unsigned nChildren = pElement->nChildren;
        while (nChildren--)
        {
            ExpandAll(lIndex + 1 + nChildren);
        }
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CMainWindow::Toggle
//
//  Synopsis:   toggles a node - expanding or collapsing as appropriate
//
//  Arguments:  [lIndex] - index of the node
//
//  History:    6-25-94   stevebl   Created
//
//----------------------------------------------------------------------------

void CMainWindow::Toggle(long lIndex)
{
    LISTBOXELEMENT * pElement = (LISTBOXELEMENT *) SendMessage(_hlb, LB_GETITEMDATA, lIndex, 0);
    if (pElement->fOpen)
    {
        Collapse(lIndex);
    }
    else
    {
        Expand(lIndex);
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CMainWindow::CreateStreamElement
//
//  Synopsis:   turns an IStream pointer into a listbox element
//
//  Arguments:  [pstm] - IStream pointer
//
//  Returns:    pointer to the newly created element
//              (NULL on failure)
//
//  History:    6-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

LISTBOXELEMENT * CMainWindow::CreateStreamElement(IStream * pstm)
{
    LISTBOXELEMENT * plbe = new LISTBOXELEMENT;
    if (plbe)
    {
        plbe->uType = LBE_STREAM;
        plbe->fOpen = FALSE;
        plbe->pInterface = (void *) pstm;
        plbe->uIndent = 0;
        plbe->hwndView = NULL;
    }
    else
    {
        MessageBoxFromStringIds(
            _hwnd,
            _hInstance,
            IDS_OUTOFMEMORY,
            IDS_ERROR,
            MB_OK | MB_ICONEXCLAMATION);
    }
    return(plbe);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMainWindow::CreateStorageElement
//
//  Synopsis:   turns an IStorage pointer into a listbox element
//
//  Arguments:  [pstg] - IStorage pointer
//
//  Returns:    pointer to the newly created element
//              (NULL on failure)
//
//  History:    6-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

LISTBOXELEMENT * CMainWindow::CreateStorageElement(IStorage * pstg)
{
    LISTBOXELEMENT * plbe = new LISTBOXELEMENT;
    if (plbe)
    {
        plbe->uType = LBE_STORAGE;
        plbe->fOpen = FALSE;
        plbe->pInterface = (void *) pstg;
        plbe->uIndent = 0;
        plbe->nChildren = 0;
    }
    else
    {
        MessageBoxFromStringIds(
            _hwnd,
            _hInstance,
            IDS_OUTOFMEMORY,
            IDS_ERROR,
            MB_OK | MB_ICONEXCLAMATION);
    }
    return(plbe);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMainWindow::DeleteElement
//
//  Synopsis:   deletes a listbox element and it's associated data
//
//  Arguments:  [plbe] - pointer to the element
//
//  History:    6-23-94   stevebl   Created
//
//  Notes:      This doesn't remove it from the list, it just frees up
//              the LISTBOXELEMENT pointer and it's data.
//
//----------------------------------------------------------------------------

void CMainWindow::DeleteElement(LISTBOXELEMENT * plbe)
{

    switch (plbe->uType)
    {
    case LBE_STORAGE:
        ((IStorage *) plbe->pInterface)->Release();
        break;
    case LBE_STREAM:
        if (plbe->fOpen)
        {
            DestroyWindow(plbe->hwndView);
        }
        ((IStream *) plbe->pInterface)->Release();
        break;
    }
    LPMALLOC pMalloc;
    CoGetMalloc(MEMCTX_TASK, &pMalloc);
    pMalloc->Free(plbe->pwcsName);
    pMalloc->Release();
    delete(plbe);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMainWindow::LoadBitmaps
//
//  Synopsis:   loads the Storage and Stream bitmap arrays
//
//  Returns:    TRUE on success
//              FALSE on failure
//
//  Modifies:   _hbmpStorage and _hbmpStream
//
//  History:    6-24-94   stevebl   Created from code used in original DFView
//
//----------------------------------------------------------------------------

BOOL CMainWindow::LoadBitmaps(void)
{
    //
    // Get some metrics/info
    //
    UINT cxBitmap = BITMAPWIDTH;
    _cyBitmap = BITMAPHEIGHT;
    if (_cyBitmap < (UINT) _tm.tmHeight + _tm.tmExternalLeading)
    {
        _cyBitmap = _tm.tmHeight + _tm.tmExternalLeading;
    }
    UINT crHigh   = (COLORREF) GetSysColor(COLOR_HIGHLIGHT);
    UINT crNorm   = (COLORREF) GetSysColor(COLOR_WINDOW);

    HDC hdc = CreateCompatibleDC(NULL);
    if (!hdc)
    {
        goto ERRLOADBITMAP;
    }
    _hbmpStorage = CreateBitmap(
        4 * cxBitmap,
        _cyBitmap,
        GetDeviceCaps(hdc, PLANES),
        GetDeviceCaps(hdc, BITSPIXEL),
        NULL);
    _hbmpStream = CreateBitmap(
        4 * cxBitmap,
        _cyBitmap,
        GetDeviceCaps(hdc, PLANES),
        GetDeviceCaps (hdc, BITSPIXEL),
        NULL);
   DeleteDC(hdc);
   if (!_hbmpStorage || !_hbmpStream)
   {
        goto ERRLOADBITMAP;
   }

    //
    //  Load our bitmaps, and munge them to create
    //  our 4-bitmap "arrays".  Anything RED in the loaded
    //  bitmap will be considered clear.
    //
    if (!LoadAndStretch(
        _hInstance,
        _hbmpStorage,
        MAKEINTRESOURCE(BMP_STORAGE),
        cxBitmap,
        _cyBitmap,

crHigh,
        crNorm))
    {
        goto ERRLOADBITMAP;
    }
    if (!LoadAndStretch(
        _hInstance,
        _hbmpStream,
        MAKEINTRESOURCE(BMP_STREAM),
        cxBitmap,
        _cyBitmap,
        crHigh,
        crNorm))
    {
        goto ERRLOADBITMAP;
    }
    return TRUE;

ERRLOADBITMAP:
    //
    // Bitmap loading error.
    //
    DeleteObject(_hbmpStorage);
    DeleteObject(_hbmpStream);
    _hbmpStorage = NULL;
    _hbmpStream = NULL;

    return FALSE;
}

//+---------------------------------------------------------------------------
//
//  Member:     CMainWindow::OpenFile
//
//  Synopsis:   attempts to open whatever docfile is named by _szFileName;
//
//  History:    6-24-94   stevebl   Created
//
//----------------------------------------------------------------------------

void CMainWindow::OpenFile(void)
{
    SendMessage(_hlb, LB_RESETCONTENT, 0, 0);
    IStorage * pstg;
#ifndef UNICODE
    // convert file name to a wide string
    OLECHAR wszFileName[MAX_PATH];
    mbstowcs(wszFileName, _szFileName, MAX_PATH);
#endif
    // get IStorage
    HRESULT hr = StgOpenStorage(
#ifdef UNICODE
        _szFileName,
#else
        wszFileName,
#endif
        NULL,
        STGM_TRANSACTED | STGM_READ | STGM_SHARE_DENY_WRITE,
        NULL,
        0,
        &pstg);
    if (FAILED(hr))
    {
        MessageBoxFromStringIdsAndArgs(
            _hwnd,
            _hInstance,
            IDS_STGOPENSTORAGEFAILED,
            IDS_ERROR,
            MB_OK | MB_ICONEXCLAMATION,
#ifdef UNICODE
            _szFileName
#else
            wszFileName
#endif
            );
        return;
    }
    // Storage was opened successfully.
    LISTBOXELEMENT * plbe = CreateStorageElement(pstg);
    if (plbe)
    {
        IMalloc * pMalloc;
        CoGetMalloc(MEMCTX_TASK, &pMalloc);
        plbe->pwcsName = (LPOLESTR) pMalloc->Alloc(sizeof(OLECHAR) *  (lstrlen(_szFileName) + wcslen(ROOTSTGSTR) + 1));
        pMalloc->Release();
        if (plbe->pwcsName)
        {
            wcscpy(plbe->pwcsName, ROOTSTGSTR);
#ifdef UNICODE
            wcscpy(&plbe->pwcsName[wcslen(ROOTSTGSTR)], _szFileName);
#else
            mbstowcs(&plbe->pwcsName[wcslen(ROOTSTGSTR)], _szFileName, strlen(_szFileName) + 1);
#endif
        }
        if (SendMessage(_hlb, LB_INSERTSTRING, 0, (LPARAM) plbe) == LB_ERRSPACE)
        {
            MessageBoxFromStringIds(
                _hwnd,
                _hInstance,
                IDS_INSERTSTRINGFAILED,
                IDS_ERROR,
                MB_OK | MB_ICONEXCLAMATION);
            DeleteElement(plbe);
        }
        else
        {
            SendMessage(_hlb, LB_SETCURSEL, 0, 0);
        }
    }
}

//+---------------------------------------------------------------------------
//
//  Function:   Exists
//
//  Synopsis:   simple function to test for the existance of a file
//
//  History:    6-16-93   stevebl   Created
//
//----------------------------------------------------------------------------

int Exists(TCHAR *sz)
{
    HANDLE h;
    h = CreateFile(sz,
        GENERIC_READ,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        0,
        0);
    if (h != INVALID_HANDLE_VALUE)
    {
        CloseHandle(h);
        return(1);
    }
    return (0);
}


MWCLASS.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       mwclass.h
//
//  Contents:   definition of the main window class
//
//  Classes:    CMainWindow
//
//  Functions:  Exists
//
//  History:    6-08-94   stevebl   Created
//
//----------------------------------------------------------------------------

#ifndef __MWCLASS_H__
#define __MWCLASS_H__

#include <cwindow.h>
#include <commdlg.h>

#ifdef __cplusplus

int Exists(TCHAR *sz);

// List Box Element Structure
struct TAG_LISTBOXELEMENT
{
#define LBE_STORAGE     STGTY_STORAGE
#define LBE_STREAM      STGTY_STREAM
    unsigned uType;
    BOOL fOpen;
    void * pInterface;
    union {
        ULONG nChildren;
        HWND hwndView;
    };
    unsigned uIndent;
    LPOLESTR pwcsName;
};
typedef struct TAG_LISTBOXELEMENT LISTBOXELEMENT;

//+---------------------------------------------------------------------------
//
//  Class:      CMainWindow
//
//  Purpose:    Code for the main docfile viewer window and the main menu.
//
//  Interface:  CMainWindow          -- constructor
//              InitInstance         -- instantiates the docfile viewer window
//              Toggle               -- toggles a selection
//              Expand               -- expands a selection
//              Collapse             -- collapses a selection
//              ExpandAll            -- expands an entire tree
//              OpenFile             -- trys to load the docfile in _szFileName
//
//  History:    6-27-94   stevebl   Created
//
//  Notes:      only the public interface is listed here
//
//----------------------------------------------------------------------------

class CMainWindow: public CHlprWindow
{
public:
    CMainWindow();
    BOOL InitInstance(HINSTANCE, int);
    void Toggle(long lIndex);
    void Expand(long lIndex);
    void Collapse(long lIndex);
    void ExpandAll(long lIndex);
    void OpenFile(void);
    TCHAR _szFileName[MAX_PATH];
protected:
    ~CMainWindow();
    LRESULT WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
private:
    LRESULT DoMenu(WPARAM wParam, LPARAM lParam);
    LISTBOXELEMENT * CreateStreamElement(IStream * pstm);
    LISTBOXELEMENT * CreateStorageElement(IStorage * pstg);
    void DeleteElement(LISTBOXELEMENT * plbe);
    BOOL LoadBitmaps(void);

    HWND _hlb;
    HBITMAP _hbmpStorage;
    HBITMAP _hbmpStream;
    COLORREF _crTextHigh;
    COLORREF _crBkHigh;
    COLORREF _crTextNorm;
    COLORREF _crBkNorm;
    WORD _wWidth;
    OPENFILENAME _ofnFile;
    TCHAR _szFileTitle[MAX_PATH];
    TEXTMETRIC _tm;
    UINT _cyBitmap;
    TCHAR _szHelpFile[MAX_PATH];
};

#endif // __cplusplus

#endif // __MWCLASS_H__


STRMVWR.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       stmvwr.cxx
//
//  Contents:   implementation for the stream viewer window class
//
//  Classes:    CStreamView
//
//  Functions:
//
//  History:    6-27-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include <windows.h>
#include <ole2.h>
#include "strmvwr.h"

#define BYTESPERLINE 16
#define DIVISIONPOINTS 8

//+---------------------------------------------------------------------------
//
//  Member:     CStreamView::CStreamView
//
//  Synopsis:   constructor
//
//  Arguments:  [hlb]    - handle to CMainWindow's listbox
//              [ple]    - pointer to the LISTBOXELEMENT structure for this
//                         stream
//
//  History:    6-27-94   stevebl   Created
//
//----------------------------------------------------------------------------

CStreamView::CStreamView(HWND hlb, LISTBOXELEMENT * ple)
{
    _hlbParent = hlb;
    _ple = ple;
    _hfListBox = NULL;
}

//+---------------------------------------------------------------------------
//
//  Member:     CStreamView::~CStreamView
//
//  Synopsis:   destructor
//
//  History:    6-27-94   stevebl   Created
//
//----------------------------------------------------------------------------

CStreamView::~CStreamView()
{
    _ple->fOpen = FALSE;
    if (_hfListBox)
    {
        DeleteObject(_hfListBox);
    }
    InvalidateRect(_hlbParent, NULL, FALSE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CStreamView::InitInstance
//
//  Synopsis:   creates and shows the stream viewer window
//
//  Arguments:  [hInstance] - the application's instance handle
//              [nCmdShow]  - command to pass to ShowWindow
//
//  Returns:    TRUE on success
//              FALSE on failure
//
//  History:    6-27-94   stevebl   Created
//
//  Notes:      must only be called once immediately after class construction
//
//----------------------------------------------------------------------------

BOOL CStreamView::InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    IStream * pstm = (IStream * ) _ple->pInterface;
    STATSTG statstg;
    HRESULT hr = pstm->Stat(&statstg, STATFLAG_NONAME);
    if (FAILED(hr))
    {
        MessageBoxFromStringIdsAndArgs(
            _hwnd,
            _hInstance,
            IDS_STMSTATFAILED,
            IDS_ERROR,
            MB_OK | MB_ICONEXCLAMATION,
            hr
            );
        return(FALSE);
    }
    _cbSize = statstg.cbSize.LowPart;

    TCHAR szStreamTitleText[MAX_STRING_LENGTH];
    if (!LoadString(_hInstance, IDS_STMTITLETEXT, szStreamTitleText, MAX_STRING_LENGTH))
    {
        lstrcpy(szStreamTitleText, TEXT(""));
    }

    TCHAR * szTitle = new TCHAR[lstrlen(TEXT("  [0x00000000 bytes]"))
        + lstrlen(szStreamTitleText) + wcslen(_ple->pwcsName) + 1];
    if (szTitle)
    {
        wsprintf(szTitle, TEXT("%s %ws [0x%08X bytes]"), szStreamTitleText, _ple->pwcsName, _cbSize);
    }
    // Note, the Create method sets the _hwnd member for me so I don't
    // need to set it myself.
    HWND hwnd = Create(
        TEXT(STREAM_VIEW_CLASS_NAME),
        szTitle,
        WS_OVERLAPPED
            | WS_MINIMIZEBOX
            | WS_MAXIMIZEBOX
            | WS_SYSMENU
            | WS_THICKFRAME,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        NULL,
        NULL,
        hInstance);
    delete[] szTitle;
    if (!hwnd)
    {
        return(FALSE);
    }
    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);
    return(TRUE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CStreamView::WindowProc
//
//  Synopsis:   window procedure for the stream viewer window
//
//  Arguments:  [uMsg]   - Window's message
//              [wParam] - first message parameter
//              [lParam] - second message parameter
//
//  History:    6-27-94   stevebl   Created
//
//----------------------------------------------------------------------------

LRESULT CStreamView::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_CREATE:
        {
            RECT rc;
            GetClientRect(_hwnd, &rc);
            // record the width before we create the list box
            _wWidth = (WORD) rc.right;

            HDC hdc = GetDC(_hwnd);
            LOGFONT lf;
            memset(&lf, 0, sizeof(LOGFONT));

            // Get an 8 point Courier font.
            lf.lfHeight = -MulDiv(8, GetDeviceCaps(hdc, LOGPIXELSY), 72);
            lstrcpy(lf.lfFaceName, TEXT("Courier"));
            _hfListBox = CreateFontIndirect(&lf);
            HFONT hfOld = (HFONT) SelectObject(hdc, _hfListBox);
            GetTextMetrics(hdc, &_tm);
            SelectObject(hdc, hfOld);
            ReleaseDC(_hwnd, hdc);

            // create the child listbox
            _hlb = CreateWindow(
                TEXT("listbox"),
                TEXT(""),
                LBS_OWNERDRAWFIXED |
                    WS_CHILD |
                    WS_VISIBLE |
                    WS_VSCROLL |
                    LBS_NOINTEGRALHEIGHT |
                    LBS_NODATA,
                0,
                0,
                rc.right,
                rc.bottom,
                _hwnd,
                (HMENU) IDC_LISTBOX,
                _hInstance,
                NULL);
            if (NULL == _hlb)
            {
                // abort window creation
                return(-1);
            }
            SetFocus(_hlb);

            SendMessage(
                _hlb,
                LB_SETCOUNT,
                (WPARAM)((_cbSize / BYTESPERLINE) + (_cbSize % BYTESPERLINE ? 1 : 0)),
                0);
            SendMessage(_hlb, WM_SETFONT, (WPARAM) _hfListBox, 0);
            SendMessage(_hlb, LB_SETCURSEL, 0, 0);
            _crTextHigh = GetSysColor(COLOR_HIGHLIGHTTEXT);
            _crTextNorm = GetSysColor(COLOR_WINDOWTEXT);
            _crBkHigh = GetSysColor(COLOR_HIGHLIGHT);
            _crBkNorm = GetSysColor(COLOR_WINDOW);
        }
        break;
    case WM_SETFOCUS:
        {
            if (_hlb)
            {
                SetFocus(_hlb);
            }
        break;
        }
    case WM_MEASUREITEM:
        {
            LPMEASUREITEMSTRUCT lpmis = (LPMEASUREITEMSTRUCT) lParam;
            lpmis->itemHeight = _tm.tmHeight + _tm.tmExternalLeading;
            lpmis->itemWidth = _wWidth;
            return(TRUE);
        }
    case WM_DRAWITEM:
        {
            LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT) lParam;
            if (-1 == lpdis->itemID)
            {
                // no items in the listbox
                break;
            }
            // draw the item
            switch (lpdis->itemAction)
            {
            case ODA_SELECT:
            case ODA_DRAWENTIRE:
                {
                    // draw the listbox item
                    LONG lStartByte = lpdis->itemID * BYTESPERLINE;

                    // NOTE:  The following string is actually a bit larger
                    // than it has to be.  The minimum size for this string
                    // is acutally somewhere between 5 and 6 bytes for each
                    // byte (plus 12) in the stream that is to be displayed.
                    // Doing it this way wastes a little space but it's much
                    // simpler, it's faster, and it's less prone to error
                    // than dynamically computing it each time.
                    TCHAR szLine[BYTESPERLINE * 6 + 12];

                    unsigned cch;
                    wsprintf(szLine, TEXT("0x%08X:"), lStartByte);
                    for (cch = BYTESPERLINE * 6 + 12; (unsigned) lstrlen(TEXT("0x12345678:")) < cch--; )
                    {
                        szLine[cch] = TEXT(' ');
                    }

                    IStream * pstm = (IStream *) _ple->pInterface;
                    BYTE rgb[BYTESPERLINE];
                    LARGE_INTEGER dlibMove;
                    dlibMove.HighPart = 0;
                    // If we ever get a stream that's big enough to require
                    // that the HiPart get set, then nobody would want to
                    // browse that much data anyway!
                    dlibMove.LowPart = lStartByte;

                    HRESULT hr = pstm->Seek(dlibMove, STREAM_SEEK_SET, NULL);
                    if (FAILED(hr))
                    {
                        MessageBoxFromStringIdsAndArgs(
                            _hwnd,
                            _hInstance,
                            IDS_STMSEEKFAILED,
                            IDS_ERROR,
                            MB_OK | MB_ICONEXCLAMATION,
                            hr
                            );
                        return(FALSE);
                    }
                    ULONG cbRead;
                    hr = pstm->Read(rgb, BYTESPERLINE, &cbRead);
                    if (FAILED(hr))
                    {
                        MessageBoxFromStringIdsAndArgs(
                            _hwnd,
                            _hInstance,
                            IDS_STMREADFAILED,
                            IDS_ERROR,
                            MB_OK | MB_ICONEXCLAMATION,
                            hr
                            );
                        return(FALSE);
                    }

                    for (cch = 0; cch < cbRead; cch++)
                    {
                        TCHAR szTemp[3];
                        wsprintf(szTemp, TEXT("%02X"), rgb[cch]);
                        szLine[cch * 3 + cch / DIVISIONPOINTS + 12] = szTemp[0];
                        szLine[cch * 3 + cch / DIVISIONPOINTS + 13] = szTemp[1];
                        char ch = rgb[cch];
                        if (ch < ' ')
                        {
                            ch = '.';
                        }
                        // this is a little trick to get the character in the
                        // correct type of string (UNICODE or not)
                        wsprintf(szTemp, TEXT("%c"), ch);
                        szLine[cch + 13 + cch / DIVISIONPOINTS + 3 * BYTESPERLINE + BYTESPERLINE / DIVISIONPOINTS] = szTemp[0];
                    }
                    szLine[cch + 13 + cch / DIVISIONPOINTS + 3 * BYTESPERLINE + BYTESPERLINE / DIVISIONPOINTS] = 0;

                    SIZE sizeText;
                    RECT rcItem = lpdis->rcItem;

                    GetTextExtentPoint32(
                        lpdis->hDC,
                        szLine,
                        lstrlen(szLine),
                        &sizeText);
                    rcItem.right = rcItem.left + sizeText.cx;
                    COLORREF crOldText = SetTextColor(
                        lpdis->hDC,
                        (lpdis->itemState & ODS_SELECTED) ? _crTextHigh : _crTextNorm);
                    COLORREF crOldBack = SetBkColor(
                        lpdis->hDC,
                        (lpdis->itemState & ODS_SELECTED) ? _crBkHigh : _crBkNorm);
                    ExtTextOut(
                        lpdis->hDC,
                        rcItem.left,
                        rcItem.top,
                        ETO_OPAQUE | ETO_CLIPPED,
                        &rcItem,
                        szLine,
                        lstrlen(szLine),
                        NULL);
                    SetTextColor(lpdis->hDC, crOldText);
                    SetBkColor(lpdis->hDC, crOldBack);
                }
                break;
            case ODA_FOCUS:
                break;
            }
            return(TRUE);
        }
    case WM_SIZE:
        {
            _wWidth = LOWORD(lParam);
            WORD wHeight = HIWORD(lParam);
            if (_hlb)
            {
                SetWindowPos(_hlb, NULL, 0, 0, _wWidth, wHeight, SWP_NOMOVE | SWP_NOZORDER);
            }
            break;
        }
    case WM_COMMAND:
    case WM_QUIT:
    case WM_CLOSE:
    default:
        return(DefWindowProc(_hwnd, uMsg, wParam, lParam));
    }
    return(FALSE);
}


STRMVWR.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       strmvwr.h
//
//  Contents:   Stream viewer window class definition.
//
//  Classes:    CStreamView
//
//  Functions:
//
//  History:    6-27-94   stevebl   Created
//
//----------------------------------------------------------------------------

#ifndef __STRMVWR_H__
#define __STRMVWR_H__
#include <cwindow.h>
#include "dfv.h"
#include "mwclass.h"

#ifdef __cplusplus

//+---------------------------------------------------------------------------
//
//  Class:      CStreamView
//
//  Purpose:    implements a stream viewer window
//
//  Interface:  CStreamView  -- constructor
//              InitInstance -- instantiates an instance of the viewer
//
//  History:    6-27-94   stevebl   Created
//
//  Notes:      only the public interface is described by this comment
//
//----------------------------------------------------------------------------

class CStreamView: public CHlprWindow
{
public:
    CStreamView(HWND hlb, LISTBOXELEMENT * ple);
    BOOL InitInstance(HINSTANCE hInst, int nCmdShow);
protected:
    ~CStreamView();
    LRESULT WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
private:
    HWND _hlbParent;
    HWND _hlb;
    LONG _lIndex;
    LISTBOXELEMENT * _ple;
    COLORREF _crTextHigh;
    COLORREF _crBkHigh;
    COLORREF _crTextNorm;
    COLORREF _crBkNorm;
    WORD _wWidth;
    DWORD _cbSize;
    TEXTMETRIC _tm;
    HFONT _hfListBox;
};

#endif // __cplusplus
#endif // __STRMVWR_H__


WINMAIN.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       winmain.cxx
//
//  Contents:   main entry point for Stereo Shop
//
//  Classes:
//
//  Functions:  WinMain
//
//  History:    6-08-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include "dfv.h"
#include "mwclass.h"
#include <ole2ver.h>

//+---------------------------------------------------------------------------
//
//  Function:   InitApplication
//
//  Synopsis:   initializes the application and registers its window class
//              (called once for all instances)
//
//  Arguments:  [hInstance] - handle to the first instance
//
//  Returns:    TRUE on success
//
//  History:    4-11-94   stevebl   Created for MFract
//              6-08-94   stevebl   Stolen from MFract
//
//----------------------------------------------------------------------------

BOOL InitApplication(HINSTANCE hInstance)
{
    WNDCLASS wc;

    wc.style = 0;
    wc.lpfnWndProc = &WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(hInstance, TEXT("StreamIcon"));
    wc.hCursor = (HCURSOR) LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = TEXT(STREAM_VIEW_CLASS_NAME);
    ATOM aReturn = RegisterClass(&wc);
    if (!aReturn)
    {
        return(aReturn);
    }
    wc.style = CS_DBLCLKS;
    wc.hIcon = LoadIcon(hInstance, TEXT("AppIcon"));
    wc.lpszMenuName = TEXT(MAIN_WINDOW_CLASS_MENU_STR);
    wc.lpszClassName = TEXT(MAIN_WINDOW_CLASS_NAME);
    return(RegisterClass(&wc));
}

//+---------------------------------------------------------------------------
//
//  Function:   WinMain
//
//  Synopsis:   main window proceedure
//
//  Arguments:  [hInstance]     - instance handle
//              [hPrevInstance] - handle of the previous instance (if any)
//              [lpCmdLine]     - pointer to the command line
//              [nCmdShow]      - show state
//
//  History:    4-11-94   stevebl   Created for MFract
//              6-08-94   stevebl   Stolen from MFract
//
//  Notes:      initializes application and starts message loop
//
//----------------------------------------------------------------------------

extern "C" int PASCAL WinMain(HINSTANCE hInstance,
            HINSTANCE hPrevInstance,
            LPSTR lpCmdLine,
            int nCmdShow)
{
    DWORD dwBuildVersion = CoBuildVersion();
    if (HIWORD(dwBuildVersion) != rmm || LOWORD(dwBuildVersion) < rup)
    {
        // alert the caller that the OLE version is incompatible
        // with this build.
        MessageBoxFromStringIdsAndArgs(
            NULL,
            hInstance,
            IDS_OLEINCOMPATIBLE,
            IDS_ERROR,
            MB_OK | MB_ICONEXCLAMATION,
            rmm, rup);
        return(FALSE);
    }
    if (FAILED(CoInitialize(NULL)))
    {
        // alert the caller that OLE couldn't be initialized
        MessageBoxFromStringIds(
            NULL,
            hInstance,
            IDS_OLEINITFAILED,
            IDS_ERROR,
            MB_OK | MB_ICONEXCLAMATION);
        return(FALSE);
    }
    if (!hPrevInstance)
    {
        if (!InitApplication(hInstance))
        {
            CoUninitialize();
            return(FALSE);
        }
    }
    CMainWindow * pw = new CMainWindow;
    if (pw == NULL)
    {
        CoUninitialize();
        return(FALSE);
    }
    if (!pw->InitInstance(hInstance, nCmdShow))
    {
        // Note, if InitInstance has failed then it would have
        // already deleted pw for me so I don't delete it here.
        // This is because when WM_CREATE returns -1 (failure)
        // Windows sends the WM_DESTROY message to the window
        // and the the CHlprWindow class destroys itself whenever
        // it receives this message.
        CoUninitialize();
        return(FALSE);
    }

    // check command line and try and load as a file

    if (strlen(lpCmdLine) > 0)
    {
#ifdef UNICODE
        mbstowcs(pw->_szFileName, lpCmdLine, MAX_PATH - 1);
#else
        strncpy(pw->_szFileName, lpCmdLine, MAX_PATH - 1);
#endif
        pw->OpenFile();
    }

    MSG msg;
    HACCEL haccel = LoadAccelerators(hInstance, TEXT("AppAccel"));
    if (haccel == NULL)
    {
        CoUninitialize();
        return(FALSE);
    }

    while (GetMessage(&msg, NULL, 0, 0))
    {
        if (!TranslateAccelerator(
            pw->GetHwnd(),
            haccel,
            &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    CoUninitialize();
    return(msg.wParam);
}


README.TXT

ActiveX Document Server Sample "DOServer"

DOServer is a very simple example of an ActiveX Document server. It
implements the minimum number of interfaces required to have an object
be an ActiveX Document.

If the application is executed without the "/embedding" switch, a dialog
box will be displayed that has options for registering the server and
creating a working file. When Register Server is selected, the
application will make all of the required entries into the registry to
register the application as an ActiveX Document server. This must be
done at least once before attempting to load a working file into a
container.

If the Create File option is selected, the user can create a new file
that can be loaded into an ActiveX Document container (such as Internet
Explorer, Microsoft Office Binder or the Framer sample in this SDK).

This ActiveX Document only implements a color feature. Once the file is
loaded into the container, the color can be selected from the Color menu
that the server adds to the container's menu.


CLSFACT.H

/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Copyright 1997 Microsoft Corporation.  All Rights Reserved.
**************************************************************************/

/******************************************************************************

   File:          ClsFact.h

   Description:   Defintions for CClassFactory.

******************************************************************************/

#ifndef CCLASSFACTORY_H
#define CCLASSFACTORY_H

/**************************************************************************
   #include statements
**************************************************************************/

#include "DOServer.h"

/**************************************************************************

   CClassFactory class definition

**************************************************************************/

class CClassFactory : public IClassFactory
{
protected:
   DWORD m_ObjRefCount;

public:
   CClassFactory();
   ~CClassFactory();

   //IUnknown methods
   STDMETHODIMP QueryInterface(REFIID, LPVOID*);
   STDMETHODIMP_(DWORD) AddRef();
   STDMETHODIMP_(DWORD) Release();

   //IClassFactory methods
   STDMETHODIMP CreateInstance(LPUNKNOWN, REFIID, LPVOID*);
   STDMETHODIMP LockServer(BOOL);
};

#endif   //CCLASSFACTORY_H


DATAOBJ.CPP

/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Copyright 1997 Microsoft Corporation.  All Rights Reserved.
**************************************************************************/

/**************************************************************************

   File:          DataObj.cpp

   Description:   CDataObject and CEnumForamtEtc implementation.

**************************************************************************/

/**************************************************************************
   #include statements
**************************************************************************/

#include "DataObj.h"

/**************************************************************************

   CDataObject::CDataObject()

**************************************************************************/

CDataObject::CDataObject(COleDocument *pOleDoc)
{
OutputDebugString(TEXT("CDataObject's constructor\n"));

m_pOleDoc = pOleDoc;

m_pDataAdviseHolder = NULL;

m_cfEmbedSource = RegisterClipboardFormat(CF_EMBEDSOURCE);
}

/**************************************************************************

   CDataObject::~CDataObject()

**************************************************************************/

CDataObject::~CDataObject(void)
{
if(m_pDataAdviseHolder)
   m_pDataAdviseHolder->Release();

OutputDebugString(TEXT("CDataObject's destructor\n"));
}

/**************************************************************************

   CDataObject::QueryInterface()

**************************************************************************/

STDMETHODIMP CDataObject::QueryInterface(REFIID riid, LPVOID *ppReturn)
{
OutputDebugString(TEXT("CDataObject::QueryInterface\n"));

return m_pOleDoc->QueryInterface(riid, ppReturn);
}

/**************************************************************************

   CDataObject::AddRef()

**************************************************************************/

STDMETHODIMP_(ULONG) CDataObject::AddRef()
{
OutputDebugString(TEXT("CDataObject::AddRef\n"));

return m_pOleDoc->AddRef();
}

/**************************************************************************

   CDataObject::Release()

**************************************************************************/

STDMETHODIMP_(ULONG) CDataObject::Release()
{
OutputDebugString(TEXT("CDataObject::Release\n"));

return m_pOleDoc->Release();
}

/**************************************************************************

   CDataObject::QueryGetData()

**************************************************************************/

STDMETHODIMP CDataObject::QueryGetData(LPFORMATETC pformatetc)
{
OutputDebugString(TEXT("CDataObject::QueryGetData\n"));

HRESULT  hr = DATA_E_FORMATETC;

// check the validity of the formatetc.
if( (pformatetc->cfFormat == m_cfEmbedSource)  &&
    (pformatetc->dwAspect == DVASPECT_CONTENT) &&
    (pformatetc->tymed == TYMED_ISTORAGE))
   {
   hr = S_OK;
   }

return hr;
}

/**************************************************************************

   CDataObject::DAdvise()

**************************************************************************/

STDMETHODIMP CDataObject::DAdvise(  LPFORMATETC pFormatetc,
                                    DWORD advf,
                                    LPADVISESINK pAdvSink,
                                    LPDWORD pdwConnection)
{
OutputDebugString(TEXT("CDataObject::DAdvise\n"));

// if no DataAdviseHolder has been created, then create one.
if(!m_pDataAdviseHolder)
   CreateDataAdviseHolder(&m_pDataAdviseHolder);

if(m_pDataAdviseHolder)
   {
   // pass on to the DataAdviseHolder
   return m_pDataAdviseHolder->Advise(this, pFormatetc, advf, pAdvSink, pdwConnection);
   }

return E_FAIL;
}

/**************************************************************************

   CDataObject::DUnadvise()

**************************************************************************/

STDMETHODIMP CDataObject::DUnadvise(DWORD dwConnection)
{
OutputDebugString(TEXT("CDataObject::DUnadvise\n"));

if(m_pDataAdviseHolder)
   {
   // pass on to the DataAdviseHolder
   return m_pDataAdviseHolder->Unadvise(dwConnection);
   }

return E_FAIL;
}

/**************************************************************************

   CDataObject::GetData()

**************************************************************************/

STDMETHODIMP CDataObject::GetData(LPFORMATETC pFE, LPSTGMEDIUM pStgMedium)
{
OutputDebugString(TEXT("CDataObject::GetData\n"));

HRESULT  hr = E_NOTIMPL;

return hr;
}

/**************************************************************************

   CDataObject::GetDataHere()

**************************************************************************/

STDMETHODIMP CDataObject::GetDataHere(LPFORMATETC pFormatEtc, LPSTGMEDIUM pStgMedium)
{
OutputDebugString(TEXT("CDataObject::GetDataHere\n"));

HRESULT  hr = DATA_E_FORMATETC;

if(pFormatEtc->cfFormat == m_cfEmbedSource)
   {
   hr = GetOleObjectDataHere( m_pOleDoc->m_pPersistStorage,
                              pFormatEtc,
                              pStgMedium);
   }

return hr;
}

/**************************************************************************

   CDataObject::GetCanonicalFormatEtc()

**************************************************************************/

STDMETHODIMP CDataObject::GetCanonicalFormatEtc(LPFORMATETC pformatetc, LPFORMATETC pformatetcOut)
{
OutputDebugString(TEXT("CDataObject::GetCanonicalFormatEtc\n"));

HRESULT hr;

if(!pformatetcOut)
   return E_INVALIDARG;

//set all out parameters to NULL.
pformatetcOut->ptd = NULL;

if(!pformatetc)
   return E_INVALIDARG;

// OLE2NOTE: we must validate that the format requested is supported
if(NOERROR != (hr = QueryGetData(pformatetc)))
   return hr;

/* OLE2NOTE: an app that is insensitive to target device (as
**    SimpSvr is) should fill in the lpformatOut parameter
**    but NULL out the "ptd" field; it should return NOERROR if the
**    input formatetc->ptd what non-NULL. this tells the caller
**    that it is NOT necessary to maintain a separate screen
**    rendering and printer rendering. if should return
**    DATA_S_SAMEFORMATETC if the input and output formatetc's are
**    identical.
*/

*pformatetcOut = *pformatetc;

if(NULL == pformatetc->ptd)
   return DATA_S_SAMEFORMATETC;

pformatetcOut->ptd = NULL;

return NOERROR;
}

/**************************************************************************

   CDataObject::SetData()

**************************************************************************/

STDMETHODIMP CDataObject::SetData(  LPFORMATETC pformatetc,
                                    LPSTGMEDIUM pmedium,
                                    BOOL fRelease)
{
OutputDebugString(TEXT("CDataObject::SetData\n"));

return DATA_E_FORMATETC;
}

/**************************************************************************

   CDataObject::EnumFormatEtc()

**************************************************************************/

STDMETHODIMP CDataObject::EnumFormatEtc(  DWORD dwDirection,
                                          LPENUMFORMATETC *ppenumFormatEtc)
{
OutputDebugString(TEXT("CDataObject::EnumFormatEtc\n"));

// need to NULL the out parameter
*ppenumFormatEtc = NULL;

if(DATADIR_SET == dwDirection)
   return E_NOTIMPL;

*ppenumFormatEtc = new CEnumFormatEtc();

if(*ppenumFormatEtc)
   {
   (*ppenumFormatEtc)->AddRef();
   return S_OK;
   }

return E_OUTOFMEMORY;
}

/**************************************************************************

   CDataObject::EnumDAdvise()

**************************************************************************/

STDMETHODIMP CDataObject::EnumDAdvise(LPENUMSTATDATA *ppenumAdvise)
{
OutputDebugString(TEXT("CDataObject::EnumDAdvise\n"));

// need to NULL the out parameter
*ppenumAdvise = NULL;

return m_pDataAdviseHolder->EnumAdvise(ppenumAdvise);
}

//////////////////////////////////////////////////////////////////////////////
//
// CEnumFormatEtc Implementation
//

/**************************************************************************

   CEnumFormatEtc::CEnumFormatEtc()

**************************************************************************/

CEnumFormatEtc::CEnumFormatEtc()
{
OutputDebugString(TEXT("CEnumFormatEtc's constructor\n"));

m_ObjRefCount = 0;

m_iCur = 0;

m_cfEmbedSource = RegisterClipboardFormat(CF_EMBEDSOURCE);
}

/**************************************************************************

   CEnumFormatEtc::~CEnumFormatEtc()

**************************************************************************/

CEnumFormatEtc::~CEnumFormatEtc(void)
{
OutputDebugString(TEXT("CEnumFormatEtc's destructor\n"));
}

/**************************************************************************

   CEnumFormatEtc::QueryInterface()

**************************************************************************/

STDMETHODIMP CEnumFormatEtc::QueryInterface(REFIID riid, LPVOID *ppReturn)
{
OutputDebugString(TEXT("CEnumFormatEtc::QueryInterface\n"));

*ppReturn = NULL;

if(IsEqualIID(riid, IID_IUnknown))
   {
   *ppReturn = (LPVOID)this;
   }
else if(IsEqualIID(riid, IID_IEnumFORMATETC))
   {
   *ppReturn = (LPVOID)this;
   }

if(NULL != *ppReturn)
   {
   ((LPUNKNOWN)*ppReturn)->AddRef();
   return NOERROR;
   }

return E_NOINTERFACE;
}

/**************************************************************************

   CEnumFormatEtc::AddRef()

**************************************************************************/

STDMETHODIMP_(ULONG) CEnumFormatEtc::AddRef(void)
{
OutputDebugString(TEXT("CEnumFormatEtc::AddRef\n"));

return ++m_ObjRefCount;
}

/**************************************************************************

   CEnumFormatEtc::Release()

**************************************************************************/

STDMETHODIMP_(ULONG) CEnumFormatEtc::Release(void)
{
TCHAR szText[MAX_PATH];
wsprintf(szText, TEXT("CEnumFormatEtc::Release - ref count will be %d\n"), m_ObjRefCount - 1);
OutputDebugString(szText);

if(0 == --m_ObjRefCount)
   {
   delete this;
   return 0;
   }

return m_ObjRefCount;
}

/**************************************************************************

   CEnumFormatEtc::Next()

**************************************************************************/

STDMETHODIMP CEnumFormatEtc::Next(ULONG cRequested, LPFORMATETC pFE, ULONG* puFetched)
{
OutputDebugString(TEXT("CEnumFormatEtc::Next\n"));

if(m_iCur > 0)
   {
   *puFetched = 0;
   return S_FALSE;
   }

pFE->cfFormat = m_cfEmbedSource;
pFE->dwAspect = DVASPECT_CONTENT;
pFE->tymed == TYMED_ISTORAGE;

*puFetched = 1;

m_iCur++;

return S_OK;
}

/**************************************************************************

   CEnumFormatEtc::Skip()

**************************************************************************/

STDMETHODIMP CEnumFormatEtc::Skip(ULONG cSkip)
{
OutputDebugString(TEXT("CEnumFormatEtc::Skip\n"));

if((m_iCur + cSkip) > 1)
   return S_FALSE;

m_iCur += cSkip;

return S_OK;
}

/**************************************************************************

   CEnumFormatEtc::Reset()

**************************************************************************/

STDMETHODIMP CEnumFormatEtc::Reset(void)
{
OutputDebugString(TEXT("CEnumFormatEtc::Reset\n"));

m_iCur = 0;

return S_OK;
}

/**************************************************************************

   CEnumFormatEtc::Clone()

**************************************************************************/

STDMETHODIMP CEnumFormatEtc::Clone(LPENUMFORMATETC* ppEnum)
{
OutputDebugString(TEXT("CEnumFormatEtc::Clone\n"));

CEnumFormatEtc* pNew;

*ppEnum = NULL;

// Create the clone.
pNew = new CEnumFormatEtc();
if (NULL == pNew)
   return (ResultFromScode(E_OUTOFMEMORY));

pNew->AddRef();
pNew->m_iCur = m_iCur;

*ppEnum = pNew;

return S_OK;
}


DATAOBJ.H

/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Copyright 1997 Microsoft Corporation.  All Rights Reserved.
**************************************************************************/

/**************************************************************************

   File:          DataObj.h

   Description:   CDataObject and CEnumForamtEtc definitions.

**************************************************************************/

#ifndef DATAOBJECT_H
#define DATAOBJECT_H

/**************************************************************************
   #include statements
**************************************************************************/

#include "DOServer.h"

#define CF_EMBEDSOURCE      TEXT("Embed Source")

/**************************************************************************

   CDataObject class defintion

**************************************************************************/

class COleDocument;

class CDataObject : public IDataObject
{
private:
COleDocument         *m_pOleDoc;
   LPDATAADVISEHOLDER   m_pDataAdviseHolder;
   UINT                 m_cfEmbedSource;

public:
CDataObject::CDataObject(COleDocument*);
CDataObject::~CDataObject(void);

//IUnknown methods
STDMETHODIMP QueryInterface(REFIID, LPVOID*);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();

//IDataObject methods
STDMETHODIMP DAdvise(LPFORMATETC, DWORD, LPADVISESINK, LPDWORD);
STDMETHODIMP DUnadvise(DWORD);
STDMETHODIMP EnumDAdvise(LPENUMSTATDATA*);
STDMETHODIMP EnumFormatEtc(DWORD, LPENUMFORMATETC*);
STDMETHODIMP GetCanonicalFormatEtc(LPFORMATETC, LPFORMATETC);
STDMETHODIMP GetData(LPFORMATETC, LPSTGMEDIUM);
STDMETHODIMP GetDataHere(LPFORMATETC, LPSTGMEDIUM);
STDMETHODIMP QueryGetData(LPFORMATETC);
STDMETHODIMP SetData(LPFORMATETC, LPSTGMEDIUM, BOOL);

};

/**************************************************************************

   CEnumFormatEtc class defintion

**************************************************************************/

class CEnumFormatEtc : public IEnumFORMATETC
{
private:
ULONG m_ObjRefCount; // Object reference count
ULONG   m_iCur;        // Current element
   UINT        m_cfEmbedSource;

public:
CEnumFormatEtc(void);
~CEnumFormatEtc(void);

// IUnknown members that delegate to m_pUnkOuter
STDMETHODIMP         QueryInterface(REFIID, LPVOID*);
STDMETHODIMP_(ULONG) AddRef(void);
STDMETHODIMP_(ULONG) Release(void);

// IEnumFORMATETC members
STDMETHODIMP Next(ULONG, LPFORMATETC, ULONG*);
STDMETHODIMP Skip(ULONG);
STDMETHODIMP Reset(void);
STDMETHODIMP Clone(LPENUMFORMATETC*);
};

#endif   //DATAOBJECT_H


DOSERVER.CPP

/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Copyright 1997 Microsoft Corporation.  All Rights Reserved.
**************************************************************************/

/**************************************************************************

   File:          DOServer.cpp

   Description:   General function implementation.

**************************************************************************/

/**************************************************************************
   #include statements
**************************************************************************/

#include "DOServer.h"
#include "ClsFact.h"
#include <windowsx.h>
#include <CommDlg.h>

#include <initguid.h>

// {F0FD0C00-65BA-11d0-B66F-00A0C90348D6}
DEFINE_GUID(   CLSID_SimpleDocObject,
               0xf0fd0c00,
               0x65ba,
               0x11d0,
               0xb6,
               0x6f,
               0x0,
               0xa0,
               0xc9,
               0x3,
               0x48,
               0xd6);

/**************************************************************************
   private function prototypes
**************************************************************************/

BOOL InitApp(BOOL, int);
void CenterWindow(HWND);

/**************************************************************************
   global variables
**************************************************************************/

HINSTANCE      g_hInst;
DWORD          g_DllRefCount;
DWORD          g_dwRegister;

/**************************************************************************

   WinMain

**************************************************************************/

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
BOOL  fEmbedding;
MSG   msg;

g_hInst = hInstance;

//check for the embedding switch
ParseCommandLine(lpCmdLine, &fEmbedding, NULL);

if(!fEmbedding)
   {
   DialogBox(g_hInst, MAKEINTRESOURCE(IDD_MAIN_DIALOG), NULL, (DLGPROC)MainDlgProc);
   return 0;
   }

if(!InitApp(fEmbedding, nCmdShow))
   return 0;

//DebugBreak();

//The message loop will only exit when the main object gets destroyed.
while(GetMessage(&msg, NULL, 0, 0))
   {
   TranslateMessage(&msg);
   DispatchMessage(&msg);
   }

OutputDebugString(TEXT("Exiting WinMain\n"));

if(g_dwRegister)
   CoRevokeClassObject(g_dwRegister);

CoUninitialize();

return msg.wParam;
}

/**************************************************************************

   InitApp()

**************************************************************************/

BOOL InitApp(BOOL fEmbedding, int nCmdShow)
{
//initialize OLE
if(FAILED(CoInitialize(NULL)))
   return FALSE;

CClassFactory  *pClassFactory = new CClassFactory();

if(NULL == pClassFactory)
   {
   CoUninitialize();
   return FALSE;
   }

LPUNKNOWN   pUnknown;
HRESULT     hr;

hr = pClassFactory->QueryInterface(IID_IUnknown, (LPVOID*)&pUnknown);
if(FAILED(hr))
   {
   pClassFactory->Release();
   return FALSE;
   }

//register the class object
hr = CoRegisterClassObject(   CLSID_SimpleDocObject,
                              pUnknown,
                              CLSCTX_LOCAL_SERVER,
                              REGCLS_MULTIPLEUSE,
                              &g_dwRegister);

if(FAILED(hr))
   {
   pClassFactory->Release();
   return FALSE;
   }

return TRUE;
}

/**************************************************************************

   MainDlgProc()

**************************************************************************/

BOOL CALLBACK MainDlgProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
static COleDocument  *pOleDoc;

switch(uMessage)
   {
   case WM_INITDIALOG:
      CenterWindow(hWnd);

      pOleDoc = new COleDocument();

      if(pOleDoc)
         {
         EnableWindow(GetDlgItem(hWnd, IDC_REGISTER), TRUE);
         EnableWindow(GetDlgItem(hWnd, IDC_CREATE_FILE), TRUE);
         }

      break;

   case WM_DESTROY:
      if(pOleDoc)
         {
         delete pOleDoc;
         }

      break;

   case WM_COMMAND:
      switch(GET_WM_COMMAND_ID(wParam, lParam))
         {
         case IDOK:
         case IDCANCEL:
            EndDialog(hWnd, 0);
            break;

         case IDC_REGISTER:
            if(pOleDoc)
               {
               pOleDoc->RegisterServer();
               }
            break;

         case IDC_CREATE_FILE:
            if(pOleDoc)
               {
               pOleDoc->CreateFile(hWnd);
               }
            break;
         }
      return FALSE;
   }

return FALSE;
}

/**************************************************************************

   ColorHookProc()

**************************************************************************/

BOOL CALLBACK ColorHookProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
static COleDocument  *pOleDoc;
static UINT          uSaveOK;

switch(uMessage)
   {
   case WM_INITDIALOG:
      {
      UINT  uSelect;

      pOleDoc = (COleDocument*)((LPOPENFILENAME)(lParam))->lCustData;

      uSaveOK = RegisterWindowMessage(FILEOKSTRING);

      switch(pOleDoc->m_Color)
         {
         default:
         case RED_COLOR:
            uSelect = IDC_RED;
            break;

         case GREEN_COLOR:
            uSelect = IDC_GREEN;
            break;

         case BLUE_COLOR:
            uSelect = IDC_BLUE;
            break;
         }

      CheckRadioButton(hWnd, IDC_RED, IDC_BLUE, uSelect);
      }
      break;

   case WM_DESTROY:
      break;

   default:
      if(uMessage == uSaveOK)
         {
         //Save button pressed
         if(IsDlgButtonChecked(hWnd, IDC_GREEN))
            {
            pOleDoc->m_Color = GREEN_COLOR;
            }
         else if(IsDlgButtonChecked(hWnd, IDC_BLUE))
            {
            pOleDoc->m_Color = BLUE_COLOR;
            }
         else
            {
            pOleDoc->m_Color = RED_COLOR;
            }
}

      break;
   }

return FALSE;
}

/*****************************************************************************

   CenterWindow

*****************************************************************************/

void CenterWindow(HWND hWnd)
{
RECT  rc;
int   x,
      y;

GetWindowRect(hWnd, &rc);

x = GetSystemMetrics(SM_CXSCREEN)/2 - ((rc.right - rc.left)/2);
y = GetSystemMetrics(SM_CYSCREEN)/2 - ((rc.bottom - rc.top)/2);

//don't move the window if it's already in the right position
if((x != rc.left) || (y != rc.top))
   MoveWindow(hWnd, x, y, rc.right - rc.left, rc.bottom - rc.top, TRUE);
}


DOSERVER.H

/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Copyright 1997 Microsoft Corporation.  All Rights Reserved.
**************************************************************************/

/**************************************************************************

   File:          DOServer.h

   Description:   General defintions for the DOServer project

**************************************************************************/

/**************************************************************************
   #include statements
**************************************************************************/

#include <windows.h>
#include <ole2.h>
#include <docobj.h>
#include "OleDoc.h"
#include "OleDocVw.h"
#include "OleObj.h"
#include "DataObj.h"
#include "OIPObj.h"
#include "OIPAObj.h"
#include "PerStor.h"
#include "PerFile.h"
#include "Utils.h"
#include "Resource.h"

/**************************************************************************
   function prototypes
**************************************************************************/

BOOL CALLBACK MainDlgProc(HWND, UINT, WPARAM, LPARAM);

/**************************************************************************
   global variables
**************************************************************************/

extern HINSTANCE  g_hInst;

#define ARRAYSIZE(a)    (sizeof(a)/sizeof(a[0]))

#define DEFAULT_COLOR   (RGB(128, 0, 0))
#define DEFAULT_X       100
#define DEFAULT_Y       100

#define DEFAULT_PROMPT  (L"*.sdo")
#define FILE_EXT        (TEXT(".sdo"))
#define PROG_ID         (TEXT("Simple.DocObject"))
#define DOC_NAME        (TEXT("Simple Doc Object"))

#define RED_COLOR (RGB(128, 0, 0))
#define GREEN_COLOR (RGB(0, 128, 0))
#define BLUE_COLOR (RGB(0, 0, 128))

// {F0FD0C00-65BA-11d0-B66F-00A0C90348D6}
DEFINE_GUID(   CLSID_SimpleDocObject,
               0xf0fd0c00,
               0x65ba,
               0x11d0,
               0xb6,
               0x6f,
               0x0,
               0xa0,
               0xc9,
               0x3,
               0x48,
               0xd6);


OIPAOBJ.CPP

/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Copyright 1997 Microsoft Corporation.  All Rights Reserved.
**************************************************************************/

/**************************************************************************

   File:          OIPAObj.cpp

   Description:   COleInPlaceActiveObject implementation.

**************************************************************************/

/**************************************************************************
   #include statements
**************************************************************************/

#include "OIPAObj.h"

/**************************************************************************

   COleInPlaceActiveObject::COleInPlaceActiveObject()

**************************************************************************/

COleInPlaceActiveObject::COleInPlaceActiveObject(COleDocument *pOleDoc)
{
OutputDebugString(TEXT("COleInPlaceActiveObject's constructor\n"));

m_pOleDoc = pOleDoc;

m_fEnableModeless = FALSE;
}

/**************************************************************************

   COleInPlaceActiveObject::~COleInPlaceActiveObject()

**************************************************************************/

COleInPlaceActiveObject::~COleInPlaceActiveObject()
{
OutputDebugString(TEXT("COleInPlaceActiveObject's destructor\n"));
}

/**************************************************************************

   COleInPlaceActiveObject::QueryInterface()

**************************************************************************/

STDMETHODIMP COleInPlaceActiveObject::QueryInterface(REFIID riid, LPVOID *ppReturn)
{
OutputDebugString(TEXT("COleInPlaceActiveObject::QueryInterface\n"));

return m_pOleDoc->QueryInterface(riid, ppReturn);
}

/**************************************************************************

   COleInPlaceActiveObject::AddRef()

**************************************************************************/

STDMETHODIMP_(ULONG) COleInPlaceActiveObject::AddRef()
{
OutputDebugString(TEXT("COleInPlaceActiveObject::AddRef\n"));

return m_pOleDoc->AddRef();
}

/**************************************************************************

   COleInPlaceActiveObject::Release()

**************************************************************************/

STDMETHODIMP_(ULONG) COleInPlaceActiveObject::Release()
{
OutputDebugString(TEXT("COleInPlaceActiveObject::Release\n"));

return m_pOleDoc->Release();
}

/**************************************************************************

   COleInPlaceActiveObject::OnDocWindowActivate()

**************************************************************************/

STDMETHODIMP COleInPlaceActiveObject::OnDocWindowActivate(BOOL fActivate)
{
OutputDebugString(TEXT("COleInPlaceActiveObject::OnDocWindowActivate\n"));

//If we are getting activated, merge our menus.
if(fActivate)
   {
   if(m_pOleDoc->m_pOleDocView)
      {
      m_pOleDoc->m_pOleDocView->MergeMenus();
      }
   }

return S_OK;
}

/**************************************************************************

   COleInPlaceActiveObject::OnFrameWindowActivate()

**************************************************************************/

STDMETHODIMP COleInPlaceActiveObject::OnFrameWindowActivate(BOOL fActivate)
{
OutputDebugString(TEXT("COleInPlaceActiveObject::OnFrameWindowActivate\n"));

return S_OK;
}

/**************************************************************************

   COleInPlaceActiveObject::GetWindow()

**************************************************************************/

STDMETHODIMP COleInPlaceActiveObject::GetWindow(HWND *phwnd)
{
OutputDebugString(TEXT("COleInPlaceActiveObject::GetWindow\n"));

return m_pOleDoc->GetWindow(phwnd);
}

/**************************************************************************

   COleInPlaceActiveObject::ContextSensitiveHelp()

**************************************************************************/

STDMETHODIMP COleInPlaceActiveObject::ContextSensitiveHelp(BOOL fEnterMode)
{
OutputDebugString(TEXT("COleInPlaceActiveObject::ContextSensitiveHelp\n"));

return E_NOTIMPL;
}

/**************************************************************************

   COleInPlaceActiveObject::TranslateAccelerator()

**************************************************************************/

STDMETHODIMP COleInPlaceActiveObject::TranslateAccelerator(LPMSG lpmsg)
{
OutputDebugString(TEXT("COleInPlaceActiveObject::TranslateAccelerator\n"));

// no accelerator table, return FALSE
return S_FALSE;
}

/**************************************************************************

   COleInPlaceActiveObject::ResizeBorder()

**************************************************************************/

STDMETHODIMP COleInPlaceActiveObject::ResizeBorder(   LPCRECT lprectBorder,
                                                      LPOLEINPLACEUIWINDOW lpUIWindow,
                                                      BOOL fFrameWindow)
{
OutputDebugString(TEXT("COleInPlaceActiveObject::ResizeBorder\n"));

return S_OK;
}

/**************************************************************************

   COleInPlaceActiveObject::EnableModeless()

**************************************************************************/

STDMETHODIMP COleInPlaceActiveObject::EnableModeless(BOOL fEnable)
{
OutputDebugString(TEXT("COleInPlaceActiveObject::EnableModeless\n"));

m_fEnableModeless = fEnable;

return S_OK;
}


OIPAOBJ.H

/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Copyright 1997 Microsoft Corporation.  All Rights Reserved.
**************************************************************************/

/**************************************************************************

   File:          OIPAObj.h

   Description:   COleInPlaceActiveObect definitions.

**************************************************************************/

#ifndef INPLACEACTIVEOBJECT_H
#define INPLACEACTIVEOBJECT_H

/**************************************************************************
   #include statements
**************************************************************************/

#include "DOServer.h"

/**************************************************************************

   COleInPlaceActiveObject class definition

**************************************************************************/

class COleDocument;

class COleInPlaceActiveObject : public IOleInPlaceActiveObject
{
private:
COleDocument   *m_pOleDoc;
   BOOL           m_fEnableModeless;

public:
COleInPlaceActiveObject::COleInPlaceActiveObject(COleDocument*);
COleInPlaceActiveObject::~COleInPlaceActiveObject();

   // IUnknown methods
STDMETHODIMP QueryInterface(REFIID, LPVOID*);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();

//IOleInPlaceActiveObject methods
STDMETHODIMP OnDocWindowActivate(BOOL);
STDMETHODIMP OnFrameWindowActivate(BOOL);
STDMETHODIMP GetWindow(HWND*);
STDMETHODIMP ContextSensitiveHelp(BOOL);
STDMETHODIMP TranslateAccelerator(LPMSG);
STDMETHODIMP ResizeBorder(LPCRECT, LPOLEINPLACEUIWINDOW, BOOL);
STDMETHODIMP EnableModeless(BOOL);

};

#endif   //INPLACEACTIVEOBJECT_H


OIPOBJ.CPP

/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Copyright 1997 Microsoft Corporation.  All Rights Reserved.
**************************************************************************/

/**************************************************************************

   File:          OIPObj.cpp

   Description:   COleInPlaceObject implementation.

**************************************************************************/

/**************************************************************************
   #include statements
**************************************************************************/

#include "OIPObj.h"

/**************************************************************************
   global variables
**************************************************************************/

/**************************************************************************

   COleInPlaceObject::COleInPlaceObject()

**************************************************************************/

COleInPlaceObject::COleInPlaceObject(COleDocument *pOleDoc)
{
OutputDebugString(TEXT("COleInPlaceObject's constructor\n"));

m_pOleDoc = pOleDoc;
}

/**************************************************************************

   COleInPlaceObject::~COleInPlaceObject()

**************************************************************************/

COleInPlaceObject::~COleInPlaceObject()
{
OutputDebugString(TEXT("COleInPlaceObject's destructor\n"));
}

/**************************************************************************

   COleInPlaceObject::QueryInterface()

**************************************************************************/

STDMETHODIMP COleInPlaceObject::QueryInterface(REFIID riid, LPVOID *ppReturn)
{
OutputDebugString(TEXT("COleInPlaceObject::QueryInterface\n"));

return m_pOleDoc->QueryInterface(riid, ppReturn);
}

/**************************************************************************

   COleInPlaceObject::AddRef()

**************************************************************************/

STDMETHODIMP_(ULONG) COleInPlaceObject::AddRef()
{
OutputDebugString(TEXT("COleInPlaceObject::AddRef\n"));

return m_pOleDoc->AddRef();
}

/**************************************************************************

   COleInPlaceObject::Release()

**************************************************************************/

STDMETHODIMP_(ULONG) COleInPlaceObject::Release()
{
OutputDebugString(TEXT("COleInPlaceObject::Release\n"));

return m_pOleDoc->Release();
}

/**************************************************************************

   COleInPlaceObject::InPlaceDeactivate()

**************************************************************************/

STDMETHODIMP COleInPlaceObject::InPlaceDeactivate()
{
OutputDebugString(TEXT("COleInPlaceObject::InPlaceDeactivate\n"));

return m_pOleDoc->DeactivateInPlace();
}

/**************************************************************************

   COleInPlaceObject::UIDeactivate()

**************************************************************************/

STDMETHODIMP COleInPlaceObject::UIDeactivate()
{
OutputDebugString(TEXT("COleInPlaceObject::UIDeactivate\n"));

return m_pOleDoc->DeactivateUI();
}

/**************************************************************************

   COleInPlaceObject::SetObjectRects()

**************************************************************************/

STDMETHODIMP COleInPlaceObject::SetObjectRects(LPCRECT lprcPosRect, LPCRECT lprcClipRect)
{
OutputDebugString(TEXT("COleInPlaceObject::SetObjectRects\n"));

return S_OK;
}

/**************************************************************************

   COleInPlaceObject::GetWindow()

**************************************************************************/

STDMETHODIMP COleInPlaceObject::GetWindow(HWND *phwnd)
{
OutputDebugString(TEXT("COleInPlaceObject::GetWindow\n"));

return m_pOleDoc->GetWindow(phwnd);
}

/**************************************************************************

   COleInPlaceObject::ContextSensitiveHelp()

**************************************************************************/

STDMETHODIMP COleInPlaceObject::ContextSensitiveHelp(BOOL fEnterMode)
{
OutputDebugString(TEXT("COleInPlaceObject::ContextSensitiveHelp\n"));

return E_NOTIMPL;
}

/**************************************************************************

   COleInPlaceObject::ReactivateAndUndo()

**************************************************************************/

STDMETHODIMP COleInPlaceObject::ReactivateAndUndo()
{
OutputDebugString(TEXT("COleInPlaceObject::ReactivateAndUndo\n"));

return INPLACE_E_NOTUNDOABLE;
}


OIPOBJ.H

/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Copyright 1997 Microsoft Corporation.  All Rights Reserved.
**************************************************************************/

/**************************************************************************

   File:          OIPObj.h

   Description:   COleInPlaceObject definitions.

**************************************************************************/

#ifndef OLEINPLACEOBJECT_H
#define OLEINPLACEOBJECT_H

/**************************************************************************
   #include statements
**************************************************************************/

#include "DOServer.h"

/**************************************************************************

   CPersistStorage class definition

**************************************************************************/

class COleDocument;

class COleInPlaceObject : public IOleInPlaceObject
{
private:
COleDocument   *m_pOleDoc;

public:
COleInPlaceObject::COleInPlaceObject(COleDocument*);
COleInPlaceObject::~COleInPlaceObject();

   //IUnknown methods
STDMETHODIMP QueryInterface(REFIID, LPVOID*);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();

//IOleInPlaceObject methods
STDMETHODIMP InPlaceDeactivate();
STDMETHODIMP UIDeactivate() ;
STDMETHODIMP SetObjectRects(LPCRECT, LPCRECT);
STDMETHODIMP GetWindow(HWND*) ;
STDMETHODIMP ContextSensitiveHelp(BOOL);
STDMETHODIMP ReactivateAndUndo();
};

#endif   //OLEINPLACEOBJECT_H


OLEDOC.CPP

/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Copyright 1997 Microsoft Corporation.  All Rights Reserved.
**************************************************************************/

/**************************************************************************

   File:          OleDoc.cpp

   Description:   COleDocument implementation.

**************************************************************************/

/**************************************************************************
   #include statements
**************************************************************************/

#include "OleDoc.h"
#include "comcat.h"

/**************************************************************************
   global variables
**************************************************************************/

extern DWORD   g_DllRefCount;

/**************************************************************************

   COleDocument::COleDocument()

**************************************************************************/

COleDocument::COleDocument()
{
OutputDebugString(TEXT("COleDocument's constructor\n"));

m_ObjRefCount = 0;

m_fDirty = FALSE;
m_fNoScribbleMode = FALSE;
m_pOleAdviseHolder = NULL;
m_pOleClientSite = NULL;
m_dwRegister = 0;
m_pOleDocView = NULL;
m_Color = DEFAULT_COLOR;
m_fCreated = FALSE;

m_pOleObject = new COleObject(this);

m_pDataObject = new CDataObject(this);

m_pOleInPlaceObject = new COleInPlaceObject(this);

m_pOleInPlaceActiveObject = new COleInPlaceActiveObject(this);

m_pPersistStorage = new CPersistStorage(this);

m_pPersistFile = new CPersistFile(this, m_pPersistStorage);

m_pOleDocView = new COleDocumentView(this);

g_DllRefCount++;
}

/**************************************************************************

   COleDocument::~COleDocument()

**************************************************************************/

COleDocument::~COleDocument()
{
OutputDebugString(TEXT("COleDocument's destructor\n"));

if(m_pOleAdviseHolder)
   m_pOleAdviseHolder->Release();

if(m_pOleClientSite)
   m_pOleClientSite->Release();

delete m_pOleDocView;

delete m_pPersistFile;

delete m_pPersistStorage;

delete m_pOleInPlaceActiveObject;

delete m_pOleInPlaceObject;

delete m_pDataObject;

delete m_pOleObject;

g_DllRefCount--;

PostQuitMessage(0);
}

///////////////////////////////////////////////////////////////////////////
//
// IUnknown Implementation
//

/**************************************************************************

   COleDocument::QueryInterface

**************************************************************************/

STDMETHODIMP COleDocument::QueryInterface(REFIID riid, LPVOID *ppReturn)
{
OutputDebugString(TEXT("COleDocument::QueryInterface - "));

HRESULT  hr = E_NOINTERFACE;
*ppReturn = NULL;

if(riid == IID_IUnknown)
   {
   *ppReturn = this;
   OutputDebugString(TEXT("IUnknown requested - "));
   }
else if (riid == IID_IOleDocument)
   {
   *ppReturn = this;
   OutputDebugString(TEXT("IOleDocument requested - "));
   }
else if (riid == IID_IOleDocumentView)
   {
   *ppReturn = m_pOleDocView;
   OutputDebugString(TEXT("IOleDocumentView requested - "));
   }
else if (riid == IID_IOleObject)
   {
   *ppReturn = m_pOleObject;
   OutputDebugString(TEXT("IOleObject requested - "));
   }
else if (riid == IID_IDataObject)
   {
   *ppReturn = m_pDataObject;
   OutputDebugString(TEXT("IDataObject requested - "));
   }
else if (riid == IID_IOleInPlaceObject)
   {
   *ppReturn = m_pOleInPlaceObject;
   OutputDebugString(TEXT("IOleInPlaceObject requested - "));
   }
else if (riid == IID_IOleInPlaceActiveObject)
   {
   *ppReturn = m_pOleInPlaceActiveObject;
   OutputDebugString(TEXT("IOleInPlaceActiveObject requested - "));
   }
else if((riid == IID_IPersistStorage) || (riid == IID_IPersist))
   {
   *ppReturn = m_pPersistStorage;
   OutputDebugString(TEXT("IPersistStorage requested - "));
   }
else if(riid == IID_IPersistFile)
   {
   *ppReturn = m_pPersistFile;
   OutputDebugString(TEXT("IPersistFile requested - "));
   }

if (*ppReturn)
   {
   OutputDebugString(TEXT("Interface Found\n"));
   ((LPUNKNOWN)*ppReturn)->AddRef();
   hr = S_OK;
   }
else
   {
   OutputDebugString(TEXT("No Interface - "));

   LPWSTR   pwsz;
   TCHAR    szString[MAX_PATH] = TEXT("");

   StringFromIID(riid, &pwsz);

   if(pwsz)
      {
#ifdef UNICODE
      lstrcpy(szString, pwsz);
#else
      WideCharToMultiByte( CP_ACP,
                           0,
                           pwsz,
                           -1,
                           szString,
                           ARRAYSIZE(szString),
                           NULL,
                           NULL);
#endif
      OutputDebugString(szString);
      OutputDebugString(TEXT("\n"));

      //free the string
      LPMALLOC pMalloc;
      CoGetMalloc(1, &pMalloc);
      pMalloc->Free(pwsz);
      pMalloc->Release();
      }
   }

return hr;
}

/**************************************************************************

   COleDocument::AddRef

**************************************************************************/

STDMETHODIMP_(DWORD) COleDocument::AddRef()
{
//OutputDebugString("COleDocument::AddRef\n");
TCHAR szText[MAX_PATH];
wsprintf(szText, TEXT("COleDocument::AddRef - ref count will be %d\n"), m_ObjRefCount + 1);
OutputDebugString(szText);

return ++m_ObjRefCount;
}

/**************************************************************************

   COleDocument::Release

**************************************************************************/

STDMETHODIMP_(DWORD) COleDocument::Release()
{
//OutputDebugString("COleDocument::Release\n");
TCHAR szText[MAX_PATH];
wsprintf(szText, TEXT("COleDocument::Release - ref count will be %d\n"), m_ObjRefCount - 1);
OutputDebugString(szText);

if(0 == --m_ObjRefCount)
   {
   delete this;
   return 0;
   }

return m_ObjRefCount;
}

/**************************************************************************

   COleDocument::CreateView()

**************************************************************************/

STDMETHODIMP COleDocument::CreateView( IOleInPlaceSite *pInPlaceSite,
                                       IStream *pStream,
                                       DWORD dwReserved,
                                       IOleDocumentView **ppOleDocumentView)
{
OutputDebugString(TEXT("COleDocument::CreateView\n"));

HRESULT  hr = E_FAIL;

//NULL the view pointer
*ppOleDocumentView = NULL;

//we only support one view, so fail if the view already exists
if(!m_fCreated)
   {
   //AddRef since we are giving away the pointer
   m_pOleDocView->AddRef();

   // if we were given a site, set this as the site for the view we just created
   if(pInPlaceSite)
      {
      m_pOleDocView->SetInPlaceSite(pInPlaceSite);
      }

   // if given a stream to initialize from, initialize our view state
   if(pStream)
      {
      m_pOleDocView->ApplyViewState(pStream);
      }

   *ppOleDocumentView = m_pOleDocView;

   m_fCreated = TRUE;

   hr = S_OK;
   }

return hr;
}

/**************************************************************************

   COleDocument::GetDocMiscStatus()

**************************************************************************/

STDMETHODIMP COleDocument::GetDocMiscStatus(DWORD *pdwStatus)
{
OutputDebugString(TEXT("COleDocument::GetDocMiscStatus\n"));

if(!pdwStatus)
   return E_INVALIDARG;

*pdwStatus = 0;

return S_OK;
}

/**************************************************************************

   COleDocument::EnumViews()

**************************************************************************/

STDMETHODIMP COleDocument::EnumViews(  IEnumOleDocumentViews **ppEnum,
                                       IOleDocumentView **ppView)
{
OutputDebugString(TEXT("COleDocument::EnumViews\n"));

*ppEnum = NULL;
*ppView = NULL;

return E_NOTIMPL;
}

/**************************************************************************

   COleDocument::GetWindow()

**************************************************************************/

STDMETHODIMP COleDocument::GetWindow(HWND *phwnd)
{
OutputDebugString(TEXT("COleDocument::GetWindow\n"));

if(!phwnd)
   return E_INVALIDARG;

*phwnd = NULL;

if(m_pOleDocView)
   *phwnd = m_pOleDocView->m_hwndView;

return S_OK;
}

/**************************************************************************

   COleDocument::UIDeactivate()

**************************************************************************/

STDMETHODIMP COleDocument::DeactivateUI()
{
OutputDebugString(TEXT("COleInPlaceObject::DeactivateUI\n"));

if(m_pOleDocView)
   return m_pOleDocView->DeactivateUI();

return E_FAIL;
}

/**************************************************************************

   COleDocumentView::DeactivateInPlace()

**************************************************************************/

STDMETHODIMP COleDocument::DeactivateInPlace()
{
OutputDebugString(TEXT("COleDocument::DeactivateInPlace\n"));

if(m_pOleDocView)
   return m_pOleDocView->DeactivateInPlace();

return E_FAIL;
}

/**************************************************************************

   COleDocument::RegisterServer

**************************************************************************/

typedef struct{
   HKEY  hRootKey;
   TCHAR szSubKey[MAX_PATH];
   TCHAR szValue[MAX_PATH];
}DOREGSTRUCT, *LPDOREGSTRUCT;

BOOL COleDocument::RegisterServer(void)
{
OutputDebugString("COleDocument::RegisterServer\n");

int      i;
HKEY     hKey;
LRESULT  lResult;
DWORD    dwDisp;
TCHAR    szSubKey[MAX_PATH];
TCHAR    szCLSID[MAX_PATH];
TCHAR    szApp[MAX_PATH];
LPWSTR   pwsz;
CLSID    clsid;

//get this app's CLSID in string form
m_pPersistStorage->GetClassID(&clsid);
StringFromIID(clsid, &pwsz);

if(pwsz)
   {
#ifdef UNICODE
   lstrcpy(szCLSID, pwsz);
#else
   WideCharToMultiByte( CP_ACP,
                        0,
                        pwsz,
                        -1,
                        szCLSID,
                        ARRAYSIZE(szCLSID),
                        NULL,
                        NULL);
#endif

   //free the string
   LPMALLOC pMalloc;
   CoGetMalloc(1, &pMalloc);
   pMalloc->Free(pwsz);
   pMalloc->Release();
   }

//get this app's path and file name
GetModuleFileName(NULL, szApp, ARRAYSIZE(szApp));

//register the file extension entries
{
DOREGSTRUCT FileEntries[] = { HKEY_CLASSES_ROOT,   TEXT("%s"),                         PROG_ID,
                              0,                   TEXT(""),                           TEXT("")};

for(i = 0; FileEntries[i].hRootKey; i++)
   {
   //create the sub key string - for this case, insert the file extension
   wsprintf(szSubKey, FileEntries[i].szSubKey, FILE_EXT);

   lResult = RegCreateKeyEx(  FileEntries[i].hRootKey,
                              szSubKey,
                              0,
                              NULL,
                              REG_OPTION_NON_VOLATILE,
                              KEY_WRITE,
                              NULL,
                              &hKey,
                              &dwDisp);

   if(NOERROR == lResult)
      {
      lResult = RegSetValueEx(   hKey,
                                 NULL,
                                 0,
                                 REG_SZ,
                                 (LPBYTE)FileEntries[i].szValue,
                                 lstrlen(FileEntries[i].szValue) + 1);

      RegCloseKey(hKey);
      }
   else
      return FALSE;
   }
}

{
DOREGSTRUCT ProgEntries[] = { HKEY_CLASSES_ROOT,   TEXT("%s"),                         DOC_NAME,
                              HKEY_CLASSES_ROOT,   TEXT("%s\\CLSID"),                  TEXT("%s"),
                              HKEY_CLASSES_ROOT,   TEXT("%s\\DocObject"),   TEXT(""),
                              0,                   TEXT(""),                           TEXT("")};

//register the ProgID entries
for(i = 0; ProgEntries[i].hRootKey; i++)
   {
   //create the sub key string - for this case, insert the ProgID
   wsprintf(szSubKey, ProgEntries[i].szSubKey, PROG_ID);

   lResult = RegCreateKeyEx(  ProgEntries[i].hRootKey,
                              szSubKey,
                              0,
                              NULL,
                              REG_OPTION_NON_VOLATILE,
                              KEY_WRITE,
                              NULL,
                              &hKey,
                              &dwDisp);

   if(NOERROR == lResult)
      {
      TCHAR szValue[MAX_PATH];

      //if necessary, create the value string
      wsprintf(szValue, ProgEntries[i].szValue, szCLSID);

      lResult = RegSetValueEx(   hKey,
                                 NULL,
                                 0,
                                 REG_SZ,
                                 (LPBYTE)szValue,
                                 lstrlen(szValue) + 1);

      RegCloseKey(hKey);
      }
   else
      return FALSE;
   }
}

{
DOREGSTRUCT ClsidEntries[] = {HKEY_CLASSES_ROOT,   TEXT("CLSID\\%s"),                  DOC_NAME,
                              HKEY_CLASSES_ROOT,   TEXT("CLSID\\%s\\LocalServer32"),   TEXT("%s"),
                              HKEY_CLASSES_ROOT,   TEXT("CLSID\\%s\\InprocHandler32"), TEXT("ole32.dll"),
                              HKEY_CLASSES_ROOT,   TEXT("CLSID\\%s\\DefaultIcon"),     TEXT("%s,0"),
                              HKEY_CLASSES_ROOT,   TEXT("CLSID\\%s\\DocObject"),   TEXT("0"),
                              HKEY_CLASSES_ROOT,   TEXT("CLSID\\%s\\ProgID"),   PROG_ID,
                              0,                   TEXT(""),                           TEXT("")};

//register the CLSID entries
for(i = 0; ClsidEntries[i].hRootKey; i++)
   {
   //create the sub key string - for this case, insert the file extension
   wsprintf(szSubKey, ClsidEntries[i].szSubKey, szCLSID);

   lResult = RegCreateKeyEx(  ClsidEntries[i].hRootKey,
                              szSubKey,
                              0,
                              NULL,
                              REG_OPTION_NON_VOLATILE,
                              KEY_WRITE,
                              NULL,
                              &hKey,
                              &dwDisp);

   if(NOERROR == lResult)
      {
      TCHAR szValue[MAX_PATH];

      //if necessary, create the value string
      wsprintf(szValue, ClsidEntries[i].szValue, szApp);

      lResult = RegSetValueEx(   hKey,
                                 NULL,
                                 0,
                                 REG_SZ,
                                 (LPBYTE)szValue,
                                 lstrlen(szValue) + 1);

      RegCloseKey(hKey);
      }
   else
      return FALSE;
   }
}

//register the component as insertable and a document object
ICatRegister   *pcr;
HRESULT        hr = S_OK ;

CoInitialize(NULL);

hr = CoCreateInstance(  CLSID_StdComponentCategoriesMgr,
                        NULL,
                        CLSCTX_INPROC_SERVER,
                        IID_ICatRegister,
                        (LPVOID*)&pcr);

if(SUCCEEDED(hr))
   {
   CATID rgcatid[2];

   rgcatid[0] = CATID_Insertable;
   rgcatid[1] = CATID_DocObject;

   hr = pcr->RegisterClassImplCategories(clsid, 2, rgcatid);

   pcr->Release();
   }

CoUninitialize();

return SUCCEEDED(hr);
}

/**************************************************************************

   COleDocument::CreateFile(HWND)

**************************************************************************/

BOOL COleDocument::CreateFile(HWND hwndParent)
{
OutputDebugString("COleDocument::CreateFile(HWND)\n");

OPENFILENAME   ofn;
TCHAR          szFilter[MAX_PATH];
TCHAR          szFile[MAX_PATH] = TEXT("");
LPTSTR         pTemp;

//build the filter string
pTemp = szFilter;
lstrcpy(pTemp, DOC_NAME);
pTemp += lstrlen(pTemp) + 1;
lstrcpy(pTemp, TEXT("*"));
lstrcat(pTemp, FILE_EXT);
pTemp += lstrlen(pTemp) + 1;
*pTemp = 0;

ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize         = sizeof(ofn);
ofn.hwndOwner           = hwndParent;
ofn.hInstance           = g_hInst;
ofn.lpstrFilter         = szFilter;
ofn.lpstrCustomFilter   = NULL;
ofn.nMaxCustFilter      = 0;
ofn.nFilterIndex        = 1;
ofn.lpstrFile           = szFile;
ofn.nMaxFile            = ARRAYSIZE(szFile);
ofn.lpstrFileTitle      = NULL;
ofn.nMaxFileTitle       = 0;
ofn.lpstrInitialDir     = NULL;
ofn.lpstrTitle          = NULL;
ofn.lpTemplateName      = MAKEINTRESOURCE(IDD_COLOR_DIALOG);
ofn.lpfnHook            = (LPOFNHOOKPROC)ColorHookProc;
ofn.lCustData           = (DWORD)this;
ofn.Flags               = OFN_EXPLORER |
                           OFN_OVERWRITEPROMPT |
                           OFN_HIDEREADONLY |
                           OFN_ENABLEHOOK |
                           OFN_ENABLETEMPLATE |
                           0;

if(GetSaveFileName(&ofn))
   {
   //if the extension isn't attached, attach it
   pTemp = szFile + lstrlen(szFile) - 4;
   if(lstrcmpi(pTemp, FILE_EXT))
      {
      lstrcat(szFile, FILE_EXT);
      }

   return CreateFile(szFile);
   }

return FALSE;
}

/**************************************************************************

   COleDocument::CreateFile(LPTSTR)

**************************************************************************/

BOOL COleDocument::CreateFile(LPTSTR pszFile)
{
OutputDebugString("COleDocument::CreateFile(LPTSTR)\n");

WCHAR szwFile[MAX_PATH];

#ifdef UNICODE
lstrcpy(szwFile, pszFile);
#else
MultiByteToWideChar( CP_ACP,
                     MB_PRECOMPOSED,
                     pszFile,
                     -1,
                     szwFile,
                     ARRAYSIZE(szwFile));
#endif

m_pPersistFile->Save(szwFile, FALSE);

return TRUE;
}


OLEDOC.H

/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Copyright 1997 Microsoft Corporation.  All Rights Reserved.
**************************************************************************/

/******************************************************************************

   File:          OleDoc.h

   Description:   COleDocumentView definitions.

******************************************************************************/

#ifndef OLEDOC_H
#define OLEDOC_H

#include "DOServer.h"

/**************************************************************************

   COleDocument class definition

**************************************************************************/

class COleDocumentView;
class COleObject;
class CDataObject;
class COleInPlaceObject;
class COleInPlaceActiveObject;
class CPersistStorage;
class CPersistFile;

class COleDocument : public IOleDocument
{
friend class COleDocumentView;
friend class COleObject;
friend class CDataObject;
friend class COleInPlaceObject;
friend class COleInPlaceActiveObject;
friend class CPersistStorage;

private:
   COLORREF                m_Color;
DWORD                   m_dwRegister;
LPOLECLIENTSITE         m_pOleClientSite;
   DWORD                   m_ObjRefCount;
   BOOL                    m_fDirty;
   BOOL                    m_fNoScribbleMode;
   LPOLEADVISEHOLDER       m_pOleAdviseHolder;
   BOOL                    m_fCreated;

   COleDocumentView        *m_pOleDocView;
   COleObject              *m_pOleObject;
   CDataObject             *m_pDataObject;
   COleInPlaceObject       *m_pOleInPlaceObject;
   COleInPlaceActiveObject *m_pOleInPlaceActiveObject;
   CPersistStorage         *m_pPersistStorage;
   CPersistFile            *m_pPersistFile;

public:
   COleDocument();
   ~COleDocument();

   //IUnknown methods
   STDMETHODIMP QueryInterface(REFIID, LPVOID*);
   STDMETHODIMP_(DWORD) AddRef();
   STDMETHODIMP_(DWORD) Release();

   //IOleDocument methods
   STDMETHODIMP CreateView(IOleInPlaceSite*, IStream*, DWORD, IOleDocumentView**);
   STDMETHODIMP GetDocMiscStatus(DWORD*);
   STDMETHODIMP EnumViews(IEnumOleDocumentViews**, IOleDocumentView**);

   //public utility methods
   BOOL RegisterServer(void);
   BOOL CreateFile(HWND);
   BOOL CreateFile(LPTSTR);

private:
   STDMETHODIMP GetWindow(HWND*);
   STDMETHODIMP DeactivateUI();
   STDMETHODIMP DeactivateInPlace();

friend BOOL CALLBACK ColorHookProc(HWND, UINT, WPARAM, LPARAM);
};

#endif   //OLEDOC_H


OLEDOCVW.CPP

/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Copyright 1997 Microsoft Corporation.  All Rights Reserved.
**************************************************************************/

/**************************************************************************

   File:          OleDocVw.cpp

   Description:   COleDocumentView implementation.

**************************************************************************/

/**************************************************************************
   #include statements
**************************************************************************/

#include "OleDocVw.h"
#include <windowsx.h>

/**************************************************************************

   COleDocumentView::COleDocumentView()

**************************************************************************/

COleDocumentView::COleDocumentView(COleDocument *pOleDoc)
{
OutputDebugString(TEXT("COleDocumentView's constructor\n"));

m_pInPlaceSite = NULL;
m_pInPlaceFrame = NULL;
m_pInPlaceUIWindow = NULL;
m_hwndView = NULL;
m_fUIActive = FALSE;
m_fInPlaceActive = FALSE;
m_hSharedMenu = NULL;
m_hOleMenu = NULL;

m_pOleDoc = pOleDoc;

CreateViewWindow();
}

/**************************************************************************

   COleDocumentView::~COleDocumentView()

**************************************************************************/

COleDocumentView::~COleDocumentView()
{
OutputDebugString(TEXT("COleDocumentView's destructor\n"));

if(m_pInPlaceFrame)
   m_pInPlaceFrame->Release();

if(m_pInPlaceUIWindow)
   m_pInPlaceUIWindow->Release();

if(m_pInPlaceSite)
   m_pInPlaceSite->Release();
}

///////////////////////////////////////////////////////////////////////////
//
// IUnknown Implementation
//

/**************************************************************************

   COleDocumentView::QueryInterface

**************************************************************************/

STDMETHODIMP COleDocumentView::QueryInterface(  REFIID riid,
                                                LPVOID *ppReturn)
{
OutputDebugString(TEXT("COleDocumentView::QueryInterface\n"));

return m_pOleDoc->QueryInterface(riid, ppReturn);
}

/**************************************************************************

   COleDocumentView::AddRef

**************************************************************************/

STDMETHODIMP_(DWORD) COleDocumentView::AddRef()
{
OutputDebugString(TEXT("COleDocumentView::AddRef\n"));

return m_pOleDoc->AddRef();
}

/**************************************************************************

   COleDocumentView::Release

**************************************************************************/

STDMETHODIMP_(DWORD) COleDocumentView::Release()
{
OutputDebugString(TEXT("COleDocumentView::Release\n"));

return m_pOleDoc->Release();
}

/**************************************************************************

   COleDocumentView::SetInPlaceSite()

**************************************************************************/

STDMETHODIMP COleDocumentView::SetInPlaceSite(IOleInPlaceSite *pNewSite)
{
OutputDebugString(TEXT("COleDocumentView::SetInPlaceSite\n"));

//clean up previous site if it exists
if(m_pInPlaceSite)
   {
   if(m_fUIActive)
      {
      DeactivateUI();
      }

   if(m_fInPlaceActive)
      {
      DeactivateInPlace();
      }

   if(m_pInPlaceFrame)
      {
      m_pInPlaceFrame->Release();
      m_pInPlaceFrame = NULL;
      }

   if(m_pInPlaceUIWindow)
      {
      m_pInPlaceUIWindow->Release();
      m_pInPlaceUIWindow = NULL;
      }

   m_pInPlaceSite->Release();
   m_pInPlaceSite = NULL;
   }

m_pInPlaceSite = pNewSite;

if(m_pInPlaceSite)
   {
   m_pInPlaceSite->AddRef();

   RECT  rcClip;

   m_FrameInfo.cb = sizeof(m_FrameInfo);

   m_pInPlaceSite->GetWindowContext(   &m_pInPlaceFrame,
                                       &m_pInPlaceUIWindow,
                                       &m_Rect,
                                       &rcClip,
                                       &m_FrameInfo);
   }

return S_OK;
}

/**************************************************************************

   COleDocumentView::GetInPlaceSite()

**************************************************************************/

STDMETHODIMP COleDocumentView::GetInPlaceSite(IOleInPlaceSite **ppInPlaceSite)
{
OutputDebugString(TEXT("COleDocumentView::GetInPlaceSite\n"));

HRESULT  hr = E_FAIL;

*ppInPlaceSite = m_pInPlaceSite;

if(*ppInPlaceSite)
   {
   (*ppInPlaceSite)->AddRef();
   hr = S_OK;
   }

return hr;
}

/**************************************************************************

   COleDocumentView::GetDocument()

**************************************************************************/

STDMETHODIMP COleDocumentView::GetDocument(IUnknown **ppUnk)
{
OutputDebugString(TEXT("COleDocumentView::GetDocument\n"));

*ppUnk = m_pOleDoc;

if(*ppUnk)
   (*ppUnk)->AddRef();

return S_OK;
}

/**************************************************************************

   COleDocumentView::SetRect()

**************************************************************************/

STDMETHODIMP COleDocumentView::SetRect(LPRECT pRect)
{
OutputDebugString(TEXT("COleDocumentView::SetRect - "));

if(!pRect)
   return E_INVALIDARG;

m_Rect = *pRect;

TCHAR szText[MAX_PATH];
wsprintf(szText, TEXT("%d, %d, %d, %d\n"), m_Rect.left, m_Rect.top, m_Rect.right, m_Rect.bottom);
OutputDebugString(szText);

MoveWindow(m_hwndView, m_Rect.left, m_Rect.top, m_Rect.right, m_Rect.bottom, TRUE);

return S_OK;
}

/**************************************************************************

   COleDocumentView::GetRect()

**************************************************************************/

STDMETHODIMP COleDocumentView::GetRect(LPRECT pRect)
{
OutputDebugString(TEXT("COleDocumentView::GetRect\n"));

if(!pRect)
   return E_INVALIDARG;

*pRect = m_Rect;

return S_OK;
}

/**************************************************************************

   COleDocumentView::SetRectComplex()

**************************************************************************/

STDMETHODIMP COleDocumentView::SetRectComplex(  LPRECT prcView,
                                                LPRECT prcHScroll,
                                                LPRECT prcVScroll,
                                                LPRECT prcSizeBox)
{
OutputDebugString(TEXT("COleDocumentView::SetRectComplex\n"));

return E_NOTIMPL;
}

/**************************************************************************

   COleDocumentView::Show()

**************************************************************************/

STDMETHODIMP COleDocumentView::Show(BOOL bShow)
{
OutputDebugString(TEXT("COleDocumentView::Show\n"));

if(bShow)
   {
   //if the object is not in-place active, make it that way
   if(!m_fInPlaceActive)
      ActivateInPlace();

   //show the window
   BringWindowToTop(m_hwndView);
   ShowWindow(m_hwndView, SW_SHOW);
   UpdateWindow(m_hwndView);
   }
else
   {
   //if the object is UI active, make remove that state
   if(m_fUIActive)
      DeactivateUI();

   //hide the window
   ShowWindow(m_hwndView, SW_HIDE);
   }

return S_OK;
}

/**************************************************************************

   COleDocumentView::UIActivate()

**************************************************************************/

STDMETHODIMP COleDocumentView::UIActivate(BOOL bActivate)
{
OutputDebugString(TEXT("COleDocumentView::UIActivate\n"));

if(bActivate)
   {
   return ActivateUI();
   }

return DeactivateUI();
}

/**************************************************************************

   COleDocumentView::ActivateUI()

**************************************************************************/

STDMETHODIMP COleDocumentView::ActivateUI()
{
OutputDebugString(TEXT("COleDocumentView::ActivateUI\n"));

//set the active object
//either one of these could be good
if(m_pInPlaceFrame)
   {
   m_pInPlaceFrame->SetActiveObject(m_pOleDoc->m_pOleInPlaceActiveObject, NULL);
   }

if(m_pInPlaceUIWindow)
   {
   m_pInPlaceUIWindow->SetActiveObject(m_pOleDoc->m_pOleInPlaceActiveObject, NULL);
   }

if(m_pInPlaceSite)
   {
   m_fUIActive = TRUE;

   ActivateInPlace();

   m_pInPlaceSite->OnUIActivate();

   MergeMenus();

   SetFocus(m_hwndView);
   }

return S_OK;
}

/**************************************************************************

   COleDocumentView::DeactivateUI()

**************************************************************************/

STDMETHODIMP COleDocumentView::DeactivateUI()
{
OutputDebugString(TEXT("COleDocumentView::DeactivateUI\n"));

m_fUIActive = FALSE;

RemoveMenus();

//remove the active object
//either one of these could be good
if(m_pInPlaceFrame)
   {
   m_pInPlaceFrame->SetActiveObject(NULL, NULL);
   }

if(m_pInPlaceUIWindow)
   {
   m_pInPlaceUIWindow->SetActiveObject(NULL, NULL);
   }

if(m_pInPlaceSite)
   {
   m_pInPlaceSite->OnUIDeactivate(FALSE);
   }

return S_OK;
}

/**************************************************************************

   COleDocumentView::Open()

**************************************************************************/

STDMETHODIMP COleDocumentView::Open(void)
{
OutputDebugString(TEXT("COleDocumentView::Open\n"));

return S_OK;
}

/**************************************************************************

   COleDocumentView::CloseView()

**************************************************************************/

STDMETHODIMP COleDocumentView::CloseView(DWORD dwReserved)
{
OutputDebugString(TEXT("COleDocumentView::CloseView\n"));

SetInPlaceSite(NULL);

return S_OK;
}

/**************************************************************************

   COleDocumentView::SaveViewState()

**************************************************************************/

STDMETHODIMP COleDocumentView::SaveViewState(IStream *pStream)
{
OutputDebugString(TEXT("COleDocumentView::SaveViewState\n"));

return E_NOTIMPL;
}

/**************************************************************************

   COleDocumentView::ApplyViewState()

**************************************************************************/

STDMETHODIMP COleDocumentView::ApplyViewState(IStream *pStream)
{
OutputDebugString(TEXT("COleDocumentView::ApplyViewState\n"));

return E_NOTIMPL;
}

/**************************************************************************

   COleDocumentView::Clone()

**************************************************************************/

STDMETHODIMP COleDocumentView::Clone(IOleInPlaceSite *pIPSite, IOleDocumentView **ppView)
{
OutputDebugString(TEXT("COleDocumentView::Clone\n"));

*ppView = NULL;

return E_NOTIMPL;
}

/**************************************************************************

   COleDocumentView::ActivateInPlace()

**************************************************************************/

STDMETHODIMP COleDocumentView::ActivateInPlace()
{
OutputDebugString(TEXT("COleDocumentView::ActivateInPlace\n"));

m_fInPlaceActive = TRUE;

if(m_pInPlaceSite)
   {
   // tell the site we are in-place activating
   m_pInPlaceSite->OnInPlaceActivate();

   HWND  hwndParent;
   m_pInPlaceSite->GetWindow(&hwndParent);
   SetParent(m_hwndView, hwndParent);

   //show the view
   Show(TRUE);
   }

return S_OK;
}

/**************************************************************************

   COleDocumentView::DeactivateInPlace()

**************************************************************************/

STDMETHODIMP COleDocumentView::DeactivateInPlace()
{
OutputDebugString(TEXT("COleDocumentView::InPlaceDeactivate\n"));

//UI deactivate, if necessary and hide the view window
Show(FALSE);

m_fInPlaceActive = FALSE;

if(m_pInPlaceSite)
   {
   // tell the site we are in-place deactivating
   m_pInPlaceSite->OnInPlaceDeactivate();
   }

//set the parent to NULL
SetParent(m_hwndView, GetDesktopWindow());

return S_OK;
}

/**************************************************************************

   COleDocumentView::CreateDocWindow()

**************************************************************************/

STDMETHODIMP COleDocumentView::CreateViewWindow()
{
OutputDebugString(TEXT("COleDocumentView::CreateViewWindow\n"));

WNDCLASS wc;

//if our window class has not been registered, then do so
if(!GetClassInfo(g_hInst, VIEW_CLASS_NAME, &wc))
   {
   ZeroMemory(&wc, sizeof(wc));
   wc.style          = CS_HREDRAW | CS_VREDRAW;
   wc.lpfnWndProc    = (WNDPROC)ViewWndProc;
   wc.cbClsExtra     = 0;
   wc.cbWndExtra     = 0;
   wc.hInstance      = g_hInst;
   wc.hIcon          = NULL;
   wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
   wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
   wc.lpszMenuName   = NULL;
   wc.lpszClassName  = VIEW_CLASS_NAME;

   if(!RegisterClass(&wc))
      return E_FAIL;
   }

m_hwndView = CreateWindowEx(  WS_EX_CLIENTEDGE,
                              VIEW_CLASS_NAME,
                              NULL,
                              WS_CHILD | WS_CLIPSIBLINGS,
                              0,
                              0,
                              0,
                              0,
                              GetDesktopWindow(),
                              NULL,
                              g_hInst,
                              (LPVOID)this);

if(!m_hwndView)
   {
   DWORD dwError = GetLastError();
   return E_FAIL;
   }

return S_OK;
}

/**************************************************************************

   COleDocumentView::ViewWndProc()

**************************************************************************/

LRESULT CALLBACK COleDocumentView::ViewWndProc(HWND hWnd, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
COleDocumentView  *pThis = (COleDocumentView*)GetWindowLong(hWnd, GWL_USERDATA);

switch (uMessage)
   {
   case WM_NCCREATE:
      {
      LPCREATESTRUCT lpcs = (LPCREATESTRUCT)lParam;
      pThis = (COleDocumentView*)(lpcs->lpCreateParams);
      SetWindowLong(hWnd, GWL_USERDATA, (LONG)pThis);
      }
      break;

   case WM_CREATE:
      return pThis->OnCreate();

   case WM_COMMAND:
      return pThis->OnCommand(   GET_WM_COMMAND_ID(wParam, lParam),
                                 GET_WM_COMMAND_CMD(wParam, lParam),
                                 GET_WM_COMMAND_HWND(wParam, lParam));

   case WM_PAINT:
      return pThis->OnPaint();

   case WM_INITMENUPOPUP:
      return pThis->UpdateMenu((HMENU)wParam);

   }

return DefWindowProc(hWnd, uMessage, wParam, lParam);
}

/**************************************************************************

   COleDocumentView::OnCreate()

**************************************************************************/

LRESULT COleDocumentView::OnCreate(void)
{
return 0;
}

/**************************************************************************

   COleDocumentView::OnCommand()

**************************************************************************/

LRESULT COleDocumentView::OnCommand(UINT uID, UINT uCmd, HWND hwndCmd)
{
switch(uID)
   {
   case IDM_RED:
      m_pOleDoc->m_Color = RED_COLOR;
      InvalidateRect(m_hwndView, NULL, TRUE);
      UpdateWindow(m_hwndView);
      m_pOleDoc->m_fDirty = TRUE;
      break;

   case IDM_GREEN:
      m_pOleDoc->m_Color = GREEN_COLOR;
      InvalidateRect(m_hwndView, NULL, TRUE);
      UpdateWindow(m_hwndView);
      m_pOleDoc->m_fDirty = TRUE;
      break;

   case IDM_BLUE:
      m_pOleDoc->m_Color = BLUE_COLOR;
      InvalidateRect(m_hwndView, NULL, TRUE);
      UpdateWindow(m_hwndView);
      m_pOleDoc->m_fDirty = TRUE;
      break;

   }

return 0;
}

/**************************************************************************

   COleDocumentView::OnPaint()

**************************************************************************/

LRESULT COleDocumentView::OnPaint(void)
{
PAINTSTRUCT ps;

BeginPaint(m_hwndView, &ps);

RECT  rc;
HBRUSH hBrush = CreateSolidBrush(m_pOleDoc->m_Color);
GetClientRect(m_hwndView, &rc);

FillRect(ps.hdc, &rc, hBrush);

DeleteObject(hBrush);

EndPaint(m_hwndView, &ps);

return 0;
}

/**************************************************************************

   COleDocumentView::MergeMenus()

**************************************************************************/

BOOL COleDocumentView::MergeMenus(void)
{
if(!m_hSharedMenu)
   {
   //  Create the menu resource
   m_hSharedMenu = CreateMenu();

   ZeroMemory(&m_mgw, sizeof(m_mgw));

   // have the contaner insert its menus
   if(SUCCEEDED(m_pInPlaceFrame->InsertMenus(m_hSharedMenu, &m_mgw)))
      {
      int   nFirstGroup = (int) m_mgw.width[0];
      HMENU hMenu,
            hSubMenu;
      TCHAR szText[MAX_PATH];

      hMenu = LoadMenu(g_hInst, MAKEINTRESOURCE(IDR_COLOR_MENU));
      hSubMenu = GetSubMenu(hMenu, 0);

      //get the menu item's text
      GetMenuString(hMenu, 0, szText, ARRAYSIZE(szText), MF_BYPOSITION);

      // insert the server menus
      InsertMenu( m_hSharedMenu,
                  nFirstGroup,
                  MF_BYPOSITION | MF_POPUP,
                  (UINT)hSubMenu,
                  szText);

      //update the OLEMENUGROUPWIDTHS structure
      m_mgw.width[1] += 1;
      m_mgw.width[3] += 0;
      m_mgw.width[5] += 0;
      }
   else
      {
      // Destroy the menu resource
      DestroyMenu(m_hSharedMenu);
      m_hSharedMenu = NULL;
      }
   }

if(!m_hOleMenu)
   {
   // tell OLE to create the menu descriptor
   m_hOleMenu = OleCreateMenuDescriptor(m_hSharedMenu, &m_mgw);
   }

m_pInPlaceFrame->SetMenu(m_hSharedMenu, m_hOleMenu, m_hwndView);

return TRUE;
}

/**************************************************************************

   COleDocumentView::RemoveMenus()

**************************************************************************/

BOOL COleDocumentView::RemoveMenus(void)
{
if(m_hSharedMenu)
   {
   int   nFirstGroup = (int) m_mgw.width[0];

   m_pInPlaceFrame->SetMenu(NULL, NULL, NULL);

   // remove the menus that we added
   RemoveMenu(m_hSharedMenu, nFirstGroup, MF_BYPOSITION);

   // have the container remove its menus
   m_pInPlaceFrame->RemoveMenus(m_hSharedMenu);

   // Destroy the menu resource
   DestroyMenu(m_hSharedMenu);

   m_hSharedMenu = NULL;
   }

if(m_hOleMenu)
   {
   // destroy the menu descriptor
   OleDestroyMenuDescriptor(m_hOleMenu);

   m_hOleMenu = NULL;
   }

return TRUE;
}

/**************************************************************************

   COleDocumentView::UpdateMenu()

**************************************************************************/

BOOL COleDocumentView::UpdateMenu(HMENU hMenu)
{
UINT  uCheck;

switch(m_pOleDoc->m_Color)
   {
   case RED_COLOR:
      uCheck = IDM_RED;
      break;

   case GREEN_COLOR:
      uCheck = IDM_GREEN;
      break;

   case BLUE_COLOR:
      uCheck = IDM_BLUE;
      break;

   default:
      uCheck = 0;
      break;
   }

return CheckMenuRadioItem( hMenu,
                           IDM_RED,
                           IDM_BLUE,
                           uCheck,
                           MF_BYCOMMAND);
}


OLEDOCVW.H

/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Copyright 1997 Microsoft Corporation.  All Rights Reserved.
**************************************************************************/

/******************************************************************************

   File:          OleDoctVw.h

   Description:   COleDocumentView definitions.

******************************************************************************/

#ifndef OLEDOCUMENTVIEW_H
#define OLEDOCUMENTVIEW_H

#include "DOServer.h"

#define VIEW_CLASS_NAME TEXT("DocObject_View_Class")

/**************************************************************************

   COleDocumentView class definition

**************************************************************************/

class COleDocument;

class COleDocumentView : public IOleDocumentView
{
friend class COleDocument;
friend class COleInPlaceActiveObject;

private:
BOOL                       m_fInPlaceActive;
RECT                       m_Rect;
   COleDocument               *m_pOleDoc;
LPOLEINPLACESITE           m_pInPlaceSite;
   HWND                       m_hwndView;
   LPOLEINPLACEFRAME          m_pInPlaceFrame;
   LPOLEINPLACEUIWINDOW       m_pInPlaceUIWindow;
   OLEINPLACEFRAMEINFO        m_FrameInfo;
   HMENU                      m_hSharedMenu;
   HOLEMENU                   m_hOleMenu;
   OLEMENUGROUPWIDTHS         m_mgw;
   BOOL                       m_fUIActive;

public:
   COleDocumentView(COleDocument*);
   ~COleDocumentView();

   //IUnknown methods
   STDMETHODIMP QueryInterface(REFIID, LPVOID*);
   STDMETHODIMP_(DWORD) AddRef();
   STDMETHODIMP_(DWORD) Release();

   //IOleDocumentView methods
   STDMETHODIMP SetInPlaceSite(IOleInPlaceSite*);
   STDMETHODIMP GetInPlaceSite(IOleInPlaceSite**);
   STDMETHODIMP GetDocument(IUnknown**);
   STDMETHODIMP SetRect(LPRECT);
   STDMETHODIMP GetRect(LPRECT);
   STDMETHODIMP SetRectComplex(LPRECT, LPRECT, LPRECT, LPRECT);
   STDMETHODIMP Show(BOOL);
   STDMETHODIMP UIActivate(BOOL);
   STDMETHODIMP Open(void);
   STDMETHODIMP CloseView(DWORD);
   STDMETHODIMP SaveViewState(IStream*);
   STDMETHODIMP ApplyViewState(IStream*);
   STDMETHODIMP Clone(IOleInPlaceSite*, IOleDocumentView**);

private:
   STDMETHODIMP ActivateInPlace();
   STDMETHODIMP DeactivateInPlace();
   STDMETHODIMP ActivateUI();
   STDMETHODIMP DeactivateUI();
   STDMETHODIMP CreateViewWindow();
   static LRESULT CALLBACK ViewWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT OnPaint(void);
LRESULT OnCommand(UINT, UINT, HWND);
LRESULT OnCreate(void);
   BOOL MergeMenus(void);
   BOOL RemoveMenus(void);
   BOOL UpdateMenu(HMENU);
};

#endif   //OLEDOCUMENTVIEW_H


OLEOBJ.CPP

/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Copyright 1997 Microsoft Corporation.  All Rights Reserved.
**************************************************************************/

/**************************************************************************

   File:          OleObj.cpp

   Description:   COleObject implementation.

**************************************************************************/

/**************************************************************************
   #include statements
**************************************************************************/

#include "OleObj.h"

/**************************************************************************

   COleObject::COleObject()

**************************************************************************/

COleObject::COleObject(COleDocument *pOleDoc)
{
OutputDebugString(TEXT("COleObject's constructor\n"));

m_pOleDoc = pOleDoc;

m_fOpen = FALSE;
}

/**************************************************************************

   COleObject::~COleObject()

**************************************************************************/

COleObject::~COleObject()
{
OutputDebugString(TEXT("COleObject's destructor\n"));
}

/**************************************************************************

   COleObject::QueryInterface()

**************************************************************************/

STDMETHODIMP COleObject::QueryInterface(REFIID riid, LPVOID *ppReturn)
{
OutputDebugString(TEXT("COleObject::QueryInterface\n"));

return m_pOleDoc->QueryInterface(riid, ppReturn);
}

/**************************************************************************

   COleObject::AddRef()

**************************************************************************/

STDMETHODIMP_(ULONG) COleObject::AddRef()
{
OutputDebugString(TEXT("COleObject::AddRef\n"));

return m_pOleDoc->AddRef();
}

/**************************************************************************

   COleObject::Release()

**************************************************************************/

STDMETHODIMP_(ULONG) COleObject::Release()
{
OutputDebugString(TEXT("COleObject::Release\n"));

return m_pOleDoc->Release();
}

/**************************************************************************

   COleObject::DoVerb()

**************************************************************************/

STDMETHODIMP COleObject::DoVerb( LONG iVerb,
                                 LPMSG lpmsg,
                                 LPOLECLIENTSITE pActiveSite,
                                 LONG lindex,
                                 HWND hwndParent,
                                 LPCRECT lprcPosRect)
{
OutputDebugString(TEXT("COleObject::DoVerb\n"));

HRESULT  hr = E_FAIL;

switch (iVerb)
   {
   case OLEIVERB_SHOW:
   case OLEIVERB_PRIMARY:
   case OLEIVERB_UIACTIVATE:
      {
      //try to get the IOleDocumentSite pointer
      LPOLEDOCUMENTSITE pOleDocSite;
      hr = pActiveSite->QueryInterface(IID_IOleDocumentSite, (LPVOID*)&pOleDocSite);
      if(SUCCEEDED(hr))
         {
         //passing NULL to this will cause the site to call our CreateView method
         hr = pOleDocSite->ActivateMe(NULL);
         }
      }
      break;

   default:
      break;
   }

return hr;
}

/**************************************************************************

   COleObject::GetClientSite()

**************************************************************************/

STDMETHODIMP COleObject::GetClientSite(LPOLECLIENTSITE *ppClientSite)
{
OutputDebugString(TEXT("COleObject::GetClientSite\n"));

*ppClientSite = m_pOleDoc->m_pOleClientSite;

return S_OK;
}

/**************************************************************************

   COleObject::SetClientSite()

**************************************************************************/

STDMETHODIMP COleObject::SetClientSite(LPOLECLIENTSITE pClientSite)
{
OutputDebugString(TEXT("COleObject::SetClientSite\n"));

// if we already have a client site, release it.
if(m_pOleDoc->m_pOleClientSite)
   {
   m_pOleDoc->m_pOleClientSite->Release();
   m_pOleDoc->m_pOleClientSite = NULL;
   }

// store copy of the client site.
m_pOleDoc->m_pOleClientSite = pClientSite;

// AddRef it so it doesn't go away.
if(m_pOleDoc->m_pOleClientSite)
   m_pOleDoc->m_pOleClientSite->AddRef();

return S_OK;
}

/**************************************************************************

   COleObject::Advise()

**************************************************************************/

STDMETHODIMP COleObject::Advise(LPADVISESINK pAdvSink, LPDWORD pdwConnection)
{
OutputDebugString(TEXT("COleObject::Advise\n"));

// if we haven't made an OleAdviseHolder yet, make one.
if(!m_pOleDoc->m_pOleAdviseHolder)
   CreateOleAdviseHolder(&m_pOleDoc->m_pOleAdviseHolder);

// pass this call onto the OleAdviseHolder.
return m_pOleDoc->m_pOleAdviseHolder->Advise(pAdvSink, pdwConnection);
}

/**************************************************************************

   COleObject::Unadvise()

**************************************************************************/

STDMETHODIMP COleObject::Unadvise(DWORD dwConnection)
{
OutputDebugString(TEXT("COleObject::Unadvise\n"));

// pass on to OleAdviseHolder.
return m_pOleDoc->m_pOleAdviseHolder->Unadvise(dwConnection);
}

/**************************************************************************

   COleObject::SetHostNames()

**************************************************************************/

STDMETHODIMP COleObject::SetHostNames(LPCOLESTR szContainerApp, LPCOLESTR szContainerObj)
{
OutputDebugString(TEXT("COleObject::SetHostNames\n"));

return S_OK;
}

/**************************************************************************

   COleObject::GetExtent()

**************************************************************************/

STDMETHODIMP COleObject::GetExtent(DWORD dwDrawAspect, LPSIZEL lpsizel)
{
OutputDebugString(TEXT("COleObject::GetExtent\n"));

return E_NOTIMPL;
}

/**************************************************************************

   COleObject::SetExtent()

**************************************************************************/

STDMETHODIMP COleObject::SetExtent(DWORD dwDrawAspect, LPSIZEL lpsizel)
{
OutputDebugString(TEXT("COleObject::SetExtent\n"));

return E_NOTIMPL;
}

/**************************************************************************

   COleObject::Update()

**************************************************************************/

STDMETHODIMP COleObject::Update()
{
OutputDebugString(TEXT("COleObject::Update\n"));

return S_OK;
}

/**************************************************************************

   COleObject::Close()

**************************************************************************/

STDMETHODIMP COleObject::Close(DWORD dwSaveOption)
{
OutputDebugString(TEXT("COleObject::Close\n"));

switch(dwSaveOption)
   {
   case OLECLOSE_SAVEIFDIRTY:
      if(m_pOleDoc->m_fDirty)
         {
         m_pOleDoc->m_pPersistStorage->Save(NULL, TRUE);
         }
      break;

   case OLECLOSE_NOSAVE:
      break;

   case OLECLOSE_PROMPTSAVE:
      break;
   }

//close the view
m_pOleDoc->m_pOleDocView->CloseView(0);

return S_OK;
}

/**************************************************************************

   COleObject::EnumVerbs()

**************************************************************************/

STDMETHODIMP COleObject::EnumVerbs(LPENUMOLEVERB *ppenumOleVerb)
{
OutputDebugString(TEXT("COleObject::EnumVerbs\n"));

return OLE_S_USEREG;
}

/**************************************************************************

   COleObject::SetMoniker()

**************************************************************************/

STDMETHODIMP COleObject::SetMoniker(DWORD dwWhichMoniker, LPMONIKER pMoniker)
{
OutputDebugString(TEXT("COleObject::SetMoniker\n"));

LPMONIKER pTempMoniker;

if(!m_pOleDoc->m_pOleClientSite)
   return E_FAIL;

if(NOERROR != m_pOleDoc->m_pOleClientSite->GetMoniker(OLEGETMONIKER_ONLYIFTHERE, OLEWHICHMK_OBJFULL, &pTempMoniker))
   return E_FAIL;

if(m_pOleDoc->m_pOleAdviseHolder)
   m_pOleDoc->m_pOleAdviseHolder->SendOnRename(pTempMoniker);

LPRUNNINGOBJECTTABLE pRot;

if(NOERROR == GetRunningObjectTable(0, &pRot))
   {
   if(m_pOleDoc->m_dwRegister)
      pRot->Revoke(m_pOleDoc->m_dwRegister);

   pRot->Register(0, m_pOleDoc, pTempMoniker, &m_pOleDoc->m_dwRegister);

   pRot->Release();
   }

return S_OK;
}

/**************************************************************************

   COleObject::GetMoniker()

**************************************************************************/

STDMETHODIMP COleObject::GetMoniker(   DWORD dwAssign,
                                       DWORD dwWhichMoniker,
                                       LPMONIKER *ppmk)
{
OutputDebugString(TEXT("COleObject::GetMoniker\n"));

*ppmk = NULL;

return m_pOleDoc->m_pOleClientSite->GetMoniker(OLEGETMONIKER_ONLYIFTHERE, OLEWHICHMK_OBJFULL, ppmk);
}

/**************************************************************************

   COleObject::InitFromData()

**************************************************************************/

STDMETHODIMP COleObject::InitFromData( LPDATAOBJECT pDataObject,
                                       BOOL fCreation,
                                       DWORD dwReserved)
{
OutputDebugString(TEXT("COleObject::InitFromData\n"));

return E_NOTIMPL;
}

/**************************************************************************

   COleObject::GetClipboardData()

**************************************************************************/

STDMETHODIMP COleObject::GetClipboardData(   DWORD dwReserved,
                                             LPDATAOBJECT *ppDataObject)
{
OutputDebugString(TEXT("COleObject::GetClipboardData\n"));

*ppDataObject = NULL;

return E_NOTIMPL;
}

/**************************************************************************

   COleObject::IsUpToDate()

**************************************************************************/

STDMETHODIMP COleObject::IsUpToDate()
{
OutputDebugString(TEXT("COleObject::IsUpToDate\n"));

return S_OK;
}

/**************************************************************************

   COleObject::GetUserClassID()

**************************************************************************/

STDMETHODIMP COleObject::GetUserClassID(CLSID *pClsid)
{
OutputDebugString(TEXT("COleObject::GetUserClassID\n"));

return m_pOleDoc->m_pPersistStorage->GetClassID(pClsid);
}

/**************************************************************************

   COleObject::GetUserType()

**************************************************************************/

STDMETHODIMP COleObject::GetUserType(DWORD dwFormOfType, LPOLESTR *pszUserType)
{
OutputDebugString(TEXT("COleObject::GetUserType\n"));

return OLE_S_USEREG;
}

/**************************************************************************

   COleObject::EnumAdvise()

**************************************************************************/

STDMETHODIMP COleObject::EnumAdvise(LPENUMSTATDATA *ppenumAdvise)
{
OutputDebugString(TEXT("COleObject::EnumAdvise\n"));

*ppenumAdvise = NULL;

// pass on to the OLE Advise holder.
return m_pOleDoc->m_pOleAdviseHolder->EnumAdvise(ppenumAdvise);
}

/**************************************************************************

   COleObject::GetMiscStatus()

**************************************************************************/

STDMETHODIMP COleObject::GetMiscStatus(DWORD dwAspect, DWORD *pdwStatus)
{
OutputDebugString(TEXT("COleObject::GetMiscStatus\n"));

*pdwStatus = NULL;

return OLE_S_USEREG;
}

/**************************************************************************

   COleObject::SetColorScheme()

**************************************************************************/

STDMETHODIMP COleObject::SetColorScheme(LPLOGPALETTE lpLogpal)
{
OutputDebugString(TEXT("COleObject::SetColorScheme\n"));

return S_OK;
}


OLEOBJ.H

/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Copyright 1997 Microsoft Corporation.  All Rights Reserved.
**************************************************************************/

/**************************************************************************

   File:          OleObj.h

   Description:   COleObject definitions.

**************************************************************************/

#ifndef OLEOBJECT_H
#define OLEOBJECT_H

/**************************************************************************
   #include statements
**************************************************************************/

#include "DOServer.h"

/**************************************************************************

   COleObject class definition

**************************************************************************/

class COleDocument;

class COleObject : public IOleObject
{
private:
COleDocument   *m_pOleDoc;
BOOL           m_fOpen;

public:
COleObject::COleObject(COleDocument *pOleDoc);
COleObject::~COleObject();

//IUnknown methods
STDMETHODIMP QueryInterface(REFIID, LPVOID*);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();

//IOleObject methods
STDMETHODIMP SetClientSite(LPOLECLIENTSITE);
STDMETHODIMP Advise(LPADVISESINK, LPDWORD);
STDMETHODIMP SetHostNames(LPCOLESTR, LPCOLESTR);
STDMETHODIMP DoVerb(LONG, LPMSG, LPOLECLIENTSITE, LONG, HWND, LPCRECT);
STDMETHODIMP GetExtent(DWORD, LPSIZEL);
STDMETHODIMP Update();
STDMETHODIMP Close(DWORD);
STDMETHODIMP Unadvise(DWORD);
STDMETHODIMP EnumVerbs(LPENUMOLEVERB*);
STDMETHODIMP GetClientSite(LPOLECLIENTSITE*);
STDMETHODIMP SetMoniker(DWORD, LPMONIKER);
STDMETHODIMP GetMoniker(DWORD, DWORD, LPMONIKER*);
STDMETHODIMP InitFromData(LPDATAOBJECT, BOOL, DWORD);
STDMETHODIMP GetClipboardData(DWORD, LPDATAOBJECT*);
STDMETHODIMP IsUpToDate();
STDMETHODIMP GetUserClassID(CLSID*);
STDMETHODIMP GetUserType(DWORD, LPOLESTR*);
STDMETHODIMP SetExtent(DWORD, LPSIZEL);
STDMETHODIMP EnumAdvise(LPENUMSTATDATA*);
STDMETHODIMP GetMiscStatus(DWORD, LPDWORD);
STDMETHODIMP SetColorScheme(LPLOGPALETTE);

};

#endif   //OLEOBJECT_H


PERFILE.CPP

/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Copyright 1997 Microsoft Corporation.  All Rights Reserved.
**************************************************************************/

/**************************************************************************

   File:          PerFile.cpp

   Description:   CPersistFile implementation.

**************************************************************************/

/**************************************************************************
   #include statements
**************************************************************************/

#include "PerFile.h"

/**************************************************************************

   CPersistFile::CPersistFile()

**************************************************************************/

CPersistFile::CPersistFile(COleDocument *pOleDoc, CPersistStorage *pPersistStorage)
{
OutputDebugString(TEXT("CPersistFile's constructor\n"));

m_pOleDoc = pOleDoc;

m_pPersistStorage = pPersistStorage;

m_pStorage = NULL;

m_pwszFile = new OLECHAR[MAX_PATH];
}

/**************************************************************************

   CPersistFile::~CPersistFile()

**************************************************************************/

CPersistFile::~CPersistFile()
{
OutputDebugString(TEXT("CPersistFile's destructor\n"));

if(m_pStorage)
   m_pStorage->Release();

delete m_pwszFile;
}

/**************************************************************************

   CPersistFile::QueryInterface()

**************************************************************************/

STDMETHODIMP CPersistFile::QueryInterface(REFIID riid, LPVOID *ppReturn)
{
OutputDebugString(TEXT("CPersistFile::QueryInterface\n"));

return m_pOleDoc->QueryInterface(riid, ppReturn);
}

/**************************************************************************

   CPersistFile::AddRef()

**************************************************************************/

STDMETHODIMP_(ULONG) CPersistFile::AddRef()
{
OutputDebugString(TEXT("CPersistFile::AddRef\n"));

return m_pOleDoc->AddRef();
}

/**************************************************************************

   CPersistFile::Release()

**************************************************************************/

STDMETHODIMP_(ULONG) CPersistFile::Release()
{
OutputDebugString(TEXT("CPersistFile::Release\n"));

return m_pOleDoc->Release();
}

/**************************************************************************

   CPersistFile::GetClassID()

**************************************************************************/

STDMETHODIMP CPersistFile::GetClassID(LPCLSID pClassID)
{
OutputDebugString(TEXT("CPersistFile::GetClassID\n"));

return m_pPersistStorage->GetClassID(pClassID);
}

/**************************************************************************

   CPersistFile::Save()

**************************************************************************/

STDMETHODIMP CPersistFile::Save(LPCOLESTR pwszFile, BOOL fRemember)
{
OutputDebugString(TEXT("CPersistFile::Save\n"));

if(!pwszFile)
   return E_INVALIDARG;

HRESULT     hr;

/*
Only create a new storage if we don't already have a storage or the file name
has changed.
*/
if(!m_pStorage || (0 != lstrcmpiW(pwszFile, m_pwszFile)))
   {
   //if we have a storage, release it
   if(m_pStorage)
      {
      m_pPersistStorage->ReleaseStreamsAndStorage();
      m_pStorage->Release();
      m_pStorage = NULL;
      *m_pwszFile = 0;
      }

   //open the file as storage
   hr = StgCreateDocfile(  pwszFile,
                           STGM_READWRITE | STGM_DIRECT | STGM_SHARE_EXCLUSIVE | STGM_CREATE,
                           0,
                           &m_pStorage);

   }

if(m_pStorage)
   {
   hr = m_pPersistStorage->Save(m_pStorage, FALSE);
   }

if(fRemember)
   {
   lstrcpyW(m_pwszFile, pwszFile);
   }
else
   {
   m_pPersistStorage->ReleaseStreamsAndStorage();
   m_pStorage->Release();
   m_pStorage = NULL;
   *m_pwszFile = 0;
   }

return hr;
}

/**************************************************************************

   CPersistFile::SaveCompleted()

**************************************************************************/

STDMETHODIMP CPersistFile::SaveCompleted(LPCOLESTR pwszFile)
{
OutputDebugString(TEXT("CPersistFile::SaveCompleted\n"));

return m_pPersistStorage->SaveCompleted(NULL);
}

/**************************************************************************

   CPersistFile::Load()

**************************************************************************/

STDMETHODIMP CPersistFile::Load(LPCOLESTR pwszFile, DWORD dwMode)
{
OutputDebugString(TEXT("CPersistFile::Load\n"));

HRESULT     hr;

if(!pwszFile)
   return E_INVALIDARG;

//close any existing storage we might have
if(m_pStorage)
   {
   m_pPersistStorage->ReleaseStreamsAndStorage();
   m_pStorage->Release();
   m_pStorage = NULL;
   *m_pwszFile = 0;
   }

//open the file as storage and load it
hr = StgOpenStorage(    pwszFile,
                        NULL,
                        dwMode | STGM_READWRITE | STGM_DIRECT | STGM_SHARE_EXCLUSIVE,
                        NULL,
                        0,
                        &m_pStorage);

if(m_pStorage)
   {
   hr = m_pPersistStorage->Load(m_pStorage);
   }
else
   {
   hr = E_FAIL;
   }

if(SUCCEEDED(hr))
   lstrcpyW(m_pwszFile, pwszFile);

return hr;
}

/**************************************************************************

   CPersistFile::IsDirty()

**************************************************************************/

STDMETHODIMP CPersistFile::IsDirty()
{
OutputDebugString(TEXT("CPersistFile::IsDirty\n"));

return m_pPersistStorage->IsDirty();
}

/**************************************************************************

   CPersistFile::GetCurFile()

**************************************************************************/

STDMETHODIMP CPersistFile::GetCurFile(LPOLESTR *ppwszOut)
{
OutputDebugString(TEXT("CPersistFile::GetCurFile\n"));

HRESULT  hr = E_FAIL;

*ppwszOut = NULL;

LPMALLOC pMalloc;
CoGetMalloc(1, &pMalloc);
if(pMalloc)
   {
   if(*m_pwszFile)
      {
      *ppwszOut = (LPOLESTR)pMalloc->Alloc(lstrlenW(m_pwszFile) + sizeof(OLECHAR));
      if(*ppwszOut)
         {
         lstrcpyW(*ppwszOut, m_pwszFile);
         hr = S_OK;
         }
      }
   else
      {
      *ppwszOut = (LPOLESTR)pMalloc->Alloc(lstrlenW(DEFAULT_PROMPT) + sizeof(OLECHAR));
      if(*ppwszOut)
         {
         lstrcpyW(*ppwszOut, DEFAULT_PROMPT);
         hr = S_FALSE;
         }
      }

   pMalloc->Release();
   }

return hr;
}


PERFILE.H

/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Copyright 1997 Microsoft Corporation.  All Rights Reserved.
**************************************************************************/

/**************************************************************************

   File:          PerFile.h

   Description:   CPersistFile definitions.

**************************************************************************/

#ifndef PERSISTFILE_H
#define PERSISTFILE_H

/**************************************************************************
   #include statements
**************************************************************************/

#include "DOServer.h"

/**************************************************************************

   CPersistFile class definition

**************************************************************************/

class COleDocument;
class CPersistStorage;

class CPersistFile : IPersistFile
{
private:
LPSTORAGE         m_pStorage;
COleDocument      *m_pOleDoc;
   CPersistStorage   *m_pPersistStorage;
   LPOLESTR          m_pwszFile;

public:
CPersistFile::CPersistFile(COleDocument *, CPersistStorage *);
CPersistFile::~CPersistFile();

STDMETHODIMP QueryInterface(REFIID, LPVOID*);
STDMETHODIMP_(ULONG)AddRef();
STDMETHODIMP_(ULONG)Release();

STDMETHODIMP GetClassID(LPCLSID);
STDMETHODIMP Save(LPCOLESTR, BOOL);
STDMETHODIMP SaveCompleted(LPCOLESTR);
STDMETHODIMP Load(LPCOLESTR, DWORD);
STDMETHODIMP IsDirty(void);
STDMETHODIMP GetCurFile(LPOLESTR*);

};

#endif   //PERSISTFILE_H


PERSTOR.CPP

/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Copyright 1997 Microsoft Corporation.  All Rights Reserved.
**************************************************************************/

/**************************************************************************

   File:          PerStor.cpp

   Description:   CPersistStorage implementation.

**************************************************************************/

/**************************************************************************
   #include statements
**************************************************************************/

#include "PerStor.h"

/**************************************************************************

   CPersistStorage::CPersistStorage()

**************************************************************************/

CPersistStorage::CPersistStorage(COleDocument *pOleDoc)
{
OutputDebugString(TEXT("CPersistStorage's constructor\n"));

m_pOleDoc = pOleDoc;

m_pStorage = NULL;
m_pColorStream = NULL;
m_fSameAsLoad = FALSE;
}

/**************************************************************************

   CPersistStorage::~CPersistStorage()

**************************************************************************/

CPersistStorage::~CPersistStorage()
{
OutputDebugString(TEXT("CPersistStorage's destructor\n"));
}

/**************************************************************************

   CPersistStorage::QueryInterface()

**************************************************************************/

STDMETHODIMP CPersistStorage::QueryInterface(REFIID riid, LPVOID *ppReturn)
{
OutputDebugString(TEXT("CPersistStorage::QueryInterface\n"));

return m_pOleDoc->QueryInterface(riid, ppReturn);
}

/**************************************************************************

   CPersistStorage::AddRef()

**************************************************************************/

STDMETHODIMP_(ULONG) CPersistStorage::AddRef()
{
OutputDebugString(TEXT("CPersistStorage::AddRef\n"));

return m_pOleDoc->AddRef();
}

/**************************************************************************

   CPersistStorage::Release()

**************************************************************************/

STDMETHODIMP_(ULONG) CPersistStorage::Release()
{
OutputDebugString(TEXT("CPersistStorage::Release\n"));

return m_pOleDoc->Release();
}

/**************************************************************************

   CPersistStorage::InitNew()

**************************************************************************/

STDMETHODIMP CPersistStorage::InitNew(LPSTORAGE pStg)
{
OutputDebugString(TEXT("CPersistStorage::InitNew\n"));

// release any streams and storages that may be open
ReleaseStreamsAndStorage();

m_pStorage = pStg;

// AddRef the new Storage
if (m_pStorage)
   m_pStorage->AddRef();

CreateStreams(m_pStorage);

return S_OK;
}

/**************************************************************************

   CPersistStorage::GetClassID()

**************************************************************************/

STDMETHODIMP CPersistStorage::GetClassID(LPCLSID pClassID)
{
OutputDebugString(TEXT("CPersistStorage::GetClassID\n"));

*pClassID = CLSID_SimpleDocObject;

return S_OK;
}

/**************************************************************************

   CPersistStorage::Save()

**************************************************************************/

STDMETHODIMP CPersistStorage::Save(LPSTORAGE pStg, BOOL fSameAsLoad)
{
OutputDebugString(TEXT("CPersistStorage::Save\n"));

m_pOleDoc->m_fNoScribbleMode = TRUE;

// save the data
LPSTREAM pTempColor;

if(!fSameAsLoad)
   {
   if(!pStg)
      return E_FAIL;

   CreateStreams(pStg, &pTempColor);
   }
else
   {
   pStg = m_pStorage;

   if(!pStg)
      return E_FAIL;

   pTempColor = m_pColorStream;
   pTempColor->AddRef();
   }

//write our class ID into the storage
CLSID clsid;
GetClassID(&clsid);
WriteClassStg(pStg, clsid);

ULARGE_INTEGER uli;

uli.LowPart = 0;
uli.HighPart = 0;

pTempColor->SetSize(uli);

LARGE_INTEGER li;

li.LowPart = 0;
li.HighPart = 0;

pTempColor->Seek(li, STREAM_SEEK_SET, NULL);

// write the color to the stream
pTempColor->Write(&m_pOleDoc->m_Color, sizeof(m_pOleDoc->m_Color), NULL);

pTempColor->Release();

m_fSameAsLoad = fSameAsLoad;

m_pOleDoc->m_fDirty = FALSE;

return S_OK;
}

/**************************************************************************

   CPersistStorage::SaveCompleted()

**************************************************************************/

STDMETHODIMP CPersistStorage::SaveCompleted(LPSTORAGE pStgNew)
{
OutputDebugString(TEXT("CPersistStorage::SaveCompleted\n"));

if(pStgNew)
   {
   ReleaseStreamsAndStorage();
   m_pStorage = pStgNew;
   m_pStorage->AddRef();
   OpenStreams(pStgNew);
   }

if(pStgNew || m_fSameAsLoad)
   {
   if(m_pOleDoc->m_fNoScribbleMode)
      {
      if(m_pOleDoc->m_pOleAdviseHolder)
         m_pOleDoc->m_pOleAdviseHolder->SendOnSave();
      }

   m_fSameAsLoad = FALSE;
   }

m_pOleDoc->m_fNoScribbleMode = FALSE;

return S_OK;
}

/**************************************************************************

   CPersistStorage::Load()

**************************************************************************/

STDMETHODIMP CPersistStorage::Load(LPSTORAGE pStg)
{
OutputDebugString(TEXT("CPersistStorage::Load\n"));

// remember the storage
if(m_pStorage)
   {
   m_pStorage->Release();
   m_pStorage = NULL;
   }

m_pStorage = pStg;

m_pStorage->AddRef();

//read our class ID from the storage
CLSID clsidStg;
ReadClassStg(m_pStorage, &clsidStg);

OpenStreams(m_pStorage);

// read the color
if(FAILED(m_pColorStream->Read(&m_pOleDoc->m_Color, sizeof(m_pOleDoc->m_Color), NULL)))
   {
   m_pOleDoc->m_Color = DEFAULT_COLOR;
   }

m_pOleDoc->m_fDirty = FALSE;

return S_OK;
}

/**************************************************************************

   CPersistStorage::IsDirty()

**************************************************************************/

STDMETHODIMP CPersistStorage::IsDirty()
{
OutputDebugString(TEXT("CPersistStorage::IsDirty\n"));

return (m_pOleDoc->m_fDirty ? S_OK : S_FALSE);
}

/**************************************************************************

   CPersistStorage::HandsOffStorage()

**************************************************************************/

STDMETHODIMP CPersistStorage::HandsOffStorage()
{
OutputDebugString(TEXT("CPersistStorage::HandsOffStorage\n"));

ReleaseStreamsAndStorage();

return S_OK;
}

/**************************************************************************

   CPersistStorage::CreateStreams()

**************************************************************************/

void CPersistStorage::CreateStreams(LPSTORAGE pStg)
{
if(m_pColorStream)
   m_pColorStream->Release();

// create a stream to save the color
pStg->CreateStream(  OLESTR("Color"),
                     STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE,
                     0,
                     0,
                     &m_pColorStream);

}

/**************************************************************************

   CPersistStorage::CreateStreams()

**************************************************************************/

void CPersistStorage::CreateStreams(   LPSTORAGE pStg,
                                       LPSTREAM* ppNewColorStream)
{
LPSTREAM pTempColorStream;

//save the member streams
pTempColorStream = m_pColorStream;

//initialize the streams
m_pColorStream = *ppNewColorStream = NULL;

//create the streams
CreateStreams(pStg);

//copy the new streams
*ppNewColorStream = m_pColorStream;

//restore the member streams
m_pColorStream = pTempColorStream;
}

/**************************************************************************

   CPersistStorage::OpenStreams()

**************************************************************************/

void CPersistStorage::OpenStreams(LPSTORAGE pStg)
{
if(m_pColorStream)
   m_pColorStream->Release();

// open the color stream
pStg->OpenStream( OLESTR("Color"),
                  0,
                  STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
                  0,
                  &m_pColorStream);

}

/**************************************************************************

   CPersistStorage::ReleaseStreamsAndStorage()

**************************************************************************/

void CPersistStorage::ReleaseStreamsAndStorage()
{
if(m_pColorStream)
   {
   m_pColorStream->Release();
   m_pColorStream = NULL;
   }

if(m_pStorage)
   {
   m_pStorage->Release();
   m_pStorage = NULL;
   }
}


PERSTOR.H

/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Copyright 1997 Microsoft Corporation.  All Rights Reserved.
**************************************************************************/

/******************************************************************************

   File:          PerStor.h

   Description:   CPersistStorage definitions.

******************************************************************************/

#ifndef PERSISTSTORAGE_H
#define PERSISTSTORAGE_H

/**************************************************************************
   #include statements
**************************************************************************/

#include "DOServer.h"

/**************************************************************************

   CPersistStorage class definition

**************************************************************************/

class COleDocument;

class CPersistStorage : public IPersistStorage
{
friend class CPersistFile;

private:
   COleDocument   *m_pOleDoc;
   LPSTORAGE      m_pStorage;
   LPSTREAM       m_pColorStream;
   BOOL           m_fSameAsLoad;

public:
   CPersistStorage(COleDocument*);
   ~CPersistStorage();

   //IUnknown methods
   STDMETHODIMP QueryInterface(REFIID, LPVOID*);
   STDMETHODIMP_(DWORD) AddRef();
   STDMETHODIMP_(DWORD) Release();

   //IPersistStorage methods
STDMETHODIMP InitNew(LPSTORAGE);
STDMETHODIMP GetClassID(LPCLSID);
STDMETHODIMP Save(LPSTORAGE, BOOL);
STDMETHODIMP SaveCompleted(LPSTORAGE);
STDMETHODIMP Load(LPSTORAGE);
STDMETHODIMP IsDirty();
STDMETHODIMP HandsOffStorage();

private:
void ReleaseStreamsAndStorage();
void OpenStreams(LPSTORAGE);
void CreateStreams(LPSTORAGE);
void CreateStreams(LPSTORAGE, LPSTREAM*);

};

#endif   //PERSISTSTORAGE_H


CLSFACT.CPP

/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Copyright 1997 Microsoft Corporation.  All Rights Reserved.
**************************************************************************/

/**************************************************************************

   File:          ClsFact.cpp

   Description:   CClassFactory implementation

**************************************************************************/

/**************************************************************************
   #include statements
**************************************************************************/

#include "ClsFact.h"

/**************************************************************************
   global variables
**************************************************************************/

extern DWORD   g_DllRefCount;

/**************************************************************************

   CClassFactory::CClassFactory

**************************************************************************/

CClassFactory::CClassFactory()
{
OutputDebugString(TEXT("CClassFactory's constructor\n"));

m_ObjRefCount = 0;
}

/**************************************************************************

   CClassFactory::~CClassFactory

**************************************************************************/

CClassFactory::~CClassFactory()
{
OutputDebugString(TEXT("CClassFactory's destructor\n"));
}

/**************************************************************************

   CClassFactory::QueryInterface

**************************************************************************/

STDMETHODIMP CClassFactory::QueryInterface(  REFIID riid,
                                             LPVOID *ppReturn)
{
OutputDebugString(TEXT("CClassFactory::QueryInterface\n"));

if(IsEqualIID(riid, IID_IUnknown))
   {
   *ppReturn = (LPUNKNOWN)(LPCLASSFACTORY)this;
   m_ObjRefCount++;
   return ResultFromScode(S_OK);
   }

if(IsEqualIID(riid, IID_IClassFactory))
   {
   *ppReturn = (LPCLASSFACTORY)this;
   m_ObjRefCount++;
   return ResultFromScode(S_OK);
   }

*ppReturn = NULL;
return ResultFromScode(E_NOINTERFACE);
}

/**************************************************************************

   CClassFactory::AddRef

**************************************************************************/

STDMETHODIMP_(DWORD) CClassFactory::AddRef()
{
OutputDebugString(TEXT("CClassFactory::AddRef\n"));

return ++m_ObjRefCount;
}

/**************************************************************************

   CClassFactory::Release

**************************************************************************/

STDMETHODIMP_(DWORD) CClassFactory::Release()
{
TCHAR szText[MAX_PATH];
wsprintf(szText, TEXT("CClassFactory::Release - ref count will be %d\n"), m_ObjRefCount - 1);
OutputDebugString(szText);

if(--m_ObjRefCount == 0)
   delete this;

return m_ObjRefCount;
}

/**************************************************************************

   CClassFactory::CreateInstance

**************************************************************************/

STDMETHODIMP CClassFactory::CreateInstance(  LPUNKNOWN pUnknown,
                                             REFIID riid,
                                             LPVOID *ppObject)
{
OutputDebugString(TEXT("CClassFactory::CreateInstance\n"));

*ppObject = NULL;

if(pUnknown != NULL)
   return ResultFromScode(CLASS_E_NOAGGREGATION);

COleDocument *pOleDoc = new COleDocument;
if(NULL == pOleDoc)
   return ResultFromScode(E_OUTOFMEMORY);

//get the QueryInterface return for our return value
HRESULT hr = pOleDoc->QueryInterface(riid, ppObject);

//return the result from QueryInterface
return hr;
}

/**************************************************************************

   CClassFactory::LockServer

**************************************************************************/

STDMETHODIMP CClassFactory::LockServer(BOOL fLock)
{
OutputDebugString(TEXT("CClassFactory::LockServer - "));

if(fLock)
   {
   OutputDebugString(TEXT("Lock\n"));
   g_DllRefCount++;
   }
else
   {
   OutputDebugString(TEXT("Unlock\n"));
   g_DllRefCount--;
   }

return S_OK;
}


RESOURCE.H

/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Copyright 1997 Microsoft Corporation.  All Rights Reserved.
**************************************************************************/

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by DOServer.rc
//
#define IDI_MAINICON                    101
#define IDR_COLOR_MENU                  102
#define IDD_MAIN_DIALOG                 103
#define IDD_COLOR_DIALOG                104
#define IDM_RED                         1000
#define IDM_GREEN                       1001
#define IDM_BLUE                        1002
#define IDC_REGISTER                    2000
#define IDC_CREATE_FILE                 2001
#define IDC_RED                         2001
#define IDC_GREEN                       2002
#define IDC_BLUE                        2003

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        105
#define _APS_NEXT_COMMAND_VALUE         1003
#define _APS_NEXT_CONTROL_VALUE         2004
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


UTILS.CPP

/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Copyright 1997 Microsoft Corporation.  All Rights Reserved.
**************************************************************************/

/**************************************************************************

   File:          Utils.cpp

   Description:   Contains utility routines for the DOServer sample.

**************************************************************************/

/**************************************************************************
   #include statements
**************************************************************************/

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

/**************************************************************************

   ParseCommandLine()

   Parses the Windows command line which was passed to WinMain.
   This function determines if the -Embedding switch has been given.

**************************************************************************/

STDAPI_(void) ParseCommandLine(LPSTR pszCommandLine, LPBOOL pfEmbedFlag, LPTSTR pszFileName)
{
int      i;
TCHAR    szCmdLine[MAX_PATH];
LPTSTR   pszCmdLine = szCmdLine;
TCHAR    szBuf[MAX_PATH];

if(!pszCommandLine)
   return;

if(!pfEmbedFlag)
   return;

*pfEmbedFlag = FALSE;

#ifdef UNICODE
MultiByteToWideChar( CP_ACP,
                     MB_PRECOMPOSED,
                     pszCommandLine,
                     -1,
                     pszCmdLine,
                     ARRAYSIZE(szCmdLine));
#else
lstrcpy(pszCmdLine, pszCommandLine);
#endif

// skip blanks
while(isspace(*pszCmdLine))
   pszCmdLine++;

// No filename or options, so start a fresh document.
if(!*pszCmdLine)
   return;

// Check for "-Embedding" or "/Embedding" and set fEmbedding.
if((*pszCmdLine == '-') || (*pszCmdLine == '/'))
   {
   pszCmdLine++;
   pszCmdLine = GetWord(pszCmdLine, szBuf);
   *pfEmbedFlag = !lstrcmp(szBuf, EMBEDDINGFLAG);
   }

if(!pszFileName)
   return;

*pszFileName = 0;

// skip blanks
while(isspace(*pszCmdLine))
   pszCmdLine++;

// set pszFileName to argument
for(i = 0; *(pszCmdLine + i); i++)
   {
   *(pszFileName + i) = *(pszCmdLine + i);
   }

*(pszFileName + i) = 0;
}

/**************************************************************************

   GetWord()

   LPSTR lpszSrc - Pointer to a source string
   LPSTR lpszDst - Pointer to destination buffer

   Will copy one space-terminated or null-terminated word from the source
   string to the destination buffer.

   returns: pointer to next character following the word.

**************************************************************************/

static LPTSTR GetWord(LPTSTR lpszSrc, LPTSTR lpszDst)
{
while (*lpszSrc && !isspace(*lpszSrc))
   *lpszDst++ = *lpszSrc++;

*lpszDst = '\0';

return lpszSrc;
}

/* GetOleObjectDataHere
** ----------------------
**    Render CF_EMBEDSOURCE/CF_EMBEDDEDOBJECT data on an TYMED_ISTORAGE
**    medium by asking the object to save into the storage.
**    the object must support IPersistStorage.
**
**    if lpMedium->tymed == TYMED_NULL, then a delete-on-release
**    storage is allocated (either file-based or memory-base depending
**    the value of fUseMemory). this is useful to support an
**    IDataObject::GetData call where the callee must allocate the
**    medium.
**
**    if lpMedium->tymed == TYMED_ISTORAGE, then the data is writen
**    into the passed in IStorage. this is useful to support an
**    IDataObject::GetDataHere call where the caller has allocated his
**    own IStorage.
*/
STDAPI GetOleObjectDataHere(  LPPERSISTSTORAGE lpPStg,
                              LPFORMATETC lpformatetc,
                              LPSTGMEDIUM lpMedium)
{
LPSTORAGE   lpstg = NULL;
DWORD       reserved = 0;
HRESULT     hr = DATA_E_FORMATETC;

lpMedium->pUnkForRelease = NULL;

if(lpMedium->tymed != TYMED_ISTORAGE)
   {
   return hr;
   }

//AddRef the IStorage pointer
lpPStg->AddRef();

// NOTE: even if OleSave returns an error you should still call
// SaveCompleted.

hr = OleSave(lpPStg, lpMedium->pstg, FALSE /* fSameAsLoad */);

lpPStg->SaveCompleted(NULL);

return hr;
}


UTILS.H

/**************************************************************************
   THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
   ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
   THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
   PARTICULAR PURPOSE.

   Copyright 1997 Microsoft Corporation.  All Rights Reserved.
**************************************************************************/

/**************************************************************************

   File:          Utils.h

   Description:   Utility definitions.

**************************************************************************/

/**************************************************************************
   function prototypes
**************************************************************************/

STDAPI_(void) ParseCommandLine(LPSTR, LPBOOL, LPTSTR);
static LPTSTR GetWord(LPTSTR, LPTSTR);
STDAPI GetOleObjectDataHere(LPPERSISTSTORAGE, LPFORMATETC, LPSTGMEDIUM);

/**************************************************************************
   global variables
**************************************************************************/

#define EMBEDDINGFLAG TEXT("Embedding")


README.TXT

======================================================
Notes for Active Documents "Framer" (Container) Sample
======================================================

Framer is intended to demonstrate simple Active Document Objects hosting as
a container.  That is, Framer demonstrates the basic support necessary to
host Active Documents.  It is implemented according to the guidlines in the
"Active Documents Specification" document and has been tested against
Microsoft Word and Microsoft Excel as well as a few other OLE-enabled
applications that do not support Active Documents.

Framer itself has almost nothing in the way of its own user interface such
as a more complete host would, such as the Office Binder.  Framer has these
menu commands available:

    Command     Description
    ----------------------------------------------------------------------
    File Open   Displays a File Open dialog and allows the user to select
                a file.  Framer will attempt to activate the file as an
                Active Document.  Failing that it will create a standard
                embedded object and activate it in a separate window.
                This command is disabled when an object already exists.
                File/Close must be used before creating another object.

    File Close  Closes the document that is currently open; if the object
                is just an embedding it will destroy that object which has
                the effect of closing the server as well.  Try opening
                a .BMP file (which should activate PaintBrush, for instance)
                and use Close to see the effect.  There are some variations to
this behavior (see next section below).

    File Exit   Performs File/Close if necessary and terminates Framer.

    Help About  Displays Framer's About box.  This command exists to
                demonstrate Active Document Help menu merging.

Whenever Framer knows it has an object it displays a small message in its
client area to remind you that you have to File/Close before creating another.

Creating and Activating an Object
---------------------------------

Framer allows you to open documents regardless of whether or not
there is a server that supports Active Documents for the document
type and regardless of whether or not the document server even knows
about Active Documents at all.

When you "Open" a file in Framer, the code call OleCreateFromFile to create
an embedded object from the contents of the file.  This has two possible
results:

    1.  If there is an Active Documents server associated with the file,
        an embedded object initialized with the file contents is created.

    2.  If there is not an Active Documents server associated with the file,
        a package object is created.

Immediately after creation, Framer activates the object with IOleObject::DoVerb
passing OLEIVERB_SHOW.

If the object is a true embedding with server support, one of two things
will happen:

    1a. If the object doesn't know about Active Documents, it is activated in
        another window.  Using File/Close in this case will close the server.
        Closing the server itself will cause Framer to do the equivalent
        of File/Close, which frees the object and re-enables File/Open.
        See CImpIIAdviseSink::OnClose in iadvsink.cpp.  The closing
        sequence is in CFrame::Close in framer.cpp.  Using Framer with the
    Paint application will demonstrate this behavior.

    1b. If the object has Active Document support, then its server will start
        activation sequence, primarily by calling IOleDocumentSite::ActivateMe
        which is found in idocsite.cpp.  Inside this member, Framer
        then performs the standard sequence of document object activation
        steps.  After these steps the Active Document will be fully
        interactive; using File/Close on the menu, which Framer still owns,
        will deactivate the object through CFrame::Close which performs the
        same set of steps as in #1b above.  Using Framer with Microsoft Word
    will demonstrate this behavior.

If you activate a package object, the result is that you launch an application
and have it open the file.  Since the application has no OLE support in it
whatsoever, there is no communication link between Framer and that application.
This means that closing Framer will not close that other application.  This is
what one observes with opening a .TXT file in Framer which will launch Notepad.
Once Notepad is running, it's separate and disconnected from Framer.  You have
to close Notepad manually.

Framer might have been written to disallow insertion of files that do not have
Active Documents support.  Framer rather demonstrates how to view any file
regardless of server support.

Help Menu Merging
-----------------

Framer implements the container side of the Help menu merging protocol
described in the Active Documents Specification.  This involves the members
CFrame::InsertMenus, CFrame::SetMenu, and CFrame::RemoveMenus.  The
InsertMenus method will install Framer's Help menu in the correct location
on the shared menu.  The container-side popup used here is loaded from
Framer's resources on startup in CFrame::Init.

Inside CFrame::SetMenu, Framer checks if there's more than one item on
the menu added in InsertMenus.  If so, then Framer remembers this fact
for later message handling.  Otherwise Framer removes this menu from the
shared menu entirely as the dicument object itself isn't using this shared
capability.

Inside CFrame::RemoveMenus, Framer simply makes sure that its own Help
menu is removed as it should be.

The really interesting stuff happens in FrameWndProc (framer.cpp) in the
WM_INITMENU, WM_INITMENUPOPUP, WM_MENUSELECT, and WM_COMMAND cases.  Inside
WM_INITMENU Framer clears a flag that indicates whether the last popup menu
that was being used was the object's additions to the Help menu.  Inside
WM_MENUSELECT, Framer checks if the originating menu is a popup and if so,
it checks if that popup is on the shared "Help" popup, and if that is also
true then Framer checks if the item being used is the first one or some
other one.  The first item is what Framer knows to be its own Help item, so
it just handles the messages as usual.  Otherwise the user is working with
an Active Document-owned menu, so Framer sets the flag m_fInObjectHelp variable
to TRUE and forwards the message to the object's window (available from
IOleInPlaceObject::GetWindow).

As long as m_fInObjectHelp is set, Framer will forward WM_COMMAND,
WM_INITMENUPOPUP, and WM_MENUSELECT messages.  As soon as another
WM_MENUSELECT message is seen for another non-object menu, then Framer
will reset the flag and begin processing messages once again itself.

In this way you'll see, with Microsoft Word for example, the correct
behavior of a shared Help popup.  Note that Microsoft Excel does not
exhibit this shared Help menu behavior as it only displays its own
Help menus.

Known Feature Limitations
-------------------------
1.  Framer does not print so it does not use the IPrint interface nor does
    it implement IContinueCallback.

2.  Framer does nothing with command targets.

3.  Framer does not forward owner-draw menu messages to the object which
    means that if owner-draw menus are used in a DocObject help menu,
    this sample will not work correctly.

4.  Framer does not provide for actually saving any changes made to an
    Active Document.  Because an Active Document is an embedding, Framer has
    to provide an instance of IStorage through IPersistStorage::InitNew
    or IPersistStorage::Load.  It does this using a temporary Compound File
    that is deleted when Framer exits.  Therefore any changes made to the data
    in the Active Document will simply be discarded.

Potential for Better UI
-----------------------

The item #4 above takes a little more explanation.  When OleCreateFromFile is
used in the File/Open command, Framer is making a COPY of the file contents.
When an Active Document is activated with this content, the document has a copy
of the file contents, not the contents of the file itself.  Therefore any
changes made there will not be reflected back into the original file, although
Framer's UI suggests that this should happen.

A real Active Document container like the Office Binder actually stores all the
object data in its own Compound File, not as separate files.  If one needs
to have an Active Document save into a separate file, then the container needs
to either use command-targets or has to call IPersistFile::Save to accomplish
this step.

In short, Active Documents is not about activating the apps that manipulate
files; it's about activating embedded *documents* saved within the container
file *as if* those documents were stand alone.

Framer's UI, which is really inappropriate for DocObjects, exists as it does
for simplicity's sake.

A more appropraite container application would maintain its own "files" in
which it collected data from a number of other "documents" which are stored
simply as embedded objects.  The Office Binder does exactly this, where
a "Binder" is a Compound File with sub-storages for each "section" in the
document.

Certainly with some more work on UI, Framer could become such an application.


FRAMER.H

/*
 * FRAMER.H
 * Document Objects Framer
 * Definitions of classes, constants, etc.
 * Includes in-line implementation of CHourGlass, CWindow
 *
 * Copyright (c)1995-1997 Microsoft Corporation, All Rights Reserved
 */

#ifndef _FRAMER_H_
#define _FRAMER_H_

#define INC_OLE2
#include <docobj.h>

#include <tchar.h>
#ifdef UNICODE
#include <wchar.h>
#endif

extern "C"
    {
    #include <commdlg.h>
    }

#include "resource.h"

//DeleteInterfaceImp calls 'delete' and NULLs the pointer
#define DeleteInterfaceImp(p)\
            {\
            if (NULL!=p)\
                {\
                delete p;\
                p=NULL;\
                }\
            }

//ReleaseInterface calls 'Release' and NULLs the pointer
#define ReleaseInterface(p)\
            {\
            IUnknown *pt=(IUnknown *)p;\
            p=NULL;\
            if (NULL!=pt)\
                pt->Release();\
            }

class CHourglass;
class CFrame;
class CSite;

/**
 ** CFrame encapsulates a main application window
 **/

//FRAMEWIN.CPP:  Standard window procedure and AboutProc
LRESULT APIENTRY FrameWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT APIENTRY ClientWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL    APIENTRY AboutProc(HWND, UINT, WPARAM, LPARAM);

#define CBFRAMEWNDEXTRA     sizeof(LONG)
#define FRAMEWL_STRUCTURE   0

#define CBCLIENTWNDEXTRA    sizeof(LONG)
#define CLIENTWL_STRUCTURE  0

#define SZCLASSFRAME        TEXT("Frame")
#define SZCLASSCLIENT       TEXT("Client")

//FRAME.CPP:  Frame object that creates a main window

class CFrame : public IOleInPlaceFrame, public IOleCommandTarget
    {
    //Let our window procedure look in our private variables.
    friend LRESULT APIENTRY FrameWndProc(HWND, UINT, WPARAM, LPARAM);
friend LRESULT APIENTRY ClientWndProc(HWND, UINT, WPARAM, LPARAM);

    protected:
        HINSTANCE       m_hInst;            //Task instance
        HWND            m_hWnd;             //Window handle of the window
        ULONG           m_cRef;

        HINSTANCE       m_hInstPrev;        //WinMain parameters
        LPTSTR          m_pszCmdLine;
        int             m_nCmdShow;

        HMENU          *m_phMenu;           //Popup menu handles
        HACCEL          m_hAccel;           //Accelerators
        HWND            m_hWndClient;       //Client area window

        BOOL            m_fInitialized;     //OleInitialize called?
        IStorage       *m_pIStorage;        //Temp file for all needs
        DWORD           m_dwIDCounter;      //For site IDs

        //For menu merging
        HMENU           m_hMenuOrg;         //Original menu
        HMENU           m_hMenuTop;//Currently shown menu
HMENUm_hMenuHelp;//Special Help menu
BOOLm_fUsingOurHelp;//Help menu in use?
        BOOLm_fInObjectHelp;//Object's menu found?
        BOOL            m_fOurMenuShowing;  //Our menu up?

        //Other object needs
        BOOL            m_fHaveObject;      //For menu enabling
HWNDm_hWndObj;//UI Active object

        class CSite    *m_pSite;            //Site holding object
        BORDERWIDTHS    m_bwIP;             //In-place tool allocations
        BOOL            m_fInContextHelp;   //In context help mode?

        IOleInPlaceActiveObject *m_pIOleIPActiveObject;

    protected:
        BOOL    RegisterAllClasses(void);
        LRESULT OnCommand(HWND, WPARAM, LPARAM);
        BOOL    OpenDialog(LPTSTR, UINT);
        BOOL    CreateObject(LPTSTR);
voidResizeClientWindow(UINT, UINT, UINT, UINT);

    public:
        CFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        ~CFrame(void);

        inline HINSTANCE Instance(void)
        { return m_hInst; }

        inline HWND      Window(void)
        { return m_hWnd; }

        inline HACCEL    Accelerators(void)
        { return m_hAccel; }

        BOOL    Init(UINT, int, int, int, int);
        WPARAM  MessageLoop(void);
void    Close();

        //Shared IUnknown implementation
        STDMETHODIMP         QueryInterface(REFIID, void **);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

//IOleInPlaceFrame implementation
        STDMETHODIMP         GetWindow(HWND *);
        STDMETHODIMP         ContextSensitiveHelp(BOOL);
        STDMETHODIMP         GetBorder(LPRECT);
        STDMETHODIMP         RequestBorderSpace(LPCBORDERWIDTHS);
        STDMETHODIMP         SetBorderSpace(LPCBORDERWIDTHS);
        STDMETHODIMP         SetActiveObject(LPOLEINPLACEACTIVEOBJECT
                                 , LPCOLESTR);
        STDMETHODIMP         InsertMenus(HMENU, LPOLEMENUGROUPWIDTHS);
        STDMETHODIMP         SetMenu(HMENU, HOLEMENU, HWND);
        STDMETHODIMP         RemoveMenus(HMENU);
        STDMETHODIMP         SetStatusText(LPCOLESTR);
        STDMETHODIMP         EnableModeless(BOOL);
        STDMETHODIMP         TranslateAccelerator(LPMSG, WORD);

//IOleCommandTarget
        STDMETHODIMP QueryStatus(const GUID *pguidCmdGroup, ULONG cCmds
            , OLECMD prgCmds[], OLECMDTEXT *pCmdText);

        STDMETHODIMP Exec(const GUID *pguidCmdGroup, DWORD nCmdID
            , DWORD nCmdexecopt, VARIANTARG *pvaIn, VARIANTARG *pvaOut);
    };

typedef CFrame *PCFrame;

/**
 ** CHourglass:  Manages the hourglass and mouse capture
 **/

class CHourglass
    {
    protected:
        HWND            m_hWndCapture;      //Window with capture
        HCURSOR         m_hCur;             //Cursor held.

    public:
        CHourglass(void)
            {
            m_hCur=SetCursor(LoadCursor(NULL, IDC_WAIT));
            m_hWndCapture=NULL;
            return;
            }

        CHourglass(HWND hWnd)
            {
            m_hCur=SetCursor(LoadCursor(NULL, IDC_WAIT));

            if (NULL!=hWnd)
                {
                m_hWndCapture=hWnd;
                SetCapture(hWnd);
                }

            return;
            }

        ~CHourglass(void)
            {
            if (NULL!=m_hWndCapture)
                ReleaseCapture();

            SetCursor(m_hCur);
            return;
            }
    };

typedef CHourglass *PCHourglass;

#include "site.h"

#endif //_FRAMER_H_


IADVSINK.CPP

/*
 * IADVSINK.CPP
 * IAdviseSink for Document Objects CSite class
 *
 * Copyright (c)1995-1997 Microsoft Corporation, All Rights Reserved
 */

#include "framer.h"

/*
 * CImpIAdviseSink::CImpIAdviseSink
 * CImpIAdviseSink::~CImpIAdviseSink
 *
 * Parameters (Constructor):
 *  pSite           PCSite of the site we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIAdviseSink::CImpIAdviseSink(PCSite pSite
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pSite=pSite;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIAdviseSink::~CImpIAdviseSink(void)
    {
    return;
    }

/*
 * CImpIAdviseSink::QueryInterface
 * CImpIAdviseSink::AddRef
 * CImpIAdviseSink::Release
 *
 * Purpose:
 *  IUnknown members for CImpIAdviseSink object.
 */

STDMETHODIMP CImpIAdviseSink::QueryInterface(REFIID riid, void **ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * Unused members in CImpIAdviseSink
 *  OnDataChange
 *  OnViewChange
 *  OnRename
 *  OnSave
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnDataChange(LPFORMATETC pFEIn
    , LPSTGMEDIUM pSTM)
    {
    return;
    }

STDMETHODIMP_(void) CImpIAdviseSink::OnViewChange(DWORD dwAspect
    , LONG lindex)
    {
    return;
    }

STDMETHODIMP_(void) CImpIAdviseSink::OnRename(LPMONIKER pmk)
    {
    return;
    }

STDMETHODIMP_(void) CImpIAdviseSink::OnSave(void)
    {
    return;
    }

/*
 * CImpIAdviseSink::OnClose
 *
 * Purpose:
 *  Informs the advise sink that the OLE object has closed and is
 *  no longer bound in any way.  We use this to do a File/Close
 *  to delete the object since we don't hold onto any.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnClose(void)
    {
//This does the same as File/Close
//Not quite because CFrame::Close requires some synchronous
//calls that will fail if done here, so just post a message to
//do this
    //m_pSite->m_pFR->Close();

HWNDhwnd;
m_pSite->m_pFR->GetWindow(&hwnd);
PostMessage(
hwnd,
WM_COMMAND,
MAKELONG(IDM_FILECLOSE, 0),
0);
    return;
    }


ICLISITE.CPP

/*
 * ICLISITE.CPP
 * IOleClientSite for Document Objects CSite class
 *
 * Copyright (c)1995-1997 Microsoft Corporation, All Rights Reserved
 */

#include "framer.h"

/*
 * CImpIOleClientSite::CImpIOleClientSite
 * CImpIOleClientSite::~CImpIOleClientSite
 *
 * Parameters (Constructor):
 *  pSite           PCSite of the site we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleClientSite::CImpIOleClientSite(PCSite pSite
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pSite=pSite;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleClientSite::~CImpIOleClientSite(void)
    {
    return;
    }

/*
 * CImpIOleClientSite::QueryInterface
 * CImpIOleClientSite::AddRef
 * CImpIOleClientSite::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleClientSite object.
 */

STDMETHODIMP CImpIOleClientSite::QueryInterface(REFIID riid
    , void **ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleClientSite::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleClientSite::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleClientSite::SaveObject
 *
 * Purpose:
 *  Requests that the container call OleSave for the object that
 *  lives here.  Typically this happens on server shutdown.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::SaveObject(void)
    {
    m_pSite->Update();
    return NOERROR;
    }

/*
 * Unimplemented/trivial members
 *  GetMoniker
 *  GetContainer
 *  RequestNewObjectLayout
 *  OnShowWindow
 *  ShowObject
 */

STDMETHODIMP CImpIOleClientSite::GetMoniker(DWORD dwAssign
    , DWORD dwWhich, LPMONIKER *ppmk)
    {
    return E_NOTIMPL;
    }

STDMETHODIMP CImpIOleClientSite::GetContainer(LPOLECONTAINER
    * ppContainer)
    {
*ppContainer=NULL;
return E_NOINTERFACE;
    }

STDMETHODIMP CImpIOleClientSite::RequestNewObjectLayout(void)
    {
    return E_NOTIMPL;
    }

STDMETHODIMP CImpIOleClientSite::OnShowWindow(BOOL fShow)
    {
    return NOERROR;
    }

STDMETHODIMP CImpIOleClientSite::ShowObject(void)
    {
    return NOERROR;
    }


IDOCSITE.CPP

/*
 * IDOCSITE.CPP
 * IOleDocumentSite for Document Objects CSite class
 *
 * Copyright (c)1995-1997 Microsoft Corporation, All Rights Reserved
 */

#include "framer.h"

/*
 * CImpIOleDocumentSite::CImpIOleDocumentSite
 * CImpIOleDocumentSite::~CImpIOleDocumentSite
 *
 * Parameters (Constructor):
 *  pSite           PCSite of the site we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleDocumentSite::CImpIOleDocumentSite(PCSite pSite
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pSite=pSite;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleDocumentSite::~CImpIOleDocumentSite(void)
    {
    return;
    }

/*
 * CImpIOleDocumentSite::QueryInterface
 * CImpIOleDocumentSite::AddRef
 * CImpIOleDocumentSite::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleDocumentSite object.
 */

STDMETHODIMP CImpIOleDocumentSite::QueryInterface(REFIID riid
    , void **ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleDocumentSite::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleDocumentSite::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleDocumentsite::ActivateMe
 *
 * Purpose:
 *  Instructs the container to activate the object in this site as
 *  a document object.
 *
 * Parameters:
 *  pView           IOleDocumentView * of the object to activate.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, error code otherwise.
 */

STDMETHODIMP CImpIOleDocumentSite::ActivateMe(IOleDocumentView *pView)
    {
    RECT                rc;
    IOleDocument*       pDoc;

    /*
     * If we're passed a NULL view pointer, then try to get one from
     * the document object (the object within us).
     */
    if (NULL==pView)
        {

        if (FAILED(m_pSite->m_pObj->QueryInterface(IID_IOleDocument
            , (void **)&pDoc)))
            return E_FAIL;

        if (FAILED(pDoc->CreateView(m_pSite->m_pImpIOleIPSite
            , m_pSite->m_pIStream, 0, &pView)))
            return E_OUTOFMEMORY;

        // Release doc pointer since CreateView is a good com method that addrefs
        pDoc->Release();
        }
    else
        {
        //Make sure that the view has our client site
        pView->SetInPlaceSite(m_pSite->m_pImpIOleIPSite);

        //We're holding onto the pointer, so AddRef it.
        pView->AddRef();
        }

    /*
     * Activation steps, now that we have a view:
     *
     *  1.  Call IOleDocumentView::SetInPlaceSite (assume done since
     *      either the view already knows, or IOleDocument::CreateView
     *      has done it already.
     *
     *  2.  Call IOleDocumentView::SetRect to give a bunch of space to
     *      the view.  In our case this is the whole client area of
     *      the CPages window.  (Patron doesn't use SetRectComplex)
     *
     *  3.  Call IOleDocumentView::Show to make the thing visible.
     *
     *  4.  Call IOleDocumentView::UIActivate to finish the job.
     *
     */

    m_pSite->m_fDocObj=TRUE;
    m_pSite->m_pIOleDocView=pView;

    //This sets up toolbars and menus first
    pView->UIActivate(TRUE);

    //Set the window size sensitive to new toolbars
    GetClientRect(m_pSite->m_hWnd, &rc);
    pView->SetRect(&rc);

//Makes it all active
    pView->Show(TRUE);
    return NOERROR;
    }


IIPSITE.CPP

/*
 * IIPSITE.CPP
 * IOleInPlaceSite for Document Objects CSite class
 *
 * Copyright (c)1995-1997 Microsoft Corporation, All Rights Reserved
 */

#include "framer.h"

/*
 * CImpIOleInPlaceSite::CImpIOleInPlaceSite
 * CImpIOleInPlaceSite::~CImpIOleInPlaceSite
 *
 * Parameters (Constructor):
 *  pSite           PCSite of the site we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleInPlaceSite::CImpIOleInPlaceSite(PCSite pSite
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pSite=pSite;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleInPlaceSite::~CImpIOleInPlaceSite(void)
    {
    return;
    }

/*
 * CImpIOleInPlaceSite::QueryInterface
 * CImpIOleInPlaceSite::AddRef
 * CImpIOleInPlaceSite::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleInPlaceSite object.
 */

STDMETHODIMP CImpIOleInPlaceSite::QueryInterface(REFIID riid
    , void **ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceSite::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceSite::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleInPlaceActiveObject::GetWindow
 *
 * Purpose:
 *  Retrieves the handle of the window associated with the object
 *  on which this interface is implemented.
 *
 * Parameters:
 *  phWnd           HWND * in which to store the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_FAIL if there is no
 *                  window.
 */

STDMETHODIMP CImpIOleInPlaceSite::GetWindow(HWND *phWnd)
    {
    //This is the client-area window in the frame
    *phWnd=m_pSite->m_hWnd;
    return NOERROR;
    }

/*
 * CImpIOleInPlaceActiveObject::ContextSensitiveHelp
 *
 * Purpose:
 *  Instructs the object on which this interface is implemented to
 *  enter or leave a context-sensitive help mode.
 *
 * Parameters:
 *  fEnterMode      BOOL TRUE to enter the mode, FALSE otherwise.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceSite::ContextSensitiveHelp
    (BOOL fEnterMode)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::CanInPlaceActivate
 *
 * Purpose:
 *  Answers the server whether or not we can currently in-place
 *  activate its object.  By implementing this interface we say
 *  that we support in-place activation, but through this function
 *  we indicate whether the object can currently be activated
 *  in-place.  Iconic aspects, for example, cannot, meaning we
 *  return S_FALSE.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR if we can in-place activate the object
 *                  in this site, S_FALSE if not.
 */

STDMETHODIMP CImpIOleInPlaceSite::CanInPlaceActivate(void)
    {
    /*
     * We can always in-place activate--no restrictions for DocObjects.
     * We don't worry about other cases since CSite only ever creates
     * embedded files.
     */
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::OnInPlaceActivate
 *
 * Purpose:
 *  Informs the container that an object is being activated in-place
 *  such that the container can prepare appropriately.  The
 *  container does not, however, make any user interface changes at
 *  this point.  See OnUIActivate.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an appropriate error code.
 */

STDMETHODIMP CImpIOleInPlaceSite::OnInPlaceActivate(void)
    {
    m_pSite->m_pObj->QueryInterface(IID_IOleInPlaceObject
        , (void **)&m_pSite->m_pIOleIPObject);

    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::OnInPlaceDeactivate
 *
 * Purpose:
 *  Notifies the container that the object has deactivated itself
 *  from an in-place state.  Opposite of OnInPlaceActivate.  The
 *  container does not change any UI at this point.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an appropriate error code.
 */

STDMETHODIMP CImpIOleInPlaceSite::OnInPlaceDeactivate(void)
    {
    /*
     * Since we don't have an Undo command, we can tell the object
     * right away to discard its Undo state.
     */
    m_pSite->Activate(OLEIVERB_DISCARDUNDOSTATE);
    ReleaseInterface(m_pSite->m_pIOleIPObject);
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::OnUIActivate
 *
 * Purpose:
 *  Informs the container that the object is going to start munging
 *  around with user interface, like replacing the menu.  The
 *  container should remove any relevant UI in preparation.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an appropriate error code.
 */

STDMETHODIMP CImpIOleInPlaceSite::OnUIActivate(void)
    {
    //No state we have to set up here.
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::OnUIDeactivate
 *
 * Purpose:
 *  Informs the container that the object is deactivating its
 *  in-place user interface at which time the container may
 *  reinstate its own.  Opposite of OnUIActivate.
 *
 * Parameters:
 *  fUndoable       BOOL indicating if the object will actually
 *                  perform an Undo if the container calls
 *                  ReactivateAndUndo.
 *
 * Return Value:
 *  HRESULT         NOERROR or an appropriate error code.
 */

STDMETHODIMP CImpIOleInPlaceSite::OnUIDeactivate(BOOL fUndoable)
    {
    //Set focus back to the frame
    SetFocus(m_pSite->m_pFR->Window());

    //Show our menu again
    m_pSite->m_pFR->SetMenu(NULL, NULL, NULL);

    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::DeactivateAndUndo
 *
 * Purpose:
 *  If immediately after activation the object does an Undo, the
 *  action being undone is the activation itself, and this call
 *  informs the container that this is, in fact, what happened.
 *  The container should call IOleInPlaceObject::UIDeactivate.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an appropriate error code.
 */

STDMETHODIMP CImpIOleInPlaceSite::DeactivateAndUndo(void)
    {
    m_pSite->m_pIOleIPObject->InPlaceDeactivate();
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::DiscardUndoState
 *
 * Purpose:
 *  Informs the container that something happened in the object
 *  that means the container should discard any undo information
 *  it currently maintains for the object.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an appropriate error code.
 */

STDMETHODIMP CImpIOleInPlaceSite::DiscardUndoState(void)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleInPlaceSite::GetWindowContext
 *
 * Purpose:
 *  Provides an in-place object with pointers to the frame and
 *  document level in-place interfaces (IOleInPlaceFrame and
 *  IOleInPlaceUIWindow) such that the object can do border
 *  negotiation and so forth.  Also requests the position and
 *  clipping rectangles of the object in the container and a
 *  pointer to an OLEINPLACEFRAME info structure which contains
 *  accelerator information.
 *
 *  Note that the two interfaces this call returns are not
 *  available through QueryInterface on IOleInPlaceSite since they
 *  live with the frame and document, but not the site.
 *
 * Parameters:
 *  ppIIPFrame      LPOLEINPLACEFRAME * in which to return the
 *                  AddRef'd pointer to the container's
 *                  IOleInPlaceFrame.
 *  ppIIPUIWindow   LPOLEINPLACEUIWINDOW * in which to return
 *                  the AddRef'd pointer to the container document's
 *                  IOleInPlaceUIWindow.
 *  prcPos          LPRECT in which to store the object's position.
 *  prcClip         LPRECT in which to store the object's visible
 *                  region.
 *  pFI             LPOLEINPLACEFRAMEINFO to fill with accelerator
 *                  stuff.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceSite::GetWindowContext
    (LPOLEINPLACEFRAME *ppIIPFrame, LPOLEINPLACEUIWINDOW
    *ppIIPUIWindow, LPRECT prcPos, LPRECT prcClip
    , LPOLEINPLACEFRAMEINFO pFI)
    {
    *ppIIPUIWindow=NULL;
    m_pSite->m_pFR->QueryInterface(IID_IOleInPlaceFrame
        , (void **)ppIIPFrame);

    if (NULL!=prcPos)
        GetClientRect(m_pSite->m_hWnd, prcPos);

    *prcClip=*prcPos;

    pFI->cb=sizeof(OLEINPLACEFRAMEINFO);
    pFI->fMDIApp=FALSE;
    pFI->hwndFrame=m_pSite->m_pFR->Window();
    pFI->haccel=m_pSite->m_pFR->Accelerators();
    pFI->cAccelEntries=CACCELERATORS;

    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::Scroll
 *
 * Purpose:
 *  Asks the container to scroll the document, and thus the object,
 *  by the given amounts in the sz parameter.
 *
 * Parameters:
 *  sz              SIZE containing signed horizontal and vertical
 *                  extents by which the container should scroll.
 *                  These are in device units.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceSite::Scroll(SIZE sz)
    {
    //Not needed for DocObjects
    return E_NOTIMPL;
    }

/*
 * CImpIOleInPlaceSite::OnPosRectChange
 *
 * Purpose:
 *  Informs the container that the in-place object was resized.
 *  The container must call IOleInPlaceObject::SetObjectRects.
 *  This does not change the site's rectangle in any case.
 *
 * Parameters:
 *  prcPos          LPCRECT containing the new size of the object.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceSite::OnPosRectChange(LPCRECT prcPos)
    {
    //Not needed for DocObjects
    return E_NOTIMPL;
    }


FRAMER.CPP

/*
 * FRAMER.CPP
 * Document Objects Framer
 *
 * Sample to demonstrate in-place activation of a DocObject--also
 * activates normal embeddings in separate windows.
 *
 * Copyright (c)1995-1997 Microsoft Corporation, All Rights Reserved
 */

#define INITGUID
#include "framer.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCFrame         pFR;
    WPARAM          wRet;

    //Attempt to allocate and initialize the application
    pFR=new CFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return 0;

    //If we can initialize pFR, start chugging messages
    if (pFR->Init(CMENUS, CW_USEDEFAULT, CW_USEDEFAULT
        , CW_USEDEFAULT, CW_USEDEFAULT))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

/*
 * CFrame::CFrame
 * CFrame::~CFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WinMain
 */

CFrame::CFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    m_hInst=hInst;
    m_hWnd=NULL;
    m_hInstPrev=hInstPrev;
    m_nCmdShow=nCmdShow;

    m_phMenu=NULL;
    m_hAccel=NULL;
    m_hWndClient=NULL;

    m_fInitialized=FALSE;
    m_pIStorage=NULL;
    m_dwIDCounter=0;

    m_hMenuOrg=NULL;
    m_hMenuTop=NULL;
m_hMenuHelp=NULL;
m_fInObjectHelp=FALSE;
m_fUsingOurHelp=FALSE;

    m_fHaveObject=FALSE;
m_hWndObj=NULL;

    m_pSite=NULL;
    m_fOurMenuShowing=TRUE;
    SetRect(&m_bwIP, 0, 0, 0, 0);
    m_fInContextHelp=FALSE;
    m_pIOleIPActiveObject=NULL;

    return;
    }

CFrame::~CFrame(void)
    {
    if (NULL!=m_hWndClient)
        DestroyWindow(m_hWndClient);

    //Frees the temp file.
    ReleaseInterface(m_pIStorage);

    //m_pSite cleaned up in Close

    //Accelerators freed automatically.

//Destroy the special help menu
if (NULL!=m_hMenuHelp)
DestroyMenu(m_hMenuHelp);

    //Free the menu handle array
    if (NULL!=m_phMenu)
        delete []((UINT *)m_phMenu);

    if (m_fInitialized)
        OleUninitialize();

    return;
    }

/*
 * CFrame::Init
 *
 * Purpose:
 *  Initializer for a CFrame object containing anything prone to
 *  failure.
 *
 * Parameters:
 *  cMenus          UINT number of menus on the frame
 *  x, y, cx, cy    int location and size of frame window
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded, FALSE
 *                  otherwise. If FALSE is returned, the caller must
 *                  guarantee that the destructor is called promptly
 *                  to insure cleanup.
 */

BOOL CFrame::Init(UINT cMenus, int x, int y, int cx, int cy)
    {
    HMENU               hMenu;
    UINT                uTemp;
    RECT                rc;

    //1.  Initialize OLE
    if (SUCCEEDED(OleInitialize(NULL)))
        m_fInitialized=TRUE;
    else
        return FALSE;

    //2.  Register window classes
    if (NULL==m_hInstPrev)
        {
        if (!RegisterAllClasses())
            return FALSE;
        }

    //3.  Create the main window and client-area window
    m_hWnd=CreateWindow(SZCLASSFRAME, TEXT("DocObject Framer")
        , WS_MINIMIZEBOX | WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN
        , x, y, cx, cy, NULL, NULL, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    GetClientRect(m_hWnd, &rc);

    m_hWndClient=CreateWindow(SZCLASSCLIENT, SZCLASSCLIENT
        , WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | WS_CLIPSIBLINGS
        , rc.left, rc.top, rc.right-rc.left
        , rc.bottom-rc.top, m_hWnd, NULL, m_hInst, this);

    if (NULL==m_hWndClient)
        return FALSE;

    //4. Allocate menu array for use with in-place menu merging.
    m_phMenu=new HMENU[cMenus];
    hMenu=GetMenu(m_hWnd);
    m_hMenuOrg=hMenu;

    for (uTemp=0; uTemp < cMenus; uTemp++)
        m_phMenu[uTemp]=GetSubMenu(hMenu, uTemp);

//Also load the special help menu
m_hMenuHelp=LoadMenu(m_hInst
, MAKEINTRESOURCE(IDR_MENUHELP));

    //5.  Load accelerators
    m_hAccel=LoadAccelerators(m_hInst
        , MAKEINTRESOURCE(IDR_ACCELERATORS));

    if (NULL==m_hAccel)
        return FALSE;

    //6.  Make us all visible.
    ShowWindow(m_hWnd, m_nCmdShow);
    UpdateWindow(m_hWnd);

    /*
     * 7.  Create a temp file for all embedded files.  Note that in this
     *     sample we don't save any changes to DocObjects because we
 *     don't manage any storage directly.
 */
    if (FAILED(StgCreateDocfile(NULL, STGM_TRANSACTED | STGM_READWRITE
        | STGM_SHARE_EXCLUSIVE | STGM_CREATE| STGM_DELETEONRELEASE
        , 0, &m_pIStorage)))
        return FALSE;

    return TRUE;
    }

/*
 * CFrame::RegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CFrame::RegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //Field that are the same for all windows.
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.hInstance     = m_hInst;
    wc.cbClsExtra    = 0;

    //Register the Frame window
    wc.lpfnWndProc   = FrameWndProc;
    wc.cbWndExtra    = CBFRAMEWNDEXTRA;
    wc.hIcon         = LoadIcon(m_hInst, TEXT("Icon"));
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = MAKEINTRESOURCE(IDR_MENU);
    wc.lpszClassName = SZCLASSFRAME;

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

    //Register the do-nothing Client window
    wc.lpfnWndProc   = ClientWndProc;
    wc.cbWndExtra    = CBCLIENTWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSCLIENT;

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

    return TRUE;
    }

/*
 * CFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the frame window so derivations can
 *  process their messages and then pass the standard commands (like
 *  file open and save) on to the base class.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CFrame::OnCommand(HWND hWnd, WPARAM wParam, LPARAM lParam)
    {
    TCHAR           szFile[MAX_PATH];
    BOOL            fOK;
    PCHourglass     pHour;
    WORD            wID=LOWORD(wParam);

    switch (wID)
        {
        case IDM_FILEOPEN:
            /*
             * This will be disabled if we already have an object.
             * User must File/Close first to get back here.
             *
             * Otherwise open the File/Open dialog
             */
            szFile[0]=0;
            if (!OpenDialog(szFile, MAX_PATH))
                return 0L;

            pHour=new CHourglass;
            fOK=CreateObject(szFile);
            delete pHour;
            return 0;

        case IDM_FILECLOSE:
            Close();
            break;

        case IDM_FILEEXIT:
            PostMessage(hWnd, WM_CLOSE, 0, 0L);
            break;

        case IDM_HELPABOUT:
            DialogBox(m_hInst, MAKEINTRESOURCE(IDD_ABOUT)
                , m_hWnd, (DLGPROC)AboutProc);
            break;

        case IDM_ENTERCONTEXTHELP:
        case IDM_ESCAPECONTEXTHELP:
            //Notify the object on entry and exit.
            ContextSensitiveHelp(IDM_ENTERCONTEXTHELP==wID);
            break;

        default:
           return DefWindowProc(hWnd, WM_COMMAND, wParam, lParam);

        }

    return 0L;
    }

/*
 * CFrame::OpenDialog
 *
 * Purpose:
 *  Invokes the COMMDLG.DLL GetOpenFileName dialog and retrieves
 *  a filename for saving or opening.
 *
 * Parameters:
 *  pszFile         LPTSTR buffer to receive the entered filename.
 *  cchFile         UINT length of pszFile
 *
 * Return Value:
 *  BOOL            TRUE if the function retrieved a filename,
 *                  FALSE if the user pressed CANCEL.
 */

BOOL CFrame::OpenDialog(LPTSTR pszFile, UINT cchFile)
    {
    OPENFILENAME        ofn;
    static TCHAR        szFilter[80]=TEXT("All Files (*.*)\0*.*\0\0");
    BOOL                fRet;
   #ifdef DEBUG
    DWORD               dwErr;
   #endif

    if (NULL==pszFile)
        return FALSE;

    memset(&ofn, 0, sizeof(OPENFILENAME));
    ofn.lStructSize      =sizeof(OPENFILENAME);
    ofn.hwndOwner        =m_hWnd;

    ofn.lpstrFilter      =szFilter;
    ofn.nFilterIndex     =1L;

    ofn.lpstrTitle       =NULL;
    ofn.lpstrFile        =pszFile;
    ofn.nMaxFile         =cchFile;

    ofn.lpstrDefExt      =TEXT("*");
    ofn.Flags            =OFN_HIDEREADONLY | OFN_FILEMUSTEXIST;

    fRet=GetOpenFileName(&ofn);

   #ifdef DEBUG
    dwErr=CommDlgExtendedError();
   #endif
    return fRet;
    }

/*
 * CFrame::CreateObject
 *
 * Purpose:
 *  Creates a site and has it create an object based on a filename.
 *
 * Parameters:
 *  pszFile         LPTSTR pointing to the filename from which to
 *                  create the object.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CFrame::CreateObject(LPTSTR pszFile)
    {
    m_pSite=new CSite(++m_dwIDCounter, m_hWndClient, this);

    if (NULL==m_pSite)
        return FALSE;

    m_pSite->AddRef();  //So we can free with Release

    /*
     * Now tell the site to create an object in it using the filename
     * and the storage we opened.  The site will create a sub-storage
     * for the doc object's use.
     */
    if (!m_pSite->Create(pszFile, m_pIStorage))
        return FALSE;

    m_fHaveObject=TRUE;

    //We created the thing, now activate it with "Show"
    m_pSite->Activate(OLEIVERB_SHOW);

//Force repaint to show "have object" message
InvalidateRect(m_hWndClient, NULL, TRUE);
UpdateWindow(m_hWndClient);
    return TRUE;
    }

/*
 * CFrame::Close
 *
 * Purpose:
 *  Handles File/Close by freeing the object and resetting the
 *  application state.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CFrame::Close(void)
    {
RECTrc;

    if (NULL!=m_pSite)
        {
CSite *pSite=m_pSite;        //Prevents reentry
m_pSite=NULL;

        pSite->Close(FALSE);         //Frees the object
        pSite->Destroy(m_pIStorage); //Cleans up the storage
        pSite->Release();            //Frees the site
        }

m_fHaveObject=FALSE;
    SetRect(&m_bwIP, 0, 0, 0, 0);

GetClientRect(m_hWnd, &rc);
ResizeClientWindow(rc.left, rc.top, rc.right-rc.left
, rc.bottom-rc.top);

//Force repaint to remove "have object" message
InvalidateRect(m_hWndClient, NULL, TRUE);
UpdateWindow(m_hWndClient);

    return;
    }

/*
 * CFrame::ResizeClientWindow
 *
 * Purpose:
 *Resizes the client-area window according to current toolbar sizes
 *  and the frame window size.
 *
 * Parameters:
 *x,y,cx,cyUINT origin and dimensions of the window
 *
 * Return Value:
 *None
 */

void CFrame::ResizeClientWindow(UINT x, UINT y, UINT cx, UINT cy)
{
SetWindowPos(m_hWndClient, NULL, x, y, cx, cy
        , SWP_NOZORDER | SWP_NOACTIVATE);

    //Tell the site to tell the object.
if (NULL!=m_pSite)
    m_pSite->UpdateObjectRects();

return;
}

/*
 * CFrame::MessageLoop
 *
 * Purpose:
 *  Spins in a standard message loop (with accelerators) until
 *  WM_QUIT is found after which it returns.
 *
 * Return Value:
 *  WPARAM          Contents of msg.wParam from WM_QUIT.
 */

WPARAM CFrame::MessageLoop(void)
    {
    MSG     msg;

    while (GetMessage(&msg, NULL, 0,0 ))
        {
        //Always give the object first crack at translation.
        if (NULL!=m_pIOleIPActiveObject)
            {
            HRESULT     hr;

            hr=m_pIOleIPActiveObject->TranslateAccelerator(&msg);

            //If the object translated the accelerator, we're done
            if (NOERROR==hr)
                continue;
            }

        if (!::TranslateAccelerator(m_hWnd, m_hAccel, &msg))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    return msg.wParam;
    }

/*
 * FrameWndProc
 *
 * Purpose:
 *  Frame window class procedure that allows a derivation of these
 *  classes to hook and process any messages desired.  Otherwise this
 *  handles standard commands as well as the status line and menus.
 */

LRESULT APIENTRY FrameWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCFrame         pFR;
    RECT            rc;
    HMENU           hMenu;

    pFR=(PCFrame)GetWindowLong(hWnd, FRAMEWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pFR=(PCFrame)((LPCREATESTRUCT)lParam)->lpCreateParams;

            SetWindowLong(hWnd, FRAMEWL_STRUCTURE, (LONG)pFR);
            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_CLOSE:
            pFR->Close();
            DestroyWindow(hWnd);
            break;

        case WM_ERASEBKGND:
            //Client area window always manages painting
            return FALSE;

        case WM_SIZE:
            //Tell the in-place object about the new frame size
            GetClientRect(hWnd, &rc);

            if (NULL!=pFR->m_pIOleIPActiveObject)
                pFR->m_pIOleIPActiveObject->ResizeBorder(&rc, pFR, TRUE);

            /*
             * Resize the client, which is done in all cases since this window
 * is the parent of the DocObject, plus we need to tell the
 * DocObject of the new size through IOleDocumentView::SetRect.
 */
            rc.left  +=pFR->m_bwIP.left;
            rc.right -=pFR->m_bwIP.right;
            rc.top   +=pFR->m_bwIP.top;
            rc.bottom-=pFR->m_bwIP.bottom;

pFR->ResizeClientWindow(rc.left, rc.top, rc.right-rc.left
, rc.bottom-rc.top);

            break;

        case WM_SETFOCUS:
            if (NULL!=pFR->m_pIOleIPActiveObject)
                {
                HWND    hWndObj;

                pFR->m_pIOleIPActiveObject->GetWindow(&hWndObj);
                SetFocus(hWndObj);
                }

            return TRUE;

case WM_INITMENU:
pFR->m_fInObjectHelp=FALSE;
break;

case WM_MENUSELECT:
{
 UINT fuFlags=(UINT)HIWORD(wParam);
 UINT uItem=(UINT)LOWORD(wParam);

if (MF_POPUP & fuFlags)
{
/*
 * If we're inside our m_hMenuHelp, and uItem is
 * not zero (first item on the menu, which is ours),
 * then we must be in an object-supplied menu.
 *
 * Therefore we set our flag and forward the message
 * as well as others that occur later.  Otherwise we
 * clear the flag so we get messages again.
 */
if (NULL!=pFR->m_hMenuHelp
&& (HMENU)lParam==pFR->m_hMenuHelp)
{
pFR->m_fInObjectHelp=(0!=uItem);

if (pFR->m_fInObjectHelp)
SendMessage(pFR->m_hWndObj, iMsg, wParam, lParam);
}
}
else
{
//Forward the message on
if (pFR->m_fInObjectHelp)
{
SendMessage(pFR->m_hWndObj, iMsg, wParam, lParam);
break;
}
}
}
break;

case WM_INITMENUPOPUP:
/*
 * If we're in the object's Help menu, forward to
 * the object received in IOleInPlaceFrame::SetActiveObject
 */
if (pFR->m_fInObjectHelp && NULL!=pFR->m_hWndObj)
SendMessage(pFR->m_hWndObj, iMsg, wParam, lParam);

//Skip the system menu
            if (TRUE==(BOOL)HIWORD(lParam))
                break;

    /*
             * If we have an object, enable Close, otherwise
             * enable Open.
             */
            hMenu=(HMENU)wParam;

            if (hMenu==pFR->m_phMenu[0])
                {
                UINT uTempE=MF_BYCOMMAND | MF_ENABLED;
                UINT uTempD=MF_BYCOMMAND | MF_DISABLED | MF_GRAYED;

                EnableMenuItem(hMenu, IDM_FILEOPEN
                    , pFR->m_fHaveObject ? uTempD : uTempE);
                EnableMenuItem(hMenu, IDM_FILECLOSE
                    , pFR->m_fHaveObject ? uTempE : uTempD);
                }

            break;

        case WM_COMMAND:
if (pFR->m_fInObjectHelp)
{
SendMessage(pFR->m_hWndObj, iMsg, wParam, lParam);
break;
}
else
            return pFR->OnCommand(hWnd, wParam, lParam);

case WM_ACTIVATEAPP:
if (NULL!=pFR->m_pIOleIPActiveObject)
{
HRESULT hr;
hr=pFR->m_pIOleIPActiveObject->OnFrameWindowActivate((BOOL)wParam);
}
break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }

/*
 * ClientWndProc
 *
 * Purpose:
 *  Client window class procedure that's only used to paint a
 *  message when we have a non-DocObject open.  Otherwise this
 *  is completely hidden.
 */

LRESULT APIENTRY ClientWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCFrame         pFR;
PAINTSTRUCTps;

    pFR=(PCFrame)GetWindowLong(hWnd, CLIENTWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pFR=(PCFrame)((LPCREATESTRUCT)lParam)->lpCreateParams;

            SetWindowLong(hWnd, CLIENTWL_STRUCTURE, (LONG)pFR);
            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        case WM_PAINT:
BeginPaint(hWnd, &ps);

if (pFR->m_fHaveObject)
{
static TCHAR szMsg[]={TEXT("A non-DocObject is open or loaded. Use File/Close to destroy it.")};

SetBkColor(ps.hdc, GetSysColor(COLOR_WINDOW));
TextOut(ps.hdc, 0, 0, szMsg, lstrlen(szMsg));
}

EndPaint(hWnd, &ps);
            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }

/*
 * AboutProc
 *
 * Purpose:
 *  Dialog procedure for the omnipresent About box.
 *
 * Parameters:
 *  The standard.
 *
 * Return Value:
 *  The value to be returned through the DialogBox call that
 *  created the dialog.
 *
 */

BOOL APIENTRY AboutProc(HWND hDlg, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    switch (iMsg)
        {
        case WM_INITDIALOG:
            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam))
                {
                case IDOK:
                    EndDialog(hDlg, TRUE);
                }
            break;
        }
    return FALSE;
    }

/*
 * IUnknown implementation
 */

/*
 * CFrame::QueryInterface
 * CFrame::AddRef
 * CFrame::Release
 */

STDMETHODIMP CFrame::QueryInterface(REFIID riid, void **ppv)
    {
    /*
     * We only know IOleInPlaceFrame and its base interfaces as well
     * as a bogus IOleCommandTarget to make PowerPoint happy.
 */
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IOleInPlaceUIWindow==riid
        || IID_IOleWindow==riid || IID_IOleInPlaceFrame==riid)
        *ppv=(IOleInPlaceFrame *)this;

if (IID_IOleCommandTarget==riid)
        *ppv=(IOleCommandTarget *)this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return E_NOINTERFACE;
    }

STDMETHODIMP_(ULONG) CFrame::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CFrame::Release(void)
    {
    //Nothing special happening here--frame's life if user-controlled.
    return --m_cRef;
    }

/*
 * IOleInPlaceFrame implementation
 */

/*
 * CFrame::GetWindow
 *
 * Purpose:
 *  Retrieves the handle of the window associated with the object
 *  on which this interface is implemented.
 *
 * Parameters:
 *  phWnd           HWND * in which to store the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_FAIL if there is no
 *                  window.
 */

STDMETHODIMP CFrame::GetWindow(HWND *phWnd)
    {
    *phWnd=m_hWnd;
    return NOERROR;
    }

/*
 * CFrame::ContextSensitiveHelp
 *
 * Purpose:
 *  Instructs the object on which this interface is implemented to
 *  enter or leave a context-sensitive help mode.
 *
 * Parameters:
 *  fEnterMode      BOOL TRUE to enter the mode, FALSE otherwise.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CFrame::ContextSensitiveHelp(BOOL fEnterMode)
    {
    /*
     * Don't bother if there is no active object since we don't do
     * context help on our own.
     */
    if (NULL==m_pIOleIPActiveObject)
        return NOERROR;

    //If the state changes, notify the active object.
    if (m_fInContextHelp!=fEnterMode)
        {
        m_fInContextHelp=fEnterMode;
        m_pIOleIPActiveObject->ContextSensitiveHelp(fEnterMode);
        }

    return NOERROR;
    }

/*
 * CFrame::GetBorder
 *
 * Purpose:
 *  Returns the rectangle in which the container is willing to
 *  negotiate about an object's adornments.
 *
 * Parameters:
 *  prcBorder       LPRECT in which to store the rectangle.
 *
 * Return Value:
 *  HRESULT         NOERROR if all is well, INPLACE_E_NOTOOLSPACE
 *                  if there is no negotiable space.
 */

STDMETHODIMP CFrame::GetBorder(LPRECT prcBorder)
    {
    if (NULL==prcBorder)
        return E_INVALIDARG;

    //We return all the client area space
    GetClientRect(m_hWnd, prcBorder);
    return NOERROR;
    }

/*
 * CFrame::RequestBorderSpace
 *
 * Purpose:
 *  Asks the container if it can surrender the amount of space
 *  in pBW that the object would like for it's adornments.  The
 *  container does nothing but validate the spaces on this call.
 *
 * Parameters:
 *  pBW             LPCBORDERWIDTHS containing the requested space.
 *                  The values are the amount of space requested
 *                  from each side of the relevant window.
 *
 * Return Value:
 *  HRESULT         NOERROR if we can give up space,
 *                  INPLACE_E_NOTOOLSPACE otherwise.
 */

STDMETHODIMP CFrame::RequestBorderSpace(LPCBORDERWIDTHS pBW)
    {
    //Framer has no border space restrictions
    return NOERROR;
    }

/*
 * CFrame::SetBorderSpace
 *
 * Purpose:
 *  Called when the object now officially requests that the
 *  container surrender border space it previously allowed
 *  in RequestBorderSpace.  The container should resize windows
 *  appropriately to surrender this space.
 *
 * Parameters:
 *  pBW             LPCBORDERWIDTHS containing the amount of space
 *                  from each side of the relevant window that the
 *                  object is now reserving.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CFrame::SetBorderSpace(LPCBORDERWIDTHS pBW)
    {
    RECT            rc;

    /*
     * Since we have no tools, we can accept anything the object sends
     * and must therefore adjust the client-area window accordingly.
     */

    /*
     * If pBW is NULL, the object is not interested in tools, so we
     * don't have to do anything.  In either case we need to save
     * the toolspace allocations in order to resize the client window
     * correctly.
     */
    if (NULL==pBW)
        {
        if (!m_fOurMenuShowing)
            SetMenu(NULL, NULL, NULL);

        SetRect(&m_bwIP, 0, 0, 0, 0);
        GetClientRect( m_hWnd, &rc );
        }
    else
        {
        GetClientRect(m_hWnd, &rc);
        rc.left  +=pBW->left;
        rc.right -=pBW->right;
        rc.top   +=pBW->top;
        rc.bottom-=pBW->bottom;

        m_bwIP=*pBW;
        }

ResizeClientWindow(rc.left, rc.top, rc.right-rc.left
, rc.bottom-rc.top);

    return NOERROR;
    }

/*
 * CFrame::SetActiveObject
 *
 * Purpose:
 *  Provides the container with the object's IOleInPlaceActiveObject
 *  pointer
 *
 * Parameters:
 *  pIIPActiveObj   LPOLEINPLACEACTIVEOBJECT of interest.
 *  pszObj          LPCOLESTR naming the object.  Not used.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CFrame::SetActiveObject
    (LPOLEINPLACEACTIVEOBJECT pIIPActiveObj, LPCOLESTR pszObj)
    {
    if (NULL!=m_pIOleIPActiveObject)
        m_pIOleIPActiveObject->Release();

    //NULLs m_pIOleIPActiveObject if pIIPActiveObj is NULL
    m_pIOleIPActiveObject=pIIPActiveObj;

    if (NULL!=m_pIOleIPActiveObject)
        m_pIOleIPActiveObject->AddRef();

m_pIOleIPActiveObject->GetWindow(&m_hWndObj);
    return NOERROR;
    }

/*
 * CFrame::InsertMenus
 *
 * Purpose:
 *  Instructs the container to place its in-place menu items where
 *  necessary in the given menu and to fill in elements 0, 2, and 4
 *  of the OLEMENUGROUPWIDTHS array to indicate how many top-level
 *  items are in each group.
 *
 * Parameters:
 *  hMenu           HMENU in which to add popups.
 *  pMGW            LPOLEMENUGROUPWIDTHS in which to store the
 *                  width of each container menu group.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CFrame::InsertMenus(HMENU hMenu
    , LPOLEMENUGROUPWIDTHS pMGW)
    {
//Copy our File menu into the shared menu.
    InsertMenu(hMenu, 0, MF_BYPOSITION | MF_POPUP, (UINT)m_phMenu[0]
        , TEXT("&File"));

    pMGW->width[0]=1;
    pMGW->width[2]=0;
    pMGW->width[4]=0;

/*
 * Add the special help menu which is the first item in
 * the m_hMenuHelp popup that's sitting around.
 */
InsertMenu(hMenu, 1, MF_BYPOSITION | MF_POPUP
    , (UINT)m_hMenuHelp, TEXT("&Help"));

//Tell the object we added our Help menu
pMGW->width[5]=1;
    return NOERROR;
    }

/*
 * CFrame::SetMenu
 *
 * Purpose:
 *  Instructs the container to replace whatever menu it's currently
 *  using with the given menu and to call OleSetMenuDescritor so OLE
 *  knows to whom to dispatch messages.
 *
 * Parameters:
 *  hMenu           HMENU to show.
 *  hOLEMenu        HOLEMENU to the menu descriptor.
 *  hWndObj         HWND of the active object to which messages are
 *                  dispatched.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CFrame::SetMenu(HMENU hMenu, HOLEMENU hOLEMenu
    , HWND hWndObj)
    {
    HRESULT         hr;

    /*
     * Our responsibilities here are to put the menu on the frame
     * window and call OleSetMenuDescriptor.
     */

    if (NULL==hMenu)
        {
        //Prevent redundant calls, or debug warnings on startup.
        if (NULL==m_hMenuTop)
            return NOERROR;

        hMenu=m_hMenuTop;
        m_hMenuTop=NULL;
        m_fOurMenuShowing=TRUE;
        }
    else
        {
        m_hMenuTop=m_hMenuOrg;
        m_fOurMenuShowing=FALSE;

/*
 * Check if our Help menu has anything added to it.  If so, then
 * remember to forward menu messages.  If not, remove the Help
 * menu altogether (destroying it after removing our normal Help
 * popup, as we also do in RemoveMenus.
 */
if (CHELPITEMS!=GetMenuItemCount(m_hMenuHelp))
m_fUsingOurHelp=TRUE;
else
{
UINTi, cItems;

cItems=GetMenuItemCount(hMenu);

//Find m_hMenuHelp in the menu and remove it.

for (i=0; i < cItems; i++)
{
if (GetSubMenu(hMenu, i)==m_hMenuHelp)
{
RemoveMenu(hMenu, i, MF_BYPOSITION);
break;
}
}
}
        }

    if (NULL!=hMenu)
        ::SetMenu(m_hWnd, hMenu);

    DrawMenuBar(m_hWnd);

    hr=OleSetMenuDescriptor(hOLEMenu, m_hWnd, hWndObj, NULL, NULL);
    return hr;
    }

/*
 * CFrame::RemoveMenus
 *
 * Purpose:
 *  Asks the container to remove any menus it put into hMenu in
 *  InsertMenus.
 *
 * Parameters:
 *  hMenu           HMENU from which to remove the container's
 *                  items.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CFrame::RemoveMenus(HMENU hMenu)
    {
    int         cItems, i, j;
    HMENU       hMenuT;

/*
     * To be defensive, loop through this menu removing anything
     * we recognize (that is, anything in m_phMenu) just in case
     * the server didn't clean it up right.  At least we can
     * give ourselves the prophylactic benefit.
     */

    if (NULL==hMenu)
        return NOERROR;

    cItems=GetMenuItemCount(hMenu);

    /*
     * Walk backwards down the menu.  For each popup, see if it
     * matches any other popup we know about, and if so, remove
     * it from the shared menu.
     */
    for (i=cItems; i >=0; i--)
        {
        hMenuT=GetSubMenu(hMenu, i);

        for (j=0; j <= CMENUS; j++)
            {
//Remove any owned popup, or our special help menu
            if (hMenuT==m_phMenu[j]
                || (hMenuT==m_hMenuHelp && m_hMenuHelp!=NULL))
                RemoveMenu(hMenu, i, MF_BYPOSITION);
            }
        }

m_fUsingOurHelp=FALSE;

    //The menu should now be empty.
    return NOERROR;
    }

/*
 * CFrame::SetStatusText
 *
 * Purpose:
 *  Asks the container to place some text in a status line, if one
 *  exists.  If the container does not have a status line it
 *  should return E_FAIL here in which case the object could
 *  display its own.
 *
 * Parameters:
 *  pszText         LPCOLESTR to display.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_TRUNCATED if not all
 *                  of the text could be displayed, or E_FAIL if
 *                  the container has no status line.
 */

STDMETHODIMP CFrame::SetStatusText(LPCOLESTR pszText)
    {
    //We have no status line...
    return E_NOTIMPL;
    }

/*
 * CFrame::EnableModeless
 *
 * Purpose:
 *  Instructs the container to show or hide any modeless popup
 *  windows that it may be using.
 *
 * Parameters:
 *  fEnable         BOOL indicating to enable/show the windows
 *                  (TRUE) or to hide them (FALSE).
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CFrame::EnableModeless(BOOL fEnable)
    {
    return NOERROR;
    }

/*
 * CFrame::TranslateAccelerator
 *
 * Purpose:
 *  When dealing with an in-place object from an EXE server, this
 *  is called to give the container a chance to process accelerators
 *  after the server has looked at the message.
 *
 * Parameters:
 *  pMSG            LPMSG for the container to examine.
 *  wID             WORD the identifier in the container's
 *                  accelerator table (from IOleInPlaceSite
 *                  ::GetWindowContext) for this message (OLE does
 *                  some translation before calling).
 *
 * Return Value:
 *  HRESULT         NOERROR if the keystroke was used,
 *                  S_FALSE otherwise.
 */

STDMETHODIMP CFrame::TranslateAccelerator(LPMSG pMSG, WORD wID)
    {
    /*
     * wID already has anything translated from m_hAccel for us,
     * so we can just check for the commands we want and process
     * them instead of calling TranslateAccelerator which would be
     * redundant and which also has a possibility of dispatching to
     * the wrong window.
     */
    if (IDM_ENTERCONTEXTHELP==wID || IDM_ESCAPECONTEXTHELP==wID)
        {
        //wID properly expands to 32-bits
        OnCommand(m_hWnd, (WPARAM)wID, 0L);
        return NOERROR;
        }

    return S_FALSE;
    }

/*
 * IOleCommandTarget methods, provided to make PowerPoint happy
 * with this frame.
 */

STDMETHODIMP CFrame::QueryStatus(const GUID *pguidCmdGroup, ULONG cCmds
    , OLECMD *prgCmds, OLECMDTEXT *pCmdText)
{
return OLECMDERR_E_UNKNOWNGROUP;
}

STDMETHODIMP CFrame::Exec(const GUID *pguidCmdGroup, DWORD nCmdID
    , DWORD nCmdexecopt, VARIANTARG *pvaIn, VARIANTARG *pvaOut)
{
return OLECMDERR_E_UNKNOWNGROUP;
}


RESOURCE.H

/*
 * RESOURCE.H
 * Document Objects Framer
 *
 * Definitions specifically pertaining to resources for the application.
 *
 * Copyright (c)1995-1997 Microsoft Corporation, All Rights Reserved
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Number of menus.
#define CMENUS                              2

//Resource identifiers.
#define IDR_MENU                            1
#define IDR_MENUHELP                        2
#define IDR_ACCELERATORS                    1
#define IDD_ABOUT                           1

//Number of items on IDR_MENUHELP
#define CHELPITEMS                          1

//Menu command identifiers.
#define IDM_FILEOPEN                        101
#define IDM_FILECLOSE                       102
#define IDM_FILEEXIT                        105

#define IDM_HELPABOUT                       200
#define IDM_CUSTOMHELPMIN                   201

//Enters context help in in-place mode
#define IDM_ENTERCONTEXTHELP                300
#define IDM_ESCAPECONTEXTHELP               301

#define CACCELERATORS                       2

#endif //_RESOURCE_H_


SITE.CPP

/*
 * SITE.CPP
 * Document Object Site Object
 *
 * Copyright (c)1995-1997 Microsoft Corporation, All Rights Reserved
 */

#include "framer.h"

/*
 * CSite::CSite
 * CSite::~CSite
 *
 * Constructor Parameters:
 *  dwID            DWORD identifer for this site.
 *  hWnd            HWND of the window associated with the site
 *  pFR             PCFrame to the parent structure.
 */

CSite::CSite(DWORD dwID, HWND hWnd, PCFrame pFR)
    {
    m_cRef=0;
    m_dwID=dwID;
    m_hWnd=hWnd;
    m_pFR=pFR;

    m_fInitialized=0;
    m_pIStorage=NULL;

    m_pObj=NULL;

    m_pIOleObject=NULL;
    m_pIOleIPObject=NULL;
    m_pIOleDocView=NULL;

    m_pImpIOleClientSite=NULL;
    m_pImpIAdviseSink=NULL;
    m_pImpIOleIPSite=NULL;
    m_pImpIOleDocumentSite=NULL;

    m_fDocObj=FALSE;
    return;
    }

CSite::~CSite(void)
    {
    //Object pointers cleaned up in Close.

    //We delete our own interfaces since we control them
    DeleteInterfaceImp(m_pImpIOleDocumentSite);
    DeleteInterfaceImp(m_pImpIOleIPSite);
    DeleteInterfaceImp(m_pImpIAdviseSink);
    DeleteInterfaceImp(m_pImpIOleClientSite);
    return;
    }

/*
 * CSite::QueryInterface
 * CSite::AddRef
 * CSite::Release
 *
 * Purpose:
 *  IUnknown members for CSite object.
 */

STDMETHODIMP CSite::QueryInterface(REFIID riid, void **ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IOleClientSite==riid)
        *ppv=m_pImpIOleClientSite;

    if (IID_IAdviseSink==riid)
        *ppv=m_pImpIAdviseSink;

    if (IID_IOleWindow==riid || IID_IOleInPlaceSite==riid)
        *ppv=m_pImpIOleIPSite;

    if (IID_IOleDocumentSite==riid)
        *ppv=m_pImpIOleDocumentSite;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CSite::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CSite::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CSite::Create
 *
 * Purpose:
 *  Asks the site to instantiate an object given a filename.
 *  This goes through OleCreateFromFile and will either create
 *  an embedded object or a package (embedded) object.  When
 *  activated, this will either launch the app and activate
 *  as a DocObject, launch the app and open the file, or fail
 *  because no app can open the file.
 *
 * Parameters:
 *  pszFile         LPTSTR of the file from which to create the object.
 *  pIStorage       IStorage * of the parent storage in which we're
 *                  to create an IStorage for the new object.
 *  dwID            DWORD identifier for this site.
 *
 * Return Value:
 *  BOOL            Result of the creation.
 */

BOOL CSite::Create(LPTSTR pszFile, IStorage *pIStorage)
    {
    HRESULT             hr=E_FAIL;
    LPUNKNOWN           pObj;

    //Create a new storage for this object (sets m_pIStorage)
    if (!CreateStorage(pIStorage))
        return FALSE;

    //Now create an object from the file
   #ifdef UNICODE
    hr=OleCreateFromFile(CLSID_NULL, pszFile, IID_IUnknown
        , OLERENDER_NONE, NULL, NULL, m_pIStorage, (void **)&pObj);
   #else
    OLECHAR     szFile[512];    //Assumption on string length

    MultiByteToWideChar(CP_ACP, 0, pszFile, -1, szFile, 512);
hr=OleCreateFromFile(CLSID_NULL, szFile, IID_IUnknown
        , OLERENDER_NONE, NULL, NULL, m_pIStorage, (void **)&pObj);
   #endif

    //If creation didn't work, get rid of the element Open created.
    if (FAILED(hr))
        {
        Destroy(pIStorage);
        return FALSE;
        }

    //We don't get the size if PatronObject data was seen already.
    if (!ObjectInitialize(pObj))
        {
        Destroy(pIStorage);
        return FALSE;
        }

    m_fInitialized=TRUE;
    return TRUE;
    }

/*
 * CSite::ObjectInitialize
 * (Protected)
 *
 * Purpose:
 *  Performs operations necessary after creating an object or
 *  reloading one from storage.
 *
 * Parameters:
 *  pObj            LPUNKNOWN of the object in this tenant.
 *  pFE             LPFORMATETC describing the graphic here.
 *  dwData          DWORD extra data.  If pFE->dwAspect==
 *                  DVASPECT_ICON then this is the iconic metafile.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CSite::ObjectInitialize(LPUNKNOWN pObj)
    {
    HRESULT         hr;
    DWORD           dw;

    if (NULL==pObj)
        return FALSE;

    m_pObj=pObj;

    //We need an IOleObject most of the time, so get one here.
    m_pIOleObject=NULL;
    hr=pObj->QueryInterface(IID_IOleObject, (void **)&m_pIOleObject);

    /*
     * Follow up object creation with advises and so forth.  If
     * we cannot get IOleObject here, then we know we can't do
     * any IOleObject actions from here on--object is static.
     */
    if (FAILED(hr))
        return TRUE;

    //SetClientSite is critical for DocObjects
    m_pIOleObject->SetClientSite(m_pImpIOleClientSite);
    m_pIOleObject->Advise(m_pImpIAdviseSink, &dw);

//This is to give PowerPoint a chance to initialize itself earlier
OleRun(m_pIOleObject);
    return TRUE;
    }

/*
 * CSite::CreateStorage
 *
 * Purpose:
 *  Creates an sub-storage within a given parent storage,
 *  setting m_pIStorage.
 *
 * Parameters:
 *  pIStorage       IStorage * of the parent storage
 *
 * Return Value:
 *  BOOL            TRUE if creation succeeds, FALSE otherwise.
 */

BOOL CSite::CreateStorage(IStorage *pIStorage)
    {
    HRESULT     hr;
    DWORD       dwMode=STGM_TRANSACTED | STGM_READWRITE
                    | STGM_SHARE_EXCLUSIVE;
    TCHAR       szName[32];

    if (NULL==pIStorage)
        return FALSE;

    /*
     * Attempt to open the storage under this ID.  If there is
     * none, then create it.  In either case we end up with an
     * IStorage that we either save in pPage or release.
     */
    wsprintf(szName, TEXT("Site %lu"), m_dwID);

   #ifdef UNICODE
    hr=pIStorage->CreateStorage(szName, dwMode, 0, 0, &m_pIStorage);
   #else
    OLECHAR  szwName[32];
MultiByteToWideChar(CP_ACP, 0, szName, -1, szwName, 32);
    hr=pIStorage->CreateStorage(szwName, dwMode, 0, 0, &m_pIStorage);
   #endif

    if (FAILED(hr))
        return FALSE;

//Create stream for the object; name is irrelevant
dwMode=STGM_DIRECT | STGM_READWRITE | STGM_SHARE_EXCLUSIVE;
   #ifdef UNICODE
    hr=m_pIStorage->CreateStream(TEXT("\003DocObjInfo"), dwMode, 0, 0
        , &m_pIStream);
   #else
MultiByteToWideChar(CP_ACP, 0, "\003DocObjInfo", -1, szwName, 32);
    hr=m_pIStorage->CreateStream(szwName, dwMode, 0, 0, &m_pIStream);
   #endif

    //If we failed to create a stream in the file, do it in memory
if (FAILED(hr))
{
if (FAILED(CreateStreamOnHGlobal(NULL, TRUE, &m_pIStream)))
    return FALSE;
}

    //Create interface implementations
    m_pImpIOleClientSite=new CImpIOleClientSite(this, this);
    m_pImpIAdviseSink=new CImpIAdviseSink(this, this);
    m_pImpIOleIPSite=new CImpIOleInPlaceSite(this, this);
    m_pImpIOleDocumentSite=new CImpIOleDocumentSite(this, this);

    if (NULL==m_pImpIOleClientSite || NULL==m_pImpIAdviseSink
        || NULL==m_pImpIOleIPSite || NULL==m_pImpIOleDocumentSite)
        return FALSE;

    return TRUE;
    }

/*
 * CSite::Close
 *
 * Purpose:
 *  Possibly commits the storage, then releases it, afterwards
 *  frees alls the object pointers.
 *
 * Parameters:
 *  fCommit         BOOL indicating if we're to commit.
 *
 * Return Value:
 *  None
 */

void CSite::Close(BOOL fCommit)
    {
    //OnInPlaceDeactivate releases this pointer.
    if (NULL!=m_pIOleIPObject)
        m_pIOleIPObject->InPlaceDeactivate();

    ReleaseInterface(m_pIOleDocView);

    if (NULL!=m_pIOleObject)
        {
        m_pIOleObject->Close(fCommit
            ? OLECLOSE_SAVEIFDIRTY : OLECLOSE_NOSAVE);
        ReleaseInterface(m_pIOleObject);
        }

    ReleaseInterface(m_pObj);
ReleaseInterface(m_pIStream);
    ReleaseInterface(m_pIStorage);

    return;
    }

/*
 * CSite::Update
 *
 * Purpose:
 *  Forces a commit on the object's storage
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CSite::Update(void)
    {
    LPPERSISTSTORAGE    pIPS;

    if (NULL!=m_pIStorage)
        return;

    m_pObj->QueryInterface(IID_IPersistStorage, (void **)&pIPS);
    OleSave(pIPS, m_pIStorage, TRUE);
    pIPS->SaveCompleted(NULL);
    pIPS->Release();

    m_pIStorage->Commit(STGC_DEFAULT);
    return;
    }

/*
 * CSite::Destroy
 *
 * Purpose:
 *  Removes this storage from the parent storage.  The caller should
 *  eventually delete this CSite object to free the object herein.
 *  Nothing is committed when being destroyed.
 *
 * Parameters:
 *  pIStorage       IStorage * of the parent
 *
 * Return Value:
 *  None
 */

void CSite::Destroy(IStorage *pIStorage)
    {
    TCHAR     szName[32];

    if (NULL==pIStorage)
        return;

    if (NULL!=m_pObj)
        Close(FALSE);

wsprintf(szName, TEXT("Site %lu"), m_dwID);
   #ifdef UNICODE
    pIStorage->DestroyElement(szName);
   #else
    OLECHAR  szwName[32];
MultiByteToWideChar(CP_ACP, 0, szName, -1, szwName, 512);
pIStorage->DestroyElement(szwName);
   #endif
    return;
    }

/*
 * CSite::Activate
 *
 * Purpose:
 *  Activates a verb on the object living in the site.
 *
 * Parameters:
 *  iVerb           LONG of the verb to execute.
 *
 * Return Value:
 *  None
 */

void CSite::Activate(LONG iVerb)
    {
    CHourglass *pHour;
    RECT        rc;

    pHour=new CHourglass;

    GetClientRect(m_hWnd, &rc);
    m_pIOleObject->DoVerb(iVerb, NULL, m_pImpIOleClientSite, 0
        , m_hWnd, &rc);

    delete pHour;
    return;
    }

/*
 * CSite::UpdateObjectRects
 *
 * Purpose:
 *  Informs the site that the client area window was resized and
 *  that the site needs to also tell the DocObject of the resize.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CSite::UpdateObjectRects(void)
    {
    RECT    rc;

    if (NULL==m_pIOleDocView)
        return;

    GetClientRect(m_hWnd, &rc);
    m_pIOleDocView->SetRect(&rc);
    return;
    }


SITE.H

/*
 * SITE.H
 * View Site for Document Objects.
 *
 * Copyright (c)1995-1997 Microsoft Corporation, All Rights Reserved
 */

#ifndef _SITE_H_
#define _SITE_H_

class CImpIOleClientSite : public IOleClientSite
    {
    protected:
        ULONG               m_cRef;
        class CSite        *m_pSite;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIOleClientSite(class CSite *, IUnknown *);
        ~CImpIOleClientSite(void);

        STDMETHODIMP QueryInterface(REFIID, void **);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP SaveObject(void);
        STDMETHODIMP GetMoniker(DWORD, DWORD, LPMONIKER *);
        STDMETHODIMP GetContainer(LPOLECONTAINER *);
        STDMETHODIMP ShowObject(void);
        STDMETHODIMP OnShowWindow(BOOL);
        STDMETHODIMP RequestNewObjectLayout(void);
    };

typedef CImpIOleClientSite *PCImpIOleClientSite;

class CImpIAdviseSink : public IAdviseSink
    {
    protected:
        ULONG               m_cRef;
        class CSite        *m_pSite;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIAdviseSink(class CSite *, IUnknown *);
        ~CImpIAdviseSink(void);

        STDMETHODIMP QueryInterface(REFIID, void **);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(void)  OnDataChange(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP_(void)  OnViewChange(DWORD, LONG);
        STDMETHODIMP_(void)  OnRename(LPMONIKER);
        STDMETHODIMP_(void)  OnSave(void);
        STDMETHODIMP_(void)  OnClose(void);
    };

typedef CImpIAdviseSink *PCImpIAdviseSink;

class CImpIOleInPlaceSite : public IOleInPlaceSite
    {
    protected:
        ULONG               m_cRef;
        class CSite        *m_pSite;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIOleInPlaceSite(class CSite *, IUnknown *);
        ~CImpIOleInPlaceSite(void);

        STDMETHODIMP QueryInterface(REFIID, void **);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetWindow(HWND *);
        STDMETHODIMP ContextSensitiveHelp(BOOL);
        STDMETHODIMP CanInPlaceActivate(void);
        STDMETHODIMP OnInPlaceActivate(void);
        STDMETHODIMP OnUIActivate(void);
        STDMETHODIMP GetWindowContext(LPOLEINPLACEFRAME *
                        , LPOLEINPLACEUIWINDOW *, LPRECT, LPRECT
                        , LPOLEINPLACEFRAMEINFO);
        STDMETHODIMP Scroll(SIZE);
        STDMETHODIMP OnUIDeactivate(BOOL);
        STDMETHODIMP OnInPlaceDeactivate(void);
        STDMETHODIMP DiscardUndoState(void);
        STDMETHODIMP DeactivateAndUndo(void);
        STDMETHODIMP OnPosRectChange(LPCRECT);
    };

typedef CImpIOleInPlaceSite *PCImpIOleInPlaceSite;

class CImpIOleDocumentSite : public IOleDocumentSite
    {
    protected:
        ULONG               m_cRef;
        class CSite        *m_pSite;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIOleDocumentSite(class CSite *, IUnknown *);
        ~CImpIOleDocumentSite(void);

        STDMETHODIMP QueryInterface(REFIID, void **);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP ActivateMe(IOleDocumentView *);
    };

typedef CImpIOleDocumentSite *PCImpIOleDocumentSite;

/*
 * The CSite class, a COM object with the interfaces IOleClientSite,
 * IAdviseSink, IOleInPlaceSite, and IOleDocumentSite.
 */

class CSite : public IUnknown
    {
    friend CImpIOleClientSite;
    friend CImpIAdviseSink;
    friend CImpIOleInPlaceSite;
    friend CImpIOleDocumentSite;

    private:
        ULONG           m_cRef;
        DWORD           m_dwID;
        HWND            m_hWnd; //Client area window of parent

        class CFrame   *m_pFR;

        BOOL            m_fInitialized; //Something here?
        LPSTORAGE       m_pIStorage;    //Storage for object
LPSTREAMm_pIStream;//Info stream for object

        //Object interfaces
        IUnknown          *m_pObj;
        IOleObject        *m_pIOleObject;
        IOleInPlaceObject *m_pIOleIPObject;
        IOleDocumentView  *m_pIOleDocView;

        BOOL               m_fDocObj;   //Document object here?

        //Our interfaces
        PCImpIOleClientSite   m_pImpIOleClientSite;
        PCImpIAdviseSink      m_pImpIAdviseSink;
        PCImpIOleInPlaceSite  m_pImpIOleIPSite;
        PCImpIOleDocumentSite m_pImpIOleDocumentSite;

    protected:
        BOOL    ObjectInitialize(LPUNKNOWN);

    public:
        CSite(DWORD, HWND, CFrame *);
        ~CSite(void);

        //Gotta have an IUnknown for delegation
        STDMETHODIMP QueryInterface(REFIID, void **);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        BOOL        Create(LPTSTR, IStorage *);
        BOOL        CreateStorage(IStorage *);
        void        Close(BOOL);
        void        Update(void);
        void        Destroy(IStorage *);
        void        Activate(LONG);
        void        UpdateObjectRects(void);
    };

typedef CSite *PCSite;

#endif //_SITE_H_


README.TXT

This sample is intended primarily to demonstrate http read range but can also
be used for simple GET requests without ranges.  Application writers may be
interested in plugging into the interface defined in range.h, one C call to
initiate the request and one C callback to receive status and data.

Other interesting aspects include...

Urlmon
* use of urlmon from a console app (not required)
* correct use of IStream::Read return values
* implementation of IHttpNegotiate to add request headers
* use of IWininetHttpInfo to parse response headers

Wininet
* passing INTERNET_FLAG_OFFLINE to InternetOpen
* calling InternetSetFilePointer in offline mode
* calling InternetTimeFromSystemTime

Files included in this sample...
    range.h         HttpReadRequest constants, structures, and prototypes.
    range.c         test stub (omit when linking app with remaining files)
    cache.cpp       HttpReadRequest entry point and wininet cache handling.
    download.cpp    HttpReadFromServer entry point and urlmon downloading.
    makefile        makefile for use with inetsdk.mak (note console=1)

Limitations
* Microsoft-IIS 2.0b2 may omit some boundaries between multiple ranges.
* Netscape-FastTrack 2.0b3 claims to accept ranges but never returns them.


CALLBACK.CPP

// ===========================================================================
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright 1996 Microsoft Corporation.  All Rights Reserved.
// ===========================================================================
#include <urlmon.h>
#include <wininet.h>
#include "callback.hpp"

#define BOUNDARY_MAXSIZE 500
#define RECV_BUF_SIZE   8192

static char szCRLF[] = "\r\n";
static char szLF[]   = "\n";

// ---------------------------------------------------------------------------
// CUrlmonCallback::CUrlmonCallback
// ---------------------------------------------------------------------------
CUrlmonCallback::CUrlmonCallback (PHTTP_REQUEST_PARAM pParam)
{
    m_pBinding = NULL;
    m_pstm = NULL;
    m_cRef = 0;

    m_pParam = pParam;

    if (m_pParam->punkOuter)
        ((IUnknown *) m_pParam->punkOuter)->AddRef();

    m_CBParam.cbStruct = sizeof(m_CBParam);
    m_CBParam.dwRequestCtx = m_pParam->dwRequestCtx;

    m_dwOffset = 0;
    m_dwResponseCode = 0;
    m_pszRangeDelimiter = NULL;
}

// ---------------------------------------------------------------------------
// CUrlmonCallback::~CUrlmonCallback
// ---------------------------------------------------------------------------
CUrlmonCallback::~CUrlmonCallback()
{
    if (m_pParam->punkOuter)
        ((IUnknown *) m_pParam->punkOuter)->Release();
    if (m_pstm)
        m_pstm->Release();
    if (m_pszRangeDelimiter)
        LocalFree ((HLOCAL) m_pszRangeDelimiter);
}

// ---------------------------------------------------------------------------
// CUrlmonCallback::QueryInterface
// ---------------------------------------------------------------------------
STDMETHODIMP CUrlmonCallback::QueryInterface(REFIID riid, void** ppv)
{
    if(IsEqualGUID(riid,IID_IUnknown))
        *ppv = (IUnknown *) (IBindStatusCallback *) this;
    else if (IsEqualGUID(riid,IID_IBindStatusCallback))
        *ppv = (IBindStatusCallback *) this;
    else if (IsEqualGUID(riid, IID_IHttpNegotiate))
        *ppv = (IHttpNegotiate *) this;
    else
        *ppv = NULL;

    if (!*ppv)
        return E_NOINTERFACE;

    // Increment our reference count before we hand out our interface
    ((LPUNKNOWN)*ppv)->AddRef();
    return S_OK;

}

// ---------------------------------------------------------------------------
// CUrlmonCallback::AddRef
// ---------------------------------------------------------------------------
STDMETHODIMP_(ULONG) CUrlmonCallback::AddRef(void)
{
    if (m_pParam->punkOuter)
        ((IUnknown *) m_pParam->punkOuter)->AddRef();
    return m_cRef++;
}

// ---------------------------------------------------------------------------
// CUrlmonCallback::Release
// ---------------------------------------------------------------------------
STDMETHODIMP_(ULONG) CUrlmonCallback::Release(void)
{
    if (m_pParam->punkOuter)
        ((IUnknown *) m_pParam->punkOuter)->Release();

    if (--m_cRef == 0)
    {
        delete this;
        return 0;
    }
    return m_cRef;
}

// ---------------------------------------------------------------------------
// CUrlmonCallback::BeginningTransaction
// ---------------------------------------------------------------------------
STDMETHODIMP
CUrlmonCallback::BeginningTransaction (LPCWSTR szURL,
    LPCWSTR szHeaders, DWORD dwReserved, LPWSTR *ppszAdditionalHeaders)
{
    static char szRangeHeader[] = "Range: bytes=";
    static char szUnlessHeader[] = "Unless-Modified-Since: ";

    HRESULT hr = S_OK;
    PSTR pszHeader = NULL;

    // Don't add any headers if not a range request.
    DWORD cRanges = m_pParam->cRanges;
    if (!cRanges)
    {
        *ppszAdditionalHeaders = NULL;
        goto done;
    }

    PHTTP_REQUEST_RANGE pRanges;
    pRanges = m_pParam->pRanges;

    // Allocate ANSI buffer.
    DWORD cbHeader;
    cbHeader = sizeof(szRangeHeader) + 20 * cRanges + 2;
    if (m_pParam->pstUnlessModifiedSince)
       cbHeader += sizeof(szUnlessHeader) + INTERNET_RFC1123_BUFSIZE + 2;
    pszHeader = (PSTR) LocalAlloc (LMEM_FIXED, cbHeader);
    if (!pszHeader)
    {
        hr = E_OUTOFMEMORY;
        goto done;
    }

    // Format the read range request header.
    UINT cchHeader;
    cchHeader = wsprintf (pszHeader, "%s", szRangeHeader);

    // Add the ranges.
    while (cRanges--)
    {
        if (pRanges->dwSize)
        {
            // Format range, end value is inclusive.
            cchHeader += wsprintf (pszHeader + cchHeader, "%d-%d",
                pRanges->dwOffset, pRanges->dwOffset + pRanges->dwSize - 1);
        }
        else
        {
            // Format range to end of file.
            cchHeader += wsprintf (pszHeader + cchHeader, "%d-",
                pRanges->dwOffset);
        }

        pRanges++;
        if (cRanges)
           cchHeader += wsprintf (pszHeader + cchHeader, ", ");
        else
           cchHeader += wsprintf (pszHeader + cchHeader, szCRLF);
    }

    if (m_pParam->pstUnlessModifiedSince)
    {
        // Add unless-modified-since qualifier.
        cchHeader += wsprintf (pszHeader + cchHeader, szUnlessHeader);
        if (!InternetTimeFromSystemTime
        (
            m_pParam->pstUnlessModifiedSince,
            INTERNET_RFC1123_FORMAT,
            pszHeader + cchHeader,
            INTERNET_RFC1123_BUFSIZE
        ))
        {
            hr = E_FAIL;
            goto done;
        }

        cchHeader += lstrlen (pszHeader + cchHeader);
        cchHeader += wsprintf (pszHeader + cchHeader, szCRLF);
    }

    cchHeader++; // for NULL termination

    // Allocate Unicode buffer.
    *ppszAdditionalHeaders = (WCHAR *) CoTaskMemAlloc (sizeof(WCHAR) * cchHeader);
    if (*ppszAdditionalHeaders)
    {
        MultiByteToWideChar (CP_ACP, 0, pszHeader, -1, *ppszAdditionalHeaders,
            sizeof(WCHAR) * cchHeader);
    }

    hr = *ppszAdditionalHeaders? S_OK : E_OUTOFMEMORY;

done:
    if (pszHeader)
        LocalFree (pszHeader);
    return hr;
}

// ---------------------------------------------------------------------------
// CUrlmonCallback::OnResponse
// ---------------------------------------------------------------------------
STDMETHODIMP CUrlmonCallback::OnResponse
(
    DWORD   dwResponseCode,
    LPCWSTR szResponseHeaders,
    LPCWSTR szRequestHeaders,
    LPWSTR  *pszAdditionalRequestHeaders
)
{
    // Get the HttpQueryInfo wrapper object.
    IWinInetHttpInfo *pHttpInfo = NULL;
    HRESULT hr = m_pBinding->QueryInterface
        (IID_IWinInetHttpInfo, (void **) &pHttpInfo);
    if (FAILED(hr))
        goto done;

    // Save the response code.
    m_dwResponseCode = dwResponseCode;
    m_CBParam.dwResponseCode = dwResponseCode;

    DWORD cbBuf;

    // Check for partial response.
    if (dwResponseCode == 206)
    {
       // Server responded with byte ranges, ergo must support them.
       m_CBParam.fdwRequestFlags = HTTP_REQUEST_ACCEPT_RANGES;

       // Look for multi-part delimiter embedded in content type.
       const static char szMultiPart[] = "multipart/x-byteranges; boundary";
       const static DWORD cbMultiPart = sizeof(szMultiPart);

       // Get length of buffer to hold content type.
       DWORD cbContentType = 0;
       pHttpInfo->QueryInfo
          (HTTP_QUERY_CONTENT_TYPE, NULL, &cbContentType, NULL, 0);

       if (cbContentType > cbMultiPart + 1)
       {
            // Content type is big enough to embed a delimiter.
            m_pszRangeDelimiter = (PSTR) LocalAlloc (LMEM_FIXED, cbContentType);
            if (!m_pszRangeDelimiter)
            {
                hr = E_OUTOFMEMORY;
                goto done;
            }

            if (S_OK != pHttpInfo->QueryInfo (HTTP_QUERY_CONTENT_TYPE,
                 m_pszRangeDelimiter, &cbContentType, NULL, 0))
            {
                hr = E_FAIL;
                goto done;
            }

            // Split the string at the '='
            m_pszRangeDelimiter[cbMultiPart - 1] = 0;
            if (lstrcmpi (m_pszRangeDelimiter, szMultiPart))
            {
                // Response must not be multi-part.
                LocalFree ((HLOCAL) m_pszRangeDelimiter);
                m_pszRangeDelimiter = NULL;
            }
            else
            {
                m_cchRangeDelimiter =
                    lstrlen (m_pszRangeDelimiter + cbMultiPart);

                // Shift the delimiter to offset 2 of string.
                MoveMemory
                (
                  m_pszRangeDelimiter + 2, // +2 for prefix
                  m_pszRangeDelimiter + cbMultiPart,
                  m_cchRangeDelimiter + 1  // +1 for null
                );

                // Prefix delimiter with "--"
                m_pszRangeDelimiter[0] = '-';
                m_pszRangeDelimiter[1] = '-';
                m_cchRangeDelimiter += 2;

                // Initialize range boundaries.
                m_dwRangeBeg = 0;
                m_dwRangeEnd = 0;
            }

        } // end if (cbContentType > cbMultiPart + 1)

        // Adjust the offset if we have a single range
        if (!m_pszRangeDelimiter)
            m_dwOffset = m_pParam->pRanges[0].dwOffset;
    }

    else // if (dwResponseCode != 206)
    {
        // Check if ranges are supported.
        static char szBytes[] = "bytes";
        char szBuf[sizeof(szBytes)];
        cbBuf = sizeof(szBytes);
        hr = pHttpInfo->QueryInfo
            (HTTP_QUERY_ACCEPT_RANGES, szBuf, &cbBuf, NULL, 0);
        if (SUCCEEDED(hr) && !lstrcmpi (szBytes, szBuf))
            m_CBParam.fdwRequestFlags = HTTP_REQUEST_ACCEPT_RANGES;
        else
            m_CBParam.fdwRequestFlags = 0;
    }

    // Get last modified time.
    SYSTEMTIME stLastModified;
    cbBuf = sizeof(stLastModified);
    hr = pHttpInfo->QueryInfo
    (
        HTTP_QUERY_FLAG_SYSTEMTIME | HTTP_QUERY_LAST_MODIFIED,
        &stLastModified, &cbBuf, NULL, 0
    );
    if (hr != S_OK)
    {
        memset (&stLastModified, 0, sizeof(stLastModified));
        hr = S_OK;
    }
    m_CBParam.pstLastModified = &stLastModified;

    // Get content length.
    m_CBParam.dwContentLength = 0;
    if (dwResponseCode != 206)
    {
        cbBuf = sizeof(m_CBParam.dwContentLength);
        hr = pHttpInfo->QueryInfo
        (
            HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_CONTENT_LENGTH,
            &m_CBParam.dwContentLength, &cbBuf, NULL, 0
        );
        if (hr != S_OK)
        {
            m_CBParam.dwContentLength = 0;
            hr = S_OK;
        }
    }

    // Inform the client the request is started.
    m_CBParam.CallbackType = REQUESTCB_STARTED;
    if (!(*(m_pParam->pfnRequestCB)) (&m_CBParam))
         m_pBinding->Abort();
    hr = S_OK;

done:

    if (pHttpInfo)
        pHttpInfo->Release();

    return hr;
}

// ---------------------------------------------------------------------------
// CUrlmonCallback::OnStartBinding
// ---------------------------------------------------------------------------
STDMETHODIMP CUrlmonCallback::OnStartBinding
    (DWORD grfBSCOption, IBinding* pBinding)
{
    if (pBinding != NULL)
    {
         m_pBinding = pBinding;
         m_pBinding->AddRef();
    }

    // Initialize receive buffer.
    if (!BufAlloc(RECV_BUF_SIZE))
        return E_OUTOFMEMORY;

    return S_OK;
}

// ---------------------------------------------------------------------------
// CUrlmonCallback::GetPriority
// ---------------------------------------------------------------------------
STDMETHODIMP CUrlmonCallback::GetPriority(LONG* pnPriority)
{
    return E_NOTIMPL;
}

// ---------------------------------------------------------------------------
// CUrlmonCallback::OnLowResource
// ---------------------------------------------------------------------------
STDMETHODIMP CUrlmonCallback::OnLowResource(DWORD dwReserved)
{
    return E_NOTIMPL;
}

// ---------------------------------------------------------------------------
// CUrlmonCallback::OnProgress
// ---------------------------------------------------------------------------
STDMETHODIMP CUrlmonCallback::OnProgress
    (ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR szStatusText)
{
    return S_OK;
}

// ---------------------------------------------------------------------------
// CUrlmonCallback::OnStopBinding
// ---------------------------------------------------------------------------
STDMETHODIMP CUrlmonCallback::OnStopBinding
    (HRESULT hrStatus, LPCWSTR pszError)
{
    // Release the binding, which will eventually release the callback object.
    m_pBinding->Release();
    m_pBinding = NULL;

    // Notify the client that we are done.
    m_CBParam.CallbackType = REQUESTCB_DONE;
    m_CBParam.hrRequest = hrStatus;
    (*(m_pParam->pfnRequestCB)) (&m_CBParam);

    return S_OK;
}

// ---------------------------------------------------------------------------
// CUrlmonCallback::GetBindInfo
// ---------------------------------------------------------------------------
STDMETHODIMP CUrlmonCallback::GetBindInfo
    (DWORD* pgrfBINDF, BINDINFO* pbindInfo)
{
    *pgrfBINDF = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA;
    *pgrfBINDF |= BINDF_GETNEWESTVERSION;
    pbindInfo->cbSize = sizeof(BINDINFO);
    pbindInfo->szExtraInfo = NULL;
    ZeroMemory (&pbindInfo->stgmedData, sizeof(STGMEDIUM));
    pbindInfo->grfBindInfoF = 0;
    pbindInfo->dwBindVerb = BINDVERB_GET;
    pbindInfo->szCustomVerb = NULL;
    return S_OK;
}

// ---------------------------------------------------------------------------
// CUrlmonCallback::OnDataAvailable
// ---------------------------------------------------------------------------
STDMETHODIMP CUrlmonCallback::OnDataAvailable
    (DWORD grfBSCF, DWORD dwSize, FORMATETC* pfmtetc, STGMEDIUM* pstgmed)
{
    if (!m_pstm && pstgmed->tymed == TYMED_ISTREAM)
    {
        m_pstm = pstgmed->pstm;
        m_pstm->AddRef();
        m_CBParam.CallbackType = REQUESTCB_DATA;
    }

    // Check for multi-part response.
    if (m_pszRangeDelimiter)
       return ParseMultiPartBuffer (grfBSCF & BSCF_LASTDATANOTIFICATION);

    HRESULT hrRead;
    DWORD cbActual;

    do // until hrRead != S_OK
    {
        // Read some more data.
        hrRead = m_pstm->Read (BufBeg(), BufSize(), &cbActual);

        // Notify the client we got some data.
        if (cbActual)
        {
            m_CBParam.dwDataOffset = m_dwOffset;
            m_CBParam.lpDataBuffer = BufBeg();
            m_CBParam.cbDataLength = cbActual;
            if (!(*(m_pParam->pfnRequestCB)) (&m_CBParam))
            {
                 // Client wants to stop.
                 m_pBinding->Abort();
                 return S_OK;
            }
        }

        m_dwOffset += cbActual;

    } while (hrRead == S_OK);

    return (hrRead == S_FALSE || hrRead == E_PENDING)? S_OK : hrRead;
}

// ---------------------------------------------------------------------------
// CUrlmonCallback::OnObjectAvailable
// ---------------------------------------------------------------------------
STDMETHODIMP CUrlmonCallback::OnObjectAvailable(REFIID riid, IUnknown* punk)
{
    return E_NOTIMPL;
}

// ---------------------------------------------------------------------------
// CUrlmonCallback::ParseMultiPartHeader
// ---------------------------------------------------------------------------
BOOL CUrlmonCallback::ParseMultiPartHeader (void)
{
    // multi-part boundary string literals.
    static char szContentType[]  = "Content-Type: ";
    static char szContentRange[] = "Content-Range: bytes ";

// Some macros for prettiness...
#define GrokStr(str) if (!BufScanStr (str, sizeof(str)-1)) return FALSE;
#define GrokInt(pint, delim) if (!BufScanInt (pint,delim)) return FALSE;

// Check for presence of boundary.
    if (m_dwRangeBeg > 0) // must not be first range
    GrokStr (szLF);   // will also detect a CR-LF
if (!BufScanStr (m_pszRangeDelimiter, m_cchRangeDelimiter))
    return FALSE;

// Check for end boundary.
if
(    m_pbDataBeg + 2 <= m_pbDataEnd
&& m_pbDataBeg[0] == '-'
&& m_pbDataBeg[1] == '-'
)
{
    m_dwRangeBeg = 0;
    m_dwRangeEnd = 0;
    return TRUE;
    }

// Scan content type and data range.
GrokStr (szLF);
GrokStr (szContentType);
GrokStr (szLF);
GrokStr (szContentRange);
    GrokInt (&m_dwRangeBeg, '-');
    GrokInt (&m_dwRangeEnd, '/');
    GrokStr (szLF);
    GrokStr (szLF);

    // Range end is inclusive; make it exclusive.
    m_dwRangeEnd++;
    return TRUE;

#undef GrokStr
#undef GrokInt
}

// ---------------------------------------------------------------------------
// CUrlmonCallback::ParseMultiPartBuffer
// ---------------------------------------------------------------------------
HRESULT CUrlmonCallback::ParseMultiPartBuffer (BOOL fLastCall)
{
    HRESULT hrRead;
    DWORD cbActual;

    do // until hrRead != S_OK
    {
        // Read data and append to buffer.
        hrRead = m_pstm->Read(m_pbDataEnd, BufSpace(), &cbActual);
        m_pbDataEnd += cbActual;

        // Dispatch to current state handler.
        if (m_dwRangeBeg != m_dwRangeEnd)
            goto process_data;
        else
            goto parse_header;

parse_header:

        // If download not at EOF, don't try to parse multi-part
        // boundary if any chance it would be split across buffers.
        if (hrRead != S_FALSE && m_pbDataBeg > m_pbDataEnd - BOUNDARY_MAXSIZE)
        {
            BufShift();
            continue;
        }

        // Attempt to parse the multi-part boundary.
        if (!ParseMultiPartHeader())
            break;

        // Check if we got termination boundary.
        if (m_dwRangeBeg == m_dwRangeEnd)
            break;

        goto process_data;

process_data:

        // Calculate amount of data to report.
        DWORD cbData   = m_pbDataEnd  - m_pbDataBeg;
        DWORD cbRange  = m_dwRangeEnd - m_dwRangeBeg;
        DWORD cbLength = min (cbData, cbRange);

        if (cbLength)
        {
            // Call back the client with more data.
            m_CBParam.dwDataOffset = m_dwRangeBeg;
            m_CBParam.lpDataBuffer = m_pbDataBeg;
            m_CBParam.cbDataLength = cbLength;
            if (!(*(m_pParam->pfnRequestCB)) (&m_CBParam))
            {
                 // Client wants to stop.
                 m_pBinding->Abort();
                 break;
            }
            m_dwRangeBeg += cbLength;
        }

        // Adjust the buffer depending on next state.
        if (m_dwRangeBeg == m_dwRangeEnd) // (cbLength == cbRange)
        {
            // Consume the data and look for next header.
            m_pbDataBeg += cbLength;
            goto parse_header;
        }
        else
        {
            // Reset buffer and get some more data.
            BufReset();
            continue;
        }

    } while (hrRead == S_OK);

    return (hrRead == S_FALSE || hrRead == E_PENDING)? S_OK : hrRead;
}


DOWNLOAD.CPP

// ===========================================================================
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright 1996 Microsoft Corporation.  All Rights Reserved.
// ===========================================================================
#include <urlmon.h>
#include <wininet.h>
// #include <datapath.h>
#include "range.h"
#include "callback.hpp"

//==============================================================================
HRESULT GetAnInterface
(
    IUnknown  *     punk,
    const IID &     riid,
    void **         pout,
    BOOL            bCheckServiceProvider,
    const IID &     siid,
    const IID &     siid_riid,
    void **         sout
)
{
    IOleObject *      oleObj    = 0;
    IOleClientSite *  oleSite   = 0;
    IOleContainer *   container = 0;
    IUnknown *        service   = 0;

    HRESULT       hr = E_FAIL;

    // Initialize passed in interface pointers: calling code assumes NULL for failure
    if(pout)
        *pout = NULL;
    if(sout)
        *sout = NULL;

    if(punk)
        hr = punk->QueryInterface( IID_IOleObject, (void **)&oleObj );

    while( SUCCEEDED(hr) && oleObj )
    {
        if( oleSite )
        {
            //oleSite->Release();
            oleSite = 0;
        }

        hr = oleObj->GetClientSite(&oleSite);

        if( FAILED(hr) )
            break;

        if( bCheckServiceProvider )
        {
            IServiceProvider * servProv;

            hr = oleSite->QueryInterface( IID_IServiceProvider, (void**)&servProv);

            if( SUCCEEDED(hr) )
            {
                hr = servProv->QueryService
                    (siid, siid_riid, (void **) &service);
                servProv->Release();
            }

            if( SUCCEEDED(hr) )
            {
                bCheckServiceProvider = FALSE;

                hr = service->QueryInterface( riid, pout );
            }

            if( SUCCEEDED(hr) )
                break;

        }

        if( container )
        {
            container->Release();
            container = 0;
        }

        hr = oleSite->GetContainer( &container );

        if( FAILED(hr) )
            break;

        hr = container->QueryInterface( riid, pout );

        if( SUCCEEDED(hr) )
            break;

        oleObj->Release();
        oleObj = 0;

        hr = container->QueryInterface( IID_IOleObject, (void**)&oleObj );

    }

    if( oleSite )
    {
        //oleSite->Release();
    }

    if( oleObj )
        oleObj->Release();

    if( container )
        container->Release();

    if( service )
        *sout = service;

    return( hr );

}

//==============================================================================
BOOL HttpReadFromServer (PHTTP_REQUEST_PARAM pParam)
{
    IMoniker*            m_pmk   = NULL;
    IBindStatusCallback* m_pbsc  = NULL;
    HRESULT hr = S_OK;

    IBindHost *pBindHost = NULL;

    // Create a callback object.
    m_pbsc = new CUrlmonCallback (pParam);
    if (m_pbsc == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto err;
    }

    // Look for a bind host.
    if (pParam->punkOuter)
    {
        hr = GetAnInterface
        (
            (IUnknown *) pParam->punkOuter,
            IID_IBindHost,           // const IID &  riid,
            (void **) &pBindHost,    // void **      pout,
            TRUE,                    // BOOL         bCheckServiceProvider,
            IID_IBindHost,           // const IID &  siid,
            IID_IBindHost,           // const IID &  siid_riid,
            (void **) &pBindHost     // void **      sout
        );
    }

    // Convert ANSI URL to Unicode.
    WCHAR wszUrl[INTERNET_MAX_URL_LENGTH];
    MultiByteToWideChar(CP_ACP, 0, pParam->pszUrl, -1, wszUrl, sizeof(wszUrl));

    // Initialize bind context, stream.
    IBindCtx *pbc;
    IStream *pstm;
    pbc =  NULL;
    pstm = NULL;

    // Call the appropriate BindToStorage.
    if (pBindHost)
    {
         pBindHost->CreateMoniker (wszUrl, NULL, &m_pmk, 0);
         pBindHost->MonikerBindToStorage
             (m_pmk, NULL, m_pbsc, IID_IStream, (void **) &pstm);
    }
    else
    {
        CreateURLMoniker(NULL, wszUrl, &m_pmk);
        CreateAsyncBindCtx(0, m_pbsc, NULL, &pbc);
        m_pmk->BindToStorage(pbc, NULL, IID_IStream, (void **) &pstm);
    }

    // Clean up bind context, stream.
    if (pbc)
        pbc->Release();
    if (pstm)
        pstm->Release();

    return SUCCEEDED(hr);

err:
    delete m_pbsc;

    if (m_pmk != NULL)
    {
        m_pmk->Release();
        m_pmk = NULL;
    }
    return FALSE;

}


RANGE.C

// ===========================================================================
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright 1996 Microsoft Corporation.  All Rights Reserved.
// ===========================================================================
#include <windows.h>
#include <urlmon.h>
#include <stdio.h>
#include <stdlib.h>
#include "range.h"

DWORD g_dwThread;

//==============================================================================
void DumpBytes (PBYTE pBuf, DWORD cbBuf)
{
    DWORD i;
    for (i=0; i<cbBuf; i++)
    {
        switch(pBuf[i])
        {
            case 10:  printf("[LF]"); break;
            case 13:  printf("[CR]"); break;
            default:  printf("%c", pBuf[i]); break;
        }
    }
}

//==============================================================================
void PrintResultCode (HRESULT hr)
{
#define CASEMSG(hr) case hr: printf(#hr); break;

printf ("Done with result ");
switch (hr)
{
CASEMSG(S_OK)
CASEMSG(S_FALSE)
CASEMSG(E_FAIL)

        CASEMSG(INET_E_INVALID_URL)
CASEMSG(INET_E_NO_SESSION)
CASEMSG(INET_E_CANNOT_CONNECT)
CASEMSG(INET_E_RESOURCE_NOT_FOUND)
CASEMSG(INET_E_OBJECT_NOT_FOUND)
CASEMSG(INET_E_DATA_NOT_AVAILABLE)
CASEMSG(INET_E_DOWNLOAD_FAILURE)
CASEMSG(INET_E_AUTHENTICATION_REQUIRED)
CASEMSG(INET_E_NO_VALID_MEDIA)
CASEMSG(INET_E_CONNECTION_TIMEOUT)
CASEMSG(INET_E_INVALID_REQUEST)
CASEMSG(INET_E_UNKNOWN_PROTOCOL)
CASEMSG(INET_E_SECURITY_PROBLEM)
CASEMSG(INET_E_CANNOT_LOAD_DATA)
CASEMSG(INET_E_CANNOT_INSTANTIATE_OBJECT)

default:
    printf ("0x%x", hr);
}
printf ("\n");
}

//==============================================================================
BOOL RequestCallback (PHTTP_REQUESTCB_PARAM pCBParam)
{
    printf ("RequestCallback[%d] ", pCBParam->dwRequestCtx);

    switch (pCBParam->CallbackType)
    {
        case REQUESTCB_STARTED:
            printf ("Started\n");
            printf ("  Status: %d\n", pCBParam->dwResponseCode);
            printf ("  Content-Length: %d\n", pCBParam->dwContentLength);
            if (pCBParam->fdwRequestFlags & HTTP_REQUEST_ACCEPT_RANGES)
                printf ("  Accept-Ranges: bytes\n");
            if (pCBParam->fdwRequestFlags & HTTP_REQUEST_FROM_CACHE)
                printf ("  From Cache\n");
            return TRUE;

        case REQUESTCB_DATA:

    // Dump some bytes so we can verify them.
    printf ("offset=%5d got %4d bytes:",
        pCBParam->dwDataOffset, pCBParam->cbDataLength);
    DumpBytes ((PBYTE) pCBParam->lpDataBuffer, min(pCBParam->cbDataLength, 15));
    printf ("\n");
    return TRUE;

        case REQUESTCB_DONE:
            PrintResultCode (pCBParam->hrRequest);
            // Let the console app thread exit the message loop.
            PostThreadMessage (g_dwThread, WM_QUIT, 0, 0);
            return TRUE;
}
}

//==============================================================================
int __cdecl main (int argc, char *argv[])
{
    MSG msg;
    HTTP_REQUEST_PARAM Param;

    SYSTEMTIME st;
    PSTR pszUrl;
    HTTP_REQUEST_RANGE ranges[10];
    DWORD cRanges;

    // Check usage.
    if (argc<2 || argc%2 || argc>(2*(sizeof(ranges))/sizeof(ranges[0])))
    {
        fprintf (stderr, "Usage:   range <http-url> [<offset1> <size1>]  [<offset2> <size2>]  [<offset3> <size3>]  ...\n");
        fprintf (stderr, "Example: range http://www.microsoft.com 2 5 10 52 20 4\n");
        exit (1);
    }

    // Get URL and ranges from command line.
    pszUrl = argv[1];
    cRanges = 0;
    while (argc -= 2)
    {
if (argc <= 0)
break;

    ranges[cRanges].dwOffset = atoi(argv[2 * cRanges + 2]);
    ranges[cRanges].dwSize   = atoi(argv[2 * cRanges + 3]);
        cRanges++;
    }

    // Pass current time as unless-modified-since time.
    GetSystemTime (&st);

    // Save the thread ID so the we can post ourselves
    // WM_QUIT from the callback without creating a window.
    g_dwThread = GetCurrentThreadId();

    // Read range depends on URL monikers which depend on COM.
    if (FAILED(CoInitialize(NULL)))
        exit (1);

    // Initiate the read range request.
    ZeroMemory (&Param, sizeof(Param));
    Param.cbStruct     = sizeof(Param);
    Param.pszUrl       = pszUrl;
    Param.pfnRequestCB = RequestCallback;
    Param.dwRequestCtx = 15;
    Param.pRanges      = &ranges[0];
    Param.cRanges      = cRanges;
    Param.pstUnlessModifiedSince = &st;

    if (HttpReadRequest (&Param))
    {
        // Must enter a message loop for the binding to progress.
        while (GetMessage (&msg, NULL, 0, 0))
        {
            TranslateMessage (&msg);
            DispatchMessage (&msg);
        }
    }

    CoUninitialize();
    return 1;
}


RANGE.H

/*===========================================================================
Copyright 1996 Microsoft Corporation.  All Rights Reserved.

THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.

OVERVIEW OF INTERFACE

The client calls HttpReadRequest to specify the URL, a callback function,
and an optional array of read ranges.  First, the callback will be notified
of the http response code, content length, last modified time, whether
ranges are supported by the server, and whether the file is from the cache.
Next, it may be called repeatedly with data which must be processed during the
call.  Finally, it will indicate completion and an error if any.  Within the
callback, a client can initiate a new HttpReadRequest and/or return FALSE to
stop the current request.

All ActiveX controls can use this interface.  Other applications must satisfy
some requirements due to an implicit dependency on URL moniker.  The client
thread must have a message loop.  Before calling HttpReadRequest, the client
process must have called CoInitialize or OleInitialize.  After exiting the
message loop, the client process must call CoUninitialize or OleUninitialize.
===========================================================================*/

#ifndef _RANGE_
#define _RANGE_

#include <windows.h>
#include <objbase.h>

#ifdef __cplusplus
extern "C" {
#endif

// ===========================================================================
// Callback parameter structure and prototype
// ===========================================================================

// Callback Types:
typedef enum
{
REQUESTCB_STARTED  = 0,
REQUESTCB_DATA     = 1,
REQUESTCB_DONE     = 2,
}
HTTP_REQUESTCB_TYPE;

// Callback parameters: valid only during the current call.
typedef struct
{
DWORD  cbStruct;       // will be sizeof(HTTP_CALLBACK_PARAM)
    DWORD  dwRequestCtx;   // context passed in to HttpReadRequest
    HTTP_REQUESTCB_TYPE CallbackType;   // callback type as defined above

    union
    {
        struct // fields used if CallbackType == REQUESTCB_STARTED
{
    DWORD       fdwRequestFlags;  // fields as defined below
    DWORD       dwResponseCode;   // status code (0 if unknown)
    SYSTEMTIME* pstLastModified;  // last modified (NULL if unknown)
            DWORD       dwContentLength;  // content length (0 if unknown)
};

        struct // fields used if CallbackType == REQUESTCB_DATA
        {
    DWORD       dwDataOffset;     // offset of data
    LPVOID      lpDataBuffer;     // pointer to data
    DWORD       cbDataLength;     // amount of data
};

struct // fields used if CallbackType == REQUESTCB_DONE
{
            HRESULT     hrRequest;        // values defined in urlmon.h
};
};
}
HTTP_REQUESTCB_PARAM, *PHTTP_REQUESTCB_PARAM;

// Fields for HTTP_REQUESTCB_PARAM.fdwRequestFlags:
#define HTTP_REQUEST_FROM_CACHE       1
#define HTTP_REQUEST_ACCEPT_RANGES    2

// Callback function supplied by client:
typedef BOOL (*PFN_REQUESTCB) (PHTTP_REQUESTCB_PARAM);

// ===========================================================================
// HttpReadRequest parameter structure and prototype
// ===========================================================================

// Structure for specifying a read range:
typedef struct
{
DWORD dwOffset;     // offset of data within the URL
DWORD dwSize;       // amount of data (0 means until end of file)
}
HTTP_REQUEST_RANGE, *PHTTP_REQUEST_RANGE;

// Read request parameters must remain valid until REQUEST_COMPLETE:
typedef struct
{
DWORD               cbStruct;        // must be sizeof(HTTP_REQUEST_PARAM)
 LPVOID              punkOuter;       // controlling IUnknown (may be NULL)
    PCSTR               pszUrl;          // URL to request via http
PFN_REQUESTCB       pfnRequestCB;    // request callback function
    DWORD               dwRequestCtx;    // context to pass to callback

    // Optional fields for specifying ranges, otherwise set all to 0:
    PHTTP_REQUEST_RANGE pRanges;                // array of read ranges
    DWORD               cRanges;                // number of ranges
    SYSTEMTIME*         pstUnlessModifiedSince; // unless-modified-since time
}
HTTP_REQUEST_PARAM, *PHTTP_REQUEST_PARAM;

// Function called by client to initiate http read request.
BOOL HttpReadRequest    (PHTTP_REQUEST_PARAM); // try cache first
BOOL HttpReadFromServer (PHTTP_REQUEST_PARAM); // bypass cache

#ifdef __cplusplus
} // extern "C" {
#endif

#endif // _RANGE_


CACHE.CPP

// ===========================================================================
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright 1996 Microsoft Corporation.  All Rights Reserved.
// ===========================================================================
#include <windows.h>
#include <wininet.h>
#include "range.h"

//==============================================================================
BOOL QueryHeaders
(
     HANDLE                hRequest,
     PHTTP_REQUESTCB_PARAM pCBParam,
     SYSTEMTIME*           pstLastModified
)
{
    DWORD cbBuf;
    BOOL fRet;

    pCBParam->CallbackType     = REQUESTCB_STARTED;
    pCBParam->fdwRequestFlags  = HTTP_REQUEST_FROM_CACHE;

    // Get if the server supports ranges from the response headers.
    static char szBytes[] ="bytes";
    char szBuf[sizeof(szBytes)];
    cbBuf = sizeof(szBytes);
    fRet = HttpQueryInfo
        (hRequest, HTTP_QUERY_ACCEPT_RANGES, szBuf, &cbBuf, 0);
    if (fRet && !lstrcmpi (szBuf, szBytes))
        pCBParam->fdwRequestFlags |= HTTP_REQUEST_ACCEPT_RANGES;

    // Get the response code.
    cbBuf = sizeof(pCBParam->dwResponseCode);
    fRet = HttpQueryInfo
    (
        hRequest,
        HTTP_QUERY_FLAG_NUMBER | HTTP_QUERY_STATUS_CODE,
        &pCBParam->dwResponseCode,
        &cbBuf,
        NULL
    );
    if (!fRet)
       pCBParam->dwResponseCode = 0;

    // Get the last modified time from the response headers.
    cbBuf = sizeof(*pstLastModified);
    fRet = HttpQueryInfo
    (
        hRequest,
        HTTP_QUERY_FLAG_SYSTEMTIME | HTTP_QUERY_LAST_MODIFIED,
        pstLastModified,
        &cbBuf,
        NULL
    );
    pCBParam->pstLastModified = fRet? pstLastModified : NULL;

    // Get the content length from the cache file size.
    pCBParam->dwContentLength =
        InternetSetFilePointer (hRequest, NULL, 0, FILE_END, 0);

    return TRUE;
}

//==============================================================================
BOOL HttpReadFromCache (PHTTP_REQUEST_PARAM pParam)
{
    // Locals that must be initialized before goto done.
    BOOL fSuccess = FALSE;
    HANDLE hInternet = NULL;
    HANDLE hConnect  = NULL;
    HANDLE hRequest  = NULL;
    PVOID pRead = NULL;
    HRESULT hrRequest = S_OK;

    // Initialize wininet
    hInternet = InternetOpen
        ("RR", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, INTERNET_FLAG_OFFLINE);
    if (!hInternet)
        goto done;
    hRequest = InternetOpenUrl
        (hInternet, pParam->pszUrl, NULL, 0, INTERNET_FLAG_OFFLINE, 0);
    if (!hRequest)
        goto done;

    // Initialize callback parameters.
    HTTP_REQUESTCB_PARAM CBParam;
    CBParam.cbStruct         = sizeof(CBParam);
    CBParam.dwRequestCtx     = pParam->dwRequestCtx;

    // Allocate read buffer
    DWORD cbRead;
    cbRead = 4096;
    pRead  = LocalAlloc (LMEM_FIXED, cbRead);
    if (!pRead)
    {
        SetLastError (ERROR_NOT_ENOUGH_MEMORY);
        goto done;
    }

    SYSTEMTIME st;
    if (!QueryHeaders (hRequest, &CBParam, &st))
        goto done;

    fSuccess = TRUE;

    // Notify the client that request is started.
    if (!(*pParam->pfnRequestCB) (&CBParam))
        goto notify;

    // Set up ranges to read.
    HTTP_REQUEST_RANGE EntireRange;
    PHTTP_REQUEST_RANGE pRanges;
    DWORD cRanges;

    if (pParam->cRanges)
    {
        pRanges = pParam->pRanges;
        cRanges = pParam->cRanges;
    }
    else
    {
        EntireRange.dwOffset = 0;
        EntireRange.dwSize = CBParam.dwContentLength;
        pRanges = &EntireRange;
        cRanges = 1;
    }

    // Loop over the ranges, reading into the buffer.
    CBParam.CallbackType = REQUESTCB_DATA;
    CBParam.lpDataBuffer = pRead;

    while (cRanges--)
    {
        CBParam.dwDataOffset = pRanges->dwOffset;
        DWORD cbRange        = pRanges->dwSize;
        if (!cbRange)
            cbRange = InternetSetFilePointer (hRequest, 0, NULL, FILE_END, 0)
              - pRanges->dwOffset;

        while (cbRange) // Loop until we have all the data for the range.
        {
            InternetSetFilePointer
                (hRequest, CBParam.dwDataOffset, NULL, FILE_BEGIN, 0);

            if (!InternetReadFile
                (hRequest, pRead, min(cbRead, cbRange), &CBParam.cbDataLength))
            {
                hrRequest = E_FAIL;
                goto notify;
            }

            // Check for EOF
            if (!CBParam.cbDataLength)
                break;

            // Call the client with data.
            if (!(*pParam->pfnRequestCB) (&CBParam))
                goto notify;

            CBParam.dwDataOffset += CBParam.cbDataLength;
            cbRange              -= CBParam.cbDataLength;
        }

        // Advance to next range.
        pRanges++;

    } // end while (cRanges--)

notify:

    // Notify the client we are done.
    CBParam.CallbackType = REQUESTCB_DONE;
    CBParam.hrRequest = hrRequest;
    (*pParam->pfnRequestCB) (&CBParam);

done:
    if (pRead)
        LocalFree ((HLOCAL) pRead);
    if (hRequest)
        InternetCloseHandle (hRequest);
    if (hConnect)
        InternetCloseHandle (hConnect);
    if (hInternet)
        InternetCloseHandle (hInternet);
    return fSuccess;
}

//==============================================================================
BOOL HttpReadRequest (PHTTP_REQUEST_PARAM pParam)
{
    if (pParam->cbStruct != sizeof(HTTP_REQUEST_PARAM))
    {
        SetLastError (ERROR_INVALID_PARAMETER);
        return FALSE;
    }

    if (HttpReadFromCache (pParam))
        return TRUE;
    return HttpReadFromServer (pParam);
}


RECVBUF.CPP

// ===========================================================================
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright 1996 Microsoft Corporation.  All Rights Reserved.
// ===========================================================================
#include "recvbuf.hpp"
#include <limits.h>

#define upcase(c)  ((BYTE) CharUpper ((LPSTR) (BYTE) (c)))

// ---------------------------------------------------------------------------
// CUrlmonCallback::BufScanStr
// Utility to seek for a string in a memory buffer.
// ---------------------------------------------------------------------------
BOOL RecvBuf::BufAlloc (DWORD dwSize)
{
    m_cbBufSize = dwSize;
    m_pbBufBeg = (PBYTE) LocalAlloc (LMEM_FIXED, m_cbBufSize);
    if (!m_pbBufBeg)
        return FALSE;
    m_pbBufEnd = m_pbBufBeg + m_cbBufSize;
    m_pbDataBeg = m_pbBufBeg;
    m_pbDataEnd = m_pbBufBeg;
    return TRUE;
}

// ---------------------------------------------------------------------------
// CUrlmonCallback::SeekPastString
// Utility to seek for a string in a memory buffer.
// ---------------------------------------------------------------------------
BOOL RecvBuf::BufScanStr (PSTR pszSearch, DWORD cbSearch)
{
PBYTE pbLimit = m_pbDataEnd - cbSearch;

while (1)
{
    DWORD ibSearch;

// Check for overflow.
if (m_pbDataBeg >= pbLimit)
return FALSE;

    for (ibSearch = 0; ibSearch < cbSearch; ibSearch++)
    {
        if (upcase(m_pbDataBeg[ibSearch]) != upcase(pszSearch[ibSearch]))
            break;
    }

    if (ibSearch == cbSearch)
    {
        // Found it.
           m_pbDataBeg += cbSearch;
        return TRUE;
    }
    else
    {
        m_pbDataBeg++;
        continue;
    }
}
}

// ---------------------------------------------------------------------------
// Utility to scan a 32-bit integer from memory buffer.
// Returns FALSE on integer overflow or end of buffer.
// ---------------------------------------------------------------------------
BOOL RecvBuf::BufScanInt (DWORD *pdwRet, BYTE cDelimit)
{
DWORD dwRet = 0;

// Scan past non-delimiters and non-digits.
while (*m_pbDataBeg != cDelimit && !isdigit(*m_pbDataBeg))
{
if (++m_pbDataBeg >= m_pbDataEnd)
return FALSE;
}

while (*m_pbDataBeg != cDelimit)
{
if (!isdigit(*m_pbDataBeg))
return FALSE;
if (dwRet > ULONG_MAX / 10)
return FALSE;
dwRet *= 10;
dwRet += *m_pbDataBeg - '0';
if (++m_pbDataBeg >= m_pbDataEnd)
return FALSE;
}

*pdwRet = dwRet;
return TRUE;
}


README.TXT

THIS TOOL IS NOT SUPPORTED BY MICROSOFT CORPORATION. IT IS PROVIDED "AS IS"
BECAUSE WE BELIEVE IT MAY BE USEFUL TO YOU.

NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE NOTE

It is necessary to build the FrameWrk samples before building ToDoSvr
or WebImage. Running "nmake" from the <sdkdir>\samples\com\activexcontrol\basectl
directory will build all of the sample code in the correct order.  Before
building any of the samples, run setenv.bat to add the Microsoft Platform SDK
include, bin, and lib directories to the front of your environment.

More information on the Internet-aware OLE controls is available in
readme.txt files in the sample directories.

The Framework Sample Code for Authoring non-MFC Controls
====+====================================================

This document presents a short discussion on using the ActiveX Controls
Framework to author new ActiveX controls.

0. Contents
=---------=

1.0  Introduction
   1.1  Target Audience
   1.2  Structure of the Framework
   1.3  Target Environment

2.0  Creating an ActiveX Control
   2.1  Building the Control
   2.2  Working with the In-Process Server

3.0  Working with Your ActiveX Control
   3.1  Structure of a Control
   3.2  Painting a Control
   3.3  Handling Messages in a Control
   3.4  Adding a Property
   3.5  Adding a Method
   3.6  Adding an Event
   3.7  Using Standard ActiveX Types
   3.8  Throwing an Exception

4.0  Persistence
   4.1  Text Persistence
   4.2  Binary Persistence

5.0  Property Pages
   5.1 Working with a Property Page
   5.2 Navigating through Associated Objects
   5.3 Marking Your Page as Dirty

6.0  String Manipulation
   6.1 Types of Strings
   6.2 Working with Strings

7.0  Localization
   7.1  Setting up for Localization

8.0 Miscellaneous
   8.1 Recommended Reading
   8.2 Host-Specific Notes
      8.2.1 Microsoft Access 95

1.0 Introduction
=--------------=

The ActiveX Controls Framework is a sample code base from which one can author new
ActiveX controls for use in existing containers, such as Microsoft Visual Basic,
Microsoft Access, and Microsoft Visual FoxPro, or in future containers of ActiveX Controls.

It differs from the Microsoft Visual C++ CDK in many ways.  Most notably, this
framework is intended to be considerably more "bare bones."  Only minimal
functionality is provided to the programmer.  Little in the way of default handlers
for various windows messages, OLE events, et. al. has been provided.  The ability
to add them is there, but the code is not.  The code base has also been designed
primarily for performance and reduced code size as much as possible.  Whenever a
choice between ease of use and performance arose, the latter was typically chosen.

In addition, the code is designed to take advantage of whatever OC '96 features are
available from the host.  The code will use any available features, and fall back
to the 'older' behavior for those cases where they aren't.

The code base is extremely extensible, however, and of course, all the source code
is there -- if something doesn't do what you want it to, make it.

  1.1 Target Audience

This framework targets a slightly more advanced programmer than the Microsoft
Visual C++ Control Developers Kit.  Specifically, the programmer will be required
to understand some of the fundamentals of OLE automation and dual interfaces.  The
user will have to be able to understand and modify an .ODL file on their own.
In addition, the user will be required to understand and be able to work with
OLE persistence interfaces, most notably IStream IPersistPropertyBag and
IPersistStream.  However, if it is not desired, the user will not be required
to have much knowledge of OLE embedding interfaces.

Programmers who do not have specific performance requirements, those not familiar
with many of the pertinent OLE technologies, or those who work primarily with the
Microsoft Foundation Classes will find the MFC/CDK far more suited to their needs.

   1.2 Structure of the Framework

The directory structure of the ActiveX Controls Framework is as follows:

 BaseCtl +
 |- \FrameWrk
 |
 |- \IELnk
 |
 |- \IEMIME
 |
 |- \Include
 |
 |- \Lib
 |
 |- \ToDoSvr
 |
 +- \WebImage

The BaseCtl\Framewrk and BaseCtl\Include directories contain the core code for
writing an ActiveX control.  The BaseCtl\Include directory contains the headers
that most controls will get their information from, and the BaseCtl\Framewrk
directory contains the core functionality (in the COleControl class) which compiles
into a static library (.LIB) form.

A few samples are provided with the framework.

   1.3  Target Environment

This framework was developed assuming you have the Microsoft Visual C++ 4.0 (or later)
toolset in your path and the Microsft Windows Platform SDK installed. Running 'nmake'
in BaseCtl or BaseCtl\FrameWrk will build debug a version of the static library.
'nmake nodebug=1' will build a retail (nodebug) static library.

All of the makefiles and build processes are command-line based.  Various people have
reported, however, that it's largely trivial to integrate that into their favorite
environments.

NOTE: Under Windows 95, Visual C++ will not, by default, register its environment
variables to set up for command line builds. Occasionally on some Windows 95 machines
you may get a bunch of "Out of Environment Space" messages when doing this.  In the
properties dialog for the Command Prompt, you can increase the size of the environment
from "Auto" to some number like 1024, and this takes care of the problem.  None of this
should happen when developing under Microsoft Windows NT.

The file dwinvers.h in <sdkdir>\samples\com\activexcontrol\BaseCtl\Framewrk is a
file that contains version and copyright information that should be updated by you
each time you run your builds.  The framework does not do this work for you.

2.0 Creating an ActiveX Control
=-------------------------=

Creating an ActiveX control using this framework proves moderately straightforward.
We will go into the process in brief here.  Templating off the sample code should be
able to fill in the holes.

The framework implements the core functionality in a few C++ classes, notably
CAutomationObject, COleControl (which inherits from CAutomationObject), and
CPropertyPage.  All objects inherit from CUnknownObject, which provides the
support for aggregation.

So, to write an ActiveX Control, you need to declare a new object which inherits
from COleControl.  In addition, you'll need to inherit from some sort of Automation
interface that describes the properties and methods for your control, say, IMyControl.
This interface description is generated by MKTYPLIB and will be put in some output
file created by MKTYPLIB.  COleControl has a number of virtual methods that are
declared as pure, which you simply must implement in your control class.  These
include WindowProc, LoadBinaryState, LoadTextState, SaveBinaryState, SaveTextState,
OnDraw, and RegisterClassData.

To write a property page, you declare a new object which inherits from CPropertyPage.
This object must implement a DialogProc.  You can also implement automation objects
and collections by declaring a new object that inherits from CAutomationObject.

Since an ActiveX control is an In-Process OLE Server, you also need one file to
describe all your objects, whether they be controls, automation objects, or property
pages.  This file will have a bunch of information in it, including a table of all
objects and information about them.  In addition, it'll have information on what
sort of localization your server would like to use, and what sort of licensing
support you'd like to have.

Finally, you'll need a resource file, an .ODL file to describe your interfaces and
event interfaces, a .DEF file for your linking information, and a file to define
all the guids that have been declared.

 You can template all this information from one of the sample controls.

   2.1  Building the Control

To build your control, you first need to generate the libraries for the Framework files.
The libraries are not automatically generated; this allows you to modify the libraries
if desired.  Doing this is as simple as going to the \Framewrk directory and typing
'nmake' (for debug) or 'nmake nodebug=1' (for retail). You will only need to recompile
the framework files if you make a change to a file in the BaseCtrl\Framewrk directories.

Once the framework files have been built, go back to your control's subdirectory,
and type "nmake" or "nmake nodebug=1" there.  This will build your control.

   2.2  Working with the In-Process Server

Your ActiveX control, any property pages, and automation objects are all just OLE COM
objects in an in-process server.  All of these objects must be declared in a global table,
g_ObjectInfo, which is found in the main in-proc server file.  Each object is declared
with a wrapper, one of CONTROLOBJECT, PROPERTYPAGE, or AUTOMATIONOBJECT.  The name of
the object is entered as an argument to the macro.  In the header file where you declare
the COM object, you'll need to use one of DEFINE_PROPERTYPAGEOBJECT, DEF
INE_CONTROLOBJECT, DEFINE_WINDOWLESSCONTROL, or DEFINE_AUTOMATIONOBJECT to actually
declare the object for use in the global table.  If it turns out that you are declaring
objects that aren't createable from a class factory, they still do need to be declared
in the global table, but the Creation function specified in the structure should be left
as NULL.

There is some additional information that must be put in the file for the in-process server.
The LIBID of the type library (.TLB) must be put in the global variable g_pLibid.  You
must indicate what sort of localization your control supports by using the variables
g_fSatelliteLocalization and g_lcidLocale.   See Section 7.0, "Localization," for more information.

In your in-proc server file, there are a few routines that you can put code in. The first
two, InitializeLibrary() and UninitializeLibrary(), are called when the DLL is first loaded
into and unloaded from memory.  This is a good place to do any sort of initialization that
can't be put off until later.  It is worth noting that for performance reasons, delaying
as much as possible is often a good idea.  The CheckForLicense function lets the control
decide if it is licensed or not to run.  GetLicenseKey() is called by the class factory.
You should return your license key here if you support licensing, or just return "" otherwise.

The RegisterData and UnregisterData routines are called from DllRegisterServer and
DllUnregisterServer, and can be used to register and clean up additional information in
the registry.

Finally, two small pieces of code are included in this file so that your project does
not have to link with any of the C-runtimes.  This typically results in smaller DLL size, and can help with performance.  If you want to link with the C runtime libraries, or the CRTDLL libraries, then you should remove these last things from the in-proc server file.

3.0  Working with Your ActiveX Control
=--------------------------------=

Once you have your control up and running, you'll want to start extending its functionality.  The first thing to note is that your control is, in many ways, much like a regular Windows program.  Even if your control doesn't have a window [ie, it's taking advantage of the windowless features of the OC '96 specification], you have a window proc, and you have to paint the client area yourself using regular windows drawing APIs and handles to Device Contexts (DC's).

To declare your control, you'll use one of DEFINE_CONTROLOBJECT and DEFINE_WINDOWLESSCOTNROL in your control's header file.  These structures are mostlystraightforward and self-documenting, but there are two parameters worth a little extra dicussion.

The first is the dwActivationPolicy parameter.  The framework allows your control to take advantage of the delayed activation features provided by IPointerInactive.  If your control wishes to use any of these, then you should insert an activation policy in to this field.  By default, it is 0.  Subclassed windows controls should not change this.  Other control authors may wish to take advantage of this interface, and should consult the OC '96 specification for more information.

The second non-trivial parameter is a boolean passed in to the DEFINE_WINDOWLESSCONTROL macro which indicates whether your control will have any transparent regions or not.  If it is true, the host will have to do a little extra work to ensure that your control paints correctly in the z-order of things.  If it is false, your control is required to paint it's entire client area, but the host has to do a little less work.

   3.1 Structure of a Control

There is a core set of methods that every control in this framework must implement, based on creation semantics and methods that COleControl simply does not provide for you.  There are also a bunch of routines that are interesting to override and provide an implementation for.  The following is a discussion of many of these.

   a. static Create() function. Every control must create their control object in this routine, and then return a pointer to its private unknown (for aggregation support).

   b. Constructor and Destructor.  These are also generated for you, and a control should initialize anything here.  Controls should try to minimize the amount of work that is done here in order to help prevent load time from degrading unacceptably.

   c. RegisterClassData().  All controls must implement this routine.  Even if your control will be windowless for the most part, it is entirely possible that a user will place your control in a host that doesn't support all of the OC '96 features, and the framework will be forced to create an HWND for you control.  In this case, you will need a window class.  This routine will only be called once the first time a control of the given type is loaded in a process in a situation in which it must have an HWND
.  Controls should register their window class (using RegisterClass and the WNDCLASS structure) here.  In addition, subclassed windows controls should get a pointer to the parent control's WindowProc and set that up in the g_ObjectInfo table using the SUBCLASSWNDPROCOFCONTROL() macro.  See the Button and Circle control samples for examples of how this is done.  Invisible at Runtime controls should just return FALSE in this routine, as it should never get called.

   d. BeforeCreateWindow() and AfterCreateWindow() are not mandatory to implement, but are extremely interesting routines.  BeforeCreateWindow() is called right before the call to CreateWindow() if your control is windowed, but after persistent state has been loaded.  Controls should use this opportunity to set the window title for their control in m_szWindowTitle, and can also set up bits in pdwWindowStyle and pdwWindowStyleEx parameters that are passed in.  In addition, controls can set their initial cap
tion here in the parameter passed in for this purpose.  This does, however, have a limit as defined in CreateInPlaceWindow in ctlmisc.cpp.  Control writers should use this opportunity to set up these defaults as much as possible as opposed to in the message handler for WM_CREATE, as it typically results in much better performance.

   e. InternalQueryInterface().  Your control implements this to support the QI for the control's primary automation interface, such as IMyControl.  You can also use this method to support additional interfaces in your control.  For example, if you want to support IPerPropertyBrowsing, you'd have your control's CMyControl class inherit from IPerPropertyBrowsing, and support the QueryInterface for IID_IPerPropertyBrowsing in InternalQueryInterface.  If the control fails the QI, then the control should deleg
ate back to COleControl::InternalQueryInterface to see if it likes the IID.

   f. LoadTextState, LoadBinaryState, SaveTextState, SaveBinaryState.  All controls must implement these persistence routines.  See Section 4.0, "Persistence," for a discussion of these interfaces.

   g. OnDraw.  This routine is called when your control is expected to draw itself.  In Design mode, this call will originate from a container calling IViewObject2::Draw.  In run mode, the controls framework will intercept the WM_PAINT message, and will translate it into a call to your OnDraw routine.  See Section 3.2, "Painting a Control," for more information on this routine.

   h. WindowProc.  Messages that are not handled in the framework code (such as SimpleFrame messages and WM_PAINT), are sent to your control here.  Your control should deal with these here.  See Section 3.3, "Handling Messages in a Control," for a discussion of this routine.  In addition, please see the note below on OnSpecialKey for more information.

   i. OnSpecialKey.  Messages for various keyboard events, such as moving the Cursor keys, function keys, and other non-standard keys do not go to the WindowProc.  Instead, they are sent to the OnSpecialKey routine.  If you want your control to handle special keys and accelerators you should override and implement this routine.  The control should return TRUE if it handles a key, or FALSE if it ignores the key.

   j. DoCustomVerb.  If you choose to have your control implement custom verbs in addition to the property page one that is provided by default (provided your control has a property page), then you should implement this routine and take appropriate action depending on what verb was sent in.  Return OLEOBJ_S_INVALIDVERB if you don't recognize the verb given.

   k. OnSetExtent.  This is called every time your control is resized.  The m_Size SIZEL structure is your control's current size in pixels.  Controls should look in here for their size information and override OnSetExtent if they want control over how their control is sized.  Please see the Invisible sample for an example of a control that is of a fixed size.

   l. OnQuickActivate.  This routine is called if your control is in a host that supports quick activation, and has used IQuickActivate::QuickActivate to activate your control.  Your control will be given a pointer to a QACONTAINER structure, and should examine the data in it which it finds interesting.  A non-trivial number of ambients will be passed to the control in this way, and will save later calls to GetAmbientProperty, which is not cheap.  Please see documentation for IQuickActivate [OC '96 specifi
cation] for more information.

In addition, the following methods/routines can be called by the ActiveX control, and often prove to be extremely useful.

   a. DoSuperClassPaint.  Subclassed window controls can call this from their OnDraw routines to paint themselves.  For most window controls, this routine will paint them correctly in design mode and run mode.  Some Windows controls, however, will prove somewhat moody, and might require a little extra tweaking.

   b. RecreateControlWindow.  Again, used for subclassed controls -- will go and recreate the control's hWnd.  This is useful if the control is changing a style bit that simply can't be changed with a SetWindowLong(GWL_STYLE ...) call.

   c. DesignMode.  Returns a BOOL indicating its best guess as to whether the environment is in design mode or not.  If it can't figure it out, it will return FALSE.

   d. GetAmbientProperty.  This routine is used to get an ambient property from the container.  Not all containers will return these (they might not support them), so be careful to check the return code.

   e. GetAmbientFont.  Gets the current ambient font.  Don't forget to release the font once you're done with it.  Again, the container may simply not implement this.  Controls are encouraged to look for the ambient font in the QACONTAINER structure passed in to OnQuickActivate.  Not calling GetAmbientFont saves time.

   f. ModalDialog.  Controls must call this before they show a modal dialog.  This is seen when the control is about to show its About Box dialog.

   g. InvalidateControl.  Much like the InvalidateRect API, but this also operates in design mode.  This will force the control to be repainted if you pass in NULL for the rectangle, or will just invalidate the given area if it's not NULL.

   h. SetControlSize.  Control's who are changing their size out of OnSetExtent should use this routine to set their size.  The control passes in a SIZEL structure in PIXELS, and should expect a call to OnSetExtent.  Be careful of some recursive situations.

   i. PropertyChanged.  Whenever the value of a property changes, this routine should be called to notify a host.  This will cause hosts to update any property browsers (such as those seen in Microsoft Visual Basic 4.0)

   j. RequestPropertyEdit.  Whenever the control wants to change a property that is marked as requestedit in the .ODL file, the control will need to call this first, and check the return code.

   k. GetResourceHandle.  Controls should call this whenever they're loading a resource that could be localized.  This routine will go and get the handle to the appropriate DLL, and deals with satellite DLLs or the lack thereof.  Please see the Localize sample for how this works.

   l. FireEvent.  The control will pass this routine an EVENTINFO structure, and an event as described in the EVENTINFO will be fired.  The control will also pass parameters to this routine, as it is a varargs method.

   m. ControlFromUnknown.  Property page code often finds it useful to get the COleControl * pointer from the IUnknown for a control object.  This routine does just that.

   n. Exception.  Your control, or any automation objects, can use this to send the user an error message.  Please see Section 3.8, "Throwing an Exception," for more information on using this routine.

Windowless that wish to support windowless in-place activation have a couple of routines they must call instead of regular Win32 APIs in order to get the correct functionality.  The following is a list of these and their replacements:

   a. GetFocus --> Writers should call OcxGetFocus().  This will return a boolean indicating whether or not the control has keyboard focus.
   b. SetFocus --> Writers should call OcxSetFocus().  This routine takes a boolean parameter indicating whether or not you want to take the focus or give it up.
   c. GetCapture --> Use OcxGetCaputre() instead.  This indicates whether or not you have the keyboard capture or not.
   d. SetCapture --> By calling OcxSetCapture(), you can ask for or release the keyboard capture.
   e. GetDC/ReleaseDC.  OcxGetDC/OcxReleaseDC now provide this functionality for windowless controls.
   f. InvalidateRect -->  OcxInvalidateRect() will let you force a repaint of your control.

It is worth noting that these routines will all work correctly if your control does, in fact have an HWND.  If your control doesn't plan on working without an HWND, then you may safely use the regular Win32 APIs.

   3.2  Painting a Control

The OnDraw routine is called whenever the control needs to paint.  Sometimes the origin of the call is from IViewObject2::Draw (as in design mode), and other times it comes from being sent a WM_PAINT message (as handled in ControlWindowProc).

Your control is given a DC, a rectangle to describe where to paint, a rectangle for describing a metafile, and an Information Context (IC, passed in as an HDC) that describes the device.  If the device is a metafile, then the control must do a little different work.  However, if the device is a raster display, the control is typically painting to the screen.

Your control must be careful not to make any assumptions about the DC, except that it will be in MM_TEXT mapping mode.  Often, there will be no default pens, brushes, fonts or colors selected into the DC.  Your control will have to do this work itself.  This will typically manifest itself by having your control look slightly different in design and run modes.

If the fOptimize parameter passed in is TRUE, then the control writer may take advantage of the optimizations described in the OC '96 specification for not fully cleaning up the DC on exit.  [Writers are still responsible for deleting GDI objects they create/load, but they may leave things selected in the DC on exit if this parameter is TRUE.  Please see the OC '96 specification for more information.]

Finally, the dvAspect parameter is passed in to the OnDraw routine.  This is used for multi-pass drawing, as described in the OC '96 specification [IViewObjectEx].  Authors not wishing to support multi-pass drawing should just ignore this parameter, as their control defaults to not supporting it.

   3.3  Handling Messages in a Control

Your control has a method called WindowProc, which is called whenever a message is sent to your control.  Your control should respond to messages in the desired fashion here.  If your control has an HWND, try to reduce the amount of work that is done in the WM_CREATE message handler, and see if it can't be put in BeforeCreateWindow().

For certain types of messages, such as keyboard messages for arrow keys, and other special keys, your WindowProc routine will not get called.  Instead, you'll find OnSpecialKey called instead.  The code in OnSpecialKey should look for WM_KEYDOWN/UP, WM_CHAR, and other messages and deal with them as appropriate.

There is a certain class of messages that typically involve notifying a window about happenings that are usually sent to a window's parent.  These include WM_COMMAND, WM_NOTIFY, WM_CTLCOLOR, etc.  These messages will be reflected by the host to your control in the form of OCM_COMMAND, OCM_NOTIFY, OCM_CTLCOLOR, etc.  Your controls should look for these messages instead of WM_COMMAND, etc.  Please see olectl.h for other OCM_ messages that the control might be interested in.  This, of course, does not apply t
o windowless controls.

   3.4  Adding a Property

One of the more important parts of a control is often the set of properties.   To add properties is a relatively straightforward and simple process.

First, you need to modify the primary dispatch interface for your control in the .ODL file.  For example, let's say you have a control called SuperScroll, and you'd like to add a LargeChange method to the control.  You'd add the following to the ISuperScroll interface description in the .ODL:

[id(DISPID_LARGECHANGE), propget, helpstring("The largechange property")]
HRESULT LargeChange([out, retval] long *plLargeChange);
[id(DISPID_LARGECHANGE), propput]
HRESULT LargeChange([in] long lLargeChange);

DISPID_LARGECHANGE is something that you define in dispids.h.  You then regenerate the type library (.TLB) file by typing:

nmake

More importantly, this regenerates SuperScrollInterfaces.H.  You can then cut and paste the following two lines from SuperScrollInterfaces.H:

STDMETHOD(get_LargeChange)(THIS_ long FAR* plLargeChange) PURE;
STDMETHOD(put_LargeChange)(THIS_ long lLargeChange) PURE;

Take these two lines and add them to your class description for CSuperScroll, and make sure that you remove the PURE declarators at the end; i.e.:

STDMETHOD(get_LargeChange)(long FAR* plLargeChange);
STDMETHOD(put_LargeChange)(long lLargeChange);

You can now implement these methods in your control file to implement your property.

Please note that there are a few standard dispids defined for you in olectl.h.  Whenever you want to declare a property, take a look in this header first to see if there is a standard dispid for it.

   3.5  Adding a Method

Adding a method is much like adding a property to your control.  First thing you need to do is define a dispid for the method.  Once you've got that, it's as simple as adding the method to the primary interface for your control.  Lets say we'd like to define a method called MooCow, with three parameters, the last of which is optional.  Here is one such method:

[id(DISPID_MOOCOW), helpstring("Makes your Cow moo")]
HRESULT MooCow([in] long lSeconds, [in] boolean fLowPitch,
[in, optional] VARIANT vPitch);

Again, as with properties, you regenerate the type library, and then paste the declaration into your control header (without the PURE declarator) and implement it.

   3.6  Adding an Event

In many situations, a control will want to fire an event.  For example, when a control gets a WM_?BUTTONDOWN message, it often makes sense to fire a MouseDown event.  This turns out to be easy.

The first thing that has to be done is the event has to be defined in an EVENTINFO structure.  There are many ways to do this, including declaring a new global variable for each event type, or using an array.  We will talk about the latter, since it's a little neater.  Let's say you want to have KeyDown, KeyUp, and KeyPushed events.

Here's how you might declare them:

typedef enum {
MyCtlEvent_KeyDown = 0,
MyCtlEvent_KeyUp = 1,
MyCtlEvent_KeyPushed = 2
} MYCTLEVENTS;

VARTYPE rgI2 [] = { VT_I2 };

EVENTINFO m_rgMyCtlEvents [] = {
{ DISPID_KEYDOWN, 1, rgI2 },
{ DISPID_KEYUP, 1, rgI2 },
{ DISPID_KEYPUSHED, 1, rgI2 }
};

The EVENTINFO structure has three members;  the dispid of the event, the count of arguments in the event, and a pointer to an array of VARTYPEs that describe the types of the parameters to the event.  Please note, again, that there are a bunch of dispids defined for you in olectl.h.  Whenever you're adding an event, go check there first to see if there's already a dispid for it.

To fire these events from code, just call the following:

FireEvent(&(m_rgMyCtlEvents[MyCtlEvent_KeyDown]), sKeyValue);

   3.7  Using Standard ActiveX Types

For many controls, you will find it useful to declare properties of types provided by OLE, such as Font, Picture, and Color.  Many hosts will detect properties of these types and put up convenient browsers for the user to select values for these types.

To declare a property of one of these types, you must first make sure their .ODL includes the following at the top:

importlib(STDTYPE_TLB);

Then, to declare a property of type Font, Picture, or Color, you would do something similar to the following, depending on the type:

[id(DISPID_FONT), propget]
HRESULT Font([out, retval] IFontDisp **ppFont);
[id(DISPID_FONT), propputref]
HRESULT Font([in] IFontDisp *pFont);

[id(DISPID_MOUSEICON), propget]
HRESULT MouseIcon([out, retval] IPictureDisp **ppMouseIcon);
[id(DISPID_MOUSEICON), propputref]
HRESULT MouseIcon([in] IPictureDisp *pMouseIcon);

[id(DISPID_FORECOLOR), propget]
HRESULT ForeColor([out, retval] OLE_COLOR *pocForeColor);
[id(DISPID_FORECOLOR), propput]
HRESULT ForeColor([in] OLE_COLOR ocForeColor);

For the get_ and put_ methods for these types, the control will get a property as declared above.  For fonts and pictures, the control will probably want to QI these for IFont and IPicture respectively.  See the FontColor control sample for an idea of how this is done.

MSDN contains some very good descriptions of how to use these fonts in your application, but the following is a short run-down.

To use a font object in your control, the control will typically call the get_hFont method, and pass the resulting HFONT to the control's DC.  Pictures will be much the same.

To use a color, the control will want to call OleTranslateColor to convert it to a real COLORREF.  OLE_COLORs are basically COLORREFs with some support for "generic" colors, such as COLOR_WINDOW, COLOR_WINDOWTEXT, etc.  To convert one of these into an OLE_COLOR, just OR (|) them with 0x80000000.  For example, to initialize the control's background to COLOR_WINDOW, set the backcolor property to COLOR_WINDOW | 0x80000000.  Then, to paint the backdrop, just call OleTranslateColor(), and use the resulting colo
rref.

NOTE:  The Framework uses dual/vtable bound automation interfaces, and uses OLE Automation functionality to support IDispatch methods on the automation objects. There is a known problem in OLE automation, which will cause problems (unexpected failures and/or crashes) when using the provided ITypeInfo::Invoke on properties that are declared to be of types that are imported from a type library (i.e., any font, picture, or color property has this problem.)

The way to get around this problem is to override Invoke(), and to look for the dispids of the control's properties that are of this type.  In this case, the control can quickly dispatch the call to the appropriate member function.  The FontColor sample does just this.  See its implementation of IDispatch::Invoke for sample code on how to work around it.  This problem will not exist in a future version of OLE Automation, but until then, the workaround is necessary -- but fortunately not terribly expensive.

   3.8 Throwing an Exception

Every once in a while, during an operation, your control will find itself rather upset with the state of the union, and will wish to communicate this to the user.

The way to do this is via an exception.  In any of the control's OLE Automation methods or property operators, the control can call the Exception method when exiting, and the method will set up all the appropriate information to trigger the error.

For example:

CMyControl::put_ButtZilla(long lButtZilla)
{
if (lButtZilla == 10)
return Exception(MYCTL_E_IHATETHENUMBER10, IDS_ERR_IHATETHENUMBER10, 0);
m_lButtZilla = lButtZilla;
return S_OK;
}

The arguments to the Exception routine are as the follows:  the first is the scode of the error the control wishes to trigger.  For errors unique to the control, you should define them something like:

#define MYCTL_E_IHATETHENUMBER10 MAKE_SCODE(SEVERITY_ERROR, FACILITY_CONTROL, 34500)

The second argument is the resource id of the string that the control should display.  The Exception code will correctly get this information from the control's localized satellite DLL.

Finally, the last argument is the helpcontextid that will be passed to the helpfile that is defined in your control's structure.

4.0 Persistence
=-------------=

One of the most important things your control will do is save out and restore its persistent state.  This will typically be done in one of two ways: through PropertyBags for text persistence, and through Streams for binary persistence.  In the latter, performance is absolutely critical to make your control load quickly.

The ActiveX Controls Framework requires that your control implement four member functions to support persistence.  The control is required to implement LoadTextState, LoadBinaryState, SaveTextState, and SaveBinaryState.  If you are positive that the control is never going to be in a host that uses IPersistPropertyBag, then you can ignore the two text interfaces, but this isn't recommended (they prove sufficiently straightforward to use).

   4.1  Text Persistence

Text persistence in the Framework is done via IPersistPropertyBag and IPropertyBag.  All ActiveX controls have an implementation of IPersistProperty Bag, and are given pointers to PropertyBag objects to do their work.

MSDN and Kraig Brockschmidt's book, "Inside OLE 2 (2nd edition)," both have descriptions of the IPropertyBag interface.  Effectively, there are two routines that the programmer will use:  Read and Write.  In both cases, the programmer will pass in a VARIANT.  In the Read case, the property, if it was saved out, will be put in the VARIANT.  If the property couldn't be found (it wasn't ever saved out), then the default value for that property should be used, and an error should probably not be returned.  For the Write, a VARIANT with the data is passed in.  To persist out a collection or an object (such as a Font or Picture object), the programmer can pass in a VT_UNKNOWN object, and the PropertyBag will then QI that object for IPersistPropertyBag or IPersistStream and persist it.  This actually proves effective for persisting collections -- they can just support IPersistPropertyBag.

All of the samples except the Localize and Circle sample have examples of how to persist out properties using PropertyBags.  For controls with many properties, it often makes sense to head to some sort of table-driven persistence to reduce code size and bug potential.

   4.2  Binary Persistence

Binary persistence turns out to be the more critical thing to work on when implementing an ActiveX control.  Control load speed can be severely hampered by a poorly written LoadBinaryState routine, so it's rather critical to spend some time thinking about how to keep this routine fast. The binary persistence code is used by many hosts all the time, and by other hosts when including the control in a generated executable file.

In both routines, the control is handed a pointer to an IStream object.  The key to load speed here is to reduce the number of operations on the stream.  For save, this is slightly less critical.

Typically, a control will want to save out the following information (often in the given order):

   - Some sort of header with a magic number, version, and size information
   - Fixed size state information, such as longs, floats, colors, strings, etc.
   - Variable sized persistent state, such as fonts, pictures, collections, etc.

Most control writers will want to start out the control's binary persistent state with some sort of header structure that includes a "magic" number that the control can check for sanity when the control is loading.  You'll also want to include some sort of version number so that future versions of your control can deal with older versions, and finally, you will often want the control to write out the number of bytes of data that were written.

The samples in the framework that have a binary persistent state use the following structure:

#define STREAMHDR_MAGIC    0x12345678

typedef struct {
DWORD dwMagic;
DWORD dwVersion;
DWORD cbSize;
} STREAMHDR;

The SaveBinaryState routine saves out this information, and the LoadBinaryState routine looks for it.

One way to write out all the fixed-size information (and therefore load it efficiently) is to do it all in one chunk -- if all the control's fixed persistent state is in a structure in the control object, then you can just write out the structure in the persistence code.   The sample controls show the properties grouped together in a m_state structure in the control declaration.

hr = pStream->Write(&m_state, sizeof(m_state), NULL);

For loading, it becomes as simple as:

hr = pStream->Read(&(m_state), sizeof(m_state), NULL);

Extremely efficient and simple.

For fonts and pictures, it's slightly more complicated.  Effectively, the control has to QI those objects for IPersistStream, and then call the Load or Save routine with the stream that the control has been given.  Typically, this can be done after the control has written out all other information.  The FontColor control sample does just this.

If you follow the above suggestions for persistent state structure, you can typically have the control's load routine look something like as follows:

IPersistStream *pps;
STREAMHDR sh;
HRESULT   hr;

// first read in the streamhdr, and make sure we like what we're getting

hr = pStream->Read(&sh, sizeof(sh), NULL);
RETURN_ON_FAILURE(hr);

// sanity check

if (sh.dwMagic != STREAMHDR_MAGIC || sh.cbSize != sizeof(m_state))
return E_UNEXPECTED;

// read in the control state information

hr = pStream->Read(&(m_state), sizeof(m_state), NULL);
RETURN_ON_FAILURE(hr);

// now read in the font!

OleCreateFontIndirect(&_fdDefault, IID_IFont, (void **)&m_pFont);
RETURN_ON_NULLALLOC(m_pFont);

// qi it for ipersiststream and load it in.

hr = m_pFont->QueryInterface(IID_IPersistStream, (void **)&pps);
RETURN_ON_FAILURE(hr);

hr = pps->Load(pStream);
pps->Release();

return hr;

This proves to be acceptably fast and robust.

5.0 Property Pages
=----------------=

Most ActiveX Controls will find property pages an invaluable addition to their design time functionality.  Fortunately, implementing them proves to be relatively straightforward.  To do so, you merely need to declare an object that inherits from CPropertyPage.

   5.1 Working with a Property Page

The control's property page is declared in the header file using the DEFINE_PROPERTYPAGE macro, which puts it into the g_ObjectInfo table.  The framework supports the creation of the property page object, but you are required to implement the static Create() function.

The property page is created much like a regular windows dialog box would be -- you use your favorite resource editor to create a DIALOG resource, and then just cut and paste it into your control's resource (.RC) file.

The most important method you'll have to implement will be the DialogProc method, which is where all the work will take place.  In addition to the regular windows messages that one would expect in a DialogProc, there are a few additional ones which people working with this framework will expect:

   a. PPM_NEWOBJECTS -- your control has been given some new objects.  The control is expected to go and populate its page's controls with information from this object.  Using the FirstControl() and NextControl() methods from the CPropertyPage class, the control can get the relevant information.

   b. PPM_APPLY -- the control has to apply any changes that have occurred now.  Again, you can use the FirstControl() and NextControl() routines to loop through all the objects for which the property pages were visible and apply the values (note that it's possible for there to be more than one object for which a property page is being displayed).

   c. PPM_EDITPROPERTY -- when the control is sent this message, the control is expected to set the focus to the control instance which represents the property of the given dispid.  You will typically only see this message called if you implement IPerPropertyBrowsing and return a value in MapPropertyToPage.

   d. PPM_FREEOBJECTS -- Various people will find it interesting to stash, in some way, the pointers they receive in PPM_NEWOBJECTS.  This message tells them that it is time to free them, as the objects are no longer valid.  This can be called from the property page's destructor, so people should be careful not to make too many assumptions about the property page.

Please see one of the sample controls for exact details on these messages.

   5.2 Navigating through Associated Objects

Your property pages will operate on one or more controls.  When initializing, you will typically get some values from the first control that you are given.  You can use the FirstControl() method to get the object pointer for this control.  You can then QI it for your primary dispatch interface to get properties to populate the page with.

When told to apply the values (PPM_APPLY), you'll want to apply them to all objects, which means you'll want to loop using FirstControl() and NextControl(), as follows:

for (pUnk = FirstControl(&dwCookie) ; pUnk; pUnk = NextControl(&dwCookie)) {
hr = pUnk->QueryInterface(IID_IButton, (void **)&pButton);
if (FAILED(hr)) continue;

GetDlgItemText(hwnd, IDC_CAPTION, szTmp, 128);
bstr = BSTRFROMANSI(szTmp);
ASSERT(bstr, "Maggots!");
pButton->put_Caption(bstr);
SysFreeString(bstr);
pButton->Release();
}

Please note that the return values of FirstControl() and NextControl() don't need to be Release()'d.

   5.3 Marking Your Page as Dirty.

It is moderately important to correctly mark the control's property page as dirty at the appropriate times.  This must be done manually.  Typically, the control will do this in response to a windows notification message, such as EN_CHANGE or BN_CLICKED.  When the control wishes to mark its page as dirty, the MakeDirty() routine should be called.  This will cause the Apply button to be enabled, if it was previously disabled, and will tell the host that the state should be saved before destroying the page.

The following code causes the page to mark itself as dirty when the user changes the text in a Text box in the property page:

case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDC_CAPTION:
if (HIWORD(wParam) == EN_CHANGE)
MakeDirty();
break;
}
break;

6.0  String Manipulation
=----------------------=

The ActiveX Controls Framework provides a robust system of macros for manipulating strings in pretty much all of the ways that you'll run into while working with an ActiveX control.

   6.1 Types of Strings

Under Win32, there are a few different types of strings, and understanding these tends to be pretty important when working with OLE, since there is great potential for memory leaks and bugs associated with strings.

There are two fundamental types of strings -- multi-byte (which can be ANSI or double byte) and Unicode strings.  Of the former, one almost always works with some sort of char * pointer (LPSTR, LPCSTR).  Of the latter, there are a few types that are commonly used.  Most notably, there are WCHAR * (LPWSTR, LPWCSTR), BSTR, and OLESTR strings.

LPWSTR pointers are just that -- a pointer to a wide string.  An LPOLESTR pointer is much the same, with some additional OLE rules added to it.  An OLESTR is merely a wide string, but when it's an out-parameter to a function, it should be allocated using the host's IMalloc allocator (i.e., CoTaskMemAlloc).

A BSTR is a string with a little more structure (specifically, a length prefix).  To work with BSTRs, you need to use special APIs designed exclusively for them, notably SysAllocString, SysFreeString, and SysStringLen (there are a few others.  See the "OLE Programmers Reference, Volume II," published by Microsoft Press, for more details).

These data types are fully interchangeable as far as compares and copies go, but they are NOT interchangeable as far as allocation and freeing go -- it is not acceptable to call SysFreeString on an OLESTR or LPWSTR string.

Both BSTRs and OLESTRs as in-parameters to functions should not be freed (as per standard OLE COM conventions).  By the same token, BSTRs and OLESTRs as out params should be expected to be freed, and should thus be allocated appropriately.

   6.2 Working with Strings

Now, the problem is that, for the most part, your controls will be working with multi-byte strings, except for where you work with OLE.  Therefore, there will be various scenarios where you'll either be given a wide string, and need the multi-byte version of it, or you'll have a multi-byte string, and need a wide string for it.

To solve these problems, the ActiveX Controls Framework includes the following macros to work with:

MAKE_WIDEPTR_FROMANSI(newstringname, convertme)
MAKE_ANSIPTR_FROMWIDE(newstringname, convertme)

BSTRFROMANSI(ansistr)
OLESTRFROMANSI(ansistr)
BSTRFROMRESID(resourceid)
OLESTRFROMRESID(resourceid)
COPYOLESTR(copyme)
COPYBSTR(copyme)

The first two macros will take a string of a given type and a name, and create a variable of the new name (do -not- declare a variable of this name yourself), and then convert the other string into the new variable.  This cannot be used as an rvalue in C/C++ expressions, nor can it be an lvalue (it pretty much needs to sit on a line by itself).

The last set of macros pretty much do all the remaining interesting work.  You can get BSTRs or IMalloc'd OLESTRs from an ANSI string, or copy OLESTRs and BSTRs.  The only additional functions of real interest are those that take a WORD which is a resource id, and loads in a string from your localization DLL (or the main if you don't do satellite localization) and makes either a BSTR or OLESTR out of it.  This proves useful in a few places where you need a localized string.

Remember that while these macros were designed with a certain amount of speed in mind, converting strings is still not a ridiculously cheap operation, and control writers should try to be moderately conservative in string conversions.

7.0  Localization
=---------------=

The ActiveX Controls Framework also has some support for robust localization of your control; most notably, property pages and anything other resources you find interesting to localize.

The scheme is follows:  the resources for the default language (typically English) are in the main in-proc server's resources.  Then, for each additional language supported, there is a satellite DLL which contains the resources for that language.  The name for this satellite DLL is generated by taking the "base" name for the DLL from a string resource in the main in-proc server file, and then adding on a three letter language code as generated by the function GetLocaleInfo with the LCTYPE LOCALE_SABBREVLAN
GNAME.

The code will first look for the specific language that is set up in the global variable g_lcidLocale. This should be initialized by all servers.  For ActiveX controls and property pages, you'll want to get this from the host.  For OLE automation servers, you'll want to set this up some other way, often by just using the system language.  If the satellite DLL for the specific langauge is not found, the framework will look for the primary language ID with SUBLANG_DEFAULT instead.  If that satellite is not f
ound, a handle to the default resources will then be returned.

Please note that fully localized type libraries are not supported, as most hosts will ignore these and just use the default one.

   7.1  Setting up for Localization

If you want to support satellite localization in your in-proc server, then you need to set up a couple of things.

First, you'll need to set the variable g_fSatelliteLocalization in your in-proc server file to TRUE.  This will instruct all further code paths to use satellite localization.

You will also, at some point, need to set up the g_lcidLocale variable, as described above.  For ActiveX controls, you can use GetAmbientProperty() and ask for AMBIENT_DISPID_LOCALE.  For property pages, there is a method on the IPropertyPageSite interface that will let you obtain the locale id, and for automation objects, you will be required to set this up yourself -- typically by obtaining the system locale id.

Whenever you want to load a resource, make sure you use GetResourceHandle() to get the instance handle for the localized resources.  This will generate the appropriate file name for the satellite DLL and attempt to load it.

Please see the Localize sample included with the framework for an example of how this works, along with an actual localized satellite DLL (in the French subdirectory). The satellite DLL should contain all localized resources you're interested in.

8.0 Miscellaneous
=---------------=

   8.1  Recommended Reading

Inside OLE2, Second Edition, Kraig Brockschmidt, Microsoft Press.  Chapters 3, 13-15 and 24 contain most of the information you'll need to work with an ActiveX control and/or automation server.  The chapters on persistence should also prove useful for those unfamiliar with them.

OLE2 Programmers Reference, Volume I and II, Microsoft Press.

MFC Source Code, Visual C++ 4.x.  When looking for ideas on how to do something, the CDK source code proves to be an invaluable resource.  Use it -- frequently.

   8.2 Host-Specific Notes

Then following are notes, things to consider, or known problems with specific ActiveX Controls hosts:

   8.2.1 Microsoft Access 95

Access 95 -requires- IPerPropertyBrowsing.  If you do not implement this interface, you will not able to put one of your controls on a form.  See MSDN and the MFC source code for documentation on this interface.

In addition, you cannot return error scodes from GetPredefinedStrings. You must return either S_OK or S_FALSE.  Returning an error scode will crash.

MapPropertyToPage should still return PERPROP_E_NOPAGEAVAILABLE if there are no pages available.


README.TXT

Playing Card Sample Control
===========================
This sample ActiveX Control implements a standard playing card, with the
ability to show the standard 52 card deck, two jokers, and 13 different
card backs.  This control is a Windowless control, which uses transparency
to create the missing card corner effect.

Building
--------
To build card.ocx, the FrameWrk sample must first be built. First run
\inetsdk\setenv.bat to be sure the ActiveX SDK include, bin, and lib
directories are included (first) in your environment. Run the 'nmake'
to build a debug version (built to the Debug subdirectory) or
'nmake nodebug=1' to build a retail (nodebug) directory into the
Retail subdirectory.

After building the FrameWrk library you can build the Card
control by running 'nmake' or 'nmake nodebug=1'.

Sample: ActiveX Video Poker
---------------------------
Included is a sample application which uses card.ocx: ActiveX
Video Poker.  To run this application, open poker\poker.htm.
This application also depends on the ielabel control and
ietimer control.

To debug and work with this example, open poker\main.htm.  This
file can be used outside of the framesets provided by poker.htm.

Sample: ActiveX Solitaire
-------------------------
Included is a sample application which uses card.ocx: ActiveX
Solitaire.  To run this application, open solitair\sol.htm.
This application also depends on the ietimer control and the
HTML Layout Control.

Using the Card Control
----------------------
The Card control is itself controlled through 3 properties:

Suite-- Enum:  0 = Invisible (card not drawn)
  1 = Clubs
  2 = Spades
  3 = Hearts
  4 = Diamonds
  -1 to -13 = Card Backs

Number-- Enum:  1 = Ace
  2 to 10 = Number Cards
  11 = Jack
  12 = Queen
  13 = King
  14 = Joker

Invert-- Boolean: controls drawing the card inverted,
      to indicate selection

CardAlignment   -- Enum:  0 = Top Left
  1 = Top Center
  2 = Top Right
  3 = Center Left
  4 = Center
  5 = Center Right
  6 = Bottom Left
  7 = Bottom Center
  8 = Bottom Right
  9 = Stretch

By default the card starts in the transparent state.  Setting the
Suite to any value other than zero will draw the card.

A property page is also included with the control, which can simply
picking the right property values.

The card control will also fire the following events:

Click-- Mouse Click
DblClick-- Mouse Double Click
MouseDown-- Mouse down
MouseUp-- Mouse up
MouseMove-- Mouse move

How it Works
------------
To keep the size of the control to a minimum, to minimize internet download
time, each card is created from the ground up.  The only resources included
in the control are the basic symbols needed to create the cards, one half
of each face cards center, and the card backs.  Where possible, monochrome
and compressed bitmaps are used.

The card control caches all card images after they have been created the
first time.  For games such as Solitaire in which the cards move
across one another, and most of the cards in a deck are displayed on the
screen at the same time, caching all the cards of course yields the best
animations.  To keep memory usage low, 4-bit DIB bitmaps are used to
store the cached images.

This control is a Windowless control, which uses transparency to create the
missing card corner effect.  When used with Internet Explorer, the control
is Windowed, and the ambient background color is used to fill in the corners.
When used in the HTML Layout Control, or another Windowless control host,
the corners are transparent and the card or background beneath can be seen.


CARDCTL.CPP

//=--------------------------------------------------------------------------=
// CardCtl.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
//
//
#include "IPServer.H"

#include "Guids.H"
#include "LocalObj.H"
#include "Util.H"
#include "Globals.H"
#include "Resource.H"
#include "cardctl.H"

#include "carddraw.h"

extern HBITMAP cardcacheMem[];
extern CRITICAL_SECTION drawCritSec;

// for ASSERT and FAIL
//
SZTHISFILE

WCHAR wszImage [] = L"Card";

//=--------------------------------------------------------------------------=
// all the events in this control
//
// TODO: add events here ...
//

static VARTYPE rgMouse[] = { VT_I2, VT_I2, VT_I4, VT_I4 };
enum { MouseMove=0, MouseDown, MouseUp, Click, DblClick };

static EVENTINFO rgEvents [] = {
    { DISPID_MOUSEMOVE, 4, rgMouse },
    { DISPID_MOUSEDOWN, 4, rgMouse },
    { DISPID_MOUSEUP, 4, rgMouse },
    { DISPID_CLICK, 0, NULL },
    { DISPID_DBLCLICK, 0, NULL },
};

//=--------------------------------------------------------------------------=
// array describing all of our property pages.  these clsids are typically
// in guids.h
//
// TODO: add any additional property page guids here ...
//
const GUID *rgCardPropPages [] = {
    &CLSID_CardGeneralPage
};

//=--------------------------------------------------------------------------=
// Custum Verb information
//
// TODO: add any custom verbs here in an array, using the VERBINFO structure.
//       then mark the controld def'n in CardCtl.H with
//       this verb array
//

//=--------------------------------------------------------------------------=
// CCardControl::CCardControl
//=--------------------------------------------------------------------------=
// "Being born is like being kidnapped.  And then sold into slavery."
//    - andy warhol (1928 - 87)
//
// Parameters:
//    IUnknown *        - [in]
//
// Notes:
//
#pragma warning(disable:4355)  // using 'this' in constructor
CCardControl::CCardControl
(
    IUnknown *pUnkOuter
)
: COleControl(pUnkOuter, OBJECT_TYPE_CTLCARD, (IDispatch *)this)
{
    static SIZEL size = { CCardDraw::CardSizeX, CCardDraw::CardSizeY };

    m_number = DefaultNumber;
    m_suite = DefaultSuite;
    m_invert = DefaultInvert;
    m_cardalignment = DefaultCardAlignment;
    BackBrush = NULL;

    SetControlSize( &size );

#if 0
// for better performance when the cards are first displayed (such as
// when a full deck is displayed to the user), un-ifdef this code.
// for better initial control creation time, keep this code ifdef'd.

    // initialize the standard 52 cards (no jokers, no backs)
    if( !cardcacheMem[0] )
    {
        EnterCriticalSection( &drawCritSec );
        if( !cardcacheMem[0] )
        {
            CCardDraw cd( CCardDraw::DrawAll );
            HDC drawDc;
            HBITMAP drawDcMem;
            int t;

            drawDc = CreateCompatibleDC( NULL );

            for( t = 0; t < 52; t++ )
            {
                cardcacheMem[t] = cd.CreateDIBCard( drawDc );
                drawDcMem = SelectObject( drawDc, cardcacheMem[t] );
                cd.DrawCard( drawDc, (t/14) + 1, (t%14) + 1 );
                SelectObject( drawDc, drawDcMem );
            }
            DeleteDC( drawDc );
        }
        LeaveCriticalSection( &drawCritSec );
    }
#endif
}
#pragma warning(default:4355)  // using 'this' in constructor

//=--------------------------------------------------------------------------=
// CCardControl::~CCardControl
//=--------------------------------------------------------------------------=
// "We all labour against our own cure, for death is the cure of all diseases"
//    - Sir Thomas Browne (1605 - 82)
//
// Notes:
//
CCardControl::~CCardControl ()
{
if( BackBrush )
DeleteObject( BackBrush );
}

//=--------------------------------------------------------------------------=
// CCardControl:RegisterClassData
//=--------------------------------------------------------------------------=
// register the window class information for your control here.
// this information will automatically get cleaned up for you on DLL shutdown.
//
// Output:
//    BOOL            - FALSE means fatal error.
//
// Notes:
//
BOOL CCardControl::RegisterClassData()
{
    WNDCLASS wndclass;

    // TODO: register any additional information you find interesting here.
    //       this method is only called once for each type of control
    //
    memset(&wndclass, 0, sizeof(WNDCLASS));
    wndclass.style          = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS | CS_OWNDC;
    wndclass.lpfnWndProc    = COleControl::ControlWindowProc;
    wndclass.hInstance      = g_hInstance;
    wndclass.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
    wndclass.lpszClassName  = WNDCLASSNAMEOFCONTROL(OBJECT_TYPE_CTLCARD);

    return RegisterClass(&wndclass);
}

//=--------------------------------------------------------------------------=
// CCardControl::InternalQueryInterface
//=--------------------------------------------------------------------------=
// qi for things only we support.
//
// Parameters:
// Parameters:
//    REFIID        - [in]  interface they want
//    void **       - [out] where they want to put the resulting object ptr.
//
// Output:
//    HRESULT       - S_OK, E_NOINTERFACE
//
// Notes:
//
HRESULT CCardControl::InternalQueryInterface
(
    REFIID  riid,
    void  **ppvObjOut
)
{
    IUnknown *pUnk;

    *ppvObjOut = NULL;

    // TODO: if you want to support any additional interrfaces, then you should
    // indicate that here.  never forget to call COleControl's version in the
    // case where you don't support the given interface.
    //
    if (DO_GUIDS_MATCH(riid, IID_ICard)) {
        pUnk = (IUnknown *)(ICard *)this;
    } else{
        return COleControl::InternalQueryInterface(riid, ppvObjOut);
    }

    pUnk->AddRef();
    *ppvObjOut = (void *)pUnk;
    return S_OK;
}

//=--------------------------------------------------------------------------=
// CCardControl::LoadTextState
//=--------------------------------------------------------------------------=
// load in our text state for this control.
//
// Parameters:
//    IPropertyBag *        - [in] property bag to read from
//    IErrorLog *           - [in] errorlog object to use with proeprty bag
//
// Output:
//    HRESULT
//
// Notes:
//    - NOTE: if you have a binary object, then you should pass an unknown
//      pointer to the property bag, and it will QI it for IPersistStream, and
//      get said object to do a Load()
//
STDMETHODIMP CCardControl::LoadTextState
(
    IPropertyBag *pPropertyBag,
    IErrorLog    *pErrorLog
)
{
    VARIANT v;
    HRESULT hr;

    VariantInit(&v);

    v.vt = VT_I2;
    hr = pPropertyBag->Read(L"Number",&v,pErrorLog);
    if (SUCCEEDED(hr))
        m_number = (enumCardNumber) v.iVal;
    ValidateNumber();
    VariantClear(&v);

    v.vt = VT_I2;
    hr = pPropertyBag->Read(L"Suite",&v,pErrorLog);
    if (SUCCEEDED(hr))
        m_suite = (enumCardSuite) v.iVal;
    ValidateSuite();
    VariantClear(&v);

    v.vt = VT_BOOL;
    hr = pPropertyBag->Read(L"Invert",&v,pErrorLog);
    if (SUCCEEDED(hr))
        m_invert = v.boolVal;
    VariantClear(&v);

    v.vt = VT_I2;
    hr = pPropertyBag->Read(L"CardAlignment",&v,pErrorLog);
    if (SUCCEEDED(hr))
        m_cardalignment = (enumCardAlignment) v.iVal;
    ValidateCardAlignment();
    VariantClear(&v);

    return S_OK;
}

#defineSTREAMHDR_MAGIC1296924267

//=--------------------------------------------------------------------------=
// CCardControl::LoadBinaryState
//=--------------------------------------------------------------------------=
// loads in our binary state using streams.
//
// Parameters:
//    IStream *            - [in] stream to write to.
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP CCardControl::LoadBinaryState
(
    IStream *pStream
)
{
    HRESULT hr;
    DWORD sh;

    hr = pStream->Read(&sh, sizeof(sh), NULL);
    RETURN_ON_FAILURE(hr);

    if (sh != STREAMHDR_MAGIC )
        return E_UNEXPECTED;

    hr = pStream->Read(&(m_number),sizeof(m_number),NULL);
    RETURN_ON_FAILURE(hr);
    ValidateNumber();

    hr = pStream->Read(&(m_suite),sizeof(m_suite),NULL);
    RETURN_ON_FAILURE(hr);
    ValidateSuite();

    hr = pStream->Read(&(m_invert),sizeof(m_invert),NULL);
    RETURN_ON_FAILURE(hr);

    hr = pStream->Read(&(m_cardalignment),sizeof(m_cardalignment),NULL);
    RETURN_ON_FAILURE(hr);
    ValidateCardAlignment();

    return S_OK;
}

//=--------------------------------------------------------------------------=
// CCardControl::SaveTextState
//=--------------------------------------------------------------------------=
// saves out the text state for this control using a property bag.
//
// Parameters:
//    IPropertyBag *        - [in] the property bag with which to work.
//    BOOL                  - [in] if TRUE, then write out ALL properties, even
//                            if they're their the default value ...
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP CCardControl::SaveTextState
(
    IPropertyBag *pPropertyBag,
    BOOL          fWriteDefaults
)
{
    VARIANT v;
    HRESULT hr;

    if( m_number != DefaultNumber )
    {
        VariantInit(&v);
        v.vt = VT_I2;
        v.iVal = m_number;
        hr = pPropertyBag->Write( L"Number", &v);
    }

    if( m_suite != DefaultSuite )
    {
        VariantInit(&v);
        v.vt = VT_I2;
        v.iVal = m_suite;
        hr = pPropertyBag->Write( L"Suite", &v);
    }

    if( m_invert != DefaultInvert )
    {
        VariantInit(&v);
        v.vt = VT_BOOL;
        v.boolVal = m_invert;
        hr = pPropertyBag->Write( L"Invert", &v);
    }

    if( m_cardalignment != DefaultCardAlignment )
    {
        VariantInit(&v);
        v.vt = VT_I2;
        v.iVal = m_cardalignment;
        hr = pPropertyBag->Write( L"CardAlignment", &v);
    }

    return S_OK;
}

//=--------------------------------------------------------------------------=
// CCardControl::SaveBinaryState
//=--------------------------------------------------------------------------=
// save out the binary state for this control, using the given IStream object.
//
// Parameters:
//    IStream  *             - [in] save to which you should save.
//
// Output:
//    HRESULT
//
// Notes:
//    - it is important that you seek to the end of where you saved your
//      properties when you're done with the IStream.
//
STDMETHODIMP CCardControl::SaveBinaryState
(
    IStream *pStream
)
{
    HRESULT hr;
    DWORD sh = STREAMHDR_MAGIC;

    // write out the stream hdr.
    //
    hr = pStream->Write(&sh, sizeof(sh), NULL);
    RETURN_ON_FAILURE(hr);

    hr = pStream->Write(&(m_number),sizeof(m_number),NULL);
    RETURN_ON_FAILURE(hr);

    hr = pStream->Write(&(m_suite),sizeof(m_suite),NULL);
    RETURN_ON_FAILURE(hr);

    hr = pStream->Write(&(m_invert),sizeof(m_invert),NULL);
    RETURN_ON_FAILURE(hr);

    hr = pStream->Write(&(m_cardalignment),sizeof(m_cardalignment),NULL);
    RETURN_ON_FAILURE(hr);

    return S_OK;
}

//=--------------------------------------------------------------------------=
// CCardControl::OnDraw
//=--------------------------------------------------------------------------=
// "I don't very much enjoy looking at paintings in general.  i know too
//  much about them.  i take them apart."
//    - georgia o'keeffe (1887-1986)
//
// Parameters:
//    HDC                - [in]  HDC to draw to
//    LPCRECTL           - [in]  rect we're drawing to
//    LPCRECTL           - [in]  window extent and origin for meta-files
//    HDC                - [in]  HIC for target device
//
// Output:
//    HRESULT
//
// Notes:
//
HRESULT CCardControl::OnDraw
(
    DWORD    dvaspect,
    HDC      hdcDraw,
    LPCRECTL prcBounds,
    LPCRECTL prcWBounds,
    HDC      hicTargetDevice,
    BOOL     fOptimize
)
{
    int OffsetX, OffsetY;
    RECTL rcBoundsDP;
    int width, height;
    int savedDC;
    HDC drawDc;
    HBITMAP drawDcMem;
    int c;

    if( !Windowless() && !BackBrush )
    {
        OLE_COLOR BackColor;
        COLORREF BackColorCR;
        if( !GetAmbientProperty( DISPID_AMBIENT_BACKCOLOR, VT_I4, &BackColor ) )
                BackColor = 0;
        OleTranslateColor( BackColor, NULL, &BackColorCR );
        BackBrush = CreateSolidBrush( BackColorCR );
    }

    rcBoundsDP = *prcBounds;
    LPtoDP( hdcDraw, (POINT *) &rcBoundsDP, 2 );

    width = rcBoundsDP.right - rcBoundsDP.left;
    height = rcBoundsDP.bottom - rcBoundsDP.top;

    switch( m_cardalignment )
    {
        case CardTopCenter:
        case CardCenter:
        case CardBottomCenter:
                OffsetX = (width - CCardDraw::CardSizeX) / 2;
                break;
        case CardTopRight:
        case CardCenterRight:
        case CardBottomRight:
                OffsetX = (width - CCardDraw::CardSizeX);
                break;
        default:
                OffsetX = 0;
                break;
    }
    switch( m_cardalignment )
    {
         case CardCenterRight:
        case CardCenter:
        case CardCenterLeft:
                OffsetY = (height - CCardDraw::CardSizeY) / 2;
                break;
        case CardBottomRight:
        case CardBottomCenter:
        case CardBottomLeft:
                OffsetY = (height - CCardDraw::CardSizeY);
                break;
        default:
                OffsetY = 0;
                break;
    }

    if( BackBrush )
        FillRect( hdcDraw, (RECT *) prcBounds, BackBrush );

    if( m_suite == CardBlank )
        return S_OK;

    savedDC = SaveDC( hdcDraw );

    SetMapMode( hdcDraw, MM_TEXT );
    SetWindowOrgEx( hdcDraw, -(OffsetX + rcBoundsDP.left),
                             -(OffsetY + rcBoundsDP.top), NULL );
    SetViewportOrgEx( hdcDraw, 0, 0, NULL );

    if( width < CCardDraw::CardSizeX || height < CCardDraw::CardSizeY )
    {
        HRGN clipRgn;
        clipRgn = CreateRectRgn( rcBoundsDP.left,
                                 rcBoundsDP.top,
                                 rcBoundsDP.left + width,
                                 rcBoundsDP.top + height );
        SelectClipRgn( hdcDraw, clipRgn );
        DeleteObject( clipRgn );
    }

    SelectObject( hdcDraw, GetStockObject( m_invert ? WHITE_PEN : BLACK_PEN ) );

    drawDc = CreateCompatibleDC( NULL );

    if( m_suite > 0 )
        c = (m_suite-1)*14 + (m_number-1);
    else
        c = (m_suite < -12) ? 56 : -m_suite + 56;

    if( !cardcacheMem[c] )
    {
        CCardDraw cd( (c > 55 ? CCardDraw::DrawBack : (int) m_number) );

        EnterCriticalSection( &drawCritSec );
        if( !cardcacheMem[c] )
        {
            cardcacheMem[c] = cd.CreateDIBCard( hdcDraw );
            drawDcMem = (HBITMAP) SelectObject( drawDc, cardcacheMem[c] );
            cd.DrawCard( drawDc, m_suite, m_number );
        }
        else
                drawDcMem = (HBITMAP) SelectObject( drawDc, cardcacheMem[c] );
        LeaveCriticalSection( &drawCritSec );
    }
    else
        drawDcMem = (HBITMAP) SelectObject( drawDc, cardcacheMem[c] );

    if( m_cardalignment != CardStretch )
    {
        BitBlt( hdcDraw, 1, 1, CCardDraw::CardSizeX-2, CCardDraw::CardSizeY-2,
                drawDc, 1, 1, m_invert ? NOTSRCCOPY : SRCCOPY );
        Polyline( hdcDraw, CCardDraw::CardBorder,
                  sizeof(CCardDraw::CardBorder)/sizeof(POINT) );
    }
    else
    {
        POINT border[9];
        memcpy( border, CCardDraw::CardBorder, sizeof(border) );
        border[2].x = border[5].x = width-3;
        border[3].x = border[4].x = width-1;
        border[4].y = border[7].y = height-3;
        border[5].y = border[6].y = height-1;
        StretchBlt( hdcDraw, 1, 1, width-2, height-2,
                    drawDc, 1, 1,
                    CCardDraw::CardSizeX-2,
                    CCardDraw::CardSizeY-2,
                    m_invert ? NOTSRCCOPY : SRCCOPY );
        Polyline( hdcDraw, border, sizeof(border)/sizeof(POINT) );
    }

    RestoreDC( hdcDraw, savedDC );

    SelectObject( drawDc, drawDcMem );
    DeleteDC( drawDc );

    return S_OK;
}

//=--------------------------------------------------------------------------=
// CCardControl::WindowProc
//=--------------------------------------------------------------------------=
// window procedure for this control.  nothing terribly exciting.
//
// Parameters:
//     see win32sdk on window procs.
//
// Notes:
//
LRESULT CCardControl::WindowProc
(
    UINT   msg,
    WPARAM wParam,
    LPARAM lParam
)
{
    POINTS pt;
    RECT rect;
    int b, s;
    int ev = -1;

    switch( msg )
    {
    case WM_MOUSEMOVE:
            ev = MouseMove;
            // FALL THROUGH!
    case WM_LBUTTONDOWN:
    case WM_MBUTTONDOWN:
    case WM_RBUTTONDOWN:
            if( ev == -1 )  ev = MouseDown;
            // FALL THROUGH!
    case WM_LBUTTONUP:
    case WM_MBUTTONUP:
    case WM_RBUTTONUP:
            if( ev == -1 )  ev = MouseUp;
            pt = MAKEPOINTS( lParam );
            OcxGetWindowRect( &rect );
            b = (wParam & 3) == 3 ? 4 : wParam & 3;
            s = (wParam >> 2) & 7;
            FireEvent( &rgEvents[ev], b, s, pt.x-rect.left, pt.y-rect.top );
            if( msg == WM_LBUTTONUP )
                    FireEvent( &rgEvents[Click] );
            break;
    case WM_LBUTTONDBLCLK:
            FireEvent( &rgEvents[DblClick] );
            break;
    }

    return OcxDefWindowProc(msg, wParam, lParam);
}

//=--------------------------------------------------------------------------=
// CCardControl::AboutBox
//=--------------------------------------------------------------------------=
// prints up an about box.  fweeeee.
//
// Notes:
//
void CCardControl::AboutBox
(
    void
)
{
    // TODO: Ideally, one would use DialogBox, and some sort of Dialog Box here if
    // they wanted a slightly more interesting About Box ...  you should
    // still call ModalDialog first, however.
    //
    ModalDialog(TRUE);
    MessageBox(NULL, "Microsoft ActiveX Card Control\n\n"
                     "Copyright 1996 - 1998 Microsoft Corp.",
                     "About Card",
                     MB_OK | MB_TASKMODAL);
    ModalDialog(FALSE);
}

STDMETHODIMP  CCardControl::get_Number(enumCardNumber * number)
{
    CHECK_POINTER(number);

    *number = m_number;

    return( S_OK );
}

STDMETHODIMP  CCardControl::put_Number(enumCardNumber number)
{
    if( m_number == number )
            return S_OK;

    m_number = number;
    ValidateNumber();
    if( m_suite >= 1 )
    {
            if( Windowless() )
            {
                    RECT rect;
                    OcxGetWindowRect( &rect );
                    OcxInvalidateRect( &rect, TRUE );
            }
            else
                    InvalidateControl( NULL );
    }

    // update anybody who cares about property changes and mark ourselves
    // as dirty
    //
    PropertyChanged(DISPID_NUMBER);
    m_fDirty = TRUE;

return S_OK;
}

STDMETHODIMP  CCardControl::get_Invert( VARIANT_BOOL *invert )
{
    CHECK_POINTER(invert);

    *invert = m_invert;

    return( S_OK );
}

STDMETHODIMP  CCardControl::put_Invert( VARIANT_BOOL invert )
{
    if( m_invert == invert )
            return S_OK;

    m_invert = invert;

    {
            if( Windowless() )
            {
                    RECT rect;
                    OcxGetWindowRect( &rect );
                    OcxInvalidateRect( &rect, TRUE );
            }
            else
                    InvalidateControl( NULL );
    }

    // update anybody who cares about property changes and mark ourselves
    // as dirty
    //
    PropertyChanged(DISPID_INVERT);
    m_fDirty = TRUE;

    return S_OK;
}

STDMETHODIMP  CCardControl::get_CardAlignment( enumCardAlignment *cardalignment )
{
    CHECK_POINTER(cardalignment);

    *cardalignment = m_cardalignment;

    return( S_OK );
}

STDMETHODIMP  CCardControl::put_CardAlignment( enumCardAlignment cardalignment )
{
     if( m_cardalignment == cardalignment )
            return S_OK;

    m_cardalignment = cardalignment;
    ValidateCardAlignment();

    {
        if( Windowless() )
        {
            RECT rect;
            OcxGetWindowRect( &rect );
            OcxInvalidateRect( &rect, TRUE );
        }
        else
                InvalidateControl( NULL );
    }

    // update anybody who cares about property changes and mark ourselves
    // as dirty
    //
    PropertyChanged(DISPID_CARDALIGNMENT);
    m_fDirty = TRUE;

    return S_OK;
}

STDMETHODIMP  CCardControl::get_Suite(enumCardSuite * suite)
{
    CHECK_POINTER(suite);

    *suite = m_suite;

    return( S_OK );
}

STDMETHODIMP  CCardControl::put_Suite(enumCardSuite suite)
{
    if( m_suite == suite )
            return S_OK;

    m_suite = suite;
    ValidateSuite();

    {
        if( Windowless() )
        {
            RECT rect;
            OcxGetWindowRect( &rect );
            OcxInvalidateRect( &rect, TRUE );
        }
        else
                InvalidateControl( NULL );
    }

    // update anybody who cares about property changes and mark ourselves
    // as dirty
    //
    PropertyChanged(DISPID_SUITE);
    m_fDirty = TRUE;

    return S_OK;
}

//=--------------------------------------------------------------------------=
// CCardControl::Create
//=--------------------------------------------------------------------------=
// global static function that creates an instance of the control an returns
// an IUnknown pointer for it.
//
// Parameters:
//    IUnknown *        - [in] controlling unknown for aggregation
//
// Output:
//    IUnknown *        - new object.
//
// Notes:
//
IUnknown *CCardControl::Create
(
    IUnknown *pUnkOuter
)
{
    // make sure we return the private unknown so that we support aggegation
    // correctly!
    //
    CCardControl *pNew = new CCardControl(pUnkOuter);
    return pNew->PrivateUnknown();
}


CARDCTL.H

//=--------------------------------------------------------------------------=
// CardCtl.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// class declaration for the Card control.
//
#ifndef _CARDCONTROL_H_

#include "IPServer.H"
#include "CtrlObj.H"
#include "CardInterfaces.h"
#include "Dispids.H"

//=--------------------------------------------------------------------------=
// CCARDControl
//=--------------------------------------------------------------------------=
// our control.
//
class CCardControl :       public COleControl,
                           public ICard,
     public ISupportErrorInfo {

  public:
    // IUnknown methods
    //
    DECLARE_STANDARD_UNKNOWN();

    // IDispatch methods
    //
    DECLARE_STANDARD_DISPATCH();

    // ISupportErrorInfo methods
    //
    DECLARE_STANDARD_SUPPORTERRORINFO();

    // ICARD methods
    //
    // TODO: copy over the method declarations from CARDInterfaces.H
    //       don't forget to remove the PURE from them.
    //
    STDMETHOD(get_Number)(THIS_ enumCardNumber * number) ;
    STDMETHOD(put_Number)(THIS_ enumCardNumber number) ;
    STDMETHOD(get_Suite)(THIS_ enumCardSuite * suite) ;
    STDMETHOD(put_Suite)(THIS_ enumCardSuite suite) ;
    STDMETHOD(get_Invert)(THIS_ VARIANT_BOOL * invert) ;
    STDMETHOD(put_Invert)(THIS_ VARIANT_BOOL invert) ;
    STDMETHOD(get_CardAlignment)(THIS_ enumCardAlignment * align) ;
    STDMETHOD(put_CardAlignment)(THIS_ enumCardAlignment align) ;
    STDMETHOD_(void, AboutBox)(THIS) ;

    // OLE Control stuff follows:
    //
    CCardControl(IUnknown *pUnkOuter);
    virtual ~CCardControl();

    // static creation function.  all controls must have one of these!
    //
    static IUnknown *Create(IUnknown *);

  private:
    // overridables that the control must implement.
    //
    STDMETHOD(LoadBinaryState)(IStream *pStream);
    STDMETHOD(SaveBinaryState)(IStream *pStream);
    STDMETHOD(LoadTextState)(IPropertyBag *pPropertyBag, IErrorLog *pErrorLog);
    STDMETHOD(SaveTextState)(IPropertyBag *pPropertyBag, BOOL fWriteDefault);
    STDMETHOD(OnDraw)(DWORD dvaspect, HDC hdcDraw, LPCRECTL prcBounds, LPCRECTL prcWBounds, HDC hicTargetDev, BOOL fOptimize);
    virtual LRESULT WindowProc( UINT msg, WPARAM wParam, LPARAM lParam);
    virtual BOOL    RegisterClassData(void);

    virtual HRESULT InternalQueryInterface(REFIID, void **);

    // private state information.
    //
HRESULT DrawMe( HDC hdcMem );
void DrawBack(void);

// Actual properties
enumCardNumberm_number;
#define DefaultNumber CardAce
void ValidateNumber()
{
if( m_number < CardAce ) m_number = CardAce;
if( m_number > CardJoker ) m_number = CardJoker;
}

enumCardSuite m_suite;
#define DefaultSuite CardBlank
void ValidateSuite()
{
if( m_suite > CardDiamond ) m_suite = CardDiamond;
}

VARIANT_BOOL m_invert;
enum { DefaultInvert = VARIANT_FALSE };

enumCardAlignment   m_cardalignment;
#define DefaultCardAlignment CardCenter
void ValidateCardAlignment()
{
if( m_cardalignment < CardTopLeft ) m_cardalignment = CardTopLeft;
if( m_cardalignment > CardStretch ) m_cardalignment = CardStretch;
}

HBRUSH BackBrush;
};

// TODO: if you have an array of verbs, then add an extern here with the name
//       of it, so that you can include it in the DEFINE_CONTROLOBJECT.
//       ie.  extern VERBINFO m_CARDCustomVerbs [];
//
extern const GUID    *rgCardPropPages [];
DEFINE_WINDOWLESSCONTROLOBJECT(Card,
    &CLSID_Card,
    "ActiveXCardControl",
    CCardControl::Create,
    1,
    &IID_ICard,
    "CARD.HLP",
    &DIID_DCardEvents,
    OLEMISC_SETCLIENTSITEFIRST|OLEMISC_ACTIVATEWHENVISIBLE|
OLEMISC_RECOMPOSEONRESIZE|OLEMISC_CANTLINKINSIDE|OLEMISC_INSIDEOUT,
FALSE,
FALSE,
    RESID_TOOLBOX_BITMAP,
    "CARDWndClass",
    1,
    rgCardPropPages,
    0,
    NULL);

#define _CARDCONTROL_H_
#endif // _CARDCONTROL_H_


CARDDRAW.CPP

//=--------------------------------------------------------------------------=
// CardDraw.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1996 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
//

#include <windows.h>

extern HINSTANCE g_hInstance;

#include "Resource.H"
#include "carddraw.h"

const POINT CCardDraw::CardBorder[9] =
{ { 0, 2 }, { 2, 0 },
  { CardSizeX-3, 0 },
  { CardSizeX-1, 2 },
  { CardSizeX-1, CardSizeY-3 },
  { CardSizeX-3, CardSizeY-1 },
  { 2, CardSizeY-1 },
  { 0, CardSizeY-3 }, { 0, 2 } };

const RECT CCardDraw::CardRect = { 0, 0, CardSizeX, CardSizeY };

CCardDraw::CCardDraw( int number )
{
symsDc = symsiDc = centerDc = jokerDc = NULL;

// Symbols bitmaps, used by all cards
if( number != DrawBack )
{
symsDc = CreateCompatibleDC( NULL );
symsMem = (HBITMAP) SelectObject( symsDc,
LoadBitmap( g_hInstance, MAKEINTRESOURCE(IDB_SYMS) ) );
symsiDc = CreateCompatibleDC( symsDc );
symsiMem = (HBITMAP) SelectObject( symsiDc,
CreateCompatibleBitmap( symsiDc, SymsSizeX, SymsSizeY ) );
StretchBlt( symsiDc, SymsSizeX-1, SymsSizeY-1, -SymsSizeX, -SymsSizeY,
symsDc, 0, 0, SymsSizeX, SymsSizeY, SRCCOPY );
}

// faces, used > 10
if( number > 10 && number != 14 )
{
centerDc = CreateCompatibleDC( NULL );
centerMem = (HBITMAP) SelectObject( centerDc,
LoadBitmap( g_hInstance, MAKEINTRESOURCE(IDB_FACES)) );
}

// joker
if( number >= 14 )
{
jokerDc = CreateCompatibleDC( NULL );
jokerMem = (HBITMAP) SelectObject( jokerDc,
     LoadBitmap( g_hInstance, MAKEINTRESOURCE(IDB_JOKER) ) );
}
}

CCardDraw::~CCardDraw()
{
HBITMAP tempMem;

#define freeDc( s ) \
       if( s##Dc ) \
  { tempMem = (HBITMAP) SelectObject( s##Dc, s##Mem ); \
    DeleteObject( tempMem ); \
    DeleteDC( s##Dc ); }

freeDc( syms );
freeDc( symsi );
freeDc( joker );
freeDc( center );

#undef freeDc
}

BOOL CCardDraw::DrawCard( HDC hdcMem, int suite, int number )
{
int LocX, LocY;

const static struct { unsigned char x, y; } LargeSymLoc[10][10] =
{ { {29,42} },
  { {29,11}, {29,86} },
  { {29,11}, {29,42}, {29,86} },
  { {16,11}, {42,11}, {16,86}, {42,86} },
  { {16,11}, {42,11}, {16,86}, {42,86}, {29,42} },
  { {16,11}, {42,11}, {16,86}, {42,86}, {16,42}, {42,42} },
  { {16,11}, {42,11}, {16,86}, {42,86}, {16,42}, {42,42}, {29,26} },
  { {16,11}, {42,11}, {16,86}, {42,86}, {16,32}, {42,32}, {16,65}, {42,65} },
  { {16,11}, {42,11}, {16,86}, {42,86}, {16,32}, {42,32}, {16,65}, {42,65}, {29,42} },
  { {16,11}, {42,11}, {16,86}, {42,86}, {16,32}, {42,32}, {16,65}, {42,65}, {29,21}, {29,76} } };

const static signed char LargeSymLoc9[] = { -2, -2, 3, 0 };

// Macros for working with the Sym DC's
#define SymBitBltTop( x, y, dx, dy, sx, sy, copy ) \
BitBlt( hdcMem, x, y, dx, dy, symsDc, sx, sy, copy );
#define SymBitBltBottom( x,y, dx, dy, sx, sy, copy ) \
BitBlt( hdcMem, CardSizeX - (x) - (dx), CardSizeY - (y) - (dy), \
dx, dy, symsiDc,\
SymsSizeX - (sx) - (dx), SymsSizeY - (sy) - (dy), copy );
#define SymBitBlt( x, y, dx, dy, sx, sy, copy ) \
{ SymBitBltTop( x, y, dx, dy, sx, sy, copy ); \
  SymBitBltBottom( x, y, dx, dy, sx, sy, copy ); }
#define SymBitBltBottomNoTransX( x,y, dx, dy, sx, sy, copy ) \
BitBlt( hdcMem, x, y - (dy), \
dx, dy, symsiDc,\
SymsSizeX - (sx) - (dx), SymsSizeY - (sy) - (dy), copy );

if( suite < 0 )
return( DrawCardBack( hdcMem, -suite ) );

// white middle
FillRect( hdcMem, &CardRect, (HBRUSH)GetStockObject(WHITE_BRUSH) );

// set background color for monochrome bitmap blt's
SetTextColor( hdcMem, RGB((suite >= 3 ? 255:0),0,0) );

// Joker
if( number == 14 )
{
static const RECT JokerCornerRect =
{ JokerCornerRectSX, JokerCornerRectSY,
  CardSizeX - JokerCornerRectEX, CardSizeY - JokerCornerRectEY };

SymBitBlt( JokerCornerX, JokerCornerY,
   JokerCornerSizeX, JokerCornerSizeY,
   JokerCornerSrcX, JokerCornerSrcY, SRCCOPY );

FillRect( hdcMem, &JokerCornerRect,
  (HBRUSH)GetStockObject(WHITE_BRUSH) );

BitBlt( hdcMem, JokerX, JokerY,JokerSizeX, JokerSizeY,
               jokerDc, 0, 0, SRCCOPY) ;
}

if( number < 14 )
{
long SrcY, SrcX;

// fill number
LocX = (number > 10 ? 2 : 3 );
SrcY = (number & 0x8 ? NumberSizeY : 0);
SrcX = (number & 0x8 ? (number-8) : (number-1));
SymBitBlt( LocX, NumberY,
   NumberSizeX,
   NumberSizeY + ((number & 0x8) >> 2),
   NumberSrcX + SrcX * NumberSizeX,
   NumberSrcY + SrcY,
   SRCCOPY );

// Small symbols
SymBitBlt( LocX, SmallSymY,
 SmallSymSizeX, SmallSymSizeY,
 SmallSymSrcX + (suite-1) * SmallSymSizeX,
 SmallSymSrcY, SRCCOPY );
}

// center of card, symbols
if( number <= 10 )
{
for( int i = 0; i < number; i++ )
{
LocX = LargeSymLoc[number-1][i].x;
LocY = LargeSymLoc[number-1][i].y;
if( number == 9 && i == 8 )// adjustment for 9
LocY += LargeSymLoc9[suite-1];
if( LocY <= CardSizeY/2 )
{
SymBitBltTop( LocX, LocY-1,
LargeSymSizeX, LargeSymSizeY,
LargeSymSrcX + (4-suite)*LargeSymSizeX,
LargeSymSrcY, SRCAND );
}
else
{
SymBitBltBottomNoTransX( LocX, LocY,
LargeSymSizeX, LargeSymSizeY,
LargeSymSrcX + (4-suite)*LargeSymSizeX,
LargeSymSrcY, SRCAND );
}
}
}

// center of card, face
if( number > 10 && number < 14 )
{
HPEN oldPen;
static const POINT centerBorder[] =
{ { PictX, PictY }, { PictX, PictY+PictSizeY+1 },
  { PictX+PictSizeX+1, PictY+PictSizeY+1 },
  { PictX+PictSizeX+1, PictY }, { PictX, PictY } };

BitBlt( hdcMem, PictX+1, PictY+1,
                PictSizeX, PictSizeY/2,
                centerDc,
                (number-11)*PictSizeX,
                (suite-1)*PictSizeY/2,
                SRCCOPY) ;
StretchBlt( hdcMem, PictX+PictSizeX, PictY+PictSizeY,
-PictSizeX, -PictSizeY/2,
centerDc,
                (number-11)*PictSizeX,
                (suite-1)*PictSizeY/2,
PictSizeX, PictSizeY/2, SRCCOPY );
oldPen = (HPEN) SelectObject( hdcMem, GetStockObject( BLACK_PEN ) );
Polyline( hdcMem, centerBorder, sizeof(centerBorder)/sizeof(POINT) );
SelectObject( hdcMem, oldPen );
}

return( TRUE );
}

BOOL CCardDraw::DrawCardBack( HDC hdcMem, int backnum )
{
if( backnum >= 1 && backnum <= 12 )
{
HDC backDc;
HBITMAP backMem;
if( !(backDc = CreateCompatibleDC( NULL )) )
return( FALSE );
if( !(backMem = (HBITMAP) SelectObject( backDc,
LoadBitmap( g_hInstance,
MAKEINTRESOURCE(backnum+IDB_BACKBASE)))) )
{
DeleteDC( backDc );
return( FALSE );
}
BitBlt( hdcMem, 1, 1, CardSizeX-2, CardSizeY-2,
         backDc, 0, 0, SRCCOPY );
DeleteObject( backMem );
DeleteDC( backDc );
}
else
{
FillRect( hdcMem, &CardRect,
  (HBRUSH)GetStockObject(WHITE_BRUSH) );

for( int x = 1; x < CardSizeX-1; x++ )
    for( int y = 1; y < CardSizeY-1; y++ )
if( ((x+y-2) & 3) < 2 )
SetPixelV( hdcMem, x, y, RGB( 0,0,128 ) );
}

return( TRUE );
}

HBITMAP CCardDraw::CreateDIBCard( HDC drawDc )
{
void *bits;
static struct {
BITMAPINFOHEADER bi;
long colors[16];
} b = { { sizeof(b.bi),
  (((CardSizeX + 3)>>2)<<2),
  CardSizeY, 1, 4, BI_RGB, 0, 0, 0, 16, 16 },
{ 0x000000, 0x800000, 0x008000, 0x808000,
  0x000080, 0x800080, 0x808080, 0xc0c0c0,
  0x808080, 0xff0000, 0x00ff00, 0xffff00,
          0x0000ff, 0xff00ff, 0x00ffff, 0xffffff } };

return CreateDIBSection( drawDc, (BITMAPINFO *) &b,
 DIB_RGB_COLORS,
 &bits, NULL, 0 );
}


CARDDRAW.H

//=--------------------------------------------------------------------------=
// CardDraw.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1996 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
//

class CCardDraw {
public:
CCardDraw( int number );
~CCardDraw();
BOOL DrawCard( HDC hdcMem, int suite, int number );
BOOL DrawCardBack( HDC hdcMem, int backnum );
HBITMAP CreateDIBCard( HDC drawDc );

// size of the card
enum { CardSizeX = 71, CardSizeY = 96 };

// constants to use in constructor
enum { DrawBack = -1, DrawAll = 15 };

// rect for filling in the card blank,
// POINT array for drawing the border with PolyLine()
static const POINT CardBorder[9];
static const RECT CardRect;

private:
// DC's for drawing, Mem's hold old DC bitmap contents
HDC symsDc, symsiDc;
HBITMAP symsMem, symsiMem;
HDC centerDc;
HBITMAP centerMem;
HDC jokerDc;
HBITMAP jokerMem;

// constants for locations of card elements
enum { SymsSizeX = 73, SymsSizeY = 51 };

enum { JokerCornerSizeY = 49, JokerCornerSizeX = 38 };
enum { JokerCornerSrcX = 0, JokerCornerSrcY = 0 };
enum { JokerCornerY = 5, JokerCornerX = 4 };
enum { JokerCornerRectSX = 13, JokerCornerRectSY = 15 };
enum { JokerCornerRectEX = 13, JokerCornerRectEY = 15 };

enum { LargeSymSrcX = 9, LargeSymSrcY = 36 };
enum { LargeSymSizeX = 15, LargeSymSizeY = 15 };

enum { SmallSymSizeX = 9, SmallSymSizeY = 10 };
enum { SmallSymSrcX = 38, SmallSymSrcY = 0 };
enum { SmallSymY = 19 };

enum { NumberSrcX = 12, NumberSrcY = 10 };
enum { NumberSizeX = 9, NumberSizeY = 12 };
enum { NumberY = 5 };

enum { JokerSizeY = 63, JokerSizeX = 45 };
enum { JokerY = 17, JokerX = 13 };

enum { PictSizeY = 72, PictSizeX = 45 };
 enum { PictY = 11, PictX = 12 };

};


CARDPPG.CPP

//=--------------------------------------------------------------------------=
// CardPPG.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// property page implementations for Card control.
//
#include "IPServer.H"

#include "LocalObj.H"
#include "CARDPPG.H"
#include "CARDCtl.H"
#include "Resource.H"
#include "Util.H"

// for ASSERT and FAIL
//
SZTHISFILE

//=--------------------------------------------------------------------------=
// Property Page messages
//=--------------------------------------------------------------------------=
// in addition to regular windows messages you'll receive for a dialog box,
// you'll receive the following messages in your property page implementation:
//
// PPM_NEWOBJECTS:
//    wParam = 0;
//    lParam = (LPARAM)(HRESULT *)hr
//
//  - in this message, you should call FirstControl() to get a pointer to a
//    control, and initialize the values in the property page dialog with
//    values from the control object.  put results from the operation in
//    the HRESULT pointed to by LPARAM.
//
// PPM_APPLY:
//    wParam = 0;
//    lParam = (LPARAM)(HRESULT *)hr
//
//  - this message is sent to your dialog whenever the user clicks APPLY or OK
//    in the dialog.  you should have a loop with the following code in it:
//
//      for (pUnk = FirstControl(&dwCookie) ; pUnk; pUnk = NextControl(&dwCookie)) {
//            hr = pUnk->QueryInterface(IID_IMyCtlInterface, (void **)&pMyCtl);
//            // set properties here!!!
//      }
//
//    call PropPageException() if there is an error while setting propertites
//    to show the exception set up by the property set routine.
//
// PPM_EDITPROPERTY:
//    wParam = dispid
//    lParam = (LPARAM)(HRESULT *)hr
//
//  - sent to your dialog when somebody wants you to set the focus to a specific
//    property [typically, one will see a call to this when one returns a page
//    from IPerPropertyBrowsing::MapPropertyToPage].  you can use this
//    to bring up dialogs, or do whatever flaps your flagella.
//

//=--------------------------------------------------------------------------=
// CCardGeneralPage::Create
//=--------------------------------------------------------------------------=
// global static creation function.
//
// Parameters:
//    IUnknown *    - [in] controlling unknown
//
// Output:
//    IUnknown *    - new prop page.
//
// Notes:
//
IUnknown *CCardGeneralPage::Create
(
    IUnknown *pUnkOuter
)
{
    return (IUnknown *)new CCardGeneralPage(pUnkOuter);
}

//=--------------------------------------------------------------------------=
// CCardGeneralPage::CCardGeneralPage
//=--------------------------------------------------------------------------=
// constructor.
//
// Parameters:
//    IUnknown *        - [in] controlling unknown.
//
// Notes:
//
CCardGeneralPage::CCardGeneralPage
(
    IUnknown *pUnkOuter
)
: CPropertyPage(pUnkOuter, OBJECT_TYPE_PPGCARDGENERAL)
{
    // initialize local variables here.
}

//=--------------------------------------------------------------------------=
// CCardGeneralPage::~CCardGeneralPage
//=--------------------------------------------------------------------------=
// destructor.
//
// Notes:
//
CCardGeneralPage::~CCardGeneralPage()
{
    // clean up
}

static int SimpleAtoi( const char *str )
{
int num = 0;
const char *s;

for( s = (*str == '-' ? str+1 : str); *s >= '0' && *s <= '9'; s++ )
num = num * 10 + (*s - '0');

return( *str == '-' ? -num : num );
}

static void SetDropDown( HWND hwnd, const char *strs[], int id, short set )
{
short newset;
int i;

newset = -1000;
for( i = 0; strs[i]; i++ )
{
SendMessage( GetDlgItem( hwnd, id ), CB_ADDSTRING,
0, (LPARAM) strs[i] );
if( SimpleAtoi(strs[i]) == set )
newset = i;
}
if( newset != -1000 )
SendMessage( GetDlgItem( hwnd, id ), CB_SETCURSEL,
(WPARAM) newset, 0 );
}

//=--------------------------------------------------------------------------=
// CCardGeneralPage::DialogProc
//=--------------------------------------------------------------------------=
// our dialog proc.
//
// Parameters:
//    - see win32sdk docs on DialogProc
//
// Notes:
//
BOOL CCardGeneralPage::DialogProc
(
    HWND   hwnd,
    UINT   msg,
    WPARAM wParam,
    LPARAM lParam
)
{
    HRESULT     hr;
    ICard *pCard;
    IUnknown   *pUnk;
    DWORD       dwDummy;
    enumCardNumber number;
    enumCardSuite suite;
    enumCardAlignment cardalignment;
VARIANT_BOOL invert;

static const char *numstr[] = {
"1   - Ace",
"2", "3", "4", "5", "6", "7", "8", "9", "10",
"11  - Jack",
"12  - Queen",
"13  - King",
        "14  - Joker",
NULL };

static const char *alignstr[] = {
"0   - Top Left",
"1   - Top Center",
"2   - Top Right",
"3   - Center Left",
"4   - Center",
"5   - Center Right",
"6   - Bottom Left",
"7   - Bottom Center",
"8   - Bottom Right",
"9   - Stretch",
NULL };

static const char *suitestr[] = {
"0   - Invisible",
"1   - Clubs",
"2   - Spades",
"3   - Hearts",
"4   - Diamonds",
"-1  - Card Back: Weave 1",
"-2  - Card Back: Weave 2",
"-3  - Card Back: Robot",
"-4  - Card Back: Roses",
"-5  - Card Back: Vine 1 (black)",
"-6  - Card Back: Vine 2 (blue)",
"-7  - Card Back: Fish 1 (light blue)",
"-8  - Card Back: Fish 2 (dark blue)",
"-9  - Card Back: Seashell",
"-10 - Card Back: Castel",
"-11 - Card Back: Beach",
"-12 - Card Back: Hand",
"-13 - Card Back: Casino",
NULL };

    switch (msg) {

      // we've been given some new objects, so go and re-set up the dialog page.
      //
      case PPM_NEWOBJECTS:
        {

        pUnk = FirstControl(&dwDummy);
        if (!pUnk) return FALSE;

        hr = pUnk->QueryInterface(IID_ICard, (void **)&pCard);
        if (FAILED(hr)) return FALSE;

        pCard->get_Number(&number);
        pCard->get_Suite(&suite);
        pCard->get_Invert(&invert);
pCard->get_CardAlignment(&cardalignment);

SetDropDown( hwnd, suitestr, IDC_SUITE, suite );
SetDropDown( hwnd, numstr, IDC_NUMBER, number );
SetDropDown( hwnd, alignstr, IDC_ALIGNMENT, cardalignment );

CheckDlgButton( hwnd, IDC_INVERT, invert == VARIANT_TRUE ? 1 : 0 );

        pCard->Release();
        }
        return TRUE;

      case PPM_APPLY:
        {
        char      szTmp[128];

        // get all the controls we have to update.
        //
        for (pUnk = FirstControl(&dwDummy) ; pUnk; pUnk = NextControl(&dwDummy))
{
            hr = pUnk->QueryInterface(IID_ICard, (void **)&pCard);
            if (FAILED(hr)) continue;

            GetDlgItemText(hwnd, IDC_NUMBER, szTmp, 128);
    number = (enumCardNumber) SimpleAtoi(szTmp);
            GetDlgItemText(hwnd, IDC_SUITE, szTmp, 128);
    suite = (enumCardSuite) SimpleAtoi(szTmp);
            GetDlgItemText(hwnd, IDC_ALIGNMENT, szTmp, 128);
    cardalignment = (enumCardAlignment) SimpleAtoi(szTmp);
    invert = IsDlgButtonChecked( hwnd, IDC_INVERT ) ? VARIANT_TRUE : VARIANT_FALSE;

            pCard->put_Number(number);
    pCard->put_Suite(suite);
    pCard->put_Invert(invert);
    pCard->put_CardAlignment(cardalignment);
            pCard->Release();
        }
        }
        return TRUE;

      case WM_COMMAND:
        switch (LOWORD(wParam)) {
          case IDC_SUITE: case IDC_NUMBER: case IDC_ALIGNMENT:
            if (HIWORD(wParam) == CBN_SELCHANGE)
                MakeDirty();
  case IDC_INVERT:
    if( HIWORD(wParam) == BN_CLICKED )
MakeDirty();
        }
        break;
    }

return(FALSE);
}


CARDPPG.H

//=--------------------------------------------------------------------------=
// CardPPG.H
//=--------------------------------------------------------------------------=
// Copyright 1996 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// class declaration for Card's property pages.
//
#ifndef _CARDPROPPAGE_H_

// kinda need these
//
#include "PropPage.H"
#include "Resource.H"
#include "Guids.H"

class CCardGeneralPage : public CPropertyPage {

  public:
    static IUnknown *Create(IUnknown *pUnkOuter);

    // constructor and destructor
    //
    CCardGeneralPage(IUnknown *pUnkOuter);
    virtual ~CCardGeneralPage();

  private:
    virtual BOOL DialogProc(HWND, UINT, WPARAM, LPARAM);

};

DEFINE_PROPERTYPAGEOBJECT(CardGeneral,
    &CLSID_CardGeneralPage,
    "Card General Propery Page",
    CCardGeneralPage::Create,
    IDD_PROPPAGE_CARDGENERAL,
    IDS_CARD_GENERALPAGETITLE,
    IDS_CARD_GENERALDOCSTRING,
    "vb.hlp",
    0);

#define _CARDPROPPAGE_H_
#endif // _CARDPROPPAGE_H_


CATHELP.CPP

//=--------------------------------------------------------------------------=
// CatHelp.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// contains the Component Category helper functions.
//

#include "comcat.h"

// Helper function to create a component category and associated description
HRESULT CreateComponentCategory(CATID catid, WCHAR* catDescription)
{

    ICatRegister* pcr = NULL ;
    HRESULT hr = S_OK ;

    hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr,
NULL, CLSCTX_INPROC_SERVER, IID_ICatRegister, (void**)&pcr);
if (FAILED(hr))
return hr;

    // Make sure the HKCR\Component Categories\{..catid...}
    // key is registered
    CATEGORYINFO catinfo;
    catinfo.catid = catid;
    catinfo.lcid = 0x0409 ; // english

// Make sure the provided description is not too long.
// Only copy the first 127 characters if it is
int len = wcslen(catDescription);
if (len>127)
len = 127;
    wcsncpy(catinfo.szDescription, catDescription, len);
// Make sure the description is null terminated
catinfo.szDescription[len] = '\0';

    hr = pcr->RegisterCategories(1, &catinfo);
pcr->Release();

return hr;
}

// Helper function to register a CLSID as belonging to a component category
HRESULT RegisterCLSIDInCategory(REFCLSID clsid, CATID catid)
{
// Register your component categories information.
    ICatRegister* pcr = NULL ;
    HRESULT hr = S_OK ;
    hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr,
NULL, CLSCTX_INPROC_SERVER, IID_ICatRegister, (void**)&pcr);
    if (SUCCEEDED(hr))
    {
       // Register this category as being "implemented" by
       // the class.
       CATID rgcatid[1] ;
       rgcatid[0] = catid;
       hr = pcr->RegisterClassImplCategories(clsid, 1, rgcatid);
    }

    if (pcr != NULL)
        pcr->Release();

return hr;
}

// Helper function to unregister a CLSID as belonging to a component category
HRESULT UnRegisterCLSIDInCategory(REFCLSID clsid, CATID catid)
{
    ICatRegister* pcr = NULL ;
    HRESULT hr = S_OK ;
    hr = CoCreateInstance(CLSID_StdComponentCategoriesMgr,
NULL, CLSCTX_INPROC_SERVER, IID_ICatRegister, (void**)&pcr);
    if (SUCCEEDED(hr))
    {
       // Unregister this category as being "implemented" by
       // the class.
       CATID rgcatid[1] ;
       rgcatid[0] = catid;
       hr = pcr->UnRegisterClassImplCategories(clsid, 1, rgcatid);
    }

    if (pcr != NULL)
        pcr->Release();

return hr;
}


CATHELP.H

/////////////////////////////////////////////////////////////////////////////
// CatHelp.h
/////////////////////////////////////////////////////////////////////////////
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
/////////////////////////////////////////////////////////////////////////////
//
// contains the prototypes for the component category helper functions
//

#include "comcat.h"

// Helper function to create a component category and associated description
HRESULT CreateComponentCategory(CATID catid, WCHAR* catDescription);

// Helper function to register a CLSID as belonging to a component category
HRESULT RegisterCLSIDInCategory(REFCLSID clsid, CATID catid);

// Helper function to unregister a CLSID as belonging to a component category
HRESULT UnRegisterCLSIDInCategory(REFCLSID clsid, CATID catid);


DISPIDS.H

//=--------------------------------------------------------------------------=
// Dispids.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// dispids for use in an automation or control object.
//
#ifndef _DISPIDS_H_

//=--------------------------------------------------------------------------=
// for the Cards control
// properties & methods
//
#define DISPID_NUMBER102
#define DISPID_SUITE    103
#define DISPID_INVERT104
#define DISPID_CARDALIGNMENT105

// events
//

#define _DISPIDS_H_
#endif // _DISPIDS_H_


DWINVERS.H

#define vszVersNumAll   "1.0.0.6\0"
#define vusVersNuml2    6
#define vusVersNumf2    0
#define vszCopyright    "Copyright 1996 - 1998 Microsoft Corporation\0\0"


GUIDS.CPP

//=--------------------------------------------------------------------------=
// Guids.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// contains the guids we will define ourselves.
//

#define INITGUID                   // define all the guids.
#include "IPServer.H"

#include "Guids.H"
#include "olectl.h"
#include "CardInterfaces.H"


GUIDS.H

//=--------------------------------------------------------------------------=
// Guids.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// guids that we define locally
//
#ifndef _GUIDS_H_

// for each property page this server will have, put the guid definition for it
// here so that it gets defined ...
//
// {55707B24-FF82-11cf-BB32-142A04C10000}
DEFINE_GUID(CLSID_CardGeneralPage,
0x55707b24, 0xff82, 0x11cf, 0xbb, 0x32, 0x14, 0x2a, 0x4, 0xc1, 0x0, 0x0);

#define _GUIDS_H_
#endif // _GUIDS_H_


LOCALOBJ.H

//=--------------------------------------------------------------------------=
// LocalObjects.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// this file is used by automation servers to delcare things that their objects
// need other parts of the server to see.
//
#ifndef _LOCALOBJECTS_H_

//=--------------------------------------------------------------------------=
// these constants are used in conjunction with the g_ObjectInfo table that
// each inproc server defines.  they are used to identify a given  object
// within the server.
//
// **** ADD ALL NEW OBJECTS TO THIS LIST ****
//

#define OBJECT_TYPE_CTLCARD 0
#define OBJECT_TYPE_PPGCARDGENERAL 1

#define _LOCALOBJECTS_H_
#endif // _LOCALOBJECTS_H_


CARD.CPP

//=--------------------------------------------------------------------------=
// Card.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// various routines et all that aren't in a file for a particular automation
// object, and don't need to be in the generic ole automation code.
//
#define INITOBJECTS                // define the descriptions for our objects

#include "IPServer.H"
#include "LocalSrv.H"

#include "LocalObj.H"
#include "CtrlObj.H"
#include "Globals.H"
#include "Util.H"
#include "Resource.H"

#include "CardCtl.H"
#include "CardPPG.H"
#include "cathelp.H"

#include "carddraw.h"

// Control Globals
//
CRITICAL_SECTION drawCritSec;
HBITMAP cardcacheMem[14*4+14];// 4 suites * 14 cards + 14 card backs

const IID IID_ICatRegister = {0x0002E012,0x0000,0x0000,{0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}};
const CATID CATID_SafeForScripting = {0x7dd95801,0x9882,0x11cf,{0x9f,0xa9,0x00,0xaa,0x00,0x6c,0x42,0xc4}};
const CATID CATID_SafeForInitializing = {0x7dd95802,0x9882,0x11cf,{0x9f,0xa9,0x00,0xaa,0x00,0x6c,0x42,0xc4}};

// needed for ASSERTs and FAIL
//
SZTHISFILE

//=--------------------------------------------------------------------------=
// our Libid.  This should be the LIBID from the Type library, or NULL if you
// don't have one.
//
const CLSID *g_pLibid = &LIBID_CardObjects;

//=--------------------------------------------------------------------------=
// Localization Information
//
// We need the following two pieces of information:
//    a. whether or not this DLL uses satellite DLLs for localization.  if
//       not, then the lcidLocale is ignored, and we just always get resources
//       from the server module file.
//    b. the ambient LocaleID for this in-proc server.  Controls calling
//       GetResourceHandle() will set this up automatically, but anybody
//       else will need to be sure that it's set up properly.
//
const VARIANT_BOOL    g_fSatelliteLocalization =  FALSE;
LCID            g_lcidLocale = MAKELCID(LANG_USER_DEFAULT, SORT_DEFAULT);

//=--------------------------------------------------------------------------=
// your license key and where under HKEY_CLASSES_ROOT_LICENSES it's sitting
//
const WCHAR g_wszLicenseKey [] = L"";
const WCHAR g_wszLicenseLocation [] = L"";

WNDPROC g_ParkingWindowProc = NULL;

//=--------------------------------------------------------------------------=
// This Table describes all the automatible objects in your automation server.
// See AutomationObject.H for a description of what goes in this structure
// and what it's used for.
//
OBJECTINFO g_ObjectInfo[] = {
    CONTROLOBJECT(Card),
    PROPERTYPAGE(CardGeneral),
    EMPTYOBJECT
};

const char g_szLibName[] = "ActiveXSDKCard";

//=--------------------------------------------------------------------------=
// IntializeLibrary
//=--------------------------------------------------------------------------=
// called from DllMain:DLL_PROCESS_ATTACH.  allows the user to do any sort of
// initialization they want to.
//
// Notes:
//
void WINAPI InitializeLibrary (void )
{
InitializeCriticalSection( &drawCritSec );
}

//=--------------------------------------------------------------------------=
// UninitializeLibrary
//=--------------------------------------------------------------------------=
// called from DllMain:DLL_PROCESS_DETACH.  allows the user to clean up anything
// they want.
//
// Notes:
//
void WINAPI UninitializeLibrary
(
    void
)
{
DeleteCriticalSection( &drawCritSec );

for( int t = 0; t < sizeof(cardcacheMem)/sizeof(cardcacheMem[0]); t++ )
if( cardcacheMem[t] )
DeleteObject( cardcacheMem[t] );
}

//=--------------------------------------------------------------------------=
// CheckForLicense
//=--------------------------------------------------------------------------=
// users can implement this if they wish to support Licensing.  otherwise,
// they can just return TRUE all the time.
//
// Parameters:
//    none
//
// Output:
//    BOOL            - TRUE means the license exists, and we can proceed
//                      FALSE means we're not licensed and cannot proceed
//
// Notes:
//    - implementers should use g_wszLicenseKey and g_wszLicenseLocation
//      from the top of this file to define their licensing [the former
//      is necessary, the latter is recommended]
//
BOOL WINAPI CheckForLicense
(
    void
)
{
    // TODO: decide whether or not your server is licensed in this function.
    // people who don't want to bother with licensing should just return
    // true here always.  g_wszLicenseKey and g_wszLicenseLocation are
    // used by IClassFactory2 to do some of the licensing work.
    //
    return TRUE;
}

//=--------------------------------------------------------------------------=
// RegisterData
//=--------------------------------------------------------------------------=
// lets the inproc server writer register any data in addition to that in
// any other objects.
//
// Output:
//    BOOL            - false means failure.
//
// Notes:
//
BOOL WINAPI RegisterData
(
    void
)
{
HRESULT hr;

hr = CreateComponentCategory(CATID_SafeForScripting, L"Controls that are safely scriptable");
hr = CreateComponentCategory(CATID_SafeForInitializing, L"Controls safely initializable from persistent data");
hr = RegisterCLSIDInCategory(CLSID_Card, CATID_SafeForScripting);
hr = RegisterCLSIDInCategory(CLSID_Card, CATID_SafeForInitializing);
    return TRUE;
}

//=--------------------------------------------------------------------------=
// UnregisterData
//=--------------------------------------------------------------------------=
// inproc server writers should unregister anything they registered in
// RegisterData() here.
//
// Output:
//    BOOL            - false means failure.
//
// Notes:
//
BOOL WINAPI UnregisterData
(
    void
)
{
HRESULT hr;

hr = UnRegisterCLSIDInCategory(CLSID_Card, CATID_SafeForScripting);
hr = UnRegisterCLSIDInCategory(CLSID_Card, CATID_SafeForInitializing);
    return TRUE;
}

BOOL WINAPI      CheckLicenseKey(LPWSTR wszCheckme)
{
return TRUE;
}

BSTR WINAPI      GetLicenseKey(void)
{
return NULL;
}


RESOURCE.H

//=--------------------------------------------------------------------------=
// Resource.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// resource IDs.
//
#ifndef _RESOURCE_H_

#define RESID_TOOLBOX_BITMAP 1

#define IDS_CARDS                       1
#define IDD_ABOUTBOX_CARDS              1
#define IDI_ABOUTDLL                    1
#define IDS_CARDS_PPG                   2
#define IDS_CARDS_PPG_CAPTION           100
#define IDD_PROPPAGE_CARDS              100
#define IDB_SYMS200
#define IDB_FACES201
#define IDB_JOKER210

#define IDB_BACKBASE500
#define IDB_BACK1501
#define IDB_BACK2502
#define IDB_BACK3503
#define IDB_BACK4504
#define IDB_BACK5505
#define IDB_BACK6506
#define IDB_BACK7507
#define IDB_BACK8508
#define IDB_BACK9509
#define IDB_BACK10510
#define IDB_BACK11511
#define IDB_BACK12512
// 500-520 reserved for card back resources

//=--------------------------------------------------------------------------=
// Strings
//
#define IDS_PROPERTIES              1000                // this MUST be 1000 !!!
#define IDS_CARD_GENERALPAGETITLE 2003
#define IDS_CARD_GENERALDOCSTRING 2004
#define IDS_CARD_ABOUTBOXVERB     2005

//=--------------------------------------------------------------------------=
// Dialog Stuff
//
#define IDD_PROPPAGE_CARDGENERAL 2000

#define IDC_SUITE 101
#define IDC_NUMBER 102
#define IDC_INVERT 103
#define IDC_ALIGNMENT 104

#define _RESOURCE_H_
#endif // _RESOURCE_H_


README.TXT

Before building the FrameWrk samples, you must run setenv.bat to
make sure the Platform SDK include, bin, and lib directories are included
first in your environment.

Run 'nmake' to build a debug build, or 'nmake nodebug=1' to build a
nodebug (retail) build of the framework library.

After building the FrameWrk libraries you can build the other BaseCtl
samples.


CLASSF.CPP

//=--------------------------------------------------------------------------=
// ClassF.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// contains the implementation of the ClassFactory object. we support
// IClassFactory and IClassFactory2
//
#include "IPServer.H"
#include "LocalSrv.H"

#include "ClassF.H"
#include "Globals.H"
#include "Unknown.H"                    // for CREATEFNOFOBJECT

//=--------------------------------------------------------------------------=
// private module level data
//=--------------------------------------------------------------------------=
//

// ASSERT and FAIL require this
//
SZTHISFILE

// private routines for this file
//
HRESULT   CreateOleObjectFromIndex(IUnknown *, int Index, void **, REFIID);

//=--------------------------------------------------------------------------=
// CClassFactory::CClassFactory
//=--------------------------------------------------------------------------=
// create the object and initialize the refcount
//
// Parameters:
//    int            - [in] index into our global table of objects for this guy
//
// Notes:
//
CClassFactory::CClassFactory
(
    int iIndex
)
: m_iIndex(iIndex)
{
    m_cRefs = 1;
}

//=--------------------------------------------------------------------------=
// CClassFactory::CClassFactory
//=--------------------------------------------------------------------------=
// "Life levels all men.  Death reveals the eminent."
// - George Bernard Shaw (1856 - 1950)
//
// Notes:
//
CClassFactory::~CClassFactory ()
{
    ASSERT(m_cRefs == 0, "Object being deleted with refs!");
    return;
}

//=--------------------------------------------------------------------------=
// CClassFactory::QueryInterface
//=--------------------------------------------------------------------------=
// the user wants another interface.  we won't give 'em. very many.
//
// Parameters:
//    REFIID        - [in]  interface they want
//    void **       - [out] where they want to put the resulting object ptr.
//
// Output:
//    HRESULT       - S_OK, E_NOINTERFACE
//
// Notes:
//
STDMETHODIMP CClassFactory::QueryInterface
(
    REFIID riid,
    void **ppvObjOut
)
{
    void *pv;

    CHECK_POINTER(ppvObjOut);

    // we support IUnknown, and the two CF interfaces
    //
    if (DO_GUIDS_MATCH(riid, IID_IClassFactory)) {
        pv = (void *)(IClassFactory *)this;
    } else if (DO_GUIDS_MATCH(riid, IID_IClassFactory2)) {
        pv = (void *)(IClassFactory2 *)this;
    } else if (DO_GUIDS_MATCH(riid, IID_IUnknown)) {
        pv = (void *)(IUnknown *)this;
    } else {
        *ppvObjOut = NULL;
        return E_NOINTERFACE;
    }

    ((IUnknown *)pv)->AddRef();
    *ppvObjOut = pv;
    return S_OK;
}

//=--------------------------------------------------------------------------=
// CClassFactory::AddRef
//=--------------------------------------------------------------------------=
// adds a tick to the current reference count.
//
// Output:
//    ULONG        - the new reference count
//
// Notes:
//
ULONG CClassFactory::AddRef
(
    void
)
{
    return ++m_cRefs;
}

//=--------------------------------------------------------------------------=
// CClassFactory::Release
//=--------------------------------------------------------------------------=
// removes a tick from the count, and delets the object if necessary
//
// Output:
//    ULONG         - remaining refs
//
// Notes:
//
ULONG CClassFactory::Release
(
    void
)
{
    ASSERT(m_cRefs, "No Refs, and we're being released!");
    if(--m_cRefs)
        return m_cRefs;

    delete this;
    return 0;
}

//=--------------------------------------------------------------------------=
// CClassFactory::CreateInstance
//=--------------------------------------------------------------------------=
// create an instance of some sort of object.
//
// Parameters:
//    IUnknown *        - [in]  controlling IUknonwn for aggregation
//    REFIID            - [in]  interface id for new object
//    void **           - [out] pointer to new interface object.
//
// Output:
//    HRESULT           - S_OK, E_NOINTERFACE, E_UNEXPECTED,
//                        E_OUTOFMEMORY, E_INVALIDARG
//
// Notes:
//
STDMETHODIMP CClassFactory::CreateInstance
(
    IUnknown *pUnkOuter,
    REFIID    riid,
    void    **ppvObjOut
)
{
    // check args
    //
    if (!ppvObjOut)
        return E_INVALIDARG;

    // check to see if we've done our licensing work.  we do this as late
    // as possible that people calling CreateInstanceLic don't suffer from
    // a performance hit here.
    //
    // crit sect this for apartment threading, since it's global
    //
    EnterCriticalSection(&g_CriticalSection);
    if (!g_fCheckedForLicense) {
        g_fMachineHasLicense = CheckForLicense();
        g_fCheckedForLicense = TRUE;
    }
    LeaveCriticalSection(&g_CriticalSection);

    // check to see if they have the appropriate license to create this stuff
    //
    if (!g_fMachineHasLicense)
        return CLASS_E_NOTLICENSED;

    // try to create one of the objects that we support
    //
    return CreateOleObjectFromIndex(pUnkOuter, m_iIndex, ppvObjOut, riid);
}

//=--------------------------------------------------------------------------=
// CClassFactory::LockServer
//=--------------------------------------------------------------------------=
// lock the server so we can't unload
//
// Parameters:
//    BOOL        - [in] TRUE means addref, false means release lock count.
//
// Output:
//    HRESULT     - S_OK, E_FAIL, E_OUTOFMEMORY, E_UNEXPECTED
//
// Notes:
//
STDMETHODIMP CClassFactory::LockServer
(
    BOOL fLock
)
{
    // update the lock count.  crit sect these in case of another thread.
    //
    if (fLock)
        InterlockedIncrement(&g_cLocks);
    else {
        ASSERT(g_cLocks, "D'oh! Lock Counting Problem");
        InterlockedDecrement(&g_cLocks);
    }

    return S_OK;
}

//=--------------------------------------------------------------------------=
// CClassFactory::GetLicInfo
//=--------------------------------------------------------------------------=
// IClassFactory2 GetLicInfo
//
// Parameters:
//    LICINFO *          - unclear
//
// Output:
//    HRESULT            - unclear
//
// Notes:
//
STDMETHODIMP CClassFactory::GetLicInfo
(
    LICINFO *pLicInfo
)
{
    CHECK_POINTER(pLicInfo);

    pLicInfo->cbLicInfo = sizeof(LICINFO);

    // This says whether RequestLicKey will work
    //
    pLicInfo->fRuntimeKeyAvail = g_fMachineHasLicense;

    // This says whether the standard CreateInstance will work
    //
    pLicInfo->fLicVerified = g_fMachineHasLicense;

    return S_OK;
}

//=--------------------------------------------------------------------------=
// CClassFactory::RequestLicKey
//=--------------------------------------------------------------------------=
// IClassFactory2 RequestLicKey
//
// Parameters:
//    DWORD             - [in]  reserved
//    BSTR *            - [out] unclear
//
// Output:
//    HRESULT           - unclear
//
// Notes:
//
STDMETHODIMP CClassFactory::RequestLicKey
(
    DWORD  dwReserved,
    BSTR  *pbstr
)
{
    // if the machine isn't licensed, then we're not about to give this to them !
    //
    if (!g_fMachineHasLicense)
        return CLASS_E_NOTLICENSED;

    *pbstr = GetLicenseKey();
    return (*pbstr) ? S_OK : E_OUTOFMEMORY;
}

//=--------------------------------------------------------------------------=
// CClassFactory::CreateInstanceLic
//=--------------------------------------------------------------------------=
// create a new instance given a licensing key, etc ...
//
// Parameters:
//    IUnknown *        - [in]  controlling IUnknown for aggregation
//    IUnknown *        - [in]  reserved, must be NULL
//    REFIID            - [in]  IID We're looking for.
//    BSTR              - [in]  license key
//    void **           - [out] where to put the new object.
//
// Output:
//    HRESULT           - unclear
//
// Notes:
//
STDMETHODIMP CClassFactory::CreateInstanceLic
(
    IUnknown *pUnkOuter,
    IUnknown *pUnkReserved,
    REFIID    riid,
    BSTR      bstrKey,
    void    **ppvObjOut
)
{
    *ppvObjOut = NULL;

    // go and see if the key they gave us matches.
    //
    if (!CheckLicenseKey(bstrKey))
        return CLASS_E_NOTLICENSED;

    // if it does, then go and create the object.
    //
    return CreateOleObjectFromIndex(pUnkOuter, m_iIndex, ppvObjOut, riid);
}

//=--------------------------------------------------------------------------=
// CreateOleObjectFromIndex
//=--------------------------------------------------------------------------=
// given an index in our object table, create an object from it.
//
// Parameters:
//    IUnknown *       - [in]  Controlling Unknown, if any, for aggregation
//    int              - [in]  index into our global table
//    void **          - [out] where to put resulting object.
//    REFIID           - [in]  the interface they want resulting object to be.
//
// Output:
//    HRESULT          - S_OK, E_OUTOFMEMORY, E_NOINTERFACE
//
// Notes:
//
HRESULT CreateOleObjectFromIndex
(
    IUnknown *pUnkOuter,
    int       iIndex,
    void    **ppvObjOut,
    REFIID    riid
)
{
    IUnknown *pUnk = NULL;
    HRESULT   hr;

    // go and create the object
    //
    ASSERT(CREATEFNOFOBJECT(iIndex), "All creatable objects must have creation fn!");
    pUnk = CREATEFNOFOBJECT(iIndex)(pUnkOuter);

    // sanity check and make sure the object actually got allocated.
    //
    RETURN_ON_NULLALLOC(pUnk);

    // make sure we support aggregation here properly -- if they gave us
    // a controlling unknown, then they -must- ask for IUnknown, and we'll
    // give them the private unknown the object gave us.
    //
    if (pUnkOuter) {
        if (!DO_GUIDS_MATCH(riid, IID_IUnknown)) {
            pUnk->Release();
            return E_INVALIDARG;
        }

        *ppvObjOut = (void *)pUnk;
        hr = S_OK;
    } else {

        // QI for whatever the user wants.
        //
        hr = pUnk->QueryInterface(riid, ppvObjOut);
        pUnk->Release();
        RETURN_ON_FAILURE(hr);
    }

    return hr;
}


CLASSF.H

//=--------------------------------------------------------------------------=
// ClassF.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// header for the ClassFactory Object.  we support IClassFactory and
// IClassFactory2
//
#ifndef _CLASSF_H_

#include "olectl.h"

class CClassFactory : public IClassFactory2 {

  public:
    // IUnknown methods
    //
    STDMETHOD(QueryInterface)(REFIID riid, void **ppvObjOut);
    STDMETHOD_(ULONG, AddRef)(void);
    STDMETHOD_(ULONG, Release)(void);

    // IClassFactory methods
    //
    STDMETHOD(CreateInstance)(IUnknown *pUnkOuter, REFIID riid, void **ppbObjOut);
    STDMETHOD(LockServer)(BOOL fLock);

    // IClassFactory2 methods
    //
    STDMETHOD(GetLicInfo)(LICINFO *pLicInfo);
    STDMETHOD(RequestLicKey)(DWORD dwReserved, BSTR *pbstrKey);
    STDMETHOD(CreateInstanceLic)(IUnknown *pUnkOuter, IUnknown *pUnkReserved, REFIID riid, BSTR bstrKey, void **ppvObjOut);

    CClassFactory(int iIndex);
    ~CClassFactory();

  private:
    ULONG m_cRefs;
    int   m_iIndex;
};

// global variable for Locks on our DLL
//
extern LONG g_cLocks;

#define _CLASSF_H_
#endif // _CLASSF_H_


CTLEMBED.CPP

//=--------------------------------------------------------------------------=
// CtlEmbed.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// implementation of the interfaces required for inplace activation for
// COleControl
//

#include "IPServer.H"
#include "CtrlObj.H"

#include "CtlHelp.H"
#include "Globals.H"
#include "StdEnum.H"
#include "Util.H"

// for ASSERT and FAIL
//
SZTHISFILE

//=--------------------------------------------------------------------------=
// all controls support the following in-place verbs at an absolute minimum.
//
#define CINPLACEVERBS 4

const VERBINFO rgInPlaceVerbs [] = {
    { OLEIVERB_SHOW,            0, 0, 0},
    { OLEIVERB_HIDE,            0, 0, 0},
    { OLEIVERB_INPLACEACTIVATE, 0, 0, 0},
    { OLEIVERB_PRIMARY,         0, 0, 0}
};

// NOTE: Resource ID for Properties string must be 1000
//
const VERBINFO ovProperties =
    { CTLIVERB_PROPERTIES, 1000, 0, OLEVERBATTRIB_ONCONTAINERMENU };

const VERBINFO ovUIActivate =
    { OLEIVERB_UIACTIVATE, 0, 0, 0};

//=--------------------------------------------------------------------------=
// COleControl::GetControlInfo    (IOleControl)
//=--------------------------------------------------------------------------=
// returns some information on a control, such as an accelerator table, and
// flags.  really used for keyboard handling and mnemonics
//
// Parameters:
//    CONTROLINFO *        - [in]  where to put said information
//
// Output:
//    HRESULT              - S_OK
//
// Notes:
//
STDMETHODIMP COleControl::GetControlInfo
(
    CONTROLINFO *pControlInfo
)
{
    CHECK_POINTER(pControlInfo);

    // certain hosts have a bug in which it doesn't initialize the cb in the
    // CONTROLINFO structure, so we can only assert on that here.
    //
    ASSERT(pControlInfo->cb == sizeof(CONTROLINFO), "Host doesn't initialize CONTROLINFO structure");

    // NOTE: control writers should override this routine if they want to
    // return accelerator information in their control.
    //
    pControlInfo->hAccel = NULL;
    pControlInfo->cAccel = NULL;

    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::OnMnemonic    [IOleControl]
//=--------------------------------------------------------------------------=
// the container has decided to pass on a key that the end-user has pressed to
// us.  default implementation will be to just activate the control.  people
// looking for more functionality should override this method.
//
// Parameters:
//    LPMSG                - [in] message for this mnemonic
//
// Output:
//    HRESULT              - S_OK, E_POINTER
//
// Notes:
//
STDMETHODIMP COleControl::OnMnemonic
(
    LPMSG pMsg
)
{
    // OVERRIDE: default implementation is to just activate our control.
    // user can override if they want more interesting behaviour.
    //
    return InPlaceActivate(OLEIVERB_UIACTIVATE);
}

//=--------------------------------------------------------------------------=
// COleControl:OnAmbientPropertyChange    [IOleControl]
//=--------------------------------------------------------------------------=
// a container calls this whenever it changes an ambient property.
//
// Parameters:
//    DISPID            - [in] dispid of the property that changed.
//
// Output:
//    HRESULT           - S_OK
//
// Notes:
//
STDMETHODIMP COleControl::OnAmbientPropertyChange
(
    DISPID dispid
)
{
    // if we're being told about a change in mode [design/run] then
    // remember that so our stashing of mode will update itself
    // correctly
    //
    if (dispid == DISPID_AMBIENT_USERMODE || dispid == DISPID_UNKNOWN)
        m_fModeFlagValid = FALSE;

    // just pass this on to the derived control and see if they want
    // to do anything with it.
    //
    AmbientPropertyChanged(dispid);
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControL::FreezeEvents    [IOleControl]
//=--------------------------------------------------------------------------=
// allows a container to freeze all of a controls events.  when events are
// frozen, a control will not fire any of them.
//
// Parameters:
//    BOOL            - [in] TRUE means FREEZE, FALSE means THAW
//
// Output:
//    HRESULT         - S_OK
//
// Notes:
//    - we maintain an internal count of freezes versus thaws.
//
STDMETHODIMP COleControl::FreezeEvents
(
    BOOL fFreeze
)
{
    // OVERRIDE: by default, we don't care.  user can override if they want to.
    //
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::SetClientSite    [IOleObject]
//=--------------------------------------------------------------------------=
// informs the embedded object [control] of it's client site [display
// location] within it's container
//
// Parameters:
//    IOleClientSite *        - [in] pointer to client site.
//
// Output:
//    HRESULT                 - S_OK, E_UNEXPECTED
//
// Notes:
//
STDMETHODIMP COleControl::SetClientSite
(
    IOleClientSite *pClientSite
)
{
    RELEASE_OBJECT(m_pClientSite);
    RELEASE_OBJECT(m_pControlSite);
    RELEASE_OBJECT(m_pSimpleFrameSite);

    // store away the new client site
    //
    m_pClientSite = pClientSite;

    // if we've actually got one, then get some other interfaces we want to keep
    // around, and keep a handle on it
    //
    if (m_pClientSite) {
        m_pClientSite->AddRef();
        m_pClientSite->QueryInterface(IID_IOleControlSite, (void **)&m_pControlSite);

        if (OLEMISCFLAGSOFCONTROL(m_ObjectType) & OLEMISC_SIMPLEFRAME)
            m_pClientSite->QueryInterface(IID_ISimpleFrameSite, (void **)&m_pSimpleFrameSite);
    }

    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::GetClientSite    [IOleObject]
//=--------------------------------------------------------------------------=
// obtains a pointer to the controls client site.
//
// Parameters:
//    IOleClientSite **        - [out]
//
// Output:
//    HRESULT                  - S_OK
//
// Notes:
//
STDMETHODIMP COleControl::GetClientSite
(
    IOleClientSite **ppClientSite
)
{
    CHECK_POINTER(ppClientSite);

    *ppClientSite = m_pClientSite;
    ADDREF_OBJECT(*ppClientSite);
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::SetHostNames    [IOleObject]
//=--------------------------------------------------------------------------=
// Provides the control with the name of its container application and the
// compound document in which it is embedded
//
// Parameters:
//    LPCOLESTR        - [in] name of container application
//    LPCOLESTR        - [in] name of container document
//
// Output:
//    HRESULT          - S_OK
//
// Notes:
//    - we don't care about this
//
STDMETHODIMP COleControl::SetHostNames
(
    LPCOLESTR szContainerApp,
    LPCOLESTR szContainerObject
)
{
    // we don't care about these
    //
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::Close    [IOleObject]
//=--------------------------------------------------------------------------=
// Changes the control from the running to the loaded state
//
// Parameters:
//    DWORD             - [in] indicates whether to save the object before closing
//
// Output:
//    HRESULT           - S_OK, OLE_E_PROMPTSAVECANCELLED
//
// Notes:
//
STDMETHODIMP COleControl::Close
(
    DWORD dwSaveOption
)
{
    HRESULT hr;

    if (m_fInPlaceActive) {
        hr = InPlaceDeactivate();
        RETURN_ON_FAILURE(hr);
    }

    // handle the save flag.
    //
    if ((dwSaveOption == OLECLOSE_SAVEIFDIRTY || dwSaveOption == OLECLOSE_PROMPTSAVE) && m_fDirty) {
        if (m_pClientSite) m_pClientSite->SaveObject();
        if (m_pOleAdviseHolder) m_pOleAdviseHolder->SendOnSave();
    }
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::SetMoniker    [IOleObject]
//=--------------------------------------------------------------------------=
// Notifies an object of its container's moniker, the object's own moniker
// relative to the container, or the object's full moniker
//
// Parameters:
//    DWORD                - [in] which moniker is being set
//    IMoniker *           - [in] the moniker
//
// Output:
//    HRESULT              - S_OK, E_FAIL
//
// Notes:
//    - we don't support monikers.
//
STDMETHODIMP COleControl::SetMoniker
(
    DWORD     dwWhichMoniker,
    IMoniker *pMoniker
)
{
    return E_NOTIMPL;
}

//=--------------------------------------------------------------------------=
// COleControl::GetMoniker    [IOleObject]
//=--------------------------------------------------------------------------=
// Returns a embedded object's moniker, which the caller can use to link to
// the object
//
// Parameters:
//    DWORD            - [in]  how it's assigned
//    DWORD            - [in]  which moniker
//    IMoniker **      - [out] duh.
//
// Output:
//    HRESULT          - E_NOTIMPL
//
// Notes:
//    - we don't support monikers
//
STDMETHODIMP COleControl::GetMoniker
(
    DWORD      dwAssign,
    DWORD      dwWhichMoniker,
    IMoniker **ppMonikerOut
)
{
    return E_NOTIMPL;
}

//=--------------------------------------------------------------------------=
// COleControl::InitFromData    [IOleObject]
//=--------------------------------------------------------------------------=
// Initializes a newly created object with data from a specified data object,
// which can reside either in the same container or on the Clipboard
//
// Parameters:
//    IDataObject*    - [in] data object with the data
//    BOOL            - [in] how object is created
//    DWORD           - reserved
//
// Output:
//    HRESULT         - S_OK, S_FALSE, E_NOTIMPL, OLE_E_NOTRUNNING
//
// Notes:
//    - we don't have data object support
//
STDMETHODIMP COleControl::InitFromData
(
    IDataObject *pDataObject,
    BOOL         fCreation,
    DWORD        dwReserved
)
{
    return E_NOTIMPL;
}

//=--------------------------------------------------------------------------=
// COleControl::GetClipboardData    [IOleObject]
//=--------------------------------------------------------------------------=
// Retrieves a data object containing the current contents of the control.
// Using the pointer to this data object, it is possible to create a new control
// with the same data as the original
//
// Parameters:
//    DWORD          - reserved
//    IDataObject ** - [out] data object for this control
//
// Output:
//    HREUSLT        - S_OK, E_NOTIMPL, OLE_E_NOTRUNNING
//
// Notes:
//
STDMETHODIMP COleControl::GetClipboardData
(
    DWORD         dwReserved,
    IDataObject **ppDataObject
)
{
    *ppDataObject = NULL;        // be a good neighbour
    return E_NOTIMPL;
}

//=--------------------------------------------------------------------------=
// COleControl::DoVerb    [IOleObject]
//=--------------------------------------------------------------------------=
// Requests an object to perform an action in response to an end-user's
// action.
//
// Parameters:
//    LONG             - [in]  verb to be performed
//    LPMSG            - [in]  event that invoked the verb
//    IOleClientSite * - [in]  the controls active client site
//    LONG             - [in]  reserved
//    HWND             - [in]  handle of window containing the object.
//    LPCRECT          - [in]  pointer to objects's display rectangle
//
// Output:
//    HRESULT          - S_OK, OLE_E_NOTINPLACEACTIVE, OLE_E_CANT_BINDTOSOURCE,
//                       DV_E_LINK, OLEOBJ_S_CANNOT_DOVERB_NOW, OLEOBJ_S_INVALIDHWND,
//                       OLEOBJ_E_NOVERBS, OLEOBJ_S_INVALIDVERB, MK_E_CONNECT,
//                       OLE_CLASSDIFF, E_NOTIMPL
//
// Notes:
//
STDMETHODIMP COleControl::DoVerb
(
    LONG            lVerb,
    LPMSG           pMsg,
    IOleClientSite *pActiveSite,
    LONG            lIndex,
    HWND            hwndParent,
    LPCRECT         prcPosRect
)
{
    HRESULT hr;

    switch (lVerb) {
      case OLEIVERB_SHOW:
      case OLEIVERB_INPLACEACTIVATE:
      case OLEIVERB_UIACTIVATE:
        hr = InPlaceActivate(lVerb);
        OnVerb(lVerb);
        return (hr);

      case OLEIVERB_HIDE:
        UIDeactivate();
        if (m_fInPlaceVisible) SetInPlaceVisible(FALSE);
        OnVerb(lVerb);
        return S_OK;

      // we used to have OLEIVERB_PRIMARY InPlaceActivate Ourselves, but it
      // turns out that the CDK and certain hosts expect this to show the
      // properties instead.  Users can change what this verb does at will.
      //
      case OLEIVERB_PRIMARY:
      case CTLIVERB_PROPERTIES:
      case OLEIVERB_PROPERTIES:
        {
        // show the frame ourselves if the hose can't.
        //
        if (m_pControlSite) {
            hr = m_pControlSite->ShowPropertyFrame();
            if (hr != E_NOTIMPL)
                return hr;
        }
        IUnknown *pUnk = (IUnknown *)(IOleObject *)this;
        MAKE_WIDEPTR_FROMANSI(pwsz, NAMEOFOBJECT(m_ObjectType));

        ModalDialog(TRUE);
        hr = OleCreatePropertyFrame(GetActiveWindow(),
                            GetSystemMetrics(SM_CXSCREEN) / 2,
                            GetSystemMetrics(SM_CYSCREEN) / 2,
                            pwsz,
                            1,
                            &pUnk,
                            CPROPPAGESOFCONTROL(m_ObjectType),
                            (LPCLSID)*(PPROPPAGESOFCONTROL(m_ObjectType)),
                            g_lcidLocale,
                            NULL, NULL);
        ModalDialog(FALSE);
        return hr;
        }

      default:
        // if it's a derived-control defined verb, pass it on to them
        //
        if (lVerb > 0) {
            hr = DoCustomVerb(lVerb);

            if (hr == OLEOBJ_S_INVALIDVERB) {
                // unrecognised verb -- just do the primary verb and
                // activate the sucker.
                //
                hr = InPlaceActivate(OLEIVERB_PRIMARY);
                return (FAILED(hr)) ? hr : OLEOBJ_S_INVALIDVERB;
            } else
                return hr;
        } else {
            FAIL("Unrecognized Negative verb in DoVerb().  bad.");
            return E_NOTIMPL;
        }
        break;
    }

    // dead code
    FAIL("this should be dead code!");
}

//=--------------------------------------------------------------------------=
// COleControl::EnumVerbs    [IOleObject]
//=--------------------------------------------------------------------------=
// create an enumerator object for the verbs this object supports.
//
// Parameters:
//    IEnumOleVERB **    - [out] new enumerator.
//
// Output:
//    HRESULT            - S_OK, E_OUTOFMEMORY
//
// Notes:
//
STDMETHODIMP COleControl::EnumVerbs
(
    IEnumOLEVERB **ppEnumVerbs
)
{
    int cVerbs;
    OLEVERB *rgVerbs, *pVerb;

    DWORD dw = OLEMISCFLAGSOFCONTROL(m_ObjectType);
    BOOL fCanInPlace = !(dw & OLEMISC_INVISIBLEATRUNTIME) || (dw & OLEMISC_ACTIVATEWHENVISIBLE);
    BOOL fCanUIActivate = !(dw & OLEMISC_NOUIACTIVATE);
    BOOL fHasProperties = (CPROPPAGESOFCONTROL(m_ObjectType) != 0);

    int cVerbExtra = CCUSTOMVERBSOFCONTROL(m_ObjectType);

    // count up all the verbs
    //
    cVerbs = (fCanInPlace ? CINPLACEVERBS : 0) + (fCanUIActivate ? 1 : 0)
             + (fHasProperties ? 1 : 0) + cVerbExtra;

    // if there aren't any, this suddenly gets really easy !
    //
    if (cVerbs == 0)
        return OLEOBJ_E_NOVERBS;

    // HeapAlloc some storage for these dudes so that we can pass them on to
    // the standard enumerator!
    //
    if (! (rgVerbs = (OLEVERB *)HeapAlloc(g_hHeap, 0, cVerbs * sizeof(OLEVERB))))
        return E_OUTOFMEMORY;

    // start copying over verbs.  first, the in-place guys
    //
    pVerb = rgVerbs;
    if (fCanInPlace) {
        memcpy(pVerb, rgInPlaceVerbs, CINPLACEVERBS * sizeof(OLEVERB));
        pVerb += CINPLACEVERBS;
      }

    if (fCanUIActivate)
        memcpy(pVerb++, &ovUIActivate, sizeof(OLEVERB));

    // if their control has properties, copy that over now.
    //
    if (fHasProperties) {
        memcpy(pVerb, &ovProperties, sizeof(OLEVERB));
        pVerb++;
    }

    // finally, any custom verbs!
    //
    if (cVerbExtra) {
        memcpy(pVerb, CUSTOMVERBSOFCONTROL(m_ObjectType), sizeof(OLEVERB) * cVerbExtra);
    }

    *ppEnumVerbs = (IEnumOLEVERB *) (IEnumGeneric *) new CStandardEnum(IID_IEnumOLEVERB,
                                     cVerbs, sizeof(OLEVERB), rgVerbs, CopyOleVerb);
    if (!*ppEnumVerbs)
        return E_OUTOFMEMORY;

    // this forces us to go and look for the Localized DLLs.  This is necessary here
    // because the CopyOleVerb will get information from localized resources, but
    // will only use the global GetResourceHandle, which only uses the global value
    // for the LCID.  This turns out to not be a big performance hit, since this
    // function is typically only called in design mode, and we stash this value.
    //
    GetResourceHandle();
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::Update    [IOleObject]
//=--------------------------------------------------------------------------=
// Updates an object handler's or link object's data or view caches.
//
// Output:
//    HRESULT            - S_OK
//
// Notes:
//
STDMETHODIMP COleControl::Update
(
    void
)
{
    // nothing to do!!!
    //
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::IsUpToDate    [IOleObject]
//=--------------------------------------------------------------------------=
// Checks recursively whether or not an object is up to date.
//
// Output:
//    HRESULT        - S_OK, S_FALSE, OLE_E_UNVAILABLE
//
// Notes:
//
STDMETHODIMP COleControl::IsUpToDate
(
    void
)
{
    // we're always up to date
    //
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::GetUserClassID    [IOleObject]
//=--------------------------------------------------------------------------=
// Returns the controls class identifier, the CLSID corresponding to the
// string identifying the object to an end user.
//
// Parameters:
//    CLSID *      - [in] where to put the CLSID
//
// Output:
//    HRESULT      - S_OK, E_FAIL
//
// Notes:
//
STDMETHODIMP COleControl::GetUserClassID
(
    CLSID *pclsid
)
{
    // this is the same as IPersist::GetClassID
    //
    return GetClassID(pclsid);
}

//=--------------------------------------------------------------------------=
// COleControl::GetUserType    [IOleObject]
//=--------------------------------------------------------------------------=
// Retrieves the user-type name of the control for display in user-interface
// elements such as menus, list boxes, and dialog boxes.
//
// Parameters:
//    DWORD        - [in]  specifies the form of the type name.
//    LPOLESTR *   - [out] where to put user type
//
// Output:
//    HRESULT      - S_OK, OLE_S_USEREG, E_OUTOFMEMORY
//
// Notes:
//
STDMETHODIMP COleControl::GetUserType
(
    DWORD     dwFormOfType,
    LPOLESTR *ppszUserType
)
{
    *ppszUserType = OLESTRFROMANSI(NAMEOFOBJECT(m_ObjectType));
    return (*ppszUserType) ? S_OK : E_OUTOFMEMORY;
}

//=--------------------------------------------------------------------------=
// COleControl::SetExtent    [IOleObject]
//=--------------------------------------------------------------------------=
// Informs the control of how much display space its container has assigned it.
//
// Parameters:
//    DWORD            - [in] which form or 'aspect'  is to be displayed.
//    SIZEL *          - [in] size limit for the control.
//
// Output:
//    HRESULT          - S_OK, E_FAIL, OLE_E_NOTRUNNING
//
// Notes:
//
STDMETHODIMP COleControl::SetExtent
(
    DWORD  dwDrawAspect,
    SIZEL *psizel
)
{
    SIZEL sl;
    RECT  rect;
    BOOL  f;

    if (dwDrawAspect & DVASPECT_CONTENT) {

        // change the units to pixels, and resize the control.
        //
        HiMetricToPixel(psizel, &sl);

        // first call the user version.  if they return FALSE, they want
        // to keep their current size
        //
        f = OnSetExtent(&sl);
        if (f)
            HiMetricToPixel(psizel, &m_Size);

        // set things up with our HWND if we've got one.
        //
        if (!m_pInPlaceSiteWndless) {
            if (m_fInPlaceActive) {

                // theoretically, one should not need to call OnPosRectChange
                // here, but there appear to be a few host related issues that
                // will make us keep it here.  we won't, however, both with
                // windowless ole controls, since they are all new hosts who
                // should know better
                //
                GetWindowRect(m_hwnd, &rect);
                MapWindowPoints(NULL, m_hwndParent, (LPPOINT)&rect, 2);
                rect.right = rect.left + m_Size.cx;
                rect.bottom = rect.top + m_Size.cy;
                m_pInPlaceSite->OnPosRectChange(&rect);

                if (m_hwnd) {
                    // just go and resize
                    //
                    if (m_hwndReflect)
                        SetWindowPos(m_hwndReflect, 0, 0, 0, m_Size.cx, m_Size.cy,
                                     SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
                    SetWindowPos(m_hwnd, 0, 0, 0, m_Size.cx, m_Size.cy,
                                 SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
                }
            } else if (m_hwnd) {
                SetWindowPos(m_hwnd, NULL, 0, 0, m_Size.cx, m_Size.cy, SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
            } else {
                ViewChanged();
            }
        } else
            if (m_pInPlaceSite) m_pInPlaceSite->OnPosRectChange(&rect);

        // return code depending on whether or not user accepted given
        // size
        //
        return (f) ? S_OK : E_FAIL;

    } else {
        // we don't support any other aspects.
        //
        return DV_E_DVASPECT;
    }

    // dead code
    FAIL("This should be dead code");
}

//=--------------------------------------------------------------------------=
// COleControl::GetExtent    [IOleObject]
//=--------------------------------------------------------------------------=
// Retrieves the control's current display size.
//
// Parameters:
//    DWORD            - [in] aspect
//    SIZEL *          - [in] where to put results
//
// Output:
//    S_OK, E_INVALIDARG
//
// Notes:
//
STDMETHODIMP COleControl::GetExtent
(
    DWORD  dwDrawAspect,
    SIZEL *pSizeLOut
)
{

    if (dwDrawAspect & DVASPECT_CONTENT) {
        PixelToHiMetric((const SIZEL *)&m_Size, pSizeLOut);
        return S_OK;
    } else {
        return DV_E_DVASPECT;
    }

    // dead code
}

//=--------------------------------------------------------------------------=
// COleControl::Advise    [IOleObject]
//=--------------------------------------------------------------------------=
// establishes and advisory connection between the control and the container,
// in which the control will notify the container of certain events.
//
// Parameters:
//    IAdviseSink *     - [in]  advise sink of calling object
//    DWORD             - [out] cookie
//
// Output:
//    HRESULT           - S_OK, E_OUTOFMEMORY
//
// Notes:
//
STDMETHODIMP COleControl::Advise
(
    IAdviseSink *pAdviseSink,
    DWORD       *pdwConnection
)
{
    HRESULT hr;

    // if we haven't yet created a standard advise holder object, do so
    // now
    //
    if (!m_pOleAdviseHolder) {
        hr = CreateOleAdviseHolder(&m_pOleAdviseHolder);
        RETURN_ON_FAILURE(hr);
    }

    // just get it to do the work for us!
    //
    return m_pOleAdviseHolder->Advise(pAdviseSink, pdwConnection);
}

//=--------------------------------------------------------------------------=
// COleControl::Unadvise    [IOleObject]
//=--------------------------------------------------------------------------=
// Deletes a previously established advisory connection.
//
// Parameters:
//    DWORD         - [in] connection cookie
//
// Output:
//    HRESULT       - S_OK, E_FAIL, OLE_E_NOCONNECTION
//
// Notes:
//
STDMETHODIMP COleControl::Unadvise
(
    DWORD dwConnection
)
{
    if (!m_pOleAdviseHolder) {
        FAIL("Somebody called Unadvise on IOleObject without calling Advise!");
        CONNECT_E_NOCONNECTION;
    }

    return m_pOleAdviseHolder->Unadvise(dwConnection);
}

//=--------------------------------------------------------------------------=
// COleControl::EnumAdvise    [IOleObject]
//=--------------------------------------------------------------------------=
// Enumerates the advisory connections registered for an object, so a container
// can know what to release prior to closing down.
//
// Parameters:
//    IEnumSTATDATA **    - [out] where to put enumerator
//
// Output:
//    HRESULT             - S_OK, E_FAIL, E_NOTIMPL
//
// Notes:
//
STDMETHODIMP COleControl::EnumAdvise
(
    IEnumSTATDATA **ppEnumOut
)
{
    if (!m_pOleAdviseHolder) {
        FAIL("Somebody Called EnumAdvise without setting up any connections");
        *ppEnumOut = NULL;
        return E_FAIL;
    }

    return m_pOleAdviseHolder->EnumAdvise(ppEnumOut);
}

//=--------------------------------------------------------------------------=
// COleControl::GetMiscStatus    [IOleObject]
//=--------------------------------------------------------------------------=
// Returns a value indicating the status of an object at creation and loading.
//
// Parameters:
//    DWORD         - [in]  aspect desired
//    DWORD *       - [out] where to put the bits.
//
// Output:
//    HRESULT       - S_OK, OLE_S_USEREG, CO_E_CLASSNOTREG, CO_E_READREGDB
//
// Notes:
//
STDMETHODIMP COleControl::GetMiscStatus
(
    DWORD  dwAspect,
    DWORD *pdwStatus
)
{
    CHECK_POINTER(pdwStatus);

    if (dwAspect == DVASPECT_CONTENT) {
        *pdwStatus = OLEMISCFLAGSOFCONTROL(m_ObjectType);
        return S_OK;
    } else {
        return DV_E_DVASPECT;
    }

    // dead code
}

//=--------------------------------------------------------------------------=
// COleControl::SetColorScheme    [IOleObject]
//=--------------------------------------------------------------------------=
// Specifies the color palette that the object application should use when it
// edits the specified object.
//
// Parameters:
//    LOGPALETTE *     - [in] new palette
//
// Output:
//    HRESULT          - S_OK, E_NOTIMPL, OLE_E_PALETTE, OLE_E_NOTRUNNING
//
// Notes:
//    - we don't care.
//
STDMETHODIMP COleControl::SetColorScheme
(
    LOGPALETTE *pLogpal
)
{
    // OVERRIDE: control writers can use this if they want to
    //
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::GetWindow    [IOleWindow/IOleInPlaceObject]
//=--------------------------------------------------------------------------=
// Returns the window handle to one of the windows participating in in-place
// activation (frame, document, parent, or in-place object window).
//
// Parameters:
//    HWND *        - [out] where to return window handle.
//
// Output:
//    HRESULT       - S_OK, E_INVALIDARG, E_OUTOFMEMORY, E_UNEXPECTED, E_FAIL
//
// Notes:
//    - this routine has slightly different semantics for windowless controls
//
STDMETHODIMP COleControl::GetWindow
(
    HWND *phwnd
)

{
    // if we're windowles, then we want to return E_FAIL for this so hosts
    // know we're windowless
    //
    if (m_pInPlaceSiteWndless)
        return E_FAIL;

    // otherwise, just return our outer window.
    //
    *phwnd = GetOuterWindow();

    return (*phwnd) ? S_OK : E_UNEXPECTED;
}

//=--------------------------------------------------------------------------=
// COleControl::ContextSensitiveHelp    [IOleWindow/IOleInPlaceObject]
//=--------------------------------------------------------------------------=
// Determines whether context-sensitive help mode should be entered during an
// in-place activation session.
//
// Parameters:
//    BOOL            - [in] whether or not to enter help mode.
//
// Output:
//    HRESULT         - S_OK, E_INVALIDARG, E_OUTOFMEMORY, E_UNEXPECTED
//
// Notes:
//
STDMETHODIMP COleControl::ContextSensitiveHelp
(
    BOOL fEnterMode
)
{
    return E_NOTIMPL;
}

//=--------------------------------------------------------------------------=
// COleControl::InPlaceActivate
//=--------------------------------------------------------------------------=
// activates the control, and depending on the verb, optionally ui activates
// it as well.
//
// Parameters:
//    LONG         - [in] the verb that caused us to activate
//
// Output:
//    HRESULT
//
// Notes:
//    - this is spaghetti code at it's worst.  effectively, we have to
//      be able to handle three types of site pointers -- IOleInPlaceSIte,
//      IOleInPlaceSiteEx, and IOleInPlaceSiteWindowless.  not terribly
//      pretty.
//
HRESULT COleControl::InPlaceActivate
(
    LONG lVerb
)
{
    BOOL f;
    SIZEL sizel;
    IOleInPlaceSiteEx *pIPSEx = NULL;
    HRESULT hr;
    BOOL    fNoRedraw = FALSE;

    // if we don't have a client site, then there's not much to do.
    //
    if (!m_pClientSite)
        return S_OK;

    // get an InPlace site pointer.
    //
    if (!GetInPlaceSite()) {

        // if they want windowless support, then we want IOleInPlaceSiteWindowless
        //
        if (FCONTROLISWINDOWLESS(m_ObjectType))
            m_pClientSite->QueryInterface(IID_IOleInPlaceSiteWindowless, (void **)&m_pInPlaceSiteWndless);

        // if we're not able to do windowless siting, then we'll just get an
        // IOleInPlaceSite pointer.
        //
        if (!m_pInPlaceSiteWndless) {
            hr = m_pClientSite->QueryInterface(IID_IOleInPlaceSite, (void **)&m_pInPlaceSite);
            RETURN_ON_FAILURE(hr);
        }
    }

    // now, we want an IOleInPlaceSiteEx pointer for windowless and flicker free
    // activation.  if we're windowless, we've already got it, else we need to
    // try and get it
    //
    if (m_pInPlaceSiteWndless) {
        pIPSEx = (IOleInPlaceSiteEx *)m_pInPlaceSiteWndless;
        pIPSEx->AddRef();
    } else
        m_pClientSite->QueryInterface(IID_IOleInPlaceSiteEx, (void **)&pIPSEx);

    // if we're not already active, go and do it.
    //
    if (!m_fInPlaceActive) {
        OLEINPLACEFRAMEINFO InPlaceFrameInfo;
        RECT rcPos, rcClip;

        // if we have a windowless site, see if we can go in-place windowless
        // active
        //
        hr = S_FALSE;
        if (m_pInPlaceSiteWndless) {
            hr = m_pInPlaceSiteWndless->CanWindowlessActivate();
            CLEANUP_ON_FAILURE(hr);

            // if they refused windowless, we'll try windowed
            //
            if (S_OK != hr) {
                RELEASE_OBJECT(m_pInPlaceSiteWndless);
                hr = m_pClientSite->QueryInterface(IID_IOleInPlaceSite, (void **)&m_pInPlaceSite);
                CLEANUP_ON_FAILURE(hr);
            }
        }

        // just try regular windowed in-place activation
        //
        if (hr != S_OK) {
            hr = m_pInPlaceSite->CanInPlaceActivate();
            if (hr != S_OK) {
                hr = (FAILED(hr)) ? E_FAIL : hr;
                goto CleanUp;
            }
        }

        // if we are here, then we have permission to go in-place active.
        // now, announce our intentions to actually go ahead and do this.
        //
        hr = (pIPSEx) ? pIPSEx->OnInPlaceActivateEx(&fNoRedraw, (m_pInPlaceSiteWndless) ? ACTIVATE_WINDOWLESS : 0)
                       : m_pInPlaceSite->OnInPlaceActivate();
        CLEANUP_ON_FAILURE(hr);

        // if we're here, we're ready to go in-place active.  we just need
        // to set up some flags, and then create the window [if we have
        // one]
        //
        m_fInPlaceActive = TRUE;

        // we need to get some information about our location in the parent
        // window, as well as some information about the parent
        //
        InPlaceFrameInfo.cb = sizeof(OLEINPLACEFRAMEINFO);
        hr = GetInPlaceSite()->GetWindow(&m_hwndParent);
        if (SUCCEEDED(hr))
            hr = GetInPlaceSite()->GetWindowContext(&m_pInPlaceFrame, &m_pInPlaceUIWindow, &rcPos, &rcClip, &InPlaceFrameInfo);
        CLEANUP_ON_FAILURE(hr);

        // make sure we'll display ourselves in the correct location with the correct size
        //
        sizel.cx = rcPos.right - rcPos.left;
        sizel.cy = rcPos.bottom - rcPos.top;
        f = OnSetExtent(&sizel);
        if (f) m_Size = sizel;
        SetObjectRects(&rcPos, &rcClip);

        // finally, create our window if we have to!
        //
        if (!m_pInPlaceSiteWndless) {

            SetInPlaceParent(m_hwndParent);

            // create the window, and display it.  die horribly if we couldnt'
            //
            if (!CreateInPlaceWindow(rcPos.left, rcPos.top, fNoRedraw)) {
                hr = E_FAIL;
                goto CleanUp;
            }
        }
    }

    // don't need this any more
    //
    RELEASE_OBJECT(pIPSEx);

    // if we're not inplace visible yet, do so now.
    //
    if (!m_fInPlaceVisible)
        SetInPlaceVisible(TRUE);

    // if we weren't asked to UIActivate, then we're done.
    //
    if (lVerb != OLEIVERB_PRIMARY && lVerb != OLEIVERB_UIACTIVATE)
        return S_OK;

    // if we're not already UI active, do sow now.
    //
    if (!m_fUIActive) {
        m_fUIActive = TRUE;

        // inform the container of our intent
        //
        GetInPlaceSite()->OnUIActivate();

        // take the focus  [which is what UI Activation is all about !]
        //
        SetFocus(TRUE);

        // set ourselves up in the host.
        //
        m_pInPlaceFrame->SetActiveObject((IOleInPlaceActiveObject *)this, NULL);
        if (m_pInPlaceUIWindow)
            m_pInPlaceUIWindow->SetActiveObject((IOleInPlaceActiveObject *)this, NULL);

        // we have to explicitly say we don't wany any border space.
        //
        m_pInPlaceFrame->SetBorderSpace(NULL);
        if (m_pInPlaceUIWindow)
            m_pInPlaceUIWindow->SetBorderSpace(NULL);
    }

    // be-de-be-de-be-de that's all folks!
    //
    return S_OK;

  CleanUp:
    // something catastrophic happened [or, at least something bad].
    // die a horrible fiery mangled painful death.
    //
    QUICK_RELEASE(pIPSEx);
    m_fInPlaceActive = FALSE;
    return hr;

}

//=--------------------------------------------------------------------------=
// COleControl::InPlaceDeactivate    [IOleInPlaceObject]
//=--------------------------------------------------------------------------=
// Deactivates an active in-place object and discards the object's undo state.
//
// Output:
//    HRESULT        - S_OK, E_UNEXPECTED
//
// Notes:
//
STDMETHODIMP COleControl::InPlaceDeactivate
(
    void
)
{
    // if we're not in-place active yet, then this is easy.
    //
    if (!m_fInPlaceActive)
        return S_OK;

    // transition from UIActive back to active
    //
    if (m_fUIActive)
        UIDeactivate();

    m_fInPlaceActive = FALSE;
    m_fInPlaceVisible = FALSE;

    // if we have a window, tell it to go away.
    //
    if (m_hwnd) {
        ASSERT(!m_pInPlaceSiteWndless, "internal state really messed up");

        // so our window proc doesn't crash.
        //
        BeforeDestroyWindow();
        SetWindowLong(m_hwnd, GWL_USERDATA, 0xFFFFFFFF);
        DestroyWindow(m_hwnd);
        m_hwnd = NULL;

        if (m_hwndReflect) {
            SetWindowLong(m_hwndReflect, GWL_USERDATA, 0);
            DestroyWindow(m_hwndReflect);
            m_hwndReflect = NULL;
        }
    }

    RELEASE_OBJECT(m_pInPlaceFrame);
    RELEASE_OBJECT(m_pInPlaceUIWindow);
    GetInPlaceSite()->OnInPlaceDeactivate();
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::UIDeactivate    [IOleInPlaceObject]
//=--------------------------------------------------------------------------=
// transitions us from UI Active to merely being active [visible]  for
// a control, this doesn't mean all that much.
//
// Output:
//    HRESULT         - S_OK, E_UNEXPECTED
//
// Notes:
//
STDMETHODIMP COleControl::UIDeactivate
(
    void
)
{
    // if we're not UIActive, not much to do.
    //
    if (!m_fUIActive)
        return S_OK;

    m_fUIActive = FALSE;

    // notify frame windows, if appropriate, that we're no longer ui-active.
    //
    if (m_pInPlaceUIWindow) m_pInPlaceUIWindow->SetActiveObject(NULL, NULL);
    m_pInPlaceFrame->SetActiveObject(NULL, NULL);

    // we don't need to explicitly release the focus here since somebody
    // else grabbing the focus is what is likely to cause us to get lose it
    //
    GetInPlaceSite()->OnUIDeactivate(FALSE);

    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::SetObjectRects    [IOleInPlaceObject]
//=--------------------------------------------------------------------------=
// Indicates how much of the control is visible.
//
// Parameters:
//    LPCRECT        - [in] position of the control.
//    LPCRECT        - [in] clipping rectangle for the control.
//
// Output:
//    HRESULT        - S_OK, E_INVALIDARG, E_OUTOFMEMORY, E_UNEXPECTED
//
// Notes:
//
STDMETHODIMP COleControl::SetObjectRects
(
    LPCRECT prcPos,
    LPCRECT prcClip
)
{
    BOOL fRemoveWindowRgn;

    // move our window to the new location and handle clipping. not applicable
    // for windowless controls, since the container will be responsible for all
    // clipping.
    //
    if (m_hwnd) {
        fRemoveWindowRgn = m_fUsingWindowRgn;
        if (prcClip) {
            // the container wants us to clip, so figure out if we really
            // need to
            //
            RECT rcIXect;
            if ( IntersectRect(&rcIXect, prcPos, prcClip) ) {
                if (!EqualRect(&rcIXect, prcPos)) {
                    OffsetRect(&rcIXect, -(prcPos->left), -(prcPos->top));

                    HRGN tempRgn = CreateRectRgnIndirect(&rcIXect);
                    SetWindowRgn(GetOuterWindow(), tempRgn, TRUE);

                    if (m_hRgn != NULL)
                       DeleteObject(m_hRgn);
                    m_hRgn = tempRgn;

                    m_fUsingWindowRgn = TRUE;
                    fRemoveWindowRgn  = FALSE;
                }
            }
        }

        if (fRemoveWindowRgn) {
            SetWindowRgn(GetOuterWindow(), NULL, TRUE);
            if (m_hRgn != NULL)
            {
               DeleteObject(m_hRgn);
               m_hRgn = NULL;
            }
            m_fUsingWindowRgn = FALSE;
        }

        // set our control's location, but don't change it's size at all
        // [people for whom zooming is important should set that up here]
        //
        DWORD dwFlag;
        OnSetObjectRectsChangingWindowPos(&dwFlag);

        int cx, cy;
        cx = prcPos->right - prcPos->left;
        cy = prcPos->bottom - prcPos->top;
        SetWindowPos(GetOuterWindow(), NULL, prcPos->left, prcPos->top, cx, cy, dwFlag | SWP_NOZORDER | SWP_NOACTIVATE);
    }

    // save out our current location.  windowless controls want this more
    // that windowed ones do, but everybody can have it just in case
    //
    m_rcLocation = *prcPos;

    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::ReactivateAndUndo    [IOleInPlaceObject]
//=--------------------------------------------------------------------------=
// Reactivates a previously deactivated object, undoing the last state of the object.
//
// Output:
//    HRESULT        - S_OK, E_NOTUNDOABLE
//
// Notes:
//
STDMETHODIMP COleControl::ReactivateAndUndo
(
    void
)
{
    return E_NOTIMPL;
}

//=--------------------------------------------------------------------------=
// COleControl::OnWindowMessage    [IOleInPlaceObjectWindowless]
//=--------------------------------------------------------------------------=
// this method lets the container dispatch a message to a windowless OLE
// object.
//
// Parameters:
//    UINT                - [in]  the message
//    WPARAM              - [in]  the messages wparam
//    LPARAM              - [in]  duh.
//    LRESULT *           - [out] the output value
//
// Output:
//    HRESULT             - S_OK
//
// Notes:
//    - people should call m_pInPlaceSiteWndless->OnDefWindowMessage [control
//      writers should just call OcxDefWindowProc(msg, wparam, lparam)];
//
STDMETHODIMP COleControl::OnWindowMessage
(
    UINT     msg,
    WPARAM   wParam,
    LPARAM   lParam,
    LRESULT *plResult
)
{
    // little bit of pre-processing -- we need to handle some cases here
    // before passing the messages on
    //
    switch (msg) {
        // make sure our UI Activation correctly matches the focus
        //
        case WM_KILLFOCUS:
        case WM_SETFOCUS:
            // give the control site focus notification
            //
            if (m_fInPlaceActive && m_pControlSite)
                m_pControlSite->OnFocus(msg == WM_SETFOCUS);
            break;
    }

    // just pass it to the control's window proc.
    //
    *plResult = WindowProc(msg, wParam, lParam);
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::GetDropTarget    [IOleInPlaceObjectWindowless]
//=--------------------------------------------------------------------------=
// this method returns a pointer to the objects IDropTarget interface.  since
// they do not have a window, windowless objects cannot register an IDropTarget
// interface.
//
// Parameters:
//    IDropTarget **        - [out]
//
// Output:
//    HRESULT               - S_OK, E_NOTIMPL
//
// Notes:
//
STDMETHODIMP COleControl::GetDropTarget
(
    IDropTarget **ppDropTarget
)
{
    // OVERRIDE: if you want to do drag and drop and you're windowless,
    // override me.
    //
    return E_NOTIMPL;
}

//=--------------------------------------------------------------------------=
// COleControl::TranslateAccelerator    [IOleInPlaceActiveObject]
//=--------------------------------------------------------------------------=
// Processes menu accelerator-key messages from the container's message queue.
//
// Parameters:
//    LPMSG            - [in] the message that has the special key in it.
//
// Output:
//    HRESULT          - S_OK, S_FALSE, E_UNEXPECTED
//
// Notes:
//
STDMETHODIMP COleControl::TranslateAccelerator
(
    LPMSG    pmsg
)
{
    // see if we want it or not.
    //
    if (OnSpecialKey(pmsg))
        return S_OK;

    // if not, then we want to forward it back to the site for further processing
    //
    if (m_pControlSite)
        return m_pControlSite->TranslateAccelerator(pmsg, _SpecialKeyState());

    // we didn't want it.
    //
    return S_FALSE;
}

//=--------------------------------------------------------------------------=
// COleControl::OnFrameWindowActivate    [IOleInPlaceActiveObject]
//=--------------------------------------------------------------------------=
// Notifies the control when the container's top-level frame window is
// activated or deactivated.
//
// Parameters:
//    BOOL        - [in] state of containers top level window.
//
// Output:
//    HRESULT     - S_OK
//
// Notes:
//
STDMETHODIMP COleControl::OnFrameWindowActivate
(
    BOOL fActivate
)
{
    // we're supposed to go UI active in this case
    //
    return InPlaceActivate(OLEIVERB_UIACTIVATE);
}

//=--------------------------------------------------------------------------=
// COleControl::OnDocWindowActivate    [IOleInPlaceActiveObject]
//=--------------------------------------------------------------------------=
// Notifies the active control when the container's document window is
// activated or deactivated.
//
// Parameters:
//    BOOL            - state of mdi child window.
//
// Output:
//    HRESULT         - S_OK
//
// Notes:
//
STDMETHODIMP COleControl::OnDocWindowActivate
(
    BOOL fActivate
)
{
    // we're supposed to go UI active in this case
    //
    return InPlaceActivate(OLEIVERB_UIACTIVATE);
}

//=--------------------------------------------------------------------------=
// COleControl::ResizeBorder    [IOleInPlaceActiveObject]
//=--------------------------------------------------------------------------=
// Alerts the control that it needs to resize its border space.
//
// Parameters:
//    LPCRECT               - [in] new outer rectangle for border space
//    IOleInPlaceUIWindow * - [in] the document or frame who's border has changed
//    BOOL                  - [in] true if it was the fram window taht called.
//
// Output:
//    HRESULT               - S_OK
//
// Notes:
//
STDMETHODIMP COleControl::ResizeBorder
(
    LPCRECT              prcBorder,
    IOleInPlaceUIWindow *pInPlaceUIWindow,
    BOOL                 fFrame
)
{
    // this is largely uninteresting to us, since we have no border.
    //
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::EnableModeless    [IOleInPlaceActiveObject]
//=--------------------------------------------------------------------------=
// Enables or disables modeless dialog boxes when the container creates or
// destroys a modal dialog box.
//
// Parameters:
//    BOOL            - [in] enable or disable modeless dialogs.
//
// Output:
//    HRESULT         - S_OK
//
// Notes:
//
STDMETHODIMP COleControl::EnableModeless
(
    BOOL fEnable
)
{
    // phenomenally uninteresting
    //
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::GetClassInfo    [IProvideClassInfo]
//=--------------------------------------------------------------------------=
// returns the TypeInfo for the control's coclass.
//
// Parameters:
//    ITypeInfo **        - [out]
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::GetClassInfo
(
    ITypeInfo **ppTypeInfo
)
{
    ITypeLib *pTypeLib;
    HRESULT hr;

    CHECK_POINTER(ppTypeInfo);
    *ppTypeInfo = NULL;

    // go and get our type library.
    // CONSIDER: - go to the same sorta scheme that we use for TypeInfo caching.
    // CONSIDER: - consider trying to register our typelib if this fails.
    //
    hr = LoadRegTypeLib(*g_pLibid, (USHORT)VERSIONOFOBJECT(m_ObjectType), 0,
                        LANGIDFROMLCID(g_lcidLocale), &pTypeLib);
    RETURN_ON_FAILURE(hr);

    // got the typelib.  get typeinfo for our coclass.
    //
    hr = pTypeLib->GetTypeInfoOfGuid((REFIID)CLSIDOFOBJECT(m_ObjectType), ppTypeInfo);
    pTypeLib->Release();
    RETURN_ON_FAILURE(hr);

    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::ViewChange    [callable]
//=--------------------------------------------------------------------------=
// called whenever the view of the object has changed.
//
// Notes:
//
void COleControl::ViewChanged
(
    void
)
{
    // send the view change notification to anybody listening.
    //
    if (m_pViewAdviseSink) {
        m_pViewAdviseSink->OnViewChange(DVASPECT_CONTENT, -1);

        // if they only asked to be advised once, kill the connection
        //
        if (m_fViewAdviseOnlyOnce)
            SetAdvise(DVASPECT_CONTENT, 0, NULL);
    }
}

//=--------------------------------------------------------------------------=
// COleControl::SetInPlaceVisible    [helper]
//=--------------------------------------------------------------------------=
// controls the visibility of the control window.
//
// Parameters:
//    BOOL        - TRUE shows FALSE hides.
//
// Notes:
//
void COleControl::SetInPlaceVisible
(
    BOOL fShow
)
{
    BOOL fVisible;

    m_fInPlaceVisible = fShow;

    // don't do anything if we don't have a window.  otherwise, set it
    //
    if (m_hwnd) {
        fVisible = ((GetWindowLong(GetOuterWindow(), GWL_STYLE) & WS_VISIBLE) != 0);

        if (fVisible && !fShow)
            ShowWindow(GetOuterWindow(), SW_HIDE);
        else if (!fVisible && fShow)
            ShowWindow(GetOuterWindow(), SW_SHOWNA);
    }
}

//=--------------------------------------------------------------------------=
// COleControl::AmbientPropertyChanged    [overridable]
//=--------------------------------------------------------------------------=
// a method that derived controls can override to do whatever they want.
// we don't particularily care about this event.
//
// Parameters:
//    DISPID            - [in] dispid of prop that changed.
//
// Notes:
//
void COleControl::AmbientPropertyChanged
(
    DISPID dispid
)
{
    // do nothing
}

//=--------------------------------------------------------------------------=
// COleControl::DoCustomVerb    [overridable]
//=--------------------------------------------------------------------------=
// we were asked to execute a verb we don't know about right away.  see if
// it's a verb that the dervied-control defined.
//
// Parameters:
//    LONG            - [in] the verb.
//
// Output:
//    HRESULT         - S_OK, OLEOBJ_S_INVALIDVERB
//
// Notes:
//
HRESULT COleControl::DoCustomVerb
(
    LONG    lVerb
)
{
    return OLEOBJ_S_INVALIDVERB;
}

//=--------------------------------------------------------------------------=
// COleControl::OnSetExtent    [overridable]
//=--------------------------------------------------------------------------=
// let the user do something in the resize, if they care.
//
// Parameters:
//    SIZEL *        - [in] new values.
//
// Output:
//    BOOL           - FALSE means keep current size
//
// Notes:
//
BOOL COleControl::OnSetExtent
(
    const SIZEL *pSizeL
)
{
    return TRUE;
}

//=--------------------------------------------------------------------------=
// COleControl::OnSpecialKey    [overridable]
//=--------------------------------------------------------------------------=
// allows controls to handle special keys such as arrows, CTRL+, etc ...
//
// Parameters:
//    LPMSG        - [in] the special key msg.
//
// Output:
//    BOOL         - TRUE we processed it, FALSE we didn't.
//
// Notes:
//
BOOL COleControl::OnSpecialKey
(
    LPMSG pmsg
)
{
    // do nothing.
    //
    return FALSE;
}

//=--------------------------------------------------------------------------=
// COleControl::ModalDialog    [callable, utility]
//=--------------------------------------------------------------------------=
// should be called when the control is about to show and hide a modal dialog.
//
// Parameters:
//    BOOL        - [in] true means showing a modal dialog, false means done
//
// Notes:
//
void COleControl::ModalDialog
(
    BOOL fShow
)
{
    // notify the container of our intention to show a modal dialog...
    //
    if (m_pInPlaceFrame)
        m_pInPlaceFrame->EnableModeless(!fShow);
}

//=--------------------------------------------------------------------------=
// COleControl::BeforeDestroyWindow    [overridable]
//=--------------------------------------------------------------------------=
// called just before we destroy a window.  gives the user the opportunity to
// save information out, especially if they're a subclassed control, and this
// is an interesting thing to do.
//
// Notes:
//
void COleControl::BeforeDestroyWindow
(
    void
)
{
    // fweeee
}

//=--------------------------------------------------------------------------=
// COleControl::OnSetObjectRectsChangingWIndowPos    [overridable]
//=--------------------------------------------------------------------------=
// called just before we perform a SetWindowPos in the SetObjectRects
// function. gives a control the opportunity to change the flags.
//
// Notes:
//
void COleControl::OnSetObjectRectsChangingWindowPos(DWORD *dwFlag)
{
    *dwFlag = 0;
}

void COleControl::OnVerb(LONG lVerb)
{
}


CTLHELP.CPP

//=--------------------------------------------------------------------------=
// CtlHelp.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// helper routines for our COleControl implementation
//

#include "IPServer.H"
#include "CtrlObj.H"

#include "CtlHelp.H"
#include "Util.H"
#include <windows.h>

// for ASSERT and FAIL
//
SZTHISFILE

//=--------------------------------------------------------------------------=
// this is used by the window reflection code.
//
extern BYTE g_fRegisteredReflect;
extern char g_szReflectClassName [];

// define this here, since it's the only guid we really need to define in the
// framework -- the user control defines all other interesting guids.
//
static const GUID IID_IControlPrv =
{ 0xd97180, 0xfcf7, 0x11ce, { 0xa0, 0x9e, 0x0, 0xaa, 0x0, 0x62, 0xbe, 0x57 } };

// this table is used for copying data around, and persisting properties.
// basically, it contains the size of a given data type
//
const BYTE g_rgcbDataTypeSize[] = {
    0,                      // VT_EMPTY= 0,
    0,                      // VT_NULL= 1,
    sizeof(short),          // VT_I2= 2,
    sizeof(long),           // VT_I4 = 3,
    sizeof(float),          // VT_R4  = 4,
    sizeof(double),         // VT_R8= 5,
    sizeof(CURRENCY),       // VT_CY= 6,
    sizeof(DATE),           // VT_DATE = 7,
    sizeof(BSTR),           // VT_BSTR = 8,
    sizeof(IDispatch *),    // VT_DISPATCH    = 9,
    sizeof(SCODE),          // VT_ERROR    = 10,
    sizeof(VARIANT_BOOL),   // VT_BOOL    = 11,
    sizeof(VARIANT),        // VT_VARIANT= 12,
    sizeof(IUnknown *),     // VT_UNKNOWN= 13,
};

const BYTE g_rgcbPromotedDataTypeSize[] = {
    0,                      // VT_EMPTY= 0,
    0,                      // VT_NULL= 1,
    sizeof(int ),           // VT_I2= 2,
    sizeof(long),           // VT_I4 = 3,
    sizeof(double),         // VT_R4  = 4,
    sizeof(double),         // VT_R8= 5,
    sizeof(CURRENCY),       // VT_CY= 6,
    sizeof(DATE),           // VT_DATE = 7,
    sizeof(BSTR),           // VT_BSTR = 8,
    sizeof(IDispatch *),    // VT_DISPATCH    = 9,
    sizeof(SCODE),          // VT_ERROR    = 10,
    sizeof(int),            // VT_BOOL    = 11,
    sizeof(VARIANT),        // VT_VARIANT= 12,
    sizeof(IUnknown *),     // VT_UNKNOWN= 13,
};

//=--------------------------------------------------------------------------=
// _SpecialKeyState
//=--------------------------------------------------------------------------=
// returns a short with some information on which of the SHIFT, ALT, and CTRL
// keys are set.
//
// Output:
//    short        - bit 0 is shift, bit 1 is ctrl, bit 2 is ALT.
//
// Notes:
//
short _SpecialKeyState()
{
    // don't appear to be able to reduce number of calls to GetKeyState
    //
    BOOL bShift = (GetKeyState(VK_SHIFT) < 0);
    BOOL bCtrl  = (GetKeyState(VK_CONTROL) < 0);
    BOOL bAlt   = (GetKeyState(VK_MENU) < 0);

    return (short)(bShift + (bCtrl << 1) + (bAlt << 2));
}

//=--------------------------------------------------------------------------=
// CopyAndAddRefObject
//=--------------------------------------------------------------------------=
// copies an object pointer, and then addref's the object.
//
// Parameters:
//    void *        - [in] dest.
//    const void *  - [in] src
//    DWORD         - [in] size, ignored, since it's always 4
//
// Notes:
//
void WINAPI CopyAndAddRefObject
(
    void       *pDest,
    const void *pSource,
    DWORD       dwSize
)
{
    ASSERT(pDest && pSource, "Bogus Pointer(s) passed into CopyAndAddRefObject!!!!");

    *((IUnknown **)pDest) = *((IUnknown **)pSource);
    ADDREF_OBJECT(*((IUnknown **)pDest));

    return;
}

//=--------------------------------------------------------------------------=
// CopyOleVerb    [helper]
//=--------------------------------------------------------------------------=
// copies an OLEVERB structure.  used in CStandardEnum
//
// Parameters:
//    void *        - [out] where to copy to
//    const void *  - [in]  where to copy from
//    DWORD         - [in]  bytes to copy
//
// Notes:
//
void WINAPI CopyOleVerb
(
    void       *pvDest,
    const void *pvSrc,
    DWORD       cbCopy
)
{
    VERBINFO * pVerbDest = (VERBINFO *) pvDest;
    const VERBINFO * pVerbSrc = (const VERBINFO *) pvSrc;

    *pVerbDest = *pVerbSrc;
    ((OLEVERB *)pVerbDest)->lpszVerbName = OLESTRFROMRESID((WORD)((VERBINFO *)pvSrc)->idVerbName);
}

//=--------------------------------------------------------------------------=
// ControlFromUnknown    [helper, callable]
//=--------------------------------------------------------------------------=
// given an unknown, get the COleControl pointer for it.
//
// Parameters:
//    IUnknown *        - [in]
//
// Output:
//    HRESULT
//
// Notes:
//
COleControl *ControlFromUnknown
(
    IUnknown *pUnk
)
{
    COleControl *pCtl = NULL;

    if (!pUnk) return NULL;
    pUnk->QueryInterface(IID_IControlPrv, (void **)&pCtl);

    return pCtl;
}

//=--------------------------------------------------------------------------=
// CreateReflectWindow    [blech]
//=--------------------------------------------------------------------------=
// unfortunately, in certain cases, we have to create two windows, one of
// which exists strictly to reflect messages on to the control.  Majorly
// lame.  Fortunately, the number of hosts which require this is quite small.
//
// Parameters:
//    BOOL        - [in] should it be created visible?
//    HWND        - [in] parent window
//    int         - [in] x pos
//    int         - [in] y pos
//    SIZEL *     - [in] size
//
// Output:
//    HWND        - reflecting hwnd or NULL if it failed.
//
// Notes:
//
HWND CreateReflectWindow
(
    BOOL   fVisible,
    HWND   hwndParent,
    int    x,
    int    y,
    SIZEL *pSize
)
{
    WNDCLASS wndclass;

    // first thing to do is register the window class.  crit sect this
    // so we don't have to move it into the control
    //
    EnterCriticalSection(&g_CriticalSection);
    if (!g_fRegisteredReflect) {

        memset(&wndclass, 0, sizeof(wndclass));
        wndclass.lpfnWndProc = COleControl::ReflectWindowProc;
        wndclass.hInstance   = g_hInstance;
        wndclass.lpszClassName = g_szReflectClassName;

        if (!RegisterClass(&wndclass)) {
            FAIL("Couldn't Register Parking Window Class!");
            LeaveCriticalSection(&g_CriticalSection);
            return NULL;
        }
        g_fRegisteredReflect = TRUE;
    }

    LeaveCriticalSection(&g_CriticalSection);

    // go and create the window.
    //
    return CreateWindowEx(0, g_szReflectClassName, NULL,
                          WS_CHILD | WS_CLIPSIBLINGS |((fVisible) ? WS_VISIBLE : 0),
                          x, y, pSize->cx, pSize->cy,
                          hwndParent,
                          NULL, g_hInstance, NULL);
}

//=--------------------------------------------------------------------------=
// in case the user doesn't want our default window proc, we support
// letting them specify one themselves. this is defined in their main ipserver
// file.
//
extern WNDPROC g_ParkingWindowProc;

//=--------------------------------------------------------------------------=
// GetParkingWindow
//=--------------------------------------------------------------------------=
// creates the global parking window that we'll use to parent things, or
// returns the already existing one
//
// Output:
//    HWND                - our parking window
//
// Notes:
//
HWND GetParkingWindow
(
    void
)
{
    WNDCLASS wndclass;

    // crit sect this creation for apartment threading support.
    //
    EnterCriticalSection(&g_CriticalSection);
    if (g_hwndParking)
        goto CleanUp;

    ZeroMemory(&wndclass, sizeof(wndclass));
    wndclass.lpfnWndProc = (g_ParkingWindowProc) ? g_ParkingWindowProc : DefWindowProc;
    wndclass.hInstance   = g_hInstance;
    wndclass.lpszClassName = "CtlFrameWork_Parking";

    if (!RegisterClass(&wndclass)) {
        FAIL("Couldn't Register Parking Window Class!");
        goto CleanUp;
    }

    g_hwndParking = CreateWindow("CtlFrameWork_Parking", NULL, WS_POPUP, 0, 0, 0, 0, NULL, NULL, g_hInstance, NULL);
    if (g_hwndParking != NULL)
       ++g_cLocks;

    ASSERT(g_hwndParking, "Couldn't Create Global parking window!!");

  CleanUp:
    LeaveCriticalSection(&g_CriticalSection);
    return g_hwndParking;
}


CTLHELP.H

//=--------------------------------------------------------------------------=
// CtlHelp.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// routines to help out our implementation of COleControl.
//

#ifndef _CTLHELP_H_

//=--------------------------------------------------------------------------=
// these two tables are used to get information on sizes about data types
// the first is used mostly in persistence, while the second is used for
// events
//
extern const BYTE g_rgcbDataTypeSize [];
extern const BYTE g_rgcbPromotedDataTypeSize [];

//=--------------------------------------------------------------------------=
// misc functions
//
short       _SpecialKeyState(void);
void WINAPI CopyAndAddRefObject(void *, const void *, DWORD);
void WINAPI CopyOleVerb(void *, const void *, DWORD);

HWND        CreateReflectWindow(BOOL fVisible, HWND hwndParent, int, int, SIZEL *);
void        CleanupReflection();

//=--------------------------------------------------------------------------=
// little private guid we'll use to help identify our objects
// {00D97180-FCF7-11ce-A09E-00AA0062BE57}
//
#define Data1_IControlPrv0xd97180

DEFINE_GUID(IID_IControlPrv, 0xd97180, 0xfcf7, 0x11ce, 0xa0, 0x9e, 0x0, 0xaa, 0x0, 0x62, 0xbe, 0x57);

#define _CTLHELP_H_
#endif // _CTLHELP_H_


CTLMISC.CPP

//=--------------------------------------------------------------------------=
// CtlMisc.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// things that aren't elsewhere, such as property pages, and connection
// points.

#include "IPServer.H"
#include "CtrlObj.H"
#include "CtlHelp.H"
#include "Globals.H"
#include "StdEnum.H"
#include "Util.H"

#include <stdarg.h>

// for ASSERT and FAIL
//
SZTHISFILE

// this is used in our window proc so that we can find out who was last created
//
static COleControl *s_pLastControlCreated;

//=--------------------------------------------------------------------------=
// COleControl::COleControl
//=--------------------------------------------------------------------------=
// constructor
//
// Parameters:
//    IUnknown *          - [in] controlling Unknown
//    int                 - [in] type of primary dispatch interface OBJECT_TYPE_*
//    void *              - [in] pointer to entire object
//  BOOL                - [in] whether to enable IDispatchEx functionality
//to allow dynamic adding of properties
// Notes:
//
COleControl::COleControl
(
    IUnknown *pUnkOuter,
    int       iPrimaryDispatch,
    void     *pMainInterface,
BOOL     fExpandoEnabled
)
: CAutomationObject(pUnkOuter, iPrimaryDispatch, pMainInterface, fExpandoEnabled),
  m_cpEvents(SINK_TYPE_EVENT),
  m_cpPropNotify(SINK_TYPE_PROPNOTIFY)
{
    // initialize all our variables -- we decided against using a memory-zeroing
    // memory allocator, so we sort of have to do this work now ...
    //
    m_nFreezeEvents = 0;

    m_pClientSite = NULL;
    m_pControlSite = NULL;
    m_pInPlaceSite = NULL;
    m_pInPlaceFrame = NULL;
    m_pInPlaceUIWindow = NULL;

    m_pInPlaceSiteWndless = NULL;

    // certain hosts don't like 0,0 as your initial size, so we're going to set
    // our initial size to 100,50 [so it's at least sort of visible on the screen]
    //
    m_Size.cx = 0;
    m_Size.cy = 0;
    memset(&m_rcLocation, 0, sizeof(m_rcLocation));

    m_hRgn = NULL;
    m_hwnd = NULL;
    m_hwndParent = NULL;
    m_hwndReflect = NULL;
    m_fHostReflects = TRUE;
    m_fCheckedReflecting = FALSE;

    m_nFreezeEvents = 0;
    m_pSimpleFrameSite = NULL;
    m_pOleAdviseHolder = NULL;
    m_pViewAdviseSink = NULL;
    m_pDispAmbient = NULL;

    m_fDirty = FALSE;
    m_fModeFlagValid = FALSE;
    m_fInPlaceActive = FALSE;
    m_fInPlaceVisible = FALSE;
    m_fUIActive = FALSE;
    m_fSaveSucceeded = FALSE;
    m_fViewAdvisePrimeFirst = FALSE;
    m_fViewAdviseOnlyOnce = FALSE;
    m_fRunMode = FALSE;
}

//=--------------------------------------------------------------------------=
// COleControl::~COleControl
//=--------------------------------------------------------------------------=
// "We are all of us resigned to death; it's life we aren't resigned to."
//    - Graham Greene (1904-91)
//
// Notes:
//
COleControl::~COleControl()
{
    // if we've still got a window, go and kill it now.
    //
    if (m_hwnd) {
        // so our window proc doesn't crash.
        //
        SetWindowLong(m_hwnd, GWL_USERDATA, 0xFFFFFFFF);
        DestroyWindow(m_hwnd);
    }

    if (m_hwndReflect) {
        SetWindowLong(m_hwndReflect, GWL_USERDATA, 0);
        DestroyWindow(m_hwndReflect);
    }

    if (m_hRgn != NULL)
    {
       DeleteObject(m_hRgn);
       m_hRgn = NULL;
    }

    // clean up all the pointers we're holding around.
    //
    QUICK_RELEASE(m_pClientSite);
    QUICK_RELEASE(m_pControlSite);
    QUICK_RELEASE(m_pInPlaceSite);
    QUICK_RELEASE(m_pInPlaceFrame);
    QUICK_RELEASE(m_pInPlaceUIWindow);
    QUICK_RELEASE(m_pSimpleFrameSite);
    QUICK_RELEASE(m_pOleAdviseHolder);
    QUICK_RELEASE(m_pViewAdviseSink);
    QUICK_RELEASE(m_pDispAmbient);

    QUICK_RELEASE(m_pInPlaceSiteWndless);
}

#ifndef DEBUG
#pragma optimize("t", on)
#endif // DEBUG

//=--------------------------------------------------------------------------=
// COleControl::InternalQueryInterface
//=--------------------------------------------------------------------------=
// derived-controls should delegate back to this when they decide to support
// additional interfaces
//
// Parameters:
//    REFIID        - [in]  interface they want
//    void **       - [out] where they want to put the resulting object ptr.
//
// Output:
//    HRESULT       - S_OK, E_NOINTERFACE
//
// Notes:
//    - NOTE: this function is speed critical!!!!
//
HRESULT COleControl::InternalQueryInterface
(
    REFIID riid,
    void **ppvObjOut
)
{
    switch (riid.Data1) {
        // private interface for prop page support
        case Data1_IControlPrv:
          if(DO_GUIDS_MATCH(riid, IID_IControlPrv)) {
            *ppvObjOut = (void *)this;
            ExternalAddRef();
            return S_OK;
          }
          goto NoInterface;
        QI_INHERITS(this, IOleControl);
        QI_INHERITS(this, IPointerInactive);
        QI_INHERITS(this, IQuickActivate);
        QI_INHERITS(this, IOleObject);
        QI_INHERITS((IPersistStorage *)this, IPersist);
        QI_INHERITS(this, IPersistStreamInit);
        QI_INHERITS(this, IOleInPlaceObject);
        QI_INHERITS(this, IOleInPlaceObjectWindowless);
        QI_INHERITS((IOleInPlaceActiveObject *)this, IOleWindow);
        QI_INHERITS(this, IOleInPlaceActiveObject);
        QI_INHERITS(this, IViewObject);
        QI_INHERITS(this, IViewObject2);
        QI_INHERITS(this, IViewObjectEx);
        QI_INHERITS(this, IConnectionPointContainer);
        QI_INHERITS(this, ISpecifyPropertyPages);
        QI_INHERITS(this, IPersistStorage);
        QI_INHERITS(this, IPersistPropertyBag);
        QI_INHERITS(this, IProvideClassInfo);
        default:
            goto NoInterface;
    }

    // we like the interface, so addref and return
    //
    ((IUnknown *)(*ppvObjOut))->AddRef();
    return S_OK;

  NoInterface:
    // delegate to super-class for automation interfaces, etc ...
    //
    return CAutomationObject::InternalQueryInterface(riid, ppvObjOut);
}

#ifndef DEBUG
#pragma optimize("s", on)
#endif // DEBUG

//=--------------------------------------------------------------------------=
// COleControl::FindConnectionPoint    [IConnectionPointContainer]
//=--------------------------------------------------------------------------=
// given an IID, find a connection point sink for it.
//
// Parameters:
//    REFIID              - [in]  interfaces they want
//    IConnectionPoint ** - [out] where the cp should go
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::FindConnectionPoint
(
    REFIID             riid,
    IConnectionPoint **ppConnectionPoint
)
{
    CHECK_POINTER(ppConnectionPoint);

    // we support the event interface, and IDispatch for it, and we also
    // support IPropertyNotifySink.
    //
    if (DO_GUIDS_MATCH(riid, EVENTIIDOFCONTROL(m_ObjectType)) || DO_GUIDS_MATCH(riid, IID_IDispatch))
        *ppConnectionPoint = &m_cpEvents;
    else if (DO_GUIDS_MATCH(riid, IID_IPropertyNotifySink))
        *ppConnectionPoint = &m_cpPropNotify;
    else
        return E_NOINTERFACE;

    // generic post-processing.
    //
    (*ppConnectionPoint)->AddRef();
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::EnumConnectionPoints    [IConnectionPointContainer]
//=--------------------------------------------------------------------------=
// creates an enumerator for connection points.
//
// Parameters:
//    IEnumConnectionPoints **    - [out]
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::EnumConnectionPoints
(
    IEnumConnectionPoints **ppEnumConnectionPoints
)
{
    IConnectionPoint **rgConnectionPoints;

    CHECK_POINTER(ppEnumConnectionPoints);

    // HeapAlloc an array of connection points [since our standard enum
    // assumes this and HeapFree's it later ]
    //
    rgConnectionPoints = (IConnectionPoint **)HeapAlloc(g_hHeap, 0, sizeof(IConnectionPoint *) * 2);
    RETURN_ON_NULLALLOC(rgConnectionPoints);

    // we support the event interface for this dude as well as IPropertyNotifySink
    //
    rgConnectionPoints[0] = &m_cpEvents;
    rgConnectionPoints[1] = &m_cpPropNotify;

    *ppEnumConnectionPoints = (IEnumConnectionPoints *)(IEnumGeneric *) new CStandardEnum(IID_IEnumConnectionPoints,
                                2, sizeof(IConnectionPoint *), (void *)rgConnectionPoints,
                                CopyAndAddRefObject);
    if (!*ppEnumConnectionPoints) {
        HeapFree(g_hHeap, 0, rgConnectionPoints);
        return E_OUTOFMEMORY;
    }

    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::GetPages    [ISpecifyPropertyPages]
//=--------------------------------------------------------------------------=
// returns a counted array with the guids for our property pages.
//
// parameters:
//    CAUUID *    - [out] where to put the counted array.
//
// Output:
//    HRESULT
//
// NOtes:
//
STDMETHODIMP COleControl::GetPages
(
    CAUUID *pPages
)
{
    const GUID **pElems;
    void *pv;
    WORD  x;

    // if there are no property pages, this is actually pretty easy.
    //
    if (!CPROPPAGESOFCONTROL(m_ObjectType)) {
        pPages->cElems = 0;
        pPages->pElems = NULL;
        return S_OK;
    }

    // fill out the Counted array, using IMalloc'd memory.
    //
    pPages->cElems = CPROPPAGESOFCONTROL(m_ObjectType);
    pv = CoTaskMemAlloc(sizeof(GUID) * (pPages->cElems));
    RETURN_ON_NULLALLOC(pv);
    pPages->pElems = (GUID *)pv;

    // loop through our array of pages and get 'em.
    //
    pElems = PPROPPAGESOFCONTROL(m_ObjectType);
    for (x = 0; x < pPages->cElems; x++)
        pPages->pElems[x] = *(pElems[x]);

    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::CConnectionPoint::m_pOleControl
//=--------------------------------------------------------------------------=
// returns a pointer to the control in which we are nested.
//
// Output:
//    COleControl *
//
// Notes:
//
inline COleControl *COleControl::CConnectionPoint::m_pOleControl
(
    void
)
{
    return (COleControl *)((BYTE *)this - ((m_bType == SINK_TYPE_EVENT)
                                          ? offsetof(COleControl, m_cpEvents)
                                          : offsetof(COleControl, m_cpPropNotify)));
}

//=--------------------------------------------------------------------------=
// COleControl::CConnectionPoint::QueryInterface
//=--------------------------------------------------------------------------=
// standard qi
//
// Parameters:
//    REFIID        - [in]  interface they want
//    void **       - [out] where they want to put the resulting object ptr.
//
// Output:
//    HRESULT       - S_OK, E_NOINTERFACE
//
// Notes:
//
STDMETHODIMP COleControl::CConnectionPoint::QueryInterface
(
    REFIID riid,
    void **ppvObjOut
)
{
    if (DO_GUIDS_MATCH(riid, IID_IConnectionPoint) || DO_GUIDS_MATCH(riid, IID_IUnknown)) {
        *ppvObjOut = (IConnectionPoint *)this;
        AddRef();
        return S_OK;
    }

    return E_NOINTERFACE;
}

//=--------------------------------------------------------------------------=
// COleControl::CConnectionPoint::AddRef
//=--------------------------------------------------------------------------=
//
// Output:
//    ULONG        - the new reference count
//
// Notes:
//
ULONG COleControl::CConnectionPoint::AddRef
(
    void
)
{
    return m_pOleControl()->ExternalAddRef();
}

//=--------------------------------------------------------------------------=
// COleControl::CConnectionPoint::Release
//=--------------------------------------------------------------------------=
//
// Output:
//    ULONG         - remaining refs
//
// Notes:
//
ULONG COleControl::CConnectionPoint::Release
(
    void
)
{
    return m_pOleControl()->ExternalRelease();
}

//=--------------------------------------------------------------------------=
// COleControl::CConnectionPoint::GetConnectionInterface
//=--------------------------------------------------------------------------=
// returns the interface we support connections on.
//
// Parameters:
//    IID *        - [out] interface we support.
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::CConnectionPoint::GetConnectionInterface
(
    IID *piid
)
{
    if (m_bType == SINK_TYPE_EVENT)
        *piid = EVENTIIDOFCONTROL(m_pOleControl()->m_ObjectType);
    else
        *piid = IID_IPropertyNotifySink;

    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::CConnectionPoint::GetConnectionPointContainer
//=--------------------------------------------------------------------------=
// returns the connection point container
//
// Parameters:
//    IConnectionPointContainer **ppCPC
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::CConnectionPoint::GetConnectionPointContainer
(
    IConnectionPointContainer **ppCPC
)
{
    return m_pOleControl()->ExternalQueryInterface(IID_IConnectionPointContainer, (void **)ppCPC);
}

//=--------------------------------------------------------------------------=
// COleControl::CConnectiontPoint::Advise
//=--------------------------------------------------------------------------=
// someboyd wants to be advised when something happens.
//
// Parameters:
//    IUnknown *        - [in]  guy who wants to be advised.
//    DWORD *           - [out] cookie
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::CConnectionPoint::Advise
(
    IUnknown *pUnk,
    DWORD    *pdwCookie
)
{
    HRESULT    hr;
    void      *pv;

    CHECK_POINTER(pdwCookie);

    // first, make sure everybody's got what they thinks they got
    //
    if (m_bType == SINK_TYPE_EVENT) {

        // CONSIDER: 12.95 -- this theoretically is broken -- if they do a find
        // connection point on IDispatch, and they just happened to also support
        // the Event IID, we'd advise on that.  this is not awesome, but will
        // prove entirely acceptable short term.
        //
        hr = pUnk->QueryInterface(EVENTIIDOFCONTROL(m_pOleControl()->m_ObjectType), &pv);
        if (FAILED(hr))
            hr = pUnk->QueryInterface(IID_IDispatch, &pv);
    }
    else
        hr = pUnk->QueryInterface(IID_IPropertyNotifySink, &pv);
    RETURN_ON_FAILURE(hr);

    // finally, add the sink.  it's now been cast to the correct type and has
    // been AddRef'd.
    //
    return AddSink(pv, pdwCookie);
}

//=--------------------------------------------------------------------------=
// COleControl::CConnectionPoint::AddSink
//=--------------------------------------------------------------------------=
// in some cases, we'll already have done the QI, and won't need to do the
// work that is done in the Advise routine above.  thus, these people can
// just call this instead. [this stems really from IQuickActivate]
//
// Parameters:
//    void *        - [in]  the sink to add. it's already been addref'd
//    DWORD *       - [out] cookie
//
// Output:
//    HRESULT
//
// Notes:
//
HRESULT COleControl::CConnectionPoint::AddSink
(
    void  *pv,
    DWORD *pdwCookie
)
{
    IUnknown **rgUnkNew;
    int        i = 0;

    // we optimize the case where there is only one sink to not allocate
    // any storage.  turns out very rarely is there more than one.
    //
    switch (m_cSinks) {

        case 0:
            ASSERT(!m_rgSinks, "this should be null when there are no sinks");
            m_rgSinks = (IUnknown **)pv;
            break;

        case 1:
            // go ahead and do the initial allocation.  we'll get 8 at a time
            //
            rgUnkNew = (IUnknown **)HeapAlloc(g_hHeap, 0, 8 * sizeof(IUnknown *));
            RETURN_ON_NULLALLOC(rgUnkNew);
            rgUnkNew[0] = (IUnknown *)m_rgSinks;
            rgUnkNew[1] = (IUnknown *)pv;
            m_rgSinks = rgUnkNew;
            break;

        default:
            // if we're out of sinks, then we have to increase the size
            // of the array
            //
            if (!(m_cSinks & 0x7)) {
                rgUnkNew = (IUnknown **)HeapReAlloc(g_hHeap, 0, m_rgSinks, (m_cSinks + 8) * sizeof(IUnknown *));
                RETURN_ON_NULLALLOC(rgUnkNew);
                m_rgSinks = rgUnkNew;
            } else
                rgUnkNew = m_rgSinks;

            rgUnkNew[m_cSinks] = (IUnknown *)pv;
            break;
    }

    *pdwCookie = (DWORD)pv;
    m_cSinks++;
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::CConnectionPoint::Unadvise
//=--------------------------------------------------------------------------=
// they don't want to be told any more.
//
// Parameters:
//    DWORD        - [in]  the cookie we gave 'em.
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::CConnectionPoint::Unadvise
(
    DWORD dwCookie
)
{
    IUnknown *pUnk;
    int       x;

    if (!dwCookie)
        return S_OK;

    // see how many sinks we've currently got, and deal with things based
    // on that.
    //
    switch (m_cSinks) {
        case 1:
            // it's the only sink.  make sure the ptrs are the same, and
            // then free things up
            //
            if ((DWORD)m_rgSinks != dwCookie)
                return CONNECT_E_NOCONNECTION;
            m_rgSinks = NULL;
            break;

        case 2:
            // there are two sinks.  go back down to one sink scenario
            //
            if ((DWORD)m_rgSinks[0] != dwCookie && (DWORD)m_rgSinks[1] != dwCookie)
                return CONNECT_E_NOCONNECTION;

            pUnk = ((DWORD)m_rgSinks[0] == dwCookie)
                   ? m_rgSinks[1]
                   : ((DWORD)m_rgSinks[1] == dwCookie) ? m_rgSinks[0] : NULL;

            if (!pUnk) return CONNECT_E_NOCONNECTION;

            HeapFree(g_hHeap, 0, m_rgSinks);
            m_rgSinks = (IUnknown **)pUnk;
            break;

        default:
            // there are more than two sinks.  just clean up the hole we've
            // got in our array now.
            //
            for (x = 0; x < m_cSinks; x++) {
                if ((DWORD)m_rgSinks[x] == dwCookie)
                    break;
            }
            if (x == m_cSinks) return CONNECT_E_NOCONNECTION;
            if (x < m_cSinks - 1)
                memcpy(&(m_rgSinks[x]), &(m_rgSinks[x + 1]), (m_cSinks -1 - x) * sizeof(IUnknown *));
            else
                m_rgSinks[x] = NULL;
            break;
    }

    // we're happy
    //
    m_cSinks--;
    ((IUnknown *)dwCookie)->Release();
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::CConnectionPoint::EnumConnections
//=--------------------------------------------------------------------------=
// enumerates all current connections
//
// Paramters:
//    IEnumConnections ** - [out] new enumerator object
//
// Output:
//    HRESULT
//
// NOtes:
//
STDMETHODIMP COleControl::CConnectionPoint::EnumConnections
(
    IEnumConnections **ppEnumOut
)
{
    CONNECTDATA *rgConnectData = NULL;
    int i;

    if (m_cSinks) {
        // allocate some memory big enough to hold all of the sinks.
        //
        rgConnectData = (CONNECTDATA *)HeapAlloc(g_hHeap, 0, m_cSinks * sizeof(CONNECTDATA));
        RETURN_ON_NULLALLOC(rgConnectData);

        // fill in the array
        //
        if (m_cSinks == 1) {
            rgConnectData[0].pUnk = (IUnknown *)m_rgSinks;
            rgConnectData[0].dwCookie = (DWORD)m_rgSinks;
        } else {
            // loop through all available sinks.
            //
            for (i = 0; i < m_cSinks; i++) {
                rgConnectData[i].pUnk = m_rgSinks[i];
                rgConnectData[i].dwCookie = (DWORD)m_rgSinks[i];
            }
        }
    }

    // create yon enumerator object.
    //
    *ppEnumOut = (IEnumConnections *)(IEnumGeneric *)new CStandardEnum(IID_IEnumConnections,
                        m_cSinks, sizeof(CONNECTDATA), rgConnectData, CopyAndAddRefObject);
    if (!*ppEnumOut) {
        HeapFree(g_hHeap, 0, rgConnectData);
        return E_OUTOFMEMORY;
    }

    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::CConnectionPoint::~CConnectionPoint
//=--------------------------------------------------------------------------=
// cleans up
//
// Notes:
//
COleControl::CConnectionPoint::~CConnectionPoint ()
{
    int x;

    // clean up some memory stuff
    //
    if (!m_cSinks)
        return;
    else if (m_cSinks == 1)
        ((IUnknown *)m_rgSinks)->Release();
    else {
        for (x = 0; x < m_cSinks; x++)
            QUICK_RELEASE(m_rgSinks[x]);
        HeapFree(g_hHeap, 0, m_rgSinks);
    }
}

//=--------------------------------------------------------------------------=
// COleControl::CConnectionPiont::DoInvoke
//=--------------------------------------------------------------------------=
// fires an event to all listening on our event interface.
//
// Parameters:
//    DISPID            - [in] event to fire.
//    DISPPARAMS        - [in]
//
// Notes:
//
void COleControl::CConnectionPoint::DoInvoke
(
    DISPID      dispid,
    DISPPARAMS *pdispparams
)
{
    int iConnection;

    // if we don't have any sinks, then there's nothing to do.  we intentionally
    // ignore errors here.
    //
    if (m_cSinks == 0)
        return;
    else if (m_cSinks == 1)
        ((IDispatch *)m_rgSinks)->Invoke(dispid, IID_NULL, 0, DISPATCH_METHOD, pdispparams, NULL, NULL, NULL);
    else
        for (iConnection = 0; iConnection < m_cSinks; iConnection++)
            ((IDispatch *)m_rgSinks[iConnection])->Invoke(dispid, IID_NULL, 0, DISPATCH_METHOD, pdispparams, NULL, NULL, NULL);
}

//=--------------------------------------------------------------------------=
// COleControl::CConnectionPoint::DoOnChanged
//=--------------------------------------------------------------------------=
// fires the OnChanged event for IPropertyNotifySink listeners.
//
// Parameters:
//    DISPID            - [in] dude that changed.
//
// Output:
//    none
//
// Notes:
//
void COleControl::CConnectionPoint::DoOnChanged
(
    DISPID dispid
)
{
    int iConnection;

    // if we don't have any sinks, then there's nothing to do.
    //
    if (m_cSinks == 0)
        return;
    else if (m_cSinks == 1)
        ((IPropertyNotifySink *)m_rgSinks)->OnChanged(dispid);
    else
        for (iConnection = 0; iConnection < m_cSinks; iConnection++)
            ((IPropertyNotifySink *)m_rgSinks[iConnection])->OnChanged(dispid);
}

//=--------------------------------------------------------------------------=
// COleControl::CConnectionPoint::DoOnRequestEdit
//=--------------------------------------------------------------------------=
// fires the OnRequestEdit for IPropertyNotifySinkListeners
//
// Parameters:
//    DISPID             - [in] dispid user wants to change.
//
// Output:
//    BOOL               - false means you cant
//
// Notes:
//
BOOL COleControl::CConnectionPoint::DoOnRequestEdit
(
    DISPID dispid
)
{
    HRESULT hr;
    int     iConnection;

    // if we don't have any sinks, then there's nothing to do.
    //
    if (m_cSinks == 0)
        hr = S_OK;
    else if (m_cSinks == 1)
        hr =((IPropertyNotifySink *)m_rgSinks)->OnRequestEdit(dispid);
    else {
        for (iConnection = 0; iConnection < m_cSinks; iConnection++) {
            hr = ((IPropertyNotifySink *)m_rgSinks[iConnection])->OnRequestEdit(dispid);
            if (hr != S_OK) break;
        }
    }

    return (hr == S_OK) ? TRUE : FALSE;
}

//=--------------------------------------------------------------------------=
// COleControl::CreateInPlaceWindow
//=--------------------------------------------------------------------------=
// creates the window with which we will be working.
// yay.
//
// Parameters:
//    int            - [in] left
//    int            - [in] top
//    BOOL           - [in] can we skip redrawing?
//
// Output:
//    HWND
//
// Notes:
//    - DANGER! DANGER!  this function is protected so that anybody can call it
//      from their control.  however, people should be extremely careful of when
//      and why they do this.  preferably, this function would only need to be
//      called by an end-control writer in design mode to take care of some
//      hosting/painting issues.  otherwise, the framework should be left to
//      call it when it wants.
//
HWND COleControl::CreateInPlaceWindow
(
    int  x,
    int  y,
    BOOL fNoRedraw
)
{
    BOOL    fVisible;
    HRESULT hr;
    DWORD   dwWindowStyle, dwExWindowStyle;
    char    szWindowTitle[128];

    // if we've already got a window, do nothing.
    //
    if (m_hwnd)
        return m_hwnd;

    // get the user to register the class if it's not already
    // been done.  we have to critical section this since more than one thread
    // can be trying to create this control
    //
    EnterCriticalSection(&g_CriticalSection);
    if (!CTLWNDCLASSREGISTERED(m_ObjectType)) {
        if (!RegisterClassData()) {
            LeaveCriticalSection(&g_CriticalSection);
            return NULL;
        } else
            CTLWNDCLASSREGISTERED(m_ObjectType) = TRUE;
    }
    LeaveCriticalSection(&g_CriticalSection);

    // let the user set up things like the window title, the
    // style, and anything else they feel interested in fiddling
    // with.
    //
    dwWindowStyle = dwExWindowStyle = 0;
    szWindowTitle[0] = '\0';
    if (!BeforeCreateWindow(&dwWindowStyle, &dwExWindowStyle, szWindowTitle))
        return NULL;

    dwWindowStyle |= (WS_CHILD | WS_CLIPSIBLINGS);

    // create window visible if parent hidden (common case)
    // otherwise, create hidden, then shown.  this is a little subtle, but
    // it makes sense eventually.
    //
    if (!m_hwndParent)
        m_hwndParent = GetParkingWindow();

    fVisible = IsWindowVisible(m_hwndParent);

    // This one kinda sucks -- if a control is subclassed, and we're in
    // a host that doesn't support Message Reflecting, we have to create
    // the user window in another window which will do all the reflecting.
    // VERY blech. [don't however, bother in design mode]
    //
    if (SUBCLASSWNDPROCOFCONTROL(m_ObjectType) && (m_hwndParent != GetParkingWindow())) {
        // determine if the host supports message reflecting.
        //
        if (!m_fCheckedReflecting) {
            VARIANT_BOOL f;
            hr = GetAmbientProperty(DISPID_AMBIENT_MESSAGEREFLECT, VT_BOOL, &f);
            if (FAILED(hr) || !f)
                m_fHostReflects = FALSE;
            m_fCheckedReflecting = TRUE;
        }

        // if the host doesn't support reflecting, then we have to create
        // an extra window around the control window, and then parent it
        // off that.
        //
        if (!m_fHostReflects) {
            ASSERT(m_hwndReflect == NULL, "Where'd this come from?");
            m_hwndReflect = CreateReflectWindow(!fVisible, m_hwndParent, x, y, &m_Size);
            if (!m_hwndReflect)
                return NULL;
            SetWindowLong(m_hwndReflect, GWL_USERDATA, (long)this);
            dwWindowStyle |= WS_VISIBLE;
        }
    } else {
        if (!fVisible)
            dwWindowStyle |= WS_VISIBLE;
    }

    // we have to mutex the entire create window process since we need to use
    // the s_pLastControlCreated to pass in the object pointer.  nothing too
    // serious
    //
    EnterCriticalSection(&g_CriticalSection);
    s_pLastControlCreated = this;
    m_fCreatingWindow = TRUE;

    // finally, go create the window, parenting it as appropriate.
    //
    m_hwnd = CreateWindowEx(dwExWindowStyle,
                            WNDCLASSNAMEOFCONTROL(m_ObjectType),
                            szWindowTitle,
                            dwWindowStyle,
                            (m_hwndReflect) ? 0 : x,
                            (m_hwndReflect) ? 0 : y,
                            m_Size.cx, m_Size.cy,
                            (m_hwndReflect) ? m_hwndReflect : m_hwndParent,
                            NULL, g_hInstance, NULL);

    // clean up some variables, and leave the critical section
    //
    m_fCreatingWindow = FALSE;
    s_pLastControlCreated = NULL;
    LeaveCriticalSection(&g_CriticalSection);

    if (m_hwnd) {
        // let the derived-control do something if they so desire
        //
        if (!AfterCreateWindow()) {
            BeforeDestroyWindow();
            SetWindowLong(m_hwnd, GWL_USERDATA, 0xFFFFFFFF);
            DestroyWindow(m_hwnd);
            m_hwnd = NULL;
            return m_hwnd;
        }

        // if we didn't create the window visible, show it now.
        //

        if (fVisible)
{
if (GetParent(m_hwnd) != m_hwndParent)
// SetWindowPos fails if parent hwnd is passed in so keep
// this behaviour in those cases without ripping.
SetWindowPos(m_hwnd, m_hwndParent, 0, 0, 0, 0,
 SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_SHOWWINDOW | ((fNoRedraw) ? SWP_NOREDRAW : 0));
}
    }

    // finally, tell the host of this
    //
    if (m_pClientSite)
        m_pClientSite->ShowObject();

    return m_hwnd;
}

//=--------------------------------------------------------------------------=
// COleControl::SetInPlaceParent    [helper]
//=--------------------------------------------------------------------------=
// sets up the parent window for our control.
//
// Parameters:
//    HWND            - [in] new parent window
//
// Notes:
//
void COleControl::SetInPlaceParent
(
    HWND hwndParent
)
{
    ASSERT(!m_pInPlaceSiteWndless, "This routine should only get called for windowed OLE controls");

    if (m_hwndParent == hwndParent)
        return;

    m_hwndParent = hwndParent;
    if (m_hwnd)
        SetParent(GetOuterWindow(), hwndParent);
}

//=--------------------------------------------------------------------------=
// COleControl::ControlWindowProc
//=--------------------------------------------------------------------------=
// default window proc for an OLE Control.   controls will have their own
// window proc called from this one, after some processing is done.
//
// Parameters:
//    - see win32sdk docs.
//
// Notes:
//
LRESULT CALLBACK COleControl::ControlWindowProc
(
    HWND    hwnd,
    UINT    msg,
    WPARAM  wParam,
    LPARAM  lParam
)
{
    COleControl *pCtl = ControlFromHwnd(hwnd);
    HRESULT hr;
    LRESULT lResult;
    DWORD   dwCookie;

    // if the value isn't a positive value, then it's in some special
    // state [creation or destruction]  this is safe because under win32,
    // the upper 2GB of an address space aren't available.
    //
    if ((LONG)pCtl == 0) {
        pCtl = s_pLastControlCreated;
        SetWindowLong(hwnd, GWL_USERDATA, (LONG)pCtl);
        pCtl->m_hwnd = hwnd;
    } else if ((ULONG)pCtl == 0xffffffff) {
        return DefWindowProc(hwnd, msg, wParam, lParam);
    }

    // message preprocessing
    //
    if (pCtl->m_pSimpleFrameSite) {
        hr = pCtl->m_pSimpleFrameSite->PreMessageFilter(hwnd, msg, wParam, lParam, &lResult, &dwCookie);
        if (hr == S_FALSE) return lResult;
    }

    // for certain messages, do not call the user window proc. instead,
    // we have something else we'd like to do.
    //
    switch (msg) {
      case WM_PAINT:
        {
        // call the user's OnDraw routine.
        //
        PAINTSTRUCT ps;
        RECT        rc;
        HDC         hdc;

        // if we're given an HDC, then use it
        //
        if (!wParam)
            hdc = BeginPaint(hwnd, &ps);
        else
            hdc = (HDC)wParam;

        GetClientRect(hwnd, &rc);
        pCtl->OnDraw(DVASPECT_CONTENT, hdc, (RECTL *)&rc, NULL, NULL, TRUE);

        if (!wParam)
            EndPaint(hwnd, &ps);
        }
        break;

      default:
        // call the derived-control's window proc
        //
        lResult = pCtl->WindowProc(msg, wParam, lParam);
        break;
    }

    // message postprocessing
    //
    switch (msg) {

      case WM_NCDESTROY:
        // after this point, the window doesn't exist any more
        //
        pCtl->m_hwnd = NULL;
        break;

      case WM_SETFOCUS:
      case WM_KILLFOCUS:
        // give the control site focus notification
        //
        if (pCtl->m_fInPlaceActive && pCtl->m_pControlSite)
            pCtl->m_pControlSite->OnFocus(msg == WM_SETFOCUS);
        break;

      case WM_SIZE:
        // a change in size is a change in view
        //
        if (!pCtl->m_fCreatingWindow)
            pCtl->ViewChanged();
        break;
    }

    // lastly, simple frame postmessage processing
    //
    if (pCtl->m_pSimpleFrameSite)
        pCtl->m_pSimpleFrameSite->PostMessageFilter(hwnd, msg, wParam, lParam, &lResult, dwCookie);

    return lResult;
}

//=--------------------------------------------------------------------------=
// COleControl::SetFocus
//=--------------------------------------------------------------------------=
// we have to override this routine to get UI Activation correct.
//
// Parameters:
//    BOOL              - [in] true means take, false release
//
// Output:
//    BOOL
//
// Notes:
//    - CONSIDER: this is pretty messy, and it's still not entirely clear
//      what the ole control/focus story is.
//
BOOL COleControl::SetFocus
(
    BOOL fGrab
)
{
    HRESULT hr;
    HWND    hwnd;

    // first thing to do is check out UI Activation state, and then set
    // focus [either with windows api, or via the host for windowless
    // controls]
    //
    if (m_pInPlaceSiteWndless) {
        if (!m_fUIActive && fGrab)
            if (FAILED(InPlaceActivate(OLEIVERB_UIACTIVATE))) return FALSE;

        hr = m_pInPlaceSiteWndless->SetFocus(fGrab);
        return (hr == S_OK) ? TRUE : FALSE;
    } else {

        // we've got a window.
        //
        if (m_fInPlaceActive) {
            hwnd = (fGrab) ? m_hwnd : m_hwndParent;
            if (!m_fUIActive && fGrab)
                return SUCCEEDED(InPlaceActivate(OLEIVERB_UIACTIVATE));
            else
                return SetGUIFocus(hwnd);
        } else
            return FALSE;
    }

    // dead code
}

//=--------------------------------------------------------------------------=
// COleControl::SetGUIFocus
//=--------------------------------------------------------------------------=
// does the work of setting the Windows GUI focus to the specified window
//
// Parameters:
//    HWND              - [in] window that should get focus
//
// Output:
//    BOOL              - [out] whether setting focus succeeded
//
// Notes:
//    we do this because some controls host non-ole window hierarchies, like
// the Netscape plugin ocx.  in such cases, the control may need to be UIActive
// to function properly in the document, but cannot take the windows focus
// away from one of its child windows.  such controls may override this method
// and respond as appropriate.
//
BOOL COleControl::SetGUIFocus
(
    HWND hwndSet
)
{
    return (::SetFocus(hwndSet) == hwndSet);
}

//=--------------------------------------------------------------------------=
// COleControl::ReflectWindowProc
//=--------------------------------------------------------------------------=
// reflects window messages on to the child window.  very lame.
//
// Parameters and Output:
//    - see win32 sdk docs
//
// Notes:
//
LRESULT CALLBACK COleControl::ReflectWindowProc
(
    HWND    hwnd,
    UINT    msg,
    WPARAM  wParam,
    LPARAM  lParam
)
{
    COleControl *pCtl;

    switch (msg) {
        case WM_COMMAND:
        case WM_NOTIFY:
        case WM_CTLCOLORBTN:
        case WM_CTLCOLORDLG:
        case WM_CTLCOLOREDIT:
        case WM_CTLCOLORLISTBOX:
        case WM_CTLCOLORMSGBOX:
        case WM_CTLCOLORSCROLLBAR:
        case WM_CTLCOLORSTATIC:
        case WM_DRAWITEM:
        case WM_MEASUREITEM:
        case WM_DELETEITEM:
        case WM_VKEYTOITEM:
        case WM_CHARTOITEM:
        case WM_COMPAREITEM:
        case WM_HSCROLL:
        case WM_VSCROLL:
        case WM_PARENTNOTIFY:
        case WM_SETFOCUS:
        case WM_SIZE:
            pCtl = (COleControl *)GetWindowLong(hwnd, GWL_USERDATA);
            if (pCtl)
                return SendMessage(pCtl->m_hwnd, OCM__BASE + msg, wParam, lParam);
            break;
    }

    return DefWindowProc(hwnd, msg, wParam, lParam);
}

//=--------------------------------------------------------------------------=
// COleControl::GetAmbientProperty    [callable]
//=--------------------------------------------------------------------------=
// returns the value of an ambient property
//
// Parameters:
//    DISPID        - [in]  property to get
//    VARTYPE       - [in]  type of desired data
//    void *        - [out] where to put the data
//
// Output:
//    BOOL          - FALSE means didn't work.
//
// Notes:
//
BOOL COleControl::GetAmbientProperty
(
    DISPID  dispid,
    VARTYPE vt,
    void   *pData
)
{
    DISPPARAMS dispparams;
    VARIANT v, v2;
    HRESULT hr;

    v.vt = VT_EMPTY;
    v.lVal = 0;
    v2.vt = VT_EMPTY;
    v2.lVal = 0;

    // get a pointer to the source of ambient properties.
    //
    if (!m_pDispAmbient) {
        if (m_pClientSite)
            m_pClientSite->QueryInterface(IID_IDispatch, (void **)&m_pDispAmbient);

        if (!m_pDispAmbient)
            return FALSE;
    }

    // now go and get the property into a variant.
    //
    memset(&dispparams, 0, sizeof(DISPPARAMS));
    hr = m_pDispAmbient->Invoke(dispid, IID_NULL, 0, DISPATCH_PROPERTYGET, &dispparams,
                                &v, NULL, NULL);
    if (FAILED(hr)) return FALSE;

    // we've got the variant, so now go an coerce it to the type that the user
    // wants.  if the types are the same, then this will copy the stuff to
    // do appropriate ref counting ...
    //
    hr = VariantChangeType(&v2, &v, 0, vt);
    if (FAILED(hr)) {
        VariantClear(&v);
        return FALSE;
    }

    // copy the data to where the user wants it
    //
    CopyMemory(pData, &(v2.lVal), g_rgcbDataTypeSize[vt]);
    VariantClear(&v);
    return TRUE;
}

//=--------------------------------------------------------------------------=
// COleControl::GetAmbientFont    [callable]
//=--------------------------------------------------------------------------=
// gets the current font for the user.
//
// Parameters:
//    IFont **         - [out] where to put the font.
//
// Output:
//    BOOL             - FALSE means couldn't get it.
//
// Notes:
//
BOOL COleControl::GetAmbientFont
(
    IFont **ppFont
)
{
    IDispatch *pFontDisp;

    // we don't have to do much here except get the ambient property and QI
    // it for the user.
    //
    *ppFont = NULL;
    if (!GetAmbientProperty(DISPID_AMBIENT_FONT, VT_DISPATCH, &pFontDisp))
        return FALSE;

    pFontDisp->QueryInterface(IID_IFont, (void **)ppFont);
    pFontDisp->Release();
    return (*ppFont) ? TRUE : FALSE;
}

//=--------------------------------------------------------------------------=
// COleControl::DesignMode
//=--------------------------------------------------------------------------=
// returns TRUE if we're in Design mode.
//
// Output:
//    BOOL            - true is design mode, false is run mode
//
// Notes:
//
BOOL COleControl::DesignMode
(
    void
)
{
    VARIANT_BOOL f;

    // if we don't already know our run mode, go and get it.  we'll assume
    // it's true unless told otherwise [or if the operation fails ...]
    //
    if (!m_fModeFlagValid) {
        f = TRUE;
        m_fModeFlagValid = TRUE;
        GetAmbientProperty(DISPID_AMBIENT_USERMODE, VT_BOOL, &f);
        m_fRunMode = f;
    }

    return !m_fRunMode;
}

//=--------------------------------------------------------------------------=
// COleControl::FireEvent
//=--------------------------------------------------------------------------=
// fires an event.  handles arbitrary number of arguments.
//
// Parameters:
//    EVENTINFO *        - [in] struct that describes the event.
//    ...                - arguments to the event
//
// Output:
//    none
//
// Notes:
//    - use stdarg's va_* macros.
//
void __cdecl COleControl::FireEvent
(
    EVENTINFO *pEventInfo,
    ...
)
{
    va_list    valist;
    DISPPARAMS dispparams;
    VARIANT    rgvParameters[MAX_ARGS];
    VARIANT   *pv;
    VARTYPE    vt;
    int        iParameter;
    int        cbSize;

    ASSERT(pEventInfo->cParameters <= MAX_ARGS, "Don't support more than MAX_ARGS params.  sorry.");

    va_start(valist, pEventInfo);

    // copy the Parameters into the rgvParameters array.  make sure we reverse
    // them for automation
    //
    pv = &(rgvParameters[pEventInfo->cParameters - 1]);
    for (iParameter = 0; iParameter < pEventInfo->cParameters; iParameter++) {

        vt = pEventInfo->rgTypes[iParameter];

        // if it's a by value variant, then just copy the whole
        // dang thing
        //
        if (vt == VT_VARIANT)
            *pv = va_arg(valist, VARIANT);
        else {
            // copy the vt and the data value.
            //
            pv->vt = vt;
            if (vt & VT_BYREF)
                cbSize = sizeof(void *);
            else
                cbSize = g_rgcbDataTypeSize[vt];

            // small optimization -- we can copy 2/4 bytes over quite
            // quickly.
            //
            if (cbSize == sizeof(short))
                V_I2(pv) = va_arg(valist, short);
            else if (cbSize == 4)
                V_I4(pv) = va_arg(valist, long);
            else {
                // copy over 8 bytes
                //
                ASSERT(cbSize == 8, "don't recognize the type!!");
                V_CY(pv) = va_arg(valist, CURRENCY);
            }
        }

        pv--;
    }

    // fire the event
    //
    dispparams.rgvarg = rgvParameters;
    dispparams.cArgs = pEventInfo->cParameters;
    dispparams.rgdispidNamedArgs = NULL;
    dispparams.cNamedArgs = 0;

    m_cpEvents.DoInvoke(pEventInfo->dispid, &dispparams);

    va_end(valist);
}

//=--------------------------------------------------------------------------=
// COleControl::AfterCreateWindow    [overridable]
//=--------------------------------------------------------------------------=
// something the user can pay attention to
//
// Output:
//    BOOL             - false means fatal error, can't continue
// Notes:
//
BOOL COleControl::AfterCreateWindow
(
    void
)
{
    return TRUE;
}

//=--------------------------------------------------------------------------=
// COleControl::BeforeCreateWindow    [overridable]
//=--------------------------------------------------------------------------=
// called just before we create a window.  the user should register their
// window class here, and set up any other things, such as the title of
// the window, and/or sytle bits, etc ...
//
// Parameters:
//    DWORD *            - [out] dwWindowFlags
//    DWORD *            - [out] dwExWindowFlags
//    LPSTR              - [out] name of window to create
//
// Output:
//    BOOL               - false means fatal error, can't continue
//
// Notes:
//
BOOL COleControl::BeforeCreateWindow
(
    DWORD *pdwWindowStyle,
    DWORD *pdwExWindowStyle,
    LPSTR  pszWindowTitle
)
{
    return TRUE;
}

//=--------------------------------------------------------------------------=
// COleControl::InvalidateControl    [callable]
//=--------------------------------------------------------------------------=
void COleControl::InvalidateControl
(
    LPCRECT lpRect
)
{
    if (m_fInPlaceActive)
        InvalidateRect(m_hwnd, lpRect, TRUE);
    else
        ViewChanged();

    // CONSIDER: one might want to call pOleAdviseHolder->OnDataChanged() here
    // if there was support for IDataObject
}

//=--------------------------------------------------------------------------=
// COleControl::SetControlSize    [callable]
//=--------------------------------------------------------------------------=
// sets the control size. they'll give us the size in pixels.  we've got to
// convert them back to HIMETRIC before passing them on!
//
// Parameters:
//    SIZEL *        - [in] new size
//
// Output:
//    BOOL
//
// Notes:
//
BOOL COleControl::SetControlSize
(
    SIZEL *pSize
)
{
    HRESULT hr;
    SIZEL slHiMetric;

    PixelToHiMetric(pSize, &slHiMetric);
    hr = SetExtent(DVASPECT_CONTENT, &slHiMetric);
    return (FAILED(hr)) ? FALSE : TRUE;
}

//=--------------------------------------------------------------------------=
// COleControl::RecreateControlWindow    [callable]
//=--------------------------------------------------------------------------=
// called by a [subclassed, typically] control to recreate it's control
// window.
//
// Parameters:
//    none
//
// Output:
//    HRESULT
//
// Notes:
//    - NOTE: USE ME EXTREMELY SPARINGLY! THIS IS AN EXTREMELY EXPENSIVE
//      OPERATION!
//
HRESULT COleControl::RecreateControlWindow
(
    void
)
{
    HRESULT hr;
    HWND    hwndPrev;

    // we need to correctly preserve the control's position within the
    // z-order here.
    //
    if (m_hwnd)
        hwndPrev = ::GetWindow(m_hwnd, GW_HWNDPREV);

    // if we're in place active, then we have to deactivate, and reactivate
    // ourselves with the new window ...
    //
    if (m_fInPlaceActive) {

        hr = InPlaceDeactivate();
        RETURN_ON_FAILURE(hr);
        hr = InPlaceActivate((m_fUIActive) ? OLEIVERB_UIACTIVATE : OLEIVERB_INPLACEACTIVATE);
        RETURN_ON_FAILURE(hr);

    } else if (m_hwnd) {
        DestroyWindow(m_hwnd);
        m_hwnd = NULL;
        if (m_hwndReflect) {
            DestroyWindow(m_hwndReflect);
            m_hwndReflect = NULL;
        }

        CreateInPlaceWindow(0, 0, FALSE);
    }

    // restore z-order position
    //
    if (m_hwnd)
        SetWindowPos(m_hwnd, hwndPrev, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);

    return m_hwnd ? S_OK : E_FAIL;
}

// from Globals.C. don't need to mutex it here since we only read it.
//
extern HINSTANCE g_hInstResources;

//=--------------------------------------------------------------------------=
// COleControl::GetResourceHandle    [callable]
//=--------------------------------------------------------------------------=
// gets the HINSTANCE of the DLL where the control should get resources
// from.  implemented in such a way to support satellite DLLs.
//
// Output:
//    HINSTANCE
//
// Notes:
//
HINSTANCE COleControl::GetResourceHandle
(
    void
)
{
    if (!g_fSatelliteLocalization)
        return g_hInstance;

    // if we've already got it, then there's not all that much to do.
    // don't need to crit sect this one right here since even if they do fall
    // into the ::GetResourceHandle call, it'll properly deal with things.
    //
    if (g_hInstResources)
        return g_hInstResources;

    // we'll get the ambient localeid from the host, and pass that on to the
    // automation object.
    //
    // crit sect this for apartment threading support.
    //
    EnterCriticalSection(&g_CriticalSection);
    if (!g_fHaveLocale)
        // if we can't get the ambient locale id, then we'll just continue
        // with the globally set up value.
        //
        if (!GetAmbientProperty(DISPID_AMBIENT_LOCALEID, VT_I4, &g_lcidLocale))
            goto Done;

    g_fHaveLocale = TRUE;

  Done:
    LeaveCriticalSection(&g_CriticalSection);
    return ::GetResourceHandle();
}


CTLOCX96.CPP

//=--------------------------------------------------------------------------=
// CtlOcx96.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// implementation of the OCX 96 interfaces that don't quite fit in to the
// categories covered by embedding, persistence, and ctlmisc.cpp
//
//
#include "IPServer.H"

#include "CtrlObj.H"
#include "Globals.H"

//=--------------------------------------------------------------------------=
// COleControl::GetActivationPolicy    [IPointerInactive]
//=--------------------------------------------------------------------------=
// returns the present activation policy for this object.  for non-subclassed
// windows controls, this means we can put off in-place activation for quite
// a while.
//
// Parameters:
//    DWORD *        - [out] activation policy
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::GetActivationPolicy
(
    DWORD *pdwPolicy
)
{
    CHECK_POINTER(pdwPolicy);

    // just get the policy in the global structure describing this control.
    //
    *pdwPolicy = ACTIVATIONPOLICYOFCONTROL(m_ObjectType);
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::OnInactiveMouseMove    [IPointerInactive]
//=--------------------------------------------------------------------------=
// indicates to an inactive oobject that the mouse pointer has moved over the
// object.
//
// Parameters:
//    LPCRECT            - [in]
//    long               - [in]
//    long               - [in]
//    DWORD              - [in]
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::OnInactiveMouseMove
(
    LPCRECT pRectBounds,
    long    x,
    long    y,
    DWORD   dwMouseMsg
)
{
    // OVERRIDE: end control writers should just override this if they want
    // to have a control that is never in-place active.
    //
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::OnInactiveSetCursor    [IPointerInactive]
//=--------------------------------------------------------------------------=
// called by the container for the inactive object under the mouse pointer on
// recept of a WM_SETCURSOR message.
//
// Parameters:
//    LPCRECT            - [in]
//    long               - [in]
//    long               - [in]
//    DWORD              - [in]
//    BOOL               - [in]
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::OnInactiveSetCursor
(
    LPCRECT pRectBounds,
    long    x,
    long    y,
    DWORD   dwMouseMsg,
    BOOL    fSetAlways
)
{
    // OVERRIDE:  just get the user to override this if they want to never
    // be activated
    //
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::QuickActivate    [IQuickActivate]
//=--------------------------------------------------------------------------=
// allows the container to activate the control.
//
// Parameters:
//    QACONTAINER *        - [in]  info about the container
//    QACONTROL *          - [out] info about the control
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::QuickActivate
(
    QACONTAINER *pContainer,
    QACONTROL *pControl
)
{
    HRESULT hr;
    DWORD   dw;

    // we need these guys.
    //
    if (!pContainer) return E_UNEXPECTED;
    if (!pControl) return E_UNEXPECTED;

    // start grabbing things from the QACONTAINER structure and apply them
    // as relevant
    //
    if (pContainer->cbSize < sizeof(QACONTAINER)) return E_UNEXPECTED;
    if (pControl->cbSize < sizeof(QACONTROL)) return E_UNEXPECTED;

    // save out the client site, of course.
    //
    if (pContainer->pClientSite) {
        hr = SetClientSite(pContainer->pClientSite);
        RETURN_ON_FAILURE(hr);
    }

    // if the lcid is not LANG_NEUTRAL, score!
    //
    if (pContainer->lcid) {
        g_lcidLocale = pContainer->lcid;
        g_fHaveLocale = TRUE;
    }

    // pay attention to some ambients
    //
    if (pContainer->dwAmbientFlags & QACONTAINER_MESSAGEREFLECT) {
        m_fHostReflects = TRUE;
        m_fCheckedReflecting = TRUE;
    }

    // hook up some notifications.  first property notifications.
    //
    if (pContainer->pPropertyNotifySink) {
        pContainer->pPropertyNotifySink->AddRef();
        hr = m_cpPropNotify.AddSink((void *)pContainer->pPropertyNotifySink, &pControl->dwPropNotifyCookie);
        if (FAILED(hr)) {
            pContainer->pPropertyNotifySink->Release();
            return hr;
        }
    }

    // then the event sink.
    //
    if (pContainer->pUnkEventSink) {
        hr = m_cpEvents.Advise(pContainer->pUnkEventSink, &pControl->dwEventCookie);
        if (FAILED(hr)) {
            pContainer->pUnkEventSink->Release();
            return hr;
        }
    }

    // finally, the advise sink.
    //
    if (pContainer->pAdviseSink) {
        // don't need to pass the cookie back since there can only be one
        // person advising at a time.
        //
        hr = Advise(pContainer->pAdviseSink, &dw);
        RETURN_ON_FAILURE(hr);
    }

    // set up a few things in the QACONTROL structure.  we're opaque by default
    //
    pControl->dwMiscStatus = OLEMISCFLAGSOFCONTROL(m_ObjectType);
    pControl->dwViewStatus = FCONTROLISOPAQUE(m_ObjectType) ? VIEWSTATUS_OPAQUE : 0;
    pControl->dwPointerActivationPolicy = ACTIVATIONPOLICYOFCONTROL(m_ObjectType);

    // that's pretty much all we're interested in.  we will, however, pass on the
    // rest of the things to the end control writer and see if they want to do
    // anything with them. they shouldn't touch any of the above except for the
    // ambients.
    //
    return OnQuickActivate(pContainer, &(pControl->dwViewStatus));
}

//=--------------------------------------------------------------------------=
// COleControl::SetContentExtent    [IQuickActivate]
//=--------------------------------------------------------------------------=
// the container calls this to set the content extent of the control.
//
// Parameters:
//    LPSIZEL            - [in] the size of the content extent
//
// Output:
//    HRESULT            - S_OK, or E_FAIL for fixed size control
//
// Notes:
//
STDMETHODIMP COleControl::SetContentExtent
(
    LPSIZEL pSize
)
{
    return SetExtent(DVASPECT_CONTENT, pSize);
}

//=--------------------------------------------------------------------------=
// COleControl::GetContentExtent    [IQuickActivate]
//=--------------------------------------------------------------------------=
// the container calls this to get the content extent of the control
//
// Parameters:
//    LPSIZEL        - [out] returns current size
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::GetContentExtent
(
    LPSIZEL pSize
)
{
    return GetExtent(DVASPECT_CONTENT, pSize);
}

//=--------------------------------------------------------------------------=
// COleControl::OnQuickActivate    [overridable]
//=--------------------------------------------------------------------------=
// not all the of the members of the QACONTAINER need to be consumed by the
// framework, but are, at least, extremely interesting.  thus, we will pass
// on the struture to the end control writer, and let them consume these.
//
// Parameters:
//    QACONTAINER *            - [in]  contains additional information
//    DWORD *                  - [out] put ViewStatus flags here.
//
// Output:
//    HRESULT
//
// Notes:
//    - control writers should only look at/consume:
//        a. dwAmbientFlags
//        b. colorFore/colorBack
//        c. pFont
//        d. pUndoMgr
//        e. dwAppearance
//        f. hpal
//
//    - all the others are set up the for the user by the framework.
//    - control writers should set up the pdwViewStatus with flags as per
//      IViewObjectEx::GetViewStatus.  if you don't know what this is or don't
//      care, then don't touch.
//
HRESULT COleControl::OnQuickActivate
(
    QACONTAINER *pContainer,
    DWORD       *pdwViewStatus
)
{
    // by default, nuthin much to do!
    //
    return S_OK;
}


CTLPSST.CPP

//=--------------------------------------------------------------------------=
// CtlPsst.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// implementation of persistence interfaces for COleControl.
//
#include "IPServer.H"
#include "CtrlObj.H"

#include "CtlHelp.H"
#include "Util.H"

// this is the name of the stream we'll save our ole controls to.
//
const WCHAR wszCtlSaveStream [] = L"CONTROLSAVESTREAM";

// for ASSERT and FAIL
//
SZTHISFILE

//=--------------------------------------------------------------------------=
// to help with out stream save implementation ...
//
#define STREAMHDR_SIGNATURE 0x12344321  // Signature to identify our format (avoid crashes!)
#define IPROP_END 0xFF                  // Marker at end of property list
#define MAXAUTOBUF 3800                 // Best if < 1 page.

typedef struct tagSTREAMHDR {

    DWORD  dwSignature;     // Signature.
    size_t cbWritten;       // Number of bytes written

} STREAMHDR;

//=--------------------------------------------------------------------------=
// COleControl persistence interfaces
//=--------------------------------------------------------------------------=

//=--------------------------------------------------------------------------=
// COleControl::Load    [IPersistPropertyBag]
//=--------------------------------------------------------------------------=
// IPersistPropertyBag.  we've got a property bag, so let's load our properties
// from it.
//
// Parameters:
//    IPropertyBag *      - [in] pbag from which to read props.
//    IErrorLog *         - [in] error log to write to
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::Load
(
    IPropertyBag *pPropertyBag,
    IErrorLog    *pErrorLog
)
{
    HRESULT hr;

    // load in our standard state first.  nothing serious here ... currently,
    // we've just got two properties, for cx and cy.
    //
    hr = LoadStandardState(pPropertyBag, pErrorLog);
    RETURN_ON_FAILURE(hr);

    // now call the user text load function, and get them to load in whatever
    // they're interested in.
    //
    hr = LoadTextState(pPropertyBag, pErrorLog);

    return hr;
}

//=--------------------------------------------------------------------------=
// COleControl::Save    [IPersistPropertyBag]
//=--------------------------------------------------------------------------=
// given a property bag, save out all the relevant state information.
//
// Parameters:
//    IPropertyBag *        - [in] property to write to
//    BOOL                  - [in] do we clear the dirty bit?
//    BOOL                  - [in] do we write out default values anyhoo?
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::Save
(
    IPropertyBag *pPropertyBag,
    BOOL          fClearDirty,
    BOOL          fWriteDefault
)
{
    HRESULT hr;

    // save out standard state information
    //
    hr = SaveStandardState(pPropertyBag);
    RETURN_ON_FAILURE(hr);

    // now call the user function and get them to save out
    // all of their properties.
    //
    hr = SaveTextState(pPropertyBag, fWriteDefault);
    RETURN_ON_FAILURE(hr);

    // now clear the dirty flag and send out notification that we're
    // done.
    //
    if (fClearDirty)
        m_fDirty = FALSE;

    if (m_pOleAdviseHolder)
        m_pOleAdviseHolder->SendOnSave();

    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::GetClassID    [IPersistStreamInit]
//=--------------------------------------------------------------------------=
// returns the classid of this mamma
//
// Parameters:
//    CLSID *         - [out] where to put the clsid
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::GetClassID
(
    CLSID *pclsid
)
{
    CHECK_POINTER(pclsid);

    // copy the thing over
    //
    *pclsid = CLSIDOFOBJECT(m_ObjectType);
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::IsDirty    [IPersistStreamInit]
//=--------------------------------------------------------------------------=
// asks if we're dirty or not.  duh.
//
// Output:
//    HRESULT        - S_OK: dirty, S_FALSE: not dirty
//
// Notes:
//
STDMETHODIMP COleControl::IsDirty
(
    void
)
{
    return (m_fDirty) ? S_OK : S_FALSE;
}

//=--------------------------------------------------------------------------=
// COleControl::InitNew    [IPersistStreamInit]
//=--------------------------------------------------------------------------=
// causes the control to intialize itself with a new bunch of state information
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::InitNew
(
    void
)
{
    BOOL f;

    // call the overridable function to do this work
    //
    f = InitializeNewState();
    return (f) ? S_OK : E_FAIL;
}

//=--------------------------------------------------------------------------=
// COleControl::GetSizeMax    [IPersistStreamInit]
//=--------------------------------------------------------------------------=
//
// Parameters:
//    ULARGE_INTEGER *    - [out]
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::GetSizeMax
(
    ULARGE_INTEGER *pulMaxSize
)
{
    return E_NOTIMPL;
}

//=--------------------------------------------------------------------------=
// COleControl::Load    [IPersistStreamInit]
//=--------------------------------------------------------------------------=
// load from an IStream
//
// Parameters:
//    IStream *    - [in] stream from which to load
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::Load
(
    IStream *pStream
)
{
    HRESULT hr;

    // first thing to do is read in standard properties the user don't
    // persist themselves.
    //
    hr = LoadStandardState(pStream);
    RETURN_ON_FAILURE(hr);

    // load in the user properties.  this method is one they -have- to implement
    // themselves.
    //
    hr = LoadBinaryState(pStream);

    return hr;
}

//=--------------------------------------------------------------------------=
// COleControl::Save    [IPersistStreamInit]
//=--------------------------------------------------------------------------=
// saves out our state using streams
//
// Parameters:
//    IStream *        - [in]
//    BOOL             - [in] clear dirty bit?
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::Save
(
    IStream *pStream,
    BOOL     fClearDirty
)
{
    HRESULT hr;

    // use our helper routine that we share with the IStorage persistence
    // code.
    //
    hr = m_SaveToStream(pStream);
    RETURN_ON_FAILURE(hr);

    // clear out dirty flag [if appropriate] and notify that we're done
    // with save.
    //
    if (fClearDirty)
        m_fDirty = FALSE;
    if (m_pOleAdviseHolder)
        m_pOleAdviseHolder->SendOnSave();

    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::InitNew    [IPersistStorage]
//=--------------------------------------------------------------------------=
// ipersiststorage version of this.  fweee
//
// Parameters:
//    IStorage *    - [in] we don't use this
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::InitNew
(
    IStorage *pStorage
)
{
    // we already have an implementation of this [for IPersistStreamInit]
    //
    return InitNew();
}

//=--------------------------------------------------------------------------=
// COleControl::Load    [IPersistStorage]
//=--------------------------------------------------------------------------=
// Ipersiststorage version of this
//
// Parameters:
//    IStorage *    - [in] DUH.
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::Load(IStorage *pStorage)
{
    IStream *pStream;
    HRESULT  hr;

    // we're going to use IPersistStream::Load from the CONTENTS stream.
    //
    hr = pStorage->OpenStream(wszCtlSaveStream, 0, STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pStream);
    RETURN_ON_FAILURE(hr);

    // IPersistStreamInit::Load
    //
    hr = Load(pStream);
    pStream->Release();
    return hr;
}

//=--------------------------------------------------------------------------=
// COleControl::Save    [IPersistStorage]
//=--------------------------------------------------------------------------=
// save into the contents stream of the given storage object.
//
// Parameters:
//    IStorage *        - [in] 10 points if you figure it out
//    BOOL              - [in] is the storage the same as the load storage?
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::Save
(
    IStorage *pStorage,
    BOOL      fSameAsLoad
)
{
    IStream *pStream;
    HRESULT  hr;

    // we're just going to save out to the CONTENTES stream.
    //
    hr = pStorage->CreateStream(wszCtlSaveStream, STGM_WRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE,
                                0, 0, &pStream);
    RETURN_ON_FAILURE(hr);

    // use our helper routine.
    //
    hr = m_SaveToStream(pStream);
    m_fSaveSucceeded = (FAILED(hr)) ? FALSE : TRUE;
    pStream->Release();
    return hr;
}

//=--------------------------------------------------------------------------=
// COleControl::SaveCompleted    [IPersistStorage]
//=--------------------------------------------------------------------------=
// lets us clear out our flags.
//
// Parameters:
//    IStorage *    - ignored
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::SaveCompleted
(
    IStorage *pStorageNew
)
{
    // if our save succeeded, then we can do our post save work.
    //
    if (m_fSaveSucceeded) {
        m_fDirty = FALSE;
        if (m_pOleAdviseHolder)
            m_pOleAdviseHolder->SendOnSave();
    }

    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::HandsOffStorage    [IPersistStorage]
//=--------------------------------------------------------------------------=
// not interesting
//
// Output:
//    S_OK
//
// Notes:
//
STDMETHODIMP COleControl::HandsOffStorage
(
    void
)
{
    // we don't ever hold on to  a storage pointer, so this is remarkably
    // uninteresting to us.
    //
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::m_SaveToStream    [helper: IPersistStreamInit/IPersistStorage]
//=--------------------------------------------------------------------------=
// save ourselves to a stream
//
// Parameters:
//    IStream *        - figure it out
//
// Output:
//    HRESULT
//
// Notes:
//
HRESULT COleControl::m_SaveToStream
(
    IStream *pStream
)
{
    HRESULT hr;

    // save out standard state information that the user has no control
    // over
    //
    hr = SaveStandardState(pStream);
    RETURN_ON_FAILURE(hr);

    // save out user-specific satte information.  they MUST implement this
    // function
    //
    hr = SaveBinaryState(pStream);

    return hr;
}

//=--------------------------------------------------------------------------=
// COleControl::LoadStandardState    [ helper ]
//=--------------------------------------------------------------------------=
// reads in standard properties that all controls are going to have, using
// text persistence APIs.  there is another version for streams.
//
// Parameters:
//    IPropertyBag *    - [in]
//    IErrorLog *       - [in]
//
// Output:
//    HRESULT
//
// Notes:
//
HRESULT COleControl::LoadStandardState
(
    IPropertyBag *pPropertyBag,
    IErrorLog    *pErrorLog
)
{
    VARIANT v;
    HRESULT hr;
    SIZEL   slHiMetric = { 100, 50 };

    // currently, our only standard properties are related to size.
    // if we can't find them, then we'll just use some defaults.
    //
    v.vt = VT_I4;
    v.lVal = 0;
    hr = pPropertyBag->Read(L"_ExtentX", &v, pErrorLog);
    if (SUCCEEDED(hr)) slHiMetric.cx = v.lVal;

    v.lVal = 0;
    hr = pPropertyBag->Read(L"_ExtentY", &v, pErrorLog);
    if (SUCCEEDED(hr)) slHiMetric.cy = v.lVal;

    HiMetricToPixel(&slHiMetric, &m_Size);
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::LoadStandardState    [ helper ]
//=--------------------------------------------------------------------------=
// reads in standard properties that all controls are going to have, using
// stream persistence APIs.  there is another version for text.
//
// Parameters:
//    IStream *         - [in]
//
// Output:
//    HRESULT
//
// Notes:
//
HRESULT COleControl::LoadStandardState
(
    IStream *pStream
)
{
    STREAMHDR stmhdr;
    HRESULT hr;
    SIZEL   slHiMetric;

    // look for our header structure, so we can verify stream validity.
    //
    hr = pStream->Read(&stmhdr, sizeof(STREAMHDR), NULL);
    RETURN_ON_FAILURE(hr);

    if (stmhdr.dwSignature != STREAMHDR_SIGNATURE)
        return E_UNEXPECTED;

    // currently, the only standard state we're writing out is
    // a SIZEL structure describing the control's size.
    //
    if (stmhdr.cbWritten != sizeof(m_Size))
        return E_UNEXPECTED;

    // we like the stream.  let's go load in our two properties.
    //
    hr = pStream->Read(&slHiMetric, sizeof(slHiMetric), NULL);
    RETURN_ON_FAILURE(hr);

    HiMetricToPixel(&slHiMetric, &m_Size);
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::SaveStandardState    [ helper ]
//=--------------------------------------------------------------------------=
// saves out standard properties that we're managing for a control using text
// persistence APIs.  there is another version for stream persistence.
//
// Parameters:
//    IPropertyBag *        - [in]
//
// Output:
//    HRESULT
//
// Notes:
//
HRESULT COleControl::SaveStandardState
(
    IPropertyBag *pPropertyBag
)
{
    HRESULT hr;
    VARIANT v;
    SIZEL   slHiMetric;

    // currently, the only standard proprerties we persist are Size related
    //
    PixelToHiMetric(&m_Size, &slHiMetric);

    v.vt = VT_I4;
    v.lVal = slHiMetric.cx;

    hr = pPropertyBag->Write(L"_ExtentX", &v);
    RETURN_ON_FAILURE(hr);

    v.lVal = slHiMetric.cy;

    hr = pPropertyBag->Write(L"_ExtentY", &v);

    return hr;
}

//=--------------------------------------------------------------------------=
// COleControl::SaveStandardState    [ helper ]
//=--------------------------------------------------------------------------=
// saves out standard properties that we're managing for a control using stream
// persistence APIs.  there is another version for text persistence.
//
// Parameters:
//    IStream *            - [in]
//
// Output:
//    HRESULT
//
// Notes:
//
HRESULT COleControl::SaveStandardState
(
    IStream *pStream
)
{
    STREAMHDR streamhdr = { STREAMHDR_SIGNATURE, sizeof(SIZEL) };
    HRESULT hr;
    SIZEL   slHiMetric;

    // first thing to do is write out our stream hdr structure.
    //
    hr = pStream->Write(&streamhdr, sizeof(STREAMHDR), NULL);
    RETURN_ON_FAILURE(hr);

    // the only properties we're currently persisting here are the size
    // properties for this control.  make sure we do that in HiMetric
    //
    PixelToHiMetric(&m_Size, &slHiMetric);

    hr = pStream->Write(&slHiMetric, sizeof(slHiMetric), NULL);
    return hr;
}

//=--------------------------------------------------------------------------=
// COleControl::InitializeNewState    [overridable]
//=--------------------------------------------------------------------------=
// the user can override this to initialize variables
//
// Output:
//    BOOL        - FALSE means couldn't do it.
//
// Notes:
//
BOOL COleControl::InitializeNewState
(
    void
)
{
    // we find this largely uninteresting
    //
    return TRUE;
}


CTLVIEW.CPP

//=--------------------------------------------------------------------------=
// CtlView.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// implementation of the IViewObjectEx interface, which is a moderately
// non-trivial bunch of code.
//
#include "IPServer.H"

#include "CtrlObj.H"
#include "Globals.H"
#include "Util.H"

// for ASSERT and FAIL
//
SZTHISFILE

// local functions we're going to find useful
//
HDC _CreateOleDC(DVTARGETDEVICE *ptd);

//=--------------------------------------------------------------------------=
// COleControl::Draw    [IViewObject2]
//=--------------------------------------------------------------------------=
// Draws a representation of an object onto the specified device context.
//
// Parameters:
//    DWORD                - [in] draw aspect
//    LONG                 - [in] part of object to draw [not relevant]
//    void *               - NULL
//    DVTARGETDEVICE *     - [in] specifies the target device
//    HDC                  - [in] information context for target device
//    HDC                  - [in] target device context
//    LPCRECTL             - [in] rectangle in which the object is drawn
//    LPCRECTL             - [in] window extent and origin for metafiles
//    BOOL (*)(DWORD)      - [in] callback for continuing or cancelling drawing
//    DWORD                - [in] parameter to pass to callback.
//
// Output:
//    HRESULT
//
// Notes:
//    - we support the following OCX 96 extensions
//        a. flicker free drawing [multi-pass drawing]
//        b. pvAspect != NULL for optimized DC handling
//        c. prcBounds == NULL for windowless inplace active objects
//
STDMETHODIMP COleControl::Draw
(
    DWORD            dwDrawAspect,
    LONG             lIndex,
    void            *pvAspect,
    DVTARGETDEVICE  *ptd,
    HDC              hicTargetDevice,
    HDC              hdcDraw,
    LPCRECTL         prcBounds,
    LPCRECTL         prcWBounds,
    BOOL (__stdcall *pfnContinue)(DWORD dwContinue),
    DWORD            dwContinue
)
{
    HRESULT hr;
    RECTL rc;
    POINT pVp, pW;
    BOOL  fOptimize = FALSE;
    int iMode;
    BYTE fMetafile = FALSE;
    BYTE fDeleteDC = FALSE;

    // support the aspects required for multi-pass drawing
    //
    switch (dwDrawAspect) {
        case DVASPECT_CONTENT:
        case DVASPECT_OPAQUE:
        case DVASPECT_TRANSPARENT:
            break;
        default:
            return DV_E_DVASPECT;
    }

    // first, have to do a little bit to support printing.
    //
    if (GetDeviceCaps(hdcDraw, TECHNOLOGY) == DT_METAFILE) {

        // We are dealing with a metafile.
        //
        fMetafile = TRUE;

        // If attributes DC is NULL, create one, based on ptd.
        //
        if (!hicTargetDevice) {

            // Does _CreateOleDC have to return an hDC
            // or can it be flagged to return an hIC
            // for this particular case?
            //
            hicTargetDevice = _CreateOleDC(ptd);
            fDeleteDC = TRUE;
        }
    }

    // check to see if we have any flags passed in the pvAspect parameter.
    //
    if (pvAspect && ((DVASPECTINFO *)pvAspect)->cb == sizeof(DVASPECTINFO))
        fOptimize = (((DVASPECTINFO *)pvAspect)->dwFlags & DVASPECTINFOFLAG_CANOPTIMIZE) ? TRUE : FALSE;

    // if we are windowless, then we just pass this on to the end control code.
    //
    if (m_fInPlaceActive) {

        // give them a rectangle with which to draw
        //
        //ASSERT(!m_fInPlaceActive || !prcBounds, "Inplace active and somebody passed in prcBounds!!!");
        if (prcBounds)
memcpy(&rc, prcBounds, sizeof(rc));
else
memcpy(&rc, &m_rcLocation, sizeof(rc));
    } else {

        // first -- convert the DC back to MM_TEXT mapping mode so that the
        // window proc and OnDraw can share the same painting code.  save
        // some information on it, so we can restore it later [without using
        // a SaveDC/RestoreDC]
        //
        rc = *prcBounds;

        // Don't do anything to hdcDraw if it's a metafile.
        // The control's Draw method must make the appropriate
        // accomodations for drawing to a metafile
        //
        if (!fMetafile) {
            LPtoDP(hdcDraw, (POINT *)&rc, 2);
            SetViewportOrgEx(hdcDraw, 0, 0, &pVp);
            SetWindowOrgEx(hdcDraw, 0, 0, &pW);
            iMode = SetMapMode(hdcDraw, MM_TEXT);
        }
    }

    // prcWBounds is NULL and not used if we are not dealing with a metafile.
    // For metafiles, we pass on rc as *prcBounds, we should also include
    // prcWBounds
    //
    hr = OnDraw(dwDrawAspect, hdcDraw, &rc, prcWBounds, hicTargetDevice, fOptimize);

    // clean up the DC when we're done with it, if appropriate.
    //
    if (!m_fInPlaceActive) {
        SetViewportOrgEx(hdcDraw, pVp.x, pVp.y, NULL);
        SetWindowOrgEx(hdcDraw, pW.x, pW.y, NULL);
        SetMapMode(hdcDraw, iMode);
    }

    // if we created a dc, blow it away now
    //
    if (fDeleteDC) DeleteDC(hicTargetDevice);
    return hr;
}

//=--------------------------------------------------------------------------=
// COleControl::DoSuperClassPaint
//=--------------------------------------------------------------------------=
// design time painting of a subclassed control.
//
// Parameters:
//    HDC                - [in]  dc to work with
//    LPCRECTL           - [in]  rectangle to paint to.  should be in pixels
//
// Output:
//    HRESULT
//
// Notes:
//
HRESULT COleControl::DoSuperClassPaint
(
    HDC      hdc,
    LPCRECTL prcBounds
)
{
    HWND hwnd;
    RECT rcClient;
    int  iMapMode;
    POINT ptWOrg, ptVOrg;
    SIZE  sWOrg, sVOrg;

    // make sure we have a window.
    //
    hwnd = CreateInPlaceWindow(0,0, FALSE);
    if (!hwnd)
        return E_FAIL;

    GetClientRect(hwnd, &rcClient);

    // set up the DC for painting.  this code largely taken from the MFC CDK
    // DoSuperClassPaint() fn.  doesn't always get things like command
    // buttons quite right ...
    //
    // NOTE: there is a windows 95 problem in which the font instance manager
    // will leak a bunch of bytes in the global GDI pool whenever you
    // change your extents and have an active font.  this code gets around
    // this for on-screen cases, but not for printing [which shouldn't be
    // too serious, because you're not often changing your control size and
    // printing rapidly in succession]
    //
    if ((rcClient.right - rcClient.left != prcBounds->right - prcBounds->left)
        && (rcClient.bottom - rcClient.top != prcBounds->bottom - prcBounds->top)) {

        iMapMode = SetMapMode(hdc, MM_ANISOTROPIC);
        SetWindowExtEx(hdc, rcClient.right, rcClient.bottom, &sWOrg);
        SetViewportExtEx(hdc, prcBounds->right - prcBounds->left, prcBounds->bottom - prcBounds->top, &sVOrg);
    }

    SetWindowOrgEx(hdc, 0, 0, &ptWOrg);
    SetViewportOrgEx(hdc, prcBounds->left, prcBounds->top, &ptVOrg);

#if STRICT
    CallWindowProc((WNDPROC)SUBCLASSWNDPROCOFCONTROL(m_ObjectType), hwnd, (g_fSysWin95Shell) ? WM_PRINT : WM_PAINT, (WPARAM)hdc, (LPARAM)(g_fSysWin95Shell ? PRF_CHILDREN | PRF_CLIENT : 0));
#else
    CallWindowProc((FARPROC)SUBCLASSWNDPROCOFCONTROL(m_ObjectType), hwnd, (g_fSysWin95Shell) ? WM_PRINT : WM_PAINT, (WPARAM)hdc, (LPARAM)(g_fSysWin95Shell ? PRF_CHILDREN | PRF_CLIENT : 0));
#endif // STRICT

    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::GetColorSet    [IViewObject2]
//=--------------------------------------------------------------------------=
// Returns the logical palette that the control will use for drawing in its
// IViewObject::Draw method with the corresponding parameters.
//
// Parameters:
//    DWORD                - [in]  how the object is to be represented
//    LONG                 - [in]  part of the object to draw [not relevant]
//    void *               - NULL
//    DVTARGETDEVICE *     - [in]  specifies the target device
//    HDC                  - [in]  information context for the target device
//    LOGPALETTE **        - [out] where to put palette
//
// Output:
//    S_OK                 - Control has a palette, and returned it through the out param.
//    S_FALSE              - Control does not currently have a palette.
//    E_NOTIMPL            - Control will never have a palette so optimize handling of this control.
//
// Notes:
//
STDMETHODIMP COleControl::GetColorSet
(
    DWORD            dwDrawAspect,
    LONG             lindex,
    void            *IgnoreMe,
    DVTARGETDEVICE  *ptd,
    HDC              hicTargetDevice,
    LOGPALETTE     **ppColorSet
)
{
    if (dwDrawAspect != DVASPECT_CONTENT)
        return DV_E_DVASPECT;

    *ppColorSet = NULL;
    return (OnGetPalette(hicTargetDevice, ppColorSet)) ? ((*ppColorSet) ? S_OK : S_FALSE) : E_NOTIMPL;
}

//=--------------------------------------------------------------------------=
// COleControl::Freeze    [IViewObject2]
//=--------------------------------------------------------------------------=
// Freezes a certain aspect of the object's presentation so that it does not
// change until the IViewObject::Unfreeze method is called.
//
// Parameters:
//    DWORD            - [in] aspect
//    LONG             - [in] part of object to draw
//    void *           - NULL
//    DWORD *          - [out] for Unfreeze
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::Freeze
(
    DWORD   dwDrawAspect,
    LONG    lIndex,
    void   *IgnoreMe,
    DWORD  *pdwFreeze
)
{
    return E_NOTIMPL;
}

//=--------------------------------------------------------------------------=
// COleControl::Unfreeze    [IVewObject2]
//=--------------------------------------------------------------------------=
// Releases a previously frozen drawing. The most common use of this method
// is for banded printing.
//
// Parameters:
//    DWORD        - [in] cookie from freeze
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::Unfreeze
(
    DWORD dwFreeze
)
{
    return E_NOTIMPL;
}

//=--------------------------------------------------------------------------=
// COleControl::SetAdvise    [IViewObject2]
//=--------------------------------------------------------------------------=
// Sets up a connection between the control and an advise sink so that the
// advise sink can be notified about changes in the control's view.
//
// Parameters:
//    DWORD            - [in] aspect
//    DWORD            - [in] info about the sink
//    IAdviseSink *    - [in] the sink
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::SetAdvise
(
    DWORD        dwAspects,
    DWORD        dwAdviseFlags,
    IAdviseSink *pAdviseSink
)
{
    // if it's not a content aspect, we don't support it.
    //
    if (!(dwAspects & DVASPECT_CONTENT)) {
        return DV_E_DVASPECT;
    }

    // set up some flags  [we gotta stash for GetAdvise ...]
    //
    m_fViewAdvisePrimeFirst = (dwAdviseFlags & ADVF_PRIMEFIRST) ? TRUE : FALSE;
    m_fViewAdviseOnlyOnce = (dwAdviseFlags & ADVF_ONLYONCE) ? TRUE : FALSE;

    RELEASE_OBJECT(m_pViewAdviseSink);
    m_pViewAdviseSink = pAdviseSink;
    ADDREF_OBJECT(m_pViewAdviseSink);

    // prime them if they want it [we need to store this so they can get flags later]
    //
    if (m_fViewAdvisePrimeFirst)
        ViewChanged();

    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::GetAdvise    [IViewObject2]
//=--------------------------------------------------------------------------=
// Retrieves the existing advisory connection on the control if there is one.
// This method simply returns the parameters used in the most recent call to
// the IViewObject::SetAdvise method.
//
// Parameters:
//    DWORD *            - [out]  aspects
//    DWORD *            - [out]  advise flags
//    IAdviseSink **     - [out]  the sink
//
// Output:
//    HRESULT
//
// Notes;
//
STDMETHODIMP COleControl::GetAdvise
(
    DWORD        *pdwAspects,
    DWORD        *pdwAdviseFlags,
    IAdviseSink **ppAdviseSink
)
{
    // if they want it, give it to them
    //
    if (pdwAspects)
        *pdwAspects = DVASPECT_CONTENT;

    if (pdwAdviseFlags) {
        *pdwAdviseFlags = 0;
        if (m_fViewAdviseOnlyOnce) *pdwAdviseFlags |= ADVF_ONLYONCE;
        if (m_fViewAdvisePrimeFirst) *pdwAdviseFlags |= ADVF_PRIMEFIRST;
    }

    if (ppAdviseSink) {
        *ppAdviseSink = m_pViewAdviseSink;
        ADDREF_OBJECT(*ppAdviseSink);
    }

    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::GetExtent    [IViewObject2]
//=--------------------------------------------------------------------------=
// Returns the size that the control will be drawn on the
// specified target device.
//
// Parameters:
//    DWORD            - [in] draw aspect
//    LONG             - [in] part of object to draw
//    DVTARGETDEVICE * - [in] information about target device
//    LPSIZEL          - [out] where to put the size
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::GetExtent
(
    DWORD           dwDrawAspect,
    LONG            lindex,
    DVTARGETDEVICE *ptd,
    LPSIZEL         psizel
)
{
    // we already have an implementation of this [from IOleObject]
    //
    return GetExtent(dwDrawAspect, psizel);
}

//=--------------------------------------------------------------------------=
// COleControl::OnGetPalette    [overridable]
//=--------------------------------------------------------------------------=
// called when the host wants palette information.  ideally, people should use
// this sparingly and carefully.
//
// Parameters:
//    HDC            - [in]  HIC for the target device
//    LOGPALETTE **  - [out] where to put the palette
//
// Output:
//    BOOL           - TRUE means we processed it, false means nope.
//
// Notes:
//
BOOL COleControl::OnGetPalette
(
    HDC          hicTargetDevice,
    LOGPALETTE **ppColorSet
)
{
    return FALSE;
}

//=--------------------------------------------------------------------------=
// COleControl::GetRect    [IViewObjectEx]
//=--------------------------------------------------------------------------=
// returns a rectnagle describing a given drawing aspect
//
// Parameters:
//    DWORD             - [in]  aspect
//    LPRECTL           - [out] region rectangle
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::GetRect
(
    DWORD    dvAspect,
    LPRECTL  prcRect
)
{
    RECTL rc;
    BOOL  f;

    // call the user routine and let them return the size
    //
    f = OnGetRect(dvAspect, &rc);
    if (!f) return DV_E_DVASPECT;

    // transform these dudes.
    //
    PixelToHiMetric((LPSIZEL)&rc, (LPSIZEL)prcRect);
    PixelToHiMetric((LPSIZEL)(LPBYTE)&rc + sizeof(SIZEL), (LPSIZEL)((LPBYTE)prcRect + sizeof(SIZEL)));

    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::GetViewStatus    [IViewObjectEx]
//=--------------------------------------------------------------------------=
// returns information about the opactiy of the object and what drawing
// aspects are supported
//
// Parameters:
//    DWORD *            - [out] the status
//
/// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::GetViewStatus
(
    DWORD *pdwStatus
)
{
    // depending on the flag in the CONTROLOBJECTINFO structure, indicate our
    // transparency vs opacity.
    // OVERRIDE:  controls that wish to support multi-pass drawing should
    // override this routine and return, in addition to the flags indication
    // opacity, flags indicating what sort of drawing aspects they support.
    //
    *pdwStatus = FCONTROLISOPAQUE(m_ObjectType) ? VIEWSTATUS_OPAQUE : 0;
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::QueryHitPoint    [IViewObjectEx]
//=--------------------------------------------------------------------------=
// indicates whether a point is within a given aspect of an object.
//
// Parameters:
//    DWORD                - [in]  aspect
//    LPCRECT              - [in]  Bounds rectangle
//    POINT                - [in]  hit location client coordinates
//    LONG                 - [in]  what the container considers close
//    DWORD *              - [out] info about the hit
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::QueryHitPoint
(
    DWORD    dvAspect,
    LPCRECT  prcBounds,
    POINT    ptLocation,
    LONG     lCloseHint,
    DWORD   *pdwHitResult
)
{
    // OVERRIDE: override me if you want to provide additional [non-opaque]
    // functionality
    //
    if (dvAspect != DVASPECT_CONTENT)
        return DV_E_DVASPECT;

    *pdwHitResult = PtInRect(prcBounds, ptLocation) ? HITRESULT_HIT : HITRESULT_OUTSIDE;
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::QueryHitRect    [IViewObjectEx]
//=--------------------------------------------------------------------------=
// indicates wheter any point in a rectangle is within a given drawing aspect
// of an object.
//
// Parameters:
//    DWORD            - [in]  aspect
//    LPCRECT          - [in]  bounds
//    LPCRECT          - [in]  location
//    LONG             - [in]  what host considers close
//    DWORD *          - [out] hit result
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::QueryHitRect
(
    DWORD     dvAspect,
    LPCRECT   prcBounds,
    LPCRECT   prcLocation,
    LONG      lCloseHint,
    DWORD    *pdwHitResult
)
{
    RECT rc;

    // OVERRIDE: override this for additional behaviour
    //
    if (dvAspect != DVASPECT_CONTENT)
        return DV_E_DVASPECT;

    *pdwHitResult = IntersectRect(&rc, prcBounds, prcLocation) ? HITRESULT_HIT : HITRESULT_OUTSIDE;
    return S_OK;
}

//=--------------------------------------------------------------------------=
// COleControl::GetNaturalExtent    [IViewObjectEx]
//=--------------------------------------------------------------------------=
// supports two types of control sizing, content and integral.
//
// Parameters:
//    DWORD            - [in]  aspect
//    LONG             - [in]  index
//    DVTARGETDEVICE * - [in]  target device information
//    HDC              - [in]  HIC
//    DVEXTENTINFO *   - [in]  sizing data
//    LPSIZEL          - [out] sizing data retunred by control
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP COleControl::GetNaturalExtent
(
    DWORD           dvAspect,
    LONG            lIndex,
    DVTARGETDEVICE *ptd,
    HDC             hicTargetDevice,
    DVEXTENTINFO   *pExtentInfo,
    LPSIZEL         pSizel
)
{
    return E_NOTIMPL;
}

//=--------------------------------------------------------------------------=
// COleControl::OnGetRect    [overridable
//=--------------------------------------------------------------------------=
// returns our rectangle
//
// Parameters:
//    DWORD              - [in]  aspect they want the rect for
//    RECTL *            - [out] the rectangle that matches this aspect
//
// Output:
//    BOOL               - false means we don't like the aspect
//
// Notes:
//
BOOL COleControl::OnGetRect
(
    DWORD   dvAspect,
    RECTL  *pRect
)
{
    // by default, we only support content drawing.
    //
    if (dvAspect != DVASPECT_CONTENT)
        return FALSE;

    // just give them our bounding rectangle
    //
    *((LPRECT)pRect) = m_rcLocation;
    return TRUE;
}

//=--------------------------------------------------------------------------=
// _CreateOleDC
//=--------------------------------------------------------------------------=
// creates an HDC given a DVTARGETDEVICE structure.
//
// Parameters:
//    DVTARGETDEVICE *              - [in] duh.
//
// Output:
//    HDC
//
// Notes:
//
HDC _CreateOleDC
(
    DVTARGETDEVICE *ptd
)
{
    LPDEVMODEW   pDevModeW;
    DEVMODEA     DevModeA, *pDevModeA;
    LPOLESTR     lpwszDriverName;
    LPOLESTR     lpwszDeviceName;
    LPOLESTR     lpwszPortName;
    HDC          hdc;

    // return screen DC for NULL target device
    //
    if (!ptd)
        return CreateDC("DISPLAY", NULL, NULL, NULL);

    if (ptd->tdExtDevmodeOffset == 0)
        pDevModeW = NULL;
    else
        pDevModeW = (LPDEVMODEW)((LPSTR)ptd + ptd->tdExtDevmodeOffset);

    lpwszDriverName = (LPOLESTR)((BYTE*)ptd + ptd->tdDriverNameOffset);
    lpwszDeviceName = (LPOLESTR)((BYTE*)ptd + ptd->tdDeviceNameOffset);
    lpwszPortName   = (LPOLESTR)((BYTE*)ptd + ptd->tdPortNameOffset);

    MAKE_ANSIPTR_FROMWIDE(pszDriverName, lpwszDriverName);
    MAKE_ANSIPTR_FROMWIDE(pszDeviceName, lpwszDeviceName);
    MAKE_ANSIPTR_FROMWIDE(pszPortName,   lpwszPortName);

    // wow, this sucks.
    //
    if (pDevModeW) {
        WideCharToMultiByte(CP_ACP, 0, pDevModeW->dmDeviceName, -1, (LPSTR)DevModeA.dmDeviceName, CCHDEVICENAME, NULL, NULL);
memcpy(&DevModeA.dmSpecVersion, &pDevModeW->dmSpecVersion,
offsetof(DEVMODEA, dmFormName) - offsetof(DEVMODEA, dmSpecVersion));
        WideCharToMultiByte(CP_ACP, 0, pDevModeW->dmFormName, -1, (LPSTR)DevModeA.dmFormName, CCHFORMNAME, NULL, NULL);
memcpy(&DevModeA.dmLogPixels, &pDevModeW->dmLogPixels, sizeof(DEVMODEA) - offsetof(DEVMODEA, dmLogPixels));
        if (pDevModeW->dmDriverExtra) {
            pDevModeA = (DEVMODEA *)HeapAlloc(g_hHeap, 0, sizeof(DEVMODEA) + pDevModeW->dmDriverExtra);
            if (!pDevModeA) return NULL;
            memcpy(pDevModeA, &DevModeA, sizeof(DEVMODEA));
            memcpy(pDevModeA + 1, pDevModeW + 1, pDevModeW->dmDriverExtra);
        } else
            pDevModeA = &DevModeA;

DevModeA.dmSize = sizeof(DEVMODEA);
    } else
        pDevModeA = NULL;

    hdc = CreateDC(pszDriverName, pszDeviceName, pszPortName, pDevModeA);
    if (pDevModeA != &DevModeA) HeapFree(g_hHeap, 0, pDevModeA);
    return hdc;
}


CTLWRAP.CPP

//=--------------------------------------------------------------------------=
// CtlWrap.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// wrappers for various routines that have slightly different implementations
// for windowed and windowless controls.
//
#include "IPServer.H"

#include "CtrlObj.H"

// for ASSERT and FAIL
//
SZTHISFILE

//=--------------------------------------------------------------------------=
// COleControl::OcxGetFocus    [wrapper]
//=--------------------------------------------------------------------------=
// indicates whether or not we have the focus.
//
// Parameters:
//    none
//
// Output:
//    TRUE if we have focus, else false
//
// Notes:
//
BOOL COleControl::OcxGetFocus
(
    void
)
{
    // if we're windowless, the site provides this functionality
    //
    if (m_pInPlaceSiteWndless) {
        return (m_pInPlaceSiteWndless->GetFocus() == S_OK);
    } else {

        // we've got a window.  just let the APIs do our work
        //
        if (m_fInPlaceActive)
            return (GetFocus() == m_hwnd);
        else
            return FALSE;
    }

    // dead code
}

//=--------------------------------------------------------------------------=
// COleControl::OcxGetWindowRect    [wrapper]
//=--------------------------------------------------------------------------=
// returns the current rectangle for this control, and correctly handles
// windowless vs windowed.
//
// Parameters:
//    LPRECT                - [out]  duh.
//
// Output:
//    BOOL                  - false means unexpected.
//
// Notes:
//
BOOL COleControl::OcxGetWindowRect
(
    LPRECT prc
)
{
    // if we're windowless, then we have this information already!
    //
    if (Windowless()) {
        *prc = m_rcLocation;
        return TRUE;
    } else
        return GetWindowRect(m_hwnd, prc);

    // dead code
}

//=--------------------------------------------------------------------------=
// COleControl::OcxDefWindowProc    [wrapper]
//=--------------------------------------------------------------------------=
// default window processing
//
// Parameters:
//    UINT           - [in] duh.
//    WPARAM         - [in] duh.
//    LPARAM         - [in] DUH.
//
// Output:
//    LRESULT
//
// Notes:
//
LRESULT COleControl::OcxDefWindowProc
(
    UINT   msg,
    WPARAM wParam,
    LPARAM lParam
)
{
    LRESULT l;

    // if we're windowless, this is a site provided pointer
    //
    if (m_pInPlaceSiteWndless)
        m_pInPlaceSiteWndless->OnDefWindowMessage(msg, wParam, lParam, &l);
    else
        // we've got a window -- just pass it along
        //
        l = DefWindowProc(m_hwnd, msg, wParam, lParam);

    return l;
}

//=--------------------------------------------------------------------------=
// COleControl::OcxGetDC    [wrapper]
//=--------------------------------------------------------------------------=
// wraps the functionality of GetDC, and correctly handles windowless controls
//
// Parameters:
//    none
//
// Output:
//    HDC            - null means we couldn't get one
//
// Notes:
//    - we don't bother with a bunch of the IOleInPlaceSiteWindowless::GetDc
//      parameters, since the windows GetDC doesn't expose these either. users
//      wanting that sort of fine tuned control can call said routine
//      explicitly
//
HDC COleControl::OcxGetDC
(
    void
)
{
    HDC hdc = NULL;

    // if we're windowless, the site provides this functionality.
    //
    if (m_pInPlaceSiteWndless)
        m_pInPlaceSiteWndless->GetDC(NULL, 0, &hdc);
    else
        hdc = GetDC(m_hwnd);

    return hdc;
}

//=--------------------------------------------------------------------------=
// COleControl::OcxReleaseDC    [wrapper]
//=--------------------------------------------------------------------------=
// releases a DC returned by OcxGetDC
//
// Parameters:
//    HDC             - [in] release me
//
// Output:
//    none
//
// Notes:
//
void COleControl::OcxReleaseDC
(
    HDC hdc
)
{
    // if we're windowless, the site does this for us
    //
    if (m_pInPlaceSiteWndless)
        m_pInPlaceSiteWndless->ReleaseDC(hdc);
    else
        ReleaseDC(m_hwnd, hdc);
}

//=--------------------------------------------------------------------------=
// COleControl::OcxSetCapture    [wrapper]
//=--------------------------------------------------------------------------=
// provides a means for the control to get or release capture.
//
// Parameters:
//    BOOL            - [in] true means take, false release
//
// Output:
//    BOOL            - true means it's yours, false nuh-uh
//
// Notes:
//
BOOL COleControl::OcxSetCapture
(
    BOOL fGrab
)
{
    HRESULT hr;

    // the host does this for us if we're windowless [i'm getting really bored
    // of typing that]
    //
    if (m_pInPlaceSiteWndless) {
        hr = m_pInPlaceSiteWndless->SetCapture(fGrab);
        return (hr == S_OK);
    } else {
        // people shouldn't call this when they're not in-place active, but
        // just in case...
        //
        if (m_fInPlaceActive) {
            SetCapture(m_hwnd);
            return TRUE;
        } else
            return FALSE;
    }

    // dead code
}

//=--------------------------------------------------------------------------=
// COleControl::OcxGetCapture    [wrapper]
//=--------------------------------------------------------------------------=
// tells you whether or not you have the capture.
//
// Parameters:
//    none
//
// Output:
//    BOOL         - true it's yours, false it's not
//
// Notes:
//
BOOL COleControl::OcxGetCapture
(
    void
)
{
    // host does this for windowless dudes
    //
    if (m_pInPlaceSiteWndless)
        return m_pInPlaceSiteWndless->GetCapture() == S_OK;
    else {
        // people shouldn't call this when they're not in-place active, but
        // just in case.
        //
        if (m_fInPlaceActive)
            return GetCapture() == m_hwnd;
        else
            return FALSE;
    }

    // dead code
}

//=--------------------------------------------------------------------------=
// COleControl::OcxInvalidateRect    [wrapper]
//=--------------------------------------------------------------------------=
// invalidates the control's rectangle
//
// Parameters:
//    LPCRECT            - [in] rectangle to invalidate
//    BOOL               - [in] do we erase background first?
//
// Output:
//    BOOL
//
// Notes:
//
BOOL COleControl::OcxInvalidateRect
(
    LPCRECT prcInvalidate,
    BOOL    fErase
)
{
    // if we're windowless, then we need to get the site to do all this for
    // us
    if (m_pInPlaceSiteWndless)
        return m_pInPlaceSiteWndless->InvalidateRect(prcInvalidate, fErase) == S_OK;
    else {
        // otherwise do something different depending on whether or not we're
        // in place active or not
        //
        if (m_fInPlaceActive)
            return InvalidateRect(m_hwnd, prcInvalidate, TRUE);
        else
            ViewChanged();
    }

    return TRUE;
}

//=--------------------------------------------------------------------------=
// COleControl::OcxScrollRect    [wrapper]
//=--------------------------------------------------------------------------=
// does some window scrolling for the control
//
// Parameters:
//    LPCRECT             - [in] region to scroll
//    LPCRECT             - [in] region to clip
//    int                 - [in] dx to scroll
//    int                 - [in] dy to scroll
//
// Output:
//    BOOL
//
// Notes:
//
BOOL COleControl::OcxScrollRect
(
    LPCRECT  prcBounds,
    LPCRECT  prcClip,
    int      dx,
    int      dy
)
{
    // if we're windowless, the site provides this functionality, otherwise
    // APIs do the job
    //
    if (m_pInPlaceSiteWndless)
        return m_pInPlaceSiteWndless->ScrollRect(dx, dy, prcBounds, prcClip) == S_OK;
    else {
        if (m_fInPlaceActive)
            ScrollWindowEx(m_hwnd, dx, dy, prcBounds, prcClip, NULL, NULL, SW_INVALIDATE);
        else
            return FALSE;
    }

    return TRUE;
}


DEBUG.CPP

//=--------------------------------------------------------------------------=
// Debug.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// contains various methods that will only really see any use in DEBUG builds
//
#ifdef DEBUG

#include "IPServer.H"
#include <stdlib.h>

//=--------------------------------------------------------------------------=
// Private Constants
//---------------------------------------------------------------------------=
//
static const char szFormat[]  = "%s\nFile %s, Line %d";
static const char szFormat2[] = "%s\n%s\nFile %s, Line %d";

#define _SERVERNAME_ "ActiveX Framework"

static const char szTitle[]  = _SERVERNAME_ " Assertion  (Abort = UAE, Retry = INT 3, Ignore = Continue)";

//=--------------------------------------------------------------------------=
// Local functions
//=--------------------------------------------------------------------------=
int NEAR _IdMsgBox(LPSTR pszText, LPCSTR pszTitle, UINT mbFlags);

//=--------------------------------------------------------------------------=
// DisplayAssert
//=--------------------------------------------------------------------------=
// Display an assert message box with the given pszMsg, pszAssert, source
// file name, and line number. The resulting message box has Abort, Retry,
// Ignore buttons with Abort as the default.  Abort does a FatalAppExit;
// Retry does an int 3 then returns; Ignore just returns.
//
VOID DisplayAssert
(
    LPSTR pszMsg,
    LPSTR pszAssert,
    LPSTR pszFile,
    UINT line
)
{
    charszMsg[250];
    LPSTRlpszText;

    lpszText = pszMsg;// Assume no file & line # info

    // If C file assert, where you've got a file name and a line #
    //
    if (pszFile) {

        // Then format the assert nicely
        //
        wsprintf(szMsg, szFormat, (pszMsg&&*pszMsg) ? pszMsg : pszAssert, pszFile, line);
        lpszText = szMsg;
    }

    // Put up a dialog box
    //
    switch (_IdMsgBox(lpszText, szTitle, MB_ICONHAND|MB_ABORTRETRYIGNORE|MB_SYSTEMMODAL)) {
        case IDABORT:
            FatalAppExit(0, lpszText);
            return;

        case IDRETRY:
            // call the win32 api to break us.
            //
            DebugBreak();
            return;
    }

    return;
}

//=---------------------------------------------------------------------------=
// Beefed-up version of WinMessageBox.
//=---------------------------------------------------------------------------=
//
int NEAR _IdMsgBox
(
    LPSTRpszText,
    LPCSTRpszTitle,
    UINTmbFlags
)
{
    HWND hwndActive;
    int  id;

    hwndActive = GetActiveWindow();

    id = MessageBox(hwndActive, pszText, pszTitle, mbFlags);

    return id;
}

#endif // DEBUG


DWINVERS.H

#define vszMakeDate"April 10, 1996\0"
#define vszMakeVers"Version 2810 - DEBUG - ntfs-f\0\0"
#define vszVersNum"2810\0"
#define vszVersNumAll"5.00.2810\0"
#define vusVersNuml210
#define vusVersNumf228
#define vszCopyright    "Copyright 1987 - 1998 Microsoft Corporation\0\0"
#define VBA_VERHI0x50000
#define VBA_VERLO0x183C


EXTOBJ.CPP

////
//
// CExpandoObject
//
// Notes:
// 1) If the LCID passed to this object changes from call to call we are screwed. This is hard to
// create an ASSERT for because it would require memoizing the LCID at some point.
// 2) There is a maximum on the number of slots allowed (this is currently 2048)
// 3) This is not a thread safe structure.
// 4) I'm currently using malloc -- this is probably wrong for IE.
//

// for ASSERT and FAIL
//

#include "IPServer.H"
#include "LocalSrv.H"
#include "Globals.H"
#include "extobj.h"
#include "Util.H"
#define GTR_MALLOC(size)  CoTaskMemAlloc(size)
#define GTR_FREE(pv) CoTaskMemFree(pv)

SZTHISFILE
////
//
// Private Utility Functions
//
////

////
//
// Get the ID of a Name
//

HRESULT CExpandoObject::GetIDOfName(LPOLESTR name, LCID lcid, BOOL caseSensitive, DISPID* id)
{
HRESULT hr = NOERROR;
ULONG hash = LHashValOfName(lcid, name);
UINT hashIndex = hash % kSlotHashTableSize;
CExpandoObjectSlot* slot;

for (slot=GetHashTableHead(hashIndex); slot!=NULL; slot=slot->Next(m_slots))
{
if (slot->CompareName(name, hash, caseSensitive))
{
*id = slot->DispId();
goto Exit;
}
}

// not found
hr = DISP_E_UNKNOWNNAME;
*id = DISPID_UNKNOWN;

Exit:
return hr;
}

////
//
// Add a new slot to the object
//

HRESULT CExpandoObject::AddSlot(LPOLESTR name, LCID lcid, BOOL caseSensitive, VARIANT* initialValue, DISPID* id)
{
HRESULT hr = NOERROR;
ULONG hash = LHashValOfName(lcid, name);
UINT hashIndex = hash % kSlotHashTableSize;
CExpandoObjectSlot* slot;
DISPIDdispId;

// first check if the slot exists
for (slot=GetHashTableHead(hashIndex); slot!=NULL; slot=slot->Next(m_slots))
{
// bail if the name matches
if (slot->CompareName(name, hash, caseSensitive))
{
hr = E_INVALIDARG;
goto Exit;
}
}

// allocate a slot
dispId = (DISPID) m_totalSlots;
slot = AllocSlot();
if (slot == NULL)
{
hr = E_OUTOFMEMORY;
goto Exit;
}

// Initialize it
// BUGBUG robwell 8May96 If this fails and the initialValue is not VT_EMTPY or VT_NULL
// there in no cleanup code.
hr = slot->Init(name, lcid, dispId + m_dispIdBase, initialValue);
if (FAILED(hr))
{
// free the slot and dispId
m_totalSlots -= 1;
goto Exit;
}

// intern the slot into the proper hash table
slot->Insert(m_slots, m_hashTable[hashIndex]);

// set the DISPID return value
*id = slot->DispId();

Exit:
return hr;
}

////
//
// Slot allocation
//
// Because slots are never freed there is no free method
//

CExpandoObjectSlot* CExpandoObject::AllocSlot()
{
// limit on the number of slots
if (m_totalSlots >= kMaxTotalSlots)
return NULL;

// do we need to realloc the array?
if (m_totalSlots == m_slotTableSize)
{
UINT i;
UINT newSize;
CExpandoObjectSlot* newSlots;

// allocate twice as many slots unless first time around
if (m_slotTableSize == 0)
newSize = kInitialSlotTableSize;
else
newSize = m_slotTableSize * 2;

// allocate the space for the slots
newSlots = (CExpandoObjectSlot*) GTR_MALLOC(sizeof(CExpandoObjectSlot)*newSize);
if (newSlots == NULL)
return NULL;

// copy the old values if the old m_slots is not NULL
if (m_slots)
{
// copy the slots
memcpy(newSlots, m_slots, sizeof(CExpandoObjectSlot)*m_totalSlots);
// free the old values
GTR_FREE(m_slots);
}

// construct all of the unused slots
for (i=m_totalSlots; i<newSize; ++i)
newSlots[i].Construct();

// make the new array the new table and fix the total size
m_slots = newSlots;
m_slotTableSize = newSize;
}

// return a pointer to the slot and bump the totalSlots count
return &m_slots[m_totalSlots++];
}

////
//
// Free all of the slots
//

void CExpandoObject::FreeAllSlots()
{
UINT i;
UINT initedSlotCount;
CExpandoObjectSlot* slots;

// first clear the hash table
ClearHashTable();

// detach the slots
slots = m_slots;
initedSlotCount = m_totalSlots;

// clear the object info
m_totalSlots = 0;
m_slotTableSize = 0;
m_slots = NULL;

// only need to destruct those slots in use
for (i=0; i<initedSlotCount; ++i)
slots[i].Destruct();

// free the storage
if (slots)
GTR_FREE(slots);
}

////
//
// IDispatch Methods
//
////

HRESULT CExpandoObject::GetTypeInfoCount(UINT *pctinfo)
{
*pctinfo = 0;
return NOERROR;
}

HRESULT CExpandoObject::GetTypeInfo(UINT itinfo, LCID lcid, ITypeInfo **pptinfo)
{
*pptinfo = NULL;
return E_NOTIMPL;
}

HRESULT CExpandoObject::GetIDsOfNames(
REFIID riid,
LPOLESTR *prgpsz,
UINT cpsz,
LCID lcid,
DISPID *prgdispid
)
{
HRESULT hr;

if (IID_NULL != riid)
return DISP_E_UNKNOWNINTERFACE;

// First see if the outer object knows about the name
if (m_pdisp)
{
hr = m_pdisp->GetIDsOfNames(
riid,
prgpsz,
cpsz,
lcid,
prgdispid);

// if so, just return
if (SUCCEEDED(hr))
return hr;
}

// Otherwise look on our expanded properties

if (cpsz == 0)
return NOERROR;

// get the ids for the name
hr = GetIDOfName(prgpsz[0], lcid, FALSE, &prgdispid[0]);

// clear the rest of the array
for (unsigned int i = 1; i < cpsz; i++)
{
if (SUCCEEDED(hr))
hr = DISP_E_UNKNOWNNAME;
prgdispid[i] = DISPID_UNKNOWN;
}

return hr;
}

HRESULT CExpandoObject::Invoke(
DISPID dispID,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS *pdispparams,
VARIANT *pvarRes,
EXCEPINFO *pexcepinfo,
UINT *puArgErr
)
{
if (IID_NULL != riid)
return DISP_E_UNKNOWNINTERFACE;

HRESULT hr;

// First try the outer object's invoke
if (m_pdisp)
{
hr = m_pdisp->Invoke(
dispID,
riid,
lcid,
wFlags,
pdispparams,
pvarRes,
pexcepinfo,
puArgErr
);

// If that succeeded, we're done
if (SUCCEEDED(hr))
return hr;
}

// Otherwise, try the expando object's invoke
if (NULL != puArgErr)
*puArgErr = 0;

if (wFlags & DISPATCH_PROPERTYGET)
{
if (NULL == pvarRes)
return NOERROR;

if (NULL != pdispparams && 0 != pdispparams->cArgs)
return E_INVALIDARG;

// clear the result slot
pvarRes->vt = VT_EMPTY;
return GetSlot(dispID, pvarRes);
}

if (wFlags & (DISPATCH_PROPERTYPUT | DISPATCH_PROPERTYPUTREF))
{
if (NULL == pdispparams
|| 1 != pdispparams->cArgs
|| 1 != pdispparams->cNamedArgs
|| DISPID_PROPERTYPUT != pdispparams->rgdispidNamedArgs[0]
)
return DISP_E_PARAMNOTOPTIONAL;

return SetSlot(dispID, &pdispparams->rgvarg[0]);
}

return DISP_E_MEMBERNOTFOUND;
}

////
//
// IDispatchEx methods
//
////

// Get dispID for names, with options
HRESULT STDMETHODCALLTYPE CExpandoObject::GetIDsOfNamesEx(
REFIID riid,
LPOLESTR *prgpsz,
UINT cpsz,
LCID lcid,
DISPID *prgid,
DWORD grfdex
)
{
HRESULT hr;
BOOL caseSensitive = ((grfdex & fdexCaseSensitive) != 0);

// First see if the outer object knows about the name
if (m_pdisp)
{
hr = m_pdisp->GetIDsOfNames(
riid,
prgpsz,
cpsz,
lcid,
prgid);

// if so, just return
if (SUCCEEDED(hr))
return hr;
}

if (IID_NULL != riid)
return DISP_E_UNKNOWNINTERFACE;

if (cpsz == 0)
return NOERROR;

// check the array arguments
if (prgpsz == NULL || prgid == NULL)
return E_INVALIDARG;

// get the id from the name
hr = GetIDOfName(prgpsz[0], lcid, caseSensitive, &prgid[0]);

// create the slot?
if (hr == DISP_E_UNKNOWNNAME && (grfdex & fdexDontCreate) == 0)
{
VARIANT initialValue;

if (grfdex & fdexInitNull)
initialValue.vt = VT_NULL;
else
initialValue.vt = VT_EMPTY;

hr = AddSlot(prgpsz[0], lcid, caseSensitive, &initialValue, &prgid[0]);
}

// clear the rest of the array
for (unsigned int i = 1; i < cpsz; i++)
{
hr = DISP_E_UNKNOWNNAME;
prgid[i] = DISPID_UNKNOWN;
}

return hr;
}

// Enumerate dispIDs and their associated "names".
// Returns S_FALSE if the enumeration is done, NOERROR if it's not, an
// error code if the call fails.
HRESULT STDMETHODCALLTYPE CExpandoObject::GetNextDispID(
DISPID id,
DISPID *pid,
BSTR *pbstrName
)
{
HRESULT hr;
CExpandoObjectSlot* slot;

// check the outgoing parameters
if (pid == NULL || pbstrName == NULL)
return E_INVALIDARG;

// set to the default failure case
*pid = DISPID_UNKNOWN;
*pbstrName = NULL;

// get the next slot
hr = Next(id, slot);
if (hr == NOERROR)
{
BSTR name;

// allocate the result string
name = SysAllocString(slot->Name());
if (name == NULL)
return E_OUTOFMEMORY;

// fill in the outgoing parameters
*pid = slot->DispId();
*pbstrName = name;
}

return hr;
}

// Copy all of the expando-object properties from obj
CExpandoObject::CloneProperties(CExpandoObject& obj)
{
    // BUGBUG  PhilBo
    // The initialization code below is copied from the default constructor.
    // This should be factored out into a shared method.

// Copy each of the properties from the original object
    HRESULT hr = S_OK;
    DISPID dispid = 0;
    BSTR bstrName = NULL;

    while (obj.GetNextDispID(dispid, &dispid, &bstrName) == S_OK)
    {
        // Get the value of the property from the original object
        VARIANT varResult;
        DISPPARAMS dispparamsNoArgs = {NULL, NULL, 0, 0};
        VariantInit(&varResult);

        hr = obj.Invoke(
        dispid,
        IID_NULL,
        LOCALE_SYSTEM_DEFAULT,
        DISPATCH_PROPERTYGET,
        &dispparamsNoArgs, &varResult, NULL, NULL);

        ASSERT(SUCCEEDED(hr), "");
        if (FAILED(hr))
            continue;

        // Set the property on the new object
        DISPID dispidNew = 0;
    hr = GetIDsOfNamesEx(IID_NULL, &bstrName, 1, LOCALE_SYSTEM_DEFAULT,
    &dispidNew, 0);

        ASSERT(SUCCEEDED(hr), "");
        if (FAILED(hr))
            continue;

        DISPPARAMS dispparams;
        dispparams.rgvarg[0] = varResult;

        DISPID rgdispid[] = {DISPID_PROPERTYPUT};
        dispparams.rgdispidNamedArgs = rgdispid;
        dispparams.cArgs = 1;
        dispparams.cNamedArgs = 1;

        hr = Invoke(
    dispidNew,
    IID_NULL,
    LOCALE_SYSTEM_DEFAULT,
    DISPATCH_PROPERTYPUT,
    &dispparams, NULL, NULL, NULL);
    }

    return hr;
}


GLOBALS.CPP

//=--------------------------------------------------------------------------=
// Globals.C
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// contains global variables and strings and the like that just don't fit
// anywhere else.
//
#include "IPServer.H"

//=--------------------------------------------------------------------------=
// support for licensing
//
BOOL g_fMachineHasLicense;
BOOL g_fCheckedForLicense;

//=--------------------------------------------------------------------------=
// does our server have a type library?
//
BOOL g_fServerHasTypeLibrary = TRUE;

//=--------------------------------------------------------------------------=
// our instance handles
//
HINSTANCE    g_hInstance;
HINSTANCE    g_hInstResources;
VARIANT_BOOL g_fHaveLocale;

//=--------------------------------------------------------------------------=
// our global memory allocator and global memory pool
//
HANDLE   g_hHeap;

//=--------------------------------------------------------------------------=
// apartment threading support.
//
CRITICAL_SECTION    g_CriticalSection;

//=--------------------------------------------------------------------------=
// global parking window for parenting various things.
//
HWND     g_hwndParking;

//=--------------------------------------------------------------------------=
// system information
//
BOOL    g_fSysWin95;                    // we're under Win95 system, not just NT SUR
BOOL    g_fSysWinNT;                    // we're under some form of Windows NT
BOOL    g_fSysWin95Shell;               // we're under Win95 or Windows NT SUR { > 3/51)


INTERNET.CPP

//=--------------------------------------------------------------------------=
// Internet.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// contains internet helper classes CDownloadSink and CInternetControl
//
#include "IPServer.H"
#include "Internet.H"
#include "Util.H"

static VARTYPE rgI4[] = { VT_I4 };

typedef enum {
    InternetEvent_Progress = 0,
    InternetEvent_ReadyStateChange = 1
} INTERNETEVENTS;

static EVENTINFO rgEvents [] = {
    { DISPID_PROGRESS, 1, rgI4 },           // (long percentDone)
    { DISPID_READYSTATECHANGE, 1, rgI4 },       // (OLE_READYSTATE newState)
};

// local class for doing async monitoring. It's not really all that
// general purpose, but it does the job...

class CDownloadSink : public IBindStatusCallback
{
public:
    CDownloadSink(IUnknown *punkOuter,CInternetControl *,DISPID );
    ~CDownloadSink();

    STDMETHOD(QueryInterface)(REFIID riid, void **ppvObjOut);
    STDMETHOD_(ULONG, AddRef)();
    STDMETHOD_(ULONG, Release)();

        STDMETHOD(OnStartBinding)(
            /* [in] */ DWORD grfBSCOption,
            /* [in] */ IBinding *pib);

        STDMETHOD(GetPriority)(
            /* [out] */ LONG *pnPriority);

        STDMETHOD(OnLowResource)(
            /* [in] */ DWORD reserved);

        STDMETHOD(OnProgress)(
            /* [in] */ ULONG ulProgress,
            /* [in] */ ULONG ulProgressMax,
            /* [in] */ ULONG ulStatusCode,
            /* [in] */ LPCWSTR szStatusText);

        STDMETHOD(OnStopBinding)(
            /* [in] */ HRESULT hresult,
            /* [in] */ LPCWSTR szError);

        STDMETHOD(GetBindInfo)(
            /* [out] */ DWORD *grfBINDINFOF,
            /* [unique][out][in] */ BINDINFO *pbindinfo);

        STDMETHOD(OnDataAvailable)(
            /* [in] */ DWORD grfBSCF,
            /* [in] */ DWORD dwSize,
            /* [in] */ FORMATETC *pformatetc,
            /* [in] */ STGMEDIUM *pstgmed);

        STDMETHOD(OnObjectAvailable)(
            /* [in] */ REFIID riid,
            /* [iid_is][in] */ IUnknown *punk);

        CDownloadSink * Next()                  { return(m_next); }
        void            Next(CDownloadSink *n)  { m_next = n; }

        DISPID          DispId()  { return(m_propId); }
        IBinding *      Binding() { return(m_binding); }

private:

        CDownloadSink *         m_next;
        CInternetControl *      m_control;
        DISPID                  m_propId;
        IBinding *              m_binding;
        DWORD                   m_ref;
                IStream *                               m_stream;

};

CDownloadSink::CDownloadSink
(
        IUnknown *              punkOuter,
        CInternetControl *      control,
        DISPID                  propId
)
{
//      CHECK_POINTER(control);

        m_control = control;
        m_control->AddRef();

        m_propId  = propId;
        m_next    = 0;
        m_binding = 0;
        m_ref     = 0;
        m_stream  = 0;
}

CDownloadSink::~CDownloadSink()
{
        if( m_control )
                m_control->Release();
        if( m_binding )
                m_binding->Release();
        if( m_stream )
                m_stream->Release();
}

STDMETHODIMP
CDownloadSink::QueryInterface(const GUID &iid,void **ppv )
{
        if( IsEqualGUID(iid,IID_IUnknown) || IsEqualGUID(iid,IID_IBindStatusCallback) )
        {
                *ppv = this;
                AddRef();
                return(NOERROR);
        }
        return( E_NOINTERFACE );
}

STDMETHODIMP_(ULONG)
CDownloadSink::AddRef()
{
        return(++m_ref);
}

STDMETHODIMP_(ULONG)
CDownloadSink::Release()
{
        if(!--m_ref)
        {
                delete this;
                return(0);
        }
        return( m_ref );
}

STDMETHODIMP
CDownloadSink::GetBindInfo( DWORD *grfBINDF, BINDINFO *pbindInfo)
{
    *grfBINDF = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA;
        return(NOERROR);
}

STDMETHODIMP
CDownloadSink::OnStartBinding(DWORD /*grfBSCOption*/,IBinding *pib)
{
        // BUGBUG: should check to see options are what we think they are
        m_binding = pib;
        pib->AddRef();
        return(NOERROR);
}

STDMETHODIMP
CDownloadSink::GetPriority( LONG *pnPriority)
{
        return(E_NOTIMPL);
}

STDMETHODIMP
CDownloadSink::OnProgress
(
     ULONG ulProgress,
     ULONG ulProgressMax,
     ULONG ulStatusCode,
     LPCWSTR pwzStatusText
)
{
        return(m_control->OnProgress(m_propId,ulProgress,
                                                        ulProgressMax,ulStatusCode,pwzStatusText) );
}

STDMETHODIMP
CDownloadSink::OnDataAvailable
(
     DWORD                      grfBSCF,
     DWORD                      dwSize,
     FORMATETC *        pFmtetc,
         STGMEDIUM *    pstgmed
)
{
#ifdef DEBUG
        char msg[200];
        wsprintf(msg,"::OnDataAvailable(%0xd,%d,%s,%s)\n",grfBSCF,dwSize,
                pFmtetc ? "pFmtetc" : "NULL", pstgmed ? "pstgmed" : "NULL" );
        OutputDebugString(msg);
#endif

    if( !m_stream )
                m_stream = pstgmed->pstm;

        return(m_control->OnData(       m_propId,
                                                                grfBSCF,
                                                                m_stream,
                                                                dwSize ));
}

STDMETHODIMP
CDownloadSink::OnObjectAvailable
(
        REFIID riid,
    IUnknown *punk
)
{
        return(E_NOTIMPL);
}

STDMETHODIMP
CDownloadSink::OnLowResource( DWORD reserved)
{
        // BUGBUG: really should have this kind of harsh policy on this ...
        m_binding->Abort();
        return(S_OK);
}

STDMETHODIMP
CDownloadSink::OnStopBinding(HRESULT hrError, LPCWSTR szError)
{
        m_binding->Release();
        m_binding = 0;
        m_control->Release();
        m_control = 0;

        return(NOERROR);
}

//------------------------------------------------------
//
// class CInternetControl
//
//
CInternetControl::CInternetControl
(
        IUnknown *      pUnkOuter,
        int                     iPrimaryDispatch,
        void *          pMainInterface
)
        : COleControl(pUnkOuter,iPrimaryDispatch,pMainInterface)
{
        m_host = 0;
        m_readyState = READYSTATE_LOADING;
}

CInternetControl::~CInternetControl()
{
        if( m_host )
                m_host->Release();
}

HRESULT CInternetControl::InternalQueryInterface
(
    REFIID  riid,
    void  **ppvObjOut
)
{
    *ppvObjOut = NULL;
     return COleControl::InternalQueryInterface(riid, ppvObjOut);
}

HRESULT
CInternetControl::GetBindHost()
{

        if( m_host )
                return(NOERROR);

    // Try service provider first...

        IServiceProvider * serviceProvider = 0;

        HRESULT hr = m_pClientSite->QueryInterface
                                                                        (
                                                                                IID_IServiceProvider,
                                                                                (void**)&serviceProvider
                                                                        );

        if( SUCCEEDED(hr) )
    {
                hr = serviceProvider->QueryService
                                    (
                                        SID_IBindHost,
                                        IID_IBindHost,
                                        (void**)&m_host
                                    );
                serviceProvider->Release();
    }

    if( FAILED(hr) )
    {
        // Some containers put IBindHost directly on the client site

        hr = m_pClientSite->QueryInterface
                                                                        (
                                        IID_IBindHost,
                                        (void**)&m_host
                                                                        );

    }

        return(hr);

}

HRESULT CInternetControl::GetAMoniker( LPOLESTR url, IMoniker ** ppmkr )
{
        HRESULT hr = GetBindHost();

        if( SUCCEEDED(hr) )
                hr = m_host->CreateMoniker(url,NULL, ppmkr,0);

        if( FAILED(hr) )
    {
           // FUTURE: This really should be a call to MkParseDisplayNameEx!!!
       hr = ::CreateURLMoniker(0,url,ppmkr);
       // hr = ::MkParseDisplayNameEx(0, url, 0, ppmkr);
    }

        return( hr );
}

HRESULT CInternetControl::SetupDownload( LPOLESTR url, DISPID propId )
{
        CHECK_POINTER(url);

        IMoniker * pmkr;

        HRESULT hr = GetAMoniker( url, &pmkr );

        IBindCtx * pBindCtx = 0;

        if( SUCCEEDED(hr) )
    {
        hr = ::CreateBindCtx(0,&pBindCtx);
    }

        CDownloadSink * sink = 0;

        if( SUCCEEDED(hr) )
        {
                sink = new CDownloadSink(0,this,propId);
                if( sink )
                        sink->AddRef();
        }

        if( SUCCEEDED(hr) && !sink )
                hr = E_OUTOFMEMORY;

        if( SUCCEEDED(hr) )
        {
                // BUGBUG: There should be a define for 0x77
                hr = ::RegisterBindStatusCallback(pBindCtx, sink,0, 0) ;
        }

        IStream * strm = 0;

        if( SUCCEEDED(hr) )
                hr = pmkr->BindToStorage( pBindCtx, 0, IID_IStream, (void**)&strm );

        if( strm )
                strm->Release();

        if( pBindCtx )
                pBindCtx->Release();

        if( FAILED(hr) && sink )
                sink->Release();

        return(hr);

}

HRESULT CInternetControl::OnData( DISPID, DWORD,IStream *, DWORD)
{
        return(NOERROR);
}

HRESULT CInternetControl::OnProgress( DISPID, ULONG progress, ULONG themax, ULONG, LPCWSTR)
{
        return(NOERROR);
}

HRESULT CInternetControl::FireReadyStateChange( long newState )
{
        FireEvent( &::rgEvents[InternetEvent_ReadyStateChange], m_readyState = newState );
        return(S_OK);
}

HRESULT CInternetControl::FireProgress( ULONG dwAmount )
{
        FireEvent( &::rgEvents[InternetEvent_Progress], dwAmount );
        return(S_OK);
}


IPSERVER.CPP

//=--------------------------------------------------------------------------=
// IPServer.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// implements all exported DLL functions for the program, as well as a few
// others that will be used by same
//
#include "IPServer.H"
#include "LocalSrv.H"

#include "AutoObj.H"
#include "ClassF.H"
#include "CtrlObj.H"
#include "Globals.H"
#include "Unknown.H"
#include "Util.H"

//=--------------------------------------------------------------------------=
// Private module level data
//
// for ASSERT and FAIL
//
SZTHISFILE

//=--------------------------------------------------------------------------=
// These are used for reflection in OLE Controls.  Not that big of a hit that
// we mind defining them for all servers, including automation or generic
// COM.
//
char g_szReflectClassName [] = "CtlFrameWork_ReflectWindow";
BYTE g_fRegisteredReflect = FALSE;

//=--------------------------------------------------------------------------=
// allow controls to register for DLL_THREAD_ATTACH and DLL_THREAD_DETACH
//
THRDNFYPROC g_pfnThreadProc = NULL;

extern "C" void SetLibraryThreadProc(THRDNFYPROC pfnThreadNotify)
{
    if ((g_pfnThreadProc = pfnThreadNotify) == NULL)
        DisableThreadLibraryCalls((HMODULE)g_hInstance);
}

// ref count for LockServer
//
LONG  g_cLocks;

// private routines for this file.
//
int       IndexOfOleObject(REFCLSID);
HRESULT   RegisterAllObjects(void);
HRESULT   UnregisterAllObjects(void);

//=--------------------------------------------------------------------------=
// DllMain
//=--------------------------------------------------------------------------=
// yon standard LibMain.
//
// Parameters and Output:
//    - see SDK Docs on DllMain
//
// Notes:
//
BOOL WINAPI DllMain
(
    HANDLE hInstance,
    DWORD  dwReason,
    void  *pvReserved
)
{
    int i;

    switch (dwReason) {
      // set up some global variables, and get some OS/Version information
      // set up.
      //
      case DLL_PROCESS_ATTACH:
        {
        DWORD dwVer = GetVersion();
        DWORD dwWinVer;

        //  swap the two lowest bytes of dwVer so that the major and minor version
        //  numbers are in a usable order.
        //  for dwWinVer: high byte = major version, low byte = minor version
        //     OS               Sys_WinVersion  (as of 5/2/95)
        //     =-------------=  =-------------=
        //     Win95            0x035F   (3.95)
        //     WinNT ProgMan    0x0333   (3.51)
        //     WinNT Win95 UI   0x0400   (4.00)
        //
        dwWinVer = (UINT)(((dwVer & 0xFF) << 8) | ((dwVer >> 8) & 0xFF));
        g_fSysWinNT = FALSE;
        g_fSysWin95 = FALSE;
        g_fSysWin95Shell = FALSE;

        if (dwVer < 0x80000000) {
            g_fSysWinNT = TRUE;
            g_fSysWin95Shell = (dwWinVer >= 0x0334);
        } else  {
            g_fSysWin95 = TRUE;
            g_fSysWin95Shell = TRUE;
        }

        // initialize a critical seciton for our apartment threading support
        //
        InitializeCriticalSection(&g_CriticalSection);

        // create an initial heap for everybody to use.
        // currently, we're going to let the system make things thread-safe,
        // which will make them a little slower, but hopefully not enough
        // to notice
        //
        g_hHeap = GetProcessHeap();
        if (!g_hHeap) {
            FAIL("Couldn't get Process Heap.  Not good!");
            return FALSE;
        }

        g_hInstance = (HINSTANCE)hInstance;

        // give the user a chance to initialize whatever
        //
        InitializeLibrary();

        // if they didn't ask for thread notifications then optimize by turning
        // them off for our DLL.
        //
        if (!g_pfnThreadProc)
            DisableThreadLibraryCalls((HMODULE)hInstance);
        }
        break;

      case DLL_THREAD_ATTACH:
      case DLL_THREAD_DETACH:
        if (g_pfnThreadProc)
            g_pfnThreadProc(hInstance, dwReason, pvReserved);
        break;

      // do  a little cleaning up!
      //
      case DLL_PROCESS_DETACH:

        // clean up our critical seciton
        //
        DeleteCriticalSection(&g_CriticalSection);

        // unregister all the registered window classes.
        //
        i = 0;

        while (!ISEMPTYOBJECT(i)) {
            if (g_ObjectInfo[i].usType == OI_CONTROL) {
                if (CTLWNDCLASSREGISTERED(i))
                    UnregisterClass(WNDCLASSNAMEOFCONTROL(i), g_hInstance);
            }
            i++;
        }

        // clean up our parking window.
        //
        if (g_hwndParking) {
            DestroyWindow(g_hwndParking);
            UnregisterClass("CtlFrameWork_Parking", g_hInstance);
            --g_cLocks;
        }

        // clean up after reflection, if appropriate.
        //
        if (g_fRegisteredReflect)
            UnregisterClass(g_szReflectClassName, g_hInstance);

        // give the user a chance to do some cleaning up
        //
        UninitializeLibrary();
        break;
    }

    return TRUE;
}

//=--------------------------------------------------------------------------=
// DllRegisterServer
//=--------------------------------------------------------------------------=
// registers the Automation server
//
// Output:
//    HRESULT
//
// Notes:
//
STDAPI DllRegisterServer
(
    void
)
{
    HRESULT hr;

    hr = RegisterAllObjects();
    RETURN_ON_FAILURE(hr);

    // call user registration function.
    //
    return (RegisterData())? S_OK : E_FAIL;
}

//=--------------------------------------------------------------------------=
// DllUnregisterServer
//=--------------------------------------------------------------------------=
// unregister's the Automation server
//
// Output:
//    HRESULT
//
// Notes:
//
STDAPI DllUnregisterServer
(
    void
)
{
    HRESULT hr;

    hr = UnregisterAllObjects();
    RETURN_ON_FAILURE(hr);

    // call user unregistration function
    //
    return (UnregisterData()) ? S_OK : E_FAIL;
}

//=--------------------------------------------------------------------------=
// DllCanUnloadNow
//=--------------------------------------------------------------------------=
// we are being asked whether or not it's okay to unload the DLL.  just check
// the lock counts on remaining objects ...
//
// Output:
//    HRESULT        - S_OK, can unload now, S_FALSE, can't.
//
// Notes:
//
STDAPI DllCanUnloadNow
(
    void
)
{
    // if there are any objects lying around, then we can't unload.  The
    // controlling CUnknownObject class that people should be inheriting from
    // takes care of this
    //
    return (g_cLocks) ? S_FALSE : S_OK;
}

//=--------------------------------------------------------------------------=
// DllGetClassObject
//=--------------------------------------------------------------------------=
// creates a ClassFactory object, and returns it.
//
// Parameters:
//    REFCLSID        - CLSID for the class object
//    REFIID          - interface we want class object to be.
//    void **         - pointer to where we should ptr to new object.
//
// Output:
//    HRESULT         - S_OK, CLASS_E_CLASSNOTAVAILABLE, E_OUTOFMEMORY,
//                      E_INVALIDARG, E_UNEXPECTED
//
// Notes:
//
STDAPI DllGetClassObject
(
    REFCLSID rclsid,
    REFIID   riid,
    void   **ppvObjOut
)
{
    HRESULT hr;
    void   *pv;
    int     iIndex;

    // arg checking
    //
    if (!ppvObjOut)
        return E_INVALIDARG;

    // first of all, make sure they're asking for something we work with.
    //
    iIndex = IndexOfOleObject(rclsid);
    if (iIndex == -1)
        return CLASS_E_CLASSNOTAVAILABLE;

    // create the blank object.
    //
    pv = (void *)new CClassFactory(iIndex);
    if (!pv)
        return E_OUTOFMEMORY;

    // QI for whatever the user has asked for.
    //
    hr = ((IUnknown *)pv)->QueryInterface(riid, ppvObjOut);
    ((IUnknown *)pv)->Release();

    return hr;
}
//=--------------------------------------------------------------------------=
// IndexOfOleObject
//=--------------------------------------------------------------------------=
// returns the index in our global table of objects of the given CLSID.  if
// it's not a supported object, then we return -1
//
// Parameters:
//    REFCLSID     - [in] duh.
//
// Output:
//    int          - >= 0 is index into global table, -1 means not supported
//
// Notes:
//
int IndexOfOleObject
(
    REFCLSID rclsid
)
{
    int x = 0;

    // an object is creatable if it's CLSID is in the table of all allowable object
    // types.
    //
    while (!ISEMPTYOBJECT(x)) {
        if (OBJECTISCREATABLE(x)) {
            if (rclsid == CLSIDOFOBJECT(x))
                return x;
        }
        x++;
    }

    return -1;
}

//=--------------------------------------------------------------------------=
// RegisterAllObjects
//=--------------------------------------------------------------------------=
// registers all the objects for the given automation server.
//
// Parameters:
//    none
//
// Output:
//    HERSULT        - S_OK, E_FAIL
//
// Notes:
//
HRESULT RegisterAllObjects
(
    void
)
{
    ITypeLib *pTypeLib;
    HRESULT hr;
    DWORD   dwPathLen;
    char    szTmp[MAX_PATH];
    int     x = 0;

    // loop through all of our creatable objects [those that have a clsid in
    // our global table] and register them.
    //
    while (!ISEMPTYOBJECT(x)) {
        if (!OBJECTISCREATABLE(x)) {
            x++;
            continue;
        }

        // depending on the object type, register different pieces of information
        //
        switch (g_ObjectInfo[x].usType) {

          // for both simple co-creatable objects and proeprty pages, do the same
          // thing
          //
          case OI_UNKNOWN:
          case OI_PROPERTYPAGE:
            RegisterUnknownObject(NAMEOFOBJECT(x), CLSIDOFOBJECT(x));
            break;

          case OI_AUTOMATION:
            RegisterAutomationObject(g_szLibName, NAMEOFOBJECT(x), VERSIONOFOBJECT(x),
                                     *g_pLibid, CLSIDOFOBJECT(x));
            break;

          case OI_CONTROL:
            RegisterControlObject(g_szLibName, NAMEOFOBJECT(x), VERSIONOFOBJECT(x),
                                  *g_pLibid, CLSIDOFOBJECT(x), OLEMISCFLAGSOFCONTROL(x),
                                  BITMAPIDOFCONTROL(x));
            break;

        }
        x++;
    }

    // Load and register our type library.
    //
    if (g_fServerHasTypeLibrary) {
        dwPathLen = GetModuleFileName(g_hInstance, szTmp, MAX_PATH);
        MAKE_WIDEPTR_FROMANSI(pwsz, szTmp);
        hr = LoadTypeLib(pwsz, &pTypeLib);
        RETURN_ON_FAILURE(hr);
        hr = RegisterTypeLib(pTypeLib, pwsz, NULL);
        pTypeLib->Release();
        RETURN_ON_FAILURE(hr);
    }

    return S_OK;
}

//=--------------------------------------------------------------------------=
// UnregisterAllObjects
//=--------------------------------------------------------------------------=
// un-registers all the objects for the given automation server.
//
// Parameters:
//    none
//
// Output:
//    HRESULT        - S_OK
//
// Notes:
//
HRESULT UnregisterAllObjects
(
    void
)
{
    int x = 0;

    // loop through all of our creatable objects [those that have a clsid in
    // our global table] and register them.
    //
    while (!ISEMPTYOBJECT(x)) {
        if (!OBJECTISCREATABLE(x)) {
            x++;
            continue;
        }

        switch (g_ObjectInfo[x].usType) {

          case OI_UNKNOWN:
          case OI_PROPERTYPAGE:
            UnregisterUnknownObject(CLSIDOFOBJECT(x));
            break;

          case OI_CONTROL:
            UnregisterControlObject(g_szLibName, NAMEOFOBJECT(x), VERSIONOFOBJECT(x),
                                    CLSIDOFOBJECT(x));

          case OI_AUTOMATION:
            UnregisterAutomationObject(g_szLibName, NAMEOFOBJECT(x), VERSIONOFOBJECT(x),
                                       CLSIDOFOBJECT(x));
            break;

        }
        x++;
    }

    // if we've got one, unregister our type library [this isn't an API function
    // -- we've implemented this ourselves]
    //
    if (g_pLibid)
        UnregisterTypeLibrary(*g_pLibid);

    return S_OK;
}


PROPPAGE.CPP

//=--------------------------------------------------------------------------=
// PropPage.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// implementation of CPropertyPage object.
//
#include "IPServer.H"
#include "PropPage.H"
#include "Util.H"
#include "Globals.H"

// for ASSERT and FAIL
//
SZTHISFILE

// this variable is used to pass the pointer to the object to the hwnd.
//
static CPropertyPage *s_pLastPageCreated;

//=--------------------------------------------------------------------------=
// CPropertyPage::CPropertyPage
//=--------------------------------------------------------------------------=
// constructor.
//
// Parameters:
//    IUnknown *          - [in] controlling unknown
//    int                 - [in] object type.
//
// Notes:
//
#pragma warning(disable:4355)  // using 'this' in constructor
CPropertyPage::CPropertyPage
(
    IUnknown         *pUnkOuter,
    int               iObjectType
)
: CUnknownObject(pUnkOuter, this), m_ObjectType(iObjectType)
{
    // initialize various dudes.
    //
    m_pPropertyPageSite = NULL;
    m_hwnd = NULL;
    m_fDirty = FALSE;
    m_fActivated = FALSE;
    m_cObjects = 0;
}
#pragma warning(default:4355)  // using 'this' in constructor

//=--------------------------------------------------------------------------=
// CPropertyPage::~CPropertyPage
//=--------------------------------------------------------------------------=
// destructor.
//
// Notes:
//
CPropertyPage::~CPropertyPage()
{
    // clean up our window.
    //
    if (m_hwnd) {
        SetWindowLong(m_hwnd, GWL_USERDATA, 0xffffffff);
        DestroyWindow(m_hwnd);
    }

    // release all the objects we're holding on to.
    //
    m_ReleaseAllObjects();

    // release the site
    //
    QUICK_RELEASE(m_pPropertyPageSite);
}

//=--------------------------------------------------------------------------=
// CPropertyPage::InternalQueryInterface
//=--------------------------------------------------------------------------=
// we support IPP and IPP2.
//
// Parameters:
//    REFIID        - [in]  interface they want
//    void **       - [out] where they want to put the resulting object ptr.
//
// Output:
//    HRESULT       - S_OK, E_NOINTERFACE
//
// Notes:
//
HRESULT CPropertyPage::InternalQueryInterface
(
    REFIID  riid,
    void  **ppvObjOut
)
{
    IUnknown *pUnk;

    *ppvObjOut = NULL;

    if (DO_GUIDS_MATCH(IID_IPropertyPage, riid)) {
        pUnk = (IUnknown *)this;
    } else if (DO_GUIDS_MATCH(IID_IPropertyPage2, riid)) {
        pUnk = (IUnknown *)this;
    } else {
        return CUnknownObject::InternalQueryInterface(riid, ppvObjOut);
    }

    pUnk->AddRef();
    *ppvObjOut = (void *)pUnk;
    return S_OK;
}

//=--------------------------------------------------------------------------=
// CPropertyPage::SetPageSite    [IPropertyPage]
//=--------------------------------------------------------------------------=
// the initialization function for a property page through which the page
// receives an IPropertyPageSite pointer.
//
// Parameters:
//    IPropertyPageSite *        - [in] new site.
//
// Output:
//    HRESULT
//
// Notes;
//
STDMETHODIMP CPropertyPage::SetPageSite
(
    IPropertyPageSite *pPropertyPageSite
)
{
    RELEASE_OBJECT(m_pPropertyPageSite);
    m_pPropertyPageSite = pPropertyPageSite;
    ADDREF_OBJECT(pPropertyPageSite);

    return S_OK;
}

//=--------------------------------------------------------------------------=
// CPropertyPage::Activate    [IPropertyPage]
//=--------------------------------------------------------------------------=
// instructs the page to create it's display window as a child of hwndparent
// and to position it according to prc.
//
// Parameters:
//    HWND                - [in]  parent window
//    LPCRECT             - [in]  where to position ourselves
//    BOOL                - [in]  whether we're modal or not.
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP CPropertyPage::Activate
(
    HWND    hwndParent,
    LPCRECT prcBounds,
    BOOL    fModal
)
{
    HRESULT hr;

    // first make sure the dialog window is loaded and created.
    //
    hr = m_EnsureLoaded();
    RETURN_ON_FAILURE(hr);

    // set our parent window if we haven't done so yet.
    //
    if (!m_fActivated) {
        SetParent(m_hwnd, hwndParent);
        m_fActivated = TRUE;
    }

    // now move ourselves to where we're told to be and show ourselves
    //
    Move(prcBounds);
    ShowWindow(m_hwnd, SW_SHOW);

    return S_OK;
}

//=--------------------------------------------------------------------------=
// CPropertyPage::Deactivate    [IPropertyPage]
//=--------------------------------------------------------------------------=
// instructs the page to destroy the window created in activate
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP CPropertyPage::Deactivate
(
    void
)
{
    // blow away yon window.
    //
    if (m_hwnd)
        DestroyWindow(m_hwnd);
    m_hwnd = NULL;
    m_fActivated = FALSE;

    return S_OK;
}

//=--------------------------------------------------------------------------=
// CPropertyPage::GetPageInfo    [IPropertyPage]
//=--------------------------------------------------------------------------=
// asks the page to fill a PROPPAGEINFO structure
//
// Parameters:
//    PROPPAGEINFO *    - [out] where to put info.
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP CPropertyPage::GetPageInfo
(
    PROPPAGEINFO *pPropPageInfo
)
{
    RECT rect;

    CHECK_POINTER(pPropPageInfo);

    m_EnsureLoaded();

    // clear it out first.
    //
    memset(pPropPageInfo, 0, sizeof(PROPPAGEINFO));

    pPropPageInfo->pszTitle = OLESTRFROMRESID(TITLEIDOFPROPPAGE(m_ObjectType));
    pPropPageInfo->pszDocString = OLESTRFROMRESID(DOCSTRINGIDOFPROPPAGE(m_ObjectType));
    pPropPageInfo->pszHelpFile = OLESTRFROMANSI(HELPFILEOFPROPPAGE(m_ObjectType));
    pPropPageInfo->dwHelpContext = HELPCONTEXTOFPROPPAGE(m_ObjectType);

    if (!(pPropPageInfo->pszTitle && pPropPageInfo->pszDocString && pPropPageInfo->pszHelpFile))
        goto CleanUp;

    // if we've got a window yet, go and set up the size information they want.
    //
    if (m_hwnd) {
        GetWindowRect(m_hwnd, &rect);

        pPropPageInfo->size.cx = rect.right - rect.left;
        pPropPageInfo->size.cy = rect.bottom - rect.top;
    }

    return S_OK;

  CleanUp:
    if (pPropPageInfo->pszDocString) CoTaskMemFree(pPropPageInfo->pszDocString);
    if (pPropPageInfo->pszHelpFile) CoTaskMemFree(pPropPageInfo->pszHelpFile);
    if (pPropPageInfo->pszTitle) CoTaskMemFree(pPropPageInfo->pszTitle);

    return E_OUTOFMEMORY;
}

//=--------------------------------------------------------------------------=
// CPropertyPage::SetObjects    [IPropertyPage]
//=--------------------------------------------------------------------------=
// provides the page with the objects being affected by the changes.
//
// Parameters:
//    ULONG            - [in] count of objects.
//    IUnknown **      - [in] objects.
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP CPropertyPage::SetObjects
(
    ULONG      cObjects,
    IUnknown **ppUnkObjects
)
{
    HRESULT hr;
    ULONG   x;

    // free up all the old objects first.
    //
    m_ReleaseAllObjects();

    if (!cObjects)
        return S_OK;

    // now go and set up the new ones.
    //
    m_ppUnkObjects = (IUnknown **)HeapAlloc(g_hHeap, 0, cObjects * sizeof(IUnknown *));
    RETURN_ON_NULLALLOC(m_ppUnkObjects);

    // loop through and copy over all the objects.
    //
    for (x = 0; x < cObjects; x++) {
        m_ppUnkObjects[x] = ppUnkObjects[x];
        ADDREF_OBJECT(m_ppUnkObjects[x]);
    }

    // go and tell the object that there are new objects
    //
    hr = S_OK;
    m_cObjects = cObjects;
    // if we've got a window, go and notify it that we've got new objects.
    //
    if (m_hwnd)
        SendMessage(m_hwnd, PPM_NEWOBJECTS, 0, (LPARAM)&hr);
    if (SUCCEEDED(hr)) m_fDirty = FALSE;

    return hr;
}

//=--------------------------------------------------------------------------=
// CPropertyPage::Show    [IPropertyPage]
//=--------------------------------------------------------------------------=
// asks the page to show or hide its window
//
// Parameters:
//    UINT             - [in] whether to show or hide
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP CPropertyPage::Show
(
    UINT nCmdShow
)
{
    if (m_hwnd)
        ShowWindow(m_hwnd, nCmdShow);
    else
        return E_UNEXPECTED;

    return S_OK;
}

//=--------------------------------------------------------------------------=
// CPropertyPage::Move    [IPropertyPage]
//=--------------------------------------------------------------------------=
// asks the page to relocate and resize itself to a position other than what
// was specified through Activate
//
// Parameters:
//    LPCRECT        - [in] new position and size
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP CPropertyPage::Move
(
    LPCRECT prcBounds
)
{
    // do what they sez
    //
    if (m_hwnd)
        SetWindowPos(m_hwnd, NULL, prcBounds->left, prcBounds->top,
                     prcBounds->right - prcBounds->left,
                     prcBounds->bottom - prcBounds->top,
                     SWP_NOZORDER);
    else
        return E_UNEXPECTED;

    return S_OK;
}

//=--------------------------------------------------------------------------=
// CPropertyPage::IsPageDirty    [IPropertyPage]
//=--------------------------------------------------------------------------=
// asks the page whether it has changed its state
//
// Output
//    S_OK            - yep
//    S_FALSE         - nope
//
// Notes:
//
STDMETHODIMP CPropertyPage::IsPageDirty
(
    void
)
{
    return m_fDirty ? S_OK : S_FALSE;
}

//=--------------------------------------------------------------------------=
// CPropertyPage::Apply    [IPropertyPage]
//=--------------------------------------------------------------------------=
// instructs the page to send its changes to all the objects passed through
// SetObjects()
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP CPropertyPage::Apply
(
    void
)
{
    HRESULT hr = S_OK;

    if (m_hwnd) {
        SendMessage(m_hwnd, PPM_APPLY, 0, (LPARAM)&hr);
        RETURN_ON_FAILURE(hr);

        if (m_fDirty) {
            m_fDirty = FALSE;
            if (m_pPropertyPageSite)
                m_pPropertyPageSite->OnStatusChange(PROPPAGESTATUS_DIRTY);
        }
    } else
        return E_UNEXPECTED;

    return S_OK;
}

//=--------------------------------------------------------------------------=
// CPropertyPage::Help    [IPropertyPage]
//=--------------------------------------------------------------------------=
// instructs the page that the help button was clicked.
//
// Parameters:
//    LPCOLESTR        - [in] help directory
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP CPropertyPage::Help
(
    LPCOLESTR pszHelpDir
)
{
    BOOL f;

    ASSERT(m_hwnd, "How can somebody have clicked Help, but we don't have an hwnd?");

    // oblige them and show the help.
    //
    MAKE_ANSIPTR_FROMWIDE(psz, pszHelpDir);
    f = WinHelp(m_hwnd, psz, HELP_CONTEXT, HELPCONTEXTOFPROPPAGE(m_ObjectType));

    return f ? S_OK : E_FAIL;
}

//=--------------------------------------------------------------------------=
// CPropertyPage::TranslateAccelerator    [IPropertyPage]
//=--------------------------------------------------------------------------=
// informs the page of keyboard events, allowing it to implement it's own
// keyboard interface.
//
// Parameters:
//    LPMSG            - [in] message that triggered this
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP CPropertyPage::TranslateAccelerator
(
    LPMSG pmsg
)
{
    ASSERT(m_hwnd, "How can we get a TranslateAccelerator call if we're not visible?");

    // just pass this message on to the dialog proc and see if they want it.
    //
    return IsDialogMessage(m_hwnd, pmsg) ? S_OK : S_FALSE;
}

//=--------------------------------------------------------------------------=
// CPropertyPage::EditProperty    [IPropertyPage2]
//=--------------------------------------------------------------------------=
// instructs the page to set the focus to the property matching the dispid.
//
// Parameters:
//    DISPID            - [in] dispid of property to set focus to.
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP CPropertyPage::EditProperty
(
    DISPID dispid
)
{
    HRESULT hr = E_NOTIMPL;

    // send the message on to the control, and see what they want to do with it.
    //
    SendMessage(m_hwnd, PPM_EDITPROPERTY, (WPARAM)dispid, (LPARAM)&hr);

    return hr;
}

//=--------------------------------------------------------------------------=
// CPropertyPage::m_EnsureLoaded
//=--------------------------------------------------------------------------=
// makes sure the dialog is actually loaded
//
// Output:
//    HRESULT
//
// Notes:
//
HRESULT CPropertyPage::m_EnsureLoaded
(
    void
)
{
    HRESULT hr = S_OK;

    // duh
    //
    if (m_hwnd)
        return S_OK;

    // set up the global variable so that when we're in the dialog proc, we can
    // stuff this in the hwnd
    //
    // crit sect this whole creation process for apartment threading support.
    //
    EnterCriticalSection(&g_CriticalSection);
    s_pLastPageCreated = this;

    // create the dialog window
    //
    CreateDialog(GetResourceHandle(), TEMPLATENAMEOFPROPPAGE(m_ObjectType), GetParkingWindow(),
                          (DLGPROC)CPropertyPage::PropPageDlgProc);
    ASSERT(m_hwnd, "Couldn't load Dialog Resource!!!");
    if (!m_hwnd) {
        LeaveCriticalSection(&g_CriticalSection);
        return HRESULT_FROM_WIN32(GetLastError());
    }

    // clean up variables and leave the critical section
    //
    s_pLastPageCreated = NULL;
    LeaveCriticalSection(&g_CriticalSection);

    // go and notify the window that it should pick up any objects that are
    // available
    //
    SendMessage(m_hwnd, PPM_NEWOBJECTS, 0, (LPARAM)&hr);

    return hr;
}

//=--------------------------------------------------------------------------=
// CPropertyPage::m_ReleaseAllObjects
//=--------------------------------------------------------------------------=
// releases all the objects that we're working with
//
// Notes:
//
void CPropertyPage::m_ReleaseAllObjects
(
    void
)
{
    HRESULT hr;
    UINT x;

    if (!m_cObjects)
        return;

    // some people will want to stash pointers in the PPM_INITOBJECTS case, so
    // we want to tell them to release them now.
    //
    SendMessage(m_hwnd, PPM_FREEOBJECTS, 0, (LPARAM)&hr);

    // loop through and blow them all away.
    //
    for (x = 0; x < m_cObjects; x++)
        QUICK_RELEASE(m_ppUnkObjects[x]);

    HeapFree(g_hHeap, 0, m_ppUnkObjects);
    m_ppUnkObjects = NULL;
}

//=--------------------------------------------------------------------------=
// CPropertyPage::PropPageDlgProc
//=--------------------------------------------------------------------------=
// static global helper dialog proc that gets called before we pass the message
// on to anybody ..
//
// Parameters:
//    - see win32sdk docs on DialogProc
//
// Notes:
//
BOOL CALLBACK CPropertyPage::PropPageDlgProc
(
    HWND    hwnd,
    UINT    msg,
    WPARAM  wParam,
    LPARAM  lParam
)
{
    CPropertyPage *pPropertyPage;

    // get the window long, and see if it's been set to the object this hwnd
    // is operating against.  if not, go and set it now.
    //
    pPropertyPage = (CPropertyPage *)GetWindowLong(hwnd, GWL_USERDATA);
    if ((ULONG)pPropertyPage == 0xffffffff)
        return FALSE;
    if (!pPropertyPage) {
        SetWindowLong(hwnd, GWL_USERDATA, (LONG)s_pLastPageCreated);
        pPropertyPage = s_pLastPageCreated;
        pPropertyPage->m_hwnd = hwnd;
    }

    ASSERT(pPropertyPage, "Uh oh.  Got a window, but no CpropertyPage for it!");

    // just call the user dialog proc and see if they want to do anything.
    //
    return pPropertyPage->DialogProc(hwnd, msg, wParam, lParam);
}

//=--------------------------------------------------------------------------=
// CPropertyPage::FirstControl
//=--------------------------------------------------------------------------=
// returns the first controlish object that we are showing ourselves for.
// returns a cookie that must be passed in for Next ...
//
// Parameters:
//    DWORD *    - [out] cookie to be used for Next
//
// Output:
//    IUnknown *
//
// Notes:
//
IUnknown *CPropertyPage::FirstControl
(
    DWORD *pdwCookie
)
{
    // just use the implementation of NEXT.
    //
    *pdwCookie = 0;
    return NextControl(pdwCookie);
}

//=--------------------------------------------------------------------------=
// CPropertyPage::NextControl
//=--------------------------------------------------------------------------=
// returns the next control in the chain of people to work with given a cookie
//
// Parameters:
//    DWORD *            - [in/out] cookie to get next from, and new cookie.
//
// Output:
//    IUnknown *
//
// Notes:
//
IUnknown *CPropertyPage::NextControl
(
    DWORD *pdwCookie
)
{
    UINT      i;

    // go looking through all the objects that we've got, and find the
    // first non-null one.
    //
    for (i = *pdwCookie; i < m_cObjects; i++) {
        if (!m_ppUnkObjects[i]) continue;

        *pdwCookie = i + 1;                // + 1 so we start at next item next time
        return m_ppUnkObjects[i];
    }

    // couldn't find it .
    //
    *pdwCookie = 0xffffffff;
    return NULL;
}

//=--------------------------------------------------------------------------=
// CPropertyPage::MakeDirty    [helper, callable]
//=--------------------------------------------------------------------------=
// marks a page as dirty.
//
// Notes:
//
void CPropertyPage::MakeDirty
(
    void
)
{
    m_fDirty = TRUE;
    if (m_pPropertyPageSite)
        m_pPropertyPageSite->OnStatusChange(PROPPAGESTATUS_DIRTY|PROPPAGESTATUS_VALIDATE);
}

// from Globals.C
//
extern HINSTANCE g_hInstResources;

//=--------------------------------------------------------------------------=
// CPropertyPage::GetResourceHandle    [helper, callable]
//=--------------------------------------------------------------------------=
// returns current resource handle, based on pagesites ambient LCID.
//
// Output:
//    HINSTANCE
//
// Notes:
//
HINSTANCE CPropertyPage::GetResourceHandle
(
    void
)
{
    if (!g_fSatelliteLocalization)
        return g_hInstance;

    // if we've already got it, then there's not all that much to do.
    // don't need to crit sect this one right here since even if they do fall
    // into the ::GetResourceHandle call, it'll properly deal with things.
    //
    if (g_hInstResources)
        return g_hInstResources;

    // we'll get the ambient localeid from the host, and pass that on to the
    // automation object.
    //
    // enter a critical section for g_lcidLocale and g_fHavelocale
    //
    EnterCriticalSection(&g_CriticalSection);
    if (!g_fHaveLocale) {
        if (m_pPropertyPageSite) {
            m_pPropertyPageSite->GetLocaleID(&g_lcidLocale);
            g_fHaveLocale = TRUE;
        }
    }
    LeaveCriticalSection(&g_CriticalSection);

    return ::GetResourceHandle();
}


AUTOOBJ.CPP

//=--------------------------------------------------------------------------=
// AutoObj.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// all of our objects will inherit from this class to share as much of the same
// code as possible.  this super-class contains the unknown, dispatch and
// error info implementations for them.
//
#include "IPServer.H"
#include "LocalSrv.H"

#include "AutoObj.H"
#include "Globals.H"
#include "Util.H"

// for ASSERT and FAIL
//
SZTHISFILE

//=--------------------------------------------------------------------------=
// CAutomationObject::CAutomationObject
//=--------------------------------------------------------------------------=
// create the object and initialize the refcount
//
// Parameters:
//    IUnknown *      - [in] controlling Unknown
//    int             - [in] the object type that we are
//    void *          - [in] the VTable of of the object we really are.
//
// Notes:
//
CAutomationObject::CAutomationObject
(
    IUnknown *pUnkOuter,
    int   ObjType,
    void *pVTable,
BOOL fExpandoEnabled
)
: CUnknownObject(pUnkOuter, pVTable), m_ObjectType (ObjType)
{
    m_fLoadedTypeInfo = FALSE;
m_fExpandoEnabled = fExpandoEnabled;
m_pexpando = NULL;
}

//=--------------------------------------------------------------------------=
// CAutomationObject::~CAutomationObject
//=--------------------------------------------------------------------------=
// "I have a rendezvous with Death, At some disputed barricade"
// - Alan Seeger (1888-1916)
//
// Notes:
//
CAutomationObject::~CAutomationObject ()
{
    // if we loaded up a type info, release our count on the globally stashed
    // type infos, and release if it becomes zero.
    //
    if (m_fLoadedTypeInfo) {

        // we have to crit sect this since it's possible to have more than
        // one thread partying with this object.
        //
        EnterCriticalSection(&g_CriticalSection);
        ASSERT(CTYPEINFOOFOBJECT(m_ObjectType), "Bogus ref counting on the Type Infos");
        CTYPEINFOOFOBJECT(m_ObjectType)--;

        // if we're the last one, free that sucker!
        //
        if (!CTYPEINFOOFOBJECT(m_ObjectType)) {
            PTYPEINFOOFOBJECT(m_ObjectType)->Release();
            PTYPEINFOOFOBJECT(m_ObjectType) = NULL;
        }
        LeaveCriticalSection(&g_CriticalSection);
    }

if (m_pexpando)
{
delete m_pexpando;
}
    return;
}

//=--------------------------------------------------------------------------=
// CAutomationObject::InternalQueryInterface
//=--------------------------------------------------------------------------=
// the controlling unknown will call this for us in the case where they're
// looking for a specific interface.
//
// Parameters:
//    REFIID        - [in]  interface they want
//    void **       - [out] where they want to put the resulting object ptr.
//
// Output:
//    HRESULT       - S_OK, E_NOINTERFACE
//
// Notes:
//
HRESULT CAutomationObject::InternalQueryInterface
(
    REFIID riid,
    void **ppvObjOut
)
{
    ASSERT(ppvObjOut, "controlling Unknown should be checking this!");

    // start looking for the guids we support, namely IDispatch, and
    // IDispatchEx

    if (DO_GUIDS_MATCH(riid, IID_IDispatch)) {
// If expando functionality is enabled, attempt to allocate an
// expando object and return that for the IDispatch interface.
// If the allocation fails, we will fall back on using the regular
// IDispatch from m_pvInterface;
if (m_fExpandoEnabled)
{
if (!m_pexpando)
m_pexpando = new CExpandoObject(m_pUnkOuter, (IDispatch*) m_pvInterface);

if (m_pexpando)
{
*ppvObjOut = (void*)(IDispatch*) m_pexpando;
((IUnknown *)(*ppvObjOut))->AddRef();
return S_OK;
}
}

        *ppvObjOut = (void*) (IDispatch*) m_pvInterface;
        ((IUnknown *)(*ppvObjOut))->AddRef();
        return S_OK;
    }
    else if (DO_GUIDS_MATCH(riid, IID_IDispatchEx) && m_fExpandoEnabled) {
// Allocate the expando object if it hasn't been allocated already
if (!m_pexpando)
m_pexpando = new CExpandoObject(m_pUnkOuter, (IDispatch*) m_pvInterface);

// If the allocation succeeded, return the IDispatchEx interface from
// the expando.  Otherwise fall through to CUnknownObject::InternalQueryInterface,
// (which will most likely fail)
if (m_pexpando)
{
 *ppvObjOut = (void *)(IDispatchEx *) m_pexpando;
((IUnknown *)(*ppvObjOut))->AddRef();
return S_OK;
}
    }

    // just get our parent class to process it from here on out.
    //
    return CUnknownObject::InternalQueryInterface(riid, ppvObjOut);
}

//=--------------------------------------------------------------------------=
// CAutomationObject::GetTypeInfoCount
//=--------------------------------------------------------------------------=
// returns the number of type information interfaces that the object provides
//
// Parameters:
//    UINT *            - [out] the number of interfaces supported.
//
// Output:
//    HRESULT           - S_OK, E_NOTIMPL, E_INVALIDARG
//
// Notes:
//
STDMETHODIMP CAutomationObject::GetTypeInfoCount
(
    UINT *pctinfo
)
{
    // arg checking
    //
    if (!pctinfo)
        return E_INVALIDARG;

    // we support GetTypeInfo, so we need to return the count here.
    //
    *pctinfo = 1;
    return S_OK;
}

//=--------------------------------------------------------------------------=
// CAutomationObject::GetTypeInfo
//=--------------------------------------------------------------------------=
// Retrieves a type information object, which can be used to get the type
// information for an interface.
//
// Parameters:
//    UINT              - [in]  the type information they'll want returned
//    LCID              - [in]  the LCID of the type info we want
//    ITypeInfo **      - [out] the new type info object.
//
// Output:
//    HRESULT           - S_OK, E_INVALIDARG, etc.
//
// Notes:
//
STDMETHODIMP CAutomationObject::GetTypeInfo
(
    UINT        itinfo,
    LCID        lcid,
    ITypeInfo **ppTypeInfoOut
)
{
    DWORD       dwPathLen;
    char        szDllPath[MAX_PATH];
    HRESULT     hr;
    ITypeLib   *pTypeLib;
    ITypeInfo **ppTypeInfo =NULL;

    // arg checking
    //
    if (itinfo != 0)
        return DISP_E_BADINDEX;

    if (!ppTypeInfoOut)
        return E_POINTER;

    *ppTypeInfoOut = NULL;

    // ppTypeInfo will point to our global holder for this particular
    // type info.  if it's null, then we have to load it up. if it's not
    // NULL, then it's already loaded, and we're happy.
    // crit sect this entire nightmare so we're okay with multiple
    // threads trying to use this object.
    //
    EnterCriticalSection(&g_CriticalSection);
    ppTypeInfo = PPTYPEINFOOFOBJECT(m_ObjectType);

    if (*ppTypeInfo == NULL) {

        ITypeInfo *pTypeInfoTmp;
        HREFTYPE   hrefType;

        // we don't have the type info around, so go load the sucker.
        //
        hr = LoadRegTypeLib(*g_pLibid, (USHORT)VERSIONOFOBJECT(m_ObjectType), 0,
                            LANG_NEUTRAL, &pTypeLib);

        // if, for some reason, we failed to load the type library this
        // way, we're going to try and load the type library directly out of
        // our resources.  this has the advantage of going and re-setting all
        // the registry information again for us.
        //
        if (FAILED(hr)) {

            dwPathLen = GetModuleFileName(g_hInstance, szDllPath, MAX_PATH);
            if (!dwPathLen) {
                hr = E_FAIL;
                goto CleanUp;
            }

            MAKE_WIDEPTR_FROMANSI(pwsz, szDllPath);
            hr = LoadTypeLib(pwsz, &pTypeLib);
            CLEANUP_ON_FAILURE(hr);
        }

        // we've got the Type Library now, so get the type info for the interface
        // we're interested in.
        //
        hr = pTypeLib->GetTypeInfoOfGuid((REFIID)INTERFACEOFOBJECT(m_ObjectType), &pTypeInfoTmp);
        pTypeLib->Release();
        CLEANUP_ON_FAILURE(hr);

        // the following couple of lines of code are to dereference the dual
        // interface stuff and take us right to the dispatch portion of the
        // interfaces.
        //
        hr = pTypeInfoTmp->GetRefTypeOfImplType(0xffffffff, &hrefType);
        if (FAILED(hr)) {
            pTypeInfoTmp->Release();
            goto CleanUp;
        }

        hr = pTypeInfoTmp->GetRefTypeInfo(hrefType, ppTypeInfo);
        pTypeInfoTmp->Release();
        CLEANUP_ON_FAILURE(hr);

        // add an extra reference to this object.  if it ever becomes zero, then
        // we need to release it ourselves.  crit sect this since more than
        // one thread can party on this object.
        //
        CTYPEINFOOFOBJECT(m_ObjectType)++;
        m_fLoadedTypeInfo = TRUE;
    }

    // we still have to go and addref the Type info object, however, so that
    // the people using it can release it.
    //
    (*ppTypeInfo)->AddRef();
    *ppTypeInfoOut = *ppTypeInfo;
    hr = S_OK;

  CleanUp:
    LeaveCriticalSection(&g_CriticalSection);
    return hr;
}

//=--------------------------------------------------------------------------=
// CAutomationObject::GetIDsOfNames
//=--------------------------------------------------------------------------=
// Maps a single member and an optional set of argument names to a
// corresponding set of integer DISPIDs
//
// Parameters:
//    REFIID            - [in]  must be IID_NULL
//    OLECHAR **        - [in]  array of names to map.
//    UINT              - [in]  count of names in the array.
//    LCID              - [in]  LCID on which to operate
//    DISPID *          - [in]  place to put the corresponding DISPIDs.
//
// Output:
//    HRESULT           - S_OK, E_OUTOFMEMORY, DISP_E_UNKNOWNNAME,
//                        DISP_E_UNKNOWNLCID
//
// Notes:
//    - we're just going to use DispGetIDsOfNames to save us a lot of hassle,
//      and to let this superclass handle it.
//
STDMETHODIMP CAutomationObject::GetIDsOfNames
(
    REFIID    riid,
    OLECHAR **rgszNames,
    UINT      cNames,
    LCID      lcid,
    DISPID   *rgdispid
)
{
    HRESULT     hr;
    ITypeInfo  *pTypeInfo;

    if (!DO_GUIDS_MATCH(riid, IID_NULL))
        return E_INVALIDARG;

    // get the type info for this dude!
    //
    hr = GetTypeInfo(0, lcid, &pTypeInfo);
    RETURN_ON_FAILURE(hr);

    // use the standard provided routines to do all the work for us.
    //
    hr = pTypeInfo->GetIDsOfNames(rgszNames, cNames, rgdispid);
    pTypeInfo->Release();

    return hr;
}

//=--------------------------------------------------------------------------=
// CAutomationObject::Invoke
//=--------------------------------------------------------------------------=
// provides access to the properties and methods on this object.
//
// Parameters:
//    DISPID            - [in]  identifies the member we're working with.
//    REFIID            - [in]  must be IID_NULL.
//    LCID              - [in]  language we're working under
//    USHORT            - [in]  flags, propput, get, method, etc ...
//    DISPPARAMS *      - [in]  array of arguments.
//    VARIANT *         - [out] where to put result, or NULL if they don't care.
//    EXCEPINFO *       - [out] filled in in case of exception
//    UINT *            - [out] where the first argument with an error is.
//
// Output:
//    HRESULT           - tonnes of them.
//
// Notes:
//
STDMETHODIMP CAutomationObject::Invoke
(
    DISPID      dispid,
    REFIID      riid,
    LCID        lcid,
    WORD        wFlags,
    DISPPARAMS *pdispparams,
    VARIANT    *pvarResult,
    EXCEPINFO  *pexcepinfo,
    UINT       *puArgErr
)
{
    HRESULT    hr;
    ITypeInfo *pTypeInfo;

    if (!DO_GUIDS_MATCH(riid, IID_NULL))
        return E_INVALIDARG;

    // get our typeinfo first!
    //
    hr = GetTypeInfo(0, lcid, &pTypeInfo);
    RETURN_ON_FAILURE(hr);

    // Clear exceptions
    //
    SetErrorInfo(0L, NULL);

    // This is exactly what DispInvoke does--so skip the overhead.
    //
    hr = pTypeInfo->Invoke(m_pvInterface, dispid, wFlags,
                           pdispparams, pvarResult,
                           pexcepinfo, puArgErr);
    pTypeInfo->Release();

return hr;

}

//=--------------------------------------------------------------------------=
// CAutomationObject::Exception
//=--------------------------------------------------------------------------=
// fills in the rich error info object so that both our vtable bound interfaces
// and calls through ITypeInfo::Invoke get the right error informaiton.
//
// Parameters:
//    HRESULT          - [in] the SCODE that should be associated with this err
//    WORD             - [in] the RESOURCE ID of the error message.
//    DWORD            - [in] helpcontextid for the error
//
// Output:
//    HRESULT          - the HRESULT that was passed in.
//
// Notes:
//
HRESULT CAutomationObject::Exception
(
    HRESULT hrExcep,
    WORD    idException,
    DWORD   dwHelpContextID
)
{
    ICreateErrorInfo *pCreateErrorInfo;
    IErrorInfo *pErrorInfo;
    WCHAR   wszTmp[256];
    char    szTmp[256];
    HRESULT hr;

    // first get the createerrorinfo object.
    //
    hr = CreateErrorInfo(&pCreateErrorInfo);
    if (FAILED(hr)) return hrExcep;

    MAKE_WIDEPTR_FROMANSI(wszHelpFile, HELPFILEOFOBJECT(m_ObjectType));

    // set up some default information on it.
    //
    pCreateErrorInfo->SetGUID((REFIID)INTERFACEOFOBJECT(m_ObjectType));
    pCreateErrorInfo->SetHelpFile(wszHelpFile);
    pCreateErrorInfo->SetHelpContext(dwHelpContextID);

    // load in the actual error string value.  max of 256.
    //
    LoadString(GetResourceHandle(), idException, szTmp, 256);
    MultiByteToWideChar(CP_ACP, 0, szTmp, -1, wszTmp, 256);
    pCreateErrorInfo->SetDescription(wszTmp);

    // load in the source
    //
    MultiByteToWideChar(CP_ACP, 0, NAMEOFOBJECT(m_ObjectType), -1, wszTmp, 256);
    pCreateErrorInfo->SetSource(wszTmp);

    // now set the Error info up with the system
    //
    hr = pCreateErrorInfo->QueryInterface(IID_IErrorInfo, (void **)&pErrorInfo);
    CLEANUP_ON_FAILURE(hr);

    SetErrorInfo(0, pErrorInfo);
    pErrorInfo->Release();

  CleanUp:
    pCreateErrorInfo->Release();
    return hrExcep;
}

//=--------------------------------------------------------------------------=
// CAutomationObject::InterfaceSupportsErrorInfo
//=--------------------------------------------------------------------------=
// indicates whether or not the given interface supports rich error information
//
// Parameters:
//    REFIID        - [in] the interface we want the answer for.
//
// Output:
//    HRESULT       - S_OK = Yes, S_FALSE = No.
//
// Notes:
//
HRESULT CAutomationObject::InterfaceSupportsErrorInfo
(
    REFIID riid
)
{
    // see if it's the interface for the type of object that we are.
    //
    if (riid == (REFIID)INTERFACEOFOBJECT(m_ObjectType))
        return S_OK;

    return S_FALSE;
}

//=--------------------------------------------------------------------------=
// CAutomationObject::GetResourceHandle    [helper]
//=--------------------------------------------------------------------------=
// virtual routine to get the resource handle.  virtual, so that inheriting
// objects, such as COleControl can use theirs instead, which goes and gets
// the Host's version ...
//
// Output:
//    HINSTANCE
//
// Notes:
//
HINSTANCE CAutomationObject::GetResourceHandle
(
    void
)
{
    return ::GetResourceHandle();
}


STDENUM.CPP

//=--------------------------------------------------------------------------=
// StdEnum.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// implementation of a generic enumerator object.
//
#include "IPServer.H"
#include "StdEnum.H"
#include "Globals.H"

SZTHISFILE

//=--------------------------------------------------------------------------=
// CStandardEnum::CStandardEnum
//=--------------------------------------------------------------------------=
// create the object and initialize the refcount
//
// Parameters:
//    REFCLSID        - [in] type of enumerator that we are
//    int             - [in] number of elements in the enumeration
//    int             - [in] size of each element
//    void *          - [in] pointer to element data
//    void (WINAPI *pfnCopyElement)(void *, const void *, DWORD)
//                    - [in] copying function
//
// Notes:
//
#pragma warning(disable:4355)  // using 'this' in constructor
CStandardEnum::CStandardEnum
(
    REFCLSID rclsid,
    int      cElements,
    int      cbElementSize,
    void    *rgElements,
    void (WINAPI *pfnCopyElement)(void *, const void *, DWORD)
)
: CUnknownObject(NULL, (IEnumGeneric *)this),
  m_iid(rclsid),
  m_cElements(cElements),
  m_cbElementSize(cbElementSize),
  m_iCurrent(0),
  m_rgElements(rgElements),
  m_pfnCopyElement(pfnCopyElement)
{
    m_pEnumClonedFrom = NULL;
}
#pragma warning(default:4355)  // using 'this' in constructor

//=--------------------------------------------------------------------------=
// CStandardEnum::CStandardEnum
//=--------------------------------------------------------------------------=
// "it is not death, but dying, which is terrible."
//    - Henry Fielding (1707-54)
//
// Notes:
//
CStandardEnum::~CStandardEnum ()
{
    // if we're a cloned object, then just release our parent object and
    // we're done. otherwise, free up the allocated memory we were given
    //
    if (m_pEnumClonedFrom)
        m_pEnumClonedFrom->Release();
    else {
        if (m_rgElements) HeapFree(g_hHeap, 0, m_rgElements);
    }
}

//=--------------------------------------------------------------------------=
// CStandardEnum::InternalQueryInterface
//=--------------------------------------------------------------------------=
// we support our internal iid, and that's all
//
// Parameters:
//    REFIID        - [in]  interface they want
//    void **       - [out] where they want to put the resulting object ptr.
//
// Output:
//    HRESULT       - S_OK, E_NOINTERFACE
//
// Notes:
//
HRESULT CStandardEnum::InternalQueryInterface
(
    REFIID riid,
    void **ppvObjOut
)
{
    if (DO_GUIDS_MATCH(riid, m_iid)) {
        ExternalAddRef();
        *ppvObjOut = (IEnumGeneric *)this;
        return S_OK;
    }

    return E_NOINTERFACE;
}

//=--------------------------------------------------------------------------=
// CStandardEnum::Next
//=--------------------------------------------------------------------------=
// returns the next dude in our iteration
//
// Parameters:
//    unsigned long     - [in]  count of elements requested
//    void    *         - [out] array of slots to put values in.
//    unsigned long *   - [out] actual number fetched
//
// Output:
//    HRESULT           - S_OK, E_INVALIDARG, S_FALSE
//
// Notes:
//
STDMETHODIMP CStandardEnum::Next
(
    unsigned long  cElm,
    void          *rgDest,
    unsigned long *pcElmOut
)
{
    unsigned long cElementsFetched = 0;
    void         *pElementDest = rgDest;
    const void   *pElementSrc = (const BYTE *)m_rgElements + (m_cbElementSize * m_iCurrent);

    while (cElementsFetched < cElm) {

        // if we hit EOF, break out
        //
        if (m_iCurrent >= m_cElements)
            break;

        // copy the element out for them
        //
        m_pfnCopyElement(pElementDest, pElementSrc, m_cbElementSize);

        // increase the counters
        //
        pElementDest = (LPBYTE)pElementDest + m_cbElementSize;
        pElementSrc  = (const BYTE *)pElementSrc + m_cbElementSize;
        m_iCurrent++;
        cElementsFetched++;
    }

    if (pcElmOut)
        *pcElmOut = cElementsFetched;

    return (cElementsFetched < cElm)? S_FALSE : S_OK;
}

//=--------------------------------------------------------------------------=
// CStandardEnum::Skip
//=--------------------------------------------------------------------------=
// skips the requested number of rows.
//
// Parameters:
//    unsigned long     - [in] number to skip
//
// Output:
//    HRESULT           - S_OK, S_FALSE
//
// Notes:
//
STDMETHODIMP CStandardEnum::Skip
(
    unsigned long cSkip
)
{
    // handle running off the end
    //
    if (m_iCurrent + (int)cSkip > m_cElements) {
        m_iCurrent = m_cElements;
        return S_FALSE;
    }

    m_iCurrent += cSkip;
    return S_OK;
}

//=--------------------------------------------------------------------------=
// CStandardEnum::Reset
//=--------------------------------------------------------------------------=
// reset the counter.
//
// Output:
//    HRESULT        - S_OK
//
// Notes:
//
STDMETHODIMP CStandardEnum::Reset
(
    void
)
{
    m_iCurrent = 0;
    return S_OK;
}

//=--------------------------------------------------------------------------=
// CStandardEnum::Clone
//=--------------------------------------------------------------------------=
// clones the object and gives the new one the same position
//
// Parameters:
//    IEnumVARIANT **    - [out] where to put the new object.
//
// Output;
//    HRESULT            - S_OK, E_OUTOFMEMORY
//
// Notes:
//
STDMETHODIMP CStandardEnum::Clone
(
    IEnumGeneric **ppEnumClone
)
{
    CStandardEnum *pNewEnum;

    pNewEnum = new CStandardEnum(m_iid, m_cElements, m_cbElementSize, m_rgElements, m_pfnCopyElement);
    RETURN_ON_NULLALLOC(pNewEnum);

    // hold on to who we were cloned from so m_rgElements stays alive, and we don't
    // have to copy it.
    //
    pNewEnum->m_pEnumClonedFrom = this;

    // AddRef() ourselves on their behalf.
    //
    AddRef();
    *ppEnumClone = (IEnumGeneric *)pNewEnum;
    return S_OK;
}


UNKNOWN.CPP

//=--------------------------------------------------------------------------=
// Unknown.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// implementation for various things in the unknown object that supports
// aggregation.
//
#include "IPServer.H"
#include "Unknown.H"
#include <stddef.h>

//=--------------------------------------------------------------------------=
// CUnknownObject::CPrivateUnknownObject::m_pMainUnknown
//=--------------------------------------------------------------------------=
// this method is used when we're sitting in the private unknown object,
// and we need to get at the pointer for the main unknown.  basically, it's
// a little better to do this pointer arithmetic than have to store a pointer
// to the parent, etc.
//
inline CUnknownObject *CUnknownObject::CPrivateUnknownObject::m_pMainUnknown
(
    void
)
{
    return (CUnknownObject *)((LPBYTE)this - offsetof(CUnknownObject, m_UnkPrivate));
}

//=--------------------------------------------------------------------------=
// CUnknownObject::CPrivateUnknownObject::QueryInterface
//=--------------------------------------------------------------------------=
// this is the non-delegating internal QI routine.
//
// Parameters:
//    REFIID        - [in]  interface they want
//    void **       - [out] where they want to put the resulting object ptr.
//
// Output:
//    HRESULT       - S_OK, E_NOINTERFACE
//
// Notes:
//
STDMETHODIMP CUnknownObject::CPrivateUnknownObject::QueryInterface
(
    REFIID riid,
    void **ppvObjOut
)
{
    CHECK_POINTER(ppvObjOut);

    // if they're asking for IUnknown, then we have to pass them ourselves.
    // otherwise defer to the inheriting object's InternalQueryInterface
    //
    if (DO_GUIDS_MATCH(riid, IID_IUnknown)) {
        m_cRef++;
        *ppvObjOut = (IUnknown *)this;
        return S_OK;
    } else
        return m_pMainUnknown()->InternalQueryInterface(riid, ppvObjOut);

    // dead code
}

//=--------------------------------------------------------------------------=
// CUnknownObject::CPrivateUnknownObject::AddRef
//=--------------------------------------------------------------------------=
// adds a tick to the current reference count.
//
// Output:
//    ULONG        - the new reference count
//
// Notes:
//
ULONG CUnknownObject::CPrivateUnknownObject::AddRef
(
    void
)
{
    return ++m_cRef;
}

//=--------------------------------------------------------------------------=
// CUnknownObject::CPrivateUnknownObject::Release
//=--------------------------------------------------------------------------=
// removes a tick from the count, and delets the object if necessary
//
// Output:
//    ULONG         - remaining refs
//
// Notes:
//
ULONG CUnknownObject::CPrivateUnknownObject::Release
(
    void
)
{
    ULONG cRef = --m_cRef;

    if (!m_cRef)
        delete m_pMainUnknown();

    return cRef;
}

//=--------------------------------------------------------------------------=
// CUnknownObject::InternalQueryInterface
//=--------------------------------------------------------------------------=
// objects that are aggregated use this to support additional interfaces.
// they should call this method on their parent so that any of it's interfaces
// are queried.
//
// Parameters:
//    REFIID        - [in]  interface they want
//    void **       - [out] where they want to put the resulting object ptr.
//
// Output:
//    HRESULT       - S_OK, E_NOINTERFACE
//
// Notes:
//
HRESULT CUnknownObject::InternalQueryInterface
(
    REFIID  riid,
    void  **ppvObjOut
)
{
    *ppvObjOut = NULL;

    return E_NOINTERFACE;
}


UTIL.CPP

//=--------------------------------------------------------------------------=
// Util.C
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// contains routines that we will find useful.
//
#include "IPServer.H"

#include "Globals.H"
#include "Util.H"

void * _cdecl operator new(size_t size);
void  _cdecl operator delete(void *ptr);

// for ASSERT and FAIL
//
SZTHISFILE

//=---------------------------------------------------------------------------=
// overloaded new
//=---------------------------------------------------------------------------=
// for the retail case, we'll just use the win32 Local* heap management
// routines for speed and size
//
// Parameters:
//    size_t         - [in] what size do we alloc
//
// Output:
//    VOID *         - new memoery.
//
// Notes:
//
void * _cdecl operator new
(
    size_t    size
)
{
    return HeapAlloc(g_hHeap, 0, size);
}

//=---------------------------------------------------------------------------=
// overloaded delete
//=---------------------------------------------------------------------------=
// retail case just uses win32 Local* heap mgmt functions
//
// Parameters:
//    void *        - [in] free me!
//
// Notes:
//
void _cdecl operator delete ( void *ptr)
{
    HeapFree(g_hHeap, 0, ptr);
}

//=--------------------------------------------------------------------------=
// MakeWideFromAnsi
//=--------------------------------------------------------------------------=
// given a string, make a BSTR out of it.
//
// Parameters:
//    LPSTR         - [in]
//    BYTE          - [in]
//
// Output:
//    LPWSTR        - needs to be cast to final desired result
//
// Notes:
//
LPWSTR MakeWideStrFromAnsi
(
    LPSTR psz,
    BYTE  bType
)
{
    LPWSTR pwsz;
    int i;

    // arg checking.
    //
    if (!psz)
        return NULL;

    // compute the length of the required BSTR
    //
    i =  MultiByteToWideChar(CP_ACP, 0, psz, -1, NULL, 0);
    if (i <= 0) return NULL;

    // allocate the widestr
    //
    switch (bType) {
      case STR_BSTR:
        // -1 since it'll add it's own space for a NULL terminator
        //
        pwsz = (LPWSTR) SysAllocStringLen(NULL, i - 1);
        break;
      case STR_OLESTR:
        pwsz = (LPWSTR) CoTaskMemAlloc(i * sizeof(WCHAR));
        break;
      default:
        FAIL("Bogus String Type.");
    }

    if (!pwsz) return NULL;
    MultiByteToWideChar(CP_ACP, 0, psz, -1, pwsz, i);
    pwsz[i - 1] = 0;
    return pwsz;
}

//=--------------------------------------------------------------------------=
// MakeWideStrFromResId
//=--------------------------------------------------------------------------=
// given a resource ID, load it, and allocate a wide string for it.
//
// Parameters:
//    WORD            - [in] resource id.
//    BYTE            - [in] type of string desired.
//
// Output:
//    LPWSTR          - needs to be cast to desired string type.
//
// Notes:
//
LPWSTR MakeWideStrFromResourceId
(
    WORD    wId,
    BYTE    bType
)
{
    int i;

    char szTmp[512];

    // load the string from the resources.
    //
    i = LoadString(GetResourceHandle(), wId, szTmp, 512);
    if (!i) return NULL;

    return MakeWideStrFromAnsi(szTmp, bType);
}

//=--------------------------------------------------------------------------=
// MakeWideStrFromWide
//=--------------------------------------------------------------------------=
// given a wide string, make a new wide string with it of the given type.
//
// Parameters:
//    LPWSTR            - [in]  current wide str.
//    BYTE              - [in]  desired type of string.
//
// Output:
//    LPWSTR
//
// Notes:
//
LPWSTR MakeWideStrFromWide
(
    LPWSTR pwsz,
    BYTE   bType
)
{
    LPWSTR pwszTmp;
    int i;

    if (!pwsz) return NULL;

    // just copy the string, depending on what type they want.
    //
    switch (bType) {
      case STR_OLESTR:
        i = lstrlenW(pwsz);
        pwszTmp = (LPWSTR)CoTaskMemAlloc((i * sizeof(WCHAR)) + sizeof(WCHAR));
        if (!pwszTmp) return NULL;
        memcpy(pwszTmp, pwsz, (sizeof(WCHAR) * i) + sizeof(WCHAR));
        break;

      case STR_BSTR:
        pwszTmp = (LPWSTR)SysAllocString(pwsz);
        break;
    }

    return pwszTmp;
}

//=--------------------------------------------------------------------------=
// StringFromGuidA
//=--------------------------------------------------------------------------=
// returns an ANSI string from a CLSID or GUID
//
// Parameters:
//    REFIID               - [in]  clsid to make string out of.
//    LPSTR                - [in]  buffer in which to place resultant GUID.
//
// Output:
//    int                  - number of chars written out.
//
// Notes:
//
int StringFromGuidA
(
    REFIID   riid,
    LPSTR    pszBuf
)
{
    return wsprintf((char *)pszBuf, "{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}", riid.Data1,
            riid.Data2, riid.Data3, riid.Data4[0], riid.Data4[1], riid.Data4[2],
            riid.Data4[3], riid.Data4[4], riid.Data4[5], riid.Data4[6], riid.Data4[7]);

}

//=--------------------------------------------------------------------------=
// RegisterUnknownObject
//=--------------------------------------------------------------------------=
// registers a simple CoCreatable object.  nothing terribly serious.
// we add the following information to the registry:
//
// HKEY_CLASSES_ROOT\CLSID\<CLSID> = <ObjectName> Object
// HKEY_CLASSES_ROOT\CLSID\<CLSID>\InprocServer32 = <path to local server>
//
// Parameters:
//    LPCSTR       - [in] Object Name
//    REFCLSID     - [in] CLSID of the object
//
// Output:
//    BOOL         - FALSE means couldn't register it all
//
// Notes:
//
BOOL RegisterUnknownObject
(
    LPCSTR   pszObjectName,
    REFCLSID riidObject
)
{
    HKEY  hk = NULL, hkSub = NULL;
    char  szGuidStr[GUID_STR_LEN];
    DWORD dwPathLen, dwDummy;
    char  szScratch[MAX_PATH];
    long  l;

    // clean out any garbage
    //
    UnregisterUnknownObject(riidObject);

    // HKEY_CLASSES_ROOT\CLSID\<CLSID> = <ObjectName> Object
    // HKEY_CLASSES_ROOT\CLSID\<CLSID>\InprocServer32 = <path to local server>
    // HKEY_CLASSES_ROOT\CLSID\<CLSID>\InprocServer32  @ThreadingModel = Apartment
    //
    if (!StringFromGuidA(riidObject, szGuidStr)) goto CleanUp;
    wsprintf(szScratch, "CLSID\\%s", szGuidStr);
    l = RegCreateKeyEx(HKEY_CLASSES_ROOT, szScratch, 0, "", REG_OPTION_NON_VOLATILE,
                       KEY_READ | KEY_WRITE, NULL, &hk, &dwDummy);
    CLEANUP_ON_ERROR(l);

    wsprintf(szScratch, "%s Object", pszObjectName);
    l = RegSetValueEx(hk, NULL, 0, REG_SZ, (BYTE *)szScratch, lstrlen(szScratch) + 1);
    CLEANUP_ON_ERROR(l);

    l = RegCreateKeyEx(hk, "InprocServer32", 0, "", REG_OPTION_NON_VOLATILE,
                       KEY_READ | KEY_WRITE, NULL, &hkSub, &dwDummy);
    CLEANUP_ON_ERROR(l);

    dwPathLen = GetModuleFileName(g_hInstance, szScratch, sizeof(szScratch));
    if (!dwPathLen) goto CleanUp;

    l = RegSetValueEx(hkSub, NULL, 0, REG_SZ, (BYTE *)szScratch, dwPathLen + 1);
    CLEANUP_ON_ERROR(l);

    l = RegSetValueEx(hkSub, "ThreadingModel", 0, REG_SZ, (BYTE *)"Apartment", sizeof("Apartment"));
    CLEANUP_ON_ERROR(l);

    RegCloseKey(hkSub);
    RegCloseKey(hk);

    return TRUE;

    // we are not very happy!
    //
  CleanUp:
    if (hk) RegCloseKey(hk);
    if (hkSub) RegCloseKey(hkSub);
    return FALSE;

}

//=--------------------------------------------------------------------------=
// RegisterAutomationObject
//=--------------------------------------------------------------------------=
// given a little bit of information about an automation object, go and put it
// in the registry.
// we add the following information in addition to that set up in
// RegisterUnknownObject:
//
//
// HKEY_CLASSES_ROOT\<LibraryName>.<ObjectName> = <ObjectName> Object
// HKEY_CLASSES_ROOT\<LibraryName>.<ObjectName>\CLSID = <CLSID>
// HKEY_CLASSES_ROOT\<LibraryName>.<ObjectName>\CurVer = <ObjectName>.Object.<VersionNumber>
//
// HKEY_CLASSES_ROOT\<LibraryName>.<ObjectName>.<VersionNumber> = <ObjectName> Object
// HKEY_CLASSES_ROOT\<LibraryName>.<ObjectName>.<VersionNumber>\CLSID = <CLSID>
//
// HKEY_CLASSES_ROOT\CLSID\<CLSID>\TypeLib = <LibidOfTypeLibrary>
// HKEY_CLASSES_ROOT\CLSID\<CLSID>\ProgID = <LibraryName>.<ObjectName>.<VersionNumber>
// HKEY_CLASSES_ROOT\CLSID\<CLSID>\VersionIndependentProgID = <LibraryName>.<ObjectName>
//
// Parameters:
//    LPCSTR       - [in] Library Name
//    LPCSTR       - [in] Object Name
//    long         - [in] Version Number
//    REFCLSID     - [in] LIBID of type library
//    REFCLSID     - [in] CLSID of the object
//
// Output:
//    BOOL         - FALSE means not all of it was registered
//
// Notes:
//
BOOL RegisterAutomationObject
(
    LPCSTR   pszLibName,
    LPCSTR   pszObjectName,
    long     lVersion,
    REFCLSID riidLibrary,
    REFCLSID riidObject
)
{
    HKEY  hk = NULL, hkSub = NULL;
    char  szGuidStr[GUID_STR_LEN];
    char  szScratch[MAX_PATH];
    long  l;
    DWORD dwDummy;

    // first register the simple Unknown stuff.
    //
    if (!RegisterUnknownObject(pszObjectName, riidObject)) return FALSE;

    // HKEY_CLASSES_ROOT\<LibraryName>.<ObjectName> = <ObjectName> Object
    // HKEY_CLASSES_ROOT\<LibraryName>.<ObjectName>\CLSID = <CLSID>
    // HKEY_CLASSES_ROOT\<LibraryName>.<ObjectName>\CurVer = <ObjectName>.Object.<VersionNumber>
    //
    lstrcpy(szScratch, pszLibName);
    lstrcat(szScratch, ".");
    lstrcat(szScratch, pszObjectName);

    l = RegCreateKeyEx(HKEY_CLASSES_ROOT, szScratch, 0L, "",
                       REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE,
                       NULL, &hk, &dwDummy);
    CLEANUP_ON_ERROR(l);

    wsprintf(szScratch, "%s Object", pszObjectName);
    l = RegSetValueEx(hk, NULL, 0L, REG_SZ, (BYTE *)szScratch, lstrlen(szScratch)+1);
    CLEANUP_ON_ERROR(l);

    l = RegCreateKeyEx(hk, "CLSID", 0L, "", REG_OPTION_NON_VOLATILE,
                       KEY_READ | KEY_WRITE, NULL, &hkSub, &dwDummy);
    CLEANUP_ON_ERROR(l);

    if (!StringFromGuidA(riidObject, szGuidStr))
        goto CleanUp;

    l = RegSetValueEx(hkSub, NULL, 0L, REG_SZ, (BYTE *)szGuidStr, lstrlen(szGuidStr) + 1);
    CLEANUP_ON_ERROR(l);

    RegCloseKey(hkSub);
    l = RegCreateKeyEx(hk, "CurVer", 0, "", REG_OPTION_NON_VOLATILE,
                       KEY_READ | KEY_WRITE, NULL, &hkSub, &dwDummy);
    CLEANUP_ON_ERROR(l);

    wsprintf(szScratch, "%s.%s.%ld", pszLibName, pszObjectName, lVersion);
    l = RegSetValueEx(hkSub, NULL, 0, REG_SZ, (BYTE *)szScratch, lstrlen(szScratch) + 1);
    CLEANUP_ON_ERROR(l);

    RegCloseKey(hkSub);
    RegCloseKey(hk);

    // HKEY_CLASSES_ROOT\<LibraryName>.<ObjectName>.<VersionNumber> = <ObjectName> Object
    // HKEY_CLASSES_ROOT\<LibraryName>.<ObjectName>.<VersionNumber>\CLSID = <CLSID>
    //
    l = RegCreateKeyEx(HKEY_CLASSES_ROOT, szScratch, 0, "", REG_OPTION_NON_VOLATILE,
                       KEY_READ | KEY_WRITE, NULL, &hk, &dwDummy);
    CLEANUP_ON_ERROR(l);

    wsprintf(szScratch, "%s Object", pszObjectName);
    l = RegSetValueEx(hk, NULL, 0, REG_SZ, (BYTE *)szScratch, lstrlen(szScratch) + 1);
    CLEANUP_ON_ERROR(l);

    l = RegCreateKeyEx(hk, "CLSID", 0, "", REG_OPTION_NON_VOLATILE,
                       KEY_READ | KEY_WRITE, NULL, &hkSub, &dwDummy);
    CLEANUP_ON_ERROR(l);

    l = RegSetValueEx(hkSub, NULL, 0, REG_SZ, (BYTE *)szGuidStr, lstrlen(szGuidStr) + 1);
    CLEANUP_ON_ERROR(l);

    RegCloseKey(hkSub);
    RegCloseKey(hk);

    // HKEY_CLASSES_ROOT\CLSID\<CLSID>\ProgID = <LibraryName>.<ObjectName>.<VersionNumber>
    // HKEY_CLASSES_ROOT\CLSID\<CLSID>\VersionIndependentProgID = <LibraryName>.<ObjectName>
    // HKEY_CLASSES_ROOT\CLSID\<CLSID>\TypeLib = <LibidOfTypeLibrary>
    //
    if (!StringFromGuidA(riidObject, szGuidStr)) goto CleanUp;
    wsprintf(szScratch, "CLSID\\%s", szGuidStr);

    l = RegCreateKeyEx(HKEY_CLASSES_ROOT, szScratch, 0, "", REG_OPTION_NON_VOLATILE,
                       KEY_READ|KEY_WRITE, NULL, &hk, &dwDummy);
    CLEANUP_ON_ERROR(l);

    l = RegCreateKeyEx(hk, "VersionIndependentProgID", 0, "", REG_OPTION_NON_VOLATILE,
                       KEY_READ | KEY_WRITE, NULL, &hkSub, &dwDummy);
    CLEANUP_ON_ERROR(l);

    wsprintf(szScratch, "%s.%s", pszLibName, pszObjectName);
    l = RegSetValueEx(hkSub, NULL, 0, REG_SZ, (BYTE *)szScratch, lstrlen(szScratch) + 1);
    CLEANUP_ON_ERROR(l);

    RegCloseKey(hkSub);

    l = RegCreateKeyEx(hk, "ProgID", 0, "", REG_OPTION_NON_VOLATILE,
                       KEY_READ | KEY_WRITE, NULL, &hkSub, &dwDummy);
    CLEANUP_ON_ERROR(l);

    wsprintf(szScratch, "%s.%s.%ld", pszLibName, pszObjectName, lVersion);
    l = RegSetValueEx(hkSub, NULL, 0, REG_SZ, (BYTE *)szScratch, lstrlen(szScratch) + 1);
    CLEANUP_ON_ERROR(l);

    RegCloseKey(hkSub);
    l = RegCreateKeyEx(hk, "TypeLib", 0, "", REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE,
                       NULL, &hkSub, &dwDummy);

    if (!StringFromGuidA(riidLibrary, szGuidStr)) goto CleanUp;

    l = RegSetValueEx(hkSub, NULL, 0, REG_SZ, (BYTE *)szGuidStr, lstrlen(szGuidStr) + 1);
    CLEANUP_ON_ERROR(l);

    RegCloseKey(hkSub);
    RegCloseKey(hk);
    return TRUE;

  CleanUp:
    if (hk) RegCloseKey(hkSub);
    if (hk) RegCloseKey(hk);
    return FALSE;
}

//=--------------------------------------------------------------------------=
// RegisterControlObject.
//=--------------------------------------------------------------------------=
// in addition to writing out automation object information, this function
// writes out some values specific to a control.
//
// What we add here:
//
// HKEY_CLASSES_ROOT\CLSID\<CLSID>\Control
// HKEY_CLASSES_ROOT\CLSID\<CLSID>\MiscStatus\1 = <MISCSTATUSBITS>
// HKEY_CLASSES_ROOT\CLSID\<CLSID>\ToolboxBitmap32 = <PATH TO BMP>
// HKEY_CLASSES_ROOT\CLSID\<CLSID>\Version = <VERSION>
//
// Parameters:
//    LPCSTR       - [in] Library Name
//    LPCSTR       - [in] Object Name
//    long         - [in] Version Number
//    REFCLSID     - [in] LIBID of type library
//    REFCLSID     - [in] CLSID of the object
//    DWORD        - [in] misc status flags for ctl
//    WORD         - [in] toolbox id for control
//
// Output:
//    BOOL
//
// Notes:
//    - not the most terribly efficient routine.
//
BOOL RegisterControlObject
(
    LPCSTR   pszLibName,
    LPCSTR   pszObjectName,
    long     lVersion,
    REFCLSID riidLibrary,
    REFCLSID riidObject,
    DWORD    dwMiscStatus,
    WORD     wToolboxBitmapId
)
{
    HKEY    hk, hkSub = NULL, hkSub2 = NULL;
    char    szTmp[MAX_PATH];
    char    szGuidStr[GUID_STR_LEN];
    DWORD   dwDummy;
    LONG    l;

    // first register all the automation information for this sucker.
    //
    if (!RegisterAutomationObject(pszLibName, pszObjectName, lVersion, riidLibrary, riidObject)) return FALSE;

    // then go and register the control specific stuff.
    //
    StringFromGuidA(riidObject, szGuidStr);
    wsprintf(szTmp, "CLSID\\%s", szGuidStr);
    l = RegOpenKeyEx(HKEY_CLASSES_ROOT, szTmp, 0, KEY_ALL_ACCESS, &hk);
    if (l != ERROR_SUCCESS) return FALSE;

    // create the control flag.
    //
    l = RegCreateKeyEx(hk, "Control", 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkSub, &dwDummy);
    CLEANUP_ON_ERROR(l);

    // now set up the MiscStatus Bits...
    //
    RegCloseKey(hkSub);
    hkSub = NULL;
    l = RegCreateKeyEx(hk, "MiscStatus", 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkSub, &dwDummy);
    CLEANUP_ON_ERROR(l);

    szTmp[0] = '0';
    szTmp[1] = '\0';
    l = RegSetValueEx(hkSub, NULL, 0, REG_SZ, (BYTE *)szTmp, 2);
    CLEANUP_ON_ERROR(l);

    l = RegCreateKeyEx(hkSub, "1", 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkSub2, &dwDummy);
    CLEANUP_ON_ERROR(l);

    wsprintf(szTmp, "%d", dwMiscStatus);
    l = RegSetValueEx(hkSub2, NULL, 0, REG_SZ, (BYTE *)szTmp, lstrlen(szTmp) + 1);
    RegCloseKey(hkSub2);
    CLEANUP_ON_ERROR(l);

    RegCloseKey(hkSub);

    // now set up the toolbox bitmap
    //
    GetModuleFileName(g_hInstance, szTmp, MAX_PATH);
    wsprintf(szGuidStr, ", %d", wToolboxBitmapId);
    lstrcat(szTmp, szGuidStr);

    l = RegCreateKeyEx(hk, "ToolboxBitmap32", 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkSub, &dwDummy);
    CLEANUP_ON_ERROR(l);

    l = RegSetValueEx(hkSub, NULL, 0, REG_SZ, (BYTE *)szTmp, lstrlen(szTmp) + 1);
    CLEANUP_ON_ERROR(l);

    // now set up the version information
    //
    RegCloseKey(hkSub);
    l = RegCreateKeyEx(hk, "Version", 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkSub, &dwDummy);
    CLEANUP_ON_ERROR(l);

    wsprintf(szTmp, "%ld.0", lVersion);
    l = RegSetValueEx(hkSub, NULL, 0, REG_SZ, (BYTE *)szTmp, lstrlen(szTmp) + 1);

  CleanUp:
    if (hk)
        RegCloseKey(hk);
    if (hkSub)
        RegCloseKey(hkSub);

    return (l == ERROR_SUCCESS) ? TRUE : FALSE;
}

//=--------------------------------------------------------------------------=
// UnregisterUnknownObject
//=--------------------------------------------------------------------------=
// cleans up all the stuff that RegisterUnknownObject puts in the
// registry.
//
// Parameters:
//    REFCLSID     - [in] CLSID of the object
//
// Output:
//    BOOL         - FALSE means not all of it was registered
//
// Notes:
//    - WARNING: this routine will blow away all other keys under the CLSID
//      for this object.  mildly anti-social, but likely not a problem.
//
BOOL UnregisterUnknownObject
(
    REFCLSID riidObject
)
{
    char szScratch[MAX_PATH];
    HKEY hk;
    BOOL f;
    long l;

    // delete everybody of the form
    //   HKEY_CLASSES_ROOT\CLSID\<CLSID> [\] *
    //
    if (!StringFromGuidA(riidObject, szScratch))
        return FALSE;

    l = RegOpenKeyEx(HKEY_CLASSES_ROOT, "CLSID", 0, KEY_ALL_ACCESS, &hk);
    if (l != ERROR_SUCCESS) return FALSE;

    f = DeleteKeyAndSubKeys(hk, szScratch);
    RegCloseKey(hk);

    return f;
}

//=--------------------------------------------------------------------------=
// UnregisterAutomationObject
//=--------------------------------------------------------------------------=
// unregisters an automation object, including all of it's unknown object
// information.
//
// Parameters:
//    LPCSTR       - [in] Library Name
//    LPCSTR       - [in] Object Name
//    long         - [in] Version Number
//    REFCLSID     - [in] CLSID of the object
//
// Output:
//    BOOL         - FALSE means couldn't get it all unregistered.
//
// Notes:
//
BOOL UnregisterAutomationObject
(
    LPCSTR   pszLibName,
    LPCSTR   pszObjectName,
    long     lVersion,
    REFCLSID riidObject
)
{
    char szScratch[MAX_PATH];
    BOOL f;

    // first thing -- unregister Unknown information
    //
    f = UnregisterUnknownObject(riidObject);
    if (!f) return FALSE;

    // delete everybody of the form:
    //   HKEY_CLASSES_ROOT\<LibraryName>.<ObjectName> [\] *
    //
    wsprintf(szScratch, "%s.%s", pszLibName, pszObjectName);
    f = DeleteKeyAndSubKeys(HKEY_CLASSES_ROOT, szScratch);
    if (!f) return FALSE;

    // delete everybody of the form
    //   HKEY_CLASSES_ROOT\<LibraryName>.<ObjectName>.<VersionNumber> [\] *
    //
    wsprintf(szScratch, "%s.%s.%ld", pszLibName, pszObjectName, lVersion);
    f = DeleteKeyAndSubKeys(HKEY_CLASSES_ROOT, szScratch);
    if (!f) return FALSE;

    return TRUE;
}

//=--------------------------------------------------------------------------=
// UnregisterTypeLibrary
//=--------------------------------------------------------------------------=
// blows away the type library keys for a given libid.
//
// Parameters:
//    REFCLSID        - [in] libid to blow away.
//
// Output:
//    BOOL            - TRUE OK, FALSE bad.
//
// Notes:
//    - WARNING: this function just blows away the entire type library section,
//      including all localized versions of the type library.  mildly anti-
//      social, but not killer.
//
BOOL UnregisterTypeLibrary
(
    REFCLSID riidLibrary
)
{
    HKEY hk;
    char szScratch[GUID_STR_LEN];
    long l;
    BOOL f;

    // convert the libid into a string.
    //
    if (!StringFromGuidA(riidLibrary, szScratch))
        return FALSE;

    l = RegOpenKeyEx(HKEY_CLASSES_ROOT, "TypeLib", 0, KEY_ALL_ACCESS, &hk);
    if (l != ERROR_SUCCESS) return FALSE;

    f = DeleteKeyAndSubKeys(hk, szScratch);
    RegCloseKey(hk);
    return f;
}

//=--------------------------------------------------------------------------=
// DeleteKeyAndSubKeys
//=--------------------------------------------------------------------------=
// delete's a key and all of it's subkeys.
//
// Parameters:
//    HKEY                - [in] delete the descendant specified
//    LPSTR               - [in] i'm the descendant specified
//
// Output:
//    BOOL                - TRUE OK, FALSE baaaad.
//
// Notes:
//    - I don't feel too bad about implementing this recursively, since the
//      depth isn't likely to get all the great.
//    - Despite the win32 docs claiming it does, RegDeleteKey doesn't seem to
//      work with sub-keys under windows 95.
//
BOOL DeleteKeyAndSubKeys
(
    HKEY    hkIn,
    LPSTR   pszSubKey
)
{
    HKEY  hk;
    char  szTmp[MAX_PATH];
    DWORD dwTmpSize;
    long  l;
    BOOL  f;
    int   x;

    l = RegOpenKeyEx(hkIn, pszSubKey, 0, KEY_ALL_ACCESS, &hk);
    if (l != ERROR_SUCCESS) return FALSE;

    // loop through all subkeys, blowing them away.
    //
    f = TRUE;
    x = 0;
    while (f) {
        dwTmpSize = MAX_PATH;
        l = RegEnumKeyEx(hk, x, szTmp, &dwTmpSize, 0, NULL, NULL, NULL);
        if (l != ERROR_SUCCESS) break;
        f = DeleteKeyAndSubKeys(hk, szTmp);
        x++;
    }

    // there are no subkeys left, [or we'll just generate an error and return FALSE].
    // let's go blow this dude away.
    //
    RegCloseKey(hk);
    l = RegDeleteKey(hkIn, pszSubKey);

    return (l == ERROR_SUCCESS) ? TRUE : FALSE;
}

//=--------------------------------------------------------------------------=
// Conversion Routines
//=--------------------------------------------------------------------------=
// the following stuff is stuff used for the various conversion routines.
//
#define HIMETRIC_PER_INCH   2540
#define MAP_PIX_TO_LOGHIM(x,ppli)   ( (HIMETRIC_PER_INCH*(x) + ((ppli)>>1)) / (ppli) )
#define MAP_LOGHIM_TO_PIX(x,ppli)   ( ((ppli)*(x) + HIMETRIC_PER_INCH/2) / HIMETRIC_PER_INCH )

static  int     s_iXppli;            // Pixels per logical inch along width
static  int     s_iYppli;            // Pixels per logical inch along height
static  BYTE    s_fGotScreenMetrics; // Are above valid?

//=--------------------------------------------------------------------------=
// GetScreenMetrics
//=--------------------------------------------------------------------------=
// private function we call to set up various metrics the conversion routines
// will use.
//
// Notes:
//
static void GetScreenMetrics
(
    void
)
{
    HDC hDCScreen;

    // we have to critical section this in case two threads are converting
    // things at the same time
    //
    EnterCriticalSection(&g_CriticalSection);
    if (s_fGotScreenMetrics)
        goto Done;

    // we want the metrics for the screen
    //
    hDCScreen = GetDC(NULL);

    ASSERT(hDCScreen, "couldn't get a DC for the screen.");
    s_iXppli = GetDeviceCaps(hDCScreen, LOGPIXELSX);
    s_iYppli = GetDeviceCaps(hDCScreen, LOGPIXELSY);

    ReleaseDC(NULL, hDCScreen);
    s_fGotScreenMetrics = TRUE;

    // we're done with our critical seciton.  clean it up
    //
  Done:
    LeaveCriticalSection(&g_CriticalSection);
}

//=--------------------------------------------------------------------------=
// HiMetricToPixel
//=--------------------------------------------------------------------------=
// converts from himetric to Pixels.
//
// Parameters:
//    const SIZEL *        - [in]  dudes in himetric
//    SIZEL *              - [out] size in pixels.
//
// Notes:
//
void HiMetricToPixel(const SIZEL * lpSizeInHiMetric, LPSIZEL lpSizeInPix)
{
    GetScreenMetrics();

    // We got logical HIMETRIC along the display, convert them to pixel units
    //
    lpSizeInPix->cx = MAP_LOGHIM_TO_PIX(lpSizeInHiMetric->cx, s_iXppli);
    lpSizeInPix->cy = MAP_LOGHIM_TO_PIX(lpSizeInHiMetric->cy, s_iYppli);
}

//=--------------------------------------------------------------------------=
// PixelToHiMetric
//=--------------------------------------------------------------------------=
// converts from pixels to himetric.
//
// Parameters:
//    const SIZEL *        - [in]  size in pixels
//    SIZEL *              - [out] size in himetric
//
// Notes:
//
void PixelToHiMetric(const SIZEL * lpSizeInPix, LPSIZEL lpSizeInHiMetric)
{
    GetScreenMetrics();

    // We got pixel units, convert them to logical HIMETRIC along the display
    //
    lpSizeInHiMetric->cx = MAP_PIX_TO_LOGHIM(lpSizeInPix->cx, s_iXppli);
    lpSizeInHiMetric->cy = MAP_PIX_TO_LOGHIM(lpSizeInPix->cy, s_iYppli);
}

//=--------------------------------------------------------------------------=
// _MakePath
//=--------------------------------------------------------------------------=
// little helper routine for RegisterLocalizedTypeLibs and GetResourceHandle.
// not terrilby efficient or smart, but it's registration code, so we don't
// really care.
//
// Notes:
//
void _MakePath
(
    LPSTR pszFull,
    const char * pszName,
    LPSTR pszOut
)
{
    LPSTR psz;
    LPSTR pszLast;

    lstrcpy(pszOut, pszFull);
    psz = pszLast = pszOut;
    while (*psz) {
        if (*psz == '\\')
            pszLast = AnsiNext(psz);
        psz = AnsiNext(psz);
    }

    // got the last \ character, so just go and replace the name.
    //
    lstrcpy(pszLast, pszName);
}

// from Globals.C
//
extern HINSTANCE    g_hInstResources;

//=--------------------------------------------------------------------------=
// GetResourceHandle
//=--------------------------------------------------------------------------=
// returns the resource handle.  we use the host's ambient Locale ID to
// determine, from a table in the DLL, which satellite DLL to load for
// localized resources.
//
// Output:
//    HINSTANCE
//
// Notes:
//
HINSTANCE GetResourceHandle
(
    void
)
{
    int i;
    char szExtension[5], szTmp[MAX_PATH];
    char szDllName[MAX_PATH], szFinalName[MAX_PATH];

    // crit sect this so that we don't screw anything up.
    //
    EnterCriticalSection(&g_CriticalSection);

    // don't do anything if we don't have to
    //
    if (g_hInstResources || !g_fSatelliteLocalization)
        goto CleanUp;

    // we're going to call GetLocaleInfo to get the abbreviated name for the
    // LCID we've got.
    //
    i = GetLocaleInfo(g_lcidLocale, LOCALE_SABBREVLANGNAME, szExtension, sizeof(szExtension));
    if (!i) goto CleanUp;

    // we've got the language extension.  go and load the DLL name from the
    // resources and then tack on the extension.
    // please note that all inproc sers -must- have the string resource 1001
    // defined to the base name of the server if they wish to support satellite
    // localization.
    //
    i = LoadString(g_hInstance, 1001, szTmp, sizeof(szTmp));
    ASSERT(i, "This server doesn't have IDS_SERVERBASENAME defined in their resources!");
    if (!i) goto CleanUp;

    // got the basename and the extention. go and combine them, and then add
    // on the .DLL for them.
    //
    wsprintf(szDllName, "%s%s.DLL", szTmp, szExtension);

    // try to load in the DLL
    //
    GetModuleFileName(g_hInstance, szTmp, MAX_PATH);
    _MakePath(szTmp, szDllName, szFinalName);

    g_hInstResources = LoadLibrary(szFinalName);

    // if we couldn't find it with the entire LCID, try it with just the primary
    // langid

//
    if (!g_hInstResources) {
        LPSTR psz;
        LCID lcid;
        lcid = MAKELCID(MAKELANGID(PRIMARYLANGID(LANGIDFROMLCID(g_lcidLocale)), SUBLANG_DEFAULT), SORT_DEFAULT);
        i = GetLocaleInfo(lcid, LOCALE_SABBREVLANGNAME, szExtension, sizeof(szExtension));
        if (!i) goto CleanUp;

        // reconstruct the DLL name.  the -7 is the length of XXX.DLL. mildly
        // hacky, but it should be fine.  there are no DBCS lang identifiers.
        // finally, retry the load
        //
        psz = szFinalName + lstrlen(szFinalName);
        memcpy((LPBYTE)psz - 7, szExtension, 3);
        g_hInstResources = LoadLibrary(szFinalName);
    }

  CleanUp:
    // if we couldn't load the DLL for some reason, then just return the
    // current resource handle, which is good enough.
    //
    if (!g_hInstResources) g_hInstResources = g_hInstance;
    LeaveCriticalSection(&g_CriticalSection);

    return g_hInstResources;
}


AUTOOBJ.H

//=--------------------------------------------------------------------------=
// AutoObj.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// all of our objects will inherit from this class to share as much of the same
// code as possible.  this super-class contains the unknown and dispatch
// implementations for them.
//
#ifndef _AUTOOBJ_H_

// all automation objects will use the Unknown object that supports aggegation.
//
#include "Unknown.H"

//=--------------------------------------------------------------------------=
// the constants in this header file uniquely identify your automation objects.
// make sure that for each object you have in the g_ObjectInfo table, you have
// a constant in this header file.
//
#include "LocalSrv.H"
#include "extobj.h"

//=--------------------------------------------------------------------------=
// AUTOMATIONOBJECTINFO
//=--------------------------------------------------------------------------=
// for each automation object type you wish to expose to the programmer/user
// that is not a control, you must fill out one of these structures.  if the
// object isn't CoCreatable, then the first four fields should be empty.
// otherwise, they should be filled in with the appropriate information.
// use the macro DEFINE_AUTOMATIONOBJECT to both declare and define your object.
// make sure you have an entry in the global table of objects, g_ObjectInfo
// in the main .Cpp file for your InProc server.
//
typedef struct {

    UNKNOWNOBJECTINFO unknowninfo;               // fill in with 0's if we're not CoCreatable
    long         lVersion;                       // Version number of Object.  ONLY USE IF YOU'RE CoCreatable!
    const IID   *riid;                           // object's type
    LPCSTR       pszHelpFile;                    // the helpfile for this automation object.
    ITypeInfo   *pTypeInfo;                      // typeinfo for this object
    UINT         cTypeInfo;                      // number of refs to the type info

} AUTOMATIONOBJECTINFO;

// macros to manipulate the AUTOMATIONOBJECTINFO in the global table table.
//
#define VERSIONOFOBJECT(index)         ((AUTOMATIONOBJECTINFO *)(g_ObjectInfo[(index)]).pInfo)->lVersion
#define INTERFACEOFOBJECT(index)       *(((AUTOMATIONOBJECTINFO *)(g_ObjectInfo[(index)]).pInfo)->riid)
#define PPTYPEINFOOFOBJECT(index)      &((((AUTOMATIONOBJECTINFO *)(g_ObjectInfo[(index)]).pInfo)->pTypeInfo))
#define PTYPEINFOOFOBJECT(index)       ((AUTOMATIONOBJECTINFO *)(g_ObjectInfo[(index)]).pInfo)->pTypeInfo
#define CTYPEINFOOFOBJECT(index)       ((AUTOMATIONOBJECTINFO *)(g_ObjectInfo[(index)]).pInfo)->cTypeInfo
#define HELPFILEOFOBJECT(index)        ((AUTOMATIONOBJECTINFO *)(g_ObjectInfo[(index)]).pInfo)->pszHelpFile

#ifndef INITOBJECTS

#define DEFINE_AUTOMATIONOBJECT(name, clsid, objname, fn, ver, riid, pszh) \
extern AUTOMATIONOBJECTINFO name##Object \

#else
#define DEFINE_AUTOMATIONOBJECT(name, clsid, objname, fn, ver, riid, pszh) \
    AUTOMATIONOBJECTINFO name##Object = { { clsid, objname, fn }, ver, riid, pszh, NULL, 0} \

#endif // INITOBJECTS

//=--------------------------------------------------------------------------=
// Standard Dispatch and SupportErrorInfo
//=--------------------------------------------------------------------------=
// all objects should declare these in their class definitions so that they
// get standard implementations of IDispatch and ISupportErrorInfo.
//
#define DECLARE_STANDARD_DISPATCH() \
    STDMETHOD(GetTypeInfoCount)(UINT *pctinfo) { \
        return CAutomationObject::GetTypeInfoCount(pctinfo); \
    } \
    STDMETHOD(GetTypeInfo)(UINT itinfo, LCID lcid, ITypeInfo **ppTypeInfoOut) { \
        return CAutomationObject::GetTypeInfo(itinfo, lcid, ppTypeInfoOut); \
    } \
    STDMETHOD(GetIDsOfNames)(REFIID riid, OLECHAR **rgszNames, UINT cnames, LCID lcid, DISPID *rgdispid) { \
        return CAutomationObject::GetIDsOfNames(riid, rgszNames, cnames, lcid, rgdispid); \
    } \
    STDMETHOD(Invoke)(DISPID dispid, REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pdispparams, VARIANT *pVarResult, EXCEPINFO *pexcepinfo, UINT *puArgErr) { \
        return CAutomationObject::Invoke(dispid, riid, lcid, wFlags, pdispparams, pVarResult, pexcepinfo, puArgErr); \
    } \

#define DECLARE_STANDARD_SUPPORTERRORINFO() \
    STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid) { \
        return CAutomationObject::InterfaceSupportsErrorInfo(riid); \
    } \

enum {EXPANDO_DISABLED=FALSE, EXPANDO_ENABLED=TRUE};

//=--------------------------------------------------------------------------=
// CAutomationObject
//=--------------------------------------------------------------------------=
// global class that all automation objects can inherit from to give them a
// bunch of implementation for free, namely IDispatch and ISupportsErrorInfo
//
//
class CAutomationObject : public CUnknownObject {

  public:
    // aggreation query interface support
    //
    virtual HRESULT InternalQueryInterface(REFIID riid, void **ppvObjOut);

    // IDispatch methods
    //
    STDMETHOD(GetTypeInfoCount)(UINT *);
    STDMETHOD(GetTypeInfo)(UINT, LCID, ITypeInfo **);
    STDMETHOD(GetIDsOfNames)(REFIID, OLECHAR **, UINT, LCID, DISPID *);
    STDMETHOD(Invoke)(DISPID, REFIID, LCID, WORD, DISPPARAMS *, VARIANT *, EXCEPINFO *, UINT *);

    //  ISupportErrorInfo methods
    //
    STDMETHOD(InterfaceSupportsErrorInfo)(REFIID);

    CAutomationObject(IUnknown *, int , void *, BOOL fExpandoEnabled=FALSE);
    virtual ~CAutomationObject();

    // callable functions -- things that most people will find useful.
    //
    virtual HINSTANCE GetResourceHandle(void);
    HRESULT Exception(HRESULT hr, WORD idException, DWORD dwHelpContextID);

  protected:
    // member variables that derived objects might need to get at information in the
    // global object table
    //
    int   m_ObjectType;

  private:
    // member variables we don't share.
    //
    BYTE  m_fLoadedTypeInfo;
BYTE  m_fExpandoEnabled;
CExpandoObject* m_pexpando;
};

#define _AUTOOBJ_H_
#endif // _AUTOOBJ_H_


CTRLOBJ.H

//=--------------------------------------------------------------------------=
// CtrlObj.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// class declaration for the COleControl object
//
#ifndef _CTRLOBJ_H_

// we need the automation object and ctlole.h
//
#include "AutoObj.H"
#include <olectl.h>

// forward declaration
//
class COleControl;

//=--------------------------------------------------------------------------=
// Misc Helper Functions
//=--------------------------------------------------------------------------=
//
// given an Unknown pointer, get the COleControl * for it.  used typically
// in property page code.
//
COleControl *ControlFromUnknown(IUnknown *);

//=--------------------------------------------------------------------------=
// Misc Constants
//=--------------------------------------------------------------------------=
// maximum number of arguments that can be sent to FireEvent()
//
#define MAX_ARGS    32

// for the types of sinks that the COleControl class has.  you shouldn't ever
// need to use these
//
#define SINK_TYPE_EVENT      0
#define SINK_TYPE_PROPNOTIFY 1

// superclass window support.  you can pass this in to DoSuperClassPaint
//
#define DRAW_SENDERASEBACKGROUND        1

//=--------------------------------------------------------------------------=
// Various Hosts don't handle OLEIVERB_PROPERTIES correctly, so we can't use
// that as our Properties verb number.  Instead, we're going to define
// CTLIVERB_PROPERTIES as 1, and return that one in IOleObject::EnumVerbs,
// but we'll still handle OLEIVERB_PROPERTIES correctly in DoVerb.
//
#define CTLIVERB_PROPERTIES     1

//=--------------------------------------------------------------------------=
// this structure is like the OLEVERB structure, except that it has a resource ID
// instead of a string for the verb's name.  better support for localization.
//
typedef struct tagVERBINFO {

    LONG    lVerb;                // verb id
    ULONG   idVerbName;           // resource ID of verb name
    DWORD   fuFlags;              // verb flags
    DWORD   grfAttribs;           // Specifies some combination of the verb attributes in the OLEVERBATTRIB enumeration.

} VERBINFO;

// describes an event
//
typedef struct tagEVENTINFO {

    DISPID    dispid;                    // dispid of the event
    int       cParameters;               // number of arguments to the event
    VARTYPE  *rgTypes;                   // type of each argument

} EVENTINFO;

//=--------------------------------------------------------------------------=
// CONTROLOBJECTINFO
//=--------------------------------------------------------------------------=
// for each control you wish to expose to the programmer/user, you need to
// declare and define one of the following structures.  the first part should
// follow the rules of the AUTOMATIONOBJECTINFO structure.  it's pretty hard,
// however, to imagine a scenario where the control isn't CoCreatable ...
// once this structre is declared/defined, an entry should be put in the
// global g_ObjectInfo table.
//
typedef struct {

    AUTOMATIONOBJECTINFO AutomationInfo;           // automation and creation information
    const IID      *piidEvents;                    // IID of primary event interface
    DWORD           dwOleMiscFlags;                // control flags
    DWORD           dwActivationPolicy;            // IPointerInactive support
    VARIANT_BOOL    fOpaque;                       // is your control 100% opaque?
    VARIANT_BOOL    fWindowless;                   // do we do windowless if we can?
    WORD            wToolboxId;                    // resource ID of Toolbox Bitmap
    LPCSTR          szWndClass;                    // name of window control class
    VARIANT_BOOL    fWindowClassRegistered;        // has the window class been registered yet?
    WORD            cPropPages;                    // number of property pages
    const GUID    **rgPropPageGuids;               // array of the property page GUIDs
    WORD            cCustomVerbs;                  // number of custom verbs
    const VERBINFO *rgCustomVerbs;                 // description of custom verbs
    WNDPROC         pfnSubClass;                   // for subclassed controls.

} CONTROLOBJECTINFO;

#ifndef INITOBJECTS

#define DEFINE_CONTROLOBJECT(name, clsid, progid, fn, ver, riid, pszh, piide, dwcf, dwap, w, szwc, cpp, rgppg, ccv, rgcv) \
extern CONTROLOBJECTINFO name##Control \

#define DEFINE_WINDOWLESSCONTROLOBJECT(name, clsid, progid, fn, ver, riid, pszh, piide, dwcf, dwap, fo,  w, szwc, cpp, rgppg, ccv, rgcv) \
extern CONTROLOBJECTINFO name##Control \

#else
#define DEFINE_CONTROLOBJECT(name, clsid, progid, fn, ver, riid, pszh, piide, dwcf, dwap, w, szwc, cpp, rgppg, ccv, rgcv) \
CONTROLOBJECTINFO name##Control = { { {clsid, progid, fn}, ver, riid, pszh, NULL, 0}, piide, dwcf, dwap, TRUE, FALSE, w, szwc, FALSE, cpp, rgppg, ccv, rgcv, NULL } \

#define DEFINE_WINDOWLESSCONTROLOBJECT(name, clsid, progid, fn, ver, riid, pszh, piide, dwcf, dwap, fo, w, szwc, cpp, rgppg, ccv, rgcv) \
CONTROLOBJECTINFO name##Control = { { {clsid, progid, fn}, ver, riid, pszh, NULL, 0}, piide, dwcf, dwap, fo, TRUE, w, szwc, FALSE, cpp, rgppg, ccv, rgcv, NULL } \

#endif // !INITOBJECTS

#define OLEMISCFLAGSOFCONTROL(index)     ((CONTROLOBJECTINFO *)(g_ObjectInfo[index].pInfo))->dwOleMiscFlags
#define FCONTROLISWINDOWLESS(index)      ((CONTROLOBJECTINFO *)(g_ObjectInfo[index].pInfo))->fWindowless
#define FCONTROLISOPAQUE(index)          ((CONTROLOBJECTINFO *)(g_ObjectInfo[index].pInfo))->fOpaque
#define ACTIVATIONPOLICYOFCONTROL(index) ((CONTROLOBJECTINFO *)(g_ObjectInfo[index].pInfo))->dwActivationPolicy
#define EVENTIIDOFCONTROL(index)         (*(((CONTROLOBJECTINFO *)(g_ObjectInfo[index].pInfo))->piidEvents))
#define WNDCLASSNAMEOFCONTROL(index)     ((CONTROLOBJECTINFO *)(g_ObjectInfo[index].pInfo))->szWndClass
#define CPROPPAGESOFCONTROL(index)       ((CONTROLOBJECTINFO *)(g_ObjectInfo[index].pInfo))->cPropPages
#define PPROPPAGESOFCONTROL(index)       ((CONTROLOBJECTINFO *)(g_ObjectInfo[index].pInfo))->rgPropPageGuids
#define CCUSTOMVERBSOFCONTROL(index)     ((CONTROLOBJECTINFO *)(g_ObjectInfo[index].pInfo))->cCustomVerbs
#define CUSTOMVERBSOFCONTROL(index)      ((CONTROLOBJECTINFO *)(g_ObjectInfo[index].pInfo))->rgCustomVerbs
#define BITMAPIDOFCONTROL(index)         ((CONTROLOBJECTINFO *)(g_ObjectInfo[index].pInfo))->wToolboxId
#define CTLWNDCLASSREGISTERED(index)     ((CONTROLOBJECTINFO *)(g_ObjectInfo[index].pInfo))->fWindowClassRegistered
#define SUBCLASSWNDPROCOFCONTROL(index)  ((CONTROLOBJECTINFO *)(g_ObjectInfo[index].pInfo))->pfnSubClass

//=--------------------------------------------------------------------------=
// COleControl
//=--------------------------------------------------------------------------=
// the mother of all C++ objects
//
class COleControl : public CAutomationObject,
                    public IOleObject, public IOleControl,
                    public IOleInPlaceObjectWindowless, public IOleInPlaceActiveObject,
                    public IViewObjectEx, public IPersistPropertyBag,
                    public IPersistStreamInit, public IPersistStorage,
                    public IConnectionPointContainer, public ISpecifyPropertyPages,
                    public IProvideClassInfo, public IPointerInactive,
                    public IQuickActivate
{
  public:
    // IUnknown methods -- there are required since we inherit from variuos
    // people who themselves inherit from IUnknown.  just delegate to controlling
    // unknown
    //
    DECLARE_STANDARD_UNKNOWN();

    //=--------------------------------------------------------------------------=
    // IPersist methods.  used by IPersistStream and IPersistStorage
    //
    STDMETHOD(GetClassID)(THIS_ LPCLSID lpClassID);

    // IPersistStreamInit methods
    //
    STDMETHOD(IsDirty)(THIS);
    STDMETHOD(Load)(LPSTREAM pStm);
    STDMETHOD(Save)(LPSTREAM pStm, BOOL fClearDirty);
    STDMETHOD(GetSizeMax)(ULARGE_INTEGER FAR* pcbSize);
    STDMETHOD(InitNew)();

    // IPersistStorage
    //
    STDMETHOD(InitNew)(IStorage  *pStg);
    STDMETHOD(Load)(IStorage  *pStg);
    STDMETHOD(Save)(IStorage  *pStgSave, BOOL fSameAsLoad);
    STDMETHOD(SaveCompleted)(IStorage  *pStgNew);
    STDMETHOD(HandsOffStorage)(void);

    // IPersistPropertyBag
    //
    STDMETHOD(Load)(LPPROPERTYBAG pPropBag, LPERRORLOG pErrorLog);
    STDMETHOD(Save)(LPPROPERTYBAG pPropBag, BOOL fClearDirty,
                    BOOL fSaveAllProperties);

    // IOleControl methods
    //
    STDMETHOD(GetControlInfo)(LPCONTROLINFO pCI);
    STDMETHOD(OnMnemonic)(LPMSG pMsg);
    STDMETHOD(OnAmbientPropertyChange)(DISPID dispid);
    STDMETHOD(FreezeEvents)(BOOL bFreeze);

    // IOleObject methods
    //
    STDMETHOD(SetClientSite)(IOleClientSite  *pClientSite);
    STDMETHOD(GetClientSite)(IOleClientSite  * *ppClientSite);
    STDMETHOD(SetHostNames)(LPCOLESTR szContainerApp, LPCOLESTR szContainerObj);
    STDMETHOD(Close)(DWORD dwSaveOption);
    STDMETHOD(SetMoniker)(DWORD dwWhichMoniker, IMoniker  *pmk);
    STDMETHOD(GetMoniker)(DWORD dwAssign, DWORD dwWhichMoniker, IMoniker  * *ppmk);
    STDMETHOD(InitFromData)(IDataObject  *pDataObject, BOOL fCreation, DWORD dwReserved);
    STDMETHOD(GetClipboardData)(DWORD dwReserved, IDataObject  * *ppDataObject);
    STDMETHOD(DoVerb)(LONG iVerb, LPMSG lpmsg, IOleClientSite  *pActiveSite, LONG lindex,
                                     HWND hwndParent, LPCRECT lprcPosRect);
    STDMETHOD(EnumVerbs)(IEnumOLEVERB  * *ppEnumOleVerb);
    STDMETHOD(Update)(void);
    STDMETHOD(IsUpToDate)(void);
    STDMETHOD(GetUserClassID)(CLSID  *pClsid);
    STDMETHOD(GetUserType)(DWORD dwFormOfType, LPOLESTR  *pszUserType);
    STDMETHOD(SetExtent)(DWORD dwDrawAspect,SIZEL  *psizel);
    STDMETHOD(GetExtent)(DWORD dwDrawAspect, SIZEL  *psizel);
    STDMETHOD(Advise)(IAdviseSink  *pAdvSink, DWORD  *pdwConnection);
    STDMETHOD(Unadvise)(DWORD dwConnection);
    STDMETHOD(EnumAdvise)(IEnumSTATDATA  * *ppenumAdvise);
    STDMETHOD(GetMiscStatus)(DWORD dwAspect, DWORD  *pdwStatus);
    STDMETHOD(SetColorScheme)(LOGPALETTE  *pLogpal);

    // IOleWindow.  required for IOleInPlaceObject and IOleInPlaceActiveObject
    //
    STDMETHOD(GetWindow)(HWND *phwnd);
    STDMETHOD(ContextSensitiveHelp)(BOOL fEnterMode);

    // IOleInPlaceObject/IOleInPlaceObjectWindowless
    //
    STDMETHOD(InPlaceDeactivate)(void);
    STDMETHOD(UIDeactivate)(void);
    STDMETHOD(SetObjectRects)(LPCRECT lprcPosRect,LPCRECT lprcClipRect) ;
    STDMETHOD(ReactivateAndUndo)(void);
    STDMETHOD(OnWindowMessage)(UINT msg, WPARAM wParam, LPARAM lParam, LRESULT *plResult);
    STDMETHOD(GetDropTarget)(IDropTarget **ppDropTarget);

    // IOleInPlaceActiveObject
    //
    STDMETHOD(TranslateAccelerator)(LPMSG lpmsg);
    STDMETHOD(OnFrameWindowActivate)(BOOL fActivate);
    STDMETHOD(OnDocWindowActivate)(BOOL fActivate);
    STDMETHOD(ResizeBorder)(LPCRECT prcBorder,
                            IOleInPlaceUIWindow  *pUIWindow,
                            BOOL fFrameWindow);
    STDMETHOD(EnableModeless)(BOOL fEnable);

    // IViewObject2/IViewObjectEx
    //
    STDMETHOD(Draw)(DWORD dwDrawAspect, LONG lindex, void  *pvAspect,
                    DVTARGETDEVICE  *ptd, HDC hdcTargetDev, HDC hdcDraw,
                    LPCRECTL lprcBounds, LPCRECTL lprcWBounds,
                    BOOL ( __stdcall  *pfnContinue )(DWORD dwContinue),
                    DWORD dwContinue);
    STDMETHOD(GetColorSet)(DWORD dwDrawAspect,LONG lindex, void  *pvAspect,
                           DVTARGETDEVICE  *ptd, HDC hicTargetDev,
                           LOGPALETTE  * *ppColorSet);
    STDMETHOD(Freeze)(DWORD dwDrawAspect, LONG lindex,
                      void  *pvAspect,DWORD  *pdwFreeze);
    STDMETHOD(Unfreeze)(DWORD dwFreeze);
    STDMETHOD(SetAdvise)(DWORD aspects, DWORD advf, IAdviseSink  *pAdvSink);
    STDMETHOD(GetAdvise)(DWORD *pAspects, DWORD  *pAdvf, IAdviseSink  * *ppAdvSink);
    STDMETHOD(GetExtent)(DWORD dwDrawAspect, LONG lindex, DVTARGETDEVICE __RPC_FAR *ptd, LPSIZEL lpsizel);
    STDMETHOD(GetRect)(DWORD dwAspect, LPRECTL pRect);
    STDMETHOD(GetViewStatus)(DWORD *pdwStatus);
    STDMETHOD(QueryHitPoint)(DWORD dwAspect, LPCRECT pRectBounds, POINT ptlLoc, LONG lCloseHint, DWORD *pHitResult);
    STDMETHOD(QueryHitRect)(DWORD dwAspect, LPCRECT pRectBounds, LPCRECT prcLoc, LONG lCloseHint, DWORD *pHitResult);
    STDMETHOD(GetNaturalExtent)(DWORD dwAspect, LONG lindex, DVTARGETDEVICE *ptd, HDC hicTargetDev, DVEXTENTINFO *pExtentInfo, LPSIZEL psizel);

    // IConnectionPointContainer methods
    //
    STDMETHOD(EnumConnectionPoints)(LPENUMCONNECTIONPOINTS FAR* ppEnum);
    STDMETHOD(FindConnectionPoint)(REFIID iid, LPCONNECTIONPOINT FAR* ppCP);

    // ISpecifyPropertyPages
    //
    STDMETHOD(GetPages)(CAUUID * pPages);

    // IProvideClassInfo methods
    //
    STDMETHOD(GetClassInfo)(LPTYPEINFO * ppTI);

    // IPointerInactive methods
    //
    STDMETHOD(GetActivationPolicy)(DWORD *pdwPolicy);
    STDMETHOD(OnInactiveMouseMove)(LPCRECT pRectBounds, long x, long y, DWORD dwMouseMsg);
    STDMETHOD(OnInactiveSetCursor)(LPCRECT pRectBounds, long x, long y, DWORD dwMouseMsg, BOOL fSetAlways);

    // IQuickActivate methods
    //
    STDMETHOD(QuickActivate)(QACONTAINER *pqacontainer, QACONTROL *pqacontrol);
    STDMETHOD(SetContentExtent)(LPSIZEL);
    STDMETHOD(GetContentExtent)(LPSIZEL);

    // constructor and destructor
    //
    COleControl(IUnknown *pUnkOuter, int iPrimaryDispatch, void *pMainInterface,
BOOL fExpandoEnabled=FALSE);
    virtual ~COleControl();

    //=--------------------------------------------------------------------------=
    // callable by anybody
    //
    static LRESULT CALLBACK ControlWindowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp);
    static LRESULT CALLBACK ReflectWindowProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp);
    static COleControl * ControlFromHwnd(HWND hwnd) {
        return (COleControl *) GetWindowLong(hwnd, GWL_USERDATA);
    }

    void __cdecl FireEvent(EVENTINFO * pEventInfo, ...);
    HINSTANCE    GetResourceHandle(void);

    //=--------------------------------------------------------------------------=
    // ole controls that want to support both windowed and windowless operations
    // should use these wrappers instead of the appropriate win32 api routine.
    // controls that don't care and just want to be windowed all the time can
    // just go ahead and use the api routines.
    //
    BOOL    SetFocus(BOOL fGrab);                       // SetFocus API
    BOOL    OcxGetFocus(void);                          // GetFocus() == m_hwnd
    BOOL    OcxGetWindowRect(LPRECT);                   // gets your current window rect
    LRESULT OcxDefWindowProc(UINT, WPARAM, LPARAM);     // DefWindowProc
    HDC     OcxGetDC(void);                             // GetDC(m_hwnd);
    void    OcxReleaseDC(HDC hdc);                      // ReleaseDC(m_hwnd, hdc);
    BOOL    OcxSetCapture(BOOL fGrab);                  // SetCapture(fGrab ? m_hwnd : NULL);
    BOOL    OcxGetCapture(void);                        // GetCapture() == m_hwnd
    BOOL    OcxInvalidateRect(LPCRECT, BOOL);           // InvalidateRect(m_hwnd, prc, f);
    BOOL    OcxScrollRect(LPCRECT, LPCRECT, int, int);  // ScrollWindowEx(...);

  protected:

    //=--------------------------------------------------------------------------=
    // member variables that derived controls can get at.
    //
    // derived controls Should NOT modify the following.
    //
    IOleClientSite     *m_pClientSite;             // client site
    IOleControlSite    *m_pControlSite;            // IOleControlSite ptr on client site
    IOleInPlaceSite    *m_pInPlaceSite;            // IOleInPlaceSite for managing activation
    IOleInPlaceFrame   *m_pInPlaceFrame;           // IOleInPlaceFrame ptr on client site
    IOleInPlaceUIWindow *m_pInPlaceUIWindow;       // for negotiating border space with client
    ISimpleFrameSite   *m_pSimpleFrameSite;        // simple frame site
    IDispatch          *m_pDispAmbient;            // ambient dispatch pointer
    SIZEL               m_Size;                    // the size of this control
    RECT                m_rcLocation;              // where we at
    HWND                m_hwnd;                    // our window
    HWND                m_hwndParent;              // our parent window
    HRGN                m_hRgn;

    // Windowless OLE controls support
    //
    IOleInPlaceSiteWindowless *m_pInPlaceSiteWndless; // IOleInPlaceSiteWindowless pointer

    // flags indicating internal state.  do not modify.
    //
    unsigned m_fDirty:1;                           // does the control need to be resaved?
    unsigned m_fInPlaceActive:1;                   // are we in place active or not?
    unsigned m_fInPlaceVisible:1;                  // we are in place visible or not?
    unsigned m_fUIActive:1;                        // are we UI active or not.
    unsigned m_fCreatingWindow:1;                  // indicates if we're in CreateWindowEx or not

    //=--------------------------------------------------------------------------=
    // methods that derived controls can override, but may need to be called
    // from their versions.
    //
    virtual void      ViewChanged(void);
    virtual HRESULT   InternalQueryInterface(REFIID riid, void **ppvObjOut);
    virtual BOOL      SetGUIFocus(HWND hwndSet);

    //=--------------------------------------------------------------------------=
    // member functions that provide for derived controls, or that we use, but
    // derived controls might still find useful.
    //
    HRESULT      DoSuperClassPaint(HDC, LPCRECTL);
    HRESULT      RecreateControlWindow(void);
    BOOL         DesignMode(void);
    BOOL         GetAmbientProperty(DISPID, VARTYPE, void *);
    BOOL         GetAmbientFont(IFont **ppFontOut);
    void         ModalDialog(BOOL fShow);
    void         InvalidateControl(LPCRECT prc);
    BOOL         SetControlSize(SIZEL *pSizel);

    HWND         CreateInPlaceWindow(int x, int y, BOOL fNoRedraw);
    HRESULT      InPlaceActivate(LONG lVerb);
    void         SetInPlaceVisible(BOOL);
    void         SetInPlaceParent(HWND);

    // IPropertyNotifySink stuff.
    //
    inline void  PropertyChanged(DISPID dispid) {
        m_cpPropNotify.DoOnChanged(dispid);
    }
    inline BOOL  RequestPropertyEdit(DISPID dispid) {
        return m_cpPropNotify.DoOnRequestEdit(dispid);
    }

    // subclassed windows controls support ...
    //
    inline HWND  GetOuterWindow(void) {
        return (m_hwndReflect) ? m_hwndReflect : m_hwnd;
    }

    // little routine for people to tell if they are windowless or not
    //
    inline BOOL  Windowless(void) {
        return !m_fInPlaceActive || m_pInPlaceSiteWndless;
    }

    // some people don't care if they're windowed or not -- they just need
    // a site pointer.  this makes it a little easier.
    //
    inline IOleInPlaceSite    *GetInPlaceSite(void) {
        return (IOleInPlaceSite *)(m_pInPlaceSiteWndless ? m_pInPlaceSiteWndless : m_pInPlaceSite);
    }

  private:
    //=--------------------------------------------------------------------------=
    // the following are methods that ALL control writers must override and implement
    //
    STDMETHOD(LoadBinaryState)(IStream *pStream) PURE;
    STDMETHOD(SaveBinaryState)(IStream *pStream) PURE;
    STDMETHOD(LoadTextState)(IPropertyBag *pPropertyBag, IErrorLog *pErrorLog) PURE;
    STDMETHOD(SaveTextState)(IPropertyBag *pPropertyBag, BOOL fWriteDefault) PURE;
    STDMETHOD(OnDraw)(DWORD dvAspect, HDC hdcDraw, LPCRECTL prcBounds, LPCRECTL prcWBounds, HDC hicTargetDev, BOOL fOptimize) PURE;
    virtual LRESULT WindowProc(UINT msg, WPARAM wParam, LPARAM lParam) PURE;
    virtual BOOL    RegisterClassData(void) PURE;

    //=--------------------------------------------------------------------------=
    // OVERRIDABLES -- methods controls can implement for customized functionality
    //
    virtual void    AmbientPropertyChanged(DISPID dispid);
    virtual BOOL    BeforeCreateWindow(DWORD *, DWORD *, LPSTR);
    virtual void    BeforeDestroyWindow(void);
    virtual HRESULT DoCustomVerb(LONG lVerb);
    virtual BOOL    OnSetExtent(const SIZEL *pSizeL);
    virtual BOOL    OnSpecialKey(LPMSG);
    virtual BOOL    OnGetPalette(HDC, LOGPALETTE **);
    virtual HRESULT OnQuickActivate(QACONTAINER *, DWORD *);
    virtual BOOL    InitializeNewState();
    virtual BOOL    AfterCreateWindow(void);
    virtual BOOL    OnGetRect(DWORD dvAspect, LPRECTL prcRect);
    virtual void    OnSetObjectRectsChangingWindowPos(DWORD *dwFlag);
    virtual void    OnVerb(LONG lVerb);

    //=--------------------------------------------------------------------------=
    // methods that various people internally will share.  not needed, however, by
    // any inherting classes.
    //
    HRESULT         m_SaveToStream(IStream *pStream);
    HRESULT         LoadStandardState(IPropertyBag *pPropertyBag, IErrorLog *pErrorLog);
    HRESULT         LoadStandardState(IStream *pStream);
    HRESULT         SaveStandardState(IPropertyBag *pPropertyBag);
    HRESULT         SaveStandardState(IStream *pStream);

    //=--------------------------------------------------------------------------=
    // member variables we don't want anybody to get their hands on, including
    // inheriting classes
    //
    HWND              m_hwndReflect;               // for subclassed windows
    IOleAdviseHolder *m_pOleAdviseHolder;          // IOleObject::Advise holder object
    IAdviseSink      *m_pViewAdviseSink;           // IViewAdvise sink for IViewObject2
    unsigned short    m_nFreezeEvents;             // count of freezes versus thaws
    unsigned          m_fHostReflects:1;           // does the host reflect messages?
    unsigned          m_fCheckedReflecting:1;      // have we checked above yet?

    // internal flags.  various other flags are visible to the end control class.
    //
    unsigned m_fModeFlagValid:1;                   // we stash the mode as much as possible
    unsigned m_fSaveSucceeded:1;                   // did an IStorage save work correctly?
    unsigned m_fViewAdvisePrimeFirst: 1;           // for IViewobject2::setadvise
    unsigned m_fViewAdviseOnlyOnce: 1;             // for iviewobject2::setadvise
    unsigned m_fUsingWindowRgn:1;                  // for SetObjectRects and clipping
    unsigned m_fRunMode:1;                         // are we in run mode or not?

    class CConnectionPoint : public IConnectionPoint {
      public:
        IUnknown **m_rgSinks;

        // IUnknown methods
        //
        STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) ;
        STDMETHOD_(ULONG,AddRef)(THIS) ;
        STDMETHOD_(ULONG,Release)(THIS) ;

        // IConnectionPoint methods
        //
        STDMETHOD(GetConnectionInterface)(IID FAR* pIID);
        STDMETHOD(GetConnectionPointContainer)(IConnectionPointContainer FAR* FAR* ppCPC);
        STDMETHOD(Advise)(LPUNKNOWN pUnkSink, DWORD FAR* pdwCookie);
        STDMETHOD(Unadvise)(DWORD dwCookie);
        STDMETHOD(EnumConnections)(LPENUMCONNECTIONS FAR* ppEnum);

        void    DoInvoke(DISPID dispid, DISPPARAMS * pdispparam);
        void    DoOnChanged(DISPID dispid);
        BOOL    DoOnRequestEdit(DISPID dispid);
        HRESULT AddSink(void *, DWORD *);

        COleControl *m_pOleControl();
        CConnectionPoint(BYTE b){
            m_bType = b;
            m_rgSinks = NULL;
            m_cSinks = 0;
        }
        ~CConnectionPoint();

      private:
        BYTE   m_bType;
        short  m_cSinks;

    } m_cpEvents, m_cpPropNotify;

    // so they can get at some of our protected things, like AddRef, QI, etc.
    //
    friend CConnectionPoint;
};

#define _CTRLOBJ_H_
#endif // _CTRLOBJ_H_


DEBUG.H

//=--------------------------------------------------------------------------=
// Debug.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// contains the various macros and the like which are only useful in DEBUG
// builds
//
#ifndef _DEBUG_H_

//=---------------------------------------------------------------------------=
// all the things required to handle our ASSERT mechanism
//=---------------------------------------------------------------------------=
//
#if DEBUG

// Function Prototypes
//
VOID DisplayAssert(LPSTR pszMsg, LPSTR pszAssert, LPSTR pszFile, UINT line);

// Macros
//
// *** Include this macro at the top of any source file using *ASSERT*() macros ***
//
#define SZTHISFILEstatic char _szThisFile[] = __FILE__;

// our versions of the ASSERT and FAIL macros.
//
#define ASSERT(fTest, szMsg)                                \
    if (!(fTest))  {                                        \
        static char szMsgCode[] = szMsg;                    \
        static char szAssert[] = #fTest;                    \
        DisplayAssert(szMsgCode, szAssert, _szThisFile, __LINE__); \
    }

#define FAIL(szMsg)                                         \
        { static char szMsgCode[] = szMsg;                    \
        DisplayAssert(szMsgCode, "FAIL", _szThisFile, __LINE__); }

// macro that checks a pointer for validity on input
//
#define CHECK_POINTER(val) if (!(val) || IsBadWritePtr((void *)(val), sizeof(void *))) return E_POINTER

#else  // DEBUG

#define SZTHISFILE
#define ASSERT(fTest, err)
#define FAIL(err)

#define CHECK_POINTER(val)
#endif// DEBUG

#define _DEBUG_H_
#endif // _DEBUG_H_


EXTOBJ.H

#ifndef __EXTOBJ_H
#define __EXTOBJ_H
////
//
// ExpandoObject header file
//
//
//
#include "IPServer.H"

////
//
// IDispatchEx
//
////

////
//
// the GUID
//

// {A0AAC450-A77B-11cf-91D0-00AA00C14A7C}
DEFINE_GUID(IID_IDispatchEx, 0xa0aac450, 0xa77b, 0x11cf, 0x91, 0xd0, 0x0, 0xaa, 0x0, 0xc1, 0x4a, 0x7c);

////
//
// IDispatchEx flags:
//

enum
{
fdexNil = 0x00,// empty
fdexDontCreate = 0x01,// don't create slot if non-existant otherwise do
fdexInitNull = 0x02,// init a new slot to VT_NULL as opposed to VT_EMPTY
fdexCaseSensitive = 0x04,// match names as case sensitive
};

////
//
// This is the interface for extensible IDispatch objects.
//

class IDispatchEx : public IDispatch
{
public:
// Get dispID for names, with options
virtual HRESULT STDMETHODCALLTYPE GetIDsOfNamesEx(
REFIID riid,
LPOLESTR *prgpsz,
UINT cpsz,
LCID lcid,
DISPID *prgid,
DWORD grfdex
) = 0;

// Enumerate dispIDs and their associated "names".
// Returns S_FALSE if the enumeration is done, NOERROR if it's not, an
// error code if the call fails.
virtual HRESULT STDMETHODCALLTYPE GetNextDispID(
DISPID id,
DISPID *pid,
BSTR *pbstrName
) = 0;
};

////
//
// Globals and definitions
//
////

#define NUM_EXPANDO_DISPIDS250
#defineNUM_CORE_DISPIDS250
#define NUM_RESERVED_EXTENDER_DISPIDS (NUM_CORE_DISPIDS + NUM_EXPANDO_DISPIDS)
#define EXTENDER_DISPID_BASE ((ULONG)(0x80010000))
#define IS_EXTENDER_DISPID(x) ( ( (ULONG)(x) & 0xFFFF0000 ) == EXTENDER_DISPID_BASE )

////
//
// Slot: the state of a value slot
//

inline WCHAR ToUpper(WCHAR ch)
{
if (ch>='a' && ch <= 'z')
return ch - 'a' + 'A';
else
return ch;

}

class CExpandoObjectSlot
{
public:
////
//
// Constructor/Destructor
//

// because these monsters are malloc'ed, we need a manual constructor and destructor methods
void Construct()
{
m_name = NULL;
m_next = -1;
VariantInit(&m_value);
// set hash and dispId to dummy values
m_hash = 0;
m_dispId = DISPID_UNKNOWN;
}

void Destruct()
{
if (m_name)
SysFreeString(m_name);
VariantClear(&m_value);
}

private:
// the constructors and destructors are private because they should never be called ...
// we could use in-place construction if we wanted to be clever ...
CExpandoObjectSlot()
{
}

~CExpandoObjectSlot()
{
}

public:
////
//
// Init the slot
//

HRESULT Init(LPOLESTR name, LCID lcid, DISPID dispId, VARIANT* value)
{
// allocate the string
m_name = SysAllocString(name);
if (m_name == NULL)
return E_OUTOFMEMORY;

// compute the hash: uses the standard OLE string hashing function
// note that this function is case insensitive
m_hash = LHashValOfName(lcid, name);

// set the dispId
m_dispId = dispId;

// Copy the variant value
return VariantCopy(&m_value, value);
}

////
//
// Name information
//

// get the name
BSTR Name()
{ return m_name; }

// compare two names
BOOL CompareName(LPOLESTR name, ULONG hash, BOOL caseSensitive)
{
// hash should be the same, length should be the same, and strings should compare
// BUGBUG robwell 8May96 These functions are probably verboten.
if (hash != m_hash)
return FALSE;

if (!name)
return !m_name;

WCHAR *c1 = name;
WCHAR *c2 = m_name;

// Travel down both strings until we reach a mismatched character
// or the end of one (or both) of the strings

if (caseSensitive)
while (*c1 && *c2 && *c1++==*c2++);
else
while (*c1 && *c2 && ToUpper(*c1++)==ToUpper(*c2++));

// The strings match if we reached the end of both without a mismatch
return !*c1 && !*c2;
 }

////
//
// DispId information
//

// get the dispatch id
DISPID DispId()
{ return m_dispId; }

////
//
// Get and set the property values
//

HRESULT Get(VARIANT* result)
{ return VariantCopy(result, &m_value); }

HRESULT Set(VARIANT* value)
{ return VariantCopy(&m_value, value); }

////
//
// List management
//

CExpandoObjectSlot* Next(CExpandoObjectSlot* base)
{ return m_next == -1? NULL: &base[m_next]; }

CExpandoObjectSlot* Insert(CExpandoObjectSlot* base, LONG& prev)
{
m_next = prev;
prev = this - base;
return this;
}

private:
// the DispId
DISPIDm_dispId;
// the name
LPOLESTRm_name;
// the name hash
ULONGm_hash;
// the property value
VARIANTm_value;
// the hash bucket link (index based)
LONGm_next;
};

// NB: CExpandoObject implements a crippled version of aggegation.
// It delegates all IUnknown calls to its controlling IUnknown, and has no
// private IUnknown interface.
// If you want the CExpandoObject to go away, simply call delete on it.
class CExpandoObject: public IDispatchEx
{
public:

////
//
// Constructor/Destructor
//

CExpandoObject(IUnknown *punkOuter, IDispatch *pdisp, ULONG dispIdBase = EXTENDER_DISPID_BASE + NUM_CORE_DISPIDS)
{
// remember our controlling outer
m_punkOuter = punkOuter;

// remember the IDispatch to try first for IDispatch functionality
m_pdisp = pdisp;

// clear the name hash table
ClearHashTable();
// set the total slots and the table of slots to 0 and empty respectively)
m_totalSlots = 0;
m_slotTableSize = 0;
m_slots = NULL;
m_dispIdBase = dispIdBase;
}

STDMETHODIMP_(ULONG) AddRef()
{
return m_punkOuter->AddRef();
}

STDMETHODIMP_(ULONG)Release()
{
return m_punkOuter->Release();
}

    STDMETHODIMP QueryInterface(REFIID riid, void **ppvObjOut)
{
return m_punkOuter->QueryInterface(riid, ppvObjOut);
}

    virtual ~CExpandoObject(void)
{
FreeAllSlots();
}

    // Copy all of the properties from obj
   CloneProperties(CExpandoObject& obj);

////
//
//
// Utility functions
//

// free all slots
void FreeAllSlots();

// IDispatch methods
virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(UINT *pctinfo);
virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(
UINT itinfo,
LCID lcid,
ITypeInfo **pptinfo
);
virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(
REFIID riid,
LPOLESTR *prgpsz,
UINT cpsz,
LCID lcid,
DISPID *prgdispID
);
virtual HRESULT STDMETHODCALLTYPE Invoke(
DISPID dispID,
REFIID riid,
LCID lcid,
WORD wFlags,
DISPPARAMS *pdispparams,
VARIANT *pvarRes,
EXCEPINFO *pexcepinfo,
UINT *puArgErr
);

// IDispatchEx methods

// Get dispID for names, with options
virtual HRESULT STDMETHODCALLTYPE GetIDsOfNamesEx(
REFIID riid,
LPOLESTR *prgpsz,
UINT cpsz,
LCID lcid,
DISPID *prgid,
DWORD grfdex
);

// Enumerate dispIDs and their associated "names".
// Returns S_FALSE if the enumeration is done, NOERROR if it's not, an
// error code if the call fails.
virtual HRESULT STDMETHODCALLTYPE GetNextDispID(
DISPID id,
DISPID *pid,
BSTR *pbstrName
);

private:
////
//
// Implementation constants
//

enum
{
kSlotHashTableSize = 10,
kInitialSlotTableSize = 4,
kMaxTotalSlots = NUM_EXPANDO_DISPIDS
};

////
//
// Utility functions
//

//
CExpandoObjectSlot* GetHashTableHead(UINT hashIndex)
{
LONG index;

return (index = m_hashTable[hashIndex]) == -1? NULL: &m_slots[index];
}

// get the ID of from a slot name
HRESULT GetIDOfName(LPOLESTR name, LCID lcid, BOOL caseSensitive, DISPID* id);
// add a slot to the object
HRESULT AddSlot(LPOLESTR name, LCID lcid, BOOL caseSensitive, VARIANT* initialValue, DISPID* id);
// allocate a slot from the slot table
CExpandoObjectSlot* AllocSlot();
// clear the hash table
void ClearHashTable()
{
UINT i;

for (i=0; i<kSlotHashTableSize; ++i)
m_hashTable[i] = -1;
}

////
//
// Slot operations
//
// DISPIDS start at kInitialDispId so we need to offset them by that amount
// in this code.
//

HRESULT GetSlot(DISPID id, VARIANT* result)
{
if ((ULONG) id < m_dispIdBase || (ULONG) id >= (m_totalSlots+m_dispIdBase))
return DISP_E_MEMBERNOTFOUND;

return m_slots[id-m_dispIdBase].Get(result);
}

HRESULT SetSlot(DISPID id, VARIANT* result)
{
if ((ULONG) id < m_dispIdBase || (ULONG) id >= (m_totalSlots+m_dispIdBase))
return DISP_E_MEMBERNOTFOUND;

return m_slots[id-m_dispIdBase].Set(result);
}

////
//
// Iteration operations
//

UINTNumDispIds()
{ return m_totalSlots; }

DISPIDFirst()
{ return m_dispIdBase; }

DISPIDLast()
{ return m_totalSlots + m_dispIdBase - 1; }

BOOLValidDispId(DISPID id)
{ return id >= First() && id <= Last(); }

HRESULTNext(DISPID key, CExpandoObjectSlot*& slot)
{
// zero restarts the enumerator
if (key == 0)
{
// if there are no slots we are done
if (NumDispIds() == 0)
return S_FALSE;

// return the first slot
slot = &m_slots[0];
return NOERROR;
}
else
if (key == Last())
{
// the key was the last slot so we are done
return S_FALSE;
}
else
if (ValidDispId(key))
{
// return the next slot
slot = &m_slots[key-m_dispIdBase+1];
return NOERROR;
}
else
// the key must be invalid
return E_INVALIDARG;
}

////
//
// The local state of the object
//

// the objects reference count
ULONGm_ref;

// the base of objectIds
ULONGm_dispIdBase;

// the hash table of slots - for fast GetIDSofNames lookup
LONGm_hashTable[kSlotHashTableSize];

// the number of slots (and the next dispId to allocate)
UINTm_totalSlots;

// the size of the allocated array of slots
UINTm_slotTableSize;

// a pointer to the allocated array of slots
CExpandoObjectSlot* m_slots;

// controlling unknown
IUnknown *m_punkOuter;

// controlling IDispatch
IDispatch *m_pdisp;
};

#endif // __EXTOBJ_H


GLOBALS.H

//=--------------------------------------------------------------------------=
// Globals.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// contains externs and stuff for Global variables, etc ..
//
#ifndef _GLOBALS_H_

// the library that we are
//
extern const CLSID *g_pLibid;

//=--------------------------------------------------------------------------=
// support for licensing
//
extern BOOL   g_fMachineHasLicense;
extern BOOL   g_fCheckedForLicense;

//=--------------------------------------------------------------------------=
// does our server have a type library?
//
extern BOOL   g_fServerHasTypeLibrary;

//=--------------------------------------------------------------------------=
// our instance handle, and various pieces of information interesting to
// localization
//
extern HINSTANCE    g_hInstance;

extern const VARIANT_BOOL g_fSatelliteLocalization;
extern VARIANT_BOOL       g_fHaveLocale;
extern LCID               g_lcidLocale;

//=--------------------------------------------------------------------------=
// apartment threading support.
//
extern CRITICAL_SECTION g_CriticalSection;

//=--------------------------------------------------------------------------=
// our global memory allocator and global memory pool
//
extern HANDLE   g_hHeap;

//=--------------------------------------------------------------------------=
// global parking window for parenting various things.
//
extern HWND     g_hwndParking;

//=--------------------------------------------------------------------------=
// system information
//
extern BOOL g_fSysWin95;                    // we're under Win95 system, not just NT SUR
extern BOOL g_fSysWinNT;                    // we're under some form of Windows NT
extern BOOL g_fSysWin95Shell;               // we're under Win95 or Windows NT SUR { > 3/51)

#define _GLOBALS_H_
#endif // _GLOBALS_H_


INTERNET.H

//=--------------------------------------------------------------------------=
// Internet.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// class declaration for the URLDib2 control.
//
#ifndef _INTERNET_H
#define _INTERNET_H

#define DISPID_PROGRESS 1958

#ifndef __MKTYPLIB__

#include "urlmon.H"
#include "ocidl.h"
#include "docobj.h"   // for IServiceProvider

#include "IPServer.H"
#include "CtrlObj.H"

class CInternetControl : public COleControl
{
public:
CInternetControl(IUnknown *     pUnkOuter,
int                     iPrimaryDispatch,
void *          pMainInterface);

virtual ~CInternetControl();

    // IUnknown methods
    //
    DECLARE_STANDARD_UNKNOWN();

// Call this method to start the download of a URL. 'propId' will
//      be passed back to you OnData below.
HRESULT SetupDownload( LPOLESTR url, DISPID propId);

//      Derived classes implement this method. It will be called when
//      data has arrived for a given dispid.
virtual HRESULT OnData( DISPID id, DWORD grfBSCF,
IStream * bitstrm, DWORD amount );

//      Derived classes can implement this method. It will be
//      called at various times during the download.
virtual HRESULT OnProgress( DISPID id, ULONG ulProgress,
ULONG ulProgressMax,
ULONG ulStatusCode,
LPCWSTR pwzStatusText);

//      Call this method to turn a URL into a Moniker.
HRESULT GetAMoniker( LPOLESTR   url, IMoniker ** );

    HRESULT FireReadyStateChange( long newState );
HRESULT FireProgress( ULONG dwAmount );

// Override base class implementation...

    virtual HRESULT InternalQueryInterface(REFIID, void **);

protected:
HRESULT GetBindHost();

    IBindHost *             m_host;
    long                    m_readyState;

    // BUGBUG: We should track all the downloads

};

#endif __MKTYPLIB__

#endif _INTERNET_H


IPSERVER.H

//=--------------------------------------------------------------------------=
// IPServer.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// global header file that contains all the windows stuff, etc ...  should
// be pre-compiled to speed things up a little bit.
//
#ifndef _IPSERVER_H_

//#define INC_OLE2
#include <windows.h>
#include <stddef.h>                    // for offsetof()
#include <olectl.h>

// things that -everybody- wants [read: is going to get]
//
#include "Debug.H"

//=--------------------------------------------------------------------------=
// controls can register for thread notifications in their InitializeLibrary()
//
#ifdef __cplusplus
extern "C" {
#endif // __cplusplus

typedef void (CALLBACK *THRDNFYPROC)(HANDLE, DWORD, void *);
void SetLibraryThreadProc(THRDNFYPROC pfnThreadNotify);

#ifdef __cplusplus
}
#endif // __cplusplus
//=--------------------------------------------------------------------------=

//=--------------------------------------------------------------------------=
// we don't want to use the CRTs, and would like some memory tracking in the
// debug case, so we'll override these guys
//=--------------------------------------------------------------------------=
//
//void * _cdecl operator new(size_t size);
//void  _cdecl operator delete(void *ptr);

//=--------------------------------------------------------------------------=
// Useful macros
//=--------------------------------------------------------------------------=
//
// handy error macros, randing from cleaning up, to returning to clearing
// rich error information as well.
//
#define RETURN_ON_FAILURE(hr) if (FAILED(hr)) return hr
#define RETURN_ON_NULLALLOC(ptr) if (!(ptr)) return E_OUTOFMEMORY
#define CLEANUP_ON_FAILURE(hr) if (FAILED(hr)) goto CleanUp
#define CLEARERRORINFORET(hr) { SetErrorInfo(0, NULL); return hr; }
#define CLEARERRORINFORET_ON_FAILURE(hr) if (FAILED(hr)) { SetErrorInfo(0, NULL); return hr; }

#define CLEANUP_ON_ERROR(l)    if (l != ERROR_SUCCESS) goto CleanUp

// conversions
//
#define BOOL_TO_VARIANTBOOL(f) (f) ? VARIANT_TRUE : VARIANT_FALSE

// Reference counting help.
//
#define RELEASE_OBJECT(ptr)    if (ptr) { IUnknown *pUnk = (ptr); (ptr) = NULL; pUnk->Release(); }
#define QUICK_RELEASE(ptr)     if (ptr) ((IUnknown *)ptr)->Release();
#define ADDREF_OBJECT(ptr)     if (ptr) (ptr)->AddRef()

//=--------------------------------------------------------------------------=
// QueryInterface Optimizations
//=--------------------------------------------------------------------------=
// for optimizing QI's
//
#define DO_GUIDS_MATCH(riid1, riid2) ((riid1.Data1 == riid2.Data1) && (riid1 == riid2))

// Data1_*
//
// the first dword of GUIDs for most of the interesting interfaces.  these are
// used by speed critical versions of QueryInterface
//
#define Data1_IAdviseSink                  0x0000010f
#define Data1_IAdviseSink2                 0x00000125
#define Data1_IAdviseSinkEx                0x3af24290
#define Data1_IBindCtx                     0x0000000e
#define Data1_ICDataDoc                    0xF413E4C0
#define Data1_IClassFactory                0x00000001
#define Data1_IClassFactory2               0xb196b28f
#define Data1_IConnectionPoint             0xb196b286
#define Data1_IConnectionPointContainer    0xb196b284
#define Data1_IControl_95                  0x9a4bbfb5
#define Data1_IControl                     0xa7fddba0
#define Data1_ICreateErrorInfo             0x22f03340
#define Data1_ICreateTypeInfo              0x00020405
#define Data1_ICreateTypeLib               0x00020406
#define Data1_IDataAdviseHolder            0x00000110
#define Data1_IDataFrame                   0x97F254E0
#define Data1_IDataFrameExpert             0x73687490
#define Data1_IDataObject                  0x0000010e
#define Data1_IDispatch                    0x00020400
#define Data1_IDropSource                  0x00000121
#define Data1_IDropTarget                  0x00000122
#define Data1_IEnumCallback                0x00000108
#define Data1_IEnumConnectionPoints        0xb196b285
#define Data1_IEnumConnections             0xb196b287
#define Data1_IEnumFORMATETC               0x00000103
#define Data1_IEnumGeneric                 0x00000106
#define Data1_IEnumHolder                  0x00000107
#define Data1_IEnumMoniker                 0x00000102
#define Data1_IEnumOLEVERB                 0x00000104
#define Data1_IEnumSTATDATA                0x00000105
#define Data1_IEnumSTATSTG                 0x0000000d
#define Data1_IEnumString                  0x00000101
#define Data1_IEnumOleUndoActions          0xb3e7c340
#define Data1_IEnumUnknown                 0x00000100
#define Data1_IEnumVARIANT                 0x00020404
#define Data1_IErrorInfo                   0x1cf2b120
#define Data1_IExternalConnection          0x00000019
#define Data1_IFont                        0xbef6e002
#define Data1_IFontDisp                    0xbef6e003
#define Data1_IFormExpert                  0x5aac7f70
#define Data1_IGangConnectWithDefault      0x6d5140c0
#define Data1_IInternalMoniker             0x00000011
#define Data1_ILockBytes                   0x0000000a
#define Data1_IMalloc                      0x00000002
#define Data1_IMarshal                     0x00000003
#define Data1_IMessageFilter               0x00000016
#define Data1_IMoniker                     0x0000000f
#define Data1_IMsoCommandTarget            0xb722bccb
#define Data1_IMsoDocument                 0xb722bcc5
#define Data1_IOleInPlaceComponent         0x5efc7970
#define Data1_IMsoView                     0xb722bcc6
#define Data1_IOleAdviseHolder             0x00000111
#define Data1_IOleCache                    0x0000011e
#define Data1_IOleCache2                   0x00000128
#define Data1_IOleCacheControl             0x00000129
#define Data1_IOleClientSite               0x00000118
#define Data1_IOleCompoundUndoAction       0xa1faf330
#define Data1_IOleContainer                0x0000011b
#define Data1_IOleControl                  0xb196b288
#define Data1_IOleControlSite              0xb196b289
#define Data1_IOleInPlaceActiveObject      0x00000117
#define Data1_IOleInPlaceFrame             0x00000116
#define Data1_IOleInPlaceObject            0x00000113
#define Data1_IOleInPlaceObjectWindowless  0x1c2056cc
#define Data1_IOleInPlaceSite              0x00000119
#define Data1_IOleInPlaceSiteEx            0x9c2cad80
#define Data1_IOleInPlaceSiteWindowless    0x922eada0
#define Data1_IOleInPlaceUIWindow          0x00000115
#define Data1_IOleItemContainer            0x0000011c
#define Data1_IOleLink                     0x0000011d
#define Data1_IOleManager                  0x0000011f
#define Data1_IOleObject                   0x00000112
#define Data1_IOlePresObj                  0x00000120
#define Data1_IOlePropertyFrame            0xb83bb801
#define Data1_IOleStandardTool             0xd97877c4
#define Data1_IOleUndoAction               0x894ad3b0
#define Data1_IOleUndoActionManager        0xd001f200
#define Data1_IOleWindow                   0x00000114
#define Data1_IPSFactory                   0x00000009
#define Data1_IPSFactoryBuffer             0xd5f569d0
#define Data1_IParseDisplayName            0x0000011a
#define Data1_IPerPropertyBrowsing         0x376bd3aa
#define Data1_IPersist                     0x0000010c
#define Data1_IPersistFile                 0x0000010b
#define Data1_IPersistPropertyBag          0x37D84F60
#define Data1_IPersistStorage              0x0000010a
#define Data1_IPersistStream               0x00000109
#define Data1_IPersistStreamInit           0x7fd52380
#define Data1_IPicture                     0x7bf80980
#define Data1_IPictureDisp                 0x7bf80981
#define Data1_IPointerInactive             0x55980ba0
#define Data1_IPropertyNotifySink          0x9bfbbc02
#define Data1_IPropertyPage                0xb196b28d
#define Data1_IPropertyPage2               0x01e44665
#define Data1_IPropertyPage3               0xb83bb803
#define Data1_IPropertyPageInPlace         0xb83bb802
#define Data1_IPropertyPageSite            0xb196b28c
#define Data1_IPropertyPageSite2           0xb83bb804
#define Data1_IProvideClassInfo            0xb196b283
#define Data1_IProvideDynamicClassInfo     0x6d5140d1
#define Data1_IQuickActivate               0xcf51ed10
#define Data1_IRequireClasses              0x6d5140d0
#define Data1_IRootStorage                 0x00000012
#define Data1_IRunnableObject              0x00000126
#define Data1_IRunningObjectTable          0x00000010
#define Data1_ISelectionContainer          0x6d5140c6
#define Data1_IServiceProvider             0x6d5140c1
#define Data1_ISimpleFrameSite             0x742b0e01
#define Data1_ISpecifyPropertyPages        0xb196b28b
#define Data1_IStdMarshalInfo              0x00000018
#define Data1_IStorage                     0x0000000b
#define Data1_IStream                      0x0000000c
#define Data1_ISupportErrorInfo            0xdf0b3d60
#define Data1_ITypeComp                    0x00020403
#define Data1_ITypeInfo                    0x00020401
#define Data1_ITypeLib                     0x00020402
#define Data1_IUnknown                     0x00000000
#define Data1_IViewObject                  0x0000010d
#define Data1_IViewObject2                 0x00000127
#define Data1_IViewObjectEx                0x3af24292
#define Data1_IWeakRef                     0x0000001a
#define Data1_ICategorizeProperties        0x4d07fc10

#define QI_INHERITS(pObj, itf)              \
    case Data1_##itf:                       \
      if(DO_GUIDS_MATCH(riid, IID_##itf))    \
      {                                     \
        *ppvObjOut = (void *)(itf *)pObj;   \
      }                                     \
      break;

#include "extobj.h"

#define _IPSERVER_H_
#endif // _IPSERVER_H_


LOCALSRV.H

//=--------------------------------------------------------------------------=
// LocalSrv.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// global routines that are specific to the inproc server itself, such as
// registration, object creation, object specification, etc...
//
#ifndef _LOCALSRV_H_

void      WINAPI InitializeLibrary(void);
void      WINAPI UninitializeLibrary(void);
BOOL      WINAPI RegisterData(void);
BOOL      WINAPI UnregisterData(void);
BOOL      WINAPI CheckForLicense();
BOOL      WINAPI CheckLicenseKey(LPWSTR wszCheckme);
BSTR      WINAPI GetLicenseKey(void);

// global variables that various people use to get information about the control.
//
extern const char g_szLibName [];
extern const CLSID *g_pLibid;

//=--------------------------------------------------------------------------=
// Global object information table
//=--------------------------------------------------------------------------=
// for each object in your application, you have an entry in this table.  they
// do not necessarily have to be CoCreatable, but if they are used, then they
// should reside here.  use the macros to fill in this table.
//
typedef struct tagOBJECTINFO {

    unsigned short usType;
    void          *pInfo;

} OBJECTINFO;

extern OBJECTINFO g_ObjectInfo[];

//=--------------------------------------------------------------------------=
// these things are used to set up our objects in our global object table
//
#define OI_UNKNOWN       0
#define OI_AUTOMATION    1
#define OI_CONTROL       2
#define OI_PROPERTYPAGE  3
#define OI_BOGUS         0xffff

#define OBJECTISCREATABLE(index)  (((UNKNOWNOBJECTINFO *)(g_ObjectInfo[(index)]).pInfo)->rclsid != NULL)
#define ISEMPTYOBJECT(index)      (g_ObjectInfo[index].usType == OI_BOGUS)

// these are the macros you should use to fill in the table.  Note that the name
// must be exactly the same as that used in the global structure you created
// for this object.
//
#define UNKNOWNOBJECT(name)    { OI_UNKNOWN,      (void *)&(name##Object) }
#define AUTOMATIONOBJECT(name) { OI_AUTOMATION,   (void *)&(name##Object) }
#define CONTROLOBJECT(name)    { OI_CONTROL,      (void *)&(name##Control) }
#define PROPERTYPAGE(name)     { OI_PROPERTYPAGE, (void *)&(name##Page) }
#define EMPTYOBJECT            { OI_BOGUS, NULL }

#define _LOCALSRV_H_
#endif // _LOCALSRV_H_


PROPPAGE.H

//=--------------------------------------------------------------------------=
// PropPage.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// class declaration for CPropertyPage.
//
#ifndef _PROPPAGE_H_

// things we really need
//
#include "Unknown.H"
#include <olectl.h>
#include "LocalSrv.H"

//=--------------------------------------------------------------------------=
// messages that we'll send to property pages to instruct them to accomplish
// tasks.
//
#define PPM_NEWOBJECTS    (WM_USER + 100)
#define PPM_APPLY         (WM_USER + 101)
#define PPM_EDITPROPERTY  (WM_USER + 102)
#define PPM_FREEOBJECTS   (WM_USER + 103)

//=--------------------------------------------------------------------------=
// structure that control writers will use to define property pages.
//
typedef struct tagPROPERTYPAGEINFO {

    UNKNOWNOBJECTINFO unknowninfo;
    WORD    wDlgResourceId;
    WORD    wTitleId;
    WORD    wDocStringId;
    LPCSTR  szHelpFile;
    DWORD   dwHelpContextId;

} PROPERTYPAGEINFO;

#ifndef INITOBJECTS

#define DEFINE_PROPERTYPAGEOBJECT(name, pclsid, pszon, pfn, wr, wt, wd, pszhf, dwhci) \
    extern PROPERTYPAGEINFO name##Page \

#else // INITOBJECTS

#define DEFINE_PROPERTYPAGEOBJECT(name, pclsid, pszon, pfn, wr, wt, wd, pszhf, dwhci) \
    PROPERTYPAGEINFO name##Page = { {pclsid, pszon, pfn }, wr, wt, wd, pszhf, dwhci } \

#endif // INITOBJECTS

#define TEMPLATENAMEOFPROPPAGE(index)    MAKEINTRESOURCE(((PROPERTYPAGEINFO *)(g_ObjectInfo[index].pInfo))->wDlgResourceId)
#define TITLEIDOFPROPPAGE(index)         (((PROPERTYPAGEINFO *)(g_ObjectInfo[index].pInfo))->wTitleId)
#define DOCSTRINGIDOFPROPPAGE(index)     (((PROPERTYPAGEINFO *)(g_ObjectInfo[index].pInfo))->wDocStringId)
#define HELPCONTEXTOFPROPPAGE(index)     (((PROPERTYPAGEINFO *)(g_ObjectInfo[index].pInfo))->dwHelpContextId)
#define HELPFILEOFPROPPAGE(index)        (((PROPERTYPAGEINFO *)(g_ObjectInfo[index].pInfo))->szHelpFile)

//=--------------------------------------------------------------------------=
//
class CPropertyPage : public CUnknownObject, public IPropertyPage2 {

  public:
    // IUnknown methods
    //
    DECLARE_STANDARD_UNKNOWN();

    // IPropertyPage methods
    //
    STDMETHOD(SetPageSite)(LPPROPERTYPAGESITE pPageSite);
    STDMETHOD(Activate)(HWND hwndParent, LPCRECT lprc, BOOL bModal);
    STDMETHOD(Deactivate)(void);
    STDMETHOD(GetPageInfo)(LPPROPPAGEINFO pPageInfo);
    STDMETHOD(SetObjects)(ULONG cObjects, LPUNKNOWN FAR* ppunk);
    STDMETHOD(Show)(UINT nCmdShow);
    STDMETHOD(Move)(LPCRECT prect);
    STDMETHOD(IsPageDirty)(void);
    STDMETHOD(Apply)(void);
    STDMETHOD(Help)(LPCOLESTR lpszHelpDir);
    STDMETHOD(TranslateAccelerator)(LPMSG lpMsg);

    // IPropertyPage2 methods
    //
    STDMETHOD(EditProperty)(THIS_ DISPID dispid);

    // constructor destructor
    //
    CPropertyPage(IUnknown *pUnkOuter, int iObjectType);
    virtual ~CPropertyPage();

    HINSTANCE GetResourceHandle(void);            // returns current resource handle.

  protected:
    IPropertyPageSite *m_pPropertyPageSite;       // pointer to our ppage site.
    void     MakeDirty();                         // makes the property page dirty.
    HWND     m_hwnd;                              // our hwnd.

    // the following two methods allow a property page implementer to get at all the
    // objects that we need to set here.
    //
    IUnknown *FirstControl(DWORD *dwCookie);
    IUnknown *NextControl(DWORD *dwCookie);

  private:
    IUnknown **m_ppUnkObjects;                    // objects that we're working with.

    unsigned m_fActivated:1;
    unsigned m_fDirty:1;
    int      m_ObjectType;                        // what type of object we are
    UINT     m_cObjects;                          // how many objects we're holding on to

    void     m_ReleaseAllObjects(void);           // clears out all objects we've got.
    HRESULT  m_EnsureLoaded(void);                // forces the load of the page.

    virtual HRESULT InternalQueryInterface(REFIID, void **);

    // default dialog proc for a page.
    //
    static BOOL CALLBACK PropPageDlgProc(HWND, UINT, WPARAM, LPARAM);

    // all page implementers MUST implement the following function.
    //
    virtual BOOL DialogProc(HWND, UINT, WPARAM, LPARAM) PURE;
};

#define _PROPPAGE_H_
#endif // _PROPPAGE_H_


STDENUM.H

//=--------------------------------------------------------------------------=
// StdEnum.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// object definition for a generic enumerator object.
//
#ifndef _STDENUM_H_

#include "Unknown.H"

// to support a generic Enumerator object, we'll just define this
// interface.  it can be safely cast to any other enumerator, since all
// they differ in is their pointer type in Next().
//
class IEnumGeneric: public IUnknown {

  public:
    virtual HRESULT __stdcall Next(ULONG celt, LPVOID rgelt, ULONG *pceltFetched) = 0;
    virtual HRESULT __stdcall Skip(ULONG celt) = 0;
    virtual HRESULT __stdcall Reset(void) = 0;
    virtual HRESULT __stdcall Clone(IEnumGeneric **ppenum) = 0;
};

//=--------------------------------------------------------------------------=
// StandardEnum
//=--------------------------------------------------------------------------=
// a generic enumerator object.  given a pointer to generic data, some
// information about the elements, and a function to copy the elements,
// we can implement a generic enumerator.
//
// NOTE: this class assumes that rgElements is HeapAlloc'd, and will free it
//       in it's destructor [although it IS valid for this to be NULL if there
//       are no elements to enumerate over.]
//
class CStandardEnum: public CUnknownObject, public IEnumGeneric {

public:
    // IUnknown methods
    //
    DECLARE_STANDARD_UNKNOWN();

    // IEnumVariant methods
    //
    STDMETHOD(Next)(unsigned long celt, void * rgvar, unsigned long * pceltFetched);
    STDMETHOD(Skip)(unsigned long celt);
    STDMETHOD(Reset)();
    STDMETHOD(Clone)(IEnumGeneric **ppEnumOut);

    CStandardEnum(REFIID riid, int cElement, int cbElement, void *rgElements,
                 void (WINAPI * pfnCopyElement)(void *, const void *, DWORD));
    ~CStandardEnum();

private:
    virtual HRESULT InternalQueryInterface(REFIID riid, void **ppvObjOut);

    IID m_iid;                        // type of enumerator that we are
    int m_cElements;                  // Total number of elements
    int m_cbElementSize;              // Size of each element
    int m_iCurrent;                   // Current position: 0 = front, m_cElt = end
    VOID * m_rgElements;              // Array of elements
    CStandardEnum *m_pEnumClonedFrom; // If we were cloned, from whom?
    void  (WINAPI * m_pfnCopyElement)(void *, const void *, DWORD);
};

#define _STDENUM_H_
#endif // _STDENUM_H_


STRCOLL.H

//=--------------------------------------------------------------------------=
// StrColl.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// contains the definitions for the various string collections we'll use
//
#ifndef _STRCOLL_H_

#include "CommDlgInterfaces.H"

//=--------------------------------------------------------------------------=
// the CStringsCollection class basically works with a safearray to expose the
// collection, and uses the safearray functions to maniplate it.
//=--------------------------------------------------------------------------=
// NOTES: 9.95 - this collection assumes that the safearray lbound is
//        zero!
//=--------------------------------------------------------------------------=
//
class CStringCollection {

  public:
    // a couple of methods that are common
    //
    STDMETHOD(get_Count)(THIS_ long FAR* pcStrings);
    STDMETHOD(get_Item)(THIS_ long lIndex, BSTR FAR* pbstrItem);
    STDMETHOD(get__NewEnum)(THIS_ IUnknown * FAR* ppUnkNewEnum);

    CStringCollection(SAFEARRAY *);
    virtual ~CStringCollection();

  protected:
    // what the collection will work with.
    //
    SAFEARRAY *m_psa;
};

class CStringDynaCollection : public CStringCollection {

  public:
    // in addition to the CStringCollection methods, we'll have
    //
    STDMETHOD(put_Item)(THIS_ long lIndex, BSTR bstrItem);
    STDMETHOD(Add)(THIS_ BSTR bstrNew);
    STDMETHOD(Remove)(THIS_ long lIndex);

    CStringDynaCollection(SAFEARRAY *);
    virtual ~CStringDynaCollection();

};

#define _STRCOLL_H_
#endif // _STRCOLL_H_


UNKNOWN.H

//=--------------------------------------------------------------------------=
// Unknown.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// a class definition for an IUnknown super-class that will support
// aggregation.
//
#ifndef _UNKNOWN_H_

//=--------------------------------------------------------------------------=
// UNKNOWNOBJECTINFO
//
// if you want a simple co-creatable object, with no other guarantees about
// it, then you need to put the following entry in the global table of objects.
// other object types that are more complex, such as automation objects, and
// controls, will also use this information...
//
typedef struct {

    const CLSID *rclsid;                    // CLSID of your object.      ONLY USE IF YOU'RE CoCreatable!
    LPCSTR       pszObjectName;             // Name of your object.       ONLY USE IF YOU'RE CoCreatable!
    IUnknown    *(*pfnCreate)(IUnknown *);  // pointer to creation fn.    ONLY USE IF YOU'RE CoCreatable!

} UNKNOWNOBJECTINFO;

#define NAMEOFOBJECT(index)       (((UNKNOWNOBJECTINFO *)(g_ObjectInfo[(index)]).pInfo)->pszObjectName)
#define CLSIDOFOBJECT(index)      (*(((UNKNOWNOBJECTINFO *)(g_ObjectInfo[(index)]).pInfo)->rclsid))
#define CREATEFNOFOBJECT(index)   (((UNKNOWNOBJECTINFO *)(g_ObjectInfo[(index)]).pInfo)->pfnCreate)

#ifndef INITOBJECTS

#define DEFINE_UNKNOWNOBJECT(name, clsid, objname, fn) \
extern UNKNOWNOBJECTINFO name##Object \

#else
#define DEFINE_UNKNOWNOBJECT(name, clsid, objname, fn) \
    UNKNOWNOBJECTINFO name##Object = { clsid, objname, fn } \

#endif // INITOBJECTS

//=--------------------------------------------------------------------------=
// DECLARE_STANDARD_UNKNOWN
//
// All objects that are going to inherit from CUnknown for their IUnknown
// implementation should put this in their class declaration instead of the
// three IUnknown methods.
//
#define DECLARE_STANDARD_UNKNOWN() \
    STDMETHOD(QueryInterface)(REFIID riid, void **ppvObjOut) { \
        return ExternalQueryInterface(riid, ppvObjOut); \
    } \
    STDMETHOD_(ULONG, AddRef)(void) { \
        return ExternalAddRef(); \
    } \
    STDMETHOD_(ULONG, Release)(void) { \
        return ExternalRelease(); \
    } \

// global variable where we store the current lock count on our DLL.  This resides
// in InProcServer.Cpp
//
extern LONG g_cLocks;

//=--------------------------------------------------------------------------=
// this class doesn't inherit from IUnknown since people inheriting from it
// are going to do so, and just delegate their IUnknown calls to the External*
// member functions on this object.  the internal private unknown object does
// need to inherit from IUnknown, since it will be used directly as an IUnknown
// object.
//
class CUnknownObject {

  public:
    CUnknownObject(IUnknown *pUnkOuter, void *pvInterface)
        : m_pvInterface(pvInterface),
          m_pUnkOuter((pUnkOuter) ? pUnkOuter : &m_UnkPrivate)
        {  InterlockedIncrement(&g_cLocks); }

    virtual ~CUnknownObject() { InterlockedDecrement(&g_cLocks); }

    // these are all protected so that classes that inherit from this can
    // at get at them.
    //
  protected:
    // IUnknown methods.  these just delegate to the controlling
    // unknown.
    //
    HRESULT ExternalQueryInterface(REFIID riid, void **ppvObjOut) {
        return m_pUnkOuter->QueryInterface(riid, ppvObjOut);
    }
    ULONG ExternalAddRef(void) {
        return m_pUnkOuter->AddRef();
    }
    ULONG ExternalRelease(void) {
        return m_pUnkOuter->Release();
    }

    // people should use this during creation to return their private
    // unknown
    //
    inline IUnknown *PrivateUnknown (void) {
        return &m_UnkPrivate;
    }

    virtual HRESULT InternalQueryInterface(REFIID riid, void **ppvObjOut);

    IUnknown *m_pUnkOuter;            // outer controlling Unknown
    void     *m_pvInterface;          // the real interface we're working with.

  private:
    // the inner, private unknown implementation is for the aggregator
    // to control the lifetime of this object, and for those cases where
    // this object isn't aggregated.
    //
    class CPrivateUnknownObject : public IUnknown {
      public:
        STDMETHOD(QueryInterface)(REFIID riid, void **ppvObjOut);
        STDMETHOD_(ULONG, AddRef)(void);
        STDMETHOD_(ULONG, Release)(void);

        // constructor is remarkably trivial
        //
        CPrivateUnknownObject() : m_cRef(1) {}

      private:
        CUnknownObject *m_pMainUnknown();
        ULONG m_cRef;
    } m_UnkPrivate;

    // so they can reference themselves in CUnknownObject from pMainUnknown()
    //
    friend class CPrivateUnknownObject;

    // by overriding this, people inheriting from this unknown can implement
    // additional interfaces.  declared as private here so they have to use their
    // own version.
    //
};

#define _UNKNOWN_H_
#endif // _UNKNOWN_H_


UTIL.H

//=--------------------------------------------------------------------------=
// Util.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// contains utilities that we will find useful.
//
#ifndef _UTIL_H_

#include "Globals.H"

//=--------------------------------------------------------------------------=
// Misc Helper Routines
//=--------------------------------------------------------------------------=
//
HWND      GetParkingWindow(void);
HINSTANCE GetResourceHandle(void);

//=--------------------------------------------------------------------------=
// miscellaneous [useful] numerical constants
//=--------------------------------------------------------------------------=
// the length of a guid once printed out with -'s, leading and trailing bracket,
// plus 1 for NULL
//
#define GUID_STR_LEN    40

//=--------------------------------------------------------------------------=
// allocates a temporary buffer that will disappear when it goes out of scope
// NOTE: be careful of that -- make sure you use the string in the same or
// nested scope in which you created this buffer. people should not use this
// class directly.  use the macro(s) below.
//
class TempBuffer {
  public:
    TempBuffer(ULONG cBytes) {
        m_pBuf = (cBytes <= 120) ? &m_szTmpBuf : HeapAlloc(g_hHeap, 0, cBytes);
        m_fHeapAlloc = (cBytes > 120);
    }
    ~TempBuffer() {
        if (m_pBuf && m_fHeapAlloc) HeapFree(g_hHeap, 0, m_pBuf);
    }
    void *GetBuffer() {
        return m_pBuf;
    }

  private:
    void *m_pBuf;
    // we'll use this temp buffer for small cases.
    //
    char  m_szTmpBuf[120];
    unsigned m_fHeapAlloc:1;
};

//=--------------------------------------------------------------------------=
// string helpers.
//
// given and ANSI String, copy it into a wide buffer.
// be careful about scoping when using this macro!
//
// how to use the below two macros:
//
//  ...
//  LPSTR pszA;
//  pszA = MyGetAnsiStringRoutine();
//  MAKE_WIDEPTR_FROMANSI(pwsz, pszA);
//  MyUseWideStringRoutine(pwsz);
//  ...
//
// similarily for MAKE_ANSIPTR_FROMWIDE.  note that the first param does not
// have to be declared, and no clean up must be done.
//
#define MAKE_WIDEPTR_FROMANSI(ptrname, ansistr) \
    long __l##ptrname = (lstrlen(ansistr) + 1) * sizeof(WCHAR); \
    TempBuffer __TempBuffer##ptrname(__l##ptrname); \
    MultiByteToWideChar(CP_ACP, 0, ansistr, -1, (LPWSTR)__TempBuffer##ptrname.GetBuffer(), __l##ptrname); \
    LPWSTR ptrname = (LPWSTR)__TempBuffer##ptrname.GetBuffer()

//
// Note: allocate lstrlenW(widestr) * 2 because its possible for a UNICODE
// character to map to 2 ansi characters this is a quick guarantee that enough
// space will be allocated.
//
#define MAKE_ANSIPTR_FROMWIDE(ptrname, widestr) \
    long __l##ptrname = (lstrlenW(widestr) + 1) * 2 * sizeof(char); \
    TempBuffer __TempBuffer##ptrname(__l##ptrname); \
    WideCharToMultiByte(CP_ACP, 0, widestr, -1, (LPSTR)__TempBuffer##ptrname.GetBuffer(), __l##ptrname, NULL, NULL); \
    LPSTR ptrname = (LPSTR)__TempBuffer##ptrname.GetBuffer()

#define STR_BSTR   0
#define STR_OLESTR 1
#define BSTRFROMANSI(x)    (BSTR)MakeWideStrFromAnsi((LPSTR)(x), STR_BSTR)
#define OLESTRFROMANSI(x)  (LPOLESTR)MakeWideStrFromAnsi((LPSTR)(x), STR_OLESTR)
#define BSTRFROMRESID(x)   (BSTR)MakeWideStrFromResourceId(x, STR_BSTR)
#define OLESTRFROMRESID(x) (LPOLESTR)MakeWideStrFromResourceId(x, STR_OLESTR)
#define COPYOLESTR(x)      (LPOLESTR)MakeWideStrFromWide(x, STR_OLESTR)
#define COPYBSTR(x)        (BSTR)MakeWideStrFromWide(x, STR_BSTR)

LPWSTR MakeWideStrFromAnsi(LPSTR, BYTE bType);
LPWSTR MakeWideStrFromResourceId(WORD, BYTE bType);
LPWSTR MakeWideStrFromWide(LPWSTR, BYTE bType);

// takes a GUID, and a pointer to a buffer, and places the string form of the
// GUID in said buffer.
//
int StringFromGuidA(REFIID, LPSTR);

//=--------------------------------------------------------------------------=
// registry helpers.
//
// takes some information about an Automation Object, and places all the
// relevant information about it in the registry.
//
BOOL RegSetMultipleValues(HKEY hkey, ...);
BOOL RegisterUnknownObject(LPCSTR pszObjectName, REFCLSID riidObject);
BOOL RegisterAutomationObject(LPCSTR pszLibName, LPCSTR pszObjectName, long lVersion, REFCLSID riidLibrary, REFCLSID riidObject);
BOOL RegisterControlObject(LPCSTR pszLibName, LPCSTR pszObjectName, long lVersion, REFCLSID riidLibrary, REFCLSID riidObject, DWORD dwMiscStatus, WORD wToolboxBitmapId);
BOOL UnregisterUnknownObject(REFCLSID riidObject);
BOOL UnregisterAutomationObject(LPCSTR pszLibName, LPCSTR pszObjectName, long lVersion, REFCLSID riidObject);
#define UnregisterControlObject UnregisterAutomationObject
BOOL UnregisterTypeLibrary(REFCLSID riidLibrary);

// deletes a key in the registr and all of it's subkeys
//
BOOL DeleteKeyAndSubKeys(HKEY hk, LPSTR pszSubKey);

//=--------------------------------------------------------------------------=
// conversion helpers.
//
void        HiMetricToPixel(const SIZEL *pSizeInHiMetric, SIZEL *pSizeinPixels);
void        PixelToHiMetric(const SIZEL *pSizeInPixels, SIZEL *pSizeInHiMetric);

#define _UTIL_H_
#endif // _UTIL_H_


VC41WARN.H

//=--------------------------------------------------------------------------=
// VC41Warn.h
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// disables several new VC4.1 warnings that trip standard headers
//

// VC4.1 warning about bool
#pragma warning(disable:4237)

// VC4.1 warning - anachronism used
#pragma warning(disable:4229)


README.TXT

Before building the ToDoSvr sample, you must run setenv.bat to make
sure the Platform SDK include, bin, and lib directories are included
first in your environment.

ToDoSvr depends on the FrameWrk sample, which must be built first. To
build a debug version of the framework library, use the command line
   nmake
from the FrameWrk directory. To build a retail version of the framework,
use the command line
   nmake nodebug=1
from the FrameWrk directory. Libraries are placed in the ..\Lib directory.

After building the FrameWrk library, you can build the ToDoSvr control
by moving to the ToDoSvr directory and entering the command
   nmake
to build a debug version, or
   nmake nodebug=1
to build a retail version.

The ToDoSvr sample will be automatically registered as part of the
build process.

ToDoSvr is a simple example of a combination OLE Control and Document Object
server based on the OLE Controls Framework sample code. Generic extensions
to the framework which would apply to any Document Object server are
contained in the class CDocumentObject, which is defined in cdocobj.h. Any
code which is specific to making ToDoSvr act as a Document Object is marked
with a comment containing the word DOCOBJ.

In its current state, ToDoSvr shows nothing more than the minimal amount of
code required to act as a Document Object from within Office Binder. It does
not manage any data and it doesn't display anything.

In its completed form, ToDoSvr will be a simple to-do list manager that can
act as a Document Object within any Document Object container. ToDoSvr will
expose a simple object model which lets you manage to-do information via OLE
automation. In addition to providing hyperlinks to documents you are working
on, ToDoSvr objects will act as hyperlink targets, so that you can easily
jump to particular tasks.


CDOCOBJ.H

//=--------------------------------------------------------------------------=
// CDocObj.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// class declaration for the document object server class.
//
#ifndef __CDOCOBJ_H_
#define __CDOCOBJ_H_

#ifndef __MKTYPLIB__

#include <IPServer.H>
#include <internet.h>

//=--------------------------------------------------------------------------=
// DOCOBJECTINFO
//=--------------------------------------------------------------------------=
// for each DocObject you wish to expose to the programmer/user, you need to
// declare and define one of the following structures.  the first part should
// follow the rules of the CONTROLOBJECTINFO structure.
// once this structre is declared/defined, an entry should be put in the
// global g_ObjectInfo table.
//
typedef struct {
    CONTROLOBJECTINFO ControlInfo;            // control, automation and creation information
DWORD             dwMiscDocObj;    // value of DocObject subkey
BOOL              bPrint;    // IPrint interface supported
UINT      nIDszExt;    // resource ID of default file extension
UINT              nIDszFileDescription; // resource ID of file description
} DOCOBJECTINFO;

#ifndef INITOBJECTS

#define DEFINE_DOCOBJECT(name, clsid, progid, fn, ver, riid, pszh, piide, dwcf, dwap, w, szwc, cpp, rgppg, ccv, rgcv, dwmisc, bprint, nidext, niddesc) \
extern DOCOBJECTINFO name##DocObj \

#else
#define DEFINE_DOCOBJECT(name, clsid, progid, fn, ver, riid, pszh, piide, dwcf, dwap, w, szwc, cpp, rgppg, ccv, rgcv, dwmisc, bprint, nidext, niddesc) \
DOCOBJECTINFO name##DocObj = { { { {clsid, progid, fn}, ver, riid, pszh, NULL, 0}, piide, dwcf, dwap, TRUE, FALSE, w, szwc, FALSE, cpp, rgppg, ccv, rgcv, NULL }, dwmisc, bprint, nidext, niddesc }\

#endif // !INITOBJECTS

#define MISCFLAGSOFDOCOBJECT(index)         ((DOCOBJECTINFO *)(g_ObjectInfo[index].pInfo))->dwMiscDocObj
#define PRINTOFDOCOBJECT(index)             ((DOCOBJECTINFO *)(g_ObjectInfo[index].pInfo))->bPrint
#define DEFAULTEXTIDOFDOCOBJECT(index)      ((DOCOBJECTINFO *)(g_ObjectInfo[index].pInfo))->nIDszExt
#define FILEDESCRIPTIONIDOFDOCOBJECT(index) ((DOCOBJECTINFO *)(g_ObjectInfo[index].pInfo))->nIDszFileDescription

// this is the macros you should use to fill in the table.  Note that the name
// must be exactly the same as that used in the global structure you created
// for this object.
//
// if we were adding docobj support right into the framework, we would
// put this definition in localobj.h, and instead of using the OI_CONTROL
// type, we would define a new type (eg OI_DOCOBJ).
//
#define DOCOBJECT(name)    { OI_CONTROL,      (void *)&(name##DocObj) }

//=--------------------------------------------------------------------------=
// CDocumentObject
//=--------------------------------------------------------------------------=
//
// if you don't want internet-aware support, change BASECLASS to COleControl
//
#define BASECLASS   CInternetControl

class CDocumentObject : public BASECLASS, public IOleDocument, public IOleDocumentView
{
public:
    // constructor and destructor
    //
    CDocumentObject(IUnknown* pUnkOuter, int iPrimaryDispatch, void* pMainInterface);
    virtual ~CDocumentObject();

    // IUnknown methods
    //
    DECLARE_STANDARD_UNKNOWN();

    // IOleObject method overrides for DocObject support
    STDMETHOD(SetClientSite)(IOleClientSite* pClientSite);
    STDMETHOD(DoVerb)(LONG iVerb, LPMSG lpmsg, IOleClientSite  *pActiveSite, LONG lindex,
                                     HWND hwndParent, LPCRECT lprcPosRect);
    STDMETHOD(SetExtent)(DWORD dwDrawAspect, SIZEL *psizel);
    STDMETHOD(Unadvise)(DWORD dwConnection);

    // IOleDocument -- required DocObject server interface
    //
    STDMETHOD(CreateView)(IOleInPlaceSite* pIPSite, IStream* pstm,
                          DWORD dwReserved, IOleDocumentView** ppView);
    STDMETHOD(GetDocMiscStatus)(DWORD* pdwStatus);
    STDMETHOD(EnumViews)(IEnumOleDocumentViews** ppEnum, IOleDocumentView** ppView);

    // IOleDocumentView -- required DocObject server interface
    //
    STDMETHOD(SetInPlaceSite)(IOleInPlaceSite* pIPSite);
    STDMETHOD(GetInPlaceSite)(IOleInPlaceSite** ppIPSite);
    STDMETHOD(GetDocument)(IUnknown** ppunk);
    STDMETHOD(SetRect)(LPRECT prcView);
    STDMETHOD(GetRect)(LPRECT prcView);
    STDMETHOD(SetRectComplex)(LPRECT prcView, LPRECT prcHScroll,
                              LPRECT prcVScroll, LPRECT prcSizeBox);
    STDMETHOD(Show)(BOOL fShow);
    STDMETHOD(UIActivate)(BOOL fUIActivate);
    STDMETHOD(Open)(void);
    STDMETHOD(CloseView)(ULONG ulReserved);
    STDMETHOD(SaveViewState)(LPSTREAM pstm);
    STDMETHOD(ApplyViewState)(LPSTREAM pstm);
    STDMETHOD(Clone)(IOleInPlaceSite* pIPSiteNew, IOleDocumentView** ppViewNew);

    // Override base class implementation...
    virtual HRESULT InternalQueryInterface(REFIID, void**);

protected:
    BOOL              m_fDocObj;
    IOleDocumentSite* m_pDocSite;
    IOleInPlaceSite*  m_pViewSite;

    // Functions to override to save/restore view state
    virtual HRESULT OnSaveViewState(IStream* pstm);
    virtual HRESULT OnApplyViewState(IStream* pstm);

    // Helper function for DocObject activation
    HRESULT ActivateAsDocObject(LONG lVerb);

};

//=--------------------------------------------------------------------------=
// helper functions
//=--------------------------------------------------------------------------=
//
BOOL RegisterDocObject(LPCSTR pszLibName, LPCSTR pszObjectName,
                       REFCLSID riidObject, DWORD dwMiscDocObj,
                   BOOL bPrint, LPCSTR szExt, LPCSTR szDescription);

//=--------------------------------------------------------------------------=
// Debugging macros
//=--------------------------------------------------------------------------=
// handy-dandy TRACE macro is useful for figuring out how docobj containers
// are calling our interface methods
//
#ifdef DEBUG
    #define TRACE(szMsg)    OutputDebugString(szMsg)
#else
    #define TRACE(szMsg)
#endif

#endif __MKTYPLIB__

#endif __CDOCOBJ_H_


DISPIDS.H

//=--------------------------------------------------------------------------=
// Dispids.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// dispids for use in an automation or control object.
//
#ifndef _DISPIDS_H_

//=--------------------------------------------------------------------------=
// for the ToDo control
// properties & methods
//

// events
//

#define _DISPIDS_H_
#endif // _DISPIDS_H_


DOREG.CPP

//=--------------------------------------------------------------------------=
// DOReg.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// implementation of registry manipulation functions for DocObject servers
//

#include "CDocObj.H"
#include <util.h>

// for ASSERT and FAIL
//
SZTHISFILE

//=--------------------------------------------------------------------------=
// RegisterDocObject.
//=--------------------------------------------------------------------------=
// This function writes out some values specific to a DocObject. You should
// call this from your RegisterData function. You don't need to do anything
// special for UnregisterData, because the framework already deletes the
// entire <LibraryName>.<ObjectName> and CLSID\<CLSID> keys.
//
// What we add here:
//
// HKEY_CLASSES_ROOT\<LibraryName>.<ObjectName>\DocObject = <MISCDOCOBJ>
// HKEY_CLASSES_ROOT\CLSID\<CLSID>\DocObject = <MISCDOCOBJ>
// HKEY_CLASSES_ROOT\CLSID\<CLSID>\Printable
// HKEY_CLASSES_ROOT\CLSID\<CLSID>\DefaultExtension = <EXT>, <DESCRIPTION>
//
// Parameters:
//    LPCSTR       - [in] Library Name
//    LPCSTR       - [in] Object Name
//    REFCLSID     - [in] CLSID of the object
//    DWORD        - [in] misc DocObject status flags
//    BOOL         - [in] whether IPrint is supported
//    LPCSTR       - [in] default file extension
//    LPCSTR       - [in] file description for File Open, File Save As
//
// Output:
//    BOOL
//
// Notes:
//    - not the most terribly efficient routine.
//
BOOL RegisterDocObject(LPCSTR pszLibName, LPCSTR pszObjectName,
                       REFCLSID riidObject, DWORD dwMiscDocObj,
                   BOOL bPrint, LPCSTR szExt, LPCSTR szDescription)
{
    HKEY    hk, hkSub = NULL;
    char    szTmp[MAX_PATH];
    char    szGuidStr[GUID_STR_LEN];
    DWORD   dwDummy;
    LONG    l;

    // HKEY_CLASSES_ROOT\<LibraryName>.<ObjectName>\DocObject = <MISCDOCOBJ>
    //
    lstrcpy(szTmp, pszLibName);
    lstrcat(szTmp, ".");
    lstrcat(szTmp, pszObjectName);

    l = RegCreateKeyEx(HKEY_CLASSES_ROOT, szTmp, 0L, "",
                       REG_OPTION_NON_VOLATILE, KEY_READ | KEY_WRITE,
                       NULL, &hk, &dwDummy);
    CLEANUP_ON_ERROR(l);

    l = RegCreateKeyEx(hk, "DocObject", 0L, "", REG_OPTION_NON_VOLATILE,
                       KEY_READ | KEY_WRITE, NULL, &hkSub, &dwDummy);
    CLEANUP_ON_ERROR(l);

    wsprintf(szTmp, "%d", dwMiscDocObj);
    l = RegSetValueEx(hkSub, NULL, 0, REG_SZ, (BYTE *)szTmp, lstrlen(szTmp) + 1);
    CLEANUP_ON_ERROR(l);
    RegCloseKey(hkSub);
    RegCloseKey(hk);

    // HKEY_CLASSES_ROOT\CLSID\<CLSID>\
    //
    StringFromGuidA(riidObject, szGuidStr);
    wsprintf(szTmp, "CLSID\\%s", szGuidStr);
    l = RegOpenKeyEx(HKEY_CLASSES_ROOT, szTmp, 0, KEY_ALL_ACCESS, &hk);
    if (l != ERROR_SUCCESS) return FALSE;

    // create the Printable key, if printing supported.
    //
if (bPrint)
{
       l = RegCreateKeyEx(hk, "Printable", 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkSub, &dwDummy);
       CLEANUP_ON_ERROR(l);
       RegCloseKey(hkSub);
       hkSub = NULL;
}

    // now set up the DocObject key...
    //
    l = RegCreateKeyEx(hk, "DocObject", 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkSub, &dwDummy);
    CLEANUP_ON_ERROR(l);

    wsprintf(szTmp, "%d", dwMiscDocObj);
    l = RegSetValueEx(hkSub, NULL, 0, REG_SZ, (BYTE *)szTmp, lstrlen(szTmp) + 1);
    CLEANUP_ON_ERROR(l);

    RegCloseKey(hkSub);
hkSub = NULL;

    // now set up the DefaultExtension key
    //
    l = RegCreateKeyEx(hk, "DefaultExtension", 0, "", REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hkSub, &dwDummy);
    CLEANUP_ON_ERROR(l);

    wsprintf(szTmp, "%s,%s", szExt, szDescription);
    l = RegSetValueEx(hkSub, NULL, 0, REG_SZ, (BYTE *)szTmp, lstrlen(szTmp) + 1);
    CLEANUP_ON_ERROR(l);

  CleanUp:
    if (hk)
        RegCloseKey(hk);
    if (hkSub)
        RegCloseKey(hkSub);

    return (l == ERROR_SUCCESS) ? TRUE : FALSE;
}


DWINVERS.H

#define vszMakeDate    "April 10, 1996\0"
#define vszMakeVers    "Version 2810 - DEBUG - ntfs-f\0\0"
#define vszVersNum     "2810\0"
#define vszVersNumAll  "5.00.2810\0"
#define vusVersNuml2   10
#define vusVersNumf2   28
#define vszCopyright   "Copyright 1987 - 1998 Microsoft Corp.\0\0"
#define VBA_VERHI      0x50000
#define VBA_VERLO      0x183C


GUIDS.CPP

//=--------------------------------------------------------------------------=
// Guids.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// contains the guids we will define ourselves.
//

#define INITGUID                   // define all the guids.
#include "IPServer.H"

#include "Guids.H"
#include "olectl.h"

#include "ToDoSvrIfc.H"


GUIDS.H

//=--------------------------------------------------------------------------=
// Guids.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// guids that we define locally
//
#ifndef _GUIDS_H_

// for each property page this server will have, put the guid definition for it
// here so that it gets defined ...
//
// {A5CBEE44-6C2C-11cf-BCFD-00AA00C14806}
DEFINE_GUID(CLSID_ToDoGeneralPage,
0xa5cbee44, 0x6c2c, 0x11cf, 0xbc, 0xfd, 0x0, 0xaa, 0x0, 0xc1, 0x48, 0x6);

#define _GUIDS_H_
#endif // _GUIDS_H_


IODOC.CPP

//=--------------------------------------------------------------------------=
// IODoc.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// implementation of the IOleDocument interface for CDocumentObject.
//
#include "CDocObj.H"

// for ASSERT and FAIL
//
SZTHISFILE

//=--------------------------------------------------------------------------=
// CDocumentObject::CreateView[IOleDocument]
//=--------------------------------------------------------------------------=
// asks the document object to create a new view sub-object
//
// Parameters:
//    IOleInPlaceSite *   - [in] pointer to container's view site object
//    IStream*            - [in] pointer to stream to initialize view with
//    DWORD               - [in] reserved
//    IOleDocumentView*   - [out] address to put interface pointer to new view
//
// Output:
//    HRESULT             - S_OK, E_POINTER, E_OUTOFMEMORY, E_UNEXPECTED,
//                          E_FAIL
//
// Notes:
//      This implementation only supports a single view!
//
STDMETHODIMP CDocumentObject::CreateView(IOleInPlaceSite* pIPSite, IStream* pstm,
                                         DWORD dwReserved, IOleDocumentView** ppView)
{
    TRACE("\nEntering CDocumentObject::CreateView");

    *ppView = NULL;

    if (dwReserved != 0 || m_pDocSite == NULL)
        return E_UNEXPECTED;

    // We only support a single view...so if view site is already
    // set, fail.
    if (m_pViewSite != NULL)
        return E_FAIL;

    // Otherwise, connect the view site to our object
    IOleDocumentView* pView = NULL;
    InternalQueryInterface(IID_IOleDocumentView, (void**)&pView);
    ASSERT(pView != NULL, "object must support IID_IOleDocumentView");

    HRESULT hr = pView->SetInPlaceSite(pIPSite);
    if (SUCCEEDED(hr))
    {
        // Return the IOleDocumentView pointer
        *ppView = pView;

        // If a saved view state is provided, restore the view state.
        if (pstm)
            hr = pView->ApplyViewState(pstm);
    }
else
        pView->Release();

    TRACE("\nLeaving CDocumentObject::CreateView");

    return hr;

}

//=--------------------------------------------------------------------------=
// CDocumentObject::GetDocMiscStatus[IOleDocument]
//=--------------------------------------------------------------------------=
// returns miscellaneous status bits describing the document object
//
// Parameters:
//    DWORD *   - [out] address to put misc status bits in
//
// Output:
//    HRESULT   - S_OK, E_POINTER
//
// Notes:
//
STDMETHODIMP CDocumentObject::GetDocMiscStatus(DWORD* pdwStatus)
{
    TRACE("\nCDocumentObject::GetDocMiscStatus");

if (pdwStatus)
{
        // m_ObjectType (from base class CAutomationObject) is the
        // index into the global object table...
*pdwStatus = MISCFLAGSOFDOCOBJECT(m_ObjectType);
return S_OK;
}
else
return E_POINTER;
}

//=--------------------------------------------------------------------------=
// CDocumentObject::EnumViews[IOleDocument]
//=--------------------------------------------------------------------------=
// creates an enumerator object that enumerates the views of the document object
//
// Parameters:
//    IEnumOleDocumentViews**   - [out] address to put enumerator interface
//    IOleDocumentView**        - [out] address to store single view interface
//
// Output:
//    HRESULT             - S_OK, E_POINTER, E_OUTOFMEMORY
//
// Notes:
//      This implementation only supports a single view!
//
STDMETHODIMP CDocumentObject::EnumViews(IEnumOleDocumentViews** ppEnum,
                                        IOleDocumentView** ppView)
{
    TRACE("\nCDocumentObject::EnumViews");

    if (!ppEnum || !ppView)
        return E_POINTER;

// Retrieve pointer to our single view
    *ppEnum = NULL;
return InternalQueryInterface(IID_IOleDocumentView, (void**)ppView);
}


IODOCVW.CPP

//=--------------------------------------------------------------------------=
// IODocVw.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// implementation of the IOleDocumentView interface for CDocumentObject
//
#include "CDocObj.H"

// for ASSERT and FAIL
//
SZTHISFILE

//=--------------------------------------------------------------------------=
// CDocumentObject::SetInPlaceSite[IOleDocument]
//=--------------------------------------------------------------------------=
// associates a view site with this view
//
// Parameters:
//    IOleInPlaceSite *   - [in] pointer to container's view site object
//
// Output:
//    HRESULT             - S_OK, E_FAIL
//
// Notes:
//
STDMETHODIMP CDocumentObject::SetInPlaceSite(IOleInPlaceSite* pIPSite)
{
    TRACE("\nCDocumentObject::SetInPlaceSite");

    // if view already has an associated site,
    //    call IOleInPlaceObject::InPlaceDeactivate and release the site
    if (m_pViewSite)
    {
        InPlaceDeactivate();
        QUICK_RELEASE(m_pViewSite);
    }

    // remember the new view site
    m_pViewSite = pIPSite;
    ADDREF_OBJECT(m_pViewSite);

    return S_OK;
}

//=--------------------------------------------------------------------------=
// CDocumentObject::GetInPlaceSite[IOleDocument]
//=--------------------------------------------------------------------------=
// retrieves the view site associated with this view
//
// Parameters:
//    IOleInPlaceSite**   - [out] location to return pointer to container's
//                                view site object
//
// Output:
//    HRESULT             - S_OK, E_FAIL
//
// Notes:
//
STDMETHODIMP CDocumentObject::GetInPlaceSite(IOleInPlaceSite** ppIPSite)
{
    TRACE("\nCDocumentObject::GetInPlaceSite");

    ADDREF_OBJECT(m_pViewSite);
    *ppIPSite = m_pViewSite;
    return S_OK;
}

//=--------------------------------------------------------------------------=
// CDocumentObject::GetDocument[IOleDocument]
//=--------------------------------------------------------------------------=
// retrieves the document associated with this view
//
// Parameters:
//    IUnknown**   - [out] location to return pointer to our object
//
// Output:
//    HRESULT      - S_OK
//
// Notes:
//
STDMETHODIMP CDocumentObject::GetDocument(IUnknown** ppunk)
{
    TRACE("\nCDocumentObject::GetDocument");
    return InternalQueryInterface(IID_IUnknown, (void**)ppunk);
}

//=--------------------------------------------------------------------------=
// CDocumentObject::SetRect[IOleDocument]
//=--------------------------------------------------------------------------=
// sets the coordinates of the view port for this view
//
// Parameters:
//    LPRECT   - [in] coordinates of viewpoint in client coordinates of the
//                    view window
//
// Output:
//    HRESULT  - S_OK, E_FAIL
//
// Notes:
//
STDMETHODIMP CDocumentObject::SetRect(LPRECT prcView)
{
    TRACE("\nCDocumentObject::SetRect");

    // SetObjectRects expects object extent to be set, but
    // DocObjects ignore SetExtent. Set m_Size to the size
    // specified by the view.
    m_Size.cx = prcView->right - prcView->left;
    m_Size.cy = prcView->bottom - prcView->top;

    HRESULT hr = SetObjectRects(prcView, prcView);
    if (SUCCEEDED(hr))
    {
        // SetObjectRects moves but does not resize window so we will
        // do that here...
        if (m_hwnd)
            SetWindowPos(m_hwnd, 0, 0, 0, m_Size.cx, m_Size.cy,
                         SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
    }
    return hr;
}

//=--------------------------------------------------------------------------=
// CDocumentObject::GetRect[IOleDocument]
//=--------------------------------------------------------------------------=
// retrieves the coordinates of the view port for this view
//
// Parameters:
//    LPRECT   - [out] location to put the coordinates of the viewpoint in
//                     client coordinates of the view window
//
// Output:
//    HRESULT  - S_OK, E_UNEXPECTED
//
// Notes:
//
STDMETHODIMP CDocumentObject::GetRect(LPRECT prcView)
{
    TRACE("\nCDocumentObject::GetRect");
    if (prcView)
    {
        prcView->top    = 0;
        prcView->left   = 0;
        prcView->bottom = m_Size.cy;
        prcView->right  = m_Size.cx;
        return S_OK;
    }
    else
        return E_UNEXPECTED;
}

//=--------------------------------------------------------------------------=
// CDocumentObject::SetRectComplex[IOleDocument]
//=--------------------------------------------------------------------------=
// sets the coordinates of the view port, scrollbars, and sizebox for this view
//
// Parameters:
//    LPRECT   - [in] coordinates of viewpoint in client coordinates of the
//                    view window
//    LPRECT   - [in] coordinates of horizontal scrollbar in client
//                    coordinates of the view window
//    LPRECT   - [in] coordinates of vertical scrollbar in client coordinates
//                    of the view window
//    LPRECT   - [in] coordinates of sizebox in client coordinates of the
//                    view window
//
// Output:
//    HRESULT  - S_OK, E_FAIL, E_NOTIMPL
//
// Notes:
//      This implementation does not support complex rectangles!
//
STDMETHODIMP CDocumentObject::SetRectComplex(LPRECT prcView, LPRECT prcHScroll,
                                             LPRECT prcVScroll, LPRECT prcSizeBox)
{
    TRACE("\nCDocumentObject::SetRectComplex");
    return E_NOTIMPL;
}

//=--------------------------------------------------------------------------=
// CDocumentObject::Show[IOleDocument]
//=--------------------------------------------------------------------------=
// instructs the view to in-place activate or in-place deactivate itself
//
// Parameters:
//    BOOL     - [in] whether to activate or deactivate
//
// Output:
//    HRESULT  - S_OK, E_OUTOFMEMORY, E_FAIL, E_UNEXPECTED
//
// Notes:
//
STDMETHODIMP CDocumentObject::Show(BOOL fShow)
{
    TRACE("\nEnter CDocumentObject::Show");

    HRESULT hr;
    if (fShow)
    {
        // inplace activate without UI activate & show the view window
        hr = ActivateAsDocObject(OLEIVERB_INPLACEACTIVATE);
    }
    else
    {
        // UI deactivate
        hr = UIActivate(FALSE);

        // hide the view window
        SetInPlaceVisible(FALSE);
    }

    TRACE("\nLeave CDocumentObject::Show");
    return hr;
}

//=--------------------------------------------------------------------------=
// CDocumentObject::UIActivate[IOleDocument]
//=--------------------------------------------------------------------------=
// instructs the view to UI activate or deactivate itself
//
// Parameters:
//    BOOL     - [in] whether to activate or deactivate
//
// Output:
//    HRESULT  - S_OK, E_OUTOFMEMORY, E_FAIL, E_UNEXPECTED
//
// Notes:
//
STDMETHODIMP CDocumentObject::UIActivate(BOOL fUIActivate)
{
    TRACE("\nEnter CDocumentObject::UIActivate");

    HRESULT hr;
    if (fUIActivate)
    {
        // UI activate the view, take focus and bring view window forward
        hr = ActivateAsDocObject(OLEIVERB_UIACTIVATE);
    }
    else
    {
        // UI deactivate
        hr = UIDeactivate();
    }

    TRACE("\nLeave CDocumentObject::UIActivate");
    return hr;
}

//=--------------------------------------------------------------------------=
// CDocumentObject::Open[IOleDocument]
//=--------------------------------------------------------------------------=
// asks the view to display itself in a separate popup window
//
// Parameters:
//    none
//
// Output:
//    HRESULT  - S_OK, E_OUTOFMEMORY, E_FAIL, E_UNEXPECTED, E_NOTIMPL
//
// Notes:
//      This implementation does not support opening a view in a
//      separate window.
//
STDMETHODIMP CDocumentObject::Open(void)
{
    TRACE("\nCDocumentObject::Open");
    return E_NOTIMPL;
}

//=--------------------------------------------------------------------------=
// CDocumentObject::CloseView[IOleDocument]
//=--------------------------------------------------------------------------=
// instructs the view to close down and release its IOleInPlaceSite pointer
//
// Parameters:
//    ULONG    - [in] reserved
//
// Output:
//    HRESULT  - S_OK
//
// Notes:
//
STDMETHODIMP CDocumentObject::CloseView(ULONG ulReserved)
{
    TRACE("\nCDocumentObject::CloseView");

    // hide the view
    Show(FALSE);

    // deactivate the object
    SetInPlaceSite(NULL);

    return S_OK;
}

//=--------------------------------------------------------------------------=
// CDocumentObject::SaveViewState[IOleDocument]
//=--------------------------------------------------------------------------=
// instructs the view to save its state into the given stream
//
// Parameters:
//    IStream* - [in] stream to write to
//
// Output:
//    HRESULT  - S_OK, E_POINTER, E_NOTIMPL
//
// Notes:
//
STDMETHODIMP CDocumentObject::SaveViewState(LPSTREAM pstm)
{
    TRACE("\nCDocumentObject::SaveViewState");

    // Delegate to a virtual function which specific DocObjects can
    // override to save their view state.
    return OnSaveViewState(pstm);
}

//=--------------------------------------------------------------------------=
// CDocumentObject::ApplyViewState[IOleDocument]
//=--------------------------------------------------------------------------=
// instructs the view to restore its state from the given stream
//
// Parameters:
//    IStream* - [in] stream to read from
//
// Output:
//    HRESULT  - S_OK, E_POINTER, E_NOTIMPL
//
// Notes:
//
STDMETHODIMP CDocumentObject::ApplyViewState(LPSTREAM pstm)
{
    TRACE("\nCDocumentObject::ApplyViewState");

    // Delegate to a virtual function which specific DocObjects can
    // override to restore their view state.
    return OnApplyViewState(pstm);
}

//=--------------------------------------------------------------------------=
// CDocumentObject::Clone[IOleDocument]
//=--------------------------------------------------------------------------=
// creates a duplicate view with an identical internal state to the current
// view
//
// Parameters:
//    IOleInPlaceSite*   - [in] pointer to the inplace site for the clone
//
//    IOleDocumentView** - [out] location for the new view pointer
//
// Output:
//    HRESULT  - S_OK, E_POINTER, E_NOTIMPL
//
// Notes:
//      This implementation does not support multiple views, therefore
//      it does not support cloning a view.
//
STDMETHODIMP CDocumentObject::Clone(IOleInPlaceSite* pIPSiteNew,
                                    IOleDocumentView** ppViewNew)
{
    TRACE("\nCDocumentObject::Clone");
    return E_NOTIMPL;
}

//=--------------------------------------------------------------------------=
// CDocumentObject::OnSaveViewState[IOleDocument]
//=--------------------------------------------------------------------------=
// instructs the view to save its state into the given stream
//
// Parameters:
//    IStream* - [in] stream to write to
//
// Output:
//    HRESULT  - S_OK, E_POINTER, E_NOTIMPL
//
// Notes:
//      This function should be overridden by any DocObject which has
//      a view state. OnApplyViewState is the corresponding function
//      which reads the view state back in.
//
HRESULT CDocumentObject::OnSaveViewState(LPSTREAM pstm)
{
    // Default implementation does nothing
    return S_OK;
}

//=--------------------------------------------------------------------------=
// CDocumentObject::OnApplyViewState
//=--------------------------------------------------------------------------=
// instructs the view to restore its state from the given stream
//
// Parameters:
//    IStream* - [in] stream to read from
//
// Output:
//    HRESULT  - S_OK, E_POINTER, E_NOTIMPL
//
// Notes:
//      This function should be overridden by any DocObject which has
//      a view state. OnSaveViewState is the corresponding function
//      which writes the view state out.
//
HRESULT CDocumentObject::OnApplyViewState(LPSTREAM pstm)
{
    // Default implementation does nothing
    return S_OK;
}


LOCALOBJ.H

//=--------------------------------------------------------------------------=
// LocalObj.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// this file is used by automation servers to delcare things that their objects
// need other parts of the server to see.
//
#ifndef _LOCALOBJ_H_

//=--------------------------------------------------------------------------=
// these constants are used in conjunction with the g_ObjectInfo table that
// each inproc server defines.  they are used to identify a given  object
// within the server.
//
// **** ADD ALL NEW OBJECTS TO THIS LIST ****
//

#define OBJECT_TYPE_CTLTODO 0
#define OBJECT_TYPE_PPGTODOGENERAL 1

#define _LOCALOBJ_H_
#endif // _LOCALOBJ_H_


MENU.CPP

//=--------------------------------------------------------------------------=
// Menu.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
#include "CDocObj.H"

// needed for ASSERTs and FAIL
//
SZTHISFILE

// Menu Merging code will go here


CDOCOBJ.CPP

//=--------------------------------------------------------------------------=
// CDocObj.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// implementation of CDocumentObject, which provides basic DocObject server
// support. This file contains overrides of basic inplace editing support.
// See iodoc.cpp and iodocvw.cpp for implementations of required interfaces
// for DocObject server support.
//
#include <IPServer.H>
#include <Globals.H>
#include "CDocObj.H"

// for ASSERT and FAIL
//
SZTHISFILE

//=--------------------------------------------------------------------------=
// CDocumentObject::CDocumentObject
//=--------------------------------------------------------------------------=
//
// Parameters:
//    IUnknown*        - [in]
//    int              - [in]
//    void*            - [in]
//
// Notes:
//
CDocumentObject::CDocumentObject(IUnknown* pUnkOuter, int iPrimaryDispatch,
                                 void* pMainInterface)
   : BASECLASS(pUnkOuter, iPrimaryDispatch, pMainInterface)
{
    m_fDocObj   = FALSE;
    m_pDocSite  = NULL;
    m_pViewSite = NULL;
}

//=--------------------------------------------------------------------------=
// CDocumentObject::~CDocumentObject
//=--------------------------------------------------------------------------=
//
// Notes:
//
CDocumentObject::~CDocumentObject()
{
    ASSERT(m_pDocSite == NULL, "CDocumentObject::m_pDocSite not properly released");
    ASSERT(m_pViewSite == NULL, "CDocumentObject::m_pViewSite not properly released");
}

//=--------------------------------------------------------------------------=
// CDocumentObject::InternalQueryInterface
//=--------------------------------------------------------------------------=
// qi for things only we support.
//
// Parameters:
// Parameters:
//    REFIID       - [in]  interface they want
//    void**       - [out] where they want to put the resulting object ptr.
//
// Output:
//    HRESULT      - S_OK, E_NOINTERFACE
//
// Notes:
//
HRESULT CDocumentObject::InternalQueryInterface(REFIID riid, void** ppvObjOut)
{
    // Check for interfaces we implement
    if (DO_GUIDS_MATCH(riid, IID_IOleDocument))
        *ppvObjOut = (void *)(IOleDocument*)this;
    else if (DO_GUIDS_MATCH(riid, IID_IOleDocumentView))
        *ppvObjOut = (void *)(IOleDocumentView*)this;
    else
        // delegate to super-class for automation interfaces, etc ...
        //
        return BASECLASS::InternalQueryInterface(riid, ppvObjOut);

    // we like the interface, so addref and return
    //
    ((IUnknown *)(*ppvObjOut))->AddRef();
    return S_OK;
}

//=--------------------------------------------------------------------------=
// CDocumentObject::SetClientSite
//=--------------------------------------------------------------------------=
// determines whether object can and should activate as a DocObject
//
// Parameters:
//    IOleClientSite*        - [in] pointer to client site.
//
// Output:
//    HRESULT                - S_OK
//
// Notes:
//
HRESULT CDocumentObject::SetClientSite(IOleClientSite* pClientSite)
{
    TRACE("\nCDocumentObject::SetClientSite");

    // perform regular SetClientSite processing
    HRESULT hr = BASECLASS::SetClientSite(pClientSite);
    if (FAILED(hr))
        return hr;

// If we have a document site pointer,
//    release it and set docobj flag FALSE
RELEASE_OBJECT(m_pDocSite);
    m_fDocObj = FALSE;

// If we got a client site,
if (pClientSite != NULL)
    {
        // If the client site supports IID_IOleDocumentSite,
        //    save the document site pointer
        //    and set docobj flag TRUE
hr = pClientSite->QueryInterface(IID_IOleDocumentSite,
                                         (void**)&m_pDocSite);
        if (SUCCEEDED(hr))
            m_fDocObj = TRUE;
    }
return S_OK;
}

//=--------------------------------------------------------------------------=
// CDocumentObject::DoVerb    [IOleObject]
//=--------------------------------------------------------------------------=
// Requests an object to perform an action in response to an end-user's
// action.
//
// Parameters:
//    LONG             - [in]  verb to be performed
//    LPMSG            - [in]  event that invoked the verb
//    IOleClientSite * - [in]  the controls active client site
//    LONG             - [in]  reserved
//    HWND             - [in]  handle of window containing the object.
//    LPCRECT          - [in]  pointer to objects's display rectangle
//
// Output:
//    HRESULT          - S_OK, OLE_E_NOTINPLACEACTIVE, OLE_E_CANT_BINDTOSOURCE,
//                       DV_E_LINK, OLEOBJ_S_CANNOT_DOVERB_NOW, OLEOBJ_S_INVALIDHWND,
//                       OLEOBJ_E_NOVERBS, OLEOBJ_S_INVALIDVERB, MK_E_CONNECT,
//                       OLE_CLASSDIFF, E_NOTIMPL
//
// Notes:
//
STDMETHODIMP CDocumentObject::DoVerb
(
    LONG            lVerb,
    LPMSG           pMsg,
    IOleClientSite *pActiveSite,
    LONG            lIndex,
    HWND            hwndParent,
    LPCRECT         prcPosRect
)
{
    TRACE("\nCDocumentObject::DoVerb");

    // if we're acting as a docobj
    if (m_fDocObj)
    {
        switch (lVerb)
        {
        // call IOleDocumentSite::ActivateMe for Show, Open, InPlaceActivate and UIActivate Verbs
        case OLEIVERB_SHOW:
        case OLEIVERB_UIACTIVATE:
        case OLEIVERB_OPEN:
        case OLEIVERB_INPLACEACTIVATE:
            return m_pDocSite->ActivateMe(NULL);

        // return E_INVALIDARG for Hide Verb
        case OLEIVERB_HIDE:
            return E_INVALIDARG;

        // otherwise, just do normal processing
        default:
            break;
        }
    }

    // if we're not acting as a docobj, just do normal processing
    return BASECLASS::DoVerb(lVerb, pMsg, pActiveSite, lIndex,
                             hwndParent, prcPosRect);

}

//=--------------------------------------------------------------------------=
// CDocumentObject::SetExtent    [IOleObject]
//=--------------------------------------------------------------------------=
// Informs the control of how much display space its container has assigned it.
//
// Parameters:
//    DWORD            - [in] which form or 'aspect'  is to be displayed.
//    SIZEL *          - [in] size limit for the control.
//
// Output:
//    HRESULT          - S_OK, E_FAIL, OLE_E_NOTRUNNING
//
// Notes:
//
STDMETHODIMP CDocumentObject::SetExtent(DWORD dwDrawAspect, SIZEL *psizel)
{
    TRACE("\nCDocumentObject::SetExtent");

    // ignore SetExtent calls if we are a docobj
    if (m_fDocObj)
        return S_OK;

    // otherwise do normal processing
    return BASECLASS::SetExtent(dwDrawAspect, psizel);
}

//=--------------------------------------------------------------------------=
// CDocumentObject::Unadvise    [IOleObject]
//=--------------------------------------------------------------------------=
// Deletes a previously established advisory connection.
//
// Parameters:
//    DWORD         - [in] connection cookie
//
// Output:
//    HRESULT       - S_OK, E_FAIL, OLE_E_NOCONNECTION
//
// Notes:
//      This override is included to work around a problem observed
//      interacting with Office Binder. When activated as a docobj,
//      IOleObject::Advise is not getting called. However, when deactivated,
//      IOleObject::Unadvise *is* called. Since no advise holder was ever
//      created, the base class method asserts.
//
//      All this function does is prevent the assert from getting hit.
//
STDMETHODIMP CDocumentObject::Unadvise(DWORD dwConnection)
{
    if (!dwConnection) {
        TRACE("\nSomebody called Unadvise on IOleObject without calling Advise!");
        return OLE_E_NOCONNECTION;
    }

    return BASECLASS::Unadvise(dwConnection);
}

//=--------------------------------------------------------------------------=
// CDocumentObject::ActivateAsDocObject
//=--------------------------------------------------------------------------=
// activates a document object, and depending on the verb, optionally
// ui activates it as well.
//
// Parameters:
//    LONG         - [in] the verb that caused us to activate
//
// Output:
//    HRESULT      - S_OK, E_UNEXPECTED
//
// Notes:
//      This implementation is based on COleControl::InPlaceActivate.
//      COleControl::InPlaceActivate is called when a CDocumentObject
//      is being used as a control or normal embedding, not this function.
//
HRESULT CDocumentObject::ActivateAsDocObject(LONG lVerb)
{
    TRACE("\nEntering CDocumentObject::ActivateAsDocObject");

    HRESULT hr = S_OK;

    // if our view site has not been set, report an error
    if (!m_pViewSite)
    {
        hr = E_UNEXPECTED;
        goto LeaveMethod;
    }

    // if we don't have a client site, then there's not much to do.
    //
    if (!m_pClientSite)
        goto LeaveMethod;

    // get an InPlaceSite pointer
    //
    if (!m_pInPlaceSite) {
        hr = m_pClientSite->QueryInterface(IID_IOleInPlaceSite, (void **)&m_pInPlaceSite);
        if (FAILED(hr))
            goto LeaveMethod;
    }

    // if we're not already active, go and do it.
    //
    if (!m_fInPlaceActive) {
        OLEINPLACEFRAMEINFO InPlaceFrameInfo;
        RECT rcPos, rcClip;

        // ask for permission and notify the container we're going active.
        //
        hr = m_pInPlaceSite->CanInPlaceActivate();
        if (hr != S_OK)
        {
            if (FAILED(hr))
                hr = E_FAIL;
            goto LeaveMethod;
        }

        hr = m_pInPlaceSite->OnInPlaceActivate();
        if (FAILED(hr))
            goto LeaveMethod;

        m_fInPlaceActive = TRUE;
        InPlaceFrameInfo.cb = sizeof(OLEINPLACEFRAMEINFO);

        // get our parent window
        //
        hr = m_pInPlaceSite->GetWindow(&m_hwndParent);
        if (!FAILED(hr))
            // get our display and clipping rectangles
            //
            hr = m_pInPlaceSite->GetWindowContext(&m_pInPlaceFrame, &m_pInPlaceUIWindow,
                                                  &rcPos, &rcClip, &InPlaceFrameInfo);
        if (FAILED(hr)) {
            InPlaceDeactivate();
            goto LeaveMethod;
        }

        // make sure we'll display ourselves in the correct location with the correct size
        //    SetObjectRects expects object extent to be set, but
  //    DocObjects ignore SetExtent. Set m_Size to the size
  //    specified for the view.
  m_Size.cx = rcPos.right - rcPos.left;
  m_Size.cy = rcPos.bottom - rcPos.top;
        SetObjectRects(&rcPos, &rcClip);

        // create the window, and display it.
        //
        CreateInPlaceWindow(rcPos.left, rcPos.top, TRUE);
    }

    if (!m_fInPlaceVisible)
        SetInPlaceVisible(TRUE);

    // if we weren't asked to UIActivate, then we're done.
    //
    if (lVerb != OLEIVERB_PRIMARY && lVerb != OLEIVERB_UIACTIVATE)
        goto LeaveMethod;

    // if we're not already UI active, do sow now.
    //
    if (!m_fUIActive) {
        m_fUIActive = TRUE;

        // inform the container of our intent
        //
        m_pInPlaceSite->OnUIActivate();

        // take the focus  [which is what UI Activation is all about !]
        //
        SetFocus(TRUE);

        // set up the active object [us] with the container.
        //
        m_pInPlaceFrame->SetActiveObject((IOleInPlaceActiveObject *)this, NULL);
        if (m_pInPlaceUIWindow)
            m_pInPlaceUIWindow->SetActiveObject((IOleInPlaceActiveObject *)this, NULL);

        // we have to explicitly say we don't wany any border space.
        //
        m_pInPlaceFrame->SetBorderSpace(NULL);
        if (m_pInPlaceUIWindow)
            m_pInPlaceUIWindow->SetBorderSpace(NULL);
    }

LeaveMethod:
    TRACE("\nLeaving CDocumentObject::ActivateAsDocObject");
    return hr;
}


RESOURCE.H

//=--------------------------------------------------------------------------=
// Resource.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// resource IDs.
//
#ifndef _RESOURCE_H_

#define RESID_TOOLBOX_BITMAP 1

//=--------------------------------------------------------------------------=
// Strings
//

// this must be defined for any server that has propety pages.  it must be one
// thousand.
//
#define IDS_PROPERTIES                        1000

// this is defined for all inproc servers that use satellite localization.  it
// must be 1001
//
#define IDS_SERVERBASENAME                    1001

#define IDS_TODO_GENERALPAGETITLE 2003
#define IDS_TODO_GENERALDOCSTRING 2004
#define IDS_TODO_ABOUTBOXVERB     2005

#define IDS_DEFAULTEXT            2006
#define IDS_FILEDESCRIPTION       2007

//=--------------------------------------------------------------------------=
// Dialog Stuff
//
#define IDD_PROPPAGE_TODOGENERAL 2000

#define _RESOURCE_H_
#endif // _RESOURCE_H_


TODOCTL.CPP

//=--------------------------------------------------------------------------=
// ToDoCtl.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
#include "IPServer.H"

#include "Guids.H"
#include "ToDoCtl.H"
#include "LocalObj.H"
#include "Util.H"
#include "Globals.H"
#include "Resource.H"

#include <commctrl.h>

// for ASSERT and FAIL
//
SZTHISFILE

//=--------------------------------------------------------------------------=
// all the events in this control
//
// TODO: add events here ...
//

//=--------------------------------------------------------------------------=
// array describing all of our property pages.  these clsids are typically
// in guids.h
//
// TODO: add any additional property page guids here ...
//
const GUID *rgToDoPropPages [] = {
    &CLSID_ToDoGeneralPage
};

//=--------------------------------------------------------------------------=
// Custum Verb information
//
// TODO: add any custom verbs here in an array, using the VERBINFO structure.
//       then mark the controld def'n in ToDoCtl.H with
//       this verb array
//

//=--------------------------------------------------------------------------=
// CToDoControl::Create
//=--------------------------------------------------------------------------=
// global static function that creates an instance of the control an returns
// an IUnknown pointer for it.
//
// Parameters:
//    IUnknown *        - [in] controlling unknown for aggregation
//
// Output:
//    IUnknown *        - new object.
//
// Notes:
//
IUnknown *CToDoControl::Create(IUnknown *pUnkOuter)
{
    // make sure we return the private unknown so that we support aggegation
    // correctly!
    //
    CToDoControl *pNew = new CToDoControl(pUnkOuter);
    return pNew->PrivateUnknown();
}

//=--------------------------------------------------------------------------=
// CToDoControl::CToDoControl
//=--------------------------------------------------------------------------=
// "Being born is like being kidnapped.  And then sold into slavery."
//    - andy warhol (1928 - 87)
//
// Parameters:
//    IUnknown *        - [in]
//
// Notes:
//
#pragma warning(disable:4355)  // using 'this' in constructor
CToDoControl::CToDoControl(IUnknown* pUnkOuter)
    : CDocumentObject(pUnkOuter, OBJECT_TYPE_CTLTODO, (IDispatch *)this)
{
    // initialize anything here ...
    //
    memset(&m_state, 0, sizeof(TODOCTLSTATE));

    // TODO: initialize anything you need to here.

}
#pragma warning(default:4355)  // using 'this' in constructor

//=--------------------------------------------------------------------------=
// CToDoControl::~CToDoControl
//=--------------------------------------------------------------------------=
// "We all labour against our own cure, for death is the cure of all diseases"
//    - Sir Thomas Browne (1605 - 82)
//
// Notes:
//
CToDoControl::~CToDoControl ()
{
    // TODO: clean up anything here.
}

//=--------------------------------------------------------------------------=
// CToDoControl:RegisterClassData
//=--------------------------------------------------------------------------=
// register the window class information for your control here.
// this information will automatically get cleaned up for you on DLL shutdown.
//
// Output:
//    BOOL            - FALSE means fatal error.
//
// Notes:
//
BOOL CToDoControl::RegisterClassData(void)
{
    WNDCLASS wndclass;

    // subclass a windows ListView control.
    //
    if (!::GetClassInfo(g_hInstance, WC_LISTVIEW, &wndclass))
        return FALSE;

    // this doesn't need a critical section for apartment threading support
    // since it's already in a critical section in CreateInPlaceWindow
    //
    SUBCLASSWNDPROCOFCONTROL(OBJECT_TYPE_CTLTODO) = (WNDPROC)wndclass.lpfnWndProc;
    wndclass.lpfnWndProc = COleControl::ControlWindowProc;
    wndclass.lpszClassName = WNDCLASSNAMEOFCONTROL(OBJECT_TYPE_CTLTODO);

    return RegisterClass(&wndclass);
}

//=--------------------------------------------------------------------------=
// CToDoControl::BeforeCreateWindow
//=--------------------------------------------------------------------------=
// called just before the window is created.  Great place to set up the
// window title, etc, so that they're passed in to the call to CreateWindowEx.
// speeds things up slightly.
//
// Parameters:
//    DWORD *            - [out] dwWindowFlags
//    DWORD *            - [out] dwExWindowFlags
//    LPSTR              - [out] name of window to create
//
// Notes:
//
BOOL CToDoControl::BeforeCreateWindow
(
    DWORD *pdwWindowStyle,
    DWORD *pdwExWindowStyle,
    LPSTR  pszWindowTitle
)
{
    // TODO: users should set the values of *pdwWindowStyle, *pdwExWindowStyle,
    // and pszWindowTitle so that the call to CreateWindowEx can use them. setting
    // them here instead of calling SetWindowStyle in WM_CREATE is a huge perf win
    // if you don't use this function, then you can probably just remove it.
    //
    lstrcpy(pszWindowTitle, "ToDo");
    return TRUE;
}

//=--------------------------------------------------------------------------=
// CToDoControl::InternalQueryInterface
//=--------------------------------------------------------------------------=
// qi for things only we support.
//
// Parameters:
// Parameters:
//    REFIID        - [in]  interface they want
//    void **       - [out] where they want to put the resulting object ptr.
//
// Output:
//    HRESULT       - S_OK, E_NOINTERFACE
//
// Notes:
//
HRESULT CToDoControl::InternalQueryInterface
(
    REFIID  riid,
    void  **ppvObjOut
)
{
    IUnknown *pUnk;

    *ppvObjOut = NULL;

    // TODO: if you want to support any additional interrfaces, then you should
    // indicate that here.  never forget to call the base class version in the
    // case where you don't support the given interface.
    //
    if (DO_GUIDS_MATCH(riid, IID_IToDo)) {
        pUnk = (IUnknown *)(IToDo *)this;
    } else{
        return CDocumentObject::InternalQueryInterface(riid, ppvObjOut);
    }

    pUnk->AddRef();
    *ppvObjOut = (void *)pUnk;
    return S_OK;
}

//=--------------------------------------------------------------------------=
// CToDoControl::LoadTextState
//=--------------------------------------------------------------------------=
// load in our text state for this control.
//
// Parameters:
//    IPropertyBag *        - [in] property bag to read from
//    IErrorLog *           - [in] errorlog object to use with proeprty bag
//
// Output:
//    HRESULT
//
// Notes:
//    - NOTE: if you have a binary object, then you should pass an unknown
//      pointer to the property bag, and it will QI it for IPersistStream, and
//      get said object to do a Load()
//
STDMETHODIMP CToDoControl::LoadTextState
(
    IPropertyBag *pPropertyBag,
    IErrorLog    *pErrorLog
)
{
    // TODO: implement your text load code here.
    //
    return S_OK;
}

//=--------------------------------------------------------------------------=
// CToDoControl::LoadBinaryState
//=--------------------------------------------------------------------------=
// loads in our binary state using streams.
//
// Parameters:
//    IStream *            - [in] stream to write to.
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP CToDoControl::LoadBinaryState
(
    IStream *pStream
)
{
    // TODO: implement your binary load code here.  to prevent this from being
    // a massive performance sink, you should probably try to organize your
    // properties in such a way that enables you to do just one IStream::Read
    // in the LoadBinaryState function.  fast is good.
    //
    return S_OK;
}

//=--------------------------------------------------------------------------=
// CToDoControl::SaveTextState
//=--------------------------------------------------------------------------=
// saves out the text state for this control using a property bag.
//
// Parameters:
//    IPropertyBag *        - [in] the property bag with which to work.
//    BOOL                  - [in] if TRUE, then write out ALL properties, even
//                            if they're their the default value ...
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP CToDoControl::SaveTextState
(
    IPropertyBag *pPropertyBag,
    BOOL          fWriteDefaults
)
{
    // TODO: implement your text save code here.
    //
    return S_OK;
}

//=--------------------------------------------------------------------------=
// CToDoControl::SaveBinaryState
//=--------------------------------------------------------------------------=
// save out the binary state for this control, using the given IStream object.
//
// Parameters:
//    IStream  *             - [in] save to which you should save.
//
// Output:
//    HRESULT
//
// Notes:
//    - it is important that you seek to the end of where you saved your
//      properties when you're done with the IStream.
//
STDMETHODIMP CToDoControl::SaveBinaryState
(
    IStream *pStream
)
{
    // TODO: implement your binary save state code here.  to prevent this from being
    // a massive performance sink, you should probably try to organize your
    // properties in such a way that enables you to do just one IStream::Read
    // in the LoadBinaryState function.  fast is good.
    //
    return S_OK;
}

//=--------------------------------------------------------------------------=
// CToDoControl::OnDraw
//=--------------------------------------------------------------------------=
// "I don't very much enjoy looking at paintings in general.  i know too
//  much about them.  i take them apart."
//    - georgia o'keeffe (1887-1986)
//
// Parameters:
//    DWORD              - [in]  drawing aspect
//    HDC                - [in]  HDC to draw to
//    LPCRECTL           - [in]  rect we're drawing to
//    LPCRECTL           - [in]  window extent and origin for meta-files
//    HDC                - [in]  HIC for target device
//    BOOL               - [in]  can we optimize dc handling?
//
// Output:
//    HRESULT
//
// Notes:
//
HRESULT CToDoControl::OnDraw
(
    DWORD    dvAspect,
    HDC      hdcDraw,
    LPCRECTL prcBounds,
    LPCRECTL prcWBounds,
    HDC      hicTargetDevice,
    BOOL     fOptimize
)
{
    // TODO: put your drawing code here ...
    //
    return DoSuperClassPaint(hdcDraw, prcBounds);
}

//=--------------------------------------------------------------------------=
// CToDoControl::WindowProc
//=--------------------------------------------------------------------------=
// window procedure for this control.  nothing terribly exciting.
//
// Parameters:
//     see win32sdk on window procs.
//
// Notes:
//
LRESULT CToDoControl::WindowProc
(
    UINT   msg,
    WPARAM wParam,
    LPARAM lParam
)
{
    // TODO: handle any messages here, like in a normal window
    // proc.  note that for special keys, you'll want to override and
    // implement OnSpecialKey.
    //
    return CallWindowProc((WNDPROC)(FARPROC)SUBCLASSWNDPROCOFCONTROL(OBJECT_TYPE_CTLTODO), (HWND) m_hwnd, msg, wParam, lParam);
}

//=--------------------------------------------------------------------------=
// CToDoControl::AboutBox
//=--------------------------------------------------------------------------=
// prints up an about box.  fweeeee.
//
// Notes:
//
void CToDoControl::AboutBox
(
    void
)
{
    // TODO: Ideally, one would use DialogBox, and some sort of Dialog Box here if
    // they wanted a slightly more interesting About Box ...  you should
    // still call ModalDialog first, however.
    //
    ModalDialog(TRUE);
    MessageBox(NULL, "Sample DocObject Server", "About ToDo", MB_OK | MB_TASKMODAL);
    ModalDialog(FALSE);
}


TODOCTL.H

//=--------------------------------------------------------------------------=
// ToDoCtl.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// class declaration for the ToDo control.
//
#ifndef _TODOCTL_H_

#include "IPServer.H"

// DOCOBJ: include CDocObj.H for DocObjects instead of CCtrlObj.
#include "CDocObj.H"

#include "ToDoSvrIfc.H"
#include "Dispids.H"

typedef struct tagTODOCTLSTATE {

    // TODO: put state variables here, and probably get rid of fGarbage, unless
    // you have a real need for it
    //
    VARIANT_BOOL fGarbage;

} TODOCTLSTATE;

//=--------------------------------------------------------------------------=
// CToDoControl
//=--------------------------------------------------------------------------=
// our control.
//
// DOCOBJ: use CDocumentObject as the base class instead of COleControl or
//         CInternetControl
//
class CToDoControl : public CDocumentObject, public IToDo, public ISupportErrorInfo {

  public:
    // IUnknown methods
    //
    DECLARE_STANDARD_UNKNOWN();

    // IDispatch methods
    //
    DECLARE_STANDARD_DISPATCH();

    // ISupportErrorInfo methods
    //
    DECLARE_STANDARD_SUPPORTERRORINFO();

    // IToDo methods
    //
    // TODO: copy over the method declarations from ToDoInterfaces.H
    //       don't forget to remove the PURE from them.
    //
    STDMETHOD_(void, AboutBox)(THIS);

    // OLE Control stuff follows:
    //
    CToDoControl(IUnknown *pUnkOuter);
    virtual ~CToDoControl();

    // static creation function.  all controls must have one of these!
    //
    static IUnknown *Create(IUnknown *);

  private:
    // overridables that the control must implement.
    //
    STDMETHOD(LoadBinaryState)(IStream *pStream);
    STDMETHOD(SaveBinaryState)(IStream *pStream);
    STDMETHOD(LoadTextState)(IPropertyBag *pPropertyBag, IErrorLog *pErrorLog);
    STDMETHOD(SaveTextState)(IPropertyBag *pPropertyBag, BOOL fWriteDefault);
    STDMETHOD(OnDraw)(DWORD dvAspect, HDC hdcDraw, LPCRECTL prcBounds, LPCRECTL prcWBounds, HDC hicTargetDev, BOOL fOptimize);
    virtual LRESULT WindowProc(UINT msg, WPARAM wParam, LPARAM lParam);
    virtual BOOL    RegisterClassData(void);

    virtual HRESULT InternalQueryInterface(REFIID, void **);
    virtual BOOL    BeforeCreateWindow(DWORD *pdwWindowStyle, DWORD *pdwExWindowStyle, LPSTR pszWindowTitle);

    // private state information.
    //
    TODOCTLSTATE m_state;
};

// TODO: if you have an array of verbs, then add an extern here with the name
//       of it, so that you can include it in the DEFINE_DOCOBJECT.
//       ie.  extern VERBINFO m_ToDoCustomVerbs [];
//
extern const GUID    *rgToDoPropPages [];

// DOCOBJ: Use DEFINE_DOCOBJECT macro instead of DEFIND_CONTROL macro to
//         set up information about the DocObject. The last 4 items are
//         specific to DocObjects.
//
DEFINE_DOCOBJECT(ToDo,
    &CLSID_ToDo,
    "ToDoCtl",
    CToDoControl::Create,
    1,
    &IID_IToDo,
    "ToDo.HLP",
    &DIID_DToDoEvents,
    OLEMISC_SETCLIENTSITEFIRST|OLEMISC_ACTIVATEWHENVISIBLE|OLEMISC_RECOMPOSEONRESIZE|OLEMISC_CANTLINKINSIDE|OLEMISC_INSIDEOUT,
    0,
    RESID_TOOLBOX_BITMAP,
    "ToDoWndClass",
    1,
    rgToDoPropPages,
    0,
    NULL,
    DOCMISC_CANTOPENEDIT|DOCMISC_NOFILESUPPORT,     // dwDocMiscStatus
    FALSE,                                          // no IPrint support
    IDS_DEFAULTEXT,                                 // resource ID of default file extension
    IDS_FILEDESCRIPTION,                            // resource ID of file description
    );

#define _TODOCTL_H_
#endif // _TODOCTL_H_


TODOPPG.CPP

//=--------------------------------------------------------------------------=
// ToDoPPG.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// property page implementations for ToDo control.
//
#include "IPServer.H"

#include "LocalObj.H"
#include "ToDoPPG.H"
#include "ToDoCtl.H"
#include "Resource.H"
#include "Util.H"

// for ASSERT and FAIL
//
SZTHISFILE

//=--------------------------------------------------------------------------=
// Property Page messages
//=--------------------------------------------------------------------------=
// in addition to regular windows messages you'll receive for a dialog box,
// you'll receive the following messages in your property page implementation:
//
// PPM_NEWOBJECTS:
//    wParam = 0;
//    lParam = (LPARAM)(HRESULT *)&hr
//
//  - in this message, you should call FirstControl() to get a pointer to a
//    control, and initialize the values in the property page dialog with
//    values from the control object.  put results from the operation in
//    the HRESULT pointed to by LPARAM.
//
// PPM_APPLY:
//    wParam = 0;
//    lParam = (LPARAM)(HRESULT *)&hr
//
//  - this message is sent to your dialog whenever the user clicks APPLY or OK
//    in the dialog.  you should have a loop with the following code in it:
//
//      for (pUnk = FirstControl(&dwCookie) ; pUnk; pUnk = NextControl(&dwCookie)) {
//            hr = pUnk->QueryInterface(IID_IMyCtlInterface, (void **)&pMyCtl);
//            // set properties here!!!
//      }
//
//    call PropPageException() if there is an error while setting propertites
//    to show the exception set up by the property set routine.
//
// PPM_EDITPROPERTY:
//    wParam = dispid
//    lParam = (LPARAM)(HRESULT *)&hr
//
//  - sent to your dialog when somebody wants you to set the focus to a specific
//    property [typically, one will see a call to this when one returns a page
//    from IPerPropertyBrowsing::MapPropertyToPage].  you can use this
//    to bring up dialogs, or do whatever flaps your flagella.
//
// PPM_FREEOBJECTS:
//    wParam = 0
//    lParam = (LPARAM)(HRESULT *)&hr
//
// - sent to your dialog when you should free up any objects.  some people will
//   find it interesting to QI and store objects in the PPM_INITOBJECTS message.
//   this message tells the prop page that these objects are no longer valid,
//   and any stashing of objects should be terminated.
//

//=--------------------------------------------------------------------------=
// CToDoGeneralPage::Create
//=--------------------------------------------------------------------------=
// global static creation function.
//
// Parameters:
//    IUnknown *    - [in] controlling unknown
//
// Output:
//    IUnknown *    - new prop page.
//
// Notes:
//
IUnknown *CToDoGeneralPage::Create
(
    IUnknown *pUnkOuter
)
{
    return (IUnknown *)new CToDoGeneralPage(pUnkOuter);
}

//=--------------------------------------------------------------------------=
// CToDoGeneralPage::CToDoGeneralPage
//=--------------------------------------------------------------------------=
// constructor.
//
// Parameters:
//    IUnknown *        - [in] controlling unknown.
//
// Notes:
//
CToDoGeneralPage::CToDoGeneralPage
(
    IUnknown *pUnkOuter
)
: CPropertyPage(pUnkOuter, OBJECT_TYPE_PPGTODOGENERAL)
{
    // initialize local variables here.
}

//=--------------------------------------------------------------------------=
// CToDoGeneralPage::~CToDoGeneralPage
//=--------------------------------------------------------------------------=
// destructor.
//
// Notes:
//
CToDoGeneralPage::~CToDoGeneralPage()
{
    // clean up
}

//=--------------------------------------------------------------------------=
// CToDoGeneralPage::DialogProc
//=--------------------------------------------------------------------------=
// our dialog proc.
//
// Parameters:
//    - see win32sdk docs on DialogProc
//
// Notes:
//
BOOL CToDoGeneralPage::DialogProc
(
    HWND   hwnd,
    UINT   msg,
    WPARAM wParam,
    LPARAM lParam
)
{
    // TODO: add support for your property page here.  this is a normal dialog
    //       proc.  make sure you handle PPM_NEWOBJECTS and PPM_APPLY.
    //
    return FALSE;
}


TODOPPG.H

//=--------------------------------------------------------------------------=
// ToDoPPG.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// class declaration for ToDo's property pages.
//
#ifndef _TODOPPG_H_

// kinda need these
//
#include "PropPage.H"
#include "Resource.H"
#include "Guids.H"

class CToDoGeneralPage : public CPropertyPage {

  public:
    static IUnknown *Create(IUnknown *pUnkOuter);

    // constructor and destructor
    //
    CToDoGeneralPage(IUnknown *pUnkOuter);
    virtual ~CToDoGeneralPage();

  private:
    virtual BOOL DialogProc(HWND, UINT, WPARAM, LPARAM);

};

DEFINE_PROPERTYPAGEOBJECT(ToDoGeneral,
    &CLSID_ToDoGeneralPage,
    "ToDo General Propery Page",
    CToDoGeneralPage::Create,
    IDD_PROPPAGE_TODOGENERAL,
    IDS_TODO_GENERALPAGETITLE,
    IDS_TODO_GENERALDOCSTRING,
    "vb.hlp",
    0);

#define _TODOPPG_H_
#endif // _TODOPPG_H_


TODOSVR.CPP

//=--------------------------------------------------------------------------=
// ToDoSvr.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// various routines et all that aren't in a file for a particular automation
// object, and don't need to be in the generic ole automation code.
//
#define INITOBJECTS                // define the descriptions for our objects

#include "IPServer.H"
#include "LocalSrv.H"

#include "LocalObj.H"
#include "ToDoSvrIfc.H"
#include "CDocObj.H"
#include "Globals.H"
#include "Util.H"
#include "Resource.H"

#include "ToDoCtl.H"
#include "ToDoPPG.H"

// needed for ASSERTs and FAIL
//
SZTHISFILE

//=--------------------------------------------------------------------------=
// our Libid.  This should be the LIBID from the Type library, or NULL if you
// don't have one.
//
const CLSID *g_pLibid = &LIBID_ToDoSvrObjects;

//=--------------------------------------------------------------------------=
// Set this up if you want to have a window proc for your parking window. This
// is really only interesting for Sub-classed controls that want, in design
// mode, certain messages that are sent only to the parent window.
//
WNDPROC g_ParkingWindowProc = NULL;

//=--------------------------------------------------------------------------=
// Localization Information
//
// We need the following two pieces of information:
//    a. whether or not this DLL uses satellite DLLs for localization.  if
//       not, then the lcidLocale is ignored, and we just always get resources
//       from the server module file.
//    b. the ambient LocaleID for this in-proc server.  Controls calling
//       GetResourceHandle() will set this up automatically, but anybody
//       else will need to be sure that it's set up properly.
//
const VARIANT_BOOL g_fSatelliteLocalization = FALSE;
LCID               g_lcidLocale = MAKELCID(LANG_USER_DEFAULT, SORT_DEFAULT);

//=--------------------------------------------------------------------------=
// your license key and where under HKEY_CLASSES_ROOT_LICENSES it's sitting
//
const WCHAR g_wszLicenseKey [] = L"";
const WCHAR g_wszLicenseLocation [] = L"";

//=--------------------------------------------------------------------------=
// This Table describes all the automatible objects in your automation server.
// See AutomationObject.H for a description of what goes in this structure
// and what it's used for.
//
// DOCOBJ: Use DOCOBJECT macro to describe our DocObject, not CONTROLOBJECT
//         as usual.
//
OBJECTINFO g_ObjectInfo[] = {
    DOCOBJECT(ToDo),
    PROPERTYPAGE(ToDoGeneral),
    EMPTYOBJECT
};

const char g_szLibName[] = "ToDoSvr";

//=--------------------------------------------------------------------------=
// InitializeLibrary
//=--------------------------------------------------------------------------=
// called from DllMain:DLL_PROCESS_ATTACH.  allows the user to do any sort of
// initialization they want to.
//
// Notes:
//
void WINAPI InitializeLibrary(void)
{
    // TODO: initialization here.  control window class should be set up in
    // RegisterClassData.
}

//=--------------------------------------------------------------------------=
// UninitializeLibrary
//=--------------------------------------------------------------------------=
// called from DllMain:DLL_PROCESS_DETACH.  allows the user to clean up anything
// they want.
//
// Notes:
//
void WINAPI UninitializeLibrary(void)
{
    // TODO: uninitialization here.  control window class will be unregistered
    // for you, but anything else needs to be cleaned up manually.
    // Please Note that the Window 95 DLL_PROCESS_DETACH isn't quite as stable
    // as NT's, and you might crash doing certain things here ...
}

//=--------------------------------------------------------------------------=
// CheckForLicense
//=--------------------------------------------------------------------------=
// users can implement this if they wish to support Licensing.  otherwise,
// they can just return TRUE all the time.
//
// Parameters:
//    none
//
// Output:
//    BOOL            - TRUE means the license exists, and we can proceed
//                      FALSE means we're not licensed and cannot proceed
//
// Notes:
//    - implementers should use g_wszLicenseKey and g_wszLicenseLocation
//      from the top of this file to define their licensing [the former
//      is necessary, the latter is recommended]
//
BOOL WINAPI CheckForLicense(void)
{
    // TODO: you should make sure the machine has your license key here.
    // this is typically done by looking in the registry.
    //
    return TRUE;
}

//=--------------------------------------------------------------------------=
// CheckLicenseKey
//=--------------------------------------------------------------------------=
// when IClassFactory2::CreateInstanceLic is called, a license key is passed
// in, and then passed on to this routine.  users should return a boolean
// indicating whether it is a valid license key or not
//
// Parameters:
//    LPWSTR          - [in] the key to check
//
// Output:
//    BOOL            - false means it's not valid, true otherwise
//
// Notes:
//
BOOL WINAPI CheckLicenseKey(LPWSTR pwszKey)
{
    // TODO: check the license key against your values here and make sure it's
    // valid.
    //
    return TRUE;
}

//=--------------------------------------------------------------------------=
// GetLicenseKey
//=--------------------------------------------------------------------------=
// returns our current license key that should be saved out, and then passed
// back to us in IClassFactory2::CreateInstanceLic
//
// Parameters:
//    none
//
// Output:
//    BSTR                 - key or NULL if Out of memory
//
// Notes:
//
BSTR WINAPI GetLicenseKey(void)
{
    // TODO: return your license key here.
    //
    return SysAllocString(L"");
}

//=--------------------------------------------------------------------------=
// RegisterData
//=--------------------------------------------------------------------------=
// lets the inproc server writer register any data in addition to that in
// any other objects.
//
// Output:
//    BOOL            - false means failure.
//
// Notes:
//
BOOL WINAPI RegisterData(void)
{
    // DOCOBJ: DocObjects need some additional registry entries.
    //         Register them here!

    // read file extension and file description strings from resources
    char szExt[20];
    if (!LoadString(GetResourceHandle(),
                    DEFAULTEXTIDOFDOCOBJECT(OBJECT_TYPE_CTLTODO),
                    szExt,
                    sizeof(szExt)))
        szExt[0] = '\0';

    char szDesc[40];
    if (!LoadString(GetResourceHandle(),
                    FILEDESCRIPTIONIDOFDOCOBJECT(OBJECT_TYPE_CTLTODO),
                    szDesc,
                    sizeof(szDesc)))
        szDesc[0] = '\0';

    // register special docobj keys
    return RegisterDocObject(g_szLibName, NAMEOFOBJECT(OBJECT_TYPE_CTLTODO),
                             CLSIDOFOBJECT(OBJECT_TYPE_CTLTODO),
                             MISCFLAGSOFDOCOBJECT(OBJECT_TYPE_CTLTODO),
                             PRINTOFDOCOBJECT(OBJECT_TYPE_CTLTODO),
                             szExt, szDesc);
}

//=--------------------------------------------------------------------------=
// UnregisterData
//=--------------------------------------------------------------------------=
// inproc server writers should unregister anything they registered in
// RegisterData() here.
//
// Output:
//    BOOL            - false means failure.
//
// Notes:
//
BOOL WINAPI UnregisterData(void)
{
    // TODO: any additional registry cleanup that you might wish to do.
    //
    return TRUE;
}

//=--------------------------------------------------------------------------=
// CRT stubs
//=--------------------------------------------------------------------------=
// these two things are here so the CRTs aren't needed. this is good.
//
// basically, the CRTs define this to suck in a bunch of stuff.  we'll just
// define them here so we don't get an unresolved external.
//
// TODO: if you are going to use the CRTs, then remove this line.
//
// extern "C" int _fltused = 1;

extern "C" int _purecall(void)
{
  FAIL("Pure virtual function called.");
  return 0;
}


README.TXT

To build WebImage, the FrameWrk sample must first be built. You must
run setenv.bat to make sure the Platform SDK include, bin, and lib
directories are included (first) in your environment. Run the 'nmake'
to build a debug version (built to the Debug subdirectory) or
'nmake nodebug=1' to build a retail (nodebug) directory into the
Retail subdirectory.

After building the FrameWrk library you can build the WebImage
control by running 'nmake' or 'nmake nodebug=1'.


DIBCLS.CPP

//=--------------------------------------------------------------------------=
// DibCls.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// contains the CDibFile and CDibSection classes.
//
#include "IPServer.H"

#include "DibCls.H"

#define WIDTHBYTES(bits)    (((bits) + 31) / 32 * 4)

//=--------------------------------------------------------------------------=
// DIB Utitilty Classes
//=--------------------------------------------------------------------------=
// Not wholey generic but getting there...
//
// Notes:
//
CDibFile::CDibFile()
{
m_headerSize = 0;
m_bmi.p = 0;
}

CDibFile::~CDibFile()
{
if( m_bmi.p )
delete m_bmi.p;
}

DWORD CDibFile::CalcImageSize()
{
DWORD & dw = m_bmi.p->bmiHeader.biSizeImage;
if( dw == 0)
        dw = WIDTHBYTES((DWORD)m_bmi.p->bmiHeader.biWidth *
                m_bmi.p->bmiHeader.biBitCount) * m_bmi.p->bmiHeader.biHeight;

return(dw);
}

HRESULT CDibFile::GetInfoHeader( IStream * strm )
{
HRESULT hr = S_OK; m_bmi.bytes = new unsigned char[ m_headerSize ];

if( !m_bmi.bytes )
hr = E_OUTOFMEMORY;

if( SUCCEEDED(hr) )
hr = strm->Read(m_bmi.bytes,m_headerSize,0);

if( SUCCEEDED(hr) )
CalcImageSize();

return(hr);
}

HRESULT CDibFile::GetFileHeader(IStream * strm)
{
BITMAPFILEHEADERbmfh;

HRESULThr = strm->Read(&bmfh,sizeof(bmfh),0);

if( SUCCEEDED(hr) && (bmfh.bfType != 0x4d42 ))
hr = E_UNEXPECTED;

if( SUCCEEDED(hr) )
m_headerSize = bmfh.bfOffBits - sizeof(bmfh);

return(hr);
}

CDibSection::CDibSection()
{
m_bitsBase= 0;
m_current= 0;
m_memDC= 0;
m_handle=
m_oldBitmap = 0;
m_w=
m_h= 32;  // totally arbitrary
}

CDibSection::~CDibSection()
{
if( m_memDC )
{
if( m_oldBitmap )
::SelectObject( m_memDC, m_oldBitmap );

::DeleteDC(m_memDC);
}

if( m_handle )
::DeleteObject(m_handle);

}

HRESULT CDibSection::Create(CDibFile& dibFile)
{
HRESULThr= S_OK;
BITMAPINFOHEADER *bmih= dibFile;// will convert itself

m_handle = ::CreateDIBSection(
m_memDC,// handle to device context
dibFile,// pointer to structure containing bitmap size,
//format, and color data
DIB_RGB_COLORS,// color data type indicator: RGB values or
//palette indices
(void **)&m_bitsBase,// pointer to variable to receive a pointer
//to the bitmap's bit values
0,// optional handle to a file mapping object
0// offset to the bitmap bit values
//within the file mapping object
);

if( !m_handle )
hr = E_FAIL;

if( SUCCEEDED(hr) )
{
m_oldBitmap = (HBITMAP) ::SelectObject( m_memDC, m_handle );

if( !m_oldBitmap )
hr = E_FAIL;
}

if( SUCCEEDED(hr) )
{
m_current = m_bitsBase;

m_w = bmih->biWidth;
m_h = bmih->biHeight;

if( m_h < 0 )
m_h *= -1;
}

return(hr);
}

HRESULT CDibSection::ReadFrom( IStream * strm, DWORD amount )
{
   DWORD    dwRead      = 0;
   HRESULT  hr;

   do
   {
      hr = strm->Read(m_current, m_imageSize, &dwRead);

      if (SUCCEEDED(hr) || hr == E_PENDING)
          m_current += dwRead;
   }
   while (!(hr == E_PENDING || hr == S_FALSE) && SUCCEEDED(hr));

   return (hr);
}

HRESULT CDibSection::Setup(HDC hdc)
{
m_memDC = ::CreateCompatibleDC(hdc);

return( m_memDC ? NOERROR : E_FAIL );
}

HRESULTCDibSection::PaintTo(HDC hdc, int x, int y)
{
BOOL b = BitBlt(
 hdc,// handle to destination device context
 x,// x-coordinate of destination rectangle's upper-left corner
 y,// x-coordinate of destination rectangle's upper-left corner
 m_w,// width of destination rectangle
 m_h,// height of destination rectangle
 m_memDC,// handle to source device context
 0,// x-coordinate of source rectangle's upper-left corner
 0,// y-coordinate of source rectangle's upper-left corner
 SRCCOPY// raster operation code
);

return( b ? NOERROR : E_FAIL );
}

HRESULTCDibSection::GetSize(SIZEL &sz)
{
sz.cx = m_w;
sz.cy = m_h;

return(S_OK);
}


DIBCLS.H

/////////////////////////////////////////////////////////////////////////////
// DibCls.h
/////////////////////////////////////////////////////////////////////////////
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
/////////////////////////////////////////////////////////////////////////////
//
// contains the prototypes for the CDibFIle and CDibSection classes
//

#ifndef __DIBCLASSES_H
#define __DIBCLASSES_H

//
//class CDibFile
//
class CDibFile
{
public:
CDibFile();
~CDibFile();

HRESULTGetFileHeader(IStream *);
HRESULTGetInfoHeader(IStream *);

DWORDHeaderSize(){ return(m_headerSize); }
DWORDCalcImageSize();

operator BITMAPINFO * (){ return(m_bmi.p); }
operator BITMAPINFOHEADER * (){ return(&m_bmi.p->bmiHeader); }

private:

DWORDm_headerSize;
union
{
BITMAPINFO *p;
unsigned char *bytes;
} m_bmi;
};

class CDibSection
{
public:
CDibSection();
~CDibSection();

HRESULT Create( CDibFile& );
HRESULTSetup( HDC basedOnThisDC);
HRESULT ReadFrom( IStream * strm, DWORD amount );
HRESULTPaintTo( HDC hdc, int x = 0, int y = 0 );
HRESULTGetSize( SIZEL &sz);

DWORDImageSize() { return(m_imageSize); }
voidImageSize(DWORD dw) { m_imageSize = dw; }

operator HANDLE() { return m_handle; }

unsigned char * Base() { return(m_bitsBase); }

private:
unsigned char *m_bitsBase;
unsigned char *m_current;
HDCm_memDC;
HBITMAPm_handle;
HBITMAPm_oldBitmap;
LONGm_h;
LONGm_w;
DWORDm_imageSize;
};

#endif __DIBCLASSES_H


DISPIDS.H

//=--------------------------------------------------------------------------=
// DispIds.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// dispids for use in an automation or control object.
//
#ifndef _DISPIDS_H_

//=--------------------------------------------------------------------------=
// for the WebImage control
// properties & methods
//
#define DISPID_BMPPATH102
#define DISPID_SCRAMBLE 103

// events
//

#define _DISPIDS_H_
#endif // _DISPIDS_H_


DWINVERS.H

#define vszMakeDate    "April 10, 1996\0"
#define vszMakeVers    "Version 2810 - DEBUG - ntfs-f\0\0"
#define vszVersNum     "2810\0"
#define vszVersNumAll  "5.00.2810\0"
#define vusVersNuml2   10
#define vusVersNumf2   28
#define vszCopyright   "Copyright 1987-1998 Microsoft Corp.\0\0"
#define VBA_VERHI      0x50000
#define VBA_VERLO      0x183C


GUIDS.CPP

//=--------------------------------------------------------------------------=
// Guids.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// contains the guids we will define ourselves.
//

#define INITGUID                   // define all the guids.
#include "IPServer.H"

#include "Guids.H"
#include "olectl.h"
#include "docobj.h"
#include "WImgInterfaces.H"


GUIDS.H

//=--------------------------------------------------------------------------=
// Guids.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// guids that we define locally
//
#ifndef _GUIDS_H_

// for each property page this server will have, put the guid definition for it
// here so that it gets defined ...
//
DEFINE_GUID(CLSID_WebImageGeneralPage, 0xbe508b20, 0x2e73, 0x11cf, 0xb6, 0xcf, 0x00, 0xaa, 0x00, 0xa7, 0x4d, 0xaf);

#define _GUIDS_H_
#endif // _GUIDS_H_


LOCALOBJ.H

//=--------------------------------------------------------------------------=
// LocalObj.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// this file is used by automation servers to delcare things that their objects
// need other parts of the server to see.
//
#ifndef _LOCALOBJECTS_H_

//=--------------------------------------------------------------------------=
// these constants are used in conjunction with the g_ObjectInfo table that
// each inproc server defines.  they are used to identify a given  object
// within the server.
//
// **** ADD ALL NEW OBJECTS TO THIS LIST ****
//

#define OBJECT_TYPE_CTLWEBIMAGE 0
#define OBJECT_TYPE_PPGWEBIMAGEGENERAL 1

#define _LOCALOBJECTS_H_
#endif // _LOCALOBJECTS_H_


RESOURCE.H

//=--------------------------------------------------------------------------=
// Resource.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// resource IDs.
//
#ifndef _RESOURCE_H_

#define RESID_TOOLBOX_BITMAP          1

//=--------------------------------------------------------------------------=
// Strings
//
#define IDS_PROPERTIES                1000                // this MUST be 1000 !!!
#define IDS_WEBIMAGE_GENERALPAGETITLE 2003
#define IDS_WEBIMAGE_GENERALDOCSTRING 2004
#define IDS_WEBIMAGE_ABOUTBOXVERB     2005

//=--------------------------------------------------------------------------=
// Dialog Stuff
//
#define IDD_PROPPAGE_WEBIMAGEGENERAL  2000

#define IDC_URL                       101

#define _RESOURCE_H_
#endif // _RESOURCE_H_


WIMG.CPP

//=--------------------------------------------------------------------------=
// WImg.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// various routines et all that aren't in a file for a particular automation
// object, and don't need to be in the generic ole automation code.
//

#define INITOBJECTS                // define the descriptions for our objects

#include "IPServer.H"
#include "LocalSrv.H"

#include "LocalObj.H"
#include "CtrlObj.H"
#include "Globals.H"
#include "Util.H"
#include "Resource.H"

#include "WImgCtl.H"
#include "WImgPPG.H"
#include "cathelp.H"

const IID IID_ICatRegister = {0x0002E012,0x0000,0x0000,{0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46}};
const CATID CATID_SafeForScripting= {0x7dd95801,0x9882,0x11cf,{0x9f,0xa9,0x00,0xaa,0x00,0x6c,0x42,0xc4}};
const CATID CATID_SafeForInitializing= {0x7dd95802,0x9882,0x11cf,{0x9f,0xa9,0x00,0xaa,0x00,0x6c,0x42,0xc4}};

// needed for ASSERTs and FAIL
//
SZTHISFILE

//=--------------------------------------------------------------------------=
// our Libid.  This should be the LIBID from the Type library, or NULL if you
// don't have one.
//
const CLSID *g_pLibid = &LIBID_WebImageObjects;

//=--------------------------------------------------------------------------=
// Localization Information
//
// We need the following two pieces of information:
//    a. whether or not this DLL uses satellite DLLs for localization.  if
//       not, then the lcidLocale is ignored, and we just always get resources
//       from the server module file.
//    b. the ambient LocaleID for this in-proc server.  Controls calling
//       GetResourceHandle() will set this up automatically, but anybody
//       else will need to be sure that it's set up properly.
//
const VARIANT_BOOL    g_fSatelliteLocalization =  FALSE;
LCID            g_lcidLocale = MAKELCID(LANG_USER_DEFAULT, SORT_DEFAULT);

//=--------------------------------------------------------------------------=
// your license key and where under HKEY_CLASSES_ROOT_LICENSES it's sitting
//
const WCHAR g_wszLicenseKey [] = L"";
const WCHAR g_wszLicenseLocation [] = L"";

WNDPROC g_ParkingWindowProc = NULL;

//=--------------------------------------------------------------------------=
// This Table describes all the automatible objects in your automation server.
// See AutomationObject.H for a description of what goes in this structure
// and what it's used for.
//
OBJECTINFO g_ObjectInfo[] = {
    CONTROLOBJECT(WebImage),
    PROPERTYPAGE(WebImageGeneral),
    EMPTYOBJECT
};

const char g_szLibName[] = "WebImage";

//=--------------------------------------------------------------------------=
// IntializeLibrary
//=--------------------------------------------------------------------------=
// called from DllMain:DLL_PROCESS_ATTACH.  allows the user to do any sort of
// initialization they want to.
//
// Notes:
//
void WINAPI InitializeLibrary
(
    void
)
{
    // TODO: initialization here.  control window class should be set up in
    // RegisterClassData.
}

//=--------------------------------------------------------------------------=
// UninitializeLibrary
//=--------------------------------------------------------------------------=
// called from DllMain:DLL_PROCESS_DETACH.  allows the user to clean up anything
// they want.
//
// Notes:
//
void WINAPI UninitializeLibrary
(
    void
)
{
    // TODO: uninitialization here.  control window class will be unregistered
    // for you, but anything else needs to be cleaned up manually.
    // Please Note that the Window 95 DLL_PROCESS_DETACH isn't quite as stable
    // as NT's, and you might crash doing certain things here ...
}

//=--------------------------------------------------------------------------=
// CheckForLicense
//=--------------------------------------------------------------------------=
// users can implement this if they wish to support Licensing.  otherwise,
// they can just return TRUE all the time.
//
// Parameters:
//    none
//
// Output:
//    BOOL            - TRUE means the license exists, and we can proceed
//                      FALSE means we're not licensed and cannot proceed
//
// Notes:
//    - implementers should use g_wszLicenseKey and g_wszLicenseLocation
//      from the top of this file to define their licensing [the former
//      is necessary, the latter is recommended]
//
BOOL WINAPI CheckForLicense
(
    void
)
{
    // TODO: decide whether or not your server is licensed in this function.
    // people who don't want to bother with licensing should just return
    // true here always.  g_wszLicenseKey and g_wszLicenseLocation are
    // used by IClassFactory2 to do some of the licensing work.
    //
    return TRUE;
}

//=--------------------------------------------------------------------------=
// RegisterData
//=--------------------------------------------------------------------------=
// lets the inproc server writer register any data in addition to that in
// any other objects.
//
// Output:
//    BOOL            - false means failure.
//
// Notes:
//
BOOL WINAPI RegisterData
(
    void
)
{
HRESULT hr;

hr = CreateComponentCategory(CATID_SafeForScripting, L"Controls that are safely scriptable");
hr = CreateComponentCategory(CATID_SafeForInitializing, L"Controls safely initializable from persistent data");
hr = RegisterCLSIDInCategory(CLSID_WebImage, CATID_SafeForScripting);
hr = RegisterCLSIDInCategory(CLSID_WebImage, CATID_SafeForInitializing);
    return TRUE;
}

//=--------------------------------------------------------------------------=
// UnregisterData
//=--------------------------------------------------------------------------=
// inproc server writers should unregister anything they registered in
// RegisterData() here.
//
// Output:
//    BOOL            - false means failure.
//
// Notes:
//
BOOL WINAPI UnregisterData
(
    void
)
{
HRESULT hr;

hr = UnRegisterCLSIDInCategory(CLSID_WebImage, CATID_SafeForScripting);
hr = UnRegisterCLSIDInCategory(CLSID_WebImage, CATID_SafeForInitializing);
    return TRUE;
}

BOOL    WINAPI  CheckLicenseKey(LPWSTR wszCheckme)
{
return TRUE;
}

BSTR    WINAPI  GetLicenseKey(void)
{
return NULL;
}

//=--------------------------------------------------------------------------=
// CRT stubs
//=--------------------------------------------------------------------------=
// these two things are here so the CRTs aren't needed. this is good.
//
// basically, the CRTs define this to suck in a bunch of stuff.  we'll just
// define them here so we don't get an unresolved external.
//
// TODO: if you are going to use the CRTs, then remove this line.
//
// extern "C" int __cdecl _fltused = 1;

extern "C" int _cdecl _purecall(void)
{
  FAIL("Pure virtual function called.");
  return 0;
}


WIMGCTL.CPP

//=--------------------------------------------------------------------------=
// WImgCtl.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
//
//
#include "IPServer.H"

#include "Guids.H"
#include "LocalObj.H"
#include "Util.H"
#include "Globals.H"
#include "Resource.H"
#include "Wimgctl.H"

// for ASSERT and FAIL
//
SZTHISFILE

WCHAR wszImage [] = L"Image";

//=--------------------------------------------------------------------------=
// all the events in this control
//
// TODO: add events here ...
//

//=--------------------------------------------------------------------------=
// array describing all of our property pages.  these clsids are typically
// in guids.h
//
// TODO: add any additional property page guids here ...
//
const GUID *rgWebImagePropPages [] = {
    &CLSID_WebImageGeneralPage
};

//=--------------------------------------------------------------------------=
// Custum Verb information
//
// TODO: add any custom verbs here in an array, using the VERBINFO structure.
//       then mark the controld def'n in WebImageCtl.H with
//       this verb array
//

//=--------------------------------------------------------------------------=
// CWebImageControl::Create
//=--------------------------------------------------------------------------=
// global static function that creates an instance of the control an returns
// an IUnknown pointer for it.
//
// Parameters:
//    IUnknown *        - [in] controlling unknown for aggregation
//
// Output:
//    IUnknown *        - new object.
//
// Notes:
//
IUnknown *CWebImageControl::Create
(
    IUnknown *pUnkOuter
)
{
    // make sure we return the private unknown so that we support aggegation
    // correctly!
    //
    CWebImageControl *pNew = new CWebImageControl(pUnkOuter);
    return pNew->PrivateUnknown();
}

//=--------------------------------------------------------------------------=
// CWebImageControl::CWebImageControl
//=--------------------------------------------------------------------------=
// "Being born is like being kidnapped.  And then sold into slavery."
//    - andy warhol (1928 - 87)
//
// Parameters:
//    IUnknown *        - [in]
//
// Notes:
//
#pragma warning(disable:4355)  // using 'this' in constructor
CWebImageControl::CWebImageControl
(
    IUnknown *pUnkOuter
)
: CInternetControl(pUnkOuter, OBJECT_TYPE_CTLWEBIMAGE, (IDispatch *)this)
{
m_bmpPath = 0;
m_dib = 0;
m_dibFile = 0;
m_state= bdsNoBitsYet;

}
#pragma warning(default:4355)  // using 'this' in constructor

//=--------------------------------------------------------------------------=
// CWebImageControl::~CWebImageControl
//=--------------------------------------------------------------------------=
// "We all labour against our own cure, for death is the cure of all diseases"
//    - Sir Thomas Browne (1605 - 82)
//
// Notes:
//
CWebImageControl::~CWebImageControl ()
{
if(m_bmpPath)
delete [] m_bmpPath;
if( m_dibFile )
delete m_dibFile;
if( m_dib )
delete m_dib;
}

//=--------------------------------------------------------------------------=
// CWebImageControl:RegisterClassData
//=--------------------------------------------------------------------------=
// register the window class information for your control here.
// this information will automatically get cleaned up for you on DLL shutdown.
//
// Output:
//    BOOL            - FALSE means fatal error.
//
// Notes:
//
BOOL CWebImageControl::RegisterClassData()
{
    WNDCLASS wndclass;

    // TODO: register any additional information you find interesting here.
    //       this method is only called once for each type of control
    //
    memset(&wndclass, 0, sizeof(WNDCLASS));
    wndclass.style          = CS_VREDRAW | CS_HREDRAW | CS_DBLCLKS | CS_OWNDC;
    wndclass.lpfnWndProc    = COleControl::ControlWindowProc;
    wndclass.hInstance      = g_hInstance;
    wndclass.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
    wndclass.lpszClassName  = WNDCLASSNAMEOFCONTROL(OBJECT_TYPE_CTLWEBIMAGE);

    return RegisterClass(&wndclass);
}

//=--------------------------------------------------------------------------=
// CWebImageControl::BeforeCreateWindow
//=--------------------------------------------------------------------------=
// called just before the window is created.  Great place to set up the
// window title, etc, so that they're passed in to the call to CreateWindowEx.
// speeds things up slightly.
//
// Notes:
//
void CWebImageControl::BeforeCreateWindow()
{
}

BOOL CWebImageControl::AfterCreateWindow()
{
m_dc = ::GetDC(m_hwnd);
HPALETTE hpal = ::CreateHalftonePalette(m_dc);
::SelectPalette(m_dc, hpal, FALSE);

        UpdateImage();
        return TRUE;
}

//=--------------------------------------------------------------------------=
// CWebImageControl::InternalQueryInterface
//=--------------------------------------------------------------------------=
// qi for things only we support.
//
// Parameters:
// Parameters:
//    REFIID        - [in]  interface they want
//    void **       - [out] where they want to put the resulting object ptr.
//
// Output:
//    HRESULT       - S_OK, E_NOINTERFACE
//
// Notes:
//
HRESULT CWebImageControl::InternalQueryInterface
(
    REFIID  riid,
    void  **ppvObjOut
)
{
    IUnknown *pUnk;

    *ppvObjOut = NULL;

    // TODO: if you want to support any additional interrfaces, then you should
    // indicate that here.  never forget to call COleControl's version in the
    // case where you don't support the given interface.
    //
    if (DO_GUIDS_MATCH(riid, IID_IWebImage)) {
        pUnk = (IUnknown *)(IWebImage *)this;
    } else{
        return COleControl::InternalQueryInterface(riid, ppvObjOut);
    }

    pUnk->AddRef();
    *ppvObjOut = (void *)pUnk;
    return S_OK;
}

//=--------------------------------------------------------------------------=
// CWebImageControl::LoadTextState
//=--------------------------------------------------------------------------=
// load in our text state for this control.
//
// Parameters:
//    IPropertyBag *        - [in] property bag to read from
//    IErrorLog *           - [in] errorlog object to use with proeprty bag
//
// Output:
//    HRESULT
//
// Notes:
//    - NOTE: if you have a binary object, then you should pass an unknown
//      pointer to the property bag, and it will QI it for IPersistStream, and
//      get said object to do a Load()
//
STDMETHODIMP CWebImageControl::LoadTextState
(
    IPropertyBag *pPropertyBag,
    IErrorLog    *pErrorLog
)
{
    VARIANT v;

VariantInit(&v);

    v.vt = VT_BSTR;
    v.bstrVal = NULL;

HRESULT hr = S_OK;
    // try to load in the property.  if we can't get it, then leave
    // things at their default.
    //
    pPropertyBag->Read(::wszImage, &v, pErrorLog);
    if (v.bstrVal) {
        MAKE_ANSIPTR_FROMWIDE(psz, v.bstrVal);
hr = SetBmpPath(psz);
        VariantClear(&v);
    }

    return hr;
}

//=--------------------------------------------------------------------------=
// CWebImageControl::LoadBinaryState
//=--------------------------------------------------------------------------=
// loads in our binary state using streams.
//
// Parameters:
//    IStream *            - [in] stream to write to.
//
// Output:
//    HRESULT
//
// Notes:
//
const DWORD STREAMHDR_MAGIC = 12345678L;

STDMETHODIMP CWebImageControl::LoadBinaryState
(
    IStream *pStream
)
{
DWORDsh;
    HRESULThr;

    // first read in the streamhdr, and make sure we like what we're getting
    //
    hr = pStream->Read(&sh, sizeof(sh), NULL);
    RETURN_ON_FAILURE(hr);

    // sanity check
    //
    if (sh != STREAMHDR_MAGIC )
return E_UNEXPECTED;

return(SetBmpPath(pStream));
}

//=--------------------------------------------------------------------------=
// CWebImageControl::SaveTextState
//=--------------------------------------------------------------------------=
// saves out the text state for this control using a property bag.
//
// Parameters:
//    IPropertyBag *        - [in] the property bag with which to work.
//    BOOL                  - [in] if TRUE, then write out ALL properties, even
//                            if they're their the default value ...
//
// Output:
//    HRESULT
//
// Notes:
//
STDMETHODIMP CWebImageControl::SaveTextState
(
    IPropertyBag *pPropertyBag,
    BOOL          fWriteDefaults
)
{
    HRESULT hr;
    VARIANT v;

    v.vt = VT_BSTR;
    v.bstrVal = BSTRFROMANSI(m_bmpPath);
    RETURN_ON_NULLALLOC(v.bstrVal);

    // this control currently only persists out the caption property
    //
    hr = pPropertyBag->Write(::wszImage, &v);
    VariantClear(&v);
    return hr;}

//=--------------------------------------------------------------------------=
// CWebImageControl::SaveBinaryState
//=--------------------------------------------------------------------------=
// save out the binary state for this control, using the given IStream object.
//
// Parameters:
//    IStream  *             - [in] save to which you should save.
//
// Output:
//    HRESULT
//
// Notes:
//    - it is important that you seek to the end of where you saved your
//      properties when you're done with the IStream.
//
STDMETHODIMP CWebImageControl::SaveBinaryState
(
    IStream *pStream
)
{
    DWORD sh = STREAMHDR_MAGIC;
    HRESULT hr;

    // write out the stream hdr.
    //
    hr = pStream->Write(&sh, sizeof(sh), NULL);
    RETURN_ON_FAILURE(hr);

    // write out he control state information
    //

DWORD dw = m_bmpPath ? lstrlen(m_bmpPath) : 0;

hr = pStream->Write(&dw,sizeof(dw),0);
RETURN_ON_FAILURE(hr);

if( dw )
hr = pStream->Write(m_bmpPath,dw+1,0);

    return hr;
}

//=--------------------------------------------------------------------------=
// CWebImageControl::OnDraw
//=--------------------------------------------------------------------------=
// "I don't very much enjoy looking at paintings in general.  i know too
//  much about them.  i take them apart."
//    - georgia o'keeffe (1887-1986)
//
// Parameters:
//    DWORD              - [in]  drawing aspect
//    HDC                - [in]  HDC to draw to
//    LPCRECTL           - [in]  rect we're drawing to
//    LPCRECTL           - [in]  window extent and origin for meta-files
//    HDC                - [in]  HIC for target device
//    BOOL               - [in]  can we optimize dc handling?
//
// Output:
//    HRESULT
//
// Notes:
//
HRESULT CWebImageControl::OnDraw
(
    DWORD    dvAspect,
    HDC      hdcDraw,
    LPCRECTL prcBounds,
    LPCRECTL prcWBounds,
    HDC      hicTargetDevice,
    BOOL     fOptimize
)
{
if(m_dib)
{
if (prcWBounds != NULL) //printing
{
SetMapMode(hdcDraw,MM_TEXT);
HBITMAP hBitmap;
//printing DIB
hBitmap = (HBITMAP) HANDLE(*m_dib);
DIBSECTION ds;
GetObject(hBitmap,sizeof(DIBSECTION),(LPSTR)&ds);
StretchDIBits(hdcDraw,prcBounds->left,prcBounds->top,prcBounds->right - prcBounds->left,
prcBounds->bottom - prcBounds->top,0,0,
ds.dsBm.bmWidth, ds.dsBm.bmHeight,
ds.dsBm.bmBits,
(BITMAPINFO *)(*m_dibFile),
DIB_RGB_COLORS,
SRCCOPY);
return S_OK;
}

if( hdcDraw != m_dc )
{
HPALETTE hpal = ::CreateHalftonePalette(hdcDraw);
::SelectPalette(hdcDraw, hpal, FALSE);
}

::RealizePalette(hdcDraw);

m_dib->PaintTo(hdcDraw,prcBounds->left,prcBounds->top);

}
    return S_OK;
}

//=--------------------------------------------------------------------------=
// CWebImageControl::OnProgress
//=--------------------------------------------------------------------------=
//
// Parameters:
//    DISPIDpropId- [in]  Same dispId that was passed into SetupDownload
//    ULONGprogress- [in]  Size downlaoded so far
//    ULONG     themax- [in]  Overall size of object being downloaded
//    ULONGstatusFlag- [in]  Flags according
//    LPCWSTR   statusStr- [in]Readable version of status
//
// Notes:
//
HRESULT CWebImageControl::OnProgress
(
DISPIDpropId,
ULONGprogress,
ULONGthemax,
ULONGstatusFlag,
LPCWSTRstatusString
)
{
// TODO: put custom progress UI notification code here. The default action
//(shown below) is to fire an OnProgress event to the host

return(FireProgress(themax ? (progress * 100)/themax : 0));
}

//=--------------------------------------------------------------------------=
// CWebImageControl::WindowProc
//=--------------------------------------------------------------------------=
// window procedure for this control.  nothing terribly exciting.
//
// Parameters:
//     see win32sdk on window procs.
//
// Notes:
//
LRESULT CWebImageControl::WindowProc
(
    UINT   msg,
    WPARAM wParam,
    LPARAM lParam
)
{
    // TODO: handle any messages here, like in a normal window
    // proc.  note that for special keys, you'll want to override and
    // implement OnSpecialKey.
    //
    return OcxDefWindowProc(msg, wParam, lParam);
}

//=--------------------------------------------------------------------------=
// CWebImageControl::AboutBox
//=--------------------------------------------------------------------------=
// prints up an about box.  fweeeee.
//
// Notes:
//
void CWebImageControl::AboutBox
(
    void
)
{
    // TODO: Ideally, one would use DialogBox, and some sort of Dialog Box here if
    // they wanted a slightly more interesting About Box ...  you should
    // still call ModalDialog first, however.
    //
    ModalDialog(TRUE);
    MessageBox(NULL, "This is My Control", "About WebImage", MB_OK | MB_TASKMODAL);
    ModalDialog(FALSE);
}

STDMETHODIMP  CWebImageControl::get_Image(BSTR * path)
{
CHECK_POINTER(path);

// Actually we need to be accepting (happily IMoniker objects)

//path->vt = VT_BSTR;

BSTR * pbstrPath = path; //&path->bstrVal;

    *pbstrPath = (m_bmpPath && *m_bmpPath) ? BSTRFROMANSI(m_bmpPath) : SysAllocString(L"");

    return (*pbstrPath) ? S_OK : E_OUTOFMEMORY;
}

STDMETHODIMP  CWebImageControl::put_Image(BSTR path)
{
// NOTE: We should reference and IMoniker*!!

//if( path.vt != VT_BSTR )
//return(E_UNEXPECTED);

BSTR & bstrPath = path; //.bstrVal;
    // get an ANSI pointer, so we can stuff it in our local buffer for captions!
    //

    MAKE_ANSIPTR_FROMWIDE(pszPath, bstrPath);

HRESULT hr = SetBmpPath(pszPath);

// update anybody who cares about property changes and mark ourselves
// as dirty
//
PropertyChanged(DISPID_BMPPATH);
m_fDirty = TRUE;

    return hr;
}

STDMETHODIMP  CWebImageControl::Scramble()
{
HRESULT hr;

        if (m_dib)
{
unsigned char * p = m_dib->Base();
DWORDdw = m_dib->ImageSize();

for( DWORD i = 0; i < dw; i+=3,p+=3 )
*p ^= 0x55;

InvalidateControl(0);

hr = S_OK;
}
else
{
hr = E_FAIL; // E_PENDING;
}

return(hr);
}

HRESULT CWebImageControl::SetBmpPath(const char *psz)
{
    // if it hasn't changed, don't waste any time.
    //
    if (psz && m_bmpPath )
{
CHECK_POINTER(psz);
if( !lstrcmp(m_bmpPath, psz) )
return S_OK;
}

if( m_bmpPath )
delete [] m_bmpPath;

if(!psz)
{
m_bmpPath = 0;
return(S_OK);
}

CHECK_POINTER(psz);

if( !*psz )
{
m_bmpPath = 0;
return(S_OK);
}

DWORD dw = lstrlen(psz);

m_bmpPath = new char[dw + 1];

if( !m_bmpPath )
{
FAIL("No memory");
return(E_OUTOFMEMORY);
}

lstrcpy(m_bmpPath, psz);

        UpdateImage();

return(S_OK);
}

HRESULT CWebImageControl::SetBmpPath(IStream * strm)
{
CHECK_POINTER(strm);

char * tmp = 0;

if( m_bmpPath )
delete [] m_bmpPath;

DWORDdw;
HRESULT hr = strm->Read(&dw,sizeof(dw),0);

if( SUCCEEDED(hr) )
{
if( !dw )
{
hr = S_OK;
}
else
{
tmp = new char[dw+1];

if( !tmp )
{
FAIL("No memory");
hr = E_OUTOFMEMORY;
}
else
{
hr = strm->Read(tmp,dw+1,0);
}
}
}

// if it hasn't changed, don't waste any time.
    //
    if ((!tmp && !m_bmpPath) || !lstrcmp(m_bmpPath, tmp))
        return S_OK;

if( m_bmpPath )
delete [] m_bmpPath;

m_bmpPath = tmp;

        UpdateImage();

return(hr);
}

HRESULT CWebImageControl::UpdateImage()
{
if( !m_hwnd )
return(S_OK);

if( !m_bmpPath )
return(S_OK);

return( SetupDownload(OLESTRFROMANSI(m_bmpPath),DISPID_BMPPATH) );
}

//=--------------------------------------------------------------------------=
// CWebImageControl::OnData
//=--------------------------------------------------------------------------=
//
// Parameters:
//    DISPIDpropId- [in]  Same dispId that was passed into SetupDownload
//    DWORDgrfBSCF- [in]  Notification flags ("URL Moniker" specification)
//    IStream *strm- [in]  Stream to read data from
//    DWORDdwSize- [in]  Overall size available so far
//
//
//Notes:This is the class that is monitering and interpreting the bits that
//are being downloaded. The methods in class inherited from IBindStatusCallback
//are only called during 'DispatchMessage' calls from the main applications loop.
//This impelmentation maintains a state machine for the bits as they arrive:
//
//Initial state:No bits have been seen yet
//
//              Have BITMAPFILEHEADER   We now have this structure. At this point there is
//enough information to calculate the following values:
//- The file is actually a Windows BMP file
//- The size of the entire image
//- This size of the BITMAPINFO structure
//
//Have BITMAPINFOWe now have enough information to call
//CreateDibSection! That function will return a
//a buffer it allocated.
//
//Getting BITSAt this point the bits are arriving and all we
//have to do is strm->Read the bits directly into
//buffer returned from the step above and notify
//our view sink that we need refreshing.
//
//BITS are done(nothing to do!?)
//
HRESULT CWebImageControl::OnData
(
DISPIDpropId,
DWORDgrfBSCF,
IStream *strm,
DWORDdwSize
)
{
HRESULThr= NOERROR;

switch(m_state)
{
case bdsNoBitsYet:
{
if( dwSize >= sizeof(BITMAPFILEHEADER) )
{
                    CHECK_POINTER(strm);
                    strm->AddRef();

if( m_dibFile )
delete m_dibFile;

m_dibFile = new CDibFile;

if( !m_dibFile )
{
hr = E_OUTOFMEMORY;
break;
}

hr = m_dibFile->GetFileHeader(strm);

if( FAILED(hr) )
break;

m_state = bdsGotFileHeader;

// now FALL THRU!
}
else
{
break;
}
}

case bdsGotFileHeader:
{
if( dwSize >= (m_dibFile->HeaderSize() + sizeof(BITMAPFILEHEADER)) )
{
if( m_dibFile )
hr = m_dibFile->GetInfoHeader(strm);
else
hr = E_OUTOFMEMORY;

if( FAILED(hr) )
break;

if( m_dib )
delete m_dib;

m_dib = new CDibSection;

if( !m_dib )
{
hr = E_OUTOFMEMORY;
break;
}

m_dib->Setup(m_dc);

hr = m_dib->Create(*m_dibFile);

if( FAILED(hr) )
break;

m_dib->ImageSize( m_dibFile->CalcImageSize() );
m_state = bdsGotBitmapInfo;

// FALL THRU!

}
else
{
break;
}
}

case bdsGotBitmapInfo:
{
SIZELsz;
m_dib->GetSize(sz);
SetControlSize(&sz);

// we need m_dibFile for printing
//delete m_dibFile;
//m_dibFile = 0;

m_state = bdsGettingBits;

// FALL THRU
}

case bdsGettingBits:
{
if( dwSize > 0 )
{
hr = m_dib->ReadFrom( strm, dwSize );

if( FAILED(hr) )
break;

::RealizePalette(m_dc);

m_dib->PaintTo(m_dc);
}

if( grfBSCF & BSCF_LASTDATANOTIFICATION )
m_state = bdsBitsAreDone;
else
break;
}

case bdsBitsAreDone:
{
m_state = bdsNoBitsYet;
                strm->Release();
break;
}

}

return(hr);
}


WIMGCTL.H

//=--------------------------------------------------------------------------=
// WImgCtl.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// class declaration for the WebImage control.
//
#ifndef _WEBIMAGECONTROL_H_

#include "IPServer.H"
#include "CtrlObj.H"
#include "Internet.h"
#include "WImgInterfaces.H"
#include "Dispids.H"

#include "DibCls.H"

typedef struct tagWEBIMAGECTLSTATE {

    // TOOD: put state variables here, and probably get rid of fGarbage, unless
    // you have a real need for it
    //
    VARIANT_BOOL fGarbage;

} WEBIMAGECTLSTATE;

//=--------------------------------------------------------------------------=
// CWebImageControl
//=--------------------------------------------------------------------------=
// our control.
//
class CWebImageControl :       public CInternetControl,
                                                                        public IWebImage,
public ISupportErrorInfo {

  public:
    // IUnknown methods
    //
    DECLARE_STANDARD_UNKNOWN();

    // IDispatch methods
    //
    DECLARE_STANDARD_DISPATCH();

    // ISupportErrorInfo methods
    //
    DECLARE_STANDARD_SUPPORTERRORINFO();

    // IWebImage methods
    //
    // TODO: copy over the method declarations from WebImageInterfaces.H
    //       don't forget to remove the PURE from them.
    //
    STDMETHOD(get_Image)(THIS_ BSTR FAR* path) ;
    STDMETHOD(put_Image)(THIS_ BSTR path) ;
    STDMETHOD_(void, AboutBox)(THIS) ;
    STDMETHOD(Scramble)(THIS) ;

    // OLE Control stuff follows:
    //
    CWebImageControl(IUnknown *pUnkOuter);
    virtual ~CWebImageControl();

    // static creation function.  all controls must have one of these!
    //
    static IUnknown *Create(IUnknown *);

  private:
    // overridables that the control must implement.
    //
    STDMETHOD(LoadBinaryState)(IStream *pStream);
    STDMETHOD(SaveBinaryState)(IStream *pStream);
    STDMETHOD(LoadTextState)(IPropertyBag *pPropertyBag, IErrorLog *pErrorLog);
    STDMETHOD(SaveTextState)(IPropertyBag *pPropertyBag, BOOL fWriteDefault);
    STDMETHOD(OnDraw)(DWORD dvAspect, HDC hdcDraw, LPCRECTL prcBounds, LPCRECTL prcWBounds, HDC hicTargetDev, BOOL fOptimize);
    virtual LRESULT WindowProc(UINT msg, WPARAM wParam, LPARAM lParam);
    virtual BOOL    RegisterClassData(void);

    virtual HRESULT InternalQueryInterface(REFIID, void **);
    virtual void    BeforeCreateWindow(void);

    virtual BOOL    AfterCreateWindow(void);

//Internet specific callbacks:
//
///OnData is called asynchronously as data for an object or property arrives...
virtual HRESULT OnData( DISPID id, DWORD grfBSCF,IStream * bitstrm, DWORD amount );

//OnProgess is called to allow you to present progess indication UI
virtual HRESULT OnProgress( DISPID, ULONG progress, ULONG themax, ULONG, LPCWSTR);

    // private state information.
    //

HRESULT SetBmpPath(const char *psz);
HRESULT SetBmpPath(IStream *);
    HRESULT UpdateImage();

enum bmpDownloadStates
{
bdsNoBitsYet,
bdsGotFileHeader,
        bdsGotBitmapInfo,
bdsGettingBits,
bdsBitsAreDone
};

HDCm_dc;
bmpDownloadStatesm_state;
CDibFile *m_dibFile;
CDibSection *m_dib;

// Actual properties
char *m_bmpPath;

};

// TODO: if you have an array of verbs, then add an extern here with the name
//       of it, so that you can include it in the DEFINE_CONTROLOBJECT.
//       ie.  extern VERBINFO m_WebImageCustomVerbs [];
//
extern const GUID    *rgWebImagePropPages [];
DEFINE_CONTROLOBJECT(WebImage,
    &CLSID_WebImage,
    "WebImageCtl",
    CWebImageControl::Create,
    1,
    &IID_IWebImage,
    "WebImage.HLP",
    &DIID_DWebImageEvents,
    OLEMISC_SETCLIENTSITEFIRST|OLEMISC_ACTIVATEWHENVISIBLE|OLEMISC_RECOMPOSEONRESIZE|OLEMISC_CANTLINKINSIDE|OLEMISC_INSIDEOUT,
    0,
    RESID_TOOLBOX_BITMAP,
    "WebImageWndClass",
    1,
    rgWebImagePropPages,
    0,
    NULL);

#define _WEBIMAGECONTROL_H_
#endif // _WEBIMAGECONTROL_H_


WIMGPPG.CPP

//=--------------------------------------------------------------------------=
// WImgPPG.Cpp
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// property page implementations for WebImage control.
//

#include "IPServer.H"

#include "LocalObj.H"
#include "WImgPPG.H"
#include "WImgCtl.H"
#include "Resource.H"
#include "Util.H"

// for ASSERT and FAIL
//
SZTHISFILE

//=--------------------------------------------------------------------------=
// Property Page messages
//=--------------------------------------------------------------------------=
// in addition to regular windows messages you'll receive for a dialog box,
// you'll receive the following messages in your property page implementation:
//
// PPM_NEWOBJECTS:
//    wParam = 0;
//    lParam = (LPARAM)(HRESULT *)hr
//
//  - in this message, you should call FirstControl() to get a pointer to a
//    control, and initialize the values in the property page dialog with
//    values from the control object.  put results from the operation in
//    the HRESULT pointed to by LPARAM.
//
// PPM_APPLY:
//    wParam = 0;
//    lParam = (LPARAM)(HRESULT *)hr
//
//  - this message is sent to your dialog whenever the user clicks APPLY or OK
//    in the dialog.  you should have a loop with the following code in it:
//
//      for (pUnk = FirstControl(&dwCookie) ; pUnk; pUnk = NextControl(&dwCookie)) {
//            hr = pUnk->QueryInterface(IID_IMyCtlInterface, (void **)&pMyCtl);
//            // set properties here!!!
//      }
//
//    call PropPageException() if there is an error while setting propertites
//    to show the exception set up by the property set routine.
//
// PPM_EDITPROPERTY:
//    wParam = dispid
//    lParam = (LPARAM)(HRESULT *)hr
//
//  - sent to your dialog when somebody wants you to set the focus to a specific
//    property [typically, one will see a call to this when one returns a page
//    from IPerPropertyBrowsing::MapPropertyToPage].  you can use this
//    to bring up dialogs, or do whatever flaps your flagella.
//

//=--------------------------------------------------------------------------=
// CWebImageGeneralPage::Create
//=--------------------------------------------------------------------------=
// global static creation function.
//
// Parameters:
//    IUnknown *    - [in] controlling unknown
//
// Output:
//    IUnknown *    - new prop page.
//
// Notes:
//
IUnknown *CWebImageGeneralPage::Create
(
    IUnknown *pUnkOuter
)
{
    return (IUnknown *)new CWebImageGeneralPage(pUnkOuter);
}

//=--------------------------------------------------------------------------=
// CWebImageGeneralPage::CWebImageGeneralPage
//=--------------------------------------------------------------------------=
// constructor.
//
// Parameters:
//    IUnknown *        - [in] controlling unknown.
//
// Notes:
//
CWebImageGeneralPage::CWebImageGeneralPage
(
    IUnknown *pUnkOuter
)
: CPropertyPage(pUnkOuter, OBJECT_TYPE_PPGWEBIMAGEGENERAL)
{
    // initialize local variables here.
}

//=--------------------------------------------------------------------------=
// CWebImageGeneralPage::~CWebImageGeneralPage
//=--------------------------------------------------------------------------=
// destructor.
//
// Notes:
//
CWebImageGeneralPage::~CWebImageGeneralPage()
{
    // clean up
}

//=--------------------------------------------------------------------------=
// CWebImageGeneralPage::DialogProc
//=--------------------------------------------------------------------------=
// our dialog proc.
//
// Parameters:
//    - see win32sdk docs on DialogProc
//
// Notes:
//
BOOL CWebImageGeneralPage::DialogProc
(
    HWND   hwnd,
    UINT   msg,
    WPARAM wParam,
    LPARAM lParam
)
{
    HRESULT     hr;
    IWebImage *pWebImage;
    IUnknown   *pUnk;
    DWORD       dwDummy;

    switch (msg) {

      // we've been given some new objects, so go and re-set up the dialog page.
      //
      case PPM_NEWOBJECTS:
        {

        pUnk = FirstControl(&dwDummy);
        if (!pUnk) return FALSE;

        hr = pUnk->QueryInterface(IID_IWebImage, (void **)&pWebImage);
        if (FAILED(hr)) return FALSE;

//VariantInit(&var);
BSTR bstr;
        pWebImage->get_Image(&bstr);
//if( var.vt == VT_BSTR )
//{
          MAKE_ANSIPTR_FROMWIDE(psz, bstr);//var.bstrVal);
          SetDlgItemText(hwnd, IDC_URL, psz);
          SysFreeString(bstr);//var.bstrVal);
//}
        pWebImage->Release();
        }
        return TRUE;

      case PPM_APPLY:
        {
        char      szTmp[120];

        // get all the controls we have to update.
        //
        for (pUnk = FirstControl(&dwDummy) ; pUnk; pUnk = NextControl(&dwDummy))
{
            hr = pUnk->QueryInterface(IID_IWebImage, (void **)&pWebImage);
            if (FAILED(hr)) continue;

            GetDlgItemText(hwnd, IDC_URL, szTmp, 128);
//VariantInit(&var);
            //var.vt = VT_BSTR;
BSTR bstr;
            //var.bstrVal = BSTRFROMANSI(szTmp);
            bstr = BSTRFROMANSI(szTmp);
            ASSERT(bstr, "Maggots!");
            pWebImage->put_Image(bstr);
            SysFreeString(bstr);
            pWebImage->Release();
        }
        }
        return TRUE;

      case WM_COMMAND:
        switch (LOWORD(wParam)) {
          case IDC_URL:
            if (HIWORD(wParam) == EN_CHANGE)
                MakeDirty();
        }
        break;
    }

return(FALSE);
}


WIMGPPG.H

//=--------------------------------------------------------------------------=
// WImgPPG.H
//=--------------------------------------------------------------------------=
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//=--------------------------------------------------------------------------=
//
// class declaration for WebImage's property pages.
//
#ifndef _WEBIMAGEPROPPAGE_H_

// kinda need these
//
#include "PropPage.H"
#include "Resource.H"
#include "Guids.H"

class CWebImageGeneralPage : public CPropertyPage {

  public:
    static IUnknown *Create(IUnknown *pUnkOuter);

    // constructor and destructor
    //
    CWebImageGeneralPage(IUnknown *pUnkOuter);
    virtual ~CWebImageGeneralPage();

  private:
    virtual BOOL DialogProc(HWND, UINT, WPARAM, LPARAM);

};

DEFINE_PROPERTYPAGEOBJECT(WebImageGeneral,
    &CLSID_WebImageGeneralPage,
    "WebImage General Propery Page",
    CWebImageGeneralPage::Create,
    IDD_PROPPAGE_WEBIMAGEGENERAL,
    IDS_WEBIMAGE_GENERALPAGETITLE,
    IDS_WEBIMAGE_GENERALDOCSTRING,
    "vb.hlp",
    0);

#define _WEBIMAGEPROPPAGE_H_
#endif // _WEBIMAGEPROPPAGE_H_


BTTNCUR.C

/*
 * BTTNCUR.C
 * Buttons & Cursors Version 1.1, March 1993
 *
 * Public functions to generate different states of toolbar buttons from
 * a single bitmap.  States are normal, pressed, checked, and disabled.
 *
 * Copyright (c)1992-1996 Microsoft Corporation, All Rights Reserved,
 * as applied to redistribution of this source code in source form
 * License is granted to use of compiled code in shipped binaries.
 */

#define STRICT
#include <windows.h>
#include <memory.h>
#include "bttncur.h"
#include "bttncuri.h"

//Display sensitive information
TOOLDISPLAYDATA     tdd;

//Library instance
HINSTANCE           ghInst;
/*
23/1/94 (kerenm)
Add intance count for 32bit dll
*/
int                 cInstances = 0;

//Cache GDI objects to speed drawing.
HDC     hDCGlyphs    = NULL;
HDC     hDCMono      = NULL;
HBRUSH  hBrushDither = NULL;

//Standard images to use in case caller doesn't provide them
HBITMAP rghBmpStandardImages[3];

//Standard button colors.
const COLORREF crStandard[4]={ RGB(0, 0, 0)          //STDCOLOR_BLACK
                      , RGB(128, 128, 128)    //STDCOLOR_DKGRAY
                      , RGB(192, 192, 192)    //STDCOLOR_LTGRAY
                      , RGB(255, 255, 255)};  //STDCOLOR_WHITE

/*
 * Mapping from image identifier to button type (command/attribute).
 * Version 1.00 of this DLL has no attribute images defined, so
 * the code will only support three states for each command
 * button.  Any state is, however, valid for an application
 * defined image.
 */

UINT mpButtonType[TOOLIMAGE_MAX-TOOLIMAGE_MIN+1]=
      {
      BUTTONTYPE_COMMAND, BUTTONTYPE_COMMAND, BUTTONTYPE_COMMAND,
      BUTTONTYPE_COMMAND, BUTTONTYPE_COMMAND, BUTTONTYPE_COMMAND,
      BUTTONTYPE_COMMAND, BUTTONTYPE_COMMAND, BUTTONTYPE_COMMAND
      };

/*
 * LibMain
 *
 * Purpose:
 *  DLL-specific entry point called from LibEntry.  Initializes
 *  global variables and loads standard image bitmaps.
 *
 * Parameters:
 *  hInstance       HANDLE instance of the DLL.
 *  wDataSeg        WORD segment selector of the DLL's data segment.
 *  wHeapSize       WORD byte count of the heap.
 *  lpCmdLine       LPSTR to command line used to start the module.
 *
 * Return Value:
 *  HANDLE          Instance handle of the DLL.
 */

BOOL xxxLibMain(HINSTANCE hInstance)
   {
    int i;

/*
23/1/94 (kerenm)
For WIN32S - should done only once. On NT ghInst is instance dataso it will
always be zero.
*/
#ifdef WIN32
   if (ghInst) {
      return(TRUE);
   }
#endif
   ghInst=hInstance;

    tdd.uDPI     =96;
   tdd.cyBar    =CYBUTTONBAR96;
   tdd.cxButton =TOOLBUTTON_STD96WIDTH;
   tdd.cyButton =TOOLBUTTON_STD96HEIGHT;
   tdd.cxImage  =TOOLBUTTON_STD96IMAGEWIDTH;
   tdd.cyImage  =TOOLBUTTON_STD96IMAGEHEIGHT;
   tdd.uIDImages=IDB_STANDARDIMAGES96;

   for (i=0; i < 3; i++)
      {
      rghBmpStandardImages[i]=LoadBitmap(hInstance
         , MAKEINTRESOURCE(IDB_STANDARDIMAGESMIN+i));

      if (NULL==rghBmpStandardImages[i])
         return FALSE;
      }

   //Perform global initialization.
   if (ToolButtonInit())
      {
      CursorsCache(hInstance);

      return TRUE;
      }

   return FALSE;

   }

#ifdef WIN32

void FAR PASCAL WEP(int);

BOOL WINAPI DllMain
(
   HINSTANCE hInstance,
   ULONG     Reason,
   PCONTEXT  Context
)
{
   OutputDebugString("bttncur LibMain:  bttncur.dll loaded\r\n");

   UNREFERENCED_PARAMETER(Context);

   if (Reason == DLL_PROCESS_DETACH) {

#ifdef WIN32
     if (!--cInstances) {
        WEP(0);
     }
#else
      WEP(0);
#endif
      return TRUE;
   }
   else if (Reason != DLL_PROCESS_ATTACH)
      return TRUE;

/* 1/23/94   */
#ifdef WIN32
   cInstances++;
#endif
   return xxxLibMain(hInstance);

}

#else

HANDLE FAR PASCAL LibMain(HANDLE hInstance, WORD wDataSeg
                    , WORD cbHeapSize, LPSTR lpCmdLine)
   {

    //Perform global initialization.
   if (xxxLibMain(hInstance))
      if (0!=cbHeapSize)
         UnlockData(0);

   return hInstance;

   }

#endif

/*
 * WEP
 *
 * Purpose:
 *  Required DLL Exit function.  Does nothing.
 *
 * Parameters:
 *  bSystemExit     BOOL indicating if the system is being shut
 *                  down or the DLL has just been unloaded.
 *
 * Return Value:
 *  void
 *
 */

void FAR PASCAL WEP(int bSystemExit)
   {
   /*
    * **Developers:  Note that WEP is called AFTER Windows does any
    *                automatic task cleanup.  You may see warnings for
    *                that two DCs, a bitmap, and a brush, were not
    *                deleted before task termination.  THIS IS NOT A
    *                PROBLEM WITH THIS CODE AND IT IS NOT A BUG.  This
    *                WEP function is properly called and performs the
    *                cleanup as appropriate.  The fact that Windows is
    *                calling WEP after checking task cleanup is not
    *                something we can control.  Just to prove it, the
    *                OutputDebugStrings in this and ToolButtonFree
    *                show that the code is exercised.
    */

#ifdef DEBUG
   OutputDebugString ("BttnCur WEP Entry\r\n");
#endif
   CursorsFree();
   ToolButtonFree();
#ifdef DEBUG
   OutputDebugString ("BttnCur WEP Exit\r\n");
#endif
   return;
   }

/*
 * UIToolConfigureForDisplay
 * Public API
 *
 * Purpose:
 *  Initializes the library to scale button images for the display type.
 *  Without calling this function the library defaults to 96 DPI (VGA).
 *  By calling this function an application acknowledges that it must
 *  use the data returned from this function to configure itself for
 *  the display.
 *
 * Parameters:
 *  lpDD            LPTOOLDISPLAYDATA to fill with the display-sensitive
 *                  size values.
 *
 * Return Value:
 *  BOOL            TRUE if the sizes were obtained, FALSE otherwise.
 */

BOOL WINAPI UIToolConfigureForDisplay(LPTOOLDISPLAYDATA lpDD)
   {
   int         cy;
   HDC         hDC;

   if (NULL==lpDD || IsBadWritePtr(lpDD, sizeof(TOOLDISPLAYDATA)))
      return FALSE;

   /*
    * Determine the aspect ratio of the display we're currently
    * running on and calculate the necessary information.
    *
    * By retrieving the logical Y extent of the display driver, you
    * only have limited possibilities:
    *      LOGPIXELSY      Display
    *      ----------------------------------------
    *         48             CGA    (unsupported)
    *         72             EGA
    *         96             VGA
    *        120             8514/a (i.e. HiRes VGA)
    */

   hDC=GetDC(NULL);

   if (NULL==hDC)
      return FALSE;

   cy=GetDeviceCaps(hDC, LOGPIXELSY);
   ReleaseDC(NULL, hDC);

   /*
    * Instead of single comparisons, check ranges instead, so in case
    * we get something funky, we'll act reasonable.
    */
   if (72 >=cy)
      {
      lpDD->uDPI     =72;
      lpDD->cyBar    =CYBUTTONBAR72;
      lpDD->cxButton =TOOLBUTTON_STD72WIDTH;
      lpDD->cyButton =TOOLBUTTON_STD72HEIGHT;
      lpDD->cxImage  =TOOLBUTTON_STD72IMAGEWIDTH;
      lpDD->cyImage  =TOOLBUTTON_STD72IMAGEHEIGHT;
      lpDD->uIDImages=IDB_STANDARDIMAGES72;
      }
   else
      {
      if (72 < cy && 120 > cy)
         {
         lpDD->uDPI     =96;
         lpDD->cyBar    =CYBUTTONBAR96;
         lpDD->cxButton =TOOLBUTTON_STD96WIDTH;
         lpDD->cyButton =TOOLBUTTON_STD96HEIGHT;
         lpDD->cxImage  =TOOLBUTTON_STD96IMAGEWIDTH;
         lpDD->cyImage  =TOOLBUTTON_STD96IMAGEHEIGHT;
         lpDD->uIDImages=IDB_STANDARDIMAGES96;
         }
      else
         {
         lpDD->uDPI     =120;
         lpDD->cyBar    =CYBUTTONBAR120;
         lpDD->cxButton =TOOLBUTTON_STD120WIDTH;
         lpDD->cyButton =TOOLBUTTON_STD120HEIGHT;
         lpDD->cxImage  =TOOLBUTTON_STD120IMAGEWIDTH;
         lpDD->cyImage  =TOOLBUTTON_STD120IMAGEHEIGHT;
         lpDD->uIDImages=IDB_STANDARDIMAGES120;
         }
      }

   return TRUE;
   }

/*
 * ToolButtonInit
 * Internal
 *
 * Purpose:
 *  Initializes GDI objects for drawing images through UIToolButtonDraw.
 *  If the function fails, the function has already performed proper
 *  cleanup.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded.  FALSE otherwise.
 */

static BOOL ToolButtonInit(void)
   {
   COLORREF        rgbHi;

   //DC for BitBltting the image (the glyph)
   hDCGlyphs=CreateCompatibleDC(NULL);

   //Create a monochrome DC and a brush for doing pattern dithering.
   hDCMono=CreateCompatibleDC(NULL);

   //Windows 3.0 doesn't support COLOR_BTNHIGHLIGHT, so leave it white.
   if (0x0300 < (UINT)GetVersion())
      rgbHi=GetSysColor(COLOR_BTNHIGHLIGHT);
   else
      rgbHi=crStandard[STDCOLOR_WHITE];

   hBrushDither=HBrushDitherCreate(GetSysColor(COLOR_BTNFACE), rgbHi);

   if (NULL==hDCGlyphs || NULL==hDCMono || NULL==hBrushDither)
      {
      //On failure, cleanup whatever might have been allocated.
      ToolButtonFree();
      return FALSE;
      }

   return TRUE;
   }

/*
 * ToolButtonFree
 * Internal
 *
 * Purpose:
 *  Free all GDI allocations made during initialization.  Note that the
 *  DEBUG output included here shows that WEP is called and cleanup actually
 *  occurs.  However, if you watch debug output in DBWIN or on a terminal,
 *  the debugging version of Windows does automatic app cleanup before WEP
 *  is called, leading some to believe that this code is buggy.  The
 *  debug output below shows that we do perform all necessary cleanup.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

static void ToolButtonFree(void)
   {
   UINT        i;

   if (NULL!=hDCMono)
      DeleteDC(hDCMono);
   hDCMono=NULL;

   if (NULL!=hDCGlyphs)
      DeleteDC(hDCGlyphs);
   hDCGlyphs=NULL;

   if (NULL!=hBrushDither)
      DeleteObject(hBrushDither);
   hBrushDither=NULL;

   for (i=0; i < 3; i++)
      {
      if (NULL!=rghBmpStandardImages[i])
         DeleteObject(rghBmpStandardImages[i]);
      rghBmpStandardImages[i]=NULL;
      }

   return;
   }

/*
 * HBrushDitherCreate
 * Internal
 *
 * Purpose:
 *  Creates and returns a handle to a pattern brush created from
 *  an 8*8 monochrome pattern bitmap.  We use the button face and
 *  highlight colors to indicate the resulting colors of a PatBlt
 *  using this brush.
 *
 * Parameters:
 *  rgbFace         COLORREF of the button face color.
 *  rgbHilight      COLORREF of the button highlight color.
 *
 * Return Value:
 *  HBITMAP         Handle to the dither bitmap.
 */

static HBRUSH HBrushDitherCreate(COLORREF rgbFace, COLORREF rgbHilight)
   {
   struct  //BITMAPINFO with 16 colors
      {
      BITMAPINFOHEADER    bmiHeader;
      RGBQUAD             bmiColors[16];
      } bmi;

   HBRUSH          hBrush=NULL;
   DWORD           patGray[8];
   HDC             hDC;
   HBITMAP         hBmp;
   static COLORREF rgbFaceOld   =0xFFFFFFFF;  //Initially an impossible color
   static COLORREF rgbHilightOld=0xFFFFFFFF;  //so at first we always create

   /*
    * If the colors haven't changed from last time, just return the
    * existing brush.
    */
   if (rgbFace==rgbFaceOld && rgbHilight==rgbHilightOld)
      return hBrushDither;

   rgbFaceOld=rgbFace;
   rgbHilightOld=rgbHilight;

   /*
    * We're going to create an 8*8 brush for PatBlt using the
    * button face color and button highlight color.  We use this
    * brush to affect the pressed state and the disabled state.
    */
   bmi.bmiHeader.biSize         = sizeof(BITMAPINFOHEADER);
   bmi.bmiHeader.biWidth        = 8;
   bmi.bmiHeader.biHeight       = 8;
   bmi.bmiHeader.biPlanes       = 1;
   bmi.bmiHeader.biBitCount     = 1;
   bmi.bmiHeader.biCompression  = BI_RGB;
   bmi.bmiHeader.biSizeImage    = 0;
   bmi.bmiHeader.biXPelsPerMeter= 0;
   bmi.bmiHeader.biYPelsPerMeter= 0;
   bmi.bmiHeader.biClrUsed      = 0;
   bmi.bmiHeader.biClrImportant = 0;

   bmi.bmiColors[0].rgbBlue     = GetBValue(rgbFace);
   bmi.bmiColors[0].rgbGreen    = GetGValue(rgbFace);
   bmi.bmiColors[0].rgbRed      = GetRValue(rgbFace);
   bmi.bmiColors[0].rgbReserved = 0;

   bmi.bmiColors[1].rgbBlue     = GetBValue(rgbHilight);
   bmi.bmiColors[1].rgbGreen    = GetGValue(rgbHilight);
   bmi.bmiColors[1].rgbRed      = GetRValue(rgbHilight);
   bmi.bmiColors[1].rgbReserved = 0;

   //Create the byte array for CreateDIBitmap.
   patGray[6]=patGray[4]=patGray[2]=patGray[0]=0x5555AAAAL;
   patGray[7]=patGray[5]=patGray[3]=patGray[1]=0xAAAA5555L;

   //Create the bitmap
   hDC=GetDC(NULL);
   hBmp=CreateDIBitmap(hDC, &bmi.bmiHeader, CBM_INIT, patGray
                  , (LPBITMAPINFO)&bmi, DIB_RGB_COLORS);
   ReleaseDC(NULL, hDC);

   //Create the brush from the bitmap
   if (NULL!=hBmp)
      {
      hBrush=CreatePatternBrush(hBmp);
      DeleteObject(hBmp);
      }

   /*
    * If we could recreate a brush, clean up and make it the current
    * pattern.  Otherwise the best we can do it return the old one,
    * which will be colored wrong, but at least it works.
    */
   if (NULL!=hBrush)
      {
      if (NULL!=hBrushDither)
         DeleteObject(hBrushDither);

      hBrushDither=hBrush;
      }

   return hBrushDither;
   }

/*
 * UIToolButtonDraw
 * Public API
 *
 * Purpose:
 *  Draws the complete image of a toolbar-style button with a given
 *  image in the center and in a specific state.  The button is drawn
 *  on a specified hDC at a given location, so this function is useful
 *  on standard owner-draw buttons as well as on toolbar controls that
 *  have only one window but show images of multiple buttons.
 *
 * Parameters:
 *  hDC             HDC on which to draw.
 *  x, y            int coordinates at which to draw.
 *  dx, dy          int dimensions of the *button*, not necessarily the image.
 *  hBmp            HBITMAP from which to draw the image.
 *  bmx, bmy        int dimensions of each bitmap in hBmp.  If hBmp is NULL
 *                  then these are forced to the standard sizes.
 *  iImage          int index to the image to draw in the button
 *  uStateIn        UINT containing the state index for the button and the
 *                  color control bits.
 *
 * Return Value:
 *  BOOL            TRUE if drawing succeeded, FALSE otherwise meaning that
 *                  hDC is NULL or hBmp is NULL and iImage is not a valid
 *                  index for a standard image.
 */

BOOL WINAPI UIToolButtonDraw(HDC hDC, int x, int y, int dx, int dy
   , HBITMAP hBmp, int bmx, int bmy, int iImage, UINT uStateIn)
   {
   return UIToolButtonDrawTDD(hDC, x, y, dx, dy, hBmp, bmx, bmy, iImage
      , uStateIn, &tdd);
   }

/*
 * UIToolButtonDrawTDD
 * Public API
 *
 * Purpose:
 *  Draws the complete image of a toolbar-style button with a given
 *  image in the center and in a specific state.  The button is drawn
 *  on a specified hDC at a given location, so this function is useful
 *  on standard owner-draw buttons as well as on toolbar controls that
 *  have only one window but show images of multiple buttons.
 *
 *  This is the same as UIToolButtonDraw but adds the pTDD configuration
 *  structure.  UIToolButtonDraw calls us with that pointing to the
 *  default 96dpi structure.
 *
 * Parameters:
 *  hDC             HDC on which to draw.
 *  x, y            int coordinates at which to draw.
 *  dx, dy          int dimensions of the *button*, not necessarily the image.
 *  hBmp            HBITMAP from which to draw the image.
 *  bmx, bmy        int dimensions of each bitmap in hBmp.  If hBmp is NULL
 *                  then these are forced to the standard sizes.
 *  iImage          int index to the image to draw in the button
 *  uStateIn        UINT containing the state index for the button and the
 *                  color control bits.
 *  pTDD            LPTOOLDISPLAYDATA containing display configuration.
 *                  Can be NULL if hBmp is non-NULL.
 *
 * Return Value:
 *  BOOL            TRUE if drawing succeeded, FALSE otherwise meaning that
 *                  hDC is NULL or hBmp is NULL and iImage is not a valid
 *                  index for a standard image.
 */

BOOL WINAPI UIToolButtonDrawTDD(HDC hDC, int x, int y, int dx, int dy
   , HBITMAP hBmp, int bmx, int bmy, int iImage, UINT uStateIn
   , LPTOOLDISPLAYDATA pTDD)
   {
   static COLORREF crSys[5];  //Avoid stack arrays in DLLs: use static
   UINT            uState=(UINT)LOBYTE((WORD)uStateIn);
   UINT            uColors=(UINT)HIBYTE((WORD)uStateIn & PRESERVE_ALL);
   int             xOffsetGlyph, yOffsetGlyph;
   int             i, iSaveDC;
   HDC             hMemDC;
   HGDIOBJ         hObj;
   HBRUSH          hBR;
   HBITMAP         hBmpT;
   HBITMAP         hBmpMono;
   HBITMAP         hBmpMonoOrg;
   HBITMAP         hBmpSave=NULL;

   if (NULL==hDC)
      return FALSE;

   /*
    * If we're given no image bitmap, then use the standard and validate the
    * image index.  We also enforce the standard bitmap size and the size of
    * the button (as requested by User Interface designers).
    */
   if (NULL==hBmp && !(uState & BUTTONGROUP_BLANK))
      {
      hBmp=rghBmpStandardImages[pTDD->uIDImages-IDB_STANDARDIMAGESMIN];

      bmx=pTDD->cxImage;            //Force bitmap dimensions
      bmy=pTDD->cyImage;

      dx=pTDD->cxButton;            //Force button dimensions
      dy=pTDD->cyButton;

      if (iImage > TOOLIMAGE_MAX)
         return FALSE;

      /*
       * If we are using a standard command button, verify that the state
       * does not contain the LIGHTFACE group which only applies to
       * attribute buttons.
       */
      if (BUTTONTYPE_COMMAND==mpButtonType[iImage]
         && (uState & BUTTONGROUP_LIGHTFACE))
         return FALSE;
      }

   //Create a dithered bitmap.
   hBmpMono=CreateBitmap(dx-2, dy-2, 1, 1, NULL);

   if (NULL==hBmpMono)
      return FALSE;

   hBmpMonoOrg=(HBITMAP)SelectObject(hDCMono,  hBmpMono);

   //Save the DC state before we munge on it.
   iSaveDC=SaveDC(hDC);

   /*
    * Draw a button sans image.  This also fills crSys with the system
    * colors for us which has space for five colors.  We don't use the
    * fifth, the frame color, in this function.
    */
   DrawBlankButton(hDC, x, y, dx, dy, (BOOL)(uState & BUTTONGROUP_DOWN), crSys);

   //Shift coordinates to account for the button's border
   x++;
   y++;
   dx-=2;
   dy-=2;

   /*
    * Determine the offset necessary to center the image but also reflect
    * the pushed-in state, which means just adding 1 to the up state.
    */
   i=(uState & BUTTONGROUP_DOWN) ? 1 : 0;
   xOffsetGlyph=((dx-bmx) >> 1)+i;
   yOffsetGlyph=((dy-bmy) >> 1)+i;

   //Select the given image bitmap into the glyph DC before calling MaskCreate
   if (NULL!=hBmp)
      hBmpSave=(HBITMAP)SelectObject(hDCGlyphs, hBmp);

   /*
    * Draw the face on the button.  If we have an up or [mouse]down
    * button then we can just draw it as-is.  For indeterminate,
    * disabled, or down disabled we have to gray the image and possibly
    * add a white shadow to it (disabled/down disabled).
    *
    * Also note that for the intermediate state we first draw the normal
    * up state, then proceed to add disabling looking highlights.
    */

   //Up, mouse down, down, indeterminate
   if ((uState & BUTTONGROUP_ACTIVE) && !(uState & BUTTONGROUP_BLANK))
      {
      BOOL            fColorsSame=TRUE;

      /*
       * In here we pay close attention to the system colors.  Where
       * the source image is black, we paint COLOR_BTNTEXT.  Where
       * light gray, we paint COLOR_BTNFACE.  Where dark gray we paint
       * COLOR_BTNSHADOW, and where white we paint COLOR_BTNHILIGHT.
       *
       * The uColors variable contains flags to prevent color
       * conversion.  To do a little optimization, we just do a
       * single BitBlt if we're preserving all colors or if no colors
       * are different than the standards, which is by far the most
       * common case.  Otherwise, cycle through the four colors we can
       * convert and do a BitBlt that converts it to the system color.
       */

      //See what colors are different.
      for (i=STDCOLOR_BLACK; i<=STDCOLOR_WHITE; i++)
         fColorsSame &= (crSys[i]==crStandard[i]);

      if (PRESERVE_ALL==uColors || fColorsSame)
         {
         BitBlt(hDC, x+xOffsetGlyph, y+yOffsetGlyph, bmx, bmy
               , hDCGlyphs, iImage*bmx, 0, SRCCOPY);
         }
      else
         {
         /*
          * Cycle through hard-coded colors and create a mask that has all
          * regions of that color in white and all other regions black.
          * Then we select a pattern brush of the color to convert to:
          * if we aren't converting the color then we use a brush of
          * the standard hard-coded color, otherwise we use the actual
          * system color.  The ROP_DSPDxax means that anything that's
          * 1's in the mask get the pattern, anything that's 0 is unchanged
          * in the destination.
          *
          * To prevent too many Blts to the screen, we use an intermediate
          * bitmap and DC.
          */

         hMemDC=CreateCompatibleDC(hDC);

         //Make sure conversion of monochrome to color stays B&W
         SetTextColor(hMemDC, 0L);                     //0's in mono -> 0
         SetBkColor(hMemDC, (COLORREF)0x00FFFFFF);     //1's in mono -> 1

         hBmpT=CreateCompatibleBitmap(hDC, bmx, bmy);
         SelectObject(hMemDC, hBmpT);

         //Copy the unmodified bitmap to the temporary bitmap
         BitBlt(hMemDC, 0, 0, bmx, bmy, hDCGlyphs, iImage*bmx, 0, SRCCOPY);

         for (i=STDCOLOR_BLACK; i<=STDCOLOR_WHITE; i++)
            {
            //Convert pixels of the color to convert to 1's in the mask
            SetBkColor(hDCGlyphs, crStandard[i]);
            BitBlt(hDCMono, 0, 0, bmx, bmy, hDCGlyphs, iImage*bmx, 0, SRCCOPY);

            //Preserve or modify the color depending on the flag.
            hBR=CreateSolidBrush((uColors & (1 << i))
                            ? crStandard[i] : crSys[i]);

            if (NULL!=hBR)
               {
               hObj=SelectObject(hMemDC, hBR);

               if (NULL!=hObj)
                  {
                  BitBlt(hMemDC, 0, 0, dx-1, dy-1, hDCMono, 0, 0, ROP_DSPDxax);
                  SelectObject(hMemDC, hObj);
                  }

               DeleteObject(hBR);
               }
            }

         //Now put the final version on the display and clean up
         BitBlt(hDC, x+xOffsetGlyph, y+yOffsetGlyph, dx-1, dy-1
               , hMemDC, 0, 0, SRCCOPY);

         DeleteDC(hMemDC);
         DeleteObject(hBmpT);

         }
      }

   //Disabled and indeterminate states (unless we're blank)
   if ((uState & BUTTONGROUP_DISABLED || ATTRIBUTEBUTTON_INDETERMINATE==uState)
      && !(uState & BUTTONGROUP_BLANK))
      {
      //Grayed state (up or down, no difference)
      MaskCreate(iImage, dx, dy, bmx, bmy, xOffsetGlyph, yOffsetGlyph, 0);

      //Make sure conversion of monochrome to color stays B&W
      SetTextColor(hDC, 0L);                     //0's in mono -> 0
      SetBkColor(hDC, (COLORREF)0x00FFFFFF);     //1's in mono -> 1

      //If we're disabled, up or down, draw the highlighted shadow.
      if (uState & BUTTONGROUP_DISABLED)
         {
         hBR=CreateSolidBrush(crSys[SYSCOLOR_HILIGHT]);

         if (NULL!=hBR)
            {
            hObj=SelectObject(hDC, hBR);

            if (NULL!=hObj)
               {
               //Draw hilight color where we have 0's in the mask
               BitBlt(hDC, x+1, y+1, dx-2, dy-2, hDCMono, 0, 0, ROP_PSDPxax);
               SelectObject(hDC, hObj);
               }
            DeleteObject(hBR);
            }
         }

      //Draw the gray image.
      hBR=CreateSolidBrush(crSys[SYSCOLOR_SHADOW]);

      if (NULL!=hBR)
         {
         hObj=SelectObject(hDC, hBR);

         if (NULL!=hObj)
            {
            //Draw the shadow color where we have 0's in the mask
            BitBlt(hDC, x, y, dx-2, dy-2, hDCMono, 0, 0, ROP_PSDPxax);
            SelectObject(hDC, hObj);
            }

         DeleteObject(hBR);
         }
      }

   //If the button is selected do the dither brush avoiding the glyph
   if (uState & BUTTONGROUP_LIGHTFACE)
      {
      HBRUSH      hBRDither;

      /*
       * Get the dither brush.  This function will recreate it if
       * necessary or return the global one if the colors already match.
       */
      hBRDither=HBrushDitherCreate(crSys[SYSCOLOR_FACE], crSys[SYSCOLOR_HILIGHT]);
      hObj=SelectObject(hDC, hBRDither);

      if (NULL!=hObj)
         {
         /*
          * The mask we create now determines where the dithering
          * ends up.  In the down disabled state, we have to preserve
          * the highlighted shadow, so the mask we create must have
          * two masks of the original glyph, one of them offset by
          * one pixel in both x & y.  For the indeterminate state,
          * we have to mask all highlighted areas.  The state passed
          * to MaskCreate matters here (we've used zero before).
          */
         MaskCreate(iImage, dx, dy, bmx, bmy
                  , xOffsetGlyph-1, yOffsetGlyph-1, uState);

         //Convert monochrome masks to B&W color bitmap in the BitBlt.
         SetTextColor(hDC, 0L);
         SetBkColor(hDC, (COLORREF)0x00FFFFFF);

         /*
          * Only draw the dither brush where the mask is 1's.  For
          * the indeterminate state we have to not overdraw the
          * shadow highlight so we use dx-3, dy-3 instead of dx-1
          * and dy-1.  We do this whether or not we're blank.
          */
         i=(ATTRIBUTEBUTTON_INDETERMINATE==uState
            || BLANKBUTTON_INDETERMINATE==uState) ? 3 : 1;

         BitBlt(hDC, x+1, y+1, dx-i, dy-i, hDCMono, 0, 0, ROP_DSPDxax);
         SelectObject(hDC, hObj);
         }

      //DO NOT delete hBRDither!  It's a reference to a shared global.
      }

   //Cleanup hDCGlyphs:  Must do AFTER calling MaskCreate
   if (NULL!=hBmpSave)
      SelectObject(hDCGlyphs, hBmpSave);

   SelectObject(hDCMono,   hBmpMonoOrg);
   DeleteObject(hBmpMono);

   //Restore everything in the DC.
   RestoreDC(hDC, iSaveDC);
   return TRUE;
   }

/*
 * DrawBlankButton
 *
 * Purpose:
 *  Draws a button with no face using the current system colors in either
 *  an up or down state.
 *
 * Parameters:
 *  hDC             HDC on which to draw
 *  x, y            int coordinates where we start drawing
 *  dx,dy           int size of the button
 *  fDown           BOOL indicating the up or down state of the button
 *  pcr             COLORREF FAR * to five colors in which we store text,
 *                  shadow, face, highlight, and frame colors.  This is
 *                  a matter of convenience for the caller, since we have
 *                  to load these colors anyway we might as well send them
 *                  back.
 *
 * Return Value:
 *  None
 */

static void DrawBlankButton(HDC hDC, int x, int y, int dx, int dy
   , BOOL fDown, COLORREF FAR *pcr)
   {
   //Get the current system colors for buttons.
   pcr[0]=GetSysColor(COLOR_BTNTEXT);
   pcr[1]=GetSysColor(COLOR_BTNSHADOW);
   pcr[2]=GetSysColor(COLOR_BTNFACE);

   //Windows 3.0 doesn't support COLOR_BTNHIGHLIGHT, so leave it white.
   if (0x0300 < (UINT)GetVersion())
      pcr[3]=GetSysColor(COLOR_BTNHIGHLIGHT);
   else
      pcr[3]=crStandard[STDCOLOR_WHITE];

   pcr[4]=GetSysColor(COLOR_WINDOWFRAME);

   //Draw the border around the button.
   PatB(hDC, x+1,    y,      dx-2, 1,    pcr[4]);
   PatB(hDC, x+1,    y+dy-1, dx-2, 1,    pcr[4]);
   PatB(hDC, x,      y+1,    1,    dy-2, pcr[4]);
   PatB(hDC, x+dx-1, y+1,    1,    dy-2, pcr[4]);

   //Shift coordinates to account for the border we just drew
   x++;
   y++;
   dx-=2;
   dy-=2;

   //Paint the interior grey as a default.
   PatB(hDC, x, y, dx, dy, pcr[2]);

   /*
    * Draw shadows and highlights.  The DOWN grouping that contains
    * down, mouse down, and down disabled are drawn depressed.  Up,
    * indeterminate, and disabled are drawn up.
    */

   if (fDown)
      {
      PatB(hDC, x, y, 1,  dy, pcr[1]);
      PatB(hDC, x, y, dx, 1,  pcr[1]);

}
   else
      {
      //Normal button look.
      PatB(hDC, x, y, 1,    dy-1, pcr[3]);
      PatB(hDC, x, y, dx-1, 1,    pcr[3]);

      PatB(hDC, x+dx-1, y,      1,  dy, pcr[1]);
      PatB(hDC, x,      y+dy-1, dx, 1,  pcr[1]);

      PatB(hDC, x+1+dx-3, y+1,    1,    dy-2, pcr[1]);
      PatB(hDC, x+1,      y+dy-2, dx-2, 1,    pcr[1]);
      }

   return;
   }

/*
 * PatB
 * Internal
 *
 * Purpose:
 *  A more convenient PatBlt operation for drawing button borders and
 *  highlights.
 *
 * Parameters:
 *  hDC             HDC on which to paint.
 *  x, y            int coordinates at which to paint.
 *  dx, dy          int dimensions of rectangle to paint.
 *  rgb             COLORREF to use as the background color.
 *
 * Return Value:
 *  None
 */

static void PatB(HDC hDC, int x, int y, int dx, int dy, COLORREF rgb)
   {
   RECT        rc;

   SetBkColor(hDC, rgb);
   SetRect(&rc, x, y, x+dx, y+dy);
   ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
   }

/*
 * MaskCreate
 * Internal
 *
 * Purpose:
 *  Creates a monochrome mask bitmap of the given image at the given offset
 *  in the global hDCMono.  Anywhere in the image that you have the light
 *  gray (STDCOLOR_LTGRAY) or the white highlight (STDCOLOR_WHITE) you get
 *  get 1's.  All other pixels are 0's
 *
 * Parameters:
 *  iImage          UINT index of the image for which to create a mask.
 *  dx, dy          int dimensions of the button.
 *  bmx, bmy        int dimensions of the bitmap to use.
 *  xOffset         int offset for x inside hDCMono where we paint.
 *  yOffset         int offset for y inside hDCMono where we paint.
 *  uState          UINT state of the image.  Special cases are made
 *                  for ATTRIBUTEBUTTON_DOWNDISABLED and
 *                  ATTRIBUTEBUTTON_INDETERMINATE.  In any case where you
 *                  do not want a special case, pass zero here, regardless
 *                  of the true button state.
 *
 * Return Value:
 *  None
 */

static void MaskCreate(UINT iImage, int dx, int dy, int bmx, int bmy
   ,int xOffset, int yOffset, UINT uState)
   {
   //Initalize whole area with zeros
   PatBlt(hDCMono, 0, 0, dx, dy, WHITENESS);

   if (uState & BUTTONGROUP_BLANK)
      return;

   //Convert face colored pixels to 1's. all others to black.
   SetBkColor(hDCGlyphs, crStandard[STDCOLOR_LTGRAY]);
   BitBlt(hDCMono, xOffset, yOffset, bmx, bmy, hDCGlyphs, iImage*bmx, 0, SRCCOPY);

   //In the indeterminate state, don't turn highlight's to 1's. Leave black.
   if (ATTRIBUTEBUTTON_INDETERMINATE!=uState)
      {
      //Convert highlight colored pixels to 1's and OR them with the previous.
      SetBkColor(hDCGlyphs, crStandard[STDCOLOR_WHITE]);
      BitBlt(hDCMono, xOffset, yOffset, bmx, bmy, hDCGlyphs, iImage*bmx, 0, SRCPAINT);
      }

   /*
    * For the down disabled state, AND this same mask with itself at an
    * offset of 1, which accounts for the highlight shadow.
    */
   if (ATTRIBUTEBUTTON_DOWNDISABLED==uState)
      BitBlt(hDCMono, 1, 1, dx-1, dy-1, hDCMono,  0, 0, SRCAND);

   return;
   }


BTTNCURI.H

/*
 * BTTNCURI.H
 *
 * Private include file for the Button Images and Cursors DLL.
 *
 * Copyright (c)1992-1996 Microsoft Corporation, All Right Reserved,
 * as applied to redistribution of this source code in source form
 * License is granted to use of compiled code in shipped binaries.
 */

#ifdef __cplusplus
extern "C"
        {
#endif

//Function prototypes.

//BTTNCUR.C
//HANDLE FAR  PASCAL LibMain(HANDLE, WORD, WORD, LPSTR);
//void   FAR  PASCAL WEP(int);
static BOOL        ToolButtonInit(void);
static void        ToolButtonFree(void);
static HBRUSH      HBrushDitherCreate(COLORREF, COLORREF);
static void        DrawBlankButton(HDC, int, int, int, int, BOOL, COLORREF FAR *);
static void        PatB(HDC, int, int, int, int, COLORREF);
static void        MaskCreate(UINT, int, int, int, int, int, int, UINT);

//CURSORS.C
void               CursorsCache(HINSTANCE);
void               CursorsFree(void);

/*
 * Wierd Wild Wooly Waster (raster) Ops for special bltting.  See the
 * Windows SDK reference on Raster Operation Codes for explanation of
 * these.  The DSPDxax and PSDPxax is a reverse-polish notation for
 * operations where D==Destination, S==Source, P==Patterm, a==AND,
 * x==XOR.  Both of these codes are actually described in Programming
 * Windows by Charles Petzold, Second Edition, pages 622-624.
 */
#define ROP_DSPDxax  0x00E20746
#define ROP_PSDPxax  0x00B8074A

/*
 * Color indices into an array of standard hard-coded black, white, and
 * gray colors.
 */

#define STDCOLOR_BLACK      0
#define STDCOLOR_DKGRAY     1
#define STDCOLOR_LTGRAY     2
#define STDCOLOR_WHITE      3

/*
 * Color indices into an array of system colors, matching those in
 * the hard-coded array for the colors they replace.
 */

#define SYSCOLOR_TEXT       0
#define SYSCOLOR_SHADOW     1
#define SYSCOLOR_FACE       2
#define SYSCOLOR_HILIGHT    3

/*
 * Button types, used internally to distinguish command buttons from
 * attribute buttons to enforce three-state or six-state possibilities.
 * Command buttons can only have three states (up, mouse down, disabled)
 * while attribute buttons add (down, down disabled, and indeterminate).
 */

#define BUTTONTYPE_COMMAND      0
#define BUTTONTYPE_ATTRIBUTE    1

#ifdef __cplusplus
        }
#endif


CURSORS.C

/*
 * CURSORS.C
 * Buttons & Cursors Version 1.1, March 1993
 *
 * Public functions to retrieve new cursors from the BTTNCUR DLL based
 * on ordinal to prevent applications from necessarily calling LoadCursor
 * directly on the DLL.
 *
 * Copyright (c)1992-1996 Microsoft Corporation, All Rights Reserved,
 * as applied to redistribution of this source code in source form
 * License is granted to use of compiled code in shipped binaries.
 */

#define STRICT
#include <windows.h>
#include "bttncur.h"
#include "bttncuri.h"

/*
 * The +1 is because MAX is the highest allowable number and MIN is not
 * necessarily zero.
 */
HCURSOR rgHCursors[IDC_NEWUICURSORMAX-IDC_NEWUICURSORMIN+1];

/*
 * CursorsCache
 * Internal
 *
 * Purpose:
 *  Loads all the cursors available through NewUICursorLoad into
 *  a global array.  This way we can clean up all the cursors without
 *  placing the burden on the application.
 *
 * Parameters:
 *  hInst           HANDLE of the DLL instance.
 *
 * Return Value:
 *  None.  If any of the LoadCursor calls fail, then the corresponding
 *  array entry is NULL and NewUICursorLoad will fail.  Better to fail
 *  an app getting a cursor than failing to load the app just for that
 *  reason; and app can attempt to load the cursor on startup if it's
 *  that important, and fail itself.
 */

void CursorsCache(HINSTANCE hInst)
        {
        UINT            i;

        for (i=IDC_NEWUICURSORMIN; i<=IDC_NEWUICURSORMAX; i++)
                rgHCursors[i-IDC_NEWUICURSORMIN]=LoadCursor(hInst, MAKEINTRESOURCE(i));

        return;
        }

/*
 * CursorsFree
 * Internal
 *
 * Purpose:
 *  Frees all the cursors previously loaded through CursorsCache.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CursorsFree(void)
        {
        /*
         * Note that since cursors are discardable resources and should
         * not be used with DestroyCursor, there's nothing to do here.
         * We still provide this API for compatibility and to maintain
         * symmetry.
         */
        return;
        }

/*
 * UICursorLoad
 * Public API
 *
 * Purpose:
 *  Loads and returns a handle to one of the new standard UI cursors
 *  contained in UITOOLS.DLL.  The application must not call DestroyCursor
 *  on this cursor as it is managed by the DLL.
 *
 * Parameters:
 *  iCursor         UINT index to the cursor to load which must be one
 *                  of the following values:
 *
 *                      IDC_RIGHTARROW    Right pointing standard arrow
 *                      IDC_CONTEXTHELP   Arrow with a ? (context help)
 *                      IDC_MAGNIFY       Magnifying glass for zooming
 *                      IDC_NODROP        Circle with a slash
 *                      IDC_TABLETOP      Small arrow pointing down
 *
 *                      IDC_SMALLARROWS   Thin four-headed arrow
 *                      IDC_LARGEARROWS   Wide four-headed arrow
 *                      IDC_HARROWS       Horizontal two-headed arrow
 *                      IDC_VARROWS       Vertical two-headed arrow
 *                      IDC_NESWARROWS    Two-headed arrow pointing NE<->SW
 *                      IDC_NWSEHARROWS   Two-headed arrow pointing NW<->SE
 *
 *                      IDC_HSIZEBAR      Horizontal two-headed arrow with
 *                                        a single vertical bar down the
 *                                        middle
 *
 *                      IDC_VSIZEBAR      Vertical two-headed arrow with a
 *                                        single horizontal bar down the
 *                                        middle
 *
 *                      IDC_HSPLITBAR     Horizontal two-headed arrow with
 *                                        split double vertical bars down the
 *                                        middle
 *
 *                      IDC_VSPLITBAR     Vertical two-headed arrow with split
 *                                        double horizontal bars down the
 *                                        middle
 *
 * Return Value:
 *  HCURSOR         Handle to the loaded cursor if successful, NULL
 *                  if iCursor is out of range or the function could not
 *                  load the cursor.
 */

HCURSOR WINAPI UICursorLoad(UINT iCursor)
        {
        HCURSOR     hCur=NULL;

        if ((iCursor >= IDC_NEWUICURSORMIN) && (iCursor <= IDC_NEWUICURSORMAX))
                hCur=rgHCursors[iCursor-IDC_NEWUICURSORMIN];

        return hCur;
        }


README.TXT

IMallocSpy Interface

Copyright 1994 - 1998 Microsoft Corporation

SUMMARY
=======

The CMALLSPY sample demonstrates a simple implementation of the IMallocSpy
interface. When the capability for replaceable memory allocators was
removed from 32-bit OLE, the ability for applications to provide their own
debug allocators for OLE was also removed. OLE disallows caller-provided
allocators for multithread safety and general robustness. To provide a way
for applications to monitor memory allocation, detect memory leaks, and
simulate memory failure, OLE now provides support for a simple wrapper for
the IMalloc interface called the IMallocSpy interface.

MORE INFORMATION
================

See the OLE programmer's documentation for a complete description of the
IMallocSpy interface and the CoRegisterMallocSpy and CoRevokeMallocSpy
functions.

You can also find a more complete implementation of IMallocSpy in the
OLESTD sample library in the MALSPY.C and MALSPY.H files.


CMALLSPY.CXX

//+---------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1994 - 1998 Microsoft Corporation.
//
//  File:       cmallspy.cxx
//
//  Contents:   Sample implementation of IMallocSpy.  The implementation here
//              is pretty simple.  It assumes that all we want to do is keep
//              a header on the front of all allocations so we can note
//              interesting information.
//
//  Classes:    CMallocSpy
//
//----------------------------------------------------------------------

#include <ole2.h>
#include "cmallspy.hxx"

const ULONG HEADERSIZE = 128;

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::CMallocSpy
//
//  Synopsis:   Constructor
//
//  Returns:
//
//  History:    24-Oct-94   Created.
//
//  Notes:
//
//----------------------------------------------------------------------
CMallocSpy::CMallocSpy(void)
{
    m_cRef = 0;
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::~CMallocSpy
//
//  Synopsis:   Destructor
//
//  Returns:
//
//  History:    24-Oct-94   Created.
//
//  Notes:
//
//----------------------------------------------------------------------
CMallocSpy::~CMallocSpy(void)
{
    // Nothing to do here
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::QueryInterface
//
//  Synopsis:   Only IUnknown and IMallocSpy are meaningful
//
//  Arguments:  [riid] --
//              [ppUnk] --
//
//  Returns:    S_OK or E_NOINTERFACE
//
//  History:    24-Oct-94   Created.
//
//  Notes:
//
//----------------------------------------------------------------------
HRESULT CMallocSpy::QueryInterface(REFIID riid, LPVOID *ppUnk)
{
    HRESULT hr = S_OK;

    if (IsEqualIID(riid, IID_IUnknown))
    {
        *ppUnk = (IUnknown *) this;
    }
    else if (IsEqualIID(riid, IID_IMallocSpy))
    {
        *ppUnk =  (IMalloc *) this;
    }
    else
    {
        *ppUnk = NULL;
        hr =  E_NOINTERFACE;
    }
    AddRef();
    return hr;
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::AddRef
//
//  Synopsis:   Add a reference
//
//  Returns:    New reference count
//
//  History:    24-Oct-94   Created.
//
//  Notes:
//
//----------------------------------------------------------------------
ULONG CMallocSpy::AddRef(void)
{
    return ++m_cRef;
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::Release
//
//  Synopsis:   Remove a reference
//
//  Returns:    The new reference count
//
//  History:    24-Oct-94   Created.
//
//  Notes:
//
//----------------------------------------------------------------------
ULONG CMallocSpy::Release(void)
{
    ULONG cRef;

    cRef = --m_cRef;
    if (cRef == 0)
    {
        delete this;
    }
    return cRef;
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::PreAlloc
//
//  Synopsis:   Called prior to OLE calling IMalloc::Alloc
//
//  Arguments:  [cbRequest] -- The number of bytes the caller of
//                             is requesting IMalloc::Alloc
//
//  Returns:    The count of bytes to actuallay allocate
//
//  History:    24-Oct-94   Created.
//
//  Notes:
//
//----------------------------------------------------------------------
ULONG CMallocSpy::PreAlloc(ULONG cbRequest)
{
    m_cbRequest = cbRequest;
    return cbRequest + HEADERSIZE;
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::PostAlloc
//
//  Synopsis:   Called after OLE calls IMalloc::Alloc
//
//  Arguments:  [pActual] -- The allocation returned by IMalloc::Alloc
//
//  Returns:    The allocation pointer to return to the caller of
//              IMalloc::Alloc
//
//  History:    24-Oct-94   Created.
//
//  Notes:
//
//----------------------------------------------------------------------
void *CMallocSpy::PostAlloc(void *pActual)
{
    // Store interesting information in the first HEADERSIZE bytes
    // of the allcation
    // ...

    // Return the allocation plus offset
    return (void *) (((BYTE *) pActual) + HEADERSIZE);
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::PreFree
//
//  Synopsis:   Called prior to OLE calling IMalloc::Free
//
//  Arguments:  [pRequest] -- The allocation to be freed
//              [fSpyed]   -- Whether it was allocated with a spy active
//
//  Returns:
//
//  History:    24-Oct-94   Created.
//
//  Notes:
//
//----------------------------------------------------------------------
void *CMallocSpy::PreFree(void *pRequest, BOOL fSpyed)
{
    // Use the stored information
    // ...

    // Check for NULL
    if (pRequest == NULL)
    {
        return NULL;
    }

    // Undo the offset
    if (fSpyed)
    {
        return (void *) (((BYTE *) pRequest) - HEADERSIZE);
    }
    else
    {
        return pRequest;
    }
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::PostFree
//
//  Synopsis:   Called after OLE calls IMalloc::Free
//
//  Arguments:  [fSpyed]   -- Whether it was allocated with a spy active
//
//  Returns:
//
//  History:    24-Oct-94   Created.
//
//  Notes:
//
//----------------------------------------------------------------------
void CMallocSpy::PostFree(BOOL fSpyed)
{
    // Note the free or whatever

    return;
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::PreRealloc
//
//  Synopsis:   Called prior to OLE calling IMalloc::Realloc
//
//  Arguments:  [pRequest]     -- The buffer to be reallocated
//              [cbRequest]    -- The requested new size of the buffer
//              [ppNewRequest] -- Where to store the new buffer pointer
//                                to be reallocated
//              [fSpyed]       -- Whether it was allocated with a spy active
//
//  Returns:    The new size to actually be allocated
//
//  History:    24-Oct-94   Created.
//
//  Notes:
//
//----------------------------------------------------------------------
ULONG CMallocSpy::PreRealloc(void *pRequest, ULONG cbRequest,
                             void **ppNewRequest, BOOL fSpyed)
{
    // Typically we don't need to extend/distend the header
    if (fSpyed)
    {
        *ppNewRequest = (void *) (((BYTE *) pRequest) - HEADERSIZE);
        return cbRequest + HEADERSIZE;
    }
    else
    {
        *ppNewRequest = pRequest;
        return cbRequest;
    }
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::PostRealloc
//
//  Synopsis:   Called after OLE calls IMalloc::Realloc
//
//  Arguments:  [pActual] -- Pointer to the reallocated buffer
//              [fSpyed]  -- Whether it was allocated with a spy active
//
//  Returns:    The buffer pointer to return
//
//  History:    24-Oct-94   Created.
//
//  Notes:
//
//----------------------------------------------------------------------
void *CMallocSpy::PostRealloc(void *pActual, BOOL fSpyed)
{
    // Return the buffer with the header offset
    if (fSpyed)
    {
        return (void *) (((BYTE *) pActual) + HEADERSIZE);
    }
    else
    {
        return pActual;
    }

    // If we extend/distend the header for a reallocation, then we'd
    // have to do some more work here
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::PreGetSize
//
//  Synopsis:   Called prior to OLE calling IMalloc::GetSize
//
//  Arguments:  [pRequest] -- The buffer whose size is to be returned
//              [fSpyed]   -- Whether it was allocated with a spy active
//
//  Returns:    The actual buffer with which to call IMalloc::GetSize
//
//  History:    24-Oct-94   Created.
//
//  Notes:
//
//----------------------------------------------------------------------
void *CMallocSpy::PreGetSize(void *pRequest, BOOL fSpyed)
{
    if (fSpyed)
    {
        return (void *) (((BYTE *) pRequest) - HEADERSIZE);
    }else
    {
        return pRequest;
    }
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::PostGetSize
//
//  Synopsis:   Called after OLE calls IMalloc::GetSize
//
//  Arguments:  [cbActual] -- The result of IMalloc::GetSize
//              [fSpyed]   -- Whether it was allocated with a spy active
//
//  Returns:    The size to return to the IMalloc::GetSize caller
//
//  History:    24-Oct-94   Created.
//
//  Notes:
//
//----------------------------------------------------------------------
ULONG CMallocSpy::PostGetSize(ULONG cbActual, BOOL fSpyed)
{
    if (fSpyed)
    {
        return cbActual - HEADERSIZE;
    }
    else
    {
        return cbActual;
    }
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::PreDidAlloc
//
//  Synopsis:   Called prior to OLE calling IMalloc::DidAlloc
//
//  Arguments:  [pRequest] -- The buffer whose allocation is being tested
//              [fSpyed]   -- Whether it was allocated with a spy active
//
//  Returns:    The buffer whose allocation is actually to be tested
//
//  History:    24-Oct-94   Created.
//
//  Notes:
//
//----------------------------------------------------------------------
void *CMallocSpy::PreDidAlloc(void *pRequest, BOOL fSpyed)
{
    if (fSpyed)
    {
        return (void *) (((BYTE *) pRequest) - HEADERSIZE);
    }
    else
    {
        return pRequest;
    }
}

//+---------------------------------------------------------------------
//
//  Function:   PostDidAlloc
//
//  Synopsis:   Called after OLE calls the IMalloc::DidAlloc
//
//  Arguments:  [pRequest] -- The passed allocation
//              [fSpyed]   -- Whether it was allocated with a spy active
//              [fActual]  -- The result of IMalloc::DidAlloc
//
//  Returns:    The result of IMalloc::DidAlloc
//
//  History:    24-Oct-94   Created.
//
//  Notes:
//
//----------------------------------------------------------------------
BOOL CMallocSpy::PostDidAlloc(void *pRequest, BOOL fSpyed, BOOL fActual)
{
    return fActual;
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::PreHeapMinimize
//
//  Synopsis:   Called prior to OLE calling the IMalloc::HeapMinimize
//
//  Returns:
//
//  History:    24-Oct-94   Created.
//
//  Notes:
//
//----------------------------------------------------------------------
void CMallocSpy::PreHeapMinimize(void)
{
    // We don't do anything here
    return;
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::PostHeapMinimize
//
//  Synopsis:   Called after OLE calls the IMalloc::HeapMinimize
//
//  Returns:
//
//  History:    24-Oct-94   Created.
//
//  Notes:
//
//----------------------------------------------------------------------
void CMallocSpy::PostHeapMinimize(void)
{
    // We don't do anything here
    return;
}


TEST.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       test.cxx
//
//  Contents:   test application for CMALSPY sample library
//
//----------------------------------------------------------------------------

#include <windows.h>
#include <ole2.h>
#include "cmallspy.hxx"

extern "C" void printf(CHAR*, ...);

const UINT NALLOCATIONS = 573;

int __cdecl main(int argc, char **argv)
{
    HRESULT     hr;
    LPMALLOCSPY pMallocSpy = new CMallocSpy();

    // Initialize
    CoInitialize(NULL);
    hr = CoRegisterMallocSpy(pMallocSpy);
    if (hr != S_OK)
    {
        printf("CoRegisterMallocSpy failed with %x\n", hr);
        CoUninitialize();
        return 0;
    }
    printf("CoRegisterMallocSpy succeeded.\n");

    // Allocate enoungh to force an expansion
    LPMALLOC pMalloc;
    void    *allocs[NALLOCATIONS];
    ULONG j;

    if ((hr = CoGetMalloc(MEMCTX_TASK, &pMalloc)) != S_OK)
    {
        printf("CoGetMalloc failed with %x\n", hr);
        CoUninitialize();
        return 0;
    }
    printf("CoGetMalloc succeeded.\n");
    printf("Allocating %u memory blocks.\n", NALLOCATIONS);
    for (j = 0; j < NALLOCATIONS; j++)
    {
        allocs[j] = pMalloc->Alloc(173);
    }

    // Now release everything
    printf("Releasing %u memory blocks.\n", NALLOCATIONS);
    for (j = 0; j < NALLOCATIONS; j++)
    {
        pMalloc->Free(allocs[j]);
        allocs[j] = NULL;
    }

    printf("Test complete.\n");
    // All done
    CoRevokeMallocSpy();
    CoUninitialize();
    return 0;
}


ALTASSRT.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       altassrt.cxx
//
//  Contents:   Assertion routines for the SDK
//
//  Functions:  NTSDKAssert
//              PopUpError
//
//----------------------------------------------------------------------------

#include <stdarg.h>
#include <stdio.h>

// ensure that this module always compiles with DEBUG settings.
#undef  NDEBUG

#include "assert.h"

int
PopUpError(
    char const *szMsg,
    int iLine,
    char const *szFile);

extern "C"
{

# ifdef WIN32
#  undef FAR
#  undef NEAR
# else
#  define MessageBoxA MessageBox
# endif

# include <windows.h>
}

//+---------------------------------------------------------------------------
//
//  Function:   PopUpAssert
//
//  Synopsis:   Display assertion information
//
//  Effects:    Called when an assertion is hit.
//
//  History:    8-19-94   stevebl   Created
//
//----------------------------------------------------------------------------

void _CRTAPI1
PopUpAssert(
    void * szFile,
    int iLine,
    void * szMessage)
{
    int id = PopUpError((char const *)szMessage,iLine, (char const *) szFile);

    if (id == IDCANCEL)
    {
#ifdef WIN32
        DebugBreak();
#else
        _asm int 3;
#endif
    }
}

//+------------------------------------------------------------
// Function:    PopUpError
//
// Synopsis:    Displays a dialog box using provided text,
//              and presents the user with the option to
//              continue or cancel.
//
// Arguments:
//      szMsg --        The string to display in main body of dialog
//      iLine --        Line number of file in error
//      szFile --       Filename of file in error
//
// Returns:
//      IDCANCEL --     User selected the CANCEL button
//      IDOK     --     User selected the OK button
//-------------------------------------------------------------

int
PopUpError(
    char const *szMsg,
    int iLine,
    char const *szFile)
{

    int id;
    static char szAssertCaption[128];
    static char szModuleName[128];

    DWORD tid = GetCurrentThreadId();
    DWORD pid = GetCurrentProcessId();
    char * pszModuleName;

    if (GetModuleFileNameA(NULL, szModuleName, 128))
    {
        pszModuleName = strrchr(szModuleName, '\\');
        if (!pszModuleName)
        {
            pszModuleName = szModuleName;
        }
        else
        {
            pszModuleName++;
        }
    }
    else
    {
        pszModuleName = "Unknown";
    }

    sprintf(szAssertCaption,"Process: %s File: %s line %u, thread id %d.%d",
        pszModuleName, szFile, iLine, pid, tid);

    DWORD dwMessageFlags = MB_SETFOREGROUND | MB_TASKMODAL |
                           MB_ICONEXCLAMATION | MB_OKCANCEL;

    id = MessageBoxA(NULL,
                     (char *) szMsg,
                     (LPSTR) szAssertCaption,
                     dwMessageFlags);

    // If id == 0, then an error occurred.  There are two possibilities
    // that can cause the error:  Access Denied, which means that this
    // process does not have access to the default desktop, and everything
    // else (usually out of memory).  Oh well.

    return id;
}


STABLIZE.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       stablize.cxx
//
//  Contents:   Stabilization Classes used to stabilize objects during
//              re-entrant calls.
//
//  Classes:    CSafeRefCount
//              CStabilize
//
//  History:    8-26-94   stevebl   Modified from code written by AlexGo
//
//----------------------------------------------------------------------------

#include <windows.h>
#include <ole2.h>
#include <stablize.h>
#include <assert.h>

//+-------------------------------------------------------------------------
//
//  Member:     CSafeRefCount::CSafeRefCount
//
//  Synopsis:   constructor for the safe ref count class
//
//  History:    dd-mmm-yy Author    Comment
//              28-Jul-94 alexgo    author
//              8-26-94   stevebl   modified for use by SDK samples
//
//--------------------------------------------------------------------------

CSafeRefCount::CSafeRefCount()
{
        m_cRefs = 0;
        m_cNest = 0;
        m_fInDelete = FALSE;
}

//+-------------------------------------------------------------------------
//
//  Member:     CSafeRefCount::~CSafeRefCount (virtual)
//
//  History:    dd-mmm-yy Author    Comment
//              28-Jul-94 alexgo    author
//              8-26-94   stevebl   modified for use by SDK samples
//
//--------------------------------------------------------------------------

CSafeRefCount::~CSafeRefCount()
{
        assert(m_cRefs == 0 && m_cNest == 0 && m_fInDelete == TRUE);
}

//+-------------------------------------------------------------------------
//
//  Member:     CSafeRefCount::SafeAddRef
//
//  Synopsis:   increments the reference count on the object
//
//  Returns:    ULONG -- the reference count after the increment
//
//  History:    dd-mmm-yy Author    Comment
//              28-Jul-94 alexgo    author
//              8-26-94   stevebl   modified for use by SDK samples
//
//--------------------------------------------------------------------------

ULONG CSafeRefCount::SafeAddRef()
{
        m_cRefs++;
        return m_cRefs;
}

//+-------------------------------------------------------------------------
//
//  Member:     CSafeRefCount::SafeRelease
//
//  Synopsis:   decrements the reference count on the object
//
//  Effects:    May delete the object!
//
//  Returns:    ULONG -- the reference count after decrement
//
//  Algorithm:  decrements the reference count.  If the reference count
//              is zero AND the nest count is zero AND we are not currently
//              trying to delete our object, then it is safe to delete.
//
//  History:    dd-mmm-yy Author    Comment
//              28-Jul-94 alexgo    author
//              8-26-94   stevebl   modified for use by SDK samples
//
//--------------------------------------------------------------------------

ULONG CSafeRefCount::SafeRelease()
{
        ULONG   cRefs;

        if( m_cRefs > 0 )
        {
                cRefs = --m_cRefs;

                if( m_cRefs == 0 && m_cNest == 0 && m_fInDelete == FALSE )
                {
                        m_fInDelete = TRUE;
                        delete this;
                }
        }
        else
        {
                // somebody is releasing a non-addrefed pointer!!
                assert(NULL == "Release called on a non-addref'ed pointer!\n");
                cRefs = 0;
        }
        return cRefs;
}

//+-------------------------------------------------------------------------
//
//  Member:     CSafeRefCount::IncrementNestCount
//
//  Synopsis:   increments the nesting count of the object
//
//  Arguments:  none
//
//  Returns:    ULONG; the nesting count after increment
//
//  History:    dd-mmm-yy Author    Comment
//              28-Jul-94 alexgo    author
//              8-26-94   stevebl   modified for use by SDK samples
//
//  Notes:      The nesting count is the count of how many times an
//              an object has been re-entered.  For example, suppose
//              somebody calls pFoo->Bar1(), which makes some calls that
//              eventually call pFoo->Bar2();.  On entrace to Bar2, the
//              nest count of the object should be 2 (since the invocation
//              of Bar1 is still on the stack above us).
//
//              It is important to keep track of the nest count so we do
//              not accidentally delete ourselves during a nested invocation.
//              If we did, then when the stack unwinds to the original
//              top level call, it could try to access a non-existent member
//              variable and crash.
//
//--------------------------------------------------------------------------

ULONG CSafeRefCount::IncrementNestCount()
{
        m_cNest++;
        return m_cNest;
}

//+-------------------------------------------------------------------------
//
//  Member:     CSafeRefCount::DecrementNestCount
//
//  Synopsis:   decrements the nesting count and deletes the object
//              (if necessary)
//
//  Effects:    may delete 'this' object!
//
//  Arguments:  none
//
//  Returns:    ULONG, the nesting count after decrement
//
//  Algorithm:  decrements the nesting count.  If the nesting count is zero
//              AND the reference count is zero AND we are not currently
//              trying to delete ourselves, then delete 'this' object
//
//  History:    dd-mmm-yy Author    Comment
//              28-Jul-94 alexgo    author
//              8-26-94   stevebl   modified for use by SDK samples
//
//  Notes:
//
//--------------------------------------------------------------------------

ULONG CSafeRefCount::DecrementNestCount()
{
        ULONG   cNest;

        if( m_cNest > 0 )
        {
                cNest = --m_cNest;

                if( m_cRefs == 0 && m_cNest == 0 && m_fInDelete == FALSE )
                {
                        m_fInDelete = TRUE;
                        delete this;
                }
        }
        else
        {
                // somebody forget to increment the nest count!!
                assert(NULL == "Unbalanced nest count!!");
                cNest = 0;
        }
        return cNest;
}


README.TXT

SAMPLE:       DECODE: Error Code Decoder Tool

DECODE is a decoder of 32 bit OLE error codes (HRESULTs)
and Windows error codes. Enter OLE error codes in hexadecimal
and Windows error codes in decimal.

This tool may not be able to decode errors added after the shipping
of the Win32 SDK in which it is included.


DECODE.H

// Dialog IDs
#define IDD_HRESULTDIALOG  100

// Control IDs
#define IDC_EDIT        200
#define IDC_DECODE      201
#define IDC_ERRORNAME   202
#define IDC_ERRORDESC   203
#define IDC_WIN32ERROR  204
#define IDC_OLEERROR    205
#define IDC_STATIC      -1

// Function prototypes
int PASCAL WinMain (HINSTANCE, HINSTANCE, LPSTR, int);
void DecodeHResult(HRESULT, HWND, HWND, BOOL);
BOOL CALLBACK HResultDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);


DECODE.CPP

/*************************************************************************
**
**    HRESULT Decoder
**
**    decode.cpp
**
**
**     Written by Microsoft Product Support Services, Windows Developer Support
**    (c) Copyright Microsoft Corp. 1996 All Rights Reserved
**
*************************************************************************/

#include <windows.h>
#include <stdio.h>
#include <olectl.h>
#include "decode.h"

int APIENTRY WinMain (HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpCmdLine, int nCmdShow)
{
   WNDCLASS wc;

   wc.style = 0;
   wc.lpfnWndProc = DefDlgProc;
   wc.cbClsExtra = 0;
   wc.cbWndExtra = DLGWINDOWEXTRA;
   wc.hInstance = hinst;
   wc.hIcon = LoadIcon(hinst, TEXT("AppIcon"));
   wc.hCursor = LoadCursor(NULL, IDC_ARROW);
   wc.hbrBackground = HBRUSH(COLOR_WINDOW + 1);
   wc.lpszMenuName = NULL;
   wc.lpszClassName = TEXT("DlgClass");
   RegisterClass(&wc);

   DialogBox(hinst, MAKEINTRESOURCE(IDD_HRESULTDIALOG), NULL, (DLGPROC)HResultDialogFunc);

   return 0;
}

BOOL CALLBACK HResultDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
   TCHAR ach[30];
   long l;
   BOOL bOleError;

   switch (msg)
   {
  case WM_INITDIALOG:
          CheckDlgButton(hwndDlg, IDC_OLEERROR, 1);
  return TRUE;

      case WM_COMMAND:
         switch(LOWORD(wParam))
         {
             case IDC_DECODE:
                 GetDlgItemText(hwndDlg, IDC_EDIT, ach, 30);
 bOleError = IsDlgButtonChecked(hwndDlg, IDC_OLEERROR);
 if (bOleError)
     sscanf(ach, "%lx", &l);
 else
 {
 sscanf(ach, "%ld", &l);
                     l = MAKE_HRESULT(0x1, FACILITY_WIN32, l);
 }

                 DecodeHResult((HRESULT)l,
                             GetDlgItem(hwndDlg, IDC_ERRORNAME),
                             GetDlgItem(hwndDlg, IDC_ERRORDESC),
 bOleError);
                 return TRUE;

             case IDOK:
             case IDCANCEL:
                 EndDialog(hwndDlg, IDCANCEL);
                 return TRUE;
         }
         break;
   }
   return FALSE;
}

#define CASE_FACILITY(f)  \
        case f: \
            lstrcpy((LPTSTR)szFacility, (LPTSTR)#f); \
            break;

#define CASE_HRESULT(hr)  \
        case hr: \
            lstrcpy((LPTSTR)szErrorName, (LPTSTR)#hr); \
            FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, hr, \
                MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), \
                szErrorDesc, sizeof(szErrorDesc), NULL);\
            break;

#define CASE_CODE(c) \
        case c: \
            lstrcpy((LPTSTR)szErrorName, (LPTSTR)#c); \
            FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, hr, \
                MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL), \
                szErrorDesc, sizeof(szErrorDesc), NULL);\
            break;

void DecodeHResult(HRESULT hr, HWND hwndDisplayErrorName, HWND hwndDisplayErrorDesc, BOOL bOleError)
{
    TCHAR szBuf[200];
    TCHAR szFacility[100];
    TCHAR szErrorName[100];
    TCHAR szErrorDesc[400];

    lstrcpy(szErrorDesc, TEXT(""));

    switch (HRESULT_FACILITY(hr))
    {
        CASE_FACILITY(FACILITY_WINDOWS)
        CASE_FACILITY(FACILITY_STORAGE)
        CASE_FACILITY(FACILITY_RPC)
        CASE_FACILITY(FACILITY_SSPI)
        CASE_FACILITY(FACILITY_WIN32)
        CASE_FACILITY(FACILITY_CONTROL)
        CASE_FACILITY(FACILITY_NULL)
CASE_FACILITY(FACILITY_INTERNET)
        CASE_FACILITY(FACILITY_ITF)
        CASE_FACILITY(FACILITY_DISPATCH)
        CASE_FACILITY(FACILITY_CERT)

        default:
            lstrcpy(szFacility, TEXT("Unknown or new FACILITY code"));
    }

    switch (hr)
    {
        CASE_HRESULT(E_UNEXPECTED)
        CASE_HRESULT(E_NOTIMPL)
        CASE_HRESULT(E_OUTOFMEMORY)
        CASE_HRESULT(E_INVALIDARG)
        CASE_HRESULT(E_NOINTERFACE)
        CASE_HRESULT(E_POINTER)
        CASE_HRESULT(E_HANDLE)
        CASE_HRESULT(E_ABORT)
        CASE_HRESULT(E_FAIL)
        CASE_HRESULT(E_ACCESSDENIED)
        CASE_HRESULT(E_PENDING)
        CASE_HRESULT(CO_E_INIT_TLS)
        CASE_HRESULT(CO_E_INIT_SHARED_ALLOCATOR)
        CASE_HRESULT(CO_E_INIT_MEMORY_ALLOCATOR)
        CASE_HRESULT(CO_E_INIT_CLASS_CACHE)
        CASE_HRESULT(CO_E_INIT_RPC_CHANNEL)
        CASE_HRESULT(CO_E_INIT_TLS_SET_CHANNEL_CONTROL)
        CASE_HRESULT(CO_E_INIT_TLS_CHANNEL_CONTROL)
        CASE_HRESULT(CO_E_INIT_UNACCEPTED_USER_ALLOCATOR)
        CASE_HRESULT(CO_E_INIT_SCM_MUTEX_EXISTS)
        CASE_HRESULT(CO_E_INIT_SCM_FILE_MAPPING_EXISTS)
        CASE_HRESULT(CO_E_INIT_SCM_MAP_VIEW_OF_FILE)
        CASE_HRESULT(CO_E_INIT_SCM_EXEC_FAILURE)
        CASE_HRESULT(CO_E_INIT_ONLY_SINGLE_THREADED)
        CASE_HRESULT(CO_E_CANT_REMOTE)
        CASE_HRESULT(CO_E_BAD_SERVER_NAME)
        CASE_HRESULT(CO_E_WRONG_SERVER_IDENTITY)
        CASE_HRESULT(CO_E_OLE1DDE_DISABLED)
        CASE_HRESULT(CO_E_RUNAS_SYNTAX)
        CASE_HRESULT(CO_E_CREATEPROCESS_FAILURE)
        CASE_HRESULT(CO_E_RUNAS_CREATEPROCESS_FAILURE)
        CASE_HRESULT(CO_E_RUNAS_LOGON_FAILURE)
        CASE_HRESULT(CO_E_LAUNCH_PERMSSION_DENIED)
        CASE_HRESULT(CO_E_START_SERVICE_FAILURE)
        CASE_HRESULT(CO_E_REMOTE_COMMUNICATION_FAILURE)
        CASE_HRESULT(CO_E_SERVER_START_TIMEOUT)
        CASE_HRESULT(CO_E_CLSREG_INCONSISTENT)
        CASE_HRESULT(CO_E_IIDREG_INCONSISTENT)
        CASE_HRESULT(CO_E_NOT_SUPPORTED)
        CASE_HRESULT(S_OK)
        CASE_HRESULT(S_FALSE)
        CASE_HRESULT(OLE_E_OLEVERB)
        CASE_HRESULT(OLE_E_ADVF)
        CASE_HRESULT(OLE_E_ENUM_NOMORE)
        CASE_HRESULT(OLE_E_ADVISENOTSUPPORTED)
        CASE_HRESULT(OLE_E_NOCONNECTION)
        CASE_HRESULT(OLE_E_NOTRUNNING)
        CASE_HRESULT(OLE_E_NOCACHE)
        CASE_HRESULT(OLE_E_BLANK)
        CASE_HRESULT(OLE_E_CLASSDIFF)
        CASE_HRESULT(OLE_E_CANT_GETMONIKER)
        CASE_HRESULT(OLE_E_CANT_BINDTOSOURCE)
        CASE_HRESULT(OLE_E_STATIC)
        CASE_HRESULT(OLE_E_PROMPTSAVECANCELLED)
        CASE_HRESULT(OLE_E_INVALIDRECT)
        CASE_HRESULT(OLE_E_WRONGCOMPOBJ)
        CASE_HRESULT(OLE_E_INVALIDHWND)
        CASE_HRESULT(OLE_E_NOT_INPLACEACTIVE)
        CASE_HRESULT(OLE_E_CANTCONVERT)
        CASE_HRESULT(OLE_E_NOSTORAGE)
        CASE_HRESULT(DV_E_FORMATETC)
        CASE_HRESULT(DV_E_DVTARGETDEVICE)
        CASE_HRESULT(DV_E_STGMEDIUM)
        CASE_HRESULT(DV_E_STATDATA)
        CASE_HRESULT(DV_E_LINDEX)
        CASE_HRESULT(DV_E_TYMED)
        CASE_HRESULT(DV_E_CLIPFORMAT)
        CASE_HRESULT(DV_E_DVASPECT)
        CASE_HRESULT(DV_E_DVTARGETDEVICE_SIZE)
        CASE_HRESULT(DV_E_NOIVIEWOBJECT)
        CASE_HRESULT(DRAGDROP_E_NOTREGISTERED)
        CASE_HRESULT(DRAGDROP_E_ALREADYREGISTERED)
        CASE_HRESULT(DRAGDROP_E_INVALIDHWND)
        CASE_HRESULT(CLASS_E_NOAGGREGATION)
        CASE_HRESULT(CLASS_E_CLASSNOTAVAILABLE)
        CASE_HRESULT(VIEW_E_DRAW)
        CASE_HRESULT(REGDB_E_READREGDB)
        CASE_HRESULT(REGDB_E_WRITEREGDB)
        CASE_HRESULT(REGDB_E_KEYMISSING)
        CASE_HRESULT(REGDB_E_INVALIDVALUE)
        CASE_HRESULT(REGDB_E_CLASSNOTREG)
        CASE_HRESULT(REGDB_E_IIDNOTREG)
        CASE_HRESULT(CACHE_E_NOCACHE_UPDATED)
        CASE_HRESULT(OLEOBJ_E_NOVERBS)
        CASE_HRESULT(OLEOBJ_E_INVALIDVERB)
        CASE_HRESULT(INPLACE_E_NOTUNDOABLE)
        CASE_HRESULT(INPLACE_E_NOTOOLSPACE)
        CASE_HRESULT(CONVERT10_E_OLESTREAM_GET)
        CASE_HRESULT(CONVERT10_E_OLESTREAM_PUT)
        CASE_HRESULT(CONVERT10_E_OLESTREAM_FMT)
        CASE_HRESULT(CONVERT10_E_OLESTREAM_BITMAP_TO_DIB)
        CASE_HRESULT(CONVERT10_E_STG_FMT)
        CASE_HRESULT(CONVERT10_E_STG_NO_STD_STREAM)
        CASE_HRESULT(CONVERT10_E_STG_DIB_TO_BITMAP)
        CASE_HRESULT(CLIPBRD_E_CANT_OPEN)
        CASE_HRESULT(CLIPBRD_E_CANT_EMPTY)
        CASE_HRESULT(CLIPBRD_E_CANT_SET)
        CASE_HRESULT(CLIPBRD_E_BAD_DATA)
        CASE_HRESULT(CLIPBRD_E_CANT_CLOSE)
        CASE_HRESULT(MK_E_CONNECTMANUALLY)
        CASE_HRESULT(MK_E_EXCEEDEDDEADLINE)
        CASE_HRESULT(MK_E_NEEDGENERIC)
        CASE_HRESULT(MK_E_UNAVAILABLE)
        CASE_HRESULT(MK_E_SYNTAX)
        CASE_HRESULT(MK_E_NOOBJECT)
        CASE_HRESULT(MK_E_INVALIDEXTENSION)
        CASE_HRESULT(MK_E_INTERMEDIATEINTERFACENOTSUPPORTED)
        CASE_HRESULT(MK_E_NOTBINDABLE)
        CASE_HRESULT(MK_E_NOTBOUND)
        CASE_HRESULT(MK_E_CANTOPENFILE)
        CASE_HRESULT(MK_E_MUSTBOTHERUSER)
        CASE_HRESULT(MK_E_NOINVERSE)
        CASE_HRESULT(MK_E_NOSTORAGE)
        CASE_HRESULT(MK_E_NOPREFIX)
        CASE_HRESULT(MK_E_ENUMERATION_FAILED)
        CASE_HRESULT(CO_E_NOTINITIALIZED)
        CASE_HRESULT(CO_E_ALREADYINITIALIZED)
        CASE_HRESULT(CO_E_CANTDETERMINECLASS)
        CASE_HRESULT(CO_E_CLASSSTRING)
        CASE_HRESULT(CO_E_IIDSTRING)
        CASE_HRESULT(CO_E_APPNOTFOUND)
        CASE_HRESULT(CO_E_APPSINGLEUSE)
        CASE_HRESULT(CO_E_ERRORINAPP)
        CASE_HRESULT(CO_E_DLLNOTFOUND)
        CASE_HRESULT(CO_E_ERRORINDLL)
        CASE_HRESULT(CO_E_WRONGOSFORAPP)
        CASE_HRESULT(CO_E_OBJNOTREG)
        CASE_HRESULT(CO_E_OBJISREG)
        CASE_HRESULT(CO_E_OBJNOTCONNECTED)
        CASE_HRESULT(CO_E_APPDIDNTREG)
        CASE_HRESULT(CO_E_RELEASED)
        CASE_HRESULT(OLE_S_USEREG)
        CASE_HRESULT(OLE_S_STATIC)
        CASE_HRESULT(OLE_S_MAC_CLIPFORMAT)
        CASE_HRESULT(DRAGDROP_S_DROP)
        CASE_HRESULT(DRAGDROP_S_CANCEL)
        CASE_HRESULT(DRAGDROP_S_USEDEFAULTCURSORS)
        CASE_HRESULT(DATA_S_SAMEFORMATETC)
        CASE_HRESULT(VIEW_S_ALREADY_FROZEN)
        CASE_HRESULT(CACHE_S_FORMATETC_NOTSUPPORTED)
        CASE_HRESULT(CACHE_S_SAMECACHE)
        CASE_HRESULT(CACHE_S_SOMECACHES_NOTUPDATED)
        CASE_HRESULT(OLEOBJ_S_INVALIDVERB)
        CASE_HRESULT(OLEOBJ_S_CANNOT_DOVERB_NOW)
        CASE_HRESULT(OLEOBJ_S_INVALIDHWND)
        CASE_HRESULT(INPLACE_S_TRUNCATED)
        CASE_HRESULT(CONVERT10_S_NO_PRESENTATION)
        CASE_HRESULT(MK_S_REDUCED_TO_SELF)
        CASE_HRESULT(MK_S_ME)
        CASE_HRESULT(MK_S_HIM)
        CASE_HRESULT(MK_S_US)
        CASE_HRESULT(MK_S_MONIKERALREADYREGISTERED)
        CASE_HRESULT(CO_E_CLASS_CREATE_FAILED)
        CASE_HRESULT(CO_E_SCM_ERROR)
        CASE_HRESULT(CO_E_SCM_RPC_FAILURE)
        CASE_HRESULT(CO_E_BAD_PATH)
        CASE_HRESULT(CO_E_SERVER_EXEC_FAILURE)
        CASE_HRESULT(CO_E_OBJSRV_RPC_FAILURE)
        CASE_HRESULT(MK_E_NO_NORMALIZED)
        CASE_HRESULT(CO_E_SERVER_STOPPING)
        CASE_HRESULT(MEM_E_INVALID_ROOT)
        CASE_HRESULT(MEM_E_INVALID_LINK)
        CASE_HRESULT(MEM_E_INVALID_SIZE)
        CASE_HRESULT(CO_S_NOTALLINTERFACES)
        CASE_HRESULT(DISP_E_UNKNOWNINTERFACE)
        CASE_HRESULT(DISP_E_MEMBERNOTFOUND)
        CASE_HRESULT(DISP_E_PARAMNOTFOUND)
        CASE_HRESULT(DISP_E_TYPEMISMATCH)
        CASE_HRESULT(DISP_E_UNKNOWNNAME)
        CASE_HRESULT(DISP_E_NONAMEDARGS)
        CASE_HRESULT(DISP_E_BADVARTYPE)
        CASE_HRESULT(DISP_E_EXCEPTION)
        CASE_HRESULT(DISP_E_OVERFLOW)
        CASE_HRESULT(DISP_E_BADINDEX)
        CASE_HRESULT(DISP_E_UNKNOWNLCID)
        CASE_HRESULT(DISP_E_ARRAYISLOCKED)
        CASE_HRESULT(DISP_E_BADPARAMCOUNT)
        CASE_HRESULT(DISP_E_PARAMNOTOPTIONAL)
        CASE_HRESULT(DISP_E_BADCALLEE)
        CASE_HRESULT(DISP_E_NOTACOLLECTION)
        CASE_HRESULT(TYPE_E_BUFFERTOOSMALL)
        CASE_HRESULT(TYPE_E_INVDATAREAD)
        CASE_HRESULT(TYPE_E_UNSUPFORMAT)
        CASE_HRESULT(TYPE_E_REGISTRYACCESS)
        CASE_HRESULT(TYPE_E_LIBNOTREGISTERED)
        CASE_HRESULT(TYPE_E_UNDEFINEDTYPE)
        CASE_HRESULT(TYPE_E_QUALIFIEDNAMEDISALLOWED)
        CASE_HRESULT(TYPE_E_INVALIDSTATE)
        CASE_HRESULT(TYPE_E_WRONGTYPEKIND)
        CASE_HRESULT(TYPE_E_ELEMENTNOTFOUND)
        CASE_HRESULT(TYPE_E_AMBIGUOUSNAME)
        CASE_HRESULT(TYPE_E_NAMECONFLICT)
        CASE_HRESULT(TYPE_E_UNKNOWNLCID)
        CASE_HRESULT(TYPE_E_DLLFUNCTIONNOTFOUND)
        CASE_HRESULT(TYPE_E_BADMODULEKIND)
        CASE_HRESULT(TYPE_E_SIZETOOBIG)
        CASE_HRESULT(TYPE_E_DUPLICATEID)
        CASE_HRESULT(TYPE_E_INVALIDID)
        CASE_HRESULT(TYPE_E_TYPEMISMATCH)
        CASE_HRESULT(TYPE_E_OUTOFBOUNDS)
        CASE_HRESULT(TYPE_E_IOERROR)
        CASE_HRESULT(TYPE_E_CANTCREATETMPFILE)
        CASE_HRESULT(TYPE_E_CANTLOADLIBRARY)
        CASE_HRESULT(TYPE_E_INCONSISTENTPROPFUNCS)
        CASE_HRESULT(TYPE_E_CIRCULARTYPE)
        CASE_HRESULT(STG_E_INVALIDFUNCTION)
        CASE_HRESULT(STG_E_FILENOTFOUND)
        CASE_HRESULT(STG_E_PATHNOTFOUND)
        CASE_HRESULT(STG_E_TOOMANYOPENFILES)
        CASE_HRESULT(STG_E_ACCESSDENIED)
        CASE_HRESULT(STG_E_INVALIDHANDLE)
        CASE_HRESULT(STG_E_INSUFFICIENTMEMORY)
        CASE_HRESULT(STG_E_INVALIDPOINTER)
        CASE_HRESULT(STG_E_NOMOREFILES)
        CASE_HRESULT(STG_E_DISKISWRITEPROTECTED)
        CASE_HRESULT(STG_E_SEEKERROR)
        CASE_HRESULT(STG_E_WRITEFAULT)
        CASE_HRESULT(STG_E_READFAULT)
        CASE_HRESULT(STG_E_SHAREVIOLATION)
        CASE_HRESULT(STG_E_LOCKVIOLATION)
        CASE_HRESULT(STG_E_FILEALREADYEXISTS)
        CASE_HRESULT(STG_E_INVALIDPARAMETER)
        CASE_HRESULT(STG_E_MEDIUMFULL)
        CASE_HRESULT(STG_E_PROPSETMISMATCHED)
        CASE_HRESULT(STG_E_ABNORMALAPIEXIT)
        CASE_HRESULT(STG_E_INVALIDHEADER)
        CASE_HRESULT(STG_E_INVALIDNAME)
        CASE_HRESULT(STG_E_UNKNOWN)
        CASE_HRESULT(STG_E_UNIMPLEMENTEDFUNCTION)
        CASE_HRESULT(STG_E_INVALIDFLAG)
        CASE_HRESULT(STG_E_INUSE)
        CASE_HRESULT(STG_E_NOTCURRENT)
        CASE_HRESULT(STG_E_REVERTED)
        CASE_HRESULT(STG_E_CANTSAVE)
        CASE_HRESULT(STG_E_OLDFORMAT)
        CASE_HRESULT(STG_E_OLDDLL)
        CASE_HRESULT(STG_E_SHAREREQUIRED)
        CASE_HRESULT(STG_E_NOTFILEBASEDSTORAGE)
        CASE_HRESULT(STG_E_EXTANTMARSHALLINGS)
        CASE_HRESULT(STG_E_DOCFILECORRUPT)
        CASE_HRESULT(STG_E_BADBASEADDRESS)
        CASE_HRESULT(STG_E_INCOMPLETE)
        CASE_HRESULT(STG_E_TERMINATED)
        CASE_HRESULT(STG_S_CONVERTED)
        CASE_HRESULT(STG_S_BLOCK)
        CASE_HRESULT(STG_S_RETRYNOW)
        CASE_HRESULT(STG_S_MONITORING)
        CASE_HRESULT(RPC_E_CALL_REJECTED)
        CASE_HRESULT(RPC_E_CALL_CANCELED)
        CASE_HRESULT(RPC_E_CANTPOST_INSENDCALL)
        CASE_HRESULT(RPC_E_CANTCALLOUT_INASYNCCALL)
        CASE_HRESULT(RPC_E_CANTCALLOUT_INEXTERNALCALL)
        CASE_HRESULT(RPC_E_CONNECTION_TERMINATED)
        CASE_HRESULT(RPC_E_SERVER_DIED)
        CASE_HRESULT(RPC_E_CLIENT_DIED)
        CASE_HRESULT(RPC_E_INVALID_DATAPACKET)
        CASE_HRESULT(RPC_E_CANTTRANSMIT_CALL)
        CASE_HRESULT(RPC_E_CLIENT_CANTMARSHAL_DATA)
        CASE_HRESULT(RPC_E_CLIENT_CANTUNMARSHAL_DATA)
        CASE_HRESULT(RPC_E_SERVER_CANTMARSHAL_DATA)
        CASE_HRESULT(RPC_E_SERVER_CANTUNMARSHAL_DATA)
        CASE_HRESULT(RPC_E_INVALID_DATA)
        CASE_HRESULT(RPC_E_INVALID_PARAMETER)
        CASE_HRESULT(RPC_E_CANTCALLOUT_AGAIN)
        CASE_HRESULT(RPC_E_SERVER_DIED_DNE)
        CASE_HRESULT(RPC_E_SYS_CALL_FAILED)
        CASE_HRESULT(RPC_E_OUT_OF_RESOURCES)
        CASE_HRESULT(RPC_E_ATTEMPTED_MULTITHREAD)
        CASE_HRESULT(RPC_E_NOT_REGISTERED)
        CASE_HRESULT(RPC_E_FAULT)
        CASE_HRESULT(RPC_E_SERVERFAULT)
        CASE_HRESULT(RPC_E_CHANGED_MODE)
        CASE_HRESULT(RPC_E_INVALIDMETHOD)
        CASE_HRESULT(RPC_E_DISCONNECTED)
        CASE_HRESULT(RPC_E_RETRY)
        CASE_HRESULT(RPC_E_SERVERCALL_RETRYLATER)
        CASE_HRESULT(RPC_E_SERVERCALL_REJECTED)
        CASE_HRESULT(RPC_E_INVALID_CALLDATA)
        CASE_HRESULT(RPC_E_CANTCALLOUT_ININPUTSYNCCALL)
        CASE_HRESULT(RPC_E_WRONG_THREAD)
        CASE_HRESULT(RPC_E_THREAD_NOT_INIT)
        CASE_HRESULT(RPC_E_VERSION_MISMATCH)
        CASE_HRESULT(RPC_E_INVALID_HEADER)
        CASE_HRESULT(RPC_E_INVALID_EXTENSION)
        CASE_HRESULT(RPC_E_INVALID_IPID)
        CASE_HRESULT(RPC_E_INVALID_OBJECT)
        CASE_HRESULT(RPC_S_CALLPENDING)
        CASE_HRESULT(RPC_S_WAITONTIMER)
        CASE_HRESULT(RPC_E_CALL_COMPLETE)
        CASE_HRESULT(RPC_E_UNSECURE_CALL)
        CASE_HRESULT(RPC_E_TOO_LATE)
        CASE_HRESULT(RPC_E_NO_GOOD_SECURITY_PACKAGES)
        CASE_HRESULT(RPC_E_ACCESS_DENIED)
        CASE_HRESULT(RPC_E_REMOTE_DISABLED)
        CASE_HRESULT(RPC_E_INVALID_OBJREF)
        CASE_HRESULT(RPC_E_UNEXPECTED)
        CASE_HRESULT(NTE_BAD_UID)
        CASE_HRESULT(NTE_BAD_HASH)
        CASE_HRESULT(NTE_BAD_KEY)
        CASE_HRESULT(NTE_BAD_LEN)
        CASE_HRESULT(NTE_BAD_DATA)
        CASE_HRESULT(NTE_BAD_SIGNATURE)
        CASE_HRESULT(NTE_BAD_VER)
        CASE_HRESULT(NTE_BAD_ALGID)
        CASE_HRESULT(NTE_BAD_FLAGS)
        CASE_HRESULT(NTE_BAD_TYPE)
        CASE_HRESULT(NTE_BAD_KEY_STATE)
        CASE_HRESULT(NTE_BAD_HASH_STATE)
        CASE_HRESULT(NTE_NO_KEY)
        CASE_HRESULT(NTE_NO_MEMORY)
        CASE_HRESULT(NTE_EXISTS)
        CASE_HRESULT(NTE_PERM)
        CASE_HRESULT(NTE_NOT_FOUND)
        CASE_HRESULT(NTE_DOUBLE_ENCRYPT)
        CASE_HRESULT(NTE_BAD_PROVIDER)
        CASE_HRESULT(NTE_BAD_PROV_TYPE)
        CASE_HRESULT(NTE_BAD_PUBLIC_KEY)
        CASE_HRESULT(NTE_BAD_KEYSET)
        CASE_HRESULT(NTE_PROV_TYPE_NOT_DEF)
        CASE_HRESULT(NTE_PROV_TYPE_ENTRY_BAD)
        CASE_HRESULT(NTE_KEYSET_NOT_DEF)
        CASE_HRESULT(NTE_KEYSET_ENTRY_BAD)
        CASE_HRESULT(NTE_PROV_TYPE_NO_MATCH)
        CASE_HRESULT(NTE_SIGNATURE_FILE_BAD)
        CASE_HRESULT(NTE_PROVIDER_DLL_FAIL)
        CASE_HRESULT(NTE_PROV_DLL_NOT_FOUND)
        CASE_HRESULT(NTE_BAD_KEYSET_PARAM)
        CASE_HRESULT(NTE_FAIL)
        CASE_HRESULT(NTE_SYS_ERR)
        CASE_HRESULT(TRUST_E_PROVIDER_UNKNOWN)
        CASE_HRESULT(TRUST_E_ACTION_UNKNOWN)
        CASE_HRESULT(TRUST_E_SUBJECT_FORM_UNKNOWN)
        CASE_HRESULT(TRUST_E_SUBJECT_NOT_TRUSTED)
        CASE_HRESULT(DIGSIG_E_ENCODE)
        CASE_HRESULT(DIGSIG_E_DECODE)
        CASE_HRESULT(DIGSIG_E_EXTENSIBILITY)
        CASE_HRESULT(DIGSIG_E_CRYPTO)
        CASE_HRESULT(PERSIST_E_SIZEDEFINITE)
        CASE_HRESULT(PERSIST_E_SIZEINDEFINITE)
        CASE_HRESULT(PERSIST_E_NOTSELFSIZING)
        CASE_HRESULT(TRUST_E_NOSIGNATURE)
        CASE_HRESULT(CERT_E_EXPIRED)
        CASE_HRESULT(CERT_E_VALIDITYPERIODNESTING)
        CASE_HRESULT(CERT_E_ROLE)
        CASE_HRESULT(CERT_E_PATHLENCONST)
        CASE_HRESULT(CERT_E_CRITICAL)
        CASE_HRESULT(CERT_E_PURPOSE)
        CASE_HRESULT(CERT_E_ISSUERCHAINING)
        CASE_HRESULT(CERT_E_MALFORMED)
        CASE_HRESULT(CERT_E_UNTRUSTEDROOT)
        CASE_HRESULT(CERT_E_CHAINING)

        // OLE controls
        CASE_HRESULT(CTL_E_ILLEGALFUNCTIONCALL)
        CASE_HRESULT(CTL_E_OVERFLOW)
        CASE_HRESULT(CTL_E_OUTOFMEMORY)
        CASE_HRESULT(CTL_E_DIVISIONBYZERO)
        CASE_HRESULT(CTL_E_OUTOFSTRINGSPACE)
        CASE_HRESULT(CTL_E_OUTOFSTACKSPACE)
        CASE_HRESULT(CTL_E_BADFILENAMEORNUMBER)
        CASE_HRESULT(CTL_E_FILENOTFOUND)
        CASE_HRESULT(CTL_E_BADFILEMODE)
        CASE_HRESULT(CTL_E_FILEALREADYOPEN)
        CASE_HRESULT(CTL_E_DEVICEIOERROR)
        CASE_HRESULT(CTL_E_FILEALREADYEXISTS)
        CASE_HRESULT(CTL_E_BADRECORDLENGTH)
        CASE_HRESULT(CTL_E_DISKFULL)
        CASE_HRESULT(CTL_E_BADRECORDNUMBER)
        CASE_HRESULT(CTL_E_BADFILENAME)
        CASE_HRESULT(CTL_E_TOOMANYFILES)
        CASE_HRESULT(CTL_E_DEVICEUNAVAILABLE)
        CASE_HRESULT(CTL_E_PERMISSIONDENIED)
        CASE_HRESULT(CTL_E_DISKNOTREADY)
        CASE_HRESULT(CTL_E_PATHFILEACCESSERROR)
        CASE_HRESULT(CTL_E_PATHNOTFOUND)
        CASE_HRESULT(CTL_E_INVALIDPATTERNSTRING)
        CASE_HRESULT(CTL_E_INVALIDUSEOFNULL)
        CASE_HRESULT(CTL_E_INVALIDFILEFORMAT)
        CASE_HRESULT(CTL_E_INVALIDPROPERTYVALUE)
        CASE_HRESULT(CTL_E_INVALIDPROPERTYARRAYINDEX)
        CASE_HRESULT(CTL_E_SETNOTSUPPORTEDATRUNTIME)
        CASE_HRESULT(CTL_E_SETNOTSUPPORTED)
        CASE_HRESULT(CTL_E_NEEDPROPERTYARRAYINDEX)
        CASE_HRESULT(CTL_E_SETNOTPERMITTED)
        CASE_HRESULT(CTL_E_GETNOTSUPPORTEDATRUNTIME)
        CASE_HRESULT(CTL_E_GETNOTSUPPORTED)
        CASE_HRESULT(CTL_E_PROPERTYNOTFOUND)
        CASE_HRESULT(CTL_E_INVALIDCLIPBOARDFORMAT)
        CASE_HRESULT(CTL_E_INVALIDPICTURE)
        CASE_HRESULT(CTL_E_PRINTERERROR)
        CASE_HRESULT(CTL_E_CANTSAVEFILETOTEMP)
        CASE_HRESULT(CTL_E_SEARCHTEXTNOTFOUND)
        CASE_HRESULT(CTL_E_REPLACEMENTSTOOLONG)
        CASE_HRESULT(CLASS_E_NOTLICENSED)

        default:
                        // Check if the HRESULT has FACILITY_WIN32. If so,
                        // extract the code and check against the codes in winerror.h
            if (HRESULT_FACILITY(hr) == FACILITY_WIN32)
            {
                switch (HRESULT_CODE(hr))
                {
                CASE_CODE(ERROR_SUCCESS)
                CASE_CODE(ERROR_INVALID_FUNCTION)
                CASE_CODE(ERROR_FILE_NOT_FOUND)
                CASE_CODE(ERROR_PATH_NOT_FOUND)
                CASE_CODE(ERROR_TOO_MANY_OPEN_FILES)
                CASE_CODE(ERROR_ACCESS_DENIED)
                CASE_CODE(ERROR_INVALID_HANDLE)
                CASE_CODE(ERROR_ARENA_TRASHED)
                CASE_CODE(ERROR_NOT_ENOUGH_MEMORY)
                CASE_CODE(ERROR_INVALID_BLOCK)
                CASE_CODE(ERROR_BAD_ENVIRONMENT)
                CASE_CODE(ERROR_BAD_FORMAT)
                CASE_CODE(ERROR_INVALID_ACCESS)
                CASE_CODE(ERROR_INVALID_DATA)
                CASE_CODE(ERROR_OUTOFMEMORY)
                CASE_CODE(ERROR_INVALID_DRIVE)
                CASE_CODE(ERROR_CURRENT_DIRECTORY)
                CASE_CODE(ERROR_NOT_SAME_DEVICE)
                CASE_CODE(ERROR_NO_MORE_FILES)
                CASE_CODE(ERROR_WRITE_PROTECT)
                CASE_CODE(ERROR_BAD_UNIT)
                CASE_CODE(ERROR_NOT_READY)
                CASE_CODE(ERROR_BAD_COMMAND)
                CASE_CODE(ERROR_CRC)
                CASE_CODE(ERROR_BAD_LENGTH)
                CASE_CODE(ERROR_SEEK)
                CASE_CODE(ERROR_NOT_DOS_DISK)
                CASE_CODE(ERROR_SECTOR_NOT_FOUND)
                CASE_CODE(ERROR_OUT_OF_PAPER)
                CASE_CODE(ERROR_WRITE_FAULT)
                CASE_CODE(ERROR_READ_FAULT)
                CASE_CODE(ERROR_GEN_FAILURE)
                CASE_CODE(ERROR_SHARING_VIOLATION)
                CASE_CODE(ERROR_LOCK_VIOLATION)
                CASE_CODE(ERROR_WRONG_DISK)
                CASE_CODE(ERROR_SHARING_BUFFER_EXCEEDED)
                CASE_CODE(ERROR_HANDLE_EOF)
                CASE_CODE(ERROR_HANDLE_DISK_FULL)
                CASE_CODE(ERROR_NOT_SUPPORTED)
                CASE_CODE(ERROR_REM_NOT_LIST)
                CASE_CODE(ERROR_DUP_NAME)
                CASE_CODE(ERROR_BAD_NETPATH)
                CASE_CODE(ERROR_NETWORK_BUSY)
                CASE_CODE(ERROR_DEV_NOT_EXIST)
                CASE_CODE(ERROR_TOO_MANY_CMDS)
                CASE_CODE(ERROR_ADAP_HDW_ERR)
                CASE_CODE(ERROR_BAD_NET_RESP)
                CASE_CODE(ERROR_UNEXP_NET_ERR)
                CASE_CODE(ERROR_BAD_REM_ADAP)
                CASE_CODE(ERROR_PRINTQ_FULL)
                CASE_CODE(ERROR_NO_SPOOL_SPACE)
                CASE_CODE(ERROR_PRINT_CANCELLED)
                CASE_CODE(ERROR_NETNAME_DELETED)
                CASE_CODE(ERROR_NETWORK_ACCESS_DENIED)
                CASE_CODE(ERROR_BAD_DEV_TYPE)
                CASE_CODE(ERROR_BAD_NET_NAME)
                CASE_CODE(ERROR_TOO_MANY_NAMES)
                CASE_CODE(ERROR_TOO_MANY_SESS)
                CASE_CODE(ERROR_SHARING_PAUSED)
                CASE_CODE(ERROR_REQ_NOT_ACCEP)
                CASE_CODE(ERROR_REDIR_PAUSED)
                CASE_CODE(ERROR_FILE_EXISTS)
                CASE_CODE(ERROR_CANNOT_MAKE)
                CASE_CODE(ERROR_FAIL_I24)
                CASE_CODE(ERROR_OUT_OF_STRUCTURES)
                CASE_CODE(ERROR_ALREADY_ASSIGNED)
                CASE_CODE(ERROR_INVALID_PASSWORD)
                CASE_CODE(ERROR_INVALID_PARAMETER)
                CASE_CODE(ERROR_NET_WRITE_FAULT)
                CASE_CODE(ERROR_NO_PROC_SLOTS)
                CASE_CODE(ERROR_TOO_MANY_SEMAPHORES)
                CASE_CODE(ERROR_EXCL_SEM_ALREADY_OWNED)
                CASE_CODE(ERROR_SEM_IS_SET)
                CASE_CODE(ERROR_TOO_MANY_SEM_REQUESTS)
                CASE_CODE(ERROR_INVALID_AT_INTERRUPT_TIME)
                CASE_CODE(ERROR_SEM_OWNER_DIED)
                CASE_CODE(ERROR_SEM_USER_LIMIT)
                CASE_CODE(ERROR_DISK_CHANGE)
                CASE_CODE(ERROR_DRIVE_LOCKED)
                CASE_CODE(ERROR_BROKEN_PIPE)
                CASE_CODE(ERROR_OPEN_FAILED)
                CASE_CODE(ERROR_BUFFER_OVERFLOW)
                CASE_CODE(ERROR_DISK_FULL)
                CASE_CODE(ERROR_NO_MORE_SEARCH_HANDLES)
                CASE_CODE(ERROR_INVALID_TARGET_HANDLE)
                CASE_CODE(ERROR_INVALID_CATEGORY)
                CASE_CODE(ERROR_INVALID_VERIFY_SWITCH)
                CASE_CODE(ERROR_BAD_DRIVER_LEVEL)
                CASE_CODE(ERROR_CALL_NOT_IMPLEMENTED)
                CASE_CODE(ERROR_SEM_TIMEOUT)
                CASE_CODE(ERROR_INSUFFICIENT_BUFFER)
                CASE_CODE(ERROR_INVALID_NAME)
                CASE_CODE(ERROR_INVALID_LEVEL)
                CASE_CODE(ERROR_NO_VOLUME_LABEL)
                CASE_CODE(ERROR_MOD_NOT_FOUND)
                CASE_CODE(ERROR_PROC_NOT_FOUND)
                CASE_CODE(ERROR_WAIT_NO_CHILDREN)
                CASE_CODE(ERROR_CHILD_NOT_COMPLETE)
                CASE_CODE(ERROR_DIRECT_ACCESS_HANDLE)
                CASE_CODE(ERROR_NEGATIVE_SEEK)
                CASE_CODE(ERROR_SEEK_ON_DEVICE)
                CASE_CODE(ERROR_IS_JOIN_TARGET)
                CASE_CODE(ERROR_IS_JOINED)
                CASE_CODE(ERROR_IS_SUBSTED)
                CASE_CODE(ERROR_NOT_JOINED)
                CASE_CODE(ERROR_NOT_SUBSTED)
                CASE_CODE(ERROR_JOIN_TO_JOIN)
                CASE_CODE(ERROR_SUBST_TO_SUBST)
                CASE_CODE(ERROR_JOIN_TO_SUBST)
                CASE_CODE(ERROR_SUBST_TO_JOIN)
                CASE_CODE(ERROR_BUSY_DRIVE)
                CASE_CODE(ERROR_SAME_DRIVE)
                CASE_CODE(ERROR_DIR_NOT_ROOT)
                CASE_CODE(ERROR_DIR_NOT_EMPTY)
                CASE_CODE(ERROR_IS_SUBST_PATH)
                CASE_CODE(ERROR_IS_JOIN_PATH)
                CASE_CODE(ERROR_PATH_BUSY)
                CASE_CODE(ERROR_IS_SUBST_TARGET)
                CASE_CODE(ERROR_SYSTEM_TRACE)
                CASE_CODE(ERROR_INVALID_EVENT_COUNT)
                CASE_CODE(ERROR_TOO_MANY_MUXWAITERS)
                CASE_CODE(ERROR_INVALID_LIST_FORMAT)
                CASE_CODE(ERROR_LABEL_TOO_LONG)
                CASE_CODE(ERROR_TOO_MANY_TCBS)
                CASE_CODE(ERROR_SIGNAL_REFUSED)
                CASE_CODE(ERROR_DISCARDED)
                CASE_CODE(ERROR_NOT_LOCKED)
                CASE_CODE(ERROR_BAD_THREADID_ADDR)
                CASE_CODE(ERROR_BAD_ARGUMENTS)
                CASE_CODE(ERROR_BAD_PATHNAME)
                CASE_CODE(ERROR_SIGNAL_PENDING)
                CASE_CODE(ERROR_MAX_THRDS_REACHED)
                CASE_CODE(ERROR_LOCK_FAILED)
                CASE_CODE(ERROR_BUSY)
                CASE_CODE(ERROR_CANCEL_VIOLATION)
                CASE_CODE(ERROR_ATOMIC_LOCKS_NOT_SUPPORTED)
                CASE_CODE(ERROR_INVALID_SEGMENT_NUMBER)
                CASE_CODE(ERROR_INVALID_ORDINAL)
                CASE_CODE(ERROR_ALREADY_EXISTS)
                CASE_CODE(ERROR_INVALID_FLAG_NUMBER)
                CASE_CODE(ERROR_SEM_NOT_FOUND)
                CASE_CODE(ERROR_INVALID_STARTING_CODESEG)
                CASE_CODE(ERROR_INVALID_STACKSEG)
                CASE_CODE(ERROR_INVALID_MODULETYPE)
                CASE_CODE(ERROR_INVALID_EXE_SIGNATURE)
                CASE_CODE(ERROR_EXE_MARKED_INVALID)
                CASE_CODE(ERROR_BAD_EXE_FORMAT)
                CASE_CODE(ERROR_ITERATED_DATA_EXCEEDS_64k)
                CASE_CODE(ERROR_INVALID_MINALLOCSIZE)
                CASE_CODE(ERROR_DYNLINK_FROM_INVALID_RING)
                CASE_CODE(ERROR_IOPL_NOT_ENABLED)
                CASE_CODE(ERROR_INVALID_SEGDPL)
                CASE_CODE(ERROR_AUTODATASEG_EXCEEDS_64k)
                CASE_CODE(ERROR_RING2SEG_MUST_BE_MOVABLE)
                CASE_CODE(ERROR_RELOC_CHAIN_XEEDS_SEGLIM)
                CASE_CODE(ERROR_INFLOOP_IN_RELOC_CHAIN)
                CASE_CODE(ERROR_ENVVAR_NOT_FOUND)
                CASE_CODE(ERROR_NO_SIGNAL_SENT)
                CASE_CODE(ERROR_FILENAME_EXCED_RANGE)
                CASE_CODE(ERROR_RING2_STACK_IN_USE)
                CASE_CODE(ERROR_META_EXPANSION_TOO_LONG)
                CASE_CODE(ERROR_INVALID_SIGNAL_NUMBER)
                CASE_CODE(ERROR_THREAD_1_INACTIVE)
                CASE_CODE(ERROR_LOCKED)
                CASE_CODE(ERROR_TOO_MANY_MODULES)
                CASE_CODE(ERROR_NESTING_NOT_ALLOWED)
                CASE_CODE(ERROR_EXE_MACHINE_TYPE_MISMATCH)
                CASE_CODE(ERROR_BAD_PIPE)
                CASE_CODE(ERROR_PIPE_BUSY)
                CASE_CODE(ERROR_NO_DATA)
                CASE_CODE(ERROR_PIPE_NOT_CONNECTED)
                CASE_CODE(ERROR_MORE_DATA)
                CASE_CODE(ERROR_VC_DISCONNECTED)
                CASE_CODE(ERROR_INVALID_EA_NAME)

CASE_CODE(ERROR_EA_LIST_INCONSISTENT)
                CASE_CODE(ERROR_NO_MORE_ITEMS)
                CASE_CODE(ERROR_CANNOT_COPY)
                CASE_CODE(ERROR_DIRECTORY)
                CASE_CODE(ERROR_EAS_DIDNT_FIT)
                CASE_CODE(ERROR_EA_FILE_CORRUPT)
                CASE_CODE(ERROR_EA_TABLE_FULL)
                CASE_CODE(ERROR_INVALID_EA_HANDLE)
                CASE_CODE(ERROR_EAS_NOT_SUPPORTED)
                CASE_CODE(ERROR_NOT_OWNER)
                CASE_CODE(ERROR_TOO_MANY_POSTS)
                CASE_CODE(ERROR_PARTIAL_COPY)
                CASE_CODE(ERROR_MR_MID_NOT_FOUND)
                CASE_CODE(ERROR_INVALID_ADDRESS)
                CASE_CODE(ERROR_ARITHMETIC_OVERFLOW)
                CASE_CODE(ERROR_PIPE_CONNECTED)
                CASE_CODE(ERROR_PIPE_LISTENING)
                CASE_CODE(ERROR_EA_ACCESS_DENIED)
                CASE_CODE(ERROR_OPERATION_ABORTED)
                CASE_CODE(ERROR_IO_INCOMPLETE)
                CASE_CODE(ERROR_IO_PENDING)
                CASE_CODE(ERROR_NOACCESS)
                CASE_CODE(ERROR_SWAPERROR)
                CASE_CODE(ERROR_STACK_OVERFLOW)
                CASE_CODE(ERROR_INVALID_MESSAGE)
                CASE_CODE(ERROR_CAN_NOT_COMPLETE)
                CASE_CODE(ERROR_INVALID_FLAGS)
                CASE_CODE(ERROR_UNRECOGNIZED_VOLUME)
                CASE_CODE(ERROR_FILE_INVALID)
                CASE_CODE(ERROR_FULLSCREEN_MODE)
                CASE_CODE(ERROR_NO_TOKEN)
                CASE_CODE(ERROR_BADDB)
                CASE_CODE(ERROR_BADKEY)
                CASE_CODE(ERROR_CANTOPEN)
                CASE_CODE(ERROR_CANTREAD)
                CASE_CODE(ERROR_CANTWRITE)
                CASE_CODE(ERROR_REGISTRY_RECOVERED)
                CASE_CODE(ERROR_REGISTRY_CORRUPT)
                CASE_CODE(ERROR_REGISTRY_IO_FAILED)
                CASE_CODE(ERROR_NOT_REGISTRY_FILE)
                CASE_CODE(ERROR_KEY_DELETED)
                CASE_CODE(ERROR_NO_LOG_SPACE)
                CASE_CODE(ERROR_KEY_HAS_CHILDREN)
                CASE_CODE(ERROR_CHILD_MUST_BE_VOLATILE)
                CASE_CODE(ERROR_NOTIFY_ENUM_DIR)
                CASE_CODE(ERROR_DEPENDENT_SERVICES_RUNNING)
                CASE_CODE(ERROR_INVALID_SERVICE_CONTROL)
                CASE_CODE(ERROR_SERVICE_REQUEST_TIMEOUT)
                CASE_CODE(ERROR_SERVICE_NO_THREAD)
                CASE_CODE(ERROR_SERVICE_DATABASE_LOCKED)
                CASE_CODE(ERROR_SERVICE_ALREADY_RUNNING)
                CASE_CODE(ERROR_INVALID_SERVICE_ACCOUNT)
                CASE_CODE(ERROR_SERVICE_DISABLED)
                CASE_CODE(ERROR_CIRCULAR_DEPENDENCY)
                CASE_CODE(ERROR_SERVICE_DOES_NOT_EXIST)
                CASE_CODE(ERROR_SERVICE_CANNOT_ACCEPT_CTRL)
                CASE_CODE(ERROR_SERVICE_NOT_ACTIVE)
                CASE_CODE(ERROR_FAILED_SERVICE_CONTROLLER_CONNECT)
                CASE_CODE(ERROR_EXCEPTION_IN_SERVICE)
                CASE_CODE(ERROR_DATABASE_DOES_NOT_EXIST)
                CASE_CODE(ERROR_SERVICE_SPECIFIC_ERROR)
                CASE_CODE(ERROR_PROCESS_ABORTED)
                CASE_CODE(ERROR_SERVICE_DEPENDENCY_FAIL)
                CASE_CODE(ERROR_SERVICE_LOGON_FAILED)
                CASE_CODE(ERROR_SERVICE_START_HANG)
                CASE_CODE(ERROR_INVALID_SERVICE_LOCK)
                CASE_CODE(ERROR_SERVICE_MARKED_FOR_DELETE)
                CASE_CODE(ERROR_SERVICE_EXISTS)
                CASE_CODE(ERROR_ALREADY_RUNNING_LKG)
                CASE_CODE(ERROR_SERVICE_DEPENDENCY_DELETED)
                CASE_CODE(ERROR_BOOT_ALREADY_ACCEPTED)
                CASE_CODE(ERROR_SERVICE_NEVER_STARTED)
                CASE_CODE(ERROR_DUPLICATE_SERVICE_NAME)
                CASE_CODE(ERROR_DIFFERENT_SERVICE_ACCOUNT)
                CASE_CODE(ERROR_END_OF_MEDIA)
                CASE_CODE(ERROR_FILEMARK_DETECTED)
                CASE_CODE(ERROR_BEGINNING_OF_MEDIA)
                CASE_CODE(ERROR_SETMARK_DETECTED)
                CASE_CODE(ERROR_NO_DATA_DETECTED)
                CASE_CODE(ERROR_PARTITION_FAILURE)
                CASE_CODE(ERROR_INVALID_BLOCK_LENGTH)
                CASE_CODE(ERROR_DEVICE_NOT_PARTITIONED)
                CASE_CODE(ERROR_UNABLE_TO_LOCK_MEDIA)
                CASE_CODE(ERROR_UNABLE_TO_UNLOAD_MEDIA)
                CASE_CODE(ERROR_MEDIA_CHANGED)
                CASE_CODE(ERROR_BUS_RESET)
                CASE_CODE(ERROR_NO_MEDIA_IN_DRIVE)
                CASE_CODE(ERROR_NO_UNICODE_TRANSLATION)
                CASE_CODE(ERROR_DLL_INIT_FAILED)
                CASE_CODE(ERROR_SHUTDOWN_IN_PROGRESS)
                CASE_CODE(ERROR_NO_SHUTDOWN_IN_PROGRESS)
                CASE_CODE(ERROR_IO_DEVICE)
                CASE_CODE(ERROR_SERIAL_NO_DEVICE)
                CASE_CODE(ERROR_IRQ_BUSY)
                CASE_CODE(ERROR_MORE_WRITES)
                CASE_CODE(ERROR_COUNTER_TIMEOUT)
                CASE_CODE(ERROR_FLOPPY_ID_MARK_NOT_FOUND)
                CASE_CODE(ERROR_FLOPPY_WRONG_CYLINDER)
                CASE_CODE(ERROR_FLOPPY_UNKNOWN_ERROR)
                CASE_CODE(ERROR_FLOPPY_BAD_REGISTERS)
                CASE_CODE(ERROR_DISK_RECALIBRATE_FAILED)
                CASE_CODE(ERROR_DISK_OPERATION_FAILED)
                CASE_CODE(ERROR_DISK_RESET_FAILED)
                CASE_CODE(ERROR_EOM_OVERFLOW)
                CASE_CODE(ERROR_NOT_ENOUGH_SERVER_MEMORY)
                CASE_CODE(ERROR_POSSIBLE_DEADLOCK)
                CASE_CODE(ERROR_MAPPED_ALIGNMENT)
                CASE_CODE(ERROR_SET_POWER_STATE_VETOED)
                CASE_CODE(ERROR_SET_POWER_STATE_FAILED)
                CASE_CODE(ERROR_TOO_MANY_LINKS)
                CASE_CODE(ERROR_OLD_WIN_VERSION)
                CASE_CODE(ERROR_APP_WRONG_OS)
                CASE_CODE(ERROR_SINGLE_INSTANCE_APP)
                CASE_CODE(ERROR_RMODE_APP)
                CASE_CODE(ERROR_INVALID_DLL)
                CASE_CODE(ERROR_NO_ASSOCIATION)
                CASE_CODE(ERROR_DDE_FAIL)
                CASE_CODE(ERROR_DLL_NOT_FOUND)
                CASE_CODE(ERROR_BAD_USERNAME)
                CASE_CODE(ERROR_NOT_CONNECTED)
                CASE_CODE(ERROR_OPEN_FILES)
                CASE_CODE(ERROR_ACTIVE_CONNECTIONS)
                CASE_CODE(ERROR_DEVICE_IN_USE)
                CASE_CODE(ERROR_BAD_DEVICE)
                CASE_CODE(ERROR_CONNECTION_UNAVAIL)
                CASE_CODE(ERROR_DEVICE_ALREADY_REMEMBERED)
                CASE_CODE(ERROR_NO_NET_OR_BAD_PATH)
                CASE_CODE(ERROR_BAD_PROVIDER)
                CASE_CODE(ERROR_CANNOT_OPEN_PROFILE)
                CASE_CODE(ERROR_BAD_PROFILE)
                CASE_CODE(ERROR_NOT_CONTAINER)
                CASE_CODE(ERROR_EXTENDED_ERROR)
                CASE_CODE(ERROR_INVALID_GROUPNAME)
                CASE_CODE(ERROR_INVALID_COMPUTERNAME)
                CASE_CODE(ERROR_INVALID_EVENTNAME)
                CASE_CODE(ERROR_INVALID_DOMAINNAME)
                CASE_CODE(ERROR_INVALID_SERVICENAME)
                CASE_CODE(ERROR_INVALID_NETNAME)
                CASE_CODE(ERROR_INVALID_SHARENAME)
                CASE_CODE(ERROR_INVALID_PASSWORDNAME)
                CASE_CODE(ERROR_INVALID_MESSAGENAME)
                CASE_CODE(ERROR_INVALID_MESSAGEDEST)
                CASE_CODE(ERROR_SESSION_CREDENTIAL_CONFLICT)
                CASE_CODE(ERROR_REMOTE_SESSION_LIMIT_EXCEEDED)
                CASE_CODE(ERROR_DUP_DOMAINNAME)
                CASE_CODE(ERROR_NO_NETWORK)
                CASE_CODE(ERROR_CANCELLED)
                CASE_CODE(ERROR_USER_MAPPED_FILE)
                CASE_CODE(ERROR_CONNECTION_REFUSED)
                CASE_CODE(ERROR_GRACEFUL_DISCONNECT)
                CASE_CODE(ERROR_ADDRESS_ALREADY_ASSOCIATED)
                CASE_CODE(ERROR_ADDRESS_NOT_ASSOCIATED)
                CASE_CODE(ERROR_CONNECTION_INVALID)
                CASE_CODE(ERROR_CONNECTION_ACTIVE)
                CASE_CODE(ERROR_NETWORK_UNREACHABLE)
                CASE_CODE(ERROR_HOST_UNREACHABLE)
                CASE_CODE(ERROR_PROTOCOL_UNREACHABLE)
                CASE_CODE(ERROR_PORT_UNREACHABLE)
                CASE_CODE(ERROR_REQUEST_ABORTED)
                CASE_CODE(ERROR_CONNECTION_ABORTED)
                CASE_CODE(ERROR_RETRY)
                CASE_CODE(ERROR_CONNECTION_COUNT_LIMIT)
                CASE_CODE(ERROR_LOGIN_TIME_RESTRICTION)
                CASE_CODE(ERROR_LOGIN_WKSTA_RESTRICTION)
                CASE_CODE(ERROR_INCORRECT_ADDRESS)
                CASE_CODE(ERROR_ALREADY_REGISTERED)
                CASE_CODE(ERROR_SERVICE_NOT_FOUND)
                CASE_CODE(ERROR_NOT_AUTHENTICATED)
                CASE_CODE(ERROR_NOT_LOGGED_ON)
                CASE_CODE(ERROR_CONTINUE)
                CASE_CODE(ERROR_ALREADY_INITIALIZED)
                CASE_CODE(ERROR_NO_MORE_DEVICES)
                CASE_CODE(ERROR_NOT_ALL_ASSIGNED)
                CASE_CODE(ERROR_SOME_NOT_MAPPED)
                CASE_CODE(ERROR_NO_QUOTAS_FOR_ACCOUNT)
                CASE_CODE(ERROR_LOCAL_USER_SESSION_KEY)
                CASE_CODE(ERROR_NULL_LM_PASSWORD)
                CASE_CODE(ERROR_UNKNOWN_REVISION)
                CASE_CODE(ERROR_REVISION_MISMATCH)
                CASE_CODE(ERROR_INVALID_OWNER)
                CASE_CODE(ERROR_INVALID_PRIMARY_GROUP)
                CASE_CODE(ERROR_NO_IMPERSONATION_TOKEN)
                CASE_CODE(ERROR_CANT_DISABLE_MANDATORY)
                CASE_CODE(ERROR_NO_LOGON_SERVERS)
                CASE_CODE(ERROR_NO_SUCH_LOGON_SESSION)
                CASE_CODE(ERROR_NO_SUCH_PRIVILEGE)
                CASE_CODE(ERROR_PRIVILEGE_NOT_HELD)
                CASE_CODE(ERROR_INVALID_ACCOUNT_NAME)
                CASE_CODE(ERROR_USER_EXISTS)
                CASE_CODE(ERROR_NO_SUCH_USER)
                CASE_CODE(ERROR_GROUP_EXISTS)
                CASE_CODE(ERROR_NO_SUCH_GROUP)
                CASE_CODE(ERROR_MEMBER_IN_GROUP)
                CASE_CODE(ERROR_MEMBER_NOT_IN_GROUP)
                CASE_CODE(ERROR_LAST_ADMIN)
                CASE_CODE(ERROR_WRONG_PASSWORD)
                CASE_CODE(ERROR_ILL_FORMED_PASSWORD)
                CASE_CODE(ERROR_PASSWORD_RESTRICTION)
                CASE_CODE(ERROR_LOGON_FAILURE)
                CASE_CODE(ERROR_ACCOUNT_RESTRICTION)
                CASE_CODE(ERROR_INVALID_LOGON_HOURS)
                CASE_CODE(ERROR_INVALID_WORKSTATION)
                CASE_CODE(ERROR_PASSWORD_EXPIRED)
                CASE_CODE(ERROR_ACCOUNT_DISABLED)
                CASE_CODE(ERROR_NONE_MAPPED)
                CASE_CODE(ERROR_TOO_MANY_LUIDS_REQUESTED)
                CASE_CODE(ERROR_LUIDS_EXHAUSTED)
                CASE_CODE(ERROR_INVALID_SUB_AUTHORITY)
                CASE_CODE(ERROR_INVALID_ACL)
                CASE_CODE(ERROR_INVALID_SID)
                CASE_CODE(ERROR_INVALID_SECURITY_DESCR)
                CASE_CODE(ERROR_BAD_INHERITANCE_ACL)
                CASE_CODE(ERROR_SERVER_DISABLED)
                CASE_CODE(ERROR_SERVER_NOT_DISABLED)
                CASE_CODE(ERROR_INVALID_ID_AUTHORITY)
                CASE_CODE(ERROR_ALLOTTED_SPACE_EXCEEDED)
                CASE_CODE(ERROR_INVALID_GROUP_ATTRIBUTES)
                CASE_CODE(ERROR_BAD_IMPERSONATION_LEVEL)
                CASE_CODE(ERROR_CANT_OPEN_ANONYMOUS)
                CASE_CODE(ERROR_BAD_VALIDATION_CLASS)
                CASE_CODE(ERROR_BAD_TOKEN_TYPE)
                CASE_CODE(ERROR_NO_SECURITY_ON_OBJECT)
                CASE_CODE(ERROR_CANT_ACCESS_DOMAIN_INFO)
                CASE_CODE(ERROR_INVALID_SERVER_STATE)
                CASE_CODE(ERROR_INVALID_DOMAIN_STATE)
                CASE_CODE(ERROR_INVALID_DOMAIN_ROLE)
                CASE_CODE(ERROR_NO_SUCH_DOMAIN)
                CASE_CODE(ERROR_DOMAIN_EXISTS)
                CASE_CODE(ERROR_DOMAIN_LIMIT_EXCEEDED)
                CASE_CODE(ERROR_INTERNAL_DB_CORRUPTION)
                CASE_CODE(ERROR_INTERNAL_ERROR)
                CASE_CODE(ERROR_GENERIC_NOT_MAPPED)
                CASE_CODE(ERROR_BAD_DESCRIPTOR_FORMAT)
                CASE_CODE(ERROR_NOT_LOGON_PROCESS)
                CASE_CODE(ERROR_LOGON_SESSION_EXISTS)
                CASE_CODE(ERROR_NO_SUCH_PACKAGE)
                CASE_CODE(ERROR_BAD_LOGON_SESSION_STATE)
                CASE_CODE(ERROR_LOGON_SESSION_COLLISION)
                CASE_CODE(ERROR_INVALID_LOGON_TYPE)
                CASE_CODE(ERROR_CANNOT_IMPERSONATE)
                CASE_CODE(ERROR_RXACT_INVALID_STATE)
                CASE_CODE(ERROR_RXACT_COMMIT_FAILURE)
                CASE_CODE(ERROR_SPECIAL_ACCOUNT)
                CASE_CODE(ERROR_SPECIAL_GROUP)
                CASE_CODE(ERROR_SPECIAL_USER)
                CASE_CODE(ERROR_MEMBERS_PRIMARY_GROUP)
                CASE_CODE(ERROR_TOKEN_ALREADY_IN_USE)
                CASE_CODE(ERROR_NO_SUCH_ALIAS)
                CASE_CODE(ERROR_MEMBER_NOT_IN_ALIAS)
                CASE_CODE(ERROR_MEMBER_IN_ALIAS)
                CASE_CODE(ERROR_ALIAS_EXISTS)
                CASE_CODE(ERROR_LOGON_NOT_GRANTED)
                CASE_CODE(ERROR_TOO_MANY_SECRETS)
                CASE_CODE(ERROR_SECRET_TOO_LONG)
                CASE_CODE(ERROR_INTERNAL_DB_ERROR)
                CASE_CODE(ERROR_TOO_MANY_CONTEXT_IDS)
                CASE_CODE(ERROR_LOGON_TYPE_NOT_GRANTED)
                CASE_CODE(ERROR_NT_CROSS_ENCRYPTION_REQUIRED)
                CASE_CODE(ERROR_NO_SUCH_MEMBER)
                CASE_CODE(ERROR_INVALID_MEMBER)
                CASE_CODE(ERROR_TOO_MANY_SIDS)
                CASE_CODE(ERROR_LM_CROSS_ENCRYPTION_REQUIRED)
                CASE_CODE(ERROR_NO_INHERITANCE)
                CASE_CODE(ERROR_FILE_CORRUPT)
                CASE_CODE(ERROR_DISK_CORRUPT)
                CASE_CODE(ERROR_NO_USER_SESSION_KEY)
                CASE_CODE(ERROR_LICENSE_QUOTA_EXCEEDED)
                CASE_CODE(ERROR_INVALID_WINDOW_HANDLE)
                CASE_CODE(ERROR_INVALID_MENU_HANDLE)
                CASE_CODE(ERROR_INVALID_CURSOR_HANDLE)
                CASE_CODE(ERROR_INVALID_ACCEL_HANDLE)
                CASE_CODE(ERROR_INVALID_HOOK_HANDLE)
                CASE_CODE(ERROR_INVALID_DWP_HANDLE)
                CASE_CODE(ERROR_TLW_WITH_WSCHILD)
                CASE_CODE(ERROR_CANNOT_FIND_WND_CLASS)
                CASE_CODE(ERROR_WINDOW_OF_OTHER_THREAD)
                CASE_CODE(ERROR_HOTKEY_ALREADY_REGISTERED)
                CASE_CODE(ERROR_CLASS_ALREADY_EXISTS)
                CASE_CODE(ERROR_CLASS_DOES_NOT_EXIST)
                CASE_CODE(ERROR_CLASS_HAS_WINDOWS)
                CASE_CODE(ERROR_INVALID_INDEX)
                CASE_CODE(ERROR_INVALID_ICON_HANDLE)
                CASE_CODE(ERROR_PRIVATE_DIALOG_INDEX)
                CASE_CODE(ERROR_LISTBOX_ID_NOT_FOUND)
                CASE_CODE(ERROR_NO_WILDCARD_CHARACTERS)
                CASE_CODE(ERROR_CLIPBOARD_NOT_OPEN)
                CASE_CODE(ERROR_HOTKEY_NOT_REGISTERED)
                CASE_CODE(ERROR_WINDOW_NOT_DIALOG)
                CASE_CODE(ERROR_CONTROL_ID_NOT_FOUND)
                CASE_CODE(ERROR_INVALID_COMBOBOX_MESSAGE)
                CASE_CODE(ERROR_WINDOW_NOT_COMBOBOX)
                CASE_CODE(ERROR_INVALID_EDIT_HEIGHT)
                CASE_CODE(ERROR_DC_NOT_FOUND)
                CASE_CODE(ERROR_INVALID_HOOK_FILTER)
                CASE_CODE(ERROR_INVALID_FILTER_PROC)
                CASE_CODE(ERROR_HOOK_NEEDS_HMOD)
                CASE_CODE(ERROR_GLOBAL_ONLY_HOOK)
                CASE_CODE(ERROR_JOURNAL_HOOK_SET)
                CASE_CODE(ERROR_HOOK_NOT_INSTALLED)
                CASE_CODE(ERROR_INVALID_LB_MESSAGE)
                CASE_CODE(ERROR_SETCOUNT_ON_BAD_LB)
                CASE_CODE(ERROR_LB_WITHOUT_TABSTOPS)
                CASE_CODE(ERROR_DESTROY_OBJECT_OF_OTHER_THREAD)
                CASE_CODE(ERROR_CHILD_WINDOW_MENU)
                CASE_CODE(ERROR_NO_SYSTEM_MENU)
                CASE_CODE(ERROR_INVALID_MSGBOX_STYLE)
                CASE_CODE(ERROR_INVALID_SPI_VALUE)
                CASE_CODE(ERROR_SCREEN_ALREADY_LOCKED)
                CASE_CODE(ERROR_HWNDS_HAVE_DIFF_PARENT)
                CASE_CODE(ERROR_NOT_CHILD_WINDOW)
                CASE_CODE(ERROR_INVALID_GW_COMMAND)
                CASE_CODE(ERROR_INVALID_THREAD_ID)
                CASE_CODE(ERROR_NON_MDICHILD_WINDOW)
                CASE_CODE(ERROR_POPUP_ALREADY_ACTIVE)
                CASE_CODE(ERROR_NO_SCROLLBARS)
                CASE_CODE(ERROR_INVALID_SCROLLBAR_RANGE)
                CASE_CODE(ERROR_INVALID_SHOWWIN_COMMAND)
                CASE_CODE(ERROR_NO_SYSTEM_RESOURCES)
                CASE_CODE(ERROR_NONPAGED_SYSTEM_RESOURCES)
                CASE_CODE(ERROR_PAGED_SYSTEM_RESOURCES)
                CASE_CODE(ERROR_WORKING_SET_QUOTA)
                CASE_CODE(ERROR_PAGEFILE_QUOTA)
                CASE_CODE(ERROR_COMMITMENT_LIMIT)
                CASE_CODE(ERROR_MENU_ITEM_NOT_FOUND)
                CASE_CODE(ERROR_INVALID_KEYBOARD_HANDLE)
                CASE_CODE(ERROR_HOOK_TYPE_NOT_ALLOWED)
                CASE_CODE(ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION)
                CASE_CODE(ERROR_EVENTLOG_FILE_CORRUPT)
                CASE_CODE(ERROR_EVENTLOG_CANT_START)
                CASE_CODE(ERROR_LOG_FILE_FULL)
                CASE_CODE(ERROR_EVENTLOG_FILE_CHANGED)
                CASE_CODE(RPC_S_INVALID_STRING_BINDING)
                CASE_CODE(RPC_S_WRONG_KIND_OF_BINDING)
                CASE_CODE(RPC_S_INVALID_BINDING)
                CASE_CODE(RPC_S_PROTSEQ_NOT_SUPPORTED)
                CASE_CODE(RPC_S_INVALID_RPC_PROTSEQ)
                CASE_CODE(RPC_S_INVALID_STRING_UUID)
                CASE_CODE(RPC_S_INVALID_ENDPOINT_FORMAT)
                CASE_CODE(RPC_S_INVALID_NET_ADDR)
                CASE_CODE(RPC_S_NO_ENDPOINT_FOUND)
                CASE_CODE(RPC_S_INVALID_TIMEOUT)
                CASE_CODE(RPC_S_OBJECT_NOT_FOUND)
                CASE_CODE(RPC_S_ALREADY_REGISTERED)
                CASE_CODE(RPC_S_TYPE_ALREADY_REGISTERED)
                CASE_CODE(RPC_S_ALREADY_LISTENING)
                CASE_CODE(RPC_S_NO_PROTSEQS_REGISTERED)
                CASE_CODE(RPC_S_NOT_LISTENING)
                CASE_CODE(RPC_S_UNKNOWN_MGR_TYPE)
                CASE_CODE(RPC_S_UNKNOWN_IF)
                CASE_CODE(RPC_S_NO_BINDINGS)
                CASE_CODE(RPC_S_NO_PROTSEQS)
                CASE_CODE(RPC_S_CANT_CREATE_ENDPOINT)
                CASE_CODE(RPC_S_OUT_OF_RESOURCES)
                CASE_CODE(RPC_S_SERVER_UNAVAILABLE)
                CASE_CODE(RPC_S_SERVER_TOO_BUSY)
                CASE_CODE(RPC_S_INVALID_NETWORK_OPTIONS)
                CASE_CODE(RPC_S_NO_CALL_ACTIVE)
                CASE_CODE(RPC_S_CALL_FAILED)
                CASE_CODE(RPC_S_CALL_FAILED_DNE)
                CASE_CODE(RPC_S_PROTOCOL_ERROR)
                CASE_CODE(RPC_S_UNSUPPORTED_TRANS_SYN)
                CASE_CODE(RPC_S_UNSUPPORTED_TYPE)
                CASE_CODE(RPC_S_INVALID_TAG)
                CASE_CODE(RPC_S_INVALID_BOUND)
                CASE_CODE(RPC_S_NO_ENTRY_NAME)
                CASE_CODE(RPC_S_INVALID_NAME_SYNTAX)
                CASE_CODE(RPC_S_UNSUPPORTED_NAME_SYNTAX)
                CASE_CODE(RPC_S_UUID_NO_ADDRESS)
                CASE_CODE(RPC_S_DUPLICATE_ENDPOINT)
                CASE_CODE(RPC_S_UNKNOWN_AUTHN_TYPE)
                CASE_CODE(RPC_S_MAX_CALLS_TOO_SMALL)
                CASE_CODE(RPC_S_STRING_TOO_LONG)
                CASE_CODE(RPC_S_PROTSEQ_NOT_FOUND)
                CASE_CODE(RPC_S_PROCNUM_OUT_OF_RANGE)
                CASE_CODE(RPC_S_BINDING_HAS_NO_AUTH)
                CASE_CODE(RPC_S_UNKNOWN_AUTHN_SERVICE)
                CASE_CODE(RPC_S_UNKNOWN_AUTHN_LEVEL)
                CASE_CODE(RPC_S_INVALID_AUTH_IDENTITY)
                CASE_CODE(RPC_S_UNKNOWN_AUTHZ_SERVICE)
                CASE_CODE(EPT_S_INVALID_ENTRY)
                CASE_CODE(EPT_S_CANT_PERFORM_OP)
                CASE_CODE(EPT_S_NOT_REGISTERED)
                CASE_CODE(RPC_S_NOTHING_TO_EXPORT)
                CASE_CODE(RPC_S_INCOMPLETE_NAME)
                CASE_CODE(RPC_S_INVALID_VERS_OPTION)
                CASE_CODE(RPC_S_NO_MORE_MEMBERS)
                CASE_CODE(RPC_S_NOT_ALL_OBJS_UNEXPORTED)
                CASE_CODE(RPC_S_INTERFACE_NOT_FOUND)
                CASE_CODE(RPC_S_ENTRY_ALREADY_EXISTS)
                CASE_CODE(RPC_S_ENTRY_NOT_FOUND)
                CASE_CODE(RPC_S_NAME_SERVICE_UNAVAILABLE)
                CASE_CODE(RPC_S_INVALID_NAF_ID)
                CASE_CODE(RPC_S_CANNOT_SUPPORT)
                CASE_CODE(RPC_S_NO_CONTEXT_AVAILABLE)
                CASE_CODE(RPC_S_INTERNAL_ERROR)
                CASE_CODE(RPC_S_ZERO_DIVIDE)
                CASE_CODE(RPC_S_ADDRESS_ERROR)
                CASE_CODE(RPC_S_FP_DIV_ZERO)
                CASE_CODE(RPC_S_FP_UNDERFLOW)
                CASE_CODE(RPC_S_FP_OVERFLOW)
                CASE_CODE(RPC_X_NO_MORE_ENTRIES)
                CASE_CODE(RPC_X_SS_CHAR_TRANS_OPEN_FAIL)
                CASE_CODE(RPC_X_SS_CHAR_TRANS_SHORT_FILE)
                CASE_CODE(RPC_X_SS_IN_NULL_CONTEXT)
                CASE_CODE(RPC_X_SS_CONTEXT_DAMAGED)
                CASE_CODE(RPC_X_SS_HANDLES_MISMATCH)
                CASE_CODE(RPC_X_SS_CANNOT_GET_CALL_HANDLE)
                CASE_CODE(RPC_X_NULL_REF_POINTER)
                CASE_CODE(RPC_X_ENUM_VALUE_OUT_OF_RANGE)
                CASE_CODE(RPC_X_BYTE_COUNT_TOO_SMALL)
                CASE_CODE(RPC_X_BAD_STUB_DATA)
                CASE_CODE(ERROR_INVALID_USER_BUFFER)
                CASE_CODE(ERROR_UNRECOGNIZED_MEDIA)
                CASE_CODE(ERROR_NO_TRUST_LSA_SECRET)
                CASE_CODE(ERROR_NO_TRUST_SAM_ACCOUNT)
                CASE_CODE(ERROR_TRUSTED_DOMAIN_FAILURE)
                CASE_CODE(ERROR_TRUSTED_RELATIONSHIP_FAILURE)
                CASE_CODE(ERROR_TRUST_FAILURE)
                CASE_CODE(RPC_S_CALL_IN_PROGRESS)
                CASE_CODE(ERROR_NETLOGON_NOT_STARTED)
                CASE_CODE(ERROR_ACCOUNT_EXPIRED)
                CASE_CODE(ERROR_REDIRECTOR_HAS_OPEN_HANDLES)
                CASE_CODE(ERROR_PRINTER_DRIVER_ALREADY_INSTALLED)
                CASE_CODE(ERROR_UNKNOWN_PORT)
                CASE_CODE(ERROR_UNKNOWN_PRINTER_DRIVER)
                CASE_CODE(ERROR_UNKNOWN_PRINTPROCESSOR)
                CASE_CODE(ERROR_INVALID_SEPARATOR_FILE)
                CASE_CODE(ERROR_INVALID_PRIORITY)
                CASE_CODE(ERROR_INVALID_PRINTER_NAME)
                CASE_CODE(ERROR_PRINTER_ALREADY_EXISTS)
                CASE_CODE(ERROR_INVALID_PRINTER_COMMAND)
                CASE_CODE(ERROR_INVALID_DATATYPE)
                CASE_CODE(ERROR_INVALID_ENVIRONMENT)
                CASE_CODE(RPC_S_NO_MORE_BINDINGS)
                CASE_CODE(ERROR_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT)
                CASE_CODE(ERROR_NOLOGON_WORKSTATION_TRUST_ACCOUNT)
                CASE_CODE(ERROR_NOLOGON_SERVER_TRUST_ACCOUNT)
                CASE_CODE(ERROR_DOMAIN_TRUST_INCONSISTENT)
                CASE_CODE(ERROR_SERVER_HAS_OPEN_HANDLES)
                CASE_CODE(ERROR_RESOURCE_DATA_NOT_FOUND)
                CASE_CODE(ERROR_RESOURCE_TYPE_NOT_FOUND)
                CASE_CODE(ERROR_RESOURCE_NAME_NOT_FOUND)
                CASE_CODE(ERROR_RESOURCE_LANG_NOT_FOUND)
                CASE_CODE(ERROR_NOT_ENOUGH_QUOTA)
                CASE_CODE(RPC_S_NO_INTERFACES)
                CASE_CODE(RPC_S_CALL_CANCELLED)
                CASE_CODE(RPC_S_BINDING_INCOMPLETE)
                CASE_CODE(RPC_S_COMM_FAILURE)
                CASE_CODE(RPC_S_UNSUPPORTED_AUTHN_LEVEL)
                CASE_CODE(RPC_S_NO_PRINC_NAME)
                CASE_CODE(RPC_S_NOT_RPC_ERROR)
                CASE_CODE(RPC_S_UUID_LOCAL_ONLY)
                CASE_CODE(RPC_S_SEC_PKG_ERROR)
                CASE_CODE(RPC_S_NOT_CANCELLED)
                CASE_CODE(RPC_X_INVALID_ES_ACTION)
                CASE_CODE(RPC_X_WRONG_ES_VERSION)
                CASE_CODE(RPC_X_WRONG_STUB_VERSION)
                CASE_CODE(RPC_X_INVALID_PIPE_OBJECT)
                CASE_CODE(RPC_X_INVALID_PIPE_OPERATION)
                CASE_CODE(RPC_X_WRONG_PIPE_VERSION)
                CASE_CODE(RPC_S_GROUP_MEMBER_NOT_FOUND)
                CASE_CODE(EPT_S_CANT_CREATE)
                CASE_CODE(RPC_S_INVALID_OBJECT)
                CASE_CODE(ERROR_INVALID_TIME)
                CASE_CODE(ERROR_INVALID_FORM_NAME)
                CASE_CODE(ERROR_INVALID_FORM_SIZE)
                CASE_CODE(ERROR_ALREADY_WAITING)
                CASE_CODE(ERROR_PRINTER_DELETED)
                CASE_CODE(ERROR_INVALID_PRINTER_STATE)
                CASE_CODE(ERROR_PASSWORD_MUST_CHANGE)
                CASE_CODE(ERROR_DOMAIN_CONTROLLER_NOT_FOUND)
                CASE_CODE(ERROR_ACCOUNT_LOCKED_OUT)
                CASE_CODE(OR_INVALID_OXID)
                CASE_CODE(OR_INVALID_OID)
                CASE_CODE(OR_INVALID_SET)
                CASE_CODE(RPC_S_SEND_INCOMPLETE)
                CASE_CODE(ERROR_NO_BROWSER_SERVERS_FOUND)
                CASE_CODE(ERROR_INVALID_PIXEL_FORMAT)
                CASE_CODE(ERROR_BAD_DRIVER)
                CASE_CODE(ERROR_INVALID_WINDOW_STYLE)
                CASE_CODE(ERROR_METAFILE_NOT_SUPPORTED)
                CASE_CODE(ERROR_TRANSFORM_NOT_SUPPORTED)
                CASE_CODE(ERROR_CLIPPING_NOT_SUPPORTED)
                CASE_CODE(ERROR_UNKNOWN_PRINT_MONITOR)
                CASE_CODE(ERROR_PRINTER_DRIVER_IN_USE)
                CASE_CODE(ERROR_SPOOL_FILE_NOT_FOUND)
                CASE_CODE(ERROR_SPL_NO_STARTDOC)
                CASE_CODE(ERROR_SPL_NO_ADDJOB)
                CASE_CODE(ERROR_PRINT_PROCESSOR_ALREADY_INSTALLED)
                CASE_CODE(ERROR_PRINT_MONITOR_ALREADY_INSTALLED)
                CASE_CODE(ERROR_INVALID_PRINT_MONITOR)
                CASE_CODE(ERROR_PRINT_MONITOR_IN_USE)
                CASE_CODE(ERROR_PRINTER_HAS_JOBS_QUEUED)
                CASE_CODE(ERROR_SUCCESS_REBOOT_REQUIRED)
                CASE_CODE(ERROR_SUCCESS_RESTART_REQUIRED)
                CASE_CODE(ERROR_WINS_INTERNAL)
                CASE_CODE(ERROR_CAN_NOT_DEL_LOCAL_WINS)
                CASE_CODE(ERROR_STATIC_INIT)
                CASE_CODE(ERROR_INC_BACKUP)
                CASE_CODE(ERROR_FULL_BACKUP)
                CASE_CODE(ERROR_REC_NON_EXISTENT)
                CASE_CODE(ERROR_RPL_NOT_ALLOWED)

                default:
                lstrcpy(szErrorName, TEXT("Unknown or new error"));
                FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, hr,
                        MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
                        szErrorDesc, sizeof(szErrorDesc), NULL);
                }
           }  // if FACILITY_WIN32
           else  if (HRESULT_FACILITY(hr) == FACILITY_CONTROL && HRESULT_CODE(hr) >= 600)
           {
                szErrorName[0] = '\0';
                lstrcpy(szErrorDesc, TEXT("This is a FACILITY_CONTROL error that is specific to the control's interface that returned this error. See the documentation of the interface that returned this error for information about this HRESULT."));
           }
           else  if (HRESULT_FACILITY(hr) == FACILITY_ITF && HRESULT_CODE(hr) >= 0x200)
           {
                szErrorName[0] = '\0';
                lstrcpy(szErrorDesc, TEXT("This is a FACILITY_ITF error that is specific to the interface that returned this error. See the documentation of the interface that returned this error for information about this HRESULT."));
           }
           else
           {
              lstrcpy(szErrorName, TEXT("Unknown or new error"));  // Not FACILITY_WIN32 or FACILITY_ITF(code >= 0x200) or FACILTY_CONTROL(code >= 600)
              FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, hr,
                             MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
                             szErrorDesc, sizeof(szErrorDesc), NULL);
           }
    }

    if (bOleError)
         wsprintf(szBuf, TEXT("HRESULT = %lx \r\nFACILITY = %s\n%s"), hr, (LPTSTR)szFacility, (LPTSTR)szErrorName);
    else wsprintf(szBuf, TEXT("ERROR = %ld \r\n%s"), HRESULT_CODE(hr), (LPTSTR)szErrorName);
SetWindowText(hwndDisplayErrorName, szBuf);
    SetWindowText(hwndDisplayErrorDesc, szErrorDesc);
}


API.C

/*
 * API.C
 * GizmoBar Version 1.01
 *
 * API functions affecting a GizmoBar and a message processing
 * function to handle the equivalent called through messages.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include <windows.h>
#include <malloc.h>
#include "gizmoint.h"

/*
 * GBMessageHandler
 *
 * Purpose:
 *  Processes control messages that are equivalents of available
 *  control API.  The data passed with these messages is simply
 *  extracted from structures and passed as parameters to their
 *  equivalent function.
 *
 * Parameters:
 *  <Standard Message Parameters> plus
 *  pGB             PGIZMOBAR providing control-specific data.
 *
 * Return Value:
 *  LRESULT         Return value from equivalent API function.
 */

LRESULT GBMessageHandler(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, PGIZMOBAR pGB)
    {
    LRESULT         lRet=0L;
    LPCREATEGIZMOW   pCGW;
    LPCREATEGIZMOA   pCGA;
    LPGBMSG         pMsg;
    LPGBGETTEXTW    pGTW;
    LPGBGETTEXTA    pGTA;
    LPGBGETINT      pGI;
    LPGBSETINT      pSI;

    if (NULL==pGB)
        return 0L;

    switch (iMsg)
        {
        case GBM_HWNDASSOCIATESET:
            lRet=(LRESULT)(UINT)GBHwndAssociateSet(hWnd
                , (HWND)wParam);
            break;

        case GBM_HWNDASSOCIATEGET:
            lRet=(LRESULT)(UINT)GBHwndAssociateGet(hWnd);
            break;

        case GBM_GIZMOADDW:
            pCGW=(LPCREATEGIZMOW)lParam;
            lRet=(LRESULT)GBGizmoAddW(pCGW->hWndParent, pCGW->iType
                , pCGW->iGizmo, pCGW->uID, pCGW->dx, pCGW->dy
                , pCGW->pszText, pCGW->hBmp, pCGW->iImage, pCGW->uState);
            break;

        case GBM_GIZMOADDA:
            pCGA=(LPCREATEGIZMOA)lParam;
            lRet=(LRESULT)GBGizmoAddA(pCGA->hWndParent, pCGA->iType
                , pCGA->iGizmo, pCGA->uID, pCGA->dx, pCGA->dy
                , pCGA->pszText, pCGA->hBmp, pCGA->iImage, pCGA->uState);
            break;

        case GBM_GIZMOREMOVE:
            lRet=(LRESULT)GBGizmoRemove(hWnd, wParam);
            break;

        case GBM_GIZMOSENDMESSAGE:
            pMsg=(LPGBMSG)lParam;
            lRet=GBGizmoSendMessage(hWnd, wParam, pMsg->iMsg
                , pMsg->wParam, pMsg->lParam);
            break;

        case GBM_GIZMOSHOW:
            lRet=(LRESULT)GBGizmoShow(hWnd, wParam
                , (BOOL)LOWORD(lParam));
            break;

        case GBM_GIZMOENABLE:
            lRet=(LRESULT)GBGizmoEnable(hWnd, wParam
                , (BOOL)LOWORD(lParam));
            break;

        case GBM_GIZMOCHECK:
            lRet=(LRESULT)GBGizmoCheck(hWnd, wParam
                , (BOOL)LOWORD(lParam));
            break;

        case GBM_GIZMOFOCUSSET:
            lRet=(LRESULT)GBGizmoFocusSet(hWnd, wParam);
            break;

        case GBM_GIZMOEXIST:
            lRet=(LRESULT)GBGizmoExist(hWnd, wParam);
            break;

        case GBM_GIZMOTYPEGET:
            lRet=(LRESULT)GBGizmoTypeGet(hWnd, wParam);
            break;

        case GBM_GIZMODATASET:
            lRet=(LRESULT)GBGizmoDataSet(hWnd, wParam
                , (DWORD)lParam);
            break;

        case GBM_GIZMODATAGET:
            lRet=(LRESULT)GBGizmoDataGet(hWnd, wParam);
            break;

        case GBM_GIZMONOTIFYSET:
            lRet=(LRESULT)GBGizmoNotifySet(hWnd, wParam
                , (BOOL)LOWORD(lParam));
            break;

        case GBM_GIZMONOTIFYGET:
            lRet=(LRESULT)GBGizmoNotifyGet(hWnd, wParam);
            break;

        case GBM_GIZMOTEXTGETW:
            pGTW=(LPGBGETTEXTW)lParam;
            lRet=(LRESULT)GBGizmoTextGetW(hWnd, wParam, pGTW->psz
                , pGTW->cch);
            break;
        case GBM_GIZMOTEXTGETA:
            pGTA=(LPGBGETTEXTA)lParam;
            lRet=(LRESULT)GBGizmoTextGetA(hWnd, wParam, pGTA->psz
                , pGTA->cch);
            break;

        case GBM_GIZMOTEXTSETW:
            GBGizmoTextSetW(hWnd, wParam, (LPWSTR)lParam);
            break;

        case GBM_GIZMOTEXTSETA:
            GBGizmoTextSetA(hWnd, wParam, (LPSTR)lParam);
            break;

        case GBM_GIZMOINTGET:
            pGI=(LPGBGETINT)lParam;
            lRet=(LRESULT)GBGizmoIntGet(hWnd, wParam, &pGI->fSuccess
                , pGI->fSigned);
            break;

        case GBM_GIZMOINTSET:
            pSI=(LPGBSETINT)lParam;
            GBGizmoIntSet(hWnd, wParam, pSI->uValue, pSI->fSigned);
            break;

        default:
            break;
        }

    return lRet;
    }

/*
 * PGizmoFromHwndID
 *
 * Purpose:
 *  Retrieves the pGizmo for the given GizmoBar and the gizmo ID.
 *
 * Parameters:
 *  hWnd            HWND of a GizmoBar.
 *  uID             UINT gizmo identifier.
 *
 * Return Value:
 *  PGIZMO          NULL if the gizmo does not exist or hWnd is
 *                  invalid.  Non-NULL PGIZMO otherwise.
 */

PGIZMO PGizmoFromHwndID(HWND hWnd, UINT uID)
    {
    PGIZMOBAR     pGB;

    if (!IsWindow(hWnd))
        return FALSE;

    pGB=(PGIZMOBAR)GetWindowLong(hWnd, GBWL_STRUCTURE);

    if (NULL==pGB)
        return FALSE;

    return GizmoPFind(&pGB->pGizmos, uID);
    }

/*
 * GBHwndAssociateSet
 *
 * Purpose:
 *  Changes the associate window of a GizmoBar.
 *
 * Parameters:
 *  hWnd            HWND of the control window.
 *
 * Set Parameters:
 *  hWndAssociate   HWND of new associate.
 *
 * Return Value:
 *  HWND            Handle of previous associate.
 */

HWND WINAPI GBHwndAssociateSet(HWND hWnd, HWND hWndNew)
    {
    HWND        hWndOld=NULL;
    PGIZMOBAR   pGB;

    pGB=(PGIZMOBAR)GetWindowLong(hWnd, GBWL_STRUCTURE);

    if (NULL!=pGB)
        {
        hWndOld=pGB->hWndAssociate;
        pGB->hWndAssociate=hWndNew;

        if (NULL!=hWndOld)
            SendCommand(hWndOld, pGB->uID, GBN_ASSOCIATELOSS, hWnd);

        if (NULL!=hWndNew)
            SendCommand(hWndNew, pGB->uID, GBN_ASSOCIATEGAIN, hWnd);
        }

    return hWndOld;
    }

/*
 * GBHwndAssociateGet
 *
 * Purpose:
 *  Retrieves the associate window of a GizmoBar
 *
 * Parameters:
 *  hWnd            HWND of the control window.
 *
 * Set Parameters:
 *  hWndAssociate   HWND of new associate.
 *
 * Return Value:
 *  HWND            Handle of current associate.
 */

HWND WINAPI GBHwndAssociateGet(HWND hWnd)
    {
    HWND        hWndOld=NULL;
    PGIZMOBAR   pGB;

    pGB=(PGIZMOBAR)GetWindowLong(hWnd, GBWL_STRUCTURE);

    if (NULL!=pGB)
        hWndOld=pGB->hWndAssociate;

    return hWndOld;
    }

/*
 * GBGizmoAdd
 *
 * Purpose:
 *  Creates a new gizmo on the GizmoBar.  Subsequent operations
 *  should be done using the identifier, uID, for this gizmo.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  iType           UINT type of the gizmo to create.
 *  iGizmo          UINT position (zero-based) at which to place the
 *                  gizmo.
 *  uID             UINT identifier for WM_COMMAND from this gizmo.
 *  dx, dy          UINT dimensions of the gizmo.
 *  pszText         LPTSTR initial text for edit, list, combo, and
 *                  text gizmos.
 *  hBitmap         HBITMAP for gizmos of the button types (COMMAND
 *                  or ATTRIBUTE) specifies a source bitmap from
 *                  which the button image is taken.
 *  iImage          UINT index into hBitmap for the image for this
 *                  button.
 *  uState          UINT initial state of the gizmo.
 *
 * Return Value:
 *  BOOL            TRUE if creation succeeded, FALSE otherwise.
 */

BOOL WINAPI GBGizmoAddW(HWND hWnd, UINT iType, UINT iGizmo, UINT uID
    , UINT dx, UINT dy, LPWSTR pszText, HBITMAP hBmp, UINT iImage
    , UINT uState)
    {
    BOOL        fSuccess;
    PGIZMOBAR   pGB;
    PGIZMO      pGizmo;

    if (!IsWindow(hWnd))
        return FALSE;

    pGB=(PGIZMOBAR)GetWindowLong(hWnd, GBWL_STRUCTURE);

    if (NULL==pGB)
        return FALSE;

    /*
     * This automatically creates the windows, allocates structures,
     * includes the gizmo in pGB->pGizmos, and so forth.
     */
    pGizmo=GizmoPAllocate(&fSuccess, &pGB->pGizmos, hWnd, iType
        , iGizmo, uID, dx, dy, pszText, hBmp, iImage, uState);

    if (fSuccess)
        {
        if (NULL!=pGB->hWndAssociate)
            {
            SendCommand(pGB->hWndAssociate,GBN_GIZMOADDED, pGB->uID
                , hWnd);
            }

        InvalidateRect(hWnd, NULL, TRUE);
        UpdateWindow(hWnd);
        }
    else
        GizmoPFree(&pGB->pGizmos, pGizmo);

    return fSuccess;
    }

BOOL WINAPI GBGizmoAddA(HWND hWnd, UINT iType, UINT iGizmo, UINT uID
    , UINT dx, UINT dy, LPSTR pszText, HBITMAP hBmp, UINT iImage
    , UINT uState)
{
    LPWSTR  pszTemp;
    BOOL    retVal;

    if(pszText != NULL)
    {
        pszTemp = malloc((1+strlen(pszText))*sizeof(WCHAR));

        if (pszTemp)
        {
            MultiByteToWideChar(CP_ACP, 0, pszText, -1, pszTemp, strlen(pszText)+1);
            retVal=GBGizmoAddW(hWnd, iType, iGizmo, uID, dx, dy, pszTemp, hBmp, iImage, uState);

            free(pszTemp);
            return retVal;
        }

    }
    else
        return GBGizmoAddW(hWnd, iType, iGizmo, uID, dx, dy, NULL, hBmp, iImage, uState);
}

/*
 * GBGizmoRemove
 *
 * Purpose:
 *  Removes an existing gizmo from the GizmoBar.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifier for this gizmo.
 *
 * Return Value:
 *  BOOL            TRUE if deletion succeeded, FALSE otherwise.
 */

BOOL WINAPI GBGizmoRemove(HWND hWnd, UINT uID)
    {
    PGIZMOBAR   pGB;
    PGIZMO      pGizmo;

    if (!IsWindow(hWnd))
        return FALSE;

    pGB=(PGIZMOBAR)GetWindowLong(hWnd, GBWL_STRUCTURE);

    if (NULL==pGB)
        return FALSE;

    pGizmo=GizmoPFind(&pGB->pGizmos, uID);

    if (NULL==pGizmo)
        return FALSE;

    GizmoPFree(&pGB->pGizmos, pGizmo);

    if (NULL!=pGB->hWndAssociate)
        {
        SendCommand(pGB->hWndAssociate, GBN_GIZMOREMOVED, pGB->uID
            , hWnd);
        }

    InvalidateRect(hWnd, NULL, TRUE);
    UpdateWindow(hWnd);
    return TRUE;
    }

/*
 * GBGizmoSendMessage
 *
 * Purpose:
 *  Implements the equivalent of SendMessage to a gizmo in the
 *  GizmoBar.  Separators, command buttons, and attribute buttons
 *  do not accept messages.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifier of the gizmo to affect.
 *  iMsg            UINT message to send.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value from the message.  0L if the
 *                  gizmo does not accept messages.
 */

LRESULT WINAPI GBGizmoSendMessage(HWND hWnd, UINT uID, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PGIZMO      pGizmo;
    LONG        lRet=0L;

    pGizmo=PGizmoFromHwndID(hWnd, uID);

    if (NULL!=pGizmo && NULL!=pGizmo->hWnd)
        lRet=SendMessage(pGizmo->hWnd, iMsg, wParam, lParam);

    return lRet;
    }

/*
 * GBGizmoShow
 *
 * Purpose:
 *  Shows or hides a control, adjusting the positions of all others
 *  to make room for or reuse the space for this control.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifier of the gizmo to affect.
 *  fShow           BOOL TRUE to show the gizmo, FALSE to hide it.
 *
 * Return Value:
 *  BOOL            TRUE if the function was successful, FALSE
 *                  otherwise.
 */

BOOL WINAPI GBGizmoShow(HWND hWnd, UINT uID, BOOL fShow)
    {
    BOOL        fRet=FALSE;
    PGIZMO      pGizmo;

    pGizmo=PGizmoFromHwndID(hWnd, uID);

    if (NULL!=pGizmo)
        {
        if (fShow && pGizmo->fHidden)
            {
            if (NULL!=pGizmo->hWnd)
                ShowWindow(pGizmo->hWnd, SW_SHOWNORMAL);

            GizmosExpand(pGizmo);
            }

        if (!fShow && !pGizmo->fHidden)
            {
            if (NULL!=pGizmo->hWnd)
                ShowWindow(pGizmo->hWnd, SW_HIDE);

            GizmosCompact(pGizmo);
            }

        //This will be right even if we didn't change anything.
        pGizmo->fHidden=!fShow;
        }

    InvalidateRect(hWnd, NULL, TRUE);
    UpdateWindow(hWnd);
    return fRet;
    }

/*
 * GBGizmoEnable
 *
 * Purpose:
 *  Enables or disables a control on the GizmoBar.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifier of the gizmo to affect.
 *  fEnable         BOOL TRUE to enable the gizmo, FALSE otherwise.
 *
 * Return Value:
 *  BOOL            TRUE if the gizmo was previously disabled, FALSE
 *                  otherwise.
 */

BOOL WINAPI GBGizmoEnable(HWND hWnd, UINT uID, BOOL fEnable)
    {
    PGIZMO      pGizmo;
    BOOL        fRet=FALSE;

    pGizmo=PGizmoFromHwndID(hWnd, uID);

    if (NULL==pGizmo)
        return FALSE;

    fRet=(BOOL)(BUTTONGROUP_DISABLED & pGizmo->uState);

    //Use windows to enable or disable window gizmos
    if (NULL!=pGizmo->hWnd)
        EnableWindow(pGizmo->hWnd, fEnable);
    else
        {
        /*
         * If we're not down, command and attribute buttons act
         * the same.
         */
        if (!(BUTTONGROUP_DOWN & pGizmo->uState))
            {
            GizmoPStateSet(hWnd, pGizmo, fEnable
                ? COMMANDBUTTON_UP : COMMANDBUTTON_DISABLED);
            }
        else
            {
            /*
             * Attribute buttons are a little more sensitive with
             * DOWNDISABLED
             */
            GizmoPStateSet(hWnd, pGizmo, fEnable
                ? ATTRIBUTEBUTTON_DOWN
                : ATTRIBUTEBUTTON_DOWNDISABLED);
            }
        }

    return fRet;
    }

/*
 * GBGizmoCheck
 *
 * Purpose:
 *  Checks or unchecks an attribute button in the GizmoBar.  If the
 *  gizmo is part of a group of mutually exclusive attributes, then
 *  other gizmos are unchecked when this one is checked.  If this is
 *  the only one checked in these circumstances, this function is a
 *  NOP.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifier of the gizmo to affect.
 *  fCheck          BOOL TRUE to check this gizmo, FALSE to uncheck.
 *
 * Return Value:
 *  BOOL            TRUE if the change took place.  FALSE otherwise.
 */

BOOL WINAPI GBGizmoCheck(HWND hWnd, UINT uID, BOOL fCheck)
    {
    PGIZMOBAR   pGB;
    PGIZMO      pGizmo;

    if (!IsWindow(hWnd))
        return FALSE;

    pGB=(PGIZMOBAR)GetWindowLong(hWnd, GBWL_STRUCTURE);

    if (NULL==pGB)
        return FALSE;

    pGizmo=GizmoPFind(&pGB->pGizmos, uID);

    if (NULL!=pGizmo)
        GizmoPCheck(hWnd, pGizmo, fCheck);

    return TRUE;
    }

/*
 * GBGizmoFocusSet
 *
 * Purpose:
 *  Sets the focus to a partuclar gizmo in the gizmo if that gizmo
 *  can accept the focus.  Separators, attribute buttons, text,
 *  and command buttons cannot have the focus.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifier of the gizmo to affect.
 *
 * Return Value:
 *  BOOL            TRUE if the focus was set.  FALSE otherwise,
 *                  such as when uID identifies a control that cannot
 *                  have focus.
 */

UINT WINAPI GBGizmoFocusSet(HWND hWnd, UINT uID)
    {
    PGIZMO      pGizmo;
    BOOL        fRet=FALSE;

    pGizmo=PGizmoFromHwndID(hWnd, uID);

    if (NULL!=pGizmo && NULL!=pGizmo->hWnd)
        {
        fRet=TRUE;
        SetFocus(pGizmo->hWnd);
        }

    return fRet;
    }

/*
 * GBGizmoExist
 *
 * Purpose:
 *  Determines if a gizmo of a given identifier exists in the
 *  GizmoBar.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifier to verify.
 *
 * Return Value:
 *  BOOL            TRUE if the gizmo exists, FALSE otherwise.
 */

BOOL WINAPI GBGizmoExist(HWND hWnd, UINT uID)
    {
    return (NULL!=PGizmoFromHwndID(hWnd, uID));
    }

/*
 * GBGizmoTypeGet
 *
 * Purpose:
 *  Returns the type of the gizmo specified by the given identifer.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifier to find.
 *
 * Return Value:
 *  int             A GIZMOTYPE_* value if the function is
 *                  successful, otherwise -1.
 */

int WINAPI GBGizmoTypeGet(HWND hWnd, UINT uID)
    {
    int         iRet=-1;
    PGIZMO      pGizmo;

    pGizmo=PGizmoFromHwndID(hWnd, uID);

    if (NULL!=pGizmo)
        iRet=pGizmo->iType;

    return iRet;
    }

/*
 * GBGizmoDataSet
 * GBGizmoDataGet
 *
 * Purpose:
 *  Sets or retrieves an extra DWORD value associated with the given
 *  gizmo.  Applications can store any information here they please.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifier of the gizmo.
 *  dwData          (Set only) DWORD data to store with the gizmo.
 *
 * Return Value:
 *  DWORD           Set:  Previous value
 *                  Get:  Current value
 */

DWORD WINAPI GBGizmoDataSet(HWND hWnd, UINT uID, DWORD dwData)
    {
    PGIZMO      pGizmo;
    DWORD       dw=0L;

    pGizmo=PGizmoFromHwndID(hWnd, uID);

    if (NULL!=pGizmo)
        {
        dw=pGizmo->dwData;
        pGizmo->dwData=dwData;
        }

    return dw;
    }

DWORD WINAPI GBGizmoDataGet(HWND hWnd, UINT uID)
    {
    PGIZMO      pGizmo;
    DWORD       dw=0L;

    pGizmo=PGizmoFromHwndID(hWnd, uID);

    if (NULL!=pGizmo)
        dw=pGizmo->dwData;

    return dw;
    }

/*
 * GBGizmoNotifySet
 * GBGizmoNotifyGet
 *
 * Purpose:
 *  Sets or retrieves the notify status of a gizmo.  If notify is
 *  FALSE, the no WM_COMMAND messages are sent from the GizmoBar to
 *  the parent window when this gizmo is used.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifier of the gizmo.
 *  fNotify         (Set only) BOOL new notify status to set.
 *
 * Return Value:
 *  BOOL            Set:  Previous value of the notify flag.
 *                  Get:  Current value of the notify flag.
 */

BOOL WINAPI GBGizmoNotifySet(HWND hWnd, UINT uID, BOOL fNotify)
    {
    PGIZMO      pGizmo;
    BOOL        fRet=FALSE;

    pGizmo=PGizmoFromHwndID(hWnd, uID);

    if (NULL!=pGizmo)
        {
        fRet=pGizmo->fNotify;
        pGizmo->fNotify=fNotify;
        }

    return fRet;
    }

BOOL WINAPI GBGizmoNotifyGet(HWND hWnd, UINT uID)
    {
    PGIZMO      pGizmo;
    BOOL        fRet=FALSE;

    pGizmo=PGizmoFromHwndID(hWnd, uID);

    if (NULL!=pGizmo)
        fRet=pGizmo->fNotify;

    return fRet;
    }

/*
 * GBGizmoTextSet
 * GBGizmoTextGet
 *
 * Purpose:
 *  Retrieves or sets text in a GizmoBar gizmo.  Separators, command
 *  buttons, and attribute buttons are not affected by this call.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifying the gizmo.
 *  psz             LPTSTR (Set) providing the text to show in the
 *                  window or (Get) pointing to a buffer to receive
 *                  the text.
 *  cch             (Get only) UINT maximum number of chars to copy
 *                  to psz.
 *
 * Return Value:
 *  int             Number of characters copied to psz.
 */

void WINAPI GBGizmoTextSetW(HWND hWnd, UINT uID, LPWSTR psz)
    {
    //This fails on non-windowed gizmos anyway, so we don't check.
    SetDlgItemTextW(hWnd, uID, psz);
    return;
    }

void WINAPI GBGizmoTextSetA(HWND hWnd, UINT uID, LPSTR psz)
    {
    //This fails on non-windowed gizmos anyway, so we don't check.
    SetDlgItemTextA(hWnd, uID, psz);
    return;
    }

int WINAPI GBGizmoTextGetW(HWND hWnd, UINT uID, LPWSTR psz, UINT cch)
    {
    //This fails on non-windowed gizmos anyway, so we don't check.
    return GetDlgItemTextW(hWnd, uID, psz, cch);
    }

int WINAPI GBGizmoTextGetA(HWND hWnd, UINT uID, LPSTR psz, UINT cch)
    {
    //This fails on non-windowed gizmos anyway, so we don't check.
    return GetDlgItemTextA(hWnd, uID, psz, cch);
    }

/*
 * GBGizmoIntSet
 * GBGizmoIntGet
 *
 * Purpose:
 *  Retrieves or sets an integer in a GizmoBar gizmo.  Separators,
 *  command buttons, and attribute buttons are not affected by this
 *  call.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifying the gizmo.
 *
 *  (Set only)
 *  u               UINT value to set in the gizmo.
 *  fSigned         BOOL TRUE to indicate if the value is signed.
 *
 *  (Get only)
 *  pfTrans         BOOL FAR * in which the success of the function
 *                  is returned.
 *  fSigned         BOOL TRUE to indicate if the value is signed.
 *
 * Return Value:
 *  (Set): None
 *  (Get): UINT     Integer translation of the gizmo's text.
 */

void WINAPI GBGizmoIntSet(HWND hWnd, UINT uID, UINT u, BOOL fSigned)
    {
    //This fails on non-windowed gizmos anyway, so we don't check.
    SetDlgItemInt(hWnd, uID, u, fSigned);
    return;
    }

UINT WINAPI GBGizmoIntGet(HWND hWnd, UINT uID, BOOL FAR *pfTrans
    , BOOL fSigned)
    {
    //This fails on non-windowed gizmos anyway, so we don't check.
    return GetDlgItemInt(hWnd, uID, pfTrans, fSigned);
    }


GIZMO.C

/*
 * GIZMO.C
 * GizmoBar Version 1.01
 *
 * Allocate, free, find, and enumerate functions for the GIZMO
 * structure and a generic subclass procedure to handle tabbing
 * between gizmos.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

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

/*
 * In order to control tabbing in the gizmos, we need to subclass
 * real pushbuttons, edit controls, listboxes, and comboboxes.  So
 * we keep an array of the four original procs for such controls.
 */
WNDPROC     pfnOrg[CSUBGIZMOS]={NULL, NULL, NULL, NULL};

CHAR szStatic[]="static";
CHAR szEdit[]="edit";
CHAR szCombobox[]="combobox";
CHAR szListbox[]="listbox";
CHAR szButton[]="button";

//Here so PAINT.C can get at it.
TOOLDISPLAYDATA tdd;

/*
 * GizmoPAllocate
 *
 * Purpose:
 *  Allocates and initializes a GIZMO data structure.
 *
 * Parameters:
 *  pfSuccess       int * flag indicating success of failure.
 *  ppFirst         PPGIZMO providing the first gizmo in this list.
 *  hWndParent      HWND of the parent of this gizmo.  Can be NULL
 *                  for iType==GIZMOTYPE_BUTTON* or
 *                  GIZMOTYPE_SEPARATOR.
 *  iType           UINT gizmo control type.
 *  iGizmo          UINT index of this gizmo in the GizmoBar.
 *  uID             UINT identifier to send with WM_COMMAND for this
 *                  control.
 *  dx, dy          UINT width and height of the gizmo.
 *  pszText         LPTSTR to the text for edits, listboxes, combobox,
 *                  and text.
 *  dwStyle         DWORD style for edits, lists, and combos, and
 *                  texts.
 *  hBmp            HBITMAP for button gizmos, is applicable.
 *  iImage          UINT index into hBmp for the button image, if
 *                  applicable.
 *  uState          UINT initial state of the control.
 *
 * Return Value:
 *  PGIZMO          If NULL returned then GizmoPAllocate could not
 *                  allocate memory.  If a non-NULL pointer is
 *                  returned with *pfSuccess, then call GizmoPFree
 *                  immediately.  If you get a non-NULL pointer and
 *                  *pfSuccess==TRUE then the function succeeded.
 */

PGIZMO GizmoPAllocate(int *pfSuccess, PPGIZMO ppFirst
    , HWND hWndParent, UINT iType, UINT iGizmo, UINT uID, UINT dx
    , UINT dy, LPWSTR pszText, HBITMAP hBmp, UINT iImage, UINT uState)
    {
    PGIZMO          pGizmo;
    PGIZMO          pCur, pPrev;
    LPSTR           pszClass;
    HINSTANCE       hInst;
    UINT            i;
    DWORD           dwStyle;
    HWND            hWndE;

    if (NULL==pfSuccess)
        return NULL;

    //Make sure we know of this gizmo type.
    if (GIZMOTYPE_MIN > iType || GIZMOTYPE_MAX < iType)
        return NULL;

    *pfSuccess=FALSE;

    //Allocate the structure
    pGizmo=(PGIZMO)(TCHAR *)LocalAlloc(LPTR, CBGIZMO);

    if (NULL==pGizmo)
        return NULL;

    //Store the necessary information for this gizmo.
    pGizmo->iType   =iType;
    pGizmo->uID     =uID;
    pGizmo->hBmp    =hBmp;
    pGizmo->iBmp    =iImage;
    pGizmo->uState  =uState;
    pGizmo->fNotify =TRUE;

    /*
     * Insert this structure into our gizmo list.  Each time we scan
     * we increment the index counter (starting at zero) comparing it
     * to the desired index of insertion.  We then know exactly where
     * to insert this new gizmo.  Note that we insert the new gizmo
     * in the list appropriately for the given owner, so enumerations
     * will come out ordered in the same way for that owner.
     */

    i=0;
    pCur=*ppFirst;
    pPrev=NULL;

    while (NULL!=pCur && i++ < iGizmo)
        {
        pPrev=pCur;
        pCur =pCur->pNext;
        }

    //Point to our neighbors
    pGizmo->pPrev=pPrev;
    pGizmo->pNext=pCur;

    //Point out neighbors to us.
    if (NULL==pPrev)
        *ppFirst=pGizmo;
    else
        pPrev->pNext=pGizmo;

    if (NULL!=pCur)
        pCur->pPrev=pGizmo;

    //Our x-coordinate is the x of the previous gizmo plus its width.
    if (NULL!=pPrev)
        pGizmo->x=pGizmo->pPrev->x+pGizmo->pPrev->dx;
    else
        pGizmo->x=4;    //First gizmo is at x=4

    //If we're a separator or image button, force standards on dx.
    UIToolConfigureForDisplay(&tdd);
    pGizmo->cxImage=tdd.cxImage;
    pGizmo->cyImage=tdd.cyImage;

    if ((GIZMOTYPE_DRAWN & iType) && NULL==hBmp)
        dx=tdd.cxButton;

    if (GIZMOTYPE_SEPARATOR==iType)
        dx=6;

    /*
     * Now create windows for edits, texts, lists, and comboboxes.
     * First calculate the most often defaults used in the switch.
     */
    pGizmo->dx=dx+6;
    pGizmo->dy=min(dy, tdd.cyButton);
    pGizmo->y=2;
    pszClass=NULL;

    //If this is new gizmo is a window, create it.
    switch (iType)
            {
            case GIZMOTYPE_TEXT:
                pGizmo->dx=dx;

                //Center vertically.
                pGizmo->y=(tdd.cyBar-1-pGizmo->dy) >> 1;
                pszClass=szStatic;
                dwStyle=SS_LEFT;
                break;

            case GIZMOTYPE_EDIT:
                //Center vertically.
                pGizmo->y=(tdd.cyBar-1-pGizmo->dy) >> 1;
                pszClass=szEdit;
                dwStyle=ES_LEFT | WS_BORDER | WS_TABSTOP;
                break;

            case GIZMOTYPE_LISTBOX:
                pGizmo->dy=dy;
                pszClass=szCombobox;
                dwStyle=CBS_DROPDOWNLIST | WS_TABSTOP;
                break;

            case GIZMOTYPE_COMBOBOX:
                pGizmo->dy=dy;
                pszClass=szCombobox;
                dwStyle=CBS_DROPDOWN | WS_TABSTOP;
                break;

            case GIZMOTYPE_BUTTONNORMAL:
                pGizmo->dy=dy;
                pszClass=szButton;
                dwStyle=BS_PUSHBUTTON | WS_TABSTOP;
                break;

            case GIZMOTYPE_SEPARATOR:
                pGizmo->dx=dx;
                pGizmo->y=3;
                break;

            case GIZMOTYPE_BUTTONATTRIBUTEIN:
            case GIZMOTYPE_BUTTONATTRIBUTEEX:
            case GIZMOTYPE_BUTTONCOMMAND:
                pGizmo->dx=dx;
                pGizmo->y=3;
                break;
            }

    //If we matched a classname, create a window.
    if (GIZMOTYPE_WINDOWS & iType)
        {
        if (!IsWindow(hWndParent))
            return pGizmo;

        hInst=GETWINDOWINSTANCE(hWndParent);    //Macro in book1632.h

        // CreateWindowW fails on Windows 95.  Must use CreateWindowA instead.
        pGizmo->hWnd = NULL;
        {
            char * sz = NULL;
            int fSuccess = TRUE;
            if (pszText)
            {
                int iLen;
                iLen = wcslen(pszText);
                sz = malloc(sizeof(char) * (iLen + 1));
                if (!sz)
                {
                    fSuccess = FALSE;
                }
                else
                {
                    wcstombs(sz, pszText, iLen);
                }
            }
            if (fSuccess)
            {
                pGizmo->hWnd=CreateWindowA(pszClass, sz
                    , dwStyle | WS_CHILD | WS_VISIBLE, pGizmo->x, pGizmo->y
                    , dx, pGizmo->dy, hWndParent, (HMENU)uID, hInst, NULL);
                if (sz)
                {
                    free(sz);
                }
            }
        }

        if (NULL==pGizmo->hWnd)
            return pGizmo;

        /*
         * Subclass comboboxes, listboxes, edits, and windowed
         * buttons.  We use iType to index the original proc array
         * so we can use a single subclass procedure for all
         * controls.  If you mess with the gizmo type definitions,
         * this is going to break.
         */

        if (GIZMOTYPE_WINDOWS & iType && GIZMOTYPE_TEXT!=iType)
            {
            //Give the window its type.
            BITPOSITION(iType, i);
            SetProp(pGizmo->hWnd, SZTYPEPROP, (HANDLE)i);

            if (NULL==pfnOrg[i])
                {
                pfnOrg[i]=(WNDPROC)GetWindowLong(pGizmo->hWnd
                    , GWL_WNDPROC);
                }

            SetWindowLong(pGizmo->hWnd, GWL_WNDPROC
                , (LONG)GenericSubProc);

            //If we're a combobox, subclass edit control
            if (GIZMOTYPE_COMBOBOX==iType)
                {
                hWndE=GetDlgItem(pGizmo->hWnd, ID_COMBOEDIT);
                SetProp(hWndE, SZTYPEPROP, (HANDLE)-1);

                if (NULL==pfnOrg[0])
                    {
                    pfnOrg[0]=(WNDPROC)GetWindowLong(pGizmo->hWnd
                        , GWL_WNDPROC);
                    }

                SetWindowLong(hWndE, GWL_WNDPROC
                    , (LONG)GenericSubProc);
                }
            }
        }

    //Finally, move all our neighbors to the right to accomodate us.
    GizmosExpand(pGizmo);

    *pfSuccess=TRUE;
    return pGizmo;
    }

/*
 * GizmoPFree
 *
 * Purpose:
 *  Reverses all initialization done by GizmoPAllocate, cleaning up
 *  any allocations including the application structure itself.
 *
 * Parameters:
 *  ppFirst         PPGIZMO providing the first gizmo in this list.
 *  pGizmo          PGIZMO to the structure
 *
 * Return Value:
 *  PGIZMO          NULL if successful, pGizmo if not, meaning we
 *                  couldn't free something.
 */

PGIZMO GizmoPFree(PPGIZMO ppFirst, PGIZMO pGizmo)
    {
    int     i;

    if (NULL==pGizmo)
        return NULL;

    //Move other gizmos to fill in this gap.
    GizmosCompact(pGizmo);

    //Unsubclass
    if (GIZMOTYPE_WINDOWS & pGizmo->iType
        && GIZMOTYPE_TEXT!=pGizmo->iType)
        {
        i=(int)GetProp(pGizmo->hWnd, SZTYPEPROP);
        RemoveProp(pGizmo->hWnd, SZTYPEPROP);

        if (GIZMOTYPE_COMBOBOX==pGizmo->iType)
            {
            HWND        hWndE;

            hWndE=GetDlgItem(pGizmo->hWnd, ID_COMBOEDIT);
            RemoveProp(hWndE, SZTYPEPROP);
            }

        SetWindowLong(pGizmo->hWnd, GWL_WNDPROC, (LONG)pfnOrg[i]);
        }

    //If this was a window gizmo, destroy the window.
    if (NULL!=pGizmo->hWnd && IsWindow(pGizmo->hWnd))
        DestroyWindow(pGizmo->hWnd);

    //Unlink ourselves.
    if (NULL!=pGizmo->pNext)
        pGizmo->pNext->pPrev=pGizmo->pPrev;

    if (NULL!=pGizmo->pPrev)
        pGizmo->pPrev->pNext=pGizmo->pNext;
    else
        *ppFirst=pGizmo->pNext;

    return (PGIZMO)LocalFree((HLOCAL)(UINT)(LONG)pGizmo);
    }

/*
 * GizmosExpand
 *
 * Purpose:
 *  Given a starting gizmo and a width, moves it and all gizmos to
 *  its right to the right by the width to make space for showing
 *  or creating a new gizmo.
 *
 * Parameters:
 *  pGizmo          PGIZMO specifying the gizmo that was inserted.
 *
 * Return Value:
 *  None
 */

void GizmosExpand(PGIZMO pGizmo)
    {
    int         cx;

    cx=(int)pGizmo->dx;

    /*
     * If we and the next control are buttons, use our width-1 to
     * expand so we overlap borders with our neighboring button.
     */

    if (NULL!=pGizmo->pNext)
        {
        if ((GIZMOTYPE_BUTTONS & pGizmo->pNext->iType)
            && (GIZMOTYPE_BUTTONS & pGizmo->iType))
            cx-=1;
        }

    //Walk the gizmo list moving them right by our width.
    pGizmo=pGizmo->pNext;

    while (NULL!=pGizmo)
        {
        pGizmo->x+=cx;

        //hWnd is NULL for buttons and separators.
        if (NULL!=pGizmo->hWnd)
            {
            SetWindowPos(pGizmo->hWnd, NULL, pGizmo->x, pGizmo->y
                , 0, 0, SWP_NOZORDER | SWP_NOSIZE);
            }

        pGizmo=pGizmo->pNext;
        }

    return;
    }

/*
 * GizmosCompact
 *
 * Purpose:
 *  Given a gizmo, moves all other gizmos to the right of it to the
 *  left by its width on the GizmoBar.  Used when removing or hiding
 *  the gizmo.
 *
 * Parameters:
 *  pGizmo          PGIZMO that is going away, visibly or physically.
 *
 * Return Value:
 *  None
 */

void GizmosCompact(PGIZMO pGizmo)
    {
    UINT        cx;
    PGIZMO      pCur;

    //Move all the gizmos beyond us back by our width.
    if (NULL!=pGizmo->pNext)
        {
        cx=pGizmo->pNext->x - pGizmo->x;
        pCur=pGizmo->pNext;

        while (NULL!=pCur)
            {
            pCur->x-=cx;

            if (NULL!=pCur->hWnd)
                {
                SetWindowPos(pCur->hWnd, NULL, pCur->x, pCur->y
                             , 0, 0, SWP_NOZORDER | SWP_NOSIZE);
                }

            pCur=pCur->pNext;
            }
        }

    return;
    }

/*
 * GizmoPFind
 *
 * Purpose:
 *  Given a GIZMO identifier, locates and returns a pointer to the
 *  structure for that position.
 *
 * Parameters:
 *  ppFirst         PPGIZMO providing the first gizmo in this list.
 *  uID             UINT identifier to find.
 *
 * Return Value:
 *  PGIZMO          A pointer to a GIZMO structure allocated through
 *                  GizmoPAllocate, NULL if iGizmo is out of range.
 */

PGIZMO GizmoPFind(PPGIZMO ppFirst, UINT uID)
    {
    PGIZMO       pGizmo;

    pGizmo=*ppFirst;

    /*
     * Yep, linear search, but a better search algorithm won't
     * improve things appreciably.  The better thing to optimize
     * is what the caller passes as ppFirst.
     */
    while (NULL!=pGizmo && uID!=pGizmo->uID)
        pGizmo=pGizmo->pNext;

    return pGizmo;
    }

/*
 * GizmoFEnum
 *
 * Purpose:
 *  Enumerates the list of GIZMO structures, passing each one to
 *  an application-defined callback.
 *
 * Parameters:
 *  ppFirst         PPGIZMO providing the first gizmo in this list.
 *  pfnEnum         PFNGIZMOENUM to call for each enumerated
 *                  structure.
 *  dw              DWORD extra data to pass to the enumeration
 *                  function.
 *
 * Return Value:
 *  PGIZMO          NULL if the enumeration completed.  Otherwise a
 *                  pointer to the gizmo that enumeration stopped on.
 */

PGIZMO GizmoPEnum(PPGIZMO ppFirst, PFNGIZMOENUM pfnEnum, DWORD dw)
    {
    PGIZMO  pGizmo;
    UINT    i=0;

    pGizmo=*ppFirst;

    while (NULL!=pGizmo)
        {
        if (!(*pfnEnum)(pGizmo, i++, dw))
            break;

        pGizmo=pGizmo->pNext;
        }

    return pGizmo;
    }

/*
 * GizmoPStateSet
 *
 * Purpose:
 *  State maniuplation functions.  Set and Clear also invalidate
 *  this gizmo's rectangle on the given window and forces a repaint.
 *
 * Parameters:
 *  hWnd            HWND of the window to repaint.
 *  pGizmo          PGIZMO affected.
 *  dwNew           DWORD new state flags.
 *
 * Return Value:
 *  UINT            Previous state.
 */

UINT  GizmoPStateSet(HWND hWnd, PGIZMO pGizmo, UINT uNew)
    {
    UINT        uRet;
    RECT        rc;

    if (GIZMOTYPE_SEPARATOR==pGizmo->iType)
        return pGizmo->uState;

    //Preserve the color conversion flags across this state change.
    uRet=pGizmo->uState;
    pGizmo->uState=(uNew & 0x00FF) | (uRet & 0xFF00);

    //Adjust the rectangle by  one to avoid repainting  borders.
    SetRect(&rc, pGizmo->x+1, pGizmo->y+1, pGizmo->x+pGizmo->dx-1
        , pGizmo->y+pGizmo->dy-1);
    InvalidateRect(hWnd, &rc, FALSE);
    UpdateWindow(hWnd);

    return uRet;
    }

/*
 * GizmoPCheck
 *
 * Purpose:
 *  Handles checking a single button in a group of attribute buttons.
 *  If the gizmo belongs to a group of mutually exclusive buttons
 *  then the others surrounding it are unchecked appropriately.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  pGizmo          PGIZMO of the gizmo affected.
 *  fCheck          BOOL TRUE to check the button, FALSE to uncheck.
 *
 * Return Value:
 *  BOOL            TRUE if the gizmo was previously checked, FALSE
 *                  otherwise.
 */

BOOL GizmoPCheck(HWND hWnd, PGIZMO pGizmo, BOOL fCheck)
    {
    BOOL        fPrevCheck;
    PGIZMO      pCur;

    //Ignore command buttons.
    if (GIZMOTYPE_BUTTONCOMMAND==pGizmo->iType)
        return FALSE;

    //Get the previous state
    fPrevCheck=(BOOL)(BUTTONGROUP_DOWN & pGizmo->uState);

    //Simply set the state for inclusive attribute buttons.
    if (GIZMOTYPE_BUTTONATTRIBUTEIN==pGizmo->iType)
        {
        if (pGizmo->fDisabled)
            {
            GizmoPStateSet(hWnd, pGizmo
                , fCheck ? ATTRIBUTEBUTTON_DOWNDISABLED
                : ATTRIBUTEBUTTON_DISABLED);
            }
        else
            {
            GizmoPStateSet(hWnd, pGizmo, fCheck
                ? ATTRIBUTEBUTTON_DOWN : ATTRIBUTEBUTTON_UP);
            }
        }

    if (GIZMOTYPE_BUTTONATTRIBUTEEX==pGizmo->iType)
        {
        //We cannot uncheck an exclusive attribute
        if (!fCheck)
            return fPrevCheck;

        /*
         * For exclusive buttons we have to do more work.  First, if
         * we're already checked (incliding DOWN and MOUSEDOWN) then
         * we set DOWN and exit.  If we're not already checked, then
         * we look for the gizmo around us, backwards and forwards,
         * that is checked and uncheck him.
         */

        //Search  backwards.
        pCur=pGizmo->pPrev;

        while (NULL!=pCur)
            {
            //Stop at any non-exclusive attribute.
            if (GIZMOTYPE_BUTTONATTRIBUTEEX!=pCur->iType)
                {
                pCur=NULL;
                break;
                }

            //If it's down, set it up and we've finished.
            if (BUTTONGROUP_DOWN & pCur->uState)
                break;

            pCur=pCur->pPrev;
            }

        //If we didn't find a previous one, pCur is NULL, look ahead.
        if (NULL==pCur)
            {
            pCur=pGizmo->pNext;

            while (NULL!=pCur)
                {
                //Stop at any non-exclusive attribute.
                if (GIZMOTYPE_BUTTONATTRIBUTEEX!=pCur->iType)
                    {
                    pCur=NULL;
                    break;
                    }

                //If it's down, set it up and we've finished.
                if (BUTTONGROUP_DOWN & pCur->uState)
                    break;

                pCur=pCur->pNext;
                }
            }

        //If pCur is non-NULL, we found a neighbor, so uncheck it
        if (NULL!=pCur)
            {
            GizmoPStateSet(hWnd, pCur, (pGizmo->fDisabled)
                ? ATTRIBUTEBUTTON_DISABLED : ATTRIBUTEBUTTON_UP);
            }

        //Always set ourselves down
        GizmoPStateSet(hWnd, pGizmo, (pGizmo->fDisabled)
            ? ATTRIBUTEBUTTON_DOWNDISABLED : ATTRIBUTEBUTTON_DOWN);
        }

    return fPrevCheck;
    }

/*
 * GenericSubProc
 *
 * Purpose:
 *  Subclasses window controls in Gizmos so we can trap the tab key
 *  and tab to the next control.  We can have one shared generic
 *  subclass procedure because we save the type index for this
 *  control in the property "iType."  This allows us to look up the
 *  original procedure in the pfnOrg array.
 *
 * Parameters:
 *  Standard
 *
 * Return Value:
 *  Standard
 */

LRESULT APIENTRY GenericSubProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    LONG        lRet;
    RECT        rc;
    RECT        rcE;
    HWND        hWndE;
    HBRUSH      hBr;
    HDC         hDC;
    UINT        dx;
    UINT        iType, i;

    i=(int)GetProp(hWnd, SZTYPEPROP);
    iType=POSITIONBIT(i);

    //Special:  paint the gap in drop-down comboboxes.
    if (GIZMOTYPE_COMBOBOX==iType && WM_PAINT==iMsg)
        {
        //Do default painting.
        lRet=(*pfnOrg[i])(hWnd, iMsg, wParam, lParam);

        hWndE=GetDlgItem(hWnd, ID_COMBOEDIT);

        GetClientRect(hWnd, &rc);
        GetClientRect(hWndE, &rcE);

        //The width of the button is the scroll bar width.
        dx=GetSystemMetrics(SM_CXVSCROLL);

        //Calculate the rectangle
        rc.right -=dx;
        rc.left   =rcE.right;
        rc.bottom+=1;

        //Paint the gap
        hDC=GetDC(hWnd);   //Already did BeginPaint and EndPaint

        hBr=CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
        FillRect(hDC, &rc, hBr);
        DeleteObject(hBr);

        ReleaseDC(hWnd, hDC);
        return lRet;
        }

    //Control tabbing to the next or previous control
    if (WM_KEYDOWN==iMsg && VK_TAB==wParam)
        {
        hWndE=hWnd;

        if (-1==i)
            hWndE=GetParent(hWnd);

        hWndE=GetNextDlgTabItem(GetParent(hWndE), hWnd
            , (BOOL)(GetKeyState(VK_SHIFT)));
        SetFocus(hWndE);
        return 0L;
        }

    if (-1==i) i=0;

    //Eat tab chars in edit controls to prevent beeping.
    if (0==i && WM_CHAR==iMsg && VK_TAB==wParam)
        return 0L;

    //Do this or edit controls bomb big-time.
    return CallWindowProc(pfnOrg[i], hWnd, iMsg, wParam, lParam);
    }


GIZMO.H

/*
 * GIZMO.H
 * GizmoBar Version 1.01
 *
 * Data structure and type definitions for the GIZMO data structure.
 * Each gizmo on a gizmobar has one of these structures associated
 * with it.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _GIZMO_H_
#define _GIZMO_H_

#ifdef __cplusplus
extern "C"
    {
#endif

typedef struct tagGIZMO
    {
    struct tagGIZMO     *pPrev;
    struct tagGIZMO     *pNext;
    UINT                 iType;
    HWND                 hWnd;       //Texts, edits, lists, combos
    UINT                 uID;
    UINT                 x, y;
    UINT                 dx, dy;
    UINT                 cxImage;    //From UIToolConfigureForDisplay
    UINT                 cyImage;
    HBITMAP              hBmp;       //Buttons only.
    UINT                 iBmp;
    BOOL                 fNotify;    //Send WM_COMMANDs?
    BOOL                 fHidden;    //Independent of state
    BOOL                 fDisabled;
    UINT                 uState;
    UINT                 uStateOrg;
    DWORD                dwData;     //Application-supplied data.
    } GIZMO, * PGIZMO;

typedef PGIZMO *PPGIZMO;
#define CBGIZMO sizeof(GIZMO)

//Property name we attach to controls in a gizmo to identify type
#define SZTYPEPROP      TEXT("iType")

//Number of controls we subclass
#define CSUBGIZMOS       4

//ID of edit controls in comboboxes
#define ID_COMBOEDIT     1001

/*
 * Conversion of iType (a positioned bit) into its position.
 * The BITPOSITION macro does not need to be fast because we only
 * use it once when creating a gizmo.  POSITIONBIT does, however,
 * since we use it in subclass procedures.
 */
#define BITPOSITION(i, j)  {int k=i; for (j=0; k>>=1; j++);}
#define POSITIONBIT(i)     (1 << i)

//Control classifications.  GIZMOBAR.H must be included first.
#define GIZMOTYPE_WINDOWS   (GIZMOTYPE_TEXT | GIZMOTYPE_EDIT \
                            | GIZMOTYPE_LISTBOX              \
                            | GIZMOTYPE_COMBOBOX             \
                            | GIZMOTYPE_BUTTONNORMAL)

#define GIZMOTYPE_BUTTONS   (GIZMOTYPE_BUTTONATTRIBUTEIN     \
                            | GIZMOTYPE_BUTTONATTRIBUTEEX    \
                            | GIZMOTYPE_BUTTONCOMMAND        \
                            | GIZMOTYPE_BUTTONNORMAL)

#define GIZMOTYPE_DRAWN     (GIZMOTYPE_BUTTONATTRIBUTEIN     \
                            | GIZMOTYPE_BUTTONATTRIBUTEEX    \
                            | GIZMOTYPE_BUTTONCOMMAND)

//These must stay in sync with GIZMOBAR.H
#define GIZMOTYPE_MIN               GIZMOTYPE_EDIT
#define GIZMOTYPE_MAX               GIZMOTYPE_BUTTONCOMMAND

//Enumeration callback
typedef BOOL (CALLBACK *PFNGIZMOENUM)(PGIZMO, UINT, DWORD);

//GIZMO.C
PGIZMO   GizmoPAllocate(int *, PPGIZMO, HWND, UINT, UINT, UINT
             , UINT, UINT, LPWSTR, HBITMAP, UINT, UINT);
void     GizmosExpand(PGIZMO);
PGIZMO   GizmoPFree(PPGIZMO, PGIZMO);
void     GizmosCompact(PGIZMO);
PGIZMO   GizmoPFind(PPGIZMO, UINT);
PGIZMO   GizmoPEnum(PPGIZMO, PFNGIZMOENUM, DWORD);
UINT     GizmoPStateSet(HWND, PGIZMO, UINT);
BOOL     GizmoPCheck(HWND, PGIZMO, BOOL);

LRESULT APIENTRY GenericSubProc(HWND, UINT, WPARAM, LPARAM);

#ifdef __cplusplus
    }
#endif

#endif //_GIZMO_H_


GIZMOBAR.C

/*
 * GIZMOBAR.C
 * GizmoBar Version 1.01
 *
 * Contains the main window procedure of the GizmoBar control
 * that handles mouse logic and Windows messages.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

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

/*
 * GizmoBarWndProc
 *
 * Purpose:
 *  Window Procedure for the GizmoBar custom control.  Handles all
 *  messages like WM_PAINT just as a normal application window would.
 *  Any message not processed here should go to DefWindowProc.
 *
 * Parameters:
 *  Standard
 *
 * Return Value:
 *  Standard
 */

LRESULT WINAPI GizmoBarWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    BOOL            fSuccess;
    BOOL            fTemp;
    LPCREATESTRUCT  pCreate;
    PGIZMOBAR       pGB;
    PGIZMO          pGizmo;
    RECT            rc;
    POINT           pt;
    short           x, y;
    COLORREF        cr;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    pGB=(PGIZMOBAR)GetWindowLong(hWnd, GBWL_STRUCTURE);

    //Pass control messages onto another function for processing.
    if (iMsg >= WM_USER)
        return GBMessageHandler(hWnd, iMsg, wParam, lParam, pGB);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pCreate=(LPCREATESTRUCT)lParam;

            pGB=GizmoBarPAllocate(&fSuccess, hWnd
                , pCreate->hInstance, pCreate->hwndParent
                , pCreate->style, 0, (UINT)pCreate->hMenu);

            if (!fSuccess)
                {
                GizmoBarPFree(pGB);
                return -1L;
                }
            else
                SetWindowLong(hWnd, GBWL_STRUCTURE, (LONG)pGB);

            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        case WM_DESTROY:
            /*
             * We want to clean up before DestroyWindow nukes all the
             * children, so WM_DESTROY is a better to do it than
             * WM_NCDESTROY.
             */
            GizmoBarPFree(pGB);
            break;

        case WM_ERASEBKGND:
            /*
             * Eat this message to avoid erasing portions that
             * we are going to repaint in WM_PAINT.  Part of a
             * change-state-and-repaint strategy is to rely on
             * WM_PAINT to do anything visual, which includes
             * erasing invalid portions.  Letting WM_ERASEBKGND
             * erase the background is redundant.
             */
            return TRUE;

       #ifdef WIN32
        case WM_CTLCOLORBTN:
        case WM_CTLCOLORSTATIC:
            fTemp=TRUE;
       #else
        case WM_CTLCOLOR:
            //Change the color of static text on the GizmoBar.
            fTemp=(HIWORD(lParam)==CTLCOLOR_STATIC
                || HIWORD(lParam)==CTLCOLOR_BTN);
       #endif

            if (fTemp)
                {
                cr=GetSysColor(COLOR_BTNFACE);
                SetTextColor((HDC)wParam
                    , GetSysColor(COLOR_BTNTEXT));
                SetBkColor((HDC)wParam, cr);

                /*
                 * If the system colors have changed, then crFace
                 * will not be equal to COLOR_BTNFACE, so we
                 * reinitialize the background brush.  This scheme
                 * handles system color changes appropriately
                 * without processing WM_WININICHANGE and without
                 * blindly creating a new brush on every WM_CTLCOLOR
                 * message.
                 */
                if (cr!=pGB->crFace)
                    {
                    pGB->crFace=cr;

                    if (NULL!=pGB->hBrFace)
                        DeleteObject(pGB->hBrFace);

                    pGB->hBrFace=CreateSolidBrush(pGB->crFace);
                    }

                return (LONG)(UINT)pGB->hBrFace;
                }

            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        case WM_PAINT:
            GizmoBarPaint(hWnd, pGB);
            break;

        case WM_SETFONT:
            /*
             * wParam has the new font that we now send to all other
             * windows controls in us.  We control repaints here to
             * prevent a lot of repainting for each control.
             */
            DefWindowProc(hWnd, WM_SETREDRAW, FALSE, 0L);

            if ((WPARAM)NULL!=wParam)
                {
                pGB->hFont=(HFONT)wParam;
                GizmoPEnum(&pGB->pGizmos, FEnumChangeFont
                    , (DWORD)(LPTSTR)pGB);

                DefWindowProc(hWnd, WM_SETREDRAW, TRUE, 0L);
                InvalidateRect(hWnd, NULL, FALSE);
                UpdateWindow(hWnd);
                }

            break;

        case WM_GETFONT:
            return (LRESULT)(UINT)pGB->hFont;

        case WM_ENABLE:
            /*
             * wParam has the new enable flag that we use to enable
             * or disable ALL controls in us at one time.  We also
             * turn the redraw off to prevent a lot of flicker.
             */
            DefWindowProc(hWnd, WM_SETREDRAW, FALSE, 0L);

            pGB->fEnabled=(BOOL)wParam;
            GizmoPEnum(&pGB->pGizmos, FEnumEnable
                , (DWORD)(LPTSTR)pGB);

            DefWindowProc(hWnd, WM_SETREDRAW, TRUE, 0L);
            InvalidateRect(hWnd, NULL, FALSE);
            UpdateWindow(hWnd);
            break;

        case WM_CANCELMODE:
            pGizmo=pGB->pGizmoTrack;

            pGB->fTracking=FALSE;
            pGB->fMouseOut=FALSE;

            if (NULL!=pGizmo)
                GizmoPStateSet(hWnd, pGizmo, COMMANDBUTTON_UP);

            ReleaseCapture();
            break;

        case WM_LBUTTONDBLCLK:
        case WM_LBUTTONDOWN:
            //Get the mouse coordinates.
            x=LOWORD(lParam);
            y=HIWORD(lParam);

            /*
             * See if we hit a command or attribute gizmo or not.
             * Anything else that is a control will get the message
             * instead of us anyway, so we don't have to check.
             * FEnumHitTest also validates drawn gizmos, enabled, and
             * visible, so we don't.
             */
            pGizmo=GizmoPEnum(&pGB->pGizmos, FEnumHitTest, lParam);

            if (NULL==pGizmo)
                break;          //Didn't hit one matching our needs.

            /*
             * Inform the associate that a command was hit like a
             * menu item.
             */
            if (NULL!=pGB->hWndAssociate)
                {
                if (pGizmo->fNotify)
                    {
                    SendMenuSelect(pGB->hWndAssociate, pGizmo->uID
                        , 0, 0);
                    }
                }

            /*
             * We hit a button.  If it's a command or attribute,
             * then change the state and start tracking.
             */
            pGB->fTracking=TRUE;
            pGB->pGizmoTrack=pGizmo;
            pGB->fMouseOut=FALSE;
            SetCapture(hWnd);

            pGizmo->uStateOrg=pGizmo->uState;
            GizmoPStateSet(hWnd, pGizmo, ATTRIBUTEBUTTON_MOUSEDOWN);

            break;

        case WM_MOUSEMOVE:
            POINTFROMLPARAM(pt, lParam);

            if (!pGB->fTracking)
                break;

            pGizmo=pGB->pGizmoTrack;
            SetRect(&rc, pGizmo->x, pGizmo->y, pGizmo->x+pGizmo->dx
                , pGizmo->y+pGizmo->dy);

            fTemp=pGB->fMouseOut;
            pGB->fMouseOut=!PtInRect(&rc, pt);

            //If the mouse went out, change state to the original.
            if (!fTemp && pGB->fMouseOut)
                {
                GizmoPStateSet(hWnd, pGizmo, pGizmo->uStateOrg);

                if (NULL!=pGB->hWndAssociate)
                    {
                    //Notify that we left the button
                    if (pGizmo->fNotify)
                        SendMenuSelect(pGB->hWndAssociate, 0, ~0, 0);
                    }
                }

            if (fTemp && !pGB->fMouseOut)
                {
                GizmoPStateSet(hWnd, pGizmo
                    , ATTRIBUTEBUTTON_MOUSEDOWN);

                if (NULL!=pGB->hWndAssociate)
                    {
                    //Notify that we pressed down again
                    if (pGizmo->fNotify)
                        {
                        SendMenuSelect(pGB->hWndAssociate
                            , pGizmo->uID , 0, 0);
                        }
                    }
                }

            break;

        case WM_LBUTTONUP:
            if (!pGB->fTracking)
                break;

            pGB->fTracking=FALSE;
            pGizmo=pGB->pGizmoTrack;
            ReleaseCapture();

            /*
             * Repaint if we were actually below the mouse when this
             * occurred.  For command buttons, pop the button up.
             * For attributes, either toggle the state (inclusive
             * buttons) or check the selected one (exclusive buttons)
             */

            if (!pGB->fMouseOut)
                {
                //Command buttons always come up.
                if (GIZMOTYPE_BUTTONCOMMAND==pGizmo->iType)
                    GizmoPStateSet(hWnd, pGizmo, COMMANDBUTTON_UP);

                //Attribute inclusive buttons toggle
                if (GIZMOTYPE_BUTTONATTRIBUTEIN==pGizmo->iType)
                    {
                    GizmoPCheck(hWnd, pGizmo, !(BUTTONGROUP_DOWN
                        & pGizmo->uStateOrg));
                    }

                //Attribure exclusive buttons are always checked.
                if (GIZMOTYPE_BUTTONATTRIBUTEEX==pGizmo->iType)
                    GizmoPCheck(hWnd, pGizmo, TRUE);

                //Only send messages if notify is ON.
                if (NULL!=pGB->hWndAssociate && pGizmo->fNotify)
                    {
                    SendMenuSelect(pGB->hWndAssociate, 0, ~0, 0);
                    SendCommand(pGB->hWndAssociate, pGizmo->uID
                        , BN_CLICKED, hWnd);
                    }
                }

            break;

        case WM_COMMAND:
            //Pass control messages on if the gizmo's notify is ON.
            if (NULL!=pGB->hWndAssociate)
                {
                pGizmo=PGizmoFromHwndID(hWnd, wID);

                if (NULL!=pGizmo)
                    {
                    if (pGizmo->fNotify)
                        {
                        SendMessage(pGB->hWndAssociate, iMsg, wParam
                            , lParam);
                        }
                    }
                }
            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }

/*
 * FEnumChangeFont
 *
 * Purpose:
 *  Enumeration callback for all the gizmos we know about in order to
 *  send a new font to them that's stored in PGIZMOBAR in dw.
 *
 * Parameters:
 *  pGizmo          PGIZMO to draw.
 *  iGizmo          UINT index on the GizmoBar of this gizmo.
 *  dw              DWORD extra data passed to GizmoPEnum, in our
 *                  case the GizmoBar's pGB.
 *
 * Return Value:
 *  BOOL            TRUE to continue the enumeration, FALSE otherwise.
 */

BOOL WINAPI FEnumChangeFont(PGIZMO pGizmo, UINT iGizmo, DWORD dw)
    {
    PGIZMOBAR   pGB=(PGIZMOBAR)dw;

    //Only need to change fonts in windowed controls using WM_SETFONT
    if (NULL!=pGizmo->hWnd)
        {
        SendMessage(pGizmo->hWnd, WM_SETFONT
            , (WPARAM)pGB->hFont, 1L);
        }

    return TRUE;
    }

/*
 * FEnumEnable
 *
 * Purpose:
 *  Enumeration callback for all the gizmos we know about in order to
 *  enable or disable them from the WM_ENABLE message.
 *
 * Parameters:
 *  pGizmo          PGIZMO to draw.
 *  iGizmo          UINT index on the GizmoBar of this gizmo.
 *  dw              DWORD extra data passed to GizmoPEnum, in our
 *                  case the GizmoBar's pGB.
 *
 * Return Value:
 *  BOOL            TRUE to continue the enumeration, FALSE
 *                  otherwise.
 */

BOOL WINAPI FEnumEnable(PGIZMO pGizmo, UINT iGizmo, DWORD dw)
    {
    PGIZMOBAR   pGB=(PGIZMOBAR)dw;
    BOOL        fEnable=pGB->fEnabled;

    //NOTE:  This code is duplicated in GBGizmoEnable in API.C
    if (NULL!=pGizmo->hWnd)
        EnableWindow(pGizmo->hWnd, fEnable);
    else
        {
        //If we're not down, command and attribute buttons act same.
        if (!(BUTTONGROUP_DOWN & pGizmo->uState))
            {
            GizmoPStateSet(pGB->hWnd, pGizmo, fEnable
                ? COMMANDBUTTON_UP : COMMANDBUTTON_DISABLED);
            }
        else
            {
            /*
             * Attribute buttons are a little more sensitive
             * with DOWNDISABLED
             */
            GizmoPStateSet(pGB->hWnd, pGizmo, fEnable
                ? ATTRIBUTEBUTTON_DOWN
                : ATTRIBUTEBUTTON_DOWNDISABLED);
            }
        }

    return TRUE;
    }

/*
 * FEnumHitTest
 *
 * Purpose:
 *  Enumeration callback for all the gizmos we know about in order to
 *  hit-test them.
 *
 * Parameters:
 *  pGizmo          PGIZMO to draw.
 *  iGizmo          UINT index on the GizmoBar of this gizmo.
 *  dw              DWORD extra data passed to GizmoPEnum, in our
 *                  case the hDC on which to draw.
 *
 * Return Value:
 *  BOOL            TRUE to continue the enumeration, FALSE
 *                  otherwise.
 */

BOOL WINAPI FEnumHitTest(PGIZMO pGizmo, UINT iGizmo, DWORD dw)
    {
    RECT  rc;
    POINT pt;

    POINTFROMLPARAM(pt, dw);

    /*
     * Hit tests have to happen on visible, enabled, and drawn
     * controls only.
     */
    if (GIZMOTYPE_DRAWN & pGizmo->iType && !pGizmo->fHidden
        && !(BUTTONGROUP_DISABLED & pGizmo->uState))
        {
        SetRect(&rc, pGizmo->x, pGizmo->y
            , pGizmo->x+pGizmo->dx, pGizmo->y+pGizmo->dy);

        //Stop enumeration if we have a hit.
        return !PtInRect(&rc, pt);
        }

    return TRUE;
    }


GIZMOINT.H

/*
 * GIZMOINT.H
 * GizmoBar Version 1.01
 *
 * Internal definitions for the GizmoBar DLL
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _GIZMOINT_H_
#define _GIZMOINT_H_

#include "gizmobar.h"
#include "win1632.h"
#include "gizmo.h"

#ifdef __cplusplus
extern "C"
    {
#endif

/*
 * The main gizmobar structure itself.  There's only one of these,
 * but it references the first GIZMO in the list.
 */

typedef struct tagGIZMOBAR
    {
    PGIZMO      pGizmos;            //List of gizmos we own.
    HWND        hWnd;               //Window handle of ourselves.
    HINSTANCE   hInst;
    HWND        hWndAssociate;      //Associate who gets messages.
    DWORD       dwStyle;            //Copy of GWL_STYLE
    UINT        uState;             //State flags
    UINT        uID;                //Control ID.

    HBRUSH      hBrFace;            //Static control background color
    COLORREF    crFace;             //Color of hBrFace
    HFONT       hFont;              //Font in use
    BOOL        fEnabled;           //Are we enabled?

    PGIZMO      pGizmoTrack;        //Current pressed button.
    BOOL        fTracking;
    BOOL        fMouseOut;
    } GIZMOBAR, * PGIZMOBAR;

#define CBGIZMOBAR sizeof(GIZMOBAR)

//Extra bytes for the window if the size of a local handle.
#define CBWINDOWEXTRA       sizeof(PGIZMOBAR)

#define GBWL_STRUCTURE      0

//Structure for passing paint info to a gizmo enumeration callback.
typedef struct
    {
    HDC     hDC;
    BOOL    fPaint;
    } PAINTGIZMO, * PPAINTGIZMO;

//Private functions specific to the control.

//INIT.C
BOOL              FRegisterControl(HINSTANCE);
PGIZMOBAR         GizmoBarPAllocate(int *, HWND, HINSTANCE, HWND
                      , DWORD, UINT, UINT);
PGIZMOBAR         GizmoBarPFree(PGIZMOBAR);

//PAINT.C
void              GizmoBarPaint(HWND, PGIZMOBAR);
BOOL WINAPI       FEnumPaintGizmos(PGIZMO, UINT, DWORD);

//GIZMOBAR.C
LRESULT WINAPI    GizmoBarWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL    WINAPI    FEnumChangeFont(PGIZMO, UINT, DWORD);
BOOL    WINAPI    FEnumEnable(PGIZMO, UINT, DWORD);
BOOL    WINAPI    FEnumHitTest(PGIZMO, UINT, DWORD);

//API.C  Also see GIZMOBAR.H for others
LRESULT    GBMessageHandler(HWND, UINT, WPARAM, LPARAM, PGIZMOBAR);
PGIZMO     PGizmoFromHwndID(HWND, UINT);

#endif //_GIZMOINT_H_


INIT.C

/*
 * INIT.C
 * GizmoBar Version 1.01
 *
 * LibMain entry point and initialization code for the GizmoBar
 * DLL that is likely to be used once or very infrequently.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

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

/*
 * LibMain(32)
 *
 * Purpose:
 *  Entry point conditionally compiled for Windows NT and Windows
 *  3.1.  Provides the proper structure for each environment.
 */

#ifdef WIN32

BOOL WINAPI DllMain(HINSTANCE hInstance, ULONG ulReason
    , PCONTEXT pContext)
    {
    UNREFERENCED_PARAMETER(pContext);

    if (DLL_PROCESS_ATTACH==ulReason)
        return FRegisterControl(hInstance);

    return TRUE;
    }

#else

int WINAPI LibMain(HANDLE hInstance, WORD wDataSeg
    , WORD cbHeapSize, LPTSTR lpCmdLine)
    {
     //Perform global initialization.
    if (FRegisterControl(hInstance))
        {
        if (0!=cbHeapSize)
            UnlockData(0);
        }

    return (int)hInstance;
    }

#endif

/*
 * FRegisterControl
 *
 * Purpose:
 *  Registers the GizmoBar control class, including CS_GLOBALCLASS
 *  to make the control available to all applications in the system.
 *
 * Parameters:
 *  hInst           HINSTANCE of the DLL that will own this class.
 *
 * Return Value:
 *  BOOL            TRUE if the class is registered, FALSE otherwise.
 */

BOOL FRegisterControl(HINSTANCE hInst)
    {
    static BOOL     fRegistered=FALSE;
    WNDCLASS        wc;

    if (!fRegistered)
        {
        wc.lpfnWndProc   =GizmoBarWndProc;
        wc.cbClsExtra    =0;
        wc.cbWndExtra    =CBWINDOWEXTRA;
        wc.hInstance     =hInst;
        wc.hIcon         =NULL;
        wc.hCursor       =LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground =(HBRUSH)(COLOR_BTNFACE+1);
        wc.lpszMenuName  =NULL;
        wc.lpszClassName =CLASS_GIZMOBAR;
        wc.style         =CS_DBLCLKS | CS_GLOBALCLASS
                          | CS_VREDRAW | CS_HREDRAW;

        fRegistered=RegisterClass(&wc);
        }

    return fRegistered;
    }

/*
 * GizmoBarPAllocate
 *
 * Purpose:
 *  Allocates and initializes the control's primary data structure
 *  for each window that gets created.
 *
 * Parameters:
 *  pfSuccess       int * indicating success of the function.
 *  hWnd            HWND that is tied to this structure.
 *  hInst           HINSTANCE of the DLL.
 *  hWndAssociate   HWND to which we send messages.
 *  dwStyle         DWORD initial style.
 *  uState          UINT initial state.
 *  uID             UINT identifier for this window.
 *
 * Return Value:
 *  PGIZMOBAR       If NULL returned then GizmoBarPAllocate could not
 *                  allocate memory.  If a non-NULL pointer is
 *                  returned with *pfSuccess, then call GizmoBarPFree
 *                  immediately.  If you get a non-NULL pointer and
 *                  *pfSuccess==TRUE then the function succeeded.
 */

PGIZMOBAR GizmoBarPAllocate(int *pfSuccess, HWND hWnd
    , HINSTANCE hInst, HWND hWndAssociate, DWORD dwStyle
    , UINT uState , UINT uID)
    {
    PGIZMOBAR     pGB;

    if (NULL==pfSuccess)
        return NULL;

    *pfSuccess=FALSE;

    //Allocate the structure
    pGB=(PGIZMOBAR)(void *)LocalAlloc(LPTR, CBGIZMOBAR);

    if (NULL==pGB)
        return NULL;

    //Initialize LibMain parameter holders.
    pGB->hWnd         =hWnd;
    pGB->hInst        =hInst;
    pGB->hWndAssociate=hWndAssociate;
    pGB->dwStyle      =dwStyle;
    pGB->uState       =uState;
    pGB->uID          =uID;
    pGB->fEnabled     =TRUE;

    pGB->crFace=GetSysColor(COLOR_BTNFACE);
    pGB->hBrFace=CreateSolidBrush(pGB->crFace);

    if (NULL==pGB->hBrFace)
        return pGB;

    pGB->hFont=GetStockObject(SYSTEM_FONT);

    *pfSuccess=TRUE;
    return pGB;
    }

/*
 * GizmoBarPFree
 *
 * Purpose:
 *  Reverses all initialization done by GizmoBarPAllocate, cleaning
 *  up any allocations including the application structure itself.
 *
 * Parameters:
 *  pGB             PGIZMOBAR to the control's structure
 *
 * Return Value:
 *  PGIZMOBAR       NULL if successful, pGB if not, meaning we
 *                  couldn't free some allocation.
 */

PGIZMOBAR GizmoBarPFree(PGIZMOBAR pGB)
    {
    if (NULL==pGB)
        return NULL;

    /*
     * Free all the gizmos we own.  When we call GizmoPFree we always
     * free the first one in the list which updates pGB->pGizmos for
     * us, so we just have to keep going until pGizmos is NULL,
     * meaning we're at the end of the list.
     */
    while (NULL!=pGB->pGizmos)
        GizmoPFree(&pGB->pGizmos, pGB->pGizmos);

    if (NULL!=pGB->hBrFace)
        DeleteObject(pGB->hBrFace);

    /*
     * Notice that since we never create a font, we aren't
     * responsible for our hFont member.
     */

    return (PGIZMOBAR)(void *)LocalFree((HLOCAL)(UINT)(LONG)pGB);
    }


PAINT.C

/*
 * PAINT.C
 * GizmoBar Version 1.01
 *
 * Contains any code related to GizmoBar visuals, primarily
 * the WM_PAINT handler.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

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

//In GIZMO.C
extern TOOLDISPLAYDATA tdd;

/*
 * GizmoBarPaint
 *
 * Purpose:
 *  Handles all WM_PAINT messages for the control and paints either
 *  the entire thing or just one GizmoBar button if pGB->pGizmoPaint
 *  is non-NULL.
 *
 * Parameters:
 *  hWnd            HWND Handle to the control.
 *  pGB             PGIZMOBAR control data pointer.
 *
 * Return Value:
 *  None
 */

void GizmoBarPaint(HWND hWnd, PGIZMOBAR pGB)
    {
    PAINTSTRUCT ps;
    RECT        rc;
    HDC         hDC;
    HBRUSH      hBr=NULL;
    HPEN        hPen=NULL;

    hDC=BeginPaint(hWnd, &ps);
    GetClientRect(hWnd, &rc);

    /*
     * The only part of the frame we need to draw is the bottom line,
     * so we inflate the rectangle such that all other parts are
     * outside the visible region.
     */
    hBr =CreateSolidBrush(GetSysColor(COLOR_BTNFACE));

    if (NULL!=hBr)
        SelectObject(hDC, hBr);

    hPen=CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));

    if (NULL!=hPen)
        SelectObject(hDC, hPen);

    Rectangle(hDC, rc.left-1, rc.top-1, rc.right+1, rc.bottom);

    /*
     * All that we have to do to draw the controls is start through
     * the list, ignoring anything but buttons, and calling BTTNCUR's
     * UIToolButtonDraw for buttons.  Since we don't even have to
     * track positions of things, we can just use an enum.
     */
    GizmoPEnum(&pGB->pGizmos, FEnumPaintGizmos, (DWORD)(LPTSTR)&ps);

    //Clean up
    EndPaint(hWnd, &ps);

    if (NULL!=hBr)
        DeleteObject(hBr);

    if (NULL!=hPen)
        DeleteObject(hPen);

    return;
    }

/*
 * FEnumPaintGizmos
 *
 * Purpose:
 *  Enumeration callback for all the gizmos we know about in order to
 *  draw them.
 *
 * Parameters:
 *  pGizmo          PGIZMO to draw.
 *  iGizmo          UINT index on the GizmoBar of this gizmo.
 *  dw              DWORD extra data passed to GizmoPEnum, in our
 *                  case a pointer to the PAINTSTRUCT.
 *
 * Return Value:
 *  BOOL            TRUE to continue the enumeration, FALSE
 *                  otherwise.
 */

BOOL WINAPI FEnumPaintGizmos(PGIZMO pGizmo, UINT iGizmo, DWORD dw)
    {
    LPPAINTSTRUCT   pps=(LPPAINTSTRUCT)dw;
    RECT            rc, rcI;

    //Only draw those marked for repaint.
    if ((GIZMOTYPE_DRAWN & pGizmo->iType))
        {
        SetRect(&rc, pGizmo->x, pGizmo->y
            , pGizmo->x+pGizmo->dx, pGizmo->y+pGizmo->dy);

        //Only draw gizmos in the repaint area
        if (IntersectRect(&rcI, &rc, &pps->rcPaint))
            {
            UIToolButtonDrawTDD(pps->hdc, pGizmo->x, pGizmo->y
                , pGizmo->dx, pGizmo->dy, pGizmo->hBmp
                , pGizmo->cxImage, pGizmo->cyImage, pGizmo->iBmp
                , (UINT)pGizmo->uState, &tdd);
            }
        }

    return TRUE;
    }


WIN1632.H

/*
 * BOOK1632.H
 *
 * Macros and other definitions that assist in porting between Win16
 * and Win32 applications.  Defines WIN32 to enable 32-bit versions.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  INTERNET>kraigb@microsoft.com
 */

#ifndef _BOOK1632_H_
#define _BOOK1632_H_

#ifdef WIN32

#ifndef COMMANDPARAMS
#define COMMANDPARAMS(wID, wCode, hWndMsg)                          \
    WORD        wID     = LOWORD(wParam);                           \
    WORD        wCode   = HIWORD(wParam);                           \
    HWND        hWndMsg = (HWND)(UINT)lParam;
#endif  //COMMANDPARAMS

#ifndef SendCommand
#define SendCommand(hWnd, wID, wCode, hControl)                     \
            SendMessage(hWnd, WM_COMMAND, MAKEWPARAM(wID, wCode)    \
                        , (LPARAM)hControl)
#endif  //SendCommand

#ifndef MENUSELECTPARAMS
#define MENUSELECTPARAMS(wID, wFlags, hMenu)                        \
    WORD        wID     = LOWORD(wParam);                           \
    WORD        wFlags  = HIWORD(wParam);                           \
    HMENU       hMenu   = (HMENU)lParam;
#endif  //MENUSELECTPARAMS

#ifndef SendMenuSelect
#define SendMenuSelect(hWnd, wID, wFlags, hMenu)                    \
            SendMessage(hWnd, WM_MENUSELECT                         \
                , MAKEWPARAM((WORD)wID, (WORD)wFlags), (LPARAM)hMenu)

#endif  //SendMenuSelect

#ifndef SendScrollPosition
#define SendScrollPosition(hWnd, iMsg, iPos)                        \
            SendMessage(hWnd, iMsg, MAKEWPARAM(SB_THUMBPOSITION, iPos), 0)
#endif //SendScrollPosition

#ifndef ScrollThumbPosition
#define ScrollThumbPosition(w, l) HIWORD(w)
#endif //ScrollThumbPosition

#ifndef GETWINDOWINSTANCE
#define GETWINDOWINSTANCE(h) (HINSTANCE)GetWindowLong(h, GWL_HINSTANCE)
#endif  //GETWINDOWINSTANCE

#ifndef GETWINDOWID
#define GETWINDOWID(h) (UINT)GetWindowLong(h, GWW_ID)
#endif  //GETWINDOWID

#ifndef POINTFROMLPARAM
#define POINTFROMLPARAM(p, l) {p.x=(LONG)(SHORT)LOWORD(l); \
                               p.y=(LONG)(SHORT)HIWORD(l);}
#endif  //POINTEFROMLPARAM

#ifndef MDIREFRESHMENU
#define MDIREFRESHMENU(h) SendMessage(h, WM_MDIREFRESHMENU, 0, 0L)
#endif  //MDIREFRESHMENU

//Activated child window in WM_MDIACTIVATE
#ifndef NEWMDIACTIVE
#define NEWMDIACTIVE lParam
#endif  //NEWMDIACTIVE

#ifndef UNICODETOANSI
#ifdef UNICODE
#define UNICODETOANSI(s, d, cch) WideCharToMultiByte(CP_ACP \
            , 0, s, -1, d, cch, NULL, NULL)
#else  //ANSI
#define UNICODETOANSI(s, d, cch) lstrcpy(d, (LPTSTR)s)
#endif
#endif //UNICODETOANSI

//****END WIN32

#else

//****START !WIN32

#ifndef POINTS
typedef POINT POINTS;
#endif  //POINTS

#ifndef LPPOINTS
typedef POINTS FAR *LPPOINTS;
#endif  //LPPOINTS

#ifndef MAKEPOINTS
#define MAKEPOINTS MAKEPOINT
#endif  //MAKEPOINTS

#ifndef COMMANDPARAMS
#define COMMANDPARAMS(wID, wCode, hWndMsg)                          \
    WORD        wID     = LOWORD(wParam);                           \
    WORD        wCode   = HIWORD(lParam);                           \
    HWND        hWndMsg = (HWND)(UINT)lParam;
#endif  //COMMANDPARAMS

#ifndef SendCommand
#define SendCommand(hWnd, wID, wCode, hControl)                     \
            SendMessage(hWnd, WM_COMMAND, wID                       \
                        , MAKELONG(hControl, wCode))
#endif  //SendCommand

#ifndef MENUSELECTPARAMS
#define MENUSELECTPARAMS(wID, wFlags, hMenu)                        \
    WORD        wID     = LOWORD(wParam);                           \
    WORD        wFlags  = LOWORD(lParam);                           \
    HMENU       hMenu   = (HMENU)HIWORD(lParam);
#endif  //MENUSELECTPARAMS

#ifndef SendMenuSelect
#define SendMenuSelect(hWnd, wID, wFlags, hMenu)                    \
            SendMessage(hWnd, WM_MENUSELECT, wID                    \
                        , MAKELONG((WORD)wFlags, (WORD)hMenu))
#endif  //SendMenuSelect

#ifndef SendScrollPosition
#define SendScrollPosition(hWnd, iMsg, iPos)                        \
            SendMessage(hWnd, iMsg, SB_THUMBPOSITION, MAKELONG(iPos, 0))
#endif //Send ScrollPosition

#ifndef ScrollThumbPosition
#define ScrollThumbPosition(w, l) LOWORD(l)
#endif //ScrollThumbPosition

#ifndef GETWINDOWINSTANCE
#define GETWINDOWINSTANCE(h) (HINSTANCE)GetWindowWord(h, GWW_HINSTANCE)
#endif  //GETWINDOWINSTANCE

#ifndef GETWINDOWID
#define GETWINDOWID(h) (UINT)GetWindowWord(h, GWW_ID)
#endif  //GETWINDOWID

#ifndef POINTFROMLPARAM
#define POINTFROMLPARAM(p, l) {p.x=LOWORD(l); p.y=HIWORD(l);}
#endif  //POINTEFROMLPARAM

#ifndef MDIREFRESHMENU
#define MDIREFRESHMENU(h) SendMessage(h, WM_MDISETMENU, TRUE, 0L)
#endif  //MDIREFRESHMENU

//Activated child window in WM_MDIACTIVATE
#ifndef NEWMDIACTIVE
#define NEWMDIACTIVE wParam
#endif  //NEWMDIACTIVE

//Things not present in Win3.1 SDK but present in Win32 SDK.

#ifndef APIENTRY
#define APIENTRY __export FAR PASCAL
#endif  //APIENTRY

#ifndef USHORT
typedef unsigned short USHORT;
#endif  //USHORT

//These are so we can write ANSI/UNICODE portable code.
#ifndef TCHAR
typedef char TCHAR;
#endif  //TCHAR

#ifndef LPTSTR
typedef TCHAR *LPTSTR;
#endif  //LPTSTR

#ifndef LPCTSTR
typedef const TCHAR *LPCTSTR;
#endif  //LPCTSTR

#ifndef _tcsncpy
#define _tcsncpy strncpy
#endif  //_tcsncpy

#ifndef lstrcpyA
#define lstrcpyA lstrcpy
#endif  //lstrcpyA

#ifndef lstrcmpiA
#define lstrcmpiA lstrcmpi
#endif  //lstrcmpiA

#ifndef TEXT
#define TEXT(a) a
#endif  //TEXT

#ifndef DeleteFile
#define DeleteFile(f)                       \
            {                               \
            OFSTRUCT    of;                 \
            OpenFile(f, &of, OF_DELETE);    \
            }
#endif  //DeleteFile

#ifndef UNICODETOANSI
#define UNICODETOANSI(s, d, cch) lstrcpy(d, (LPTSTR)s)
#endif  //UNICODETOANSI

#ifndef SetForegroundWindow
#define SetForegroundWindow(w) SetActiveWindow(w)
#endif  //SetForegroundWindow

#endif  //!WIN32

//These definitions we need regardless of Win16 or Win32

typedef struct
    {
    short   left;
    short   top;
    short   right;
    short   bottom;
    } RECTS, FAR *LPRECTS;

#define RECTSTORECT(rs, r) {(r).left=(rs).left;(r).top=(rs).top; \
    (r).right=(rs).right;(r).bottom=(rs).bottom;};

#define RECTTORECTS(r, rs) {(rs).left=(short)(r).left;   \
    (rs).top=(short)(r).top;(rs).right=(short)(r).right; \
    (rs).bottom=(short)(r).bottom;};

#endif  //_BOOK1632_H_


ANSIAPI.H

/*
 * ansiapi.h
 * This file contains prototypes of ANSI version of OLE apis
 * and mapping fooA to foo
 */
#ifndef _ANSIAPI_INCL
#define _ANSIAPI_INCL

#define UNICODEOLE32

#ifdef WIN32S
#if !defined(UNICODEOLE32)
#pragma message("Compiling for 32bit ANSI Ole!\n");
#endif
#endif

#define WASANSI() {                                                           \
                   OutputDebugString("\n\r\t\tUnicode string was ANSI!\r\n"); \
                   Assert(0);                                                 \
                   _asm { int 3 }                                             \
                  }

#ifndef UNICODEOLE32

#define W2A(w, a, cb)     lstrcpy (a, w)
#define A2W(a, w, cb)     lstrcpy (w, a)

#define FREELOCALSTRING(p)
#define MAKE_STR_LOCAL_COPYW2A(s, l)  l = s
#define MAKE_STR_LOCAL_COPYA2W(s, l)  l = s

#define OLESTRCPY                lstrcpy
#define OLESTRCAT                lstrcat
#define OLESTRLEN                lstrlen
#define OLESTRCMP                lstrcmp

#define DeleteFile_AW            DeleteFile
#define _lopen_AW                _lopen
#define _lcreat_AW               _lcreat
#define GlobalAddAtom_AW         GlobalAddAtom
#define GlobalGetAtomName_AW     GlobalGetAtomName
#define RegOpenKey_AW            RegOpenKey

#define CoLoadLibraryA   CoLoadLibrary

#define StringFromCLSID2A    StringFromCLSID2
#define StringFromIID2A      StringFromIID2
#define StringFromGUID2A     StringFromGUID2
#define CLSIDFromProgIDA     CLSIDFromProgID
#define CLSIDFromStringA     CLSIDFromString
#define ProgIDFromCLSIDA     ProgIDFromCLSID
#define StringFromCLSIDA     StringFromCLSID

#define UtDupStringA2W       UtDupString

// Storage APIs
#define StgOpenStorageA      StgOpenStorage

// IPersistFile ansi translation

#define IPersistFile_LoadA(pf, file, mode) (pf)->Load(file, mode)

// IMoniker ansi translation

#define IMoniker_GetDisplayNameA(pm, p1, p2, p3) (pm)->GetDisplayName(p1,p2,p3)

#define CreateFileMonikerA   CreateFileMoniker

#else  // UNICODEOLE32

#define W2A(w, a, cb)     WideCharToMultiByte(                              \
                                               CP_ACP,                      \
                                               0,                           \
                                               w,                           \
                                               -1,                          \
                                               a,                           \
                                               cb,                          \
                                               NULL,                        \
                                               NULL)

#define A2W(a, w, cb)     MultiByteToWideChar(                              \
                                               CP_ACP,                      \
                                               0,                           \
                                               a,                           \
                                               -1,                          \
                                               w,                           \
                                               cb)

#define FREELOCALSTRING(p)      delete (p)
#define MAKE_STR_LOCAL_COPYW2A(s, l)  {                                   \
                                      l = UtDupStringW2A(s);              \
                                      if (!l) {                           \
                                         return ResultFromScode(S_OOM);   \
                                      }                                   \
                                   }

#define MAKE_STR_LOCAL_COPYA2W(s, l)  {                                   \
                                      l = UtDupStringA2W(s);              \
                                      if (!l) {                           \
                                         return ResultFromScode(S_OOM);   \
                                      }                                   \
                                   }

#define OLESTRCPY                wcscpy
#define OLESTRCAT                wcscat
#define OLESTRLEN                wcslen
#define OLESTRCMP                wcscmp

#define DeleteFile_AW            DeleteFileW
#define _lopen_AW                _lopenW
#define _lcreat_AW               _lcreatW
#define GlobalAddAtom_AW         GlobalAddAtomW
#define GlobalGetAtomName_AW     GlobalGetAtomNameW
#define RegOpenKey_AW            RegOpenKeyW

//STDAPI_(HINSTANCE) CoLoadLibraryA(LPSTR lpszLibName, BOOL bAutoFree);

#define StringFromCLSID2A(rclsid, lpsz, cbMax) \
    StringFromGUID2A(rclsid, lpsz, cbMax)

#define StringFromIID2A(riid, lpsz, cbMax) \
    StringFromGUID2A(riid, lpsz, cbMax)

//STDAPI_(int)  StringFromGUID2A(REFGUID rguid, LPSTR lpsz, int cbMax);
//STDAPI        CLSIDFromProgIDA(LPCSTR szProgID, LPCLSID pclsid);
//STDAPI        CLSIDFromStringA(LPSTR lpsz, LPCLSID lpclsid);
//STDAPI        StringFromCLSIDA(REFCLSID rclsid, LPSTR FAR* lplpsz);
//STDAPI        ProgIDFromCLSIDA (REFCLSID clsid, LPSTR FAR* lplpszProgID);

//LPWSTR UtDupStringA2W(LPCSTR pSrc);
//LPSTR  UtDupStringW2A(LPCWSTR pSrc);

// Storage APIs
//STDAPI StgOpenStorageA(LPCSTR pwcsName,IStorage FAR *pstgPriority, DWORD grfMode, SNB snbExclude, DWORD reserved, IStorage FAR * FAR *ppstgOpen);

// IPersistFile ansi translation

//HRESULT IPersistFile_LoadA(LPPERSISTFILE pIPF, LPSTR szFile, DWORD dwMode);

// IMoniker ansi translation

//HRESULT IMoniker_GetDisplayNameA(LPMONIKER pm, LPBC p1, LPMONIKER p2, LPSTR FAR *p3);

//OLEAPI CreateFileMonikerA ( LPSTR lpszPathName, LPMONIKER FAR * ppmk );

#endif // !UNICODEOLE32

#endif //  _ANSIAPI_INCL


ASSERT.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       assert.h
//
//  Contents:   private definition of assert, used by NT SDK OLE2 samples
//
//  History:    8-19-94   stevebl   Created
//
//----------------------------------------------------------------------------

/*
 * Conditional macro definition for function calling type and variable type
 * qualifiers.
 */
#if   ( (_MSC_VER >= 800) && (_M_IX86 >= 300) )

/*
 * Definitions for MS C8-32 (386/486) compiler
 */
#ifndef _CRTAPI1
#define _CRTAPI1 __cdecl
#endif
#ifndef _CRTAPI2
#define _CRTAPI2 __cdecl
#endif

#else

/*
 * Other compilers (e.g., MIPS)
 */
#ifndef _CRTAPI1
#define _CRTAPI1
#endif
#ifndef _CRTAPI2
#define _CRTAPI2
#endif

#endif

#undef  assert

#ifdef NDEBUG

#define assert(exp)     ((void)0)

#else

#ifdef __cplusplus
extern "C" {
#endif
void _CRTAPI1
PopUpAssert(
    void * szFile,
    int iLine,
    void * szMessage);
#ifdef __cplusplus
}
#endif

#define assert(exp) (void)( (exp) || (PopUpAssert(__FILE__, __LINE__, #exp), 0) )
#define _assert(exp, file, line) PopUpAssert(file, line, exp)

#endif  /* NDEBUG */


BTTNCUR.H

/*
 * BTTNCUR.H
 * Buttons & Cursors Version 1.1, March 1993
 *
 * Public include file for the Button Images and Cursor DLL, including
 * structures, definitions, and function prototypes.
 *
 * Copyright (c)1992-1996 Microsoft Corporation, All Rights Reserved,
 * as applied to redistribution of this source code in source form
 * License is granted to use of compiled code in shipped binaries.
 */

#ifndef _BTTNCUR_H_
#define _BTTNCUR_H_

#ifdef __cplusplus
extern "C"
{
#endif

//Standard image bitmap

//WARNING:  Obsolete.  Use the return from UIToolDisplayData
#define IDB_STANDARDIMAGES              400

//New values for display types
#define IDB_STANDARDIMAGESMIN           400
#define IDB_STANDARDIMAGES96            400
#define IDB_STANDARDIMAGES72            401
#define IDB_STANDARDIMAGES120           402

//Image indices inside the standard bitmap.
#define TOOLIMAGE_MIN                   0
#define TOOLIMAGE_EDITCUT               0
#define TOOLIMAGE_EDITCOPY              1
#define TOOLIMAGE_EDITPASTE             2
#define TOOLIMAGE_FILENEW               3
#define TOOLIMAGE_FILEOPEN              4
#define TOOLIMAGE_FILESAVE              5
#define TOOLIMAGE_FILEPRINT             6
#define TOOLIMAGE_HELP                  7
#define TOOLIMAGE_HELPCONTEXT           8
#define TOOLIMAGE_MAX                   8

//Additional Standard Cursors as defined in the UI Design Guide.
#define IDC_NEWUICURSORMIN              500
#define IDC_RIGHTARROW                  500
#define IDC_CONTEXTHELP                 501
#define IDC_MAGNIFY                     502
#define IDC_NODROP                      503
#define IDC_TABLETOP                    504
#define IDC_HSIZEBAR                    505
#define IDC_VSIZEBAR                    506
#define IDC_HSPLITBAR                   507
#define IDC_VSPLITBAR                   508
#define IDC_SMALLARROWS                 509
#define IDC_LARGEARROWS                 510
#define IDC_HARROWS                     511
#define IDC_VARROWS                     512
#define IDC_NESWARROWS                  513
#define IDC_NWSEARROWS                  514
#define IDC_NEWUICURSORMAX              514

//Standard sizes for toolbar buttons and bitmaps on display types

//WARNING:  These are obsolete for version 1.0 compatibility/
#define TOOLBUTTON_STDWIDTH             24
#define TOOLBUTTON_STDHEIGHT            22
#define TOOLBUTTON_STDIMAGEWIDTH        16
#define TOOLBUTTON_STDIMAGEHEIGHT       15

/*
 * Applications can call UIToolDisplayData to get the particular
 * values to use for the current display instead of using these values
 * directly.  However, if the application has the aspect ratio already
 * then these are available for them.
 */

//Sizes for 72 DPI (EGA)
#define TOOLBUTTON_STD72WIDTH           24
#define TOOLBUTTON_STD72HEIGHT          16
#define TOOLBUTTON_STD72IMAGEWIDTH      16
#define TOOLBUTTON_STD72IMAGEHEIGHT     11

//Sizes for 96 DPI (VGA)
#define TOOLBUTTON_STD96WIDTH           24
#define TOOLBUTTON_STD96HEIGHT          22
#define TOOLBUTTON_STD96IMAGEWIDTH      16
#define TOOLBUTTON_STD96IMAGEHEIGHT     15

//Sizes for 120 DPI (8514/a)
#define TOOLBUTTON_STD120WIDTH          32
#define TOOLBUTTON_STD120HEIGHT         31
#define TOOLBUTTON_STD120IMAGEWIDTH     24
#define TOOLBUTTON_STD120IMAGEHEIGHT    23

//Sizes of a standard button bar depending on the display
#define CYBUTTONBAR72                   23
#define CYBUTTONBAR96                   29
#define CYBUTTONBAR120                  38

/*
 * The low-word of the state contains the display state where each
 * value is mutually exclusive and contains one or more grouping bits.
 * Each group represents buttons that share some sub-state in common.
 *
 * The high-order byte controls which colors in the source bitmap,
 * black, white, gray, and dark gray, are to be converted into the
 * system colors COLOR_BTNTEXT, COLOR_HILIGHT, COLOR_BTNFACE, and
 * COLOR_BTNSHADOW.  Any or all of these bits may be set to allow
 * the application control over specific colors.
 *
 * The actual state values are split into a command group and an
 * attribute group.  Up, mouse down, and disabled states are identical,
 * but only attributes can have down, down disabled, and indeterminate
 * states.
 *
 * BUTTONGROUP_BLANK is defined so an application can draw only the button
 * without an image in the up, down, mouse down, or indeterminate
 * state, that is, BUTTONGROUP_BLANK is inclusive with BUTTONGROUP_DOWN
 * and BUTTONGROUP_LIGHTFACE.
 */

#define BUTTONGROUP_DOWN                0x0001
#define BUTTONGROUP_ACTIVE              0x0002
#define BUTTONGROUP_DISABLED            0x0004
#define BUTTONGROUP_LIGHTFACE           0x0008
#define BUTTONGROUP_BLANK               0x0010

//Command buttons only
#define COMMANDBUTTON_UP                (BUTTONGROUP_ACTIVE)
#define COMMANDBUTTON_MOUSEDOWN         (BUTTONGROUP_ACTIVE | BUTTONGROUP_DOWN)
#define COMMANDBUTTON_DISABLED          (BUTTONGROUP_DISABLED)

//Attribute buttons only
#define ATTRIBUTEBUTTON_UP              (BUTTONGROUP_ACTIVE)
#define ATTRIBUTEBUTTON_MOUSEDOWN       (BUTTONGROUP_ACTIVE | BUTTONGROUP_DOWN)
#define ATTRIBUTEBUTTON_DISABLED        (BUTTONGROUP_DISABLED)
#define ATTRIBUTEBUTTON_DOWN            (BUTTONGROUP_ACTIVE | BUTTONGROUP_DOWN | BUTTONGROUP_LIGHTFACE)
#define ATTRIBUTEBUTTON_INDETERMINATE   (BUTTONGROUP_ACTIVE | BUTTONGROUP_LIGHTFACE)
#define ATTRIBUTEBUTTON_DOWNDISABLED    (BUTTONGROUP_DISABLED | BUTTONGROUP_DOWN | BUTTONGROUP_LIGHTFACE)

//Blank buttons only
#define BLANKBUTTON_UP                  (BUTTONGROUP_ACTIVE | BUTTONGROUP_BLANK)
#define BLANKBUTTON_DOWN                (BUTTONGROUP_ACTIVE | BUTTONGROUP_BLANK | BUTTONGROUP_DOWN | BUTTONGROUP_LIGHTFACE)
#define BLANKBUTTON_MOUSEDOWN           (BUTTONGROUP_ACTIVE | BUTTONGROUP_BLANK | BUTTONGROUP_DOWN)
#define BLANKBUTTON_INDETERMINATE       (BUTTONGROUP_ACTIVE | BUTTONGROUP_BLANK | BUTTONGROUP_LIGHTFACE)

/*
 * Specific bits to prevent conversions of specific colors to system
 * colors.  If an application uses this newer library and never specified
 * any bits, then they benefit from color conversion automatically.
 */
#define PRESERVE_BLACK                  0x0100
#define PRESERVE_DKGRAY                 0x0200
#define PRESERVE_LTGRAY                 0x0400
#define PRESERVE_WHITE                  0x0800

#define PRESERVE_ALL                    (PRESERVE_BLACK | PRESERVE_DKGRAY | PRESERVE_LTGRAY | PRESERVE_WHITE)
#define PRESERVE_NONE                   0   //Backwards compatible

//Structure for UIToolConfigureForDisplay
typedef struct tagTOOLDISPLAYDATA
{
UINT        uDPI;       //Display driver DPI
UINT        cyBar;      //Vertical size for a bar containing buttons.
UINT        cxButton;   //Dimensions of a button.
UINT        cyButton;
UINT        cxImage;    //Dimensions of bitmap image
UINT        cyImage;
UINT        uIDImages;  //Standard resource ID for display-sensitive images
} TOOLDISPLAYDATA, FAR *LPTOOLDISPLAYDATA;

//Public functions in BTTNCUR.DLL
HCURSOR WINAPI UICursorLoad(UINT);
BOOL    WINAPI UIToolConfigureForDisplay(LPTOOLDISPLAYDATA);
BOOL    WINAPI UIToolButtonDraw(HDC, int, int, int, int, HBITMAP, int, int, int, UINT);
BOOL    WINAPI UIToolButtonDrawTDD(HDC, int, int, int, int, HBITMAP, int, int, int, UINT, LPTOOLDISPLAYDATA);

#ifdef __cplusplus
}
#endif

#endif //_BTTNCUR_H_


GIZMOBAR.H

/*
 * GIZMOBAR.H
 * GizmoBar Version 1.01
 *
 * Public definitions for application that use the GizmoBar such as
 * messages, prototypes for API functions, notification codes, and
 * control styles.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _GIZMOBAR_H_
#define _GIZMOBAR_H_

#include <bttncur.h>

#ifdef __cplusplus
extern "C"
    {
#endif

//Classname
#define CLASS_GIZMOBAR  TEXT("gizmobar")

//Message API Functions
HWND    WINAPI GBHwndAssociateSet(HWND, HWND);
HWND    WINAPI GBHwndAssociateGet(HWND);

BOOL    WINAPI GBGizmoAddW(HWND, UINT, UINT, UINT, UINT, UINT, LPWSTR , HBITMAP, UINT, UINT);
BOOL    WINAPI GBGizmoAddA(HWND, UINT, UINT, UINT, UINT, UINT, LPSTR , HBITMAP, UINT, UINT);

int     WINAPI GBGizmoTextGetW(HWND, UINT, LPWSTR, UINT);
int     WINAPI GBGizmoTextGetA(HWND, UINT, LPSTR, UINT);

void    WINAPI GBGizmoTextSetW(HWND, UINT, LPWSTR);
void    WINAPI GBGizmoTextSetA(HWND, UINT, LPSTR);

#ifdef UNICODE
#define GBGizmoAdd      GBGizmoAddW
#define GBGizmoTextGet  GBGizmoTextGetW
#define GBGizmoTextSet  GBGizmoTextSetW
#else
#define GBGizmoAdd      GBGizmoAddA
#define GBGizmoTextGet  GBGizmoTextGetA
#define GBGizmoTextSet  GBGizmoTextSetA
#endif

BOOL    WINAPI GBGizmoRemove(HWND, UINT);

LRESULT WINAPI GBGizmoSendMessage(HWND, UINT, UINT, WPARAM, LPARAM);

BOOL    WINAPI GBGizmoShow(HWND, UINT, BOOL);
BOOL    WINAPI GBGizmoEnable(HWND, UINT, BOOL);
BOOL    WINAPI GBGizmoCheck(HWND, UINT, BOOL);
UINT    WINAPI GBGizmoFocusSet(HWND, UINT);
BOOL    WINAPI GBGizmoExist(HWND, UINT);

int     WINAPI GBGizmoTypeGet(HWND, UINT);

DWORD   WINAPI GBGizmoDataSet(HWND, UINT, DWORD);
DWORD   WINAPI GBGizmoDataGet(HWND, UINT);
BOOL    WINAPI GBGizmoNotifySet(HWND, UINT, BOOL);
BOOL    WINAPI GBGizmoNotifyGet(HWND, UINT);

UINT    WINAPI GBGizmoIntGet(HWND, UINT, BOOL FAR *, BOOL);
void    WINAPI GBGizmoIntSet(HWND, UINT, UINT, BOOL);

//Notification codes sent via WM_COMMAND from GBHwndAssociateSet
#define GBN_ASSOCIATEGAIN               1
#define GBN_ASSOCIATELOSS               2
#define GBN_GIZMOADDED                  3
#define GBN_GIZMOREMOVED                4

//Message equivalents for functions.
#define GBM_HWNDASSOCIATESET            (WM_USER+0)
#define GBM_HWNDASSOCIATEGET            (WM_USER+1)
#define GBM_GIZMOADDW                   (WM_USER+2)
#define GBM_GIZMOADDA                   (WM_USER+3)
#define GBM_GIZMOREMOVE                 (WM_USER+4)
#define GBM_GIZMOSENDMESSAGE            (WM_USER+5)
#define GBM_GIZMOSHOW                   (WM_USER+6)
#define GBM_GIZMOENABLE                 (WM_USER+7)
#define GBM_GIZMOCHECK                  (WM_USER+8)
#define GBM_GIZMOFOCUSSET               (WM_USER+9)
#define GBM_GIZMOEXIST                  (WM_USER+10)
#define GBM_GIZMOTYPEGET                (WM_USER+11)
#define GBM_GIZMODATASET                (WM_USER+12)
#define GBM_GIZMODATAGET                (WM_USER+13)
#define GBM_GIZMONOTIFYSET              (WM_USER+14)
#define GBM_GIZMONOTIFYGET              (WM_USER+15)
#define GBM_GIZMOTEXTGETA               (WM_USER+16)
#define GBM_GIZMOTEXTGETW               (WM_USER+17)
#define GBM_GIZMOTEXTSETA               (WM_USER+18)
#define GBM_GIZMOTEXTSETW               (WM_USER+19)
#define GBM_GIZMOINTGET                 (WM_USER+20)
#define GBM_GIZMOINTSET                 (WM_USER+21)

#ifdef UNICODE
#define GBM_GIZMOTEXTGET GBM_GIZMOTEXTGETW
#define GBM_GIZMOTEXTSET GBM_GIZMOTEXTSETW
#define GBM_GIZMOADD GBM_GIZMOADDW
#else
#define GBM_GIZMOTEXTGET GBM_GIZMOTEXTGETA
#define GBM_GIZMOTEXTSET GBM_GIZMOTEXTSETA
#define GBM_GIZMOADD GBM_GIZMOADDA
#endif

/*
 * Structure passed in lParam of GBM_GIZMOADD that mirrors the
 * parameters to GBGizmoAdd.
 */

typedef struct
    {
    HWND        hWndParent;         //Parent window
    UINT        iType;              //Type of gizmo
    UINT        iGizmo;             //Position to create gizmo
    UINT        uID;                //Identifier of gizmo
    UINT        dx;                 //Dimensions of gizmo
    UINT        dy;
    LPSTR       pszText;            //Gizmo text
    HBITMAP     hBmp;               //Source of gizmo button image.
    UINT        iImage;             //Index of image from hBmp
    UINT        uState;             //Initial state of the gizmo.
    } CREATEGIZMOA, FAR *LPCREATEGIZMOA;
typedef struct
    {
    HWND        hWndParent;         //Parent window
    UINT        iType;              //Type of gizmo
    UINT        iGizmo;             //Position to create gizmo
    UINT        uID;                //Identifier of gizmo
    UINT        dx;                 //Dimensions of gizmo
    UINT        dy;
    LPWSTR      pszText;            //Gizmo text
    HBITMAP     hBmp;               //Source of gizmo button image.
    UINT        iImage;             //Index of image from hBmp
    UINT        uState;             //Initial state of the gizmo.
    } CREATEGIZMOW, FAR *LPCREATEGIZMOW;

#ifdef UNICODE
typedef CREATEGIZMOW    CREATEGIZMO;
typedef LPCREATEGIZMOW  LPCREATEGIZMO;
#define CBCREATEGIZMO sizeof(CREATEGIZMOW)
#else
typedef CREATEGIZMOA    CREATEGIZMO;
typedef LPCREATEGIZMOA  LPCREATEGIZMO;
#define CBCREATEGIZMO sizeof(CREATEGIZMOA)
#endif

//For GBM_GIZMOSENDMESSAGE
typedef struct
    {
    UINT        iMsg;
    WPARAM      wParam;
    LPARAM      lParam;
    } GBMSG, FAR * LPGBMSG;

#define CBGBMSG sizeof(GBMSG);

//For GBM_GIZMOGETTEXT
typedef struct
    {
    LPSTR       psz;
    UINT        cch;
    } GBGETTEXTA, FAR * LPGBGETTEXTA;
typedef struct
    {
    LPWSTR      psz;
    UINT        cch;
    } GBGETTEXTW, FAR * LPGBGETTEXTW;

#ifdef UNICODE
typedef GBGETTEXTW  GBGETTEXT;
typedef LPGBGETTEXTW  LPGBGETTEXT;
#define CBGBGETTEXT sizeof(GBGETTEXTW);
#else
typedef GBGETTEXTA  GBGETTEXT;
typedef LPGBGETTEXTA  LPGBGETTEXT;
#define CBGBGETTEXT sizeof(GBGETTEXTA);
#endif

//For GBM_GIZMOGETINT
typedef struct
    {
    BOOL        fSigned;
    BOOL        fSuccess;
    } GBGETINT, FAR * LPGBGETINT;

#define CBGBGETINT sizeof(GBGETINT);

//For GBM_GIZMOSETINT
typedef struct
    {
    UINT        uValue;
    BOOL        fSigned;
    } GBSETINT, FAR * LPGBSETINT;

#define CBGBSETINT sizeof(GBSETINT);

//Gizmo control types.  DO NOT CHANGE THESE!
#define GIZMOTYPE_EDIT                  0x0001
#define GIZMOTYPE_LISTBOX               0x0002
#define GIZMOTYPE_COMBOBOX              0x0004
#define GIZMOTYPE_BUTTONNORMAL          0x0008
#define GIZMOTYPE_TEXT                  0x0010
#define GIZMOTYPE_SEPARATOR             0x0020
#define GIZMOTYPE_BUTTONATTRIBUTEIN     0x0040
#define GIZMOTYPE_BUTTONATTRIBUTEEX     0x0080
#define GIZMOTYPE_BUTTONCOMMAND         0x0100

//Generic state flags for non-buttons based on BTTNCUR.H's groups.
#define GIZMO_NORMAL                    (BUTTONGROUP_ACTIVE)
#define GIZMO_DISABLED                  (BUTTONGROUP_DISABLED)

#ifdef __cplusplus
    }   //Match with extern "C" above.
#endif

#endif //_GIZMOBAR_H_


MSGFILTR.H

/*************************************************************************
**
**    OLE 2 Utility Code
**
**    msgfiltr.h
**
**    This file contains Private definitions, structures, types, and
**    function prototypes for the OleStdMessageFilter implementation of
**    the IMessageFilter interface.
**    This file is part of the OLE 2.0 User Interface support library.
**
**    (c) Copyright Microsoft Corp. 1990 - 1996 All Rights Reserved
**
*************************************************************************/

#if !defined( _MSGFILTR_H_ )
#define _MSGFILTR_H_

#ifndef RC_INVOKED
#pragma message ("INCLUDING MSGFILTR.H from " __FILE__)
#endif  /* RC_INVOKED */

#include "ansiapi.h"

// Message Pending callback procedure
typedef BOOL (CALLBACK* MSGPENDINGPROC)(MSG FAR *);

// HandleInComingCall callback procedure
typedef DWORD (CALLBACK* HANDLEINCOMINGCALLBACKPROC)
   (
      DWORD               dwCallType,
      HTASK               htaskCaller,
      DWORD               dwTickCount,
      LPINTERFACEINFO     lpInterfaceInfo
   );

/* PUBLIC FUNCTIONS */
STDAPI_(LPMESSAGEFILTER) OleStdMsgFilter_Create(
      HWND hWndParent,
      LPOLESTR szAppName,
      MSGPENDINGPROC lpfnCallback,
      LPFNOLEUIHOOK  lpfnOleUIHook         // Busy dialog hook callback
);

STDAPI_(void) OleStdMsgFilter_SetInComingCallStatus(
      LPMESSAGEFILTER lpThis, DWORD dwInComingCallStatus);

STDAPI_(DWORD) OleStdMsgFilter_GetInComingCallStatus(
      LPMESSAGEFILTER lpThis);

STDAPI_(HANDLEINCOMINGCALLBACKPROC)
   OleStdMsgFilter_SetHandleInComingCallbackProc(
      LPMESSAGEFILTER             lpThis,
      HANDLEINCOMINGCALLBACKPROC  lpfnHandleInComingCallback);

STDAPI_(BOOL) OleStdMsgFilter_EnableBusyDialog(
      LPMESSAGEFILTER lpThis, BOOL fEnable);

STDAPI_(BOOL) OleStdMsgFilter_EnableNotRespondingDialog(
      LPMESSAGEFILTER lpThis, BOOL fEnable);

STDAPI_(HWND) OleStdMsgFilter_SetParentWindow(
      LPMESSAGEFILTER lpThis, HWND hWndParent);

#endif // _MSGFILTR_H_


OLESTD.H

/*************************************************************************
**
**    OLE 2.0 Utilities
**
**    olestd.h
**
**    This file contains file contains data structure defintions,
**    function prototypes, constants, etc. for the common OLE 2.0
**    utilities.
**
**    These utilities include the following:
**          Debuging Assert/Verify macros
**          HIMETRIC conversion routines
**          reference counting debug support
**          OleStd API's for common compound-document app support
**
**    (c) Copyright Microsoft Corp. 1990 - 1996 All Rights Reserved
**
*************************************************************************/

#if !defined( _OLESTD_H_ )
#define _OLESTD_H_

#if !defined(__cplusplus) && !defined( __TURBOC__)
#define NONAMELESSUNION     // use strict ANSI standard (for DVOBJ.H)
#endif

#include <windows.h>
#include <shellapi.h>
#include <ole2.h>
#include <string.h>
#include <dlgs.h>           //For fileopen dlg; standard include
#include "ansiapi.h"
#include "oledlg.h"

/*************************************************************************
** DEBUG ASSERTION ROUTINES
*************************************************************************/

#if DBG
#include <assert.h>
#define FnAssert(lpstrExpr, lpstrMsg, lpstrFileName, iLine)     \
        (_assert(lpstrMsg ? lpstrMsg : lpstrExpr,               \
                 lpstrFileName,                                 \
                 iLine), NOERROR)
#endif //DBG

// BEGINING OF OLD OLESTD.H FILE
#if defined( __TURBOC__ ) || defined( WIN32 )
#define _based(a)
#endif

#ifndef RC_INVOKED
#include <dos.h>        // needed for filetime
#endif  /* RC_INVOKED */

#include <commdlg.h>    // needed for LPPRINTDLG
#include <shellapi.h>   // needed for HKEY
#include "ansiapi.h"

// String table defines...
#define  IDS_OLESTDNOCREATEFILE   700
#define  IDS_OLESTDNOOPENFILE     701
#define  IDS_OLESTDDISKFULL       702

/*
 * Some C interface declaration stuff
 */

#if ! defined(__cplusplus)
typedef struct tagINTERFACEIMPL {
      IUnknownVtbl FAR*       lpVtbl;
      LPVOID                  lpBack;
      int                     cRef;   // interface specific ref count.
} INTERFACEIMPL, FAR* LPINTERFACEIMPL;

#define INIT_INTERFACEIMPL(lpIFace, pVtbl, pBack)   \
      ((lpIFace)->lpVtbl = pVtbl, \
         ((LPINTERFACEIMPL)(lpIFace))->lpBack = (LPVOID)pBack,   \
         ((LPINTERFACEIMPL)(lpIFace))->cRef = 0  \
      )

#if defined( _DEBUG )
#define OleDbgQueryInterfaceMethod(lpUnk)   \
      ((lpUnk) != NULL ? ((LPINTERFACEIMPL)(lpUnk))->cRef++ : 0)
#define OleDbgAddRefMethod(lpThis, iface)   \
      ((LPINTERFACEIMPL)(lpThis))->cRef++

#if _DEBUGLEVEL >= 2
#define OleDbgReleaseMethod(lpThis, iface) \
      (--((LPINTERFACEIMPL)(lpThis))->cRef == 0 ? \
         OleDbgOut("\t" iface "* RELEASED (cRef == 0)\r\n"),1 : \
          (((LPINTERFACEIMPL)(lpThis))->cRef < 0) ? \
            ( \
               DebugBreak(), \
               OleDbgOut(  \
                  "\tERROR: " iface "* RELEASED TOO MANY TIMES\r\n") \
            ),1 : \
            1)

#else       // if _DEBUGLEVEL < 2
#define OleDbgReleaseMethod(lpThis, iface) \
      (--((LPINTERFACEIMPL)(lpThis))->cRef == 0 ? \
         1 : \
          (((LPINTERFACEIMPL)(lpThis))->cRef < 0) ? \
            ( \
               OleDbgOut(  \
                  "\tERROR: " iface "* RELEASED TOO MANY TIMES\r\n") \
      ),1 : \
            1)

#endif      // if _DEBUGLEVEL < 2

#else       // ! defined (_DEBUG)

#define OleDbgQueryInterfaceMethod(lpUnk)
#define OleDbgAddRefMethod(lpThis, iface)
#define OleDbgReleaseMethod(lpThis, iface)

#endif      // if defined( _DEBUG )

#endif      // ! defined(__cplusplus)

/*
 * Some docfiles stuff
 */

#define STGM_DFRALL (STGM_READWRITE | STGM_TRANSACTED | STGM_SHARE_DENY_WRITE)
#define STGM_DFALL (STGM_READWRITE | STGM_TRANSACTED | STGM_SHARE_EXCLUSIVE)
#define STGM_SALL (STGM_READWRITE | STGM_SHARE_EXCLUSIVE)

/*
 * Some Concurrency stuff
 */

/* standard Delay (in msec) to wait before retrying an LRPC call.
**    this value is returned from IMessageFilter::RetryRejectedCall
*/
#define OLESTDRETRYDELAY    (DWORD)5000

/* Cancel the pending outgoing LRPC call.
**    this value is returned from IMessageFilter::RetryRejectedCall
*/
#define OLESTDCANCELRETRY   (DWORD)-1

/*
 * Some Icon support stuff.
 *
 * The following API's are now OBSOLETE because equivalent API's have been
 * added to the OLE2.DLL library
 *      GetIconOfFile       superceeded by OleGetIconOfFile
 *      GetIconOfClass      superceeded by OleGetIconOfClass
 *      OleUIMetafilePictFromIconAndLabel
 *                          superceeded by OleMetafilePictFromIconAndLabel
 *
 * The following macros are defined for backward compatibility with previous
 * versions of the OLE2UI library. It is recommended that the new Ole* API's
 * should be used instead.
 */
#define GetIconOfFile(hInst, lpszFileName, fUseFileAsLabel) \
   OleGetIconOfFile(lpszFileName, fUseFileAsLabel)

#define GetIconOfClass(hInst, rclsid, lpszLabel, fUseTypeAsLabel) \
   OleGetIconOfClass(rclsid, lpszLabel, fUseTypeAsLabel)

#define OleUIMetafilePictFromIconAndLabel(hIcon,pszLabel,pszSourceFile,iIcon)\
   OleMetafilePictFromIconAndLabel(hIcon, pszLabel, pszSourceFile, iIcon)

/*
 * Some Clipboard Copy/Paste & Drag/Drop support stuff
 */

//Macro to set all FormatEtc fields
#define SETFORMATETC(fe, cf, asp, td, med, li)   \
   ((fe).cfFormat=cf, \
    (fe).dwAspect=asp, \
    (fe).ptd=td, \
    (fe).tymed=med, \
    (fe).lindex=li)

//Macro to set interesting FormatEtc fields defaulting the others.
#define SETDEFAULTFORMATETC(fe, cf, med)  \
   ((fe).cfFormat=cf, \
    (fe).dwAspect=DVASPECT_CONTENT, \
    (fe).ptd=NULL, \
    (fe).tymed=med, \
    (fe).lindex=-1)

// Macro to test if two FormatEtc structures are an exact match
#define IsEqualFORMATETC(fe1, fe2)  \
   (OleStdCompareFormatEtc(&(fe1), &(fe2))==0)

// Clipboard format strings
#define CF_EMBEDSOURCE      TEXT("Embed Source")
#define CF_EMBEDDEDOBJECT   TEXT("Embedded Object")
#define CF_LINKSOURCE       TEXT("Link Source")
#define CF_CUSTOMLINKSOURCE TEXT("Custom Link Source")
#define CF_OBJECTDESCRIPTOR TEXT("Object Descriptor")
#define CF_LINKSRCDESCRIPTOR TEXT("Link Source Descriptor")
#define CF_OWNERLINK        TEXT("OwnerLink")
#define CF_FILENAME         TEXT("FileName")

#define OleStdQueryOleObjectData(lpformatetc)   \
   (((lpformatetc)->tymed & TYMED_ISTORAGE) ?    \
         NOERROR : ResultFromScode(DV_E_FORMATETC))

#define OleStdQueryLinkSourceData(lpformatetc)   \
   (((lpformatetc)->tymed & TYMED_ISTREAM) ?    \
         NOERROR : ResultFromScode(DV_E_FORMATETC))

#define OleStdQueryObjectDescriptorData(lpformatetc)    \
   (((lpformatetc)->tymed & TYMED_HGLOBAL) ?    \
         NOERROR : ResultFromScode(DV_E_FORMATETC))

#define OleStdQueryFormatMedium(lpformatetc, tymd)  \
   (((lpformatetc)->tymed & tymd) ?    \
         NOERROR : ResultFromScode(DV_E_FORMATETC))

// Make an independent copy of a MetafilePict
#define OleStdCopyMetafilePict(hpictin, phpictout)  \
   (*(phpictout) = OleDuplicateData(hpictin,CF_METAFILEPICT,GHND|GMEM_SHARE))

// REVIEW: these need to be added to OLE2.H
#if !defined( DD_DEFSCROLLINTERVAL )
#define DD_DEFSCROLLINTERVAL    50
#endif

#if !defined( DD_DEFDRAGDELAY )
#define DD_DEFDRAGDELAY         200
#endif

#if !defined( DD_DEFDRAGMINDIST )
#define DD_DEFDRAGMINDIST       2
#endif

/* OleStdGetDropEffect
** -------------------
**
** Convert a keyboard state into a DROPEFFECT.
**
** returns the DROPEFFECT value derived from the key state.
**    the following is the standard interpretation:
**          no modifier -- Default Drop     (NULL is returned)
**          CTRL        -- DROPEFFECT_COPY
**          SHIFT       -- DROPEFFECT_MOVE
**          CTRL-SHIFT  -- DROPEFFECT_LINK
**
**    Default Drop: this depends on the type of the target application.
**    this is re-interpretable by each target application. a typical
**    interpretation is if the drag is local to the same document
**    (which is source of the drag) then a MOVE operation is
**    performed. if the drag is not local, then a COPY operation is
**    performed.
*/
#define OleStdGetDropEffect(grfKeyState)    \
   ( (grfKeyState & MK_CONTROL) ?          \
      ( (grfKeyState & MK_SHIFT) ? DROPEFFECT_LINK : DROPEFFECT_COPY ) :  \
      ( (grfKeyState & MK_SHIFT) ? DROPEFFECT_MOVE : 0 ) )

#define OLESTDDROP_NONE         0
#define OLESTDDROP_DEFAULT      1
#define OLESTDDROP_NONDEFAULT   2

/*
 * Some misc stuff
 */

#define EMBEDDINGFLAG "Embedding"     // Cmd line switch for launching a srvr

#define HIMETRIC_PER_INCH   2540      // number HIMETRIC units per inch
#define PTS_PER_INCH        72        // number points (font size) per inch

#define MAP_PIX_TO_LOGHIM(x,ppli)   MulDiv(HIMETRIC_PER_INCH, (x), (ppli))
#define MAP_LOGHIM_TO_PIX(x,ppli)   MulDiv((ppli), (x), HIMETRIC_PER_INCH)

// Returns TRUE if all fields of the two Rect's are equal, else FALSE.
#define AreRectsEqual(lprc1, lprc2)     \
   (((lprc1->top == lprc2->top) &&     \
     (lprc1->left == lprc2->left) &&   \
     (lprc1->right == lprc2->right) && \
     (lprc1->bottom == lprc2->bottom)) ? TRUE : FALSE)

#define LSTRCPYN(lpdst, lpsrc, cch) \
(\
   (lpdst)[(cch)-1] = '\0', \
   ((cch)>1 ? _fstrncpy(lpdst, lpsrc, (cch)-1) : 0)\
)

/****** DEBUG Stuff *****************************************************/

#ifdef _DEBUG

#if !defined( _DBGTRACE )
#define _DEBUGLEVEL 2
#else
#define _DEBUGLEVEL _DBGTRACE
#endif

#if defined( NOASSERT )

#define OLEDBGASSERTDATA
#define OleDbgAssert(a)
#define OleDbgAssertSz(a, b)
#define OleDbgVerify(a)
#define OleDbgVerifySz(a, b)

#else   // ! NOASSERT

#define OLEDBGASSERTDATA    \
      static char _based(_segname("_CODE")) _szAssertFile[]= __FILE__;

#define OleDbgAssert(a) \
      (!(a) ? FnAssert(#a, NULL, _szAssertFile, __LINE__) : (HRESULT)1)

#define OleDbgAssertSz(a, b)    \
      (!(a) ? FnAssert(#a, b, _szAssertFile, __LINE__) : (HRESULT)1)

#define OleDbgVerify(a) \
      OleDbgAssert(a)

#define OleDbgVerifySz(a, b)    \
      OleDbgAssertSz(a, b)

#endif  // ! NOASSERT

#define OLEDBGDATA_MAIN(szPrefix)   \
      char near g_szDbgPrefix[] = szPrefix;    \
      OLEDBGASSERTDATA
#define OLEDBGDATA  \
      extern char near g_szDbgPrefix[];    \
      OLEDBGASSERTDATA

#define OLEDBG_BEGIN(lpsz) \
      OleDbgPrintAlways(g_szDbgPrefix,lpsz,1);

#define OLEDBG_END  \
      OleDbgPrintAlways(g_szDbgPrefix,"End\r\n",-1);

#define OleDbgOut(lpsz) \
      OleDbgPrintAlways(g_szDbgPrefix,lpsz,0)

#define OleDbgOutNoPrefix(lpsz) \
      OleDbgPrintAlways("",lpsz,0)

#define OleDbgOutRefCnt(lpsz,lpObj,refcnt)      \
      OleDbgPrintRefCntAlways(g_szDbgPrefix,lpsz,lpObj,(ULONG)refcnt)

#define OleDbgOutRect(lpsz,lpRect)      \
      OleDbgPrintRectAlways(g_szDbgPrefix,lpsz,lpRect)

#define OleDbgOutHResult(lpsz,hr)   \
      OleDbgPrintScodeAlways(g_szDbgPrefix,lpsz,GetScode(hr))

#define OleDbgOutScode(lpsz,sc) \
      OleDbgPrintScodeAlways(g_szDbgPrefix,lpsz,sc)

#define OleDbgOut1(lpsz)    \
      OleDbgPrint(1,g_szDbgPrefix,lpsz,0)

#define OleDbgOutNoPrefix1(lpsz)    \
      OleDbgPrint(1,"",lpsz,0)

#define OLEDBG_BEGIN1(lpsz)    \
      OleDbgPrint(1,g_szDbgPrefix,lpsz,1);

#define OLEDBG_END1 \
      OleDbgPrint(1,g_szDbgPrefix,"End\r\n",-1);

#define OleDbgOutRefCnt1(lpsz,lpObj,refcnt)     \
      OleDbgPrintRefCnt(1,g_szDbgPrefix,lpsz,lpObj,(ULONG)refcnt)

#define OleDbgOutRect1(lpsz,lpRect)     \
      OleDbgPrintRect(1,g_szDbgPrefix,lpsz,lpRect)

#define OleDbgOut2(lpsz)    \
      OleDbgPrint(2,g_szDbgPrefix,lpsz,0)

#define OleDbgOutNoPrefix2(lpsz)    \
      OleDbgPrint(2,"",lpsz,0)

#define OLEDBG_BEGIN2(lpsz)    \
      OleDbgPrint(2,g_szDbgPrefix,lpsz,1);

#define OLEDBG_END2 \
      OleDbgPrint(2,g_szDbgPrefix,"End\r\n",-1);

#define OleDbgOutRefCnt2(lpsz,lpObj,refcnt)     \
      OleDbgPrintRefCnt(2,g_szDbgPrefix,lpsz,lpObj,(ULONG)refcnt)

#define OleDbgOutRect2(lpsz,lpRect)     \
      OleDbgPrintRect(2,g_szDbgPrefix,lpsz,lpRect)

#define OleDbgOut3(lpsz)    \
      OleDbgPrint(3,g_szDbgPrefix,lpsz,0)

#define OleDbgOutNoPrefix3(lpsz)    \
      OleDbgPrint(3,"",lpsz,0)

#define OLEDBG_BEGIN3(lpsz)    \
      OleDbgPrint(3,g_szDbgPrefix,lpsz,1);

#define OLEDBG_END3 \
      OleDbgPrint(3,g_szDbgPrefix,"End\r\n",-1);

#define OleDbgOutRefCnt3(lpsz,lpObj,refcnt)     \
      OleDbgPrintRefCnt(3,g_szDbgPrefix,lpsz,lpObj,(ULONG)refcnt)

#define OleDbgOutRect3(lpsz,lpRect)     \
      OleDbgPrintRect(3,g_szDbgPrefix,lpsz,lpRect)

#define OleDbgOut4(lpsz)    \
      OleDbgPrint(4,g_szDbgPrefix,lpsz,0)

#define OleDbgOutNoPrefix4(lpsz)    \
      OleDbgPrint(4,"",lpsz,0)

#define OLEDBG_BEGIN4(lpsz)    \
      OleDbgPrint(4,g_szDbgPrefix,lpsz,1);

#define OLEDBG_END4 \
      OleDbgPrint(4,g_szDbgPrefix,"End\r\n",-1);

#define OleDbgOutRefCnt4(lpsz,lpObj,refcnt)     \
      OleDbgPrintRefCnt(4,g_szDbgPrefix,lpsz,lpObj,(ULONG)refcnt)

#define OleDbgOutRect4(lpsz,lpRect)     \
      OleDbgPrintRect(4,g_szDbgPrefix,lpsz,lpRect)

#else   //  !_DEBUG

#define OLEDBGDATA_MAIN(szPrefix)
#define OLEDBGDATA
#define OleDbgAssert(a)
#define OleDbgAssertSz(a, b)
#define OleDbgVerify(a)         (a)
#define OleDbgVerifySz(a, b)    (a)
#define OleDbgOutHResult(lpsz,hr)
#define OleDbgOutScode(lpsz,sc)
#define OLEDBG_BEGIN(lpsz)
#define OLEDBG_END
#define OleDbgOut(lpsz)
#define OleDbgOut1(lpsz)
#define OleDbgOut2(lpsz)
#define OleDbgOut3(lpsz)
#define OleDbgOut4(lpsz)
#define OleDbgOutNoPrefix(lpsz)
#define OleDbgOutNoPrefix1(lpsz)
#define OleDbgOutNoPrefix2(lpsz)
#define OleDbgOutNoPrefix3(lpsz)
#define OleDbgOutNoPrefix4(lpsz)
#define OLEDBG_BEGIN1(lpsz)
#define OLEDBG_BEGIN2(lpsz)
#define OLEDBG_BEGIN3(lpsz)
#define OLEDBG_BEGIN4(lpsz)
#define OLEDBG_END1
#define OLEDBG_END2
#define OLEDBG_END3
#define OLEDBG_END4
#define OleDbgOutRefCnt(lpsz,lpObj,refcnt)
#define OleDbgOutRefCnt1(lpsz,lpObj,refcnt)
#define OleDbgOutRefCnt2(lpsz,lpObj,refcnt)
#define OleDbgOutRefCnt3(lpsz,lpObj,refcnt)
#define OleDbgOutRefCnt4(lpsz,lpObj,refcnt)
#define OleDbgOutRect(lpsz,lpRect)
#define OleDbgOutRect1(lpsz,lpRect)
#define OleDbgOutRect2(lpsz,lpRect)
#define OleDbgOutRect3(lpsz,lpRect)
#define OleDbgOutRect4(lpsz,lpRect)

#endif  //  _DEBUG

/*************************************************************************
** Function prototypes
*************************************************************************/

//OLESTD.C
STDAPI_(int) SetDCToAnisotropic(HDC hDC, LPRECT lprcPhysical, LPRECT lprcLogical, LPRECT lprcWindowOld, LPRECT lprcViewportOld);
STDAPI_(int) SetDCToDrawInHimetricRect(HDC, LPRECT, LPRECT, LPRECT, LPRECT);
STDAPI_(int) ResetOrigDC(HDC, int, LPRECT, LPRECT);

STDAPI_(int)        XformWidthInHimetricToPixels(HDC, int);
STDAPI_(int)        XformWidthInPixelsToHimetric(HDC, int);
STDAPI_(int)        XformHeightInHimetricToPixels(HDC, int);
STDAPI_(int)        XformHeightInPixelsToHimetric(HDC, int);

STDAPI_(void) XformRectInPixelsToHimetric(HDC, LPRECT, LPRECT);
STDAPI_(void) XformRectInHimetricToPixels(HDC, LPRECT, LPRECT);
STDAPI_(void) XformSizeInPixelsToHimetric(HDC, LPSIZEL, LPSIZEL);
STDAPI_(void) XformSizeInHimetricToPixels(HDC, LPSIZEL, LPSIZEL);
STDAPI_(int) XformWidthInHimetricToPixels(HDC, int);
STDAPI_(int) XformWidthInPixelsToHimetric(HDC, int);
STDAPI_(int) XformHeightInHimetricToPixels(HDC, int);
STDAPI_(int) XformHeightInPixelsToHimetric(HDC, int);

STDAPI_(void) ParseCmdLine(LPSTR, BOOL FAR *, LPSTR);

STDAPI_(BOOL) OleStdIsOleLink(LPUNKNOWN lpUnk);
STDAPI_(LPUNKNOWN) OleStdQueryInterface(LPUNKNOWN lpUnk, REFIID riid);
STDAPI_(LPSTORAGE) OleStdCreateRootStorage(LPOLESTR lpszStgName, DWORD grfMode);
STDAPI_(LPSTORAGE) OleStdOpenRootStorage(LPOLESTR lpszStgName, DWORD grfMode);
STDAPI_(LPSTORAGE) OleStdOpenOrCreateRootStorage(LPOLESTR lpszStgName, DWORD grfMode);
STDAPI_(LPSTORAGE) OleStdCreateChildStorage(LPSTORAGE lpStg, LPOLESTR lpszStgName);
STDAPI_(LPSTORAGE) OleStdOpenChildStorage(LPSTORAGE lpStg, LPOLESTR lpszStgName, DWORD grfMode);
STDAPI_(BOOL) OleStdCommitStorage(LPSTORAGE lpStg);
STDAPI OleStdDestroyAllElements(LPSTORAGE lpStg);

STDAPI_(LPSTORAGE) OleStdCreateStorageOnHGlobal(
      HANDLE hGlobal,
      BOOL fDeleteOnRelease,
      DWORD dwgrfMode
);
STDAPI_(LPSTORAGE) OleStdCreateTempStorage(BOOL fUseMemory, DWORD grfMode);
STDAPI OleStdDoConvert(LPSTORAGE lpStg, REFCLSID rClsidNew);
STDAPI_(BOOL) OleStdGetTreatAsFmtUserType(
      REFCLSID        rClsidApp,
      LPSTORAGE       lpStg,
      CLSID FAR*      lpclsid,
      CLIPFORMAT FAR* lpcfFmt,
      LPOLESTR FAR*      lplpszType
);
STDAPI OleStdDoTreatAsClass(LPOLESTR lpszUserType, REFCLSID rclsid, REFCLSID rclsidNew);
STDAPI_(BOOL) OleStdSetupAdvises(LPOLEOBJECT lpOleObject, DWORD dwDrawAspect,
               LPOLESTR lpszContainerApp, LPOLESTR lpszContainerObj,
               LPADVISESINK lpAdviseSink, BOOL fCreate);
STDAPI OleStdSwitchDisplayAspect(
      LPOLEOBJECT             lpOleObj,
      LPDWORD                 lpdwCurAspect,
      DWORD                   dwNewAspect,
      HGLOBAL                 hMetaPict,
      BOOL                    fDeleteOldAspect,
      BOOL                    fSetupViewAdvise,
      LPADVISESINK            lpAdviseSink,
      BOOL FAR*               lpfMustUpdate
);
STDAPI OleStdSetIconInCache(LPOLEOBJECT lpOleObj, HGLOBAL hMetaPict);
STDAPI_(HGLOBAL) OleStdGetData(
      LPDATAOBJECT        lpDataObj,
      CLIPFORMAT          cfFormat,
      DVTARGETDEVICE FAR* lpTargetDevice,
      DWORD               dwAspect,
      LPSTGMEDIUM         lpMedium
);
STDAPI_(void) OleStdMarkPasteEntryList(
      LPDATAOBJECT        lpSrcDataObj,
      LPOLEUIPASTEENTRY   lpPriorityList,
      int                 cEntries
);
STDAPI_(int) OleStdGetPriorityClipboardFormat(
      LPDATAOBJECT        lpSrcDataObj,
      LPOLEUIPASTEENTRY   lpPriorityList,
      int                 cEntries
);
STDAPI_(BOOL) OleStdIsDuplicateFormat(
      LPFORMATETC         lpFmtEtc,
      LPFORMATETC         arrFmtEtc,
      int                 nFmtEtc
);
STDAPI_(void) OleStdRegisterAsRunning(LPUNKNOWN lpUnk, LPMONIKER lpmkFull, DWORD FAR* lpdwRegister);
STDAPI_(void) OleStdRevokeAsRunning(DWORD FAR* lpdwRegister);
STDAPI_(void) OleStdNoteFileChangeTime(LPOLESTR lpszFileName, DWORD dwRegister);
STDAPI_(void) OleStdNoteObjectChangeTime(DWORD dwRegister);
STDAPI OleStdGetOleObjectData(
      LPPERSISTSTORAGE    lpPStg,
      LPFORMATETC         lpformatetc,
      LPSTGMEDIUM         lpMedium,
      BOOL                fUseMemory
);
STDAPI OleStdGetLinkSourceData(
      LPMONIKER           lpmk,
      LPCLSID             lpClsID,
      LPFORMATETC         lpformatetc,
      LPSTGMEDIUM         lpMedium
);
STDAPI_(HGLOBAL) OleStdGetObjectDescriptorData(
      CLSID               clsid,
      DWORD               dwAspect,
      SIZEL               sizel,
      POINTL              pointl,
      DWORD               dwStatus,
      LPOLESTR            lpszFullUserTypeName,
      LPOLESTR            lpszSrcOfCopy
);
STDAPI_(HGLOBAL) OleStdGetObjectDescriptorDataFromOleObject(
      LPOLEOBJECT         lpOleObj,
      LPOLESTR            lpszSrcOfCopy,
      DWORD               dwAspect,
      POINTL              pointl,
      LPSIZEL             lpSizelHim
);
STDAPI_(HGLOBAL) OleStdFillObjectDescriptorFromData(
      LPDATAOBJECT       lpDataObject,
      LPSTGMEDIUM        lpmedium,
      CLIPFORMAT FAR*    lpcfFmt
);
STDAPI_(HANDLE) OleStdGetMetafilePictFromOleObject(
      LPOLEOBJECT         lpOleObj,
      DWORD               dwDrawAspect,
      LPSIZEL             lpSizelHim,
      DVTARGETDEVICE FAR* ptd
);

STDAPI_(void) OleStdCreateTempFileMoniker(LPOLESTR lpszPrefixString, UINT FAR* lpuUnique, LPOLESTR lpszName, LPMONIKER FAR* lplpmk);
STDAPI_(LPMONIKER) OleStdGetFirstMoniker(LPMONIKER lpmk);
STDAPI_(ULONG) OleStdGetLenFilePrefixOfMoniker(LPMONIKER lpmk);
STDAPI OleStdMkParseDisplayName(
      REFCLSID        rClsid,
      LPBC            lpbc,
      LPOLESTR        lpszUserName,
      ULONG FAR*      lpchEaten,
      LPMONIKER FAR*  lplpmk
);
STDAPI_(LPVOID) OleStdMalloc(ULONG ulSize);
STDAPI_(LPVOID) OleStdRealloc(LPVOID pmem, ULONG ulSize);
STDAPI_(void) OleStdFree(LPVOID pmem);
STDAPI_(ULONG) OleStdGetSize(LPVOID pmem);
STDAPI_(void) OleStdFreeString(LPOLESTR lpsz, LPMALLOC lpMalloc);
STDAPI_(LPOLESTR) OleStdCopyString(LPOLESTR lpszSrc, LPMALLOC lpMalloc);
STDAPI_(ULONG) OleStdGetItemToken(LPOLESTR lpszSrc, LPOLESTR lpszDst,int nMaxChars);

STDAPI_(UINT)     OleStdIconLabelTextOut(HDC        hDC,
                               HFONT      hFont,
                               int        nXStart,
                               int        nYStart,
                               UINT       fuOptions,
                               RECT FAR * lpRect,
                               LPOLESTR   lpszString,
                               UINT       cchString,
                               int FAR *  lpDX);

// registration database query functions
STDAPI_(UINT)     OleStdGetAuxUserType(REFCLSID rclsid,
                             WORD   wAuxUserType,
                             LPOLESTR  lpszAuxUserType,
                             int    cch,
                             HKEY   hKey);

STDAPI_(UINT)     OleStdGetUserTypeOfClass(REFCLSID rclsid,
                                 LPOLESTR lpszUserType,
                                 UINT cch,
                                 HKEY hKey);

STDAPI_(BOOL) OleStdGetMiscStatusOfClass(REFCLSID, HKEY, DWORD FAR *);
STDAPI_(CLIPFORMAT) OleStdGetDefaultFileFormatOfClass(
      REFCLSID        rclsid,
      HKEY            hKey
);

STDAPI_(void) OleStdInitVtbl(LPVOID lpVtbl, UINT nSizeOfVtbl);
STDMETHODIMP OleStdNullMethod(LPUNKNOWN lpThis);
STDAPI_(BOOL) OleStdCheckVtbl(LPVOID lpVtbl, UINT nSizeOfVtbl, LPOLESTR lpszIface);
STDAPI_(ULONG) OleStdVerifyRelease(LPUNKNOWN lpUnk, LPOLESTR lpszMsg);
STDAPI_(ULONG) OleStdRelease(LPUNKNOWN lpUnk);

STDAPI_(HDC) OleStdCreateDC(DVTARGETDEVICE FAR* ptd);
STDAPI_(HDC) OleStdCreateIC(DVTARGETDEVICE FAR* ptd);
STDAPI_(DVTARGETDEVICE FAR*) OleStdCreateTargetDevice(LPPRINTDLG lpPrintDlg);
STDAPI_(BOOL) OleStdDeleteTargetDevice(DVTARGETDEVICE FAR* ptd);
STDAPI_(DVTARGETDEVICE FAR*) OleStdCopyTargetDevice(DVTARGETDEVICE FAR* ptdSrc);
STDAPI_(BOOL) OleStdCopyFormatEtc(LPFORMATETC petcDest, LPFORMATETC petcSrc);
STDAPI_(int) OleStdCompareFormatEtc(FORMATETC FAR* pFetcLeft, FORMATETC FAR* pFetcRight);
STDAPI_(BOOL) OleStdCompareTargetDevice
   (DVTARGETDEVICE FAR* ptdLeft, DVTARGETDEVICE FAR* ptdRight);

STDAPI_(void) OleDbgPrint(
      int     nDbgLvl,
      LPSTR   lpszPrefix,
      LPSTR   lpszMsg,
      int     nIndent
);
STDAPI_(void) OleDbgPrintAlways(LPSTR lpszPrefix, LPSTR lpszMsg, int nIndent);
STDAPI_(void) OleDbgSetDbgLevel(int nDbgLvl);
STDAPI_(int) OleDbgGetDbgLevel( void );
STDAPI_(void) OleDbgIndent(int n);
STDAPI_(void) OleDbgPrintRefCnt(
      int         nDbgLvl,
      LPSTR       lpszPrefix,
      LPSTR       lpszMsg,
      LPVOID      lpObj,
      ULONG       refcnt
);
STDAPI_(void) OleDbgPrintRefCntAlways(
      LPSTR       lpszPrefix,
      LPSTR       lpszMsg,
      LPVOID      lpObj,
      ULONG       refcnt
);
STDAPI_(void) OleDbgPrintRect(
      int         nDbgLvl,
      LPSTR       lpszPrefix,
      LPSTR       lpszMsg,
      LPRECT      lpRect
);
STDAPI_(void) OleDbgPrintRectAlways(
      LPSTR       lpszPrefix,
      LPSTR       lpszMsg,
      LPRECT      lpRect
);
STDAPI_(void) OleDbgPrintScodeAlways(LPSTR lpszPrefix, LPSTR lpszMsg, SCODE sc);

STDAPI_(LPENUMFORMATETC)
  OleStdEnumFmtEtc_Create(ULONG nCount, LPFORMATETC lpEtc);

STDAPI_(LPENUMSTATDATA)
  OleStdEnumStatData_Create(ULONG nCount, LPSTATDATA lpStat);

STDAPI_(BOOL)
  OleStdCopyStatData(LPSTATDATA pDest, LPSTATDATA pSrc);

STDAPI_(HPALETTE)
  OleStdCreateStandardPalette(void);

#if defined( OBSOLETE )

/*************************************************************************
** The following API's have been converted into macros:
**          OleStdQueryOleObjectData
**          OleStdQueryLinkSourceData
**          OleStdQueryObjectDescriptorData
**          OleStdQueryFormatMedium
**          OleStdCopyMetafilePict
**          AreRectsEqual
**          OleStdGetDropEffect
**
**    These macros are defined above
*************************************************************************/
STDAPI_(BOOL) AreRectsEqual(LPRECT lprc1, LPRECT lprc2);
STDAPI_(BOOL) OleStdCopyMetafilePict(HANDLE hpictin, HANDLE FAR* phpictout);
STDAPI OleStdQueryOleObjectData(LPFORMATETC lpformatetc);
STDAPI OleStdQueryLinkSourceData(LPFORMATETC lpformatetc);
STDAPI OleStdQueryObjectDescriptorData(LPFORMATETC lpformatetc);
STDAPI OleStdQueryFormatMedium(LPFORMATETC lpformatetc, TYMED tymed);
STDAPI_(DWORD) OleStdGetDropEffect ( DWORD grfKeyState );
#endif  // OBSOLETE

// END OF OLD OLESTD.H FILE

#define UPDATELINKS_STARTDELAY 2000 // delay before first link

#ifdef __TURBOC__
#define _getcwd getcwd
#define _itoa   itoa
#define __max   max
#define _find_t find_t
#endif // __TURBOC__

#ifdef WIN32
#define _fmemset memset
#define _fmemcpy memcpy
#define _fmemcmp memcmp
#define _fstrcpy strcpy
#define _fstrncpy strncpy
#define _fstrlen strlen
#define _fstrrchr strrchr
#define _fstrtok strtok

// BUGBUG32: isspace function does not seem to work properly
#undef isspace
#define isspace(j) (j==' ' || j=='\t' || j=='\n')
#endif  // WIN32

#if !defined( EXPORT )
#ifdef WIN32
#define EXPORT
#else
#define EXPORT  __export
#endif  // WIN32
#endif  // !EXPORT

/*
 * Initialization / Uninitialization routines.  OleStdInitialize
 * must be called prior to using any functions in OLESTD.LIB.
 */

STDAPI_(BOOL) OleStdInitialize(HINSTANCE hInstance);
STDAPI_(void) OleStdUninitialize(void);

// object count, used to support DllCanUnloadNow and OleUICanUnloadNow
extern DWORD g_dwObjectCount;

STDAPI OleUICanUnloadNow(void);
STDAPI OleUILockLibrary(BOOL fLock);

//Dialog Identifiers as passed in Help messages to identify the source.
#define IDD_FILEOPEN            32253

// The following Dialogs are message dialogs used by OleUIPromptUser API

// Stringtable identifers
#define IDS_OLE2UIUNKNOWN       32300
#define IDS_OLE2UILINK          32301
#define IDS_OLE2UIOBJECT        32302
#define IDS_OLE2UIEDIT          32303
#define IDS_OLE2UICONVERT       32304
#define IDS_OLE2UIEDITLINKCMD_1VERB     32305
#define IDS_OLE2UIEDITOBJECTCMD_1VERB   32306
#define IDS_OLE2UIEDITLINKCMD_NVERB     32307
#define IDS_OLE2UIEDITOBJECTCMD_NVERB   32308
#define IDS_OLE2UIEDITNOOBJCMD  32309
// def. icon label (usu. "Document")
#define IDS_DEFICONLABEL        32310
#define IDS_OLE2UIPASTELINKEDTYPE  32311

#define IDS_FILTERS             32320
#define IDS_ICONFILTERS         32321
#define IDS_BROWSE              32322

//Resource identifiers for bitmaps
#define IDB_RESULTSEGA                  32325
#define IDB_RESULTSVGA                  32326
#define IDB_RESULTSHIRESVGA             32327

//Missing from windows.h
#ifndef PVOID
typedef VOID *PVOID;
#endif

//Hook type used in all structures.
typedef UINT (CALLBACK *LPFNOLEUIHOOK)(HWND, UINT, WPARAM, LPARAM);

//Strings for registered messages
#define SZOLEUI_MSG_FILEOKSTRING        "OLEUI_MSG_FILEOKSTRING"

//Help Button Identifier
#define ID_OLEUIHELP                    99

// Static text control (use this instead of -1 so things work correctly for
// localization
#define  ID_STATIC                      98

//Maximum key size we read from the RegDB.
#define OLEUI_CCHKEYMAX                 256  // make any changes to this in geticon.c too

//Maximum verb length and length of Object menu
#define OLEUI_CCHVERBMAX                32
#define OLEUI_OBJECTMENUMAX             256

//Maximum MS-DOS pathname.
#define OLEUI_CCHPATHMAX                256 // make any changes to this in geticon.c too
#define OLEUI_CCHFILEMAX                13

//Icon label length
#define OLEUI_CCHLABELMAX               40  // make any changes to this in geticon.c too

//Length of the CLSID string
#define OLEUI_CCHCLSIDSTRING            39

//Metafile utility functions
WINOLEAPI_(HGLOBAL) OleUIMetafilePictFromIconAndLabel(HICON, LPOLESTR, LPOLESTR, UINT);
STDAPI_(void)    OleUIMetafilePictIconFree(HGLOBAL);
STDAPI_(BOOL)    OleUIMetafilePictIconDraw(HDC, LPRECT, HGLOBAL, BOOL);
STDAPI_(UINT)    OleUIMetafilePictExtractLabel(HGLOBAL, LPOLESTR, UINT, LPDWORD);

STDAPI_(HICON)   OleUIMetafilePictExtractIcon(HGLOBAL);
STDAPI_(BOOL)    OleUIMetafilePictExtractIconSource(HGLOBAL,LPOLESTR,UINT FAR *);

/*************************************************************************
** INSERT OBJECT DIALOG
*************************************************************************/

//Insert Object Dialog identifiers
#define ID_IO_CREATENEW                 2100
#define ID_IO_CREATEFROMFILE            2101
#define ID_IO_LINKFILE                  2102
#define ID_IO_OBJECTTYPELIST            2103
#define ID_IO_DISPLAYASICON             2104
#define ID_IO_CHANGEICON                2105
#define ID_IO_FILE                      2106
#define ID_IO_FILEDISPLAY               2107
#define ID_IO_RESULTIMAGE               2108
#define ID_IO_RESULTTEXT                2109
#define ID_IO_ICONDISPLAY               2110
#define ID_IO_OBJECTTYPETEXT            2111
#define ID_IO_FILETEXT                  2112
#define ID_IO_FILETYPE                  2113

// Strings in OLE2UI resources
#define IDS_IORESULTNEW                 32400
#define IDS_IORESULTNEWICON             32401
#define IDS_IORESULTFROMFILE1           32402
#define IDS_IORESULTFROMFILE2           32403
#define IDS_IORESULTFROMFILEICON2       32404
#define IDS_IORESULTLINKFILE1           32405
#define IDS_IORESULTLINKFILE2           32406
#define IDS_IORESULTLINKFILEICON1       32407
#define IDS_IORESULTLINKFILEICON2       32408

/*************************************************************************
** PASTE SPECIAL DIALOG
*************************************************************************/

//Paste Special Dialog identifiers
#define ID_PS_PASTE                    500
#define ID_PS_PASTELINK                501
#define ID_PS_SOURCETEXT               502
#define ID_PS_PASTELIST                503
#define ID_PS_PASTELINKLIST            504
#define ID_PS_DISPLAYLIST              505
#define ID_PS_DISPLAYASICON            506
#define ID_PS_ICONDISPLAY              507
#define ID_PS_CHANGEICON               508
#define ID_PS_RESULTIMAGE              509
#define ID_PS_RESULTTEXT               510
#define ID_PS_RESULTGROUP              511
#define ID_PS_STXSOURCE                512
#define ID_PS_STXAS                    513

// Paste Special String IDs
#define IDS_PSPASTEDATA                32410
#define IDS_PSPASTEOBJECT              32411
#define IDS_PSPASTEOBJECTASICON        32412
#define IDS_PSPASTELINKDATA            32413
#define IDS_PSPASTELINKOBJECT          32414
#define IDS_PSPASTELINKOBJECTASICON    32415
#define IDS_PSNONOLE                   32416
#define IDS_PSUNKNOWNTYPE              32417
#define IDS_PSUNKNOWNSRC               32418
#define IDS_PSUNKNOWNAPP               32419

/*************************************************************************
** EDIT LINKS DIALOG
*************************************************************************/

// Edit Links Dialog identifiers
#define ID_EL_CHANGESOURCE             201
#define ID_EL_AUTOMATIC                202
#define ID_EL_CLOSE                    208
#define ID_EL_CANCELLINK               209
#define ID_EL_UPDATENOW                210
#define ID_EL_OPENSOURCE               211
#define ID_EL_MANUAL                   212
#define ID_EL_LINKSOURCE               216
#define ID_EL_LINKTYPE                 217
#define ID_EL_UPDATE                   218
#define ID_EL_NULL                     -1
#define ID_EL_LINKSLISTBOX             206
#define ID_EL_COL1                     220
#define ID_EL_COL2                     221
#define ID_EL_COL3                     222

/*************************************************************************
** CHANGE ICON DIALOG
*************************************************************************/

//Change Icon Dialog identifiers
#define ID_GROUP                    120
#define ID_CURRENT                  121
#define ID_CURRENTICON              122
#define ID_DEFAULT                  123
#define ID_DEFAULTICON              124
#define ID_FROMFILE                 125
#define ID_FROMFILEEDIT             126
#define ID_ICONLIST                 127
#define ID_LABEL                    128
#define ID_LABELEDIT                129
#define ID_BROWSE                   130
#define ID_RESULTICON               132
#define ID_RESULTLABEL              133

// Stringtable defines for Change Icon
#define IDS_CINOICONSINFILE         32430
#define IDS_CIINVALIDFILE           32431
#define IDS_CIFILEACCESS            32432
#define IDS_CIFILESHARE             32433
#define IDS_CIFILEOPENFAIL          32434

/*************************************************************************
** CONVERT DIALOG
*************************************************************************/

//Convert Dialog identifiers
#define IDCV_OBJECTTYPE             150
#define IDCV_DISPLAYASICON          152
#define IDCV_CHANGEICON             153
#define IDCV_ACTIVATELIST           154
#define IDCV_CONVERTTO              155
#define IDCV_ACTIVATEAS             156
#define IDCV_RESULTTEXT             157
#define IDCV_CONVERTLIST            158
#define IDCV_ICON                   159
#define IDCV_ICONLABEL1             160
#define IDCV_ICONLABEL2             161
#define IDCV_STXCURTYPE             162
#define IDCV_GRPRESULT              163
#define IDCV_STXCONVERTTO           164

// String IDs for Convert dialog
#define IDS_CVRESULTCONVERTLINK     32440
#define IDS_CVRESULTCONVERTTO       32441
#define IDS_CVRESULTNOCHANGE        32442
#define IDS_CVRESULTDISPLAYASICON   32443
#define IDS_CVRESULTACTIVATEAS      32444
#define IDS_CVRESULTACTIVATEDIFF    32445

/*************************************************************************
** BUSY DIALOG
*************************************************************************/

// Busy dialog identifiers
#define IDBZ_RETRY                      600
#define IDBZ_ICON                       601
#define IDBZ_MESSAGE1                   602
#define IDBZ_SWITCHTO                   604

// Busy dialog stringtable defines
#define IDS_BZRESULTTEXTBUSY            32447
#define IDS_BZRESULTTEXTNOTRESPONDING   32448

// Links dialog stringtable defines
#define IDS_LINK_AUTO           32450
#define IDS_LINK_MANUAL         32451
#define IDS_LINK_UNKNOWN        32452
#define IDS_LINKS               32453
#define IDS_FAILED              32454
#define IDS_CHANGESOURCE        32455
#define IDS_INVALIDSOURCE       32456
#define IDS_ERR_GETLINKSOURCE   32457
#define IDS_ERR_GETLINKUPDATEOPTIONS    32458
#define IDS_ERR_ADDSTRING       32459
#define IDS_CHANGEADDITIONALLINKS   32460
#define IDS_CLOSE               32461

/*************************************************************************
** PROMPT USER DIALOGS
*************************************************************************/
#define ID_PU_LINKS             900
#define ID_PU_TEXT              901
#define ID_PU_CONVERT           902
#define ID_PU_BROWSE            904
#define ID_PU_METER             905
#define ID_PU_PERCENT           906
#define ID_PU_STOP              907

// used for -1 ids in dialogs:
#define ID_DUMMY    999

/* inside ole2ui.c */
#ifdef __cplusplus
extern "C"
#endif

/*************************************************************************
** OLE OBJECT FEEDBACK EFFECTS
*************************************************************************/

#define OLEUI_HANDLES_USEINVERSE    0x00000001L
#define OLEUI_HANDLES_NOBORDER      0x00000002L
#define OLEUI_HANDLES_INSIDE        0x00000004L
#define OLEUI_HANDLES_OUTSIDE       0x00000008L

#define OLEUI_SHADE_FULLRECT        1
#define OLEUI_SHADE_BORDERIN        2
#define OLEUI_SHADE_BORDEROUT       3

/* objfdbk.c function prototypes */
STDAPI_(void) OleUIDrawHandles(LPRECT lpRect, HDC hdc, DWORD dwFlags, UINT cSize, BOOL fDraw);
STDAPI_(void) OleUIDrawShading(LPRECT lpRect, HDC hdc, DWORD dwFlags, UINT cWidth);
STDAPI_(void) OleUIShowObject(LPCRECT lprc, HDC hdc, BOOL fIsLink);

/*************************************************************************
** Hatch window definitions and prototypes                              **
*************************************************************************/
#define DEFAULT_HATCHBORDER_WIDTH   4

STDAPI_(BOOL) RegisterHatchWindowClass(HINSTANCE hInst);
STDAPI_(HWND) CreateHatchWindow(HWND hWndParent, HINSTANCE hInst);
STDAPI_(UINT) GetHatchWidth(HWND hWndHatch);
STDAPI_(void) GetHatchRect(HWND hWndHatch, LPRECT lpHatchRect);
STDAPI_(void) SetHatchRect(HWND hWndHatch, LPRECT lprcHatchRect);
STDAPI_(void) SetHatchWindowSize(
      HWND        hWndHatch,
      LPRECT      lprcIPObjRect,
      LPRECT      lprcClipRect,
      LPPOINT     lpptOffset
);

#define OLEUI_VERSION_MAGIC 0x4D42

#endif // _OLESTD_H_


STABLIZE.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       stablize.h
//
//  Contents:   Stabilization Classes used to stabilize objects during
//              re-entrant calls.
//
//  Classes:    CSafeRefCount
//              CStabilize
//
//  History:    8-26-94   stevebl   Modified from code written by AlexGo
//
//----------------------------------------------------------------------------

#ifndef __STABLIZE__
#define __STABLIZE__

//+-------------------------------------------------------------------------
//
//  Class:      CSafeRefCount
//
//  Purpose:    A concrete class for objects to inherit from.
//              CSafeRefCount will keep track of reference counts,
//              nesting counts, and zombie states, allowing objects
//              to easily manage the liveness of their memory images.
//
//  Interface:
//
//  History:    dd-mmm-yy Author    Comment
//              01-Aug-94 alexgo    author
//
//  Notes:      inherits CPrivAlloc
//
//--------------------------------------------------------------------------

class CSafeRefCount
{
public:
        ULONG   SafeAddRef();
        ULONG   SafeRelease();
        ULONG   IncrementNestCount();
        ULONG   DecrementNestCount();
                CSafeRefCount();
        virtual ~CSafeRefCount();

private:

        ULONG   m_cRefs;
        ULONG   m_cNest;
        BOOL    m_fInDelete;
};

//+-------------------------------------------------------------------------
//
//  Class:      CStabilize
//
//  Purpose:    An instance of this class should be allocated on the
//              stack of every object method that makes an outgoing call.
//              The contstructor takes a pointer to the object's base
//              CSafeRefCount class.
//
//  Interface:
//
//  History:    dd-mmm-yy Author    Comment
//              01-Aug-94 alexgo    author
//
//  Notes:      The constructor will increment the nest count of the
//              object while the destructor will decrement it.
//
//--------------------------------------------------------------------------

class CStabilize
{
public:
        inline CStabilize( CSafeRefCount *pObjSafeRefCount );
        inline ~CStabilize();

private:
        CSafeRefCount * m_pObjSafeRefCount;
};

inline CStabilize::CStabilize( CSafeRefCount *pObjSafeRefCount )
{
        pObjSafeRefCount->IncrementNestCount();
        m_pObjSafeRefCount = pObjSafeRefCount;
}

inline CStabilize::~CStabilize()
{
        m_pObjSafeRefCount->DecrementNestCount();
}

#endif  // __STABLIZE__


README.TXT

OLE COM Sample

The MFract sample uses OLE2's Component Object Model (COM) to provide a
mechanism for implementing modular fractal engines.

Each of MFract's fractal engines are implemented as inproc servers that
provide a custom interface called IFractalEngine. This allows new fractal
engines to be created for MFract without having to modify or recompile the
application. In fact, creating a new fractal engine doesn't require that the
programmer know anything about Mfract beyond the custom interfaces that it
uses.

To compile
----------

MFract can be compiled as either a Unicode or ANSI application. Type
"nmake unicode" to build a Unicode version. Type "nmake" or "nmake ansi" to
build an ANSI version. Type "nmake clean" to remove any target files from
the build.

To run
------

Run regedit MFract.Reg to register Mfract.
Run MFract.Exe.


ABOUT.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       about.cxx
//
//  Contents:   implementation for the about dialog box
//
//  Classes:    CAbout
//
//  Functions:
//
//  History:    4-12-94   stevebl   Created
//
//----------------------------------------------------------------------------

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

//+---------------------------------------------------------------------------
//
//  Member:     CAbout::DialogProc
//
//  Synopsis:   dialog proc for the About dialog box
//
//  Arguments:  [uMsg]   - message
//              [wParam] - first message parameter
//              [lParam] - second message parameter
//
//  History:    4-12-94   stevebl   Created
//
//----------------------------------------------------------------------------

BOOL CAbout::DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_INITDIALOG:
        return(TRUE);
    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK
            || LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hwndDlg, TRUE);
            return(TRUE);
        }
    }
    return(FALSE);
}


ABOUT.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       about.h
//
//  Contents:   definition for the About dialog box class
//
//  Classes:    CAbout
//
//  Functions:
//
//  History:    4-12-94   stevebl   Created
//
//----------------------------------------------------------------------------

#ifndef __ABOUT_H__
#define __ABOUT_H__

#include <cdialog.h>

#ifdef __cplusplus

//+---------------------------------------------------------------------------
//
//  Class:      CAbout
//
//  Purpose:    implements the about dialog box
//
//  Interface:  DialogProc -- dialog procedure
//
//  History:    4-28-94   stevebl   Created
//
//----------------------------------------------------------------------------

class CAbout: public CHlprDialog
{
public:
    BOOL DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
};

#endif //__cplusplus

#endif //__ABOUT_H__


CHNGDIM.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       chngdim.cxx
//
//  Contents:   implementation for the Change Graph Dimension dialog box
//
//  Classes:    CChangeDim
//
//  Functions:
//
//  History:    4-20-94   stevebl   Created
//
//----------------------------------------------------------------------------

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

//+---------------------------------------------------------------------------
//
//  Member:     CChangeDim::DialogProc
//
//  Synopsis:   dialog proc for the change dimensions dialog box
//
//  Arguments:  [hwndDlg] - dialog's window handle
//              [uMsg]    - message
//              [wParam]  - first message parameter
//              [lParam]  - second message parameter
//
//  History:    4-20-94   stevebl   Created
//
//----------------------------------------------------------------------------

BOOL CChangeDim::DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    unsigned uImageWidth, uImageHeight;
    switch (uMsg)
    {
    case WM_INITDIALOG:
        _pwf->GetGraphSize(&uImageWidth, &uImageHeight);
        SetDlgItemInt(hwndDlg, IDC_HEIGHT, uImageHeight, FALSE);
        SetDlgItemInt(hwndDlg, IDC_WIDTH, uImageWidth, FALSE);
        CheckButtons(hwndDlg, uImageHeight, uImageWidth);
        return (TRUE);
    case WM_COMMAND:
        switch(LOWORD(wParam))
        {
        case IDC_VGA:
            SetDlgItemInt(hwndDlg, IDC_WIDTH, 640, FALSE);
            SetDlgItemInt(hwndDlg, IDC_HEIGHT, 480, FALSE);
            break;
        case IDC_SVGA:
            SetDlgItemInt(hwndDlg, IDC_WIDTH, 800, FALSE);
            SetDlgItemInt(hwndDlg, IDC_HEIGHT, 600, FALSE);
            break;
        case IDC_1024:
            SetDlgItemInt(hwndDlg, IDC_WIDTH, 1024, FALSE);
            SetDlgItemInt(hwndDlg, IDC_HEIGHT, 768, FALSE);
            break;
        case IDC_1280:
            SetDlgItemInt(hwndDlg, IDC_WIDTH, 1280, FALSE);
            SetDlgItemInt(hwndDlg, IDC_HEIGHT, 1024, FALSE);
            break;
        case IDC_QUICKDRAW:
            SetDlgItemInt(hwndDlg, IDC_WIDTH, QUICKWIDTH, FALSE);
            SetDlgItemInt(hwndDlg, IDC_HEIGHT, QUICKHEIGHT, FALSE);
            break;
        case IDC_HEIGHT:
        case IDC_WIDTH:
            CheckButtons(
                hwndDlg,
                GetDlgItemInt(hwndDlg, IDC_WIDTH, NULL, FALSE),
                GetDlgItemInt(hwndDlg, IDC_HEIGHT, NULL, FALSE));
            break;
        case IDC_RESET:
            uImageHeight = GetDlgItemInt(hwndDlg, IDC_HEIGHT, NULL, FALSE);
            uImageWidth = GetDlgItemInt(hwndDlg, IDC_WIDTH, NULL, FALSE);
            _pwf->SetGraphSize(uImageWidth, uImageHeight);
            EndDialog(hwndDlg,TRUE);
            return(TRUE);
        case IDC_RESIZE:
            uImageHeight = GetDlgItemInt(hwndDlg, IDC_HEIGHT, NULL, FALSE);
            uImageWidth = GetDlgItemInt(hwndDlg, IDC_WIDTH, NULL, FALSE);
            _pwf->SetGraphSize(uImageWidth, uImageHeight);
            EndDialog(hwndDlg,FALSE);
            return(TRUE);
        case IDCANCEL:
            EndDialog(hwndDlg,FALSE);
            return(TRUE);
        }
        break;
    }
    return (FALSE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CChangeDim::CheckButtons
//
//  Synopsis:   checks to correct check box in the dialog box for a given
//              set of dimensions
//
//  Arguments:  [hwndDlg] - dialog handle
//              [iWidth]   - width of the graph
//              [iHeight]  - height of the graph
//
//  History:    4-20-94   stevebl   Created
//
//----------------------------------------------------------------------------

void CChangeDim::CheckButtons(HWND hwndDlg, int iWidth, int iHeight)
{
    if (iWidth == 640 && iHeight == 480)
    {
        CheckRadioButton(hwndDlg, IDC_QUICKDRAW, IDC_CUSTOM, IDC_VGA);
    }
    else if (iWidth == 800 && iHeight == 600)
    {
        CheckRadioButton(hwndDlg, IDC_QUICKDRAW, IDC_CUSTOM, IDC_SVGA);
    }
    else if (iWidth == 1024 && iHeight == 768)
    {
        CheckRadioButton(hwndDlg, IDC_QUICKDRAW, IDC_CUSTOM, IDC_1024);
    }
    else if (iWidth == 1280 && iHeight == 1024)
    {
        CheckRadioButton(hwndDlg, IDC_QUICKDRAW, IDC_CUSTOM, IDC_1280);
    }
    else if (iWidth == QUICKWIDTH && iHeight == QUICKHEIGHT)
    {
        CheckRadioButton(hwndDlg, IDC_QUICKDRAW, IDC_CUSTOM, IDC_QUICKDRAW);
    }
    else
    {
        CheckRadioButton(hwndDlg, IDC_QUICKDRAW, IDC_CUSTOM, IDC_CUSTOM);
    }
}


CHNGDIM.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       chngdim.h
//
//  Contents:   definition for the Change Graph Dimension dialog box class
//
//  Classes:    CChangeDim
//
//  Functions:
//
//  History:    4-20-94   stevebl   Created
//
//----------------------------------------------------------------------------

#ifndef __CHNGDIM_H__
#define __CHNGDIM_H__

#include "cdialog.h"
#include "fclass.h"

#ifdef __cplusplus

//+---------------------------------------------------------------------------
//
//  Class:      CChangeDim
//
//  Purpose:    implements the Change Graph Size dialog box
//
//  Interface:  CChangeDim    -- constructor
//              DialogProc    -- dialog proc
//              CheckButtons  -- updates the radio button state
//
//  History:    4-20-94   stevebl   Created
//
//----------------------------------------------------------------------------

class CChangeDim: public CHlprDialog
{
public:
    CChangeDim(CFractalWindow * pwf)
    {
        _pwf = pwf;
    };
    BOOL DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
    void CheckButtons(HWND hwndDlg, int iWidth, int iHeight);

protected:
    CFractalWindow * _pwf;
    unsigned _uImageWidth, _uImageHeight;
};

#endif //__cplusplus

#endif //__CHNGDIM_H__


DIB.C

/*******************************************************************************
 *                                                                             *
 *  MODULE  : DIB.C                                                            *
 *                                                                             *
 *  DESCRIPTION : Routines for dealing with Device Independent Bitmaps.        *
 *                                                                             *
 *  FUNCTIONS   : OpenDIB()       - Opens DIB file and creates a memory DIB    *
 *                                                                             *
 *        WriteDIB()          - Writes a global handle in CF_DIB format        *
 *                  to a file.                                                 *
 *                                                                             *
 *        DibInfo()       - Retrieves the info. block associated               *
 *                  with a CF_DIB format memory block.                         *
 *                                                                             *
 *        CreateBIPalette()   - Creates a GDI palette given a pointer          *
 *                  to a BITMAPINFO structure.                                 *
 *                                                                             *
 *        CreateDibPalette()  - Creates a GDI palette given a HANDLE           *
 *                  to a BITMAPINFO structure.                                 *
 *                                                                             *
 *        ReadDibBitmapInfo() - Reads a file in DIB format and returns         *
 *                  a global handle to it's BITMAPINFO                         *
 *                                                                             *
 *        PaletteSize()       - Calculates the palette size in bytes           *
 *                  of given DIB                                               *
 *                                                                             *
 *        DibNumColors()      - Determines the number of colors in DIB         *
 *                                                                             *
 *        BitmapFromDib()     - Creates a DDB given a global handle to         *
 *                  a block in CF_DIB format.                                  *
 *                                                                             *
 *        DibFromBitmap()     - Creates a DIB repr. the DDB passed in.         *
 *                                                                             *
 *        DrawBitmap()        - Draws a bitmap at specified position           *
 *                  in the DC.                                                 *
 *                                                                             *
 *        DibBlt()        - Draws a bitmap in CIF_DIB format using             *
 *                  SetDIBitsToDevice()                                        *
 *                                                                             *
 *        StretchDibBlt()     - Draws a bitmap in CIF_DIB format using         *
 *                  StretchDIBits()                                            *
 *                                                                             *
 *        lread()         - Private routine to read more than 64k              *
 *                                                                             *
 *        lwrite()        - Private routine to write more than 64k             *
 *                                                                             *
 *******************************************************************************/

#include <windows.h>
#include <stdlib.h>

#include "dib.h"
HCURSOR hcurSave;

/****************************************************************************
 *                                                                          *
 *  FUNCTION   :OpenDIB(LPWSTR szFile)                                      *
 *                                                                          *
 *  PURPOSE    :Open a DIB file and create a MEMORY DIB, a memory handle    *
 *      containing BITMAPINFO, palette data and the bits.                   *
 *                                                                          *
 *  RETURNS    :A handle to the DIB.                                        *
 *                                                                          *
 ****************************************************************************/
HANDLE OpenDIB (szFile)
LPWSTR szFile;
{
    unsigned        fh;
    BITMAPINFOHEADER    bi;
    LPBITMAPINFOHEADER  lpbi;
    DWORD       dwLen = 0;
    DWORD       dwBits;
    HANDLE      hdib;
    HANDLE      h;
    OFSTRUCT        of;
    char sz[256];

    /* Open the file and read the DIB information */
    wcstombs(sz,szFile,256);
    fh = OpenFile(sz, &of, OF_READ);
    if (fh == -1)
        return NULL;

    hdib = ReadDibBitmapInfo(fh);
    if (!hdib)
        return NULL;
    DibInfo(hdib,&bi);

    /* Calculate the memory needed to hold the DIB */
    dwBits = bi.biSizeImage;
    dwLen  = bi.biSize + (DWORD)PaletteSize (&bi) + dwBits;

    /* Try to increase the size of the bitmap info. buffer to hold the DIB */
    h = GlobalReAlloc(hdib, dwLen, GHND);
    if (!h)
    {
        GlobalFree(hdib);
        hdib = NULL;
    }
    else
        hdib = h;

    /* Read in the bits */
    if (hdib)
    {
        lpbi = (VOID FAR *)GlobalLock(hdib);
        lread(fh, (LPSTR)lpbi + (WORD)lpbi->biSize + PaletteSize(lpbi), dwBits);
        GlobalUnlock(hdib);
    }
    _lclose(fh);

    return hdib;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : WriteDIB(LPWSTR szFile,HANDLE hdib)                        *
 *                                                                          *
 *  PURPOSE    : Write a global handle in CF_DIB format to a file.          *
 *                                                                          *
 *  RETURNS    : TRUE  - if successful.                                     *
 *       FALSE - otherwise                                                  *
 *                                                                          *
 ****************************************************************************/
BOOL WriteDIB (szFile, hdib)
LPWSTR szFile;
HANDLE hdib;
{
    BITMAPFILEHEADER    hdr;
    LPBITMAPINFOHEADER  lpbi;
    int                 fh;
    OFSTRUCT            of;
    char sz[256];

    if (!hdib)
        return FALSE;

    wcstombs(sz,szFile,256);
    fh = OpenFile (sz, &of, OF_CREATE|OF_READWRITE);
    if (fh == -1)
        return FALSE;

    lpbi = (VOID FAR *)GlobalLock (hdib);

    /* Fill in the fields of the file header */
    hdr.bfType      = BFT_BITMAP;
    hdr.bfSize      = GlobalSize (hdib) + sizeof (BITMAPFILEHEADER);
    hdr.bfReserved1 = 0;
    hdr.bfReserved2 = 0;
    hdr.bfOffBits   = (DWORD)sizeof(BITMAPFILEHEADER) + lpbi->biSize +
        PaletteSize(lpbi);

    /* Write the file header */
    _lwrite (fh, (LPSTR)&hdr, sizeof (BITMAPFILEHEADER));

    /* Write the DIB header and the bits */
    lwrite (fh, (LPSTR)lpbi, GlobalSize (hdib));

    GlobalUnlock (hdib);
    _lclose (fh);
    return TRUE;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : DibInfo(HANDLE hbi,LPBITMAPINFOHEADER lpbi)                *
 *                                                                          *
 *  PURPOSE    : Retrieves the DIB info associated with a CF_DIB            *
 *       format memory block.                                               *
 *                                                                          *
 *  RETURNS    : TRUE  - if successful.                                     *
 *       FALSE - otherwise                                                  *
 *                                                                          *
 ****************************************************************************/
BOOL DibInfo (hbi, lpbi)
HANDLE hbi;
LPBITMAPINFOHEADER lpbi;
{
    if (hbi)
    {
        *lpbi = *(LPBITMAPINFOHEADER)GlobalLock (hbi);

        /* fill in the default fields */
        if (lpbi->biSize != sizeof (BITMAPCOREHEADER))
        {
            if (lpbi->biSizeImage == 0L)
                lpbi->biSizeImage =
                    WIDTHBYTES(lpbi->biWidth*lpbi->biBitCount) * lpbi->biHeight;

            if (lpbi->biClrUsed == 0L)
                lpbi->biClrUsed = DibNumColors (lpbi);
        }
        GlobalUnlock (hbi);
        return TRUE;
    }
    return FALSE;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : CreateBIPalette(LPBITMAPINFOHEADER lpbi)                   *
 *                                                                          *
 *  PURPOSE    : Given a Pointer to a BITMAPINFO struct will create a       *
 *       a GDI palette object from the color table.                         *
 *                                                                          *
 *  RETURNS    : A handle to the palette.                                   *
 *                                                                          *
 ****************************************************************************/
HPALETTE CreateBIPalette (lpbi)
LPBITMAPINFOHEADER lpbi;
{
    LOGPALETTE  *pPal;
    HPALETTE    hpal = NULL;
    WORD        nNumColors;
    BYTE        red;
    BYTE        green;
    BYTE        blue;
    int         i;
    RGBQUAD FAR *pRgb;

    if (!lpbi)
        return NULL;

    if (lpbi->biSize != sizeof(BITMAPINFOHEADER))
        return NULL;

    /* Get a pointer to the color table and the number of colors in it */
    pRgb = (RGBQUAD FAR *)((LPSTR)lpbi + (WORD)lpbi->biSize);
    nNumColors = DibNumColors(lpbi);

    if (nNumColors)
    {
        /* Allocate for the logical palette structure */
        pPal = (LOGPALETTE*)LocalAlloc(LPTR,sizeof(LOGPALETTE) + nNumColors * sizeof(PALETTEENTRY));
        if (!pPal)
            return NULL;

        pPal->palNumEntries = nNumColors;
        pPal->palVersion    = PALVERSION;

        /* Fill in the palette entries from the DIB color table and
         * create a logical color palette.
         */
        for (i = 0; i < nNumColors; i++)
        {
            pPal->palPalEntry[i].peRed   = pRgb[i].rgbRed;
            pPal->palPalEntry[i].peGreen = pRgb[i].rgbGreen;
            pPal->palPalEntry[i].peBlue  = pRgb[i].rgbBlue;
            pPal->palPalEntry[i].peFlags = (BYTE)0;
        }
        hpal = CreatePalette(pPal);
        LocalFree((HANDLE)pPal);
    }
    else if (lpbi->biBitCount == 24)
    {
        /* A 24 bitcount DIB has no color table entries so, set the number of
         * to the maximum value (256).
         */
        nNumColors = MAXPALETTE;
        pPal = (LOGPALETTE*)LocalAlloc(LPTR,sizeof(LOGPALETTE) + nNumColors * sizeof(PALETTEENTRY));
        if (!pPal)
            return NULL;

        pPal->palNumEntries = nNumColors;
        pPal->palVersion    = PALVERSION;

        red = green = blue = 0;

        /* Generate 256 (= 8*8*4) RGB combinations to fill the palette
         * entries.
         */
        for (i = 0; i < pPal->palNumEntries; i++)
        {
            pPal->palPalEntry[i].peRed   = red;
            pPal->palPalEntry[i].peGreen = green;
            pPal->palPalEntry[i].peBlue  = blue;
            pPal->palPalEntry[i].peFlags = (BYTE)0;

            if (!(red += 32))
                if (!(green += 32))
                    blue += 64;
        }
        hpal = CreatePalette(pPal);
        LocalFree((HANDLE)pPal);
    }
    return hpal;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : CreateDibPalette(HANDLE hbi)                               *
 *                                                                          *
 *  PURPOSE    : Given a Global HANDLE to a BITMAPINFO Struct               *
 *       will create a GDI palette object from the color table.             *
 *       (BITMAPINFOHEADER format DIBs only)                                *
 *                                                                          *
 *  RETURNS    : A handle to the palette.                                   *
 *                                                                          *
 ****************************************************************************/
HPALETTE CreateDibPalette (hbi)
HANDLE hbi;
{
    HPALETTE hpal;

    if (!hbi)
        return NULL;
    hpal = CreateBIPalette((LPBITMAPINFOHEADER)GlobalLock(hbi));
    GlobalUnlock(hbi);
    return hpal;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : ReadDibBitmapInfo(int fh)                                  *
 *                                                                          *
 *  PURPOSE    : Will read a file in DIB format and return a global HANDLE  *
 *       to it's BITMAPINFO.  This function will work with both             *
 *       "old" (BITMAPCOREHEADER) and "new" (BITMAPINFOHEADER)              *
 *       bitmap formats, but will always return a "new" BITMAPINFO          *
 *                                                                          *
 *  RETURNS    : A handle to the BITMAPINFO of the DIB in the file.         *
 *                                                                          *
 ****************************************************************************/
HANDLE ReadDibBitmapInfo (fh)
int fh;
{
    DWORD     off;
    HANDLE    hbi = NULL;
    int       size;
    int       i;
    WORD      nNumColors;

    RGBQUAD FAR       *pRgb;
    BITMAPINFOHEADER   bi;
    BITMAPCOREHEADER   bc;
    LPBITMAPINFOHEADER lpbi;
    BITMAPFILEHEADER   bf;
    DWORD          dwWidth = 0;
    DWORD          dwHeight = 0;
    WORD           wPlanes, wBitCount;

    if (fh == -1)
        return NULL;

    /* Reset file pointer and read file header */
    off = _llseek(fh, 0L, SEEK_CUR);
    if (sizeof (bf) != _lread (fh, (LPSTR)&bf, sizeof (bf)))
        return FALSE;

    /* Do we have a RC HEADER? */
    if (!ISDIB (bf.bfType))
    {
        bf.bfOffBits = 0L;
        _llseek (fh, off, SEEK_SET);
    }
    if (sizeof (bi) != _lread (fh, (LPSTR)&bi, sizeof(bi)))
        return FALSE;

    nNumColors = DibNumColors (&bi);

    /* Check the nature (BITMAPINFO or BITMAPCORE) of the info. block
     * and extract the field information accordingly. If a BITMAPCOREHEADER,
     * transfer it's field information to a BITMAPINFOHEADER-style block
     */
    switch (size = (int)bi.biSize)
    {
    case sizeof (BITMAPINFOHEADER):
        break;

    case sizeof (BITMAPCOREHEADER):
        bc = *(BITMAPCOREHEADER*)&bi;
        dwWidth   = (DWORD)bc.bcWidth;
        dwHeight  = (DWORD)bc.bcHeight;
        wPlanes   = bc.bcPlanes;
        wBitCount = bc.bcBitCount;
        bi.biSize           = sizeof(BITMAPINFOHEADER);
        bi.biWidth          = dwWidth;
        bi.biHeight         = dwHeight;
        bi.biPlanes         = wPlanes;
        bi.biBitCount       = wBitCount;
        bi.biCompression    = BI_RGB;
        bi.biSizeImage      = 0;
        bi.biXPelsPerMeter  = 0;
        bi.biYPelsPerMeter  = 0;
        bi.biClrUsed        = nNumColors;
        bi.biClrImportant   = nNumColors;
        _llseek (fh, (LONG)sizeof (BITMAPCOREHEADER) - sizeof (BITMAPINFOHEADER), SEEK_CUR);
        break;

    default:
        /* Not a DIB! */
        return NULL;
    }

    /*  Fill in some default values if they are zero */
    if (bi.biSizeImage == 0)
    {
        bi.biSizeImage = WIDTHBYTES ((DWORD)bi.biWidth * bi.biBitCount)
            * bi.biHeight;
    }
    if (bi.biClrUsed == 0)
        bi.biClrUsed = DibNumColors(&bi);

    /* Allocate for the BITMAPINFO structure and the color table. */
    hbi = GlobalAlloc (GHND, (LONG)bi.biSize + nNumColors * sizeof(RGBQUAD));
    if (!hbi)
        return NULL;
    lpbi = (VOID FAR *)GlobalLock (hbi);
    *lpbi = bi;

    /* Get a pointer to the color table */
    pRgb = (RGBQUAD FAR *)((LPSTR)lpbi + bi.biSize);
    if (nNumColors)
    {
        if (size == sizeof(BITMAPCOREHEADER))
        {
            /* Convert a old color table (3 byte RGBTRIPLEs) to a new
             * color table (4 byte RGBQUADs)
             */
            _lread (fh, (LPSTR)pRgb, nNumColors * sizeof(RGBTRIPLE));

            for (i = nNumColors - 1; i >= 0; i--)
            {
                RGBQUAD rgb;

                rgb.rgbRed  = ((RGBTRIPLE FAR *)pRgb)[i].rgbtRed;
                rgb.rgbBlue = ((RGBTRIPLE FAR *)pRgb)[i].rgbtBlue;
                rgb.rgbGreen    = ((RGBTRIPLE FAR *)pRgb)[i].rgbtGreen;
                rgb.rgbReserved = (BYTE)0;

                pRgb[i] = rgb;
            }
        }
        else
            _lread(fh,(LPSTR)pRgb,nNumColors * sizeof(RGBQUAD));
    }

    if (bf.bfOffBits != 0L)
        _llseek(fh,off + bf.bfOffBits,SEEK_SET);

    GlobalUnlock(hbi);
    return hbi;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   :  PaletteSize(VOID FAR * pv)                                *
 *                                                                          *
 *  PURPOSE    :  Calculates the palette size in bytes. If the info. block  *
 *        is of the BITMAPCOREHEADER type, the number of colors is          *
 *        multiplied by 3 to give the palette size, otherwise the           *
 *        number of colors is multiplied by 4.                              *
 *                                                                          *
 *  RETURNS    :  Palette size in number of bytes.                          *
 *                                                                          *
 ****************************************************************************/
WORD PaletteSize (pv)
VOID FAR * pv;
{
    LPBITMAPINFOHEADER lpbi;
    WORD NumColors;

    lpbi      = (LPBITMAPINFOHEADER)pv;
    NumColors = DibNumColors(lpbi);

    if (lpbi->biSize == sizeof(BITMAPCOREHEADER))
        return NumColors * sizeof(RGBTRIPLE);
    else
        return NumColors * sizeof(RGBQUAD);
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : DibNumColors(VOID FAR * pv)                                *
 *                                                                          *
 *  PURPOSE    : Determines the number of colors in the DIB by looking at   *
 *       the BitCount filed in the info block.                              *
 *                                                                          *
 *  RETURNS    : The number of colors in the DIB.                           *
 *                                                                          *
 ****************************************************************************/
WORD DibNumColors (pv)
VOID FAR * pv;
{
    int bits;
    LPBITMAPINFOHEADER lpbi;
    LPBITMAPCOREHEADER lpbc;

    lpbi = ((LPBITMAPINFOHEADER)pv);
    lpbc = ((LPBITMAPCOREHEADER)pv);

    /*  With the BITMAPINFO format headers, the size of the palette
     *  is in biClrUsed, whereas in the BITMAPCORE - style headers, it
     *  is dependent on the bits per pixel ( = 2 raised to the power of
     *  bits/pixel).
     */
    if (lpbi->biSize != sizeof(BITMAPCOREHEADER))
    {
        if (lpbi->biClrUsed != 0)
            return (WORD)lpbi->biClrUsed;
        bits = lpbi->biBitCount;
    }
    else
        bits = lpbc->bcBitCount;

    switch (bits)
    {
    case 1:
        return 2;
    case 4:
        return 16;
    case 8:
        return 256;
    default:
        /* A 24 bitcount DIB has no color table */
        return 0;
    }
}
/****************************************************************************
 *                                                                          *
 *  FUNCTION   : DibFromBitmap()                                            *
 *                                                                          *
 *  PURPOSE    : Will create a global memory block in DIB format that       *
 *       represents the Device-dependent bitmap (DDB) passed in.            *
 *                                                                          *
 *  RETURNS    : A handle to the DIB                                        *
 *                                                                          *
 ****************************************************************************/
HANDLE DibFromBitmap (hbm, biStyle, biBits, hpal)
HBITMAP      hbm;
DWORD        biStyle;
WORD         biBits;
HPALETTE     hpal;
{
    BITMAP bm;
    BITMAPINFOHEADER bi;
    BITMAPINFOHEADER FAR *lpbi;
    DWORD dwLen;
    HANDLE hdib;
    HANDLE h;
    HDC hdc;

    if (!hbm)
        return NULL;

    if (hpal == NULL)
        hpal = GetStockObject(DEFAULT_PALETTE);

    GetObject(hbm,sizeof(bm),(LPSTR)&bm);

    if (biBits == 0)
        biBits =  bm.bmPlanes * bm.bmBitsPixel;

    bi.biSize           = sizeof(BITMAPINFOHEADER);
    bi.biWidth          = bm.bmWidth;
    bi.biHeight         = bm.bmHeight;
    bi.biPlanes         = 1;
    bi.biBitCount       = biBits;
    bi.biCompression    = biStyle;
    bi.biSizeImage      = 0;
    bi.biXPelsPerMeter  = 0;
    bi.biYPelsPerMeter  = 0;
    bi.biClrUsed        = 0;
    bi.biClrImportant   = 0;

    dwLen  = bi.biSize + PaletteSize(&bi);

    hdc = GetDC(NULL);
    hpal = SelectPalette(hdc,hpal,FALSE);
    RealizePalette(hdc);

    hdib = GlobalAlloc(GHND,dwLen);

    if (!hdib)
    {
        SelectPalette(hdc,hpal,FALSE);
        ReleaseDC(NULL,hdc);
        return NULL;
    }

    lpbi = (VOID FAR *)GlobalLock(hdib);

    *lpbi = bi;

    /*  call GetDIBits with a NULL lpBits param, so it will calculate the
     *  biSizeImage field for us
     */
    GetDIBits(hdc, hbm, 0, (WORD)bi.biHeight,
    NULL, (LPBITMAPINFO)lpbi, DIB_RGB_COLORS);

    bi = *lpbi;
    GlobalUnlock(hdib);

    /* If the driver did not fill in the biSizeImage field, make one up */
    if (bi.biSizeImage == 0)
    {
        bi.biSizeImage = WIDTHBYTES((DWORD)bm.bmWidth * biBits) * bm.bmHeight;

        if (biStyle != BI_RGB)
            bi.biSizeImage = (bi.biSizeImage * 3) / 2;
    }

    /*  realloc the buffer big enough to hold all the bits */
    dwLen = bi.biSize + PaletteSize(&bi) + bi.biSizeImage;
    if (h = GlobalReAlloc(hdib,dwLen,0))
        hdib = h;
    else
    {
        GlobalFree(hdib);
        hdib = NULL;

        SelectPalette(hdc,hpal,FALSE);
        ReleaseDC(NULL,hdc);
        return hdib;
    }

    /*  call GetDIBits with a NON-NULL lpBits param, and actualy get the
     *  bits this time
     */
    lpbi = (VOID FAR *)GlobalLock(hdib);

    if (GetDIBits( hdc,
        hbm,
        0,
        (WORD)bi.biHeight,
        (LPSTR)lpbi + (WORD)lpbi->biSize + PaletteSize(lpbi),
        (LPBITMAPINFO)lpbi, DIB_RGB_COLORS) == 0)
    {
        GlobalUnlock(hdib);
        hdib = NULL;
        SelectPalette(hdc,hpal,FALSE);
        ReleaseDC(NULL,hdc);
        return NULL;
    }

    bi = *lpbi;
    GlobalUnlock(hdib);

    SelectPalette(hdc,hpal,FALSE);
    ReleaseDC(NULL,hdc);
    return hdib;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : BitmapFromDib(HANDLE hdib, HPALETTE hpal)                  *
 *                                                                          *
 *  PURPOSE    : Will create a DDB (Device Dependent Bitmap) given a global *
 *       handle to a memory block in CF_DIB format                          *
 *                                                                          *
 *  RETURNS    : A handle to the DDB.                                       *
 *                                                                          *
 ****************************************************************************/
HBITMAP BitmapFromDib (hdib, hpal)
HANDLE     hdib;
HPALETTE   hpal;
{
    LPBITMAPINFOHEADER  lpbi;
    HPALETTE    hpalT;
    HDC         hdc;
    HBITMAP     hbm;

    StartWait();

    if (!hdib)
        return NULL;

    lpbi = (VOID FAR *)GlobalLock(hdib);

    if (!lpbi)
        return NULL;

    hdc = GetDC(NULL);

    if (hpal)
    {
        hpalT = SelectPalette(hdc,hpal,FALSE);
        RealizePalette(hdc);     // GDI Bug...????
    }

    hbm = CreateDIBitmap(hdc,
        (LPBITMAPINFOHEADER)lpbi,
        (LONG)CBM_INIT,
        (LPSTR)lpbi + lpbi->biSize + PaletteSize(lpbi),
        (LPBITMAPINFO)lpbi,
        DIB_RGB_COLORS );

    if (hpal)
        SelectPalette(hdc,hpalT,FALSE);

    ReleaseDC(NULL,hdc);
    GlobalUnlock(hdib);

    EndWait();

    return hbm;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : DrawBitmap(HDC hdc, int x, int y, HBITMAP hbm, DWORD rop)  *
 *                                                                          *
 *  PURPOSE    : Draws bitmap <hbm> at the specifed position in DC <hdc>    *
 *                                                                          *
 *  RETURNS    : Return value of BitBlt()                                   *
 *                                                                          *
 ****************************************************************************/
BOOL DrawBitmap (hdc, x, y, hbm, rop)
HDC    hdc;
int    x, y;
HBITMAP    hbm;
DWORD      rop;
{
    HDC       hdcBits;
    BITMAP    bm;
    BOOL      f;

    if (!hdc || !hbm)
        return FALSE;

    hdcBits = CreateCompatibleDC(hdc);
    GetObject(hbm,sizeof(BITMAP),(LPSTR)&bm);
    SelectObject(hdcBits,hbm);
    f = BitBlt(hdc,0,0,bm.bmWidth,bm.bmHeight,hdcBits,0,0,rop);
    DeleteDC(hdcBits);

    return f;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : DibBlt( HDC hdc,                                           *
 *           int x0, int y0,                                                *
 *           int dx, int dy,                                                *
 *           HANDLE hdib,                                                   *
 *           int x1, int y1,                                                *
 *           LONG rop)                                                      *
 *                                                                          *
 *  PURPOSE    : Draws a bitmap in CF_DIB format, using SetDIBits to device.*
 *       taking the same parameters as BitBlt().                            *
 *                                                                          *
 *  RETURNS    : TRUE  - if function succeeds.                              *
 *       FALSE - otherwise.                                                 *
 *                                                                          *
 ****************************************************************************/
BOOL DibBlt (hdc, x0, y0, dx, dy, hdib, x1, y1, rop)
HDC    hdc;
int    x0, y0, dx, dy;
HANDLE     hdib;
int    x1, y1;
LONG       rop;
{
    LPBITMAPINFOHEADER   lpbi;
    LPSTR        pBuf;

    if (!hdib)
        return PatBlt(hdc,x0,y0,dx,dy,rop);

    lpbi = (VOID FAR *)GlobalLock(hdib);

    if (!lpbi)
        return FALSE;

    pBuf = (LPSTR)lpbi + (WORD)lpbi->biSize + PaletteSize(lpbi);
    SetDIBitsToDevice (hdc, x0, y0, dx, dy,
               x1,y1,
               x1,
               dy,
               pBuf, (LPBITMAPINFO)lpbi,
               DIB_RGB_COLORS );

    GlobalUnlock(hdib);
    return TRUE;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : StretchDibBlt( HDC hdc,                                    *
 *              int x, int y,                                               *

*              int dx, int dy,                                             *
 *              HANDLE hdib,                                                *
 *              int x0, int y0,                                             *
 *              int dx0, int dy0,                                           *
 *              LONG rop)                                                   *
 *                                                                          *
 *  PURPOSE    : Draws a bitmap in CF_DIB format, using StretchDIBits()     *
 *       taking the same parameters as StretchBlt().                        *
 *                                                                          *
 *  RETURNS    : TRUE  - if function succeeds.                              *
 *       FALSE - otherwise.                                                 *
 *                                                                          *
 ****************************************************************************/
BOOL StretchDibBlt (hdc, x, y, dx, dy, hdib, x0, y0, dx0, dy0, rop)
HDC hdc;
int x, y;
int dx, dy;
HANDLE hdib;
int x0, y0;
int dx0, dy0;
LONG rop;

{
    LPBITMAPINFOHEADER lpbi;
    LPSTR    pBuf;
    BOOL     f;

    if (!hdib)
        return PatBlt(hdc,x,y,dx,dy,rop);

    lpbi = (VOID FAR *)GlobalLock(hdib);

    if (!lpbi)
        return FALSE;

    pBuf = (LPSTR)lpbi + (WORD)lpbi->biSize + PaletteSize(lpbi);

    f = StretchDIBits ( hdc,
            x, y,
            dx, dy,
            x0, y0,
            dx0, dy0,
            pBuf, (LPBITMAPINFO)lpbi,
            DIB_RGB_COLORS,
            rop);

    GlobalUnlock(hdib);
    return f;
}

 /************* PRIVATE ROUTINES TO READ/WRITE MORE THAN 64K ***************/
/****************************************************************************
 *                                                                          *
 *  FUNCTION   : lread(int fh, VOID FAR *pv, DWORD ul)                      *
 *                                                                          *
 *  PURPOSE    : Reads data in steps of 32k till all the data has been read.*
 *                                                                          *
 *  RETURNS    : 0 - If read did not proceed correctly.                     *
 *       number of bytes read otherwise.                                    *
 *                                                                          *
 ****************************************************************************/
DWORD PASCAL lread (fh, pv, ul)
int       fh;
VOID far      *pv;
DWORD         ul;
{
    DWORD     ulT = ul;
    BYTE      *hp = pv;

    while (ul > (DWORD)MAXREAD)
    {
        if (_lread(fh, (LPSTR)hp, (WORD)MAXREAD) != MAXREAD)
            return 0;
        ul -= MAXREAD;
        hp += MAXREAD;
    }
    if (_lread(fh, (LPSTR)hp, (WORD)ul) != (WORD)ul)
        return 0;
    return ulT;
}

/****************************************************************************
 *                                                                          *
 *  FUNCTION   : lwrite(int fh, VOID FAR *pv, DWORD ul)                     *
 *                                                                          *
 *  PURPOSE    : Writes data in steps of 32k till all the data is written.  *
 *                                                                          *
 *  RETURNS    : 0 - If write did not proceed correctly.                    *
 *       number of bytes written otherwise.                                 *
 *                                                                          *
 ****************************************************************************/
DWORD PASCAL lwrite (fh, pv, ul)
int      fh;
VOID FAR     *pv;
DWORD        ul;
{
    DWORD     ulT = ul;
    BYTE      *hp = pv;

    while (ul > MAXREAD)
    {
        if (_lwrite(fh, (LPSTR)hp, (WORD)MAXREAD) != MAXREAD)
            return 0;
        ul -= MAXREAD;
        hp += MAXREAD;
    }
    if (_lwrite(fh, (LPSTR)hp, (WORD)ul) != (WORD)ul)
        return 0;
    return ulT;
}


DIB.H

/****************************************************************************
 *                                                                          *
 *  FILE    : DIB.H                                                         *
 *                                                                          *
 *  DESCRIPTION : Header/include file for DIB.C                             *
 *                                                                          *
 ****************************************************************************/

// This is code that I lifted years ago from the Windows SDK and have since
// ported over to NT.
//      -- stevebl

#ifndef __DIB_H__
#define __DIB_H__
#ifdef __cplusplus
extern "C" {
#endif

/* Macros to display/remove hourglass cursor for lengthy operations */
extern HCURSOR hcurSave;
#define StartWait() hcurSave = SetCursor(LoadCursor(NULL,IDC_WAIT))
#define EndWait()   SetCursor(hcurSave)

/* flags for _lseek */
#define  SEEK_CUR 1
#define  SEEK_END 2
#define  SEEK_SET 0

#define MAXREAD  32768             /* Number of bytes to be read during */
                       /* each read operation.          */

/* Header signatutes for various resources */
#define BFT_ICON   0x4349   /* 'IC' */
#define BFT_BITMAP 0x4d42   /* 'BM' */
#define BFT_CURSOR 0x5450   /* 'PT' */

/* macro to determine if resource is a DIB */
#define ISDIB(bft) ((bft) == BFT_BITMAP)

/* Macro to determine to round off the given value to the closest byte */
#define WIDTHBYTES(i)   ((i+31)/32*4)

#define PALVERSION  0x300
#define MAXPALETTE  256   /* max. # supported palette entries */

/***************** FUNCTION DECLARATIONS *******************/

DWORD PASCAL lread(int fh, VOID FAR *pv, DWORD ul);
DWORD PASCAL lwrite(int fh, VOID FAR *pv, DWORD ul);

/***********************************************************/
/* Declarations of functions used in dib.c module      */
/***********************************************************/

HANDLE      OpenDIB (LPWSTR szFile);
BOOL        WriteDIB (LPWSTR szFile,HANDLE hdib);
WORD        PaletteSize (VOID FAR * pv);
WORD        DibNumColors (VOID FAR * pv);
HPALETTE    CreateDibPalette (HANDLE hdib);
HPALETTE    CreateBIPalette (LPBITMAPINFOHEADER lpbi);
HANDLE      DibFromBitmap (HBITMAP hbm, DWORD biStyle, WORD biBits, HPALETTE hpal);
HBITMAP     BitmapFromDib (HANDLE hdib, HPALETTE hpal);
BOOL        DibBlt (HDC hdc, int x0, int y0, int dx, int dy, HANDLE hdib, int x1, int y1, LONG rop);
BOOL        StretchDibBlt (HDC hdc, int x0, int y0, int dx, int dy, HANDLE hdib, int x1, int y1, int dx1, int dy1, LONG rop);
BOOL        DibInfo (HANDLE hdib,LPBITMAPINFOHEADER lpbi);
HANDLE      ReadDibBitmapInfo (int fh);
BOOL        DrawBitmap (HDC hdc, int x, int y, HBITMAP hbm, DWORD rop);
#ifdef __cplusplus
}
#endif
#endif // __DIB_H__


ENGNLIST.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       engnlist.cxx
//
//  Contents:   implementation for CEngineList
//
//  Classes:    CEngineList
//
//  Functions:
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include "engnlist.h"
#include <mandcid.h>
#include <juliacid.h>
#include <plasmcid.h>
#include <rgencid.h>

//+---------------------------------------------------------------------------
//
//  Member:     CEngineList::CEngineList
//
//  Synopsis:   constructor
//
//  Arguments:  [hmenuOuter] - handle to the menu where the list of engines
//                             will be attached
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

CEngineList::CEngineList(HMENU hmenuOuter)
{
    _hmenuOuter = hmenuOuter;
    _hMenu = NULL;
    _rgCID = NULL;
    _rgszName = NULL;
    _nEntries = 0;
}

//+---------------------------------------------------------------------------
//
//  Member:     CEngineList::~CEngineList
//
//  Synopsis:   destructor
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

CEngineList::~CEngineList()
{
    if (_rgCID)
    {
        delete(_rgCID);
    }
    if (_rgszName)
    {
        for (unsigned x = _nEntries; x--;)
        {
            delete[](_rgszName[x]);
        }
        delete[](_rgszName);
    }
    if (_hMenu)
    {
        DestroyMenu(_hMenu);
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CEngineList::Read
//
//  Synopsis:   Reads the list of engines from the registry.
//
//              If there are no entries in the registry for MFract then this
//              routine creates some entries with the default set of
//              fractal engines.
//
//  Returns:    S_OK on success
//              E_FAIL if there was a problem
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT CEngineList::Read()
{
    HRESULT hr = S_OK;
    if (FAILED(hr))
    {
        return(hr);
    }
    DWORD dwDisposition;
    HKEY hkSoftware;
    LONG lReturn = RegCreateKeyEx(
        HKEY_LOCAL_MACHINE,
        TEXT("Software"),
        0,
        NULL,
        REG_OPTION_NON_VOLATILE,
        KEY_READ,
        NULL,
        &hkSoftware,
        &dwDisposition);
    if (lReturn == ERROR_SUCCESS)
    {
        HKEY hkMicrosoft;
        lReturn = RegCreateKeyEx(
            hkSoftware,
            TEXT("Microsoft"),
            0,
            NULL,
            REG_OPTION_NON_VOLATILE,
            KEY_READ |
            KEY_WRITE,
            NULL,
            &hkMicrosoft,
            &dwDisposition);
        if (lReturn == ERROR_SUCCESS)
        {
            HKEY hkApp;
            lReturn = RegCreateKeyEx(
                hkMicrosoft,
                TEXT(APP_TITLE),
                0,
                NULL,
                REG_OPTION_NON_VOLATILE,
                KEY_READ |
                KEY_WRITE |
                KEY_SET_VALUE,
                NULL,
                &hkApp,
                &dwDisposition);
            if (lReturn == ERROR_SUCCESS)
            {
                HKEY hkFractalTypes;
                lReturn = RegCreateKeyEx(
                    hkApp,
                    TEXT("Fractal Types"),
                    0,
                    NULL,
                    REG_OPTION_NON_VOLATILE,
                    KEY_READ |
                    KEY_WRITE |
                    KEY_SET_VALUE,
                    NULL,
                    &hkFractalTypes,
                    &dwDisposition);
                if (lReturn == ERROR_SUCCESS)
                {
                    if (dwDisposition == REG_OPENED_EXISTING_KEY)
                    {
                        // Enumerate the values in this key to build our list
                        DWORD cchClass = 0;
                        DWORD cSubkeys;
                        DWORD cchMaxSubkey;
                        DWORD cchMaxClass;
                        DWORD cValues;
                        DWORD cchMaxValueName;
                        DWORD cbMaxValueData;
                        DWORD cbSecurityDescriptor;
                        FILETIME ftLastWriteTime;
                        lReturn = RegQueryInfoKey(
                            hkFractalTypes,
                            NULL,
                            &cchClass,
                            0,
                            &cSubkeys,
                            &cchMaxSubkey,
                            &cchMaxClass,
                            &cValues,
                            &cchMaxValueName,
                            &cbMaxValueData,
                            &cbSecurityDescriptor,
                            &ftLastWriteTime);
                        _rgCID = new CLSID[cValues];
                        _rgszName = new TCHAR *[cValues];
                        for (DWORD uIndex = cValues; uIndex--;)
                        {
                            TCHAR * szCLSID = (TCHAR *) new BYTE [cbMaxValueData];
                            TCHAR * szName = new TCHAR[cchMaxValueName+1];
                            DWORD cchValue = cchMaxValueName+1;
                            DWORD cbData = cbMaxValueData;
                            lReturn = RegEnumValue(
                                hkFractalTypes,
                                uIndex,
                                szName,
                                &cchValue,
                                0,
                                NULL,
                                NULL,
                                &cbData);
                            cchValue++;
                            _rgszName[uIndex] = new TCHAR[cchValue];
                            lReturn = RegEnumValue(
                                hkFractalTypes,
                                uIndex,
                                _rgszName[uIndex],
                                &cchValue,
                                0,
                                NULL,
                                (BYTE *) szCLSID,
                                &cbData);
#ifdef UNICODE
                            CLSIDFromString(szCLSID, &_rgCID[uIndex]);
#else
                            OLECHAR wszCLSID[40]; // should be more than large enough
                            mbstowcs(wszCLSID, szCLSID, 40);
                            CLSIDFromString(wszCLSID, &_rgCID[uIndex]);
#endif
                            delete[] szCLSID;
                            delete[] szName;
                        }
                        _nEntries = cValues;
                    }
                    else
                    {
                        // This is the first time MFract has been run on this machine.
                        // Create the default key entries.
                        _rgCID = new CLSID[4];
                        _rgszName = new TCHAR * [4];
                        TCHAR szCLSID[40];  // more than large enough
#ifndef UNICODE
                        OLECHAR wszCLSID[40];
#endif
#ifdef UNICODE
                        StringFromGUID2(CLSID_MANDELBROT, szCLSID, 40);
#else
                        StringFromGUID2(CLSID_MANDELBROT, wszCLSID, 40);
                        wcstombs(szCLSID, wszCLSID, 40);
#endif
                        lReturn = RegSetValueEx(
                            hkFractalTypes,
                            TEXT("Mandelbrot Fractal"),
                            0,
                            REG_SZ,
                            (BYTE *) szCLSID,
                            sizeof(TCHAR) * (1 +
#ifdef UNICODE
                                wcslen(szCLSID)
#else
                                strlen(szCLSID)
#endif
                                )
                            );
                        memcpy(&_rgCID[0], &CLSID_MANDELBROT, sizeof (CLSID));
                        _rgszName[0] =
#ifdef UNICODE
                        _wcsdup(TEXT("Mandelbrot Fractal"));
#else
                        _strdup(TEXT("Mandelbrot Fractal"));
#endif
#ifdef UNICODE
                        StringFromGUID2(CLSID_JULIASET, szCLSID, 40);
#else
                        StringFromGUID2(CLSID_JULIASET, wszCLSID, 40);
                        wcstombs(szCLSID, wszCLSID, 40);
#endif
                        lReturn = RegSetValueEx(
                            hkFractalTypes,
                            TEXT("Julia Set Fractal"),
                            0,
                            REG_SZ,
                            (BYTE *) szCLSID,
                            sizeof(TCHAR) * (1 +
#ifdef UNICODE
                                wcslen(szCLSID)
#else
                                strlen(szCLSID)
#endif
                                )
                            );
                        memcpy(&_rgCID[1], &CLSID_JULIASET, sizeof (CLSID));
                        _rgszName[1] =
#ifdef UNICODE
                        _wcsdup(TEXT("Julia Set Fractal"));
#else
                        _strdup(TEXT("Julia Set Fractal"));
#endif
#ifdef UNICODE
                        StringFromGUID2(CLSID_PLASMA, szCLSID, 40);
#else
                        StringFromGUID2(CLSID_PLASMA, wszCLSID, 40);
                        wcstombs(szCLSID, wszCLSID, 40);
#endif
                        lReturn = RegSetValueEx(
                            hkFractalTypes,
                            TEXT("Plasma Cloud Fractal"),
                            0,
                            REG_SZ,
                            (BYTE *) szCLSID,
                            sizeof(TCHAR) * (1 +
#ifdef UNICODE
                                wcslen(szCLSID)
#else
                                strlen(szCLSID)
#endif
                                )
                            );
                        memcpy(&_rgCID[2], &CLSID_PLASMA, sizeof (CLSID));
                        _rgszName[2] =
#ifdef UNICODE
                        _wcsdup(TEXT("Plasma Cloud Fractal"));
#else
                        _strdup(TEXT("Plasma Cloud Fractal"));
#endif
#ifdef UNICODE
                        StringFromGUID2(CLSID_RGEN, szCLSID, 40);
#else
                        StringFromGUID2(CLSID_RGEN, wszCLSID, 40);
                        wcstombs(szCLSID, wszCLSID, 40);
#endif
                        lReturn = RegSetValueEx(
                            hkFractalTypes,
                            TEXT("Recursive Generator Fractal"),
                            0,
                            REG_SZ,
                            (BYTE *) szCLSID,
                            sizeof(TCHAR) * (1 +
#ifdef UNICODE
                                wcslen(szCLSID)
#else
                                strlen(szCLSID)
#endif
                                )
                            );
                        memcpy(&_rgCID[3], &CLSID_RGEN, sizeof (CLSID));
                        _rgszName[3] =
#ifdef UNICODE
                        _wcsdup(TEXT("Recursive Generator Fractal"));
#else
                        _strdup(TEXT("Recursive Generator Fractal"));
#endif
                        _nEntries = 4;
                    }
                    RegCloseKey(hkFractalTypes);
                }
                else
                {
                    // couldn't access registry
                    hr = E_FAIL;
                }
                RegCloseKey(hkApp);
            }
            else
            {
                // couldn't access registry
                hr = E_FAIL;
            }
            RegCloseKey(hkMicrosoft);
        }
        else
        {
            // couldn't access registry
            hr = E_FAIL;
        }
        RegCloseKey(hkSoftware);
    }
    else
    {
        // couldn't access registry
        hr = E_FAIL;
    }
    if (_hMenu)
    {
        DestroyMenu(_hMenu);
    }
    _hMenu = CreatePopupMenu();
    if (_hMenu)
    {
        for (unsigned u=_nEntries; u--;)
        {
            if (!InsertMenu(
                _hMenu,
                0,
                MF_BYPOSITION | (MF_CHECKED * (0 == u)),
                IDM_FRACTAL_TYPE + u,
                _rgszName[u]))
            {
                // Insert Menu Failed
                hr = E_FAIL;
            }
        }
        TCHAR szMenuString[MAX_STRING_LENGTH];
        GetMenuString(_hmenuOuter,
            IDM_SELECT_TYPE,
            szMenuString,
            MAX_STRING_LENGTH,
            MF_BYCOMMAND);
        if (!ModifyMenu(
            _hmenuOuter,
            IDM_SELECT_TYPE,
            MF_BYCOMMAND | MF_POPUP,
            (UINT)_hMenu,
            szMenuString))
        {
            // ModifyMenu failed
            hr = E_FAIL;
        }
    }
    else
    {
        // couldn't create menu
        hr = E_FAIL;
    }
    return(hr);
}

//+---------------------------------------------------------------------------
//
//  Member:     CEngineList::GetEntry
//
//  Synopsis:   returns the CLSID of the fractal engine at a given index
//
//  Arguments:  [uIndex] - index of the engine
//
//  Returns:    CLSID of the fractal engine
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

CLSID CEngineList::GetEntry(unsigned uIndex)
{
    return(_rgCID[uIndex]);
}

//+---------------------------------------------------------------------------
//
//  Member:     CEngineList::FindEntry
//
//  Synopsis:   Looks through the list of CLSIDs to find out what engine number
//              has this CLSID.
//
//  Arguments:  [cid] - CLSID of the fractal engine
//
//  Returns:    >= 0 if the CLSID was found
//              -1   if the CLSID was not found
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

int CEngineList::FindEntry(CLSID cid)
{
    for (unsigned u = _nEntries; u--;)
    {
        if (IsEqualGUID(cid, _rgCID[u]))
        {
            return((int) u);
        }
    }
    // not found in the list
    return(-1);
}


ENGNLIST.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       engnlist.h
//
//  Contents:   definition of the Engine List class
//
//  Classes:    CEngineList
//
//  Functions:
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

#ifndef __ENGNLIST_H__
#define __ENGNLIST_H__

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

#ifdef __cplusplus

//+---------------------------------------------------------------------------
//
//  Class:      CEngineList
//
//  Purpose:    implements a simple list of fractal engines
//
//  Interface:  CEngineList  -- constructor
//              ~CEngineList -- destructor
//              Read         -- reads the list from the registry and attaches
//                              the Choose Fractal Type popup menu to the
//                              main menu
//              FindEntry    -- looks up the index of a fractal engine
//                              by its class ID
//              GetEntry     -- gets the class ID of a fractal engine
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

class CEngineList
{
public:
    CEngineList(HMENU hmenuOuter);
    ~CEngineList();
    HRESULT Read();
    int FindEntry(CLSID cid);
    CLSID GetEntry(unsigned uIndex);
private:
    CLSID * _rgCID;
    TCHAR ** _rgszName;
    HMENU _hMenu;
    HMENU _hmenuOuter;
    unsigned _nEntries;
};

#endif // __cplusplus

#endif // __ENGNLIST_H__


FCLASS.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       fclass.cxx
//
//  Contents:   implementation for CFractalWindow
//
//  Classes:    CFractalWindow
//
//  Functions:
//
//  History:    4-11-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include <windows.h>
#include <ole2.h>
#include "fractal.h"
#include "fclass.h"
#include "about.h"
#include "chngdim.h"
#include "dib.h"

#define START_IN_QUICKDRAWMODE

#define WM_REFRESHCOLORS    WM_USER

#define SCROLL_STEP 10

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::DrawRect
//
//  Synopsis:   Draws the zoom rectangle
//
//  Arguments:  [hdc]  - handle to the display context that contains the graph
//              [lprc] - rectangle containing the zoom rect's dimensions
//
//  Returns:    TRUE on success
//
//  History:    5-02-94   stevebl   Created
//
//----------------------------------------------------------------------------

BOOL CFractalWindow::DrawRect(HDC hdc, const RECT * lprc)
{
   HPALETTE hOldPal =  SelectPalette(hdc, _hpal, FALSE);
   RealizePalette(hdc);
   HBRUSH hbr = CreateSolidBrush(WHITENESS);
   BOOL fReturn = FrameRect(hdc, lprc, hbr);
   DeleteObject(hbr);
   SelectPalette(hdc, hOldPal, TRUE);
   RealizePalette(hdc);
   return(fReturn);
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::EraseRect
//
//  Synopsis:   Erases the zoom rectangle
//
//  Arguments:  [hdc]  - handle to a display context
//              [lprc] - rectangle containing the dimensions of the zoom rect
//
//  Returns:    TRUE on success
//
//  History:    5-02-94   stevebl   Created
//
//----------------------------------------------------------------------------

BOOL CFractalWindow::EraseRect(HDC hdc, const RECT * lprc)
{
   HPALETTE hOldPal =  SelectPalette(hdc, _hpal, FALSE);
   RealizePalette(hdc);
   BOOL fReturn;
   fReturn = BitBlt(hdc,
      lprc->left,
      lprc->top,
      lprc->right - lprc->left,
      1,
      _hdcMem,
      lprc->left + _uScrollPosX,
      lprc->top + _uScrollPosY,
      SRCCOPY);
   fReturn &= BitBlt(hdc,
      lprc->left,
      lprc->top,
      1,
      lprc->bottom - lprc->top,
      _hdcMem,
      lprc->left + _uScrollPosX,
      lprc->top + _uScrollPosY,
      SRCCOPY);
   fReturn &= BitBlt(hdc,
      lprc->right - 1,
      lprc->top,
      1,
      lprc->bottom - lprc->top,
      _hdcMem,
      (lprc->right - 1) + _uScrollPosX,
      lprc->top + _uScrollPosY,
      SRCCOPY);
   fReturn &= BitBlt(hdc,
      lprc->left,
      lprc->bottom - 1,
      lprc->right - lprc->left,
      1,
      _hdcMem,
      lprc->left + _uScrollPosX,
      (lprc->bottom - 1) + _uScrollPosY,
      SRCCOPY);
   SelectPalette(hdc, hOldPal, TRUE);
   RealizePalette(hdc);
   return(fReturn);
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::GetHwnd
//
//  Synopsis:   simple method to retrieve the HWND of the window
//
//  Returns:    the window handle to the window
//
//  History:    4-26-94   stevebl   Created
//
//----------------------------------------------------------------------------

HWND CFractalWindow::GetHwnd(void)
{
    return(_hwnd);
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::InitInstance
//
//  Synopsis:   initializes an instance of a window object belonging to the
//              CFractalWindow class
//
//  Arguments:  [hInstance] - instance handle
//              [nCmdShow]  - show command
//
//  History:    4-11-94   stevebl   Created
//
//----------------------------------------------------------------------------

BOOL CFractalWindow::InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    HWND hwnd = Create(
        TEXT("CFractalWindow"),
        TEXT(APP_TITLE),
        WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX
            | WS_MAXIMIZEBOX | WS_THICKFRAME | WS_VSCROLL | WS_HSCROLL,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        NULL,
        NULL,
        hInstance);
    if (!hwnd)
    {
        return(FALSE);
    }

    if (!SetTimer(hwnd, REFRESH_TIMER, _uRefreshRate, NULL))
    {
        TCHAR szTitle[MAX_STRING_LENGTH];
        TCHAR szText[MAX_STRING_LENGTH];
        if (LoadString(_hInstance, IDS_ERROR, szTitle, MAX_STRING_LENGTH))
        {
            if (LoadString(_hInstance, IDS_TOOMANYTIMERS, szText, MAX_STRING_LENGTH))
            {
                MessageBox(
                    hwnd,
                    szText,
                    szTitle,
                    MB_OK | MB_ICONEXCLAMATION);
            }
        }
        return(FALSE);
    }

    _hGraphStartedEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (!_hGraphStartedEvent)
    {
        TCHAR szTitle[MAX_STRING_LENGTH];
        TCHAR szText[MAX_STRING_LENGTH];
        if (LoadString(_hInstance, IDS_ERROR, szTitle, MAX_STRING_LENGTH))
        {
            if (LoadString(_hInstance, IDS_GRAPHICSEVENTFAILED, szText, MAX_STRING_LENGTH))
            {
                MessageBox(
                    hwnd,
                    szText,
                    szTitle,
                    MB_OK | MB_ICONEXCLAMATION);
            }
        }
        return(FALSE);
    }

    _hTitleEvent = CreateEvent(NULL, TRUE, TRUE, NULL);
    if (!_hTitleEvent)
    {
        TCHAR szTitle[MAX_STRING_LENGTH];
        TCHAR szText[MAX_STRING_LENGTH];
        if (LoadString(_hInstance, IDS_ERROR, szTitle, MAX_STRING_LENGTH))
        {
            if (LoadString(_hInstance, IDS_TITLEEVENTFAILED, szText, MAX_STRING_LENGTH))
            {
                MessageBox(
                    hwnd,
                    szText,
                    szTitle,
                    MB_OK | MB_ICONEXCLAMATION);
            }
        }
        return(FALSE);
    }

    // build a path to where the help file should be
    // (it should be in the same directory as the .EXE)
    // first get the exe's path
    DWORD cch = GetModuleFileName(hInstance, _szHelpFile, MAX_PATH);
    // then back up past the name of the application
    while (cch > 0 &&
        _szHelpFile[cch - 1] != TEXT('\\') &&
        _szHelpFile[cch - 1] != TEXT('/') &&
        _szHelpFile[cch - 1] != TEXT(':'))
    {
        cch--;
    }
    // finally copy the help file's name at the end of the path
    lstrcpy(&_szHelpFile[cch], TEXT(HELPFILE_STR));

    ShowWindow(hwnd, nCmdShow);
    UpdateWindow(hwnd);
    return(TRUE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::NewWindowSize
//
//  Synopsis:   updates the scroll bars every time the window's size changes
//
//  Arguments:  [uWidth]  - window width
//              [uHeight] - window height
//
//  Modifies:   _uWindowWidth, _uWindowHeight
//
//  History:    4-12-94   stevebl   Created
//
//----------------------------------------------------------------------------

void CFractalWindow::NewWindowSize(unsigned uWidth, unsigned uHeight)
{
    BOOL fRedraw = FALSE;
    _uWindowWidth = uWidth;
    _uWindowHeight = uHeight;
    unsigned uVRange = (uHeight > _uImageHeight ? 0 : _uImageHeight-uHeight);
    unsigned uHRange = (uWidth > _uImageWidth ? 0 : _uImageWidth-uWidth);
    SetScrollRange(_hwnd, SB_HORZ, 0, uHRange, FALSE);
    SetScrollRange(_hwnd, SB_VERT, 0, uVRange, FALSE);
    if (_uScrollPosX > uHRange)
    {
        fRedraw = TRUE;
        _uScrollPosX = uHRange;
    }
    if (_uScrollPosY > uVRange)
    {
        fRedraw = TRUE;
        _uScrollPosY = uVRange;
    }
    if (fRedraw)
    {
        InvalidateRect(_hwnd, NULL, FALSE);
    }
    SetScrollPos(_hwnd, SB_HORZ, _uScrollPosX, TRUE);
    SetScrollPos(_hwnd, SB_VERT, _uScrollPosY, TRUE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::OnCreate
//
//  Synopsis:   called when the window is first created (on WM_CREATE)
//              to initialize all relevent member variables
//
//  Modifies:   _hdcMem, _uScreenWidth, _uScreenHeight, etc.
//
//  History:    4-12-94   stevebl   Created
//
//  Notes:      Calls SetGraphSize to initialize the graph.
//
//----------------------------------------------------------------------------

void CFractalWindow::OnCreate(void)
{
    HDC hdc = GetDC(_hwnd);
    _hdcMem = CreateCompatibleDC(hdc);
    _uScreenWidth = GetDeviceCaps(hdc, HORZRES);
    _uScreenHeight = GetDeviceCaps(hdc, VERTRES);

    // If we don't have an animating palette with 256 colors and
    // exactly 20 static colors then we're going to disable our
    // animate palette features.

    _fCanAnimate = (GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE) != 0
        && GetDeviceCaps(hdc, SIZEPALETTE) == 256
        && GetDeviceCaps(hdc, NUMRESERVED) == 20;

    ReleaseDC(_hwnd, hdc);
    SetUpColorTable();
#ifdef START_IN_QUICKDRAWMODE
    SetGraphSize(QUICKWIDTH, QUICKHEIGHT);
#else
    SetGraphSize(_uScreenWidth, _uScreenHeight);
#endif
    SetDefaultColors();
    // Fill in the Palette's open file name structure
    _szPalFileName[0] = 0;
    _szPalFileTitle[0] = 0;
    _ofnPalette.lStructSize = sizeof(OPENFILENAME);
    _ofnPalette.hwndOwner = _hwnd;
    _ofnPalette.lpstrFilter = TEXT("Palette Files (*.PAL)\0*.PAL\0All Files (*.*)\0\\*.*\0");
    _ofnPalette.lpstrCustomFilter = NULL;
    _ofnPalette.nMaxCustFilter = 0;
    _ofnPalette.nFilterIndex = 1;
    _ofnPalette.lpstrFile = _szPalFileName;
    _ofnPalette.nMaxFile = MAX_PATH;
    _ofnPalette.lpstrInitialDir = NULL;
    _ofnPalette.lpstrFileTitle = _szPalFileTitle;
    _ofnPalette.nMaxFileTitle = MAX_PATH;
    _ofnPalette.lpstrTitle = NULL;
    _ofnPalette.lpstrDefExt = TEXT("PAL");
    _ofnPalette.Flags = OFN_HIDEREADONLY;

    // Fill in the BMP's open file name structure
    _szBmpFileName[0] = 0;
    _szBmpFileTitle[0] = 0;
    _ofnBitmap.lStructSize = sizeof(OPENFILENAME);
    _ofnBitmap.hwndOwner = _hwnd;
    _ofnBitmap.lpstrFilter = TEXT("Bitmaps (*.BMP)\0*.BMP\0All Files (*.*)\0\\*.*\0");
    _ofnBitmap.lpstrCustomFilter = NULL;
    _ofnBitmap.nMaxCustFilter = 0;
    _ofnBitmap.nFilterIndex = 1;
    _ofnBitmap.lpstrFile = _szBmpFileName;
    _ofnBitmap.nMaxFile = MAX_PATH;
    _ofnBitmap.lpstrInitialDir = NULL;
    _ofnBitmap.lpstrFileTitle = _szBmpFileTitle;
    _ofnBitmap.nMaxFileTitle = MAX_PATH;
    _ofnBitmap.lpstrTitle = NULL;
    _ofnBitmap.lpstrDefExt = TEXT("BMP");
    _ofnBitmap.Flags = OFN_HIDEREADONLY;

    // Fill in the File's open file name structure
    _szFileName[0] = 0;
    _szFileTitle[0] = 0;
    _ofnFile.lStructSize = sizeof(OPENFILENAME);
    _ofnFile.hwndOwner = _hwnd;
    _ofnFile.lpstrFilter = TEXT("Fractal Files (*.MFR)\0*.MFR\0All Files (*.*)\0\\*.*\0");
    _ofnFile.lpstrCustomFilter = NULL;
    _ofnFile.nMaxCustFilter = 0;
    _ofnFile.nFilterIndex = 1;
    _ofnFile.lpstrFile = _szFileName;
    _ofnFile.nMaxFile = MAX_PATH;
    _ofnFile.lpstrInitialDir = NULL;
    _ofnFile.lpstrFileTitle = _szFileTitle;
    _ofnFile.nMaxFileTitle = MAX_PATH;
    _ofnFile.lpstrTitle = NULL;
    _ofnFile.lpstrDefExt = TEXT("MFR");
    _ofnFile.Flags = OFN_HIDEREADONLY;
}

#include <stdio.h>

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::ChangeFractalEngines
//
//  Synopsis:   releases the old fractal engine and binds to the new one
//
//  History:    4-15-94   stevebl   Created
//
//----------------------------------------------------------------------------

void CFractalWindow::ChangeFractalEngines(void)
{
    if(_pFractalEngine)
    {
        _pFractalEngine->Release();
    }
    HRESULT hr = CoCreateInstance(
        _pEngineList->GetEntry(_uFractalType),
        NULL,
        CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER,
        IID_IFractalEngine,
        (void **)&_pFractalEngine);
    if (SUCCEEDED(hr))
    {
        hr = _pFractalEngine->Init((IFractalHost *)this);
    }
    else
    {
        _pFractalEngine = NULL;
    }
    if (SUCCEEDED(hr))
    {
        hr = _pFractalEngine->SetGraphSize(_uImageWidth, _uImageHeight);
    }
    if (SUCCEEDED(hr))
    {
        hr = _pFractalEngine->SetDefaults();
    }
    if (SUCCEEDED(hr))
    {
        hr = _pFractalEngine->GetExtent(&_dLeft, &_dTop, &_dRight, &_dBottom);
    }
    Rect(0, 0, (int)_uImageWidth - 1, (int)_uImageHeight - 1, 0);
    if (FAILED(hr))
    {
        if (_pFractalEngine)
        {
            _pFractalEngine->Release();
        }
        TCHAR szTitle[MAX_STRING_LENGTH];
        TCHAR szText[MAX_STRING_LENGTH];
        if (LoadString(_hInstance, IDS_ERROR, szTitle, MAX_STRING_LENGTH))
        {
            if (LoadString(_hInstance, IDS_NOFRACTALENGINE, szText, MAX_STRING_LENGTH))
            {
                MessageBox(
                    _hwnd,
                    szText,
                    szTitle,
                    MB_OK | MB_ICONEXCLAMATION);
            }
        }
        _pFractalEngine = NULL;
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::WindowProc
//
//  Synopsis:   main message proc for CFractalWindow
//
//  Arguments:  [uMsg]   - message
//              [wParam] - first message parameter
//              [lParam] - second message parameter
//
//  History:    4-11-94   stevebl   Created
//
//----------------------------------------------------------------------------

LRESULT CFractalWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    if (_fZooming)
    {
        switch (uMsg)
        {
        // this first set of messages fall through to the normal message
        // handler during a zoom
        case WM_PALETTECHANGED:
        case WM_QUERYNEWPALETTE:
        case WM_PAINT:
            break;

        // The next set of messages are handled differently during a zoom

        case WM_LBUTTONDBLCLK:
            // zoom in
            {
                HDC hdc = GetDC(_hwnd);
                EraseRect(hdc, &_rectZoom);
                ReleaseDC(_hwnd, hdc);
                OffsetRect(&_rectZoom, _uScrollPosX, _uScrollPosY);
                double dScaler = (_dRight - _dLeft) / (double)_uImageWidth;
                double dLeft = _dLeft
                    + (double)_rectZoom.left * dScaler;
                double dRight = _dLeft
                    + (double)_rectZoom.right * dScaler;
                double dBottom = _dBottom
                    + (double)(_uImageHeight - _rectZoom.bottom) * dScaler;
                double dTop = dBottom +
                     (_dTop - _dBottom) * (dRight - dLeft) / (_dRight - _dLeft);

                if (_pFractalEngine)
                {
                    _pFractalEngine->SetExtent(dLeft, dTop, dRight, dBottom);
                }
            }
            ReleaseCapture();
            ShowCursor(TRUE);
            _fZooming = FALSE;
            return 0;
        case WM_RBUTTONDBLCLK:
            // zoom out
            {
                HDC hdc = GetDC(_hwnd);
                EraseRect(hdc, &_rectZoom);
                ReleaseDC(_hwnd, hdc);
                OffsetRect(&_rectZoom, _uScrollPosX, _uScrollPosY);
                double dScaler = (_dRight - _dLeft) /
                    (double)(_rectZoom.right - _rectZoom.left);
                double dLeft = _dLeft
                    - (double)_rectZoom.left * dScaler;
                double dRight = _dRight +
                    (double)(_uImageWidth - _rectZoom.right) * dScaler;
                double dBottom = _dBottom -
                    (double)(_uImageHeight - _rectZoom.bottom) * dScaler;
                double dTop = _dTop +
                    (double)_rectZoom.top * dScaler;
                if (_pFractalEngine)
                {
                    _pFractalEngine->SetExtent(dLeft, dTop, dRight, dBottom);
                }
            }
            ReleaseCapture();
            ShowCursor(TRUE);
            _fZooming = FALSE;
            return 0;
        case WM_MOUSEMOVE:
            {
                HDC hdc = GetDC(_hwnd);
                EraseRect(hdc, &_rectZoom);
                POINT p;
                p.x = LOWORD(lParam);
                p.y = HIWORD(lParam);
                if (p.x > (int) _uImageWidth)
                {
                    p.x = _uImageWidth;
                }
                else if (p.x < 0)
                {
                    p.x = 0;
                }
                if (p.y > (int) _uImageHeight)
                {
                    p.y = _uImageHeight;
                }
                else if (p.y < 0)
                {
                    p.y = 0;
                }
                if (MK_RBUTTON == wParam || MK_LBUTTON == wParam)
                {
                    // change size of the zoom window
                    int dx, dy;
                    BOOL fChangeX;
                    fChangeX = abs(p.y - _rectZoom.bottom) >
                        abs(p.x - _rectZoom.right);
                    _rectZoom.bottom = p.y;
                    _rectZoom.right = p.x;
                    if (_rectZoom.right <= _rectZoom.left)
                    {
                        _rectZoom.right = _rectZoom.left + 1;
                    }
                    if (_rectZoom.bottom <= _rectZoom.top)
                    {
                        _rectZoom.bottom = _rectZoom.top + 1;
                    }
                    dx = _rectZoom.right - _rectZoom.left;
                    dy = _rectZoom.bottom - _rectZoom.top;
                    if (dx < 4)
                    {
                        dx = 4;
                        _rectZoom.right = _rectZoom.left + 4;
                    }
                    if (dy < 4)
                    {
                        dy = 4;
                        _rectZoom.bottom = _rectZoom.top + 4;
                    }
                    // normalize the zoom window
                    if (fChangeX)
                    {
                        _rectZoom.right = _rectZoom.left +
                            MulDiv(dy, _uImageWidth, _uImageHeight);
                    }
                    else
                    {
                        _rectZoom.bottom = _rectZoom.top +
                            MulDiv(dx, _uImageHeight, _uImageWidth);
                    }
                    p.x = _rectZoom.right;
                    p.y = _rectZoom.bottom;
                    ClientToScreen(_hwnd, &p);
                    SetCursorPos(p.x, p.y);
                }
                else
                {
                    // move the zoom window
                    OffsetRect(
                        &_rectZoom,
                        p.x - _rectZoom.right,
                        p.y - _rectZoom.bottom);
                    OffsetRect(
                        &_rectZoom,
                        _rectZoom.left < 0 ? 0 - _rectZoom.left : 0,
                        _rectZoom.top < 0 ? 0 - _rectZoom.top : 0);
                    OffsetRect(
                        &_rectZoom,
                        _rectZoom.right > (int) _uImageWidth ? _uImageWidth - _rectZoom.right : 0,
                        _rectZoom.bottom > (int) _uImageHeight ? _uImageHeight - _rectZoom.bottom : 0);
                }
                DrawRect(hdc, &_rectZoom);
                ReleaseDC(_hwnd, hdc);
                return 0;
            }
            // change zoom rectangle
            break;
        case WM_KEYUP:
            if (wParam == VK_ESCAPE)
            {
                // abort the zoom window
                HDC hdc = GetDC(_hwnd);
                EraseRect(hdc, &_rectZoom);
                ReleaseDC(_hwnd, hdc);
                ReleaseCapture();
                ShowCursor(TRUE);
                _fZooming = FALSE;
            }
            else
                return(DefWindowProc(_hwnd, uMsg, wParam, lParam));
            return 0;

        // All other messages are swallowed up and sent
        // to the DefWindowProc during a zoom.
        default:
            return(DefWindowProc(_hwnd, uMsg, wParam, lParam));
        }
    }
    switch (uMsg)
    {
    case WM_CREATE:
        OnCreate();
        break;
    case WM_SHOWWINDOW:
        if (!_pEngineList)
        {
            // this is the first time the window is being shown

            HMENU hmenu = GetMenu(_hwnd);
            if (!_fCanAnimate)
            {
                EnableMenuItem(
                    hmenu,
                    IDM_CYCLEIN,
                    MF_BYCOMMAND | MF_GRAYED);
                EnableMenuItem(
                    hmenu,
                    IDM_CYCLEOUT,
                    MF_BYCOMMAND | MF_GRAYED);
                EnableMenuItem(
                    hmenu,
                    IDM_CYCLESTOP,
                    MF_BYCOMMAND | MF_GRAYED);
            }

            _pEngineList = new CEngineList(hmenu);
            if (_pEngineList)
            {
                if SUCCEEDED(_pEngineList->Read())
                {
                    _uFractalType = 0;
                    ChangeFractalEngines();
                    if (_pFractalEngine)
                    {
                        SetDefaultColors();
                        if (SUCCEEDED(_pFractalEngine->Start()))
                        {
                            OnStart();
                        }
                        else
                        {
                            OnStop();
                        }
                    }
                }
                else
                {
                    // couldn't prime the engine list
                    TCHAR szTitle[MAX_STRING_LENGTH];
                    TCHAR szText[MAX_STRING_LENGTH];
                    if (LoadString(_hInstance, IDS_ERROR, szTitle, MAX_STRING_LENGTH))
                    {
                        if (LoadString(_hInstance, IDS_REGISTRYERROR, szText, MAX_STRING_LENGTH))
                        {
                            MessageBox(
                                _hwnd,
                                szText,
                                szTitle,
                                MB_OK | MB_ICONEXCLAMATION);
                        }
                    }
                    SendMessage(_hwnd, WM_CLOSE, 0, 0);
                }
            }
        }
        PostMessage(_hwnd, WM_REFRESHCOLORS, 0, 0);
        break;
    case WM_REFRESHCOLORS:
        {
            PALETTEENTRY rgColors[PALETTESIZE];
            memcpy(rgColors, _pEntry1, sizeof(PALETTEENTRY) * PALETTESIZE);
            Wash(PALETTESIZE, rgColors);
        }
        break;
    case WM_SIZE:
        NewWindowSize(LOWORD(lParam), HIWORD(lParam));
        break;
    case WM_HSCROLL:
        switch (LOWORD(wParam))
        {
        case SB_PAGEUP:
            _uScrollPosX = (_uScrollPosX > _uWindowWidth ?
                _uScrollPosX - _uWindowWidth :
                0);
            break;
        case SB_PAGEDOWN:
            _uScrollPosX = (_uScrollPosX + _uWindowWidth * 2 < _uImageWidth ?
                _uScrollPosX + _uWindowWidth :
                _uImageWidth - _uWindowWidth);
            break;
        case SB_LINEUP:
            _uScrollPosX = (_uScrollPosX > SCROLL_STEP ?
                _uScrollPosX - SCROLL_STEP :
                0);
            break;
        case SB_LINEDOWN:
            _uScrollPosX = (_uScrollPosX + _uWindowWidth + SCROLL_STEP < _uImageWidth ?
                _uScrollPosX + SCROLL_STEP :
                _uImageWidth - _uWindowWidth);
            break;
        case SB_THUMBTRACK:
            _uScrollPosX = HIWORD(wParam);
            break;
        }
        SetScrollPos(_hwnd, SB_HORZ, _uScrollPosX, TRUE);
        InvalidateRect(_hwnd, NULL, FALSE);
        break;
    case WM_VSCROLL:
        switch (LOWORD(wParam))
        {
        case SB_PAGEUP:
            _uScrollPosY = (_uScrollPosY > _uWindowHeight ?
                _uScrollPosY - _uWindowHeight :
                0);
            break;
        case SB_PAGEDOWN:
            _uScrollPosY = (_uScrollPosY + _uWindowHeight * 2 < _uImageHeight ?
                _uScrollPosY + _uWindowHeight :
                _uImageHeight - _uWindowHeight);
            break;
        case SB_LINEUP:
            _uScrollPosY = (_uScrollPosY > SCROLL_STEP ?
                _uScrollPosY - SCROLL_STEP :
                0);
            break;
        case SB_LINEDOWN:
            _uScrollPosY = (_uScrollPosY + _uWindowHeight + SCROLL_STEP < _uImageHeight ?
                _uScrollPosY + SCROLL_STEP :
                _uImageHeight - _uWindowHeight);
            break;
        case SB_THUMBTRACK:
            _uScrollPosY = HIWORD(wParam);
            break;
        }
        SetScrollPos(_hwnd, SB_VERT, _uScrollPosY, TRUE);
        InvalidateRect(_hwnd, NULL, FALSE);
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            RECT rc;
              EnterCriticalSection(&_csGraph);
            HDC hdc = BeginPaint(_hwnd, &ps);
            HPALETTE hOldPal =  SelectPalette(hdc, _hpal, FALSE);
            RealizePalette(hdc);
            if (GetClipBox(ps.hdc, &rc) != NULLREGION)
            {
                BitBlt(ps.hdc,
                    rc.left,
                    rc.top,
                    rc.right - rc.left,
                    rc.bottom - rc.top,
                    _hdcMem,
                    rc.left + _uScrollPosX,
                    rc.top + _uScrollPosY,
                    SRCCOPY);
            }
            SelectPalette(hdc, hOldPal, TRUE);
            RealizePalette(hdc);
            EndPaint(_hwnd, &ps);
            LeaveCriticalSection(&_csGraph);
            break;
        }
    case WM_PALETTECHANGED:
        if ((HWND)wParam == _hwnd)
        {
            break;
        }
    case WM_QUERYNEWPALETTE:
        {
            if (_fReRealizePalette && GetFocus() == _hwnd)
            {
                // The first time we realized this
                // palette, it wasn't in the forground.  This
                // means that we must realize it again to ensure
                // that we get the entire palette to ourselves.

                UnrealizeObject(_hpal);
                _fReRealizePalette = FALSE;
            }
            HDC hdc = GetDC(_hwnd);
            HPALETTE hOldPal = SelectPalette(hdc, _hpal, FALSE);
            int iTemp = RealizePalette(hdc);

            // this next step is really unnecessary since
            SelectPalette(hdc, hOldPal, TRUE);
            RealizePalette(hdc);
            ReleaseDC(_hwnd, hdc);
            if (iTemp)
            {
                InvalidateRect(_hwnd, NULL, TRUE);
            }
            return(iTemp);
        }

    // When the title is not shown and mouse is clicked in client area,
    // we fool the system into thinking that the mouse has been clicked
    // on the caption (see WM_NCHITTEST message handling).  But this means
    // that the system thinks that a left button double click in the client
    // area when the title is not displayed is a non-client double click.
    // Therefore when the title is not displayed, we get WM_NCLBUTTONDBLCLK
    // messages instead of WM_LBUTTONDBLCLK messages, but need to handle
    // them the same way.

    case WM_NCLBUTTONDBLCLK:
        if (WaitForSingleObject(_hTitleEvent,0) == WAIT_OBJECT_0)
        {
            break;
        }
        // else FALL THROUGH to WM_LBUTTONDBLCLK case
        //

    case WM_LBUTTONDBLCLK:
        ToggleTitle();
        return(0);

    case WM_NCRBUTTONDOWN:
        if (WaitForSingleObject(_hTitleEvent,0) == WAIT_OBJECT_0)
        {
            break;
        }
    case WM_RBUTTONDOWN:
        _fRButtonDown = TRUE;
        break;

    case WM_NCRBUTTONUP:
        if (WaitForSingleObject(_hTitleEvent,0) == WAIT_OBJECT_0)
        {
            break;
        }
    case WM_RBUTTONUP:
        if (_fRButtonDown && WaitForSingleObject(_hGraphStartedEvent,0) == WAIT_TIMEOUT)
        {
            SendMessage(_hwnd, WM_COMMAND, IDM_EDIT_PROPERTIES, 0);
            _fRButtonDown = FALSE;
        }
        break;

    case WM_NCHITTEST:  // stolen from clock program

wParam = DefWindowProc(_hwnd, uMsg, wParam, lParam);
        if (WaitForSingleObject(_hTitleEvent,0) != WAIT_OBJECT_0 && (wParam == HTCLIENT) && !IsZoomed(_hwnd))
        {
            return HTCAPTION;
        }
        return wParam;
        case WM_TIMER:
        if (wParam == CYCLE_TIMER)
        {
            // Cycle the colors
            RGBQUAD rgb[PALETTESIZE];
            for (int c = PALETTESIZE - 1; c--;)
            {
                int iTemp = c + _iCycleVelocity;
                while (iTemp < 0)
                {
                    iTemp += PALETTESIZE - 1;
                }
                while (iTemp >= PALETTESIZE - 1)
                {
                    iTemp -= PALETTESIZE - 1;
                }
                _pEntry2[c + 1] = _pEntry1[iTemp + 1];
                rgb[c].rgbRed = _pEntry2[c + 1].peRed;
                rgb[c].rgbBlue = _pEntry2[c + 1].peBlue;
                rgb[c].rgbGreen = _pEntry2[c + 1].peGreen;
            }
            _pEntry2[0] = _pEntry1[0];
            PALETTEENTRY * pEntryTemp = _pEntry1;
            _pEntry1 = _pEntry2;
            _pEntry2 = pEntryTemp;
            EnterCriticalSection(&_csGraph);
            SetDIBColorTable(_hdcMem, 11, PALETTESIZE - 1, rgb);
            AnimatePalette(_hpal, 11, PALETTESIZE - 1, &_pEntry1[1]);
            LeaveCriticalSection(&_csGraph);
        }
        if (wParam == REFRESH_TIMER)
        {
            if (WaitForSingleObject(_hGraphStartedEvent, 0) == WAIT_OBJECT_0)
            {
                InvalidateRect(_hwnd, &_rectPainted, FALSE);
                SetRectEmpty(&_rectPainted);
            }
        }
        return(0);
    case WM_COMMAND:
        return DoMenu(wParam, lParam);
    case WM_QUIT:
    case WM_CLOSE:
    default:
        return(DefWindowProc(_hwnd, uMsg, wParam, lParam));
    }
    return(FALSE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::DoMenu
//
//  Synopsis:   processes menu commands
//
//  Arguments:  [wParam] - menu command
//              [lParam] - command parameter
//
//  History:    4-12-94   stevebl   Created
//
//----------------------------------------------------------------------------

LRESULT CFractalWindow::DoMenu(WPARAM wParam, LPARAM lParam)
{
    switch (LOWORD(wParam))
    {
    case IDM_ABOUT:
        {
            CAbout dlgAbout;
            dlgAbout.ShowDialog(_hInstance, MAKEINTRESOURCE(IDM_ABOUT), _hwnd);
        }
        break;
    case IDM_CHANGEDIM:
        if (_pFractalEngine)
        {
            unsigned uSavedImageWidth = _uImageWidth;
            unsigned uSavedImageHeight = _uImageHeight;
            CChangeDim dlgChangeDim(this);
            if (dlgChangeDim.ShowDialog(_hInstance, MAKEINTRESOURCE(IDM_CHANGEDIM), _hwnd))
            {
                if (SUCCEEDED(_pFractalEngine->SetGraphSize(_uImageWidth, _uImageHeight)))
                {
                    _pFractalEngine->SetDefaults();
                }
                else
                {
                    _uImageWidth = uSavedImageWidth;
                    _uImageHeight = uSavedImageHeight;
                }
            }
            else
            {
                if (FAILED(_pFractalEngine->SetGraphSize(_uImageWidth, _uImageHeight)))
                {
                    _uImageWidth = uSavedImageWidth;
                    _uImageHeight = uSavedImageHeight;
                }
            }
        }
        break;
    case IDM_START:
        if (_pFractalEngine)
        {
            if (SUCCEEDED(_pFractalEngine->Start()))
            {
                _pFractalEngine->GetExtent(&_dLeft, &_dTop, &_dRight, &_dBottom);
                OnStart();
            }
        }
        break;
    case IDM_STOP:
        if (_pFractalEngine)
        {
            if (SUCCEEDED(_pFractalEngine->Stop()))
            {
                OnStop();
            }
        }
        break;
    case IDM_EDIT_PROPERTIES:
        if (_pFractalEngine)
        {
            _pFractalEngine->SetProperties(_hwnd);
        }
        break;
    case IDM_BRING_FORWARD:
        ToggleTitle();
        break;
    case IDM_EXIT:
        SendMessage(_hwnd, WM_CLOSE, 0, 0);
        break;
    case IDM_ZOOM:
        if (WaitForSingleObject(_hGraphStartedEvent,0) == WAIT_TIMEOUT)
        {
            SetCapture(_hwnd);
            ShowCursor(FALSE);
            SetRect(
                &_rectZoom,
                2 * _uImageWidth / 5,
                2 * _uImageHeight / 5,
                3 * _uImageWidth / 5,
                3 * _uImageHeight / 5);
            HDC hdc = GetDC(_hwnd);
            DrawRect(hdc, &_rectZoom);
            ReleaseDC(_hwnd, hdc);
            _fZooming = TRUE;
        }
        break;
    case IDM_HELP:
        if (!Exists(_szHelpFile))
        {
            TCHAR szTitle[MAX_STRING_LENGTH];
            TCHAR szText[MAX_STRING_LENGTH];
            if (LoadString(_hInstance, IDS_ERROR, szTitle, MAX_STRING_LENGTH))
            {
                if (LoadString(_hInstance, IDS_NOHELPFILE, szText, MAX_STRING_LENGTH))
                {
                    MessageBox(
                        _hwnd,
                        szText,
                        szTitle,
                        MB_OK | MB_ICONEXCLAMATION);
                }
            }
        }
        else
        {
            WinHelp(_hwnd, _szHelpFile, HELP_INDEX, 0);
        }
        break;
    case IDM_DEFAULTCOLORS:
        SetDefaultColors();
        break;
    case IDM_LOADPAL:
        if (!GetOpenFileName((LPOPENFILENAME)&_ofnPalette))
        {
            return(FALSE);
        }
        LoadPalette();
        break;
    case IDM_SAVEPAL:
        if (!GetSaveFileName((LPOPENFILENAME)&_ofnPalette))
        {
            return(FALSE);
        }
        SavePalette();
        break;
    case IDM_SAVEBMP:
        if (!GetSaveFileName((LPOPENFILENAME)&_ofnBitmap))
        {
            return(FALSE);
        }
        SaveBitmap();
        break;
    case IDM_LOAD:
        if (!GetOpenFileName((LPOPENFILENAME)&_ofnFile))
        {
            return(FALSE);
        }
        LoadFile();
        break;
    case IDM_SAVE:
        if (!GetSaveFileName((LPOPENFILENAME)&_ofnFile))
        {
            return(FALSE);
        }
        SaveFile();
        break;
    case IDM_CYCLEIN:
        if (_fCanAnimate)
        {
            Faster(-1);
        }
        break;
    case IDM_CYCLEOUT:
        if (_fCanAnimate)
        {
            Faster(1);
        }
        break;
    case IDM_CYCLESTOP:
        if (_fCanAnimate)
        {
            CycleStop();
        }
        break;
    default:
        if (WaitForSingleObject(_hGraphStartedEvent,0) == WAIT_TIMEOUT &&
            LOWORD(wParam) >= IDM_FRACTAL_TYPE &&
            LOWORD(wParam) < IDM_FRACTAL_TYPE + MAX_FRACTAL_TYPES &&
            LOWORD(wParam) - IDM_FRACTAL_TYPE != (int)_uFractalType)
        {
            // changing fractal types
            HMENU hmenu = GetMenu(_hwnd);
            CheckMenuItem(
                hmenu,
                _uFractalType + IDM_FRACTAL_TYPE,
                MF_BYCOMMAND | MF_UNCHECKED);
            CheckMenuItem(
                hmenu,
                LOWORD(wParam),
                MF_BYCOMMAND | MF_CHECKED);
            _uFractalType = LOWORD(wParam) - IDM_FRACTAL_TYPE;
            ChangeFractalEngines();
        }
        else
        {
            return(DefWindowProc(_hwnd, WM_COMMAND, wParam, lParam));
        }
    }
    return(FALSE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::CFractalWindow
//
//  Synopsis:   constructor
//
//  History:    4-11-94   stevebl   Created
//
//----------------------------------------------------------------------------

CFractalWindow::CFractalWindow()
{
    _pstg = NULL;
    _rgPixels = NULL;
    _hInstance = NULL;
    _hwnd = NULL;
    _hdcMem = NULL;
    _uScreenWidth = 0;
    _uScreenHeight = 0;
    _uImageWidth = 0;
    _uImageHeight = 0;
    _uWindowWidth = 0;
    _uWindowHeight = 0;
    _uScrollPosX = 0;
    _uScrollPosY = 0;
    _iCycleVelocity = 0;
    _iCurrentDirection = 0;
    _uRefreshRate = 1000;
    _hTitleEvent = NULL;
    _hGraphStartedEvent = NULL;
    _hbmSavedBitmap = NULL;
    _hbmMainWindow = NULL;
    _hpal = NULL;
    _pLogPal = NULL;
    _pSecondLogPal = NULL;
    _pEntry1 = NULL;
    _pEntry2 = NULL;
    _uRefCount = 0;
    _pEngineList = 0;
    _uFractalType = 0;
    _pFractalEngine = NULL;
    _fZooming = FALSE;
    _fRButtonDown = FALSE;
    InitializeCriticalSection(&_csGraph);
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::~CFractalWindow
//
//  Synopsis:   destructor
//
//  History:    4-11-94   stevebl   Created
//
//  Notes:      Frees up all resources allocated by the window.
//
//----------------------------------------------------------------------------

CFractalWindow::~CFractalWindow()
{
    if (_pFractalEngine)
    {
        _pFractalEngine->Stop();
    }
    WinHelp(_hwnd, _szHelpFile, HELP_QUIT, 0);
    if (_pFractalEngine)
    {
        _pFractalEngine->Release();
    }
    if (_hTitleEvent)
    {
        CloseHandle(_hTitleEvent);
    }
    if (_hGraphStartedEvent)
    {
        CloseHandle(_hGraphStartedEvent);
    }
    if (_hbmSavedBitmap)
    {
        HDC hdc = GetDC(_hwnd);
        HBITMAP hbm = (HBITMAP) SelectObject(hdc,_hbmSavedBitmap);
        ReleaseDC(_hwnd, hdc);
        DeleteObject(hbm);
    }
    if (_pLogPal)
    {
        LocalFree(_pLogPal);
    }
    if (_pSecondLogPal)
    {
        LocalFree(_pSecondLogPal);
    }
    if (_pEntry1)
    {
        LocalFree(_pEntry1);
    }
    if (_pEntry2)
    {
        LocalFree(_pEntry2);
    }
    if (_pEngineList)
    {
        delete _pEngineList;
    }
    DeleteCriticalSection(&_csGraph);
    PostQuitMessage(0);
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::SetUpColorTable
//
//  Synopsis:   initializes the color palette
//
//  Modifies:   _pEntry1, _pEntry2, _pLogPal
//
//  History:    4-12-94   stevebl   Created
//
//----------------------------------------------------------------------------

void CFractalWindow::SetUpColorTable(void)
{
    _pLogPal = (LOGPALETTE *) LocalAlloc(LMEM_FIXED,
        (sizeof(LOGPALETTE)+
        (sizeof(PALETTEENTRY)*(255))));

    _pEntry1 = (PALETTEENTRY *) LocalAlloc(LMEM_FIXED, sizeof(PALETTEENTRY)*(PALETTESIZE));
    _pEntry2 = (PALETTEENTRY *) LocalAlloc(LMEM_FIXED, sizeof(PALETTEENTRY)*(PALETTESIZE));
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::SetDefaultColors
//
//  Synopsis:   sets the default colors in the color palette wich are:
//                  black - background
//                  Then red
//                  washing to green
//                  washing to blue
//                  and washing back to red again.
//
//  History:    4-12-94   stevebl   Created
//
//----------------------------------------------------------------------------

void CFractalWindow::SetDefaultColors(void)
{
    PALETTEENTRY rgColors[4];

    rgColors[0].peRed = 0;
    rgColors[0].peGreen = 0;
    rgColors[0].peBlue = 0;

    rgColors[1].peRed = 255;
    rgColors[1].peGreen = 0;
    rgColors[1].peBlue = 0;

    rgColors[2].peRed = 0;
    rgColors[2].peGreen = 255;
    rgColors[2].peBlue = 0;

    rgColors[3].peRed = 0;
    rgColors[3].peGreen = 0;
    rgColors[3].peBlue = 255;

    Wash(4,rgColors);
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::Faster
//
//  Synopsis:   increses or decreses the palette's cycle velocity
//
//  Arguments:  [iDirection] - direction to cycle the palette
//                             -1 is in, 1 is out
//
//  History:    4-20-94   stevebl   Created
//
//----------------------------------------------------------------------------

void CFractalWindow::Faster(int iDirection)
{
    if (_iCycleVelocity == 0)
    {
        SetTimer(_hwnd, CYCLE_TIMER, CYCLE_RATE, NULL);
        _iCurrentDirection = _iCycleVelocity = iDirection;
    }
    else if (_iCycleVelocity == -iDirection)
    {
        CycleStop();
    }
    else if (iDirection == _iCurrentDirection)
    {
        _iCycleVelocity *= 2;
    }
    else
    {
        _iCycleVelocity /= 2;
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::CycleStop
//
//  Synopsis:   stops color cycling
//
//  History:    4-20-94   stevebl   Created
//
//----------------------------------------------------------------------------

void CFractalWindow::CycleStop(void)
{
    if (_iCycleVelocity)
    {
        KillTimer(_hwnd, CYCLE_TIMER);
        _iCycleVelocity = 0;
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::Wash
//
//  Synopsis:   "washes" a set of colors over the last n-1 colors in the
//              color palette, smoothly cycling the colors over the entire
//              range
//
//  Arguments:  [i]        - number of colors to wash
//              [rgColors] - array of colors
//
//  Modifies:   _pEntry1, _pEntry2, _hpal, _pLogPal
//
//  History:    4-12-94   stevebl   Created
//
//  Notes:      Color 0 is the graph background (or points inside the graph
//              set where appropriate).  It is always the first palette entry.
//              The other colors are cycled or washed over the remaining
//              palette entries so that they smoothly transition from one
//              to the next.
//
//----------------------------------------------------------------------------

void CFractalWindow::Wash(int i, PALETTEENTRY rgColors[])
{
    HPALETTE hPalOld;

    int c;
    int iFirst, iNext, iSource, iDestination;
    int iSpread, iIndex;

    EnterCriticalSection(&_csGraph);

    // Remember if we were in the foreground when we realized
    // our main window's palette.  If we're not in the foreground then we
    // will have to do it again when we get the WM_QUERYNEWPALETTE message.

    _fReRealizePalette = GetFocus() != _hwnd;

    // Now create an all black palette and realize it to rudely bump
    // everyone elese's colors out of the system palette.

    _pLogPal->palVersion    = 0x300;
    _pLogPal->palNumEntries = 256;

    for (c = 256; c--; )
    {
        _pLogPal->palPalEntry[c].peRed = 0;
        _pLogPal->palPalEntry[c].peGreen = 0;
        _pLogPal->palPalEntry[c].peBlue = 0;
        _pLogPal->palPalEntry[c].peFlags = PC_NOCOLLAPSE;
    }
    HPALETTE hPalTemp = CreatePalette((LPLOGPALETTE)_pLogPal);
    HDC hdc = GetDC(_hwnd);
    hPalOld = SelectPalette(hdc, hPalTemp, 0);
    RealizePalette(hdc);
    DeleteObject(hPalOld);

    // Now we're ready to create the palette that we're really interested in.

    _pLogPal->palVersion    = 0x300;
    _pLogPal->palNumEntries = 256;

    // First we'll get the system palette and initialize our palette to it.

    GetSystemPaletteEntries(hdc, 0, 256, &_pLogPal->palPalEntry[0]);

    // Here we'll make sure that everything in the palette (other than the
    // system colors) gets marked at least PC_NOCOLLAPSE.  This way
    // we can rudely be sure that no other app will be able to slow our
    // app down by destroying our one to one identity mapping with the
    // system palette.

    for (c = 245; c > PALETTESIZE + 10; c--)
    {
        _pLogPal->palPalEntry[c].peFlags = PC_NOCOLLAPSE;
    }

    // Now we'll fill in the rest of the colors that we're interested in,
    // marking them PC_RESERVED so that they can be animated.

    _pEntry1[0].peRed = _pLogPal->palPalEntry[10].peRed = rgColors[0].peRed;
    _pEntry1[0].peGreen = _pLogPal->palPalEntry[10].peGreen = rgColors[0].peGreen;
    _pEntry1[0].peBlue = _pLogPal->palPalEntry[10].peBlue = rgColors[0].peBlue;
    _pEntry1[0].peFlags = _pLogPal->palPalEntry[10].peFlags =
        _fCanAnimate ? PC_RESERVED : PC_NOCOLLAPSE;
    for (c = 0; c < i - 1 ; c++)
    {
        iSource = c + 1;
        iDestination = (c+1)%(i-1)+1;
        iFirst = 1 + MulDiv(c, PALETTESIZE - 1, i - 1);
        iNext = 1 + MulDiv(c + 1, PALETTESIZE - 1, i - 1);
        iSpread = iNext-iFirst;
        for (iIndex = iSpread; iIndex--; )
        {
            _pEntry1[iIndex + iFirst].peRed =
                _pLogPal->palPalEntry[iIndex + iFirst + 10].peRed =
                    (rgColors[iSource].peRed * (iSpread-iIndex)) / iSpread +
                    (rgColors[iDestination].peRed * iIndex) / iSpread;
            _pEntry1[iIndex + iFirst].peGreen =
                _pLogPal->palPalEntry[iIndex + iFirst + 10].peGreen =
                    (rgColors[iSource].peGreen * (iSpread-iIndex)) / iSpread +
                    (rgColors[iDestination].peGreen * iIndex) / iSpread;
            _pEntry1[iIndex + iFirst].peBlue =
                _pLogPal->palPalEntry[iIndex + iFirst + 10].peBlue =
                    (rgColors[iSource].peBlue * (iSpread-iIndex)) / iSpread +
                    (rgColors[iDestination].peBlue * iIndex) / iSpread;
            _pEntry1[iIndex + iFirst].peFlags =
                _pLogPal->palPalEntry[iIndex + iFirst + 10].peFlags =
                _fCanAnimate ? PC_RESERVED : PC_NOCOLLAPSE;
        }
    }

    _hpal = CreatePalette((LPLOGPALETTE) _pLogPal);
    hPalOld = SelectPalette(hdc, _hpal, 0);
    RealizePalette(hdc);
    ReleaseDC(_hwnd, hdc);
    DeleteObject(hPalOld);

    // now select the same palette into the memory DC

    hPalOld = SelectPalette (_hdcMem, _hpal, 0);
    RealizePalette(_hdcMem);
    DeleteObject(hPalOld);

    // prepare the dibsection's color table to exactly match the palette

    RGBQUAD rgb[256];
    for (int x = 256; x--;)
    {
        rgb[x].rgbRed = _pLogPal->palPalEntry[x].peRed;
        rgb[x].rgbGreen = _pLogPal->palPalEntry[x].peGreen;
        rgb[x].rgbBlue = _pLogPal->palPalEntry[x].peBlue;
        rgb[x].rgbReserved = 0;
    }

    SetDIBColorTable(_hdcMem, 0, 256, rgb);

    // force the bitmap to be repainted
    InvalidateRect(_hwnd, NULL, FALSE);
    LeaveCriticalSection(&_csGraph);
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::LoadPalette
//
//  Synopsis:   Loads the palette from a text file.
//
//  History:    4-21-94   stevebl   Created
//
//----------------------------------------------------------------------------

void CFractalWindow::LoadPalette(void)
{
    HANDLE h;
    PALETTEENTRY rgColors[PALETTESIZE];
    int c;
    char sz[256];
    DWORD dwCount;
    h = CreateFile(_szPalFileName,
        GENERIC_READ,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        0,
        0);
    if (h != INVALID_HANDLE_VALUE)
    {
        for (c = 0; c < PALETTESIZE; c++)
        {
            int n = 0;
            do
            {
                ReadFile(h,&sz[n++], 1, &dwCount, NULL);
            } while (sz[n-1] != '\n' && dwCount && n < 255);
            if (n != 1)
            {
                unsigned u1, u2, u3;
                if(sscanf(sz, "%u %u %u",
                    &u1,
                    &u2,
                    &u3) != 3)
                {
                    break;
                }
                rgColors[c].peRed = u1;
                rgColors[c].peGreen = u2;
                rgColors[c].peBlue = u3;
            }
            else break;
        }
        CloseHandle(h);
        Wash(c,rgColors);
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::SavePalette
//
//  Synopsis:   Saves the palette to a text file.
//
//  History:    4-21-94   stevebl   Created
//
//----------------------------------------------------------------------------

void CFractalWindow::SavePalette(void)
{
    HANDLE h;
    int c;
    char sz[256];
    DWORD dwCount;
    h = CreateFile(_szPalFileName,
        GENERIC_WRITE,
        0,
        NULL,
        CREATE_ALWAYS,
        FILE_ATTRIBUTE_NORMAL,
        0);
    if (h != INVALID_HANDLE_VALUE)
    {
        for (c = 0; c < PALETTESIZE; c++)
        {
            sprintf(sz, "%u %u %u\n",
                _pEntry1[c].peRed,
                _pEntry1[c].peGreen,
                _pEntry1[c].peBlue);
            WriteFile(h, sz, strlen(sz), &dwCount, NULL);
        }
        CloseHandle(h);
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::SaveBitmap
//
//  Synopsis:   saves the graph as a standard device independent bitmap
//
//  History:    4-21-94   stevebl   Created
//
//----------------------------------------------------------------------------

void CFractalWindow::SaveBitmap(void)
{
    HANDLE hdibCurrent = DibFromBitmap(_hbmMainWindow, BI_RGB, 0, _hpal);
    StartWait();
#ifndef UNICODE
    // convert file name to a wide string
    WCHAR wszBmpFileName[MAX_PATH];
    mbstowcs(wszBmpFileName, _szBmpFileName, MAX_PATH);
    if (!WriteDIB(wszBmpFileName, hdibCurrent))
#else
    if (!WriteDIB(_szBmpFileName, hdibCurrent))
#endif
    {
        EndWait();
        TCHAR szTitle[MAX_STRING_LENGTH];
        TCHAR szText[MAX_STRING_LENGTH];
        if (LoadString(_hInstance, IDS_ERROR, szTitle, MAX_STRING_LENGTH))
        {
            if (LoadString(_hInstance, IDS_SAVEBMPFAILED, szText, MAX_STRING_LENGTH))
            {
                MessageBox(
                    _hwnd,
                    szText,
                    szTitle,
                    MB_OK | MB_ICONEXCLAMATION);
            }
        }
    }
    else
    {
        EndWait();
        TCHAR szText[MAX_STRING_LENGTH];
        if (LoadString(_hInstance, IDS_SAVEBMPSUCCEEDED, szText, MAX_STRING_LENGTH))
        {
            MessageBox(
                _hwnd,
                szText,
                TEXT(APP_TITLE),
                MB_OK | MB_ICONEXCLAMATION);
        }
    }
    GlobalFree(hdibCurrent);
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::LoadFile
//
//  Synopsis:   loads a fractal file
//
//  History:    4-22-94   stevebl   Created
//
//----------------------------------------------------------------------------

void CFractalWindow::LoadFile(void)
{
    StartWait();

#ifndef UNICODE
    // convert file name to a wide string
    WCHAR wszFileName[MAX_PATH];
    mbstowcs(wszFileName, _szFileName, MAX_PATH);
#endif
    // get IStorage
    HRESULT hr = StgOpenStorage(
#ifdef UNICODE
        _szFileName,
#else
        wszFileName,
#endif
        NULL,
        STGM_TRANSACTED | STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
        NULL,
        0,
        &_pstg);
    if (SUCCEEDED(hr))
    {
        IStream * pstm;

        // load properies from "Properties" stream using OleLoadFromStream

        hr = _pstg->OpenStream(
            OLESTR("Properties"),
            0,
            STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
            0,
            &pstm);
        if (SUCCEEDED(hr))
        {
            IFractalEngine * pFractalEngine;
            IPersistStream * pFractalEngineStream;
            hr = OleLoadFromStream(pstm, IID_IFractalEngine, (void **)&pFractalEngine);
            if (SUCCEEDED(hr))
            {
                if (_pFractalEngine)
                {
                    _pFractalEngine->Release();
                }
                _pFractalEngine = pFractalEngine;
                hr = pFractalEngine->QueryInterface( IID_IPersistStream, (void**)&pFractalEngineStream);
                if (SUCCEEDED(hr) )
                {
                    // Tell the new fractal engine where our IFractalHost pointer is.

                    _pFractalEngine->Init((IFractalHost *)this);
                    HMENU hmenu = GetMenu(_hwnd);

                    // Clear our check mark by our old fractal type.

                    CheckMenuItem(
                        hmenu,
                        _uFractalType + IDM_FRACTAL_TYPE,
                        MF_BYCOMMAND | MF_UNCHECKED);

                    // And check our new fractal type.

                    CLSID cid;
                    pFractalEngineStream->GetClassID(&cid);
                    pFractalEngineStream->Release();
                    int iIndex = _pEngineList->FindEntry(cid);
                    if (iIndex >= 0)
                    {
                        _uFractalType = (unsigned) iIndex;
                        CheckMenuItem(
                            hmenu,
                            _uFractalType + IDM_FRACTAL_TYPE,
                            MF_BYCOMMAND | MF_CHECKED);
                    }
                    else
                    {
                        // This is a previously unknown fractal type.
                        // HOWEVER it could be loaded and so there is
                        // nothing keeping us from adding it to our
                        // list of known fractal engines.

                        // At this time there is no mechanism for adding
                        // fractal engines to the Engine List object.
                        // This would be an easy thing to add at some time
                        // in the future.

                        // For now, we'll allow this condition (since there
                        // is no reason not to) but we'll do
                        // nothing about it.
                    }
                    _pFractalEngine->GetExtent(&_dLeft, &_dTop, &_dRight, &_dBottom);
                }
            }
            pstm->Release();

        }

        // Load image from "Image" stream

        if (SUCCEEDED(hr))
        {
            hr = _pstg->OpenStream(
                OLESTR("Image"),
                0,
                STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
                0,
                &pstm);
            if (SUCCEEDED(hr))
            {
                unsigned uWidth, uHeight;
                hr = pstm->Read(&uWidth, sizeof(unsigned), NULL);
                if (SUCCEEDED(hr))
                {
                    hr = pstm->Read(&uHeight, sizeof(unsigned), NULL);
                }
                if (SUCCEEDED(hr))
                {
                    hr = SetGraphSize(uWidth, uHeight);
                    if (SUCCEEDED(hr))
                    {
                        _pFractalEngine->SetGraphSize(uWidth, uHeight);
                        hr = pstm->Read(_rgPixels, ((uWidth  + 3) / 4) * 4 * uHeight, NULL);
                    }
                }
                pstm->Release();
            }
        }

        // Load palette from "Palette" stream

        if (SUCCEEDED(hr))
        {
            hr = _pstg->OpenStream(
                OLESTR("Palette"),
                0,
                STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
                0,
                &pstm);
            if (SUCCEEDED(hr))
            {
                PALETTEENTRY rgColors[PALETTESIZE];
                hr = pstm->Read(rgColors, sizeof(PALETTEENTRY) * (PALETTESIZE), NULL);

                // select new palette
                if (SUCCEEDED(hr))
                {
                    Wash(PALETTESIZE, rgColors);
                }
                pstm->Release();
            }
        }

        _pstg->Release();
        _pstg = NULL;
    }
    EndWait();
    if (FAILED(hr))
    {
        TCHAR szTitle[MAX_STRING_LENGTH];
        TCHAR szText[MAX_STRING_LENGTH];
        if (LoadString(_hInstance, IDS_ERROR, szTitle, MAX_STRING_LENGTH))
        {
            if (LoadString(_hInstance, IDS_LOADFRACTALFAILED, szText, MAX_STRING_LENGTH))
            {
                MessageBox(
                    _hwnd,
                    szText,
                    szTitle,
                    MB_OK | MB_ICONEXCLAMATION);
            }
        }
    }
}

//+---------------------------------------------------------------------------

//
//  Member:     CFractalWindow::SaveFile
//
//  Synopsis:   saves a fractal file
//
//  History:    4-22-94   stevebl   Created
//
//----------------------------------------------------------------------------

void CFractalWindow::SaveFile(void)
{
    if (!_pFractalEngine)
    {
        TCHAR szTitle[MAX_STRING_LENGTH];
        TCHAR szText[MAX_STRING_LENGTH];
        if (LoadString(_hInstance, IDS_ERROR, szTitle, MAX_STRING_LENGTH))
        {
            if (LoadString(_hInstance, IDS_SAVEFRACTALFAILED, szText, MAX_STRING_LENGTH))
            {
                MessageBox(
                    _hwnd,
                    szText,
                    szTitle,
                    MB_OK | MB_ICONEXCLAMATION);
            }
        }
        return;
    }
    StartWait();
#ifndef UNICODE
    // convert file name to a wide string
    WCHAR wszFileName[MAX_PATH];
    mbstowcs(wszFileName, _szFileName, MAX_PATH);
#endif
    HRESULT hr = StgCreateDocfile(
#ifdef UNICODE
        _szFileName,
#else
        wszFileName,
#endif
        STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
        0,
        &_pstg);
    if (SUCCEEDED(hr))
    {
        IStream * pstm;
        hr = _pstg->CreateStream(
            OLESTR("Palette"),
            STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
            0,
            0,
            &pstm);
        if (SUCCEEDED(hr))
        {
            hr = pstm->Write(_pEntry1, sizeof(PALETTEENTRY) * (PALETTESIZE), NULL);
            if (SUCCEEDED(hr))
            {
                hr = pstm->Commit(STGC_DEFAULT);
            }
            pstm->Release();
            if (SUCCEEDED(hr))
            {
                hr = _pstg->CreateStream(
                    OLESTR("Image"),
                    STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
                    0,
                    0,
                    &pstm);
                if (SUCCEEDED(hr))
                {
                    hr = pstm->Write(&_uImageWidth, sizeof(unsigned), NULL);
                    if (SUCCEEDED(hr))
                    {
                        hr = pstm->Write(&_uImageHeight, sizeof(unsigned), NULL);
                    }
                    if (SUCCEEDED(hr))
                    {
                        hr = pstm->Write(_rgPixels, ((_uImageWidth  + 3) / 4) * 4 * _uImageHeight, NULL);
                    }
                    pstm->Release();

                    hr = _pstg->CreateStream(
                        OLESTR("Properties"),
                        STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
                        0,
                        0,
                        &pstm);
                    if (SUCCEEDED(hr))
                    {
                        IPersistStream * pPStm;
                        hr = _pFractalEngine->QueryInterface(IID_IPersistStream, (void **)&pPStm);
                        if (SUCCEEDED(hr))
                        {
                            hr = OleSaveToStream(pPStm, pstm);
                            if (SUCCEEDED(hr))
                            {
                                hr = pstm->Commit(STGC_DEFAULT);
                            }
                            pstm->Release();
                            pPStm->Release();
                        }
                    }
                }
            }
        }
        if (SUCCEEDED(hr))
        {
            hr = _pstg->Commit(STGC_DEFAULT);
        }
        _pstg->Release();
        _pstg = NULL;
    }
    EndWait();
    if (SUCCEEDED(hr))
    {
        TCHAR szText[MAX_STRING_LENGTH];
        if (LoadString(_hInstance, IDS_SAVEFRACTALSUCCEEDED, szText, MAX_STRING_LENGTH))
        {
            MessageBox(
                _hwnd,
                szText,
                TEXT(APP_TITLE),
                MB_OK | MB_ICONEXCLAMATION);
        }
    }
    else
    {
        TCHAR szTitle[MAX_STRING_LENGTH];
        TCHAR szText[MAX_STRING_LENGTH];
        if (LoadString(_hInstance, IDS_ERROR, szTitle, MAX_STRING_LENGTH))
        {
            if (LoadString(_hInstance, IDS_SAVEFRACTALFAILED, szText, MAX_STRING_LENGTH))
            {
                MessageBox(
                    _hwnd,
                    szText,
                    szTitle,
                    MB_OK | MB_ICONEXCLAMATION);
            }
        }
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::ToggleTitle
//
//  Synopsis:   toggles full screen mode on/off
//
//  History:    4-22-94   stevebl   Created
//
//----------------------------------------------------------------------------

void CFractalWindow::ToggleTitle(void)
{
    static ULONG ulID;
    static RECT OldRec;
    RECT rectTemp;
    static int iNoTitleTop = 0;
    static int iNoTitleLeft = 0;
    ULONG  ulStyleBits = GetWindowLong(_hwnd, GWL_STYLE);

    if (WaitForSingleObject(_hTitleEvent,0) != WAIT_OBJECT_0)
    {
        GetWindowRect(_hwnd,&rectTemp);
        iNoTitleTop = rectTemp.top;
        iNoTitleLeft = rectTemp.left;
        ulStyleBits |= WS_TILEDWINDOW;
        SetWindowLong(_hwnd, GWL_ID, ulID);
        MoveWindow(_hwnd,OldRec.left,OldRec.top,
            OldRec.right-OldRec.left,OldRec.bottom-OldRec.top,TRUE);
        SetWindowPos(_hwnd,HWND_NOTOPMOST,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_NOACTIVATE);
        SetEvent(_hTitleEvent);
        if (WaitForSingleObject(_hGraphStartedEvent, 0) != WAIT_OBJECT_0)
        {
            OnStop();
        }
        else
        {
            OnStart();
        }
    }
    else
    {
        int iWidth, iHeight;
        ResetEvent(_hTitleEvent);
        GetWindowRect(_hwnd,&OldRec);
        ulStyleBits &= ~(WS_DLGFRAME | WS_SYSMENU |
            WS_MINIMIZEBOX | WS_MAXIMIZEBOX |
            WS_THICKFRAME | WS_VSCROLL | WS_HSCROLL);
        ulID = SetWindowLong(_hwnd, GWL_ID, 0);
        iWidth = _uImageWidth + GetSystemMetrics(SM_CXBORDER) * 2;
        iHeight = _uImageHeight + GetSystemMetrics(SM_CYBORDER) * 2;
        if (iNoTitleTop == 0 && iNoTitleLeft == 0)
        {
            iNoTitleTop = 0-GetSystemMetrics(SM_CYBORDER);
            iNoTitleLeft = 0-GetSystemMetrics(SM_CXBORDER);
        }
        MoveWindow(_hwnd
            ,iNoTitleLeft
            ,iNoTitleTop
            ,iWidth
            ,iHeight
            ,TRUE);
    }

    SetWindowLong(_hwnd, GWL_STYLE, ulStyleBits);
    SetWindowPos( _hwnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE |
    SWP_NOZORDER | SWP_FRAMECHANGED );
    ShowWindow(_hwnd, SW_SHOW);
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::OnStart
//
//  Synopsis:   Called whenever the graphics engine has been
//              successfully started.
//
//  History:    4-22-94   stevebl   Created
//
//  Notes:      Disables the following menu items:
//                  IDM_LOAD
//                  IDM_SAVE
//                  IDM_SAVEBMP
//                  IDM_START
//                  IDM_SELECT_TYPE
//                  IDM_EDIT_PROPERTIES
//                  IDM_CHANGEDIM
//                  IDM_ZOOM
//
//              Enables the following menu items:
//                  IDM_STOP
//
//----------------------------------------------------------------------------

void CFractalWindow::OnStart(void)
{
    SetEvent(_hGraphStartedEvent);
    HMENU hmenu = GetMenu(_hwnd);
    UINT rgCommandList[] =
        {
            IDM_LOAD,
            IDM_SAVE,
            IDM_SAVEBMP,
            IDM_START,
            IDM_SELECT_TYPE,
            IDM_EDIT_PROPERTIES,
            IDM_CHANGEDIM,
            IDM_ZOOM
        };
    for (int iIndex = sizeof(rgCommandList)/sizeof(UINT); iIndex--;)
    {
        EnableMenuItem(
            hmenu,
            rgCommandList[iIndex],
            MF_BYCOMMAND | MF_GRAYED);
    }
    EnableMenuItem(
        hmenu,
        IDM_STOP,
        MF_BYCOMMAND | MF_ENABLED);
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::OnStop
//
//  Synopsis:   Called whenever the graphics engine has been stopped.
//
//  History:    4-22-94   stevebl   Created
//
//  Notes:      Disables the following menu items:
//                  IDM_STOP
//
//              Enables the following menu items:
//                  IDM_LOAD
//                  IDM_SAVE
//                  IDM_SAVEBMP
//                  IDM_START
//                  IDM_SELECT_TYPE
//                  IDM_EDIT_PROPERTIES
//                  IDM_CHANGEDIM
//                  IDM_ZOOM
//
//----------------------------------------------------------------------------

void CFractalWindow::OnStop(void)
{
    ResetEvent(_hGraphStartedEvent);
    HMENU hmenu = GetMenu(_hwnd);
    UINT rgCommandList[] =
        {
            IDM_LOAD,
            IDM_SAVE,
            IDM_SAVEBMP,
            IDM_START,
            IDM_SELECT_TYPE,
            IDM_EDIT_PROPERTIES,
            IDM_CHANGEDIM,
            IDM_ZOOM
        };
    for (int iIndex = sizeof(rgCommandList)/sizeof(UINT); iIndex--;)
    {
        EnableMenuItem(
            hmenu,
            rgCommandList[iIndex],
            MF_BYCOMMAND | MF_ENABLED);
    }
    EnableMenuItem(
        hmenu,
        IDM_STOP,
        MF_BYCOMMAND | MF_GRAYED);
    InvalidateRect(_hwnd, NULL, FALSE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::SetGraphSize
//
//  Synopsis:   called every time the graph's size changes
//
//  Arguments:  [uWidth]  - width of the graph in pixels
//              [uHeight] - height of the graph in pixels
//
//  Modifies:   _uImageWidth, _uImageHeight
//
//  History:    4-12-94   stevebl   Created
//
//  Notes:      calls NewWindowSize to update the scroll bars
//
//----------------------------------------------------------------------------

STDMETHODIMP CFractalWindow::SetGraphSize(unsigned uWidth, unsigned uHeight)
{
    HDC hdc = GetDC(_hwnd);
    BITMAPINFO bmi;

    bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth = uWidth;
    bmi.bmiHeader.biHeight= uHeight;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 8;
    bmi.bmiHeader.biCompression = BI_RGB;
    bmi.bmiHeader.biSizeImage = 0;
    bmi.bmiHeader.biXPelsPerMeter = 0;
    bmi.bmiHeader.biYPelsPerMeter = 0;
    bmi.bmiHeader.biClrUsed = 0;
    bmi.bmiHeader.biClrImportant = 0;
    char * rgPixels;
    HBITMAP hbmMainWindow = CreateDIBSection(hdc, &bmi, DIB_PAL_COLORS, (void **)&rgPixels, NULL, NULL);
    ReleaseDC(_hwnd, hdc);
    if (hbmMainWindow == NULL)
    {
        return(E_OUTOFMEMORY);
    }
    _uImageWidth = uWidth;
    _uImageHeight = uHeight;
    _hbmMainWindow = hbmMainWindow;
    _rgPixels = rgPixels;

    if (!_hbmSavedBitmap)
    {
        // this is the very first time this has been called.
        EnterCriticalSection(&_csGraph);
        _hbmSavedBitmap = (HBITMAP) SelectObject(_hdcMem, _hbmMainWindow);
        LeaveCriticalSection(&_csGraph);
    }
    else
    {
        EnterCriticalSection(&_csGraph);
        HBITMAP hbm = (HBITMAP) SelectObject(_hdcMem, _hbmMainWindow);
        LeaveCriticalSection(&_csGraph);
        DeleteObject(hbm);
    }

    NewWindowSize(_uWindowWidth, _uWindowHeight);

    Rect(0, 0, (int)uWidth-1, (int)uHeight-1, 0);
    InvalidateRect(_hwnd, NULL, TRUE);

    PostMessage(_hwnd, WM_REFRESHCOLORS, 0, 0);

    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::GetGraphSize
//
//  Synopsis:   returns the size of the graph's bitmap
//
//  Arguments:  [puWidth]  - width in pixels
//              [puHeight] - height in pixels
//
//  History:    4-13-94   stevebl   Created
//
//----------------------------------------------------------------------------

STDMETHODIMP CFractalWindow::GetGraphSize(unsigned * puWidth, unsigned * puHeight)
{
    *puWidth = _uImageWidth;
    *puHeight = _uImageHeight;
    return(S_OK);
}

// IUnknown methods:

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::QueryInterface
//
//  Synopsis:   returns an interface supported by this object
//
//  Arguments:  [riid] - id of desired interface
//              [ppv]  - pointer to recieve interface
//
//  Returns:    S_OK if the interface is supported
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

STDMETHODIMP CFractalWindow::QueryInterface (REFIID riid, void ** ppv)
{
    HRESULT hr = S_OK;
    if (IsEqualGUID(riid, IID_IUnknown))
    {
        *ppv = (LPVOID)(IUnknown *) this;
    }
    else if (IsEqualGUID(riid, IID_IFractalHost))
    {
        *ppv = (LPVOID)(IFractalHost *) this;
    }
    else
    {
        *ppv = NULL;
        hr = E_NOINTERFACE;
    }
    return(hr);
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::AddRef
//
//  Synopsis:   increments refcount
//
//  History:    4-14-94   stevebl   Created
//
//  Notes:      Since this is an application, the refcount isn't acutally
//              used for anything.  The app will remain around until the
//              user closes it.
//
//----------------------------------------------------------------------------

STDMETHODIMP_(ULONG) CFractalWindow::AddRef(void)
{
    return(++_uRefCount);
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::Release
//
//  Synopsis:   decrements the refcount
//
//  Returns:    new refcount
//
//  History:    4-14-94   stevebl   Created
//
//  Notes:      Since this is an application the refcount isn't actually used
//              for anything.  If the refcount reaches zero, the app will
//              still remain open until the user closes it.
//
//----------------------------------------------------------------------------

STDMETHODIMP_(ULONG) CFractalWindow::Release(void)
{
    if (_uRefCount > 0)
    {
        _uRefCount--;
    }
    else
    {
        // This object is _NOT_ destroyed if it's reference count reaches zero.
    }
    return(_uRefCount);
}

// IFractalHost methods:

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::GetPoint
//
//  Synopsis:   gets the color at the given point
//
//  Arguments:  [puColor] - pointer to variable to recieve the color
//              [x]       - x position of the pixel
//              [y]       - y position of the pixel
//
//  Returns:    S_OK if the point is on the graph
//              S_FALSE if the point is off the graph (and *puColor is set to 0)
//
//  History:    4-12-94   stevebl   Created
//              7-07-94   stevebl   changed parameters and return value
//
//----------------------------------------------------------------------------

STDMETHODIMP CFractalWindow::GetPoint(unsigned * puColor, int x, int y)
{
    if ((unsigned) x >= _uImageWidth || (unsigned) y >= _uImageHeight)
    {
        *puColor = 0;
        return(S_FALSE);
    }
    EnterCriticalSection(&_csGraph);
    * puColor = (unsigned)_rgPixels[x + (_uImageHeight - (y + 1)) * (((_uImageWidth + 3) / 4) * 4)] - 10;
    LeaveCriticalSection(&_csGraph);
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::Rect
//
//  Synopsis:   paints a filled rectangle
//
//  Arguments:  [iLeft]   - left side
//              [iTop]    - top
//              [iRight]  - right side
//              [iBottom] - bottom
//              [uColor]  - color index
//
//  Returns:    S_OK if any part of the rectangle is painted
//              S_FALSE if the rectangle is entirely off the graph
//
//  History:    4-12-94   stevebl   Created
//
//----------------------------------------------------------------------------

STDMETHODIMP CFractalWindow::Rect(
    int iLeft,
    int iTop,
    int iRight,
    int iBottom,
    unsigned uColor)
{
    if (iRight < iLeft)
    {
        int i = iRight;
        iRight = iLeft;
        iLeft = i;
    }
    if (iBottom < iTop)
    {
        int i = iBottom;
        iBottom = iTop;
        iTop = i;
    }
    if (iRight < 0 || iBottom < 0 || iLeft >= (int) _uImageWidth || iTop >= (int) _uImageHeight)
    {
        // Rectangle is entirely outside of the boundaries
        return(S_FALSE);
    }
    if (iLeft < 0)
    {
        iLeft = 0;
    }
    if (iTop < 0)
    {
        iTop = 0;
    }
    if (iRight >= (int) _uImageWidth)
    {
        iRight = _uImageWidth - 1;
    }
    if (iBottom >= (int) _uImageHeight)
    {
        iBottom = _uImageHeight - 1;
    }
    EnterCriticalSection(&_csGraph);
    char chColor = char (uColor + 10);
    if (0 == iLeft &&
        0 == iTop &&
        (int) _uImageWidth - 1 == iRight &&
        (int) _uImageHeight - 1 == iBottom)
    {
        memset(_rgPixels, chColor, ((_uImageWidth + 3) / 4) * 4 * _uImageHeight);

        // This little trick causes the screen to always repaint if the
        // calling process paints the entire window (which always happens
        // when a graph engine is restarted).

        InvalidateRect(_hwnd, NULL, TRUE);
    }
    else
    {
        RECT rect;
        rect.left = iLeft;
        rect.top = iTop;
        // add one to the right and bottom to
        // make sure that we never get a null region
        rect.right = iRight + 1;
        rect.bottom = iBottom + 1;
        UnionRect(&_rectPainted, &_rectPainted, &rect);
        for (unsigned y = (iBottom - iTop) + 1; y--;)
        {
            unsigned uBase = (_uImageHeight - (y + iTop + 1)) * ((_uImageWidth + 3) / 4) * 4 + iLeft;
            for (unsigned x = (iRight - iLeft) + 1; x--;)
            {
                _rgPixels[uBase + x] = chColor;
            }
        }
    }
    LeaveCriticalSection(&_csGraph);
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::ClipLine
//
//  Synopsis:   clips a line to the graph boundaries
//
//  Arguments:  [x1] - x coordinate of point 1
//              [y1] - y coordinate of point 1
//              [x2] - x coordinate of point 2
//              [y2] - y coordinate of point 2
//
//  Returns:    TRUE if the line is completely outside the graph (rejected)
//              FALSE if any part of the line should be drawn
//
//  Modifies:   the endpoints are clipped to be within the graph
//
//  History:    5-03-94   stevebl   Created
//
//----------------------------------------------------------------------------

BOOL CFractalWindow::ClipLine(int &x1, int &y1, int &x2, int &y2)
{
    // Test for trivial rejections
    if (
        (x1 < 0 && x2 < 0)
        || (y1 < 0 && y2 < 0)
        || (x1 >= (int) _uImageWidth && x2 >= (int) _uImageWidth)
        || (y1 >= (int) _uImageHeight && y2 >= (int) _uImageHeight)
       )
    {
        return(TRUE);   // line has been rejected
    }

    // Now adjust any points that are outside the bounding box to be cut off
    // at and edge of the bounding box.

    int dx = x2 - x1;
    int dy = y2 - y1;

    if (x1 < 0)
    {
        y1 = ((0 - x1) * dy) / dx + y1;
        x1 = 0;
    }
    else if (x2 < 0)
    {
        y2 = ((0 - x2) * dy) / dx + y2;
        x2 = 0;
    }
    if ((int) y1 < 0)
    {
        x1 = ((0 - y1) * dx) / dy + x1;
        y1 = 0;
    }
    else if (y2 < 0)
    {
        x2 = ((0 - y2) * dx) / dy + x2;
        y2 = 0;
    }
    if (x1 >= (int) _uImageWidth)
    {
        y1 = (((int) _uImageWidth - x1) * dy) / dx + y1;
        x1 = _uImageWidth - 1;
    }
    else if (x2 >= (int) _uImageWidth)
    {
        y2 = (((int) _uImageWidth - x2) * dy) / dx + y2;
        x2 = _uImageWidth - 1;
    }
    if (y1 >= (int) _uImageHeight)
    {
        x1 = (((int) _uImageHeight - y1) * dx) / dy + x1;
        y1 = _uImageHeight - 1;
    }
    else if (y2 >= (int) _uImageHeight)
    {
        x2 = (((int) _uImageHeight - y2) * dx) / dy + x2;
        y2 = _uImageHeight - 1;
    }

    // Test again for rejection
    if (
        x1 < 0
        || x2 < 0
        || y1 < 0
        || y2 < 0
        || x1 >= (int) _uImageWidth
        || x2 >= (int) _uImageWidth
        || y1 >= (int) _uImageHeight
        || y2 >= (int) _uImageHeight
       )
    {
        return(TRUE);   // line has been rejected
    }

    return(FALSE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::Line
//
//  Synopsis:   draws a line on the graph
//
//  Arguments:  [x1]     - starting point
//              [y1]
//              [x2]     - ending point
//              [y2]
//              [uColor] - color
//
//  Returns:    S_OK if any part of the line could be drawn
//              S_FALSE if all of the line was outside the graph (rejected)
//
//  History:    4-12-94   stevebl   Created
//
//----------------------------------------------------------------------------

STDMETHODIMP CFractalWindow::Line(
    int x1,
    int y1,
    int x2,
    int y2,
    unsigned uColor)
{
    // clip the line
    if (ClipLine(x1, y1, x2, y2))
    {
        // line was rejected (it's not on the graph)
        return(S_FALSE);
    }

    EnterCriticalSection(&_csGraph);
    RECT rect;

    if (x1 < x2)
    {
        rect.left = x1;
        rect.right = x2;
    }
    else
    {
        rect.left = x2;
        rect.right = x1;
    }
    if (y1 < y2)
    {
        rect.top = y1;
        rect.bottom = y2;
    }
    else
    {
        rect.top = y2;
        rect.bottom = y1;
    }
    int dx = rect.right - rect.left;
    int dy = rect.bottom - rect.top;
    char chColor = (char) uColor + 10;
    int p, c1, c2, iNextLine;
    unsigned uCurrent, uLast;
    if (dx > dy)
    {
        // step along x axis
        p = 2 * dy - dx;
        c1 = 2 * dy;
        c2 = 2 * (dy - dx);
        if (x1 > x2)
        {
            // last point to first point
            uCurrent = x2 + (_uImageHeight - (y2 + 1)) * (((_uImageWidth + 3) / 4) * 4);
            uLast = x1 + (_uImageHeight - (y1 + 1)) * (((_uImageWidth + 3) / 4) * 4);
            if (y2 < y1)
            {
                // posative slope
                iNextLine = (0 - (((_uImageWidth + 3) / 4) * 4)) + 1;
            }
            else
            {
                // negative slope
                iNextLine = (((_uImageWidth + 3) / 4) * 4) + 1;
            }
        }
        else
        {
            // first point to last point
            uCurrent = x1 + (_uImageHeight - (y1 + 1)) * (((_uImageWidth + 3) / 4) * 4);
            uLast = x2 + (_uImageHeight - (y2 + 1)) * (((_uImageWidth + 3) / 4) * 4);
            if (y1 < y2)
            {
                // posative slope
                iNextLine = (0 - (((_uImageWidth + 3) / 4) * 4)) + 1;
            }
            else
            {
                // negative slope
                iNextLine = (((_uImageWidth + 3) / 4) * 4) + 1;
            }
        }
        _rgPixels[uCurrent] = chColor;
        while (uCurrent != uLast)
        {
            if (p < 0)
            {
                uCurrent++;
                p += c1;
            }
            else
            {
                uCurrent += iNextLine;
                p += c2;
            }
            _rgPixels[uCurrent] = chColor;
        }
    }
    else
    {
        // step along y axis
        p = 2 * dx - dy;
        c1 = 2 * dx;
        c2 = 2 * (dx - dy);
        if (y1 > y2)
        {
            // last point to first point
            uCurrent = x2 + (_uImageHeight - (y2 + 1)) * (((_uImageWidth + 3) / 4) * 4);
            uLast = x1 + (_uImageHeight - (y1 + 1)) * (((_uImageWidth + 3) / 4) * 4);
            if (x2 > x1)
            {
                // negative slope
                iNextLine = (0 - (((_uImageWidth + 3) / 4) * 4)) - 1;
            }
            else
            {
                // posative slope
                iNextLine = (0 - (((_uImageWidth + 3) / 4) * 4)) + 1;
            }
        }
        else
        {
            // first point to last point
            uCurrent = x1 + (_uImageHeight - (y1 + 1)) * (((_uImageWidth + 3) / 4) * 4);
            uLast = x2 + (_uImageHeight - (y2 + 1)) * (((_uImageWidth + 3) / 4) * 4);
            if (x1 > x2)
            {
                // negative slope
                iNextLine = (0 - (((_uImageWidth + 3) / 4) * 4)) - 1;
            }
            else
            {
                // posative slope
                iNextLine = (0 - (((_uImageWidth + 3) / 4) * 4)) + 1;
            }
        }
        _rgPixels[uCurrent] = chColor;
        while (uCurrent != uLast)
        {
            if (p < 0)
            {
                uCurrent += (0 - (((_uImageWidth + 3) / 4) * 4));
                p += c1;
            }
            else
            {
                uCurrent += iNextLine;
                p += c2;
            }
            _rgPixels[uCurrent] = chColor;
        }
    }
    // add one to the right and bottom to
    // make sure that we never get a null region
    rect.bottom++;
    rect.right++;
    UnionRect(&_rectPainted, &_rectPainted, &rect);
    LeaveCriticalSection(&_csGraph);
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::Point
//
//  Synopsis:   plots a pixel on the graph
//
//  Arguments:  [x]      - x coordinate
//              [y]      - y coordinate
//              [uColor] - color
//
//  Returns:    S_OK if the point was plotted
//              S_FALSE if the point is outside the graph
//
//  History:    4-12-94   stevebl   Created
//
//----------------------------------------------------------------------------

STDMETHODIMP CFractalWindow::Point(
    int x,
    int y,
    unsigned uColor)
{
    // clip the point
    if ((unsigned) x >= _uImageWidth || (unsigned) y >= _uImageHeight)
    {
        return(S_FALSE);
    }
    RECT rectPt;
    EnterCriticalSection(&_csGraph);
    rectPt.left = x;
    rectPt.top = y;
    // Have to have a rectangle with at least one pixel or UnionRect won't
    // do anything.
    rectPt.right = x + 1;
    rectPt.bottom = y + 1;
    UnionRect(&_rectPainted, &_rectPainted, &rectPt);
    _rgPixels[x + (_uImageHeight - (y + 1)) * (((_uImageWidth + 3) / 4) * 4)] = (char)(uColor + 10);
    LeaveCriticalSection(&_csGraph);
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CFractalWindow::DoneDrawingGraph
//
//  Synopsis:   called by the fractal engine when its done drawing a graph
//
//  Returns:    S_OK
//
//  History:    4-12-94   stevebl   Created
//
//----------------------------------------------------------------------------

STDMETHODIMP CFractalWindow::DoneDrawingGraph(void)
{
    MessageBeep(0xffffffff);
    OnStop();
    return(S_OK);
}


FCLASS.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       fclass.h
//
//  Contents:   definition for the CFractalWindow class
//
//  Classes:    CFractalWindow
//
//  Functions:
//
//  History:    4-11-94   stevebl   Created
//
//----------------------------------------------------------------------------

#ifndef __WFCLASS_H__
#define __WFCLASS_H__

#include "cwindow.h"
#include <commdlg.h>
#include <frhost.h>
#include <frcngn.h>
#include "engnlist.h"
#include <palsize.h>
#ifdef __cplusplus

//+---------------------------------------------------------------------------
//
//  Class:      CFractalWindow
//
//  Purpose:    implements MFract's main window
//
//  Interface:  CFractalWindow       -- constructor
//              ~CFractalWindow      -- destructor
//              InitInstance         -- initializes an instance of the class
//              WindowProc           -- main window procedure
//              DoMenu               -- menu command handler
//              OnCreate             -- called when the window is created
//              NewWindowSize        -- called when window size changes
//              SetUpColorTable      -- initializes the color table
//              SetDefaultColors     -- selects the default color set
//              Wash                 -- creats a wash over a range of colors
//              ToggleTitle          -- toggles full screen mode on/off
//              ChangeFractalEngines -- changes fractal engines
//              CycleStop            -- stops color cycling
//              Faster               -- alters speed of color cycling
//              LoadPalette          -- loads a color palette from a file
//              SavePalette          -- saves a color palette to a file
//              SaveBitmap           -- saves the graph as a bitmap
//              LoadFile             -- loads the graph from a docfile
//              SaveFile             -- saves the graph to a docfile
//              OnStart              -- starts the graph
//              OnStop               -- stops the graph
//              GetHwnd              -- gets the main window handle
//              DrawRect             -- draws the zoom rectangle
//              EraseRect            -- erases the zoom rectangle
//              ClipLine             -- clips a line to the graph dimensions
//
//              IUnknown methods:
//              QueryInterface       -- retreives interface pointers
//              AddRef               -- increments ref-count
//              Release              -- decrements ref-count
//
//              IFractalHost methods:
//              SetGraphSize         -- changes the size of the graph
//              GetGraphSize         -- returns the size of the graph
//              GetPoint             -- returns the color at a point
//              Rect                 -- draws a rectangle
//              Line                 -- draws a line
//              Point                -- plots a point
//              DoneDrawingGraph     -- signals when the graph is complete
//
//  History:    4-11-94   stevebl   Created
//              7-07-94   stevebl   changed definition of GetPoint
//
//----------------------------------------------------------------------------

class CFractalWindow: public CHlprWindow, public IFractalHost
{
public:
    CFractalWindow();
    ~CFractalWindow();
    BOOL InitInstance(HINSTANCE, int);
    LRESULT WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
    LRESULT DoMenu(WPARAM wParam, LPARAM lParam);
    void OnCreate(void);
    void NewWindowSize(unsigned uWidth, unsigned uHeight);
    void SetUpColorTable(void);
    void SetDefaultColors(void);
    void Wash(int i, PALETTEENTRY rgColors[]);
    void ToggleTitle(void);
    void ChangeFractalEngines(void);
    void CycleStop(void);
    void Faster(int);
    void LoadPalette(void);
    void SavePalette(void);
    void SaveBitmap(void);
    void LoadFile(void);
    void SaveFile(void);
    void OnStart(void);
    void OnStop(void);
    HWND GetHwnd(void);
    BOOL DrawRect(HDC, const RECT *);
    BOOL EraseRect(HDC, const RECT *);
    BOOL ClipLine(int & x1, int & y1, int & x2, int & y2);

    // IUnknown members
    STDMETHOD(QueryInterface) (THIS_ REFIID, void **);
    STDMETHOD_(ULONG, AddRef) (THIS);
    STDMETHOD_(ULONG, Release) (THIS);

    // IFractalHost members
    STDMETHOD(SetGraphSize) (THIS_ unsigned uWidth, unsigned uHeight);
    STDMETHOD(GetGraphSize) (THIS_ unsigned * puWidth, unsigned * puHeight);
    STDMETHOD(GetPoint) (THIS_ unsigned * puValue, int x, int y);
    STDMETHOD(Rect)
        (THIS_
        int iLeft,
        int iTop,
        int iRight,
        int iBottom,
        unsigned uColor);
    STDMETHOD(Line)
        (THIS_
        int x1,
        int y1,
        int x2,
        int y2,
        unsigned uColor);
    STDMETHOD(Point)
        (THIS_
        int x,
        int y,
        unsigned uColor);
    STDMETHOD(DoneDrawingGraph)(THIS_ void);

private:
    HDC         _hdcMem;
    ULONG       _uRefreshRate;
    HANDLE      _hTitleEvent;
    HANDLE      _hGraphStartedEvent;
    HPALETTE    _hpal;
    HBITMAP     _hbmSavedBitmap;
    HBITMAP     _hbmMainWindow;
    unsigned    _uScreenWidth;
    unsigned    _uScreenHeight;
    unsigned    _uImageWidth;
    unsigned    _uImageHeight;
    unsigned    _uWindowWidth;
    unsigned    _uWindowHeight;
    unsigned    _uScrollPosX;
    unsigned    _uScrollPosY;
    int         _iCycleVelocity;
    int         _iCurrentDirection;
    double      _dLeft, _dRight, _dTop, _dBottom;
    PALETTEENTRY * _pEntry1, * _pEntry2;
    LOGPALETTE * _pLogPal;
    LOGPALETTE * _pSecondLogPal;
    ULONG       _uRefCount;
    CEngineList * _pEngineList;
    unsigned    _uFractalType;
    IFractalEngine * _pFractalEngine;
    RECT        _rectZoom;
    BOOL        _fZooming;
    BOOL        _fRButtonDown;
    OPENFILENAME _ofnPalette;
    OPENFILENAME _ofnBitmap;
    OPENFILENAME _ofnFile;
    TCHAR       _szPalFileName[MAX_PATH];
    TCHAR       _szPalFileTitle[MAX_PATH];
    TCHAR       _szBmpFileName[MAX_PATH];
    TCHAR       _szBmpFileTitle[MAX_PATH];
    TCHAR       _szFileName[MAX_PATH];
    TCHAR       _szFileTitle[MAX_PATH];
    TCHAR       _szHelpFile[MAX_PATH];
    RECT        _rectPainted;
    IStorage  * _pstg;
    char      * _rgPixels;
    BOOL        _fReRealizePalette;
    BOOL        _fCanAnimate;
    CRITICAL_SECTION _csGraph;
};

#endif // __cplusplus

#define REFRESH_TIMER 1
#define CYCLE_TIMER 2
#define CYCLE_RATE 50

#define QUICKWIDTH 160
#define QUICKHEIGHT 120

#endif // __WFCLASS_H__


FRACTAL.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       fractal.cxx
//
//  Contents:   main windows engine
//
//  Classes:
//
//  Functions:  InitApplication
//              WinMain
//              Exists
//
//  History:    4-11-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include <windows.h>
#include <ole2.h>
#include <ole2ver.h>
#include "fractal.h"
#include "fclass.h"

//+---------------------------------------------------------------------------
//
//  Function:   InitApplication
//
//  Synopsis:   initializes the application and registers its window class
//              (called once for all instances)
//
//  Arguments:  [hInstance] - handle to the first instance
//
//  Returns:    TRUE on success
//
//  History:    4-11-94   stevebl   Created
//
//----------------------------------------------------------------------------

BOOL InitApplication(HINSTANCE hInstance)
{
    WNDCLASS wc;

    wc.style = CS_DBLCLKS;
    wc.lpfnWndProc = &WindowProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = LoadIcon(hInstance, TEXT("AppIcon"));
    wc.hCursor = (HCURSOR) LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName = TEXT("CFractalWindowMenu");
    wc.lpszClassName = TEXT("CFractalWindow");
    return(RegisterClass(&wc));
}

//+---------------------------------------------------------------------------
//
//  Function:   WinMain
//
//  Synopsis:   main window proceedure
//
//  Arguments:  [hInstance]     - instance handle
//              [hPrevInstance] - handle of the previous instance (if any)
//              [lpCmdLine]     - pointer to the command line
//              [nCmdShow]      - show state
//
//  History:    4-11-94   stevebl   Created
//
//  Notes:      initializes application and starts message loop
//
//----------------------------------------------------------------------------

extern "C" int PASCAL WinMain(HINSTANCE hInstance,
            HINSTANCE hPrevInstance,
            LPSTR lpCmdLine,
            int nCmdShow)
{
    DWORD dwBuildVersion = OleBuildVersion();
    if (HIWORD(dwBuildVersion) != rmm || LOWORD(dwBuildVersion) < rup)
    {
        // alert the caller that the OLE version is incompatible
        // with this build.
        TCHAR szTitle[MAX_STRING_LENGTH];
        TCHAR szText[MAX_STRING_LENGTH];
        if (LoadString(hInstance, IDS_ERROR, szTitle, MAX_STRING_LENGTH))
        {
            if (LoadString(hInstance, IDS_OLEINCOMPATIBLE, szText, MAX_STRING_LENGTH))
            {
                MessageBox(
                    NULL,
                    szText,
                    szTitle,
                    MB_OK | MB_ICONEXCLAMATION);
            }
        }
        return(FALSE);
    }
    if (FAILED(OleInitialize(NULL)))
    {
        // alert the caller that OLE couldn't be initialized
        TCHAR szTitle[MAX_STRING_LENGTH];
        TCHAR szText[MAX_STRING_LENGTH];
        if (LoadString(hInstance, IDS_ERROR, szTitle, MAX_STRING_LENGTH))
        {
            if (LoadString(hInstance, IDS_OLEINITFAILED, szText, MAX_STRING_LENGTH))
            {
                MessageBox(
                    NULL,
                    szText,
                    szTitle,
                    MB_OK | MB_ICONEXCLAMATION);
            }
        }
        return(FALSE);
    }
    if (!hPrevInstance)
    {
        if (!InitApplication(hInstance))
        {
            OleUninitialize();
            return(FALSE);
        }
    }
    CFractalWindow * pw = new CFractalWindow;
    if (pw == NULL)
    {
        return(FALSE);
    }
    if (!pw->InitInstance(hInstance, nCmdShow))
    {
        // Note, if InitInstance has failed then it would have
        // already deleted pw for me so I don't delete it here.
        // This is because when WM_CREATE returns -1 (failure)
        // Windows sends the WM_DESTROY message to the window
        // and the the CHlprWindow class destroys itself whenever
        // it receives this message.
        OleUninitialize();
        return(FALSE);
    }
    MSG msg;
    HACCEL haccel = LoadAccelerators(hInstance, TEXT("AppAccel"));
    if (haccel == NULL)
    {
        OleUninitialize();
        return(FALSE);
    }

    while (GetMessage(&msg, NULL, 0, 0))
    {
        if (!TranslateAccelerator(
            pw->GetHwnd(),
            haccel,
            &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    OleUninitialize();
    return(msg.wParam);
}

//+---------------------------------------------------------------------------
//
//  Function:   Exists
//
//  Synopsis:   simple function to test for the existance of a file
//
//  History:    6-16-93   stevebl   Created
//
//----------------------------------------------------------------------------

int Exists(TCHAR *sz)
{
    HANDLE h;
    h = CreateFile(sz,
        GENERIC_READ,
        FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL,
        OPEN_EXISTING,
        0,
        0);
    if (h != INVALID_HANDLE_VALUE)
    {
        CloseHandle(h);
        return(1);
    }
    return (0);
}


FRACTAL.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       fractal.h
//
//  Contents:   global definitions used by MFract
//
//  Classes:
//
//  Functions:  WinMain
//              Exists
//
//  History:    4-28-94   stevebl   Created
//
//----------------------------------------------------------------------------

#ifndef __FRACTAL_H__
#define __FRACTAL_H__

#ifdef __cplusplus
extern "C" {
#endif

#define IDM_ABOUT           200
#define IDM_HELP            201
#define IDM_LOAD            202
#define IDM_SAVE            203
#define IDM_SAVEBMP         204
#define IDM_EXIT            205
#define IDM_START           302
#define IDM_STOP            303
#define IDM_SELECT_TYPE     304
#define IDM_EDIT_PROPERTIES 305
#define IDM_ZOOM            306
#define IDM_BRING_FORWARD   320
#define IDM_CHANGEDIM       400
#define IDM_LOADPAL         500
#define IDM_SAVEPAL         501
#define IDM_DEFAULTCOLORS   502
#define IDM_CYCLEIN         504
#define IDM_CYCLESTOP       505
#define IDM_CYCLEOUT        506
#define IDM_FRACTAL_TYPE    1000

#define IDC_WIDTH       601
#define IDC_HEIGHT      602
#define IDC_RESIZE      603
#define IDC_RESET       604
#define IDC_QUICKDRAW   605
#define IDC_VGA         606
#define IDC_SVGA        607
#define IDC_1024        608
#define IDC_1280        609
#define IDC_CUSTOM      610

#define MAX_FRACTAL_TYPES   1000

#define IDS_ERROR                2000
#define IDS_OLEINCOMPATIBLE      2001
#define IDS_OLEINITFAILED        2002
#define IDS_TOOMANYTIMERS        2003
#define IDS_GRAPHICSEVENTFAILED  2004
#define IDS_TITLEEVENTFAILED     2005
#define IDS_NOFRACTALENGINE      2006
#define IDS_NOHELPFILE           2007
#define IDS_SAVEBMPFAILED        2008
#define IDS_SAVEBMPSUCCEEDED     2009
#define IDS_LOADFRACTALFAILED    2010
#define IDS_SAVEFRACTALFAILED    2011
#define IDS_SAVEFRACTALSUCCEEDED 2012
#define IDS_REGISTRYERROR        2013

#define COLORTABLESIZE 199

#define HELPFILE_STR    "MFract.hlp"
#define APP_TITLE       "MFract"

#define MAX_STRING_LENGTH   256

int PASCAL WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
int Exists(TCHAR *sz);

#ifdef __cplusplus
}
#endif

#endif // __FRACTAL_H__


STMDIB.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       stmdib.cxx
//
//  Contents:   functions for saving and loading DIBs to OLE streams
//
//  Classes:
//
//  Functions:  WriteDibToStream
//              ReadDibFromStream
//              ReadDibBitmapInfoFromStream
//
//  History:    4-22-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include "stmdib.h"
#include "dib.h"
#include <stdlib.h>

//+---------------------------------------------------------------------------
//
//  Function:   WriteDibToStream
//
//  Synopsis:   writes a global handle in CF_DIB format to a stream
//
//  Arguments:  [pstm] - stream
//              [hdib] - DIB handle
//
//  Returns:    S_OK if successful
//
//  History:    4-22-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT WriteDibToStream (IStream * pstm, HANDLE hdib)
{
    BITMAPFILEHEADER    hdr;
    LPBITMAPINFOHEADER  lpbi;
    OFSTRUCT            of;
    HRESULT hr;

    if (!hdib)
        return FALSE;

    lpbi = (LPBITMAPINFOHEADER)GlobalLock (hdib);

    /* Fill in the fields of the file header */
    hdr.bfType      = BFT_BITMAP;
    hdr.bfSize      = GlobalSize (hdib) + sizeof (BITMAPFILEHEADER);
    hdr.bfReserved1 = 0;
    hdr.bfReserved2 = 0;
    hdr.bfOffBits   = (DWORD)sizeof(BITMAPFILEHEADER) + lpbi->biSize +
        PaletteSize(lpbi);

    /* Write the file header */
    if (FAILED(hr = pstm->Write(&hdr, sizeof (BITMAPFILEHEADER), NULL)))
    {
        return(hr);
    }

    /* Write the DIB header and the bits */
    if (FAILED(hr = pstm->Write(lpbi, GlobalSize (hdib), NULL)))
    {
        return(hr);
    }

    GlobalUnlock (hdib);
    return S_OK;
}

//+---------------------------------------------------------------------------
//
//  Function:   ReadDibFromStream
//
//  Synopsis:   reads a DIB stored in CF_DIB format from a stream
//
//  Arguments:  [pstm]  - pointer to the stream
//              [phDib] - pointer to recieve the DIB handle
//
//  History:    4-22-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT ReadDibFromStream (IStream * pstm, HANDLE * phDib)
{
    BITMAPINFOHEADER    bi;
    LPBITMAPINFOHEADER  lpbi;
    DWORD       dwLen = 0;
    DWORD       dwBits;
    HANDLE      hdib;
    HANDLE      h;
    OFSTRUCT        of;
    HRESULT hr;

    *phDib = NULL;

    if (FAILED(hr = ReadDibBitmapInfoFromStream(pstm, &hdib)))
    {
        return(hr);
    }
    DibInfo(hdib,&bi);

    /* Calculate the memory needed to hold the DIB */
    dwBits = bi.biSizeImage;
    dwLen  = bi.biSize + (DWORD)PaletteSize (&bi) + dwBits;

    /* Try to increase the size of the bitmap info. buffer to hold the DIB */
    h = GlobalReAlloc(hdib, dwLen, GHND);
    if (!h)
    {
        GlobalFree(hdib);
        return(E_OUTOFMEMORY);
    }
    hdib = h;

    /* Read in the bits */

    lpbi = (LPBITMAPINFOHEADER)GlobalLock(hdib);
    if (FAILED(hr = pstm->Read(((LPSTR)lpbi + (WORD)lpbi->biSize + PaletteSize(lpbi)), dwBits, NULL)))
    {
        return(hr);
    }
    GlobalUnlock(hdib);

    *phDib = hdib;
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Function:   ReadDibBitmapInfoFromStream
//
//  Synopsis:   reads a file in DIB format and retrieves a global HANDLE to
//              its BITMAPINFO.
//
//  Arguments:  [pstm] - stream
//              [phbi] - pointer to receive HANDLE to BITMAPINFO
//
//  Returns:    S_OK on success
//
//  History:    4-22-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT ReadDibBitmapInfoFromStream(IStream * pstm, HANDLE * phbi)
{
    ULARGE_INTEGER libOff;
    LARGE_INTEGER dlibMove;
    HANDLE    hbi = NULL;
    int       size;
    int       i;
    WORD      nNumColors;

    RGBQUAD FAR       *pRgb;
    BITMAPINFOHEADER   bi;
    BITMAPCOREHEADER   bc;
    LPBITMAPINFOHEADER lpbi;
    BITMAPFILEHEADER   bf;
    DWORD          dwWidth = 0;
    DWORD          dwHeight = 0;
    WORD           wPlanes, wBitCount;
    HRESULT hr;

    *phbi = NULL;

    /* Reset file pointer and read file header */

    dlibMove.HighPart = 0;
    dlibMove.LowPart = 0;
    if (FAILED(hr = pstm->Seek(dlibMove, STREAM_SEEK_CUR, &libOff)))
    {
        return(hr);
    }
    if (FAILED(hr = pstm->Read(&bf, sizeof(bf), NULL)))
    {
        return(hr);
    }

    /* Do we have a RC HEADER? */
    if (!ISDIB (bf.bfType))
    {
        bf.bfOffBits = 0L;
        dlibMove.HighPart = libOff.HighPart;
        dlibMove.LowPart = libOff.LowPart;
        if (FAILED(hr = pstm->Seek(dlibMove, STREAM_SEEK_SET, NULL)))
        {
            return(hr);
        }
    }
    if (FAILED(hr = pstm->Read(&bi, sizeof(bi), NULL)))
    {
        return(hr);
    }

    nNumColors = DibNumColors (&bi);

    /* Check the nature (BITMAPINFO or BITMAPCORE) of the info. block
     * and extract the field information accordingly. If a BITMAPCOREHEADER,
     * transfer it's field information to a BITMAPINFOHEADER-style block
     */
    switch (size = (int)bi.biSize)
    {
    case sizeof (BITMAPINFOHEADER):
        break;

    case sizeof (BITMAPCOREHEADER):
        bc = *(BITMAPCOREHEADER*)&bi;
        dwWidth   = (DWORD)bc.bcWidth;
        dwHeight  = (DWORD)bc.bcHeight;
        wPlanes   = bc.bcPlanes;
        wBitCount = bc.bcBitCount;
        bi.biSize           = sizeof(BITMAPINFOHEADER);
        bi.biWidth          = dwWidth;
        bi.biHeight         = dwHeight;
        bi.biPlanes         = wPlanes;
        bi.biBitCount       = wBitCount;
        bi.biCompression    = BI_RGB;
        bi.biSizeImage      = 0;
        bi.biXPelsPerMeter  = 0;
        bi.biYPelsPerMeter  = 0;
        bi.biClrUsed        = nNumColors;
        bi.biClrImportant   = nNumColors;
        dlibMove.HighPart = 0;
        dlibMove.LowPart = sizeof (BITMAPCOREHEADER) - sizeof (BITMAPINFOHEADER);
        if (FAILED(hr = pstm->Seek(dlibMove, STREAM_SEEK_CUR, NULL)))
        {
            return(hr);
        }
        break;

    default:
        /* Not a DIB! */
        return E_FAIL;
    }

    /*  Fill in some default values if they are zero */
    if (bi.biSizeImage == 0)
    {
        bi.biSizeImage = WIDTHBYTES ((DWORD)bi.biWidth * bi.biBitCount)
            * bi.biHeight;
    }
    if (bi.biClrUsed == 0)
        bi.biClrUsed = DibNumColors(&bi);

    /* Allocate for the BITMAPINFO structure and the color table. */
    hbi = GlobalAlloc (GHND, (LONG)bi.biSize + nNumColors * sizeof(RGBQUAD));
    if (!hbi)
        return NULL;
    lpbi = (LPBITMAPINFOHEADER)GlobalLock (hbi);
    *lpbi = bi;

    /* Get a pointer to the color table */
    pRgb = (RGBQUAD FAR *)((LPSTR)lpbi + bi.biSize);
    if (nNumColors)
    {
        if (size == sizeof(BITMAPCOREHEADER))
        {
            /* Convert a old color table (3 byte RGBTRIPLEs) to a new
             * color table (4 byte RGBQUADs)
             */
             if (FAILED(hr = pstm->Read(pRgb, nNumColors * sizeof(RGBTRIPLE), NULL)))
             {
                 return(hr);
             }

            for (i = nNumColors - 1; i >= 0; i--)
            {
                RGBQUAD rgb;

                rgb.rgbRed  = ((RGBTRIPLE FAR *)pRgb)[i].rgbtRed;
                rgb.rgbBlue = ((RGBTRIPLE FAR *)pRgb)[i].rgbtBlue;
                rgb.rgbGreen    = ((RGBTRIPLE FAR *)pRgb)[i].rgbtGreen;
                rgb.rgbReserved = (BYTE)0;

                pRgb[i] = rgb;
            }
        }
        else
        {
            if (FAILED(hr = pstm->Read(pRgb, nNumColors * sizeof(RGBQUAD), NULL)))
            {
                return(hr);
            }
        }
    }

    if (bf.bfOffBits != 0L)
    {
        dlibMove.HighPart = libOff.HighPart;
        dlibMove.LowPart = libOff.LowPart + bf.bfOffBits;
        if (FAILED(hr = pstm->Seek(dlibMove, STREAM_SEEK_SET, NULL)))
        {
            return(hr);
        }
    }

    GlobalUnlock(hbi);
    *phbi = hbi;
    return(S_OK);
}


STMDIB.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       stmdib.h
//
//  Contents:   Functions for saving and loading DIBs to OLE streams
//
//  Classes:
//
//  Functions:  WriteDibToStream
//              ReadDibFromStream
//              ReadDibBitmapInfoFromStream
//
//  History:    4-22-94   stevebl   Created
//
//----------------------------------------------------------------------------

#ifndef __STMDIB_H__
#define __STMDIB_H__

#ifdef __cplusplus
extern "C" {
#endif

#include <windows.h>
#include <ole2.h>

HRESULT WriteDibToStream (IStream * pstm, HANDLE hdib);
HRESULT ReadDibFromStream (IStream * pstm, HANDLE * phDib);
HRESULT ReadDibBitmapInfoFromStream(IStream * pstm, HANDLE * phbi);

#ifdef __cplusplus
}
#endif

#endif //__STMDIB_H__


JULIA.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       julia.cxx
//
//  Contents:   implementation for Julia Set Fractal engine
//
//  Classes:    CJuliaCF
//
//  Functions:  DllEntryPoint
//              DllGetClassObject
//              DllCanUnloadNow
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include <windows.h>
#include "julia.h"
#include <juliacid.h>

ULONG gcRef, gcLock;
HINSTANCE ghinst;

extern "C"
{
    BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID);
}

//+---------------------------------------------------------------------------
//
//  Function:   DllEntryPoint
//
//  Synopsis:   the DLL's entry point
//
//  Arguments:  [hinst]      - instance handle for the dll
//              [fdwReason]  - reason for calling the entry point
//              [lpReserved] - reserved
//
//  Returns:    TRUE if DLL_PROCESS_ATTACH initialization succeeds
//              FALSE otherwise
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

BOOL WINAPI DllMain(
    HINSTANCE hinst,
    DWORD fdwReason,
    LPVOID lpReserved)
{
    switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        ghinst = hinst;
        gcRef = gcLock = 0;
        return(TRUE); // initialization succeeded
    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        break;
    }
    return(FALSE);  // initialization failed (ignored if not DLL_PROCESS_ATTACH)
}

//+---------------------------------------------------------------------------
//
//  Function:   DllGetClassObject
//
//  Synopsis:   function called by OLE to get a class object
//
//  Arguments:  [rclsid] - the class id of the desired class object
//              [riid]   - the id of the desired interface
//              [ppv]    - pointer to recieve the interface
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT PASCAL DllGetClassObject(
    REFCLSID rclsid,
    REFIID riid,
    LPVOID * ppv)
{
    if (!IsEqualGUID(rclsid, CLSID_JULIASET))
    {
        return(E_FAIL);
    }
    if ((!IsEqualGUID(riid, IID_IUnknown)) &&
        (!IsEqualGUID(riid, IID_IClassFactory)))
    {
        return(E_NOINTERFACE);
    }
    *ppv = (LPVOID) new CJuliaCF();
    if (NULL == *ppv)
    {
        return(E_OUTOFMEMORY);
    }
    ((IUnknown*)*ppv)->AddRef();
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Function:   DllCanUnloadNow
//
//  Synopsis:   determines if there are no outstanding references on this DLL
//
//  Returns:    S_OK if the DLL can be unloaded
//              S_FALSE if not
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

STDAPI DllCanUnloadNow(void)
{
    // return S_OK if there are no references on myself
    if (0 == gcRef && 0 == gcLock)
    {
        return(S_OK);
    }
    return(S_FALSE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CJuliaCF::CJuliaCF
//
//  Synopsis:   constructor
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

CJuliaCF::CJuliaCF()
{
    _cRef = 0;
}

//+---------------------------------------------------------------------------
//
//  Member:     CJuliaCF::~CJuliaCF
//
//  Synopsis:   destructor
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

CJuliaCF::~CJuliaCF()
{
}

//+---------------------------------------------------------------------------
//
//  Member:     CJuliaCF::QueryInterface
//
//  Synopsis:   Standard OLE interface
//
//  Arguments:  [riid] - id of the desired interface
//              [ppv]  - pointer to receive the interface
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CJuliaCF::QueryInterface(REFIID riid, LPVOID * ppv)
{
    *ppv = NULL;
    if ((!IsEqualGUID(riid, IID_IUnknown)) &&
        (!IsEqualGUID(riid, IID_IClassFactory)))
    {
        return(E_NOINTERFACE);
    }
    *ppv = (IClassFactory *) this;
    AddRef();
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CJuliaCF::AddRef
//
//  Synopsis:   increment reference count
//
//  Returns:    new reference count
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CJuliaCF::AddRef(void)
{
    return(++_cRef);
}

//+---------------------------------------------------------------------------
//
//  Member:     CJuliaCF::Release
//
//  Synopsis:   decrement reference count
//
//  Returns:    new reference count
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CJuliaCF::Release(void)
{
    ULONG cRef = --_cRef;
    if (0 == cRef)
    {
        delete(this);
    }
    return(cRef);
}

//+---------------------------------------------------------------------------
//
//  Member:     CJuliaCF::CreateInstance
//
//  Synopsis:   creates an instance of a Julia Engine
//
//  Arguments:  [pUnkOuter] - controlling unknown (must be NULL)
//              [riid]      - id of desired interface
//              [ppv]       - pointer to receive the interface
//
//  Returns:    S_OK - success
//              CLASS_E_NOAGGREATION - the caller tried to aggregate
//              CLASS_E_CLASSNOTAVAILABLE - couldn't initialize the class
//              E_OUTOFMEMORY - not enough memory to instantiate class
//
//  Modifies:
//
//  History:    4-14-94   stevebl   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CJuliaCF::CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, LPVOID * ppv)
{
    HRESULT hr = S_OK;
    *ppv = NULL;
    if (pUnkOuter != NULL)
    {
        return(CLASS_E_NOAGGREGATION);
    }

    CJulia * pObj = new CJulia;
    if (NULL == pObj)
    {
        return(E_OUTOFMEMORY);
    }

    if (!pObj->Initialize())
    {
        delete ppv;
        return(CLASS_E_CLASSNOTAVAILABLE);
    }

    hr = pObj->QueryInterface(riid, ppv);
    if (FAILED(hr))
    {
        delete ppv;
    }
    else
    {
        gcRef++;
    }

    return(hr);
}

//+---------------------------------------------------------------------------
//
//  Member:     CJuliaCF::LockServer
//
//  Synopsis:   locks the server, preventing it from being unloaded
//
//  Arguments:  [fLock] - TRUE to lock, FALSE to unlock
//
//  Returns:    S_OK
//
//  Modifies:   gcLock
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CJuliaCF::LockServer(BOOL fLock)
{
    if (fLock)
    {
        gcLock++;
    }
    else
    {
        gcLock--;
    }
    return(S_OK);
}


JULIA.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       julia.h
//
//  Contents:   class definitions for the Julia Set Fractal engine
//
//  Classes:    CJuliaCF
//              CJulia
//
//  Functions:
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

#ifndef __JULIA_H__
#define __JULIA_H__

#define PROPERTIES 100
#define IDC_LOWREAL         1001
#define IDC_LOWIMAGINARY    1002
#define IDC_HIGHREAL        1003
#define IDC_HIGHIMAGINARY   1004
#define IDC_CREAL           1005
#define IDC_CIMAGINARY      1006
#define IDC_LIMIT           1007
#define IDC_ABOUT           1008
#define IDC_USEBOUNDINGBOXES 1009
#define IDS_ABOUT_TITLE     1100
#define IDS_ABOUT_TEXT      1101

#ifdef __cplusplus

#include <frcngn.h>
#include <frhost.h>
#include <qudcln.h>
#include <qudngn.h>
#include <cdialog.h>

//+---------------------------------------------------------------------------
//
//  Class:      CJuliaCF
//
//  Purpose:    class factory for the Julia set engine
//
//  Interface:  CJuliaCF       -- constructor
//              ~CJuliaCF      -- destructor
//              QueryInterface -- requests an interface
//              AddRef         -- increments refcount
//              Release        -- decrements refcount
//              CreateInstance -- creates a Mandelbrot engine
//              LockServer     -- keeps cf around if released
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

class CJuliaCF : public IClassFactory
{
public:
    CJuliaCF();
    ~CJuliaCF();

    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID *);
    ULONG STDMETHODCALLTYPE AddRef(void);
    ULONG STDMETHODCALLTYPE Release(void);

    HRESULT STDMETHODCALLTYPE CreateInstance(LPUNKNOWN, REFIID, LPVOID*);
    HRESULT STDMETHODCALLTYPE LockServer(BOOL);

protected:
    ULONG _cRef;
};

//+---------------------------------------------------------------------------
//
//  Class:      CJulia
//
//  Purpose:    implements the Julia set engine
//
//  Interface:  QueryInterface     -- retrieves an interface
//              AddRef             -- increments refcount
//              Release            -- decrements refcount
//              GetClassID         -- gets the CLSID
//              IsDirty            -- tests if data should be saved
//              Load               -- loads properties
//              Save               -- saves properties
//              GetSizeMax         -- returns max size of property stream
//              Init               -- initializes engine
//              SetDefaults        -- sets default property values
//              SetProperties      -- displays the property dialog box
//              GetExtent          -- gets size of the graph (in graph units)
//              SetExtent          -- sets size of the graph (in graph units)
//              SetGraphSize       -- sets size of the graph (in pixels)
//              Start              -- starts the graph engine
//              Stop               -- stops the graph engine
//              ComputePoint       -- returns the graph color at a given point
//              DoneDrawingGraph   -- called when the graph engine is finished
//              DialogProc         -- dialog proc for the property dialog box
//              Initialize         -- private initialization function
//              CJulia             -- constructor
//              ~CJulia            -- destructor
//
//  History:    4-14-94   stevebl   Created
//              7-07-94   stevebl   Changed definition of ComputePoint
//
//----------------------------------------------------------------------------

class CJulia : public IFractalEngine, IPersistStream, IQuadrantClient, CHlprDialog
{
public:
    // IUnknown methods
    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID *);
    ULONG STDMETHODCALLTYPE AddRef(void);
    ULONG STDMETHODCALLTYPE Release(void);

    // IPersist methods
    HRESULT STDMETHODCALLTYPE GetClassID(LPCLSID pclsid);

    // IPersistStream methods
    HRESULT STDMETHODCALLTYPE IsDirty(void);
    HRESULT STDMETHODCALLTYPE Load(LPSTREAM pStm);
    HRESULT STDMETHODCALLTYPE Save(LPSTREAM pStm, BOOL fClearDirty);
    HRESULT STDMETHODCALLTYPE GetSizeMax(ULARGE_INTEGER * pcbSize);

    // IFractalEngine methods
    HRESULT STDMETHODCALLTYPE Init(IFractalHost *pfh);

    HRESULT STDMETHODCALLTYPE SetDefaults(void);

    HRESULT STDMETHODCALLTYPE SetProperties(HWND hwnd);

    HRESULT STDMETHODCALLTYPE GetExtent(
        double *pdLeft,
        double *pdTop,
        double *pdRight,
        double *pdBottom);

    HRESULT STDMETHODCALLTYPE SetExtent(
        double dLeft,
        double dTop,
        double dRight,
        double dBottom);

    HRESULT STDMETHODCALLTYPE SetGraphSize(
        unsigned int uWidth,
        unsigned int uHeight);

    HRESULT STDMETHODCALLTYPE Start(void);

    HRESULT STDMETHODCALLTYPE Stop(void);

    // IQuadrantClient methods
    HRESULT STDMETHODCALLTYPE ComputePoint(
        unsigned *puColor,
        int x,
        int y);

    HRESULT STDMETHODCALLTYPE DoneDrawingGraph(void);

    // Methods from CHlprDialog
    BOOL DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);

    // Methods that aren't part of any interface
    BOOL Initialize(void);
    CJulia();
    ~CJulia();
private:
    ULONG _cRef;
    double _dLeft, _dTop, _dRight, _dBottom;
    double _dCr, _dCi;
    unsigned _uWidth, _uHeight;
    BOOL _fRunning;
    IFractalHost * _pfh;
    IQuadrantEngine * _pqe;
    unsigned _uLimit;
    BOOL _fDirty;
    BOOL _fUseBoundingBoxes;
};

extern HINSTANCE ghinst;
extern ULONG gcRef, gcLock;

#endif //__cplusplus
#endif //__JULIA_H__


JULIAOB.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       juliaob.cxx
//
//  Contents:   Julia Fractal engine object
//
//  Classes:    CJulia
//
//  Functions:
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include <windows.h>
#include <ole2.h>
#include <quadcid.h>
#include <juliacid.h>
#include "julia.h"
#include <stdio.h>
#include <strmhelp.h>
#include <palsize.h>

//+---------------------------------------------------------------------------
//
//  Member:     CJulia::QueryInterface
//
//  Synopsis:   standard OLE method
//
//  Arguments:  [riid] - id of the desired interface
//              [ppv]  - pointer to receive the interface
//
//  Returns:    S_OK on success
//              E_NOINTERFACE if the requested interface isn't supported
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CJulia::QueryInterface(REFIID riid, LPVOID * ppv)
{
    *ppv = NULL;
    if (IsEqualGUID(IID_IFractalEngine, riid))
    {
        *ppv = (IFractalEngine *)this;
    }
    else if (IsEqualGUID(IID_IPersist, riid))
    {
        *ppv = (IPersist *)this;
    }
    else if (IsEqualGUID(IID_IPersistStream, riid))
    {
        *ppv = (IPersistStream *)this;
    }
    else if (IsEqualGUID(IID_IUnknown, riid))
    {
        *ppv = (IUnknown *)(IFractalEngine *)this;
    }
    else if (IsEqualGUID(IID_IQuadrantClient, riid))
    {
        *ppv = (IQuadrantClient *)this;
    }
    if (*ppv)
    {
        ((IUnknown *)*ppv)->AddRef();
        return(S_OK);
    }
    return(E_NOINTERFACE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CJulia::AddRef
//
//  Synopsis:   increments the reference count
//
//  Returns:    new reference count
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CJulia::AddRef(void)
{
    return(++_cRef);
}

//+---------------------------------------------------------------------------
//
//  Member:     CJulia::Release
//
//  Synopsis:   decrements the reference count
//
//  Returns:    new reference count
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CJulia::Release(void)
{
    ULONG cRef = --_cRef;
    if (0 == cRef)
    {
        delete this;
        gcRef--;
    }
    return(cRef);
}

// IPersist methods

//+---------------------------------------------------------------------------
//
//  Member:     CJulia::GetClassID
//
//  Synopsis:   returns the object's class ID
//
//  Arguments:  [pclsid] - pointer to recieve the class ID
//
//  Returns:    S_OK
//
//  History:    4-21-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT CJulia::GetClassID(LPCLSID pclsid)
{
    memcpy(pclsid, &CLSID_JULIASET, sizeof(GUID));
    return(S_OK);
}

// IPersistStream methods

//+---------------------------------------------------------------------------
//
//  Member:     CJulia::IsDirty
//
//  Synopsis:   used to determine if the state of the object has changed
//
//  Returns:    S_OK if it is dirty
//              S_FALSE if it is not dirty
//
//  History:    4-21-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT CJulia::IsDirty(void)
{
    if (_fDirty)
    {
        return(S_OK);
    }
    else
    {
        return(S_FALSE);
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CJulia::Load
//
//  Synopsis:   loads the fractal properties from a stream
//
//  Arguments:  [pStm] - pointer to an IStream interface
//
//  Returns:    S_OK on success
//              E_FAIL on error
//              E_OUTOFMEMORY if there's not enough memory (can't happen here)
//
//  History:    4-21-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT CJulia::Load(LPSTREAM pStm)
{
    HRESULT hr;
    if (_fRunning)
    {
        return(E_FAIL);
    }
    if (FAILED(hr = ReadDouble(pStm, &_dLeft, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = ReadDouble(pStm, &_dTop, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = ReadDouble(pStm, &_dRight, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = ReadDouble(pStm, &_dBottom, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = pStm->Read(&_uLimit, sizeof(_uLimit), NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = ReadDouble(pStm, &_dCr, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = ReadDouble(pStm, &_dCi, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = pStm->Read(&_fUseBoundingBoxes, sizeof(_fUseBoundingBoxes), NULL)))
    {
        return(hr);
    }
    _fDirty = FALSE;
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CJulia::Save
//
//  Synopsis:   saves the fractal properties to a stream
//
//  Arguments:  [pStm]        - pointer to an IStream interface
//              [fClearDirty] - TRUE if the dirty flag should be cleared
//
//  Returns:    S_OK on success
//
//  History:    4-21-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT CJulia::Save(LPSTREAM pStm, BOOL fClearDirty)
{
    if (_fRunning)
    {
        return(STG_E_CANTSAVE);
    }
    HRESULT hr;
    if (FAILED(hr = WriteDouble(pStm, _dLeft, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = WriteDouble(pStm, _dTop, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = WriteDouble(pStm, _dRight, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = WriteDouble(pStm, _dBottom, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = pStm->Write(&_uLimit, sizeof(_uLimit), NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = WriteDouble(pStm, _dCr, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = WriteDouble(pStm, _dCi, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = pStm->Write(&_fUseBoundingBoxes, sizeof(_fUseBoundingBoxes), NULL)))
    {
        return(hr);
    }
    if (fClearDirty)
    {
        _fDirty = FALSE;
    }
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CJulia::GetSizeMax
//
//  Synopsis:   used to determine the maximum size of the object's data
//
//  Arguments:  [pcbSize] - pointer to recieve the max size
//
//  Returns:    S_OK
//
//  History:    4-21-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT CJulia::GetSizeMax(ULARGE_INTEGER * pcbSize)
{
    pcbSize->HighPart = 0;
    pcbSize->LowPart = SizeDouble() * 6 + sizeof(_uLimit);
    return(S_OK);
}

// IFractalEngine methods

//+---------------------------------------------------------------------------
//
//  Member:     CJulia::Init
//
//  Synopsis:   Initializes the graph engine and saves a pointer to the
//              IFractalHost interface that WinFract passes in as
//              the engine is initialized.
//
//  Arguments:  [pfh] - pointer to an IFractalHost interface
//
//  Returns:    S_OK
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CJulia::Init(IFractalHost *pfh)
{
    _pfh = pfh;
    // NOTE that I don't addref this pointer!  That would create a circular
    // reference count paradox.

    _pqe->Init((IQuadrantClient *)this, pfh);
    _pqe->UseBoundingBoxes(_fUseBoundingBoxes);

    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CJulia::SetDefaults
//
//  Synopsis:   sets default values for the Julia Set
//
//  History:    4-22-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CJulia::SetDefaults(void)
{
    if (_uWidth > _uHeight)
    {
        // image is wider than it is tall
        _dTop = 2.0;
        _dBottom = -2.0;
        _dLeft = (-2.0 * _uWidth) / _uHeight;
        _dRight = (2.0 * _uWidth) / _uHeight;
    }
    else
    {
        // image is taller than it is wide
        _dLeft = -2.0;
        _dRight = 2.0;
        _dTop = (2.0 * _uHeight) / _uWidth;
        _dBottom = (-2.0 * _uHeight) / _uWidth;
    }
    _uLimit = 200;
    _pqe->Reset();
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CJulia::SetProperties
//
//  Synopsis:   displays the properties dialog box
//
//  Arguments:  [hwnd] - parent window
//
//  History:    4-22-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CJulia::SetProperties(HWND hwnd)
{
    ShowDialog(ghinst, MAKEINTRESOURCE(PROPERTIES), hwnd);
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CJulia::GetExtent
//
//  Synopsis:   retrieves the extent of the graph
//
//  Arguments:  [pdLeft]   - x value at the left of the graph
//              [pdTop]    - y value at the top of the graph
//              [pdRight]  - x value at the right of the graph
//              [pdBottom] - y value at the bottom of the graph
//
//  Returns:    S_OK
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CJulia::GetExtent(
    double *pdLeft,
    double *pdTop,
    double *pdRight,
    double *pdBottom)
{
    *pdLeft = _dLeft;
    *pdTop = _dTop;
    *pdRight = _dRight;
    *pdBottom = _dBottom;
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CJulia::SetExtent
//
//  Synopsis:   sets the graph's extent
//
//  Arguments:  [dLeft]   - x value at the left of the graph
//              [dTop]    - y value at the top of the graph
//              [dRight]  - x value at the right of the graph
//              [dBottom] - y value at the bottom of the graph
//
//  Returns:    S_OK - extents set
//              E_FAIL - the graph was running so the extents couldn't be set
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CJulia::SetExtent(
    double dLeft,
    double dTop,
    double dRight,
    double dBottom)
{
    if (_fRunning)
    {
        return(E_FAIL);
    }
    _dLeft = dLeft;
    _dTop = dTop;
    _dRight = dRight;
    _dBottom = dBottom;
    _pqe->Reset();
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CJulia::SetGraphSize
//
//  Synopsis:   Tells the graph engine how big the graph is
//
//  Arguments:  [uWidth]  - width of the graph (in pixels)
//              [uHeight] - height of the graph (in pixels)
//
//  Returns:    S_OK - success
//              E_FAIL - the graph was running or a value was illegal
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CJulia::SetGraphSize(
    unsigned int uWidth,
    unsigned int uHeight)
{
    if (_fRunning || uWidth == 0 || uHeight == 0)
    {
        return(E_FAIL);
    }
    _uWidth = uWidth;
    _uHeight = uHeight;
    return(_pqe->SetGraphSize(uWidth, uHeight));
}

//+---------------------------------------------------------------------------
//
//  Member:     CJulia::Start
//
//  Synopsis:   starts the graph engine
//
//  History:    4-22-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CJulia::Start(void)
{
    _fRunning = TRUE;
    return(_pqe->Start());
}

//+---------------------------------------------------------------------------
//
//  Member:     CJulia::Stop
//
//  Synopsis:   Stops the graph engine
//
//  History:    4-22-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CJulia::Stop(void)
{
    _fRunning = FALSE;
    return(_pqe->Stop());
}

// IQuadrantClient methods

//+---------------------------------------------------------------------------
//
//  Member:     CJulia::ComputePoint
//
//  Synopsis:   Returns what color should be painted at this particular point
//              on the graph.
//
//  Arguments:  [puColor] - pointer to variable to recieve the color
//              [x]       - x position of the pixel
//              [y]       - y position of the pixel
//
//  Returns:    S_OK
//
//  Notes:      Assume there are PALETTESIZE colors in the palette.
//              Colors 1 to PALETTESIZE-1 can cycle; 0 cannot.
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CJulia::ComputePoint(
    unsigned * puColor,
    int x,
    int y)
{
    unsigned c = 1;

    //  The Julia Set formula is: Z' <= Z^2 + C

    double
        dZr = 0.0,          // real component of Z
        dZi = 0.0,          // imaginary component of Z
        dZiSqr = 0.0,       // Zi squared
        dZrSqr = 0.0,       // Zr squared
        dZr1;               // temporary holder for Zr

    dZr = ((x * (_dRight - _dLeft)) / _uWidth) + _dLeft;
    dZi = ((((_uHeight - 1) - y) * (_dTop - _dBottom)) / _uHeight) + _dBottom;
    if (dZr < -2 || dZr > 2 || dZi < -2 || dZi > 2)
    {
        *puColor = 1;
        return(S_OK);
    }
    dZiSqr = dZi * dZi;
    dZrSqr = dZr * dZr;
    while (c < _uLimit && dZiSqr + dZrSqr < 4)
    {
        dZr1 = dZrSqr - dZiSqr + _dCr;
        dZi = 2 * dZr * dZi + _dCi;
        dZr = dZr1;
        dZiSqr = dZi * dZi;
        dZrSqr = dZr * dZr;
        ++c;
    }
    if (c >= _uLimit)
    {
        *puColor = 0;
        return(S_OK);
    }
    *puColor = ((c - 1) % (PALETTESIZE - 1)) + 1;
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CJulia::DoneDrawingGraph
//
//  Synopsis:   called by the quad helper engine when the graph is done
//
//  History:    4-22-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CJulia::DoneDrawingGraph(void)
{
    _fRunning = FALSE;
    return(S_OK);
}

// Methods that aren't part of any interface

//+---------------------------------------------------------------------------
//
//  Member:     CJulia::Initialize
//
//  Synopsis:   initialize the object
//
//  Returns:    TRUE on success, FALSE on failure
//
//  History:    4-22-94   stevebl   Created
//
//----------------------------------------------------------------------------

BOOL CJulia::Initialize(void)
{
    HRESULT hr = CoCreateInstance(
        CLSID_QUADRANTENGINE,
        NULL,
        CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER,
        IID_IQuadrantEngine,
        (void **)&_pqe);
    if (FAILED(hr))
    {
        // couldn't get an IQuadrantEngine interface
        return(FALSE);
    }
    return(TRUE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CJulia::CJulia
//
//  Synopsis:   constructor
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

CJulia::CJulia()
{
    _dLeft = -2.0;
    _dTop = 2.0;
    _dRight = 2.0;
    _dBottom = -2.0;
    _uWidth = 160;
    _uHeight = 120;
    _dCr = -1.25;
    _dCi = 0.1;
    _fRunning = FALSE;
    _cRef = 0;
    _pfh = NULL;
    _pqe = NULL;
    _uLimit = 200;  // default limit
    _fDirty = TRUE;
    _fUseBoundingBoxes = TRUE;
}

//+---------------------------------------------------------------------------
//
//  Member:     CJulia::~CJulia
//
//  Synopsis:   destructor
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

CJulia::~CJulia()
{
    if (_pqe)
    {
        _pqe->Release();
    }
}

#define LENGTH 20
#define ABOUT_STRING_LENGTH 256

//+---------------------------------------------------------------------------
//
//  Member:     CJulia::DialogProc
//
//  Synopsis:   Dialog proc for the Properties dialog box
//
//  Arguments:  [hwndDlg] - handle to the dialog
//              [uMsg]    - message
//              [wParam]  - first message parameter
//              [lParam]  - second message parameter
//
//  History:    4-18-94   stevebl   Created
//
//----------------------------------------------------------------------------

BOOL CJulia::DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    char mbs[LENGTH + 1];

    switch (uMsg)
    {
    case WM_INITDIALOG:
        CheckDlgButton(
            hwndDlg,
            IDC_USEBOUNDINGBOXES,
            _fUseBoundingBoxes);
        sprintf(mbs, "%.13lg", _dLeft);
        SetDlgItemTextA(hwndDlg, IDC_LOWREAL, mbs);
        sprintf(mbs, "%.13lg", _dBottom);
        SetDlgItemTextA(hwndDlg, IDC_LOWIMAGINARY, mbs);
        sprintf(mbs, "%.13lg", _dRight);
        SetDlgItemTextA(hwndDlg, IDC_HIGHREAL, mbs);
        sprintf(mbs, "%.13lg", _dTop);
        SetDlgItemTextA(hwndDlg, IDC_HIGHIMAGINARY, mbs);
        sprintf(mbs, "%.13lg", _dCr);
        SetDlgItemTextA(hwndDlg, IDC_CREAL, mbs);
        sprintf(mbs, "%.13lg", _dCi);
        SetDlgItemTextA(hwndDlg, IDC_CIMAGINARY, mbs);
        sprintf(mbs, "%d", _uLimit);
        SetDlgItemTextA(hwndDlg, IDC_LIMIT, mbs);
        return(TRUE);
    case WM_COMMAND:
        switch (LOWORD(wParam))
        {
        case IDOK:
            _fUseBoundingBoxes = IsDlgButtonChecked(hwndDlg, IDC_USEBOUNDINGBOXES);
            _pqe->UseBoundingBoxes(_fUseBoundingBoxes);
            GetDlgItemTextA(hwndDlg, IDC_LOWREAL, mbs, LENGTH);
            sscanf(mbs, "%lf", &_dLeft);
            GetDlgItemTextA(hwndDlg, IDC_LOWIMAGINARY, mbs, LENGTH);
            sscanf(mbs, "%lf", &_dBottom);
            GetDlgItemTextA(hwndDlg, IDC_HIGHREAL, mbs, LENGTH);
            sscanf(mbs, "%lf", &_dRight);
            GetDlgItemTextA(hwndDlg, IDC_HIGHIMAGINARY, mbs, LENGTH);
            sscanf(mbs, "%lf", &_dTop);
            GetDlgItemTextA(hwndDlg, IDC_CREAL, mbs, LENGTH);
            sscanf(mbs, "%lf", &_dCr);
            GetDlgItemTextA(hwndDlg, IDC_CIMAGINARY, mbs, LENGTH);
            sscanf(mbs, "%lf", &_dCi);
            GetDlgItemTextA(hwndDlg, IDC_LIMIT, mbs, LENGTH);
            sscanf(mbs, "%u", &_uLimit);
            if (_uLimit < 1)
            {
                _uLimit = 1;
            }
            _pqe->Reset();
            EndDialog(hwndDlg, TRUE);
            return(TRUE);
        case IDC_ABOUT:
            {
                TCHAR szTitle[ABOUT_STRING_LENGTH];
                TCHAR szText[ABOUT_STRING_LENGTH];
                if (LoadString(ghinst, IDS_ABOUT_TITLE, szTitle, ABOUT_STRING_LENGTH))
                {
                    if (LoadString(ghinst, IDS_ABOUT_TEXT, szText, ABOUT_STRING_LENGTH))
                    {
                        MessageBox(
                            hwndDlg,
                            szText,
                            szTitle,
                            MB_OK | MB_ICONINFORMATION);
                    }
                }
            }
            return(TRUE);
        case IDCANCEL:
            EndDialog(hwndDlg, FALSE);
            return(TRUE);
        default:
            return(FALSE);
        }
    }
    return(FALSE);
}


MANDEL.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       mandel.cxx
//
//  Contents:   implementation for Mandelbrot Fractal engine
//
//  Classes:    CMandelbrotCF
//
//  Functions:  DllEntryPoint
//              DllGetClassObject
//              DllCanUnloadNow
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include <windows.h>
#include <ole2.h>
#include "mandel.h"
#include <mandcid.h>

ULONG gcRef, gcLock;
HINSTANCE ghinst;

extern "C"
{
    BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID);
}

//+---------------------------------------------------------------------------
//
//  Function:   DllEntryPoint
//
//  Synopsis:   the DLL's entry point
//
//  Arguments:  [hinst]      - instance handle for the dll
//              [fdwReason]  - reason for calling the entry point
//              [lpReserved] - reserved
//
//  Returns:    TRUE if DLL_PROCESS_ATTACH initialization succeeds
//              FALSE otherwise
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

BOOL WINAPI DllMain(
    HINSTANCE hinst,
    DWORD fdwReason,
    LPVOID lpReserved)
{
    switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        ghinst = hinst;
        gcRef = gcLock = 0;
        return(TRUE); // initialization succeeded
    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        break;
    }
    return(FALSE);  // initialization failed (ignored if not DLL_PROCESS_ATTACH)
}

//+---------------------------------------------------------------------------
//
//  Function:   DllGetClassObject
//
//  Synopsis:   function called by OLE to get a class object
//
//  Arguments:  [rclsid] - the class id of the desired class object
//              [riid]   - the id of the desired interface
//              [ppv]    - pointer to recieve the interface
//
//  Returns:    S_OK if the class and interface are implemented
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT PASCAL DllGetClassObject(
    REFCLSID rclsid,
    REFIID riid,
    LPVOID * ppv)
{
    if (!IsEqualGUID(rclsid, CLSID_MANDELBROT))
    {
        return(E_FAIL);
    }
    if ((!IsEqualGUID(riid, IID_IUnknown)) &&
        (!IsEqualGUID(riid, IID_IClassFactory)))
    {
        return(E_NOINTERFACE);
    }
    *ppv = (LPVOID) new CMandelbrotCF();
    if (NULL == *ppv)
    {
        return(E_OUTOFMEMORY);
    }
    ((IUnknown*)*ppv)->AddRef();
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Function:   DllCanUnloadNow
//
//  Synopsis:   determines if there are no outstanding references on this DLL
//
//  Returns:    S_OK if the DLL can be unloaded
//              S_FALSE if not
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

STDAPI DllCanUnloadNow(void)
{
    // return S_OK if there are no references on myself
    if (0 == gcRef && 0 == gcLock)
    {
        return(S_OK);
    }
    return(S_FALSE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrotCF::CMandelbrotCF
//
//  Synopsis:   constructor
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

CMandelbrotCF::CMandelbrotCF()
{
    _cRef = 0;
}

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrotCF::~CMandelbrotCF
//
//  Synopsis:   destructor
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

CMandelbrotCF::~CMandelbrotCF()
{
}

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrotCF::QueryInterface
//
//  Synopsis:   Standard OLE interface
//
//  Arguments:  [riid] - id of the desired interface
//              [ppv]  - pointer to receive the interface
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CMandelbrotCF::QueryInterface(REFIID riid, LPVOID * ppv)
{
    *ppv = NULL;
    if ((!IsEqualGUID(riid, IID_IUnknown)) &&
        (!IsEqualGUID(riid, IID_IClassFactory)))
    {
        return(E_NOINTERFACE);
    }
    *ppv = (IClassFactory *) this;
    AddRef();
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrotCF::AddRef
//
//  Synopsis:   increment reference count
//
//  Returns:    new reference count
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CMandelbrotCF::AddRef(void)
{
    return(++_cRef);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrotCF::Release
//
//  Synopsis:   decrement reference count
//
//  Returns:    new reference count
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CMandelbrotCF::Release(void)
{
    ULONG cRef = --_cRef;
    if (0 == cRef)
    {
        delete(this);
    }
    return(cRef);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrotCF::CreateInstance
//
//  Synopsis:   creates an instance of a Mandelbrot Engine
//
//  Arguments:  [pUnkOuter] - controlling unknown (must be NULL)
//              [riid]      - id of desired interface
//              [ppv]       - pointer to receive the interface
//
//  Returns:    S_OK - success
//              CLASS_E_NOAGGREATION - the caller tried to aggregate
//              CLASS_E_CLASSNOTAVAILABLE - couldn't initialize the class
//              E_OUTOFMEMORY - not enough memory to instantiate class
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CMandelbrotCF::CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, LPVOID * ppv)
{
    HRESULT hr = S_OK;
    *ppv = NULL;
    if (pUnkOuter != NULL)
    {
        return(CLASS_E_NOAGGREGATION);
    }

    CMandelbrot * pObj = new CMandelbrot;
    if (NULL == pObj)
    {
        return(E_OUTOFMEMORY);
    }

    if (!pObj->Initialize())
    {
        delete ppv;
        return(CLASS_E_CLASSNOTAVAILABLE);
    }

    hr = pObj->QueryInterface(riid, ppv);
    if (FAILED(hr))
    {
        delete ppv;
    }
    else
    {
        gcRef++;
    }

    return(hr);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrotCF::LockServer
//
//  Synopsis:   locks the server, preventing it from being unloaded
//
//  Arguments:  [fLock] - TRUE to lock, FALSE to unlock
//
//  Returns:    S_OK
//
//  Modifies:   gcLock
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CMandelbrotCF::LockServer(BOOL fLock)
{
    if (fLock)
    {
        gcLock++;
    }
    else
    {
        gcLock--;
    }
    return(S_OK);
}


MANDEL.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       mandel.h
//
//  Contents:   class definitions for the Mandelbrot Fractal engine
//
//  Classes:    CMandelbrotCF
//              CMandelbrot
//
//  Functions:
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

#ifndef __MANDEL_H__
#define __MANDEL_H__

#define PROPERTIES 100

#define IDC_LOWREAL         1001
#define IDC_LOWIMAGINARY    1002
#define IDC_HIGHREAL        1003
#define IDC_HIGHIMAGINARY   1004
#define IDC_LIMIT           1005
#define IDC_ABOUT           1006
#define IDC_USEBOUNDINGBOXES 1007

#define IDS_ABOUT_TITLE     1100
#define IDS_ABOUT_TEXT      1101

#ifdef __cplusplus

#include <frcngn.h>
#include <frhost.h>
#include <qudcln.h>
#include <qudngn.h>
#include <cdialog.h>

//+---------------------------------------------------------------------------
//
//  Class:      CMandelbrotCF
//
//  Purpose:    class factory for the Mandelbrot engine
//
//  Interface:  CMandelbrotCF  -- constructor
//              ~CMandelbrotCF -- destrctor
//              QueryInterface -- requests an interface
//              AddRef         -- increments refcount
//              Release        -- decrements refcount
//              CreateInstance -- creates a Mandelbrot engine
//              LockServer     -- keeps cf around if released
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

class CMandelbrotCF : public IClassFactory
{
public:
    CMandelbrotCF();
    ~CMandelbrotCF();

    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID *);
    ULONG STDMETHODCALLTYPE AddRef(void);
    ULONG STDMETHODCALLTYPE Release(void);

    HRESULT STDMETHODCALLTYPE CreateInstance(LPUNKNOWN, REFIID, LPVOID*);
    HRESULT STDMETHODCALLTYPE LockServer(BOOL);

protected:
    ULONG _cRef;
};

//+---------------------------------------------------------------------------
//
//  Class:      CMandelbrot
//
//  Purpose:    implements the Mandlebrot engine
//
//  Interface:  QueryInterface     -- retrieves an interface
//              AddRef             -- increments refcount
//              Release            -- decrements refcount
//              GetClassID         -- gets the CLSID
//              IsDirty            -- tests if data should be saved
//              Load               -- loads properties
//              Save               -- saves properties
//              GetSizeMax         -- returns max size of property stream
//              Init               -- initializes engine
//              SetDefaults        -- sets default property values
//              SetProperties      -- displays the property dialog box
//              GetExtent          -- gets size of the graph (in graph units)
//              SetExtent          -- sets size of the graph (in graph units)
//              SetGraphSize       -- sets size of the graph (in pixels)
//              Start              -- starts the graph engine
//              Stop               -- stops the graph engine
//              ComputePoint       -- returns the graph color at a given point
//              DoneDrawingGraph   -- called when the graph engine is finished
//              DialogProc         -- dialog proc for the property dialog box
//              Initialize         -- private initialization function
//              CMandelbrot        -- constructor
//              ~CMandelbrot       -- destructor
//
//  History:    4-14-94   stevebl   Created
//              7-07-94   stevebl   changed definition of ComputePoint
//
//----------------------------------------------------------------------------

class CMandelbrot : public IFractalEngine, IPersistStream, IQuadrantClient, CHlprDialog
{
public:
    // IUnknown methods
    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID *);
    ULONG STDMETHODCALLTYPE AddRef(void);
    ULONG STDMETHODCALLTYPE Release(void);

    // IPersist methods
    HRESULT STDMETHODCALLTYPE GetClassID(LPCLSID pclsid);

    // IPersistStream methods
    HRESULT STDMETHODCALLTYPE IsDirty(void);
    HRESULT STDMETHODCALLTYPE Load(LPSTREAM pStm);
    HRESULT STDMETHODCALLTYPE Save(LPSTREAM pStm, BOOL fClearDirty);
    HRESULT STDMETHODCALLTYPE GetSizeMax(ULARGE_INTEGER * pcbSize);

    // IFractalEngine methods
    HRESULT STDMETHODCALLTYPE Init(IFractalHost *pfh);

    HRESULT STDMETHODCALLTYPE SetDefaults(void);

    HRESULT STDMETHODCALLTYPE SetProperties(HWND hwnd);

    HRESULT STDMETHODCALLTYPE GetExtent(
        double *pdLeft,
        double *pdTop,
        double *pdRight,
        double *pdBottom);

    HRESULT STDMETHODCALLTYPE SetExtent(
        double dLeft,
        double dTop,
        double dRight,
        double dBottom);

    HRESULT STDMETHODCALLTYPE SetGraphSize(
        unsigned int uWidth,
        unsigned int uHeight);

    HRESULT STDMETHODCALLTYPE Start(void);

    HRESULT STDMETHODCALLTYPE Stop(void);

    // IQuadrantClient methods
    HRESULT STDMETHODCALLTYPE ComputePoint(
        unsigned * puColor,
        int x,
        int y);

    HRESULT STDMETHODCALLTYPE DoneDrawingGraph(void);

    // Methods from CHlprDialog
    BOOL DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);

    // Methods that aren't part of any interface
    BOOL Initialize(void);
    CMandelbrot();
    ~CMandelbrot();
private:
    ULONG _cRef;
    double _dLeft, _dTop, _dRight, _dBottom;
    unsigned _uWidth, _uHeight;
    BOOL _fRunning;
    IFractalHost * _pfh;
    IQuadrantEngine * _pqe;
    unsigned _uLimit;
    BOOL _fDirty;
    BOOL _fUseBoundingBoxes;
};

extern HINSTANCE ghinst;
extern ULONG gcRef, gcLock;

#endif //__cplusplus
#endif //__MANDEL_H__


MANDELOB.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       mandelob.cxx
//
//  Contents:   Mandelbrot Fractal engine object
//
//  Classes:    CMandelbrot
//
//  Functions:
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include <windows.h>
#include <ole2.h>
#include <quadcid.h>
#include <mandcid.h>
#include "mandel.h"
#include <stdio.h>
#include <strmhelp.h>
#include <palsize.h>

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrot::QueryInterface
//
//  Synopsis:   standard OLE method
//
//  Arguments:  [riid] - id of desired interface
//              [ppv]  - pointer to receive interface
//
//  Returns:    S_OK on success
//              E_NOINTERFACE if the requested interface isn't supported
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CMandelbrot::QueryInterface(REFIID riid, LPVOID * ppv)
{
    *ppv = NULL;
    if (IsEqualGUID(IID_IFractalEngine, riid))
    {
        *ppv = (IFractalEngine *)this;
    }
    else if (IsEqualGUID(IID_IPersist, riid))
    {
        *ppv = (IPersist *)this;
    }
    else if (IsEqualGUID(IID_IPersistStream, riid))
    {
        *ppv = (IPersistStream *)this;
    }
    else if (IsEqualGUID(IID_IUnknown, riid))
    {
        *ppv = (IUnknown *)(IFractalEngine *)this;
    }
    else if (IsEqualGUID(IID_IQuadrantClient, riid))
    {
        *ppv = (IQuadrantClient *)this;
    }
    if (*ppv)
    {
        ((IUnknown *)*ppv)->AddRef();
        return(S_OK);
    }
    return(E_NOINTERFACE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrot::AddRef
//
//  Synopsis:   increments the reference count
//
//  Returns:    new reference count
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CMandelbrot::AddRef(void)
{
    return(++_cRef);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrot::Release
//
//  Synopsis:   decrements the reference count
//
//  Returns:    new reference count
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CMandelbrot::Release(void)
{
    ULONG cRef = --_cRef;
    if (0 == cRef)
    {
        delete this;
        gcRef--;
    }
    return(cRef);
}

// IPersist methods

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrot::GetClassID
//
//  Synopsis:   returns the object's class ID
//
//  Arguments:  [pclsid] - pointer to recieve the class ID
//
//  Returns:    S_OK
//
//  History:    4-21-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT CMandelbrot::GetClassID(LPCLSID pclsid)
{
    memcpy(pclsid, &CLSID_MANDELBROT, sizeof(GUID));
    return(S_OK);
}

// IPersistStream methods

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrot::IsDirty
//
//  Synopsis:   used to determine if the state of the object has changed
//
//  Returns:    S_OK if it is dirty
//              S_FALSE if it is not dirty
//
//  History:    4-21-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT CMandelbrot::IsDirty(void)
{
    if (_fDirty)
    {
        return(S_OK);
    }
    else
    {
        return(S_FALSE);
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrot::Load
//
//  Synopsis:   loads the fractal properties from a stream
//
//  Arguments:  [pStm] - pointer to an IStream interface
//
//  Returns:    S_OK on success
//              E_FAIL on error
//              E_OUTOFMEMORY if there's not enough memory (can't happen here)
//
//  History:    4-21-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT CMandelbrot::Load(LPSTREAM pStm)
{
    HRESULT hr;
    if (_fRunning)
    {
        return(E_FAIL);
    }
    if (FAILED(hr = ReadDouble(pStm, &_dLeft, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = ReadDouble(pStm, &_dTop, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = ReadDouble(pStm, &_dRight, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = ReadDouble(pStm, &_dBottom, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = pStm->Read(&_uLimit, sizeof(_uLimit), NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = pStm->Read(&_fUseBoundingBoxes, sizeof(_fUseBoundingBoxes), NULL)))
    {
        return(hr);
    }
    _fDirty = FALSE;
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrot::Save
//
//  Synopsis:   saves the fractal properties to a stream
//
//  Arguments:  [pStm]        - pointer to an IStream interface
//              [fClearDirty] - TRUE if the dirty flag should be cleared
//
//  Returns:    S_OK on success
//
//  History:    4-21-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT CMandelbrot::Save(LPSTREAM pStm, BOOL fClearDirty)
{
    if (_fRunning)
    {
        return(STG_E_CANTSAVE);
    }
    HRESULT hr;
    if (FAILED(hr = WriteDouble(pStm, _dLeft, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = WriteDouble(pStm, _dTop, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = WriteDouble(pStm, _dRight, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = WriteDouble(pStm, _dBottom, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = pStm->Write(&_uLimit, sizeof(_uLimit), NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = pStm->Write(&_fUseBoundingBoxes, sizeof(_fUseBoundingBoxes), NULL)))
    {
        return(hr);
    }
    if (fClearDirty)
    {
        _fDirty = FALSE;
    }
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrot::GetSizeMax
//
//  Synopsis:   used to determine the maximum size of the object's data
//
//  Arguments:  [pcbSize] - pointer to recieve the max size
//
//  Returns:    S_OK
//
//  History:    4-21-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT CMandelbrot::GetSizeMax(ULARGE_INTEGER * pcbSize)
{
    pcbSize->HighPart = 0;
    pcbSize->LowPart = SizeDouble() * 4 + sizeof(_uLimit);
    return(S_OK);
}

// IFractalEngine methods

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrot::Init
//
//  Synopsis:   Initializes the graph engine and saves a pointer to the
//              IFractalHost interface that WinFract passes in as
//              the engine is initialized.
//
//  Arguments:  [pfh] - pointer to an IFractalHost interface
//
//  Returns:    S_OK
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CMandelbrot::Init(IFractalHost *pfh)
{
    _pfh = pfh;
    // NOTE that I don't addref this pointer!  That would create a circular
    // reference count paradox.

    _pqe->Init((IQuadrantClient *)this, pfh);
    _pqe->UseBoundingBoxes(_fUseBoundingBoxes);
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrot::SetDefaults
//
//  Synopsis:   initializes the graph engine with its default values
//
//  Returns:    S_OK
//
//  History:    4-21-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CMandelbrot::SetDefaults(void)
{
    if (_uWidth > _uHeight)
    {
        // image is wider than it is tall
        _dTop = 2.0;
        _dBottom = -2.0;
        _dLeft = (-2.0 * _uWidth) / _uHeight;
        _dRight = (2.0 * _uWidth) / _uHeight;
    }
    else
    {
        // image is taller than it is wide
        _dLeft = -2.0;
        _dRight = 2.0;
        _dTop = (2.0 * _uHeight) / _uWidth;
        _dBottom = (-2.0 * _uHeight) / _uWidth;
    }
    _uLimit = 200;
    _fDirty = TRUE;
    _pqe->Reset();
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrot::SetProperties
//
//  Synopsis:   Displays the graph engine's property dialog box.
//
//  Arguments:  [hwnd] - handle to the parent window
//
//  Returns:    S_OK
//
//  History:    4-21-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CMandelbrot::SetProperties(HWND hwnd)
{
    ShowDialog(ghinst, MAKEINTRESOURCE(PROPERTIES), hwnd);
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrot::GetExtent
//
//  Synopsis:   retrieves the extent of the graph
//
//  Arguments:  [pdLeft]   - x value at the left of the graph
//              [pdTop]    - y value at the top of the graph
//              [pdRight]  - x value at the right of the graph
//              [pdBottom] - y value at the bottom of the graph
//
//  Returns:    S_OK
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CMandelbrot::GetExtent(
    double *pdLeft,
    double *pdTop,
    double *pdRight,
    double *pdBottom)
{
    *pdLeft = _dLeft;
    *pdTop = _dTop;
    *pdRight = _dRight;
    *pdBottom = _dBottom;
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrot::SetExtent
//
//  Synopsis:   sets the graph's extent
//
//  Arguments:  [dLeft]   - x value at the left of the graph
//              [dTop]    - y value at the top of the graph
//              [dRight]  - x value at the right of the graph
//              [dBottom] - y value at the bottom of the graph
//
//  Returns:    S_OK - extents set
//              E_FAIL - the graph was running so the extents couldn't be set
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CMandelbrot::SetExtent(
    double dLeft,
    double dTop,
    double dRight,
    double dBottom)
{
    if (_fRunning)
    {
        return(E_FAIL);
    }
    _dLeft = dLeft;
    _dTop = dTop;
    _dRight = dRight;
    _dBottom = dBottom;
    _fDirty = TRUE;
    _pqe->Reset();
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrot::SetGraphSize
//
//  Synopsis:   Tells the graph engine how big the graph is
//
//  Arguments:  [uWidth]  - width of the graph (in pixels)
//              [uHeight] - height of the graph (in pixels)
//
//  Returns:    S_OK - success
//              E_FAIL - the graph was running or a value was illegal
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CMandelbrot::SetGraphSize(
    unsigned int uWidth,
    unsigned int uHeight)
{
    if (_fRunning || uWidth == 0 || uHeight == 0)
    {
        return(E_FAIL);
    }
    _uWidth = uWidth;
    _uHeight = uHeight;
    return(_pqe->SetGraphSize(uWidth, uHeight));
}

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrot::Start
//
//  Synopsis:   starts the graph engine
//
//  Returns:    S_OK on success
//
//  History:    4-21-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CMandelbrot::Start(void)
{
    _fRunning = TRUE;
    return(_pqe->Start());
}

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrot::Stop
//
//  Synopsis:   stops the graph engine
//
//  Returns:    S_OK on success
//
//  History:    4-21-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CMandelbrot::Stop(void)
{
    _fRunning = FALSE;
    return(_pqe->Stop());
}

// IQuadrantClient methods

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrot::ComputePoint
//
//  Synopsis:   Returns what color should be painted at this particular point
//              on the graph.
//
//  Arguments:  [puColor] - pointer to variable to recieve color
//              [x]       - x position of the pixel
//              [y]       - y position of the pixel
//
//  Returns:    S_OK
//
//  Notes:      There are PALETESIZE colors in the palette.
//              Colors 1 - PALETESIZE-1 can cycle; 0 cannot.
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CMandelbrot::ComputePoint(
    unsigned * puColor,
    int x,
    int y)
{
    unsigned c = 0;

    //  The Mandelbrot formula is: Z' <= Z^2 + C

    double
        dCr,                // real component of C
        dCi,                // imaginary component of C
        dZr = 0.0,          // real component of Z
        dZi = 0.0,          // imaginary component of Z
        dZiSqr = 0.0,       // Zi squared
        dZrSqr = 0.0,       // Zr squared
        dZr1;               // temporary holder for Zr

    dCr = ((x * (_dRight - _dLeft)) / _uWidth) + _dLeft;
    dCi = ((((_uHeight - 1) - y) * (_dTop - _dBottom)) / _uHeight) + _dBottom;
    if (dCr < -2 || dCr > 2 || dCi < -2 || dCi > 2)
    {
        *puColor = 1;
        return(S_OK);
    }
    while (c < _uLimit && dZiSqr + dZrSqr < 4)
    {
        dZr1 = dZrSqr - dZiSqr + dCr;
        dZi = 2 * dZr * dZi + dCi;
        dZr = dZr1;
        dZiSqr = dZi * dZi;
        dZrSqr = dZr * dZr;
        ++c;
    }
    if (c >= _uLimit)
    {
        *puColor = 0;
    }
    else
    {
        *puColor = ((c - 1) % (PALETTESIZE - 1)) + 1;
    }
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrot::DoneDrawingGraph
//
//  Synopsis:   called by the Quad graph helper engine when the graph is done
//
//  History:    4-21-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CMandelbrot::DoneDrawingGraph(void)
{
    _fRunning = FALSE;
    return(S_OK);
}

// Methods that aren't part of any interface

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrot::Initialize
//
//  Synopsis:   called by the clas factory to initialize the object
//
//  Returns:    TRUE on success
//              FALSE if the object couldn't be initialized
//
//  History:    4-21-94   stevebl   Created
//
//----------------------------------------------------------------------------

BOOL CMandelbrot::Initialize(void)
{
    HRESULT hr = CoCreateInstance(
        CLSID_QUADRANTENGINE,
        NULL,
        CLSCTX_INPROC_SERVER | CLSCTX_INPROC_HANDLER,
        IID_IQuadrantEngine,
        (void **)&_pqe);
    if (FAILED(hr))
    {
        // couldn't get an IQuadrantEngine interface
        return(FALSE);
    }
    return(TRUE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrot::CMandelbrot
//
//  Synopsis:   constructor
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

CMandelbrot::CMandelbrot()
{
    _fUseBoundingBoxes = TRUE;
    _dLeft = -2.0;
    _dTop = 2.0;
    _dRight = 2.0;
    _dBottom = -2.0;
    _uWidth = 160;
    _uHeight = 120;
    _fRunning = FALSE;
    _cRef = 0;
    _pfh = NULL;
    _pqe = NULL;
    _fDirty = TRUE;
    _uLimit = 200;  // default limit
}

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrot::~CMandelbrot
//
//  Synopsis:   destructor
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

CMandelbrot::~CMandelbrot()
{
    if (_pqe)
    {
        _pqe->Release();
    }
}

#define LENGTH 50
#define ABOUT_STRING_LENGTH 256

//+---------------------------------------------------------------------------
//
//  Member:     CMandelbrot::DialogProc
//
//  Synopsis:   Dialog proc for the Properties dialog box
//
//  Arguments:  [hwndDlg] - handle to the dialog
//              [uMsg]    - message
//              [wParam]  - first message parameter
//              [lParam]  - second message parameter
//
//  History:    4-18-94   stevebl   Created
//
//----------------------------------------------------------------------------

BOOL CMandelbrot::DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    char mbs[LENGTH + 1];

    switch (uMsg)
    {
    case WM_INITDIALOG:
        CheckDlgButton(
            hwndDlg,
            IDC_USEBOUNDINGBOXES,
            _fUseBoundingBoxes);
        sprintf(mbs, "%.13lg", _dLeft);
        SetDlgItemTextA(hwndDlg, IDC_LOWREAL, mbs);
        sprintf(mbs, "%.13lg", _dBottom);
        SetDlgItemTextA(hwndDlg, IDC_LOWIMAGINARY, mbs);
        sprintf(mbs, "%.13lg", _dRight);
        SetDlgItemTextA(hwndDlg, IDC_HIGHREAL, mbs);
        sprintf(mbs, "%.13lg", _dTop);
        SetDlgItemTextA(hwndDlg, IDC_HIGHIMAGINARY, mbs);
        sprintf(mbs, "%d", _uLimit);
        SetDlgItemTextA(hwndDlg, IDC_LIMIT, mbs);
        return(TRUE);
    case WM_COMMAND:
        switch (LOWORD(wParam))
        {
        case IDOK:
            _fUseBoundingBoxes = IsDlgButtonChecked(hwndDlg, IDC_USEBOUNDINGBOXES);
            _pqe->UseBoundingBoxes(_fUseBoundingBoxes);
            GetDlgItemTextA(hwndDlg, IDC_LOWREAL, mbs, LENGTH);
            sscanf(mbs, "%lf", &_dLeft);
            GetDlgItemTextA(hwndDlg, IDC_LOWIMAGINARY, mbs, LENGTH);
            sscanf(mbs, "%lf", &_dBottom);
            GetDlgItemTextA(hwndDlg, IDC_HIGHREAL, mbs, LENGTH);
            sscanf(mbs, "%lf", &_dRight);
            GetDlgItemTextA(hwndDlg, IDC_HIGHIMAGINARY, mbs, LENGTH);
            sscanf(mbs, "%lf", &_dTop);
            GetDlgItemTextA(hwndDlg, IDC_LIMIT, mbs, LENGTH);
            sscanf(mbs, "%u", &_uLimit);
            if (_uLimit < 1)
            {
                _uLimit = 1;
            }
            _fDirty = TRUE;
            _pqe->Reset();
            EndDialog(hwndDlg, TRUE);
            return(TRUE);
        case IDC_ABOUT:
            {
                TCHAR szTitle[ABOUT_STRING_LENGTH];
                TCHAR szText[ABOUT_STRING_LENGTH];
                if (LoadString(ghinst, IDS_ABOUT_TITLE, szTitle, ABOUT_STRING_LENGTH))
                {
                    if (LoadString(ghinst, IDS_ABOUT_TEXT, szText, ABOUT_STRING_LENGTH))
                    {
                        MessageBox(
                            hwndDlg,
                            szText,
                            szTitle,
                            MB_OK | MB_ICONINFORMATION);
                    }
                }
            }
            return(TRUE);
        case IDCANCEL:
            EndDialog(hwndDlg, FALSE);
            return(TRUE);
        default:
            return(FALSE);
        }
    }
    return(FALSE);
}


PLASMA.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       plasma.cxx
//
//  Contents:   implementation for Plasma Fractal engine
//
//  Classes:    CPlasmaCF
//
//  Functions:  DllEntryPoint
//              DllGetClassObject
//              DllCanUnloadNow
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include <windows.h>
#include <ole2.h>
#include "plasma.h"
#include <plasmcid.h>

ULONG gcRef, gcLock;
HINSTANCE ghinst;

extern "C"
{
    BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID);
}

//+---------------------------------------------------------------------------
//
//  Function:   DllEntryPoint
//
//  Synopsis:   the DLL's entry point
//
//  Arguments:  [hinst]      - instance handle for the dll
//              [fdwReason]  - reason for calling the entry point
//              [lpReserved] - reserved
//
//  Returns:    TRUE if DLL_PROCESS_ATTACH initialization succeeds
//              FALSE otherwise
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

BOOL WINAPI DllMain(
    HINSTANCE hinst,
    DWORD fdwReason,
    LPVOID lpReserved)
{
    switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        ghinst = hinst;
        gcRef = gcLock = 0;
        return(TRUE); // initialization succeeded
    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        break;
    }
    return(FALSE);  // initialization failed (ignored if not DLL_PROCESS_ATTACH)
}

//+---------------------------------------------------------------------------
//
//  Function:   DllGetClassObject
//
//  Synopsis:   function called by OLE to get a class object
//
//  Arguments:  [rclsid] - the class id of the desired class object
//              [riid]   - the id of the desired interface
//              [ppv]    - pointer to recieve the interface
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT PASCAL DllGetClassObject(
    REFCLSID rclsid,
    REFIID riid,
    LPVOID * ppv)
{
    if (!IsEqualGUID(rclsid, CLSID_PLASMA))
    {
        return(E_FAIL);
    }
    if ((!IsEqualGUID(riid, IID_IUnknown)) &&
        (!IsEqualGUID(riid, IID_IClassFactory)))
    {
        return(E_NOINTERFACE);
    }
    *ppv = (LPVOID) new CPlasmaCF();
    if (NULL == *ppv)
    {
        return(E_OUTOFMEMORY);
    }
    ((IUnknown*)*ppv)->AddRef();
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Function:   DllCanUnloadNow
//
//  Synopsis:   determines if there are no outstanding references on this DLL
//
//  Returns:    S_OK if the DLL can be unloaded
//              S_FALSE if not
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

STDAPI DllCanUnloadNow(void)
{
    // return S_OK if there are no references on myself
    if (0 == gcRef && 0 == gcLock)
    {
        return(S_OK);
    }
    return(S_FALSE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasmaCF::CPlasmaCF
//
//  Synopsis:   constructor
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

CPlasmaCF::CPlasmaCF()
{
    _cRef = 0;
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasmaCF::~CPlasmaCF
//
//  Synopsis:   destructor
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

CPlasmaCF::~CPlasmaCF()
{
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasmaCF::QueryInterface
//
//  Synopsis:   Standard OLE interface
//
//  Arguments:  [riid] - id of the desired interface
//              [ppv]  - pointer to receive the interface
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CPlasmaCF::QueryInterface(REFIID riid, LPVOID * ppv)
{
    *ppv = NULL;
    if ((!IsEqualGUID(riid, IID_IUnknown)) &&
        (!IsEqualGUID(riid, IID_IClassFactory)))
    {
        return(E_NOINTERFACE);
    }
    *ppv = (IClassFactory *) this;
    AddRef();
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasmaCF::AddRef
//
//  Synopsis:   increment reference count
//
//  Returns:    new reference count
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CPlasmaCF::AddRef(void)
{
    return(++_cRef);
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasmaCF::Release
//
//  Synopsis:   decrement reference count
//
//  Returns:    new reference count
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CPlasmaCF::Release(void)
{
    ULONG cRef = --_cRef;
    if (0 == cRef)
    {
        delete(this);
    }
    return(cRef);
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasmaCF::CreateInstance
//
//  Synopsis:   creates an instance of a Plasma Engine
//
//  Arguments:  [pUnkOuter] - controlling unknown (must be NULL)
//              [riid]      - id of desired interface
//              [ppv]       - pointer to receive the interface
//
//  Returns:    S_OK - success
//              CLASS_E_NOAGGREATION - the caller tried to aggregate
//              CLASS_E_CLASSNOTAVAILABLE - couldn't initialize the class
//              E_OUTOFMEMORY - not enough memory to instantiate class
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CPlasmaCF::CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, LPVOID * ppv)
{
    HRESULT hr = S_OK;
    *ppv = NULL;
    if (pUnkOuter != NULL)
    {
        return(CLASS_E_NOAGGREGATION);
    }

    CPlasma * pObj = new CPlasma;
    if (NULL == pObj)
    {
        return(E_OUTOFMEMORY);
    }

    if (!pObj->Initialize())
    {
        delete ppv;
        return(CLASS_E_CLASSNOTAVAILABLE);
    }

    hr = pObj->QueryInterface(riid, ppv);
    if (FAILED(hr))
    {
        delete ppv;
    }
    else
    {
        gcRef++;
    }

    return(hr);
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasmaCF::LockServer
//
//  Synopsis:   locks the server, preventing it from being unloaded
//
//  Arguments:  [fLock] - TRUE to lock, FALSE to unlock
//
//  Returns:    S_OK
//
//  Modifies:   gcLock
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CPlasmaCF::LockServer(BOOL fLock)
{
    if (fLock)
    {
        gcLock++;
    }
    else
    {
        gcLock--;
    }
    return(S_OK);
}


PLASMA.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       plasma.h
//
//  Contents:   class definitions for the Plasma Fractal engine
//
//  Classes:    CPlasmaCF
//              CPlasma
//
//  Functions:
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

#ifndef __PLASMA_H__
#define __PLASMA_H__

#define PROPERTIES 100

#define IDM_RANDOMNESS      1000
#define IDM_ABOUT           1006
#define IDS_ABOUT_TITLE     1100
#define IDS_ABOUT_TEXT      1101

#ifdef __cplusplus

#include <frcngn.h>
#include <frhost.h>
#include <cdialog.h>

//+---------------------------------------------------------------------------
//
//  Class:      CPlasmaCF
//
//  Purpose:    class factory for the Plasma engine
//
//  Interface:  CPlasmaCF      -- constructor
//              ~CPlasmaCF     -- destructor
//              QueryInterface -- requests an interface
//              AddRef         -- increments refcount
//              Release        -- decrements refcount
//              CreateInstance -- creates a Mandelbrot engine
//              LockServer     -- keeps cf around if released
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

class CPlasmaCF : public IClassFactory
{
public:
    CPlasmaCF();
    ~CPlasmaCF();

    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID *);
    ULONG STDMETHODCALLTYPE AddRef(void);
    ULONG STDMETHODCALLTYPE Release(void);

    HRESULT STDMETHODCALLTYPE CreateInstance(LPUNKNOWN, REFIID, LPVOID*);
    HRESULT STDMETHODCALLTYPE LockServer(BOOL);

protected:
    ULONG _cRef;
};

//+---------------------------------------------------------------------------
//
//  Class:      CPlasma
//
//  Purpose:    implements the plasma engine
//
//  Interface:  QueryInterface     -- retrieves an interface
//              AddRef             -- increments refcount
//              Release            -- decrements refcount
//              GetClassID         -- gets the CLSID
//              IsDirty            -- tests if data should be saved
//              Load               -- loads properties
//              Save               -- saves properties
//              GetSizeMax         -- returns max size of property stream
//              Init               -- initializes engine
//              SetDefaults        -- sets default property values
//              SetProperties      -- displays the property dialog box
//              GetExtent          -- gets size of the graph (in graph units)
//              SetExtent          -- sets size of the graph (in graph units)
//              SetGraphSize       -- sets size of the graph (in pixels)
//              Start              -- starts the graph engine
//              Stop               -- stops the graph engine
//              DialogProc         -- dialog proc for the property dialog box
//              Initialize         -- private initialization function
//              GraphicsThread     -- graphics thread entry point
//              Subdivide          -- graph routine for each quadrant
//              CPlasma            -- constructor
//              ~CPlasma           -- destructor
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

class CPlasma : public IFractalEngine, IPersistStream, CHlprDialog
{
public:
    // IUnknown methods
    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID *);
    ULONG STDMETHODCALLTYPE AddRef(void);
    ULONG STDMETHODCALLTYPE Release(void);

    // IPersist methods
    HRESULT STDMETHODCALLTYPE GetClassID(LPCLSID pclsid);

    // IPersistStream methods
    HRESULT STDMETHODCALLTYPE IsDirty(void);
    HRESULT STDMETHODCALLTYPE Load(LPSTREAM pStm);
    HRESULT STDMETHODCALLTYPE Save(LPSTREAM pStm, BOOL fClearDirty);
    HRESULT STDMETHODCALLTYPE GetSizeMax(ULARGE_INTEGER * pcbSize);

    // IFractalEngine methods
    HRESULT STDMETHODCALLTYPE Init(IFractalHost *pfh);

    HRESULT STDMETHODCALLTYPE SetDefaults(void);

    HRESULT STDMETHODCALLTYPE SetProperties(HWND hwnd);

    HRESULT STDMETHODCALLTYPE GetExtent(
        double *pdLeft,
        double *pdTop,
        double *pdRight,
        double *pdBottom);

    HRESULT STDMETHODCALLTYPE SetExtent(
        double dLeft,
        double dTop,
        double dRight,
        double dBottom);

    HRESULT STDMETHODCALLTYPE SetGraphSize(
        unsigned int uWidth,
        unsigned int uHeight);

    HRESULT STDMETHODCALLTYPE Start(void);

    HRESULT STDMETHODCALLTYPE Stop(void);

    // Methods from CHlprDialog
    BOOL DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);

    // Methods that aren't part of any interface
    BOOL Initialize(void);
    DWORD CPlasma::GraphicsThread(void);
    void Subdivide(int xl, int yl, int xh, int yh);

    CPlasma();
    ~CPlasma();
private:
    ULONG _cRef;
    HANDLE _hRestart, _hRunning, _hEngine;
    DWORD _dwThreadId;
    double _dRandomnessFactor;
    unsigned _uWidth, _uHeight;
    IFractalHost * _pfh;
    BOOL _fDirty;
};

extern HINSTANCE ghinst;
extern ULONG gcRef, gcLock;

#endif //__cplusplus
#endif //__PLASMA_H__


PLASMAOB.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       plasmaob.cxx
//
//  Contents:   Plasma Fractal engine object
//
//  Classes:    CPlasma
//
//  Functions:  StartGraphicsThread
//              Normalize
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include <windows.h>
#include <ole2.h>
#include <plasmcid.h>
#include "plasma.h"
#include <stdio.h>
#include <strmhelp.h>
#include <palsize.h>
#include <math.h>

#define MAX_RANDOM 50.0
#define MIN_RANDOM 0

//+---------------------------------------------------------------------------
//
//  Function:   StartGraphicsThread
//
//  Synopsis:   Global function used to bootstrap into the GraphicsThread
//              member of the CPlasma class.
//
//  Arguments:  [lpdwParam] - not used
//
//  History:    4-15-94   stevebl   Created
//
//----------------------------------------------------------------------------

DWORD StartGraphicsThread(LPDWORD lpdwParam)
{
    return (((CPlasma *)lpdwParam)->GraphicsThread());
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasma::QueryInterface
//
//  Synopsis:   standard OLE method
//
//  Arguments:  [riid] - id of desired interface
//              [ppv]  - pointer to receive interface
//
//  Returns:    S_OK on success
//              E_NOINTERFACE if the requested interface isn't supported
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CPlasma::QueryInterface(REFIID riid, LPVOID * ppv)
{
    *ppv = NULL;
    if (IsEqualGUID(IID_IFractalEngine, riid))
    {
        *ppv = (IFractalEngine *)this;
    }
    else if (IsEqualGUID(IID_IPersist, riid))
    {
        *ppv = (IPersist *)this;
    }
    else if (IsEqualGUID(IID_IPersistStream, riid))
    {
        *ppv = (IPersistStream *)this;
    }
    else if (IsEqualGUID(IID_IUnknown, riid))
    {
        *ppv = (IUnknown *)(IFractalEngine *)this;
    }
    if (*ppv)
    {
        ((IUnknown *)*ppv)->AddRef();
        return(S_OK);
    }
    return(E_NOINTERFACE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasma::AddRef
//
//  Synopsis:   increments the reference count
//
//  Returns:    new reference count
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CPlasma::AddRef(void)
{
    return(++_cRef);
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasma::Release
//
//  Synopsis:   decrements the reference count
//
//  Returns:    new reference count
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CPlasma::Release(void)
{
    ULONG cRef = --_cRef;
    if (0 == cRef)
    {
        delete this;
        gcRef--;
    }
    return(cRef);
}

// IPersist methods

//+---------------------------------------------------------------------------
//
//  Member:     CPlasma::GetClassID
//
//  Synopsis:   returns the object's class ID
//
//  Arguments:  [pclsid] - pointer to recieve the class ID
//
//  Returns:    S_OK
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT CPlasma::GetClassID(LPCLSID pclsid)
{
    memcpy(pclsid, &CLSID_PLASMA, sizeof(GUID));
    return(S_OK);
}

// IPersistStream methods

//+---------------------------------------------------------------------------
//
//  Member:     CPlasma::IsDirty
//
//  Synopsis:   used to determine if the state of the object has changed
//
//  Returns:    S_OK if it is dirty
//              S_FALSE if it is not dirty
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT CPlasma::IsDirty(void)
{
    if (_fDirty)
    {
        return(S_OK);
    }
    else
    {
        return(S_FALSE);
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasma::Load
//
//  Synopsis:   loads the fractal properties from a stream
//
//  Arguments:  [pStm] - pointer to an IStream interface
//
//  Returns:    S_OK on success
//              E_FAIL on error
//              E_OUTOFMEMORY if there's not enough memory (can't happen here)
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT CPlasma::Load(LPSTREAM pStm)
{
    if (WAIT_OBJECT_0 == WaitForSingleObject(_hRunning, 0))
    {
        return(E_FAIL);
    }
    HRESULT hr;
    if (FAILED(hr = ReadDouble(pStm, &_dRandomnessFactor, NULL)))
    {
        return(hr);
    }
    _fDirty = FALSE;
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasma::Save
//
//  Synopsis:   saves the fractal properties to a stream
//
//  Arguments:  [pStm]        - pointer to an IStream interface
//              [fClearDirty] - TRUE if the dirty flag should be cleared
//
//  Returns:    S_OK on success
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT CPlasma::Save(LPSTREAM pStm, BOOL fClearDirty)
{
    if (WAIT_OBJECT_0 == WaitForSingleObject(_hRunning, 0))
    {
        return(E_FAIL);
    }
    HRESULT hr;
    if (FAILED(hr = WriteDouble(pStm, _dRandomnessFactor, NULL)))
    {
        return(hr);
    }
    if (fClearDirty)
    {
        _fDirty = FALSE;
    }
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasma::GetSizeMax
//
//  Synopsis:   used to determine the maximum size of the object's data
//
//  Arguments:  [pcbSize] - pointer to recieve the max size
//
//  Returns:    S_OK
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT CPlasma::GetSizeMax(ULARGE_INTEGER * pcbSize)
{
    pcbSize->HighPart = 0;
    pcbSize->LowPart = SizeDouble();
    return(S_OK);
}

// IFractalEngine methods

//+---------------------------------------------------------------------------
//
//  Member:     CPlasma::Init
//
//  Synopsis:   Initializes the graph engine and saves a pointer to the
//              IFractalHost interface that WinFract passes in as
//              the engine is initialized.
//
//  Arguments:  [pfh] - pointer to an IFractalHost interface
//
//  Returns:    S_OK
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CPlasma::Init(IFractalHost *pfh)
{
    _pfh = pfh;
    // NOTE that I don't addref this pointer!  That would create a circular
    // reference count paradox.
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasma::SetDefaults
//
//  Synopsis:   initializes the graph engine with its default values
//
//  Returns:    S_OK
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CPlasma::SetDefaults(void)
{
    _dRandomnessFactor = 2.0;
    _fDirty = TRUE;
    SetEvent(_hRestart);
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasma::SetProperties
//
//  Synopsis:   Displays the graph engine's property dialog box.
//
//  Arguments:  [hwnd] - handle to the parent window
//
//  Returns:    S_OK
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CPlasma::SetProperties(HWND hwnd)
{
    ShowDialog(ghinst, MAKEINTRESOURCE(PROPERTIES), hwnd);
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasma::GetExtent
//
//  Synopsis:   retrieves the extent of the graph
//
//  Arguments:  [pdLeft]   - x value at the left of the graph
//              [pdTop]    - y value at the top of the graph
//              [pdRight]  - x value at the right of the graph
//              [pdBottom] - y value at the bottom of the graph
//
//  Returns:    S_OK
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CPlasma::GetExtent(
    double *pdLeft,
    double *pdTop,
    double *pdRight,
    double *pdBottom)
{
    *pdLeft = -1.0;
    *pdTop = 1.0;
    *pdRight = 1.0;
    *pdBottom = -1.0;
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasma::SetExtent
//
//  Synopsis:   sets the graph's extent
//
//  Arguments:  [dLeft]   - x value at the left of the graph
//              [dTop]    - y value at the top of the graph
//              [dRight]  - x value at the right of the graph
//              [dBottom] - y value at the bottom of the graph
//
//  Returns:    S_OK - extents set
//              E_FAIL - the graph was running so the extents couldn't be set
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CPlasma::SetExtent(
    double dLeft,
    double dTop,
    double dRight,
    double dBottom)
{
    if (WAIT_OBJECT_0 == WaitForSingleObject(_hRunning, 0))
    {
        return(E_FAIL);
    }
    _fDirty = TRUE;
    SetEvent(_hRestart);
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasma::SetGraphSize
//
//  Synopsis:   Tells the graph engine how big the graph is
//
//  Arguments:  [uWidth]  - width of graph (in pixels)
//              [uHeight] - height of graph (in pixels)
//
//  Returns:    S_OK - success
//              E_FAIL - the graph was running or a value was illegal
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CPlasma::SetGraphSize(
    unsigned int uWidth,
    unsigned int uHeight)
{
    if (WAIT_OBJECT_0 == WaitForSingleObject(_hRunning, 0))
    {
        return(E_FAIL);
    }
    if (uWidth == 0 || uHeight == 0)
    {
        return(E_FAIL);
    }
    _uWidth = uWidth;
    _uHeight = uHeight;
    SetEvent(_hRestart);
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasma::Start
//
//  Synopsis:   starts the graph engine
//
//  Returns:    S_OK on success
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CPlasma::Start(void)
{
    SetEvent(_hRunning);
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasma::Stop
//
//  Synopsis:   stops the graph engine
//
//  Returns:    S_OK on success
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CPlasma::Stop(void)
{
    ResetEvent(_hRunning);
    return(S_OK);
}

// Methods that aren't part of any interface

//+---------------------------------------------------------------------------
//
//  Member:     CPlasma::Initialize
//
//  Synopsis:   called by the clas factory to initialize the object
//
//  Returns:    TRUE on success
//              FALSE if the object couldn't be initialized
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

BOOL CPlasma::Initialize(void)
{
    _hRestart = CreateEvent(
        NULL,
        TRUE,   // must be manually reset
        TRUE,   // initially signaled
        NULL);
    if (_hRestart == NULL)
    {
        return(FALSE);  // couldn't create an event
    }
    _hRunning = CreateEvent(
        NULL,
        TRUE,
        FALSE,
        NULL);
    if (_hRunning == NULL)
    {
        return(FALSE);
    }
    _hEngine = CreateThread(
        (LPSECURITY_ATTRIBUTES)NULL,
        0,
        (LPTHREAD_START_ROUTINE) StartGraphicsThread,
        this,
        0,
        &_dwThreadId);
    if (_hEngine == NULL)
    {
        return(FALSE);  // couldn't start the graphics thread
    }
    return(TRUE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasma::GraphicsThread
//
//  Synopsis:   main entry point for the engine's graphics thread
//
//  History:    4-23-94   stevebl   Created
//
//  Notes:      This routine enters into an eternal loop.  It will continue
//              looping until the class is destroyed at which point this
//              thread will be simply terminated.
//
//  Algorithm:  Wait for the engine to be started.
//              Clear the graph.
//              Plot the points at the graph's four corners.
//              Divide it into four quadrants and call Subdivide for each.
//              After falling out of the recursion, check to see if the
//              graph has been aborted.
//              If not, notify that the graph has been completed.
//              Loop back around to the beginning.
//
//----------------------------------------------------------------------------
DWORD CPlasma::GraphicsThread(void)
{
    do
    {
        // wait here until someone starts the graphics engine
        WaitForSingleObject(_hRunning, INFINITE);

        // reset the _hRestart event so that we won't immediately fall out
        // of the engine
        ResetEvent(_hRestart);

        // Now begin drawing the graph

        // erase the background
        _pfh->Rect(0, 0, (int) _uWidth - 1, (int) _uHeight - 1, 0);

        // Seed the random number generator
        srand((UINT)GetTickCount());

        // plot the upper corner
        _pfh->Point(0, 0, rand() % (PALETTESIZE -1 ));

        // begin the recursion
        Subdivide(0, 0, (int) _uWidth, (int) _uHeight);

        // We're either done drawing the graph now or we have been restarted
        if (WAIT_TIMEOUT == WaitForSingleObject(_hRestart, 0))
        {
            // Tell our clients that we're finished
            _pfh->DoneDrawingGraph();
            ResetEvent(_hRunning);
        }
    } while (TRUE);
    return(0);
}

//+---------------------------------------------------------------------------
//
//  Function:   Normalize
//
//  Synopsis:   Makes sure that the value is within the legal color set.
//
//  Arguments:  [iTemp] - color index value
//
//  Returns:    0 <= iTemp < (PALETTESIZE - 1)
//
//  History:    4-28-94   stevebl   Created
//
//  Notes:      There are a couple ways this could be achieved, most of them
//              are undesirable.
//
//              For instance, we could just perform a modulus operation on
//              the value:
//
//                  iTemp = iTemp % (PALETTESIZE - 1)
//
//              This would have the effect of causing numbers to wrap
//              around (PALETTESIZE - 1 would become 0).  This would be bad because
//              in order for the graph to look good we want all the points to
//              transition as smoothley as possible.  Doing it this way
//              would cause the graph to have an area where the elevation
//              suddenly changed dramaticaly when it should have been very
//              smooth.
//
//              Another option would be to just truncate the value:
//
//              if (iTemp) > PALETTESIZE - 2) iTemp = PALETTESIZE - 2
//
//              This would cause our graph to have areas where it is
//              artificially flat.
//
//              Instead, this routine reflects the value back down into a
//              range of values.  For every unit the value is over
//              PALETTESIZE - 2, it is made to be that many units under
//              PALETTESIZE - 1.  This avoids both sudden elevation changes
//              and flat spots.
//
//----------------------------------------------------------------------------

inline void Normalize(int & iTemp)
{
    while (iTemp < 0 || iTemp >= PALETTESIZE - 1)
    {
        if (iTemp < 0)
        {
            iTemp = 0 - iTemp;
        }
        else
        {
            iTemp = ((PALETTESIZE - 1) * 2) - (iTemp + 1);
        }
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasma::Subdivide
//
//  Synopsis:   recurses into  a rectangle
//
//  Arguments:  [xl] - left side
//              [yl] - top side
//              [xh] - right side
//              [yh] - bottom side
//
//  Returns:    nothing
//
//  History:    4-25-94   stevebl   Created
//
//  Notes:      Each ittertation is responsible for drawing the
//              points marked X below if the haven't already been drawn.
//              The O's are points drawn by previous ittertations.
//              The .'s represent points to be drawn by future itterations.
//
//              O . . . . . X . . . . . O
//              . . . . . . . . . . . . .
//              . . . . . . . . . . . . .
//              . . . . . . . . . . . . .
//              . . . . . . . . . . . . .
//              . . . . . . . . . . . . .
//              X . . . . . X . . . . . X
//              . . . . . . . . . . . . .
//              . . . . . . . . . . . . .
//              . . . . . . . . . . . . .
//              . . . . . . . . . . . . .
//              . . . . . . . . . . . . .
//              O . . . . . X . . . . . O
//
//              Once the routine has plotted its points, it divides
//              the graph into quarters and calls itself
//              for each quarter.
//
//              Since the graph is erased to color 0 before itterations are
//              begun, testing if a point has already been plotted is simple:
//              if the point contians color 0 then it hasn't been plotted yet.
//
//----------------------------------------------------------------------------

void CPlasma::Subdivide(int xl, int yl, int xh, int yh)
{
    if (WAIT_TIMEOUT == WaitForSingleObject(_hRestart, 0))
    {
        WaitForSingleObject(_hRunning, INFINITE);
        // find the midpoints
        int iMidX = (xh + xl) / 2;
        int iMidY = (yh + yl) / 2;

        // find the four corner colors
        unsigned char cTL, cTR, cBL, cBR;
        unsigned uTemp;
        _pfh->GetPoint(&uTemp, xl, yl);
        cTL = uTemp - 1;
        _pfh->GetPoint(&uTemp, xh % _uWidth, yl);
        cTR = uTemp - 1;
        _pfh->GetPoint(&uTemp, xl, yh % _uHeight);
        cBL = uTemp - 1;
        _pfh->GetPoint(&uTemp, xh % _uWidth, yh % _uHeight);
        cBR = uTemp - 1;
        int cTM, cLM, cRM, cBM;

        unsigned uSpread = (unsigned)((xh - xl) * _dRandomnessFactor);

        // choose the top point
        _pfh->GetPoint(&uTemp, iMidX, yl);
        if (0 == uTemp)
        {
            cTM = (rand() % (uSpread * 2 + 1)) - uSpread;
            cTM += (cTL + cTR) / 2;
            Normalize(cTM);
            _pfh->Point(iMidX, yl, cTM + 1);
        }

        // choose the bottom point
        _pfh->GetPoint(&uTemp, iMidX, yh % _uHeight);
        if (0 == uTemp)
        {
            cBM = (rand() % (uSpread * 2 + 1)) - uSpread;
            cBM += (cBL + cBR) / 2;
            Normalize(cBM);
            // Don't need to use yh % _uHeight here because the only
            // way we can get here is if yh < _uHeight.
            _pfh->Point(iMidX, yh, cBM + 1);
        }

        uSpread = (unsigned)((yh - yl) * _dRandomnessFactor);

        // choose the left point
        _pfh->GetPoint(&uTemp,xl, iMidY);
        if (0 == uTemp)
        {
            cLM = (rand() % (uSpread * 2 + 1)) - uSpread;
            cLM += (cTL + cBL) / 2;
            Normalize(cLM);
            _pfh->Point(xl, iMidY, cLM + 1);
        }

        // choose the right point
        _pfh->GetPoint(&uTemp, xh % _uWidth, iMidY);
        if (0 == uTemp)
        {
            cRM = (rand() % (uSpread * 2 + 1)) - uSpread;
            cRM += (cTR + cBR) / 2;
            Normalize(cRM);
            // Don't need to use xh % _uWidth here because the only
            // way we can get here is if xh < _uWidth.
            _pfh->Point(xh, iMidY, cRM + 1);
        }

        _pfh->GetPoint(&uTemp, iMidX, iMidY);
        if (0 == uTemp)
        {
            // choose the center point
            uSpread = (unsigned)((sqrt(((float)(xh - xl) * (xh - xl) + (yh - yl) * (yh - yl))) / 2) * _dRandomnessFactor);
            int cC = (rand() % (uSpread * 2 + 1)) - uSpread;
            cC += (cTL + cBR + cTR + cBL) / 4;
            Normalize(cC);
            _pfh->Point(iMidX, iMidY, cC + 1);
        }

        // now recurse
        if (xh - xl > 2 || yh - yl > 2)
        {
            Subdivide(xl, yl, iMidX, iMidY);
            Subdivide(iMidX, yl, xh, iMidY);
            Subdivide(xl, iMidY, iMidX, yh);
            Subdivide(iMidX, iMidY, xh, yh);
        }
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasma::CPlasma
//
//  Synopsis:   constructor
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

CPlasma::CPlasma()
{
    _uWidth = 160;
    _uHeight = 120;
    _dRandomnessFactor = 2.0;
    _hRestart = _hRunning = _hEngine = NULL;
    _dwThreadId = 0;
    _cRef = 0;
    _pfh = NULL;
    _fDirty = TRUE;
}

//+---------------------------------------------------------------------------
//
//  Member:     CPlasma::~CPlasma
//
//  Synopsis:   destructor
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

CPlasma::~CPlasma()
{
    if (_hRestart)
    {
        CloseHandle(_hRestart);
    }
    if (_hRunning)
    {
        CloseHandle(_hRunning);
    }
    if (_hEngine)
    {
        TerminateThread(_hEngine, 0);
        CloseHandle(_hEngine);
    }
}

#define LENGTH 50
#define ABOUT_STRING_LENGTH 256

//+---------------------------------------------------------------------------
//
//  Member:     CPlasma::DialogProc
//
//  Synopsis:   Dialog proc for the Properties dialog box
//
//  Arguments:  [hwndDlg] - the dialog's window handle
//              [uMsg]    - message
//              [wParam]  - first message parameter
//              [lParam]  - second message parameter
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

BOOL CPlasma::DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    char mbs[LENGTH + 1];

    switch (uMsg)
    {
    case WM_INITDIALOG:
        sprintf(mbs, "%.13g", _dRandomnessFactor);
        SetDlgItemTextA(hwndDlg, IDM_RANDOMNESS, mbs);
        return(TRUE);
    case WM_COMMAND:
        switch (LOWORD(wParam))
        {
        case IDOK:
            GetDlgItemTextA(hwndDlg, IDM_RANDOMNESS, mbs, LENGTH);
            sscanf(mbs, "%lg", &_dRandomnessFactor);
            if (_dRandomnessFactor < MIN_RANDOM)
            {
                _dRandomnessFactor = MIN_RANDOM;
            }
            if (_dRandomnessFactor > MAX_RANDOM)
            {
                _dRandomnessFactor = MAX_RANDOM;
            }
            _fDirty = TRUE;
            SetEvent(_hRestart);
            EndDialog(hwndDlg, TRUE);
            return(TRUE);
        case IDM_ABOUT:
            {
                TCHAR szTitle[ABOUT_STRING_LENGTH];
                TCHAR szText[ABOUT_STRING_LENGTH];
                if (LoadString(ghinst, IDS_ABOUT_TITLE, szTitle, ABOUT_STRING_LENGTH))
                {
                    if (LoadString(ghinst, IDS_ABOUT_TEXT, szText, ABOUT_STRING_LENGTH))
                    {
                        MessageBox(
                            hwndDlg,
                            szText,
                            szTitle,
                            MB_OK | MB_ICONINFORMATION);
                    }
                }
            }
            return(TRUE);
        case IDCANCEL:
            EndDialog(hwndDlg, FALSE);
            return(TRUE);
        default:
            return(FALSE);
        }
    }
    return(FALSE);
}


QUAD.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       mandel.cxx
//
//  Contents:   implementation for Mandelbrot Fractal engine
//
//  Classes:    CQuadrantEngineCF
//
//  Functions:  DllEntryPoint
//              DllGetClassObject
//              DllCanUnloadNow
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include <windows.h>
#include <ole2.h>
#include <quadcid.h>
#include "quad.h"

ULONG gcRef, gcLock;
HINSTANCE ghinst;

extern "C"
{
    BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID);
}

//+---------------------------------------------------------------------------
//
//  Function:   DllEntryPoint
//
//  Synopsis:   the DLL's entry point
//
//  Arguments:  [hinst]      - instance handle for the dll
//              [fdwReason]  - reason for calling the entry point
//              [lpReserved] - reserved
//
//  Returns:    TRUE if DLL_PROCESS_ATTACH initialization succeeds
//              FALSE otherwise
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

BOOL WINAPI DllMain(
    HINSTANCE hinst,
    DWORD fdwReason,
    LPVOID lpReserved)
{
    switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        ghinst = hinst;
        gcRef = gcLock = 0;
        return(TRUE); // initialization succeeded
    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        break;
    }
    return(FALSE);  // initialization failed (ignored if not DLL_PROCESS_ATTACH)
}

//+---------------------------------------------------------------------------
//
//  Function:   DllGetClassObject
//
//  Synopsis:   function called by OLE to get a class object
//
//  Arguments:  [rclsid] - the class id of the desired class object
//              [riid]   - the id of the desired interface
//              [ppv]    - pointer to recieve the interface
//
//  Returns:    S_OK if the class and interface are implemented
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT PASCAL DllGetClassObject(
    REFCLSID rclsid,
    REFIID riid,
    LPVOID * ppv)
{
    if (!IsEqualGUID(rclsid, CLSID_QUADRANTENGINE))
    {
        return(E_FAIL);
    }
    if ((!IsEqualGUID(riid, IID_IUnknown)) &&
        (!IsEqualGUID(riid, IID_IClassFactory)))
    {
        return(E_NOINTERFACE);
    }
    *ppv = (LPVOID) new CQuadrantEngineCF();
    if (NULL == *ppv)
    {
        return(E_OUTOFMEMORY);
    }
    ((IUnknown*)*ppv)->AddRef();
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Function:   DllCanUnloadNow
//
//  Synopsis:   determines if there are no outstanding references on this DLL
//
//  Returns:    S_OK if the DLL can be unloaded
//              S_FALSE if not
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

STDAPI DllCanUnloadNow(void)
{
    // return S_OK if there are no references on myself
    if (0 == gcRef && 0 == gcLock)
    {
        return(S_OK);
    }
    return(S_FALSE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CQuadrantEngineCF::CQuadrantEngineCF
//
//  Synopsis:   constructor
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

CQuadrantEngineCF::CQuadrantEngineCF()
{
    _cRef = 0;
}

//+---------------------------------------------------------------------------
//
//  Member:     CQuadrantEngineCF::~CQuadrantEngineCF
//
//  Synopsis:   destructor
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

CQuadrantEngineCF::~CQuadrantEngineCF()
{
}

//+---------------------------------------------------------------------------
//
//  Member:     CQuadrantEngineCF::QueryInterface
//
//  Synopsis:   Standard OLE interface
//
//  Arguments:  [riid] - id of the desired interface
//              [ppv]  - pointer to receive the interface pointer
//
//  Returns:    S_OK on success
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CQuadrantEngineCF::QueryInterface(REFIID riid, LPVOID * ppv)
{
    *ppv = NULL;
    if ((!IsEqualGUID(riid, IID_IUnknown)) &&
        (!IsEqualGUID(riid, IID_IClassFactory)))
    {
        return(E_NOINTERFACE);
    }
    *ppv = (IClassFactory *) this;
    AddRef();
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CQuadrantEngineCF::AddRef
//
//  Synopsis:   increment reference count
//
//  Returns:    new reference count
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CQuadrantEngineCF::AddRef(void)
{
    return(++_cRef);
}

//+---------------------------------------------------------------------------
//
//  Member:     CQuadrantEngineCF::Release
//
//  Synopsis:   decrement reference count
//
//  Returns:    new reference count
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CQuadrantEngineCF::Release(void)
{
    ULONG cRef = --_cRef;
    if (0 == cRef)
    {
        delete(this);
    }
    return(cRef);
}

//+---------------------------------------------------------------------------
//
//  Member:     CQuadrantEngineCF::CreateInstance
//
//  Synopsis:   creates an instance of a Mandelbrot Engine
//
//  Arguments:  [pUnkOuter] - pointer to the controlling unknown (must be NULL)
//              [riid]      - id of the desired interface
//              [ppv]       - pointer to receive the interface
//
//  Returns:    S_OK - success
//              CLASS_E_NOAGGREATION - the caller tried to aggregate
//              CLASS_E_CLASSNOTAVAILABLE - couldn't initialize the class
//              E_OUTOFMEMORY - not enough memory to instantiate class
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CQuadrantEngineCF::CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, LPVOID * ppv)
{
    HRESULT hr = S_OK;
    *ppv = NULL;
    if (pUnkOuter != NULL)
    {
        return(CLASS_E_NOAGGREGATION);
    }

    CQuadrantEngine * pObj = new CQuadrantEngine;
    if (NULL == pObj)
    {
        return(E_OUTOFMEMORY);
    }

    if (!pObj->Initialize())
    {
        delete ppv;
        return(CLASS_E_CLASSNOTAVAILABLE);
    }

    hr = pObj->QueryInterface(riid, ppv);
    if (FAILED(hr))
    {
        delete ppv;
    }
    else
    {
        gcRef++;
    }

    return(hr);
}

//+---------------------------------------------------------------------------
//
//  Member:     CQuadrantEngineCF::LockServer
//
//  Synopsis:   locks the server, preventing it from being unloaded
//
//  Arguments:  [fLock] - TRUE to lock, FALSE to unlock
//
//  Returns:    S_OK
//
//  Modifies:   gcLock
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CQuadrantEngineCF::LockServer(BOOL fLock)
{
    if (fLock)
    {
        gcLock++;
    }
    else
    {
        gcLock--;
    }
    return(S_OK);
}


QUAD.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       quad.h
//
//  Contents:   class definitions for the Quadrant Engine
//
//  Classes:    CQuadrantEngineCF
//              CQuadrantEngine
//
//  Functions:
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

#ifndef __QUAD_H__
#define __QUAD_H__
#ifdef __cplusplus

#include <frcngn.h>
#include <frhost.h>
#include <qudcln.h>
#include <qudngn.h>

//+---------------------------------------------------------------------------
//
//  Class:      CQuadrantEngineCF
//
//  Purpose:    the class factory for the Quadrant Engine
//
//  Interface:  CQuadrantEngineCF  -- constructor
//              ~CQuadrantEngineCF -- destructor
//              QueryInterface     -- retrieves an interface on the cf
//              AddRef             -- incrementes referencec count
//              Release            -- decrements reference count
//              CreateInstance     -- creates a Quadrant Engine object
//              LockServer         -- keeps cf from being deleted on release
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

class CQuadrantEngineCF : public IClassFactory
{
public:
    CQuadrantEngineCF();
    ~CQuadrantEngineCF();

    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID *);
    ULONG STDMETHODCALLTYPE AddRef(void);
    ULONG STDMETHODCALLTYPE Release(void);

    HRESULT STDMETHODCALLTYPE CreateInstance(LPUNKNOWN, REFIID, LPVOID*);
    HRESULT STDMETHODCALLTYPE LockServer(BOOL);

protected:
    ULONG _cRef;
};

//+---------------------------------------------------------------------------
//
//  Class:      CQuadrantEngine
//
//  Purpose:    implementation for the quadrant engine object
//
//  Interface:  QueryInterface     -- retrieves an interface from the engine
//              AddRef             -- increments refcount
//              Release            -- decrements refcount
//              Init               -- initializes quadrant engine
//              UseBoundingBoxes   -- tells engine to use bounding boxes
//              SetGraphSize       -- tells engine the size of its graph
//              Start              -- starts engine
//              Stop               -- stops engine
//              Reset              -- resets engine
//              GraphicsThread     -- entry point for the graphics thread
//              PlotAreaSlowly     -- plots a rectangle
//              Subdivide          -- recursively plots a quadrant
//              Initialize         -- private method to initialize c++ object
//              CQuadrantEngine    -- constructor
//              ~CQuadrantEngine   -- destructor
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

class CQuadrantEngine : public IQuadrantEngine
{
public:
    // IUnknown methods
    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID *);
    ULONG STDMETHODCALLTYPE AddRef(void);
    ULONG STDMETHODCALLTYPE Release(void);

    // IQuadrantEngine methods
    HRESULT STDMETHODCALLTYPE Init(
        IQuadrantClient *pqc,
        IFractalHost *pfh);

    HRESULT STDMETHODCALLTYPE UseBoundingBoxes(BOOL fFlag);

    HRESULT STDMETHODCALLTYPE SetGraphSize(
        unsigned int uWidth,
        unsigned int uHeight);

    HRESULT STDMETHODCALLTYPE Start(void);

    HRESULT STDMETHODCALLTYPE Stop(void);

    HRESULT STDMETHODCALLTYPE Reset(void);

    // Methods that aren't part of any interface
    DWORD GraphicsThread(void);
    void PlotAreaSlowly(
        int iXLow,
        int iYLow,
        int iXHigh,
        int iYHigh);
    void Subdivide(
        int iXLow,
        int iYLow,
        int iXHigh,
        int iYHigh);
    BOOL Initialize(void);
    CQuadrantEngine();
    ~CQuadrantEngine();
private:
    BOOL _fUseBoundingBoxes;
    ULONG _cRef;
    unsigned _uWidth, _uHeight;
    HANDLE _hRestart, _hRunning;
    HANDLE _hEngine;
    DWORD _dwThreadId;
    IFractalHost * _pfh;
    IQuadrantClient * _pqc;
};

extern HINSTANCE ghinst;
extern ULONG gcRef, gcLock;

#endif //__cplusplus
#endif //__QUAD_H__


QUADOBJ.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       mandelob.cxx
//
//  Contents:   Quadrant Engine object
//
//  Classes:    CQuadrantEngine
//
//  Functions:  StartGraphicsThread
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include <windows.h>
#include <ole2.h>
#include "quad.h"

//+---------------------------------------------------------------------------
//
//  Function:   StartGraphicsThread
//
//  Synopsis:   Global function used to bootstrap into the GraphicsThread
//              member of the CQuadrantEngine class.
//
//  Arguments:  [lpdwParam] - this pointer to a CQuadrantEngine class object
//
//  History:    4-15-94   stevebl   Created
//
//----------------------------------------------------------------------------

DWORD StartGraphicsThread(LPDWORD lpdwParam)
{
    return (((CQuadrantEngine *)lpdwParam)->GraphicsThread());
}

//+---------------------------------------------------------------------------
//
//  Member:     CQuadrantEngine::QueryInterface
//
//  Synopsis:   standard OLE method
//
//  Arguments:  [riid] - reference to the requested interface id
//              [ppv]  - pointer to recieve the interface
//
//  Returns:    S_OK on success
//              E_NOINTERFACE if the requested interface isn't supported
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CQuadrantEngine::QueryInterface(REFIID riid, LPVOID * ppv)
{
    *ppv = NULL;
    if (IsEqualGUID(IID_IQuadrantEngine, riid))
    {
        *ppv = (IQuadrantEngine *)this;
    }
    else if (IsEqualGUID(IID_IUnknown, riid))
    {
        *ppv = (IUnknown *)(IFractalEngine *)this;
    }
    if (*ppv)
    {
        ((IUnknown *)*ppv)->AddRef();
        return(S_OK);
    }
    return(E_NOINTERFACE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CQuadrantEngine::AddRef
//
//  Synopsis:   increments the reference count
//
//  Returns:    new reference count
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CQuadrantEngine::AddRef(void)
{
    return(++_cRef);
}

//+---------------------------------------------------------------------------
//
//  Member:     CQuadrantEngine::Release
//
//  Synopsis:   decrements the reference count
//
//  Returns:    new reference count
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CQuadrantEngine::Release(void)
{
    ULONG cRef = --_cRef;
    if (0 == cRef)
    {
        delete this;
        gcRef--;
    }
    return(cRef);
}

// IQuadrantEngine methods

//+---------------------------------------------------------------------------
//
//  Member:     CQuadrantEngine::Init
//
//  Synopsis:   initializes the quadrant engine
//
//  Arguments:  [pqc] - pointer to an IQuadrantClient interface
//              [pfh] - pointer to an IFractalHost interface
//
//  Returns:    S_OK
//
//  History:    4-27-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CQuadrantEngine::Init(
    IQuadrantClient *pqc,
    IFractalHost *pfh)
{
    _pqc = pqc;
    _pfh = pfh;
    // NOTE that neither of these interface pointers are reference counted since
    // I am depending on their owners to tell me when I can be unloaded.
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CQuadrantEngine::UseBoundingBoxes
//
//  Synopsis:   tells the quadrant engine to use the bounding box optimization
//
//  Arguments:  [fFlag] - TRUE if the optimization is enabled (FALSE otherwise)
//
//  Returns:    S_OK
//
//  History:    4-27-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CQuadrantEngine::UseBoundingBoxes(BOOL fFlag)
{
    _fUseBoundingBoxes = fFlag;
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CQuadrantEngine::SetGraphSize
//
//  Synopsis:   tells the quadrant engine how big the graph is
//
//  Arguments:  [uWidth]  - width of the graph in pixels
//              [uHeight] - height of the graph in pixels
//
//  Returns:    S_OK on success
//              E_FAIL if the graph size can't be altered at this time
//
//  History:    4-27-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CQuadrantEngine::SetGraphSize(
    unsigned int uWidth,
    unsigned int uHeight)
{
    if (WAIT_OBJECT_0 == WaitForSingleObject(_hRunning, 0))
    {
        return(E_FAIL);
    }
    _uWidth = uWidth;
    _uHeight = uHeight;
    SetEvent(_hRestart);
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CQuadrantEngine::Start
//
//  Synopsis:   Starts the graph engine
//
//  Returns:    S_OK on success
//
//  History:    4-27-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CQuadrantEngine::Start(void)
{
    SetEvent(_hRunning);
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CQuadrantEngine::Stop
//
//  Synopsis:   stops the graph engine
//
//  Returns:    S_OK on success
//
//  History:    4-27-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CQuadrantEngine::Stop(void)
{
    ResetEvent(_hRunning);
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CQuadrantEngine::Reset
//
//  Synopsis:   tells the graph engine to quit drawing the current graph
//              (if it is drawing it) and start over again
//
//  Returns:    S_OK
//
//  History:    4-27-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CQuadrantEngine::Reset(void)
{
    SetEvent(_hRestart);
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CQuadrantEngine::Initialize
//
//  Synopsis:   private method used to initialize the c++ class object
//
//  History:    4-27-94   stevebl   Created
//
//----------------------------------------------------------------------------

BOOL CQuadrantEngine::Initialize(void)
{
    _hRestart = CreateEvent(
        NULL,
        TRUE,   // must be manually reset
        TRUE,   // initially signaled
        NULL);
    if (_hRestart == NULL)
    {
        return(FALSE);  // couldn't create an event
    }
    _hRunning = CreateEvent(
        NULL,
        TRUE,
        FALSE,
        NULL);
    if (_hRunning == NULL)
    {
        return(FALSE);
    }
    _hEngine = CreateThread(
        (LPSECURITY_ATTRIBUTES)NULL,
        0,
        (LPTHREAD_START_ROUTINE) StartGraphicsThread,
        this,
        0,
        &_dwThreadId);
    if (_hEngine == NULL)
    {
        return(FALSE);  // couldn't start the graphics thread
    }
    return(TRUE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CQuadrantEngine::GraphicsThread
//
//  Synopsis:   main entry point for the engine's graphics thread
//
//  History:    4-27-94   stevebl   Created
//
//  Notes:      This routine enters into an eternal loop.  It will continue
//              looping until the class is destroyed at which point this
//              thread will be simply terminated.
//
//  Algorithm:  Wait for the engine to be started.
//              Clear the graph.
//              Plot the points at the graph's edge and also plot two lines
//              through the center of the graph, dividing it into fourths.
//              For each quadrant, call Subdivide to begin the recursion.
//              After falling out of the recursion, check to see if the
//              graph has been aborted.
//              If not, notify that the graph has been completed.
//              Loop back around to the beginning.
//
//----------------------------------------------------------------------------

DWORD CQuadrantEngine::GraphicsThread(void)
{
    do
    {
        // wait here until someone starts the graphics engine
        WaitForSingleObject(_hRunning, INFINITE);
        // reset the _hRestart event so that we won't immediately fall out
        // of the engine
        ResetEvent(_hRestart);
        // Erase the graph
        _pfh->Rect(0, 0, (int) _uWidth - 1, (int) _uHeight - 1, PALETTEINDEX(0));
        unsigned uColor;
        for (int x = 0;
            x < (int)_uWidth
            && WAIT_TIMEOUT == WaitForSingleObject(_hRestart, 0)
            ; x++)
        {
            _pqc->ComputePoint(&uColor, x, 0);
            _pfh->Point(x, 0, uColor);
            _pqc->ComputePoint(&uColor, x, ((int) _uHeight - 1) / 2);
            _pfh->Point(x, ((int) _uHeight - 1) / 2, uColor);
            _pqc->ComputePoint(&uColor, x, (int) _uHeight - 1);
            _pfh->Point(x, (int) _uHeight - 1, uColor);
            WaitForSingleObject(_hRunning, INFINITE);
        }
        int y;
        for (y = 1;
            y < ((int) _uHeight - 1) / 2
            && WAIT_TIMEOUT == WaitForSingleObject(_hRestart, 0)
            ; y++)
        {
            _pqc->ComputePoint(&uColor, 0, y);
            _pfh->Point(0, y, uColor);
            _pqc->ComputePoint(&uColor, ((int) _uWidth - 1) / 2, y);
            _pfh->Point(((int) _uWidth - 1) / 2, y, uColor);
            _pqc->ComputePoint(&uColor, (int) _uWidth - 1, y);
            _pfh->Point((int) _uWidth - 1, y, uColor);
            WaitForSingleObject(_hRunning, INFINITE);
        }
        for (y = ((int) _uHeight - 1) / 2 + 1;
            y < (int) _uHeight - 1
            && WAIT_TIMEOUT == WaitForSingleObject(_hRestart, 0)
            ; y++)
        {
            _pqc->ComputePoint(&uColor, 0, y);
            _pfh->Point(0, y, uColor);
            _pqc->ComputePoint(&uColor, ((int) _uWidth - 1) / 2, y);
            _pfh->Point(((int) _uWidth - 1) / 2, y, uColor);
            _pqc->ComputePoint(&uColor, (int) _uWidth - 1, y);
            _pfh->Point((int) _uWidth - 1, y, uColor);
            WaitForSingleObject(_hRunning, INFINITE);
        }
        Subdivide(0, 0, (int) (_uWidth - 1) / 2, (int) (_uHeight - 1) / 2);
        Subdivide((int) (_uWidth - 1) / 2, 0, (int) _uWidth - 1, (int) (_uHeight - 1) / 2);
        Subdivide(0, (int) (_uHeight - 1) / 2, (int) (_uWidth - 1) / 2, (int) _uHeight - 1);
        Subdivide((int) (_uWidth - 1) / 2, (int) (_uHeight - 1) / 2, (int) _uWidth - 1, (int) _uHeight - 1);
        if (WAIT_TIMEOUT == WaitForSingleObject(_hRestart, 0))
        {
            // Tell our clients that we're finished
            _pqc->DoneDrawingGraph();
            _pfh->DoneDrawingGraph();
            ResetEvent(_hRunning);
        }
    } while (TRUE);
    return(0);
}

//+---------------------------------------------------------------------------
//
//  Member:     CQuadrantEngine::PlotAreaSlowly
//
//  Synopsis:   plots each point in a rectangle by brute force: one pixel at a
//              time
//
//  Arguments:  [iXLow]  - left side of rectangle
//              [iYLow]  - top
//              [iXHigh] - right side
//              [iYHigh] - bottom
//
//  History:    4-27-94   stevebl   Created
//
//  Notes:      Used when a box is to small to be subdivided any farther.
//
//----------------------------------------------------------------------------

void CQuadrantEngine::PlotAreaSlowly(
    int iXLow,
    int iYLow,
    int iXHigh,
    int iYHigh)
{
    for (int y = iYLow ; y <= iYHigh
        && WAIT_TIMEOUT == WaitForSingleObject(_hRestart, 0)
        ; y++)
    {
        for (int x = iXLow; x <= iXHigh
            && WAIT_TIMEOUT == WaitForSingleObject(_hRestart, 0)
            ; x++)
        {
            unsigned uColor;
            _pqc->ComputePoint(&uColor, x, y);
            _pfh->Point(x, y, uColor);
            WaitForSingleObject(_hRunning, INFINITE);
        }
    }
}

// SUBDIVIDE determines at what point the Subdivide routine quits trying
// to draw ever-smaller boxes and just plots the pixels by brute force.
CONST unsigned SUBDIVIDE = 3;

//+---------------------------------------------------------------------------
//
//  Member:     CQuadrantEngine::Subdivide
//
//  Synopsis:   recursively plots all points within a rectangle
//
//  Arguments:  [iXLow]  - left side of rectangle
//              [iYLow]  - top
//              [iXHigh] - right side
//              [iYHigh] - bottom
//
//  History:    4-27-94   stevebl   Created
//
//  Algorithm:  If the bounding box optimization is enabled, check to see if
//              the perimiter is all the same color.
//              If the perimiter is all one color, fill the entire rectangle
//              with that color and fall out of the recursion.
//              Otherwise, divide the rectangle into four quadrants and
//              continue the recursion into each of them.
//
//----------------------------------------------------------------------------

void CQuadrantEngine::Subdivide(
    int iXLow,
    int iYLow,
    int iXHigh,
    int iYHigh)
{
    if (WAIT_TIMEOUT == WaitForSingleObject(_hRestart, 0))
    {
        BOOL fDescend = !_fUseBoundingBoxes;
        //
        // If the bounding box optimization is enabled then fDescend
        // will be FALSE until it finds two points on the perimiter
        // that don't match.
        //
        // If the bounding box optimization is disabled then fDescend
        // will be TRUE, causing the checking to be disabled altogether.
        //

        if (iXHigh - iXLow > SUBDIVIDE && iYHigh - iYLow > SUBDIVIDE)
        {
            unsigned uTest, uTemp;
            _pfh->GetPoint(&uTest, iXLow, iYLow);
            for (int x = iXLow;
                (!fDescend) && x <= iXHigh;
                x++)
            {
                _pfh->GetPoint(&uTemp, x, iYLow);
                if (uTest != uTemp)
                {
                    fDescend = TRUE;
                }
                else
                {
                    _pfh->GetPoint(&uTemp, x, iYHigh);
                    if (uTest != uTemp)
                    {
                        fDescend = TRUE;
                    }
                }
            }
            for (int y = iYLow + 1;
                (!fDescend) && y < iYHigh;
                y++)
            {
                _pfh->GetPoint(&uTemp, iXLow, y);
                if (uTest != uTemp)
                {
                    fDescend = TRUE;
                }
                else
                {
                    _pfh->GetPoint(&uTemp, iXHigh, y);
                    if (uTest != uTemp)
                    {
                        fDescend = TRUE;
                    }
                }
            }
            if (fDescend)
            {
                unsigned uColor;
                int iXMid = (iXLow + iXHigh) / 2;
                int iYMid = (iYLow + iYHigh) / 2;
                for (int x = iXLow + 1;
                    x < iXHigh
                    && WAIT_TIMEOUT == WaitForSingleObject(_hRestart, 0)
                    ; x++)
                {
                    _pqc->ComputePoint(&uColor, x, iYMid);
                    _pfh->Point(x, iYMid, uColor);
                    WaitForSingleObject(_hRunning, INFINITE);
                }
                int y;
                for (y = iYLow + 1;
                    y < iYMid
                    && WAIT_TIMEOUT == WaitForSingleObject(_hRestart, 0)
                    ; y++)
                {
                    _pqc->ComputePoint(&uColor, iXMid, y);
                    _pfh->Point(iXMid, y, uColor);
                    WaitForSingleObject(_hRunning, INFINITE);
                }
                for (y = iYMid + 1;
                    y < iYHigh
                    && WAIT_TIMEOUT == WaitForSingleObject(_hRestart, 0)
                    ; y++)
                {
                    _pqc->ComputePoint(&uColor, iXMid, y);
                    _pfh->Point(iXMid, y, uColor);
                    WaitForSingleObject(_hRunning, INFINITE);
                }
                Subdivide(iXLow, iYLow, iXMid, iYMid);
                Subdivide(iXMid, iYLow, iXHigh, iYMid);
                Subdivide(iXLow, iYMid, iXMid, iYHigh);
                Subdivide(iXMid, iYMid, iXHigh, iYHigh);
            }
            else
            {
                _pfh->Rect(iXLow + 1, iYLow + 1,
                    iXHigh - 1, iYHigh - 1,
                    uTest);
            }
        }
        else
        {
            // do a simple iteration to plot the graph
            PlotAreaSlowly(iXLow + 1, iYLow + 1, iXHigh - 1, iYHigh - 1);
        }
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CQuadrantEngine::CQuadrantEngine
//
//  Synopsis:   constructor
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

CQuadrantEngine::CQuadrantEngine()
{
    _uWidth = 160;
    _uHeight = 180;
    _cRef = 0;
    _pfh = NULL;
    _pqc = NULL;
    _hRunning = NULL;
    _hRestart = NULL;
    _hEngine = NULL;
    _dwThreadId = 0;
    _fUseBoundingBoxes = TRUE;
}

//+---------------------------------------------------------------------------
//
//  Member:     CQuadrantEngine::~CQuadrantEngine
//
//  Synopsis:   destructor
//
//  History:    4-14-94   stevebl   Created
//
//----------------------------------------------------------------------------

CQuadrantEngine::~CQuadrantEngine()
{
    if (_hRestart)
    {
        CloseHandle(_hRestart);
    }
    if (_hRunning)
    {
        CloseHandle(_hRunning);
    }
    if (_hEngine)
    {
        TerminateThread(_hEngine, 0);
        CloseHandle(_hEngine);
    }
}


GENWIN.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       genwin.cxx
//
//  Contents:   implementation for the generator window control
//
//  Classes:    CGeneratorWindow
//
//  Functions:
//
//  History:    5-05-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include <windows.h>
#include "genwin.h"
#include <math.h>
#include "polar.h"

#define POINTRADIUS 2

//+---------------------------------------------------------------------------
//
//  Member:     CGeneratorWindow::WindowProc
//
//  Synopsis:   Handles mouse messages and painting.
//
//  Arguments:  [uMsg]   - message
//              [wParam] - first message parameter
//              [lParam] - second message parameter
//
//  History:    5-05-94   stevebl   Created
//
//----------------------------------------------------------------------------

LRESULT CGeneratorWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_LBUTTONDOWN:
        {
            SetCapture(_hwnd);

            // translate mouse coordinates to real coordinates
            double x, y;
            x = ((double) (LOWORD(lParam) * 2.0) / _uWidth) - 1.0;
            y = ((double) (HIWORD(lParam) * -2.0) / _uHeight) + 1.0;

            // Find if we need to create a new point or if we are going to track
            // an old point.

            // Distances used here are really distances squared
            // to keep us from having to do a sqrt function.

            unsigned uClosest = 1;
            double dClosestDistance = 10.0;
            // an insanely huge number given our graph dimensions of 2 x 2

            // start with point one and go to point _nPoints - 2
            // since the end points are not moveable

            unsigned uIndex = 1;
            double dTestDistance =
                ((double)(4 * POINTRADIUS) / _uWidth) *
                ((double)(4 * POINTRADIUS) / _uHeight);
            while (uIndex < _nPoints - 1)
            {
                double dDistance =
                    (x - _rgX[uIndex]) * (x - _rgX[uIndex]) +
                    (y - _rgY[uIndex]) * (y - _rgY[uIndex]);
                if (dTestDistance >= dDistance)
                {
                    // we clicked on a point (or at least close enough to it)
                    _uTrackingPoint = uIndex;
                    return(FALSE);
                }
                if (dDistance < dClosestDistance)
                {
                    dClosestDistance = dDistance;
                    uClosest = uIndex;
                }
                uIndex++;
            }
            // If we haven't returned yet, then we didn't click on a point.
            // Therefore we must be inserting a new point.

            // Determine if we can add any more points to the list.
            if (MAX_POINTS == _nPoints)
            {
                // Nope.  Better just track the closest point then.
                _uTrackingPoint = uClosest;
                return(FALSE);
            }

            // Next determine if we should insert behind or in front.
            if (_nPoints > 2)
            {
                // We always insert in front if there are only two points.
                // Otherwise, a simple distance check will help us decide which
                // segment to insert in.
                double d1 =
                    (x - _rgX[uClosest - 1]) * (x - _rgX[uClosest - 1]) +
                    (y - _rgY[uClosest - 1]) * (y - _rgY[uClosest - 1]);
                double d2 =
                    (x - _rgX[uClosest + 1]) * (x - _rgX[uClosest + 1]) +
                    (y - _rgY[uClosest + 1]) * (y - _rgY[uClosest + 1]);
                if (d2 < d1)
                {
                    // insert after
                    ++uClosest;
                }
            }

            // Then insert the point into the list of points.

            for (uIndex = _nPoints; uIndex > uClosest; uIndex--)
            {
                _rgX[uIndex] = _rgX[uIndex - 1];
                _rgY[uIndex] = _rgY[uIndex - 1];
            }
            _rgX[uClosest] = x;
            _rgY[uClosest] = y;
            _uTrackingPoint = uClosest;
            _nPoints++;

            // And cause the window to be repainted.
            InvalidateRect(_hwnd, NULL, TRUE);
        }
        break;
    case WM_MOUSEMOVE:
        if (wParam & MK_LBUTTON)
        {
            // translate mouse coordinates to real coordinates
            double x, y;
            x = ((double) (LOWORD(lParam) * 2.0) / _uWidth) - 1.0;
            y = ((double) (HIWORD(lParam) * -2.0) / _uHeight) + 1.0;

            // Move whatever point we are tracking
            _rgX[_uTrackingPoint] = x;
            _rgY[_uTrackingPoint] = y;

            // And cause the window to be repainted.
            InvalidateRect(_hwnd, NULL, TRUE);
        }
        break;
    case WM_LBUTTONUP:
        // If the point we have been tracking has moved off the screen, remove it from the list.
        if (_rgX[_uTrackingPoint] < -1.0
            || _rgX[_uTrackingPoint] > 1.0
            || _rgY[_uTrackingPoint] < -1.0
            || _rgY[_uTrackingPoint] > 1.0
            )
        {
            _nPoints--;
            for (unsigned uIndex = _uTrackingPoint; uIndex < _nPoints; uIndex++)
            {
                _rgX[uIndex] = _rgX[uIndex + 1];
                _rgY[uIndex] = _rgY[uIndex + 1];
            }
        }

        // And cause the window to be repainted.
        InvalidateRect(_hwnd, NULL, TRUE);
        ReleaseCapture();
        break;
    case WM_SIZE:
        _uWidth = LOWORD(lParam);
        _uHeight = HIWORD(lParam);
        break;
    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            RECT rc;
            HDC hdc = BeginPaint(_hwnd, &ps);
            if (GetClipBox(ps.hdc, &rc) != NULLREGION)
            {
                // paint the background grid
                HPEN hpen = (HPEN) CreatePen(PS_DOT, 0, WHITENESS);
                hpen = (HPEN) SelectObject(ps.hdc, hpen);

#define NUMDIVISIONS 12

                int i1, i2;
                for (i1 = NUMDIVISIONS-1; i1--;)
                {
                    i2 = (_uHeight * (i1 + 1)) / NUMDIVISIONS;
                    MoveToEx(ps.hdc, 0, i2, NULL);
                    LineTo(ps.hdc, _uWidth, i2);
                    i2 = (_uWidth * (i1 + 1)) / NUMDIVISIONS;
                    MoveToEx(ps.hdc, i2, 0, NULL);
                    LineTo(ps.hdc, i2, _uHeight);
                }

                // The vertical lines

                hpen = (HPEN) SelectObject(ps.hdc, hpen);
                DeleteObject(hpen);

                // paint the generator
                hpen = (HPEN) GetStockObject(WHITE_PEN);
                HBRUSH hbrush = (HBRUSH) GetStockObject(WHITE_BRUSH);
                hpen = (HPEN) SelectObject(ps.hdc, hpen);
                hbrush = (HBRUSH) SelectObject(ps.hdc, hbrush);

                POINT rgPt[MAX_POINTS];
                for (unsigned uIndex = 0; uIndex < _nPoints; uIndex++)
                {
                    rgPt[uIndex].x = (long)(((_rgX[uIndex] + 1.0) * _uWidth) / 2);
                    rgPt[uIndex].y = (long)(((_rgY[uIndex] - 1.0) * _uHeight) / -2);
                    Ellipse(
                        ps.hdc,
                        rgPt[uIndex].x - POINTRADIUS,
                        rgPt[uIndex].y - POINTRADIUS,
                        rgPt[uIndex].x + POINTRADIUS,
                        rgPt[uIndex].y + POINTRADIUS);
                }
                Polyline(ps.hdc, rgPt, _nPoints);

                hbrush = (HBRUSH) SelectObject(ps.hdc, hbrush);
                hpen = (HPEN) SelectObject(ps.hdc, hpen);
                DeleteObject(hbrush);
                DeleteObject(hpen);
            }
            EndPaint(_hwnd, &ps);
        }
        break;
    default:
        return(DefWindowProc(_hwnd, uMsg, wParam, lParam));
    }
    return(FALSE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CGeneratorWindow::InitializePoints
//
//  Synopsis:   sets up internal list of points
//
//  Arguments:  [rgRadius]  - array of radius values
//              [rgTheta]   - array of angles
//              [nSegments] - number of segments in the lists
//
//  Modifies:   _rgX, _rgY, and _nPoints
//
//  History:    5-05-94   stevebl   Created
//
//----------------------------------------------------------------------------

void CGeneratorWindow::InitializePoints(
    double * rgRadius,
    double * rgTheta,
    unsigned nSegments)
{
    _rgX[0] = -0.5;
    _rgY[0] = 0;
    for (unsigned index = 0; index < nSegments-1; index ++)
    {
        CartesianFromPolar(
            _rgX[index + 1],
            _rgY[index + 1],
            rgRadius[index],
            rgTheta[index]);
        _rgX[index + 1] += _rgX[index];
        _rgY[index + 1] += _rgY[index];
    }

    // guarantee the position of the last point
    _rgX[nSegments] = 0.5;
    _rgY[nSegments] = 0;
    _nPoints = nSegments + 1;
}

//+---------------------------------------------------------------------------
//
//  Member:     CGeneratorWindow::RetrievePoints
//
//  Synopsis:   returns a new list of segments built from the point list
//
//  Arguments:  [rgRadius]  - [out] array of radius values
//              [rgTheta]   - [out] array of angles
//              [nSegments] - [out] number of segments
//
//  History:    5-05-94   stevebl   Created
//
//----------------------------------------------------------------------------

void CGeneratorWindow::RetrievePoints(
    double * rgRadius,
    double * rgTheta,
    unsigned &nSegments)
{
    nSegments = _nPoints - 1;
    for (unsigned index = 0; index < nSegments - 1; index++)
    {
        PolarFromCartesian(
            rgRadius[index],
            rgTheta[index],
            _rgX[index + 1] - _rgX[index],
            _rgY[index + 1] - _rgY[index]);
    }
    // guarantee the position of the last point
    PolarFromCartesian(
        rgRadius[nSegments - 1],
        rgTheta[nSegments - 1],
        0.5 - _rgX[nSegments - 1],
        0.0 - _rgY[nSegments - 1]);
}

//+---------------------------------------------------------------------------
//
//  Member:     CGeneratorWindow::Redraw
//
//  Synopsis:   causes the generator control to repaint itself
//
//  History:    5-05-94   stevebl   Created
//
//----------------------------------------------------------------------------

void CGeneratorWindow::Redraw(void)
{
    InvalidateRect(_hwnd, NULL, TRUE);
}


GENWIN.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       genwin.h
//
//  Contents:   definition for the generator control window
//
//  Classes:    CGeneratorWindow
//
//  Functions:
//
//  History:    5-05-94   stevebl   Created
//
//----------------------------------------------------------------------------

#ifndef __GENWIN_H__
#define __GENWIN_H__

#include <windows.h>
#include <cwindow.h>
#define MAX_POINTS 51

#ifdef __cplusplus

class CGeneratorWindow: public CHlprWindow
{
public:
    LRESULT WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
    void InitializePoints(double * rgRadius, double * rgTheta, unsigned nSegments);
    void RetrievePoints(double * rgRadius, double * rgTheta, unsigned &nSegments);
    void Redraw(void);
private:
    double _rgX[MAX_POINTS];
    double _rgY[MAX_POINTS];
    unsigned _uTrackingPoint;
    unsigned _nPoints;
    unsigned _uWidth, _uHeight;
};

#endif // __cplusplus
#endif // __GENWIN_H__


POLAR.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       polar.cxx
//
//  Contents:   routines for converting between polar and cartesian coordinate
//              systems
//
//  Classes:
//
//  Functions:  PolarFromCartesian
//              CartesianFromPolar
//
//  History:    5-04-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include "polar.h"
#include <math.h>

//+---------------------------------------------------------------------------
//
//  Function:   CartesianFromPolar
//
//  Synopsis:   converts from polar coordinates to cartesian coordinates
//
//  Arguments:  [x]     - [out] x position
//              [y]     - [out] y position
//              [r]     - [in] radius
//              [theta] - [in] angle in radians
//
//  Returns:    nothing
//
//  History:    5-04-94   stevebl   Created
//
//----------------------------------------------------------------------------

void CartesianFromPolar(double & x, double & y, double r, double theta)
{
    x = r * cos(theta);
    y = r * sin(theta);
}

//+---------------------------------------------------------------------------
//
//  Function:   PolarFromCartesian
//
//  Synopsis:   converts from cartesian to polar coordinates
//
//  Arguments:  [r]     - [out] radius
//              [theta] - [out] angle in radians
//              [x]     - [in] x position
//              [y]     - [in] y position
//
//  Returns:    nothing
//
//  History:    5-04-94   stevebl   Created
//
//----------------------------------------------------------------------------

void PolarFromCartesian(double & r, double & theta, double x, double y)
{
    r = sqrt(x * x + y * y);
    if (r == 0)
    {
        theta = 0;
        return;
    }
    if (y > 0)
    {
        theta = acos(x / r);
    }
    else
    {
        // acos(0) == pi / 2
        // I'm using the trig functions instead of a constant
        // to ensure that the conversion functions use the
        // same values when going in both directions.
        theta = 4.0 * acos((float)0) - acos(x / r);
    }
}


POLAR.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       polar.h
//
//  Contents:   routines for converting between polar and cartesian coordinate
//              systems
//
//  Classes:
//
//  Functions:  PolarFromCartesian
//              CartesianFromPolar
//
//  History:    5-04-94   stevebl   Created
//
//----------------------------------------------------------------------------

#ifndef __POLAR_H__
#define __POLAR_H__

#ifdef __cplusplus

//+---------------------------------------------------------------------------
//
//  Function:   CartesianFromPolar
//
//  Synopsis:   converts from polar coordinates to cartesian coordinates
//
//  Arguments:  [x]     - [out] x position
//              [y]     - [out] y position
//              [r]     - [in] radius
//              [theta] - [in] angle in radians
//
//  Returns:    nothing
//
//  History:    5-04-94   stevebl   Created
//
//----------------------------------------------------------------------------

void CartesianFromPolar(double & x, double & y, double r, double theta);

//+---------------------------------------------------------------------------
//
//  Function:   PolarFromCartesian
//
//  Synopsis:   converts from cartesian to polar coordinates
//
//  Arguments:  [r]     - [out] radius
//              [theta] - [out] angle in radians
//              [x]     - [in] x position
//              [y]     - [in] y position
//
//  Returns:    nothing
//
//  History:    5-04-94   stevebl   Created
//
//----------------------------------------------------------------------------

void PolarFromCartesian(double & r, double & theta, double x, double y);

#endif // __cplusplus

#endif // __POLAR_H__


RGEN.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       RGen.cxx
//
//  Contents:   implementation for the Recursive Generator Fractal engine
//
//  Classes:    CRGenCF
//
//  Functions:  DllEntryPoint
//              DllGetClassObject
//              DllCanUnloadNow
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include <windows.h>
#include <ole2.h>
#include "RGen.h"
#include <rgencid.h>

ULONG gcRef, gcLock;
HINSTANCE ghinst;

extern "C"
{
    BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID);
}

//+---------------------------------------------------------------------------
//
//  Function:   DllEntryPoint
//
//  Synopsis:   the DLL's entry point
//
//  Arguments:  [hinst]      - instance handle for the dll
//              [fdwReason]  - reason for calling the entry point
//              [lpReserved] - reserved
//
//  Returns:    TRUE if DLL_PROCESS_ATTACH initialization succeeds
//              FALSE otherwise
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

BOOL WINAPI DllMain(
    HINSTANCE hinst,
    DWORD fdwReason,
    LPVOID lpReserved)
{
    switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        ghinst = hinst;
        gcRef = gcLock = 0;
        WNDCLASS wc;
        wc.style = 0;
        wc.lpfnWndProc = &WindowProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 0;
        wc.hInstance = hinst;
        wc.hIcon = NULL;
        wc.hCursor = (HCURSOR) LoadCursor(hinst, MAKEINTRESOURCE(IDC_CHOOSE));
        wc.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
        wc.lpszMenuName = NULL;
        wc.lpszClassName = TEXT("RGen Generator");
        return(0 != RegisterClass(&wc));
    case DLL_THREAD_ATTACH:
        break;
    case DLL_THREAD_DETACH:
        break;
    case DLL_PROCESS_DETACH:
        break;
    }
    return(FALSE);  // initialization failed (ignored if not DLL_PROCESS_ATTACH)
}

//+---------------------------------------------------------------------------
//
//  Function:   DllGetClassObject
//
//  Synopsis:   function called by OLE to get a class object
//
//  Arguments:  [rclsid] - the class id of the desired class object
//              [riid]   - the id of the desired interface
//              [ppv]    - pointer to recieve the interface
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT PASCAL DllGetClassObject(
    REFCLSID rclsid,
    REFIID riid,
    LPVOID * ppv)
{
    if (!IsEqualGUID(rclsid, CLSID_RGEN))
    {
        return(E_FAIL);
    }
    if ((!IsEqualGUID(riid, IID_IUnknown)) &&
        (!IsEqualGUID(riid, IID_IClassFactory)))
    {
        return(E_NOINTERFACE);
    }
    *ppv = (LPVOID) new CRGenCF();
    if (NULL == *ppv)
    {
        return(E_OUTOFMEMORY);
    }
    ((IUnknown*)*ppv)->AddRef();
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Function:   DllCanUnloadNow
//
//  Synopsis:   determines if there are no outstanding references on this DLL
//
//  Returns:    S_OK if the DLL can be unloaded
//              S_FALSE if not
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

STDAPI DllCanUnloadNow(void)
{
    // return S_OK if there are no references on myself
    if (0 == gcRef && 0 == gcLock)
    {
        return(S_OK);
    }
    return(S_FALSE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGenCF::CRGenCF
//
//  Synopsis:   constructor
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

CRGenCF::CRGenCF()
{
    _cRef = 0;
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGenCF::~CRGenCF
//
//  Synopsis:   destructor
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

CRGenCF::~CRGenCF()
{
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGenCF::QueryInterface
//
//  Synopsis:   Standard OLE interface
//
//  Arguments:  [riid] - id of the desired interface
//              [ppv]  - pointer to receive the interface
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CRGenCF::QueryInterface(REFIID riid, LPVOID * ppv)
{
    *ppv = NULL;
    if ((!IsEqualGUID(riid, IID_IUnknown)) &&
        (!IsEqualGUID(riid, IID_IClassFactory)))
    {
        return(E_NOINTERFACE);
    }
    *ppv = (IClassFactory *) this;
    AddRef();
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGenCF::AddRef
//
//  Synopsis:   increment reference count
//
//  Returns:    new reference count
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CRGenCF::AddRef(void)
{
    return(++_cRef);
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGenCF::Release
//
//  Synopsis:   decrement reference count
//
//  Returns:    new reference count
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CRGenCF::Release(void)
{
    ULONG cRef = --_cRef;
    if (0 == cRef)
    {
        delete(this);
    }
    return(cRef);
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGenCF::CreateInstance
//
//  Synopsis:   creates an instance of a RGen Engine
//
//  Arguments:  [pUnkOuter] - controlling unknown (must be NULL)
//              [riid]      - id of desired interface
//              [ppv]       - pointer to receive the interface
//
//  Returns:    S_OK - success
//              CLASS_E_NOAGGREATION - the caller tried to aggregate
//              CLASS_E_CLASSNOTAVAILABLE - couldn't initialize the class
//              E_OUTOFMEMORY - not enough memory to instantiate class
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CRGenCF::CreateInstance(LPUNKNOWN pUnkOuter, REFIID riid, LPVOID * ppv)
{
    HRESULT hr = S_OK;
    *ppv = NULL;
    if (pUnkOuter != NULL)
    {
        return(CLASS_E_NOAGGREGATION);
    }

    CRGen * pObj = new CRGen;
    if (NULL == pObj)
    {
        return(E_OUTOFMEMORY);
    }

    if (!pObj->Initialize())
    {
        delete ppv;
        return(CLASS_E_CLASSNOTAVAILABLE);
    }

    hr = pObj->QueryInterface(riid, ppv);
    if (FAILED(hr))
    {
        delete ppv;
    }
    else
    {
        gcRef++;
    }

    return(hr);
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGenCF::LockServer
//
//  Synopsis:   locks the server, preventing it from being unloaded
//
//  Arguments:  [fLock] - TRUE to lock, FALSE to unlock
//
//  Returns:    S_OK
//
//  Modifies:   gcLock
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CRGenCF::LockServer(BOOL fLock)
{
    if (fLock)
    {
        gcLock++;
    }
    else
    {
        gcLock--;
    }
    return(S_OK);
}


RGEN.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       RGen.h
//
//  Contents:   class definitions for the Recursive Generator Fractal engine
//
//  Classes:    CRGenCF
//              CRGen
//
//  Functions:
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

#ifndef __RGen_H__
#define __RGen_H__

#define PROPERTIES 100

#define IDD_RESET                   102
#define IDD_ABOUT                   103
#define IDD_ITTERATIONS              105
#define IDD_GENERATOR               101

#define IDS_ABOUT_TITLE     200
#define IDS_ABOUT_TEXT      201

#define IDC_CHOOSE  300

#ifdef __cplusplus

#include <frcngn.h>
#include <frhost.h>
#include <cdialog.h>
#include "genwin.h"

#define MAX_SEGMENTS (MAX_POINTS - 1)
//+---------------------------------------------------------------------------
//
//  Class:      CRGenCF
//
//  Purpose:    class factory for the RGen engine
//
//  Interface:  CRGenCF      -- constructor
//              ~CRGenCF     -- destructor
//              QueryInterface -- requests an interface
//              AddRef         -- increments refcount
//              Release        -- decrements refcount
//              CreateInstance -- creates a Mandelbrot engine
//              LockServer     -- keeps cf around if released
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

class CRGenCF : public IClassFactory
{
public:
    CRGenCF();
    ~CRGenCF();

    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID *);
    ULONG STDMETHODCALLTYPE AddRef(void);
    ULONG STDMETHODCALLTYPE Release(void);

    HRESULT STDMETHODCALLTYPE CreateInstance(LPUNKNOWN, REFIID, LPVOID*);
    HRESULT STDMETHODCALLTYPE LockServer(BOOL);

protected:
    ULONG _cRef;
};

//+---------------------------------------------------------------------------
//
//  Class:      CRGen
//
//  Purpose:    implements the RGen engine
//
//  Interface:  QueryInterface     -- retrieves an interface
//              AddRef             -- increments refcount
//              Release            -- decrements refcount
//              GetClassID         -- gets the CLSID
//              IsDirty            -- tests if data should be saved
//              Load               -- loads properties
//              Save               -- saves properties
//              GetSizeMax         -- returns max size of property stream
//              Init               -- initializes engine
//              SetDefaults        -- sets default property values
//              SetProperties      -- displays the property dialog box
//              GetExtent          -- gets size of the graph (in graph units)
//              SetExtent          -- sets size of the graph (in graph units)
//              SetGraphSize       -- sets size of the graph (in pixels)
//              Start              -- starts the graph engine
//              Stop               -- stops the graph engine
//              DialogProc         -- dialog proc for the property dialog box
//              Initialize         -- private initialization function
//              GraphicsThread     -- graphics thread entry point
//              CRGen            -- constructor
//              ~CRGen           -- destructor
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

class CRGen : public IFractalEngine, IPersistStream, CHlprDialog
{
public:
    // IUnknown methods
    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, LPVOID *);
    ULONG STDMETHODCALLTYPE AddRef(void);
    ULONG STDMETHODCALLTYPE Release(void);

    // IPersist methods
    HRESULT STDMETHODCALLTYPE GetClassID(LPCLSID pclsid);

    // IPersistStream methods
    HRESULT STDMETHODCALLTYPE IsDirty(void);
    HRESULT STDMETHODCALLTYPE Load(LPSTREAM pStm);
    HRESULT STDMETHODCALLTYPE Save(LPSTREAM pStm, BOOL fClearDirty);
    HRESULT STDMETHODCALLTYPE GetSizeMax(ULARGE_INTEGER * pcbSize);

    // IFractalEngine methods
    HRESULT STDMETHODCALLTYPE Init(IFractalHost *pfh);

    HRESULT STDMETHODCALLTYPE SetDefaults(void);

    HRESULT STDMETHODCALLTYPE SetProperties(HWND hwnd);

    HRESULT STDMETHODCALLTYPE GetExtent(
        double *pdLeft,
        double *pdTop,
        double *pdRight,
        double *pdBottom);

    HRESULT STDMETHODCALLTYPE SetExtent(
        double dLeft,
        double dTop,
        double dRight,
        double dBottom);

    HRESULT STDMETHODCALLTYPE SetGraphSize(
        unsigned int uWidth,
        unsigned int uHeight);

    HRESULT STDMETHODCALLTYPE Start(void);

    HRESULT STDMETHODCALLTYPE Stop(void);

    // Methods from CHlprDialog
    BOOL DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);

    // Methods that aren't part of any interface
    BOOL Initialize(void);
    DWORD GraphicsThread(void);
    HRESULT SetDefaultSegmentList(void);
    void Recurse(double x, double y, double dRadius, double dTheta, unsigned uItteration);

    CRGen();
    ~CRGen();
private:
    ULONG _cRef;
    HANDLE _hRestart, _hRunning, _hEngine;
    DWORD _dwThreadId;
    double _dLeft, _dRight, _dTop, _dBottom;
    unsigned _nSegments;
    unsigned _nItterations;
    double _rgTheta[MAX_SEGMENTS];
    double _rgRadius[MAX_SEGMENTS];
    unsigned _uWidth, _uHeight, _uColor;
    IFractalHost * _pfh;
    BOOL _fDirty;
    CGeneratorWindow  * _pwinGeneratorWindow;
};

extern HINSTANCE ghinst;
extern ULONG gcRef, gcLock;

#endif //__cplusplus
#endif //__RGen_H__


RGENOB.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       RGenob.cxx
//
//  Contents:   Recursive Generator Fractal engine object
//
//  Classes:    CRGen
//
//  Functions:  StartGraphicsThread
//              Normalize
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include <windows.h>
#include <ole2.h>
#include <rgencid.h>
#include "RGen.h"
#include <stdio.h>
#include <strmhelp.h>
#include <palsize.h>
#include "polar.h"
#include <math.h>

#define MAX_RANDOM 50.0
#define MIN_RANDOM 0

//+---------------------------------------------------------------------------
//
//  Function:   StartGraphicsThread
//
//  Synopsis:   Global function used to bootstrap into the GraphicsThread
//              member of the CRGen class.
//
//  Arguments:  [lpdwParam] - not used
//
//  History:    4-15-94   stevebl   Created
//
//----------------------------------------------------------------------------

DWORD StartGraphicsThread(LPDWORD lpdwParam)
{
    return (((CRGen *)lpdwParam)->GraphicsThread());
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGen::QueryInterface
//
//  Synopsis:   standard OLE method
//
//  Arguments:  [riid] - id of desired interface
//              [ppv]  - pointer to receive interface
//
//  Returns:    S_OK on success
//              E_NOINTERFACE if the requested interface isn't supported
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CRGen::QueryInterface(REFIID riid, LPVOID * ppv)
{
    *ppv = NULL;
    if (IsEqualGUID(IID_IFractalEngine, riid))
    {
        *ppv = (IFractalEngine *)this;
    }
    else if (IsEqualGUID(IID_IPersist, riid))
    {
        *ppv = (IPersist *)this;
    }
    else if (IsEqualGUID(IID_IPersistStream, riid))
    {
        *ppv = (IPersistStream *)this;
    }
    else if (IsEqualGUID(IID_IUnknown, riid))
    {
        *ppv = (IUnknown *)(IFractalEngine *)this;
    }
    if (*ppv)
    {
        ((IUnknown *)*ppv)->AddRef();
        return(S_OK);
    }
    return(E_NOINTERFACE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGen::AddRef
//
//  Synopsis:   increments the reference count
//
//  Returns:    new reference count
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CRGen::AddRef(void)
{
    return(++_cRef);
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGen::Release
//
//  Synopsis:   decrements the reference count
//
//  Returns:    new reference count
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

ULONG STDMETHODCALLTYPE CRGen::Release(void)
{
    ULONG cRef = --_cRef;
    if (0 == cRef)
    {
        delete this;
        gcRef--;
    }
    return(cRef);
}

// IPersist methods

//+---------------------------------------------------------------------------
//
//  Member:     CRGen::GetClassID
//
//  Synopsis:   returns the object's class ID
//
//  Arguments:  [pclsid] - pointer to recieve the class ID
//
//  Returns:    S_OK
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT CRGen::GetClassID(LPCLSID pclsid)
{
    memcpy(pclsid, &CLSID_RGEN, sizeof(GUID));
    return(S_OK);
}

// IPersistStream methods

//+---------------------------------------------------------------------------
//
//  Member:     CRGen::IsDirty
//
//  Synopsis:   used to determine if the state of the object has changed
//
//  Returns:    S_OK if it is dirty
//              S_FALSE if it is not dirty
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT CRGen::IsDirty(void)
{
    if (_fDirty)
    {
        return(S_OK);
    }
    else
    {
        return(S_FALSE);
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGen::Load
//
//  Synopsis:   loads the fractal properties from a stream
//
//  Arguments:  [pStm] - pointer to an IStream interface
//
//  Returns:    S_OK on success
//              E_FAIL on error
//              E_OUTOFMEMORY if there's not enough memory (can't happen here)
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT CRGen::Load(LPSTREAM pStm)
{
    if (WAIT_OBJECT_0 == WaitForSingleObject(_hRunning, 0))
    {
        return(E_FAIL);
    }
    HRESULT hr;
    if (FAILED(hr = pStm->Read(&_nItterations, sizeof(_nItterations), NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = pStm->Read(&_nSegments, sizeof(_nSegments), NULL)))
    {
        return(hr);
    }
    for (unsigned uIndex = 0; uIndex < _nSegments; uIndex++)
    {
        if (FAILED(hr = ReadDouble(pStm, &_rgRadius[uIndex], NULL)))
        {
            return(hr);
        }
        if (FAILED(hr = ReadDouble(pStm, &_rgTheta[uIndex], NULL)))
        {
            return(hr);
        }
    }
    if (FAILED(hr = ReadDouble(pStm, &_dLeft, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = ReadDouble(pStm, &_dTop, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = ReadDouble(pStm, &_dRight, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = ReadDouble(pStm, &_dBottom, NULL)))
    {
        return(hr);
    }
    _fDirty = FALSE;
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGen::Save
//
//  Synopsis:   saves the fractal properties to a stream
//
//  Arguments:  [pStm]        - pointer to an IStream interface
//              [fClearDirty] - TRUE if the dirty flag should be cleared
//
//  Returns:    S_OK on success
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT CRGen::Save(LPSTREAM pStm, BOOL fClearDirty)
{
    if (WAIT_OBJECT_0 == WaitForSingleObject(_hRunning, 0))
    {
        return(E_FAIL);
    }
    HRESULT hr;
    if (FAILED(hr = pStm->Write(&_nItterations, sizeof(_nItterations), NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = pStm->Write(&_nSegments, sizeof(_nSegments), NULL)))
    {
        return(hr);
    }
    for (unsigned uIndex = 0; uIndex < _nSegments; uIndex++)
    {
        if (FAILED(hr = WriteDouble(pStm, _rgRadius[uIndex], NULL)))
        {
            return(hr);
        }
        if (FAILED(hr = WriteDouble(pStm, _rgTheta[uIndex], NULL)))
        {
            return(hr);
        }
    }
    if (FAILED(hr = WriteDouble(pStm, _dLeft, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = WriteDouble(pStm, _dTop, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = WriteDouble(pStm, _dRight, NULL)))
    {
        return(hr);
    }
    if (FAILED(hr = WriteDouble(pStm, _dBottom, NULL)))
    {
        return(hr);
    }
    if (fClearDirty)
    {
        _fDirty = FALSE;
    }
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGen::GetSizeMax
//
//  Synopsis:   used to determine the maximum size of the object's data
//
//  Arguments:  [pcbSize] - pointer to recieve the max size
//
//  Returns:    S_OK
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT CRGen::GetSizeMax(ULARGE_INTEGER * pcbSize)
{
    pcbSize->HighPart = 0;
    pcbSize->LowPart = SizeDouble();
    return(S_OK);
}

// IFractalEngine methods

//+---------------------------------------------------------------------------
//
//  Member:     CRGen::Init
//
//  Synopsis:   Initializes the graph engine and saves a pointer to the
//              IFractalHost interface that WinFract passes in as
//              the engine is initialized.
//
//  Arguments:  [pfh] - pointer to an IFractalHost interface
//
//  Returns:    S_OK
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CRGen::Init(IFractalHost *pfh)
{
    _pfh = pfh;
    // NOTE that I don't addref this pointer!  That would create a circular
    // reference count paradox.
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGen::SetDefaultSegmentList
//
//  Synopsis:   sets the default segment list
//
//  Returns:    S_OK on success
//              E_OUTOFMEMORY if the list couldn't be created
//
//  History:    5-04-94   stevebl   Created
//
//  Notes:      The default segment list consists of four
//              segments that conform to the following shape:
//
//                   /\      .
//                  /  \     .
//              ___/    \___
//
//              This is the Triadic Koch Curve.
//
//              The distance from the first point to the last point in the
//              segment list (or path) is always 1.0 units.  The degree is
//              always 0.
//
//----------------------------------------------------------------------------

HRESULT CRGen::SetDefaultSegmentList(void)
{
    _rgTheta[0] = 0;

    // acos(0) == pi / 2
    // I'm using the trig functions instead of a constant
    // to ensure that the conversion functions use the
    // same values as the ones which I have computed.
    _rgTheta[1] = (acos((float)0) * 2.0) / 3.0;
    _rgTheta[2] = 0 - (acos((float)0) * 2.0) / 3.0;
    _rgTheta[3] = 0;
    _rgRadius[0] = _rgRadius[1] = _rgRadius[2] = _rgRadius[3] = 1.0 / 3.0;
    _nSegments = 4;
    return(S_OK);
}
//+---------------------------------------------------------------------------
//
//  Member:     CRGen::SetDefaults
//
//  Synopsis:   initializes the graph engine with its default values
//
//  Returns:    S_OK
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CRGen::SetDefaults(void)
{
    if (_uWidth > _uHeight)
    {
        // image is wider than it is tall
        _dTop = 1.0;
        _dBottom = -1.0;
        _dLeft = (-1.0 * _uWidth) / _uHeight;
        _dRight = (1.0 * _uWidth) / _uHeight;
    }
    else
    {
        // image is taller than it is wide
        _dLeft = -1.0;
        _dRight = 1.0;
        _dTop = (1.0 * _uHeight) / _uWidth;
        _dBottom = (-1.0 * _uHeight) / _uWidth;
    }
    _nItterations = 6;
    _fDirty = TRUE;
    SetEvent(_hRestart);
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGen::SetProperties
//
//  Synopsis:   Displays the graph engine's property dialog box.
//
//  Arguments:  [hwnd] - handle to the parent window
//
//  Returns:    S_OK
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CRGen::SetProperties(HWND hwnd)
{
    ShowDialog(ghinst, MAKEINTRESOURCE(PROPERTIES), hwnd);
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGen::GetExtent
//
//  Synopsis:   retrieves the extent of the graph
//
//  Arguments:  [pdLeft]   - x value at the left of the graph
//              [pdTop]    - y value at the top of the graph
//              [pdRight]  - x value at the right of the graph
//              [pdBottom] - y value at the bottom of the graph
//
//  Returns:    S_OK
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CRGen::GetExtent(
    double *pdLeft,
    double *pdTop,
    double *pdRight,
    double *pdBottom)
{
    *pdLeft = _dLeft;
    *pdTop = _dTop;
    *pdRight = _dRight;
    *pdBottom = _dBottom;
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGen::SetExtent
//
//  Synopsis:   sets the graph's extent
//
//  Arguments:  [dLeft]   - x value at the left of the graph
//              [dTop]    - y value at the top of the graph
//              [dRight]  - x value at the right of the graph
//              [dBottom] - y value at the bottom of the graph
//
//  Returns:    S_OK - extents set
//              E_FAIL - the graph was running so the extents couldn't be set
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CRGen::SetExtent(
    double dLeft,
    double dTop,
    double dRight,
    double dBottom)
{
    if (WAIT_OBJECT_0 == WaitForSingleObject(_hRunning, 0))
    {
        return(E_FAIL);
    }
    _dLeft = dLeft;
    _dTop = dTop;
    _dRight = dRight;
    _dBottom = dBottom;
    _fDirty = TRUE;
    SetEvent(_hRestart);
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGen::SetGraphSize
//
//  Synopsis:   Tells the graph engine how big the graph is
//
//  Arguments:  [uWidth]  - width of graph (in pixels)
//              [uHeight] - height of graph (in pixels)
//
//  Returns:    S_OK - success
//              E_FAIL - the graph was running or a value was illegal
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CRGen::SetGraphSize(
    unsigned int uWidth,
    unsigned int uHeight)
{
    if (WAIT_OBJECT_0 == WaitForSingleObject(_hRunning, 0))
    {
        return(E_FAIL);
    }
    if (uWidth == 0 || uHeight == 0)
    {
        return(E_FAIL);
    }
    _uWidth = uWidth;
    _uHeight = uHeight;
    SetEvent(_hRestart);
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGen::Start
//
//  Synopsis:   starts the graph engine
//
//  Returns:    S_OK on success
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CRGen::Start(void)
{
    SetEvent(_hRunning);
    return(S_OK);
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGen::Stop
//
//  Synopsis:   stops the graph engine
//
//  Returns:    S_OK on success
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT STDMETHODCALLTYPE CRGen::Stop(void)
{
    ResetEvent(_hRunning);
    return(S_OK);
}

// Methods that aren't part of any interface

//+---------------------------------------------------------------------------
//
//  Member:     CRGen::Initialize
//
//  Synopsis:   called by the class factory to initialize the object
//
//  Returns:    TRUE on success
//              FALSE if the object couldn't be initialized
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

BOOL CRGen::Initialize(void)
{
    _hRestart = CreateEvent(
        NULL,
        TRUE,   // must be manually reset
        TRUE,   // initially signaled
        NULL);
    if (_hRestart == NULL)
    {
        return(FALSE);  // couldn't create an event
    }
    _hRunning = CreateEvent(
        NULL,
        TRUE,
        FALSE,
        NULL);
    if (_hRunning == NULL)
    {
        return(FALSE);
    }
    _hEngine = CreateThread(
        (LPSECURITY_ATTRIBUTES)NULL,
        0,
        (LPTHREAD_START_ROUTINE) StartGraphicsThread,
        this,
        0,
        &_dwThreadId);
    if (_hEngine == NULL)
    {
        return(FALSE);  // couldn't start the graphics thread
    }
    SetDefaultSegmentList();
    return(TRUE);
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGen::GraphicsThread
//
//  Synopsis:   main entry point for the engine's graphics thread
//
//  History:    4-23-94   stevebl   Created
//
//  Notes:      This routine enters into an eternal loop.  It will continue
//              looping until the class is destroyed at which point this
//              thread will be simply terminated.
//
//  Algorithm:
//
//----------------------------------------------------------------------------
DWORD CRGen::GraphicsThread(void)
{
    do
    {
        // wait here until someone starts the graphics engine
        WaitForSingleObject(_hRunning, INFINITE);

        // reset the _hRestart event so that we won't immediately fall out
        // of the engine
        ResetEvent(_hRestart);

        // Now begin drawing the graph

        // erase the background
        _pfh->Rect(0, 0, _uWidth - 1, _uHeight - 1, 0);

        // begin the recursion
        _uColor = 1;
        Recurse(-0.5, 0.0, 1.0, 0.0, 0);

        // We're either done drawing the graph now or we have been restarted
        if (WAIT_TIMEOUT == WaitForSingleObject(_hRestart, 0))
        {
            // Tell our clients that we're finished
            _pfh->DoneDrawingGraph();
            ResetEvent(_hRunning);
        }
    } while (TRUE);
    return(0);
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGen::Recurse
//
//  Synopsis:   Plots one level of the recursion.
//
//  Arguments:  [x]           - the x coordinate of the start of the segment
//              [y]           - the y coordinate of the start of the segment
//              [dRadius]     - the length of the segment
//              [dTheta]      - the angle of the segment
//              [uItteration] - the current depth
//
//  History:    5-04-94   stevebl   Created
//
//  Notes:
//
//----------------------------------------------------------------------------

void CRGen::Recurse(double x, double y, double dRadius, double dTheta, unsigned uItteration)
{
    if (WAIT_TIMEOUT == WaitForSingleObject(_hRestart, 0))
    {
        WaitForSingleObject(_hRunning, INFINITE);
        if (uItteration < _nItterations)
        {
            double dOffsetX, dOffsetY;

            for (unsigned uIndex = 0; uIndex < _nSegments; uIndex++)
            {
                Recurse(
                    x,
                    y,
                    _rgRadius[uIndex] * dRadius,
                    _rgTheta[uIndex] + dTheta,
                    uItteration + 1);
                CartesianFromPolar(
                    dOffsetX,
                    dOffsetY,
                    _rgRadius[uIndex] * dRadius,
                    _rgTheta[uIndex] + dTheta);
                x += dOffsetX;
                y += dOffsetY;
            }
        }
        else
        {
            double dDeltaX, dDeltaY;

            CartesianFromPolar(dDeltaX, dDeltaY, dRadius, dTheta);

            double dWidth = _dRight - _dLeft;
            double dHeight = _dBottom - _dTop;

#define CONVERTX(x) ((((x) - _dLeft) * (_uWidth - 1)) / dWidth)
#define CONVERTY(y) ((((y) - _dTop) * (_uHeight - 1)) / dHeight)

            int x1 = (int) CONVERTX(x);
            int y1 = (int) CONVERTY(y);
            int x2 = (int) CONVERTX(x + dDeltaX);
            int y2 = (int) CONVERTY(y + dDeltaY);

            _pfh->Line(x1, y1, x2, y2, _uColor);

            // advance to the next color
            _uColor = 1 + (_uColor % (PALETTESIZE - 1));
        }
    }
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGen::CRGen
//
//  Synopsis:   constructor
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

CRGen::CRGen()
{
    _uWidth = 160;
    _uHeight = 120;
    _nSegments = 0;
    _nItterations = 1;
    _hRestart = _hRunning = _hEngine = NULL;
    _dwThreadId = 0;
    _dLeft = -1;
    _dTop = 1;
    _dRight = 1;
    _dBottom = 1;
    _cRef = 0;
    _pfh = NULL;
    _fDirty = TRUE;
}

//+---------------------------------------------------------------------------
//
//  Member:     CRGen::~CRGen
//
//  Synopsis:   destructor
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

CRGen::~CRGen()
{
    if (_hRestart)
    {
        CloseHandle(_hRestart);
    }
    if (_hRunning)
    {
        CloseHandle(_hRunning);
    }
    if (_hEngine)
    {
        TerminateThread(_hEngine, 0);
        CloseHandle(_hEngine);
    }
}

#define LENGTH 50
#define ABOUT_STRING_LENGTH 256

//+---------------------------------------------------------------------------
//
//  Member:     CRGen::DialogProc
//
//  Synopsis:   Dialog proc for the Properties dialog box
//
//  Arguments:  [hwndDlg] - the dialog's window handle
//              [uMsg]    - message
//              [wParam]  - first message parameter
//              [lParam]  - second message parameter
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

BOOL CRGen::DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_INITDIALOG:
        {
            _pwinGeneratorWindow = new CGeneratorWindow;
            if (_pwinGeneratorWindow)
            {
                _pwinGeneratorWindow->InitializePoints(_rgRadius, _rgTheta, _nSegments);
                RECT rDim;
                rDim.left = 3;
                rDim.right = 100;
                rDim.top = 10;
                rDim.bottom = 100;
                MapDialogRect(hwndDlg, &rDim);
                _pwinGeneratorWindow->Create(
                    TEXT("RGen Generator"),
                    NULL,
                    WS_CHILD | WS_VISIBLE,
                    rDim.left,
                    rDim.top,
                    rDim.right,
                    rDim.bottom,
                    hwndDlg,
                    NULL,
                    ghinst);
            }
            SetDlgItemInt(hwndDlg, IDD_ITTERATIONS, _nItterations, FALSE);
        }
        return(TRUE);
    case WM_COMMAND:
        switch (LOWORD(wParam))
        {
        case IDOK:
            _nItterations = GetDlgItemInt(hwndDlg, IDD_ITTERATIONS, NULL, FALSE);
            if (_pwinGeneratorWindow)
            {
                _pwinGeneratorWindow->RetrievePoints(_rgRadius, _rgTheta, _nSegments);
            }
            _fDirty = TRUE;
            SetEvent(_hRestart);
            EndDialog(hwndDlg, TRUE);
            return(TRUE);
        case IDD_RESET:
            {
                SetDefaultSegmentList();
                if (_pwinGeneratorWindow)
                {
                    _pwinGeneratorWindow->InitializePoints(_rgRadius, _rgTheta, _nSegments);
                    _pwinGeneratorWindow->Redraw();
                }
                return(TRUE);
            }
        case IDD_ABOUT:
            {
                TCHAR szTitle[ABOUT_STRING_LENGTH];
                TCHAR szText[ABOUT_STRING_LENGTH];
                if (LoadString(ghinst, IDS_ABOUT_TITLE, szTitle, ABOUT_STRING_LENGTH))
                {
                    if (LoadString(ghinst, IDS_ABOUT_TEXT, szText, ABOUT_STRING_LENGTH))
                    {
                        MessageBox(
                            hwndDlg,
                            szText,
                            szTitle,
                            MB_OK | MB_ICONINFORMATION);
                    }
                }
            }
            return(TRUE);
        case IDCANCEL:
            EndDialog(hwndDlg, FALSE);
            return(TRUE);
        default:
            return(FALSE);
        }
    }
    return(FALSE);
}


FRCNGN.IDL

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       frcngn.idl
//
//  Contents:   definition for IFractalEngine
//
//  History:    4-13-94   stevebl   Created
//
//----------------------------------------------------------------------------

//+---------------------------------------------------------------------------
//
//  Class:      IFractalEngine (fe)
//
//  Purpose:    interface implemented by fractal engines
//
//  Interface:  Init          -- called first to initialize the engine
//              SetDefaults   -- tells engine to choose default values for
//                               all its properties
//              SetProperties -- invokes the engine's property dialog box
//              GetExtent     -- asks the engine what the max and min
//                               values in the graph are
//              SetExtent     -- asks the engine to change it's extent
//              SetGraphSize  -- tells the engine how big its graph is
//              Start         -- starts the graph engine
//              Stop          -- stops the graph engine
//
//  History:    4-13-94   stevebl   Created
//
//----------------------------------------------------------------------------

[
    local,
    object,
    uuid(2a0e4f58-3908-101b-8963-00000b65c75b),
    pointer_default(unique)
]

interface IFractalEngine : IUnknown
{
    import "unknwn.idl";
    import "frhost.idl";

    HRESULT Init(IFractalHost * pfh);
    HRESULT SetDefaults(void);
    HRESULT SetProperties(HWND hwnd);
    HRESULT GetExtent(
        double * pdLeft,
        double * pdTop,
        double * pdRight,
        double * pdBottom);
    HRESULT SetExtent(
        double dLeft,
        double dTop,
        double dRight,
        double dBottom);
    HRESULT SetGraphSize(
        unsigned uWidth,
        unsigned uHeight);
    HRESULT Start(void);
    HRESULT Stop(void);
}


FRHOST.IDL

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       frhost.idl
//
//  Contents:   definition for the IFractalHost interface
//
//  Classes:
//
//  Functions:
//
//  History:    4-13-94   stevebl   Created
//
//----------------------------------------------------------------------------

//+---------------------------------------------------------------------------
//
//  Class:      IFractalHost (fh)
//
//  Purpose:    interface implemented by host fractal app and used by fractal
//              engines
//
//  Interface:  SetGraphSize     -- asks host to change the graph size
//              GetGraphSize     -- asks host what its graph size is
//              GetPoint         -- gets the color at a given point
//              Rect             -- paints a rectangle
//              Line             -- draws a line between two points
//              Point            -- plots a point
//              DoneDrawingGraph -- notifies host app that the graph is done
//
//  History:    4-13-94   stevebl   Created
//              7-07-94   stevebl   modified definition of GetPoint
//
//----------------------------------------------------------------------------

[
    local,
    object,
    uuid(2a0e4ef4-3908-101b-8963-00000b65c75b),
    pointer_default(unique)
]

interface IFractalHost : IUnknown
{
    import "unknwn.idl";

    HRESULT SetGraphSize(unsigned uWidth, unsigned uHeight);
    HRESULT GetGraphSize(unsigned * puWidth, unsigned * puHeight);
    HRESULT GetPoint(unsigned * puColor, int x, int y);
    HRESULT Rect(
        int iLeft,
        int iTop,
        int iRight,
        int iBottom,
        unsigned uColor);
    HRESULT Line(
        int x1,
        int y1,
        int x2,
        int y2,
        unsigned uColor);
    HRESULT Point(
        int x,
        int y,
        unsigned uColor);
    HRESULT DoneDrawingGraph(void);
}


JULIACID.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       juliacid.cxx
//
//  Contents:   CLSID_JULIASET
//
//  History:    4-20-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include <windows.h>
#include <ole2.h>
#include "juliacid.h"

CLSID CLSID_JULIASET = { /* 1bf7cc8c-3efd-101b-b94a-00aa001f2ef4 */
    0x1bf7cc8c,
    0x3efd,
    0x101b,
    {0xb9, 0x4a, 0x00, 0xaa, 0x00, 0x1f, 0x2e, 0xf4}
  };


JULIACID.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       mandcid.h
//
//  Contents:   CLSID_JULIASET;
//
//  History:    4-20-94   stevebl   Created
//
//----------------------------------------------------------------------------

extern CLSID CLSID_JULIASET;


MANDCID.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       mandcid.cxx
//
//  Contents:   CLSID_MANDELBROT
//
//  History:    4-15-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include <windows.h>
#include <ole2.h>
#include "mandcid.h"

CLSID CLSID_MANDELBROT = { /* aeb94135-39c3-101b-b940-00aa001f2ef4 */
    0xaeb94135,
    0x39c3,
    0x101b,
    {0xb9, 0x40, 0x00, 0xaa, 0x00, 0x1f, 0x2e, 0xf4}
  };


MANDCID.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       mandcid.h
//
//  Contents:   CLSID_MANDELBROT;
//
//  History:    4-15-94   stevebl   Created
//
//----------------------------------------------------------------------------

extern CLSID CLSID_MANDELBROT;


PALSIZE.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       palsize.h
//
//  Contents:   Defines the size of MFract's color palette.
//              Color 0 is static.  The others may animate.
//
//  Classes:
//
//  Functions:
//
//  History:    4-30-94   stevebl   Created
//
//----------------------------------------------------------------------------

#ifndef PALETTESIZE
#define PALETTESIZE 236
#endif


PLASMCID.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       plasmcid.cxx
//
//  Contents:   CLSID_PLASMA
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include <windows.h>
#include <ole2.h>
#include "plasmcid.h"

CLSID CLSID_PLASMA = { /* d49bcab0-40d1-101b-b94f-00aa001f2ef4 */
    0xd49bcab0,
    0x40d1,
    0x101b,
    {0xb9, 0x4f, 0x00, 0xaa, 0x00, 0x1f, 0x2e, 0xf4}
  };


PLASMCID.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       plasmcid.h
//
//  Contents:   CLSID_PLASMA;
//
//  History:    4-23-94   stevebl   Created
//
//----------------------------------------------------------------------------

extern CLSID CLSID_PLASMA;


QUADCID.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       quadcid.cxx
//
//  Contents:   contains CLSID_QUADRANT
//
//  History:    4-15-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include <windows.h>
#include <ole2.h>
#include "quadcid.h"

CLSID CLSID_QUADRANTENGINE = { /* bf600080-3a8c-101b-b941-00aa001f2ef4 */
    0xbf600080,
    0x3a8c,
    0x101b,
    {0xb9, 0x41, 0x00, 0xaa, 0x00, 0x1f, 0x2e, 0xf4}
  };


QUADCID.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       quadcid.h
//
//  Contents:   defines CLSID_QUADRANTENGINE
//
//  History:    4-15-94   stevebl   Created
//
//----------------------------------------------------------------------------

extern CLSID CLSID_QUADRANTENGINE;


QUDCLN.IDL

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       qudcln.idl
//
//  Contents:   definition for IQuadrantClient
//
//  History:    4-13-94   stevebl   Created
//
//----------------------------------------------------------------------------

//+---------------------------------------------------------------------------
//
//  Class:      IQuadrantClient (qc)
//
//  Purpose:    interface implemented by fractal engines that wish to use the
//              Quadrant Fractal Engine Helper
//
//  Interface:  ComputePoint     -- asks the fractal engine which color should
//                                  be painted at a given point on the graph
//              DoneDrawingGraph -- notifies fractal engine that the graph
//                                  is done
//
//  History:    4-13-94   stevebl   Created
//              7-07-94   stevebl   Modified definition of ComputePoint
//
//----------------------------------------------------------------------------

[
    local,
    object,
    uuid(2a0e4fbc-3908-101b-8963-00000b65c75b),
    pointer_default(unique)
]

interface IQuadrantClient : IUnknown
{
    import "unknwn.idl";

    HRESULT ComputePoint(unsigned * puColor, int x, int y);
    HRESULT DoneDrawingGraph(void);
}


QUDNGN.IDL

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       qudngn.idl
//
//  Contents:   definition for IQuadrantEngine
//
//  History:    4-13-94   stevebl   Created
//
//----------------------------------------------------------------------------

//+---------------------------------------------------------------------------
//
//  Class:      IQuadrantEngine (qe)
//
//  Purpose:    interface used to communicate to the Quadrant Fractal Engine
//              Helper
//
//  Interface:  Init             -- initializes the object (call first)
//              UseBoundingBoxes -- enables/disables bounding box optimization
//              SetGraphSize     -- tells the engine how big the graph is
//              Start            -- starts the engine
//              Stop             -- stops the engine
//              Reset            -- tells the engine restart drawing the graph
//
//  History:    4-13-94   stevebl   Created
//
//----------------------------------------------------------------------------

[
    local,
    object,
    uuid(2a0e4fbd-3908-101b-8963-00000b65c75b),
    pointer_default(unique)
]

interface IQuadrantEngine : IUnknown
{
    import "qudcln.idl";
    import "frhost.idl";

    HRESULT Init(IQuadrantClient * pqc, IFractalHost * pfh);
    HRESULT UseBoundingBoxes(BOOL fFlag);
    HRESULT SetGraphSize(
        unsigned uWidth,
        unsigned uHeight);
    HRESULT Start(void);
    HRESULT Stop(void);
    HRESULT Reset(void);
}


RGENCID.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       rgencid.cxx
//
//  Contents:   CLSID_RGEN
//
//  History:    5-03-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include <windows.h>
#include <ole2.h>
#include "plasmcid.h"

CLSID CLSID_RGEN = { /* 7970b884-48f7-101b-b967-00aa001f2ef4 */
    0x7970b884,
    0x48f7,
    0x101b,
    {0xb9, 0x67, 0x00, 0xaa, 0x00, 0x1f, 0x2e, 0xf4}
  };


RGENCID.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       rgencid.h
//
//  Contents:   CLSID_RGEN
//
//  History:    5-03-94   stevebl   Created
//
//----------------------------------------------------------------------------

extern CLSID CLSID_RGEN;


README.TXT

CALLAS

The CALLAS program uses the call_as attribute to transmit a non-remoteable,
OLE custom interface. The distributed application provides a class factory
to manufacture a server object and the implementation of the callas server
object.

The directory samples\rpc\object\callas contains the following files for
building the distributed application CALLAS:

File          Description

README.TXT    Readme file for the CALLAS sample
CALLAS.IDL    Interface definition language file
CALLAS.ACF    Attribute configuration file
CALLAS.DEF    Module definitions file
CALL_AS.C     Glue routines for proxy DLL
CLIENT.C      Client application
SERVER.CXX    Server application
MAKEFILE      Nmake file to build for Windows NT or Windows 95


CALLAS.ACF

/* file: callas.acf */
interface IHelloCallAs
{
    // This is the directive that says to use the local "HelloProc"
    // in the application view of the RemoteHelloProc
    //
[call_as (HelloProc)]
    RemoteHelloProc();

}


CALLAS.IDL

/* file: callas.idl */
[
object,
uuid(a46deb30-9f33-11cd-b23f-00aa00339cce)
]
interface IHelloCallAs : IUnknown
{
    // pull in the definitions for all the standard interfaces
import "unknwn.idl";

    typedef     int     MyBoolean;

    // this is the local appearance of the HelloProc; it is the
    // view that the client and server applications see.  This
    // routine can have any signature (including "...") as long as
    // it can map to the corresponding remoted version.
[local] MyBoolean HelloProc([in, string] char *pszString);

    // this is the remoted appearance of the HelloProc; it is the
    // wire contract remoting the interface, and must obey all the
    // regular rules about remotable custom interfaces.
    //
    // the ACF file specifies "[call_as( HelloProc )]"

    HRESULT RemoteHelloProc([in, string] char *pszString);

}


CLIENT.C

/****************************************************************************
                       Microsoft RPC
                Copyright Microsoft Corp. 1992-1996
                  object call_as Example

    FILE:       client.c

    USAGE:      client

    PURPOSE:    Client side application of remoted custom interface

    FUNCTIONS:  main() - binds to server and calls remote procedure

    COMMENTS:   This version of the Ole2 distributed application that
                prints "hello, world" (or other string) on the server
                features a non-rpcable custom interface being remoted with
                [call_as].

****************************************************************************/

#define INC_OLE2
#include <windows.h>
#include <stdio.h>
#include <assert.h>
#include "callas.h"     // generated by MIDL from callas.idl/.acf

// the class ID of the server exe
const
CLSID CLSID_CallAs =
    {0xf9246030,0x9f33,0x11cd,{0xb2,0x3f,0x00,0xaa,0x00,0x33,0x9c,0xce}};

/***********************************************************************

    set up the following registry keys:

    \Registry\Machine\Software\Classes
        CLSID
            {f9246030-9f33-11cd-b23f-00aa00339cce}
                LocalServer32
                    = server.exe
            {a46deb30-9f33-11cd-b23f-00aa00339cce}
                InProcServer32
                    = callas.dll
        Interface
            {a46deb30-9f33-11cd-b23f-00aa00339cce}
                ProxyStubClsid32
                    = {a46deb30-9f33-11cd-b23f-00aa00339cce}

    for the server, the LocalServer32 is the out-of-process server exe
    for the proxy, the InProcServer32 is the proxy dll
    for the interface, the ProxyStubClsid32 is the class id of the
        proxy class factory.

***********************************************************************/

void InitializeRegistry(char *pszProxyDll, char *pszServer)
{
    // the server exe CLSID
    const char *pszServerKey = "{f9246030-9f33-11cd-b23f-00aa00339cce}";

    // the interface UUID
    const char *pszInterfaceKey = "{a46deb30-9f33-11cd-b23f-00aa00339cce}";

    // the class factory CLSID ( the same as the interface UUID )
    const char *pszProxyDllKey = "{a46deb30-9f33-11cd-b23f-00aa00339cce}";

    LONG error          = 0;
    HKEY hKeyInterface;     // key for ...\Classes\Interface
    HKEY hKeyCLSID;         // key for ...\Classes\CLSID
    HKEY hKey;              // current key
    DWORD dwDisposition;

    //Register the interfaces.

    //create ...\Classes\Interface key
    error = RegCreateKeyEx(
        HKEY_CLASSES_ROOT,
        "Interface",
        0,
        "REG_SZ",
        REG_OPTION_NON_VOLATILE,
        KEY_ALL_ACCESS,
        0,
        &hKeyInterface,
        &dwDisposition);

    // create the ...\Classes\Interface\{ <interface uuid> }  key
    error = RegCreateKeyEx(
        hKeyInterface,
        pszInterfaceKey,
        0,
        "REG_SZ",
        REG_OPTION_NON_VOLATILE,
        KEY_ALL_ACCESS,
        0,
        &hKey,
        &dwDisposition);

    //create ProxyStubClsid32 key under the interface
    error = RegCreateKeyEx(
        hKey,
        "ProxyStubClsid32",
        0,
        "REG_SZ",
        REG_OPTION_NON_VOLATILE,
        KEY_ALL_ACCESS,
        0,
        &hKey,
        &dwDisposition);

    //Set the class id for the PSFactoryBuffer for the interface
    error = RegSetValueEx(
        hKey,
        "",
        0,
        REG_SZ,
        pszProxyDllKey,
        strlen(pszProxyDllKey) + 1);

    //Register the classes.

    //create the CLSID key
    error = RegCreateKeyEx(
        HKEY_CLASSES_ROOT,
        "CLSID",
        0,
        "REG_SZ",
        REG_OPTION_NON_VOLATILE,
        KEY_ALL_ACCESS,
        0,
        &hKeyCLSID,
        &dwDisposition);

    //create key for the server
    error = RegCreateKeyEx(
        hKeyCLSID,
        pszServerKey,
        0,
        "REG_SZ",
        REG_OPTION_NON_VOLATILE,
        KEY_ALL_ACCESS,
        0,
        &hKey,
        &dwDisposition);

    //create LocalServer32 key.
    error = RegCreateKeyEx(
        hKey,
        "LocalServer32",
        0,
        "REG_SZ",
        REG_OPTION_NON_VOLATILE,
        KEY_ALL_ACCESS,
        0,
        &hKey,
        &dwDisposition);

    //Set the server name.
    error = RegSetValueEx(
        hKey,
        "",
        0,
        REG_SZ,
        pszServer,
        strlen(pszServer) + 1);

    error = RegFlushKey(
        hKey );

    //create key for the proxy dll
    error = RegCreateKeyEx(
        hKeyCLSID,
        pszProxyDllKey,
        0,
        "REG_SZ",
        REG_OPTION_NON_VOLATILE,
        KEY_ALL_ACCESS,
        0,
        &hKey,
        &dwDisposition);

    //create InProcServer32 key for the proxy dll
    error = RegCreateKeyEx(
        hKey,
        "InProcServer32",
        0,
        "REG_SZ",
        REG_OPTION_NON_VOLATILE,
        KEY_ALL_ACCESS,
        0,
        &hKey,
        &dwDisposition);

    //register the proxy DLL
    error = RegSetValueEx(
        hKey,
        "",
        0,
        REG_SZ,
        pszProxyDll,
        strlen(pszProxyDll) + 1);

    error = RegFlushKey(
        hKey );

    //  printf("waiting for registry to update OLE\n");
    Sleep(1000);
}

void Test()
{
    IClassFactory *pClassFactory =0;
    IUnknown *punk = 0;
    IHelloCallAs *pCallAs = 0;
    HRESULT hr;

    hr = CoInitialize(NULL);
    if (FAILED(hr))
    {
        printf("CoInitialize failed with %lx\n", hr);
        return;
    }

    hr = CoGetClassObject(&CLSID_CallAs, CLSCTX_LOCAL_SERVER, 0, &IID_IClassFactory, &pClassFactory);
    if(FAILED(hr))
        printf("CoGetClassObject failed with %lx\n", hr);
    else
    {
        assert(pClassFactory);
        hr = pClassFactory->lpVtbl->CreateInstance(pClassFactory, 0, &IID_IUnknown, &punk);
        if(FAILED(hr))
            printf("IClassFactory::CreateInstance failed with %lx\n", hr);
        else
        {
            assert(punk);
            hr = punk->lpVtbl->QueryInterface(punk, &IID_IHelloCallAs, &pCallAs);
            if(FAILED(hr))
                printf("IUnknown::QueryInterface failed with %lx\n", hr);
            else
            {
                assert(pCallAs);
                hr = pCallAs->lpVtbl->HelloProc(pCallAs, "Hello World!");
                if(FAILED(hr))
                    printf("IHelloCallAs::HelloProc failed with %lx\n", hr);
                else
                    printf("IHelloCallAs::HelloProc succeeded");

                pCallAs->lpVtbl->Release(pCallAs);
                pCallAs = 0;
            }
            punk->lpVtbl->Release(punk);
            punk = 0;
        }
        pClassFactory->lpVtbl->Release(pClassFactory);
        pClassFactory = 0;
    }
    CoUninitialize();
}

void TestDll()
{
    // the dll name of the proxy dll
    const char *pszProxyDllName = "callas.dll";

    char szDrive[_MAX_DRIVE];
    char szDirectory[_MAX_DIR];
    char szFileName[_MAX_FNAME];

    char szClient[_MAX_PATH];
    char szProxyDll[_MAX_PATH];
    char szServer[_MAX_PATH];

    GetModuleFileName(0, szClient, _MAX_PATH);
    _splitpath(szClient, szDrive, szDirectory, szFileName, NULL);

    //Get the full path name of the server.
    sprintf(szServer, "%s%sserver.exe", szDrive, szDirectory);

    //Get the full path name of the proxy dll.
    sprintf(szProxyDll, "%s%s%s", szDrive, szDirectory, pszProxyDllName);

    printf("Testing Dll\n");
    InitializeRegistry(szProxyDll, szServer);

    Test();
}

void __cdecl main(int argc, char *argv[])
{
    TestDll();
}


CALL_AS.C

/****************************************************************************
                       Microsoft RPC
                Copyright Microsoft Corp. 1992-1996
                  object call_as Example

    FILE:       call_as.c

    PURPOSE:    [call_as] Glue routines for proxy DLL

    FUNCTIONS:  IHelloCallAs_HelloProc_Proxy
                IHelloCallAs_HelloProc_Stub

    COMMENTS:   This version of the Ole2 distributed application that
                prints "hello, world" (or other string) on the server
                features a non-rpcable custom interface being remoted with
                [call_as].

****************************************************************************/

#include <stdio.h>
#include "rpcproxy.h"
#define INC_OLE2
#include <windows.h>
#include "callas.h" // this is the header file generated by midl

// this file provides the conversion routines between the remoted
// interface and the local view of the interface.  This allows an interface
// with apparently non-remotable methods to be remoted.  The conversions
// may be similar to transmit_as or represent_as conversions, or they
// can be more dramatic, such as synthesizing additional parameters, etc.
// in this case, the return type is changed

// The IHelloCallAs_HelloProc_Proxy routine goes into the vtable for
// the clients IHelloCallAs objects, so the client calls this first
// when they invoke the HelloProc method on an IHelloCallAs object.

// The IHelloCallAs_HelloProc_Stub routine is called by the server
// side stub when the remoted call comes in.  It does any conversions
// needed and then invokes the real HelloProc method on the servers
// IHelloCallAs object implementation.

// both of these must be provided in the proxy DLL, because the
// SAME proxy dll is used for both the client and server sides.

// this routine goes into the client side "proxy" object for the server.
// it has the parameter list of the non-remoted (local) flavor of the
// call, and converts it to the remoted flavor of the call.  It can also
// do special case memory management, have debugging hooks, etc.

MyBoolean
STDMETHODCALLTYPE
IHelloCallAs_HelloProc_Proxy (
     IHelloCallAs        * pThis,
     unsigned char * pszString
    )
{
    HRESULT     rc;

    printf("Proxy: %s\n", pszString);
    rc = IHelloCallAs_RemoteHelloProc_Proxy(pThis, pszString);

    return FAILED(rc);
}

// this routine is directly called by the server-side stub code.
// it is called with the remoted param list, and must convert to the
// local flavor.  As with the proxy side routine, this one can also
// do special case memory management, have debugging hooks, etc.

HRESULT
RPC_ENTRY
IHelloCallAs_HelloProc_Stub (
     IHelloCallAs        * pThis,
     unsigned char * pszString
     )
{
    MyBoolean   rc;

    rc = pThis->lpVtbl->HelloProc(pThis, pszString);
    return rc ? S_OK : E_FAIL;
}


SERVER.CXX

/****************************************************************************
                       Microsoft RPC
                Copyright Microsoft Corp. 1992-1996
                  object call_as Example

    FILE:       server.cxx

    USAGE:      server  - note that this is auto-loaded by the client
                          requesting this server, and exits at completion

    PURPOSE:    Server side of RPC distributed application

    FUNCTIONS:  main() - binds to server and calls remote procedure

    COMMENTS:   This version of the Ole2 distributed application that
                prints "hello, world" (or other string) on the server
                features a non-rpcable custom interface being remoted with
                [call_as].

                It provides a class factory to manufacture a server object,
                and the implementation of the callas server object

****************************************************************************/

#define INC_OLE2
#include <windows.h>
#include <stdio.h>
#include <assert.h>
#include "callas.h"

unsigned long   ObjectCount         = 0;
BOOL            fClassRegistered    = FALSE;
DWORD           dwHandle            = 0;

// this is the CLSID for the class factory for the server

const CLSID CLSID_CallAs
    = {0xf9246030,0x9f33,0x11cd,{0xb2,0x3f,0x00,0xaa,0x00,0x33,0x9c,0xce}};

IClassFactory *GetCallAsFactory();

// here are some convenient inline functions to reduce the casting involved
// with having the RefCount be unsigned instead of signed, and pass by
// reference

inline
unsigned long
IncrementRefCount( unsigned long & RefCount )
    {
    return InterlockedIncrement( (long *) &RefCount );
    }

inline
unsigned long
DecrementRefCount( unsigned long & RefCount )
    {
    return InterlockedDecrement( (long *) &RefCount );
    }

// this is the class factory for the CCallAs server
// it manufactures CCallAs server objects in response to a CreateInstance
// call

class CCallAsFactory : public IClassFactory

{
public:
    // note that the virtual methods must exactly match the set for
    // the IClassFactory in the idl (and callas.h)
    unsigned long    RefCount;

                            CCallAsFactory()
                                {
                                RefCount    = 0;
                                }

        HRESULT STDMETHODCALLTYPE QueryInterface(
                REFIID iid,
            void **ppv);

        ULONG STDMETHODCALLTYPE AddRef();

        ULONG STDMETHODCALLTYPE Release();

        HRESULT STDMETHODCALLTYPE CreateInstance(
            IUnknown *punkOuter,
            REFIID riid,
            void **ppv);

   HRESULT STDMETHODCALLTYPE LockServer(
        BOOL fLock );
};

// allocate a static classfactory to avoid construction races if
// we were multithreaded.
CCallAsFactory       gCallAsClassFactory;

class CCallAs   : public IHelloCallAs

{

    unsigned long    RefCount;

public:

    // remoted functions (virtual)

        HRESULT STDMETHODCALLTYPE QueryInterface(
                REFIID iid,
            void **ppv);

        ULONG STDMETHODCALLTYPE AddRef();

        ULONG STDMETHODCALLTYPE Release();

        MyBoolean __stdcall HelloProc(
            unsigned char *pszString);

        // local functions
        // constructor
                            CCallAs()
                                {
                                RefCount    = 0;
                                }

    void    *   operator new( size_t s )
                                {
                                return CoTaskMemAlloc( s );
                                }

    void        operator delete( void * pv )
                                {
                                CoTaskMemFree( pv );
                                }

};

//+---------------------------------------------------------------------------
//
//  Function:   main
//
//  Synopsis:
//
//  Arguments:
//
//  Returns:    void
//
//  Modifies:   nothing
//
//  Notes:      none
//
//----------------------------------------------------------------------------

void _cdecl main()
{
    HRESULT hr;
    MSG msg;
        IClassFactory *pClassFactory;

    //
    // Initialize OLE before calling any other OLE functions.
    //

    hr = CoInitialize(NULL);

    if( FAILED(hr))
    {
            printf("Server: CoInitialize failed(%x)\n",hr);
        return;
    }

        pClassFactory = GetCallAsFactory();

        hr = CoRegisterClassObject(CLSID_CallAs,
                                               (IUnknown *) pClassFactory,
                                               CLSCTX_LOCAL_SERVER,
                                               REGCLS_SINGLEUSE,
                                               &dwHandle);

        pClassFactory->Release();

        if (FAILED(hr))
            printf("Server: CoRegisterClassObject failed %x\n", hr);
        else
        {
                fClassRegistered = TRUE;

            // message loop
        while (GetMessage(&msg, NULL, 0, 0))
        {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
        }
        }
    CoUninitialize();

    return;
}

//+-------------------------------------------------------------------------
//
//  Method:     GetCallAsFactory
//
//  Synopsis:   Get an IClassFactory * for the CallAs class.
//
//--------------------------------------------------------------------------
IClassFactory *GetCallAsFactory()
{
    // increment the file object count the first time it is created
        if(gCallAsClassFactory.RefCount == 0)
        {
                IncrementRefCount(gCallAsClassFactory.RefCount);
                IncrementRefCount(ObjectCount);
        }
        else
        {
                IncrementRefCount(gCallAsClassFactory.RefCount);
        }
        return &gCallAsClassFactory;
}

//+-------------------------------------------------------------------------
//
//  Method:     CCallAsFactory::QueryInterface, public
//
//  Synopsis:   Query for an interface on the class factory.
//
//  Derivation: IUnknown
//
//--------------------------------------------------------------------------
HRESULT STDMETHODCALLTYPE
CCallAsFactory::QueryInterface (
    REFIID iid,
    void **ppv )
{
    HRESULT hr = E_NOINTERFACE;

        assert(ppv);

    *ppv = 0;
    if ( IsEqualGUID( iid, IID_IUnknown) ||
         IsEqualGUID( iid, IID_IClassFactory ) )
    {
        *ppv = this;
                AddRef();
            hr = S_OK;
    }

    return hr;
}
//+-------------------------------------------------------------------------
//
//  Method:     CCallAsFactory::AddRef, public
//
//  Synopsis:   Increment DLL reference counts
//
//  Derivation: IUnknown
//
//      Notes: We have a single instance of the CCallAsFactory.
//
//--------------------------------------------------------------------------
ULONG STDMETHODCALLTYPE
CCallAsFactory::AddRef()
{
    IncrementRefCount(RefCount);
        return RefCount;
}

//+-------------------------------------------------------------------------
//
//  Method:     CCallAsFactory::Release, public
//
//  Synopsis:   Decrement DLL reference count
//
//  Derivation: IUnknown
//
//--------------------------------------------------------------------------
ULONG STDMETHODCALLTYPE
CCallAsFactory::Release()
{
        long t;
        unsigned long count;

    t = DecrementRefCount(RefCount);

        if(t == 0)
        {
                count = 0;
                //decrement the object count
                if(DecrementRefCount(ObjectCount) == 0)
                {
                        //The last object has been destroyed.
                        PostQuitMessage(0);
                }
        }
        else
                count = RefCount;

    return count;
}

//+-------------------------------------------------------------------------
//
//  Method:     CCallAsFactory::CreateInstance, public
//
//  Synopsis:   Create an instance of CCallAs.
//
//--------------------------------------------------------------------------
HRESULT STDMETHODCALLTYPE
CCallAsFactory::CreateInstance
(
    IUnknown *punkOuter,
    REFIID riid,
    void **ppv
)
{
    HRESULT hr = E_OUTOFMEMORY;
        CCallAs *pCallAs;

        assert(ppv);

        *ppv = 0;

        if(fClassRegistered == TRUE)
        {
                fClassRegistered = FALSE;
                CoRevokeClassObject(dwHandle);
        }

        pCallAs = new CCallAs();
        if(pCallAs)
        {
            // invoke the constructor
                *pCallAs = CCallAs();

                //increment the object count.
                //The object count will keep this process alive until all
                //objects are released.
                IncrementRefCount(ObjectCount);
                hr = pCallAs->QueryInterface( riid, ppv);
        }

    return hr;
}

//+-------------------------------------------------------------------------
//
//  Method:     CCallAsFactory::LockServer, public
//
//  Synopsis:   Lock the server in memory (by adding an extra reference)
//
//--------------------------------------------------------------------------
HRESULT STDMETHODCALLTYPE
CCallAsFactory::LockServer(
        BOOL fLock )
{
    if ( fLock )
            IncrementRefCount( ObjectCount );
        else
            DecrementRefCount( ObjectCount );

        return S_OK;

}

//+-------------------------------------------------------------------------
//
//  Method:     CCallAs::QueryInterface, public
//
//  Synopsis:   Query for an interface on the class factory.
//
//  Derivation: IUnknown
//
//--------------------------------------------------------------------------
HRESULT STDMETHODCALLTYPE
CCallAs::QueryInterface (
    REFIID iid,
    void **ppv )
{
    HRESULT hr = E_NOINTERFACE;

        assert(ppv);

    *ppv = 0;
    if ( IsEqualGUID( iid, IID_IUnknown ) ||
         IsEqualGUID( iid, IID_IHelloCallAs ) )
    {
        *ppv = this;
                AddRef();
            hr = S_OK;
    }

    return hr;
}
//+-------------------------------------------------------------------------
//
//  Method:     CCallAs::AddRef, public
//
//  Synopsis:   Increment reference count
//
//  Derivation: IUnknown
//
//--------------------------------------------------------------------------
ULONG STDMETHODCALLTYPE
CCallAs::AddRef()
{
    IncrementRefCount(RefCount);
        return RefCount;
}

//+-------------------------------------------------------------------------
//
//  Method:     CCallAs::Release, public
//
//  Synopsis:   Decrement DLL reference count
//
//  Derivation: IUnknown
//
//--------------------------------------------------------------------------
ULONG STDMETHODCALLTYPE
CCallAs::Release()
{
        long t;
        unsigned long count;

    t = DecrementRefCount(RefCount);

        if(t == 0)
        {
                count = 0;

                //decrement the object count
                if(DecrementRefCount(ObjectCount) == 0)
                {
                        //The last object has been destroyed.
                        PostQuitMessage(0);
                }
                delete this;
        }
        else
                count = RefCount;

    return count;
}

//+---------------------------------------------------------------------------
//
//  Function:   CCallAs::HelloProc()
//
//  Synopsis:   Displays the specified string
//
//  Arguments:  void
//
//  Returns:    Success.
//
//  Modifies:   nothing
//
//  Notes:      none
//
//----------------------------------------------------------------------------
MyBoolean __stdcall
CCallAs::HelloProc(unsigned char *pszString)
{
    printf("HelloProc: %s \n", pszString);
    printf(" <pausing for 5 sec>\n");
    // pretend to be busy computing for a bit
    Sleep( 5000 ); // 5000 msec = 5 sec

    return TRUE;
}


README.TXT

OHELLO

The OHELLO program demonstrates OLE custom interfaces and proper reference
counting and shutdown behavior for a multiple-use local server. The program
also shows how to use the Win32 registry functions to install a local server
and a proxy DLL in the registry.

FILES
=====

File          Description

README.TXT    Readme file for the AUTO sample
OHELLO.IDL    Interface definition language file
OHELLO.DEF    Module definition file
CLIENT.CXX    Client main program
SERVER.CXX    Server main program
CHELLO.CXX    Implements the CHello object and class factory
ERROR.CXX     Demonstrates how to get error code message text from
              system resources
MAKEFILE      Nmake file to build for Windows NT or Windows 95


CLIENT.CXX

/*+----------------------------------------------------------------------------

Microsoft Windows Sample Program
Copyright 1994 - 1998 Microsoft Corporation.  All rights reserved.

FILE:       client.cxx

USAGE:      client

PURPOSE:    OLE client application uses a custom interface to call the
            server and print "Hello".

FUNCTIONS:  main() - Main entry point for the client application.

COMMENTS:   The server program must be installed before running the client.
            Run server.exe /REGSERVER to install the server program.

-----------------------------------------------------------------------------*/
#include <windows.h>
#include <stdio.h>
#include "ohello.h"     // generated by MIDL from ohello.idl

// the class ID of the server exe
const CLSID CLSID_OHello =
    {0xf9246031,0x9f33,0x11cd,{0xb2,0x3f,0x00,0xaa,0x00,0x33,0x9c,0xce}};

void PrintErrorMessage(HRESULT hr);

//+---------------------------------------------------------------------------
//
//  Function:   main
//
//  Synopsis:   Main entry point for the client application.  First we
//              call CoCreateInstance to create an instance of the server
//              object.  Next, we use the IHello custom interface to
//              call the server object and print "Hello".  After waiting
//              5 seconds, we call the server object to print "Goodbye".
//              Finally, we release the IHello interface pointer and
//              terminate.
//
//----------------------------------------------------------------------------
void __cdecl main(int argc, char *argv[])
{
    HRESULT hr;
    IHello *pHello = 0;

    hr = CoInitialize(NULL);

    if(SUCCEEDED(hr))
    {
        hr = CoCreateInstance(CLSID_OHello, 0, CLSCTX_LOCAL_SERVER, IID_IHello, (void **) &pHello);
        if(SUCCEEDED(hr))
        {
            hr = pHello->HelloProc((unsigned char *) "Hello");

            if(SUCCEEDED(hr))
            {
                printf("Successfully printed Hello.\n");
            }
            else
            {
                printf("IHello::HelloProc failed.\n");
                PrintErrorMessage(hr);
            }

            printf(" <pausing for 5 sec>\n");
            Sleep( 5000 ); // 5000 msec = 5 sec

            hr = pHello->HelloProc((unsigned char *) "Goodbye");

            if(SUCCEEDED(hr))
            {
                printf("Successfully printed Goodbye.\n");
            }
            else
            {
                printf("IHello::HelloProc failed.\n");
                PrintErrorMessage(hr);
            }

            pHello->Release();
        }
        else
        {
            printf("CoCreateInstance failed.\n");
            PrintErrorMessage(hr);

            if(hr == REGDB_E_CLASSNOTREG)
                printf("Run server.exe /REGSERVER to install the server program.\n");
        }

        CoUninitialize();
    }
    else
    {
        printf("CoInitialize failed.\n");
        PrintErrorMessage(hr);
    }
}


ERROR.CXX

/*+----------------------------------------------------------------------------

Microsoft Windows Sample Program
Copyright 1994 - 1998 Microsoft Corporation.  All rights reserved.

FILE:       error.cxx

PURPOSE:    Demonstrates how to get the message text for an error code from
            the system resources.

FUNCTIONS:  PrintErrorMessage - Prints the message text for an error code.

-----------------------------------------------------------------------------*/
#include <windows.h>
#include <stdio.h>

//+---------------------------------------------------------------------------
//
//  Function:   PrintErrorMessage
//
//  Synopsis:   Prints the message text for the specified error code.
//
//  Notes:      PrintErrorMessage uses FormatMessage to get the message
//              text from the system resources.
//
//----------------------------------------------------------------------------
void PrintErrorMessage(HRESULT hr)
{
  DWORD cMsgLen;
  char *pszErrorMessage = 0; /* buffer for message text from system */

  /* get the text description for that error number from the system */
  cMsgLen = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
                          0,
                          (DWORD) hr,
                          MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
                          (LPTSTR) &pszErrorMessage,
                          512,
                          0);

    if(cMsgLen > 0)
    {
        printf("Error %x: %s\n", hr, pszErrorMessage);
        LocalFree((HLOCAL) pszErrorMessage);
    }
    else
    {
        printf("Error %x\n", hr);
    }
}


OHELLO.IDL

/* file: OHello.idl */
[
object,
uuid(a46deb31-9f33-11cd-b23f-00aa00339cce)
]
interface IHello : IUnknown
{
    // pull in the definitions for all the standard interfaces
import "unknwn.idl";

    HRESULT HelloProc([in, string] char *pszString);

}


CHELLO.CXX

/*+----------------------------------------------------------------------------

Microsoft Windows Sample Program
Copyright 1994 - 1998 Microsoft Corporation.  All rights reserved.

FILE:       CHello.cxx

PURPOSE:    Implements the CHello object and class factory.

CLASSES:    CHello
            CHelloClassFactory

FUNCTIONS:  RegisterClassFactory
            RevokeClassFactory

-----------------------------------------------------------------------------*/
#include <windows.h>
#include <stdio.h>
#include "ohello.h"

void DecrementLockCount();
void IncrementLockCount();
void ObjectCreated();
void ObjectDestroyed();

extern "C" const CLSID CLSID_OHello;

long    g_fClassRegistered  = FALSE;
DWORD   g_dwRegister        = 0;

//The CHello class implements the server object.
class CHello : public IHello
{
private:
    unsigned long    m_cRef;

    //destructor
    ~CHello()
    {
        DecrementLockCount();
        ObjectDestroyed();
    }

public:
    //constructor
    CHello()
    {
        m_cRef = 1;
        ObjectCreated();
        IncrementLockCount();
    }

HRESULT STDMETHODCALLTYPE QueryInterface(
REFIID iid,
    void **ppv);

ULONG STDMETHODCALLTYPE AddRef();

ULONG STDMETHODCALLTYPE Release();

HRESULT STDMETHODCALLTYPE HelloProc(unsigned char *pszString);
};

//+-------------------------------------------------------------------------
//
//  Method:     CHello::AddRef, public
//
//  Synopsis:   Increment reference count
//
//  See Also:   IUnknown::AddRef
//
//--------------------------------------------------------------------------
ULONG STDMETHODCALLTYPE
CHello::AddRef()
{
    InterlockedIncrement((long *) &m_cRef);
    return m_cRef;
}

//+---------------------------------------------------------------------------
//
//  Method:     CHello::HelloProc()
//
//  Synopsis:   Displays the specified string.
//
//  See Also:   IHello::HelloProc
//
//----------------------------------------------------------------------------
HRESULT STDMETHODCALLTYPE
CHello::HelloProc(unsigned char *pszString)
{
    printf("HelloProc: %s \n", pszString);
    return S_OK;
}

//+-------------------------------------------------------------------------
//
//  Method:     CHello::QueryInterface, public
//
//  Synopsis:   Query for an interface on the class factory.
//
//  See Also:   IUnknown:QueryInterface
//
//--------------------------------------------------------------------------
HRESULT STDMETHODCALLTYPE
CHello::QueryInterface (
    REFIID iid,
    void **ppv )
{
    HRESULT hr;

    if ( IsEqualGUID( iid, IID_IUnknown ) ||
         IsEqualGUID( iid, IID_IHello ) )
    {
        *ppv = (IHello *) this;
        AddRef();
    hr = S_OK;
    }
    else
    {
        *ppv = 0;
        hr = E_NOINTERFACE;
    }

    return hr;
}

//+-------------------------------------------------------------------------
//
//  Method:     CHello::Release, public
//
//  Synopsis:   Decrement DLL reference count
//
//  Notes:      After the m_cRef is decremented, the object may be
//              deleted by another thread.  In order to make this code safe
//              for multiple threads, we have to access the object state
//              before decrementing m_cRef.
//
//  See Also:   IUnknown::Release.
//
//--------------------------------------------------------------------------
ULONG STDMETHODCALLTYPE
CHello::Release()
{
    unsigned long count;

    count = m_cRef - 1;

    if(InterlockedDecrement((long *) &m_cRef) == 0)
    {
        count = 0;
        delete this;
    }

    return count;
}

// this is the class factory for the CHello server object.
// it manufactures CHello server objects in response to a CreateInstance
// call

class CHelloClassFactory : public IClassFactory
{
private:
    unsigned long    m_cRef;

    //destructor
    ~CHelloClassFactory()
    {
        ObjectDestroyed();
    }

public:
    //constructor
    CHelloClassFactory()
    {
        m_cRef = 1;
        ObjectCreated();
    }

HRESULT STDMETHODCALLTYPE QueryInterface(
REFIID iid,
    void **ppv);

ULONG STDMETHODCALLTYPE AddRef();

ULONG STDMETHODCALLTYPE Release();

HRESULT STDMETHODCALLTYPE CreateInstance(
    IUnknown *punkOuter,
    REFIID riid,
    void **ppv);

   HRESULT STDMETHODCALLTYPE LockServer(
        BOOL fLock );

};

//+-------------------------------------------------------------------------
//
//  Method:     CHelloClassFactory::AddRef, public
//
//  Synopsis:   Increment DLL reference counts
//
//  See Also:   IUnknown::AddRef
//
//--------------------------------------------------------------------------
ULONG STDMETHODCALLTYPE
CHelloClassFactory::AddRef()
{
    InterlockedIncrement((long *) &m_cRef);
    return m_cRef;
}

//+-------------------------------------------------------------------------
//
//  Method:     CHelloClassFactory::CreateInstance, public
//
//  Synopsis:   Create an instance of CHello.
//
//  See Also:   IClassFactory::CreateInstance
//
//--------------------------------------------------------------------------
HRESULT STDMETHODCALLTYPE
CHelloClassFactory::CreateInstance
(
    IUnknown *punkOuter,
    REFIID riid,
    void **ppv
)
{
    HRESULT hr;
    CHello *pHello;

    if(punkOuter != 0)
    {
        //The CHello class doesn't support aggregation.
        return CLASS_E_NOAGGREGATION;
    }

    pHello = new CHello();
    if(pHello != 0)
    {
        hr = pHello->QueryInterface(riid, ppv);
        pHello->Release();
    }
    else
    {
        hr = E_OUTOFMEMORY;
        *ppv = 0;
    }

    return hr;
}

//+-------------------------------------------------------------------------
//
//  Method:     CHelloClassFactory::LockServer, public
//
//  Synopsis:   Lock the server in memory (by adding an extra reference)
//
//  Notes:      The class factory will be revoked when the lock count
//              is decremented to zero.  LockServer(TRUE) will increment the
//              lock count and ensure that the class factory will
//              not be revoked.
//
//  See Also:   IClassFactory::LockServer
//
//--------------------------------------------------------------------------
HRESULT STDMETHODCALLTYPE
CHelloClassFactory::LockServer(
    BOOL fLock )
{
    if (fLock == TRUE)
        IncrementLockCount();
    else
        DecrementLockCount();

    return S_OK;
}

//+-------------------------------------------------------------------------
//
//  Method:     CHelloClassFactory::QueryInterface, public
//
//  Synopsis:   Query for an interface on the class factory.
//
//  See Also:   IUnknown::QueryInterface
//
//--------------------------------------------------------------------------
HRESULT STDMETHODCALLTYPE
CHelloClassFactory::QueryInterface (
    REFIID iid,
    void **ppv )
{
    HRESULT hr;

    if ( IsEqualGUID( iid, IID_IUnknown) ||
         IsEqualGUID( iid, IID_IClassFactory ) )
    {
        *ppv = this;
        AddRef();
        hr = S_OK;
    }
    else
    {
        *ppv = 0;
        hr = E_NOINTERFACE;
    }

    return hr;
}
//+-------------------------------------------------------------------------
//
//  Method:     CHelloClassFactory::Release, public
//
//  Synopsis:   Decrement DLL reference count
//
//  See Also:   IUnknown::Release
//
//--------------------------------------------------------------------------
ULONG STDMETHODCALLTYPE
CHelloClassFactory::Release()
{
    unsigned long count;

    count = m_cRef - 1;

    if(InterlockedDecrement((long *) &m_cRef) == 0)
    {
        count = 0;

        ObjectDestroyed();
    }

    return count;
}

//+-------------------------------------------------------------------------
//
//  Function:   RegisterClassFactory.
//
//  Synopsis:   Register the class factory if it is not currently registered.
//
//--------------------------------------------------------------------------
HRESULT RegisterClassFactory()
{
    HRESULT hr;
    CHelloClassFactory *pClassFactory;

    if(InterlockedExchange(&g_fClassRegistered, TRUE) == FALSE)
    {
        pClassFactory = new CHelloClassFactory;

        if(pClassFactory != 0)
        {
            hr = CoRegisterClassObject(CLSID_OHello,
                          (IUnknown *) pClassFactory,
                           CLSCTX_LOCAL_SERVER,
                           REGCLS_MULTIPLEUSE,
                           &g_dwRegister);
          pClassFactory->Release();
        }
        else
        {
           hr = E_OUTOFMEMORY;
        }
    }
    else
    {
        hr = S_OK;
    }
    return hr;
}

//+-------------------------------------------------------------------------
//
//  Function:   RevokeClassFactory.
//
//  Synopsis:   Revoke the registered class factories if they have not
//              already been revoked.
//
//--------------------------------------------------------------------------
HRESULT RevokeClassFactory()
{
    HRESULT hr;

    if(InterlockedExchange(&g_fClassRegistered, FALSE) == TRUE)
    {
        hr = CoRevokeClassObject(g_dwRegister);
    }
    else
    {
        hr = S_OK;
    }
    return hr;
}


SERVER.CXX

/*+----------------------------------------------------------------------------

Microsoft Windows Sample Program
Copyright 1994 - 1998 Microsoft Corporation.  All rights reserved.

FILE:       server.cxx

USAGE:      server /REGSERVER   - Installs registry entries for the server.
            server /UNREGSERVER - Removes registry entries for the server.
            server -Embedding   - Server was auto-started by OLE.

PURPOSE:    Example of an OLE local server using a custom interface.
            This program illustrates the correct reference counting and
            shutdown behavior for a multiple-use local server.

            The sample code is safe for multiple threads.  This is not
            really necessary, but it makes it easy to convert this program
            to a multi-threaded server for a future version of Windows.

            This sample also provides an example of how to use the
            Win32 registry functions to install a local server and a
            proxy DLL in the registry.

FUNCTIONS:  DecrementLockCount  - Decrement the server's lock count.
            IncrementLockCount  - Increment the server's lock count.
            main                - Main entry point.
            ObjectCreated       - Called when object is created.
            ObjectDestroyed     - Called when object is destroyed.
            RegisterLocalServer - Create a registry entry for a local server.
            RegisterProxyDll    - Create a registry entry for a proxy DLL.
            UnregisterLocalServer - Remove a registry entry for a local server.
            UnregisterProxyDll  - Remove a registry entry for a proxy DLL.

COMMENTS:   The server program must be installed before running the client.
            Run server.exe /REGSERVER to install the server program.

-----------------------------------------------------------------------------*/
#include <windows.h>
#include <stdio.h>
#include "ohello.h"

void    PrintErrorMessage(HRESULT hr);
HRESULT RegisterClassFactory();
HRESULT RevokeClassFactory();

//Prototypes for local functions
HRESULT RegisterLocalServer(REFCLSID rclsid);
HRESULT RegisterProxyDll(const char *pszProxyDll);
HRESULT UnregisterLocalServer(REFCLSID rclsid);
HRESULT UnregisterProxyDll(const char *pszProxyDll);

//CLSID for the server object
extern "C" const CLSID CLSID_OHello
    = {0xf9246031,0x9f33,0x11cd,{0xb2,0x3f,0x00,0xaa,0x00,0x33,0x9c,0xce}};

long    g_LockCount   = 0;
long    g_ObjectCount = 0;

//+---------------------------------------------------------------------------
//
//  Function:   DecrementLockCount
//
//  Synopsis:   Decrements the lock count.  The lock count controls the
//              registration of the class factory in the class table.
//              DecrementLockCount revokes the class factory when the
//              lock count is zero.
//
//  Called By:  ~CHello, IClassFactory::LockServer(FALSE).
//
//  See Also:   ObjectCreated, ObjectDestroyed, IncrementLockCount.
//
//----------------------------------------------------------------------------
void DecrementLockCount()
{
    //Decrement the lock count.
    if(InterlockedDecrement(&g_LockCount) == 0)
    {
        //When the lock count is decremented to zero,
        //revoke the class factory.
        RevokeClassFactory();
    }
}

//+---------------------------------------------------------------------------
//
//  Function:   IncrementLockCount
//
//  Synopsis:   Increments the lock count.  The lock count controls the
//              registration of the class factory in the class table.
//              If necessary, IncrementLockCount will reregister the class
//              factory in the class table.
//
//  Called By:  CHello, IClassFactory::LockServer(TRUE).
//
//  See Also:   DecrementLockCount, ObjectCreated, ObjectDestroyed.
//
//----------------------------------------------------------------------------
void IncrementLockCount()
{
    if(g_LockCount == 0)
    {
        //Increment the lock count.
        InterlockedIncrement(&g_LockCount);

        //Reregister the class factory if necessary.
        RegisterClassFactory();
    }
    else
    {
        //Increment the lock count.
        InterlockedIncrement(&g_LockCount);
    }
}

//+---------------------------------------------------------------------------
//
//  Function:   main
//
//  Synopsis:   Main entry point for the server application.  This function
//              initializes the server and processes the message loop.
//
//----------------------------------------------------------------------------
int _cdecl main(int argc, char *argv[])
{
    HRESULT hr = S_OK;
    int i;
    BOOL fRegisterServer = FALSE;
    BOOL fUnregisterServer = FALSE;
    BOOL fEmbedding = FALSE;
    BOOL fHelp = FALSE;
    MSG msg;

    // Parse each item, skip the command name
    for (i = 1; i < argc; i++)
    {
        if (stricmp( argv[i], "-Embedding" ) == 0)
        {
            //This server has been activated by OLE.
            fEmbedding = TRUE;
        }
        else if (stricmp( argv[i], "/REGSERVER" ) == 0)
        {
            fRegisterServer = TRUE;
        }
        else if (stricmp( argv[i], "/UNREGSERVER" ) == 0)
        {
            fUnregisterServer = TRUE;
        }
        else
        {
            fHelp = TRUE;
        }
    }

    if(fHelp == TRUE)
        {
        printf("USAGE: server /REGSERVER   - Installs server in the registry.\n");
        printf("       server /UNREGSERVER - Removes server from the registry.\n");
        printf("       server -Embedding   - Server was auto-started by OLE.\n");
        return 0;
    }
    else if(fUnregisterServer == TRUE)
    {
        hr = UnregisterLocalServer(CLSID_OHello);
        if(FAILED(hr))
        {
            printf("Failed to remove local server from the registry.\n");
            PrintErrorMessage(hr);
        }

        hr = UnregisterProxyDll("ohello.dll");
        if(FAILED(hr))
        {
            printf("Failed to remove proxy DLL from the registry.\n");
            PrintErrorMessage(hr);
        }
        return 0;
    }

    //Install the local server in the registry.
    hr = RegisterLocalServer(CLSID_OHello);
    if(FAILED(hr))
    {
        printf("Failed to install local server in the registry.\n");
        PrintErrorMessage(hr);
    }

    //Install the proxy DLL in the registry.
    hr = RegisterProxyDll("ohello.dll");
    if(FAILED(hr))
    {
        printf("Failed to install proxy DLL in the registry.\n");
        PrintErrorMessage(hr);
    }

    if(fRegisterServer == TRUE)
        return 0;

    //
    // Initialize OLE before calling any other OLE functions.
    //

    hr = CoInitialize(NULL);

    if(SUCCEEDED(hr))
    {
        hr = RegisterClassFactory();

        if(SUCCEEDED(hr))
        {
            printf("Waiting for client to connect...\n");
            // Main message loop.  We will remain in the message loop
            // until we get a WM_QUIT message.
        while (GetMessage(&msg, NULL, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

            //If the class factory is still registered in the
            //class table, revoke it before we shut down.
            RevokeClassFactory();
    }
        else
        {
            printf("Failed to register class factory.\n");
            PrintErrorMessage(hr);
        }
        CoUninitialize();
    }
    else
    {
        printf("CoInitialize failed.\n");
        PrintErrorMessage(hr);
    }

    return 0;
}

//+---------------------------------------------------------------------------
//
//  Function:   ObjectCreated
//
//  Synopsis:   Increments the object count controlling the main message loop
//              of the server process.
//
//  Called By:  CHello, CHelloFactory.
//
//  See Also:   DecrementLockCount, IncrementLockCount, ObjectDestroyed.
//
//----------------------------------------------------------------------------
void ObjectCreated()
{
    InterlockedIncrement(&g_ObjectCount);
}

//+---------------------------------------------------------------------------
//
//  Function:   ObjectDestroyed
//
//  Synopsis:   Decrements the object count controlling the main message loop
//              of the server process.  When the object count is decremented
//              to zero, we post a WM_QUIT message.  The main message loop will
//              exit and terminate the server process.
//
//  Called By:  ~CHello, ~CHelloFactory.
//
//  See Also:   DecrementLockCount, IncrementLockCount, ObjectCreated.
//
//----------------------------------------------------------------------------
void ObjectDestroyed()
{
    if(InterlockedDecrement(&g_ObjectCount) == 0)
    {
        //When the last object is released, post a quit message.
        PostQuitMessage(0);
    }
}

//+---------------------------------------------------------------------------
//
//  Function:   RegisterLocalServer
//
//  Synopsis:   Creates a registry key for a local server.
//
//  Parameters: rclsid - Supplies the class ID of the local server.
//
//----------------------------------------------------------------------------
HRESULT RegisterLocalServer(REFCLSID rclsid)
{
    HRESULT hr;
    LONG error;
    HKEY hKeyCLSID;         // key for ...\Classes\CLSID
    HKEY hKeyClassID;
    HKEY hKey;              // current key
    DWORD dwDisposition;
    char szServer[MAX_PATH];
    char szClassID[39];
    unsigned long length;

    length = GetModuleFileNameA(0, szServer, sizeof(szServer));

    if(length == 0)
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
        return hr;
    }

    //create the CLSID key
    error = RegCreateKeyExA(
            HKEY_CLASSES_ROOT,
            "CLSID",
            0,
            "REG_SZ",
            REG_OPTION_NON_VOLATILE,
            KEY_ALL_ACCESS,
            0,
            &hKeyCLSID,
            &dwDisposition);

    if(!error)
    {
        //convert the class ID to a registry key name.
        sprintf(szClassID, "{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
                rclsid.Data1, rclsid.Data2, rclsid.Data3,
                rclsid.Data4[0], rclsid.Data4[1],
                rclsid.Data4[2], rclsid.Data4[3],
                rclsid.Data4[4], rclsid.Data4[5],
                rclsid.Data4[6], rclsid.Data4[7]);

        //create key for the server class
        error = RegCreateKeyExA(hKeyCLSID,
                                szClassID,
                                0,
                                "REG_SZ",
                                REG_OPTION_NON_VOLATILE,
                                KEY_ALL_ACCESS,
                                0,
                                &hKeyClassID,
                                &dwDisposition);

        if(!error)
        {
            //create LocalServer32 key.
            error = RegCreateKeyExA(hKeyClassID,
                                    "LocalServer32",
                                    0,
                                    "REG_SZ",
                                    REG_OPTION_NON_VOLATILE,
                                    KEY_ALL_ACCESS,
                                    0,
                                    &hKey,
                                    &dwDisposition);

            if(!error)
            {
                //Set the server name.
                error = RegSetValueExA(hKey,
                                       "",
                                       0,
                                       REG_SZ,
                                       (const unsigned char *)szServer,
                                       strlen(szServer) + 1);

                RegFlushKey(hKey);
                RegCloseKey(hKey);
            }
            RegCloseKey(hKeyClassID);
        }
        RegCloseKey(hKeyCLSID);
    }

    if(!error)
        hr = S_OK;
    else
        hr = HRESULT_FROM_WIN32(error);

    return hr;
}

//+---------------------------------------------------------------------------
//
//  Function:   RegisterProxyDll
//
//  Synopsis:   Creates registry entries for the interfaces contained in a
//              proxy DLL.
//
//  Parameters: pszProxyDll - Supplies the filename of the proxy DLL.
//
//  Notes:      RegisterProxyDll calls the DllRegisterServer function
//              exported from the proxy DLL.
//
//----------------------------------------------------------------------------
HRESULT RegisterProxyDll(const char *pszProxyDll)
{
    HRESULT hr;
    HINSTANCE hProxyDll;
    HRESULT (STDAPICALLTYPE *pfnDllRegisterServer)();

    hProxyDll = LoadLibraryA(pszProxyDll);

    if(hProxyDll != 0)
    {
        pfnDllRegisterServer = (HRESULT (STDAPICALLTYPE *)())
            GetProcAddress(hProxyDll, "DllRegisterServer");

        if(pfnDllRegisterServer != 0)
            hr = (*pfnDllRegisterServer)();
        else
            hr = HRESULT_FROM_WIN32(GetLastError());

        FreeLibrary(hProxyDll);
    }
    else
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }

    return hr;
}

//+---------------------------------------------------------------------------
//
//  Function:   UnregisterLocalServer
//
//  Synopsis:   Removes the registry key for a local server.
//
//  Parameters: rclsid - Supplies the class ID of the local server.
//
//----------------------------------------------------------------------------
HRESULT UnregisterLocalServer(REFCLSID rclsid)
{
    HRESULT hr;
    HKEY hKeyCLSID;
    HKEY hKeyClassID;
    long error;
    char szClassID[39];

    //open the CLSID key
    error = RegOpenKeyExA(
        HKEY_CLASSES_ROOT,
        "CLSID",
        0,
        KEY_ALL_ACCESS,
        &hKeyCLSID);

    if(!error)
    {
    //convert the class ID to a registry key name.
    sprintf(szClassID, "{%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}",
            rclsid.Data1, rclsid.Data2, rclsid.Data3,
            rclsid.Data4[0], rclsid.Data4[1],
            rclsid.Data4[2], rclsid.Data4[3],
            rclsid.Data4[4], rclsid.Data4[5],
            rclsid.Data4[6], rclsid.Data4[7]);

        //open registry key for class ID string
        error = RegOpenKeyExA(
            hKeyCLSID,
            szClassID,
            0,
            KEY_ALL_ACCESS,
            &hKeyClassID);

        if(!error)
        {
            //delete LocalServer32 key.
            error = RegDeleteKeyA(hKeyClassID, "LocalServer32");
            RegCloseKey(hKeyClassID);
        }

        error = RegDeleteKeyA(hKeyCLSID, szClassID);
        RegCloseKey(hKeyCLSID);
    }

    if(!error)
        hr = S_OK;
    else
        hr = HRESULT_FROM_WIN32(error);

    return hr;
}

//+---------------------------------------------------------------------------
//
//  Function:   UnregisterProxyDll
//
//  Synopsis:   Removes registry entries for the interfaces contained in a
//              proxy DLL.
//
//  Parameters: pszProxyDll - Supplies the filename of the proxy DLL.
//
//  Notes:      UnregisterProxyDll calls the DllUnregisterServer function
//              exported from the proxy DLL.
//
//----------------------------------------------------------------------------
HRESULT UnregisterProxyDll(const char *pszProxyDll)
{
    HRESULT hr;
    HINSTANCE hProxyDll;
    HRESULT (STDAPICALLTYPE *pfnDllUnregisterServer)();

    hProxyDll = LoadLibraryA(pszProxyDll);

    if(hProxyDll != 0)
    {
        pfnDllUnregisterServer = (HRESULT (STDAPICALLTYPE *)())
            GetProcAddress(hProxyDll, "DllUnregisterServer");

        if(pfnDllUnregisterServer != 0)
            hr = (*pfnDllUnregisterServer)();
        else
            hr = HRESULT_FROM_WIN32(GetLastError());

        FreeLibrary(hProxyDll);
    }
    else
    {
        hr = HRESULT_FROM_WIN32(GetLastError());
    }

    return hr;
}


README.TXT

OLE Apartment Model Threading

SUMMARY
=======

The OLEAPT sample demonstrates OLE apartment model threading. The term
"apartment" describes a thread with a message queue that supports OLE/COM
objects. Objects within an apartment are reentrant in only the traditional
Windows sense, similar to single-threaded OLE. Therefore, operations that
yield to the message queue can cause further messages to be sent to any
objects within the apartment. Apartment model threading allows there to be
more than one "apartment" where previously there was only one: the main
application thread.

MORE INFORMATION
================

This program is both a client and a server. It starts as a client by
default, registering its executable as a server and creating another
instance of itself to be the server.

The client does nothing interesting and is always shown as a minimized
window. It simply creates an instance of its worker COM object in the
server. When it shuts down, it releases its reference to the object.

The server registers a class-factory on the main application thread and also
creates several worker threads. When requests arrive from clients to create
instances of the class, the server class-factory picks a worker thread for
the object (simply allocating them round-robin to the workers) and then goes
through the process of having the object created within the thread and
marshaled from the worker thread back to the class-factory, where it can be
returned to the caller. Note that this marshaling to the main thread happens
during creation only; subsequent calls from the client to the object go
straight from the client's process into the worker apartment.

The only mechanisms used here above-and-beyond Windows NT 3.5 are:

  - Ability to call CoInitialize in multiple threads
  - The use of the new marshaling flag, MSHCTX_INPROC, to marshall pointers
    between threads

This sample can be compiled as UNICODE or ANSI, so that it can run on both
Windows NT 3.51 and Windows 95.


OLEAPT.CPP

// ===========================================================================
// File: O L E A P T . C P P
//
//  Demonstrates OLE Apartment model threading. "Apartment" is essentially
// just a way of describing a thread with a message queue that supports OLE/COM
// objects. Objects within an apartment are reentrant in only the traditional
// Windows sense, identically to single-threaded OLE. That is to say that
// operations which yield to the message queue can cause further messages to
// be sent to any objects within the apartment. Apartment model threading simply
// allows there to be more than one "apartment" where previously there was
// only one: the main application thread.
//
//  This program is both a client and an server. It launches as a client
// by default, registering its executable as a server and creating another
// instance of itself to be the server.
//
//  The client does nothing interesting and is always shown as a minimized
// window. It simply creates an instance of its worker COM object in the
// server. When it shuts down, it releases its reference to the object,
// which is reflected in the server's UI.
//
//  The server registers a class-factory on the main application thread and
// also spawns several worker threads. When requests arrive from clients to
// create instances of the class, the server class-factory picks a worker
// thread for the object (simply allocating them round-robin to the workers)
// and then goes through the process of having the object created within the
// thread and properly marshalled from the worker thread back to the class-
// factory where it can be returned to the caller. Note that this marshalling
// to the main thread is fairly transient, it lasts for creation only:
// subsequent calls from the client to the object go straight from the client's
// process into the worker apartment.
//
//  The only mechanisms used here above-and-beyond NT 3.5 are:
//   * Ability to call CoInitialize() in multiple threads
//   * The use of the new marshalling flag, MSHCTX_INPROC, to marshall
//     pointers between threads
//
//  This sample may be compiled as UNICODE for the NT 3.51 beta, or as Ansi
// (listed here) to run on both the NT 3.51 beta and the Windows 95 M8 Beta.
//
// Copyright 1995 - 1998 Microsoft Corporation.  All Rights Reserved.
// ===========================================================================

// %%Includes: ---------------------------------------------------------------
#define INC_OLE2
#define STRICT
#include <windows.h>
#include <initguid.h>
#include <stdio.h>

// %%Constants: --------------------------------------------------------------
#define cServerThreads  5
#define szWindowClass   TEXT("OLEAPT_CLASS")
#define szTitleServer   TEXT("SERVER: OLE Apartment Sample")
#define szTitleClient   TEXT("CLIENT: OLE Apartment Sample")
#define szCloseQuery    TEXT("Client references still exist. Are you sure you want to close?")
#define COINIT_APARTMENTTHREADED    2
const LARGE_INTEGER     bZero = {0,0};

// %%Guids: ------------------------------------------------------------------
DEFINE_GUID(CLSID_CObject, 0x5c06abc0, 0x2dcd, 0x11ce, 0x84, 0x17, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);

// %%Classes: ----------------------------------------------------------------
// the class-factory object exists in the main application apartment/thread
// and is used to create instances of the worker objects on worker threads.
class CClassFactory : public IClassFactory
    {
  // IClassFactory
    STDMETHODIMP            QueryInterface(REFIID iid, void **ppv);
    STDMETHODIMP_(ULONG)    AddRef(void)    { return mcRef++; }
    STDMETHODIMP_(ULONG)    Release(void)   { if (--mcRef == 0) { delete this; return 0; } return mcRef; }
    STDMETHODIMP            CreateInstance(LPUNKNOWN punkOuter, REFIID iid, LPVOID FAR *ppv);
    STDMETHODIMP            LockServer(BOOL fLock);

  // data members
    ULONG   mcRef;

  // constructors/destructors
  public:
    CClassFactory() { mcRef = 1; }
    };

// this worker object is simple: it simply supports IUnknown. more interesting
// interfaces can be readily added here and implemented for the worker.
class CObject : public IUnknown
    {
  // IUnknown
    STDMETHODIMP            QueryInterface(REFIID iid, void **ppv);
    STDMETHODIMP_(ULONG)    AddRef(void)    { return mcRef++; }
    STDMETHODIMP_(ULONG)    Release(void);

  // data members
    ULONG   mcRef;

  // constructors/destructors
  public:
    CObject() { mcRef = 1; }
    };

// %%Globals: ----------------------------------------------------------------
BOOL        vfServer;                       // is this instance a server or client?
HANDLE      vrghThread[cServerThreads];     // worker thread handles
DWORD       vrgtid[cServerThreads];         // worker thread id's
HANDLE      vrghEvent[cServerThreads];      // creation event for each worker
INT         vrgcObjects[cServerThreads];    // # of objects created on each thread
HANDLE      vhEventServer;                  // creation-complete event for class-factory
CClassFactory   vclassfactory;
UINT        viNextThread;                   // next worker to create an object on
LPUNKNOWN   vpunkService;                   // client's x-process reference to its
                                            //  worker object in the server
HWND        vhwndMain;
LPSTREAM    vpstmMarshalling;               // scratch stream used for cross-apt marshalling
HRESULT     vhrThreadStatus;                // signals status to class-factory

// %%Prototypes: -------------------------------------------------------------
LRESULT CALLBACK    MainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT             ServerThreadProc(LPARAM lParam);
BOOL                FAutoRegister(HINSTANCE hinst);

// ---------------------------------------------------------------------------
// %%Function: MainWndProc
//  Window procedure for both client and server versions of the sample. Clients
// display nothing, server displays statistics about
// ---------------------------------------------------------------------------
 LRESULT CALLBACK
MainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    LRESULT lresult = TRUE;

    switch (message)
        {
        case WM_QUERYOPEN:
            // clients must stay minimized
            return vfServer;

        case WM_PAINT:
            {
            HDC hdc;
            PAINTSTRUCT ps;
            int i;

            if ((hdc = BeginPaint(hwnd, &ps)) != NULL)
                {
                if (vfServer)
                    {
                    for (i=0; i<cServerThreads; i++)
                        {
                        TCHAR    rgch[200];

                        wsprintf(rgch, TEXT("Thread #%d: %d objects"), i, vrgcObjects[i]);
                        TextOut(hdc, 20, 10+(i*25), rgch, lstrlen(rgch));
                        }
                    }
                }
            EndPaint(hwnd, &ps);
            }
            break;

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_CLOSE:
            // check for clients and prompt in the UI.
            if (vfServer)
                {
                for (int i=0; i<cServerThreads; i++)
                    {
                    if (vrgcObjects[i] > 0)
                        {
                        if (MessageBox(hwnd, szCloseQuery,
                                szTitleServer, MB_YESNO) == IDNO)
                            return FALSE;
                        else
                            break;
                        }
                    }
                }

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

    return lresult;
}  // MainWndProc

// ---------------------------------------------------------------------------
// %%Function: WinMain
//  Initialization, main message-pump for clients and servers.
// ---------------------------------------------------------------------------
 int PASCAL
WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR szCmdLine, int nCmdShow)
{
    WNDCLASS wndclass;
    HRESULT hr;
    int     i;
    DWORD   dwRegister = 0;
    MSG     msg;

    // parse the command-line
    if (szCmdLine)
        {
        LPSTR   sz = strtok(szCmdLine, " \t");

        vfServer = (sz && !strcmpi(sz, "-Embedding"));
        }

    // initialize COM
    hr = CoInitialize(NULL);
    if (FAILED(hr))
        {
        MessageBeep(0);
        return hr;
        }

    // register the CObject class in the registry so
    // that the client can create instances of the server
    if (!FAutoRegister(hinst))
        {
        hr = GetLastError();
        goto LCleanup;
        }

    if (vfServer)
        {
        // create the threads and synchronization events
        // which the server will need
        for (i=0; i<cServerThreads; i++)
            {
            TCHAR    rgch[32];

            // create the thread suspended so its event can be
            // created using its thread-id and it will be able to
            // use it as soon as it runs
            vrghThread[i] = CreateThread(NULL,
                0,
                (LPTHREAD_START_ROUTINE)&ServerThreadProc,
                0,
                CREATE_SUSPENDED,
                &vrgtid[i]);
            if (vrghThread[i] == NULL)
                {
                hr = GetLastError();
                goto LCleanup;
                }

            // a thread can use its thread-id to OpenEvent this existing
            // event at any time. it can also look for its thread-id in
            // vrgtid[] to determine its index for use with vrghEvent.
            // this event signals to a worker thread to create a new CObject
            wsprintf(rgch, TEXT("Thread_%d"), vrgtid[i]);
            vrghEvent[i] = CreateEvent(NULL, FALSE, FALSE, rgch);
            if (vrghEvent[i] == NULL)
                {
                hr = GetLastError();
                goto LCleanup;
                }

            // now that the event is available, let the thread run
            ResumeThread(vrghThread[i]);
            }

        // this signals the status of a worker threads creation after
        // receiving its create signal via vrghEvent[i]
        vhEventServer = CreateEvent(NULL, FALSE, FALSE, TEXT("Server"));
        if (vhEventServer == NULL)
            {
            hr = GetLastError();
            goto LCleanup;
            }

        // register the class-factory with COM
        hr = CoRegisterClassObject(CLSID_CObject,
            (IUnknown *)&vclassfactory,
            CLSCTX_LOCAL_SERVER,
            REGCLS_MULTIPLEUSE,
            &dwRegister);
        if (FAILED(hr))
            goto LCleanup;

        // create an IStream to be used for marshalling new objects between
        // the worker and the CClassFactory
        hr = CreateStreamOnHGlobal(NULL, TRUE, &vpstmMarshalling);
        if (FAILED(hr))
            goto LCleanup;
        }
    else
        {
        // this is the client case: create an instance of the service object
        // in the server
        hr = CoCreateInstance(CLSID_CObject,
            NULL,
            CLSCTX_LOCAL_SERVER,
            IID_IUnknown,
            (void**)&vpunkService);
        if (FAILED(hr))
            goto LCleanup;
        }

    // create the client and server UI
    memset(&wndclass, 0, sizeof(wndclass));
    wndclass.lpszClassName = szWindowClass;
    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = MainWndProc;
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
    wndclass.hInstance = hinst;

    if (!RegisterClass(&wndclass) ||
        (vhwndMain = CreateWindow(szWindowClass,
            vfServer ? szTitleServer : szTitleClient,
            WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
            HWND_DESKTOP, NULL, hinst, NULL)) == NULL)
        {
        hr = GetLastError();
        goto LCleanup;
        }

    // display clients as minimized, server as a normal window
    ShowWindow(vhwndMain, vfServer ? nCmdShow : SW_SHOWMINNOACTIVE);

    while (GetMessage(&msg, NULL, 0, 0))
        {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
        }
    hr = msg.wParam;

LCleanup:
    if (hr != S_OK)
        MessageBeep(0);
    if (vfServer)
        {
        // we explicitly don't clean up threads and events
        if (dwRegister != 0)
            CoRevokeClassObject(dwRegister);
        if (vpstmMarshalling != NULL)
            vpstmMarshalling->Release();
        }
    else
        {
        // this is the client case: make sure to release the service
        if (vpunkService != NULL)
            vpunkService->Release();
        }

    CoUninitialize();
    return hr;
}  // WinMain

// ---------------------------------------------------------------------------
// %%Function: FAutoRegister
//  Registers the CObject class in the registry.
// ---------------------------------------------------------------------------
 BOOL
FAutoRegister(HINSTANCE hinst)
{
    static TCHAR szClassDesc[] = TEXT("Ole Apartments Sample");
    static TCHAR szCLSIDEntry[] = TEXT("CLSID\\{5c06abc0-2dcd-11ce-8417-00aa00389b71}");
    TCHAR       szBuf[512];
    TCHAR       szPath[512];
    HKEY        hkeyT = NULL;

    // install the OLEPerf OLE1/OLE2 compatibility sections
    if ((RegSetValue(HKEY_CLASSES_ROOT, szCLSIDEntry, REG_SZ, szClassDesc,
            lstrlen(szClassDesc)) != ERROR_SUCCESS) ||
        (RegCreateKey(HKEY_CLASSES_ROOT, szCLSIDEntry, &hkeyT)
            != ERROR_SUCCESS) ||
        !GetModuleFileName(hinst, szBuf, sizeof(szBuf)))
        return FALSE;
    lstrcpy(szPath, szBuf);
    if (RegSetValue(hkeyT, TEXT("LocalServer32"), REG_SZ, szBuf, lstrlen(szBuf))
            != ERROR_SUCCESS)
        goto LErrExit;
    RegCloseKey(hkeyT);
    return TRUE;
LErrExit:
    RegCloseKey(hkeyT);
    return FALSE;
}  // FAutoRegister

// ===========================================================================
//                          C C L A S S F A C T O R Y
// ===========================================================================

// ---------------------------------------------------------------------------
// %%Function: CClassFactory::QueryInterface
//  Returns a new reference of the specified iid-type to a CClassFactory.
// ---------------------------------------------------------------------------
 STDMETHODIMP
CClassFactory::QueryInterface(REFIID iid, void **ppv)
{
    *ppv = NULL;

    if (iid == IID_IClassFactory || iid == IID_IUnknown)
        {
        *ppv = (IClassFactory *)this;
        }
    if (*ppv != NULL)
        {
        AddRef();
        return S_OK;
        }
    return E_NOINTERFACE;
}  // CClassFactory::QueryInterface

// ---------------------------------------------------------------------------
// %%Function: CClassFactory::CreateInstance
//  Creates a new instance of a CObject on the next worker thread.
// ---------------------------------------------------------------------------
 STDMETHODIMP
CClassFactory::CreateInstance(LPUNKNOWN punkOuter, REFIID iid, void **ppv)
{
    LPUNKNOWN   punk;
    HRESULT     hr;

    *ppv = NULL;
    if (punkOuter != NULL)
        return CLASS_E_NOAGGREGATION;

    // trigger the worker thread that we want to create an object
    SetEvent(vrghEvent[viNextThread]);

    // now wait for the object to signal its completion
    WaitForSingleObject(vhEventServer, INFINITE);

    // once the worker thread signals completion, vhrThreadStatus
    // lets us know if the creation process was successful, and if
    // vpstmMarshalling creates a marshalled interface pointer
    if (FAILED(vhrThreadStatus))
        return vhrThreadStatus;

    // unmarshal an IUnknown from the scratch stream. if unmarshaling
    // fails, it takes care of releasing the object inside the marshal-data
    hr = vpstmMarshalling->Seek(bZero, STREAM_SEEK_SET, NULL);
    if (FAILED(hr))
        return hr;
    hr = CoUnmarshalInterface(vpstmMarshalling, IID_IUnknown, (void **)&punk);
    if (FAILED(hr))
        return hr;

    // get a reference to the interface asked for
    hr = punk->QueryInterface(iid, ppv);
    punk->Release();

    viNextThread++;
    viNextThread %= cServerThreads;

    return hr;
}  // CClassFactory::CreateInstance

 STDMETHODIMP
CClassFactory::LockServer(BOOL fLock)
{
    // there's no need to support this for this sample
    return E_FAIL;
}  // CClassFactory::LockServer

// ===========================================================================
//                               C O B J E C T
// ===========================================================================

// ---------------------------------------------------------------------------
// %%Function: ServerThreadProc
//  The worker thread function. Handles messages for objects of its thread/apt
// and creates new objects.
// ---------------------------------------------------------------------------
 LRESULT
ServerThreadProc(LPARAM lParam)
{
    HRESULT hr;
    MSG     msg;
    int     iThread;

    // figure out which thread this is: it needs its synchronization event
    for (iThread=0; iThread<cServerThreads; iThread++)
        {
        if (vrgtid[iThread] == GetCurrentThreadId())
            break;
        }
    if (iThread==cServerThreads)
        return E_UNEXPECTED;

    // initialize COM
    hr = CoInitialize(NULL);
    if (FAILED(hr))
        {
        MessageBeep(0);
        return hr;
        }

    // apartment message/event loop
    // (see SDK documentation for MsgWaitForMultipleObjects)
    // here worker message loops last forever. in situations without a
    // static number of worker threads, the loop could easily be terminated by
    // WM_QUITs sent from the main thread which might manage the worker thread
    // pool more carefully.
    while (TRUE)
        {
        DWORD dwWaitResult;

        // wait for any message sent or posted to this queue
        // or for one of the passed handles to become signaled
        dwWaitResult = MsgWaitForMultipleObjects(1, &vrghEvent[iThread],
            FALSE, INFINITE, QS_ALLINPUT);

        // result tells us the type of event we have:
        // a message or a signaled handle

        // if there are one or more messages in the queue ...
        if (dwWaitResult == (WAIT_OBJECT_0 + 1))
            {
            // dispatch all of the messages in this next loop
            // (here is where we'd check for WM_QUITs to end this
            // worker thread if we wanted to)
            while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
                DispatchMessage(&msg);
            }
        else
            {
            // this thread was signaled to create a new object
            try
                {
                LPUNKNOWN   punk;

                // create a new CObject
                punk = (IUnknown *)new CObject;
                if (punk == NULL)
                    throw E_OUTOFMEMORY;

                hr = vpstmMarshalling->Seek(bZero, STREAM_SEEK_SET, NULL);
                if (FAILED(hr))
                    throw hr;
                hr = CoMarshalInterface(vpstmMarshalling,
                    IID_IUnknown,
                    punk,
                    MSHCTX_INPROC,
                    NULL,
                    MSHLFLAGS_NORMAL);
                if (FAILED(hr))
                    throw hr;

                // punk is now referenced by its marshal-data in vpstmMarshalling.
                // we release our local reference here so the unmarshaller will
                // have the sole reference. a common mistake is to forget this
                // release and end up with orphaned objects in the server.
                punk->Release();

                // modify the global state -- the # of objects/thread -- which is
                // displayed in the server UI
                vrgcObjects[iThread]++;
                InvalidateRect(vhwndMain, NULL, TRUE);

                vhrThreadStatus = S_OK;
                }
            catch (HRESULT hr)
                {
                vhrThreadStatus = hr;
                }
            SetEvent(vhEventServer);
            }

        }

    CoUninitialize();
    return msg.wParam;
}  // ServerThreadProc

// ---------------------------------------------------------------------------
// %%Function: CObject::QueryInterface
//  Returns a new reference of the specified iid-type to a CObject.
// ---------------------------------------------------------------------------
 STDMETHODIMP
CObject::QueryInterface(REFIID iid, void **ppv)
{
    *ppv = NULL;

    if (iid == IID_IUnknown)
        {
        *ppv = (IUnknown *)this;
        }
    if (*ppv != NULL)
        {
        AddRef();
        return S_OK;
        }
    return E_NOINTERFACE;
}  // CObject::QueryInterface

// ---------------------------------------------------------------------------
// %%Function: CObject::Release
//  Handles releases of references to a CObject. Purpose here is to have code
// which alters the global state which is displayed in the servers UI.
// ---------------------------------------------------------------------------
 STDMETHODIMP_(ULONG)
CObject::Release(void)
{
    if (--mcRef == 0)
        {
        // change the global server state -- the # of objects/thread --
        // reflected in the server UI
        for (int i=0; i<cServerThreads; i++)
            {
            if (vrgtid[i] == GetCurrentThreadId())
                {
                vrgcObjects[i]--;
                InvalidateRect(vhwndMain, NULL, TRUE);
                break;
                }
            }

        delete this;
        return 0;
        }
    return mcRef;
}  // CObject::Release

// EOF =======================================================================


README.TXT

OLE Standard Utility Library

The OLESTD sample creates a library of utility functions which you may find
helpful to use as a model for writing your own code.

Most of this code was once part of the OLE2UI sample library that used to
be included in the OLE samples. Since OLEDLG.DLL was added to the set of
system-supported OLE libraries, much of the old OLE2UI code has become
obsolete. This library contains most of the remaining functionality from
that DLL.

THIS LIBRARY IS NOT AN OFFICIAL PART OF THE OLE SPECIFICATION. It is
intended only to demonstrate techniques and methods; it is not intended to
be used as a professional development library. Microsoft cannot guarantee
that all of this code is completely correct or fully operational.

You may feel free to incorporate some of this code in your applications but
please be aware that it has undergone limited testing and may not be
entirely free of bugs.


COMMON.H

/*
 * COMMON.H
 *
 * Structures and definitions applicable to all OLE 2.0 UI dialogs.
 *
 * Copyright (c)1992-1996 Microsoft Corporation, All Right Reserved
 */

#ifndef _COMMON_H_
#define _COMMON_H_

#include "ansiapi.h"

//Structure for label and source extraction from a metafile
typedef struct tagLABELEXTRACT
   {
   LPOLESTR    lpsz;
   UINT        Index;      // index in lpsz (so we can retrieve 2+ lines)
   DWORD       PrevIndex;  // index of last line (so we can mimic word wrap)

   union
      {
      UINT    cch;        //Length of label for label extraction
      UINT    iIcon;      //Index of icon in source extraction.
      } u;

   //For internal use in enum procs
   BOOL        fFoundIconOnly;
   BOOL        fFoundSource;
   BOOL        fFoundIndex;
   } LABELEXTRACT, FAR * LPLABELEXTRACT;

//Structure for extracting icons from a metafile (CreateIcon parameters)
typedef struct tagICONEXTRACT
   {
   HICON       hIcon;          //Icon created in the enumeration proc.

   /*
    * Since we want to handle multitasking well we have the caller
    * of the enumeration proc instantiate these variables instead of
    * using statics in the enum proc (which would be bad).
    */
   BOOL        fAND;
   HGLOBAL     hMemAND;        //Enumeration proc allocates and copies
   } ICONEXTRACT, FAR * LPICONEXTRACT;

//Structure to use to pass info to EnumMetafileDraw
typedef struct tagDRAWINFO
   {
   RECT     Rect;
   BOOL     fIconOnly;
   } DRAWINFO, FAR * LPDRAWINFO;

int CALLBACK EXPORT EnumMetafileIconDraw(HDC, HANDLETABLE FAR *, METARECORD FAR *, int, LPARAM);
int CALLBACK EXPORT EnumMetafileExtractLabel(HDC, HANDLETABLE FAR *, METARECORD FAR *, int, LPLABELEXTRACT);
int CALLBACK EXPORT EnumMetafileExtractIcon(HDC, HANDLETABLE FAR *, METARECORD FAR *, int, LPICONEXTRACT);
int CALLBACK EXPORT EnumMetafileExtractIconSource(HDC, HANDLETABLE FAR *, METARECORD FAR *, int, LPLABELEXTRACT);

//Shared globals:  our instance and clipboard formats used by the PasteSpecial dialog

extern HINSTANCE  ghInst;

extern UINT       cfObjectDescriptor;
extern UINT       cfLinkSrcDescriptor;
extern UINT       cfEmbedSource;
extern UINT       cfEmbeddedObject;
extern UINT       cfLinkSource;
extern UINT       cfOwnerLink;
extern UINT       cfFileName;

#endif //_COMMON_H_


DBALLOC.CPP

/***
*dballoc.cpp
*
*  Copyright 1992 - 1998 Microsoft Corporation.  All Rights Reserved.
*
*Purpose:
*  This file contains a debug implementation of the IMalloc interface.
*
*  This implementation is basically a simple wrapping of the C runtime,
*  with additional work to detect memory leakage, and memory overwrite.
*
*  Leakage is detected by tracking each allocation in an address
*  instance table, and then checking to see if the table is empty
*  when the last reference to the allocator is released.
*
*  Memory overwrite is detected by placing a signature at the end
*  of every allocated block, and checking to make sure the signature
*  is unchanged when the block is freed.
*
*  This implementation also has additional param validation code, as
*  well as additional check make sure that instances that are passed
*  to Free() were actually allocated by the corresponding instance
*  of the allocator.
*
*
*  Creating an instance of this debug allocator that uses the default
*  output interface would look like the following,
*
*
*  BOOL init_application_instance()
*  {
*    HRESULT hresult;
*    IMalloc FAR* pmalloc;
*
*    pmalloc = NULL;
*
*    if((hresult = OleStdCreateDbAlloc(0,&pmalloc))!=NOERROR)
*      goto LReturn;
*
*    hresult = OleInitialize(pmalloc);
*
*    // release pmalloc to let OLE hold the only ref to the it. later
*    // when OleUnitialize is called, memory leaks will be reported.
*    if(pmalloc != NULL)
*      pmalloc->Release();
*
*  LReturn:
*
*    return (hresult == NOERROR) ? TRUE : FALSE;
*  }
*
*
*  CONSIDER: could add an option to force error generation, something
*   like DBALLOC_ERRORGEN
*
*  CONSIDER: add support for heap-checking. say for example,
*   DBALLOC_HEAPCHECK would do a heapcheck every free? every 'n'
*   calls to free? ...
*
*
*Implementation Notes:
*
*  The method IMalloc::DidAlloc() is allowed to always return
*  "Dont Know" (-1).  This method is called by Ole, and they take
*  some appropriate action when they get this answer.
*
*****************************************************************************/

// Note: this file is designed to be stand-alone; it includes a
// carefully chosen, minimal set of headers.
//
// For conditional compilation we use the ole2 conventions,
//    _MAC      = mac
//    WIN32     = Win32 (NT really)
//    <nothing> = defaults to Win16

// REVIEW: the following needs to modified to handle _MAC
#define STRICT

#include <windows.h>

#include <ole2.h>

#if defined( __TURBOC__)
#define __STDC__ (1)
#endif

#define WINDLL  1           // make far pointer version of stdargs.h
#include <stdarg.h>

#if defined( __TURBOC__)
#undef __STDC__
#endif

#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <limits.h>

#include "dballoc.h"

extern "C" DWORD g_dwObjectCount; // since we don't include ole2ui.h

#define DIM(X) (sizeof(X)/sizeof((X)[0]))

#define UNREACHED 0

#if defined(WIN32)
# define MEMCMP(PV1, PV2, CB)   memcmp((PV1), (PV2), (CB))
# define MEMCPY(PV1, PV2, CB)   memcpy((PV1), (PV2), (CB))
# define MEMSET(PV,  VAL, CB)   memset((PV),  (VAL), (CB))
# define MALLOC(CB)     malloc(CB)
# define REALLOC(PV, CB)    realloc((PV), (CB))
# define FREE(PV)       free(PV)
# define HEAPMIN()      _heapmin()
#elif defined(_MAC)
# define MEMCMP(PV1, PV2)   ERROR -- NYI
# define MEMCPY(PV1, PV2, CB)   ERROR -- NYI
# define MEMSET(PV,  VAL, CB)   ERROR -- NYI
# define MALLOC(CB)     ERROR -- NYI
# define REALLOC(PV, CB)    ERROR -- NYI
# define FREE(PV)       ERROR -- NYI
# define HEAPMIN()      ERROR -- NYI
#else
# define MEMCMP(PV1, PV2, CB)   _fmemcmp((PV1), (PV2), (CB))
# define MEMCPY(PV1, PV2, CB)   _fmemcpy((PV1), (PV2), (CB))
# define MEMSET(PV,  VAL, CB)   _fmemset((PV),  (VAL), (CB))
# define MALLOC(CB)     _fmalloc(CB)
# define REALLOC(PV, CB)    _frealloc(PV, CB)
# define FREE(PV)       _ffree(PV)
# define HEAPMIN()      _fheapmin()
#endif

/*************************************************************************
** DEBUG ASSERTION ROUTINES
*************************************************************************/

#if DBG
#include <assert.h>
#define FnAssert(lpstrExpr, lpstrMsg, lpstrFileName, iLine)     \
        (_assert(lpstrMsg ? lpstrMsg : lpstrExpr,               \
                 lpstrFileName,                                 \
                 iLine), NOERROR)
#endif //DBG

#if defined( __TURBOC__ )
#define classmodel _huge
#else
#define classmodel FAR
#endif

class classmodel CStdDbOutput : public IDbOutput {
public:
   static IDbOutput FAR* Create();

   // IUnknown methods

   STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppv);
   STDMETHOD_(ULONG, AddRef)(void);
   STDMETHOD_(ULONG, Release)(void);

   // IDbOutput methods

   virtual void _cdecl  Printf (char FAR* szFmt, ...);

   STDMETHOD_(void, Assertion)(
     BOOL cond,
     char FAR* szExpr,
     char FAR* szFile,
     UINT uLine,
     char FAR* szMsg);

   void FAR* operator new(size_t cb){
     return MALLOC(cb);
   }
   void operator delete(void FAR* pv){
     FREE(pv);
   }

   CStdDbOutput(){
     g_dwObjectCount++ ;
     m_refs = 0;
   }

   ~CStdDbOutput() { g_dwObjectCount-- ; }

private:
   ULONG m_refs;

   char m_rgch[128]; // buffer for output formatting
};

//---------------------------------------------------------------------
//                implementation of the debug allocator
//---------------------------------------------------------------------

class FAR CAddrNode
{
public:
   void FAR*      m_pv;    // instance
   ULONG      m_cb;    // size of allocation in BYTES
   ULONG          m_nAlloc;    // the allocation pass count
   CAddrNode FAR* m_next;

   void FAR* operator new(size_t cb){
     return MALLOC(cb);
   }
   void operator delete(void FAR* pv){
     FREE(pv);
   }
};

class classmodel CDbAlloc : public IMalloc
{
public:
   static HRESULT Create(
     ULONG options, IDbOutput FAR* pdbout, IMalloc FAR* FAR* ppmalloc);

   // IUnknown methods

   STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppv);
   STDMETHOD_(ULONG, AddRef)(void);
   STDMETHOD_(ULONG, Release)(void);

   // IMalloc methods

   STDMETHOD_(void FAR*, Alloc)(ULONG cb);
   STDMETHOD_(void FAR*, Realloc)(void FAR* pv, ULONG cb);
   STDMETHOD_(void, Free)(void FAR* pv);
   STDMETHOD_(ULONG, GetSize)(void FAR* pv);
   STDMETHOD_(int, DidAlloc)(void FAR* pv);
   STDMETHOD_(void, HeapMinimize)(void);

   void FAR* operator new(size_t cb){
     return MALLOC(cb);
   }
   void operator delete(void FAR* pv){
     FREE(pv);
   }

   CDbAlloc(){
     m_refs = 1;
     m_pdbout = NULL;
     m_cAllocCalls = 0;
     m_nBreakAtNthAlloc = 0;
     m_nBreakAtAllocSize = 0;
     MEMSET(m_rganode, 0, sizeof(m_rganode));

     g_dwObjectCount++ ;

   }

   ~CDbAlloc() {   g_dwObjectCount-- ; }

private:

   ULONG m_refs;
   ULONG m_cAllocCalls;        // total count of allocation calls
   ULONG m_nBreakAtNthAlloc;   // allocation number to break to debugger
                        //  this value should be set typically in the
                        //  debugger.
   ULONG m_nBreakAtAllocSize;  // allocation size to break to debugger
                        //  this value should be set typically in the
                        //  debugger.
   IDbOutput FAR* m_pdbout;        // output interface
   CAddrNode FAR* m_rganode[64];   // address instance table

   // instance table methods

   BOOL IsEmpty(void);

   void AddInst(void FAR* pv, ULONG nAlloc, ULONG cb);
   void DelInst(void FAR* pv);
   CAddrNode FAR* GetInst(void FAR* pv);

   void DumpInst(CAddrNode FAR* pn);
   void DumpInstTable(void);

   inline UINT HashInst(void FAR* pv) const {
     return ((UINT)((ULONG)pv >> 4)) % DIM(m_rganode);
   }

   // output method(s)

   inline void Assertion(
     BOOL cond,
     char FAR* szExpr,
     char FAR* szFile,
     UINT uLine,
     char FAR* szMsg)
   {
     m_pdbout->Assertion(cond, szExpr, szFile, uLine, szMsg);
   }

   #define ASSERT(X) Assertion(X, #X, __FILE__, __LINE__, NULL)

   #define ASSERTSZ(X, SZ) Assertion(X, #X, __FILE__, __LINE__, SZ)

   static const unsigned char m_rgchSig[4];
};

const unsigned char CDbAlloc::m_rgchSig[] = { 0xDE, 0xAD, 0xBE, 0xEF };

/***
*HRESULT OleStdCreateDbAlloc(ULONG reserved, IMalloc** ppmalloc)
* Purpose:
*  Create an instance of CDbAlloc -- a debug implementation
*  of IMalloc.
*
* Parameters:
*   ULONG reserved              - reserved for future use. must be 0.
*   IMalloc FAR* FAR* ppmalloc  - (OUT) pointer to an IMalloc interface
*                                   of new debug allocator object
* Returns:
*   HRESULT
*       NOERROR         - if no error.
*       E_OUTOFMEMORY   - allocation failed.
*
***********************************************************************/
STDAPI OleStdCreateDbAlloc(ULONG reserved,IMalloc FAR* FAR* ppmalloc)
{
   return CDbAlloc::Create(reserved, NULL, ppmalloc);
}

HRESULT
CDbAlloc::Create(
   ULONG options,
   IDbOutput FAR* pdbout,
   IMalloc FAR* FAR* ppmalloc)
{
   HRESULT hresult;
   CDbAlloc FAR* pmalloc;

   // default the instance of IDbOutput if the user didn't supply one
   if(pdbout == NULL && ((pdbout = CStdDbOutput::Create()) == NULL)){
     hresult = E_OUTOFMEMORY;
     goto LError0;
   }

   pdbout->AddRef();

   if((pmalloc = new FAR CDbAlloc()) == NULL){
     hresult = E_OUTOFMEMORY;
     goto LError1;
   }

   pmalloc->m_pdbout = pdbout;

   *ppmalloc = pmalloc;

   return NOERROR;

LError1:;
   pdbout->Release();
   pmalloc->Release();

LError0:;
   return hresult;
}

STDMETHODIMP
CDbAlloc::QueryInterface(REFIID riid, void FAR* FAR* ppv)
{
   if(riid == IID_IUnknown || riid == IID_IMalloc){
     *ppv = this;
     AddRef();
     return NOERROR;
   }
   return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG)
CDbAlloc::AddRef()
{
   return ++m_refs;
}

STDMETHODIMP_(ULONG)
CDbAlloc::Release()
{
   if(--m_refs == 0){

     // check for memory leakage
     if(IsEmpty()){
        m_pdbout->Printf("No Memory Leaks.\n");
     }else{
        m_pdbout->Printf("Memory Leak Detected,\n");
        DumpInstTable();
     }

     m_pdbout->Release();
     delete this;
     return 0;
   }
   return m_refs;
}

STDMETHODIMP_(void FAR*)
CDbAlloc::Alloc(ULONG cb)
{
   size_t size;
   void FAR* pv;

   ++m_cAllocCalls;

   if (m_nBreakAtNthAlloc && m_cAllocCalls == m_nBreakAtNthAlloc) {
      ASSERTSZ(FALSE, "DBALLOC: NthAlloc Break target reached\r\n");
   } else if (m_nBreakAtAllocSize && cb == m_nBreakAtAllocSize) {
      ASSERTSZ(FALSE, "DBALLOC: AllocSize Break target reached\r\n");
   }

   // REVIEW: need to add support for huge allocations (on win16)
   if((cb + sizeof(m_rgchSig)) > UINT_MAX)
     return NULL;

   size = (size_t)cb;

   if((pv = MALLOC(size + sizeof(m_rgchSig))) == NULL)
     return NULL;

   // set allocated block to some non-zero value
   MEMSET(pv, -1, size);

   // put signature at end of allocated block
   MEMCPY((char FAR*)pv + size, m_rgchSig, sizeof(m_rgchSig));

   AddInst(pv, m_cAllocCalls, size);

   return pv;
}

STDMETHODIMP_(void FAR*)
CDbAlloc::Realloc(void FAR* pv, ULONG cb)
{
   size_t size;

   // REVIEW: need to add support for huge realloc
   if((cb + sizeof(m_rgchSig)) > UINT_MAX)
     return NULL;

   if(pv == NULL){
     return Alloc(cb);
   }

   ++m_cAllocCalls;

   ASSERT(GetInst(pv) != NULL);

   DelInst(pv);

   if(cb == 0){
     Free(pv);
     return NULL;
   }

   size = (size_t)cb;

   if((pv = REALLOC(pv, size + sizeof(m_rgchSig))) == NULL)
     return NULL;

   // put signature at end of allocated block
   MEMCPY((char FAR*)pv + size, m_rgchSig, sizeof(m_rgchSig));

   AddInst(pv, m_cAllocCalls, size);

   return pv;
}

STDMETHODIMP_(void)
CDbAlloc::Free(void FAR* pv)
{
   CAddrNode FAR* pn;
static char szSigMsg[] = "Signature Check Failed";

   if (pv == NULL) return;

   pn = GetInst(pv);

   // check for attempt to free an instance we didnt allocate
   if(pn == NULL){
     ASSERTSZ(FALSE, "pointer freed by wrong allocator");
     return;
   }

   // verify the signature
   if(MEMCMP((char FAR*)pv + pn->m_cb, m_rgchSig, sizeof(m_rgchSig)) != 0){
     m_pdbout->Printf(szSigMsg); m_pdbout->Printf("\n");
     DumpInst(GetInst(pv));
     ASSERTSZ(FALSE, szSigMsg);
   }

   // stomp on the contents of the block
   MEMSET(pv, 0xCC, (size_t)pn->m_cb + sizeof(m_rgchSig));

   DelInst(pv);

   FREE(pv);
}

STDMETHODIMP_(ULONG)
CDbAlloc::GetSize(void FAR* pv)
{
   CAddrNode FAR* pn;

   pn = GetInst(pv);

   if (pn == NULL) {
      return (ULONG)-1;
   }

   return pn->m_cb;
}

/***
*PUBLIC HRESULT CDbAlloc::DidAlloc
*Purpose:
*  Answer if the given address belongs to a block allocated by
*  this allocator.
*
*Entry:
*  pv = the instance to lookup
*
*Exit:
*  return value = int
*    1 - did alloc
*    0 - did *not* alloc
*   -1 - dont know (according to the ole2 spec it is always legal
*        for the allocator to answer "dont know")
*
***********************************************************************/
STDMETHODIMP_(int)
CDbAlloc::DidAlloc(void FAR* pv)
{
   return -1; // answer "I dont know"
}

STDMETHODIMP_(void)
CDbAlloc::HeapMinimize()
{
#ifdef WIN32
   ASSERTSZ (FALSE,"In HeapMinimize () - heapmin not defined in 32bit version.");
#else
   HEAPMIN();
#endif
}

//---------------------------------------------------------------------
//                      instance table methods
//---------------------------------------------------------------------

/***
*PRIVATE CDbAlloc::AddInst
*Purpose:
*  Add the given instance to the address instance table.
*
*Entry:
*  pv = the instance to add
*  nAlloc = the allocation passcount of this instance
*
*Exit:
*  None
*
***********************************************************************/
void
CDbAlloc::AddInst(void FAR* pv, ULONG nAlloc, ULONG cb)
{
   UINT hash;
   CAddrNode FAR* pn;

   ASSERT(pv != NULL);

   pn = (CAddrNode FAR*)new FAR CAddrNode();

   if (pn == NULL) {
      ASSERT(pn != NULL);
      return;
   }

   pn->m_pv = pv;
   pn->m_cb = cb;
   pn->m_nAlloc = nAlloc;

   hash = HashInst(pv);
   pn->m_next = m_rganode[hash];
   m_rganode[hash] = pn;
}

/***
*UNDONE
*Purpose:
*  Remove the given instance from the address instance table.
*
*Entry:
*  pv = the instance to remove
*
*Exit:
*  None
*
***********************************************************************/
void
CDbAlloc::DelInst(void FAR* pv)
{
   CAddrNode FAR* FAR* ppn, FAR* pnDead;

   for(ppn = &m_rganode[HashInst(pv)]; *ppn != NULL; ppn = &(*ppn)->m_next){
     if((*ppn)->m_pv == pv){
   pnDead = *ppn;
   *ppn = (*ppn)->m_next;
   delete pnDead;
   // make sure it doesnt somehow appear twice
   ASSERT(GetInst(pv) == NULL);
   return;
     }
   }

   // didnt find the instance
   ASSERT(UNREACHED);
}

CAddrNode FAR*
CDbAlloc::GetInst(void FAR* pv)
{
   CAddrNode FAR* pn;

   for(pn = m_rganode[HashInst(pv)]; pn != NULL; pn = pn->m_next){
     if(pn->m_pv == pv)
      return pn;
   }
   return NULL;
}

void
CDbAlloc::DumpInst(CAddrNode FAR* pn)
{
   if (pn == NULL)
      return;

   m_pdbout->Printf("[0x%lx]  nAlloc=%ld  size=%ld\n",
     pn->m_pv, pn->m_nAlloc, GetSize(pn->m_pv));
}

/***
*PRIVATE BOOL IsEmpty
*Purpose:
*  Answer if the address instance table is empty.
*
*Entry:
*  None
*
*Exit:
*  return value = BOOL, TRUE if empty, FALSE otherwise
*
***********************************************************************/
BOOL
CDbAlloc::IsEmpty()
{
   UINT u;

   for(u = 0; u < DIM(m_rganode); ++u){
     if(m_rganode[u] != NULL)
   return FALSE;
   }

   return TRUE;
}

/***
*PRIVATE CDbAlloc::Dump
*Purpose:
*  Print the current contents of the address instance table,
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
void
CDbAlloc::DumpInstTable()
{
   UINT u;
   CAddrNode FAR* pn;

   for(u = 0; u < DIM(m_rganode); ++u){
     for(pn = m_rganode[u]; pn != NULL; pn = pn->m_next){
        DumpInst(pn);
     }
   }
}

//---------------------------------------------------------------------
//                implementation of CStdDbOutput
//---------------------------------------------------------------------

IDbOutput FAR*
CStdDbOutput::Create()
{
   return (IDbOutput FAR*)new FAR CStdDbOutput();
}

STDMETHODIMP
CStdDbOutput::QueryInterface(REFIID riid, void FAR* FAR* ppv)
{
   if(riid == IID_IUnknown){
     *ppv = this;
     AddRef();
     return NOERROR;
   }
   return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG)
CStdDbOutput::AddRef()
{
   return ++m_refs;
}

STDMETHODIMP_(ULONG)
CStdDbOutput::Release()
{
   if(--m_refs == 0){
     delete this;
     return 0;
   }
   return m_refs;
}

void _cdecl
CStdDbOutput::Printf(char FAR* lpszFmt, ...)
{
   va_list args;
   char szBuf[256];
#if defined( OBSOLETE )
   char *pn, FAR* pf;
static char rgchFmtBuf[128];
static char rgchOutputBuf[128];

   // copy the 'far' format string to a near buffer so we can use
   // a medium model vsprintf, which only supports near data pointers.
   //
   pn = rgchFmtBuf, pf=szFmt;
   while(*pf != '\0')
     *pn++ = *pf++;
   *pn = '\0';
#endif

   va_start(args, lpszFmt);

//    wvsprintf(rgchOutputBuf, rgchFmtBuf, args);
   wvsprintf(szBuf, lpszFmt, args);

   OutputDebugString(szBuf);
}

STDMETHODIMP_(void)
CStdDbOutput::Assertion(
   BOOL cond,
   char FAR* szExpr,
   char FAR* szFile,
   UINT uLine,
   char FAR* szMsg)
{
   if(cond)
     return;

#ifdef _DEBUG
   // following is from compobj.dll (ole2)
   FnAssert(szExpr, szMsg, szFile, uLine);
#else
   // REVIEW: should be able to do something better that this...
   DebugBreak();
#endif
}


DBALLOC.H

/***
*dballoc.h
*
*  Copyright (C) 1992-96, Microsoft Corporation.  All Rights Reserved.
*
*Purpose:
*  This file contains the definition of CDbAlloc - A debug implementation
*  of the IMalloc interface.
*
*Implementation Notes:
*
*****************************************************************************/

#ifndef DBALLOC_H_INCLUDED /* { */
#define DBALLOC_H_INCLUDED

interface IDbOutput : public IUnknown
{
   STDMETHOD(QueryInterface)(THIS_ REFIID riid, void FAR* FAR* ppv) PURE;
   STDMETHOD_(ULONG, AddRef)(THIS) PURE;
   STDMETHOD_(ULONG, Release)(THIS) PURE;

   virtual void _cdecl Printf (THIS_
     char FAR* szFmt, ...) PURE;

   STDMETHOD_(void, Assertion)(THIS_
     BOOL cond,
     char FAR* szExpr,
     char FAR* szFile,
     UINT uLine,
     char FAR* szMsg) PURE;
};

#endif /* } DBALLOC_H_INCLUDED */


DBGUTIL.C

/*************************************************************************
**
**    OLE 2.0 Common Utilities
**
**    dbgutil.h
**
**    This file contains file contains functions to support debug output.
**
**    (c) Copyright Microsoft Corp. 1990 - 1996 All Rights Reserved
**
*************************************************************************/

#define STRICT  1
#include "olestd.h"

static int s_nDbgIndent = 0;        // indent level for debug message
#if defined( _DEBUG )
static int s_nDbgLevel = 0;   // default dbg level printed
#else
static int s_nDbgLevel = 0;   // default dbg level printed
#endif

STDAPI_(void) OleDbgPrint(
                int     nDbgLvl,
                LPSTR   lpszPrefix,
                LPSTR   lpszMsg,
                int     nIndent
)
{
        if (nDbgLvl <= s_nDbgLevel)
                OleDbgPrintAlways(lpszPrefix, lpszMsg, nIndent);
}

STDAPI_(void) OleDbgPrintAlways(LPSTR lpszPrefix, LPSTR lpszMsg, int nIndent)
{
        int i;

        if (nIndent < 0)
                OleDbgIndent(nIndent);

        if (lpszPrefix && *lpszPrefix != '\0') {
                OutputDebugString("| ");
                for (i = 0; i < s_nDbgIndent; i++)
                        OutputDebugString("----");

                OutputDebugString(lpszPrefix);
                OutputDebugString(": ");
        }

        OutputDebugString(lpszMsg);
        if (nIndent > 0)
                OleDbgIndent(nIndent);
}

STDAPI_(void) OleDbgSetDbgLevel(int nDbgLvl)
{
        s_nDbgLevel = nDbgLvl;
}

STDAPI_(int) OleDbgGetDbgLevel( void )
{
        return s_nDbgLevel;
}

STDAPI_(void) OleDbgIndent(int n)
{
        switch (n) {
                case -1:
                        s_nDbgIndent--;
                        break;
                case 1:
                        s_nDbgIndent++;
                        break;
                case -2:
                        s_nDbgIndent = 0;
                        break;
        }
}

STDAPI_(void) OleDbgPrintRefCnt(
                int         nDbgLvl,
                LPSTR       lpszPrefix,
                LPSTR       lpszMsg,
                LPVOID      lpObj,
                ULONG       refcnt
)
{
        if (nDbgLvl <= s_nDbgLevel)
                OleDbgPrintRefCntAlways(lpszPrefix, lpszMsg, lpObj, refcnt);
}

STDAPI_(void) OleDbgPrintRefCntAlways(
                LPSTR       lpszPrefix,
                LPSTR       lpszMsg,
                LPVOID      lpObj,
                ULONG       refcnt
)
{
#if defined( _DEBUG )
        char szBuf[256];

        wsprintf(szBuf, "[obj=(0x%lx) cnt=%ld] %s", lpObj, refcnt, lpszMsg);
        OleDbgPrintAlways(lpszPrefix, szBuf, 0);
#endif
}

STDAPI_(void) OleDbgPrintRect(
                int         nDbgLvl,
                LPSTR       lpszPrefix,
                LPSTR       lpszMsg,
                LPRECT      lpRect
)
{
        if (nDbgLvl <= s_nDbgLevel)
                OleDbgPrintRectAlways(lpszPrefix, lpszMsg, lpRect);
}

STDAPI_(void) OleDbgPrintRectAlways(
                LPSTR       lpszPrefix,
                LPSTR       lpszMsg,
                LPRECT      lpRect
)
{
#if defined( _DEBUG )
        char szBuf[256];

        wsprintf(
                        szBuf,
                        "%s: (%d,%d)-(%d,%d) %dx%d\r\n",
                        lpszMsg,
                        lpRect->left,
                        lpRect->top,
                        lpRect->right,
                        lpRect->bottom,
                        (lpRect->right-lpRect->left),
                        (lpRect->bottom-lpRect->top)
        );
        OleDbgPrintAlways(lpszPrefix, szBuf, 0);
#endif
}

#define CASE_SCODE(sc)  \
                case sc: \
                        lstrcpy(/*(LPSTR)*/szErrName, /*(LPSTR)*/#sc); \
                        break;

STDAPI_(void) OleDbgPrintScodeAlways(LPSTR lpszPrefix, LPSTR lpszMsg, SCODE sc)
{
#if defined( _DEBUG )
        char szBuf[256];
        char szErrName[40];

        switch (sc) {

                /* SCODE's defined in SCODE.H */

                CASE_SCODE(S_OK)
                CASE_SCODE(S_FALSE)
                CASE_SCODE(E_UNEXPECTED)
                CASE_SCODE(E_OUTOFMEMORY)
                CASE_SCODE(E_INVALIDARG)
                CASE_SCODE(E_NOINTERFACE)
                CASE_SCODE(E_POINTER)
                CASE_SCODE(E_HANDLE)
                CASE_SCODE(E_ABORT)
                CASE_SCODE(E_FAIL)
                CASE_SCODE(E_ACCESSDENIED)

                /* SCODE's defined in OLE2.H */

                CASE_SCODE(OLE_E_OLEVERB)
                CASE_SCODE(OLE_E_ADVF)
                CASE_SCODE(OLE_E_ENUM_NOMORE)
                CASE_SCODE(OLE_E_ADVISENOTSUPPORTED)
                CASE_SCODE(OLE_E_NOCONNECTION)
                CASE_SCODE(OLE_E_NOTRUNNING)
                CASE_SCODE(OLE_E_NOCACHE)
                CASE_SCODE(OLE_E_BLANK)
                CASE_SCODE(OLE_E_CLASSDIFF)
                CASE_SCODE(OLE_E_CANT_GETMONIKER)
                CASE_SCODE(OLE_E_CANT_BINDTOSOURCE)
                CASE_SCODE(OLE_E_STATIC)
                CASE_SCODE(OLE_E_PROMPTSAVECANCELLED)
                CASE_SCODE(OLE_E_INVALIDRECT)
                CASE_SCODE(OLE_E_WRONGCOMPOBJ)
                CASE_SCODE(OLE_E_INVALIDHWND)
                CASE_SCODE(OLE_E_NOT_INPLACEACTIVE)
                CASE_SCODE(OLE_E_CANTCONVERT)
                CASE_SCODE(OLE_E_NOSTORAGE)

                CASE_SCODE(DV_E_FORMATETC)
                CASE_SCODE(DV_E_DVTARGETDEVICE)
                CASE_SCODE(DV_E_STGMEDIUM)
                CASE_SCODE(DV_E_STATDATA)
                CASE_SCODE(DV_E_LINDEX)
                CASE_SCODE(DV_E_TYMED)
                CASE_SCODE(DV_E_CLIPFORMAT)
                CASE_SCODE(DV_E_DVASPECT)
                CASE_SCODE(DV_E_DVTARGETDEVICE_SIZE)
                CASE_SCODE(DV_E_NOIVIEWOBJECT)

                CASE_SCODE(OLE_S_USEREG)
                CASE_SCODE(OLE_S_STATIC)
                CASE_SCODE(OLE_S_MAC_CLIPFORMAT)

                CASE_SCODE(CONVERT10_E_OLESTREAM_GET)
                CASE_SCODE(CONVERT10_E_OLESTREAM_PUT)
                CASE_SCODE(CONVERT10_E_OLESTREAM_FMT)
                CASE_SCODE(CONVERT10_E_OLESTREAM_BITMAP_TO_DIB)
                CASE_SCODE(CONVERT10_E_STG_FMT)
                CASE_SCODE(CONVERT10_E_STG_NO_STD_STREAM)
                CASE_SCODE(CONVERT10_E_STG_DIB_TO_BITMAP)
                CASE_SCODE(CONVERT10_S_NO_PRESENTATION)

                CASE_SCODE(CLIPBRD_E_CANT_OPEN)
                CASE_SCODE(CLIPBRD_E_CANT_EMPTY)
                CASE_SCODE(CLIPBRD_E_CANT_SET)
                CASE_SCODE(CLIPBRD_E_BAD_DATA)
                CASE_SCODE(CLIPBRD_E_CANT_CLOSE)

                CASE_SCODE(DRAGDROP_E_NOTREGISTERED)
                CASE_SCODE(DRAGDROP_E_ALREADYREGISTERED)
                CASE_SCODE(DRAGDROP_E_INVALIDHWND)
                CASE_SCODE(DRAGDROP_S_DROP)
                CASE_SCODE(DRAGDROP_S_CANCEL)
                CASE_SCODE(DRAGDROP_S_USEDEFAULTCURSORS)

                CASE_SCODE(OLEOBJ_E_NOVERBS)
                CASE_SCODE(OLEOBJ_E_INVALIDVERB)
                CASE_SCODE(OLEOBJ_S_INVALIDVERB)
                CASE_SCODE(OLEOBJ_S_CANNOT_DOVERB_NOW)
                CASE_SCODE(OLEOBJ_S_INVALIDHWND)
                CASE_SCODE(INPLACE_E_NOTUNDOABLE)
                CASE_SCODE(INPLACE_E_NOTOOLSPACE)
                CASE_SCODE(INPLACE_S_TRUNCATED)

                /* SCODE's defined in COMPOBJ.H */

                CASE_SCODE(CO_E_NOTINITIALIZED)
                CASE_SCODE(CO_E_ALREADYINITIALIZED)
                CASE_SCODE(CO_E_CANTDETERMINECLASS)
                CASE_SCODE(CO_E_CLASSSTRING)
                CASE_SCODE(CO_E_IIDSTRING)
                CASE_SCODE(CO_E_APPNOTFOUND)
                CASE_SCODE(CO_E_APPSINGLEUSE)
                CASE_SCODE(CO_E_ERRORINAPP)
                CASE_SCODE(CO_E_DLLNOTFOUND)
                CASE_SCODE(CO_E_ERRORINDLL)
                CASE_SCODE(CO_E_WRONGOSFORAPP)
                CASE_SCODE(CO_E_OBJNOTREG)
                CASE_SCODE(CO_E_OBJISREG)
                CASE_SCODE(CO_E_OBJNOTCONNECTED)
                CASE_SCODE(CO_E_APPDIDNTREG)
                CASE_SCODE(CLASS_E_NOAGGREGATION)
                CASE_SCODE(CLASS_E_CLASSNOTAVAILABLE)
                CASE_SCODE(REGDB_E_READREGDB)
                CASE_SCODE(REGDB_E_WRITEREGDB)
                CASE_SCODE(REGDB_E_KEYMISSING)
                CASE_SCODE(REGDB_E_INVALIDVALUE)
                CASE_SCODE(REGDB_E_CLASSNOTREG)
                CASE_SCODE(REGDB_E_IIDNOTREG)
                CASE_SCODE(RPC_E_CALL_REJECTED)
                CASE_SCODE(RPC_E_CALL_CANCELED)
                CASE_SCODE(RPC_E_CANTPOST_INSENDCALL)
                CASE_SCODE(RPC_E_CANTCALLOUT_INASYNCCALL)
                CASE_SCODE(RPC_E_CANTCALLOUT_INEXTERNALCALL)
                CASE_SCODE(RPC_E_CONNECTION_TERMINATED)
                CASE_SCODE(RPC_E_SERVER_DIED)
                CASE_SCODE(RPC_E_CLIENT_DIED)
                CASE_SCODE(RPC_E_INVALID_DATAPACKET)
                CASE_SCODE(RPC_E_CANTTRANSMIT_CALL)
                CASE_SCODE(RPC_E_CLIENT_CANTMARSHAL_DATA)
                CASE_SCODE(RPC_E_CLIENT_CANTUNMARSHAL_DATA)
                CASE_SCODE(RPC_E_SERVER_CANTMARSHAL_DATA)
                CASE_SCODE(RPC_E_SERVER_CANTUNMARSHAL_DATA)
                CASE_SCODE(RPC_E_INVALID_DATA)
                CASE_SCODE(RPC_E_INVALID_PARAMETER)
                CASE_SCODE(RPC_E_UNEXPECTED)

                /* SCODE's defined in DVOBJ.H */

                CASE_SCODE(DATA_S_SAMEFORMATETC)
                CASE_SCODE(VIEW_E_DRAW)
                CASE_SCODE(VIEW_S_ALREADY_FROZEN)
                CASE_SCODE(CACHE_E_NOCACHE_UPDATED)
                CASE_SCODE(CACHE_S_FORMATETC_NOTSUPPORTED)
                CASE_SCODE(CACHE_S_SAMECACHE)
                CASE_SCODE(CACHE_S_SOMECACHES_NOTUPDATED)

                /* SCODE's defined in STORAGE.H */

                CASE_SCODE(STG_E_INVALIDFUNCTION)
                CASE_SCODE(STG_E_FILENOTFOUND)
                CASE_SCODE(STG_E_PATHNOTFOUND)
                CASE_SCODE(STG_E_TOOMANYOPENFILES)
                CASE_SCODE(STG_E_ACCESSDENIED)
                CASE_SCODE(STG_E_INVALIDHANDLE)
                CASE_SCODE(STG_E_INSUFFICIENTMEMORY)
                CASE_SCODE(STG_E_INVALIDPOINTER)
                CASE_SCODE(STG_E_NOMOREFILES)
                CASE_SCODE(STG_E_DISKISWRITEPROTECTED)
                CASE_SCODE(STG_E_SEEKERROR)
                CASE_SCODE(STG_E_WRITEFAULT)
                CASE_SCODE(STG_E_READFAULT)
                CASE_SCODE(STG_E_SHAREVIOLATION)
                CASE_SCODE(STG_E_LOCKVIOLATION)
                CASE_SCODE(STG_E_FILEALREADYEXISTS)
                CASE_SCODE(STG_E_INVALIDPARAMETER)
                CASE_SCODE(STG_E_MEDIUMFULL)
                CASE_SCODE(STG_E_ABNORMALAPIEXIT)
                CASE_SCODE(STG_E_INVALIDHEADER)
                CASE_SCODE(STG_E_INVALIDNAME)
                CASE_SCODE(STG_E_UNKNOWN)
                CASE_SCODE(STG_E_UNIMPLEMENTEDFUNCTION)
                CASE_SCODE(STG_E_INVALIDFLAG)
                CASE_SCODE(STG_E_INUSE)
                CASE_SCODE(STG_E_NOTCURRENT)
                CASE_SCODE(STG_E_REVERTED)
                CASE_SCODE(STG_E_CANTSAVE)
                CASE_SCODE(STG_E_OLDFORMAT)
                CASE_SCODE(STG_E_OLDDLL)
                CASE_SCODE(STG_E_SHAREREQUIRED)
                CASE_SCODE(STG_E_NOTFILEBASEDSTORAGE)
                CASE_SCODE(STG_E_EXTANTMARSHALLINGS)
                CASE_SCODE(STG_S_CONVERTED)

                /* SCODE's defined in MONIKER.H */

                CASE_SCODE(MK_E_CONNECTMANUALLY)
                CASE_SCODE(MK_E_EXCEEDEDDEADLINE)
                CASE_SCODE(MK_E_NEEDGENERIC)
                CASE_SCODE(MK_E_UNAVAILABLE)
                CASE_SCODE(MK_E_SYNTAX)
                CASE_SCODE(MK_E_NOOBJECT)
                CASE_SCODE(MK_E_INVALIDEXTENSION)
                CASE_SCODE(MK_E_INTERMEDIATEINTERFACENOTSUPPORTED)
                CASE_SCODE(MK_E_NOTBINDABLE)
                CASE_SCODE(MK_E_NOTBOUND)
                CASE_SCODE(MK_E_CANTOPENFILE)
                CASE_SCODE(MK_E_MUSTBOTHERUSER)
                CASE_SCODE(MK_E_NOINVERSE)
                CASE_SCODE(MK_E_NOSTORAGE)
                CASE_SCODE(MK_E_NOPREFIX)
                CASE_SCODE(MK_S_REDUCED_TO_SELF)
                CASE_SCODE(MK_S_ME)
                CASE_SCODE(MK_S_HIM)
                CASE_SCODE(MK_S_US)
                CASE_SCODE(MK_S_MONIKERALREADYREGISTERED)

                default:
                        lstrcpy(szErrName, "UNKNOWN SCODE");
        }

        wsprintf(szBuf, "%s %s (0x%lx)\n", lpszMsg, /*(LPSTR)*/szErrName, sc);
        OleDbgPrintAlways(lpszPrefix, szBuf, 0);
#endif  // _DEBUG
}


DEBUG.H

/*
 * DEBUG.H
 *
 * Definitions, structures, types, and function prototypes for debugging
 * purposes.
 *
 * Copyright (c)1992-1996 Microsoft Corporation, All Right Reserved,
 * as applied to redistribution of this source code in source form
 * License is granted to use of compiled code in shipped binaries.
 */

#ifndef _DEBUG_H_
#define _DEBUG_H_

#ifdef DEBUG

//Basic debug macros
#define D(x)        {x;}
#define ODS(x)      D(OutputDebugString(x);OutputDebugString("\r\n"))

#define ODSsz(f, s)  {\
 char        szDebug[128];\
 wsprintf(szDebug, f, (LPSTR)s);\
 ODS(szDebug);\
 }

#define ODSu(f, u)  {\
char        szDebug[128];\
wsprintf(szDebug, f, (UINT)u);\
ODS(szDebug);\
}

#define ODSlu(f, lu) {\
 char        szDebug[128];\
 wsprintf(szDebug, f, (DWORD)lu);\
 ODS(szDebug);\
 }

#define ODSszu(f, s, u) {\
char        szDebug[128];\
wsprintf(szDebug, f, (LPSTR)s, (UINT)u);\
ODS(szDebug);\
}

#define ODSszlu(f, s, lu) {\
  char        szDebug[128];\
  wsprintf(szDebug, f, (LPSTR)s, (DWORD)lu);\
  ODS(szDebug);\
  }

#else   //NO DEBUG

#define D(x)
#define ODS(x)

#define ODSsz(f, s)
#define ODSu(f, u)
#define ODSlu(f, lu)
#define ODSszu(f, s, u)
#define ODSszlu(f, s, lu)

#endif //DEBUG

#endif //_DEBUG_H_


DRAWICON.C

/*
 * DRAWICON.C
 *
 * Functions to handle creation of metafiles with icons and labels
 * as well as functions to draw such metafiles with or without the label.
 *
 * The metafile is created with a comment that marks the records containing
 * the label code.  Drawing the metafile enumerates the records, draws
 * all records up to that point, then decides to either skip the label
 * or draw it.
 *
 * Copyright (c)1992-1996 Microsoft Corporation, All Right Reserved
 */

#define STRICT  1
#include "olestd.h"
#include "common.h"
#include "utility.h"

/*
 * Strings for metafile comments.  KEEP THESE IN SYNC WITH THE
 * STRINGS IN GETICON.C.
 */

static char szIconOnly[]="IconOnly";        //Where to stop to exclude label.

/*
 * OleUIMetafilePictIconFree
 *
 * Purpose:
 *  Deletes the metafile contained in a METAFILEPICT structure and
 *  frees the memory for the structure itself.
 *
 * Parameters:
 *  hMetaPict       HGLOBAL metafilepict structure created in
 *                  OleUIMetafilePictFromIconAndLabel
 *
 * Return Value:
 *  None
 */

STDAPI_(void) OleUIMetafilePictIconFree(HGLOBAL hMetaPict)
   {
   LPMETAFILEPICT      pMF;

   if (NULL==hMetaPict)
      return;

   pMF=(LPMETAFILEPICT)GlobalLock(hMetaPict);

   if (NULL!=pMF)
      {
      if (NULL!=pMF->hMF)
         DeleteMetaFile(pMF->hMF);
      }

   GlobalUnlock(hMetaPict);
   GlobalFree(hMetaPict);
   return;
   }

/*
 * OleUIMetafilePictIconDraw
 *
 * Purpose:
 *  Draws the metafile from OleUIMetafilePictFromIconAndLabel, either with
 *  the label or without.
 *
 * Parameters:
 *  hDC             HDC on which to draw.
 *  pRect           LPRECT in which to draw the metafile.
 *  hMetaPict       HGLOBAL to the METAFILEPICT from
 *                  OleUIMetafilePictFromIconAndLabel
 *  fIconOnly       BOOL specifying to draw the label or not.
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful, FALSE if the
 *                  given metafilepict is invalid.
 */

STDAPI_(BOOL) OleUIMetafilePictIconDraw(HDC hDC, LPRECT pRect, HGLOBAL hMetaPict
                             , BOOL fIconOnly)
   {
   LPMETAFILEPICT  pMF;
   DRAWINFO        di;
   int             cx, cy;
   SIZE            size;
   POINT           point;

   if (NULL==hMetaPict)
      return FALSE;

   pMF=GlobalLock(hMetaPict);

   if (NULL==pMF)
      return FALSE;

   di.Rect = *pRect;
   di.fIconOnly = fIconOnly;

   //Transform to back to pixels
   cx=XformWidthInHimetricToPixels(hDC, pMF->xExt);
   cy=XformHeightInHimetricToPixels(hDC, pMF->yExt);

   SaveDC(hDC);

   SetMapMode(hDC, pMF->mm);
   SetViewportOrgEx(hDC, (pRect->right - cx) / 2, 0, &point);

   SetViewportExtEx(hDC, min ((pRect->right - cx) / 2 + cx, cx), cy, &size);

   if (fIconOnly)
      {
      // Since we've used the __export keyword on the
      // EnumMetafileIconDraw proc, we do not need to use
      // MakeProcInstance
      EnumMetaFile(hDC, pMF->hMF, (MFENUMPROC)EnumMetafileIconDraw
         , (LPARAM)(LPDRAWINFO)&di);
      }
   else
      PlayMetaFile(hDC, pMF->hMF);

   RestoreDC(hDC, -1);

   GlobalUnlock(hMetaPict);
   return TRUE;
   }

/*
 * EnumMetafileIconDraw
 *
 * Purpose:
 *  EnumMetaFile callback function that draws either the icon only or
 *  the icon and label depending on given flags.
 *
 * Parameters:
 *  hDC             HDC into which the metafile should be played.
 *  phTable         HANDLETABLE FAR * providing handles selected into the DC.
 *  pMFR            METARECORD FAR * giving the enumerated record.
 *  lParam          LPARAM flags passed in EnumMetaFile.
 *
 * Return Value:
 *  int             0 to stop enumeration, 1 to continue.
 */

int CALLBACK EXPORT EnumMetafileIconDraw(HDC hDC, HANDLETABLE FAR *phTable
   , METARECORD FAR *pMFR, int cObj, LPARAM lParam)
   {
   LPDRAWINFO lpdi = (LPDRAWINFO)lParam;

   /*
    * We play everything blindly except for DIBBITBLT (or DIBSTRETCHBLT)
    * and ESCAPE with MFCOMMENT.  For the BitBlts we change the x,y to
    * draw at (0,0) instead of wherever it was written to draw.  The
    * comment tells us there to stop if we don't want to draw the label.
    */

   //If we're playing icon only, stop enumeration at the comment.
   if (lpdi->fIconOnly)
      {
      if (META_ESCAPE==pMFR->rdFunction && MFCOMMENT==pMFR->rdParm[0])
         {
         if (0==lstrcmpi(szIconOnly, (LPSTR)&pMFR->rdParm[2]))
            return 0;
         }

      /*
       * Check for the records in which we want to munge the coordinates.
       * destX is offset 6 for BitBlt, offset 9 for StretchBlt, either of
       * which may appear in the metafile.
       */
      if (META_DIBBITBLT==pMFR->rdFunction)
         pMFR->rdParm[6]=0;

      if (META_DIBSTRETCHBLT==pMFR->rdFunction)
           pMFR->rdParm[9] = 0;

      }

   PlayMetaFileRecord(hDC, phTable, pMFR, cObj);
   return 1;
   }

/*
 * OleUIMetafilePictExtractLabel
 *
 * Purpose:
 *  Retrieves the label string from metafile representation of an icon.
 *
 * Parameters:
 *  hMetaPict       HGLOBAL to the METAFILEPICT containing the metafile.
 *  lpszLabel       LPSTR in which to store the label.
 *  cchLabel        UINT length of lpszLabel.
 *  lpWrapIndex     DWORD index of first character in last line. Can be NULL
 *                  if calling function doesn't care about word wrap.
 *
 * Return Value:
 *  UINT            Number of characters copied.
 */
STDAPI_(UINT) OleUIMetafilePictExtractLabel(HGLOBAL hMetaPict, LPOLESTR lpszLabel
                                , UINT cchLabel, LPDWORD lpWrapIndex)
   {
   LPMETAFILEPICT  pMF;
   LABELEXTRACT    le;
   HDC             hDC;

   /*
    * We extract the label by getting a screen DC and walking the metafile
    * records until we see the ExtTextOut record we put there.  That
    * record will have the string embedded in it which we then copy out.
    */

   if (NULL==hMetaPict || NULL==lpszLabel || 0==cchLabel)
      return FALSE;

   pMF=GlobalLock(hMetaPict);

   if (NULL==pMF)
      return FALSE;

   le.lpsz=lpszLabel;
   le.u.cch=cchLabel;
   le.Index=0;
   le.fFoundIconOnly=FALSE;
   le.fFoundSource=FALSE;  //Unused for this function.
   le.fFoundIndex=FALSE;   //Unused for this function.
   le.PrevIndex = 0;

   //Use a screen DC so we have something valid to pass in.
   hDC=GetDC(NULL);

   // Since we've used the EXPORT keyword on the
   // EnumMetafileExtractLabel proc, we do not need to use
   // MakeProcInstance

   EnumMetaFile(hDC, pMF->hMF, (MFENUMPROC)EnumMetafileExtractLabel, (LONG)(LPLABELEXTRACT)&le);

   ReleaseDC(NULL, hDC);

   GlobalUnlock(hMetaPict);

   //Tell where we wrapped (if calling function cares)
   if (NULL != lpWrapIndex)
      *lpWrapIndex = le.PrevIndex;

   //Return amount of text copied
   return le.u.cch;
   }

/*
 * EnumMetafileExtractLabel
 *
 * Purpose:
 *  EnumMetaFile callback function that walks a metafile looking for
 *  ExtTextOut, then concatenates the text from each one into a buffer
 *  in lParam.
 *
 * Parameters:
 *  hDC             HDC into which the metafile should be played.
 *  phTable         HANDLETABLE FAR * providing handles selected into the DC.
 *  pMFR            METARECORD FAR * giving the enumerated record.
 *  pLE             LPLABELEXTRACT providing the destination buffer and length.
 *
 * Return Value:
 *  int             0 to stop enumeration, 1 to continue.
 */

int CALLBACK EXPORT EnumMetafileExtractLabel(HDC hDC, HANDLETABLE FAR *phTable
   , METARECORD FAR *pMFR, int cObj, LPLABELEXTRACT pLE)
   {

   /*
    * We don't allow anything to happen until we see "IconOnly"
    * in an MFCOMMENT that is used to enable everything else.
    */
   if (!pLE->fFoundIconOnly)
      {
      if (META_ESCAPE==pMFR->rdFunction && MFCOMMENT==pMFR->rdParm[0])
         {
         if (0==lstrcmpi(szIconOnly, (LPSTR)&pMFR->rdParm[2]))
            pLE->fFoundIconOnly=TRUE;
         }

      return 1;
      }

   //Enumerate all records looking for META_EXTTEXTOUT - there can be more
   //than one.
   if (META_EXTTEXTOUT==pMFR->rdFunction)
      {
      UINT        cchMax;
      LPOLESTR    lpszTemp;

       /*
       * If ExtTextOut has NULL fuOptions, then the rectangle is omitted
       * from the record, and the string starts at rdParm[4].  If
       * fuOptions is non-NULL, then the string starts at rdParm[8]
       * (since the rectange takes up four WORDs in the array).  In
       * both cases, the string continues for (rdParm[2]+1) >> 1
       * words.  We just cast a pointer to rdParm[8] to an LPSTR and
       * lstrcpyn into the buffer we were given.
       *
       * Note that we use element 8 in rdParm instead of 4 because we
       * passed ETO_CLIPPED in for the options on ExtTextOut--docs say
       * [4] which is rect doesn't exist if we passed zero there.
       *
       */

      cchMax=min(pLE->u.cch - pLE->Index, (UINT)pMFR->rdParm[2]);
      lpszTemp = /*(LPOLESTR)*/((/*(LPSTR)*/pLE->lpsz) + pLE->Index);

      A2W ((LPSTR)&(pMFR->rdParm[8]), lpszTemp, cchMax);
      lpszTemp[cchMax]='\0';

      pLE->u.cch = OLESTRLEN (pLE->lpsz);
      pLE->PrevIndex = pLE->Index;
      pLE->Index += cchMax;
      }

   return 1;
   }

/*
 * OleUIMetafilePictExtractIcon
 *
 * Purpose:
 *  Retrieves the icon from metafile into which DrawIcon was done before.
 *
 * Parameters:
 *  hMetaPict       HGLOBAL to the METAFILEPICT containing the metafile.
 *
 * Return Value:
 *  HICON           Icon recreated from the data in the metafile.
 */
STDAPI_(HICON) OleUIMetafilePictExtractIcon(HGLOBAL hMetaPict)
   {
   LPMETAFILEPICT  pMF;
   HDC             hDC;
   ICONEXTRACT     ie;

   /*
    * We extract the label by getting a screen DC and walking the metafile
    * records until we see the ExtTextOut record we put there.  That
    * record will have the string embedded in it which we then copy out.
    */

   if (NULL==hMetaPict)
      return NULL;

   pMF=GlobalLock(hMetaPict);

   if (NULL==pMF)
      return FALSE;

   //Use a screen DC so we have something valid to pass in.
   hDC=GetDC(NULL);
   ie.fAND=TRUE;

   // We get information back in the ICONEXTRACT structure.
   // (Since we've used the EXPORT keyword on the
   // EnumMetafileExtractLabel proc, we do not need to use
   // MakeProcInstance)
   EnumMetaFile(hDC, pMF->hMF, (MFENUMPROC)EnumMetafileExtractIcon, (LONG)(LPICONEXTRACT)&ie);

   ReleaseDC(NULL, hDC);
   GlobalUnlock(hMetaPict);

   return ie.hIcon;
   }

/*
 * EnumMetafileExtractIcon
 *
 * Purpose:
 *  EnumMetaFile callback function that walks a metafile looking for
 *  StretchBlt (3.1) and BitBlt (3.0) records.  We expect to see two
 *  of them, the first being the AND mask and the second being the XOR
 *  data.  We
 *  ExtTextOut, then copies the text into a buffer in lParam.
 *
 * Parameters:
 *  hDC             HDC into which the metafile should be played.
 *  phTable         HANDLETABLE FAR * providing handles selected into the DC.
 *  pMFR            METARECORD FAR * giving the enumerated record.
 *  pIE             LPICONEXTRACT providing the destination buffer and length.
 *
 * Return Value:
 *  int             0 to stop enumeration, 1 to continue.
 */

int CALLBACK EXPORT EnumMetafileExtractIcon(HDC hDC, HANDLETABLE FAR *phTable
   , METARECORD FAR *pMFR, int cObj, LPICONEXTRACT pIE)
   {
   LPBITMAPINFO        lpBI;
   LPBITMAPINFOHEADER  lpBH;
   LPBYTE              lpbSrc;
   LPBYTE              lpbDst;
   UINT                uWidth, uHeight;
   DWORD               cb;
   HGLOBAL             hMem;
   BITMAP              bm;
   HBITMAP             hBmp;
   int                 cxIcon, cyIcon;

   //Continue enumeration if we don't see the records we want.
   if (META_DIBBITBLT!=pMFR->rdFunction && META_DIBSTRETCHBLT!=pMFR->rdFunction)
      return 1;

   /*
    * Windows 3.0 DrawIcon uses META_DIBBITBLT in whereas 3.1 uses
    * META_DIBSTRETCHBLT so we have to handle each case separately.
    */

   if (META_DIBBITBLT==pMFR->rdFunction)       //Win3.0
      {
      //Get dimensions and the BITMAPINFO struct.
      uHeight=pMFR->rdParm[1];
      uWidth =pMFR->rdParm[2];
      lpBI=(LPBITMAPINFO)&(pMFR->rdParm[8]);
      }

   if (META_DIBSTRETCHBLT==pMFR->rdFunction)   //Win3.1
      {
      //Get dimensions and the BITMAPINFO struct.
      uHeight=pMFR->rdParm[2];
      uWidth =pMFR->rdParm[3];
      lpBI=(LPBITMAPINFO)&(pMFR->rdParm[10]);
      }

   lpBH=(LPBITMAPINFOHEADER)&(lpBI->bmiHeader);

   //Pointer to the bits which follows the BITMAPINFO structure.
   lpbSrc=(LPBYTE)lpBI+lpBH->biSize;

   //Add the length of the color table.
   if (0!=lpBH->biClrUsed)
      lpbSrc+=(DWORD)(lpBH->biClrUsed*sizeof(RGBQUAD));
   else
      {
      /*
       * 1 << bc gives 2, 16, 256 for 1, 4, or 8 bits.  24-bit
       * bitmaps have no color table, so there's no need to
       * change lpbSrc.
       */
      if (lpBH->biBitCount <= 8)
          lpbSrc+=(DWORD)((1 << (lpBH->biBitCount))*sizeof(RGBQUAD));
      }

   /*
    * All the bits we have in lpbSrc are device-independent, so we
    * need to change them over to be device-dependent using SetDIBits.
    * Once we have a bitmap with the device-dependent bits, we can
    * GetBitmapBits to have buffers with the real data.
    *
    * For each pass we have to allocate memory for the bits.  We save
    * the memory for the mask between passes.
    */

   //Use CreateBitmap for ANY monochrome bitmaps
   if (pIE->fAND || 1==lpBH->biBitCount || lpBH->biBitCount > 8)
      hBmp=CreateBitmap((UINT)lpBH->biWidth, (UINT)lpBH->biHeight, 1, 1, NULL);
   else if (lpBH->biBitCount <= 8)
      hBmp=CreateCompatibleBitmap(hDC, (UINT)lpBH->biWidth, (UINT)lpBH->biHeight);

   if (!hBmp || !SetDIBits(hDC, hBmp, 0, (UINT)lpBH->biHeight, (LPVOID)lpbSrc, lpBI, DIB_RGB_COLORS))
      {
      if (!pIE->fAND)
         GlobalFree(pIE->hMemAND);

      DeleteObject(hBmp);
      return 0;
      }

   //Allocate memory and get the DDBits into it.
   GetObject(hBmp, sizeof(bm), &bm);

   cb=bm.bmHeight*bm.bmWidthBytes * bm.bmPlanes;

//    if (cb % 4 != 0)        // dword align
//      cb += 4 - (cb % 4);

   hMem=GlobalAlloc(GHND, cb);

   if (NULL==hMem)
      {
      if (NULL!=pIE->hMemAND)
         GlobalFree(pIE->hMemAND);

      DeleteObject(hBmp);
      return 0;
      }

   lpbDst=(LPBYTE)GlobalLock(hMem);
   GetBitmapBits(hBmp, cb, (LPVOID)lpbDst);

   DeleteObject(hBmp);
   GlobalUnlock(hMem);

   /*
    * If this is the first pass (pIE->fAND==TRUE) then save the memory
    * of the AND bits for the next pass.
    */
   if (pIE->fAND)
      {
      pIE->fAND=FALSE;
      pIE->hMemAND=hMem;

      //Continue enumeration looking for the next blt record.
      return 1;
      }
   else
      {
      //Get the AND pointer again.
      lpbSrc=(LPBYTE)GlobalLock(pIE->hMemAND);

      /*
       * Create the icon now that we have all the data.  lpbDst already
       * points to the XOR bits.
       */
      cxIcon = GetSystemMetrics(SM_CXICON);
      cyIcon = GetSystemMetrics(SM_CYICON);

      pIE->hIcon=CreateIcon(ghInst,
                       uWidth,
                       uHeight,
                       (BYTE)bm.bmPlanes,
                       (BYTE)bm.bmBitsPixel,
                       (LPVOID)lpbSrc,
                       (LPVOID)lpbDst);

      GlobalUnlock(pIE->hMemAND);
      GlobalFree(pIE->hMemAND);
      GlobalFree(hMem);

      //We're done so we can stop.
      return 0;
      }
   }

/*
 * OleUIMetafilePictExtractIconSource
 *
 * Purpose:
 *  Retrieves the filename and index of the icon source from a metafile
 *  created with OleUIMetafilePictFromIconAndLabel.
 *
 * Parameters:
 *  hMetaPict       HGLOBAL to the METAFILEPICT containing the metafile.
 *  lpszSource      LPSTR in which to store the source filename.  This
 *                  buffer should be OLEUI_CCHPATHMAX characters.
 *  piIcon          UINT FAR * in which to store the icon's index
 *                  within lpszSource
 *
 * Return Value:
 *  BOOL            TRUE if the records were found, FALSE otherwise.
 */
STDAPI_(BOOL) OleUIMetafilePictExtractIconSource(HGLOBAL hMetaPict
   , LPOLESTR lpszSource, UINT FAR *piIcon)
   {
   LPMETAFILEPICT  pMF;
   LABELEXTRACT    le;
   HDC             hDC;

   /*
    * We will walk the metafile looking for the two comment records
    * following the IconOnly comment.  The flags fFoundIconOnly and
    * fFoundSource indicate if we have found IconOnly and if we have
    * found the source comment already.
    */

   if (NULL==hMetaPict || NULL==lpszSource || NULL==piIcon)
      return FALSE;

   pMF=GlobalLock(hMetaPict);

   if (NULL==pMF)
      return FALSE;

   le.lpsz=lpszSource;
   le.fFoundIconOnly=FALSE;
   le.fFoundSource=FALSE;
   le.fFoundIndex=FALSE;

   //Use a screen DC so we have something valid to pass in.
   hDC=GetDC(NULL);

   EnumMetaFile(hDC, pMF->hMF, (MFENUMPROC)EnumMetafileExtractIconSource, (LONG)(LPLABELEXTRACT)&le);

   ReleaseDC(NULL, hDC);
   GlobalUnlock(hMetaPict);

   //Copy the icon index to the caller's variable.
   *piIcon=le.u.iIcon;

   //Check that we found everything.
   return (le.fFoundIconOnly && le.fFoundSource && le.fFoundIndex);
   }

/*
 * EnumMetafileExtractIconSource
 *
 * Purpose:
 *  EnumMetaFile callback function that walks a metafile skipping the first
 *  comment record, extracting the source filename from the second, and
 *  the index of the icon in the third.
 *
 * Parameters:
 *  hDC             HDC into which the metafile should be played.
 *  phTable         HANDLETABLE FAR * providing handles selected into the DC.
 *  pMFR            METARECORD FAR * giving the enumerated record.
 *  pLE             LPLABELEXTRACT providing the destination buffer and
 *                  area to store the icon index.
 *
 * Return Value:
 *  int             0 to stop enumeration, 1 to continue.
 */
int CALLBACK EXPORT EnumMetafileExtractIconSource(HDC hDC, HANDLETABLE FAR *phTable
   , METARECORD FAR *pMFR, int cObj, LPLABELEXTRACT pLE)
   {
   LPSTR       psz;

   /*
    * We don't allow anything to happen until we see "IconOnly"
    * in an MFCOMMENT that is used to enable everything else.
    */
   if (!pLE->fFoundIconOnly)
      {
      if (META_ESCAPE==pMFR->rdFunction && MFCOMMENT==pMFR->rdParm[0])
         {
         if (0==lstrcmpi(szIconOnly, (LPSTR)&pMFR->rdParm[2]))
            pLE->fFoundIconOnly=TRUE;
         }

      return 1;
      }

   //Now see if we find the source string.
   if (!pLE->fFoundSource)
      {
      if (META_ESCAPE==pMFR->rdFunction && MFCOMMENT==pMFR->rdParm[0])
         {
         A2W  ((LPSTR)&pMFR->rdParm[2], pLE->lpsz, OLEUI_CCHPATHMAX);
         pLE->lpsz[OLEUI_CCHPATHMAX-1] = '\0';
         pLE->fFoundSource=TRUE;
         }

      return 1;
      }

   //Next comment will be the icon index.
   if (META_ESCAPE==pMFR->rdFunction && MFCOMMENT==pMFR->rdParm[0])
      {
      /*
       * This string contains the icon index in string form,
       * so we need to convert back to a UINT.  After we see this
       * we can stop the enumeration.  The comment will have
       * a null terminator because we made sure to save it.
       */
      psz=(LPSTR)&pMFR->rdParm[2];
      pLE->u.iIcon=0;

      //Do Ye Olde atoi
      while (*psz)
         pLE->u.iIcon=(10*pLE->u.iIcon)+((*psz++)-'0');

      pLE->fFoundIndex=TRUE;
      return 0;
      }

   return 1;
   }


ENUMFETC.C

/*************************************************************************
**
**    OLE 2 Utility Code
**
**    enumfetc.c
**
**    This file contains a standard implementation of IEnumFormatEtc
**    interface.
**    This file is part of the OLE 2.0 User Interface support library.
**
**    (c) Copyright Microsoft Corp. 1990 - 1996 All Rights Reserved
**
*************************************************************************/

#define STRICT  1
#include "olestd.h"

typedef struct tagOleStdEnumFmtEtc {
  IEnumFORMATETCVtbl FAR* lpVtbl;
  ULONG m_dwRefs;       /* referance count */
  ULONG m_nIndex;       /* current index in list */
  ULONG m_nCount;       /* how many items in list */
  LPFORMATETC m_lpEtc;  /* list of formatetc */
} OLESTDENUMFMTETC, FAR* LPOLESTDENUMFMTETC;

VOID  OleStdEnumFmtEtc_Destroy(LPOLESTDENUMFMTETC pEtc);

STDMETHODIMP OleStdEnumFmtEtc_QueryInterface(
                LPENUMFORMATETC lpThis, REFIID riid, LPVOID FAR* ppobj);
STDMETHODIMP_(ULONG)  OleStdEnumFmtEtc_AddRef(LPENUMFORMATETC lpThis);
STDMETHODIMP_(ULONG)  OleStdEnumFmtEtc_Release(LPENUMFORMATETC lpThis);
STDMETHODIMP  OleStdEnumFmtEtc_Next(LPENUMFORMATETC lpThis, ULONG celt,
                                                                  LPFORMATETC rgelt, ULONG FAR* pceltFetched);
STDMETHODIMP  OleStdEnumFmtEtc_Skip(LPENUMFORMATETC lpThis, ULONG celt);
STDMETHODIMP  OleStdEnumFmtEtc_Reset(LPENUMFORMATETC lpThis);
STDMETHODIMP  OleStdEnumFmtEtc_Clone(LPENUMFORMATETC lpThis,
                                                                         LPENUMFORMATETC FAR* ppenum);

static IEnumFORMATETCVtbl g_EnumFORMATETCVtbl = {
                OleStdEnumFmtEtc_QueryInterface,
                OleStdEnumFmtEtc_AddRef,
                OleStdEnumFmtEtc_Release,
                OleStdEnumFmtEtc_Next,
                OleStdEnumFmtEtc_Skip,
                OleStdEnumFmtEtc_Reset,
                OleStdEnumFmtEtc_Clone,
};

/////////////////////////////////////////////////////////////////////////////

STDAPI_(LPENUMFORMATETC)
  OleStdEnumFmtEtc_Create(ULONG nCount, LPFORMATETC lpEtc)
//----------------------------------------------------------------------------
//
//----------------------------------------------------------------------------
{
  LPMALLOC lpMalloc=NULL;
  LPOLESTDENUMFMTETC lpEF=NULL;
  DWORD dwSize;
  WORD i;
  HRESULT hRes;

  hRes = CoGetMalloc(MEMCTX_TASK, &lpMalloc);
  if (hRes != NOERROR) {
        return NULL;
  }

  lpEF = (LPOLESTDENUMFMTETC)lpMalloc->lpVtbl->Alloc(lpMalloc,
                                                                                sizeof(OLESTDENUMFMTETC));
  if (lpEF == NULL) {
        goto errReturn;
  }

  lpEF->lpVtbl = &g_EnumFORMATETCVtbl;
  lpEF->m_dwRefs = 1;
  lpEF->m_nCount = nCount;
  lpEF->m_nIndex = 0;

  dwSize = sizeof(FORMATETC) * lpEF->m_nCount;

  lpEF->m_lpEtc = (LPFORMATETC)lpMalloc->lpVtbl->Alloc(lpMalloc, dwSize);
  if (lpEF->m_lpEtc == NULL)
        goto errReturn;

  lpMalloc->lpVtbl->Release(lpMalloc);

  for (i=0; i<nCount; i++) {
        OleStdCopyFormatEtc(
                        (LPFORMATETC)&(lpEF->m_lpEtc[i]), (LPFORMATETC)&(lpEtc[i]));
  }

  g_dwObjectCount++ ;

  return (LPENUMFORMATETC)lpEF;

errReturn:
  if (lpEF != NULL)
        lpMalloc->lpVtbl->Free(lpMalloc, lpEF);

  if (lpMalloc != NULL)
        lpMalloc->lpVtbl->Release(lpMalloc);

  return NULL;

} /* OleStdEnumFmtEtc_Create()
   */

VOID
  OleStdEnumFmtEtc_Destroy(LPOLESTDENUMFMTETC lpEF)
//----------------------------------------------------------------------------
//
//----------------------------------------------------------------------------
{
        LPMALLOC lpMalloc=NULL;
        WORD i;

        if (lpEF != NULL) {

                if (CoGetMalloc(MEMCTX_TASK, &lpMalloc) == NOERROR) {

                        /* NOTE: we MUST free any memory that was allocated for
                        **    TARGETDEVICES contained within the FORMATETC elements.
                        */
                        for (i=0; i<lpEF->m_nCount; i++) {
                                OleStdFree(lpEF->m_lpEtc[i].ptd);
                        }

                        if (lpEF->m_lpEtc != NULL) {
                                lpMalloc->lpVtbl->Free(lpMalloc, lpEF->m_lpEtc);
                        }

                        lpMalloc->lpVtbl->Free(lpMalloc, lpEF);
                        lpMalloc->lpVtbl->Release(lpMalloc);
                }
        }

        g_dwObjectCount-- ;

} /* OleStdEnumFmtEtc_Destroy()
   */

STDMETHODIMP
  OleStdEnumFmtEtc_QueryInterface(
                                LPENUMFORMATETC lpThis, REFIID riid, LPVOID FAR* ppobj)
//----------------------------------------------------------------------------
//
//----------------------------------------------------------------------------
{
  LPOLESTDENUMFMTETC lpEF = (LPOLESTDENUMFMTETC)lpThis;
  *ppobj = NULL;

  if (IsEqualIID(riid,&IID_IUnknown) || IsEqualIID(riid,&IID_IEnumFORMATETC)){
        *ppobj = (LPVOID)lpEF;
  }

  if (*ppobj == NULL) return E_NOINTERFACE;
  else{
        OleStdEnumFmtEtc_AddRef(lpThis);
        return NOERROR;
  }

} /* OleStdEnumFmtEtc_QueryInterface()
   */

STDMETHODIMP_(ULONG)
  OleStdEnumFmtEtc_AddRef(LPENUMFORMATETC lpThis)
//----------------------------------------------------------------------------
//
//----------------------------------------------------------------------------
{
  LPOLESTDENUMFMTETC lpEF = (LPOLESTDENUMFMTETC)lpThis;
  return lpEF->m_dwRefs++;

} /* OleStdEnumFmtEtc_AddRef()
   */

STDMETHODIMP_(ULONG)
  OleStdEnumFmtEtc_Release(LPENUMFORMATETC lpThis)
//----------------------------------------------------------------------------
//
//----------------------------------------------------------------------------
{
  LPOLESTDENUMFMTETC lpEF = (LPOLESTDENUMFMTETC)lpThis;
  DWORD dwRefs = --lpEF->m_dwRefs;

  if (dwRefs == 0)
        OleStdEnumFmtEtc_Destroy(lpEF);

  return dwRefs;

} /* OleStdEnumFmtEtc_Release()
   */

STDMETHODIMP
  OleStdEnumFmtEtc_Next(LPENUMFORMATETC lpThis, ULONG celt, LPFORMATETC rgelt,
                                          ULONG FAR* pceltFetched)
//----------------------------------------------------------------------------
//
//----------------------------------------------------------------------------
{
  LPOLESTDENUMFMTETC lpEF = (LPOLESTDENUMFMTETC)lpThis;
  ULONG i=0;
  ULONG nOffset;

  if (rgelt == NULL) {
        return E_INVALIDARG;
  }

  while (i < celt) {
        nOffset = lpEF->m_nIndex + i;

        if (nOffset < lpEF->m_nCount) {
          OleStdCopyFormatEtc(
                        (LPFORMATETC)&(rgelt[i]), (LPFORMATETC)&(lpEF->m_lpEtc[nOffset]));
          i++;
        }else{
          break;
        }
  }

  lpEF->m_nIndex += (WORD)i;

  if (pceltFetched != NULL) {
        *pceltFetched = i;
  }

  if (i != celt) {
        return S_FALSE;
  }

  return NOERROR;
} /* OleStdEnumFmtEtc_Next()
   */

STDMETHODIMP
  OleStdEnumFmtEtc_Skip(LPENUMFORMATETC lpThis, ULONG celt)
//----------------------------------------------------------------------------
//
//----------------------------------------------------------------------------
{
  LPOLESTDENUMFMTETC lpEF = (LPOLESTDENUMFMTETC)lpThis;
  ULONG i=0;
  ULONG nOffset;

  while (i < celt) {
        nOffset = lpEF->m_nIndex + i;

        if (nOffset < lpEF->m_nCount) {
          i++;
        }else{
          break;
        }
  }

  lpEF->m_nIndex += (WORD)i;

  if (i != celt) {
        return S_FALSE;
  }

  return NOERROR;
} /* OleStdEnumFmtEtc_Skip()
   */

STDMETHODIMP
  OleStdEnumFmtEtc_Reset(LPENUMFORMATETC lpThis)
//----------------------------------------------------------------------------
//
//----------------------------------------------------------------------------
{
  LPOLESTDENUMFMTETC lpEF = (LPOLESTDENUMFMTETC)lpThis;
  lpEF->m_nIndex = 0;

  return NOERROR;
} /* OleStdEnumFmtEtc_Reset()
   */

STDMETHODIMP
  OleStdEnumFmtEtc_Clone(LPENUMFORMATETC lpThis, LPENUMFORMATETC FAR* ppenum)
//----------------------------------------------------------------------------
//
//----------------------------------------------------------------------------
{
  LPOLESTDENUMFMTETC lpEF = (LPOLESTDENUMFMTETC)lpThis;

  if (ppenum == NULL) {
        return E_INVALIDARG;
  }

  *ppenum = OleStdEnumFmtEtc_Create(lpEF->m_nCount, lpEF->m_lpEtc);

  // make sure cloned enumerator has same index state as the original
  if (*ppenum) {
          LPOLESTDENUMFMTETC lpEFClone = (LPOLESTDENUMFMTETC)*ppenum;
          lpEFClone->m_nIndex = lpEF->m_nIndex;
          return NOERROR;
  } else
          return E_OUTOFMEMORY;

} /* OleStdEnumFmtEtc_Clone()
   */


ENUMSTAT.C

/*************************************************************************
**
**    OLE 2 Utility Code
**
**    enumstat.c
**
**    This file contains a standard implementation of IEnumStatData
**    interface.
**    This file is part of the OLE 2.0 User Interface support library.
**
**    (c) Copyright Microsoft Corp. 1990 - 1996 All Rights Reserved
**
*************************************************************************/

#define STRICT  1
#include "olestd.h"

typedef struct tagOleStdEnumStatData {
  IEnumSTATDATAVtbl FAR* lpVtbl;
  ULONG m_dwRefs;       /* referance count */
  ULONG m_nIndex;       /* current index in list */
  ULONG m_nCount;       /* how many items in list */
  LPSTATDATA m_lpStat;  /* list of STATDATA */
} OLESTDENUMSTATDATA, FAR* LPOLESTDENUMSTATDATA;

VOID  OleStdEnumStatData_Destroy(LPOLESTDENUMSTATDATA pStat);

STDMETHODIMP OleStdEnumStatData_QueryInterface(
      LPENUMSTATDATA lpThis, REFIID riid, LPVOID FAR* ppobj);
STDMETHODIMP_(ULONG)  OleStdEnumStatData_AddRef(LPENUMSTATDATA lpThis);
STDMETHODIMP_(ULONG)  OleStdEnumStatData_Release(LPENUMSTATDATA lpThis);
STDMETHODIMP  OleStdEnumStatData_Next(LPENUMSTATDATA lpThis, ULONG celt,
                          LPSTATDATA rgelt, ULONG FAR* pceltFetched);
STDMETHODIMP  OleStdEnumStatData_Skip(LPENUMSTATDATA lpThis, ULONG celt);
STDMETHODIMP  OleStdEnumStatData_Reset(LPENUMSTATDATA lpThis);
STDMETHODIMP  OleStdEnumStatData_Clone(LPENUMSTATDATA lpThis,
                            LPENUMSTATDATA FAR* ppenum);

static IEnumSTATDATAVtbl g_EnumSTATDATAVtbl = {
      OleStdEnumStatData_QueryInterface,
      OleStdEnumStatData_AddRef,
      OleStdEnumStatData_Release,
      OleStdEnumStatData_Next,
      OleStdEnumStatData_Skip,
      OleStdEnumStatData_Reset,
      OleStdEnumStatData_Clone,
};

/////////////////////////////////////////////////////////////////////////////

STDAPI_(BOOL)
  OleStdCopyStatData(LPSTATDATA pDest, LPSTATDATA pSrc)
//----------------------------------------------------------------------------
//
//----------------------------------------------------------------------------
{
  if ((pDest == NULL) || (pSrc == NULL)) {
   return FALSE;
  }

  if (OleStdCopyFormatEtc(&pDest->formatetc, &pSrc->formatetc) == FALSE) {
   return FALSE;
  }

  pDest->advf = pSrc->advf;
  pDest->pAdvSink = pSrc->pAdvSink;
  pDest->dwConnection = pSrc->dwConnection;

  if (pDest->pAdvSink != NULL) {
   pDest->pAdvSink->lpVtbl->AddRef(pDest->pAdvSink);
  }

  return TRUE;

} /* OleStdCopyStatData()
   */

STDAPI_(LPENUMSTATDATA)
  OleStdEnumStatData_Create(ULONG nCount, LPSTATDATA lpStatOrg)
//----------------------------------------------------------------------------
//
//----------------------------------------------------------------------------
{
  LPMALLOC lpMalloc=NULL;
  LPOLESTDENUMSTATDATA lpSD=NULL;
  DWORD dwSize;
  WORD i;
  HRESULT hRes;

  hRes = CoGetMalloc(MEMCTX_TASK, &lpMalloc);
  if (hRes != NOERROR) {
   return NULL;
  }

  lpSD = (LPOLESTDENUMSTATDATA)lpMalloc->lpVtbl->Alloc(lpMalloc,
                                     sizeof(OLESTDENUMSTATDATA));
  if (lpSD == NULL) {
   goto errReturn;
  }

  lpSD->lpVtbl = &g_EnumSTATDATAVtbl;
  lpSD->m_dwRefs = 1;
  lpSD->m_nCount = nCount;
  lpSD->m_nIndex = 0;

  dwSize = sizeof(STATDATA) * lpSD->m_nCount;

  lpSD->m_lpStat = (LPSTATDATA)lpMalloc->lpVtbl->Alloc(lpMalloc, dwSize);
  if (lpSD->m_lpStat == NULL)
   goto errReturn;

  lpMalloc->lpVtbl->Release(lpMalloc);

  for (i=0; i<nCount; i++) {
   OleStdCopyStatData(
         (LPSTATDATA)&(lpSD->m_lpStat[i]), (LPSTATDATA)&(lpStatOrg[i]));
  }

  g_dwObjectCount++ ;

  return (LPENUMSTATDATA)lpSD;

errReturn:
  if (lpSD != NULL)
   lpMalloc->lpVtbl->Free(lpMalloc, lpSD);

  if (lpMalloc != NULL)
   lpMalloc->lpVtbl->Release(lpMalloc);

  return NULL;

} /* OleStdEnumStatData_Create()
   */

VOID
  OleStdEnumStatData_Destroy(LPOLESTDENUMSTATDATA lpSD)
//----------------------------------------------------------------------------
//
//----------------------------------------------------------------------------
{
   LPMALLOC lpMalloc=NULL;
   WORD i;

   if (lpSD != NULL) {

      if (CoGetMalloc(MEMCTX_TASK, &lpMalloc) == NOERROR) {

         /* NOTE: we MUST free any memory that was allocated for
         **    TARGETDEVICES contained within the STATDATA elements.
         */
         for (i=0; i<lpSD->m_nCount; i++) {
            if( lpSD->m_lpStat[i].pAdvSink )
               lpSD->m_lpStat[i].pAdvSink->lpVtbl->Release(lpSD->m_lpStat[i].pAdvSink);

            OleStdFree(lpSD->m_lpStat[i].formatetc.ptd);
         }

         if (lpSD->m_lpStat != NULL) {
            lpMalloc->lpVtbl->Free(lpMalloc, lpSD->m_lpStat);
         }

         lpMalloc->lpVtbl->Free(lpMalloc, lpSD);
         lpMalloc->lpVtbl->Release(lpMalloc);
      }
   }

   g_dwObjectCount-- ;

} /* OleStdEnumStatData_Destroy()
   */

STDMETHODIMP
  OleStdEnumStatData_QueryInterface(
            LPENUMSTATDATA lpThis, REFIID riid, LPVOID FAR* ppobj)
//----------------------------------------------------------------------------
//
//----------------------------------------------------------------------------
{
  LPOLESTDENUMSTATDATA lpSD = (LPOLESTDENUMSTATDATA)lpThis;
  *ppobj = NULL;

  if (IsEqualIID(riid,&IID_IUnknown) || IsEqualIID(riid,&IID_IEnumSTATDATA)){
   *ppobj = (LPVOID)lpSD;
  }

  if (*ppobj == NULL) return E_NOINTERFACE;
  else{
   OleStdEnumStatData_AddRef(lpThis);
   return NOERROR;
  }

} /* OleStdEnumStatData_QueryInterface()
   */

STDMETHODIMP_(ULONG)
  OleStdEnumStatData_AddRef(LPENUMSTATDATA lpThis)
//----------------------------------------------------------------------------
//
//----------------------------------------------------------------------------
{
  LPOLESTDENUMSTATDATA lpSD = (LPOLESTDENUMSTATDATA)lpThis;
  return lpSD->m_dwRefs++;

} /* OleStdEnumStatData_AddRef()
   */

STDMETHODIMP_(ULONG)
  OleStdEnumStatData_Release(LPENUMSTATDATA lpThis)
//----------------------------------------------------------------------------
//
//----------------------------------------------------------------------------
{
  LPOLESTDENUMSTATDATA lpSD = (LPOLESTDENUMSTATDATA)lpThis;
  DWORD dwRefs = --lpSD->m_dwRefs;

  if (dwRefs == 0)
   OleStdEnumStatData_Destroy(lpSD);

  return dwRefs;

} /* OleStdEnumStatData_Release()
   */

STDMETHODIMP
  OleStdEnumStatData_Next(LPENUMSTATDATA lpThis, ULONG celt, LPSTATDATA rgelt,
                 ULONG FAR* pceltFetched)
//----------------------------------------------------------------------------
//
//----------------------------------------------------------------------------
{
  LPOLESTDENUMSTATDATA lpSD = (LPOLESTDENUMSTATDATA)lpThis;
  ULONG i=0;
  ULONG nOffset;

  if (rgelt == NULL) {
   return E_INVALIDARG;
  }

  while (i < celt) {
   nOffset = lpSD->m_nIndex + i;

   if (nOffset < lpSD->m_nCount) {
     OleStdCopyStatData(
         (LPSTATDATA)&(rgelt[i]), (LPSTATDATA)&(lpSD->m_lpStat[nOffset]));
     i++;
   }else{
     break;
   }
  }

  lpSD->m_nIndex += (WORD)i;

  if (pceltFetched != NULL) {
   *pceltFetched = i;
  }

  if (i != celt) {
   return S_FALSE;
  }

  return NOERROR;
} /* OleStdEnumStatData_Next()
   */

STDMETHODIMP
  OleStdEnumStatData_Skip(LPENUMSTATDATA lpThis, ULONG celt)
//----------------------------------------------------------------------------
//
//----------------------------------------------------------------------------
{
  LPOLESTDENUMSTATDATA lpSD = (LPOLESTDENUMSTATDATA)lpThis;
  ULONG i=0;
  ULONG nOffset;

  while (i < celt) {
   nOffset = lpSD->m_nIndex + i;

   if (nOffset < lpSD->m_nCount) {
     i++;
   }else{
     break;
   }
  }

  lpSD->m_nIndex += (WORD)i;

  if (i != celt) {
   return S_FALSE;
  }

  return NOERROR;
} /* OleStdEnumStatData_Skip()
   */

STDMETHODIMP
  OleStdEnumStatData_Reset(LPENUMSTATDATA lpThis)
//----------------------------------------------------------------------------
//
//----------------------------------------------------------------------------
{
  LPOLESTDENUMSTATDATA lpSD = (LPOLESTDENUMSTATDATA)lpThis;
  lpSD->m_nIndex = 0;

  return NOERROR;
} /* OleStdEnumStatData_Reset()
   */

STDMETHODIMP
  OleStdEnumStatData_Clone(LPENUMSTATDATA lpThis, LPENUMSTATDATA FAR* ppenum)
//----------------------------------------------------------------------------
//
//----------------------------------------------------------------------------
{
  LPOLESTDENUMSTATDATA lpSD = (LPOLESTDENUMSTATDATA)lpThis;

  if (ppenum == NULL) {
   return E_INVALIDARG;
  }

  *ppenum = OleStdEnumStatData_Create(lpSD->m_nCount, lpSD->m_lpStat);

  // make sure cloned enumerator has same index state as the original
  if (*ppenum) {
     LPOLESTDENUMSTATDATA lpSDClone = (LPOLESTDENUMSTATDATA)*ppenum;
     lpSDClone->m_nIndex = lpSD->m_nIndex;
     return NOERROR;
  } else
     return E_OUTOFMEMORY;

} /* OleStdEnumStatData_Clone()
   */


GETICON.C

/*************************************************************************
**
** The following API's are now OBSOLETE because equivalent API's have been
** added to the OLE2.DLL library
**      GetIconOfFile       superceeded by OleGetIconOfFile
**      GetIconOfClass      superceeded by OleGetIconOfClass
**      OleUIMetafilePictFromIconAndLabel
**                          superceeded by OleMetafilePictFromIconAndLabel
*************************************************************************/

/*
 *  GETICON.C
 *
 *  Functions to create DVASPECT_ICON metafile from filename or classname.
 *
 *  GetIconOfFile
 *  GetIconOfClass
 *  OleUIMetafilePictFromIconAndLabel
 *  HIconAndSourceFromClass Extracts the first icon in a class's server path
 *                          and returns the path and icon index to caller.
 *  FIconFileFromClass      Retrieves the path to the exe/dll containing the
 *                           default icon, and the index of the icon.
 *  OleStdIconLabelTextOut  Draw icon label text (line break if necessary)
 *
 *    (c) Copyright Microsoft Corp. 1992-1996 All Rights Reserved
 */

/*******
 *
 * ICON (DVASPECT_ICON) METAFILE FORMAT:
 *
 * The metafile generated with OleUIMetafilePictFromIconAndLabel contains
 * the following records which are used by the functions in DRAWICON.C
 * to draw the icon with and without the label and to extract the icon,
 * label, and icon source/index.
 *
 *  SetWindowOrg
 *  SetWindowExt
 *  DrawIcon:
 *      Inserts records of DIBBITBLT or DIBSTRETCHBLT, once for the
 *      AND mask, one for the image bits.
 *  Escape with the comment "IconOnly"
 *      This indicates where to stop record enumeration to draw only
 *      the icon.
 *  SetTextColor
 *  SetTextAlign
 *  SetBkColor
 *  CreateFont
 *  SelectObject on the font.
 *  ExtTextOut
 *      One or more ExtTextOuts occur if the label is wrapped.  The
 *      text in these records is used to extract the label.
 *  SelectObject on the old font.
 *  DeleteObject on the font.
 *  Escape with a comment that contains the path to the icon source.
 *  Escape with a comment that is the ASCII of the icon index.
 *
 *******/

#define STRICT  1
#include "olestd.h"

#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <commdlg.h>
#include <memory.h>
#include <cderr.h>
#include "common.h"
#include "utility.h"

static char szSeparators[] = " \t\\/!:";

#define IS_SEPARATOR(c)         ( (c) == ' ' || (c) == '\\' || (c) == '/' || (c) == '\t' || (c) == '!' || (c) == ':' )
#define IS_FILENAME_DELIM(c)    ( (c) == '\\' || (c) == '/' || (c) == ':' )

#if defined( OBSOLETE )
static HINSTANCE  s_hInst;

static char szMaxWidth[] ="WWWWWWWWWW";

//Strings for metafile comments.
static OLECHAR szIconOnly[]= OLESTR("IconOnly");        //Where to stop to exclude label.

static char szOLE2DLL[] = "ole2.dll";   // name of OLE 2.0 library

#define ICONINDEX              0

#define AUXUSERTYPE_SHORTNAME  USERCLASSTYPE_SHORT  // short name
#define HIMETRIC_PER_INCH   2540      // number HIMETRIC units per inch
#define PTS_PER_INCH          72      // number points (font size) per inch

#define MAP_PIX_TO_LOGHIM(x,ppli)   MulDiv(HIMETRIC_PER_INCH, (x), (ppli))
#define MAP_LOGHIM_TO_PIX(x,ppli)   MulDiv((ppli), (x), HIMETRIC_PER_INCH)

static char szVanillaDocIcon[] = "DefIcon";

static char szDocument[40] = "";

/*
 * GetIconOfFile(HINSTANCE hInst, LPSTR lpszPath, BOOL fUseFileAsLabel)
 *
 * Purpose:
 *  Returns a hMetaPict containing an icon and label (filename) for the
 *  specified filename.
 *
 * Parameters:
 *  hinst
 *  lpszPath        LPSTR path including filename to use
 *  fUseFileAsLabel BOOL TRUE if the icon's label is the filename, FALSE if
 *                  there should be no label.
 *
 * Return Value:
 *  HGLOBAL         hMetaPict containing the icon and label - if there's no
 *                  class in reg db for the file in lpszPath, then we use
 *                  Document.  If lpszPath is NULL, then we return NULL.
 */

STDAPI_(HGLOBAL) GetIconOfFile(HINSTANCE hInst, LPSTR lpszPath, BOOL fUseFileAsLabel)
{
  char     szIconFile[OLEUI_CCHPATHMAX];
  char     szLabel[OLEUI_CCHLABELMAX];
  LPSTR    lpszClsid = NULL;
  CLSID    clsid;
  HICON    hDefIcon = NULL;
  UINT     IconIndex = 0;
  HGLOBAL  hMetaPict;
  HRESULT  hResult;

  if (NULL == lpszPath)  // even if fUseFileAsLabel is FALSE, we still
   return NULL;             // need a valid filename to get the class.

  s_hInst = hInst;

  hResult = GetClassFile(lpszPath, &clsid);

  if (NOERROR == hResult)  // use the clsid we got to get to the icon
  {
     hDefIcon = HIconAndSourceFromClass(&clsid,
                               szIconFile,
                               &IconIndex);
  }

  if ( (NOERROR != hResult) || (NULL == hDefIcon) )
  {
    // Here, either GetClassFile failed or HIconAndSourceFromClass failed.

    LPSTR lpszTemp;

    lpszTemp = lpszPath;

    while ((*lpszTemp != '.') && (*lpszTemp != '\0'))
      lpszTemp++;

    if ('.' != *lpszTemp)
      goto UseVanillaDocument;

    if (FALSE == GetAssociatedExecutable(lpszTemp, szIconFile))
      goto UseVanillaDocument;

    hDefIcon = ExtractIcon(s_hInst, szIconFile, IconIndex);
  }

  if (hDefIcon <= (HICON)1) // ExtractIcon returns 1 if szExecutable is not exe,
  {                         // 0 if there are no icons.
UseVanillaDocument:

   OLESTRCPY(szIconFile, szOLE2DLL);
   IconIndex = ICONINDEX;
   hDefIcon = ExtractIcon(s_hInst, szIconFile, IconIndex);

  }

  // Now let's get the label we want to use.

  if (fUseFileAsLabel)   // strip off path, so we just have the filename.
  {
    int istrlen;
    LPSTR lpszBeginFile;

    istrlen = lstrlen(lpszPath);

    // set pointer to END of path, so we can walk backwards through it.
    lpszBeginFile = lpszPath + istrlen -1;

    while ( (lpszBeginFile >= lpszPath)
          && (!IS_FILENAME_DELIM(*lpszBeginFile)) )
     lpszBeginFile--;

    lpszBeginFile++;  // step back over the delimiter

    LSTRCPYN(szLabel, lpszBeginFile, OLEUI_CCHLABELMAX);
  }

  else   // use the short user type (AuxUserType2) for the label
  {

     if (0 == OleStdGetAuxUserType(&clsid, AUXUSERTYPE_SHORTNAME,
                           szLabel, OLEUI_CCHLABELMAX, NULL)) {

       if ('\0'==szDocument[0]) {
          LoadString(
             s_hInst,IDS_DEFICONLABEL,szDocument,sizeof(szDocument));
       }
       lstrcpy(szLabel, szDocument);
     }
  }

  hMetaPict = OleUIMetafilePictFromIconAndLabel(hDefIcon,
                                    szLabel,
                                    szIconFile,
                                    IconIndex);

  DestroyIcon(hDefIcon);

  return hMetaPict;

}

/*
 * GetIconOfClass(HINSTANCE hInst, REFCLSID rclsid, LPSTR lpszLabel, BOOL fUseTypeAsLabel)
 *
 * Purpose:
 *  Returns a hMetaPict containing an icon and label (human-readable form
 *  of class) for the specified clsid.
 *
 * Parameters:
 *  hinst
 *  rclsid          REFCLSID pointing to clsid to use.
 *  lpszLabel       label to use for icon.
 *  fUseTypeAsLabel Use the clsid's user type name as the icon's label.
 *
 * Return Value:
 *  HGLOBAL         hMetaPict containing the icon and label - if we
 *                  don't find the clsid in the reg db then we
 *                  return NULL.
 */

STDAPI_(HGLOBAL)    GetIconOfClass(HINSTANCE hInst, REFCLSID rclsid, LPSTR lpszLabel, BOOL fUseTypeAsLabel)
{

  char    szLabel[OLEUI_CCHLABELMAX];
  char    szIconFile[OLEUI_CCHPATHMAX];
  HICON   hDefIcon;
  UINT    IconIndex;
  HGLOBAL hMetaPict;

  s_hInst = hInst;

  if (!fUseTypeAsLabel)  // Use string passed in as label
  {
   if (NULL != lpszLabel)
      LSTRCPYN(szLabel, lpszLabel, sizeof(szLabel));
   else
      *szLabel = '\0';
  }
  else   // Use AuxUserType2 (short name) as label
  {

     if (0 == OleStdGetAuxUserType(rclsid,
                           AUXUSERTYPE_SHORTNAME,
                           szLabel,
                           OLEUI_CCHLABELMAX,
                           NULL))

      // If we can't get the AuxUserType2, then try the long name
      if (0 == OleStdGetUserTypeOfClass(rclsid, szLabel, OLEUI_CCHKEYMAX, NULL)) {
       if ('\0'==szDocument[0]) {
          LoadString(
             s_hInst,IDS_DEFICONLABEL,szDocument,sizeof(szDocument));
       }
       lstrcpy(szLabel, szDocument);  // last resort
      }
  }

  // Get the icon, icon index, and path to icon file
  hDefIcon = HIconAndSourceFromClass(rclsid,
              szIconFile,
              &IconIndex);

  if (NULL == hDefIcon)  // Use Vanilla Document
  {
   OLESTRCPY(szIconFile, szOLE2DLL);
   IconIndex = ICONINDEX;
   hDefIcon = ExtractIcon(s_hInst, szIconFile, IconIndex);
  }

  // Create the metafile
  hMetaPict = OleUIMetafilePictFromIconAndLabel(hDefIcon, szLabel,
                                    szIconFile, IconIndex);

  DestroyIcon(hDefIcon);

  return hMetaPict;

}

/*
 * OleUIMetafilePictFromIconAndLabel
 *
 * Purpose:
 *  Creates a METAFILEPICT structure that container a metafile in which
 *  the icon and label are drawn.  A comment record is inserted between
 *  the icon and the label code so our special draw function can stop
 *  playing before the label.
 *
 * Parameters:
 *  hIcon           HICON to draw into the metafile
 *  pszLabel        LPSTR to the label string.
 *  pszSourceFile   LPSTR containing the local pathname of the icon
 *                  as we either get from the user or from the reg DB.
 *  iIcon           UINT providing the index into pszSourceFile where
 *                  the icon came from.
 *
 * Return Value:
 *  HGLOBAL         Global memory handle containing a METAFILEPICT where
 *                  the metafile uses the MM_ANISOTROPIC mapping mode.  The
 *                  extents reflect both icon and label.
 */

STDAPI_(HGLOBAL) OleUIMetafilePictFromIconAndLabel(HICON hIcon, LPSTR pszLabel
   , LPSTR pszSourceFile, UINT iIcon)
   {
   HDC             hDC, hDCScreen;
   HMETAFILE       hMF;
   HGLOBAL         hMem;
   LPMETAFILEPICT  pMF;
   UINT            cxIcon, cyIcon;
   UINT            cxText, cyText;
   UINT            cx, cy;
   UINT            cchLabel = 0;
   HFONT           hFont, hFontT;
   int             cyFont;
   char            szIndex[10];
   RECT            TextRect;
   SIZE            size;
   POINT           point;
   UINT            fuAlign;

   if (NULL==hIcon)  // null label is valid but NOT a null icon
      return NULL;

   //Create a memory metafile
   hDC=(HDC)CreateMetaFile(NULL);

   if (NULL==hDC)
      return NULL;

   //Allocate the metafilepict
   hMem=GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE, sizeof(METAFILEPICT));

   if (NULL==hMem)
      {
      hMF=CloseMetaFile(hDC);
      DeleteMetaFile(hMF);
      return NULL;
      }

   if (NULL!=pszLabel)
      {
      cchLabel=lstrlen(pszLabel);

      if (cchLabel >= OLEUI_CCHLABELMAX)
         pszLabel[cchLabel] = '\0';   // truncate string
      }

   //Need to use the screen DC for these operations
   hDCScreen=GetDC(NULL);
   cyFont=-(8*GetDeviceCaps(hDCScreen, LOGPIXELSY))/72;

   //cyFont was calculated to give us 8 point.
   hFont=CreateFont(cyFont, 5, 0, 0, FW_NORMAL, 0, 0, 0, ANSI_CHARSET
      , OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, PROOF_QUALITY
      , FF_SWISS, "MS Sans Serif");

   hFontT=SelectObject(hDCScreen, hFont);

   GetTextExtentPoint(hDCScreen,szMaxWidth,lstrlen(szMaxWidth),&size);
   SelectObject(hDCScreen, hFontT);

   cxText = size.cx;
   cyText = size.cy * 2;

   cxIcon = GetSystemMetrics(SM_CXICON);
   cyIcon = GetSystemMetrics(SM_CYICON);

   // If we have no label, then we want the metafile to be the width of
   // the icon (plus margin), not the width of the fattest string.
   if ( (NULL == pszLabel) || ('\0' == *pszLabel) )
      cx = cxIcon + cxIcon / 4;
   else
      cx = max(cxText, cxIcon);

   cy=cyIcon+cyText+4;

   //Set the metafile size to fit the icon and label
   SetWindowOrgEx(hDC, 0, 0, &point);
   SetWindowExtEx(hDC, cx, cy, &size);

   //Set up rectangle to pass to OleStdIconLabelTextOut
   SetRectEmpty(&TextRect);

   TextRect.right = cx;
   TextRect.bottom = cy;

   //Draw the icon and the text, centered with respect to each other.
   DrawIcon(hDC, (cx-cxIcon)/2, 0, hIcon);

   //String that indicates where to stop if we're only doing icons
   Escape(hDC, MFCOMMENT, lstrlen(szIconOnly)+1, szIconOnly, NULL);

   SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));
   SetBkMode(hDC, TRANSPARENT);
   fuAlign = SetTextAlign(hDC, TA_LEFT | TA_TOP | TA_NOUPDATECP);

   OleStdIconLabelTextOut(hDC,
                     hFont,
                     0,
                     cy - cyText,
                     ETO_CLIPPED,
                     &TextRect,
                     pszLabel,
                     cchLabel,
                     NULL);

   //Write comments containing the icon source file and index.
   if (NULL!=pszSourceFile)
      {
      //+1 on string lengths insures the null terminator is embedded.
      Escape(hDC, MFCOMMENT, lstrlen(pszSourceFile)+1, pszSourceFile, NULL);

      cchLabel=wsprintf(szIndex, "%u", iIcon);
      Escape(hDC, MFCOMMENT, cchLabel+1, szIndex, NULL);
      }

   SetTextAlign(hDC, fuAlign);

   //All done with the metafile, now stuff it all into a METAFILEPICT.
   hMF=CloseMetaFile(hDC);

   if (NULL==hMF)
      {
      GlobalFree(hMem);
      ReleaseDC(NULL, hDCScreen);
      return NULL;
      }

   //Fill out the structure
   pMF=(LPMETAFILEPICT)GlobalLock(hMem);

   //Transform to HIMETRICS
   cx=XformWidthInPixelsToHimetric(hDCScreen, cx);
   cy=XformHeightInPixelsToHimetric(hDCScreen, cy);
   ReleaseDC(NULL, hDCScreen);

   pMF->mm=MM_ANISOTROPIC;
   pMF->xExt=cx;
   pMF->yExt=cy;
   pMF->hMF=hMF;

   GlobalUnlock(hMem);

   DeleteObject(hFont);

   return hMem;
   }

#endif  // OBSOLETE

/*
 * GetAssociatedExecutable
 *
 * Purpose:  Finds the executable associated with the provided extension
 *
 * Parameters:
 *   lpszExtension   LPSTR points to the extension we're trying to find
 *                   an exe for. Does **NO** validation.
 *
 *   lpszExecutable  LPSTR points to where the exe name will be returned.
 *                   No validation here either - pass in 128 char buffer.
 *
 * Return:
 *   BOOL            TRUE if we found an exe, FALSE if we didn't.
 *
 */

BOOL FAR PASCAL GetAssociatedExecutable(LPSTR lpszExtension, LPSTR lpszExecutable)

{
   HKEY    hKey;
   LONG    dw;
   LRESULT lRet;
   char    szValue[OLEUI_CCHKEYMAX];
   char    szKey[OLEUI_CCHKEYMAX];
   LPSTR   lpszTemp, lpszExe;

   lRet = RegOpenKey(HKEY_CLASSES_ROOT, NULL, &hKey);

   if (ERROR_SUCCESS != lRet)
     return FALSE;

   dw = OLEUI_CCHPATHMAX;
   lRet = RegQueryValue(hKey, lpszExtension, szValue, &dw);  //ProgId

   if (ERROR_SUCCESS != lRet)
   {
     RegCloseKey(hKey);
     return FALSE;
   }

   // szValue now has ProgID
   lstrcpy(szKey, szValue);
   lstrcat(szKey, "\\Shell\\Open\\Command");

   dw = OLEUI_CCHPATHMAX;
   lRet = RegQueryValue(hKey, szKey, szValue, &dw);

   if (ERROR_SUCCESS != lRet)
   {
     RegCloseKey(hKey);
     return FALSE;
   }

   // szValue now has an executable name in it.  Let's null-terminate
   // at the first post-executable space (so we don't have cmd line
   // args.

   lpszTemp = szValue;

   while (('\0' != *lpszTemp) && (isspace(*lpszTemp)))
     lpszTemp++;     // Strip off leading spaces

   lpszExe = lpszTemp;

   while (('\0' != *lpszTemp) && (!isspace(*lpszTemp)))
     lpszTemp++;     // Step through exe name

   *lpszTemp = '\0';  // null terminate at first space (or at end).

   lstrcpy(lpszExecutable, lpszExe);

   return TRUE;

}

/*
 * HIconAndSourceFromClass
 *
 * Purpose:
 *  Given an object class name, finds an associated executable in the
 *  registration database and extracts the first icon from that
 *  executable.  If none is available or the class has no associated
 *  executable, this function returns NULL.
 *
 * Parameters:
 *  rclsid          pointer to clsid to look up.
 *  pszSource       LPSTR in which to place the source of the icon.
 *                  This is assumed to be OLEUI_CCHPATHMAX
 *  puIcon          UINT FAR * in which to store the index of the
 *                  icon in pszSource.
 *
 * Return Value:
 *  HICON           Handle to the extracted icon if there is a module
 *                  associated to pszClass.  NULL on failure to either
 *                  find the executable or extract and icon.
 */

HICON FAR PASCAL HIconAndSourceFromClass(REFCLSID rclsid, LPSTR pszSource, UINT FAR *puIcon)
   {
   HICON           hIcon;
   UINT            IconIndex;

   if (NULL==rclsid || NULL==pszSource || IsEqualCLSID(rclsid,&CLSID_NULL))
      return NULL;

   if (!FIconFileFromClass(rclsid, pszSource, OLEUI_CCHPATHMAX, &IconIndex))
      return NULL;

   hIcon=ExtractIcon(ghInst, pszSource, IconIndex);

   if ((HICON)32 > hIcon)
      hIcon=NULL;
   else
      *puIcon= IconIndex;

   return hIcon;
   }

/*
 * PointerToNthField
 *
 * Purpose:
 *  Returns a pointer to the beginning of the nth field.
 *  Assumes null-terminated string.
 *
 * Parameters:
 *  lpszString        string to parse
 *  nField            field to return starting index of.
 *  chDelimiter       char that delimits fields
 *
 * Return Value:
 *  LPSTR             pointer to beginning of nField field.
 *                    NOTsE: If the null terminator is found
 *                          Before we find the Nth field, then
 *                          we return a pointer to the null terminator -
 *                          calling app should be sure to check for
 *                          this case.
 *
 */
LPSTR FAR PASCAL PointerToNthField(LPSTR lpszString, int nField, char chDelimiter)
{
   LPSTR lpField = lpszString;
   int   cFieldFound = 1;

   if (1 ==nField)
     return lpszString;

   while (*lpField != '\0')
   {

     if (*lpField++ == chDelimiter)
     {

       cFieldFound++;

       if (nField == cFieldFound)
         return lpField;
     }
   }

   return lpField;

}

/*
 * FIconFileFromClass
 *
 * Purpose:
 *  Looks up the path to executable that contains the class default icon.
 *
 * Parameters:
 *  rclsid          pointer to CLSID to look up.
 *  pszEXE          LPSTR at which to store the server name
 *  cch             UINT size of pszEXE
 *  lpIndex         LPUINT to index of icon within executable
 *
 * Return Value:
 *  BOOL            TRUE if one or more characters were loaded into pszEXE.
 *                  FALSE otherwise.
 */

BOOL FAR PASCAL FIconFileFromClass(REFCLSID rclsid, LPSTR pszEXE, UINT cch, UINT FAR *lpIndex)
{

   LONG          dw;
   LONG          lRet;
   HKEY          hKey;
   LPMALLOC      lpIMalloc;
   HRESULT       hrErr;
   LPSTR         lpBuffer;
   LPSTR         lpIndexString;
   UINT          cBufferSize = 136;// room for 128 char path and icon's index
   char          szKey[64];
   char          pszClass[256];
   LPOLESTR      pszUniClass;

   if (NULL==rclsid || NULL==pszEXE || 0==cch || IsEqualCLSID(rclsid,&CLSID_NULL))
      return FALSE;

   //Here, we use CoGetMalloc and alloc a buffer (maxpathlen + 8) to
   //pass to RegQueryValue.  Then, we copy the exe to pszEXE and the
   //index to *lpIndex.

   hrErr = CoGetMalloc(MEMCTX_TASK, &lpIMalloc);

   if (NOERROR != hrErr)
     return FALSE;

   lpBuffer = /*(LPSTR)*/lpIMalloc->lpVtbl->Alloc(lpIMalloc, cBufferSize);

   if (NULL == lpBuffer)
   {
     lpIMalloc->lpVtbl->Release(lpIMalloc);
     return FALSE;
   }

   if (CoIsOle1Class(rclsid))
   {

     LPOLESTR lpszUniProgID;
     char lpszProgID[256];

     // we've got an ole 1.0 class on our hands, so we look at
     // progID\protocol\stdfileedting\server to get the
     // name of the executable.

     ProgIDFromCLSID(rclsid, &lpszUniProgID);
     W2A (lpszUniProgID, lpszProgID, 256);

     //Open up the class key
     lRet=RegOpenKey(HKEY_CLASSES_ROOT, lpszProgID, &hKey);

     if (ERROR_SUCCESS != lRet)
     {
       lpIMalloc->lpVtbl->Free(lpIMalloc, lpszUniProgID);
       lpIMalloc->lpVtbl->Free(lpIMalloc, lpBuffer);
       lpIMalloc->lpVtbl->Release(lpIMalloc);
       return FALSE;
     }

     dw=(LONG)cBufferSize;
     lRet = RegQueryValue(hKey, "Protocol\\StdFileEditing\\Server", lpBuffer, &dw);

     if (ERROR_SUCCESS != lRet)
     {

       RegCloseKey(hKey);
       lpIMalloc->lpVtbl->Free(lpIMalloc, lpszUniProgID);
       lpIMalloc->lpVtbl->Free(lpIMalloc, lpBuffer);
       lpIMalloc->lpVtbl->Release(lpIMalloc);
       return FALSE;
     }

     // Use server and 0 as the icon index
     LSTRCPYN(pszEXE, lpBuffer, cch);

     *lpIndex = 0;

     RegCloseKey(hKey);
     lpIMalloc->lpVtbl->Free(lpIMalloc, lpszUniProgID);
     lpIMalloc->lpVtbl->Free(lpIMalloc, lpBuffer);
     lpIMalloc->lpVtbl->Release(lpIMalloc);
     return TRUE;

   }

   /*
    * We have to go walking in the registration database under the
    * classname, so we first open the classname key and then check
    * under "\\DefaultIcon" to get the file that contains the icon.
    */

   StringFromCLSID(rclsid, &pszUniClass);
   W2A (pszUniClass, pszClass, 256);

   lstrcpy(szKey, "CLSID\\");
   lstrcat(szKey, pszClass);

   //Open up the class key
   lRet=RegOpenKey(HKEY_CLASSES_ROOT, szKey, &hKey);

   if (ERROR_SUCCESS != lRet)
   {
      lpIMalloc->lpVtbl->Free(lpIMalloc, lpBuffer);
      lpIMalloc->lpVtbl->Free(lpIMalloc, pszUniClass);
      lpIMalloc->lpVtbl->Release(lpIMalloc);
      return FALSE;
   }

   //Get the executable path and icon index.

   dw=(LONG)cBufferSize;
   lRet=RegQueryValue(hKey, "DefaultIcon", lpBuffer, &dw);

   if (ERROR_SUCCESS != lRet)
   {
     // no DefaultIcon  key...try LocalServer

     dw=(LONG)cBufferSize;
#ifdef WIN32
     lRet=RegQueryValue(hKey, "LocalServer32", lpBuffer, &dw);
#else
     lRet=RegQueryValue(hKey, "LocalServer", lpBuffer, &dw);
#endif

     if (ERROR_SUCCESS != lRet)
     {
       // no LocalServer entry either...they're outta luck.

       RegCloseKey(hKey);
       lpIMalloc->lpVtbl->Free(lpIMalloc, lpBuffer);
       lpIMalloc->lpVtbl->Free(lpIMalloc, pszUniClass);
       lpIMalloc->lpVtbl->Release(lpIMalloc);
       return FALSE;
     }

     // Use server from LocalServer or Server and 0 as the icon index
     LSTRCPYN(pszEXE, lpBuffer, cch);

     *lpIndex = 0;

     RegCloseKey(hKey);
     lpIMalloc->lpVtbl->Free(lpIMalloc, lpBuffer);
     lpIMalloc->lpVtbl->Free(lpIMalloc, pszUniClass);
     lpIMalloc->lpVtbl->Release(lpIMalloc);
     return TRUE;
   }

   RegCloseKey(hKey);

   // lpBuffer contains a string that looks like "<pathtoexe>,<iconindex>",
   // so we need to separate the path and the icon index.

   lpIndexString = PointerToNthField(lpBuffer, 2, ',');

   if ('\0' == *lpIndexString)  // no icon index specified - use 0 as default.
   {
      *lpIndex = 0;

   }
   else
   {
      LPSTR lpTemp;
      static char  szTemp[16];

      lstrcpy(szTemp, lpIndexString);

      // Put the icon index part into *pIconIndex
      *lpIndex = atoi((const char *)szTemp);

      // Null-terminate the exe part.
      lpTemp = AnsiPrev(lpBuffer, lpIndexString);
      *lpTemp = '\0';
   }

   if (!LSTRCPYN(pszEXE, lpBuffer, cch))
   {
      lpIMalloc->lpVtbl->Free(lpIMalloc, lpBuffer);
      lpIMalloc->lpVtbl->Free(lpIMalloc, pszUniClass);
      lpIMalloc->lpVtbl->Release(lpIMalloc);
      return FALSE;
   }

   // Free the memory we alloc'd and leave.
   lpIMalloc->lpVtbl->Free(lpIMalloc, lpBuffer);
   lpIMalloc->lpVtbl->Free(lpIMalloc, pszUniClass);
   lpIMalloc->lpVtbl->Release(lpIMalloc);
   return TRUE;
}

/*
 * OleStdIconLabelTextOut
 *
 * Purpose:
 *  Replacement for DrawText to be used in the "Display as Icon" metafile.
 *  Uses ExtTextOut to output a string center on (at most) two lines.
 *  Uses a very simple word wrap algorithm to split the lines.
 *
 * Parameters:  (same as for ExtTextOut, except for hFont)
 *  hDC           device context to draw into; if this is NULL, then we don't
 *                ETO the text, we just return the index of the beginning
 *                of the second line
 *  hFont         font to use
 *  nXStart       x-coordinate of starting position
 *  nYStart       y-coordinate of starting position
 *  fuOptions     rectangle type
 *  lpRect        rect far * containing rectangle to draw text in.
 *  lpszString    string to draw
 *  cchString     length of string (truncated if over OLEUI_CCHLABELMAX)
 *  lpDX          spacing between character cells
 *
 * Return Value:
 *  UINT          Index of beginning of last line (0 if there's only one
 *                line of text).
 *
 */

STDAPI_(UINT) OleStdIconLabelTextOut(HDC        hDC,
                            HFONT      hFont,
                            int        nXStart,
                            int        nYStart,
                            UINT       fuOptions,
                            RECT FAR * lpRect,
                            LPOLESTR   lpszString,
                            UINT       cchString,
                            int FAR *  lpDX)
{

  HDC          hDCScreen;
  static char  szTempBuff[OLEUI_CCHLABELMAX];
  int          cxString, cyString, cxMaxString;
  int          cxFirstLine, cyFirstLine, cxSecondLine;
  int          index;
  int          cch = cchString;
  char         chKeep;
  LPSTR        lpszSecondLine;
  HFONT        hFontT;
  BOOL         fPrintText = TRUE;
  UINT         iLastLineStart = 0;
  SIZE         size;

  // Initialization stuff...

  if (NULL == hDC)  // If we got NULL as the hDC, then we don't actually call ETO
   fPrintText = FALSE;

  // Make a copy of the string (NULL or non-NULL) that we're using
  if (NULL == lpszString)
   *szTempBuff = '\0';

  else
  {
     W2A (lpszString, szTempBuff, OLEUI_CCHLABELMAX);
  }

  // set maximum width
  cxMaxString = lpRect->right - lpRect->left;

  // get screen DC to do text size calculations
  hDCScreen = GetDC(NULL);

  hFontT=SelectObject(hDCScreen, hFont);

  // get the extent of our label
  GetTextExtentPoint(hDCScreen, szTempBuff, cch, &size);

  cxString = size.cx;
  cyString = size.cy;

  // Select in the font we want to use
  if (fPrintText)
    SelectObject(hDC, hFont);

  // String is smaller than max string - just center, ETO, and return.
  if (cxString <= cxMaxString)
  {

   if (fPrintText)
      ExtTextOut(hDC,
              nXStart + (lpRect->right - cxString) / 2,
              nYStart,
              fuOptions,
              lpRect,
              szTempBuff,
              cch,
              NULL);

   iLastLineStart = 0;  // only 1 line of text
   goto CleanupAndLeave;
  }

  // String is too long...we've got to word-wrap it.

  // Are there any spaces, slashes, tabs, or bangs in string?

  if (lstrlen(szTempBuff) != (int)strcspn(szTempBuff, szSeparators))
  {
    // Yep, we've got spaces, so we'll try to find the largest
    // space-terminated string that will fit on the first line.

    index = cch;

    while (index >= 0)
    {

      char cchKeep;

      // scan the string backwards for spaces, slashes, tabs, or bangs

      while (!IS_SEPARATOR(szTempBuff[index]) )
       index--;

      if (index <= 0)
       break;

      cchKeep = szTempBuff[index];  // remember what char was there

      szTempBuff[index] = '\0';  // just for now

      GetTextExtentPoint(
            hDCScreen, szTempBuff,lstrlen(szTempBuff),&size);

      cxFirstLine = size.cx;
      cyFirstLine = size.cy;

      szTempBuff[index] = cchKeep;   // put the right char back

      if (cxFirstLine <= cxMaxString)
      {

         iLastLineStart = index + 1;

         if (!fPrintText)
          goto CleanupAndLeave;

         ExtTextOut(hDC,
                 nXStart +  (lpRect->right - cxFirstLine) / 2,
                 nYStart,
                 fuOptions,
                 lpRect,
                 szTempBuff,

index + 1,
                 lpDX);

         lpszSecondLine = szTempBuff;

         lpszSecondLine += index + 1;

         GetTextExtentPoint(hDCScreen,
                           lpszSecondLine,
                           lstrlen(lpszSecondLine),
                           &size);

         // If the second line is wider than the rectangle, we
         // just want to clip the text.
         cxSecondLine = min(size.cx, cxMaxString);

         ExtTextOut(hDC,
                 nXStart + (lpRect->right - cxSecondLine) / 2,
                 nYStart + cyFirstLine,
                 fuOptions,
                 lpRect,
                 lpszSecondLine,
                 lstrlen(lpszSecondLine),
                 lpDX);

         goto CleanupAndLeave;

      }  // end if

      index--;

    }  // end while

  }  // end if

  // Here, there are either no spaces in the string (strchr(szTempBuff, ' ')
  // returned NULL), or there spaces in the string, but they are
  // positioned so that the first space terminated string is still
  // longer than one line. So, we walk backwards from the end of the
  // string until we find the largest string that will fit on the first
  // line , and then we just clip the second line.

  cch = lstrlen(szTempBuff);

  chKeep = szTempBuff[cch];
  szTempBuff[cch] = '\0';

  GetTextExtentPoint(hDCScreen, szTempBuff, lstrlen(szTempBuff),&size);

  cxFirstLine = size.cx;
  cyFirstLine = size.cy;

  while (cxFirstLine > cxMaxString)
  {
    // We allow 40 characters in the label, but the metafile is
    // only as wide as 10 W's (for aesthetics - 20 W's wide looked
    // dumb.  This means that if we split a long string in half (in
    // terms of characters), then we could still be wider than the
    // metafile.  So, if this is the case, we just step backwards
    // from the halfway point until we get something that will fit.
    // Since we just let ETO clip the second line

    szTempBuff[cch--] = chKeep;
    if (0 == cch)
      goto CleanupAndLeave;

    chKeep = szTempBuff[cch];
    szTempBuff[cch] = '\0';

    GetTextExtentPoint(
          hDCScreen, szTempBuff, lstrlen(szTempBuff), &size);
    cxFirstLine = size.cx;
  }

  iLastLineStart = cch;

  if (!fPrintText)
   goto CleanupAndLeave;

  ExtTextOut(hDC,
          nXStart + (lpRect->right - cxFirstLine) / 2,
          nYStart,
          fuOptions,
          lpRect,
          szTempBuff,
          lstrlen(szTempBuff),
          lpDX);

  szTempBuff[cch] = chKeep;
  lpszSecondLine = szTempBuff;
  lpszSecondLine += cch;

  GetTextExtentPoint(
        hDCScreen, lpszSecondLine, lstrlen(lpszSecondLine), &size);

  // If the second line is wider than the rectangle, we
  // just want to clip the text.
  cxSecondLine = min(size.cx, cxMaxString);

  ExtTextOut(hDC,
          nXStart + (lpRect->right - cxSecondLine) / 2,
          nYStart + cyFirstLine,
          fuOptions,
          lpRect,
          lpszSecondLine,
          lstrlen(lpszSecondLine),
          lpDX);

CleanupAndLeave:
  SelectObject(hDCScreen, hFontT);
  ReleaseDC(NULL, hDCScreen);
  return iLastLineStart;

}


HATCH.C

/*
 * HATCH.C
 *
 * Miscellaneous API's to generate hatch window for in-place active
 * objects. This is part of the OLE 2.0 User Interface Support Library.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Right Reserved
 */

#define STRICT  1
#include "olestd.h"

// offsets in the extra bytes stored with the hatch window
#define EB_HATCHWIDTH       0
#define EB_HATCHRECT_LEFT   2
#define EB_HATCHRECT_TOP    4
#define EB_HATCHRECT_RIGHT  6
#define EB_HATCHRECT_BOTTOM 8

// class name of hatch window
#define CLASS_HATCH     "Hatch Window"

// local function prototypes
LRESULT FAR PASCAL EXPORT HatchWndProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam);

/*
 * HatchRegisterClass
 *
 * Purpose:
 *  Register the hatch window
 *
 * Parameters:
 *  hInst           Process instance
 *
 * Return Value:
 *  TRUE            if successful
 *  FALSE           if failed
 *
 */
STDAPI_(BOOL) RegisterHatchWindowClass(HINSTANCE hInst)
{
   WNDCLASS wc;

   // Register Hatch Window Class
   wc.style = CS_BYTEALIGNWINDOW;
   wc.lpfnWndProc = HatchWndProc;
   wc.cbClsExtra = 0;
   wc.cbWndExtra = 5 * sizeof(int);    // extra bytes stores
                              //     uHatchWidth
                              //     rcHatchRect
   wc.hInstance = hInst;
   wc.hIcon = NULL;
   wc.hCursor = LoadCursor(NULL, IDC_ARROW);
   wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
   wc.lpszMenuName = NULL;
   wc.lpszClassName = CLASS_HATCH;

   if (!RegisterClass(&wc))
      return FALSE;
   else
      return TRUE;
}

/*
 * CreateHatchWindow
 *
 * Purpose:
 *  Create the hatch window
 *
 * Parameters:
 *  hWndParent          parent of hatch window
 *  hInst               instance handle
 *
 * Return Value:
 *  pointer to hatch window         if successful
 *  NULL                            if failed
 *
 */
STDAPI_(HWND) CreateHatchWindow(HWND hWndParent, HINSTANCE hInst)
{
   HWND         hWnd;

   if (!hWndParent || !hInst)
      return NULL;

   hWnd = CreateWindow(
      CLASS_HATCH,
      "Hatch Window",
      WS_CHILDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS,
      0, 0, 0, 0,
      hWndParent,
      (HMENU)NULL,
      hInst,
      0L
   );

   if (!hWnd)
      return NULL;

   return hWnd;
}

/*
 *  GetHatchWidth
 *
 *  Purpose:
 *      Get width of hatch border
 *
 *  Parameters:
 *      hWndHatch       hatch window handle
 *
 *  Return Value:
 *      width of the hatch border
 */
STDAPI_(UINT) GetHatchWidth(HWND hWndHatch)
{
   if (!IsWindow(hWndHatch))
      return 0;

   return (UINT)GetWindowWord(hWndHatch, EB_HATCHWIDTH);
}

/*
 *  GetHatchRect
 *
 *  Purpose:
 *      Get hatch rect. this is the size of the hatch window if it were
 *      not clipped by the ClipRect.
 *
 *  Parameters:
 *      hWndHatch       hatch window handle
 *      lprcHatchRect   hatch rect
 *
 *  Return Value:
 *      none
 */
STDAPI_(void) GetHatchRect(HWND hWndHatch, LPRECT lprcHatchRect)
{
   if (!IsWindow(hWndHatch)) {
      SetRect(lprcHatchRect, 0, 0, 0, 0);
      return;
   }

   lprcHatchRect->left = GetWindowWord(hWndHatch, EB_HATCHRECT_LEFT);
   lprcHatchRect->top = GetWindowWord(hWndHatch, EB_HATCHRECT_TOP);
   lprcHatchRect->right = GetWindowWord(hWndHatch, EB_HATCHRECT_RIGHT);
   lprcHatchRect->bottom = GetWindowWord(hWndHatch, EB_HATCHRECT_BOTTOM);
}

/* SetHatchRect
 *
 *
 *  Purpose:
 *      Store hatch rect with HatchRect window.
 *      this rect is the size of the hatch window if it were
 *      not clipped by the ClipRect.
 *
 *  Parameters:
 *      hWndHatch       hatch window handle
 *      lprcHatchRect   hatch rect
 *
 *  Return Value:
 *      none
 */
STDAPI_(void) SetHatchRect(HWND hWndHatch, LPRECT lprcHatchRect)
{
   if (!IsWindow(hWndHatch))
      return;

   SetWindowWord(hWndHatch, EB_HATCHRECT_LEFT,  (WORD)lprcHatchRect->left);
   SetWindowWord(hWndHatch, EB_HATCHRECT_TOP,   (WORD)lprcHatchRect->top);
   SetWindowWord(hWndHatch, EB_HATCHRECT_RIGHT, (WORD)lprcHatchRect->right);
   SetWindowWord(hWndHatch, EB_HATCHRECT_BOTTOM,(WORD)lprcHatchRect->bottom);
}

/* SetHatchWindowSize
 *
 *
 *  Purpose:
 *      Move/size the HatchWindow correctly given the rect required by the
 *      in-place server object window and the lprcClipRect imposed by the
 *      in-place container. both rect's are expressed in the client coord.
 *      of the in-place container's window (which is the parent of the
 *      HatchWindow).
 *
 *      NOTE: the in-place server must honor the lprcClipRect specified
 *      by its in-place container. it must NOT draw outside of the ClipRect.
 *      in order to achieve this, the hatch window is sized to be
 *      exactly the size that should be visible (rcVisRect). the
 *      rcVisRect is defined as the intersection of the full size of
 *      the HatchRect window and the lprcClipRect.
 *      the ClipRect could infact clip the HatchRect on the
 *      right/bottom and/or on the top/left. if it is clipped on the
 *      right/bottom then it is sufficient to simply resize the hatch
 *      window. but if the HatchRect is clipped on the top/left then
 *      in-place server document window (child of HatchWindow) must be moved
 *      by the delta that was clipped. the window origin of the
 *      in-place server window will then have negative coordinates relative
 *      to its parent HatchWindow.
 *
 *  Parameters:
 *      hWndHatch       hatch window handle
 *      lprcIPObjRect   full size of in-place server object window
 *      lprcClipRect    clipping rect imposed by in-place container
 *      lpptOffset      offset required to position in-place server object
 *                      window properly. caller should call:
 *                          OffsetRect(&rcObjRect,lpptOffset->x,lpptOffset->y)
 *
 *  Return Value:
 *      none
 */
STDAPI_(void) SetHatchWindowSize(
      HWND        hWndHatch,
      LPRECT      lprcIPObjRect,
      LPRECT      lprcClipRect,
      LPPOINT       lpptOffset
)
{
   RECT        rcHatchRect;
   RECT        rcVisRect;
   UINT        uHatchWidth;
   POINT       ptOffset;

   if (!IsWindow(hWndHatch))
      return;

   rcHatchRect = *lprcIPObjRect;
   uHatchWidth = GetHatchWidth(hWndHatch);
   InflateRect((LPRECT)&rcHatchRect, uHatchWidth + 1, uHatchWidth + 1);

   IntersectRect((LPRECT)&rcVisRect, (LPRECT)&rcHatchRect, lprcClipRect);
   MoveWindow(
         hWndHatch,
         rcVisRect.left,
         rcVisRect.top,
         rcVisRect.right-rcVisRect.left,
         rcVisRect.bottom-rcVisRect.top,
         TRUE    /* fRepaint */
   );
   InvalidateRect(hWndHatch, NULL, TRUE);

   ptOffset.x = -rcHatchRect.left + (rcHatchRect.left - rcVisRect.left);
   ptOffset.y = -rcHatchRect.top + (rcHatchRect.top - rcVisRect.top);

   /* convert the rcHatchRect into the client coordinate system of the
   **    HatchWindow itself
   */
   OffsetRect((LPRECT)&rcHatchRect, ptOffset.x, ptOffset.y);

   SetHatchRect(hWndHatch, (LPRECT)&rcHatchRect);

   // calculate offset required to position in-place server doc window
   lpptOffset->x = ptOffset.x;
   lpptOffset->y = ptOffset.y;
}

/*
 *  HatchWndProc
 *
 *  Purpose:
 *      WndProc for hatch window
 *
 *  Parameters:
 *      hWnd
 *      Message
 *      wParam
 *      lParam
 *
 *  Return Value:
 *      message dependent
 */
LRESULT FAR PASCAL EXPORT HatchWndProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
   int nBorderWidth;

   switch (Message) {

      case WM_CREATE:
         nBorderWidth = GetProfileInt(
            "windows",
            "oleinplaceborderwidth",
            DEFAULT_HATCHBORDER_WIDTH
         );
         SetWindowWord(hWnd, EB_HATCHWIDTH, (WORD)nBorderWidth);
         break;

      case WM_PAINT:
      {
         HDC hDC;
         PAINTSTRUCT ps;
         RECT rcHatchRect;

         nBorderWidth = GetHatchWidth(hWnd);
         hDC = BeginPaint(hWnd, &ps);
         GetHatchRect(hWnd, (LPRECT)&rcHatchRect);
         OleUIDrawShading(&rcHatchRect, hDC, OLEUI_SHADE_BORDERIN,
               nBorderWidth);
         InflateRect((LPRECT)&rcHatchRect, -nBorderWidth, -nBorderWidth);
         OleUIDrawHandles(&rcHatchRect, hDC, OLEUI_HANDLES_OUTSIDE,
               nBorderWidth+1, TRUE);
         EndPaint(hWnd, &ps);
         break;
      }

      /* NOTE: Any window that is used during in-place activation
      **    must handle the WM_SETCURSOR message or else the cursor
      **    of the in-place parent will be used. if WM_SETCURSOR is
      **    not handled, then DefWindowProc sends the message to the
      **    window's parent.
      */
      case WM_SETCURSOR:
         SetCursor(LoadCursor( NULL, MAKEINTRESOURCE(IDC_ARROW) ) );
         return (LRESULT)TRUE;

      default:
         return DefWindowProc(hWnd, Message, wParam, lParam);
   }

   return 0L;
}


MALSPY.C

//+---------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1994 - 1996.
//
//  File:       cmallspy.c
//
//  Contents:   Sample implementation of IMallocSpy.  The implementation here
//              is pretty simple.  It assumes that all we want to do is keep
//              a header on the front of all allocations so we can note
//              interesting information.
//
//  Classes:    CMallocSpy
//
//----------------------------------------------------------------------

#include <ole2.h>
#include <olestd.h>

OLEDBGDATA

#include "malspy.h"

#define TOKEN 0xDEAD

typedef struct tagHeader
{
    UINT    uiTag;
    ULONG   cbSize;
} HEADER, * LPHEADER;

// The following macro is used to ensure that the token placed at the
// end of each memory block is always aligned on a dword boundary.
// This is necessary to avoid alignment faults on some systems.
#define DWORDALIGN(x) ((x + 3) & (~3))

typedef struct CMallocSpy {
  IMallocSpyVtbl FAR*     lpVtbl;
  ULONG                   m_cRef;   // interface specific ref count.
  ULONG                   m_cbRequested;
} CMALLOCSPY, *LPCMALLOCSPY;

STDMETHODIMP MallocSpy_QueryInterface(
      LPMALLOCSPY             this,
      REFIID                  riid,
      LPVOID FAR*             lplpUnk);

STDMETHODIMP_(ULONG) MallocSpy_AddRef(
    LPMALLOCSPY this);

STDMETHODIMP_(ULONG) MallocSpy_Release(
    LPMALLOCSPY this);

STDMETHODIMP_(ULONG) MallocSpy_PreAlloc(
    LPMALLOCSPY this,
    ULONG cbRequest);

STDMETHODIMP_(void *) MallocSpy_PostAlloc(
    LPMALLOCSPY this,
    void *pActual);

STDMETHODIMP_(void *) MallocSpy_PreFree(
    LPMALLOCSPY this,
    void *pRequest, BOOL fSpyed);

STDMETHODIMP_(void) MallocSpy_PostFree(
    LPMALLOCSPY this,
    BOOL fSpyed);

STDMETHODIMP_(ULONG) MallocSpy_PreRealloc(
    LPMALLOCSPY this,
    void *pRequest,
    ULONG cbRequest,
    void **ppNewRequest,
    BOOL fSpyed);

STDMETHODIMP_(void *) MallocSpy_PostRealloc(
    LPMALLOCSPY this,
    void *pActual,
    BOOL fSpyed);

STDMETHODIMP_(void *) MallocSpy_PreGetSize(
    LPMALLOCSPY this,
    void *pRequest,
    BOOL fSpyed);

STDMETHODIMP_(ULONG) MallocSpy_PostGetSize(
    LPMALLOCSPY this,
    ULONG cbActual,
    BOOL fSpyed);

STDMETHODIMP_(void *) MallocSpy_PreDidAlloc(
    LPMALLOCSPY this,
    void *pRequest,
    BOOL fSpyed);

STDMETHODIMP_(BOOL) MallocSpy_PostDidAlloc(
    LPMALLOCSPY this,
    void *pRequest,
    BOOL fSpyed,
    BOOL fActual);

STDMETHODIMP_(void) MallocSpy_PreHeapMinimize(
    LPMALLOCSPY this);

STDMETHODIMP_(void) MallocSpy_PostHeapMinimize(
    LPMALLOCSPY this);

IMallocSpyVtbl g_MallocSpyVtbl;

BOOL InitializeMallocSpy(void)
{
    BOOL fStatus;
    LPCMALLOCSPY lpcMallocSpy = NULL;
    HRESULT hr;

    OleStdInitVtbl(
          &g_MallocSpyVtbl,
          sizeof(g_MallocSpyVtbl));

    g_MallocSpyVtbl.QueryInterface      = MallocSpy_QueryInterface;
    g_MallocSpyVtbl.AddRef              = MallocSpy_AddRef;
    g_MallocSpyVtbl.Release             = MallocSpy_Release;
    g_MallocSpyVtbl.PreAlloc            = MallocSpy_PreAlloc;
    g_MallocSpyVtbl.PostAlloc           = MallocSpy_PostAlloc;
    g_MallocSpyVtbl.PreFree             = MallocSpy_PreFree;
    g_MallocSpyVtbl.PostFree            = MallocSpy_PostFree;
    g_MallocSpyVtbl.PreRealloc          = MallocSpy_PreRealloc;
    g_MallocSpyVtbl.PostRealloc         = MallocSpy_PostRealloc;
    g_MallocSpyVtbl.PreGetSize          = MallocSpy_PreGetSize;
    g_MallocSpyVtbl.PostGetSize         = MallocSpy_PostGetSize;
    g_MallocSpyVtbl.PreDidAlloc         = MallocSpy_PreDidAlloc;
    g_MallocSpyVtbl.PostDidAlloc        = MallocSpy_PostDidAlloc;
    g_MallocSpyVtbl.PreHeapMinimize     = MallocSpy_PreHeapMinimize;
    g_MallocSpyVtbl.PostHeapMinimize    = MallocSpy_PostHeapMinimize;

    fStatus = OleStdCheckVtbl(
          &g_MallocSpyVtbl,
          sizeof(g_MallocSpyVtbl),
          OLESTR("IMallocSpy"));
    if (! fStatus)
        return FALSE;

    lpcMallocSpy = malloc(sizeof(CMALLOCSPY));

    if (NULL == lpcMallocSpy)
    {
        return FALSE;
    }
    lpcMallocSpy->lpVtbl = &g_MallocSpyVtbl;
    lpcMallocSpy->m_cRef = 0;

    hr = CoRegisterMallocSpy((LPMALLOCSPY)lpcMallocSpy);

    return(SUCCEEDED(hr));
}

void UninitializeMallocSpy()
{
    CoRevokeMallocSpy();
}

//+---------------------------------------------------------------------------
//
//  Function:   CbUserFromCbActual
//
//  Synopsis:   adjusts the size to account for the header and footer
//
//  Arguments:  [cbActual] - actual size of the memory block
//
//  Returns:    size reported to the user
//
//----------------------------------------------------------------------------

ULONG CbUserFromCbActual(ULONG cbActual)
{
    return(cbActual - (sizeof(HEADER) + sizeof(UINT)));
}

//+---------------------------------------------------------------------------
//
//  Function:   CbActualFromCbUser
//
//  Synopsis:   adjusts the size to account for the header and footer
//
//  Arguments:  [cbUser] - size reported to the user
//
//  Returns:    actual size of the memory block
//
//----------------------------------------------------------------------------

ULONG CbActualFromCbUser(ULONG cbUser)
{
    return(DWORDALIGN(cbUser) + sizeof(HEADER) + sizeof(UINT));
}

//+---------------------------------------------------------------------------
//
//  Function:   PActualFromPUser
//
//  Synopsis:   adjusts the pointer to account for the header, verifying
//              the memory integrity in the process
//
//  Arguments:  [pUser] - pointer reported to the user
//
//  Returns:    acutal pointer to the memory block
//
//----------------------------------------------------------------------------

void * PActualFromPUser(void * pUser)
{
    HEADER * pHeader;
    UINT * puiFooter;
    pHeader = ((LPHEADER)pUser) - 1;
    puiFooter = (UINT *)(((BYTE *)pUser) + DWORDALIGN(pHeader->cbSize));

    OleDbgAssertSz(TOKEN == pHeader->uiTag, "Illegal memory block.");
    OleDbgAssertSz(TOKEN == *puiFooter, "Memory block was overrun.");

    if (TOKEN != pHeader->uiTag)
    {
        // Just to be safe, if we don't find the TOKEN we'll not adjust the
        // pointer
        return(pUser);
    }
    return ((void *) (pHeader));
}

//+---------------------------------------------------------------------------
//
//  Function:   PUserFromPActual
//
//  Synopsis:   adjusts the pointer to account for the header, initializing
//              the header and footer in the process to allow verification of
//              memory integrity
//
//  Arguments:  [pActual]    - actual pointer to the memory block
//              [cbDataSize] - size of the memory block requested by the user
//
//  Returns:    pointer to the data which the user may modify
//
//----------------------------------------------------------------------------

void * PUserFromPActual(void * pActual, ULONG cbDataSize)
{
    // Mark the block with a TOKEN at the front and the end
    // and its size so we can tell if it is bogus
    LPHEADER pHeader = pActual;
    UINT* puiFooter;

    pHeader->uiTag = TOKEN;
    pHeader->cbSize = cbDataSize;

    pActual = (void *)(pHeader + 1);

    puiFooter = (UINT *)(((BYTE *) pActual) + DWORDALIGN(pHeader->cbSize));
    *puiFooter = TOKEN;
    return(pActual);
}

STDMETHODIMP MallocSpy_QueryInterface(
      LPMALLOCSPY  this,
      REFIID riid,
      LPVOID *                ppUnk)
{
    HRESULT hr = S_OK;

    if (IsEqualIID(riid, &IID_IUnknown) || IsEqualIID(riid, &IID_IMallocSpy))
    {
        *ppUnk = this;
    }
    else
    {
        *ppUnk = NULL;
        hr =  E_NOINTERFACE;
    }
    this->lpVtbl->AddRef((LPMALLOCSPY)this);
    return hr;
}

STDMETHODIMP_(ULONG) MallocSpy_AddRef(
    LPMALLOCSPY this)
{
    return ++(((LPCMALLOCSPY)this)->m_cRef);
}

STDMETHODIMP_(ULONG) MallocSpy_Release(
    LPMALLOCSPY this)
{
    ULONG cRef;

    cRef = --(((LPCMALLOCSPY)this)->m_cRef);
    if (cRef == 0)
    {
        free(this);
    }
    return cRef;
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::PreAlloc
//
//  Synopsis:   Called prior to OLE calling IMalloc::Alloc
//
//  Arguments:  [cbRequest] -- The number of bytes the caller of
//                             is requesting IMalloc::Alloc
//
//  Returns:    The count of bytes to actually allocate
//
//----------------------------------------------------------------------
STDMETHODIMP_(ULONG) MallocSpy_PreAlloc(
    LPMALLOCSPY this,
    ULONG cbRequest)
{
    ((LPCMALLOCSPY)this)->m_cbRequested = cbRequest;
    return CbActualFromCbUser(cbRequest);
}

#ifdef TRACEMEM
char szScratch[256];
#endif

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::PostAlloc
//
//  Synopsis:   Called after OLE calls IMalloc::Alloc
//
//  Arguments:  [pActual] -- The allocation returned by IMalloc::Alloc
//
//  Returns:    The allocation pointer to return to the caller of
//              IMalloc::Alloc
//
//----------------------------------------------------------------------
STDMETHODIMP_(void *) MallocSpy_PostAlloc(
    LPMALLOCSPY this,
    void *pActual)
{
    void * pUser = PUserFromPActual(pActual, ((LPCMALLOCSPY)this)->m_cbRequested);

#ifdef TRACEMEM
    wsprintf(szScratch, "IMalloc: 0x%08lX allocated\n", pUser);
    OleDbgOut1(szScratch);
#endif

    return(pUser);
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::PreFree
//
//  Synopsis:   Called prior to OLE calling IMalloc::Free
//
//  Arguments:  [pRequest] -- The allocation to be freed
//              [fSpyed]   -- Whether it was allocated with a spy active
//
//----------------------------------------------------------------------
STDMETHODIMP_(void *) MallocSpy_PreFree(
    LPMALLOCSPY this,
    void *pRequest, BOOL fSpyed)
{
    // Check for NULL
    if (pRequest == NULL)
    {
        return NULL;
    }

#ifdef TRACEMEM
    wsprintf(szScratch, "IMalloc: 0x%08lX freeing\n", pRequest);
    OleDbgOut1(szScratch);
#endif

    // Undo the offset
    if (fSpyed)
    {
        return(PActualFromPUser(pRequest));
    }
    else
    {
        return pRequest;
    }
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::PostFree
//
//  Synopsis:   Called after OLE calls IMalloc::Free
//
//  Arguments:  [fSpyed]   -- Whether it was allocated with a spy active
//
//  Returns:
//
//----------------------------------------------------------------------
STDMETHODIMP_(void) MallocSpy_PostFree(
    LPMALLOCSPY this,
    BOOL fSpyed)
{
    return;
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::PreRealloc
//
//  Synopsis:   Called prior to OLE calling IMalloc::Realloc
//
//  Arguments:  [pRequest]     -- The buffer to be reallocated
//              [cbRequest]    -- The requested new size of the buffer
//              [ppNewRequest] -- Where to store the new buffer pointer
//                                to be reallocated
//              [fSpyed]       -- Whether it was allocated with a spy active
//
//  Returns:    The new size to actually be allocated
//
//----------------------------------------------------------------------
STDMETHODIMP_(ULONG) MallocSpy_PreRealloc(
    LPMALLOCSPY this,
    void *pRequest,
    ULONG cbRequest,
    void **ppNewRequest,
    BOOL fSpyed)
{
#ifdef TRACEMEM
    wsprintf(szScratch, "IMalloc: 0x%08lX realocating\n", pRequest);
    OleDbgOut1(szScratch);
#endif

    if (fSpyed)
    {
        *ppNewRequest = PActualFromPUser(pRequest);
        ((LPCMALLOCSPY)this)->m_cbRequested = cbRequest;
        return CbActualFromCbUser(cbRequest);
    }
    else
    {
        *ppNewRequest = pRequest;
        return cbRequest;
    }
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::PostRealloc
//
//  Synopsis:   Called after OLE calls IMalloc::Realloc
//
//  Arguments:  [pActual] -- Pointer to the reallocated buffer
//              [fSpyed]  -- Whether it was allocated with a spy active
//
//  Returns:    The buffer pointer to return
//
//----------------------------------------------------------------------
STDMETHODIMP_(void *) MallocSpy_PostRealloc(
    LPMALLOCSPY this,
    void *pActual,
    BOOL fSpyed)
{
    void * pUser;

    if (fSpyed)
    {
        pUser = PUserFromPActual(pActual, ((LPCMALLOCSPY)this)->m_cbRequested);
    }
    else
    {
        pUser = pActual;
    }

#ifdef TRACEMEM
    wsprintf(szScratch, "IMalloc: 0x%08lX reallocated\n", pUser);
    OleDbgOut1(szScratch);
#endif

    return(pUser);
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::PreGetSize
//
//  Synopsis:   Called prior to OLE calling IMalloc::GetSize
//
//  Arguments:  [pRequest] -- The buffer whose size is to be returned
//              [fSpyed]   -- Whether it was allocated with a spy active
//
//  Returns:    The actual buffer with which to call IMalloc::GetSize
//
//----------------------------------------------------------------------
STDMETHODIMP_(void *) MallocSpy_PreGetSize(
    LPMALLOCSPY this,
    void *pRequest,
    BOOL fSpyed)
{
    if (fSpyed)
    {
        return(PActualFromPUser(pRequest));
    }else
    {
        return pRequest;
    }
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::PostGetSize
//
//  Synopsis:   Called after OLE calls IMalloc::GetSize
//
//  Arguments:  [cbActual] -- The result of IMalloc::GetSize
//              [fSpyed]   -- Whether it was allocated with a spy active
//
//  Returns:    The size to return to the IMalloc::GetSize caller
//
//----------------------------------------------------------------------
STDMETHODIMP_(ULONG) MallocSpy_PostGetSize(
    LPMALLOCSPY this,
    ULONG cbActual,
    BOOL fSpyed)
{
    if (fSpyed)
    {
        return CbUserFromCbActual(cbActual);
    }
    else
    {
        return cbActual;
    }
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::PreDidAlloc
//
//  Synopsis:   Called prior to OLE calling IMalloc::DidAlloc
//
//  Arguments:  [pRequest] -- The buffer whose allocation is being tested
//              [fSpyed]   -- Whether it was allocated with a spy active
//
//  Returns:    The buffer whose allocation is actually to be tested
//
//----------------------------------------------------------------------
STDMETHODIMP_(void *) MallocSpy_PreDidAlloc(
    LPMALLOCSPY this,
    void *pRequest,
    BOOL fSpyed)
{
    if (fSpyed)
    {
        return(PActualFromPUser(pRequest));
    }
    else
    {
        return pRequest;
    }
}

//+---------------------------------------------------------------------
//
//  Function:   PostDidAlloc
//
//  Synopsis:   Called after OLE calls the IMalloc::DidAlloc
//
//  Arguments:  [pRequest] -- The passed allocation
//              [fSpyed]   -- Whether it was allocated with a spy active
//              [fActual]  -- The result of IMalloc::DidAlloc
//
//  Returns:    The result of IMalloc::DidAlloc
//
//----------------------------------------------------------------------
STDMETHODIMP_(BOOL) MallocSpy_PostDidAlloc(
    LPMALLOCSPY this,
    void *pRequest,
    BOOL fSpyed,
    BOOL fActual)
{
    return fActual;
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::PreHeapMinimize
//
//  Synopsis:   Called prior to OLE calling the IMalloc::HeapMinimize
//
//  Returns:
//
//----------------------------------------------------------------------
STDMETHODIMP_(void) MallocSpy_PreHeapMinimize(
    LPMALLOCSPY this)
{
    return;
}

//+---------------------------------------------------------------------
//
//  Member:     CMallocSpy::PostHeapMinimize
//
//  Synopsis:   Called after OLE calls the IMalloc::HeapMinimize
//
//  Returns:
//
//----------------------------------------------------------------------
STDMETHODIMP_(void) MallocSpy_PostHeapMinimize(
    LPMALLOCSPY this)
{
    return;
}


MALSPY.H

//+---------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright (C) Microsoft Corporation, 1993 - 1996.
//
//  File:       malspy.h
//
//  Contents:   CMallocSpy definitions
//
//----------------------------------------------------------------------

BOOL InitializeMallocSpy(void);

void UninitializeMallocSpy(void);


MSGFILTR.C

/*
 *    MSGFILTR.C
 *
 *    This file contains a standard implementation of IMessageFilter
 *    interface.
 *    This file is part of the OLE 2.0 User Interface support library.
 *
 *    (c) Copyright Microsoft Corp. 1990 - 1996 All Rights Reserved
 *
 */

#define STRICT  1
#include "olestd.h"
#include "msgfiltr.h"

OLEDBGDATA

typedef struct tagOLESTDMESSAGEFILTER {
   IMessageFilterVtbl FAR* m_lpVtbl;
   UINT                    m_cRef;
   HWND                    m_hWndParent;
   DWORD                   m_dwInComingCallStatus; // Status to return from
                                       // HandleIncomingCall
   HANDLEINCOMINGCALLBACKPROC m_lpfnHandleInComingCallback;
                                       // Callback function
                                       // to selectively handle
                                       // interface method calls
   BOOL                    m_fEnableBusyDialog;    // enable RetryRejected
                                       //  Call dialog
   BOOL                    m_fEnableNotRespondingDialog; // enable
                                       // MessagePending dialog
   MSGPENDINGPROC          m_lpfnMessagePendingCallback; // MessagePending
                                       // Callback function
   LPFNOLEUIHOOK           m_lpfnBusyDialogHookCallback; // Busy dialog hook
   LPOLESTR                m_lpszAppName;          // Name of application
                                       // installing filter
   HWND                    m_hWndBusyDialog;       // HWND of busy dialog.  Used
                                       // to tear down dialog.
   BOOL                    m_bUnblocking;

 }OLESTDMESSAGEFILTER, FAR* LPOLESTDMESSAGEFILTER;

/* interface IMessageFilter implementation */
STDMETHODIMP OleStdMsgFilter_QueryInterface(
      LPMESSAGEFILTER lpThis, REFIID riid, LPVOID FAR* ppvObj);
STDMETHODIMP_(ULONG) OleStdMsgFilter_AddRef(LPMESSAGEFILTER lpThis);
STDMETHODIMP_(ULONG) OleStdMsgFilter_Release(LPMESSAGEFILTER lpThis);
STDMETHODIMP_(DWORD) OleStdMsgFilter_HandleInComingCall (
      LPMESSAGEFILTER     lpThis,
      DWORD               dwCallType,
      HTASK               htaskCaller,
      DWORD               dwTickCount,
      LPINTERFACEINFO     dwReserved
);
STDMETHODIMP_(DWORD) OleStdMsgFilter_RetryRejectedCall (
      LPMESSAGEFILTER     lpThis,
      HTASK               htaskCallee,
      DWORD               dwTickCount,
      DWORD               dwRejectType
);
STDMETHODIMP_(DWORD) OleStdMsgFilter_MessagePending (
      LPMESSAGEFILTER     lpThis,
      HTASK               htaskCallee,
      DWORD               dwTickCount,
      DWORD               dwPendingType
);

static IMessageFilterVtbl g_OleStdMessageFilterVtbl = {
   OleStdMsgFilter_QueryInterface,
   OleStdMsgFilter_AddRef,
   OleStdMsgFilter_Release,
   OleStdMsgFilter_HandleInComingCall,
   OleStdMsgFilter_RetryRejectedCall,
   OleStdMsgFilter_MessagePending
};

/* GetTopWindowInWindowsTask
** -------------------------
**    Get the top most window that has focus in the given task to be
**    used as the parent for the busy dialog. we do this to handle the
**    case where a dialog window is currently up when we need to give
**    the busy dialog. if we use the current assigned parent window
**    (which typically will be the frame window of the app), then the
**    busy dialog will not be modal to the current active dialog
**    window.
*/
static HWND GetTopWindowInWindowsTask(HWND hwnd)
{
   HWND hwndActive = GetActiveWindow();
   if (!hwndActive)
      return hwnd;

   if (GetWindowTask(hwnd) == GetWindowTask(hwndActive))
      return hwndActive;
   else
      return hwnd;
}

STDAPI_(LPMESSAGEFILTER) OleStdMsgFilter_Create(
      HWND            hWndParent,
      LPOLESTR           szAppName,
      MSGPENDINGPROC  lpfnCallback,
      LPFNOLEUIHOOK   lpfnOleUIHook         // Busy dialog hook callback
)
{
   LPOLESTDMESSAGEFILTER lpStdMsgFilter;
   LPMALLOC lpMalloc;

   if (CoGetMalloc(MEMCTX_TASK, (LPMALLOC FAR*)&lpMalloc) != NOERROR)
      return NULL;

   lpStdMsgFilter = (LPOLESTDMESSAGEFILTER)lpMalloc->lpVtbl->Alloc(
         lpMalloc, (sizeof(OLESTDMESSAGEFILTER)));
   lpMalloc->lpVtbl->Release(lpMalloc);
   if (! lpStdMsgFilter) return NULL;

   lpStdMsgFilter->m_lpVtbl = &g_OleStdMessageFilterVtbl;
   lpStdMsgFilter->m_cRef = 1;
   lpStdMsgFilter->m_hWndParent = hWndParent;
   lpStdMsgFilter->m_dwInComingCallStatus = SERVERCALL_ISHANDLED;
   lpStdMsgFilter->m_lpfnHandleInComingCallback = NULL;
   lpStdMsgFilter->m_fEnableBusyDialog = TRUE;
   lpStdMsgFilter->m_fEnableNotRespondingDialog = TRUE;
   lpStdMsgFilter->m_lpszAppName = szAppName;
   lpStdMsgFilter->m_lpfnMessagePendingCallback = lpfnCallback;
   lpStdMsgFilter->m_lpfnBusyDialogHookCallback = lpfnOleUIHook;
   lpStdMsgFilter->m_hWndBusyDialog = NULL;
   lpStdMsgFilter->m_bUnblocking = FALSE;

  g_dwObjectCount ++;

   return (LPMESSAGEFILTER)lpStdMsgFilter;
}

/* OleStdMsgFilter_SetInComingStatus
** ---------------------------------
**    This is a private function that allows the caller to control what
**    value is returned from the IMessageFilter::HandleInComing method.
**
**    if a HandleInComingCallbackProc is installed by a call to
**    OleStdMsgFilter_SetHandleInComingCallbackProc, then this
**    overrides the dwIncomingCallStatus established by a call to
**    OleStdMsgFilter_SetInComingStatus.  Using
**    OleStdMsgFilter_SetInComingStatus allows the app to reject or
**    accept ALL in coming calls. Using a HandleInComingCallbackProc
**    allows the app to selectively handle or reject particular method
**    calls.
*/

STDAPI_(void) OleStdMsgFilter_SetInComingCallStatus(
      LPMESSAGEFILTER lpThis, DWORD dwInComingCallStatus)
{
   LPOLESTDMESSAGEFILTER lpStdMsgFilter = (LPOLESTDMESSAGEFILTER)lpThis;

   if (!IsBadWritePtr((LPVOID)lpStdMsgFilter,  sizeof(OLESTDMESSAGEFILTER)))
      lpStdMsgFilter->m_dwInComingCallStatus = dwInComingCallStatus;
   else
      OleDbgAssert(
         "OleStdMsgFilter_SetIncomingCallStatus: Invalid IMessageFilter*");

#if defined( _DEBUG )
   {
   char szBuf[80];
   char *szReturn;

   switch(dwInComingCallStatus) {
      case SERVERCALL_ISHANDLED:
         szReturn = "SERVERCALL_ISHANDLED";
         break;
      case SERVERCALL_REJECTED:
         szReturn = "SERVERCALL_REJECTED";
         break;
      case SERVERCALL_RETRYLATER:
         szReturn = "SERVERCALL_RETRYLATER";
         break;
      default:
         szReturn = "** ERROR: UNKNOWN **";
         break;
      }
   wsprintf(
      szBuf,
      "OleStdMsgFilter_SetInComingCallStatus: Status set to %s.\r\n",
      /*(LPSTR)*/szReturn
   );
   OleDbgOut3(szBuf);
   }
#endif

}

/* OleStdMsgFilter_SetHandleInComingCallbackProc
** ---------------------------------------------
**    This is a private function that allows the caller to install (or
**    de-install) a special callback function to selectively
**    handle/reject specific incoming method calls on particular
**    interfaces.
**
**    if a HandleInComingCallbackProc is installed by a call to
**    OleStdMsgFilter_SetHandleInComingCallbackProc, then this
**    overrides the dwIncomingCallStatus established by a call to
**    OleStdMsgFilter_SetInComingStatus.  Using
**    OleStdMsgFilter_SetInComingStatus allows the app to reject or
**    accept ALL in coming calls. Using a HandleInComingCallbackProc
**    allows the app to selectively handle or reject particular method
**    calls.
**
**    to de-install the HandleInComingCallbackProc, call
**          OleStdMsgFilter_SetHandleInComingCallbackProc(NULL);
**
**    Returns previous callback proc in effect.
*/

STDAPI_(HANDLEINCOMINGCALLBACKPROC)
   OleStdMsgFilter_SetHandleInComingCallbackProc(
      LPMESSAGEFILTER             lpThis,
      HANDLEINCOMINGCALLBACKPROC  lpfnHandleInComingCallback)
{
   LPOLESTDMESSAGEFILTER lpStdMsgFilter = (LPOLESTDMESSAGEFILTER)lpThis;
   HANDLEINCOMINGCALLBACKPROC    lpfnPrevCallback =
         lpStdMsgFilter->m_lpfnHandleInComingCallback;

   if (!IsBadWritePtr((LPVOID)lpStdMsgFilter, sizeof(OLESTDMESSAGEFILTER))) {
      lpStdMsgFilter->m_lpfnHandleInComingCallback =
            lpfnHandleInComingCallback;
   } else {
      OleDbgAssert(
         "OleStdMsgFilter_SetIncomingCallStatus: Invalid IMessageFilter*");
   }

#if defined( _DEBUG )
   {
      if (lpfnHandleInComingCallback)
         OleDbgOut3(
            "OleStdMsgFilter_SetHandleInComingCallbackProc SET\r\n");
      else
         OleDbgOut3(
            "OleStdMsgFilter_SetHandleInComingCallbackProc CLEARED\r\n");

   }
#endif  // _DEBUG

   return lpfnPrevCallback;
}

/* OleStdMsgFilter_GetInComingStatus
** ---------------------------------
**    This is a private function that returns the current
**    incoming call status.  Can be used to disable/enable options
**    in the calling application.
**
** Returns: one of
**
**    SERVERCALL_ISHANDLED
**    SERVERCALL_REJECTED
**    SERVERCALL_RETRYLATER
**    or -1 for ERROR
**
*/

STDAPI_(DWORD) OleStdMsgFilter_GetInComingCallStatus(
      LPMESSAGEFILTER lpThis)
{
   LPOLESTDMESSAGEFILTER lpStdMsgFilter = (LPOLESTDMESSAGEFILTER)lpThis;
   DWORD dwReturn;

   if (!IsBadReadPtr((LPVOID)lpStdMsgFilter,  sizeof(OLESTDMESSAGEFILTER)))
      dwReturn = lpStdMsgFilter->m_dwInComingCallStatus;
   else
      {
      OleDbgAssert(
         "OleStdMsgFilter_GetIncomingCallStatus: Invalid IMessageFilter*");
      dwReturn = (DWORD)-1;
      }

#if defined( _DEBUG )
   {
   char szBuf[80];
   char *szReturn;

   switch(dwReturn) {
      case SERVERCALL_ISHANDLED:
         szReturn = "SERVERCALL_ISHANDLED";
         break;
      case SERVERCALL_REJECTED:
         szReturn = "SERVERCALL_REJECTED";
         break;
      case SERVERCALL_RETRYLATER:
         szReturn = "SERVERCALL_RETRYLATER";
         break;
      default:
         szReturn = "-1";
         break;
      }
   wsprintf(
      szBuf,
      "OleStdMsgFilter_GetInComingCallStatus returns %s.\r\n",
      /*(LPSTR)*/szReturn
   );
   OleDbgOut3(szBuf);
   }
#endif

   return dwReturn;
}

/* OleStdMsgFilter_EnableBusyDialog
** --------------------------------
**    This function allows the caller to control whether
**    the busy dialog is enabled. this is the dialog put up when
**    IMessageFilter::RetryRejectedCall is called because the server
**    responded SERVERCALL_RETRYLATER or SERVERCALL_REJECTED.
**
**    if the busy dialog is NOT enabled, then the rejected call is
**    immediately canceled WITHOUT prompting the user. in this situation
**    OleStdMsgFilter_RetryRejectedCall always retuns
**    OLESTDCANCELRETRY canceling the outgoing LRPC call.
**    If the busy dialog is enabled, then the user is given the choice
**    of whether to retry, switch to, or cancel.
**
**    Returns previous dialog enable state
*/

STDAPI_(BOOL) OleStdMsgFilter_EnableBusyDialog(
      LPMESSAGEFILTER lpThis, BOOL fEnable)
{
   LPOLESTDMESSAGEFILTER lpStdMsgFilter = (LPOLESTDMESSAGEFILTER)lpThis;
   BOOL fPrevEnable = lpStdMsgFilter->m_fEnableBusyDialog;

   if (!IsBadWritePtr((LPVOID)lpStdMsgFilter,  sizeof(OLESTDMESSAGEFILTER)))
      lpStdMsgFilter->m_fEnableBusyDialog = fEnable;
   else
      OleDbgAssert(
            "OleStdMsgFilter_EnableBusyDialog: Invalid IMessageFilter*");

#if defined( _DEBUG )
   {
   char szBuf[80];
   wsprintf(
      szBuf,
      "OleStdMsgFilter_EnableBusyDialog: Dialog is %s.\r\n",
      fEnable ? /*(LPSTR)*/"ENABLED" : /*(LPSTR)*/"DISABLED"
   );
   OleDbgOut3(szBuf);
   }
#endif

   return fPrevEnable;
}

/* OleStdMsgFilter_EnableNotRespondingDialog
** -----------------------------------------
**    This function allows the caller to control whether
**    the app "NotResponding" (Blocked) dialog is enabled. this is the
**    dialog put up when IMessageFilter::MessagePending is called.
**    If the NotResponding dialog is enabled, then the user is given
**    the choice of whether to retry or switch to, but NOT to cancel.
**
**    Returns previous dialog enable state
*/

STDAPI_(BOOL) OleStdMsgFilter_EnableNotRespondingDialog(
      LPMESSAGEFILTER lpThis, BOOL fEnable)
{
   LPOLESTDMESSAGEFILTER lpStdMsgFilter = (LPOLESTDMESSAGEFILTER)lpThis;
   BOOL fPrevEnable = lpStdMsgFilter->m_fEnableNotRespondingDialog;

   if (!IsBadWritePtr((LPVOID)lpStdMsgFilter,  sizeof(OLESTDMESSAGEFILTER)))
      lpStdMsgFilter->m_fEnableNotRespondingDialog = fEnable;
   else
      OleDbgAssert(
            "OleStdMsgFilter_EnableNotRespondingDialog: Invalid IMessageFilter*");

#if defined( _DEBUG )
   {
   char szBuf[80];
   wsprintf(
      szBuf,
      "OleStdMsgFilter_EnableNotRespondingDialog: Dialog is %s.\r\n",
      fEnable ? /*(LPSTR)*/"ENABLED" : /*(LPSTR)*/"DISABLED"
   );
   OleDbgOut3(szBuf);
   }
#endif

   return fPrevEnable;
}

/* OleStdMsgFilter_SetParentWindow
** -------------------------------
**    This function allows caller to set which window will be used as
**    the parent for the busy dialog.
**
**    NOTE: it would be inportant for an in-place active server to
**    reset this to its current in-place frame window when in-place
**    activated. if the hWndParent is set to NULL then the dialogs will
**    be parented to the desktop.
**
**    Returns: previous parent window
*/

STDAPI_(HWND) OleStdMsgFilter_SetParentWindow(
      LPMESSAGEFILTER lpThis, HWND hWndParent)
{
   LPOLESTDMESSAGEFILTER lpStdMsgFilter = (LPOLESTDMESSAGEFILTER)lpThis;
   HWND hWndPrev = lpStdMsgFilter->m_hWndParent;

   lpStdMsgFilter->m_hWndParent = hWndParent;
   return hWndPrev;
}

STDMETHODIMP OleStdMsgFilter_QueryInterface(
      LPMESSAGEFILTER lpThis, REFIID riid, LPVOID FAR* ppvObj)
{
   LPOLESTDMESSAGEFILTER lpStdMsgFilter = (LPOLESTDMESSAGEFILTER)lpThis;
   SCODE scode;

   /* Two interfaces supported: IUnknown, IMessageFilter
   */

   if (IsEqualIID(riid, &IID_IMessageFilter) || IsEqualIID(riid, &IID_IUnknown)) {
      lpStdMsgFilter->m_cRef++;   // A pointer to this object is returned
      *ppvObj = lpThis;
      scode = S_OK;
   }
   else {                 // unsupported interface
      *ppvObj = NULL;
      scode = E_NOINTERFACE;
   }

   return scode;
}

STDMETHODIMP_(ULONG) OleStdMsgFilter_AddRef(LPMESSAGEFILTER lpThis)
{
   LPOLESTDMESSAGEFILTER lpStdMsgFilter = (LPOLESTDMESSAGEFILTER)lpThis;
   return ++lpStdMsgFilter->m_cRef;
}

STDMETHODIMP_(ULONG) OleStdMsgFilter_Release(LPMESSAGEFILTER lpThis)
{
   LPOLESTDMESSAGEFILTER lpStdMsgFilter = (LPOLESTDMESSAGEFILTER)lpThis;
   LPMALLOC lpMalloc;

   if (--lpStdMsgFilter->m_cRef != 0) // Still used by others
      return lpStdMsgFilter->m_cRef;

   // Free storage
   if (CoGetMalloc(MEMCTX_TASK, (LPMALLOC FAR*)&lpMalloc) != NOERROR)
      return (ULONG)0;

   lpMalloc->lpVtbl->Free(lpMalloc, lpStdMsgFilter);
   lpMalloc->lpVtbl->Release(lpMalloc);

  g_dwObjectCount --;

   return (ULONG)0;
}

STDMETHODIMP_(DWORD) OleStdMsgFilter_HandleInComingCall (
      LPMESSAGEFILTER     lpThis,
      DWORD               dwCallType,
      HTASK               htaskCaller,
      DWORD               dwTickCount,
      LPINTERFACEINFO     dwReserved
)
{
   LPOLESTDMESSAGEFILTER lpStdMsgFilter = (LPOLESTDMESSAGEFILTER)lpThis;

   /* if a HandleInComingCallbackProc is in effect, then this
   **    overrides dwIncomingCallStatus established by a call to
   **    OleStdMsgFilter_SetInComingStatus.  we will call this
   **    callback to allow the app to selectively handle or reject
   **    incoming method calls. the LPINTERFACEINFO parameter
   **    describes which method is being called.
   */
   if (lpStdMsgFilter->m_lpfnHandleInComingCallback &&
      !IsBadCodePtr((FARPROC)lpStdMsgFilter->m_lpfnHandleInComingCallback)){
      return lpStdMsgFilter->m_lpfnHandleInComingCallback(
            dwCallType,
            htaskCaller,
            dwTickCount,
            dwReserved
      );
   }

   switch (dwCallType) {
      case CALLTYPE_TOPLEVEL:
         /* NOTE: we currently have NO pending outgoing call and
         **    there is a new toplevel incoming call.
         **    this call may be rejected.
         */
         return lpStdMsgFilter->m_dwInComingCallStatus;

      case CALLTYPE_TOPLEVEL_CALLPENDING:
         /* NOTE: we currently HAVE a pending outgoing call and
         **    there is a new toplevel incoming call.
         **    this call may be rejected.
         */
         return lpStdMsgFilter->m_dwInComingCallStatus;

      case CALLTYPE_NESTED:
         /* NOTE: we currently HAVE a pending outgoing call and
         **    there callback on behalf of the previous outgoing
         **    call. this type of call should ALWAYS be handled.
         */
         return SERVERCALL_ISHANDLED;

      case CALLTYPE_ASYNC:
         /* NOTE: we currently have NO pending outgoing call and
         **    there is a new asyncronis incoming call.
         **    this call can NEVER be rejected. OLE actually ignores
         **    the return code in this case and always allows the
         **    call through.
         */
         return SERVERCALL_ISHANDLED;    // value returned does not matter

      case CALLTYPE_ASYNC_CALLPENDING:
         /* NOTE: we currently HAVE a pending outgoing call and
         **    there is a new asyncronis incoming call.
         **    this call can NEVER be rejected. OLE ignore the
         **    return code in this case.
         */
         return SERVERCALL_ISHANDLED;    // value returned does not

      default:
         OleDbgAssert(
               "OleStdMsgFilter_HandleInComingCall: Invalid CALLTYPE");
         return lpStdMsgFilter->m_dwInComingCallStatus;
   }
}

STDMETHODIMP_(DWORD) OleStdMsgFilter_RetryRejectedCall (
      LPMESSAGEFILTER     lpThis,
      HTASK               htaskCallee,
      DWORD               dwTickCount,
      DWORD               dwRejectType
)
{
   LPOLESTDMESSAGEFILTER   lpStdMsgFilter = (LPOLESTDMESSAGEFILTER)lpThis;
   DWORD                   dwRet = 0;
   UINT                    uRet;
   char                    szBuf[80];
   OLEDBG_BEGIN2("OleStdMsgFilter_RetryRejectedCall\r\n")

   /* NOTE: we should only put up the application busy dialog when
   **    the callee has responded SERVERCALL_RETRYLATER. if the
   **    dwRejectType is SERVERCALL_REJECTED then there is something
   **    seriously wrong with the callee (perhaps a severe low memory
   **    situation). we don't want to even try to "Switch To" this app
   **    or even try to "Retry".
   */
   if (dwRejectType == SERVERCALL_RETRYLATER &&
         lpStdMsgFilter->m_fEnableBusyDialog) {

      OLEUIBUSY bz;

      /* NOTE: we do not want to put up the Busy dialog immediately
      **    the when an app says RETRYLATER. we should continue retrying
      **    for a while in case the app can un-busy itself in a
      **    reasonable amount of time.
      */
      if (dwTickCount <= (DWORD)OLESTDRETRYDELAY) {
         dwRet = 500;                  // Retry after .5 sec
         OLEDBG_END2
         return dwRet;
      }

      /*
      ** Set up structure for calling OLEUIBUSY dialog
      */

      bz.cbStruct = sizeof(OLEUIBUSY);
      bz.dwFlags = 0L;
      bz.hWndOwner =GetTopWindowInWindowsTask(lpStdMsgFilter->m_hWndParent);
      W2A(lpStdMsgFilter->m_lpszAppName, szBuf, sizeof(szBuf));
      bz.lpszCaption = szBuf;
      bz.lpfnHook = lpStdMsgFilter->m_lpfnBusyDialogHookCallback;
      bz.lCustData = 0;
      bz.hInstance = NULL;
      bz.lpszTemplate = NULL;
      bz.hResource = 0;
      bz.hTask = htaskCallee;
      bz.lphWndDialog = NULL; // We don't need the hDlg for this call

      uRet = OleUIBusy(&bz);

      switch (uRet) {
         case OLEUI_BZ_RETRYSELECTED:
            dwRet = 0;                  // Retry immediately
            break;

         case OLEUI_CANCEL:
            dwRet = OLESTDCANCELRETRY;  // Cancel pending outgoing call
            break;

         case OLEUI_BZERR_HTASKINVALID:
            // Htask was invalid, return OLESTDRETRYDELAY anyway
            dwRet = OLESTDRETRYDELAY;   // Retry after <retry delay> msec

#if defined( _DEBUG )
            wsprintf(
                  szBuf,
                  "OleStdMsgFilter_RetryRejectedCall, HTASK 0x%x invalid\r\n",
                  htaskCallee
            );
            OleDbgOut3(szBuf);
#endif
            break;
      }
   } else {
      dwRet = OLESTDCANCELRETRY;  // Cancel pending outgoing call
   }

#if defined( _DEBUG )
   wsprintf(szBuf,
          "OleStdMsgFilter_RetryRejectedCall returns %d\r\n",
          dwRet);
   OleDbgOut3(szBuf);
#endif

   OLEDBG_END2
   return dwRet;
}

/* a significant message is consider a mouse click or keyboard input. */
#define IS_SIGNIFICANT_MSG(lpmsg)   \
   (   \
      (PeekMessage((lpmsg), NULL, WM_LBUTTONDOWN, WM_LBUTTONDOWN, \
             PM_NOREMOVE | PM_NOYIELD)) \
    || (PeekMessage((lpmsg), NULL, WM_LBUTTONDBLCLK, WM_LBUTTONDBLCLK, \
             PM_NOREMOVE | PM_NOYIELD)) \
    || (PeekMessage((lpmsg), NULL, WM_NCLBUTTONDOWN, WM_NCLBUTTONDOWN, \
             PM_NOREMOVE | PM_NOYIELD)) \
    || (PeekMessage((lpmsg), NULL, WM_NCLBUTTONDBLCLK, WM_NCLBUTTONDBLCLK, \
             PM_NOREMOVE | PM_NOYIELD)) \
    || (PeekMessage((lpmsg), NULL, WM_KEYDOWN, WM_KEYDOWN, \
             PM_NOREMOVE | PM_NOYIELD)) \
    || (PeekMessage((lpmsg), NULL, WM_SYSKEYDOWN, WM_SYSKEYDOWN, \
             PM_NOREMOVE | PM_NOYIELD)) \
   )

STDMETHODIMP_(DWORD) OleStdMsgFilter_MessagePending (
      LPMESSAGEFILTER     lpThis,
      HTASK               htaskCallee,
      DWORD               dwTickCount,
      DWORD               dwPendingType
)
{
   LPOLESTDMESSAGEFILTER   lpStdMsgFilter = (LPOLESTDMESSAGEFILTER)lpThis;
   DWORD                   dwReturn = PENDINGMSG_WAITDEFPROCESS;
   MSG                     msg;
   BOOL                    fIsSignificantMsg = IS_SIGNIFICANT_MSG(&msg);
   UINT                    uRet;

   char szBuf[128];
#if defined( _DEBUG )
   wsprintf(
         szBuf,
         "OleStdMsgFilter_MessagePending, dwTickCount = 0x%lX\r\n",
         (DWORD)dwTickCount
   );
   OleDbgOut4(szBuf);
#endif

   /* NOTE: If our tick count for this call exceeds our standard retry
   **      delay, then we need to put up the dialog.  We will only
   **      consider putting up the dialog if the user has issued a
   **      "significant" event (ie. mouse click or keyboard event). a
   **      simple mouse move should NOT trigger this dialog.
   **      Since our call to
   **      OleUIBusy below enters a DialogBox() message loop, there's a
   **      possibility that another call will be initiated during the dialog,
   **      and this procedure will be re-entered.  Just so we don't put up
   **      two dialogs at a time, we use the m_bUnblocking varable
   **      to keep track of this situation.
   */

   if (dwTickCount > (DWORD)OLESTDRETRYDELAY && fIsSignificantMsg
         && !lpStdMsgFilter->m_bUnblocking)
   {

      if (lpStdMsgFilter->m_fEnableNotRespondingDialog)
      {
      OLEUIBUSY bz;

      lpStdMsgFilter->m_bUnblocking = TRUE;

      // Eat messages in our queue that we do NOT want to be dispatched
      while (PeekMessage(&msg, NULL, WM_CLOSE, WM_CLOSE, PM_REMOVE | PM_NOYIELD));

      /* Set up structure for calling OLEUIBUSY dialog,
      ** using the "not responding" variety
      */

      bz.cbStruct = sizeof(OLEUIBUSY);
      bz.dwFlags = BZ_NOTRESPONDINGDIALOG;
      bz.hWndOwner =GetTopWindowInWindowsTask(lpStdMsgFilter->m_hWndParent);
      W2A(lpStdMsgFilter->m_lpszAppName, szBuf, sizeof(szBuf));
      bz.lpszCaption = szBuf;
      bz.lpfnHook = lpStdMsgFilter->m_lpfnBusyDialogHookCallback;
      bz.lCustData = 0;
      bz.hInstance = NULL;
      bz.lpszTemplate = NULL;
      bz.hResource = 0;
      bz.hTask = htaskCallee;

      /* Set up the address to the hWnd in our MsgFilter structure.  The
      ** call to OleUIBusy will fill this in with the hWnd of the busy
      ** dialog box
      */

      bz.lphWndDialog =  (HWND FAR *)&(lpStdMsgFilter->m_hWndBusyDialog);
      uRet = OleUIBusy(&bz);

      lpStdMsgFilter->m_bUnblocking = FALSE;

      return PENDINGMSG_WAITNOPROCESS;
      }
#if defined( _DEBUG )
      else {
         OleDbgOut3("OleStdMsgFilter_MessagePending: BLOCKED but dialog Disabled\r\n");
      }
#endif
   }

   /* If we're already unblocking, we're being re-entered.  Don't
   ** process message
   */

   if (lpStdMsgFilter->m_bUnblocking)
      return PENDINGMSG_WAITDEFPROCESS;

   /* NOTE: If we have a callback function set up, call it with the
   ** current message.  If not, tell OLE LPRC mechanism to automatically
   ** handle all messages.
   */
   if (lpStdMsgFilter->m_lpfnMessagePendingCallback &&
      !IsBadCodePtr((FARPROC)lpStdMsgFilter->m_lpfnMessagePendingCallback)){
      MSG msg;

      /* NOTE: the app provided a MessagePendingCallback
      **    function. we will PeekMessage for the first message in
      **    the queue and pass it to the app. the app in its callback
      **    function can decide to Dispatch this message or it can
      **    PeekMessage on its own giving particular message filter
      **    criteria. if the app returns TRUE then we return
      **    PENDINGMSG_WAITNOPROCESS to OLE telling OLE to leave the
      **    message in the queue. If the app returns FALSE, then we
      **    return PENDINGMSG_WAITDEFPROCESS to OLE telling OLE to do
      **    its default processing with the message. by default OLE
      **    dispatches system messages and eats other messages and
      **    beeps.
      */
      if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE | PM_NOYIELD)) {

         if (lpStdMsgFilter->m_lpfnMessagePendingCallback(&msg)) {
            /* TRUE return means that the app processed message.
            **
            ** NOTE: (CHANGE FROM OLE2.0 VERSION) we leave it up to
            **    the callback routine to remove the message if it
            **    wants.
            */
            dwReturn = PENDINGMSG_WAITNOPROCESS;
         } else {
            /* FALSE means that the app did not process the
            **    message. we will let OLE take its
            **    default action.
            **
            ** NOTE: (CHANGE FROM OLE2.0 VERSION) we used to return
            **    PENDINGMSG_WAITNOPROCESS to leave the message in
            **    the queue; now we return PENDINGMSG_WAITDEFPROCESS
            **    to let OLE do default processing.
            */
            dwReturn = PENDINGMSG_WAITDEFPROCESS;

#if defined( _DEBUG )
            wsprintf(
                  szBuf,
                  "Message (0x%x) (wParam=0x%x, lParam=0x%lx) using WAITDEFPROCESS while blocked\r\n",
                  msg.message,
                  msg.lParam,
                  msg.wParam
            );
            OleDbgOut2(szBuf);
#endif  // _DEBUG
         }
      }
   }

   return dwReturn;
}


OBJFDBK.C

/*
 * OBJFDBK.C
 *
 * Miscellaneous API's to generate UI feedback effects for OLE objects. This
 * is part of the OLE 2.0 User Interface Support Library.
 * The following feedback effects are supported:
 *      1. Object selection handles (OleUIDrawHandles)
 *      2. Open Object window shading (OleUIDrawShading)
 *
 * Copyright (c)1992-1996 Microsoft Corporation, All Right Reserved
 */

#define STRICT  1
#include "olestd.h"

static void DrawHandle(HDC hdc, int x, int y, UINT cSize, BOOL bInvert, BOOL fDraw);

/*
 * OleUIDrawHandles
 *
 * Purpose:
 *  Draw handles or/and boundary around Container Object when selected
 *
 * Parameters:
 *  lpRect      Dimensions of Container Object
 *  hdc         HDC of Container Object (MM_TEXT mapping mode)
 *  dwFlags-
 *      Exclusive flags
 *          OLEUI_HANDLES_INSIDE    Draw handles on inside of rect
 *          OLEUI_HANDLES_OUTSIDE   Draw handles on outside of rect
 *      Optional flags
 *          OLEUI_HANDLES_NOBORDER  Draw handles only, no rect
 *          OLEUI_HANDLES_USEINVERSE
 *              use invert for handles and rect, o.t. use COLOR_WINDOWTEXT
 *  cSize       size of handle box
 *  fDraw       Draw if TRUE, erase if FALSE
 *
 * Return Value: null
 *
 */
STDAPI_(void) OleUIDrawHandles(
        LPRECT  lpRect,
        HDC     hdc,
        DWORD   dwFlags,
        UINT    cSize,
        BOOL    fDraw
)
{
        HBRUSH  hbr;
        RECT    rc;
        int     bkmodeOld;
        BOOL    bInvert = (BOOL) (dwFlags & OLEUI_HANDLES_USEINVERSE);

        CopyRect((LPRECT)&rc, lpRect);

        bkmodeOld = SetBkMode(hdc, TRANSPARENT);

        if (dwFlags & OLEUI_HANDLES_OUTSIDE)
                InflateRect((LPRECT)&rc, cSize - 1, cSize - 1);

        // Draw the handles inside the rectangle boundary
        DrawHandle(hdc, rc.left, rc.top, cSize, bInvert, fDraw);
        DrawHandle(hdc, rc.left, rc.top+(rc.bottom-rc.top-cSize)/2, cSize, bInvert, fDraw);
        DrawHandle(hdc, rc.left, rc.bottom-cSize, cSize, bInvert, fDraw);
        DrawHandle(hdc, rc.left+(rc.right-rc.left-cSize)/2, rc.top, cSize, bInvert, fDraw);
        DrawHandle(hdc, rc.left+(rc.right-rc.left-cSize)/2, rc.bottom-cSize, cSize, bInvert, fDraw);
        DrawHandle(hdc, rc.right-cSize, rc.top, cSize, bInvert, fDraw);
        DrawHandle(hdc, rc.right-cSize, rc.top+(rc.bottom-rc.top-cSize)/2, cSize, bInvert, fDraw);
        DrawHandle(hdc, rc.right-cSize, rc.bottom-cSize, cSize, bInvert, fDraw);

        if (!(dwFlags & OLEUI_HANDLES_NOBORDER)) {
                if (fDraw)
                        hbr = GetStockObject(BLACK_BRUSH);
                else
                        hbr = GetStockObject(WHITE_BRUSH);

                FrameRect(hdc, lpRect, hbr);
        }

        SetBkMode(hdc, bkmodeOld);
}

/*
 * DrawHandle
 *
 * Purpose:
 *  Draw a handle box at the specified coordinate
 *
 * Parameters:
 *  hdc         HDC to be drawn into
 *  x, y        upper left corner coordinate of the handle box
 *  cSize       size of handle box
 *  bInvert     use InvertRect() if TRUE, otherwise use Rectangle()
 *  fDraw       Draw if TRUE, erase if FALSE, ignored if bInvert is TRUE
 *
 * Return Value: null
 *
 */
static void DrawHandle(HDC hdc, int x, int y, UINT cSize, BOOL bInvert, BOOL fDraw)
{
        HBRUSH  hbr;
        HBRUSH  hbrOld;
        HPEN    hpen;
        HPEN    hpenOld;
        RECT    rc;

        if (!bInvert) {
                if (fDraw) {
                        hpen = GetStockObject(BLACK_PEN);
                        hbr = GetStockObject(BLACK_BRUSH);
                } else {
                        hpen = GetStockObject(WHITE_PEN);
                        hbr = GetStockObject(WHITE_PEN);
                }

                hpenOld = SelectObject(hdc, hpen);
                hbrOld = SelectObject(hdc, hbr);
                Rectangle(hdc, x, y, x+cSize, y+cSize);
                SelectObject(hdc, hpenOld);
                SelectObject(hdc, hbrOld);
        }
        else {
                rc.left = x;
                rc.top = y;
                rc.right = x + cSize;
                rc.bottom = y + cSize;
                InvertRect(hdc, (LPRECT)&rc);
        }
}

/*
 * OleUIDrawShading
 *
 * Purpose:
 *  Shade the object when it is in in-place editing. Borders are drawn
 *  on the Object rectangle. The right and bottom edge of the rectangle
 *  are excluded in the drawing.
 *
 * Parameters:
 *  lpRect      Dimensions of Container Object
 *  hdc         HDC for drawing
 *  dwFlags-
 *      Exclusive flags
 *          OLEUI_SHADE_FULLRECT    Shade the whole rectangle
 *          OLEUI_SHADE_BORDERIN    Shade cWidth pixels inside rect
 *          OLEUI_SHADE_BORDEROUT   Shade cWidth pixels outside rect
 *      Optional flags
 *          OLEUI_SHADE_USEINVERSE
 *              use PATINVERT instead of the hex value
 *  cWidth      width of border in pixel
 *
 * Return Value: null
 *
 */
STDAPI_(void) OleUIDrawShading(LPRECT lpRect, HDC hdc, DWORD dwFlags, UINT cWidth)
{
        HBRUSH  hbr;
        HBRUSH  hbrOld;
        HBITMAP hbm;
        RECT    rc;
        WORD    wHatchBmp[] = {0x11, 0x22, 0x44, 0x88, 0x11, 0x22, 0x44, 0x88};
        COLORREF cvText;
        COLORREF cvBk;

        hbm = CreateBitmap(8, 8, 1, 1, wHatchBmp);
        hbr = CreatePatternBrush(hbm);
        hbrOld = SelectObject(hdc, hbr);

        rc = *lpRect;

        if (dwFlags == OLEUI_SHADE_FULLRECT) {
                cvText = SetTextColor(hdc, RGB(255, 255, 255));
                cvBk = SetBkColor(hdc, RGB(0, 0, 0));
                PatBlt(hdc, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top,
                        0x00A000C9L /* DPa */ );

        } else {    // either inside or outside rect

                if (dwFlags == OLEUI_SHADE_BORDEROUT)
                        InflateRect((LPRECT)&rc, cWidth - 1, cWidth - 1);

                cvText = SetTextColor(hdc, RGB(255, 255, 255));
                cvBk = SetBkColor(hdc, RGB(0, 0, 0));
                PatBlt(hdc, rc.left, rc.top, rc.right - rc.left,
                        cWidth, 0x00A000C9L /* DPa */);
                PatBlt(hdc, rc.left, rc.top, cWidth, rc.bottom - rc.top,
                        0x00A000C9L /* DPa */);
                PatBlt(hdc, rc.right - cWidth, rc.top, cWidth,
                        rc.bottom - rc.top, 0x00A000C9L /* DPa */);
                PatBlt(hdc, rc.left, rc.bottom - cWidth, rc.right-rc.left,
                        cWidth, 0x00A000C9L /* DPa */);
        }

        SetTextColor(hdc, cvText);
        SetBkColor(hdc, cvBk);
        SelectObject(hdc, hbrOld);
        DeleteObject(hbr);
        DeleteObject(hbm);
}

/*
 * OleUIShowObject
 *
 * Purpose:
 *  Draw the ShowObject effect around the object
 *
 * Parameters:
 *  lprc        rectangle for drawing
 *  hdc         HDC for drawing
 *  fIsLink     linked object (TRUE) or embedding object (FALSE)
 *
 * Return Value: null
 *
 */
STDAPI_(void) OleUIShowObject(LPCRECT lprc, HDC hdc, BOOL fIsLink)
{
        HPEN    hpen;
        HPEN    hpenOld;
        HBRUSH  hbrOld;

        if (!lprc || !hdc)
                return;

        hpen = fIsLink ? CreatePen(PS_DASH, 1, RGB(0,0,0)) :
                                         GetStockObject(BLACK_PEN);

        if (!hpen)
                return;

        hpenOld = SelectObject(hdc, hpen);
        hbrOld = SelectObject(hdc, GetStockObject(NULL_BRUSH));

        Rectangle(hdc, lprc->left, lprc->top, lprc->right, lprc->bottom);

        SelectObject(hdc, hpenOld);
        SelectObject(hdc, hbrOld);

        if (fIsLink)
                DeleteObject(hpen);

}


OLE2UI.C

/*
 * OLE2UI.C
 *
 * Contains initialization routines and miscellaneous API implementations for
 * the OLE 2.0 User Interface Support Library.
 *
 * Copyright (c)1992-1996 Microsoft Corporation, All Right Reserved
 */

#define STRICT  1

#include "olestd.h"
#include "common.h"
#include "utility.h"
#include <commdlg.h>
#include "malspy.h"

OLEDBGDATA

#define WINDLL  1           // make far pointer version of stdargs.h
#include <stdarg.h>

//The instance handle shared amongst all dialogs.
HINSTANCE     ghInst;

// object count, used to support DllCanUnloadNow and OleUICanUnloadNow
DWORD g_dwObjectCount=0;
DWORD g_dwLockCount=0;

//Registered messages for use with all the dialogs, registered in LibMain
UINT        uMsgHelp=0;
UINT        uMsgEndDialog=0;
UINT        uMsgBrowse=0;
UINT        uMsgChangeIcon=0;
UINT        uMsgFileOKString=0;
UINT        uMsgCloseBusyDlg=0;

//Clipboard formats used by PasteSpecial
UINT  cfObjectDescriptor;
UINT  cfLinkSrcDescriptor;
UINT  cfEmbedSource;
UINT  cfEmbeddedObject;
UINT  cfLinkSource;
UINT  cfOwnerLink;
UINT  cfFileName;

// local definition
#define WM_U_UPDATELINK WM_USER

/*
 * OleStdInitialize
 *
 * NOTE: This function should be called by your application before using any of
 *       the functions found in this library.
 *
 * Purpose:
 *   Initializes the OLE UI Library.
 *
 * Parameters:
 *
 *  hInstance       HINSTANCE of the module where the UI library resources
 *                  and Dialog Procedures are contained.  If you are calling
 *                  this function yourself, this should be the instance handle
 *                  of your application.
 *
 *  hPrevInst       HINSTANCE of the previous application instance.
 *                  This is the parameter passed in to your WinMain.  For
 *                  the DLL version, this should always be set to zero (for
 *                  WIN16 DLLs).
 *
 * Return Value:
 *  BOOL            TRUE if initialization was successful.
 *                  FALSE if either the "Magic Number" did not verify, or one of
 *                  the window classes could not be registered.  If the
 *                  "Magic Number" did not verify, then the resources
 *                  in your module are of a different version than the
 *                  ones you compiled with.
 */

STDAPI_(BOOL) OleStdInitialize(HINSTANCE hInstance)
{
   OleDbgOut1("OleStdInitialize called.\r\n");
   ghInst=hInstance;

   // Register Clipboard Formats used by PasteSpecial dialog.
   cfObjectDescriptor = RegisterClipboardFormat(CF_OBJECTDESCRIPTOR);
   cfLinkSrcDescriptor= RegisterClipboardFormat(CF_LINKSRCDESCRIPTOR);
   cfEmbedSource      = RegisterClipboardFormat(CF_EMBEDSOURCE);
   cfEmbeddedObject   = RegisterClipboardFormat(CF_EMBEDDEDOBJECT);
   cfLinkSource       = RegisterClipboardFormat(CF_LINKSOURCE);
   cfOwnerLink        = RegisterClipboardFormat(CF_OWNERLINK);
   cfFileName         = RegisterClipboardFormat(CF_FILENAME);

#ifdef TRACEMEM
   return (InitializeMallocSpy());
#else
   return (TRUE);
#endif
}

STDAPI_(void) OleStdUninitialize(void)
{
#ifdef TRACEMEM
   UninitializeMallocSpy();
#endif
}

/*
 * OleUICanUnloadNow
 *
 * NOTE: if you link to this library and you implement
 *       DllCanUnloadNow, then you must call this routine in your
 *       implementation of DllCanUnloadNow to determine if you can be
 *       unloaded or not.
 *
 * Purpose:
 *   Determines when it is safe to go away
 *   (ie. there are no existing object instances).
 *
 * Return Value:
 *   HRESULT    NOERROR it is safe to go away, S_FALSE this code must stay
 *              loaded.
 *
 * Comments:
 *
 *   If an INPROC server DLL links to the OLESTD library,
 *   then the OleUILockLibrary function should NOT be used.
 *   instead the INPROC server DLL should call OleUICanUnloadNow API from
 *   within its own DllCanUnloadNow function. The idea here is, that if there
 *   are any existing instance of objects created by the OLESTD library
 *   functions (eg. EnumFORMATETC objects created by OleStdEnumFmtEtc_Create)
 *   then, the INPROC server DLL must NOT let itself be unloaded.
 *
 *   An EXE based object using the OLESTD libray need NOT use either the
 *   OleUILockLibrary or OleUICanUnloadNow functions. All objects created
 *   by the OLESTD library will have LRPC proxies and stubs created to
 *   manage remoting method calls. the worst that can happen when the EXE
 *   exits is that any outstanding proxies for unreleased objects will get
 *   RPC_E_SERVERDIED errors; they will not GPFault.
 */

STDAPI OleUICanUnloadNow()
{
  if (g_dwObjectCount == 0) {
   return NOERROR;
  }

  return S_FALSE;
}


OLESTD.C

/*************************************************************************
**
**    OLE 2 Standard Utilities
**
**    olestd.c
**
**    This file contains utilities that are useful for most standard
**        OLE 2.0 compound document type applications.
**
**    (c) Copyright Microsoft Corp. 1992-1996 All Rights Reserved
**
*************************************************************************/

#define NONAMELESSUNION     // use strict ANSI standard (for DVOBJ.H)

#define STRICT  1
#include "olestd.h"
#include <stdlib.h>
#include <ctype.h>
#include <shellapi.h>
#include "common.h"

OLEDBGDATA

static char szAssertMemAlloc[] = "CoGetMalloc failed";
static int IsCloseFormatEtc(FORMATETC FAR* pFetcLeft, FORMATETC FAR* pFetcRight);

/* OleStdSetupAdvises
** ------------------
**    Setup the standard View advise required by a standard,
**    compound document-oriented container. Such a container relies on
**    Ole to manage the presentation of the Ole object. The container
**    call IViewObject::Draw to render (display) the object.
**
**    This helper routine performs the following tasks:
**                      setup View advise
**                      Call IOleObject::SetHostNames
**                      Call OleSetContainedObject
**
**    fCreate should be set to TRUE if the object is being created. if
**    an existing object is being loaded, then fCreate should be FALSE.
**    if it is a creation situation, then the ADVF_PRIMEFIRST flag is
**    used when settinp up the IViewObject::Advise. This will result in
**    the immediate sending of the initial picture.
**
**    NOTE: the standard container does NOT need to set up an OLE
**    Advise (IOleObject::Advise). this routine does NOT set up an OLE
**    Advise (a previous version of this function used to setup this
**    advise, but it was not useful).
*/
STDAPI_(BOOL) OleStdSetupAdvises(LPOLEOBJECT lpOleObject, DWORD dwDrawAspect,
               LPOLESTR lpszContainerApp, LPOLESTR lpszContainerObj,
               LPADVISESINK lpAdviseSink, BOOL fCreate)
{
   LPVIEWOBJECT lpViewObject;
   HRESULT hrErr;
   BOOL fStatus = TRUE;
#if defined( SPECIAL_CONTAINER )
   DWORD dwTemp;
#endif

   hrErr = lpOleObject->lpVtbl->QueryInterface(
         lpOleObject,
         &IID_IViewObject,
         (LPVOID FAR*)&lpViewObject
   );

   /* Setup View advise */
   if (hrErr == NOERROR) {

      OLEDBG_BEGIN2("IViewObject::SetAdvise called\r\n")
      lpViewObject->lpVtbl->SetAdvise(
            lpViewObject,
            dwDrawAspect,
            (fCreate ? ADVF_PRIMEFIRST : 0),
            lpAdviseSink
      );
      OLEDBG_END2

      OleStdRelease((LPUNKNOWN)lpViewObject);
   } else {
      fStatus = FALSE;
   }

#if defined( SPECIAL_CONTAINER )
   /* Setup OLE advise.
   **    NOTE: normally containers do NOT need to setup an OLE
   **    advise. this advise connection is only useful for the OLE's
   **    DefHandler and the OleLink object implementation. some
   **    special container's might need to setup this advise for
   **    programatic reasons.
   **
   **    NOTE: this advise will be torn down automatically by the
   **    server when we release the object, therefore we do not need
   **    to store the connection id.
   */
   OLEDBG_BEGIN2("IOleObject::Advise called\r\n")
   hrErr = lpOleObject->lpVtbl->Advise(
         lpOleObject,
         lpAdviseSink,
         (DWORD FAR*)&dwTemp
   );
   OLEDBG_END2
   if (hrErr != NOERROR) fStatus = FALSE;
#endif

   /* Setup the host names for the OLE object. */
   OLEDBG_BEGIN2("IOleObject::SetHostNames called\r\n")
   hrErr = lpOleObject->lpVtbl->SetHostNames(
         lpOleObject,
         lpszContainerApp,
         lpszContainerObj
   );
   OLEDBG_END2
   if (hrErr != NOERROR) fStatus = FALSE;

   /* Inform the loadded object's handler/inproc-server that it is in
   **    its embedding container's process.
   */
   OLEDBG_BEGIN2("OleSetContainedObject(TRUE) called\r\n")
   OleSetContainedObject((LPUNKNOWN)lpOleObject, TRUE);
   OLEDBG_END2

   return fStatus;
}

/* OleStdSwitchDisplayAspect
** -------------------------
**    Switch the currently cached display aspect between DVASPECT_ICON
**    and DVASPECT_CONTENT.
**
**    NOTE: when setting up icon aspect, any currently cached content
**    cache is discarded and any advise connections for content aspect
**    are broken.
**
**    RETURNS:
**      S_OK -- new display aspect setup successfully
**      E_INVALIDARG -- IOleCache interface is NOT supported (this is
**                  required).
**      <other SCODE> -- any SCODE that can be returned by
**                  IOleCache::Cache method.
**      NOTE: if an error occurs then the current display aspect and
**            cache contents unchanged.
*/
STDAPI OleStdSwitchDisplayAspect(
      LPOLEOBJECT             lpOleObj,
      LPDWORD                 lpdwCurAspect,
      DWORD                   dwNewAspect,
      HGLOBAL                 hMetaPict,
      BOOL                    fDeleteOldAspect,
      BOOL                    fSetupViewAdvise,
      LPADVISESINK            lpAdviseSink,
      BOOL FAR*               lpfMustUpdate
)
{
   LPOLECACHE      lpOleCache = NULL;
   LPVIEWOBJECT    lpViewObj = NULL;
   LPENUMSTATDATA  lpEnumStatData = NULL;
   STATDATA        StatData;
   FORMATETC       FmtEtc;
   STGMEDIUM       Medium;
   DWORD           dwAdvf;
   DWORD           dwNewConnection;
   DWORD           dwOldAspect = *lpdwCurAspect;
   HRESULT         hrErr;

   if (lpfMustUpdate)
      *lpfMustUpdate = FALSE;

   lpOleCache = (LPOLECACHE)OleStdQueryInterface(
                              (LPUNKNOWN)lpOleObj,&IID_IOleCache);

   // if IOleCache* is NOT available, do nothing
   if (! lpOleCache)
      return E_INVALIDARG;

   // Setup new cache with the new aspect
   FmtEtc.cfFormat = 0;     // whatever is needed to draw
   FmtEtc.ptd      = NULL;
   FmtEtc.dwAspect = dwNewAspect;
   FmtEtc.lindex   = -1;
   FmtEtc.tymed    = TYMED_NULL;

   /* NOTE: if we are setting up Icon aspect with a custom icon
   **    then we do not want DataAdvise notifications to ever change
   **    the contents of the data cache. thus we set up a NODATA
   **    advise connection. otherwise we set up a standard DataAdvise
   **    connection.
   */
   if (dwNewAspect == DVASPECT_ICON && hMetaPict)
      dwAdvf = ADVF_NODATA;
   else
      dwAdvf = ADVF_PRIMEFIRST;

   OLEDBG_BEGIN2("IOleCache::Cache called\r\n")
   hrErr = lpOleCache->lpVtbl->Cache(
         lpOleCache,
         (LPFORMATETC)&FmtEtc,
         dwAdvf,
         (LPDWORD)&dwNewConnection
   );
   OLEDBG_END2

   if (! SUCCEEDED(hrErr)) {
      OleDbgOutHResult("IOleCache::Cache returned", hrErr);
      OleStdRelease((LPUNKNOWN)lpOleCache);
      return hrErr;
   }

   *lpdwCurAspect = dwNewAspect;

   /* NOTE: if we are setting up Icon aspect with a custom icon,
   **    then stuff the icon into the cache. otherwise the cache must
   **    be forced to be updated. set the *lpfMustUpdate flag to tell
   **    caller to force the object to Run so that the cache will be
   **    updated.
   */
   if (dwNewAspect == DVASPECT_ICON && hMetaPict) {

      FmtEtc.cfFormat = CF_METAFILEPICT;
      FmtEtc.ptd      = NULL;
      FmtEtc.dwAspect = DVASPECT_ICON;
      FmtEtc.lindex   = -1;
      FmtEtc.tymed    = TYMED_MFPICT;

      Medium.tymed            = TYMED_MFPICT;
      Medium.u.hGlobal        = hMetaPict;
      Medium.pUnkForRelease   = NULL;

      OLEDBG_BEGIN2("IOleCache::SetData called\r\n")
      hrErr = lpOleCache->lpVtbl->SetData(
            lpOleCache,
            (LPFORMATETC)&FmtEtc,
            (LPSTGMEDIUM)&Medium,
            FALSE   /* fRelease */
      );
      OLEDBG_END2
   } else {
      if (lpfMustUpdate)
         *lpfMustUpdate = TRUE;
   }

   if (fSetupViewAdvise && lpAdviseSink) {
      /* NOTE: re-establish the ViewAdvise connection */
      lpViewObj = (LPVIEWOBJECT)OleStdQueryInterface(
                              (LPUNKNOWN)lpOleObj,&IID_IViewObject);

      if (lpViewObj) {

         OLEDBG_BEGIN2("IViewObject::SetAdvise called\r\n")
         lpViewObj->lpVtbl->SetAdvise(
               lpViewObj,
               dwNewAspect,
               0,
               lpAdviseSink
         );
         OLEDBG_END2

         OleStdRelease((LPUNKNOWN)lpViewObj);
      }
   }

   /* NOTE: remove any existing caches that are set up for the old
   **    display aspect. It WOULD be possible to retain the caches set
   **    up for the old aspect, but this would increase the storage
   **    space required for the object and possibly require additional
   **    overhead to maintain the unused cachaes. For these reasons the
   **    strategy to delete the previous caches is prefered. if it is a
   **    requirement to quickly switch between Icon and Content
   **    display, then it would be better to keep both aspect caches.
   */

   if (fDeleteOldAspect) {
      OLEDBG_BEGIN2("IOleCache::EnumCache called\r\n")
      hrErr = lpOleCache->lpVtbl->EnumCache(
            lpOleCache,
            (LPENUMSTATDATA FAR*)&lpEnumStatData
      );
      OLEDBG_END2

      while(hrErr == NOERROR) {
         hrErr = lpEnumStatData->lpVtbl->Next(
               lpEnumStatData,
               1,
               (LPSTATDATA)&StatData,
               NULL
         );
         if (hrErr != NOERROR)
            break;              // DONE! no more caches.

         if (StatData.formatetc.dwAspect == dwOldAspect) {

            // Remove previous cache with old aspect
            OLEDBG_BEGIN2("IOleCache::Uncache called\r\n")
            lpOleCache->lpVtbl->Uncache(lpOleCache,StatData.dwConnection);
            OLEDBG_END2
         }
      }

      if (lpEnumStatData) {
         OleStdVerifyRelease(
               (LPUNKNOWN)lpEnumStatData,
               OLESTR ("OleStdSwitchDisplayAspect: Cache enumerator NOT released"));
      }
   }

   if (lpOleCache)
      OleStdRelease((LPUNKNOWN)lpOleCache);

   return NOERROR;
}

/* OleStdSetIconInCache
** --------------------
**    SetData a new icon into the existing DVASPECT_ICON cache.
**
**    RETURNS:
**      HRESULT returned from IOleCache::SetData
*/
STDAPI OleStdSetIconInCache(LPOLEOBJECT lpOleObj, HGLOBAL hMetaPict)
{
   LPOLECACHE      lpOleCache = NULL;
   FORMATETC       FmtEtc;
   STGMEDIUM       Medium;
   HRESULT         hrErr;

   if (! hMetaPict)
      return FALSE;   // invalid icon

   lpOleCache = (LPOLECACHE)OleStdQueryInterface(
                              (LPUNKNOWN)lpOleObj,&IID_IOleCache);
   if (! lpOleCache)
      return FALSE;   // if IOleCache* is NOT available, do nothing

   FmtEtc.cfFormat = CF_METAFILEPICT;
   FmtEtc.ptd      = NULL;
   FmtEtc.dwAspect = DVASPECT_ICON;
   FmtEtc.lindex   = -1;
   FmtEtc.tymed    = TYMED_MFPICT;

   // stuff the icon into the cache.
   Medium.tymed            = TYMED_MFPICT;
   Medium.u.hGlobal        = hMetaPict;
   Medium.pUnkForRelease   = NULL;

   OLEDBG_BEGIN2("IOleCache::SetData called\r\n")
   hrErr = lpOleCache->lpVtbl->SetData(
         lpOleCache,
         (LPFORMATETC)&FmtEtc,
         (LPSTGMEDIUM)&Medium,
         FALSE   /* fRelease */
   );
   OLEDBG_END2

   OleStdRelease((LPUNKNOWN)lpOleCache);

   return hrErr;
}

/* OleStdDoConvert
** ---------------
** Do the container-side responsibilities for converting an object.
**    This function would be used in conjunction with the OleUIConvert
**    dialog. If the user selects to convert an object then the
**    container must do the following:
**          1. unload the object.
**          2. write the NEW CLSID and NEW user type name
**              string into the storage of the object,
**              BUT write the OLD format tag.
**          3. force an update of the object to force the actual
**              conversion of the data bits.
**
**    This function takes care of step 2.
*/
STDAPI OleStdDoConvert(LPSTORAGE lpStg, REFCLSID rClsidNew)
{
   HRESULT error;
   CLSID clsidOld;
   CLIPFORMAT cfOld;
   LPOLESTR lpszOld = NULL;
   OLECHAR szNew[OLEUI_CCHKEYMAX];

   if ((error = ReadClassStg(lpStg, &clsidOld)) != NOERROR) {
      clsidOld = CLSID_NULL;
      goto errRtn;
   }

   // read old fmt/old user type; sets out params to NULL on error
   error = ReadFmtUserTypeStg(lpStg, &cfOld, &lpszOld);
   OleDbgAssert(error == NOERROR || (cfOld == 0 && lpszOld == NULL));

   // get new user type name; if error, set to NULL string
   if (OleStdGetUserTypeOfClass(
         (LPCLSID)rClsidNew, szNew, OLEUI_CCHKEYMAX,NULL /* hKey */) == 0)
      OLESTRCPY (szNew, OLESTR (""));

   // write class stg
   if ((error = WriteClassStg(lpStg, rClsidNew)) != NOERROR)
      goto errRtn;

   // write old fmt/new user type;
   if ((error = WriteFmtUserTypeStg(lpStg, cfOld, szNew)) != NOERROR)
      goto errRewriteInfo;

   // set convert bit
   if ((error = SetConvertStg(lpStg, TRUE)) != NOERROR)
      goto errRewriteInfo;

   goto okRtn;

errRewriteInfo:
   (void)WriteClassStg(lpStg, &clsidOld);
   (void)WriteFmtUserTypeStg(lpStg, cfOld, lpszOld);

errRtn:

okRtn:
   OleStdFreeString(lpszOld, NULL);
   return error;
}

/* OleStdGetTreatAsFmtUserType
** ---------------------------
**    Determine if the application should perform a TreatAs (ActivateAs
**    object or emulation) operation for the object that is stored in
**    the storage.
**
**    if the CLSID written in the storage is not the same as the
**    application's own CLSID (clsidApp), then a TreatAs operation
**    should take place. if so determine the format the data should be
**    written and the user type name of the object the app should
**    emulate (ie. pretend to be). if this information is not written
**    in the storage then it is looked up in the REGDB. if it can not
**    be found in the REGDB, then the TreatAs operation can NOT be
**    executed.
**
**    RETURNS: TRUE -- if TreatAs should be performed.
**               valid lpclsid, lplpszType, lpcfFmt to TreatAs are returned
**                      (NOTE: lplpszType must be freed by caller)
**             FALSE -- NO TreatAs. lpszType will be NULL.
**               lpclsid = CLSID_NULL; lplpszType = lpcfFmt = NULL;
*/
STDAPI_(BOOL) OleStdGetTreatAsFmtUserType(
      REFCLSID        rclsidApp,
      LPSTORAGE       lpStg,
      CLSID FAR*      lpclsid,
      CLIPFORMAT FAR* lpcfFmt,
      LPOLESTR FAR*   lplpszType
)
{
   HRESULT hrErr;
   HKEY    hKey;
   LONG    lRet;
   UINT    lSize;
   OLECHAR szBuf[OLEUI_CCHKEYMAX];

   *lpclsid    = CLSID_NULL;
   *lpcfFmt    = 0;
   *lplpszType = NULL;

   hrErr = ReadClassStg(lpStg, lpclsid);
   if (hrErr == NOERROR &&
      ! IsEqualCLSID(lpclsid, &CLSID_NULL) &&
      ! IsEqualCLSID(lpclsid, rclsidApp)) {

      hrErr = ReadFmtUserTypeStg(lpStg,(CLIPFORMAT FAR*)lpcfFmt,lplpszType);
      if (hrErr == NOERROR && lplpszType && *lpcfFmt != 0)
         return TRUE;    // Do TreatAs. info was in lpStg.

      /* read info from REGDB
      **    *lpcfFmt = value of field: CLSID\{...}\DataFormats\DefaultFile
      **    *lplpszType = value of field: CLSID\{...}
      */
      //Open up the root key.
      lRet=RegOpenKey(HKEY_CLASSES_ROOT, NULL, &hKey);
      if (lRet != (LONG)ERROR_SUCCESS)
         return FALSE;
      *lpcfFmt = OleStdGetDefaultFileFormatOfClass(lpclsid, hKey);
      if (*lpcfFmt == 0)
         return FALSE;
      lSize = OleStdGetUserTypeOfClass(lpclsid,szBuf,OLEUI_CCHKEYMAX, hKey);
      if (lSize == 0)
         return FALSE;
      *lplpszType = OleStdCopyString(szBuf, NULL);
   } else {
      return FALSE;       // NO TreatAs
   }
}

/* OleStdDoTreatAsClass
** --------------------
** Do the container-side responsibilities for "ActivateAs" (aka.
**    TreatAs) for an object.
**    This function would be used in conjunction with the OleUIConvert
**    dialog. If the user selects to ActivateAs an object then the
**    container must do the following:
**          1. unload ALL objects of the OLD class that app knows about
**          2. add the TreatAs tag in the registration database
**              by calling CoTreatAsClass().
**          3. lazily it can reload the objects; when the objects
**              are reloaded the TreatAs will take effect.
**
**    This function takes care of step 2.
*/
STDAPI OleStdDoTreatAsClass(LPOLESTR lpszUserType, REFCLSID rclsid, REFCLSID rclsidNew)
{
   HRESULT hrErr;
   LPOLESTR   lpszCLSID = NULL;
   char       lpszAnsiCLSID[256];
   LONG    lRet;
   HKEY    hKey;
   char    lpszAnsiUserType[256];

   OLEDBG_BEGIN2("CoTreatAsClass called\r\n")
   hrErr = CoTreatAsClass(rclsid, rclsidNew);
   OLEDBG_END2

   if ((hrErr != NOERROR) && lpszUserType) {
      lRet = RegOpenKey(HKEY_CLASSES_ROOT, (LPCSTR)"CLSID",
            (HKEY FAR *)&hKey);
      StringFromCLSID(rclsid, /*(LPOLESTR FAR*)*/&lpszCLSID);
      W2A (lpszCLSID, lpszAnsiCLSID, 256);
      W2A (lpszUserType, lpszAnsiUserType, 256);
      RegSetValue(hKey, lpszAnsiCLSID, REG_SZ, lpszAnsiUserType,
            lstrlen(lpszAnsiUserType));

      if (lpszCLSID)
         OleStdFreeString(lpszCLSID, NULL);

      hrErr = CoTreatAsClass(rclsid, rclsidNew);
      RegCloseKey(hKey);
   }

   return hrErr;
}

/* OleStdIsOleLink
** ---------------
**    Returns TRUE if the OleObject is infact an OLE link object. this
**    checks if IOleLink interface is supported. if so, the object is a
**    link, otherwise not.
*/
STDAPI_(BOOL) OleStdIsOleLink(LPUNKNOWN lpUnk)
{
   LPOLELINK lpOleLink;

   lpOleLink = (LPOLELINK)OleStdQueryInterface(lpUnk, &IID_IOleLink);

   if (lpOleLink) {
      OleStdRelease((LPUNKNOWN)lpOleLink);
      return TRUE;
   } else
      return FALSE;
}

/* OleStdQueryInterface
** --------------------
**    Returns the desired interface pointer if exposed by the given object.
**    Returns NULL if the interface is not available.
**    eg.:
**      lpDataObj = OleStdQueryInterface(lpOleObj, &IID_DataObject);
*/
STDAPI_(LPUNKNOWN) OleStdQueryInterface(LPUNKNOWN lpUnk, REFIID riid)
{
   LPUNKNOWN lpInterface;
   HRESULT hrErr;

   hrErr = lpUnk->lpVtbl->QueryInterface(
         lpUnk,
         riid,
         (LPVOID FAR*)&lpInterface
   );

   if (hrErr == NOERROR)
      return lpInterface;
   else
      return NULL;
}

/* OleStdGetData
** -------------
**    Retrieve data from an IDataObject in a specified format on a
**    global memory block. This function ALWAYS returns a private copy
**    of the data to the caller. if necessary a copy is made of the
**    data (ie. if lpMedium->pUnkForRelease != NULL). The caller assumes
**    ownership of the data block in all cases and must free the data
**    when done with it. The caller may directly free the data handle
**    returned (taking care whether it is a simple HGLOBAL or a HANDLE
**    to a MetafilePict) or the caller may call
**    ReleaseStgMedium(lpMedium). this OLE helper function will do the
**    right thing.
**
**    PARAMETERS:
**        LPDATAOBJECT lpDataObj  -- object on which GetData should be
**                                                         called.
**        CLIPFORMAT cfFormat     -- desired clipboard format (eg. CF_TEXT)
**        DVTARGETDEVICE FAR* lpTargetDevice -- target device for which
**                                  the data should be composed. This may
**                                  be NULL. NULL can be used whenever the
**                                  data format is insensitive to target
**                                  device or when the caller does not care
**                                  what device is used.
**        LPSTGMEDIUM lpMedium    -- ptr to STGMEDIUM struct. the
**                                  resultant medium from the
**                                  IDataObject::GetData call is
**                                  returned.
**
**    RETURNS:
**       HGLOBAL -- global memory handle of retrieved data block.
**       NULL    -- if error.
*/
STDAPI_(HGLOBAL) OleStdGetData(
      LPDATAOBJECT        lpDataObj,
      CLIPFORMAT          cfFormat,
      DVTARGETDEVICE FAR* lpTargetDevice,
      DWORD               dwDrawAspect,
      LPSTGMEDIUM         lpMedium
)
{
   HRESULT hrErr;
   FORMATETC formatetc;
   HGLOBAL hGlobal = NULL;
   HGLOBAL hCopy;
   LPVOID  lp;

   formatetc.cfFormat = cfFormat;
   formatetc.ptd = lpTargetDevice;
   formatetc.dwAspect = dwDrawAspect;
   formatetc.lindex = -1;

   switch (cfFormat) {
      case CF_METAFILEPICT:
         formatetc.tymed = TYMED_MFPICT;
         break;

      case CF_BITMAP:
         formatetc.tymed = TYMED_GDI;
         break;

      default:
         formatetc.tymed = TYMED_HGLOBAL;
         break;
   }

   OLEDBG_BEGIN2("IDataObject::GetData called\r\n")
   hrErr = lpDataObj->lpVtbl->GetData(
         lpDataObj,
         (LPFORMATETC)&formatetc,
         lpMedium
   );
   OLEDBG_END2

   if (hrErr != NOERROR)
      return NULL;

   if ((hGlobal = lpMedium->u.hGlobal) == NULL)
      return NULL;

   // Check if hGlobal really points to valid memory
   if ((lp = GlobalLock(hGlobal)) != NULL) {
      if (IsBadReadPtr(lp, 1)) {
         GlobalUnlock(hGlobal);
         return NULL;    // ERROR: memory is NOT valid
      }
      GlobalUnlock(hGlobal);
   }

   if (hGlobal != NULL && lpMedium->pUnkForRelease != NULL) {
      /* NOTE: the callee wants to retain ownership of the data.
      **    this is indicated by passing a non-NULL pUnkForRelease.
      **    thus, we will make a copy of the data and release the
      **    callee's copy.
      */

      hCopy = OleDuplicateData(hGlobal, cfFormat, GHND|GMEM_SHARE);
      ReleaseStgMedium(lpMedium); // release callee's copy of data

      hGlobal = hCopy;
      lpMedium->u.hGlobal = hCopy;
      lpMedium->pUnkForRelease = NULL;
   }
   return hGlobal;
}

/* OleStdMalloc
** ------------
**    allocate memory using the currently active IMalloc* allocator
*/
STDAPI_(LPVOID) OleStdMalloc(ULONG ulSize)
{
   LPVOID pout;
   LPMALLOC pmalloc;

   if (CoGetMalloc(MEMCTX_TASK, &pmalloc) != NOERROR) {
      OleDbgAssertSz(0, szAssertMemAlloc);
      return NULL;
   }

   pout = (LPVOID)pmalloc->lpVtbl->Alloc(pmalloc, ulSize);

   if (pmalloc != NULL) {
      ULONG refs = pmalloc->lpVtbl->Release(pmalloc);
   }

   return pout;
}

/* OleStdRealloc
** -------------
**    re-allocate memory using the currently active IMalloc* allocator
*/
STDAPI_(LPVOID) OleStdRealloc(LPVOID pmem, ULONG ulSize)
{
   LPVOID pout = NULL;
   LPMALLOC pmalloc;

   if (CoGetMalloc(MEMCTX_TASK, &pmalloc) != NOERROR) {
      OleDbgAssertSz(0, szAssertMemAlloc);
      return NULL;
   }

   if (pmalloc != NULL)
   {
       ULONG refs;
       pout = (LPVOID)pmalloc->lpVtbl->Realloc(pmalloc, pmem, ulSize);
       refs = pmalloc->lpVtbl->Release(pmalloc);
   }

   return pout;
}

/* OleStdFree
** ----------
**    free memory using the currently active IMalloc* allocator
*/
STDAPI_(void) OleStdFree(LPVOID pmem)
{
   LPMALLOC pmalloc;

   if (pmem == NULL)
      return;

   if (CoGetMalloc(MEMCTX_TASK, &pmalloc) != NOERROR) {
      OleDbgAssertSz(0, szAssertMemAlloc);
      return;
   }

    if (pmalloc != NULL)
    {
        ULONG refs;
#if defined(_DEBUG)
        int iReturn;
        iReturn = pmalloc->lpVtbl->DidAlloc(pmalloc, pmem);
        OleDbgAssertSz(1 == iReturn, "OleStdFree tried to release a suspicious pointer");
#endif
        pmalloc->lpVtbl->Free(pmalloc, pmem);
        refs = pmalloc->lpVtbl->Release(pmalloc);
    }
}

/* OleStdGetSize
** -------------
**    Get the size of a memory block that was allocated using the
**    currently active IMalloc* allocator.
*/
STDAPI_(ULONG) OleStdGetSize(LPVOID pmem)
{
   ULONG ulSize;
   LPMALLOC pmalloc;

   if (CoGetMalloc(MEMCTX_TASK, &pmalloc) != NOERROR) {
      OleDbgAssertSz(0, szAssertMemAlloc);
      return (ULONG)-1;
   }

   ulSize = pmalloc->lpVtbl->GetSize(pmalloc, pmem);

   if (pmalloc != NULL) {
      ULONG refs = pmalloc->lpVtbl->Release(pmalloc);
   }

   return ulSize;
}

/* OleStdFreeString
** ----------------
**    Free a string that was allocated with the currently active
**    IMalloc* allocator.
**
**    if the caller has the current IMalloc* handy, then it can be
**    passed as a argument, otherwise this function will retrieve the
**    active allocator and use it.
*/
STDAPI_(void) OleStdFreeString(LPOLESTR lpsz, LPMALLOC lpMalloc)
{
   BOOL fMustRelease = FALSE;

   if (! lpMalloc) {
      if (CoGetMalloc(MEMCTX_TASK, &lpMalloc) != NOERROR)
         return;
      fMustRelease = TRUE;
   }

   lpMalloc->lpVtbl->Free(lpMalloc, lpsz);

   if (fMustRelease)
      lpMalloc->lpVtbl->Release(lpMalloc);
}

/* OleStdCopyString
** ----------------
**    Copy a string into memory allocated with the currently active
**    IMalloc* allocator.
**
**    if the caller has the current IMalloc* handy, then it can be
**    passed as a argument, otherwise this function will retrieve the
**    active allocator and use it.
*/
STDAPI_(LPOLESTR) OleStdCopyString(LPOLESTR lpszSrc, LPMALLOC lpMalloc)
{
   LPOLESTR lpszDest = NULL;
   BOOL fMustRelease = FALSE;
   UINT lSize = OLESTRLEN(lpszSrc);

   if (! lpMalloc) {
      if (CoGetMalloc(MEMCTX_TASK, &lpMalloc) != NOERROR)
         return NULL;
      fMustRelease = TRUE;
   }

   lpszDest = lpMalloc->lpVtbl->Alloc(lpMalloc, (lSize+1)*sizeof(OLECHAR));

   if (lpszDest)
      OLESTRCPY(lpszDest, lpszSrc);

   if (fMustRelease)
      lpMalloc->lpVtbl->Release(lpMalloc);
   return lpszDest;
}

/*
 * OleStdCreateStorageOnHGlobal()
 *
 * Purpose:
 *  Create a memory based IStorage*.
 *
 *  NOTE: if fDeleteOnRelease==TRUE, then the ILockBytes is created
 *            such that it will delete them memory on its last release.
 *            the IStorage on created on top of the ILockBytes in NOT
 *            created with STGM_DELETEONRELEASE. when the IStorage receives
 *            its last release, it will release the ILockBytes which will
 *            in turn free the memory. it is in fact an error to specify
 *            STGM_DELETEONRELEASE in this situation.
 *
 * Parameters:
 *  hGlobal --  handle to MEM_SHARE allocated memory. may be NULL and
 *              memory will be automatically allocated.
 *  fDeleteOnRelease -- controls if the memory is freed on the last release.
 *  grfMode --  flags passed to StgCreateDocfileOnILockBytes
 *
 *  NOTE: if hGlobal is NULL, then a new IStorage is created and
 *              STGM_CREATE flag is passed to StgCreateDocfileOnILockBytes.
 *        if hGlobal is non-NULL, then it is assumed that the hGlobal already
 *              has an IStorage inside it and STGM_CONVERT flag is passed
 *              to StgCreateDocfileOnILockBytes.
 *
 * Return Value:
 *    SCODE  -  S_OK if successful
 */
STDAPI_(LPSTORAGE) OleStdCreateStorageOnHGlobal(
      HANDLE hGlobal,
      BOOL fDeleteOnRelease,
      DWORD grfMode
)
{
   DWORD grfCreateMode=grfMode | (hGlobal==NULL ? STGM_CREATE:STGM_CONVERT);
   HRESULT hrErr;
   LPLOCKBYTES lpLockBytes = NULL;
   DWORD reserved = 0;
   LPSTORAGE lpStg = NULL;

   hrErr = CreateILockBytesOnHGlobal(
         hGlobal,
         fDeleteOnRelease,
         (LPLOCKBYTES FAR*)&lpLockBytes
   );
   if (hrErr != NOERROR)
      return NULL;

   hrErr = StgCreateDocfileOnILockBytes(
         lpLockBytes,
         grfCreateMode,
         reserved,
         (LPSTORAGE FAR*)&lpStg
   );
   if (hrErr != NOERROR) {
      OleStdRelease((LPUNKNOWN)lpLockBytes);
      return NULL;
   }
   OleStdRelease((LPUNKNOWN)lpLockBytes);
   return lpStg;
}

/*
 * OleStdCreateTempStorage()
 *
 * Purpose:
 *  Create a temporay IStorage* that will DeleteOnRelease.
 *  this can be either memory based or file based.
 *
 * Parameters:
 *  fUseMemory -- controls if memory-based or file-based stg is created
 *  grfMode --  storage mode flags
 *
 * Return Value:
 *    LPSTORAGE  -  if successful, NULL otherwise
 */
STDAPI_(LPSTORAGE) OleStdCreateTempStorage(BOOL fUseMemory, DWORD grfMode)
{
   LPSTORAGE   lpstg;
   HRESULT     hrErr;
   DWORD       reserved = 0;

   if (fUseMemory) {
      lpstg = OleStdCreateStorageOnHGlobal(
            NULL,  /* auto allocate */
            TRUE,  /* delete on release */

grfMode
      );
   } else {
      /* allocate a temp docfile that will delete on last release */
      hrErr = StgCreateDocfile(
            NULL,
            grfMode | STGM_DELETEONRELEASE | STGM_CREATE,
            reserved,
            &lpstg
      );
      if (hrErr != NOERROR)
         return NULL;
   }
   return lpstg;
}

/* OleStdGetOleObjectData
** ----------------------
**    Render CF_EMBEDSOURCE/CF_EMBEDDEDOBJECT data on an TYMED_ISTORAGE
**    medium by asking the object to save into the storage.
**    the object must support IPersistStorage.
**
**    if lpMedium->tymed == TYMED_NULL, then a delete-on-release
**    storage is allocated (either file-based or memory-base depending
**    the value of fUseMemory). this is useful to support an
**    IDataObject::GetData call where the callee must allocate the
**    medium.
**
**    if lpMedium->tymed == TYMED_ISTORAGE, then the data is writen
**    into the passed in IStorage. this is useful to support an
**    IDataObject::GetDataHere call where the caller has allocated his
**    own IStorage.
*/
STDAPI OleStdGetOleObjectData(
      LPPERSISTSTORAGE        lpPStg,
      LPFORMATETC             lpformatetc,
      LPSTGMEDIUM             lpMedium,
      BOOL                    fUseMemory
)
{
   LPSTORAGE   lpstg = NULL;
   DWORD       reserved = 0;
   SCODE       sc = S_OK;
   HRESULT     hrErr;

   lpMedium->pUnkForRelease = NULL;

   if (lpMedium->tymed == TYMED_NULL) {

      if (lpformatetc->tymed & TYMED_ISTORAGE) {

         /* allocate a temp docfile that will delete on last release */
         lpstg = OleStdCreateTempStorage(
               TRUE /*fUseMemory*/,
               STGM_READWRITE | STGM_TRANSACTED | STGM_SHARE_EXCLUSIVE
         );
         if (!lpstg)
            return E_OUTOFMEMORY;

         lpMedium->u.pstg = lpstg;
         lpMedium->tymed = TYMED_ISTORAGE;
         lpMedium->pUnkForRelease = NULL;
      } else {
         return DATA_E_FORMATETC;
      }
   } else if (lpMedium->tymed == TYMED_ISTORAGE) {
      lpMedium->tymed = TYMED_ISTORAGE;
   } else {
      return DATA_E_FORMATETC;
   }

   // NOTE: even if OleSave returns an error you should still call
   // SaveCompleted.

   OLEDBG_BEGIN2("OleSave called\r\n")
   hrErr = OleSave(lpPStg, lpMedium->u.pstg, FALSE /* fSameAsLoad */);
   OLEDBG_END2

   if (hrErr != NOERROR) {
      OleDbgOutHResult("WARNING: OleSave returned", hrErr);
      sc = hrErr;
   }
   OLEDBG_BEGIN2("IPersistStorage::SaveCompleted called\r\n")
   hrErr = lpPStg->lpVtbl->SaveCompleted(lpPStg, NULL);
   OLEDBG_END2

   if (hrErr != NOERROR) {
      OleDbgOutHResult("WARNING: SaveCompleted returned",hrErr);
      if (sc == S_OK)
         sc = hrErr;
   }

   return sc;
}

STDAPI OleStdGetLinkSourceData(
      LPMONIKER           lpmk,
      LPCLSID             lpClsID,
      LPFORMATETC         lpformatetc,
      LPSTGMEDIUM         lpMedium
)
{
   LPSTREAM    lpstm = NULL;
   DWORD       reserved = 0;
   HRESULT     hrErr;

   if (lpMedium->tymed == TYMED_NULL) {
      if (lpformatetc->tymed & TYMED_ISTREAM) {
         hrErr = CreateStreamOnHGlobal(
               NULL, /* auto allocate */
               TRUE, /* delete on release */
               (LPSTREAM FAR*)&lpstm
         );
         if (hrErr != NOERROR) {
            lpMedium->pUnkForRelease = NULL;
            return E_OUTOFMEMORY;
         }
         lpMedium->u.pstm = lpstm;
         lpMedium->tymed = TYMED_ISTREAM;
         lpMedium->pUnkForRelease = NULL;
      } else {
         lpMedium->pUnkForRelease = NULL;
         return DATA_E_FORMATETC;
      }
   } else {
      if (lpMedium->tymed == TYMED_ISTREAM) {
         lpMedium->tymed = TYMED_ISTREAM;
         lpMedium->u.pstm = lpMedium->u.pstm;
         lpMedium->pUnkForRelease = NULL;
      } else {
         lpMedium->pUnkForRelease = NULL;
         return DATA_E_FORMATETC;
      }
   }

   hrErr = OleSaveToStream((LPPERSISTSTREAM)lpmk, lpMedium->u.pstm);
   if (hrErr != NOERROR) return hrErr;
   return WriteClassStm(lpMedium->u.pstm, lpClsID);
}

/*
 * OleStdGetObjectDescriptorData
 *
 * Purpose:
 *  Fills and returns a OBJECTDESCRIPTOR structure.
 *  See OBJECTDESCRIPTOR for more information.
 *
 * Parameters:
 *  clsid           CLSID   CLSID of object being transferred
 *  dwDrawAspect    DWORD   Display Aspect of object
 *  sizel           SIZEL   Size of object in HIMETRIC
 *  pointl          POINTL  Offset from upper-left corner of object where mouse went
 *                          down for drag. Meaningful only when drag-drop is used.
 *  dwStatus        DWORD   OLEMISC flags
 *  lpszFullUserTypeName  LPSTR Full User Type Name
 *  lpszSrcOfCopy   LPSTR   Source of Copy
 *
 * Return Value:
 *  HBGLOBAL         Handle to OBJECTDESCRIPTOR structure.
 */
STDAPI_(HGLOBAL) OleStdGetObjectDescriptorData(
   CLSID     clsid,
   DWORD     dwDrawAspect,
   SIZEL     sizel,
   POINTL    pointl,
   DWORD     dwStatus,
   LPOLESTR  lpszFullUserTypeName,
   LPOLESTR  lpszSrcOfCopy
)
{
   HGLOBAL            hMem = NULL;
   IBindCtx   FAR    *pbc = NULL;
   LPOBJECTDESCRIPTOR lpOD;
   DWORD     dwObjectDescSize, dwFullUserTypeNameLen, dwSrcOfCopyLen;
   DWORD dwODSize ;

   // Get the length of Full User Type Name; Add 1 for the null terminator
   dwFullUserTypeNameLen = lpszFullUserTypeName ?
                                    OLESTRLEN(lpszFullUserTypeName)+1 : 0;

   // Get the Source of Copy string and it's length; Add 1 for the null terminator
   if (lpszSrcOfCopy)
      dwSrcOfCopyLen = OLESTRLEN(lpszSrcOfCopy)+1;
   else {
      // No src moniker so use user type name as source string.
      lpszSrcOfCopy =  lpszFullUserTypeName;
      dwSrcOfCopyLen = dwFullUserTypeNameLen;
   }

   // Allocate space for OBJECTDESCRIPTOR and the additional string data
   dwObjectDescSize = sizeof(OBJECTDESCRIPTOR);
   dwODSize = dwObjectDescSize + (sizeof(OLECHAR) *
                                (dwFullUserTypeNameLen + dwSrcOfCopyLen)) ;
   hMem = GlobalAlloc((GMEM_MOVEABLE | GMEM_SHARE), dwODSize) ;
   if (NULL == hMem)
      goto error;

   lpOD = (LPOBJECTDESCRIPTOR)GlobalLock(hMem);

   // Set the FullUserTypeName offset and copy the string
   if (lpszFullUserTypeName)
   {
      lpOD->dwFullUserTypeName = dwObjectDescSize;
      OLESTRCPY((LPOLESTR)((LPSTR)lpOD + lpOD->dwFullUserTypeName) ,
                                                    lpszFullUserTypeName);
   }
   else lpOD->dwFullUserTypeName = 0;  // zero offset indicates that string is not present

   // Set the SrcOfCopy offset and copy the string
   if (lpszSrcOfCopy)
   {
      lpOD->dwSrcOfCopy = dwObjectDescSize +
                               (sizeof(OLECHAR) * dwFullUserTypeNameLen) ;
      OLESTRCPY((LPOLESTR)((LPSTR)lpOD + lpOD->dwSrcOfCopy), lpszSrcOfCopy);
   }
   else lpOD->dwSrcOfCopy = 0;  // zero offset indicates that string is not present

   // Initialize the rest of the OBJECTDESCRIPTOR
   lpOD->cbSize       = dwODSize ;
   lpOD->clsid        = clsid;
   lpOD->dwDrawAspect = dwDrawAspect;
   lpOD->sizel        = sizel;
   lpOD->pointl       = pointl;
   lpOD->dwStatus     = dwStatus;

   GlobalUnlock(hMem);
   return hMem;

error:
   if (hMem)
   {
      GlobalUnlock(hMem);
      GlobalFree(hMem);
   }
   return NULL;
}

/*
 * OleStdGetObjectDescriptorDataFromOleObject
 *
 * Purpose:
 *  Fills and returns a OBJECTDESCRIPTOR structure. Information for the structure is
 *  obtained from an OLEOBJECT.
 *  See OBJECTDESCRIPTOR for more information.
 *
 * Parameters:
 *  lpOleObj        LPOLEOBJECT OleObject from which ONJECTDESCRIPTOR info
 *                  is obtained.
 *  lpszSrcOfCopy   LPSTR string to identify source of copy.
 *                  May be NULL in which case IOleObject::GetMoniker is called
 *                  to get the moniker of the object. if the object is loaded
 *                  as part of a data transfer document, then usually
 *                  lpOleClientSite==NULL is passed to OleLoad when loading
 *                  the object. in this case the IOleObject:GetMoniker call
 *                  will always fail (it tries to call back to the object's
 *                  client site). in this situation a non-NULL lpszSrcOfCopy
 *                  parameter should be passed.
 *  dwDrawAspect    DWORD   Display Aspect of object
 *  pointl          POINTL  Offset from upper-left corner of object where
 *                  mouse went down for drag. Meaningful only when drag-drop
 *                  is used.
 *  lpSizelHim      SIZEL   (optional) If the object is being scaled in its
 *                  container, then the container should pass the extents
 *                  that it is using to display the object.
 *                  May be NULL if the object is NOT being scaled. in this
 *                  case, IViewObject2::GetExtent will be called to get the
 *                  extents from the object.
 *
 * Return Value:
 *  HBGLOBAL         Handle to OBJECTDESCRIPTOR structure.
 */

STDAPI_(HGLOBAL) OleStdGetObjectDescriptorDataFromOleObject(
      LPOLEOBJECT lpOleObj,
      LPOLESTR       lpszSrcOfCopy,
      DWORD       dwDrawAspect,
      POINTL      pointl,
      LPSIZEL     lpSizelHim
)
{
   CLSID clsid;
   LPOLESTR lpszFullUserTypeName = NULL;
   char     lpszAnsiFullUserTypeName[256];
   LPMONIKER lpSrcMonikerOfCopy = NULL;
   HGLOBAL hObjDesc;
   IBindCtx  FAR  *pbc = NULL;
   HRESULT hrErr;
   SIZEL sizelHim;
   BOOL  fFreeSrcOfCopy = FALSE;
   LPOLELINK lpOleLink = (LPOLELINK)
         OleStdQueryInterface((LPUNKNOWN)lpOleObj,&IID_IOleLink);
   LPVIEWOBJECT2 lpViewObj2 = (LPVIEWOBJECT2)
         OleStdQueryInterface((LPUNKNOWN)lpOleObj, &IID_IViewObject2);
   BOOL  fIsLink = (lpOleLink ? TRUE : FALSE);
   OLECHAR  szLinkedTypeFmt[80];
   char     szAnsiLinkedTypeFmt[80];
   LPOLESTR lpszBuf = NULL;
   char     lpszAnsiBuf[256];
   DWORD dwStatus = 0;

   // Get CLSID
   OLEDBG_BEGIN2("IOleObject::GetUserClassID called\r\n")
   hrErr = lpOleObj->lpVtbl->GetUserClassID(lpOleObj, &clsid);
   OLEDBG_END2
   if (hrErr != NOERROR)
      clsid = CLSID_NULL;

   // Get FullUserTypeName
   OLEDBG_BEGIN2("IOleObject::GetUserType called\r\n")
   hrErr = lpOleObj->lpVtbl->GetUserType(
         lpOleObj,
         USERCLASSTYPE_FULL,
         /*(LPOLESTR FAR*)*/&lpszFullUserTypeName
   );
   OLEDBG_END2

   /* if object is a link, then expand usertypename to be "Linked %s" */
   if (fIsLink && lpszFullUserTypeName) {
      if (0 == LoadString(ghInst, (UINT)IDS_OLE2UIPASTELINKEDTYPE,
                  /*(LPSTR)*/szAnsiLinkedTypeFmt, 80))
         lstrcpy(szAnsiLinkedTypeFmt, /*(LPSTR)*/"Linked %s");
      A2W (szAnsiLinkedTypeFmt, szLinkedTypeFmt, 80);
      lpszBuf = OleStdMalloc(
            (OLESTRLEN(lpszFullUserTypeName)+OLESTRLEN(szLinkedTypeFmt)+1)*sizeof(OLECHAR));
      if (lpszBuf) {
         W2A (lpszFullUserTypeName, lpszAnsiFullUserTypeName, 256);
         wsprintf(lpszAnsiBuf, szAnsiLinkedTypeFmt, lpszAnsiFullUserTypeName);
         OleStdFreeString(lpszFullUserTypeName, NULL);
         A2W (lpszAnsiBuf, lpszBuf, 256);
         lpszFullUserTypeName = lpszBuf;
      }
   }

   /* Get Source Of Copy
   **    if the object is an embedding, then get the object's moniker
   **    if the object is a link, then get the link source moniker
   */
   if (fIsLink) {

      OLEDBG_BEGIN2("IOleLink::GetSourceDisplayName called\r\n")
      hrErr = lpOleLink->lpVtbl->GetSourceDisplayName(
            lpOleLink, &lpszSrcOfCopy );
      OLEDBG_END2
      fFreeSrcOfCopy = TRUE;

   } else {

      if (lpszSrcOfCopy == NULL) {
         OLEDBG_BEGIN2("IOleObject::GetMoniker called\r\n")
         hrErr = lpOleObj->lpVtbl->GetMoniker(
               lpOleObj,
               OLEGETMONIKER_TEMPFORUSER,
               OLEWHICHMK_OBJFULL,
               (LPMONIKER FAR*)&lpSrcMonikerOfCopy
         );
         OLEDBG_END2
         if (hrErr == NOERROR)
         {
            CreateBindCtx(0, (LPBC FAR*)&pbc);
            lpSrcMonikerOfCopy->lpVtbl->GetDisplayName(
                  lpSrcMonikerOfCopy, pbc, NULL, &lpszSrcOfCopy);
            pbc->lpVtbl->Release(pbc);
            fFreeSrcOfCopy = TRUE;
         }
      }
   }

   // Get SIZEL
   if (lpSizelHim) {
      // Use extents passed by the caller
      sizelHim = *lpSizelHim;
   } else if (lpViewObj2) {
      // Get the current extents from the object
      OLEDBG_BEGIN2("IViewObject2::GetExtent called\r\n")
      hrErr = lpViewObj2->lpVtbl->GetExtent(
            lpViewObj2,
            dwDrawAspect,
            -1,     /*lindex*/
            NULL,   /*ptd*/
            (LPSIZEL)&sizelHim
      );
      OLEDBG_END2
      if (hrErr != NOERROR)
         sizelHim.cx = sizelHim.cy = 0;
   } else {
      sizelHim.cx = sizelHim.cy = 0;
   }

   // Get DWSTATUS
   OLEDBG_BEGIN2("IOleObject::GetMiscStatus called\r\n")
   hrErr = lpOleObj->lpVtbl->GetMiscStatus(
            lpOleObj,
            dwDrawAspect,
            &dwStatus
   );
   OLEDBG_END2
   if (hrErr != NOERROR)
      dwStatus = 0;

   // Get OBJECTDESCRIPTOR
   hObjDesc = OleStdGetObjectDescriptorData(
         clsid,
         dwDrawAspect,
         sizelHim,
         pointl,
         dwStatus,
         lpszFullUserTypeName,
         lpszSrcOfCopy
   );
   if (! hObjDesc)
      goto error;

   // Clean up
   if (lpszFullUserTypeName)
      OleStdFreeString(lpszFullUserTypeName, NULL);
   if (fFreeSrcOfCopy && lpszSrcOfCopy)
      OleStdFreeString(lpszSrcOfCopy, NULL);
   if (lpSrcMonikerOfCopy)
      OleStdRelease((LPUNKNOWN)lpSrcMonikerOfCopy);
   if (lpOleLink)
      OleStdRelease((LPUNKNOWN)lpOleLink);
   if (lpViewObj2)
      OleStdRelease((LPUNKNOWN)lpViewObj2);

   return hObjDesc;

error:
   if (lpszFullUserTypeName)
      OleStdFreeString(lpszFullUserTypeName, NULL);
   if (fFreeSrcOfCopy && lpszSrcOfCopy)
      OleStdFreeString(lpszSrcOfCopy, NULL);
   if (lpSrcMonikerOfCopy)
      OleStdRelease((LPUNKNOWN)lpSrcMonikerOfCopy);
   if (lpOleLink)
      OleStdRelease((LPUNKNOWN)lpOleLink);
   if (lpViewObj2)
      OleStdRelease((LPUNKNOWN)lpViewObj2);

   return NULL;
}

/*
 * OleStdFillObjectDescriptorFromData
 *
 * Purpose:
 *  Fills and returns a OBJECTDESCRIPTOR structure. The source object will
 *  offer CF_OBJECTDESCRIPTOR if it is an OLE2 object, CF_OWNERLINK if it
 *  is an OLE1 object, or CF_FILENAME if it has been copied to the clipboard
 *  by FileManager.
 *
 * Parameters:
 *  lpDataObject    LPDATAOBJECT Source object
 *  lpmedium        LPSTGMEDIUM  Storage medium
 *  lpcfFmt         CLIPFORMAT FAR * Format offered by lpDataObject
 *                  (OUT parameter)
 *
 * Return Value:
 *  HBGLOBAL         Handle to OBJECTDESCRIPTOR structure.
 */

STDAPI_(HGLOBAL) OleStdFillObjectDescriptorFromData(
      LPDATAOBJECT     lpDataObject,
      LPSTGMEDIUM      lpmedium,
      CLIPFORMAT FAR* lpcfFmt
)
{
   CLSID              clsid;
   SIZEL              sizelHim;
   POINTL             pointl;
   LPOLESTR           lpsz, szFullUserTypeName, szSrcOfCopy, szClassName, szDocName, szItemName;
   int                nClassName, nDocName, nItemName, nFullUserTypeName;
   LPOLESTR           szBuf = NULL;
   HGLOBAL            hMem = NULL;
   HKEY               hKey = NULL;
   LPMALLOC           pIMalloc = NULL;
   DWORD              dw = OLEUI_CCHKEYMAX;
   HGLOBAL            hObjDesc;
   HRESULT            hrErr;
   char               szAnsiString[256];

   // GetData CF_OBJECTDESCRIPTOR format from the object on the clipboard.
   // Only OLE 2 objects on the clipboard will offer CF_OBJECTDESCRIPTOR
   if (hMem = OleStdGetData(
         lpDataObject,
         (CLIPFORMAT)cfObjectDescriptor,
         NULL,
         DVASPECT_CONTENT,
         lpmedium))
   {
      *lpcfFmt = cfObjectDescriptor;
      return hMem;  // Don't drop to clean up at the end of this function
   }
   // If CF_OBJECTDESCRIPTOR is not available, i.e. if this is not an OLE2 object,
   //     check if this is an OLE 1 object. OLE 1 objects will offer CF_OWNERLINK
   else if (hMem = OleStdGetData(
            lpDataObject,
            (CLIPFORMAT)cfOwnerLink,
            NULL,
            DVASPECT_CONTENT,
            lpmedium))
   {
      *lpcfFmt = cfOwnerLink;
      // CF_OWNERLINK contains null-terminated strings for class name, document name
      // and item name with two null terminating characters at the end
      szClassName = /*(LPOLESTR)*/GlobalLock(hMem);
      nClassName  = OLESTRLEN(szClassName);
      szDocName   = szClassName + nClassName + 1;
      nDocName    = OLESTRLEN(szDocName);
      szItemName  = szDocName + nDocName + 1;
      nItemName   = OLESTRLEN(szItemName);

      hrErr = CoGetMalloc(MEMCTX_TASK, &pIMalloc);
      if (hrErr != NOERROR)
         goto error;

      // Find FullUserTypeName from Registration database using class name
      if (RegOpenKey(HKEY_CLASSES_ROOT, NULL, &hKey) != ERROR_SUCCESS)
         goto error;

      // Allocate space for szFullUserTypeName & szSrcOfCopy. Maximum length of FullUserTypeName
      // is OLEUI_CCHKEYMAX. SrcOfCopy is constructed by concatenating FullUserTypeName, Document
      // Name and ItemName separated by spaces.
      szBuf = /*(LPOLESTR)*/pIMalloc->lpVtbl->Alloc(pIMalloc,
                     (DWORD)2*OLEUI_CCHKEYMAX+nDocName+nItemName+4);
      if (NULL == szBuf)
         goto error;
      szFullUserTypeName = szBuf;
      szSrcOfCopy = szFullUserTypeName+OLEUI_CCHKEYMAX+1;

      // Get FullUserTypeName
      if (RegQueryValue(hKey, NULL, szAnsiString, &dw) != ERROR_SUCCESS)
         goto error;
      A2W (szAnsiString, szFullUserTypeName, OLEUI_CCHKEYMAX);

      // Build up SrcOfCopy string from FullUserTypeName, DocumentName & ItemName
      lpsz = szSrcOfCopy;
      OLESTRCPY(lpsz, szFullUserTypeName);
      nFullUserTypeName = OLESTRLEN(szFullUserTypeName);
      lpsz[nFullUserTypeName]=' ';
      lpsz += nFullUserTypeName+1;
      OLESTRCPY(lpsz, szDocName);
      lpsz[nDocName] = ' ';
      lpsz += nDocName+1;
      OLESTRCPY(lpsz, szItemName);

      sizelHim.cx = sizelHim.cy = 0;
      pointl.x = pointl.y = 0;

      CLSIDFromProgID(szClassName, &clsid);

      hObjDesc = OleStdGetObjectDescriptorData(
            clsid,
            DVASPECT_CONTENT,
            sizelHim,
            pointl,
            0,
            szFullUserTypeName,
            szSrcOfCopy
      );
      if (!hObjDesc)
         goto error;
    }
    // Check if object is CF_FILENAME
    else if (hMem = OleStdGetData(
            lpDataObject,
            (CLIPFORMAT)cfFileName,
            NULL,
            DVASPECT_CONTENT,
            lpmedium))
    {
       *lpcfFmt = cfFileName;
       lpsz = /*(LPOLESTR)*/GlobalLock(hMem);
       hrErr = GetClassFile(lpsz, &clsid);

       /* NOTE: if the file does not have an OLE class
       **    associated, then use the OLE 1 Packager as the class of
       **    the object to be created. this is the behavior of
       **    OleCreateFromData API
       */
       if (hrErr != NOERROR)
         CLSIDFromProgID(OLESTR("Package"), &clsid);
       sizelHim.cx = sizelHim.cy = 0;
       pointl.x = pointl.y = 0;

       hrErr = CoGetMalloc(MEMCTX_TASK, &pIMalloc);
       if (hrErr != NOERROR)
         goto error;
       szBuf = /*(LPOLESTR)*/pIMalloc->lpVtbl->Alloc(pIMalloc, (DWORD)OLEUI_CCHKEYMAX);
       if (NULL == szBuf)
         goto error;

       OleStdGetUserTypeOfClass(&clsid, szBuf, OLEUI_CCHKEYMAX, NULL);

       hObjDesc = OleStdGetObjectDescriptorData(
            clsid,
            DVASPECT_CONTENT,
            sizelHim,
            pointl,
            0,
            szBuf,
            lpsz
      );
      if (!hObjDesc)
         goto error;
    }
    else goto error;

    // Clean up
    if (szBuf)
       pIMalloc->lpVtbl->Free(pIMalloc, (LPVOID)szBuf);
    if (pIMalloc)
       pIMalloc->lpVtbl->Release(pIMalloc);
    if (hMem)
    {
       GlobalUnlock(hMem);
       GlobalFree(hMem);
    }
    if (hKey)
       RegCloseKey(hKey);
    return hObjDesc;

error:
   if (szBuf)
      pIMalloc->lpVtbl->Free(pIMalloc, (LPVOID)szBuf);
   if (pIMalloc)
      pIMalloc->lpVtbl->Release(pIMalloc);
    if (hMem)
    {
       GlobalUnlock(hMem);
       GlobalFree(hMem);
    }
    if (hKey)
       RegCloseKey(hKey);
    return NULL;
}

#if defined( OBSOLETE )

/*************************************************************************
** The following API's have been converted into macros:
**          OleStdQueryOleObjectData
**          OleStdQueryLinkSourceData
**          OleStdQueryObjectDescriptorData
**          OleStdQueryFormatMedium
**          OleStdCopyMetafilePict
**          OleStdGetDropEffect
**
**    These macros are defined in olestd.h
*************************************************************************/

STDAPI OleStdQueryOleObjectData(LPFORMATETC lpformatetc)
{
   if (lpformatetc->tymed & TYMED_ISTORAGE) {
      return NOERROR;
   } else {
      return DATA_E_FORMATETC;
   }
}

STDAPI OleStdQueryLinkSourceData(LPFORMATETC lpformatetc)
{
   if (lpformatetc->tymed & TYMED_ISTREAM) {
      return NOERROR;
   } else {
      return DATA_E_FORMATETC;
   }
}

STDAPI OleStdQueryObjectDescriptorData(LPFORMATETC lpformatetc)
{
   if (lpformatetc->tymed & TYMED_HGLOBAL) {
      return NOERROR;
   } else {
      return DATA_E_FORMATETC;
   }
}

STDAPI OleStdQueryFormatMedium(LPFORMATETC lpformatetc, TYMED tymed)
{
   if (lpformatetc->tymed & tymed) {
      return NOERROR;
   } else {
      return DATA_E_FORMATETC;
   }
}

/*
 * OleStdCopyMetafilePict()
 *
 * Purpose:
 *    Make an independent copy of a MetafilePict
 * Parameters:
 *
 * Return Value:
 *    TRUE if successful, else FALSE.
 */
STDAPI_(BOOL) OleStdCopyMetafilePict(HANDLE hpictin, HANDLE FAR* phpictout)
{
   HANDLE hpictout;
   LPMETAFILEPICT ppictin, ppictout;

   if (hpictin == NULL || phpictout == NULL) {
      OleDbgAssert(hpictin == NULL || phpictout == NULL);
      return FALSE;
   }

   *phpictout = NULL;

   if ((ppictin = (LPMETAFILEPICT)GlobalLock(hpictin)) == NULL) {
      return FALSE;
   }

   hpictout = GlobalAlloc(GHND|GMEM_SHARE, sizeof(METAFILEPICT));

   if (hpictout && (ppictout = (LPMETAFILEPICT)GlobalLock(hpictout))){
      ppictout->hMF  = CopyMetaFile(ppictin->hMF, NULL);
      ppictout->xExt = ppictin->xExt;
      ppictout->yExt = ppictin->yExt;
      ppictout->mm   = ppictin->mm;
      GlobalUnlock(hpictout);
   }

   *phpictout = hpictout;

   return TRUE;

}

/* OleStdGetDropEffect
** -------------------
**
** Convert a keyboard state into a DROPEFFECT.
**
** returns the DROPEFFECT value derived from the key state.
**    the following is the standard interpretation:
**          no modifier -- Default Drop     (NULL is returned)
**          CTRL        -- DROPEFFECT_COPY
**          SHIFT       -- DROPEFFECT_MOVE
**          CTRL-SHIFT  -- DROPEFFECT_LINK
**
**    Default Drop: this depends on the type of the target application.
**    this is re-interpretable by each target application. a typical
**    interpretation is if the drag is local to the same document
**    (which is source of the drag) then a MOVE operation is
**    performed. if the drag is not local, then a COPY operation is
**    performed.
*/
STDAPI_(DWORD) OleStdGetDropEffect( DWORD grfKeyState )
{

   if (grfKeyState & MK_CONTROL) {

      if (grfKeyState & MK_SHIFT)
         return DROPEFFECT_LINK;
      else
         return DROPEFFECT_COPY;

   } else if (grfKeyState & MK_SHIFT)
      return DROPEFFECT_MOVE;

   return 0;    // no modifier -- do default operation
}
#endif  // OBSOLETE

/*
 * OleStdGetMetafilePictFromOleObject()
 *
 * Purpose:
 *      Generate a MetafilePict by drawing the OLE object.
 * Parameters:
 *  lpOleObj        LPOLEOBJECT pointer to OLE Object
 *  dwDrawAspect    DWORD   Display Aspect of object
 *  lpSizelHim      SIZEL   (optional) If the object is being scaled in its
 *                  container, then the container should pass the extents
 *                  that it is using to display the object.
 *                  May be NULL if the object is NOT being scaled. in this
 *                  case, IViewObject2::GetExtent will be called to get the
 *                  extents from the object.
 *  ptd             TARGETDEVICE FAR*   (optional) target device to render
 *                  metafile for. May be NULL.
 *
 * Return Value:
 *    HANDLE    -- handle of allocated METAFILEPICT
 */
STDAPI_(HANDLE) OleStdGetMetafilePictFromOleObject(
      LPOLEOBJECT         lpOleObj,
      DWORD               dwDrawAspect,
      LPSIZEL             lpSizelHim,
      DVTARGETDEVICE FAR* ptd
)
{
   LPVIEWOBJECT2 lpViewObj2 = NULL;
   HDC hDC;
   HMETAFILE hmf;
   HANDLE hMetaPict;
   LPMETAFILEPICT lpPict;
   RECT rcHim;
   RECTL rclHim;
   SIZEL sizelHim;
   HRESULT hrErr;
   SIZE size;
   POINT point;

   lpViewObj2 = (LPVIEWOBJECT2)OleStdQueryInterface(
         (LPUNKNOWN)lpOleObj, &IID_IViewObject2);
   if (! lpViewObj2)
      return NULL;

   // Get SIZEL
   if (lpSizelHim) {
      // Use extents passed by the caller
      sizelHim = *lpSizelHim;
   } else {
      // Get the current extents from the object
      OLEDBG_BEGIN2("IViewObject2::GetExtent called\r\n")
      hrErr = lpViewObj2->lpVtbl->GetExtent(
            lpViewObj2,
            dwDrawAspect,
            -1,     /*lindex*/
            ptd,    /*ptd*/
            (LPSIZEL)&sizelHim
      );
      OLEDBG_END2
      if (hrErr != NOERROR)
         sizelHim.cx = sizelHim.cy = 0;
   }

   hDC = CreateMetaFile(NULL);

   rclHim.left     = 0;
   rclHim.top      = 0;
   rclHim.right    = sizelHim.cx;
   rclHim.bottom   = sizelHim.cy;

   rcHim.left      = (int)rclHim.left;
   rcHim.top       = (int)rclHim.top;
   rcHim.right     = (int)rclHim.right;
   rcHim.bottom    = (int)rclHim.bottom;

   SetWindowOrgEx(hDC, rcHim.left, rcHim.top, &point);
   SetWindowExtEx(hDC, rcHim.right-rcHim.left, rcHim.bottom-rcHim.top,&size);

   OLEDBG_BEGIN2("IViewObject::Draw called\r\n")
   hrErr = lpViewObj2->lpVtbl->Draw(
         lpViewObj2,
         dwDrawAspect,
         -1,
         NULL,
         ptd,
         NULL,
         hDC,
         (LPRECTL)&rclHim,
         (LPRECTL)&rclHim,
         NULL,
         0
   );
   OLEDBG_END2

   OleStdRelease((LPUNKNOWN)lpViewObj2);
   if (hrErr != NOERROR) {
      OleDbgOutHResult("IViewObject::Draw returned", hrErr);
      CloseMetaFile(hDC);
      return(NULL);
   }

   hmf = CloseMetaFile(hDC);

   hMetaPict = GlobalAlloc(GHND|GMEM_SHARE, sizeof(METAFILEPICT));

   if (hMetaPict && (lpPict = (LPMETAFILEPICT)GlobalLock(hMetaPict))){
      lpPict->hMF  = hmf;
      lpPict->xExt = (int)sizelHim.cx ;
      lpPict->yExt = (int)sizelHim.cy ;
      lpPict->mm   = MM_ANISOTROPIC;
      GlobalUnlock(hMetaPict);
   }

   return hMetaPict;
}

/* Call Release on the object that is expected to go away.
**      if the refcnt of the object did no go to 0 then give a debug message.
*/
STDAPI_(ULONG) OleStdVerifyRelease(LPUNKNOWN lpUnk, LPOLESTR lpszMsg)
{
   ULONG cRef;
   char  lpszAnsiMsg[256];

   W2A (lpszMsg, lpszAnsiMsg, 256);
   cRef = lpUnk->lpVtbl->Release(lpUnk);

#if defined( _DEBUG )
   if (cRef != 0) {
      char szBuf[80];
      if (lpszMsg)
         MessageBox(NULL, lpszAnsiMsg, NULL, MB_ICONEXCLAMATION | MB_OK);
      wsprintf(
            /*(LPSTR)*/szBuf,
            "refcnt (%ld) != 0 after object (0x%lx) release\n",
            cRef,
            lpUnk
      );
      if (lpszMsg)
         OleDbgOut1(lpszAnsiMsg);
      OleDbgOut1(/*(LPSTR)*/szBuf);
      OleDbgAssertSz(cRef == 0, /*(LPSTR)*/szBuf);
   } else {
      char szBuf[80];
      wsprintf(
            /*(LPSTR)*/szBuf,
            "refcnt = 0 after object (0x%lx) release\n", lpUnk
      );
      OleDbgOut4(/*(LPSTR)*/szBuf);
   }
#endif
   return cRef;
}

/* Call Release on the object that is NOT necessarily expected to go away.
*/
STDAPI_(ULONG) OleStdRelease(LPUNKNOWN lpUnk)
{
   ULONG cRef;

   cRef = lpUnk->lpVtbl->Release(lpUnk);

#if defined( _DEBUG )
   {
      char szBuf[80];
      wsprintf(
            /*(LPSTR)*/szBuf,
            "refcnt = %ld after object (0x%lx) release\n",

cRef,
            lpUnk
      );
      OleDbgOut4(/*(LPSTR)*/szBuf);
   }
#endif
   return cRef;
}

/* OleStdInitVtbl
** --------------
**
**    Initialize an interface Vtbl to ensure that there are no NULL
**    function pointers in the Vtbl. All entries in the Vtbl are
**    set to a valid funtion pointer (OleStdNullMethod) that issues
**        debug assert message (message box) and returns E_NOTIMPL if called.
**
**    NOTE: this funtion does not initialize the Vtbl with usefull
**    function pointers, only valid function pointers to avoid the
**    horrible run-time crash when a call is made through the Vtbl with
**    a NULL function pointer. this API is only necessary when
**    initializing the Vtbl's in C. C++ guarantees that all interface
**    functions (in C++ terms -- pure virtual functions) are implemented.
*/

STDAPI_(void) OleStdInitVtbl(LPVOID lpVtbl, UINT nSizeOfVtbl)
{
   LPVOID FAR* lpFuncPtrArr = (LPVOID FAR*)lpVtbl;
   UINT nMethods = nSizeOfVtbl/sizeof(VOID FAR*);
   UINT i;

   for (i = 0; i < nMethods; i++) {
      lpFuncPtrArr[i] = OleStdNullMethod;
   }
}

/* OleStdCheckVtbl
** ---------------
**
**    Check if all entries in the Vtbl are properly initialized with
**    valid function pointers. If any entries are either NULL or
**    OleStdNullMethod, then this function returns FALSE. If compiled
**    for _DEBUG this function reports which function pointers are
**    invalid.
**
**    RETURNS:  TRUE if all entries in Vtbl are valid
**                              FALSE otherwise.
*/

STDAPI_(BOOL) OleStdCheckVtbl(LPVOID lpVtbl, UINT nSizeOfVtbl, LPOLESTR lpszIface)
{
   LPVOID FAR* lpFuncPtrArr = (LPVOID FAR*)lpVtbl;
   UINT nMethods = nSizeOfVtbl/sizeof(VOID FAR*);
   UINT i;
   BOOL fStatus = TRUE;
   int nChar = 0;

   for (i = 0; i < nMethods; i++) {
      if (lpFuncPtrArr[i] == NULL || lpFuncPtrArr[i] == OleStdNullMethod) {
#if defined( _DEBUG )
         char szAnsiStr[256];
         char szBuf[256];
         W2A (lpszIface, szAnsiStr, 256);
         wsprintf(szBuf, "%s::method# %d NOT valid!", szAnsiStr, i);
         OleDbgOut1(/*(LPSTR)*/szBuf);
#endif
         fStatus = FALSE;
      }
   }
   return fStatus;
}

/* OleStdNullMethod
** ----------------
**    Dummy method used by OleStdInitVtbl to initialize an interface
**    Vtbl to ensure that there are no NULL function pointers in the
**    Vtbl. All entries in the Vtbl are set to this function. this
**    function issues a debug assert message (message box) and returns
**    E_NOTIMPL if called. If all is done properly, this function will
**    NEVER be called!
*/
STDMETHODIMP OleStdNullMethod(LPUNKNOWN lpThis)
{
   MessageBox(
         NULL,
         "ERROR: INTERFACE METHOD NOT IMPLEMENTED!\r\n",
         NULL,
         MB_SYSTEMMODAL | MB_ICONHAND | MB_OK
   );

   return E_NOTIMPL;
}

static BOOL  GetFileTimes(LPSTR lpszFileName, FILETIME FAR* pfiletime)
{
#ifdef WIN32
   WIN32_FIND_DATA fd;
   HANDLE hFind;
   hFind = FindFirstFile(lpszFileName,&fd);
   if (hFind == NULL || hFind == INVALID_HANDLE_VALUE) {
      return FALSE;
   }
   FindClose(hFind);
   *pfiletime = fd.ftLastWriteTime;
   return TRUE;
#else
   static char sz[256];
   static struct _find_t fileinfo;

   LSTRCPYN(/*(LPSTR)*/sz, lpszFileName, sizeof(sz)-1);
   sz[sizeof(sz)-1]= '\0';
   AnsiToOem(sz, sz);
   return (_dos_findfirst(sz,_A_NORMAL|_A_HIDDEN|_A_SUBDIR|_A_SYSTEM,
                (struct _find_t *)&fileinfo) == 0 &&
      CoDosDateTimeToFileTime(fileinfo.wr_date,fileinfo.wr_time,pfiletime));
#endif
}

/* OleStdRegisterAsRunning
** -----------------------
**    Register a moniker in the RunningObjectTable.
**    if there is an existing registration (*lpdwRegister!=NULL), then
**    first revoke that registration.
**
**    new dwRegister key is returned via *lpdwRegister parameter.
*/
STDAPI_(void) OleStdRegisterAsRunning(LPUNKNOWN lpUnk, LPMONIKER lpmkFull, DWORD FAR* lpdwRegister)
{
   LPRUNNINGOBJECTTABLE lpROT;
   HRESULT hrErr;
   DWORD dwOldRegister = *lpdwRegister;

   OLEDBG_BEGIN2("OleStdRegisterAsRunning\r\n")

   OLEDBG_BEGIN2("GetRunningObjectTable called\r\n")
   hrErr = GetRunningObjectTable(0,(LPRUNNINGOBJECTTABLE FAR*)&lpROT);
   OLEDBG_END2

   if (hrErr == NOERROR) {

      /* register as running if a valid moniker is passed
      **
      ** NOTE: we deliberately register the new moniker BEFORE
      **    revoking the old moniker just in case the object
      **    currently has no external locks. if the object has no
      **    locks then revoking it from the running object table will
      **    cause the object's StubManager to initiate shutdown of
      **    the object.
      */
      if (lpmkFull) {

         OLEDBG_BEGIN2("IRunningObjectTable::Register called\r\n")
         lpROT->lpVtbl->Register(lpROT, 0, lpUnk,lpmkFull,lpdwRegister);
         OLEDBG_END2

#if defined(_DEBUG)
         {
            char szBuf[512];
            LPOLESTR lpszDisplay;
            char     lpszAnsiDisplay[256];
            LPBC lpbc;

            CreateBindCtx(0, (LPBC FAR*)&lpbc);
            lpmkFull->lpVtbl->GetDisplayName(
                  lpmkFull,
                  lpbc,
                  NULL,
                  /*(LPOLESTR FAR*)*/&lpszDisplay
            );
            OleStdRelease((LPUNKNOWN)lpbc);
            W2A (lpszDisplay, lpszAnsiDisplay, 256);
            wsprintf(
                  szBuf,
                  "Moniker '%s' REGISTERED as [0x%lx] in ROT\r\n",
                  lpszAnsiDisplay,
                  *lpdwRegister
            );
            OleDbgOut2(szBuf);
            OleStdFreeString(lpszDisplay, NULL);
         }
#endif  // _DEBUG

      }

      // if already registered, revoke
      if (dwOldRegister != 0) {

#if defined(_DEBUG)
         {
            char szBuf[512];

            wsprintf(
                  szBuf,
                  "Moniker [0x%lx] REVOKED from ROT\r\n",
                  dwOldRegister
            );
            OleDbgOut2(szBuf);
         }
#endif  // _DEBUG

         OLEDBG_BEGIN2("IRunningObjectTable::Revoke called\r\n")
         lpROT->lpVtbl->Revoke(lpROT, dwOldRegister);
         OLEDBG_END2
      }

      OleStdRelease((LPUNKNOWN)lpROT);
   } else {
      OleDbgAssertSz(
            lpROT != NULL,
            "OleStdRegisterAsRunning: GetRunningObjectTable FAILED\r\n"
      );
   }

   OLEDBG_END2
}

/* OleStdRevokeAsRunning
** ---------------------
**    Revoke a moniker from the RunningObjectTable if there is an
**    existing registration (*lpdwRegister!=NULL).
**
**    *lpdwRegister parameter will be set to NULL.
*/
STDAPI_(void) OleStdRevokeAsRunning(DWORD FAR* lpdwRegister)
{
   LPRUNNINGOBJECTTABLE lpROT;
   HRESULT hrErr;

   OLEDBG_BEGIN2("OleStdRevokeAsRunning\r\n")

   // if still registered, then revoke
   if (*lpdwRegister != 0) {

      OLEDBG_BEGIN2("GetRunningObjectTable called\r\n")
      hrErr = GetRunningObjectTable(0,(LPRUNNINGOBJECTTABLE FAR*)&lpROT);
      OLEDBG_END2

      if (hrErr == NOERROR) {

#if defined(_DEBUG)
         {
            char szBuf[512];

            wsprintf(
                  szBuf,
                  "Moniker [0x%lx] REVOKED from ROT\r\n",
                  *lpdwRegister
            );
            OleDbgOut2(szBuf);
         }
#endif  // _DEBUG

         OLEDBG_BEGIN2("IRunningObjectTable::Revoke called\r\n")
         lpROT->lpVtbl->Revoke(lpROT, *lpdwRegister);
         OLEDBG_END2

         *lpdwRegister = 0;

         OleStdRelease((LPUNKNOWN)lpROT);
      } else {
         OleDbgAssertSz(
               lpROT != NULL,
               "OleStdRevokeAsRunning: GetRunningObjectTable FAILED\r\n"
         );
      }
   }
   OLEDBG_END2
}

/* OleStdNoteFileChangeTime
** ------------------------
**    Note the time a File-Based object has been saved in the
**    RunningObjectTable. These change times are used as the basis for
**    IOleObject::IsUpToDate.
**    It is important to set the time of the file-based object
**    following a save operation to exactly the time of the saved file.
**    this helps IOleObject::IsUpToDate to give the correct answer
**    after a file has been saved.
*/
STDAPI_(void) OleStdNoteFileChangeTime(LPOLESTR lpszFileName, DWORD dwRegister)
{
   char lpszAnsiFileName[256];

   W2A (lpszFileName, lpszAnsiFileName, 256);
   if (dwRegister != 0) {

      LPRUNNINGOBJECTTABLE lprot;
      FILETIME filetime;

      if (GetFileTimes(lpszAnsiFileName, &filetime) &&
         GetRunningObjectTable(0,&lprot) == NOERROR)
      {
         lprot->lpVtbl->NoteChangeTime( lprot, dwRegister, &filetime );
         lprot->lpVtbl->Release(lprot);

         OleDbgOut2("IRunningObjectTable::NoteChangeTime called\r\n");
      }
   }
}

/* OleStdNoteObjectChangeTime
** --------------------------
**    Set the last change time of an object that is registered in the
**    RunningObjectTable. These change times are used as the basis for
**    IOleObject::IsUpToDate.
**
**    every time the object sends out a OnDataChange notification, it
**    should update the Time of last change in the ROT.
**
**    NOTE: this function set the change time to the current time.
*/
STDAPI_(void) OleStdNoteObjectChangeTime(DWORD dwRegister)
{
   if (dwRegister != 0) {

      LPRUNNINGOBJECTTABLE lprot;
      FILETIME filetime;

      if (GetRunningObjectTable(0,&lprot) == NOERROR)
      {
         CoFileTimeNow( &filetime );
         lprot->lpVtbl->NoteChangeTime( lprot, dwRegister, &filetime );
         lprot->lpVtbl->Release(lprot);

         OleDbgOut2("IRunningObjectTable::NoteChangeTime called\r\n");
      }
   }
}

/* OleStdCreateTempFileMoniker
** ---------------------------
**    return the next available FileMoniker that can be used as the
**    name of an untitled document.
**    the FileMoniker is built of the form:
**          <lpszPrefixString><number>
**      eg. "Outline1", "Outline2", etc.
**
**    The RunningObjectTable (ROT) is consulted to determine if a
**    FileMoniker is in use. If the name is in use then the number is
**    incremented and the ROT is checked again.
**
** Parameters:
**    LPSTR lpszPrefixString    - prefix used to build the name
**    UINT FAR* lpuUnique       - (IN-OUT) last used number.
**                                  this number is used to make the
**                                  name unique. on entry, the input
**                                  number is incremented. on output,
**                                  the number used is returned. this
**                                  number should be passed again
**                                  unchanged on the next call.
**    LPSTR lpszName            - (OUT) buffer used to build string.
**                                  caller must be sure buffer is large
**                                  enough to hold the generated string.
**    LPMONIKER FAR* lplpmk     - (OUT) next unused FileMoniker
**
** Returns:
**    void
**
** Comments:
**    This function is similar in spirit to the Windows API
**    CreateTempFileName.
*/
STDAPI_(void) OleStdCreateTempFileMoniker(
      LPOLESTR        lpszPrefixString,
      UINT FAR*       lpuUnique,
      LPOLESTR        lpszName,
      LPMONIKER FAR*  lplpmk
)
{
   LPRUNNINGOBJECTTABLE lpROT = NULL;
   UINT i = (lpuUnique != NULL ? *lpuUnique : 1);
   HRESULT hrErr;
   char    lpszAnsiPrefixString[256], lpszAnsiName[256];

   W2A (lpszPrefixString, lpszAnsiPrefixString, 256);
   wsprintf(lpszAnsiName, "%s%d", lpszAnsiPrefixString, i++);
   A2W (lpszAnsiName, lpszName, OLEUI_CCHLABELMAX);
   CreateFileMoniker(lpszName, lplpmk);

   OLEDBG_BEGIN2("GetRunningObjectTable called\r\n")
   hrErr = GetRunningObjectTable(0,(LPRUNNINGOBJECTTABLE FAR*)&lpROT);
   OLEDBG_END2

   if (hrErr == NOERROR) {

      while (1) {
         if (! *lplpmk)
            break;  // failed to create FileMoniker

         OLEDBG_BEGIN2("IRunningObjectTable::IsRunning called\r\n")
         hrErr = lpROT->lpVtbl->IsRunning(lpROT,*lplpmk);
         OLEDBG_END2

         if (hrErr != NOERROR)
            break;  // the Moniker is NOT running; found unused one!

         OleStdVerifyRelease(
               (LPUNKNOWN)*lplpmk,
               OLESTR("OleStdCreateTempFileMoniker: Moniker NOT released")
            );

         wsprintf(lpszAnsiName, "%s%d", lpszAnsiPrefixString, i++);
         A2W (lpszAnsiName, lpszName, OLEUI_CCHLABELMAX);
         CreateFileMoniker(lpszName, lplpmk);
      }

      OleStdRelease((LPUNKNOWN)lpROT);
   }

   if (lpuUnique != NULL)
      *lpuUnique = i;
}

/* OleStdGetFirstMoniker
** ---------------------
**    return the first piece of a moniker.
**
**    NOTE: if the given moniker is not a generic composite moniker,
**    then an AddRef'ed pointer to the given moniker is returned.
*/
STDAPI_(LPMONIKER) OleStdGetFirstMoniker(LPMONIKER lpmk)
{
   LPMONIKER       lpmkFirst = NULL;
   LPENUMMONIKER   lpenumMoniker;
   DWORD           dwMksys;
   HRESULT         hrErr;

   if (! lpmk)
      return NULL;

   if (lpmk->lpVtbl->IsSystemMoniker(lpmk, (LPDWORD)&dwMksys) == NOERROR
      && dwMksys == MKSYS_GENERICCOMPOSITE) {

      /* NOTE: the moniker is a GenericCompositeMoniker.
      **    enumerate the moniker to pull off the first piece.
      */

      hrErr = lpmk->lpVtbl->Enum(
            lpmk,
            TRUE /* fForward */,
            (LPENUMMONIKER FAR*)&lpenumMoniker
      );
      if (hrErr != NOERROR)
         return NULL;    // ERROR: give up!

      hrErr = lpenumMoniker->lpVtbl->Next(
            lpenumMoniker,
            1,
            (LPMONIKER FAR*)&lpmkFirst,
            NULL
      );
      lpenumMoniker->lpVtbl->Release(lpenumMoniker);
      return lpmkFirst;

   } else {
      /* NOTE: the moniker is NOT a GenericCompositeMoniker.
      **    return an AddRef'ed pointer to the input moniker.
      */
      lpmk->lpVtbl->AddRef(lpmk);
      return lpmk;
   }
}

/* OleStdGetLenFilePrefixOfMoniker
** -------------------------------
**    if the first piece of the Moniker is a FileMoniker, then return
**    the length of the filename string.
**
**    lpmk      pointer to moniker
**
**    Returns
**      0       if moniker does NOT start with a FileMoniker
**      uLen    string length of filename prefix of the display name
**              retrieved from the given (lpmk) moniker.
*/
STDAPI_(ULONG) OleStdGetLenFilePrefixOfMoniker(LPMONIKER lpmk)
{
   LPMONIKER       lpmkFirst = NULL;
   DWORD           dwMksys;
   LPOLESTR        lpsz = NULL;
   LPBC            lpbc = NULL;
   ULONG           uLen = 0;
   HRESULT         hrErr;

   if (! lpmk)
      return 0;

   lpmkFirst = OleStdGetFirstMoniker(lpmk);
   if (lpmkFirst) {
      if ( (lpmkFirst->lpVtbl->IsSystemMoniker(
                     lpmkFirst, (LPDWORD)&dwMksys) == NOERROR)
            && dwMksys == MKSYS_FILEMONIKER) {

         hrErr = CreateBindCtx(0, (LPBC FAR*)&lpbc);
         if (hrErr == NOERROR) {
            hrErr = lpmkFirst->lpVtbl->GetDisplayName(
                  lpmkFirst,
                  lpbc,
                  NULL,   /* pmkToLeft */
                  /*(LPOLESTR FAR*)*/&lpsz
            );
            if (hrErr == NOERROR && lpsz != NULL) {
               uLen = OLESTRLEN(lpsz);
               OleStdFreeString(lpsz, NULL);
            }
            OleStdRelease((LPUNKNOWN)lpbc);
         }
      }
      lpmkFirst->lpVtbl->Release(lpmkFirst);
   }
   return uLen;
}

/* OleStdMkParseDisplayName
**    Parse a string into a Moniker by calling the OLE API
**    MkParseDisplayName. if the original link source class was an OLE1
**    class, then attempt the parsing assuming the same class applies.
**
**    if the class of the previous link source was an OLE1 class,
**    then first attempt to parse a string that it is qualified
**    with the progID associated with the OLE1 class. this more
**    closely matches the semantics of OLE1 where the class of
**    link sources is not expected to change. prefixing the
**    string with "@<ProgID -- OLE1 class name>!" will force the
**    parsing of the string to assume the file is of that
**    class.
**    NOTE: this trick of prepending the string with "@<ProgID>
**    only works for OLE1 classes.
**
**  PARAMETERS:
**    REFCLSID rClsid       -- original class of link source.
**                              CLSID_NULL if class is unknown
**    ... other parameters the same as MkParseDisplayName API ...
**
**  RETURNS
**    NOERROR if string parsed successfully
**    else error code returned by MkParseDisplayName
*/
STDAPI OleStdMkParseDisplayName(
      REFCLSID        rClsid,
      LPBC            lpbc,
      LPOLESTR        lpszUserName,
      ULONG FAR*      lpchEaten,
      LPMONIKER FAR*  lplpmk
)
{
   HRESULT hrErr;

   if (!IsEqualCLSID(rClsid,&CLSID_NULL) && CoIsOle1Class(rClsid) &&
      lpszUserName[0] != '@') {
      LPOLESTR lpszBuf;
      LPOLESTR lpszProgID;
      char     lpszAnsiBuf[256], lpszAnsiProgID[256], lpszAnsiUserName[256];

      // Prepend "@<ProgID>!" to the input string
      ProgIDFromCLSID(rClsid, &lpszProgID);
      if (lpszProgID == NULL)
         goto Cont1;
      lpszBuf = OleStdMalloc(
            ((ULONG)OLESTRLEN(lpszUserName)+OLESTRLEN(lpszProgID)+3) * sizeof(OLECHAR));
      if (lpszBuf == NULL) {
         if (lpszProgID)
            OleStdFree(lpszProgID);
         goto Cont1;
      }

      W2A (lpszProgID, lpszAnsiProgID, 256);
      W2A (lpszUserName, lpszAnsiUserName, 256);
      wsprintf(lpszAnsiBuf, "@%s!%s", lpszAnsiProgID, lpszAnsiUserName);
      A2W (lpszAnsiBuf, lpszBuf, OLESTRLEN(lpszUserName)+OLESTRLEN(lpszProgID)+3);
      OLEDBG_BEGIN2("MkParseDisplayName called\r\n")
      hrErr = MkParseDisplayName(lpbc, lpszBuf, lpchEaten, lplpmk);
      OLEDBG_END2

      if (lpszProgID)
         OleStdFree(lpszProgID);
      if (lpszBuf)
         OleStdFree(lpszBuf);

      if (hrErr == NOERROR)
         return NOERROR;
   }

Cont1:
   OLEDBG_BEGIN2("MkParseDisplayName called\r\n")
   hrErr = MkParseDisplayName(lpbc, lpszUserName, lpchEaten, lplpmk);
   OLEDBG_END2

   return hrErr;
}

/*
 * OleStdMarkPasteEntryList
 *
 * Purpose:
 *  Mark each entry in the PasteEntryList if its format is available from
 *  the source IDataObject*. the dwScratchSpace field of each PasteEntry
 *  is set to TRUE if available, else FALSE.
 *
 * Parameters:
 *  LPOLEUIPASTEENTRY   array of PasteEntry structures
 *  int                 count of elements in PasteEntry array
 *  LPDATAOBJECT        source IDataObject* pointer
 *
 * Return Value:
 *   none
 */
STDAPI_(void) OleStdMarkPasteEntryList(
      LPDATAOBJECT        lpSrcDataObj,
      LPOLEUIPASTEENTRY   lpPriorityList,
      int                 cEntries
)
{
   LPENUMFORMATETC     lpEnumFmtEtc = NULL;
   #define FORMATETC_MAX 20
   FORMATETC           rgfmtetc[FORMATETC_MAX];
   int                 i;
   HRESULT             hrErr;
   long                j, cFetched;

   // Clear all marks
   for (i = 0; i < cEntries; i++) {
      lpPriorityList[i].dwScratchSpace = FALSE;

      if (! lpPriorityList[i].fmtetc.cfFormat) {
         // caller wants this item always considered available
         // (by specifying a NULL format)
         lpPriorityList[i].dwScratchSpace = TRUE;
      } else if (lpPriorityList[i].fmtetc.cfFormat == cfEmbeddedObject
            || lpPriorityList[i].fmtetc.cfFormat == cfEmbedSource
            || lpPriorityList[i].fmtetc.cfFormat == cfFileName) {

         // if there is an OLE object format, then handle it
         // specially by calling OleQueryCreateFromData. the caller
         // need only specify one object type format.
         OLEDBG_BEGIN2("OleQueryCreateFromData called\r\n")
         hrErr = OleQueryCreateFromData(lpSrcDataObj);
         OLEDBG_END2
         if(NOERROR == hrErr) {
            lpPriorityList[i].dwScratchSpace = TRUE;
         }
      } else if (lpPriorityList[i].fmtetc.cfFormat == cfLinkSource) {

         // if there is OLE 2.0 LinkSource format, then handle it
         // specially by calling OleQueryLinkFromData.
         OLEDBG_BEGIN2("OleQueryLinkFromData called\r\n")
         hrErr = OleQueryLinkFromData(lpSrcDataObj);
         OLEDBG_END2
         if(NOERROR == hrErr) {
            lpPriorityList[i].dwScratchSpace = TRUE;
         }
      }
   }

   OLEDBG_BEGIN2("IDataObject::EnumFormatEtc called\r\n")
   hrErr = lpSrcDataObj->lpVtbl->EnumFormatEtc(
         lpSrcDataObj,
         DATADIR_GET,
         (LPENUMFORMATETC FAR*)&lpEnumFmtEtc
   );
   OLEDBG_END2

   if (hrErr != NOERROR)
      return;    // unable to get format enumerator

   // Enumerate the formats offered by the source
   // Loop over all formats offered by the source
   cFetched = 0;
   _fmemset(rgfmtetc,0,sizeof(rgfmtetc[FORMATETC_MAX]) );
   if (lpEnumFmtEtc->lpVtbl->Next(
         lpEnumFmtEtc, FORMATETC_MAX, rgfmtetc, &cFetched) == NOERROR
      || (cFetched > 0 && cFetched <= FORMATETC_MAX) )
   {

      for (j = 0; j < cFetched; j++)
      {
         for (i = 0; i < cEntries; i++)
         {
            if (! lpPriorityList[i].dwScratchSpace &&
               IsCloseFormatEtc(&rgfmtetc[j], &lpPriorityList[i].fmtetc))
            {
               lpPriorityList[i].dwScratchSpace = TRUE;
            }
         }
      }
   } // endif

   // Clean up
   if (lpEnumFmtEtc)
      OleStdRelease((LPUNKNOWN)lpEnumFmtEtc);
}

/* OleStdGetPriorityClipboardFormat
** --------------------------------
**
**    Retrieve the first clipboard format in a list for which data
**    exists in the source IDataObject*.
**
**    Returns -1 if no acceptable match is found.
**            index of first acceptable match in the priority list.
**
*/
STDAPI_(int) OleStdGetPriorityClipboardFormat(
      LPDATAOBJECT        lpSrcDataObj,
      LPOLEUIPASTEENTRY   lpPriorityList,
      int                 cEntries
)
{
   int i;
   int nFmtEtc = -1;

   // Mark all entries that the Source provides
   OleStdMarkPasteEntryList(lpSrcDataObj, lpPriorityList, cEntries);

   // Loop over the target's priority list of formats
   for (i = 0; i < cEntries; i++)
   {
      if (lpPriorityList[i].dwFlags != OLEUIPASTE_PASTEONLY &&
            !(lpPriorityList[i].dwFlags & OLEUIPASTE_PASTE))
         continue;

      // get first marked entry
      if (lpPriorityList[i].dwScratchSpace) {
         nFmtEtc = i;
         break;          // Found priority format; DONE
      }
   }

   return nFmtEtc;
}

/* OleStdIsDuplicateFormat
** -----------------------
**    Returns TRUE if the lpFmtEtc->cfFormat is found in the array of
**    FormatEtc structures.
*/
STDAPI_(BOOL) OleStdIsDuplicateFormat(
      LPFORMATETC         lpFmtEtc,
      LPFORMATETC         arrFmtEtc,
      int                 nFmtEtc
)
{
   int i;

   for (i = 0; i < nFmtEtc; i++) {
      if (IsEqualFORMATETC((*lpFmtEtc), arrFmtEtc[i]))
         return TRUE;
   }

   return FALSE;
}

/* OleStdGetItemToken
 * ------------------
 *
 * LPSTR lpszSrc - Pointer to a source string
 * LPSTR lpszDst - Pointer to destination buffer
 *
 * Will copy one token from the lpszSrc buffer to the lpszItem buffer.
 * It considers all alpha-numeric and white space characters as valid
 * characters for a token. the first non-valid character delimates the
 * token.
 *
 * returns the number of charaters eaten.
 */
STDAPI_(ULONG) OleStdGetItemToken(LPOLESTR lpszSrc, LPOLESTR lpszDst, int nMaxChars)
{
   ULONG chEaten = 0L;
   char szAnsiSrc[256], szAnsiDst[256];
   LPSTR lpszAnsiDst = szAnsiDst;
   LPSTR lpszAnsiSrc = szAnsiSrc;

   W2A (lpszSrc, szAnsiSrc, 256);

   // skip leading delimeter characters
   while (*lpszAnsiSrc && --nMaxChars > 0
                        && ((*lpszAnsiSrc=='/') || (*lpszAnsiSrc=='\\') ||
                           (*lpszAnsiSrc=='!') || (*lpszAnsiSrc==':'))) {
      *lpszAnsiSrc++;
      chEaten++;
   }

   // Extract token string (up to first delimeter char or EOS)
   while (*lpszAnsiSrc && --nMaxChars > 0
                        && !((*lpszAnsiSrc=='/') || (*lpszAnsiSrc=='\\') ||
                           (*lpszAnsiSrc=='!') || (*lpszAnsiSrc==':'))) {
      *lpszAnsiDst++ = *lpszAnsiSrc++;
      chEaten++;
   }
   *lpszAnsiDst = '\0';

   A2W (szAnsiDst, lpszDst, OLEUI_CCHPATHMAX);
   return chEaten;
}

/*************************************************************************
** OleStdCreateRootStorage
**    create a root level Storage given a filename that is compatible
**    to be used by a top-level OLE container. if the filename
**    specifies an existing file, then an error is returned.
**    the root storage (Docfile) that is created by this function
**    is suitable to be used to create child storages for embedings.
**    (CreateChildStorage can be used to create child storages.)
**    NOTE: the root-level storage is opened in transacted mode.
*************************************************************************/

STDAPI_(LPSTORAGE) OleStdCreateRootStorage(LPOLESTR lpszStgName, DWORD grfMode)
{
   HRESULT hr;
   DWORD grfCreateMode = STGM_READWRITE | STGM_TRANSACTED;
   DWORD reserved = 0;
   LPSTORAGE lpRootStg;
   char szMsg[64];

   // if temp file is being created, enable delete-on-release
   if (! lpszStgName)
      grfCreateMode |= STGM_DELETEONRELEASE;

   hr = StgCreateDocfile(
         lpszStgName,
         grfMode | grfCreateMode,
         reserved,
         (LPSTORAGE FAR*)&lpRootStg
      );

   if (hr == NOERROR)
      return lpRootStg;               // existing file successfully opened

   OleDbgOutHResult("StgCreateDocfile returned", hr);

   if (0 == LoadString(ghInst, (UINT)IDS_OLESTDNOCREATEFILE, /*(LPSTR)*/szMsg, 64))
     return NULL;

   MessageBox(NULL, /*(LPSTR)*/szMsg, NULL,MB_ICONEXCLAMATION | MB_OK);
   return NULL;
}

/*************************************************************************
** OleStdOpenRootStorage
**    open a root level Storage given a filename that is compatible
**    to be used by a top-level OLE container. if the file does not
**    exist then an error is returned.
**    the root storage (Docfile) that is opened by this function
**    is suitable to be used to create child storages for embedings.
**    (CreateChildStorage can be used to create child storages.)
**    NOTE: the root-level storage is opened in transacted mode.
*************************************************************************/

STDAPI_(LPSTORAGE) OleStdOpenRootStorage(LPOLESTR lpszStgName, DWORD grfMode)
{
   HRESULT hr;
   DWORD reserved = 0;
   LPSTORAGE lpRootStg;
   char  szMsg[64];

   if (lpszStgName) {

      hr = StgOpenStorage(
            lpszStgName,
            NULL,
            grfMode | STGM_TRANSACTED,
            NULL,
            reserved,
            (LPSTORAGE FAR*)&lpRootStg
         );

      if (hr == NOERROR)
         return lpRootStg;     // existing file successfully opened

      OleDbgOutHResult("StgOpenStorage returned", hr);
   }

   if (0 == LoadString(ghInst, (UINT)IDS_OLESTDNOOPENFILE, szMsg, 64))
     return NULL;

   MessageBox(NULL, /*(LPSTR)*/szMsg, NULL,MB_ICONEXCLAMATION | MB_OK);
   return NULL;
}

/*************************************************************************
** OpenOrCreateRootStorage
**    open a root level Storage given a filename that is compatible
**    to be used by a top-level OLE container. if the filename
**    specifies an existing file, then it is open, otherwise a new file
**    with the given name is created.
**    the root storage (Docfile) that is created by this function
**    is suitable to be used to create child storages for embedings.
**    (CreateChildStorage can be used to create child storages.)
**    NOTE: the root-level storage is opened in transacted mode.
*************************************************************************/

STDAPI_(LPSTORAGE) OleStdOpenOrCreateRootStorage(LPOLESTR lpszStgName, DWORD grfMode)
{
   HRESULT hrErr;
   SCODE sc;
   DWORD reserved = 0;
   LPSTORAGE lpRootStg;
   char      szMsg[64];

   if (lpszStgName) {

      hrErr = StgOpenStorage(
            lpszStgName,
            NULL,
            grfMode | STGM_READWRITE | STGM_TRANSACTED,
            NULL,
            reserved,
            (LPSTORAGE FAR*)&lpRootStg
      );

if (hrErr == NOERROR)
         return lpRootStg;      // existing file successfully opened

      OleDbgOutHResult("StgOpenStorage returned", hrErr);
      sc = hrErr;

      if (sc!=STG_E_FILENOTFOUND && sc!=STG_E_FILEALREADYEXISTS) {
         return NULL;
      }
   }

   /* if file did not already exist, try to create a new one */
   hrErr = StgCreateDocfile(
         lpszStgName,
         grfMode | STGM_READWRITE | STGM_TRANSACTED,
         reserved,
         (LPSTORAGE FAR*)&lpRootStg
   );

   if (hrErr == NOERROR)
      return lpRootStg;               // existing file successfully opened

   OleDbgOutHResult("StgCreateDocfile returned", hrErr);

   if (0 == LoadString(ghInst, (UINT)IDS_OLESTDNOCREATEFILE, /*(LPSTR)*/szMsg, 64))
     return NULL;

   MessageBox(NULL, /*(LPSTR)*/szMsg, NULL, MB_ICONEXCLAMATION | MB_OK);
   return NULL;
}

/*
** OleStdCreateChildStorage
**    create a child Storage inside the given lpStg that is compatible
**    to be used by an embedded OLE object. the return value from this
**    function can be passed to OleCreateXXX functions.
**    NOTE: the child storage is opened in transacted mode.
*/
STDAPI_(LPSTORAGE) OleStdCreateChildStorage(LPSTORAGE lpStg, LPOLESTR lpszStgName)
{
   if (lpStg != NULL) {
      LPSTORAGE lpChildStg;
      DWORD grfMode = (STGM_READWRITE | STGM_TRANSACTED |
            STGM_SHARE_EXCLUSIVE);
      DWORD reserved = 0;

      HRESULT hrErr = lpStg->lpVtbl->CreateStorage(
            lpStg,
            lpszStgName,
            grfMode,
            reserved,
            reserved,
            (LPSTORAGE FAR*)&lpChildStg
         );

      if (hrErr == NOERROR)
         return lpChildStg;

      OleDbgOutHResult("lpStg->lpVtbl->CreateStorage returned", hrErr);
   }
   return NULL;
}

/*
** OleStdOpenChildStorage
**    open a child Storage inside the given lpStg that is compatible
**    to be used by an embedded OLE object. the return value from this
**    function can be passed to OleLoad function.
**    NOTE: the child storage is opened in transacted mode.
*/
STDAPI_(LPSTORAGE) OleStdOpenChildStorage(LPSTORAGE lpStg, LPOLESTR lpszStgName, DWORD grfMode)
{
   LPSTORAGE lpChildStg;
   LPSTORAGE lpstgPriority = NULL;
   DWORD reserved = 0;
   HRESULT hrErr;

   if (lpStg  != NULL) {

      hrErr = lpStg->lpVtbl->OpenStorage(
            lpStg,
            lpszStgName,
            lpstgPriority,
            grfMode | STGM_TRANSACTED | STGM_SHARE_EXCLUSIVE,
            NULL,
            reserved,
            (LPSTORAGE FAR*)&lpChildStg
         );

      if (hrErr == NOERROR)
         return lpChildStg;

      OleDbgOutHResult("lpStg->lpVtbl->OpenStorage returned", hrErr);
   }
   return NULL;
}

/* OleStdCommitStorage
** -------------------
**    Commit the changes to the given IStorage*. This routine can be
**    called on either a root-level storage as used by an OLE-Container
**    or by a child storage as used by an embedded object.
**
**    This routine first attempts to perform this commit in a safe
**    manner. if this fails it then attempts to do the commit in a less
**    robust manner (STGC_OVERWRITE).
*/
STDAPI_(BOOL) OleStdCommitStorage(LPSTORAGE lpStg)
{
   HRESULT hrErr;

   // make the changes permanent
   hrErr = lpStg->lpVtbl->Commit(lpStg, 0);

   if (hrErr == STG_E_MEDIUMFULL) {
      // try to commit changes in less robust manner.
      OleDbgOut("Warning: commiting with STGC_OVERWRITE specified\n");
      hrErr = lpStg->lpVtbl->Commit(lpStg, STGC_OVERWRITE);
   }

   if (hrErr != NOERROR)
   {
      char szMsg[64];

      if (0 == LoadString(ghInst, (UINT)IDS_OLESTDDISKFULL, /*(LPSTR)*/szMsg, 64))
         return FALSE;

      MessageBox(NULL, /*(LPSTR)*/szMsg, NULL, MB_ICONEXCLAMATION | MB_OK);
      return FALSE;
   }
   else {
      return TRUE;
   }
}

/* OleStdDestroyAllElements
** ------------------------
**    Destroy all elements within an open storage. this is subject
**    to the current transaction.
*/
STDAPI OleStdDestroyAllElements(LPSTORAGE lpStg)
{
   IEnumSTATSTG FAR* lpEnum;
   STATSTG sstg;
   HRESULT hrErr;

   hrErr = lpStg->lpVtbl->EnumElements(
         lpStg, 0, NULL, 0, (IEnumSTATSTG FAR* FAR*)&lpEnum);

   if (hrErr != NOERROR)
      return hrErr;

   while (1) {
      if (lpEnum->lpVtbl->Next(lpEnum, 1, &sstg, NULL) != NOERROR)
         break;
      lpStg->lpVtbl->DestroyElement(lpStg, sstg.pwcsName);
      OleStdFree(sstg.pwcsName);
   }
   lpEnum->lpVtbl->Release(lpEnum);
   return NOERROR;
}

// returns 1 for a close match
//  (all fields match exactly except the tymed which simply overlaps)
// 0 for no match

int IsCloseFormatEtc(FORMATETC FAR* pFetcLeft, FORMATETC FAR* pFetcRight)
{
        if (pFetcLeft->cfFormat != pFetcRight->cfFormat)
                return 0;
        else if (!OleStdCompareTargetDevice (pFetcLeft->ptd, pFetcRight->ptd))
                return 0;
        if (pFetcLeft->dwAspect != pFetcRight->dwAspect)
                return 0;
        return((pFetcLeft->tymed | pFetcRight->tymed) != 0);
}


OLEUTL.C

/*
 * OLEUTL.C
 *
 * Miscellaneous utility functions for OLE 2.0 Applications:
 *
 *  Function                      Purpose
 *  -------------------------------------------------------------------
 *  SetDCToDrawInHimetricRect     Sets up an HIMETRIC mapping mode in a DC.
 *  ResetOrigDC                   Performs the opposite of
 *                                SetDCToDrawInHimetricRect
 *  XformWidthInPixelsToHimetric  Converts an int width into HiMetric units
 *  XformWidthInHimetricToPixels  Converts an int width from HiMetric units
 *  XformHeightInPixelsToHimetric Converts an int height into HiMetric units
 *  XformHeightInHimetricToPixels Converts an int height from HiMetric units
 *  XformRectInPixelsToHimetric   Converts a rect into HiMetric units
 *  XformRectInHimetricToPixels   Converts a rect from HiMetric units
 *  XformSizeInPixelsToHimetric   Converts a SIZEL into HiMetric units
 *  XformSizeInHimetricToPixels   Converts a SIZEL from HiMetric units
 *  AreRectsEqual                 Compares to Rect's
 *
 *  ParseCmdLine                  Determines if -Embedding exists
 *  OpenOrCreateRootStorage       Creates a root docfile for OLE storage
 *  CommitStorage                 Commits all changes in a docfile
 *  CreateChildStorage            Creates child storage in another storage
 *  OpenChildStorage              Opens child storage in another storage
 *
 *
 * Copyright (c)1992-1996 Microsoft Corporation, All Right Reserved
 */

#define STRICT  1
#include "olestd.h"
#include <stdlib.h>
#include <ctype.h>

//Internal function to this module
static LPSTR GetWord(LPSTR lpszSrc, LPSTR lpszDst);

/*
 * SetDCToAnisotropic
 *
 * Purpose:
 *  Setup the correspondence between the rect in device unit (Viewport) and
 *  the rect in logical unit (Window) so that the proper scaling of
 *  coordinate systems will be calculated. set up both the Viewport and
 *  the window as follows:
 *
 *      1) ------------------ ( 2
 *      |                     |
 *      |                     |
 *      |                     |
 *      |                     |
 *      |                     |
 *      3) ------------------ ( 4
 *
 *      Origin   = P3
 *      X extent = P2x - P3x
 *      Y extent = P2y - P3y
 *
 * Parameters:
 *  hDC             HDC to affect
 *  lprcPhysical    LPRECT containing the physical (device) extents of DC
 *  lprcLogical     LPRECT containing the logical extents
 *  lprcWindowOld   LPRECT in which to preserve the window for ResetOrigDC
 *  lprcViewportOld LPRECT in which to preserver the viewport for ResetOrigDC
 *
 * Return Value:
 *  int             The original mapping mode of the DC.
 */

STDAPI_(int) SetDCToAnisotropic(
                HDC hDC,
                LPRECT lprcPhysical, LPRECT lprcLogical,
                LPRECT lprcWindowOld, LPRECT lprcViewportOld)
{
        int     nMapModeOld=SetMapMode(hDC, MM_ANISOTROPIC);

        SetWindowOrgEx(hDC, lprcLogical->left, lprcLogical->bottom, (LPPOINT)&lprcWindowOld->left);
        SetWindowExtEx(hDC, (lprcLogical->right-lprcLogical->left), (lprcLogical->top-lprcLogical->bottom), (LPSIZE)&lprcWindowOld->right);
        SetViewportOrgEx(hDC, lprcPhysical->left, lprcPhysical->bottom, (LPPOINT)&lprcViewportOld->left);
        SetViewportExtEx(hDC, (lprcPhysical->right-lprcPhysical->left), (lprcPhysical->top-lprcPhysical->bottom), (LPSIZE)&lprcViewportOld->right);

        return nMapModeOld;
}

/*
 * SetDCToDrawInHimetricRect
 *
 * Purpose:
 *  Setup the correspondence between the rect in pixels (Viewport) and
 *  the rect in HIMETRIC (Window) so that the proper scaling of
 *  coordinate systems will be calculated. set up both the Viewport and
 *  the window as follows:
 *
 *      1) ------------------ ( 2
 *      |                     |
 *      |                     |
 *      |                     |
 *      |                     |
 *      |                     |
 *      3) ------------------ ( 4
 *
 *      Origin   = P3
 *      X extent = P2x - P3x
 *      Y extent = P2y - P3y
 *
 * Parameters:
 *  hDC             HDC to affect
 *  lprcPix         LPRECT containing the pixel extents of DC
 *  lprcHiMetric    LPRECT to receive the himetric extents
 *  lprcWindowOld   LPRECT in which to preserve the window for ResetOrigDC
 *  lprcViewportOld LPRECT in which to preserver the viewport for ResetOrigDC
 *
 * Return Value:
 *  int             The original mapping mode of the DC.
 */
STDAPI_(int) SetDCToDrawInHimetricRect(
        HDC hDC,
        LPRECT lprcPix, LPRECT lprcHiMetric,
        LPRECT lprcWindowOld, LPRECT lprcViewportOld)
        {
        int     nMapModeOld=SetMapMode(hDC, MM_ANISOTROPIC);
        BOOL    fSystemDC  =FALSE;

        if (NULL==hDC)
                {
                hDC=GetDC(NULL);
                fSystemDC=TRUE;
                }

        XformRectInPixelsToHimetric(hDC, lprcPix, lprcHiMetric);

        SetWindowOrgEx(hDC, lprcHiMetric->left, lprcHiMetric->bottom, (LPPOINT)&lprcWindowOld->left);
        SetWindowExtEx(hDC, (lprcHiMetric->right-lprcHiMetric->left), (lprcHiMetric->top-lprcHiMetric->bottom), (LPSIZE)&lprcWindowOld->right);
        SetViewportOrgEx(hDC, lprcPix->left, lprcPix->bottom, (LPPOINT)&lprcViewportOld->left);
        SetViewportExtEx(hDC, (lprcPix->right-lprcPix->left), (lprcPix->top-lprcPix->bottom), (LPSIZE)&lprcViewportOld->right);

        if (fSystemDC)
                ReleaseDC(NULL, hDC);

        return nMapModeOld;
        }

/*
 * ResetOrigDC
 *
 * Purpose:
 *  Restores a DC set to draw in himetric from SetDCToDrawInHimetricRect.
 *
 * Parameters:
 *  hDC             HDC to restore
 *  nMapModeOld     int original mapping mode of hDC
 *  lprcWindowOld   LPRECT filled in SetDCToDrawInHimetricRect
 *  lprcViewportOld LPRECT filled in SetDCToDrawInHimetricRect
 *
 * Return Value:
 *  int             Same as nMapModeOld.
 */

STDAPI_(int) ResetOrigDC(
        HDC hDC, int nMapModeOld,
        LPRECT lprcWindowOld, LPRECT lprcViewportOld)
        {
        POINT     pOld;

        SetMapMode(hDC, nMapModeOld);

        SetWindowOrgEx(hDC,   lprcWindowOld->left,    lprcWindowOld->top,      (LPPOINT)&pOld);
        SetWindowExtEx(hDC,   lprcWindowOld->right,   lprcWindowOld->bottom,   (LPSIZE)&pOld);
        SetViewportOrgEx(hDC, lprcViewportOld->left,  lprcViewportOld->top,    (LPPOINT)&pOld);
        SetViewportExtEx(hDC, lprcViewportOld->right, lprcViewportOld->bottom, (LPSIZE)&pOld);

        return nMapModeOld;
        }

/*
 * XformWidthInPixelsToHimetric
 * XformWidthInHimetricToPixels
 * XformHeightInPixelsToHimetric
 * XformHeightInHimetricToPixels
 *
 * Functions to convert an int between a device coordinate system and
 * logical HiMetric units.
 *
 * Parameters:
 *  hDC             HDC providing reference to the pixel mapping.  If
 *                  NULL, a screen DC is used.
 *
 *  Size Functions:
 *  lpSizeSrc       LPSIZEL providing the structure to convert.  This
 *                  contains pixels in XformSizeInPixelsToHimetric and
 *                  logical HiMetric units in the complement function.
 *  lpSizeDst       LPSIZEL providing the structure to receive converted
 *                  units.  This contains pixels in
 *                  XformSizeInPixelsToHimetric and logical HiMetric
 *                  units in the complement function.
 *
 *  Width Functions:
 *  iWidth          int containing the value to convert.
 *
 * Return Value:
 *  Size Functions:     None
 *  Width Functions:    Converted value of the input parameters.
 *
 * NOTE:
 *  When displaying on the screen, Window apps display everything enlarged
 *  from its actual size so that it is easier to read. For example, if an
 *  app wants to display a 1in. horizontal line, that when printed is
 *  actually a 1in. line on the printed page, then it will display the line
 *  on the screen physically larger than 1in. This is described as a line
 *  that is "logically" 1in. along the display width. Windows maintains as
 *  part of the device-specific information about a given display device:
 *      LOGPIXELSX -- no. of pixels per logical in along the display width
 *      LOGPIXELSY -- no. of pixels per logical in along the display height
 *
 *  The following formula converts a distance in pixels into its equivalent
 *  logical HIMETRIC units:
 *
 *      DistInHiMetric = (HIMETRIC_PER_INCH * DistInPix)
 *                       -------------------------------
 *                           PIXELS_PER_LOGICAL_IN
 *
 */
STDAPI_(int) XformWidthInPixelsToHimetric(HDC hDC, int iWidthInPix)
        {
        int     iXppli;     //Pixels per logical inch along width
        int     iWidthInHiMetric;
        BOOL    fSystemDC=FALSE;

        if (NULL==hDC)
                {
                hDC=GetDC(NULL);
                fSystemDC=TRUE;
                }

        iXppli = GetDeviceCaps (hDC, LOGPIXELSX);

        //We got pixel units, convert them to logical HIMETRIC along the display
        iWidthInHiMetric = MAP_PIX_TO_LOGHIM(iWidthInPix, iXppli);

        if (fSystemDC)
                ReleaseDC(NULL, hDC);

        return iWidthInHiMetric;
        }

STDAPI_(int) XformWidthInHimetricToPixels(HDC hDC, int iWidthInHiMetric)
        {
        int     iXppli;     //Pixels per logical inch along width
        int     iWidthInPix;
        BOOL    fSystemDC=FALSE;

        if (NULL==hDC)
                {
                hDC=GetDC(NULL);
                fSystemDC=TRUE;
                }

        iXppli = GetDeviceCaps (hDC, LOGPIXELSX);

        //We got logical HIMETRIC along the display, convert them to pixel units
        iWidthInPix = MAP_LOGHIM_TO_PIX(iWidthInHiMetric, iXppli);

        if (fSystemDC)
                ReleaseDC(NULL, hDC);

        return iWidthInPix;
        }

STDAPI_(int) XformHeightInPixelsToHimetric(HDC hDC, int iHeightInPix)
        {
        int     iYppli;     //Pixels per logical inch along height
        int     iHeightInHiMetric;
        BOOL    fSystemDC=FALSE;

        if (NULL==hDC)
                {
                hDC=GetDC(NULL);
                fSystemDC=TRUE;
                }

        iYppli = GetDeviceCaps (hDC, LOGPIXELSY);

        //* We got pixel units, convert them to logical HIMETRIC along the display
        iHeightInHiMetric = MAP_PIX_TO_LOGHIM(iHeightInPix, iYppli);

        if (fSystemDC)
                ReleaseDC(NULL, hDC);

        return iHeightInHiMetric;
        }

STDAPI_(int) XformHeightInHimetricToPixels(HDC hDC, int iHeightInHiMetric)
        {
        int     iYppli;     //Pixels per logical inch along height
        int     iHeightInPix;
        BOOL    fSystemDC=FALSE;

        if (NULL==hDC)
                {
                hDC=GetDC(NULL);
                fSystemDC=TRUE;
                }

        iYppli = GetDeviceCaps (hDC, LOGPIXELSY);

        //* We got logical HIMETRIC along the display, convert them to pixel units
        iHeightInPix = MAP_LOGHIM_TO_PIX(iHeightInHiMetric, iYppli);

        if (fSystemDC)
                ReleaseDC(NULL, hDC);

        return iHeightInPix;
        }

/*
 * XformRectInPixelsToHimetric
 * XformRectInHimetricToPixels
 *
 * Purpose:
 *  Convert a rectangle between pixels of a given hDC and HIMETRIC units
 *  as manipulated in OLE.  If the hDC is NULL, then a screen DC is used
 *  and assumes the MM_TEXT mapping mode.
 *
 * Parameters:
 *  hDC             HDC providing reference to the pixel mapping.  If
 *                  NULL, a screen DC is used.
 *  lprcSrc         LPRECT providing the rectangle to convert.  This
 *                  contains pixels in XformRectInPixelsToHimetric and
 *                  logical HiMetric units in the complement function.
 *  lprcDst         LPRECT providing the rectangle to receive converted units.
 *                  This contains pixels in XformRectInPixelsToHimetric and
 *                  logical HiMetric units in the complement function.
 *
 * Return Value:
 *  None
 *
 * NOTE:
 *  When displaying on the screen, Window apps display everything enlarged
 *  from its actual size so that it is easier to read. For example, if an
 *  app wants to display a 1in. horizontal line, that when printed is
 *  actually a 1in. line on the printed page, then it will display the line
 *  on the screen physically larger than 1in. This is described as a line
 *  that is "logically" 1in. along the display width. Windows maintains as
 *  part of the device-specific information about a given display device:
 *      LOGPIXELSX -- no. of pixels per logical in along the display width
 *      LOGPIXELSY -- no. of pixels per logical in along the display height
 *
 *  The following formula converts a distance in pixels into its equivalent
 *  logical HIMETRIC units:
 *
 *      DistInHiMetric = (HIMETRIC_PER_INCH * DistInPix)
 *                      -------------------------------
 *                            PIXELS_PER_LOGICAL_IN
 *
 * Rect in Pixels (MM_TEXT):
 *
 *              0---------- X
 *              |
 *              |       1) ------------------ ( 2   P1 = (rc.left, rc.top)
 *              |       |                     |     P2 = (rc.right, rc.top)
 *              |       |                     |     P3 = (rc.left, rc.bottom)
 *              |       |                     |     P4 = (rc.right, rc.bottom)
 *                      |                     |
 *              Y       |                     |
 *                      3) ------------------ ( 4
 *
 *              NOTE:   Origin   = (P1x, P1y)
 *                      X extent = P4x - P1x
 *                      Y extent = P4y - P1y
 *
 *
 * Rect in Himetric (MM_HIMETRIC):
 *
 *
 *                      1) ------------------ ( 2   P1 = (rc.left, rc.top)
 *              Y       |                     |     P2 = (rc.right, rc.top)
 *                      |                     |     P3 = (rc.left, rc.bottom)
 *              |       |                     |     P4 = (rc.right, rc.bottom)
 *              |       |                     |
 *              |       |                     |
 *              |       3) ------------------ ( 4
 *              |
 *              0---------- X
 *
 *              NOTE:   Origin   = (P3x, P3y)
 *                      X extent = P2x - P3x
 *                      Y extent = P2y - P3y
 *
 *
 */

STDAPI_(void) XformRectInPixelsToHimetric(
        HDC hDC, LPRECT lprcPix, LPRECT lprcHiMetric)
        {
        int     iXppli;     //Pixels per logical inch along width
        int     iYppli;     //Pixels per logical inch along height
        int     iXextInPix=(lprcPix->right-lprcPix->left);
        int     iYextInPix=(lprcPix->bottom-lprcPix->top);
        BOOL    fSystemDC=FALSE;

        if (NULL==hDC ||
            GetDeviceCaps(hDC, TECHNOLOGY) == DT_METAFILE ||
            GetDeviceCaps(hDC, LOGPIXELSX) == 0)
                {
                hDC=GetDC(NULL);
                fSystemDC=TRUE;
                }

        iXppli = GetDeviceCaps (hDC, LOGPIXELSX);
        iYppli = GetDeviceCaps (hDC, LOGPIXELSY);

        //We got pixel units, convert them to logical HIMETRIC along the display
        lprcHiMetric->right = MAP_PIX_TO_LOGHIM(iXextInPix, iXppli);
        lprcHiMetric->top   = MAP_PIX_TO_LOGHIM(iYextInPix, iYppli);

        lprcHiMetric->left    = 0;
        lprcHiMetric->bottom  = 0;

        if (fSystemDC)
                ReleaseDC(NULL, hDC);

        return;
        }

STDAPI_(void) XformRectInHimetricToPixels(
        HDC hDC, LPRECT lprcHiMetric, LPRECT lprcPix)
        {
        int     iXppli;     //Pixels per logical inch along width
        int     iYppli;     //Pixels per logical inch along height
        int     iXextInHiMetric=(lprcHiMetric->right-lprcHiMetric->left);
        int     iYextInHiMetric=(lprcHiMetric->bottom-lprcHiMetric->top);
        BOOL    fSystemDC=FALSE;

        if (NULL==hDC ||
            GetDeviceCaps(hDC, TECHNOLOGY) == DT_METAFILE ||
            GetDeviceCaps(hDC, LOGPIXELSX) == 0)
                {
                hDC=GetDC(NULL);
                fSystemDC=TRUE;
                }

        iXppli = GetDeviceCaps (hDC, LOGPIXELSX);
        iYppli = GetDeviceCaps (hDC, LOGPIXELSY);

        //We got pixel units, convert them to logical HIMETRIC along the display
        lprcPix->right = MAP_LOGHIM_TO_PIX(iXextInHiMetric, iXppli);
        lprcPix->top   = MAP_LOGHIM_TO_PIX(iYextInHiMetric, iYppli);

        lprcPix->left  = 0;
        lprcPix->bottom= 0;

        if (fSystemDC)
                ReleaseDC(NULL, hDC);

        return;
        }

/*
 * XformSizeInPixelsToHimetric
 * XformSizeInHimetricToPixels
 *
 * Functions to convert a SIZEL structure (Size functions) or
 * an int (Width functions) between a device coordinate system and
 * logical HiMetric units.
 *
 * Parameters:
 *  hDC             HDC providing reference to the pixel mapping.  If
 *                  NULL, a screen DC is used.
 *
 *  Size Functions:
 *  lpSizeSrc       LPSIZEL providing the structure to convert.  This
 *                  contains pixels in XformSizeInPixelsToHimetric and
 *                  logical HiMetric units in the complement function.
 *  lpSizeDst       LPSIZEL providing the structure to receive converted
 *                  units.  This contains pixels in
 *                  XformSizeInPixelsToHimetric and logical HiMetric
 *                  units in the complement function.
 *
 *  Width Functions:
 *  iWidth          int containing the value to convert.
 *
 * Return Value:
 *  Size Functions:     None
 *  Width Functions:    Converted value of the input parameters.
 *
 * NOTE:
 *  When displaying on the screen, Window apps display everything enlarged
 *  from its actual size so that it is easier to read. For example, if an
 *  app wants to display a 1in. horizontal line, that when printed is
 *  actually a 1in. line on the printed page, then it will display the line
 *  on the screen physically larger than 1in. This is described as a line
 *  that is "logically" 1in. along the display width. Windows maintains as
 *  part of the device-specific information about a given display device:
 *      LOGPIXELSX -- no. of pixels per logical in along the display width
 *      LOGPIXELSY -- no. of pixels per logical in along the display height
 *
 *  The following formula converts a distance in pixels into its equivalent
 *  logical HIMETRIC units:
 *
 *      DistInHiMetric = (HIMETRIC_PER_INCH * DistInPix)
 *                       -------------------------------
 *                           PIXELS_PER_LOGICAL_IN
 *
 */

STDAPI_(void) XformSizeInPixelsToHimetric(
        HDC hDC, LPSIZEL lpSizeInPix, LPSIZEL lpSizeInHiMetric)
        {
        int     iXppli;     //Pixels per logical inch along width
        int     iYppli;     //Pixels per logical inch along height
        BOOL    fSystemDC=FALSE;

        if (NULL==hDC ||
            GetDeviceCaps(hDC, TECHNOLOGY) == DT_METAFILE ||
            GetDeviceCaps(hDC, LOGPIXELSX) == 0)
                {
                hDC=GetDC(NULL);
                fSystemDC=TRUE;
                }

        iXppli = GetDeviceCaps (hDC, LOGPIXELSX);
        iYppli = GetDeviceCaps (hDC, LOGPIXELSY);

        //We got pixel units, convert them to logical HIMETRIC along the display
        lpSizeInHiMetric->cx = (long)MAP_PIX_TO_LOGHIM((int)lpSizeInPix->cx, iXppli);
        lpSizeInHiMetric->cy = (long)MAP_PIX_TO_LOGHIM((int)lpSizeInPix->cy, iYppli);

        if (fSystemDC)
                ReleaseDC(NULL, hDC);

        return;
        }

STDAPI_(void) XformSizeInHimetricToPixels(
        HDC hDC, LPSIZEL lpSizeInHiMetric, LPSIZEL lpSizeInPix)
        {
        int     iXppli;     //Pixels per logical inch along width
        int     iYppli;     //Pixels per logical inch along height
        BOOL    fSystemDC=FALSE;

        if (NULL==hDC ||
            GetDeviceCaps(hDC, TECHNOLOGY) == DT_METAFILE ||
            GetDeviceCaps(hDC, LOGPIXELSX) == 0)
                {
                hDC=GetDC(NULL);
                fSystemDC=TRUE;
                }

        iXppli = GetDeviceCaps (hDC, LOGPIXELSX);
        iYppli = GetDeviceCaps (hDC, LOGPIXELSY);

        //We got logical HIMETRIC along the display, convert them to pixel units
        lpSizeInPix->cx = (long)MAP_LOGHIM_TO_PIX((int)lpSizeInHiMetric->cx, iXppli);
        lpSizeInPix->cy = (long)MAP_LOGHIM_TO_PIX((int)lpSizeInHiMetric->cy, iYppli);

        if (fSystemDC)
                ReleaseDC(NULL, hDC);

        return;
        }

#if defined( OBSOLETE )
// This function has been converted to a macro

/* AreRectsEqual
** -------------
*/
STDAPI_(BOOL) AreRectsEqual(LPRECT lprc1, LPRECT lprc2)
{
        if ((lprc1->top == lprc2->top) &&
                (lprc1->left == lprc2->left) &&
                (lprc1->right == lprc2->right) &&
                (lprc1->bottom == lprc2->bottom))
                return TRUE;

        return FALSE;
}
#endif  // OBSOLETE

/*
 * ParseCmdLine
 *
 * Parses the Windows command line which was passed to WinMain.
 * This function determines if the -Embedding switch has been given.
 *
 */

STDAPI_(void) ParseCmdLine(
        LPSTR lpszLine,
        BOOL FAR* lpfEmbedFlag,
        LPSTR szFileName)
{
        int i=0;
        char szBuf[256];

        if(lpfEmbedFlag)
                *lpfEmbedFlag = FALSE;
        szFileName[0]='\0';             // NULL string

        // skip blanks
        while(isspace(*lpszLine)) lpszLine++;

        if(!*lpszLine)   // No filename or options, so start a fresh document.
                return;

        // Check for "-Embedding" or "/Embedding" and set fEmbedding.
        if(lpfEmbedFlag && (*lpszLine == '-' || *lpszLine == '/')) {
                lpszLine++;
                lpszLine = GetWord(lpszLine, szBuf);
                *lpfEmbedFlag = !lstrcmp(szBuf, EMBEDDINGFLAG);
        }

        // skip blanks
        while(isspace(*lpszLine)) lpszLine++;

        // set szFileName to argument
        while(lpszLine[i]) {
                szFileName[i]=lpszLine[i];
                i++;
        }
        szFileName[i]='\0';
}

/* GetWord
 * -------
 *
 * LPSTR lpszSrc - Pointer to a source string
 * LPSTR lpszDst - Pointer to destination buffer
 *
 * Will copy one space-terminated or null-terminated word from the source
 * string to the destination buffer.
 * returns: pointer to next character following the word.
 */
static LPSTR GetWord(LPSTR lpszSrc, LPSTR lpszDst)
{
        while (*lpszSrc && !isspace(*lpszSrc))
                *lpszDst++ = *lpszSrc++;

        *lpszDst = '\0';
        return lpszSrc;
}


ANSIAPI.H

/*
 * ansiapi.h
 * This file contains prototypes of ANSI version of OLE apis
 * and mapping fooA to foo
 */

#ifndef _ANSIAPI_INCL
#define _ANSIAPI_INCL

#define UNICODEOLE32

#ifdef WIN32S
#if !defined(UNICODEOLE32)
#pragma message("Compiling for 32bit ANSI Ole!\n");
#endif
#endif

#define WASANSI() {                                                           \
                   OutputDebugString("\n\r\t\tUnicode string was ANSI!\r\n"); \
                   Assert(0);                                                 \
                   _asm { int 3 }                                             \
                  }

#ifndef UNICODEOLE32

#define W2A(w, a, cb)     lstrcpy (a, w)
#define A2W(a, w, cb)     lstrcpy (w, a)

#define FREELOCALSTRING(p)
#define MAKE_STR_LOCAL_COPYW2A(s, l)  l = s
#define MAKE_STR_LOCAL_COPYA2W(s, l)  l = s

#define OLESTRCPY                lstrcpy
#define OLESTRCAT                lstrcat
#define OLESTRLEN                lstrlen
#define OLESTRCMP                lstrcmp
#define OLESTRCMPI               lstrcmpi

#define DeleteFile_AW            DeleteFile
#define _lopen_AW                _lopen
#define _lcreat_AW               _lcreat
#define GlobalAddAtom_AW         GlobalAddAtom
#define GlobalGetAtomName_AW     GlobalGetAtomName
#define RegOpenKey_AW            RegOpenKey

#define CoLoadLibraryA   CoLoadLibrary

#define StringFromCLSID2A    StringFromCLSID2
#define StringFromIID2A      StringFromIID2
#define StringFromGUID2A     StringFromGUID2
#define CLSIDFromProgIDA     CLSIDFromProgID
#define CLSIDFromStringA     CLSIDFromString
#define ProgIDFromCLSIDA     ProgIDFromCLSID
#define StringFromCLSIDA     StringFromCLSID

#define UtDupStringA2W       UtDupString

// Storage APIs
#define StgOpenStorageA      StgOpenStorage

// IPersistFile ansi translation

#define IPersistFile_LoadA(pf, file, mode) (pf)->Load(file, mode)

// IMoniker ansi translation

#define IMoniker_GetDisplayNameA(pm, p1, p2, p3) (pm)->GetDisplayName(p1,p2,p3)

#define CreateFileMonikerA   CreateFileMoniker

#else  // UNICODEOLE32

#define W2A(w, a, cb)     WideCharToMultiByte(                              \
                                               CP_ACP,                      \
                                               0,                           \
                                               w,                           \
                                               -1,                          \
                                               a,                           \
                                               cb,                          \
                                               NULL,                        \
                                               NULL)

#define A2W(a, w, cb)     MultiByteToWideChar(                              \
                                               CP_ACP,                      \
                                               0,                           \
                                               a,                           \
                                               -1,                          \
                                               w,                           \
                                               cb)

#define FREELOCALSTRING(p)      delete (p)
#define MAKE_STR_LOCAL_COPYW2A(s, l)  {                                   \
                                      l = UtDupStringW2A(s);              \
                                      if (!l) {                           \
                                         return ResultFromScode(S_OOM);   \
                                      }                                   \
                                   }

#define MAKE_STR_LOCAL_COPYA2W(s, l)  {                                   \
                                      l = UtDupStringA2W(s);              \
                                      if (!l) {                           \
                                         return ResultFromScode(S_OOM);   \
                                      }                                   \
                                   }

#define OLESTRCPY                lstrcpyW
#define OLESTRCAT                lstrcatW
#define OLESTRLEN                lstrlenW
#define OLESTRCMP                lstrcmpW
#define OLESTRCMPI               lstrcmpiW

#define DeleteFile_AW            DeleteFileW
#define _lopen_AW                _lopenW
#define _lcreat_AW               _lcreatW
#define GlobalAddAtom_AW         GlobalAddAtomW
#define GlobalGetAtomName_AW     GlobalGetAtomNameW
#define RegOpenKey_AW            RegOpenKeyW

STDAPI_(HINSTANCE) CoLoadLibraryA(LPSTR lpszLibName, BOOL bAutoFree);

#define StringFromCLSID2A(rclsid, lpsz, cbMax) \
    StringFromGUID2A(rclsid, lpsz, cbMax)

#define StringFromIID2A(riid, lpsz, cbMax) \
    StringFromGUID2A(riid, lpsz, cbMax)

STDAPI_(int)  StringFromGUID2A(REFGUID rguid, LPSTR lpsz, int cbMax);
STDAPI        CLSIDFromProgIDA(LPCSTR szProgID, LPCLSID pclsid);
STDAPI        CLSIDFromStringA(LPSTR lpsz, LPCLSID lpclsid);
STDAPI        StringFromCLSIDA(REFCLSID rclsid, LPSTR FAR* lplpsz);
STDAPI        ProgIDFromCLSIDA (REFCLSID clsid, LPSTR FAR* lplpszProgID);

LPWSTR UtDupStringA2W(LPCSTR pSrc);
LPSTR  UtDupStringW2A(LPCWSTR pSrc);

// Storage APIs
STDAPI StgOpenStorageA(LPCSTR pwcsName,IStorage FAR *pstgPriority, DWORD grfMode, SNB snbExclude, DWORD reserved, IStorage FAR * FAR *ppstgOpen);

// IPersistFile ansi translation

HRESULT IPersistFile_LoadA(LPPERSISTFILE pIPF, LPSTR szFile, DWORD dwMode);

// IMoniker ansi translation

HRESULT IMoniker_GetDisplayNameA(LPMONIKER pm, LPBC p1, LPMONIKER p2, LPSTR FAR *p3);

//OLEAPI CreateFileMonikerA ( LPSTR lpszPathName, LPMONIKER FAR * ppmk );

#endif // !UNICODEOLE32

#endif //  _ANSIAPI_INCL


REGDB.C

/*
 *  REGDB.C
 *
 *  Functions to query the registration database
 *
 *  OleStdGetMiscStatusOfClass
 *  OleStdGetDefaultFileFormatOfClass
 *  OleStdGetAuxUserType
 *  OleStdGetUserTypeOfClass
 *
 *    (c) Copyright Microsoft Corp. 1992-1996 All Rights Reserved
 *
 */

#define STRICT  1
#include "olestd.h"
#include "common.h"
#include <ctype.h>

OLEDBGDATA

// Replacement for stdlib atol,
// which didn't work and doesn't take far pointers.
// Must be tolerant of leading spaces.
//
//
static LONG Atol(LPSTR lpsz)
{
   signed int sign = +1;
   UINT base = 10;
   LONG l = 0;

   if (NULL==lpsz)
   {
      OleDbgAssert (0);
      return 0;
   }
   while (isspace(*lpsz))
      lpsz++;

   if (*lpsz=='-')
   {
      lpsz++;
      sign = -1;
   }
   if (lpsz[0]=='0' && lpsz[1]=='x')
   {
      base = 16;
      lpsz+=2;
   }

   if (base==10)
   {
      while (isdigit(*lpsz))
      {
         l = l * base + *lpsz - '0';
         lpsz++;
      }
   }
   else
   {
      OleDbgAssert (base==16);
      while (isxdigit(*lpsz))
      {
         l = l * base + isdigit(*lpsz) ? *lpsz - '0' : toupper(*lpsz) - 'A' + 10;
         lpsz++;
      }
   }
   return l * sign;
}

/*
 * OleStdGetUserTypeOfClass(REFCLSID, LPSTR, UINT, HKEY)
 *
 * Purpose:
 *  Returns the user type (human readable class name) of the specified class.
 *
 * Parameters:
 *  rclsid          pointer to the clsid to retrieve user type of.
 *  lpszUserType    pointer to buffer to return user type in.
 *  cch             length of buffer pointed to by lpszUserType
 *  hKey            hKey for reg db - if this is NULL, then we
 *                   open and close the reg db within this function.  If it
 *                   is non-NULL, then we assume it's a valid key to the
 *                   \ root and use it without closing it. (useful
 *                   if you're doing lots of reg db stuff).
 *
 * Return Value:
 *  UINT            Number of characters in returned string.  0 on error.
 *
 */
STDAPI_(UINT) OleStdGetUserTypeOfClass(REFCLSID rclsid, LPOLESTR lpszUserType, UINT cch, HKEY hKey)
{

   LONG     dw;
   LONG     lRet;
   LPOLESTR lpszCLSID, lpszProgID;
   char     lpszAnsiCLSID[256], lpszAnsiProgID[256];
   BOOL     fFreeProgID = FALSE;
   BOOL     bCloseRegDB = FALSE;
   char     szKey[128];
   LPMALLOC lpIMalloc;
   char     lpszAnsiUserType[256];

   if (!lpszUserType)
      return 0;

   *lpszUserType = '\0';
   if (hKey == NULL)
   {

    //Open up the root key.
    lRet=RegOpenKey(HKEY_CLASSES_ROOT, NULL, &hKey);

    if ((LONG)ERROR_SUCCESS!=lRet)
      return (UINT)FALSE;

    bCloseRegDB = TRUE;
   }

   // Get a string containing the class name
   StringFromCLSID(rclsid, &lpszCLSID);

   W2A (lpszCLSID, lpszAnsiCLSID, 256);
   wsprintf(szKey, "CLSID\\%s", lpszAnsiCLSID);

   dw=cch;
   lRet = RegQueryValue(hKey, szKey, lpszAnsiUserType, &dw);
   A2W (lpszAnsiUserType, lpszUserType, cch);

   if ((LONG)ERROR_SUCCESS!=lRet) {
      // Load 'Unknown Source' and 'Unknown Type' strings
      dw = (LONG)LoadString(ghInst, (UINT)IDS_PSUNKNOWNTYPE, lpszAnsiUserType, cch);
      A2W (lpszAnsiUserType, lpszUserType, cch);
   }

   if ( ((LONG)ERROR_SUCCESS!=lRet) && (CoIsOle1Class(rclsid)) )
   {
     // We've got an OLE 1.0 class, so let's try to get the user type
     // name from the ProgID entry.

     ProgIDFromCLSID(rclsid, &lpszProgID);
     fFreeProgID = TRUE;

     dw = cch;
     W2A (lpszProgID, lpszAnsiProgID, 256);
     lRet = RegQueryValue(hKey, lpszAnsiProgID, lpszAnsiUserType, &dw);
     A2W (lpszAnsiUserType, lpszUserType, cch);

     if ((LONG)ERROR_SUCCESS != lRet)
      dw = 0;
   }

   if (NOERROR == CoGetMalloc(MEMCTX_TASK, &lpIMalloc))
   {
      if (fFreeProgID)
       lpIMalloc->lpVtbl->Free(lpIMalloc, (LPVOID)lpszProgID);

      lpIMalloc->lpVtbl->Free(lpIMalloc, (LPVOID)lpszCLSID);
      lpIMalloc->lpVtbl->Release(lpIMalloc);
   }

   if (bCloseRegDB)
     RegCloseKey(hKey);

   return (UINT)dw;

}

/*
 * OleStdGetAuxUserType(RCLSID, WORD, LPSTR, int, HKEY)
 *
 * Purpose:
 *  Returns the specified AuxUserType from the reg db.
 *
 * Parameters:
 *  rclsid          pointer to the clsid to retrieve aux user type of.
 *  hKey            hKey for reg db - if this is NULL, then we
 *                   open and close the reg db within this function.  If it
 *                   is non-NULL, then we assume it's a valid key to the
 *                   \ root and use it without closing it. (useful
 *                   if you're doing lots of reg db stuff).
 *  wAuxUserType    which aux user type field to look for.  In 4/93 release
 *                  2 is short name and 3 is exe name.
 *  lpszUserType    pointer to buffer to return user type in.
 *  cch             length of buffer pointed to by lpszUserType
 *
 * Return Value:
 *  UINT            Number of characters in returned string.  0 on error.
 *
 */
STDAPI_(UINT) OleStdGetAuxUserType(REFCLSID rclsid,
                           WORD     wAuxUserType,
                           LPOLESTR lpszAuxUserType,
                           int      cch,
                           HKEY     hKey)
{
   HKEY     hThisKey;
   BOOL     fCloseRegDB = FALSE;
   LONG     dw;
   LRESULT  lRet;
   LPOLESTR lpszCLSID;
   char     lpszAnsiCLSID[256];
   char     lpszAnsiAuxUserType[256];
   LPMALLOC lpIMalloc;
   char     szKey[OLEUI_CCHKEYMAX];
   char     szTemp[32];

   lpszAnsiAuxUserType[0] = '\0';

   if (NULL == hKey)
   {
     lRet = RegOpenKey(HKEY_CLASSES_ROOT, NULL, &hThisKey);

     if (ERROR_SUCCESS != lRet)
        return 0;
   }
   else
     hThisKey = hKey;

   StringFromCLSID(rclsid, &lpszCLSID);
   W2A (lpszCLSID, lpszAnsiCLSID, 256);

   lstrcpy(szKey, "CLSID\\");
   lstrcat(szKey, lpszAnsiCLSID);
   wsprintf(szTemp, "\\AuxUserType\\%d", wAuxUserType);
   lstrcat(szKey, szTemp);

   dw = cch;

   lRet = RegQueryValue(hThisKey, szKey, lpszAnsiAuxUserType, &dw);

   if (ERROR_SUCCESS != lRet) {
    dw = 0;
    lpszAuxUserType[0] = '\0';
   }

   if (fCloseRegDB)
     RegCloseKey(hThisKey);

   if (NOERROR == CoGetMalloc(MEMCTX_TASK, &lpIMalloc))
   {
      lpIMalloc->lpVtbl->Free(lpIMalloc, (LPVOID)lpszCLSID);
      lpIMalloc->lpVtbl->Release(lpIMalloc);
   }

   A2W (lpszAnsiAuxUserType, lpszAuxUserType, cch);
   return (UINT)dw;
}

/*
 * OleStdGetMiscStatusOfClass(REFCLSID, HKEY)
 *
 * Purpose:
 *  Returns the value of the misc status for the given clsid.
 *
 * Parameters:
 *  rclsid          pointer to the clsid to retrieve user type of.
 *  hKey            hKey for reg db - if this is NULL, then we
 *                   open and close the reg db within this function.  If it
 *                   is non-NULL, then we assume it's a valid key to the
 *                   \\CLSID root and use it without closing it. (useful
 *                   if you're doing lots of reg db stuff).
 *
 * Return Value:
 *  BOOL            TRUE on success, FALSE on failure.
 *
 */
STDAPI_(BOOL) OleStdGetMiscStatusOfClass(REFCLSID rclsid, HKEY hKey, DWORD FAR * lpdwValue)
{
   DWORD dw;
   LONG  lRet;
   LPOLESTR lpszCLSID;
   char     lpszAnsiCLSID[256];
   char  szKey[64];
   char  szMiscStatus[OLEUI_CCHKEYMAX];
   BOOL  bCloseRegDB = FALSE;

   if (hKey == NULL)
   {

    //Open up the root key.
    lRet=RegOpenKey(HKEY_CLASSES_ROOT, "CLSID", &hKey);

    if ((LONG)ERROR_SUCCESS!=lRet)
      return FALSE;

    bCloseRegDB = TRUE;
   }

   // Get a string containing the class name
   StringFromCLSID(rclsid, &lpszCLSID);
   W2A (lpszCLSID, lpszAnsiCLSID, 256);

   // Construct key
   lstrcpy(szKey, lpszAnsiCLSID);
   lstrcat(szKey, "\\MiscStatus");

   dw=OLEUI_CCHKEYMAX;
   lRet = RegQueryValue(hKey, szKey, /*(LPSTR)*/szMiscStatus, &dw);

   if ((LONG)ERROR_SUCCESS!=lRet)
   {
      OleStdFreeString(lpszCLSID, NULL);

      if (bCloseRegDB)
        RegCloseKey(hKey);

      return FALSE;

   }

   *lpdwValue = Atol(/*(LPSTR)*/szMiscStatus);

   OleStdFreeString(lpszCLSID, NULL);

   if (bCloseRegDB)
     RegCloseKey(hKey);

   return TRUE;

}

/*
 * CLIPFORMAT OleStdGetDefaultFileFormatOfClass(REFCLSID, HKEY)
 *
 * Purpose:
 *  Returns the default file format of the specified class.
 *  this is entered in REGDB as follows:
 *      CLSID\{...}\DataFormats\DefaultFile = <cfFmt>
 *
 * Parameters:
 *  rclsid          pointer to the clsid to retrieve user type of.
 *  hKey            hKey for reg db- if this is NULL, then we
 *                   open and close the reg db within this function.  If it
 *                   is non-NULL, then we assume it's a valid key to the
 *                   \ root and use it without closing it. (useful
 *                   if you're doing lots of reg db stuff).
 *
 * Return Value:
 *  cfFmt   -- DefaultFile format
 *  NULL    -- failed to get default file format
 *
 */
STDAPI_(CLIPFORMAT) OleStdGetDefaultFileFormatOfClass(
      REFCLSID        rclsid,
      HKEY            hKey
)
{
   CLIPFORMAT cfFmt = 0;
   DWORD dw;
   LONG  lRet;
   LPOLESTR lpszCLSID;
   char     lpszAnsiCLSID[256];
   BOOL  bCloseRegDB = FALSE;
   char  szKey[128];
   char  szDefaultFile[OLEUI_CCHKEYMAX];
   BOOL  bStatus = TRUE;

   if (hKey == NULL)
   {

    //Open up the root key.
    lRet=RegOpenKey(HKEY_CLASSES_ROOT, NULL, &hKey);

    if ((LONG)ERROR_SUCCESS!=lRet)
      return 0;

    bCloseRegDB = TRUE;
   }

   // Get a string containing the class name
   StringFromCLSID(rclsid, &lpszCLSID);
   W2A (lpszCLSID, lpszAnsiCLSID, 256);

   // Construct key
   wsprintf(szKey, "CLSID\\%s\\DataFormats\\DefaultFile", lpszCLSID);
   OleStdFreeString(lpszCLSID, NULL);

   dw=OLEUI_CCHKEYMAX;
   lRet = RegQueryValue(hKey, szKey, /*(LPSTR)*/szDefaultFile, (LONG FAR *)&dw);

   if ((LONG)ERROR_SUCCESS!=lRet)
      bStatus = FALSE;
   else {
      /* if the format is a number, then it should refer to one of the
      **    standard Windows formats.
      */
      if (isdigit(szDefaultFile[0]))
         cfFmt = (CLIPFORMAT)Atol(szDefaultFile);
      else
         cfFmt = RegisterClipboardFormat(szDefaultFile);
   }

   if (bCloseRegDB)
     RegCloseKey(hKey);

   return cfFmt;
}


STDPAL.C

/*-----------------------------------------------------------------------
|   stdpal.c
|
|   Standard App Palette useful for OLE applications.
|   Use OleStdCreateStandardPalette API to create the palette.
|
|   Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
|
-----------------------------------------------------------------------*/

#ifndef PC_RESERVED
#include <windows.h>
#endif

#include <olestd.h>

#include "stdpal.h"

#define cpeAppPal 256  // number of colors in our apps palette
typedef struct
   {
   WORD wVersion;
   WORD cpe;
   PALETTEENTRY rgpe[cpeAppPal];
   } LOGPAL;

/*-----------------------------------------------------------------------
|   OleStdCreateStandardPalette
|
|       Creates the standard Apps palette.  Create one of these for your
|   app, and select/realize it into each DC.
|
|   Arguments:
|       void:
|
|   Returns:
|
|   Keywords:
-----------------------------------------------------------------------*/
STDAPI_(HPALETTE) OleStdCreateStandardPalette(void)
   {
   HDC hdc;
   HPALETTE hpal;

   hpal = (HPALETTE) NULL;
   hdc = GetDC(NULL);
   if (hdc != NULL && GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE)
      {
      int cpeSysPal;
      int cpeReserved;

      cpeSysPal = GetDeviceCaps(hdc, SIZEPALETTE);
      cpeReserved = GetDeviceCaps(hdc, NUMRESERVED);
      if (cpeSysPal > cpeReserved)
         {
         int cpeReserved2;
         unsigned char FAR* lpb;
         PALETTEENTRY FAR* ppe;
         PALETTEENTRY FAR* ppeMac;
         LOGPAL logpal;

         cpeReserved2 = cpeReserved/2;

         // Get the system palette entries at the beginning and end.
         GetSystemPaletteEntries(hdc, 0, cpeReserved2, logpal.rgpe);
         GetSystemPaletteEntries(hdc, cpeSysPal - cpeReserved2, cpeReserved2,
            &logpal.rgpe[cpeAppPal-cpeReserved2]);

         logpal.cpe = cpeAppPal;
         logpal.wVersion = 0x300;

         lpb = (BYTE FAR *) &palSVGA[10];
         ppe = (PALETTEENTRY FAR*)&logpal.rgpe[cpeReserved2];
         ppeMac = (PALETTEENTRY FAR*)&logpal.rgpe[cpeAppPal-cpeReserved2];
         while (ppe < ppeMac)
            {
            ppe->peFlags = PC_NOCOLLAPSE;
            ppe->peRed   = *lpb++;
            ppe->peGreen = *lpb++;
            ppe->peBlue  = *lpb++;
            ppe++;
            }
         hpal = CreatePalette((LOGPALETTE FAR *)&logpal);
         }
      }
   ReleaseDC(NULL, hdc);
   return hpal;
   }


STDPAL.H

/*-----------------------------------------------------------------------
|   stdpal.h
|
|   Standard App Palette useful for OLE applications.  v 1.01
|
|   #include this file in the same file as HpalCreateAppPalette
|
|   NOTE:  Palette MUST be created with HpalCreateAppPalette
|
|   Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
|
-----------------------------------------------------------------------*/

#ifndef CSCONST
#if (defined(FLAT) || defined(__TURBOC__))
#define CSCONST(type) type const
#else
#define CSCONST(type) type _based(_segname("_CODE")) const
#endif
#endif

CSCONST(unsigned char) palSVGA[256][3] =
{
// R     G     B
{0x00, 0x00, 0x00}, // 0 Sys Black  gray 0
{0x80, 0x00, 0x00}, // 1 Sys Dk Red
{0x00, 0x80, 0x00}, // 2 Sys Dk Green
{0x80, 0x80, 0x00}, // 3 Sys Dk Yellow
{0x00, 0x00, 0x80}, // 4 Sys Dk Blue
{0x80, 0x00, 0x80}, // 5 Sys Dk Violet
{0x00, 0x80, 0x80}, // 6 Sys Dk Cyan
{0xc0, 0xc0, 0xc0}, // 7 Sys Lt Grey    gray 192
{0xc0, 0xdc, 0xc0}, // 8 Sys 8
{0xa6, 0xca, 0xf0}, // 9 Sys 9 (the first 10 are fixed by Windows)

{0x80, 0x00, 0x00}, // 10 Sys Dk Red repeat
{0x00, 0x80, 0x00}, // 11 Sys Dk Green repeat
{0x80, 0x80, 0x00}, // 12 Sys Dk Yellow repeat
{0x00, 0x00, 0x80}, // 13 Sys Dk Blue repeat
{0x80, 0x00, 0x80}, // 14 Sys Dk Violet repeat
{0x00, 0x80, 0x80}, // 15 Sys Dk Cyan repeat
{0x80, 0x80, 0x80}, // 16 Sys Dk Grey repeat    gray 128
{0x80, 0x80, 0xff}, // 17 Excel Chart Fill 1
{0x80, 0x20, 0x60}, // 18 Excel Chart Fill 2
{0xff, 0xff, 0xc0}, // 19 Excel Chart Fill 3
{0xa0, 0xe0, 0xe0}, // 20 Excel Chart Fill 4
{0x60, 0x00, 0x80}, // 21 Excel Chart Fill 4
{0xff, 0x80, 0x80}, // 22 Excel Chart Fill 6
{0x00, 0x80, 0xc0}, // 23 Excel Chart Fill 7
{0xc0, 0xc0, 0xff}, // 24 Excel Chart Fill 8
{0x00, 0xcf, 0xff}, // 25 Excel clrt entry
{0x69, 0xff, 0xff}, // 26 Excel clrt entry
{0xe0, 0xff, 0xe0}, // 27 Excel clrt entry
{0xdd, 0x9c, 0xb3}, // 28 Excel clrt entry
{0xb3, 0x8f, 0xee}, // 29 Excel clrt entry
{0x2a, 0x6f, 0xf9}, // 30 Excel clrt entry
{0x3f, 0xb8, 0xcd}, // 31 Excel clrt entry
{0x48, 0x84, 0x36}, // 32 Excel clrt entry
{0x95, 0x8c, 0x41}, // 33 Excel clrt entry
{0x8e, 0x5e, 0x42}, // 34 Excel clrt entry
{0xa0, 0x62, 0x7a}, // 35 Excel clrt entry
{0x62, 0x4f, 0xac}, // 36 Excel clrt entry
{0x1d, 0x2f, 0xbe}, // 37 Excel clrt entry
{0x28, 0x66, 0x76}, // 38 Excel clrt entry
{0x00, 0x45, 0x00}, // 39 Excel clrt entry
{0x45, 0x3e, 0x01}, // 40 Excel clrt entry
{0x6a, 0x28, 0x13}, // 41 Excel clrt entry
{0x85, 0x39, 0x6a}, // 42 Excel clrt entry
{0x4a, 0x32, 0x85}, // 43 Excel clrt entry
{0x04, 0x04, 0x04}, // 44   gray 4
{0x08, 0x08, 0x08}, // 45   gray 8
{0x0c, 0x0c, 0x0c}, // 46   gray 12
{0x11, 0x11, 0x11}, // 47   gray 17
{0x16, 0x16, 0x16}, // 48   gray 22
{0x1c, 0x1c, 0x1c}, // 49   gray 28
{0x22, 0x22, 0x22}, // 50   gray 34
{0x29, 0x29, 0x29}, // 51   gray 41
{0x30, 0x30, 0x30}, // 52   gray 48
{0x5f, 0x5f, 0x5f}, // 53 swapped so inversions look good   gray 95
{0x55, 0x55, 0x55}, // 54 swapped so inversions look good   gray 85
{0x4d, 0x4d, 0x4d}, // 55 swapped so inversions look good   gray 77
{0x42, 0x42, 0x42}, // 56 swapped so inversions look good   gray 66
{0x39, 0x39, 0x39}, // 57 swapped so inversions look good   gray 57
{0x00, 0x07, 0x00}, // 58
{0x0d, 0x00, 0x00}, // 59
{0xb7, 0x99, 0x81}, // 60
{0x84, 0x99, 0xb4}, // 61
{0xbd, 0xbd, 0x90}, // 62
{0x7f, 0x7f, 0x60}, // 63
{0x60, 0x60, 0x7f}, // 64
{0x00, 0x0e, 0x00}, // 65
{0x1b, 0x00, 0x00}, // 66
{0x28, 0x00, 0x00}, // 67
{0x08, 0x09, 0x2b}, // 68
{0x00, 0x1d, 0x00}, // 69
{0x39, 0x00, 0x00}, // 70
{0x00, 0x00, 0x9b}, // 71
{0x00, 0x25, 0x00}, // 72
{0x49, 0x00, 0x00}, // 73
{0x11, 0x11, 0x3b}, // 74
{0x00, 0x2f, 0x00}, // 75
{0x5d, 0x00, 0x00}, // 76
{0x17, 0x17, 0x45}, // 77
{0x00, 0x3a, 0x00}, // 78
{0x49, 0x11, 0x11}, // 79
{0x1c, 0x1c, 0x53}, // 80
{0x00, 0x16, 0xff}, // 81
{0x2b, 0x00, 0xff}, // 82
{0x21, 0x21, 0x6c}, // 83
{0x59, 0x14, 0x14}, // 84
{0x00, 0x51, 0x00}, // 85
{0x47, 0x1a, 0x6a}, // 86
{0x19, 0x32, 0x67}, // 87
{0x00, 0x61, 0x00}, // 88
{0x00, 0x31, 0xff}, // 89
{0x61, 0x00, 0xff}, // 90
{0x53, 0x20, 0x7b}, // 91
{0x16, 0x43, 0x67}, // 92
{0x2e, 0x2e, 0xe2}, // 93
{0x26, 0x59, 0x16}, // 94
{0x51, 0x46, 0x04}, // 95
{0x68, 0x2e, 0x49}, // 96
{0x07, 0x52, 0x8f}, // 97
{0x6a, 0x18, 0xb8}, // 98
{0x90, 0x23, 0x15}, // 99
{0x00, 0x53, 0xff}, // 100
{0xa3, 0x00, 0xff}, // 101
{0x6a, 0x4a, 0x12}, // 102
{0x75, 0x33, 0x6c}, // 103
{0x4a, 0x41, 0x9a}, // 104
{0x37, 0x65, 0x0b}, // 105
{0xa4, 0x2c, 0x15}, // 106
{0x83, 0x1f, 0xb1}, // 107
{0x4e, 0x2c, 0xff}, // 108
{0x20, 0x51, 0xb6}, // 109
{0x08, 0x64, 0x92}, // 110
{0x6f, 0x56, 0x0b}, // 111
{0x59, 0x43, 0xad}, // 112
{0x36, 0x72, 0x12}, // 113
{0xb0, 0x33, 0x17}, // 114
{0x00, 0xa1, 0x00}, // 115
{0x77, 0x5f, 0x1f}, // 116
{0x89, 0x47, 0x71}, // 117
{0xb0, 0x43, 0x1c}, // 118
{0xb7, 0x2d, 0x7d}, // 119
{0x00, 0x86, 0x95}, // 120
{0x7a, 0x6e, 0x23}, // 121
{0x26, 0x9f, 0x00}, // 122
{0x73, 0xa9, 0x01}, // 123
{0x00, 0x00, 0x00}, // 124 free 0   gray 0
{0x00, 0x00, 0x00}, // 125 free 2   gray 0
{0x00, 0x00, 0x00}, // 126 free 4   gray 0
{0x00, 0x00, 0x00}, // 127 free 6   gray 0
{0x00, 0x00, 0x00}, // 128 free 7   gray 0
{0x00, 0x00, 0x00}, // 129 free 5   gray 0
{0x00, 0x00, 0x00}, // 130 free 3   gray 0
{0x00, 0x00, 0x00}, // 131 free 1   gray 0
{0x00, 0xca, 0x00}, // 132
{0xac, 0x5b, 0x01}, // 133
{0x20, 0x1d, 0xc2}, // 134
{0x94, 0x52, 0x70}, // 135
{0x24, 0xaa, 0x4c}, // 136
{0x0a, 0x94, 0x89}, // 137
{0x36, 0x6e, 0x7b}, // 138
{0x44, 0x75, 0x90}, // 139
{0xff, 0x00, 0xa8}, // 140
{0x00, 0x71, 0xff}, // 141
{0xdf, 0x00, 0xff}, // 142
{0x56, 0x91, 0x4a}, // 143
{0x34, 0x48, 0xf8}, // 144
{0xcc, 0x32, 0x82}, // 145
{0xe4, 0x41, 0x70}, // 146
{0x68, 0xca, 0x01}, // 147
{0x36, 0xbc, 0x42}, // 148
{0x00, 0x9a, 0xff}, // 149
{0x96, 0x22, 0xb7}, // 150
{0x85, 0x7d, 0x33}, // 151
{0x25, 0xb7, 0x8c}, // 152
{0x36, 0x5a, 0xed}, // 153
{0x5c, 0xff, 0x00}, // 154
{0xff, 0x48, 0x00}, // 155
{0x22, 0x9b, 0xa2}, // 156
{0x42, 0xcf, 0x4d}, // 157
{0xc2, 0x58, 0x52}, // 158
{0x20, 0xd3, 0x95}, // 159
{0xa5, 0x24, 0xe0}, // 160
{0x73, 0x56, 0xb5}, // 161
{0xa9, 0xa9, 0x00}, // 162
{0xd0, 0x6f, 0x3c}, // 163
{0x67, 0x9f, 0x58}, // 164
{0x89, 0xcf, 0x0b}, // 165
{0xff, 0xac, 0x00}, // 166
{0xa7, 0x2e, 0xfe}, // 167
{0xe2, 0x59, 0x7f}, // 168
{0x4c, 0xdc, 0x67}, // 169
{0xff, 0x18, 0xff}, // 170
{0x3a, 0x7d, 0xff}, // 171
{0xb1, 0xd0, 0x18}, // 172
{0xc7, 0xff, 0x00}, // 173
{0xff, 0xe2, 0x00}, // 174
{0xdf, 0x9a, 0x3d}, // 175
{0x56, 0x81, 0x9f}, // 176
{0xc6, 0x43, 0xba}, // 177
{0xaf, 0x71, 0x8b}, // 178
{0x38, 0xa2, 0xc9}, // 179
{0xd1, 0x53, 0xce}, // 180
{0xff, 0x9a, 0x65}, // 181
{0x46, 0xca, 0xdb}, // 182
{0xff, 0x4d, 0xff}, // 183
{0xc8, 0xe9, 0x6a}, // 184
{0x4c, 0xde, 0xe0}, // 185
{0xff, 0x98, 0xff}, // 186
{0xdf, 0xc0, 0x82}, // 187
{0xe9, 0xec, 0xa5}, // 188
{0xf5, 0xf6, 0xcd}, // 189
{0xff, 0xd0, 0xff}, // 190
{0xb1, 0xac, 0x5a}, // 191
{0x63, 0x91, 0xae}, // 192
{0x22, 0x4c, 0x65}, // 193
{0x8d, 0x4e, 0x3f}, // 194
{0x50, 0x70, 0x70}, // 195
{0xd0, 0xff, 0xff}, // 196
{0xff, 0xe7, 0xff}, // 197
{0x69, 0x69, 0x69}, // 198  gray 105
{0x77, 0x77, 0x77}, // 199  gray 119
{0x86, 0x86, 0x86}, // 200  gray 134
{0x96, 0x96, 0x96}, // 201  gray 150
{0x9d, 0x9d, 0x9d}, // 202  gray 157
{0xa4, 0xa4, 0xa4}, // 203  gray 164
{0xb2, 0xb2, 0xb2}, // 204  gray 178
{0xcb, 0xcb, 0xcb}, // 205  gray 203
{0xd7, 0xd7, 0xd7}, // 206  gray 215
{0xdd, 0xdd, 0xdd}, // 207  gray 221
{0xe3, 0xe3, 0xe3}, // 208  gray 227
{0xea, 0xea, 0xea}, // 209  gray 234
{0xf1, 0xf1, 0xf1}, // 210  gray 241
{0xf8, 0xf8, 0xf8}, // 211  gray 248
{0xb2, 0xc1, 0x66}, // 212
{0x80, 0xbf, 0x78}, // 213
{0xc6, 0xf0, 0xf0}, // 214
{0xb2, 0xa4, 0xff}, // 215
{0xff, 0xb3, 0xff}, // 216
{0xd1, 0x8e, 0xa3}, // 217
{0xc3, 0xdc, 0x37}, // 218
{0xa0, 0x9e, 0x54}, // 219
{0x76, 0xae, 0x70}, // 220
{0x78, 0x9e, 0xc1}, // 221
{0x83, 0x64, 0xbf}, // 222
{0xa4, 0x83, 0xd3}, // 223
{0xd1, 0x3f, 0x32}, // 224
{0xff, 0x7d, 0x00}, // 225
{0x44, 0x78, 0x23}, // 226
{0x24, 0x5f, 0x60}, // 227
{0x0e, 0x0e, 0x2c}, // 228
{0xbe, 0x00, 0x00}, // 229
{0xff, 0x1f, 0x00}, // 230
{0x31, 0x39, 0x00}, // 231
{0xd9, 0x85, 0x3e}, // 232
{0x02, 0x77, 0x85}, // 233
{0xb0, 0xd8, 0x81}, // 234
{0x56, 0x21, 0x1d}, // 235
{0x00, 0x00, 0x30}, // 236
{0x88, 0xc8, 0xb3}, // 237
{0xa0, 0x79, 0x00}, // 238
{0xc0, 0xc0, 0xc0}, // 239 Sys Dk Grey repeat inversion gray 192
{0xea, 0x70, 0x81}, // 240
{0x51, 0xf1, 0x69}, // 241
{0xff, 0xff, 0x80}, // 242
{0x91, 0x74, 0xcd}, // 243
{0xff, 0x7c, 0xff}, // 244
{0xa2, 0xff, 0xff}, // 245

{0xff, 0xfb, 0xf0}, // 246 Sys Reserved
{0xa0, 0xa0, 0xa4}, // 247 Sys Reserved
{0x80, 0x80, 0x80}, // 248 Sys Lt Gray  gray 128
{0xff, 0x00, 0x00}, // 249 Sys Red
{0x00, 0xff, 0x00}, // 250 Sys Green
{0xff, 0xff, 0x00}, // 251 Sys Yellow
{0x00, 0x00, 0xff}, // 252 Sys Blue
{0xff, 0x00, 0xff}, // 253 Sys Violet
{0x00, 0xff, 0xff}, // 254 Sys Cyan
{0xff, 0xff, 0xff} // 255 Sys White gray 255
};


SUMINFO.CPP

/*************************************************************************
**
**    OLE 2.0 Property Set Utilities
**
**    suminfo.cpp
**
**    This file contains functions that are useful for the manipulation
**    of OLE 2.0 Property Sets particularly to manage the Summary Info
**    property set.
**
**    (c) Copyright Microsoft Corp. 1990 - 1996 All Rights Reserved
**
*************************************************************************/

// Note: this file is designed to be stand-alone; it includes a
// carefully chosen, minimal set of headers.
//
// For conditional compilation we use the ole2 conventions,
//    _MAC      = mac
//    WIN32     = Win32 (NT really)
//    <nothing> = defaults to Win16

// REVIEW: the following needs to modified to handle _MAC
#define STRICT

#include <string.h>
#include <windows.h>
#include <ole2.h>
#include "ansiapi.h"

#ifdef WIN32
#define _fmemset memset
#define _fmemcpy memcpy
#define _fmemcmp memcmp
#define _fstrcpy strcpy
#define _fstrlen strlen
#endif

/* A LPSUMINFO variable is a pointer to an instance of an abstract data
**    type.  There can be an arbitrary number of SummaryInfo streams open
**    simultaneously (subject to available memory); each must have its
**    own LPSUMINFO instance. Each LPSUMINFO instance must
**    be initialized prior to use by calling Init and freed after its
**    last use by calling Free.  The param argument to Init is reserved
**    for future expansion and should be zero initially. Once a LPSUMINFO
**    instance is allocated (by Init), the user can call the Set
**    procedures to initialize fields.  A copy of the arguments is made
**    in every case except SetThumbnail where control of the storage
**    occupied by the METAFILEPICT is merely transferred.  When the
**    Free routine is called, all storage will be deallocated including
**    that of the thumbnail.  The arguments to SetThumbNail and the
**    return values from GetThumbNail correspond to the OLE2.0 spec.
**    Note that on input, the thumbnail is read on demand but all the
**    other properties are pre-loaded.  The thumbnail is manipulated as
**    a windows handle to a METAFILEPICT structure, which in turn
**    contains a handle to the METAFILE.  The transferClip argument on
**    GetThumbNail, when set to true, transfers responsibility for
**    storage management of the thumbnail to the caller; that is, after
**    Free has been called, the handle is still valid. Clear can be
**    used to free storage for all the properties but then you must
**    call Read to load them again.  All the code is based on FAR
**    pointers.
**    CoInitialize MUST be called PRIOR to calling OleStdInitSummaryInfo.
**    Memory is allocated using the currently active IMalloc*
**    allocator (as is returned by call CoGetMalloc(MEMCTX_TASK) ).
**
** Common scenarios:
**    Read SummaryInfo
**    ----------------
**      OleStdInitSummaryInfo()
**      OleStdReadSummaryInfo()
**      . . . . .
**      call different Get routines
**      . . . . .
**      OleStdFreeSummaryInfo()
**
**    Create SummaryInfo
**    ------------------
**      OleStdInitSummaryInfo()
**      call different Set routines
**      OleStdWriteSummaryInfo()
**      OleStdFreeSummaryInfo()
**
**    Update SummaryInfo
**    ------------------
**      OleStdInitSummaryInfo()
**      OleStdReadSummaryInfo()
**      OleStdGetThumbNailProperty(necessary only if no SetThumb)
**      call different Set routines
**      OleStdWriteSummaryInfo()
**      OleStdFreeSummaryInfo()
*/

#define CHAR unsigned char
#define fTrue 1
#define fFalse 0
#define BYTE unsigned char
#define WORD unsigned short
#define DWORD unsigned long
#define LPVOID void FAR *
#define uchar unsigned char
#define ulong unsigned long
#define BOOL unsigned char
#define BF unsigned int

#include "suminfo.h"
#include "wn_dos.h"

#if defined( _DEBUG )
// following is from compobj.dll (ole2)
#define ASSERT(x) (!(x) ? FnAssert(#x, NULL, __FILE__, __LINE__) : 0)
#else
#define ASSERT(x)
#endif

typedef struct _RSUMINFO
   {
   WORD byteOrder;
   WORD formatVersion;
   WORD getOSVersion;
   WORD osVersion;
   CLSID classId;  //from compobj.h
   DWORD cSections;
   PROPSETLIST rgPropSet[1/*cSections*/]; //one section in standard summary info
   STANDARDSECINMEM section;
   ULONG fileOffset;   //offset for thumbnail to support demand read
   } RSUMINFO;

typedef RSUMINFO FAR * LPRSI;

   typedef union _foo{
      ULARGE_INTEGER uli;
      struct {
         DWORD       dw;
         DWORD       dwh;
         };
      struct {
         WORD    w0;
         WORD    w1;
         WORD    w2;
         WORD    w3;
         };
      } Foo;

/* MemAlloc
** ---------
**    allocate memory using the currently active IMalloc* allocator
*/
static LPVOID MemAlloc(ULONG ulSize)
{
   LPVOID pout;
   LPMALLOC pmalloc;

   if (CoGetMalloc(MEMCTX_TASK, &pmalloc) != NOERROR) {
      ASSERT(pmalloc);
      return NULL;
   }

   pout = (LPVOID)pmalloc->Alloc(ulSize);

   if (pmalloc != NULL) {
      ULONG refs = pmalloc->Release();
   }

   return pout;
}

/* MemFree
** -------
**    free memory using the currently active IMalloc* allocator
*/
static void MemFree(LPVOID pmem)
{
   LPMALLOC pmalloc;

   if (pmem == NULL)
      return;

   if (CoGetMalloc(MEMCTX_TASK, &pmalloc) != NOERROR) {
      ASSERT(pmalloc);
      return;
   }

   pmalloc->Free(pmem);

   if (pmalloc != NULL) {
      ULONG refs = pmalloc->Release();
   }
}

// Replace the first argument with the product of itself and the multiplier
static void ulargeMultiply(ULARGE_INTEGER FAR *ul, USHORT m)
{
   Foo out, in;
   in.uli = *ul;
   out.dw = (ULONG)m * in.w0;      in.w0 = out.w0;
   out.dw = (ULONG)m * in.w1 + out.w1; in.w1 = out.w0;
   out.dw = (ULONG)m * in.w2 + out.w1; in.w2 = out.w0;
   out.dw = (ULONG)m * in.w3 + out.w1; in.w3 = out.w0;
   *ul = in.uli;
}

// Replace the first argument with the product of itself and the multiplier
static void ulargeDivide(ULARGE_INTEGER FAR *ul, USHORT m)
{
   Foo out, in;
   DWORD i;
   in.uli = *ul;
   out.dwh = in.dwh/(ULONG)m;
   i = in.dwh%(ULONG)m;
   in.w2 = in.w1;
   in.w3 = (WORD)i;
   out.w1 = (WORD)(in.dwh/(ULONG)m);
   in.w1 = (WORD)(in.dwh%(ULONG)m);
   out.w0 = (WORD)(in.dw/(ULONG)m);
   *ul = out.uli;
}

static void setStandard(LPRSI lprsi)
{
   int i;
   lprsi->cSections = 1;
   SetSumInfFMTID(&lprsi->rgPropSet[0].formatID);
   _fmemcpy(&lprsi->classId, &lprsi->rgPropSet[0].formatID, sizeof(FMTID));
   lprsi->rgPropSet[0].byteOffset = cbNewSummaryInfo(1);
   for (i=0; i<cPIDSI_STANDARD; i++)
      lprsi->section.rgPropId[i].propertyID = PIDSI_TITLE+i;
   lprsi->section.cProperties = cPIDSI_STANDARD; //always; do null test to check validity
}

extern "C" {

/*************************************************************************
**
** OleStdInitSummaryInfo
**
** Purpose:
**    Initialize a Summary Info structure.
**
** Parameters:
**    int reserved             - reserverd for future use. must be 0.
**
** Return Value:
**    LPSUMINFO
**
** Comments:
**    CoInitialize MUST be called PRIOR to calling OleStdInitSummaryInfo.
**    Memory is allocated using the currently active IMalloc*
**    allocator (as is returned by call CoGetMalloc(MEMCTX_TASK) ).
**    Each LPSUMINFO instance must be initialized prior to use by
**    calling OleStdInitSummaryInfo. Once a LPSUMINFO instance is allocated
**    (by OleStdInitSummaryInfo), the user can call the Set procedures to
**    initialize fields.
*************************************************************************/

STDAPI_(LPSUMINFO) OleStdInitSummaryInfo(int reserved)
{
   LPRSI lprsi;

   if ((lprsi = (LPRSI)MemAlloc(sizeof(RSUMINFO))) != NULL)
   {
      ClearSumInf(lprsi, sizeof(RSUMINFO));
   } else return NULL;

   setStandard(lprsi);
   return (LPSUMINFO)lprsi;
}

/*************************************************************************
**
** OleStdFreeSummaryInfo
**
** Purpose:
**    Free a Summary Info structure.
**
** Parameters:
**    LPSUMINFO FAR *lp           - pointer to open Summary Info struct
**
** Return Value:
**    void
**
** Comments:
**    Memory is freed using the currently active IMalloc*
**    allocator (as is returned by call CoGetMalloc(MEMCTX_TASK) ).
**    Every LPSUMINFO struct must be freed after its last use.
**    When the OleStdFreeSummaryInfo routine is called, all storage will be
**    deallocated including that of the thumbnail (unless ownership of
**    the thumbnail has been transfered to the caller -- see
**    description of transferClip in GetThumbnail API).
**
*************************************************************************/

STDAPI_(void) OleStdFreeSummaryInfo(LPSUMINFO FAR *lplp)
{
   if (lplp==NULL||*lplp==NULL) return;
   OleStdClearSummaryInfo(*lplp);
   MemFree(*lplp);
   *lplp = NULL;
}

/*************************************************************************
**
** OleStdClearSummaryInfo
**
** Purpose:
**    Free storage (memory) for all the properties of the LPSUMINFO.
**
** Parameters:
**    LPSUMINFO FAR *lp             - pointer to open Summary Info struct
**
** Return Value:
**    void
**
** Comments:
**    After calling OleStdClearSummaryInfo you must call OleStdReadSummaryInfo to
**    load them again.
**
*************************************************************************/

STDAPI_(void) OleStdClearSummaryInfo(LPSUMINFO lp)
{
   OleStdSetStringProperty(lp, PIDSI_TITLE, NULL);
   OleStdSetStringProperty(lp, PIDSI_SUBJECT, NULL);
   OleStdSetStringProperty(lp, PIDSI_AUTHOR, NULL);
   OleStdSetStringProperty(lp, PIDSI_KEYWORDS, NULL);
   OleStdSetStringProperty(lp, PIDSI_COMMENTS, NULL);
   OleStdSetStringProperty(lp, PIDSI_TEMPLATE, NULL);
   OleStdSetStringProperty(lp, PIDSI_REVNUMBER, NULL);
   OleStdSetStringProperty(lp, PIDSI_APPNAME, NULL);
   OleStdSetThumbNailProperty(NULL, lp, VT_CF_EMPTY, 0, NULL, NULL, 0);
   ClearSumInf((LPRSI)lp, sizeof(RSUMINFO));
}

/*************************************************************************
**
** OleStdReadSummaryInfo
**
** Purpose:
**    Read all Summary Info properties into memory (except thumbnail
**    which is demand loaded).
**
** Parameters:
**    LPSTREAM lps                  - open SummaryInfo IStream*
**    LPSUMINFO FAR *lp             - pointer to open Summary Info struct
**
** Return Value:
**    int                           - 1 for success
**                                  - 0 if error occurs
** Comments:
**
*************************************************************************/
/*
STDAPI_(int) OleStdReadSummaryInfo(LPSTREAM lpStream, LPSUMINFO lp)
{
   STANDARDSECINMEM FAR *lpSSIM=(STANDARDSECINMEM FAR*)&((LPRSI)lp)->section;
   LPRSI lpSumInfo = (LPRSI)lp;
   SCODE sc;
   ULONG cbRead,i,sectionOffset;
   LARGE_INTEGER a;
   ULARGE_INTEGER b;
   int j,k,l;
   union {
      RSUMINFO rsi;
      STDZ stdz;
   };
   OleStdClearSummaryInfo(lp);
   LISet32(a, 0);
   sc = GetScode(lpStream->Seek(a, STREAM_SEEK_SET, &b));
   if (FAILED(sc)) goto fail;
   sectionOffset = cbNewSummaryInfo(1);
   sc = GetScode(lpStream->Read(&rsi, sectionOffset, &cbRead));
   if (FAILED(sc)||cbRead<sectionOffset) goto fail;
   if (!FIntelOrder(&rsi)||rsi.formatVersion!=0) goto fail;
   j = (int)rsi.cSections;
   while (j-->0) {
      if (FEqSumInfFMTID(&rsi.rgPropSet[0].formatID)) {
         sectionOffset = rsi.rgPropSet[0].byteOffset;
         break;
      } else {
         sc = GetScode(lpStream->Read(&rsi.rgPropSet[0].formatID, sizeof(PROPSETLIST), &cbRead));
         if (FAILED(sc)||cbRead!=sizeof(PROPSETLIST)) goto fail;
      }
      if (j<=0) goto fail;
   }

   LISet32(a, sectionOffset);
   sc = GetScode(lpStream->Seek(a, STREAM_SEEK_SET, &b));
   if (FAILED(sc)) goto fail;
   sc = GetScode(lpStream->Read(&rsi.section, cbNewSection(1), &cbRead));
   if (FAILED(sc)||cbRead!=cbNewSection(1)) goto fail;
   i = rsi.section.cBytes+sectionOffset;
   j = (int)rsi.section.cProperties;
   if (j>cPIDSI_STANDARD) goto fail;
   k = 0;
   while (j-->0) {
      k++;
      switch (l=(int)rsi.section.rgPropId[0].propertyID) {
         case PIDSI_PAGECOUNT:
         case PIDSI_WORDCOUNT:
         case PIDSI_CHARCOUNT:
         case PIDSI_DOC_SECURITY:
            if (l==PIDSI_DOC_SECURITY) l=3; else l-=PIDSI_PAGECOUNT;
            cbRead = sectionOffset+rsi.section.rgPropId[0].byteOffset;
            if (cbRead>=i) goto fail;
            LISet32(a, cbRead);
            sc = GetScode(lpStream->Seek(a, STREAM_SEEK_SET, &b));
            if (FAILED(sc)) goto fail;
            sc = GetScode(lpStream->Read(&lpSSIM->rgInts[l], sizeof(INTS), &cbRead));
            if (FAILED(sc)||cbRead!=sizeof(INTS)) goto fail;
            if (lpSSIM->rgInts[l].vtType==VT_EMPTY) break;
            if (lpSSIM->rgInts[l].vtType!=VT_I4) goto fail;
            break;
         case PIDSI_EDITTIME:
         case PIDSI_LASTPRINTED:
         case PIDSI_CREATE_DTM:
         case PIDSI_LASTSAVE_DTM:
            l-=PIDSI_EDITTIME;
            cbRead = sectionOffset+rsi.section.rgPropId[0].byteOffset;
            if (cbRead>=i) goto fail;
            LISet32(a, cbRead);
            sc = GetScode(lpStream->Seek(a, STREAM_SEEK_SET, &b));
            if (FAILED(sc)) goto fail;
            sc = GetScode(lpStream->Read(&lpSSIM->rgTime[l], sizeof(TIME), &cbRead));
            if (FAILED(sc)||cbRead!=sizeof(TIME)) goto fail;
            if (lpSSIM->rgTime[l].vtType==VT_EMPTY) break;
            if (lpSSIM->rgTime[l].vtType!=VT_FILETIME) goto fail;
            break;
         case PIDSI_TITLE:
         case PIDSI_SUBJECT:
         case PIDSI_AUTHOR:
         case PIDSI_KEYWORDS:
         case PIDSI_COMMENTS:
         case PIDSI_TEMPLATE:
         case PIDSI_LASTAUTHOR:
         case PIDSI_REVNUMBER:
         case PIDSI_APPNAME:
            cbRead = sectionOffset+rsi.section.rgPropId[0].byteOffset;
            if (cbRead>=i) goto fail;
            LISet32(a, cbRead);
            sc = GetScode(lpStream->Seek(a, STREAM_SEEK_SET, &b));
            if (FAILED(sc)) goto fail;
            sc = GetScode(lpStream->Read(&stdz, sizeof(STDZ), &cbRead));
            if (FAILED(sc)||cbRead<sizeof(DWORD)*2) goto fail;
            if (stdz.vtType==VT_EMPTY||stdz.vtByteCount<=1) break;
            if (stdz.vtType!=VT_LPSTR||stdz.vtByteCount>WORDMAX) goto fail;
            stdz.rgchars[(int)stdz.vtByteCount-1] = '\0';
            OleStdSetStringProperty(lp, (DWORD)l, (LPSTR)&stdz.rgchars[0]);
            break;
         case PIDSI_THUMBNAIL:
            cbRead = sectionOffset+rsi.section.rgPropId[0].byteOffset;
            if (cbRead>=i) goto fail;
            LISet32(a, cbRead);
            sc = GetScode(lpStream->Seek(a, STREAM_SEEK_SET, &b));
            if (FAILED(sc)) goto fail;
            lpSumInfo->fileOffset = cbRead+sizeof(DWORD)*3;
            sc = GetScode(lpStream->Read(&lpSSIM->thumb, sizeof(DWORD)*4, &cbRead));
            if (FAILED(sc)||cbRead!=sizeof(DWORD)*4) {
               lpSSIM->thumb.vtType = VT_EMPTY;
               goto fail;
            }
            if (lpSSIM->thumb.vtType == VT_EMPTY) {
               lpSSIM->thumb.cBytes = 0;
               break;
            }
            if (lpSSIM->thumb.vtType != VT_CF) {
               lpSSIM->thumb.vtType = VT_EMPTY;
               goto fail;
            }
            lpSSIM->thumb.cBytes -= sizeof(DWORD); //for selector
            if (lpSSIM->thumb.selector==VT_CF_WIN||lpSSIM->thumb.selector==VT_CF_MAC) {
               lpSumInfo->fileOffset += sizeof(DWORD);
               lpSSIM->thumb.cBytes -= sizeof(DWORD); //for format val
            }
            break;
            default: ;
      }
      if (j<=0)
      {
      // We should fail if the document is password-protected.
         if(OleStdGetSecurityProperty(lp)==fSecurityPassworded)
            goto fail;
         return 1;
      }
      LISet32(a, sectionOffset+sizeof(DWORD)*2+k*sizeof(PROPIDLIST));
      sc = GetScode(lpStream->Seek(a, STREAM_SEEK_SET, &b));
      if (FAILED(sc)) goto fail;
      sc = GetScode(lpStream->Read(&rsi.section.rgPropId[0], sizeof(PROPIDLIST), &cbRead));
      if (FAILED(sc)||cbRead!=sizeof(PROPIDLIST)) goto fail;
   }

fail:
   OleStdClearSummaryInfo(lpSumInfo);
   return 0;
}
*/

/*************************************************************************
**
** OleStdWriteSummaryInfo
**
** Purpose:
**    Write all Summary Info properties to a IStream*
**
** Parameters:
**    LPSTREAM lps                  - open SummaryInfo IStream*
**    LPSUMINFO FAR *lp             - pointer to open Summary Info struct
**
** Return Value:
**    int                           - 1 for success
**                                  - 0 if error occurs
** Comments:
**
*************************************************************************/

STDAPI_(int) OleStdWriteSummaryInfo(LPSTREAM lpStream, LPSUMINFO lp)
{
   STANDARDSECINMEM FAR *lpSSIM=(STANDARDSECINMEM FAR*)&((LPRSI)lp)->section;
   // REVIEW: localization issues for propert sets
   //         do we need to include a code page and dictionary?

   LPRSI lpSumInfo = (LPRSI)lp;
   SCODE sc;
   ULONG cbWritten;
   ULONG cBytes, oBytes, k,l,m,n;
   LARGE_INTEGER a;
   ULARGE_INTEGER b;
   LPOLESTR lps;
   LPMETAFILEPICT lpmfp;
   int i,j,s;

   setStandard(lpSumInfo);
   oBytes = cbNewSection(cPIDSI_STANDARD);  //offsets are relative to the section
   cBytes = cbNewSection(cPIDSI_STANDARD)+(sizeof(TIME)*MAXTIME)+(sizeof(INTS)*MAXINTS);

   lpSSIM->rgPropId[PIDSI_EDITTIME-2].byteOffset = oBytes;
   lpSSIM->rgPropId[PIDSI_LASTPRINTED-2].byteOffset = oBytes+sizeof(TIME);
   lpSSIM->rgPropId[PIDSI_CREATE_DTM-2].byteOffset = oBytes+sizeof(TIME)*2;
   lpSSIM->rgPropId[PIDSI_LASTSAVE_DTM-2].byteOffset = oBytes+sizeof(TIME)*3;

   lpSSIM->rgPropId[PIDSI_PAGECOUNT-2].byteOffset = oBytes+(sizeof(TIME)*MAXTIME);
   lpSSIM->rgPropId[PIDSI_WORDCOUNT-2].byteOffset = oBytes+(sizeof(TIME)*MAXTIME+sizeof(INTS));
   lpSSIM->rgPropId[PIDSI_CHARCOUNT-2].byteOffset = oBytes+(sizeof(TIME)*MAXTIME+sizeof(INTS)*2);
   lpSSIM->rgPropId[PIDSI_DOC_SECURITY-2].byteOffset = oBytes+(sizeof(TIME)*MAXTIME+sizeof(INTS)*3);
   oBytes += sizeof(TIME)*MAXTIME + sizeof(INTS)*MAXINTS;

   lpSSIM->rgPropId[PIDSI_THUMBNAIL-2].byteOffset = oBytes;
   l = 0;
   if (lpSSIM->thumb.vtType==VT_EMPTY) k = sizeof(DWORD);
   else {
      l = ((lpSSIM->thumb.cBytes+4-1)>>2)<<2;
      if (lpSSIM->thumb.selector==VT_CF_BYTES) k = sizeof(DWORD)*3;
      else if (lpSSIM->thumb.selector==VT_CF_FMTID) {k = sizeof(DWORD)*3; l += sizeof(FMTID); }
      else if (lpSSIM->thumb.selector==VT_CF_NAME)  {k = sizeof(DWORD)*3; l += (((*lpSSIM->thumb.lpstzName+1+3)>>2)<<2);}
      else k = sizeof(DWORD)*4;
   }
   cBytes += k+l;
   oBytes += k+l;

   for (i=0; i<MAXSTDZ; i++) {
      j = 0;
      if (lpSSIM->rglpsz[i]!=NULL) {
         j = lpSSIM->rglpsz[i]->VTCB+1/*null*/;
         lpSSIM->rglpsz[i]->vtByteCount = j;
         j = (((j+4-1)>>2)<<2)+sizeof(DWORD);
         cBytes += j;
      }
      if (i!=MAXSTDZ-1) lpSSIM->rgPropId[i].byteOffset = oBytes;
      else lpSSIM->rgPropId[PIDSI_APPNAME-2].byteOffset = oBytes;
      oBytes += j+sizeof(DWORD);
      cBytes += sizeof(DWORD); //type
   }
   lpSSIM->cBytes = cBytes;

   LISet32(a, 0);
   sc = GetScode(lpStream->Seek(a, STREAM_SEEK_SET, &b));
   if (FAILED(sc)) return 0;
   sc = GetScode(lpStream->Write(lpSumInfo, cbNewSummaryInfo(1), &cbWritten));
   if (FAILED(sc)||cbWritten!=cbNewSummaryInfo(1)) return 0;
   sc = GetScode(lpStream->Write(lpSSIM, cbNewSection(cPIDSI_STANDARD)+sizeof(TIME)*MAXTIME+sizeof(INTS)*MAXINTS, &cbWritten));
   if (FAILED(sc)||cbWritten!=cbNewSection(cPIDSI_STANDARD)+sizeof(TIME)*MAXTIME+sizeof(INTS)*MAXINTS) return 0;

   m = lpSSIM->thumb.cBytes;
   if (lpSSIM->thumb.lpstzName!=NULL) s = *lpSSIM->thumb.lpstzName;
   else s = 0;
   if (m!=0) {
      lpSSIM->thumb.cBytes = (k-sizeof(DWORD)*2)+
         (((lpSSIM->thumb.cBytes+4-1)>>2)<<2)+(((s+4-1)>>2)<<2);
      n = lpSSIM->thumb.selector;
      lps = lpSSIM->thumb.lpByte;
      ASSERT(lps!=NULL);  //maybe a GetThumbNail here
      ASSERT(n!=VT_CF_NAME);
      if (n==VT_CF_WIN) { //bytes are in global memory
         lpmfp = (LPMETAFILEPICT)GlobalLock((HANDLE)(DWORD)lps);
         if (lpmfp==NULL) goto fail;
         lps = (LPOLESTR)GlobalLock(lpmfp->hMF);
      }
      if (n==VT_CF_NAME) lpSSIM->thumb.selector = *lpSSIM->thumb.lpstzName+1/*null*/;
   }
   sc = GetScode(lpStream->Write(&lpSSIM->thumb, k, &cbWritten));
   if (FAILED(sc)||cbWritten!=k) goto fail;
   if (s!=0) {
      k = ((s+1+4-1)>>2)<<2;
      sc = GetScode(lpStream->Write(lpSSIM->thumb.lpstzName+1, k, &cbWritten));
      if (FAILED(sc)||cbWritten!=k) goto fail;
   }
   if (m!=0) {
      k = ((m+3)>>2)<<2;
      if (n==VT_CF_WIN||VT_CF_NAME) { //bytes are in global memory
         sc = GetScode(lpStream->Write(lpmfp, sizeof(METAFILEPICT), &cbWritten));
         k -= sizeof(METAFILEPICT);
      }
      sc = GetScode(lpStream->Write(lps, k, &cbWritten));
      if (FAILED(sc)||cbWritten!=k) goto fail;
      if (n==VT_CF_WIN||VT_CF_NAME) { //bytes are in global memory
         GlobalUnlock(lpmfp->hMF);
         GlobalUnlock((HANDLE)(DWORD)lpSSIM->thumb.lpByte);
      }
   }
   lpSSIM->thumb.cBytes = m;   //restore in mem value
   lpSSIM->thumb.selector = n;

   k = VT_EMPTY;
   for (i=0; i<MAXSTDZ; i++) {
      if (lpSSIM->rglpsz[i]!=NULL) {
         l = lpSSIM->rglpsz[i]->vtByteCount;
         j = ((((int)l+4-1)/4)*4)+sizeof(DWORD)*2;
         sc = GetScode(lpStream->Write(lpSSIM->rglpsz[i], j, &cbWritten));
         if (FAILED(sc)||cbWritten!=(ULONG)j) return 0;
         lpSSIM->rglpsz[i]->vtByteCount = 0; //restore stz count convention
         lpSSIM->rglpsz[i]->VTCB = (int)l;
      } else {
         sc = GetScode(lpStream->Write(&k, sizeof(DWORD), &cbWritten));
         if (FAILED(sc)||cbWritten!=sizeof(DWORD)) return 0;
      }
   }
   return 1;
fail:
   lpSSIM->thumb.cBytes = m;   //restore in mem value
   lpSSIM->thumb.selector = n;
   if (m!=0&&(n==VT_CF_WIN||VT_CF_NAME)) { //bytes are in global memory
      GlobalUnlock((HANDLE)(DWORD)lps);
   }
   return 0;
}

/*************************************************************************
**
** OleStdGetSecurityProperty
**
** Purpose:
**    Retrieve the Security Property
**
** Parameters:
**    LPSUMINFO FAR *lp           - pointer to open Summary Info struct
**
** Return Value:
**    DWORD                       - security level
**          AllSecurityFlagsEqNone 0    - no security
**          fSecurityPassworded 1       - password required
**          fSecurityRORecommended 2    - read-only is recommended
**          fSecurityRO 4               - read-only is required
**          fSecurityLockedForAnnotations 8 - locked for annotations
**
** Comments:
**    by noting the (suggested; that is, application-enforced) security
**    level on the document, an application other than the originator
**    of the document can adjust its user interface to the properties
**    appropriately. An application should not display any of the
**    information about a password protected document, and should not
**    allow modifications to enforced read-only or locked for
**    annotations documents. It should warn the user about read-only
**    recommended if the user attempts to modify properties.
**
*************************************************************************/

STDAPI_(DWORD) OleStdGetSecurityProperty(LPSUMINFO lp)
{
STANDARDSECINMEM FAR *lpSSIM=(STANDARDSECINMEM FAR*)&((LPRSI)lp)->section;
   if (lpSSIM->rgInts[3].vtType == VT_I4) return lpSSIM->rgInts[3].value;
   return 0;
}

/*************************************************************************
**
** OleStdSetSecurityProperty
**
** Purpose:
**    Set the Security Property
**
** Parameters:
**    LPSUMINFO FAR *lp           - pointer to open Summary Info struct
**    DWORD security              - security level
**          AllSecurityFlagsEqNone 0    - no security
**          fSecurityPassworded 1       - password required
**          fSecurityRORecommended 2    - read-only is recommended
**          fSecurityRO 4               - read-only is required
**          fSecurityLockedForAnnotations 8 - locked for annotations
**
** Return Value:
**    int                           - 1 for success
**                                  - 0 if error occurs
**                                    (there are no errors)
**
** Comments:
**    by noting the (suggested; that is, application-enforced) security
**    level on the document, an application other than the originator
**    of the document can adjust its user interface to the properties
**    appropriately. An application should not display any of the
**    information about a password protected document, and should not
**    allow modifications to enforced read-only or locked for
**    annotations documents. It should warn the user about read-only
**    recommended if the user attempts to modify properties.
**
*************************************************************************/

STDAPI_(int) OleStdSetSecurityProperty(LPSUMINFO lp, DWORD security)
{
   STANDARDSECINMEM FAR *lpSSIM=(STANDARDSECINMEM FAR*)&((LPRSI)lp)->section;

   // REVIEW: check valid transitions; how do we know APP called us?

   if (security==0) {
      lpSSIM->rgInts[3].vtType = VT_EMPTY;
      return 1;
   }
   lpSSIM->rgInts[3].vtType = VT_I4;
   lpSSIM->rgInts[3].value = security;
   return 1;
}

/*************************************************************************
**
** OleStdGetStringProperty
**
** Purpose:
**    Retrieve a String Propety.
**    (returns zero terminated string -- C string)
**
** Parameters:
**    LPSUMINFO FAR *lp             - pointer to open Summary Info struct
**    DWORD pid                     - ID of String Property
**
** Return Value:
**    LPSTR                         - value of String Property
**                                      (zero terminated string--C string)
**
** Comments:
**    String should NOT be freed by caller. Memory for string will be
**    freed when OleStdFreeSummaryInfo is called.
*************************************************************************/

STDAPI_(LPSTR) OleStdGetStringProperty(LPSUMINFO lp, DWORD pid)
{
   LPSTR l = OleStdGetStringZProperty(lp,pid);
   if (l==NULL) return NULL; else return l+1;
}

/*************************************************************************
**
** OleStdSetStringProperty
**
** Purpose:
**    Set a String Propety
**    (takes zero terminated string -- C string)
**
** Parameters:
**    LPSUMINFO FAR *lp             - pointer to open Summary Info struct
**    DWORD pid                     - ID of String Property
**    LPSTR lpsz                    - new value for String Property.
**                                      zero terminated string -- C string.
**                                      May be NULL, in which case the
**                                      propery is cleared.
**
** Return Value:
**    int                           - 1 if successful
**                                  - 0 invalid property id
**
** Comments:
**    The input string is copied.
**
*************************************************************************/

STDAPI_(int) OleStdSetStringProperty(LPSUMINFO lp, DWORD pid, LPOLESTR lpsz)
{
   LPRSI lprsi=(LPRSI)lp;
   STANDARDSECINMEM FAR* lpSSIM=(STANDARDSECINMEM FAR*)&((LPRSI)lp)->section;
   int i;
   if (pid==PIDSI_APPNAME) {
      pid = MAXSTDZ-1;
   } else if (pid<PIDSI_TITLE || pid>PIDSI_REVNUMBER) return 0; else pid -= 2;
   ASSERT(lpSSIM);
   if (lpSSIM->rglpsz[pid]) MemFree(lpSSIM->rglpsz[pid]);
   if ((lpsz==NULL)||(*lpsz==0)) {
      lpSSIM->rglpsz[pid] = NULL;
      return (1);
   }
   i = OLESTRLEN(lpsz);
   lpSSIM->rglpsz[pid] = (STDZ FAR*)MemAlloc(i+1/*null*/+sizeof(DWORD)*2);
   if (lpSSIM->rglpsz[pid]==NULL) return 0;
   W2A(lpsz, (LPSTR)&lpSSIM->rglpsz[pid]->rgchars, sizeof(&lpSSIM->rglpsz[pid]->rgchars));
   lpSSIM->rglpsz[pid]->vtType = VT_LPSTR;
   lpSSIM->rglpsz[pid]->vtByteCount = 0;
   lpSSIM->rglpsz[pid]->VTCB = i;
   return (1);
}

/*************************************************************************
**
** OleStdGetStringZProperty
**
** Purpose:
**    Retrieve a String Propety.
**    (returns zero-terminated with leading byte count string)
**
** Parameters:
**    LPSUMINFO FAR *lp             - pointer to open Summary Info struct
**    DWORD pid                     - ID of Property
**
** Return Value:
**    LPSTZR                        - value of String Property
**                                      (zero-terminated with leading
**                                      byte count)

**
** Comments:
**    String should NOT be freed by caller. Memory for string will be
**    freed when OleStdFreeSummaryInfo is called.
*************************************************************************/

STDAPI_(LPSTZR) OleStdGetStringZProperty(LPSUMINFO lp, DWORD pid)
{
   STANDARDSECINMEM FAR *lpSSIM=(STANDARDSECINMEM FAR*)&((LPRSI)lp)->section;
   if (pid==PIDSI_APPNAME) {
      pid = MAXSTDZ-1;
   } else if (pid<PIDSI_TITLE || pid>PIDSI_REVNUMBER) return NULL; else pid -= 2;
   if (lpSSIM->rglpsz[pid]!=NULL) {
      return (LPSTR)&lpSSIM->rglpsz[pid]->VTCB;
   }
   return NULL;
}

/*************************************************************************
**
** OleStdGetDocProperty
**
** Purpose:
**    Retrieve document properties (no. pages, no. words, no. characters)
**
** Parameters:
**    LPSUMINFO FAR *lp             - pointer to open Summary Info struct
**    DWORD FAR *nPage              - (OUT) number of pages in document
**    DWORD FAR *nWords             - (OUT) number of words in document
**    DWORD FAR *nChars             - (OUT) number of charactrs in doc
**
** Return Value:
**    void
**
** Comments:
**
*************************************************************************/

STDAPI_(void) OleStdGetDocProperty(
      LPSUMINFO       lp,
      DWORD FAR*      nPage,
      DWORD FAR*      nWords,
      DWORD FAR*      nChars
)
{
STANDARDSECINMEM FAR *lpSSIM=(STANDARDSECINMEM FAR*)&((LPRSI)lp)->section;
   *nPage=0; *nWords=0; *nChars=0;
   if (lpSSIM->rgInts[0].vtType == VT_I4) *nPage = lpSSIM->rgInts[0].value;
   if (lpSSIM->rgInts[1].vtType == VT_I4) *nWords = lpSSIM->rgInts[1].value;
   if (lpSSIM->rgInts[2].vtType == VT_I4) *nChars = lpSSIM->rgInts[2].value;
}

/*************************************************************************
**
** OleStdSetDocProperty
**
** Purpose:
**    Set document properties (no. pages, no. words, no. characters)
**
** Parameters:
**    LPSUMINFO FAR *lp             - pointer to open Summary Info struct
**    DWORD nPage                   - number of pages in document
**    DWORD nWords                  - number of words in document
**    DWORD nChars                  - number of charactrs in doc
**
** Return Value:
**    int                           - 1 for success
**                                  - 0 if error occurs
**                                    (there are no errors)
**
** Comments:
**
*************************************************************************/

STDAPI_(int) OleStdSetDocProperty(
      LPSUMINFO       lp,
      DWORD           nPage,
      DWORD           nWords,
      DWORD           nChars
)
{
DWORD vttype=VT_I4;
STANDARDSECINMEM FAR *lpSSIM=(STANDARDSECINMEM FAR*)&((LPRSI)lp)->section;
   if ((nPage|nWords|nChars)==0) {
      vttype = VT_EMPTY;
      nPage=0; nWords=0; nChars=0;
   }
   lpSSIM->rgInts[0].vtType = vttype;
   lpSSIM->rgInts[1].vtType = vttype;
   lpSSIM->rgInts[2].vtType = vttype;
   lpSSIM->rgInts[0].value = nPage;
   lpSSIM->rgInts[1].value = nWords;
   lpSSIM->rgInts[2].value = nChars;
   return 1;
}

/*************************************************************************
**
** OleStdGetThumbNailProperty
**
** Purpose:
**    Retrieve a Thumbnail Property
**
** Parameters:
**    LPSTREAM lps
**    LPSUMINFO FAR *lp             - pointer to open Summary Info struct
**    DWORD FAR* clipFormatNo       - clipboard format for thumbnail
**                                      (type of value depends on vtcf
**                                      return value.)
**                                      NOTE: ONLY VT_CF_WIN is
**                                      implemented, so clipFormatNo
**                                      will be CF_METAFILEPICT
**    LPSTR FAR* lpszName           - format name if VT_CF_NAME is
**                                      returned
**                                      NOTE: NOT IMPLEMENTED
**    THUMBNAIL FAR* clip           - handle to thumbnail
**                                      for VT_CF_WIN clip will be
**                                      handle to MetafilePict
**                                      NOTE: only VT_CF_WIN IMPLEMENTED
**    DWORD FAR* byteCount          - size of thumbnail stream
**                                      for VT_CF_WIN case this should
**                                      be combined size of both the
**                                      Metafile as well as the
**                                      MetafilePict structure.
**    BOOL transferClip             - transfer ownership of thumbnail
**                                      to caller. (see comment)
**
** Return Value:
**    int vtcfNo                    - OLE thumbnail selector value
**      VT_CF_WIN                   - Windows thumbnail
**                                      (interpret clipFormatNo as
**                                      Windows clipboard format)
**      VT_CF_FMTID                 - (NOT IMPLEMENTED)
**                                      thumbnail format is specified
**                                      by ID. use clipFormatNo.
**                                      (but NOT a Windows format ID)
**
**      VT_CF_NAME                  - (NOT IMPLEMENTED)
**                                      thumbnail format is specified
**                                      by name. use lpszName.
**      VT_CF_EMPTY                 - blank thumbnail
**                                      (clip will be NULL)
**      VT_CF_OOM                   - Memory allocation failure
**
** Comments:
**    NOTE: Currently there is only proper support for VT_CF_WIN.
**    OleStdSetThumbNailProperty does implement VT_CF_FMTID and VT_CF_NAME,
**    however, OleStdGetThumbNailProperty, OleStdReadSummaryInfo and
**    OleStdWriteSummaryInfo only support VT_CF_WIN.
**
**    Note that on input, the thumbnail is read on demand while all the
**    other properties are pre-loaded.  The thumbnail is manipulated as
**    a windows handle to a METAFILEPICT structure, which in turn
**    contains a handle to the METAFILE.  The transferClip argument on
**    GetThumbNail, when set to true, transfers responsibility for
**    storage management of the thumbnail to the caller; that is, after
**    OleStdFreeSummaryInfo has been called, the handle is still valid.
*************************************************************************/

STDAPI_(int) OleStdGetThumbNailProperty(
      LPSTREAM        lps,
      LPSUMINFO       lp,
      DWORD FAR*      clipFormatNo,
      LPOLESTR FAR*   lpszName,
      THUMBNAIL FAR*  clip,
      DWORD FAR*      byteCount,
      BOOL            transferClip
)
{
   int i;
   LPRSI lprsi=(LPRSI)lp;
   STANDARDSECINMEM FAR *lpSSIM=(STANDARDSECINMEM FAR*)&((LPRSI)lp)->section;
   ULONG cbRead, cbToRead;
   LARGE_INTEGER a;
   ULARGE_INTEGER b;
   LPOLESTR lpst;
   LPMETAFILEPICT lpmfp;
   HANDLE hst, hmfp;
   SCODE sc;
   *byteCount = 0;
   if (lpSSIM->thumb.cBytes==0) return VT_CF_EMPTY;
   if (lpSSIM->thumb.lpByte==NULL) {
      LISet32(a, lprsi->fileOffset);
      sc = GetScode(lps->Seek(a, STREAM_SEEK_SET, &b));
      if (FAILED(sc)) return VT_CF_EMPTY;
      i = (int) lpSSIM->thumb.selector;
      if (i>0||i==VT_CF_FMTID) {
         if (i>255) return VT_CF_EMPTY;
         else if (i==VT_CF_FMTID) i = sizeof(FMTID);
         else lpSSIM->thumb.selector = (DWORD) VT_CF_NAME;
         cbToRead = ((i+3)>>2)<<2;
         lpSSIM->thumb.lpstzName=(LPOLESTR)MemAlloc(i+1/*n*/+1);
         if (lpSSIM->thumb.lpstzName==NULL) return VT_CF_OOM;
         sc = GetScode(lps->Read(lpSSIM->thumb.lpstzName+1, cbToRead, &cbRead));
         if (FAILED(sc)||cbRead!=cbToRead) return VT_CF_EMPTY;
         *lpSSIM->thumb.lpstzName = i;
         *(lpSSIM->thumb.lpstzName+i) = 0;
         lpSSIM->thumb.cBytes -= cbToRead+sizeof(DWORD);
      }
      i = (int) lpSSIM->thumb.selector;
      cbToRead = lpSSIM->thumb.cBytes;
      if (cbToRead>65535) return VT_CF_OOM;
      ASSERT(i!=VT_CF_NAME);
      if (i==VT_CF_WIN) {
         cbToRead -= sizeof(METAFILEPICT);
         hmfp = GlobalAlloc(GMEM_MOVEABLE, sizeof(METAFILEPICT));
         if (hmfp==NULL) return VT_CF_OOM;
         hst = GlobalAlloc(GMEM_MOVEABLE, cbToRead);
         if (hst==NULL) {
            GlobalFree(hmfp);
            return VT_CF_OOM;
         }
         lpmfp = (LPMETAFILEPICT)GlobalLock(hmfp);
         sc = GetScode(lps->Read(lpmfp, sizeof(METAFILEPICT), &cbRead));
         if (FAILED(sc)||cbRead!=sizeof(METAFILEPICT)) {
            GlobalUnlock(hmfp);
            GlobalFree(hmfp);
            GlobalFree(hst);
            return VT_CF_EMPTY;
         }
         lpst = (LPOLESTR)GlobalLock(hst);
         lpmfp->hMF = (HMETAFILE)hst;
         lpSSIM->thumb.lpByte = (LPOLESTR)hmfp;
      } else {
         lpst = (LPOLESTR)MemAlloc((int)cbToRead);
         if (lpst==NULL) return VT_CF_OOM;
         lpSSIM->thumb.lpByte = lpst;
      }
      sc = GetScode(lps->Read(lpst, cbToRead, &cbRead));
      if (i==VT_CF_WIN) {
         GlobalUnlock(hst);
         GlobalUnlock(hmfp);
      }
      if (FAILED(sc)||cbRead!=cbToRead) {
         if (i==VT_CF_WIN) {
            GlobalFree(hst);
            GlobalFree(hmfp);
         } else MemFree(lpst);
         lpSSIM->thumb.lpByte = NULL;
         if ((i==VT_CF_NAME||i==VT_CF_FMTID)&&(lpSSIM->thumb.lpstzName!=NULL))
            MemFree(lpSSIM->thumb.lpstzName);
         return VT_CF_EMPTY;
      }
   }
   *clipFormatNo = lpSSIM->thumb.clipFormat;
   *byteCount = lpSSIM->thumb.cBytes;
   if(lpszName!=NULL)
      *lpszName = (LPOLESTR)lpSSIM->thumb.lpstzName+1;
   *clip = (char FAR*)lpSSIM->thumb.lpByte;
   if (transferClip) lpSSIM->thumb.lpByte=NULL;
   return (int)lpSSIM->thumb.selector;
}

/*************************************************************************
**
** OleStdSetThumbNailProperty
**
** Purpose:
**    Set a Thumbnail Property
**
** Parameters:
**    LPSTREAM lps                  - open SummaryInfo IStream*
**    LPSUMINFO FAR *lp             - pointer to open Summary Info struct
**    int vtcfNo                    - OLE thumbnail selector value
**          VT_CF_WIN                   - Windows thumbnail
**                                        (interpret clipFormatNo as
**                                        Windows clipboard format)
**          VT_CF_FMTID                 - thumbnail format is specified
**                                        by ID. use clipFormatNo.
**                                        (but NOT a Windows format ID)
**
**          VT_CF_NAME                  - thumbnail format is specified
**                                        by name. use lpszName.
**          VT_CF_EMPTY                 - blank thumbnail
**                                        (clip will be NULL)
**
**    DWORD FAR* clipFormatNo       - clipboard format for thumbnail
**                                      used if vtcfNo is VT_CF_WIN or
**                                      VT_CF_FMTID. interpretation of
**                                      value depends on vtcfNo specified.
**                                      (normally vtcfNo==VT_CF_WIN and
**                                      clipFormatNo==CF_METAFILEPICT)
**    LPSTR FAR* lpszName           - format name if vtcfNo is VT_CF_NAME
**    THUMBNAIL clip                - handle to thumbnail
**                                      for VT_CF_WIN clip will be
**                                      handle to MetafilePict
**    DWORD FAR* byteCount          - size of thumbnail stream
**                                      for VT_CF_WIN case this should
**                                      be combined size of both the
**                                      Metafile as well as the
**                                      MetafilePict structure.
**
** Return Value:
**    int                           - 1 for success
**                                  - 0 if error occurs
**
** Comments:
**    NOTE: Currently there is only proper support for VT_CF_WIN.
**    OleStdSetThumbNailProperty does implement VT_CF_FMTID and VT_CF_NAME,
**    however, OleStdGetThumbNailProperty, OleStdReadSummaryInfo and
**    OleStdWriteSummaryInfo only support VT_CF_WIN.
**
**    This function copies lpszName but saves the "clip" handle passed.
**
**    NOTE: overwriting or emptying frees space for clip and name.
**    The thumbnail is manipulated as a windows handle to a
**    METAFILEPICT structure, which in turn contains a handle to the
**    METAFILE.
*************************************************************************/

STDAPI_(int) OleStdSetThumbNailProperty(
      LPSTREAM        lps,
      LPSUMINFO       lp,
      int             vtcfNo,
      DWORD           clipFormatNo,
      LPOLESTR        lpszName,
      THUMBNAIL       clip,
      DWORD           byteCount
)
{
   int i;
   LPRSI lprsi=(LPRSI)lp;
   STANDARDSECINMEM FAR *lpSSIM=(STANDARDSECINMEM FAR*)&((LPRSI)lp)->section;
   LPMETAFILEPICT lpmfp;
   if (lpSSIM==NULL||vtcfNo>0||vtcfNo<VT_CF_EMPTY||(vtcfNo==VT_CF_NAME&&(lpszName==NULL||*lpszName==0))) {
      return 0;
   }
   if (vtcfNo!=VT_CF_EMPTY&&(clip==0||byteCount==0)) return 0;
   i = (int) lpSSIM->thumb.vtType;
   if (i!=VT_EMPTY) {
      i = (int) lpSSIM->thumb.selector;
      ASSERT(i!=VT_CF_NAME);
      if (i==VT_CF_WIN) {
         if (lpSSIM->thumb.lpByte!=NULL) {
            lpmfp = (LPMETAFILEPICT)GlobalLock((HANDLE)(DWORD)lpSSIM->thumb.lpByte);
            GlobalFree(lpmfp->hMF);
            GlobalUnlock((HANDLE)(DWORD)lpSSIM->thumb.lpByte);
            GlobalFree((HANDLE)(DWORD)lpSSIM->thumb.lpByte);
         }
      } else {
         MemFree(lpSSIM->thumb.lpByte);
      }
      if ((i==VT_CF_NAME||i==VT_CF_FMTID)&&(lpSSIM->thumb.lpstzName!=NULL))
         MemFree(lpSSIM->thumb.lpstzName);
      lpSSIM->thumb.lpstzName = NULL;
      lpSSIM->thumb.lpByte = NULL;
   }
   if (vtcfNo==VT_CF_EMPTY) {
      lpSSIM->thumb.vtType = VT_EMPTY;
      lpSSIM->thumb.cBytes = 0;
   } else {
      lpSSIM->thumb.vtType = VT_CF;
      lpSSIM->thumb.selector = vtcfNo;
      lpSSIM->thumb.cBytes = byteCount;
      lpSSIM->thumb.clipFormat = clipFormatNo;
      lpSSIM->thumb.lpByte = (LPOLESTR)clip;    //just save the hnadle
      if (vtcfNo==VT_CF_NAME||vtcfNo==VT_CF_FMTID) {
         i = OLESTRLEN(lpszName);
         if (vtcfNo==VT_CF_FMTID) ASSERT(i==sizeof(FMTID));
         lpSSIM->thumb.lpstzName =
            (LPOLESTR)MemAlloc(i+1/*n*/+1/*null*/);
         if (lpSSIM->thumb.lpstzName==NULL) {
            lpSSIM->thumb.vtType = VT_EMPTY;
            return 0;
         }
         W2A (lpszName, (char FAR*)lpSSIM->thumb.lpstzName+1, sizeof(lpSSIM->thumb.lpstzName+1));
         *lpSSIM->thumb.lpstzName = i;
      }
   }
   return 1;
}

/*************************************************************************
**
** OleStdGetDateProperty
**
** Purpose:
**    Retrieve Data Property
**
** Parameters:
**    LPSUMINFO FAR *lp             - pointer to open Summary Info struct
**    DWORD pid                     - ID of Property
**    int FAR *yr                   - (OUT) year
**    int FAR *mo                   - (OUT) month
**    int FAR *dy                   - (OUT) day
**    DWORD FAR *sc                 - (OUT) seconds
**
** Return Value:
**    void
**
** Comments:
**
*************************************************************************/

STDAPI_(void) OleStdGetDateProperty(
      LPSUMINFO       lp,
      DWORD           pid,
      int FAR*        yr,
      int FAR*        mo,
      int FAR*        dy,
      DWORD FAR*      sc
)
{
   STANDARDSECINMEM FAR *lpSSIM=(STANDARDSECINMEM FAR*)&((LPRSI)lp)->section;
   SFFS sffs;
   pid -= PIDSI_EDITTIME;
   *yr = 0; *mo = 0; *dy = 0; *sc = 0;
   if (pid<0||pid>=MAXTIME) return;
   if (lpSSIM->rgTime[pid].vtType == VT_FILETIME) {
      if (pid==0) {
         //convert from 100ns to seconds
         ulargeDivide((ULARGE_INTEGER FAR*)&lpSSIM->rgTime[0].time, 10000);
         ulargeDivide((ULARGE_INTEGER FAR*)&lpSSIM->rgTime[0].time, 1000);
         pid = lpSSIM->rgTime[0].time.dwLowDateTime;
         *sc = pid%((DWORD)60*60*24);
         pid /= (DWORD)60*60*24;
         *dy = (int)(pid%(DWORD)30);
         pid /= (DWORD)30;
         *mo = (int)(pid%(DWORD)12);
         *yr = (int)(pid/(DWORD)12);
      } else {
         if (CoFileTimeToDosDateTime(&lpSSIM->rgTime[pid].time,
                  &sffs.dateVariable, &sffs.timeVariable)) {
            *yr = sffs.yr+1980;
            *mo = sffs.mon;
            *dy = sffs.dom;
            *sc = (DWORD)sffs.hr*3600+sffs.mint*60+sffs.sec*2;
         }
      }
   }
   return;
}

/*************************************************************************
**
** OleStdSetDateProperty
**
** Purpose:
**    Set Data Property
**
** Parameters:
**    LPSUMINFO FAR *lp             - pointer to open Summary Info struct
**    DWORD pid                     - ID of Property
**    int yr                        - year
**    int mo                        - month
**    int dy                        - day
**    DWORD sc                      - seconds
**
** Return Value:
**    int                           - 1 for success
**                                  - 0 if error occurs
**
** Comments:
**    Use all zeros to clear.
**    The following is an example of valid input:
**          yr=1993 mo=1(Jan) dy=1(1st) hr=12(noon) mn=30 sc=23
**    for PIDSI_EDITTIME property, the values are a zero-origin duration
**    of time.
**
*************************************************************************/

STDAPI_(int) OleStdSetDateProperty(
      LPSUMINFO       lp,
      DWORD           pid,
      int             yr,
      int             mo,
      int             dy,
      int             hr,
      int             mn,
      int             sc
)
{
   STANDARDSECINMEM FAR *lpSSIM=(STANDARDSECINMEM FAR*)&((LPRSI)lp)->section;
   SFFS sffs;
   pid -= PIDSI_EDITTIME;
   if (pid<0||pid>=MAXTIME) return 0;
   if ((yr|mo|dy|hr|mn|sc)==0) {   //all must be zero
      lpSSIM->rgTime[pid].vtType = VT_EMPTY;
      return 1;
   }
   lpSSIM->rgTime[pid].vtType = VT_FILETIME;
   if (pid==0) {
      lpSSIM->rgTime[0].time.dwLowDateTime =
            (((((DWORD)yr*365+mo*30)+dy)*24+hr)*60+mn)*60+sc;
      lpSSIM->rgTime[0].time.dwHighDateTime = 0;
      //10^7 nanoseconds/second
      ulargeMultiply((ULARGE_INTEGER FAR*)&lpSSIM->rgTime[0].time, 10000);
      //convert to units of 100 ns
      ulargeMultiply((ULARGE_INTEGER FAR*)&lpSSIM->rgTime[0].time, 1000);
   } else {
      sffs.yr = max(yr-1980,0);
      sffs.mon = mo;
      sffs.dom = dy;
      sffs.hr = hr;
      sffs.mint= mn;
      sffs.sec = sc/2;  //dos is 2 second intervals
      if (!CoDosDateTimeToFileTime(sffs.date, sffs.time,
            &lpSSIM->rgTime[pid].time)) {
         lpSSIM->rgTime[pid].vtType = VT_EMPTY;
         return 0;
      }
   }
   return 1;
}

} //END C


SUMINFO.H

/*************************************************************************
**
**    OLE 2.0 Property Set Utilities
**
**    suminfo.h
**
**    This file contains file contains data structure defintions,
**    function prototypes, constants, etc. for OLE 2.0 Property Set
**    utilities used to manage the Summary Info property set.
**
**    (c) Copyright Microsoft Corp. 1990 - 1996 All Rights Reserved
**
*************************************************************************/

#ifndef SUMINFO_H
#define SUMINFO_H

#include <ole2.h>
#include <oleauto.h>

/* A SUMINFO variable is an instance of an abstract data type.  Thus,
**    there can be an arbitrary number of SummaryInfo streams open
**    simultaneously (subject to available memory).  Each variable must
**    be initialized prior to use by calling Init and freed after its
**    last use by calling Free.  The param argument to Init is reserved
**    for future expansion and should be zero initially. Once a SUMINFO
**    variable is allocated (by Init), the user can call the Set
**    procedures to initialize fields.  A copy of the arguments is made
**    in every case except SetThumbnail where control of the storage
**    occupied by the METAFILEPICT is merely transferred.  When the
**    Free routine is called, all storage will be deallocated including
**    that of the thumbnail.  The arguments to SetThumbNail and the
**    return values from GetThumbNail correspond to the OLE2.0 spec.
**    Note that on input, the thumbnail is read on demand but all the
**    other properties are pre-loaded.  The thumbnail is manipulated as
**    a windows handle to a METAFILEPICT structure, which in turn
**    contains a handle to the METAFILE.  The transferClip argument on
**    GetThumbNail, when set to true, transfers responsibility for
**    storage management of the thumbnail to the caller; that is, after
**    Free has been called, the handle is still valid. Clear can be
**    used to free storage for all the properties but then you must
**    call Read to load them again.  All the code is based on FAR
**    pointers.
**    CoInitialize MUST be called PRIOR to calling OleStdInitSummaryInfo.
**    Memory is allocated using the currently active IMalloc*
**    allocator (as is returned by call CoGetMalloc(MEMCTX_TASK) ).
**
** Common scenarios:
**    Read SummaryInfo
**    ----------------
**      OleStdInitSummaryInfo()
**      OleStdReadSummaryInfo()
**      . . . . .
**      call different Get routines
**      . . . . .
**      OleStdFreeSummaryInfo()
**
**    Create SummaryInfo
**    ------------------
**      OleStdInitSummaryInfo()
**      call different Set routines
**      OleStdWriteSummaryInfo()
**      OleStdFreeSummaryInfo()
**
**    Update SummaryInfo
**    ------------------
**      OleStdInitSummaryInfo()
**      OleStdReadSummaryInfo()
**      OleStdGetThumbNailProperty(necessary only if no SetThumb)
**      call different Set routines
**      OleStdWriteSummaryInfo()
**      OleStdFreeSummaryInfo()
*/

#define WORDMAX 256     //current string max for APPS; 255 + null terminator

typedef     union {
     short    iVal;             /* VT_I2                */
     long     lVal;             /* VT_I4                */
     float    fltVal;           /* VT_R4                */
     double       dblVal;       /* VT_R8                */
     DWORD bool;                /* VT_BOOL              */
     SCODE    scodeVal;         /* VT_ERROR             */
     DWORD    systimeVal;       /* VT_SYSTIME           */
     unsigned char bstrVal[WORDMAX]; /* VT_BSTR              */
   } VTUNION;

typedef struct _PROPSETLIST
   {
   FMTID formatID;
   DWORD byteOffset;
   } PROPSETLIST;

typedef struct _PROPIDLIST
   {
   DWORD propertyID;
   DWORD byteOffset;
   } PROPIDLIST;

typedef struct _PROPVALUE
   {
   DWORD vtType;
   VTUNION vtValue;
   } PROPVALUE;

typedef struct _SECTION
   {
   DWORD cBytes;
   DWORD cProperties;
   PROPIDLIST rgPropId[1/*cProperties*/];  //variable-length array
   PROPVALUE rgPropValue[1];      //CANNOT BE ACCESSED BY NAME; ONLY BY POINTER
   } SECTION;

typedef struct _SUMMARYINFO
   {
   WORD byteOrder;
   WORD formatVersion;
   WORD getOSVersion;
   WORD osVersion;
   CLSID classId;  //from compobj.h
   DWORD cSections;
   PROPSETLIST rgPropSet[1/*cSections*/]; //variable-length array
   SECTION rgSections[1/*cSections*/];    //CANNOT BE ACCESSED BY NAME; ONLY BY POINTER
   } SUMMARYINFO;

#define osWinOnDos 0
#define osMac 1
#define osWinNT 2

#define cPIDSI_STANDARD (PIDSI_DOC_SECURITY+1-2)

#ifndef WIN32
#define MAXWORD 256         //maximum string size for APPS at present
#endif

typedef struct _STDZ
   {
   DWORD vtType;
   union {
   DWORD vtByteCount;
   OLECHAR fill[4];  //use last byte as byte count for stz requests
   };
   OLECHAR rgchars[MAXWORD];
   } STDZ;
#define VTCB fill[3]    //used to set/get the count byte when in memory

typedef struct _THUMB
   {
   DWORD vtType;
   DWORD cBytes;       //clip size in memory
   DWORD selector;     //on disk -1,win clip no.  -2,mac clip no. -3,ole FMTID  0,bytes  nameLength, format name
   DWORD clipFormat;
   LPOLESTR lpstzName;
   LPOLESTR lpByte;
   } THUMB;

#define VT_CF_BYTES 0
#define VT_CF_WIN (-1)
#define VT_CF_MAC (-2)
#define VT_CF_FMTID (-3)
#define VT_CF_NAME (-4)
#define VT_CF_EMPTY (-5)
#define VT_CF_OOM (-6)      // Out of memory
typedef THUMB FAR *LPTHUMB;

typedef STDZ FAR *LPSTDZ;

typedef struct _TIME
   {
   DWORD vtType;
   FILETIME time;
   } TIME;

typedef struct _INTS
   {
   DWORD vtType;
   DWORD value;
   } INTS;

#define MAXTIME (PIDSI_LASTSAVE_DTM-PIDSI_EDITTIME+1)
#define MAXINTS (PIDSI_CHARCOUNT-PIDSI_PAGECOUNT+1+1)
#define MAXSTDZ (PIDSI_REVNUMBER-PIDSI_TITLE+1+1)

typedef struct _STANDARDSECINMEM
   {
   DWORD cBytes;
   DWORD cProperties;
   PROPIDLIST rgPropId[cPIDSI_STANDARD/*cProperties*/];  //variable-length array
   TIME rgTime[MAXTIME];
   INTS rgInts[MAXINTS];
   LPSTDZ rglpsz[MAXSTDZ];
   THUMB thumb;
   } STANDARDSECINMEM;

#define OFFSET_NIL 0X00000000

#define AllSecurityFlagsEqNone 0
#define fSecurityPassworded 1
#define fSecurityRORecommended 2
#define fSecurityRO 4
#define fSecurityLockedForAnnotations 8

#define PropStreamNamePrefixByte '\005'
#define PropStreamName "\005SummaryInformation"
#define cbNewSummaryInfo(nSection) (sizeof(SUMMARYINFO)-sizeof(SECTION)+sizeof(PROPSETLIST)*((nSection)-1))
#define cbNewSection(nPropIds) (sizeof(SECTION)-sizeof(PROPVALUE)+sizeof(PROPIDLIST)*((nPropIds)-1))

#define FIntelOrder(prop) ((prop)->byteOrder==0xfffe)
#define SetOs(prop, os) {(prop)->osVersion=os; (prop)->getOSVersion=LOWORD(GetVersion());}
#define SetSumInfFMTID(fmtId) {(fmtId)->Data1=0XF29F85E0; *(long FAR *)&(fmtId)->Data2=0X10684FF9;\
            *(long FAR *)&(fmtId)->Data4[0]=0X000891AB; *(long FAR *)&(fmtId)->Data4[4]=0XD9B3272B;}
#define FEqSumInfFMTID(fmtId) ((fmtId)->dword==0XF29F85E0&&*((long FAR *)&(fmtId)->words)==0X10684FF9&&\
            *((long FAR *)&(fmtId)->bytes[0])==0X000891AB&&*((long FAR *)&(fmtId)->bytes[4])==0XD9B3272B)
#define FSzEqPropStreamName(sz) _fstricmp(sz, PropStreamName)
#define ClearSumInf(lpsuminf, cb) {_fmemset(lpsuminf,0,cb); (lpsuminf)->byteOrder=0xfffe;\
            SetOs(lpsuminf, osWinOnDos);}

typedef void FAR *LPSUMINFO;
typedef LPSTR LPSTZR;
typedef void FAR *THUMBNAIL;  //for VT_CF_WIN this is an unlocked global handle
#define API __far __pascal

/*************************************************************************
** Public Summary Info Property Set Management API
*************************************************************************/

extern "C" {
STDAPI_(LPSUMINFO) OleStdInitSummaryInfo(int reserved);
STDAPI_(void) OleStdFreeSummaryInfo(LPSUMINFO FAR *lplp);
STDAPI_(void) OleStdClearSummaryInfo(LPSUMINFO lp);
STDAPI_(int) OleStdReadSummaryInfo(LPSTREAM lpStream, LPSUMINFO lp);
STDAPI_(int) OleStdWriteSummaryInfo(LPSTREAM lpStream, LPSUMINFO lp);
STDAPI_(DWORD) OleStdGetSecurityProperty(LPSUMINFO lp);
STDAPI_(int) OleStdSetSecurityProperty(LPSUMINFO lp, DWORD security);
STDAPI_(LPSTR) OleStdGetStringProperty(LPSUMINFO lp, DWORD pid);
STDAPI_(int) OleStdSetStringProperty(LPSUMINFO lp, DWORD pid, LPOLESTR lpsz);
STDAPI_(LPSTZR) OleStdGetStringZProperty(LPSUMINFO lp, DWORD pid);
STDAPI_(void) OleStdGetDocProperty(
      LPSUMINFO       lp,
      DWORD FAR*      nPage,
      DWORD FAR*      nWords,
      DWORD FAR*      nChars
);
STDAPI_(int) OleStdSetDocProperty(
      LPSUMINFO       lp,
      DWORD           nPage,
      DWORD           nWords,
      DWORD           nChars
);
STDAPI_(int) OleStdGetThumbNailProperty(
      LPSTREAM        lps,
      LPSUMINFO       lp,
      DWORD FAR*      clipFormatNo,
      LPOLESTR FAR*   lpszName,
      THUMBNAIL FAR*  clip,
      DWORD FAR*      byteCount,
      BOOL            transferClip
);
STDAPI_(int) OleStdSetThumbNailProperty(
      LPSTREAM        lps,
      LPSUMINFO       lp,
      int             vtcfNo,
      DWORD           clipFormatNo,
      LPOLESTR        lpszName,
      THUMBNAIL       clip,
      DWORD           byteCount
);
STDAPI_(void) OleStdGetDateProperty(
      LPSUMINFO       lp,
      DWORD           pid,
      int FAR*        yr,
      int FAR*        mo,
      int FAR*        dy,
      DWORD FAR*      sc
);
STDAPI_(int) OleStdSetDateProperty(
      LPSUMINFO       lp,
      DWORD           pid,
      int             yr,
      int             mo,
      int             dy,
      int             hr,
      int             mn,
      int             sc
);

} //END C

/*************************************************************************
** DEBUG ASSERTION ROUTINES
*************************************************************************/

#if DBG
#include <assert.h>
#define FnAssert(lpstrExpr, lpstrMsg, lpstrFileName, iLine)     \
        (_assert(lpstrMsg ? lpstrMsg : lpstrExpr,               \
                 lpstrFileName,                                 \
                 iLine), NOERROR)
#endif //DBG

#endif  // SUMINFO_H


TARGTDEV.C

/*************************************************************************
**
**    OLE 2 Standard Utilities
**
**    olestd.c
**
**    This file contains utilities that are useful for dealing with
**    target devices.
**
**    (c) Copyright Microsoft Corp. 1992-1996 All Rights Reserved
**
*************************************************************************/

#define STRICT  1
#include "olestd.h"
#ifndef WIN32
#include <print.h>
#endif

/*
 * OleStdCreateDC()
 *
 * Purpose:
 *
 * Parameters:
 *
 * Return Value:
 *    SCODE  -  S_OK if successful
 */
STDAPI_(HDC) OleStdCreateDC(DVTARGETDEVICE FAR* ptd)
{
        HDC hdc=NULL;
        LPDEVNAMES lpDevNames;
        LPDEVMODE lpDevMode;
        LPSTR lpszDriverName;
        LPSTR lpszDeviceName;
        LPSTR lpszPortName;

        if (ptd == NULL) {
                hdc = CreateDC("DISPLAY", NULL, NULL, NULL);
                goto errReturn;
        }

        lpDevNames = (LPDEVNAMES) ptd; // offset for size field

        if (ptd->tdExtDevmodeOffset == 0) {
                lpDevMode = NULL;
        }else{
                lpDevMode  = (LPDEVMODE) (/*(LPSTR)*/ptd + ptd->tdExtDevmodeOffset);
        }

        lpszDriverName = (LPSTR) lpDevNames + ptd->tdDriverNameOffset;
        lpszDeviceName = (LPSTR) lpDevNames + ptd->tdDeviceNameOffset;
        lpszPortName   = (LPSTR) lpDevNames + ptd->tdPortNameOffset;

        hdc = CreateDC(lpszDriverName, lpszDeviceName, lpszPortName, lpDevMode);

errReturn:
        return hdc;
}

/*
 * OleStdCreateIC()
 *
 * Purpose: Same as OleStdCreateDC, except that information context is
 *          created, rather than a whole device context.  (CreateIC is
 *          used rather than CreateDC).
 *          OleStdDeleteDC is still used to delete the information context.
 *
 * Parameters:
 *
 * Return Value:
 *    SCODE  -  S_OK if successful
 */
STDAPI_(HDC) OleStdCreateIC(DVTARGETDEVICE FAR* ptd)
{
        HDC hdcIC=NULL;
        LPDEVNAMES lpDevNames;
        LPDEVMODE lpDevMode;
        LPSTR lpszDriverName;
        LPSTR lpszDeviceName;
        LPSTR lpszPortName;

        if (ptd == NULL) {
                hdcIC = CreateIC("DISPLAY", NULL, NULL, NULL);
                goto errReturn;
        }

        lpDevNames = (LPDEVNAMES) ptd; // offset for size field

        lpDevMode  = (LPDEVMODE) (/*(LPSTR)*/ptd + ptd->tdExtDevmodeOffset);

        lpszDriverName = (LPSTR) lpDevNames + ptd->tdDriverNameOffset;
        lpszDeviceName = (LPSTR) lpDevNames + ptd->tdDeviceNameOffset;
        lpszPortName   = (LPSTR) lpDevNames + ptd->tdPortNameOffset;

        hdcIC = CreateIC(lpszDriverName, lpszDeviceName, lpszPortName, lpDevMode);

errReturn:
        return hdcIC;
}

/*
 * OleStdCreateTargetDevice()
 *
 * Purpose:
 *
 * Parameters:
 *
 * Return Value:
 *    SCODE  -  S_OK if successful
 */
STDAPI_(DVTARGETDEVICE FAR*) OleStdCreateTargetDevice(LPPRINTDLG lpPrintDlg)
{
        DVTARGETDEVICE FAR* ptd=NULL;
        LPDEVNAMES lpDevNames, pDN;
        LPDEVMODE lpDevMode, pDM;
        UINT nMaxOffset;
        LPSTR pszName;
        DWORD dwDevNamesSize, dwDevModeSize, dwPtdSize;

        if ((pDN = (LPDEVNAMES)GlobalLock(lpPrintDlg->hDevNames)) == NULL) {
                goto errReturn;
        }

        if ((pDM = (LPDEVMODE)GlobalLock(lpPrintDlg->hDevMode)) == NULL) {
                goto errReturn;
        }

        nMaxOffset =  (pDN->wDriverOffset > pDN->wDeviceOffset) ?
                pDN->wDriverOffset : pDN->wDeviceOffset ;

        nMaxOffset =  (pDN->wOutputOffset > nMaxOffset) ?
                pDN->wOutputOffset : nMaxOffset ;

        pszName = (LPSTR)pDN + nMaxOffset;

        dwDevNamesSize = (DWORD)(nMaxOffset+lstrlen(pszName) + 1/* NULL term */);
        dwDevModeSize = (DWORD) (pDM->dmSize + pDM->dmDriverExtra);

        dwPtdSize = sizeof(DWORD) + dwDevNamesSize + dwDevModeSize;

        if ((ptd = (DVTARGETDEVICE FAR*)OleStdMalloc(dwPtdSize)) != NULL) {

                // copy in the info
                ptd->tdSize = (UINT)dwPtdSize;

                lpDevNames = (LPDEVNAMES) &ptd->tdDriverNameOffset;
                _fmemcpy(lpDevNames, pDN, (size_t)dwDevNamesSize);

                lpDevMode=(LPDEVMODE)(/*(LPSTR)*/&ptd->tdDriverNameOffset+dwDevNamesSize);
                _fmemcpy(lpDevMode, pDM, (size_t)dwDevModeSize);

                ptd->tdDriverNameOffset += 4 ;
                ptd->tdDeviceNameOffset += 4 ;
                ptd->tdPortNameOffset   += 4 ;
                ptd->tdExtDevmodeOffset = (UINT)dwDevNamesSize + 4 ;
        }

errReturn:
        GlobalUnlock(lpPrintDlg->hDevNames);
        GlobalUnlock(lpPrintDlg->hDevMode);

        return ptd;
}

/*
 * OleStdDeleteTargetDevice()
 *
 * Purpose:
 *
 * Parameters:
 *
 * Return Value:
 *    SCODE  -  S_OK if successful
 */
STDAPI_(BOOL) OleStdDeleteTargetDevice(DVTARGETDEVICE FAR* ptd)
{
        BOOL res=TRUE;

        if (ptd != NULL) {
                OleStdFree(ptd);
        }

        return res;
}

/*
 * OleStdCopyTargetDevice()
 *
 * Purpose:
 *  duplicate a TARGETDEVICE struct. this function allocates memory for
 *  the copy. the caller MUST free the allocated copy when done with it
 *  using the standard allocator returned from CoGetMalloc.
 *  (OleStdFree can be used to free the copy).
 *
 * Parameters:
 *  ptdSrc      pointer to source TARGETDEVICE
 *
 * Return Value:
 *    pointer to allocated copy of ptdSrc
 *    if ptdSrc==NULL then retuns NULL is returned.
 *    if ptdSrc!=NULL and memory allocation fails, then NULL is returned
 */
STDAPI_(DVTARGETDEVICE FAR*) OleStdCopyTargetDevice(DVTARGETDEVICE FAR* ptdSrc)
{
  DVTARGETDEVICE FAR* ptdDest = NULL;

  if (ptdSrc == NULL) {
        return NULL;
  }

  if ((ptdDest = (DVTARGETDEVICE FAR*)OleStdMalloc(ptdSrc->tdSize)) != NULL) {
        _fmemcpy(ptdDest, ptdSrc, (size_t)ptdSrc->tdSize);
  }

  return ptdDest;
}

/*
 * OleStdCopyFormatEtc()
 *
 * Purpose:
 *  Copies the contents of a FORMATETC structure. this function takes
 *  special care to copy correctly copying the pointer to the TARGETDEVICE
 *  contained within the source FORMATETC structure.
 *  if the source FORMATETC has a non-NULL TARGETDEVICE, then a copy
 *  of the TARGETDEVICE will be allocated for the destination of the
 *  FORMATETC (petcDest).
 *
 *  NOTE: the caller MUST free the allocated copy of the TARGETDEVICE
 *  within the destination FORMATETC when done with it
 *  using the standard allocator returned from CoGetMalloc.
 *  (OleStdFree can be used to free the copy).
 *
 * Parameters:
 *  petcDest      pointer to destination FORMATETC
 *  petcSrc       pointer to source FORMATETC
 *
 * Return Value:
 *    returns TRUE is copy is successful; retuns FALSE if not successful
 */
STDAPI_(BOOL) OleStdCopyFormatEtc(LPFORMATETC petcDest, LPFORMATETC petcSrc)
{
  if ((petcDest == NULL) || (petcSrc == NULL)) {
        return FALSE;
  }

  petcDest->cfFormat = petcSrc->cfFormat;
  petcDest->ptd      = OleStdCopyTargetDevice(petcSrc->ptd);
  petcDest->dwAspect = petcSrc->dwAspect;
  petcDest->lindex   = petcSrc->lindex;
  petcDest->tymed    = petcSrc->tymed;

  return TRUE;

}

// returns 0 for exact match, 1 for no match, -1 for partial match (which is
// defined to mean the left is a subset of the right: fewer aspects, null target
// device, fewer medium).

STDAPI_(int) OleStdCompareFormatEtc(FORMATETC FAR* pFetcLeft, FORMATETC FAR* pFetcRight)
{
        BOOL bExact = TRUE;

        if (pFetcLeft->cfFormat != pFetcRight->cfFormat)
                return 1;
        else if (!OleStdCompareTargetDevice (pFetcLeft->ptd, pFetcRight->ptd))
                return 1;
        if (pFetcLeft->dwAspect == pFetcRight->dwAspect)
                // same aspects; equal
                ;
        else if ((pFetcLeft->dwAspect & ~pFetcRight->dwAspect) != 0)
                // left not subset of aspects of right; not equal
                return 1;
        else
                // left subset of right
                bExact = FALSE;

        if (pFetcLeft->tymed == pFetcRight->tymed)
                // same medium flags; equal
                ;
        else if ((pFetcLeft->tymed & ~pFetcRight->tymed) != 0)
                // left not subset of medium flags of right; not equal
                return 1;
        else
                // left subset of right
                bExact = FALSE;

        return bExact ? 0 : -1;
}

STDAPI_(BOOL) OleStdCompareTargetDevice
        (DVTARGETDEVICE FAR* ptdLeft, DVTARGETDEVICE FAR* ptdRight)
{
        if (ptdLeft == ptdRight)
                // same address of td; must be same (handles NULL case)
                return TRUE;
        else if ((ptdRight == NULL) || (ptdLeft == NULL))
                return FALSE;
        else if (ptdLeft->tdSize != ptdRight->tdSize)
                // different sizes, not equal
                return FALSE;
#ifdef WIN32
        else if (memcmp(ptdLeft, ptdRight, ptdLeft->tdSize) != 0)
#else
        else if (_fmemcmp(ptdLeft, ptdRight, (int)ptdLeft->tdSize) != 0)
#endif
                // not same target device, not equal
                return FALSE;

        return TRUE;
}


UTILITY.C

/*
 * UTILITY.C
 *
 * Utility routines for functions inside OLESTD.LIB
 *
 *  General:
 *  ----------------------
 *  HourGlassOn             Displays the hourglass
 *  HourGlassOff            Hides the hourglass
 *
 *  Misc Tools:
 *  ----------------------
 *  Browse                  Displays the "File..." or "Browse..." dialog.
 *  ReplaceCharWithNull     Used to form filter strings for Browse.
 *  ErrorWithFile           Creates an error message with embedded filename
 *  OpenFileError           Give error message for OpenFile error return
 *  ChopText                Chop a file path to fit within a specified width
 *  DoesFileExist           Checks if file is valid
 *
 *  Registration Database:
 *  ----------------------
 *  HIconFromClass          Extracts the first icon in a class's server path
 *  FServerFromClass        Retrieves the server path for a class name (fast)
 *  UClassFromDescription   Finds the classname given a description (slow)
 *  UDescriptionFromClass   Retrieves the description for a class name (fast)
 *  FGetVerb                Retrieves a specific verb for a class (fast)
 *
 *
 * Copyright (c)1992-1996 Microsoft Corporation, All Right Reserved
 */

#define STRICT  1
#include "olestd.h"
#include <stdlib.h>
#include <commdlg.h>
#include <memory.h>
#include <cderr.h>
#include "common.h"
#include "utility.h"

#ifdef WIN32
/* 1/23/94 - define LINK_COMMDLG to avoid LoadLibrary("commdlg.dll") */
#define LINK_COMMDLG
#endif

OLEDBGDATA

/*
 * HourGlassOn
 *
 * Purpose:
 *  Shows the hourglass cursor returning the last cursor in use.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HCURSOR         Cursor in use prior to showing the hourglass.
 */

HCURSOR WINAPI HourGlassOn(void)
   {
   HCURSOR     hCur;

   hCur=SetCursor(LoadCursor(NULL, IDC_WAIT));
   ShowCursor(TRUE);

   return hCur;
   }

/*
 * HourGlassOff
 *
 * Purpose:
 *  Turns off the hourglass restoring it to a previous cursor.
 *
 * Parameters:
 *  hCur            HCURSOR as returned from HourGlassOn
 *
 * Return Value:
 *  None
 */

void WINAPI HourGlassOff(HCURSOR hCur)
   {
   ShowCursor(FALSE);
   SetCursor(hCur);
   return;
   }

/*
 * Browse
 *
 * Purpose:
 *  Displays the standard GetOpenFileName dialog with the title of
 *  "Browse."  The types listed in this dialog are controlled through
 *  iFilterString.  If it's zero, then the types are filled with "*.*"
 *  Otherwise that string is loaded from resources and used.
 *
 * Parameters:
 *  hWndOwner       HWND owning the dialog
 *  lpszFile        LPSTR specifying the initial file and the buffer in
 *                  which to return the selected file.  If there is no
 *                  initial file the first character of this string should
 *                  be NULL.
 *  lpszInitialDir  LPSTR specifying the initial directory.  If none is to
 *                  set (ie, the cwd should be used), then this parameter
 *                  should be NULL.
 *  cchFile         UINT length of pszFile
 *  iFilterString   UINT index into the stringtable for the filter string.
 *  dwOfnFlags      DWORD flags to OR with OFN_HIDEREADONLY
 *
 * Return Value:
 *  BOOL            TRUE if the user selected a file and pressed OK.
 *                  FALSE otherwise, such as on pressing Cancel.
 */

BOOL WINAPI Browse(HWND hWndOwner, LPSTR lpszFile, LPSTR lpszInitialDir, UINT cchFile, UINT iFilterString, DWORD dwOfnFlags)
   {
      UINT           cch;
      char           szFilters[256];
      OPENFILENAME   ofn;
      BOOL           fStatus;
      DWORD          dwError;
      char            szDlgTitle[128];  // that should be big enough
#if !defined( LINK_COMMDLG )
      HINSTANCE hinstCommDlg = NULL;
      typedef BOOL (WINAPI* LPFNGETOPENFILENAME) (OPENFILENAME FAR*);
      LPFNGETOPENFILENAME lpfnGetOpenFileName;
      typedef DWORD (WINAPI* LPFNCOMMDLGEXTENDEDERROR) (void);
      LPFNCOMMDLGEXTENDEDERROR lpfnCommDlgExtendedError;
#endif

   if (NULL==lpszFile || 0==cchFile)
      return FALSE;

   /*
    * REVIEW:  Exact contents of the filter combobox is TBD.  One idea
    * is to take all the extensions in the RegDB and place them in here
    * with the descriptive class name associate with them.  This has the
    * extra step of finding all extensions of the same class handler and
    * building one extension string for all of them.  Can get messy quick.
    * UI demo has only *.* which we do for now.
    */

   if (0!=iFilterString)
      cch=LoadString(ghInst, iFilterString, /*(LPSTR)*/szFilters, sizeof(szFilters));
   else
      {
      szFilters[0]=0;
      cch=1;
      }

   if (0==cch)
      return FALSE;

   ReplaceCharWithNull(szFilters, szFilters[cch-1]);

   //Prior string must also be initialized, if there is one.
   _fmemset((LPOPENFILENAME)&ofn, 0, sizeof(ofn));
   ofn.lStructSize =sizeof(ofn);
   ofn.hwndOwner   =hWndOwner;
   ofn.lpstrFile   =lpszFile;
   ofn.nMaxFile    =cchFile;
   ofn.lpstrFilter =/*(LPSTR)*/szFilters;
   ofn.nFilterIndex=1;
   if (LoadString(ghInst, (UINT)IDS_BROWSE, /*(LPSTR)*/szDlgTitle, sizeof(szDlgTitle)))
      ofn.lpstrTitle  =/*(LPSTR)*/szDlgTitle;
   ofn.hInstance = ghInst;
   ofn.lpTemplateName = MAKEINTRESOURCE(IDD_FILEOPEN);
   if (NULL != lpszInitialDir)
     ofn.lpstrInitialDir = lpszInitialDir;

   ofn.Flags= OFN_HIDEREADONLY | OFN_ENABLETEMPLATE | (dwOfnFlags) ;

#if !defined( LINK_COMMDLG )
   // Load the COMMDLG.DLL library module
   hinstCommDlg = LoadLibrary("COMMDLG.DLL");
   if (hinstCommDlg <= HINSTANCE_ERROR)   /* load failed */
      return FALSE;

   // Retrieve the address of required functions
   lpfnGetOpenFileName = (LPFNGETOPENFILENAME)
         GetProcAddress(hinstCommDlg, "GetOpenFileName");
   lpfnCommDlgExtendedError = (LPFNCOMMDLGEXTENDEDERROR)
         GetProcAddress(hinstCommDlg, "CommDlgExtendedError");

   if (lpfnGetOpenFileName == NULL || lpfnCommDlgExtendedError == NULL)
      {
      FreeLibrary(hinstCommDlg);
      return FALSE;
      }

   //On success, copy the chosen filename to the static display
   fStatus = (*lpfnGetOpenFileName) ((LPOPENFILENAME)&ofn);
   dwError = (*lpfnCommDlgExtendedError)();

   FreeLibrary(hinstCommDlg);
   return fStatus;
#else
   //On success, copy the chosen filename to the static display
   fStatus = GetOpenFileName((LPOPENFILENAME)&ofn);
   dwError = CommDlgExtendedError();
   return fStatus;
#endif
   }

/*
 * ReplaceCharWithNull
 *
 * Purpose:
 *  Walks a null-terminated string and replaces a given character
 *  with a zero.  Used to turn a single string for file open/save
 *  filters into the appropriate filter string as required by the
 *  common dialog API.
 *
 * Parameters:
 *  psz             LPSTR to the string to process.
 *  ch              int character to replace.
 *
 * Return Value:
 *  int             Number of characters replaced.  -1 if psz is NULL.
 */

int WINAPI ReplaceCharWithNull(LPSTR psz, int ch)
   {
   int             cChanged=-1;

   if (NULL!=psz)
      {
      while (0!=*psz)
         {
         if (ch==*psz)
            {
            *psz=0;
            cChanged++;
            }
         psz++;
         }
      }
   return cChanged;
   }

/*
 * ErrorWithFile
 *
 * Purpose:
 *  Displays a message box built from a stringtable string containing
 *  one %s as a placeholder for a filename and from a string of the
 *  filename to place there.
 *
 * Parameters:
 *  hWnd            HWND owning the message box.  The caption of this
 *                  window is the caption of the message box.
 *  hInst           HINSTANCE from which to draw the idsErr string.
 *  idsErr          UINT identifier of a stringtable string containing
 *                  the error message with a %s.
 *  lpszFile        LPSTR to the filename to include in the message.
 *  uFlags          UINT flags to pass to MessageBox, like MB_OK.
 *
 * Return Value:
 *  int             Return value from MessageBox.
 */

int WINAPI ErrorWithFile(HWND hWnd, HINSTANCE hInst, UINT idsErr
              , LPSTR pszFile, UINT uFlags)
   {
   int             iRet=0;
   HANDLE          hMem;
   const UINT      cb=(2*OLEUI_CCHPATHMAX);
   LPSTR           psz1, psz2, psz3;

   if (NULL==hInst || NULL==pszFile)
      return iRet;

   //Allocate three 2*OLEUI_CCHPATHMAX byte work buffers
   hMem=GlobalAlloc(GHND, (DWORD)(3*cb));

   if (NULL==hMem)
      return iRet;

   psz1=GlobalLock(hMem);
   psz2=psz1+cb;
   psz3=psz2+cb;

   if (0!=LoadString(hInst, idsErr, psz1, cb))
      {
      wsprintf(psz2, psz1, pszFile);

      //Steal the caption of the dialog
      GetWindowText(hWnd, psz3, cb);
      iRet=MessageBox(hWnd, psz2, psz3, uFlags);
      }

   GlobalUnlock(hMem);
   GlobalFree(hMem);
   return iRet;
   }

/*
 * HIconFromClass
 *
 * Purpose:
 *  Given an object class name, finds an associated executable in the
 *  registration database and extracts the first icon from that
 *  executable.  If none is available or the class has no associated
 *  executable, this function returns NULL.
 *
 * Parameters:
 *  pszClass        LPSTR giving the object class to look up.
 *
 * Return Value:
 *  HICON           Handle to the extracted icon if there is a module
 *                  associated to pszClass.  NULL on failure to either
 *                  find the executable or extract and icon.
 */

HICON WINAPI HIconFromClass(LPSTR pszClass)
   {
   HICON           hIcon;
   char            szEXE[OLEUI_CCHPATHMAX];
   UINT            Index;
   CLSID           clsid;
   OLECHAR         szUniStr[256];

   if (NULL==pszClass)
      return NULL;

   A2W (pszClass, szUniStr, 256);
   CLSIDFromString(szUniStr, &clsid);

   if (!FIconFileFromClass((REFCLSID)&clsid, szEXE, sizeof(szEXE), &Index))
      return NULL;

   hIcon=ExtractIcon(ghInst, szEXE, Index);

   if ((HICON)32 > hIcon)
      hIcon=NULL;

   return hIcon;
   }

/*
 * FServerFromClass
 *
 * Purpose:
 *  Looks up the classname in the registration database and retrieves
 *  the name under CLSID\[Local|InProc]Server.
 *
 * Parameters:
 *  pszClass        LPSTR to the classname to look up.
 *  pszEXE          LPSTR at which to store the server name
 *  cch             UINT size of pszEXE
 *
 * Return Value:
 *  BOOL            TRUE if one or more characters were loaded into pszEXE.
 *                  FALSE otherwise.
 */

BOOL WINAPI FServerFromClass(LPSTR pszClass, LPSTR pszEXE, UINT cch)
{

   DWORD       dw;
   LONG        lRet;
   HKEY        hKey;

   if (NULL==pszClass || NULL==pszEXE || 0==cch)
      return FALSE;

   /*
    * We have to go walking in the registration database under the
    * classname, so we first open the classname key and then check
    * under "\\LocalServer" to get the .EXE.
    */

   //Open up the class key
   lRet=RegOpenKey(HKEY_CLASSES_ROOT, pszClass, &hKey);

   if ((LONG)ERROR_SUCCESS!=lRet)
      return FALSE;

   //Get the executable path.
   dw=(DWORD)cch;
#ifdef WIN32
   lRet=RegQueryValue(hKey, "LocalServer32", pszEXE, &dw);
#else
   lRet=RegQueryValue(hKey, "LocalServer", pszEXE, &dw);
#endif

   if ((LONG)ERROR_SUCCESS!=lRet)
        {
        //Try InprocServer
#ifdef WIN32
        lRet=RegQueryValue(hKey, "InProcServer32", pszEXE, &dw);
#else
        lRet=RegQueryValue(hKey, "InProcServer32", pszEXE, &dw);
#endif
        }

   RegCloseKey(hKey);

   return ((ERROR_SUCCESS == lRet) && (dw > 0));
}

/*
 * UClassFromDescription
 *
 * Purpose:
 *  Looks up the actual OLE class name in the registration database
 *  for the given descriptive name chosen from a listbox.
 *
 * Parameters:
 *  psz             LPSTR to the descriptive name.
 *  pszClass        LPSTR in which to store the class name.
 *  cb              UINT maximum length of pszClass.
 *
 * Return Value:
 *  UINT            Number of characters copied to pszClass.  0 on failure.
 */

UINT WINAPI UClassFromDescription(LPSTR psz, LPSTR pszClass, UINT cb)
   {
   DWORD           dw;
   HKEY            hKey;
   char            szClass[OLEUI_CCHKEYMAX];
   LONG            lRet;
   UINT            i;

   //Open up the root key.
   lRet=RegOpenKey(HKEY_CLASSES_ROOT, NULL, &hKey);

   if ((LONG)ERROR_SUCCESS!=lRet)
      return 0;

   i=0;
   lRet=RegEnumKey(hKey, i++, szClass, OLEUI_CCHKEYMAX);

   //Walk the available keys
   while ((LONG)ERROR_SUCCESS==lRet)
      {
      dw=(DWORD)cb;
      lRet=RegQueryValue(hKey, szClass, pszClass, &dw);

      //Check if the description matches the one just enumerated
      if ((LONG)ERROR_SUCCESS==lRet)
         {
         if (!lstrcmp(pszClass, psz))
            break;
         }

      //Continue with the next key.
      lRet=RegEnumKey(hKey, i++, szClass, OLEUI_CCHKEYMAX);
      }

   //If we found it, copy to the return buffer
   if ((LONG)ERROR_SUCCESS==lRet)
      lstrcpy(pszClass, szClass);
   else
      dw=0L;

   RegCloseKey(hKey);
   return (UINT)dw;
   }

/*
 * UDescriptionFromClass
 *
 * Purpose:
 *  Looks up the actual OLE descriptive name name in the registration
 *  database for the given class name.
 *
 * Parameters:
 *  pszClass        LPSTR to the class name.
 *  psz             LPSTR in which to store the descriptive name.
 *  cb              UINT maximum length of psz.
 *
 * Return Value:
 *  UINT            Number of characters copied to pszClass.  0 on failure.
 */

UINT WINAPI UDescriptionFromClass(LPSTR pszClass, LPSTR psz, UINT cb)
   {
   DWORD           dw;
   HKEY            hKey;
   LONG            lRet;

   if (NULL==pszClass || NULL==psz)
      return 0;

   //Open up the root key.
   lRet=RegOpenKey(HKEY_CLASSES_ROOT, NULL, &hKey);

   if ((LONG)ERROR_SUCCESS!=lRet)
      return 0;

   //Get the descriptive name using the class name.
   dw=(DWORD)cb;
   lRet=RegQueryValue(hKey, pszClass, psz, &dw);

   RegCloseKey(hKey);

   psz+=lstrlen(psz)+1;
   *psz=0;

   if ((LONG)ERROR_SUCCESS!=lRet)
      return 0;

   return (UINT)dw;
   }

// returns width of line of text. this is a support routine for ChopText
static LONG GetTextWSize(HDC hDC, LPSTR lpsz)
{
   SIZE size;

   if (GetTextExtentPoint(hDC, lpsz, lstrlen(lpsz), (LPSIZE)&size))
      return size.cx;
   else {
      return 0;
   }
}

/*
 * ChopText
 *
 * Purpose:
 *  Parse a string (pathname) and convert it to be within a specified
 *  length by chopping the least significant part
 *
 * Parameters:
 *  hWnd            window handle in which the string resides
 *  nWidth          max width of string in pixels
 *                  use width of hWnd if zero
 *  lpch            pointer to beginning of the string
 *
 * Return Value:
 *  pointer to the modified string
 */
LPSTR WINAPI ChopText(HWND hWnd, int nWidth, LPSTR lpch)
{
#define PREFIX_SIZE    7 + 1
#define PREFIX_FORMAT "%c%c%c...\\"

   char    szPrefix[PREFIX_SIZE];
   BOOL    fDone = FALSE;
   int     i;
   RECT    rc;
   HDC     hdc;
   HFONT   hfont;
   HFONT   hfontOld = NULL;

   if (!hWnd || !lpch)
      return NULL;

   /* Get length of static field. */
   if (!nWidth) {
      GetClientRect(hWnd, (LPRECT)&rc);
      nWidth = rc.right - rc.left;
   }

   /* Set up DC appropriately for the static control */
   hdc = GetDC(hWnd);
   hfont = (HFONT)SendMessage(hWnd, WM_GETFONT, 0, 0L);

   if (NULL != hfont)   // WM_GETFONT returns NULL if window uses system font
      hfontOld = SelectObject(hdc, hfont);

   /* check horizontal extent of string */
   if (GetTextWSize(hdc, lpch) > nWidth) {

      /* string is too long to fit in static control; chop it */
      /* set up new prefix & determine remaining space in control */
      wsprintf(/*(LPSTR)*/ szPrefix, PREFIX_FORMAT, lpch[0], lpch[1], lpch[2]);
      nWidth -= (int)GetTextWSize(hdc, /*(LPSTR)*/ szPrefix);

      /*
      ** advance a directory at a time until the remainder of the
      ** string fits into the static control after the "x:\...\" prefix
      */
      while (!fDone) {

#ifdef DBCS
         while (*lpch && (*lpch != '\\'))
            lpch = AnsiNext(lpch);
         if (*lpch)
            lpch = AnsiNext(lpch);
#else
         while (*lpch && (*lpch++ != '\\'));
#endif

         if (!*lpch || GetTextWSize(hdc, lpch) <= nWidth) {
            if (!*lpch)
               /*
               ** Nothing could fit after the prefix; remove the
               ** final "\" from the prefix
               */
               szPrefix[lstrlen(/*(LPSTR)*/ szPrefix) - 1] = 0;

               /* rest or string fits -- stick prefix on front */
               for (i = lstrlen(/*(LPSTR)*/ szPrefix) - 1; i >= 0; --i)
                  *--lpch = szPrefix[i];
               fDone = TRUE;
         }
      }
   }

   if (NULL != hfont)
     SelectObject(hdc, hfontOld);
   ReleaseDC(hWnd, hdc);

   return(lpch);

#undef PREFIX_SIZE
#undef PREFIX_FORMAT
}

/*
 * OpenFileError
 *
 * Purpose:
 *  display message for error returned from OpenFile
 *
 * Parameters:
 *  hDlg            HWND of the dialog.
 *  nErrCode        UINT error code returned in OFSTRUCT passed to OpenFile
 *  lpszFile        LPSTR file name passed to OpenFile
 *
 * Return Value:
 *  None
 */
void WINAPI OpenFileError(HWND hDlg, UINT nErrCode, LPSTR lpszFile)
{
   switch (nErrCode) {
      case 0x0005:    // Access denied
         ErrorWithFile(hDlg, ghInst, (UINT)IDS_CIFILEACCESS, lpszFile, MB_OK);
         break;

      case 0x0020:    // Sharing violation
         ErrorWithFile(hDlg, ghInst, (UINT)IDS_CIFILESHARE, lpszFile, MB_OK);
         break;

      case 0x0002:    // File not found
      case 0x0003:    // Path not found
         ErrorWithFile(hDlg, ghInst, (UINT)IDS_CIINVALIDFILE, lpszFile, MB_OK);
         break;

      default:
         ErrorWithFile(hDlg, ghInst, (UINT)IDS_CIFILEOPENFAIL, lpszFile, MB_OK);
         break;
   }
}

#define chSpace        ' '
#define chPeriod       '.'
#define PARSE_EMPTYSTRING   -1
#define PARSE_INVALIDDRIVE  -2
#define PARSE_INVALIDPERIOD -3
#define PARSE_INVALIDDIRCHAR    -4
#define PARSE_INVALIDCHAR   -5
#define PARSE_WILDCARDINDIR -6
#define PARSE_INVALIDNETPATH    -7
#define PARSE_INVALIDSPACE  -8
#define PARSE_EXTENTIONTOOLONG  -9
#define PARSE_DIRECTORYNAME -10
#define PARSE_FILETOOLONG   -11

/*---------------------------------------------------------------------------
 * ParseFile
 * Purpose:  Determine if the filename is a legal DOS name
 * Input:    Long pointer to a SINGLE file name
 *           Circumstance checked:
 *           1) Valid as directory name, but not as file name
 *           2) Empty String
 *           3) Illegal Drive label
 *           4) Period in invalid location (in extention, 1st in file name)
 *           5) Missing directory character
 *           6) Illegal character
 *           7) Wildcard in directory name
 *           8) Double slash beyond 1st 2 characters
 *           9) Space character in the middle of the name (trailing spaces OK)
 *          10) Filename greater than 8 characters
 *          11) Extention greater than 3 characters
 * Notes:
 *   Filename length is NOT checked.
 *   Valid filenames will have leading spaces, trailing spaces and
 *     terminating period stripped in place.
 *
 * Returns:  If valid, LOWORD is byte offset to filename
 *                     HIWORD is byte offset to extention
 *                            if string ends with period, 0
 *                            if no extention is given, string length
 *           If invalid, LOWORD is error code suggesting problem (< 0)
 *                       HIWORD is approximate offset where problem found
 *                       Note that this may be beyond the offending character
 *--------------------------------------------------------------------------*/

static long ParseFile(LPSTR lpstrFileName)
{
  short nFile, nExt, nFileOffset, nExtOffset;
  BOOL bExt;
  BOOL bWildcard;
  short nNetwork = 0;
  BOOL  bUNCPath = FALSE;
  LPSTR lpstr = lpstrFileName;

/* Strip off initial white space.  Note that TAB is not checked */
/* because it cannot be received out of a standard edit control */
/* 30 January 1991  clarkc                                      */
  while (*lpstr == chSpace)
     lpstr++;

  if (!*lpstr)
   {
     nFileOffset = PARSE_EMPTYSTRING;
     goto FAILURE;
   }

  if (lpstr != lpstrFileName)
   {
     lstrcpy(lpstrFileName, lpstr);
     lpstr = lpstrFileName;
   }

  if (*AnsiNext(lpstr) == ':')
   {
     char cDrive = (*lpstr | (BYTE) 0x20);  /* make lowercase */

/* This does not test if the drive exists, only if it's legal */
     if ((cDrive < 'a') || (cDrive > 'z'))
      {
        nFileOffset = PARSE_INVALIDDRIVE;
        goto FAILURE;
      }
     lpstr = AnsiNext(AnsiNext(lpstr));
   }

  if ((*lpstr == '\\') || (*lpstr == '/'))
   {
     if (*++lpstr == chPeriod)               /* cannot have c:\. */
      {
        if ((*++lpstr != '\\') && (*lpstr != '/'))   /* unless it's stupid */
         {
           if (!*lpstr)        /* it's the root directory */
              goto MustBeDir;

           nFileOffset = PARSE_INVALIDPERIOD;
           goto FAILURE;
         }
        else
           ++lpstr;   /* it's saying top directory (again), thus allowed */
      }
     else if ((*lpstr == '\\') && (*(lpstr-1) == '\\'))
      {
/* It seems that for a full network path, whether a drive is declared or
 * not is insignificant, though if a drive is given, it must be valid
 * (hence the code above should remain there).
 * 13 February 1991           clarkc
 */
        ++lpstr;            /* ...since it's the first slash, 2 are allowed */
        nNetwork = -1;      /* Must receive server and share to be real     */
        bUNCPath = TRUE;    /* No wildcards allowed if UNC name             */
      }
     else if (*lpstr == '/')
      {
        nFileOffset = PARSE_INVALIDDIRCHAR;
        goto FAILURE;
      }
   }
  else if (*lpstr == chPeriod)
   {
     if (*++lpstr == chPeriod)  /* Is this up one directory? */
        ++lpstr;
     if (!*lpstr)
        goto MustBeDir;
     if ((*lpstr != '\\') && (*lpstr != '/'))
      {
        nFileOffset = PARSE_INVALIDPERIOD;
        goto FAILURE;
      }
     else
        ++lpstr;   /* it's saying directory, thus allowed */
   }

  if (!*lpstr)
   {
     goto MustBeDir;
   }

/* Should point to first char in 8.3 filename by now */
  nFileOffset = nExtOffset = nFile = nExt = 0;
  bWildcard = bExt = FALSE;
  while (*lpstr)
   {
/*
 *  The next comparison MUST be unsigned to allow for extended characters!
 *  21 Feb 1991   clarkc
 */
     if (*lpstr < chSpace)
      {
        nFileOffset = PARSE_INVALIDCHAR;
        goto FAILURE;
      }
     switch (*lpstr)
      {
        case '"':             /* All invalid */
        case '+':
        case ',':
        case ':':
        case ';':
        case '<':
        case '=':
        case '>':
        case '[':
        case ']':
        case '|':
         {
           nFileOffset = PARSE_INVALIDCHAR;
           goto FAILURE;
         }

        case '\\':      /* Subdirectory indicators */
        case '/':
         nNetwork++;
         if (bWildcard)
           {
            nFileOffset = PARSE_WILDCARDINDIR;
            goto FAILURE;
           }

         else if (nFile == 0)        /* can't have 2 in a row */
           {
            nFileOffset = PARSE_INVALIDDIRCHAR;
            goto FAILURE;
           }
         else
           {                         /* reset flags */
            ++lpstr;
            if (!nNetwork && !*lpstr)
              {
               nFileOffset = PARSE_INVALIDNETPATH;
               goto FAILURE;
              }
            nFile = nExt = 0;
            bExt = FALSE;
           }
         break;

        case chSpace:
         {
           LPSTR lpSpace = lpstr;

           *lpSpace = '\0';
           while (*++lpSpace)
            {
              if (*lpSpace != chSpace)
               {
                 *lpstr = chSpace;        /* Reset string, abandon ship */
                 nFileOffset = PARSE_INVALIDSPACE;
                 goto FAILURE;
               }
            }
         }
         break;

        case chPeriod:
         if (nFile == 0)
           {
            if (*++lpstr == chPeriod)
               ++lpstr;
            if (!*lpstr)
               goto MustBeDir;

            if ((*lpstr != '\\') && (*lpstr != '/'))
              {
               nFileOffset = PARSE_INVALIDPERIOD;
               goto FAILURE;
              }

            ++lpstr;              /* Flags are already set */
           }
         else if (bExt)
           {
            nFileOffset = PARSE_INVALIDPERIOD;  /* can't have one in ext */
            goto FAILURE;
           }
         else
           {
            nExtOffset = 0;
            ++lpstr;
            bExt = TRUE;
           }
         break;

        case '*':
        case '?':
         if (bUNCPath)
           {
            nFileOffset = PARSE_INVALIDNETPATH;
            goto FAILURE;
           }
         bWildcard = TRUE;
/* Fall through to normal character processing */

        default:
         if (bExt)
           {
            if (++nExt == 1)
               nExtOffset = lpstr - lpstrFileName;
            else if (nExt > 3)
              {
               nFileOffset = PARSE_EXTENTIONTOOLONG;
               goto FAILURE;
              }
            if ((nNetwork == -1) && (nFile + nExt > 11))
              {
               nFileOffset = PARSE_INVALIDNETPATH;
               goto FAILURE;
              }
           }
         else if (++nFile == 1)
            nFileOffset = lpstr - lpstrFileName;
         else if (nFile > 8)
           {
            /* If it's a server name, it can have 11 characters */
            if (nNetwork != -1)
              {
               nFileOffset = PARSE_FILETOOLONG;
               goto FAILURE;
              }
            else if (nFile > 11)
              {
               nFileOffset = PARSE_INVALIDNETPATH;
               goto FAILURE;
              }
           }

         lpstr = AnsiNext(lpstr);
         break;
      }
   }

/* Did we start with a double backslash but not have any more slashes? */
  if (nNetwork == -1)
   {
     nFileOffset = PARSE_INVALIDNETPATH;
     goto FAILURE;
   }

  if (!nFile)
   {
MustBeDir:
     nFileOffset = PARSE_DIRECTORYNAME;
     goto FAILURE;
   }

  if ((*(lpstr - 1) == chPeriod) &&          /* if true, no extention wanted */
           (*AnsiNext(lpstr-2) == chPeriod))
     *(lpstr - 1) = '\0';               /* Remove terminating period   */
  else if (!nExt)
FAILURE:
     nExtOffset = lpstr - lpstrFileName;

  return(MAKELONG(nFileOffset, nExtOffset));
}

/*
 * DoesFileExist
 *
 * Purpose:
 *  Determines if a file path exists
 *
 * Parameters:
 *  lpszFile        LPSTR - file name
 *  lpOpenBuf       OFSTRUCT FAR* - points to the OFSTRUCT structure that
 *                      will receive information about the file when the
 *                      file is first opened. this field is filled by the
 *                      Windows OpenFile API.
 *
 * Return Value:
 *  HFILE   HFILE_ERROR - file does NOT exist
 *          file handle (as returned from OpenFile) - file exists
 */
HFILE WINAPI DoesFileExist(LPSTR lpszFile, OFSTRUCT FAR* lpOpenBuf)
{
   long        nRet;
   int         i;
   static char *arrIllegalNames[] = {
      "LPT1",
      "LPT2",
      "LPT3",
      "COM1",
      "COM2",
      "COM3",
      "COM4",
      "CON",
      "AUX",
      "PRN"
   };

   // Check if file name is syntactically correct.
   //   (OpenFile sometimes crashes if path is not syntactically correct)
   nRet = ParseFile(lpszFile);
   if (LOWORD(nRet) < 0)
      goto error;

   // Check is the name is an illegal name (eg. the name of a device)
   for (i=0; i < (sizeof(arrIllegalNames)/sizeof(arrIllegalNames[0])); i++) {
      if (lstrcmpi(lpszFile, arrIllegalNames[i])==0)

goto error; // illegal name FOUND
   }
   return OpenFile(lpszFile, lpOpenBuf, OF_EXIST);

error:
   _fmemset(lpOpenBuf, 0, sizeof(OFSTRUCT));
   lpOpenBuf->nErrCode = 0x0002;   // File not found
   return HFILE_ERROR;
}


UTILITY.H

/*
 * UTILITY.H
 *
 * Miscellaneous prototypes and definitions for OLE UI dialogs.
 *
 * Copyright (c)1992-1996 Microsoft Corporation, All Right Reserved
 */

#ifndef _UTILITY_H_
#define _UTILITY_H_

//Function prototypes
//UTILITY.C
HCURSOR  WINAPI HourGlassOn(void);
void     WINAPI HourGlassOff(HCURSOR);

BOOL     WINAPI Browse(HWND, LPSTR, LPSTR, UINT, UINT, DWORD);
int      WINAPI ReplaceCharWithNull(LPSTR, int);
int      WINAPI ErrorWithFile(HWND, HINSTANCE, UINT, LPSTR, UINT);
HFILE WINAPI DoesFileExist(LPSTR lpszFile, OFSTRUCT FAR* lpOpenBuf);

HICON FAR PASCAL    HIconAndSourceFromClass(REFCLSID, LPSTR, UINT FAR *);
BOOL FAR PASCAL FIconFileFromClass(REFCLSID, LPSTR, UINT, UINT FAR *);
LPSTR FAR PASCAL PointerToNthField(LPSTR, int, char);
BOOL FAR PASCAL GetAssociatedExecutable(LPSTR, LPSTR);
HICON    WINAPI HIconFromClass(LPSTR);
BOOL     WINAPI FServerFromClass(LPSTR, LPSTR, UINT);
UINT     WINAPI UClassFromDescription(LPSTR, LPSTR, UINT);
UINT     WINAPI UDescriptionFromClass(LPSTR, LPSTR, UINT);
BOOL     WINAPI FVerbGet(LPSTR, UINT, LPSTR);
LPSTR    WINAPI ChopText(HWND hwndStatic, int nWidth, LPSTR lpch);
void     WINAPI OpenFileError(HWND hDlg, UINT nErrCode, LPSTR lpszFile);

#endif //_UTILITY_H_


WN_DOS.H

/*************************************************************************
**
**    OLE 2.0 Property Set Utilities
**
**    wn_dos.h
**
**    This file contains file contains data structure defintions,
**    function prototypes, constants, etc. for Windows 3.x form of
**    DOS calls. This is used by the SUMINFO OLE 2.0 Property Set
**    utilities used to manage the Summary Info property set.
**
**    (c) Copyright Microsoft Corp. 1990 - 1996 All Rights Reserved
**
*************************************************************************/

#ifndef WN_DOS_H
#define WN_DOS_H

#include <dos.h>

#define WIN 1

#define cbMaxFile 146 //from inc\path.h
#define SEEK_FROM_BEGINNING 0
#define SEEK_FROM_END 2
#define chDOSPath ('\\')        // FUTURE: not used all places it could be
#define chDOSWildAll    '*'     /* DOS File name wild card. */
#define chDOSWildSingle '?'

// Close, seek, delete, rename, flush, get attributes, read, write
/* RPC TEMP
int  FCloseOsfnWin(WORD);
#define FCloseOsfn(osfn)    FCloseOsfnWin(osfn)
long DwSeekDwWin(WORD,LONG,WORD);
#define DwSeekDw(osfn, dwSeek, bSeekFrom)   DwSeekDwWin(osfn, dwSeek, bSeekFrom)
EC  EcDeleteSzFfnameWin(char *);
#define EcDeleteSzFfname(szFile) EcDeleteSzFfnameWin(szFile)
EC  EcRenameSzFfnameWin(char *,char *);
#define EcRenameSzFfname(szFileCur,szFileNew) EcRenameSzFfnameWin(szFileCur,szFileNew)
int FFlushOsfnWin(int);
#define FFlushOsfn(osfn)    FFlushOsfnWin(osfn)
WORD DaGetFileModeSzWin(char *);
#define DaGetFileModeSz(szFile) DaGetFileModeSzWin(szFile)
int  CbReadOsfnWin(int, void far *, UINT);
int  CbWriteOsfnWin(int, void far *, UINT);
#define CbWriteOsfn(osfn,lpch,cbWrite)  CbWriteOsfnWin(osfn,lpch,cbWrite)
*/
#define WinOpenFile(sz,ofs,n)   OpenFile(sz,ofs,n)
#define SeekHfile(f,off,kind) _llseek(f,off,kind)
#define CbReadOsfn(osfn,lpch,cbRead)    CbReadOsfnWin(osfn,lpch,cbRead)
#define CbReadHfile(f,buf,n) _lread(f,buf,n)
#define CbReadOsfnWin(f,buf,n) CbReadHfile(f,buf,n)
#define EcFindFirst4dm(a,b,c) _dos_findfirst((const char *)(b),c,(struct find_t*)a)
#define EcFindNext4dm(a) _dos_findnext((struct find_t*)a)
#define FHfileToSffsDate(handle,date,time) _dos_getftime(handle, (unsigned *)(date), (unsigned *)(time))
#define SeekHfile(f, off, kind) _llseek(f,off,kind)

/* buffer structure to be used with EcFindFirst() and EcFindNext() */
typedef struct _SFFS
{ /* Search Find File Structure */
uchar buff[21]; // dos search info
uchar wAttr;
union
{
unsigned short timeVariable;    /*RPC47*/
BF time:16;
struct
{
BF sec : 5;
BF mint: 6;
BF hr  : 5;
};
};
union
{
unsigned short dateVariable;
BF date:16;
struct
{
BF dom : 5;
BF mon : 4;
BF yr  : 7;
};
};
ulong cbFile;
uchar szFileName[13];
} SFFS;

// find first file/find next file
#define PszFromPsffs(psffs)     ((psffs)->szFileName)
#define CopySzFilePsffs(psffs,sz)   OemToAnsi((char HUGE *)&((psffs)->szFileName[0]),(char HUGE *)(sz))
#define CbSzFilePsffs(psffs)    CbSz((psffs)->szFileName)
#define CbFileSizePsffs(psffs)  (psffs)->cbFile
#define AttribPsffs(psffs)      (psffs)->wAttr
#define EcFindFirstCore(psffs, sz, wAttr) EcFindFirst(psffs, sz, wAttr)   /*RPC22*/
#define FDotPsffs(psffs) ((psffs)->szFileName[0]=='.')   /*RPC23*/
#define AppendSzWild(sz) {int i=_fstrlen((char FAR *)(sz)); sz[i]='*'; sz[i+1]='.'; sz[i+2]='*'; sz[i+3]='\0';}
// disk free space

unsigned long LcbDiskFreeSpaceWin(int);
#define LcbDiskFreeSpace(chDrive) LcbDiskFreeSpaceWin(chDrive)

// date and time    /*RPC39*/
/*
typedef struct _TIM {                    // Time structure returned by OsTime
CHAR minutes, hour, hsec, sec;
} TIM;

typedef struct _DAT {                    // Date structure returned by OsDate
int  year;
CHAR month, day, dayOfWeek;
} DAT;
*/
#define TIM dostime_t    /*RPC39*/
#define DAT dosdate_t
#define OsTimeWin(TIM) _dos_gettime(TIM)
#define OsDateWin(DAT) _dos_getdate(DAT)

/* DOS File Attributes */
#define DA_NORMAL       0x00
#define DA_READONLY     0x01
#define DA_HIDDEN       0x02
#define DA_SYSTEM       0x04
#define DA_VOLUME       0x08
#define DA_SUBDIR       0x10
#define DA_ARCHIVE      0x20
#define DA_NIL          0xFFFF  /* Error DA */
#define dosxSharing     32      /* Extended error code for sharing viol. */
#define nErrNoAcc       5       /* OpenFile error code for Access Denied */
#define nErrFnf         2       /* OpenFile error code for File Not Found */

/* Components of the Open mode for OpenSzFfname (DOS FUNC 3DH) */
#define MASK_fINH       0x80
#define MASK_bSHARE     0x70
#define MASK_bACCESS    0x07

#define bSHARE_DENYRDWR 0x10
#define bSHARE_DENYWR   0x20
#define bSHARE_DENYNONE 0x40

/* Seek-from type codes passed to DOS function 42H */

#define SF_BEGINNING    0       /* Seek from beginning of file */
#define SF_CURRENT      1       /* Seek from current file pointer */
#define SF_END          2       /* Seek from end of file */

typedef struct _DOSDTTM /* DOS DaTe TiMe */
{
union
{
long lDOSDttm;
struct
{
BF day: 5;
BF month:   4;
BF year:    7;
BF sec: 5;
BF mint:    6;
BF hours:   5;
} S1;
} U1;
} DOSDTTM;

int  FOsfnIsFile(int);

void DateStamp(int, LONG *,  int);
int  DosxError(void);
int  ShellExec(int, int);

#endif //WN_DOS_H


README.TXT

OLE 2.0 Outline Sample Code

SUMMARY
=======

The OUTLINE sample application set demonstrates taking a 'base' application
(in this case OUTLINE.EXE) and extending it into an OLE 2 server and
container (SVROUTL and CNTROUTL respectively) and into an OLE 2 in-place
(aka visual editing) container and server (ICNTROTL and ISVROTL).

These applications attempt to implement the complete OLE 2 functionality
and recommended user model. For example, these applications implement all
of the OLE 2 User Interface dialog boxes. As such, this is not a trivial
sample application set.

All OUTLINE sample applications support loading and saving files; they all
use docfiles for storage.

MORE INFORMATION
================

The OUTLINE sample uses the following directory structure:

                        OUTLINE
                           |
       +---------+---------+--------+--------+
       |         |         |        |        |
    OUTLINE   SVROUTL   CNTROUTL ISVROTL  ICNTROTL

All five variations of the OUTLINE applications are built from common
sources. All of the common source code (*.C and *.H files) is in the
OUTLINE directory. Each variant of the application builds under its own
subdirectory for this release.

Files that begin with OUTL comprise the base version of the application.
They are also used by OLE versions of the applicaton. Files that begin with
CNTR are specific to the container version and files that begin with SVR are
specific to the server version. Files that begin with OLE are common to both
the container and server versions. This series of sample applications
highlights the changes necessary to implement OLE into an existing
application. The container version has the same functionality as the base
(OUTLINE) and also allows you to embed objects.

Some of the major OLE features are organized into special
files in order to group related code across the application variants:

File         Description

DRAGDROP.C   Drag/Drop implementation.
CLIPBRD.C    OLE 2 style copy/paste implementation (data transfer).
LINKING.C    Linking and Moniker support implementation.
CNTRINPL.C   In-place container implementation.
SVRINPL.C    In-place server implementation.
CLASSFAC.C   IClassFactory implementation.

Conditional compilation is used to control what code is used with which
version. The following defines are used to control the compilation:

Define       Description

OLE_VERSION  Code common to all OLE versions.
OLE_SERVER   Code used by both SVROUTL and ISVROTL.
OLE_CNTR     Code used by both CNTROUTL and ICNTROTL.
INPLACE_CNTR Code used by ICNTROTL only.

The following defines are used to identify code specific for a particular
feature. NOTE: It is not really intended that these symbols should be
turned off.

Define          Description

USE_DRAGDROP    Drag/drop code.
USE_FRAMETOOLS  Formula bar and tool bar.
USE_HEADING     Row/column headings code.
USE_STATUSBAR   Status bar code.
USE_CTL3D       Use CTL3D.DLL to have 3D effect for dialogs.
USE_MSGFILTER   IMessageFilter code.
_DEBUG          Debug code.

HOW TO BUILD
------------

To build, simply run "nmake" from the root of the outline directory.

You must first have built SDKUTIL.LIB, OLESTD.LIB, BTTNCUR.LIB,
BTTNCUR.DLL, GIZMOBAR.LIB and GIZMOBAR.DLL. To build these files, run
"nmake" in each of the COMMON, BTTNCUR, GIZMOBAR and OLESTD directories
under the OLE sample directory (in that order).

All the outline samples are copied into the OLE\BIN subdirectory after
compilation.

You will want to load the OUTLINE.REG file into the system registry before
attempting to embed any of the outline objects.


CLASSFAC.C

/*************************************************************************
**
**    OLE 2 Sample Code
**
**    classfac.c
**
**    This file contains the implementation for IClassFactory for both the
**    server and the client version of the OUTLINE app.
**
**    (c) Copyright Microsoft Corp. 1992 - 1997 All Rights Reserved
**
*************************************************************************/

#include "outline.h"

OLEDBGDATA

extern LPOUTLINEAPP             g_lpApp;

/* NOTE: this object illustrates the manner in which to statically
**    (compile-time) initialize an interface VTBL.
*/
static IClassFactoryVtbl g_AppClassFactoryVtbl = {
        AppClassFactory_QueryInterface,
        AppClassFactory_AddRef,
        AppClassFactory_Release,
        AppClassFactory_CreateInstance,
        AppClassFactory_LockServer
};

/* AppClassFactory_Create
** ----------------------
**    create an instance of APPCLASSFACTORY.
**    NOTE: type of pointer returned is an IClassFactory* interface ptr.
**          the returned pointer can be directly passed to
**          CoRegisterClassObject and released later by calling the
**          Release method of the interface.
*/
LPCLASSFACTORY WINAPI AppClassFactory_Create(void)
{
        LPAPPCLASSFACTORY lpAppClassFactory;
        LPMALLOC lpMalloc;

        if (CoGetMalloc(MEMCTX_TASK, (LPMALLOC FAR*)&lpMalloc) != NOERROR)
                return NULL;

        lpAppClassFactory = (LPAPPCLASSFACTORY)lpMalloc->lpVtbl->Alloc(
                        lpMalloc, (sizeof(APPCLASSFACTORY)));
        lpMalloc->lpVtbl->Release(lpMalloc);
        if (! lpAppClassFactory) return NULL;

        lpAppClassFactory->m_lpVtbl = &g_AppClassFactoryVtbl;
        lpAppClassFactory->m_cRef   = 1;
#if defined( _DEBUG )
        lpAppClassFactory->m_cSvrLock = 0;
#endif
        return (LPCLASSFACTORY)lpAppClassFactory;
}

/*************************************************************************
** OleApp::IClassFactory interface implementation
*************************************************************************/

STDMETHODIMP AppClassFactory_QueryInterface(
                LPCLASSFACTORY lpThis, REFIID riid, LPVOID FAR* ppvObj)
{
        LPAPPCLASSFACTORY lpAppClassFactory = (LPAPPCLASSFACTORY)lpThis;
        SCODE scode;

        // Two interfaces supported: IUnknown, IClassFactory

        if (IsEqualIID(riid, &IID_IClassFactory) ||
                        IsEqualIID(riid, &IID_IUnknown)) {
                lpAppClassFactory->m_cRef++;   // A pointer to this object is returned
                *ppvObj = lpThis;
                scode = S_OK;
        }
        else {                 // unsupported interface
                *ppvObj = NULL;
                scode = E_NOINTERFACE;
        }

        return scode;
}

STDMETHODIMP_(ULONG) AppClassFactory_AddRef(LPCLASSFACTORY lpThis)
{
        LPAPPCLASSFACTORY lpAppClassFactory = (LPAPPCLASSFACTORY)lpThis;
        return ++lpAppClassFactory->m_cRef;
}

STDMETHODIMP_(ULONG) AppClassFactory_Release(LPCLASSFACTORY lpThis)
{
        LPAPPCLASSFACTORY lpAppClassFactory = (LPAPPCLASSFACTORY)lpThis;
        LPMALLOC lpMalloc;

        if (--lpAppClassFactory->m_cRef != 0) // Still used by others
                return lpAppClassFactory->m_cRef;

        // Free storage
        if (CoGetMalloc(MEMCTX_TASK, (LPMALLOC FAR*)&lpMalloc) != NOERROR)
                return 0;

        lpMalloc->lpVtbl->Free(lpMalloc, lpAppClassFactory);
        lpMalloc->lpVtbl->Release(lpMalloc);
        return 0;
}

STDMETHODIMP AppClassFactory_CreateInstance (
                LPCLASSFACTORY      lpThis,
                LPUNKNOWN           lpUnkOuter,
                REFIID              riid,
                LPVOID FAR*         lplpvObj
)
{
        LPOUTLINEAPP    lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
        LPOLEDOC        lpOleDoc;
        HRESULT         hrErr;

        OLEDBG_BEGIN2("AppClassFactory_CreateInstance\r\n")

        /* NOTE: we must make sure to set all out parameters to NULL. */
        *lplpvObj = NULL;

        /*********************************************************************
        ** NOTE: this is an SDI app; it can only create and support one
        **    instance. After the instance is created, the OLE libraries
        **    should not call CreateInstance again. it is a good practise
        **    to specifically guard against this.
        *********************************************************************/

        if (lpOutlineApp->m_lpDoc != NULL)
                return E_UNEXPECTED;

        /* NOTE: create a new document instance. by the time we return
        **    from this method the document's refcnt must be 1.
        */
        lpOutlineApp->m_lpDoc = OutlineApp_CreateDoc(lpOutlineApp, FALSE);
        lpOleDoc = (LPOLEDOC)lpOutlineApp->m_lpDoc;
        if (! lpOleDoc) {
                OLEDBG_END2
                return E_OUTOFMEMORY;
        }

        /* NOTE: retrieve pointer to requested interface. the ref cnt
        **    of the object after OutlineApp_CreateDoc is 0. this call to
        **    QueryInterface will increment the refcnt to 1. the object
        **    returned from IClassFactory::CreateInstance should have a
        **    refcnt of 1 and be controlled by the caller. If the caller
        **    releases the document, the document should be destroyed.
        */
        hrErr = OleDoc_QueryInterface(lpOleDoc, riid, lplpvObj);

        OLEDBG_END2
        return hrErr;
}

STDMETHODIMP AppClassFactory_LockServer (
                LPCLASSFACTORY      lpThis,
                BOOL                fLock
)
{
        LPAPPCLASSFACTORY lpAppClassFactory = (LPAPPCLASSFACTORY)lpThis;
        LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;
        HRESULT hrErr;
        OLEDBG_BEGIN2("AppClassFactory_LockServer\r\n")

#if defined( _DEBUG )
        if (fLock) {
                ++lpAppClassFactory->m_cSvrLock;
                OleDbgOutRefCnt3(
                                "AppClassFactory_LockServer: cLock++\r\n",
                                lpAppClassFactory, lpAppClassFactory->m_cSvrLock);
        } else {

                /* NOTE: when there are no open documents and the app is not
                **    under the control of the user and there are no outstanding
                **    locks on the app, then revoke our ClassFactory to enable the
                **    app to shut down.
                */
                --lpAppClassFactory->m_cSvrLock;
                OleDbgAssertSz (lpAppClassFactory->m_cSvrLock >= 0,
                                "AppClassFactory_LockServer(FALSE) called with cLock == 0"
                );

                if (lpAppClassFactory->m_cSvrLock == 0) {
                        OleDbgOutRefCnt2(
                                        "AppClassFactory_LockServer: UNLOCKED\r\n",
                                        lpAppClassFactory, lpAppClassFactory->m_cSvrLock);
                } else {
                        OleDbgOutRefCnt3(
                                        "AppClassFactory_LockServer: cLock--\r\n",
                                        lpAppClassFactory, lpAppClassFactory->m_cSvrLock);
                }
        }
#endif  // _DEBUG
        /* NOTE: in order to hold the application alive we call
        **    CoLockObjectExternal to add a strong reference to our app
        **    object. this will keep the app alive when all other external
        **    references release us. if the user issues File.Exit the
        **    application will shut down in any case ignoring any
        **    outstanding LockServer locks because CoDisconnectObject is
        **    called in OleApp_CloseAllDocsAndExitCommand. this will
        **    forceably break any existing strong reference counts
        **    including counts that we add ourselves by calling
        **    CoLockObjectExternal and guarantee that the App object gets
        **    its final release (ie. cRefs goes to 0).
        */
        hrErr = OleApp_Lock(lpOleApp, fLock, TRUE /* fLastUnlockReleases */);

        OLEDBG_END2
        return hrErr;
}


CLIPBRD.C

/*************************************************************************
**
**    OLE 2 Sample Code
**
**    clipbrd.c
**
**    This file contains the major interfaces, methods and related support
**    functions for implementing clipboard data transfer. The code
**    contained in this file is used by BOTH the Container and Server
**    (Object) versions of the Outline sample code.
**    (see file dragdrop.c for Drag/Drop support implementation)
**
**    OleDoc Object
**      exposed interfaces:
**          IDataObject
**
**    (c) Copyright Microsoft Corp. 1992 - 1997 All Rights Reserved
**
*************************************************************************/

#include "outline.h"

OLEDBGDATA

extern LPOUTLINEAPP             g_lpApp;

// REVIEW: should use string resource for messages
OLECHAR ErrMsgPasting[] = OLESTR("Could not paste data from clipboard!");
OLECHAR ErrMsgBadFmt[] = OLESTR("Invalid format selected!");
OLECHAR ErrMsgPasteFailed[] = OLESTR("Could not paste data from clipboard!");
OLECHAR ErrMsgClipboardChanged[] = OLESTR("Contents of clipboard have changed!\r\nNo paste performed.");

#ifdef WIN32S
#define GALLOCFLG (GMEM_SHARE | GMEM_ZEROINIT | GMEM_MOVEABLE)
#else
#define GALLOCFLG (GMEM_SHARE | GMEM_ZEROINIT)
#endif

/*************************************************************************
** OleDoc::IDataObject interface implementation
*************************************************************************/

// IDataObject::QueryInterface
STDMETHODIMP OleDoc_DataObj_QueryInterface (
      LPDATAOBJECT        lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
)
{
   LPOLEDOC lpOleDoc = ((struct CDocDataObjectImpl FAR*)lpThis)->lpOleDoc;

   return OleDoc_QueryInterface((LPOLEDOC)lpOleDoc, riid, lplpvObj);
}

// IDataObject::AddRef
STDMETHODIMP_(ULONG) OleDoc_DataObj_AddRef(LPDATAOBJECT lpThis)
{
   LPOLEDOC lpOleDoc = ((struct CDocDataObjectImpl FAR*)lpThis)->lpOleDoc;

   OleDbgAddRefMethod(lpThis, "IDataObject");

   return OleDoc_AddRef((LPOLEDOC)lpOleDoc);
}

// IDataObject::Release
STDMETHODIMP_(ULONG) OleDoc_DataObj_Release (LPDATAOBJECT lpThis)
{
   LPOLEDOC lpOleDoc = ((struct CDocDataObjectImpl FAR*)lpThis)->lpOleDoc;

   OleDbgReleaseMethod(lpThis, "IDataObject");

   return OleDoc_Release((LPOLEDOC)lpOleDoc);
}

// IDataObject::GetData
STDMETHODIMP OleDoc_DataObj_GetData (
      LPDATAOBJECT        lpThis,
      LPFORMATETC         lpFormatetc,
      LPSTGMEDIUM         lpMedium
)
{
   LPOLEDOC lpOleDoc = ((struct CDocDataObjectImpl FAR*)lpThis)->lpOleDoc;
   HRESULT hrErr;

   OLEDBG_BEGIN2("OleDoc_DataObj_GetData\r\n")

#if defined( OLE_SERVER )
   // Call OLE Server specific version of this function
   hrErr = ServerDoc_GetData((LPSERVERDOC)lpOleDoc, lpFormatetc, lpMedium);
#endif
#if defined( OLE_CNTR )
   // Call OLE Container specific version of this function
   hrErr = ContainerDoc_GetData(
         (LPCONTAINERDOC)lpOleDoc,
         lpFormatetc,
         lpMedium
   );
#endif

   OLEDBG_END2
   return hrErr;
}

// IDataObject::GetDataHere
STDMETHODIMP OleDoc_DataObj_GetDataHere (
      LPDATAOBJECT        lpThis,
      LPFORMATETC         lpFormatetc,
      LPSTGMEDIUM         lpMedium
)
{
   LPOLEDOC lpOleDoc = ((struct CDocDataObjectImpl FAR*)lpThis)->lpOleDoc;
   HRESULT hrErr;

   OLEDBG_BEGIN2("OleDoc_DataObj_GetDataHere\r\n")

#if defined( OLE_SERVER )
   // Call OLE Server specific version of this function
   hrErr = ServerDoc_GetDataHere(
         (LPSERVERDOC)lpOleDoc,
         lpFormatetc,
         lpMedium
   );
#endif
#if defined( OLE_CNTR )
   // Call OLE Container specific version of this function
   hrErr = ContainerDoc_GetDataHere(
         (LPCONTAINERDOC)lpOleDoc,
         lpFormatetc,
         lpMedium
   );
#endif

   OLEDBG_END2
   return hrErr;
}

// IDataObject::QueryGetData
STDMETHODIMP OleDoc_DataObj_QueryGetData (
      LPDATAOBJECT        lpThis,
      LPFORMATETC         lpFormatetc
)
{
   LPOLEDOC lpOleDoc = ((struct CDocDataObjectImpl FAR*)lpThis)->lpOleDoc;
   HRESULT hrErr;
   OLEDBG_BEGIN2("OleDoc_DataObj_QueryGetData\r\n");

#if defined( OLE_SERVER )
   // Call OLE Server specific version of this function
   hrErr = ServerDoc_QueryGetData((LPSERVERDOC)lpOleDoc, lpFormatetc);
#endif
#if defined( OLE_CNTR )
   // Call OLE Container specific version of this function
   hrErr = ContainerDoc_QueryGetData((LPCONTAINERDOC)lpOleDoc, lpFormatetc);
#endif

   OLEDBG_END2
   return hrErr;
}

// IDataObject::GetCanonicalFormatEtc
STDMETHODIMP OleDoc_DataObj_GetCanonicalFormatEtc(
      LPDATAOBJECT        lpThis,
      LPFORMATETC         lpformatetc,
      LPFORMATETC         lpformatetcOut
)
{
   HRESULT hrErr;
   OleDbgOut2("OleDoc_DataObj_GetCanonicalFormatEtc\r\n");

   if (!lpformatetcOut)
      return E_INVALIDARG;

   /* NOTE: we must make sure to set all out parameters to NULL. */
   lpformatetcOut->ptd = NULL;

   if (!lpformatetc)
      return E_INVALIDARG;

   // NOTE: we must validate that the format requested is supported
   if ((hrErr=lpThis->lpVtbl->QueryGetData(lpThis,lpformatetc)) != NOERROR)
      return hrErr;

   /* NOTE: an app that is insensitive to target device (as the
   **    Outline Sample is) should fill in the lpformatOut parameter
   **    but NULL out the "ptd" field; it should return NOERROR if the
   **    input formatetc->ptd what non-NULL. this tells the caller
   **    that it is NOT necessary to maintain a separate screen
   **    rendering and printer rendering. if should return
   **    DATA_S_SAMEFORMATETC if the input and output formatetc's are
   **    identical.
   */

   *lpformatetcOut = *lpformatetc;
   if (lpformatetc->ptd == NULL)
      return DATA_S_SAMEFORMATETC;
   else {
      lpformatetcOut->ptd = NULL;
      return NOERROR;
   }
}

// IDataObject::SetData
STDMETHODIMP OleDoc_DataObj_SetData (
      LPDATAOBJECT    lpThis,
      LPFORMATETC     lpFormatetc,
      LPSTGMEDIUM     lpMedium,
      BOOL            fRelease
)
{
   LPOLEDOC lpOleDoc = ((struct CDocDataObjectImpl FAR*)lpThis)->lpOleDoc;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   SCODE sc = S_OK;
   OLEDBG_BEGIN2("OleDoc_DataObj_SetData\r\n")

   /* NOTE: a document that is used to transfer data (either via
   **    the clipboard or drag/drop) does NOT accept SetData on ANY
   **    format!
   */
   if (lpOutlineDoc->m_fDataTransferDoc) {
      sc = E_FAIL;
      goto error;
   }

#if defined( OLE_SERVER )
   if (lpFormatetc->cfFormat == lpOutlineApp->m_cfOutline) {
      OLEDBG_BEGIN2("ServerDoc_SetData: CF_OUTLINE\r\n")
      OutlineDoc_SetRedraw ( lpOutlineDoc, FALSE );
      OutlineDoc_ClearAllLines(lpOutlineDoc);
      OutlineDoc_PasteOutlineData(lpOutlineDoc,lpMedium->u.hGlobal,-1);
      OutlineDoc_SetRedraw ( lpOutlineDoc, TRUE );
      OLEDBG_END3
   } else if (lpFormatetc->cfFormat == CF_TEXT) {
      OLEDBG_BEGIN2("ServerDoc_SetData: CF_TEXT\r\n")
      OutlineDoc_SetRedraw ( lpOutlineDoc, FALSE );
      OutlineDoc_ClearAllLines(lpOutlineDoc);
      OutlineDoc_PasteTextData(lpOutlineDoc,lpMedium->u.hGlobal,-1);
      OutlineDoc_SetRedraw ( lpOutlineDoc, TRUE );
      OLEDBG_END3
   } else {
      sc = DV_E_FORMATETC;
   }
#endif  // OLE_SERVER
#if defined( OLE_CNTR )
   /* the Container-Only version of Outline does NOT offer
   **    IDataObject interface from its User documents. this is
   **    required by objects which can be embedded or linked. the
   **    Container-only app only allows linking to its contained
   **    objects, NOT the data of the container itself.
   */
   OleDbgAssertSz(0, "User documents do NOT support IDataObject\r\n");
   sc = E_NOTIMPL;
#endif  // OLE_CNTR

error:

   /* NOTE: if fRelease==TRUE, then we must take
   **    responsibility to release the lpMedium. we should only do
   **    this if we are going to return NOERROR. if we do NOT
   **    accept the data, then we should NOT release the lpMedium.
   **    if fRelease==FALSE, then the caller retains ownership of
   **    the data.
   */
   if (sc == S_OK && fRelease)
      ReleaseStgMedium(lpMedium);

   OLEDBG_END2
   return sc;

}

// IDataObject::EnumFormatEtc
STDMETHODIMP OleDoc_DataObj_EnumFormatEtc(
      LPDATAOBJECT            lpThis,
      DWORD                   dwDirection,
      LPENUMFORMATETC FAR*    lplpenumFormatEtc
)
{
   LPOLEDOC lpOleDoc=((struct CDocDataObjectImpl FAR*)lpThis)->lpOleDoc;
   HRESULT hrErr;

   OLEDBG_BEGIN2("OleDoc_DataObj_EnumFormatEtc\r\n")

   /* NOTE: we must make sure to set all out parameters to NULL. */
   *lplpenumFormatEtc = NULL;

#if defined( OLE_SERVER )
   /* NOTE: a user document only needs to enumerate the static list
   **    of formats that are registered for our app in the
   **    registration database. OLE provides a default enumerator
   **    which enumerates from the registration database. this default
   **    enumerator is requested by returning OLE_S_USEREG. it is NOT
   **    required that a user document (ie. non-DataTransferDoc)
   **    enumerate the OLE formats: CF_LINKSOURCE, CF_EMBEDSOURCE, or
   **    CF_EMBEDDEDOBJECT.
   **
   **    An object implemented as a server EXE (as this sample
   **    is) may simply return OLE_S_USEREG to instruct the OLE
   **    DefHandler to call the OleReg* helper API which uses info in
   **    the registration database. Alternatively, the OleRegEnumFormatEtc
   **    API may be called directly. Objects implemented as a server
   **    DLL may NOT return OLE_S_USEREG; they must call the OleReg*
   **    API or provide their own implementation. For EXE based
   **    objects it is more efficient to return OLE_S_USEREG, because
   **    in then the enumerator is instantiated in the callers
   **    process space and no LRPC remoting is required.
   */
   if (! ((LPOUTLINEDOC)lpOleDoc)->m_fDataTransferDoc)
      return OLE_S_USEREG;

   // Call OLE Server specific version of this function
   hrErr = ServerDoc_EnumFormatEtc(
         (LPSERVERDOC)lpOleDoc,
         dwDirection,
         lplpenumFormatEtc
   );
#endif
#if defined( OLE_CNTR )
   // Call OLE Container specific version of this function
   hrErr = ContainerDoc_EnumFormatEtc(
         (LPCONTAINERDOC)lpOleDoc,
         dwDirection,
         lplpenumFormatEtc
   );
#endif

   OLEDBG_END2
   return hrErr;
}

// IDataObject::DAdvise
STDMETHODIMP OleDoc_DataObj_DAdvise(
      LPDATAOBJECT        lpThis,
      FORMATETC FAR*      lpFormatetc,
      DWORD               advf,
      LPADVISESINK        lpAdvSink,
      DWORD FAR*          lpdwConnection
)
{
   LPOLEDOC lpOleDoc=((struct CDocDataObjectImpl FAR*)lpThis)->lpOleDoc;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;
   SCODE sc;

   OLEDBG_BEGIN2("OleDoc_DataObj_DAdvise\r\n")

   /* NOTE: we must make sure to set all out parameters to NULL. */
   *lpdwConnection = 0;

   /* NOTE: a document that is used to transfer data (either via
   **    the clipboard or drag/drop) does NOT support Advise notifications.
   */
   if (lpOutlineDoc->m_fDataTransferDoc) {
      sc = OLE_E_ADVISENOTSUPPORTED;
      goto error;
   }

#if defined( OLE_SERVER )
   {
      HRESULT hrErr;
      LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOleDoc;

      /* NOTE: we should validate if the caller is setting up an
      **    Advise for a data type that we support. we must
      **    explicitly allow an advise for the "wildcard" advise.
      */
#ifdef WIN32
      if ( !( lpFormatetc->cfFormat == 0 &&
            lpFormatetc->ptd == 0 &&
#else
      if ( !( lpFormatetc->cfFormat == NULL &&
            lpFormatetc->ptd == NULL &&
#endif
            lpFormatetc->dwAspect == -1L &&
            lpFormatetc->lindex == -1L &&
            lpFormatetc->tymed == -1L) &&
          (hrErr = OleDoc_DataObj_QueryGetData(lpThis, lpFormatetc))
                                                != NOERROR) {
         sc = hrErr;
         goto error;
      }

      if (lpServerDoc->m_OleDoc.m_fObjIsClosing)
      {
          // We don't accept any more Advies's once we're closing
          sc = OLE_E_ADVISENOTSUPPORTED;
          goto error;
      }

      if (lpServerDoc->m_lpDataAdviseHldr == NULL &&
         CreateDataAdviseHolder(&lpServerDoc->m_lpDataAdviseHldr)
                                                != NOERROR) {
            sc = E_OUTOFMEMORY;
            goto error;
      }
      // artificial AddRef in case someone releases this object
      // during the next call
      OleDoc_DataObj_AddRef(lpThis);

      OLEDBG_BEGIN2("IDataAdviseHolder::Advise called\r\n");
      hrErr = lpServerDoc->m_lpDataAdviseHldr->lpVtbl->Advise(
            lpServerDoc->m_lpDataAdviseHldr,
            (LPDATAOBJECT)&lpOleDoc->m_DataObject,
            lpFormatetc,
            advf,
            lpAdvSink,
            lpdwConnection
      );
      OLEDBG_END2

      // release artificial AddRef above
      OleDoc_DataObj_Release(lpThis);

      OLEDBG_END2
      return hrErr;
   }
#endif  // OLE_SVR
#if defined( OLE_CNTR )
   {
      /* the Container-Only version of Outline does NOT offer
      **    IDataObject interface from its User documents. this is
      **    required by objects which can be embedded or linked. the
      **    Container-only app only allows linking to its contained
      **    objects, NOT the data of the container itself.
      */
      OleDbgAssertSz(0, "User documents do NOT support IDataObject\r\n");
      sc = E_NOTIMPL;
      goto error;
   }
#endif  // OLE_CNTR

error:
   OLEDBG_END2
   return sc;
}

// IDataObject::DUnadvise
STDMETHODIMP OleDoc_DataObj_DUnadvise(LPDATAOBJECT lpThis, DWORD dwConnection)
{
   LPOLEDOC lpOleDoc=((struct CDocDataObjectImpl FAR*)lpThis)->lpOleDoc;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;
   SCODE sc;

   OLEDBG_BEGIN2("OleDoc_DataObj_DUnadvise\r\n")

   /* NOTE: a document that is used to transfer data (either via
   **    the clipboard or drag/drop) does NOT support Advise notifications.
   */
   if (lpOutlineDoc->m_fDataTransferDoc) {
      sc = OLE_E_ADVISENOTSUPPORTED;
      goto error;
   }

#if defined( OLE_SERVER )
   {
      LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOleDoc;
      HRESULT hrErr;

      if (lpServerDoc->m_lpDataAdviseHldr == NULL) {
         sc = E_FAIL;
         goto error;
      }

      // artificial AddRef in case someone releases this object
      // during the next call
      OleDoc_DataObj_AddRef(lpThis);

      OLEDBG_BEGIN2("IDataAdviseHolder::Unadvise called\r\n");
      hrErr = lpServerDoc->m_lpDataAdviseHldr->lpVtbl->Unadvise(
            lpServerDoc->m_lpDataAdviseHldr,
            dwConnection
      );
      OLEDBG_END2

      // release artifical AddRef
      OleDoc_DataObj_Release(lpThis);

      OLEDBG_END2
      return hrErr;
   }
#endif
#if defined( OLE_CNTR )
   {
      /* the Container-Only version of Outline does NOT offer
      **    IDataObject interface from its User documents. this is
      **    required by objects which can be embedded or linked. the
      **    Container-only app only allows linking to its contained
      **    objects, NOT the data of the container itself.
      */
      OleDbgAssertSz(0, "User documents do NOT support IDataObject\r\n");
      sc = E_NOTIMPL;
      goto error;
   }
#endif

error:
   OLEDBG_END2
   return sc;
}

// IDataObject::EnumDAdvise
STDMETHODIMP OleDoc_DataObj_EnumDAdvise(
      LPDATAOBJECT        lpThis,
      LPENUMSTATDATA FAR* lplpenumAdvise
)
{
   LPOLEDOC lpOleDoc=((struct CDocDataObjectImpl FAR*)lpThis)->lpOleDoc;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;
   SCODE sc;

   OLEDBG_BEGIN2("OleDoc_DataObj_EnumDAdvise\r\n")

   /* NOTE: we must make sure to set all out parameters to NULL. */
   *lplpenumAdvise = NULL;

   /* NOTE: a document that is used to transfer data (either via
   **    the clipboard or drag/drop) does NOT support Advise notifications.
   */
   if (lpOutlineDoc->m_fDataTransferDoc) {
      sc = OLE_E_ADVISENOTSUPPORTED;
      goto error;
   }

#if defined( OLE_SERVER )
   {
      LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOleDoc;
      HRESULT hrErr;

      if (lpServerDoc->m_lpDataAdviseHldr == NULL) {
         sc = E_FAIL;
         goto error;
      }

      // artificial AddRef in case someone releases this
      // object during the next call
      OleDoc_DataObj_AddRef(lpThis);

      OLEDBG_BEGIN2("IDataAdviseHolder::EnumAdvise called\r\n");
      hrErr = lpServerDoc->m_lpDataAdviseHldr->lpVtbl->EnumAdvise(
            lpServerDoc->m_lpDataAdviseHldr,
            lplpenumAdvise
      );
      OLEDBG_END2

      // release artifical AddRef
      OleDoc_DataObj_Release(lpThis);

      OLEDBG_END2
      return hrErr;
   }
#endif
#if defined( OLE_CNTR )
   {
      /* the Container-Only version of Outline does NOT offer
      **    IDataObject interface from its User documents. this is
      **    required by objects which can be embedded or linked. the
      **    Container-only app only allows linking to its contained
      **    objects, NOT the data of the container itself.
      */
      OleDbgAssertSz(0, "User documents do NOT support IDataObject\r\n");
      sc = E_NOTIMPL;
      goto error;
   }
#endif

error:
   OLEDBG_END2
   return sc;
}

/*************************************************************************
** OleDoc Supprt Functions common to both Container and Server versions
*************************************************************************/

/* OleDoc_CopyCommand
 * ------------------
 *  Copy selection to clipboard.
 *  Post to the clipboard the formats that the app can render.
 *  the actual data is not rendered at this time. using the
 *  delayed rendering technique, Windows will send the clipboard
 *  owner window either a WM_RENDERALLFORMATS or a WM_RENDERFORMAT
 *  message when the actual data is requested.
 *
 *    NOTE: the normal delayed rendering technique where Windows
 *    sends the clipboard owner window either a WM_RENDERALLFORMATS or
 *    a WM_RENDERFORMAT message when the actual data is requested is
 *    NOT exposed to the app calling OleSetClipboard. OLE internally
 *    creates its own window as the clipboard owner and thus our app
 *    will NOT get these WM_RENDER messages.
 */
void OleDoc_CopyCommand(LPOLEDOC lpSrcOleDoc)
{
   LPOUTLINEDOC lpSrcOutlineDoc = (LPOUTLINEDOC)lpSrcOleDoc;
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOUTLINEDOC lpClipboardDoc;

   /* squirrel away a copy of the current selection to the ClipboardDoc */
   lpClipboardDoc = OutlineDoc_CreateDataTransferDoc(lpSrcOutlineDoc);

   if (! lpClipboardDoc)
      return;     // Error: could not create DataTransferDoc

   lpOutlineApp->m_lpClipboardDoc = (LPOUTLINEDOC)lpClipboardDoc;

   /* NOTE: initially the Doc object is created with a 0 ref
   **    count. in order to have a stable Doc object during the
   **    process of initializing the Doc instance and transfering it
   **    to the clipboard, we intially AddRef the Doc ref cnt and later
   **    Release it. This initial AddRef is artificial; it is simply
   **    done to guarantee that a harmless QueryInterface followed by
   **    a Release does not inadvertantly force our object to destroy
   **    itself prematurely.
   */
   OleDoc_AddRef((LPOLEDOC)lpClipboardDoc);

   /* NOTE: the OLE 2.0 style to put data onto the clipboard is to
   **    give the clipboard a pointer to an IDataObject interface that
   **    is able to statisfy IDataObject::GetData calls to render
   **    data. in our case we give the pointer to the ClipboardDoc
   **    which holds a cloned copy of the current user's selection.
   */
   OLEDBG_BEGIN2("OleSetClipboard called\r\n")
   OleSetClipboard((LPDATAOBJECT)&((LPOLEDOC)lpClipboardDoc)->m_DataObject);
   OLEDBG_END2

   OleDoc_Release((LPOLEDOC)lpClipboardDoc);   // rel artificial AddRef above
}

/* OleDoc_PasteCommand
** -------------------
**    Paste default format data from the clipboard.
**    In this function we choose the highest fidelity format that the
**    source clipboard IDataObject* offers that we understand.
**
**    NOTE: clipboard handling in an OLE 2.0 application is
**    different than normal Windows clipboard handling. Data from the
**    clipboard is retieved by getting the IDataObject* pointer
**    returned by calling OleGetClipboard.
*/
void OleDoc_PasteCommand(LPOLEDOC lpOleDoc)
{
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPDATAOBJECT lpClipboardDataObj = NULL;
   BOOL fLink = FALSE;
   BOOL fLocalDataObj = FALSE;
   BOOL fStatus;
   HRESULT hrErr;

   hrErr = OleGetClipboard((LPDATAOBJECT FAR*)&lpClipboardDataObj);
   if (hrErr != NOERROR)
      return;     // Clipboard seems to be empty or can't be accessed

   OutlineDoc_SetRedraw ( lpOutlineDoc, FALSE );

   /* check if the data on the clipboard is local to our application
   **    instance.
   */
   if (lpOutlineApp->m_lpClipboardDoc) {
      LPOLEDOC lpOleDoc = (LPOLEDOC)lpOutlineApp->m_lpClipboardDoc;
      if (lpClipboardDataObj == (LPDATAOBJECT)&lpOleDoc->m_DataObject)
         fLocalDataObj = TRUE;
   }

   fStatus = OleDoc_PasteFromData(
         lpOleDoc,
         lpClipboardDataObj,
         fLocalDataObj,
         fLink
   );

   OutlineDoc_SetRedraw ( lpOutlineDoc, TRUE );

   if (! fStatus)
      OutlineApp_ErrorMessage(g_lpApp,OLESTR("Could not paste data from clipboard!"));

   if (lpClipboardDataObj)
      OleStdRelease((LPUNKNOWN)lpClipboardDataObj);
}

/* OleDoc_PasteSpecialCommand
** --------------------------
**    Allow the user to paste data in a particular format from the
**    clipboard. The paste special command displays a dialog to the
**    user that allows him to choose the format to be pasted from the
**    list of formats available.
**
**    NOTE: the PasteSpecial dialog is one of the standard OLE 2.0
**    UI dialogs for which the dialog is implemented and in the OLE2UI
**    library.
**
**    NOTE: clipboard handling in an OLE 2.0 application is
**    different than normal Windows clipboard handling. Data from the
**    clipboard is retieved by getting the IDataObject* pointer
**    returned by calling OleGetClipboard.
*/
void OleDoc_PasteSpecialCommand(LPOLEDOC lpOleDoc)
{
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;
   LPDATAOBJECT lpClipboardDataObj = NULL;
   CLIPFORMAT cfFormat;
   int nFmtEtc;
   UINT uInt;
   BOOL fLink = FALSE;
   BOOL fLocalDataObj = FALSE;
   BOOL fStatus;
   HRESULT hrErr;
   OLEUIPASTESPECIAL ouiPasteSpl;
   BOOL fDisplayAsIcon;

   hrErr = OleGetClipboard((LPDATAOBJECT FAR*)&lpClipboardDataObj);
   if (hrErr != NOERROR)
      return;     // Clipboard seems to be empty or can't be accessed

   /* check if the data on the clipboard is local to our application
   **    instance.
   */
   if (lpOutlineApp->m_lpClipboardDoc) {
      LPOLEDOC lpOleDoc = (LPOLEDOC)lpOutlineApp->m_lpClipboardDoc;
      if (lpClipboardDataObj == (LPDATAOBJECT)&lpOleDoc->m_DataObject)
         fLocalDataObj = TRUE;
   }

   /* Display the PasteSpecial dialog and allow the user to select the
   **    format to paste.
   */
   _fmemset((LPOLEUIPASTESPECIAL)&ouiPasteSpl, 0, sizeof(OLEUIPASTESPECIAL));
   ouiPasteSpl.cbStruct = sizeof(OLEUIPASTESPECIAL);       //Structure Size
   ouiPasteSpl.dwFlags =  PSF_SELECTPASTE | PSF_SHOWHELP;  //IN-OUT:  Flags
   ouiPasteSpl.hWndOwner = lpOutlineApp->m_lpDoc->m_hWndDoc; //Owning window
   ouiPasteSpl.lpszCaption = "Paste Special";    //Dialog caption bar contents
   ouiPasteSpl.lpfnHook = NULL;       //Hook callback
   ouiPasteSpl.lCustData = 0;         //Custom data to pass to hook
   ouiPasteSpl.hInstance = NULL;      //Instance for customized template name
   ouiPasteSpl.lpszTemplate = NULL;   //Customized template name
   ouiPasteSpl.hResource = NULL;      //Customized template handle

   ouiPasteSpl.arrPasteEntries = lpOleApp->m_arrPasteEntries;
   ouiPasteSpl.cPasteEntries = lpOleApp->m_nPasteEntries;
   ouiPasteSpl.lpSrcDataObj = lpClipboardDataObj;
   ouiPasteSpl.arrLinkTypes = lpOleApp->m_arrLinkTypes;
   ouiPasteSpl.cLinkTypes = lpOleApp->m_nLinkTypes;
   ouiPasteSpl.cClsidExclude = 0;

   OLEDBG_BEGIN3("OleUIPasteSpecial called\r\n")
   uInt = OleUIPasteSpecial(&ouiPasteSpl);
   OLEDBG_END3

   fDisplayAsIcon =
         (ouiPasteSpl.dwFlags & PSF_CHECKDISPLAYASICON ? TRUE : FALSE);

   if (uInt == OLEUI_OK) {
      nFmtEtc = ouiPasteSpl.nSelectedIndex;
      fLink =  ouiPasteSpl.fLink;

      if (nFmtEtc < 0 || nFmtEtc >= lpOleApp->m_nPasteEntries) {
         OutlineApp_ErrorMessage(lpOutlineApp, ErrMsgBadFmt);
         goto error;
      }

      OutlineDoc_SetRedraw ( lpOutlineDoc, FALSE );

      cfFormat = lpOleApp->m_arrPasteEntries[nFmtEtc].fmtetc.cfFormat;

      fStatus = OleDoc_PasteFormatFromData(
            lpOleDoc,
            cfFormat,
            lpClipboardDataObj,
            fLocalDataObj,
            fLink,
            fDisplayAsIcon,
            ouiPasteSpl.hMetaPict,
            (LPSIZEL)&ouiPasteSpl.sizel
      );

      OutlineDoc_SetRedraw ( lpOutlineDoc, TRUE );

      if (! fStatus) {
         OutlineApp_ErrorMessage(lpOutlineApp, ErrMsgPasteFailed);
         goto error;
      }

   } else if (uInt == OLEUI_PSERR_CLIPBOARDCHANGED) {
      /* NOTE: this error code is returned when the contents of
      **    the clipboard change while the PasteSpecial dialog is up.
      **    in this situation the PasteSpecial dialog automatically
      **    brings itself down and NO paste operation should be performed.
      */
      OutlineApp_ErrorMessage(lpOutlineApp, ErrMsgClipboardChanged);
   }

error:

   if (lpClipboardDataObj)
      OleStdRelease((LPUNKNOWN)lpClipboardDataObj);

   if (uInt == OLEUI_OK && ouiPasteSpl.hMetaPict)
      // clean up metafile
      OleUIMetafilePictIconFree(ouiPasteSpl.hMetaPict);
}

/* OleDoc_CreateDataTransferDoc
 * ----------------------------
 *
 *      Create a document to be use to transfer data (either via a
 *  drag/drop operation of the clipboard). Copy the selection of the
 *  source doc to the data transfer document. A data transfer document is
 *  the same as a document that is created by the user except that it is
 *  NOT made visible to the user. it is specially used to hold a copy of
 *  data that the user should not be able to change.
 *
 *  NOTE: in the OLE version the data transfer document is used
 *      specifically to provide an IDataObject* that renders the data copied.
 */
LPOUTLINEDOC OleDoc_CreateDataTransferDoc(LPOLEDOC lpSrcOleDoc)
{
   LPOUTLINEDOC lpSrcOutlineDoc = (LPOUTLINEDOC)lpSrcOleDoc;
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOUTLINEDOC lpDestOutlineDoc;
   LPLINELIST lpSrcLL = &lpSrcOutlineDoc->m_LineList;
   LINERANGE lrSel;
   int nCopied;

   lpDestOutlineDoc = OutlineApp_CreateDoc(lpOutlineApp, TRUE);
   if (! lpDestOutlineDoc) return NULL;

   // set the ClipboardDoc to an (Untitled) doc.
   if (! OutlineDoc_InitNewFile(lpDestOutlineDoc))
      goto error;

   LineList_GetSel(lpSrcLL, (LPLINERANGE)&lrSel);
   nCopied = LineList_CopySelToDoc(
         lpSrcLL,
         (LPLINERANGE)&lrSel,
         lpDestOutlineDoc
   );

   if (nCopied != (lrSel.m_nEndLine - lrSel.m_nStartLine + 1)) {
      OleDbgAssertSz(FALSE,"OleDoc_CreateDataTransferDoc: entire selection NOT copied\r\n");
      goto error;     // ERROR: all lines could NOT be copied
   }

#if defined( OLE_SERVER )
   {
      LPOLEDOC lpSrcOleDoc = (LPOLEDOC)lpSrcOutlineDoc;
      LPOLEDOC lpDestOleDoc = (LPOLEDOC)lpDestOutlineDoc;
      LPSERVERDOC lpDestServerDoc = (LPSERVERDOC)lpDestOutlineDoc;
      LPMONIKER lpmkDoc = NULL;
      LPMONIKER lpmkItem = NULL;

      /* If source document is able to provide a moniker, then the
      **    destination document (lpDestOutlineDoc) should offer
      **    CF_LINKSOURCE via its IDataObject interface that it gives
      **    to the clipboard or the drag/drop operation.
      **
      **    NOTE: we want to ask the source document if it can
      **    produce a moniker, but we do NOT want to FORCE moniker
      **    assignment at this point. we only want to FORCE moniker
      **    assignment later if a Paste Link occurs (ie. GetData for
      **    CF_LINKSOURCE). if the source document is able to give

**    a moniker, then we store a pointer to the source document
      **    so we can ask it at a later time to get the moniker. we
      **    also save the range of the current selection so we can
      **    generate a proper item name later when Paste Link occurs.
      **    Also we need to give a string which identifies the source
      **    of the copy in the CF_OBJECTDESCRIPTOR format. this
      **    string is used to display in the PasteSpecial dialog. we
      **    get and store a TEMPFORUSER moniker which identifies the
      **    source of copy.
      */
      lpDestOleDoc->m_lpSrcDocOfCopy = lpSrcOleDoc;
      lpmkDoc = OleDoc_GetFullMoniker(lpSrcOleDoc, GETMONIKER_TEMPFORUSER);
      if (lpmkDoc != NULL) {
         lpDestOleDoc->m_fLinkSourceAvail = TRUE;
         lpDestServerDoc->m_lrSrcSelOfCopy = lrSel;
         OleStdRelease((LPUNKNOWN)lpmkDoc);
      }
   }
#endif
#if defined( OLE_CNTR )
   {
      LPOLEDOC lpSrcOleDoc = (LPOLEDOC)lpSrcOutlineDoc;
      LPOLEDOC lpDestOleDoc = (LPOLEDOC)lpDestOutlineDoc;
      LPCONTAINERDOC lpDestContainerDoc = (LPCONTAINERDOC)lpDestOutlineDoc;

      /* If one line was copied from the source document, and it was a
      **    single OLE object, then the destination document should
      **    offer additional data formats to allow the transfer of
      **    the OLE object via IDataObject::GetData. Specifically, the
      **    following additional data formats are offered if a single
      **    OLE object is copied:
      **          CF_EMBEDDEDOBJECT
      **          CF_OBJECTDESCRIPTOR     (should be given even w/o object)
      **          CF_METAFILEPICT         (note: dwAspect depends on object)
      **          CF_LINKSOURCE           -- if linking is possible
      **          CF_LINKSOURCEDESCRIPTOR -- if linking is possible
      **
      **    optionally the container may give
      **          <data format available in OLE object's cache>
      */

      if (nCopied == 1) {
         LPOLEOBJECT lpSrcOleObj;
         LPCONTAINERLINE lpSrcContainerLine;
         DWORD dwStatus;

         lpSrcContainerLine = (LPCONTAINERLINE)LineList_GetLine(
               lpSrcLL,
               lrSel.m_nStartLine
         );

         if (! lpSrcContainerLine)
            goto error;

         lpDestOleDoc->m_lpSrcDocOfCopy = lpSrcOleDoc;

         if ((((LPLINE)lpSrcContainerLine)->m_lineType==CONTAINERLINETYPE)
               && ((lpSrcOleObj=lpSrcContainerLine->m_lpOleObj)!=NULL)) {

            lpDestContainerDoc->m_fEmbeddedObjectAvail = TRUE;
            lpSrcOleObj->lpVtbl->GetUserClassID(
                  lpSrcOleObj,
                  &lpDestContainerDoc->m_clsidOleObjCopied
            );
            lpDestContainerDoc->m_dwAspectOleObjCopied =
                     lpSrcContainerLine->m_dwDrawAspect;

            /* NOTE: if the object is allowed to be linked
            **    to from the inside (ie. we are allowed to
            **    give out a moniker which binds to the running
            **    OLE object), then we want to offer
            **    CF_LINKSOURCE format. if the object is an OLE
            **    2.0 embedded object then it is allowed to be
            **    linked to from the inside. if the object is
            **    either an OleLink or an OLE 1.0 embedding
            **    then it can not be linked to from the inside.
            **    if we were a container/server app then we
            **    could offer linking to the outside of the
            **    object (ie. a pseudo object within our
            **    document). we are a container only app that
            **    does not support linking to ranges of its data.
            */

            lpSrcOleObj->lpVtbl->GetMiscStatus(
                  lpSrcOleObj,
                  DVASPECT_CONTENT, /* aspect is not important */
                  (LPDWORD)&dwStatus
            );
            if (! (dwStatus & OLEMISC_CANTLINKINSIDE)) {
               /* Our container supports linking to an embedded
               **    object. We want the lpDestContainerDoc to
               **    offer CF_LINKSOURCE via the IDataObject
               **    interface that it gives to the clipboard or
               **    the drag/drop operation. The link source will
               **    be identified by a composite moniker
               **    comprised of the FileMoniker of the source
               **    document and an ItemMoniker which identifies
               **    the OLE object inside the container. we do
               **    NOT want to force moniker assignment to the
               **    OLE object now (at copy time); we only want
               **    to FORCE moniker assignment later if a Paste
               **    Link occurs (ie. GetData for CF_LINKSOURCE).
               **    thus we store a pointer to the source document
               **    and the source ContainerLine so we can
               **    generate a proper ItemMoniker later when
               **    Paste Link occurs.
               */
               lpDestOleDoc->m_fLinkSourceAvail = TRUE;
               lpDestContainerDoc->m_lpSrcContainerLine =
                     lpSrcContainerLine;
            }
         }
      }
   }

#endif  // OLE_CNTR

   return lpDestOutlineDoc;

error:
   if (lpDestOutlineDoc)
      OutlineDoc_Destroy(lpDestOutlineDoc);

   return NULL;
}

/* OleDoc_PasteFromData
** --------------------
**
**    Paste data from an IDataObject*. The IDataObject* may come from
**    the clipboard (GetClipboard) or from a drag/drop operation.
**    In this function we choose the best format that we prefer.
**
**    Returns TRUE if data was successfully pasted.
**            FALSE if data could not be pasted.
*/

BOOL OleDoc_PasteFromData(
      LPOLEDOC            lpOleDoc,
      LPDATAOBJECT        lpSrcDataObj,
      BOOL                fLocalDataObj,
      BOOL                fLink
)
{
   LPOLEAPP        lpOleApp = (LPOLEAPP)g_lpApp;
   CLIPFORMAT      cfFormat;
   BOOL            fDisplayAsIcon = FALSE;
   SIZEL           sizelInSrc = {0, 0};
   HGLOBAL         hMem = NULL;
   HGLOBAL         hMetaPict = NULL;
   STGMEDIUM       medium;

   if (fLink) {
#if defined( OLE_SERVER )
      return FALSE;       // server version of app does NOT support links
#endif
#if defined( OLE_CNTR )
      // container version of app only supports OLE object type links
      cfFormat = lpOleApp->m_cfLinkSource;
#endif

   } else {

      int nFmtEtc;

      nFmtEtc = OleStdGetPriorityClipboardFormat(
            lpSrcDataObj,
            lpOleApp->m_arrPasteEntries,
            lpOleApp->m_nPasteEntries
      );

      if (nFmtEtc < 0)
         return FALSE;   // there is no format we like

      cfFormat = lpOleApp->m_arrPasteEntries[nFmtEtc].fmtetc.cfFormat;
   }

   /* NOTE: we need to check what dwDrawAspect is being
   **    transfered. if the data is an object that is displayed as an
   **    icon in the source, then we want to keep it as an icon. the
   **    aspect the object is displayed in at the source is transfered
   **    via the CF_OBJECTDESCRIPTOR format for a Paste operation.
   */
   if (hMem = OleStdGetData(
         lpSrcDataObj,
         (CLIPFORMAT)lpOleApp->m_cfObjectDescriptor,
         NULL,
         DVASPECT_CONTENT,
         (LPSTGMEDIUM)&medium)) {
      LPOBJECTDESCRIPTOR lpOD = GlobalLock(hMem);
      fDisplayAsIcon = (lpOD->dwDrawAspect == DVASPECT_ICON ? TRUE : FALSE);
      sizelInSrc = lpOD->sizel;   // size of object/picture in source (opt.)
      GlobalUnlock(hMem);
      ReleaseStgMedium((LPSTGMEDIUM)&medium);     // equiv to GlobalFree

      if (fDisplayAsIcon) {
         hMetaPict = OleStdGetData(
               lpSrcDataObj,
               CF_METAFILEPICT,
               NULL,
               DVASPECT_ICON,
               (LPSTGMEDIUM)&medium
         );
         if (hMetaPict == NULL)
            fDisplayAsIcon = FALSE; // give up; failed to get icon MFP
      }
   }

   return OleDoc_PasteFormatFromData(
         lpOleDoc,
         cfFormat,
         lpSrcDataObj,
         fLocalDataObj,
         fLink,
         fDisplayAsIcon,
         hMetaPict,
         (LPSIZEL)&sizelInSrc
   );

   if (hMetaPict)
      ReleaseStgMedium((LPSTGMEDIUM)&medium);  // properly free METAFILEPICT
}

/* OleDoc_PasteFormatFromData
** --------------------------
**
**    Paste a particular data format from a IDataObject*. The
**    IDataObject* may come from the clipboard (GetClipboard) or from a
**    drag/drop operation.
**
**    Returns TRUE if data was successfully pasted.
**            FALSE if data could not be pasted.
*/

BOOL OleDoc_PasteFormatFromData(
      LPOLEDOC            lpOleDoc,
      CLIPFORMAT          cfFormat,
      LPDATAOBJECT        lpSrcDataObj,
      BOOL                fLocalDataObj,
      BOOL                fLink,
      BOOL                fDisplayAsIcon,
      HGLOBAL             hMetaPict,
      LPSIZEL             lpSizelInSrc
)
{
#if defined( OLE_SERVER )
   /* call server specific version of the function. */
   return ServerDoc_PasteFormatFromData(
         (LPSERVERDOC)lpOleDoc,
         cfFormat,
         lpSrcDataObj,
         fLocalDataObj,
         fLink
   );
#endif
#if defined( OLE_CNTR )

   /* call container specific version of the function. */
   return ContainerDoc_PasteFormatFromData(
         (LPCONTAINERDOC)lpOleDoc,
         cfFormat,
         lpSrcDataObj,
         fLocalDataObj,
         fLink,
         fDisplayAsIcon,
         hMetaPict,
         lpSizelInSrc
   );
#endif
}

/* OleDoc_QueryPasteFromData
** -------------------------
**
**    Check if the IDataObject* offers data in a format that we can
**    paste. The IDataObject* may come from the clipboard
**    (GetClipboard) or from a drag/drop operation.
**
**    Returns TRUE if paste can be performed
**            FALSE if paste is not possible.
*/

BOOL OleDoc_QueryPasteFromData(
      LPOLEDOC            lpOleDoc,
      LPDATAOBJECT        lpSrcDataObj,
      BOOL                fLink
)
{
#if defined( OLE_SERVER )
   return ServerDoc_QueryPasteFromData(
         (LPSERVERDOC) lpOleDoc,
         lpSrcDataObj,
         fLink
   );
#endif
#if defined( OLE_CNTR )

   return ContainerDoc_QueryPasteFromData(
         (LPCONTAINERDOC) lpOleDoc,
         lpSrcDataObj,
         fLink
   );
#endif
}

/* OleDoc_GetExtent
 * ----------------
 *
 *      Get the extent (width, height) of the entire document in Himetric.
 */
void OleDoc_GetExtent(LPOLEDOC lpOleDoc, LPSIZEL lpsizel)
{
   LPLINELIST lpLL = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;

   LineList_CalcSelExtentInHimetric(lpLL, NULL, lpsizel);
}

/* OleDoc_GetObjectDescriptorData
 * ------------------------------
 *
 * Return a handle to an object's data in CF_OBJECTDESCRIPTOR form
 *
 */
HGLOBAL OleDoc_GetObjectDescriptorData(LPOLEDOC lpOleDoc, LPLINERANGE lplrSel)
{
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;

   /* Only our data transfer doc renders CF_OBJECTDESCRIPTOR */
   OleDbgAssert(lpOutlineDoc->m_fDataTransferDoc);

#if defined( OLE_SERVER )
   {
      LPSERVERDOC   lpServerDoc = (LPSERVERDOC)lpOleDoc;
      SIZEL         sizel;
      POINTL        pointl;
      LPOLESTR      lpszSrcOfCopy = NULL;
      IBindCtx  FAR *pbc = NULL;
      HGLOBAL       hObjDesc;
      DWORD         dwStatus = 0;
      LPOUTLINEDOC  lpSrcDocOfCopy=(LPOUTLINEDOC)lpOleDoc->m_lpSrcDocOfCopy;
      LPMONIKER lpSrcMonikerOfCopy = ServerDoc_GetSelFullMoniker(
            (LPSERVERDOC)lpOleDoc->m_lpSrcDocOfCopy,
            &lpServerDoc->m_lrSrcSelOfCopy,
            GETMONIKER_TEMPFORUSER
      );

      SvrDoc_OleObj_GetMiscStatus(
            (LPOLEOBJECT)&lpServerDoc->m_OleObject,
            DVASPECT_CONTENT,
            &dwStatus
      );

      OleDoc_GetExtent(lpOleDoc, &sizel);
      pointl.x = pointl.y = 0;

      if (lpSrcMonikerOfCopy) {
         CreateBindCtx(0, (LPBC FAR*)&pbc);
         lpSrcMonikerOfCopy->lpVtbl->GetDisplayName(
            lpSrcMonikerOfCopy, pbc, NULL, &lpszSrcOfCopy);
         pbc->lpVtbl->Release(pbc);
         lpSrcMonikerOfCopy->lpVtbl->Release(lpSrcMonikerOfCopy);
      } else {
         /* this document has no moniker; use our FullUserTypeName
         **    as the description of the source of copy.
         */
         lpszSrcOfCopy = FULLUSERTYPENAME;
      }

      hObjDesc =  OleStdGetObjectDescriptorData(
            CLSID_APP,
            DVASPECT_CONTENT,
            sizel,
            pointl,
            dwStatus,
            FULLUSERTYPENAME,
            lpszSrcOfCopy
      );

      if (lpSrcMonikerOfCopy && lpszSrcOfCopy)
         OleStdFreeString(lpszSrcOfCopy, NULL);
      return hObjDesc;

   }
#endif
#if defined( OLE_CNTR )
   {
      LPCONTAINERDOC lpContainerDoc = (LPCONTAINERDOC)lpOleDoc;
      LPLINELIST lpLL = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;
      LPCONTAINERLINE lpContainerLine;
      HGLOBAL hObjDesc;
      BOOL fSelIsOleObject = FALSE;
      LPOLEOBJECT lpOleObj;
      SIZEL sizel;
      POINTL pointl;

      if ( lpLL->m_nNumLines == 1 ) {
         fSelIsOleObject = ContainerDoc_IsSelAnOleObject(
               lpContainerDoc,
               &IID_IOleObject,
               (LPUNKNOWN FAR*)&lpOleObj,
               NULL,    /* we don't need the line index */
               (LPCONTAINERLINE FAR*)&lpContainerLine
         );
      }

      pointl.x = pointl.y = 0;

      if (fSelIsOleObject) {
         /* NOTE: a single OLE object is being transfered via
         **    this DataTransferDoc. we need to generate the
         **    CF_ObjectDescrioptor which describes the OLE object.
         */

         LPOUTLINEDOC lpSrcOutlineDoc =
               (LPOUTLINEDOC)lpOleDoc->m_lpSrcDocOfCopy;
         LPOLESTR lpszSrcOfCopy = lpSrcOutlineDoc->m_szFileName;
         BOOL fFreeSrcOfCopy = FALSE;
         SIZEL sizelOleObject;
         LPLINE lpLine = (LPLINE)lpContainerLine;

         /* if the object copied can be linked to then get a
         **    TEMPFORUSER form of the moniker which identifies the
         **    source of copy. we do not want to force the
         **    assignment of the moniker until CF_LINKSOURCE is
         **    rendered.
         **    if the object copied can not be a link source then use
         **    the source filename to identify the source of copy.
         **    there is no need to generate a moniker for the object
         **    copied.
         */
         if (lpOleDoc->m_fLinkSourceAvail &&
               lpContainerDoc->m_lpSrcContainerLine) {
            LPBINDCTX pbc = NULL;
            LPMONIKER lpSrcMonikerOfCopy = ContainerLine_GetFullMoniker(
                  lpContainerDoc->m_lpSrcContainerLine,
                  GETMONIKER_TEMPFORUSER
            );
            if (lpSrcMonikerOfCopy) {
               CreateBindCtx(0, (LPBC FAR*)&pbc);
               if (pbc != NULL) {
                  lpSrcMonikerOfCopy->lpVtbl->GetDisplayName(
                        lpSrcMonikerOfCopy, pbc, NULL,&lpszSrcOfCopy);
                  pbc->lpVtbl->Release(pbc);
                  fFreeSrcOfCopy = TRUE;
               }
               lpSrcMonikerOfCopy->lpVtbl->Release(lpSrcMonikerOfCopy);
            }
         }

         /* NOTE: Get size that object is being drawn. If the
         **    object has been scaled because the user resized the
         **    object, then we want to pass the scaled size of the
         **    object in the ObjectDescriptor rather than the size
         **    that the object would return via
         **    IOleObject::GetExtent and IViewObject2::GetExtent. in
         **    this way if the object is transfered to another container
         **    (via clipboard or drag/drop), then the object will
         **    remain the scaled size.
         */
         sizelOleObject.cx = lpLine->m_nWidthInHimetric;
         sizelOleObject.cy = lpLine->m_nHeightInHimetric;

         hObjDesc = OleStdGetObjectDescriptorDataFromOleObject(
               lpOleObj,
               lpszSrcOfCopy,
               lpContainerLine->m_dwDrawAspect,
               pointl,
               (LPSIZEL)&sizelOleObject
         );

         if (fFreeSrcOfCopy && lpszSrcOfCopy)
            OleStdFreeString(lpszSrcOfCopy, NULL);
         OleStdRelease((LPUNKNOWN)lpOleObj);
         return hObjDesc;
      } else {
         /* NOTE: the data being transfered via this
         **    DataTransferDoc is NOT a single OLE object. thus in
         **    this case the CF_ObjectDescriptor data should
         **    describe our container app itself.
         */
         OleDoc_GetExtent(lpOleDoc, &sizel);
         return OleStdGetObjectDescriptorData(
               CLSID_NULL, /* not used if no object formats */
               DVASPECT_CONTENT,
               sizel,
               pointl,
               0,
               NULL,       /* UserTypeName not used if no obj fmt's */
               FULLUSERTYPENAME   /* string to identify source of copy */
         );

      }
   }
#endif  // OLE_CNTR
}

#if defined( OLE_SERVER )

/*************************************************************************
** ServerDoc Supprt Functions Used by Server versions
*************************************************************************/

/* ServerDoc_PasteFormatFromData
** -----------------------------
**
**    Paste a particular data format from a IDataObject*. The
**    IDataObject* may come from the clipboard (GetClipboard) or from a
**    drag/drop operation.
**
**    NOTE: fLink is specified then FALSE if returned because the
**    Server only version of the app can not support links.
**
**    Returns TRUE if data was successfully pasted.
**            FALSE if data could not be pasted.
*/
BOOL ServerDoc_PasteFormatFromData(
      LPSERVERDOC             lpServerDoc,
      CLIPFORMAT              cfFormat,
      LPDATAOBJECT            lpSrcDataObj,
      BOOL                    fLocalDataObj,
      BOOL                    fLink
)
{
   LPLINELIST   lpLL = (LPLINELIST)&((LPOUTLINEDOC)lpServerDoc)->m_LineList;
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOLEAPP     lpOleApp = (LPOLEAPP)g_lpApp;
   int          nIndex;
   int          nCount = 0;
   HGLOBAL      hData;
   STGMEDIUM    medium;
   LINERANGE    lrSel;

   if (LineList_GetCount(lpLL) == 0)
      nIndex = -1;    // pasting to empty list
   else
      nIndex=LineList_GetFocusLineIndex(lpLL);

   if (fLink) {
      /* We should paste a Link to the data, but we do not support links */
      return FALSE;

   } else {

      if (cfFormat == lpOutlineApp->m_cfOutline) {

         hData = OleStdGetData(
               lpSrcDataObj,
               (CLIPFORMAT)lpOutlineApp->m_cfOutline,
               NULL,
               DVASPECT_CONTENT,
               (LPSTGMEDIUM)&medium
         );
         if (hData == NULL)
            return FALSE;

         nCount = OutlineDoc_PasteOutlineData(
               (LPOUTLINEDOC)lpServerDoc,
               hData,
               nIndex
         );
         // NOTE: we must free data handle by releasing the medium
         ReleaseStgMedium((LPSTGMEDIUM)&medium);

      } else if(cfFormat == CF_TEXT) {

         hData = OleStdGetData(
               lpSrcDataObj,
               CF_TEXT,
               NULL,
               DVASPECT_CONTENT,
               (LPSTGMEDIUM)&medium
         );
         if (hData == NULL)
            return FALSE;

         nCount = OutlineDoc_PasteTextData(
               (LPOUTLINEDOC)lpServerDoc,
               hData,
               nIndex
         );
         // NOTE: we must free data handle by releasing the medium
         ReleaseStgMedium((LPSTGMEDIUM)&medium);
      }
   }

   lrSel.m_nEndLine   = nIndex + 1;
   lrSel.m_nStartLine = nIndex + nCount;
   LineList_SetSel(lpLL, &lrSel);
   return TRUE;
}

/* ServerDoc_QueryPasteFromData
** ----------------------------
**
**    Check if the IDataObject* offers data in a format that we can
**    paste. The IDataObject* may come from the clipboard
**    (GetClipboard) or from a drag/drop operation.
**    In this function we look if one of the following formats is
**    offered:
**              CF_OUTLINE
**              CF_TEXT
**
**    NOTE: fLink is specified then FALSE if returned because the
**    Server only version of the app can not support links.
**
**    Returns TRUE if paste can be performed
**            FALSE if paste is not possible.
*/
BOOL ServerDoc_QueryPasteFromData(
      LPSERVERDOC             lpServerDoc,
      LPDATAOBJECT            lpSrcDataObj,
      BOOL                    fLink
)
{
   LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;

   if (fLink) {
      /* we do not support links */
      return FALSE;

   } else {

      int nFmtEtc;

      nFmtEtc = OleStdGetPriorityClipboardFormat(
            lpSrcDataObj,
            lpOleApp->m_arrPasteEntries,
            lpOleApp->m_nPasteEntries
         );

      if (nFmtEtc < 0)
         return FALSE;   // there is no format we like
   }

   return TRUE;
}

/* ServerDoc_GetData
 * -----------------
 *
 * Render data from the document on a CALLEE allocated STGMEDIUM.
 *      This routine is called via IDataObject::GetData.
 */

HRESULT ServerDoc_GetData (
      LPSERVERDOC             lpServerDoc,
      LPFORMATETC             lpformatetc,
      LPSTGMEDIUM             lpMedium
)
{
   LPOLEDOC  lpOleDoc = (LPOLEDOC)lpServerDoc;
   LPOUTLINEDOC  lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   LPSERVERAPP lpServerApp = (LPSERVERAPP)g_lpApp;
   LPOLEAPP  lpOleApp = (LPOLEAPP)lpServerApp;
   LPOUTLINEAPP  lpOutlineApp = (LPOUTLINEAPP)lpServerApp;
   HRESULT hrErr;
   SCODE sc;

   // NOTE: we must set out pointer parameters to NULL
   lpMedium->pUnkForRelease = NULL;

   /* NOTE: we must make sure to set all out parameters to NULL. */
   lpMedium->tymed = TYMED_NULL;
   lpMedium->pUnkForRelease = NULL;    // we transfer ownership to caller
   lpMedium->u.hGlobal = NULL;

   if(lpformatetc->cfFormat == lpOutlineApp->m_cfOutline) {
      // Verify caller asked for correct medium
      if (!(lpformatetc->tymed & TYMED_HGLOBAL)) {
         sc = DV_E_FORMATETC;
         goto error;
      }
      lpMedium->u.hGlobal = OutlineDoc_GetOutlineData (lpOutlineDoc,NULL);
      if (! lpMedium->u.hGlobal) {
         sc = E_OUTOFMEMORY;
         goto error;
      }

      lpMedium->tymed = TYMED_HGLOBAL;
      OleDbgOut3("ServerDoc_GetData: rendered CF_OUTLINE\r\n");
      return NOERROR;

   } else if (lpformatetc->cfFormat == CF_METAFILEPICT &&
      (lpformatetc->dwAspect & DVASPECT_CONTENT) ) {
      // Verify caller asked for correct medium
      if (!(lpformatetc->tymed & TYMED_MFPICT)) {
         sc = DV_E_FORMATETC;
         goto error;
      }

      lpMedium->u.hGlobal = ServerDoc_GetMetafilePictData(lpServerDoc,NULL);
      if (! lpMedium->u.hGlobal) {
         sc = E_OUTOFMEMORY;
         goto error;
      }

      lpMedium->tymed = TYMED_MFPICT;
      OleDbgOut3("ServerDoc_GetData: rendered CF_METAFILEPICT\r\n");
      return NOERROR;

   } else if (lpformatetc->cfFormat == CF_METAFILEPICT &&
      (lpformatetc->dwAspect & DVASPECT_ICON) ) {
      CLSID clsid;
      // Verify caller asked for correct medium
      if (!(lpformatetc->tymed & TYMED_MFPICT)) {
         sc = DV_E_FORMATETC;
         goto error;
      }

      /* NOTE: we should return the default icon for our class.
      **    we must be carefull to use the correct CLSID here.
      **    if we are currently preforming a "TreatAs (aka. ActivateAs)"
      **    operation then we need to use the class of the object
      **    written in the storage of the object. otherwise we would
      **    use our own class id.
      */
      if (ServerDoc_GetClassID(lpServerDoc, (LPCLSID)&clsid) != NOERROR) {
         sc = DV_E_FORMATETC;
         goto error;
      }

      lpMedium->u.hGlobal=GetIconOfClass(g_lpApp->m_hInst,(REFCLSID)&clsid, NULL, FALSE);
      if (! lpMedium->u.hGlobal) {
         sc = E_OUTOFMEMORY;
         goto error;
      }

      lpMedium->tymed = TYMED_MFPICT;
      OleDbgOut3("ServerDoc_GetData: rendered CF_METAFILEPICT (icon)\r\n");
      return NOERROR;

   } else if (lpformatetc->cfFormat == CF_TEXT) {
      // Verify caller asked for correct medium
      if (!(lpformatetc->tymed & TYMED_HGLOBAL)) {
         sc = DV_E_FORMATETC;
         goto error;
      }

      lpMedium->u.hGlobal = OutlineDoc_GetTextData (
            (LPOUTLINEDOC)lpServerDoc,
            NULL
      );
      if (! lpMedium->u.hGlobal) {
         sc = E_OUTOFMEMORY;
         goto error;
      }

      lpMedium->tymed = TYMED_HGLOBAL;
      OleDbgOut3("ServerDoc_GetData: rendered CF_TEXT\r\n");
      return NOERROR;
   }

   /* the above are the only formats supports by a user document (ie.
   **    a non-data transfer doc). if the document is used for
   **    purposes of data transfer, then additional formats are offered.
   */
   if (! lpOutlineDoc->m_fDataTransferDoc) {
      sc = DV_E_FORMATETC;
      goto error;
   }

   /* NOTE: ObjectDescriptor and LinkSrcDescriptor will
   **    contain the same data for the pure container and pure server
   **    type applications. only a container/server application may
   **    have different content for ObjectDescriptor and
   **    LinkSrcDescriptor. if a container/server copies a link for
   **    example, then the ObjectDescriptor would give the class
   **    of the link source but the LinkSrcDescriptor would give the
   **    class of the container/server itself. in this situation if a
   **    paste operation occurs, an equivalent link is pasted, but if
   **    a pastelink operation occurs, then a link to a pseudo object
   **    in the container/server is created.
   */
   if (lpformatetc->cfFormat == lpOleApp->m_cfObjectDescriptor ||
      (lpformatetc->cfFormat == lpOleApp->m_cfLinkSrcDescriptor &&
            lpOleDoc->m_fLinkSourceAvail)) {
      // Verify caller asked for correct medium
      if (!(lpformatetc->tymed & TYMED_HGLOBAL)) {
         sc = DV_E_FORMATETC;
         goto error;
      }

      lpMedium->u.hGlobal = OleDoc_GetObjectDescriptorData (
            (LPOLEDOC)lpServerDoc,
            NULL
      );
      if (! lpMedium->u.hGlobal) {
         sc = E_OUTOFMEMORY;
         goto error;
      }

      lpMedium->tymed = TYMED_HGLOBAL;
      OleDbgOut3("ServerDoc_GetData: rendered CF_OBJECTDESCRIPTOR\r\n");
      return NOERROR;

   } else if (lpformatetc->cfFormat == lpOleApp->m_cfEmbedSource) {
      hrErr = OleStdGetOleObjectData(
            (LPPERSISTSTORAGE)&lpServerDoc->m_PersistStorage,
            lpformatetc,
            lpMedium,
            FALSE   /* fUseMemory -- (use file-base stg) */

      );
      if (hrErr != NOERROR) {
         sc = hrErr;
         goto error;
      }
      OleDbgOut3("ServerDoc_GetData: rendered CF_EMBEDSOURCE\r\n");
      return NOERROR;

   } else if (lpformatetc->cfFormat == lpOleApp->m_cfLinkSource) {
      if (lpOleDoc->m_fLinkSourceAvail) {
         LPMONIKER lpmk;

         lpmk = ServerDoc_GetSelFullMoniker(
               (LPSERVERDOC)lpOleDoc->m_lpSrcDocOfCopy,
               &lpServerDoc->m_lrSrcSelOfCopy,
               GETMONIKER_FORCEASSIGN
         );
         if (lpmk) {
            hrErr = OleStdGetLinkSourceData(
                  lpmk,
                  (LPCLSID)&CLSID_APP,
                  lpformatetc,
                  lpMedium
            );
            OleStdRelease((LPUNKNOWN)lpmk);
            if (hrErr != NOERROR) {
               sc = hrErr;
               goto error;
            }
            OleDbgOut3("ServerDoc_GetData: rendered CF_LINKSOURCE\r\n");
            return NOERROR;

         } else {
            sc = E_FAIL;
            goto error;
         }
      } else {
         sc = DV_E_FORMATETC;
         goto error;
      }

   } else {
      sc = DV_E_FORMATETC;
      goto error;
   }

   return NOERROR;

error:
   return sc;
}

/* ServerDoc_GetDataHere
 * ---------------------
 *
 * Render data from the document on a CALLER allocated STGMEDIUM.
 *      This routine is called via IDataObject::GetDataHere.
 */
HRESULT ServerDoc_GetDataHere (
      LPSERVERDOC             lpServerDoc,
      LPFORMATETC             lpformatetc,
      LPSTGMEDIUM             lpMedium
)
{
   LPOLEDOC        lpOleDoc = (LPOLEDOC)lpServerDoc;
   LPOUTLINEDOC    lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   LPSERVERAPP     lpServerApp = (LPSERVERAPP)g_lpApp;

LPOLEAPP        lpOleApp = (LPOLEAPP)lpServerApp;
   LPOUTLINEAPP    lpOutlineApp = (LPOUTLINEAPP)lpServerApp;
   HRESULT         hrErr;
   SCODE           sc;

   // NOTE: lpMedium is an IN parameter. we should NOT set
   //           lpMedium->pUnkForRelease to NULL

   /* our user document does not support any formats for GetDataHere.
   **    if the document is used for
   **    purposes of data transfer, then additional formats are offered.
   */
   if (! lpOutlineDoc->m_fDataTransferDoc) {
      sc = DV_E_FORMATETC;
      goto error;
   }

   if (lpformatetc->cfFormat == lpOleApp->m_cfEmbedSource) {
      hrErr = OleStdGetOleObjectData(
            (LPPERSISTSTORAGE)&lpServerDoc->m_PersistStorage,
            lpformatetc,
            lpMedium,
            FALSE   /* fUseMemory -- (use file-base stg) */
      );
      if (hrErr != NOERROR) {
         sc = hrErr;
         goto error;
      }
      OleDbgOut3("ServerDoc_GetDataHere: rendered CF_EMBEDSOURCE\r\n");
      return NOERROR;

   } else if (lpformatetc->cfFormat == lpOleApp->m_cfLinkSource) {
      if (lpOleDoc->m_fLinkSourceAvail) {
         LPMONIKER lpmk;

         lpmk = ServerDoc_GetSelFullMoniker(
               (LPSERVERDOC)lpOleDoc->m_lpSrcDocOfCopy,
               &lpServerDoc->m_lrSrcSelOfCopy,
               GETMONIKER_FORCEASSIGN
         );
         if (lpmk) {
            hrErr = OleStdGetLinkSourceData(
                  lpmk,
                  (LPCLSID)&CLSID_APP,
                  lpformatetc,
                  lpMedium
            );
            OleStdRelease((LPUNKNOWN)lpmk);
            if (hrErr != NOERROR) {
               sc = hrErr;
               goto error;
            }

            OleDbgOut3("ServerDoc_GetDataHere: rendered CF_LINKSOURCE\r\n");
            return NOERROR;

         } else {
            sc = E_FAIL;
            goto error;
         }
      } else {
         sc = DV_E_FORMATETC;
         goto error;
      }
   } else {

      /* Caller is requesting data to be returned in Caller allocated
      **    medium, but we do NOT support this. we only support
      **    global memory blocks that WE allocate for the caller.
      */
      sc = DV_E_FORMATETC;
      goto error;
   }

   return NOERROR;

error:
   return sc;
}

/* ServerDoc_QueryGetData
 * ----------------------
 *
 * Answer if a particular data format is supported via GetData/GetDataHere.
 *      This routine is called via IDataObject::QueryGetData.
 */

HRESULT ServerDoc_QueryGetData (LPSERVERDOC lpServerDoc,LPFORMATETC lpformatetc)
{
   LPOLEDOC        lpOleDoc = (LPOLEDOC)lpServerDoc;
   LPOUTLINEDOC    lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   LPSERVERAPP     lpServerApp = (LPSERVERAPP)g_lpApp;
   LPOLEAPP        lpOleApp = (LPOLEAPP)lpServerApp;
   LPOUTLINEAPP    lpOutlineApp = (LPOUTLINEAPP)lpServerApp;

   /* Caller is querying if we support certain format but does not
   **    want any data actually returned.
   */
   if (lpformatetc->cfFormat == lpOutlineApp->m_cfOutline ||
         lpformatetc->cfFormat == CF_TEXT) {
      // we only support HGLOBAL
      return OleStdQueryFormatMedium(lpformatetc, TYMED_HGLOBAL);
   } else if (lpformatetc->cfFormat == CF_METAFILEPICT &&
      (lpformatetc->dwAspect &
         (DVASPECT_CONTENT | DVASPECT_ICON)) ) {
      return OleStdQueryFormatMedium(lpformatetc, TYMED_MFPICT);
   }

   /* the above are the only formats supports by a user document (ie.
   **    a non-data transfer doc). if the document is used for
   **    purposes of data transfer, then additional formats are offered.
   */
   if (! lpOutlineDoc->m_fDataTransferDoc)
      return DV_E_FORMATETC;

   if (lpformatetc->cfFormat == lpOleApp->m_cfEmbedSource) {
      return OleStdQueryOleObjectData(lpformatetc);

   } else if (lpformatetc->cfFormat == lpOleApp->m_cfLinkSource &&
      lpOleDoc->m_fLinkSourceAvail) {
      return OleStdQueryLinkSourceData(lpformatetc);

   } else if (lpformatetc->cfFormat == lpOleApp->m_cfObjectDescriptor) {
      return OleStdQueryObjectDescriptorData(lpformatetc);

   } else if (lpformatetc->cfFormat == lpOleApp->m_cfLinkSrcDescriptor &&
            lpOleDoc->m_fLinkSourceAvail) {
      return OleStdQueryObjectDescriptorData(lpformatetc);
   }

   return DV_E_FORMATETC;
}

/* ServerDoc_EnumFormatEtc
 * -----------------------
 *
 * Return an enumerator which enumerates the data accepted/offered by
 *      the document.
 *      This routine is called via IDataObject::EnumFormatEtc.
 */
HRESULT ServerDoc_EnumFormatEtc(
      LPSERVERDOC             lpServerDoc,
      DWORD                   dwDirection,
      LPENUMFORMATETC FAR*    lplpenumFormatEtc
)
{
   LPOLEDOC lpOleDoc = (LPOLEDOC)lpServerDoc;
   LPOLEAPP  lpOleApp = (LPOLEAPP)g_lpApp;
   int nActualFmts;
   SCODE sc = S_OK;

   /* NOTE: the enumeration of formats for a data transfer
   **    document is not a static list. the list of formats offered
   **    may or may not include CF_LINKSOURCE depending on whether a
   **    moniker is available for our document. thus we can NOT use
   **    the default OLE enumerator which enumerates the formats that
   **    are registered for our app in the registration database.
   */
   if (dwDirection == DATADIR_GET) {
      nActualFmts = lpOleApp->m_nDocGetFmts;

      /* If the document does not have a Moniker, then exclude
      **    CF_LINKSOURCE and CF_LINKSRCDESCRIPTOR from the list of
      **    formats available. these formats are deliberately listed
      **    last in the array of possible "Get" formats.
      */
      if (! lpOleDoc->m_fLinkSourceAvail)
         nActualFmts -= 2;

      *lplpenumFormatEtc = OleStdEnumFmtEtc_Create(
            nActualFmts, lpOleApp->m_arrDocGetFmts);
      if (*lplpenumFormatEtc == NULL)
         sc = E_OUTOFMEMORY;

   } else if (dwDirection == DATADIR_SET) {
      /* NOTE: a document that is used to transfer data
      **    (either via the clipboard or drag/drop does NOT
      **    accept SetData on ANY format!
      */
      sc = E_NOTIMPL;
      goto error;
   } else {
      sc = E_INVALIDARG;
      goto error;
   }

error:
   return sc;
}

/* ServerDoc_GetMetafilePictData
 * -----------------------------
 *
 * Return a handle to an object's picture data in metafile format.
 *
 *
 * RETURNS: A handle to the object's data in metafile format.
 *
 */
HGLOBAL ServerDoc_GetMetafilePictData(
      LPSERVERDOC         lpServerDoc,
      LPLINERANGE         lplrSel
)
{
   LPOUTLINEAPP    lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOUTLINEDOC    lpOutlineDoc=(LPOUTLINEDOC)lpServerDoc;
   LPLINELIST      lpLL=(LPLINELIST)&lpOutlineDoc->m_LineList;
   LPLINE          lpLine;
   LPMETAFILEPICT  lppict = NULL;
   HGLOBAL         hMFPict = NULL;
   HMETAFILE       hMF = NULL;
   RECT            rect;
   RECT            rectWBounds;
   HDC             hDC;
   int             i;
   int             nWidth;
   int     nStart = (lplrSel ? lplrSel->m_nStartLine : 0);
   int     nEnd =(lplrSel ? lplrSel->m_nEndLine : LineList_GetCount(lpLL)-1);
   int     nLines = nEnd - nStart + 1;
   UINT    fuAlign;
   POINT point;
   SIZE  size;

   hDC = CreateMetaFile(NULL);

   rect.left = 0;
   rect.right = 0;
   rect.bottom = 0;

   if (nLines > 0) {
   // calculate the total height/width of LineList in HIMETRIC
      for(i = nStart; i <= nEnd; i++) {
         lpLine = LineList_GetLine(lpLL,i);
         if (! lpLine)
            continue;

         nWidth = Line_GetTotalWidthInHimetric(lpLine);
         rect.right = max(rect.right, nWidth);
         rect.bottom -= Line_GetHeightInHimetric(lpLine);
      }

      SetMapMode(hDC, MM_ANISOTROPIC);

      SetWindowOrgEx(hDC, 0, 0, &point);
      SetWindowExtEx(hDC, rect.right, rect.bottom, &size);
      rectWBounds = rect;

      // Set the default font size, and font face name
      SelectObject(hDC, OutlineApp_GetActiveFont(lpOutlineApp));

      FillRect(hDC, (LPRECT) &rect, GetStockObject(WHITE_BRUSH));

      rect.bottom = 0;

      fuAlign = SetTextAlign(hDC, TA_LEFT | TA_TOP | TA_NOUPDATECP);

      /* While more lines print out the text */
      for(i = nStart; i <= nEnd; i++) {
         lpLine = LineList_GetLine(lpLL,i);
         if (! lpLine)
            continue;

         rect.top = rect.bottom;
         rect.bottom -= Line_GetHeightInHimetric(lpLine);

         /* Draw the line */
         Line_Draw(lpLine, hDC, &rect, &rectWBounds, FALSE /*fHighlight*/);
      }

      SetTextAlign(hDC, fuAlign);
   }

   // Get handle to the metafile.
   if (!(hMF = CloseMetaFile (hDC)))
      return NULL;

   if (!(hMFPict = GlobalAlloc (GALLOCFLG, sizeof (METAFILEPICT)))) {
      DeleteMetaFile (hMF);
      return NULL;
   }

   if (!(lppict = (LPMETAFILEPICT)GlobalLock(hMFPict))) {
      DeleteMetaFile (hMF);
      GlobalFree (hMFPict);
      return NULL;
   }

   lppict->mm   =  MM_ANISOTROPIC;
   lppict->hMF  =  hMF;
   lppict->xExt =  rect.right;
   lppict->yExt =  - rect.bottom;  // add minus sign to make it +ve
   GlobalUnlock (hMFPict);

   return hMFPict;
}

#endif  // OLE_SERVER

#if defined( OLE_CNTR )

/*************************************************************************
** ContainerDoc Supprt Functions Used by Container versions
*************************************************************************/

/* Paste OLE Link from clipboard */
void ContainerDoc_PasteLinkCommand(LPCONTAINERDOC lpContainerDoc)
{
   LPOUTLINEAPP    lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOLEAPP        lpOleApp = (LPOLEAPP)g_lpApp;
   LPDATAOBJECT    lpClipboardDataObj = NULL;
   BOOL            fLink = TRUE;
   BOOL            fLocalDataObj = FALSE;
   BOOL            fDisplayAsIcon = FALSE;
   SIZEL           sizelInSrc;
   HCURSOR         hPrevCursor;
   HGLOBAL         hMem = NULL;
   HGLOBAL         hMetaPict = NULL;
   STGMEDIUM       medium;
   BOOL            fStatus;
   HRESULT         hrErr;

   hrErr = OleGetClipboard((LPDATAOBJECT FAR*)&lpClipboardDataObj);
   if (hrErr != NOERROR)
      return;     // Clipboard seems to be empty or can't be accessed

   // this may take a while, put up hourglass cursor
   hPrevCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));

   /* check if the data on the clipboard is local to our application
   **    instance.
   */
   if (lpOutlineApp->m_lpClipboardDoc) {
      LPOLEDOC lpOleDoc = (LPOLEDOC)lpOutlineApp->m_lpClipboardDoc;
      if (lpClipboardDataObj == (LPDATAOBJECT)&lpOleDoc->m_DataObject)
         fLocalDataObj = TRUE;
   }

   /* NOTE: we need to check what dwDrawAspect is being
   **    transfered. if the data is an object that is displayed as an
   **    icon in the source, then we want to keep it as an icon. the
   **    aspect the object is displayed in at the source is transfered
   **    via the CF_LINKSOURCEDESCRIPTOR format for a PasteLink
   **    operation.
   */
   if (hMem = OleStdGetData(
         lpClipboardDataObj,
         (CLIPFORMAT)lpOleApp->m_cfLinkSrcDescriptor,
         NULL,
         DVASPECT_CONTENT,
         (LPSTGMEDIUM)&medium)) {
      LPOBJECTDESCRIPTOR lpOD = GlobalLock(hMem);
      fDisplayAsIcon = (lpOD->dwDrawAspect == DVASPECT_ICON ? TRUE : FALSE);
      sizelInSrc = lpOD->sizel;   // size of object/picture in source (opt.)
      GlobalUnlock(hMem);
      ReleaseStgMedium((LPSTGMEDIUM)&medium);     // equiv to GlobalFree

      if (fDisplayAsIcon) {
         hMetaPict = OleStdGetData(
               lpClipboardDataObj,
               CF_METAFILEPICT,
               NULL,
               DVASPECT_ICON,
               (LPSTGMEDIUM)&medium
         );
         if (hMetaPict == NULL)
            fDisplayAsIcon = FALSE; // give up; failed to get icon MFP
      }
   }

   fStatus = ContainerDoc_PasteFormatFromData(
         lpContainerDoc,
         (CLIPFORMAT)lpOleApp->m_cfLinkSource,
         lpClipboardDataObj,
         fLocalDataObj,
         fLink,
         fDisplayAsIcon,
         hMetaPict,
         (LPSIZEL)&sizelInSrc
   );

   if (!fStatus)
      OutlineApp_ErrorMessage(g_lpApp, ErrMsgPasting);

   if (hMetaPict)
      ReleaseStgMedium((LPSTGMEDIUM)&medium);  // properly free METAFILEPICT

   if (lpClipboardDataObj)
      OleStdRelease((LPUNKNOWN)lpClipboardDataObj);

   SetCursor(hPrevCursor);     // restore original cursor
}

/* ContainerDoc_PasteFormatFromData
** --------------------------------
**
**    Paste a particular data format from a IDataObject*. The
**    IDataObject* may come from the clipboard (GetClipboard) or from a
**    drag/drop operation.
**
**    Returns TRUE if data was successfully pasted.
**            FALSE if data could not be pasted.
*/
BOOL ContainerDoc_PasteFormatFromData(
      LPCONTAINERDOC          lpContainerDoc,
      CLIPFORMAT              cfFormat,
      LPDATAOBJECT            lpSrcDataObj,
      BOOL                    fLocalDataObj,
      BOOL                    fLink,
      BOOL                    fDisplayAsIcon,
      HGLOBAL                 hMetaPict,
      LPSIZEL                 lpSizelInSrc
)
{
   LPLINELIST lpLL = (LPLINELIST)&((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   LPOUTLINEAPP    lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOLEAPP        lpOleApp = (LPOLEAPP)g_lpApp;
   LPCONTAINERAPP  lpContainerApp = (LPCONTAINERAPP)g_lpApp;
   int             nIndex;
   int             nCount = 0;
   HGLOBAL         hData;
   STGMEDIUM       medium;
   FORMATETC       formatetc;
   HRESULT         hrErr;
   LINERANGE       lrSel;

   if (LineList_GetCount(lpLL) == 0)
      nIndex = -1;    // pasting to empty list
   else
      nIndex=LineList_GetFocusLineIndex(lpLL);

   if (fLink) {

      /* We should paste a Link to the data */

      if (cfFormat != lpOleApp->m_cfLinkSource)
         return FALSE;   // we only support OLE object type links

      nCount = ContainerDoc_PasteOleObject(
            lpContainerDoc,
            lpSrcDataObj,
            OLECREATEFROMDATA_LINK,
            cfFormat,
            nIndex,
            fDisplayAsIcon,
            hMetaPict,
            lpSizelInSrc
         );
      return (nCount > 0 ? TRUE : FALSE);

   } else {

      if (cfFormat == lpContainerApp->m_cfCntrOutl) {
         if (fLocalDataObj) {

            /* CASE I: IDataObject* is local to our app
            **
            **    if the source of the data is local to our
            **    application instance, then we can get direct
            **    access to the original OleDoc object that
            **    corresponds to the IDataObject* given.
            **    CF_CNTROUTL data is passed through a LPSTORAGE.
            **    if we call OleGetData asking for CF_CNTROUTL, we
            **    will be returned a copy of the existing open pStg
            **    of the original source document. we can NOT open
            **    streams and sub-storages again via this pStg
            **    since it is already open within our same
            **    application instance. we must copy the data from
            **    the original OleDoc source document.
            */
            LPLINELIST lpSrcLL;
            LPOLEDOC lpLocalSrcDoc =
               ((struct CDocDataObjectImpl FAR*)lpSrcDataObj)->lpOleDoc;

            /* copy all lines from SrcDoc to DestDoc. */
            lpSrcLL = &((LPOUTLINEDOC)lpLocalSrcDoc)->m_LineList;
            nCount = LineList_CopySelToDoc(
                  lpSrcLL,
                  NULL,
                  (LPOUTLINEDOC)lpContainerDoc
            );

         } else {

            /* CASE II: IDataObject* is NOT local to our app
            **
            **    if the source of the data comes from another
            **    application instance. we can call GetDataHere to
            **    retrieve the CF_CNTROUTL data. CF_CNTROUTL data
            **    is passed through a LPSTORAGE. we MUST use
            **    IDataObject::GetDataHere. calling
            **    IDataObject::GetData does NOT work because OLE
            **    currently does NOT support remoting of a callee
            **    allocated root storage back to the caller. this
            **    hopefully will be supported in a future version.
            **    in order to call GetDataHere we must allocate an
            **    IStorage instance for the callee to write into.
            **    we will allocate an IStorage docfile that will
            **    delete-on-release. we could use either a
            **    memory-based storage or a file-based storage.
            */
            LPSTORAGE lpTmpStg = OleStdCreateTempStorage(
                  FALSE /*fUseMemory*/,
                  STGM_READWRITE | STGM_TRANSACTED |STGM_SHARE_EXCLUSIVE
            );
            if (! lpTmpStg)
               return FALSE;

            formatetc.cfFormat = cfFormat;
            formatetc.ptd = NULL;
            formatetc.dwAspect = DVASPECT_CONTENT;
            formatetc.tymed = TYMED_ISTORAGE;
            formatetc.lindex = -1;

            medium.tymed = TYMED_ISTORAGE;
            medium.u.pstg = lpTmpStg;
            medium.pUnkForRelease = NULL;

            OLEDBG_BEGIN2("IDataObject::GetDataHere called\r\n")
            hrErr = lpSrcDataObj->lpVtbl->GetDataHere(
                  lpSrcDataObj,
                  (LPFORMATETC)&formatetc,
                  (LPSTGMEDIUM)&medium
            );
            OLEDBG_END2

            if (hrErr == NOERROR) {
               nCount = ContainerDoc_PasteCntrOutlData(
                     lpContainerDoc,
                     lpTmpStg,
                     nIndex
               );
            }
            OleStdVerifyRelease(
               (LPUNKNOWN)lpTmpStg, OLESTR("Temp stg NOT released!\r\n"));
            return ((hrErr == NOERROR) ? TRUE : FALSE);
         }

      } else if (cfFormat == lpOutlineApp->m_cfOutline) {

         hData = OleStdGetData(
               lpSrcDataObj,
               (CLIPFORMAT)lpOutlineApp->m_cfOutline,
               NULL,
               DVASPECT_CONTENT,
               (LPSTGMEDIUM)&medium
         );
         nCount = OutlineDoc_PasteOutlineData(
               (LPOUTLINEDOC)lpContainerDoc,
               hData,
               nIndex
            );
         // NOTE: we must free data handle by releasing the medium
         ReleaseStgMedium((LPSTGMEDIUM)&medium);

      } else if (cfFormat == lpOleApp->m_cfEmbedSource ||
         cfFormat == lpOleApp->m_cfEmbeddedObject ||
         cfFormat == lpOleApp->m_cfFileName) {
         /* NOTE: OleCreateFromData API creates an OLE object if
         **    CF_EMBEDDEDOBJECT, CF_EMBEDSOURCE, or CF_FILENAME are
         **    available from the source data object. the
         **    CF_FILENAME case arises when a file is copied to the
         **    clipboard from the FileManager. if the file has an
         **    associated class (see GetClassFile API), then an
         **    object of that class is created. otherwise an OLE 1.0
         **    Packaged object is created.
         */
         nCount = ContainerDoc_PasteOleObject(
               lpContainerDoc,
               lpSrcDataObj,
               OLECREATEFROMDATA_OBJECT,
               0,   /* N/A -- cfFormat */
               nIndex,
               fDisplayAsIcon,
               hMetaPict,
               lpSizelInSrc
         );
         return (nCount > 0 ? TRUE : FALSE);

      } else if (cfFormat == CF_METAFILEPICT
               || cfFormat == CF_DIB
               || cfFormat == CF_BITMAP) {

         /* NOTE: OleCreateStaticFromData API creates an static
         **    OLE object if CF_METAFILEPICT, CF_DIB, or CF_BITMAP is
         **    CF_EMBEDDEDOBJECT, CF_EMBEDSOURCE, or CF_FILENAME are
         **    available from the source data object.
         */
         nCount = ContainerDoc_PasteOleObject(
               lpContainerDoc,
               lpSrcDataObj,
               OLECREATEFROMDATA_STATIC,
               cfFormat,
               nIndex,
               fDisplayAsIcon,
               hMetaPict,
               lpSizelInSrc
         );
         return (nCount > 0 ? TRUE : FALSE);

      } else if(cfFormat == CF_TEXT) {

         hData = OleStdGetData(
               lpSrcDataObj,
               CF_TEXT,
               NULL,
               DVASPECT_CONTENT,
               (LPSTGMEDIUM)&medium
         );
         nCount = OutlineDoc_PasteTextData(
               (LPOUTLINEDOC)lpContainerDoc,
               hData,
               nIndex
            );
         // NOTE: we must free data handle by releasing the medium
         ReleaseStgMedium((LPSTGMEDIUM)&medium);

      } else {
         return FALSE;   // no acceptable format available to paste
      }
   }

   lrSel.m_nStartLine = nIndex + nCount;
   lrSel.m_nEndLine = nIndex + 1;
   LineList_SetSel(lpLL, &lrSel);
   return TRUE;
}

/* ContainerDoc_PasteCntrOutlData
 * -------------------------------
 *
 *      Load the lines stored in a lpSrcStg (stored in CF_CNTROUTL format)
 *  into the document.
 *
 * Return the number of items added
 */
int ContainerDoc_PasteCntrOutlData(
      LPCONTAINERDOC          lpDestContainerDoc,
      LPSTORAGE               lpSrcStg,
      int                     nStartIndex
)
{
   int nCount;
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOUTLINEDOC lpDestOutlineDoc = (LPOUTLINEDOC)lpDestContainerDoc;
   LPOUTLINEDOC lpSrcOutlineDoc;
   LPLINELIST   lpSrcLL;

   // create a temp document that will be used to load the lpSrcStg data.
   lpSrcOutlineDoc = (LPOUTLINEDOC)OutlineApp_CreateDoc(lpOutlineApp, FALSE);
   if ( ! lpSrcOutlineDoc )
      return 0;

   if (! OutlineDoc_LoadFromStg(lpSrcOutlineDoc, lpSrcStg))
      goto error;

   /* copy all lines from the SrcDoc to the DestDoc. */
   lpSrcLL = &lpSrcOutlineDoc->m_LineList;
   nCount = LineList_CopySelToDoc(lpSrcLL, NULL, lpDestOutlineDoc);

   if (lpSrcOutlineDoc)            // destroy temporary document.
      OutlineDoc_Close(lpSrcOutlineDoc, OLECLOSE_NOSAVE);

   return nCount;

error:
   if (lpSrcOutlineDoc)            // destroy temporary document.
      OutlineDoc_Close(lpSrcOutlineDoc, OLECLOSE_NOSAVE);

   return 0;
}

/* ContainerDoc_QueryPasteFromData
** -------------------------------
**
**    Check if the IDataObject* offers data in a format that we can
**    paste. The IDataObject* may come from the clipboard
**    (GetClipboard) or from a drag/drop operation.
**    In this function we look if one of the following formats is
**    offered:
**              CF_OUTLINE
**              <OLE object -- CF_EMBEDSOURCE or CF_EMBEDDEDOBJECT>
**              CF_TEXT
**
**    NOTE: fLink is specified and CF_LINKSOURCE is available then TRUE
**    is returned, else FALSE.
**
**    Returns TRUE if paste can be performed
**            FALSE if paste is not possible.
*/
BOOL ContainerDoc_QueryPasteFromData(
      LPCONTAINERDOC          lpContainerDoc,
      LPDATAOBJECT            lpSrcDataObj,
      BOOL                    fLink
)
{
   LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;

   if (fLink) {
      /* check if we can paste a Link to the data */
      if (OleQueryLinkFromData(lpSrcDataObj) != NOERROR)
         return FALSE;   // linking is NOT possible
   } else {

      int nFmtEtc;

      nFmtEtc = OleStdGetPriorityClipboardFormat(
            lpSrcDataObj,
            lpOleApp->m_arrPasteEntries,
            lpOleApp->m_nPasteEntries
         );

      if (nFmtEtc < 0)
         return FALSE;   // there is no format we like
   }

   return TRUE;
}

/* ContainerDoc_PasteOleObject
** ---------------------------
**
**    Embed or link an OLE object. the source of the data is a pointer
**    to an IDataObject. normally this lpSrcDataObj comes from the
**    clipboard after call OleGetClipboard.
**
**    dwCreateType controls what type of object will created:
**    OLECREATEFROMDATA_LINK -- OleCreateLinkFromData will be called
**    OLECREATEFROMDATA_OBJECT -- OleCreateFromData will be called
**    OLECREATEFROMDATA_STATIC -- OleCreateStaticFromData will be called
**                                  cfFormat controls the type of static
**    a CONTAINERLINE object is created to manage the OLE object. this
**    CONTAINERLINE is added to the ContainerDoc after line nIndex.
**
*/
int ContainerDoc_PasteOleObject(
      LPCONTAINERDOC          lpContainerDoc,
      LPDATAOBJECT            lpSrcDataObj,
      DWORD                   dwCreateType,
      CLIPFORMAT              cfFormat,
      int                     nIndex,
      BOOL                    fDisplayAsIcon,
      HGLOBAL                 hMetaPict,
      LPSIZEL                 lpSizelInSrc
)
{
   LPLINELIST          lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   LPLINE              lpLine = NULL;
   HDC                 hDC;
   int                 nTab = 0;
   OLECHAR             szStgName[CWCSTORAGENAME];
   LPCONTAINERLINE     lpContainerLine = NULL;

   ContainerDoc_GetNextStgName(lpContainerDoc, szStgName, CWCSTORAGENAME);

   /* default the new line to have the same indent as previous line */
   lpLine = LineList_GetLine(lpLL, nIndex);
   if (lpLine)
      nTab = Line_GetTabLevel(lpLine);

   hDC = LineList_GetDC(lpLL);

   lpContainerLine = ContainerLine_CreateFromData(
         hDC,
         nTab,
         lpContainerDoc,
         lpSrcDataObj,
         dwCreateType,
         cfFormat,
         fDisplayAsIcon,
         hMetaPict,
         szStgName
      );
   LineList_ReleaseDC(lpLL, hDC);

   if (! lpContainerLine)
      goto error;

   /* add a ContainerLine object to the document's LineList. The
   **    ContainerLine manages the rectangle on the screen occupied by
   **    the OLE object. later when the app is updated to support
   **    extended layout, there could be more than one Line associated
   **    with the OLE object.
   */

   LineList_AddLine(lpLL, (LPLINE)lpContainerLine, nIndex);

   /* NOTE: if the source of the OLE object just pasted, passed a
   **    non-zero sizel in the ObjectDescriptor, then we will try to
   **    keep the object the same size as it is in the source. this
   **    may be a scaled size if the object had been resized in the
   **    source container. if the source did not give a valid sizel,
   **    then we will retrieve the size of the object by calling
   **    IViewObject2::GetExtent.
   */
   if (lpSizelInSrc && (lpSizelInSrc->cx != 0 || lpSizelInSrc->cy != 0)) {
      ContainerLine_UpdateExtent(lpContainerLine, lpSizelInSrc);
   } else
      ContainerLine_UpdateExtent(lpContainerLine, NULL);

   OutlineDoc_SetModified((LPOUTLINEDOC)lpContainerDoc, TRUE, TRUE, TRUE);

   return 1;   // one line added to LineList

error:
   // NOTE: if ContainerLine_CreateFromClip failed
   OutlineApp_ErrorMessage(g_lpApp, OLESTR("Paste Object failed!"));
   return 0;       // no lines added to line list
}

/* ContainerDoc_GetData
 * --------------------
 *
 * Render data from the document on a CALLEE allocated STGMEDIUM.
 *      This routine is called via IDataObject::GetData.
 */
HRESULT ContainerDoc_GetData (
      LPCONTAINERDOC          lpContainerDoc,
      LPFORMATETC             lpformatetc,
      LPSTGMEDIUM             lpMedium
)
{
   LPOLEDOC  lpOleDoc = (LPOLEDOC)lpContainerDoc;
   LPOUTLINEDOC  lpOutlineDoc = (LPOUTLINEDOC)lpContainerDoc;
   LPCONTAINERAPP lpContainerApp = (LPCONTAINERAPP)g_lpApp;
   LPOLEAPP  lpOleApp = (LPOLEAPP)lpContainerApp;
   LPOUTLINEAPP  lpOutlineApp = (LPOUTLINEAPP)lpContainerApp;
   HRESULT hrErr;
   SCODE sc;

   // NOTE: we must set out pointer parameters to NULL
   lpMedium->pUnkForRelease = NULL;

   /* NOTE: we must set all out pointer parameters to NULL. */
   lpMedium->tymed = TYMED_NULL;
   lpMedium->pUnkForRelease = NULL;    // we transfer ownership to caller
   lpMedium->u.hGlobal = NULL;

   if (lpformatetc->cfFormat == lpContainerApp->m_cfCntrOutl) {

      /* NOTE: currently OLE does NOT support remoting a root
      **    level IStorage (either memory or file based) as an OUT
      **    parameter. thus, we can NOT support GetData for this
      **    TYMED_ISTORAGE based format. the caller MUST call GetDataHere.
      */
      sc = DV_E_FORMATETC;
      goto error;

   } else if (!lpContainerDoc->m_fEmbeddedObjectAvail &&
         lpformatetc->cfFormat == lpOutlineApp->m_cfOutline) {
      // Verify caller asked for correct medium
      if (!(lpformatetc->tymed & TYMED_HGLOBAL)) {
         sc = DV_E_FORMATETC;
         goto error;
      }

      lpMedium->u.hGlobal = OutlineDoc_GetOutlineData(lpOutlineDoc, NULL);
      if (! lpMedium->u.hGlobal) {
         sc = E_OUTOFMEMORY;
         goto error;

}

      lpMedium->tymed = TYMED_HGLOBAL;
      OleDbgOut3("ContainerDoc_GetData: rendered CF_OUTLINE\r\n");
      return NOERROR;

   } else if (!lpContainerDoc->m_fEmbeddedObjectAvail &&
         lpformatetc->cfFormat == CF_TEXT) {
      // Verify caller asked for correct medium
      if (!(lpformatetc->tymed & TYMED_HGLOBAL)) {
         sc = DV_E_FORMATETC;
         goto error;
      }

      lpMedium->u.hGlobal = OutlineDoc_GetTextData (
            (LPOUTLINEDOC)lpContainerDoc,
            NULL
      );
      if (! lpMedium->u.hGlobal) {
         sc = E_OUTOFMEMORY;
         goto error;
      }

      lpMedium->tymed = TYMED_HGLOBAL;
      OleDbgOut3("ContainerDoc_GetData: rendered CF_TEXT\r\n");
      return NOERROR;

   } else if ( lpformatetc->cfFormat == lpOleApp->m_cfObjectDescriptor ||
      (lpformatetc->cfFormat == lpOleApp->m_cfLinkSrcDescriptor &&
         lpOleDoc->m_fLinkSourceAvail) ) {
      // Verify caller asked for correct medium
      if (!(lpformatetc->tymed & TYMED_HGLOBAL)) {
         sc = DV_E_FORMATETC;
         goto error;
      }

      lpMedium->u.hGlobal = OleDoc_GetObjectDescriptorData (
            (LPOLEDOC)lpContainerDoc,
            NULL
      );
      if (! lpMedium->u.hGlobal) {
         sc = E_OUTOFMEMORY;
         goto error;
      }

      lpMedium->tymed = TYMED_HGLOBAL;
#if defined( _DEBUG )
      if (lpformatetc->cfFormat == lpOleApp->m_cfObjectDescriptor)
         OleDbgOut3(
            "ContainerDoc_GetData: rendered CF_OBJECTDESCRIPTOR\r\n");
      else
         OleDbgOut3(
            "ContainerDoc_GetData: rendered CF_LINKSRCDESCRIPTOR\r\n");
#endif
      return NOERROR;

   } else if (lpContainerDoc->m_fEmbeddedObjectAvail) {

      /* NOTE: if this document contains a single OLE object
      **    (ie. cfEmbeddedObject data format is available), then
      **    the formats offered via our IDataObject must include
      **    the formats available from the OLE object itself.
      **    thus, we delegate this call to the IDataObject* of the
      **    OLE object.
      */

      if (lpformatetc->cfFormat == lpOleApp->m_cfEmbeddedObject) {
         LPPERSISTSTORAGE lpPersistStg =
               (LPPERSISTSTORAGE)ContainerDoc_GetSingleOleObject(
                     lpContainerDoc,
                     &IID_IPersistStorage,
                     NULL
         );

         if (! lpPersistStg)
            return DV_E_FORMATETC;

         /* render CF_EMBEDDEDOBJECT by asking the object to save
         **    into a temporary, DELETEONRELEASE pStg allocated by us.
         */

         hrErr = OleStdGetOleObjectData(
               lpPersistStg,
               lpformatetc,
               lpMedium,
               FALSE   /* fUseMemory -- (use file-base stg) */
         );
         OleStdRelease((LPUNKNOWN)lpPersistStg);
         if (hrErr != NOERROR) {
            sc = hrErr;
            goto error;
         }
         OleDbgOut3("ContainerDoc_GetData: rendered CF_EMBEDDEDOBJECT\r\n");
         return hrErr;

      } else if (lpformatetc->cfFormat == CF_METAFILEPICT) {

         /* NOTE: as a container which draws objects, when a single
         **    OLE object is copied, we can give the Metafile picture of
         **    the object.
         */
         LPCONTAINERLINE lpContainerLine;
         LPOLEOBJECT lpOleObj;
         SIZEL sizelOleObject;

         // Verify caller asked for correct medium
         if (!(lpformatetc->tymed & TYMED_MFPICT)) {
            sc = DV_E_FORMATETC;
            goto error;
         }

         lpOleObj = (LPOLEOBJECT)ContainerDoc_GetSingleOleObject(
               lpContainerDoc,
               &IID_IOleObject,
               (LPCONTAINERLINE FAR*)&lpContainerLine
         );

         if (! lpOleObj) {
            sc = E_OUTOFMEMORY;     // could not load object
            goto error;
         }
         if (lpformatetc->dwAspect & lpContainerLine->m_dwDrawAspect) {
            LPLINE lpLine = (LPLINE)lpContainerLine;

            /* render CF_METAFILEPICT by drawing the object into
            **    a metafile DC
            */

            /* NOTE: Get size that object is being drawn. If the
            **    object has been scaled because the user resized the
            **    object, then we want to render a metafile with the
            **    scaled size.
            */
            sizelOleObject.cx = lpLine->m_nWidthInHimetric;
            sizelOleObject.cy = lpLine->m_nHeightInHimetric;

            lpMedium->u.hGlobal = OleStdGetMetafilePictFromOleObject(
                  lpOleObj,
                  lpContainerLine->m_dwDrawAspect,
                  (LPSIZEL)&sizelOleObject,
                  lpformatetc->ptd
            );
            OleStdRelease((LPUNKNOWN)lpOleObj);
            if (! lpMedium->u.hGlobal) {
               sc = E_OUTOFMEMORY;
               goto error;
            }

            lpMedium->tymed = TYMED_MFPICT;
            OleDbgOut3("ContainerDoc_GetData: rendered CF_METAFILEPICT\r\n");
            return NOERROR;
         } else {
            // improper aspect requested
            OleStdRelease((LPUNKNOWN)lpOleObj);
            return DV_E_FORMATETC;
         }

      } else if (lpformatetc->cfFormat == lpOleApp->m_cfLinkSource) {
         if (lpOleDoc->m_fLinkSourceAvail) {
            LPMONIKER lpmk;

            lpmk = ContainerLine_GetFullMoniker(
                  lpContainerDoc->m_lpSrcContainerLine,
                  GETMONIKER_FORCEASSIGN
            );
            if (lpmk) {
               hrErr = OleStdGetLinkSourceData(
                     lpmk,
                     &lpContainerDoc->m_clsidOleObjCopied,
                     lpformatetc,
                     lpMedium
               );
               OleStdRelease((LPUNKNOWN)lpmk);
               if (hrErr != NOERROR) {
                  sc = hrErr;
                  goto error;
               }
               OleDbgOut3("ContainerDoc_GetData: rendered CF_LINKSOURCE\r\n");
               return hrErr;
            } else {
               sc = DV_E_FORMATETC;
               goto error;
            }
         } else {
            sc = DV_E_FORMATETC;
            goto error;
         }

      }
#if defined( OPTIONAL_ADVANCED_DATA_TRANSFER )
      /* NOTE: optionally, a container that wants to have a
      **    potentially richer data transfer, can enumerate the data
      **    formats from the OLE object's cache and offer them too. if
      **    the object has a special handler, then it might be able to
      **    render additional data formats. in this case, the
      **    container must delegate the GetData call to the object if
      **    it does not directly support the format.
      **
      **    CNTROUTL does NOT enumerate the cache; it implements the
      **    simpler strategy of offering a static list of formats.
      **    thus the delegation is NOT required.
      */
     else {

         /* NOTE: we delegate this call to the IDataObject* of the
         **    OLE object.
         */
         LPDATAOBJECT lpDataObj;

         lpDataObj = (LPDATAOBJECT)ContainerDoc_GetSingleOleObject(
               lpContainerDoc,
               &IID_IDataObject,
               NULL
         );

         if (! lpDataObj) {
            sc = DV_E_FORMATETC;
            goto error;
         }

         OLEDBG_BEGIN2("ContainerDoc_GetData: delegate to OLE obj\r\n")
         hrErr=lpDataObj->lpVtbl->GetData(lpDataObj,lpformatetc,lpMedium);
         OLEDBG_END2

         OleStdRelease((LPUNKNOWN)lpDataObj);
         return hrErr;
      }
#endif  // ! OPTIONAL_ADVANCED_DATA_TRANSFER

   }

   // if we get here then we do NOT support the requested format
   sc = DV_E_FORMATETC;

error:
   return sc;
}

/* ContainerDoc_GetDataHere
 * ------------------------
 *
 * Render data from the document on a CALLER allocated STGMEDIUM.
 *      This routine is called via IDataObject::GetDataHere.
 */
HRESULT ContainerDoc_GetDataHere (
      LPCONTAINERDOC          lpContainerDoc,
      LPFORMATETC             lpformatetc,
      LPSTGMEDIUM             lpMedium
)
{
   LPOLEDOC  lpOleDoc = (LPOLEDOC)lpContainerDoc;
   LPOUTLINEDOC  lpOutlineDoc = (LPOUTLINEDOC)lpContainerDoc;
   LPCONTAINERAPP lpContainerApp = (LPCONTAINERAPP)g_lpApp;
   LPOLEAPP  lpOleApp = (LPOLEAPP)lpContainerApp;
   LPOUTLINEAPP  lpOutlineApp = (LPOUTLINEAPP)lpContainerApp;
   HRESULT hrErr;

   // NOTE: lpMedium is an IN parameter. we should NOT set
   //           lpMedium->pUnkForRelease to NULL

   // we only support  IStorage medium
   if (lpformatetc->cfFormat == lpContainerApp->m_cfCntrOutl) {
      if (!(lpformatetc->tymed & TYMED_ISTORAGE))
         return DV_E_FORMATETC;

      if (lpMedium->tymed == TYMED_ISTORAGE) {
         /* Caller has allocated the storage. we must copy all of our
         **    data into his storage.
         */

         /*  NOTE: we must be sure to write our class ID into our
         **    storage. this information is used by OLE to determine the
         **    class of the data stored in our storage.
         */
         if((hrErr=WriteClassStg(lpMedium->u.pstg,&CLSID_APP)) != NOERROR)
            return hrErr;

         OutlineDoc_SaveSelToStg(
               (LPOUTLINEDOC)lpContainerDoc,
               NULL,   /* entire doc */
               lpContainerApp->m_cfCntrOutl,
               lpMedium->u.pstg,
               FALSE,  /* fSameAsLoad */
               FALSE   /* fRemember */
         );
         OleStdCommitStorage(lpMedium->u.pstg);

         OleDbgOut3("ContainerDoc_GetDataHere: rendered CF_CNTROUTL\r\n");
         return NOERROR;
      } else {
         // we only support IStorage medium
         return DV_E_FORMATETC;
      }

   } else if (lpContainerDoc->m_fEmbeddedObjectAvail) {

      /* NOTE: if this document contains a single OLE object
      **    (ie. cfEmbeddedObject data format is available), then
      **    the formats offered via our IDataObject must include
      **    CF_EMBEDDEDOBJECT and the formats available from the OLE
      **    object itself.
      */

      if (lpformatetc->cfFormat == lpOleApp->m_cfEmbeddedObject) {
         LPPERSISTSTORAGE lpPersistStg =
               (LPPERSISTSTORAGE)ContainerDoc_GetSingleOleObject(
                     lpContainerDoc,
                     &IID_IPersistStorage,
                     NULL
         );

         if (! lpPersistStg) {
            return E_OUTOFMEMORY;
         }
         /* render CF_EMBEDDEDOBJECT by asking the object to save
         **    into the IStorage allocated by the caller.
         */

         hrErr = OleStdGetOleObjectData(
               lpPersistStg,
               lpformatetc,
               lpMedium,
               FALSE   /* fUseMemory -- N/A */
         );
         OleStdRelease((LPUNKNOWN)lpPersistStg);
         if (hrErr != NOERROR) {
            return hrErr;
         }
         OleDbgOut3("ContainerDoc_GetDataHere: rendered CF_EMBEDDEDOBJECT\r\n");
         return hrErr;

      } else if (lpformatetc->cfFormat == lpOleApp->m_cfLinkSource) {
         if (lpOleDoc->m_fLinkSourceAvail) {
            LPMONIKER lpmk;

            lpmk = ContainerLine_GetFullMoniker(
                  lpContainerDoc->m_lpSrcContainerLine,
                  GETMONIKER_FORCEASSIGN
            );
            if (lpmk) {
               hrErr = OleStdGetLinkSourceData(
                     lpmk,
                     &lpContainerDoc->m_clsidOleObjCopied,
                     lpformatetc,
                     lpMedium
               );
               OleStdRelease((LPUNKNOWN)lpmk);
               OleDbgOut3("ContainerDoc_GetDataHere: rendered CF_LINKSOURCE\r\n");
               return hrErr;
            } else {
               return E_FAIL;
            }
         } else {
            return DV_E_FORMATETC;
         }

      } else {
#if !defined( OPTIONAL_ADVANCED_DATA_TRANSFER )
         return DV_E_FORMATETC;
#endif
#if defined( OPTIONAL_ADVANCED_DATA_TRANSFER )
         /* NOTE: optionally, a container that wants to have a
         **    potentially richer data transfer, can enumerate the data
         **    formats from the OLE object's cache and offer them too. if
         **    the object has a special handler, then it might be able to
         **    render additional data formats. in this case, the
         **    container must delegate the GetData call to the object if
         **    it does not directly support the format.
         **
         **    CNTROUTL does NOT enumerate the cache; it implements the
         **    simpler strategy of offering a static list of formats.
         **    thus the delegation is NOT required.
         */
         /* NOTE: we delegate this call to the IDataObject* of the
         **    OLE object.
         */
         LPDATAOBJECT lpDataObj;

         lpDataObj = (LPDATAOBJECT)ContainerDoc_GetSingleOleObject(
               lpContainerDoc,
               &IID_IDataObject,
               NULL
         );

         if (! lpDataObj)
            return DV_E_FORMATETC;

         OLEDBG_BEGIN2("ContainerDoc_GetDataHere: delegate to OLE obj\r\n")
         hrErr = lpDataObj->lpVtbl->GetDataHere(
               lpDataObj,
               lpformatetc,
               lpMedium
         );
         OLEDBG_END2

         OleStdRelease((LPUNKNOWN)lpDataObj);
         return hrErr;
#endif  // OPTIONAL_ADVANCED_DATA_TRANSFER
      }
   } else {
      return DV_E_FORMATETC;
   }
}

/* ContainerDoc_QueryGetData
 * -------------------------
 *
 * Answer if a particular data format is supported via GetData/GetDataHere.
 *      This routine is called via IDataObject::QueryGetData.
 */
HRESULT ContainerDoc_QueryGetData (
      LPCONTAINERDOC          lpContainerDoc,
      LPFORMATETC             lpformatetc
)
{
   LPOLEDOC  lpOleDoc = (LPOLEDOC)lpContainerDoc;
   LPOUTLINEDOC  lpOutlineDoc = (LPOUTLINEDOC)lpContainerDoc;
   LPCONTAINERAPP lpContainerApp = (LPCONTAINERAPP)g_lpApp;
   LPOLEAPP  lpOleApp = (LPOLEAPP)lpContainerApp;
   LPOUTLINEAPP  lpOutlineApp = (LPOUTLINEAPP)lpContainerApp;
   LPDATAOBJECT  lpDataObj = NULL;
   LPCONTAINERLINE lpContainerLine = NULL;
   SCODE sc;
   HRESULT hrErr;

   if (lpContainerDoc->m_fEmbeddedObjectAvail) {
      lpDataObj = (LPDATAOBJECT)ContainerDoc_GetSingleOleObject(
               lpContainerDoc,
               &IID_IDataObject,
               (LPCONTAINERLINE FAR*)&lpContainerLine
      );
   }

   /* Caller is querying if we support certain format but does not
   **    want any data actually returned.
   */
   if (lpformatetc->cfFormat == lpContainerApp->m_cfCntrOutl) {
      // we only support ISTORAGE medium
      sc =  OleStdQueryFormatMedium(lpformatetc, TYMED_ISTORAGE) ;

   } else if (lpformatetc->cfFormat == lpOleApp->m_cfEmbeddedObject &&
         lpContainerDoc->m_fEmbeddedObjectAvail ) {
      sc =  OleStdQueryOleObjectData(lpformatetc) ;

   } else if (lpformatetc->cfFormat == lpOleApp->m_cfLinkSource &&
         lpOleDoc->m_fLinkSourceAvail) {
      sc =  OleStdQueryLinkSourceData(lpformatetc) ;

   // CF_TEXT and CF_OUTLINE are NOT supported when single object is copied
   } else if (!lpContainerDoc->m_fEmbeddedObjectAvail &&
         (lpformatetc->cfFormat == (lpOutlineApp)->m_cfOutline ||
          lpformatetc->cfFormat == CF_TEXT) ) {
      // we only support HGLOBAL medium
      sc =  OleStdQueryFormatMedium(lpformatetc, TYMED_HGLOBAL) ;

   } else if ( lpformatetc->cfFormat == lpOleApp->m_cfObjectDescriptor ||
      (lpformatetc->cfFormat == lpOleApp->m_cfLinkSrcDescriptor &&
         lpOleDoc->m_fLinkSourceAvail) ) {
      sc =  OleStdQueryObjectDescriptorData(lpformatetc) ;

   } else if (lpformatetc->cfFormat == CF_METAFILEPICT &&
         lpContainerDoc->m_fEmbeddedObjectAvail && lpContainerLine &&
         (lpformatetc->dwAspect & lpContainerLine->m_dwDrawAspect)) {

      /* NOTE: as a container which draws objects, when a single
      **    OLE object is copied, we can give the Metafile picture of
      **    the object.
      */
      // we only support MFPICT medium
      sc =  OleStdQueryFormatMedium(lpformatetc, TYMED_MFPICT) ;

   } else if (lpDataObj) {

      /* NOTE: if this document contains a single OLE object
      **    (ie. cfEmbeddedObject data format is available), then
      **    the formats offered via our IDataObject must include
      **    the formats available from the OLE object itself.
      **    thus we delegate this call to the IDataObject* of the
      **    OLE object.
      */
      OLEDBG_BEGIN2("ContainerDoc_QueryGetData: delegate to OLE obj\r\n")
      hrErr = lpDataObj->lpVtbl->QueryGetData(lpDataObj, lpformatetc);
      OLEDBG_END2

      sc = hrErr;

   } else {
      sc = DV_E_FORMATETC;
   }

   if (lpDataObj)
      OleStdRelease((LPUNKNOWN)lpDataObj);
   return sc;
}

/* ContainerDoc_SetData
 * --------------------
 *
 * Set (modify) data of the document.
 *      This routine is called via IDataObject::SetData.
 */
HRESULT ContainerDoc_SetData (
      LPCONTAINERDOC          lpContainerDoc,
      LPFORMATETC             lpformatetc,
      LPSTGMEDIUM             lpmedium,
      BOOL                    fRelease
)
{
   /* in the container version of Outline, only DataTransferDoc's support
   **    IDataObject interface; the user documents do not support
   **    IDataObject. DataTransferDoc's do not accept SetData calls.
   */
   return DV_E_FORMATETC;
}

/* ContainerDoc_EnumFormatEtc
 * --------------------------
 *
 * Return an enumerator which enumerates the data accepted/offered by
 *      the document.
 *      This routine is called via IDataObject::SetData.
 */
HRESULT ContainerDoc_EnumFormatEtc(
      LPCONTAINERDOC          lpContainerDoc,
      DWORD                   dwDirection,
      LPENUMFORMATETC FAR*    lplpenumFormatEtc
)
{
   LPOLEDOC lpOleDoc = (LPOLEDOC)lpContainerDoc;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpContainerDoc;
   LPOLEAPP  lpOleApp = (LPOLEAPP)g_lpApp;
   LPCONTAINERAPP lpContainerApp = (LPCONTAINERAPP)lpOleApp;
   int nActualFmts;
   int i;
   SCODE sc = S_OK;

   /* the Container-Only version of Outline does NOT offer
   **    IDataObject interface from its User documents.
   */
   if (! lpOutlineDoc->m_fDataTransferDoc)
      return E_FAIL;

   if (dwDirection == DATADIR_GET) {
      if (lpContainerDoc->m_fEmbeddedObjectAvail) {

         /* NOTE: if this document contains a single OLE object
         **    (ie. cfEmbeddedObject data format is available), then
         **    the formats offered via our enumerator must include
         **    the formats available from the OLE object itself. we
         **    have previously set up a special array of FORMATETC's
         **    in OutlineDoc_CreateDataTransferDoc routine which includes
         **    the combination of data we offer directly and data
         **    offered by the OLE object.
         */

         /* If the document does not have a Moniker, then exclude
         **    CF_LINKSOURCE CF_LINKSRCDESCRIPTOR from the list of
         **    formats available. these formats are deliberately
         **    listed last in the array of possible "Get" formats.
         */
         nActualFmts = lpContainerApp->m_nSingleObjGetFmts;
         if (! lpOleDoc->m_fLinkSourceAvail)
            nActualFmts -= 2;

         // set correct dwDrawAspect for METAFILEPICT of object copied
         for (i = 0; i < nActualFmts; i++) {
            if (lpContainerApp->m_arrSingleObjGetFmts[i].cfFormat ==
                                             CF_METAFILEPICT) {
               lpContainerApp->m_arrSingleObjGetFmts[i].dwAspect =
                     lpContainerDoc->m_dwAspectOleObjCopied;
               break;  // DONE
            }
         }
         *lplpenumFormatEtc = OleStdEnumFmtEtc_Create(
               nActualFmts, lpContainerApp->m_arrSingleObjGetFmts);
         if (*lplpenumFormatEtc == NULL)
            sc = E_OUTOFMEMORY;

      } else {

         /* This document does NOT offer cfEmbeddedObject,
         **    therefore we can simply enumerate the
         **    static list of formats that we handle directly.
         */
         *lplpenumFormatEtc = OleStdEnumFmtEtc_Create(
               lpOleApp->m_nDocGetFmts, lpOleApp->m_arrDocGetFmts);
         if (*lplpenumFormatEtc == NULL)
            sc = E_OUTOFMEMORY;
      }
   } else if (dwDirection == DATADIR_SET) {
      /* NOTE: a document that is used to transfer data
      **    (either via the clipboard or drag/drop does NOT
      **    accept SetData on ANY format!
      */
      sc = E_NOTIMPL;

   } else {
      sc = E_NOTIMPL;
   }

   return sc;
}

#if defined( OPTIONAL_ADVANCED_DATA_TRANSFER )
/* NOTE: optionally, a container that wants to have a
**    potentially richer data transfer, can enumerate the data
**    formats from the OLE object's cache and offer them too. if
**    the object has a special handler, then it might be able to
**    render additional data formats.
**
**    CNTROUTL does NOT enumerate the cache; it implements the simpler
**    strategy of offering a static list of formats. the following
**    function is included in order to illustrates how enumerating the
**    cache could be done. CNTROUTL does NOT call this function.
**
*/

/* ContainerDoc_SetupDocGetFmts
** ----------------------------
**    Setup the combined list of formats that this data transfer
**    ContainerDoc which contains a single OLE object should offer.
**
**    NOTE: The list of formats that should be offered when a
**    single OLE object is being transfered include the following:
**          * any formats the container app wants to give
**          * CF_EMBEDDEDOBJECT
**          * CF_METAFILEPICT
**          * any formats that the OLE object's cache can offer directly
**
**    We will offer the following formats in the order given:
**                  1. CF_CNTROUTL
**                  2. CF_EMBEDDEDOBJECT
**                  3. CF_OBJECTDESCRIPTOR
**                  4. CF_METAFILEPICT
**                  5. <data formats from OLE object's cache>
**                  6. CF_LINKSOURCE
**                  7. CF_LINKSRCDESCRIPTOR
*/
BOOL ContainerDoc_SetupDocGetFmts(
      LPCONTAINERDOC          lpContainerDoc,
      LPCONTAINERLINE         lpContainerLine
)
{
   LPOLEDOC lpOleDoc = (LPOLEDOC)lpContainerDoc;
   LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;
   LPCONTAINERAPP lpContainerApp = (LPCONTAINERAPP)g_lpApp;
   LPOLECACHE lpOleCache;
   HRESULT hrErr;
   STATDATA        StatData;
   LPENUMSTATDATA  lpEnumStatData = NULL;
   LPFORMATETC lparrDocGetFmts = NULL;
   UINT nOleObjFmts = 0;
   UINT nTotalFmts;
   UINT i;
   UINT iFmt;

   lpOleCache = (LPOLECACHE)OleStdQueryInterface(
         (LPUNKNOWN)lpContainerLine->m_lpOleObj,
         &IID_IOleCache
   );
   if (lpOleCache) {
      OLEDBG_BEGIN2("IOleCache::EnumCache called\r\n")
      hrErr = lpOleCache->lpVtbl->EnumCache(
            lpOleCache,
            (LPENUMSTATDATA FAR*)&lpEnumStatData
      );
      OLEDBG_END2
   }

   if (lpEnumStatData) {
      /* Cache enumerator is available. count the number of
      **    formats that the OLE object's cache offers.
      */
      while(lpEnumStatData->lpVtbl->Next(
               lpEnumStatData,
               1,
               (LPSTATDATA)&StatData,
               NULL) == NOERROR) {
         nOleObjFmts++;
         // NOTE: we MUST free the TargetDevice
         OleStdFree(StatData.formatetc.ptd);
      }
      lpEnumStatData->lpVtbl->Reset(lpEnumStatData);  // reset for next loop
   }

   /* NOTE: the maximum total number of formats that our IDataObject
   **    could offer equals the sum of the following:
   **          n offered by the OLE object's cache
   **       +  n normally offered by our app
   **       +  1 CF_EMBEDDEDOBJECT
   **       +  1 CF_METAFILEPICT
   **       +  1 CF_LINKSOURCE
   **       +  1 CF_LINKSRCDESCRIPTOR
   **    the actual number of formats that we can offer could be less
   **    than this total if there is any clash between the formats
   **    that we offer directly and those offered by the cache. if
   **    there is a clash, the container's rendering overrides that of
   **    the object. eg.: as a container transfering an OLE object we
   **    should directly offer CF_METAFILEPICT to guarantee that this
   **    format is always available. thus, if the cache offers
   **    CF_METAFILEPICT then it is skipped.
   */
   nTotalFmts = nOleObjFmts + lpOleApp->m_nDocGetFmts + 4;
   lparrDocGetFmts = (LPFORMATETC)New (nTotalFmts * sizeof(FORMATETC));

   OleDbgAssertSz(lparrDocGetFmts != NULL,"Error allocating arrDocGetFmts");
   if (lparrDocGetFmts == NULL)
         return FALSE;

   for (i = 0, iFmt = 0; i < lpOleApp->m_nDocGetFmts; i++) {
      _fmemcpy((LPFORMATETC)&lparrDocGetFmts[iFmt++],
            (LPFORMATETC)&lpOleApp->m_arrDocGetFmts[i],
            sizeof(FORMATETC)
      );
      if (lpOleApp->m_arrDocGetFmts[i].cfFormat ==
         lpContainerApp->m_cfCntrOutl) {
         /* insert CF_EMBEDDEDOBJECT, CF_METAFILEPICT, and formats
         **    available from the OLE object's cache following
         **    CF_CNTROUTL.
         */
         lparrDocGetFmts[iFmt].cfFormat = lpOleApp->m_cfEmbeddedObject;
         lparrDocGetFmts[iFmt].ptd      = NULL;
         lparrDocGetFmts[iFmt].dwAspect = DVASPECT_CONTENT;
         lparrDocGetFmts[iFmt].tymed    = TYMED_ISTORAGE;
         lparrDocGetFmts[iFmt].lindex   = -1;
         iFmt++;
         lparrDocGetFmts[iFmt].cfFormat = CF_METAFILEPICT;
         lparrDocGetFmts[iFmt].ptd      = NULL;
         lparrDocGetFmts[iFmt].dwAspect = lpContainerLine->m_dwDrawAspect;
         lparrDocGetFmts[iFmt].tymed    = TYMED_MFPICT;
         lparrDocGetFmts[iFmt].lindex   = -1;
         iFmt++;

         if (lpEnumStatData) {
            /* Cache enumerator is available. enumerate all of
            **    the formats that the OLE object's cache offers.
            */
            while(lpEnumStatData->lpVtbl->Next(
                  lpEnumStatData,
                  1,
                  (LPSTATDATA)&StatData,
                  NULL) == NOERROR) {
               /* check if the format clashes with one of our fmts */
               if (StatData.formatetc.cfFormat != CF_METAFILEPICT
                  && ! OleStdIsDuplicateFormat(
                        (LPFORMATETC)&StatData.formatetc,
                        lpOleApp->m_arrDocGetFmts,
                        lpOleApp->m_nDocGetFmts)) {
                  OleStdCopyFormatEtc(
                        &(lparrDocGetFmts[iFmt]),&StatData.formatetc);
                  iFmt++;
               }
               // NOTE: we MUST free the TargetDevice
               OleStdFree(StatData.formatetc.ptd);
            }
         }
      }
   }

   if (lpOleCache)
      OleStdRelease((LPUNKNOWN)lpOleCache);

   /* append CF_LINKSOURCE format */
   lparrDocGetFmts[iFmt].cfFormat = lpOleApp->m_cfLinkSource;
   lparrDocGetFmts[iFmt].ptd      = NULL;
   lparrDocGetFmts[iFmt].dwAspect = DVASPECT_CONTENT;
   lparrDocGetFmts[iFmt].tymed    = TYMED_ISTREAM;
   lparrDocGetFmts[iFmt].lindex   = -1;
   iFmt++;

   /* append CF_LINKSRCDESCRIPTOR format */
   lparrDocGetFmts[iFmt].cfFormat = lpOleApp->m_cfLinkSrcDescriptor;
   lparrDocGetFmts[iFmt].ptd      = NULL;
   lparrDocGetFmts[iFmt].dwAspect = DVASPECT_CONTENT;
   lparrDocGetFmts[iFmt].tymed    = TYMED_HGLOBAL;
   lparrDocGetFmts[iFmt].lindex   = -1;
   iFmt++;

   lpContainerDoc->m_lparrDocGetFmts = lparrDocGetFmts;
   lpContainerDoc->m_nDocGetFmts = iFmt;

   if (lpEnumStatData)
      OleStdVerifyRelease(
            (LPUNKNOWN)lpEnumStatData,
            "Cache enumerator not released properly"
      );

   return TRUE;
}
#endif  // OPTIONAL_ADVANCED_DATA_TRANSFER

#endif  // OLE_CNTR


CNTRBASE.C

/*************************************************************************
**
**    OLE 2 Container Sample Code
**
**    cntrbase.c
**
**    This file contains all interfaces, methods and related support
**    functions for the basic OLE Container application. The
**    basic OLE Container application supports being a container for
**    embedded and linked objects.
**    The basic Container application includes the following
**    implementation objects:
**
**    ContainerDoc Object
**      no required interfaces for basic functionality
**      (see linking.c for linking related support)
**      (see clipbrd.c for clipboard related support)
**      (see dragdrop.c for drag/drop related support)
**
**    ContainerLine Object
**    (see cntrline.c for all ContainerLine functions and interfaces)
**      exposed interfaces:
**          IOleClientSite
**          IAdviseSink
**
**    (c) Copyright Microsoft Corp. 1992 - 1997 All Rights Reserved
**
*************************************************************************/

#include "outline.h"

OLEDBGDATA

extern LPOUTLINEAPP             g_lpApp;
extern IOleUILinkContainerVtbl  g_CntrDoc_OleUILinkContainerVtbl;

#if defined( INPLACE_CNTR )
extern BOOL g_fInsideOutContainer;
#endif  // INPLACE_CNTR

// REVIEW: should use string resource for messages
OLECHAR ErrMsgShowObj[] = OLESTR("Could not show object server!");
OLECHAR ErrMsgInsertObj[] = OLESTR("Insert Object failed!");
OLECHAR ErrMsgConvertObj[] = OLESTR("Convert Object failed!");
OLECHAR ErrMsgCantConvert[] = OLESTR("Unable to convert the selection!");
OLECHAR ErrMsgActivateAsObj[] = OLESTR("Activate As Object failed!");

extern OLECHAR ErrMsgSaving[];
extern OLECHAR ErrMsgOpening[];

/* ContainerDoc_Init
 * -----------------
 *
 *  Initialize the fields of a new ContainerDoc object. The doc is initially
 *  not associated with a file or an (Untitled) document. This function sets
 *  the docInitType to DOCTYPE_UNKNOWN. After calling this function the
 *  caller should call:
 *      1.) Doc_InitNewFile to set the ContainerDoc to (Untitled)
 *      2.) Doc_LoadFromFile to associate the ContainerDoc with a file.
 *  This function creates a new window for the document.
 *
 *  NOTE: the window is initially created with a NIL size. it must be
 *        sized and positioned by the caller. also the document is initially
 *        created invisible. the caller must call Doc_ShowWindow
 *        after sizing it to make the document window visible.
 */
BOOL ContainerDoc_Init(LPCONTAINERDOC lpContainerDoc, BOOL fDataTransferDoc)
{
   LPCONTAINERAPP lpContainerApp = (LPCONTAINERAPP)g_lpApp;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpContainerDoc;

   lpOutlineDoc->m_cfSaveFormat             = lpContainerApp->m_cfCntrOutl;
   lpContainerDoc->m_nNextObjNo            = 0L;
   lpContainerDoc->m_lpNewStg              = NULL;
   lpContainerDoc->m_fEmbeddedObjectAvail  = FALSE;
   lpContainerDoc->m_clsidOleObjCopied     = CLSID_NULL;
   lpContainerDoc->m_dwAspectOleObjCopied  = DVASPECT_CONTENT;
   lpContainerDoc->m_lpSrcContainerLine    = NULL;
   lpContainerDoc->m_fShowObject           = TRUE;

#if defined( INPLACE_CNTR )
   lpContainerDoc->m_lpLastIpActiveLine    = NULL;
   lpContainerDoc->m_lpLastUIActiveLine    = NULL;
   lpContainerDoc->m_hWndUIActiveObj       = NULL;
   lpContainerDoc->m_fAddMyUI              = TRUE; // UI needs to be added
   lpContainerDoc->m_cIPActiveObjects      = 0;
   lpContainerApp->m_fMenuHelpMode         = FALSE; // F1 pressed in menu

#if defined( INPLACE_CNTRSVR )
   lpContainerDoc->m_lpTopIPFrame          =
               (LPOLEINPLACEUIWINDOW)&lpContainerDoc->m_OleInPlaceFrame;
   lpContainerDoc->m_lpTopIPDoc            =
               (LPOLEINPLACEUIWINDOW)&lpContainerDoc->m_OleInPlaceDoc;
   lpContainerDoc->m_hSharedMenu           = NULL;
   lpContainerDoc->m_hOleMenu              = NULL;

#endif  // INPLACE_CNTRSVR
#endif  // INPLACE_CNTR

   INIT_INTERFACEIMPL(
         &lpContainerDoc->m_OleUILinkContainer,
         &g_CntrDoc_OleUILinkContainerVtbl,
         lpContainerDoc
   );

   return TRUE;
}

/* ContainerDoc_GetNextLink
 * ------------------------
 *
 *  Update all links in the document. A dialog box will be popped up showing
 *  the progress of the update and allow the user to quit by pushing the
 *  stop button
 */
LPCONTAINERLINE ContainerDoc_GetNextLink(
      LPCONTAINERDOC lpContainerDoc,
      LPCONTAINERLINE lpContainerLine
)
{
   LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   DWORD dwNextLink = 0;
   LPLINE lpLine;
   static int nIndex = 0;

   if (lpContainerLine==NULL)
      nIndex = 0;

   for ( ; nIndex < lpLL->m_nNumLines; nIndex++) {
      lpLine = LineList_GetLine(lpLL, nIndex);

      if (lpLine
         && (Line_GetLineType(lpLine) == CONTAINERLINETYPE)
         && ContainerLine_IsOleLink((LPCONTAINERLINE)lpLine)) {

         nIndex++;
         ContainerLine_LoadOleObject((LPCONTAINERLINE)lpLine);
         return (LPCONTAINERLINE)lpLine;
      }
   }

   return NULL;
}

/* ContainerDoc_UpdateLinks
 * ------------------------
 *
 *  Update all links in the document. A dialog box will be popped up showing
 *  the progress of the update and allow the user to quit by pushing the
 *  stop button
 */
void ContainerDoc_UpdateLinks(LPCONTAINERDOC lpContainerDoc)
{
   int             cLinks;
   BOOL            fAllLinksUpToDate = TRUE;
   HWND            hwndDoc = ((LPOUTLINEDOC)lpContainerDoc)->m_hWndDoc;
   HCURSOR         hCursor;
   LPCONTAINERLINE lpContainerLine = NULL;
   HRESULT         hrErr;
   DWORD           dwUpdateOpt;
   LPOLEAPP        lpOleApp = (LPOLEAPP)g_lpApp;
   BOOL            fPrevEnable1;
   BOOL            fPrevEnable2;

   hCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));

   /* NOTE: we do not want to ever give the Busy/NotResponding
   **    dialogs when we are updating automatic links as part of
   **    opening a document.  even if the link source of data is busy,
   **    we do not want put up the busy dialog. thus we will disable
   **    the dialog and later re-enable them
   */
   OleApp_DisableBusyDialogs(lpOleApp, &fPrevEnable1, &fPrevEnable2);

   /* get total number of automatic links */
   cLinks = 0;
   while (lpContainerLine = ContainerDoc_GetNextLink(
                           lpContainerDoc,
                           lpContainerLine)) {
      hrErr = CntrDoc_LinkCont_GetLinkUpdateOptions(
            (LPOLEUILINKCONTAINER)&lpContainerDoc->m_OleUILinkContainer,
            (DWORD)lpContainerLine,
            (LPDWORD)&dwUpdateOpt
      );
      if (hrErr == NOERROR) {
         if (dwUpdateOpt==OLEUPDATE_ALWAYS) {
            cLinks++;
            if (fAllLinksUpToDate) {
               OLEDBG_BEGIN2("IOleObject::IsUpToDate called\r\n")
               hrErr = lpContainerLine->m_lpOleObj->lpVtbl->IsUpToDate(
                     lpContainerLine->m_lpOleObj);
               OLEDBG_END2
               if (hrErr != NOERROR)
                  fAllLinksUpToDate = FALSE;
            }
         }
      }
#if defined( _DEBUG )
      else
         OleDbgOutHResult("IOleUILinkContainer::GetLinkUpdateOptions returned",hrErr);
#endif

   }

   if (fAllLinksUpToDate)
      goto done; // don't bother user if all links are up-to-date

   SetCursor(hCursor);

   if ((cLinks > 0) && !OleUIUpdateLinks(
         (LPOLEUILINKCONTAINER)&lpContainerDoc->m_OleUILinkContainer,
         hwndDoc,
         APPNAMEA,
         cLinks))
      {
      char szTemp[256];
      wcstombs(szTemp, APPNAME, 255);
      if (ID_PU_LINKS == OleUIPromptUser(
            (WORD)IDD_CANNOTUPDATELINK,
            hwndDoc,
            szTemp)) {
         ContainerDoc_EditLinksCommand(lpContainerDoc);
      }
   }

done:
   // re-enable the Busy/NotResponding dialogs
   OleApp_EnableBusyDialogs(lpOleApp, fPrevEnable1, fPrevEnable2);
}

/* ContainerDoc_SetShowObjectFlag
 * ------------------------------
 *
 *  Set/Clear the ShowObject flag of ContainerDoc
 */
void ContainerDoc_SetShowObjectFlag(LPCONTAINERDOC lpContainerDoc, BOOL fShow)
{
   if (!lpContainerDoc)
      return;

   lpContainerDoc->m_fShowObject = fShow;
}

/* ContainerDoc_GetShowObjectFlag
 * ------------------------------
 *
 *  Get the ShowObject flag of ContainerDoc
 */
BOOL ContainerDoc_GetShowObjectFlag(LPCONTAINERDOC lpContainerDoc)
{
   if (!lpContainerDoc)
      return FALSE;

   return lpContainerDoc->m_fShowObject;
}

/* ContainerDoc_InsertOleObjectCommand
 * -----------------------------------
 *
 * Insert a new OLE object in the ContainerDoc.
 */
void ContainerDoc_InsertOleObjectCommand(LPCONTAINERDOC lpContainerDoc)
{
   LPLINELIST              lpLL =&((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   LPLINE                  lpLine = NULL;
   HDC                     hDC;
   int                     nTab = 0;
   int                     nIndex = LineList_GetFocusLineIndex(lpLL);
   LPCONTAINERLINE         lpContainerLine=NULL;
   OLECHAR                 szStgName[CWCSTORAGENAME];
   UINT                    uRet;
   OLEUIINSERTOBJECT       io;
   char                   szFile[OLEUI_CCHPATHMAX];
   OLECHAR                  szFileW[OLEUI_CCHPATHMAX];
   DWORD                   dwOleCreateType;
   BOOL                    fDisplayAsIcon;
   HCURSOR                 hPrevCursor;

   _fmemset((LPOLEUIINSERTOBJECT)&io, 0, sizeof(OLEUIINSERTOBJECT));
   io.cbStruct=sizeof(OLEUIINSERTOBJECT);
   io.dwFlags=IOF_SELECTCREATENEW | IOF_SHOWHELP;
   io.hWndOwner=((LPOUTLINEDOC)lpContainerDoc)->m_hWndDoc;
   io.lpszFile=szFile;
   io.cchFile=OLEUI_CCHPATHMAX;
//   _fmemset(/*(LPOLESTR)*/szFile, 0, OLEUI_CCHPATHMAX);
   _fmemset(/*(LPOLESTR)*/szFile, 0, sizeof(szFile));

#if defined( OLE_VERSION )
   OleApp_PreModalDialog((LPOLEAPP)g_lpApp, (LPOLEDOC)lpContainerDoc);
#endif

   OLEDBG_BEGIN3("OleUIInsertObject called\r\n")
   uRet=OleUIInsertObject((LPOLEUIINSERTOBJECT)&io);
   OLEDBG_END3

#if defined( OLE_VERSION )
   OleApp_PostModalDialog((LPOLEAPP)g_lpApp, (LPOLEDOC)lpContainerDoc);
#endif

   if (OLEUI_OK != uRet)
      return;     // user canceled dialog

   // this may take a while, put up hourglass cursor
   hPrevCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));

   fDisplayAsIcon = (io.dwFlags & IOF_CHECKDISPLAYASICON ? TRUE : FALSE);

   // make up a storage name for the OLE object
   ContainerDoc_GetNextStgName(lpContainerDoc, szStgName, CWCSTORAGENAME);

   /* default the new line to have the same indent as previous line */
   lpLine = LineList_GetLine(lpLL, nIndex);
   if (lpLine)
      nTab = Line_GetTabLevel(lpLine);

   hDC = LineList_GetDC(lpLL);

   if ((io.dwFlags & IOF_SELECTCREATENEW))
      dwOleCreateType = IOF_SELECTCREATENEW;
   else if ((io.dwFlags & IOF_CHECKLINK))
      dwOleCreateType = IOF_CHECKLINK;
   else
      dwOleCreateType = IOF_SELECTCREATEFROMFILE;

   A2W(szFile, szFileW, OLEUI_CCHPATHMAX);
   lpContainerLine = ContainerLine_Create(
         dwOleCreateType,
         hDC,
         nTab,
         lpContainerDoc,
         &io.clsid,
         szFileW,
         fDisplayAsIcon,
         io.hMetaPict,
         szStgName
   );

   if (!lpContainerLine)
      goto error;         // creation of OLE object FAILED

   if (io.hMetaPict) {
      OleUIMetafilePictIconFree(io.hMetaPict);    // clean up metafile
   }

   /* add a ContainerLine object to the document's LineList. The
   **    ContainerLine manages the rectangle on the screen occupied by
   **    the OLE object.
   */

   LineList_AddLine(lpLL, (LPLINE)lpContainerLine, nIndex);

   /* before calling DoVerb(OLEIVERB_SHOW), check to see if the object
   **    has any initial extents.
   */
   ContainerLine_UpdateExtent(lpContainerLine, NULL);

   /* If a new embedded object was created, tell the object server to
   **    make itself visible (show itself).
   **    NOTE: the standard OLE 2 User Model is to only call
   **    IOleObject::DoVerb(OLEIVERB_SHOW...) if a new object is
   **    created. specifically, it should NOT be calld if the object
   **    is created from file or link to file.
   */
   if (dwOleCreateType == IOF_SELECTCREATENEW) {
      if (! ContainerLine_DoVerb(
            lpContainerLine, OLEIVERB_SHOW, NULL, TRUE, TRUE)) {
         OutlineApp_ErrorMessage(g_lpApp, ErrMsgShowObj);
      }

      /* NOTE: we will immediately force a save of the object
      **    to guarantee that a valid initial object is saved
      **    with our document. if the object is a OLE 1.0 object,
      **    then it may exit without update. by forcing this
      **    initial save we consistently always have a valid
      **    object even if it is a OLE 1.0 object that exited
      **    without saving. if we did NOT do this save here, then
      **    we would have to worry about deleting the object if
      **    it was a OLE 1.0 object that closed without saving.
      **    the OLE 2.0 User Model dictates that the object
      **    should always be valid after CreateNew performed. the
      **    user must explicitly delete it.
      */
      ContainerLine_SaveOleObjectToStg(
            lpContainerLine,
            lpContainerLine->m_lpStg,
            lpContainerLine->m_lpStg,
            TRUE    /* fRemember */
      );
   }
#if defined( INPLACE_CNTR )
   else if (dwOleCreateType == IOF_SELECTCREATEFROMFILE) {
      /* NOTE: an inside-out container should check if the object
      **    created from file is an inside-out and prefers to be
      **    activated when visible type of object. if so, the object
      **    should be immediately activated in-place, BUT NOT UIActived.
      */
      if (g_fInsideOutContainer &&
            lpContainerLine->m_dwDrawAspect == DVASPECT_CONTENT &&
            lpContainerLine->m_fInsideOutObj ) {
         HWND hWndDoc = OutlineDoc_GetWindow((LPOUTLINEDOC)lpContainerDoc);

         ContainerLine_DoVerb(
               lpContainerLine,OLEIVERB_INPLACEACTIVATE,NULL,FALSE,FALSE);

         /* NOTE: following this DoVerb(INPLACEACTIVATE) the
         **    object may have taken focus. but because the
         **    object is NOT UIActive it should NOT have focus.
         **    we will make sure our document has focus.
         */
         SetFocus(hWndDoc);
      }
   }
#endif  // INPLACE_CNTR

   OutlineDoc_SetModified((LPOUTLINEDOC)lpContainerDoc, TRUE, TRUE, TRUE);

   LineList_ReleaseDC(lpLL, hDC);

   SetCursor(hPrevCursor);     // restore original cursor

   return;

error:
   // NOTE: if ContainerLine_Create failed
   LineList_ReleaseDC(lpLL, hDC);

   if (OLEUI_OK == uRet && io.hMetaPict)
      OleUIMetafilePictIconFree(io.hMetaPict);    // clean up metafile

   SetCursor(hPrevCursor);     // restore original cursor
   OutlineApp_ErrorMessage(g_lpApp, ErrMsgInsertObj);
}

void ContainerDoc_EditLinksCommand(LPCONTAINERDOC lpContainerDoc)
{
   UINT        uRet;
   OLEUIEDITLINKS      el;
   LPCONTAINERLINE lpContainerLine = NULL;
   LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;

   _fmemset((LPOLEUIEDITLINKS)&el,0,sizeof(OLEUIEDITLINKS));
   el.cbStruct=sizeof(OLEUIEDITLINKS);
   el.dwFlags=ELF_SHOWHELP;
   el.hWndOwner=((LPOUTLINEDOC)lpContainerDoc)->m_hWndDoc;
   el.lpOleUILinkContainer =
         (LPOLEUILINKCONTAINER)&lpContainerDoc->m_OleUILinkContainer;

#if defined( OLE_VERSION )
   OleApp_PreModalDialog((LPOLEAPP)g_lpApp, (LPOLEDOC)lpContainerDoc);
#endif

   OLEDBG_BEGIN3("OleUIEditLinks called\r\n")
   uRet=OleUIEditLinks((LPOLEUIEDITLINKS)&el);
   OLEDBG_END3

#if defined( OLE_VERSION )
   OleApp_PostModalDialog((LPOLEAPP)g_lpApp, (LPOLEDOC)lpContainerDoc);
#endif

   OleDbgAssert((uRet==1) || (uRet==OLEUI_CANCEL));

}

/* Convert command - brings up the "Convert" dialog
 */
void ContainerDoc_ConvertCommand(
      LPCONTAINERDOC      lpContainerDoc,
      BOOL                fServerNotRegistered
)
{
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpContainerDoc;
   OLEUICONVERT ct;
   UINT         uRet;
   LPDATAOBJECT  lpDataObj;
   LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   LPCONTAINERLINE lpContainerLine = NULL;
   BOOL         fSelIsOleObject;
   int          nIndex;
   STGMEDIUM    medium;
   LPOLESTR     lpErrMsg = NULL;
   HRESULT      hrErr;
   HCURSOR      hPrevCursor;
   BOOL         fMustRun = FALSE;
   BOOL         fMustClose = FALSE;
   BOOL         fObjConverted = FALSE;
   BOOL         fDisplayChanged = FALSE;
   BOOL         fHaveCLSID = FALSE;
   BOOL         fHaveFmtUserType = FALSE;
   OLECHAR      szUserType[128];
   LPOLESTR     lpszUserType;
   LPOLESTR     lpszDefLabel;
   BOOL         fMustActivate;

   /* NOTE: if we came to the Convert dialog because the user
   **    activated a non-registered object, then we should activate
   **    the object after the user has converted it or setup an
   **    ActivateAs server.
   */
   fMustActivate = fServerNotRegistered;

   _fmemset((LPOLEUICONVERT)&ct,0,sizeof(OLEUICONVERT));

   fSelIsOleObject = ContainerDoc_IsSelAnOleObject(
         (LPCONTAINERDOC)lpContainerDoc,
         &IID_IDataObject,
         (LPUNKNOWN FAR*)&lpDataObj,
         &nIndex,
         (LPCONTAINERLINE FAR*)&lpContainerLine
   );

   lpErrMsg = ErrMsgCantConvert;

   if (! fSelIsOleObject)
      goto error;     // can NOT do Convert.

   if (! lpContainerLine) {
      OleStdRelease((LPUNKNOWN)lpDataObj);
      goto error;     // can NOT do Convert.
   }

   ct.cbStruct  = sizeof(OLEUICONVERT);
   ct.dwFlags   = CF_SHOWHELPBUTTON;
   ct.hWndOwner = lpContainerDoc->m_OleDoc.m_OutlineDoc.m_hWndDoc;
   ct.lpszCaption = NULL;
   ct.lpfnHook  = NULL;
   ct.lCustData = 0;
   ct.hInstance = NULL;
   ct.lpszTemplate = NULL;
   ct.hResource = 0;
   ct.fIsLinkedObject = ContainerLine_IsOleLink(lpContainerLine);
   ct.dvAspect = lpContainerLine->m_dwDrawAspect;
   ct.cClsidExclude = 0;
   ct.lpClsidExclude = NULL;

   if (! ct.fIsLinkedObject || !lpContainerLine->m_lpOleLink) {
      /* NOTE: the object is an embedded object. we should first
      **    attempt to read the actual object CLSID, file data
      **    format, and full user type name that is written inside of
      **    the object's storage as this should be the most
      **    definitive information. if this fails we will ask the
      **    object what its class is and attempt to get the rest of
      **    the information out of the REGDB.
      */
      hrErr=ReadClassStg(lpContainerLine->m_lpStg,(CLSID FAR*)&(ct.clsid));
      if (hrErr == NOERROR)
         fHaveCLSID = TRUE;
      else {
         OleDbgOutHResult("ReadClassStg returned", hrErr);
      }
      hrErr = ReadFmtUserTypeStg(
            lpContainerLine->m_lpStg,
            (CLIPFORMAT FAR*)&ct.wFormat,
            &lpszUserType);
      ct.lpszUserType = NULL;
      if (lpszUserType)
      {
          int cch = OLESTRLEN(lpszUserType)+1;
          ct.lpszUserType=OleStdMalloc(cch);
          if (ct.lpszUserType)
          {
              W2A(lpszUserType, ct.lpszUserType, cch);
          }
      }
      if (hrErr == NOERROR)
         fHaveFmtUserType = TRUE;
      else {
         OleDbgOutHResult("ReadFmtUserTypeStg returned", hrErr);
      }
   } else {
      /* NOTE: the object is a linked object. we should give the
      **    DisplayName of the link source as the default icon label.
      */
      OLEDBG_BEGIN2("IOleLink::GetSourceDisplayName called\r\n")
      hrErr = lpContainerLine->m_lpOleLink->lpVtbl->GetSourceDisplayName(
            lpContainerLine->m_lpOleLink, &lpszDefLabel);
      if (lpszDefLabel)
      {
          int cch = OLESTRLEN(lpszDefLabel) + 1;
          ct.lpszDefLabel = OleStdMalloc(cch);
          if (ct.lpszDefLabel)
          {
              W2A(lpszDefLabel, ct.lpszDefLabel, cch);
          }
      }
      OLEDBG_END2
   }

   if (! fHaveCLSID) {
      hrErr = lpContainerLine->m_lpOleObj->lpVtbl->GetUserClassID(
            lpContainerLine->m_lpOleObj,
            (CLSID FAR*)&ct.clsid
      );
      if (hrErr != NOERROR)
         ct.clsid = CLSID_NULL;
   }
   if (! fHaveFmtUserType) {
      ct.wFormat = 0;
      if (OleStdGetUserTypeOfClass(
            (CLSID FAR*)&ct.clsid,szUserType,128,NULL))
      {
          int cch = OLESTRLEN(szUserType) + 1;
          ct.lpszUserType = OleStdMalloc(cch);
          if (ct.lpszUserType)
          {
              W2A(szUserType, ct.lpszUserType, cch);
          }
      } else {
         ct.lpszUserType = NULL;
      }
   }

   if (lpContainerLine->m_dwDrawAspect == DVASPECT_ICON) {
      ct.hMetaPict = OleStdGetData(
            lpDataObj,
            CF_METAFILEPICT,
            NULL,
            DVASPECT_ICON,
            (LPSTGMEDIUM)&medium
      );
   } else {
      ct.hMetaPict = NULL;
   }
   OleStdRelease((LPUNKNOWN)lpDataObj);

#if defined( OLE_VERSION )
   OleApp_PreModalDialog((LPOLEAPP)g_lpApp, (LPOLEDOC)lpContainerDoc);
#endif

   OLEDBG_BEGIN3("OleUIConvert called\r\n")
   uRet = OleUIConvert(&ct);
   OLEDBG_END3

#if defined( OLE_VERSION )
   OleApp_PostModalDialog((LPOLEAPP)g_lpApp, (LPOLEDOC)lpContainerDoc);
#endif

   // this may take a while, put up hourglass cursor
   hPrevCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));

   if (uRet == OLEUI_OK) {

      /*****************************************************************
      **  NOTE: the convert dialog actually allows the user to
      **    change two orthogonal properties of the object: the
      **    object's type/server and the object's display aspect.
      **    first we will execute the ConvertTo/ActivateAs action and
      **    then we will deal with any display aspect change. we want
      **    to be careful to only call IOleObject::Update once
      **    because this is an expensive operation; it results in
      **    launching the object's server.
      *****************************************************************/

      if (ct.dwFlags & CF_SELECTCONVERTTO &&
            ! IsEqualCLSID(&ct.clsid, &ct.clsidNew)) {

         /* user selected CONVERT.
         **
         ** NOTE: to achieve the "Convert To" at this point we
         **    need to take the following steps:
         **    1. unload the object.
         **    2. write the NEW CLSID and NEW user type name
         **       string into the storage of the object,
         **       BUT write the OLD format tag.
         **    3. force an update to force the actual conversion of
         **       the data bits.
         */
         lpErrMsg = ErrMsgConvertObj; // setup correct msg in case of error

         ContainerLine_UnloadOleObject(lpContainerLine, OLECLOSE_SAVEIFDIRTY);

         OLEDBG_BEGIN2("OleStdDoConvert called \r\n")
         hrErr = OleStdDoConvert(
               lpContainerLine->m_lpStg, (REFCLSID)&ct.clsidNew);
         OLEDBG_END2
         if (hrErr != NOERROR)
            goto error;

         // Reload the object
         ContainerLine_LoadOleObject(lpContainerLine);

         /* we need to force the object to run to complete the
         **    conversion. set flag to force OleRun to be called at
         **    end of function.
         */
         fMustRun = TRUE;
         fObjConverted = TRUE;

      } else if (ct.dwFlags & CF_SELECTACTIVATEAS) {
         /* user selected ACTIVATE AS.
         **
         ** NOTE: to achieve the "Activate As" at this point we
         **    need to take the following steps:
         **    1. unload ALL objects of the OLD class that app knows about
         **    2. add the TreatAs tag in the registration database
         **    by calling CoTreatAsClass().
         **    3. lazily it can reload the objects; when the objects
         **    are reloaded the TreatAs will take effect.
         */
         lpErrMsg = ErrMsgActivateAsObj; // setup msg in case of error

         ContainerDoc_UnloadAllOleObjectsOfClass(
               lpContainerDoc,
               (REFCLSID)&ct.clsid,
               OLECLOSE_SAVEIFDIRTY
         );

         OLEDBG_BEGIN2("OleStdDoTreatAsClass called \r\n")
         A2W(ct.lpszUserType, szUserType, 128);
         hrErr = OleStdDoTreatAsClass(szUserType, (REFCLSID)&ct.clsid,
               (REFCLSID)&ct.clsidNew);
         OLEDBG_END2

         // Reload the object
         ContainerLine_LoadOleObject(lpContainerLine);

         fMustActivate = TRUE;   // we should activate this object
      }

      /*****************************************************************
      **  NOTE: now we will try to change the display if
      **    necessary.
      *****************************************************************/

      if (lpContainerLine->m_lpOleObj &&
            ct.dvAspect != lpContainerLine->m_dwDrawAspect) {
         /* user has selected to change display aspect between icon
         **    aspect and content aspect.
         **
         ** NOTE: if we got here because the server was not
         **    registered, then we will NOT delete the object's
         **    original display aspect. because we do not have the
         **    original server, we can NEVER get it back. this is a
         **    safety precaution.
         */

         hrErr = OleStdSwitchDisplayAspect(
               lpContainerLine->m_lpOleObj,
               &lpContainerLine->m_dwDrawAspect,
               ct.dvAspect,
               ct.hMetaPict,
               !fServerNotRegistered,   /* fDeleteOldAspect */
               TRUE,                    /* fSetupViewAdvise */
               (LPADVISESINK)&lpContainerLine->m_AdviseSink,
               (BOOL FAR*)&fMustRun
         );

         if (hrErr == NOERROR)
            fDisplayChanged = TRUE;

#if defined( INPLACE_CNTR )
            ContainerDoc_UpdateInPlaceObjectRects(
                  lpContainerLine->m_lpDoc, nIndex);
#endif

      } else if (ct.dvAspect == DVASPECT_ICON && ct.fObjectsIconChanged) {
         hrErr = OleStdSetIconInCache(
               lpContainerLine->m_lpOleObj,
               ct.hMetaPict
         );

         if (hrErr == NOERROR)
            fDisplayChanged = TRUE;
      }

      /* we deliberately run the object so that the update won't shut
      ** the server down.
      */
      if (fMustActivate || fMustRun) {

         /* if we force the object to run, then shut it down after
         **    the update. do NOT force the object to close if we
         **    want to activate the object or if the object was
         **    already running.
         */
         if (!fMustActivate && !OleIsRunning(lpContainerLine->m_lpOleObj))
            fMustClose = TRUE;  // shutdown after update

         hrErr = ContainerLine_RunOleObject(lpContainerLine);

         if (fObjConverted &&
            FAILED(hrErr) && hrErr!=OLE_E_STATIC) {

            // ERROR: convert of the object failed.
            // revert the storage to restore the original link.
            // (NOTE: static object always return OLE_E_STATIC
            //        when told to run; this is NOT an error here.
            //        the OLE2 libraries have built in handlers for
            //        the static objects that do the conversion.
            ContainerLine_UnloadOleObject(
                  lpContainerLine, OLECLOSE_NOSAVE);
            lpContainerLine->m_lpStg->lpVtbl->Revert(
                  lpContainerLine->m_lpStg);
            goto error;

         } else if (fObjConverted) {
            FORMATETC  FmtEtc;
            DWORD      dwNewConnection;
            LPOLECACHE lpOleCache = (LPOLECACHE)OleStdQueryInterface
                 ((LPUNKNOWN)lpContainerLine->m_lpOleObj,&IID_IOleCache);

            /* NOTE: we need to force the converted object to
            **    setup a new OLERENDER_DRAW cache. it is possible
            **    that the new object needs to cache different data
            **    in order to support drawing than the old object.
            */
            if (lpOleCache &&
                  lpContainerLine->m_dwDrawAspect == DVASPECT_CONTENT) {
               FmtEtc.cfFormat = 0; //NULL; // whatever is needed for Draw
               FmtEtc.ptd      = NULL;
               FmtEtc.dwAspect = DVASPECT_CONTENT;
               FmtEtc.lindex   = -1;
               FmtEtc.tymed    = TYMED_NULL;

               OLEDBG_BEGIN2("IOleCache::Cache called\r\n")
               hrErr = lpOleCache->lpVtbl->Cache(
                     lpOleCache,
                     (LPFORMATETC)&FmtEtc,
                     ADVF_PRIMEFIRST,
                     (LPDWORD)&dwNewConnection
               );
               OLEDBG_END2
#if defined( _DEBUG )
               if (! SUCCEEDED(hrErr))
                  OleDbgOutHResult("IOleCache::Cache returned", hrErr);
#endif
               OleStdRelease((LPUNKNOWN)lpOleCache);

}

            // Close and force object to save; this will commit the stg
            ContainerLine_CloseOleObject(
               lpContainerLine, OLECLOSE_SAVEIFDIRTY);
            fMustClose = FALSE;     // we already closed the object
         }
         if (fMustClose)
            ContainerLine_CloseOleObject(lpContainerLine,OLECLOSE_NOSAVE);
      }

      if (fDisplayChanged) {
         /* the Object's display was changed, force a repaint of
         **    the line. note the extents of the object may have
         **    changed.
         */
         ContainerLine_UpdateExtent(lpContainerLine, NULL);
         LineList_ForceLineRedraw(lpLL, nIndex, TRUE);
      }

      if (fDisplayChanged || fObjConverted) {
         /* mark ContainerDoc as now dirty. if display changed, then
         **    the extents of the object may have changed.
         */
         OutlineDoc_SetModified(lpOutlineDoc, TRUE, TRUE, fDisplayChanged);
      }

      if (fMustActivate) {
         ContainerLine_DoVerb(
               lpContainerLine, OLEIVERB_PRIMARY, NULL, FALSE,FALSE);
      }
   }

   if (ct.lpszUserType)
      OleStdFree(ct.lpszUserType);

   if (ct.lpszDefLabel)
      OleStdFree(ct.lpszDefLabel);

   if (ct.hMetaPict)
      OleUIMetafilePictIconFree(ct.hMetaPict);    // clean up metafile

   SetCursor(hPrevCursor);     // restore original cursor

   return;

error:
   if (ct.lpszUserType)
      OleStdFree(ct.lpszUserType);

   if (ct.hMetaPict)
      OleUIMetafilePictIconFree(ct.hMetaPict);    // clean up metafile

   SetCursor(hPrevCursor);     // restore original cursor
   if (lpErrMsg)
      OutlineApp_ErrorMessage(g_lpApp, lpErrMsg);
}

/* ContainerDoc_CloseAllOleObjects
** -------------------------------
**    Close all OLE objects. This forces all OLE objects to transition
**    from the running state to the loaded state.
**
**    Returns TRUE if all objects closed successfully
**            FALSE if any object could not be closed.
*/
BOOL ContainerDoc_CloseAllOleObjects(
      LPCONTAINERDOC          lpContainerDoc,
      DWORD                   dwSaveOption
)
{
   LPLINELIST  lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   int         i;
   LPLINE      lpLine;
   BOOL        fStatus = TRUE;

   for (i = 0; i < lpLL->m_nNumLines; i++) {
      lpLine=LineList_GetLine(lpLL, i);

      if (lpLine && (Line_GetLineType(lpLine)==CONTAINERLINETYPE))
         if (! ContainerLine_CloseOleObject(
                              (LPCONTAINERLINE)lpLine,dwSaveOption))
            fStatus = FALSE;
   }

   return fStatus;
}

/* ContainerDoc_UnloadAllOleObjectsOfClass
** ---------------------------------------
**    Unload all OLE objects of a particular class. this is necessary
**    when a class level "ActivateAs" (aka. TreatAs) is setup. the user
**    can do this with the Convert dialog. for the TreatAs to take
**    effect, all objects of the class have to loaded and reloaded.
*/
void ContainerDoc_UnloadAllOleObjectsOfClass(
      LPCONTAINERDOC      lpContainerDoc,
      REFCLSID            rClsid,
      DWORD               dwSaveOption
)
{
   LPLINELIST  lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   int         i;
   LPLINE      lpLine;
   CLSID       clsid;
   HRESULT     hrErr;

   for (i = 0; i < lpLL->m_nNumLines; i++) {
      lpLine=LineList_GetLine(lpLL, i);

      if (lpLine && (Line_GetLineType(lpLine)==CONTAINERLINETYPE)) {
         LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)lpLine;

         if (! lpContainerLine->m_lpOleObj)
            continue;       // this object is NOT loaded

         hrErr = lpContainerLine->m_lpOleObj->lpVtbl->GetUserClassID(
               lpContainerLine->m_lpOleObj,
               (CLSID FAR*)&clsid
         );
         if (hrErr == NOERROR &&
               ( IsEqualCLSID((CLSID FAR*)&clsid,rClsid)
                 || IsEqualCLSID(rClsid,&CLSID_NULL) ) ) {
            ContainerLine_UnloadOleObject(lpContainerLine, dwSaveOption);
         }
      }
   }
}

/* ContainerDoc_UpdateExtentOfAllOleObjects
** ----------------------------------------
**    Update the extents of any OLE object that is marked that its size
**    may  have changed. when an IAdviseSink::OnViewChange notification
**    is received, the corresponding ContainerLine is marked
**    (m_fDoGetExtent==TRUE) and a message (WM_U_UPDATEOBJECTEXTENT) is
**    posted to the document indicating that there are dirty objects.
**    when this message is received, this function is called.
*/
void ContainerDoc_UpdateExtentOfAllOleObjects(LPCONTAINERDOC lpContainerDoc)
{
   LPLINELIST  lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   int         i;
   LPLINE      lpLine;
   BOOL        fStatus = TRUE;
#if defined( INPLACE_CNTR )
   int         nFirstUpdate = -1;
#endif

   for (i = 0; i < lpLL->m_nNumLines; i++) {
      lpLine=LineList_GetLine(lpLL, i);

      if (lpLine && (Line_GetLineType(lpLine)==CONTAINERLINETYPE)) {
         LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)lpLine;

         if (lpContainerLine->m_fDoGetExtent) {
            ContainerLine_UpdateExtent(lpContainerLine, NULL);
#if defined( INPLACE_CNTR )
            if (nFirstUpdate == -1)
               nFirstUpdate = i;
#endif
         }
      }
   }

#if defined( INPLACE_CNTR )
   /* NOTE: after changing the extents of any line, we need to
   **    update the PosRect of the In-Place active
   **    objects (if any) that follow the first modified line.
   */
   if (nFirstUpdate != -1)
      ContainerDoc_UpdateInPlaceObjectRects(lpContainerDoc, nFirstUpdate+1);
#endif
}

BOOL ContainerDoc_SaveToFile(
      LPCONTAINERDOC          lpContainerDoc,
      LPCOLESTR               lpszFileName,
      UINT                    uFormat,
      BOOL                    fRemember
)
{
   LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpContainerDoc;
   LPOLEDOC lpOleDoc = (LPOLEDOC)lpContainerDoc;
   LPSTORAGE lpDestStg;
   BOOL fStatus;
   BOOL fMustRelDestStg = FALSE;
   HRESULT hrErr;
#if defined( OPTIONAL )
   FILETIME filetimeBeforeSave;
#endif

   if (fRemember) {
      if (lpszFileName) {
         fStatus = OutlineDoc_SetFileName(
               lpOutlineDoc, (LPOLESTR)lpszFileName, NULL);
         if (! fStatus) goto error;
      }

      /* The ContainerDoc keeps its storage open at all times. it is not
      **    necessary to reopen the file.
      ** if SaveAs is pending, then lpNewStg is the new destination for
      **    the save operation, else the existing storage is the dest.
      */
      lpDestStg = (lpContainerDoc->m_lpNewStg ?
                  lpContainerDoc->m_lpNewStg : lpOleDoc->m_lpStg);

#if defined( OPTIONAL )
      /* NOTE: an automatic link to an embedded object within the
      **    same container document (that uses ItemMonikers) will
      **    always be considered "out-of-date' by OLE. if a container
      **    application REALLY wants to fix this it can do one of the
      **    following:
      **      1. implement a new moniker better than ItemMonikers
      **    that look into the objects storage to find the real last
      **    change time (rather then defaulting to that of the outer
      **    container file).
      ** or   2. using item monikers it is possible to fix the case
      **    where the container document is saved while the embedded
      **    object is running but it will NOT fix the case when the
      **    document is saved when the embedded object was only
      **    loaded. the fix is to:
      **      a. remember the time (T) before the save operation starts
      **      b. call IRunningObjectTable::NoteChangeTime(lpDoc, T)
      **      c. do the saving and commit the file
      **      d. call StgSetTimes to reset the file time to T
      **      e. remember time T in document structure and when the
      **         root storage is finally released reset the file time
      **         again to T (closing the file on DOS sets the time).
      */
      CoFileTimeNow( &filetimeBeforeSave );
      if (lpOleDoc->m_dwRegROT != 0) {
         LPRUNNINGOBJECTTABLE lprot;

         if (GetRunningObjectTable(0,&lprot) == NOERROR)
         {
            OleDbgOut2("IRunningObjectTable::NoteChangeTime called\r\n");
            lprot->lpVtbl->NoteChangeTime(
                  lprot, lpOleDoc->m_dwRegROT, &filetimeBeforeSave );
            lprot->lpVtbl->Release(lprot);
         }
      }
#endif
   } else {
      if (! lpszFileName)
         goto error;

      /* NOTE: since we are preforming a SaveCopyAs operation, we
      **    do not need to have the DocFile open in STGM_TRANSACTED mode.
      **    there is less overhead to use STGM_DIRECT mode.
      */
      hrErr = StgCreateDocfile(
            lpszFileName,
            STGM_READWRITE|STGM_DIRECT|STGM_SHARE_EXCLUSIVE|STGM_CREATE,
            0,
            &lpDestStg
      );
      OleDbgAssertSz(hrErr == NOERROR, "Could not create Docfile");
      if (hrErr != NOERROR) {
            OleDbgOutHResult("StgCreateDocfile returned", hrErr);
         goto error;
        }
      fMustRelDestStg = TRUE;
   }

   /*  NOTE: we must be sure to write our class ID into our
   **    storage. this information is used by OLE to determine the
   **    class of the data stored in our storage. Even for top
   **    "file-level" objects this information should be written to
   **    the file.
   */
   hrErr = WriteClassStg(lpDestStg, &CLSID_APP);
   if(hrErr != NOERROR) goto error;

   fStatus = OutlineDoc_SaveSelToStg(
         lpOutlineDoc,
         NULL,           // save all lines
         uFormat,
         lpDestStg,
         FALSE,          // fSameAsLoad
         TRUE            // remember this stg
      );

   if (fStatus)
      fStatus = OleStdCommitStorage(lpDestStg);

   if (fRemember) {
      /* if SaveAs was pending, then release the old storage and remember
      **    the new storage as the active current storage. all data from
      **    the old storage has been copied into the new storage.
      */
      if (lpContainerDoc->m_lpNewStg) {
         OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpStg);  // free old stg
         lpOleDoc->m_lpStg = lpContainerDoc->m_lpNewStg;   // save new stg
         lpContainerDoc->m_lpNewStg = NULL;
      }
      if (! fStatus) goto error;

      OutlineDoc_SetModified(lpOutlineDoc, FALSE, FALSE, FALSE);

#if defined( OPTIONAL )
      /* reset time of file on disk to be time just prior to saving.
      ** NOTE: it would also be necessary to remember
      **    filetimeBeforeSave in the document structure and when the
      **    root storage is finally released reset the file time
      **    again to this value (closing the file on DOS sets the time).
      */
      StgSetTimes(
            lpOutlineDoc->m_szFileName, NULL, NULL, &filetimeBeforeSave);
#endif
   }

   if (fMustRelDestStg)
      OleStdRelease((LPUNKNOWN)lpDestStg);
   return TRUE;

error:
   if (fMustRelDestStg)
      OleStdRelease((LPUNKNOWN)lpDestStg);
   OutlineApp_ErrorMessage(g_lpApp, ErrMsgSaving);
   return FALSE;
}

/* ContainerDoc_ContainerLineDoVerbCommand
** ---------------------------------------
**    Execute a verb of the OLE object in the current focus line.
*/
void ContainerDoc_ContainerLineDoVerbCommand(
      LPCONTAINERDOC          lpContainerDoc,
      LONG                    iVerb
)
{
   LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   int nIndex = LineList_GetFocusLineIndex(lpLL);
   LPLINE lpLine = LineList_GetLine(lpLL, nIndex);
   HCURSOR                 hPrevCursor;

   if (! lpLine || (Line_GetLineType(lpLine) != CONTAINERLINETYPE) ) return;

   // this may take a while, put up hourglass cursor
   hPrevCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));

   ContainerLine_DoVerb((LPCONTAINERLINE) lpLine, iVerb, NULL, TRUE, TRUE);

   SetCursor(hPrevCursor);     // restore original cursor
}

/* ContainerDoc_GetNextStgName
** ---------------------------
**    Generate the next unused name for a sub-storage to be used by an
**    OLE object. The ContainerDoc keeps a counter. The storages for
**    OLE objects are simply numbered (eg. Obj 0, Obj 1). A "long"
**    integer worth of storage names should be more than enough than we
**    will ever need.
**
**    NOTE: when an OLE object is transfered via drag/drop or the
**    clipboard, we attempt to keep the currently assigned name for the
**    object (if not currently in use). thus it is possible that an
**    object with a the next default name (eg. "Obj 5") already exists
**    in the current document if an object with this name was privously
**    transfered (pasted or dropped). we therefore loop until we find
**    the next lowest unused name.
*/
void ContainerDoc_GetNextStgName(
      LPCONTAINERDOC          lpContainerDoc,
      LPOLESTR                lpszStgName,
      int                     nLen
)
{
   char szAnsiStgName[256];

   wsprintf(szAnsiStgName, "%s %ld",
         (LPSTR)DEFOBJNAMEPREFIX,
         ++(lpContainerDoc->m_nNextObjNo)
   );

   while (ContainerDoc_IsStgNameUsed(lpContainerDoc, lpszStgName) == TRUE) {
      wsprintf(szAnsiStgName, "%s %ld",
            (LPSTR)DEFOBJNAMEPREFIX,
            ++(lpContainerDoc->m_nNextObjNo)
      );
   }
   A2W (szAnsiStgName, lpszStgName, nLen);
}

/* ContainerDoc_IsStgNameUsed
** --------------------------
**    Check if a given StgName is already in use.
*/
BOOL ContainerDoc_IsStgNameUsed(
      LPCONTAINERDOC          lpContainerDoc,
      LPOLESTR                lpszStgName
)
{
   LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   int i;
   LPLINE lpLine;

   for (i = 0; i < lpLL->m_nNumLines; i++) {
      lpLine=LineList_GetLine(lpLL, i);

      if (lpLine && (Line_GetLineType(lpLine)==CONTAINERLINETYPE)) {
         if (OLESTRCMP(lpszStgName,
               ((LPCONTAINERLINE)lpLine)->m_szStgName) == 0) {
            return TRUE;    // Match FOUND!
         }
      }
   }
   return FALSE;   // if we get here, then NO match was found.
}

LPSTORAGE ContainerDoc_GetStg(LPCONTAINERDOC lpContainerDoc)
{
   return ((LPOLEDOC)lpContainerDoc)->m_lpStg;
}

/* ContainerDoc_GetSingleOleObject
** -------------------------------
**    If the entire document contains a single OLE object, then
**    return the desired interface of the object.
**
**    Returns NULL if there is are multiple lines in the document or
**    the single line is not a ContainerLine.
*/
LPUNKNOWN ContainerDoc_GetSingleOleObject(
      LPCONTAINERDOC          lpContainerDoc,
      REFIID                  riid,
      LPCONTAINERLINE FAR*    lplpContainerLine
)
{
   LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   LPLINE lpLine;
   LPUNKNOWN lpObj = NULL;

   if (lplpContainerLine)
      *lplpContainerLine = NULL;

   if (lpLL->m_nNumLines != 1)
   {
      return NULL;    // doc does NOT contain a single line
   }

   lpLine=LineList_GetLine(lpLL, 0);

   if (lpLine && (Line_GetLineType(lpLine)==CONTAINERLINETYPE))
      lpObj = ContainerLine_GetOleObject((LPCONTAINERLINE)lpLine, riid);

   if (lplpContainerLine)
      *lplpContainerLine = (LPCONTAINERLINE)lpLine;

   return lpObj;
}

/* ContainerDoc_IsSelAnOleObject
** -----------------------------
**    Check if the selection is a single selection of an OLE object.
**    if so, then optionally return the desired interface of the object
**    and/or index of the ContainerLine containing the OLE object.
**
**    Returns FALSE if there is a multiple selection or the single
**    selection is not a ContainerLine.
*/
BOOL ContainerDoc_IsSelAnOleObject(
      LPCONTAINERDOC          lpContainerDoc,
      REFIID                  riid,
      LPUNKNOWN FAR*          lplpvObj,
      int FAR*                lpnIndex,
      LPCONTAINERLINE FAR*    lplpContainerLine
)
{
   LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   LINERANGE lrSel;
   int nNumSel;
   LPLINE lpLine;

   if (lplpvObj) *lplpvObj = NULL;
   if (lpnIndex) *lpnIndex = -1;
   if (lplpContainerLine) *lplpContainerLine = NULL;

   nNumSel = LineList_GetSel(lpLL, (LPLINERANGE)&lrSel);
   if (nNumSel != 1)
   {
      return FALSE;   // selection is not a single line
   }

   lpLine = LineList_GetLine(lpLL, lrSel.m_nStartLine);

   if (lpLine && (Line_GetLineType(lpLine)==CONTAINERLINETYPE)) {
      if (lpnIndex)
         *lpnIndex = lrSel.m_nStartLine;
      if (lplpContainerLine)
         *lplpContainerLine = (LPCONTAINERLINE)lpLine;
      if (riid) {
         *lplpvObj = ContainerLine_GetOleObject(
               (LPCONTAINERLINE)lpLine,
               riid
         );
      }

      return (*lplpvObj ? TRUE : FALSE);
   }

   return FALSE;
}

/*************************************************************************
** ContainerDoc::IOleUILinkContainer interface implementation
*************************************************************************/

STDMETHODIMP CntrDoc_LinkCont_QueryInterface(
      LPOLEUILINKCONTAINER    lpThis,
      REFIID                  riid,
      LPVOID FAR*             lplpvObj
)
{
   LPOLEDOC lpOleDoc = (LPOLEDOC)
         ((struct CDocOleUILinkContainerImpl FAR*)lpThis)->lpContainerDoc;

   return OleDoc_QueryInterface(lpOleDoc, riid, lplpvObj);
}

STDMETHODIMP_(ULONG) CntrDoc_LinkCont_AddRef(LPOLEUILINKCONTAINER lpThis)
{
   LPOLEDOC lpOleDoc = (LPOLEDOC)
         ((struct CDocOleUILinkContainerImpl FAR*)lpThis)->lpContainerDoc;

   OleDbgAddRefMethod(lpThis, OLESTR("IOleUILinkContainer"));

   return OleDoc_AddRef(lpOleDoc);
}

STDMETHODIMP_(ULONG) CntrDoc_LinkCont_Release(LPOLEUILINKCONTAINER lpThis)
{
   LPOLEDOC lpOleDoc = (LPOLEDOC)
         ((struct CDocOleUILinkContainerImpl FAR*)lpThis)->lpContainerDoc;

   OleDbgReleaseMethod(lpThis, "IOleUILinkContainer");

   return OleDoc_Release(lpOleDoc);
}

STDMETHODIMP_(DWORD) CntrDoc_LinkCont_GetNextLink(
      LPOLEUILINKCONTAINER    lpThis,
      DWORD                   dwLink
)
{
   LPCONTAINERDOC lpContainerDoc =
         ((struct CDocOleUILinkContainerImpl FAR*)lpThis)->lpContainerDoc;
   LPCONTAINERLINE lpContainerLine = NULL;

    // artificial AddRef in case object is released
    // during this call
    CntrDoc_LinkCont_AddRef(lpThis);

   OLEDBG_BEGIN2("CntrDoc_LinkCont_GetNextLink\r\n")

   lpContainerLine = ContainerDoc_GetNextLink(
         lpContainerDoc,
         (LPCONTAINERLINE)dwLink
   );

   OLEDBG_END2

   // release artificial AddRef
   CntrDoc_LinkCont_Release(lpThis);

   return (DWORD)lpContainerLine;
}

STDMETHODIMP CntrDoc_LinkCont_SetLinkUpdateOptions(
      LPOLEUILINKCONTAINER    lpThis,
      DWORD                   dwLink,
      DWORD                   dwUpdateOpt
)
{
   LPCONTAINERDOC lpContainerDoc =
         ((struct CDocOleUILinkContainerImpl FAR*)lpThis)->lpContainerDoc;
   LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)dwLink;
   LPOLELINK lpOleLink = lpContainerLine->m_lpOleLink;
   SCODE sc = S_OK;
   HRESULT hrErr;

   // artificial AddRef in case object is released during call
   CntrDoc_LinkCont_AddRef(lpThis);

   OLEDBG_BEGIN2("CntrDoc_LinkCont_SetLinkUpdateOptions\r\n")

   OleDbgAssert(lpContainerLine);

   if (! lpOleLink) {
      sc = E_FAIL;
      goto error;
   }

   OLEDBG_BEGIN2("IOleLink::SetUpdateOptions called\r\n")
   hrErr = lpOleLink->lpVtbl->SetUpdateOptions(
         lpOleLink,
         dwUpdateOpt
   );
   OLEDBG_END2

   // save new link type update option
   lpContainerLine->m_dwLinkType = dwUpdateOpt;

   if (hrErr != NOERROR) {
      OleDbgOutHResult("IOleLink::SetUpdateOptions returned", hrErr);
      sc = hrErr;
      goto error;
   }

error:
   // release artificial AddRef
   CntrDoc_LinkCont_Release(lpThis);

   OLEDBG_END2
   return sc;
}

STDMETHODIMP CntrDoc_LinkCont_GetLinkUpdateOptions(
      LPOLEUILINKCONTAINER    lpThis,
      DWORD                   dwLink,
      DWORD FAR*              lpdwUpdateOpt
)
{
   LPCONTAINERDOC lpContainerDoc =
         ((struct CDocOleUILinkContainerImpl FAR*)lpThis)->lpContainerDoc;
   LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)dwLink;
   LPOLELINK lpOleLink = lpContainerLine->m_lpOleLink;
   SCODE sc = S_OK;
   HRESULT hrErr;

   // artificial AddRef in case object is released during call
   CntrDoc_LinkCont_AddRef(lpThis);

   OLEDBG_BEGIN2("CntrDoc_LinkCont_GetLinkUpdateOptions\r\n")

   OleDbgAssert(lpContainerLine);

   if (! lpOleLink) {
      sc = E_FAIL;
      goto error;
   }

   OLEDBG_BEGIN2("IOleLink::GetUpdateOptions called\r\n")
   hrErr = lpOleLink->lpVtbl->GetUpdateOptions(
         lpOleLink,
         lpdwUpdateOpt
   );
   OLEDBG_END2

   // reset saved link type to ensure it is correct
   lpContainerLine->m_dwLinkType = *lpdwUpdateOpt;

   if (hrErr != NOERROR) {
      OleDbgOutHResult("IOleLink::GetUpdateOptions returned", hrErr);
      sc = hrErr;
      goto error;
   }

error:
   OLEDBG_END2

   // release artificial AddRef
   CntrDoc_LinkCont_Release(lpThis);

   return sc;
}

STDMETHODIMP CntrDoc_LinkCont_SetLinkSource(
      LPOLEUILINKCONTAINER    lpThis,
      DWORD                   dwLink,
      LPSTR                lpszDisplayName,
      ULONG                   lenFileName,
      ULONG FAR*              lpchEaten,
      BOOL                    fValidateSource
)
{
   LPCONTAINERDOC lpContainerDoc =
         ((struct CDocOleUILinkContainerImpl FAR*)lpThis)->lpContainerDoc;
   LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)dwLink;
   SCODE       sc = S_OK;
   HRESULT     hrErr;
   LPOLELINK   lpOleLink = lpContainerLine->m_lpOleLink;
   LPBC        lpbc = NULL;
   LPMONIKER   lpmk = NULL;
   LPOLEOBJECT lpLinkSrcOleObj = NULL;
   CLSID       clsid = CLSID_NULL;
   CLSID       clsidOld = CLSID_NULL;
   OLECHAR     szDisplayName[256];

   // artificial AddRef in case object is released during call
   CntrDoc_LinkCont_AddRef(lpThis);

   OLEDBG_BEGIN2("CntrDoc_LinkCont_SetLinkSource\r\n")

   OleDbgAssert(lpContainerLine);

   lpContainerLine->m_fLinkUnavailable = TRUE;

   if (fValidateSource) {

      /* NOTE: validate the link source by parsing the string
      **    into a Moniker. if this is successful, then the string is
      **    valid.
      */
      hrErr = CreateBindCtx(0, (LPBC FAR*)&lpbc);
      if (hrErr != NOERROR) {
         sc = hrErr;   // ERROR: OOM
         goto cleanup;
      }

      // Get class of orignial link source if it is available
      if (lpContainerLine->m_lpOleObj) {

         OLEDBG_BEGIN2("IOleObject::GetUserClassID called\r\n")
         hrErr = lpContainerLine->m_lpOleObj->lpVtbl->GetUserClassID(
               lpContainerLine->m_lpOleObj, (CLSID FAR*)&clsidOld);
         OLEDBG_END2
         if (hrErr != NOERROR) clsidOld = CLSID_NULL;
      }

      A2W(lpszDisplayName, szDisplayName, 256);
      hrErr = OleStdMkParseDisplayName(
           &clsidOld,lpbc,szDisplayName,lpchEaten,(LPMONIKER FAR*)&lpmk);

      if (hrErr != NOERROR) {
         sc = hrErr;   // ERROR in parsing moniker!
         goto cleanup;
      }
      /* NOTE: the link source was validated; it successfully
      **    parsed into a Moniker. we can set the source of the link
      **    directly with this Moniker. if we want the link to be
      **    able to know the correct class for the new link source,
      **    we must bind to the moniker and get the CLSID. if we do
      **    not do this then methods like IOleObject::GetUserType
      **    will return nothing (NULL strings).
      */

      hrErr = lpmk->lpVtbl->BindToObject(
            lpmk,lpbc,NULL,&IID_IOleObject,(LPVOID FAR*)&lpLinkSrcOleObj);
      if (hrErr == NOERROR) {
         OLEDBG_BEGIN2("IOleObject::GetUserClassID called\r\n")
         hrErr = lpLinkSrcOleObj->lpVtbl->GetUserClassID(
                     lpLinkSrcOleObj, (CLSID FAR*)&clsid);
         OLEDBG_END2
         lpContainerLine->m_fLinkUnavailable = FALSE;

         /* get the short user type name of the link because it may
         **    have changed. we cache this name and must update our
         **    cache. this name is used all the time when we have to
         **    build the object verb menu. we cache this information
         **    to make it quicker to build the verb menu.
         */
         if (lpContainerLine->m_lpszShortType) {
            OleStdFree(lpContainerLine->m_lpszShortType);
            lpContainerLine->m_lpszShortType = NULL;
         }
         OLEDBG_BEGIN2("IOleObject::GetUserType called\r\n")
         lpContainerLine->m_lpOleObj->lpVtbl->GetUserType(
               lpContainerLine->m_lpOleObj,
               USERCLASSTYPE_SHORT,
               /*(LPOLESTR FAR*)*/&lpContainerLine->m_lpszShortType
         );
         OLEDBG_END2
      }
      else
         lpContainerLine->m_fLinkUnavailable = TRUE;
   }
   else {
      LPMONIKER   lpmkFile = NULL;
      LPMONIKER   lpmkItem = NULL;
      char        szDelim[2];
      OLECHAR     wszDelim[2];
      char        *lpszName;
      OLECHAR     wszName[256];

      szDelim[0] = lpszDisplayName[(int)lenFileName];
      szDelim[1] = '\0';
      lpszDisplayName[(int)lenFileName] = '\0';

      OLEDBG_BEGIN2("CreateFileMoniker called\r\n")
      A2W(lpszDisplayName, szDisplayName, 256);
      CreateFileMoniker(szDisplayName, (LPMONIKER FAR*)&lpmkFile);
      OLEDBG_END2

      lpszDisplayName[(int)lenFileName] = szDelim[0];

      if (!lpmkFile)
         goto cleanup;

      if (strlen(lpszDisplayName) > (int)lenFileName) {  // have item name
         lpszName = lpszDisplayName + lenFileName + 1;

         OLEDBG_BEGIN2("CreateItemMoniker called\r\n")
         A2W(szDelim, wszDelim, 2);
         A2W(lpszName, wszName, 256);
         CreateItemMoniker(
               wszDelim, wszName, (LPMONIKER FAR*)&lpmkItem);
         OLEDBG_END2

         if (!lpmkItem) {
            OleStdRelease((LPUNKNOWN)lpmkFile);
            goto cleanup;
         }

         OLEDBG_BEGIN2("CreateGenericComposite called\r\n")
         CreateGenericComposite(lpmkFile, lpmkItem, (LPMONIKER FAR*)&lpmk);
         OLEDBG_END2

         if (lpmkFile)
            OleStdRelease((LPUNKNOWN)lpmkFile);
         if (lpmkItem)
            OleStdRelease((LPUNKNOWN)lpmkItem);

         if (!lpmk)
            goto cleanup;
      }
      else
         lpmk = lpmkFile;
   }

   if (! lpOleLink) {
      OleDbgAssert(lpOleLink != NULL);
      sc = E_FAIL;
      goto cleanup;
   }

   if (lpmk) {

      OLEDBG_BEGIN2("IOleLink::SetSourceMoniker called\r\n")
      hrErr = lpOleLink->lpVtbl->SetSourceMoniker(
            lpOleLink, lpmk, (REFCLSID)&clsid);
      OLEDBG_END2

      if (FAILED(hrErr)) {
         OleDbgOutHResult("IOleLink::SetSourceMoniker returned",hrErr);
         sc = hrErr;
         goto cleanup;
      }

      /* NOTE: above we forced the link source moniker to bind.
      **    because we deliberately hold on to the bind context
      **    (lpbc) the link source object will not shut down. during
      **    the call to IOleLink::SetSourceMoniker, the link will
      **    connect to the running link source (the link internally
      **    calls BindIfRunning). it is important to initially allow
      **    the link to bind to the running object so that it can get
      **    an update of the presentation for its cache. we do not
      **    want the connection from our link to the link source be
      **    the only reason the link source stays running. thus we
      **    deliberately for the link to release (unbind) the source
      **    object, we then release the bind context, and then we
      **    allow the link to rebind to the link source if it is
      **    running anyway.
      */
      if (lpbc && lpmk->lpVtbl->IsRunning(lpmk,lpbc,NULL,NULL) == NOERROR) {

         OLEDBG_BEGIN2("IOleLink::Update called\r\n")
         hrErr = lpOleLink->lpVtbl->Update(lpOleLink, NULL);
         OLEDBG_END2

#if defined( _DEBUG )
         if (FAILED(hrErr))
            OleDbgOutHResult("IOleLink::Update returned",hrErr);
#endif

         OLEDBG_BEGIN2("IOleLink::UnbindSource called\r\n")
         hrErr = lpOleLink->lpVtbl->UnbindSource(lpOleLink);
         OLEDBG_END2

#if defined( _DEBUG )
         if (FAILED(hrErr))
            OleDbgOutHResult("IOleLink::UnbindSource returned",hrErr);
#endif

         if (lpLinkSrcOleObj) {
            OleStdRelease((LPUNKNOWN)lpLinkSrcOleObj);
            lpLinkSrcOleObj = NULL;

}

         if (lpbc) {
            OleStdRelease((LPUNKNOWN)lpbc);
            lpbc = NULL;
         }

         OLEDBG_BEGIN2("IOleLink::BindIfRunning called\r\n")
         hrErr = lpOleLink->lpVtbl->BindIfRunning(lpOleLink);
         OLEDBG_END2

#if defined( _DEBUG )
         if (FAILED(hrErr))
            OleDbgOutHResult("IOleLink::BindIfRunning returned",hrErr);
#endif
      }
   } else {
      /* NOTE: the link source was NOT validated; it was NOT
      **    successfully parsed into a Moniker. we can only set the
      **    display name string as the source of the link. this link
      **    is not able to bind.
      */
      OLEDBG_BEGIN2("IOleLink::SetSourceDisplayName called\r\n")
      hrErr = lpOleLink->lpVtbl->SetSourceDisplayName(
            lpOleLink, (LPCOLESTR)lpszDisplayName);
      OLEDBG_END2

      if (hrErr != NOERROR) {
         OleDbgOutHResult("IOleLink::SetSourceDisplayName returned",hrErr);
         sc = hrErr;
         goto cleanup;
      }
   }

cleanup:
   if (lpLinkSrcOleObj)
      OleStdRelease((LPUNKNOWN)lpLinkSrcOleObj);
   if (lpmk)
      OleStdRelease((LPUNKNOWN)lpmk);
   if (lpbc)
      OleStdRelease((LPUNKNOWN)lpbc);

   OLEDBG_END2
   // release artificial AddRef
   CntrDoc_LinkCont_Release(lpThis);

   return sc;
}

STDMETHODIMP CntrDoc_LinkCont_GetLinkSource(
      LPOLEUILINKCONTAINER    lpThis,
      DWORD                   dwLink,
      LPSTR FAR*           lplpszDisplayName,
      ULONG FAR*              lplenFileName,
      LPSTR FAR*           lplpszFullLinkType,
      LPSTR FAR*           lplpszShortLinkType,
      BOOL FAR*               lpfSourceAvailable,
      BOOL FAR*               lpfIsSelected
)
{
   LPCONTAINERDOC lpContainerDoc =
         ((struct CDocOleUILinkContainerImpl FAR*)lpThis)->lpContainerDoc;
   LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)dwLink;
   LPOLELINK       lpOleLink = lpContainerLine->m_lpOleLink;
   LPOLEOBJECT     lpOleObj = NULL;
   LPMONIKER       lpmk = NULL;
   LPMONIKER       lpmkFirst = NULL;
   LPBC            lpbc = NULL;
   SCODE           sc = S_OK;
   HRESULT         hrErr;
   LPOLESTR        lpwszFullLinkType;
   LPOLESTR        lpwszShortLinkType;
   LPOLESTR        lpwszDisplayName;
   int cch;

   // artificial AddRef in case object is released during call
   CntrDoc_LinkCont_AddRef(lpThis);

   OLEDBG_BEGIN2("CntrDoc_LinkCont_GetLinkSource\r\n")

   /* NOTE: we must make sure to set all out parameters to NULL. */
   lpwszDisplayName  = NULL;
   lpwszFullLinkType = NULL;
   lpwszShortLinkType= NULL;
   *lplenFileName      = 0;
   *lpfSourceAvailable = !lpContainerLine->m_fLinkUnavailable;

   OleDbgAssert(lpContainerLine);

   if (! lpOleLink) {
      OLEDBG_END2
      sc = E_FAIL;
      goto cleanup;
   }

   OLEDBG_BEGIN2("IOleLink::GetSourceMoniker called\r\n")
   hrErr = lpOleLink->lpVtbl->GetSourceMoniker(
         lpOleLink,
         (LPMONIKER FAR*)&lpmk
   );
   OLEDBG_END2

   if (hrErr == NOERROR) {
      /* NOTE: the link has the Moniker form of the link source;
      **    this is therefore a validated link source. if the first
      **    part of the Moniker is a FileMoniker, then we need to
      **    return the length of the filename string. we need to
      **    return the ProgID associated with the link source as the
      **    "lpszShortLinkType". we need to return the
      **    FullUserTypeName associated with the link source as the
      **    "lpszFullLinkType".
      */

      lpOleObj = (LPOLEOBJECT)OleStdQueryInterface(
            (LPUNKNOWN)lpOleLink, &IID_IOleObject);
      *lplpszFullLinkType = NULL;
      *lplpszShortLinkType = NULL;
      if (lpOleObj) {
         lpOleObj->lpVtbl->GetUserType(
               lpOleObj,
               USERCLASSTYPE_FULL,
               &lpwszFullLinkType
         );
         if (lpwszFullLinkType)
         {
             cch = OLESTRLEN(lpwszFullLinkType) + 1;
             *lplpszFullLinkType = OleStdMalloc(cch);
             if (*lplpszFullLinkType)
             {
                 W2A(lpwszFullLinkType, *lplpszFullLinkType, cch);
             }
         }
         lpOleObj->lpVtbl->GetUserType(
               lpOleObj,
               USERCLASSTYPE_SHORT,
               &lpwszShortLinkType
         );
         if (lpwszShortLinkType)
         {
             cch = OLESTRLEN(lpwszShortLinkType) + 1;
             *lplpszShortLinkType = OleStdMalloc(cch);
             if (*lplpszShortLinkType)
             {
                 W2A(lpwszShortLinkType, *lplpszShortLinkType, cch);
             }
         }
         OleStdRelease((LPUNKNOWN)lpOleObj);
      }
      *lplenFileName = OleStdGetLenFilePrefixOfMoniker(lpmk);
      lpmk->lpVtbl->Release(lpmk);
   }

   OLEDBG_BEGIN2("IOleLink::GetSourceDisplayName called\r\n")
   hrErr = lpOleLink->lpVtbl->GetSourceDisplayName(
         lpOleLink,
         &lpwszDisplayName
   );
   if (lpwszDisplayName)
   {
       cch = OLESTRLEN(lpwszDisplayName) + 1;
       *lplpszDisplayName = OleStdMalloc(cch);
       if (*lplpszDisplayName)
       {
           W2A(lpwszDisplayName, *lplpszDisplayName, cch);
       }
   }
   else
       *lplpszDisplayName = NULL;
   OLEDBG_END2

   if (hrErr != NOERROR) {
      OleDbgOutHResult("IOleLink::GetSourceDisplayName returned", hrErr);
      OLEDBG_END2
      sc = hrErr;
      goto cleanup;
   }

   OLEDBG_END2

   if (lpfIsSelected)
      *lpfIsSelected = Line_IsSelected((LPLINE)lpContainerLine);

   sc = NOERROR;

cleanup:
   // release artificial AddRef
   CntrDoc_LinkCont_Release(lpThis);

   return sc;
}

STDMETHODIMP CntrDoc_LinkCont_OpenLinkSource(
      LPOLEUILINKCONTAINER    lpThis,
      DWORD                   dwLink
)
{
   LPCONTAINERDOC lpContainerDoc =
         ((struct CDocOleUILinkContainerImpl FAR*)lpThis)->lpContainerDoc;
   LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)dwLink;
   SCODE sc = S_OK;

   // artificial AddRef in case object is destroyed during call
   CntrDoc_LinkCont_AddRef(lpThis);

   OLEDBG_BEGIN2("CntrDoc_LinkCont_OpenLinkSource\r\n")

   OleDbgAssert(lpContainerLine);

   if (! ContainerLine_DoVerb(
         lpContainerLine, OLEIVERB_SHOW, NULL, TRUE, FALSE)) {
      sc = E_FAIL;
   }

   lpContainerLine->m_fLinkUnavailable = (sc != S_OK);

   OLEDBG_END2

   // release artificial AddRef
   CntrDoc_LinkCont_Release(lpThis);

   return sc;
}

STDMETHODIMP CntrDoc_LinkCont_UpdateLink(
      LPOLEUILINKCONTAINER    lpThis,
      DWORD                   dwLink,
      BOOL                    fErrorMessage,
      BOOL                    fErrorAction        // ignore if fErrorMessage
                                       //      is FALSE
)
{
   LPCONTAINERDOC lpContainerDoc =
         ((struct CDocOleUILinkContainerImpl FAR*)lpThis)->lpContainerDoc;
   LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)dwLink;
   SCODE sc = S_OK;
   // default to update of the link
   HRESULT hrErr = S_FALSE;

   // artificial AddRef in case object is destroyed during call
   CntrDoc_LinkCont_AddRef(lpThis);

   OLEDBG_BEGIN2("CntrDoc_LinkCont_UpdateLink\r\n")

   OleDbgAssert(lpContainerLine);

   if (! lpContainerLine->m_lpOleObj)
      ContainerLine_LoadOleObject(lpContainerLine);

   if (!fErrorMessage) {
      OLEDBG_BEGIN2("IOleObject::IsUpToDate called\r\n")
      hrErr = lpContainerLine->m_lpOleObj->lpVtbl->IsUpToDate(
            lpContainerLine->m_lpOleObj
      );
      OLEDBG_END2
   }

   if (hrErr != NOERROR) {
      OLEDBG_BEGIN2("IOleObject::Update called\r\n")
      hrErr = lpContainerLine->m_lpOleObj->lpVtbl->Update(
            lpContainerLine->m_lpOleObj
      );
      OLEDBG_END2
   }

   /* NOTE: If IOleObject::Update on the Link object returned
   **    OLE_E_CLASSDIFF because the link source is no longer
   **    the expected class, then the link should be re-created with
   **    the new link source. thus the link will be updated with the
   **    new link source.
   */
   if (hrErr == OLE_E_CLASSDIFF)
      hrErr = ContainerLine_ReCreateLinkBecauseClassDiff(lpContainerLine);

   lpContainerLine->m_fLinkUnavailable = (hrErr != NOERROR);

   if (hrErr != NOERROR) {
      OleDbgOutHResult("IOleObject::Update returned", hrErr);
      sc = hrErr;
      if (fErrorMessage) {
         ContainerLine_ProcessOleRunError(
               lpContainerLine,hrErr,fErrorAction,FALSE/*fMenuInvoked*/);
      }
   }
   /* NOTE: if the update of the object requires us to update our
   **    display, then we will automatically be sent a OnViewChange
   **    advise. thus we do not need to take any action here to force
   **    a repaint.
   */

   OLEDBG_END2

   // release artificial AddRef
   CntrDoc_LinkCont_Release(lpThis);

   return sc;
}

/* CntrDoc_LinkCont_CancelLink
** ---------------------------
**    Convert the link to a static picture.
**
**    NOTE: OleCreateStaticFromData can be used to create a static
**    picture object.
*/
STDMETHODIMP CntrDoc_LinkCont_CancelLink(
      LPOLEUILINKCONTAINER    lpThis,
      DWORD                   dwLink
)
{
   LPCONTAINERDOC lpContainerDoc =
         ((struct CDocOleUILinkContainerImpl FAR*)lpThis)->lpContainerDoc;
   LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)dwLink;
   LPLINELIST          lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   LPLINE              lpLine = NULL;
   HDC                 hDC;
   int                 nTab = 0;
   OLECHAR             szStgName[CWCSTORAGENAME];
   LPCONTAINERLINE     lpNewContainerLine = NULL;
   LPDATAOBJECT        lpSrcDataObj;
   LPOLELINK           lpOleLink = lpContainerLine->m_lpOleLink;
   int nIndex = LineList_GetLineIndex(lpLL, (LPLINE)lpContainerLine);

   // artificial AddRef in case object is destroyed during call
   CntrDoc_LinkCont_AddRef(lpThis);

   OLEDBG_BEGIN2("CntrDoc_LinkCont_CancelLink\r\n")

   /* we will first break the connection of the link to its source. */
   if (lpOleLink) {
      lpContainerLine->m_dwLinkType = 0;
      OLEDBG_BEGIN2("IOleLink::SetSourceMoniker called\r\n")
      lpOleLink->lpVtbl->SetSourceMoniker(
            lpOleLink, NULL, (REFCLSID)&CLSID_NULL);
      OLEDBG_END2
   }

   lpSrcDataObj = (LPDATAOBJECT)ContainerLine_GetOleObject(
         lpContainerLine,&IID_IDataObject);
   if (! lpSrcDataObj)
      goto error;

   ContainerDoc_GetNextStgName(lpContainerDoc, szStgName, CWCSTORAGENAME);
   nTab = Line_GetTabLevel((LPLINE)lpContainerLine);
   hDC = LineList_GetDC(lpLL);

   lpNewContainerLine = ContainerLine_CreateFromData(
         hDC,
         nTab,
         lpContainerDoc,
         lpSrcDataObj,
         OLECREATEFROMDATA_STATIC,
         0,   /* no special cfFormat required */
         (lpContainerLine->m_dwDrawAspect == DVASPECT_ICON),
         NULL,   /* hMetaPict */
         szStgName
   );
   LineList_ReleaseDC(lpLL, hDC);

   OleStdRelease((LPUNKNOWN)lpSrcDataObj);

   if (! lpNewContainerLine)
      goto error;

   OutlineDoc_SetModified((LPOUTLINEDOC)lpContainerDoc, TRUE, TRUE, FALSE);

   LineList_ReplaceLine(lpLL, (LPLINE)lpNewContainerLine, nIndex);

   OLEDBG_END2
   // release artificial AddRef
   CntrDoc_LinkCont_Release(lpThis);

   return NOERROR;

error:
   OutlineApp_ErrorMessage(g_lpApp, OLESTR("Could not break the link."));
   OLEDBG_END2
   // release artificial AddRef
   CntrDoc_LinkCont_Release(lpThis);

   return E_FAIL;
}


CNTRINPL.C

/*************************************************************************
**
**    OLE 2 Container Sample Code
**
**    cntrinpl.c
**
**    This file contains all interfaces, methods and related support
**    functions for an In-Place Container application (aka. Visual
**    Editing). The in-place Container application includes the following
**    implementation objects:
**
**    ContainerApp Object
**      exposed interfaces:
**          IOleInPlaceFrame
**
**    ContainerDoc Object
**      support functions only
**      (ICntrOtl is an SDI app; it doesn't support a Doc level IOleUIWindow)
**
**    ContainerLin Object
**      exposed interfaces:
**          IOleInPlaceSite
**
**    (c) Copyright Microsoft Corp. 1992 - 1997 All Rights Reserved
**
*************************************************************************/

#include "outline.h"
#if defined( USE_STATUSBAR )
#include "status.h"
#endif

OLEDBGDATA

extern LPOUTLINEAPP g_lpApp;
extern BOOL g_fInsideOutContainer;
extern RECT g_rectNull;

/*************************************************************************
** ContainerApp::IOleInPlaceFrame interface implementation
*************************************************************************/

// IOleInPlaceFrame::QueryInterface
STDMETHODIMP CntrApp_IPFrame_QueryInterface(
      LPOLEINPLACEFRAME   lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
)
{
   SCODE sc = E_NOINTERFACE;
   LPCONTAINERAPP lpContainerApp =
         ((struct COleInPlaceFrameImpl FAR*)lpThis)->lpContainerApp;

   /* The object should not be able to access the other interfaces
   ** of our App object by doing QI on this interface.
   */
   *lplpvObj = NULL;
   if (IsEqualIID(riid, &IID_IUnknown) ||
      IsEqualIID(riid, &IID_IOleWindow) ||
      IsEqualIID(riid, &IID_IOleInPlaceUIWindow) ||
      IsEqualIID(riid, &IID_IOleInPlaceFrame)) {
      OleDbgOut4("CntrApp_IPFrame_QueryInterface: IOleInPlaceFrame* RETURNED\r\n");
      *lplpvObj = (LPVOID) &lpContainerApp->m_OleInPlaceFrame;
      OleApp_AddRef((LPOLEAPP)lpContainerApp);
      sc = S_OK;
   }

   OleDbgQueryInterfaceMethod(*lplpvObj);

   return sc;
}

// IOleInPlaceFrame::AddRef
STDMETHODIMP_(ULONG) CntrApp_IPFrame_AddRef(LPOLEINPLACEFRAME lpThis)
{
   OleDbgAddRefMethod(lpThis, "IOleInPlaceFrame");

   return OleApp_AddRef((LPOLEAPP)g_lpApp);
}

// IOleInPlaceFrame::Release
STDMETHODIMP_(ULONG) CntrApp_IPFrame_Release(LPOLEINPLACEFRAME lpThis)
{
   OleDbgReleaseMethod(lpThis, "IOleInPlaceFrame");

   return OleApp_Release((LPOLEAPP)g_lpApp);
}

// IOleInPlaceFrame::GetWindow
STDMETHODIMP CntrApp_IPFrame_GetWindow(
   LPOLEINPLACEFRAME   lpThis,
   HWND FAR*           lphwnd
)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;

   OLEDBG_BEGIN2("CntrApp_IPFrame_GetWindow\r\n")
   *lphwnd = lpOutlineApp->m_hWndApp;
   OLEDBG_END2
   return NOERROR;
}

// IOleInPlaceFrame::ContextSensitiveHelp
STDMETHODIMP CntrApp_IPFrame_ContextSensitiveHelp(
   LPOLEINPLACEFRAME   lpThis,
   BOOL                fEnterMode
)
{
   LPCONTAINERAPP lpContainerApp =
         ((struct COleInPlaceFrameImpl FAR*)lpThis)->lpContainerApp;

   // artificial AddRef in case object is destroyed during call
   CntrApp_IPFrame_AddRef(lpThis);

   OleDbgOut("CntrApp_IPFrame_ContextSensitiveHelp\r\n");
   /* NOTE: see context sensitive help technote (CSHELP.DOC)
   **    This method is called when F1 is pressed when a menu item is
   **    selected. We set the frame's m_fMenuMode flag here. later,
   **    in WM_COMMAND processing in the AppWndProc, if this flag is
   **    set then the command is NOT executed and help is given
   **    instead.
   */
   lpContainerApp->m_fMenuHelpMode = fEnterMode;

   // release artificial AddRef
   CntrApp_IPFrame_Release(lpThis);

   return NOERROR;
}

// IOleInPlaceFrame::GetBorder
STDMETHODIMP CntrApp_IPFrame_GetBorder(
   LPOLEINPLACEFRAME   lpThis,
   LPRECT              lprectBorder
)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;

   // artificial AddRef in case object is destroyed during call
   CntrApp_IPFrame_AddRef(lpThis);

   OLEDBG_BEGIN2("CntrApp_IPFrame_GetBorder\r\n")

   OutlineApp_GetFrameRect(lpOutlineApp, lprectBorder);

   OLEDBG_END2
   // release artificial AddRef
   CntrApp_IPFrame_Release(lpThis);
   return NOERROR;
}

// IOleInPlaceFrame::RequestBorderSpace
STDMETHODIMP CntrApp_IPFrame_RequestBorderSpace(
   LPOLEINPLACEFRAME   lpThis,
   LPCBORDERWIDTHS     lpWidths
)
{
#if defined( _DEBUG )
   OleDbgOut2("CntrApp_IPFrame_RequestBorderSpace\r\n");

   {
      /* FOR DEBUGING PURPOSES ONLY -- we will fail to allow to an
      **    object to get any frame border space for frame tools if
      **    our own frame tools are poped up in the tool pallet. this
      **    is NOT recommended UI behavior but it allows us to test
      **    in the condition when the frame does not give border
      **    space for the object. an object in this situation must
      **    then either popup its tools in a floating pallet, do
      **    without the tools, or fail to in-place activate.
      */
      LPCONTAINERAPP lpContainerApp =
            ((struct COleInPlaceFrameImpl FAR*)lpThis)->lpContainerApp;
      LPFRAMETOOLS lpft = OutlineApp_GetFrameTools(
            (LPOUTLINEAPP)lpContainerApp);

      if (lpft->m_ButtonBar.m_nState == BARSTATE_POPUP &&
         lpft->m_FormulaBar.m_nState == BARSTATE_POPUP) {
         OleDbgOut3(
               "CntrApp_IPFrame_RequestBorderSpace: allow NO SPACE\r\n");
         return E_FAIL;
      }
   }
#endif  // _DEBUG

   /* NOTE: we allow the object to have as much border space as it
   **    wants.
   */
   return NOERROR;
}

// IOleInPlaceFrame::SetBorderSpace
STDMETHODIMP CntrApp_IPFrame_SetBorderSpace(
   LPOLEINPLACEFRAME   lpThis,
   LPCBORDERWIDTHS     lpWidths
)
{
   LPCONTAINERAPP lpContainerApp =
         ((struct COleInPlaceFrameImpl FAR*)lpThis)->lpContainerApp;

   // artificial AddRef in case object is destroyed during call
   CntrApp_IPFrame_AddRef(lpThis);

   OLEDBG_BEGIN2("CntrApp_IPFrame_SetBorderSpace\r\n")

   /* NOTE: this fMustResizeClientArea flag is used as part of our
   **    defensive programming for frame window resizing. when the
   **    frame window is resized,IOleInPlaceActiveObject::ResizeBorder
   **    is called the object should normally call back to renegotiate
   **    for frame-level tools space. if SetBorderSpace is called then
   **    our client area windows are properly resized. if the in-place
   **    active object does NOT call SetBorderSpace, then the
   **    container must take care to resize its client area windows
   **    itself (see ContainerDoc_FrameWindowResized)
   */
   if (lpContainerApp->m_fMustResizeClientArea)
      lpContainerApp->m_fMustResizeClientArea = FALSE;

   if (lpWidths == NULL) {

      /* NOTE: IOleInPlaceSite::SetBorderSpace(NULL) is called
      **    when the in-place active object does NOT want any tool
      **    space. in this situation the in-place container should
      **    put up its tools.
      */
      LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)lpContainerApp;
      LPCONTAINERDOC lpContainerDoc;

      lpContainerDoc =(LPCONTAINERDOC)OutlineApp_GetActiveDoc(lpOutlineApp);
      ContainerDoc_AddFrameLevelTools(lpContainerDoc);
   } else {

      // NOTE: you could do validation of borderwidths here
#if defined( _DEBUG )
      /* FOR DEBUGING PURPOSES ONLY -- we will fail to allow to an
      **    object to get any frame border space for frame tools if
      **    our own frame tools are poped up in the tool pallet. this
      **    is NOT recommended UI behavior but it allows us to test
      **    in the condition when the frame does not give border
      **    space for the object. an object in this situation must
      **    then either popup its tools in a floating pallet, do
      **    without the tools, or fail to in-place activate.
      */
      LPFRAMETOOLS lpft = OutlineApp_GetFrameTools(
            (LPOUTLINEAPP)lpContainerApp);

      if ((lpft->m_ButtonBar.m_nState == BARSTATE_POPUP &&
         lpft->m_FormulaBar.m_nState == BARSTATE_POPUP) &&
         (lpWidths->top || lpWidths->bottom ||
            lpWidths->left || lpWidths->right) ) {
         OleDbgOut3("CntrApp_IPFrame_SetBorderSpace: allow NO SPACE\r\n");
         OLEDBG_END2

         OutlineApp_SetBorderSpace(
               (LPOUTLINEAPP) lpContainerApp,
               (LPBORDERWIDTHS)&g_rectNull
         );
         OLEDBG_END2
         // release artificial AddRef
         CntrApp_IPFrame_Release(lpThis);

         return E_FAIL;
      }
#endif  // _DEBUG

      OutlineApp_SetBorderSpace(
            (LPOUTLINEAPP) lpContainerApp,
            (LPBORDERWIDTHS)lpWidths
      );
   }
   OLEDBG_END2
   // release artificial AddRef
   CntrApp_IPFrame_Release(lpThis);

   return NOERROR;
}

// IOleInPlaceFrame::SetActiveObject
STDMETHODIMP CntrApp_IPFrame_SetActiveObject(
   LPOLEINPLACEFRAME           lpThis,
   LPOLEINPLACEACTIVEOBJECT    lpActiveObject,
   LPCOLESTR                   lpszObjName
)
{
   LPCONTAINERAPP lpContainerApp = (LPCONTAINERAPP)g_lpApp;
   // artificial AddRef in case object is destroyed during call
   CntrApp_IPFrame_AddRef(lpThis);

   OLEDBG_BEGIN2("CntrApp_IPFrame_SetActiveObject\r\n")

   lpContainerApp->m_hWndUIActiveObj = NULL;

   if (lpContainerApp->m_lpIPActiveObj)
      lpContainerApp->m_lpIPActiveObj->lpVtbl->Release(lpContainerApp->m_lpIPActiveObj);

   if ((lpContainerApp->m_lpIPActiveObj = lpActiveObject) != NULL) {
      lpContainerApp->m_lpIPActiveObj->lpVtbl->AddRef(
            lpContainerApp->m_lpIPActiveObj);

      OLEDBG_BEGIN2("IOleInPlaceActiveObject::GetWindow called\r\n")
      lpActiveObject->lpVtbl->GetWindow(
            lpActiveObject,
            (HWND FAR*)&lpContainerApp->m_hWndUIActiveObj
      );
      OLEDBG_END2

      /* NOTE: see comment for ContainerDoc_ForwardPaletteChangedMsg */
      /* No need to do this if you don't allow object to own the palette */
      OleApp_QueryNewPalette((LPOLEAPP)lpContainerApp);
   }

   /* NOTE: the new UI Guidelines recommend that in-place
   **    containers do NOT change their window titles when an object
   **    becomes in-place (UI) active.
   */

   OLEDBG_END2
   // release artificial AddRef
   CntrApp_IPFrame_Release(lpThis);
   return NOERROR;
}

// IOleInPlaceFrame::InsertMenus
STDMETHODIMP CntrApp_IPFrame_InsertMenus(
   LPOLEINPLACEFRAME       lpThis,
   HMENU                   hMenu,
   LPOLEMENUGROUPWIDTHS    lpMenuWidths
)
{
   LPCONTAINERAPP lpContainerApp = (LPCONTAINERAPP)g_lpApp;
   BOOL    fNoError = TRUE;

   OLEDBG_BEGIN2("CntrApp_IPFrame_InsertMenus\r\n")

   fNoError &= AppendMenu(hMenu, MF_POPUP, (UINT)lpContainerApp->m_hMenuFile,
                  "&File");
   fNoError &= AppendMenu(hMenu, MF_POPUP, (UINT)lpContainerApp->m_hMenuView,
                  "O&utline");
   fNoError &= AppendMenu(hMenu, MF_POPUP,(UINT)lpContainerApp->m_hMenuDebug,
                  "DbgI&Cntr");
   lpMenuWidths->width[0] = 1;
   lpMenuWidths->width[2] = 1;
   lpMenuWidths->width[4] = 1;

   OLEDBG_END2

   return (fNoError ? NOERROR : E_FAIL);
}

// IOleInPlaceFrame::SetMenu
STDMETHODIMP CntrApp_IPFrame_SetMenu(
   LPOLEINPLACEFRAME   lpThis,
   HMENU               hMenuShared,
   HOLEMENU            hOleMenu,
   HWND                hwndActiveObject
)
{
   LPCONTAINERAPP lpContainerApp = (LPCONTAINERAPP)g_lpApp;
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   HMENU   hMenu;
   HRESULT hrErr;

   OLEDBG_BEGIN2("CntrApp_IPFrame_InsertMenus\r\n")

   /* NOTE: either put up the shared menu (combined menu from
   **    in-place server and in-place container) or our container's
   **    normal menu as directed.
   */
   if (hOleMenu && hMenuShared)
      hMenu = hMenuShared;
   else
      hMenu = lpOutlineApp->m_hMenuApp;

   /* NOTE: SDI apps put menu on frame by calling SetMenu.
   **    MDI apps would send WM_MDISETMENU message instead.
   */
   SetMenu (lpOutlineApp->m_hWndApp, hMenu);
   OLEDBG_BEGIN2("OleSetMenuDescriptor called\r\n")
   hrErr = OleSetMenuDescriptor (hOleMenu, lpOutlineApp->m_hWndApp,
               hwndActiveObject, NULL, NULL);
   OLEDBG_END2

   OLEDBG_END2
   return hrErr;
}

// IOleInPlaceFrame::RemoveMenus
STDMETHODIMP CntrApp_IPFrame_RemoveMenus(
   LPOLEINPLACEFRAME   lpThis,
   HMENU               hMenu
)
{
   LPCONTAINERAPP lpContainerApp = (LPCONTAINERAPP)g_lpApp;
   BOOL fNoError = TRUE;

   OLEDBG_BEGIN2("CntrApp_IPFrame_RemoveMenus\r\n")

   /* Remove container group menus */
   while (GetMenuItemCount(hMenu))
      fNoError &= RemoveMenu(hMenu, 0, MF_BYPOSITION);

   OleDbgAssert(fNoError == TRUE);

   OLEDBG_END2

   return (fNoError ? NOERROR : E_FAIL);
}

// IOleInPlaceFrame::SetStatusText
STDMETHODIMP CntrApp_IPFrame_SetStatusText(
   LPOLEINPLACEFRAME   lpThis,
   LPCOLESTR           lpszStatusText
)
{
#if defined( USE_STATUSBAR )
   LPOUTLINEAPP   lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   OLECHAR         szMessageHold[128];
   OleDbgOut2("CntrApp_IPFrame_SetStatusText\r\n");

   /* NOTE: it is important to save a private copy of status text.
   **    lpszStatusText is only valid for the duration of this call.
   */
   OLESTRCPY(szMessageHold, lpszStatusText);
   OutlineApp_SetStatusText(lpOutlineApp, /*(LPOLESTR)*/szMessageHold);

   return S_OK;
#else
   return E_NOTIMPL;
#endif  // USE_STATUSBAR
}

// IOleInPlaceFrame::EnableModeless
STDMETHODIMP CntrApp_IPFrame_EnableModeless(
   LPOLEINPLACEFRAME   lpThis,
   BOOL                fEnable
)
{
   LPOLEAPP lpOleApp = (LPOLEAPP)
         ((struct COleInPlaceFrameImpl FAR*)lpThis)->lpContainerApp;
#if defined( _DEBUG )
   if (fEnable)
      OleDbgOut2("CntrApp_IPFrame_EnableModeless(TRUE)\r\n");
   else
      OleDbgOut2("CntrApp_IPFrame_EnableModeless(FALSE)\r\n");
#endif  // _DEBUG

   /* NOTE: this method is called when an object puts up a modal
   **    dialog. it tells the top-level in-place container to disable
   **    it modeless dialogs for the duration that the object is
   **    displaying a modal dialog.
   **
   **    ICNTROTL does not use any modeless dialogs, thus we can
   **    ignore this method.
   */
   return NOERROR;
}

// IOleInPlaceFrame::TranslateAccelerator
STDMETHODIMP CntrApp_IPFrame_TranslateAccelerator(
   LPOLEINPLACEFRAME   lpThis,
   LPMSG               lpmsg,
   WORD                wID
)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPCONTAINERAPP lpContainerApp = (LPCONTAINERAPP)g_lpApp;
   SCODE sc;

   if (TranslateAccelerator (lpOutlineApp->m_hWndApp,
                  lpContainerApp->m_hAccelIPCntr, lpmsg))
      sc = S_OK;

#if defined (MDI_VERSION)
   else if (TranslateMDISysAccel(lpOutlineApp->m_hWndMDIClient, lpmsg))
      sc = S_OK;
#endif  // MDI_VERSION

   else
      sc = S_FALSE;

   return sc;
}

/*************************************************************************
** ContainerDoc Support Functions
*************************************************************************/

/* ContainerDoc_UpdateInPlaceObjectRects
** -------------------------------------
**    Update the PosRect and ClipRect of the currently in-place active
**    object. if there is no object active in-place, then do nothing.
**
**    NOTE: this function should be called when an action occurs
**    that changes either the position of the object in the document
**    (eg. changing document margins changes PosRect) or the clipRect
**    changes (eg. resizing the document window changes the ClipRect).
*/
void ContainerDoc_UpdateInPlaceObjectRects(LPCONTAINERDOC lpContainerDoc, int nIndex)
{
   LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   int i;
   LPLINE lpLine;
   RECT rcClipRect;

   if (g_fInsideOutContainer) {

      if (lpContainerDoc->m_cIPActiveObjects) {

         /* NOTE: (INSIDE-OUT CONTAINER) we must update the
         **    PosRect/ClipRect for all in-place active objects
         **    starting from line "nIndex".
         */
         ContainerDoc_GetClipRect(lpContainerDoc, (LPRECT)&rcClipRect);

#if defined( _DEBUG )
         OleDbgOutRect3(
               "ContainerDoc_UpdateInPlaceObjectRects (ClipRect)",
               (LPRECT)&rcClipRect
         );
#endif
         for (i = nIndex; i < lpLL->m_nNumLines; i++) {
            lpLine=LineList_GetLine(lpLL, i);

            if (lpLine && (Line_GetLineType(lpLine)==CONTAINERLINETYPE)) {
               LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)lpLine;
               ContainerLine_UpdateInPlaceObjectRects(
                     lpContainerLine, &rcClipRect);
            }
         }
      }
   }
   else {
      /* NOTE: (OUTSIDE-IN CONTAINER) if there is a currently
      **    UIActive object, we must inform it that the
      **    PosRect/ClipRect has now changed.
      */
      LPCONTAINERLINE lpLastUIActiveLine =
            lpContainerDoc->m_lpLastUIActiveLine;
      if (lpLastUIActiveLine && lpLastUIActiveLine->m_fUIActive) {
         ContainerDoc_GetClipRect(lpContainerDoc, (LPRECT)&rcClipRect);

         OleDbgOutRect3("OutlineDoc_Resize (ClipRect)",(LPRECT)&rcClipRect);
         ContainerLine_UpdateInPlaceObjectRects(
               lpLastUIActiveLine, &rcClipRect);
      }
   }
}

/* ContainerDoc_IsUIDeactivateNeeded
** ---------------------------------
**    Check if it is necessary to UIDeactivate an in-place active
**    object upon a mouse LBUTTONDOWN event. The position of the button
**    down click is given by "pt".
**    If there is not currently an in-place active line, then
**    UIDeactivate is NOT needed.
**    If there is a current in-place active line, then check if the
**    point position is outside of the object extents on the screen. If
**    so, then the object should be UIDeactivated, otherwise not.
*/
BOOL ContainerDoc_IsUIDeactivateNeeded(
      LPCONTAINERDOC  lpContainerDoc,
      POINT           pt
)
{
   LPCONTAINERLINE lpUIActiveLine=lpContainerDoc->m_lpLastUIActiveLine;
   RECT rect;

   if (! lpUIActiveLine || ! lpUIActiveLine->m_fUIActive)
      return FALSE;

   ContainerLine_GetPosRect(
         lpUIActiveLine,
         (LPRECT) &rect
   );

   if (! PtInRect((LPRECT) &rect, pt))
      return TRUE;

   return FALSE;
}

/* ContainerDoc_ShutDownLastInPlaceServerIfNotNeeded
** -------------------------------------------------
**    NOTE: this function ONLY applies for OUTSIDE-IN containers
**
**    If there is a previous in-place active server still running and
**    this server will not be needed to support the next OLE object
**    about to be activated, then shut it down.
**    in this way we manage a policy of having at most one in-place
**    server running at a time. we do not imediately shut down the
**    in-place server when the object is UIDeactivated because we want
**    it to be fast if the server decides to re-activate the object
**    in-place.
**
**    shutting down the server is achieved by forcing the object to
**    transition from running to loaded by calling IOleObject::Close.
*/
void ContainerDoc_ShutDownLastInPlaceServerIfNotNeeded(
      LPCONTAINERDOC          lpContainerDoc,
      LPCONTAINERLINE         lpNextActiveLine
)
{
   LPCONTAINERLINE lpLastIpActiveLine = lpContainerDoc->m_lpLastIpActiveLine;
   BOOL fEnableServerShutDown = TRUE;
   LPMONIKER lpmkLinkSrc;
   LPMONIKER lpmkLastActiveObj;
   LPMONIKER lpmkCommonPrefix;
   LPOLELINK lpOleLink;
   HRESULT hrErr;

   /* NOTE: an inside-out style container can NOT use this scheme
   **    to shut down in-place servers. it would have to have a more
   **    sophistocated mechanism by which it keeps track of which
   **    objects are on screen and which were the last recently used.
   */
   if (g_fInsideOutContainer)
      return;

   if (lpLastIpActiveLine != lpNextActiveLine) {
      if (lpLastIpActiveLine) {

         /* NOTE: if the object which is about to be activated is
         **    actually a link to the OLE object in last activated line,
         **    then we do NOT want to shut down the last activated
         **    server because it is about to be used. when activating a
         **    linked object, the source of the link gets activated.
         */
         lpOleLink = (LPOLELINK)ContainerLine_GetOleObject(
               lpNextActiveLine,
               &IID_IOleLink
         );
         if (lpOleLink) {
            OLEDBG_BEGIN2("IOleObject::GetSourceMoniker called\r\n")
            lpOleLink->lpVtbl->GetSourceMoniker(
                  lpOleLink,
                  (LPMONIKER FAR*)&lpmkLinkSrc
            );
            OLEDBG_END2

            if (lpmkLinkSrc) {
               lpmkLastActiveObj = ContainerLine_GetFullMoniker(
                     lpLastIpActiveLine,
                     GETMONIKER_ONLYIFTHERE
               );
               if (lpmkLastActiveObj) {
                  hrErr = lpmkLinkSrc->lpVtbl->CommonPrefixWith(
                        lpmkLinkSrc,
                        lpmkLastActiveObj,
                        &lpmkCommonPrefix

                  );
                  if (hrErr == MK_S_HIM ||
                                hrErr == NOERROR ||
                        hrErr == MK_S_US) {
                     /* the link source IS to the object
                     **    contained in the last activated
                     **    line of the document; disable the
                     **    attempt to shut down the last
                     **    running in-place server.
                     */
                     fEnableServerShutDown = FALSE;
                  }
                  if (lpmkCommonPrefix)
                     OleStdRelease((LPUNKNOWN)lpmkCommonPrefix);
                  OleStdRelease((LPUNKNOWN)lpmkLastActiveObj);
               }
               OleStdRelease((LPUNKNOWN)lpmkLinkSrc);
            }
            OleStdRelease((LPUNKNOWN)lpOleLink);
         }

         /* if it is OK to shut down the previous in-place server
         **    and one is still running, then shut it down. shutting
         **    down the server is accomplished by forcing the OLE
         **    object to close. this forces the object to transition
         **    from running to loaded. if the object is actually
         **    only loaded then this is a NOP.
         */
         if (fEnableServerShutDown &&
               lpLastIpActiveLine->m_fIpServerRunning) {

            OleDbgOut1("@@@ previous in-place server SHUT DOWN\r\n");
            ContainerLine_CloseOleObject(
                  lpLastIpActiveLine, OLECLOSE_SAVEIFDIRTY);

            // we can now forget this last in-place active line.
            lpContainerDoc->m_lpLastIpActiveLine = NULL;
         }
      }
   }
}

/* ContainerDoc_GetUIActiveWindow
** ------------------------------
**    If there is an UIActive object, then return its HWND.
*/
HWND ContainerDoc_GetUIActiveWindow(LPCONTAINERDOC lpContainerDoc)
{
   return lpContainerDoc->m_hWndUIActiveObj;
}

/* ContainerDoc_GetClipRect
** ------------------------
**    Get the ClipRect in client coordinates.
**
** NOTE: the ClipRect is defined as the maximum window rectangle
**    that the in-place active object must be clipped to. this
**    rectangle MUST be described in Client Coordinates of the window
**    that is used as the Parent of the in-place active object's
**    window. in our case, the LineList ListBox window is both the
**    parent of the in-place active object AND defines precisely the
**    clipping rectangle.
*/
void ContainerDoc_GetClipRect(
      LPCONTAINERDOC      lpContainerDoc,
      LPRECT              lprcClipRect
)
{
   /* NOTE: the ClipRect can be used to ensure that the in-place
   **    server does not overwrite areas of the window that the
   **    container paints into but which should never be overwritten
   **    (eg. if an app were to paint row and col headings directly in
   **    the same window that is the parent of the in-place window.
   **    whenever the window size changes or gets scrolled, in-place
   **    active object must be informed of the new clip rect.
   **
   **    normally an app would pass the rect returned from GetClientRect.
   **    but because CntrOutl uses separate windows for row/column
   **    headings, status line, formula/tool bars, etc. it is NOT
   **    necessary to pass a constrained clip rect. Windows standard
   **    window clipping will automatically take care of all clipping
   **    that is necessary. thus we can take a short cut of passing an
   **    "infinite" clip rect, and then we do NOT need to call
   **    IOleInPlaceObject::SetObjectRects when our document is scrolled.
   */

   lprcClipRect->top = -32767;
   lprcClipRect->left = -32767;
   lprcClipRect->right = 32767;
   lprcClipRect->bottom = 32767;
}

/* ContainerDoc_GetTopInPlaceFrame
** -------------------------------
**    returns NON-AddRef'ed pointer to Top In-Place Frame interface
*/
LPOLEINPLACEFRAME ContainerDoc_GetTopInPlaceFrame(
      LPCONTAINERDOC      lpContainerDoc
)
{
#if defined( INPLACE_CNTRSVR )
   return lpContainerDoc->m_lpTopIPFrame;
#else
   return (LPOLEINPLACEFRAME)&((LPCONTAINERAPP)g_lpApp)->m_OleInPlaceFrame;
#endif
}

void ContainerDoc_GetSharedMenuHandles(
      LPCONTAINERDOC  lpContainerDoc,
      HMENU FAR*      lphSharedMenu,
      HOLEMENU FAR*   lphOleMenu
)
{
#if defined( INPLACE_CNTRSVR )
   if (lpContainerDoc->m_DocType == DOCTYPE_EMEBEDDEDOBJECT) {
      *lphSharedMenu = lpContainerDoc->m_hSharedMenu;
      *lphOleMenu = lpContainerDoc->m_hOleMenu;
      return;
   }
#endif

   *lphSharedMenu = NULL;
   *lphOleMenu = NULL;
}

#if defined( USE_FRAMETOOLS )
void ContainerDoc_RemoveFrameLevelTools(LPCONTAINERDOC lpContainerDoc)
{
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpContainerDoc;
   OleDbgAssert(lpOutlineDoc->m_lpFrameTools != NULL);

   FrameTools_Enable(lpOutlineDoc->m_lpFrameTools, FALSE);
}
#endif

void ContainerDoc_AddFrameLevelUI(LPCONTAINERDOC lpContainerDoc)
{
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpContainerDoc;
   LPOLEINPLACEFRAME lpTopIPFrame = ContainerDoc_GetTopInPlaceFrame(
         lpContainerDoc);
   HMENU           hSharedMenu;            // combined obj/cntr menu
   HOLEMENU        hOleMenu;               // returned by OleCreateMenuDesc.

   ContainerDoc_GetSharedMenuHandles(
         lpContainerDoc,
         &hSharedMenu,
         &hOleMenu
   );

   lpTopIPFrame->lpVtbl->SetMenu(
         lpTopIPFrame,
         hSharedMenu,
         hOleMenu,
         lpOutlineDoc->m_hWndDoc
   );

   /* NOTE: even if our app does NOT use FrameTools, we must still
   **    call IOleInPlaceFrame::SetBorderSpace.
   */
   ContainerDoc_AddFrameLevelTools(lpContainerDoc);
}

void ContainerDoc_AddFrameLevelTools(LPCONTAINERDOC lpContainerDoc)
{
   LPOLEINPLACEFRAME lpTopIPFrame = ContainerDoc_GetTopInPlaceFrame(
         lpContainerDoc);
   LPCONTAINERAPP lpContainerApp = (LPCONTAINERAPP)g_lpApp;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpContainerDoc;

   OleDbgAssert(lpTopIPFrame != NULL);

#if defined( USE_FRAMETOOLS )

   FrameTools_Enable(lpOutlineDoc->m_lpFrameTools, TRUE);
   OutlineDoc_UpdateFrameToolButtons(lpOutlineDoc);

   FrameTools_NegotiateForSpaceAndShow(
         lpOutlineDoc->m_lpFrameTools,
         NULL,
         lpTopIPFrame
   );

#else   // ! USE_FRAMETOOLS

#if defined( INPLACE_CNTRSVR )
   if (lpContainerDoc->m_DocType == DOCTYPE_EMBEDDEDOBJECT) {
      /* this says i do not need space, so the top Frame should
      **    leave its tools behind
      */
      OLEDBG_BEGIN2("IOleInPlaceFrame::SetBorderSpace(NULL) called\r\n")
      lpTopIPFrame->lpVtbl->SetBorderSpace(lpTopIPFrame, NULL);
      OLEDBG_END2
      return;
   }
#else   // INPLACE_CNTR && ! USE_FRAMETOOLS

   OLEDBG_BEGIN2("IOleInPlaceFrame::SetBorderSpace(0,0,0,0) called\r\n")
   lpTopIPFrame->lpVtbl->SetBorderSpace(
         lpTopIPFrame,
         (LPCBORDERWIDTHS)&g_rectNull
   );
   OLEDBG_END2

#endif  // INPLACE_CNTR && ! USE_FRAMETOOLS
#endif  // ! USE_FRAMETOOLS

}

void ContainerDoc_FrameWindowResized(LPCONTAINERDOC lpContainerDoc)
{
   LPCONTAINERAPP lpContainerApp = (LPCONTAINERAPP)g_lpApp;

   if (lpContainerApp->m_lpIPActiveObj) {
      RECT rcFrameRect;

      /* NOTE: this fMustResizeClientArea flag is used as part of
      **    our defensive programming for frame window resizing. when

**    the frame window is
      **    resized, IOleInPlaceActiveObject::ResizeBorder is called
      **    the object should normally call back to renegotiate
      **    for frame-level tools space. if SetBorderSpace is called
      **    then our client area windows are properly resized.
      **    CntrApp_IPFrame_SetBorderSpace clears this flag. if the
      **    in-place active object does NOT call SetBorderSpace, then
      **    the container must take care to resize its client area
      **    windows itself.
      */
      lpContainerApp->m_fMustResizeClientArea = TRUE;

      OutlineApp_GetFrameRect(g_lpApp, (LPRECT)&rcFrameRect);

      OLEDBG_BEGIN2("IOleInPlaceActiveObject::ResizeBorder called\r\n")
      lpContainerApp->m_lpIPActiveObj->lpVtbl->ResizeBorder(
            lpContainerApp->m_lpIPActiveObj,
            (LPCRECT)&rcFrameRect,
            (LPOLEINPLACEUIWINDOW)&lpContainerApp->m_OleInPlaceFrame,
            TRUE    /* fFrameWindow */
      );
      OLEDBG_END2

      /* the object did NOT call IOleInPlaceUIWindow::SetBorderSpace
      **    therefore we must resize our client area windows ourself.
      */
      if (lpContainerApp->m_fMustResizeClientArea) {
         lpContainerApp->m_fMustResizeClientArea = FALSE;
         OutlineApp_ResizeClientArea(g_lpApp);
      }
   }

#if defined( USE_FRAMETOOLS )
   else {
      ContainerDoc_AddFrameLevelTools(lpContainerDoc);
   }
#endif
}

#if defined( INPLACE_CNTRSVR ) || defined( INPLACE_MDICNTR )

/* ContainerDoc_GetTopInPlaceDoc
**    returns NON-AddRef'ed pointer to Top In-Place Doc interface
*/
LPOLEINPLACEUIWINDOW ContainerDoc_GetTopInPlaceDoc(
      LPCONTAINERDOC      lpContainerDoc
)
{
#if defined( INPLACE_CNTRSVR )
   return lpContainerDoc->m_lpTopIPDoc;
#else
   return (LPOLEINPLACEUIWINDOW)&lpContainerDoc->m_OleInPlaceDoc;
#endif
}

void ContainerDoc_RemoveDocLevelTools(LPCONTAINERDOC lpContainerDoc);
{
   LPOLEINPLACEUIWINDOW lpTopIPDoc = ContainerDoc_GetTopInPlaceDoc(
         lpContainerDoc);

   if (lpTopIPDoc && lpContainerDoc->m_fMyToolsOnDoc) {
      lpContainerDoc->m_fMyToolsOnDoc = FALSE;

      // if we had doc tools we would HIDE them here;
      //   but NOT call SetBorderSpace(NULL)

   }
}

void ContainerDoc_AddDocLevelTools(LPCONTAINERDOC lpContainerDoc)
{
   LPOLEINPLACEUIWINDOW lpTopIPDoc = ContainerDoc_GetTopInPlaceDoc(
         lpContainerDoc);

   if (! lpTopIPDoc)
      return;

#if defined( USE_DOCTOOLS )
   if (lpTopIPDoc && ! lpContainerDoc->m_fMyToolsOnDoc) {

      /* if we had doc tools we would negotiate for toolbar space at
      **    doc level and SHOW them.
      */

      /* we do NOT have doc level tools, so we just call
      **    SetBorderSpace() to indicate to the top doc that
      **    our object does not need tool space.
      */

      lpContainerDoc->m_fMyToolsOnDoc = TRUE;
      return;
   }
#else   // ! USE_DOCTOOLS

#if defined( INPLACE_CNTRSVR )
   if (lpContainerDoc->m_DocType == DOCTYPE_EMBEDDEDOBJECT) {
      /* this says i do not need space, so the top doc should
      **    leave its tools behind
      */
      lpTopIPDoc->lpVtbl->SetBorderSpace(lpTopIPDoc, NULL);
      return;
   }
#else
   lpTopIPDoc->lpVtbl->SetBorderSpace(
         lpTopIPDoc,
         (LPCBORDERWIDTHS)&g_rectNull
   );

#endif
#endif  // ! USE_DOCTOOLS
}

#endif  // INPLACE_CNTRSVR || INPLACE_MDICNTR

/* ContainerDoc_ContextSensitiveHelp
** ---------------------------------
**    forward the ContextSensitiveHelp mode on to any in-place objects
**    that currently have their window visible. this informs the
**    objects whether to give help or take action on subsequent mouse
**    clicks and menu commands. this function is called from our
**    IOleInPlaceSite::ContextSensitiveHelp implementation.
**
** NOTE: see context sensitive help technote (CSHELP.DOC).
**    This function is called when SHIFT-F1 context sensitive help is
**    entered. the cursor should then change to a question mark
**    cursor and the app should enter a modal state where the next
**    mouse click does not perform its normal action but rather
**    gives help corresponding to the location clicked. if the app
**    does not implement a help system, it should at least eat the
**    click and do nothing.
*/
void ContainerDoc_ContextSensitiveHelp(
      LPCONTAINERDOC  lpContainerDoc,
      BOOL            fEnterMode,
      BOOL            fInitiatedByObj
)
{
   LPOLEDOC lpOleDoc = (LPOLEDOC)lpContainerDoc;
   LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   int i;
   LPLINE lpLine;

   lpOleDoc->m_fCSHelpMode = fEnterMode;

   if (g_fInsideOutContainer) {

      if (lpContainerDoc->m_cIPActiveObjects) {
         for (i = 0; i < lpLL->m_nNumLines; i++) {
            lpLine=LineList_GetLine(lpLL, i);

            if (lpLine && (Line_GetLineType(lpLine)==CONTAINERLINETYPE)) {
               LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)lpLine;
               ContainerLine_ContextSensitiveHelp(
                     lpContainerLine, fEnterMode);
            }
         }
      }
   }
   else if (! fInitiatedByObj) {
      /* NOTE: (OUTSIDE-IN CONTAINER) if there is a currently
      **    UIActive object (ie. its window is visible), we must
      **    forward the ContextSensitiveHelp mode on to the
      **    object--assuming it was not the object that initiated the
      **    context sensitve help mode.
      */
      LPCONTAINERLINE lpLastUIActiveLine =
            lpContainerDoc->m_lpLastUIActiveLine;
      if (lpLastUIActiveLine && lpLastUIActiveLine->m_fUIActive) {
         ContainerLine_ContextSensitiveHelp(lpLastUIActiveLine,fEnterMode);
      }
   }
}

/* ContainerDoc_ForwardPaletteChangedMsg
** -------------------------------------
**    forward the WM_PALETTECHANGED message (via SendMessage) to any
**    in-place objects that currently have their window visible. this
**    gives these objects the chance to select their palette as a
**    BACKGROUND palette.
**
**    SEE THE TECHNOTE FOR DETAILED INFORMATION ON PALETTE COORDINATION
**
**    NOTE: For containers and objects to manage palettes properly
**    (when objects are getting inplace edited) they should follow a
**    set of rules.
**
**    Rule 1: The Container can decide if it wants to own the palette or
**    it wants to allow its UIActive object to own the palette.
**    a) If the container wants to let its UIActive object own the
**    palette then it should forward WM_QUERYNEWPALETTE to the object
**    when it is received to the top frame window. also it should send
**    WM_QUERYNEWPALETTE to the object in its
**    IOleInPlaceFrame::SetActiveObject implementation. if the object
**    is given ownership of the palette, then it owns the palette until
**    it is UIDeactivated.
**    b) If the container wants to own the palette it should NOT send
**    WM_QUERYNEWPALETTE to the UIActive object.
**
**    Rule 2: Whether the container wants to own the palette or not, it
**    should forward the WM_PALETTECHANGED to the immediate child
**    inplace active objects in its documents. if it is an inside-out
**    style container then it must forward it to ALL objects that
**    currently have their windows visible. When the object recives the
**    WM_PALETTECHANGED message it must select its color palette as the
**    background palette. When the objects are in loaded state they will be
**    drawn by (OLE) by selecting their palettes as background palettes
**    anyway.
**
**    Note: IOleObject::SetColorScheme is not related to the palette
**    issue.
*/
void ContainerDoc_ForwardPaletteChangedMsg(
      LPCONTAINERDOC  lpContainerDoc,
      HWND            hwndPalChg
)
{
   LPLINELIST lpLL;
   int i;
   LPLINE lpLine;

   if (!lpContainerDoc)
      return;

   lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   if (g_fInsideOutContainer) {

      if (lpContainerDoc->m_cIPActiveObjects) {
         for (i = 0; i < lpLL->m_nNumLines; i++) {
            lpLine=LineList_GetLine(lpLL, i);

            if (lpLine && (Line_GetLineType(lpLine)==CONTAINERLINETYPE)) {
               LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)lpLine;
               ContainerLine_ForwardPaletteChangedMsg(
                     lpContainerLine, hwndPalChg);
            }
         }
      }
   }
   else {
      /* NOTE: (OUTSIDE-IN CONTAINER) if there is a currently
      **    UIActive object (ie. its window is visible), we must
      **    forward it the WM_PALETTECHANGED message.
      */
      LPCONTAINERLINE lpLastUIActiveLine =
            lpContainerDoc->m_lpLastUIActiveLine;
      if (lpLastUIActiveLine && lpLastUIActiveLine->m_fUIActive) {
         ContainerLine_ForwardPaletteChangedMsg(
               lpLastUIActiveLine, hwndPalChg);
      }
   }
}

/*************************************************************************
** ContainerLine Support Functions and Interfaces
*************************************************************************/

/* ContainerLine_UIDeactivate
** --------------------------
**    tell the OLE object associated with the ContainerLine to
**    UIDeactivate. this informs the in-place server to tear down
**    the UI and window that it put up for the object. it will remove
**    its active editor menus and any frame and object adornments
**    (eg. toolbars, rulers, etc.).
*/
void ContainerLine_UIDeactivate(LPCONTAINERLINE lpContainerLine)
{
   HRESULT hrErr;

   if (!lpContainerLine || !lpContainerLine->m_fUIActive)
      return;

   OLEDBG_BEGIN2("IOleInPlaceObject::UIDeactivate called\r\n")
   hrErr = lpContainerLine->m_lpOleIPObj->lpVtbl->UIDeactivate(
         lpContainerLine->m_lpOleIPObj);
   OLEDBG_END2

   if (hrErr != NOERROR) {
      OleDbgOutHResult("IOleInPlaceObject::UIDeactivate returned", hrErr);
   }
}

/* ContainerLine_UpdateInPlaceObjectRects
** -------------------------------------
**    Update the PosRect and ClipRect of the given line
**    currently in-place active
**    object. if there is no object active in-place, then do nothing.
**
**    NOTE: this function should be called when an action occurs
**    that changes either the position of the object in the document
**    (eg. changing document margins changes PosRect) or the clipRect
**    changes (eg. resizing the document window changes the ClipRect).
*/
void ContainerLine_UpdateInPlaceObjectRects(
      LPCONTAINERLINE lpContainerLine,
      LPRECT          lprcClipRect
)
{
   LPCONTAINERDOC lpContainerDoc = lpContainerLine->m_lpDoc;
   RECT rcClipRect;
   RECT rcPosRect;

   if (! lpContainerLine->m_fIpVisible)
      return;

   if (! lprcClipRect) {
      ContainerDoc_GetClipRect(lpContainerDoc, (LPRECT)&rcClipRect);
      lprcClipRect = (LPRECT)&rcClipRect;
   }

#if defined( _DEBUG )
   OleDbgOutRect3(
         "ContainerLine_UpdateInPlaceObjectRects (ClipRect)",
         (LPRECT)&rcClipRect
   );
#endif
   ContainerLine_GetPosRect(lpContainerLine,(LPRECT)&rcPosRect);

#if defined( _DEBUG )
   OleDbgOutRect3(
      "ContainerLine_UpdateInPlaceObjectRects (PosRect)",(LPRECT)&rcPosRect);
#endif

   OLEDBG_BEGIN2("IOleInPlaceObject::SetObjectRects called\r\n")
   lpContainerLine->m_lpOleIPObj->lpVtbl->SetObjectRects(
         lpContainerLine->m_lpOleIPObj,
         (LPRECT)&rcPosRect,
         lprcClipRect
   );
   OLEDBG_END2
}

/* ContainerLine_ContextSensitveHelp
** ---------------------------------
**    forward the ContextSensitiveHelp mode on to the in-place object
**    if it currently has its window visible. this informs the
**    object whether to give help or take action on subsequent mouse
**    clicks and menu commands.
**
**    this function is called from ContainerDoc_ContextSensitiveHelp
**    function as a result of a call to
**    IOleInPlaceSite::ContextSensitiveHelp if the in-place container
**    is operating as an in-side out container.
*/
void ContainerLine_ContextSensitiveHelp(
      LPCONTAINERLINE lpContainerLine,
      BOOL            fEnterMode
)
{
   if (! lpContainerLine->m_fIpVisible)
      return;

   OLEDBG_BEGIN2("IOleInPlaceObject::ContextSensitiveHelp called\r\n")
   lpContainerLine->m_lpOleIPObj->lpVtbl->ContextSensitiveHelp(
         lpContainerLine->m_lpOleIPObj, fEnterMode);
   OLEDBG_END2
}

/* ContainerLine_ForwardPaletteChangedMsg
** --------------------------------------
**    forward it the WM_PALETTECHANGED message (via SendMessage) to the
**    in-place object if it currently has its window visible. this
**    gives the object the chance to select its palette as a BACKGROUND
**    palette if it doesn't own the palette--or as the
**    foreground palette if it currently DOES own the palette.
**
**    SEE THE TECHNOTE FOR DETAILED INFORMATION ON PALETTE COORDINATION
*/
void ContainerLine_ForwardPaletteChangedMsg(
      LPCONTAINERLINE lpContainerLine,
      HWND             hwndPalChg
)
{
   if (! lpContainerLine->m_fIpVisible)
      return;

   if (lpContainerLine->m_hWndIpObject)
   {
       SendMessage(
             lpContainerLine->m_hWndIpObject,
             WM_PALETTECHANGED,
             (WPARAM)hwndPalChg,
             0L
       );
   }
}

/*************************************************************************
** ContainerLine::IOleInPlaceSite interface implementation
*************************************************************************/

STDMETHODIMP CntrLine_IPSite_QueryInterface(
      LPOLEINPLACESITE    lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleInPlaceSiteImpl FAR*)lpThis)->lpContainerLine;

   return ContainerLine_QueryInterface(lpContainerLine, riid, lplpvObj);
}

STDMETHODIMP_(ULONG) CntrLine_IPSite_AddRef(LPOLEINPLACESITE lpThis)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleInPlaceSiteImpl FAR*)lpThis)->lpContainerLine;

   OleDbgAddRefMethod(lpThis, "IOleInPlaceSite");

   return ContainerLine_AddRef(lpContainerLine);
}

STDMETHODIMP_(ULONG) CntrLine_IPSite_Release(LPOLEINPLACESITE lpThis)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleInPlaceSiteImpl FAR*)lpThis)->lpContainerLine;

   OleDbgReleaseMethod(lpThis, "IOleInPlaceSite");

   return ContainerLine_Release(lpContainerLine);
}

STDMETHODIMP CntrLine_IPSite_GetWindow(
   LPOLEINPLACESITE    lpThis,
   HWND FAR*           lphwnd
)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleInPlaceSiteImpl FAR*)lpThis)->lpContainerLine;

   // artificial AddRef in case object is destroyed during call
   CntrLine_IPSite_AddRef(lpThis);

   OleDbgOut2("CntrLine_IPSite_GetWindow\r\n");

   *lphwnd = LineList_GetWindow(
         &((LPOUTLINEDOC)lpContainerLine->m_lpDoc)->m_LineList);

   // release artificial AddRef
   CntrLine_IPSite_Release(lpThis);

   return NOERROR;
}

// IOleInPlaceSite::ContextSensitiveHelp
STDMETHODIMP CntrLine_IPSite_ContextSensitiveHelp(
   LPOLEINPLACESITE    lpThis,
   BOOL                fEnterMode
)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleInPlaceSiteImpl FAR*)lpThis)->lpContainerLine;
   LPOLEDOC lpOleDoc = (LPOLEDOC)lpContainerLine->m_lpDoc;

   // artificial AddRef in case object is destroyed during call
   CntrLine_IPSite_AddRef(lpThis);

   OleDbgOut2("CntrLine_IPSite_ContextSensitiveHelp\r\n");

   /* NOTE: see context sensitive help technote (CSHELP.DOC).
   **    This method is called when SHIFT-F1 context sensitive help is
   **    entered. the cursor should then change to a question mark
   **    cursor and the app should enter a modal state where the next
   **    mouse click does not perform its normal action but rather
   **    gives help corresponding to the location clicked. if the app
   **    does not implement a help system, it should at least eat the
   **    click and do nothing.
   **
   **    NOTE: the implementation here is specific to an SDI simple
   **    container. an MDI container or container/server application
   **    would have additional work to do (see the technote).
   **
   **    NOTE: (INSIDE-OUT CONTAINER) if there are currently
   **    any in-place objects active with their windows visible
   **    (ie. fIpVisible), we must forward the
   **    ContextSensitiveHelp mode on to these objects.
   */
   ContainerDoc_ContextSensitiveHelp(
            lpContainerLine->m_lpDoc,fEnterMode,TRUE /*InitiatedByObj*/);

   // release artificial AddRef
   CntrLine_IPSite_Release(lpThis);
   return NOERROR;
}

STDMETHODIMP CntrLine_IPSite_CanInPlaceActivate(LPOLEINPLACESITE lpThis)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleInPlaceSiteImpl FAR*)lpThis)->lpContainerLine;
   OleDbgOut2("CntrLine_IPSite_CanInPlaceActivate\r\n");

   /* NOTE: the container can NOT allow in-place activation if it
   **    is currently displaying the object as an ICON
   **    (DVASPECT_ICON). it can ONLY do in-place activation if it is
   **    displaying the DVASPECT_CONTENT of the OLE object.
   */
   if (lpContainerLine->m_dwDrawAspect == DVASPECT_CONTENT)
      return NOERROR;
   else
      return S_FALSE;
}

STDMETHODIMP CntrLine_IPSite_OnInPlaceActivate(LPOLEINPLACESITE lpThis)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleInPlaceSiteImpl FAR*)lpThis)->lpContainerLine;
   LPCONTAINERDOC lpContainerDoc = lpContainerLine->m_lpDoc;
   SCODE sc = S_OK;

   // artificial AddRef in case object is destroyed during call
   CntrLine_IPSite_AddRef(lpThis);
   OLEDBG_BEGIN2("CntrLine_IPSite_OnInPlaceActivate\r\n");

   /* NOTE: (OUTSIDE-IN CONTAINER) as a policy for managing
   **    running in-place servers, we will keep only 1 inplace server
   **    active at any given time. so when we start to inp-place activate
   **    another line, then we want to shut down the previously
   **    activated server. in this way we keep at most one inplace
   **    server active at a time. this is NOT a required policy. apps
   **    may choose to have a more sophisticated strategy. inside-out
   **    containers will HAVE to have a more sophisticated strategy,
   **    because they need (at a minimum) to keep all visible object
   **    servers running.
   **
   **    if the in-place activation is the result of activating a
   **    linked object in another container, then we may arrive at
   **    this method while another object is currently active.
   **    normally, if the object is in-place activated by
   **    double-clicking or Edit.<verb> from our own container, then
   **    the previous in-place object would have been de-activated in
   **    ContainerLine_DoVerb method.
   */
   if (!g_fInsideOutContainer) {
      if (lpContainerDoc->m_lpLastIpActiveLine) {
         ContainerDoc_ShutDownLastInPlaceServerIfNotNeeded(
               lpContainerDoc, lpContainerLine);
      }
      lpContainerDoc->m_lpLastIpActiveLine = lpContainerLine;
   }

   /* NOTE: to avoid LRPC problems it is important to cache the
   **    IOleInPlaceObject* pointer and NOT to call QueryInterface
   **    each time it is needed.
   */
   lpContainerLine->m_lpOleIPObj = (LPOLEINPLACEOBJECT)
         ContainerLine_GetOleObject(lpContainerLine,&IID_IOleInPlaceObject);

   if (! lpContainerLine->m_lpOleIPObj) {
#if defined( _DEBUG )
      OleDbgAssertSz(
            lpContainerLine->m_lpOleIPObj!=NULL,
            "OLE object must support IOleInPlaceObject"
      );
#endif
      // release artificial AddRef
      CntrLine_IPSite_Release(lpThis);
      return E_FAIL; // ERROR: refuse to in-place activate
   }

   lpContainerLine->m_fIpActive        = TRUE;
   lpContainerLine->m_fIpVisible       = TRUE;

   OLEDBG_BEGIN2("IOleInPlaceObject::GetWindow called\r\n")
   lpContainerLine->m_lpOleIPObj->lpVtbl->GetWindow(
         lpContainerLine->m_lpOleIPObj,
         (HWND FAR*)&lpContainerLine->m_hWndIpObject
   );
   OLEDBG_END2

   if (! lpContainerLine->m_fIpServerRunning) {
      /* NOTE: it is VERY important that an in-place container
      **    that also support linking to embeddings properly manage
      **    the running of its in-place objects. in an outside-in
      **    style in-place container, when the user clicks
      **    outside of the in-place active object, the object gets
      **    UIDeactivated and the object hides its window. in order
      **    to make the object fast to reactivate, the container
      **    deliberately does not call IOleObject::Close. the object
      **    stays running in the invisible unlocked state. the idea
      **    here is if the user simply clicks outside of the object
      **    and then wants to double click again to re-activate the
      **    object, we do not want this to be slow. if we want to
      **    keep the object running, however, we MUST Lock it
      **    running. otherwise the object will be in an unstable
      **    state where if a linking client does a "silent-update"
      **    (eg. UpdateNow from the Links dialog), then the in-place
      **    server will shut down even before the object has a chance
      **    to be saved back in its container. this saving normally
      **    occurs when the in-place container closes the object. also
      **    keeping the object in the unstable, hidden, running,
      **    not-locked state can cause problems in some scenarios.
      **    ICntrOtl keeps only one object running. if the user
      **    intiates a DoVerb on another object, then that last
      **    running in-place active object is closed. a more
      **    sophistocated in-place container may keep more object running.
      **    this lock gets unlocked in ContainerLine_CloseOleObject.
      */
      lpContainerLine->m_fIpServerRunning = TRUE;

      OLEDBG_BEGIN2("OleLockRunning(TRUE, 0) called\r\n")
      OleLockRunning((LPUNKNOWN)lpContainerLine->m_lpOleIPObj, TRUE, 0);
      OLEDBG_END2
   }

   lpContainerLine->m_lpDoc->m_cIPActiveObjects++;

   OLEDBG_END2
   // release artificial AddRef
   CntrLine_IPSite_Release(lpThis);
   return NOERROR;
}

STDMETHODIMP CntrLine_IPSite_OnUIActivate (LPOLEINPLACESITE lpThis)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleInPlaceSiteImpl FAR*)lpThis)->lpContainerLine;
   LPCONTAINERDOC lpContainerDoc = lpContainerLine->m_lpDoc;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpContainerLine->m_lpDoc;
   LPCONTAINERLINE lpLastUIActiveLine = lpContainerDoc->m_lpLastUIActiveLine;

   // artificial AddRef in case object is destroyed during call
   CntrLine_IPSite_AddRef(lpThis);

   OLEDBG_BEGIN2("CntrLine_IPSite_OnUIActivate\r\n");

   lpContainerLine->m_fUIActive        = TRUE;
   lpContainerDoc->m_fAddMyUI          = FALSE;
   lpContainerDoc->m_lpLastUIActiveLine = lpContainerLine;

   if (g_fInsideOutContainer) {
      /* NOTE: (INSIDE-OUT CONTAINER) an inside-out style
      **    container must UIDeactivate the previous UIActive object
      **    when a new object is going UIActive. since the inside-out
      **    objects have their own windows visible, it is possible
      **    that a click directly in an another server window will
      **    cause it to UIActivate. OnUIActivate is the containers
      **    notification that such has occured. it must then
      **    UIDeactivate the other because at most one object can be
      **    UIActive at a time.
      */
      if (lpLastUIActiveLine && (lpLastUIActiveLine!=lpContainerLine)) {
         ContainerLine_UIDeactivate(lpLastUIActiveLine);

         // Make sure new UIActive window is on top of all others
         SetWindowPos(
               lpContainerLine->m_hWndIpObject,
               HWND_TOPMOST,
               0,0,0,0,
               SWP_NOMOVE | SWP_NOSIZE
         );

         OLEDBG_END2
         goto cleanup;
      }
   }

   lpContainerDoc->m_hWndUIActiveObj = lpContainerLine->m_hWndIpObject;

#if defined( USE_FRAMETOOLS )
   ContainerDoc_RemoveFrameLevelTools(lpContainerDoc);
#endif

#if defined( USE_DOCTOOLS )
   ContainerDoc_RemoveDocLevelTools(lpContainerDoc);
#endif

   OLEDBG_END2
cleanup:
   // release artificial AddRef
   CntrLine_IPSite_Release(lpThis);

   return NOERROR;
}

STDMETHODIMP CntrLine_IPSite_GetWindowContext(
   LPOLEINPLACESITE            lpThis,
   LPOLEINPLACEFRAME FAR*      lplpFrame,
   LPOLEINPLACEUIWINDOW FAR*   lplpDoc,
   LPRECT                      lprcPosRect,
   LPRECT                      lprcClipRect,
   LPOLEINPLACEFRAMEINFO       lpFrameInfo
)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPCONTAINERAPP lpContainerApp = (LPCONTAINERAPP)g_lpApp;
   LPCONTAINERLINE lpContainerLine =
         ((struct COleInPlaceSiteImpl FAR*)lpThis)->lpContainerLine;

   // artificial AddRef in case object is destroyed during call
   CntrLine_IPSite_AddRef(lpThis);

   OLEDBG_BEGIN2("CntrLine_IPSite_GetWindowContext\r\n")

   /* NOTE: The server should fill in the "cb" field so that the
   **    container can tell what size structure the server is
   **    expecting. this enables this structure to be easily extended
   **    in future releases of OLE. the container should check this
   **    field so that it doesn't try to use fields that do not exist
   **    since the server may be using an old structure definition.
   **    Since this is the first release of OLE2.0, the structure is
   **    guaranteed to be at least large enough for the current
   **    definition of OLEINPLACEFRAMEINFO struct. thus we do NOT need
   **    to consider this an error if the server did not fill in this
   **    field correctly. this server may have trouble in the future,
   **    however, when the structure is extended.
   */
   *lplpFrame = (LPOLEINPLACEFRAME)&lpContainerApp->m_OleInPlaceFrame;
   (*lplpFrame)->lpVtbl->AddRef(*lplpFrame);   // must return AddRef'ed ptr

   // NOTE: an MDI app would have to provide *lplpDoc
   *lplpDoc  = NULL;

   ContainerLine_GetPosRect(lpContainerLine, lprcPosRect);
   ContainerDoc_GetClipRect(lpContainerLine->m_lpDoc, lprcClipRect);

   OleDbgOutRect3("CntrLine_IPSite_GetWindowContext (PosRect)", lprcPosRect);
   OleDbgOutRect3("CntrLine_IPSite_GetWindowContext (ClipRect)",lprcClipRect);
   lpFrameInfo->hwndFrame      = lpOutlineApp->m_hWndApp;

#if defined( MDI_VERSION )
   lpFrameInfo->fMDIApp        = TRUE;
#else
   lpFrameInfo->fMDIApp        = FALSE;
#endif

   lpFrameInfo->haccel         = lpContainerApp->m_hAccelIPCntr;
   lpFrameInfo->cAccelEntries  =
      GetAccelItemCount(lpContainerApp->m_hAccelIPCntr);

   OLEDBG_END2
   // release artificial AddRef
   CntrLine_IPSite_Release(lpThis);
   return NOERROR;
}

STDMETHODIMP CntrLine_IPSite_Scroll(
   LPOLEINPLACESITE    lpThis,
   SIZE                scrollExtent
)
{
   OleDbgOut2("CntrLine_IPSite_Scroll\r\n");
   return E_FAIL;
}

STDMETHODIMP CntrLine_IPSite_OnUIDeactivate(
   LPOLEINPLACESITE    lpThis,
   BOOL                fUndoable
)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleInPlaceSiteImpl FAR*)lpThis)->lpContainerLine;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpContainerLine->m_lpDoc;
   LPCONTAINERAPP lpContainerApp = (LPCONTAINERAPP) g_lpApp;
   LPLINELIST lpLL;
   int nIndex;
   MSG msg;
   HRESULT hrErr;

   // artificial AddRef in case object is destroyed during call
   CntrLine_IPSite_AddRef(lpThis);

   OLEDBG_BEGIN2("CntrLine_IPSite_OnUIDeactivate\r\n")

   lpContainerLine->m_fUIActive = FALSE;
   lpContainerLine->m_fIpChangesUndoable = fUndoable;
   lpContainerLine->m_lpDoc->m_hWndUIActiveObj = NULL;

   if (lpContainerLine == lpContainerLine->m_lpDoc->m_lpLastUIActiveLine) {

      lpContainerLine->m_lpDoc->m_lpLastUIActiveLine = NULL;

      /* NOTE: here we look ahead if there is a DBLCLK sitting in our
      **    message queue. if so, it could result in in-place activating
      **    another object. we want to avoid placing our tools and
      **    repainting if immediately another object is going to do the
      **    same. SO, if there is a DBLCLK in the queue for this document
      **    we will only set the fAddMyUI flag to indicate that this work
      **    is still to be done. if another object actually in-place
      **    activates then this flag will be cleared in
      **    IOleInPlaceSite::OnUIActivate. if it does NOT get cleared,
      **    then at the end of processing the DBLCLK message in our
      **    OutlineDocWndProc we will put our tools back.
      */
      if (! PeekMessage(&msg, lpOutlineDoc->m_hWndDoc,
            WM_LBUTTONDBLCLK, WM_LBUTTONDBLCLK,
            PM_NOREMOVE | PM_NOYIELD)) {

         /* NOTE: You need to generate QueryNewPalette only if
         **    you own the top level frame (ie. you are a top-level
         **    inplace container).
         */

         OleApp_QueryNewPalette((LPOLEAPP)g_lpApp);

#if defined( USE_DOCTOOLS )
         ContainerDoc_AddDocLevelTools(lpContainerLine->m_lpDoc);
#endif

#if defined( USE_FRAMETOOLS )
         ContainerDoc_AddFrameLevelUI(lpContainerLine->m_lpDoc);
#endif
      } else {
         lpContainerLine->m_lpDoc->m_fAddMyUI = TRUE;
      }

      /* NOTE: we should re-take focus. the in-place server window
      **    previously had the focus; this window has just been removed.
      */
      SetFocus(OutlineDoc_GetWindow((LPOUTLINEDOC)lpContainerLine->m_lpDoc));

      // force the line to redraw to remove in-place active hatch
      lpLL = OutlineDoc_GetLineList(lpOutlineDoc);
      nIndex = LineList_GetLineIndex(lpLL, (LPLINE)lpContainerLine);
      LineList_ForceLineRedraw(lpLL, nIndex, TRUE);
   }

#if defined( UNDOSUPPORTED )

   /* NOTE: an outside-in style container that supports UNDO would
   **    call IOleObject::DoVerb(OLEIVERB_HIDE) to make the in-place
   **    object go invisible. when it wants the in-place active object
   **    to discard its undo state, it would call
   **    IOleInPlaceObject::InPlaceDeactivate when it wants the object
   **    to discard its undo state. there is no need for an outside-in
   **    style container to call
   **    IOleObject::DoVerb(OLEIVERB_DISCARDUNDOSTATE). if either the
   **    container or the object do not support UNDO, then the
   **    container might as well immediately call InPlaceDeactivate
   **    instead of calling DoVerb(HIDE).
   **
   **    an inside-out style container that supports UNDO would simply
   **    UIDeactivate the object. it would call
   **    IOleObject::DoVerb(OLEIVERB_DISCARDUNDOSTATE) when it wants
   **    the object discard its undo state. it would call
   **    IOleInPlaceObject::InPlaceDeactivate if it wants the object
   **    to take down its window.
   */
   if (! g_fInsideOutContainer || !lpContainerLine->m_fInsideOutObj) {

      if (lpContainerLine->m_fIpChangesUndoable) {
         ContainerLine_DoVerb(
               lpContainerLine,OLEIVERB_HIDE,NULL,FALSE,FALSE);
      } else {
         lpContainerLine->m_lpOleIPObj->lpVtbl->InPlaceDeactivate(
               lpContainerLine->m_lpOleIPObj);
      }
      lpContainerLine->m_fIpVisible = FALSE;
      lpContainerLine->m_hWndIpObject = NULL;
   }
#else

   /* NOTE: an outside-in style container that does NOT support
   **    UNDO would immediately tell the UIDeactivated server (UI
   **    removed) to IOleInPlaceObject::InPlaceDeactivate.
   **
   **    an inside-out style container that does NOT support UNDO
   **    would call IOleObject::DoVerb(OLEIVERB_DISCARDUNDOSTATE) to
   **    tell the object to discard its undo state. it would call
   **    IOleInPlaceObject::InPlaceDeactivate if it wants the object
   **    to take down its window.
   */

   if (g_fInsideOutContainer) {

      if (lpContainerLine->m_fInsideOutObj) {

         if (lpContainerLine->m_fIpChangesUndoable) {
            OLEDBG_BEGIN3("ContainerLine_DoVerb(OLEIVERB_DISCARDUNDOSTATE) called!\r\n")
            ContainerLine_DoVerb(lpContainerLine,
                  OLEIVERB_DISCARDUNDOSTATE,NULL,FALSE,FALSE);
            OLEDBG_END3
         }

      } else {    // !fInsideOutObj

         /* NOTE: (INSIDEOUT CONTAINER) if the object is not
         **    registered OLEMISC_ACTIVATEWHENVISIBLE, then we will
         **    make the object behave in an outside-in manner. since
         **    we do NOT deal with UNDO we can simply
         **    InPlaceDeactivate the object. it should NOT be
         **    allowed to leave its window visible when
         **    UIDeactivated.
         */
         OLEDBG_BEGIN2("IOleInPlaceObject::InPlaceDeactivate called\r\n")
         hrErr = lpContainerLine->m_lpOleIPObj->lpVtbl->InPlaceDeactivate(
                  lpContainerLine->m_lpOleIPObj);
         OLEDBG_END2
         if (hrErr != NOERROR) {
            OleDbgOutHResult("IOleInPlaceObject::InPlaceDeactivate returned", hrErr);
         }
      }

   } else {

      /* NOTE: (OUTSIDE-IN CONTAINER) since we do NOT deal with
      **    UNDO we can simply InPlaceDeactivate the object. it
      **    should NOT be allowed to leave its window visible when
      **    UIDeactivated.
        **
        **    NOTE: In-place servers must handle InPlaceDeactivate
        **    being called before its call to
        **    IOleInPlaceSite::OnUIDeactivate returns. in case there
        **    are misbehaving servers out there, one way to work around
        **    this problem is to call
        **    IOleObject::DoVerb(OLEIVERB_HIDE...) here.
      */
      OLEDBG_BEGIN2("IOleInPlaceObject::InPlaceDeactivate called\r\n")
      hrErr = lpContainerLine->m_lpOleIPObj->lpVtbl->InPlaceDeactivate(
            lpContainerLine->m_lpOleIPObj);
      OLEDBG_END2
      if (hrErr != NOERROR) {
         OleDbgOutHResult("IOleInPlaceObject::InPlaceDeactivate returned", hrErr);
      }
   }

#endif // ! UNDOSUPPORTED

   OLEDBG_END2
   // release artificial AddRef
   CntrLine_IPSite_Release(lpThis);
   return NOERROR;
}

STDMETHODIMP CntrLine_IPSite_OnInPlaceDeactivate(LPOLEINPLACESITE lpThis)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleInPlaceSiteImpl FAR*)lpThis)->lpContainerLine;

   // artificial AddRef in case object is destroyed during call
   CntrLine_IPSite_AddRef(lpThis);
   OLEDBG_BEGIN2("CntrLine_IPSite_OnInPlaceDeactivate\r\n");

   lpContainerLine->m_fIpActive            = FALSE;
   lpContainerLine->m_fIpVisible           = FALSE;
   lpContainerLine->m_fIpChangesUndoable   = FALSE;
   lpContainerLine->m_hWndIpObject         = NULL;

   OleStdRelease((LPUNKNOWN) lpContainerLine->m_lpOleIPObj);
   lpContainerLine->m_lpOleIPObj = NULL;
   lpContainerLine->m_lpDoc->m_cIPActiveObjects--;

   OLEDBG_END2
   // release artificial AddRef
   CntrLine_IPSite_Release(lpThis);
   return NOERROR;
}

STDMETHODIMP CntrLine_IPSite_DiscardUndoState(LPOLEINPLACESITE lpThis)
{
   OleDbgOut2("CntrLine_IPSite_DiscardUndoState\r\n");
   return NOERROR;
}

STDMETHODIMP CntrLine_IPSite_DeactivateAndUndo(LPOLEINPLACESITE lpThis)
{
   OleDbgOut2("CntrLine_IPSite_DeactivateAndUndo\r\n");
   return NOERROR;
}

STDMETHODIMP CntrLine_IPSite_OnPosRectChange(
   LPOLEINPLACESITE    lpThis,
   LPCRECT             lprcPosRect
)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleInPlaceSiteImpl FAR*)lpThis)->lpContainerLine;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpContainerLine->m_lpDoc;
   LPSCALEFACTOR lpscale = OutlineDoc_GetScaleFactor(lpOutlineDoc);
   LPLINE lpLine = (LPLINE)lpContainerLine;
   LPLINELIST lpLL;
   int nIndex;
   RECT rcClipRect;
   RECT rcNewPosRect;
   SIZEL sizelPix;
   SIZEL sizelHim;
   int nIPObjHeight = lprcPosRect->bottom - lprcPosRect->top;
   int nIPObjWidth = lprcPosRect->right - lprcPosRect->left;

   // artificial AddRef in case object is destroyed during call
   CntrLine_IPSite_AddRef(lpThis);

   OLEDBG_BEGIN2("CntrLine_IPSite_OnPosRectChange\r\n")
   OleDbgOutRect3("CntrLine_IPSite_OnPosRectChange (PosRect --IN)", (LPRECT)lprcPosRect);

   /* NOTE: if the in-place container does NOT impose any
   **    size contraints on its in-place active objects, then it may
   **    simply grant the size requested by the object by immediately
   **    calling IOleInPlaceObject::SetObjectRects with the
   **    lprcPosRect passed by the in-place object.
   **
   **    Container Outline, however, imposes a size constraint on its
   **    embedded objects (see comment in ContainerLine_UpdateExtent),
   **    thus it is necessary to calculate the size that the in-place
   **    active object is allowed to be.
   **
   **    Here we need to know the new extents of the in-place object.
   **    We can NOT directly ask the object via IOleObject::GetExtent
   **    because this method will retreive the extents of the last
   **    cached metafile. the cache has not yet been updated by this
   **    point. We can not reliably call IOleObject::GetExtent
   **    because, despite the fact that will be delegated to the
   **    object properly, some objects may not have reformated their
   **    object and computed the new extents at the time of calling
   **    OnPosRectChange.
   **
   **    the best we can do to get the new extents of the object is
   **    to determine the scale factor that the object was operating at
   **    prior to the OnPosRect call and scale the new lprcPosRect
   **    using this scale factor back to HIMETRIC units.
   */
   if (lpContainerLine->m_sizeInHimetric.cx > 0 &&
      lpContainerLine->m_sizeInHimetric.cy > 0) {
      sizelHim.cx = lpLine->m_nWidthInHimetric;
      sizelHim.cy = lpLine->m_nHeightInHimetric;
      XformSizeInHimetricToPixels(NULL, &sizelHim, &sizelPix);
      sizelHim.cx = lpContainerLine->m_sizeInHimetric.cx *
               nIPObjWidth / sizelPix.cx;
      sizelHim.cy = lpContainerLine->m_sizeInHimetric.cy *
               nIPObjHeight / sizelPix.cy;

      // Convert size back to 100% zoom
      sizelHim.cx = sizelHim.cx * lpscale->dwSxD / lpscale->dwSxN;
      sizelHim.cy = sizelHim.cy * lpscale->dwSyD / lpscale->dwSyN;
   } else {
      sizelHim.cx = (long)DEFOBJWIDTH;
      sizelHim.cy = (long)DEFOBJHEIGHT;
      XformSizeInHimetricToPixels(NULL, &sizelHim, &sizelPix);
      sizelHim.cx = sizelHim.cx * nIPObjWidth / sizelPix.cx;
      sizelHim.cy = sizelHim.cy * nIPObjHeight / sizelPix.cy;
   }

   ContainerLine_UpdateExtent(lpContainerLine, &sizelHim);
   ContainerLine_GetPosRect(lpContainerLine, (LPRECT)&rcNewPosRect);
   ContainerDoc_GetClipRect(lpContainerLine->m_lpDoc, (LPRECT)&rcClipRect);

#if defined( _DEBUG )
   OleDbgOutRect3("CntrLine_IPSite_OnPosRectChange (PosRect --OUT)",
         (LPRECT)&rcNewPosRect);
   OleDbgOutRect3("CntrLine_IPSite_OnPosRectChange (ClipRect--OUT)",
         (LPRECT)&rcClipRect);
#endif
   OLEDBG_BEGIN2("IOleInPlaceObject::SetObjectRects called\r\n")
   lpContainerLine->m_lpOleIPObj->lpVtbl->SetObjectRects(
         lpContainerLine->m_lpOleIPObj,
         (LPRECT)&rcNewPosRect,
         (LPRECT)&rcClipRect
   );
   OLEDBG_END2

   /* NOTE: (INSIDEOUT CONTAINER) Because this object just changed
   **    size, this may cause other in-place active objects in the
   **    document to move. in ICNTROTL's case any object below this
   **    object would be affected. in this case it would be necessary
   **    to call SetObjectRects to each affected in-place active object.
   */
   if (g_fInsideOutContainer) {
      lpLL = OutlineDoc_GetLineList(lpOutlineDoc);
      nIndex = LineList_GetLineIndex(lpLL, (LPLINE)lpContainerLine);

      ContainerDoc_UpdateInPlaceObjectRects(
            lpContainerLine->m_lpDoc, nIndex);
   }
   OLEDBG_END2
   // release artificial AddRef
   CntrLine_IPSite_Release(lpThis);
   return NOERROR;
}


CNTRLINE.C

/*************************************************************************
**
**    OLE 2 Container Sample Code
**
**    cntrline.c
**
**    This file contains ContainerLine methods.
**
**    (c) Copyright Microsoft Corp. 1992 - 1997 All Rights Reserved
**
*************************************************************************/

#include "outline.h"

OLEDBGDATA

extern LPOUTLINEAPP         g_lpApp;
extern IUnknownVtbl         g_CntrLine_UnknownVtbl;
extern IOleClientSiteVtbl   g_CntrLine_OleClientSiteVtbl;
extern IAdviseSinkVtbl      g_CntrLine_AdviseSinkVtbl;

#if defined( INPLACE_CNTR )
extern IOleInPlaceSiteVtbl  g_CntrLine_OleInPlaceSiteVtbl;
extern BOOL g_fInsideOutContainer;
#endif  // INPLACE_CNTR

// REVIEW: should use string resource for messages
OLECHAR ErrMsgDoVerb[] = OLESTR("OLE object action failed!");

/* prototype for static functions */
static void InvertDiffRect(LPRECT lprcPix, LPRECT lprcObj, HDC hDC);

/*************************************************************************
** ContainerLine
**    This object represents the location within the container where
**    the embedded/linked object lives. It exposes interfaces to the
**    object that allow the object to get information about its
**    embedding site and to announce notifications of important events
**    (changed, closed, saved)
**
**    The ContainerLine exposes the following interfaces:
**      IUnknown
**      IOleClientSite
**      IAdviseSink
*************************************************************************/

/*************************************************************************
** ContainerLine::IUnknown interface implementation
*************************************************************************/

// IUnknown::QueryInterface
STDMETHODIMP CntrLine_Unk_QueryInterface(
      LPUNKNOWN           lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;

   return ContainerLine_QueryInterface(lpContainerLine, riid, lplpvObj);
}

// IUnknown::AddRef
STDMETHODIMP_(ULONG) CntrLine_Unk_AddRef(LPUNKNOWN lpThis)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;

   OleDbgAddRefMethod(lpThis, "IUnknown");

   return ContainerLine_AddRef(lpContainerLine);
}

// IUnknown::Release
STDMETHODIMP_(ULONG) CntrLine_Unk_Release(LPUNKNOWN lpThis)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;

   OleDbgReleaseMethod(lpThis, "IUnknown");

   return ContainerLine_Release(lpContainerLine);
}

/*************************************************************************
** ContainerLine::IOleClientSite interface implementation
*************************************************************************/

// IOleClientSite::QueryInterface
STDMETHODIMP CntrLine_CliSite_QueryInterface(
      LPOLECLIENTSITE     lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;

   return ContainerLine_QueryInterface(lpContainerLine, riid, lplpvObj);
}

// IOleClientSite::AddRef
STDMETHODIMP_(ULONG) CntrLine_CliSite_AddRef(LPOLECLIENTSITE lpThis)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;

   OleDbgAddRefMethod(lpThis, "IOleClientSite");

   return ContainerLine_AddRef(lpContainerLine);
}

// IOleClientSite::Release
STDMETHODIMP_(ULONG) CntrLine_CliSite_Release(LPOLECLIENTSITE lpThis)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;

   OleDbgReleaseMethod(lpThis, "IOleClientSite");

   return ContainerLine_Release(lpContainerLine);
}

// IOleClientSite::SaveObject
STDMETHODIMP CntrLine_CliSite_SaveObject(LPOLECLIENTSITE lpThis)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;
   LPPERSISTSTORAGE lpPersistStg = lpContainerLine->m_lpPersistStg;
   SCODE sc = S_OK;
   HRESULT hrErr;

   OLEDBG_BEGIN2("CntrLine_CliSite_SaveObject\r\n")

   if (! lpPersistStg) {
      /* NOTE: The object is NOT loaded. a container must be
      **    prepared for the fact that an object that it thinks it
      **    has unloaded may still call IOleClientSite::SaveObject.
      **    in this case the container should fail the save call and
      **    NOT try to reload the object. this scenario arises if you
      **    have a in-process server (DLL object) which has a
      **    connected linking client. even after the embedding
      **    container unloads the DLL object, the link connection
      **    keeps the object alive. it may then as part of its
      **    shutdown try to call IOCS::SaveObject, but then it is too
      **    late.
      */
      OLEDBG_END2

      return E_FAIL;
   }

   // mark ContainerDoc as now dirty
   OutlineDoc_SetModified(
         (LPOUTLINEDOC)lpContainerLine->m_lpDoc, TRUE, TRUE, FALSE);

   /* Tell OLE object to save itself
   ** NOTE: it is NOT sufficient to ONLY call
   **    IPersistStorage::Save method. it is also necessary to call
   **    WriteClassStg. the helper API OleSave does this automatically.
   */
   OLEDBG_BEGIN2("OleSave called\r\n")
   hrErr=OleSave(lpPersistStg,lpContainerLine->m_lpStg, TRUE/*fSameAsLoad*/);
   OLEDBG_END2

   if (hrErr != NOERROR) {
      OleDbgOutHResult("WARNING: OleSave returned", hrErr);
      sc = hrErr;
   }

   // NOTE: even if OleSave fails, SaveCompleted must be called.
   OLEDBG_BEGIN2("IPersistStorage::SaveCompleted called\r\n")
   hrErr = lpPersistStg->lpVtbl->SaveCompleted(lpPersistStg, NULL);
   OLEDBG_END2

   if (hrErr != NOERROR) {
      OleDbgOutHResult("WARNING: SaveCompleted returned",hrErr);
      if (sc == S_OK)
         sc = hrErr;
   }

   OLEDBG_END2
   return sc;
}

// IOleClientSite::GetMoniker
STDMETHODIMP CntrLine_CliSite_GetMoniker(
      LPOLECLIENTSITE     lpThis,
      DWORD               dwAssign,
      DWORD               dwWhichMoniker,
      LPMONIKER FAR*      lplpmk
)
{
   LPCONTAINERLINE lpContainerLine;

   lpContainerLine=((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;

   OLEDBG_BEGIN2("CntrLine_CliSite_GetMoniker\r\n")

   // NOTE: we must make sure to set output pointer parameters to NULL
   *lplpmk = NULL;

   switch (dwWhichMoniker) {

      case OLEWHICHMK_CONTAINER:
         /* NOTE: create a FileMoniker which identifies the
         **    entire container document.
         */
         *lplpmk = OleDoc_GetFullMoniker(
               (LPOLEDOC)lpContainerLine->m_lpDoc,
               dwAssign
         );
         break;

      case OLEWHICHMK_OBJREL:

         /* NOTE: create an ItemMoniker which identifies the
         **    OLE object relative to the container document.
         */
         *lplpmk = ContainerLine_GetRelMoniker(lpContainerLine, dwAssign);
         break;

      case OLEWHICHMK_OBJFULL:
      {
         /* NOTE: create an absolute moniker which identifies the
         **    OLE object in the container document. this moniker is
         **    created as a composite of the absolute moniker for the
         **    entire document appended with an item moniker which
         **    identifies the OLE object relative to the document.
         */

         *lplpmk = ContainerLine_GetFullMoniker(lpContainerLine, dwAssign);
         break;
      }
   }

   OLEDBG_END2

   if (*lplpmk != NULL)
      return NOERROR;
   else
      return E_FAIL;
}

// IOleClientSite::GetContainer
STDMETHODIMP CntrLine_CliSite_GetContainer(
      LPOLECLIENTSITE     lpThis,
      LPOLECONTAINER FAR* lplpContainer
)
{
   LPCONTAINERLINE lpContainerLine;
   HRESULT hrErr;

   // artificial AddRef in case object is deleted during call
   CntrLine_CliSite_AddRef(lpThis);
   OLEDBG_BEGIN2("CntrLine_CliSite_GetContainer\r\n")

   lpContainerLine=((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;

   hrErr = OleDoc_QueryInterface(
         (LPOLEDOC)lpContainerLine->m_lpDoc,
         &IID_IOleContainer,
         (LPVOID FAR*)lplpContainer
   );

   OLEDBG_END2
   // release artificial AddRef
   CntrLine_CliSite_Release(lpThis);
   return hrErr;
}

// IOleClientSite::ShowObject
STDMETHODIMP CntrLine_CliSite_ShowObject(LPOLECLIENTSITE lpThis)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpContainerLine->m_lpDoc;
   LPLINELIST lpLL = OutlineDoc_GetLineList(lpOutlineDoc);
   int nIndex = LineList_GetLineIndex(lpLL, (LPLINE)lpContainerLine);
   HWND hWndFrame = OutlineApp_GetFrameWindow(g_lpApp);

   // artificial AddRef in case object is deleted during call
   CntrLine_CliSite_AddRef(lpThis);

   OLEDBG_BEGIN2("CntrLine_CliSite_ShowObject\r\n")

   /* make sure our doc window is visible and not minimized.
   **    the OutlineDoc_ShowWindow function will cause the app window
   **    to show itself SW_SHOWNORMAL.
   */
   if (! IsWindowVisible(hWndFrame) || IsIconic(hWndFrame))
      OutlineDoc_ShowWindow(lpOutlineDoc);

   BringWindowToTop(hWndFrame);

   /* make sure that the OLE object is currently in view. if necessary
   **    scroll the document in order to bring it into view.
   */
   LineList_ScrollLineIntoView(lpLL, nIndex);

#if defined( INPLACE_CNTR )
   /* after the in-place object is scrolled into view, we need to ask
   **    it to update its rect for the new clip rect coordinates
   */
   ContainerDoc_UpdateInPlaceObjectRects((LPCONTAINERDOC)lpOutlineDoc, 0);
#endif

   OLEDBG_END2
   // release artificial AddRef
   CntrLine_CliSite_Release(lpThis);
   return NOERROR;
}

// IOleClientSite::OnShowWindow
STDMETHODIMP CntrLine_CliSite_OnShowWindow(LPOLECLIENTSITE lpThis, BOOL fShow)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpContainerLine->m_lpDoc;
   LPLINELIST lpLL = OutlineDoc_GetLineList(lpOutlineDoc);
   int nIndex = LineList_GetLineIndex(lpLL, (LPLINE)lpContainerLine);

   // artificial AddRef in case object is deleted during call
   CntrLine_CliSite_AddRef(lpThis);

   if (fShow) {
      OLEDBG_BEGIN2("CntrLine_CliSite_OnShowWindow(TRUE)\r\n")

      /* NOTE: we need to hatch out the OLE object now; it has
      **    just been opened in a window elsewhere (open editing as
      **    opposed to in-place activation).
      **    force the line to re-draw with the hatch.
      */
      lpContainerLine->m_fObjWinOpen = TRUE;
      LineList_ForceLineRedraw(lpLL, nIndex, FALSE /*fErase*/);

   } else {
      OLEDBG_BEGIN2("CntrLine_CliSite_OnShowWindow(FALSE)\r\n")

      /* NOTE: the object associated with this container site has
      **    just closed its server window. we should now remove the
      **    hatching that indicates that the object is open
      **    elsewhere. also our window should now come to the top.
      **    force the line to re-draw without the hatch.
      */
      lpContainerLine->m_fObjWinOpen = FALSE;
      LineList_ForceLineRedraw(lpLL, nIndex, TRUE /*fErase*/);

      BringWindowToTop(lpOutlineDoc->m_hWndDoc);
      SetFocus(lpOutlineDoc->m_hWndDoc);
   }

   OLEDBG_END2
   // release artificial AddRef
   CntrLine_CliSite_Release(lpThis);

   return NOERROR;
}

// IOleClientSite::RequestNewObjectLayout
STDMETHODIMP CntrLine_CliSite_RequestNewObjectLayout(LPOLECLIENTSITE lpThis)
{
   OleDbgOut2("CntrLine_CliSite_RequestNewObjectLayout\r\n");

   /* NOTE: this method is NOT yet used. it is for future layout
   **    negotiation support.
   */
   return E_NOTIMPL;
}

/*************************************************************************
** ContainerLine::IAdviseSink interface implementation
*************************************************************************/

// IAdviseSink::QueryInterface
STDMETHODIMP CntrLine_AdvSink_QueryInterface(
      LPADVISESINK        lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;

   return ContainerLine_QueryInterface(lpContainerLine, riid, lplpvObj);
}

// IAdviseSink::AddRef
STDMETHODIMP_(ULONG) CntrLine_AdvSink_AddRef(LPADVISESINK lpThis)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;

   OleDbgAddRefMethod(lpThis, "IAdviseSink");

   return ContainerLine_AddRef(lpContainerLine);
}

// IAdviseSink::Release
STDMETHODIMP_(ULONG) CntrLine_AdvSink_Release (LPADVISESINK lpThis)
{
   LPCONTAINERLINE lpContainerLine =
         ((struct COleClientSiteImpl FAR*)lpThis)->lpContainerLine;

   OleDbgReleaseMethod(lpThis, "IAdviseSink");

   return ContainerLine_Release(lpContainerLine);
}

// IAdviseSink::OnDataChange
STDMETHODIMP_(void) CntrLine_AdvSink_OnDataChange(
      LPADVISESINK        lpThis,
      FORMATETC FAR*      lpFormatetc,
      STGMEDIUM FAR*      lpStgmed
)
{
   OleDbgOut2("CntrLine_AdvSink_OnDataChange\r\n");
   // We are not interested in data changes (only view changes)
   //      (ie. nothing to do)
}

STDMETHODIMP_(void) CntrLine_AdvSink_OnViewChange(
      LPADVISESINK        lpThis,
      DWORD               aspects,
      LONG                lindex
)
{
   LPCONTAINERLINE lpContainerLine;
   LPOUTLINEDOC lpOutlineDoc;
   HWND hWndDoc;
   LPLINELIST lpLL;
   MSG msg;
   int nIndex;

   // artificial AddRef in case object is destroyed during call
   CntrLine_AdvSink_AddRef(lpThis);

   OLEDBG_BEGIN2("CntrLine_AdvSink_OnViewChange\r\n")

   lpContainerLine = ((struct CAdviseSinkImpl FAR*)lpThis)->lpContainerLine;
   lpOutlineDoc = (LPOUTLINEDOC)lpContainerLine->m_lpDoc;

   /* NOTE: at this point we simply invalidate the rectangle of
   **    the object to force a repaint in the future, we mark
   **    that the extents of the object may have changed
   **    (m_fDoGetExtent=TRUE), and we post a message
   **    (WM_U_UPDATEOBJECTEXTENT) to our document that one or more
   **    OLE objects may need to have their extents updated. later
   **    when this message is processed, the document loops through
   **    all lines to see if any are marked as needing an extent update.
   **    if infact the extents did change. this can be done by calling
   **    IViewObject2::GetExtent to retreive the object's current
   **    extents and comparing with the last known extents for the
   **    object. if the extents changed, then relayout space for the
   **    object before drawing. we postpone the check to get
   **    the extents now because OnViewChange is an asyncronis method,
   **    and we have to careful not to call any syncronis methods back
   **    to the object. while it WOULD be OK to call the
   **    IViewObject2::GetExtent method within the asyncronis
   **    OnViewChange method (because this method is handled by the
   **    object handler and never remoted), it is good practise to not
   **    call any object methods from within an asyncronis
   **    notification method.
   **    if there is already WM_U_UPDATEOBJECTEXTENT message waiting
   **    in our message queue, there is no need to post another one.
   **    in this way, if the server is updating quicker than we can
   **    keep up, we do not make unneccsary GetExtent calls. also if
   **    drawing is disabled, we postpone updating the extents of any
   **    objects until drawing is re-enabled.
   */
   lpContainerLine->m_fDoGetExtent = TRUE;
   hWndDoc = OutlineDoc_GetWindow((LPOUTLINEDOC)lpContainerLine->m_lpDoc);

   if (lpOutlineDoc->m_nDisableDraw == 0 &&
      ! PeekMessage(&msg, hWndDoc,
         WM_U_UPDATEOBJECTEXTENT, WM_U_UPDATEOBJECTEXTENT,
         PM_NOREMOVE | PM_NOYIELD)) {
      PostMessage(hWndDoc, WM_U_UPDATEOBJECTEXTENT, 0, 0L);
   }

   // force the modified line to redraw.
   lpLL = OutlineDoc_GetLineList(lpOutlineDoc);
   nIndex = LineList_GetLineIndex(lpLL, (LPLINE)lpContainerLine);
   LineList_ForceLineRedraw(lpLL, nIndex, TRUE /*fErase*/);

   OLEDBG_END2
   // release artificial AddRef
   CntrLine_AdvSink_Release(lpThis);
}

// IAdviseSink::OnRename
STDMETHODIMP_(void) CntrLine_AdvSink_OnRename(
      LPADVISESINK        lpThis,
      LPMONIKER           lpmk
)
{
   OleDbgOut2("CntrLine_AdvSink_OnRename\r\n");
   /* NOTE: the Embedding Container has nothing to do here. this
   **    notification is important for linking situations. it tells
   **    the OleLink objects to update their moniker because the
   **    source object has been renamed (track the link source).
   */
}

// IAdviseSink::OnSave
STDMETHODIMP_(void) CntrLine_AdvSink_OnSave(LPADVISESINK lpThis)
{
   OleDbgOut2("CntrLine_AdvSink_OnSave\r\n");
   /* NOTE: the Embedding Container has nothing to do here. this
   **    notification is only useful to clients which have set up a
   **    data cache with the ADVFCACHE_ONSAVE flag.
   */
}

// IAdviseSink::OnClose
STDMETHODIMP_(void) CntrLine_AdvSink_OnClose(LPADVISESINK lpThis)
{
   OleDbgOut2("CntrLine_AdvSink_OnClose\r\n");
   /* NOTE: the Embedding Container has nothing to do here. this
   **    notification is important for the OLE's default object handler
   **    and the OleLink object. it tells them the remote object is
   **    shutting down.
   */
}

/*************************************************************************
** ContainerLine Support Functions
*************************************************************************/

/* ContainerLine_Init
** ------------------
**  Initialize fields in a newly constructed ContainerLine line object.
**  NOTE: ref cnt of ContainerLine initialized to 0
*/
void ContainerLine_Init(LPCONTAINERLINE lpContainerLine, int nTab, HDC hDC)
{
   Line_Init((LPLINE)lpContainerLine, nTab, hDC);  // init base class fields

   ((LPLINE)lpContainerLine)->m_lineType           = CONTAINERLINETYPE;
   ((LPLINE)lpContainerLine)->m_nWidthInHimetric   = DEFOBJWIDTH;
   ((LPLINE)lpContainerLine)->m_nHeightInHimetric  = DEFOBJHEIGHT;
   lpContainerLine->m_cRef                         = 0;
   lpContainerLine->m_szStgName[0]                 = '\0';
   lpContainerLine->m_fObjWinOpen                  = FALSE;
   lpContainerLine->m_fMonikerAssigned             = FALSE;
   lpContainerLine->m_dwDrawAspect                 = DVASPECT_CONTENT;

   lpContainerLine->m_fGuardObj                    = FALSE;
   lpContainerLine->m_fDoGetExtent                 = FALSE;
   lpContainerLine->m_fDoSetExtent                 = FALSE;
   lpContainerLine->m_sizeInHimetric.cx            = -1;
   lpContainerLine->m_sizeInHimetric.cy            = -1;

   lpContainerLine->m_lpStg                        = NULL;
   lpContainerLine->m_lpDoc                        = NULL;
   lpContainerLine->m_lpOleObj                     = NULL;
   lpContainerLine->m_lpViewObj2                   = NULL;
   lpContainerLine->m_lpPersistStg                 = NULL;
   lpContainerLine->m_lpOleLink                    = NULL;
   lpContainerLine->m_dwLinkType                   = 0;
   lpContainerLine->m_fLinkUnavailable             = FALSE;
   lpContainerLine->m_lpszShortType                = NULL;

#if defined( INPLACE_CNTR )
   lpContainerLine->m_fIpActive                    = FALSE;
   lpContainerLine->m_fUIActive                    = FALSE;
   lpContainerLine->m_fIpVisible                   = FALSE;
   lpContainerLine->m_lpOleIPObj                   = NULL;
   lpContainerLine->m_fInsideOutObj                = FALSE;
   lpContainerLine->m_fIpChangesUndoable           = FALSE;
   lpContainerLine->m_fIpServerRunning             = FALSE;
   lpContainerLine->m_hWndIpObject                 = NULL;
   lpContainerLine->m_nHorizScrollShift            = 0;
#endif  // INPLACE_CNTR

   INIT_INTERFACEIMPL(
         &lpContainerLine->m_Unknown,
         &g_CntrLine_UnknownVtbl,
         lpContainerLine
   );

   INIT_INTERFACEIMPL(
         &lpContainerLine->m_OleClientSite,
         &g_CntrLine_OleClientSiteVtbl,
         lpContainerLine
   );

   INIT_INTERFACEIMPL(
         &lpContainerLine->m_AdviseSink,
         &g_CntrLine_AdviseSinkVtbl,
         lpContainerLine
   );

#if defined( INPLACE_CNTR )
   INIT_INTERFACEIMPL(
         &lpContainerLine->m_OleInPlaceSite,
         &g_CntrLine_OleInPlaceSiteVtbl,
         lpContainerLine
   );
#endif  // INPLACE_CNTR
}

/* Setup the OLE object associated with the ContainerLine */
BOOL ContainerLine_SetupOleObject(
      LPCONTAINERLINE         lpContainerLine,
      BOOL                    fDisplayAsIcon,
      HGLOBAL                 hMetaPict
)
{
   DWORD dwDrawAspect = (fDisplayAsIcon ? DVASPECT_ICON : DVASPECT_CONTENT);
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpContainerLine->m_lpDoc;

   /* Cache a pointer to the IViewObject2* interface. *Required*
   **      we need this everytime we draw the object.
   **
   ** NOTE: We require the object to support IViewObject2
   **    interface. this is an extension to the IViewObject interface
   **    that was added with the OLE 2.01 release. This interface must
   **    be supported by all object handlers and DLL-based objects.
   */
   lpContainerLine->m_lpViewObj2 = (LPVIEWOBJECT2)OleStdQueryInterface(
         (LPUNKNOWN)lpContainerLine->m_lpOleObj, &IID_IViewObject2);
   if (! lpContainerLine->m_lpViewObj2) {
#if defined( _DEBUG )
      OleDbgAssertSz(
         lpContainerLine->m_lpViewObj2,"IViewObject2 NOT supported\r\n");
#endif
      return FALSE;
   }

   // Cache a pointer to the IPersistStorage* interface. *Required*
   //      we need this everytime we save the object.
   lpContainerLine->m_lpPersistStg = (LPPERSISTSTORAGE)OleStdQueryInterface(
         (LPUNKNOWN)lpContainerLine->m_lpOleObj, &IID_IPersistStorage);
   if (! lpContainerLine->m_lpPersistStg) {
      OleDbgAssert(lpContainerLine->m_lpPersistStg);
      return FALSE;
   }

   // Cache a pointer to the IOleLink* interface if supported. *Optional*
   //      if supported the object is a link. we need this to manage the link
   lpContainerLine->m_lpOleLink = (LPOLELINK)OleStdQueryInterface(
         (LPUNKNOWN)lpContainerLine->m_lpOleObj, &IID_IOleLink);
   if (lpContainerLine->m_lpOleLink) {
      OLEDBG_BEGIN2("IOleLink::GetUpdateOptions called\r\n")
      lpContainerLine->m_lpOleLink->lpVtbl->GetUpdateOptions(
            lpContainerLine->m_lpOleLink, &lpContainerLine->m_dwLinkType);
      OLEDBG_END2
   } else
      lpContainerLine->m_dwLinkType = 0;  // NOT a link

   /* get the short user type name of the object. this
   **    is used all the time when we have to build the object
   **    verb menu. we will cache this information to make it
   **    quicker to build the verb menu.
   */
   OleDbgAssert(lpContainerLine->m_lpszShortType == NULL);
   OLEDBG_BEGIN2("IOleObject::GetUserType called\r\n")
   lpContainerLine->m_lpOleObj->lpVtbl->GetUserType(
         lpContainerLine->m_lpOleObj,
         USERCLASSTYPE_SHORT,
         /*(LPOLESTR FAR*)*/&lpContainerLine->m_lpszShortType
   );
   OLEDBG_END2

   /* Perform that standard setup for the OLE object. this includes:
   **      setup View advise
   **      Call IOleObject::SetHostNames
   **      Call OleSetContainedObject
   */
   OleStdSetupAdvises(
         lpContainerLine->m_lpOleObj,
         dwDrawAspect,
         /*(LPOLESTR)*/APPNAME,
         lpOutlineDoc->m_lpszDocTitle,
         (LPADVISESINK)&lpContainerLine->m_AdviseSink,
         TRUE    /*fCreate*/
   );

#if defined( INPLACE_CNTR )
   /* NOTE: (INSIDE-OUT CONTAINER) An inside-out container should
   **    check if the object is an inside-out and prefers to be
   **    activated when visible type of object. if not the object
   **    should not be allowed to keep its window up after it gets
   **    UIDeactivated.
   */
   if (g_fInsideOutContainer) {
      DWORD mstat;
      OLEDBG_BEGIN2("IOleObject::GetMiscStatus called\r\n")
      lpContainerLine->m_lpOleObj->lpVtbl->GetMiscStatus(
            lpContainerLine->m_lpOleObj,
            DVASPECT_CONTENT,
            (DWORD FAR*)&mstat
      );
      OLEDBG_END2

      lpContainerLine->m_fInsideOutObj = (BOOL)
            (mstat & (OLEMISC_INSIDEOUT|OLEMISC_ACTIVATEWHENVISIBLE));
   }
#endif  // INPLACE_CNTR

   if (fDisplayAsIcon) {
      /* user has requested to display icon aspect instead of content
      **    aspect.
      **    NOTE: we do not have to delete the previous aspect cache
      **    because one did not get set up.
      */
      OleStdSwitchDisplayAspect(
            lpContainerLine->m_lpOleObj,
            &lpContainerLine->m_dwDrawAspect,
            dwDrawAspect,
            hMetaPict,
            FALSE,  /* fDeleteOldAspect */
            TRUE,   /* fSetupViewAdvise */
            (LPADVISESINK)&lpContainerLine->m_AdviseSink,
            NULL /*fMustUpdate*/        // this can be ignored; update
                                 // for switch to icon not req'd
      );
   }
   return TRUE;
}

/* Create an ContainerLine object and return the pointer */
LPCONTAINERLINE ContainerLine_Create(
      DWORD                   dwOleCreateType,
      HDC                     hDC,
      UINT                    nTab,
      LPCONTAINERDOC          lpContainerDoc,
      LPCLSID                 lpclsid,
      LPOLESTR                lpszFileName,
      BOOL                    fDisplayAsIcon,
      HGLOBAL                 hMetaPict,
      LPOLESTR                lpszStgName
)
{
   LPCONTAINERLINE lpContainerLine = NULL;
   LPOLEOBJECT     lpObj = NULL;
   LPSTORAGE       lpDocStg = ContainerDoc_GetStg(lpContainerDoc);
   DWORD           dwDrawAspect =
                  (fDisplayAsIcon ? DVASPECT_ICON : DVASPECT_CONTENT);
   DWORD           dwOleRenderOpt =
                  (fDisplayAsIcon ? OLERENDER_NONE : OLERENDER_DRAW);
   HRESULT         hrErr;

   OLEDBG_BEGIN3("ContainerLine_Create\r\n")

   if (lpDocStg == NULL) {
      OleDbgAssertSz(lpDocStg != NULL, "Doc storage is NULL");
      goto error;
   }

   lpContainerLine=(LPCONTAINERLINE) New((DWORD)sizeof(CONTAINERLINE));
   if (lpContainerLine == NULL) {
      OleDbgAssertSz(lpContainerLine!=NULL,"Error allocating ContainerLine");
      goto error;
   }

   ContainerLine_Init(lpContainerLine, nTab, hDC);

   /* NOTE: in order to avoid re-entrancy we will set a flag to
   **    guard our object. if this guard is set, then the object is
   **    not ready to have any OLE interface methods called. it is
   **    necessary to guard the object this way while it is being
   **    created or loaded.
   */
   lpContainerLine->m_fGuardObj = TRUE;

   /* NOTE: In order to have a stable ContainerLine object we must
   **    AddRef the object's refcnt. this will be later released when
   **    the ContainerLine is deleted.
   */
   ContainerLine_AddRef(lpContainerLine);

   OLESTRCPY(lpContainerLine->m_szStgName, lpszStgName);
   lpContainerLine->m_lpDoc = lpContainerDoc;

   /* Create a new storage for the object inside the doc's storage */
   lpContainerLine->m_lpStg = OleStdCreateChildStorage(lpDocStg,lpszStgName);
   if (lpContainerLine->m_lpStg == NULL) {
      OleDbgAssert(lpContainerLine->m_lpStg != NULL);
      goto error;
   }

   lpContainerLine->m_dwLinkType = 0;

   switch (dwOleCreateType) {

      case IOF_SELECTCREATENEW:

         OLEDBG_BEGIN2("OleCreate called\r\n")
         hrErr = OleCreate (
               lpclsid,
               &IID_IOleObject,
               dwOleRenderOpt,
               NULL,
               (LPOLECLIENTSITE)&lpContainerLine->m_OleClientSite,
               lpContainerLine->m_lpStg,
               (LPVOID FAR*)&lpContainerLine->m_lpOleObj
         );
         OLEDBG_END2

#if defined( _DEBUG )
         if (hrErr != NOERROR)
            OleDbgOutHResult("OleCreate returned", hrErr);
#endif

         break;

      case IOF_SELECTCREATEFROMFILE:

         OLEDBG_BEGIN2("OleCreateFromFile called\r\n")
         hrErr = OleCreateFromFile (
               &CLSID_NULL,
               lpszFileName,
               &IID_IOleObject,
               dwOleRenderOpt,
               NULL,
               (LPOLECLIENTSITE)&lpContainerLine->m_OleClientSite,
               lpContainerLine->m_lpStg,
               (LPVOID FAR*)&lpContainerLine->m_lpOleObj
         );
         OLEDBG_END2

#if defined( _DEBUG )
         if (hrErr != NOERROR)
            OleDbgOutHResult("OleCreateFromFile returned", hrErr);

#endif
         break;

      case IOF_CHECKLINK:

         OLEDBG_BEGIN2("OleCreateLinkToFile called\r\n")
         hrErr = OleCreateLinkToFile (
               lpszFileName,
               &IID_IOleObject,
               dwOleRenderOpt,
               NULL,
               (LPOLECLIENTSITE)&lpContainerLine->m_OleClientSite,
               lpContainerLine->m_lpStg,
               (LPVOID FAR*)&lpContainerLine->m_lpOleObj
         );
         OLEDBG_END2

#if defined( _DEBUG )
         if (hrErr != NOERROR)
            OleDbgOutHResult("OleCreateLinkToFile returned", hrErr);
#endif
         break;
   }
   if (hrErr != NOERROR)
      goto error;

   if (! ContainerLine_SetupOleObject(
                        lpContainerLine, fDisplayAsIcon, hMetaPict)) {
      goto error;
   }

   /* NOTE: clear our re-entrancy guard. the object is now ready
   **    to have interface methods called.
   */
   lpContainerLine->m_fGuardObj = FALSE;

   OLEDBG_END3
   return lpContainerLine;

error:
   OutlineApp_ErrorMessage(g_lpApp, OLESTR("Could not create object!"));

   // Destroy partially created OLE object
   if (lpContainerLine)
      ContainerLine_Delete(lpContainerLine);
   OLEDBG_END3
   return NULL;
}

LPCONTAINERLINE ContainerLine_CreateFromData(
      HDC                     hDC,
      UINT                    nTab,
      LPCONTAINERDOC          lpContainerDoc,
      LPDATAOBJECT            lpSrcDataObj,
      DWORD                   dwCreateType,
      CLIPFORMAT              cfFormat,
      BOOL                    fDisplayAsIcon,
      HGLOBAL                 hMetaPict,
      LPOLESTR                lpszStgName
)
{
   HGLOBAL         hData = NULL;
   LPCONTAINERLINE lpContainerLine = NULL;
   LPOLEOBJECT     lpObj = NULL;
   LPSTORAGE       lpDocStg = ContainerDoc_GetStg(lpContainerDoc);
   DWORD           dwDrawAspect =
                  (fDisplayAsIcon ? DVASPECT_ICON : DVASPECT_CONTENT);
   DWORD           dwOleRenderOpt;
   FORMATETC       renderFmtEtc;
   LPFORMATETC     lpRenderFmtEtc = NULL;
   HRESULT         hrErr;
   LPUNKNOWN       lpUnk = NULL;

   OLEDBG_BEGIN3("ContainerLine_CreateFromData\r\n")

   if (dwCreateType == OLECREATEFROMDATA_STATIC && cfFormat != 0) {
      // a particular type of static object should be created

      dwOleRenderOpt = OLERENDER_FORMAT;
      lpRenderFmtEtc = (LPFORMATETC)&renderFmtEtc;

      if (cfFormat == CF_METAFILEPICT)
         SETDEFAULTFORMATETC(renderFmtEtc, cfFormat, TYMED_MFPICT);
      else if (cfFormat == CF_BITMAP)
         SETDEFAULTFORMATETC(renderFmtEtc, cfFormat, TYMED_GDI);
      else
         SETDEFAULTFORMATETC(renderFmtEtc, cfFormat, TYMED_HGLOBAL);

   } else if (dwCreateType == OLECREATEFROMDATA_STATIC && fDisplayAsIcon) {
      // a link that currently displayed as an icon needs to be
      // converted to a STATIC picture object. this case is driven
      // from "BreakLink" in the "Links" dialog. because the current
      // data in the source object's cache is DVASPECT_ICON we need
      // to tell the OleCreateStaticFromData API to look for
      // DVASPECT_ICON data. the static object that results however,
      // is considered to be displayed in the DVASPECT_CONTENT view.

      dwOleRenderOpt = OLERENDER_DRAW;
      lpRenderFmtEtc = (LPFORMATETC)&renderFmtEtc;
      SETFORMATETC(renderFmtEtc,0,DVASPECT_ICON,NULL,TYMED_NULL,-1);
      dwDrawAspect = DVASPECT_CONTENT;   // static obj displays only CONTENT

   } else if (fDisplayAsIcon && hMetaPict) {
      // a special icon should be used. first we create the object
      // OLERENDER_NONE and then we stuff the special icon into the cache.

      dwOleRenderOpt = OLERENDER_NONE;

   } else if (fDisplayAsIcon && hMetaPict == NULL) {
      // the object's default icon should be used

      dwOleRenderOpt = OLERENDER_DRAW;
      lpRenderFmtEtc = (LPFORMATETC)&renderFmtEtc;
      SETFORMATETC(renderFmtEtc,0,DVASPECT_ICON,NULL,TYMED_NULL,-1);

   } else {
      // create standard DVASPECT_CONTENT/OLERENDER_DRAW object
      dwOleRenderOpt = OLERENDER_DRAW;
   }

   if (lpDocStg == NULL) {
      OleDbgAssertSz(lpDocStg != NULL, "Doc storage is NULL");
      goto error;
   }

   lpContainerLine=(LPCONTAINERLINE) New((DWORD)sizeof(CONTAINERLINE));
   if (lpContainerLine == NULL) {
      OleDbgAssertSz(lpContainerLine!=NULL,"Error allocating ContainerLine");
      goto error;
   }

   ContainerLine_Init(lpContainerLine, nTab, hDC);

   /* NOTE: in order to avoid re-entrancy we will set a flag to
   **    guard our object. if this guard is set, then the object is
   **    not ready to have any OLE interface methods called. it is
   **    necessary to guard the object this way while it is being
   **    created or loaded.
   */
   lpContainerLine->m_fGuardObj = TRUE;

   /* NOTE: In order to have a stable ContainerLine object we must
   **    AddRef the object's refcnt. this will be later released when
   **    the ContainerLine is deleted.
   */
   ContainerLine_AddRef(lpContainerLine);

   OLESTRCPY(lpContainerLine->m_szStgName, lpszStgName);
   lpContainerLine->m_lpDoc = lpContainerDoc;

   /* Create a new storage for the object inside the doc's storage */
   lpContainerLine->m_lpStg = OleStdCreateChildStorage(lpDocStg,lpszStgName);
   if (lpContainerLine->m_lpStg == NULL) {
      OleDbgAssert(lpContainerLine->m_lpStg != NULL);
      goto error;
   }

   switch (dwCreateType) {

      case OLECREATEFROMDATA_LINK:

         OLEDBG_BEGIN2("OleCreateLinkFromData called\r\n")
         hrErr = OleCreateLinkFromData (
               lpSrcDataObj,
               &IID_IOleObject,
               dwOleRenderOpt,
               lpRenderFmtEtc,
               (LPOLECLIENTSITE)&lpContainerLine->m_OleClientSite,
               lpContainerLine->m_lpStg,
               (LPVOID FAR*)&lpContainerLine->m_lpOleObj
         );
         OLEDBG_END2

#if defined( _DEBUG )
         if (hrErr != NOERROR)
            OleDbgOutHResult("OleCreateLinkFromData returned", hrErr);
#endif
         break;

      case OLECREATEFROMDATA_OBJECT:

         OLEDBG_BEGIN2("OleCreateFromData called\r\n")
         hrErr = OleCreateFromData (
               lpSrcDataObj,
               &IID_IOleObject,
               dwOleRenderOpt,
               lpRenderFmtEtc,
               (LPOLECLIENTSITE)&lpContainerLine->m_OleClientSite,
               lpContainerLine->m_lpStg,
               (LPVOID FAR*)&lpContainerLine->m_lpOleObj
         );
         OLEDBG_END2

#if defined( _DEBUG )
         if (hrErr != NOERROR)
            OleDbgOutHResult("OleCreateFromData returned", hrErr);
#endif
         break;

      case OLECREATEFROMDATA_STATIC:

         OLEDBG_BEGIN2("OleCreateStaticFromData called\r\n")
         hrErr = OleCreateStaticFromData (
               lpSrcDataObj,
               &IID_IOleObject,
               dwOleRenderOpt,
               lpRenderFmtEtc,
               (LPOLECLIENTSITE)&lpContainerLine->m_OleClientSite,
               lpContainerLine->m_lpStg,
               (LPVOID FAR*)&lpContainerLine->m_lpOleObj
         );
         OLEDBG_END2

#if defined( _DEBUG )
         if (hrErr != NOERROR)
            OleDbgOutHResult("OleCreateStaticFromData returned", hrErr);
#endif
         break;
   }

   if (hrErr != NOERROR)
      goto error;

   if (! ContainerLine_SetupOleObject(
                        lpContainerLine, fDisplayAsIcon, hMetaPict)) {
      goto error;
   }

   /* NOTE: clear our re-entrancy guard. the object is now ready
   **    to have interface methods called.
   */
   lpContainerLine->m_fGuardObj = FALSE;

   OLEDBG_END3
   return lpContainerLine;

error:
   OutlineApp_ErrorMessage(g_lpApp, OLESTR("Could not create object!"));
   // Destroy partially created OLE object
   if (lpContainerLine)
      ContainerLine_Delete(lpContainerLine);
   OLEDBG_END3
   return NULL;
}

/* ContainerLine_AddRef
** --------------------
**
**  increment the ref count of the line object.
**
**    Returns the new ref count on the object
*/
ULONG ContainerLine_AddRef(LPCONTAINERLINE lpContainerLine)
{
   ++lpContainerLine->m_cRef;

#if defined( _DEBUG )
   OleDbgOutRefCnt4(
         "ContainerLine_AddRef: cRef++\r\n",
         lpContainerLine,
         lpContainerLine->m_cRef
   );
#endif
   return lpContainerLine->m_cRef;
}

/* ContainerLine_Release
** ---------------------
**
**  decrement the ref count of the line object.
**    if the ref count goes to 0, then the line is destroyed.
**
**    Returns the remaining ref count on the object
*/
ULONG ContainerLine_Release(LPCONTAINERLINE lpContainerLine)
{
   ULONG cRef;

   /*********************************************************************
   ** NOTE: when the obj refcnt == 0, then destroy the object.     **
   **     otherwise the object is still in use.                        **
   *********************************************************************/

   cRef = --lpContainerLine->m_cRef;

#if defined( _DEBUG )
   OleDbgAssertSz(
         lpContainerLine->m_cRef >= 0,"Release called with cRef == 0");

   OleDbgOutRefCnt4(
         "ContainerLine_Release: cRef--\r\n",
         lpContainerLine,
         cRef
   );
#endif
   if (cRef == 0)
      ContainerLine_Destroy(lpContainerLine);

   return cRef;
}

/* ContainerLine_QueryInterface
** ----------------------------
**
** Retrieve a pointer to an interface on the ContainerLine object.
**
**    Returns NOERROR if interface is successfully retrieved.
**            E_NOINTERFACE if the interface is not supported
*/
HRESULT ContainerLine_QueryInterface(
      LPCONTAINERLINE         lpContainerLine,
      REFIID                  riid,
      LPVOID FAR*             lplpvObj
)
{
   SCODE sc = E_NOINTERFACE;

   /* NOTE: we must make sure to set all out ptr parameters to NULL. */
   *lplpvObj = NULL;

   if (IsEqualIID(riid, &IID_IUnknown)) {
      OleDbgOut4("ContainerLine_QueryInterface: IUnknown* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpContainerLine->m_Unknown;
      ContainerLine_AddRef(lpContainerLine);
      sc = S_OK;
   }
   else if (IsEqualIID(riid, &IID_IOleClientSite)) {
      OleDbgOut4("ContainerLine_QueryInterface: IOleClientSite* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpContainerLine->m_OleClientSite;
      ContainerLine_AddRef(lpContainerLine);
      sc = S_OK;
   }
   else if (IsEqualIID(riid, &IID_IAdviseSink)) {
      OleDbgOut4("ContainerLine_QueryInterface: IAdviseSink* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpContainerLine->m_AdviseSink;
      ContainerLine_AddRef(lpContainerLine);
      sc = S_OK;
   }
#if defined( INPLACE_CNTR )
   else if (IsEqualIID(riid, &IID_IOleWindow)
          || IsEqualIID(riid, &IID_IOleInPlaceSite)) {
      OleDbgOut4("ContainerLine_QueryInterface: IOleInPlaceSite* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpContainerLine->m_OleInPlaceSite;
      ContainerLine_AddRef(lpContainerLine);
      sc = S_OK;
   }
#endif  // INPLACE_CNTR

   OleDbgQueryInterfaceMethod(*lplpvObj);

   return sc;
}

BOOL ContainerLine_LoadOleObject(LPCONTAINERLINE lpContainerLine)
{
   LPOUTLINEDOC    lpOutlineDoc = (LPOUTLINEDOC)lpContainerLine->m_lpDoc;
   LPSTORAGE       lpDocStg = ContainerDoc_GetStg(lpContainerLine->m_lpDoc);
   LPOLECLIENTSITE lpOleClientSite;
   LPMONIKER       lpmkObj;
   LPOLEAPP        lpOleApp = (LPOLEAPP)g_lpApp;
   BOOL            fPrevEnable1;
   BOOL            fPrevEnable2;
   HRESULT         hrErr;

   if (lpContainerLine->m_fGuardObj)
      return FALSE;                // object in process of creation

   if (lpContainerLine->m_lpOleObj)
      return TRUE;                // object already loaded

   OLEDBG_BEGIN3("ContainerLine_LoadOleObject\r\n")

   /* NOTE: in order to avoid re-entrancy we will set a flag to
   **    guard our object. if this guard is set, then the object is
   **    not ready to have any OLE interface methods called. it is
   **    necessary to guard the object this way while it is being
   **    created or loaded.
   */
   lpContainerLine->m_fGuardObj = TRUE;

   /* if object storage is not already open, then open it */
   if (! lpContainerLine->m_lpStg) {
      lpContainerLine->m_lpStg = OleStdOpenChildStorage(
            lpDocStg,
            lpContainerLine->m_szStgName,
            STGM_READWRITE
      );
      if (lpContainerLine->m_lpStg == NULL) {
         OleDbgAssert(lpContainerLine->m_lpStg != NULL);
         goto error;
      }
   }

   /* NOTE: if the OLE object being loaded is in a data transfer
   **    document, then we should NOT pass a IOleClientSite* pointer
   **    to the OleLoad call. This particularly critical if the OLE
   **    object is an OleLink object. If a non-NULL client site is
   **    passed to the OleLoad function, then the link will bind to
   **    the source if its is running. in the situation that we are
   **    loading the object as part of a data transfer document we do
   **    not want this connection to be established. even worse, if
   **    the link source is currently blocked or busy, then this could
   **    hang the system. it is simplest to never pass a
   **    IOleClientSite* when loading an object in a data transfer
   **    document.
   */
   lpOleClientSite = (lpOutlineDoc->m_fDataTransferDoc ?
         NULL : (LPOLECLIENTSITE)&lpContainerLine->m_OleClientSite);

   /* NOTE: we do not want to ever give the Busy/NotResponding
   **    dialogs when we are loading an object. if the object is a
   **    link, it will attempt to BindIfRunning to the link source. if
   **    the link source is currently busy, this could cause the Busy
   **    dialog to come up. even if the link source is busy,
   **    we do not want put up the busy dialog. thus we will disable
   **    the dialog and later re-enable them
   */
   OleApp_DisableBusyDialogs(lpOleApp, &fPrevEnable1, &fPrevEnable2);

   OLEDBG_BEGIN2("OleLoad called\r\n")
   hrErr = OleLoad (
         lpContainerLine->m_lpStg,
         &IID_IOleObject,
         lpOleClientSite,
         (LPVOID FAR*)&lpContainerLine->m_lpOleObj
   );
   OLEDBG_END2

   // re-enable the Busy/NotResponding dialogs
   OleApp_EnableBusyDialogs(lpOleApp, fPrevEnable1, fPrevEnable2);

   if (hrErr != NOERROR) {
      OleDbgAssertSz(hrErr == NOERROR, "Could not load object!");
      OleDbgOutHResult("OleLoad returned", hrErr);
      goto error;
   }

   /* Cache a pointer to the IViewObject2* interface. *Required*
   **      we need this everytime we draw the object.
   **
   ** NOTE: We require the object to support IViewObject2
   **    interface. this is an extension to the IViewObject interface
   **    that was added with the OLE 2.01 release. This interface must
   **    be supported by all object handlers and DLL-based objects.
   */
   lpContainerLine->m_lpViewObj2 = (LPVIEWOBJECT2)OleStdQueryInterface(
         (LPUNKNOWN)lpContainerLine->m_lpOleObj, &IID_IViewObject2);
   if (! lpContainerLine->m_lpViewObj2) {
#if defined( _DEBUG )
      OleDbgAssertSz(
         lpContainerLine->m_lpViewObj2,"IViewObject2 NOT supported\r\n");
#endif
      goto error;
   }

   // Cache a pointer to the IPersistStorage* interface. *Required*
   //      we need this everytime we save the object.
   lpContainerLine->m_lpPersistStg = (LPPERSISTSTORAGE)OleStdQueryInterface(
         (LPUNKNOWN)lpContainerLine->m_lpOleObj, &IID_IPersistStorage);
   if (! lpContainerLine->m_lpPersistStg) {
      OleDbgAssert(lpContainerLine->m_lpPersistStg);
      goto error;
   }

   // Cache a pointer to the IOleLink* interface if supported. *Optional*
   //      if supported the object is a link. we need this to manage the link
   if (lpContainerLine->m_dwLinkType != 0) {
      lpContainerLine->m_lpOleLink = (LPOLELINK)OleStdQueryInterface(
            (LPUNKNOWN)lpContainerLine->m_lpOleObj, &IID_IOleLink);
      if (! lpContainerLine->m_lpOleLink) {
         OleDbgAssert(lpContainerLine->m_lpOleLink);
         goto error;
      }
   }

   /* NOTE: clear our re-entrancy guard. the object is now ready
   **    to have interface methods called.
   */
   lpContainerLine->m_fGuardObj = FALSE;

   /* NOTE: similarly, if the OLE object being loaded is in a data
   **    transfer document, then we do NOT need to setup any advises,
   **    call SetHostNames, SetMoniker, etc.
   */
   if (lpOleClientSite) {
      /* Setup the Advises (OLE notifications) that we are interested
      ** in receiving.
      */
      OleStdSetupAdvises(
            lpContainerLine->m_lpOleObj,
            lpContainerLine->m_dwDrawAspect,
            /*(LPOLESTR)*/APPNAME,
            lpOutlineDoc->m_lpszDocTitle,
            (LPADVISESINK)&lpContainerLine->m_AdviseSink,
            FALSE   /*fCreate*/
      );

      /* NOTE: if the OLE object has a moniker assigned, we need to
      **    inform the object by calling IOleObject::SetMoniker. this
      **    will force the OLE object to register in the
      **    RunningObjectTable when it enters the running state.
      */
      if (lpContainerLine->m_fMonikerAssigned) {
         lpmkObj = ContainerLine_GetRelMoniker(
               lpContainerLine,
               GETMONIKER_ONLYIFTHERE
         );

         if (lpmkObj) {
            OLEDBG_BEGIN2("IOleObject::SetMoniker called\r\n")
            lpContainerLine->m_lpOleObj->lpVtbl->SetMoniker(
                  lpContainerLine->m_lpOleObj,
                  OLEWHICHMK_OBJREL,
                  lpmkObj
            );
            OLEDBG_END2
            OleStdRelease((LPUNKNOWN)lpmkObj);
         }
      }

      /* get the Short form of the user type name of the object. this
      **    is used all the time when we have to build the object
      **    verb menu. we will cache this information to make it
      **    quicker to build the verb menu.
      */
      OLEDBG_BEGIN2("IOleObject::GetUserType called\r\n")
      lpContainerLine->m_lpOleObj->lpVtbl->GetUserType(
            lpContainerLine->m_lpOleObj,
            USERCLASSTYPE_SHORT,
            /*(LPOLESTR FAR*)*/&lpContainerLine->m_lpszShortType
      );
      OLEDBG_END2

#if defined( INPLACE_CNTR )
      /* NOTE: an inside-out container should check if the object
      **    is an inside-out and prefers to be activated when visible
      **    type of object. if so, the object should be immediately
      **    activated in-place, BUT NOT UIActived.
      */
      if (g_fInsideOutContainer &&
            lpContainerLine->m_dwDrawAspect == DVASPECT_CONTENT) {
         DWORD mstat;
         OLEDBG_BEGIN2("IOleObject::GetMiscStatus called\r\n")
         lpContainerLine->m_lpOleObj->lpVtbl->GetMiscStatus(
               lpContainerLine->m_lpOleObj,
               DVASPECT_CONTENT,
               (DWORD FAR*)&mstat
         );
         OLEDBG_END2

         lpContainerLine->m_fInsideOutObj = (BOOL)
               (mstat & (OLEMISC_INSIDEOUT|OLEMISC_ACTIVATEWHENVISIBLE));

         if ( lpContainerLine->m_fInsideOutObj ) {
            HWND hWndDoc = OutlineDoc_GetWindow(lpOutlineDoc);

            ContainerLine_DoVerb(
                  lpContainerLine,
                  OLEIVERB_INPLACEACTIVATE,
                  NULL,
                  FALSE,
                  FALSE
            );

            /* NOTE: following this DoVerb(INPLACEACTIVATE) the
            **    object may have taken focus. but because the
            **    object is NOT UIActive it should NOT have focus.
            **    we will make sure our document has focus.
            */
            SetFocus(hWndDoc);
         }
      }
#endif  // INPLACE_CNTR
      OLEDBG_END2

   }

   OLEDBG_END2
   return TRUE;

error:
   OLEDBG_END2
   return FALSE;
}

/* ContainerLine_CloseOleObject
** ----------------------------
**    Close the OLE object associated with the ContainerLine.
**
**    Closing the object forces the object to transition from the
**    running state to the loaded state. if the object was not running,
**    then there is no effect. it is necessary to close the OLE object
**    before releasing the pointers to the OLE object.
**
**    Returns TRUE if successfully closed,
**            FALSE if closing was aborted.
*/
BOOL ContainerLine_CloseOleObject(
      LPCONTAINERLINE         lpContainerLine,
      DWORD                   dwSaveOption
)
{
   HRESULT hrErr;
   SCODE   sc;

   if (lpContainerLine->m_fGuardObj)
      return FALSE;                // object in process of creation

   if (! lpContainerLine->m_lpOleObj)
      return TRUE;    // object is NOT loaded

   OLEDBG_BEGIN2("IOleObject::Close called\r\n")
   hrErr = lpContainerLine->m_lpOleObj->lpVtbl->Close(
         lpContainerLine->m_lpOleObj,
         (dwSaveOption == OLECLOSE_NOSAVE ?
               OLECLOSE_NOSAVE : OLECLOSE_SAVEIFDIRTY)
   );
   OLEDBG_END2

#if defined( INPLACE_CNTR )
   if (lpContainerLine->m_fIpServerRunning) {
      /* NOTE: unlock the lock held on the in-place object.
      **    it is VERY important that an in-place container
      **    that also support linking to embeddings properly manage
      **    the running of its in-place objects. in an outside-in
      **    style in-place container, when the user clicks
      **    outside of the in-place active object, the object gets
      **    UIDeactivated and the object hides its window. in order
      **    to make the object fast to reactivate, the container
      **    deliberately does not call IOleObject::Close. the object
      **    stays running in the invisible unlocked state. the idea
      **    here is if the user simply clicks outside of the object
      **    and then wants to double click again to re-activate the
      **    object, we do not want this to be slow. if we want to
      **    keep the object running, however, we MUST Lock it
      **    running. otherwise the object will be in an unstable
      **    state where if a linking client does a "silent-update"
      **    (eg. UpdateNow from the Links dialog), then the in-place
      **    server will shut down even before the object has a chance
      **    to be saved back in its container. this saving normally
      **    occurs when the in-place container closes the object. also
      **    keeping the object in the unstable, hidden, running,
      **    not-locked state can cause problems in some scenarios.
      **    ICntrOtl keeps only one object running. if the user
      **    intiates a DoVerb on another object, then that last
      **    running in-place active object is closed. a more
      **    sophistocated in-place container may keep more object running.
      **    (see CntrLine_IPSite_OnInPlaceActivate)
      */
      lpContainerLine->m_fIpServerRunning = FALSE;

      OLEDBG_BEGIN2("OleLockRunning(FALSE,TRUE) called\r\n")
      OleLockRunning((LPUNKNOWN)lpContainerLine->m_lpOleObj, FALSE, TRUE);
      OLEDBG_END2
   }
#endif

   if (hrErr != NOERROR) {
      OleDbgOutHResult("IOleObject::Close returned", hrErr);
      sc = hrErr;
      if (sc == RPC_E_CALL_REJECTED || sc==OLE_E_PROMPTSAVECANCELLED)
         return FALSE;   // object aborted shutdown
   }
   return TRUE;
}

/* ContainerLine_UnloadOleObject
** -----------------------------
**    Close the OLE object associated with the ContainerLine and
**    release all pointer held to the object.
**
**    Closing the object forces the object to transition from the
**    running state to the loaded state. if the object was not running,
**    then there is no effect. it is necessary to close the OLE object
**    before releasing the pointers to the OLE object. releasing all
**    pointers to the object allows the object to transition from
**    loaded to unloaded (or passive).
*/
void ContainerLine_UnloadOleObject(
      LPCONTAINERLINE         lpContainerLine,
      DWORD                   dwSaveOption
)
{
   if (lpContainerLine->m_lpOleObj) {

      OLEDBG_BEGIN2("IOleObject::Close called\r\n")
      lpContainerLine->m_lpOleObj->lpVtbl->Close(
            lpContainerLine->m_lpOleObj, dwSaveOption);
      OLEDBG_END2

      /* NOTE: we will take our IOleClientSite* pointer away from
      **    the object before we release all pointers to the object.
      **    in the scenario where the object is implemented as an
      **    in-proc server (DLL object), then, if there are link
      **    connections to the DLL object, it is possible that the
      **    object will not be destroyed when we release our pointers
      **    to the object. the existance of the remote link
      **    connections will hold the object alive. later when these
      **    strong connections are released, then the object may
      **    attempt to call IOleClientSite::Save if we had not taken
      **    away the client site pointer.
      */
      OLEDBG_BEGIN2("IOleObject::SetClientSite(NULL) called\r\n")
      lpContainerLine->m_lpOleObj->lpVtbl->SetClientSite(
            lpContainerLine->m_lpOleObj, NULL);
      OLEDBG_END2

      OleStdRelease((LPUNKNOWN)lpContainerLine->m_lpOleObj);
      lpContainerLine->m_lpOleObj = NULL;

      if (lpContainerLine->m_lpViewObj2) {
         OleStdRelease((LPUNKNOWN)lpContainerLine->m_lpViewObj2);
         lpContainerLine->m_lpViewObj2 = NULL;
      }
      if (lpContainerLine->m_lpPersistStg) {
         OleStdRelease((LPUNKNOWN)lpContainerLine->m_lpPersistStg);
         lpContainerLine->m_lpPersistStg = NULL;
      }

      if (lpContainerLine->m_lpOleLink) {
         OleStdRelease((LPUNKNOWN)lpContainerLine->m_lpOleLink);
         lpContainerLine->m_lpOleLink = NULL;
      }
   }

   if (lpContainerLine->m_lpszShortType) {
      OleStdFreeString(lpContainerLine->m_lpszShortType, NULL);
      lpContainerLine->m_lpszShortType = NULL;
   }
}

/* ContainerLine_Delete
** --------------------
**    Delete the ContainerLine.
**
**    NOTE: we can NOT directly destroy the memory for the
**    ContainerLine; the ContainerLine maintains a reference count. a
**    non-zero reference count indicates that the object is still
**    in-use. The OleObject keeps a reference-counted pointer to the
**    ClientLine object. we must take the actions necessary so that the
**    ContainerLine object receives Releases for outstanding
**    references. when the reference count of the ContainerLine reaches
**    zero, then the memory for the object will actually be destroyed
**    (ContainerLine_Destroy called).
**
*/
void ContainerLine_Delete(LPCONTAINERLINE lpContainerLine)
{
   OLEDBG_BEGIN2("ContainerLine_Delete\r\n")

#if defined( INPLACE_CNTR )
   if (lpContainerLine == lpContainerLine->m_lpDoc->m_lpLastIpActiveLine)
      lpContainerLine->m_lpDoc->m_lpLastIpActiveLine = NULL;
   if (lpContainerLine == lpContainerLine->m_lpDoc->m_lpLastUIActiveLine)
      lpContainerLine->m_lpDoc->m_lpLastUIActiveLine = NULL;
#endif

   /* NOTE: in order to have a stable line object during the
   **    process of deleting, we intially AddRef the line ref cnt and
   **    later Release it. This initial AddRef is artificial; it is
   **    simply done to guarantee that our object does not destroy
   **    itself until the END of this routine.
   */
   ContainerLine_AddRef(lpContainerLine);

   // Unload the loaded OLE object
   if (lpContainerLine->m_lpOleObj)
      ContainerLine_UnloadOleObject(lpContainerLine, OLECLOSE_NOSAVE);

   /* NOTE: we can NOT directly free the memory for the ContainerLine
   **    data structure until everyone holding on to a pointer to our
   **    ClientSite interface and IAdviseSink interface has released
   **    their pointers. There is one refcnt on the ContainerLine object
   **    which is held by the container itself. we will release this
   **    refcnt here.
   */
   ContainerLine_Release(lpContainerLine);

   /* NOTE: this call forces all external connections to our
   **    ContainerLine to close down and therefore guarantees that
   **    we receive all releases associated with those external
   **    connections. Strictly this call should NOT be necessary, but
   **    it is defensive coding to make this call.
   */
   OLEDBG_BEGIN2("CoDisconnectObject(lpContainerLine) called\r\n")
   CoDisconnectObject((LPUNKNOWN)&lpContainerLine->m_Unknown, 0);
   OLEDBG_END2

#if defined( _DEBUG )
   /* at this point the object all references from the OLE object to
   **    our ContainerLine object should have been released. there
   **    should only be 1 remaining reference that will be released below.
   */
   if (lpContainerLine->m_cRef != 1) {
      OleDbgOutRefCnt(
         "WARNING: ContainerLine_Delete: cRef != 1\r\n",
         lpContainerLine,
         lpContainerLine->m_cRef
      );
   }
#endif

   ContainerLine_Release(lpContainerLine); // release artificial AddRef above
   OLEDBG_END2
}

/* ContainerLine_Destroy

** ---------------------
**    Destroy (Free) the memory used by a ContainerLine structure.
**    This function is called when the ref count of the ContainerLine goes
**    to zero. the ref cnt goes to zero after ContainerLine_Delete forces
**    the OleObject to unload and release its pointers to the
**    ContainerLine IOleClientSite and IAdviseSink interfaces.
*/

void ContainerLine_Destroy(LPCONTAINERLINE lpContainerLine)
{
   LPUNKNOWN lpTmpObj;

   OLEDBG_BEGIN2("ContainerLine_Destroy\r\n")

   // Release the storage opened for the OLE object
   if (lpContainerLine->m_lpStg) {
      lpTmpObj = (LPUNKNOWN)lpContainerLine->m_lpStg;
      lpContainerLine->m_lpStg = NULL;

      OleStdRelease(lpTmpObj);
   }

   if (lpContainerLine->m_lpszShortType) {
      OleStdFreeString(lpContainerLine->m_lpszShortType, NULL);
      lpContainerLine->m_lpszShortType = NULL;
   }

   Delete(lpContainerLine);        // Free the memory for the structure itself
   OLEDBG_END2
}

/* ContainerLine_CopyToDoc
 * -----------------------
 *
 *      Copy a ContainerLine to another Document (usually ClipboardDoc)
 */
BOOL ContainerLine_CopyToDoc(
      LPCONTAINERLINE         lpSrcLine,
      LPOUTLINEDOC            lpDestDoc,
      int                     nIndex
)
{
   LPCONTAINERLINE lpDestLine = NULL;
   LPLINELIST  lpDestLL = &lpDestDoc->m_LineList;
   HDC         hDC;
   HRESULT     hrErr;
   BOOL        fStatus;
   LPSTORAGE   lpDestDocStg = ((LPOLEDOC)lpDestDoc)->m_lpStg;
   LPSTORAGE   lpDestObjStg = NULL;

   lpDestLine = (LPCONTAINERLINE) New((DWORD)sizeof(CONTAINERLINE));
   if (lpDestLine == NULL) {
      OleDbgAssertSz(lpDestLine!=NULL, "Error allocating ContainerLine");
      return FALSE;
   }

   hDC = LineList_GetDC(lpDestLL);
   ContainerLine_Init(lpDestLine, ((LPLINE)lpSrcLine)->m_nTabLevel, hDC);
   LineList_ReleaseDC(lpDestLL, hDC);

   /* NOTE: In order to have a stable ContainerLine object we must
   **    AddRef the object's refcnt. this will be later released when
   **    the ContainerLine is deleted.
   */
   ContainerLine_AddRef(lpDestLine);

   lpDestLine->m_lpDoc = (LPCONTAINERDOC)lpDestDoc;

   // Copy data of the original source ContainerLine.
   ((LPLINE)lpDestLine)->m_nWidthInHimetric =
         ((LPLINE)lpSrcLine)->m_nWidthInHimetric;
   ((LPLINE)lpDestLine)->m_nHeightInHimetric =
         ((LPLINE)lpSrcLine)->m_nHeightInHimetric;
   lpDestLine->m_fMonikerAssigned = lpSrcLine->m_fMonikerAssigned;
   lpDestLine->m_dwDrawAspect = lpSrcLine->m_dwDrawAspect;
   lpDestLine->m_sizeInHimetric = lpSrcLine->m_sizeInHimetric;
   lpDestLine->m_dwLinkType = lpSrcLine->m_dwLinkType;

   /* We must create a new sub-storage for the embedded object within
   **    the destination document's storage. We will first attempt to
   **    use the same storage name as the source line. if this name is
   **    in use, then we will allocate a new name. in this way we try
   **    to keep the name associated with the OLE object unchanged
   **    through a Cut/Paste operation.
   */
   lpDestObjStg = OleStdCreateChildStorage(
         lpDestDocStg,
         lpSrcLine->m_szStgName
   );
   if (lpDestObjStg) {
      OLESTRCPY(lpDestLine->m_szStgName, lpSrcLine->m_szStgName);
   } else {
      /* the original name was in use, make up a new name. */
      ContainerDoc_GetNextStgName(
            (LPCONTAINERDOC)lpDestDoc,
            lpDestLine->m_szStgName,
            CWCSTORAGENAME
      );
      lpDestObjStg = OleStdCreateChildStorage(
            lpDestDocStg,
            lpDestLine->m_szStgName
      );
   }
   if (lpDestObjStg == NULL) {
      OleDbgAssertSz(lpDestObjStg != NULL, "Error creating child stg");
      goto error;
   }

   // Copy over storage of the embedded object itself

   if (! lpSrcLine->m_lpOleObj) {

      /*****************************************************************
      ** CASE 1: object is NOT loaded.
      **    because the object is not loaded, we can simply copy the
      **    object's current storage to the new storage.
      *****************************************************************/

      /* if current object storage is not already open, then open it */
      if (! lpSrcLine->m_lpStg) {
         LPSTORAGE lpSrcDocStg = ((LPOLEDOC)lpSrcLine->m_lpDoc)->m_lpStg;

         if (! lpSrcDocStg) goto error;

         // open object storage.
         lpSrcLine->m_lpStg = OleStdOpenChildStorage(
               lpSrcDocStg,
               lpSrcLine->m_szStgName,
               STGM_READWRITE
         );
         if (lpSrcLine->m_lpStg == NULL) {
#if defined( _DEBUG )
            OleDbgAssertSz(
                  lpSrcLine->m_lpStg != NULL,
                  "Error opening child stg"
            );
#endif
            goto error;
         }
      }

      hrErr = lpSrcLine->m_lpStg->lpVtbl->CopyTo(
            lpSrcLine->m_lpStg,
            0,
            NULL,
            NULL,
            lpDestObjStg
      );
      if (hrErr != NOERROR) {
         OleDbgOutHResult("WARNING: lpSrcObjStg->CopyTo returned", hrErr);
         goto error;
      }

      fStatus = OleStdCommitStorage(lpDestObjStg);

   } else {

      /*****************************************************************
      ** CASE 2: object IS loaded.
      **    we must tell the object to save into the new storage.
      *****************************************************************/

      SCODE sc = S_OK;
      LPPERSISTSTORAGE lpPersistStg = lpSrcLine->m_lpPersistStg;
      OleDbgAssert(lpPersistStg);

      OLEDBG_BEGIN2("OleSave called\r\n")
      hrErr = OleSave(lpPersistStg, lpDestObjStg, FALSE /*fSameAsLoad*/);
      OLEDBG_END2

      if (hrErr != NOERROR) {
         OleDbgOutHResult("WARNING: OleSave returned", hrErr);
         sc = hrErr;
      }

      // NOTE: even if OleSave fails, SaveCompleted must be called.
      OLEDBG_BEGIN2("IPersistStorage::SaveCompleted called\r\n")
      hrErr=lpPersistStg->lpVtbl->SaveCompleted(lpPersistStg,NULL);
      OLEDBG_END2

      if (hrErr != NOERROR) {
         OleDbgOutHResult("WARNING: SaveCompleted returned",hrErr);
         if (sc == S_OK)
            sc = hrErr;
      }

      if (sc != S_OK)
         goto error;

   }

   OutlineDoc_AddLine(lpDestDoc, (LPLINE)lpDestLine, nIndex);
   OleStdVerifyRelease(
         (LPUNKNOWN)lpDestObjStg,
         OLESTR("Copied object stg not released")
   );

   return TRUE;

error:

   // Delete any partially created storage.
   if (lpDestObjStg) {
      OleStdVerifyRelease(
            (LPUNKNOWN)lpDestObjStg,
            OLESTR("Copied object stg not released")
      );

      lpDestDocStg->lpVtbl->DestroyElement(
            lpDestDocStg,
            lpDestLine->m_szStgName
      );
      lpDestLine->m_szStgName[0] = '\0';
   }

   // destroy partially created ContainerLine
   if (lpDestLine)
      ContainerLine_Delete(lpDestLine);
   return FALSE;
}

/* ContainerLine_UpdateExtent
** --------------------------
**   Update the size of the ContainerLine because the extents of the
**    object may have changed.
**
**    NOTE: because we are using a Windows OwnerDraw ListBox, we must
**    constrain the maximum possible height of a line. the ListBox has
**    a limitation (unfortunately) that no line can become larger than
**    255 pixels. thus we force the object to scale maintaining its
**    aspect ratio if this maximum line height limit is reached. the
**    actual maximum size for an object at 100% Zoom is
**    255
**
**    RETURNS TRUE -- if the extents of the object changed
**            FALSE -- if the extents did NOT change
*/
BOOL ContainerLine_UpdateExtent(
      LPCONTAINERLINE     lpContainerLine,
      LPSIZEL             lpsizelHim
)
{
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpContainerLine->m_lpDoc;
   LPLINELIST lpLL = OutlineDoc_GetLineList(lpOutlineDoc);
   LPLINE lpLine = (LPLINE)lpContainerLine;
   int nIndex = LineList_GetLineIndex(lpLL, lpLine);
   UINT nOrgWidthInHimetric = lpLine->m_nWidthInHimetric;
   UINT nOrgHeightInHimetric = lpLine->m_nHeightInHimetric;
   BOOL fWidthChanged = FALSE;
   BOOL fHeightChanged = FALSE;
   SIZEL sizelHim;
   HRESULT hrErr;

   if (!lpContainerLine || !lpContainerLine->m_lpOleObj)
      return FALSE;

   if (lpContainerLine->m_fGuardObj)
      return FALSE;                // object in process of creation

   OLEDBG_BEGIN3("ContainerLine_UpdateExtent\r\n");

   lpContainerLine->m_fDoGetExtent = FALSE;

   if (! lpsizelHim) {
      /* NOTE: We want to call IViewObject2::GetExtent instead of
      **    IOleObject::GetExtent. IViewObject2::GetExtent method was
      **    added in OLE 2.01 release. It always retrieves the
      **    extents of the object corresponding to that which will be
      **    drawn by calling IViewObject::Draw. Normally, this is
      **    determined by the data stored in the data cache. This
      **    call will never result in a remoted (LRPC) call.
      */
      OLEDBG_BEGIN2("IViewObject2::GetExtent called\r\n")
      hrErr = lpContainerLine->m_lpViewObj2->lpVtbl->GetExtent(
            lpContainerLine->m_lpViewObj2,
            lpContainerLine->m_dwDrawAspect,
            -1,     /*lindex*/
            NULL,   /*ptd*/
            (LPSIZEL)&sizelHim
      );
      OLEDBG_END2
      if (hrErr != NOERROR)
         sizelHim.cx = sizelHim.cy = 0;

      lpsizelHim = (LPSIZEL)&sizelHim;
   }

   if (lpsizelHim->cx == lpContainerLine->m_sizeInHimetric.cx &&
      lpsizelHim->cy == lpContainerLine->m_sizeInHimetric.cy) {
      goto noupdate;
   }

   if (lpsizelHim->cx > 0 || lpsizelHim->cy > 0) {
      lpContainerLine->m_sizeInHimetric = *lpsizelHim;
   } else {
      /* object does not have any extents; let's use our container
      **    chosen arbitrary size for OLE objects.
      */
      lpContainerLine->m_sizeInHimetric.cx = (long)DEFOBJWIDTH;
      lpContainerLine->m_sizeInHimetric.cy = (long)DEFOBJHEIGHT;
   }

   ContainerLine_SetLineHeightFromObjectExtent(
         lpContainerLine,
         (LPSIZEL)&lpContainerLine->m_sizeInHimetric);

   // if height of object changed, then reset the height of line in LineList
   if (nOrgHeightInHimetric != lpLine->m_nHeightInHimetric) {
      LineList_SetLineHeight(lpLL, nIndex, lpLine->m_nHeightInHimetric);
      fHeightChanged = TRUE;
   }

   fWidthChanged = LineList_RecalcMaxLineWidthInHimetric(
         lpLL,
         nOrgWidthInHimetric
   );
   fWidthChanged |= (nOrgWidthInHimetric != lpLine->m_nWidthInHimetric);

   if (fHeightChanged || fWidthChanged) {
      OutlineDoc_ForceRedraw(lpOutlineDoc, TRUE);

      // mark ContainerDoc as now dirty
      OutlineDoc_SetModified(lpOutlineDoc, TRUE, TRUE, TRUE);
   }

   OLEDBG_END3
   return TRUE;

noupdate:
   OLEDBG_END3
   return FALSE;   // No UPDATE necessary
}

/* ContainerLine_DoVerb
** --------------------
**    Activate the OLE object and perform a specific verb.
*/
BOOL ContainerLine_DoVerb(
      LPCONTAINERLINE lpContainerLine,
      LONG            iVerb,
      LPMSG           lpMsg,
      BOOL            fMessage,
      BOOL            fAction
)
{
   HRESULT hrErr;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpContainerLine->m_lpDoc;
   RECT rcPosRect;
   OLEDBG_BEGIN3("ContainerLine_DoVerb\r\n")

   if (lpContainerLine->m_fGuardObj) {
      // object in process of creation--Fail the DoVerb call
      hrErr = E_FAIL;
      goto error;
   }

   /* if object is not already loaded, then load it now. objects are
   **    loaded lazily in this manner.
   */
   if (! lpContainerLine->m_lpOleObj)
      ContainerLine_LoadOleObject(lpContainerLine);

   if (! lpContainerLine->m_lpOleObj) {
#if defined( _DEBUG )
      OleDbgAssertSz(
            lpContainerLine->m_lpOleObj != NULL,
            "OLE object not loaded"
      );
#endif
      goto error;
   }

ExecuteDoVerb:

   ContainerLine_GetPosRect(lpContainerLine, (LPRECT)&rcPosRect);

   // run the object
   hrErr = ContainerLine_RunOleObject(lpContainerLine);
   if (hrErr != NOERROR)
      goto error;

   /* Tell object server to perform a "verb". */
   OLEDBG_BEGIN2("IOleObject::DoVerb called\r\n")
   hrErr = lpContainerLine->m_lpOleObj->lpVtbl->DoVerb (
         lpContainerLine->m_lpOleObj,
         iVerb,
         lpMsg,
         (LPOLECLIENTSITE)&lpContainerLine->m_OleClientSite,
         -1,
         OutlineDoc_GetWindow(lpOutlineDoc),
         (LPCRECT)&rcPosRect
   );
   OLEDBG_END2

   /* NOTE: IOleObject::DoVerb may return a success code
   **    OLE_S_INVALIDVERB. this SCODE should NOT be considered an
   **    error; thus it is important to use the "FAILED" macro to
   **    check for an error SCODE.
   */
   if (FAILED(hrErr)) {
      OleDbgOutHResult("WARNING: lpOleObj->DoVerb returned", hrErr);
      goto error;
   }

#if defined( INPLACE_CNTR )
   /* NOTE: we want to keep only 1 inplace server active at any
   **    given time. so when we start to do a DoVerb on another line,
   **    then we want to shut down the previously activated server. in
   **    this way we keep at most one inplace server active at a time.
   **    because it is possible that the object we want to do DoVerb
   **    on is handled by the same EXE as that of the previously
   **    activated server, then we do not want the EXE to be shut down
   **    only to be launched again. in order to avoid this we will do
   **    the DoVerb BEFORE trying to shutdown the previous object.
   */
   if (!g_fInsideOutContainer) {
      ContainerDoc_ShutDownLastInPlaceServerIfNotNeeded(
            lpContainerLine->m_lpDoc, lpContainerLine);
   }
#endif  // INPLACE_CNTR

   OLEDBG_END3
   return TRUE;

error:

   if (lpContainerLine->m_dwLinkType != 0)
      lpContainerLine->m_fLinkUnavailable = TRUE;

#if defined( INPLACE_CNTR )
   /* NOTE: we want to keep only 1 inplace server active at any
   **    given time. so when we start to do a DoVerb on another line,
   **    then we want to shut down the previously activated server. in
   **    this way we keep at most one inplace server active at a time.
   **    even though the DoVerb failed, we will still shutdown the
   **    previous server. it is possible that we ran out of memory and
   **    that the DoVerb will succeed next time after shutting down
   **    the pervious server.
   */
   if (!g_fInsideOutContainer) {
      ContainerDoc_ShutDownLastInPlaceServerIfNotNeeded(
            lpContainerLine->m_lpDoc, lpContainerLine);
   }
#endif  // INPLACE_CNTR

   /* NOTE: if an error occurs we must give the appropriate error
   **    message box. there are many potential errors that can occur.
   **    the OLE2.0 user model has specific guidelines as to the
   **    dialogs that should be displayed given the various potential
   **    errors (eg. server not registered, unavailable link source.
   **    the OLE2UI library includes support for most of the
   **    recommended message dialogs. (see OleUIPrompUser function)
   */
   if (fMessage) {
      BOOL fReDoVerb = ContainerLine_ProcessOleRunError(
            lpContainerLine,
            hrErr,
            fAction,
            (lpMsg==NULL && iVerb>=0)   /* fMenuInvoked */
      );
      if (fReDoVerb) {
         goto ExecuteDoVerb;
      }
   }

   OLEDBG_END3
   return FALSE;
}

/* ContainerLine_ProcessOleRunError
 * --------------------------------
 *
 *  Handle the various errors possible when attempting OleRun of an object.
 *  Popup up appropriate message according to the error and/or take action
 *  specified button pressed by the user.
 *
 *  NOTE: The OLE 2.0 User Interface Guidelines specify the messages
 *            that should be given for the following situations:
 *                  1. Link Source Unavailable...goto Links dialog
 *                  2. Server Not Registered...goto Convert dialog
 *                  3. Link Type Changed
 *                  4. Server Not Found
 *
 *  Returns:    TRUE -- repeat IOleObject::DoVerb call.
 *              FALSE -- do NOT repeat IOleObject::DoVerb call.
 *
 *  Comments:
 *      (see LinkTypeChanged case)
 */
BOOL ContainerLine_ProcessOleRunError(
      LPCONTAINERLINE         lpContainerLine,
      HRESULT                 hrErr,
      BOOL                    fAction,
      BOOL                    fMenuInvoked
)
{
   LPOUTLINEDOC    lpOutlineDoc = (LPOUTLINEDOC)lpContainerLine->m_lpDoc;
   HWND            hwndParent = OutlineDoc_GetWindow(lpOutlineDoc);
   SCODE           sc = hrErr;
   BOOL            fReDoVerb = FALSE;
   char szTemp[256];
   char szTemp2[256];

   OleDbgOutHResult("ProcessError", hrErr);
   if ((sc >= MK_E_FIRST) && (sc <= MK_E_LAST))
      goto LinkSourceUnavailable;
   if (sc == OLE_E_CANT_BINDTOSOURCE)
      goto LinkSourceUnavailable;
   if (sc == STG_E_PATHNOTFOUND)
      goto LinkSourceUnavailable;
   if (sc == REGDB_E_CLASSNOTREG)
      goto ServerNotReg;
   if (sc == OLE_E_STATIC)
      goto ServerNotReg;  // user dblclk'ed a static object w/ no svr reg'd
   if (sc == OLE_E_CLASSDIFF)
      goto LinkTypeChanged;
   if (sc == CO_E_APPDIDNTREG)
      goto ServerNotFound;
   if (sc == CO_E_APPNOTFOUND)
      goto ServerNotFound;
   if (sc == E_OUTOFMEMORY)
      goto OutOfMemory;

   if (ContainerLine_IsOleLink(lpContainerLine))
      goto LinkSourceUnavailable;
   else
      goto ServerNotFound;

/*************************************************************************
** Error handling routines                                              **
*************************************************************************/
LinkSourceUnavailable:
   wcstombs(szTemp, APPNAME, 255);
   if (ID_PU_LINKS == OleUIPromptUser(
            (WORD)IDD_LINKSOURCEUNAVAILABLE,
            hwndParent,
            szTemp)) {
      if (fAction) {
         ContainerDoc_EditLinksCommand(lpContainerLine->m_lpDoc);
      }
   }
   return fReDoVerb;

ServerNotReg:
   {
   LPOLESTR lpszUserType = NULL;
   CLIPFORMAT  cfFormat;       // not used

   hrErr = ReadFmtUserTypeStg(
         lpContainerLine->m_lpStg, &cfFormat, &lpszUserType);

   wcstombs(szTemp, APPNAME, 255);
   wcstombs(szTemp2, lpszUserType, 255);
   if (ID_PU_CONVERT == OleUIPromptUser(
         (WORD)IDD_SERVERNOTREG,
         hwndParent,
         szTemp,
         (hrErr == NOERROR) ? szTemp2 : "Unknown Object")) {
      if (fAction) {
         ContainerDoc_ConvertCommand(
               lpContainerLine->m_lpDoc,
               TRUE        // fMustActivate
         );
      }
   }

   if (lpszUserType)
      OleStdFreeString(lpszUserType, NULL);

   return fReDoVerb;
   }

LinkTypeChanged:
   {
   /* NOTE: If IOleObject::DoVerb is executed on a Link object and it
   **    returns OLE_E_CLASSDIFF because the link source is no longer
   **    the expected class, then if the verb is a semantically
   **    defined verb (eg. OLEIVERB_PRIMARY, OLEIVERB_SHOW,
   **    OLEIVERB_OPEN, etc.), then the link should be re-created with
   **    the new link source and the same verb executed on the new
   **    link. there is no need to give a message to the user. if the
   **    user had selected a verb from the object's verb menu
   **    (fMenuInvoked==TRUE), then we can not be certain of the
   **    semantics of the verb and whether the new link can still
   **    support the verb. in this case the user is given a prompt
   **    telling him to "choose a different command offered by the new
   **    type".
   */

   LPOLESTR       lpszUserType = NULL;

   if (fMenuInvoked) {
      hrErr = lpContainerLine->m_lpOleObj->lpVtbl->GetUserType(
            lpContainerLine->m_lpOleObj,USERCLASSTYPE_FULL,&lpszUserType);
      wcstombs(szTemp, APPNAME, 255);
      wcstombs(szTemp2, lpszUserType, 255);
      OleUIPromptUser(
            (WORD)IDD_LINKTYPECHANGED,
            hwndParent,
            szTemp,
            (hrErr == NOERROR) ? szTemp2 : "Unknown Object"
      );
   } else {
      fReDoVerb = TRUE;
   }
   ContainerLine_ReCreateLinkBecauseClassDiff(lpContainerLine);

   if (lpszUserType)
      OleStdFreeString(lpszUserType, NULL);

   return fReDoVerb;
   }

ServerNotFound:
   wcstombs(szTemp, APPNAME, 255);
   OleUIPromptUser(
         (WORD)IDD_SERVERNOTFOUND,
         hwndParent,
         /*(LPOLESTR)*/szTemp);
   return fReDoVerb;

OutOfMemory:
   wcstombs(szTemp, APPNAME, 255);
   OleUIPromptUser(
         (WORD)IDD_OUTOFMEMORY,
         hwndParent,
         szTemp);
   return fReDoVerb;
}

/* ContainerLine_ReCreateLinkBecauseClassDiff
** ------------------------------------------
**    Re-create the link. The existing link was created when
**    the moniker binds to a link source bound of a different class
**    than the same moniker currently binds to. the link may be a
**    special link object specifically used with the old link
**    source class. thus the link object needs to be re-created to
**    give the new link source the opportunity to create its own
**    special link object. (see description "Custom Link Source")
*/
HRESULT ContainerLine_ReCreateLinkBecauseClassDiff(
      LPCONTAINERLINE lpContainerLine
)
{
   LPOLELINK   lpOleLink = lpContainerLine->m_lpOleLink;
   HGLOBAL     hMetaPict = NULL;
   LPMONIKER   lpmkLinkSrc = NULL;
   SCODE       sc = E_FAIL;
   HRESULT     hrErr;

   if (lpOleLink &&
      lpOleLink->lpVtbl->GetSourceMoniker(
            lpOleLink, (LPMONIKER FAR*)&lpmkLinkSrc) == NOERROR) {

      BOOL            fDisplayAsIcon =
                     (lpContainerLine->m_dwDrawAspect==DVASPECT_ICON);
      STGMEDIUM       medium;
      LPDATAOBJECT    lpDataObj = NULL;
      DWORD           dwOleRenderOpt;
      FORMATETC       renderFmtEtc;
      LPFORMATETC     lpRenderFmtEtc = NULL;

      // get the current icon if object is displayed as icon
      if (fDisplayAsIcon &&
         (lpDataObj = (LPDATAOBJECT)OleStdQueryInterface( (LPUNKNOWN)
               lpContainerLine->m_lpOleObj,&IID_IDataObject)) != NULL ) {
         hMetaPict = OleStdGetData(
               lpDataObj, CF_METAFILEPICT, NULL, DVASPECT_ICON, &medium);
         OleStdRelease((LPUNKNOWN)lpDataObj);
      }

      if (fDisplayAsIcon && hMetaPict) {
         // a special icon should be used. first we create the object
         // OLERENDER_NONE. then we stuff the special icon into the cache.

         dwOleRenderOpt = OLERENDER_NONE;

      } else if (fDisplayAsIcon && hMetaPict == NULL) {
         // the object's default icon should be used

         dwOleRenderOpt = OLERENDER_DRAW;
         lpRenderFmtEtc = (LPFORMATETC)&renderFmtEtc;
         SETFORMATETC(renderFmtEtc,0,DVASPECT_ICON,NULL,TYMED_NULL,-1);

      } else {
         // create standard DVASPECT_CONTENT/OLERENDER_DRAW object
         dwOleRenderOpt = OLERENDER_DRAW;
      }

      // unload original link object
      ContainerLine_UnloadOleObject(lpContainerLine, OLECLOSE_SAVEIFDIRTY);

      // delete entire contents of the current object's storage
      OleStdDestroyAllElements(lpContainerLine->m_lpStg);

      OLEDBG_BEGIN2("OleCreateLink called\r\n")
      hrErr = OleCreateLink (
            lpmkLinkSrc,
            &IID_IOleObject,
            dwOleRenderOpt,
            lpRenderFmtEtc,
            (LPOLECLIENTSITE)&lpContainerLine->m_OleClientSite,
            lpContainerLine->m_lpStg,
            (LPVOID FAR*)&lpContainerLine->m_lpOleObj
      );
      OLEDBG_END2

      if (hrErr == NOERROR) {
         if (! ContainerLine_SetupOleObject(
               lpContainerLine, fDisplayAsIcon, hMetaPict) ) {

            // ERROR: setup of the new link failed.
            // revert the storage to restore the original link.
            ContainerLine_UnloadOleObject(
                  lpContainerLine, OLECLOSE_NOSAVE);
            lpContainerLine->m_lpStg->lpVtbl->Revert(
                  lpContainerLine->m_lpStg);
            sc = E_FAIL;
         } else {
            sc = S_OK;  // IT WORKED!

         }
      }
      else {
         sc = hrErr;
         OleDbgOutHResult("OleCreateLink returned", hrErr);
         // ERROR: Re-creating the link failed.
         // revert the storage to restore the original link.
         lpContainerLine->m_lpStg->lpVtbl->Revert(
               lpContainerLine->m_lpStg);
      }
   }

   if (hMetaPict)
      OleUIMetafilePictIconFree(hMetaPict); // clean up metafile
   return sc;
}

/* ContainerLine_GetOleObject
** --------------------------
**    return pointer to desired interface of embedded/linked object.
**
**    NOTE: this function causes an AddRef to the object. when the caller is
**          finished with the object, it must call Release.
**          this function does not AddRef the ContainerLine object.
*/
LPUNKNOWN ContainerLine_GetOleObject(
      LPCONTAINERLINE         lpContainerLine,
      REFIID                  riid
)
{
   /* if object is not already loaded, then load it now. objects are
   **    loaded lazily in this manner.
   */
   if (! lpContainerLine->m_lpOleObj)
      ContainerLine_LoadOleObject(lpContainerLine);

   if (lpContainerLine->m_lpOleObj)
      return OleStdQueryInterface(
            (LPUNKNOWN)lpContainerLine->m_lpOleObj,
            riid
      );
   else
      return NULL;
}

/* ContainerLine_RunOleObject
** --------------------------
**    Load and run the object. Upon running and if size of object has changed,
**    use SetExtent to change to new size.
**
*/
HRESULT ContainerLine_RunOleObject(LPCONTAINERLINE lpContainerLine)
{
   LPLINE lpLine = (LPLINE)lpContainerLine;
   SIZEL   sizelNew;
   HRESULT hrErr;
   HCURSOR  hPrevCursor;

   if (! lpContainerLine)
      return NOERROR;

   if (lpContainerLine->m_fGuardObj) {
      // object in process of creation--Fail to Run the object
      return E_FAIL;
   }

   if (lpContainerLine->m_lpOleObj &&
      OleIsRunning(lpContainerLine->m_lpOleObj))
      return NOERROR;     // object already running

   // this may take a while, put up hourglass cursor
   hPrevCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
   OLEDBG_BEGIN3("ContainerLine_RunOleObject\r\n")

   if (! lpContainerLine->m_lpOleObj) {
      if (! ContainerLine_LoadOleObject(lpContainerLine))
      {
         SetCursor(hPrevCursor);
         return E_OUTOFMEMORY; // Error: couldn't load obj
      }
   }

   OLEDBG_BEGIN2("OleRun called\r\n")
   hrErr = OleRun((LPUNKNOWN)lpContainerLine->m_lpOleObj);
   OLEDBG_END2

   if (hrErr != NOERROR) {
      SetCursor(hPrevCursor);     // restore original cursor

      OleDbgOutHResult("OleRun returned", hrErr);
      OLEDBG_END3
      return hrErr;
   }

   if (lpContainerLine->m_fDoSetExtent) {
      /* NOTE: the OLE object was resized when it was not running
      **    and the object did not have the OLEMISC_RECOMPOSEONRESIZE
      **    bit set. if it had, the object would have been run
      **    immediately when it was resized. this flag indicates that
      **    the object does something other than simple scaling when
      **    it is resized. because the object is being run now, we
      **    will call IOleObject::SetExtent.
      */
      lpContainerLine->m_fDoSetExtent = FALSE;

      // the size stored in our Line includes the border around the object.
      // we must subtract the border to get the size of the object itself.
      sizelNew.cx = lpLine->m_nWidthInHimetric;
      sizelNew.cy = lpLine->m_nHeightInHimetric;

      if ((sizelNew.cx != lpContainerLine->m_sizeInHimetric.cx) ||
         (sizelNew.cy != lpContainerLine->m_sizeInHimetric.cy)) {

         OLEDBG_BEGIN2("IOleObject::SetExtent called\r\n")
         lpContainerLine->m_lpOleObj->lpVtbl->SetExtent(
               lpContainerLine->m_lpOleObj,
               lpContainerLine->m_dwDrawAspect,
               (LPSIZEL)&sizelNew
         );
         OLEDBG_END2
      }
   }

   SetCursor(hPrevCursor);     // restore original cursor

   OLEDBG_END3
   return NOERROR;

}

/* ContainerLine_IsOleLink
** -----------------------
**
**    return TRUE if the ContainerLine has an OleLink.
**           FALSE if the ContainerLine has an embedding
*/
BOOL ContainerLine_IsOleLink(LPCONTAINERLINE lpContainerLine)
{
   if (!lpContainerLine)
      return FALSE;

   return (lpContainerLine->m_dwLinkType != 0);
}

/*  ContainerLine_Draw
**  ------------------
**
**      Draw a ContainerLine object on a DC.
**
**  Parameters:
**      hDC     - DC to which the line will be drawn
**      lpRect  - the object rect in logical coordinates
**      lpRectWBounds - bounding rect of the metafile underneath hDC

**                      (NULL if hDC is not a metafile DC)
**      fHighlight    - TRUE if line has selection highlight
*/
void ContainerLine_Draw(
      LPCONTAINERLINE         lpContainerLine,
      HDC                     hDC,
      LPRECT                  lpRect,
      LPRECT                  lpRectWBounds,
      BOOL                    fHighlight
)
{
   LPLINE  lpLine = (LPLINE) lpContainerLine;
   HRESULT hrErr = NOERROR;
   RECTL   rclHim;
   RECTL   rclHimWBounds;
   RECT    rcHim;

   if (lpContainerLine->m_fGuardObj) {
      // object in process of creation--do NOT try to draw
      return;
   }

   /* if object is not already loaded, then load it now. objects are
   **    loaded lazily in this manner.
   */
   if (! lpContainerLine->m_lpViewObj2) {
      if (! ContainerLine_LoadOleObject(lpContainerLine))
      {
         return;     // Error: could not load object
      }
   }

   if (lpRectWBounds) {
      rclHimWBounds.left      = (long) lpRectWBounds->left;
      rclHimWBounds.bottom    = (long) lpRectWBounds->bottom;
      rclHimWBounds.top       = (long) lpRectWBounds->top;
      rclHimWBounds.right     = (long) lpRectWBounds->right;
   }

   /* construct bounds rectangle for the object.
   **  offset origin for object to correct tab indentation
   */
   rclHim.left     = (long) lpRect->left;
   rclHim.bottom   = (long) lpRect->bottom;
   rclHim.top      = (long) lpRect->top;
   rclHim.right    = (long) lpRect->right;

   rclHim.left += (long) ((LPLINE)lpContainerLine)->m_nTabWidthInHimetric;
   rclHim.right += (long) ((LPLINE)lpContainerLine)->m_nTabWidthInHimetric;

#if defined( INPLACE_CNTR )
   /* NOTE: if the OLE object currently has a visible in-place
   **    window, then we do NOT want to draw on top of its window.
   **    this could interfere with the object's display.
   */
   if ( !lpContainerLine->m_fIpVisible )
#endif
   {
   hrErr = lpContainerLine->m_lpViewObj2->lpVtbl->Draw(
         lpContainerLine->m_lpViewObj2,
         lpContainerLine->m_dwDrawAspect,
         -1,
         NULL,
         NULL,
         NULL,
         hDC,
         (LPRECTL)&rclHim,
         (lpRectWBounds ? (LPRECTL)&rclHimWBounds : NULL),
         NULL,
         0
   );
   if (hrErr != NOERROR)
      OleDbgOutHResult("IViewObject::Draw returned", hrErr);

   if (lpContainerLine->m_fObjWinOpen)
      {
      rcHim.left      = (int) rclHim.left;
      rcHim.top       = (int) rclHim.top;
      rcHim.right     = (int) rclHim.right;
      rcHim.bottom    = (int) rclHim.bottom;

      /* NOTE: if the object servers window is Open (ie. not active
      **    in-place) then we must shade the object in our document to
      **    indicate to the user that the object is open elsewhere.
      */
      OleUIDrawShading((LPRECT)&rcHim, hDC, OLEUI_SHADE_FULLRECT, 0);
      }
   }

   /* if the object associated with the ContainerLine is an automatic
   **    link then try to connect it with its LinkSource if the
   **    LinkSource is already running. we do not want to force the
   **    LinkSource to run.
   **
   **    NOTE: a sophistocated container will want to continually
   **    attempt to connect its automatic links. OLE does NOT
   **    automatically connect links when link sources become
   **    available. some containers will want to attempt to connect
   **    its links as part of idle time processing. another strategy
   **    is to attempt to connect an automatic link every time it is
   **    drawn on the screen. (this is the strategy used by this
   **    CntrOutl sample application.)
   */
   if (lpContainerLine->m_dwLinkType == OLEUPDATE_ALWAYS)
      ContainerLine_BindLinkIfLinkSrcIsRunning(lpContainerLine);

   return;
}

void ContainerLine_DrawSelHilight(
      LPCONTAINERLINE lpContainerLine,
      HDC             hDC,            // MM_TEXT mode
      LPRECT          lprcPix,        // listbox rect
      UINT            itemAction,
      UINT            itemState
)
{
   LPLINE  lpLine = (LPLINE)lpContainerLine;
   RECT    rcObj;
   DWORD   dwFlags = OLEUI_HANDLES_INSIDE | OLEUI_HANDLES_USEINVERSE;
   int     nHandleSize;
   LPCONTAINERDOC lpContainerDoc;

   if (!lpContainerLine || !hDC || !lprcPix)
      return;

   lpContainerDoc = lpContainerLine->m_lpDoc;

   // Get size of OLE object
   ContainerLine_GetOleObjectRectInPixels(lpContainerLine, (LPRECT)&rcObj);

   nHandleSize = GetProfileInt("windows", "oleinplaceborderwidth",
         DEFAULT_HATCHBORDER_WIDTH) + 1;

   OleUIDrawHandles((LPRECT)&rcObj, hDC, dwFlags, nHandleSize, TRUE);
}

/* InvertDiffRect
** --------------
**
**    Paint the surrounding of the Obj rect black but within lprcPix
**      (similar to the lprcPix minus lprcObj)
*/
static void InvertDiffRect(LPRECT lprcPix, LPRECT lprcObj, HDC hDC)
{
   RECT rcBlack;

   // draw black in all space outside of object's rectangle
   rcBlack.top = lprcPix->top;
   rcBlack.bottom = lprcPix->bottom;

   rcBlack.left = lprcPix->left + 1;
   rcBlack.right = lprcObj->left - 1;
   InvertRect(hDC, (LPRECT)&rcBlack);

   rcBlack.left = lprcObj->right + 1;
   rcBlack.right = lprcPix->right - 1;
   InvertRect(hDC, (LPRECT)&rcBlack);

   rcBlack.top = lprcPix->top;
   rcBlack.bottom = lprcPix->top + 1;
   rcBlack.left = lprcObj->left - 1;
   rcBlack.right = lprcObj->right + 1;
   InvertRect(hDC, (LPRECT)&rcBlack);

   rcBlack.top = lprcPix->bottom;
   rcBlack.bottom = lprcPix->bottom - 1;
   rcBlack.left = lprcObj->left - 1;
   rcBlack.right = lprcObj->right + 1;
   InvertRect(hDC, (LPRECT)&rcBlack);
}

/* Edit the ContainerLine line object.
**      returns TRUE if line was changed
**              FALSE if the line was NOT changed
*/
BOOL ContainerLine_Edit(LPCONTAINERLINE lpContainerLine, HWND hWndDoc,HDC hDC)
{
   ContainerLine_DoVerb(lpContainerLine, OLEIVERB_PRIMARY, NULL, TRUE, TRUE);

   /* assume object was NOT changed, if it was obj will send Changed
   **    or Saved notification.
   */
   return FALSE;
}

/* ContainerLine_SetHeightInHimetric
** ---------------------------------
**
** Set the height of a ContainerLine object. The widht will be changed
** to keep the aspect ratio
*/
void ContainerLine_SetHeightInHimetric(LPCONTAINERLINE lpContainerLine, int nHeight)
{
   LPLINE  lpLine = (LPLINE)lpContainerLine;
   SIZEL   sizelOleObject;
   HRESULT hrErr;

   if (!lpContainerLine)
      return;

   if (lpContainerLine->m_fGuardObj) {
      // object in process of creation--Fail to set the Height
      return;
   }

   if (nHeight != -1) {
      BOOL    fMustClose = FALSE;
      BOOL    fMustRun   = FALSE;

      /* if object is not already loaded, then load it now. objects are
      **    loaded lazily in this manner.
      */
      if (! lpContainerLine->m_lpOleObj)
         ContainerLine_LoadOleObject(lpContainerLine);

      // the height argument specifies the desired height for the Line.
      sizelOleObject.cy = nHeight;

      // we will calculate the corresponding width for the object by
      // maintaining the current aspect ratio of the object.
      sizelOleObject.cx = (int)(sizelOleObject.cy *
            lpContainerLine->m_sizeInHimetric.cx /
            lpContainerLine->m_sizeInHimetric.cy);

      /* NOTE: if the OLE object is already running then we can
      **    immediately call SetExtent. But, if the object is NOT
      **    currently running then we will check if the object
      **    indicates that it is normally recomposes itself on
      **    resizing. ie. that the object does not simply scale its
      **    display when it it resized. if so then we will force the
      **    object to run so that we can call IOleObject::SetExtent.
      **    SetExtent does not have any effect if the object is only
      **    loaded. if the object does NOT indicate that it
      **    recomposes on resize (OLEMISC_RECOMPOSEONRESIZE) then we
      **    will wait till the next time that the object is run to
      **    call SetExtent. we will store a flag in the ContainerLine
      **    to indicate that a SetExtent is necessary. It is
      **    necessary to persist this flag.
      */
      if (! OleIsRunning(lpContainerLine->m_lpOleObj)) {
         DWORD dwStatus;

         OLEDBG_BEGIN2("IOleObject::GetMiscStatus called\r\n")
         hrErr = lpContainerLine->m_lpOleObj->lpVtbl->GetMiscStatus(
               lpContainerLine->m_lpOleObj,
               lpContainerLine->m_dwDrawAspect,
               (LPDWORD)&dwStatus
         );
         OLEDBG_END2
         if (hrErr == NOERROR && (dwStatus & OLEMISC_RECOMPOSEONRESIZE)) {
            // force the object to run
            ContainerLine_RunOleObject(lpContainerLine);
            fMustClose = TRUE;
         } else {
            /*  the OLE object is NOT running and does NOT
            **    recompose on resize. simply scale the object now
            **    and do the SetExtent the next time the object is
            **    run. we set the Line to the new size even though
            **    the object's extents have not been changed.
            **    this has the result of scaling the object's
            **    display to the new size.
            */
            lpContainerLine->m_fDoSetExtent = TRUE;
            ContainerLine_SetLineHeightFromObjectExtent(
                  lpContainerLine, (LPSIZEL)&sizelOleObject);
            return;
         }
      }

      OLEDBG_BEGIN2("IOleObject::SetExtent called\r\n")
      hrErr = lpContainerLine->m_lpOleObj->lpVtbl->SetExtent(
            lpContainerLine->m_lpOleObj,
            lpContainerLine->m_dwDrawAspect,
            (LPSIZEL)&sizelOleObject);
      OLEDBG_END2

      if (hrErr != NOERROR) {
         /* OLE Object refuses to take on the new extents. Set the
         **    Line to the new size even though the object refused
         **    the new extents. this has the result of scaling the
         **    object's display to the new size.
         **
         **    if the object HAD accepted the new extents, then it
         **    will send out an OnViewChange/OnDataChange
         **    notification. this results in our container receiving
         **    an OnViewChange notification; the line height will be
         **    reset when this notification is received.
         */
         ContainerLine_SetLineHeightFromObjectExtent(
               lpContainerLine, (LPSIZEL)&sizelOleObject);
      }

      if (fMustClose)
         ContainerLine_CloseOleObject(
               lpContainerLine, OLECLOSE_SAVEIFDIRTY);
   }
   else {
      /* Set the line to default height given the natural (unscaled)
      **    extents of the OLE object.
      */
      ContainerLine_SetLineHeightFromObjectExtent(
            lpContainerLine,(LPSIZEL)&lpContainerLine->m_sizeInHimetric);
   }
}

/*  ContainerLine_SetLineHeightFromObjectExtent
 *
 *  Purpose:
 *      Calculate the corresponding line height from the OleObject size
 *      Scale the line height to fit the limit if necessary
 *
 *  Parameters:
 *      lpsizelOleObject        pointer to size of OLE Object
 *
 *  Returns:
 *      nil
 */
void ContainerLine_SetLineHeightFromObjectExtent(
      LPCONTAINERLINE         lpContainerLine,
      LPSIZEL                 lpsizelOleObject
)
{
   LPLINE lpLine = (LPLINE)lpContainerLine;

   UINT uMaxObjectHeight = XformHeightInPixelsToHimetric(NULL,
         LISTBOX_HEIGHT_LIMIT);

   if (!lpContainerLine || !lpsizelOleObject)
      return;

   if (lpContainerLine->m_fGuardObj) {
      // object in process of creation--Fail to set the Height
      return;
   }

   lpLine->m_nWidthInHimetric = (int)lpsizelOleObject->cx;
   lpLine->m_nHeightInHimetric = (int)lpsizelOleObject->cy;

   // Rescale the object if height is greater than the limit
   if (lpLine->m_nHeightInHimetric > (UINT)uMaxObjectHeight) {

      lpLine->m_nWidthInHimetric = (UINT)
            ((long)lpLine->m_nWidthInHimetric *
            (long)uMaxObjectHeight /
            (long)lpLine->m_nHeightInHimetric);

      lpLine->m_nHeightInHimetric = uMaxObjectHeight;
   }

}

/* ContainerLine_SaveToStg
** -----------------------
**    Save a given ContainerLine and associated OLE object to an IStorage*.
*/
BOOL ContainerLine_SaveToStm(
      LPCONTAINERLINE         lpContainerLine,
      LPSTREAM                lpLLStm
)
{
   CONTAINERLINERECORD_ONDISK objLineRecord;
   ULONG nWritten;
   HRESULT hrErr;
   //  Compilers should handle alignment correctly

   OLESTRCPY(objLineRecord.m_szStgName, lpContainerLine->m_szStgName);
   objLineRecord.m_fMonikerAssigned = (USHORT)lpContainerLine->m_fMonikerAssigned;
   objLineRecord.m_dwDrawAspect = lpContainerLine->m_dwDrawAspect;
   objLineRecord.m_sizeInHimetric = lpContainerLine->m_sizeInHimetric;
   objLineRecord.m_dwLinkType = lpContainerLine->m_dwLinkType;
   objLineRecord.m_fDoSetExtent = (USHORT) lpContainerLine->m_fDoSetExtent;

   /* write line record */
   hrErr = lpLLStm->lpVtbl->Write(
         lpLLStm,
         (LPVOID)&objLineRecord,
         sizeof(objLineRecord),
         &nWritten
   );

   if (hrErr != NOERROR) {
      OleDbgAssertSz(hrErr == NOERROR,"Could not write to LineList stream");
    {
      return FALSE;
    }
   }

   return TRUE;
}

/* ContainerLine_SaveOleObjectToStg
** --------------------------------
**    Save the OLE object associated with the ContainerLine to an IStorage*.
*/
BOOL ContainerLine_SaveOleObjectToStg(
      LPCONTAINERLINE         lpContainerLine,
      LPSTORAGE               lpSrcStg,
      LPSTORAGE               lpDestStg,
      BOOL                    fRemember
)
{
   HRESULT         hrErr;
   SCODE           sc = S_OK;
   BOOL            fStatus;
   BOOL            fSameAsLoad = (lpSrcStg==lpDestStg ? TRUE : FALSE);
   LPSTORAGE       lpObjDestStg;

   if (lpContainerLine->m_fGuardObj) {
      // object in process of creation--Fail to save
      return FALSE;
   }

   if (! lpContainerLine->m_lpOleObj) {

      /*****************************************************************
      ** CASE 1: object is NOT loaded.
      *****************************************************************/

      if (fSameAsLoad) {
         /*************************************************************
         ** CASE 1A: we are saving to the current storage. because
         **    the object is not loaded, it is up-to-date
         **    (ie. nothing to do).
         *************************************************************/

         ;

      } else {
         /*************************************************************
         ** CASE 1B: we are saving to a new storage. because
         **    the object is not loaded, we can simply copy the
         **    object's current storage to the new storage.
         *************************************************************/

         /* if current object storage is not already open, then open it */
         if (! lpContainerLine->m_lpStg) {
            lpContainerLine->m_lpStg = OleStdOpenChildStorage(
                  lpSrcStg,
                  lpContainerLine->m_szStgName,
                  STGM_READWRITE
               );
            if (lpContainerLine->m_lpStg == NULL) {
#if defined( _DEBUG )
               OleDbgAssertSz(
                     lpContainerLine->m_lpStg != NULL,
                     "Error opening child stg"
               );
#endif
               return FALSE;
            }
         }

         /* Create a child storage inside the destination storage. */
         lpObjDestStg = OleStdCreateChildStorage(
               lpDestStg,
               lpContainerLine->m_szStgName
         );

         if (lpObjDestStg == NULL) {
#if defined( _DEBUG )
            OleDbgAssertSz(
                  lpObjDestStg != NULL,
                  "Could not create obj storage!"
            );
#endif
            return FALSE;
         }

         hrErr = lpContainerLine->m_lpStg->lpVtbl->CopyTo(
               lpContainerLine->m_lpStg,
               0,
               NULL,
               NULL,
               lpObjDestStg
         );
         // REVIEW: should we handle error here?
         fStatus = OleStdCommitStorage(lpObjDestStg);

         /* if we are supposed to remember this storage as the new
         **    storage for the object, then release the old one and
         **    save the new one. else, throw away the new one.
         */
         if (fRemember) {
            OleStdVerifyRelease(
                  (LPUNKNOWN)lpContainerLine->m_lpStg,
                  OLESTR("Original object stg not released")
            );
            lpContainerLine->m_lpStg = lpObjDestStg;
         } else {
            OleStdVerifyRelease(
                  (LPUNKNOWN)lpObjDestStg,
                  OLESTR("Copied object stg not released")
            );
         }
      }

   } else {

      /*****************************************************************
      ** CASE 2: object IS loaded.
      *****************************************************************/

      if (fSameAsLoad) {
         /*************************************************************
         ** CASE 2A: we are saving to the current storage. if the object
         **    is not dirty, then the current storage is up-to-date
         **    (ie. nothing to do).
         *************************************************************/

         LPPERSISTSTORAGE lpPersistStg = lpContainerLine->m_lpPersistStg;
         OleDbgAssert(lpPersistStg);

         hrErr = lpPersistStg->lpVtbl->IsDirty(lpPersistStg);

         /* NOTE: we will only accept an explicit "no i
         **    am NOT dirty statement" (ie. S_FALSE) as an
         **    indication that the object is clean. eg. if
         **    the object returns E_NOTIMPL we must
         **    interpret it as the object IS dirty.
         */
         if (hrErr != S_FALSE) {

            /* OLE object IS dirty */

            OLEDBG_BEGIN2("OleSave called\r\n")
            hrErr = OleSave(
                  lpPersistStg, lpContainerLine->m_lpStg, fSameAsLoad);
            OLEDBG_END2

            if (hrErr != NOERROR) {
               OleDbgOutHResult("WARNING: OleSave returned", hrErr);
               sc = hrErr;
            }

            // NOTE: if OleSave fails, SaveCompleted must be called.
            OLEDBG_BEGIN2("IPersistStorage::SaveCompleted called\r\n")
            hrErr=lpPersistStg->lpVtbl->SaveCompleted(lpPersistStg,NULL);
            OLEDBG_END2

            if (hrErr != NOERROR) {
               OleDbgOutHResult("WARNING: SaveCompleted returned",hrErr);
               if (sc == S_OK)
                  sc = hrErr;
            }

            if (sc != S_OK)
            {
               return FALSE;
            }
         }

      } else {
         /*************************************************************
         ** CASE 2B: we are saving to a new storage. we must
         **    tell the object to save into the new storage.
         *************************************************************/

         LPPERSISTSTORAGE lpPersistStg = lpContainerLine->m_lpPersistStg;

         if (! lpPersistStg) return FALSE;

         /* Create a child storage inside the destination storage. */
         lpObjDestStg = OleStdCreateChildStorage(
               lpDestStg,
               lpContainerLine->m_szStgName
         );

         if (lpObjDestStg == NULL) {
#if defined( _DEBUG )
            OleDbgAssertSz(
                  lpObjDestStg != NULL,
                  "Could not create object storage!"
            );
#endif
            return FALSE;
         }

         OLEDBG_BEGIN2("OleSave called\r\n")
         hrErr = OleSave(lpPersistStg, lpObjDestStg, fSameAsLoad);
         OLEDBG_END2

         // NOTE: even if OleSave fails, must still call SaveCompleted
         if (hrErr != NOERROR) {
            OleDbgOutHResult("WARNING: OleSave returned", hrErr);
            sc = hrErr;
         }

         /* NOTE: a root level container should immediately
         **    call IPersistStorage::SaveCompleted after calling
         **    OleSave. a nested level container should not call
         **    SaveCompleted now, but must wait until SaveCompleted
         **    is call on it by its container. since our container
         **    is not a container/server, then we always call
         **    SaveComplete here.
         **
         **    if this is a SaveAs operation, then we need to pass
         **    the lpStg back in SaveCompleted to inform the object
         **    of its new storage that it may hold on to. if this is
         **    a Save or a SaveCopyAs operation, then we simply pass
         **    NULL in SaveCompleted; the object can continue to hold
         **    its current storage. if an error occurs during the
         **    OleSave call we must still call SaveCompleted but we
         **    must pass NULL.
         */
         OLEDBG_BEGIN2("IPersistStorage::SaveCompleted called\r\n")
         hrErr = lpPersistStg->lpVtbl->SaveCompleted(
               lpPersistStg,
               ((FAILED(sc) || !fRemember) ? NULL : lpObjDestStg)
         );
         OLEDBG_END2

         if (hrErr != NOERROR) {
            OleDbgOutHResult("WARNING: SaveCompleted returned",hrErr);
            if (sc == S_OK)
               sc = hrErr;
         }

         if (sc != S_OK) {
            OleStdVerifyRelease(
                  (LPUNKNOWN)lpObjDestStg,
                  OLESTR("Copied object stg not released")
            );
            return FALSE;
         }

         /* if we are supposed to remember this storage as the new
         **    storage for the object, then release the old one and
         **    save the new one. else, throw away the new one.
         */
         if (fRemember) {
            OleStdVerifyRelease(
                  (LPUNKNOWN)lpContainerLine->m_lpStg,
                  OLESTR("Original object stg not released")
            );
            lpContainerLine->m_lpStg = lpObjDestStg;
         } else {
            OleStdVerifyRelease(
                  (LPUNKNOWN)lpObjDestStg,
                  OLESTR("Copied object stg not released")
            );
         }
      }
   }

   /* NOTE: after saving an OLE object it is possible that it sent
   **    an OnViewChange notification because it had been modified. in
   **    this situation it is possible that the extents of the object
   **    have changed. if so we want to relayout the space for the
   **    object immediately so that the extent information saved with
   **    the ContainerLine match the data saved with the OLE object
   **    itself.
   */
   if (lpContainerLine->m_fDoGetExtent) {
      BOOL fSizeChanged = ContainerLine_UpdateExtent(lpContainerLine, NULL);
#if defined( INPLACE_CNTR )
      /* if the extents of this ContainerLine have changed, then we
      **    need to reset the fDoGetExtent flag to TRUE so that later
      **    when ContainerDoc_UpdateExtentOfAllOleObjects is called
      **    (when the WM_U_UPDATEOBJECTEXTENT message is processed),
      **    it is recognized that the extents of this line have
      **    changed. if any line changes size, then any in-place
      **    active object below this line must be told to update the
      **    position of their windows (via SetObjectRects -- see
      **    ContainerDoc_UpdateInPlaceObjectRects function).
      */
      lpContainerLine->m_fDoGetExtent = fSizeChanged;
#endif
   }

   return TRUE;
}

/* ContainerLine_LoadFromStg
** -------------------------
**    Create a ContainerLine object and initialize it with data that
**    was previously writen to an IStorage*. this function does not
**    immediately OleLoad the associated OLE object, only the data of
**    the ContainerLine object itself is loaded from the IStorage*.
*/
LPLINE ContainerLine_LoadFromStg(
      LPSTORAGE               lpSrcStg,
      LPSTREAM                lpLLStm,
      LPOUTLINEDOC            lpDestDoc
)
{
   HDC         hDC;
   LPLINELIST  lpDestLL = &lpDestDoc->m_LineList;
   ULONG nRead;
   HRESULT hrErr;
   LPCONTAINERLINE lpContainerLine;
   CONTAINERLINERECORD_ONDISK objLineRecord;

   lpContainerLine=(LPCONTAINERLINE) New((DWORD)sizeof(CONTAINERLINE));
   if (lpContainerLine == NULL) {
      OleDbgAssertSz(lpContainerLine!=NULL,"Error allocating ContainerLine");
      return NULL;
   }

   hDC = LineList_GetDC(lpDestLL);
   ContainerLine_Init(lpContainerLine, 0, hDC);
   LineList_ReleaseDC(lpDestLL, hDC);

   /* NOTE: In order to have a stable ContainerLine object we must
   **    AddRef the object's refcnt. this will be later released when
   **    the ContainerLine is deleted.
   */
   ContainerLine_AddRef(lpContainerLine);

   lpContainerLine->m_lpDoc = (LPCONTAINERDOC) lpDestDoc;

   /* read line record */
   hrErr = lpLLStm->lpVtbl->Read(
         lpLLStm,
         (LPVOID)&objLineRecord,
         sizeof(objLineRecord),
         &nRead
   );

   if (hrErr != NOERROR) {
      OleDbgAssertSz(hrErr==NOERROR, "Could not read from LineList stream");
      goto error;
   }

   //  Compilers should handle alignment correctly
   OLESTRCPY(lpContainerLine->m_szStgName, objLineRecord.m_szStgName);
   lpContainerLine->m_fMonikerAssigned = (BOOL) objLineRecord.m_fMonikerAssigned;
   lpContainerLine->m_dwDrawAspect = objLineRecord.m_dwDrawAspect;
   lpContainerLine->m_sizeInHimetric = objLineRecord.m_sizeInHimetric;
   lpContainerLine->m_dwLinkType = objLineRecord.m_dwLinkType;
   lpContainerLine->m_fDoSetExtent = (BOOL) objLineRecord.m_fDoSetExtent;

   return (LPLINE)lpContainerLine;

error:
   // destroy partially created ContainerLine
   if (lpContainerLine)
      ContainerLine_Delete(lpContainerLine);
   return NULL;
}

/* ContainerLine_GetTextLen
 * ------------------------
 *
 * Return length of the string representation of the ContainerLine
 *  (not considering the tab level). we will use the following as the
 *  string representation of a ContainerLine:
 *      "<" + user type name of OLE object + ">"
 *  eg:
 *      <Microsoft Excel Worksheet>
 */
int ContainerLine_GetTextLen(LPCONTAINERLINE lpContainerLine)
{
   LPOLESTR   lpszUserType = NULL;
   HRESULT hrErr;
   int     nLen;
   BOOL    fIsLink = ContainerLine_IsOleLink(lpContainerLine);

   /* if object is not already loaded, then load it now. objects are
   **    loaded lazily in this manner.
   */
   if (! lpContainerLine->m_lpOleObj)
      ContainerLine_LoadOleObject(lpContainerLine);

   OLEDBG_BEGIN2("IOleObject::GetUserType called\r\n")
   hrErr = lpContainerLine->m_lpOleObj->lpVtbl->GetUserType(
         lpContainerLine->m_lpOleObj,
         USERCLASSTYPE_FULL,
         &lpszUserType
   );
   OLEDBG_END2

   if (hrErr != NOERROR)   {
      // user type is NOT available
      nLen = sizeof(UNKNOWN_OLEOBJ_TYPE) + 2; // allow space for '<' + '>'
      nLen += lstrlen((LPSTR)(fIsLink ? szOLELINK : szOLEOBJECT)) + 1;
   } else {
      nLen = OLESTRLEN(lpszUserType) + 2;   // allow space for '<' + '>'
      nLen += OLESTRLEN(/*(LPOLESTR)*/(fIsLink ? szOLELINK : szOLEOBJECT)) + 1;

      /* NOTE: we must free the string that was allocated by the
      **    IOleObject::GetUserType method.
      */
      OleStdFreeString(lpszUserType, NULL);
   }

   return nLen;
}

/* ContainerLine_GetTextData
 * -------------------------
 *
 * Return the string representation of the ContainerLine
 *  (not considering the tab level). we will use the following as the
 *  string representation of a ContainerLine:
 *      "<" + user type name of OLE object + ">"
 *  eg:
 *      <Microsoft Excel Worksheet>
 */
void ContainerLine_GetTextData(LPCONTAINERLINE lpContainerLine, LPOLESTR lpszBuf)
{
   LPOLESTR   lpszUserType = NULL;
   BOOL    fIsLink = ContainerLine_IsOleLink(lpContainerLine);
   HRESULT hrErr;
   char    szAnsiBuf[256], szAnsiUserType[256], szAnsiString[256];
   LPOLESTR lpszUniStr;

   /* if object is not already loaded, then load it now. objects are
   **    loaded lazily in this manner.
   */
   if (! lpContainerLine->m_lpOleObj)
      ContainerLine_LoadOleObject(lpContainerLine);

   hrErr = lpContainerLine->m_lpOleObj->lpVtbl->GetUserType(
         lpContainerLine->m_lpOleObj,
         USERCLASSTYPE_FULL,
         &lpszUserType
   );

   W2A (lpszUserType, szAnsiUserType, 256);
   lpszUniStr = (fIsLink ? szOLELINK : szOLEOBJECT);
   W2A (lpszUniStr, szAnsiString, 256);
   if (hrErr != NOERROR)   {
      // user type is NOT available
      wsprintf(
            szAnsiBuf,
            "<%s %s>",
            UNKNOWN_OLEOBJ_TYPE,
            (LPSTR)szAnsiString
      );
   } else {
      wsprintf(
            szAnsiBuf,
            "<%s %s>",

szAnsiUserType,
            (LPSTR)szAnsiString
      );

      /* NOTE: we must free the string that was allocated by the
      **    IOleObject::GetUserType method.
      */
      OleStdFreeString(lpszUserType, NULL);
   }
   A2W (szAnsiBuf, lpszBuf, 256); // lpszBuf should be big enough
}

/* ContainerLine_GetOutlineData
 * ----------------------------
 *
 * Return the CF_OUTLINE format data for the ContainerLine.
 */
BOOL ContainerLine_GetOutlineData(
      LPCONTAINERLINE         lpContainerLine,
      LPTEXTLINE              lpBuf
)
{
   LPLINE      lpLine = (LPLINE)lpContainerLine;
   LPLINELIST  lpLL = &((LPOUTLINEDOC)lpContainerLine->m_lpDoc)->m_LineList;
   HDC         hDC;
   OLECHAR     szTmpBuf[MAXSTRLEN+1];
   char        szAnsiStr[256];
   LPTEXTLINE  lpTmpTextLine;

   // Create a TextLine with the Text representation of the ContainerLine.
   ContainerLine_GetTextData(lpContainerLine, /*(LPOLESTR)*/szTmpBuf);

   hDC = LineList_GetDC(lpLL);
   W2A (szTmpBuf, szAnsiStr, 256);
   lpTmpTextLine = TextLine_Create(hDC, lpLine->m_nTabLevel, szAnsiStr);
   LineList_ReleaseDC(lpLL, hDC);

   TextLine_Copy(lpTmpTextLine, lpBuf);

   // Delete the temporary TextLine
   TextLine_Delete(lpTmpTextLine);
   return TRUE;
}

/* ContainerLine_GetPosRect
** -----------------------
**    Get the PosRect in client coordinates for the OLE object's window.
**
** NOTE: the PosRect must take into account the scroll postion of
**    the document window.
*/
void ContainerLine_GetPosRect(
      LPCONTAINERLINE     lpContainerLine,
      LPRECT              lprcPosRect
)
{
   ContainerLine_GetOleObjectRectInPixels(lpContainerLine,lprcPosRect);

   // shift rect for left margin
   lprcPosRect->left += lpContainerLine->m_nHorizScrollShift;
   lprcPosRect->right += lpContainerLine->m_nHorizScrollShift;
}

/* ContainerLine_GetOleObjectRectInPixels
** --------------------------------------
**    Get the extent of the OLE Object contained in the given Line in
**    client coordinates after scaling.
*/
void ContainerLine_GetOleObjectRectInPixels(LPCONTAINERLINE lpContainerLine, LPRECT lprc)
{
   LPOUTLINEDOC lpOutlineDoc;
   LPSCALEFACTOR lpscale;
   LPLINELIST lpLL;
   LPLINE lpLine;
   int nIndex;
   HDC hdcLL;

   if (!lpContainerLine || !lprc)
      return;

   lpOutlineDoc = (LPOUTLINEDOC)lpContainerLine->m_lpDoc;
   lpscale = OutlineDoc_GetScaleFactor(lpOutlineDoc);
   lpLL = OutlineDoc_GetLineList(lpOutlineDoc);
   lpLine = (LPLINE)lpContainerLine;
   nIndex = LineList_GetLineIndex(lpLL, lpLine);

   LineList_GetLineRect(lpLL, nIndex, lprc);

   hdcLL = GetDC(lpLL->m_hWndListBox);

   /* lprc is set to be size of Line Object (including the boundary) */
   lprc->left += (int)(
         (long)XformWidthInHimetricToPixels(hdcLL,
               lpLine->m_nTabWidthInHimetric +
               LOWORD(OutlineDoc_GetMargin(lpOutlineDoc))) *
         lpscale->dwSxN / lpscale->dwSxD);
   lprc->right = (int)(
         lprc->left + (long)
         XformWidthInHimetricToPixels(hdcLL, lpLine->m_nWidthInHimetric) *
         lpscale->dwSxN / lpscale->dwSxD);

   ReleaseDC(lpLL->m_hWndListBox, hdcLL);
}

/* ContainerLine_GetOleObjectSizeInHimetric
** ----------------------------------------
**    Get the size of the OLE Object contained in the given Line
*/
void ContainerLine_GetOleObjectSizeInHimetric(LPCONTAINERLINE lpContainerLine, LPSIZEL lpsizel)
{
   if (!lpContainerLine || !lpsizel)
      return;

   *lpsizel = lpContainerLine->m_sizeInHimetric;
}

/* ContainerLine_BindLinkIfLinkSrcIsRunning
** ----------------------------------------
**    Try to connect the OLE link object associated with the
**    ContainerLine with its LinkSource if the LinkSource is already
**    running and the link is an automatic link. we do not want to
**    force the LinkSource to run.
**
**    NOTE: a sophistocated container will want to continually
**    attempt to connect its automatic links. OLE does NOT
**    automatically connect links when link source become available. some
**    containers will want to attempt to connect its links as part of
**    idle time processing. another strategy is to attempt to connect
**    an automatic link every time it is drawn on the screen. (this is
**    the strategy used by this CntrOutl sample application.)
*/
void ContainerLine_BindLinkIfLinkSrcIsRunning(LPCONTAINERLINE lpContainerLine)
{
   LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;
   HRESULT hrErr;
   BOOL fPrevEnable1;
   BOOL fPrevEnable2;

   // if the link source is known to be un-bindable, then don't even try
   if (lpContainerLine->m_fLinkUnavailable)
      return;

   /* NOTE: we do not want to ever give the Busy/NotResponding
   **    dialogs when we are attempting to BindIfRunning to the link
   **    source. if the link source is currently busy, this could
   **    cause the Busy dialog to come up. even if the link source is
   **    busy, we do not want put up the busy dialog. thus we will
   **    disable the dialog and later re-enable them
   */
   OleApp_DisableBusyDialogs(lpOleApp, &fPrevEnable1, &fPrevEnable2);

   OLEDBG_BEGIN2("IOleLink::BindIfRunning called\r\n")
   hrErr = lpContainerLine->m_lpOleLink->lpVtbl->BindIfRunning(
         lpContainerLine->m_lpOleLink);
   OLEDBG_END2

   // re-enable the Busy/NotResponding dialogs
   OleApp_EnableBusyDialogs(lpOleApp, fPrevEnable1, fPrevEnable2);
}


CNTROUTL.C

/*************************************************************************
**
**    OLE 2.0 Sample Code
**
**    precomp.c
**
**    This file is used to precompile the OUTLINE.H header file
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#include "outline.h"


CNTROUTL.H

/*************************************************************************
**
**    OLE 2.0 Container Sample Code
**
**    cntroutl.h
**
**    This file contains file contains data structure defintions,
**    function prototypes, constants, etc. used by the OLE 2.0 container
**    app version of the Outline series of sample applications:
**          Outline -- base version of the app (without OLE functionality)
**          SvrOutl -- OLE 2.0 Server sample app
**          CntrOutl -- OLE 2.0 Containter (Container) sample app
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#if !defined( _CNTROUTL_H_ )
#define _CNTROUTL_H_

#ifndef RC_INVOKED
#pragma message ("INCLUDING CNTROUTL.H from " __FILE__)
#endif  /* RC_INVOKED */

#include "oleoutl.h"
#include "cntrrc.h"
#include "ansiapi.h"

// REVIEW: should load from string resource
#define DEFOBJNAMEPREFIX    "Obj"   // Prefix for auto-generated stg names
#define DEFOBJWIDTH         5000    // default size for embedded obj.
#define DEFOBJHEIGHT        5000    // default size for embedded obj.
#define UNKNOWN_OLEOBJ_TYPE "Unknown OLE Object Type"
#define szOLEOBJECT OLESTR("Object")
#define szOLELINK   OLESTR("Link")

#ifdef TEST32
#define CONTAINERDOCFORMAT  OLESTR("CntrOu32")      // CF_CntrOutl format name
#else
#define CONTAINERDOCFORMAT  OLESTR("CntrOutl")      // CF_CntrOutl format name
#endif //TEST32

/* Forward definition of types */
typedef struct tagCONTAINERDOC FAR* LPCONTAINERDOC;
typedef struct tagCONTAINERLINE FAR* LPCONTAINERLINE;

// Flags to specify type of OLECREATE???FROMDATA call required
typedef enum tagOLECREATEFROMDATATYPE {
   OLECREATEFROMDATA_LINK    = 1,
   OLECREATEFROMDATA_OBJECT  = 2,
   OLECREATEFROMDATA_STATIC  = 3
} OLECREATEFROMDATATYPE;

/*************************************************************************
** class CONTAINERLINE : LINE
**    The class CONTAINERLINE is a concrete subclass of the abstract base
**    class LINE. The CONTAINERLINE maintains all information about the
**    place within the CONTAINERDOC that an OLE object is embedded. This
**    object implements the following OLE 2.0 interfaces:
**          IOleClientSite
**          IAdviseSink
**    In the CntrOutl client app either CONTAINERLINE objects or TEXTLINE
**    objects can be created. The CONTAINERLINE class inherits all fields
**    from the LINE class. This inheritance is achieved by including a
**    member variable of type LINE as the first field in the CONTAINERLINE
**    structure. Thus a pointer to a CONTAINERLINE object can be cast to be
**    a pointer to a LINE object.
**    Each CONTAINERLINE object that is created in added to the LINELIST of
**    the associated OUTLINEDOC document.
*************************************************************************/

typedef struct tagCONTAINERLINE {
   LINE            m_Line;         // ContainerLine inherits fields of Line
   ULONG           m_cRef;         // total ref count for line
   OLECHAR         m_szStgName[CWCSTORAGENAME]; // stg name w/i cntr stg
   BOOL            m_fObjWinOpen;  // is obj window open? if so, shade obj.
   BOOL            m_fMonikerAssigned; // has a moniker been assigned to obj
   DWORD           m_dwDrawAspect; // current display aspect for obj
                           //      (either DVASPECT_CONTENT or
                           //      DVASPECT_ICON)
   BOOL            m_fGuardObj;    // Guard against re-entrancy while
                           //  loading or creating an OLE object
   BOOL            m_fDoGetExtent; // indicates extents may have changed
   BOOL            m_fDoSetExtent; // obj was resized when not running
                           //  IOO::SetExtent needed on next run
   SIZEL           m_sizeInHimetric; // extents of obj in himetric units
   LPSTORAGE       m_lpStg;        // open pstg when obj is loaded
   LPCONTAINERDOC  m_lpDoc;        // ptr to associated client doc
   LPOLEOBJECT     m_lpOleObj;     // ptr to IOleObject* when obj is loaded
   LPVIEWOBJECT2   m_lpViewObj2;   // ptr to IViewObject2* when obj is loaded
   LPPERSISTSTORAGE m_lpPersistStg;// ptr to IPersistStorage* when obj loaded
   LPOLELINK       m_lpOleLink;    // ptr to IOleLink* if link is loaded
   DWORD           m_dwLinkType;   // is it a linked object?
                           //   0 -- NOT a link
                           //   OLEUPDATE_ALWAYS (1) -- auto link
                           //   OLEUPDATE_ONCALL (3) -- man. link
   BOOL            m_fLinkUnavailable;     // is the link unavailable?
   LPOLESTR        m_lpszShortType;// short type name of OLE object needed
                           //  to make the Edit.Object.Verb menu
   int             m_nHorizScrollShift;    // horiz scroll shift required
                           // for object's inplace window.
                           // (note: this is ICNTROTL specific)

#if defined( INPLACE_CNTR )
   BOOL            m_fIpActive;    // is object in-place active (undo valid)
   BOOL            m_fUIActive;    // is object UIActive
   BOOL            m_fIpVisible;   // is object's in-place window visible
   BOOL            m_fInsideOutObj;// is obj inside-out (visible when loaded)
   LPOLEINPLACEOBJECT m_lpOleIPObj; // IOleInPlaceObject* of in-place obj
   BOOL            m_fIpChangesUndoable;   // can in-place object do undo
   BOOL            m_fIpServerRunning; // is in-place server running
   HWND            m_hWndIpObject;

   struct COleInPlaceSiteImpl {
      IOleInPlaceSiteVtbl FAR* lpVtbl;
      LPCONTAINERLINE         lpContainerLine;
      int                     cRef;   // interface specific ref count.
   } m_OleInPlaceSite;
#endif  // INPLACE_CNTR

   struct CUnknownImpl {
      IUnknownVtbl FAR*       lpVtbl;
      LPCONTAINERLINE         lpContainerLine;
      int                     cRef;   // interface specific ref count.
   } m_Unknown;

   struct COleClientSiteImpl {
      IOleClientSiteVtbl FAR* lpVtbl;
      LPCONTAINERLINE         lpContainerLine;
      int                 cRef;   // interface specific ref count.
   } m_OleClientSite;

   struct CAdviseSinkImpl {
      IAdviseSinkVtbl FAR*    lpVtbl;
      LPCONTAINERLINE         lpContainerLine;
      int                     cRef;   // interface specific ref count.
   } m_AdviseSink;

} CONTAINERLINE;

/* ContainerLine methods (functions) */
void ContainerLine_Init(LPCONTAINERLINE lpContainerLine, int nTab, HDC hDC);
BOOL ContainerLine_SetupOleObject(
      LPCONTAINERLINE         lpContainerLine,
      BOOL                    fDisplayAsIcon,
      HGLOBAL                 hMetaPict
);
LPCONTAINERLINE ContainerLine_Create(
      DWORD                   dwOleCreateType,
      HDC                     hDC,
      UINT                    nTab,
      LPCONTAINERDOC          lpContainerDoc,
      LPCLSID                 lpclsid,
      LPOLESTR                lpszFileName,
      BOOL                    fDisplayAsIcon,
      HGLOBAL                 hMetaPict,
      LPOLESTR                lpszStgName
);
LPCONTAINERLINE ContainerLine_CreateFromData(
      HDC                     hDC,
      UINT                    nTab,
      LPCONTAINERDOC          lpContainerDoc,
      LPDATAOBJECT            lpSrcDataObj,
      DWORD                   dwCreateType,
      CLIPFORMAT              cfFormat,
      BOOL                    fDisplayAsIcon,
      HGLOBAL                 hMetaPict,
      LPOLESTR                lpszStgName
);
ULONG ContainerLine_AddRef(LPCONTAINERLINE lpContainerLine);
ULONG ContainerLine_Release(LPCONTAINERLINE lpContainerLine);
HRESULT ContainerLine_QueryInterface(
      LPCONTAINERLINE         lpContainerLine,
      REFIID                  riid,
      LPVOID FAR*             lplpUnk
);
BOOL ContainerLine_CloseOleObject(
      LPCONTAINERLINE         lpContainerLine,
      DWORD                   dwSaveOption
);
void ContainerLine_UnloadOleObject(
      LPCONTAINERLINE         lpContainerLine,
      DWORD                   dwSaveOption
);
void ContainerLine_Delete(LPCONTAINERLINE lpContainerLine);
void ContainerLine_Destroy(LPCONTAINERLINE lpContainerLine);
BOOL ContainerLine_CopyToDoc(
      LPCONTAINERLINE         lpSrcLine,
      LPOUTLINEDOC            lpDestDoc,
      int                     nIndex
);
BOOL ContainerLine_LoadOleObject(LPCONTAINERLINE lpContainerLine);
BOOL ContainerLine_UpdateExtent(
      LPCONTAINERLINE     lpContainerLine,
      LPSIZEL             lpsizelHim
);
BOOL ContainerLine_DoVerb(
      LPCONTAINERLINE lpContainerLine,
      LONG iVerb,
      LPMSG lpMsg,
      BOOL fMessage,
      BOOL fAction
);
LPUNKNOWN ContainerLine_GetOleObject(
      LPCONTAINERLINE         lpContainerLine,
      REFIID                  riid
);
HRESULT ContainerLine_RunOleObject(LPCONTAINERLINE lpContainerLine);
BOOL ContainerLine_ProcessOleRunError(
      LPCONTAINERLINE         lpContainerLine,
      HRESULT                 hrErr,
      BOOL                    fAction,
      BOOL                    fMenuInvoked
);
HRESULT ContainerLine_ReCreateLinkBecauseClassDiff(
      LPCONTAINERLINE lpContainerLine
);
BOOL ContainerLine_IsOleLink(LPCONTAINERLINE lpContainerLine);
void ContainerLine_BindLinkIfLinkSrcIsRunning(LPCONTAINERLINE lpContainerLine);
void ContainerLine_Draw(
      LPCONTAINERLINE         lpContainerLine,
      HDC                     hDC,
      LPRECT                  lpRect,
      LPRECT                  lpRectWBounds,
      BOOL                    fHighlight

);
void ContainerLine_DrawSelHilight(
      LPCONTAINERLINE lpContainerLine,
      HDC             hDC,
      LPRECT          lpRect,
      UINT            itemAction,
      UINT            itemState
);
BOOL ContainerLine_Edit(LPCONTAINERLINE lpContainerLine,HWND hWndDoc,HDC hDC);
void ContainerLine_SetHeightInHimetric(LPCONTAINERLINE lpContainerLine, int nHeight);
void ContainerLine_SetLineHeightFromObjectExtent(
      LPCONTAINERLINE         lpContainerLine,
      LPSIZEL                 lpsizelOleObject
);
BOOL ContainerLine_SaveToStm(
      LPCONTAINERLINE         lpContainerLine,
      LPSTREAM                lpLLStm
);
BOOL ContainerLine_SaveOleObjectToStg(
      LPCONTAINERLINE         lpContainerLine,
      LPSTORAGE               lpSrcStg,
      LPSTORAGE               lpDestStg,
      BOOL                    fRemember
);
LPLINE ContainerLine_LoadFromStg(
      LPSTORAGE               lpSrcStg,
      LPSTREAM                lpLLStm,
      LPOUTLINEDOC            lpDestDoc
);
LPMONIKER ContainerLine_GetRelMoniker(
      LPCONTAINERLINE         lpContainerLine,
      DWORD                   dwAssign
);
LPMONIKER ContainerLine_GetFullMoniker(
      LPCONTAINERLINE         lpContainerLine,
      DWORD                   dwAssign
);
int ContainerLine_GetTextLen(LPCONTAINERLINE lpContainerLine);
void ContainerLine_GetTextData(LPCONTAINERLINE lpContainerLine,LPOLESTR lpszBuf);
BOOL ContainerLine_GetOutlineData(
      LPCONTAINERLINE         lpContainerLine,
      LPTEXTLINE              lpBuf
);
void ContainerLine_GetOleObjectRectInPixels(
      LPCONTAINERLINE lpContainerLine,
      LPRECT lprc
);
void ContainerLine_GetPosRect(
      LPCONTAINERLINE     lpContainerLine,
      LPRECT              lprcPosRect
);
void ContainerLine_GetOleObjectSizeInHimetric(
      LPCONTAINERLINE lpContainerLine,
      LPSIZEL lpsizel
);

#if defined( INPLACE_CNTR )
void ContainerLine_UIDeactivate(LPCONTAINERLINE lpContainerLine);
void ContainerLine_InPlaceDeactivate(LPCONTAINERLINE lpContainerLine);
void ContainerLine_UpdateInPlaceObjectRects(
   LPCONTAINERLINE lpContainerLine,
   LPRECT          lprcClipRect
);
void ContainerLine_ContextSensitiveHelp(
      LPCONTAINERLINE lpContainerLine,
      BOOL            fEnterMode
);
void ContainerLine_ForwardPaletteChangedMsg(
      LPCONTAINERLINE lpContainerLine,
      HWND             hwndPalChg
);
void ContainerDoc_ContextSensitiveHelp(
      LPCONTAINERDOC  lpContainerDoc,
      BOOL            fEnterMode,
      BOOL            fInitiatedByObj
);
void ContainerDoc_ForwardPaletteChangedMsg(
      LPCONTAINERDOC  lpContainerDoc,
      HWND            hwndPalChg
);
#endif  // INPLACE_CNTR

/* ContainerLine::IUnknown methods (functions) */
STDMETHODIMP CntrLine_Unk_QueryInterface(
      LPUNKNOWN           lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
);
STDMETHODIMP_(ULONG) CntrLine_Unk_AddRef(LPUNKNOWN lpThis);
STDMETHODIMP_(ULONG) CntrLine_Unk_Release(LPUNKNOWN lpThis);

/* ContainerLine::IOleClientSite methods (functions) */
STDMETHODIMP CntrLine_CliSite_QueryInterface(
      LPOLECLIENTSITE     lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
);
STDMETHODIMP_(ULONG) CntrLine_CliSite_AddRef(LPOLECLIENTSITE lpThis);
STDMETHODIMP_(ULONG) CntrLine_CliSite_Release(LPOLECLIENTSITE lpThis);
STDMETHODIMP CntrLine_CliSite_SaveObject(LPOLECLIENTSITE lpThis);
STDMETHODIMP CntrLine_CliSite_GetMoniker(
      LPOLECLIENTSITE     lpThis,
      DWORD               dwAssign,
      DWORD               dwWhichMoniker,
      LPMONIKER FAR*      lplpmk
);
STDMETHODIMP CntrLine_CliSite_GetContainer(
      LPOLECLIENTSITE     lpThis,
      LPOLECONTAINER FAR* lplpContainer
);
STDMETHODIMP CntrLine_CliSite_ShowObject(LPOLECLIENTSITE lpThis);
STDMETHODIMP CntrLine_CliSite_OnShowWindow(LPOLECLIENTSITE lpThis,BOOL fShow);
STDMETHODIMP CntrLine_CliSite_RequestNewObjectLayout(LPOLECLIENTSITE lpThis);

/* ContainerLine::IAdviseSink methods (functions) */
STDMETHODIMP CntrLine_AdvSink_QueryInterface(
      LPADVISESINK        lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
);
STDMETHODIMP_(ULONG) CntrLine_AdvSink_AddRef(LPADVISESINK lpThis);
STDMETHODIMP_(ULONG) CntrLine_AdvSink_Release (LPADVISESINK lpThis);
STDMETHODIMP_(void) CntrLine_AdvSink_OnDataChange(
      LPADVISESINK        lpThis,
      FORMATETC FAR*      lpFormatetc,
      STGMEDIUM FAR*      lpStgmed
);
STDMETHODIMP_(void) CntrLine_AdvSink_OnViewChange(
      LPADVISESINK        lpThis,
      DWORD               aspects,
      LONG                lindex
);
STDMETHODIMP_(void) CntrLine_AdvSink_OnRename(
      LPADVISESINK        lpThis,
      LPMONIKER           lpmk
);
STDMETHODIMP_(void) CntrLine_AdvSink_OnSave(LPADVISESINK lpThis);
STDMETHODIMP_(void) CntrLine_AdvSink_OnClose(LPADVISESINK lpThis);

#if defined( INPLACE_CNTR )
/* ContainerLine::IOleInPlaceSite methods (functions) */

STDMETHODIMP CntrLine_IPSite_QueryInterface(
      LPOLEINPLACESITE    lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
);
STDMETHODIMP_(ULONG) CntrLine_IPSite_AddRef(LPOLEINPLACESITE lpThis);
STDMETHODIMP_(ULONG) CntrLine_IPSite_Release(LPOLEINPLACESITE lpThis);
STDMETHODIMP CntrLine_IPSite_GetWindow(
      LPOLEINPLACESITE    lpThis,
      HWND FAR*           lphwnd
);
STDMETHODIMP CntrLine_IPSite_ContextSensitiveHelp(
   LPOLEINPLACESITE    lpThis,
   BOOL                fEnterMode
);
STDMETHODIMP CntrLine_IPSite_CanInPlaceActivate(LPOLEINPLACESITE lpThis);
STDMETHODIMP CntrLine_IPSite_OnInPlaceActivate(LPOLEINPLACESITE lpThis);
STDMETHODIMP CntrLine_IPSite_OnUIActivate (LPOLEINPLACESITE lpThis);
STDMETHODIMP CntrLine_IPSite_GetWindowContext(
   LPOLEINPLACESITE            lpThis,
   LPOLEINPLACEFRAME FAR*      lplpFrame,
   LPOLEINPLACEUIWINDOW FAR*   lplpDoc,
   LPRECT                      lprcPosRect,
   LPRECT                      lprcClipRect,
   LPOLEINPLACEFRAMEINFO       lpFrameInfo
);
STDMETHODIMP CntrLine_IPSite_Scroll(
   LPOLEINPLACESITE    lpThis,
   SIZE                scrollExtent
);
STDMETHODIMP CntrLine_IPSite_OnUIDeactivate(
   LPOLEINPLACESITE    lpThis,
   BOOL                fUndoable
);
STDMETHODIMP CntrLine_IPSite_OnInPlaceDeactivate(LPOLEINPLACESITE lpThis);
STDMETHODIMP CntrLine_IPSite_DiscardUndoState(LPOLEINPLACESITE lpThis);
STDMETHODIMP CntrLine_IPSite_DeactivateAndUndo(LPOLEINPLACESITE lpThis);
STDMETHODIMP CntrLine_IPSite_OnPosRectChange(
   LPOLEINPLACESITE    lpThis,
   LPCRECT             lprcPosRect
);
#endif  // INPLACE_CNTR

/* struct definition for persistant data storage of ContainerLine */

#pragma pack(push, 2)
typedef struct tagCONTAINERLINERECORD_ONDISK
{
      OLECHAR m_szStgName[CWCSTORAGENAME]; // stg name w/i cntr stg
      USHORT  m_fMonikerAssigned;          // has a moniker been assigned to obj
      DWORD   m_dwDrawAspect;              // current display aspect for obj
                                                                               //     (either DVASPECT_CONTENT or
                                                                               //     DVASPECT_ICON)
      SIZEL   m_sizeInHimetric;            // extents of obj in himetric units
      DWORD   m_dwLinkType;                // is it a linked object?
                                                                               //  0 -- NOT a link
                                                                               //  OLEUPDATE_ALWAYS (1) -- auto link
                                                                               //  OLEUPDATE_ONCALL (3) -- man. link
      USHORT  m_fDoSetExtent;              // obj was resized when not running
                                                                               //  IOO::SetExtent needed on next run
} CONTAINERLINERECORD_ONDISK, FAR* LPCONTAINERLINERECORD_ONDISK;
#pragma pack(pop)

typedef struct tagCONTAINERLINERECORD {
   OLECHAR m_szStgName[CWCSTORAGENAME]; // stg name w/i cntr stg
   BOOL    m_fMonikerAssigned;          // has a moniker been assigned to obj
   DWORD   m_dwDrawAspect;              // current display aspect for obj
                               //     (either DVASPECT_CONTENT or
                               //     DVASPECT_ICON)
   SIZEL   m_sizeInHimetric;            // extents of obj in himetric units
   DWORD   m_dwLinkType;                // is it a linked object?
                               //  0 -- NOT a link
                               //  OLEUPDATE_ALWAYS (1) -- auto link
                               //  OLEUPDATE_ONCALL (3) -- man. link
   BOOL    m_fDoSetExtent;              // obj was resized when not running
                               //  IOO::SetExtent needed on next run
} CONTAINERLINERECORD, FAR* LPCONTAINERLINERECORD;

/*************************************************************************
** class CONTAINERDOC : OUTLINEDOC
**    CONTAINERDOC is an extention to the base OUTLINEDOC object (structure)
**    that adds OLE 2.0 Container functionality. There is one instance of
**    CONTAINERDOC object created per document open in the app. The SDI
**    version of the app supports one CONTAINERDOC at a time. The MDI
**    version of the app can manage multiple documents at one time.
**    The CONTAINERDOC class inherits all fields
**    from the OUTLINEDOC class. This inheritance is achieved by including a
**    member variable of type OUTLINEDOC as the first field in the
**    CONTAINERDOC structure. Thus a pointer to a CONTAINERDOC object
**    can be cast to be a pointer to a OUTLINEDOC object.
*************************************************************************/

typedef struct tagCONTAINERDOC {
   OLEDOC      m_OleDoc;       // ContainerDoc inherits all fields of OleDoc
   ULONG       m_nNextObjNo;   // next available obj no. for stg name
   LPSTORAGE   m_lpNewStg;     // holds new pStg when SaveAs is pending
   BOOL        m_fEmbeddedObjectAvail; // is single OLE embed copied to doc
   CLSID       m_clsidOleObjCopied;    // if obj copied, CLSID of obj
   DWORD       m_dwAspectOleObjCopied; // if obj copied, draw aspect of obj
   LPCONTAINERLINE m_lpSrcContainerLine;  // src line if doc created for copy
   BOOL        m_fShowObject;          // show object flag

#if defined( INPLACE_CNTR )
   LPCONTAINERLINE m_lpLastIpActiveLine;   // last in-place active line
   LPCONTAINERLINE m_lpLastUIActiveLine;   // last UIActive line
   HWND            m_hWndUIActiveObj;      // HWND of UIActive obj.
   BOOL            m_fAddMyUI;             // if adding tools/menu postponed
   int             m_cIPActiveObjects;

#if defined( INPLACE_CNTRSVR )
   LPOLEINPLACEFRAME m_lpTopIPFrame;       // ptr to Top In-place frame.
   LPOLEINPLACEFRAME m_lpTopIPDoc;         // ptr to Top In-place Doc window.
   HMENU             m_hSharedMenu;        // combined obj/cntr menu
                                 // NULL if we are top container
   HOLEMENU        m_hOleMenu;             // returned by OleCreateMenuDesc.
                                 // NULL if we are top container
#endif  // INPLACE_CNTRSVR
#endif  // INPLACE_CNTR

   struct CDocOleUILinkContainerImpl {
      IOleUILinkContainerVtbl FAR*  lpVtbl;
      LPCONTAINERDOC                lpContainerDoc;
      int                           cRef;   // interface specific ref count.
   } m_OleUILinkContainer;

} CONTAINERDOC;

/* ContainerDoc methods (functions) */
BOOL ContainerDoc_Init(LPCONTAINERDOC lpContainerDoc, BOOL fDataTransferDoc);
LPCONTAINERLINE ContainerDoc_GetNextLink(
      LPCONTAINERDOC lpContainerDoc,
      LPCONTAINERLINE lpContainerLine
);
void ContainerDoc_UpdateLinks(LPCONTAINERDOC lpContainerDoc);
void ContainerDoc_SetShowObjectFlag(LPCONTAINERDOC lpContainerDoc, BOOL fShow);
BOOL ContainerDoc_GetShowObjectFlag(LPCONTAINERDOC lpContainerDoc);
void ContainerDoc_InsertOleObjectCommand(LPCONTAINERDOC lpContainerDoc);
void ContainerDoc_EditLinksCommand(LPCONTAINERDOC lpContainerDoc);
void ContainerDoc_PasteLinkCommand(LPCONTAINERDOC lpContainerDoc);
void ContainerDoc_ConvertCommand(
      LPCONTAINERDOC      lpContainerDoc,
      BOOL                fServerNotRegistered
);
BOOL ContainerDoc_PasteFormatFromData(
      LPCONTAINERDOC          lpContainerDoc,
      CLIPFORMAT              cfFormat,
      LPDATAOBJECT            lpSrcDataObj,
      BOOL                    fLocalDataObj,
      BOOL                    fLink,
      BOOL                    fDisplayAsIcon,
      HGLOBAL                 hMetaPict,
      LPSIZEL                 lpSizelInSrc
);
int ContainerDoc_PasteCntrOutlData(
      LPCONTAINERDOC          lpDestContainerDoc,
      LPSTORAGE               lpSrcStg,
      int                     nStartIndex
);
BOOL ContainerDoc_QueryPasteFromData(
      LPCONTAINERDOC          lpContainerDoc,
      LPDATAOBJECT            lpSrcDataObj,
      BOOL                    fLink
);
int ContainerDoc_PasteOleObject(
      LPCONTAINERDOC          lpContainerDoc,
      LPDATAOBJECT            lpSrcDataObj,
      DWORD                   dwCreateType,
      CLIPFORMAT              cfFormat,
      int                     nIndex,
      BOOL                    fDisplayAsIcon,
      HGLOBAL                 hMetaPict,
      LPSIZEL                 lpSizelInSrc
);
BOOL ContainerDoc_CloseAllOleObjects(
      LPCONTAINERDOC          lpContainerDoc,
      DWORD                   dwSaveOption
);
void ContainerDoc_UnloadAllOleObjectsOfClass(
      LPCONTAINERDOC      lpContainerDoc,
      REFCLSID            rClsid,
      DWORD               dwSaveOption
);
void ContainerDoc_InformAllOleObjectsDocRenamed(
      LPCONTAINERDOC          lpContainerDoc,
      LPMONIKER               lpmkDoc
);
void ContainerDoc_UpdateExtentOfAllOleObjects(LPCONTAINERDOC lpContainerDoc);
BOOL ContainerDoc_SaveToFile(
      LPCONTAINERDOC          lpContainerDoc,
      LPCOLESTR               lpszFileName,
      UINT                    uFormat,
      BOOL                    fRemember
);
void ContainerDoc_ContainerLineDoVerbCommand(
      LPCONTAINERDOC          lpContainerDoc,
      LONG                    iVerb
);
void ContainerDoc_GetNextStgName(
      LPCONTAINERDOC          lpContainerDoc,
      LPOLESTR                lpszStgName,
      int                     nLen
);
BOOL ContainerDoc_IsStgNameUsed(
      LPCONTAINERDOC          lpContainerDoc,
      LPOLESTR                lpszStgName
);
LPSTORAGE ContainerDoc_GetStg(LPCONTAINERDOC lpContainerDoc);
HRESULT ContainerDoc_GetObject(
      LPCONTAINERDOC          lpContainerDoc,
      LPOLESTR                lpszItem,
      DWORD                   dwSpeedNeeded,
      REFIID                  riid,
      LPVOID FAR*             lplpvObject
);
HRESULT ContainerDoc_GetObjectStorage(
      LPCONTAINERDOC          lpContainerDoc,
      LPOLESTR                lpszItem,
      LPSTORAGE FAR*          lplpStg
);
HRESULT ContainerDoc_IsRunning(LPCONTAINERDOC   lpContainerDoc, LPOLESTR lpszItem);
LPUNKNOWN ContainerDoc_GetSingleOleObject(
      LPCONTAINERDOC          lpContainerDoc,
      REFIID                  riid,
      LPCONTAINERLINE FAR*    lplpContainerLine
);
BOOL ContainerDoc_IsSelAnOleObject(
      LPCONTAINERDOC          lpContainerDoc,
      REFIID                  riid,
      LPUNKNOWN FAR*          lplpvObj,
      int FAR*                lpnIndex,
      LPCONTAINERLINE FAR*    lplpContainerLine
);
HRESULT ContainerDoc_GetData (
      LPCONTAINERDOC          lpContainerDoc,
      LPFORMATETC             lpformatetc,
      LPSTGMEDIUM             lpMedium
);
HRESULT ContainerDoc_GetDataHere (
      LPCONTAINERDOC          lpContainerDoc,
      LPFORMATETC             lpformatetc,
      LPSTGMEDIUM             lpMedium
);
HRESULT ContainerDoc_QueryGetData (
      LPCONTAINERDOC          lpContainerDoc,
      LPFORMATETC             lpformatetc
);
HRESULT ContainerDoc_SetData (
      LPCONTAINERDOC          lpContainerDoc,
      LPFORMATETC             lpformatetc,
      LPSTGMEDIUM             lpmedium,
      BOOL                    fRelease
);
HRESULT ContainerDoc_EnumFormatEtc(
      LPCONTAINERDOC          lpContainerDoc,
      DWORD                   dwDirection,
      LPENUMFORMATETC FAR*    lplpenumFormatEtc
);
BOOL ContainerDoc_SetupDocGetFmts(
      LPCONTAINERDOC          lpContainerDoc,
      LPCONTAINERLINE         lpContainerLine
);

#if defined( INPLACE_CNTR )

void ContainerDoc_ShutDownLastInPlaceServerIfNotNeeded(
      LPCONTAINERDOC          lpContainerDoc,
      LPCONTAINERLINE         lpNextActiveLine
);
BOOL ContainerDoc_IsUIDeactivateNeeded(
      LPCONTAINERDOC  lpContainerDoc,
      POINT           pt
);
HWND ContainerDoc_GetUIActiveWindow(LPCONTAINERDOC lpContainerDoc);
void ContainerDoc_UpdateInPlaceObjectRects(LPCONTAINERDOC lpContainerDoc, int nIndex);
void ContainerDoc_GetClipRect(
      LPCONTAINERDOC      lpContainerDoc,
      LPRECT              lprcClipRect
);
void ContainerDoc_FrameWindowResized(LPCONTAINERDOC lpContainerDoc);
LPOLEINPLACEFRAME ContainerDoc_GetTopInPlaceFrame(
      LPCONTAINERDOC      lpContainerDoc
);
void ContainerDoc_GetSharedMenuHandles(
      LPCONTAINERDOC  lpContainerDoc,
      HMENU FAR*      lphSharedMenu,
      HOLEMENU FAR*   lphOleMenu
);
void ContainerDoc_RemoveFrameLevelTools(LPCONTAINERDOC lpContainerDoc);
void ContainerDoc_AddFrameLevelUI(LPCONTAINERDOC lpContainerDoc);
void ContainerDoc_AddFrameLevelTools(LPCONTAINERDOC lpContainerDoc);

#if defined( INPLACE_CNTRSVR ) || defined( INPLACE_MDICNTR )

LPOLEINPLACEUIWINDOW ContainerDoc_GetTopInPlaceDoc(
      LPCONTAINERDOC      lpContainerDoc
);
void ContainerDoc_RemoveDocLevelTools(LPCONTAINERDOC lpContainerDoc);
void ContainerDoc_AddDocLevelTools(LPCONTAINERDOC lpContainerDoc);

#endif  // INPLACE_CNTRSVR || INPLACE_MDICNTR
#endif  // INPLACE_CNTR

/* ContainerDoc::IOleUILinkContainer methods (functions) */
STDMETHODIMP CntrDoc_LinkCont_QueryInterface(
      LPOLEUILINKCONTAINER    lpThis,
      REFIID                  riid,
      LPVOID FAR*             lplpvObj
);
STDMETHODIMP_(ULONG) CntrDoc_LinkCont_AddRef(LPOLEUILINKCONTAINER lpThis);
STDMETHODIMP_(ULONG) CntrDoc_LinkCont_Release(LPOLEUILINKCONTAINER lpThis);
STDMETHODIMP_(DWORD) CntrDoc_LinkCont_GetNextLink(
      LPOLEUILINKCONTAINER    lpThis,
      DWORD                   dwLink
);
STDMETHODIMP CntrDoc_LinkCont_SetLinkUpdateOptions(
      LPOLEUILINKCONTAINER    lpThis,
      DWORD                   dwLink,
      DWORD                   dwUpdateOpt
);
STDMETHODIMP CntrDoc_LinkCont_GetLinkUpdateOptions(
      LPOLEUILINKCONTAINER    lpThis,
      DWORD                   dwLink,
      DWORD FAR*              dwUpdateOpt
);

STDMETHODIMP CntrDoc_LinkCont_SetLinkSource(
      LPOLEUILINKCONTAINER    lpThis,
      DWORD                   dwLink,
      LPSTR                lpszDisplayName,
      ULONG                   clenFileName,
      ULONG FAR*              lpchEaten,
      BOOL                    fValidateSource
);
STDMETHODIMP CntrDoc_LinkCont_GetLinkSource(
      LPOLEUILINKCONTAINER    lpThis,
      DWORD                   dwLink,
      LPSTR FAR*           lplpszDisplayName,
      ULONG FAR*              lplenFileName,
      LPSTR FAR*           lplpszFullLinkType,
      LPSTR FAR*           lplpszShortLinkType,
      BOOL FAR*               lpfSourceAvailable,
      BOOL FAR*               lpfIsSelected
);
STDMETHODIMP CntrDoc_LinkCont_OpenLinkSource(
      LPOLEUILINKCONTAINER    lpThis,
      DWORD                   dwLink
);
STDMETHODIMP CntrDoc_LinkCont_UpdateLink(
      LPOLEUILINKCONTAINER    lpThis,
      DWORD                   dwLink,

BOOL                    fErrorMessage,
      BOOL                    fErrorAction
);
STDMETHODIMP CntrDoc_LinkCont_CancelLink(
      LPOLEUILINKCONTAINER    lpThis,
      DWORD                   dwLink
);

/*************************************************************************
** class CONTAINERAPP : OLEAPP
**    CONTAINERAPP is an extention to the base OLEAPP object (structure)
**    that adds special Container functionality. There is one instance of
**    CONTAINERApp object created per running application instance. This
**    object holds many fields that could otherwise be organized as
**    global variables. The CONTAINERAPP class inherits all fields
**    from the OLEAPP class. This inheritance is achieved by including a
**    member variable of type OLEAPP as the first field in the CONTAINERAPP
**    structure. OLEAPP inherits from OUTLINEAPP. This inheritance is
**    achieved in the same manner. Thus a pointer to a CONTAINERAPP object
**    can be cast to be a pointer to an OLEAPP or an OUTLINEAPP object
*************************************************************************/

/* Forward definition of types */
typedef struct tagCONTAINERAPP FAR* LPCONTAINERAPP;

typedef struct tagCONTAINERAPP {
   OLEAPP  m_OleApp;       // ContainerApp inherits all fields of OleApp
   UINT    m_cfCntrOutl;   // clipboard format for CntrOutl (client ver) data
   int     m_nSingleObjGetFmts; // no. formats avail when single obj copied
   FORMATETC m_arrSingleObjGetFmts[MAXNOFMTS];
                              // array of FormatEtc's available via
                              // IDataObject::GetData when a single
                              // OLE object is copied.

#if defined( INPLACE_CNTR )
   HACCEL  m_hAccelIPCntr; // accelerators for container's workspace commands
   HMENU   m_hMenuFile;    // handle to File menu of container app
   HMENU   m_hMenuView;    // handle to View menu of container app
   HMENU   m_hMenuDebug;   // handle to Debug menu of container app
   LPOLEINPLACEACTIVEOBJECT m_lpIPActiveObj; // ptr to inplace active OLE obj
   HWND    m_hWndUIActiveObj;      // HWND of UIActive obj.
   BOOL    m_fPendingUIDeactivate; // should app UIDeactivate on LBUTTONUP
   BOOL    m_fMustResizeClientArea;// if client area resize pending
                           //  (see Doc_FrameWindowResized)
   BOOL    m_fMenuHelpMode;// is F1 pressed in menu, if so give help
#ifdef _DEBUG
   BOOL    m_fOutSideIn;
#endif

   struct COleInPlaceFrameImpl {
      IOleInPlaceFrameVtbl FAR* lpVtbl;
      LPCONTAINERAPP          lpContainerApp;
      int                     cRef;   // interface specific ref count.
   } m_OleInPlaceFrame;

#endif  // INPLACE_CNTR

} CONTAINERAPP;

/* ContainerApp methods (functions) */
BOOL ContainerApp_InitInstance(
      LPCONTAINERAPP          lpContainerApp,
      HINSTANCE               hInst,
      int                     nCmdShow
);
BOOL ContainerApp_InitVtbls(LPCONTAINERAPP lpApp);

#if defined( INPLACE_CNTR )

/* ContainerApp::IOleInPlaceFrame methods (functions) */

STDMETHODIMP CntrApp_IPFrame_QueryInterface(
      LPOLEINPLACEFRAME   lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
);
STDMETHODIMP_(ULONG) CntrApp_IPFrame_AddRef(LPOLEINPLACEFRAME lpThis);
STDMETHODIMP_(ULONG) CntrApp_IPFrame_Release(LPOLEINPLACEFRAME lpThis);
STDMETHODIMP CntrApp_IPFrame_GetWindow(
   LPOLEINPLACEFRAME   lpThis,
   HWND FAR*           lphwnd
);
STDMETHODIMP CntrApp_IPFrame_ContextSensitiveHelp(
   LPOLEINPLACEFRAME   lpThis,
   BOOL                fEnterMode
);
STDMETHODIMP CntrApp_IPFrame_GetBorder(
   LPOLEINPLACEFRAME   lpThis,
   LPRECT              lprectBorder
);
STDMETHODIMP CntrApp_IPFrame_RequestBorderSpace(
   LPOLEINPLACEFRAME   lpThis,
   LPCBORDERWIDTHS     lpWidths
);
STDMETHODIMP CntrApp_IPFrame_SetBorderSpace(
   LPOLEINPLACEFRAME   lpThis,
   LPCBORDERWIDTHS     lpWidths
);
STDMETHODIMP CntrApp_IPFrame_SetActiveObject(
   LPOLEINPLACEFRAME           lpThis,
   LPOLEINPLACEACTIVEOBJECT    lpActiveObject,
   LPCOLESTR                   lpszObjName
);
STDMETHODIMP CntrApp_IPFrame_InsertMenus(
   LPOLEINPLACEFRAME       lpThis,
   HMENU                   hmenu,
   LPOLEMENUGROUPWIDTHS    lpMenuWidths
);
STDMETHODIMP CntrApp_IPFrame_SetMenu(
   LPOLEINPLACEFRAME   lpThis,
   HMENU               hmenuShared,
   HOLEMENU            holemenu,
   HWND                hwndActiveObject
);
STDMETHODIMP CntrApp_IPFrame_RemoveMenus(
   LPOLEINPLACEFRAME   lpThis,
   HMENU               hmenu
);
STDMETHODIMP CntrApp_IPFrame_SetStatusText(
   LPOLEINPLACEFRAME   lpThis,
   LPCOLESTR           lpszStatusText
);
STDMETHODIMP CntrApp_IPFrame_EnableModeless(
   LPOLEINPLACEFRAME   lpThis,
   BOOL                fEnable
);
STDMETHODIMP CntrApp_IPFrame_TranslateAccelerator(
   LPOLEINPLACEFRAME   lpThis,
   LPMSG               lpmsg,
   WORD                wID
);

#endif  // INPLACE_CNTR

#endif // _CNTROUTL_H_


CNTRRC.H

/*************************************************************************
**
**    OLE 2.0 Container Sample Code
**
**    cntrrc.h
**
**    This file contains constants used in rc file for CNTROUTL.EXE
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#if !defined( _CNTRRC_H_ )
#define _CNTRRC_H_

#ifndef RC_INVOKED
#pragma message ("INCLUDING CNTRRC.H from " __FILE__)
#endif  /* RC_INVOKED */

#define IDM_E_INSERTOBJECT          2700
#define IDM_E_EDITLINKS             3300
#define IDM_E_PASTELINK             2750
#define IDM_E_CONVERTVERB           9000
#define IDM_E_OBJECTVERBMIN         10000

#define IDM_D_INSIDEOUT             2755

#define WM_U_UPDATEOBJECTEXTENT     WM_USER+1

#endif // _CNTRRC_H_


DEBUG.C

/*************************************************************************
**
**    OLE 2 Sample Code
**
**    debug.c
**
**    This file contains some functions for debugging support
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#include "outline.h"

OLEDBGDATA

extern LPOUTLINEAPP g_lpApp;

void SetDebugLevelCommand(void)
{
   char szBuf[80];
   HWND hWndFrame = OutlineApp_GetFrameWindow(g_lpApp);

   wsprintf(szBuf, "%d", OleDbgGetDbgLevel());

   if (InputTextDlg(hWndFrame, szBuf, "Debug Level [0-4]")) {
      switch (szBuf[0]) {
         case '0':
            OleDbgSetDbgLevel(0);
            break;
         case '1':
            OleDbgSetDbgLevel(1);
            break;
         case '2':
            OleDbgSetDbgLevel(2);
            break;
         case '3':
            OleDbgSetDbgLevel(3);
            break;
         case '4':
            OleDbgSetDbgLevel(4);
            break;
         default:
            OutlineApp_ErrorMessage(g_lpApp, OLESTR("Valid Debug Level Range: 0-4"));
            break;
      }
   }
}

#if defined( OLE_VERSION )

/* InstallMessageFilterCommand
 * ---------------------------
 *
 * Handles the "Install Message Filter" menu item.  If a message filter is
 * already installed, this function de-installs it.  If there is not one
 * already installed, this function installs one.
 *
 */

void InstallMessageFilterCommand(void)
{
   LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;

   /*
   ** Check to see if we've already installed a MessageFilter.
   ** If so, uninstall it.
   */
   if (lpOleApp->m_lpMsgFilter != NULL)
      OleApp_RevokeMessageFilter(lpOleApp);
   else
      OleApp_RegisterMessageFilter(lpOleApp);
}

/* RejectIncomingCommand
 * ---------------------
 *
 * Toggles between rejecting and not-handling in coming LRPC calls
 *
 */

void RejectIncomingCommand(void)
{
   DWORD dwOldStatus;
   DWORD dwNewStatus;
   LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;

   dwOldStatus = OleStdMsgFilter_GetInComingCallStatus(lpOleApp->m_lpMsgFilter);

   if (dwOldStatus == SERVERCALL_RETRYLATER)
      dwNewStatus = SERVERCALL_ISHANDLED;
   else
      dwNewStatus = SERVERCALL_RETRYLATER;

   OleStdMsgFilter_SetInComingCallStatus(lpOleApp->m_lpMsgFilter, dwNewStatus);
}

#endif  // OLE_VERSION


DEBUG2.C

/*************************************************************************
**
**    OLE 2 Sample Code
**
**    debug2.c
**
**    This file contains various debug / subclass routines for the
**    ABOUT dialog
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#include "outline.h"
#include <stdlib.h>
#include <time.h>

extern LPOUTLINEAPP g_lpApp;

LONG CALLBACK EXPORT DebugAbout(HWND hWnd, unsigned uMsg, WORD wParam, LONG lParam);
void RandomizeStars(HDC hDC);
BOOL InitStrings(void);
BOOL DrawString(int iCount, HDC hDC, LPRECT rcDrawIn);

static FARPROC lpRealAboutProc = 0L;
static int width, height;
static RECT rc;
static HANDLE hStrBlock = NULL;
static LPSTR lpStrings = NULL;
static WORD       wLineHeight;

/* TraceDebug
 * ----------
 *
 * Called once when our About Box's gets the INITDIALOG message.  Subclasses
 * dialog.
 */

void TraceDebug(HWND hDlg, int iControl)
{

        // Load strings, if the strings aren't there, then don't subclass
        // the dialog
        if (InitStrings() != TRUE)
                return;

        // Subclass the dialog
        lpRealAboutProc = (FARPROC)(LONG)GetWindowLong(hDlg, GWL_WNDPROC);
        SetWindowLong(hDlg, GWL_WNDPROC, (LONG)(FARPROC)DebugAbout);

        // Get rect of control in screen coords, and translate to our dialog
        // box's coordinates
        GetWindowRect(GetDlgItem(hDlg, iControl), &rc);
        MapWindowPoints(NULL, hDlg, (LPPOINT)&rc, 2);

        width  = rc.right - rc.left;
        height = rc.bottom - rc.top;
}

/* DebugAbout
 * ----------
 *
 * The subclassed About dialog's main window proc.
 */

LONG CALLBACK EXPORT DebugAbout(HWND hWnd, unsigned uMsg, WORD wParam, LONG lParam)
{
        RECT              rcOut;
        static BOOL       bTimerStarted = FALSE;
        static int        iTopLocation;
        HDC               hDCScr;
        static HDC        hDCMem;
        static HBITMAP    hBitmap;
        static HBITMAP    hBitmapOld;
        static RECT       rcMem;
        static HFONT      hFont;

        switch (uMsg)
        {

        /*
         * If we get a LBUTTONDBLCLICK in the upper left of
         * the dialog, fire off the about box effects
         */

        case WM_LBUTTONDBLCLK:
                if ((wParam & MK_CONTROL) && (wParam & MK_SHIFT)
                        && LOWORD(lParam) < 10 && HIWORD(lParam) < 10 &&
                        bTimerStarted == FALSE)
                        {
                        if (SetTimer ( hWnd, 1, 10, NULL ))
                                {
                                LOGFONT lf;
                                int i;

                                bTimerStarted = TRUE;

                                // "Open up" the window
                                hDCScr = GetDC ( hWnd );
                                hDCMem = CreateCompatibleDC     ( hDCScr );

                                hBitmap = CreateCompatibleBitmap(hDCScr, width, height);
                                hBitmapOld = SelectObject(hDCMem, hBitmap);

                                // Blt from dialog to memDC
                                BitBlt(hDCMem, 0, 0, width, height,
                                hDCScr, rc.left, rc.top, SRCCOPY);

                                for (i=0;i<height;i+=1)
                                {
                                        BitBlt(hDCScr, rc.left, rc.top + i + 1, width, height-i-1, hDCMem, 0, 0, SRCCOPY);
                                        PatBlt(hDCScr, rc.left, rc.top + i, width, 1, BLACKNESS);
                                }

                                SelectObject(hDCMem, hBitmapOld);
                                DeleteObject(hBitmap);

                                // Set up memory DC with default attributes
                                hBitmap   = CreateCompatibleBitmap(hDCScr, width, height);
                                ReleaseDC(hWnd, hDCScr);

                                hBitmapOld = SelectObject(hDCMem, hBitmap);

                                SetBkMode(hDCMem, TRANSPARENT);
                                SetBkColor(hDCMem, RGB(0,0,0));

                                // Create font
                                memset(&lf, 0, sizeof(LOGFONT));
                                lf.lfHeight = -(height / 7); // Fit 7 lines of text in box
                                lf.lfWeight = FW_BOLD;
                                strcpy(lf.lfFaceName, "Arial");
                                hFont = CreateFontIndirect(&lf);

                                // If we can't create the font, revert and use the standard
                                // system font.
                                if (!hFont)
                                        GetObject(GetStockObject(SYSTEM_FONT), sizeof(LOGFONT), &lf);

                                wLineHeight = abs(lf.lfHeight) + 5; // 5 pixels between lines

                                // Set location of top of banner at bottom of the window
                                iTopLocation = height + 50;

                                SetRect(&rcMem, 0, 0, width, height);
                                }
                        }
                        // Call our real window procedure in case they want to
                        // handle LBUTTONDOWN messages also
                        goto Default;

        case WM_TIMER:
                {
                int iCount;
                HFONT hfold;

                /*
                 * On each timer message, we are going to construct the next image
                 * in the animation sequence, then bitblt this to our dialog.
                 */

                // Clear out old bitmap and place random star image on background
                PatBlt(hDCMem, rcMem.left, rcMem.top, rcMem.right, rcMem.bottom, BLACKNESS);
                RandomizeStars(hDCMem);

                // Set initial location to draw text
                rcOut = rcMem;
                rcOut.top = 0 + iTopLocation;
                rcOut.bottom = rcOut.top + wLineHeight;

                iCount = 0;
                if (hFont) hfold = SelectObject(hDCMem, hFont);

                SetTextColor(hDCMem, RGB(0,255,0));
                while (DrawString(iCount, hDCMem, &rcOut) == TRUE)
                        {
                        rcOut.top    += wLineHeight;
                        rcOut.bottom += wLineHeight;
                        iCount++;
                        }
                if (hFont) SelectObject(hDCMem, hfold);

                // Now blt the memory dc that we have just constructed
                // to the screen
                hDCScr = GetDC(hWnd);
                BitBlt(hDCScr, rc.left, rc.top, rc.right, rc.bottom,
                        hDCMem, 0, 0, SRCCOPY);
                ReleaseDC(hWnd, hDCScr);

                // For the next animation sequence, we want to move the
                // whole thing up, so decrement the location of the top
                // of the banner

                iTopLocation -= 2;

                // If we've gone through the banner once, reset it
                if (iTopLocation < -(int)(wLineHeight * iCount))
                        iTopLocation = height + 50;
                }
                // Goto default
                goto Default;

        case WM_NCDESTROY:
                {
                LONG defReturn;

                /*
                 * We're being destroyed.  Clean up what we created.
                 */

                if (bTimerStarted)
                {
                        KillTimer(hWnd, 1);
                        SelectObject (hDCMem, hBitmapOld);
                        DeleteObject (hBitmap);
                        DeleteDC (hDCMem);
                        if (hFont) DeleteObject(hFont);
                        bTimerStarted = FALSE;
                }

                if (lpStrings)
                        UnlockResource(hStrBlock), lpStrings = NULL;
                if (hStrBlock)
                        FreeResource(hStrBlock), hStrBlock = NULL;

                // Pass the NCDESTROY on to our real window procedure.  Since
                // this is the last message that we are going to be getting,
                // we can go ahead and free the proc instance here.

                defReturn = CallWindowProc((WNDPROC)lpRealAboutProc, hWnd,
                                           uMsg, wParam, lParam);
                return defReturn;
                }

        Default:
        default:
                return CallWindowProc(
                                (WNDPROC)lpRealAboutProc, hWnd, uMsg, wParam, lParam);
        }
        return 0L;
}

/* RandomizeStars
 * --------------
 *
 * Paints random stars on the specified hDC
 *
 */

void RandomizeStars(HDC hDC)
{
        int             i;

        // Seed the random number generator with current time.  This will,
        // in effect, only change the seed every second, so our
        // starfield will change only every second.
        srand((unsigned)time(NULL));

        // Generate random white stars
        for (i=0;i<20;i++)
                PatBlt(hDC, getrandom(0,width), getrandom(0,height), 2, 2, WHITENESS);
}

/* InitStrings
 * --------------
 *
 * Reads strings from stringtable.  Returns TRUE if it worked OK.
 *
 */

BOOL InitStrings()
{
        HRSRC hResStrings;
        LPSTR lpWalk;

        // Load the block of strings
        if ((hResStrings = FindResource(
                        g_lpApp->m_hInst,
                        MAKEINTRESOURCE(9999),
                        RT_RCDATA)) == NULL)
                return FALSE;
        if ((hStrBlock = LoadResource(g_lpApp->m_hInst, hResStrings)) == NULL)
                return FALSE;
        if ((lpStrings = LockResource(hStrBlock)) == NULL)
                return FALSE;

        if (lpStrings && *(lpStrings+2)!=0x45)
        {
                lpWalk = lpStrings;
                 while (*(UNALIGNED WORD *)lpWalk != (WORD)0x0000)
                {
                    if (*lpWalk != (char)0x00)
                          *((UNALIGNED WORD *)lpWalk) ^= 0x98;
                           lpWalk++;
                }
        }
        return TRUE;
}

/* DrawString
 * ----------
 *
 * Draws the next string on the specified hDC using the
 * output rectangle.  If iCount == 0, reset to start of list.
 *
 * Returns: TRUE to contine, FALSE if we're done
 */

BOOL DrawString(int iCount, HDC hDC, LPRECT rcDrawIn)
{
        static LPSTR lpPtr = NULL;

        if (iCount == 0)
                // First time, reset pointer
                lpPtr = lpStrings;

        if (*lpPtr == '\0') // If we've hit a NULL string, we're done
                return FALSE;

        // If we're drawing outside of visible box, don't call DrawText
        if ((rcDrawIn->bottom > 0) && (rcDrawIn->top < height))
                DrawText(hDC, lpPtr, -1, rcDrawIn, DT_CENTER);

        // Advance pointer to next string
        lpPtr += lstrlen(lpPtr) + 1;

        return TRUE;
}


DEFGUID.H

/*************************************************************************
**
**    OLE 2.0 Sample Code
**
**    clsid.h
**
**    This file contains file contains GUID definitions used for the
**    OLE versions of OUTLINE.
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#if defined( OLE_SERVER ) || defined( OLE_CNTR )
// NOTE: We need access to these GUIDs in modules other than
//  where they are defined (MAIN.C).  Even though the values of the
//  GUIDs are duplicated here, they are not used.  Refer to MAIN.C
//  for the definition of these GUIDs.

/* CLASS ID CONSTANTS (GUID's)
**    NOTE: these class id values are allocated out of a private pool
**    of GUID's allocated to the OLE 2.0 development team. GUID's of
**    the following range have been allocated to OLE 2.0 sample code:
**         00000400-0000-0000-C000-000000000046
**         000004FF-0000-0000-C000-000000000046
**
**    values reserved thus far:
**          00000400                -- Ole 2.0 Server Sample Outline
**          00000401                -- Ole 2.0 Container Sample Outline
**          00000402                -- Ole 2.0 In-Place Server Outline
**          00000403                -- Ole 2.0 In-Place Container Outline
**          00000404 : 000004FE     -- reserved for OLE Sample code
**          000004FF                -- IID_IOleUILinkContainer
*/
#ifdef TEST32

DEFINE_GUID(CLSID_SvrOut32, 0x00000600, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
DEFINE_GUID(CLSID_CntrOu32, 0x00000601, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
DEFINE_GUID(CLSID_ISvrOu32, 0x00000602, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
DEFINE_GUID(CLSID_ICntrO32, 0x00000603, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);

#else

DEFINE_GUID(CLSID_SvrOutl, 0x00000400, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
DEFINE_GUID(CLSID_CntrOutl, 0x00000401, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
DEFINE_GUID(CLSID_ISvrOtl, 0x00000402, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
DEFINE_GUID(CLSID_ICntrOtl, 0x00000403, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);

#endif  //TEST32

#if defined( OLE_CNTR )
DEFINE_GUID(IID_IOleUILinkContainer, 0x000004FF, 0x0000, 0x0000, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46);
#endif

#endif


DIALOGS.C

/*************************************************************************
**
**    OLE 2 Sample Code
**
**    dialogs.c
**
**    This file contains dialog functions and support function
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#include "outline.h"

OLEDBGDATA

extern LPOUTLINEAPP g_lpApp;

static OLECHAR  g_szBuf[MAXSTRLEN+1];
static LPSTR g_lpszDlgTitle;

// REVIEW: should use string resource for messages
static OLECHAR ErrMsgInvalidRange[] = OLESTR("Invalid Range entered!");
static OLECHAR ErrMsgInvalidValue[] = OLESTR("Invalid Value entered!");
static OLECHAR ErrMsgInvalidName[] = OLESTR("Invalid Name entered!");
static OLECHAR ErrMsgNullName[] = OLESTR("NULL string disallowed!");
static OLECHAR ErrMsgNameNotFound[] = OLESTR("Name doesn't exist!");

/* InputTextDlg
 * ------------
 *
 *      Put up a dialog box to allow the user to edit text
 */
BOOL InputTextDlg(HWND hWnd, LPSTR lpszText, LPSTR lpszDlgTitle)
{
   int nResult;

   g_lpszDlgTitle = lpszDlgTitle;
   A2W (lpszText, g_szBuf, MAXSTRLEN+1);  // preload dialog with input text

   nResult = DialogBox(g_lpApp->m_hInst, (LPSTR)"AddEditLine", hWnd,
                  (DLGPROC)AddEditDlgProc);
   if (nResult) {
      W2A(g_szBuf, lpszText, MAXSTRLEN+1);
      return TRUE;
   } else {
      return FALSE;
   }
}

/* AddEditDlgProc
 * --------------
 *
 * This procedure is associated with the dialog box that is included in
 * the function name of the procedure. It provides the service routines
 * for the events (messages) that occur because the end user operates
 * one of the dialog box's buttons, entry fields, or controls.
 */
BOOL CALLBACK EXPORT AddEditDlgProc(HWND hDlg, UINT Message, WPARAM wParam, LPARAM lParam)
{
   HWND hEdit;
   char szAnsiString[256];

   switch(Message) {
      case WM_INITDIALOG:
         /* initialize working variables */
         hEdit=GetDlgItem(hDlg,IDD_EDIT);
         SendMessage(hEdit,EM_LIMITTEXT,(WPARAM)MAXSTRLEN,0L);
         SetWindowText(hDlg, g_lpszDlgTitle);
         W2A (g_szBuf, szAnsiString, 256);
         SetDlgItemText(hDlg,IDD_EDIT, szAnsiString);
         break; /* End of WM_INITDIALOG */

      case WM_CLOSE:
         /* Closing the Dialog behaves the same as Cancel */
         PostMessage(hDlg, WM_COMMAND, IDCANCEL, 0L);
         break; /* End of WM_CLOSE */

      case WM_COMMAND:
         switch (wParam) {
            case IDOK:
               /* save data values entered into the controls
               ** and dismiss the dialog box returning TRUE
               */
               GetDlgItemText(hDlg,IDD_EDIT,(LPSTR)g_szBuf,MAXSTRLEN+1);
               EndDialog(hDlg, TRUE);
               break;

            case IDCANCEL:
               /* ignore data values entered into the controls
               ** and dismiss the dialog box returning FALSE
               */
               EndDialog(hDlg, FALSE);
               break;
         }
         break;    /* End of WM_COMMAND */

      default:
         return FALSE;
   }

   return TRUE;
} /* End of AddEditDlgProc */

/* SetLineHeightDlgProc
 * --------------------
 *
 *      Dialog procedure for set line height
 */
BOOL CALLBACK EXPORT SetLineHeightDlgProc(HWND hDlg, UINT Message, WPARAM wParam, LPARAM lParam)
{
   BOOL    fTranslated;
   BOOL    fEnable;
   static LPINT    lpint;
   int     nHeight;
   static int nMaxHeight;

   switch (Message) {
      case WM_INITDIALOG:
      {
         char cBuf[80];

         nMaxHeight = XformHeightInPixelsToHimetric(NULL,
                        LISTBOX_HEIGHT_LIMIT);
         lpint = (LPINT)lParam;
         SetDlgItemInt(hDlg, IDD_EDIT, *lpint, FALSE);
         wsprintf(cBuf, "Maximum value is %d units", nMaxHeight);
         SetDlgItemText(hDlg, IDD_LIMIT, (LPSTR)cBuf);
         break;
      }

      case WM_COMMAND:
         switch (wParam) {
            case IDOK:
               if (IsDlgButtonChecked(hDlg, IDD_CHECK)) {
                  *lpint = -1;
               }
               else {
                  /* save the value in the edit control */
                  nHeight = GetDlgItemInt(hDlg, IDD_EDIT,
                        (BOOL FAR*)&fTranslated, FALSE);
                  if (!fTranslated || !nHeight || (nHeight>nMaxHeight)){
                     OutlineApp_ErrorMessage(g_lpApp,
                           ErrMsgInvalidValue);
                     break;
                  }
                  *lpint = nHeight;
               }
               EndDialog(hDlg, TRUE);
               break;

            case IDCANCEL:
               *lpint = 0;
               EndDialog(hDlg, FALSE);
               break;

            case IDD_CHECK:
               fEnable = !IsDlgButtonChecked(hDlg, IDD_CHECK);
               EnableWindow(GetDlgItem(hDlg, IDD_EDIT), fEnable);
               EnableWindow(GetDlgItem(hDlg, IDD_TEXT), fEnable);
               break;
         }
         break;  /* WM_COMMAND */

      case WM_CLOSE:  /* Closing the Dialog behaves the same as Cancel */
         PostMessage(hDlg, WM_COMMAND, IDCANCEL, 0L);
         break; /* End of WM_CLOSE */

      default:
         return FALSE;
   }

   return TRUE;

} /* end of SetLineHeightProc */

/* DefineNameDlgProc
 * -----------------
 *
 *      Dialog procedure for define name
 */
BOOL CALLBACK EXPORT DefineNameDlgProc(HWND hDlg, UINT Message, WPARAM wParam, LPARAM lParam)
{
   static HWND hCombo;
   static LPOUTLINEDOC lpOutlineDoc = NULL;
   static LPOUTLINENAMETABLE lpOutlineNameTable = NULL;
   LPOUTLINENAME lpOutlineName = NULL;
   UINT nIndex;
   LINERANGE lrSel;
   BOOL fTranslated;
   char szAnsiString[256];

   switch(Message) {
      case WM_INITDIALOG:
      /* initialize working variables */
         hCombo=GetDlgItem(hDlg,IDD_COMBO);
         lpOutlineDoc = (LPOUTLINEDOC) lParam;
         lpOutlineNameTable = OutlineDoc_GetNameTable(lpOutlineDoc);

         SendMessage(hCombo,CB_LIMITTEXT,(WPARAM)MAXNAMESIZE,0L);
         NameDlg_LoadComboBox(lpOutlineNameTable, hCombo);

         OutlineDoc_GetSel(lpOutlineDoc, (LPLINERANGE)&lrSel);
         lpOutlineName = OutlineNameTable_FindNamedRange(
               lpOutlineNameTable,
               &lrSel
         );

         /* if current selection already has a name, hilight it */
         if (lpOutlineName) {
            nIndex = (int) SendMessage(
                  hCombo,
                  CB_FINDSTRINGEXACT,
                  (WPARAM)0xffff,
                  (LPARAM)(LPCSTR)lpOutlineName->m_szName
            );
            if (nIndex != CB_ERR) {
               SendMessage(hCombo, CB_SETCURSEL, (WPARAM)nIndex, 0L);
            }
         }

         SetDlgItemInt(hDlg, IDD_FROM, (UINT)lrSel.m_nStartLine+1,FALSE);
         SetDlgItemInt(hDlg, IDD_TO, (UINT)lrSel.m_nEndLine+1, FALSE);

         break; /* End of WM_INITDIALOG */

      case WM_CLOSE:
      /* Closing the Dialog behaves the same as Cancel */
         PostMessage(hDlg, WM_COMMAND, IDD_CLOSE, 0L);
         break; /* End of WM_CLOSE */

      case WM_COMMAND:
         switch(wParam) {
            case IDOK:
               GetDlgItemText(hDlg,IDD_COMBO,(LPSTR)g_szBuf,MAXNAMESIZE);
               if(! SendMessage(hCombo,WM_GETTEXTLENGTH,0,0L)) {
                  W2A (ErrMsgNullName, szAnsiString, 256);
                  MessageBox(
                        hDlg,
                        szAnsiString,
                        NULL,
                        MB_ICONEXCLAMATION
                  );
                  break;
               } else if(SendMessage(hCombo,CB_GETCURSEL,0,0L)==CB_ERR &&
                     wcschr(g_szBuf, ' ')) {
                  W2A (ErrMsgInvalidName, szAnsiString, 256);
                  MessageBox(
                        hDlg,
                        szAnsiString,
                        NULL,
                        MB_ICONEXCLAMATION
                  );
                  break;
               } else {
                  nIndex = (int) SendMessage(hCombo,CB_FINDSTRINGEXACT,
                     (WPARAM)0xffff,(LPARAM)(LPCSTR)g_szBuf);

                  /* Line indices are 1 less than the number in
                  **    the row heading
                  */
                  lrSel.m_nStartLine = GetDlgItemInt(hDlg, IDD_FROM,
                        (BOOL FAR*)&fTranslated, FALSE) - 1;
                  if(! fTranslated) {
                     OutlineApp_ErrorMessage(g_lpApp,
                           ErrMsgInvalidRange);
                     break;
                  }
                  lrSel.m_nEndLine = GetDlgItemInt(hDlg, IDD_TO,
                        (BOOL FAR*)&fTranslated, FALSE) - 1;
                  if (!fTranslated ||
                     (lrSel.m_nStartLine < 0) ||
                     (lrSel.m_nEndLine < lrSel.m_nStartLine) ||
                     (lrSel.m_nEndLine >= OutlineDoc_GetLineCount(
                           lpOutlineDoc))) {
                     OutlineApp_ErrorMessage(g_lpApp,
                           ErrMsgInvalidRange);
                     break;
                  }

                  if(nIndex != CB_ERR) {
                     NameDlg_UpdateName(
                           hCombo,
                           lpOutlineDoc,
                           nIndex,
                           g_szBuf,
                           &lrSel
                     );
                  } else {
                     NameDlg_AddName(
                           hCombo,
                           lpOutlineDoc,
                           g_szBuf,
                           &lrSel
                     );
                  }
               }
               // fall through

            case IDD_CLOSE:
               /* Ignore data values entered into the controls */
               /* and dismiss the dialog window returning FALSE */
               EndDialog(hDlg,0);
               break;

            case IDD_DELETE:
               GetDlgItemText(hDlg,IDD_COMBO,(LPSTR)szAnsiString,MAXNAMESIZE);
               if((nIndex=(int)SendMessage(hCombo,CB_FINDSTRINGEXACT,
               (WPARAM)0xffff,(LPARAM)(LPCSTR)szAnsiString))==CB_ERR) {
                  W2A (ErrMsgNameNotFound, szAnsiString, 256);
                  MessageBox(hDlg, szAnsiString, NULL, MB_ICONEXCLAMATION);
               }
               else {
                  NameDlg_DeleteName(hCombo, lpOutlineDoc, nIndex);
               }
               break;

            case IDD_COMBO:
               if(HIWORD(lParam) == CBN_SELCHANGE) {
                  nIndex=(int)SendMessage(hCombo, CB_GETCURSEL, 0, 0L);
                  lpOutlineName = (LPOUTLINENAME)SendMessage(
                        hCombo,
                        CB_GETITEMDATA,
                        (WPARAM)nIndex,
                        0L
                  );
                  SetDlgItemInt(
                        hDlg,
                        IDD_FROM,
                        (UINT) lpOutlineName->m_nStartLine + 1,
                        FALSE
                  );
                  SetDlgItemInt(
                        hDlg,
                        IDD_TO,
                        (UINT) lpOutlineName->m_nEndLine + 1,
                        FALSE
                  );
               }
         }
         break;    /* End of WM_COMMAND */

      default:
         return FALSE;
   }

   return TRUE;
} /* End of DefineNameDlgProc */

/* GotoNameDlgProc
 * ---------------
 *
 *      Dialog procedure for goto name
 */
BOOL CALLBACK EXPORT GotoNameDlgProc(HWND hDlg, UINT Message, WPARAM wParam, LPARAM lParam)
{
   static HWND hLBName;
   static LPOUTLINEDOC lpOutlineDoc = NULL;
   static LPOUTLINENAMETABLE lpOutlineNameTable = NULL;
   UINT nIndex;
   LINERANGE lrLineRange;
   LPOUTLINENAME lpOutlineName;

   switch(Message) {
      case WM_INITDIALOG:
      /* initialize working variables */
         lpOutlineDoc = (LPOUTLINEDOC) lParam;
         lpOutlineNameTable = OutlineDoc_GetNameTable(lpOutlineDoc);

         hLBName=GetDlgItem(hDlg,IDD_LINELISTBOX);
         NameDlg_LoadListBox(lpOutlineNameTable, hLBName);

         // highlight 1st item
         SendMessage(hLBName, LB_SETCURSEL, 0, 0L);
         // trigger to initialize edit control
         SendMessage(hDlg, WM_COMMAND, (WPARAM)IDD_LINELISTBOX,
            MAKELONG(hLBName, LBN_SELCHANGE));

         break; /* End of WM_INITDIALOG */

      case WM_CLOSE:
      /* Closing the Dialog behaves the same as Cancel */
         PostMessage(hDlg, WM_COMMAND, IDCANCEL, 0L);
         break; /* End of WM_CLOSE */

      case WM_COMMAND:
         switch(wParam) {
            case IDD_LINELISTBOX:
               if(HIWORD(lParam) == LBN_SELCHANGE) {
                  // update the line range display
                  nIndex=(int)SendMessage(hLBName, LB_GETCURSEL, 0, 0L);
                  lpOutlineName = (LPOUTLINENAME)SendMessage(hLBName, LB_GETITEMDATA,
                                 (WPARAM)nIndex,0L);
                  if (lpOutlineName) {
                     SetDlgItemInt(
                           hDlg,
                           IDD_FROM,
                           (UINT) lpOutlineName->m_nStartLine + 1,
                           FALSE
                     );
                     SetDlgItemInt(
                           hDlg,
                           IDD_TO,
                           (UINT) lpOutlineName->m_nEndLine + 1,
                           FALSE
                     );
                  }
                  break;
               }
               // double click will fall through
               else if(HIWORD(lParam) != LBN_DBLCLK)
                  break;

            case IDOK:
               nIndex=(int)SendMessage(hLBName,LB_GETCURSEL,0,0L);
               if(nIndex!=LB_ERR) {
                  lpOutlineName = (LPOUTLINENAME)SendMessage(hLBName,
                        LB_GETITEMDATA, (WPARAM)nIndex, 0L);
                  lrLineRange.m_nStartLine=lpOutlineName->m_nStartLine;
                  lrLineRange.m_nEndLine = lpOutlineName->m_nEndLine;
                  OutlineDoc_SetSel(lpOutlineDoc, &lrLineRange);
               }   // fall through

            case IDCANCEL:
            /* Ignore data values entered into the controls */
            /* and dismiss the dialog window returning FALSE */
               EndDialog(hDlg,0);
               break;

         }
         break;    /* End of WM_COMMAND */

      default:
         return FALSE;
   }

   return TRUE;
} /* End of GotoNameDlgProc */

/* NameDlg_LoadComboBox
 * --------------------
 *
 *      Load defined names into combo box
 */
void NameDlg_LoadComboBox(LPOUTLINENAMETABLE lpOutlineNameTable,HWND hCombo)
{
   LPOUTLINENAME lpOutlineName;
   int i, nIndex;
   int nCount;

   nCount=OutlineNameTable_GetCount((LPOUTLINENAMETABLE)lpOutlineNameTable);
   if(!nCount) return;

   SendMessage(hCombo,WM_SETREDRAW,(WPARAM)FALSE,0L);
   for(i=0; i<nCount; i++) {
      lpOutlineName=OutlineNameTable_GetName((LPOUTLINENAMETABLE)lpOutlineNameTable,i);
      nIndex = (int)SendMessage(
            hCombo,
            CB_ADDSTRING,
            0,
            (LPARAM)(LPCSTR)lpOutlineName->m_szName
      );
      SendMessage(hCombo,CB_SETITEMDATA,(WPARAM)nIndex,(LPARAM)lpOutlineName);
   }
   SendMessage(hCombo,WM_SETREDRAW,(WPARAM)TRUE,0L);
}

/* NameDlg_LoadListBox
 * -------------------
 *
 *      Load defined names into list box
 */
void NameDlg_LoadListBox(LPOUTLINENAMETABLE lpOutlineNameTable,HWND hListBox)
{
   int i;
   int nCount;
   int nIndex;
   LPOUTLINENAME lpOutlineName;

   nCount=OutlineNameTable_GetCount((LPOUTLINENAMETABLE)lpOutlineNameTable);

   SendMessage(hListBox,WM_SETREDRAW,(WPARAM)FALSE,0L);
   for(i=0; i<nCount; i++) {
      lpOutlineName=OutlineNameTable_GetName((LPOUTLINENAMETABLE)lpOutlineNameTable,i);
      nIndex = (int)SendMessage(
            hListBox,
            LB_ADDSTRING,
            0,
            (LPARAM)(LPCSTR)lpOutlineName->m_szName
      );
      SendMessage(hListBox,LB_SETITEMDATA,(WPARAM)nIndex,(LPARAM)lpOutlineName);
   }
   SendMessage(hListBox,WM_SETREDRAW,(WPARAM)TRUE,0L);
}

/* NameDlg_AddName
 * ---------------
 *
 *      Add a name to the name table corresponding to the name dialog
 *      combo box.
 */
void NameDlg_AddName(HWND hCombo, LPOUTLINEDOC lpOutlineDoc, LPOLESTR lpszName, LPLINERANGE lplrSel)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOUTLINENAME lpOutlineName;

   lpOutlineName = OutlineApp_CreateName(lpOutlineApp);

   if (lpOutlineName) {
      OLESTRCPY(lpOutlineName->m_szName, lpszName);
      lpOutlineName->m_nStartLine = lplrSel->m_nStartLine;
      lpOutlineName->m_nEndLine = lplrSel->m_nEndLine;
      OutlineDoc_AddName(lpOutlineDoc, lpOutlineName);
   } else {
      // REVIEW: do we need error message here?
   }
}

/* NameDlg_UpdateName
 * ------------------
 *
 *      Update a name in the name table corresponding to a name in
 *      the name dialog combo box.
 */
void NameDlg_UpdateName(HWND hCombo, LPOUTLINEDOC lpOutlineDoc, int nIndex, LPOLESTR lpszName, LPLINERANGE lplrSel)
{
   LPOUTLINENAME lpOutlineName;

   lpOutlineName = (LPOUTLINENAME)SendMessage(
         hCombo,
         CB_GETITEMDATA,
         (WPARAM)nIndex,
         0L
   );

   OutlineName_SetName(lpOutlineName, lpszName);
   OutlineName_SetSel(lpOutlineName, lplrSel, TRUE /* name modified */);
   OutlineDoc_SetModified(lpOutlineDoc, TRUE, FALSE, FALSE);
}

/* NameDlg_DeleteName
 * ------------------
 *
 *      Delete a name from the name dialog combo box and corresponding
 *      name table.
 */
void NameDlg_DeleteName(HWND hCombo, LPOUTLINEDOC lpOutlineDoc, UINT nIndex)
{
   SendMessage(hCombo,CB_DELETESTRING,(WPARAM)nIndex,0L);
   OutlineDoc_DeleteName(lpOutlineDoc, nIndex);
}

/* PlaceBitmap
 * -----------
 *
 *      Places a bitmap centered in the specified control in the dialog on the
 *      specified DC.
 *
 */

PlaceBitmap(HWND hDlg, int control, HDC hDC, HBITMAP hBitmap)
{
   BITMAP bm;
   HDC hdcmem;
   HBITMAP hbmOld;
   RECT rcControl;     // Rect of dialog control
   int width, height;

   GetObject(hBitmap, sizeof(BITMAP), &bm);

   hdcmem= CreateCompatibleDC(hDC);
   hbmOld = SelectObject(hdcmem, hBitmap);

   // Get rect of control in screen coords, and translate to our dialog
   // box's coordinates
   GetWindowRect(GetDlgItem(hDlg, control), &rcControl);
   MapWindowPoints(NULL, hDlg, (LPPOINT)&rcControl, 2);

   width  = rcControl.right - rcControl.left;
   height = rcControl.bottom - rcControl.top;

   BitBlt(hDC, rcControl.left + (width - bm.bmWidth) / 2,
            rcControl.top + (height - bm.bmHeight) /2,
            bm.bmWidth, bm.bmHeight,
            hdcmem, 0, 0, SRCCOPY);

   SelectObject(hdcmem, hbmOld);
   DeleteDC(hdcmem);
   return 1;
}

/* AboutDlgProc
 * ------------
 *
 *      Dialog procedure for the About function
 */
BOOL CALLBACK EXPORT AboutDlgProc(HWND hDlg, UINT Message, WPARAM wParam, LPARAM lParam)
{
   int  narrVersion[2];
   static HBITMAP hbmLogo;
   char   szAnsiString[256];
   char   szAppName[250];

   switch(Message) {

      case WM_INITDIALOG:
         // get version number of app
         W2A (APPNAME, szAppName, 250);
         wsprintf(szAnsiString, "About %s", (LPSTR)szAppName);
         SetWindowText(hDlg, (LPCSTR)szAnsiString);
         OutlineApp_GetAppVersionNo(g_lpApp, narrVersion);
         wsprintf(szAnsiString, "%s version %d.%d", (LPSTR) APPDESC,
            narrVersion[0], narrVersion[1]);
         SetDlgItemText(hDlg, IDD_APPTEXT, (LPCSTR)szAnsiString);

         // Load bitmap for displaying later
         hbmLogo = LoadBitmap(g_lpApp->m_hInst, "LogoBitmap");
         TraceDebug(hDlg, IDD_BITMAPLOCATION);
         ShowWindow(GetDlgItem(hDlg, IDD_BITMAPLOCATION), SW_HIDE);
         break;

      case WM_PAINT:
         {
         PAINTSTRUCT ps;
         BeginPaint(hDlg, &ps);

         // Display bitmap in IDD_BITMAPLOCATION control area
         PlaceBitmap(hDlg, IDD_BITMAPLOCATION, ps.hdc, hbmLogo);
         EndPaint(hDlg, &ps);
         }
         break;

      case WM_CLOSE :
         PostMessage(hDlg, WM_COMMAND, IDOK, 0L);
         break;

      case WM_COMMAND :
         switch(wParam) {
            case IDOK:
            case IDCANCEL:
               if (hbmLogo) DeleteObject(hbmLogo);
               EndDialog(hDlg,0);
               break;
         }
         break;

      default :
         return FALSE;

   }
   return TRUE;
}


DRAGDROP.C

/*************************************************************************
**
**    OLE 2 Sample Code
**
**    dragdrop.c
**
**    This file contains the major interfaces, methods and related support
**    functions for implementing Drag/Drop. The code contained in this
**    file is used by BOTH the Container and Server (Object) versions
**    of the Outline sample code.
**    The Drag/Drop support includes the following implementation objects:
**
**    OleDoc Object
**      exposed interfaces:
**          IDropSource
**          IDropTarget
**
**    (c) Copyright Microsoft Corp. 1992 - 1997 All Rights Reserved
**
*************************************************************************/

#include "outline.h"

OLEDBGDATA

extern LPOUTLINEAPP             g_lpApp;

#if defined( USE_DRAGDROP )

/* OleDoc_QueryDrag
 * ----------------
 * Check to see if Drag operation should be initiated. A Drag operation
 * should be initiated when the mouse in either the top 10 pixels of the
 * selected list box entry or in the bottom 10 pixels of the last selected
 * item.
 */

BOOL OleDoc_QueryDrag(LPOLEDOC lpOleDoc, int y)
{
        LPLINELIST lpLL = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;
        LINERANGE LineRange;

        if ( LineList_GetSel( lpLL,  (LPLINERANGE)&LineRange) ) {
                RECT rect;

                if (!LineList_GetLineRect(lpLL,LineRange.m_nStartLine,(LPRECT)&rect))
                        return FALSE ;

                if ( rect.top <= y && y <= rect.top + DD_SEL_THRESH )
                        return TRUE;

                LineList_GetLineRect( lpLL, LineRange.m_nEndLine, (LPRECT)&rect );
                if ( rect.bottom >= y && y >= rect.bottom - DD_SEL_THRESH )
                        return TRUE;

        }

        return FALSE;
}

/* OleDoc_DoDragScroll
 * -------------------
 * Check to see if Drag scroll operation should be initiated. A Drag scroll
 * operation should be initiated when the mouse has remained in the active
 * scroll area (11 pixels frame around border of window) for a specified
 * amount of time (50ms).
 */

BOOL OleDoc_DoDragScroll(LPOLEDOC lpOleDoc, POINTL pointl)
{
        LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;
        LPLINELIST lpLL = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;
        HWND hWndListBox = LineList_GetWindow(lpLL);
        DWORD dwScrollDir = SCROLLDIR_NULL;
        DWORD dwTime = GetCurrentTime();
        int nScrollInset = lpOleApp->m_nScrollInset;
        int nScrollDelay = lpOleApp->m_nScrollDelay;
        int nScrollInterval = lpOleApp->m_nScrollInterval;
        POINT point;
        RECT rect;

        if ( lpLL->m_nNumLines == 0 )
                return FALSE;

        point.x = (int)pointl.x;
        point.y = (int)pointl.y;

        ScreenToClient( hWndListBox, &point);
        GetClientRect ( hWndListBox, (LPRECT) &rect );

        if (rect.top <= point.y && point.y<=(rect.top+nScrollInset))
                dwScrollDir = SCROLLDIR_UP;
        else if ((rect.bottom-nScrollInset) <= point.y && point.y <= rect.bottom)
                dwScrollDir = SCROLLDIR_DOWN;

        if (lpOleDoc->m_dwTimeEnterScrollArea) {

                /* cursor was already in Scroll Area */

                if (! dwScrollDir) {
                        /* cusor moved OUT of scroll area.
                        **      clear "EnterScrollArea" time.
                        */
                        lpOleDoc->m_dwTimeEnterScrollArea = 0L;
                        lpOleDoc->m_dwNextScrollTime = 0L;
                        lpOleDoc->m_dwLastScrollDir = SCROLLDIR_NULL;
                } else if (dwScrollDir != lpOleDoc->m_dwLastScrollDir) {
                        /* cusor moved into a different direction scroll area.
                        **      reset "EnterScrollArea" time to start a new 50ms delay.
                        */
                        lpOleDoc->m_dwTimeEnterScrollArea = dwTime;
                        lpOleDoc->m_dwNextScrollTime = dwTime + (DWORD)nScrollDelay;
                        lpOleDoc->m_dwLastScrollDir = dwScrollDir;
                } else if (dwTime  && dwTime >= lpOleDoc->m_dwNextScrollTime) {
                        LineList_Scroll ( lpLL, dwScrollDir );  // Scroll doc NOW
                        lpOleDoc->m_dwNextScrollTime = dwTime + (DWORD)nScrollInterval;
                }
        } else {
                if (dwScrollDir) {
                        /* cusor moved INTO a scroll area.
                        **      reset "EnterScrollArea" time to start a new 50ms delay.
                        */
                        lpOleDoc->m_dwTimeEnterScrollArea = dwTime;
                        lpOleDoc->m_dwNextScrollTime = dwTime + (DWORD)nScrollDelay;
                        lpOleDoc->m_dwLastScrollDir = dwScrollDir;
                }
        }

        return (dwScrollDir ? TRUE : FALSE);
}

/* OleDoc_QueryDrop
** ----------------
**    Check if the desired drop operation (identified by the given key
**    state) is possible at the current mouse position (pointl).
*/
BOOL OleDoc_QueryDrop (
        LPOLEDOC        lpOleDoc,
        DWORD           grfKeyState,
        POINTL          pointl,
        BOOL            fDragScroll,
        LPDWORD         lpdwEffect
)
{
        LPLINELIST lpLL   = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;
        LINERANGE  linerange;
        short      nIndex = LineList_GetLineIndexFromPointl( lpLL, pointl );
        DWORD      dwScrollEffect = 0L;
        DWORD      dwOKEffects = *lpdwEffect;

        /* check if the cursor is in the active scroll area, if so need the
        **    special scroll cursor.
        */
        if (fDragScroll)
                dwScrollEffect = DROPEFFECT_SCROLL;

        /* if we have already determined that the source does NOT have any
        **    acceptable data for us, the return NO-DROP
        */
        if (! lpOleDoc->m_fCanDropCopy && ! lpOleDoc->m_fCanDropLink)
                goto dropeffect_none;

        /* if the Drag/Drop is local to our document, we can NOT accept a
        **    drop in the middle of the current selection (which is the exact
        **    data that is being dragged!).
        */
        if (lpOleDoc->m_fLocalDrag) {
                LineList_GetSel( lpLL, (LPLINERANGE)&linerange );

                if (linerange.m_nStartLine <= nIndex && nIndex<linerange.m_nEndLine)
                        goto dropeffect_none;
        }

        /* NOTE: determine what type of drop should be performed given
        **    the current modifier key state. we rely on the standard
        **    interpretation of the modifier keys:
        **          no modifier -- DROPEFFECT_MOVE or whatever is allowed by src
        **          SHIFT       -- DROPEFFECT_MOVE
        **          CTRL        -- DROPEFFECT_COPY
        **          CTRL-SHIFT  -- DROPEFFECT_LINK
        */

        *lpdwEffect = OleStdGetDropEffect(grfKeyState);
        if (*lpdwEffect == 0) {
                // No modifier keys given. Try in order MOVE, COPY, LINK.
                if ((DROPEFFECT_MOVE & dwOKEffects) && lpOleDoc->m_fCanDropCopy)
                        *lpdwEffect = DROPEFFECT_MOVE;
                else if ((DROPEFFECT_COPY & dwOKEffects) && lpOleDoc->m_fCanDropCopy)
                        *lpdwEffect = DROPEFFECT_COPY;
                else if ((DROPEFFECT_LINK & dwOKEffects) && lpOleDoc->m_fCanDropLink)
                        *lpdwEffect = DROPEFFECT_LINK;
                else
                        goto dropeffect_none;
        } else {
                /* NOTE: we should check if the drag source application allows
                **    the desired drop effect.
                */
                if (!(*lpdwEffect & dwOKEffects))
                        goto dropeffect_none;

                if ((*lpdwEffect == DROPEFFECT_COPY || *lpdwEffect == DROPEFFECT_MOVE)
                                && ! lpOleDoc->m_fCanDropCopy)
                        goto dropeffect_none;

                if (*lpdwEffect == DROPEFFECT_LINK && ! lpOleDoc->m_fCanDropLink)
                        goto dropeffect_none;
        }

        *lpdwEffect |= dwScrollEffect;
        return TRUE;

dropeffect_none:

        *lpdwEffect = DROPEFFECT_NONE;
        return FALSE;
}

/* OleDoc_DoDragDrop
 * -----------------
 *  Actually perform a drag/drop operation with the current selection in
 *      the source document (lpSrcOleDoc).
 *
 *  returns the result effect of the drag/drop operation:
 *      DROPEFFECT_NONE,
 *      DROPEFFECT_COPY,
 *      DROPEFFECT_MOVE, or
 *      DROPEFFECT_LINK
 */

DWORD OleDoc_DoDragDrop (LPOLEDOC lpSrcOleDoc)
{
        LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
        LPOLEAPP     lpOleApp = (LPOLEAPP)g_lpApp;
        LPOUTLINEDOC lpSrcOutlineDoc = (LPOUTLINEDOC)lpSrcOleDoc;
        LPOLEDOC lpDragDoc;
        LPLINELIST  lpSrcLL =
                                        (LPLINELIST)&((LPOUTLINEDOC)lpSrcOleDoc)->m_LineList;
        DWORD       dwEffect     = 0;
        LPLINE      lplineStart, lplineEnd;
        LINERANGE   linerange;
        BOOL        fPrevEnable1;
        BOOL        fPrevEnable2;
        HRESULT     hrErr;

        OLEDBG_BEGIN3("OleDoc_DoDragDrop\r\n")

        /* squirrel away a copy of the current selection to the ClipboardDoc */
        lpDragDoc = (LPOLEDOC)OutlineDoc_CreateDataTransferDoc(lpSrcOutlineDoc);
        if ( ! lpDragDoc) {
                dwEffect = DROPEFFECT_NONE;
                goto error;
        }

        /* NOTE: initially the DataTransferDoc is created with a 0 ref
        **    count. in order to have a stable Doc object during the drag/
        **    drop operation, we intially AddRef the Doc ref cnt and later
        **    Release it. This AddRef is artificial; it is simply
        **    done to guarantee that a harmless QueryInterface followed by
        **    a Release does not inadvertantly force our object to destroy
        **    itself prematurely.
        */
        OleDoc_AddRef(lpDragDoc);

        //NOTE: we need to keep the LPLINE pointers
        //      rather than the indexes because the
        //      indexes will not be the same after the
        //      drop occurs  -- the drop adds new
        //      entries to the list thereby shifting
        //      the whole list.
        LineList_GetSel( lpSrcLL, (LPLINERANGE)&linerange );
        lplineStart = LineList_GetLine ( lpSrcLL, linerange.m_nStartLine );
        lplineEnd   = LineList_GetLine ( lpSrcLL, linerange.m_nEndLine );

        if (! lplineStart || ! lplineEnd) {
                dwEffect = DROPEFFECT_NONE;
                goto error;
        }

        lpSrcOleDoc->m_fLocalDrop     = FALSE;
        lpSrcOleDoc->m_fLocalDrag     = TRUE;

        /* NOTE: it is VERY important to DISABLE the Busy/NotResponding
        **    dialogs BEFORE calling DoDragDrop. The DoDragDrop API starts
        **    a mouse capture modal loop. if the Busy/NotResponding comes
        **    up in the middle of this loop (eg. if one of the remoted
        **    calls like IDropTarget::DragOver call takes a long time, then
        **    the NotResponding dialog may want to come up), then the mouse
        **    capture is lost by OLE and things can get messed up.
        */
        OleApp_DisableBusyDialogs(lpOleApp, &fPrevEnable1, &fPrevEnable2);

        OLEDBG_BEGIN2("DoDragDrop called\r\n")
        hrErr = DoDragDrop ( (LPDATAOBJECT) &lpDragDoc->m_DataObject,
                                 (LPDROPSOURCE) &lpSrcOleDoc->m_DropSource,
                                 DROPEFFECT_MOVE | DROPEFFECT_COPY | DROPEFFECT_LINK,
                                 (LPDWORD) &dwEffect
        );
        OLEDBG_END2

        // re-enable the Busy/NotResponding dialogs
        OleApp_EnableBusyDialogs(lpOleApp, fPrevEnable1, fPrevEnable2);

#if defined( _DEBUG )
        if (FAILED(hrErr))
                OleDbgOutHResult("DoDragDrop returned", hrErr);
#endif
        lpSrcOleDoc->m_fLocalDrag     = FALSE;

        /* NOTE: we need to guard the lifetime of our lpSrcOleDoc
        **    object while we are deleting the lines that were drag
        **    moved. it is possible that deleting these lines could
        **    cause the deletion of a PseudoObj. the deletion of a
        **    PseudoObj will cause the Doc to be unlock
        **    (CoLockObjectExternal(FALSE,TRUE) called). each PseudoObj
        **    holds a strong lock on the Doc. It is always best to have
        **    a memory guard around such critical sections of code. in
        **    this case, it is particularly important if we were an
        **    in-place active server and this drag ended up in a drop
        **    in our outer container. this scenario will lead to a
        **    crash if we do not hold this memory guard.
        */
        OleDoc_Lock(lpSrcOleDoc, TRUE, 0);

        /* if after the Drag/Drop modal (mouse capture) loop is finished
        **    and a drag MOVE operation was performed, then we must delete
        **    the lines that were dragged.
        */
        if ( hrErr == DRAGDROP_S_DROP
                        && (dwEffect & DROPEFFECT_MOVE) != 0 ) {

                int i,j,iEnd;
                LPLINE lplineFocusLine;

                /* disable repainting and sending data changed notifications
                **    until after all lines have been deleted.
                */
                OutlineDoc_SetRedraw ( (LPOUTLINEDOC)lpSrcOleDoc, FALSE );

                /* if the drop was local to our document, then we must take
                **    into account that the line indices of the original source
                **    of the drag could have shifted because the dropped lines
                **    have been inserted into our document. thus we will
                **    re-determine the source line indices.
                */
                if (lpSrcOleDoc->m_fLocalDrop) {
                        i = LineList_GetFocusLineIndex ( lpSrcLL );
                        lplineFocusLine = LineList_GetLine ( lpSrcLL, i );
                }

                for ( i = j = LineList_GetLineIndex(lpSrcLL,lplineStart ) ,
                          iEnd  = LineList_GetLineIndex(lpSrcLL,lplineEnd ) ;
                          i <= iEnd ;
                          i++
                ) OutlineDoc_DeleteLine ((LPOUTLINEDOC)lpSrcOleDoc, j );

                LineList_RecalcMaxLineWidthInHimetric(lpSrcLL, 0);

                if (lpSrcOleDoc->m_fLocalDrop) {
                        i = LineList_GetLineIndex ( lpSrcLL, lplineFocusLine );
                        LineList_SetFocusLine ( lpSrcLL, (WORD)i );
                }

                OutlineDoc_SetRedraw ( (LPOUTLINEDOC)lpSrcOleDoc, TRUE );

                /* if it is a local Drag/Drop move, we need to balance the
                **    SetRedraw(FALSE) call that was made in the implementation
                **    of IDropTarget::Drop.
                */
                if (lpSrcOleDoc->m_fLocalDrop)
                        OutlineDoc_SetRedraw ( (LPOUTLINEDOC)lpSrcOleDoc, TRUE );

                LineList_ForceRedraw ( lpSrcLL, FALSE );
        }

        OleDoc_Release(lpDragDoc);  // rel artificial AddRef above
        OleDoc_Lock(lpSrcOleDoc, FALSE, FALSE);  // unlock artificial lock guard

        OLEDBG_END3
        return dwEffect;

error:
        OLEDBG_END3
        return dwEffect;
}

/*************************************************************************
** OleDoc::IDropSource interface implementation
*************************************************************************/

STDMETHODIMP OleDoc_DropSource_QueryInterface(
        LPDROPSOURCE            lpThis,
        REFIID                  riid,
        LPVOID FAR*             lplpvObj
)
{
        LPOLEDOC lpOleDoc = ((struct CDocDropSourceImpl FAR*)lpThis)->lpOleDoc;

        return OleDoc_QueryInterface(lpOleDoc, riid, lplpvObj);
}

STDMETHODIMP_(ULONG) OleDoc_DropSource_AddRef( LPDROPSOURCE lpThis )
{
        LPOLEDOC lpOleDoc = ((struct CDocDropSourceImpl FAR*)lpThis)->lpOleDoc;

        OleDbgAddRefMethod(lpThis, "IDropSource");

        return OleDoc_AddRef(lpOleDoc);
}

STDMETHODIMP_(ULONG) OleDoc_DropSource_Release ( LPDROPSOURCE lpThis)
{
        LPOLEDOC lpOleDoc = ((struct CDocDropSourceImpl FAR*)lpThis)->lpOleDoc;

        OleDbgReleaseMethod(lpThis, "IDropSource");

        return OleDoc_Release(lpOleDoc);
}

STDMETHODIMP    OleDoc_DropSource_QueryContinueDrag (
        LPDROPSOURCE            lpThis,
        BOOL                    fEscapePressed,
        DWORD                   grfKeyState
){
        if (fEscapePressed)
                return DRAGDROP_S_CANCEL;
        else if (!(grfKeyState & MK_LBUTTON))
                return DRAGDROP_S_DROP;
        else
                return NOERROR;
}

STDMETHODIMP    OleDoc_DropSource_GiveFeedback (
        LPDROPSOURCE            lpThis,
        DWORD                   dwEffect
)
{
        // Tell OLE to use the standard drag/drop feedback cursors
        return DRAGDROP_S_USEDEFAULTCURSORS;

#if defined( IF_SPECIAL_DD_CURSORS_NEEDED )
        switch (dwEffect) {

                case DROPEFFECT_NONE:
                        SetCursor ( ((LPOLEAPP)g_lpApp)->m_hcursorDragNone );
                        break;

                case DROPEFFECT_COPY:
                        SetCursor ( ((LPOLEAPP)g_lpApp)->m_hcursorDragCopy );
                        break;

                case DROPEFFECT_MOVE:
                        SetCursor ( ((LPOLEAPP)g_lpApp)->m_hcursorDragMove );
                        break;

                case DROPEFFECT_LINK:
                        SetCursor ( ((LPOLEAPP)g_lpApp)->m_hcursorDragLink );
                        break;

        }

        return NOERROR;
#endif

}

/*************************************************************************
** OleDoc::IDropTarget interface implementation
*************************************************************************/

STDMETHODIMP OleDoc_DropTarget_QueryInterface(
                LPDROPTARGET        lpThis,
                REFIID              riid,
                LPVOID FAR*         lplpvObj
)
{
        LPOLEDOC lpOleDoc = ((struct CDocDropTargetImpl FAR*)lpThis)->lpOleDoc;

        return OleDoc_QueryInterface(lpOleDoc, riid, lplpvObj);
}

STDMETHODIMP_(ULONG) OleDoc_DropTarget_AddRef(LPDROPTARGET lpThis)
{
        LPOLEDOC lpOleDoc = ((struct CDocDropTargetImpl FAR*)lpThis)->lpOleDoc;

        OleDbgAddRefMethod(lpThis, "IDropTarget");

        return OleDoc_AddRef(lpOleDoc);
}

STDMETHODIMP_(ULONG) OleDoc_DropTarget_Release ( LPDROPTARGET lpThis)
{
        LPOLEDOC lpOleDoc = ((struct CDocDropTargetImpl FAR*)lpThis)->lpOleDoc;

        OleDbgReleaseMethod(lpThis, "IDropTarget");

        return OleDoc_Release(lpOleDoc);
}

STDMETHODIMP    OleDoc_DropTarget_DragEnter (
        LPDROPTARGET            lpThis,
        LPDATAOBJECT            lpDataObj,
        DWORD                   grfKeyState,
        POINTL                  pointl,
        LPDWORD                 lpdwEffect
)
{
        LPOLEAPP   lpOleApp = (LPOLEAPP)g_lpApp;
        LPOLEDOC   lpOleDoc = ((struct CDocDropTargetImpl FAR*)lpThis)->lpOleDoc;
        LPLINELIST lpLL     = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;
        BOOL       fDragScroll;

        // artificial AddRef in case object is destroyed during call
        OleDoc_DropTarget_AddRef(lpThis);

        OLEDBG_BEGIN2("OleDoc_DropTarget_DragEnter\r\n")

        lpOleDoc->m_fDragLeave              = FALSE;
        lpOleDoc->m_dwTimeEnterScrollArea   = 0;
        lpOleDoc->m_dwLastScrollDir         = SCROLLDIR_NULL;

        /* Determine if the drag source data object offers a data format
        **    that we can copy and/or link to.
        */

        lpOleDoc->m_fCanDropCopy = OleDoc_QueryPasteFromData(
                        lpOleDoc,
                        lpDataObj,
                        FALSE   /* fLink */
        );

        lpOleDoc->m_fCanDropLink = OleDoc_QueryPasteFromData(
                        lpOleDoc,
                        lpDataObj,
                        TRUE   /* fLink */
        );

        fDragScroll = OleDoc_DoDragScroll ( lpOleDoc, pointl );

        if (OleDoc_QueryDrop(lpOleDoc,grfKeyState,pointl,fDragScroll,lpdwEffect))
                LineList_SetDragOverLineFromPointl( lpLL, pointl );

        OLEDBG_END2

        // release artificial AddRef
        OleDoc_DropTarget_Release(lpThis);

        return NOERROR;
}

STDMETHODIMP OleDoc_DropTarget_DragOver (
        LPDROPTARGET            lpThis,
        DWORD                   grfKeyState,
        POINTL                  pointl,
        LPDWORD                 lpdwEffect
)
{
        LPOLEAPP   lpOleApp = (LPOLEAPP)g_lpApp;
        LPOLEDOC   lpOleDoc = ((struct CDocDropTargetImpl FAR*)lpThis)->lpOleDoc;
        LPLINELIST lpLL   = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;
        BOOL       fDragScroll;

        // artificial AddRef in case object is destroyed during call
        OleDoc_DropTarget_AddRef(lpThis);

        fDragScroll = OleDoc_DoDragScroll ( lpOleDoc, pointl );

        if (OleDoc_QueryDrop(lpOleDoc,grfKeyState, pointl,fDragScroll,lpdwEffect))
                LineList_SetDragOverLineFromPointl( lpLL, pointl );
        else
                LineList_RestoreDragFeedback( lpLL );

        // release artificial AddRef
        OleDoc_DropTarget_Release(lpThis);
        return NOERROR;
}

STDMETHODIMP    OleDoc_DropTarget_DragLeave ( LPDROPTARGET lpThis)
{
        LPOLEDOC   lpOleDoc = ((struct CDocDropTargetImpl FAR*)lpThis)->lpOleDoc;
        LPLINELIST lpLL     = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;

        // artificial AddRef in case object is destroyed during call
        OleDoc_DropTarget_AddRef(lpThis);

        OLEDBG_BEGIN2("OleDoc_DropTarget_DragLeave\r\n")

        lpOleDoc->m_fDragLeave = TRUE;

        LineList_RestoreDragFeedback( lpLL );

        OLEDBG_END2

        // release artificial AddRef
        OleDoc_DropTarget_Release(lpThis);
        return NOERROR;

}

STDMETHODIMP    OleDoc_DropTarget_Drop (
        LPDROPTARGET            lpThis,
        LPDATAOBJECT            lpDataObj,
        DWORD                   grfKeyState,
        POINTL                  pointl,
        LPDWORD                 lpdwEffect
)
{
        LPOLEDOC   lpOleDoc = ((struct CDocDropTargetImpl FAR*)lpThis)->lpOleDoc;
        LPLINELIST lpLL     = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;

        // artificial AddRef in case object is destroyed during call
        OleDoc_DropTarget_AddRef(lpThis);

        OLEDBG_BEGIN2("OleDoc_DropTarget_Drop\r\n")

        lpOleDoc->m_fDragLeave = TRUE;
        lpOleDoc->m_fLocalDrop = TRUE;

        LineList_RestoreDragFeedback( lpLL );
        SetFocus( LineList_GetWindow( lpLL) );

        if (OleDoc_QueryDrop(lpOleDoc, grfKeyState, pointl, FALSE, lpdwEffect)) {
                BOOL fLink     = (*lpdwEffect == DROPEFFECT_LINK);
                int iFocusLine = LineList_GetFocusLineIndex( lpLL );
                BOOL fStatus;

                OutlineDoc_SetRedraw ( (LPOUTLINEDOC)lpOleDoc, FALSE );
                LineList_SetFocusLineFromPointl ( lpLL, pointl );

                fStatus = OleDoc_PasteFromData(
                                lpOleDoc,
                                lpDataObj,
                                lpOleDoc->m_fLocalDrag, /* data source is local to app */
                                fLink
                );

                // if drop was unsuccessfull, restore the original focus line
                if (! fStatus)
                        LineList_SetFocusLine( lpLL, (WORD)iFocusLine );

#if defined( INPLACE_CNTR )
                {
                        LPCONTAINERDOC lpContainerDoc = (LPCONTAINERDOC)lpOleDoc;

                        /* NOTE: if there is currently a UIActive OLE object,
                        **    then we must tell it to UIDeactivate after
                        **    the drop has completed.
                        */
                        if (lpContainerDoc->m_lpLastUIActiveLine) {
                                ContainerLine_UIDeactivate(
                                                lpContainerDoc->m_lpLastUIActiveLine);
                        }
                }
#endif

#if defined( INPLACE_SVR )
                {
                        /* NOTE: if the drop was into a in-place visible
                        **    (in-place active but NOT UIActive object), then we
                        **    want to UIActivate the object after the drop is
                        **    complete.
                        */
                        ServerDoc_UIActivate((LPSERVERDOC) lpOleDoc);
                }
#endif

                /* if it is a local Drag/Drop move, don't enable redraw.
                **    after the source is done deleting the moved lines, it
                **    will re-enable redraw
                */
                if (! (lpOleDoc->m_fLocalDrag
                        && (*lpdwEffect & DROPEFFECT_MOVE) != 0 ))
                        OutlineDoc_SetRedraw ( (LPOUTLINEDOC)lpOleDoc, TRUE );
        }

        OLEDBG_END2
        // release artificial AddRef
        OleDoc_DropTarget_Release(lpThis);
        return NOERROR;
}

#endif  // USE_DRAGDROP


FRAMETLS.C

/*************************************************************************
**
**    OLE 2 Server Sample Code
**
**    frametls.c
**
**    This file contains all FrameTools methods and related support
**    functions. The FrameTools object is an encapsulation of the apps
**    formula bar and button bar.
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#include "outline.h"

OLEDBGDATA

/* private function prototype */
static void Bar_Move(LPBAR lpbar, LPRECT lprcClient, LPRECT lprcPopup);
static void FB_ResizeEdit(LPBAR lpbar);

extern LPOUTLINEAPP g_lpApp;
extern RECT g_rectNull;

/*
 * FrameToolsRegisterClass
 *
 * Purpose:
 *  Register the popup toolbar window class
 *
 * Parameters:
 *  hInst           Process instance
 *
 * Return Value:
 *  TRUE            if successful
 *  FALSE           if failed
 *
 */
BOOL FrameToolsRegisterClass(HINSTANCE hInst)
{
        WNDCLASS wc;

        // Register Tool Palette Class
        wc.style = CS_BYTEALIGNWINDOW;
        wc.lpfnWndProc = FrameToolsWndProc;
        wc.cbClsExtra = 0;
        wc.cbWndExtra = 4;
        wc.hInstance = hInst;
        wc.hIcon = NULL;
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName = NULL;
        wc.lpszClassName = CLASS_PALETTE;

        if (!RegisterClass(&wc))
                return FALSE;
        else
                return TRUE;
}

static BOOL FrameTools_CreatePopupPalette(LPFRAMETOOLS lpft, HWND hWndFrame)
{
        if (lpft->m_hWndPopupPalette)
                DestroyWindow(lpft->m_hWndPopupPalette);

        lpft->m_hWndPopupPalette = CreateWindow(
                CLASS_PALETTE,
                "Tool Palette",
                WS_POPUP | WS_CAPTION | WS_CLIPCHILDREN,
                CW_USEDEFAULT, 0, 0, 0,
                hWndFrame,
                (HMENU)NULL,
                g_lpApp->m_hInst,
                0L
        );

        if (!lpft->m_hWndPopupPalette)
                return FALSE;

        SetWindowLong(lpft->m_hWndPopupPalette, 0, (LONG)lpft);
        return TRUE;
}

/*
 * FrameTools_Init
 *
 * Purpose:
 *  Init and create the toolbar
 *
 * Parameters:
 *  lpft            FrameTools object
 *  hWndParent      The window which owns the toolbar
 *  hInst           Process instance
 *
 * Return Value:
 *  TRUE            if successful
 *  FALSE           if failed
 *
 */
BOOL FrameTools_Init(LPFRAMETOOLS lpft, HWND hWndParent, HINSTANCE hInst)
{
        RECT        rc;
        UINT        uPos;
        UINT        dx;
        UINT        dy;

        if (!lpft || !hWndParent || !hInst)
                return FALSE;

        //Get BTTNCUR's display information
        UIToolConfigureForDisplay(&lpft->m_tdd);

        dx=lpft->m_tdd.cxButton;
        dy=lpft->m_tdd.cyButton;

        // 15 is calculated from the total number of buttons and separators
        lpft->m_uPopupWidth = dx * 15;

        lpft->m_hWndApp = hWndParent;
        lpft->m_ButtonBar.m_nState = BARSTATE_TOP;
        lpft->m_FormulaBar.m_nState = BARSTATE_TOP;
        lpft->m_fInFormulaBar = FALSE;

        lpft->m_fToolsDisabled = FALSE;

        lpft->m_ButtonBar.m_uHeight = lpft->m_tdd.cyBar;
        lpft->m_FormulaBar.m_uHeight = lpft->m_tdd.cyBar;

        //Get our image bitmaps for the display type we're on
        if (72 == lpft->m_tdd.uDPI)
                lpft->m_hBmp = LoadBitmap(hInst, (LPCSTR)"Image72");
        if (96 == lpft->m_tdd.uDPI)
                lpft->m_hBmp = LoadBitmap(hInst, (LPCSTR)"Image96");
        if (120 == lpft->m_tdd.uDPI)
                lpft->m_hBmp = LoadBitmap(hInst, (LPCSTR)"Image120");

        if (!lpft->m_hBmp)
                return FALSE;

        /* Create Popup Tool Palette window */
        lpft->m_hWndPopupPalette = NULL;
        if (! FrameTools_CreatePopupPalette(lpft, hWndParent))
                return FALSE;

        uPos = 0;
        //Create the GizmoBar and the client area window
        GetClientRect(hWndParent, &rc);
        lpft->m_ButtonBar.m_hWnd = CreateWindow(
                CLASS_GIZMOBAR,
                "ButtonBar",
                WS_CHILD | WS_VISIBLE,
                0, 0, rc.right-rc.left, lpft->m_tdd.cyBar,
                hWndParent,
                (HMENU)IDC_GIZMOBAR,
                hInst,
                0L
        );

        if (!lpft->m_ButtonBar.m_hWnd)
                return FALSE;

        SendMessage(lpft->m_ButtonBar.m_hWnd, WM_SETREDRAW, FALSE, 0L);

        //File new, open, save, print
        GBGizmoAdd(lpft->m_ButtonBar.m_hWnd, GIZMOTYPE_BUTTONCOMMAND, uPos++, IDM_F_NEW, dx, dy, NULL, NULL, TOOLIMAGE_FILENEW, GIZMO_NORMAL);
        GBGizmoAdd(lpft->m_ButtonBar.m_hWnd, GIZMOTYPE_BUTTONCOMMAND, uPos++, IDM_F_OPEN, dx, dy, NULL, NULL, TOOLIMAGE_FILEOPEN, GIZMO_NORMAL);
        GBGizmoAdd(lpft->m_ButtonBar.m_hWnd, GIZMOTYPE_BUTTONCOMMAND, uPos++, IDM_F_SAVE, dx, dy, NULL, NULL, TOOLIMAGE_FILESAVE, GIZMO_NORMAL);
        GBGizmoAdd(lpft->m_ButtonBar.m_hWnd, GIZMOTYPE_BUTTONCOMMAND, uPos++, IDM_F_PRINT, dx, dy, NULL, NULL, TOOLIMAGE_FILEPRINT, GIZMO_NORMAL);

        // separator
        GBGizmoAdd(lpft->m_ButtonBar.m_hWnd, GIZMOTYPE_SEPARATOR, uPos++, 0, dx/2, dy, NULL, NULL, 0, GIZMO_NORMAL);

        // Edit cut, copy, paste
        GBGizmoAdd(lpft->m_ButtonBar.m_hWnd, GIZMOTYPE_BUTTONCOMMAND, uPos++, IDM_E_CUT, dx, dy, NULL, NULL, TOOLIMAGE_EDITCUT, GIZMO_NORMAL);
        GBGizmoAdd(lpft->m_ButtonBar.m_hWnd, GIZMOTYPE_BUTTONCOMMAND, uPos++, IDM_E_COPY, dx, dy, NULL, NULL, TOOLIMAGE_EDITCOPY, GIZMO_NORMAL);
        GBGizmoAdd(lpft->m_ButtonBar.m_hWnd, GIZMOTYPE_BUTTONCOMMAND, uPos++, IDM_E_PASTE, dx, dy, NULL, NULL, TOOLIMAGE_EDITPASTE, GIZMO_NORMAL);

        // separator
        GBGizmoAdd(lpft->m_ButtonBar.m_hWnd, GIZMOTYPE_SEPARATOR, uPos++, 0, dx/2, dy, NULL, NULL, 0, GIZMO_NORMAL);

        // Line indent, unindent
        GBGizmoAdd(lpft->m_ButtonBar.m_hWnd, GIZMOTYPE_BUTTONCOMMAND, uPos++, IDM_L_UNINDENTLINE, dx, dy, NULL, lpft->m_hBmp, IDB_UNINDENTLINE, GIZMO_NORMAL);
        GBGizmoAdd(lpft->m_ButtonBar.m_hWnd, GIZMOTYPE_BUTTONCOMMAND, uPos++, IDM_L_INDENTLINE, dx, dy, NULL, lpft->m_hBmp, IDB_INDENTLINE, GIZMO_NORMAL);

        // separator
        GBGizmoAdd(lpft->m_ButtonBar.m_hWnd, GIZMOTYPE_SEPARATOR, uPos++, 0, dx/2, dy, NULL, NULL, 0, GIZMO_NORMAL);

        // Help
        GBGizmoAdd(lpft->m_ButtonBar.m_hWnd, GIZMOTYPE_BUTTONCOMMAND, uPos++, IDM_H_ABOUT, dx, dy, NULL, NULL, TOOLIMAGE_HELP, GIZMO_NORMAL);

        SendMessage(lpft->m_ButtonBar.m_hWnd, WM_SETREDRAW, TRUE, 0L);

        uPos = 0;
        lpft->m_FormulaBar.m_hWnd = CreateWindow(
                CLASS_GIZMOBAR,
                "FormulaBar",
                WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS,
                0, lpft->m_tdd.cyBar, rc.right-rc.left, lpft->m_tdd.cyBar,
                hWndParent,
                (HMENU)IDC_FORMULABAR,
                hInst,
                0L
        );

        if (!lpft->m_FormulaBar.m_hWnd)
                return FALSE;

        SendMessage(lpft->m_FormulaBar.m_hWnd, WM_SETREDRAW, FALSE, 0L);

        // Line add line
        GBGizmoAdd(lpft->m_FormulaBar.m_hWnd, GIZMOTYPE_BUTTONCOMMAND, uPos++, IDM_L_ADDLINE, dx, dy, NULL, lpft->m_hBmp, IDB_ADDLINE, GIZMO_NORMAL);

        // separator
        GBGizmoAdd(lpft->m_FormulaBar.m_hWnd, GIZMOTYPE_SEPARATOR, uPos++, 0, dx/2, dy, NULL, NULL, 0, GIZMO_NORMAL);

        // Line edit line, Cancel
        GBGizmoAdd(lpft->m_FormulaBar.m_hWnd, GIZMOTYPE_BUTTONCOMMAND, uPos++, IDM_L_EDITLINE, dx, dy, NULL, lpft->m_hBmp, IDB_EDITLINE, GIZMO_NORMAL);
        GBGizmoAdd(lpft->m_FormulaBar.m_hWnd, GIZMOTYPE_BUTTONCOMMAND, uPos++, IDM_FB_CANCEL, dx, dy, NULL, lpft->m_hBmp, IDB_CANCEL, GIZMO_NORMAL);

        // separator
        GBGizmoAdd(lpft->m_FormulaBar.m_hWnd, GIZMOTYPE_SEPARATOR, uPos++, 0, dx/2, dy, NULL, NULL, 0, GIZMO_NORMAL);

        // Edit control for line input
        GBGizmoAdd(lpft->m_FormulaBar.m_hWnd, GIZMOTYPE_EDIT, uPos++, IDM_FB_EDIT, dx*10, lpft->m_tdd.cyBar-5, NULL, NULL, 0, GIZMO_NORMAL);

        SendMessage(lpft->m_FormulaBar.m_hWnd, WM_SETREDRAW, TRUE, 0L);

        // Limit the text lenght of edit control
        GBGizmoSendMessage(lpft->m_FormulaBar.m_hWnd, IDM_FB_EDIT, EM_LIMITTEXT,
                (WPARAM)MAXSTRLEN, 0L);

        //Set the GizmoBar's associate to be this client window
        GBHwndAssociateSet(lpft->m_ButtonBar.m_hWnd, hWndParent);

        //Set the FormulaBar's associate to be this client window
        GBHwndAssociateSet(lpft->m_FormulaBar.m_hWnd, hWndParent);

        return TRUE;
}

void FrameTools_AttachToFrame(LPFRAMETOOLS lpft, HWND hWndFrame)
{
        if (! lpft)
                return;

        if (hWndFrame == NULL)
                hWndFrame = OutlineApp_GetFrameWindow((LPOUTLINEAPP)g_lpApp);

        if (lpft->m_hWndApp == hWndFrame)
                return;     // already have this parent frame

        lpft->m_hWndApp = hWndFrame;

        /* parent the tool bars to the frame so we can safely
        **    destroy/recreate the palette window.
        */
        SetParent(lpft->m_ButtonBar.m_hWnd, hWndFrame);
        SetParent(lpft->m_FormulaBar.m_hWnd, hWndFrame);

        // recreate popup palette so that it is owned by the hWndFrame
        FrameTools_CreatePopupPalette(lpft, hWndFrame);

        // restore the correct parent for the tool bars
        FrameTools_BB_SetState(lpft, lpft->m_ButtonBar.m_nState);
        FrameTools_FB_SetState(lpft, lpft->m_FormulaBar.m_nState);
}

void FrameTools_AssociateDoc(LPFRAMETOOLS lpft, LPOUTLINEDOC lpOutlineDoc)
{
        HWND hWnd = OutlineDoc_GetWindow(lpOutlineDoc);

        if (! lpft)
                return;

        // if no Doc is given, then associate with the App's frame window.
        if (lpOutlineDoc)
                hWnd = OutlineDoc_GetWindow(lpOutlineDoc);
        else
                hWnd = OutlineApp_GetWindow((LPOUTLINEAPP)g_lpApp);

        //Set the GizmoBar's associate to be this client window
        GBHwndAssociateSet(lpft->m_ButtonBar.m_hWnd, hWnd);

        //Set the FormulaBar's associate to be this client window
        GBHwndAssociateSet(lpft->m_FormulaBar.m_hWnd, hWnd);
}

/*
 * FrameTools_Destroy
 *
 * Purpose:
 *  Destroy the toolbar
 *
 * Parameters:
 *  lpft            FrameTools object
 *
 * Return Value:
 *  nil
 */
void FrameTools_Destroy(LPFRAMETOOLS lpft)
{
        if (!lpft)
                return;

        if (IsWindow(lpft->m_ButtonBar.m_hWnd))
                DestroyWindow(lpft->m_ButtonBar.m_hWnd);
        if (IsWindow(lpft->m_FormulaBar.m_hWnd))
                DestroyWindow(lpft->m_FormulaBar.m_hWnd);
        if (IsWindow(lpft->m_hWndPopupPalette))
                DestroyWindow(lpft->m_hWndPopupPalette);

        if (lpft->m_hBmp)
                DeleteObject(lpft->m_hBmp);
}

/*
 * FrameTools_Move
 *
 * Purpose:
 *  Move and resize the toolbar
 *
 * Parameters:
 *  lpft            FrameTools object
 *  lprc            Pointer to client rectangle
 *
 * Return Value:
 *  nil
 */
void FrameTools_Move(LPFRAMETOOLS lpft, LPRECT lprcClient)
{
        RECT rcPopup;
        LPRECT lprcPopup = (LPRECT)&rcPopup;
        int nCmdShow = SW_HIDE;

        if (!lpft || lpft->m_fToolsDisabled)
                return;

        lprcPopup->left = 0;
        lprcPopup->top = 0;
        lprcPopup->right = lpft->m_uPopupWidth;
        lprcPopup->bottom = lpft->m_ButtonBar.m_uHeight +
                        lpft->m_FormulaBar.m_uHeight;

        switch (lpft->m_ButtonBar.m_nState) {
                case BARSTATE_HIDE:
                case BARSTATE_POPUP:
                case BARSTATE_TOP:
                        Bar_Move(&lpft->m_ButtonBar, lprcClient, lprcPopup);
                        Bar_Move(&lpft->m_FormulaBar, lprcClient, lprcPopup);
                        break;

                case BARSTATE_BOTTOM:
                        Bar_Move(&lpft->m_FormulaBar, lprcClient, lprcPopup);
                        Bar_Move(&lpft->m_ButtonBar, lprcClient, lprcPopup);
                        break;
        }

        if (lprcPopup->top) {
                SetWindowPos(lpft->m_hWndPopupPalette, NULL, 0, 0, lprcPopup->right,
                                lprcPopup->top + GetSystemMetrics(SM_CYCAPTION),
                                SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | SWP_SHOWWINDOW);
        }
        else
                ShowWindow(lpft->m_hWndPopupPalette, SW_HIDE);

        FB_ResizeEdit(&lpft->m_FormulaBar);

        InvalidateRect(lpft->m_ButtonBar.m_hWnd, NULL, TRUE);
        InvalidateRect(lpft->m_FormulaBar.m_hWnd, NULL, TRUE);
}

/*
 * FrameTools_PopupTools
 *
 * Purpose:
 *  Put both formula bar and button bar in Popup Window.
 *
 * Parameters:
 *  lpft            FrameTools object
 *
 * Return Value:
 *  nil
 */
void FrameTools_PopupTools(LPFRAMETOOLS lpft)
{
        if (! lpft)
                return;

        FrameTools_BB_SetState(lpft, BARSTATE_POPUP);
        FrameTools_FB_SetState(lpft, BARSTATE_POPUP);
        FrameTools_Move(lpft, NULL);
}

/*
 * FrameTools_Enable
 *
 * Purpose:
 *  Enable/Disable(hide) all the tools of the toolbar.
 *  this will hide both the buttonbar and the
 *  formulabar independent of whether they are floating or anchored.
 *
 * Parameters:
 *  lpft            FrameTools object
 *  fEnable
 *
 * Return Value:
 *  nil
 */
void FrameTools_Enable(LPFRAMETOOLS lpft, BOOL fEnable)
{
        lpft->m_fToolsDisabled = !fEnable;
        if (lpft->m_fToolsDisabled) {
                ShowWindow(lpft->m_hWndPopupPalette, SW_HIDE);
                ShowWindow(lpft->m_ButtonBar.m_hWnd, SW_HIDE);
                ShowWindow(lpft->m_FormulaBar.m_hWnd, SW_HIDE);
        }
}

/*
 * FrameTools_EnableWindow
 *
 * Purpose:
 *  EnableWindow for all the tools of the toolbar.
 *  this enables/disables mouse and keyboard input to the tools.
 *  while a modal dialog is up, it is inportant to disable the
 *  floating tool windows.
 *  this will NOT hide any windows; it will only call EnableWindow.
 *
 * Parameters:
 *  lpft            FrameTools object
 *  fEnable
 *
 * Return Value:
 *  nil
 */
void FrameTools_EnableWindow(LPFRAMETOOLS lpft, BOOL fEnable)
{
        EnableWindow(lpft->m_hWndPopupPalette, fEnable);
        EnableWindow(lpft->m_ButtonBar.m_hWnd, fEnable);
        EnableWindow(lpft->m_FormulaBar.m_hWnd, fEnable);
}

#if defined( INPLACE_CNTR ) || defined( INPLACE_SVR )

/*
 * FrameTools_NegotiateForSpaceAndShow
 *
 * Purpose:
 *  Negotiate for space for the toolbar tools with the given frame window.
 *  and make them visible.
 *  Negotiation steps:
 *     1. try to get enough space at top/bottom of window
 *     2. float the tools as a palette if space not available
 *
 * Parameters:
 *  lpft            FrameTools object
 *
 * Return Value:
 *  none
 */
void FrameTools_NegotiateForSpaceAndShow(
                LPFRAMETOOLS            lpft,
                LPRECT                  lprcFrameRect,
                LPOLEINPLACEFRAME       lpTopIPFrame
)
{
        BORDERWIDTHS    borderwidths;
        RECT            rectBorder;
        HRESULT         hrErr;

        if (lprcFrameRect)
                rectBorder = *lprcFrameRect;
        else {
                /* NOTE: by calling GetBorder, the server can find out the
                **    size of the frame window. it can use this information to
                **    make decisions about how to orient/organize it tools (eg.
                **    if window is taller than wide put tools vertically at
                **    left edge).
                */
                OLEDBG_BEGIN2("IOleInPlaceFrame::GetBorder called\r\n")
                hrErr = lpTopIPFrame->lpVtbl->GetBorder(
                                lpTopIPFrame,
                                (LPRECT)&rectBorder
                );
                OLEDBG_END2
        }

        /* Try SetBorderSpace() with the space that you need. If it fails then
        ** you can negotiate for space and then do the SetBorderSpace().
        */
        FrameTools_GetRequiredBorderSpace(lpft,(LPBORDERWIDTHS)&borderwidths);
        OLEDBG_BEGIN2("IOleInPlaceFrame::SetBorderSpace called\r\n")
        hrErr = lpTopIPFrame->lpVtbl->SetBorderSpace(
                        lpTopIPFrame,
                        (LPCBORDERWIDTHS)&borderwidths
        );
        OLEDBG_END2

#if defined( LATER )
        if (hrErr != NOERROR) {
                /* Frame did not give the toolsspace that we want. So negotiate */

                // REVIEW: try a different placement of the tools here

                OLEDBG_BEGIN2("IOleInPlaceFrame::RequestBorderSpace called\r\n")
                hrErr = lpTopIPFrame->lpVtbl->RequestBorderSpace(
                                lpTopIPFrame,
                                (LPCBORDERWIDTHS)&borderwidths
                );
                OLEDBG_END2

                if (hrErr == NOERROR) {
                        OLEDBG_BEGIN2("IOleInPlaceFrame::SetBorderSpace called\r\n")
                        hrErr = lpTopIPFrame->lpVtbl->SetBorderSpace(
                                        lpTopIPFrame,
                                        (LPCBORDERWIDTHS)&borderwidths
                        );
                        OLEDBG_END2
                }
        }
#endif

        if (hrErr == NOERROR) {
                FrameTools_Move(lpft, (LPRECT)&rectBorder);   // we got what we wanted
        } else {
                /* We did not get tool space, so POP them up.
                /* NOTE: since we are poping up our tools, we MUST inform
                **    the top in-place frame window that we need NO tool space
                **    BUT that it should NOT put its own tools up. if we were
                **    to pass NULL instead of (0,0,0,0), then the container
                **    would have the option to leave its own tools up.
                */
                OLEDBG_BEGIN2("IOleInPlaceFrame::SetBorderSpace(NULL) called\r\n")
                hrErr = lpTopIPFrame->lpVtbl->SetBorderSpace(
                                lpTopIPFrame,
                                (LPCBORDERWIDTHS)&g_rectNull
                );
                OLEDBG_END2
                FrameTools_PopupTools(lpft);
        }
}

#endif  // INPLACE_CNTR || INPLACE_SVR

/*
 * FrameTools_GetRequiredBorderSpace
 *
 * Purpose:
 *  Calculate the desired space for the toolbar tools.
 *
 * Parameters:
 *  lpft            FrameTools object
 *  lpBorderWidths  Widths required at top,bottom,left,right
 *
 * Return Value:
 *  nil
 */
void FrameTools_GetRequiredBorderSpace(LPFRAMETOOLS lpft, LPBORDERWIDTHS lpBorderWidths)
{
        *lpBorderWidths = g_rectNull;

        switch (lpft->m_ButtonBar.m_nState) {
                case BARSTATE_TOP:
                        lpBorderWidths->top += lpft->m_ButtonBar.m_uHeight;
                        break;

                case BARSTATE_BOTTOM:
                        lpBorderWidths->bottom += lpft->m_ButtonBar.m_uHeight;
                        break;
        }

        switch (lpft->m_FormulaBar.m_nState) {
                case BARSTATE_TOP:
                        lpBorderWidths->top += lpft->m_FormulaBar.m_uHeight;
                        break;

                case BARSTATE_BOTTOM:
                        lpBorderWidths->bottom += lpft->m_FormulaBar.m_uHeight;
                        break;
        }
}

/*
 * FrameTools_UpdateButtons
 *
 * Purpose:
 *  Enable/disable individual buttons of the toolbar according to the
 *  state of the app
 *
 * Parameters:
 *  lpft            FrameTools object
 *
 * Return Value:
 *  nil
 */
void FrameTools_UpdateButtons(LPFRAMETOOLS lpft, LPOUTLINEDOC lpOutlineDoc)
{
        BOOL            fEnable;

#if defined( OLE_VERSION )
        LPDATAOBJECT    lpClipboardDataObj;
        HRESULT         hrErr;
        LPOLEAPP        lpOleApp = (LPOLEAPP)g_lpApp;
        BOOL            fPrevEnable1;
        BOOL            fPrevEnable2;
#endif

        if (!lpft)
                return;

#if defined( INPLACE_CNTR )
        {
                LPCONTAINERDOC lpContainerDoc = (LPCONTAINERDOC)lpOutlineDoc;
                if (lpContainerDoc->m_lpLastUIActiveLine &&
                        lpContainerDoc->m_lpLastUIActiveLine->m_fUIActive) {

                        /* if there is a UIActive object, then we should disable
                        **    all of our "active editor" commands. we should enable
                        **    only those commands that are "workspace" commands.
                        */
                        if (lpft->m_FormulaBar.m_nState != BARSTATE_HIDE) {

                                GBGizmoEnable(lpft->m_FormulaBar.m_hWnd,IDM_L_EDITLINE,FALSE);
                                GBGizmoEnable(lpft->m_FormulaBar.m_hWnd,IDM_L_ADDLINE,FALSE);
                                GBGizmoEnable(lpft->m_FormulaBar.m_hWnd,IDM_FB_CANCEL,FALSE);
                                GBGizmoEnable(lpft->m_FormulaBar.m_hWnd,IDM_L_EDITLINE,FALSE);
                        }

                        if (lpft->m_ButtonBar.m_nState != BARSTATE_HIDE)
                        {
                                GBGizmoEnable(lpft->m_ButtonBar.m_hWnd, IDM_E_CUT, FALSE);
                                GBGizmoEnable(lpft->m_ButtonBar.m_hWnd, IDM_E_COPY, FALSE);
                                GBGizmoEnable(lpft->m_ButtonBar.m_hWnd, IDM_E_PASTE, FALSE);
                                GBGizmoEnable(lpft->m_ButtonBar.m_hWnd,IDM_L_INDENTLINE,FALSE);
                                GBGizmoEnable(lpft->m_ButtonBar.m_hWnd, IDM_L_UNINDENTLINE, FALSE);
                                GBGizmoEnable(lpft->m_ButtonBar.m_hWnd, IDM_H_ABOUT, FALSE);
                                GBGizmoEnable(lpft->m_ButtonBar.m_hWnd, IDM_FB_EDIT, FALSE);
                                GBGizmoEnable(lpft->m_ButtonBar.m_hWnd, IDM_F_NEW, TRUE);
                                GBGizmoEnable(lpft->m_ButtonBar.m_hWnd, IDM_F_OPEN, TRUE);
                                GBGizmoEnable(lpft->m_ButtonBar.m_hWnd, IDM_F_SAVE, TRUE);
                        }
                        return;
                }
        }
#endif    // INPLACE_CNTR

        fEnable = (BOOL)OutlineDoc_GetLineCount(lpOutlineDoc);

        if (lpft->m_FormulaBar.m_nState != BARSTATE_HIDE) {

                GBGizmoEnable(lpft->m_FormulaBar.m_hWnd, IDM_L_EDITLINE, fEnable);

                if (! lpft->m_fInFormulaBar) {
                        GBGizmoEnable(lpft->m_FormulaBar.m_hWnd, IDM_L_ADDLINE, FALSE);
                        GBGizmoEnable(lpft->m_FormulaBar.m_hWnd, IDM_FB_CANCEL, FALSE);
                        GBGizmoEnable(lpft->m_FormulaBar.m_hWnd, IDM_L_EDITLINE, FALSE);
                        if (!fEnable) {
                                GBGizmoTextSet(lpft->m_FormulaBar.m_hWnd, IDM_FB_EDIT, "");
                        }
                } else {
                        GBGizmoEnable(lpft->m_FormulaBar.m_hWnd, IDM_L_ADDLINE, TRUE);
                        GBGizmoEnable(lpft->m_FormulaBar.m_hWnd, IDM_FB_CANCEL, TRUE);
                }
        }

        if (lpft->m_ButtonBar.m_nState != BARSTATE_HIDE)
        {
                GBGizmoEnable(lpft->m_ButtonBar.m_hWnd, IDM_E_CUT, fEnable);
                GBGizmoEnable(lpft->m_ButtonBar.m_hWnd, IDM_E_COPY, fEnable);
                GBGizmoEnable(lpft->m_ButtonBar.m_hWnd, IDM_L_INDENTLINE, fEnable);
                GBGizmoEnable(lpft->m_ButtonBar.m_hWnd, IDM_L_UNINDENTLINE, fEnable);

#if defined( OLE_SERVER )

                {
                        LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOutlineDoc;

#if defined( INPLACE_SVR )
                        fEnable = ((lpServerDoc->m_fUIActive) ? FALSE : TRUE);
#else
                        fEnable = (lpOutlineDoc->m_docInitType != DOCTYPE_EMBEDDED);
#endif  // INPLACE_SVR

                        GBGizmoEnable(lpft->m_ButtonBar.m_hWnd, IDM_F_NEW, fEnable);
                        GBGizmoEnable(lpft->m_ButtonBar.m_hWnd, IDM_F_OPEN, fEnable);
                        GBGizmoEnable(lpft->m_ButtonBar.m_hWnd, IDM_F_SAVE, fEnable);
                }

#endif  // OLE_SERVER

#if defined( OLE_VERSION )

                /* NOTE: we do not want to ever give the busy dialog when we
                **    are trying to enable or disable our tool bar buttons eg.
                **    even if the source of data on the clipboard is busy, we do
                **    not want put up the busy dialog. thus we will disable the
                **    dialog and at the end re-enable it.
                */
                OleApp_DisableBusyDialogs(lpOleApp, &fPrevEnable1, &fPrevEnable2);

                /* NOTE: perform OLE specific menu initialization.
                **    the OLE versions use the OleGetClipboard mechanism for
                **    clipboard handling. thus, they determine if the Paste
                **    command should be enabled in an OLE specific manner.
                */
                fEnable = FALSE;
                hrErr = OleGetClipboard((LPDATAOBJECT FAR*)&lpClipboardDataObj);

                if (hrErr == NOERROR) {
                        int nFmtEtc;

                        nFmtEtc = OleStdGetPriorityClipboardFormat(
                                        lpClipboardDataObj,
                                        lpOleApp->m_arrPasteEntries,
                                        lpOleApp->m_nPasteEntries
                        );

                        fEnable = (nFmtEtc >= 0);  // there IS a format we like

                        OleStdRelease((LPUNKNOWN)lpClipboardDataObj);
                }

                // re-enable the busy dialog
                OleApp_EnableBusyDialogs(lpOleApp, fPrevEnable1, fPrevEnable2);

                GBGizmoEnable(lpft->m_ButtonBar.m_hWnd, IDM_E_PASTE, fEnable);

#else

                // Base Outline version uses standard Windows clipboard handling
                if(IsClipboardFormatAvailable(g_lpApp->m_cfOutline) ||
                                IsClipboardFormatAvailable(CF_TEXT))
                        GBGizmoEnable(lpft->m_ButtonBar.m_hWnd, IDM_E_PASTE, TRUE);
                else
                        GBGizmoEnable(lpft->m_ButtonBar.m_hWnd, IDM_E_PASTE, FALSE);

#endif  // OLE_VERSION

        }
}

/*
 * FrameTools_FB_SetEditText
 *
 * Purpose:
 *  Set text in the edit control in FormulaBar
 *
 * Parameters:
 *  lpft            FrameTools object
 *  lpsz            pointer to string to be set
 *
 * Return Value:
 *  nil
 */
void FrameTools_FB_SetEditText(LPFRAMETOOLS lpft, LPSTR lpsz)
{
        GBGizmoTextSet(lpft->m_FormulaBar.m_hWnd, IDM_FB_EDIT, lpsz);
}

/*
 * FrameTools_FB_GetEditText
 *
 * Purpose:
 *  Get text from the edit control in FormulaBar
 *
 * Parameters:
 *  lpft            FrameTools object
 *  lpsz            pointer to buffer to receive the text
 *  cch             buffer size
 *
 * Return Value:
 *  nil
 */
void FrameTools_FB_GetEditText(LPFRAMETOOLS lpft, LPSTR lpsz, UINT cch)
{
        GBGizmoTextGet(lpft->m_FormulaBar.m_hWnd, IDM_FB_EDIT, lpsz, cch);
}

/*
 * FrameTools_FB_FocusEdit
 *
 * Purpose:
 *  Set the focus in the edit control of FormulaBar
 *
 * Parameters:
 *  lpft            FrameTools object
 *
 * Return Value:
 *  nil
 */
void FrameTools_FB_FocusEdit(LPFRAMETOOLS lpft)
{
        GBGizmoFocusSet(lpft->m_FormulaBar.m_hWnd, IDM_FB_EDIT);

        // select the whole text in the edit control
        GBGizmoSendMessage(lpft->m_FormulaBar.m_hWnd, IDM_FB_EDIT, EM_SETSEL,
                        (WPARAM)TRUE, MAKELPARAM(0, -1));
}

/*
 * FrameTools_FB_SendMessage
 *
 * Purpose:
 *  Send a message to the FormulaBar window gizmo
 *
 * Parameters:
 *  lpft            FrameTools object
 *  uID             gizmo ID
 *  msg
 *  wParam
 *  lParam
 *
 * Return Value:
 *  nil
 */
void FrameTools_FB_SendMessage(LPFRAMETOOLS lpft, UINT uID, UINT msg, WPARAM wParam, LPARAM lParam)
{
        GBGizmoSendMessage(lpft->m_FormulaBar.m_hWnd, uID, msg, wParam, lParam);
}

/*
 * FrameTools_FB_ForceRedraw
 *
 * Purpose:
 *  Force the toolbar to draw itself
 *
 * Parameters:
 *  lpft            FrameTools object
 *
 * Return Value:
 *  nil
 */
void FrameTools_ForceRedraw(LPFRAMETOOLS lpft)
{
        InvalidateRect(lpft->m_ButtonBar.m_hWnd, NULL, TRUE);
        InvalidateRect(lpft->m_FormulaBar.m_hWnd, NULL, TRUE);
        InvalidateRect(lpft->m_hWndPopupPalette, NULL, TRUE);
}

/*
 * FrameTools_BB_SetState
 *
 * Purpose:

*  Set display state of ButtonBar
 *
 * Parameters:
 *  lpft            FrameTools object
 *  nState          new display state
 *
 * Return Value:
 *  nil
 */
void FrameTools_BB_SetState(LPFRAMETOOLS lpft, int nState)
{
        if (!lpft) {
                return;
        }

        lpft->m_ButtonBar.m_nState = nState;

        if (nState == BARSTATE_POPUP)
                SetParent(lpft->m_ButtonBar.m_hWnd, lpft->m_hWndPopupPalette);
        else
                SetParent(lpft->m_ButtonBar.m_hWnd, lpft->m_hWndApp);
}

/*
 * FrameTools_BB_GetState
 *
 * Purpose:
 *  Get display state of ButtonBar
 *
 * Parameters:
 *  lpft            FrameTools object
 *
 * Return Value:
 *  nState          current display state
 */
int FrameTools_BB_GetState(LPFRAMETOOLS lpft)
{
        return lpft->m_ButtonBar.m_nState;
}

/*
 * FrameTools_FB_SetState
 *
 * Purpose:
 *  Set display state of FormulaBar
 *
 * Parameters:
 *  lpft            FrameTools object
 *  nState          new display state
 *
 * Return Value:
4 *  nil
 */
void FrameTools_FB_SetState(LPFRAMETOOLS lpft, int nState)
{
        if (!lpft) {
                return;
        }

        lpft->m_FormulaBar.m_nState = nState;

        if (nState == BARSTATE_POPUP)
                SetParent(lpft->m_FormulaBar.m_hWnd, lpft->m_hWndPopupPalette);

#if defined( INPLACE_SVR )
        /* NOTE: it is dangerous for an in-place server to hide its
        **    toolbar window and leave it parented to the hWndFrame of the
        **    in-place container. if the in-place container call
        **    ShowOwnedPopups, then it could inadvertantly be made visible.
        **    to avoid this we will parent the toolbar window back to our
        **    own application main window. if we are not in-place active
        **    then this is the same as lpft->m_hWndApp.
        */
        else if (nState == BARSTATE_HIDE)
                SetParent(lpft->m_FormulaBar.m_hWnd, g_lpApp->m_hWndApp);
#endif

        else
                SetParent(lpft->m_FormulaBar.m_hWnd, lpft->m_hWndApp);
}

/*
 * FrameTools_FB_GetState
 *
 * Purpose:
 *  Get display state of FormulaBar
 *
 * Parameters:
 *  lpft            FrameTools object
 *
 * Return Value:
 *  nState          current display state
 */
int FrameTools_FB_GetState(LPFRAMETOOLS lpft)
{
        return lpft->m_FormulaBar.m_nState;
}

/*
 * FrameToolsWndProc
 *
 * Purpose:
 *  WndProc for toolbar window
 *
 * Parameters:
 *  hWnd
 *  Message
 *  wParam
 *  lParam
 *
 * Return Value:
 *  message dependent
 */
LRESULT FAR PASCAL FrameToolsWndProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
        LPFRAMETOOLS lpft = (LPFRAMETOOLS)GetWindowLong(hWnd, 0);

        switch (Message) {

                case WM_MOUSEACTIVATE:
                        return MA_NOACTIVATE;

                default:
                        return DefWindowProc(hWnd, Message, wParam, lParam);
        }

        return 0L;
}

/*
 * Bar_Move
 *
 * Purpose:
 *  Resize and reposition a bar
 *
 * Parameters:
 *  lpbar           Bar object
 *  lprcClient      pointer to Client rect
 *  lprcPopup       pointer to Popup rect
 *
 * Return Value:
 *  nil
 */
static void Bar_Move(LPBAR lpbar, LPRECT lprcClient, LPRECT lprcPopup)
{
        if (lpbar->m_nState == BARSTATE_HIDE) {
                ShowWindow(lpbar->m_hWnd, SW_HIDE);
        }
        else {
                ShowWindow(lpbar->m_hWnd, SW_SHOW);
                switch (lpbar->m_nState) {
                        case BARSTATE_POPUP:
                                MoveWindow(lpbar->m_hWnd, lprcPopup->left, lprcPopup->top,
                                                lprcPopup->right - lprcPopup->left, lpbar->m_uHeight,
                                                TRUE);
                                lprcPopup->top += lpbar->m_uHeight;
                                break;

                        case BARSTATE_TOP:
                                MoveWindow(lpbar->m_hWnd, lprcClient->left, lprcClient->top,
                                                lprcClient->right - lprcClient->left,
                                                lpbar->m_uHeight, TRUE);
                                lprcClient->top += lpbar->m_uHeight;
                                break;

                        case BARSTATE_BOTTOM:
                                MoveWindow(lpbar->m_hWnd, lprcClient->left,
                                                lprcClient->bottom - lpbar->m_uHeight,
                                                lprcClient->right - lprcClient->left,
                                                lpbar->m_uHeight, TRUE);
                                lprcClient->bottom -= lpbar->m_uHeight;
                                break;
                }
        }
}

/*
 * FB_ResizeEdit
 *
 * Purpose:
 *  Resize the edit control in FormulaBar
 *
 * Parameters:
 *  lpft            Bar object
 *
 * Return Value:
 *  nil
 */
static void FB_ResizeEdit(LPBAR lpbar)
{
        RECT rcClient;
        RECT rcEdit;
        HWND hwndEdit;

        GetClientRect(lpbar->m_hWnd, (LPRECT)&rcClient);
        hwndEdit = GetDlgItem(lpbar->m_hWnd, IDM_FB_EDIT);
        GetWindowRect(hwndEdit, (LPRECT)&rcEdit);
        ScreenToClient(lpbar->m_hWnd, (LPPOINT)&rcEdit.left);
        ScreenToClient(lpbar->m_hWnd, (LPPOINT)&rcEdit.right);

        SetWindowPos(hwndEdit, NULL, 0, 0, rcClient.right - rcEdit.left - SPACE,
                        rcEdit.bottom - rcEdit.top, SWP_NOMOVE | SWP_NOZORDER);
}


FRAMETLS.H

/*************************************************************************
**
**    OLE 2 Sample Code
**
**    frametls.h
**
**    This file contains file contains data structure defintions,
**    function prototypes, constants, etc. used by the frame level
**    tools used by the Outline series of sample applications. The
**    frame level tools include a formula bar and a button bar (toolbar)
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#if !defined( _FRAMETLS_H_ )
#define _FRAMETLS_H_

#ifndef RC_INVOKED
#pragma message ("INCLUDING FRAMETLS.H from " __FILE__)
#endif  /* RC_INVOKED */

#include "bttncur.h"
#include "gizmobar.h"

#define SPACE   5
#define POPUPSTUB_HEIGHT    5

/* forward type references */
typedef struct tagOUTLINEDOC FAR* LPOUTLINEDOC;

#define IDC_GIZMOBAR    1000
#define IDC_FORMULABAR  1001

#define IDB_CANCEL          0
#define IDB_EDITLINE        1
#define IDB_ADDLINE         2
#define IDB_UNINDENTLINE    3
#define IDB_INDENTLINE      4

#define BARSTATE_TOP        1
#define BARSTATE_BOTTOM     2
#define BARSTATE_POPUP      3
#define BARSTATE_HIDE       4

#define CLASS_PALETTE   "Tool Palette"

typedef struct tagBAR{
   UINT        m_uHeight;
   HWND        m_hWnd;
   int         m_nState;
} BAR, FAR* LPBAR;

typedef struct tagFRAMETOOLS {
   HWND        m_hWndPopupPalette;     // Popup Tool Palette window
   HWND        m_hWndApp;              // App Frame window
   UINT        m_uPopupWidth;          // Width of the popup palette
   HBITMAP     m_hBmp;                 // Image bitmaps
   BOOL        m_fInFormulaBar;        // does formula bar have edit focus
   BOOL        m_fToolsDisabled;       // when TRUE all tools are hidden

   BAR         m_ButtonBar;            // Button Bar
   BAR         m_FormulaBar;           // Formula Bar

   TOOLDISPLAYDATA m_tdd;      // from UIToolConfigureForDisplay
} FRAMETOOLS, FAR* LPFRAMETOOLS;

BOOL FrameToolsRegisterClass(HINSTANCE hInst);
BOOL FrameTools_Init(LPFRAMETOOLS lpft, HWND hWndParent, HINSTANCE hInst);
void FrameTools_AttachToFrame(LPFRAMETOOLS lpft, HWND hWndFrame);
void FrameTools_AssociateDoc(LPFRAMETOOLS lpft, LPOUTLINEDOC lpOutlineDoc);
void FrameTools_Destroy(LPFRAMETOOLS lpft);
void FrameTools_Move(LPFRAMETOOLS lpft, LPRECT lprcClient);
void FrameTools_PopupTools(LPFRAMETOOLS lpft);
void FrameTools_Enable(LPFRAMETOOLS lpft, BOOL fEnable);
void FrameTools_EnableWindow(LPFRAMETOOLS lpft, BOOL fEnable);

#if defined( INPLACE_CNTR ) || defined( INPLACE_SVR )
void FrameTools_NegotiateForSpaceAndShow(
      LPFRAMETOOLS            lpft,
      LPRECT                  lprcFrameRect,
      LPOLEINPLACEFRAME       lpTopIPFrame
);
#endif  // INPLACE_CNTR || INPLACE_SVR

void FrameTools_GetRequiredBorderSpace(LPFRAMETOOLS lpft, LPBORDERWIDTHS lpBorderWidths);

void FrameTools_UpdateButtons(LPFRAMETOOLS lpft, LPOUTLINEDOC lpOutlineDoc);
void FrameTools_FB_SetEditText(LPFRAMETOOLS lpft, LPSTR lpsz);
void FrameTools_FB_GetEditText(LPFRAMETOOLS lpft, LPSTR lpsz, UINT cch);
void FrameTools_FB_FocusEdit(LPFRAMETOOLS lpft);
void FrameTools_FB_SendMessage(LPFRAMETOOLS lpft, UINT uID, UINT msg, WPARAM wParam, LPARAM lParam);
void FrameTools_ForceRedraw(LPFRAMETOOLS lpft);
void FrameTools_BB_SetState(LPFRAMETOOLS lpft, int nState);
void FrameTools_FB_SetState(LPFRAMETOOLS lpft, int nState);
int FrameTools_BB_GetState(LPFRAMETOOLS lpft);
int FrameTools_FB_GetState(LPFRAMETOOLS lpft);
LRESULT FAR PASCAL FrameToolsWndProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam);

#endif // _FRAMETLS_H_


HEADING.C

/*************************************************************************
**
**    OLE 2 Sample Code
**
**    heading.c
**
**    This file contains functions and support for OutlineDoc's row and
**    column headings.
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#include "outline.h"

extern LPOUTLINEAPP g_lpApp;

BOOL Heading_Create(LPHEADING lphead, HWND hWndParent, HINSTANCE hInst)
{
   HDC         hDC;
   TEXTMETRIC  tm;

   if (!lphead || !hWndParent || !hInst)
      return FALSE;

   hDC = GetDC(hWndParent);
   if (!hDC)
      return FALSE;

   if (!GetTextMetrics(hDC, (TEXTMETRIC FAR*)&tm))
      return FALSE;
   lphead->m_colhead.m_uHeight = tm.tmHeight;
   lphead->m_rowhead.m_uWidth = 4 * tm.tmAveCharWidth;
   lphead->m_fShow = TRUE;

   ReleaseDC(hWndParent, hDC);

   lphead->m_hfont = CreateFont(
         tm.tmHeight,
         0,0,0,0,0,0,0,0,
         OUT_TT_PRECIS,      // use TrueType
         CLIP_DEFAULT_PRECIS,
         DEFAULT_QUALITY,
         DEFAULT_PITCH | FF_DONTCARE,
         HEADING_FONT
   );

   if (!lphead->m_hfont)
      return FALSE;

   lphead->m_colhead.m_hWnd = CreateWindow(
         "listbox",
         "Column Heading",
         WS_VISIBLE | WS_CHILD | WS_DISABLED | LBS_OWNERDRAWVARIABLE |
               LBS_NOINTEGRALHEIGHT,
         0,0,0,0,        // any values
         hWndParent,
         (HMENU)IDC_COLHEADING,
         hInst,
         NULL);

   if (!lphead->m_colhead.m_hWnd)
      return FALSE;

   // add a dummy line to get WM_DRAWITEM message
   SendMessage(lphead->m_colhead.m_hWnd, LB_ADDSTRING, 0,
         MAKELPARAM(lphead->m_colhead.m_uHeight,0));

   lphead->m_rowhead.m_hWnd = CreateWindow(
         "listbox",
         "Row Heading",
         WS_VISIBLE | WS_CHILD | WS_DISABLED | LBS_OWNERDRAWVARIABLE,
         0,0,0,0,        // any values
         hWndParent,
         (HMENU)IDC_ROWHEADING,
         hInst,
         NULL);

   if (!lphead->m_rowhead.m_hWnd)
      return FALSE;

   SendMessage(lphead->m_rowhead.m_hWnd, LB_ADDSTRING, 0,
         MAKELPARAM(lphead->m_colhead.m_uHeight,0));

   lphead->m_rowhead.m_WndProc =
         (FARPROC) GetWindowLong(lphead->m_rowhead.m_hWnd, GWL_WNDPROC );
   SetWindowLong(lphead->m_rowhead.m_hWnd, GWL_WNDPROC,
         (LONG) RowHeadWndProc);

   lphead->m_hwndButton = CreateWindow(
         "button",
         NULL,
         WS_VISIBLE | WS_CHILD,
         0,0,0,0,        // any values
         hWndParent,
         (HMENU)IDC_BUTTON,
         hInst,
         NULL);

   if (!lphead->m_hwndButton)
      return FALSE;

   return TRUE;
}

void Heading_Destroy(LPHEADING lphead)
{
   if (!lphead)
      return;

   if (IsWindow(lphead->m_colhead.m_hWnd)) {
      DestroyWindow(lphead->m_colhead.m_hWnd);
      lphead->m_colhead.m_hWnd = NULL;
   }
   if (IsWindow(lphead->m_rowhead.m_hWnd)) {
      DestroyWindow(lphead->m_rowhead.m_hWnd);
      lphead->m_rowhead.m_hWnd = NULL;
   }
   if (IsWindow(lphead->m_hwndButton)) {
      DestroyWindow(lphead->m_hwndButton);
      lphead->m_hwndButton = NULL;
   }
#ifdef WIN32
   if (GetObjectType(lphead->m_hfont)) {
#else
   if (IsGDIObject(lphead->m_hfont)) {
#endif
      DeleteObject(lphead->m_hfont);
      lphead->m_hfont = NULL;
   }

}

void Heading_Move(LPHEADING lphead, HWND hwndDoc, LPSCALEFACTOR lpscale)
{
   int nOffsetX;
   int nOffsetY;
   RECT rcDoc;

   if (!lphead || !hwndDoc || !lpscale)
      return;

   if (!lphead->m_fShow)
      return;

   nOffsetX = (int) Heading_RH_GetWidth(lphead, lpscale);
   nOffsetY = (int) Heading_CH_GetHeight(lphead, lpscale);
   GetClientRect(hwndDoc, (LPRECT)&rcDoc);

   MoveWindow(lphead->m_hwndButton, 0, 0, nOffsetX, nOffsetY, TRUE);

   MoveWindow(
         lphead->m_colhead.m_hWnd,
         nOffsetX, 0,
         rcDoc.right-rcDoc.left-nOffsetX, nOffsetY,
         TRUE
   );

   MoveWindow(lphead->m_rowhead.m_hWnd, 0, nOffsetY, nOffsetX,
         rcDoc.bottom-rcDoc.top-nOffsetY, TRUE);
}

void Heading_Show(LPHEADING lphead, BOOL fShow)
{
   int nCmdShow;

   if (!lphead)
      return;

   lphead->m_fShow = fShow;
   nCmdShow = fShow ? SW_SHOW : SW_HIDE;

   ShowWindow(lphead->m_hwndButton, nCmdShow);
   ShowWindow(lphead->m_colhead.m_hWnd, nCmdShow);
   ShowWindow(lphead->m_rowhead.m_hWnd, nCmdShow);
}

void Heading_ReScale(LPHEADING lphead, LPSCALEFACTOR lpscale)
{
   UINT uHeight;

   if (!lphead || !lpscale)
      return;

   // Row heading is scaled with the LineList_Rescale. So, only
   // Column heading needed to be scaled here.
   uHeight = (UINT)(lphead->m_colhead.m_uHeight * lpscale->dwSyN /
         lpscale->dwSyD);
   SendMessage(lphead->m_colhead.m_hWnd, LB_SETITEMHEIGHT, 0,
         MAKELPARAM(uHeight, 0));
}

void Heading_CH_Draw(LPHEADING lphead, LPDRAWITEMSTRUCT lpdis, LPRECT lprcScreen, LPRECT lprcObject)
{
   HPEN        hpenOld;
   HPEN        hpen;
   HBRUSH      hbr;
   HFONT       hfOld;
   int         nTabInPix;
   char        letter;
   int         i;
   int         nOldMapMode;
   RECT        rcWindowOld;
   RECT        rcViewportOld;
   POINT    point;

   if (!lpdis || !lphead)
      return;

   hbr = GetStockObject(LTGRAY_BRUSH);
   FillRect(lpdis->hDC, (LPRECT)&lpdis->rcItem, hbr);

   nOldMapMode = SetDCToAnisotropic(lpdis->hDC, lprcScreen, lprcObject,
         (LPRECT)&rcWindowOld, (LPRECT)&rcViewportOld);

   hfOld = SelectObject(lpdis->hDC, lphead->m_hfont);
   hpen = GetStockObject(BLACK_PEN);
   hpenOld = SelectObject(lpdis->hDC, hpen);

   nTabInPix = XformWidthInHimetricToPixels(lpdis->hDC, TABWIDTH);
   SetBkMode(lpdis->hDC, TRANSPARENT);

   letter = COLUMN_LETTER;
   MoveToEx(lpdis->hDC, lprcObject->left, lprcObject->bottom,&point);
   LineTo(lpdis->hDC, lprcObject->left, lprcObject->top);

   for (i = 0; i < COLUMN; i++) {
      lprcObject->right = lprcObject->left + nTabInPix;
      DrawText(lpdis->hDC, (LPCSTR)&letter, 1, lprcObject,
            DT_SINGLELINE | DT_CENTER | DT_VCENTER);
      MoveToEx(lpdis->hDC, lprcObject->right, lprcObject->bottom, &point);
      LineTo(lpdis->hDC, lprcObject->right, lprcObject->top);

      letter++;
      lprcObject->left += nTabInPix;
   }

   SelectObject(lpdis->hDC, hpenOld);
   SelectObject(lpdis->hDC, hfOld);

   ResetOrigDC(lpdis->hDC, nOldMapMode, (LPRECT)&rcWindowOld,
         (LPRECT)&rcViewportOld);
}

void Heading_CH_SetHorizontalExtent(LPHEADING lphead, HWND hwndListBox)
{
   RECT rcLL;
   RECT rcCH;
   int  nLLWidth;
   int  nCHWidth;
   int  nHorizExtent;

   if (!lphead || !hwndListBox)
      return;

   nHorizExtent=(int)SendMessage(hwndListBox, LB_GETHORIZONTALEXTENT, 0, 0L);
   GetClientRect(hwndListBox, (LPRECT)&rcLL);
   GetClientRect(lphead->m_colhead.m_hWnd, (LPRECT)&rcCH);

   nLLWidth = rcLL.right - rcLL.left;
   nCHWidth = rcCH.right - rcCH.left;
   nHorizExtent += nCHWidth - nLLWidth;

   SendMessage(lphead->m_colhead.m_hWnd, LB_SETHORIZONTALEXTENT,
         nHorizExtent, 0L);
}

UINT Heading_CH_GetHeight(LPHEADING lphead, LPSCALEFACTOR lpscale)
{
   if (!lphead || !lpscale)
      return 0;

   if (lphead->m_fShow)
      return (UINT)(lphead->m_colhead.m_uHeight * lpscale->dwSyN /
            lpscale->dwSyD);
   else
      return 0;
}

LRESULT Heading_CH_SendMessage(LPHEADING lphead, UINT msg, WPARAM wParam, LPARAM lParam)
{
   if (!lphead)
      return 0;

   if (lphead->m_colhead.m_hWnd)
      return SendMessage(lphead->m_colhead.m_hWnd, msg, wParam, lParam);
}

void Heading_CH_ForceRedraw(LPHEADING lphead, BOOL fErase)
{
   if (!lphead)
      return;

   InvalidateRect(lphead->m_colhead.m_hWnd, NULL, fErase);
}

void Heading_RH_ForceRedraw(LPHEADING lphead, BOOL fErase)
{
   if (!lphead)
      return;

   InvalidateRect(lphead->m_rowhead.m_hWnd, NULL, fErase);
}

void Heading_RH_Draw(LPHEADING lphead, LPDRAWITEMSTRUCT lpdis)
{
   char        cBuf[5];
   HPEN        hpenOld;
   HPEN        hpen;
   HBRUSH      hbrOld;
   HBRUSH      hbr;
   HFONT       hfOld;
   RECT        rc;
   RECT        rcWindowOld;
   RECT        rcViewportOld;
   int         nMapModeOld;

   if (!lpdis || !lphead)
      return;

   lpdis->rcItem;

   rc.left = 0;
   rc.bottom = 0;
   rc.top = (int)lpdis->itemData;
   rc.right = lphead->m_rowhead.m_uWidth;

   nMapModeOld = SetDCToAnisotropic(lpdis->hDC, &lpdis->rcItem, &rc,
         (LPRECT)&rcWindowOld, (LPRECT)&rcViewportOld);

   hpen = GetStockObject(BLACK_PEN);
   hpenOld = SelectObject(lpdis->hDC, hpen);
   hbr = GetStockObject(LTGRAY_BRUSH);
   hbrOld = SelectObject(lpdis->hDC, hbr);

   Rectangle(lpdis->hDC, rc.left, rc.top, rc.right,
         rc.bottom);

   hfOld = SelectObject(lpdis->hDC, lphead->m_hfont);

   SetBkMode(lpdis->hDC, TRANSPARENT);

   wsprintf(cBuf, "%d", lpdis->itemID + 1);

   DrawText(lpdis->hDC, (LPSTR)cBuf, lstrlen(cBuf), (LPRECT)&rc,
         DT_SINGLELINE | DT_CENTER | DT_VCENTER);

   SelectObject(lpdis->hDC, hfOld);

   SelectObject(lpdis->hDC, hpenOld);
   SelectObject(lpdis->hDC, hbrOld);

   ResetOrigDC(lpdis->hDC, nMapModeOld, (LPRECT)&rcWindowOld,
         (LPRECT)&rcViewportOld);
}

LRESULT Heading_RH_SendMessage(LPHEADING lphead, UINT msg, WPARAM wParam, LPARAM lParam)
{
   if (!lphead)
      return 0;

   if (lphead->m_rowhead.m_hWnd)
      return SendMessage(lphead->m_rowhead.m_hWnd, msg, wParam, lParam);
}

UINT Heading_RH_GetWidth(LPHEADING lphead, LPSCALEFACTOR lpscale)
{
   if (!lphead || !lpscale)
      return 0;

   if (lphead->m_fShow)
      return (UINT)(lphead->m_rowhead.m_uWidth * lpscale->dwSxN /
            lpscale->dwSxD);
   else
      return 0;
}

void Heading_RH_Scroll(LPHEADING lphead, HWND hwndListBox)
{
   int nTopLL;
   int nTopRH;

   if (!lphead || !hwndListBox)
      return;

   nTopLL = (int)SendMessage(hwndListBox, LB_GETTOPINDEX, 0, 0L);
   nTopRH = (int)SendMessage(
         lphead->m_rowhead.m_hWnd, LB_GETTOPINDEX, 0, 0L);

   if (nTopLL != nTopRH)
      SendMessage(
            lphead->m_rowhead.m_hWnd,LB_SETTOPINDEX,(WPARAM)nTopLL,0L);
}

LRESULT FAR PASCAL RowHeadWndProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
   HWND     hwndParent = GetParent (hWnd);
   LPOUTLINEDOC lpDoc = (LPOUTLINEDOC)GetWindowLong(hwndParent, 0);
   LPHEADING lphead = OutlineDoc_GetHeading(lpDoc);

   switch (Message) {
      case WM_PAINT:
      {
         LPLINELIST lpLL = OutlineDoc_GetLineList(lpDoc);
         PAINTSTRUCT ps;

         // If there is no line in listbox, trap the message and draw the
         // background gray. Without this, the background will be painted
         // as default color.
         if (!LineList_GetCount(lpLL)) {
            BeginPaint(hWnd, &ps);
            EndPaint(hWnd, &ps);
            return 0;
         }

         break;
      }

      case WM_ERASEBKGND:
      {
         HDC hDC = (HDC)wParam;
         RECT rc;

         GetClientRect(hWnd, (LPRECT)&rc);
         FillRect(hDC, (LPRECT)&rc, GetStockObject(GRAY_BRUSH));

         return 1;
      }
   }

   return CallWindowProc(
         (WNDPROC)lphead->m_rowhead.m_WndProc,
         hWnd,
         Message,
         wParam,
         lParam
   );
}


HEADING.H

/*************************************************************************
**
**    OLE 2 Sample Code
**
**    heading.c
**
**    This file contains definitions used by OutlineDoc's row and
**    column headings.
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#define COLUMN  10

#define IDC_ROWHEADING  2000
#define IDC_COLHEADING  2001
#define IDC_BUTTON      2002

#define HEADING_FONT    "Arial"

#define COLUMN_LETTER   'A'

typedef struct tagCOLHEADING {
HWND m_hWnd;
UINT m_uHeight;
} COLHEADING, FAR* LPCOLHEADING;

typedef struct tagROWHEADING {
HWND m_hWnd;
UINT m_uWidth;
FARPROC    m_WndProc;
} ROWHEADING, FAR* LPROWHEADING;

typedef struct tagHEADING {
COLHEADING m_colhead;
ROWHEADING m_rowhead;
HWND       m_hwndButton;
BOOL       m_fShow;
HFONT      m_hfont;
} HEADING, FAR* LPHEADING;

BOOL Heading_Create(LPHEADING lphead, HWND hWndParent, HINSTANCE hInst);
void Heading_Destroy(LPHEADING lphead);
void Heading_Move(LPHEADING lphead, HWND hwndListBox, LPSCALEFACTOR lpscale);
void Heading_Show(LPHEADING lphead, BOOL fShow);
void Heading_ReScale(LPHEADING lphead, LPSCALEFACTOR lpscale);
void Heading_CH_Draw(LPHEADING lphead, LPDRAWITEMSTRUCT lpdis, LPRECT lprcScreen, LPRECT lprcObject);
void Heading_CH_SetHorizontalExtent(LPHEADING lphead, HWND hwndListBox);
UINT Heading_CH_GetHeight(LPHEADING lphead, LPSCALEFACTOR lpscale);
LRESULT Heading_CH_SendMessage(LPHEADING lphead, UINT msg, WPARAM wParam, LPARAM lParam);
void Heading_CH_ForceRedraw(LPHEADING lphead, BOOL fErase);
void Heading_RH_ForceRedraw(LPHEADING lphead, BOOL fErase);
void Heading_RH_Draw(LPHEADING lphead, LPDRAWITEMSTRUCT lpdis);
LRESULT Heading_RH_SendMessage(LPHEADING lphead, UINT msg, WPARAM wParam, LPARAM lParam);
UINT Heading_RH_GetWidth(LPHEADING lphead, LPSCALEFACTOR lpscale);
void Heading_RH_Scroll(LPHEADING lphead, HWND hwndListBox);
LRESULT FAR PASCAL RowHeadWndProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam);


ICNTROTL.C

/*************************************************************************
**
**    OLE 2.0 Sample Code
**
**    precomp.c
**
**    This file is used to precompile the OUTLINE.H header file
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#include "outline.h"


ISVROTL.C

/*************************************************************************
**
**    OLE 2.0 Sample Code
**
**    precomp.c
**
**    This file is used to precompile the OUTLINE.H header file
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#include "outline.h"


LINKING.C

/*************************************************************************
**
**    OLE 2 Sample Code
**
**    linking.c
**
**    This file contains the major interfaces, methods and related support
**    functions for implementing linking to items. The code
**    contained in this file is used by BOTH the Container and Server
**    (Object) versions of the Outline sample code.
**
**    As a server SVROUTL supports linking to the whole document object
**    (either a file-based document or as an embedded object). It also
**    supports linking to ranges (or PseudoObjects).
**
**    As a container CNTROUTL supports linking to embedded objects.
**    (see file svrpsobj.c for Pseudo Object implementation)
**
**    OleDoc Object
**      exposed interfaces:
**          IPersistFile
**          IOleItemContainer
**          IExternalConnection
**
**    (c) Copyright Microsoft Corp. 1992 - 1997 All Rights Reserved
**
*************************************************************************/

#include "outline.h"

OLEDBGDATA

extern LPOUTLINEAPP             g_lpApp;

/*************************************************************************
** OleDoc::IPersistFile interface implementation
*************************************************************************/

// IPersistFile::QueryInterface
STDMETHODIMP OleDoc_PFile_QueryInterface(
      LPPERSISTFILE       lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
)
{
   LPOLEDOC lpOleDoc = ((struct CDocPersistFileImpl FAR*)lpThis)->lpOleDoc;

   return OleDoc_QueryInterface(lpOleDoc, riid, lplpvObj);
}

// IPersistFile::AddRef
STDMETHODIMP_(ULONG) OleDoc_PFile_AddRef(LPPERSISTFILE lpThis)
{
   LPOLEDOC lpOleDoc = ((struct CDocPersistFileImpl FAR*)lpThis)->lpOleDoc;

   OleDbgAddRefMethod(lpThis, "IPersistFile");

   return OleDoc_AddRef(lpOleDoc);
}

// IPersistFile::Release
STDMETHODIMP_(ULONG) OleDoc_PFile_Release (LPPERSISTFILE lpThis)
{
   LPOLEDOC lpOleDoc = ((struct CDocPersistFileImpl FAR*)lpThis)->lpOleDoc;

   OleDbgReleaseMethod(lpThis, "IPersistFile");

   return OleDoc_Release(lpOleDoc);
}

// IPersistFile::GetClassID
STDMETHODIMP OleDoc_PFile_GetClassID (
      LPPERSISTFILE       lpThis,
      CLSID FAR*          lpclsid
)
{
   LPOLEDOC lpOleDoc = ((struct CDocPersistFileImpl FAR*)lpThis)->lpOleDoc;
   LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;
   OleDbgOut2("OleDoc_PFile_GetClassID\r\n");

#if defined( OLE_SERVER ) && defined( SVR_TREATAS )

   /* NOTE: we must be carefull to return the correct CLSID here.
   **    if we are currently preforming a "TreatAs (aka. ActivateAs)"
   **    operation then we need to return the class of the object
   **    written in the storage of the object. otherwise we would
   **    return our own class id.
   */
   return ServerDoc_GetClassID((LPSERVERDOC)lpOleDoc, lpclsid);
#else
   *lpclsid = CLSID_APP;
#endif
   return NOERROR;
}

// IPersistFile::IsDirty
STDMETHODIMP  OleDoc_PFile_IsDirty(LPPERSISTFILE lpThis)
{
   LPOLEDOC lpOleDoc = ((struct CDocPersistFileImpl FAR*)lpThis)->lpOleDoc;
   OleDbgOut2("OleDoc_PFile_IsDirty\r\n");

   if (OutlineDoc_IsModified((LPOUTLINEDOC)lpOleDoc))
      return NOERROR;
   else
      return S_FALSE;
}

// IPersistFile::Load
STDMETHODIMP OleDoc_PFile_Load (
      LPPERSISTFILE       lpThis,
      LPCOLESTR           lpszFileName,
      DWORD               grfMode
)
{
   LPOLEDOC lpOleDoc = ((struct CDocPersistFileImpl FAR*)lpThis)->lpOleDoc;
   SCODE sc;

   OLEDBG_BEGIN2("OleDoc_PFile_Load\r\n")

   /* NOTE: grfMode passed from the caller indicates if the caller
   **    needs Read or ReadWrite permissions. if appropriate the
   **    callee should open the file with the requested permissions.
   **    the caller will normally not impose sharing permissions.
   **
   **    the sample code currently always opens its file ReadWrite.
   */

   if (OutlineDoc_LoadFromFile((LPOUTLINEDOC)lpOleDoc, (LPOLESTR)lpszFileName))
      sc = S_OK;
   else
      sc = E_FAIL;

   OLEDBG_END2
   return sc;
}

// IPersistFile::Save
STDMETHODIMP OleDoc_PFile_Save (
      LPPERSISTFILE       lpThis,
      LPCOLESTR           lpszFileName,
      BOOL                fRemember
)
{
   LPOLEDOC lpOleDoc = ((struct CDocPersistFileImpl FAR*)lpThis)->lpOleDoc;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;
   SCODE sc;

   OLEDBG_BEGIN2("OleDoc_PFile_Save\r\n")

   /* NOTE: it is only legal to perform a Save or SaveAs operation
   **    on a file-based document. if the document is an embedded
   **    object then we can not be changed to a file-base object.
   **
   **      fRemember   lpszFileName     Type of Save
   **    ----------------------------------------------
   **        TRUE        NULL           SAVE
   **        TRUE        ! NULL         SAVE AS
   **        FALSE       ! NULL         SAVE COPY AS
   **        FALSE       NULL           ***error***
   */
   if ( (lpszFileName==NULL || (lpszFileName != NULL && fRemember))
         && ((lpOutlineDoc->m_docInitType != DOCTYPE_FROMFILE
            && lpOutlineDoc->m_docInitType != DOCTYPE_NEW)) ) {
      OLEDBG_END2
      return E_INVALIDARG;
   }

   if (OutlineDoc_SaveToFile(
         (LPOUTLINEDOC)lpOleDoc,
         lpszFileName,
         lpOutlineDoc->m_cfSaveFormat,
         fRemember)) {
      sc = S_OK;
   } else
      sc = E_FAIL;

   OLEDBG_END2
   return sc;
}

// IPersistFile::SaveCompleted
STDMETHODIMP OleDoc_PFile_SaveCompleted (
      LPPERSISTFILE       lpThis,
      LPCOLESTR           lpszFileName
)
{
   LPOLEDOC lpOleDoc = ((struct CDocPersistFileImpl FAR*)lpThis)->lpOleDoc;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;

   OleDbgOut2("OleDoc_PFile_SaveCompleted\r\n");

   /* This method is called after IPersistFile::Save is called. during
   **    the period between Save and SaveCompleted the object must
   **    consider itself in NOSCRIBBLE mode (ie. it is NOT allowed to
   **    write to its file. here the object can clear its NOSCRIBBLE
   **    mode flag. the outline app never scribbles to its storage, so
   **    we have nothing to do.
   */
   return NOERROR;
}

// IPersistFile::GetCurFile
STDMETHODIMP OleDoc_PFile_GetCurFile (
      LPPERSISTFILE   lpThis,
      LPOLESTR FAR*   lplpszFileName
)
{
   LPOLEDOC lpOleDoc = ((struct CDocPersistFileImpl FAR*)lpThis)->lpOleDoc;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;
   LPMALLOC lpMalloc;
   LPOLESTR lpsz;
   SCODE sc;
   char  szAnsiStr[256];

   OleDbgOut2("OleDoc_PFile_GetCurFile\r\n");

   /* NOTE: we must make sure to set all out ptr parameters to NULL. */
   *lplpszFileName = NULL;

   /*********************************************************************
   ** NOTE: memory returned for the lplpszFileName must be
   **    allocated appropriately using the current registered IMalloc
   **    interface. the allows the ownership of the memory to be
   **    passed to the caller (even if in another process).
   *********************************************************************/

   CoGetMalloc(MEMCTX_TASK, &lpMalloc);
   if (! lpMalloc) {

      return E_FAIL;
   }

   if (lpOutlineDoc->m_docInitType == DOCTYPE_FROMFILE) {
      /* valid filename associated; return file name */
      lpsz = /*(LPOLESTR)*/lpMalloc->lpVtbl->Alloc(
            lpMalloc,
            (OLESTRLEN(lpOutlineDoc->m_szFileName)+1)*sizeof(OLECHAR)
      );
      if (! lpsz) {
         sc = E_OUTOFMEMORY;
         goto error;
      }

      OLESTRCPY(lpsz, /*(LPOLESTR)*/lpOutlineDoc->m_szFileName);
      sc = S_OK;
   } else {
      /* no file associated; return default file name prompt */
      lpsz=/*(LPOLESTR)*/lpMalloc->lpVtbl->Alloc(lpMalloc, (lstrlen(DEFEXTENSION)+3)*sizeof(OLECHAR));
      wsprintf(szAnsiStr, "*.%s", DEFEXTENSION);
      A2W (szAnsiStr, lpsz, OLEUI_CCHPATHMAX);
      sc = S_FALSE;
   }

error:
   OleStdRelease((LPUNKNOWN)lpMalloc);
   *lplpszFileName = lpsz;

   return sc;
}

/*************************************************************************
** OleDoc::IOleItemContainer interface implementation
*************************************************************************/

// IOleItemContainer::QueryInterface
STDMETHODIMP OleDoc_ItemCont_QueryInterface(
      LPOLEITEMCONTAINER  lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocOleItemContainerImpl FAR*)lpThis)->lpOleDoc;

   return OleDoc_QueryInterface(lpOleDoc, riid, lplpvObj);
}

// IOleItemContainer::AddRef
STDMETHODIMP_(ULONG) OleDoc_ItemCont_AddRef(LPOLEITEMCONTAINER lpThis)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocOleItemContainerImpl FAR*)lpThis)->lpOleDoc;

   OleDbgAddRefMethod(lpThis, "IOleItemContainer");

   return OleDoc_AddRef((LPOLEDOC)lpOleDoc);
}

// IOleItemContainer::Release
STDMETHODIMP_(ULONG) OleDoc_ItemCont_Release(LPOLEITEMCONTAINER lpThis)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocOleItemContainerImpl FAR*)lpThis)->lpOleDoc;

   OleDbgReleaseMethod(lpThis, "IOleItemContainer");

   return OleDoc_Release((LPOLEDOC)lpOleDoc);
}

// IOleItemContainer::ParseDisplayName
STDMETHODIMP OleDoc_ItemCont_ParseDisplayName(
      LPOLEITEMCONTAINER  lpThis,
      LPBC                lpbc,
      LPOLESTR            lpszDisplayName,
      ULONG FAR*          lpchEaten,
      LPMONIKER FAR*      lplpmkOut
)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocOleItemContainerImpl FAR*)lpThis)->lpOleDoc;
   OLECHAR szItemName[MAXNAMESIZE];
   LPUNKNOWN lpUnk;
   HRESULT hrErr;

   OleDbgOut2("OleDoc_ItemCont_ParseDisplayName\r\n");

   /* NOTE: we must make sure to set all out ptr parameters to NULL. */
   *lplpmkOut = NULL;

   *lpchEaten = OleStdGetItemToken(
         lpszDisplayName,
         szItemName,
         MAXNAMESIZE*sizeof(OLECHAR)
   );

   /* NOTE: get a pointer to a running instance of the object. we
   **    should force the object to go running if necessary (even if
   **    this means launching its server EXE). this is the meaining of
   **    BINDSPEED_INDEFINITE. Parsing a Moniker is known to be an
   **    "EXPENSIVE" operation.
   */
   hrErr = OleDoc_ItemCont_GetObject(
         lpThis,
         szItemName,
         BINDSPEED_INDEFINITE,
         lpbc,
         &IID_IUnknown,
         (LPVOID FAR*)&lpUnk
   );

   if (hrErr == NOERROR) {
      OleStdRelease(lpUnk);   // item name FOUND; don't need obj ptr.
      CreateItemMoniker(OLESTR("\\"), szItemName, lplpmkOut);
   } else
      *lpchEaten = 0;     // item name is NOT valid

   return hrErr;
}

// IOleItemContainer::EnumObjects
STDMETHODIMP OleDoc_ItemCont_EnumObjects(
      LPOLEITEMCONTAINER  lpThis,
      DWORD               grfFlags,
      LPENUMUNKNOWN FAR*  lplpenumUnknown
)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocOleItemContainerImpl FAR*)lpThis)->lpOleDoc;

   OLEDBG_BEGIN2("OleDoc_ItemCont_EnumObjects\r\n")

   /* NOTE: we must make sure to set all out ptr parameters to NULL. */
   *lplpenumUnknown = NULL;

   /* NOTE: this method should be implemented to allow programatic
   **    clients the ability to what elements the container holds.
   **    this method is NOT called in the standard linking scenarios.
   **
   **    grfFlags can be one of the following:
   **        OLECONTF_EMBEDDINGS   -- enumerate embedded objects
   **        OLECONTF_LINKS        -- enumerate linked objects
   **        OLECONTF_OTHERS       -- enumerate non-OLE compound doc objs
   **        OLECONTF_ONLYUSER     -- enumerate only objs named by user
   **        OLECONTF_ONLYIFRUNNING-- enumerate only objs in running state
   */

   OleDbgAssertSz(0, "NOT YET IMPLEMENTED!");

   OLEDBG_END2

   return E_NOTIMPL;
}

// IOleItemContainer::LockContainer
STDMETHODIMP OleDoc_ItemCont_LockContainer(
      LPOLEITEMCONTAINER  lpThis,
      BOOL                fLock
)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocOleItemContainerImpl FAR*)lpThis)->lpOleDoc;
   HRESULT hrErr;
   OLEDBG_BEGIN2("OleDoc_ItemCont_LockContainer\r\n")

#if defined( _DEBUG )
   if (fLock) {
      ++lpOleDoc->m_cCntrLock;
      OleDbgOutRefCnt3(
            "OleDoc_ItemCont_LockContainer: cLock++\r\n",
            lpOleDoc,
            lpOleDoc->m_cCntrLock
      );
   } else {
      /* NOTE: when there are no open documents and the app is not
      **    under the control of the user and there are no outstanding
      **    locks on the app, then revoke our ClassFactory to enable the
      **    app to shut down.
      */
      --lpOleDoc->m_cCntrLock;
      OleDbgAssertSz (
            lpOleDoc->m_cCntrLock >= 0,
            "OleDoc_ItemCont_LockContainer(FALSE) called with cLock == 0"
      );

      if (lpOleDoc->m_cCntrLock == 0) {
         OleDbgOutRefCnt2(
               "OleDoc_ItemCont_LockContainer: UNLOCKED\r\n",
               lpOleDoc, lpOleDoc->m_cCntrLock);
      } else {
         OleDbgOutRefCnt3(
               "OleDoc_ItemCont_LockContainer: cLock--\r\n",
               lpOleDoc, lpOleDoc->m_cCntrLock);
      }
   }
#endif  // _DEBUG

   /* NOTE: in order to hold the document alive we call
   **    CoLockObjectExternal to add a strong reference to our Doc
   **    object. this will keep the Doc alive when all other external
   **    references release us. whenever an embedded object goes
   **    running a LockContainer(TRUE) is called. when the embedded
   **    object shuts down (ie. transitions from running to loaded)
   **    LockContainer(FALSE) is called. if the user issues File.Close
   **    the document will shut down in any case ignoring any
   **    outstanding LockContainer locks because CoDisconnectObject is
   **    called in OleDoc_Close. this will forceably break any
   **    existing strong reference counts including counts that we add
   **    ourselves by calling CoLockObjectExternal and guarantee that
   **    the Doc object gets its final release (ie. cRefs goes to 0).
   */
   hrErr = OleDoc_Lock(lpOleDoc, fLock, TRUE /* fLastUnlockReleases */);

   OLEDBG_END2
   return hrErr;
}

// IOleItemContainer::GetObject
STDMETHODIMP OleDoc_ItemCont_GetObject(
      LPOLEITEMCONTAINER  lpThis,
      LPOLESTR            lpszItem,
      DWORD               dwSpeedNeeded,
      LPBINDCTX           lpbc,
      REFIID              riid,
      LPVOID FAR*         lplpvObject
)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocOleItemContainerImpl FAR*)lpThis)->lpOleDoc;
   HRESULT hrErr;

   OLEDBG_BEGIN2("OleDoc_ItemCont_GetObject\r\n")

   /* NOTE: we must make sure to set all out ptr parameters to NULL. */
   *lplpvObject = NULL;

#if defined( OLE_SERVER )

   /* NOTE: SERVER ONLY version should return PseudoObjects with
   **    BINDSPEED_IMMEDIATE, thus the dwSpeedNeeded is not important
   **    in the case of a pure server.
   */
   hrErr = ServerDoc_GetObject(
         (LPSERVERDOC)lpOleDoc, lpszItem,riid,lplpvObject);
#endif
#if defined( OLE_CNTR )

   /* NOTE: dwSpeedNeeded indicates how long the caller is willing
   **    to wait for us to get the object:
   **      BINDSPEED_IMMEDIATE -- only if obj already loaded && IsRunning
   **      BINDSPEED_MODERATE  -- load obj if necessary && if IsRunning
   **      BINDSPEED_INDEFINITE-- force obj to load and run if necessary
   */
   hrErr = ContainerDoc_GetObject(
         (LPCONTAINERDOC)lpOleDoc,lpszItem,dwSpeedNeeded,riid,lplpvObject);
#endif

   OLEDBG_END2

   return hrErr;
}

// IOleItemContainer::GetObjectStorage
STDMETHODIMP OleDoc_ItemCont_GetObjectStorage(
      LPOLEITEMCONTAINER  lpThis,
      LPOLESTR            lpszItem,
      LPBINDCTX           lpbc,
      REFIID              riid,
      LPVOID FAR*         lplpvStorage
)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocOleItemContainerImpl FAR*)lpThis)->lpOleDoc;
   OleDbgOut2("OleDoc_ItemCont_GetObjectStorage\r\n");

   /* NOTE: we must make sure to set all out ptr parameters to NULL. */
   *lplpvStorage = NULL;

#if defined( OLE_SERVER )
   /* NOTE: in the SERVER ONLY version, item names identify pseudo
   **    objects. pseudo objects, do NOT have identifiable storage.
   */
   return E_FAIL;
#endif
#if defined( OLE_CNTR )
   // We can only return an IStorage* type pointer
   if (! IsEqualIID(riid, &IID_IStorage))
   {
      return E_FAIL;
   }

   return ContainerDoc_GetObjectStorage(
         (LPCONTAINERDOC)lpOleDoc,
         lpszItem,
         (LPSTORAGE FAR*)lplpvStorage
   );
#endif
}

// IOleItemContainer::IsRunning
STDMETHODIMP OleDoc_ItemCont_IsRunning(
      LPOLEITEMCONTAINER  lpThis,
      LPOLESTR            lpszItem
)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocOleItemContainerImpl FAR*)lpThis)->lpOleDoc;
   HRESULT hrErr;

   OLEDBG_BEGIN2("OleDoc_ItemCont_IsRunning\r\n")

   /* NOTE: Check if item name is valid. if so then return if
   **    Object is running. PseudoObjects in the Server version are
   **    always considered running. Ole objects in the container must
   **    be checked if they are running.
   */

#if defined( OLE_SERVER )
   hrErr = ServerDoc_IsRunning((LPSERVERDOC)lpOleDoc, lpszItem);
#endif
#if defined( OLE_CNTR )
   hrErr = ContainerDoc_IsRunning((LPCONTAINERDOC)lpOleDoc, lpszItem);
#endif

   OLEDBG_END2
   return hrErr;
}

/*************************************************************************
** OleDoc::IExternalConnection interface implementation
*************************************************************************/

// IExternalConnection::QueryInterface
STDMETHODIMP OleDoc_ExtConn_QueryInterface(
      LPEXTERNALCONNECTION    lpThis,
      REFIID                  riid,
      LPVOID FAR*             lplpvObj
)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocExternalConnectionImpl FAR*)lpThis)->lpOleDoc;

   return OleDoc_QueryInterface(lpOleDoc, riid, lplpvObj);
}

// IExternalConnection::AddRef
STDMETHODIMP_(ULONG) OleDoc_ExtConn_AddRef(LPEXTERNALCONNECTION lpThis)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocExternalConnectionImpl FAR*)lpThis)->lpOleDoc;

   OleDbgAddRefMethod(lpThis, "IExternalConnection");

   return OleDoc_AddRef(lpOleDoc);
}

// IExternalConnection::Release
STDMETHODIMP_(ULONG) OleDoc_ExtConn_Release (LPEXTERNALCONNECTION lpThis)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocExternalConnectionImpl FAR*)lpThis)->lpOleDoc;

   OleDbgReleaseMethod(lpThis, "IExternalConnection");

   return OleDoc_Release(lpOleDoc);
}

// IExternalConnection::AddConnection
STDMETHODIMP_(DWORD) OleDoc_ExtConn_AddConnection(
      LPEXTERNALCONNECTION    lpThis,
      DWORD                   extconn,
      DWORD                   reserved
)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocExternalConnectionImpl FAR*)lpThis)->lpOleDoc;

   if( extconn & EXTCONN_STRONG ) {

#if defined( _DEBUG )
      OleDbgOutRefCnt3(
            "OleDoc_ExtConn_AddConnection: dwStrongExtConn++\r\n",
            lpOleDoc,
            lpOleDoc->m_dwStrongExtConn + 1
      );
#endif
      return ++(lpOleDoc->m_dwStrongExtConn);
   } else
   {
      return 0;
   }
}

// IExternalConnection::ReleaseConnection
STDMETHODIMP_(DWORD) OleDoc_ExtConn_ReleaseConnection(
      LPEXTERNALCONNECTION    lpThis,
      DWORD                   extconn,
      DWORD                   reserved,
      BOOL                    fLastReleaseCloses
)
{
   LPOLEDOC lpOleDoc =
         ((struct CDocExternalConnectionImpl FAR*)lpThis)->lpOleDoc;

   if( extconn & EXTCONN_STRONG ){
      DWORD dwSave = --(lpOleDoc->m_dwStrongExtConn);
#if defined( _DEBUG )
      OLEDBG_BEGIN2( (fLastReleaseCloses ?
                  "OleDoc_ExtConn_ReleaseConnection(TRUE)\r\n" :
                  "OleDoc_ExtConn_ReleaseConnection(FALSE)\r\n") )
      OleDbgOutRefCnt3(
            "OleDoc_ExtConn_ReleaseConnection: dwStrongExtConn--\r\n",
            lpOleDoc,
            lpOleDoc->m_dwStrongExtConn
      );
      OleDbgAssertSz (
            lpOleDoc->m_dwStrongExtConn >= 0,
            "OleDoc_ExtConn_ReleaseConnection called with dwStrong == 0"
      );
#endif  // _DEBUG

      if( lpOleDoc->m_dwStrongExtConn == 0 && fLastReleaseCloses )
         OleDoc_Close(lpOleDoc, OLECLOSE_SAVEIFDIRTY);

      OLEDBG_END2
      return dwSave;
   } else
   {
      return 0;
   }
}

/*************************************************************************
** OleDoc Common Support Functions
*************************************************************************/

/* OleDoc_GetFullMoniker
** ---------------------
**    Return the full, absolute moniker of the document.
**
**    NOTE: the caller must release the pointer returned when done.
*/
LPMONIKER OleDoc_GetFullMoniker(LPOLEDOC lpOleDoc, DWORD dwAssign)
{
   LPMONIKER lpMoniker = NULL;

   OLEDBG_BEGIN3("OleDoc_GetFullMoniker\r\n")

   if (lpOleDoc->m_lpSrcDocOfCopy) {
      /* CASE I: this document was created for a copy or drag/drop
      **    operation. generate the moniker which identifies the
      **    source document of the original copy.
      */
      if (! lpOleDoc->m_fLinkSourceAvail)
         goto done;        // we already know a moniker is not available

      lpMoniker=OleDoc_GetFullMoniker(lpOleDoc->m_lpSrcDocOfCopy, dwAssign);
   }
   else if (lpOleDoc->m_lpFileMoniker) {

      /* CASE II: this document is a top-level user document (either
      **    file-based or untitled). return the FileMoniker stored
      **    with the document; it uniquely identifies the document.
      */
      // we must AddRef the moniker to pass out a ptr
      lpOleDoc->m_lpFileMoniker->lpVtbl->AddRef(lpOleDoc->m_lpFileMoniker);

      lpMoniker = lpOleDoc->m_lpFileMoniker;
   }

#if defined( OLE_SERVER )

   else if (((LPSERVERDOC)lpOleDoc)->m_lpOleClientSite) {

      /* CASE III: this document is an embedded object, ask our
      **    container for our moniker.
      */
      OLEDBG_BEGIN2("IOleClientSite::GetMoniker called\r\n");
      ((LPSERVERDOC)lpOleDoc)->m_lpOleClientSite->lpVtbl->GetMoniker(
            ((LPSERVERDOC)lpOleDoc)->m_lpOleClientSite,
            dwAssign,
            OLEWHICHMK_OBJFULL,
            &lpMoniker
      );
      OLEDBG_END2
   }

#endif

   else {
      lpMoniker = NULL;
   }

done:
   OLEDBG_END3
   return lpMoniker;
}

/* OleDoc_DocRenamedUpdate
** -----------------------
**    Update the documents registration in the running object table (ROT).
**    Also inform all embedded OLE objects (container only) and/or psedudo
**    objects (server only) that the name of the document has changed.
*/
void OleDoc_DocRenamedUpdate(LPOLEDOC lpOleDoc, LPMONIKER lpmkDoc)
{
   OLEDBG_BEGIN3("OleDoc_DocRenamedUpdate\r\n")

   OleDoc_AddRef(lpOleDoc);

   /* NOTE: we must re-register ourselves as running when we
   **    get a new moniker assigned (ie. when we are renamed).
   */
   OLEDBG_BEGIN3("OleStdRegisterAsRunning called\r\n")
   OleStdRegisterAsRunning(
         (LPUNKNOWN)&lpOleDoc->m_Unknown,
         lpmkDoc,
         &lpOleDoc->m_dwRegROT
   );
   OLEDBG_END3

#if defined( OLE_SERVER )
   {
      LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOleDoc;
      LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;

      /* NOTE: inform any linking clients that the document has been
      **    renamed.
      */
      ServerDoc_SendAdvise (
            lpServerDoc,
            OLE_ONRENAME,
            lpmkDoc,
            0        /* advf -- not relevant here */
      );

      /* NOTE: inform any clients of pseudo objects
      **    within our document, that our document's
      **    Moniker has changed.
      */
      ServerNameTable_InformAllPseudoObjectsDocRenamed(
            (LPSERVERNAMETABLE)lpOutlineDoc->m_lpNameTable, lpmkDoc);
   }
#endif
#if defined( OLE_CNTR )
   {
      LPCONTAINERDOC lpContainerDoc = (LPCONTAINERDOC)lpOleDoc;

      /* NOTE: must tell all OLE objects that our container
      **    moniker changed.
      */
      ContainerDoc_InformAllOleObjectsDocRenamed(
            lpContainerDoc,
            lpmkDoc
      );
   }
#endif

   OleDoc_Release(lpOleDoc);       // release artificial AddRef above
   OLEDBG_END3
}

#if defined( OLE_SERVER )

/*************************************************************************
** ServerDoc Supprt Functions Used by Server versions
*************************************************************************/

/* ServerDoc_PseudoObjLockDoc
** --------------------------
**    Add a lock on the Doc on behalf of the PseudoObject. the Doc may not
**    close while the Doc exists.
**
**    when a pseudo object is first created, it calls this method to
**    guarantee that the document stays alive (PseudoObj_Init).
**    when a pseudo object is destroyed, it call
**    ServerDoc_PseudoObjUnlockDoc to release this hold on the document.
*/
void ServerDoc_PseudoObjLockDoc(LPSERVERDOC lpServerDoc)
{
   LPOLEDOC lpOleDoc = (LPOLEDOC)lpServerDoc;
   ULONG cPseudoObj;

   cPseudoObj = ++lpServerDoc->m_cPseudoObj;

#if defined( _DEBUG )
   OleDbgOutRefCnt3(
         "ServerDoc_PseudoObjLockDoc: cPseudoObj++\r\n",
         lpServerDoc,
         cPseudoObj
   );
#endif
   OleDoc_Lock(lpOleDoc, TRUE /* fLock */, 0 /* not applicable */);
   return;
}

/* ServerDoc_PseudoObjUnlockDoc
** ----------------------------
**    Release the lock on the Doc on behalf of the PseudoObject. if this was
**    the last lock on the Doc, then it will shutdown.
*/
void ServerDoc_PseudoObjUnlockDoc(
      LPSERVERDOC         lpServerDoc,
      LPPSEUDOOBJ         lpPseudoObj
)
{
   ULONG cPseudoObj;
   LPOLEDOC lpOleDoc = (LPOLEDOC)lpServerDoc;
   OLEDBG_BEGIN3("ServerDoc_PseudoObjUnlockDoc\r\n")

   /* NOTE: when there are no active pseudo objects in the Doc and
   **    the Doc is not visible, and if there are no outstanding locks
   **    on the Doc, then this is a "silent update"
   **    situation. our Doc is being used programatically by some
   **    client; it is NOT accessible to the user because it is
   **    NOT visible. thus since all Locks have been released, we
   **    will close the document. if the app is only running due
   **    to the presence of this document, then the app will now
   **    also shut down.
   */
   cPseudoObj = --lpServerDoc->m_cPseudoObj;

#if defined( _DEBUG )
   OleDbgAssertSz (
         lpServerDoc->m_cPseudoObj >= 0,
         "PseudoObjUnlockDoc called with cPseudoObj == 0"
   );

   OleDbgOutRefCnt3(
         "ServerDoc_PseudoObjUnlockDoc: cPseudoObj--\r\n",
         lpServerDoc,
         cPseudoObj
   );
#endif
   OleDoc_Lock(lpOleDoc, FALSE /* fLock */, TRUE /* fLastUnlockReleases */);

   OLEDBG_END3
   return;
}

/* ServerDoc_GetObject
** -------------------
**
**    Return a pointer to an object identified by an item string
**    (lpszItem). For a server-only app, the object returned will be a
**    pseudo object.
*/
HRESULT ServerDoc_GetObject(
      LPSERVERDOC             lpServerDoc,
      LPOLESTR                lpszItem,
      REFIID                  riid,
      LPVOID FAR*             lplpvObject
)
{
   LPPSEUDOOBJ lpPseudoObj;
   LPSERVERNAMETABLE lpServerNameTable =
         (LPSERVERNAMETABLE)((LPOUTLINEDOC)lpServerDoc)->m_lpNameTable;

   *lplpvObject = NULL;

   /* Get the PseudoObj which corresponds to an item name. if the item
   **    name does NOT exist in the name table then NO object is
   **    returned. the ServerNameTable_GetPseudoObj routine finds a
   **    name entry corresponding to the item name, it then checks if
   **    a PseudoObj has already been allocated. if so, it returns the
   **    existing object, otherwise it allocates a new PseudoObj.
   */
   lpPseudoObj = ServerNameTable_GetPseudoObj(
         lpServerNameTable,
         lpszItem,
         lpServerDoc
   );

   if (! lpPseudoObj) {
      *lplpvObject = NULL;
      return MK_E_NOOBJECT;
   }

   // return the desired interface pointer of the pseudo object.
   return PseudoObj_QueryInterface(lpPseudoObj, riid, lplpvObject);
}

/* ServerDoc_IsRunning
** -------------------
**
**    Check if the object identified by an item string (lpszItem) is in
**    the running state. For a server-only app, if the item name exists in
**    in the NameTable then the item name is considered running.
**    IOleItemContainer::GetObject would succeed.
*/

HRESULT ServerDoc_IsRunning(LPSERVERDOC lpServerDoc, LPOLESTR lpszItem)

{
   LPOUTLINENAMETABLE lpOutlineNameTable =
         ((LPOUTLINEDOC)lpServerDoc)->m_lpNameTable;
   LPSERVERNAME lpServerName;

   lpServerName = (LPSERVERNAME)OutlineNameTable_FindName(
         lpOutlineNameTable,
         lpszItem
   );

   if (lpServerName)
      return NOERROR;
   else
      return MK_E_NOOBJECT;
}

/* ServerDoc_GetSelRelMoniker
** --------------------------
**    Retrieve the relative item moniker which identifies the given
**    selection (lplrSel).
**
**    Returns NULL if a moniker can NOT be created.
*/

LPMONIKER ServerDoc_GetSelRelMoniker(
      LPSERVERDOC             lpServerDoc,
      LPLINERANGE             lplrSel,
      DWORD                   dwAssign
)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   LPSERVERNAMETABLE lpServerNameTable =
         (LPSERVERNAMETABLE)lpOutlineDoc->m_lpNameTable;
   LPOUTLINENAMETABLE lpOutlineNameTable =
         (LPOUTLINENAMETABLE)lpServerNameTable;
   LPOUTLINENAME lpOutlineName;
   LPMONIKER lpmk;

   lpOutlineName=OutlineNameTable_FindNamedRange(lpOutlineNameTable,lplrSel);

   if (lpOutlineName) {
      /* the selection range already has a name assigned */
      CreateItemMoniker(OLESTR("\\"), lpOutlineName->m_szName, &lpmk);
   } else {
      char szbuf[MAXNAMESIZE];
      OLECHAR szUniBuf[MAXNAMESIZE];

      switch (dwAssign) {

         case GETMONIKER_FORCEASSIGN:

            /* Force the assignment of the name. This is called when a
            **    Paste Link actually occurs. At this point we want to
            **    create a Name and add it to the NameTable in order to
            **    track the source of the link. This name (as all
            **    names) will be updated upon editing of the document.
            */
            wsprintf(
                  szbuf,
                  "%s %ld",
                  (LPSTR)DEFRANGENAMEPREFIX,
                  ++(lpServerDoc->m_nNextRangeNo)
            );

            lpOutlineName = OutlineApp_CreateName(lpOutlineApp);

            if (lpOutlineName) {
               A2W (szbuf, lpOutlineName->m_szName, OLEUI_CCHPATHMAX);
               lpOutlineName->m_nStartLine = lplrSel->m_nStartLine;
               lpOutlineName->m_nEndLine = lplrSel->m_nEndLine;
               OutlineDoc_AddName(lpOutlineDoc, lpOutlineName);
            } else {
               // REVIEW: do we need "Out-of-Memory" error message here?
            }
            break;

         case GETMONIKER_TEMPFORUSER:

            /* Create a name to show to the user in the Paste
            **    Special dialog but do NOT yet incur the overhead
            **    of adding a Name to the NameTable. The Moniker
            **    generated should be useful to display to the user
            **    to indicate the source of the copy, but will NOT
            **    be used to create a link directly (the caller
            **    should ask again for a moniker specifying FORCEASSIGN).
            **    we will generate the name that would be the next
            **    auto-generated range name, BUT will NOT actually
            **    increment the range counter.
            */
            wsprintf(
                  szbuf,
                  "%s %ld",
                  (LPSTR)DEFRANGENAMEPREFIX,
                  (lpServerDoc->m_nNextRangeNo)+1
            );
            break;

         case GETMONIKER_ONLYIFTHERE:

            /* the caller only wants a name if one has already been
            **    assigned. we have already above checked if the
            **    current selection has a name, so we will simply
            **    return NULL here.
            */
            return NULL;    // no moniker is assigned

         default:
            return NULL;    // unknown flag given
      }

      A2W (szbuf, szUniBuf, MAXNAMESIZE);
      CreateItemMoniker(OLESTR("\\"), szUniBuf, &lpmk);
   }

   return lpmk;
}

/* ServerDoc_GetSelFullMoniker
** ---------------------------
**    Retrieve the full absolute moniker which identifies the given
**    selection (lplrSel).
**    this moniker is created as a composite of the absolute moniker for
**    the entire document appended with an item moniker which identifies
**    the selection relative to the document.
**    Returns NULL if a moniker can NOT be created.
*/
LPMONIKER ServerDoc_GetSelFullMoniker(
      LPSERVERDOC             lpServerDoc,
      LPLINERANGE             lplrSel,
      DWORD                   dwAssign
)
{
   LPMONIKER lpmkDoc = NULL;
   LPMONIKER lpmkItem = NULL;
   LPMONIKER lpmkFull = NULL;

   lpmkDoc = OleDoc_GetFullMoniker(
         (LPOLEDOC)lpServerDoc,
         dwAssign
   );
   if (! lpmkDoc) return NULL;

   lpmkItem = ServerDoc_GetSelRelMoniker(
         lpServerDoc,
         lplrSel,
         dwAssign
   );
   if (lpmkItem) {
      CreateGenericComposite(lpmkDoc, lpmkItem, (LPMONIKER FAR*)&lpmkFull);
      OleStdRelease((LPUNKNOWN)lpmkItem);
   }

   if (lpmkDoc)
      OleStdRelease((LPUNKNOWN)lpmkDoc);

   return lpmkFull;
}

/* ServerNameTable_EditLineUpdate
 * -------------------------------
 *
 *      Update the table when a line at nEditIndex is edited.
 */
void ServerNameTable_EditLineUpdate(
      LPSERVERNAMETABLE       lpServerNameTable,
      int                     nEditIndex
)
{
   LPOUTLINENAMETABLE lpOutlineNameTable =
                              (LPOUTLINENAMETABLE)lpServerNameTable;
   LPOUTLINENAME lpOutlineName;
   LINERANGE lrSel;
   LPPSEUDOOBJ lpPseudoObj;
   int i;

   for(i = 0; i < lpOutlineNameTable->m_nCount; i++) {
      lpOutlineName=OutlineNameTable_GetName(lpOutlineNameTable, i);

      lpPseudoObj = ((LPSERVERNAME)lpOutlineName)->m_lpPseudoObj;

      /* if there is a pseudo object associated with this name, then
      **    check if the line that was modified is included within
      **    the named range.
      */
      if (lpPseudoObj) {
         OutlineName_GetSel(lpOutlineName, &lrSel);

         if(((int)lrSel.m_nStartLine <= nEditIndex) &&
            ((int)lrSel.m_nEndLine >= nEditIndex)) {

            // inform linking clients data has changed
            PseudoObj_SendAdvise(
                  lpPseudoObj,
                  OLE_ONDATACHANGE,
                  NULL,   /* lpmkDoc -- not relevant here */
                  0       /* advf -- no flags necessary */
            );
         }

      }
   }
}

/* ServerNameTable_InformAllPseudoObjectsDocRenamed
 * ------------------------------------------------
 *
 *      Inform all pseudo object clients that the name of the pseudo
 *      object has changed.
 */
void ServerNameTable_InformAllPseudoObjectsDocRenamed(
      LPSERVERNAMETABLE       lpServerNameTable,
      LPMONIKER               lpmkDoc
)
{
   LPOUTLINENAMETABLE lpOutlineNameTable =
                              (LPOUTLINENAMETABLE)lpServerNameTable;
   LPOUTLINENAME lpOutlineName;
   LPPSEUDOOBJ lpPseudoObj;
   LPMONIKER lpmkObj;
   int i;

   OLEDBG_BEGIN2("ServerNameTable_InformAllPseudoObjectsDocRenamed\r\n");

   for(i = 0; i < lpOutlineNameTable->m_nCount; i++) {
      lpOutlineName=OutlineNameTable_GetName(lpOutlineNameTable, i);

      lpPseudoObj = ((LPSERVERNAME)lpOutlineName)->m_lpPseudoObj;

      /* if there is a pseudo object associated with this name, then
      **    send OnRename advise to its linking clients.
      */
      if (lpPseudoObj &&
         ((lpmkObj=PseudoObj_GetFullMoniker(lpPseudoObj,lpmkDoc))!=NULL)) {

         // inform the clients that the name has changed
         PseudoObj_SendAdvise (
               lpPseudoObj,
               OLE_ONRENAME,
               lpmkObj,
               0           /* advf -- not relevant here */
         );
      }
   }
   OLEDBG_END2
}

/* ServerNameTable_InformAllPseudoObjectsDocSaved
 * ------------------------------------------------
 *
 *      Inform all pseudo object clients that the name of the pseudo
 *      object has changed.
 */
void ServerNameTable_InformAllPseudoObjectsDocSaved(
      LPSERVERNAMETABLE       lpServerNameTable,
      LPMONIKER               lpmkDoc
)
{
   LPOUTLINENAMETABLE lpOutlineNameTable =
                              (LPOUTLINENAMETABLE)lpServerNameTable;
   LPOUTLINENAME lpOutlineName;
   LPPSEUDOOBJ lpPseudoObj;
   LPMONIKER lpmkObj;
   int i;

   OLEDBG_BEGIN2("ServerNameTable_InformAllPseudoObjectsDocSaved\r\n");

   for(i = 0; i < lpOutlineNameTable->m_nCount; i++) {
      lpOutlineName=OutlineNameTable_GetName(lpOutlineNameTable, i);

      lpPseudoObj = ((LPSERVERNAME)lpOutlineName)->m_lpPseudoObj;

      /* if there is a pseudo object associated with this name, then
      **    send OnSave advise to its linking clients.
      */
      if (lpPseudoObj &&
         ((lpmkObj=PseudoObj_GetFullMoniker(lpPseudoObj,lpmkDoc))!=NULL)) {

         // inform the clients that the name has been saved
         PseudoObj_SendAdvise (
               lpPseudoObj,
               OLE_ONSAVE,
               NULL,   /* lpmkDoc -- not relevant here */
               0       /* advf -- not relevant here */
         );
      }
   }
   OLEDBG_END2
}

/* ServerNameTable_SendPendingAdvises
 * ----------------------------------
 *
 *      Send any pending change notifications for pseudo objects.
 *  while ReDraw is diabled on the ServerDoc, then change advise
 *  notifications are not sent to pseudo object clients.
 */
void ServerNameTable_SendPendingAdvises(LPSERVERNAMETABLE lpServerNameTable)
{
   LPOUTLINENAMETABLE lpOutlineNameTable =
                              (LPOUTLINENAMETABLE)lpServerNameTable;
   LPSERVERNAME lpServerName;
   int i;

   for(i = 0; i < lpOutlineNameTable->m_nCount; i++) {
      lpServerName = (LPSERVERNAME)OutlineNameTable_GetName(
            lpOutlineNameTable,
            i
      );
      ServerName_SendPendingAdvises(lpServerName);
   }
}

/* ServerNameTable_GetPseudoObj
** ----------------------------
**
**    Return a pointer to a pseudo object identified by an item string
**    (lpszItem). if the pseudo object already exists, then return the
**    existing object, otherwise allocate a new pseudo object.
*/
LPPSEUDOOBJ ServerNameTable_GetPseudoObj(
      LPSERVERNAMETABLE       lpServerNameTable,
      LPOLESTR                lpszItem,
      LPSERVERDOC             lpServerDoc
)
{
   LPSERVERNAME lpServerName;

   lpServerName = (LPSERVERNAME)OutlineNameTable_FindName(
         (LPOUTLINENAMETABLE)lpServerNameTable,
         lpszItem
   );

   if (lpServerName)
      return ServerName_GetPseudoObj(lpServerName, lpServerDoc);
   else
      return NULL;
}

/* ServerNameTable_CloseAllPseudoObjs
 * ----------------------------------
 *
 *  Force all pseudo objects to close. this results in sending OnClose
 *  notification to each pseudo object's linking clients.
 */
void ServerNameTable_CloseAllPseudoObjs(LPSERVERNAMETABLE lpServerNameTable)
{
   LPOUTLINENAMETABLE lpOutlineNameTable =
                              (LPOUTLINENAMETABLE)lpServerNameTable;
   LPSERVERNAME lpServerName;
   int i;

   OLEDBG_BEGIN3("ServerNameTable_CloseAllPseudoObjs\r\n")

   for(i = 0; i < lpOutlineNameTable->m_nCount; i++) {
      lpServerName = (LPSERVERNAME)OutlineNameTable_GetName(
            lpOutlineNameTable,
            i
      );
        ServerName_ClosePseudoObj(lpServerName);
   }

   OLEDBG_END3
}

/* ServerName_SetSel
 * -----------------
 *
 *      Change the line range of a  name.
 */
void ServerName_SetSel(
      LPSERVERNAME            lpServerName,
      LPLINERANGE             lplrSel,
      BOOL                    fRangeModified
)
{
   LPOUTLINENAME lpOutlineName = (LPOUTLINENAME)lpServerName;
   BOOL fPseudoObjChanged = fRangeModified;

   if (lpOutlineName->m_nStartLine != lplrSel->m_nStartLine) {
      lpOutlineName->m_nStartLine = lplrSel->m_nStartLine;
      fPseudoObjChanged = TRUE;
   }

   if (lpOutlineName->m_nEndLine != lplrSel->m_nEndLine) {
      lpOutlineName->m_nEndLine = lplrSel->m_nEndLine;
      fPseudoObjChanged = TRUE;
   }

   /* NOTE: if the range of an active pseudo object has
   **    changed, then inform any linking clients that the object
   **    has changed.
   */
   if (lpServerName->m_lpPseudoObj && fPseudoObjChanged) {
      PseudoObj_SendAdvise(
            lpServerName->m_lpPseudoObj,
            OLE_ONDATACHANGE,
            NULL,   /* lpmkDoc -- not relevant here */
            0       /* advf -- no flags necessary */
      );
   }
}

/* ServerName_SendPendingAdvises
 * -----------------------------
 *
 *      Send any pending change notifications for the associated
 *  pseudo objects for this name (if one exists).
 *  while ReDraw is diabled on the ServerDoc, then change advise
 *  notifications are not sent to pseudo object clients.
 */
void ServerName_SendPendingAdvises(LPSERVERNAME lpServerName)
{
   if (! lpServerName->m_lpPseudoObj)
      return;     // no associated pseudo object

   if (lpServerName->m_lpPseudoObj->m_fDataChanged)
      PseudoObj_SendAdvise(
            lpServerName->m_lpPseudoObj,
            OLE_ONDATACHANGE,
            NULL,   /* lpmkDoc -- not relevant here */
            0       /* advf -- no flags necessary */
      );
}

/* ServerName_GetPseudoObj
** -----------------------
**
**    Return a pointer to a pseudo object associated to a ServerName.
**    if the pseudo object already exists, then return the
**    existing object, otherwise allocate a new pseudo object.
**
**    NOTE: the PseudoObj is returned with a 0 refcnt if first created,
**    else the existing refcnt is unchanged.
*/
LPPSEUDOOBJ ServerName_GetPseudoObj(
      LPSERVERNAME            lpServerName,
      LPSERVERDOC             lpServerDoc
)
{
   // Check if a PseudoObj already exists
   if (lpServerName->m_lpPseudoObj)
      return lpServerName->m_lpPseudoObj;

   // A PseudoObj does NOT already exist, allocate a new one.
   lpServerName->m_lpPseudoObj=(LPPSEUDOOBJ) New((DWORD)sizeof(PSEUDOOBJ));
   if (lpServerName->m_lpPseudoObj == NULL) {
      OleDbgAssertSz(lpServerName->m_lpPseudoObj != NULL,   "Error allocating PseudoObj");
      return NULL;
   }

   PseudoObj_Init(lpServerName->m_lpPseudoObj, lpServerName, lpServerDoc);
   return lpServerName->m_lpPseudoObj;
}

/* ServerName_ClosePseudoObj
 * -------------------------
 *
 *      if there is an associated pseudo objects for this name (if one
 *  exists), then close it. this results in sending OnClose
 *  notification to the pseudo object's linking clients.
 */
void ServerName_ClosePseudoObj(LPSERVERNAME lpServerName)
{
   if (!lpServerName || !lpServerName->m_lpPseudoObj)
      return;     // no associated pseudo object

   PseudoObj_Close(lpServerName->m_lpPseudoObj);
}

#endif  // OLE_SERVER

#if defined( OLE_CNTR )

/*************************************************************************
** ContainerDoc Supprt Functions Used by Container versions
*************************************************************************/

/* ContainerLine_GetRelMoniker
** ---------------------------
**    Retrieve the relative item moniker which identifies the OLE object
**    relative to the container document.
**
**    Returns NULL if a moniker can NOT be created.
*/
LPMONIKER ContainerLine_GetRelMoniker(
      LPCONTAINERLINE         lpContainerLine,
      DWORD                   dwAssign
)
{
   LPMONIKER lpmk = NULL;

   /* NOTE: we should only give out a moniker for the OLE object
   **    if the object is allowed to be linked to from the inside. if
   **    so we are allowed to give out a moniker which binds to the
   **    running OLE object). if the object is an OLE 2.0 embedded
   **    object then it is allowed to be linked to from the inside. if
   **    the object is either an OleLink or an OLE 1.0 embedding
   **    then it can not be linked to from the inside.
   **    if we were a container/server app then we could offer linking
   **    to the outside of the object (ie. a pseudo object within our
   **    document). we are a container only app that does not support
   **    linking to ranges of its data.
   */

   switch (dwAssign) {

      case GETMONIKER_FORCEASSIGN:

            /* Force the assignment of the name. This is called when a
            **    Paste Link actually occurs. From now on we want
            **    to inform the OLE object that its moniker is
            **    assigned and is thus necessary to register itself
            **    in the RunningObjectTable.
            */
            CreateItemMoniker(
                  OLESTR("\\"), lpContainerLine->m_szStgName, &lpmk);

            /* NOTE: if the OLE object is already loaded and it
            **    is being assigned a moniker for the first time,
            **    then we need to inform it that it now has a moniker
            **    assigned by calling IOleObject::SetMoniker. this
            **    will force the OLE object to register in the
            **    RunningObjectTable when it enters the running
            **    state. if the object is not currently loaded,
            **    SetMoniker will be called automatically later when
            **    the object is loaded by the function
            **    ContainerLine_LoadOleObject.
            */
            if (! lpContainerLine->m_fMonikerAssigned) {

               /* we must remember forever more that this object has a
               **    moniker assigned.
               */
               lpContainerLine->m_fMonikerAssigned = TRUE;

               // we are now dirty and must be saved
               OutlineDoc_SetModified(
                     (LPOUTLINEDOC)lpContainerLine->m_lpDoc,
                     TRUE,   /* fModified */
                     FALSE,  /* fDataChanged--N/A for container ver. */
                     FALSE   /* fSizeChanged--N/A for container ver. */
               );

               if (lpContainerLine->m_lpOleObj) {
                  OLEDBG_BEGIN2("IOleObject::SetMoniker called\r\n")
                  lpContainerLine->m_lpOleObj->lpVtbl->SetMoniker(
                        lpContainerLine->m_lpOleObj,
                        OLEWHICHMK_OBJREL,
                        lpmk
                  );
                  OLEDBG_END2
               }
            }
            break;

      case GETMONIKER_ONLYIFTHERE:

            /* If the OLE object currently has a moniker assigned,
            **    then return it.
            */
            if (lpContainerLine->m_fMonikerAssigned) {
               CreateItemMoniker(
                     OLESTR("\\"),
                     lpContainerLine->m_szStgName,
                     &lpmk
               );
            }
            break;

      case GETMONIKER_TEMPFORUSER:

            /* Return the moniker that would be used for the OLE
            **    object but do NOT force moniker assignment at
            **    this point. Since our strategy is to use the
            **    storage name of the object as its item name, we
            **    can simply create the corresponding ItemMoniker
            **    (indepenedent of whether the moniker is currently
            **    assigned or not).
            */
            CreateItemMoniker(
                  OLESTR("\\"),
                  lpContainerLine->m_szStgName,
                  &lpmk
            );
            break;

      case GETMONIKER_UNASSIGN:

            lpContainerLine->m_fMonikerAssigned = FALSE;
            break;

   }

   return lpmk;
}

/* ContainerLine_GetFullMoniker
** ----------------------------
**    Retrieve the full absolute moniker which identifies the OLE object
**    in the container document.
**    this moniker is created as a composite of the absolute moniker for
**    the entire document appended with an item moniker which identifies
**    the OLE object relative to the document.
**    Returns NULL if a moniker can NOT be created.
*/
LPMONIKER ContainerLine_GetFullMoniker(
      LPCONTAINERLINE         lpContainerLine,
      DWORD                   dwAssign
)
{
   LPMONIKER lpmkDoc = NULL;
   LPMONIKER lpmkItem = NULL;
   LPMONIKER lpmkFull = NULL;

   lpmkDoc = OleDoc_GetFullMoniker(
         (LPOLEDOC)lpContainerLine->m_lpDoc,
         dwAssign
   );
   if (! lpmkDoc)
   {
       return NULL;
   }
   lpmkItem = ContainerLine_GetRelMoniker(lpContainerLine, dwAssign);

   if (lpmkItem) {
      CreateGenericComposite(lpmkDoc, lpmkItem, (LPMONIKER FAR*)&lpmkFull);
      OleStdRelease((LPUNKNOWN)lpmkItem);
   }

   if (lpmkDoc)
      OleStdRelease((LPUNKNOWN)lpmkDoc);

   return lpmkFull;
}

/* ContainerDoc_InformAllOleObjectsDocRenamed
** ------------------------------------------
**    Inform all OLE objects that the name of the ContainerDoc has changed.
*/
void ContainerDoc_InformAllOleObjectsDocRenamed(
      LPCONTAINERDOC          lpContainerDoc,
      LPMONIKER               lpmkDoc
)
{
   LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   int i;
   LPLINE lpLine;

   // artificial AddRef in case someone releases object during call
   OleDoc_AddRef((LPOLEDOC)lpContainerDoc);

   for (i = 0; i < lpLL->m_nNumLines; i++) {
      lpLine=LineList_GetLine(lpLL, i);

      if (lpLine && (Line_GetLineType(lpLine)==CONTAINERLINETYPE)) {
         LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)lpLine;

         /* NOTE: if the OLE object is already loaded AND the
         **    object already has a moniker assigned, then we need
         **    to inform it that the moniker of the ContainerDoc has
         **    changed. of course, this means the full moniker of
         **    the object has changed. to do this we call
         **    IOleObject::SetMoniker. this will force the OLE
         **    object to re-register in the RunningObjectTable if it
         **    is currently in the running state. it is not in the
         **    running state, the object handler can make not that
         **    the object has a new moniker. if the object is not
         **    currently loaded, SetMoniker will be called
         **    automatically later when the object is loaded by the
         **    function ContainerLine_LoadOleObject.
         **    also if the object is a linked object, we always want
         **    to call SetMoniker on the link so that in case the
         **    link source is contained within our same container,
         **    the link source will be tracked. the link rebuilds
         **    its absolute moniker if it has a relative moniker.
         */
         if (lpContainerLine->m_lpOleObj) {
            if (lpContainerLine->m_fMonikerAssigned ||
               lpContainerLine->m_dwLinkType != 0) {
               OLEDBG_BEGIN2("IOleObject::SetMoniker called\r\n")
               lpContainerLine->m_lpOleObj->lpVtbl->SetMoniker(
                     lpContainerLine->m_lpOleObj,
                     OLEWHICHMK_CONTAINER,
                     lpmkDoc
               );
               OLEDBG_END2
            }

            /* NOTE: we must call IOleObject::SetHostNames so
            **    any open objects can update their window titles.
            */
            OLEDBG_BEGIN2("IOleObject::SetHostNames called\r\n")
            lpContainerLine->m_lpOleObj->lpVtbl->SetHostNames(
                  lpContainerLine->m_lpOleObj,
                  /*(LPOLESTR)*/APPNAME,
                  ((LPOUTLINEDOC)lpContainerDoc)->m_lpszDocTitle
            );
            OLEDBG_END2
         }
      }
   }
   // release artificial AddRef
   OleDoc_Release((LPOLEDOC)lpContainerDoc);
}

/* ContainerDoc_GetObject
** ----------------------
**    Return a pointer to the desired interface of an object identified
**    by an item string (lpszItem). the object returned will be an OLE
**    object (either link or embedding).
**
**      NOTE: we must force the object to run because we are
**          REQUIRED to return a pointer the OLE object in the
**          RUNNING state.
**
**    dwSpeedNeeded indicates how long the caller is willing
**    to wait for us to get the object:
**      BINDSPEED_IMMEDIATE -- only if obj already loaded && IsRunning
**      BINDSPEED_MODERATE  -- load obj if necessary && if IsRunning
**      BINDSPEED_INDEFINITE-- force obj to load and run if necessary
*/
HRESULT ContainerDoc_GetObject(
      LPCONTAINERDOC          lpContainerDoc,
      LPOLESTR                lpszItem,
      DWORD                   dwSpeedNeeded,
      REFIID                  riid,
      LPVOID FAR*             lplpvObject
)
{
   LPLINELIST  lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   int         i;
   LPLINE      lpLine;
   BOOL        fMatchFound = FALSE;
   DWORD       dwStatus;
   HRESULT     hrErr;

   *lplpvObject = NULL;

   for (i = 0; i < lpLL->m_nNumLines; i++) {
      lpLine=LineList_GetLine(lpLL, i);

      if (lpLine && (Line_GetLineType(lpLine)==CONTAINERLINETYPE)) {
         LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)lpLine;

         if (OLESTRCMP(lpContainerLine->m_szStgName, lpszItem) == 0) {

            fMatchFound = TRUE;     // valid item name

            // check if object is loaded.
            if (lpContainerLine->m_lpOleObj == NULL) {

               // if BINDSPEED_IMMEDIATE is requested, object must
               // ALREADY be loadded.
               if (dwSpeedNeeded == BINDSPEED_IMMEDIATE)
                  return MK_E_EXCEEDEDDEADLINE;

               ContainerLine_LoadOleObject(lpContainerLine);
               if (! lpContainerLine->m_lpOleObj)
                  return E_OUTOFMEMORY;
            }

            /* NOTE: check if the object is allowed to be linked
            **    to from the inside (ie. we are allowed to
            **    give out a moniker which binds to the running
            **    OLE object). if the object is an OLE
            **    2.0 embedded object then it is allowed to be
            **    linked to from the inside. if the object is
            **    either an OleLink or an OLE 1.0 embedding
            **    then it can not be linked to from the inside.
            **    if we were a container/server app then we
            **    could offer linking to the outside of the
            **    object (ie. a pseudo object within our
            **    document). we are a container only app that
            **    does not support linking to ranges of its data.
            */
            OLEDBG_BEGIN2("IOleObject::GetMiscStatus called\r\n");
            lpContainerLine->m_lpOleObj->lpVtbl->GetMiscStatus(
                  lpContainerLine->m_lpOleObj,
                  DVASPECT_CONTENT, /* aspect is not important */
                  (LPDWORD)&dwStatus
            );
            OLEDBG_END2
            if (dwStatus & OLEMISC_CANTLINKINSIDE)
            {
               return MK_E_NOOBJECT;
            }

            // check if object is running.
            if (! OleIsRunning(lpContainerLine->m_lpOleObj)) {

               // if BINDSPEED_MODERATE is requested, object must
               // ALREADY be running.
               if (dwSpeedNeeded == BINDSPEED_MODERATE)
               {
                  return MK_E_EXCEEDEDDEADLINE;
               }

               /* NOTE: we have found a match for the item name.
               **    now we must return a pointer to the desired
               **    interface on the RUNNING object. we must
               **    carefully load the object and initially ask for
               **    an interface that we are sure the loaded form of
               **    the object supports. if we immediately ask the
               **    loaded object for the desired interface, the
               **    QueryInterface call might fail if it is an
               **    interface that is supported only when the object
               **    is running. thus we force the object to load and
               **    return its IUnknown*. then we force the object to
               **    run, and then finally, we can ask for the
               **    actually requested interface.
               */
               hrErr = ContainerLine_RunOleObject(lpContainerLine);
               if (hrErr != NOERROR) {
                  return hrErr;
               }
            }

            // Retrieve the requested interface
            *lplpvObject = OleStdQueryInterface(
                  (LPUNKNOWN)lpContainerLine->m_lpOleObj, riid);

            break;  // Match FOUND!
         }
      }
   }

   if (*lplpvObject != NULL) {
      return NOERROR;
   } else
   {
      return (fMatchFound ? E_NOINTERFACE
                     : MK_E_NOOBJECT);
   }
}

/* ContainerDoc_GetObjectStorage

** -----------------------------
**    Return a pointer to the IStorage* used by the object identified
**    by an item string (lpszItem). the object identified could be either
**    an OLE object (either link or embedding).
*/
HRESULT ContainerDoc_GetObjectStorage(
      LPCONTAINERDOC          lpContainerDoc,
      LPOLESTR                lpszItem,
      LPSTORAGE FAR*          lplpStg
)
{
   LPLINELIST lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   int i;
   LPLINE lpLine;

   *lplpStg = NULL;

   for (i = 0; i < lpLL->m_nNumLines; i++) {
      lpLine=LineList_GetLine(lpLL, i);

      if (lpLine && (Line_GetLineType(lpLine)==CONTAINERLINETYPE)) {
         LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)lpLine;

         if (OLESTRCMP(lpContainerLine->m_szStgName, lpszItem) == 0) {

            *lplpStg = lpContainerLine->m_lpStg;
            break;  // Match FOUND!
         }
      }
   }

   if (*lplpStg != NULL) {
      return NOERROR;
   } else
   {
      return MK_E_NOOBJECT;
   }
}

/* ContainerDoc_IsRunning
** ----------------------
**    Check if the object identified by an item string (lpszItem) is in
**    the running state.
**    For a container-only app, a check is made if the OLE object
**    associated with the item name is running.
*/
HRESULT ContainerDoc_IsRunning(LPCONTAINERDOC   lpContainerDoc, LPOLESTR lpszItem)
{
   LPLINELIST  lpLL = &((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
   int         i;
   LPLINE      lpLine;
   DWORD       dwStatus;

   for (i = 0; i < lpLL->m_nNumLines; i++) {
      lpLine=LineList_GetLine(lpLL, i);

      if (lpLine && (Line_GetLineType(lpLine)==CONTAINERLINETYPE)) {
         LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)lpLine;

         if (OLESTRCPY(lpContainerLine->m_szStgName, lpszItem) == 0) {

            /* NOTE: we have found a match for the item name.
            **    now we must check if the OLE object is running.
            **    we will load the object if not already loaded.
            */
            if (! lpContainerLine->m_lpOleObj) {
               ContainerLine_LoadOleObject(lpContainerLine);
               if (! lpContainerLine->m_lpOleObj)
                  return E_OUTOFMEMORY;
            }

            /* NOTE: check if the object is allowed to be linked
            **    to from the inside (ie. we are allowed to
            **    give out a moniker which binds to the running
            **    OLE object). if the object is an OLE
            **    2.0 embedded object then it is allowed to be
            **    linked to from the inside. if the object is
            **    either an OleLink or an OLE 1.0 embedding
            **    then it can not be linked to from the inside.
            **    if we were a container/server app then we
            **    could offer linking to the outside of the
            **    object (ie. a pseudo object within our
            **    document). we are a container only app that
            **    does not support linking to ranges of its data.
            */
            OLEDBG_BEGIN2("IOleObject::GetMiscStatus called\r\n")
            lpContainerLine->m_lpOleObj->lpVtbl->GetMiscStatus(
                  lpContainerLine->m_lpOleObj,
                  DVASPECT_CONTENT, /* aspect is not important */
                  (LPDWORD)&dwStatus
            );
            OLEDBG_END2
            if (dwStatus & OLEMISC_CANTLINKINSIDE)
            {
               return MK_E_NOOBJECT;
            }

            if (OleIsRunning(lpContainerLine->m_lpOleObj))
            {
               return NOERROR;
            }
            else
            {
               return S_FALSE;
            }
         }
      }
   }
   // no object was found corresponding to the item name
   return MK_E_NOOBJECT;
}

#endif  // OLE_CNTR


MAIN.C

/*************************************************************************
**
**    OLE 2 Sample Code
**
**    main.c
**
**    This file contains initialization functions which are WinMain,
**    WndProc, and OutlineApp_InitalizeMenu.
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#include "ansiapi.h"
#include "outline.h"
#if defined( USE_STATUSBAR )
#include "status.h"
#endif

#if !defined( WIN32 )
#if defined( USE_CTL3D )
//#include "..\include\ctl3d.h"
#include "ctl3d.h"
#endif  // USE_CTL3D
#endif  // !WIN32

#include "initguid.h"           // forces our GUIDs to be initialized
#include "defguid.h"

#if defined( OLE_CNTR )
//*************************************************************************

#if defined( INPLACE_CNTR )
OLEDBGDATA_MAIN("ICNTR")
#else
OLEDBGDATA_MAIN("CNTR")
#endif

CONTAINERAPP g_OutlineApp;  // Global App object maintains app instance state

/* Global interface Vtbl's
 * NOTE: we only need one copy of each Vtbl. When an object which
 *      exposes an interface is instantiated, its lpVtbl is intialized
 *      to point to one of these global Vtbl's.
 */
IUnknownVtbl            g_OleApp_UnknownVtbl;
IClassFactoryVtbl       g_OleApp_ClassFactoryVtbl;
IMessageFilterVtbl      g_OleApp_MessageFilterVtbl;

IUnknownVtbl            g_OleDoc_UnknownVtbl;
IPersistFileVtbl        g_OleDoc_PersistFileVtbl;
IOleItemContainerVtbl   g_OleDoc_OleItemContainerVtbl;
IExternalConnectionVtbl g_OleDoc_ExternalConnectionVtbl;
IDataObjectVtbl         g_OleDoc_DataObjectVtbl;

#if defined( USE_DRAGDROP )
IDropSourceVtbl         g_OleDoc_DropSourceVtbl;
IDropTargetVtbl         g_OleDoc_DropTargetVtbl;
#endif  // USE_DRAGDROP

IOleUILinkContainerVtbl g_CntrDoc_OleUILinkContainerVtbl;

IOleClientSiteVtbl      g_CntrLine_UnknownVtbl;
IOleClientSiteVtbl      g_CntrLine_OleClientSiteVtbl;
IAdviseSinkVtbl         g_CntrLine_AdviseSinkVtbl;

#if defined( INPLACE_CNTR )
IOleInPlaceSiteVtbl     g_CntrLine_OleInPlaceSiteVtbl;
IOleInPlaceFrameVtbl    g_CntrApp_OleInPlaceFrameVtbl;
BOOL g_fInsideOutContainer = FALSE;     // default to outside-in activation
#endif  // INPLACE_CNTR

//*************************************************************************
#endif  // OLE_CNTR

#if defined( OLE_SERVER )
//*************************************************************************

#if defined( INPLACE_SVR )
OLEDBGDATA_MAIN("ISVR")
#else
OLEDBGDATA_MAIN("SVR")
#endif

SERVERAPP g_OutlineApp; // Global App object maintains app instance state

/* Global interface Vtbl's
 * NOTE: we only need one copy of each Vtbl. When an object which
 *      exposes an interface is instantiated, its lpVtbl is intialized
 *      to point to one of these global Vtbl's.
 */
IUnknownVtbl            g_OleApp_UnknownVtbl;
IClassFactoryVtbl       g_OleApp_ClassFactoryVtbl;
IMessageFilterVtbl      g_OleApp_MessageFilterVtbl;

IUnknownVtbl            g_OleDoc_UnknownVtbl;
IPersistFileVtbl        g_OleDoc_PersistFileVtbl;
IOleItemContainerVtbl   g_OleDoc_OleItemContainerVtbl;
IExternalConnectionVtbl g_OleDoc_ExternalConnectionVtbl;
IDataObjectVtbl         g_OleDoc_DataObjectVtbl;

#if defined( USE_DRAGDROP )
IDropSourceVtbl         g_OleDoc_DropSourceVtbl;
IDropTargetVtbl         g_OleDoc_DropTargetVtbl;
#endif  // USE_DRAGDROP

IOleObjectVtbl          g_SvrDoc_OleObjectVtbl;
IPersistStorageVtbl     g_SvrDoc_PersistStorageVtbl;

#if defined( SVR_TREATAS )
IStdMarshalInfoVtbl     g_SvrDoc_StdMarshalInfoVtbl;
#endif  // SVR_TREATAS

#if defined( INPLACE_SVR )
IOleInPlaceObjectVtbl       g_SvrDoc_OleInPlaceObjectVtbl;
IOleInPlaceActiveObjectVtbl g_SvrDoc_OleInPlaceActiveObjectVtbl;
#endif // INPLACE_SVR

IUnknownVtbl            g_PseudoObj_UnknownVtbl;
IOleObjectVtbl          g_PseudoObj_OleObjectVtbl;
IDataObjectVtbl         g_PseudoObj_DataObjectVtbl;

//*************************************************************************
#endif  // OLE_SVR

#if !defined( OLE_VERSION )
OLEDBGDATA_MAIN("OUTL")
OUTLINEAPP g_OutlineApp;    // Global App object maintains app instance state
#endif

LPOUTLINEAPP g_lpApp=(LPOUTLINEAPP)&g_OutlineApp;   // ptr to global app obj
RECT        g_rectNull = {0, 0, 0, 0};
UINT        g_uMsgHelp = 0;  // help msg from ole2ui dialogs
BOOL        g_fAppActive = FALSE;

/* WinMain
** -------
**    Main routine for the Windows application.
*/
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                  LPSTR lpszCmdLine, int nCmdShow)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   MSG         msg;            /* MSG structure to store your messages */

#if defined( OLE_VERSION )
   /* NOTE: it is recommended that all OLE applications to set
   **    their message queue size to 96. this improves the capacity
   **    and performance of OLE's LRPC mechanism.
   */
   int cMsg = 96;   // recommend msg queue size for OLE
   while (cMsg && ! SetMessageQueue(cMsg))  // take largest size we can get.
      cMsg -= 8;
   if (! cMsg)
      return -1;  // ERROR: we got no message queue
#endif

#if defined( USE_CTL3D )
   Ctl3dRegister(hInstance);
   Ctl3dAutoSubclass(hInstance);
#endif

   if(! hPrevInstance) {
      /* register window classes if first instance of application */
      if(! OutlineApp_InitApplication(lpOutlineApp, hInstance))
         return 0;
   }

   /* Create App Frame window */
   if (! OutlineApp_InitInstance(lpOutlineApp, hInstance, nCmdShow))
      return 0;

   if (! OutlineApp_ParseCmdLine(lpOutlineApp, lpszCmdLine, nCmdShow))
      return 0;

   lpOutlineApp->m_hAccelApp = LoadAccelerators(hInstance, APPACCEL);
   lpOutlineApp->m_hAccelFocusEdit = LoadAccelerators(hInstance,
         FB_EDIT_ACCEL);
   lpOutlineApp->m_hAccel = lpOutlineApp->m_hAccelApp;
   lpOutlineApp->m_hWndAccelTarget = lpOutlineApp->m_hWndApp;

   // Main message loop
   while(GetMessage(&msg, NULL, 0, 0)) {        /* Until WM_QUIT message */
      if(!MyTranslateAccelerator(&msg)) {
         TranslateMessage(&msg);
         DispatchMessage(&msg);
      }
   }

#if defined( OLE_VERSION )
   OleApp_TerminateApplication((LPOLEAPP)lpOutlineApp);
#else
   /* NOTE: CoInitialize() is called in OutlineApp_InitInstance
   **    and therefore we need to uninitialize it when exit.
   */
   CoUninitialize();
#endif

#if defined( USE_CTL3D )
   Ctl3dUnregister(hInstance);
#endif

   return msg.wParam;

} /*  End of WinMain */

BOOL MyTranslateAccelerator(LPMSG lpmsg)
{
   // if it's not a keystroke it can not be an accelerator
   if (lpmsg->message < WM_KEYFIRST || lpmsg->message > WM_KEYLAST)
      return FALSE;

   if (g_lpApp->m_hWndAccelTarget &&
      TranslateAccelerator(g_lpApp->m_hWndAccelTarget,
                                       g_lpApp->m_hAccel,lpmsg))
      return TRUE;

#if defined( INPLACE_SVR )
   /* NOTE: if we are in-place active and we did not translate the
   **    accelerator, we need to give the top-level (frame) in-place
   **    container a chance to translate the accelerator.
   **    we ONLY need to call OleTranslateAccelerator API if the
   **    message is a keyboard message. otherwise it is harmless but
   **    unnecessary.
   **
   **    NOTE: even a in-place server that does NOT have any
   **    Accelerators must still call OleTranslateAccelerator for all
   **    keyboard messages so that the server's OWN menu mneumonics
   **    (eg. &Edit -- Alt-e) function properly.
   **
   **    NOTE: an in-place server MUST check that the accelerator is
   **    NOT one of its own accelerators BEFORE calling
   **    OleTranslateAccelerator which tries to see if it is a
   **    container accelerator. if this is a server accelerator that
   **    was not translateed because the associated menu command was
   **    disabled, we MUST NOT call OleTranslateAccelerator. The
   **    IsAccelerator helper API has been added to assist with this
   **    check.
   */
   if (g_OutlineApp.m_lpIPData &&
      !IsAccelerator(g_lpApp->m_hAccel,
         GetAccelItemCount(g_lpApp->m_hAccel), lpmsg,NULL) &&
      OleTranslateAccelerator(g_OutlineApp.m_lpIPData->lpFrame,
            (LPOLEINPLACEFRAMEINFO)&g_OutlineApp.m_lpIPData->frameInfo,
            lpmsg) == NOERROR) {
      return TRUE;
   }
#endif

   return FALSE;
}

/************************************************************************/
/*                                                                      */
/* Main Window Procedure                                                */
/*                                                                      */
/* This procedure provides service routines for the Windows events      */
/* (messages) that Windows sends to the window, as well as the user     */
/* initiated events (messages) that are generated when the user selects */
/* the action bar and pulldown menu controls or the corresponding       */
/* keyboard accelerators.                                               */
/*                                                                      */
/************************************************************************/

LRESULT FAR PASCAL AppWndProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)GetWindowLong(hWnd, 0);
   LPOUTLINEDOC lpOutlineDoc = NULL;
#if defined( OLE_VERSION )
   LPOLEAPP lpOleApp = (LPOLEAPP)lpOutlineApp;
#endif
#if defined( OLE_CNTR )
   LPCONTAINERAPP lpContainerApp = (LPCONTAINERAPP)lpOutlineApp;
   char         szAnsiString[256];
#endif
   HWND         hWndDoc = NULL;

#if defined( USE_FRAMETOOLS )
   LPFRAMETOOLS lptb = OutlineApp_GetFrameTools(lpOutlineApp);
#endif

   if (lpOutlineApp) {
      lpOutlineDoc = OutlineApp_GetActiveDoc(lpOutlineApp);

      if (lpOutlineDoc)
         hWndDoc = OutlineDoc_GetWindow(lpOutlineDoc);
   }

   switch (Message) {
      case WM_COMMAND:
      {
#ifdef WIN32
         WORD wID    = LOWORD(wParam);
#else
         WORD wID    = wParam;
#endif

#if defined( INPLACE_CNTR )
         LPCONTAINERDOC lpContainerDoc = (LPCONTAINERDOC)lpOutlineDoc;
         LPOLEDOC lpOleDoc = (LPOLEDOC)lpOutlineDoc;

         /* NOTE: see context sensitive help technote (CSHELP.DOC)
         **    m_fMenuHelpMode flag is set when F1 is pressed when a
         **    menu item is selected. this flag is set in
         **    IOleInPlaceFrame::ContextSensitveHelp method.
         **    m_fCSHelpMode flag is set when SHIFT-F1 context
         **    sensitive help is entered. this flag is set in
         **    IOleInPlaceSite::ContextSensitiveHelp method.
         **    if either of these flags are set then the WM_COMMAND
         **    message is received then, the corresponding command
         **    should NOT executed; help can be given (if desired).
         **    also the context sensitve help mode should be exited.
         **    the two different cases have their own way to exit
         **    the mode (please refer to the technote).
         */
         if (lpOleDoc &&
            (lpContainerApp->m_fMenuHelpMode||lpOleDoc->m_fCSHelpMode) &&
            (wID > IDM_FILE)   /* min wID for app command */ &&
            (wID!=IDM_FB_EDIT) /* special wID to control FormulaBar */ ) {

            if ((lpContainerApp->m_fMenuHelpMode)) {
               LPOLEINPLACEACTIVEOBJECT lpIPActiveObj =
                     lpContainerApp->m_lpIPActiveObj;

               lpContainerApp->m_fMenuHelpMode = FALSE;

               // inform the in-place active object that we handled the
               //   menu help mode (F1) selection.
               if (lpIPActiveObj) {
                  OLEDBG_BEGIN2("IOleInPlaceActiveObject::ContextSensitiveHelp(FALSE) called\r\n")
                  lpIPActiveObj->lpVtbl->ContextSensitiveHelp(
                        lpIPActiveObj, FALSE);
                  OLEDBG_END2
               }
            }

            if ((lpOleDoc->m_fCSHelpMode)) {
               LPOLEINPLACEOBJECT lpIPObj;
               LPCONTAINERLINE lpLastIpActiveLine =
                     lpContainerDoc->m_lpLastIpActiveLine;

               lpOleDoc->m_fCSHelpMode = FALSE;

               /* inform immediate in-place container parent and,
               **    if we were a container/server, immediate
               **    in-place object children that we handled the
               **    context sensitive help mode.
               */
               if (lpLastIpActiveLine &&
                     (lpIPObj=lpLastIpActiveLine->m_lpOleIPObj)!=NULL){
                  OLEDBG_BEGIN2("IOleInPlaceObject::ContextSensitiveHelp(FALSE) called\r\n")
                  lpIPObj->lpVtbl->ContextSensitiveHelp(lpIPObj, FALSE);
                  OLEDBG_END2
               }
            }

            // if we provided help, we would do it here...

            // remove context sensitive help cursor
            SetCursor(LoadCursor(NULL,IDC_ARROW));
            return 0L;
         }
#endif  // INPLACE_CNTR

         switch (wID) {

            case IDM_F_NEW:
               OleDbgIndent(-2);   // Reset debug output indent level
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("OutlineApp_NewCommand\r\n")
               OutlineApp_NewCommand(lpOutlineApp);
               OLEDBG_END3

#if defined( OLE_CNTR )
               /* NOTE: this call will attempt to recover
               **    resources by unloading DLL's that were loaded
               **    by OLE and are no longer being used. it is a
               **    good idea to call this API now and then if
               **    your app tends to run for a long time.
               **    otherwise these DLL's will be unloaded when
               **    the app exits. some apps may want to call
               **    this as part of idle-time processing. this
               **    call is optional.
               */
               OLEDBG_BEGIN2("CoFreeUnusedLibraries called\r\n")
               CoFreeUnusedLibraries();
               OLEDBG_END2
#endif

#if defined( USE_FRAMETOOLS )
               OutlineDoc_UpdateFrameToolButtons(
                     OutlineApp_GetActiveDoc(lpOutlineApp));
#endif
               break;

            case IDM_F_OPEN:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("OutlineApp_OpenCommand\r\n")
               OutlineApp_OpenCommand(lpOutlineApp);
               OLEDBG_END3

#if defined( OLE_CNTR )
               /* NOTE: this call will attempt to recover
               **    resources by unloading DLL's that were loaded
               **    by OLE and are no longer being used. it is a
               **    good idea to call this API now and then if
               **    your app tends to run for a long time.
               **    otherwise these DLL's will be unloaded when
               **    the app exits. some apps may want to call
               **    this as part of idle-time processing. this
               **    call is optional.
               */
               OLEDBG_BEGIN2("CoFreeUnusedLibraries called\r\n")
               CoFreeUnusedLibraries();
               OLEDBG_END2
#endif

#if defined( USE_FRAMETOOLS )
               OutlineDoc_UpdateFrameToolButtons(
                     OutlineApp_GetActiveDoc(lpOutlineApp));
#endif
               break;

            case IDM_F_SAVE:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("OutlineApp_SaveCommand\r\n")
               OutlineApp_SaveCommand(lpOutlineApp);
               OLEDBG_END3
               break;

            case IDM_F_SAVEAS:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("OutlineApp_SaveAsCommand\r\n")
               OutlineApp_SaveAsCommand(lpOutlineApp);
               OLEDBG_END3
               break;

            case IDM_F_PRINT:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("OutlineApp_PrintCommand\r\n")
               OutlineApp_PrintCommand(lpOutlineApp);
               OLEDBG_END3
               break;

            case IDM_F_PRINTERSETUP:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("OutlineApp_PrinterSetupCommand\r\n")
               OutlineApp_PrinterSetupCommand(lpOutlineApp);
               OLEDBG_END3
               break;

            case IDM_F_EXIT:
               SendMessage(hWnd, WM_CLOSE, 0, 0L);
               break;

            case IDM_H_ABOUT:
               OutlineApp_AboutCommand(lpOutlineApp);
               break;

#if defined( INPLACE_CNTR )
            case IDM_ESCAPE:
            {
               /* ESCAPE key pressed */
               LPCONTAINERDOC lpContainerDoc =
                        (LPCONTAINERDOC)lpOutlineDoc;

               /* NOTE: The standard OLE 2.0 UI convention
               **    is to have ESCAPE key exit in-place
               **    activation (ie. UIDeactivate). If
               **    possible it is recommended for both
               **    in-place servers AND in-place containers
               **    to take responsibility to handle the
               **    ESCAPE key accelerator. The server has
               **    the first crack at handling accelerator
               **    keys and normally the server should do
               **    the UIDeactivation. It is a good idea for
               **    in-place containers, in order to
               **    guarantee consistent behavior, to also
               **    handle the ESCAPE key and UIDeactivate
               **    the object in case the object does not do
               **    it itself. normally this should be
               **    unnecessary.
               */
               if (lpContainerDoc->m_lpLastUIActiveLine &&
                  lpContainerDoc->m_lpLastUIActiveLine->m_fUIActive)
               {
                  ContainerLine_UIDeactivate(
                        lpContainerDoc->m_lpLastUIActiveLine);
               }
               break;
            }
#endif  // INPLACE_CNTR

            default:
               // forward message to document window
               if (hWndDoc) {
                  return DocWndProc(hWndDoc, Message,wParam,lParam);
               }
         }

         break;  /* End of WM_COMMAND */
      }

      case WM_INITMENU:
         OutlineApp_InitMenu(lpOutlineApp, lpOutlineDoc, (HMENU)wParam);
         break;

#if defined( OLE_VERSION )

      /* NOTE: WM_INITMENUPOPUP is trapped primarily for the Edit
      **    menu. We didn't update the Edit menu until it is popped
      **    up to avoid the overheads of the OLE calls which are
      **    required to initialize some Edit menu items.
      */
      case WM_INITMENUPOPUP:
      {
         HMENU hMenuEdit = GetSubMenu(lpOutlineApp->m_hMenuApp, 1);
#if defined( INPLACE_CNTR )
         LPCONTAINERDOC lpContainerDoc = (LPCONTAINERDOC)lpOutlineDoc;

         /* NOTE: we must check if there is an object currently
         **    in-place UIActive. if so, then our edit menu is not
         **    on the menu; we do not want to bother updating the
         **    edit menu when it is not even there.
         */
         if (lpContainerDoc && lpContainerDoc->m_lpLastUIActiveLine &&
            lpContainerDoc->m_lpLastUIActiveLine->m_fUIActive)
            break;  // an object is in-place UI active
#endif
         if ((HMENU)wParam == hMenuEdit &&
            (LOWORD(lParam) == POS_EDITMENU) &&
            OleDoc_GetUpdateEditMenuFlag((LPOLEDOC)lpOutlineDoc)) {
            OleApp_UpdateEditMenu(lpOleApp, lpOutlineDoc, hMenuEdit);
         }
         break;
      }
#endif      // OLE_VERSION

      case WM_SIZE:
         if (wParam != SIZE_MINIMIZED)
            OutlineApp_ResizeWindows(lpOutlineApp);
         break;

      case WM_ACTIVATEAPP:
#if defined (OLE_CNTR)
         if (g_fAppActive = (BOOL) wParam)
            OleApp_QueryNewPalette(lpOleApp);
#endif

#if defined( INPLACE_CNTR )
         {
            BOOL fActivate = (BOOL)wParam;
            LPOLEINPLACEACTIVEOBJECT lpIPActiveObj =
                  lpContainerApp->m_lpIPActiveObj;

            /* NOTE: the in-place container MUST inform the
            **    inner most in-place active object (this is NOT
            **    necessarily our immediate child if there are
            **    nested levels of embedding) of the WM_ACTIVATEAPP
            **    status.
            */
            if (lpIPActiveObj) {
#if defined( _DEBUG )
               OLEDBG_BEGIN2((fActivate ?
                  "IOleInPlaceActiveObject::OnFrameWindowActivate(TRUE) called\r\n" :
                  "IOleInPlaceActiveObject::OnFrameWindowActivate(FALSE) called\r\n"))
#endif  // _DEUBG
               lpIPActiveObj->lpVtbl->OnFrameWindowActivate(
                  lpIPActiveObj, fActivate);
               OLEDBG_END2
            }
         }

#endif  // INPLACE_CNTR

         // NOTE: We can't call OutlineDoc_UpdateFrameToolButtons
         //           right away which
         //           would generate some OLE calls and eventually
         //           WM_ACTIVATEAPP and a loop was formed. Therefore, we
         //           should delay the frame tool initialization until
         //           WM_ACTIVATEAPP is finished by posting a message
         //           to ourselves.
         //           we want to ignore the WM_ACTIVATEAPP that comes
         //           as we bring up a modal dialog.

         /* Update enable/disable state of buttons in toolbar */
         if (wParam
#if defined( OLE_VERSION )
               && lpOleApp->m_cModalDlgActive == 0
#endif
         ) {
            PostMessage(hWnd, WM_U_INITFRAMETOOLS, 0, 0L);
         }
         return 0L;

      case WM_SETFOCUS:
         SetFocus(hWndDoc);
         break;

#if defined( OLE_CNTR )
      case WM_QUERYNEWPALETTE:
         if (!g_fAppActive)
            return 0L;

         return OleApp_QueryNewPalette(lpOleApp);

      case WM_PALETTECHANGED:
      {
         HWND hWndPalChg = (HWND) wParam;
         static BOOL fInPaletteChanged = FALSE;

         if (fInPaletteChanged)  // Guard against recursion
            return 0L;

         fInPaletteChanged = TRUE;

         if (hWnd != hWndPalChg)
            wSelectPalette(hWnd, lpOleApp->m_hStdPal,TRUE/*fBackground*/);

#if defined( INPLACE_CNTR )
         /* NOTE: always forward the WM_PALETTECHANGED message (via
         **    SendMessage) to any in-place objects that currently have
         **    their window visible. this gives these objects the chance
         **    to select their palettes. this is
         **    REQUIRED by all in-place containers independent of
         **    whether they use color palettes themselves--their objects
         **    may use color palettes.
         **    (see ContainerDoc_ForwardPaletteChangedMsg for more info)
         */
         if (lpOutlineDoc){
            ContainerDoc_ForwardPaletteChangedMsg(
                  (LPCONTAINERDOC)lpOutlineDoc, hWndPalChg);
         }
#endif  // INPLACE_CNTR

         fInPaletteChanged = FALSE;
         return 0L;
      }
#endif  // OLE_CNTR

      case WM_DESTROY:
         PostQuitMessage(0);
         break;

      case WM_CLOSE:  /* close the window */

         /* Close all active documents. if successful, then exit */
         OleDbgOutNoPrefix2("\r\n");

         OutlineApp_CloseAllDocsAndExitCommand(lpOutlineApp, FALSE);
         break;

      case WM_QUERYENDSESSION:
      {
#if defined( OLE_CNTR )
         /* NOTE: we are not able to make OLE LRPC calls when
         **    WM_QUERYENDSESSION is recieved (this is a
         **    SendMessage). this means, for example, that we are
         **    NOT able to ask objects to save. thus the most we can
         **    do is ask the user if he wants to exit with
         **    discarding changes or else abort shutting down.
         */

         int nResponse;

         W2A (APPNAME, szAnsiString, 256);

         nResponse = MessageBox(
               hWnd,
               "Discard changes?",
               szAnsiString,
               MB_ICONQUESTION | MB_OKCANCEL
         );
         if(nResponse == IDOK)
            return 1L;      /* can terminate */

#endif
#if defined( OLE_SERVER )
         /* NOTE: an embedded object should never prompt whether
         **    it should be saved (according the OLE 2.0 User
         **    Model). therefore, an embedded object will never
         **    complain that it needs to be saved. it will always
         **    allow the QueryEndSession to proceed.
         */
         if (lpOutlineApp->m_lpDoc->m_docInitType == DOCTYPE_EMBEDDED)
            return 1L;      /* can terminate */
         else
#endif
         {
            /* this is not an embedded object; it is a user
            **    document. we will prompt if the user wants to
            **    save the document now in WM_QUERYENDSESSION. if
            **    the user cancels then that would abort the
            **    shutdown. if the user does not abort, then later
            **    in WM_ENDSESSION the document will be actually
            **    closed.
            **
            **    Because this is an SDI app, there is only one
            **    document. An MDI would need to loop through all
            **    open documents.
            */
            DWORD dwSaveOption = OLECLOSE_PROMPTSAVE;
            if (OutlineDoc_CheckSaveChanges(
                  lpOutlineApp->m_lpDoc, &dwSaveOption))
               return 1L;      /* can terminate */
         }

         /* else: can't terminate now */

         break;
      }

#if defined( OLE_VERSION)
      case WM_ENDSESSION:
      {
         BOOL fEndSession = (BOOL)wParam;

         if (fEndSession) {
            OutlineApp_CloseAllDocsAndExitCommand(lpOutlineApp, TRUE);
            return 0L;
         }
      }
      break;
#endif  // OLE_VERSION

#if defined( USE_STATUSBAR )
      case WM_MENUSELECT:
      {
         LPSTR lpszMessage;
         OLECHAR lpszUniMessage[256];
#ifdef WIN32
         UINT fuFlags    = (UINT)HIWORD(wParam);
         UINT uItem      = (UINT)LOWORD(wParam);
#else
         UINT fuFlags    = (UINT)LOWORD(lParam);
         UINT uItem      = (UINT)wParam;
#endif

         if (uItem == 0 && fuFlags == (UINT)-1) {
            GetControlMessage(STATUS_READY, &lpszMessage);
            A2W (lpszMessage, lpszUniMessage, 256);
            OutlineApp_SetStatusText(lpOutlineApp, lpszUniMessage);
         }
         else if (fuFlags & MF_POPUP) {
#ifdef WIN32
            HMENU hMainMenu = (HMENU)lParam;
            HMENU hPopupMenu = GetSubMenu(hMainMenu,uItem);
#else
            HMENU hPopupMenu = (HMENU)wParam;
#endif
            GetPopupMessage(hPopupMenu, &lpszMessage);
            A2W (lpszMessage, lpszUniMessage, 256);
            OutlineApp_SetStatusText(lpOutlineApp, lpszUniMessage);
         }
         else if (fuFlags & MF_SYSMENU) {
            GetSysMenuMessage(uItem, &lpszMessage);
            A2W (lpszMessage, lpszUniMessage, 256);
            OutlineApp_SetStatusText(lpOutlineApp, lpszUniMessage);
         }
         else if (uItem != 0) {  // Command Item
            GetItemMessage(uItem, &lpszMessage);
            A2W (lpszMessage, lpszUniMessage, 256);
            OutlineApp_SetStatusText(lpOutlineApp, lpszUniMessage);
         }
         else {
            GetControlMessage(STATUS_BLANK, &lpszMessage);
            A2W (lpszMessage, lpszUniMessage, 256);
            OutlineApp_SetStatusText(lpOutlineApp, lpszUniMessage);
         }
         break;
      }
#endif  // USE_STATUSBAR

#if defined( USE_FRAMETOOLS )
      case WM_U_INITFRAMETOOLS:
         OutlineDoc_UpdateFrameToolButtons(lpOutlineDoc);
         break;
#endif

      default:
         /* For any message for which you don't specifically provide a  */
         /* service routine, you should return the message to Windows   */
         /* for default message processing.                             */

         return DefWindowProc(hWnd, Message, wParam, lParam);
   }

   return (LRESULT)0;
}     /* End of AppWndProc*/

/************************************************************************/
/*                                                                      */
/* Document Window Procedure                                            */
/*                                                                      */
/*   The Document Window is the parent of the OwnerDraw Listbox which   */
/* maintains the list of lines in the current document. This window     */
/* receives the ownerdraw callback messages from the list box.          */
/************************************************************************/

LRESULT FAR PASCAL DocWndProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM lParam)
{

LPOUTLINEAPP    lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOUTLINEDOC    lpOutlineDoc = (LPOUTLINEDOC)GetWindowLong(hWnd, 0);
   LPLINELIST      lpLL = OutlineDoc_GetLineList(lpOutlineDoc);
   LPSCALEFACTOR   lpscale = OutlineDoc_GetScaleFactor(lpOutlineDoc);

#if defined( OLE_VERSION )
   LPOLEAPP lpOleApp = (LPOLEAPP)lpOutlineApp;
   LPOLEDOC lpOleDoc = (LPOLEDOC)lpOutlineDoc;
#if defined( OLE_CNTR )
   LPCONTAINERDOC lpContainerDoc = (LPCONTAINERDOC)lpOutlineDoc;
#endif
#if defined( OLE_SERVER )
   LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOutlineDoc;
#endif
#if defined( INPLACE_CNTR )
   LPCONTAINERAPP lpContainerApp = (LPCONTAINERAPP)lpOutlineApp;
#endif
#endif  // OLE_VERSION

   switch(Message) {

#if defined( INPLACE_SVR )

      /* NOTE: ISVROTL doesn't use color palettes. The inplace objects
      **    that use color palettes must implement the following
      **    lines of code.
      **
         case WM_QUERYNEWPALETTE:
            return wSelectPalette(hWnd, hPal, FALSE); // foreground

         case WM_PALETTECHANGED:
            if (hWnd != (HWND) wParam)
               wSelectPalette(hWnd, hPal, TRUE);     // background
         break;
      **
      **
      **
      */
#endif

      case WM_MEASUREITEM:
      {
         LPMEASUREITEMSTRUCT lpmis = ((LPMEASUREITEMSTRUCT)lParam);

         switch (wParam) {
            case IDC_LINELIST:
            {
               HDC hDC = LineList_GetDC(lpLL);
               UINT uHeight;

               uHeight=Line_GetHeightInHimetric((LPLINE)lpmis->itemData);
               uHeight = XformHeightInHimetricToPixels(hDC, uHeight);
               uHeight = (UINT) (uHeight * lpscale->dwSyN /
                     lpscale->dwSyD);

               if (uHeight >LISTBOX_HEIGHT_LIMIT)
                  uHeight = LISTBOX_HEIGHT_LIMIT;

               lpmis->itemHeight = uHeight;
               LineList_ReleaseDC(lpLL, hDC);
               break;
            }

            case IDC_NAMETABLE:
            {
               // NOTE: NameTable is never made visible. do nothing.
               break;
            }

#if defined( USE_HEADING )
            case IDC_ROWHEADING:
            {
               UINT uHeight;

               uHeight = LOWORD(lpmis->itemData);
               uHeight = (UINT) (uHeight * lpscale->dwSyN /
                     lpscale->dwSyD);
               if (uHeight >LISTBOX_HEIGHT_LIMIT)
                  uHeight = LISTBOX_HEIGHT_LIMIT;
               lpmis->itemHeight = uHeight;
               break;
            }

            case IDC_COLHEADING:
            {
               UINT uHeight;

               uHeight = LOWORD(lpmis->itemData);
               uHeight = (UINT) (uHeight * lpscale->dwSyN /
                     lpscale->dwSyD);
               if (uHeight > LISTBOX_HEIGHT_LIMIT)
                  uHeight = LISTBOX_HEIGHT_LIMIT;
               lpmis->itemHeight = uHeight;
               break;
            }
#endif  // USE_HEADING

         }
         return (LRESULT)TRUE;
      }

      case WM_DRAWITEM:
      {
         LPDRAWITEMSTRUCT lpdis = ((LPDRAWITEMSTRUCT)lParam);

         switch (lpdis->CtlID) {

            case IDC_LINELIST:
            {
               RECT   rcClient;
               RECT   rcDevice;
               HWND   hWndLL = LineList_GetWindow(lpLL);
               LPLINE lpLine = (LPLINE)lpdis->itemData;

               // NOTE: When itemID == -1, the listbox is empty.
               //       We are supposed to draw focus rect only
               //       But it is not done in this app. If this line is
               //       removed, the app will crash in Line_DrawToScreen
               //       because of invalid lpLine.
               if (lpdis->itemID == -1)
                  break;

               GetClientRect(hWndLL, &rcClient);

               rcDevice = lpdis->rcItem;

               // shift the item rect to account for horizontal scrolling

               rcDevice.left += rcClient.right - lpdis->rcItem.right;

#if defined( OLE_CNTR )
               /* we need to remember the horizontal scroll offset
               **    needed for the in-place object's window.
               **    (this is specific to ICNTROTL)
               */
               if(lpdis->itemAction & ODA_DRAWENTIRE) {
                  if (Line_GetLineType(lpLine) == CONTAINERLINETYPE)
                     ((LPCONTAINERLINE)lpLine)->m_nHorizScrollShift =
                        rcDevice.left;
               }
#endif  // OLE_CNTR

               // shift rect for left margin
               rcDevice.left += (int)(XformWidthInHimetricToPixels(NULL,
                     LOWORD(OutlineDoc_GetMargin(lpOutlineDoc))) *
                     lpscale->dwSxN / lpscale->dwSxD);

               rcDevice.right = rcDevice.left +
                     (int)(XformWidthInHimetricToPixels(lpdis->hDC,
                           Line_GetWidthInHimetric(lpLine)) *
                     lpscale->dwSxN / lpscale->dwSxD);

               Line_DrawToScreen(
                     lpLine,
                     lpdis->hDC,
                     &lpdis->rcItem,
                     lpdis->itemAction,
                     lpdis->itemState,
                     &rcDevice
               );

#if defined( USE_FRAMETOOLS )
               if (lpdis->itemState & ODS_FOCUS)
                  OutlineDoc_SetFormulaBarEditText(lpOutlineDoc,lpLine);
#endif
               break;
            }
            case IDC_NAMETABLE:
            {
               // NOTE: NameTable is never made visible. do nothing
               break;
            }

#if defined( USE_HEADING )
            case IDC_ROWHEADING:
            {
               LPHEADING lphead;

               // Last dummy item shouldn't be drawn
               if (lpdis->itemID == (UINT)LineList_GetCount(lpLL))
                  break;

               // only DrawEntire need be trapped as window is disabled
               if (lpdis->itemAction == ODA_DRAWENTIRE) {
                  lphead = OutlineDoc_GetHeading(lpOutlineDoc);
                  Heading_RH_Draw(lphead, lpdis);
               }
               break;
            }

            case IDC_COLHEADING:
            {
               RECT   rect;
               RECT   rcDevice;
               RECT   rcLogical;
               LPHEADING lphead;

               // only DrawEntire need be trapped as window is disabled
               if (lpdis->itemAction == ODA_DRAWENTIRE) {
                  lphead = OutlineDoc_GetHeading(lpOutlineDoc);
                  GetClientRect(lpdis->hwndItem, &rect);

                  rcDevice = lpdis->rcItem;

                  // shift the item rect to account for
                  // horizontal scrolling
                  rcDevice.left = -(rcDevice.right - rect.right);

                  // shift rect for left margin
                  rcDevice.left += (int)(XformWidthInHimetricToPixels(
                        NULL,
                        LOWORD(OutlineDoc_GetMargin(lpOutlineDoc))) *
                     lpscale->dwSxN / lpscale->dwSxD);

                  rcDevice.right = rcDevice.left + (int)lpscale->dwSxN;
                  rcLogical.left = 0;
                  rcLogical.bottom = 0;
                  rcLogical.right = (int)lpscale->dwSxD;
                  rcLogical.top = LOWORD(lpdis->itemData);

                  Heading_CH_Draw(lphead, lpdis, &rcDevice, &rcLogical);
               }
               break;
            }
#endif  // USE_HEADING

         }
         return (LRESULT)TRUE;
      }

      case WM_SETFOCUS:
         if (lpLL)
            SetFocus(LineList_GetWindow(lpLL));
         break;

#if !defined( OLE_VERSION )
      case WM_RENDERFORMAT:
      {
         LPOUTLINEDOC lpClipboardDoc = lpOutlineApp->m_lpClipboardDoc;
         if (lpClipboardDoc)
            OutlineDoc_RenderFormat(lpClipboardDoc, wParam);

         break;
      }
      case WM_RENDERALLFORMATS:
      {
         LPOUTLINEDOC lpClipboardDoc = lpOutlineApp->m_lpClipboardDoc;
         if (lpClipboardDoc)
            OutlineDoc_RenderAllFormats(lpClipboardDoc);

         break;
      }
      case WM_DESTROYCLIPBOARD:
         if (g_lpApp->m_lpClipboardDoc) {
            OutlineDoc_Destroy(g_lpApp->m_lpClipboardDoc);
            g_lpApp->m_lpClipboardDoc = NULL;
         }
         break;

#endif   // OLE_VERSION

#if defined( OLE_CNTR )
      case WM_U_UPDATEOBJECTEXTENT:
      {
         /* Update the extents of any OLE object that is marked that
         **    its size may  have changed. when an
         **    IAdviseSink::OnViewChange notification is received,
         **    the corresponding ContainerLine is marked
         **    (m_fDoGetExtent==TRUE) and a message
         **    (WM_U_UPDATEOBJECTEXTENT) is posted to the document
         **    indicating that there are dirty objects.
         */
         ContainerDoc_UpdateExtentOfAllOleObjects(lpContainerDoc);
         break;
      }
#endif  // OLE_CNTR

#if defined( INPLACE_SVR ) || defined( INPLACE_CNTR )
      /* NOTE: Any window that is used during in-place activation
      **    must handle the WM_SETCURSOR message or else the cursor
      **    of the in-place parent will be used. if WM_SETCURSOR is
      **    not handled, then DefWindowProc sends the message to the
      **    window's parent.
      **
      **    see context sensitive help technote (CSHELP.DOC).
      **    m_fCSHelpMode flag is set when SHIFT-F1 context
      **    sensitive help is entered.
      **    if this flag is set then the context sensitive help
      **    cursor should be shown.
      */
      case WM_SETCURSOR:
         if (lpOleDoc->m_fCSHelpMode)
            SetCursor(UICursorLoad(IDC_CONTEXTHELP));
         else
            SetCursor(LoadCursor(NULL, IDC_ARROW) );
         return (LRESULT)TRUE;
#endif  // INPLACE_SVR || INPLACE_CNTR

#if defined( INPLACE_SVR )
      /* NOTE: when the in-place active, our in-place server
      **    document window (passed to IOleInPlaceFrame::SetMenu)
      **    will receive the WM_INITMENU and WM_INITMENUPOPUP messages.
      */
      case WM_INITMENU:
         OutlineApp_InitMenu(lpOutlineApp, lpOutlineDoc, (HMENU)wParam);
         break;

      /* NOTE: WM_INITMENUPOPUP is trapped primarily for the Edit
      **    menu. We didn't update the Edit menu until it is popped
      **    up to avoid the overheads of the OLE calls which are
      **    required to initialize some Edit menu items.
      */
      case WM_INITMENUPOPUP:
      {
         HMENU hMenuEdit = GetSubMenu(lpOutlineApp->m_hMenuApp, 1);
         if ((HMENU)wParam == hMenuEdit &&
            (LOWORD(lParam) == POS_EDITMENU) &&
            OleDoc_GetUpdateEditMenuFlag((LPOLEDOC)lpOutlineDoc)) {
            OleApp_UpdateEditMenu(
                  (LPOLEAPP)lpOutlineApp, lpOutlineDoc, hMenuEdit);
         }
         break;
      }
#endif      // INPLACE_SVR

#if defined( INPLACE_SVR ) && defined( USE_STATUSBAR )
      /* NOTE: when the server is in-place active the
      **    WM_MENUSELECT message is sent to the object's window and
      **    not the server app's frame window. processing this
      **    message allows there in-place server to give status bar
      **    help text for menu commands.
      */
      case WM_MENUSELECT:
      {
         LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOleDoc;
         LPSTR lpszMessage;
         OLECHAR szUniMessage[256];

#ifdef WIN32
         UINT fuFlags    = (UINT)HIWORD(wParam);
         UINT uItem      = (UINT)LOWORD(wParam);
#else
         UINT fuFlags    = (UINT)LOWORD(lParam);
         UINT uItem      = (UINT)wParam;
#endif

         if (uItem == 0 && fuFlags == (UINT)-1) {
            GetControlMessage(STATUS_READY, &lpszMessage);
            A2W( lpszMessage, szUniMessage, 256);
            ServerDoc_SetStatusText(lpServerDoc, szUniMessage);
         }
         else if (fuFlags & MF_POPUP) {
#ifdef WIN32
            HMENU hMainMenu = (HMENU)lParam;
            HMENU hPopupMenu = GetSubMenu(hMainMenu,uItem);
#else
            HMENU hPopupMenu = (HMENU)wParam;
#endif
            GetPopupMessage(hPopupMenu, &lpszMessage);
            A2W( lpszMessage, szUniMessage, 256);
            ServerDoc_SetStatusText(lpServerDoc, szUniMessage);
         }
         else if (uItem != 0) {  // Command Item
            GetItemMessage(uItem, &lpszMessage);
            A2W( lpszMessage, szUniMessage, 256);
            ServerDoc_SetStatusText(lpServerDoc, szUniMessage);
         }
         else {
            GetControlMessage(STATUS_BLANK, &lpszMessage);
            A2W( lpszMessage, szUniMessage, 256);
            ServerDoc_SetStatusText(lpServerDoc, szUniMessage);
         }
         break;
      }
#endif  // INPLACE_SVR && USE_STATUSBAR
#if defined( INPLACE_SVR ) && defined( USE_FRAMETOOLS )

      case WM_U_INITFRAMETOOLS:
         OutlineDoc_UpdateFrameToolButtons(lpOutlineDoc);
         break;
#endif      // INPLACE_SVR && USE_FRAMETOOLS

      case WM_COMMAND:
      {
#ifdef WIN32
         WORD wNotifyCode = HIWORD(wParam);
         WORD wID          = LOWORD(wParam);
         HWND hwndCtl     = (HWND) lParam;
#else
         WORD wNotifyCode = HIWORD(lParam);
         WORD wID             = wParam;
         HWND hwndCtl     = (HWND) LOWORD(lParam);
#endif

#if defined( INPLACE_SVR )
         /* NOTE: see context sensitive help technote (CSHELP.DOC)
         **    m_fMenuHelpMode flag is set when F1 is pressed when a
         **    menu item is selected. this flag is set in
         **    IOleInPlaceActiveObject::ContextSensitveHelp method.
         **    m_fCSHelpMode flag is set when SHIFT-F1 context
         **    sensitive help is entered. this flag is set in
         **    IOleInPlaceObject::ContextSensitiveHelp method.
         **    if either of these flags are set then the WM_COMMAND
         **    message is received then, the corresponding command
         **    should NOT executed; help can be given (if desired).
         **    also the context sensitve help mode should be exited.
         **    the two different cases have their own way to exit
         **    the mode (please refer to the technote).
         */
         if (lpOleDoc &&
            (lpServerDoc->m_fMenuHelpMode||lpOleDoc->m_fCSHelpMode) &&
            (wID > IDM_FILE)   /* min wID for app command */ &&
            (wID!=IDM_FB_EDIT) /* special wID to control FormulaBar */ ) {

            if ((lpServerDoc->m_fMenuHelpMode)) {
               LPOLEINPLACEFRAME lpFrame;

               lpServerDoc->m_fMenuHelpMode = FALSE;

               // inform top-level frame that we handled the
               //   menu help mode (F1) selection.
               if (lpServerDoc->m_lpIPData &&
                     (lpFrame=lpServerDoc->m_lpIPData->lpFrame)!=NULL){
                  OLEDBG_BEGIN2("IOleInPlaceFrame::ContextSensitiveHelp(FALSE) called\r\n")
                  lpFrame->lpVtbl->ContextSensitiveHelp(lpFrame, FALSE);
                  OLEDBG_END2
               }
            }

            if ((lpOleDoc->m_fCSHelpMode)) {
               LPOLEINPLACESITE lpSite;

               lpOleDoc->m_fCSHelpMode = FALSE;

               /* inform immediate in-place container parent and,
               **    if we were a container/server, immediate
               **    in-place object children that we handled the
               **    context sensitive help mode.
               */
               if (lpServerDoc->m_lpIPData &&
                     (lpSite=lpServerDoc->m_lpIPData->lpSite) !=NULL) {
                  OLEDBG_BEGIN2("IOleInPlaceSite::ContextSensitiveHelp(FALSE) called\r\n")
                  lpSite->lpVtbl->ContextSensitiveHelp(lpSite, FALSE);
                  OLEDBG_END2
               }
            }

            // if we provided help, we would do it here...

            // remove context sensitive help cursor
            SetCursor(LoadCursor(NULL,IDC_ARROW));
            return 0L;
         }
#endif  // INPLACE_SVR
#if defined( INPLACE_CNTR )

         /* NOTE: see context sensitive help technote (CSHELP.DOC)
         **    m_fMenuHelpMode flag is set when F1 is pressed when a
         **    menu item is selected. this flag is set in
         **    IOleInPlaceFrame::ContextSensitveHelp method.
         **    m_fCSHelpMode flag is set when SHIFT-F1 context
         **    sensitive help is entered. this flag is set in
         **    IOleInPlaceSite::ContextSensitiveHelp method.
         **    if either of these flags are set then the WM_COMMAND
         **    message is received then, the corresponding command
         **    should NOT executed; help can be given (if desired).
         **    also the context sensitve help mode should be exited.
         **    the two different cases have their own way to exit
         **    the mode (please refer to the technote).
         */
         if (lpOleDoc &&
            (lpContainerApp->m_fMenuHelpMode||lpOleDoc->m_fCSHelpMode) &&
            (wID > IDM_FILE)   /* min wID for app command */ &&
            (wID!=IDM_FB_EDIT) /* special wID to control FormulaBar */ ) {

            if ((lpContainerApp->m_fMenuHelpMode)) {
               LPOLEINPLACEACTIVEOBJECT lpIPActiveObj =
                     lpContainerApp->m_lpIPActiveObj;

               lpContainerApp->m_fMenuHelpMode = FALSE;

               // inform the in-place active object that we handled the
               //   menu help mode (F1) selection.
               if (lpIPActiveObj) {
                  OLEDBG_BEGIN2("IOleInPlaceActiveObject::ContextSensitiveHelp(FALSE) called\r\n")
                  lpIPActiveObj->lpVtbl->ContextSensitiveHelp(
                        lpIPActiveObj, FALSE);
                  OLEDBG_END2
               }
            }

            if ((lpOleDoc->m_fCSHelpMode)) {
               LPOLEINPLACEOBJECT lpIPObj;
               LPCONTAINERLINE lpLastIpActiveLine =
                     lpContainerDoc->m_lpLastIpActiveLine;

               lpOleDoc->m_fCSHelpMode = FALSE;

               /* inform immediate in-place container parent and,
               **    if we were a container/server, immediate
               **    in-place object children that we handled the
               **    context sensitive help mode.
               */
               if (lpLastIpActiveLine &&
                     (lpIPObj=lpLastIpActiveLine->m_lpOleIPObj)!=NULL){
                  OLEDBG_BEGIN2("IOleInPlaceObject::ContextSensitiveHelp(FALSE) called\r\n")
                  lpIPObj->lpVtbl->ContextSensitiveHelp(lpIPObj, FALSE);
                  OLEDBG_END2
               }
            }

            // if we provided help, we would do it here...

            // remove context sensitive help cursor
            SetCursor(LoadCursor(NULL,IDC_ARROW));
            return 0L;
         }
#endif  // INPLACE_CNTR

         switch (wID) {

            /*********************************************************
            ** File new, open, save and print as well as Help about
            **    are duplicated in this switch statement and they are
            **    used to trap the message from the toolbar
            **
            *********************************************************/

            case IDM_F_NEW:
               OleDbgIndent(-2);   // Reset debug output indent level
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("OutlineApp_NewCommand\r\n")
               OutlineApp_NewCommand(lpOutlineApp);
               OLEDBG_END3

#if defined( OLE_CNTR )
               /* NOTE: this call will attempt to recover
               **    resources by unloading DLL's that were loaded
               **    by OLE and are no longer being used. it is a
               **    good idea to call this API now and then if
               **    your app tends to run for a long time.
               **    otherwise these DLL's will be unloaded when
               **    the app exits. some apps may want to call
               **    this as part of idle-time processing. this
               **    call is optional.
               */
               OLEDBG_BEGIN2("CoFreeUnusedLibraries called\r\n")
               CoFreeUnusedLibraries();
               OLEDBG_END2
#endif

#if defined( USE_FRAMETOOLS )
               OutlineDoc_UpdateFrameToolButtons(
                     OutlineApp_GetActiveDoc(lpOutlineApp));
#endif
               break;

            case IDM_F_OPEN:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("OutlineApp_OpenCommand\r\n")
               OutlineApp_OpenCommand(lpOutlineApp);
               OLEDBG_END3

#if defined( OLE_CNTR )
               /* NOTE: this call will attempt to recover
               **    resources by unloading DLL's that were loaded
               **    by OLE and are no longer being used. it is a
               **    good idea to call this API now and then if
               **    your app tends to run for a long time.
               **    otherwise these DLL's will be unloaded when
               **    the app exits. some apps may want to call
               **    this as part of idle-time processing. this
               **    call is optional.
               */
               OLEDBG_BEGIN2("CoFreeUnusedLibraries called\r\n")
               CoFreeUnusedLibraries();
               OLEDBG_END2
#endif

#if defined( USE_FRAMETOOLS )
               OutlineDoc_UpdateFrameToolButtons(
                     OutlineApp_GetActiveDoc(lpOutlineApp));
#endif
               break;

            case IDM_F_SAVE:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("OutlineApp_SaveCommand\r\n")
               OutlineApp_SaveCommand(lpOutlineApp);
               OLEDBG_END3
               break;

            case IDM_F_PRINT:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("OutlineApp_PrintCommand\r\n")
               OutlineApp_PrintCommand(lpOutlineApp);
               OLEDBG_END3
               break;

            case IDM_E_UNDO:
               // SORRY. NOT IMPLEMENTED
               break;

            case IDM_E_CUT:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("OutlineDoc_CutCommand\r\n")
               OutlineDoc_CutCommand(lpOutlineDoc);
               OLEDBG_END3

#if defined( USE_FRAMETOOLS )
               OutlineDoc_UpdateFrameToolButtons(lpOutlineDoc);
#endif
               break;

            case IDM_E_COPY:

               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("OutlineDoc_CopyCommand\r\n")
               OutlineDoc_CopyCommand(lpOutlineDoc);
               OLEDBG_END3

#if defined( USE_FRAMETOOLS )
               OutlineDoc_UpdateFrameToolButtons(lpOutlineDoc);
#endif
               break;

            case IDM_E_PASTE:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("OutlineDoc_PasteCommand\r\n")
               OutlineDoc_PasteCommand(lpOutlineDoc);
               OLEDBG_END3

#if defined( USE_FRAMETOOLS )
               OutlineDoc_UpdateFrameToolButtons(lpOutlineDoc);
#endif
               break;

#if defined( OLE_VERSION )
            case IDM_E_PASTESPECIAL:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("OleDoc_PasteSpecialCommand\r\n")
               OleDoc_PasteSpecialCommand((LPOLEDOC)lpOutlineDoc);
               OLEDBG_END3

#if defined( USE_FRAMETOOLS )
               OutlineDoc_UpdateFrameToolButtons(lpOutlineDoc);
#endif
               break;

#endif  // OLE_VERSION

            case IDM_E_CLEAR:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("OutlineDoc_ClearCommand\r\n")
               OutlineDoc_ClearCommand(lpOutlineDoc);
               OLEDBG_END3

#if defined( OLE_CNTR )
               /* NOTE: this call will attempt to recover
               **    resources by unloading DLL's that were loaded
               **    by OLE and are no longer being used. it is a
               **    good idea to call this API now and then if
               **    your app tends to run for a long time.
               **    otherwise these DLL's will be unloaded when
               **    the app exits. some apps may want to call
               **    this as part of idle-time processing. this
               **    call is optional.
               */
               OLEDBG_BEGIN2("CoFreeUnusedLibraries called\r\n")
               CoFreeUnusedLibraries();
               OLEDBG_END2
#endif

#if defined( USE_FRAMETOOLS )
               OutlineDoc_UpdateFrameToolButtons(lpOutlineDoc);
#endif
               break;

            case IDM_L_ADDLINE:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("OutlineDoc_AddTextLineCommand\r\n")
               OutlineDoc_AddTextLineCommand(lpOutlineDoc);
               OLEDBG_END3

#if defined( USE_FRAMETOOLS )
               OutlineDoc_UpdateFrameToolButtons(lpOutlineDoc);
               SetFocus(LineList_GetWindow(lpLL));
#endif
               break;

            case IDM_L_EDITLINE:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("OutlineDoc_EditLineCommand\r\n")
               OutlineDoc_EditLineCommand(lpOutlineDoc);
               OLEDBG_END3
               SetFocus(LineList_GetWindow(lpLL));
               break;

            case IDM_L_INDENTLINE:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("OutlineDoc_IndentCommand\r\n")
               OutlineDoc_IndentCommand(lpOutlineDoc);
               OLEDBG_END3
               break;

            case IDM_L_UNINDENTLINE:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("OutlineDoc_UnindentCommand\r\n")
               OutlineDoc_UnindentCommand(lpOutlineDoc);
               OLEDBG_END3
               break;

            case IDM_L_SETLINEHEIGHT:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("OutlineDoc_SetLineHeight\r\n")
               OutlineDoc_SetLineHeightCommand(lpOutlineDoc);
               OLEDBG_END3
               break;

            case IDM_E_SELECTALL:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("OutlineDoc_SelectAllCommand\r\n")
               OutlineDoc_SelectAllCommand(lpOutlineDoc);
               OLEDBG_END3
               break;

#if defined( OLE_CNTR )
            case IDM_E_INSERTOBJECT:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("ContainerDoc_InsertOleObjectCommand\r\n")
               ContainerDoc_InsertOleObjectCommand(lpContainerDoc);
               OLEDBG_END3

#if defined( USE_FRAMETOOLS )
               OutlineDoc_UpdateFrameToolButtons(lpOutlineDoc);
#endif
               break;

            case IDM_E_EDITLINKS:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("ContainerDoc_EditLinksCommand\r\n")
               ContainerDoc_EditLinksCommand(lpContainerDoc);
               OLEDBG_END3
               break;

            case IDM_E_CONVERTVERB:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("ContainerDoc_ConvertCommand\r\n")
               ContainerDoc_ConvertCommand(
                     lpContainerDoc, FALSE /*fMustActivate*/);
               OLEDBG_END3
               break;

            case IDM_E_PASTELINK:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("ContainerDoc_PasteLinkCommand\r\n")
               ContainerDoc_PasteLinkCommand(lpContainerDoc);
               OLEDBG_END3

#if defined( USE_FRAMETOOLS )
               OutlineDoc_UpdateFrameToolButtons(lpOutlineDoc);
#endif
               break;

#endif  // OLE_CNTR

            case IDM_N_DEFINENAME:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("OutlineDoc_DefineNameCommand\r\n")
               OutlineDoc_DefineNameCommand(lpOutlineDoc);
               OLEDBG_END3
               break;

            case IDM_N_GOTONAME:
               OleDbgOutNoPrefix2("\r\n");

               OLEDBG_BEGIN3("OutlineDoc_GotoNameCommand\r\n")
               OutlineDoc_GotoNameCommand(lpOutlineDoc);
               OLEDBG_END3
               break;

#if defined( USE_FRAMETOOLS )
            case IDM_O_BB_TOP:
               FrameTools_BB_SetState(
                     lpOutlineDoc->m_lpFrameTools, BARSTATE_TOP);
               OutlineDoc_AddFrameLevelTools(lpOutlineDoc);
               break;

            case IDM_O_BB_BOTTOM:
               FrameTools_BB_SetState(
                     lpOutlineDoc->m_lpFrameTools, BARSTATE_BOTTOM);
               OutlineDoc_AddFrameLevelTools(lpOutlineDoc);
               break;

            case IDM_O_BB_POPUP:
               FrameTools_BB_SetState(

lpOutlineDoc->m_lpFrameTools, BARSTATE_POPUP);
               OutlineDoc_AddFrameLevelTools(lpOutlineDoc);
               break;

            case IDM_O_BB_HIDE:
               FrameTools_BB_SetState(
                     lpOutlineDoc->m_lpFrameTools, BARSTATE_HIDE);
               OutlineDoc_AddFrameLevelTools(lpOutlineDoc);
               break;

            case IDM_O_FB_TOP:
               FrameTools_FB_SetState(
                     lpOutlineDoc->m_lpFrameTools, BARSTATE_TOP);
               OutlineDoc_AddFrameLevelTools(lpOutlineDoc);
               break;

            case IDM_O_FB_BOTTOM:
               FrameTools_FB_SetState(
                     lpOutlineDoc->m_lpFrameTools, BARSTATE_BOTTOM);
               OutlineDoc_AddFrameLevelTools(lpOutlineDoc);
               break;

            case IDM_O_FB_POPUP:
               FrameTools_FB_SetState(
                     lpOutlineDoc->m_lpFrameTools, BARSTATE_POPUP);
               OutlineDoc_AddFrameLevelTools(lpOutlineDoc);
               break;

            case IDM_FB_EDIT:

               switch (wNotifyCode) {
                  case EN_SETFOCUS:
                     OutlineDoc_SetFormulaBarEditFocus(
                           lpOutlineDoc, TRUE);
                     OutlineDoc_UpdateFrameToolButtons(lpOutlineDoc);
                     break;

                  case EN_KILLFOCUS:
                     OutlineDoc_SetFormulaBarEditFocus(
                           lpOutlineDoc, FALSE);
                     OutlineDoc_UpdateFrameToolButtons(lpOutlineDoc);
                     break;
               }
               break;

            case IDM_FB_CANCEL:

               SetFocus(hWnd);
               break;

            case IDM_F2:
               SendMessage(hWnd, WM_COMMAND, (WPARAM)IDM_FB_EDIT,
                     MAKELONG(0, EN_SETFOCUS));
               break;
#endif  // USE_FRAMETOOLS

            case IDM_ESCAPE:
               /* ESCAPE key pressed */

#if defined( USE_FRAMETOOLS )
               if (OutlineDoc_IsEditFocusInFormulaBar(lpOutlineDoc))
                  SendMessage(
                     hWnd, WM_COMMAND,(WPARAM)IDM_FB_CANCEL,(LPARAM)0);
#endif  // USE_FRAMETOOLS

#if defined( INPLACE_SVR )
               else {
                  LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOutlineDoc;

                  /* NOTE: The standard OLE 2.0 UI convention
                  **    is to have ESCAPE key exit in-place
                  **    activation (ie. UIDeactivate). If
                  **    possible it is recommended for both
                  **    in-place servers AND in-place containers
                  **    to take responsibility to handle the
                  **    ESCAPE key accelerator. The server has
                  **    the first crack at handling accelerator
                  **    keys and normally the server should do
                  **    the UIDeactivation.
                  */
                  if (lpServerDoc->m_fUIActive) {
                     SvrDoc_IPObj_UIDeactivate( (LPOLEINPLACEOBJECT)&
                           lpServerDoc->m_OleInPlaceObject);
                  }
               }
#endif  // INPLACE_SVR

               break;

#if defined( USE_HEADING )
            case IDC_BUTTON:
               if (wNotifyCode == BN_CLICKED) {
                  SendMessage(hWnd, WM_COMMAND, IDM_E_SELECTALL, 0L);
                  SetFocus(hWnd);
               }
               break;

            case IDM_O_HEAD_SHOW:
               OutlineDoc_ShowHeading(lpOutlineDoc, TRUE);
               break;

            case IDM_O_HEAD_HIDE:
               OutlineDoc_ShowHeading(lpOutlineDoc, FALSE);
               break;
#endif  // USE_HEADING

#if defined( OLE_CNTR )
            case IDM_O_SHOWOBJECT:
            {
               LPCONTAINERDOC lpContainerDoc =
                        (LPCONTAINERDOC)lpOutlineDoc;
               BOOL        fShowObject;

               fShowObject = !ContainerDoc_GetShowObjectFlag(
                     lpContainerDoc);
               ContainerDoc_SetShowObjectFlag(
                     lpContainerDoc, fShowObject);
               LineList_ForceRedraw(lpLL, TRUE);

               break;
            }
#endif  // OLE_CNTR

#if !defined( OLE_CNTR )
            // Container does not allow zoom factors > 100%
            case IDM_V_ZOOM_400:
            case IDM_V_ZOOM_300:
            case IDM_V_ZOOM_200:
#endif      // !OLE_CNTR

            case IDM_V_ZOOM_100:
            case IDM_V_ZOOM_75:
            case IDM_V_ZOOM_50:
            case IDM_V_ZOOM_25:
               OutlineDoc_SetCurrentZoomCommand(lpOutlineDoc, wID);
               break;

            case IDM_V_SETMARGIN_0:
            case IDM_V_SETMARGIN_1:
            case IDM_V_SETMARGIN_2:
            case IDM_V_SETMARGIN_3:
            case IDM_V_SETMARGIN_4:
               OutlineDoc_SetCurrentMarginCommand(lpOutlineDoc, wID);
               break;

            case IDM_V_ADDTOP_1:
            case IDM_V_ADDTOP_2:
            case IDM_V_ADDTOP_3:
            case IDM_V_ADDTOP_4:
            {
               UINT nHeightInHimetric;

               switch (wID) {
                  case IDM_V_ADDTOP_1:
                     nHeightInHimetric = 1000;
                     break;

                  case IDM_V_ADDTOP_2:
                     nHeightInHimetric = 2000;
                     break;

                  case IDM_V_ADDTOP_3:
                     nHeightInHimetric = 3000;
                     break;

                  case IDM_V_ADDTOP_4:
                     nHeightInHimetric = 4000;
                     break;
               }

               OutlineDoc_AddTopLineCommand(
                     lpOutlineDoc, nHeightInHimetric);
               break;
            }

            case IDM_H_ABOUT:
               OutlineApp_AboutCommand(lpOutlineApp);
               break;

            case IDM_D_DEBUGLEVEL:
               SetDebugLevelCommand();
               break;

#if defined( OLE_VERSION )
            case IDM_D_INSTALLMSGFILTER:
               InstallMessageFilterCommand();
               break;

            case IDM_D_REJECTINCOMING:
               RejectIncomingCommand();
               break;
#endif  // OLE_VERSION

#if defined( INPLACE_CNTR )
            case IDM_D_INSIDEOUT:
               g_fInsideOutContainer = !g_fInsideOutContainer;

               // force all object to unload so they can start new
               // activation behavior.
               ContainerDoc_UnloadAllOleObjectsOfClass(
                     (LPCONTAINERDOC)lpOutlineDoc,
                     &CLSID_NULL,
                     OLECLOSE_SAVEIFDIRTY
               );
               OutlineDoc_ForceRedraw(lpOutlineDoc, TRUE);
               break;
#endif  // INPLACE_CNTR

#if defined( OLE_CNTR )
            case IDC_LINELIST: {

               if (wNotifyCode == LBN_DBLCLK) {

                  /* NOTE: a container should execute the
                  **    OLEIVERB_PRIMARY verb on an OLE object
                  **    when the user DBLCLK's the object.
                  */
                  int nIndex = LineList_GetFocusLineIndex(lpLL);
                  LPLINE lpLine = LineList_GetLine(lpLL, nIndex);

                  if (lpLine &&
                        Line_GetLineType(lpLine)==CONTAINERLINETYPE) {
                     MSG msg;

                     _fmemset((LPMSG)&msg,0,sizeof(msg));
                     msg.hwnd = hWnd;
                     msg.message = Message;
                     msg.wParam = wParam;
                     msg.lParam = lParam;

                     ContainerLine_DoVerb(
                           (LPCONTAINERLINE)lpLine,
                           OLEIVERB_PRIMARY,
                           (LPMSG)&msg,
                           TRUE,
                           TRUE
                     );
                  }

#if defined( INPLACE_CNTR )
                  { // BEGIN BLOCK
                     LPCONTAINERDOC lpContainerDoc =
                           (LPCONTAINERDOC) lpOutlineDoc;
                     if (lpContainerDoc->m_fAddMyUI) {
                        /* NOTE: fAddMyUI is TRUE when
                        **    there was previously an in-place
                        **    active object which got
                        **    UIDeactivated as a result of this
                        **    DBLCLK AND the DBLCLK did NOT
                        **    result in in-place activating
                        **    another object.
                        **    (see IOleInPlaceSite::OnUIActivate and
                        **    IOleInPlaceSite::OnUIDeactivate
                        **    methods).
                        */

                        /* NOTE: You need to generate
                        **    QueryNewPalette only if you own
                        **    the top level frame (ie. you are
                        **    a top-level inplace container).
                        */

                        OleApp_QueryNewPalette((LPOLEAPP)g_lpApp);

#if defined( USE_DOCTOOLS )
                        ContainerDoc_AddDocLevelTools(lpContainerDoc);
#endif

#if defined( USE_FRAMETOOLS )
                        ContainerDoc_AddFrameLevelUI(lpContainerDoc);
#endif
                        lpContainerDoc->m_fAddMyUI = FALSE;
                     }
                  } // END BLOCK
#endif // INPLACE_CNTR
               }
               break;
            }
#endif  // OLE_CNTR

            default:

#if defined( OLE_CNTR )
               if (wID >= IDM_E_OBJECTVERBMIN) {

                  OleDbgOutNoPrefix2("\r\n");
                  OLEDBG_BEGIN3("ContainerDoc_ContainerLineDoVerbCommand\r\n")
                  ContainerDoc_ContainerLineDoVerbCommand(
                        (LPCONTAINERDOC)lpOutlineDoc,
                        (LONG)(wID-IDM_E_OBJECTVERBMIN)
                  );
                  OLEDBG_END3
                  break;
               }
#endif  // OLE_CNTR
               return DefWindowProc(hWnd, Message, wParam, lParam);
         }

         break;  /* End of WM_COMMAND */
      }
      default:

         if (Message == g_uMsgHelp) {
            /* Handle OLE2UI dialog's help messages.
            ** We get the hDlg of the dialog that called us in the wParam
            ** and the dialog type in the LOWORD of the lParam,
            ** so we pass this along to our help function.
            */
            OutlineDoc_DialogHelp((HWND)wParam, LOWORD(lParam));
            break;
         }

         /* For any message for which you don't specifically provide a  */
         /* service routine, you should return the message to Windows   */
         /* for default message processing.                             */
         return DefWindowProc(hWnd, Message, wParam, lParam);
   }

   return (LRESULT)0;

} /* End of DocWndProc */

//***********************************************************************
//*
//* LineListWndProc()  Drag and Drop Listbox Window Proc Sub-Class
//*
//* Sub Class the Ownerdraw list box in order to activate the drag drop.
//***********************************************************************

LRESULT FAR PASCAL LineListWndProc(
   HWND   hWnd,
   UINT   Message,
   WPARAM wParam,
   LPARAM lParam
)
{
   HWND         hwndParent = GetParent ( hWnd );
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC) GetWindowLong( hwndParent, 0 );
   LPLINELIST   lpLL = OutlineDoc_GetLineList(lpOutlineDoc);

#if defined( OLE_VERSION )
   LPOLEAPP     lpOleApp = (LPOLEAPP)lpOutlineApp;
   LPOLEDOC     lpOleDoc = (LPOLEDOC)lpOutlineDoc;
#endif  // OLE_VERSION

#if defined( INPLACE_SVR )
   LPSERVERDOC  lpServerDoc = (LPSERVERDOC)lpOutlineDoc;
   static BOOL  fUIActivateClick = FALSE;
   static BOOL  fInWinPosChged = FALSE;
#endif  // INPLACE_SVR

#if defined( INPLACE_CNTR )
   LPCONTAINERAPP lpContainerApp=(LPCONTAINERAPP)lpOutlineApp;
   LPCONTAINERDOC lpContainerDoc=(LPCONTAINERDOC)lpOutlineDoc;
#endif  // INPLACE_CNTR

   switch (Message) {

      case WM_KILLFOCUS:
         /* NOTE: when our window looses focus we
         **    should not display any active selection
         */
#if defined( INPLACE_CNTR )
         if (! lpContainerApp->m_fPendingUIDeactivate)
#endif  // INPLACE_CNTR
            LineList_RemoveSel(lpLL);
         break;

      case WM_SETFOCUS:

#if defined( INPLACE_CNTR )
         {
            HWND hWndObj=ContainerDoc_GetUIActiveWindow(lpContainerDoc);

            /* NOTE: if there is a UIActive in-place object, we must
            **    forward focus to its window as long as there is
            **    not a pending UIDeactivate. if the mouse is
            **    clicked outside of the object and the object is
            **    about to be deactivated then we do NOT want to
            **    forward focus to the object. we do NOT want it to
            **    restore its selection feedback.
            */
            if (lpContainerApp->m_fPendingUIDeactivate)
               break;
            else if (hWndObj) {
               SetFocus(hWndObj);
               break;      // do not restore containers selection state
            }
         }
#endif  // INPLACE_CNTR

         /* NOTE: when our window gains focus we
         **    should restore the previous selection
         */
         LineList_RestoreSel(lpLL);

         break;

#if defined( INPLACE_SVR )
      case WM_MOUSEACTIVATE:
      {
         if (lpServerDoc->m_fInPlaceActive && !lpServerDoc->m_fUIActive) {
            fUIActivateClick = TRUE;
         };
         break;
      }

#endif  // INPLACE_SVR

#if defined( USE_FRAMETOOLS )
      case WM_CHAR:
      {
         OutlineDoc_SetFormulaBarEditFocus(lpOutlineDoc, TRUE);
         FrameTools_FB_SetEditText(lpOutlineDoc->m_lpFrameTools, NULL);
         FrameTools_FB_SendMessage(
               lpOutlineDoc->m_lpFrameTools,
               IDM_FB_EDIT,
               Message,
               wParam,
               lParam
         );

         return (LRESULT)0;   // don't do default listbox processing
      }
#endif  // USE_FRAMETOOLS

#if defined( INPLACE_CNTR )
      case WM_VSCROLL:
      {
         if (wParam == SB_ENDSCROLL) {
            /* NOTE: after scrolling finishes, update position of
            **    in-place visible windows.
            **    (ICNTROTL specific) we first let the ListBox
            **    perform it normal processing with the EndScroll
            **    message. also we let the ListBox handle all other
            **    scroll messages.
            */
            LRESULT lResult =  CallWindowProc(
                  (WNDPROC)lpOutlineApp->m_ListBoxWndProc,
                  hWnd,
                  Message,
                  wParam,
                  lParam
            );
            ContainerDoc_UpdateInPlaceObjectRects(lpContainerDoc, 0);
            return lResult;
         }

         break;
      }
#endif  // INPLACR_CNTR

#if defined( USE_HEADING )
      case WM_HSCROLL:
      {
         LPHEADING lphead = OutlineDoc_GetHeading(lpOutlineDoc);

         Heading_CH_SendMessage(lphead, Message, wParam, lParam);

         break;
      }

      /* NOTE: WM_PAINT trapped in order to track vertical scrolling
      **    that has taken place so the row headings can be
      **    coordinated with the LineList. we wanted to trap instead
      **    but it is not generated from scrolling without using
      **    scroll bar (e.g. use keyboard).
      */
      case WM_PAINT:
      {
         Heading_RH_Scroll(OutlineDoc_GetHeading(lpOutlineDoc), hWnd);
         break;
      }

#endif  // USE_HEADING

      case WM_LBUTTONUP:
      {

#if defined( USE_DRAGDROP )
         if (lpOleDoc->m_fPendingDrag) {
            /* ButtonUP came BEFORE distance/time threshholds were
            **    exceeded. clear fPendingDrag state.
            */
            ReleaseCapture();
            KillTimer(hWnd, 1);
            lpOleDoc->m_fPendingDrag = FALSE;
         }
#endif  // USE_DRAGDROP

#if defined( INPLACE_SVR )
         if (fUIActivateClick) {
            fUIActivateClick = FALSE;
            ServerDoc_UIActivate((LPSERVERDOC) lpOleDoc);
         }
#endif  // INPLACE_SVR

#if defined( INPLACE_CNTR )
         {
            /* check if a UIDeactivate is pending.
            **      (see comment in WM_LBUTTONDOWN)
            */
            if ( lpContainerApp->m_fPendingUIDeactivate ) {
               ContainerLine_UIDeactivate(
                     lpContainerDoc->m_lpLastUIActiveLine);

               lpContainerApp->m_fPendingUIDeactivate = FALSE;
            }
         }
#endif  // INPLACE_CNTR

         break;
      }

      case WM_LBUTTONDOWN:
      {
         POINT pt;

         pt.x = (int)(short)LOWORD (lParam );
         pt.y = (int)(short)HIWORD (lParam );

#if defined( INPLACE_SVR ) || defined( INPLACE_CNTR )
         /* NOTE: see context sensitive help technote (CSHELP.DOC)
         **    m_fCSHelpMode flag is set when SHIFT-F1 context
         **    sensitive help is entered.
         **    if this flag is set then the button click should not
         **    cause any action. if the application implements a
         **    help system, then context sensitive help should be
         **    given for the location clicked by the user.
         */
         if (lpOleDoc->m_fCSHelpMode) {
            return (LRESULT)0;   // eat the button click because we do
                            // not give any help.
         }
#endif  // INPLACE_SVR || INPLACE_CNTR

#if defined( INPLACE_CNTR )
         {
            /* NOTE: both inside-out and outside-in style
            **    containers must check if the mouse click is
            **    outside of the current UIActive object (if
            **    any). If so, then set the flag indicating that
            **    there is a pending UIDeactivate needed. We do NOT
            **    want to do it now,
            **    because that would result in un-wanted movement of
            **    the data on the screen as frame adornments (eg.
            **    toolbar) and/or object adornments (eg. ruler) would
            **    be removed from the screen. we want to defer the
            **    UIDeactivate till the mouse up event. The listbox's
            **    default processing captures the mouse on button down
            **    so that it is sure to get the button up message.
            **
            **    SPECIAL NOTE: there is potential interaction here
            **    with Drag/Drop. if this button down event actually
            **    starts a Drag/Drop operation, then OLE does the mouse
            **    capture. in this situation we will NOT get our button
            **    up event. we must instead perform the UIDeactivate
            **    when the drop operation finishes
            */
            lpContainerApp->m_fPendingUIDeactivate =
                  ContainerDoc_IsUIDeactivateNeeded(lpContainerDoc, pt);
         }
#endif  // INPLACE_CNTR

#if defined( USE_DRAGDROP )

         /* NOTE: check if this is a button down on the region
         **    that is a handle to start a drag operation. for us,
         **    this this the top/bottom border of the selection.
         **    do NOT want to start a drag immediately; we want to
         **    wait until the mouse moves a certain threshold. if
         **    LButtonUp comes before mouse move to start drag, then
         **    the fPendingDrag state is cleared. we must capture
         **    the mouse to ensure the modal state is handled
         **    properly.
         */
         if ( OleDoc_QueryDrag(lpOleDoc, pt.y) ) {
            lpOleDoc->m_fPendingDrag = TRUE;
            lpOleDoc->m_ptButDown = pt;
            SetTimer(hWnd, 1, lpOleApp->m_nDragDelay, NULL);
            SetCapture(hWnd);

            /* We do NOT want to do the listbox's default
            **    processing which would be to capture the mouse
            **    and enter a modal multiple selection state until
            **    a mouse up occurs. we have just finished a modal
            **    drag/drop operation where OLE has captured the
            **    mouse. thus by now the mouse up has already occured.
            */

            return (LRESULT)0;   // don't do default listbox processing
         }
#endif  // USE_DRAGDROP

         break;
      }

      case WM_MOUSEMOVE: {

#if defined( USE_DRAGDROP )

         int  x = (int)(short)LOWORD (lParam );
         int  y = (int)(short)HIWORD (lParam );
         POINT pt = lpOleDoc->m_ptButDown;
         int nDragMinDist = lpOleApp->m_nDragMinDist;

         if (lpOleDoc->m_fPendingDrag) {

            if (! ( ((pt.x - nDragMinDist) <= x)
                  && (x <= (pt.x + nDragMinDist))
                  && ((pt.y - nDragMinDist) <= y)
                  && (y <= (pt.y + nDragMinDist)) ) ) {

               DWORD dwEffect;

               // mouse moved beyond threshhold to start drag
               ReleaseCapture();
               KillTimer(hWnd, 1);
               lpOleDoc->m_fPendingDrag = FALSE;

               // perform the modal drag/drop operation.
               dwEffect = OleDoc_DoDragDrop( lpOleDoc );

#if defined( INPLACE_CNTR )
               {
                  /* if necessary UIDeactive the in-place object.
                  **    this applies to outside-in style
                  **    container only.
                  **    (see comment above)
                  */
                  if (lpContainerApp->m_fPendingUIDeactivate) {
                     lpContainerApp->m_fPendingUIDeactivate = FALSE;

                     // do not UIDeactivate if drag/drop was canceled
                     if (dwEffect != DROPEFFECT_NONE)
                        ContainerLine_UIDeactivate(
                              lpContainerDoc->m_lpLastUIActiveLine
                        );
                  }
               }
#endif  // INPLACE_CNTR

               return (LRESULT)0; // don't do default listbox process
            }
            else {
               /* cursor did not move from initial mouse down
               **    (pending drag) point.
               */
               return (LRESULT)0; // don't do default listbox process
            }
         }

#endif  // USE_DRAGDROP

#if defined( INPLACE_CNTR )
         { // BEGIN BLOCK
            if (lpContainerDoc->m_fAddMyUI) {
               /* NOTE: fAddMyUI is TRUE when
               **    there was previously an in-place
               **    active object which got
               **    UIDeactivated as a result of a
               **    DBLCLK AND the DBLCLK did NOT
               **    result in in-place activating
               **    another object.
               **    (see IOleInPlaceSite::OnUIActivate and
               **    IOleInPlaceSite::OnUIDeactivate
               **    methods).
               */
#if defined( USE_DOCTOOLS )
               ContainerDoc_AddDocLevelTools(lpContainerDoc);
#endif

#if defined( USE_FRAMETOOLS )
               ContainerDoc_AddFrameLevelUI(lpContainerDoc);
#endif
               lpContainerDoc->m_fAddMyUI = FALSE;
            }
         } // END BLOCK
#endif // INPLACE_CNTR

         break;
      }

#if defined( USE_DRAGDROP )
      case WM_TIMER:
      {
         DWORD dwEffect;

         // drag time delay threshhold exceeded -- start drag
         ReleaseCapture();
         KillTimer(hWnd, 1);
         lpOleDoc->m_fPendingDrag = FALSE;

         // perform the modal drag/drop operation.
         dwEffect = OleDoc_DoDragDrop( lpOleDoc );

#if defined( INPLACE_CNTR )
         /* if necessary UIDeactive the in-place object.
         **    this applies to outside-in style
         **    container only.
         **    (see comment above)
         */
         if (lpContainerApp->m_fPendingUIDeactivate) {
            lpContainerApp->m_fPendingUIDeactivate = FALSE;

            // do not UIDeactivate if drag/drop was canceled
            if (dwEffect != DROPEFFECT_NONE)
               ContainerLine_UIDeactivate(
                     lpContainerDoc->m_lpLastUIActiveLine);
         }
#endif  // INPLACE_CNTR
         break;
      }
#endif  // USE_DRAGDROP

      case WM_SETCURSOR:
      {
         RECT rc;
         POINT ptCursor;
#if defined( INPLACE_SVR ) || defined( INPLACE_CNTR )
         /* NOTE: see context sensitive help technote (CSHELP.DOC)
         **    m_fCSHelpMode flag is set when SHIFT-F1 context
         **    sensitive help is entered.
         **    if this flag is set then the context sensitive help
         **    cursor should be shown.
         */
         if (lpOleDoc->m_fCSHelpMode) {
            SetCursor(UICursorLoad(IDC_CONTEXTHELP));
            return (LRESULT)TRUE;
         }
#endif  // INPLACE_SVR || INPLACE_CNTR

         GetCursorPos((POINT FAR*)&ptCursor);
         ScreenToClient(hWnd, (POINT FAR*)&ptCursor);
         GetClientRect(hWnd, (LPRECT)&rc);

         // use arrow cursor if in scroll bar
         if (! PtInRect((LPRECT)&rc, ptCursor) )
            SetCursor(LoadCursor(NULL, IDC_ARROW) );

#if defined( USE_DRAGDROP )
         // use arrow cursor if on drag handle (top/bottom of selection)
         else if ( OleDoc_QueryDrag ( lpOleDoc, ptCursor.y) )
            SetCursor(LoadCursor(NULL, IDC_ARROW) );
#endif  // USE_DRAGDROP

         else
            SetCursor(lpOutlineApp->m_hcursorSelCur);

         return (LRESULT)TRUE;
      }

#if defined( INPLACE_SVR )

      /* The handling of WM_WINDOWPOSCHANGED message is ISVROTL
      **    application specific. The nature of the owner-draw list
      **    box used by the ISVROTL application causes a recursive
      **    call to this message in some situations when in-place
      **    active. in order not to crash this recursive call must be
      **    guarded.
      */
      case WM_WINDOWPOSCHANGED:
      {
         WINDOWPOS FAR* lpWinPos = (WINDOWPOS FAR*) lParam;
         LRESULT lResult;

         // guard against recursive call
         if (fInWinPosChged)
            return (LRESULT)0;

         fInWinPosChged = TRUE;
         lResult = CallWindowProc(
               (WNDPROC)lpOutlineApp->m_ListBoxWndProc,
               hWnd,
               Message,
               wParam,
               lParam
         );
         fInWinPosChged = FALSE;

         return lResult;
      }
#endif  // INPLACE_SVR

   }

   return CallWindowProc(
         (WNDPROC)lpOutlineApp->m_ListBoxWndProc,
         hWnd,
         Message,
         wParam,
         lParam
   );

}

// Utility function to count the number of accelerator items in an
//  accelerator table.  A number of OLE APIs need this count, so
//  this can be quite handy.
// (code shamelessly stolen from the Microsoft Foundation Classes)

int GetAccelItemCount(HACCEL hAccel)
{
#if defined( WIN32 )
    return CopyAcceleratorTable(hAccel, NULL, 0);
#else
   #pragma pack(1)
   typedef struct tagACCELERATOR
   {
      BYTE    fFlags;
      WORD    wEvent;
      WORD    wID;
   } ACCELERATOR;
   #pragma pack()

   // attempt to lock down the accelerator resource
   ACCELERATOR FAR* pAccel;
   int cAccelItems = 1;
   if (hAccel == NULL ||
      (pAccel = (ACCELERATOR FAR*)LockResource((HGLOBAL)hAccel)) == NULL)
   {
      // NULL accerator table or LockResource failed on the HACCEL,
      //  no accelerators
      return 0;
   }
   // otherwise, count them -- last entry in accel table has 0x80 bit set
   while ((pAccel->fFlags & 0x80) == 0)
   {
      ++cAccelItems;
      ++pAccel;
   }
   UnlockResource((HGLOBAL)hAccel);
   return cAccelItems;
#endif
}


MEMMGR.C

/*************************************************************************
**
**    OLE 2 Sample Code
**
**    memmgr.c
**
**    This file contains memory management functions.
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#include "outline.h"

/* New
 * ---
 *
 *      Allocate memory for a new structure
 */
LPVOID New(DWORD lSize)
{
LPVOID lp = OleStdMalloc((ULONG)lSize);

return lp;
}

/* Delete
 * ------
 *
 *      Free memory allocated for a structure
 */
void Delete(LPVOID lp)
{
OleStdFree(lp);
}


MESSAGE.H

/*************************************************************************
**
**    OLE 2.0 Server Sample Code
**
**    message.h
**
**    This file is a user customizable list of status messages associated
**    with menu items.
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

// Status bar messages and associated data.

// Message type for status bar messages.
typedef struct {
UINT wIDItem;
char *string;
} STATMESG;

/*
 * CUSTOMIZATION NOTE:  Be sure to change NUM_POPUP if you
 *                      change the number of popup messages.
 */

// REVIEW: these messages should be loaded from a string resource

// List of all menu item messages.
STATMESG MesgList[] =
{
{ IDM_F_NEW,        "Creates a new outline" },
{ IDM_F_OPEN,       "Opens an existing outline file"    },
{ IDM_F_SAVE,       "Saves the outline" },
{ IDM_F_SAVEAS,     "Saves the outline with a new name" },
{ IDM_F_PRINT,      "Prints the outline" },
{ IDM_F_PRINTERSETUP, "Changes the printer and the printing options" },
{ IDM_F_EXIT,       "Quits the application, prompting to save changes" },

{ IDM_E_UNDO,       "Undo not yet implemented" },
{ IDM_E_CUT,        "Cuts the selection and puts it on the Clipboard" },
{ IDM_E_COPY,       "Copies the selection and puts it on the Clipboard" },
{ IDM_E_PASTE,      "Inserts the Clipboard contents after current line" },
{ IDM_E_PASTESPECIAL,"Allows pasting Clipboard data using a special format" },
{ IDM_E_CLEAR,      "Clears the selection" },
{ IDM_E_SELECTALL,  "Selects the entire outline" },
#if defined( OLE_CNTR )
{ IDM_E_INSERTOBJECT, "Inserts new object after current line" },
{ IDM_E_EDITLINKS, "Edit and view links contained in the document" },
   { IDM_E_CONVERTVERB, "Converts or activates an object as another type" },
{ IDM_E_OBJECTVERBMIN, "Opens, edits or interacts with an object" },
{ IDM_E_OBJECTVERBMIN+1, "Opens, edits or interacts with an object1" },
{ IDM_E_OBJECTVERBMIN+2, "Opens, edits or interacts with an object2" },
{ IDM_E_OBJECTVERBMIN+3, "Opens, edits or interacts with an object3" },
{ IDM_E_OBJECTVERBMIN+4, "Opens, edits or interacts with an object4" },
{ IDM_E_OBJECTVERBMIN+5, "Opens, edits or interacts with an object5" },
#endif

{ IDM_L_ADDLINE,    "Adds a new line after current line" },
{ IDM_L_EDITLINE,   "Edits the current line" },
{ IDM_L_INDENTLINE, "Indents the selection" },
{ IDM_L_UNINDENTLINE, "Unindents the selection" },
{ IDM_L_SETLINEHEIGHT, "Modify the height of a line" },

{ IDM_N_DEFINENAME, "Assigns a name to the selection" },
{ IDM_N_GOTONAME,   "Jumps to a specified place in the outline" },

{ IDM_H_ABOUT,      "Displays program info, version no., and copyright" },

{ IDM_D_DEBUGLEVEL,     "Set debug level (0-4)" },
{ IDM_D_INSTALLMSGFILTER,"Install/deinstall the IMessageFilter" },
{ IDM_D_REJECTINCOMING, "Return RETRYLATER to incoming method calls" },

{ IDM_O_BB_TOP, "Position ButtonBar at top of window" },
{ IDM_O_BB_BOTTOM, "Position ButtonBar at botttom of window" },
{ IDM_O_BB_POPUP, "Put ButtonBar in popup pallet" },
{ IDM_O_BB_HIDE, "Hide ButtonBar" },

{ IDM_O_FB_TOP, "Position FormulaBar at top of window" },
{ IDM_O_FB_BOTTOM, "Position FormulaBar at botttom of window" },
{ IDM_O_FB_POPUP, "Put FormulaBar in popup pallet" },

{ IDM_O_HEAD_SHOW, "Show row/column headings" },
{ IDM_O_HEAD_HIDE, "Hide row/column headings" },
{ IDM_O_SHOWOBJECT, "Show border around objects/links" },

{ IDM_V_ZOOM_400, "Set document zoom level" },
{ IDM_V_ZOOM_300, "Set document zoom level" },
{ IDM_V_ZOOM_200, "Set document zoom level" },
{ IDM_V_ZOOM_100, "Set document zoom level" },
{ IDM_V_ZOOM_75, "Set document zoom level" },
{ IDM_V_ZOOM_50, "Set document zoom level" },
{ IDM_V_ZOOM_25, "Set document zoom level" },

{ IDM_V_SETMARGIN_0, "Remove left/right document margins" },
{ IDM_V_SETMARGIN_1, "Set left/right document margins" },
{ IDM_V_SETMARGIN_2, "Set left/right document margins" },
{ IDM_V_SETMARGIN_3, "Set left/right document margins" },
{ IDM_V_SETMARGIN_4, "Set left/right document margins" },

{ IDM_V_ADDTOP_1, "Add top line" },
{ IDM_V_ADDTOP_2, "Add top line" },
{ IDM_V_ADDTOP_3, "Add top line" },
{ IDM_V_ADDTOP_4, "Add top line" }
};

#define NUM_STATS   sizeof(MesgList)/sizeof(MesgList[0])
#define NUM_POPUP   10  // Maximum number of popup messages.
#define MAX_MESSAGE 100 // Maximum characters in a popup message.


OLEAPP.C

/*************************************************************************
**
**    OLE 2 Sample Code
**
**    oleapp.c
**
**    This file contains functions and methods that are common to
**    server and the client version of the app. This includes the class
**    factory methods and all OleApp functions.
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#include "outline.h"
#include <ole2ver.h>

OLEDBGDATA

extern LPOUTLINEAPP             g_lpApp;

extern IUnknownVtbl             g_OleApp_UnknownVtbl;

extern IUnknownVtbl             g_OleDoc_UnknownVtbl;
extern IPersistFileVtbl         g_OleDoc_PersistFileVtbl;
extern IOleItemContainerVtbl    g_OleDoc_OleItemContainerVtbl;
extern IExternalConnectionVtbl  g_OleDoc_ExternalConnectionVtbl;
extern IDataObjectVtbl          g_OleDoc_DataObjectVtbl;

#if defined( USE_DRAGDROP )
extern IDropTargetVtbl          g_OleDoc_DropTargetVtbl;
extern IDropSourceVtbl          g_OleDoc_DropSourceVtbl;
#endif  // USE_DRAGDROP

#if defined( OLE_SERVER )
extern IOleObjectVtbl       g_SvrDoc_OleObjectVtbl;
extern IPersistStorageVtbl  g_SvrDoc_PersistStorageVtbl;

#if defined( SVR_TREATAS )
extern IStdMarshalInfoVtbl  g_SvrDoc_StdMarshalInfoVtbl;
#endif  // SVR_TREATAS

extern IUnknownVtbl         g_PseudoObj_UnknownVtbl;
extern IOleObjectVtbl       g_PseudoObj_OleObjectVtbl;
extern IDataObjectVtbl      g_PseudoObj_DataObjectVtbl;

#if defined( INPLACE_SVR )
extern IOleInPlaceObjectVtbl        g_SvrDoc_OleInPlaceObjectVtbl;
extern IOleInPlaceActiveObjectVtbl  g_SvrDoc_OleInPlaceActiveObjectVtbl;
#endif  // INPLACE_SVR

#endif  // OLE_SERVER

#if defined( OLE_CNTR )

extern IOleUILinkContainerVtbl  g_CntrDoc_OleUILinkContainerVtbl;
extern IUnknownVtbl             g_CntrLine_UnknownVtbl;
extern IOleClientSiteVtbl       g_CntrLine_OleClientSiteVtbl;
extern IAdviseSinkVtbl          g_CntrLine_AdviseSinkVtbl;

#if defined( INPLACE_CNTR )
extern IOleInPlaceSiteVtbl      g_CntrLine_OleInPlaceSiteVtbl;
extern IOleInPlaceFrameVtbl     g_CntrApp_OleInPlaceFrameVtbl;
extern BOOL g_fInsideOutContainer;
#endif  // INPLACE_CNTR

#endif  // OLE_CNTR

// REVIEW: these are NOT useful end-user messages
static OLECHAR ErrMsgCreateCF[] = OLESTR("Can't create Class Factory!");
static OLECHAR ErrMsgRegCF[] = OLESTR("Can't register Class Factory!");
static OLECHAR ErrMsgRegMF[] = OLESTR("Can't register Message Filter!");

extern UINT g_uMsgHelp;

/* OleApp_InitInstance
 * -------------------
 *
 * Initialize the app instance by creating the main frame window and
 * performing app instance specific initializations
 *  (eg. initializing interface Vtbls).
 *
 * RETURNS: TRUE if the memory could be allocated, and the server app
 *               was properly initialized.
 *          FALSE otherwise
 *
 */
BOOL OleApp_InitInstance(LPOLEAPP lpOleApp, HINSTANCE hInst, int nCmdShow)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)lpOleApp;
   HRESULT hrErr;
   DWORD   dwBuildVersion = OleBuildVersion();
   LPMALLOC lpMalloc = NULL;

   OLEDBG_BEGIN3("OleApp_InitInstance\r\n")

   lpOleApp->m_fOleInitialized = FALSE;
   lpOleApp->m_fOleStdInit = FALSE;

   /* NOTE: check if the build version of the OLE2 DLL's match
   **    what our application is expecting.
   */
   if (HIWORD(dwBuildVersion) != rmm || LOWORD(dwBuildVersion) < rup) {
      OleDbgAssertSz(0, "ERROR: OLE 2.0 DLL's are NOT compatible!");

#if !defined( _DEBUG )
      return FALSE;   // Wrong version of DLL's
#endif
   }

   /* NOTE: the OLE libraries must be properly initialized before
   **    making any calls. OleInitialize automatically calls
   **    CoInitialize. we will use the default task memory allocator
   **    therefore we pass NULL to OleInitialize.
   */
   OLEDBG_BEGIN2("OleInitialize called\r\n")
   hrErr = OleInitialize(NULL);
   OLEDBG_END2

   if (hrErr != NOERROR) {
      OutlineApp_ErrorMessage(lpOutlineApp,OLESTR("OLE initialization failed!"));
      goto error;
   }

   /*****************************************************************
   ** NOTE: we must remember the fact that OleInitialize has
   **    been call successfully. the very last thing an app must
   **    be do is properly shut down OLE by calling
   **    OleUninitialize. This call MUST be guarded! it is only
   **    allowable to call OleUninitialize if OleInitialize has
   **    been called SUCCESSFULLY.
   *****************************************************************/

   lpOleApp->m_fOleInitialized = TRUE;

   lpOleApp->m_fOleStdInit = OleStdInitialize(hInst);
   if (!lpOleApp->m_fOleStdInit)
   {
      OutlineApp_ErrorMessage(lpOutlineApp,OLESTR("OleStd initialization failed!"));
      goto error;
   }

   // Initialize the OLE 2.0 interface method tables.
   if (! OleApp_InitVtbls(lpOleApp))
      goto error;

   // Register OLE 2.0 clipboard formats.
   lpOleApp->m_cfEmbedSource = RegisterClipboardFormat(CF_EMBEDSOURCE);
   lpOleApp->m_cfEmbeddedObject = RegisterClipboardFormat(
         CF_EMBEDDEDOBJECT
   );
   lpOleApp->m_cfLinkSource = RegisterClipboardFormat(CF_LINKSOURCE);
   lpOleApp->m_cfFileName = RegisterClipboardFormat(CF_FILENAME);
   lpOleApp->m_cfObjectDescriptor =
         RegisterClipboardFormat(CF_OBJECTDESCRIPTOR);
   lpOleApp->m_cfLinkSrcDescriptor =
         RegisterClipboardFormat(CF_LINKSRCDESCRIPTOR);

   lpOleApp->m_cRef                    = 0;
   lpOleApp->m_cDoc                    = 0;
   lpOleApp->m_fUserCtrl               = FALSE;
   lpOleApp->m_dwRegClassFac           = 0;
   lpOleApp->m_lpClassFactory          = NULL;
   lpOleApp->m_cModalDlgActive         = 0;

   INIT_INTERFACEIMPL(
         &lpOleApp->m_Unknown,
         &g_OleApp_UnknownVtbl,
         lpOleApp
   );

#if defined( USE_DRAGDROP )

   // delay before dragging should start, in milliseconds
   lpOleApp->m_nDragDelay = GetProfileInt(
         "windows",
         "DragDelay",
         DD_DEFDRAGDELAY
   );

   // minimum distance (radius) before drag should start, in pixels
   lpOleApp->m_nDragMinDist = GetProfileInt(
         "windows",
         "DragMinDist",
         DD_DEFDRAGMINDIST
   );

   // delay before scrolling, in milliseconds
   lpOleApp->m_nScrollDelay = GetProfileInt(
         "windows",
         "DragScrollDelay",
         DD_DEFSCROLLDELAY
   );

   // inset-width of the hot zone, in pixels
   lpOleApp->m_nScrollInset = GetProfileInt(
         "windows",
         "DragScrollInset",
         DD_DEFSCROLLINSET
   );

   // scroll interval, in milliseconds
   lpOleApp->m_nScrollInterval = GetProfileInt(
         "windows",
         "DragScrollInterval",
         DD_DEFSCROLLINTERVAL
   );

#if defined( IF_SPECIAL_DD_CURSORS_NEEDED )
   // This would be used if the app wanted to have custom drag/drop cursors
   lpOleApp->m_hcursorDragNone  = LoadCursor ( hInst, "DragNoneCur" );
   lpOleApp->m_hcursorDragCopy  = LoadCursor ( hInst, "DragCopyCur" );
   lpOleApp->m_hcursorDragMove  = LoadCursor ( hInst, "DragMoveCur" );
   lpOleApp->m_hcursorDragLink  = LoadCursor ( hInst, "DragLinkCur" );
#endif  // IF_SPECIAL_DD_CURSORS_NEEDED

#endif  // USE_DRAGDROP

   lpOleApp->m_lpMsgFilter = NULL;

#if defined( USE_MSGFILTER )
   /* NOTE: Register our message filter upon app startup. the
   **    message filter is used to handle concurrency.
   **    we will use a standard implementation of IMessageFilter that
   **    is included as part of the OLE2UI library.
   */
   lpOleApp->m_lpMsgFilter = NULL;
   if (! OleApp_RegisterMessageFilter(lpOleApp))
      goto error;

   /* NOTE: because our app is initially INVISIBLE, we must
   **    DISABLE the busy dialog. we should NOT put up any dialogs if
   **    our app is invisible. when our app window is made visible,
   **    then the busy dialog will be enabled.
   */
   OleStdMsgFilter_EnableBusyDialog(lpOleApp->m_lpMsgFilter, FALSE);
#endif  // USE_MSGFILTER

#if defined( OLE_SERVER )
   /* NOTE: perform initialization specific for an OLE server */
   if (! ServerApp_InitInstance((LPSERVERAPP)lpOutlineApp, hInst, nCmdShow))
      goto error;
#endif
#if defined( OLE_CNTR )
   /* NOTE: perform initialization specific for an OLE container */

   // Register help message
   g_uMsgHelp = RegisterWindowMessage(SZOLEUI_MSG_HELP);

   if (! ContainerApp_InitInstance((LPCONTAINERAPP)lpOutlineApp, hInst, nCmdShow))
      goto error;
#endif

#if defined( OLE_CNTR )
   lpOleApp->m_hStdPal = OleStdCreateStandardPalette();
#endif

   OLEDBG_END3
   return TRUE;

error:
   OLEDBG_END3
   return FALSE;
}

/*
 * OleApp_TerminateApplication
 * ---------------------------
 *  Perform proper OLE application cleanup before shutting down
 */
void OleApp_TerminateApplication(LPOLEAPP lpOleApp)
{
   OLEDBG_BEGIN3("OleApp_TerminateApplication\r\n")

   /* NOTE: perform a clean shut down for OLE. at this point our
   **    App refcnt should be 0, or else we should never have reached
   **    this point!
   */
   OleDbgAssertSz(lpOleApp->m_cRef == 0, "App NOT shut down properly");

   if (lpOleApp->m_fOleStdInit)
   {
       OLEDBG_BEGIN2("OleStdUninitialize called\r\n")
       OleStdUninitialize();
       OLEDBG_END2
   }
   if(lpOleApp->m_fOleInitialized) {
      OLEDBG_BEGIN2("OleUninitialize called\r\n")
      OleUninitialize();
      OLEDBG_END2
   }
   OLEDBG_END3
}

/* OleApp_ParseCmdLine
 * -------------------
 *
 * Parse the command line for any execution flags/arguments.
 *      NOTE: check if "-Embedding" switch is given.
 */
BOOL OleApp_ParseCmdLine(LPOLEAPP lpOleApp, LPSTR lpszCmdLine, int nCmdShow)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)lpOleApp;
   char szFileName[256];   /* buffer for filename in command line */
   BOOL fStatus = TRUE;
   BOOL fEmbedding = FALSE;
   OLECHAR szUniStr[256];

   OLEDBG_BEGIN3("OleApp_ParseCmdLine\r\n")

   szFileName[0] = '\0';
   ParseCmdLine(lpszCmdLine, &fEmbedding, (LPSTR)szFileName);

#if defined( MDI_VERSION )
   /* NOTE: an MDI app would ALWAYS register its ClassFactory. it
   **    can handle multiple objects at the same time, while an SDI
   **    application can only handle a single embedded or file-based
   **    object at a time.
   */
   fStatus = OleApp_RegisterClassFactory(lpOleApp);
#endif

   if(fEmbedding) {

      if (szFileName[0] == '\0') {

         /*****************************************************************
         ** App was launched with /Embedding.
         **    We must register our ClassFactory with OLE, remain hidden
         **    (the app window is initially created not visible), and
         **    wait for OLE to call IClassFactory::CreateInstance
         **    method. We do not automatically create a document as we
         **    do when the app is launched by the user from the
         **    FileManager. We must NOT make our app window visible
         **    until told to do so by our container.
         **
         ** NOTE: Because we are an SDI app, we only register our
         **    ClassFactory if we are launched with the /Embedding
         **    flag WITHOUT a filename. an MDI app would ALWAYS
         **    register its ClassFactory. it can handle multiple
         **    objects at the same time, while an SDI application
         **    can only handle a single embedded or file-based
         **    object at a time.
         *****************************************************************/

#if defined( SDI_VERSION )
         fStatus = OleApp_RegisterClassFactory(lpOleApp);
#endif
      } else {

         /*****************************************************************
         ** App was launched with /Embedding <Filename>.
         **    We must create a document and load the file and
         **    register it in the RunningObjectTable BEFORE we
         **    enter our GetMessage loop (ie. before we yield).
         **    One way to perform these tasks is to call the same
         **    interface methods that OLE 2.0 calls for linking to a
         **    file:
         **          IClassFactory::CreateInstance
         **          IPersistFile::Load
         **
         **    We must NOT make our app window visible until told to
         **    do so by our container. An application will be
         **    launched in this manner by an OLE 1.0 application
         **    link situation (eg. double clicking a linked object
         **    or OleCreateLinkFromFile called).
         **
         ** NOTE: Because we are an SDI app, we should NOT
         **    register our ClassFactory when we are launched with the
         **    /Embedding <Filename> flag. our SDI instance can only
         **    handle a single embedded or file-based object.
         **    an MDI app WOULD register its ClassFactory at all
         **    times because it can handle multiple objects.
         *****************************************************************/

         // allocate a new document object
         lpOutlineApp->m_lpDoc = OutlineApp_CreateDoc(lpOutlineApp, FALSE);
         if (! lpOutlineApp->m_lpDoc) {
            OLEDBG_END3
            return FALSE;
         }

         /* NOTE: initially the Doc object is created with a 0 ref
         **    count. in order to have a stable Doc object during the
         **    process of initializing the new Doc instance,
         **    we intially AddRef the Doc ref cnt and later
         **    Release it. This initial AddRef is artificial; it is simply
         **    done to guarantee that a harmless QueryInterface followed by
         **    a Release does not inadvertantly force our object to destroy
         **    itself prematurely.
         */
         OleDoc_AddRef((LPOLEDOC)lpOutlineApp->m_lpDoc);

         /* NOTE: OutlineDoc_LoadFromFile will register our document
         **    in the RunningObjectTable. this registration will
         **    AddRef our document. therefore our document will not
         **    be destroyed when we release the artificial AddRef
         */
         A2W (szFileName, szUniStr, 256);
         fStatus = OutlineDoc_LoadFromFile(
               lpOutlineApp->m_lpDoc, /*(LPOLESTR)*/szUniStr);

         OleDoc_Release((LPOLEDOC)lpOutlineApp->m_lpDoc); // rel AddRef

         OLEDBG_END3
         return fStatus;
      }
   } else {

      /*****************************************************************
      ** App was launched by the user (without /Embedding) and
      **    therefore is marked to be under user control.
      **    In this case, because we are an SDI app, we do NOT
      **    register our ClassFactory with OLE. This app instance can
      **    only manage one document at a time (either a user
      **    document or an embedded object document). An MDI app
      **    would register its ClassFactory here.
      **
      **    We must create a document for the user (either
      **    initialized from a file given on the command line or
      **    initialized as an untitled document. We must also make
      **    our app window visible to the user.
      *****************************************************************/

      // allocate a new document object
      lpOutlineApp->m_lpDoc = OutlineApp_CreateDoc(lpOutlineApp, FALSE);
      if (! lpOutlineApp->m_lpDoc) goto error;

      /* NOTE: initially the Doc object is created with a 0 ref
      **    count. in order to have a stable Doc object during the
      **    process of initializing the new Doc instance,
      **    we intially AddRef the Doc ref cnt and later
      **    Release it. This initial AddRef is artificial; it is simply
      **    done to guarantee that a harmless QueryInterface followed by
      **    a Release does not inadvertantly force our object to destroy
      **    itself prematurely.
      */
      OleDoc_AddRef((LPOLEDOC)lpOutlineApp->m_lpDoc);

      if(*szFileName) {
         // initialize the document from the specified file
         A2W (szFileName, szUniStr, 256);
         if (! OutlineDoc_LoadFromFile(lpOutlineApp->m_lpDoc, szUniStr))
            goto error;
      } else {
         // set the doc to an (Untitled) doc.
         if (! OutlineDoc_InitNewFile(lpOutlineApp->m_lpDoc))
            goto error;
      }

      // position and size the new doc window
      OutlineApp_ResizeWindows(lpOutlineApp);
      OutlineDoc_ShowWindow(lpOutlineApp->m_lpDoc); // calls OleDoc_Lock
      OleDoc_Release((LPOLEDOC)lpOutlineApp->m_lpDoc);// rel AddRef above

      // show main app window
      ShowWindow(lpOutlineApp->m_hWndApp, nCmdShow);
      UpdateWindow(lpOutlineApp->m_hWndApp);

#if defined( OLE_CNTR )
      ContainerDoc_UpdateLinks((LPCONTAINERDOC)lpOutlineApp->m_lpDoc);
#endif

   }

   OLEDBG_END3
   return fStatus;

error:
   // REVIEW: should load string from string resource
   OutlineApp_ErrorMessage(
         lpOutlineApp,
         OLESTR("Could not create document--Out of Memory")
   );
   if (lpOutlineApp->m_lpDoc)      // rel artificial AddRef above
      OleDoc_Release((LPOLEDOC)lpOutlineApp->m_lpDoc);

   OLEDBG_END3
   return FALSE;
}

/* OleApp_CloseAllDocsAndExitCommand
 * ---------------------------------
 *
 *  Close all active documents and exit the app.
 *  Because this is an SDI, there is only one document
 *  If the doc was modified, prompt the user if he wants to save it.
 *
 *  Returns:
 *      TRUE if the app is successfully closed
 *      FALSE if failed or aborted
 *
 * NOTE: in the OLE version, we can NOT directly
 *     destroy the App object. we can only take all
 *     necessary actions to ensure that our object receives
 *     all of its Releases from clients holding onto
 *     pointers (eg. closing all docs and flushing the
 *     clipboard) and then we must hide our window and wait
 *     actually for our refcnt to reach 0. when it reaches 0,
 *     our destructor (OutlineApp_Destroy) will be called.
 *     each document addref's the app object in order to
 *     guarentee that the app does not shut down while the doc
 *     is still open. closing all docs, will release these
 *     refcnt's. if there are now more open documents AND the
 *     app is not under the control of the user (ie. launched by
 *     OLE) then the app will now shut down. the OleApp_Release
 *     function executes this shut down procedure. after closing
 *     all docs, then releasing the user refcnt will force the
 *     app to shut down.
 */
BOOL OleApp_CloseAllDocsAndExitCommand(
      LPOLEAPP            lpOleApp,
      BOOL                fForceEndSession
)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)lpOleApp;
   DWORD dwSaveOption = (fForceEndSession ?
                           OLECLOSE_NOSAVE : OLECLOSE_PROMPTSAVE);

   /* NOTE: in order to have a stable App object during the
   **    process of closing, we intially AddRef the App ref cnt and
   **    later Release it. This initial AddRef is artificial; it is
   **    simply done to guarantee that our App object does not
   **    destroy itself until the end of this routine.
   */
   OleApp_AddRef(lpOleApp);

   /* Because this is an SDI app, there is only one document.
   ** Close the doc. if it is successfully closed and the app will
   ** not automatically exit, then also exit the app.
   ** if this were an MDI app, we would loop through and close all
   ** open MDI child documents.
   */

#if defined( OLE_SERVER )
   if (!fForceEndSession &&
         lpOutlineApp->m_lpDoc->m_docInitType == DOCTYPE_EMBEDDED)
      dwSaveOption = OLECLOSE_SAVEIFDIRTY;
#endif

   if (! OutlineDoc_Close(lpOutlineApp->m_lpDoc, dwSaveOption)) {
      OleApp_Release(lpOleApp);
      return FALSE;     // User Aborted shutdown
   }
#if defined( _DEBUG )
   OleDbgAssertSz(
         lpOutlineApp->m_lpDoc==NULL,
         "Closed doc NOT properly destroyed"
   );
#endif

#if defined( OLE_CNTR )
   /* if we currently have data on the clipboard then we must tell
   **    the clipboard to release our clipboard data object
   **    (document)
   */
   if (lpOutlineApp->m_lpClipboardDoc)
      OleApp_FlushClipboard(lpOleApp);
#endif

   OleApp_HideWindow(lpOleApp);

   /* NOTE: this call forces all external connections to our
   **    object to close down and therefore guarantees that we receive
   **    all releases associated with those external connections.
   */
   OLEDBG_BEGIN2("CoDisconnectObject(lpApp) called\r\n")
   CoDisconnectObject((LPUNKNOWN)&lpOleApp->m_Unknown, 0);
   OLEDBG_END2

   OleApp_Release(lpOleApp);       // release artificial AddRef above

   return TRUE;
}

/* OleApp_ShowWindow
 * -----------------
 *
 *      Show the window of the app to the user.
 *      make sure app window is visible and bring the app to the top.
 *      IF fGiveUserCtrl == TRUE
 *          THEN give the user the control over the life-time of the app.
 */
void OleApp_ShowWindow(LPOLEAPP lpOleApp, BOOL fGiveUserCtrl)
{
   LPOUTLINEAPP    lpOutlineApp = (LPOUTLINEAPP)lpOleApp;

   OLEDBG_BEGIN3("OleApp_ShowWindow\r\n")

   /* NOTE: while the application is visible and under user
   **    control, we do NOT want it to be prematurely destroyed when
   **    the user closes a document. thus we must inform OLE to hold
   **    an external lock on our application on behalf of the user.
   **    this arranges that OLE holds at least 1 reference to our
   **    application that will NOT be released until we release this
   **    external lock. later, when the application window is hidden, we
   **    will release this external lock.
   */
   if (fGiveUserCtrl && ! lpOleApp->m_fUserCtrl) {
      lpOleApp->m_fUserCtrl = TRUE;
      OleApp_Lock(lpOleApp, TRUE /* fLock */, 0 /* not applicable */);
   }

   // we must show our App window and force it to have input focus
   ShowWindow(lpOutlineApp->m_hWndApp, SW_SHOWNORMAL);
   SetForegroundWindow(lpOutlineApp->m_hWndApp);

   /* NOTE: because our app is now visible, we can enable the busy
   **    dialog. we should NOT put up any dialogs if our app is
   **    invisible.
   */
   OleApp_EnableBusyDialogs(lpOleApp, TRUE, TRUE);

   OLEDBG_END3
}

/* OleApp_HideWindow
 * -----------------
 *
 *      Hide the window of the app from the user.
 *      take away the control of the app by the user.
 */
void OleApp_HideWindow(LPOLEAPP lpOleApp)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)lpOleApp;

   OLEDBG_BEGIN3("OleApp_HideWindow\r\n")

   /* NOTE: the application is now being hidden, so we must release
   **    the external lock that was made on behalf of the user.
   **    if this is that last external lock on our application, thus
   **    enabling our application to complete its shutdown operation.
   */
   if (lpOleApp->m_fUserCtrl) {
      lpOleApp->m_fUserCtrl = FALSE;
      OleApp_Lock(lpOleApp, FALSE /*fLock*/, TRUE /*fLastUnlockReleases*/);
   }

   ShowWindow(lpOutlineApp->m_hWndApp, SW_HIDE);

   /* NOTE: because our app is now INVISIBLE, we must DISABLE the busy
   **    dialog. we should NOT put up any dialogs if our app is
   **    invisible.
   */
   OleApp_EnableBusyDialogs(lpOleApp, FALSE, FALSE);
   OLEDBG_END3
}

/* OleApp_Lock
** -----------
**    Lock/Unlock the App object. if the last lock is unlocked and
**    fLastUnlockReleases == TRUE, then the app object will shut down
**    (ie. it will recieve its final release and its refcnt will go to 0).
*/
HRESULT OleApp_Lock(LPOLEAPP lpOleApp, BOOL fLock, BOOL fLastUnlockReleases)
{
   HRESULT hrErr;

#if defined( _DEBUG )
   if (fLock) {
      OLEDBG_BEGIN2("CoLockObjectExternal(lpApp,TRUE) called\r\n")
   } else {
      if (fLastUnlockReleases)
         OLEDBG_BEGIN2("CoLockObjectExternal(lpApp,FALSE,TRUE) called\r\n")
      else
         OLEDBG_BEGIN2("CoLockObjectExternal(lpApp,FALSE,FALSE) called\r\n")
   }
#endif  // _DEBUG

   OleApp_AddRef(lpOleApp);       // artificial AddRef to make object stable

   hrErr = CoLockObjectExternal(
         (LPUNKNOWN)&lpOleApp->m_Unknown, fLock, fLastUnlockReleases);

   OleApp_Release(lpOleApp);       // release artificial AddRef above

   OLEDBG_END2
   return hrErr;
}

/* OleApp_Destroy
 * --------------
 *
 *  Free all OLE related resources that had been allocated for the app.
 */
void OleApp_Destroy(LPOLEAPP lpOleApp)
{
   // NOTE: Revoke our message filter upon app shutdown.
   OleApp_RevokeMessageFilter(lpOleApp);

   // NOTE: Revoke our ClassFactory upon app shutdown.
   OleApp_RevokeClassFactory(lpOleApp);

#if defined( IF_SPECIAL_DD_CURSORS_NEEDED )
   // This would be used if the app wanted to have custom drag/drop cursors
   DestroyCursor(lpOleApp->m_hcursorDragNone);
   DestroyCursor(lpOleApp->m_hcursorDragCopy);
   DestroyCursor(lpOleApp->m_hcursorDragLink);
   DestroyCursor(lpOleApp->m_hcursorDragMove);
#endif  // IF_SPECIAL_DD_CURSORS_NEEDED

#if defined( OLE_CNTR )
   if (lpOleApp->m_hStdPal) {
      DeleteObject(lpOleApp->m_hStdPal);
      lpOleApp->m_hStdPal = NULL;
   }
#endif
}

/* OleApp_DocLockApp
** -----------------
**    Add a lock on the App on behalf of the Doc. the App may not close
**    while the Doc exists.
**
**    when a document is first created, it calls this method to
**    guarantee that the application stays alive (OleDoc_Init).
**    when a document is destroyed, it calls
**    OleApp_DocUnlockApp to release this hold on the app.
*/
void OleApp_DocLockApp(LPOLEAPP lpOleApp)
{
   ULONG cDoc;

   OLEDBG_BEGIN3("OleApp_DocLockApp\r\n")

   cDoc = ++lpOleApp->m_cDoc;

   OleDbgOutRefCnt3("OleApp_DocLockApp: cDoc++\r\n", lpOleApp, cDoc);

   OleApp_Lock(lpOleApp, TRUE /* fLock */, 0 /* not applicable */);

   OLEDBG_END3
   return;
}

/* OleApp_DocUnlockApp
** -------------------
**    Forget all references to a closed document.
**    Release the lock on the App on behalf of the Doc. if this was the
**    last lock on the app, then it will shutdown.
*/
void OleApp_DocUnlockApp(LPOLEAPP lpOleApp, LPOUTLINEDOC lpOutlineDoc)
{
   ULONG cDoc;

   OLEDBG_BEGIN3("OleApp_DocUnlockApp\r\n")

   /* NOTE: when there are no open documents and the app is not
   **    under the control of the user then revoke our ClassFactory to
   **    enable the app to shut down.
   */
   cDoc = --lpOleApp->m_cDoc;

#if defined( _DEBUG )
   OleDbgAssertSz (
         lpOleApp->m_cDoc >= 0, "DocUnlockApp called with cDoc == 0");

   OleDbgOutRefCnt3(
         "OleApp_DocUnlockApp: cDoc--\r\n", lpOleApp, cDoc);
#endif

   OleApp_Lock(lpOleApp, FALSE /* fLock */, TRUE /* fLastUnlockReleases */);

   OLEDBG_END3
   return;
}

/* OleApp_HideIfNoReasonToStayVisible
** ----------------------------------
**
** if there are no more documents visible to the user and the app
**    itself is not under user control, then it has no reason to stay
**    visible. we thus should hide the app. we can not directly destroy
**    the app, because it may be validly being used programatically by
**    another client application and should remain running. the app
**    should simply be hidden from the user.
*/
void OleApp_HideIfNoReasonToStayVisible(LPOLEAPP lpOleApp)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)lpOleApp;
   LPOUTLINEDOC lpOutlineDoc;

   OLEDBG_BEGIN3("OleApp_HideIfNoReasonToStayVisible\r\n")

   if (lpOleApp->m_fUserCtrl) {
      OLEDBG_END3
      return;     // remain visible; user in control of app
   }

   /* Because this is an SDI app, there is only one user document.
   ** check if it is visible to the user. an MDI app would loop over
   ** all open MDI child documents to see if any are visible.
   */
   lpOutlineDoc = (LPOUTLINEDOC)lpOutlineApp->m_lpDoc;
   if (lpOutlineDoc && IsWindowVisible(lpOutlineDoc->m_hWndDoc))
      return;     // remain visible; the doc is visible to the user

   // if we reached here, the app should be hidden
   OleApp_HideWindow(lpOleApp);

   OLEDBG_END3
}

/* OleApp_AddRef
** -------------
**
**  increment the ref count of the App object.
**
**    Returns the new ref count on the object
*/
ULONG OleApp_AddRef(LPOLEAPP lpOleApp)
{
   ++lpOleApp->m_cRef;

#if defined( _DEBUG )
   OleDbgOutRefCnt4(
         "OleApp_AddRef: cRef++\r\n",
         lpOleApp,
         lpOleApp->m_cRef
   );
#endif
   return lpOleApp->m_cRef;
}

/* OleApp_Release
** --------------
**
**  decrement the ref count of the App object.
**    if the ref count goes to 0, then the app object is destroyed.
**
**    Returns the remaining ref count on the object
*/
ULONG OleApp_Release (LPOLEAPP lpOleApp)
{
   ULONG cRef;

   cRef = --lpOleApp->m_cRef;

#if defined( _DEBUG )
   OleDbgAssertSz (lpOleApp->m_cRef >= 0, "Release called with cRef == 0");

   OleDbgOutRefCnt4(
         "OleApp_Release: cRef--\r\n", lpOleApp, cRef);

#endif  // _DEBUG
   /*********************************************************************
   ** NOTE: when the ClassFactory refcnt == 0, then destroy it.    **
   **    otherwise the ClassFactory is still in use.                   **
   *********************************************************************/

   if(cRef == 0)
      OutlineApp_Destroy((LPOUTLINEAPP)lpOleApp);

   return cRef;
}

/* OleApp_QueryInterface
** ---------------------
**
** Retrieve a pointer to an interface on the app object.
**
**    NOTE: this function will AddRef the ref cnt of the object.
**
**    Returns NOERROR if interface is successfully retrieved.
**            E_NOINTERFACE if the interface is not supported
*/
HRESULT OleApp_QueryInterface (
      LPOLEAPP                lpOleApp,
      REFIID                  riid,
      LPVOID FAR*             lplpvObj
)
{
   SCODE sc;

   /* NOTE: we must make sure to set all out ptr parameters to NULL. */
   *lplpvObj = NULL;

   if (IsEqualIID(riid, &IID_IUnknown)) {
      OleDbgOut4("OleApp_QueryInterface: IUnknown* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpOleApp->m_Unknown;
      OleApp_AddRef(lpOleApp);
      sc = S_OK;
   }
   else {
      sc = E_NOINTERFACE;
   }

   OleDbgQueryInterfaceMethod(*lplpvObj);
   return sc;
}

/* OleApp_RejectInComingCalls
** -------------------------
**    Reject/Handle in coming OLE (LRPC) calls.
**
**    NOTE: if the app is in a state when it can NOT handle in
**    coming OLE method calls from an external process (eg. the app has
**    an application modal dialog up), then it should call
**    OleApp_RejectInComingCalls(TRUE). in this state the
**    IMessageFilter::HandleInComingCall method will return
**    SERVERCALL_RETRYLATER. this tells the caller to try again in a
**    little while. normally the calling app will put up a dialog (see
**    OleUIBusy dialog) in this situation informing the user of the
**    situation. the user then is normally given the option to
**    "Switch To..." the busy application, retry, or cancel the
**    operation. when the app is ready to continue processing such
**    calls, it should call OleApp_RejectInComingCalls(FALSE). in this
**    state, SERVERCALL_ISHANDLED is returned by
**    IMessageFilter::HandleInComingCall.
*/
void OleApp_RejectInComingCalls(LPOLEAPP lpOleApp, BOOL fReject)
{
#if defined( _DEBUG )
   if (fReject)
      OleDbgOut3("OleApp_RejectInComingCalls(TRUE)\r\n");
   else
      OleDbgOut3("OleApp_RejectInComingCalls(FALSE)\r\n");
#endif  // _DEBUG

   OleDbgAssert(lpOleApp->m_lpMsgFilter != NULL);
   if (! lpOleApp->m_lpMsgFilter)
      return;

   OleStdMsgFilter_SetInComingCallStatus(
         lpOleApp->m_lpMsgFilter,
         (fReject ? SERVERCALL_RETRYLATER : SERVERCALL_ISHANDLED)
   );
}

/* OleApp_DisableBusyDialogs
** -------------------------
**    Disable the Busy and NotResponding dialogs.
**
**    Returns previous enable state so that it can be restored by
**    calling OleApp_ReEnableBusyDialogs.
*/
void OleApp_DisableBusyDialogs(
      LPOLEAPP        lpOleApp,
      BOOL FAR*       lpfPrevBusyEnable,
      BOOL FAR*       lpfPrevNREnable
)
{
   if (lpOleApp->m_lpMsgFilter) {
      *lpfPrevNREnable = OleStdMsgFilter_EnableNotRespondingDialog(
            lpOleApp->m_lpMsgFilter, FALSE);
      *lpfPrevBusyEnable = OleStdMsgFilter_EnableBusyDialog(
            lpOleApp->m_lpMsgFilter, FALSE);
   }
}

/* OleApp_EnableBusyDialogs
** ------------------------
**    Set the enable state of the Busy and NotResponding dialogs.
**
**    This function is typically used after a call to
**    OleApp_DisableBusyDialogs in order to restore the previous enable
**    state of the dialogs.
*/
void OleApp_EnableBusyDialogs(
      LPOLEAPP        lpOleApp,
      BOOL            fPrevBusyEnable,
      BOOL            fPrevNREnable
)
{
   if (lpOleApp->m_lpMsgFilter) {
      OleStdMsgFilter_EnableNotRespondingDialog(
            lpOleApp->m_lpMsgFilter, fPrevNREnable);
      OleStdMsgFilter_EnableBusyDialog(
            lpOleApp->m_lpMsgFilter, fPrevBusyEnable);
   }
}

/* OleApp_PreModalDialog
** ---------------------
**    Keep track that a modal dialog is about to be brought up.
**
**    while a modal dialog is up we need to take special actions:
**    1. we do NOT want to initialize our tool bar buttons on
**    WM_ACTIVATEAPP. the tool bar is not accessible.
**    2. we want to reject new top-level, incoming LRPC calls
**       (return SERVERCALL_RETRYLATER from IMessageFilter::
**        HandleInComingCall).
**    3. (IN-PLACE SERVER) tell our in-place container to disable
**    modeless dialogs by calling IOleInPlaceFrame::
**    EnableModeless(FALSE).
**    4. (IN-PLACE CONTAINER) tell our UIActive in-place object to
**    disable modeless dialogs by calling IOleInPlaceActiveObject::
**    EnableModeless(FALSE).
*/
void OleApp_PreModalDialog(LPOLEAPP lpOleApp, LPOLEDOC lpOleDoc)
{
   if (lpOleApp->m_cModalDlgActive == 0) {
      // top-level modal dialog is being brought up

#if defined( USE_FRAMETOOLS )
      LPFRAMETOOLS lptb;

      if (lpOleDoc)
         lptb = ((LPOUTLINEDOC)lpOleDoc)->m_lpFrameTools;
      else
         lptb = OutlineApp_GetFrameTools((LPOUTLINEAPP)lpOleApp);
      if (lptb)
         FrameTools_EnableWindow(lptb, FALSE);
#endif  // USE_FRAMETOOLS

      OleApp_RejectInComingCalls(lpOleApp, TRUE);

#if defined( INPLACE_SVR )
      {
         LPSERVERDOC  lpServerDoc = (LPSERVERDOC)lpOleDoc;

         /* if the document bringing up the modal dialog is
         **    currently a UIActive in-place object, then tell the
         **    top-level in-place frame to disable its modeless
         **    dialogs.
         */
         if (lpServerDoc && lpServerDoc->m_fUIActive &&
               lpServerDoc->m_lpIPData &&
               lpServerDoc->m_lpIPData->lpFrame) {
            OLEDBG_BEGIN2("IOleInPlaceFrame::EnableModless(FALSE) called\r\n");
            lpServerDoc->m_lpIPData->lpFrame->lpVtbl->EnableModeless(
                  lpServerDoc->m_lpIPData->lpFrame, FALSE);
            OLEDBG_END2
         }
      }
#endif  // INPLACE_SVR
#if defined( INPLACE_CNTR )
      {
         LPCONTAINERAPP lpContainerApp = (LPCONTAINERAPP)lpOleApp;

         /* if the document bringing up the modal dialog is an
         **    in-place container that has a UIActive object, then
         **    tell the UIActive object to disable its modeless
         **    dialogs.
         */
         if (lpContainerApp->m_lpIPActiveObj) {
            OLEDBG_BEGIN2("IOleInPlaceActiveObject::EnableModless(FALSE) called\r\n");
            lpContainerApp->m_lpIPActiveObj->lpVtbl->EnableModeless(
                  lpContainerApp->m_lpIPActiveObj, FALSE);
            OLEDBG_END2
         }
      }
#endif  // INPLACE_CNTR
   }

   lpOleApp->m_cModalDlgActive++;
}

/* OleApp_PostModalDialog
** ----------------------
**    Keep track that a modal dialog is being brought down. this call
**    balances the OleApp_PreModalDialog call.
*/
void OleApp_PostModalDialog(LPOLEAPP lpOleApp, LPOLEDOC lpOleDoc)
{
   lpOleApp->m_cModalDlgActive--;

   if (lpOleApp->m_cModalDlgActive == 0) {
      // last modal dialog is being brought down

#if defined( USE_FRAMETOOLS )
      LPFRAMETOOLS lptb;

      if (lpOleDoc)
         lptb = ((LPOUTLINEDOC)lpOleDoc)->m_lpFrameTools;
      else
         lptb = OutlineApp_GetFrameTools((LPOUTLINEAPP)lpOleApp);
      if (lptb) {
         FrameTools_EnableWindow(lptb, TRUE);
         FrameTools_UpdateButtons(lptb, (LPOUTLINEDOC)lpOleDoc);
      }
#endif  // USE_FRAMETOOLS

      OleApp_RejectInComingCalls(lpOleApp, FALSE);

#if defined( INPLACE_SVR )
      {
         LPSERVERDOC  lpServerDoc = (LPSERVERDOC)lpOleDoc;

         /* if the document bringing down the modal dialog is
         **    currently a UIActive in-place object, then tell the
         **    top-level in-place frame it can re-enable its
         **    modeless dialogs.
         */
         if (lpServerDoc && lpServerDoc->m_fUIActive &&
               lpServerDoc->m_lpIPData &&
               lpServerDoc->m_lpIPData->lpFrame) {
            OLEDBG_BEGIN2("IOleInPlaceFrame::EnableModless(TRUE) called\r\n");
            lpServerDoc->m_lpIPData->lpFrame->lpVtbl->EnableModeless(
                  lpServerDoc->m_lpIPData->lpFrame, TRUE);
            OLEDBG_END2
         }
      }
#endif  // INPLACE_SVR
#if defined( INPLACE_CNTR )
      {
         LPCONTAINERAPP lpContainerApp = (LPCONTAINERAPP)lpOleApp;

         /* if the document bringing down the modal dialog is an
         **    in-place container that has a UIActive object, then
         **    tell the UIActive object it can re-enable its
         **    modeless dialogs.
         */
         if (lpContainerApp->m_lpIPActiveObj) {
            OLEDBG_BEGIN2("IOleInPlaceActiveObject::EnableModless(TRUE) called\r\n");
            lpContainerApp->m_lpIPActiveObj->lpVtbl->EnableModeless(
                  lpContainerApp->m_lpIPActiveObj, TRUE);
            OLEDBG_END2
         }
      }
#endif  // INPLACE_CNTR
   }
}

/* OleApp_InitVtbls
 * ----------------
 *
 * initialize the methods in all of the interface Vtbl's
 *
 * NOTE: we only need one copy of each Vtbl. When an object which
 *      exposes an interface is instantiated, its lpVtbl is intialized
 *      to point to the single copy of the Vtbl.
 *
 */
BOOL OleApp_InitVtbls (LPOLEAPP lpOleApp)
{
   BOOL fStatus;

   // OleApp::IUnknown method table
   OleStdInitVtbl(&g_OleApp_UnknownVtbl, sizeof(IUnknownVtbl));
   g_OleApp_UnknownVtbl.QueryInterface = OleApp_Unk_QueryInterface;
   g_OleApp_UnknownVtbl.AddRef         = OleApp_Unk_AddRef;
   g_OleApp_UnknownVtbl.Release        = OleApp_Unk_Release;
   fStatus = OleStdCheckVtbl(
         &g_OleApp_UnknownVtbl,
         sizeof(IUnknownVtbl),
         OLESTR("IUnknown")
      );
   if (! fStatus) return FALSE;

   // OleDoc::IUnknown method table
   OleStdInitVtbl(&g_OleDoc_UnknownVtbl, sizeof(IUnknownVtbl));
   g_OleDoc_UnknownVtbl.QueryInterface = OleDoc_Unk_QueryInterface;
   g_OleDoc_UnknownVtbl.AddRef         = OleDoc_Unk_AddRef;
   g_OleDoc_UnknownVtbl.Release        = OleDoc_Unk_Release;
   fStatus = OleStdCheckVtbl(
         &g_OleDoc_UnknownVtbl,
         sizeof(IUnknownVtbl),
         OLESTR("IUnknown")
      );
   if (! fStatus) return FALSE;

   // OleDoc::IPersistFile method table
   OleStdInitVtbl(&g_OleDoc_PersistFileVtbl, sizeof(IPersistFileVtbl));
   g_OleDoc_PersistFileVtbl.QueryInterface = OleDoc_PFile_QueryInterface;
   g_OleDoc_PersistFileVtbl.AddRef         = OleDoc_PFile_AddRef;
   g_OleDoc_PersistFileVtbl.Release        = OleDoc_PFile_Release;
   g_OleDoc_PersistFileVtbl.GetClassID     = OleDoc_PFile_GetClassID;
   g_OleDoc_PersistFileVtbl.IsDirty        = OleDoc_PFile_IsDirty;
   g_OleDoc_PersistFileVtbl.Load           = OleDoc_PFile_Load;
   g_OleDoc_PersistFileVtbl.Save           = OleDoc_PFile_Save;
   g_OleDoc_PersistFileVtbl.SaveCompleted  = OleDoc_PFile_SaveCompleted;
   g_OleDoc_PersistFileVtbl.GetCurFile     = OleDoc_PFile_GetCurFile;
   fStatus = OleStdCheckVtbl(
         &g_OleDoc_PersistFileVtbl,
         sizeof(IPersistFileVtbl),
         OLESTR("IPersistFile")
      );
   if (! fStatus) return FALSE;

   // OleDoc::IOleItemContainer method table
   OleStdInitVtbl(&g_OleDoc_OleItemContainerVtbl, sizeof(IOleItemContainerVtbl));
   g_OleDoc_OleItemContainerVtbl.QueryInterface =
                                 OleDoc_ItemCont_QueryInterface;
   g_OleDoc_OleItemContainerVtbl.AddRef    = OleDoc_ItemCont_AddRef;
   g_OleDoc_OleItemContainerVtbl.Release   = OleDoc_ItemCont_Release;
   g_OleDoc_OleItemContainerVtbl.ParseDisplayName  =
                                 OleDoc_ItemCont_ParseDisplayName;
   g_OleDoc_OleItemContainerVtbl.EnumObjects= OleDoc_ItemCont_EnumObjects;
   g_OleDoc_OleItemContainerVtbl.LockContainer =
                                 OleDoc_ItemCont_LockContainer;
   g_OleDoc_OleItemContainerVtbl.GetObject = OleDoc_ItemCont_GetObject;
   g_OleDoc_OleItemContainerVtbl.GetObjectStorage =
                                 OleDoc_ItemCont_GetObjectStorage;
   g_OleDoc_OleItemContainerVtbl.IsRunning = OleDoc_ItemCont_IsRunning;
   fStatus = OleStdCheckVtbl(
         &g_OleDoc_OleItemContainerVtbl,
         sizeof(IOleItemContainerVtbl),
         OLESTR("IOleItemContainer")
      );
   if (! fStatus) return FALSE;

   // OleDoc::IExternalConnection method table
   OleStdInitVtbl(
         &g_OleDoc_ExternalConnectionVtbl,sizeof(IExternalConnectionVtbl));
   g_OleDoc_ExternalConnectionVtbl.QueryInterface =
                                 OleDoc_ExtConn_QueryInterface;
   g_OleDoc_ExternalConnectionVtbl.AddRef         = OleDoc_ExtConn_AddRef;
   g_OleDoc_ExternalConnectionVtbl.Release        = OleDoc_ExtConn_Release;
   g_OleDoc_ExternalConnectionVtbl.AddConnection  =
                                 OleDoc_ExtConn_AddConnection;
   g_OleDoc_ExternalConnectionVtbl.ReleaseConnection =
                                 OleDoc_ExtConn_ReleaseConnection;
   fStatus = OleStdCheckVtbl(
         &g_OleDoc_ExternalConnectionVtbl,
         sizeof(IExternalConnectionVtbl),
         OLESTR("IExternalConnection")
   );
   if (! fStatus) return FALSE;

   // OleDoc::IDataObject method table
   OleStdInitVtbl(&g_OleDoc_DataObjectVtbl, sizeof(IDataObjectVtbl));
   g_OleDoc_DataObjectVtbl.QueryInterface  = OleDoc_DataObj_QueryInterface;
   g_OleDoc_DataObjectVtbl.AddRef          = OleDoc_DataObj_AddRef;
   g_OleDoc_DataObjectVtbl.Release         = OleDoc_DataObj_Release;
   g_OleDoc_DataObjectVtbl.GetData         = OleDoc_DataObj_GetData;
   g_OleDoc_DataObjectVtbl.GetDataHere     = OleDoc_DataObj_GetDataHere;
   g_OleDoc_DataObjectVtbl.QueryGetData    = OleDoc_DataObj_QueryGetData;
   g_OleDoc_DataObjectVtbl.GetCanonicalFormatEtc =
                              OleDoc_DataObj_GetCanonicalFormatEtc;
   g_OleDoc_DataObjectVtbl.SetData         = OleDoc_DataObj_SetData;
   g_OleDoc_DataObjectVtbl.EnumFormatEtc   = OleDoc_DataObj_EnumFormatEtc;
   g_OleDoc_DataObjectVtbl.DAdvise          = OleDoc_DataObj_DAdvise;
   g_OleDoc_DataObjectVtbl.DUnadvise        = OleDoc_DataObj_DUnadvise;
   g_OleDoc_DataObjectVtbl.EnumDAdvise      = OleDoc_DataObj_EnumDAdvise;

   fStatus = OleStdCheckVtbl(
         &g_OleDoc_DataObjectVtbl,
         sizeof(IDataObjectVtbl),
         OLESTR("IDataObject")
      );
   if (! fStatus) return FALSE;

#if defined( USE_DRAGDROP )

   // OleDoc::IDropTarget method table
   OleStdInitVtbl(&g_OleDoc_DropTargetVtbl, sizeof(IDropTargetVtbl));
   g_OleDoc_DropTargetVtbl.QueryInterface= OleDoc_DropTarget_QueryInterface;
   g_OleDoc_DropTargetVtbl.AddRef      = OleDoc_DropTarget_AddRef;
   g_OleDoc_DropTargetVtbl.Release     = OleDoc_DropTarget_Release;

   g_OleDoc_DropTargetVtbl.DragEnter   = OleDoc_DropTarget_DragEnter;
   g_OleDoc_DropTargetVtbl.DragOver    = OleDoc_DropTarget_DragOver;
   g_OleDoc_DropTargetVtbl.DragLeave   = OleDoc_DropTarget_DragLeave;
   g_OleDoc_DropTargetVtbl.Drop        = OleDoc_DropTarget_Drop;

   fStatus = OleStdCheckVtbl(
         &g_OleDoc_DropTargetVtbl,
         sizeof(IDropTargetVtbl),
         OLESTR("IDropTarget")
   );
   if (! fStatus)
      return FALSE;

   // OleDoc::IDropSource method table
   OleStdInitVtbl(&g_OleDoc_DropSourceVtbl, sizeof(IDropSourceVtbl));
   g_OleDoc_DropSourceVtbl.QueryInterface  =
                              OleDoc_DropSource_QueryInterface;
   g_OleDoc_DropSourceVtbl.AddRef          = OleDoc_DropSource_AddRef;
   g_OleDoc_DropSourceVtbl.Release         = OleDoc_DropSource_Release;

   g_OleDoc_DropSourceVtbl.QueryContinueDrag =
                              OleDoc_DropSource_QueryContinueDrag;
   g_OleDoc_DropSourceVtbl.GiveFeedback    = OleDoc_DropSource_GiveFeedback;

   fStatus = OleStdCheckVtbl(
         &g_OleDoc_DropSourceVtbl,
         sizeof(IDropSourceVtbl),
         OLESTR("IDropSource")
   );
   if (! fStatus) return FALSE;
#endif  // USE_DRAGDROP

#if defined( OLE_SERVER )

   // Initialize the server specific interface method tables.
   if (! ServerApp_InitVtbls((LPSERVERAPP)lpOleApp))
      return FALSE;
#endif
#if defined( OLE_CNTR )

   // Initialize the container specific interface method tables.
   if (! ContainerApp_InitVtbls((LPCONTAINERAPP)lpOleApp))
      return FALSE;
#endif
   return TRUE;
};

/* OleApp_InitMenu
 * ---------------
 *
 *      Enable or Disable menu items depending on the state of
 * the appliation.
 * The OLE versions of the Outline sample app add a PasteSpecial command.
 * Also, the container version add InsertObject and ObjectVerb menu items.
 */
void OleApp_InitMenu(
      LPOLEAPP                lpOleApp,
      LPOLEDOC                lpOleDoc,
      HMENU                   hMenu
)
{
   BOOL bMsgFilterInstalled = FALSE;
   BOOL bRejecting = FALSE;

   if (!lpOleApp || !hMenu)
      return;

   OLEDBG_BEGIN3("OleApp_InitMenu\r\n")

   /*
   ** Enable/disable menu items for Message Filter
   */
   bMsgFilterInstalled = (lpOleApp->m_lpMsgFilter != NULL);
   bRejecting = bMsgFilterInstalled &&
      OleStdMsgFilter_GetInComingCallStatus(lpOleApp->m_lpMsgFilter) != SERVERCALL_ISHANDLED;

   CheckMenuItem(hMenu,
      IDM_D_INSTALLMSGFILTER,
      bMsgFilterInstalled ? MF_CHECKED : MF_UNCHECKED);

   EnableMenuItem(hMenu,
      IDM_D_REJECTINCOMING,
      bMsgFilterInstalled ? MF_ENABLED : MF_GRAYED);

   CheckMenuItem(hMenu,
      IDM_D_REJECTINCOMING,
      bRejecting ? MF_CHECKED : MF_UNCHECKED);

#if defined( OLE_CNTR )
   {
      LPCONTAINERDOC lpContainerDoc = (LPCONTAINERDOC)lpOleDoc;
      BOOL fShowObject;

      fShowObject = ContainerDoc_GetShowObjectFlag(lpContainerDoc);
      CheckMenuItem(
            hMenu,
            IDM_O_SHOWOBJECT,
            (fShowObject ? MF_CHECKED : MF_UNCHECKED)
      );

#if defined( INPLACE_CNTR ) && defined( _DEBUG )
      CheckMenuItem(
            hMenu,
            IDM_D_INSIDEOUT,
            g_fInsideOutContainer ? MF_CHECKED:MF_UNCHECKED);
#endif  // INPLACE_CNTR && _DEBUG

   }
#endif  // OLE_CNTR

   OLEDBG_END3
}

/* OleApp_UpdateEditMenu
 * ---------------------
 *
 *  Purpose:
 *      Update the Edit menuitems of the App according to the state of
 *      OutlineDoc
 *
 *  Parameter:
 *      lpOutlineDoc        pointer to the document
 *      hMenuEdit           edit menu handle
 */
void OleApp_UpdateEditMenu(
      LPOLEAPP                lpOleApp,
      LPOUTLINEDOC            lpOutlineDoc,
      HMENU                   hMenuEdit
)
{
   int             nFmtEtc;
   UINT            uEnablePaste = MF_GRAYED;
   UINT            uEnablePasteLink = MF_GRAYED;
   LPDATAOBJECT    lpClipboardDataObj;
   LPOLEDOC        lpOleDoc = (LPOLEDOC)lpOutlineDoc;
   HRESULT         hrErr;
   BOOL            fPrevEnable1;
   BOOL            fPrevEnable2;

   if (!lpOleApp || !lpOutlineDoc || !hMenuEdit)
      return;

   if (!OleDoc_GetUpdateEditMenuFlag(lpOleDoc))
      /* NOTE: if the flag is not set, we don't have to update
      **    the edit menu again. This blocks repetitive updating when
      **    the user move the mouse across Edit menu while holding
      **    down the button
      */
      return;

   OLEDBG_BEGIN3("OleApp_InitEditMenu\r\n")

   /* NOTE: we do not want to ever give the busy dialog when we
   **    are trying to put up our menus. eg. even if the source of
   **    data on the clipboard is busy, we do not want put up the busy
   **    dialog. thus we will disable the dialog and at the end
   **    re-enable it.
   */
   OleApp_DisableBusyDialogs(lpOleApp, &fPrevEnable1, &fPrevEnable2);

   // check if there is data on the clipboard that we can paste/paste link

   OLEDBG_BEGIN2("OleGetClipboard called\r\n")
   hrErr = OleGetClipboard((LPDATAOBJECT FAR*)&lpClipboardDataObj);
   OLEDBG_END2

   if (hrErr == NOERROR) {
      nFmtEtc = OleStdGetPriorityClipboardFormat(
            lpClipboardDataObj,
            lpOleApp->m_arrPasteEntries,
            lpOleApp->m_nPasteEntries
      );

      if (nFmtEtc >= 0)
         uEnablePaste = MF_ENABLED;  // there IS a format we like

      OLEDBG_BEGIN2("OleQueryLinkFromData called\r\n")
      hrErr = OleQueryLinkFromData(lpClipboardDataObj);
      OLEDBG_END2

      if(hrErr == NOERROR)
         uEnablePasteLink = MF_ENABLED;

      OleStdRelease((LPUNKNOWN)lpClipboardDataObj);
   }

   EnableMenuItem(hMenuEdit, IDM_E_PASTE, uEnablePaste);
   EnableMenuItem(hMenuEdit, IDM_E_PASTESPECIAL, uEnablePaste);

#if defined( OLE_CNTR )
   if (ContainerDoc_GetNextLink((LPCONTAINERDOC)lpOutlineDoc, NULL))
      EnableMenuItem(hMenuEdit, IDM_E_EDITLINKS, MF_ENABLED);
   else
      EnableMenuItem(hMenuEdit, IDM_E_EDITLINKS, MF_GRAYED);

   {
      LPCONTAINERAPP  lpContainerApp = (LPCONTAINERAPP)lpOleApp;
      HMENU           hMenuVerb = NULL;
      LPOLEOBJECT     lpOleObj = NULL;
      LPCONTAINERLINE lpContainerLine = NULL;
      BOOL            fSelIsOleObject;
      char            szAnsiString[256];

      EnableMenuItem(hMenuEdit, IDM_E_PASTELINK, uEnablePasteLink);

      /* check if selection is a single line that contains an OleObject */

      fSelIsOleObject = ContainerDoc_IsSelAnOleObject(
            (LPCONTAINERDOC)lpOutlineDoc,
            &IID_IOleObject,
            (LPUNKNOWN FAR*)&lpOleObj,
            NULL,    /* we don't need the line index */
            (LPCONTAINERLINE FAR*)&lpContainerLine
      );

      if (hMenuEdit != NULL) {
         if (lpContainerLine)
         {
             W2A(lpContainerLine->m_lpszShortType, szAnsiString, 256);
         }
         /* If the current line is an ContainerLine, add the object
         **    verb sub menu to the Edit menu. if the line is not an
         **    ContainerLine, (lpOleObj==NULL) then disable the
         **    Edit.Object command. this helper API takes care of
         **    building the verb menu as appropriate.
         */
         OleUIAddVerbMenu(
               (LPOLEOBJECT)lpOleObj,
               (lpContainerLine ? szAnsiString:NULL),
               hMenuEdit,
               POS_OBJECT,
               IDM_E_OBJECTVERBMIN,
               0,                     // no uIDVerbMax enforced
               TRUE,                  // Add Convert menu item
               IDM_E_CONVERTVERB,     // ID for Convert menu item
               (HMENU FAR*) &hMenuVerb
         );

#if defined( USE_STATUSBAR_LATER )
         /* setup status messages for the object verb menu */
         if (hMenuVerb) {
            // REVIEW: this string should come from a string resource.
            // REVIEW: this doesn't work for dynamically created menus
            AssignPopupMessage(
                  hMenuVerb,
                  "Open, edit or interact with an object"
            );
         }
#endif  // USE_STATUSBAR_LATER
      }

      if (lpOleObj)
         OleStdRelease((LPUNKNOWN)lpOleObj);
   }

#endif  // OLE_CNTR

   // re-enable the Busy/NotResponding dialogs
   OleApp_EnableBusyDialogs(lpOleApp, fPrevEnable1, fPrevEnable2);

   OleDoc_SetUpdateEditMenuFlag(lpOleDoc, FALSE);

   OLEDBG_END3
}

/* OleApp_RegisterClassFactory
 * ---------------------------
 *
 * Register our app's ClassFactory with OLE.
 *
 */
BOOL OleApp_RegisterClassFactory(LPOLEAPP lpOleApp)
{
   HRESULT hrErr;

   if (lpOleApp->m_lpClassFactory)
      return TRUE;    // already registered

   OLEDBG_BEGIN3("OleApp_RegisterClassFactory\r\n")

   /******************************************************************
   ** An SDI app must register its ClassFactory if it is launched
   **    for embedding (/Embedding command line option specified).
   ** An MDI app must register its ClassFactory in all cases,
   ******************************************************************/

   lpOleApp->m_lpClassFactory = AppClassFactory_Create();
   if (! lpOleApp->m_lpClassFactory) {
      OutlineApp_ErrorMessage(g_lpApp, ErrMsgCreateCF);
      goto error;
   }

   OLEDBG_BEGIN2("CoRegisterClassObject called\r\n")
   hrErr = CoRegisterClassObject(
            &CLSID_APP,
            (LPUNKNOWN)lpOleApp->m_lpClassFactory,
            CLSCTX_LOCAL_SERVER,
            REGCLS_SINGLEUSE,
            &lpOleApp->m_dwRegClassFac
   );
   OLEDBG_END2

   if(hrErr != NOERROR) {
      OleDbgOutHResult("CoRegisterClassObject returned", hrErr);
      OutlineApp_ErrorMessage(g_lpApp, ErrMsgRegCF);
      goto error;
   }

   OLEDBG_END3
   return TRUE;

error:

   if (lpOleApp->m_lpClassFactory) {
      OleStdRelease((LPUNKNOWN)lpOleApp->m_lpClassFactory);
      lpOleApp->m_lpClassFactory = NULL;
   }
   OLEDBG_END3
   return FALSE;
}

/* OleApp_RevokeClassFactory
 * -------------------------
 *
 * Revoke our app's ClassFactory.
 *
 */
void OleApp_RevokeClassFactory(LPOLEAPP lpOleApp)
{
   HRESULT hrErr;

   if (lpOleApp->m_lpClassFactory) {

      OLEDBG_BEGIN2("CoRevokeClassObject called\r\n")
      hrErr = CoRevokeClassObject(lpOleApp->m_dwRegClassFac);
      OLEDBG_END2

#if defined( _DEBUG )
      if (hrErr != NOERROR) {
         OleDbgOutHResult("CoRevokeClassObject returned", hrErr);
      }
#endif

      OleStdVerifyRelease(
            (LPUNKNOWN)lpOleApp->m_lpClassFactory,
            OLESTR("ClassFactory NOT released properly!")
      );
      lpOleApp->m_lpClassFactory = NULL;
   }
}

#if defined( USE_MSGFILTER )

/* OleApp_RegisterMessageFilter
 * ----------------------------
 *  Register our IMessageFilter*. the message filter is used to handle
 *  concurrency. we will use a standard implementation of IMessageFilter
 *  that is included as part of the OLE2UI library.
 */
BOOL OleApp_RegisterMessageFilter(LPOLEAPP lpOleApp)
{
   HRESULT hrErr;
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)lpOleApp;

   if (lpOleApp->m_lpMsgFilter == NULL) {
      // Register our message filter.
      lpOleApp->m_lpfnMsgPending = (MSGPENDINGPROC)MessagePendingProc;
      lpOleApp->m_lpMsgFilter = OleStdMsgFilter_Create(
            g_lpApp->m_hWndApp,
            /*(LPOLESTR)*/APPNAME,
            lpOleApp->m_lpfnMsgPending,
            NULL    /* Busy dialog callback hook function */
      );

      OLEDBG_BEGIN2("CoRegisterMessageFilter called\r\n")
      hrErr = CoRegisterMessageFilter(
               lpOleApp->m_lpMsgFilter,
               NULL    /* don't need previous message filter */
      );
      OLEDBG_END2

      if(hrErr != NOERROR) {
         OutlineApp_ErrorMessage(lpOutlineApp, ErrMsgRegMF);
         return FALSE;
      }
   }
   return TRUE;
}

/* OleApp_RevokeMessageFilter
 * --------------------------
 *  Revoke our IMessageFilter*. the message filter is used to handle
 *  concurrency. we will use a standard implementation of IMessageFilter
 *  that is included as part of the OLE2UI library.
 */
void OleApp_RevokeMessageFilter(LPOLEAPP lpOleApp)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;

   if (lpOleApp->m_lpMsgFilter != NULL) {
      // Revoke our message filter
      OLEDBG_BEGIN2("CoRegisterMessageFilter(NULL) called\r\n")
      CoRegisterMessageFilter(NULL, NULL);
      OLEDBG_END2

      if (lpOleApp->m_lpfnMsgPending) {
         lpOleApp->m_lpfnMsgPending = NULL;
      }

      OleStdVerifyRelease(
            (LPUNKNOWN)lpOleApp->m_lpMsgFilter,
            OLESTR("Release MessageFilter FAILED!")
      );
      lpOleApp->m_lpMsgFilter = NULL;
   }
}

/* MessagePendingProc
 * ------------------
 *
 * Callback function for the IMessageFilter::MessagePending procedure.  This
 * function is called when a message is received by our application while
 * we are waiting for an OLE call to complete.  We are essentially
 * blocked at this point, waiting for a response from the other OLE application.
 * We should not process any messages which might cause another OLE call
 * to become blocked, or any other call which might cause re-entrancy problems.
 *
 * For this application, only process WM_PAINT messages.  A more sophisticated
 * application might allow certain menu messages and menu items to be processed
 * also.
 *
 * RETURNS: TRUE if we processed the message, FALSE if we did not.
 */

BOOL FAR PASCAL EXPORT MessagePendingProc(MSG FAR *lpMsg)
{
   // Our application is only handling WM_PAINT messages when we are blocked
   switch (lpMsg->message) {
      case WM_PAINT:
         OleDbgOut2("WM_PAINT dispatched while blocked\r\n");

         DispatchMessage(lpMsg);
         break;
   }

   return FALSE;   // return PENDINGMSG_WAITDEFPROCESS from MessagePending
}
#endif  // USE_MSGFILTER

/* OleApp_FlushClipboard
 * ---------------------

*
 *  Force the Windows clipboard to release our clipboard DataObject.
 */
void OleApp_FlushClipboard(LPOLEAPP lpOleApp)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)lpOleApp;
   LPOLEDOC lpClipboardDoc = (LPOLEDOC)lpOutlineApp->m_lpClipboardDoc;
   OLEDBG_BEGIN3("OleApp_FlushClipboard\r\n")

   /* NOTE: if for some reason our clipboard data transfer
   **    document is still held on to by an external client, we want
   **    to forceably break all external connections.
   */
   OLEDBG_BEGIN2("CoDisconnectObject called\r\n")
   CoDisconnectObject((LPUNKNOWN)&lpClipboardDoc->m_Unknown, 0);
   OLEDBG_END2

   OLEDBG_BEGIN2("OleFlushClipboard called\r\n")
   OleFlushClipboard();
   OLEDBG_END2

   lpOutlineApp->m_lpClipboardDoc = NULL;

   OLEDBG_END3
}

/* OleApp_NewCommand
 * -----------------
 *
 *  Start a new untitled document (File.New command).
 */
void OleApp_NewCommand(LPOLEAPP lpOleApp)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)lpOleApp;
   LPOUTLINEDOC lpOutlineDoc = lpOutlineApp->m_lpDoc;

   if (! OutlineDoc_Close(lpOutlineDoc, OLECLOSE_PROMPTSAVE))
      return;

   OleDbgAssertSz(lpOutlineApp->m_lpDoc==NULL,"Closed doc NOT properly destroyed");
   lpOutlineApp->m_lpDoc = OutlineApp_CreateDoc(lpOutlineApp, FALSE);
   if (! lpOutlineApp->m_lpDoc) goto error;

   /* NOTE: initially the Doc object is created with a 0 ref
   **    count. in order to have a stable Doc object during the
   **    process of initializing the new Doc instance,
   **    we intially AddRef the Doc ref cnt and later
   **    Release it. This initial AddRef is artificial; it is simply
   **    done to guarantee that a harmless QueryInterface followed by
   **    a Release does not inadvertantly force our object to destroy
   **    itself prematurely.
   */
   OleDoc_AddRef((LPOLEDOC)lpOutlineApp->m_lpDoc);

   // set the doc to an (Untitled) doc.
   if (! OutlineDoc_InitNewFile(lpOutlineApp->m_lpDoc))
      goto error;

   // position and size the new doc window
   OutlineApp_ResizeWindows(lpOutlineApp);
   OutlineDoc_ShowWindow(lpOutlineApp->m_lpDoc); // calls OleDoc_Lock

   OleDoc_Release((LPOLEDOC)lpOutlineApp->m_lpDoc);  // rel artificial AddRef

   return;

error:
   // REVIEW: should load string from string resource
   OutlineApp_ErrorMessage(lpOutlineApp, OLESTR("Could not create new document"));

   if (lpOutlineApp->m_lpDoc) {
      // releasing the artificial AddRef above will destroy the document
      OleDoc_Release((LPOLEDOC)lpOutlineApp->m_lpDoc);
      lpOutlineApp->m_lpDoc = NULL;
   }

   return;
}

/* OleApp_OpenCommand
 * ------------------
 *
 *  Load a document from file (File.Open command).
 */
void OleApp_OpenCommand(LPOLEAPP lpOleApp)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)lpOleApp;
   LPOUTLINEDOC lpOutlineDoc = lpOutlineApp->m_lpDoc;
   OPENFILENAME ofn;
   char szFilter[]=APPFILENAMEFILTER;
   char szFileName[256];
   UINT i;
   DWORD dwSaveOption = OLECLOSE_PROMPTSAVE;
   BOOL fStatus = TRUE;
   OLECHAR szUniStr[256];

   if (! OutlineDoc_CheckSaveChanges(lpOutlineDoc, &dwSaveOption))
      return;           // abort opening new doc

   for(i=0; szFilter[i]; i++)
      if(szFilter[i]=='|') szFilter[i]='\0';

   _fmemset((LPOPENFILENAME)&ofn,0,sizeof(OPENFILENAME));

   szFileName[0]='\0';

   ofn.lStructSize=sizeof(OPENFILENAME);
   ofn.hwndOwner=lpOutlineApp->m_hWndApp;
   ofn.lpstrFilter=(LPSTR)szFilter;
   ofn.lpstrFile=(LPSTR)szFileName;
   ofn.nMaxFile=256;
   ofn.Flags=OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
   ofn.lpstrDefExt=DEFEXTENSION;

   OleApp_PreModalDialog(lpOleApp, (LPOLEDOC)lpOutlineApp->m_lpDoc);

   fStatus = GetOpenFileName((LPOPENFILENAME)&ofn);

   OleApp_PostModalDialog(lpOleApp, (LPOLEDOC)lpOutlineApp->m_lpDoc);

   if(! fStatus)
      return;         // user canceled file open dialog

   OutlineDoc_Close(lpOutlineDoc, OLECLOSE_NOSAVE);
   OleDbgAssertSz(lpOutlineApp->m_lpDoc==NULL,"Closed doc NOT properly destroyed");

   lpOutlineApp->m_lpDoc = OutlineApp_CreateDoc(lpOutlineApp, FALSE);
   if (! lpOutlineApp->m_lpDoc) goto error;

   /* NOTE: initially the Doc object is created with a 0 ref
   **    count. in order to have a stable Doc object during the
   **    process of initializing the new Doc instance,
   **    we intially AddRef the Doc ref cnt and later
   **    Release it. This initial AddRef is artificial; it is simply
   **    done to guarantee that a harmless QueryInterface followed by
   **    a Release does not inadvertantly force our object to destroy
   **    itself prematurely.
   */
   OleDoc_AddRef((LPOLEDOC)lpOutlineApp->m_lpDoc);

   A2W (szFileName, szUniStr, 256);
   fStatus=OutlineDoc_LoadFromFile(lpOutlineApp->m_lpDoc, /*(LPOLESTR)*/szUniStr);

   if (! fStatus) {
      // loading the doc failed; create an untitled instead

      // releasing the artificial AddRef above will destroy the document
      OleDoc_Release((LPOLEDOC)lpOutlineApp->m_lpDoc);

      lpOutlineApp->m_lpDoc = OutlineApp_CreateDoc(lpOutlineApp, FALSE);
      if (! lpOutlineApp->m_lpDoc) goto error;
      OleDoc_AddRef((LPOLEDOC)lpOutlineApp->m_lpDoc);

      if (! OutlineDoc_InitNewFile(lpOutlineApp->m_lpDoc))
         goto error;
   }

   // position and size the new doc window
   OutlineApp_ResizeWindows(lpOutlineApp);
   OutlineDoc_ShowWindow(lpOutlineApp->m_lpDoc);

#if defined( OLE_CNTR )
   UpdateWindow(lpOutlineApp->m_hWndApp);
   ContainerDoc_UpdateLinks((LPCONTAINERDOC)lpOutlineApp->m_lpDoc);
#endif

   OleDoc_Release((LPOLEDOC)lpOutlineApp->m_lpDoc);  // rel artificial AddRef

   return;

error:
   // REVIEW: should load string from string resource
   OutlineApp_ErrorMessage(lpOutlineApp, OLESTR("Could not create new document"));

   if (lpOutlineApp->m_lpDoc) {
      // releasing the artificial AddRef above will destroy the document
      OleDoc_Release((LPOLEDOC)lpOutlineApp->m_lpDoc);
      lpOutlineApp->m_lpDoc = NULL;
   }

   return;
}

#if defined( OLE_CNTR )

/* NOTE: forward the WM_QUERYNEWPALETTE message (via
**    SendMessage) to UIActive in-place object if there is one.
**    this gives the UIActive object the opportunity to select
**    and realize its color palette as the FOREGROUND palette.
**    this is optional for in-place containers. if a container
**    prefers to force its color palette as the foreground
**    palette then it should NOT forward the this message. or
**    the container can give the UIActive object priority; if
**    the UIActive object returns 0 from the WM_QUERYNEWPALETTE
**    message (ie. it did not realize its own palette), then
**    the container can realize its palette.
**    (see ContainerDoc_ForwardPaletteChangedMsg for more info)
**
**    (It is a good idea for containers to use the standard
**    palette even if they do not use colors themselves. this
**    will allow embedded object to get a good distribution of
**    colors when they are being drawn by the container)
**
*/

LRESULT OleApp_QueryNewPalette(LPOLEAPP lpOleApp)
{
#if defined( INPLACE_CNTR )
   LPCONTAINERAPP lpContainerApp = (LPCONTAINERAPP)lpOleApp;

   if (lpContainerApp && lpContainerApp->m_hWndUIActiveObj) {
      if (SendMessage(lpContainerApp->m_hWndUIActiveObj, WM_QUERYNEWPALETTE,
            (WPARAM)0, (LPARAM)0)) {
         /* Object selected its palette as foreground palette */
         return (LRESULT)1;
      }
   }
#endif  // INPLACE_CNTR

   return wSelectPalette(((LPOUTLINEAPP)lpOleApp)->m_hWndApp,
      lpOleApp->m_hStdPal, FALSE/*fBackground*/);
}

#endif // OLE_CNTR

/* This is just a helper routine */

LRESULT wSelectPalette(HWND hWnd, HPALETTE hPal, BOOL fBackground)
{
   HDC hdc;
   HPALETTE hOldPal;
   UINT iPalChg = 0;

   if (hPal == 0)
      return (LRESULT)0;

   hdc = GetDC(hWnd);
   hOldPal = SelectPalette(hdc, hPal, fBackground);
   iPalChg = RealizePalette(hdc);
   SelectPalette(hdc, hOldPal, TRUE /*fBackground*/);
   ReleaseDC(hWnd, hdc);

   if (iPalChg > 0)
      InvalidateRect(hWnd, NULL, TRUE);

   return (LRESULT)1;
}

/*************************************************************************
** OleApp::IUnknown interface implementation
*************************************************************************/

STDMETHODIMP OleApp_Unk_QueryInterface(
      LPUNKNOWN           lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
)
{
   LPOLEAPP lpOleApp = ((struct CAppUnknownImpl FAR*)lpThis)->lpOleApp;

   return OleApp_QueryInterface(lpOleApp, riid, lplpvObj);
}

STDMETHODIMP_(ULONG) OleApp_Unk_AddRef(LPUNKNOWN lpThis)
{
   LPOLEAPP lpOleApp = ((struct CAppUnknownImpl FAR*)lpThis)->lpOleApp;

   OleDbgAddRefMethod(lpThis, "IUnknown");

   return OleApp_AddRef(lpOleApp);
}

STDMETHODIMP_(ULONG) OleApp_Unk_Release (LPUNKNOWN lpThis)
{
   LPOLEAPP lpOleApp = ((struct CAppUnknownImpl FAR*)lpThis)->lpOleApp;

   OleDbgReleaseMethod(lpThis, "IUnknown");

   return OleApp_Release(lpOleApp);
}

#if defined( OLE_SERVER )

/*************************************************************************
** ServerDoc Supprt Functions Used by Server versions
*************************************************************************/

/* ServerApp_InitInstance
 * ----------------------
 *
 * Initialize the app instance by creating the main frame window and
 * performing app instance specific initializations
 *  (eg. initializing interface Vtbls).
 *
 * RETURNS: TRUE if the memory could be allocated, and the server app
 *               was properly initialized.
 *          FALSE otherwise
 *
 */

BOOL ServerApp_InitInstance(
      LPSERVERAPP             lpServerApp,
      HINSTANCE               hInst,
      int                     nCmdShow
)
{
   LPOLEAPP lpOleApp = (LPOLEAPP)lpServerApp;
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)lpServerApp;

   /* Setup arrays used by IDataObject::EnumFormatEtc.
   **
   ** NOTE: The order that the formats are listed for GetData is very
   **    significant. It should be listed in order of highest fidelity
   **    formats to least fidelity formats. A common ordering will be:
   **                  1. private app formats
   **                  2. EmbedSource
   **                  3. lower fidelity interchange formats
   **                  4. pictures (metafile, dib, etc.)
   **                      (graphic-related apps offer pictures 1st!)
   **                  5. LinkSource
   */

   /* m_arrDocGetFmts array enumerates the formats that a ServerDoc
   **    DataTransferDoc object can offer (give) through a
   **    IDataObject::GetData call. a ServerDoc DataTransferDoc offers
   **    data formats in the following order:
   **                  1. CF_OUTLINE
   **                  2. CF_EMBEDSOURCE
   **                  3. CF_OBJECTDESCRIPTOR
   **                  4. CF_TEXT
   **                  5. CF_METAFILEPICT
   **                  6. CF_LINKSOURCE *
   **                  7. CF_LINKSRCDESCRIPTOR *
   **
   **    * NOTE: CF_LINKSOURCE and CF_LINKSRCDESCRIPTOR is only
   **    offered if the doc is able to give
   **    a Moniker which references the data. CF_LINKSOURCE is
   **    deliberately listed last in this array of possible formats.
   **    if the doc does not have a Moniker then the last element of
   **    this array is not used. (see SvrDoc_DataObj_EnumFormatEtc).
   **
   **    NOTE: The list of formats that a USER ServerDoc document can
   **    offer is a static list and is registered in the registration
   **    database for the SVROUTL class. The
   **    IDataObject::EnumFormatEtc method returns OLE_S_USEREG in the
   **    case the document is a user docuemt (ie. created via
   **    File.New, File.Open, InsertObject in a container, or
   **    IPersistFile::Load during binding a link source). this tells
   **    OLE to enumerate the formats automatically using the data the
   **    the REGDB.
   */

   lpOleApp->m_arrDocGetFmts[0].cfFormat   = lpOutlineApp->m_cfOutline;
   lpOleApp->m_arrDocGetFmts[0].ptd        = NULL;
   lpOleApp->m_arrDocGetFmts[0].dwAspect   = DVASPECT_CONTENT;
   lpOleApp->m_arrDocGetFmts[0].tymed      = TYMED_HGLOBAL;
   lpOleApp->m_arrDocGetFmts[0].lindex     = -1;

   lpOleApp->m_arrDocGetFmts[1].cfFormat   = lpOleApp->m_cfEmbedSource;
   lpOleApp->m_arrDocGetFmts[1].ptd        = NULL;
   lpOleApp->m_arrDocGetFmts[1].dwAspect   = DVASPECT_CONTENT;
   lpOleApp->m_arrDocGetFmts[1].tymed      = TYMED_ISTORAGE;
   lpOleApp->m_arrDocGetFmts[1].lindex     = -1;

   lpOleApp->m_arrDocGetFmts[2].cfFormat   = CF_TEXT;
   lpOleApp->m_arrDocGetFmts[2].ptd        = NULL;
   lpOleApp->m_arrDocGetFmts[2].dwAspect   = DVASPECT_CONTENT;
   lpOleApp->m_arrDocGetFmts[2].tymed      = TYMED_HGLOBAL;
   lpOleApp->m_arrDocGetFmts[2].lindex     = -1;

   lpOleApp->m_arrDocGetFmts[3].cfFormat   = CF_METAFILEPICT;
   lpOleApp->m_arrDocGetFmts[3].ptd        = NULL;
   lpOleApp->m_arrDocGetFmts[3].dwAspect   = DVASPECT_CONTENT;
   lpOleApp->m_arrDocGetFmts[3].tymed      = TYMED_MFPICT;
   lpOleApp->m_arrDocGetFmts[3].lindex     = -1;

   lpOleApp->m_arrDocGetFmts[4].cfFormat   = lpOleApp->m_cfObjectDescriptor;
   lpOleApp->m_arrDocGetFmts[4].ptd        = NULL;
   lpOleApp->m_arrDocGetFmts[4].dwAspect   = DVASPECT_CONTENT;
   lpOleApp->m_arrDocGetFmts[4].tymed      = TYMED_HGLOBAL;
   lpOleApp->m_arrDocGetFmts[4].lindex     = -1;

   lpOleApp->m_arrDocGetFmts[5].cfFormat   = lpOleApp->m_cfLinkSource;
   lpOleApp->m_arrDocGetFmts[5].ptd        = NULL;
   lpOleApp->m_arrDocGetFmts[5].dwAspect   = DVASPECT_CONTENT;
   lpOleApp->m_arrDocGetFmts[5].tymed      = TYMED_ISTREAM;
   lpOleApp->m_arrDocGetFmts[5].lindex     = -1;

   lpOleApp->m_arrDocGetFmts[6].cfFormat   = lpOleApp->m_cfLinkSrcDescriptor;
   lpOleApp->m_arrDocGetFmts[6].ptd        = NULL;
   lpOleApp->m_arrDocGetFmts[6].dwAspect   = DVASPECT_CONTENT;
   lpOleApp->m_arrDocGetFmts[6].tymed      = TYMED_HGLOBAL;
   lpOleApp->m_arrDocGetFmts[6].lindex     = -1;

   lpOleApp->m_nDocGetFmts = 7;

   /* m_arrPasteEntries array enumerates the formats that a ServerDoc
   **    object can accept (get) from the clipboard.
   **    The formats are listed in priority order.
   **    ServerDoc accept data formats in the following order:
   **                  1. CF_OUTLINE
   **                  2. CF_TEXT
   */
   // REVIEW: strings should be loaded from string resource
   lpOleApp->m_arrPasteEntries[0].fmtetc.cfFormat =lpOutlineApp->m_cfOutline;
   lpOleApp->m_arrPasteEntries[0].fmtetc.ptd      = NULL;
   lpOleApp->m_arrPasteEntries[0].fmtetc.dwAspect = DVASPECT_CONTENT;
   lpOleApp->m_arrPasteEntries[0].fmtetc.tymed    = TYMED_HGLOBAL;
   lpOleApp->m_arrPasteEntries[0].fmtetc.lindex   = -1;
   lpOleApp->m_arrPasteEntries[0].lpstrFormatName = "Outline Data";
   lpOleApp->m_arrPasteEntries[0].lpstrResultText = "Outline Data";
   lpOleApp->m_arrPasteEntries[0].dwFlags         = OLEUIPASTE_PASTEONLY;

   lpOleApp->m_arrPasteEntries[1].fmtetc.cfFormat = CF_TEXT;
   lpOleApp->m_arrPasteEntries[1].fmtetc.ptd      = NULL;
   lpOleApp->m_arrPasteEntries[1].fmtetc.dwAspect = DVASPECT_CONTENT;
   lpOleApp->m_arrPasteEntries[1].fmtetc.tymed    = TYMED_HGLOBAL;
   lpOleApp->m_arrPasteEntries[1].fmtetc.lindex   = -1;
   lpOleApp->m_arrPasteEntries[1].lpstrFormatName = "Text";
   lpOleApp->m_arrPasteEntries[1].lpstrResultText = "text";
   lpOleApp->m_arrPasteEntries[1].dwFlags         = OLEUIPASTE_PASTEONLY;

   lpOleApp->m_nPasteEntries = 2;

   /**    m_arrLinkTypes array enumerates the link types that a ServerDoc
   **    object can accept from the clipboard. ServerDoc does NOT
   **    accept any type of link from the clipboard. ServerDoc can
   **    only be the source of a link. it can not contain links.
   */

   lpOleApp->m_nLinkTypes = 0;

#if defined( INPLACE_SVR )

   lpServerApp->m_hAccelBaseApp = NULL;
   lpServerApp->m_hAccelIPSvr = LoadAccelerators(
         hInst,
         "InPlaceSvrOutlAccel"
   );

   lpServerApp->m_lpIPData = NULL;

   lpServerApp->m_hMenuEdit = GetSubMenu (
         lpOutlineApp->m_hMenuApp,
         POS_EDITMENU
   );
   lpServerApp->m_hMenuLine = GetSubMenu (
         lpOutlineApp->m_hMenuApp,
         POS_LINEMENU
   );
   lpServerApp->m_hMenuName = GetSubMenu (
         lpOutlineApp->m_hMenuApp,
         POS_NAMEMENU
   );
   lpServerApp->m_hMenuOptions = GetSubMenu (
         lpOutlineApp->m_hMenuApp,
         POS_OPTIONSMENU
   );
   lpServerApp->m_hMenuDebug = GetSubMenu (
         lpOutlineApp->m_hMenuApp,
         POS_DEBUGMENU
   );
   lpServerApp->m_hMenuHelp = GetSubMenu (
         lpOutlineApp->m_hMenuApp,
         POS_HELPMENU
   );

#endif  // INPLACE_SVR

   return TRUE;
}

/* ServerApp_InitVtbls
 * -------------------
 *
 * initialize the methods in all of the interface Vtbl's
 *
 * NOTE: we only need one copy of each Vtbl. When an object which
 *      exposes an interface is instantiated, its lpVtbl is intialized
 *      to point to the single copy of the Vtbl.
 *
 */
BOOL ServerApp_InitVtbls (LPSERVERAPP lpServerApp)
{
   BOOL fStatus;

   // ServerDoc::IOleObject method table
   OleStdInitVtbl(&g_SvrDoc_OleObjectVtbl, sizeof(IOleObjectVtbl));
   g_SvrDoc_OleObjectVtbl.QueryInterface   = SvrDoc_OleObj_QueryInterface;
   g_SvrDoc_OleObjectVtbl.AddRef           = SvrDoc_OleObj_AddRef;
   g_SvrDoc_OleObjectVtbl.Release          = SvrDoc_OleObj_Release;
   g_SvrDoc_OleObjectVtbl.SetClientSite    = SvrDoc_OleObj_SetClientSite;
   g_SvrDoc_OleObjectVtbl.GetClientSite    = SvrDoc_OleObj_GetClientSite;
   g_SvrDoc_OleObjectVtbl.SetHostNames     = SvrDoc_OleObj_SetHostNames;
   g_SvrDoc_OleObjectVtbl.Close            = SvrDoc_OleObj_Close;
   g_SvrDoc_OleObjectVtbl.SetMoniker       = SvrDoc_OleObj_SetMoniker;
   g_SvrDoc_OleObjectVtbl.GetMoniker       = SvrDoc_OleObj_GetMoniker;
   g_SvrDoc_OleObjectVtbl.InitFromData     = SvrDoc_OleObj_InitFromData;
   g_SvrDoc_OleObjectVtbl.GetClipboardData = SvrDoc_OleObj_GetClipboardData;
   g_SvrDoc_OleObjectVtbl.DoVerb           = SvrDoc_OleObj_DoVerb;
   g_SvrDoc_OleObjectVtbl.EnumVerbs        = SvrDoc_OleObj_EnumVerbs;
   g_SvrDoc_OleObjectVtbl.Update           = SvrDoc_OleObj_Update;
   g_SvrDoc_OleObjectVtbl.IsUpToDate       = SvrDoc_OleObj_IsUpToDate;
   g_SvrDoc_OleObjectVtbl.GetUserClassID   = SvrDoc_OleObj_GetUserClassID;
   g_SvrDoc_OleObjectVtbl.GetUserType      = SvrDoc_OleObj_GetUserType;
   g_SvrDoc_OleObjectVtbl.SetExtent        = SvrDoc_OleObj_SetExtent;
   g_SvrDoc_OleObjectVtbl.GetExtent        = SvrDoc_OleObj_GetExtent;
   g_SvrDoc_OleObjectVtbl.Advise           = SvrDoc_OleObj_Advise;
   g_SvrDoc_OleObjectVtbl.Unadvise         = SvrDoc_OleObj_Unadvise;
   g_SvrDoc_OleObjectVtbl.EnumAdvise       = SvrDoc_OleObj_EnumAdvise;
   g_SvrDoc_OleObjectVtbl.GetMiscStatus    = SvrDoc_OleObj_GetMiscStatus;
   g_SvrDoc_OleObjectVtbl.SetColorScheme   = SvrDoc_OleObj_SetColorScheme;
   fStatus = OleStdCheckVtbl(
         &g_SvrDoc_OleObjectVtbl,
         sizeof(IOleObjectVtbl),
         OLESTR("IOleObject")
      );
   if (! fStatus) return FALSE;

   // ServerDoc::IPersistStorage method table
   OleStdInitVtbl(&g_SvrDoc_PersistStorageVtbl, sizeof(IPersistStorageVtbl));
   g_SvrDoc_PersistStorageVtbl.QueryInterface  = SvrDoc_PStg_QueryInterface;
   g_SvrDoc_PersistStorageVtbl.AddRef          = SvrDoc_PStg_AddRef;
   g_SvrDoc_PersistStorageVtbl.Release         = SvrDoc_PStg_Release;
   g_SvrDoc_PersistStorageVtbl.GetClassID      = SvrDoc_PStg_GetClassID;
   g_SvrDoc_PersistStorageVtbl.IsDirty         = SvrDoc_PStg_IsDirty;
   g_SvrDoc_PersistStorageVtbl.InitNew         = SvrDoc_PStg_InitNew;
   g_SvrDoc_PersistStorageVtbl.Load            = SvrDoc_PStg_Load;
   g_SvrDoc_PersistStorageVtbl.Save            = SvrDoc_PStg_Save;
   g_SvrDoc_PersistStorageVtbl.SaveCompleted   = SvrDoc_PStg_SaveCompleted;
   g_SvrDoc_PersistStorageVtbl.HandsOffStorage = SvrDoc_PStg_HandsOffStorage;
   fStatus = OleStdCheckVtbl(
         &g_SvrDoc_PersistStorageVtbl,
         sizeof(IPersistStorageVtbl),
         OLESTR("IPersistStorage")
      );
   if (! fStatus) return FALSE;

#if defined( SVR_TREATAS )
   // ServerDoc::IStdMarshalInfo method table
   OleStdInitVtbl(
         &g_SvrDoc_StdMarshalInfoVtbl, sizeof(IStdMarshalInfoVtbl));
   g_SvrDoc_StdMarshalInfoVtbl.QueryInterface  =
                                 SvrDoc_StdMshl_QueryInterface;
   g_SvrDoc_StdMarshalInfoVtbl.AddRef          = SvrDoc_StdMshl_AddRef;
   g_SvrDoc_StdMarshalInfoVtbl.Release         = SvrDoc_StdMshl_Release;
   g_SvrDoc_StdMarshalInfoVtbl.GetClassForHandler =
                                 SvrDoc_StdMshl_GetClassForHandler;
   fStatus = OleStdCheckVtbl(
         &g_SvrDoc_StdMarshalInfoVtbl,
         sizeof(IStdMarshalInfoVtbl),
         OLESTR("IStdMarshalInfo")
      );
   if (! fStatus) return FALSE;
#endif  // SVR_TREATAS

#if defined( INPLACE_SVR )
   // ServerDoc::IOleInPlaceObject method table
   OleStdInitVtbl(
      &g_SvrDoc_OleInPlaceObjectVtbl,
      sizeof(IOleInPlaceObjectVtbl)
   );
   g_SvrDoc_OleInPlaceObjectVtbl.QueryInterface
                  = SvrDoc_IPObj_QueryInterface;
   g_SvrDoc_OleInPlaceObjectVtbl.AddRef
                  = SvrDoc_IPObj_AddRef;
   g_SvrDoc_OleInPlaceObjectVtbl.Release
                  = SvrDoc_IPObj_Release;
   g_SvrDoc_OleInPlaceObjectVtbl.GetWindow
                  = SvrDoc_IPObj_GetWindow;
   g_SvrDoc_OleInPlaceObjectVtbl.ContextSensitiveHelp
                  = SvrDoc_IPObj_ContextSensitiveHelp;
   g_SvrDoc_OleInPlaceObjectVtbl.InPlaceDeactivate
                  = SvrDoc_IPObj_InPlaceDeactivate;
   g_SvrDoc_OleInPlaceObjectVtbl.UIDeactivate
                  = SvrDoc_IPObj_UIDeactivate;
   g_SvrDoc_OleInPlaceObjectVtbl.SetObjectRects
                  = SvrDoc_IPObj_SetObjectRects;
   g_SvrDoc_OleInPlaceObjectVtbl.ReactivateAndUndo
                  = SvrDoc_IPObj_ReactivateAndUndo;
   fStatus = OleStdCheckVtbl(
         &g_SvrDoc_OleInPlaceObjectVtbl,
         sizeof(IOleInPlaceObjectVtbl),
         OLESTR("IOleInPlaceObject")
      );
   if (! fStatus) return FALSE;

   // ServerDoc::IOleInPlaceActiveObject method table
   OleStdInitVtbl(
      &g_SvrDoc_OleInPlaceActiveObjectVtbl,
      sizeof(IOleInPlaceActiveObjectVtbl)
   );
   g_SvrDoc_OleInPlaceActiveObjectVtbl.QueryInterface
                  = SvrDoc_IPActiveObj_QueryInterface;
   g_SvrDoc_OleInPlaceActiveObjectVtbl.AddRef
                  = SvrDoc_IPActiveObj_AddRef;
   g_SvrDoc_OleInPlaceActiveObjectVtbl.Release
                  = SvrDoc_IPActiveObj_Release;
   g_SvrDoc_OleInPlaceActiveObjectVtbl.GetWindow
                  = SvrDoc_IPActiveObj_GetWindow;
   g_SvrDoc_OleInPlaceActiveObjectVtbl.ContextSensitiveHelp
                  = SvrDoc_IPActiveObj_ContextSensitiveHelp;
   g_SvrDoc_OleInPlaceActiveObjectVtbl.TranslateAccelerator
                  = SvrDoc_IPActiveObj_TranslateAccelerator;
   g_SvrDoc_OleInPlaceActiveObjectVtbl.OnFrameWindowActivate
                  = SvrDoc_IPActiveObj_OnFrameWindowActivate;
   g_SvrDoc_OleInPlaceActiveObjectVtbl.OnDocWindowActivate
                  = SvrDoc_IPActiveObj_OnDocWindowActivate;
   g_SvrDoc_OleInPlaceActiveObjectVtbl.ResizeBorder
                  = SvrDoc_IPActiveObj_ResizeBorder;
   g_SvrDoc_OleInPlaceActiveObjectVtbl.EnableModeless
                  = SvrDoc_IPActiveObj_EnableModeless;
   fStatus = OleStdCheckVtbl(
         &g_SvrDoc_OleInPlaceActiveObjectVtbl,
         sizeof(IOleInPlaceActiveObjectVtbl),
         OLESTR("IOleInPlaceActiveObject")
      );
   if (! fStatus) return FALSE;

#endif

   // PseudoObj::IUnknown method table
   OleStdInitVtbl(&g_PseudoObj_UnknownVtbl, sizeof(IUnknownVtbl));
   g_PseudoObj_UnknownVtbl.QueryInterface  = PseudoObj_Unk_QueryInterface;
   g_PseudoObj_UnknownVtbl.AddRef          = PseudoObj_Unk_AddRef;
   g_PseudoObj_UnknownVtbl.Release         = PseudoObj_Unk_Release;
   fStatus = OleStdCheckVtbl(
         &g_PseudoObj_UnknownVtbl,
         sizeof(IUnknownVtbl),
         OLESTR("IUnknown")
      );
   if (! fStatus) return FALSE;

   // PseudoObj::IOleObject method table
   OleStdInitVtbl(&g_PseudoObj_OleObjectVtbl, sizeof(IOleObjectVtbl));
   g_PseudoObj_OleObjectVtbl.QueryInterface= PseudoObj_OleObj_QueryInterface;
   g_PseudoObj_OleObjectVtbl.AddRef        = PseudoObj_OleObj_AddRef;
   g_PseudoObj_OleObjectVtbl.Release       = PseudoObj_OleObj_Release;
   g_PseudoObj_OleObjectVtbl.SetClientSite = PseudoObj_OleObj_SetClientSite;
   g_PseudoObj_OleObjectVtbl.GetClientSite = PseudoObj_OleObj_GetClientSite;
   g_PseudoObj_OleObjectVtbl.SetHostNames  = PseudoObj_OleObj_SetHostNames;
   g_PseudoObj_OleObjectVtbl.Close         = PseudoObj_OleObj_Close;
   g_PseudoObj_OleObjectVtbl.SetMoniker    = PseudoObj_OleObj_SetMoniker;
   g_PseudoObj_OleObjectVtbl.GetMoniker    = PseudoObj_OleObj_GetMoniker;
   g_PseudoObj_OleObjectVtbl.InitFromData  = PseudoObj_OleObj_InitFromData;
   g_PseudoObj_OleObjectVtbl.GetClipboardData =
                                 PseudoObj_OleObj_GetClipboardData;
   g_PseudoObj_OleObjectVtbl.DoVerb        = PseudoObj_OleObj_DoVerb;
   g_PseudoObj_OleObjectVtbl.EnumVerbs     = PseudoObj_OleObj_EnumVerbs;
   g_PseudoObj_OleObjectVtbl.Update        = PseudoObj_OleObj_Update;
   g_PseudoObj_OleObjectVtbl.IsUpToDate    = PseudoObj_OleObj_IsUpToDate;
   g_PseudoObj_OleObjectVtbl.GetUserType   = PseudoObj_OleObj_GetUserType;
   g_PseudoObj_OleObjectVtbl.GetUserClassID= PseudoObj_OleObj_GetUserClassID;
   g_PseudoObj_OleObjectVtbl.SetExtent     = PseudoObj_OleObj_SetExtent;
   g_PseudoObj_OleObjectVtbl.GetExtent     = PseudoObj_OleObj_GetExtent;
   g_PseudoObj_OleObjectVtbl.Advise        = PseudoObj_OleObj_Advise;
   g_PseudoObj_OleObjectVtbl.Unadvise      = PseudoObj_OleObj_Unadvise;
   g_PseudoObj_OleObjectVtbl.EnumAdvise    = PseudoObj_OleObj_EnumAdvise;
   g_PseudoObj_OleObjectVtbl.GetMiscStatus = PseudoObj_OleObj_GetMiscStatus;
   g_PseudoObj_OleObjectVtbl.SetColorScheme= PseudoObj_OleObj_SetColorScheme;
   fStatus = OleStdCheckVtbl(
         &g_PseudoObj_OleObjectVtbl,
         sizeof(IOleObjectVtbl),
         OLESTR("IOleObject")
      );
   if (! fStatus) return FALSE;

   // ServerDoc::IDataObject method table
   OleStdInitVtbl(&g_PseudoObj_DataObjectVtbl, sizeof(IDataObjectVtbl));
   g_PseudoObj_DataObjectVtbl.QueryInterface =
                           PseudoObj_DataObj_QueryInterface;
   g_PseudoObj_DataObjectVtbl.AddRef       = PseudoObj_DataObj_AddRef;
   g_PseudoObj_DataObjectVtbl.Release      = PseudoObj_DataObj_Release;
   g_PseudoObj_DataObjectVtbl.GetData      = PseudoObj_DataObj_GetData;
   g_PseudoObj_DataObjectVtbl.GetDataHere  = PseudoObj_DataObj_GetDataHere;
   g_PseudoObj_DataObjectVtbl.QueryGetData = PseudoObj_DataObj_QueryGetData;
   g_PseudoObj_DataObjectVtbl.GetCanonicalFormatEtc =
                           PseudoObj_DataObj_GetCanonicalFormatEtc;
   g_PseudoObj_DataObjectVtbl.SetData      = PseudoObj_DataObj_SetData;
   g_PseudoObj_DataObjectVtbl.EnumFormatEtc= PseudoObj_DataObj_EnumFormatEtc;
   g_PseudoObj_DataObjectVtbl.DAdvise       = PseudoObj_DataObj_DAdvise;
   g_PseudoObj_DataObjectVtbl.DUnadvise     = PseudoObj_DataObj_DUnadvise;
   g_PseudoObj_DataObjectVtbl.EnumDAdvise   = PseudoObj_DataObj_EnumAdvise;

   fStatus = OleStdCheckVtbl(
         &g_PseudoObj_DataObjectVtbl,
         sizeof(IDataObjectVtbl),
         OLESTR("IDataObject")
      );
   if (! fStatus) return FALSE;

   return TRUE;
}

#endif  // OLE_SERVER

#if defined( OLE_CNTR )

/*************************************************************************
** ContainerDoc Supprt Functions Used by Container versions
*************************************************************************/

/* ContainerApp_InitInstance
 * -------------------------
 *
 * Initialize the app instance by creating the main frame window and
 * performing app instance specific initializations
 *  (eg. initializing interface Vtbls).
 *
 * RETURNS: TRUE if the memory could be allocated, and the server app
 *               was properly initialized.
 *          FALSE otherwise
 *
 */

BOOL ContainerApp_InitInstance(
      LPCONTAINERAPP          lpContainerApp,
      HINSTANCE               hInst,
      int                     nCmdShow
)
{
   LPOLEAPP lpOleApp = (LPOLEAPP)lpContainerApp;
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)lpContainerApp;
   char  szAnsiString[256];

   W2A (CONTAINERDOCFORMAT, szAnsiString, 256);
   lpContainerApp->m_cfCntrOutl=RegisterClipboardFormat(szAnsiString);
   if(! lpContainerApp->m_cfCntrOutl) {
      // REVIEW: should load string from string resource
      OutlineApp_ErrorMessage(lpOutlineApp, OLESTR("Can't register clipboard format!"));
      return FALSE;
   }

#if defined( INPLACE_CNTR )

   lpContainerApp->m_fPendingUIDeactivate  = FALSE;
   lpContainerApp->m_fMustResizeClientArea = FALSE;
   lpContainerApp->m_lpIPActiveObj         = NULL;
   lpContainerApp->m_hWndUIActiveObj       = NULL;
   lpContainerApp->m_hAccelIPCntr = LoadAccelerators(
         hInst,
         "InPlaceCntrOutlAccel"
   );
   lpContainerApp->m_hMenuFile = GetSubMenu (
         lpOutlineApp->m_hMenuApp,

POS_FILEMENU
   );
   lpContainerApp->m_hMenuView = GetSubMenu (
         lpOutlineApp->m_hMenuApp,
         POS_VIEWMENU
   );
   lpContainerApp->m_hMenuDebug = GetSubMenu (
         lpOutlineApp->m_hMenuApp,
         POS_DEBUGMENU
   );

   INIT_INTERFACEIMPL(
         &lpContainerApp->m_OleInPlaceFrame,
         &g_CntrApp_OleInPlaceFrameVtbl,
         lpContainerApp
   );

#endif

   /* Setup arrays used by IDataObject::EnumFormatEtc. This is used to
   **    support copy/paste and drag/drop operations.
   **
   ** NOTE: The order that the formats are listed for GetData is very
   **    significant. It should be listed in order of highest fidelity
   **    formats to least fidelity formats. A common ordering will be:
   **                  1. private app formats
   **                  2. CF_EMBEDSOURCE or CF_EMBEDOBJECT (as appropriate)
   **                  3. lower fidelity interchange formats
   **                  4. CF_METAFILEPICT
   **                      (graphic-related apps might offer picture 1st!)
   **                  5. CF_OBJECTDESCRIPTOR
   **                  6. CF_LINKSOURCE
   **                  6. CF_LINKSRCDESCRIPTOR
   */

   /* m_arrDocGetFmts array enumerates the formats that a ContainerDoc
   **    object can offer (give) through a IDataObject::GetData call
   **    when the selection copied is NOT a single embedded object.
   **    when a single embedded object this list of formats available
   **    is built dynamically depending on the object copied. (see
   **    ContainerDoc_SetupDocGetFmts).
   **    The formats are listed in priority order.
   **    ContainerDoc objects accept data formats in the following order:
   **                  1. CF_CNTROUTL
   **                  2. CF_OUTLINE
   **                  3. CF_TEXT
   **                  4. CF_OBJECTDESCRIPTOR
   **
   **    NOTE: CF_OBJECTDESCRIPTOR format is used to describe the
   **    data on the clipboard. this information is intended to be
   **    used, for example, to drive the PasteSpecial dialog. it is
   **    useful to render CF_OBJECTDESCRIPTOR format even when the
   **    data on the clipboard does NOT include CF_EMBEDDEDOBJECT
   **    format or CF_EMBEDSOURCE format as when a selection that is
   **    not a single OLE object is copied from the container only
   **    version CNTROUTL. by rendering CF_OBJECTDESCRIPTOR format the
   **    app can indicate a useful string to identifiy the source of
   **    the copy to the user.
   */

   lpOleApp->m_arrDocGetFmts[0].cfFormat = lpContainerApp->m_cfCntrOutl;
   lpOleApp->m_arrDocGetFmts[0].ptd      = NULL;
   lpOleApp->m_arrDocGetFmts[0].dwAspect = DVASPECT_CONTENT;
   lpOleApp->m_arrDocGetFmts[0].tymed    = TYMED_ISTORAGE;
   lpOleApp->m_arrDocGetFmts[0].lindex   = -1;

   lpOleApp->m_arrDocGetFmts[1].cfFormat = lpOutlineApp->m_cfOutline;
   lpOleApp->m_arrDocGetFmts[1].ptd      = NULL;
   lpOleApp->m_arrDocGetFmts[1].dwAspect = DVASPECT_CONTENT;
   lpOleApp->m_arrDocGetFmts[1].tymed    = TYMED_HGLOBAL;
   lpOleApp->m_arrDocGetFmts[1].lindex   = -1;

   lpOleApp->m_arrDocGetFmts[2].cfFormat = CF_TEXT;
   lpOleApp->m_arrDocGetFmts[2].ptd      = NULL;
   lpOleApp->m_arrDocGetFmts[2].dwAspect = DVASPECT_CONTENT;
   lpOleApp->m_arrDocGetFmts[2].tymed    = TYMED_HGLOBAL;
   lpOleApp->m_arrDocGetFmts[2].lindex   = -1;

   lpOleApp->m_arrDocGetFmts[3].cfFormat = lpOleApp->m_cfObjectDescriptor;
   lpOleApp->m_arrDocGetFmts[3].ptd      = NULL;
   lpOleApp->m_arrDocGetFmts[3].dwAspect = DVASPECT_CONTENT;
   lpOleApp->m_arrDocGetFmts[3].tymed    = TYMED_HGLOBAL;
   lpOleApp->m_arrDocGetFmts[3].lindex   = -1;

   lpOleApp->m_nDocGetFmts = 4;

   /* m_arrSingleObjGetFmts array enumerates the formats that a
   **    ContainerDoc object can offer (give) through a
   **    IDataObject::GetData call when the selection copied IS a
   **    single OLE object.
   **    ContainerDoc objects accept data formats in the following order:
   **                  1. CF_CNTROUTL
   **                  2. CF_EMBEDDEDOBJECT
   **                  3. CF_OBJECTDESCRIPTOR
   **                  4. CF_METAFILEPICT  (note DVASPECT will vary)
   **                  5. CF_LINKSOURCE *
   **                  6. CF_LINKSRCDESCRIPTOR *
   **
   **    * NOTE: CF_LINKSOURCE and CF_LINKSRCDESCRIPTOR is only
   **    offered if the OLE object is allowed to be linked to from the
   **    inside (ie. we are allowed to give out a moniker which binds
   **    to the running OLE object), then we want to offer
   **    CF_LINKSOURCE format. if the object is an OLE 2.0 embedded
   **    object then it is allowed to be linked to from the inside. if
   **    the object is either an OleLink or an OLE 1.0 embedding then
   **    it can not be linked to from the inside. if we were a
   **    container/server app then we could offer linking to the
   **    outside of the object (ie. a pseudo object within our
   **    document). we are a container only app that does not support
   **    linking to ranges of its data.
   **    the simplest way to determine if an object can be linked to
   **    on the inside is to call IOleObject::GetMiscStatus and test
   **    to see if the OLEMISC_CANTLINKINSIDE bit is NOT set.
   **
   **    NOTE: optionally, a container that wants to have a
   **    potentially richer data transfer, can enumerate the data
   **    formats from the OLE object's cache and offer them too. if
   **    the object has a special handler, then it might be able to
   **    render additional data formats.
   */
   lpContainerApp->m_arrSingleObjGetFmts[0].cfFormat =
                                    lpContainerApp->m_cfCntrOutl;
   lpContainerApp->m_arrSingleObjGetFmts[0].ptd      = NULL;
   lpContainerApp->m_arrSingleObjGetFmts[0].dwAspect = DVASPECT_CONTENT;
   lpContainerApp->m_arrSingleObjGetFmts[0].tymed    = TYMED_ISTORAGE;
   lpContainerApp->m_arrSingleObjGetFmts[0].lindex   = -1;

   lpContainerApp->m_arrSingleObjGetFmts[1].cfFormat =
                                    lpOleApp->m_cfEmbeddedObject;
   lpContainerApp->m_arrSingleObjGetFmts[1].ptd      = NULL;
   lpContainerApp->m_arrSingleObjGetFmts[1].dwAspect = DVASPECT_CONTENT;
   lpContainerApp->m_arrSingleObjGetFmts[1].tymed    = TYMED_ISTORAGE;
   lpContainerApp->m_arrSingleObjGetFmts[1].lindex   = -1;

   lpContainerApp->m_arrSingleObjGetFmts[2].cfFormat =
                                    lpOleApp->m_cfObjectDescriptor;
   lpContainerApp->m_arrSingleObjGetFmts[2].ptd      = NULL;
   lpContainerApp->m_arrSingleObjGetFmts[2].dwAspect = DVASPECT_CONTENT;
   lpContainerApp->m_arrSingleObjGetFmts[2].tymed    = TYMED_HGLOBAL;
   lpContainerApp->m_arrSingleObjGetFmts[2].lindex   = -1;

   lpContainerApp->m_arrSingleObjGetFmts[3].cfFormat = CF_METAFILEPICT;
   lpContainerApp->m_arrSingleObjGetFmts[3].ptd      = NULL;
   lpContainerApp->m_arrSingleObjGetFmts[3].dwAspect = DVASPECT_CONTENT;
   lpContainerApp->m_arrSingleObjGetFmts[3].tymed    = TYMED_MFPICT;
   lpContainerApp->m_arrSingleObjGetFmts[3].lindex   = -1;

   lpContainerApp->m_arrSingleObjGetFmts[4].cfFormat =
                                       lpOleApp->m_cfLinkSource;
   lpContainerApp->m_arrSingleObjGetFmts[4].ptd      = NULL;
   lpContainerApp->m_arrSingleObjGetFmts[4].dwAspect = DVASPECT_CONTENT;
   lpContainerApp->m_arrSingleObjGetFmts[4].tymed    = TYMED_ISTREAM;
   lpContainerApp->m_arrSingleObjGetFmts[4].lindex   = -1;

   lpContainerApp->m_arrSingleObjGetFmts[5].cfFormat =
                                   lpOleApp->m_cfLinkSrcDescriptor;
   lpContainerApp->m_arrSingleObjGetFmts[5].ptd      = NULL;
   lpContainerApp->m_arrSingleObjGetFmts[5].dwAspect = DVASPECT_CONTENT;
   lpContainerApp->m_arrSingleObjGetFmts[5].tymed    = TYMED_HGLOBAL;
   lpContainerApp->m_arrSingleObjGetFmts[5].lindex   = -1;

   lpContainerApp->m_nSingleObjGetFmts = 6;

   /* NOTE: the Container-Only version of Outline does NOT offer
   **    IDataObject interface from its User documents and the
   **    IDataObject interface available from DataTransferDoc's do NOT
   **    support SetData. IDataObject interface is required by objects
   **    which can be embedded or linked. the Container-only app only
   **    allows linking to its contained objects, NOT the data of the
   **    container itself.
   */

   /*    m_arrPasteEntries array enumerates the formats that a ContainerDoc
   **    object can accept from the clipboard. this array is used to
   **    support the PasteSpecial dialog.
   **    The formats are listed in priority order.
   **    ContainerDoc objects accept data formats in the following order:
   **                  1. CF_CNTROUTL
   **                  2. CF_OUTLINE
   **                  3. CF_EMBEDDEDOBJECT
   **                  4. CF_TEXT
   **                  5. CF_METAFILEPICT
   **                  6. CF_DIB
   **                  7. CF_BITMAP
   **                  8. CF_LINKSOURCE
   **
   **    NOTE: specifying CF_EMBEDDEDOBJECT in the PasteEntry array
   **    indicates that the caller is interested in pasting OLE
   **    objects (ie. the caller calls OleCreateFromData). the
   **    OleUIPasteSpecial dialog and OleStdGetPriorityClipboardFormat
   **    call OleQueryCreateFromData to see if an OLE object format is
   **    available. thus, in fact if CF_EMBEDSOURCE or CF_FILENAME are
   **    available from the data source then and OLE object can be
   **    created and this entry will be matched. the caller should
   **    only specify one object type format.
   **    CF_FILENAME format (as generated by copying a file to
   **    the clipboard from the FileManager) is considered an object
   **    format; OleCreatFromData creates an object if the file has an
   **    associated class (see GetClassFile API) or if no class it
   **    creates an OLE 1.0 Package object. this format can also be
   **    paste linked by calling OleCreateLinkFromData.
   */
   // REVIEW: strings should be loaded from string resource

   lpOleApp->m_arrPasteEntries[0].fmtetc.cfFormat =
                           lpContainerApp->m_cfCntrOutl;
   lpOleApp->m_arrPasteEntries[0].fmtetc.ptd      = NULL;
   lpOleApp->m_arrPasteEntries[0].fmtetc.dwAspect = DVASPECT_CONTENT;
   lpOleApp->m_arrPasteEntries[0].fmtetc.tymed    = TYMED_ISTORAGE;
   lpOleApp->m_arrPasteEntries[0].fmtetc.lindex   = -1;
   lpOleApp->m_arrPasteEntries[0].lpstrFormatName = "Container Outline Data";
   lpOleApp->m_arrPasteEntries[0].lpstrResultText = "Container Outline Data";
   lpOleApp->m_arrPasteEntries[0].dwFlags         = OLEUIPASTE_PASTEONLY;

   lpOleApp->m_arrPasteEntries[1].fmtetc.cfFormat =lpOutlineApp->m_cfOutline;
   lpOleApp->m_arrPasteEntries[1].fmtetc.ptd      = NULL;
   lpOleApp->m_arrPasteEntries[1].fmtetc.dwAspect = DVASPECT_CONTENT;
   lpOleApp->m_arrPasteEntries[1].fmtetc.tymed    = TYMED_HGLOBAL;
   lpOleApp->m_arrPasteEntries[1].fmtetc.lindex   = -1;
   lpOleApp->m_arrPasteEntries[1].lpstrFormatName = "Outline Data";
   lpOleApp->m_arrPasteEntries[1].lpstrResultText = "Outline Data";
   lpOleApp->m_arrPasteEntries[1].dwFlags         = OLEUIPASTE_PASTEONLY;

   lpOleApp->m_arrPasteEntries[2].fmtetc.cfFormat =
                           lpOleApp->m_cfEmbeddedObject;
   lpOleApp->m_arrPasteEntries[2].fmtetc.ptd      = NULL;
   lpOleApp->m_arrPasteEntries[2].fmtetc.dwAspect = DVASPECT_CONTENT;
   lpOleApp->m_arrPasteEntries[2].fmtetc.tymed    = TYMED_ISTORAGE;
   lpOleApp->m_arrPasteEntries[2].fmtetc.lindex   = -1;
   lpOleApp->m_arrPasteEntries[2].lpstrFormatName = "%s";
   lpOleApp->m_arrPasteEntries[2].lpstrResultText = "%s";
   lpOleApp->m_arrPasteEntries[2].dwFlags         =
                           OLEUIPASTE_PASTE | OLEUIPASTE_ENABLEICON;

   lpOleApp->m_arrPasteEntries[3].fmtetc.cfFormat = CF_TEXT;
   lpOleApp->m_arrPasteEntries[3].fmtetc.ptd      = NULL;
   lpOleApp->m_arrPasteEntries[3].fmtetc.dwAspect = DVASPECT_CONTENT;
   lpOleApp->m_arrPasteEntries[3].fmtetc.tymed    = TYMED_HGLOBAL;
   lpOleApp->m_arrPasteEntries[3].fmtetc.lindex   = -1;
   lpOleApp->m_arrPasteEntries[3].lpstrFormatName = "Text";
   lpOleApp->m_arrPasteEntries[3].lpstrResultText = "text";
   lpOleApp->m_arrPasteEntries[3].dwFlags         = OLEUIPASTE_PASTEONLY;

   lpOleApp->m_arrPasteEntries[4].fmtetc.cfFormat = CF_METAFILEPICT;
   lpOleApp->m_arrPasteEntries[4].fmtetc.ptd      = NULL;
   lpOleApp->m_arrPasteEntries[4].fmtetc.dwAspect = DVASPECT_CONTENT;
   lpOleApp->m_arrPasteEntries[4].fmtetc.tymed    = TYMED_MFPICT;
   lpOleApp->m_arrPasteEntries[4].fmtetc.lindex   = -1;
   lpOleApp->m_arrPasteEntries[4].lpstrFormatName = "Picture (Metafile)";
   lpOleApp->m_arrPasteEntries[4].lpstrResultText = "a static picture";
   lpOleApp->m_arrPasteEntries[4].dwFlags         = OLEUIPASTE_PASTEONLY;

   lpOleApp->m_arrPasteEntries[5].fmtetc.cfFormat = CF_DIB;
   lpOleApp->m_arrPasteEntries[5].fmtetc.ptd      = NULL;
   lpOleApp->m_arrPasteEntries[5].fmtetc.dwAspect = DVASPECT_CONTENT;
   lpOleApp->m_arrPasteEntries[5].fmtetc.tymed    = TYMED_HGLOBAL;
   lpOleApp->m_arrPasteEntries[5].fmtetc.lindex   = -1;
   lpOleApp->m_arrPasteEntries[5].lpstrFormatName = "Picture (DIB)";
   lpOleApp->m_arrPasteEntries[5].lpstrResultText = "a static picture";
   lpOleApp->m_arrPasteEntries[5].dwFlags         = OLEUIPASTE_PASTEONLY;

   lpOleApp->m_arrPasteEntries[6].fmtetc.cfFormat = CF_BITMAP;
   lpOleApp->m_arrPasteEntries[6].fmtetc.ptd      = NULL;
   lpOleApp->m_arrPasteEntries[6].fmtetc.dwAspect = DVASPECT_CONTENT;
   lpOleApp->m_arrPasteEntries[6].fmtetc.tymed    = TYMED_GDI;
   lpOleApp->m_arrPasteEntries[6].fmtetc.lindex   = -1;
   lpOleApp->m_arrPasteEntries[6].lpstrFormatName = "Picture (Bitmap)";
   lpOleApp->m_arrPasteEntries[6].lpstrResultText = "a static picture";
   lpOleApp->m_arrPasteEntries[6].dwFlags         = OLEUIPASTE_PASTEONLY;

   lpOleApp->m_arrPasteEntries[7].fmtetc.cfFormat = lpOleApp->m_cfLinkSource;
   lpOleApp->m_arrPasteEntries[7].fmtetc.ptd      = NULL;
   lpOleApp->m_arrPasteEntries[7].fmtetc.dwAspect = DVASPECT_CONTENT;
   lpOleApp->m_arrPasteEntries[7].fmtetc.tymed    = TYMED_ISTREAM;
   lpOleApp->m_arrPasteEntries[7].fmtetc.lindex   = -1;
   lpOleApp->m_arrPasteEntries[7].lpstrFormatName = "%s";
   lpOleApp->m_arrPasteEntries[7].lpstrResultText = "%s";
   lpOleApp->m_arrPasteEntries[7].dwFlags         =
                        OLEUIPASTE_LINKTYPE1 | OLEUIPASTE_ENABLEICON;

   lpOleApp->m_nPasteEntries = 8;

   /*    m_arrLinkTypes array enumerates the link types that a ContainerDoc
   **    object can accept from the clipboard
   */

   lpOleApp->m_arrLinkTypes[0] = lpOleApp->m_cfLinkSource;
   lpOleApp->m_nLinkTypes = 1;

   return TRUE;
}

/* ContainerApp_InitVtbls
** ----------------------
**
**    initialize the interface Vtbl's used to support the OLE 2.0
**    Container functionality.
*/

BOOL ContainerApp_InitVtbls(LPCONTAINERAPP lpApp)
{
   BOOL fStatus;

   // ContainerDoc::IOleUILinkContainer method table
   OleStdInitVtbl(
         &g_CntrDoc_OleUILinkContainerVtbl,
         sizeof(IOleUILinkContainerVtbl)
   );
   g_CntrDoc_OleUILinkContainerVtbl.QueryInterface =
                                 CntrDoc_LinkCont_QueryInterface;
   g_CntrDoc_OleUILinkContainerVtbl.AddRef    = CntrDoc_LinkCont_AddRef;
   g_CntrDoc_OleUILinkContainerVtbl.Release   = CntrDoc_LinkCont_Release;
   g_CntrDoc_OleUILinkContainerVtbl.GetNextLink =
                              CntrDoc_LinkCont_GetNextLink;
   g_CntrDoc_OleUILinkContainerVtbl.SetLinkUpdateOptions =
                              CntrDoc_LinkCont_SetLinkUpdateOptions;
   g_CntrDoc_OleUILinkContainerVtbl.GetLinkUpdateOptions =
                              CntrDoc_LinkCont_GetLinkUpdateOptions;
   g_CntrDoc_OleUILinkContainerVtbl.SetLinkSource =
                              CntrDoc_LinkCont_SetLinkSource;
   g_CntrDoc_OleUILinkContainerVtbl.GetLinkSource =
                              CntrDoc_LinkCont_GetLinkSource;
   g_CntrDoc_OleUILinkContainerVtbl.OpenLinkSource =
                              CntrDoc_LinkCont_OpenLinkSource;
   g_CntrDoc_OleUILinkContainerVtbl.UpdateLink =
                              CntrDoc_LinkCont_UpdateLink;
   g_CntrDoc_OleUILinkContainerVtbl.CancelLink =
                              CntrDoc_LinkCont_CancelLink;
   fStatus = OleStdCheckVtbl(
         &g_CntrDoc_OleUILinkContainerVtbl,
         sizeof(IOleUILinkContainerVtbl),
         OLESTR("IOleUILinkContainer")
      );
   if (! fStatus) return FALSE;

#if defined( INPLACE_CNTR )

   // ContainerApp::IOleInPlaceFrame interface method table
   OleStdInitVtbl(
         &g_CntrApp_OleInPlaceFrameVtbl,
         sizeof(g_CntrApp_OleInPlaceFrameVtbl)
   );

   g_CntrApp_OleInPlaceFrameVtbl.QueryInterface
                  = CntrApp_IPFrame_QueryInterface;
   g_CntrApp_OleInPlaceFrameVtbl.AddRef
                  = CntrApp_IPFrame_AddRef;
   g_CntrApp_OleInPlaceFrameVtbl.Release
                  = CntrApp_IPFrame_Release;
   g_CntrApp_OleInPlaceFrameVtbl.GetWindow
                  = CntrApp_IPFrame_GetWindow;
   g_CntrApp_OleInPlaceFrameVtbl.ContextSensitiveHelp
                  = CntrApp_IPFrame_ContextSensitiveHelp;

   g_CntrApp_OleInPlaceFrameVtbl.GetBorder
                  = CntrApp_IPFrame_GetBorder;
   g_CntrApp_OleInPlaceFrameVtbl.RequestBorderSpace
                  = CntrApp_IPFrame_RequestBorderSpace;
   g_CntrApp_OleInPlaceFrameVtbl.SetBorderSpace
                  = CntrApp_IPFrame_SetBorderSpace;
   g_CntrApp_OleInPlaceFrameVtbl.SetActiveObject
                  = CntrApp_IPFrame_SetActiveObject;
   g_CntrApp_OleInPlaceFrameVtbl.InsertMenus
                  = CntrApp_IPFrame_InsertMenus;
   g_CntrApp_OleInPlaceFrameVtbl.SetMenu
                  = CntrApp_IPFrame_SetMenu;
   g_CntrApp_OleInPlaceFrameVtbl.RemoveMenus
                  = CntrApp_IPFrame_RemoveMenus;
   g_CntrApp_OleInPlaceFrameVtbl.SetStatusText
                  = CntrApp_IPFrame_SetStatusText;
   g_CntrApp_OleInPlaceFrameVtbl.EnableModeless
                  = CntrApp_IPFrame_EnableModeless;
   g_CntrApp_OleInPlaceFrameVtbl.TranslateAccelerator
                  = CntrApp_IPFrame_TranslateAccelerator;

   fStatus = OleStdCheckVtbl(
         &g_CntrApp_OleInPlaceFrameVtbl,
         sizeof(g_CntrApp_OleInPlaceFrameVtbl),
         OLESTR("IOleInPlaceFrame")
      );
   if (! fStatus) return FALSE;

#endif  // INPLACE_CNTR

   // ContainerLine::IUnknown interface method table
   OleStdInitVtbl(
         &g_CntrLine_UnknownVtbl,
         sizeof(g_CntrLine_UnknownVtbl)
      );
   g_CntrLine_UnknownVtbl.QueryInterface   = CntrLine_Unk_QueryInterface;
   g_CntrLine_UnknownVtbl.AddRef           = CntrLine_Unk_AddRef;
   g_CntrLine_UnknownVtbl.Release          = CntrLine_Unk_Release;
   fStatus = OleStdCheckVtbl(
         &g_CntrLine_UnknownVtbl,
         sizeof(g_CntrLine_UnknownVtbl),
         OLESTR("IUnknown")
      );
   if (! fStatus) return FALSE;

   // ContainerLine::IOleClientSite interface method table
   OleStdInitVtbl(
         &g_CntrLine_OleClientSiteVtbl,
         sizeof(g_CntrLine_OleClientSiteVtbl)
      );
   g_CntrLine_OleClientSiteVtbl.QueryInterface =
                                 CntrLine_CliSite_QueryInterface;
   g_CntrLine_OleClientSiteVtbl.AddRef       = CntrLine_CliSite_AddRef;
   g_CntrLine_OleClientSiteVtbl.Release      = CntrLine_CliSite_Release;
   g_CntrLine_OleClientSiteVtbl.SaveObject   = CntrLine_CliSite_SaveObject;
   g_CntrLine_OleClientSiteVtbl.GetMoniker   = CntrLine_CliSite_GetMoniker;
   g_CntrLine_OleClientSiteVtbl.GetContainer = CntrLine_CliSite_GetContainer;
   g_CntrLine_OleClientSiteVtbl.ShowObject   = CntrLine_CliSite_ShowObject;
   g_CntrLine_OleClientSiteVtbl.OnShowWindow = CntrLine_CliSite_OnShowWindow;
   g_CntrLine_OleClientSiteVtbl.RequestNewObjectLayout =
                           CntrLine_CliSite_RequestNewObjectLayout;
   fStatus = OleStdCheckVtbl(
         &g_CntrLine_OleClientSiteVtbl,
         sizeof(g_CntrLine_OleClientSiteVtbl),
         OLESTR("IOleClientSite")
      );
   if (! fStatus) return FALSE;

   // ContainerLine::IAdviseSink interface method table
   OleStdInitVtbl(
         &g_CntrLine_AdviseSinkVtbl,
         sizeof(g_CntrLine_AdviseSinkVtbl)
   );
   g_CntrLine_AdviseSinkVtbl.QueryInterface= CntrLine_AdvSink_QueryInterface;
   g_CntrLine_AdviseSinkVtbl.AddRef        = CntrLine_AdvSink_AddRef;
   g_CntrLine_AdviseSinkVtbl.Release       = CntrLine_AdvSink_Release;
   g_CntrLine_AdviseSinkVtbl.OnDataChange  = CntrLine_AdvSink_OnDataChange;
   g_CntrLine_AdviseSinkVtbl.OnViewChange  = CntrLine_AdvSink_OnViewChange;
   g_CntrLine_AdviseSinkVtbl.OnRename      = CntrLine_AdvSink_OnRename;
   g_CntrLine_AdviseSinkVtbl.OnSave        = CntrLine_AdvSink_OnSave;
   g_CntrLine_AdviseSinkVtbl.OnClose       = CntrLine_AdvSink_OnClose;
   fStatus = OleStdCheckVtbl(
         &g_CntrLine_AdviseSinkVtbl,
         sizeof(g_CntrLine_AdviseSinkVtbl),
         OLESTR("IAdviseSink")
      );
   if (! fStatus) return FALSE;

#if defined( INPLACE_CNTR )

   // ContainerLine::IOleInPlaceSite interface method table
   OleStdInitVtbl(
         &g_CntrLine_OleInPlaceSiteVtbl,
         sizeof(g_CntrLine_OleInPlaceSiteVtbl)
   );

   g_CntrLine_OleInPlaceSiteVtbl.QueryInterface
                  = CntrLine_IPSite_QueryInterface;
   g_CntrLine_OleInPlaceSiteVtbl.AddRef
                  = CntrLine_IPSite_AddRef;
   g_CntrLine_OleInPlaceSiteVtbl.Release
                  = CntrLine_IPSite_Release;
   g_CntrLine_OleInPlaceSiteVtbl.GetWindow
                  = CntrLine_IPSite_GetWindow;
   g_CntrLine_OleInPlaceSiteVtbl.ContextSensitiveHelp
                  = CntrLine_IPSite_ContextSensitiveHelp;
   g_CntrLine_OleInPlaceSiteVtbl.CanInPlaceActivate
                  = CntrLine_IPSite_CanInPlaceActivate;
   g_CntrLine_OleInPlaceSiteVtbl.OnInPlaceActivate
                  = CntrLine_IPSite_OnInPlaceActivate;
   g_CntrLine_OleInPlaceSiteVtbl.OnUIActivate
                  = CntrLine_IPSite_OnUIActivate;
   g_CntrLine_OleInPlaceSiteVtbl.GetWindowContext
                  = CntrLine_IPSite_GetWindowContext;
   g_CntrLine_OleInPlaceSiteVtbl.Scroll
                  = CntrLine_IPSite_Scroll;
   g_CntrLine_OleInPlaceSiteVtbl.OnUIDeactivate
                  = CntrLine_IPSite_OnUIDeactivate;

   g_CntrLine_OleInPlaceSiteVtbl.OnInPlaceDeactivate
                  = CntrLine_IPSite_OnInPlaceDeactivate;
   g_CntrLine_OleInPlaceSiteVtbl.DiscardUndoState
                  = CntrLine_IPSite_DiscardUndoState;
   g_CntrLine_OleInPlaceSiteVtbl.DeactivateAndUndo
                  = CntrLine_IPSite_DeactivateAndUndo;
   g_CntrLine_OleInPlaceSiteVtbl.OnPosRectChange
                  = CntrLine_IPSite_OnPosRectChange;

   fStatus = OleStdCheckVtbl(
         &g_CntrLine_OleInPlaceSiteVtbl,
         sizeof(g_CntrLine_OleInPlaceSiteVtbl),
         OLESTR("IOleInPlaceSite")
      );
   if (! fStatus) return FALSE;

#endif  // INPLACE_CNTR

   return TRUE;
}

#endif  // OLE_CNTR


OLEDOC.C

/*************************************************************************
**
**    OLE 2 Server Sample Code
**
**    oledoc.c
**
**    This file contains general OleDoc methods and related support
**    functions. OleDoc implementation is used by both the Container
**    versions and the Server (Object) versions of the Outline Sample.
**
**    This file includes general support for the following:
**    1. show/hide doc window
**    2. QueryInterface, AddRef, Release
**    3. document locking (calls CoLockObjectExternal)
**    4. document shutdown (Close, Destroy)
**    5. clipboard support
**
**    OleDoc Object
**      exposed interfaces:
**          IUnknown
**          IPersistFile
**          IOleItemContainer
**          IDataObject
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#include "outline.h"

OLEDBGDATA

extern LPOUTLINEAPP             g_lpApp;

extern IUnknownVtbl             g_OleDoc_UnknownVtbl;
extern IPersistFileVtbl         g_OleDoc_PersistFileVtbl;
extern IOleItemContainerVtbl    g_OleDoc_OleItemContainerVtbl;
extern IExternalConnectionVtbl  g_OleDoc_ExternalConnectionVtbl;
extern IDataObjectVtbl          g_OleDoc_DataObjectVtbl;

#if defined( USE_DRAGDROP )
extern IDropTargetVtbl          g_OleDoc_DropTargetVtbl;
extern IDropSourceVtbl          g_OleDoc_DropSourceVtbl;
#endif  // USE_DRAGDROP

#if defined( INPLACE_CNTR )
extern BOOL g_fInsideOutContainer;
#endif

/* OleDoc_Init
 * -----------
 *
 *  Initialize the fields of a new OleDoc object. The object is initially
 *  not associated with a file or an (Untitled) document. This function sets
 *  the docInitType to DOCTYPE_UNKNOWN. After calling this function the
 *  caller should call:
 *      1.) Doc_InitNewFile to set the OleDoc to (Untitled)
 *      2.) Doc_LoadFromFile to associate the OleDoc with a file.
 *  This function creates a new window for the document.
 *
 *  NOTE: the window is initially created with a NIL size. it must be
 *        sized and positioned by the caller. also the document is initially
 *        created invisible. the caller must call OutlineDoc_ShowWindow
 *        after sizing it to make the document window visible.
 */
BOOL OleDoc_Init(LPOLEDOC lpOleDoc, BOOL fDataTransferDoc)
{
   LPOLEAPP   lpOleApp = (LPOLEAPP)g_lpApp;
   LPLINELIST lpLL     = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;

   lpOleDoc->m_cRef                        = 0;
   lpOleDoc->m_dwStrongExtConn             = 0;
#if defined( _DEBUG )
   lpOleDoc->m_cCntrLock                   = 0;
#endif
   lpOleDoc->m_lpStg                       = NULL;
   lpOleDoc->m_lpLLStm                     = NULL;
   lpOleDoc->m_lpNTStm                     = NULL;
   lpOleDoc->m_dwRegROT                    = 0;
   lpOleDoc->m_lpFileMoniker               = NULL;
   lpOleDoc->m_fLinkSourceAvail            = FALSE;
   lpOleDoc->m_lpSrcDocOfCopy              = NULL;
   lpOleDoc->m_fObjIsClosing               = FALSE;
   lpOleDoc->m_fObjIsDestroying            = FALSE;
   lpOleDoc->m_fUpdateEditMenu             = FALSE;

#if defined( USE_DRAGDROP )
   lpOleDoc->m_dwTimeEnterScrollArea       = 0L;
   lpOleDoc->m_dwNextScrollTime            = 0L;
   lpOleDoc->m_dwLastScrollDir             = SCROLLDIR_NULL;
   lpOleDoc->m_fRegDragDrop                = FALSE;
   lpOleDoc->m_fLocalDrag                  = FALSE;
   lpOleDoc->m_fCanDropCopy                = FALSE;
   lpOleDoc->m_fCanDropLink                = FALSE;
   lpOleDoc->m_fLocalDrop                  = FALSE;
   lpOleDoc->m_fDragLeave                  = FALSE;
   lpOleDoc->m_fPendingDrag                = FALSE;
#endif
#if defined( INPLACE_SVR ) || defined( INPLACE_CNTR )
   lpOleDoc->m_fCSHelpMode                 = FALSE;    // Shift-F1 context
                                          // sensitive help mode
#endif

   INIT_INTERFACEIMPL(
         &lpOleDoc->m_Unknown,
         &g_OleDoc_UnknownVtbl,
         lpOleDoc
   );

   INIT_INTERFACEIMPL(
         &lpOleDoc->m_PersistFile,
         &g_OleDoc_PersistFileVtbl,
         lpOleDoc
   );

   INIT_INTERFACEIMPL(
         &lpOleDoc->m_OleItemContainer,
         &g_OleDoc_OleItemContainerVtbl,
         lpOleDoc
   );

   INIT_INTERFACEIMPL(
         &lpOleDoc->m_ExternalConnection,
         &g_OleDoc_ExternalConnectionVtbl,
         lpOleDoc
   );

   INIT_INTERFACEIMPL(
         &lpOleDoc->m_DataObject,
         &g_OleDoc_DataObjectVtbl,
         lpOleDoc
   );

#if defined( USE_DRAGDROP )
   INIT_INTERFACEIMPL(
         &lpOleDoc->m_DropSource,
         &g_OleDoc_DropSourceVtbl,
         lpOleDoc
   );

   INIT_INTERFACEIMPL(
         &lpOleDoc->m_DropTarget,
         &g_OleDoc_DropTargetVtbl,
         lpOleDoc
   );
#endif  // USE_DRAGDROP

   /*
   ** NOTE: each user level document addref's the app object in
   **    order to guarentee that the app does not shut down while the
   **    doc is still open.
   */

   // NOTE: data transfer documents should not hold the app alive
   if (! fDataTransferDoc)
      OleApp_DocLockApp(lpOleApp);

#if defined( OLE_SERVER )
   /* NOTE: perform initialization specific for an OLE server */
   if (! ServerDoc_Init((LPSERVERDOC)lpOleDoc, fDataTransferDoc))
      return FALSE;
#endif
#if defined( OLE_CNTR )

   /* NOTE: perform initialization specific for an OLE container */
   if (! ContainerDoc_Init((LPCONTAINERDOC)lpOleDoc, fDataTransferDoc))
      return FALSE;
#endif

   return TRUE;
}

/* OleDoc_InitNewFile
 * ------------------
 *
 *  Initialize the document to be a new (Untitled) document.
 *  This function sets the docInitType to DOCTYPE_NEW.
 *
 *  NOTE: if this is a visible user document then generate a unique
 *  untitled name that we can use to register in the RunningObjectTable.
 *  We need a unique name so that clients can link to data in this document
 *  even when the document is in the un-saved (untitled) state. it would be
 *  ambiguous to register two documents titled "Outline1" in the ROT. we
 *  thus generate the lowest numbered document that is not already
 *  registered in the ROT.
 */
BOOL OleDoc_InitNewFile(LPOLEDOC lpOleDoc)
{
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;

   static UINT uUnique = 1;

   OleDbgAssert(lpOutlineDoc->m_docInitType == DOCTYPE_UNKNOWN);

#if defined( OLE_CNTR )
   {
      LPCONTAINERDOC lpContainerDoc = (LPCONTAINERDOC)lpOleDoc;
#if defined( _DEBUG )
      OleDbgAssertSz(lpOleDoc->m_lpStg == NULL,
            "Setting to untitled with current file open"
      );
#endif

      /* Create a temp, (delete-on-release) file base storage
      **  for the untitled document.
      */
      lpOleDoc->m_lpStg = OleStdCreateRootStorage(
            NULL,
            STGM_SHARE_EXCLUSIVE
      );
      if (! lpOleDoc->m_lpStg) return FALSE;
   }
#endif

   lpOutlineDoc->m_docInitType = DOCTYPE_NEW;

   if (! lpOutlineDoc->m_fDataTransferDoc) {
      /* NOTE: choose a unique name for a Moniker so that
      **    potential clients can link to our new, untitled document.
      **    if links are established (and currently are connected),
      **    then they will be notified that we have been renamed when
      **    this document is saved to a file.
      */

      lpOleDoc->m_fLinkSourceAvail = TRUE;

      // REVIEW: should load UNTITLED string from string resource
      OleStdCreateTempFileMoniker(
            UNTITLED,
            (UINT FAR*)&uUnique,
            lpOutlineDoc->m_szFileName,
            &lpOleDoc->m_lpFileMoniker
      );

      OLEDBG_BEGIN3("OleStdRegisterAsRunning called\r\n")
      OleStdRegisterAsRunning(
            (LPUNKNOWN)&lpOleDoc->m_PersistFile,
            (LPMONIKER)lpOleDoc->m_lpFileMoniker,
            &lpOleDoc->m_dwRegROT
      );
      OLEDBG_END3

      lpOutlineDoc->m_lpszDocTitle = lpOutlineDoc->m_szFileName;
      OutlineDoc_SetTitle(lpOutlineDoc, FALSE /*fMakeUpperCase*/);
   } else {
      OLESTRCPY(lpOutlineDoc->m_szFileName, UNTITLED);
      lpOutlineDoc->m_lpszDocTitle = lpOutlineDoc->m_szFileName;
   }

   return TRUE;
}

/* OleDoc_ShowWindow
 * -----------------
 *
 *      Show the window of the document to the user.
 *      make sure app window is visible and bring the document to the top.
 *      if the document is a file-based document or a new untitled
 *      document, give the user the control over the life-time of the doc.
 */
void OleDoc_ShowWindow(LPOLEDOC lpOleDoc)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;
   LPLINELIST lpLL     = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;
#if defined( OLE_SERVER )
   LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOleDoc;
#endif // OLE_SERVER

   OLEDBG_BEGIN3("OleDoc_ShowWindow\r\n")

   /* NOTE: while the document is visible, we do NOT want it to be
   **    prematurely destroyed when a linking client disconnects. thus
   **    we must inform OLE to hold an external lock on our document.
   **    this arranges that OLE holds at least 1 reference to our
   **    document that will NOT be released until we release this
   **    external lock. later, when the document window is hidden, we
   **    will release this external lock.
   */
   if (! IsWindowVisible(lpOutlineDoc->m_hWndDoc))
      OleDoc_Lock(lpOleDoc, TRUE /* fLock */, 0 /* not applicable */);

#if defined( USE_DRAGDROP )
   /* NOTE: since our window is now being made visible, we will
   **    register our window as a potential drop target. when the
   **    window is hidden there is no reason to be registered as a
   **    drop target.
   */
   if (! lpOleDoc->m_fRegDragDrop) {
      OLEDBG_BEGIN2("RegisterDragDrop called\r\n")
      RegisterDragDrop(
            LineList_GetWindow(lpLL),
            (LPDROPTARGET)&lpOleDoc->m_DropTarget
      );
      OLEDBG_END2
      lpOleDoc->m_fRegDragDrop = TRUE;
   }
#endif  // USE_DRAGDROP

#if defined( USE_FRAMETOOLS )
   {
      /* NOTE: we need to enable our frame level tools
      */
      FrameTools_Enable(lpOutlineDoc->m_lpFrameTools, TRUE);
   }
#endif // USE_FRAMETOOLS

#if defined( OLE_SERVER )

   if (lpOutlineDoc->m_docInitType == DOCTYPE_EMBEDDED &&
         lpServerDoc->m_lpOleClientSite != NULL) {

      /* NOTE: we must also ask our container to show itself if
      **    it is not already visible and to scroll us into view. we
      **    must make sure to call this BEFORE showing our server's
      **    window and taking focus. we do not want our container's
      **    window to end up on top.
      */
      OLEDBG_BEGIN2("IOleClientSite::ShowObject called\r\n");
      lpServerDoc->m_lpOleClientSite->lpVtbl->ShowObject(
            lpServerDoc->m_lpOleClientSite
      );
      OLEDBG_END2

      /* NOTE: if we are an embedded object and we are not
      **    in-place active in our containers window, we must inform our
      **    embedding container that our window is opening.
      **    the container must now hatch our object.
      */

#if defined( INPLACE_SVR )
      if (! lpServerDoc->m_fInPlaceActive)
#endif
      {
         OLEDBG_BEGIN2("IOleClientSite::OnShowWindow(TRUE) called\r\n");
         lpServerDoc->m_lpOleClientSite->lpVtbl->OnShowWindow(
               lpServerDoc->m_lpOleClientSite,
               TRUE
         );
         OLEDBG_END2
      }

      /* NOTE: the life-time of our document is controlled by our
      **    client and NOT by the user. we are not an independent
      **    file-level object. we simply want to show our window here.
      **
      **    if we are not in-place active (ie. we are opening
      **    our own window), we must make sure our main app window is
      **    visible. we do not, however, want to give the user
      **    control of the App window; we do not want OleApp_ShowWindow
      **    to call OleApp_Lock on behalf of the user.
      */
      if (! IsWindowVisible(lpOutlineApp->m_hWndApp) ||
            IsIconic(lpOutlineApp->m_hWndApp)) {
#if defined( INPLACE_SVR )
         if (! ((LPSERVERDOC)lpOleDoc)->m_fInPlaceActive)
#endif
            OleApp_ShowWindow(lpOleApp, FALSE /* fGiveUserCtrl */);
         SetFocus(lpOutlineDoc->m_hWndDoc);
      }

   } else
#endif  // OLE_SERVER

   {    // DOCTYPE_NEW || DOCTYPE_FROMFILE

      // we must make sure our app window is visible
      OleApp_ShowWindow(lpOleApp, TRUE /* fGiveUserCtrl */);
   }

   // make document window visible and make sure it is not minimized
   ShowWindow(lpOutlineDoc->m_hWndDoc, SW_SHOWNORMAL);
   SetForegroundWindow(lpOutlineDoc->m_hWndDoc);

   OLEDBG_END3
}

/* OleDoc_HideWindow
 * -----------------
 *
 *      Hide the window of the document from the user.
 *      take away the control of the document by the user.
 */
void OleDoc_HideWindow(LPOLEDOC lpOleDoc, BOOL fShutdown)
{
   LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;
   LPLINELIST lpLL     = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;

   if (! IsWindowVisible(lpOutlineDoc->m_hWndDoc))
      return;     // already visible

   OLEDBG_BEGIN3("OleDoc_HideWindow\r\n")

#if defined( USE_DRAGDROP )
   // The document's window is being hidden, revoke it as a DropTarget
   if (lpOleDoc->m_fRegDragDrop) {
      OLEDBG_BEGIN2("RevokeDragDrop called\r\n");
      RevokeDragDrop(LineList_GetWindow(lpLL));
      OLEDBG_END2

      lpOleDoc->m_fRegDragDrop = FALSE ;
   }
#endif  // USE_DRAGDROP

   /* NOTE: the document is now being hidden, so we must release
   **    the external lock made when the document was made visible.
   **    if this is a shutdown situation (fShutdown==TRUE), then OLE
   **    is instructed to release our document. if this is that last
   **    external lock on our document, thus enabling our document to
   **    complete its shutdown operation. If This is not a shutdown
   **    situation (eg. in-place server hiding its window when
   **    UIDeactivating or IOleObject::DoVerb(OLEVERB_HIDE) is called),
   **    then OLE is told to NOT immediately release the document.
   **    this leaves the document in an unstable state where the next
   **    Lock/Unlock sequence will shut the document down (eg. a
   **    linking client connecting and disconnecting).
   */
   if (IsWindowVisible(lpOutlineDoc->m_hWndDoc))
      OleDoc_Lock(lpOleDoc, FALSE /* fLock */, fShutdown);

   ShowWindow(((LPOUTLINEDOC)lpOleDoc)->m_hWndDoc, SW_HIDE);

#if defined( OLE_SERVER )
   {
      LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOleDoc;

      /* NOTE: if we are an embedded object and we are not
      **    in-place active, we must inform our
      **    embedding container that our window is hiding (closing
      **    from the user's perspective). the container must now
      **    un-hatch our object.
      */
      if (lpServerDoc->m_lpOleClientSite != NULL
#if defined( INPLACE_SVR )
         && !lpServerDoc->m_fInPlaceVisible
#endif
      ) {
         OLEDBG_BEGIN2("IOleClientSite::OnShowWindow(FALSE) called\r\n");
         lpServerDoc->m_lpOleClientSite->lpVtbl->OnShowWindow(
               lpServerDoc->m_lpOleClientSite,
               FALSE
         );
         OLEDBG_END2
      }
   }
#endif

   /* NOTE: if there are no more documents visible to the user.
   **    and the app itself is not under user control, then
   **    it has no reason to stay visible. we thus should hide the
   **    app. we can not directly destroy the app, because it may be
   **    validly being used programatically by another client
   **    application and should remain running. it should simply be
   **    hidded from the user.
   */
   OleApp_HideIfNoReasonToStayVisible(lpOleApp);
   OLEDBG_END3
}

/* OleDoc_Lock
** -----------
**    Lock/Unlock the Doc object. if the last lock is unlocked and
**    fLastUnlockReleases == TRUE, then the Doc object will shut down
**    (ie. it will recieve its final release and its refcnt will go to 0).
*/
HRESULT OleDoc_Lock(LPOLEDOC lpOleDoc, BOOL fLock, BOOL fLastUnlockReleases)
{
   HRESULT hrErr;

#if defined( _DEBUG )
   if (fLock) {
      OLEDBG_BEGIN2("CoLockObjectExternal(lpDoc,TRUE) called\r\n")
   } else {
      if (fLastUnlockReleases)
         OLEDBG_BEGIN2("CoLockObjectExternal(lpDoc,FALSE,TRUE) called\r\n")
      else
         OLEDBG_BEGIN2("CoLockObjectExternal(lpDoc,FALSE,FALSE) called\r\n")
   }
#endif  // _DEBUG

   hrErr = CoLockObjectExternal(
         (LPUNKNOWN)&lpOleDoc->m_Unknown, fLock, fLastUnlockReleases);

   OLEDBG_END2
   return hrErr;
}

/* OleDoc_AddRef
** -------------
**
**  increment the ref count of the document object.
**
**    Returns the new ref count on the object
*/
ULONG OleDoc_AddRef(LPOLEDOC lpOleDoc)
{
   ++lpOleDoc->m_cRef;

#if defined( _DEBUG )
   OleDbgOutRefCnt4(
         "OleDoc_AddRef: cRef++\r\n",
         lpOleDoc,
         lpOleDoc->m_cRef
   );
#endif
   return lpOleDoc->m_cRef;
}

/* OleDoc_Release
** --------------
**
**  decrement the ref count of the document object.
**    if the ref count goes to 0, then the document is destroyed.
**
**    Returns the remaining ref count on the object
*/
ULONG OleDoc_Release (LPOLEDOC lpOleDoc)
{
   ULONG cRef;
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;

   /*********************************************************************
   ** NOTE: when the obj refcnt == 0, then destroy the object.     **
   **     otherwise the object is still in use.                        **
   *********************************************************************/

   cRef = --lpOleDoc->m_cRef;

#if defined( _DEBUG )
   OleDbgAssertSz (lpOleDoc->m_cRef >= 0, "Release called with cRef == 0");

   OleDbgOutRefCnt4(
         "OleDoc_Release: cRef--\r\n", lpOleDoc, cRef);
#endif
   if (cRef == 0)
      OutlineDoc_Destroy((LPOUTLINEDOC)lpOleDoc);

   return cRef;
}

/* OleDoc_QueryInterface
** ---------------------
**
** Retrieve a pointer to an interface on the document object.
**
**    NOTE: this function will AddRef the ref cnt of the object.
**
**    Returns S_OK if interface is successfully retrieved.
**            E_NOINTERFACE if the interface is not supported
*/
HRESULT OleDoc_QueryInterface(
      LPOLEDOC          lpOleDoc,
      REFIID            riid,
      LPVOID FAR*       lplpvObj
)
{
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;
   SCODE sc = E_NOINTERFACE;

   /* NOTE: we must make sure to set all out ptr parameters to NULL. */
   *lplpvObj = NULL;

   if (IsEqualIID(riid, &IID_IUnknown)) {
      OleDbgOut4("OleDoc_QueryInterface: IUnknown* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpOleDoc->m_Unknown;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }
   else if(lpOutlineDoc->m_fDataTransferDoc
         && IsEqualIID(riid, &IID_IDataObject)) {
      OleDbgOut4("OleDoc_QueryInterface: IDataObject* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpOleDoc->m_DataObject;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }

   /* NOTE: if this document is a DataTransferDocument used to
   **    support a clipboard or drag/drop operation, then it should
   **    only expose IUnknown, IDataObject, and IDropSource
   **    interfaces. if the document is a normal user document, then
   **    we will also continue to consider our other interfaces.
   */
   if (lpOutlineDoc->m_fDataTransferDoc)
      goto done;

   if(IsEqualIID(riid,&IID_IPersist) || IsEqualIID(riid,&IID_IPersistFile)) {
      OleDbgOut4("OleDoc_QueryInterface: IPersistFile* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpOleDoc->m_PersistFile;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }
   else if(IsEqualIID(riid, &IID_IOleItemContainer) ||
         IsEqualIID(riid, &IID_IOleContainer) ||
         IsEqualIID(riid, &IID_IParseDisplayName) ) {
      OleDbgOut4("OleDoc_QueryInterface: IOleItemContainer* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpOleDoc->m_OleItemContainer;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }
   else if(IsEqualIID(riid, &IID_IExternalConnection)) {
      OleDbgOut4("OleDoc_QueryInterface: IExternalConnection* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpOleDoc->m_ExternalConnection;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }

#if defined( USE_DRAGDROP )
   else if(IsEqualIID(riid, &IID_IDropTarget)) {
      OleDbgOut4("OleDoc_QueryInterface: IDropTarget* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpOleDoc->m_DropTarget;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }
   else if(IsEqualIID(riid, &IID_IDropSource)) {
      OleDbgOut4("OleDoc_QueryInterface: IDropSource* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpOleDoc->m_DropSource;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }
#endif

#if defined( OLE_CNTR )
   else if (IsEqualIID(riid, &IID_IOleUILinkContainer)) {
      OleDbgOut4("OleDoc_QueryInterface: IOleUILinkContainer* RETURNED\r\n");

      *lplpvObj=(LPVOID)&((LPCONTAINERDOC)lpOleDoc)->m_OleUILinkContainer;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }
#endif

#if defined( OLE_SERVER )

   /* NOTE: if OLE server version, than also offer the server
   **    specific interfaces: IOleObject and IPersistStorage.
   */
   else if (IsEqualIID(riid, &IID_IOleObject)) {
      OleDbgOut4("OleDoc_QueryInterface: IOleObject* RETURNED\r\n");

      *lplpvObj = (LPVOID) &((LPSERVERDOC)lpOleDoc)->m_OleObject;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }
   else if(IsEqualIID(riid, &IID_IPersistStorage)) {
      OleDbgOut4("OleDoc_QueryInterface: IPersistStorage* RETURNED\r\n");

      *lplpvObj = (LPVOID) &((LPSERVERDOC)lpOleDoc)->m_PersistStorage;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }
   else if(IsEqualIID(riid, &IID_IDataObject)) {
      OleDbgOut4("OleDoc_QueryInterface: IDataObject* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpOleDoc->m_DataObject;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }

#if defined( SVR_TREATAS )
   else if(IsEqualIID(riid, &IID_IStdMarshalInfo)) {
      OleDbgOut4("OleDoc_QueryInterface: IStdMarshalInfo* RETURNED\r\n");

      *lplpvObj = (LPVOID) &((LPSERVERDOC)lpOleDoc)->m_StdMarshalInfo;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }
#endif  // SVR_TREATAS

#if defined( INPLACE_SVR )
   else if (IsEqualIID(riid, &IID_IOleWindow) ||
          IsEqualIID(riid, &IID_IOleInPlaceObject)) {
      OleDbgOut4("OleDoc_QueryInterface: IOleInPlaceObject* RETURNED\r\n");

      *lplpvObj = (LPVOID) &((LPSERVERDOC)lpOleDoc)->m_OleInPlaceObject;
      OleDoc_AddRef(lpOleDoc);
      sc = S_OK;
   }
#endif // INPLACE_SVR
#endif // OLE_SERVER

done:
   OleDbgQueryInterfaceMethod(*lplpvObj);

   return sc;
}

/* OleDoc_Close
 * ------------
 *
 *  Close the document.
 *      This functions performs the actions that are in common to all
 *      document types which derive from OleDoc (eg. ContainerDoc and
 *      ServerDoc) which are required to close a document.
 *
 *  Returns:
 *      FALSE -- user canceled the closing of the doc.
 *      TRUE -- the doc was successfully closed
 */

BOOL OleDoc_Close(LPOLEDOC lpOleDoc, DWORD dwSaveOption)
{
   LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOLEDOC lpClipboardDoc;
   LPLINELIST lpLL     = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;
   BOOL fAbortIfSaveCanceled = (dwSaveOption == OLECLOSE_PROMPTSAVE);

   if (! lpOleDoc)
      return TRUE;    // active doc's are already destroyed

   if (lpOleDoc->m_fObjIsClosing)
      return TRUE;    // Closing is already in progress

   OLEDBG_BEGIN3("OleDoc_Close\r\n")

   if (! OutlineDoc_CheckSaveChanges((LPOUTLINEDOC)lpOleDoc,&dwSaveOption)
         && fAbortIfSaveCanceled) {
      OLEDBG_END3
      return FALSE;           // cancel closing the doc
   }

   lpOleDoc->m_fObjIsClosing = TRUE;   // guard against recursive call

   /* NOTE: in order to have a stable app and doc during the
   **    process of closing, we intially AddRef the App and Doc ref
   **    cnts and later Release them. These initial AddRefs are
   **    artificial; they simply guarantee that these objects do not
   **    get destroyed until the end of this routine.
   */
   OleApp_AddRef(lpOleApp);
   OleDoc_AddRef(lpOleDoc);

#if defined( OLE_CNTR )
   {
      LPCONTAINERDOC lpContainerDoc = (LPCONTAINERDOC)lpOleDoc;

      /* NOTE: force all OLE objects to close. this forces all
      **    OLE object to transition from running to loaded. we can
      **    NOT exit if any embeddings are still running.
      **    if an object can't be closed and this close operation was
      **    started by the user, then we will abort closing our document.
      */
      if (! ContainerDoc_CloseAllOleObjects(lpContainerDoc, OLECLOSE_NOSAVE)
            && fAbortIfSaveCanceled) {
         OleDoc_Release(lpOleDoc);       // release artificial AddRef above
         OleApp_Release(lpOleApp);       // release artificial AddRef above
         lpOleDoc->m_fObjIsClosing = FALSE; // clear recursion guard

         OLEDBG_END3
         return FALSE;   // Closing is aborted
      }
   }
#endif

#if defined( INPLACE_SVR )
   /* NOTE: if the server is currently in-place active we must
   **    deactivate it now before closing
   */
   ServerDoc_DoInPlaceDeactivate((LPSERVERDOC)lpOleDoc);
#endif

   /* NOTE: if this document is the source of data for the
   **    clipboard, then flush the clipboard. it is important to flush
   **    the clipboard BEFORE calling sending any notifications to
   **    clients (eg. IOleClientSite::OnShowWindow(FALSE)) which could
   **    give them a chance to run and try to get our clipboard data
   **    object that we want to destroy. (eg. our app tries to
   **    update the paste button of the toolbar when
   **    WM_ACTIVATEAPP is received.)
   */
   lpClipboardDoc = (LPOLEDOC)lpOutlineApp->m_lpClipboardDoc;
   if (lpClipboardDoc &&
      lpClipboardDoc->m_lpSrcDocOfCopy == lpOleDoc) {
      OleApp_FlushClipboard(lpOleApp);
   }

   /* NOTE: Revoke the object from the Running Object Table. it is
   **    best if the object is revoke prior to calling
   **    COLockObjectExternal(FALSE,TRUE) which is called when the
   **    document window is hidden from the user.
   */
   OLEDBG_BEGIN3("OleStdRevokeAsRunning called\r\n")
   OleStdRevokeAsRunning(&lpOleDoc->m_dwRegROT);
   OLEDBG_END3

   /* NOTE: if the user is in control of the document, the user
   **    accounts for one refcnt on the document. Closing the
   **    document is achieved by releasing the object on behalf of
   **    the user. if the document is not referenced by any other
   **    clients, then the document will also be destroyed. if it
   **    is referenced by other clients, then it will remain until
   **    they release it. it is important to hide the window and call
   **    IOleClientSite::OnShowWindow(FALSE) BEFORE sending OnClose
   **    notification.
   */
   OleDoc_HideWindow(lpOleDoc, TRUE);

#if defined( OLE_SERVER )
   {
      LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOleDoc;
      LPSERVERNAMETABLE lpServerNameTable =
         (LPSERVERNAMETABLE)((LPOUTLINEDOC)lpOleDoc)->m_lpNameTable;

      /* NOTE: force all pseudo objects to close. this informs all
      **    linking clients of pseudo objects to release their PseudoObj.
      */
      ServerNameTable_CloseAllPseudoObjs(lpServerNameTable);

      /* NOTE: send last OnDataChange notification to clients
      **    that have registered for data notifications when object
      **    stops running (ADVF_DATAONSTOP), if the data in our
      **    object has ever changed. it is best to only send this
      **    notification if necessary.
      */
      if (lpServerDoc->m_lpDataAdviseHldr) {
         if (lpServerDoc->m_fSendDataOnStop) {
            ServerDoc_SendAdvise(
                  (LPSERVERDOC)lpOleDoc,
                  OLE_ONDATACHANGE,
                  NULL,   /* lpmkDoc -- not relevant here */
                  ADVF_DATAONSTOP
            );
         }
         /* NOTE: we just sent the last data notification that we
         **    need to send; release our DataAdviseHolder. we SHOULD be
         **    the only one using it.
         */

         OleStdVerifyRelease(
               (LPUNKNOWN)lpServerDoc->m_lpDataAdviseHldr,
               OLESTR("DataAdviseHldr not released properly")
         );
         lpServerDoc->m_lpDataAdviseHldr = NULL;
      }

      // NOTE: inform all of our linking clients that we are closing.

      if (lpServerDoc->m_lpOleAdviseHldr) {

ServerDoc_SendAdvise(
               (LPSERVERDOC)lpOleDoc,
               OLE_ONCLOSE,
               NULL,   /* lpmkDoc -- not relevant here */
               0       /* advf -- not relevant here */
         );

         /* NOTE: OnClose is the last notification that we need to
         **    send; release our OleAdviseHolder. we SHOULD be the only
         **    one using it. this will make our destructor realize that
         **    OnClose notification has already been sent.
         */
         OleStdVerifyRelease(
               (LPUNKNOWN)lpServerDoc->m_lpOleAdviseHldr,
               OLESTR("OleAdviseHldr not released properly")
         );
         lpServerDoc->m_lpOleAdviseHldr = NULL;
      }

      /* release our Container's ClientSite. */
      if(lpServerDoc->m_lpOleClientSite) {
         OleStdRelease((LPUNKNOWN)lpServerDoc->m_lpOleClientSite);
         lpServerDoc->m_lpOleClientSite = NULL;
      }
   }
#endif

   if (lpOleDoc->m_lpLLStm) {
      /* release our LineList stream. */
      OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpLLStm);
      lpOleDoc->m_lpLLStm = NULL;
   }

   if (lpOleDoc->m_lpNTStm) {
      /* release our NameTable stream. */
      OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpNTStm);
      lpOleDoc->m_lpNTStm = NULL;
   }

   if (lpOleDoc->m_lpStg) {
      /* release our doc storage. */
      OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpStg);
      lpOleDoc->m_lpStg = NULL;
   }

   if (lpOleDoc->m_lpFileMoniker) {
      OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpFileMoniker);
      lpOleDoc->m_lpFileMoniker = NULL;
   }

   /* NOTE: this call forces all external connections to our
   **    object to close down and therefore guarantees that we receive
   **    all releases associated with those external connections.
   */
   OLEDBG_BEGIN2("CoDisconnectObject(lpDoc) called\r\n")
   CoDisconnectObject((LPUNKNOWN)&lpOleDoc->m_Unknown, 0);
   OLEDBG_END2

   OleDoc_Release(lpOleDoc);       // release artificial AddRef above
   OleApp_Release(lpOleApp);       // release artificial AddRef above

   OLEDBG_END3
   return TRUE;
}

/* OleDoc_Destroy
 * --------------
 *
 *  Free all OLE related resources that had been allocated for a document.
 */
void OleDoc_Destroy(LPOLEDOC lpOleDoc)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpOleDoc;

   if (lpOleDoc->m_fObjIsDestroying)
      return;     // Doc destruction is already in progress

   lpOleDoc->m_fObjIsDestroying = TRUE;    // guard against recursive call

#if defined( OLE_SERVER )

   /* NOTE: it is ALWAYS necessary to make sure that the work we
   **    do in our OleDoc_Close function is performed before we
   **    destroy our document object. this includes revoking from the
   **    Running Object Table (ROT), sending OnClose notification,
   **    revoking from Drag/Drop, closing all pseudo objects, etc.
   **    There are some tricky scenarios involving linking and
   **    when IOleObject::Close is called versus when we get our
   **    final release causing us to call our OleDoc_Destroy
   **    (destructor) function.
   **
   **    SCENARIO 1 -- closing from server (File.Exit or File.Close)
   **                  OleDoc_Close function is called directly by the
   **                  server in response to the menu command
   **                  (WM_COMMAND processing).
   **
   **    SCENARIO 2 -- closed by embedding container
   **                  our embedding container calls IOleObject::Close
   **                  directly.
   **
   **    SCENARIO 3 -- silent-update final release
   **                  THIS IS THE TRICKY ONE!!!
   **                  in the case that our object is launched because
   **                  a linking client calls IOleObject::Update on
   **                  its link, then our object will be run
   **                  invisibly, typically GetData will be called,
   **                  and then the connection from the linking client
   **                  will be released. the release of this last
   **                  linking connection should cause our object to
   **                  shut down.
   **                  there are 2 strategies to deal with this scenario:
   **
   **                  STRATEGY 1 -- implement IExternalConnection.
   **                  IExternalConnection::AddConnection will be
   **                  called (by the StubManager) every time that an
   **                  external (linking) connection is created or
   **                  CoLockObjectExternal is called. the object
   **                  should maintain a count of strong connections
   **                  (m_dwStrongExtConn). IExternalConnection::
   **                  ReleaseConnection will be called when these
   **                  connections are released. when the
   **                  m_dwStrongExtConn transistions to 0, the object
   **                  should call its IOleObject::Close function.
   **                  this assumes that CoLockObjectExternal is used
   **                  to manage locks by the object itself (eg. when
   **                  the object is visible to the user--fUserCtrl,
   **                  and when PseudoObjects are created, etc.)
   **                  this is the strategy implemented by SVROUTL.
   **
   **                  STRATEGY 2 -- guard both the destructor
   **                  function and the Close function. if the
   **                  destructor is called directly without Close
   **                  first being called, then call Close before
   **                  proceeding with the destruction code.
   **                  previously SVROUTL was organized in this
   **                  manner. that old code is conditionaly compiled
   **                  away with "#ifdef OBSOLETE" below. this
   **                  method has the disadvantage that external
   **                  remoting is no longer possible by the time the
   **                  Close is called making it impossible for
   **                  the object to ask its container to save the
   **                  object if the object is dirty. this can result
   **                  in data loss. thus STRATEGY 1 is safer.
   **                  consider the scenario where an in-place
   **                  container UIDeactivates an object but does NOT
   **                  keep the object locked running (this is
   **                  required--see CntrLine_IPSite_OnInPlaceActivate
   **                  in cntrline.c), then, if a linking client binds
   **                  and unbinds from the object, the object will be
   **                  destroyed and will NOT have an opportunity to
   **                  be saved. by implementing IExternalConnection,
   **                  a server can insulate itself from a poorly
   **                  written container.
   */
#if defined( _DEBUG )
   OleDbgAssertSz(
         (lpOutlineDoc->m_fDataTransferDoc || lpOleDoc->m_fObjIsClosing),
         "Destroy called without Close being called\r\n"
   );
#endif  // _DEBUG
#if defined( OBSOLETE )
   /* NOTE: if the document destructor is called directly because
   **    the object's refcnt went to 0 (ie. without OleDoc_Close first
   **    being called), then we need to make sure that the document is
   **    properly closed before destroying the object. this scenario
   **    could arise during a silent-update of a link. calling
   **    OleDoc_Close here guarantees that the clipboard will be
   **    properly flushed, the doc's moniker will be properly revoked,
   **    the document will be saved if necessary, etc.
   */
   if (!lpOutlineDoc->m_fDataTransferDoc && !lpOleDoc->m_fObjIsClosing)
      OleDoc_Close(lpOleDoc, OLECLOSE_NOSAVE);
#endif

   {
      LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOleDoc;
      /* NOTE: perform processing specific for an OLE server */

#if defined( SVR_TREATAS )
      if (lpServerDoc->m_lpszTreatAsType) {
         OleStdFreeString(lpServerDoc->m_lpszTreatAsType, NULL);
         lpServerDoc->m_lpszTreatAsType = NULL;
      }
#endif  // SVR_TREATAS

#if defined( INPLACE_SVR )
      if (IsWindow(lpServerDoc->m_hWndHatch))
         DestroyWindow(lpServerDoc->m_hWndHatch);
#endif  // INPLACE_SVR
   }
#endif  // OLE_SERVER

   if (lpOleDoc->m_lpLLStm) {
      /* release our LineList stream. */
      OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpLLStm);
      lpOleDoc->m_lpLLStm = NULL;
   }

   if (lpOleDoc->m_lpNTStm) {
      /* release our NameTable stream. */
      OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpNTStm);
      lpOleDoc->m_lpNTStm = NULL;
   }

   if (lpOleDoc->m_lpStg) {
      /* release our doc storage. */
      OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpStg);
      lpOleDoc->m_lpStg = NULL;
   }

   if (lpOleDoc->m_lpFileMoniker) {
      OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpFileMoniker);
      lpOleDoc->m_lpFileMoniker = NULL;
   }

   /*****************************************************************
   ** NOTE: each document addref's the app object in order to  **
   **    guarentee that the app does not shut down while the doc   **
   **    is still open. since this doc is now destroyed, we will   **
   **    release this refcnt now. if there are now more open       **
   **    documents AND the app is not under the control of the     **
   **    user (ie. launched by OLE) then the app will revoke its   **
   **    ClassFactory. if there are no more references to the      **
   **    ClassFactory after it is revoked, then the app will shut  **
   **    down. this whole procedure is triggered by calling        **
   **    OutlineApp_DocUnlockApp.                                  **
   *****************************************************************/

   OutlineApp_DocUnlockApp(lpOutlineApp, lpOutlineDoc);
}

/* OleDoc_SetUpdateEditMenuFlag
 * ----------------------------
 *
 *  Purpose:
 *      Set/clear the UpdateEditMenuFlag in OleDoc.
 *
 *  Parameters:
 *      fUpdate     new value of the flag
 *
 *  Returns:
 */
void OleDoc_SetUpdateEditMenuFlag(LPOLEDOC lpOleDoc, BOOL fUpdate)
{
   if (!lpOleDoc)
      return;

   lpOleDoc->m_fUpdateEditMenu = fUpdate;
}

/* OleDoc_GetUpdateEditMenuFlag
 * ----------------------------
 *
 *  Purpose:
 *      Get the value of the UpdateEditMenuFlag in OleDoc
 *
 *  Parameters:
 *
 *  Returns:
 *      value of the flag
 */
BOOL OleDoc_GetUpdateEditMenuFlag(LPOLEDOC lpOleDoc)
{
   if (!lpOleDoc)
      return FALSE;

   return lpOleDoc->m_fUpdateEditMenu;
}

/*************************************************************************
** OleDoc::IUnknown interface implementation
*************************************************************************/

STDMETHODIMP OleDoc_Unk_QueryInterface(
      LPUNKNOWN           lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
)
{
   LPOLEDOC lpOleDoc = ((struct CDocUnknownImpl FAR*)lpThis)->lpOleDoc;

   return OleDoc_QueryInterface(lpOleDoc, riid, lplpvObj);
}

STDMETHODIMP_(ULONG) OleDoc_Unk_AddRef(LPUNKNOWN lpThis)
{
   LPOLEDOC lpOleDoc = ((struct CDocUnknownImpl FAR*)lpThis)->lpOleDoc;

   OleDbgAddRefMethod(lpThis, "IUnknown");

   return OleDoc_AddRef(lpOleDoc);
}

STDMETHODIMP_(ULONG) OleDoc_Unk_Release (LPUNKNOWN lpThis)
{
   LPOLEDOC lpOleDoc = ((struct CDocUnknownImpl FAR*)lpThis)->lpOleDoc;

   OleDbgReleaseMethod(lpThis, "IUnknown");

   return OleDoc_Release(lpOleDoc);
}


OLEOUTL.H

/*************************************************************************
**
**    OLE 2.0 Sample Code
**
**    oleoutl.h
**
**    This file contains file contains data structure defintions,
**    function prototypes, constants, etc. which are common to the
**    server version and the container version of the app.
**    app version of the Outline series of sample applications:
**          Outline -- base version of the app (without OLE functionality)
**          SvrOutl -- OLE 2.0 Server sample app
**          CntrOutl -- OLE 2.0 Containter sample app
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#if !defined( _OLEOUTL_H_ )
#define _OLEOUTL_H_

#ifndef RC_INVOKED
#pragma message ("INCLUDING OLEOUTL.H from " __FILE__)
// make 'different levels of inderection' considered an error
#pragma warning (error:4047)
#endif  /* RC_INVOKED */

#if defined( USE_MSGFILTER )
//#include "..\include\msgfiltr.h"
#include "msgfiltr.h"
#endif  // USE_MSGFILTER

#include "defguid.h"
#include "ansiapi.h"

/* Defines */

/* NOTE: these strings should correspond to the strings registered
**    in the registration database.
*/
// REVIEW: should load strings from resource file
#if defined( INPLACE_SVR )

#ifdef TEST32

#define CLSID_APP   CLSID_ISvrOu32
#define FULLUSERTYPENAME    OLESTR("Ole 2.0 In-Place Server Outline 32")
#define SHORTUSERTYPENAME   OLESTR("Outline32")   // max 15 chars
#undef  APPFILENAMEFILTER
#define APPFILENAMEFILTER   "Outline Files (*.OLT)|*.olt|All files (*.*)|*.*|"
#undef  DEFEXTENSION
#define DEFEXTENSION    "olt"           // Default file extension

#else

#define CLSID_APP   CLSID_ISvrOtl
#define FULLUSERTYPENAME    OLESTR("Ole 2.0 In-Place Server Outline")
#define SHORTUSERTYPENAME   OLESTR("Outline")   // max 15 chars
#undef  APPFILENAMEFILTER
#define APPFILENAMEFILTER   "Outline Files (*.OLN)|*.oln|All files (*.*)|*.*|"
#undef  DEFEXTENSION
#define DEFEXTENSION    "oln"           // Default file extension

#endif  // TEST32
#endif  // INPLACE_SVR

#if defined( INPLACE_CNTR )

#ifdef TEST32

#define CLSID_APP   CLSID_ICntrO32
#define FULLUSERTYPENAME    OLESTR("Ole 2.0 In-Place Container Outline 32")
// #define SHORTUSERTYPENAME    "Outline32"   // max 15 chars
#undef  APPFILENAMEFILTER
#define APPFILENAMEFILTER   "CntrOutl Files (*.OLC)|*.olc|Outline Files (*.OLT)|*.olt|All files (*.*)|*.*|"
#undef  DEFEXTENSION
#define DEFEXTENSION    "olc"           // Default file extension

#else

#define CLSID_APP   CLSID_ICntrOtl
#define FULLUSERTYPENAME    OLESTR("Ole 2.0 In-Place Container Outline")
// #define SHORTUSERTYPENAME    "Outline"   // max 15 chars
#undef  APPFILENAMEFILTER
#define APPFILENAMEFILTER   "CntrOutl Files (*.OLC)|*.olc|Outline Files (*.OLN)|*.oln|All files (*.*)|*.*|"
#undef  DEFEXTENSION
#define DEFEXTENSION    "olc"           // Default file extension

#endif TEST32
#endif  // INPLACE_CNTR

#if defined( OLE_SERVER ) && !defined( INPLACE_SVR )

#ifdef TEST32

#define CLSID_APP   CLSID_SvrOut32
#define FULLUSERTYPENAME    OLESTR("Ole 2.0 Server Sample Outline 32")
#define SHORTUSERTYPENAME   OLESTR("Outline32")
#undef  APPFILENAMEFILTER
#define APPFILENAMEFILTER   "Outline Files (*.OLT)|*.olt|All files (*.*)|*.*|"
#undef  DEFEXTENSION
#define DEFEXTENSION    "olt"           // Default file extension

#else

#define CLSID_APP   CLSID_SvrOutl
#define FULLUSERTYPENAME    OLESTR("Ole 2.0 Server Sample Outline")
#define SHORTUSERTYPENAME   OLESTR("Outline")
#undef  APPFILENAMEFILTER
#define APPFILENAMEFILTER   "Outline Files (*.OLN)|*.oln|All files (*.*)|*.*|"
#undef  DEFEXTENSION
#define DEFEXTENSION    "oln"           // Default file extension

#endif  //TEST32
#endif  // OLE_SERVER && ! INPLACE_SVR

#if defined( OLE_CNTR ) && !defined( INPLACE_CNTR )
#ifdef TEST32

#define CLSID_APP   CLSID_CntrOu32
#define FULLUSERTYPENAME    OLESTR("Ole 2.0 Container Sample Outline 32")
// #define SHORTUSERTYPENAME    "Outline32"  // max 15 chars
#undef  APPFILENAMEFILTER
#define APPFILENAMEFILTER   "CntrOutl Files (*.OLC)|*.olc|Outline Files (*.OLT)|*.olt|All files (*.*)|*.*|"
#undef  DEFEXTENSION
#define DEFEXTENSION    "olc"           // Default file extension

#else

#define CLSID_APP   CLSID_CntrOutl
#define FULLUSERTYPENAME    OLESTR("Ole 2.0 Container Sample Outline")
// #define SHORTUSERTYPENAME    "Outline"  // max 15 chars
#undef  APPFILENAMEFILTER
#define APPFILENAMEFILTER   "CntrOutl Files (*.OLC)|*.olc|Outline Files (*.OLN)|*.oln|All files (*.*)|*.*|"
#undef  DEFEXTENSION
#define DEFEXTENSION    "olc"           // Default file extension

#endif  //TEST32
#endif  // OLE_CNTR && ! INPLACE_CNTR

// Maximum number of formats offered by IDataObject::GetData/SetData
#define MAXNOFMTS       10
#define MAXNOLINKTYPES   3

#if defined( USE_DRAGDROP )
#define DD_SEL_THRESH       HITTESTDELTA    // Border threshold to start drag
#define MAX_SEL_ITEMS       0x0080
#endif  // USE_DRAGDROP

/* Positions of the various menus */
#define POS_FILEMENU        0
#define POS_EDITMENU        1
#define POS_VIEWMENU        2
#define POS_LINEMENU        3
#define POS_NAMEMENU        4
#define POS_OPTIONSMENU     5
#define POS_DEBUGMENU       6
#define POS_HELPMENU        7

#define POS_OBJECT      11

/* Types */

// Document initialization type
#define DOCTYPE_EMBEDDED    3   // init from an IStorage* of an embedded obj
#define DOCTYPE_FROMSTG     4   // init from an IStorage* with doc bit set

/* Forward type definitions */
typedef struct tagOLEAPP FAR* LPOLEAPP;
typedef struct tagOLEDOC FAR* LPOLEDOC;

/* Flags to control Moniker assignment for OleDoc_GetFullMoniker */
// REVIEW: should use official OLEGETMONIKER type for final version
typedef enum tagGETMONIKERTYPE {
   GETMONIKER_ONLYIFTHERE  = 1,
   GETMONIKER_FORCEASSIGN  = 2,
   GETMONIKER_UNASSIGN     = 3,
   GETMONIKER_TEMPFORUSER  = 4
} GETMONIKERTYPE;

/* Flags to control direction for drag scrolling */
typedef enum tagSCROLLDIR {
   SCROLLDIR_NULL          = 0,
   SCROLLDIR_UP            = 1,
   SCROLLDIR_DOWN          = 2,
   SCROLLDIR_RIGHT         = 3,    // currently not used
   SCROLLDIR_LEFT          = 4     // currently not used
} SCROLLDIR;

/*************************************************************************
** class OLEDOC : OUTLINEDOC
**    OLEDOC is an extention to the base OUTLINEDOC object (structure)
**    that adds common OLE 2.0 functionality used by both the server
**    and container versions. This is an abstract class. You do not
**    instantiate an instance of OLEDOC directly but instead
**    instantiate one of its concrete subclasses: SERVERDOC or
**    CONTAINERDOC. There is one instance of an document
**    object created per document open in the app. The SDI
**    version of the app supports one ServerDoc at a time. The MDI
**    version of the app can manage multiple documents at one time.
**    The OLEDOC class inherits all fields from the OUTLINEDOC class.
**    This inheritance is achieved by including a member variable of
**    type OUTLINEDOC as the first field in the OLEDOC
**    structure. Thus a pointer to an OLEDOC object can be cast to be
**    a pointer to a OUTLINEDOC object.
*************************************************************************/

typedef struct tagOLEDOC {
   OUTLINEDOC      m_OutlineDoc;       // ServerDoc inherits from OutlineDoc
   ULONG           m_cRef;             // total ref count for document
   ULONG           m_dwStrongExtConn;  // total strong connection count
                              //  (from IExternalConnection)
                              //  when this count transitions to 0
                              //  and fLastUnlockCloses==TRUE, then
                              //  IOleObject::Close is called to
                              //  close the document.
#if defined( _DEBUG )
   ULONG           m_cCntrLock;        // total count of LockContainer locks
                              //  (for debugging purposes only)
#endif
   LPSTORAGE       m_lpStg;            // OleDoc must keep its stg open
                              //  even in-memory server doc should
                              //  keep Stg open for low memory save
   LPSTREAM        m_lpLLStm;          // Hold LineList IStream* open for
                              //  low memory save
   LPSTREAM        m_lpNTStm;          // Hold NameTable IStream* open for
                              //  low memory save
   BOOL            m_fObjIsClosing;    // flag to guard recursive close call
   BOOL            m_fObjIsDestroying; // flag to guard recursiv destroy call
   DWORD           m_dwRegROT;         // key if doc registered as running
   LPMONIKER       m_lpFileMoniker;    // moniker if file-based/untitled doc
   BOOL            m_fLinkSourceAvail; // can doc offer CF_LINKSOURCE
   LPOLEDOC        m_lpSrcDocOfCopy;   // src doc if doc created for copy
   BOOL            m_fUpdateEditMenu;  // need to update edit menu??

#if defined( USE_DRAGDROP )
   DWORD           m_dwTimeEnterScrollArea;  // time entering scroll region
   DWORD           m_dwLastScrollDir;  // current dir for drag scroll
   DWORD           m_dwNextScrollTime; // time for next scroll
   BOOL            m_fRegDragDrop;     // is doc registered as drop target?
   BOOL            m_fLocalDrag;       // is doc source of the drag
   BOOL            m_fLocalDrop;       // was doc target of the drop
   BOOL            m_fCanDropCopy;     // is Drag/Drop copy/move possible?
   BOOL            m_fCanDropLink;     // is Drag/Drop link possible?
   BOOL            m_fDragLeave;       // has drag left
   BOOL            m_fPendingDrag;     // LButtonDown--possible drag pending
   POINT           m_ptButDown;        // LButtonDown coordinates
#endif  // USE_DRAGDROP

#if defined( INPLACE_SVR ) || defined( INPLACE_CNTR )
   BOOL            m_fCSHelpMode;      // Shift-F1 context help mode
#endif

   struct CDocUnknownImpl {
      IUnknownVtbl FAR*       lpVtbl;
      LPOLEDOC                lpOleDoc;
      int                     cRef;   // interface specific ref count.
   } m_Unknown;

   struct CDocPersistFileImpl {
      IPersistFileVtbl FAR*   lpVtbl;
      LPOLEDOC                lpOleDoc;
      int                     cRef;   // interface specific ref count.
   } m_PersistFile;

   struct CDocOleItemContainerImpl {
      IOleItemContainerVtbl FAR*  lpVtbl;
      LPOLEDOC                    lpOleDoc;
      int                         cRef;   // interface specific ref count.
   } m_OleItemContainer;

   struct CDocExternalConnectionImpl {
      IExternalConnectionVtbl FAR* lpVtbl;
      LPOLEDOC                lpOleDoc;
      int                     cRef;   // interface specific ref count.
   } m_ExternalConnection;

   struct CDocDataObjectImpl {
      IDataObjectVtbl FAR*        lpVtbl;
      LPOLEDOC                    lpOleDoc;
      int                         cRef;   // interface specific ref count.
   } m_DataObject;

#ifdef USE_DRAGDROP
   struct CDocDropSourceImpl {
      IDropSourceVtbl FAR*    lpVtbl;
      LPOLEDOC                lpOleDoc;
      int                     cRef;   // interface specific ref count.
   } m_DropSource;

   struct CDocDropTargetImpl {
      IDropTargetVtbl FAR*    lpVtbl;
      LPOLEDOC                lpOleDoc;
      int                     cRef;   // interface specific ref count.
   } m_DropTarget;
#endif  // USE_DRAGDROP

} OLEDOC;

/* OleDoc methods (functions) */
BOOL OleDoc_Init(LPOLEDOC lpOleDoc, BOOL fDataTransferDoc);
BOOL OleDoc_InitNewFile(LPOLEDOC lpOleDoc);
void OleDoc_ShowWindow(LPOLEDOC lpOleDoc);
void OleDoc_HideWindow(LPOLEDOC lpOleDoc, BOOL fShutDown);
HRESULT OleDoc_Lock(LPOLEDOC lpOleDoc, BOOL fLock, BOOL fLastUnlockReleases);
ULONG OleDoc_AddRef(LPOLEDOC lpOleDoc);
ULONG OleDoc_Release (LPOLEDOC lpOleDoc);
HRESULT OleDoc_QueryInterface(
      LPOLEDOC          lpOleDoc,
      REFIID            riid,
      LPVOID FAR*       lplpUnk
);
BOOL OleDoc_Close(LPOLEDOC lpOleDoc, DWORD dwSaveOption);
void OleDoc_Destroy(LPOLEDOC lpOleDoc);
void OleDoc_SetUpdateEditMenuFlag(LPOLEDOC lpOleDoc, BOOL fUpdate);
BOOL OleDoc_GetUpdateEditMenuFlag(LPOLEDOC lpOleDoc);
void OleDoc_GetExtent(LPOLEDOC lpOleDoc, LPSIZEL lpsizel);
HGLOBAL OleDoc_GetObjectDescriptorData(
      LPOLEDOC            lpOleDoc,
      LPLINERANGE         lplrSel
);
LPMONIKER OleDoc_GetFullMoniker(LPOLEDOC lpOleDoc, DWORD dwAssign);
void OleDoc_GetExtent(LPOLEDOC lpOleDoc, LPSIZEL lpsizel);
void OleDoc_DocRenamedUpdate(LPOLEDOC lpOleDoc, LPMONIKER lpmkDoc);
void OleDoc_CopyCommand(LPOLEDOC lpSrcOleDoc);
void OleDoc_PasteCommand(LPOLEDOC lpOleDoc);
void OleDoc_PasteSpecialCommand(LPOLEDOC lpOleDoc);
LPOUTLINEDOC OleDoc_CreateDataTransferDoc(LPOLEDOC lpSrcOleDoc);
BOOL OleDoc_PasteFromData(
      LPOLEDOC            lpOleDoc,
      LPDATAOBJECT        lpSrcDataObj,
      BOOL                fLocalDataObj,
      BOOL                fLink
);
BOOL OleDoc_PasteFormatFromData(
      LPOLEDOC            lpOleDoc,
      CLIPFORMAT          cfFormat,
      LPDATAOBJECT        lpSrcDataObj,
      BOOL                fLocalDataObj,
      BOOL                fLink,
      BOOL                fDisplayAsIcon,
      HGLOBAL             hMetaPict,
      LPSIZEL             lpSizelInSrc
);
BOOL OleDoc_QueryPasteFromData(
      LPOLEDOC            lpOleDoc,
      LPDATAOBJECT        lpSrcDataObj,
      BOOL                fLink
);

#if defined( USE_DRAGDROP )

BOOL OleDoc_QueryDrag( LPOLEDOC lpOleDoc, int y );
BOOL OleDoc_QueryDrop (
   LPOLEDOC        lpOleDoc,
   DWORD           grfKeyState,
   POINTL          pointl,
   BOOL            fDragScroll,
   LPDWORD         lpdwEffect
);
DWORD OleDoc_DoDragDrop (LPOLEDOC lpSrcOleDoc);
BOOL OleDoc_DoDragScroll(LPOLEDOC lpOleDoc, POINTL pointl);

#endif  // USE_DRAGDROP

/* OleDoc::IUnknown methods (functions) */
STDMETHODIMP OleDoc_Unk_QueryInterface(
      LPUNKNOWN           lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
);
STDMETHODIMP_(ULONG) OleDoc_Unk_AddRef(LPUNKNOWN lpThis);
STDMETHODIMP_(ULONG) OleDoc_Unk_Release (LPUNKNOWN lpThis);

/* OleDoc::IPersistFile methods (functions) */
STDMETHODIMP OleDoc_PFile_QueryInterface(
      LPPERSISTFILE       lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
);
STDMETHODIMP_(ULONG) OleDoc_PFile_AddRef(LPPERSISTFILE lpThis);
STDMETHODIMP_(ULONG) OleDoc_PFile_Release (LPPERSISTFILE lpThis);
STDMETHODIMP OleDoc_PFile_GetClassID (
      LPPERSISTFILE       lpThis,
      CLSID FAR*          lpclsid
);
STDMETHODIMP  OleDoc_PFile_IsDirty(LPPERSISTFILE lpThis);
STDMETHODIMP OleDoc_PFile_Load (
      LPPERSISTFILE       lpThis,
      LPCOLESTR           lpszFileName,
      DWORD               grfMode
);
STDMETHODIMP OleDoc_PFile_Save (
      LPPERSISTFILE       lpThis,
      LPCOLESTR           lpszFileName,
      BOOL                fRemember
);
STDMETHODIMP OleDoc_PFile_SaveCompleted (
      LPPERSISTFILE       lpThis,
      LPCOLESTR           lpszFileName
);
STDMETHODIMP OleDoc_PFile_GetCurFile (
      LPPERSISTFILE   lpThis,
      LPOLESTR FAR*   lplpszFileName
);

/* OleDoc::IOleItemContainer methods (functions) */
STDMETHODIMP OleDoc_ItemCont_QueryInterface(
      LPOLEITEMCONTAINER  lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
);
STDMETHODIMP_(ULONG) OleDoc_ItemCont_AddRef(LPOLEITEMCONTAINER lpThis);
STDMETHODIMP_(ULONG) OleDoc_ItemCont_Release(LPOLEITEMCONTAINER lpThis);
STDMETHODIMP OleDoc_ItemCont_ParseDisplayName(
      LPOLEITEMCONTAINER  lpThis,
      LPBC                lpbc,
      LPOLESTR            lpszDisplayName,
      ULONG FAR*          lpchEaten,
      LPMONIKER FAR*      lplpmkOut
);

STDMETHODIMP OleDoc_ItemCont_EnumObjects(
      LPOLEITEMCONTAINER  lpThis,
      DWORD               grfFlags,
      LPENUMUNKNOWN FAR*  lplpenumUnknown
);
STDMETHODIMP OleDoc_ItemCont_LockContainer(
      LPOLEITEMCONTAINER  lpThis,
      BOOL                fLock
);
STDMETHODIMP OleDoc_ItemCont_GetObject(
      LPOLEITEMCONTAINER  lpThis,
      LPOLESTR            lpszItem,
      DWORD               dwSpeedNeeded,
      LPBINDCTX           lpbc,
      REFIID              riid,
      LPVOID FAR*         lplpvObject
);
STDMETHODIMP OleDoc_ItemCont_GetObjectStorage(
      LPOLEITEMCONTAINER  lpThis,
      LPOLESTR            lpszItem,
      LPBINDCTX           lpbc,
      REFIID              riid,
      LPVOID FAR*         lplpvStorage
);
STDMETHODIMP OleDoc_ItemCont_IsRunning(
      LPOLEITEMCONTAINER  lpThis,
      LPOLESTR            lpszItem
);

/* OleDoc::IPersistFile methods (functions) */
STDMETHODIMP OleDoc_ExtConn_QueryInterface(
      LPEXTERNALCONNECTION    lpThis,
      REFIID                  riid,
      LPVOID FAR*             lplpvObj
);
STDMETHODIMP_(ULONG) OleDoc_ExtConn_AddRef(LPEXTERNALCONNECTION lpThis);
STDMETHODIMP_(ULONG) OleDoc_ExtConn_Release (LPEXTERNALCONNECTION lpThis);
STDMETHODIMP_(DWORD) OleDoc_ExtConn_AddConnection(
      LPEXTERNALCONNECTION    lpThis,
      DWORD                   extconn,
      DWORD                   reserved
);
STDMETHODIMP_(DWORD) OleDoc_ExtConn_ReleaseConnection(
      LPEXTERNALCONNECTION    lpThis,
      DWORD                   extconn,
      DWORD                   reserved,
      BOOL                    fLastReleaseCloses
);

/* OleDoc::IDataObject methods (functions) */
STDMETHODIMP OleDoc_DataObj_QueryInterface (
      LPDATAOBJECT        lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
);
STDMETHODIMP_(ULONG) OleDoc_DataObj_AddRef(LPDATAOBJECT lpThis);
STDMETHODIMP_(ULONG) OleDoc_DataObj_Release (LPDATAOBJECT lpThis);
STDMETHODIMP OleDoc_DataObj_GetData (
      LPDATAOBJECT        lpThis,
      LPFORMATETC         lpFormatetc,
      LPSTGMEDIUM         lpMedium
);
STDMETHODIMP OleDoc_DataObj_GetDataHere (
      LPDATAOBJECT        lpThis,
      LPFORMATETC         lpFormatetc,
      LPSTGMEDIUM         lpMedium
);
STDMETHODIMP OleDoc_DataObj_QueryGetData (
      LPDATAOBJECT        lpThis,
      LPFORMATETC         lpFormatetc
);
STDMETHODIMP OleDoc_DataObj_GetCanonicalFormatEtc(
      LPDATAOBJECT        lpThis,
      LPFORMATETC         lpformatetc,
      LPFORMATETC         lpformatetcOut
);
STDMETHODIMP OleDoc_DataObj_SetData (
      LPDATAOBJECT    lpThis,
      LPFORMATETC     lpFormatetc,
      LPSTGMEDIUM     lpMedium,
      BOOL            fRelease
);
STDMETHODIMP OleDoc_DataObj_EnumFormatEtc(
      LPDATAOBJECT            lpThis,
      DWORD                   dwDirection,
      LPENUMFORMATETC FAR*    lplpenumFormatEtc
);
STDMETHODIMP OleDoc_DataObj_DAdvise(
      LPDATAOBJECT        lpThis,
      FORMATETC FAR*      lpFormatetc,
      DWORD               advf,
      LPADVISESINK        lpAdvSink,
      DWORD FAR*          lpdwConnection
);
STDMETHODIMP OleDoc_DataObj_DUnadvise(LPDATAOBJECT lpThis,DWORD dwConnection);
STDMETHODIMP OleDoc_DataObj_EnumDAdvise(
      LPDATAOBJECT        lpThis,
      LPENUMSTATDATA FAR* lplpenumAdvise
);

#ifdef USE_DRAGDROP

/* OleDoc::IDropSource methods (functions) */
STDMETHODIMP OleDoc_DropSource_QueryInterface(
   LPDROPSOURCE            lpThis,
   REFIID                  riid,
   LPVOID FAR*             lplpvObj
);
STDMETHODIMP_(ULONG) OleDoc_DropSource_AddRef( LPDROPSOURCE lpThis );
STDMETHODIMP_(ULONG) OleDoc_DropSource_Release ( LPDROPSOURCE lpThis);
STDMETHODIMP    OleDoc_DropSource_QueryContinueDrag (
   LPDROPSOURCE            lpThis,
   BOOL                    fEscapePressed,
   DWORD                   grfKeyState
);
STDMETHODIMP    OleDoc_DropSource_GiveFeedback (
   LPDROPSOURCE            lpThis,
   DWORD                   dwEffect
);

/* OleDoc::IDropTarget methods (functions) */
STDMETHODIMP OleDoc_DropTarget_QueryInterface(
      LPDROPTARGET        lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
);
STDMETHODIMP_(ULONG) OleDoc_DropTarget_AddRef(LPDROPTARGET lpThis);
STDMETHODIMP_(ULONG) OleDoc_DropTarget_Release ( LPDROPTARGET lpThis);
STDMETHODIMP    OleDoc_DropTarget_DragEnter (
   LPDROPTARGET            lpThis,
   LPDATAOBJECT            lpDataObj,
   DWORD                   grfKeyState,
   POINTL                  pointl,
   LPDWORD                 lpdwEffect
);
STDMETHODIMP    OleDoc_DropTarget_DragOver (
   LPDROPTARGET            lpThis,
   DWORD                   grfKeyState,
   POINTL                  pointl,
   LPDWORD                 lpdwEffect
);
STDMETHODIMP    OleDoc_DropTarget_DragLeave ( LPDROPTARGET lpThis);
STDMETHODIMP    OleDoc_DropTarget_Drop (
   LPDROPTARGET            lpThis,
   LPDATAOBJECT            lpDataObj,
   DWORD                   grfKeyState,
   POINTL                  pointl,
   LPDWORD                 lpdwEffect
);

#endif  // USE_DRAGDROP

/*************************************************************************
** class APPCLASSFACTORY
**  APPCLASSFACTORY implements the IClassFactory interface. it
**    instantiates document instances of the correct type depending on
**    how the application is compiled (either ServerDoc or ContainerDoc
**    instances). by implementing this
**    interface in a seperate interface from the App object itself, it
**    is easier to manage when the IClassFactory should be
**    registered/revoked. when the OleApp object is first initialized
**    in OleApp_InitInstance an instance of APPCLASSFACTORY is created
**    and registered (CoRegisterClassObject called). when the App
**    object gets destroyed (in OleApp_Destroy) this APPCLASSFACTORY is
**    revoked (CoRevokeClassObject called) and released. the simple
**    fact that the IClassFactory is registered does not on its own keep
**    the application alive.
*************************************************************************/

typedef struct tagAPPCLASSFACTORY {
   IClassFactoryVtbl FAR*  m_lpVtbl;
   UINT                    m_cRef;
#if defined( _DEBUG )
   LONG                    m_cSvrLock; // total count of LockServer locks
                              //  (for debugging purposes only)
#endif
 } APPCLASSFACTORY, FAR* LPAPPCLASSFACTORY;

/* PUBLIC FUNCTIONS */
LPCLASSFACTORY WINAPI AppClassFactory_Create(void);

/* interface IClassFactory implementation */
STDMETHODIMP AppClassFactory_QueryInterface(
      LPCLASSFACTORY lpThis, REFIID riid, LPVOID FAR* ppvObj);
STDMETHODIMP_(ULONG) AppClassFactory_AddRef(LPCLASSFACTORY lpThis);
STDMETHODIMP_(ULONG) AppClassFactory_Release(LPCLASSFACTORY lpThis);
STDMETHODIMP AppClassFactory_CreateInstance (
      LPCLASSFACTORY      lpThis,
      LPUNKNOWN           lpUnkOuter,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
);
STDMETHODIMP AppClassFactory_LockServer (
      LPCLASSFACTORY      lpThis,
      BOOL                fLock
);

/*************************************************************************
** class OLEAPP : OUTLINEAPP
**    OLEAPP is an extention to the base OUTLINEAPP object (structure)
**    that adds common OLE 2.0 functionality used by both the server
**    and container versions. This is an abstract class. You do not
**    instantiate an instance of OLEAPP directly but instead
**    instantiate one of its concrete subclasses: SERVERAPP or
**    CONTAINERAPP. There is one instance of an document application
**    object created per running application instance. This
**    object holds many fields that could otherwise be organized as
**    global variables. The OLEAPP class inherits all fields
**    from the OUTLINEAPP class. This inheritance is achieved by including a
**    member variable of type OUTLINEAPP as the first field in the OLEAPP
**    structure. Thus a pointer to a OLEAPP object can be cast to be
**    a pointer to a OUTLINEAPP object.
*************************************************************************/

typedef struct tagOLEAPP {
   OUTLINEAPP  m_OutlineApp;       // inherits all fields of OutlineApp
   ULONG       m_cRef;             // total ref count for app
   ULONG       m_cDoc;             // total count of open documents
   BOOL        m_fUserCtrl;        // does user control life-time of app?
   DWORD       m_dwRegClassFac;    // value returned by CoRegisterClassObject
   LPCLASSFACTORY m_lpClassFactory;// ptr to allocated ClassFactory instance
#if defined( USE_MSGFILTER )
   LPMESSAGEFILTER m_lpMsgFilter;  // ptr to allocated MsgFilter instance
   MSGPENDINGPROC m_lpfnMsgPending;// ptr to msg pending callback function
#endif  // USE_MSGFILTER
   BOOL        m_fOleInitialized;  // was OleInitialize called
   BOOL        m_fOleStdInit;      // was OleStdInitialize called
   UINT        m_cModalDlgActive;  // count of modal dialogs up; 0 = no dlg.
   UINT        m_cfEmbedSource;    // OLE 2.0 clipboard format
   UINT        m_cfEmbeddedObject; // OLE 2.0 clipboard format
   UINT        m_cfLinkSource;     // OLE 2.0 clipboard format
   UINT        m_cfObjectDescriptor; // OLE 2.0 clipboard format
   UINT        m_cfLinkSrcDescriptor; // OLE 2.0 clipboard format
   UINT        m_cfFileName;       // std Windows clipboard format
   FORMATETC   m_arrDocGetFmts[MAXNOFMTS];  // fmts offered by copy & GetData
   UINT        m_nDocGetFmts;      // no of fmtetc's for GetData

   OLEUIPASTEENTRY m_arrPasteEntries[MAXNOFMTS];   // input for PasteSpl.
   int         m_nPasteEntries;                    // input for PasteSpl.
   UINT        m_arrLinkTypes[MAXNOLINKTYPES];     // input for PasteSpl.
   int         m_nLinkTypes;                       // input for PasteSpl.

#if defined( USE_DRAGDROP )
   int m_nDragDelay;       // time delay (in msec) before drag should start
   int m_nDragMinDist;     // min. distance (radius) before drag should start
   int m_nScrollDelay;     // time delay (in msec) before scroll should start
   int m_nScrollInset;     // Border inset distance to start drag scroll
   int m_nScrollInterval;  // scroll interval time (in msec)

#if defined( IF_SPECIAL_DD_CURSORS_NEEDED )
   // This would be used if the app wanted to have custom drag/drop cursors
   HCURSOR     m_hcursorDragNone;
   HCURSOR     m_hcursorDragCopy;
   HCURSOR     m_hcursorDragLink;
#endif  // IF_SPECIAL_DD_CURSORS_NEEDED
#endif  // USE_DRAGDROP

#if defined( OLE_CNTR )
   HPALETTE    m_hStdPal;        // standard color palette for OLE
                             //  it is a good idea for containers
                             //  to use this standard palette
                             //  even if they do not use colors
                             //  themselves. this will allow
                             //  embedded object to get a good
                             //  distribution of colors when they
                             //  are being drawn by the container.
                             //
#endif

   struct CAppUnknownImpl {
      IUnknownVtbl FAR*       lpVtbl;
      LPOLEAPP                lpOleApp;
      int                     cRef;   // interface specific ref count.
   } m_Unknown;

} OLEAPP;

/* ServerApp methods (functions) */
BOOL OleApp_InitInstance(LPOLEAPP lpOleApp, HINSTANCE hInst, int nCmdShow);
void OleApp_TerminateApplication(LPOLEAPP lpOleApp);
BOOL OleApp_ParseCmdLine(LPOLEAPP lpOleApp, LPSTR lpszCmdLine, int nCmdShow);
void OleApp_Destroy(LPOLEAPP lpOleApp);
BOOL OleApp_CloseAllDocsAndExitCommand(
      LPOLEAPP            lpOleApp,
      BOOL                fForceEndSession
);
void OleApp_ShowWindow(LPOLEAPP lpOleApp, BOOL fGiveUserCtrl);
void OleApp_HideWindow(LPOLEAPP lpOleApp);
void OleApp_HideIfNoReasonToStayVisible(LPOLEAPP lpOleApp);
void OleApp_DocLockApp(LPOLEAPP lpOleApp);
void OleApp_DocUnlockApp(LPOLEAPP lpOleApp, LPOUTLINEDOC lpOutlineDoc);
HRESULT OleApp_Lock(LPOLEAPP lpOleApp, BOOL fLock, BOOL fLastUnlockReleases);
ULONG OleApp_AddRef(LPOLEAPP lpOleApp);
ULONG OleApp_Release (LPOLEAPP lpOleApp);
HRESULT OleApp_QueryInterface (
      LPOLEAPP                lpOleApp,
      REFIID                  riid,
      LPVOID FAR*             lplpUnk
);
void OleApp_RejectInComingCalls(LPOLEAPP lpOleApp, BOOL fReject);
void OleApp_DisableBusyDialogs(
      LPOLEAPP        lpOleApp,
      BOOL FAR*       lpfPrevBusyEnable,
      BOOL FAR*       lpfPrevNREnable
);
void OleApp_EnableBusyDialogs(
      LPOLEAPP        lpOleApp,
      BOOL            fPrevBusyEnable,
      BOOL            fPrevNREnable
);
void OleApp_PreModalDialog(LPOLEAPP lpOleApp, LPOLEDOC lpActiveOleDoc);
void OleApp_PostModalDialog(LPOLEAPP lpOleApp, LPOLEDOC lpActiveOleDoc);
BOOL OleApp_InitVtbls (LPOLEAPP lpOleApp);
void OleApp_InitMenu(
      LPOLEAPP                lpOleApp,
      LPOLEDOC                lpOleDoc,
      HMENU                   hMenu
);
void OleApp_UpdateEditMenu(
      LPOLEAPP                lpOleApp,
      LPOUTLINEDOC            lpOutlineDoc,
      HMENU                   hMenuEdit
);
BOOL OleApp_RegisterClassFactory(LPOLEAPP lpOleApp);
void OleApp_RevokeClassFactory(LPOLEAPP lpOleApp);

#if defined( USE_MSGFILTER )
BOOL OleApp_RegisterMessageFilter(LPOLEAPP lpOleApp);

void OleApp_RevokeMessageFilter(LPOLEAPP lpOleApp);
BOOL FAR PASCAL EXPORT MessagePendingProc(MSG FAR *lpMsg);
#endif  // USE_MSGFILTER

void OleApp_FlushClipboard(LPOLEAPP lpOleApp);
void OleApp_NewCommand(LPOLEAPP lpOleApp);
void OleApp_OpenCommand(LPOLEAPP lpOleApp);

#if defined( OLE_CNTR )
LRESULT OleApp_QueryNewPalette(LPOLEAPP lpOleApp);
#endif // OLE_CNTR

LRESULT wSelectPalette(HWND hWnd, HPALETTE hPal, BOOL fBackground);

/* OleApp::IUnknown methods (functions) */
STDMETHODIMP OleApp_Unk_QueryInterface(
      LPUNKNOWN           lpThis,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
);
STDMETHODIMP_(ULONG) OleApp_Unk_AddRef(LPUNKNOWN lpThis);
STDMETHODIMP_(ULONG) OleApp_Unk_Release (LPUNKNOWN lpThis);

/* Function prototypes in debug.c */
void InstallMessageFilterCommand(void);
void RejectIncomingCommand(void);

#if defined( OLE_SERVER )
#include "svroutl.h"
#endif
#if defined( OLE_CNTR )
#include "cntroutl.h"
#endif

#endif // _OLEOUTL_H_


OUTLAPP.C

/*************************************************************************
**
**    OLE 2 Sample Code
**
**    outlapp.c
**
**    This file contains OutlineApp functions.
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#include "outline.h"
#include "ansiapi.h"

#if defined( USE_STATUSBAR )
#include "status.h"
#endif

#if !defined( WIN32 )
#include <print.h>
#endif

OLEDBGDATA

extern LPOUTLINEAPP g_lpApp;
extern RECT g_rectNull;

// REVIEW: should use string resource for messages
OLECHAR ErrMsgClass[] = OLESTR("Can't register window classes!");
OLECHAR ErrMsgFrame[] = OLESTR("Can't create Frame Window!");
OLECHAR ErrMsgPrinting[] = OLESTR("Can't access printer!");

/* OutlineApp_InitApplication
** --------------------------
** Sets up the class data structures and does a one-time
**      initialization of the app by registering the window classes.
**      Returns TRUE if initialization is successful
**              FALSE otherwise
*/

BOOL OutlineApp_InitApplication(LPOUTLINEAPP lpOutlineApp, HINSTANCE hInst)
{
   WNDCLASS    wndclass;

   // REVIEW: should load msg strings from string resource

   /* Register the app frame class */
   wndclass.style = CS_HREDRAW | CS_VREDRAW | CS_BYTEALIGNWINDOW;
   wndclass.lpfnWndProc = AppWndProc;
   /* Extra storage for Class and Window objects */
   wndclass.cbClsExtra = 0;
   wndclass.cbWndExtra = sizeof(LPOUTLINEAPP); /* to store lpApp */
   wndclass.hInstance = hInst;
   wndclass.hIcon = LoadIcon(hInst, APPICON);
   wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
   /* Create brush for erasing background */
   wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
   wndclass.lpszMenuName = APPMENU;     /* Menu Name is App Name */
   wndclass.lpszClassName = APPWNDCLASS; /* Class Name is App Name */

   if(! RegisterClass(&wndclass)) {
      OutlineApp_ErrorMessage(lpOutlineApp, ErrMsgFrame);
      return FALSE;
   }

   /* Register the document window class */
   wndclass.style = CS_BYTEALIGNWINDOW;
   wndclass.lpfnWndProc = DocWndProc;
   wndclass.hIcon = NULL;
   wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
   wndclass.lpszMenuName = NULL;
   wndclass.lpszClassName = DOCWNDCLASS;
   wndclass.cbWndExtra = sizeof(LPOUTLINEDOC); /* to store lpDoc */
   if(! RegisterClass(&wndclass)) {
      OutlineApp_ErrorMessage(lpOutlineApp, ErrMsgClass);
      return FALSE;
   }

#if defined( USE_STATUSBAR )
   if (! RegisterStatusClass(hInst))
      return FALSE;
#endif

#if defined( USE_FRAMETOOLS )
   if (! FrameToolsRegisterClass(hInst)) {
      return FALSE;
   }
#endif

#if defined( INPLACE_SVR )
   // We should only register the hatch window class
   // in the UI Library once per application.
   RegisterHatchWindowClass(hInst);

#endif

   return TRUE;
}

/* OutlineApp_InitInstance
 * -----------------------
 *
 *  Performs a per-instance initialization of app.
 *  This method creates the frame window.
 *
 *  RETURNS    : TRUE  - If initialization was successful.
 *               FALSE - otherwise.
 */

BOOL OutlineApp_InitInstance(LPOUTLINEAPP lpOutlineApp, HINSTANCE hInst, int nCmdShow)
{
   char  szAnsiString[256];
   lpOutlineApp->m_hInst = hInst;

   /* create application's Frame window */
   W2A (APPNAME, szAnsiString, 256);
   lpOutlineApp->m_hWndApp = CreateWindow(
         APPWNDCLASS,             /* Window class name */
         szAnsiString,            /* initial Window title */
         WS_OVERLAPPEDWINDOW|
         WS_CLIPCHILDREN,
         CW_USEDEFAULT, 0,        /* Use default X, Y            */
         CW_USEDEFAULT, 0,        /* Use default X, Y            */
         HWND_DESKTOP,            /* Parent window's handle      */
         NULL,                    /* Default to Class Menu       */
         hInst,                   /* Instance of window          */
         NULL                     /* Create struct for WM_CREATE */
   );

   if(! lpOutlineApp->m_hWndApp) {
      // REVIEW: should load string from string resource
      OutlineApp_ErrorMessage(lpOutlineApp, ErrMsgFrame);
      return FALSE;
   }

   SetWindowLong(lpOutlineApp->m_hWndApp, 0, (LONG) lpOutlineApp);

   /* defer creating the user's SDI document until we parse the cmd line. */
   lpOutlineApp->m_lpDoc = NULL;

   /* Initialize clipboard.
   */
   lpOutlineApp->m_lpClipboardDoc = NULL;
   W2A (OUTLINEDOCFORMAT, szAnsiString, 256);
   if(!(lpOutlineApp->m_cfOutline = RegisterClipboardFormat(szAnsiString))) {
      // REVIEW: should load string from string resource
      OutlineApp_ErrorMessage(lpOutlineApp, OLESTR("Can't register clipboard format!"));
      return FALSE;
   }

   /* init the standard font to be used for drawing/printing text
   *       request a Roman style True Type font of the desired size
   */
   lpOutlineApp->m_hStdFont = CreateFont(
         -DEFFONTSIZE,
         0,0,0,0,0,0,0,0,
         OUT_TT_PRECIS,      // use TrueType
         CLIP_DEFAULT_PRECIS,
         DEFAULT_QUALITY,
         VARIABLE_PITCH | FF_ROMAN,
         DEFFONTFACE
   );

   // Load special cursor for selection of Lines in ListBox.
   lpOutlineApp->m_hcursorSelCur = LoadCursor ( hInst, "SelCur" );

   /* init the Print Dialog structure */
   _fmemset((LPVOID)&lpOutlineApp->m_PrintDlg,0,sizeof(PRINTDLG));
   lpOutlineApp->m_PrintDlg.lStructSize = sizeof(PRINTDLG);
   lpOutlineApp->m_PrintDlg.hDevMode = NULL;
   lpOutlineApp->m_PrintDlg.hDevNames = NULL;
   lpOutlineApp->m_PrintDlg.Flags = PD_RETURNDC | PD_NOSELECTION | PD_NOPAGENUMS |
               PD_HIDEPRINTTOFILE;
   lpOutlineApp->m_PrintDlg.nCopies = 1;
   lpOutlineApp->m_PrintDlg.hwndOwner = lpOutlineApp->m_hWndApp;

#if defined( USE_STATUSBAR )
   lpOutlineApp->m_hWndStatusBar = CreateStatusWindow(lpOutlineApp->m_hWndApp, hInst);
   if (! lpOutlineApp->m_hWndStatusBar)
      return FALSE;

   lpOutlineApp->m_hMenuApp = GetMenu(lpOutlineApp->m_hWndApp);

   /* setup status messages for the application menus */
   {
      HMENU hMenuFile = GetSubMenu(lpOutlineApp->m_hMenuApp, 0);
      HMENU hMenuEdit = GetSubMenu(lpOutlineApp->m_hMenuApp, 1);
      HMENU hMenuOutline = GetSubMenu(lpOutlineApp->m_hMenuApp, 2);
      HMENU hMenuLine = GetSubMenu(lpOutlineApp->m_hMenuApp, 3);
      HMENU hMenuName = GetSubMenu(lpOutlineApp->m_hMenuApp, 4);
      HMENU hMenuOptions = GetSubMenu(lpOutlineApp->m_hMenuApp, 5);
      HMENU hMenuDebug = GetSubMenu(lpOutlineApp->m_hMenuApp, 6);
      HMENU hMenuHelp = GetSubMenu(lpOutlineApp->m_hMenuApp, 7);
      HMENU hMenuSys = GetSystemMenu(lpOutlineApp->m_hWndApp, FALSE);

      AssignPopupMessage(hMenuFile, "Create, open, save, print outlines or quit application");
      AssignPopupMessage(hMenuEdit, "Cut, copy, paste or clear selection");
      AssignPopupMessage(hMenuOutline, "Set zoom and margins");
      AssignPopupMessage(hMenuLine, "Create, edit, and indent lines");
      AssignPopupMessage(hMenuName, "Create, edit, delete and goto names");
      AssignPopupMessage(hMenuOptions, "Modify tools, row/col headings, display options");
      AssignPopupMessage(hMenuDebug, "Set debug trace level and other debug options");
      AssignPopupMessage(hMenuHelp, "Get help on using the application");
      AssignPopupMessage(hMenuSys,"Move, size or close application window");
   }
#endif

#if defined ( USE_FRAMETOOLS ) || defined ( INPLACE_CNTR )
   lpOutlineApp->m_FrameToolWidths = g_rectNull;
#endif  // USE_FRAMETOOLS || INPLACE_CNTR

#if defined( USE_FRAMETOOLS )
   if (! FrameTools_Init(&lpOutlineApp->m_frametools,
         lpOutlineApp->m_hWndApp, lpOutlineApp->m_hInst))
      return FALSE;
#endif

#if defined( OLE_VERSION )

   /* NOTE: perform initialization required for OLE */
   if (! OleApp_InitInstance((LPOLEAPP)lpOutlineApp, hInst, nCmdShow))
      return FALSE;
#else
   /* NOTE: Although no OLE call is made in the base outline,
   **    OLE memory allocator is used and thus CoInitialize() needs to
   **    be called.
   */
   {
      HRESULT hrErr;

      hrErr = CoInitialize(NULL);
      if (hrErr != NOERROR) {
         OutlineApp_ErrorMessage(lpOutlineApp,
               OLESTR("CoInitialize initialization failed!"));
         return FALSE;
      }
   }
#endif

   return TRUE;
}

/* OutlineApp_ParseCmdLine
 * -----------------------
 *
 * Parse the command line for any execution flags/arguments.
 */
BOOL OutlineApp_ParseCmdLine(LPOUTLINEAPP lpOutlineApp, LPSTR lpszCmdLine, int nCmdShow)
{

#if defined( OLE_VERSION )
   // Call OLE version of this function instead
   return OleApp_ParseCmdLine((LPOLEAPP)lpOutlineApp,lpszCmdLine,nCmdShow);

#else

   BOOL fStatus = TRUE;
   char szFileName[256];   /* buffer for filename in command line */
   OLECHAR szTempW[256];   /* buffer for unicode/ansi strings*/

   szFileName[0] = '\0';
   ParseCmdLine(lpszCmdLine, NULL, (LPSTR)szFileName);

   if(*szFileName) {
      // allocate a new document
      lpOutlineApp->m_lpDoc = OutlineApp_CreateDoc(lpOutlineApp, FALSE);
      if (! lpOutlineApp->m_lpDoc) goto error;

      // open the specified file
      A2W(szFileName, szTempW, 256);
      if (! OutlineDoc_LoadFromFile(lpOutlineApp->m_lpDoc, szTempW))
         goto error;
   } else {
      // create a new document
      lpOutlineApp->m_lpDoc = OutlineApp_CreateDoc(lpOutlineApp, FALSE);
      if (! lpOutlineApp->m_lpDoc) goto error;

      // set the doc to an (Untitled) doc.
      if (! OutlineDoc_InitNewFile(lpOutlineApp->m_lpDoc))
         goto error;
   }

   // position and size the new doc window
   OutlineApp_ResizeWindows(lpOutlineApp);
   OutlineDoc_ShowWindow(lpOutlineApp->m_lpDoc);

   // show main app window
   ShowWindow(lpOutlineApp->m_hWndApp, nCmdShow);
   UpdateWindow(lpOutlineApp->m_hWndApp);

   return TRUE;

error:
   // REVIEW: should load string from string resource
   OutlineApp_ErrorMessage(lpOutlineApp, OLESTR("Could not create new document"));

   if (lpOutlineApp->m_lpDoc) {
      OutlineDoc_Destroy(lpOutlineApp->m_lpDoc);
      lpOutlineApp->m_lpDoc = NULL;
   }

   return FALSE;

#endif
}

/* OutlineApp_InitMenu
 * -------------------
 *
 *      Enable or Disable menu items depending on the state of
 * the appliation
 */
void OutlineApp_InitMenu(LPOUTLINEAPP lpOutlineApp, LPOUTLINEDOC lpOutlineDoc, HMENU hMenu)
{
   WORD status;
   static UINT     uCurrentZoom = (UINT)-1;
   static UINT     uCurrentMargin = (UINT)-1;
   static UINT     uBBState = (UINT)-1;
   static UINT     uFBState = (UINT)-1;

   if (!lpOutlineApp || !lpOutlineDoc || !hMenu)
      return;

   EnableMenuItem(lpOutlineApp->m_hMenuApp, IDM_E_UNDO, MF_GRAYED);

   status = (WORD)(OutlineDoc_GetLineCount(lpOutlineDoc) ? MF_ENABLED : MF_GRAYED);

   EnableMenuItem(lpOutlineApp->m_hMenuApp, IDM_E_CUT ,status);
   EnableMenuItem(lpOutlineApp->m_hMenuApp, IDM_E_COPY ,status);
   EnableMenuItem(lpOutlineApp->m_hMenuApp, IDM_E_CLEAR ,status);
   EnableMenuItem(lpOutlineApp->m_hMenuApp, IDM_E_SELECTALL ,status);

   EnableMenuItem(lpOutlineApp->m_hMenuApp, IDM_L_EDITLINE ,status);
   EnableMenuItem(lpOutlineApp->m_hMenuApp, IDM_L_INDENTLINE ,status);
   EnableMenuItem(lpOutlineApp->m_hMenuApp, IDM_L_UNINDENTLINE ,status);
   EnableMenuItem(lpOutlineApp->m_hMenuApp, IDM_L_SETLINEHEIGHT ,status);

   EnableMenuItem(lpOutlineApp->m_hMenuApp, IDM_N_DEFINENAME ,status);

   status = (WORD)(OutlineDoc_GetNameCount(lpOutlineDoc) ? MF_ENABLED : MF_GRAYED);

   EnableMenuItem(lpOutlineApp->m_hMenuApp, IDM_N_GOTONAME, status);

   if (uCurrentZoom != (UINT)-1)
      CheckMenuItem(lpOutlineApp->m_hMenuApp, uCurrentZoom, MF_UNCHECKED);
   uCurrentZoom = OutlineDoc_GetCurrentZoomMenuCheck(lpOutlineDoc);
   CheckMenuItem(lpOutlineApp->m_hMenuApp, uCurrentZoom, MF_CHECKED);

   if (uCurrentMargin != (UINT)-1)
      CheckMenuItem(lpOutlineApp->m_hMenuApp, uCurrentMargin, MF_UNCHECKED);
   uCurrentMargin = OutlineDoc_GetCurrentMarginMenuCheck(lpOutlineDoc);
   CheckMenuItem(lpOutlineApp->m_hMenuApp, uCurrentMargin, MF_CHECKED);

#if defined( USE_FRAMETOOLS )
   if (uBBState != (UINT)-1)
      CheckMenuItem(lpOutlineApp->m_hMenuApp, uBBState, MF_UNCHECKED);
   if (lpOutlineDoc->m_lpFrameTools) {
      switch (FrameTools_BB_GetState(lpOutlineDoc->m_lpFrameTools)) {
         case BARSTATE_TOP:
            uBBState = IDM_O_BB_TOP;
            break;
         case BARSTATE_BOTTOM:
            uBBState = IDM_O_BB_BOTTOM;
            break;
         case BARSTATE_POPUP:
            uBBState = IDM_O_BB_POPUP;
            break;
         case BARSTATE_HIDE:
            uBBState = IDM_O_BB_HIDE;
            break;
      }
      CheckMenuItem(lpOutlineApp->m_hMenuApp, uBBState, MF_CHECKED);
   }

   if (uFBState != (UINT)-1)
      CheckMenuItem(lpOutlineApp->m_hMenuApp, uFBState, MF_UNCHECKED);
   if (lpOutlineDoc->m_lpFrameTools) {
      switch (FrameTools_FB_GetState(lpOutlineDoc->m_lpFrameTools)) {
         case BARSTATE_TOP:
            uFBState = IDM_O_FB_TOP;
            break;
         case BARSTATE_BOTTOM:
            uFBState = IDM_O_FB_BOTTOM;
            break;
         case BARSTATE_POPUP:
            uFBState = IDM_O_FB_POPUP;
            break;
      }
      CheckMenuItem(lpOutlineApp->m_hMenuApp, uFBState, MF_CHECKED);
   }
#endif  // USE_FRAMETOOLS

#if defined( OLE_VERSION )
   /* NOTE: perform OLE specific menu initialization.
   **    the OLE versions use the OleGetClipboard mechanism for
   **    clipboard handling. thus, they determine if the Paste and
   **    PasteSpecial commands should be enabled in an OLE specific
   **    manner.
   **    (Container only) build the OLE object verb menu if necessary.
   */
   OleApp_InitMenu(
         (LPOLEAPP)lpOutlineApp,
         (LPOLEDOC)lpOutlineDoc,
         lpOutlineApp->m_hMenuApp
   );

   /* NOTE: To avoid the overhead of initializing the Edit menu,
   **    we do it only when it is popped up. Thus we just set a flag
   **    in the OleDoc saying that the Edit menu needs to be updated
   **    but we don't do it immediately
   */
   OleDoc_SetUpdateEditMenuFlag((LPOLEDOC)lpOutlineDoc, TRUE);

#else
   // Base Outline version uses standard Windows clipboard handling
   if(IsClipboardFormatAvailable(lpOutlineApp->m_cfOutline) ||
      IsClipboardFormatAvailable(CF_TEXT))
      status = MF_ENABLED;
   else
      status = MF_GRAYED;
   EnableMenuItem(lpOutlineApp->m_hMenuApp, IDM_E_PASTE, status);

#endif

#if defined( USE_FRAMETOOLS )
   if (! OutlineDoc_IsEditFocusInFormulaBar(lpOutlineDoc)) {
      EnableMenuItem(lpOutlineApp->m_hMenuApp, IDM_L_ADDLINE, MF_GRAYED);
      EnableMenuItem(lpOutlineApp->m_hMenuApp, IDM_L_EDITLINE, MF_GRAYED);
   }
   else
      EnableMenuItem(lpOutlineApp->m_hMenuApp, IDM_L_ADDLINE, MF_ENABLED);

#endif      // USE_FRAMETOOLS

}

/* OutlineApp_GetWindow
 * --------------------
 *
 *      Get the window handle of the application frame.
 */
HWND OutlineApp_GetWindow(LPOUTLINEAPP lpOutlineApp)
{
   if (!lpOutlineApp)
      return NULL;

   return lpOutlineApp->m_hWndApp;
}

/* OutlineApp_GetFrameWindow
** -------------------------
**    Gets the current frame window to use as a parent to any dialogs
**    this app uses.
**
**    NOTE: normally this is simply the main hWnd of the app. but,
**    if the app is currently supporting an in-place server document,
**    then the frame window of the top in-place container must be used.
*/
HWND OutlineApp_GetFrameWindow(LPOUTLINEAPP lpOutlineApp)
{
   HWND hWndApp = OutlineApp_GetWindow(lpOutlineApp);

#if defined( INPLACE_SVR )
   LPSERVERDOC lpServerDoc =
         (LPSERVERDOC)OutlineApp_GetActiveDoc(lpOutlineApp);
   if (lpServerDoc && lpServerDoc->m_fUIActive)
      return lpServerDoc->m_lpIPData->frameInfo.hwndFrame;
#endif

   return hWndApp;
}

/* OutlineApp_GetInstance
 * ----------------------
 *
 *      Get the process instance of the application.
 */
HINSTANCE OutlineApp_GetInstance(LPOUTLINEAPP lpOutlineApp)
{
   if (!lpOutlineApp)
      return NULL;

   return lpOutlineApp->m_hInst;
}

/* OutlineApp_CreateDoc
 * --------------------
 *
 * Allocate a new document of the appropriate type.
 *  OutlineApp  --> creates OutlineDoc type documents
 *
 *      Returns lpOutlineDoc for successful, NULL if error.
 */
LPOUTLINEDOC OutlineApp_CreateDoc(
      LPOUTLINEAPP    lpOutlineApp,
      BOOL            fDataTransferDoc
)
{
   LPOUTLINEDOC lpOutlineDoc;

   OLEDBG_BEGIN3("OutlineApp_CreateDoc\r\n")

#if defined( OLE_SERVER )
   lpOutlineDoc = (LPOUTLINEDOC)New((DWORD)sizeof(SERVERDOC));
   _fmemset(lpOutlineDoc, 0, sizeof(SERVERDOC));
#endif
#if defined( OLE_CNTR )
   lpOutlineDoc = (LPOUTLINEDOC)New((DWORD)sizeof(CONTAINERDOC));
   _fmemset(lpOutlineDoc, 0, sizeof(CONTAINERDOC));
#endif
#if !defined( OLE_VERSION )
   lpOutlineDoc = (LPOUTLINEDOC)New((DWORD)sizeof(OUTLINEDOC));
   _fmemset(lpOutlineDoc, 0, sizeof(OUTLINEDOC));
#endif

   OleDbgAssertSz(lpOutlineDoc != NULL, "Error allocating OutlineDoc");
   if (lpOutlineDoc == NULL)
      return NULL;

   // initialize new document
   if (! OutlineDoc_Init(lpOutlineDoc, fDataTransferDoc))
      goto error;

   OLEDBG_END3
   return lpOutlineDoc;

error:
   if (lpOutlineDoc)
      Delete(lpOutlineDoc);

   OLEDBG_END3
   return NULL;
}

/* OutlineApp_CreateName
 * ---------------------
 *
 * Allocate a new Name of the appropriate type.
 *  OutlineApp --> creates standard OutlineName type names.
 *  ServerApp  --> creates enhanced SeverName type names.
 *
 *      Returns lpOutlineName for successful, NULL if error.
 */
LPOUTLINENAME OutlineApp_CreateName(LPOUTLINEAPP lpOutlineApp)
{
   LPOUTLINENAME lpOutlineName;

#if defined( OLE_SERVER )
   lpOutlineName = (LPOUTLINENAME)New((DWORD)sizeof(SERVERNAME));
#else
   lpOutlineName = (LPOUTLINENAME)New((DWORD)sizeof(OUTLINENAME));
#endif

   OleDbgAssertSz(lpOutlineName != NULL, "Error allocating Name");
   if (lpOutlineName == NULL)
      return NULL;

#if defined( OLE_SERVER )
   _fmemset((LPVOID)lpOutlineName,0,sizeof(SERVERNAME));
#else
   _fmemset((LPVOID)lpOutlineName,0,sizeof(OUTLINENAME));
#endif

   return lpOutlineName;
}

/* OutlineApp_DocUnlockApp
** -----------------------
**    Forget all references to a closed document.
*/
void OutlineApp_DocUnlockApp(LPOUTLINEAPP lpOutlineApp, LPOUTLINEDOC lpOutlineDoc)
{
   /* forget pointers to destroyed document */
   if (lpOutlineApp->m_lpDoc == lpOutlineDoc)
      lpOutlineApp->m_lpDoc = NULL;
   else if (lpOutlineApp->m_lpClipboardDoc == lpOutlineDoc)
      lpOutlineApp->m_lpClipboardDoc = NULL;

#if defined( OLE_VERSION )
   /* NOTE: when there are no open documents and the app is not
   **    under the control of the user then revoke our ClassFactory to
   **    enable the app to shut down.
   **
   **    NOTE: data transfer documents (non-user documents) do NOT
   **    hold the app alive. therefore they do not Lock the app.
   */
   if (! lpOutlineDoc->m_fDataTransferDoc)
      OleApp_DocUnlockApp((LPOLEAPP)lpOutlineApp, lpOutlineDoc);
#endif
}

/* OutlineApp_NewCommand
 * ---------------------
 *
 *  Start a new untitled document (File.New command).
 */
void OutlineApp_NewCommand(LPOUTLINEAPP lpOutlineApp)
{
#if defined( OLE_VERSION )
   // Call OLE version of this function instead
   OleApp_NewCommand((LPOLEAPP)lpOutlineApp);

#else

   LPOUTLINEDOC lpOutlineDoc = lpOutlineApp->m_lpDoc;

   if (! OutlineDoc_Close(lpOutlineDoc, OLECLOSE_PROMPTSAVE))
      return;

   OleDbgAssertSz(lpOutlineApp->m_lpDoc==NULL,"Closed doc NOT properly destroyed");

   lpOutlineApp->m_lpDoc = OutlineApp_CreateDoc(lpOutlineApp, FALSE);
   if (! lpOutlineApp->m_lpDoc) goto error;

   // set the doc to an (Untitled) doc.
   if (! OutlineDoc_InitNewFile(lpOutlineApp->m_lpDoc))
      goto error;

   // position and size the new doc window
   OutlineApp_ResizeWindows(lpOutlineApp);
   OutlineDoc_ShowWindow(lpOutlineApp->m_lpDoc); // calls OleDoc_Lock

   return;

error:
   // REVIEW: should load string from string resource
   OutlineApp_ErrorMessage(lpOutlineApp, OLESTR("Could not create new document"));

   if (lpOutlineApp->m_lpDoc) {
      OutlineDoc_Destroy(lpOutlineApp->m_lpDoc);
      lpOutlineApp->m_lpDoc = NULL;
   }

   return;

#endif
}

/* OutlineApp_OpenCommand
 * ----------------------
 *
 *  Load a document from file (File.Open command).
 */
void OutlineApp_OpenCommand(LPOUTLINEAPP lpOutlineApp)
{
#if defined( OLE_VERSION )
   // Call OLE version of this function instead
   OleApp_OpenCommand((LPOLEAPP)lpOutlineApp);

#else

   OPENFILENAME ofn;
   char szFilter[]=APPFILENAMEFILTER;
   char szFileName[256];
   OLECHAR szUniFileName[256];
   UINT i;
   DWORD dwSaveOption = OLECLOSE_PROMPTSAVE;
   BOOL fStatus = TRUE;

   if (! OutlineDoc_CheckSaveChanges(lpOutlineApp->m_lpDoc, &dwSaveOption))
      return;           // abort opening new doc

   for(i=0; szFilter[i]; i++)
      if(szFilter[i]=='|') szFilter[i]='\0';

   _fmemset((LPOPENFILENAME)&ofn,0,sizeof(OPENFILENAME));

   szFileName[0]='\0';

   ofn.lStructSize=sizeof(OPENFILENAME);
   ofn.hwndOwner=lpOutlineApp->m_hWndApp;
   ofn.lpstrFilter=(LPSTR)szFilter;
   ofn.lpstrFile=(LPSTR)szFileName;
   ofn.nMaxFile=256;
   ofn.Flags=OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
   ofn.lpstrDefExt=DEFEXTENSION;

   if(! GetOpenFileName((LPOPENFILENAME)&ofn))
      return;         // user canceled file open dialog

   OutlineDoc_Close(lpOutlineApp->m_lpDoc, OLECLOSE_NOSAVE);
   OleDbgAssertSz(lpOutlineApp->m_lpDoc==NULL,"Closed doc NOT properly destroyed");

   lpOutlineApp->m_lpDoc = OutlineApp_CreateDoc(lpOutlineApp, FALSE);
   if (! lpOutlineApp->m_lpDoc) goto error;

   A2W (szFileName, szUniFileName, 256);
   fStatus=OutlineDoc_LoadFromFile(lpOutlineApp->m_lpDoc, /*(LPOLESTR)*/szUniFileName);

   if (! fStatus) {
      // loading the doc failed; create an untitled instead
      OutlineDoc_Destroy(lpOutlineApp->m_lpDoc);  // destroy unused doc
      lpOutlineApp->m_lpDoc = OutlineApp_CreateDoc(lpOutlineApp, FALSE);
      if (! lpOutlineApp->m_lpDoc) goto error;
      if (! OutlineDoc_InitNewFile(lpOutlineApp->m_lpDoc))
         goto error;
   }

   // position and size the new doc window
   OutlineApp_ResizeWindows(lpOutlineApp);
   OutlineDoc_ShowWindow(lpOutlineApp->m_lpDoc);

   return;

error:
   // REVIEW: should load string from string resource
   OutlineApp_ErrorMessage(lpOutlineApp, OLESTR("Could not create new document"));

   if (lpOutlineApp->m_lpDoc) {
      OutlineDoc_Destroy(lpOutlineApp->m_lpDoc);
      lpOutlineApp->m_lpDoc = NULL;
   }

   return;

#endif
}

/* OutlineApp_PrintCommand
 * -----------------------
 *
 *      Print the document
 */
void OutlineApp_PrintCommand(LPOUTLINEAPP lpOutlineApp)
{
   LPOUTLINEDOC    lpOutlineDoc = lpOutlineApp->m_lpDoc;
   HDC             hDC=NULL;
   BOOL            fMustDeleteDC = FALSE;
   BOOL            fStatus;

#if defined( OLE_VERSION )
   OleApp_PreModalDialog(
         (LPOLEAPP)lpOutlineApp, (LPOLEDOC)lpOutlineApp->m_lpDoc);
#endif

   fStatus = PrintDlg((LPPRINTDLG)&lpOutlineApp->m_PrintDlg);

#if defined( OLE_VERSION )
   OleApp_PostModalDialog(
         (LPOLEAPP)lpOutlineApp, (LPOLEDOC)lpOutlineApp->m_lpDoc);
#endif

   if (!fStatus) {
      if (!CommDlgExtendedError()) {      // Cancel button pressed
         return;
      }
   }
   else {
      hDC = OutlineApp_GetPrinterDC(lpOutlineApp);
      if (hDC) {

#if defined( OLE_VERSION )
         /* NOTE: while we are printing we do NOT want to
         **    receive any OnDataChange notifications or other OLE
         **    interface calls which could disturb the printing of
         **    the document. we will temporarily reply
         **    SERVERCALL_RETRYLATER
         */
         OleApp_RejectInComingCalls((LPOLEAPP)lpOutlineApp, TRUE);
#endif

         OutlineDoc_Print(lpOutlineDoc, hDC);
         DeleteDC(hDC);

#if defined( OLE_VERSION )
         // re-enable LRPC calls
         OleApp_RejectInComingCalls((LPOLEAPP)lpOutlineApp, FALSE);
#endif

         return;                         // Printing completed
      }
   }

   // REVIEW: should load string from string resource
   OutlineApp_ErrorMessage(lpOutlineApp, ErrMsgPrinting);
}

/* OutlineApp_PrinterSetupCommand
 * ------------------------------
 *
 *      Setup a different printer for printing
 */
void OutlineApp_PrinterSetupCommand(LPOUTLINEAPP lpOutlineApp)
{
   DWORD FlagSave;

   FlagSave = lpOutlineApp->m_PrintDlg.Flags;
   lpOutlineApp->m_PrintDlg.Flags |= PD_PRINTSETUP;

#if defined( OLE_VERSION )
   OleApp_PreModalDialog(
         (LPOLEAPP)lpOutlineApp, (LPOLEDOC)lpOutlineApp->m_lpDoc);
#endif

   PrintDlg((LPPRINTDLG)&lpOutlineApp->m_PrintDlg);

#if defined( OLE_VERSION )
   OleApp_PostModalDialog(
         (LPOLEAPP)lpOutlineApp, (LPOLEDOC)lpOutlineApp->m_lpDoc);
#endif

   lpOutlineApp->m_PrintDlg.Flags = FlagSave;
}

/*
 *  FUNCTION   : OutlineApp_GetPrinterDC ()
 *
 *  PURPOSE    : Creates a printer display context for the printer
 *
 *  RETURNS    : HDC   - A handle to printer DC.
 */
HDC OutlineApp_GetPrinterDC(LPOUTLINEAPP lpApp)
{

   HDC         hDC;
   LPDEVMODE   lpDevMode = NULL;
   LPDEVNAMES  lpDevNames;
   LPSTR       lpszDriverName;
   LPSTR       lpszDeviceName;
   LPSTR       lpszPortName;

   if(lpApp->m_PrintDlg.hDC) {
      hDC = lpApp->m_PrintDlg.hDC;
   } else {
      if(! lpApp->m_PrintDlg.hDevNames)
         return(NULL);
      lpDevNames = (LPDEVNAMES)GlobalLock(lpApp->m_PrintDlg.hDevNames);
      lpszDriverName = (LPSTR)lpDevNames + lpDevNames->wDriverOffset;
      lpszDeviceName = (LPSTR)lpDevNames + lpDevNames->wDeviceOffset;
      lpszPortName   = (LPSTR)lpDevNames + lpDevNames->wOutputOffset;
      GlobalUnlock(lpApp->m_PrintDlg.hDevNames);

      if(lpApp->m_PrintDlg.hDevMode)
         lpDevMode = (LPDEVMODE)GlobalLock(lpApp->m_PrintDlg.hDevMode);
#if defined( WIN32 )
      hDC = CreateDC(
            lpszDriverName,
            lpszDeviceName,
            lpszPortName,
            (CONST DEVMODE FAR*)lpDevMode);
#else
      hDC = CreateDC(
            lpszDriverName,
            lpszDeviceName,
            lpszPortName,
            (LPSTR)lpDevMode);
#endif

      if(lpApp->m_PrintDlg.hDevMode && lpDevMode)
         GlobalUnlock(lpApp->m_PrintDlg.hDevMode);
   }

   return(hDC);
}

/* OutlineApp_SaveCommand
 * ----------------------
 *
 *      Save the document with same name. If no name exists, prompt the user
 *      for a name (via SaveAsCommand)
 *
 *  Parameters:
 *
 *  Returns:
 *      TRUE    if succesfully
 *      FALSE   if failed or aborted
 */
BOOL OutlineApp_SaveCommand(LPOUTLINEAPP lpOutlineApp)
{
   LPOUTLINEDOC lpOutlineDoc = OutlineApp_GetActiveDoc(lpOutlineApp);

   if(lpOutlineDoc->m_docInitType == DOCTYPE_NEW)  /* file with no name */
      return OutlineApp_SaveAsCommand(lpOutlineApp);

   if(OutlineDoc_IsModified(lpOutlineDoc)) {

#if defined( OLE_SERVER )

      if (lpOutlineDoc->m_docInitType == DOCTYPE_EMBEDDED) {
         LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOutlineDoc;
         HRESULT hrErr;

         /* NOTE: if the document is an embedded object, then
         **    the "File.Save" command is changed to "File.Update".
         **    in order to update our container, we must ask our
         **    container to save us.
         */
         OleDbgAssert(lpServerDoc->m_lpOleClientSite != NULL);
         OLEDBG_BEGIN2("IOleClientSite::SaveObject called\r\n")
         hrErr = lpServerDoc->m_lpOleClientSite->lpVtbl->SaveObject(
               lpServerDoc->m_lpOleClientSite
         );
         OLEDBG_END2

         if (hrErr != NOERROR) {
            OleDbgOutHResult("IOleClientSite::SaveObject returned",hrErr);
            return FALSE;
         }
      } else
         // document is file-base user document, save it to its file.

#endif      // OLE_SERVER

      (void)OutlineDoc_SaveToFile(
            lpOutlineDoc,
            NULL,
            lpOutlineDoc->m_cfSaveFormat,
            TRUE
      );
   }

   return TRUE;
}

/* OutlineApp_SaveAsCommand
 * ------------------------
 *
 *      Save the document as another name
 *
 *  Parameters:
 *
 *  Returns:
 *      TRUE    if saved successful
 *      FALSE   if failed or aborted
 */
BOOL OutlineApp_SaveAsCommand(LPOUTLINEAPP lpOutlineApp)

{
   LPOUTLINEDOC lpOutlineDoc = lpOutlineApp->m_lpDoc;
   OPENFILENAME ofn;
   char szFilter[]=APPFILENAMEFILTER;
   char szFileName[256]="";
   OLECHAR szUniFileName[256];
   int i;
   UINT uFormat;
   BOOL fNoError = TRUE;
   BOOL fRemember = TRUE;
   BOOL fStatus;

   for(i=0; szFilter[i]; i++)
      if(szFilter[i]=='|') szFilter[i]='\0';

   _fmemset((LPOPENFILENAME)&ofn,0,sizeof(OPENFILENAME));

   ofn.lStructSize=sizeof(OPENFILENAME);
   ofn.hwndOwner=lpOutlineDoc->m_hWndDoc;
   ofn.lpstrFilter=(LPSTR)szFilter;
   ofn.lpstrFile=(LPSTR)szFileName;
   ofn.nMaxFile=256;

   ofn.Flags=OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY;
   ofn.lpstrDefExt=DEFEXTENSION;

#if defined( OLE_VERSION )
   OleApp_PreModalDialog(
         (LPOLEAPP)lpOutlineApp, (LPOLEDOC)lpOutlineApp->m_lpDoc);
#endif

   fStatus = GetSaveFileName((LPOPENFILENAME)&ofn);

#if defined( OLE_VERSION )
   OleApp_PostModalDialog(
         (LPOLEAPP)lpOutlineApp, (LPOLEDOC)lpOutlineApp->m_lpDoc);
#endif

   if (fStatus) {

#if defined( OLE_CNTR )
      // determine which file type the user selected.
      switch (ofn.nFilterIndex) {
         case 1:
            uFormat = ((LPCONTAINERAPP)lpOutlineApp)->m_cfCntrOutl;
            break;
         case 2:
            uFormat = lpOutlineApp->m_cfOutline;
            break;
         default:
            uFormat = ((LPCONTAINERAPP)lpOutlineApp)->m_cfCntrOutl;
            break;
      }
#else
      uFormat = lpOutlineApp->m_cfOutline;
#endif

#if defined( OLE_SERVER )
      /* NOTE: if the document is an embedded object, then the
      **    File.SaveAs command is changed to File.SaveCopyAs. with the
      **    Save Copy As operation, the document does NOT remember the
      **    saved file as the associated file for the document.
      */
      if (lpOutlineDoc->m_docInitType == DOCTYPE_EMBEDDED)
         fRemember = FALSE;
#endif
      A2W (szFileName, szUniFileName, 256);
      (void)OutlineDoc_SaveToFile(
            lpOutlineDoc,
            szUniFileName,
            uFormat,
            fRemember
      );

   }
   else
      fNoError = FALSE;

   return fNoError;

}

/* OutlineApp_AboutCommand
 * -----------------------
 *
 *      Show the About dialog box
 */
void OutlineApp_AboutCommand(LPOUTLINEAPP lpOutlineApp)
{
#if defined( OLE_VERSION )
   OleApp_PreModalDialog(
         (LPOLEAPP)lpOutlineApp, (LPOLEDOC)lpOutlineApp->m_lpDoc);
#endif

   DialogBox(
         lpOutlineApp->m_hInst,
         (LPSTR)"About",
         OutlineApp_GetFrameWindow(lpOutlineApp),
         (DLGPROC)AboutDlgProc
   );

#if defined( OLE_VERSION )
   OleApp_PostModalDialog(
         (LPOLEAPP)lpOutlineApp, (LPOLEDOC)lpOutlineApp->m_lpDoc);
#endif
}

/* OutlineApp_CloseAllDocsAndExitCommand
 * -------------------------------------
 *
 *  Close all active documents and exit the app.
 *  Because this is an SDI, there is only one document
 *  If the doc was modified, prompt the user if he wants to save it.
 *
 *  Returns:
 *      TRUE if the app is successfully closed
 *      FALSE if failed or aborted
 */
BOOL OutlineApp_CloseAllDocsAndExitCommand(
      LPOUTLINEAPP        lpOutlineApp,
      BOOL                fForceEndSession
)
{
   BOOL fResult;

   OLEDBG_BEGIN2("OutlineApp_CloseAllDocsAndExitCommand\r\n")

#if defined( OLE_VERSION )
   // Call OLE specific version of this function
   fResult = OleApp_CloseAllDocsAndExitCommand(
         (LPOLEAPP)lpOutlineApp, fForceEndSession);

#else

   /* Because this is an SDI app, there is only one document.
   ** Close the doc. if it is successfully closed and the app will
   ** not automatically exit, then also exit the app.
   ** if this were an MDI app, we would loop through and close all
   ** open MDI child documents.
   */
   if (OutlineDoc_Close(lpOutlineApp->m_lpDoc, OLECLOSE_PROMPTSAVE)) {

#if defined( _DEBUG )
      OleDbgAssertSz(
            lpOutlineApp->m_lpDoc==NULL,
            "Closed doc NOT properly destroyed"
      );
#endif

      OutlineApp_Destroy(lpOutlineApp);
      fResult = TRUE;

   } // else User Canceled shutdown
   else
      fResult = FALSE;

#endif

   OLEDBG_END2

   return fResult;
}

/* OutlineApp_Destroy
 * ------------------
 *
 *      Destroy all data structures used by the app and force the
 * app to shut down. This should be called after all documents have
 * been closed.
 */
void OutlineApp_Destroy(LPOUTLINEAPP lpOutlineApp)
{
   OLEDBG_BEGIN3("OutlineApp_Destroy\r\n");

#if defined( OLE_VERSION )
   /* NOTE: perform processing required for OLE */
   OleApp_Destroy((LPOLEAPP)lpOutlineApp);
#endif

   SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_ARROW)));
   DestroyCursor(lpOutlineApp->m_hcursorSelCur);

#if defined( USE_FRAMETOOLS )
   FrameTools_Destroy(&lpOutlineApp->m_frametools);
#endif

   if (lpOutlineApp->m_hStdFont)
      DeleteObject(lpOutlineApp->m_hStdFont);

   if(lpOutlineApp->m_PrintDlg.hDevMode)
      GlobalFree(lpOutlineApp->m_PrintDlg.hDevMode);
   if(lpOutlineApp->m_PrintDlg.hDevNames)
      GlobalFree(lpOutlineApp->m_PrintDlg.hDevNames);

#if defined( USE_STATUSBAR )
   if(lpOutlineApp->m_hWndStatusBar) {
      DestroyStatusWindow(lpOutlineApp->m_hWndStatusBar);
      lpOutlineApp->m_hWndStatusBar = NULL;
   }
#endif

   OutlineApp_DestroyWindow(lpOutlineApp);
   OleDbgOut1("@@@@ APP DESTROYED\r\n");

   OLEDBG_END3
}

/* OutlineApp_DestroyWindow
 * ------------------------
 *
 *  Destroy all windows created by the App.
 */
void OutlineApp_DestroyWindow(LPOUTLINEAPP lpOutlineApp)
{
   HWND hWndApp = lpOutlineApp->m_hWndApp;

   if(hWndApp) {
      lpOutlineApp->m_hWndApp = NULL;
      lpOutlineApp->m_hWndAccelTarget = NULL;
      DestroyWindow(hWndApp);  /* Quit the app */
   }
}

/* OutlineApp_GetFrameRect
** -----------------------
**    Get the rectangle of the app frame window EXCLUDING space for the
**    status line.
**
**    NOTE: this is the rectangle that an in-place container can
**    offer to an in-place active object from which to get frame tool
**    space.
*/
void OutlineApp_GetFrameRect(LPOUTLINEAPP lpOutlineApp, LPRECT lprcFrameRect)
{
   GetClientRect(lpOutlineApp->m_hWndApp, lprcFrameRect);

#if defined( USE_STATUSBAR )
   lprcFrameRect->bottom -= STATUS_HEIGHT;
#endif

}

/* OutlineApp_GetClientAreaRect
** ----------------------------
**    Get the rectangle of the app frame window EXCLUDING space for the
**    status line AND EXCLUDING space for any frame-level tools.
**
**    NOTE: this is the rectangle that an in-place container gives
**    to its in-place active object as the lpClipRect in
**    IOleInPlaceSite::GetWindowContext.
*/
void OutlineApp_GetClientAreaRect(
      LPOUTLINEAPP        lpOutlineApp,
      LPRECT              lprcClientAreaRect
)
{
   OutlineApp_GetFrameRect(lpOutlineApp, lprcClientAreaRect);

   /* if the app either uses frame-level tools itself or, as in-place
   **    container, is prepared to allow an in-place active object to
   **    have space for tools, then it must subtract away the space
   **    required for the tools.
   */
#if defined ( USE_FRAMETOOLS ) || defined ( INPLACE_CNTR )

   lprcClientAreaRect->top    += lpOutlineApp->m_FrameToolWidths.top;
   lprcClientAreaRect->left   += lpOutlineApp->m_FrameToolWidths.left;
   lprcClientAreaRect->right  -= lpOutlineApp->m_FrameToolWidths.right;
   lprcClientAreaRect->bottom -= lpOutlineApp->m_FrameToolWidths.bottom;
#endif  // USE_FRAMETOOLS || INPLACE_CNTR

}

/* OutlineApp_GetStatusLineRect
** ----------------------------
**    Get the rectangle required for the status line.
**
**    NOTE: the top frame-level in-place container displays its
**    status line even when an object is active in-place.
*/
void OutlineApp_GetStatusLineRect(
      LPOUTLINEAPP        lpOutlineApp,
      LPRECT              lprcStatusLineRect
)
{
   RECT rcFrameRect;
   GetClientRect(lpOutlineApp->m_hWndApp, (LPRECT)&rcFrameRect);
   lprcStatusLineRect->left    = rcFrameRect.left;
   lprcStatusLineRect->top     = rcFrameRect.bottom - STATUS_HEIGHT;
   lprcStatusLineRect->right   = rcFrameRect.right;
   lprcStatusLineRect->bottom  = rcFrameRect.bottom;
}

/* OutlineApp_ResizeWindows
 * ------------------------
 *
 * Changes the size and position of the SDI document and tool windows.
 * Normally called on a WM_SIZE message.
 *
 * Currently the app supports a status bar and a single SDI document window.
 * In the future it will have a formula bar and possibly multiple MDI
 * document windows.
 *
 * CUSTOMIZATION: Change positions of windows.
 */
void OutlineApp_ResizeWindows(LPOUTLINEAPP lpOutlineApp)
{
   LPOUTLINEDOC lpOutlineDoc = OutlineApp_GetActiveDoc(lpOutlineApp);
   RECT rcStatusLineRect;

   if (! lpOutlineApp)
      return;

#if defined( INPLACE_CNTR )
   if (lpOutlineDoc)
      ContainerDoc_FrameWindowResized((LPCONTAINERDOC)lpOutlineDoc);
#else
#if defined( USE_FRAMETOOLS )
   if (lpOutlineDoc)
      OutlineDoc_AddFrameLevelTools(lpOutlineDoc);
#else
   OutlineApp_ResizeClientArea(lpOutlineApp);
#endif  // ! USE_FRAMETOOLS
#endif  // ! INPLACE_CNTR

#if defined( USE_STATUSBAR )
   if (lpOutlineApp->m_hWndStatusBar) {
      OutlineApp_GetStatusLineRect(lpOutlineApp, (LPRECT)&rcStatusLineRect);
      MoveWindow(
            lpOutlineApp->m_hWndStatusBar,
            rcStatusLineRect.left,
            rcStatusLineRect.top,
            rcStatusLineRect.right - rcStatusLineRect.left,
            rcStatusLineRect.bottom - rcStatusLineRect.top,
            TRUE    /* fRepaint */
         );
   }
#endif  // USE_STATUSBAR
}

#if defined( USE_FRAMETOOLS ) || defined( INPLACE_CNTR )

void OutlineApp_SetBorderSpace(
      LPOUTLINEAPP        lpOutlineApp,
      LPBORDERWIDTHS      lpBorderWidths
)
{
   lpOutlineApp->m_FrameToolWidths = *lpBorderWidths;
   OutlineApp_ResizeClientArea(lpOutlineApp);
}
#endif  // USE_FRAMETOOLS || INPLACE_CNTR

void OutlineApp_ResizeClientArea(LPOUTLINEAPP lpOutlineApp)
{
   RECT rcClientAreaRect;

#if defined( MDI_VERSION )

   // Resize MDI Client Area Window here

#else

   if (lpOutlineApp->m_lpDoc) {
         OutlineApp_GetClientAreaRect(
               lpOutlineApp, (LPRECT)&rcClientAreaRect);
         OutlineDoc_Resize(lpOutlineApp->m_lpDoc,
               (LPRECT)&rcClientAreaRect);
   }

#endif

}

/* OutlineApp_GetActiveDoc
 * -----------------------
 *
 * Return the document in focus. For SDI, the same (only one) document is
 * always returned.
 */
LPOUTLINEDOC OutlineApp_GetActiveDoc(LPOUTLINEAPP lpOutlineApp)
{
   return lpOutlineApp->m_lpDoc;
}

/* OutlineApp_GetMenu
 * ------------------
 *
 * Return the menu handle of the app
 */
HMENU OutlineApp_GetMenu(LPOUTLINEAPP lpOutlineApp)
{
   if (!lpOutlineApp) {
      return NULL;
   }

   return lpOutlineApp->m_hMenuApp;
}

#if defined( USE_FRAMETOOLS )

/* OutlineApp_GetFrameTools
 * ---------------------
 *
 * Return the pointer to the toolbar object
 */
LPFRAMETOOLS OutlineApp_GetFrameTools(LPOUTLINEAPP lpOutlineApp)
{
   return (LPFRAMETOOLS)&lpOutlineApp->m_frametools;
}
#endif

/* OutlineApp_SetStatusText
 * ------------------------
 *
 * Show the given string in the status line
 */
void OutlineApp_SetStatusText(LPOUTLINEAPP lpOutlineApp, LPOLESTR lpszMessage)
{
   char szAnsiMessage[256];

   W2A (lpszMessage, szAnsiMessage, 256);
   SetStatusText(lpOutlineApp->m_hWndStatusBar, szAnsiMessage);
}

/* OutlineApp_GetActiveFont
 * ------------------------
 *
 *      Return the font used by the application
 */
HFONT OutlineApp_GetActiveFont(LPOUTLINEAPP lpOutlineApp)
{
   return lpOutlineApp->m_hStdFont;
}

/* OutlineApp_GetAppName
 * ---------------------
 *
 *      Retrieve the application name
 */
void OutlineApp_GetAppName(LPOUTLINEAPP lpOutlineApp, LPOLESTR lpszAppName)
{
   OLESTRCPY (lpszAppName, APPNAME);
}

/* OutlineApp_GetAppVersionNo
 * --------------------------
 *
 *      Get the version number (major and minor) of the application
 */
void OutlineApp_GetAppVersionNo(LPOUTLINEAPP lpOutlineApp, int narrAppVersionNo[])
{
   narrAppVersionNo[0] = APPMAJORVERSIONNO;
   narrAppVersionNo[1] = APPMINORVERSIONNO;
}

/* OutlineApp_VersionNoCheck
 * -------------------------
 *
 *      Check if the version stamp read from a file is compatible
 *      with the current instance of the application.
 *      returns TRUE if the file can be read, else FALSE.
 */
BOOL OutlineApp_VersionNoCheck(LPOUTLINEAPP lpOutlineApp, LPOLESTR lpszFormatName, int narrAppVersionNo[])
{
#if defined( OLE_CNTR )

   /* ContainerApp accepts both CF_OUTLINE and CF_CONTAINEROUTLINE formats */
   if (OLESTRCMP(lpszFormatName, CONTAINERDOCFORMAT) != 0 &&
      OLESTRCMP(lpszFormatName, OUTLINEDOCFORMAT) != 0) {
      // REVIEW: should load string from string resource
      OutlineApp_ErrorMessage(
            lpOutlineApp,
            OLESTR("File is either corrupted or not of proper type.")
         );
      return FALSE;
   }

#else

   /* OutlineApp accepts CF_OUTLINE format only */
   if (OLESTRCMP(lpszFormatName, OUTLINEDOCFORMAT) != 0) {
      // REVIEW: should load string from string resource
      OutlineApp_ErrorMessage(
            lpOutlineApp,
            OLESTR("File is either corrupted or not of proper type.")
         );
      return FALSE;
   }
#endif

   if (narrAppVersionNo[0] < APPMAJORVERSIONNO) {
      // REVIEW: should load string from string resource
      OutlineApp_ErrorMessage(
            lpOutlineApp,
            OLESTR("File was created by an older version; it can not be read.")
         );
      return FALSE;
   }

   return TRUE;
}

/* OutlineApp_ErrorMessage
 * -----------------------
 *
 *      Display an error message box
 */
void OutlineApp_ErrorMessage(LPOUTLINEAPP lpOutlineApp, LPOLESTR lpszErrMsg)
{
   HWND hWndFrame = OutlineApp_GetFrameWindow(lpOutlineApp);
   char   szAnsiErrMsg[256];

   // NOTE: only put up user message boxes if app is visible
   if (IsWindowVisible(hWndFrame)) {
#if defined( OLE_VERSION )
      OleApp_PreModalDialog(
            (LPOLEAPP)lpOutlineApp, (LPOLEDOC)lpOutlineApp->m_lpDoc);
#endif
      W2A (lpszErrMsg, szAnsiErrMsg, 256);
      MessageBox(hWndFrame, szAnsiErrMsg, NULL, MB_ICONEXCLAMATION | MB_OK);

#if defined( OLE_VERSION )
      OleApp_PostModalDialog(
            (LPOLEAPP)lpOutlineApp, (LPOLEDOC)lpOutlineApp->m_lpDoc);
#endif
   }
}

#if defined( USE_FRAMETOOLS )

/* OutlineApp_SetFormulaBarAccel
 * -----------------------------
 *
 *  Set accelerator table based on state of formula bar.
 */
void OutlineApp_SetFormulaBarAccel(
      LPOUTLINEAPP            lpOutlineApp,
      BOOL                    fEditFocus
)
{
   if (fEditFocus)
      lpOutlineApp->m_hAccel = lpOutlineApp->m_hAccelFocusEdit;
   else
      lpOutlineApp->m_hAccel = lpOutlineApp->m_hAccelApp;
}

#endif  // USE_FRAMETOOLS

/* OutlineApp_ForceRedraw
 * ----------------------
 *
 *      Force the Application window to repaint.
 */
void OutlineApp_ForceRedraw(LPOUTLINEAPP lpOutlineApp, BOOL fErase)
{
   if (!lpOutlineApp)
      return;

   InvalidateRect(lpOutlineApp->m_hWndApp, NULL, fErase);
}


OUTLDOC.C

/*************************************************************************
**
**    OLE 2 Sample Code
**
**    outldoc.c
**
**    This file contains OutlineDoc functions.
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#include "outline.h"

#if !defined( OLE_VERSION )
#include <commdlg.h>
#endif

OLEDBGDATA

extern LPOUTLINEAPP g_lpApp;

#ifdef WIN32S
#define GALLOCFLG (GMEM_SHARE | GMEM_ZEROINIT | GMEM_MOVEABLE)
#else
#define GALLOCFLG (GMEM_SHARE | GMEM_ZEROINIT)
#endif

// REVIEW: should use string resource for messages
OLECHAR ErrMsgDocWnd[] = OLESTR("Can't create Document Window!");
OLECHAR ErrMsgFormatNotSupported[] = OLESTR("Clipboard format not supported!");
OLECHAR MsgSaveFile[] = OLESTR("Save existing file ?");
OLECHAR ErrMsgSaving[] = OLESTR("Error in saving file!");
OLECHAR ErrMsgOpening[] = OLESTR("Error in opening file!");
OLECHAR ErrMsgFormat[] = OLESTR("Improper file format!");
OLECHAR ErrOutOfMemory[] = OLESTR("Error: out of memory!");
static OLECHAR ErrMsgPrint[] = OLESTR("Printing Error!");

static BOOL fCancelPrint;    // TRUE if the user has canceled the print job
static HWND hWndPDlg;       // Handle to the cancel print dialog

/* OutlineDoc_Init
 * ---------------
 *
 *  Initialize the fields of a new OutlineDoc object. The object is initially
 *  not associated with a file or an (Untitled) document. This function sets
 *  the docInitType to DOCTYPE_UNKNOWN. After calling this function the
 *  caller should call:
 *      1. OutlineDoc_InitNewFile to set the OutlineDoc to (Untitled)
 *      2. OutlineDoc_LoadFromFile to associate the OutlineDoc with a file.
 *  This function creates a new window for the document.
 *
 *  NOTE: the window is initially created with a NIL size. it must be
 *        sized and positioned by the caller. also the document is initially
 *        created invisible. the caller must call OutlineDoc_ShowWindow
 *        after sizing it to make the document window visible.
 */
BOOL OutlineDoc_Init(LPOUTLINEDOC lpOutlineDoc, BOOL fDataTransferDoc)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;

#if defined( INPLACE_CNTR )
   lpOutlineDoc->m_hWndDoc = CreateWindow(
               DOCWNDCLASS,            // Window class name
               NULL,                   // Window's title

               /* NOTE: an in-place contanier MUST use
               **    WS_CLIPCHILDREN window style for the window
               **    that it uses as the parent for the server's
               **    in-place active window so that its
               **    painting does NOT interfere with the painting
               **    of the server's in-place active child window.
               */

               WS_CLIPCHILDREN | WS_CLIPSIBLINGS |
               WS_CHILDWINDOW,
               0, 0,
               0, 0,
               lpOutlineApp->m_hWndApp,// Parent window's handle
               (HMENU)1,               // child window id
               lpOutlineApp->m_hInst,  // Instance of window
               NULL);                  // Create struct for WM_CREATE

#else

   lpOutlineDoc->m_hWndDoc = CreateWindow(
               DOCWNDCLASS,            // Window class name
               NULL,                   // Window's title
               WS_CHILDWINDOW,
               0, 0,
               0, 0,
               lpOutlineApp->m_hWndApp,// Parent window's handle
               (HMENU)1,               // child window id
               lpOutlineApp->m_hInst,  // Instance of window
               NULL);                  // Create struct for WM_CREATE
#endif

   if(! lpOutlineDoc->m_hWndDoc) {
      OutlineApp_ErrorMessage(lpOutlineApp, ErrMsgDocWnd);
      return FALSE;
   }

   SetWindowLong(lpOutlineDoc->m_hWndDoc, 0, (LONG) lpOutlineDoc);

   if (! LineList_Init(&lpOutlineDoc->m_LineList, lpOutlineDoc))
      return FALSE;

   lpOutlineDoc->m_lpNameTable = OutlineDoc_CreateNameTable(lpOutlineDoc);
   if (! lpOutlineDoc->m_lpNameTable )
      return FALSE;

   lpOutlineDoc->m_docInitType = DOCTYPE_UNKNOWN;
   lpOutlineDoc->m_cfSaveFormat = lpOutlineApp->m_cfOutline;
   lpOutlineDoc->m_szFileName[0] = '\0';
   lpOutlineDoc->m_lpszDocTitle = lpOutlineDoc->m_szFileName;
   lpOutlineDoc->m_fDataTransferDoc = fDataTransferDoc;
   lpOutlineDoc->m_uCurrentZoom = IDM_V_ZOOM_100;
   lpOutlineDoc->m_scale.dwSxN  = (DWORD) 1;
   lpOutlineDoc->m_scale.dwSxD  = (DWORD) 1;
   lpOutlineDoc->m_scale.dwSyN  = (DWORD) 1;
   lpOutlineDoc->m_scale.dwSyD  = (DWORD) 1;
   lpOutlineDoc->m_uCurrentMargin = IDM_V_SETMARGIN_0;
   lpOutlineDoc->m_nLeftMargin  = 0;
   lpOutlineDoc->m_nRightMargin = 0;
   lpOutlineDoc->m_nDisableDraw = 0;
   OutlineDoc_SetModified(lpOutlineDoc, FALSE, FALSE, FALSE);

#if defined( USE_HEADING )
   if (! fDataTransferDoc) {
      if (!Heading_Create((LPHEADING)&lpOutlineDoc->m_heading,
            lpOutlineDoc->m_hWndDoc, lpOutlineApp->m_hInst)) {
         return FALSE;

      }
   }
#endif  // USE_HEADING

#if defined( USE_FRAMETOOLS )
   if (! fDataTransferDoc) {
      lpOutlineDoc->m_lpFrameTools = OutlineApp_GetFrameTools(lpOutlineApp);
      FrameTools_AssociateDoc(
            lpOutlineDoc->m_lpFrameTools,
            lpOutlineDoc
      );
   }
#endif  // USE_FRAMETOOLS

#if defined( OLE_VERSION )
   /* NOTE: perform initialization required for OLE */
   if (! OleDoc_Init((LPOLEDOC)lpOutlineDoc, fDataTransferDoc))
      return FALSE;
#endif  // OLE_VERSION

   return TRUE;
}

/* OutlineDoc_InitNewFile
 * ----------------------
 *
 *  Initialize the OutlineDoc object to be a new (Untitled) document.
 *  This function sets the docInitType to DOCTYPE_NEW.
 */
BOOL OutlineDoc_InitNewFile(LPOUTLINEDOC lpOutlineDoc)
{
#if defined( OLE_VERSION )
   // NOTE: call OLE version of this function instead
   return OleDoc_InitNewFile((LPOLEDOC)lpOutlineDoc);

#else

   OleDbgAssert(lpOutlineDoc->m_docInitType == DOCTYPE_UNKNOWN);

   // set file name to untitled
   // REVIEW: should load from string resource
   lstrcpyW(lpOutlineDoc->m_szFileName, UNTITLED);
   lpOutlineDoc->m_lpszDocTitle = lpOutlineDoc->m_szFileName;
   lpOutlineDoc->m_docInitType = DOCTYPE_NEW;

   if (! lpOutlineDoc->m_fDataTransferDoc)
      OutlineDoc_SetTitle(lpOutlineDoc, FALSE /*fMakeUpperCase*/);

   return TRUE;

#endif      // BASE OUTLINE VERSION
}

/* OutlineDoc_CreateNameTable
 * --------------------------
 *
 * Allocate a new NameTable of the appropriate type. Each document has
 * a NameTable and a LineList.
 *  OutlineDoc --> creates standard OutlineNameTable type name tables.
 *  ServerDoc  --> creates enhanced SeverNameTable type name tables.
 *
 *      Returns lpNameTable for successful, NULL if error.
 */
LPOUTLINENAMETABLE OutlineDoc_CreateNameTable(LPOUTLINEDOC lpOutlineDoc)
{
   LPOUTLINENAMETABLE lpOutlineNameTable;

   lpOutlineNameTable = (LPOUTLINENAMETABLE)New(
         (DWORD)sizeof(OUTLINENAMETABLE)
   );

   OleDbgAssertSz(lpOutlineNameTable != NULL,"Error allocating NameTable");
   if (lpOutlineNameTable == NULL)
      return NULL;

   // initialize new NameTable
   if (! OutlineNameTable_Init(lpOutlineNameTable, lpOutlineDoc) )
      goto error;

   return lpOutlineNameTable;

error:
   if (lpOutlineNameTable)
      Delete(lpOutlineNameTable);
   return NULL;
}

/* OutlineDoc_ClearCommand
 * -----------------------
 *
 *      Delete selection in list box by calling OutlineDoc_Delete
 */
void OutlineDoc_ClearCommand(LPOUTLINEDOC lpOutlineDoc)
{
   LPLINELIST lpLL = &lpOutlineDoc->m_LineList;
   int i;
   int nNumSel;
   LINERANGE lrSel;

   nNumSel=LineList_GetSel(lpLL, (LPLINERANGE)&lrSel);

   OutlineDoc_SetRedraw ( lpOutlineDoc, FALSE );
   for(i = 0; i < nNumSel; i++)
      OutlineDoc_DeleteLine(lpOutlineDoc, lrSel.m_nStartLine);

   OutlineDoc_SetRedraw ( lpOutlineDoc, TRUE );

   LineList_RecalcMaxLineWidthInHimetric(lpLL, 0);
}

/* OutlineDoc_CutCommand
 * ---------------------
 *
 * Cut selection to clipboard
 */
void OutlineDoc_CutCommand(LPOUTLINEDOC lpOutlineDoc)
{
   OutlineDoc_CopyCommand(lpOutlineDoc);
   OutlineDoc_ClearCommand(lpOutlineDoc);
}

/* OutlineDoc_CopyCommand
 * ----------------------
 *  Copy selection to clipboard.
 *  Post to the clipboard the formats that the app can render.
 *  the actual data is not rendered at this time. using the
 *  delayed rendering technique, Windows will send the clipboard
 *  owner window either a WM_RENDERALLFORMATS or a WM_RENDERFORMAT
 *  message when the actual data is requested.
 *
 *    NOTE: the normal delayed rendering technique where Windows
 *    sends the clipboard owner window either a WM_RENDERALLFORMATS or
 *    a WM_RENDERFORMAT message when the actual data is requested is
 *    NOT exposed to the app calling OleSetClipboard. OLE internally
 *    creates its own window as the clipboard owner and thus our app
 *    will NOT get these WM_RENDER messages.
 */
void OutlineDoc_CopyCommand(LPOUTLINEDOC lpSrcOutlineDoc)
{
#if defined( OLE_VERSION )
   // Call OLE version of this function instead
   OleDoc_CopyCommand((LPOLEDOC)lpSrcOutlineDoc);

#else
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOUTLINEDOC lpClipboardDoc;

   OpenClipboard(lpSrcOutlineDoc->m_hWndDoc);
   EmptyClipboard();

   /* squirrel away a copy of the current selection to the ClipboardDoc */
   lpClipboardDoc = OutlineDoc_CreateDataTransferDoc(lpSrcOutlineDoc);

   if (! lpClipboardDoc)
      return;     // Error: could not create DataTransferDoc

   lpOutlineApp->m_lpClipboardDoc = (LPOUTLINEDOC)lpClipboardDoc;

   SetClipboardData(lpOutlineApp->m_cfOutline, NULL);
   SetClipboardData(CF_TEXT, NULL);

   CloseClipboard();

#endif  // ! OLE_VERSION
}

/* OutlineDoc_ClearAllLines
 * ------------------------
 *
 *      Delete all lines in the document.
 */
void OutlineDoc_ClearAllLines(LPOUTLINEDOC lpOutlineDoc)
{
   LPLINELIST lpLL = &lpOutlineDoc->m_LineList;
   int i;

   for(i = 0; i < lpLL->m_nNumLines; i++)
      OutlineDoc_DeleteLine(lpOutlineDoc, 0);

   LineList_RecalcMaxLineWidthInHimetric(lpLL, 0);
}

/* OutlineDoc_CreateDataTransferDoc
 * --------------------------------
 *
 *      Create a document to be use to transfer data (either via a
 *  drag/drop operation of the clipboard). Copy the selection of the
 *  source doc to the data transfer document. A data transfer document is
 *  the same as a document that is created by the user except that it is
 *  NOT made visible to the user. it is specially used to hold a copy of
 *  data that the user should not be able to change.
 *
 *  NOTE: in the OLE version the data transfer document is used
 *      specifically to provide an IDataObject* that renders the data copied.
 */
LPOUTLINEDOC OutlineDoc_CreateDataTransferDoc(LPOUTLINEDOC lpSrcOutlineDoc)
{
#if defined( OLE_VERSION )
   // Call OLE version of this function instead
   return OleDoc_CreateDataTransferDoc((LPOLEDOC)lpSrcOutlineDoc);

#else
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOUTLINEDOC lpDestOutlineDoc;
   LPLINELIST lpSrcLL = &lpSrcOutlineDoc->m_LineList;
   LINERANGE lrSel;
   int nCopied;

   lpDestOutlineDoc = OutlineApp_CreateDoc(lpOutlineApp, TRUE);
   if (! lpDestOutlineDoc) return NULL;

   // set the ClipboardDoc to an (Untitled) doc.
   if (! OutlineDoc_InitNewFile(lpDestOutlineDoc))
      goto error;

   LineList_GetSel(lpSrcLL, (LPLINERANGE)&lrSel);
   nCopied = LineList_CopySelToDoc(
         lpSrcLL,
         (LPLINERANGE)&lrSel,
         lpDestOutlineDoc
   );

   return lpDestOutlineDoc;

error:
   if (lpDestOutlineDoc)
      OutlineDoc_Destroy(lpDestOutlineDoc);

   return NULL;

#endif  // ! OLE_VERSION
}

/* OutlineDoc_PasteCommand
 * -----------------------
 *
 * Paste lines from clipboard
 */
void OutlineDoc_PasteCommand(LPOUTLINEDOC lpOutlineDoc)
{
#if defined( OLE_VERSION )
   // Call OLE version of this function instead
   OleDoc_PasteCommand((LPOLEDOC)lpOutlineDoc);

#else

   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPLINELIST lpLL = (LPLINELIST)&lpOutlineDoc->m_LineList;
   int nIndex;
   int nCount;
   HGLOBAL hData;
   LINERANGE lrSel;
   UINT uFormat;

   if (LineList_GetCount(lpLL) == 0)
      nIndex = -1;    // pasting to empty list
   else
      nIndex=LineList_GetFocusLineIndex(lpLL);

   OutlineDoc_SetRedraw ( lpOutlineDoc, FALSE );

   OpenClipboard(lpOutlineDoc->m_hWndDoc);

   uFormat = 0;
   while(uFormat = EnumClipboardFormats(uFormat)) {
      if(uFormat == lpOutlineApp->m_cfOutline) {
         hData = GetClipboardData(lpOutlineApp->m_cfOutline);
         nCount = OutlineDoc_PasteOutlineData(lpOutlineDoc, hData, nIndex);
         break;
      }
      if(uFormat == CF_TEXT) {
         hData = GetClipboardData(CF_TEXT);
         nCount = OutlineDoc_PasteTextData(lpOutlineDoc, hData, nIndex);
         break;
      }
   }

   lrSel.m_nStartLine = nIndex + nCount;
   lrSel.m_nEndLine = nIndex + 1;
   LineList_SetSel(lpLL, &lrSel);
   OutlineDoc_SetRedraw ( lpOutlineDoc, TRUE );

   CloseClipboard();

#endif      // ! OLE_VERSION
}

/* OutlineDoc_PasteOutlineData
 * ---------------------------
 *
 *      Put an array of Line Objects (stored in hOutline) into the document
 *
 * Return the number of items added
 */
int OutlineDoc_PasteOutlineData(LPOUTLINEDOC lpOutlineDoc, HGLOBAL hOutline, int nStartIndex)
{
   int nCount;
   int i;
   LPTEXTLINE arrLine;

   nCount = (int) GlobalSize(hOutline) / sizeof(TEXTLINE);
   arrLine = (LPTEXTLINE)GlobalLock(hOutline);
   if (!arrLine)
      return 0;

   for(i = 0; i < nCount; i++)
      Line_CopyToDoc((LPLINE)&arrLine[i], lpOutlineDoc, nStartIndex+i);

   GlobalUnlock(hOutline);

   return nCount;
}

/* OutlineDoc_PasteTextData
 * ------------------------
 *
 *      Build Line Objects from the strings (separated by '\n') in hText
 * and put them into the document
 */
int OutlineDoc_PasteTextData(LPOUTLINEDOC lpOutlineDoc, HGLOBAL hText, int nStartIndex)
{
   LPLINELIST  lpLL = (LPLINELIST)&lpOutlineDoc->m_LineList;
   HDC         hDC;
   LPSTR       lpszText;
   LPSTR       lpszEnd;
   LPTEXTLINE  lpLine;
   int         nLineCount;
   int         i;
   UINT        nTab;
   char        szBuf[MAXSTRLEN+1];

   lpszText=(LPSTR)GlobalLock(hText);
   if(!lpszText)
      return 0;

   lpszEnd = lpszText + lstrlen(lpszText);
   nLineCount=0;

   while(*lpszText && (lpszText<lpszEnd)) {

      // count the tab level
      nTab = 0;
      while((*lpszText == '\t') && (lpszText<lpszEnd)) {
         nTab++;
         lpszText++;
      }

      // collect the text string character by character
      for(i=0; (i<MAXSTRLEN) && (lpszText<lpszEnd); i++) {
         if ((! *lpszText) || (*lpszText == '\n'))
            break;
         szBuf[i] = *lpszText++;
      }
      szBuf[i] = 0;
      lpszText++;
      if ((i > 0) && (szBuf[i-1] == '\r'))
         szBuf[i-1] = 0;     // remove carriage return at the end

      hDC = LineList_GetDC(lpLL);
      lpLine = TextLine_Create(hDC, nTab, szBuf);
      LineList_ReleaseDC(lpLL, hDC);

      OutlineDoc_AddLine(
            lpOutlineDoc,
            (LPLINE)lpLine,
            nStartIndex + nLineCount
      );
      nLineCount++;

   }

   GlobalUnlock(hText);

   return nLineCount;
}

/* OutlineDoc_AddTextLineCommand
 * -----------------------------
 *
 *      Add a new text line following the current focus line.
 */
void OutlineDoc_AddTextLineCommand(LPOUTLINEDOC lpOutlineDoc)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPLINELIST lpLL = &lpOutlineDoc->m_LineList;
   HDC hDC;
   int nIndex = LineList_GetFocusLineIndex(lpLL);
   char szBuf[MAXSTRLEN+1];
   UINT nTab = 0;
   LPLINE lpLine;
   LPTEXTLINE lpTextLine;

   szBuf[0] = '\0';

#if defined( USE_FRAMETOOLS )
   FrameTools_FB_GetEditText(
         lpOutlineDoc->m_lpFrameTools, szBuf, MAXSTRLEN+1);
#else
   if (! InputTextDlg(lpOutlineDoc->m_hWndDoc, szBuf, "Add Line"))
      return;
#endif

   hDC = LineList_GetDC(lpLL);
   lpLine = LineList_GetLine(lpLL, nIndex);
   if (lpLine)
      nTab = Line_GetTabLevel(lpLine);

   lpTextLine=TextLine_Create(hDC, nTab, szBuf);
   LineList_ReleaseDC(lpLL, hDC);

   if (! lpTextLine) {
      OutlineApp_ErrorMessage(lpOutlineApp, ErrOutOfMemory);
      return;
   }
   OutlineDoc_AddLine(lpOutlineDoc, (LPLINE)lpTextLine, nIndex);
}

/* OutlineDoc_AddTopLineCommand
 * ----------------------------
 *
 *      Add a top (margin) line as the first line in the LineList.
 *      (do not change the current selection)
 */
void OutlineDoc_AddTopLineCommand(
      LPOUTLINEDOC        lpOutlineDoc,
      UINT                nHeightInHimetric
)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPLINELIST  lpLL = &lpOutlineDoc->m_LineList;
   HDC         hDC = LineList_GetDC(lpLL);
   LPTEXTLINE  lpTextLine = TextLine_Create(hDC, 0, NULL);
   LPLINE      lpLine = (LPLINE)lpTextLine;
   LINERANGE   lrSel;
   int         nNumSel;

   LineList_ReleaseDC(lpLL, hDC);

   if (! lpTextLine) {
      OutlineApp_ErrorMessage(lpOutlineApp, ErrOutOfMemory);
      return;
   }

   Line_SetHeightInHimetric(lpLine, nHeightInHimetric);

   nNumSel=LineList_GetSel(lpLL, (LPLINERANGE)&lrSel);
   if (nNumSel > 0) {
      // adjust current selection to keep equivalent selection
      lrSel.m_nStartLine += 1;
      lrSel.m_nEndLine += 1;
   }
   OutlineDoc_AddLine(lpOutlineDoc, lpLine, -1);
   if (nNumSel > 0)
      LineList_SetSel(lpLL, (LPLINERANGE)&lrSel);
}

#if defined( USE_FRAMETOOLS )

/* OutlineDoc_SetFormulaBarEditText
 * --------------------------------
 *
 *      Fill the edit control in the formula with the text string from a
 * TextLine in focus.
 */
void OutlineDoc_SetFormulaBarEditText(
      LPOUTLINEDOC            lpOutlineDoc,
      LPLINE                  lpLine
)
{
   LPLINELIST lpLL = &lpOutlineDoc->m_LineList;
   char cBuf[MAXSTRLEN+1];

   if (! lpOutlineDoc || ! lpOutlineDoc->m_lpFrameTools)
      return;

   if (Line_GetLineType(lpLine) != TEXTLINETYPE) {
      FrameTools_FB_SetEditText(lpOutlineDoc->m_lpFrameTools, NULL);
   } else {
      TextLine_GetTextData((LPTEXTLINE)lpLine, (LPSTR)cBuf);
      FrameTools_FB_SetEditText(lpOutlineDoc->m_lpFrameTools, (LPSTR)cBuf);
   }
}

/* OutlineDoc_SetFormulaBarEditFocus
 * ---------------------------------
 *
 *  Setup for formula bar to gain or loose edit focus.
 *  if gaining focus, setup up special accelerator table and scroll line
 *      into view.
 *  else restore normal accelerator table.
 */
void OutlineDoc_SetFormulaBarEditFocus(
      LPOUTLINEDOC            lpOutlineDoc,
      BOOL                    fEditFocus
)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPLINELIST lpLL;
   int nFocusIndex;

   if (! lpOutlineDoc || ! lpOutlineDoc->m_lpFrameTools)
      return;

   lpOutlineDoc->m_lpFrameTools->m_fInFormulaBar = fEditFocus;

   if (fEditFocus && lpOutlineDoc->m_lpFrameTools) {
      lpLL = OutlineDoc_GetLineList(lpOutlineDoc);

      nFocusIndex = LineList_GetFocusLineIndex(lpLL);
      LineList_ScrollLineIntoView(lpLL, nFocusIndex);
      FrameTools_FB_FocusEdit(lpOutlineDoc->m_lpFrameTools);
   }

   OutlineApp_SetFormulaBarAccel(lpOutlineApp, fEditFocus);
}

/* OutlineDoc_IsEditFocusInFormulaBar
** ----------------------------------
**    Returns TRUE if edit focus is currently in the formula bar
**    else FALSE if not.
*/
BOOL OutlineDoc_IsEditFocusInFormulaBar(LPOUTLINEDOC lpOutlineDoc)
{
   if (! lpOutlineDoc || ! lpOutlineDoc->m_lpFrameTools)
      return FALSE;

   return lpOutlineDoc->m_lpFrameTools->m_fInFormulaBar;
}

/* OutlineDoc_UpdateFrameToolButtons
** ---------------------------------
**    Update the Enable/Disable states of the buttons in the formula
**    bar and button bar.
*/
void OutlineDoc_UpdateFrameToolButtons(LPOUTLINEDOC lpOutlineDoc)
{
   if (! lpOutlineDoc || ! lpOutlineDoc->m_lpFrameTools)
      return;
   FrameTools_UpdateButtons(lpOutlineDoc->m_lpFrameTools, lpOutlineDoc);
}
#endif  // USE_FRAMETOOLS

/* OutlineDoc_EditLineCommand
 * --------------------------
 *
 *      Edit the current focus line.
 */
void OutlineDoc_EditLineCommand(LPOUTLINEDOC lpOutlineDoc)
{
   LPLINELIST lpLL = &lpOutlineDoc->m_LineList;
   HDC hDC = LineList_GetDC(lpLL);
   int nIndex = LineList_GetFocusLineIndex(lpLL);
   LPLINE lpLine = LineList_GetLine(lpLL, nIndex);
   int nOrgLineWidthInHimetric;
   int nNewLineWidthInHimetric;
   BOOL fSizeChanged;

   if (!lpLine)
      return;

   nOrgLineWidthInHimetric = Line_GetTotalWidthInHimetric(lpLine);
   if (Line_Edit(lpLine, lpOutlineDoc->m_hWndDoc, hDC)) {
      nNewLineWidthInHimetric = Line_GetTotalWidthInHimetric(lpLine);

      if (nNewLineWidthInHimetric > nOrgLineWidthInHimetric) {
         fSizeChanged = LineList_SetMaxLineWidthInHimetric(
               lpLL,
               nNewLineWidthInHimetric
            );
      } else {
         fSizeChanged = LineList_RecalcMaxLineWidthInHimetric(
               lpLL,
               nOrgLineWidthInHimetric
            );
      }

#if defined( OLE_SERVER )
      /* Update Name Table */
      ServerNameTable_EditLineUpdate(
            (LPSERVERNAMETABLE)lpOutlineDoc->m_lpNameTable,
            nIndex
      );
#endif

      OutlineDoc_SetModified(lpOutlineDoc, TRUE, TRUE, fSizeChanged);

      LineList_ForceLineRedraw(lpLL, nIndex, TRUE);
   }
   LineList_ReleaseDC(lpLL, hDC);
}

/* OutlineDoc_IndentCommand
 * ------------------------
 *
 *      Indent selection of lines
 */
void OutlineDoc_IndentCommand(LPOUTLINEDOC lpOutlineDoc)
{
   LPLINELIST  lpLL = &lpOutlineDoc->m_LineList;
   LPLINE      lpLine;
   HDC         hDC = LineList_GetDC(lpLL);
   int         i;
   int         nIndex;
   int         nNumSel;
   LINERANGE   lrSel;
   BOOL        fSizeChanged = FALSE;

   nNumSel=LineList_GetSel(lpLL, (LPLINERANGE)&lrSel);

   OutlineDoc_SetRedraw ( lpOutlineDoc, FALSE );

   for(i = 0; i < nNumSel; i++) {
      nIndex = lrSel.m_nStartLine + i;
      lpLine=LineList_GetLine(lpLL, nIndex);
      if (! lpLine)
         continue;

      Line_Indent(lpLine, hDC);
      if (LineList_SetMaxLineWidthInHimetric(lpLL,
         Line_GetTotalWidthInHimetric(lpLine))) {
         fSizeChanged = TRUE;
      }
      LineList_ForceLineRedraw(lpLL, nIndex, TRUE);

#if defined( OLE_SERVER )
      /* Update Name Table */
      ServerNameTable_EditLineUpdate(
            (LPSERVERNAMETABLE)lpOutlineDoc->m_lpNameTable,
            nIndex
      );
#endif

   }

   LineList_ReleaseDC(lpLL, hDC);

   OutlineDoc_SetModified(lpOutlineDoc, TRUE, TRUE, fSizeChanged);
   OutlineDoc_SetRedraw ( lpOutlineDoc, TRUE );
}

/* OutlineDoc_UnindentCommand
 * --------------------------
 *
 *      Unindent selection of lines
 */
void OutlineDoc_UnindentCommand(LPOUTLINEDOC lpOutlineDoc)
{
   LPLINELIST  lpLL = &lpOutlineDoc->m_LineList;
   LPLINE      lpLine;
   HDC         hDC = LineList_GetDC(lpLL);
   int         nOrgLineWidthInHimetric;
   int         nOrgMaxLineWidthInHimetric = 0;
   int         i;
   int         nIndex;
   int         nNumSel;
   LINERANGE   lrSel;
   BOOL        fSizeChanged;

   nNumSel=LineList_GetSel(lpLL, (LPLINERANGE)&lrSel);

   OutlineDoc_SetRedraw ( lpOutlineDoc, FALSE );

   for(i = 0; i < nNumSel; i++) {
      nIndex = lrSel.m_nStartLine + i;
      lpLine=LineList_GetLine(lpLL, nIndex);
      if (!lpLine)
         continue;

      nOrgLineWidthInHimetric = Line_GetTotalWidthInHimetric(lpLine);
      nOrgMaxLineWidthInHimetric =
            (nOrgLineWidthInHimetric > nOrgMaxLineWidthInHimetric ?
               nOrgLineWidthInHimetric : nOrgMaxLineWidthInHimetric);
      Line_Unindent(lpLine, hDC);
      LineList_ForceLineRedraw(lpLL, nIndex, TRUE);

#if defined( OLE_SERVER )
      /* Update Name Table */
      ServerNameTable_EditLineUpdate(
            (LPSERVERNAMETABLE)lpOutlineDoc->m_lpNameTable,
            nIndex
      );
#endif

   }

   LineList_ReleaseDC(lpLL, hDC);

   fSizeChanged = LineList_RecalcMaxLineWidthInHimetric(
         lpLL,
         nOrgMaxLineWidthInHimetric
      );

   OutlineDoc_SetModified(lpOutlineDoc, TRUE, TRUE, fSizeChanged);
   OutlineDoc_SetRedraw ( lpOutlineDoc, TRUE );
}

/* OutlineDoc_SetLineHeightCommand
 * -------------------------------
 *
 *      Set height of the selection of lines
 */
void OutlineDoc_SetLineHeightCommand(LPOUTLINEDOC lpOutlineDoc)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPLINELIST  lpLL;
   HDC         hDC;
   LPLINE      lpLine;
   int         nNewHeight;
   int         i;
   int         nIndex;
   int         nNumSel;
   LINERANGE   lrSel;

   if (!lpOutlineDoc)
      return;

   lpLL = &lpOutlineDoc->m_LineList;
   nNumSel=LineList_GetSel(lpLL, (LPLINERANGE)&lrSel);
   lpLine = LineList_GetLine(lpLL, lrSel.m_nStartLine);
   if (!lpLine)
      return;

   nNewHeight = Line_GetHeightInHimetric(lpLine);

#if defined( OLE_VERSION )
   OleApp_PreModalDialog((LPOLEAPP)lpOutlineApp, (LPOLEDOC)lpOutlineDoc);
#endif

   DialogBoxParam(
         lpOutlineApp->m_hInst,
         (LPSTR)"SetLineHeight",
         lpOutlineDoc->m_hWndDoc,
         (DLGPROC)SetLineHeightDlgProc,
         (LPARAM)(LPINT)&nNewHeight
   );

#if defined( OLE_VERSION )
   OleApp_PostModalDialog((LPOLEAPP)lpOutlineApp, (LPOLEDOC)lpOutlineDoc);
#endif

   if (nNewHeight == 0)
      return;     /* user hit cancel */

   hDC = LineList_GetDC(lpLL);

   for (i = 0; i < nNumSel; i++) {
      nIndex = lrSel.m_nStartLine + i;
      lpLine=LineList_GetLine(lpLL, nIndex);
      if (nNewHeight == -1) {
         switch (Line_GetLineType(lpLine)) {

            case TEXTLINETYPE:

               TextLine_CalcExtents((LPTEXTLINE)lpLine, hDC);
               break;

#if defined( OLE_CNTR )
            case CONTAINERLINETYPE:

               ContainerLine_SetHeightInHimetric(
                     (LPCONTAINERLINE)lpLine, -1);
               break;
#endif

         }
      }
      else
         Line_SetHeightInHimetric(lpLine, nNewHeight);

      LineList_SetLineHeight(lpLL, nIndex,
            Line_GetHeightInHimetric(lpLine));
   }

   LineList_ReleaseDC(lpLL, hDC);

   OutlineDoc_SetModified(lpOutlineDoc, TRUE, TRUE, TRUE);
   LineList_ForceRedraw(lpLL, TRUE);
}

/* OutlineDoc_SelectAllCommand
 * ---------------------------
 *
 *      Select all the lines in the document.
 */
void OutlineDoc_SelectAllCommand(LPOUTLINEDOC lpOutlineDoc)
{
   LPLINELIST lpLL = &lpOutlineDoc->m_LineList;
   LINERANGE lrSel;

   lrSel.m_nStartLine = 0;
   lrSel.m_nEndLine = LineList_GetCount(lpLL) - 1;
   LineList_SetSel(lpLL, &lrSel);
}

/* OutlineDoc_DefineNameCommand
 * ----------------------------
 *
 *      Define a name in the document
 */
void OutlineDoc_DefineNameCommand(LPOUTLINEDOC lpOutlineDoc)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;

#if defined( OLE_VERSION )
   OleApp_PreModalDialog((LPOLEAPP)lpOutlineApp, (LPOLEDOC)lpOutlineDoc);
#endif

   DialogBoxParam(
         lpOutlineApp->m_hInst,
         (LPSTR)"DefineName",
         lpOutlineDoc->m_hWndDoc,
         (DLGPROC)DefineNameDlgProc,
         (LPARAM) lpOutlineDoc
   );

#if defined( OLE_VERSION )
   OleApp_PostModalDialog((LPOLEAPP)lpOutlineApp, (LPOLEDOC)lpOutlineDoc);
#endif
}

/* OutlineDoc_GotoNameCommand
 * --------------------------
 *
 *      Goto a predefined name in the document
 */
void OutlineDoc_GotoNameCommand(LPOUTLINEDOC lpOutlineDoc)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;

#if defined( OLE_VERSION )
   OleApp_PreModalDialog((LPOLEAPP)lpOutlineApp, (LPOLEDOC)lpOutlineDoc);
#endif

   DialogBoxParam(
         lpOutlineApp->m_hInst,
         (LPSTR)"GotoName",
         lpOutlineDoc->m_hWndDoc,
         (DLGPROC)GotoNameDlgProc,
         (LPARAM)lpOutlineDoc
   );

#if defined( OLE_VERSION )
   OleApp_PostModalDialog((LPOLEAPP)lpOutlineApp, (LPOLEDOC)lpOutlineDoc);
#endif
}

/* OutlineDoc_ShowWindow
 * ---------------------
 *
 *      Show the window of the document to the user.

*/
void OutlineDoc_ShowWindow(LPOUTLINEDOC lpOutlineDoc)
{
#if defined( _DEBUG )
   OleDbgAssertSz(lpOutlineDoc->m_docInitType != DOCTYPE_UNKNOWN,
            "OutlineDoc_ShowWindow: can't show unitialized document\r\n");
#endif
   if (lpOutlineDoc->m_docInitType == DOCTYPE_UNKNOWN)
      return;

#if defined( OLE_VERSION )
   // Call OLE version of this function instead
   OleDoc_ShowWindow((LPOLEDOC)lpOutlineDoc);
#else
   ShowWindow(lpOutlineDoc->m_hWndDoc, SW_SHOWNORMAL);
   SetForegroundWindow(lpOutlineDoc->m_hWndDoc);
#endif
}

#if defined( USE_FRAMETOOLS )

void OutlineDoc_AddFrameLevelTools(LPOUTLINEDOC lpOutlineDoc)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
#if defined( INPLACE_CNTR )
   // Call OLE In-Place Container version of this function instead
   ContainerDoc_AddFrameLevelTools((LPCONTAINERDOC)lpOutlineDoc);

#else   // ! INPLACE_CNTR
   RECT rcFrameRect;
   BORDERWIDTHS frameToolWidths;

#if defined( INPLACE_SVR )
   LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOutlineDoc;
   LPOLEINPLACEFRAME lpTopIPFrame=ServerDoc_GetTopInPlaceFrame(lpServerDoc);

   // if in-place active, add our tools to our in-place container's frame.
   if (lpTopIPFrame) {
      ServerDoc_AddFrameLevelTools(lpServerDoc);
      return;
   }
#endif  // INPLACE_SVR

   OutlineApp_GetFrameRect(g_lpApp, (LPRECT)&rcFrameRect);
   FrameTools_GetRequiredBorderSpace(
         lpOutlineDoc->m_lpFrameTools,
         (LPBORDERWIDTHS)&frameToolWidths
   );
   OutlineApp_SetBorderSpace(g_lpApp, (LPBORDERWIDTHS)&frameToolWidths);
   FrameTools_AttachToFrame(
         lpOutlineDoc->m_lpFrameTools, OutlineApp_GetWindow(lpOutlineApp));
   FrameTools_Move(lpOutlineDoc->m_lpFrameTools, (LPRECT)&rcFrameRect);
#endif  // ! INPLACE_CNTR

}

#endif  // USE_FRAMETOOLS

/* OutlineDoc_GetWindow
 * --------------------
 *
 *      Get the window handle of the document.
 */
HWND OutlineDoc_GetWindow(LPOUTLINEDOC lpOutlineDoc)
{
   if(! lpOutlineDoc) return NULL;
   return lpOutlineDoc->m_hWndDoc;
}

/* OutlineDoc_AddLine
 * ------------------
 *
 *      Add one line to the Document's LineList
 */
void OutlineDoc_AddLine(LPOUTLINEDOC lpOutlineDoc, LPLINE lpLine, int nIndex)
{
   LPLINELIST lpLL = &lpOutlineDoc->m_LineList;

   LineList_AddLine(lpLL, lpLine, nIndex);

   /* Update Name Table */
   OutlineNameTable_AddLineUpdate(lpOutlineDoc->m_lpNameTable, nIndex);

#if defined( INPLACE_CNTR )
   {
      LPCONTAINERDOC lpContainerDoc = (LPCONTAINERDOC)lpOutlineDoc;
      /* NOTE: after adding a line we need to
      **    update the PosRect of the In-Place active
      **    objects (if any) that follow the added line.
      **    NOTE: nIndex is index of line before new line.
      **          nIndex+1 is index of new line
      **          nIndex+2 is index of line after new line.
      */
      ContainerDoc_UpdateInPlaceObjectRects(lpContainerDoc, nIndex+2);
   }
#endif

   OutlineDoc_SetModified(lpOutlineDoc, TRUE, TRUE, TRUE);
}

/* OutlineDoc_DeleteLine
 * ---------------------
 *
 *
 *      Delete one line from the document's LineList
 */
void OutlineDoc_DeleteLine(LPOUTLINEDOC lpOutlineDoc, int nIndex)
{
   LPLINELIST lpLL = &lpOutlineDoc->m_LineList;

#if defined( OLE_CNTR )
   LPLINE lpLine = LineList_GetLine(lpLL, nIndex);
   LPSTORAGE lpStgDoc = NULL;
   OLECHAR szSaveStgName[CWCSTORAGENAME];
   BOOL fDeleteChildStg = FALSE;

   if (lpLine && (Line_GetLineType(lpLine) == CONTAINERLINETYPE) ) {

      /* NOTE: when a ContainerLine is being deleted by the user,
      **    it is important to delete the object's sub-storage
      **    otherwise it wastes space in the ContainerDoc's file.
      **    this function is called when lines are deleted by the
      **    Clear command and when lines are deleted by a DRAGMOVE
      **    operation.
      */
      LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)lpLine;

      // save name of child storage
//      LSTRCPYN(szSaveStgName, lpContainerLine->m_szStgName,sizeof(szSaveStgName));
      OLESTRCPY(szSaveStgName, lpContainerLine->m_szStgName);
      lpStgDoc = ((LPOLEDOC)lpContainerLine->m_lpDoc)->m_lpStg;
      fDeleteChildStg = TRUE;
   }
#endif  // OLE_CNTR

   LineList_DeleteLine(lpLL, nIndex);

#if defined( OLE_CNTR )
   if (fDeleteChildStg && lpStgDoc) {
      HRESULT hrErr;

      // delete the obsolete child storage. it is NOT fatal if this fails

      hrErr = lpStgDoc->lpVtbl->DestroyElement(lpStgDoc, szSaveStgName);

#if defined( _DEBUG )
      if (hrErr != NOERROR) {
         OleDbgOutHResult("IStorage::DestroyElement return", hrErr);
      }
#endif
   }
#endif  // OLE_CNTR

   /* Update Name Table */
   OutlineNameTable_DeleteLineUpdate(lpOutlineDoc->m_lpNameTable, nIndex);

#if defined( INPLACE_CNTR )
   {
      LPCONTAINERDOC lpContainerDoc = (LPCONTAINERDOC)lpOutlineDoc;
      /* NOTE: after deleting a line we need to
      **    update the PosRect of the In-Place active
      **    objects (if any).
      */
      ContainerDoc_UpdateInPlaceObjectRects(lpContainerDoc, nIndex);
   }
#endif

   OutlineDoc_SetModified(lpOutlineDoc, TRUE, TRUE, TRUE);

#if defined( OLE_VERSION )
   {
      LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
      LPOLEDOC    lpClipboardDoc = (LPOLEDOC)lpOutlineApp->m_lpClipboardDoc;

      /* NOTE: if the document that is the source of data on the
      **    clipborad has just had lines deleted, then the copied data
      **    is no longer considered a valid potential link source.
      **    disable the offering of CF_LINKSOURCE from the clipboard
      **    document. this avoids problems that arise when the
      **    editing operation changes or deletes the original data
      **    copied. we will not go to the trouble of determining if
      **    the deleted line actually is part of the link source.
      */
      if (lpClipboardDoc
         && lpClipboardDoc->m_fLinkSourceAvail
         && lpClipboardDoc->m_lpSrcDocOfCopy == (LPOLEDOC)lpOutlineDoc) {
         lpClipboardDoc->m_fLinkSourceAvail = FALSE;

         /* NOTE: since we are changing the list of formats on
         **    the clipboard (ie. removing CF_LINKSOURCE), we must
         **    call OleSetClipboard again. to be sure that the
         **    clipboard datatransfer document object does not get
         **    destroyed we will guard the call to OleSetClipboard
         **    within a pair of AddRef/Release.
         */
         OleDoc_AddRef((LPOLEDOC)lpClipboardDoc);    // guard obj life-time

         OLEDBG_BEGIN2("OleSetClipboard called\r\n")
         OleSetClipboard(
               (LPDATAOBJECT)&((LPOLEDOC)lpClipboardDoc)->m_DataObject);
         OLEDBG_END2

         OleDoc_Release((LPOLEDOC)lpClipboardDoc);    // rel. AddRef above
      }
   }
#endif  // OLE_VERSION
}

/* OutlineDoc_AddName
 * ------------------
 *
 *      Add a Name to the Document's NameTable
 */
void OutlineDoc_AddName(LPOUTLINEDOC lpOutlineDoc, LPOUTLINENAME lpOutlineName)
{
   LPOUTLINENAMETABLE lpOutlineNameTable = lpOutlineDoc->m_lpNameTable;

   OutlineNameTable_AddName(lpOutlineNameTable, lpOutlineName);

   OutlineDoc_SetModified(lpOutlineDoc, TRUE, FALSE, FALSE);
}

/* OutlineDoc_DeleteName
 * ---------------------
 *
 *
 *      Delete Name from the document's NameTable
 */
void OutlineDoc_DeleteName(LPOUTLINEDOC lpOutlineDoc, int nIndex)
{
   LPOUTLINENAMETABLE lpOutlineNameTable = lpOutlineDoc->m_lpNameTable;

   OutlineNameTable_DeleteName(lpOutlineNameTable, nIndex);

   OutlineDoc_SetModified(lpOutlineDoc, TRUE, FALSE, FALSE);
}

/* OutlineDoc_Destroy
 * ------------------
 *
 *  Free all memory that had been allocated for a document.
 *      this destroys the LineList & NameTable of the document.
 */
void OutlineDoc_Destroy(LPOUTLINEDOC lpOutlineDoc)
{
   LPLINELIST lpLL = &lpOutlineDoc->m_LineList;
#if defined( OLE_VERSION )
   LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;
   LPOLEDOC lpOleDoc = (LPOLEDOC)lpOutlineDoc;

   if (lpOleDoc->m_fObjIsDestroying)
      return;     // doc destruction is in progress
#endif  // OLE_VERSION

   OLEDBG_BEGIN3("OutlineDoc_Destroy\r\n");

#if defined( OLE_VERSION )

   /* NOTE: in order to guarantee that the application does not
   **    prematurely exit before the destruction of the document is
   **    complete, we intially AddRef the App refcnt later Release it.
   **    This initial AddRef is artificial; it simply guarantees that
   **    the app object does not get destroyed until the end of this
   **    routine.
   */
   OleApp_AddRef(lpOleApp);

   /* NOTE: perform processing required for OLE */
   OleDoc_Destroy(lpOleDoc);
#endif

   LineList_Destroy(lpLL);
   OutlineNameTable_Destroy(lpOutlineDoc->m_lpNameTable);

#if defined( USE_HEADING )
   if (! lpOutlineDoc->m_fDataTransferDoc)
      Heading_Destroy((LPHEADING)&lpOutlineDoc->m_heading);
#endif

#if defined( USE_FRAMETOOLS )
   if (! lpOutlineDoc->m_fDataTransferDoc)
      FrameTools_AssociateDoc(lpOutlineDoc->m_lpFrameTools, NULL);
#endif  // USE_FRAMETOOLS

   DestroyWindow(lpOutlineDoc->m_hWndDoc);
   Delete(lpOutlineDoc);   // free memory for doc itself
   OleDbgOut1("@@@@ DOC DESTROYED\r\n");

#if defined( OLE_VERSION )
   OleApp_Release(lpOleApp);       // release artificial AddRef above
#endif

   OLEDBG_END3
}

/* OutlineDoc_ReSize
 * -----------------
 *
 *  Resize the document and its components
 *
 * Parameter:
 *      lpRect  the new size of the document. Use current size if NULL
 */
void OutlineDoc_Resize(LPOUTLINEDOC lpOutlineDoc, LPRECT lpRect)
{
   RECT            rect;
   LPLINELIST      lpLL;

#if defined( USE_HEADING )
   LPHEADING       lphead;
#endif  // USE_HEADING

   LPSCALEFACTOR   lpscale;
   HWND            hWndLL;

   if (!lpOutlineDoc)
      return;

   lpLL = (LPLINELIST)&lpOutlineDoc->m_LineList;
   lpscale = (LPSCALEFACTOR)&lpOutlineDoc->m_scale;
   hWndLL = LineList_GetWindow(lpLL);

   if (lpRect) {
      CopyRect((LPRECT)&rect, lpRect);
      MoveWindow(lpOutlineDoc->m_hWndDoc, rect.left, rect.top,
            rect.right-rect.left, rect.bottom-rect.top, TRUE);
   }

   GetClientRect(lpOutlineDoc->m_hWndDoc, (LPRECT)&rect);

#if defined( USE_HEADING )
   lphead = OutlineDoc_GetHeading(lpOutlineDoc);
   rect.left += Heading_RH_GetWidth(lphead, lpscale);
   rect.top += Heading_CH_GetHeight(lphead, lpscale);
#endif  // USE_HEADING

   if (lpLL) {
      MoveWindow(hWndLL, rect.left, rect.top,
            rect.right-rect.left, rect.bottom-rect.top, TRUE);
   }

#if defined( USE_HEADING )
   if (lphead)
      Heading_Move(lphead, lpOutlineDoc->m_hWndDoc, lpscale);
#endif  // USE_HEADING

#if defined( INPLACE_CNTR )
   ContainerDoc_UpdateInPlaceObjectRects((LPCONTAINERDOC)lpOutlineDoc, 0);
#endif
}

/* OutlineDoc_GetNameTable
 * -----------------------
 *
 *      Get nametable associated with the line list
 */
LPOUTLINENAMETABLE OutlineDoc_GetNameTable(LPOUTLINEDOC lpOutlineDoc)
{
   if (!lpOutlineDoc)
      return NULL;
   else
      return lpOutlineDoc->m_lpNameTable;
}

/* OutlineDoc_GetLineList
 * ----------------------
 *
 *      Get listlist associated with the OutlineDoc
 */
LPLINELIST OutlineDoc_GetLineList(LPOUTLINEDOC lpOutlineDoc)
{
   if (!lpOutlineDoc)
      return NULL;
   else
      return (LPLINELIST)&lpOutlineDoc->m_LineList;
}

/* OutlineDoc_GetNameCount
 * -----------------------
 *
 * Return number of names in table
 */
int OutlineDoc_GetNameCount(LPOUTLINEDOC lpOutlineDoc)
{
   return OutlineNameTable_GetCount(lpOutlineDoc->m_lpNameTable);
}

/* OutlineDoc_GetLineCount
 * -----------------------
 *
 * Return number of lines in the LineList
 */
int OutlineDoc_GetLineCount(LPOUTLINEDOC lpOutlineDoc)
{
   return LineList_GetCount(&lpOutlineDoc->m_LineList);
}

/* OutlineDoc_SetFileName
 * ----------------------
 *
 *      Set the filename of a document.
 *
 *  NOTE: If the ServerDoc has a valid filename then, the object is
 *  registered in the running object table (ROT). if the name of the doc
 *  changes (eg. via SaveAs) then the previous registration must be revoked
 *  and the document re-registered under the new name.
 */
BOOL OutlineDoc_SetFileName(LPOUTLINEDOC lpOutlineDoc, LPOLESTR lpszNewFileName, LPSTORAGE lpNewStg)
{
   OleDbgAssertSz(lpszNewFileName != NULL,   "Can't reset doc to Untitled!");
   if (lpszNewFileName == NULL)
      return FALSE;

//   AnsiLowerBuff(lpszNewFileName, (UINT)OLESTRLEN(lpszNewFileName));
   CharLowerBuffW(lpszNewFileName, OLESTRLEN(lpszNewFileName));

#if defined( OLE_CNTR )
   {
      LPCONTAINERDOC lpContainerDoc = (LPCONTAINERDOC)lpOutlineDoc;
      LPOLEDOC lpOleDoc = (LPOLEDOC)lpOutlineDoc;

      /* NOTE: the container version of the application keeps its
      **    storage open at all times. if the document's storage is not
      **    open, then open it.
      */

      if (lpNewStg) {

         /* CASE 1 -- document is being loaded from a file. lpNewStg is
         **    still open from the OutlineDoc_LoadFromFile function.
         */

         lpOutlineDoc->m_docInitType = DOCTYPE_FROMFILE;

      } else {

         /* CASE 2 -- document is being associated with a valid file
         **    that is not yet open. thus we must now open the file.
         */

         if (lpOutlineDoc->m_docInitType == DOCTYPE_FROMFILE &&
               OLESTRCMP(lpOutlineDoc->m_szFileName,lpszNewFileName)==0) {

            /* CASE 2a -- new filename is same as current file. if the
            **    stg is already open, then the lpStg is still valid.
            **    if it is not open, then open it.
            */
            if (! lpOleDoc->m_lpStg) {
               lpOleDoc->m_lpStg = OleStdOpenRootStorage(
                     lpszNewFileName,
                     STGM_READWRITE | STGM_SHARE_DENY_WRITE
               );
               if (! lpOleDoc->m_lpStg) return FALSE;
            }

         } else {

            /* CASE 2b -- new filename is NOT same as current file.
            **    a SaveAs operation is pending. open the new file and
            **    hold the storage pointer in m_lpNewStg. the
            **    subsequent call to Doc_SaveToFile will save the
            **    document into the new storage pointer and release the
            **    old storage pointer.
            */

            lpOutlineDoc->m_docInitType = DOCTYPE_FROMFILE;

            lpContainerDoc->m_lpNewStg = OleStdCreateRootStorage(
                  lpszNewFileName,
                  STGM_READWRITE | STGM_SHARE_DENY_WRITE | STGM_CREATE
            );
            if (! lpContainerDoc->m_lpNewStg) return FALSE;
         }
      }
   }
#endif      // OLE_CNTR

   if (lpOutlineDoc->m_docInitType != DOCTYPE_FROMFILE ||
      OLESTRCMP(lpOutlineDoc->m_szFileName, lpszNewFileName) != 0) {

      /* A new valid file name is being associated with the document */

      OLESTRCPY(lpOutlineDoc->m_szFileName, lpszNewFileName);
      lpOutlineDoc->m_docInitType = DOCTYPE_FROMFILE;

      // set lpszDocTitle to point to filename without path
      lpOutlineDoc->m_lpszDocTitle = lpOutlineDoc->m_szFileName +
         OLESTRLEN(lpOutlineDoc->m_szFileName) - 1;
      while (lpOutlineDoc->m_lpszDocTitle > lpOutlineDoc->m_szFileName
         && ! IS_FILENAME_DELIM(lpOutlineDoc->m_lpszDocTitle[-1])) {
         lpOutlineDoc->m_lpszDocTitle--;
      }

      OutlineDoc_SetTitle(lpOutlineDoc, TRUE /*fMakeUpperCase*/);

#if defined( OLE_VERSION )
      {
         /* NOTE: both containers and servers must properly
         **    register in the RunningObjectTable. if the document
         **    is performing a SaveAs operation, then it must
         **    re-register in the ROT with the new moniker. in
         **    addition any embedded object, pseudo objects, and/or
         **    linking clients must be informed that the document's
         **    moniker has changed.
         */

         LPOLEDOC lpOleDoc = (LPOLEDOC)lpOutlineDoc;

         if (lpOleDoc->m_lpFileMoniker) {
            OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpFileMoniker);
            lpOleDoc->m_lpFileMoniker = NULL;
         }

         CreateFileMoniker(lpszNewFileName,&lpOleDoc->m_lpFileMoniker);
         OleDoc_DocRenamedUpdate(lpOleDoc, lpOleDoc->m_lpFileMoniker);
      }
#endif      // OLE_VERSION

   }

   return TRUE;
}

/* OutlineDoc_SetTitle
 * -------------------
 *
 * Set window text to be current filename.
 * The following window hierarchy exits:
 *      hWndApp
 *          hWndDoc
 *              hWndListBox
 *  The frame window is the window which gets the title.
 */
void OutlineDoc_SetTitle(LPOUTLINEDOC lpOutlineDoc, BOOL fMakeUpperCase)
{
   HWND hWnd;
   LPSTR lpszText;
   char  szAnsiStr[256];

   if (!lpOutlineDoc->m_hWndDoc) return;
   if ((hWnd = GetParent(lpOutlineDoc->m_hWndDoc)) == NULL) return;

   lpszText = OleStdMalloc((UINT)(OLESTRLEN(APPNAME) + 4 +
                           OLESTRLEN(lpOutlineDoc->m_lpszDocTitle)));
   if (!lpszText) return;

   W2A(APPNAME, lpszText, OLESTRLEN(APPNAME)+1);
   lstrcat(lpszText," - ");
   W2A (lpOutlineDoc->m_lpszDocTitle, szAnsiStr, 256);
   lstrcat(lpszText, (LPSTR)szAnsiStr);

   if (fMakeUpperCase)
      AnsiUpperBuff(lpszText, (UINT)lstrlen(lpszText));

   SetWindowText(hWnd,lpszText);
   OleStdFree(lpszText);
}

/* OutlineDoc_Close
 * ----------------
 *
 * Close active document. If modified, prompt the user if
 * he wants to save.
 *
 *  Returns:
 *      FALSE -- user canceled the closing of the doc.
 *      TRUE -- the doc was successfully closed
 */
BOOL OutlineDoc_Close(LPOUTLINEDOC lpOutlineDoc, DWORD dwSaveOption)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;

#if defined( OLE_VERSION )
   /* NOTE: call OLE specific function instead */
   return OleDoc_Close((LPOLEDOC)lpOutlineDoc, dwSaveOption);

#else

   if (! lpOutlineDoc)
      return TRUE;            // active doc's are already destroyed

   if (! OutlineDoc_CheckSaveChanges(lpOutlineDoc, &dwSaveOption))
      return FALSE;           // abort closing the doc

   OutlineDoc_Destroy(lpOutlineDoc);

   OutlineApp_DocUnlockApp(lpOutlineApp, lpOutlineDoc);

   return TRUE;

#endif      // ! OLE_VERSION
}

/* OutlineDoc_CheckSaveChanges
 * ---------------------------
 *
 * Check if the document has been modified. if so, prompt the user if
 *      the changes should be saved. if yes save them.
 * Returns TRUE if the doc is safe to close (user answered Yes or No)
 *         FALSE if the user canceled the save changes option.
 */
BOOL OutlineDoc_CheckSaveChanges(
      LPOUTLINEDOC        lpOutlineDoc,
      LPDWORD             lpdwSaveOption
)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   int nResponse;
   char szAnsiString[256];
   char szAnsiString2[256];

   if (*lpdwSaveOption == OLECLOSE_NOSAVE)
      return TRUE;

   if(! OutlineDoc_IsModified(lpOutlineDoc))
      return TRUE;    // saving is not necessary

   /* NOTE: our document is dirty so it needs to be saved. if
   **    OLECLOSE_PROMPTSAVE the user should be prompted to see if the
   **    document should be saved. is specified but the document is NOT
   **    visible to the user, then the user can NOT be prompted. in
   **    the situation the document should be saved without prompting.
   **    if OLECLOSE_SAVEIFDIRTY is specified then, the document
   **    should also be saved without prompting.
   */
   if (*lpdwSaveOption == OLECLOSE_PROMPTSAVE &&
         IsWindowVisible(lpOutlineDoc->m_hWndDoc)) {

      // prompt the user to see if changes should be saved.
#if defined( OLE_VERSION )
      OleApp_PreModalDialog(
            (LPOLEAPP)lpOutlineApp, (LPOLEDOC)lpOutlineApp->m_lpDoc);
#endif
      W2A (APPNAME, szAnsiString, 256);
      W2A (MsgSaveFile, szAnsiString2, 256);
      nResponse = MessageBox(
            lpOutlineApp->m_hWndApp,
            szAnsiString2,
            szAnsiString,
            MB_ICONQUESTION | MB_YESNOCANCEL
      );
#if defined( OLE_VERSION )
      OleApp_PostModalDialog(
            (LPOLEAPP)lpOutlineApp, (LPOLEDOC)lpOutlineApp->m_lpDoc);
#endif
      if(nResponse==IDCANCEL)
         return FALSE;   // close is canceled
      if(nResponse==IDNO) {
         // Reset the save option to NOSAVE per user choice
         *lpdwSaveOption = OLECLOSE_NOSAVE;
         return TRUE;    // don't save, but is ok to close
      }
   } else if (*lpdwSaveOption != OLECLOSE_SAVEIFDIRTY) {
      OleDbgAssertSz(FALSE, "Invalid dwSaveOption\r\n");
      *lpdwSaveOption = OLECLOSE_NOSAVE;
      return TRUE;        // unknown *lpdwSaveOption; close w/o saving
   }

#if defined( OLE_SERVER )
   if (lpOutlineDoc->m_docInitType == DOCTYPE_EMBEDDED) {
      LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOutlineDoc;
      HRESULT hrErr;

      /* NOTE: Update the container before closing without prompting
      **    the user. To update the container, we must ask our container
      **    to save us.
      */
      OleDbgAssert(lpServerDoc->m_lpOleClientSite != NULL);
      OLEDBG_BEGIN2("IOleClientSite::SaveObject called\r\n")
      hrErr = lpServerDoc->m_lpOleClientSite->lpVtbl->SaveObject(
            lpServerDoc->m_lpOleClientSite
      );
      OLEDBG_END2

      if (hrErr != NOERROR) {
         OleDbgOutHResult("IOleClientSite::SaveObject returned", hrErr);
         return FALSE;
      }

      return TRUE;    // doc is safe to be closed

   } else
#endif      // OLE_SERVER
   {
      return OutlineApp_SaveCommand(lpOutlineApp);
   }
}

/* OutlineDoc_IsModified
 * ---------------------
 *
 * Return modify flag of OUTLINEDOC
 */
BOOL OutlineDoc_IsModified(LPOUTLINEDOC lpOutlineDoc)
{
   if (lpOutlineDoc->m_fModified)
      return lpOutlineDoc->m_fModified;

#if defined( OLE_CNTR )
   {
      /* NOTE: if there are OLE objects, then we must ask if any of
      **    them are dirty. if so we must consider our document
      **    as modified.
      */
      LPCONTAINERDOC lpContainerDoc = (LPCONTAINERDOC)lpOutlineDoc;
      LPLINELIST  lpLL;
      int         nLines;
      int         nIndex;
      LPLINE      lpLine;
      HRESULT     hrErr;

      lpLL = (LPLINELIST)&((LPOUTLINEDOC)lpContainerDoc)->m_LineList;
      nLines = LineList_GetCount(lpLL);

      for (nIndex = 0; nIndex < nLines; nIndex++) {
         lpLine = LineList_GetLine(lpLL, nIndex);
         if (!lpLine)
            break;
         if (Line_GetLineType(lpLine) == CONTAINERLINETYPE) {
            LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)lpLine;
            if (lpContainerLine->m_lpPersistStg) {
               hrErr = lpContainerLine->m_lpPersistStg->lpVtbl->IsDirty(
                     lpContainerLine->m_lpPersistStg);
               /* NOTE: we will only accept an explicit "no i
               **    am NOT dirty statement" (ie. S_FALSE) as an
               **    indication that the object is clean. eg. if
               **    the object returns E_NOTIMPL we must
               **    interpret it as the object IS dirty.
               */
               if (GetScode(hrErr) != S_FALSE)
                  return TRUE;
            }
         }
      }
   }
#endif
   return FALSE;
}

/* OutlineDoc_SetModified
 * ----------------------
 *
 *  Set the modified flag of the document
 *
 */
void OutlineDoc_SetModified(LPOUTLINEDOC lpOutlineDoc, BOOL fModified, BOOL fDataChanged, BOOL fSizeChanged)
{
   lpOutlineDoc->m_fModified = fModified;

#if defined( OLE_SERVER )
   if (! lpOutlineDoc->m_fDataTransferDoc) {
      LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOutlineDoc;

      /* NOTE: if the document has changed, then broadcast the change
      **    to all clients who have set up Advise connections. notify
      **    them that our data (and possibly also our extents) have
      **    changed.
      */
      if (fDataChanged) {
         lpServerDoc->m_fDataChanged     = TRUE;
         lpServerDoc->m_fSizeChanged     = fSizeChanged;
         lpServerDoc->m_fSendDataOnStop  = TRUE;

         ServerDoc_SendAdvise(
               lpServerDoc,
               OLE_ONDATACHANGE,
               NULL,   /* lpmkDoc -- not relevant here */
               0       /* advf -- no flags necessary */
         );
      }
   }
#endif  // OLE_SERVER
}

/* OutlineDoc_SetRedraw
 * --------------------
 *
 *  Enable/Disable the redraw of the document on screen.
 *  The calls to SetRedraw counted so that nested calls can be handled
 *  properly. calls to SetRedraw must be balanced.
 *
 *  fEnbaleDraw = TRUE      - enable redraw
 *                FALSE     - disable redraw
 */
void OutlineDoc_SetRedraw(LPOUTLINEDOC lpOutlineDoc, BOOL fEnableDraw)
{
   static HCURSOR hPrevCursor = NULL;

   if (fEnableDraw) {
      if (lpOutlineDoc->m_nDisableDraw == 0)
         return;     // already enabled; no state transition

      if (--lpOutlineDoc->m_nDisableDraw > 0)
         return;     // drawing should still be disabled
   } else {
      if (lpOutlineDoc->m_nDisableDraw++ > 0)
         return;     // already disabled; no state transition
   }

   if (lpOutlineDoc->m_nDisableDraw > 0) {
      // this may take a while, put up hourglass cursor
      hPrevCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));
   } else {
      if (hPrevCursor) {
         SetCursor(hPrevCursor);     // restore original cursor
         hPrevCursor = NULL;
      }
   }

#if defined( OLE_SERVER )
   /* NOTE: for the Server version, while Redraw is disabled
   **    postpone sending advise notifications until Redraw is re-enabled.
   */
   {
      LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOutlineDoc;
      LPSERVERNAMETABLE lpServerNameTable =
            (LPSERVERNAMETABLE)lpOutlineDoc->m_lpNameTable;

      if (lpOutlineDoc->m_nDisableDraw == 0) {
         /* drawing is being Enabled. if changes occurred while drawing
         **    was disabled, then notify clients now.
         */
         if (lpServerDoc->m_fDataChanged)
            ServerDoc_SendAdvise(
                  lpServerDoc,
                  OLE_ONDATACHANGE,
                  NULL,   /* lpmkDoc -- not relevant here */
                  0       /* advf -- no flags necessary */
            );

         /* NOTE: send pending change notifications for pseudo objs. */
         ServerNameTable_SendPendingAdvises(lpServerNameTable);

      }
   }
#endif      // OLE_SERVER

#if defined( OLE_CNTR )
   /* NOTE: for the Container version, while Redraw is disabled
   **    postpone updating the extents of OLE objects until Redraw is
   **    re-enabled.
   */
   {
      LPCONTAINERDOC lpContainerDoc = (LPCONTAINERDOC)lpOutlineDoc;

      /* Update the extents of any OLE object that is marked that
      **    its size may  have changed. when an
      **    IAdviseSink::OnViewChange notification is received,
      **    the corresponding ContainerLine is marked
      **    (m_fDoGetExtent==TRUE) and a message
      **    (WM_U_UPDATEOBJECTEXTENT) is posted to the document
      **    indicating that there are dirty objects.
      */
      if (lpOutlineDoc->m_nDisableDraw == 0)
         ContainerDoc_UpdateExtentOfAllOleObjects(lpContainerDoc);
   }
#endif      // OLE_CNTR

   // enable/disable redraw of the LineList listbox
   LineList_SetRedraw(&lpOutlineDoc->m_LineList, fEnableDraw);
}

/* OutlineDoc_SetSel
 * -----------------
 *
 *      Set the selection in the documents's LineList
 */
void OutlineDoc_SetSel(LPOUTLINEDOC lpOutlineDoc, LPLINERANGE lplrSel)
{
   LineList_SetSel(&lpOutlineDoc->m_LineList, lplrSel);
}

/* OutlineDoc_GetSel
 * -----------------
 *
 *      Get the selection in the documents's LineList.
 *
 *      Returns the count of items selected
 */
int OutlineDoc_GetSel(LPOUTLINEDOC lpOutlineDoc, LPLINERANGE lplrSel)
{
   return LineList_GetSel(&lpOutlineDoc->m_LineList, lplrSel);
}

/* OutlineDoc_ForceRedraw
 * ----------------------
 *
 *      Force the document window to repaint.
 */
void OutlineDoc_ForceRedraw(LPOUTLINEDOC lpOutlineDoc, BOOL fErase)
{
   if (!lpOutlineDoc)
      return;

   LineList_ForceRedraw(&lpOutlineDoc->m_LineList, fErase);
   Heading_CH_ForceRedraw(&lpOutlineDoc->m_heading, fErase);
   Heading_RH_ForceRedraw(&lpOutlineDoc->m_heading, fErase);
}

/* OutlineDoc_RenderFormat
 * -----------------------
 *
 *      Render a clipboard format supported by ClipboardDoc
 */
void OutlineDoc_RenderFormat(LPOUTLINEDOC lpOutlineDoc, UINT uFormat)

{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   HGLOBAL      hData = NULL;

   if (uFormat == lpOutlineApp->m_cfOutline)
      hData = OutlineDoc_GetOutlineData(lpOutlineDoc, NULL);

   else if (uFormat == CF_TEXT)
      hData = OutlineDoc_GetTextData(lpOutlineDoc, NULL);

   else {
      OutlineApp_ErrorMessage(lpOutlineApp, ErrMsgFormatNotSupported);
      return;
   }

   SetClipboardData(uFormat, hData);
}

/* OutlineDoc_RenderAllFormats
 * ---------------------------
 *
 *      Render all formats supported by ClipboardDoc
 */
void OutlineDoc_RenderAllFormats(LPOUTLINEDOC lpOutlineDoc)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   HGLOBAL      hData = NULL;

   OpenClipboard(lpOutlineDoc->m_hWndDoc);

   hData = OutlineDoc_GetOutlineData(lpOutlineDoc, NULL);
   SetClipboardData(lpOutlineApp->m_cfOutline, hData);

   hData = OutlineDoc_GetTextData(lpOutlineDoc, NULL);
   SetClipboardData(CF_TEXT, hData);

   CloseClipboard();
}

/* OutlineDoc_GetOutlineData
 * -------------------------
 *
 * Return a handle to an array of TextLine objects for the desired line
 *      range.
 *  NOTE: if lplrSel == NULL, then all lines are returned
 *
 */
HGLOBAL OutlineDoc_GetOutlineData(LPOUTLINEDOC lpOutlineDoc, LPLINERANGE lplrSel)
{
   HGLOBAL     hOutline  = NULL;
   LPLINELIST  lpLL=(LPLINELIST)&lpOutlineDoc->m_LineList;
   LPLINE      lpLine;
   LPTEXTLINE  arrLine;
   int     i;
   int     nStart = (lplrSel ? lplrSel->m_nStartLine : 0);
   int     nEnd =(lplrSel ? lplrSel->m_nEndLine : LineList_GetCount(lpLL)-1);
   int     nLines = nEnd - nStart + 1;
   int     nCopied = 0;

   hOutline=GlobalAlloc(GALLOCFLG, sizeof(TEXTLINE)*nLines);

   if (! hOutline) return NULL;

   arrLine=(LPTEXTLINE)GlobalLock(hOutline);

   for (i = nStart; i <= nEnd; i++) {
      lpLine=LineList_GetLine(lpLL, i);
      if (lpLine && Line_GetOutlineData(lpLine, &arrLine[nCopied]))
         nCopied++;
   }

   GlobalUnlock(hOutline);

   return hOutline;
}

/* OutlineDoc_GetTextData
 * ----------------------
 *
 * Return a handle to an object's data in text form for the desired line
 *      range.
 *  NOTE: if lplrSel == NULL, then all lines are returned
 *
 */
HGLOBAL OutlineDoc_GetTextData(LPOUTLINEDOC lpOutlineDoc, LPLINERANGE lplrSel)
{
   LPLINELIST  lpLL=(LPLINELIST)&lpOutlineDoc->m_LineList;
   LPLINE  lpLine;
   HGLOBAL hText = NULL;
   LPOLESTR   lpszText = NULL;
   DWORD   dwMemSize=0;
   int     i,j;
   int     nStart = (lplrSel ? lplrSel->m_nStartLine : 0);
   int     nEnd =(lplrSel ? lplrSel->m_nEndLine : LineList_GetCount(lpLL)-1);
   int     nTabLevel;

   // calculate memory size required
   for(i = nStart; i <= nEnd; i++) {
      lpLine=LineList_GetLine(lpLL, i);
      if (! lpLine)
         continue;

      dwMemSize += Line_GetTabLevel(lpLine);
      dwMemSize += Line_GetTextLen(lpLine);

      dwMemSize += 2; // add 1 for '\r\n' at the end of each line
   }
   dwMemSize++;        // add 1 for '\0' at the end of string

   if(!(hText = GlobalAlloc(GALLOCFLG, dwMemSize)))
      return NULL;

   if(!(lpszText = /*(LPOLESTR)*/GlobalLock(hText)))
      return NULL;

   // put line text to memory
   for(i = nStart; i <= nEnd; i++) {
      lpLine=LineList_GetLine(lpLL, i);
      if (! lpLine)
         continue;

      nTabLevel=Line_GetTabLevel(lpLine);
      for(j = 0; j < nTabLevel; j++)
         *lpszText++='\t';

      Line_GetTextData(lpLine, lpszText);
      while(*lpszText)
         lpszText++;     // advance to end of string

      *lpszText++ = '\r';
      *lpszText++ = '\n';
   }

   GlobalUnlock (hText);

   return hText;
}

/* OutlineDoc_SaveToFile
 * ---------------------
 *
 *      Save the document to a file with the same name as stored in the
 * document
 */
BOOL OutlineDoc_SaveToFile(LPOUTLINEDOC lpOutlineDoc, LPCOLESTR lpszFileName, UINT uFormat, BOOL fRemember)
{
#if defined( OLE_CNTR )
   // Call OLE container specific function instead
   return ContainerDoc_SaveToFile(
         (LPCONTAINERDOC)lpOutlineDoc,
         lpszFileName,
         uFormat,
         fRemember
   );

#else

   LPSTORAGE lpDestStg = NULL;
   HRESULT hrErr;
   BOOL fStatus;
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;

   if (fRemember) {
      if (lpszFileName) {
         fStatus = OutlineDoc_SetFileName(
               lpOutlineDoc,
               (LPOLESTR)lpszFileName,
               NULL
         );
         if (! fStatus) goto error;
      } else
         lpszFileName = lpOutlineDoc->m_szFileName; // use cur. file name
   } else if (! lpszFileName) {
      goto error;
   }

   hrErr = StgCreateDocfile(
         lpszFileName,
         STGM_READWRITE|STGM_DIRECT|STGM_SHARE_EXCLUSIVE|STGM_CREATE,
         0,
         &lpDestStg
   );

   OleDbgAssertSz(hrErr == NOERROR, "Could not create Docfile");
   if (hrErr != NOERROR)
      goto error;

#if defined( OLE_SERVER )

   /*  NOTE: we must be sure to write our class ID into our
   **    storage. this information is used by OLE to determine the
   **    class of the data stored in our storage. Even for top
   **    "file-level" objects this information should be written to
   **    the file.
   */
   if(WriteClassStg(lpDestStg, &CLSID_APP) != NOERROR)
      goto error;
#endif

   fStatus = OutlineDoc_SaveSelToStg(
         lpOutlineDoc,
         NULL,
         uFormat,
         lpDestStg,
         FALSE,      /* fSameAsLoad */
         fRemember
   );
   if (! fStatus) goto error;

   OleStdRelease((LPUNKNOWN)lpDestStg);

   if (fRemember)
      OutlineDoc_SetModified(lpOutlineDoc, FALSE, FALSE, FALSE);

#if defined( OLE_SERVER )

   /* NOTE: (SERVER-ONLY) inform any linking clients that the
   **    document has been saved. in addition, any currently active
   **    pseudo objects should also inform their clients.
   */
   ServerDoc_SendAdvise (
         (LPSERVERDOC)lpOutlineDoc,
         OLE_ONSAVE,
         NULL,   /* lpmkDoc -- not relevant here */
         0       /* advf -- not relevant here */
   );

#endif

   return TRUE;

error:
   if (lpDestStg)
      OleStdRelease((LPUNKNOWN)lpDestStg);

   OutlineApp_ErrorMessage(lpOutlineApp, ErrMsgSaving);
   return FALSE;

#endif  // ! OLE_CNTR
}

/* OutlineDoc_LoadFromFile
 * -----------------------
 *
 *      Load a document from a file
 */
BOOL OutlineDoc_LoadFromFile(LPOUTLINEDOC lpOutlineDoc, LPOLESTR lpszFileName)
{
   LPOUTLINEAPP    lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPLINELIST      lpLL = &lpOutlineDoc->m_LineList;
   HRESULT         hrErr;
   SCODE           sc;
   LPSTORAGE       lpSrcStg;
   BOOL            fStatus;

   hrErr = StgOpenStorage(lpszFileName,
         NULL,
#if defined( OLE_CNTR )
         STGM_READWRITE | STGM_TRANSACTED | STGM_SHARE_DENY_WRITE,
#else
         STGM_READ | STGM_SHARE_DENY_WRITE,
#endif
         NULL,
         0,
         &lpSrcStg
   );

   if ((sc = GetScode(hrErr)) == STG_E_FILENOTFOUND) {
      OutlineApp_ErrorMessage(lpOutlineApp, OLESTR("File not found"));
      return FALSE;
   } else if (sc == STG_E_FILEALREADYEXISTS) {
      OutlineApp_ErrorMessage(lpOutlineApp, ErrMsgFormat);
      return FALSE;
   } else if (sc != S_OK) {
      OleDbgOutScode("StgOpenStorage returned", sc);
      OutlineApp_ErrorMessage(
            lpOutlineApp,
            OLESTR("File already in use--could not be opened")
      );
      return FALSE;
   }

   if(! OutlineDoc_LoadFromStg(lpOutlineDoc, lpSrcStg)) goto error;

   fStatus = OutlineDoc_SetFileName(lpOutlineDoc, lpszFileName, lpSrcStg);
   if (! fStatus) goto error;

   OleStdRelease((LPUNKNOWN)lpSrcStg);

   return TRUE;

error:
   OleStdRelease((LPUNKNOWN)lpSrcStg);
   OutlineApp_ErrorMessage(lpOutlineApp, ErrMsgOpening);
   return FALSE;
}

/* OutlineDoc_LoadFromStg
 * ----------------------
 *
 *  Load entire document from an open IStorage pointer (lpSrcStg)
 *      Return TRUE if ok, FALSE if error.
 */
BOOL OutlineDoc_LoadFromStg(LPOUTLINEDOC lpOutlineDoc, LPSTORAGE lpSrcStg)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   HRESULT hrErr;
   BOOL fStatus;
   ULONG nRead;
   LINERANGE lrSel = { 0, 0 };
   LPSTREAM lpLLStm;
   OUTLINEDOCHEADER docRecord;
   OUTLINEDOCHEADER_ONDISK docRecordOnDisk;

   hrErr = lpSrcStg->lpVtbl->OpenStream(
         lpSrcStg,
         OLESTR("LineList"),
         NULL,
         STGM_READ | STGM_SHARE_EXCLUSIVE,
         0,
         &lpLLStm
   );

   if (hrErr != NOERROR) {
      OleDbgOutHResult("Open LineList Stream returned", hrErr);
      goto error;
   }

   /* read OutlineDoc header record */
   hrErr = lpLLStm->lpVtbl->Read(
         lpLLStm,
         (LPVOID)&docRecordOnDisk,
         sizeof(docRecordOnDisk),
         &nRead
   );

   if (hrErr != NOERROR) {
      OleDbgOutHResult("Read OutlineDoc header returned", hrErr);
      goto error;
    }

    //  Transform docRecordOnDisk into docRecord
    //  Compilers should handle aligment correctly
    OLESTRCPY(docRecord.m_szFormatName, docRecordOnDisk.m_szFormatName);
    docRecord.m_narrAppVersionNo[0] = (int) docRecordOnDisk.m_narrAppVersionNo[0];
    docRecord.m_narrAppVersionNo[1] = (int) docRecordOnDisk.m_narrAppVersionNo[1];
    docRecord.m_fShowHeading = (BOOL) docRecordOnDisk.m_fShowHeading;
    docRecord.m_reserved1 = docRecordOnDisk.m_reserved1;
    docRecord.m_reserved2 = docRecordOnDisk.m_reserved2;
    docRecord.m_reserved3 = docRecordOnDisk.m_reserved3;
    docRecord.m_reserved4 = docRecordOnDisk.m_reserved4;

   fStatus = OutlineApp_VersionNoCheck(
         lpOutlineApp,
         docRecord.m_szFormatName,
         docRecord.m_narrAppVersionNo
   );

   /* storage is an incompatible version; file can not be read */
   if (! fStatus)
      goto error;

   lpOutlineDoc->m_heading.m_fShow = docRecord.m_fShowHeading;

#if defined( OLE_SERVER )
   {
      // Load ServerDoc specific data
      LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOutlineDoc;
#if defined( SVR_TREATAS )
      LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
      CLSID       clsid;
      CLIPFORMAT  cfFmt;
      LPOLESTR       lpszType;
#endif  // SVR_TREATAS

      lpServerDoc->m_nNextRangeNo = (ULONG)docRecord.m_reserved1;

#if defined( SVR_TREATAS )
      /* NOTE: if the Server is capable of supporting "TreatAs"
      **    (aka. ActivateAs), it must read the class that is written
      **    into the storage. if this class is NOT the app's own
      **    class ID, then this is a TreatAs operation. the server
      **    then must faithfully pretend to be the class that is
      **    written into the storage. it must also faithfully write
      **    the data back to the storage in the SAME format as is
      **    written in the storage.
      **
      **    SVROUTL and ISVROTL can emulate each other. they have the
      **    simplification that they both read/write the identical
      **    format. thus for these apps no actual conversion of the
      **    native bits is actually required.
      */
      lpServerDoc->m_clsidTreatAs = CLSID_NULL;
      if (OleStdGetTreatAsFmtUserType(&CLSID_APP, lpSrcStg, &clsid,
                     (CLIPFORMAT FAR*)&cfFmt, /*(LPOLESTR FAR*)*/&lpszType)) {

         if (cfFmt == lpOutlineApp->m_cfOutline) {
            // We should perform TreatAs operation
            if (lpServerDoc->m_lpszTreatAsType)
               OleStdFreeString(lpServerDoc->m_lpszTreatAsType, NULL);

            lpServerDoc->m_clsidTreatAs = clsid;
            ((LPOUTLINEDOC)lpServerDoc)->m_cfSaveFormat = cfFmt;
            lpServerDoc->m_lpszTreatAsType = lpszType;

            OleDbgOut3("OutlineDoc_LoadFromStg: TreateAs ==> '");
//          OleDbgOutNoPrefix3(lpServerDoc->m_lpszTreatAsType);
            OleDbgOutNoPrefix3("'\r\n");
         } else {
            // ERROR: we ONLY support TreatAs for CF_OUTLINE format
            OleDbgOut("SvrDoc_PStg_InitNew: INVALID TreatAs Format\r\n");
            OleStdFreeString(lpszType, NULL);
         }
      }
#endif  // SVR_TREATAS
   }
#endif  // OLE_SVR
#if defined( OLE_CNTR )
   {
      // Load ContainerDoc specific data
      LPCONTAINERDOC lpContainerDoc = (LPCONTAINERDOC)lpOutlineDoc;

      lpContainerDoc->m_nNextObjNo = (ULONG)docRecord.m_reserved2;
   }
#endif  // OLE_CNTR

   OutlineDoc_SetRedraw ( lpOutlineDoc, FALSE );

   if(! LineList_LoadFromStg(&lpOutlineDoc->m_LineList, lpSrcStg, lpLLStm))
      goto error;
   if(! OutlineNameTable_LoadFromStg(lpOutlineDoc->m_lpNameTable, lpSrcStg))
      goto error;

   OutlineDoc_SetModified(lpOutlineDoc, FALSE, FALSE, FALSE);
   OutlineDoc_SetSel(lpOutlineDoc, &lrSel);

   OutlineDoc_SetRedraw ( lpOutlineDoc, TRUE );

   OleStdRelease((LPUNKNOWN)lpLLStm);

#if defined( OLE_CNTR )
   {
      LPOLEDOC lpOleDoc = (LPOLEDOC)lpOutlineDoc;

      /* A ContainerDoc keeps its storage open at all times. it is necessary
      *   to AddRef the lpSrcStg in order to hang on to it.
      */
      if (lpOleDoc->m_lpStg) {
         OleStdVerifyRelease((LPUNKNOWN)lpOleDoc->m_lpStg,
               OLESTR("Doc Storage not released properly"));
      }
      lpSrcStg->lpVtbl->AddRef(lpSrcStg);
      lpOleDoc->m_lpStg = lpSrcStg;
   }
#endif      // OLE_CNTR

   return TRUE;

error:
   OutlineDoc_SetRedraw ( lpOutlineDoc, TRUE );
   if (lpLLStm)
      OleStdRelease((LPUNKNOWN)lpLLStm);
   return FALSE;
}

/* OutlineDoc_SaveSelToStg
 * -----------------------
 *
 *      Save the specified selection of document into an IStorage*. All lines
 * within the selection along with any names completely contained within the
 * selection will be written
 *
 *      Return TRUE if ok, FALSE if error
 */
BOOL OutlineDoc_SaveSelToStg(
      LPOUTLINEDOC        lpOutlineDoc,
      LPLINERANGE         lplrSel,
      UINT                uFormat,
      LPSTORAGE           lpDestStg,
      BOOL                fSameAsLoad,
      BOOL                fRemember
)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   HRESULT hrErr = NOERROR;
   LPSTREAM lpLLStm = NULL;
   LPSTREAM lpNTStm = NULL;
   ULONG nWritten;
   BOOL fStatus;
   OUTLINEDOCHEADER docRecord;
   OUTLINEDOCHEADER_ONDISK docRecordOnDisk;
   HCURSOR  hPrevCursor;
   char szAnsiString[256];

#if defined( OLE_VERSION )
   LPOLESTR lpszUserType;
   LPOLEDOC lpOleDoc = (LPOLEDOC)lpOutlineDoc;

   /*  NOTE: we must be sure to write the information required for
   **    OLE into our docfile. this includes user type
   **    name, data format, etc. Even for top "file-level" objects
   **    this information should be written to the file. Both
   **    containters and servers should write this information.
   */

#if defined( OLE_SERVER ) && defined( SVR_TREATAS )
   LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOutlineDoc;

   /* NOTE: if the Server is emulating another class (ie.
   **    "TreatAs" aka. ActivateAs), it must write the same user type
   **    name and format that was was originally written into the
   **    storage rather than its own user type name.
   **
   **    SVROUTL and ISVROTL can emulate each other. they have the
   **    simplification that they both read/write the identical
   **    format. thus for these apps no actual conversion of the
   **    native bits is actually required.
   */
   if (! IsEqualCLSID(&lpServerDoc->m_clsidTreatAs, &CLSID_NULL))
      lpszUserType = lpServerDoc->m_lpszTreatAsType;
   else
#endif  // OLE_SERVER && SVR_TREATAS

      lpszUserType = /*(LPOLESTR)*/FULLUSERTYPENAME;

   hrErr = WriteFmtUserTypeStg(lpDestStg, (CLIPFORMAT)uFormat, lpszUserType);
   if(hrErr != NOERROR) goto error;

   if (fSameAsLoad) {
      /* NOTE: we are saving into to same storage that we were
      **    passed an load time. we deliberatly opened the streams we
      **    need (lpLLStm and lpNTStm) at load time, so that we can
      **    robustly save at save time in a low-memory situation.
      **    this is particulary important the embedded objects do NOT
      **    consume additional memory when
      **    IPersistStorage::Save(fSameAsLoad==TRUE) is called.
      */
      LARGE_INTEGER libZero;
      ULARGE_INTEGER ulibZero;
      LISet32( libZero, 0 );
      LISet32( ulibZero, 0 );
      lpLLStm = lpOleDoc->m_lpLLStm;

      /*  because this is the fSameAsLoad==TRUE case, we will save
      **    into the streams that we hold open. we will AddRef the
      **    stream here so that the release below will NOT close the
      **    stream.
      */
      lpLLStm->lpVtbl->AddRef(lpLLStm);

      // truncate the current stream and seek to beginning
      lpLLStm->lpVtbl->SetSize(lpLLStm, ulibZero);
      lpLLStm->lpVtbl->Seek(
            lpLLStm, libZero, STREAM_SEEK_SET, NULL);

      lpNTStm = lpOleDoc->m_lpNTStm;
      lpNTStm->lpVtbl->AddRef(lpNTStm);   // (see comment above)

      // truncate the current stream and seek to beginning
      lpNTStm->lpVtbl->SetSize(lpNTStm, ulibZero);
      lpNTStm->lpVtbl->Seek(
            lpNTStm, libZero, STREAM_SEEK_SET, NULL);
   } else
#endif  // OLE_VERSION
   {
      hrErr = lpDestStg->lpVtbl->CreateStream(
            lpDestStg,
            OLESTR("LineList"),
            STGM_WRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE,
            0,
            0,
            &lpLLStm
      );
      if (hrErr != NOERROR) {
         OleDbgAssertSz(hrErr==NOERROR,"Could not create LineList stream");
         OleDbgOutHResult("LineList CreateStream returned", hrErr);
         goto error;
      }

      hrErr = lpDestStg->lpVtbl->CreateStream(
            lpDestStg,
            OLESTR("NameTable"),
            STGM_WRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE,
            0,
            0,
            &lpNTStm
      );
      if (hrErr != NOERROR) {
         OleDbgAssertSz(hrErr==NOERROR,"Could not create NameTable stream");
         OleDbgOutHResult("NameTable CreateStream returned", hrErr);
         goto error;
      }
   }

   // this may take a while, put up hourglass cursor
   hPrevCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));

   _fmemset((LPOUTLINEDOCHEADER)&docRecord,0,sizeof(OUTLINEDOCHEADER));
   GetClipboardFormatName(
         uFormat,
         szAnsiString,
         256
   );
   A2W(szAnsiString, docRecord.m_szFormatName, sizeof(docRecord.m_szFormatName) / sizeof(WCHAR));
   OutlineApp_GetAppVersionNo(lpOutlineApp, docRecord.m_narrAppVersionNo);

   docRecord.m_fShowHeading = lpOutlineDoc->m_heading.m_fShow;

#if defined( OLE_SERVER )
   {
      // Store ServerDoc specific data
      LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOutlineDoc;

      docRecord.m_reserved1 = (DWORD)lpServerDoc->m_nNextRangeNo;
   }
#endif
#if defined( OLE_CNTR )
   {
      // Store ContainerDoc specific data
      LPCONTAINERDOC lpContainerDoc = (LPCONTAINERDOC)lpOutlineDoc;

      docRecord.m_reserved2 = (DWORD)lpContainerDoc->m_nNextObjNo;
   }
#endif

   /* write OutlineDoc header record */

    //  Transform docRecord into docRecordOnDisk
    //  Compilers should handle aligment correctly
    OLESTRCPY(docRecordOnDisk.m_szFormatName, docRecord.m_szFormatName);
    docRecordOnDisk.m_narrAppVersionNo[0] = (short) docRecord.m_narrAppVersionNo[0];
    docRecordOnDisk.m_narrAppVersionNo[1] = (short) docRecord.m_narrAppVersionNo[1];
    docRecordOnDisk.m_fShowHeading = (USHORT) docRecord.m_fShowHeading;
    docRecordOnDisk.m_reserved1 = docRecord.m_reserved1;
    docRecordOnDisk.m_reserved2 = docRecord.m_reserved2;
    docRecordOnDisk.m_reserved3 = docRecord.m_reserved3;
    docRecordOnDisk.m_reserved4 = docRecord.m_reserved4;
   hrErr = lpLLStm->lpVtbl->Write(
         lpLLStm,
         (LPVOID)&docRecordOnDisk,
         sizeof(docRecordOnDisk),
         &nWritten
      );

   if (hrErr != NOERROR) {
      OleDbgOutHResult("Write OutlineDoc header returned", hrErr);
      goto error;
    }

   // Save LineList
   /* NOTE: A ContainerDoc keeps its storage open at all times. It is
   **    necessary to pass the current open storage (lpOleDoc->m_lpStg)
   **    to the LineList_SaveSelToStg method so that currently written data
   **    for any embeddings is also saved to the new destination
   **    storage. The data required by a contained object is both the
   **    ContainerLine information and the associated sub-storage that is
   **    written directly by the embedded object.
   */
   fStatus = LineList_SaveSelToStg(
      &lpOutlineDoc->m_LineList,
         lplrSel,
         uFormat,
#if defined( OLE_CNTR )
         lpOleDoc->m_lpStg,
#else
         NULL,
#endif
         lpDestStg,
         lpLLStm,
         fRemember
   );
   if (! fStatus) goto error;

   // Save associated NameTable
   fStatus = OutlineNameTable_SaveSelToStg(
         lpOutlineDoc->m_lpNameTable,
         lplrSel,
         uFormat,
         lpNTStm
   );

   if (! fStatus) goto error;

   OleStdRelease((LPUNKNOWN)lpLLStm);
   lpOutlineDoc->m_cfSaveFormat = uFormat;  // remember format used to save

   SetCursor(hPrevCursor);     // restore original cursor
   return TRUE;

error:
   if (lpLLStm)
      OleStdRelease((LPUNKNOWN)lpLLStm);

   SetCursor(hPrevCursor);     // restore original cursor
   return FALSE;
}

/* OutlineDoc_Print
 * ----------------
 *  Prints the contents of the list box in HIMETRIC mapping mode. Origin
 *  remains to be the upper left corner and the print proceeds down the
 *  page using a negative y-cordinate.
 *
 */
void OutlineDoc_Print(LPOUTLINEDOC lpOutlineDoc, HDC hDC)
{
   LPLINELIST lpLL = &lpOutlineDoc->m_LineList;
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   WORD    nIndex;
   WORD    nTotal;
   int     dy;
   BOOL    fError = FALSE;
   LPLINE  lpLine;
   RECT    rcLine;
   RECT    rcPix;
   RECT    rcHim;
   RECT    rcWindowOld;
   RECT    rcViewportOld;
   HFONT   hOldFont;
   DOCINFO di;         /* Document information for StartDoc function */
   char    szAnsiStr[256];

   /* Get dimension of page */
   rcPix.left = 0;
   rcPix.top = 0;
   rcPix.right = GetDeviceCaps(hDC, HORZRES);
   rcPix.bottom = GetDeviceCaps(hDC, VERTRES);

   SetDCToDrawInHimetricRect(hDC, (LPRECT)&rcPix, (LPRECT)&rcHim,
         (LPRECT)&rcWindowOld, (LPRECT)&rcViewportOld);

   // Set the default font size, and font face name
   hOldFont = SelectObject(hDC, lpOutlineApp->m_hStdFont);

   /* Get the lines in document */
   nIndex     = 0;
   nTotal  = LineList_GetCount(lpLL);

   /* Create the Cancel dialog */
   // REVIEW: should load dialog title from string resource file
   hWndPDlg = CreateDialog (
         lpOutlineApp->m_hInst,
         "Print",
         lpOutlineApp->m_hWndApp,
         (DLGPROC)PrintDlgProc
   );

   if(!hWndPDlg)
      goto getout;

   /* Allow the app. to inform GDI of the abort function to call */
   if(SetAbortProc(hDC, (ABORTPROC)AbortProc) < 0) {
      fError = TRUE;
      goto getout3;
   }

   /* Disable the main application window */
   EnableWindow (lpOutlineApp->m_hWndApp, FALSE);

   // initialize the rectangle for the first line
   rcLine.left = rcHim.left;
   rcLine.bottom = rcHim.top;

   /* Initialize the document */
   fCancelPrint = FALSE;

   di.cbSize = sizeof(DOCINFO);
   W2A (lpOutlineDoc->m_lpszDocTitle, szAnsiStr, 256);
   di.lpszDocName = szAnsiStr;
   di.lpszOutput = NULL;

   if(StartDoc(hDC, (DOCINFO FAR*)&di) <= 0) {
      fError = TRUE;
      OleDbgOut2("StartDoc error\n");
      goto getout5;
   }

   if(StartPage(hDC) <= 0) {       // start first page
      fError = TRUE;
      OleDbgOut2("StartPage error\n");
      goto getout2;
   }

   /* While more lines print out the text */
   while(nIndex < nTotal) {
      lpLine = LineList_GetLine(lpLL, nIndex);
      if (! lpLine)
         continue;

      dy = Line_GetHeightInHimetric(lpLine);

      /* Reached end of page. Tell the device driver to eject a page */
      if(rcLine.bottom - dy < rcHim.bottom) {
         if (EndPage(hDC) < 0) {
            fError=TRUE;
            OleDbgOut2("EndPage error\n");
            goto getout2;
         }

         // NOTE: Reset the Mapping mode of DC
         SetDCToDrawInHimetricRect(hDC, (LPRECT)&rcPix, (LPRECT)&rcHim,
               (LPRECT)&rcWindowOld, (LPRECT)&rcViewportOld);

         // Set the default font size, and font face name
         SelectObject(hDC, lpOutlineApp->m_hStdFont);

         if (StartPage(hDC) <= 0) {
            fError=TRUE;
            OleDbgOut2("StartPage error\n");
            goto getout2;
         }

         rcLine.bottom = rcHim.top;
      }

      rcLine.top = rcLine.bottom;
      rcLine.bottom -= dy;
      rcLine.right = rcLine.left + Line_GetWidthInHimetric(lpLine);

      /* Print the line */
      Line_Draw(lpLine, hDC, &rcLine, NULL, FALSE /*fHighlight*/);

      OleDbgOut2("a line is drawn\n");

      /* Test and see if the Abort flag has been set. If yes, exit. */
      if (fCancelPrint)
         goto getout2;

      /* Move down the page */
      nIndex++;
   }

   {
      int nCode;

      /* Eject the last page. */
      if((nCode = EndPage(hDC)) < 0) {
#if defined( _DEBUG )
         char szBuf[255];
         wsprintf(szBuf, "EndPage error code is %d\n", nCode);
         OleDbgOut2(szBuf);
#endif
         fError=TRUE;
         goto getout2;
      }
   }

   /* Complete the document. */
   if(EndDoc(hDC) < 0) {
      fError=TRUE;
      OleDbgOut2("EndDoc error\n");

getout2:
      /* Ran into a problem before NEWFRAME? Abort the document */
      AbortDoc(hDC);
   }

getout5:
   /* Re-enable main app. window */
   EnableWindow (lpOutlineApp->m_hWndApp, TRUE);

getout3:
   /* Close the cancel dialog */
   DestroyWindow (hWndPDlg);

getout:

   /* Error? make sure the user knows... */
   if(fError || CommDlgExtendedError())
      OutlineApp_ErrorMessage(lpOutlineApp, ErrMsgPrint);

   SelectObject(hDC, hOldFont);
}

/* OutlineDoc_DialogHelp
 * ---------------------
 *
 *  Show help message for ole2ui dialogs.
 *
 * Parameters:
 *
 *   hDlg      HWND to the dialog the help message came from - use
 *             this in the call to WinHelp/MessageBox so that
 *             activation/focus goes back to the dialog, and not the
 *             main window.
 *
 *   wParam    ID of the dialog (so we know what type of dialog it is).
 */
void OutlineDoc_DialogHelp(HWND hDlg, WPARAM wDlgID)
{

   char szMessageBoxText[64];

   if (!IsWindow(hDlg))  // don't do anything if we've got a bogus hDlg.
    return;

   lstrcpy(szMessageBoxText, "Help Message for ");

   switch (wDlgID)
   {

   case IDD_CONVERT:
      lstrcat(szMessageBoxText, "Convert");
      break;

   case IDD_CHANGEICON:
      lstrcat(szMessageBoxText, "Change Icon");
      break;

   case IDD_INSERTOBJECT:
      lstrcat(szMessageBoxText, "Insert Object");
      break;

   case IDD_PASTESPECIAL:
      lstrcat(szMessageBoxText, "Paste Special");
      break;

   case IDD_EDITLINKS:
      lstrcat(szMessageBoxText, "Edit Links");
      break;

   case IDD_CHANGESOURCE:
      lstrcat(szMessageBoxText, "Change Source");
      break;

   case IDD_INSERTFILEBROWSE:
      lstrcat(szMessageBoxText, "Insert From File Browse");
      break;

   case IDD_CHANGEICONBROWSE:
      lstrcat(szMessageBoxText, "Change Icon Browse");
      break;

   default:
      lstrcat(szMessageBoxText, "Unknown");
      break;
   }

   lstrcat(szMessageBoxText, " Dialog.");

   // You'd probably really a call to WinHelp here.
   MessageBox(hDlg, szMessageBoxText, "Help", MB_OK);

   return;
}

/* OutlineDoc_SetCurrentZoomCommand
 * --------------------------------
 *
 *  Set current zoom level to be checked in the menu.
 *  Set the corresponding scalefactor for the document.
 */
void OutlineDoc_SetCurrentZoomCommand(
      LPOUTLINEDOC        lpOutlineDoc,
      UINT                uCurrentZoom
)
{
   SCALEFACTOR scale;

   if (!lpOutlineDoc)
      return;

   lpOutlineDoc->m_uCurrentZoom = uCurrentZoom;

   switch (uCurrentZoom) {

#if !defined( OLE_CNTR )
         case IDM_V_ZOOM_400:
            scale.dwSxN = (DWORD) 4;
            scale.dwSxD = (DWORD) 1;
            scale.dwSyN = (DWORD) 4;
            scale.dwSyD = (DWORD) 1;
            break;

         case IDM_V_ZOOM_300:
            scale.dwSxN = (DWORD) 3;

scale.dwSxD = (DWORD) 1;
            scale.dwSyN = (DWORD) 3;
            scale.dwSyD = (DWORD) 1;
            break;

         case IDM_V_ZOOM_200:
            scale.dwSxN = (DWORD) 2;
            scale.dwSxD = (DWORD) 1;
            scale.dwSyN = (DWORD) 2;
            scale.dwSyD = (DWORD) 1;
            break;
#endif      // !OLE_CNTR

         case IDM_V_ZOOM_100:
            scale.dwSxN = (DWORD) 1;
            scale.dwSxD = (DWORD) 1;
            scale.dwSyN = (DWORD) 1;
            scale.dwSyD = (DWORD) 1;
            break;

         case IDM_V_ZOOM_75:
            scale.dwSxN = (DWORD) 3;
            scale.dwSxD = (DWORD) 4;
            scale.dwSyN = (DWORD) 3;
            scale.dwSyD = (DWORD) 4;
            break;

         case IDM_V_ZOOM_50:
            scale.dwSxN = (DWORD) 1;
            scale.dwSxD = (DWORD) 2;
            scale.dwSyN = (DWORD) 1;
            scale.dwSyD = (DWORD) 2;
            break;

         case IDM_V_ZOOM_25:
            scale.dwSxN = (DWORD) 1;
            scale.dwSxD = (DWORD) 4;
            scale.dwSyN = (DWORD) 1;
            scale.dwSyD = (DWORD) 4;
            break;
   }

   OutlineDoc_SetScaleFactor(lpOutlineDoc, (LPSCALEFACTOR)&scale, NULL);
}

/* OutlineDoc_GetCurrentZoomMenuCheck
 * ----------------------------------
 *
 *  Get current zoom level to be checked in the menu.
 */
UINT OutlineDoc_GetCurrentZoomMenuCheck(LPOUTLINEDOC lpOutlineDoc)
{
   return lpOutlineDoc->m_uCurrentZoom;
}

/* OutlineDoc_SetScaleFactor
 * -------------------------
 *
 *  Set the scale factor of the document which will affect the
 *      size of the document on the screen
 *
 * Parameters:
 *
 *   scale      structure containing x and y scales
 */
void OutlineDoc_SetScaleFactor(
      LPOUTLINEDOC        lpOutlineDoc,
      LPSCALEFACTOR       lpscale,
      LPRECT              lprcDoc
)
{
   LPLINELIST      lpLL = OutlineDoc_GetLineList(lpOutlineDoc);
   HWND            hWndLL = LineList_GetWindow(lpLL);

   if (!lpOutlineDoc || !lpscale)
      return;

   InvalidateRect(hWndLL, NULL, TRUE);

   lpOutlineDoc->m_scale = *lpscale;
   LineList_ReScale((LPLINELIST)&lpOutlineDoc->m_LineList, lpscale);

#if defined( USE_HEADING )
   Heading_ReScale((LPHEADING)&lpOutlineDoc->m_heading, lpscale);
#endif

   OutlineDoc_Resize(lpOutlineDoc, lprcDoc);
}

/* OutlineDoc_GetScaleFactor
 * -------------------------
 *
 *  Retrieve the scale factor of the document
 *
 * Parameters:
 *
 */
LPSCALEFACTOR OutlineDoc_GetScaleFactor(LPOUTLINEDOC lpOutlineDoc)
{
   if (!lpOutlineDoc)
      return NULL;

   return (LPSCALEFACTOR)&lpOutlineDoc->m_scale;
}

/* OutlineDoc_SetCurrentMarginCommand
 * ----------------------------------
 *
 *  Set current Margin level to be checked in the menu.
 */
void OutlineDoc_SetCurrentMarginCommand(
      LPOUTLINEDOC        lpOutlineDoc,
      UINT                uCurrentMargin
)
{
   if (!lpOutlineDoc)
      return;

   lpOutlineDoc->m_uCurrentMargin = uCurrentMargin;

   switch (uCurrentMargin) {
      case IDM_V_SETMARGIN_0:
         OutlineDoc_SetMargin(lpOutlineDoc, 0, 0);
         break;

      case IDM_V_SETMARGIN_1:
         OutlineDoc_SetMargin(lpOutlineDoc, 1000, 1000);
         break;

      case IDM_V_SETMARGIN_2:
         OutlineDoc_SetMargin(lpOutlineDoc, 2000, 2000);
         break;

      case IDM_V_SETMARGIN_3:
         OutlineDoc_SetMargin(lpOutlineDoc, 3000, 3000);
         break;

      case IDM_V_SETMARGIN_4:
         OutlineDoc_SetMargin(lpOutlineDoc, 4000, 4000);
         break;
   }
}

/* OutlineDoc_GetCurrentMarginMenuCheck
 * ------------------------------------
 *
 *  Get current Margin level to be checked in the menu.
 */
UINT OutlineDoc_GetCurrentMarginMenuCheck(LPOUTLINEDOC lpOutlineDoc)
{
   return lpOutlineDoc->m_uCurrentMargin;
}

/* OutlineDoc_SetMargin
 * --------------------
 *
 *  Set the left and right margin of the document
 *
 * Parameters:
 *      nLeftMargin  - left margin in Himetric values
 *      nRightMargin - right margin in Himetric values
 */
void OutlineDoc_SetMargin(LPOUTLINEDOC lpOutlineDoc, int nLeftMargin, int nRightMargin)
{
   LPLINELIST lpLL;
   int        nMaxWidthInHim;

   if (!lpOutlineDoc)
      return;

   lpOutlineDoc->m_nLeftMargin = nLeftMargin;
   lpOutlineDoc->m_nRightMargin = nRightMargin;
   lpLL = OutlineDoc_GetLineList(lpOutlineDoc);

   // Force recalculation of Horizontal extent
   nMaxWidthInHim = LineList_GetMaxLineWidthInHimetric(lpLL);
   LineList_SetMaxLineWidthInHimetric(lpLL, -nMaxWidthInHim);

#if defined( INPLACE_CNTR )
   ContainerDoc_UpdateInPlaceObjectRects((LPCONTAINERDOC)lpOutlineDoc, 0);
#endif

   OutlineDoc_ForceRedraw(lpOutlineDoc, TRUE);
}

/* OutlineDoc_GetMargin
 * --------------------
 *
 *  Get the left and right margin of the document
 *
 *  Parameters:
 *      nLeftMargin  - left margin in Himetric values
 *      nRightMargin - right margin in Himetric values
 *
 *  Returns:
 *      low order word  - left margin
 *      high order word - right margin
 */
LONG OutlineDoc_GetMargin(LPOUTLINEDOC lpOutlineDoc)
{
   if (!lpOutlineDoc)
      return 0;

   return MAKELONG(lpOutlineDoc->m_nLeftMargin, lpOutlineDoc->m_nRightMargin);
}

#if defined( USE_HEADING )

/* OutlineDoc_GetHeading
 * ---------------------
 *
 *      Get Heading Object in OutlineDoc
 */
LPHEADING OutlineDoc_GetHeading(LPOUTLINEDOC lpOutlineDoc)
{
   if (!lpOutlineDoc || lpOutlineDoc->m_fDataTransferDoc)
      return NULL;
   else
      return (LPHEADING)&lpOutlineDoc->m_heading;
}

/* OutlineDoc_ShowHeading
 * ----------------------
 *
 *  Show/Hide document row/column headings.
 */
void OutlineDoc_ShowHeading(LPOUTLINEDOC lpOutlineDoc, BOOL fShow)
{
   LPHEADING   lphead = OutlineDoc_GetHeading(lpOutlineDoc);
#if defined( INPLACE_SVR )
   LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpOutlineDoc;
#endif

   if (! lphead)
      return;

   Heading_Show(lphead, fShow);

#if defined( INPLACE_SVR )
   if (lpServerDoc->m_fUIActive) {
      LPINPLACEDATA lpIPData = lpServerDoc->m_lpIPData;

      /* NOTE: our extents have NOT changed; only our the size of
      **    our object-frame adornments is changing. we can use the
      **    current PosRect and ClipRect and simply resize our
      **    windows WITHOUT informing our in-place container.
      */
      ServerDoc_ResizeInPlaceWindow(
            lpServerDoc,
            (LPRECT)&(lpIPData->rcPosRect),
            (LPRECT)&(lpIPData->rcClipRect)
      );
   } else
#else   // !INPLACE_SVR

   OutlineDoc_Resize(lpOutlineDoc, NULL);

#if defined( INPLACE_CNTR )
   ContainerDoc_UpdateInPlaceObjectRects((LPCONTAINERDOC)lpOutlineDoc, 0);
#endif  // INPLACE_CNTR

#endif  // INPLACE_SVR

   OutlineDoc_ForceRedraw(lpOutlineDoc, TRUE);
}

#endif  // USE_HEADING

/* AbortProc
 * ---------
 *  AborProc is called by GDI print code to check for user abort.
 */
BOOL FAR PASCAL EXPORT AbortProc (HDC hdc, WORD reserved)
{
   MSG msg;

   /* Allow other apps to run, or get abort messages */
   while(! fCancelPrint && PeekMessage (&msg, NULL, 0, 0, PM_REMOVE)) {
      if(!hWndPDlg || !IsDialogMessage (hWndPDlg, &msg)) {
         TranslateMessage (&msg);
         DispatchMessage  (&msg);
      }
   }
   return !fCancelPrint;
}

/* PrintDlgProc
 * ------------
 *  Dialog function for the print cancel dialog box.
 *
 *  RETURNS    : TRUE  - OK to abort/ not OK to abort
 *               FALSE - otherwise.
 */
BOOL FAR PASCAL EXPORT PrintDlgProc(
      HWND hwnd,
      WORD msg,
      WORD wParam,
      LONG lParam
)
{
   switch (msg) {
      case WM_COMMAND:
      /* abort printing if the only button gets hit */
         fCancelPrint = TRUE;
         return TRUE;
   }

   return FALSE;
}


OUTLINE.C

/*************************************************************************
**
**    OLE 2.0 Sample Code
**
**    precomp.c
**
**    This file is used to precompile the OUTLINE.H header file
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#include "outline.h"


OUTLINE.H

/*************************************************************************
**
**    OLE 2 Sample Code
**
**    outline.h
**
**    This file contains file contains data structure defintions,
**    function prototypes, constants, etc. used by the Outline series
**    of sample applications:
**          Outline  -- base version of the app (without OLE functionality)
**          SvrOutl  -- OLE 2.0 Server sample app
**          CntrOutl -- OLE 2.0 Containter (Container) sample app
**          ISvrOtl  -- OLE 2.0 Server sample app
**          CntrOutl -- OLE 2.0 Containter (Container) sample app
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
**    For structures which we read from and write to disk we define shadow
**    structures (with the _ONDISK suffix) that allow us to maintain
**    16-bit Windows and Macintosh compatibility.
**
*************************************************************************/

#if !defined( _OUTLINE_H_ )
#define _OUTLINE_H_

#if !defined( RC_INVOKED )
#pragma message ("INCLUDING OUTLINE.H from " __FILE__)
#endif  /* RC_INVOKED */

// use strict ANSI standard (for DVOBJ.H)
#define NONAMELESSUNION

// use system defined bitmap, this line must go before windows.h
#define OEMRESOURCE

#ifdef WIN32
#define EXPORT

#define _fstrchr strchr

#else
#define EXPORT _export
#endif

#define SDI_VERSION         1   // ONLY SDI version is currently supported

#if defined( OLE_SERVER ) || defined( OLE_CNTR )
#define OLE_VERSION         1
#define USE_DRAGDROP        1   // enable drag/drop code in OLE versions
#define USE_MSGFILTER       1   // enable IMessageFilter implementation
#endif

#define USE_HEADING         1   // enable the row/col headings
#define USE_STATUSBAR       1   // enable status bar window
#define USE_FRAMETOOLS      1   // enable the toolbar
#ifndef WIN32   //BUGBUG32
#define USE_CTL3D           1   // enable 3D looking dialogs
#endif

#define STRICT  1
#include <windows.h>
#include <string.h>
#include <commdlg.h>
#include <ole2.h>
#include <olestd.h>
#include "outlrc.h"

#define APPMAJORVERSIONNO   3   // major no. incremented for major releases
                        //  (eg. when an incompatible change is made
                        //  to the storage format)
#define APPMINORVERSIONNO   5   // minor no. incremented for minor releases

/* Definition of SCALEFACTOR */
typedef struct tagSCALEFACTOR {
   ULONG       dwSxN;      // numerator in x direction
   ULONG       dwSxD;      // denominator in x direction
   ULONG       dwSyN;      // numerator in y direction
   ULONG       dwSyD;      // denominator in y direction
} SCALEFACTOR, FAR* LPSCALEFACTOR;

#if defined( USE_FRAMETOOLS )
#include "frametls.h"
#endif

#if defined( USE_HEADING )
#include "heading.h"
#endif

/* max line height (in pixels) allowed in a listbox */
#define LISTBOX_HEIGHT_LIMIT    255

#define MAXSTRLEN   80      // max string len in bytes
#define MAXNAMESIZE 30      // max length of names
#define MAXFORMATSIZE   10  // max length of DEFDOCFORMAT (actually size is 5)
#define TABWIDTH        2000 // 2000 in Himetric units, i.e. 2cm
#define DEFFONTPTSIZE   12
#define DEFFONTSIZE     ((DEFFONTPTSIZE*HIMETRIC_PER_INCH)/PTS_PER_INCH)
#define DEFFONTFACE     "Times New Roman"

#ifdef WIN32S
#define OUTLINEDOCFORMAT    OLESTR("Outline32")     // CF_Outline format name
#else
#define OUTLINEDOCFORMAT    OLESTR("Outline")       // CF_Outline format name
#endif

#define IS_FILENAME_DELIM(c)    ( (c) == '\\' || (c) == '/' || (c) == ':' )
// REVIEW: some of these strings should be loaded from a resource file
#define UNTITLED    OLESTR("Outline")   // title used for untitled document
#define HITTESTDELTA    5

/* Macro to get a random integer within a specified range */
#define getrandom( min, max ) ((rand() % (int)(((max)+1) - (min))) + (min))

// REVIEW: should load strings from string resource file
#ifdef TEST32

#define APPFILENAMEFILTER   "Outline Files (*.OLT)|*.olt|All files (*.*)|*.*|"
#define DEFEXTENSION    "olt"           // Default file extension

#else

#define APPFILENAMEFILTER   "Outline Files (*.OLN)|*.oln|All files (*.*)|*.*|"
#define DEFEXTENSION    "oln"           // Default file extension

#endif //TEST32

/* forward type references */
typedef struct tagOUTLINEDOC FAR* LPOUTLINEDOC;
typedef struct tagTEXTLINE FAR* LPTEXTLINE;

typedef enum tagLINETYPE {
   UNKNOWNLINETYPE,
   TEXTLINETYPE,
   CONTAINERLINETYPE
} LINETYPE;

/*************************************************************************
** class LINE
**    The class LINE is an abstract base class. Instances of class LINE
**    are NOT created; only instances of the concrete subclasses of
**    LINE can be created. In the base app version and the OLE 2.0
**    server-only version only TEXTLINE objects can be created. In the
**    OLE 2.0 client app version either TEXTLINE objects or CONTAINERLINE
**    objects can be created. The LINE class has all fields and methods
**    that are common independent of which subclass of LINE is used.
**    Each LINE object that is created in added to the LINELIST of the
**    OUTLINEDOC document.
*************************************************************************/

typedef struct tagLINE {
   LINETYPE    m_lineType;
   UINT        m_nTabLevel;
   UINT        m_nTabWidthInHimetric;
   UINT        m_nWidthInHimetric;
   UINT        m_nHeightInHimetric;
   BOOL        m_fSelected;        // does line have selection feedback

#if defined( USE_DRAGDROP )
   BOOL        m_fDragOverLine;    // does line have drop target feedback
#endif
} LINE, FAR* LPLINE;

/* Line methods (functions) */
void Line_Init(LPLINE lpLine, int nTab, HDC hDC);
void Line_Delete(LPLINE lpLine);
BOOL Line_CopyToDoc(LPLINE lpSrcLine, LPOUTLINEDOC lpDestDoc, int nIndex);
BOOL Line_Edit(LPLINE lpLine, HWND hWndDoc, HDC hDC);
void Line_Draw(
      LPLINE      lpLine,
      HDC         hDC,
      LPRECT      lpRect,
      LPRECT      lpRectWBounds,
      BOOL        fHighlight
);
void Line_DrawToScreen(
      LPLINE      lpLine,
      HDC         hDC,
      LPRECT      lprcPix,
      UINT        itemAction,
      UINT        itemState,
      LPRECT      lprcDevice
);
void Line_DrawSelHilight(LPLINE lpLine, HDC hDC, LPRECT lpRect, UINT itemAction, UINT itemState);
void Line_DrawFocusRect(LPLINE lpLine, HDC hDC, LPRECT lpRect, UINT itemAction, UINT itemState);
void Line_Unindent(LPLINE lpLine, HDC hDC);
void Line_Indent(LPLINE lpLine, HDC hDC);
LINETYPE Line_GetLineType(LPLINE lpLine);
UINT Line_GetTotalWidthInHimetric(LPLINE lpLine);
void Line_SetWidthInHimetric(LPLINE lpLine, int nWidth);
UINT Line_GetWidthInHimetric(LPLINE lpLine);
UINT Line_GetHeightInHimetric(LPLINE lpLine);
void Line_SetHeightInHimetric(LPLINE lpLine, int nHeight);
UINT Line_GetTabLevel(LPLINE lpLine);
int Line_GetTextLen(LPLINE lpLine);
void Line_GetTextData(LPLINE lpLine, LPOLESTR lpszBuf);
BOOL Line_GetOutlineData(LPLINE lpLine, LPTEXTLINE lpBuf);
int Line_CalcTabWidthInHimetric(LPLINE lpLine, HDC hDC);
BOOL Line_SaveToStg(LPLINE lpLine, UINT uFormat, LPSTORAGE lpSrcStg, LPSTORAGE lpDestStg, LPSTREAM lpLLStm, BOOL fRemember);
LPLINE Line_LoadFromStg(LPSTORAGE lpSrcStg, LPSTREAM lpLLStm, LPOUTLINEDOC lpDestDoc);
void Line_DrawDragFeedback(LPLINE lpLine, HDC hDC, LPRECT lpRect, UINT itemState );
BOOL Line_IsSelected(LPLINE lpLine);

/*************************************************************************
** class TEXTLINE : LINE
**    The class TEXTLINE is a concrete subclass of the abstract base
**    class LINE. The TEXTLINE class holds a string that can be edited
**    by the user. In the base app version and the OLE 2.0
**    server-only version only TEXTLINE objects can be created. In the
**    OLE 2.0 client app version either TEXTLINE objects or CONTAINERLINE
**    objects can be created. The TEXTLINE class inherits all fields
**    from the LINE class. This inheritance is achieved by including a
**    member variable of type LINE as the first field in the TEXTLINE
**    structure. Thus a pointer to a TEXTLINE object can be cast to be
**    a pointer to a LINE object.
**    Each TEXTLINE object that is created in added to the LINELIST of
**    the associated OUTLINEDOC document.
*************************************************************************/

typedef struct tagTEXTLINE {
   LINE m_Line;        // TextLine inherits all fields of Line

   UINT m_nLength;
   char m_szText[MAXSTRLEN+1];
} TEXTLINE;

LPTEXTLINE TextLine_Create(HDC hDC, UINT nTab, LPSTR szText);
void TextLine_Init(LPTEXTLINE lpTextLine, int nTab, HDC hDC);
void TextLine_CalcExtents(LPTEXTLINE lpLine, HDC hDC);
void TextLine_SetHeightInHimetric(LPTEXTLINE lpTextLine, int nHeight);
void TextLine_Delete(LPTEXTLINE lpLine);
BOOL TextLine_Edit(LPTEXTLINE lpLine, HWND hWndDoc, HDC hDC);
void TextLine_Draw(
      LPTEXTLINE  lpTextLine,
      HDC         hDC,
      LPRECT      lpRect,
      LPRECT      lpRectWBounds,
      BOOL        fHighlight
);
void TextLine_DrawSelHilight(LPTEXTLINE lpTextLine, HDC hDC, LPRECT lpRect, UINT itemAction, UINT itemState);
BOOL TextLine_Copy(LPTEXTLINE lpSrcLine, LPTEXTLINE lpDestLine);
BOOL TextLine_CopyToDoc(LPTEXTLINE lpSrcLine, LPOUTLINEDOC lpDestDoc, int nIndex);
int TextLine_GetTextLen(LPTEXTLINE lpTextLine);
void TextLine_GetTextData(LPTEXTLINE lpTextLine, LPSTR lpszBuf);
BOOL TextLine_GetOutlineData(LPTEXTLINE lpTextLine, LPTEXTLINE lpBuf);
BOOL TextLine_SaveToStm(LPTEXTLINE lpLine, LPSTREAM lpLLStm);
LPLINE TextLine_LoadFromStg(LPSTORAGE lpSrcStg, LPSTREAM lpLLStm, LPOUTLINEDOC lpDestDoc);

/*************************************************************************
** class LINERANGE
**    The class LINERANGE is a supporting object used to describe a
**    particular range in an OUTLINEDOC. A range is defined by a starting
**    line index and an ending line index.
*************************************************************************/

typedef struct tagLINERANGE {
   signed short    m_nStartLine;
   signed short    m_nEndLine;
} LINERANGE, FAR* LPLINERANGE;

/*************************************************************************
** class OUTLINENAME
**    The class OUTLINENAME stores a particular named selection in the
**    OUTLINEDOC document. The NAMETABLE class holds all of the names
**    defined in a particular OUTLINEDOC document. Each OUTLINENAME
**    object has a string as its key and a starting line index and an
**    ending line index for the named range.
*************************************************************************/

typedef struct tagOUTLINENAME {
   OLECHAR         m_szName[MAXNAMESIZE+1];
   signed short    m_nStartLine;  // must be signed for table update
   signed short    m_nEndLine;    // functions to work
} OUTLINENAME, FAR* LPOUTLINENAME;

void OutlineName_SetName(LPOUTLINENAME lpOutlineName, LPOLESTR lpszName);
void OutlineName_SetSel(LPOUTLINENAME lpOutlineName, LPLINERANGE lplrSel, BOOL fRangeModified);
void OutlineName_GetSel(LPOUTLINENAME lpOutlineName, LPLINERANGE lplrSel);
BOOL OutlineName_SaveToStg(LPOUTLINENAME lpOutlineName, LPLINERANGE lplrSel, UINT uFormat, LPSTREAM lpNTStm, BOOL FAR* lpfNameSaved);

BOOL OutlineName_SaveToStg(LPOUTLINENAME lpOutlineName, LPLINERANGE lplrSel, UINT uFormat, LPSTREAM lpNTStm, BOOL FAR* lpfNameSaved);
BOOL OutlineName_LoadFromStg(LPOUTLINENAME lpOutlineName, LPSTREAM lpNTStm);

/*************************************************************************
** class OUTLINENAMETABLE
**    OUTLINENAMETABLE manages the table of named selections in the
**    OUTLINEDOC document. Each OUTLINENAMETABLE entry has a string as its key
**    and a starting line index and an ending line index for the
**    named range. There is always one instance of OUTLINENAMETABLE for each
**    OUTLINEDOC created.
*************************************************************************/

typedef struct tagOUTLINENAMETABLE {
   HWND        m_hWndListBox;
   int         m_nCount;
} OUTLINENAMETABLE, FAR* LPOUTLINENAMETABLE;

/* OutlineNameTable methods (functions) */
BOOL OutlineNameTable_Init(LPOUTLINENAMETABLE lpOutlineNameTable, LPOUTLINEDOC lpOutlineDoc);
void OutlineNameTable_Destroy(LPOUTLINENAMETABLE lpOutlineNameTable);
void OutlineNameTable_ClearAll(LPOUTLINENAMETABLE lpOutlineNameTable);
LPOUTLINENAME OutlineNameTable_CreateName(LPOUTLINENAMETABLE lpOutlineNameTable);
void OutlineNameTable_AddName(LPOUTLINENAMETABLE lpOutlineNameTable, LPOUTLINENAME lpOutlineName);
void OutlineNameTable_DeleteName(LPOUTLINENAMETABLE lpOutlineNameTable, int nIndex);
int OutlineNameTable_GetNameIndex(LPOUTLINENAMETABLE lpOutlineNameTable, LPOUTLINENAME lpOutlineName);
LPOUTLINENAME OutlineNameTable_GetName(LPOUTLINENAMETABLE lpOutlineNameTable, int nIndex);
LPOUTLINENAME OutlineNameTable_FindName(LPOUTLINENAMETABLE lpOutlineNameTable, LPOLESTR lpszName);
LPOUTLINENAME OutlineNameTable_FindNamedRange(LPOUTLINENAMETABLE lpOutlineNameTable, LPLINERANGE lplrSel);
int OutlineNameTable_GetCount(LPOUTLINENAMETABLE lpOutlineNameTable);
void OutlineNameTable_AddLineUpdate(LPOUTLINENAMETABLE lpOutlineNameTable, int nAddIndex);
void OutlineNameTable_DeleteLineUpdate(LPOUTLINENAMETABLE lpOutlineNameTable, int nDeleteIndex);
BOOL OutlineNameTable_LoadFromStg(LPOUTLINENAMETABLE lpOutlineNameTable, LPSTORAGE lpSrcStg);
BOOL OutlineNameTable_SaveSelToStg(
      LPOUTLINENAMETABLE      lpOutlineNameTable,
      LPLINERANGE             lplrSel,
      UINT                    uFormat,
      LPSTREAM                lpNTStm
);

/*************************************************************************
** class LINELIST
**    The class LINELIST manages the list of Line objects in the
**    OUTLINEDOC document. This class uses a Window's Owner-draw ListBox
**    to hold the list of LINE objects. There is always one instance of
**    LINELIST for each OUTLINEDOC created.
*************************************************************************/

typedef struct tagLINELIST {
   HWND            m_hWndListBox;  // hWnd of OwnerDraw listbox
   int             m_nNumLines;        // number of lines in LineList
   int             m_nMaxLineWidthInHimetric;  // max width of listbox
   LPOUTLINEDOC    m_lpDoc;        // ptr to associated OutlineDoc
   LINERANGE       m_lrSaveSel;    // selection saved on WM_KILLFOCUS

#if defined( USE_DRAGDROP )
   int             m_iDragOverLine;    // line index w/ drop target feedback
#endif
} LINELIST, FAR* LPLINELIST;

/* LineList methods (functions) */
BOOL LineList_Init(LPLINELIST lpLL, LPOUTLINEDOC lpOutlineDoc);
void LineList_Destroy(LPLINELIST lpLL);
void LineList_AddLine(LPLINELIST lpLL, LPLINE lpLine, int nIndex);
void LineList_DeleteLine(LPLINELIST lpLL, int nIndex);
void LineList_ReplaceLine(LPLINELIST lpLL, LPLINE lpLine, int nIndex);
int LineList_GetLineIndex(LPLINELIST lpLL, LPLINE lpLine);
LPLINE LineList_GetLine(LPLINELIST lpLL, int nIndex);
void LineList_SetFocusLine ( LPLINELIST lpLL, WORD wIndex );
BOOL LineList_GetLineRect(LPLINELIST lpLL, int nIndex, LPRECT lpRect);
int LineList_GetFocusLineIndex(LPLINELIST lpLL);
int LineList_GetCount(LPLINELIST lpLL);
BOOL LineList_SetMaxLineWidthInHimetric(
      LPLINELIST lpLL,
      int nWidthInHimetric
);
void LineList_ScrollLineIntoView(LPLINELIST lpLL, int nIndex);
int LineList_GetMaxLineWidthInHimetric(LPLINELIST lpLL);
BOOL LineList_RecalcMaxLineWidthInHimetric(
      LPLINELIST          lpLL,
      int                 nWidthInHimetric
);
void LineList_CalcSelExtentInHimetric(
      LPLINELIST          lpLL,
      LPLINERANGE         lplrSel,
      LPSIZEL             lpsizel
);
HWND LineList_GetWindow(LPLINELIST lpLL);
HDC LineList_GetDC(LPLINELIST lpLL);
void LineList_ReleaseDC(LPLINELIST lpLL, HDC hDC);
void LineList_SetLineHeight(LPLINELIST lpLL,int nIndex,int nHeightInHimetric);
void LineList_ReScale(LPLINELIST lpLL, LPSCALEFACTOR lpscale);
void LineList_SetSel(LPLINELIST lpLL, LPLINERANGE lplrSel);
int LineList_GetSel(LPLINELIST lpLL, LPLINERANGE lplrSel);
void LineList_RemoveSel(LPLINELIST lpLL);
void LineList_RestoreSel(LPLINELIST lpLL);
void LineList_SetRedraw(LPLINELIST lpLL, BOOL fEnableDraw);
void LineList_ForceRedraw(LPLINELIST lpLL, BOOL fErase);
void LineList_ForceLineRedraw(LPLINELIST lpLL, int nIndex, BOOL fErase);
int LineList_CopySelToDoc(
      LPLINELIST              lpSrcLL,
      LPLINERANGE             lplrSel,
      LPOUTLINEDOC            lpDestDoc
);
BOOL LineList_SaveSelToStg(
      LPLINELIST              lpLL,
      LPLINERANGE             lplrSel,
      UINT                    uFormat,
      LPSTORAGE               lpSrcStg,
      LPSTORAGE               lpDestStg,
      LPSTREAM                lpLLStm,
      BOOL                    fRemember
);
BOOL LineList_LoadFromStg(
      LPLINELIST              lpLL,
      LPSTORAGE               lpSrcStg,
      LPSTREAM                lpLLStm
);

#if defined( USE_DRAGDROP )
void LineList_SetFocusLineFromPointl( LPLINELIST lpLL, POINTL pointl );
void LineList_SetDragOverLineFromPointl ( LPLINELIST lpLL, POINTL pointl );
void LineList_Scroll(LPLINELIST lpLL, DWORD dwScrollDir);
int LineList_GetLineIndexFromPointl(LPLINELIST lpLL, POINTL pointl);
void LineList_RestoreDragFeedback(LPLINELIST lpLL);
#endif

LRESULT FAR PASCAL LineListWndProc(
   HWND   hWnd,
   UINT   Message,
   WPARAM wParam,
   LPARAM lParam
);

// Document initialization type
#define DOCTYPE_UNKNOWN     0   // new doc created but not yet initialized
#define DOCTYPE_NEW         1   // init from scratch (new doc)
#define DOCTYPE_FROMFILE    2   // init from a file (open doc)

/*************************************************************************
** class OUTLINEDOC
**    There is one instance of the OutlineDoc class created per
**    document open in the app. The SDI version of the app supports one
**    OUTLINEDOC at a time. The MDI version of the app can manage
**    multiple documents at one time.
*************************************************************************/

/* Definition of OUTLINEDOC */
typedef struct tagOUTLINEDOC {
   LINELIST    m_LineList;         // list of lines in the doc
   LPOUTLINENAMETABLE m_lpNameTable;   // table of names in the doc
   HWND        m_hWndDoc;          // client area window for the Doc
   int         m_docInitType;      // is doc new or loaded from a file?
   BOOL        m_fDataTransferDoc; // is doc created for copy | drag/drop
   CLIPFORMAT  m_cfSaveFormat;      // format used to save the doc
   OLECHAR     m_szFileName[256];  // associated file; "(Untitled)" if none
   LPOLESTR    m_lpszDocTitle;     // name of doc to appear in window title
   BOOL        m_fModified;        // is the doc dirty (needs to be saved)?
   UINT        m_nDisableDraw;     // enable/disable updating the display
   SCALEFACTOR m_scale;            // current scale factor of the doc
   int         m_nLeftMargin;      // left margin in Himetric
   int         m_nRightMargin;     // right margin in Himetric
   UINT        m_uCurrentZoom;     // cur. zoom (used for menu checking)
   UINT        m_uCurrentMargin;   // cur. margin (used for menu checking)
#if defined( USE_HEADING )
   HEADING     m_heading;
#endif

#if defined( USE_FRAMETOOLS )
   LPFRAMETOOLS m_lpFrameTools;    // ptr to frame tools used by this doc
#endif

} OUTLINEDOC;

/* OutlineDoc methods (functions) */

BOOL OutlineDoc_Init(LPOUTLINEDOC lpOutlineDoc, BOOL fDataTransferDoc);
BOOL OutlineDoc_InitNewFile(LPOUTLINEDOC lpOutlineDoc);
LPOUTLINENAMETABLE OutlineDoc_CreateNameTable(LPOUTLINEDOC lpOutlineDoc);
void OutlineDoc_Destroy(LPOUTLINEDOC lpOutlineDoc);
BOOL OutlineDoc_Close(LPOUTLINEDOC lpOutlineDoc, DWORD dwSaveOption);
void OutlineDoc_ShowWindow(LPOUTLINEDOC lpOutlineDoc);
void OutlineDoc_FrameWindowResized(
      LPOUTLINEDOC        lpOutlineDoc,
      LPRECT              lprcFrameRect,
      LPBORDERWIDTHS      lpFrameToolWidths
);

void OutlineDoc_ClearCommand(LPOUTLINEDOC lpOutlineDoc);
void OutlineDoc_CutCommand(LPOUTLINEDOC lpOutlineDoc);
void OutlineDoc_CopyCommand(LPOUTLINEDOC lpOutlineDoc);
void OutlineDoc_ClearAllLines(LPOUTLINEDOC lpOutlineDoc);
LPOUTLINEDOC OutlineDoc_CreateDataTransferDoc(LPOUTLINEDOC lpSrcOutlineDoc);
void OutlineDoc_PasteCommand(LPOUTLINEDOC lpOutlineDoc);
int OutlineDoc_PasteOutlineData(LPOUTLINEDOC lpOutlineDoc, HGLOBAL hOutline, int nStartIndex);
int OutlineDoc_PasteTextData(LPOUTLINEDOC lpOutlineDoc, HGLOBAL hText, int nStartIndex);
void OutlineDoc_AddTextLineCommand(LPOUTLINEDOC lpOutlineDoc);
void OutlineDoc_AddTopLineCommand(
      LPOUTLINEDOC        lpOutlineDoc,
      UINT                nHeightInHimetric
);
void OutlineDoc_EditLineCommand(LPOUTLINEDOC lpOutlineDoc);
void OutlineDoc_IndentCommand(LPOUTLINEDOC lpOutlineDoc);
void OutlineDoc_UnindentCommand(LPOUTLINEDOC lpOutlineDoc);
void OutlineDoc_SetLineHeightCommand(LPOUTLINEDOC lpDoc);
void OutlineDoc_SelectAllCommand(LPOUTLINEDOC lpOutlineDoc);
void OutlineDoc_DefineNameCommand(LPOUTLINEDOC lpOutlineDoc);
void OutlineDoc_GotoNameCommand(LPOUTLINEDOC lpOutlineDoc);

void OutlineDoc_Print(LPOUTLINEDOC lpOutlineDoc, HDC hDC);
BOOL OutlineDoc_SaveToFile(LPOUTLINEDOC lpOutlineDoc, LPCOLESTR lpszFileName, UINT uFormat, BOOL fRemember);
void OutlineDoc_AddLine(LPOUTLINEDOC lpOutlineDoc, LPLINE lpLine, int nIndex);
void OutlineDoc_DeleteLine(LPOUTLINEDOC lpOutlineDoc, int nIndex);
void OutlineDoc_AddName(LPOUTLINEDOC lpOutlineDoc, LPOUTLINENAME lpOutlineName);
void OutlineDoc_DeleteName(LPOUTLINEDOC lpOutlineDoc, int nIndex);
void OutlineDoc_Resize(LPOUTLINEDOC lpDoc, LPRECT lpRect);
LPOUTLINENAMETABLE OutlineDoc_GetNameTable(LPOUTLINEDOC lpOutlineDoc);
LPLINELIST OutlineDoc_GetLineList(LPOUTLINEDOC lpOutlineDoc);
int OutlineDoc_GetNameCount(LPOUTLINEDOC lpOutlineDoc);
int OutlineDoc_GetLineCount(LPOUTLINEDOC lpOutlineDoc);
void OutlineDoc_SetTitle(LPOUTLINEDOC lpOutlineDoc, BOOL fMakeUpperCase);
BOOL OutlineDoc_CheckSaveChanges(
      LPOUTLINEDOC        lpOutlineDoc,
      LPDWORD             lpdwSaveOption
);
BOOL OutlineDoc_IsModified(LPOUTLINEDOC lpOutlineDoc);
void OutlineDoc_SetModified(LPOUTLINEDOC lpOutlineDoc, BOOL fModified, BOOL fDataChanged, BOOL fSizeChanged);
void OutlineDoc_SetRedraw(LPOUTLINEDOC lpOutlineDoc, BOOL fEnableDraw);
BOOL OutlineDoc_LoadFromFile(LPOUTLINEDOC lpOutlineDoc, LPOLESTR szFileName);
BOOL OutlineDoc_SaveSelToStg(
      LPOUTLINEDOC        lpOutlineDoc,
      LPLINERANGE         lplrSel,
      UINT                uFormat,
      LPSTORAGE           lpDestStg,
      BOOL                fSameAsLoad,
      BOOL                fRemember
);
BOOL OutlineDoc_LoadFromStg(LPOUTLINEDOC lpOutlineDoc, LPSTORAGE lpSrcStg);
BOOL OutlineDoc_SetFileName(LPOUTLINEDOC lpOutlineDoc, LPOLESTR lpszFileName, LPSTORAGE lpNewStg);
HWND OutlineDoc_GetWindow(LPOUTLINEDOC lpOutlineDoc);
void OutlineDoc_SetSel(LPOUTLINEDOC lpOutlineDoc, LPLINERANGE lplrSel);
int OutlineDoc_GetSel(LPOUTLINEDOC lpOutlineDoc, LPLINERANGE lplrSel);
void OutlineDoc_ForceRedraw(LPOUTLINEDOC lpOutlineDoc, BOOL fErase);
void OutlineDoc_RenderFormat(LPOUTLINEDOC lpOutlineDoc, UINT uFormat);
void OutlineDoc_RenderAllFormats(LPOUTLINEDOC lpOutlineDoc);
HGLOBAL OutlineDoc_GetOutlineData(LPOUTLINEDOC lpOutlineDoc, LPLINERANGE lplrSel);
HGLOBAL OutlineDoc_GetTextData(LPOUTLINEDOC lpOutlineDoc, LPLINERANGE lplrSel);
void OutlineDoc_DialogHelp(HWND hDlg, WPARAM wDlgID);
void OutlineDoc_SetCurrentZoomCommand(
      LPOUTLINEDOC        lpOutlineDoc,
      UINT                uCurrentZoom
);
UINT OutlineDoc_GetCurrentZoomMenuCheck(LPOUTLINEDOC lpOutlineDoc);
void OutlineDoc_SetScaleFactor(
      LPOUTLINEDOC        lpOutlineDoc,
      LPSCALEFACTOR       lpscale,
      LPRECT              lprcDoc
);
LPSCALEFACTOR OutlineDoc_GetScaleFactor(LPOUTLINEDOC lpDoc);
void OutlineDoc_SetCurrentMarginCommand(
      LPOUTLINEDOC        lpOutlineDoc,
      UINT                uCurrentMargin
);
UINT OutlineDoc_GetCurrentMarginMenuCheck(LPOUTLINEDOC lpOutlineDoc);
void OutlineDoc_SetMargin(LPOUTLINEDOC lpDoc, int nLeftMargin, int nRightMargin);
LONG OutlineDoc_GetMargin(LPOUTLINEDOC lpDoc);

#if defined( USE_FRAMETOOLS )
void OutlineDoc_AddFrameLevelTools(LPOUTLINEDOC lpOutlineDoc);
void OutlineDoc_SetFormulaBarEditText(
      LPOUTLINEDOC            lpOutlineDoc,
      LPLINE                  lpLine
);
void OutlineDoc_SetFormulaBarEditFocus(
      LPOUTLINEDOC            lpOutlineDoc,
      BOOL                    fEditFocus
);
BOOL OutlineDoc_IsEditFocusInFormulaBar(LPOUTLINEDOC lpOutlineDoc);
void OutlineDoc_UpdateFrameToolButtons(LPOUTLINEDOC lpOutlineDoc);
#endif  // USE_FRAMETOOLS

#if defined( USE_HEADING )
LPHEADING OutlineDoc_GetHeading(LPOUTLINEDOC lpOutlineDoc);
void OutlineDoc_ShowHeading(LPOUTLINEDOC lpOutlineDoc, BOOL fShow);
#endif  // USE_HEADING

/*************************************************************************
** class OUTLINEAPP
**    There is one instance of the OUTLINEAPP class created per running
**    application instance. This object holds many fields that could
**    otherwise be organized as global variables.
*************************************************************************/

/* Definition of OUTLINEAPP */
typedef struct tagOUTLINEAPP {
   HWND            m_hWndApp;        // top-level frame window for the App
   HMENU           m_hMenuApp;       // handle to frame level menu for App
   HACCEL          m_hAccelApp;
   HACCEL          m_hAccelFocusEdit;// Accelerator when Edit in Focus
   LPOUTLINEDOC    m_lpDoc;          // main SDI document visible to user
   LPOUTLINEDOC    m_lpClipboardDoc; // hidden doc for snapshot of copied sel
   HWND            m_hWndStatusBar;  // window for the status bar
   HCURSOR         m_hcursorSelCur;  // cursor used to select lines
   HINSTANCE       m_hInst;
   PRINTDLG        m_PrintDlg;
   HFONT           m_hStdFont;       // font used for TextLines
   UINT            m_cfOutline;      // clipboard format for Outline data
   HACCEL          m_hAccel;
   HWND            m_hWndAccelTarget;
   FARPROC         m_ListBoxWndProc; // orig listbox WndProc for subclassing

#if defined ( USE_FRAMETOOLS ) || defined ( INPLACE_CNTR )
   BORDERWIDTHS    m_FrameToolWidths;  // space required by frame-level tools
#endif  // USE_FRAMETOOLS || INPLACE_CNTR

#if defined( USE_FRAMETOOLS )
   FRAMETOOLS      m_frametools;     // frame tools (button & formula bars)
#endif  // USE_FRAMETOOLS

} OUTLINEAPP, FAR* LPOUTLINEAPP;

/* OutlineApp methods (functions) */
BOOL OutlineApp_InitApplication(LPOUTLINEAPP lpOutlineApp, HINSTANCE hInst);
BOOL OutlineApp_InitInstance(LPOUTLINEAPP lpOutlineApp, HINSTANCE hInst, int nCmdShow);
BOOL OutlineApp_ParseCmdLine(LPOUTLINEAPP lpOutlineApp, LPSTR lpszCmdLine, int nCmdShow);
void OutlineApp_Destroy(LPOUTLINEAPP lpOutlineApp);
LPOUTLINEDOC OutlineApp_CreateDoc(
      LPOUTLINEAPP    lpOutlineApp,
      BOOL            fDataTransferDoc
);
HWND OutlineApp_GetWindow(LPOUTLINEAPP lpOutlineApp);
HWND OutlineApp_GetFrameWindow(LPOUTLINEAPP lpOutlineApp);
HINSTANCE OutlineApp_GetInstance(LPOUTLINEAPP lpOutlineApp);
LPOUTLINENAME OutlineApp_CreateName(LPOUTLINEAPP lpOutlineApp);
void OutlineApp_DocUnlockApp(LPOUTLINEAPP lpOutlineApp, LPOUTLINEDOC lpOutlineDoc);
void OutlineApp_InitMenu(LPOUTLINEAPP lpOutlineApp, LPOUTLINEDOC lpDoc, HMENU hMenu);
void OutlineApp_GetFrameRect(LPOUTLINEAPP lpOutlineApp, LPRECT lprcFrameRect);
void OutlineApp_GetClientAreaRect(
      LPOUTLINEAPP        lpOutlineApp,
      LPRECT              lprcClientAreaRect
);
void OutlineApp_GetStatusLineRect(
      LPOUTLINEAPP        lpOutlineApp,
      LPRECT              lprcStatusLineRect
);
void OutlineApp_ResizeWindows(LPOUTLINEAPP lpOutlineApp);
void OutlineApp_ResizeClientArea(LPOUTLINEAPP lpOutlineApp);
void OutlineApp_AboutCommand(LPOUTLINEAPP lpOutlineApp);
void OutlineApp_NewCommand(LPOUTLINEAPP lpOutlineApp);
void OutlineApp_OpenCommand(LPOUTLINEAPP lpOutlineApp);
void OutlineApp_PrintCommand(LPOUTLINEAPP lpOutlineApp);
BOOL OutlineApp_SaveCommand(LPOUTLINEAPP lpOutlineApp);
BOOL OutlineApp_SaveAsCommand(LPOUTLINEAPP lpOutlineApp);
BOOL OutlineApp_CloseAllDocsAndExitCommand(
      LPOUTLINEAPP        lpOutlineApp,
      BOOL                fForceEndSession
);
void OutlineApp_DestroyWindow(LPOUTLINEAPP lpOutlineApp);

#if defined( USE_FRAMETOOLS )
void OutlineApp_SetBorderSpace(
      LPOUTLINEAPP        lpOutlineApp,
      LPBORDERWIDTHS      lpBorderWidths
);
LPFRAMETOOLS OutlineApp_GetFrameTools(LPOUTLINEAPP lpOutlineApp);
void OutlineApp_SetFormulaBarAccel(
      LPOUTLINEAPP            lpOutlineApp,
      BOOL                    fEditFocus
);
#endif  // USE_FRAMETOOLS

void OutlineApp_SetStatusText(LPOUTLINEAPP lpOutlineApp, LPOLESTR lpszMessage);
LPOUTLINEDOC OutlineApp_GetActiveDoc(LPOUTLINEAPP lpOutlineApp);
HMENU OutlineApp_GetMenu(LPOUTLINEAPP lpOutlineApp);
HFONT OutlineApp_GetActiveFont(LPOUTLINEAPP lpOutlineApp);
HDC OutlineApp_GetPrinterDC(LPOUTLINEAPP lpApp);
void OutlineApp_PrinterSetupCommand(LPOUTLINEAPP lpOutlineApp);
void OutlineApp_ErrorMessage(LPOUTLINEAPP lpOutlineApp, LPOLESTR lpszMsg);
void OutlineApp_GetAppVersionNo(LPOUTLINEAPP lpOutlineApp, int narrAppVersionNo[]);
void OutlineApp_GetAppName(LPOUTLINEAPP lpOutlineApp, LPOLESTR lpszAppName);
BOOL OutlineApp_VersionNoCheck(LPOUTLINEAPP lpOutlineApp, LPOLESTR lpszAppName, int narrAppVersionNo[]);

void OutlineApp_SetEditText(LPOUTLINEAPP lpApp);
void OutlineApp_SetFocusEdit(LPOUTLINEAPP lpApp, BOOL bFocusEdit);
BOOL OutlineApp_GetFocusEdit(LPOUTLINEAPP lpApp);
void OutlineApp_ForceRedraw(LPOUTLINEAPP lpOutlineApp, BOOL fErase);

/* struct definition for persistant data storage of OutlineDoc data */

#pragma pack(push, 2)
typedef struct tagOUTLINEDOCHEADER_ONDISK {
        OLECHAR     m_szFormatName[32];
        short       m_narrAppVersionNo[2];
        USHORT      m_fShowHeading;
        DWORD       m_reserved1;            // space reserved for future use
        DWORD       m_reserved2;            // space reserved for future use
        DWORD       m_reserved3;            // space reserved for future use
        DWORD       m_reserved4;            // space reserved for future use
} OUTLINEDOCHEADER_ONDISK, FAR* LPOUTLINEDOCHEADER_ONDISK;
#pragma pack(pop)

typedef struct tagOUTLINEDOCHEADER {
   OLECHAR     m_szFormatName[32];
   int         m_narrAppVersionNo[2];
   BOOL        m_fShowHeading;
   DWORD       m_reserved1;            // space reserved for future use
   DWORD       m_reserved2;            // space reserved for future use
   DWORD       m_reserved3;            // space reserved for future use
   DWORD       m_reserved4;            // space reserved for future use
} OUTLINEDOCHEADER, FAR* LPOUTLINEDOCHEADER;

#pragma pack(push,2)
typedef struct tagLINELISTHEADER_ONDISK {
        USHORT      m_nNumLines;
        DWORD       m_reserved1;            // space reserved for future use
        DWORD       m_reserved2;            // space reserved for future use
} LINELISTHEADER_ONDISK, FAR* LPLINELISTHEADER_ONDISK;
#pragma pack(pop)

typedef struct tagLINELISTHEADER {
   int         m_nNumLines;
   DWORD       m_reserved1;            // space reserved for future use
   DWORD       m_reserved2;            // space reserved for future use
} LINELISTHEADER, FAR* LPLINELISTHEADER;

#pragma pack(push,2)
typedef struct tagLINERECORD_ONDISK {
        USHORT      m_lineType;
        USHORT      m_nTabLevel;
        USHORT      m_nTabWidthInHimetric;
        USHORT      m_nWidthInHimetric;
        USHORT      m_nHeightInHimetric;
        DWORD       m_reserved;         // space reserved for future use
} LINERECORD_ONDISK, FAR* LPLINERECORD_ONDISK;
#pragma pack(pop)

typedef struct tagLINERECORD {
   LINETYPE    m_lineType;
   UINT        m_nTabLevel;
   UINT        m_nTabWidthInHimetric;
   UINT        m_nWidthInHimetric;
   UINT        m_nHeightInHimetric;
   DWORD       m_reserved;         // space reserved for future use
} LINERECORD, FAR* LPLINERECORD;

/* Function prototypes in main.c */
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                  LPSTR lpszCmdLine, int nCmdShow);
BOOL MyTranslateAccelerator(LPMSG lpmsg);
int GetAccelItemCount(HACCEL hAccel);

LRESULT CALLBACK EXPORT AppWndProc(HWND hWnd, UINT Message, WPARAM wParam,
                  LPARAM lParam);
LRESULT CALLBACK EXPORT DocWndProc(HWND hWnd, UINT Message, WPARAM wParam,
                  LPARAM lParam);

/* Function prototypes in outldlgs.c */
BOOL InputTextDlg(HWND hWnd, LPSTR lpszText, LPSTR lpszDlgTitle);
BOOL CALLBACK EXPORT AddEditDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK EXPORT SetLineHeightDlgProc(HWND hDlg, UINT Message, WPARAM wParam, LPARAM lParam);
BOOL CALLBACK EXPORT DefineNameDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL CALLBACK EXPORT GotoNameDlgProc(HWND, UINT, WPARAM, LPARAM);
void NameDlg_LoadComboBox(LPOUTLINENAMETABLE lpOutlineNameTable,HWND hCombo);
void NameDlg_LoadListBox(LPOUTLINENAMETABLE lpOutlineNameTable,HWND hListBox);
void NameDlg_AddName(HWND hCombo, LPOUTLINEDOC lpOutlineDoc, LPOLESTR lpszName, LPLINERANGE lplrSel);
void NameDlg_UpdateName(HWND hCombo, LPOUTLINEDOC lpOutlineDoc, int nIndex, LPOLESTR lpszName, LPLINERANGE lplrSel);
void NameDlg_DeleteName(HWND hCombo, LPOUTLINEDOC lpOutlineDoc, UINT nIndex);
BOOL CALLBACK EXPORT AboutDlgProc(HWND hDlg, UINT Message, WPARAM wParam, LPARAM lParam);

/* Function prototypes in outldata.c */
LPVOID New(DWORD lSize);
void Delete(LPVOID p);

/* Function prototypes in outlprnt.c */
BOOL CALLBACK EXPORT AbortProc (HDC hdc, WORD reserved);
BOOL CALLBACK EXPORT PrintDlgProc(HWND hwnd, WORD msg, WORD wParam, LONG lParam);

/* Function prototypes in debug.c */
void SetDebugLevelCommand(void);
void TraceDebug(HWND, int);

/*************************************************************************
** DEBUG ASSERTION ROUTINES
*************************************************************************/

#ifdef DBG
#include <assert.h>
#define FnAssert(lpstrExpr, lpstrMsg, lpstrFileName, iLine)     \
        (_assert(lpstrMsg ? lpstrMsg : lpstrExpr,               \
                 lpstrFileName,                                 \
                 iLine), NOERROR)
#endif //DBG

#if defined( OLE_VERSION )
#include "oleoutl.h"

#endif  // OLE_VERSION

#endif // _OUTLINE_H_


OUTLLINE.C

/*************************************************************************
**
**    OLE 2 Sample Code
**
**    outlline.c
**
**    This file contains Line functions.
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#include "outline.h"

OLEDBGDATA

extern LPOUTLINEAPP g_lpApp;

/* Line_Init
 * ---------
 *
 *      Init the calculated data of a line object
 */
void Line_Init(LPLINE lpLine, int nTab, HDC hDC)
{
   lpLine->m_lineType              = UNKNOWNLINETYPE;
   lpLine->m_nTabLevel             = nTab;
   lpLine->m_nTabWidthInHimetric   = Line_CalcTabWidthInHimetric(lpLine,hDC);
   lpLine->m_nWidthInHimetric      = 0;
   lpLine->m_nHeightInHimetric     = 0;
   lpLine->m_fSelected             = FALSE;

#if defined( USE_DRAGDROP )
   lpLine->m_fDragOverLine         = FALSE;
#endif
}

/* Line_Edit
 * ---------
 *
 *      Edit the line object.
 *
 *      Returns TRUE if line was changed
 *              FALSE if the line was NOT changed
 */
BOOL Line_Edit(LPLINE lpLine, HWND hWndDoc, HDC hDC)
{
   switch (lpLine->m_lineType) {
      case TEXTLINETYPE:
         return TextLine_Edit((LPTEXTLINE)lpLine, hWndDoc, hDC);

#if defined( OLE_CNTR )
      case CONTAINERLINETYPE:
         ContainerLine_Edit((LPCONTAINERLINE)lpLine, hWndDoc, hDC);
         break;
#endif

      default:
         return FALSE;       // unknown line type
   }
}

/* Line_GetLineType
 * ----------------
 *
 * Return type of the line
 */
LINETYPE Line_GetLineType(LPLINE lpLine)
{
   if (! lpLine) return 0;

   return lpLine->m_lineType;
}

/* Line_GetTextLen
 * ---------------
 *
 * Return length of string representation of the Line
 *  (not considering the tab level).
 */
int Line_GetTextLen(LPLINE lpLine)
{
   switch (lpLine->m_lineType) {
      case TEXTLINETYPE:
         return TextLine_GetTextLen((LPTEXTLINE)lpLine);

#if defined( OLE_CNTR )
      case CONTAINERLINETYPE:
         return ContainerLine_GetTextLen((LPCONTAINERLINE)lpLine);
#endif

      default:
         return 0;       // unknown line type
   }
}

/* Line_GetTextData
 * ----------------
 *
 * Return the string representation of the Line.
 *  (not considering the tab level).
 */
void Line_GetTextData(LPLINE lpLine, LPOLESTR lpszBuf)
{
   char szAnsiStr[256];

   switch (lpLine->m_lineType) {
     case TEXTLINETYPE:
         TextLine_GetTextData((LPTEXTLINE)lpLine, szAnsiStr);
         A2W (szAnsiStr, lpszBuf, 256);  // lpszBuf ought to be big enough
         break;

#if defined( OLE_CNTR )
      case CONTAINERLINETYPE:
         ContainerLine_GetTextData((LPCONTAINERLINE)lpLine, lpszBuf);
         break;
#endif

      default:
         *lpszBuf = '\0';
         return;     // unknown line type
   }
}

/* Line_GetOutlineData
 * -------------------
 *
 * Return the CF_OUTLINE format representation of the Line.
 */
BOOL Line_GetOutlineData(LPLINE lpLine, LPTEXTLINE lpBuf)
{
   switch (lpLine->m_lineType) {
      case TEXTLINETYPE:
         return TextLine_GetOutlineData((LPTEXTLINE)lpLine, lpBuf);

#if defined( OLE_CNTR )
      case CONTAINERLINETYPE:
         return ContainerLine_GetOutlineData(
               (LPCONTAINERLINE)lpLine,
               lpBuf
         );
#endif

      default:
         return FALSE;       // unknown line type
   }
}

/* Line_CalcTabWidthInHimetric
 * ---------------------------
 *
 *      Recalculate the width for the line's current tab level
 */
static int Line_CalcTabWidthInHimetric(LPLINE lpLine, HDC hDC)
{
   int nTabWidthInHimetric;

   nTabWidthInHimetric=lpLine->m_nTabLevel * TABWIDTH;
   return nTabWidthInHimetric;
}

/* Line_Indent
 * -----------
 *
 *      Increment the tab level for the line
 */
void Line_Indent(LPLINE lpLine, HDC hDC)
{
   lpLine->m_nTabLevel++;
   lpLine->m_nTabWidthInHimetric = Line_CalcTabWidthInHimetric(lpLine, hDC);

#if defined( INPLACE_CNTR )
   if (Line_GetLineType(lpLine) == CONTAINERLINETYPE)
      ContainerLine_UpdateInPlaceObjectRects((LPCONTAINERLINE)lpLine, NULL);
#endif
}

/* Line_Unindent
 * -------------
 *
 * Decrement the tab level for the line
 */
void Line_Unindent(LPLINE lpLine, HDC hDC)
{
   if(lpLine->m_nTabLevel > 0) {
      lpLine->m_nTabLevel--;
      lpLine->m_nTabWidthInHimetric = Line_CalcTabWidthInHimetric(lpLine, hDC);
   }

#if defined( INPLACE_CNTR )
   if (Line_GetLineType(lpLine) == CONTAINERLINETYPE)
      ContainerLine_UpdateInPlaceObjectRects((LPCONTAINERLINE)lpLine, NULL);
#endif
}

/* Line_GetTotalWidthInHimetric
 * ----------------------------
 *
 *      Calculate the total width of the line
 */
UINT Line_GetTotalWidthInHimetric(LPLINE lpLine)
{
   return lpLine->m_nWidthInHimetric + lpLine->m_nTabWidthInHimetric;
}

/* Line_SetWidthInHimetric
 * -----------------------
 *
 *      Set the width of the line
 */
void Line_SetWidthInHimetric(LPLINE lpLine, int nWidth)
{
   if (!lpLine)
      return;

   lpLine->m_nWidthInHimetric = nWidth;
}

/* Line_GetWidthInHimetric
 * -----------------------
 *
 *      Return the width of the line
 */
UINT Line_GetWidthInHimetric(LPLINE lpLine)
{
   if (!lpLine)
      return 0;

   return lpLine->m_nWidthInHimetric;
}

/* Line_GetTabLevel
 * ----------------
 *
 * Return the tab level of a line object.
 */
UINT Line_GetTabLevel(LPLINE lpLine)
{
   return lpLine->m_nTabLevel;
}

/* Line_DrawToScreen
 * -----------------
 *
 *      Draw the item in the owner-draw listbox
 */
void Line_DrawToScreen(
      LPLINE      lpLine,
      HDC         hDC,
      LPRECT      lprcPix,
      UINT        itemAction,
      UINT        itemState,
      LPRECT      lprcDevice
)
{
   if (!lpLine || !hDC || !lprcPix || !lprcDevice)
      return;

   /* Draw a list box item in its normal drawing action.
    * Then check if it is selected or has the focus state, and call
    * functions to handle drawing for these states if necessary.
    */
   if(itemAction & (ODA_SELECT | ODA_DRAWENTIRE)) {
      HFONT hfontOld;
      int nMapModeOld;
      RECT rcWindowOld;
      RECT rcViewportOld;
      RECT rcLogical;

      // NOTE: we have to set the device context to HIMETRIC in order
      // draw the line; however, we have to restore the HDC before
      // we draw focus or dragfeedback...

      rcLogical.left = 0;
      rcLogical.bottom = 0;
      rcLogical.right = lpLine->m_nWidthInHimetric;
      rcLogical.top = lpLine->m_nHeightInHimetric;

      {
         HBRUSH hbr;
         RECT    rcDraw;

         lpLine->m_fSelected = (BOOL)(itemState & ODS_SELECTED);

         if (ODS_SELECTED & itemState) {
            /*Get proper txt colors */
            hbr = CreateSolidBrush(GetSysColor(COLOR_HIGHLIGHT));
         }
         else {
            hbr = CreateSolidBrush(GetSysColor(COLOR_WINDOW));
         }

         rcDraw = *lprcPix;
         rcDraw.right = lprcDevice->left;
         FillRect(hDC, lprcPix, hbr);

         rcDraw = *lprcPix;
         rcDraw.left = lprcDevice->right;
         FillRect(hDC, lprcPix, hbr);

         DeleteObject(hbr);
      }

      nMapModeOld=SetDCToAnisotropic(hDC, lprcDevice, &rcLogical,
                     (LPRECT)&rcWindowOld, (LPRECT)&rcViewportOld);

      // Set the default font size, and font face name
      hfontOld = SelectObject(hDC, OutlineApp_GetActiveFont(g_lpApp));

      Line_Draw(lpLine, hDC, &rcLogical, NULL, (ODS_SELECTED & itemState));

      SelectObject(hDC, hfontOld);

      ResetOrigDC(hDC, nMapModeOld, (LPRECT)&rcWindowOld,
            (LPRECT)&rcViewportOld);

#if defined( OLE_CNTR )
      if ((itemState & ODS_SELECTED) &&
         (Line_GetLineType(lpLine)==CONTAINERLINETYPE))
         ContainerLine_DrawSelHilight(
               (LPCONTAINERLINE)lpLine,
               hDC,
               lprcPix,
               ODA_SELECT,
               ODS_SELECTED
         );
#endif

   }

   /* If a list box item just gained or lost the focus,
   * call function (which could check if ODS_FOCUS bit is set)
   * and draws item in focus or non-focus state.
   */
   if(itemAction & ODA_FOCUS )
      Line_DrawFocusRect(lpLine, hDC, lprcPix, itemAction, itemState);

#if defined( OLE_CNTR )
   if (Line_GetLineType(lpLine) == CONTAINERLINETYPE) {
      LPCONTAINERLINE lpContainerLine = (LPCONTAINERLINE)lpLine;
      LPCONTAINERDOC lpDoc = lpContainerLine->m_lpDoc;
      BOOL fIsLink;
      RECT rcObj;

      if (ContainerDoc_GetShowObjectFlag(lpDoc)) {
         ContainerLine_GetOleObjectRectInPixels(lpContainerLine, &rcObj);
         fIsLink = ContainerLine_IsOleLink(lpContainerLine);
         OleUIShowObject(&rcObj, hDC, fIsLink);
      }
   }
#endif

#if defined( USE_DRAGDROP )
   if (lpLine->m_fDragOverLine)
      Line_DrawDragFeedback(lpLine, hDC, lprcPix, itemState );
#endif

}

/* Line_Draw
 * ---------
 *
 *  Draw a line on a DC.
 *
 * Parameters:
 *      hDC     - DC to which the line will be drawn
 *      lpRect  - the object rect in logical coordinates
 */
void Line_Draw(
      LPLINE      lpLine,
      HDC         hDC,
      LPRECT      lpRect,
      LPRECT      lpRectWBounds,
      BOOL        fHighlight
)
{
   switch (lpLine->m_lineType) {
      case TEXTLINETYPE:
         TextLine_Draw(
             (LPTEXTLINE)lpLine, hDC, lpRect,lpRectWBounds,fHighlight);
         break;

#if defined( OLE_CNTR )
      case CONTAINERLINETYPE:
         ContainerLine_Draw(
             (LPCONTAINERLINE)lpLine,hDC,lpRect,lpRectWBounds,fHighlight);
         break;
#endif

      default:
         return;     // unknown line type
   }
   return;
}

/* Line_DrawSelHilight
 * -------------------
 *
 *      Handles selection of list box item
 */
void Line_DrawSelHilight(LPLINE lpLine, HDC hDC, LPRECT lpRect, UINT itemAction, UINT itemState)
{
   switch (lpLine->m_lineType) {
      case TEXTLINETYPE:
         TextLine_DrawSelHilight((LPTEXTLINE)lpLine, hDC, lpRect,
            itemAction, itemState);
         break;

#if defined( OLE_CNTR )
      case CONTAINERLINETYPE:
         ContainerLine_DrawSelHilight((LPCONTAINERLINE)lpLine, hDC, lpRect,
            itemAction, itemState);
         break;
#endif

      default:
         return;     // unknown line type
   }
   return;

}

/* Line_DrawFocusRect
 * ------------------
 *
 *      Handles focus state of list box item
 */
void Line_DrawFocusRect(LPLINE lpLine, HDC hDC, LPRECT lpRect, UINT itemAction, UINT itemState)
{
   if(lpLine)
      DrawFocusRect(hDC, lpRect);
}

#if defined( USE_DRAGDROP )

/* Line_DrawDragFeedback
 * ---------------------
 *
 *      Handles focus state of list box item
 */
void Line_DrawDragFeedback(LPLINE lpLine, HDC hDC, LPRECT lpRect, UINT itemState )
{
   if(lpLine)
      DrawFocusRect(hDC, lpRect);
}

#endif  // USE_DRAGDROP

/* Line_GetHeightInHimetric
 * ------------------------
 *
 *      Return the height of the item in HIMETRIC units
 */
UINT Line_GetHeightInHimetric(LPLINE lpLine)
{
   if (!lpLine)
      return 0;

   return (UINT)lpLine->m_nHeightInHimetric;
}

/* Line_SetHeightInHimetric
 * ------------------------
 *
 *      Set the height of the item in HIMETRIC units.
 */
void Line_SetHeightInHimetric(LPLINE lpLine, int nHeight)
{
   if (!lpLine)
      return;

   switch (lpLine->m_lineType) {
      case TEXTLINETYPE:
         TextLine_SetHeightInHimetric((LPTEXTLINE)lpLine, nHeight);
         break;

#if defined( OLE_CNTR )
      case CONTAINERLINETYPE:
         ContainerLine_SetHeightInHimetric((LPCONTAINERLINE)lpLine,
               nHeight);
         break;
#endif

   }
}

/* Line_Delete
 * -----------
 *
 *      Delete the Line structure
 */
void Line_Delete(LPLINE lpLine)
{
   switch (lpLine->m_lineType) {
      case TEXTLINETYPE:
         TextLine_Delete((LPTEXTLINE)lpLine);
         break;

#if defined( OLE_CNTR )
      case CONTAINERLINETYPE:
         ContainerLine_Delete((LPCONTAINERLINE)lpLine);
         break;
#endif

      default:
         break;      // unknown line type
   }
}

/* Line_CopyToDoc
 * --------------
 *
 *      Copy a line to another Document (usually ClipboardDoc)
 */
BOOL Line_CopyToDoc(LPLINE lpSrcLine, LPOUTLINEDOC lpDestDoc, int nIndex)
{
   switch (lpSrcLine->m_lineType) {
      case TEXTLINETYPE:
         return TextLine_CopyToDoc((LPTEXTLINE)lpSrcLine,lpDestDoc,nIndex);
         break;

#if defined( OLE_CNTR )
      case CONTAINERLINETYPE:
         return ContainerLine_CopyToDoc(
               (LPCONTAINERLINE)lpSrcLine,
               lpDestDoc,
               nIndex
         );
         break;
#endif

      default:
         return FALSE;       // unknown line type
   }
}

/* Line_SaveToStg
 * --------------
 *
 *      Save a single line object to a storage
 *
 *      Return TRUE if successful, FALSE otherwise
 */
BOOL Line_SaveToStg(LPLINE lpLine, UINT uFormat, LPSTORAGE lpSrcStg, LPSTORAGE lpDestStg, LPSTREAM lpLLStm, BOOL fRemember)
{
   LINERECORD_ONDISK lineRecord;
   ULONG nWritten;
   HRESULT hrErr;
   BOOL fStatus;
   LARGE_INTEGER dlibSavePos;
   LARGE_INTEGER dlibZeroOffset;
   LISet32( dlibZeroOffset, 0 );

   /* save seek position before line record is written in case of error */
   hrErr = lpLLStm->lpVtbl->Seek(
         lpLLStm,
         dlibZeroOffset,
         STREAM_SEEK_CUR,
         (ULARGE_INTEGER FAR*)&dlibSavePos
   );
   if (hrErr != NOERROR) return FALSE;

#if defined( OLE_CNTR )
   if (lpLine->m_lineType == CONTAINERLINETYPE) {
      /* NOTE: asking an OLE object to save may cause the
      **    object to send an OnViewChange notification if there are any
      **    outstanding changes to the object. this is particularly true
      **    for objects with coarse update granularity like OLE 1.0
      **    objects. if an OnViewChange notification is received then the
      **    object's presentation cache will be updated BEFORE it is
      **    saved. It is important that the extents stored as part of
      **    the ContainerLine/Line record associated with the OLE object
      **    are updated before the Line data is saved to the storage. it
      **    is important that this extent information matches the data
      **    saved with the OLE object. the Line extent information is
      **    updated in the IAdviseSink::OnViewChange method implementation.
      */
      // only save the OLE object if format is compatible.
      if (uFormat != ((LPCONTAINERAPP)g_lpApp)->m_cfCntrOutl)
         goto error;

      fStatus = ContainerLine_SaveOleObjectToStg(
            (LPCONTAINERLINE)lpLine,
            lpSrcStg,
            lpDestStg,
            fRemember
      );
      if (! fStatus) goto error;
   }
#endif

   // compilers should handle alignment correctly
   lineRecord.m_lineType = (USHORT)lpLine->m_lineType;
   lineRecord.m_nTabLevel = (USHORT)lpLine->m_nTabLevel;
   lineRecord.m_nTabWidthInHimetric = (USHORT)lpLine->m_nTabWidthInHimetric;
   lineRecord.m_nWidthInHimetric = (USHORT)lpLine->m_nWidthInHimetric;
   lineRecord.m_nHeightInHimetric = (USHORT)lpLine->m_nHeightInHimetric;
   lineRecord.m_reserved = 0;

   /* write line record header */
   hrErr = lpLLStm->lpVtbl->Write(
         lpLLStm,
         (LPVOID)&lineRecord,
         sizeof(lineRecord),
         &nWritten
   );

   if (hrErr != NOERROR) {
      OleDbgOutHResult("Write Line header returned", hrErr);
      goto error;
    }

   switch (lpLine->m_lineType) {
      case TEXTLINETYPE:
         fStatus = TextLine_SaveToStm((LPTEXTLINE)lpLine, lpLLStm);
         if (! fStatus) goto error;
         break;

#if defined( OLE_CNTR )
      case CONTAINERLINETYPE:
         fStatus=ContainerLine_SaveToStm((LPCONTAINERLINE)lpLine,lpLLStm);
         if (! fStatus) goto error;
         break;
#endif

      default:
         goto error;       // unknown line type
   }

   return TRUE;

error:

   /* retore seek position prior to writing Line record */
   lpLLStm->lpVtbl->Seek(
         lpLLStm,
         dlibSavePos,
         STREAM_SEEK_SET,
         NULL
   );

   return FALSE;
}

/* Line_LoadFromStg
 * ----------------
 *
 *      Load a single line object from storage
 */
LPLINE Line_LoadFromStg(LPSTORAGE lpSrcStg, LPSTREAM lpLLStm, LPOUTLINEDOC lpDestDoc)
{
   LINERECORD_ONDISK lineRecord;
   LPLINE lpLine = NULL;
   ULONG nRead;
   HRESULT hrErr;

   /* read line record header */
   hrErr = lpLLStm->lpVtbl->Read(
         lpLLStm,
         (LPVOID)&lineRecord,
         sizeof(lineRecord),
         &nRead
   );

   if (hrErr != NOERROR) {
      OleDbgOutHResult("Read Line header returned", hrErr);
      return NULL;
    }

   switch ((LINETYPE)lineRecord.m_lineType) {
      case TEXTLINETYPE:
         lpLine = TextLine_LoadFromStg(lpSrcStg, lpLLStm, lpDestDoc);
         break;

#if defined( OLE_CNTR )
      case CONTAINERLINETYPE:
         lpLine = ContainerLine_LoadFromStg(lpSrcStg, lpLLStm, lpDestDoc);
         break;
#endif

      default:
         return NULL;        // unknown line type
   }

   lpLine->m_lineType = (LINETYPE) lineRecord.m_lineType;
   lpLine->m_nTabLevel = (UINT) lineRecord.m_nTabLevel;
   lpLine->m_nTabWidthInHimetric = (UINT) lineRecord.m_nTabWidthInHimetric;
   lpLine->m_nWidthInHimetric = (UINT) lineRecord.m_nWidthInHimetric;
   lpLine->m_nHeightInHimetric = (UINT) lineRecord.m_nHeightInHimetric;

   return lpLine;
}

/* Line_IsSelected
 * ---------------
 *
 *      Return the selection state of the line
 */
BOOL Line_IsSelected(LPLINE lpLine)
{
   if (!lpLine)
      return FALSE;

   return lpLine->m_fSelected;
}


OUTLLIST.C

/*************************************************************************
**
**    OLE 2 Sample Code
**
**    outldata.c
**
**    This file contains LineList and NameTable functions
**    and related support functions.
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#include "outline.h"

OLEDBGDATA

extern LPOUTLINEAPP g_lpApp;

OLECHAR ErrMsgListBox[] = OLESTR("Can't create ListBox!");

static int g_iMapMode;

/* LineList_Init
 * -------------
 *
 *      Create and Initialize the LineList (owner-drawn listbox)
 */
BOOL LineList_Init(LPLINELIST lpLL, LPOUTLINEDOC lpOutlineDoc)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;

#if defined( INPLACE_CNTR )
   lpLL->m_hWndListBox = CreateWindow(
               "listbox",              /* Window class name           */
               NULL,                   /* Window's title              */

               /* NOTE: an in-place contanier MUST use
               **    WS_CLIPCHILDREN window style for the window
               **    that it uses as the parent for the server's
               **    in-place active window so that its
               **    painting does NOT interfere with the painting
               **    of the server's in-place active child window.
               */

               WS_CLIPCHILDREN |
               WS_CHILDWINDOW |
               WS_VISIBLE |
               WS_VSCROLL |
               WS_HSCROLL |
               LBS_EXTENDEDSEL |
               LBS_NOTIFY |
               LBS_OWNERDRAWVARIABLE |
               LBS_NOINTEGRALHEIGHT |
               LBS_USETABSTOPS,
               0, 0,                   /* Use default X, Y            */
               0, 0,                   /* Use default X, Y            */
               lpOutlineDoc->m_hWndDoc,/* Parent window's handle      */
               (HMENU)IDC_LINELIST,    /* Child Window ID             */
               lpOutlineApp->m_hInst,  /* Instance of window          */
               NULL);                  /* Create struct for WM_CREATE */
#else
   lpLL->m_hWndListBox = CreateWindow(
               "listbox",              /* Window class name           */
               NULL,                   /* Window's title              */
               WS_CHILDWINDOW |
               WS_VISIBLE |
               WS_VSCROLL |
               WS_HSCROLL |
               LBS_EXTENDEDSEL |
               LBS_NOTIFY |
               LBS_OWNERDRAWVARIABLE |
               LBS_NOINTEGRALHEIGHT |
               LBS_USETABSTOPS,
               0, 0,                   /* Use default X, Y            */
               0, 0,                   /* Use default X, Y            */
               lpOutlineDoc->m_hWndDoc,/* Parent window's handle      */
               (HMENU)IDC_LINELIST,    /* Child Window ID             */
               lpOutlineApp->m_hInst,  /* Instance of window          */
               NULL);                  /* Create struct for WM_CREATE */

#endif

   if(! lpLL->m_hWndListBox) {
      OutlineApp_ErrorMessage(g_lpApp, ErrMsgListBox);
      return FALSE;
   }

   lpOutlineApp->m_ListBoxWndProc =
         (FARPROC) GetWindowLong ( lpLL->m_hWndListBox, GWL_WNDPROC );
   SetWindowLong (lpLL->m_hWndListBox, GWL_WNDPROC, (LONG) LineListWndProc);

#if defined ( USE_DRAGDROP )
   /* m_iDragOverLine saves index of line that has drag/drop target
   **    feedback. we currently use our focus rectangle feedback for
   **    this. it would be better to have a different visual feedback
   **    for potential target of the pending drop.
   */
   lpLL->m_iDragOverLine = -1;
#endif

   lpLL->m_nNumLines = 0;
   lpLL->m_nMaxLineWidthInHimetric = 0;
   lpLL->m_lpDoc = lpOutlineDoc;
   _fmemset(&lpLL->m_lrSaveSel, 0, sizeof(LINERANGE));

   return TRUE;
}

/* LineList_Destroy
 * ----------------
 *
 *      Clear (delete) all Line objects from the list and free supporting
 *      memory (ListBox Window) used by the LineList object itself.
 */
void LineList_Destroy(LPLINELIST lpLL)
{
   int i;
   int linesTotal = lpLL->m_nNumLines;

   // Delete all Line objects
   for (i = 0; i < linesTotal; i++)
      LineList_DeleteLine(lpLL, 0);   // NOTE: always delete line 0

   // Remove all Lines from the ListBox
   SendMessage(lpLL->m_hWndListBox,LB_RESETCONTENT,0,0L);

   lpLL->m_nNumLines=0;
   DestroyWindow(lpLL->m_hWndListBox);
   lpLL->m_hWndListBox = NULL;
}

/* LineList_AddLine
 * ----------------
 *
 *      Add one line to the list box. The line is added following the
 * line with index "nIndex". If nIndex is larger than the number of lines
 * in the ListBox, then the line is appended to the end. The selection
 * is set to the newly added line.
 */
void LineList_AddLine(LPLINELIST lpLL, LPLINE lpLine, int nIndex)
{
   int nAddIndex = (lpLL->m_nNumLines == 0 ?
         0 :
         (nIndex >= lpLL->m_nNumLines ? lpLL->m_nNumLines : nIndex+1));
   LINERANGE lrSel;

#if defined( USE_HEADING )
   int nHeight = Line_GetHeightInHimetric(lpLine);

   nHeight = XformHeightInHimetricToPixels(NULL, nHeight);

   // Add a dummy string to the row heading
   Heading_RH_SendMessage(OutlineDoc_GetHeading(lpLL->m_lpDoc),
         LB_INSERTSTRING, (WPARAM)nAddIndex, MAKELPARAM(nHeight, 0));
#endif

   lrSel.m_nStartLine = nAddIndex;
   lrSel.m_nEndLine =   nAddIndex;

   if (!lpLine) {
      OutlineApp_ErrorMessage(g_lpApp, OLESTR("Could not create line."));
      return;
   }

   SendMessage(lpLL->m_hWndListBox, LB_INSERTSTRING, (WPARAM)nAddIndex,
         (DWORD)lpLine);

   LineList_SetMaxLineWidthInHimetric(
         lpLL,
         Line_GetTotalWidthInHimetric(lpLine)
   );

   lpLL->m_nNumLines++;

   LineList_SetSel(lpLL, &lrSel);
}

/* LineList_DeleteLine
 * -------------------
 *
 *      Delete one line from listbox and memory
 */
void LineList_DeleteLine(LPLINELIST lpLL, int nIndex)
{
   LPLINE lpLine = LineList_GetLine(lpLL, nIndex);
   BOOL fResetSel;

   fResetSel = (BOOL)SendMessage(lpLL->m_hWndListBox, LB_GETSEL, (WPARAM)nIndex, 0L);

   if (lpLine)
      Line_Delete(lpLine);    // free memory of Line

   // Remove the Line from the ListBox
   SendMessage(lpLL->m_hWndListBox, LB_DELETESTRING, (WPARAM)nIndex, 0L);
   lpLL->m_nNumLines--;

   if (fResetSel) {
      if (nIndex > 0) {
#if defined( WIN32 )
         SendMessage(
               lpLL->m_hWndListBox,
               LB_SETSEL,
               (WPARAM)TRUE,
               (LPARAM)nIndex-1
         );
#else
         SendMessage(
               lpLL->m_hWndListBox,
               LB_SETSEL,
               (WPARAM)TRUE,
               MAKELPARAM(nIndex-1,0)
         );
#endif
      } else {
         if (lpLL->m_nNumLines > 0) {
#if defined( WIN32 )
            SendMessage(
                  lpLL->m_hWndListBox,
                  LB_SETSEL,
                  (WPARAM)TRUE,
                  (LPARAM)0
            );
#else
            SendMessage(
                  lpLL->m_hWndListBox,
                  LB_SETSEL,
                  (WPARAM)TRUE,
                  MAKELPARAM(0,0)
            );
#endif
         }
      }
   }

#if defined( USE_HEADING )
   // Remove the dummy string from the row heading
   Heading_RH_SendMessage(OutlineDoc_GetHeading(lpLL->m_lpDoc),
         LB_DELETESTRING, (WPARAM)nIndex, 0L);
#endif

}

/* LineList_ReplaceLine
 * --------------------
 *
 *      Replace the line at a given index in the list box with a new
 * line.
 */
void LineList_ReplaceLine(LPLINELIST lpLL, LPLINE lpLine, int nIndex)
{
   LPLINE lpOldLine = LineList_GetLine(lpLL, nIndex);

   if (lpOldLine)
      Line_Delete(lpOldLine);    // free memory of Line
   else
      return;     // if no previous line then invalid index

   SendMessage(
         lpLL->m_hWndListBox,
         LB_SETITEMDATA,
         (WPARAM)nIndex,
         (LPARAM)lpLine
   );
}

/* LineList_GetLineIndex
 * ---------------------
 *
 *      Return the index of the Line given a pointer to the line.
 *      Return -1 if the line is not found.
 */
int LineList_GetLineIndex(LPLINELIST lpLL, LPLINE lpLine)
{
   LRESULT lRet;

   if (! lpLine) return -1;

   lRet = SendMessage(
         lpLL->m_hWndListBox,
         LB_FINDSTRING,
         (WPARAM)-1,
         (LPARAM)(LPCSTR)lpLine
      );

   return ((lRet == LB_ERR) ? -1 : (int)lRet);
}

/* LineList_GetLine
 * ----------------
 *
 *      Retrieve the pointer to the Line given its index in the LineList
 */
LPLINE LineList_GetLine(LPLINELIST lpLL, int nIndex)
{
   DWORD dWord;
   LRESULT lRet;

   if (lpLL->m_nNumLines == 0 || nIndex > lpLL->m_nNumLines || nIndex < 0)
      return NULL;

   lRet = SendMessage(
         lpLL->m_hWndListBox,LB_GETTEXT,nIndex,(LPARAM)(LPCSTR)&dWord);

   return ((lRet == LB_ERR || lRet == 0) ? NULL : (LPLINE)dWord);
}

/* LineList_SetFocusLine
 * ---------------------
 *
 */

void LineList_SetFocusLine ( LPLINELIST lpLL, WORD wIndex )
{

   SendMessage(lpLL->m_hWndListBox, LB_SETCARETINDEX, (WPARAM)wIndex, 0L );

}

/* LineList_GetLineRect
 * --------------------
 *
 * Retrieve the rectangle of a Line given its index in the LineList
 */
BOOL LineList_GetLineRect(LPLINELIST lpLL, int nIndex, LPRECT lpRect)
{
   DWORD iReturn = (DWORD)LB_ERR;

   if ( !(lpLL->m_nNumLines == 0 || nIndex > lpLL->m_nNumLines || nIndex < 0) )
      iReturn = SendMessage(lpLL->m_hWndListBox,LB_GETITEMRECT,nIndex,(LPARAM)lpRect);

   return (iReturn == LB_ERR ? FALSE : TRUE );
}

/* LineList_GetFocusLineIndex
 * --------------------------
 *
 * Get the index of the line that currently has focus (the active line).
 */
int LineList_GetFocusLineIndex(LPLINELIST lpLL)
{
   return (int)SendMessage(lpLL->m_hWndListBox,LB_GETCARETINDEX,0,0L);
}

/* LineList_GetCount
 * -----------------
 *
 *      Return number of line objects
 */
int LineList_GetCount(LPLINELIST lpLL)
{
   if (lpLL)
      return lpLL->m_nNumLines;
   else {
      OleDbgAssert(lpLL!=NULL);
      return 0;
   }
}

/* LineList_SetMaxLineWidthInHimetric
 * ----------------------------------
 *
 *  Adjust the maximum line width for the listbox. The max line width is
 *  used to determine if a horizontal scroll bar is needed.
 *
 *  Parameters:
 *      nWidthInHimetric - if +ve, width of an additional line
 *                       - if -ve, reset Max to be the value
 *
 *  Returns:
 *      TRUE is max line width of LineList changed
 *      FALSE if no change
 */
BOOL LineList_SetMaxLineWidthInHimetric(LPLINELIST lpLL, int nWidthInHimetric)
{
   int nWidthInPix;
   BOOL fSizeChanged = FALSE;
   LPSCALEFACTOR lpscale;

   if (!lpLL)
      return FALSE;

   lpscale = OutlineDoc_GetScaleFactor(lpLL->m_lpDoc);

   if (nWidthInHimetric < 0) {
      lpLL->m_nMaxLineWidthInHimetric = -1;
      nWidthInHimetric *= -1;
   }

   if (nWidthInHimetric > lpLL->m_nMaxLineWidthInHimetric) {
      lpLL->m_nMaxLineWidthInHimetric = nWidthInHimetric;
      nWidthInPix = XformWidthInHimetricToPixels(NULL, nWidthInHimetric +
            LOWORD(OutlineDoc_GetMargin(lpLL->m_lpDoc)) +
            HIWORD(OutlineDoc_GetMargin(lpLL->m_lpDoc)));

      nWidthInPix = (int)(nWidthInPix * lpscale->dwSxN / lpscale->dwSxD);
      SendMessage(
            lpLL->m_hWndListBox,
            LB_SETHORIZONTALEXTENT,
            nWidthInPix,
            0L
      );
      fSizeChanged = TRUE;

#if defined( USE_HEADING )
      Heading_CH_SetHorizontalExtent(
            OutlineDoc_GetHeading(lpLL->m_lpDoc), lpLL->m_hWndListBox);
#endif

   }
   return fSizeChanged;
}

/* LineList_GetMaxLineWidthInHimetric
 * ----------------------------------
 *
 *      Return the width of the widest line
 */
int LineList_GetMaxLineWidthInHimetric(LPLINELIST lpLL)
{
   return lpLL->m_nMaxLineWidthInHimetric;
}

/* LineList_RecalcMaxLineWidthInHimetric
 * -------------------------------------
 *
 *  Recalculate the maximum line width in the entire list.
 *
 *  Parameters:
 *      nWidthInHimetric should be set to the width of line being removed.
 *      nWidthInHimetric == 0 forces list to recalculate in all cases.
 *      nWidthInHimetric == current max width => forces recalc.
 *
 *  Returns:
 *      TRUE is max line width of LineList changed
 *      FALSE if no change
 */
BOOL LineList_RecalcMaxLineWidthInHimetric(
      LPLINELIST          lpLL,
   int                 nWidthInHimetric
)
{
   int i;
   LPLINE lpLine;
   BOOL fSizeChanged = FALSE;
   int nOrgMaxLineWidthInHimetric = lpLL->m_nMaxLineWidthInHimetric;

   if (nWidthInHimetric == 0 ||
      nWidthInHimetric == lpLL->m_nMaxLineWidthInHimetric) {

      lpLL->m_nMaxLineWidthInHimetric = -1;

      LineList_SetMaxLineWidthInHimetric(lpLL, 0);

      for(i = 0; i < lpLL->m_nNumLines; i++) {
         lpLine=LineList_GetLine(lpLL, i);
         LineList_SetMaxLineWidthInHimetric(
               lpLL,
               Line_GetTotalWidthInHimetric(lpLine)
         );
      }
   }

   if (nOrgMaxLineWidthInHimetric != lpLL->m_nMaxLineWidthInHimetric)
      fSizeChanged = TRUE;

   return fSizeChanged;
}

/* LineList_CalcSelExtentInHimetric
 * --------------------------------
 *
 *      Calculate the extents (widht and height) of a selection of lines.
 *
 * if lplrSel == NULL, calculate extent of all lines.
 */
void LineList_CalcSelExtentInHimetric(
      LPLINELIST          lpLL,
      LPLINERANGE         lplrSel,
      LPSIZEL             lpsizel
)
{
   int i;
   int nEndLine;
   int nStartLine;
   LPLINE lpLine;
   long lWidth;

   if (lplrSel) {
      nEndLine = lplrSel->m_nEndLine;
      nStartLine = lplrSel->m_nStartLine;
   } else {
      nEndLine = LineList_GetCount(lpLL) - 1;
      nStartLine = 0;
   }

   lpsizel->cx = 0;
   lpsizel->cy = 0;

   for(i = nStartLine; i <= nEndLine; i++) {
      lpLine=LineList_GetLine(lpLL,i);
      if (lpLine) {
         lWidth = (long)Line_GetTotalWidthInHimetric(lpLine);
         lpsizel->cx = max(lpsizel->cx, lWidth);
         lpsizel->cy += lpLine->m_nHeightInHimetric;
      }
   }
}

/* LineList_GetWindow
 * ------------------
 *
 * Return handle of list box
 */
HWND LineList_GetWindow(LPLINELIST lpLL)
{
   return lpLL->m_hWndListBox;
}

/* LineList_GetDC
 * --------------
 *
 * Return DC handle of list box
 */
HDC LineList_GetDC(LPLINELIST lpLL)
{
   HFONT hfontOld;
   HDC hDC = GetDC(lpLL->m_hWndListBox);
   int     iXppli;     //* pixels per logical inch along width
   int     iYppli;     //* pixels per logical inch along height
   SIZE    size;

   // Setup a mapping mode for the DC which maps physical pixel
   // coordinates to HIMETRIC units. The standard MM_HIMETRIC mapping
   // mode does not work correctly because it does not take into
   // account that a logical inch on the display screen is drawn
   // physically larger than 1 inch. We will setup an anisotropic
   // mapping mode which will perform the transformation properly.

   g_iMapMode = SetMapMode(hDC, MM_ANISOTROPIC);
   iXppli = GetDeviceCaps (hDC, LOGPIXELSX);
   iYppli = GetDeviceCaps (hDC, LOGPIXELSY);
   SetViewportExtEx(hDC, iXppli, iYppli, &size);
   SetWindowExtEx(hDC, HIMETRIC_PER_INCH, HIMETRIC_PER_INCH, &size);

   // Set the default font size, and font face name
   hfontOld = SelectObject(hDC, OutlineApp_GetActiveFont(g_lpApp));

   return hDC;
}

/* LineList_ReleaseDC
 * ------------------
 *
 *      Release DC of list box returned from previous LineList_GetDC call.
 */
void LineList_ReleaseDC(LPLINELIST lpLL, HDC hDC)
{
   SetMapMode(hDC, g_iMapMode);
   ReleaseDC(lpLL->m_hWndListBox, hDC);
}

/* LineList_SetLineHeight
 * ----------------------
 *
 *      Set the height of a line in the LineList list box
 */
void LineList_SetLineHeight(LPLINELIST lpLL,int nIndex,int nHeightInHimetric)
{
   LPARAM          lParam;
   LPOUTLINEDOC    lpDoc;
   LPSCALEFACTOR   lpscale;
   UINT            uHeightInPix;
   LPHEADING       lphead;

   if (!lpLL)
      return;

   lpDoc = lpLL->m_lpDoc;
   lphead = OutlineDoc_GetHeading(lpDoc);
   lpscale = OutlineDoc_GetScaleFactor(lpDoc);

   uHeightInPix = XformHeightInHimetricToPixels(NULL, nHeightInHimetric);

   Heading_RH_SendMessage(lphead, LB_SETITEMDATA, (WPARAM)nIndex,
         MAKELPARAM(uHeightInPix, 0));

   uHeightInPix = (UINT)(uHeightInPix * lpscale->dwSyN / lpscale->dwSyD);

   if (uHeightInPix > LISTBOX_HEIGHT_LIMIT)
      uHeightInPix = LISTBOX_HEIGHT_LIMIT;

   lParam = MAKELPARAM(uHeightInPix, 0);
   SendMessage(lpLL->m_hWndListBox,LB_SETITEMHEIGHT,(WPARAM)nIndex, lParam);
   Heading_RH_SendMessage(lphead, LB_SETITEMHEIGHT, (WPARAM)nIndex, lParam);
   Heading_RH_ForceRedraw(lphead, TRUE);
}

/* LineList_ReScale
 * ----------------
 *
 *      Re-scale the LineList list box
 */
void LineList_ReScale(LPLINELIST lpLL, LPSCALEFACTOR lpscale)
{
   int nIndex;
   LPLINE lpLine;
   UINT uWidthInHim;

   if (!lpLL)
      return;

   for (nIndex = 0; nIndex < lpLL->m_nNumLines; nIndex++) {
      lpLine = LineList_GetLine(lpLL, nIndex);
      if (lpLine) {
         LineList_SetLineHeight(
               lpLL,
               nIndex,
               Line_GetHeightInHimetric(lpLine)
         );
      }
   }

   uWidthInHim = LineList_GetMaxLineWidthInHimetric(lpLL);
   LineList_SetMaxLineWidthInHimetric(lpLL, -(int)uWidthInHim);
}

/* LineList_SetSel
 * ---------------
 *
 *      Set the selection in list box
 */
void LineList_SetSel(LPLINELIST lpLL, LPLINERANGE lplrSel)
{
   DWORD dwSel;

   if (lpLL->m_nNumLines <= 0 || lplrSel->m_nStartLine < 0)
      return;     // no lines in list; can't set a selection

   dwSel = MAKELPARAM(lplrSel->m_nStartLine, lplrSel->m_nEndLine);

   lpLL->m_lrSaveSel = *lplrSel;

   /* remove previous selection */
#if defined( WIN32 )
   SendMessage(
         lpLL->m_hWndListBox,
         LB_SETSEL,
         (WPARAM)FALSE,
         (LPARAM)-1
   );
#else
   SendMessage(
         lpLL->m_hWndListBox,
         LB_SETSEL,
         (WPARAM)FALSE,
         MAKELPARAM(-1,0)
   );
#endif

   /* mark selection */
   SendMessage(lpLL->m_hWndListBox,LB_SELITEMRANGE, (WPARAM)TRUE, (LPARAM)dwSel);
   /* set focus line (caret) */
   LineList_SetFocusLine ( lpLL, (WORD)lplrSel->m_nStartLine );

}

/* LineList_GetSel
 * ---------------
 *
 * Get the selection in list box.
 *
 * Returns the count of items selected
 */
int LineList_GetSel(LPLINELIST lpLL, LPLINERANGE lplrSel)
{
   int nNumSel=(int)SendMessage(lpLL->m_hWndListBox,LB_GETSELCOUNT,0,0L);

   if (nNumSel) {
      SendMessage(lpLL->m_hWndListBox,LB_GETSELITEMS,
         (WPARAM)1,(LPARAM)(int FAR*)&(lplrSel->m_nStartLine));
      lplrSel->m_nEndLine = lplrSel->m_nStartLine + nNumSel - 1;
   } else {
      _fmemset(lplrSel, 0, sizeof(LINERANGE));
   }
   return nNumSel;
}

/* LineList_RemoveSel
 * ------------------
 *
 * Remove the selection in list box but save the selection state so that
 * it can be restored by calling LineList_RestoreSel
 * LineList_RemoveSel is called when the LineList window looses focus.
 */
void LineList_RemoveSel(LPLINELIST lpLL)
{
   LINERANGE lrSel;
   if (LineList_GetSel(lpLL, &lrSel) > 0) {
      lpLL->m_lrSaveSel = lrSel;
#if defined( WIN32 )
      SendMessage(
            lpLL->m_hWndListBox,
            LB_SETSEL,
            (WPARAM)FALSE,
            (LPARAM)-1
      );
#else
      SendMessage(
            lpLL->m_hWndListBox,
            LB_SETSEL,
            (WPARAM)FALSE,
            MAKELPARAM(-1,0)
      );
#endif
   }
}

/* LineList_RestoreSel
 * ------------------
 *
 * Restore the selection in list box that was previously saved by a call to
 * LineList_RemoveSel.
 * LineList_RestoreSel is called when the LineList window gains focus.
 */
void LineList_RestoreSel(LPLINELIST lpLL)
{
   LineList_SetSel(lpLL, &lpLL->m_lrSaveSel);
}

/* LineList_SetRedraw
 * ------------------
 *
 *      Enable/Disable the redraw of the linelist (listbox) on screen
 *
 *  fEnbaleDraw = TRUE      - enable redraw
 *                FALSE     - disable redraw
 */
void LineList_SetRedraw(LPLINELIST lpLL, BOOL fEnableDraw)
{
   SendMessage(lpLL->m_hWndListBox,WM_SETREDRAW,(WPARAM)fEnableDraw,0L);
}

/* LineList_ForceRedraw
 * --------------------
 *
 *      Force redraw of the linelist (listbox) on screen
 */
void LineList_ForceRedraw(LPLINELIST lpLL, BOOL fErase)
{
   InvalidateRect(lpLL->m_hWndListBox, NULL, fErase);
}

/* LineList_ForceLineRedraw
 * ------------------------
 *
 *      Force a particular line of the linelist (listbox) to redraw.
 */
void LineList_ForceLineRedraw(LPLINELIST lpLL, int nIndex, BOOL fErase)
{
   RECT   rect;

   LineList_GetLineRect( lpLL, nIndex, (LPRECT)&rect );
   InvalidateRect( lpLL->m_hWndListBox, (LPRECT)&rect, fErase );
}

/* LineList_ScrollLineIntoView
 * ---------------------------
 *  Make sure that the specified line is in view; if necessary scroll
 *      the listbox. if any portion of the line is visible, then no
 *      scrolling will occur.
 */
void LineList_ScrollLineIntoView(LPLINELIST lpLL, int nIndex)
{
   RECT rcWindow;
   RECT rcLine;
   RECT rcInt;

   if ( lpLL->m_nNumLines == 0 )
      return;

   if (! LineList_GetLineRect( lpLL, nIndex, (LPRECT)&rcLine ) )
      return;

   GetClientRect( lpLL->m_hWndListBox, (LPRECT) &rcWindow );

   if (! IntersectRect((LPRECT)&rcInt, (LPRECT)&rcWindow, (LPRECT)&rcLine))
      SendMessage(
            lpLL->m_hWndListBox,
            LB_SETTOPINDEX,
            (WPARAM)nIndex,
            (LPARAM)NULL
      );
}

/* LineList_CopySelToDoc
 * ---------------------
 *
 *      Copy the selection of the linelist to another document
 *
 *  RETURNS: number of lines copied.
 */
int LineList_CopySelToDoc(
      LPLINELIST              lpSrcLL,
      LPLINERANGE             lplrSel,
      LPOUTLINEDOC            lpDestDoc
)
{
   int             nEndLine;
   int             nStartLine;
   LPLINELIST      lpDestLL = &lpDestDoc->m_LineList;
   signed short    nDestIndex = LineList_GetFocusLineIndex(lpDestLL);
   LPLINE          lpSrcLine;
   int             nCopied = 0;
   int             i;

   if (lplrSel) {
      nEndLine = lplrSel->m_nEndLine;
      nStartLine = lplrSel->m_nStartLine;
   } else {
      nEndLine = LineList_GetCount(lpSrcLL) - 1;
      nStartLine = 0;
   }

   for(i = nStartLine; i <= nEndLine; i++) {
      lpSrcLine = LineList_GetLine(lpSrcLL, i);
      if (lpSrcLine && Line_CopyToDoc(lpSrcLine, lpDestDoc, nDestIndex)) {
         nDestIndex++;
         nCopied++;
      }
   }

   return nCopied;
}

/* LineList_SaveSelToStg
 * ---------------------
 *
 *      Save lines in selection into lpDestStg.
 *
 *      Return TRUE if ok, FALSE if error
 */
BOOL LineList_SaveSelToStg(
      LPLINELIST              lpLL,
      LPLINERANGE             lplrSel,
      UINT                    uFormat,
      LPSTORAGE               lpSrcStg,
      LPSTORAGE               lpDestStg,
      LPSTREAM                lpLLStm,
      BOOL                    fRemember
)
{
   int nEndLine;
   int nStartLine;
   int nNumLinesWritten = 0;
   HRESULT hrErr = NOERROR;
   ULONG nWritten;
   LPLINE lpLine;
   LINELISTHEADER_ONDISK llhRecord;
   int i;
   LARGE_INTEGER dlibSaveHeaderPos;
   LARGE_INTEGER dlibZeroOffset;
   LISet32( dlibZeroOffset, 0 );

   if (lplrSel) {
      nEndLine = lplrSel->m_nEndLine;
      nStartLine = lplrSel->m_nStartLine;
   } else {
      nEndLine = LineList_GetCount(lpLL) - 1;
      nStartLine = 0;
   }

   _fmemset(&llhRecord,0,sizeof(llhRecord));

   /* save seek position for LineList header record */
   hrErr = lpLLStm->lpVtbl->Seek(
         lpLLStm,
         dlibZeroOffset,
         STREAM_SEEK_CUR,
         (ULARGE_INTEGER FAR*)&dlibSaveHeaderPos
   );
   if (hrErr != NOERROR) goto error;

   /* write LineList header record */
   hrErr = lpLLStm->lpVtbl->Write(
         lpLLStm,
         (LPVOID)&llhRecord,
         sizeof(llhRecord),
         &nWritten
    );
   if (hrErr != NOERROR) {
      OleDbgOutHResult("Write LineList header returned", hrErr);
      goto error;
    }

   for(i = nStartLine; i <= nEndLine; i++) {
      lpLine = LineList_GetLine(lpLL, i);
      if(lpLine &&
         Line_SaveToStg(lpLine, uFormat, lpSrcStg, lpDestStg, lpLLStm,
                                                fRemember))
         llhRecord.m_nNumLines++;
   }

   /* retore seek position for LineList header record */
   hrErr = lpLLStm->lpVtbl->Seek(
         lpLLStm,
         dlibSaveHeaderPos,
         STREAM_SEEK_SET,
         NULL
   );
   if (hrErr != NOERROR) goto error;

   /* write LineList header record */
   hrErr = lpLLStm->lpVtbl->Write(
         lpLLStm,
         (LPVOID)&llhRecord,
         sizeof(llhRecord),
         &nWritten
   );
   if (hrErr != NOERROR) goto error;

   /* reset seek position to end of stream */
   hrErr = lpLLStm->lpVtbl->Seek(
         lpLLStm,
         dlibZeroOffset,
         STREAM_SEEK_END,
         NULL
   );
   if (hrErr != NOERROR) goto error;

   return TRUE;

error:
#if defined( _DEBUG )
   OleDbgAssertSz(
         hrErr == NOERROR,
         "Could not write LineList header to LineList stream"
   );
#endif
   return FALSE;
}

/* LineList_LoadFromStg
 * --------------------
 *
 *      Load lines into linelist from storage.
 *
 *      Return TRUE if ok, FALSE if error
 */
BOOL LineList_LoadFromStg(
      LPLINELIST              lpLL,
      LPSTORAGE               lpSrcStg,
      LPSTREAM                lpLLStm
)
{
   HRESULT hrErr;
   ULONG nRead;
   LPLINE lpLine;
   int i;
   int nNumLines;
   LINELISTHEADER_ONDISK llineRecord;

   /* write LineList header record */
   hrErr = lpLLStm->lpVtbl->Read(
         lpLLStm,
         (LPVOID)&llineRecord,
         sizeof(llineRecord),
         &nRead
   );

   if (hrErr != NOERROR) {
      OleDbgOutHResult("Read LineList header returned", hrErr);
      goto error;
    }

   nNumLines = (int) llineRecord.m_nNumLines;

   for(i = 0; i < nNumLines; i++) {
      lpLine = Line_LoadFromStg(lpSrcStg, lpLLStm, lpLL->m_lpDoc);
      if (! lpLine)
         goto error;

      // Directly add lines to LineList without trying to update a NameTbl
      LineList_AddLine(lpLL, lpLine, i-1);
   }

   return TRUE;

error:
   // Delete any Line objects that were created
   if (lpLL->m_nNumLines > 0) {
      int nNumLines = lpLL->m_nNumLines;
      for (i = 0; i < nNumLines; i++)
         LineList_DeleteLine(lpLL, i);
   }

   return FALSE;
}

#if defined( USE_DRAGDROP )

/* LineList_SetFocusLineFromPointl
 * -------------------------------
 *
 */

void LineList_SetFocusLineFromPointl( LPLINELIST lpLL, POINTL pointl )
{
   int i = LineList_GetLineIndexFromPointl( lpLL, pointl );

   if ( i == (int)-1)
      return ;
   else
      LineList_SetFocusLine( lpLL, (WORD)i );
}

/* LineList_SetDragOverLineFromPointl
 * ----------------------------------
 *
 */

void LineList_SetDragOverLineFromPointl ( LPLINELIST lpLL, POINTL pointl )
{
   int    nIndex = LineList_GetLineIndexFromPointl( lpLL, pointl );
   LPLINE lpline = LineList_GetLine( lpLL, nIndex );

   if (!lpline)
      return;

   if (! lpline->m_fDragOverLine) {
      /* user has dragged over a new line. force new drop target line
      **    to repaint so that drop feedback will be drawn.
      */
      lpline->m_fDragOverLine = TRUE;
      LineList_ForceLineRedraw( lpLL, nIndex, TRUE /*fErase*/);

      if (lpLL->m_iDragOverLine!= -1 && lpLL->m_iDragOverLine!=nIndex) {

         /* force previous drop target line to repaint so that drop
         **    feedback will be undrawn
         */
         lpline = LineList_GetLine( lpLL, lpLL->m_iDragOverLine );
         if (lpline)
            lpline->m_fDragOverLine = FALSE;

         LineList_ForceLineRedraw(
               lpLL,lpLL->m_iDragOverLine,TRUE /*fErase*/);

}

      lpLL->m_iDragOverLine = nIndex;

      // Force repaint immediately
      UpdateWindow(lpLL->m_hWndListBox);
   }
}

/* LineList_Scroll
 * ---------------
 *
 * Scroll the LineList list box in the desired direction by one line.
 *
 *      this function is called during a drag operation.
 */

void LineList_Scroll(LPLINELIST lpLL, DWORD dwScrollDir)
{
   switch (dwScrollDir) {
      case SCROLLDIR_UP:
         SendMessage( lpLL->m_hWndListBox, WM_VSCROLL, SB_LINEUP, 0L );
         break;

      case SCROLLDIR_DOWN:
         SendMessage( lpLL->m_hWndListBox, WM_VSCROLL, SB_LINEDOWN, 0L );
         break;
   }
}

/* LineList_GetLineIndexFromPointl
 * -------------------------------
 *   do hit test to get index of line corresponding to pointl
 */
int LineList_GetLineIndexFromPointl(LPLINELIST lpLL, POINTL pointl)
{
   RECT  rect;
   POINT point;
   DWORD i;

   point.x = (int)pointl.x;
   point.y = (int)pointl.y;

   ScreenToClient( lpLL->m_hWndListBox, &point);

   if ( lpLL->m_nNumLines == 0 )
      return -1;

   GetClientRect( lpLL->m_hWndListBox, (LPRECT) &rect );

   i = SendMessage( lpLL->m_hWndListBox, LB_GETTOPINDEX, (WPARAM)NULL, (LPARAM)NULL );

   for ( ;; i++){

      RECT rectItem;

      if (!LineList_GetLineRect( lpLL, (int)i, (LPRECT)&rectItem ) )
         return -1;

      if ( rectItem.top > rect.bottom )
         return -1;

      if ( rectItem.top <= point.y && point.y <= rectItem.bottom)
         return (int)i;

   }

}

/* LineList_RestoreDragFeedback
 * ----------------------------
 *
 * Retore the index of the line that currently has focus (the active line).
 */
void LineList_RestoreDragFeedback(LPLINELIST lpLL)
{
   LPLINE lpLine;

   if (lpLL->m_iDragOverLine < 0 )
      return;

   lpLine = LineList_GetLine( lpLL, lpLL->m_iDragOverLine);

   if (lpLine) {

      lpLine->m_fDragOverLine = FALSE;
      LineList_ForceLineRedraw( lpLL,lpLL->m_iDragOverLine,TRUE /*fErase*/);

      // Force repaint immediately
      UpdateWindow(lpLL->m_hWndListBox);
   }

   lpLL->m_iDragOverLine = -1;

}

#endif


OUTLNAME.C

/*************************************************************************
**
**    OLE 2 Sample Code
**
**    outlname.c
**
**    This file contains OutlineName functions.
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#include "outline.h"

OLEDBGDATA

/* OutlineName_SetName
 * -------------------
 *
 *      Change the string of a name.
 */
void OutlineName_SetName(LPOUTLINENAME lpOutlineName, LPOLESTR lpszName)
{
   OLESTRCPY(lpOutlineName->m_szName, lpszName);
}

/* OutlineName_SetSel
 * ------------------
 *
 *      Change the line range of a  name.
 */
void OutlineName_SetSel(LPOUTLINENAME lpOutlineName, LPLINERANGE lplrSel, BOOL fRangeModified)
{
#if defined( OLE_SERVER )
   // Call OLE server specific function instead
   ServerName_SetSel((LPSERVERNAME)lpOutlineName, lplrSel, fRangeModified);
#else

   lpOutlineName->m_nStartLine = lplrSel->m_nStartLine;
   lpOutlineName->m_nEndLine = lplrSel->m_nEndLine;
#endif
}

/* OutlineName_GetSel
 * ------------------
 *
 *      Retrieve the line range of a name.
 */
void OutlineName_GetSel(LPOUTLINENAME lpOutlineName, LPLINERANGE lplrSel)
{
   lplrSel->m_nStartLine = lpOutlineName->m_nStartLine;
   lplrSel->m_nEndLine = lpOutlineName->m_nEndLine;
}

/* OutlineName_SaveToStg
 * ---------------------
 *
 *      Save a name into a storage
 */
BOOL OutlineName_SaveToStg(LPOUTLINENAME lpOutlineName, LPLINERANGE lplrSel, UINT uFormat, LPSTREAM lpNTStm, BOOL FAR* lpfNameSaved)
{
   HRESULT hrErr = NOERROR;
   ULONG nWritten;

   *lpfNameSaved = FALSE;

   /* if no range given or if the name is completely within the range,
   **      write it out.
   */
   if (!lplrSel ||
      ((lplrSel->m_nStartLine <= lpOutlineName->m_nStartLine) &&
      (lplrSel->m_nEndLine >= lpOutlineName->m_nEndLine))) {

      hrErr = lpNTStm->lpVtbl->Write(
            lpNTStm,
            lpOutlineName,
            sizeof(OUTLINENAME),
            &nWritten
      );
      *lpfNameSaved = TRUE;
   }
   return ((hrErr == NOERROR) ? TRUE : FALSE);
}

/* OutlineName_LoadFromStg
 * -----------------------
 *
 *      Load names from an open stream of a storage. if the name already
 * exits in the OutlineNameTable, it is NOT modified.
 *
 *      Returns TRUE is all ok, else FALSE.
 */
BOOL OutlineName_LoadFromStg(LPOUTLINENAME lpOutlineName, LPSTREAM lpNTStm)
{
   HRESULT hrErr = NOERROR;
   ULONG nRead;

   hrErr = lpNTStm->lpVtbl->Read(
         lpNTStm,
         lpOutlineName,
         sizeof(OUTLINENAME),
         &nRead
   );

   return ((hrErr == NOERROR) ? TRUE : FALSE);
}


OUTLNTBL.C

/*************************************************************************
**
**    OLE 2 Sample Code
**
**    outlntbl.c
**
**    This file contains OutlineNameTable functions.
**
**    (c) Copyright Microsoft Corp. 1992 - 1997 All Rights Reserved
**
*************************************************************************/

#include "outline.h"

OLEDBGDATA

extern LPOUTLINEAPP g_lpApp;

OLECHAR ErrMsgNameTable[] = OLESTR("Can't create NameTable!");

/* OutlineNameTable_Init
 * ---------------------
 *
 *      initialize a name table.
 */
BOOL OutlineNameTable_Init(LPOUTLINENAMETABLE lpOutlineNameTable, LPOUTLINEDOC lpOutlineDoc)
{
   HWND lpParent = OutlineDoc_GetWindow(lpOutlineDoc);

   lpOutlineNameTable->m_nCount = 0;

   /* We will use an OwnerDraw listbox as our data structure to
   **    maintain the table of Names. this listbox will never be made
   **    visible. the listbox is just a convenient data structure to
   **    manage a collection.
   */
   lpOutlineNameTable->m_hWndListBox = CreateWindow(
               "listbox",              /* Window class name           */
               NULL,                   /* Window's title              */
               WS_CHILDWINDOW |
               LBS_OWNERDRAWFIXED,
               0, 0,                   /* Use default X, Y            */
               0, 0,                   /* Use default X, Y            */
               lpParent,               /* Parent window's handle      */
               (HMENU)IDC_NAMETABLE,   /* Child Window ID             */
               g_lpApp->m_hInst,       /* Instance of window          */
               NULL);                  /* Create struct for WM_CREATE */

   if (! lpOutlineNameTable->m_hWndListBox) {
      OutlineApp_ErrorMessage(g_lpApp, ErrMsgNameTable);
      return FALSE;
   }

   return TRUE;
}

/* OutlineNameTable_Destroy
 * ------------------------
 *
 *      Free memory used by the name table.
 */
void OutlineNameTable_Destroy(LPOUTLINENAMETABLE lpOutlineNameTable)
{
   // Delete all names
   OutlineNameTable_ClearAll(lpOutlineNameTable);

   DestroyWindow(lpOutlineNameTable->m_hWndListBox);
   Delete(lpOutlineNameTable);
}

/* OutlineNameTable_AddName
 * ------------------------
 *
 *      Add a name to the table
 */
void OutlineNameTable_AddName(LPOUTLINENAMETABLE lpOutlineNameTable, LPOUTLINENAME lpOutlineName)
{
   SendMessage(
         lpOutlineNameTable->m_hWndListBox,
         LB_ADDSTRING,
         0,
         (DWORD)lpOutlineName
   );
   lpOutlineNameTable->m_nCount++;
}

/* OutlineNameTable_DeleteName
 * ---------------------------
 *
 *      Delete a name from table
 */
void OutlineNameTable_DeleteName(LPOUTLINENAMETABLE lpOutlineNameTable,int nIndex)
{
   LPOUTLINENAME lpOutlineName = OutlineNameTable_GetName(lpOutlineNameTable, nIndex);

#if defined( OLE_SERVER )
   /* NOTE: if there is a pseudo object attached to this name, it
   **    must first be closed before deleting the Name. this will
   **    cause OnClose notification to be sent to all linking clients.
   */
   ServerName_ClosePseudoObj((LPSERVERNAME)lpOutlineName);
#endif

   if (lpOutlineName)
      Delete(lpOutlineName);      // free memory for name

   SendMessage(
         lpOutlineNameTable->m_hWndListBox,
         LB_DELETESTRING,
         (WPARAM)nIndex,
         0L
   );
   lpOutlineNameTable->m_nCount--;
}

/* OutlineNameTable_GetNameIndex
 * -----------------------------
 *
 *      Return the index of the Name given a pointer to the Name.
 *      Return -1 if the Name is not found.
 */
int OutlineNameTable_GetNameIndex(LPOUTLINENAMETABLE lpOutlineNameTable, LPOUTLINENAME lpOutlineName)
{
   LRESULT lReturn;

   if (! lpOutlineName) return -1;

   lReturn = SendMessage(
         lpOutlineNameTable->m_hWndListBox,
         LB_FINDSTRING,
         (WPARAM)-1,
         (LPARAM)(LPCSTR)lpOutlineName
      );

   return ((lReturn == LB_ERR) ? -1 : (int)lReturn);
}

/* OutlineNameTable_GetName
 * ------------------------
 *
 *      Retrieve the pointer to the Name given its index in the NameTable
 */
LPOUTLINENAME OutlineNameTable_GetName(LPOUTLINENAMETABLE lpOutlineNameTable, int nIndex)
{
   LPOUTLINENAME lpOutlineName = NULL;
    LRESULT lResult;

   if (lpOutlineNameTable->m_nCount == 0 ||
      nIndex > lpOutlineNameTable->m_nCount ||
      nIndex < 0) {
      return NULL;
   }

   lResult = SendMessage(
         lpOutlineNameTable->m_hWndListBox,
         LB_GETTEXT,
         nIndex,
         (LPARAM)(LPCSTR)&lpOutlineName
   );
    OleDbgAssert(lResult != LB_ERR);
   return lpOutlineName;
}

/* OutlineNameTable_FindName
 * -------------------------
 *
 *      Find a name in the name table given a string.
 */
LPOUTLINENAME OutlineNameTable_FindName(LPOUTLINENAMETABLE lpOutlineNameTable, LPOLESTR lpszName)
{
   LPOUTLINENAME lpOutlineName;
   BOOL fFound = FALSE;
   int i;

   for (i = 0; i < lpOutlineNameTable->m_nCount; i++) {
      lpOutlineName = OutlineNameTable_GetName(lpOutlineNameTable, i);
      if (OLESTRCMP(lpOutlineName->m_szName, lpszName) == 0) {
         fFound = TRUE;
         break;      // FOUND MATCH!
      }
   }

   return (fFound ? lpOutlineName : NULL);
}

/* OutlineNameTable_FindNamedRange
 * -------------------------------
 *
 *      Find a name in the name table which matches a given line range.
 */
LPOUTLINENAME OutlineNameTable_FindNamedRange(LPOUTLINENAMETABLE lpOutlineNameTable, LPLINERANGE lplrSel)
{
   LPOUTLINENAME lpOutlineName;
   BOOL fFound = FALSE;
   int i;

   for (i = 0; i < lpOutlineNameTable->m_nCount; i++) {
      lpOutlineName = OutlineNameTable_GetName(lpOutlineNameTable, i);
      if ((lpOutlineName->m_nStartLine == lplrSel->m_nStartLine) &&
         (lpOutlineName->m_nEndLine == lplrSel->m_nEndLine) ) {
         fFound = TRUE;
         break;      // FOUND MATCH!
      }
   }

   return (fFound ? lpOutlineName : NULL);
}

/* OutlineNameTable_GetCount
 * -------------------------
 *
 * Return number of names in nametable
 */
int OutlineNameTable_GetCount(LPOUTLINENAMETABLE lpOutlineNameTable)
{
   if (!lpOutlineNameTable)
      return 0;

   return lpOutlineNameTable->m_nCount;
}

/* OutlineNameTable_ClearAll
 * -------------------------
 *
 *      Remove all names from table
 */
void OutlineNameTable_ClearAll(LPOUTLINENAMETABLE lpOutlineNameTable)
{
   LPOUTLINENAME lpOutlineName;
   int i;
   int nCount = lpOutlineNameTable->m_nCount;

   for (i = 0; i < nCount; i++) {
      lpOutlineName = OutlineNameTable_GetName(lpOutlineNameTable, i);
      Delete(lpOutlineName);      // free memory for name
   }

   lpOutlineNameTable->m_nCount = 0;
   SendMessage(lpOutlineNameTable->m_hWndListBox,LB_RESETCONTENT,0,0L);
}

/* OutlineNameTable_AddLineUpdate
 * ------------------------------
 *
 *      Update table when a new line is added at nAddIndex
 * The line used to be at nAddIndex is pushed down
 */
void OutlineNameTable_AddLineUpdate(LPOUTLINENAMETABLE lpOutlineNameTable, int nAddIndex)
{
   LPOUTLINENAME lpOutlineName;
   LINERANGE lrSel;
   int i;
   BOOL fRangeModified = FALSE;

   for(i = 0; i < lpOutlineNameTable->m_nCount; i++) {
      lpOutlineName=OutlineNameTable_GetName(lpOutlineNameTable, i);
      OutlineName_GetSel(lpOutlineName, &lrSel);

      if((int)lrSel.m_nStartLine > nAddIndex) {
         lrSel.m_nStartLine++;
         fRangeModified = !fRangeModified;
      }
      if((int)lrSel.m_nEndLine > nAddIndex) {
         lrSel.m_nEndLine++;
         fRangeModified = !fRangeModified;
      }

      OutlineName_SetSel(lpOutlineName, &lrSel, fRangeModified);
   }
}

/* OutlineNameTable_DeleteLineUpdate
 * ---------------------------------
 *
 *      Update the table when a line at nDeleteIndex is removed
 */
void OutlineNameTable_DeleteLineUpdate(LPOUTLINENAMETABLE lpOutlineNameTable, int nDeleteIndex)
{
   LPOUTLINENAME lpOutlineName;
   LINERANGE lrSel;
   int i;
   BOOL fRangeModified = FALSE;

   for(i = 0; i < lpOutlineNameTable->m_nCount; i++) {
      lpOutlineName=OutlineNameTable_GetName(lpOutlineNameTable, i);
      OutlineName_GetSel(lpOutlineName, &lrSel);

      if((int)lrSel.m_nStartLine > nDeleteIndex) {
         lrSel.m_nStartLine--;
         fRangeModified = !fRangeModified;
      }
      if((int)lrSel.m_nEndLine >= nDeleteIndex) {
         lrSel.m_nEndLine--;
         fRangeModified = !fRangeModified;
      }

      // delete the name if its entire range is deleted
      if(lrSel.m_nStartLine > lrSel.m_nEndLine) {
         OutlineNameTable_DeleteName(lpOutlineNameTable, i);
         i--;  // re-examine this name
      } else {
         OutlineName_SetSel(lpOutlineName, &lrSel, fRangeModified);
      }
   }
}

/* OutlineNameTable_SaveSelToStg
 * -----------------------------
 *
 *      Save only the names that refer to lines completely contained in the
 * specified selection range.
 */
BOOL OutlineNameTable_SaveSelToStg(
      LPOUTLINENAMETABLE      lpOutlineNameTable,
      LPLINERANGE             lplrSel,
      UINT                    uFormat,
      LPSTREAM                lpNTStm
)
{
   HRESULT hrErr;
   ULONG nWritten;
   LPOUTLINENAME lpOutlineName;
   short nNameCount = 0;
   BOOL fNameSaved;
   BOOL fStatus;
   int i;
   LARGE_INTEGER dlibZeroOffset;
   LISet32( dlibZeroOffset, 0 );

   /* initially write 0 for count of names. the correct count will be
   **    written at the end when we know how many names qualified to
   **    be written (within the selection).
   */
   hrErr = lpNTStm->lpVtbl->Write(
         lpNTStm,
         (short FAR*)&nNameCount,
         sizeof(nNameCount),
         &nWritten
   );
   if (hrErr != NOERROR) {
      OleDbgOutHResult("Write NameTable header returned", hrErr);
      goto error;
    }

   for(i = 0; i < lpOutlineNameTable->m_nCount; i++) {
      lpOutlineName=OutlineNameTable_GetName(lpOutlineNameTable, i);
      fStatus = OutlineName_SaveToStg(
            lpOutlineName,
            lplrSel,
            uFormat,
            lpNTStm,
            (BOOL FAR*)&fNameSaved
      );
      if (! fStatus) goto error;
      if (fNameSaved) nNameCount++;
   }

   /* write the final count of names written. */
   hrErr = lpNTStm->lpVtbl->Seek(
         lpNTStm,
         dlibZeroOffset,
         STREAM_SEEK_SET,
         NULL
   );
   if (hrErr != NOERROR) {
      OleDbgOutHResult("Seek to NameTable header returned", hrErr);
      goto error;
    }

   hrErr = lpNTStm->lpVtbl->Write(
         lpNTStm,
         (short FAR*)&nNameCount,
         sizeof(nNameCount),
         &nWritten
   );
   if (hrErr != NOERROR) {
      OleDbgOutHResult("Write NameTable count in header returned", hrErr);
      goto error;
    }

   OleStdRelease((LPUNKNOWN)lpNTStm);
   return TRUE;

error:
   if (lpNTStm)
      OleStdRelease((LPUNKNOWN)lpNTStm);

   return FALSE;
}

/* OutlineNameTable_LoadFromStg
 * ----------------------------
 *
 *      Load Name Table from file
 *
 *      Return TRUE if ok, FALSE if error
 */
BOOL OutlineNameTable_LoadFromStg(LPOUTLINENAMETABLE lpOutlineNameTable, LPSTORAGE lpSrcStg)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   HRESULT hrErr;
   IStream FAR* lpNTStm;
   ULONG nRead;
   short nCount;
   LPOUTLINENAME lpOutlineName;
   BOOL fStatus;
   short i;

   hrErr = lpSrcStg->lpVtbl->OpenStream(
         lpSrcStg,
         OLESTR("NameTable"),
         NULL,
         STGM_READ | STGM_SHARE_EXCLUSIVE,
         0,
         &lpNTStm
   );
   if (hrErr != NOERROR) {
      OleDbgOutHResult("OpenStream NameTable returned", hrErr);
      goto error;
    }

   hrErr = lpNTStm->lpVtbl->Read(lpNTStm,&nCount,sizeof(nCount),&nRead);
   if (hrErr != NOERROR) {
      OleDbgOutHResult("Read NameTable header returned", hrErr);
      goto error;
    }

   for (i = 0; i < nCount; i++) {
      lpOutlineName = OutlineApp_CreateName(lpOutlineApp);
      if (! lpOutlineName) goto error;
      fStatus = OutlineName_LoadFromStg(lpOutlineName, lpNTStm);
      if (! fStatus) goto error;
      OutlineNameTable_AddName(lpOutlineNameTable, lpOutlineName);
   }

   OleStdRelease((LPUNKNOWN)lpNTStm);
   return TRUE;

error:
   if (lpNTStm)
      OleStdRelease((LPUNKNOWN)lpNTStm);

   return FALSE;
}


OUTLRC.H

/*************************************************************************
**
**    OLE 2.0 Sample Code
**
**    outlrc.h
**
**    This file containes constants used in rc file for Outline.exe
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#if !defined( _OUTLRC_H_ )
#define _OUTLRC_H_

#ifndef RC_INVOKED
#pragma message ("INCLUDING OUTLRC.H from " __FILE__)
#endif  /* RC_INVOKED */

#if defined( OLE_SERVER ) && ! defined( INPLACE_SVR )

#ifdef TEST32
#define APPNAME                 OLESTR("SvrOut32")
#define APPNAMEA                "SvrOut32"
#define APPDESC                 "OLE 2.0 Server Sample Code 32"
#else
#define APPNAME                 OLESTR("SvrOutl")
#define APPNAMEA                "SvrOutl"
#define APPDESC                 "OLE 2.0 Server Sample Code"
#endif   //TEST32

#define APPMENU                 "SvrOutlMenu"
#define APPACCEL                "SvrOutlAccel"
#define FB_EDIT_ACCEL           "SvrOutlAccelFocusEdit"
#define APPICON                 "SvrOutlIcon"
#define APPWNDCLASS             "SvrOutlApp"
#define DOCWNDCLASS             "SvrOutlDoc"

#endif  // OLE_SERVER && ! INPLACE_SVR

#if defined( INPLACE_SVR )

#ifdef TEST32
#define APPNAME                 OLESTR("ISvrOu32")
#define APPNAMEA                "ISvrOu32"
#define APPDESC                 "OLE 2.0 In-Place Server Sample Code 32"
#else
#define APPNAME                 OLESTR("ISvrOtl")
#define APPNAMEA                "ISvrOtl"
#define APPDESC                 "OLE 2.0 In-Place Server Sample Code"
#endif //TEST32

#define APPMENU                 "SvrOutlMenu"
#define APPACCEL                "SvrOutlAccel"
#define FB_EDIT_ACCEL           "SvrOutlAccelFocusEdit"
#define APPICON                 "SvrOutlIcon"
#define APPWNDCLASS             "SvrOutlApp"
#define DOCWNDCLASS             "SvrOutlDoc"
#endif  // INPLACE_SVR

#if defined( OLE_CNTR ) && ! defined( INPLACE_CNTR )

#ifdef TEST32
#define APPNAME                 OLESTR("CntrOu32")
#define APPNAMEA                "CntrOu32"
#define APPDESC                 "OLE 2.0 Container Sample Code 32"
#else
#define APPNAME                 OLESTR("CntrOutl")
#define APPNAMEA                "CntrOutl"
#define APPDESC                 "OLE 2.0 Container Sample Code"
#endif //TEST32

#define APPMENU                 "CntrOutlMenu"
#define APPACCEL                "CntrOutlAccel"
#define FB_EDIT_ACCEL           "CntrOutlAccelFocusEdit"
#define APPICON                 "CntrOutlIcon"
#define APPWNDCLASS             "CntrOutlApp"
#define DOCWNDCLASS             "CntrOutlDoc"
#endif  // OLE_CNTR && ! INPLACE_CNTR

#if defined( INPLACE_CNTR )

#ifdef TEST32
#define APPNAME                 OLESTR("ICntrO32")
#define APPNAMEA                "ICntrO32"
#define APPDESC                 "OLE 2.0 In-Place Container Sample Code 32"
#else
#define APPNAME                 OLESTR("ICntrOtl")
#define APPNAMEA                "ICntrOtl"
#define APPDESC                 "OLE 2.0 In-Place Container Sample Code"
#endif

#define APPMENU                 "CntrOutlMenu"
#define APPACCEL                "CntrOutlAccel"
#define FB_EDIT_ACCEL           "CntrOutlAccelFocusEdit"
#define APPICON                 "CntrOutlIcon"
#define APPWNDCLASS             "CntrOutlApp"
#define DOCWNDCLASS             "CntrOutlDoc"
#endif  // INPLACE_CNTR

#if !defined( OLE_VERSION )
#define APPNAME                 OLESTR("Outline")
#define APPNAMEA                "Outline"
#define APPMENU                 "OutlineMenu"
#define APPACCEL                "OutlineAccel"
#define FB_EDIT_ACCEL           "OutlineAccelFocusEdit"
#define APPICON                 "OutlineIcon"
#define APPWNDCLASS             "OutlineApp"
#define DOCWNDCLASS             "OutlineDoc"
#define APPDESC                 "OLE 2.0 Sample Code"
#endif  // OLE_VERSION

#define IDM_FILE                       1000
#define IDM_F_NEW                      1050
#define IDM_F_OPEN                     1100
#define IDM_F_SAVE                     1150
#define IDM_F_SAVEAS                   1200
#define IDM_F_PRINT                    1300
#define IDM_F_PRINTERSETUP             1350
#define IDM_F_EXIT                     1450
#define IDM_EDIT                       2000
#define IDM_E_UNDO                     2050
#define IDM_E_CUT                      2150
#define IDM_E_COPY                     2200
#define IDM_E_PASTE                    2250
#define IDM_E_PASTESPECIAL             2255
#define IDM_E_CLEAR                    2300
#define IDM_E_SELECTALL                2560
#define IDM_LINE                       3000
#define IDM_L_ADDLINE                  3400
#define IDM_L_EDITLINE                 3450
#define IDM_L_INDENTLINE               3500
#define IDM_L_UNINDENTLINE             3550
#define IDM_L_SETLINEHEIGHT            3560
#define IDM_NAME                       4000
#define IDM_N_DEFINENAME               4050
#define IDM_N_GOTONAME                 4100
#define IDM_HELP                       5000
#define IDM_H_ABOUT                    5050
#define IDM_DEBUG                      6000
#define IDM_D_DEBUGLEVEL               6050
#define IDM_D_INSTALLMSGFILTER         6060
#define IDM_D_REJECTINCOMING            6070
#define IDM_O_BB_TOP                   6100
#define IDM_O_BB_BOTTOM                6150
#define IDM_O_BB_POPUP                 6200
#define IDM_O_BB_HIDE                  6210
#define IDM_O_FB_TOP                   6250
#define IDM_O_FB_BOTTOM                6300
#define IDM_O_FB_POPUP                 6350
#define IDM_O_HEAD_SHOW                6400
#define IDM_O_HEAD_HIDE                6450
#define IDM_O_SHOWOBJECT               6460
#define IDM_V_ZOOM_400                 6500
#define IDM_V_ZOOM_300                 6510
#define IDM_V_ZOOM_200                 6520
#define IDM_V_ZOOM_100                 6550
#define IDM_V_ZOOM_75                  6600
#define IDM_V_ZOOM_50                  6650
#define IDM_V_ZOOM_25                  6700
#define IDM_V_SETMARGIN_0              6750
#define IDM_V_SETMARGIN_1              6800
#define IDM_V_SETMARGIN_2              6850
#define IDM_V_SETMARGIN_3              6860
#define IDM_V_SETMARGIN_4              6870
#define IDM_V_ADDTOP_1                 6900
#define IDM_V_ADDTOP_2                 6910
#define IDM_V_ADDTOP_3                 6920
#define IDM_V_ADDTOP_4                 6930

#define IDM_FB_EDIT                    7000
#define IDM_FB_CANCEL                  7005
#define IDM_F2                         7010
#define IDM_ESCAPE                     7015

#define IDD_LINELISTBOX                101
#define IDD_EDIT                       102
#define IDD_COMBO                      103
#define IDD_DELETE                     104
#define IDD_CLOSE                      105
#define IDD_APPTEXT                    106
#define IDD_FROM                       107
#define IDD_TO                         108
#define IDD_BITMAPLOCATION             109
#define IDD_CHECK                      110
#define IDD_TEXT                       111
#define IDD_LIMIT                      112

#define IDC_LINELIST                   201
#define IDC_NAMETABLE                  202

#define WM_U_INITFRAMETOOLS            WM_USER

#ifdef RC_INVOKED
#include "debug.rc"
#endif  /* RC_INVOKED */

#endif // _OUTLRC_H_


OUTLTXTL.C

/*************************************************************************
**
**    OLE 2 Sample Code
**
**    outltxtl.c
**
**    This file contains TextLine methods and related support functions.
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#include "outline.h"

OLEDBGDATA

extern LPOUTLINEAPP g_lpApp;

/* TextLine_Create
 * ---------------
 *
 *      Create a text line object and return the pointer
 */
LPTEXTLINE TextLine_Create(HDC hDC, UINT nTab, LPSTR lpszText)
{
        LPTEXTLINE lpTextLine;

        lpTextLine=(LPTEXTLINE) New((DWORD)sizeof(TEXTLINE));
        if (lpTextLine == NULL) {
                OleDbgAssertSz(lpTextLine!=NULL,"Error allocating TextLine");
                return NULL;
        }

        TextLine_Init(lpTextLine, nTab, hDC);

        if (lpszText) {
                lpTextLine->m_nLength = lstrlen(lpszText);
                lstrcpy((LPSTR)lpTextLine->m_szText, lpszText);
        } else {
                lpTextLine->m_nLength = 0;
                lpTextLine->m_szText[0] = '\0';
        }

        TextLine_CalcExtents(lpTextLine, hDC);

        return(lpTextLine);
}

/* TextLine_Init
 * -------------
 *
 *      Calculate the width/height of a text line object.
 */
void TextLine_Init(LPTEXTLINE lpTextLine, int nTab, HDC hDC)
{
        Line_Init((LPLINE)lpTextLine, nTab, hDC);   // init the base class fields

        ((LPLINE)lpTextLine)->m_lineType = TEXTLINETYPE;
        lpTextLine->m_nLength = 0;
        lpTextLine->m_szText[0] = '\0';
}

/* TextLine_Delete
 * ---------------
 *
 *      Delete the TextLine structure
 */
void TextLine_Delete(LPTEXTLINE lpTextLine)
{
        Delete((LPVOID)lpTextLine);
}

/* TextLine_Edit
 * -------------
 *
 *      Edit the text line object.
 *
 *      Returns TRUE if line was changed
 *              FALSE if the line was NOT changed
 */
BOOL TextLine_Edit(LPTEXTLINE lpLine, HWND hWndDoc, HDC hDC)
{
#if defined( USE_FRAMETOOLS )
        LPFRAMETOOLS lptb = OutlineApp_GetFrameTools(g_lpApp);
#endif
        BOOL fStatus = FALSE;

#if defined( USE_FRAMETOOLS )
        FrameTools_FB_GetEditText(lptb, lpLine->m_szText, sizeof(lpLine->m_szText));
#else
        if (! InputTextDlg(hWndDoc, lpLine->m_szText, "Edit Line"))
                return FALSE;
#endif

        lpLine->m_nLength = lstrlen(lpLine->m_szText);
        TextLine_CalcExtents(lpLine, hDC);
        fStatus = TRUE;

        return fStatus;
}

/* TextLine_CalcExtents
 * --------------------
 *
 *      Calculate the width/height of a text line object.
 */
void TextLine_CalcExtents(LPTEXTLINE lpTextLine, HDC hDC)
{
        SIZE size;
        LPLINE lpLine = (LPLINE)lpTextLine;

        if (lpTextLine->m_nLength) {
                GetTextExtentPoint(hDC, lpTextLine->m_szText,
                                                        lpTextLine->m_nLength, &size);
                lpLine->m_nWidthInHimetric=size.cx;
                lpLine->m_nHeightInHimetric=size.cy;
        } else {
                // we still need to calculate proper height even for NULL string
                TEXTMETRIC tm;
                GetTextMetrics(hDC, &tm);

                // required to set height
                lpLine->m_nHeightInHimetric = tm.tmHeight;
                lpLine->m_nWidthInHimetric = 0;
        }

#if defined( _DEBUG )
        {
                RECT rc;
                rc.left = 0;
                rc.top = 0;
                rc.right = XformWidthInHimetricToPixels(hDC,
                                lpLine->m_nWidthInHimetric);
                rc.bottom = XformHeightInHimetricToPixels(hDC,
                                lpLine->m_nHeightInHimetric);

                OleDbgOutRect3("TextLine_CalcExtents", (LPRECT)&rc);
        }
#endif
}

/* TextLine_SetHeightInHimetric
 * ----------------------------
 *
 *      Set the height of a textline object.
 */
void TextLine_SetHeightInHimetric(LPTEXTLINE lpTextLine, int nHeight)
{
        if (!lpTextLine)
                return;

        ((LPLINE)lpTextLine)->m_nHeightInHimetric = nHeight;
}

/* TextLine_GetTextLen
 * -------------------
 *
 * Return length of string of the TextLine (not considering the tab level).
 */
int TextLine_GetTextLen(LPTEXTLINE lpTextLine)
{
        return lstrlen((LPSTR)lpTextLine->m_szText);
}

/* TextLine_GetTextData
 * --------------------
 *
 * Return the string of the TextLine (not considering the tab level).
 */
void TextLine_GetTextData(LPTEXTLINE lpTextLine, LPSTR lpszBuf)
{
        lstrcpy(lpszBuf, (LPSTR)lpTextLine->m_szText);
}

/* TextLine_GetOutlineData
 * -----------------------
 *
 * Return the CF_OUTLINE format data for the TextLine.
 */
BOOL TextLine_GetOutlineData(LPTEXTLINE lpTextLine, LPTEXTLINE lpBuf)
{
        TextLine_Copy((LPTEXTLINE)lpTextLine, lpBuf);
        return TRUE;
}

/* TextLine_Draw
 * -------------
 *
 *      Draw a text line object on a DC.
 * Parameters:
 *      hDC     - DC to which the line will be drawn
 *      lpRect  - the object rectangle in logical coordinates
 *      lpRectWBounds - bounding rect of the metafile underneath hDC
 *                      (NULL if hDC is not a metafile DC)
 *                      this is used by ContainerLine_Draw to draw the OLE obj
 *      fHighlight    - TRUE use selection highlight text color
 */
void TextLine_Draw(
                LPTEXTLINE  lpTextLine,
                HDC         hDC,
                LPRECT      lpRect,
                LPRECT      lpRectWBounds,
                BOOL        fHighlight
)
{
        RECT rc;
        int nBkMode;
        COLORREF clrefOld;

        if (!lpTextLine)
                return;

        rc = *lpRect;
        rc.left += ((LPLINE)lpTextLine)->m_nTabWidthInHimetric;
        rc.right += ((LPLINE)lpTextLine)->m_nTabWidthInHimetric;

        nBkMode = SetBkMode(hDC, TRANSPARENT);

        if (fHighlight) {
                /*Get proper txt colors */
                clrefOld = SetTextColor(hDC,GetSysColor(COLOR_HIGHLIGHTTEXT));
        }
        else {
                clrefOld = SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));
        }

        ExtTextOut(
                        hDC,
                        rc.left,
                        rc.top,
                        ETO_CLIPPED,
                        (LPRECT)&rc,
                        lpTextLine->m_szText,
                        lpTextLine->m_nLength,
                        (LPINT) NULL /* default char spacing */
        );

        SetTextColor(hDC, clrefOld);
        SetBkMode(hDC, nBkMode);
}

/* TextLine_DrawSelHilight
 * -----------------------
 *
 *      Handles selection of textline
 */
void TextLine_DrawSelHilight(LPTEXTLINE lpTextLine, HDC hDC, LPRECT lpRect, UINT itemAction, UINT itemState)
{
        if (itemAction & ODA_SELECT) {
                // check if there is a selection state change, ==> invert rect
                if (itemState & ODS_SELECTED) {
                        if (!((LPLINE)lpTextLine)->m_fSelected) {
                                ((LPLINE)lpTextLine)->m_fSelected = TRUE;
                                InvertRect(hDC, (LPRECT)lpRect);
                        }
                } else {
                        if (((LPLINE)lpTextLine)->m_fSelected) {
                                ((LPLINE)lpTextLine)->m_fSelected = FALSE;
                                InvertRect(hDC, lpRect);
                        }
                }
        } else if (itemAction & ODA_DRAWENTIRE) {
                ((LPLINE)lpTextLine)->m_fSelected=((itemState & ODS_SELECTED) ? TRUE : FALSE);
                InvertRect(hDC, lpRect);
        }
}

/* TextLine_Copy
 * -------------
 *
 *      Duplicate a textline
 */
BOOL TextLine_Copy(LPTEXTLINE lpSrcLine, LPTEXTLINE lpDestLine)
{
        _fmemcpy(lpDestLine, lpSrcLine, sizeof(TEXTLINE));
        return TRUE;
}

/* TextLine_CopyToDoc
 * ------------------
 *
 *      Copy a textline to another Document (usually ClipboardDoc)
 */
BOOL TextLine_CopyToDoc(LPTEXTLINE lpSrcLine, LPOUTLINEDOC lpDestDoc, int nIndex)
{
        LPTEXTLINE  lpDestLine;
        BOOL        fStatus = FALSE;

        lpDestLine = (LPTEXTLINE) New((DWORD)sizeof(TEXTLINE));
        if (lpDestLine == NULL) {
                OleDbgAssertSz(lpDestLine!=NULL,"Error allocating TextLine");
                return FALSE;
        }

        if (TextLine_Copy(lpSrcLine, lpDestLine)) {
                OutlineDoc_AddLine(lpDestDoc, (LPLINE)lpDestLine, nIndex);
                fStatus = TRUE;
        }

        return fStatus;
}

/* TextLine_SaveToStg
 * ------------------
 *
 *      Save a textline into a storage
 *
 *      Return TRUE if successful, FALSE otherwise
 */
BOOL TextLine_SaveToStm(LPTEXTLINE lpTextLine, LPSTREAM lpLLStm)
{
        HRESULT hrErr;
        ULONG nWritten;
        USHORT nLengthOnDisk;

        nLengthOnDisk = (USHORT) lpTextLine->m_nLength;

        hrErr = lpLLStm->lpVtbl->Write(
                        lpLLStm,
                        (LPVOID)&nLengthOnDisk,
                        sizeof(nLengthOnDisk),
                        &nWritten
        );
        if (hrErr != NOERROR) {
                OleDbgOutHResult("Write TextLine data (1) returned", hrErr);
                return FALSE;
    }

        hrErr = lpLLStm->lpVtbl->Write(
                        lpLLStm,
                        (LPVOID)lpTextLine->m_szText,
                        lpTextLine->m_nLength,
                        &nWritten
        );
        if (hrErr != NOERROR) {
                OleDbgOutHResult("Write TextLine data (2) returned", hrErr);
                return FALSE;
    }

        return TRUE;
}

/* TextLine_LoadFromStg
 * --------------------
 *
 *      Load a textline from storage
 */
LPLINE TextLine_LoadFromStg(LPSTORAGE lpSrcStg, LPSTREAM lpLLStm, LPOUTLINEDOC lpDestDoc)
{
        HRESULT hrErr;
        ULONG nRead;
        LPTEXTLINE lpTextLine;
        USHORT nLengthOnDisk;

        lpTextLine=(LPTEXTLINE) New((DWORD)sizeof(TEXTLINE));
        if (lpTextLine == NULL) {
                OleDbgAssertSz(lpTextLine!=NULL,"Error allocating TextLine");
                return NULL;
        }

        TextLine_Init(lpTextLine, 0, NULL);

        hrErr = lpLLStm->lpVtbl->Read(
                        lpLLStm,
                        (LPVOID)&nLengthOnDisk,
                        sizeof(nLengthOnDisk),
                        &nRead
        );
        if (hrErr != NOERROR) {
                OleDbgOutHResult("Read TextLine data (1) returned", hrErr);
                return NULL;
    }

        lpTextLine->m_nLength = (UINT) nLengthOnDisk;
        OleDbgAssert(lpTextLine->m_nLength < sizeof(lpTextLine->m_szText));

        hrErr = lpLLStm->lpVtbl->Read(
                        lpLLStm,
                        (LPVOID)&lpTextLine->m_szText,
                        lpTextLine->m_nLength,
                        &nRead
        );
        if (hrErr != NOERROR) {
                OleDbgOutHResult("Read TextLine data (1) returned", hrErr);
                return NULL;
    }

        lpTextLine->m_szText[lpTextLine->m_nLength] = '\0'; // add str terminator

        return (LPLINE)lpTextLine;
}


STATUS.C

/*************************************************************************
**
**    OLE 2.0 Sample Code
**
**    status.c
**
**    This file contains the window handlers, and various initialization
**    and utility functions for an application status bar.
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

// Application specific include files
#include "outline.h"
#include "message.h"
#include "status.h"

// Current status message.
static char lpszStatusMessage[256];

// Window proc for status window.
LRESULT FAR PASCAL StatusWndProc
   (HWND hwnd, unsigned message, WPARAM wParam, LPARAM lParam);

// List of all constant messages.
static STATMESG ControlList[2] =
{
   {   STATUS_READY,   "Ready."    },
   {   STATUS_BLANK,   " "         }
};

// List of all system menu messages.
static STATMESG SysMenuList[16] =
{
   {   SC_SIZE,        "Change the size of the window."            },
   {   SC_MOVE,        "Move the window."                          },
   {   SC_MINIMIZE,    "Make the window iconic."                   },
   {   SC_MAXIMIZE,    "Make the window the size of the screen."   },
   {   SC_NEXTWINDOW,  "Activate the next window."                 },
   {   SC_PREVWINDOW,  "Activate the previous window."             },
   {   SC_CLOSE,       "Close this window."                        },
   {   SC_VSCROLL,     "Vertical scroll?"                          },
   {   SC_HSCROLL,     "Horizontal scroll?"                        },
   {   SC_MOUSEMENU,   "A menu for mice."                          },
   {   SC_KEYMENU,     "A menu for keys (I guess)."                },
   {   SC_ARRANGE,     "Arrange something."                        },
   {   SC_RESTORE,     "Make the window noramally sized."          },
   {   SC_TASKLIST,    "Put up the task list dialog."              },
   {   SC_SCREENSAVE,  "Save the screen!  Run for your life!"      },
   {   SC_HOTKEY,      "Boy, is this key hot!"                     }
};

// Message type for popup messages.
typedef struct {
   HMENU hmenu;
   char string[MAX_MESSAGE];
} STATPOPUP;

// List of all popup messages.
static STATPOPUP PopupList[NUM_POPUP];

static UINT nCurrentPopup = 0;

/* RegisterStatusClass
 * -------------------
 *
 * Creates classes for status window.
 *
 * HINSTANCE hInstance
 *
 * RETURNS: TRUE if class successfully registered.
 *          FALSE otherwise.
 *
 * CUSTOMIZATION: Change class name.
 *
 */
BOOL RegisterStatusClass(HINSTANCE hInstance)
{
   WNDCLASS  wc;

   wc.lpszClassName = "ObjStatus";
   wc.lpfnWndProc   = StatusWndProc;
   wc.style         = 0;
   wc.hInstance     = hInstance;
   wc.hIcon         = NULL;
   wc.cbClsExtra    = 4;
   wc.cbWndExtra    = 0;
   wc.lpszMenuName  = NULL;
   wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
   wc.hbrBackground = GetStockObject(LTGRAY_BRUSH);

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

   return TRUE;
}

/* CreateStatusWindow
 * ------------------
 *
 * Creates status window.
 *
 * HWND hwndMain
 *
 * RETURNS: HWND of status window if creation is successful.
 *          NULL otherwise.
 *
 * CUSTOMIZATION: Change class name.
 *
 */
HWND CreateStatusWindow(HWND hWndApp, HINSTANCE hInst)
{
   RECT rect;
   int width, height;
   HWND hWndStatusBar;

   lstrcpy (lpszStatusMessage, ControlList[0].string);
   GetClientRect(hWndApp, &rect);
   width = rect.right - rect.left;
   height = rect.bottom - rect.top;

   hWndStatusBar = CreateWindow (
      "ObjStatus",
      "SvrStatus",
      WS_CHILD |
      WS_CLIPSIBLINGS |
      WS_VISIBLE,
      0, height - STATUS_HEIGHT,
      width,
      STATUS_HEIGHT,
      hWndApp,
      NULL,
      hInst,
      NULL
   );

   return hWndStatusBar;
}

/* DestroyStatusWindow
 * -------------------
 *
 * Destroys status window.
 *
 * CUSTOMIZATION: None.
 *
 */
void DestroyStatusWindow(HWND hWndStatusBar)
{
   DestroyWindow(hWndStatusBar);
}

/* AssignPopupMessage
 * ------------------
 *
 * Associates a string with a popup menu handle.
 *
 * HMENU hmenuPopup
 * char *szMessage
 *
 * CUSTOMIZATION: None.
 *
 */
void AssignPopupMessage(HMENU hmenuPopup, char *szMessage)
{
   if (nCurrentPopup < NUM_POPUP) {
      PopupList[nCurrentPopup].hmenu = hmenuPopup;
      lstrcpy(PopupList[nCurrentPopup].string, szMessage);
      ++nCurrentPopup;
   }
}

/* SetStatusText
 * -------------
 *
 * Show the message in the status line.
 */
void SetStatusText(HWND hWndStatusBar, LPSTR lpszMessage)
{
   lstrcpy (lpszStatusMessage, lpszMessage);
   InvalidateRect (hWndStatusBar, (LPRECT)NULL,  TRUE);
   UpdateWindow (hWndStatusBar);
}

/* GetItemMessage
 * --------------
 *
 * Retrieve the message associated with the given menu command item number.
 *
 * UINT wIDItem
 * LPVOID lpDoc
 *
 * CUSTOMIZATION: None.
 *
 */
void GetItemMessage(UINT wIDItem, LPSTR FAR* lplpszMessage)
{
   UINT i;

   *lplpszMessage = ControlList[1].string;
   for (i = 0; i < NUM_STATS; ++i) {
      if (wIDItem == MesgList[i].wIDItem) {
         *lplpszMessage = MesgList[i].string;
         break;
      }
   }
}

/* GetPopupMessage
 * ---------------
 *
 * Retrieve the message associated with the given popup menu.
 *
 * HMENU hmenuPopup
 * LPVOID lpDoc
 *
 * CUSTOMIZATION: None.
 *
 */
void GetPopupMessage(HMENU hmenuPopup, LPSTR FAR* lplpszMessage)
{
   UINT i;

   *lplpszMessage = ControlList[1].string;
   for (i = 0; i < nCurrentPopup; ++i) {
      if (hmenuPopup == PopupList[i].hmenu) {
         *lplpszMessage = PopupList[i].string;
         break;
      }
   }
}

/* GetSysMenuMessage
 * -----------------
 *
 * Retrieves the messages to correspond to items on the system menu.
 *
 *
 * UINT wIDItem
 * LPVOID lpDoc
 *
 * CUSTOMIZATION: None.
 *
 */
void GetSysMenuMessage(UINT wIDItem, LPSTR FAR* lplpszMessage)
{
   UINT i;

   *lplpszMessage = ControlList[1].string;
   for (i = 0; i < 16; ++i) {
      if (wIDItem == SysMenuList[i].wIDItem) {
         *lplpszMessage = SysMenuList[i].string;
         break;
      }
   }
}

/* GetControlMessage
 * -----------------
 *
 * Retrieves the general system messages.
 *
 *
 * STATCONTROL scCommand
 * LPVOID lpDoc
 *
 * CUSTOMIZATION: Add new messages.
 *
 */
void GetControlMessage(STATCONTROL scCommand, LPSTR FAR* lplpszMessage)
{
   UINT i;

   *lplpszMessage = ControlList[1].string;
   for (i = 0; i < 2; ++i) {
      if ((UINT)scCommand == ControlList[i].wIDItem) {
         *lplpszMessage = ControlList[i].string;
         break;
      }
   }
}

/* StatusWndProc
 * -------------
 *
 * Message handler for the statusbar window.
 *
 *
 * CUSTOMIZATION: None
 *
 */
LRESULT FAR PASCAL StatusWndProc
   (HWND hwnd, unsigned message, WPARAM wParam, LPARAM lParam)
{
   if (message == WM_PAINT) {
      RECT        rc;
      HDC         hdc;
      PAINTSTRUCT paintstruct;
      HPEN        hpenOld;
      HPEN        hpen;
      HFONT       hfontOld;
      HFONT       hfont;
      HPALETTE    hpalOld = NULL;
      POINT       point;

      BeginPaint (hwnd, &paintstruct);
      hdc = GetDC (hwnd);

      GetClientRect (hwnd, (LPRECT) &rc);

      hpenOld = SelectObject (hdc, GetStockObject (BLACK_PEN));

      MoveToEx (hdc, 0, 0, &point);
      LineTo (hdc, rc.right, 0);

      SelectObject (hdc, GetStockObject (WHITE_PEN));

      MoveToEx (hdc, STATUS_RRIGHT, STATUS_RTOP, &point);
      LineTo (hdc, STATUS_RRIGHT, STATUS_RBOTTOM);
      LineTo (hdc, STATUS_RLEFT-1, STATUS_RBOTTOM);

      hpen = CreatePen (PS_SOLID, 1, /* DKGRAY */ 0x00808080);
      SelectObject (hdc, hpen);

      MoveToEx (hdc, STATUS_RLEFT, STATUS_RBOTTOM-1, &point);
      LineTo (hdc, STATUS_RLEFT, STATUS_RTOP);
      LineTo (hdc, STATUS_RRIGHT, STATUS_RTOP);

      SetBkMode (hdc, TRANSPARENT);
      SetTextAlign (hdc, TA_LEFT | TA_TOP);
      hfont = CreateFont (STATUS_THEIGHT, 0, 0, 0, FW_NORMAL, FALSE, FALSE,
                     FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS,
                     CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
                     DEFAULT_PITCH | FF_DONTCARE, "MS Sans Serif");

      hfontOld = SelectObject(hdc, hfont);

      TextOut (hdc, STATUS_TLEFT, STATUS_TTOP,
             lpszStatusMessage,
             lstrlen(lpszStatusMessage));

      // Restore original objects
      SelectObject (hdc, hfontOld);
      SelectObject (hdc, hpenOld);
      DeleteObject (hpen);
      DeleteObject (hfont);

      ReleaseDC (hwnd, hdc);
      EndPaint (hwnd, &paintstruct);

      return 0;
   }
   else {
      return DefWindowProc(hwnd, message, wParam, lParam);
   }
}


STATUS.H

/*************************************************************************
**
**    OLE 2.0 Sample Code
**
**    status.h
**
**    This file contains typedefs, defines, global variable declarations,
**    and function prototypes for the status bar window.
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

// Sizes of statusbar items
#if defined( USE_STATUSBAR )
   #define STATUS_HEIGHT   23
#else
   #define STATUS_HEIGHT   0
#endif
#define STATUS_RLEFT    8
#define STATUS_RRIGHT   400
#define STATUS_RTOP     3
#define STATUS_RBOTTOM  20
#define STATUS_TTOP     4
#define STATUS_TLEFT    11
#define STATUS_THEIGHT  18

typedef enum {
   STATUS_READY,
   STATUS_BLANK
} STATCONTROL;

// Window for status bar.
extern HWND hwndStatusbar;

BOOL RegisterStatusClass(HINSTANCE hInstance);
HWND CreateStatusWindow(HWND hWndApp, HINSTANCE hInst);
void DestroyStatusWindow(HWND hWndStatusBar);

void AssignPopupMessage(HMENU hmenuPopup, char *szMessage);

void SetStatusText(HWND hWndStatusBar, LPSTR lpszMessage);
void GetItemMessage(UINT wIDItem, LPSTR FAR* lplpszMessage);
void GetPopupMessage(HMENU hmenuPopup, LPSTR FAR* lplpszMessage);
void GetSysMenuMessage(UINT wIDItem, LPSTR FAR* lplpszMessage);
void GetControlMessage(STATCONTROL scCommand, LPSTR FAR* lplpszMessage);


SVRBASE.C

/*************************************************************************
**
**    OLE 2 Server Sample Code
**
**    svrbase.c
**
**    This file contains all interfaces, methods and related support
**    functions for the basic OLE Object (Server) application. The
**    basic OLE Object application supports embedding an object and
**    linking to a file-based or embedded object as a whole. The basic
**    Object application includes the following implementation objects:
**
**    ClassFactory (aka. ClassObject) Object    (see file classfac.c)
**      exposed interfaces:
**          IClassFactory interface
**
**    ServerDoc Object
**      exposed interfaces:
**          IUnknown
**          IOleObject interface
**          IPersistStorage interface
**          IDataObject interface
**
**    ServerApp Object
**      exposed interfaces:
**          IUnknown
**
**    (c) Copyright Microsoft Corp. 1992 - 1997 All Rights Reserved
**
*************************************************************************/

#include "outline.h"

OLEDBGDATA

extern LPOUTLINEAPP             g_lpApp;
extern IOleObjectVtbl           g_SvrDoc_OleObjectVtbl;
extern IPersistStorageVtbl      g_SvrDoc_PersistStorageVtbl;

#if defined( INPLACE_SVR )
extern IOleInPlaceObjectVtbl        g_SvrDoc_OleInPlaceObjectVtbl;
extern IOleInPlaceActiveObjectVtbl  g_SvrDoc_OleInPlaceActiveObjectVtbl;
#endif  // INPLACE_SVR

#if defined( SVR_TREATAS )
extern IStdMarshalInfoVtbl      g_SvrDoc_StdMarshalInfoVtbl;
#endif  // SVR_TREATAS

// REVIEW: should use string resource for messages
extern OLECHAR ErrMsgSaving[];
extern OLECHAR ErrMsgFormatNotSupported[];
static OLECHAR ErrMsgPSSaveFail[] = OLESTR("PSSave failed");
static OLECHAR ErrMsgLowMemNClose[] = OLESTR("Warning OUT OF MEMORY! We must close down");
extern char g_szUpdateCntrDoc[] = "&Update %s";
extern char g_szExitNReturnToCntrDoc[] = "E&xit && Return to %s";

/*************************************************************************
** ServerDoc::IOleObject interface implementation
*************************************************************************/

// IOleObject::QueryInterface method

STDMETHODIMP SvrDoc_OleObj_QueryInterface(
      LPOLEOBJECT             lpThis,
      REFIID                  riid,
      LPVOID FAR*             lplpvObj
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;

   return OleDoc_QueryInterface((LPOLEDOC)lpServerDoc, riid, lplpvObj);
}

// IOleObject::AddRef method

STDMETHODIMP_(ULONG) SvrDoc_OleObj_AddRef(LPOLEOBJECT lpThis)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;

   OleDbgAddRefMethod(lpThis, "IOleObject");

   return OleDoc_AddRef((LPOLEDOC)lpServerDoc);
}

// IOleObject::Release method

STDMETHODIMP_(ULONG) SvrDoc_OleObj_Release(LPOLEOBJECT lpThis)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;

   OleDbgReleaseMethod(lpThis, "IOleObject");

   return OleDoc_Release((LPOLEDOC)lpServerDoc);
}

// IOleObject::SetClientSite method

STDMETHODIMP SvrDoc_OleObj_SetClientSite(
      LPOLEOBJECT             lpThis,
      LPOLECLIENTSITE         lpclientSite
)
{
      LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;

   // artificial AddRef in case object is destroyed during call
   SvrDoc_OleObj_AddRef(lpThis);

   OLEDBG_BEGIN2("SvrDoc_OleObj_SetClientSite\r\n")

   // SetClientSite is only valid to call on an embedded object
   if (lpOutlineDoc->m_docInitType != DOCTYPE_EMBEDDED) {
      OleDbgAssert(lpOutlineDoc->m_docInitType == DOCTYPE_EMBEDDED);
      OLEDBG_END2
      // release artificial AddRef
      SvrDoc_OleObj_Release(lpThis);
      return E_UNEXPECTED;
   }

   /* if we currently have a client site ptr, then release it. */
   if (lpServerDoc->m_lpOleClientSite)
      OleStdRelease((LPUNKNOWN)lpServerDoc->m_lpOleClientSite);

   lpServerDoc->m_lpOleClientSite = (LPOLECLIENTSITE) lpclientSite;
   // NOTE: to be able to hold onto clientSite pointer, we must AddRef it
   if (lpclientSite)
      lpclientSite->lpVtbl->AddRef(lpclientSite);

   OLEDBG_END2
   // release artificial AddRef
   SvrDoc_OleObj_Release(lpThis);
   return NOERROR;
}

// IOleObject::GetClientSite method

STDMETHODIMP SvrDoc_OleObj_GetClientSite(
      LPOLEOBJECT             lpThis,
      LPOLECLIENTSITE FAR*    lplpClientSite
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;

   // artificial AddRef in case object is destroyed during call
   SvrDoc_OleObj_AddRef(lpThis);

   OleDbgOut2("SvrDoc_OleObj_GetClientSite\r\n");

   /* NOTE: we MUST AddRef this interface pointer to give the
   **    caller a personal copy of the pointer
   */
   lpServerDoc->m_lpOleClientSite->lpVtbl->AddRef(
         lpServerDoc->m_lpOleClientSite
   );
   *lplpClientSite = lpServerDoc->m_lpOleClientSite;

   // release artificial AddRef
   SvrDoc_OleObj_Release(lpThis);

   return NOERROR;

}

// IOleObject::SetHostNames method

STDMETHODIMP SvrDoc_OleObj_SetHostNames(
      LPOLEOBJECT             lpThis,
      LPCOLESTR               szContainerApp,
      LPCOLESTR               szContainerObj
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;
   LPOUTLINEDOC    lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   char szAnsiStr1[256], szAnsiStr2[256], szAnsiStr3[256];

   // artificial AddRef in case object is destroyed during call
   SvrDoc_OleObj_AddRef(lpThis);

   OleDbgOut2("SvrDoc_OleObj_SetHostNames\r\n");

   OLESTRCPY(/*(LPOLESTR)*/lpServerDoc->m_szContainerApp, szContainerApp);
   OLESTRCPY(/*(LPOLESTR)*/lpServerDoc->m_szContainerObj, szContainerObj);

   /* The Window title for an embedded object is constructed as
   **    follows:
   **      <server app name> - <obj short type> in <cont. doc name>
   **
   **    here we construct the current document title portion of the
   **    name which follows the '-'. OutlineDoc_SetTitle prepends the
   **    "<server app name> - " to the document title.
   */
   // REVIEW: this string should be loaded from string resource
   W2A (lpServerDoc->m_szContainerObj, szAnsiStr1, 256);
   W2A (SHORTUSERTYPENAME, szAnsiStr3, 256);
   wsprintf(szAnsiStr2, "%s in %s",
         szAnsiStr3, (LPSTR)szAnsiStr1);
   A2W (szAnsiStr2, lpOutlineDoc->m_szFileName, OLEUI_CCHPATHMAX);

   lpOutlineDoc->m_lpszDocTitle = lpOutlineDoc->m_szFileName;
   OutlineDoc_SetTitle(lpOutlineDoc, FALSE /*fMakeUpperCase*/);

   /* NOTE: update the application menus correctly for an embedded
   **    object. the changes include:
   **      1 Remove File/New and File/Open (SDI ONLY)
   **      2 Change File/Save As.. to File/Save Copy As..
   **      3 Change File menu so it contains "Update" instead of "Save"
   **      4 Change File/Exit to File/Exit & Return to <client doc>"
   */
   ServerDoc_UpdateMenu(lpServerDoc);

   // release artificial AddRef
   SvrDoc_OleObj_Release(lpThis);

   return NOERROR;
}

// IOleObject::Close method

STDMETHODIMP SvrDoc_OleObj_Close(
      LPOLEOBJECT             lpThis,
      DWORD                   dwSaveOption
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;
   BOOL fStatus;

   OLEDBG_BEGIN2("SvrDoc_OleObj_Close\r\n")

   /* NOTE: the OLE 2.0 user model is that embedded objects should
   **    always be saved when closed WITHOUT any prompting to the
   **    user. this is the recommendation irregardless of whether the
   **    object is activated in-place or open in its own window.
   **    this is a CHANGE from the OLE 1.0 user model where it
   **    was the guideline that servers always prompt to save changes.
   **    thus OLE 2.0 compound document oriented container's should
   **    always pass dwSaveOption==OLECLOSE_SAVEIFDIRTY. it is
   **    possible that for programmatic uses a container may want to
   **    specify a different dwSaveOption. the implementation of
   **    various save options can be tricky, particularly considering
   **    cases involving in-place activation. the following would be
   **    reasonable behavior:
   **
   **      (1) OLECLOSE_SAVEIFDIRTY: if dirty, save. close.
   **      (2) OLECLOSE_NOSAVE: close.
   **      (3) OLECLOSE_PROMPTSAVE:
   **        (a) object visible, but not in-place:
   **               if not dirty, close.
   **               switch(prompt)
   **                  case IDYES: save. close.
   **                  case IDNO: close.
   **                  case IDCANCEL: return OLE_E_PROMPTSAVECANCELLED
   **        (b) object invisible (includes UIDeactivated object)
   **               if dirty, save. close.
   **               NOTE: NO PROMPT. it is not appropriate to prompt
   **                     if the object is not visible.
   **        (c) object is in-place active:
   **               if dirty, save. close.
   **               NOTE: NO PROMPT. it is not appropriate to prompt
   **                     if the object is active in-place.
   */
   fStatus = OutlineDoc_Close((LPOUTLINEDOC)lpServerDoc, dwSaveOption);
   OleDbgAssertSz(fStatus == TRUE, "SvrDoc_OleObj_Close failed\r\n");

   OLEDBG_END2

   return (fStatus ? NOERROR : E_FAIL);
}

// IOleObject::SetMoniker method

STDMETHODIMP SvrDoc_OleObj_SetMoniker(
      LPOLEOBJECT             lpThis,
      DWORD                   dwWhichMoniker,
      LPMONIKER               lpmk
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;
   LPOLEDOC lpOleDoc = (LPOLEDOC)lpServerDoc;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   LPMONIKER lpmkFull = NULL;
   HRESULT hrErr;
   HRESULT sc;

   // artificial AddRef in case object is destroyed during call
   SvrDoc_OleObj_AddRef(lpThis);

   OLEDBG_BEGIN2("SvrDoc_OleObj_SetMoniker\r\n")

   /* NOTE: if our full moniker is passed then we can use it,
   **    otherwise we must call back to our ClientSite to get our full
   **    moniker.
   */
   if (dwWhichMoniker == OLEWHICHMK_OBJFULL) {

      /* Register the document as running with the new moniker and
      **      notify any clients that our moniker has changed.
      */
      OleDoc_DocRenamedUpdate(lpOleDoc, lpmk);

      if (lpOutlineDoc->m_docInitType != DOCTYPE_EMBEDDED) {
         IBindCtx  FAR  *pbc = NULL;
         LPOLESTR lpszName = NULL;

         /* NOTE: if this is a FILE-based or untitled document
         **    then we should accept this new moniker as our document's
         **    moniker. we will remember this moniker instead of the
         **    FileMoniker that we have by default. this allows
         **    systems that use special monikers to track the
         **    location of documents to inform a document that is a
         **    link source of its special moniker. this enables the
         **    document to use this special moniker when building
         **    composite monikers to identify contained objects and
         **    pseudo objects (ranges).
         **
         **    we should also use the DisplayName form of this
         **    moniker as our document name in our window title.
         */
         if (lpOleDoc->m_lpFileMoniker) {
            lpOleDoc->m_lpFileMoniker->lpVtbl->Release(
                  lpOleDoc->m_lpFileMoniker);
         }
         lpOleDoc->m_lpFileMoniker = lpmk;
         // we must AddRef the moniker to hold on to it
         lpmk->lpVtbl->AddRef(lpmk);

         /* we should also use the DisplayName form of this
         **    moniker as our document name in our window title.
         */
         CreateBindCtx(0, (LPBC FAR*)&pbc);
         lpmk->lpVtbl->GetDisplayName(lpmk,pbc,NULL,&lpszName);
         pbc->lpVtbl->Release(pbc);
         if (lpszName) {
            OLESTRCPY(lpOutlineDoc->m_szFileName, lpszName);
            lpOutlineDoc->m_lpszDocTitle = lpOutlineDoc->m_szFileName;
            OutlineDoc_SetTitle(lpOutlineDoc, FALSE /*fMakeUpperCase*/);
            OleStdFreeString(lpszName, NULL);
         }
      }

      OLEDBG_END2
      // release artificial AddRef
      SvrDoc_OleObj_Release(lpThis);
      return NOERROR;
   }

   /* if the passed moniker was NOT a full moniker then we must call
   **    back to our ClientSite to get our full moniker. this is
   **    needed in order to register in the RunningObjectTable. if we
   **    don't have a ClientSite then this is an error.
   */
   if (lpServerDoc->m_lpOleClientSite == NULL) {
      sc = E_FAIL;
      goto error;
   }

   hrErr = lpServerDoc->m_lpOleClientSite->lpVtbl->GetMoniker(
         lpServerDoc->m_lpOleClientSite,
         OLEGETMONIKER_ONLYIFTHERE,
         OLEWHICHMK_OBJFULL,
         &lpmkFull
   );
   if (hrErr != NOERROR) {
      sc = hrErr;
      goto error;
   }

   /* Register the document as running with the new moniker and
   **      notify any clients that our moniker has changed.
   */
   OleDoc_DocRenamedUpdate(lpOleDoc, lpmkFull);

   if (lpmkFull)
      OleStdRelease((LPUNKNOWN)lpmkFull);

   OLEDBG_END2

   // release artificial AddRef
   SvrDoc_OleObj_Release(lpThis);
   return NOERROR;

error:
   OLEDBG_END2

   // release artificial AddRef
   SvrDoc_OleObj_Release(lpThis);
   return sc;
}

// IOleObject::GetMoniker method

STDMETHODIMP SvrDoc_OleObj_GetMoniker(
      LPOLEOBJECT             lpThis,
      DWORD                   dwAssign,
      DWORD                   dwWhichMoniker,
      LPMONIKER FAR*          lplpmk
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;
   LPOLEDOC lpOleDoc = (LPOLEDOC)lpServerDoc;
   HRESULT sc;

   // artificial AddRef in case object is destroyed during call
   SvrDoc_OleObj_AddRef(lpThis);

   OLEDBG_BEGIN2("SvrDoc_OleObj_GetMoniker\r\n")

   /* NOTE: we must make sure to set all out parameters to NULL. */
   *lplpmk = NULL;

   if (lpServerDoc->m_lpOleClientSite) {

      /* document is an embedded object. retrieve our moniker from
      **    our container.
      */
      OLEDBG_BEGIN2("IOleClientSite::GetMoniker called\r\n")
      sc =  lpServerDoc->m_lpOleClientSite->lpVtbl->GetMoniker(
            lpServerDoc->m_lpOleClientSite,
            dwAssign,
            dwWhichMoniker,
            lplpmk
      ) ;
      OLEDBG_END2

   } else if (lpOleDoc->m_lpFileMoniker) {

      /* document is a top-level user document (either
      **    file-based or untitled). return the FileMoniker stored
      **    with the document; it uniquely identifies the document.
      */
      if (dwWhichMoniker == OLEWHICHMK_CONTAINER)
         sc = E_INVALIDARG;  // file-based object has no CONTAINER moniker
      else {
         *lplpmk = lpOleDoc->m_lpFileMoniker;
         (*lplpmk)->lpVtbl->AddRef(*lplpmk); // must AddRef to pass out ptr
         sc = S_OK;
      }

   } else {
      // document is not yet fully initialized => no moniker
      sc = E_FAIL;
   }

   OLEDBG_END2

   // release artificial AddRef
   SvrDoc_OleObj_Release(lpThis);
   return sc;
}

// IOleObject::InitFromData method

STDMETHODIMP SvrDoc_OleObj_InitFromData(
      LPOLEOBJECT             lpThis,
      LPDATAOBJECT            lpDataObject,
      BOOL                    fCreation,
      DWORD                   reserved
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;

   OLEDBG_BEGIN2("SvrDoc_OleObj_InitFromData\r\n")

   // REVIEW: NOT YET IMPLEMENTED

   OLEDBG_END2
   return E_NOTIMPL;
}

// IOleObject::GetClipboardData method

STDMETHODIMP SvrDoc_OleObj_GetClipboardData(
      LPOLEOBJECT             lpThis,
      DWORD                   reserved,
      LPDATAOBJECT FAR*       lplpDataObject
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;

   OLEDBG_BEGIN2("SvrDoc_OleObj_GetClipboardData\r\n")

   // REVIEW: NOT YET IMPLEMENTED

   OLEDBG_END2
   return E_NOTIMPL;
}

// IOleObject::DoVerb method

STDMETHODIMP SvrDoc_OleObj_DoVerb(
      LPOLEOBJECT             lpThis,
      LONG                    lVerb,
      LPMSG                   lpmsg,
      LPOLECLIENTSITE         lpActiveSite,
      LONG                    lindex,
      HWND                    hwndParent,
      LPCRECT                 lprcPosRect
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   HRESULT sc = S_OK;

   // artificial AddRef in case object is destroyed during call
   SvrDoc_OleObj_AddRef(lpThis);

   OLEDBG_BEGIN2("SvrDoc_OleObj_DoVerb\r\n")

   switch (lVerb) {

      default:
         /* NOTE: when an unknown verb number is given, the
         **    server must take careful action:
         **    1. if it is one of the specially defined OLEIVERB
         **    (negative numbered) verbs, the app should return an
         **    error (E_NOTIMPL) and perform no action.
         **
         **    2. if the verb is a application specific verb
         **    (positive numbered verb), then the app should
         **    return the special HRESULT (OLEOBJ_S_INVALIDVERB). BUT,
         **    we should still perform our normal primary verb action.
         */
         if (lVerb < 0) {
            OLEDBG_END2
            return E_NOTIMPL;
         } else {
            sc = OLEOBJ_S_INVALIDVERB;
         }

         // deliberatly fall through to Primary Verb

#if !defined( INPLACE_SVR )
      case 0:
      case OLEIVERB_SHOW:
      case OLEIVERB_OPEN:
         OutlineDoc_ShowWindow(lpOutlineDoc);
         break;

      case OLEIVERB_HIDE:
         OleDoc_HideWindow((LPOLEDOC)lpServerDoc, FALSE /*fShutdown*/);
         break;
#endif  // ! INPLACE_SVR
#if defined( INPLACE_SVR )
      case 0:
      case OLEIVERB_SHOW:

         /* NOTE: if our window is already open (visible) then
         **    we should simply surface the open window. if not,
         **    then we can do our primary action of in-place
         **    activation.
         */
         if ( lpServerDoc->m_lpOleClientSite
               && ! (IsWindowVisible(lpOutlineDoc->m_hWndDoc) &&
                     ! lpServerDoc->m_fInPlaceActive) ) {
            ServerDoc_DoInPlaceActivate(
                  lpServerDoc, lVerb, lpmsg, lpActiveSite);
         }
         OutlineDoc_ShowWindow(lpOutlineDoc);
         break;

      case 1:
      case OLEIVERB_OPEN:
         ServerDoc_DoInPlaceDeactivate(lpServerDoc);
         OutlineDoc_ShowWindow(lpOutlineDoc);
         break;

      case OLEIVERB_HIDE:
         if (lpServerDoc->m_fInPlaceActive) {

            SvrDoc_IPObj_UIDeactivate(
                  (LPOLEINPLACEOBJECT)&lpServerDoc->m_OleInPlaceObject);

#if defined( SVR_INSIDEOUT )
            /* NOTE: an inside-out style in-place server will
            **    NOT hide its window in UIDeactive (an outside-in
            **    style object will hide its window in
            **    UIDeactivate). thus we need to explicitly hide
            **    our window now.
            */
            ServerDoc_DoInPlaceHide(lpServerDoc);
#endif // INSIEDOUT

         } else {
            OleDoc_HideWindow((LPOLEDOC)lpServerDoc, FALSE /*fShutdown*/);
         }
         break;

      case OLEIVERB_UIACTIVATE:

#if defined( SVR_INSIDEOUT )
      /* NOTE: only an inside-out style object supports
      **    INPLACEACTIVATE verb
      */
      case OLEIVERB_INPLACEACTIVATE:
#endif // SVR_INSIDEOUT

         /* NOTE: if our window is already open (visible) then
         **    we can NOT activate in-place.
         */
         if (IsWindowVisible(lpOutlineDoc->m_hWndDoc) &&
                  ! lpServerDoc->m_fInPlaceActive ) {
            sc = OLE_E_NOT_INPLACEACTIVE;
         } else {
            sc =  ServerDoc_DoInPlaceActivate(
                  lpServerDoc, lVerb, lpmsg, lpActiveSite) ;
            if (SUCCEEDED(sc))
               OutlineDoc_ShowWindow(lpOutlineDoc);
         }
         break;
#endif  // INPLACE_SVR
   }

   OLEDBG_END2

   // release artificial AddRef
   SvrDoc_OleObj_Release(lpThis);
   return sc;
}

// IOleObject::EnumVerbs method

STDMETHODIMP SvrDoc_OleObj_EnumVerbs(
      LPOLEOBJECT             lpThis,
      LPENUMOLEVERB FAR*      lplpenumOleVerb
)
{
   OleDbgOut2("SvrDoc_OleObj_EnumVerbs\r\n");

   /* NOTE: we must make sure to set all out parameters to NULL. */
   *lplpenumOleVerb = NULL;

   /* An object implemented as a server EXE (as this sample
   **    is) may simply return OLE_S_USEREG to instruct the OLE
   **    DefHandler to call the OleReg* helper API which uses info in
   **    the registration database. Alternatively, the OleRegEnumVerbs
   **    API may be called directly. Objects implemented as a server
   **    DLL may NOT return OLE_S_USEREG; they must call the OleReg*
   **    API or provide their own implementation. For EXE based
   **    objects it is more efficient to return OLE_S_USEREG, because
   **    in then the verb enumerator is instantiated in the callers
   **    process space and no LRPC remoting is required.
   */
   return OLE_S_USEREG;
}

// IOleObject::Update method

STDMETHODIMP SvrDoc_OleObj_Update(LPOLEOBJECT lpThis)
{
   OleDbgOut2("SvrDoc_OleObj_Update\r\n");

   /* NOTE: a server-only app is always "up-to-date".
   **    a container-app which contains links where the link source
   **    has changed since the last update of the link would be
   **    considered "out-of-date". the "Update" method instructs the
   **    object to get an update from any out-of-date links.
   */

   return NOERROR;
}

// IOleObject::IsUpToDate method

STDMETHODIMP SvrDoc_OleObj_IsUpToDate(LPOLEOBJECT lpThis)
{
   OleDbgOut2("SvrDoc_OleObj_IsUpToDate\r\n");

   /* NOTE: a server-only app is always "up-to-date".
   **    a container-app which contains links where the link source
   **    has changed since the last update of the link would be
   **    considered "out-of-date".
   */
   return NOERROR;
}

// IOleObject::GetUserClassID method

STDMETHODIMP SvrDoc_OleObj_GetUserClassID(
      LPOLEOBJECT             lpThis,
      LPCLSID                 lpClassID
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;
   OleDbgOut2("SvrDoc_OleObj_GetClassID\r\n");

   /* NOTE: we must be carefull to return the correct CLSID here.
   **    if we are currently preforming a "TreatAs (aka. ActivateAs)"
   **    operation then we need to return the class of the object
   **    written in the storage of the object. otherwise we would
   **    return our own class id.
   */
   return ServerDoc_GetClassID(lpServerDoc, lpClassID);
}

// IOleObject::GetUserType method

STDMETHODIMP SvrDoc_OleObj_GetUserType(
      LPOLEOBJECT             lpThis,
      DWORD                   dwFormOfType,
      LPOLESTR FAR*           lpszUserType
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;
   OleDbgOut2("SvrDoc_OleObj_GetUserType\r\n");

   /* NOTE: we must make sure to set all out parameters to NULL. */
   *lpszUserType = NULL;

   /* NOTE: we must be carefull to return the correct user type here.
   **    if we are currently preforming a "TreatAs (aka. ActivateAs)"
   **    operation then we need to return the user type name that
   **    corresponds to the class of the object we are currently
   **    emmulating. otherwise we should return our normal user type
   **    name corresponding to our own class. This routine determines
   **    the current clsid in effect.
   **
   **    An object implemented as a server EXE (as this sample
   **    is) may simply return OLE_S_USEREG to instruct the OLE
   **    DefHandler to call the OleReg* helper API which uses info in
   **    the registration database. Alternatively, the OleRegGetUserType
   **    API may be called directly. Objects implemented as a server
   **    DLL may NOT return OLE_S_USEREG; they must call the OleReg*
   **    API or provide their own implementation. For EXE based
   **    objects it is more efficient to return OLE_S_USEREG, because
   **    in then the return string is instantiated in the callers
   **    process space and no LRPC remoting is required.
   */
#if defined( SVR_TREATAS )
   if (! IsEqualCLSID(&lpServerDoc->m_clsidTreatAs, &CLSID_NULL) )
      return OleRegGetUserType(
         (REFCLSID)&lpServerDoc->m_clsidTreatAs,dwFormOfType,lpszUserType);
   else
#endif  // SVR_TREATAS

   return OLE_S_USEREG;
}

// IOleObject::SetExtent method

STDMETHODIMP SvrDoc_OleObj_SetExtent(
      LPOLEOBJECT             lpThis,
      DWORD                   dwDrawAspect,
      LPSIZEL                 lplgrc
)
{
   OleDbgOut2("SvrDoc_OleObj_SetExtent\r\n");

   /* SVROUTL does NOT allow the object's size to be set by its
   **    container. the size of the ServerDoc object is determined by
   **    the data contained within the document.
   */
   return E_FAIL;
}

// IOleObject::GetExtent method

STDMETHODIMP SvrDoc_OleObj_GetExtent(
      LPOLEOBJECT             lpThis,
      DWORD                   dwDrawAspect,
      LPSIZEL                 lpsizel
)
{
   LPOLEDOC lpOleDoc =
         (LPOLEDOC)((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;

   // artificial AddRef in case object is destroyed during call
   SvrDoc_OleObj_AddRef(lpThis);

   OleDbgOut2("SvrDoc_OleObj_GetExtent\r\n");

   /* NOTE: it is VERY important to check which aspect the caller
   **    is asking about. an object implemented by a server EXE MAY
   **    fail to return extents when asked for DVASPECT_ICON.
   */
   if (dwDrawAspect == DVASPECT_CONTENT) {
      OleDoc_GetExtent(lpOleDoc, lpsizel);
      // release artificial AddRef
      SvrDoc_OleObj_Release(lpThis);
      return NOERROR;
   }

#if defined( LATER )

   else if (dwDrawAspect == DVASPECT_THUMBNAIL)
   {
      /* as our thumbnail we will render only the first page of the
      **    document. calculate extents of our thumbnail rendering.
      **
      ** NOTE: thumbnails are most often used by applications in
      **    FindFile or FileOpen type dialogs to give the user a
      **    quick view of the contents of the file or object.
      */
      OleDoc_GetThumbnailExtent(lpOleDoc, lpsizel);
      // release artificial AddRef
      SvrDoc_OleObj_Release(lpThis);
      return NOERROR;
   }
#endif

   else
   {
      // release artificial AddRef
      SvrDoc_OleObj_Release(lpThis);
      return E_FAIL;
   }
}

// IOleObject::Advise method

STDMETHODIMP SvrDoc_OleObj_Advise(
      LPOLEOBJECT             lpThis,
      LPADVISESINK            lpAdvSink,
      LPDWORD                 lpdwConnection
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;
   HRESULT hrErr;
   HRESULT   sc;

   // artificial AddRef in case object is destroyed during call
   SvrDoc_OleObj_AddRef(lpThis);

   OLEDBG_BEGIN2("SvrDoc_OleObj_Advise\r\n");

   if (lpServerDoc->m_OleDoc.m_fObjIsClosing)
   {
       //  We don't accept any more Advise's once we're closing
       sc = OLE_E_ADVISENOTSUPPORTED;
       goto error;
   }

   if (lpServerDoc->m_lpOleAdviseHldr == NULL &&
      CreateOleAdviseHolder(&lpServerDoc->m_lpOleAdviseHldr) != NOERROR) {
      sc = E_OUTOFMEMORY;
      goto error;
   }

   OLEDBG_BEGIN2("IOleAdviseHolder::Advise called\r\n")
   hrErr = lpServerDoc->m_lpOleAdviseHldr->lpVtbl->Advise(
         lpServerDoc->m_lpOleAdviseHldr,
         lpAdvSink,
         lpdwConnection
   );
   OLEDBG_END2

   OLEDBG_END2
   // release artificial AddRef
   SvrDoc_OleObj_Release(lpThis);
   return hrErr;

error:
   OLEDBG_END2
   *lpdwConnection = 0;
   // release artificial AddRef
   SvrDoc_OleObj_Release(lpThis);
   return sc;
}

// IOleObject::Unadvise method

STDMETHODIMP SvrDoc_OleObj_Unadvise(LPOLEOBJECT lpThis, DWORD dwConnection)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;
   HRESULT hrErr;
   HRESULT   sc;

   // artificial AddRef in case object is destroyed during call
   SvrDoc_OleObj_AddRef(lpThis);

   OLEDBG_BEGIN2("SvrDoc_OleObj_Unadvise\r\n");

   if (lpServerDoc->m_lpOleAdviseHldr == NULL) {
      sc = E_FAIL;
      goto error;
   }

   OLEDBG_BEGIN2("IOleAdviseHolder::Unadvise called\r\n")

hrErr = lpServerDoc->m_lpOleAdviseHldr->lpVtbl->Unadvise(
         lpServerDoc->m_lpOleAdviseHldr,
         dwConnection
   );
   OLEDBG_END2

   OLEDBG_END2

   // release artificial AddRef
   SvrDoc_OleObj_Release(lpThis);
   return hrErr;

error:
   OLEDBG_END2

   // release artificial AddRef
   SvrDoc_OleObj_Release(lpThis);
   return sc;
}

// IOleObject::EnumAdvise method

STDMETHODIMP SvrDoc_OleObj_EnumAdvise(
      LPOLEOBJECT             lpThis,
      LPENUMSTATDATA FAR*     lplpenumAdvise
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;
   HRESULT hrErr;
   HRESULT   sc;

   // artificial AddRef in case object is destroyed during call
   SvrDoc_OleObj_AddRef(lpThis);

   OLEDBG_BEGIN2("SvrDoc_OleObj_EnumAdvise\r\n");

   /* NOTE: we must make sure to set all out parameters to NULL. */
   *lplpenumAdvise = NULL;

   if (lpServerDoc->m_lpOleAdviseHldr == NULL) {
      sc = E_FAIL;
      goto error;
   }

   OLEDBG_BEGIN2("IOleAdviseHolder::EnumAdvise called\r\n")
   hrErr = lpServerDoc->m_lpOleAdviseHldr->lpVtbl->EnumAdvise(
         lpServerDoc->m_lpOleAdviseHldr,
         lplpenumAdvise
   );
   OLEDBG_END2

   OLEDBG_END2

   // release artificial AddRef
   SvrDoc_OleObj_Release(lpThis);
   return hrErr;

error:
   OLEDBG_END2

   // release artificial AddRef
   SvrDoc_OleObj_Release(lpThis);
   return sc;
}

// IOleObject::GetMiscStatus method

STDMETHODIMP SvrDoc_OleObj_GetMiscStatus(
      LPOLEOBJECT             lpThis,
      DWORD                   dwAspect,
      DWORD FAR*              lpdwStatus
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;

   // artificial AddRef in case object is destroyed during call
   SvrDoc_OleObj_AddRef(lpThis);

   OleDbgOut2("SvrDoc_OleObj_GetMiscStatus\r\n");

   /* Get our default MiscStatus for the given Aspect. this
   **    information is registered in the RegDB. We query the RegDB
   **    here to guarantee that the value returned from this method
   **    agrees with the values in RegDB. in this way we only have to
   **    maintain the info in one place (in the RegDB). Alternatively
   **    we could have the values hard coded here.
   */
   OleRegGetMiscStatus((REFCLSID)&CLSID_APP, dwAspect, lpdwStatus);

   /* NOTE: check if the data copied is compatible to be
   **    linked by an OLE 1.0 container. it is compatible if
   **    either the data is an untitled document, a file, or a
   **    selection of data within a file. if the data is part of
   **    an embedded object, then it is NOT compatible to be
   **    linked by an OLE 1.0 container. if it is compatible then
   **    we must include OLEMISC_CANLINKBYOLE1 as part of the
   **    dwStatus flags transfered via CF_OBJECTDESCRIPTOR or
   **    CF_LINKSRCDESCRIPTOR.
   */
   if (lpOutlineDoc->m_docInitType == DOCTYPE_NEW ||
      lpOutlineDoc->m_docInitType == DOCTYPE_FROMFILE)
      *lpdwStatus |= OLEMISC_CANLINKBYOLE1;

#if defined( INPLACE_SVR )
   if (dwAspect == DVASPECT_CONTENT)
      *lpdwStatus |= (OLEMISC_INSIDEOUT | OLEMISC_ACTIVATEWHENVISIBLE);
#endif  // INPLACE_SVR

   // release artificial AddRef
   SvrDoc_OleObj_Release(lpThis);
   return NOERROR;
}

// IOleObject::SetColorScheme method

STDMETHODIMP SvrDoc_OleObj_SetColorScheme(
      LPOLEOBJECT             lpThis,
      LPLOGPALETTE            lpLogpal
)
{
   OleDbgOut2("SvrDoc_OleObj_SetColorScheme\r\n");

   // REVIEW: NOT YET IMPLEMENTED

   return E_NOTIMPL;
}

/*************************************************************************
** ServerDoc::IPersistStorage interface implementation
*************************************************************************/

// IPersistStorage::QueryInterface method

STDMETHODIMP SvrDoc_PStg_QueryInterface(
      LPPERSISTSTORAGE        lpThis,
      REFIID                  riid,
      LPVOID FAR*             lplpvObj
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocPersistStorageImpl FAR*)lpThis)->lpServerDoc;

   return OleDoc_QueryInterface((LPOLEDOC)lpServerDoc, riid, lplpvObj);
}

// IPersistStorage::AddRef method

STDMETHODIMP_(ULONG) SvrDoc_PStg_AddRef(LPPERSISTSTORAGE lpThis)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocPersistStorageImpl FAR*)lpThis)->lpServerDoc;

   OleDbgAddRefMethod(lpThis, "IPersistStorage");

   return OleDoc_AddRef((LPOLEDOC)lpServerDoc);
}

// IPersistStorage::Release method

STDMETHODIMP_(ULONG) SvrDoc_PStg_Release(LPPERSISTSTORAGE lpThis)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocPersistStorageImpl FAR*)lpThis)->lpServerDoc;

   OleDbgReleaseMethod(lpThis, "IPersistStorage");

   return OleDoc_Release((LPOLEDOC)lpServerDoc);
}

// IPersistStorage::GetClassID method

STDMETHODIMP SvrDoc_PStg_GetClassID(
      LPPERSISTSTORAGE        lpThis,
      LPCLSID                 lpClassID
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocPersistStorageImpl FAR*)lpThis)->lpServerDoc;
   OleDbgOut2("SvrDoc_PStg_GetClassID\r\n");

   /* NOTE: we must be carefull to return the correct CLSID here.
   **    if we are currently preforming a "TreatAs (aka. ActivateAs)"
   **    operation then we need to return the class of the object
   **    written in the storage of the object. otherwise we would
   **    return our own class id.
   */
   return ServerDoc_GetClassID(lpServerDoc, lpClassID);
}

// IPersistStorage::IsDirty method

STDMETHODIMP  SvrDoc_PStg_IsDirty(LPPERSISTSTORAGE  lpThis)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocPersistStorageImpl FAR*)lpThis)->lpServerDoc;
   OleDbgOut2("SvrDoc_PStg_IsDirty\r\n");

   if (OutlineDoc_IsModified((LPOUTLINEDOC)lpServerDoc))
      return NOERROR;
   else
      return S_FALSE;
}

// IPersistStorage::InitNew method

STDMETHODIMP SvrDoc_PStg_InitNew(
      LPPERSISTSTORAGE        lpThis,
      LPSTORAGE               lpStg
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocPersistStorageImpl FAR*)lpThis)->lpServerDoc;
   LPOLEDOC lpOleDoc = (LPOLEDOC)lpServerDoc;
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPOLESTR    lpszUserType = /*(LPOLESTR)*/FULLUSERTYPENAME;
   HRESULT hrErr;
   HRESULT sc;

   OLEDBG_BEGIN2("SvrDoc_PStg_InitNew\r\n")

#if defined( SVR_TREATAS )
   {
      LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
      CLSID       clsid;
      CLIPFORMAT  cfFmt;
      LPOLESTR       lpszType;

      /* NOTE: if the Server is capable of supporting "TreatAs"
      **    (aka. ActivateAs), it must read the class that is written
      **    into the storage. if this class is NOT the app's own
      **    class ID, then this is a TreatAs operation. the server
      **    then must faithfully pretend to be the class that is
      **    written into the storage. it must also faithfully write
      **    the data back to the storage in the SAME format as is
      **    written in the storage.
      **
      **    SVROUTL and ISVROTL can emulate each other. they have the
      **    simplification that they both read/write the identical
      **    format. thus for these apps no actual conversion of the
      **    native bits is actually required.
      */
      lpServerDoc->m_clsidTreatAs = CLSID_NULL;
      if (OleStdGetTreatAsFmtUserType(&CLSID_APP, lpStg, &clsid,
                     (CLIPFORMAT FAR*)&cfFmt, /*(LPOLESTR FAR*)*/&lpszType)) {

         if (cfFmt == lpOutlineApp->m_cfOutline) {
            // We should perform TreatAs operation
            if (lpServerDoc->m_lpszTreatAsType)
               OleStdFreeString(lpServerDoc->m_lpszTreatAsType, NULL);

            lpServerDoc->m_clsidTreatAs = clsid;
            ((LPOUTLINEDOC)lpServerDoc)->m_cfSaveFormat = cfFmt;
            lpServerDoc->m_lpszTreatAsType = lpszType;
            lpszUserType = lpServerDoc->m_lpszTreatAsType;

            OleDbgOut3("SvrDoc_PStg_InitNew: TreateAs ==> '");
//            OleDbgOutNoPrefix3(lpServerDoc->m_lpszTreatAsType);
            OleDbgOutNoPrefix3("'\r\n");
         } else {
            // ERROR: we ONLY support TreatAs for CF_OUTLINE format
            OleDbgOut("SvrDoc_PStg_InitNew: INVALID TreatAs Format\r\n");
            OleStdFreeString(lpszType, NULL);
         }
      }
   }
#endif  // SVR_TREATAS

   /* NOTE: a server EXE object should write its format tag to its
   **    storage in InitNew so that the DefHandler can know the format
   **    of the object. this is particularly important if the objects
   **    uses CF_METATFILE or CF_DIB as its format. the DefHandler
   **    automatically avoids separately storing presentation cache
   **    data when the object's native data is a standard presentation
   **    format.
   */
   WriteFmtUserTypeStg(lpStg,(CLIPFORMAT)lpOutlineApp->m_cfOutline,lpszUserType);

   // set the doc to a new embedded object.
   if (! ServerDoc_InitNewEmbed(lpServerDoc)) {
      sc = E_FAIL;
      goto error;
   }

   /* NOTE: An embedded object must guarantee that it can save
   **    even in low memory situations. it must be able to
   **    successfully save itself without consuming any additional
   **    memory. this means that a server is NOT supposed to open or
   **    create any streams or storages when
   **    IPersistStorage::Save(fSameAsLoad==TRUE) is called. thus an
   **    embedded object should hold onto its storage and pre-open and
   **    hold open any streams that it will need later when it is time
   **    to save.
   */
   hrErr = lpStg->lpVtbl->CreateStream(
         lpStg,
         OLESTR("LineList"),
         STGM_WRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE,
         0,
         0,
         &lpOleDoc->m_lpLLStm
   );

   if (hrErr != NOERROR) {
      OleDbgAssertSz(hrErr==NOERROR,"Could not create LineList stream");
      OleDbgOutHResult("LineList CreateStream returned", hrErr);
      sc = hrErr;
      goto error;
   }

   hrErr = lpStg->lpVtbl->CreateStream(
         lpStg,
         OLESTR("NameTable"),
         STGM_WRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE,
         0,
         0,
         &lpOleDoc->m_lpNTStm
   );

   if (hrErr != NOERROR) {
      OleDbgAssertSz(hrErr==NOERROR,"Could not create NameTable stream");
      OleDbgOutHResult("NameTable CreateStream returned", hrErr);
      sc = hrErr;
      goto error;
   }

   lpOleDoc->m_lpStg = lpStg;

   // NOTE: to be able to hold onto IStorage* pointer, we must AddRef it
   lpStg->lpVtbl->AddRef(lpStg);

   OLEDBG_END2

   return NOERROR;

error:
   OLEDBG_END2

   return sc;
}

// IPersistStorage::Load method

STDMETHODIMP SvrDoc_PStg_Load(
      LPPERSISTSTORAGE        lpThis,
      LPSTORAGE               lpStg
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocPersistStorageImpl FAR*)lpThis)->lpServerDoc;
   LPOLEDOC lpOleDoc = (LPOLEDOC)lpServerDoc;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   HRESULT sc;
   HRESULT hrErr;

   OLEDBG_BEGIN2("SvrDoc_PStg_Load\r\n")

   if (OutlineDoc_LoadFromStg((LPOUTLINEDOC)lpServerDoc, lpStg)) {

      ((LPOUTLINEDOC)lpServerDoc)->m_docInitType = DOCTYPE_EMBEDDED;

      /* NOTE: we need to check if the ConvertStg bit is on. if
      **    so, we need to clear the ConvertStg bit and mark the
      **    document as dirty so as to force a save when the document
      **    is closed. the actual conversion of the bits should be
      **    performed when the data is loaded from the IStorage*. in
      **    our case any conversion of data formats would be done in
      **    OutlineDoc_LoadFromStg function. in reality both SVROUTL
      **    and ISVROTL read and write the same format so no actual
      **    conversion of data bits is necessary.
      */
      if (GetConvertStg(lpStg) == NOERROR) {
         SetConvertStg(lpStg, FALSE);

         OleDbgOut3("SvrDoc_PStg_Load: ConvertStg==TRUE\r\n");
         OutlineDoc_SetModified(lpOutlineDoc, TRUE, FALSE, FALSE);
      }

   } else {
      sc = E_FAIL;
      goto error;
   }

   /* NOTE: An embedded object must guarantee that it can save
   **    even in low memory situations. it must be able to
   **    successfully save itself without consuming any additional
   **    memory. this means that a server is NOT supposed to open or
   **    create any streams or storages when
   **    IPersistStorage::Save(fSameAsLoad==TRUE) is called. thus an
   **    embedded object should hold onto its storage and pre-open and
   **    hold open any streams that it will need later when it is time
   **    to save.
   */
   if (lpOleDoc->m_lpLLStm)
      OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpLLStm);
   hrErr = lpStg->lpVtbl->OpenStream(
         lpStg,
         OLESTR("LineList"),
         NULL,
         STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
         0,
         &lpOleDoc->m_lpLLStm
   );

   if (hrErr != NOERROR) {
      OleDbgAssertSz(hrErr==NOERROR,"Could not create LineList stream");
      OleDbgOutHResult("LineList CreateStream returned", hrErr);
      sc = hrErr;
      goto error;
   }

   if (lpOleDoc->m_lpNTStm)
      OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpNTStm);
   hrErr = lpStg->lpVtbl->OpenStream(
         lpStg,
         OLESTR("NameTable"),
         NULL,
         STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
         0,
         &lpOleDoc->m_lpNTStm
   );

   if (hrErr != NOERROR) {
      OleDbgAssertSz(hrErr==NOERROR,"Could not create NameTable stream");
      OleDbgOutHResult("NameTable CreateStream returned", hrErr);
      sc = hrErr;
      goto error;
   }

   lpOleDoc->m_lpStg = lpStg;

   // NOTE: to be able to hold onto IStorage* pointer, we must AddRef it
   lpStg->lpVtbl->AddRef(lpStg);

   OLEDBG_END2
   return NOERROR;

error:
   OLEDBG_END2
   return sc;
}

// IPersistStorage::Save method

STDMETHODIMP SvrDoc_PStg_Save(
      LPPERSISTSTORAGE        lpThis,
      LPSTORAGE               lpStg,
      BOOL                    fSameAsLoad
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocPersistStorageImpl FAR*)lpThis)->lpServerDoc;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   BOOL fStatus;
   HRESULT sc;

   OLEDBG_BEGIN2("SvrDoc_PStg_Save\r\n")

   fStatus = OutlineDoc_SaveSelToStg(
         (LPOUTLINEDOC)lpServerDoc,
         NULL,
         lpOutlineDoc->m_cfSaveFormat,
         lpStg,
         fSameAsLoad,
         FALSE
   );

   if (! fStatus) {
      OutlineApp_ErrorMessage(g_lpApp, ErrMsgPSSaveFail);
      sc = E_FAIL;
      goto error;
   }

   lpServerDoc->m_fSaveWithSameAsLoad = fSameAsLoad;
   lpServerDoc->m_fNoScribbleMode = TRUE;

   OLEDBG_END2
   return NOERROR;

error:
   OLEDBG_END2
   return sc;
}

// IPersistStorage::SaveCompleted method

STDMETHODIMP SvrDoc_PStg_SaveCompleted(
      LPPERSISTSTORAGE        lpThis,
      LPSTORAGE               lpStgNew
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocPersistStorageImpl FAR*)lpThis)->lpServerDoc;
   LPOLEDOC lpOleDoc = (LPOLEDOC)lpServerDoc;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   HRESULT hrErr;

   OLEDBG_BEGIN2("SvrDoc_PStg_SaveCompleted\r\n")

   /* NOTE: this sample application is a pure server application.
   **    a container/server application would have to call SaveCompleted
   **    for each of its contained compound document objects. if a new
   **    storage was given, then the container/server would have to
   **    open the corresponding new sub-storage for each compound
   **    document object and pass as an argument in the SaveCompleted
   **    call.
   */

   /* NOTE: it is only legal to perform a Save or SaveAs operation
   **    on an embedded object. if the document is a file-based document
   **    then we can not be changed to a IStorage-base object.
   **
   **      fSameAsLoad   lpStgNew     Type of Save     Send OnSave
   **    ---------------------------------------------------------
   **         TRUE        NULL        SAVE             YES
   **         TRUE        ! NULL      SAVE *           YES
   **         FALSE       ! NULL      SAVE AS          YES
   **         FALSE       NULL        SAVE COPY AS     NO
   **
   **    * this is a strange case that is possible. it is inefficient
   **    for the caller; it would be better to pass lpStgNew==NULL for
   **    the Save operation.
   */
   if ( ((lpServerDoc->m_fSaveWithSameAsLoad && lpStgNew==NULL) || lpStgNew)
         && (lpOutlineDoc->m_docInitType != DOCTYPE_EMBEDDED) ) {
      OLEDBG_END2
      return E_INVALIDARG;
   }

   /* NOTE: inform any linking clients that the document has been
   **    saved. in addition, any currently active pseudo objects
   **    should also inform their clients. we should only broadcast an
   **    OnSave notification if a Save or SaveAs operation was
   **    performed. we do NOT want to send the notification if a
   **    SaveCopyAs operation was performed.
   */
   if (lpStgNew || lpServerDoc->m_fSaveWithSameAsLoad) {

      /* NOTE: if IPersistStorage::Save has been called, then we
      **    need to clear the dirty bit and send OnSave notification.
      **    if HandsOffStorage is called directly without first
      **    calling Save, then we do NOT want to clear the dirty bit
      **    and send OnSave when SaveCompleted is called.
      */
      if (lpServerDoc->m_fNoScribbleMode) {
         OutlineDoc_SetModified(lpOutlineDoc, FALSE, FALSE, FALSE);

         ServerDoc_SendAdvise (
               lpServerDoc,
               OLE_ONSAVE,
               NULL,   /* lpmkDoc -- not relevant here */
               0       /* advf -- not relevant here */
         );
      }
      lpServerDoc->m_fSaveWithSameAsLoad = FALSE;
   }
   lpServerDoc->m_fNoScribbleMode = FALSE;

   /* NOTE: An embedded object must guarantee that it can save
   **    even in low memory situations. it must be able to
   **    successfully save itself without consuming any additional
   **    memory. this means that a server is NOT supposed to open or
   **    create any streams or storages when
   **    IPersistStorage::Save(fSameAsLoad==TRUE) is called. thus an
   **    embedded object should hold onto its storage and pre-open and
   **    hold open any streams that it will need later when it is time
   **    to save. if this is a SaveAs situtation, then we want to
   **    pre-open and hold open our streams to guarantee that a
   **    subsequent save will be successful in low-memory. if we fail
   **    to open these streams then we want to force ourself to close
   **    to make sure the can't make editing changes that can't be
   **    later saved.
   */
   if ( lpStgNew && !lpServerDoc->m_fSaveWithSameAsLoad ) {

      // release previous streams
      if (lpOleDoc->m_lpLLStm) {
         OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpLLStm);
         lpOleDoc->m_lpLLStm = NULL;
      }
      if (lpOleDoc->m_lpNTStm) {
         OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpNTStm);
         lpOleDoc->m_lpNTStm = NULL;
      }
      if (lpOleDoc->m_lpStg) {
         OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpStg);
         lpOleDoc->m_lpStg = NULL;
      }

      hrErr = lpStgNew->lpVtbl->OpenStream(
            lpStgNew,
            OLESTR("LineList"),
            NULL,
            STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
            0,
            &lpOleDoc->m_lpLLStm
      );

      if (hrErr != NOERROR) {
         OleDbgAssertSz(hrErr==NOERROR,"Could not create LineList stream");
         OleDbgOutHResult("LineList CreateStream returned", hrErr);
         goto error;
      }

      hrErr = lpStgNew->lpVtbl->OpenStream(
            lpStgNew,
            OLESTR("NameTable"),
            NULL,
            STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
            0,
            &lpOleDoc->m_lpNTStm
      );

      if (hrErr != NOERROR) {
         OleDbgAssertSz(hrErr==NOERROR,"Could not create NameTable stream");
         OleDbgOutHResult("NameTable CreateStream returned", hrErr);
         goto error;
      }

      lpOleDoc->m_lpStg = lpStgNew;

      // NOTE: to hold onto IStorage* pointer, we must AddRef it
      lpStgNew->lpVtbl->AddRef(lpStgNew);
   }

   OLEDBG_END2
   return NOERROR;

error:
   OLEDBG_END2
   return E_OUTOFMEMORY;
}

// IPersistStorage::HandsOffStorage method

STDMETHODIMP SvrDoc_PStg_HandsOffStorage(LPPERSISTSTORAGE lpThis)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocPersistStorageImpl FAR*)lpThis)->lpServerDoc;
   LPOLEDOC lpOleDoc = (LPOLEDOC)lpServerDoc;

   OLEDBG_BEGIN2("SvrDoc_PStg_HandsOffStorage\r\n")

   /* NOTE: An embedded object must guarantee that it can save
   **    even in low memory situations. it must be able to
   **    successfully save itself without consuming any additional
   **    memory. this means that a server is NOT supposed to open or
   **    create any streams or storages when
   **    IPersistStorage::Save(fSameAsLoad==TRUE) is called. thus an
   **    embedded object should hold onto its storage and pre-open and
   **    hold open any streams that it will need later when it is time
   **    to save. Now when HandsOffStorage is called the object must
   **    release its storage and any streams that is holds open.
   **    later when SaveCompleted is called, it will be given back its
   **    storage.
   */
   if (lpOleDoc->m_lpLLStm) {
      OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpLLStm);
      lpOleDoc->m_lpLLStm = NULL;
   }
   if (lpOleDoc->m_lpNTStm) {
      OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpNTStm);
      lpOleDoc->m_lpNTStm = NULL;
   }
   if (lpOleDoc->m_lpStg) {
      OleStdRelease((LPUNKNOWN)lpOleDoc->m_lpStg);
      lpOleDoc->m_lpStg = NULL;
   }

   OLEDBG_END2
   return NOERROR;
}

#if defined( SVR_TREATAS )

/*************************************************************************
** ServerDoc::IStdMarshalInfo interface implementation
*************************************************************************/

// IStdMarshalInfo::QueryInterface method

STDMETHODIMP SvrDoc_StdMshl_QueryInterface(
      LPSTDMARSHALINFO        lpThis,
      REFIID                  riid,
      LPVOID FAR*             lplpvObj
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocStdMarshalInfoImpl FAR*)lpThis)->lpServerDoc;

   return OleDoc_QueryInterface((LPOLEDOC)lpServerDoc, riid, lplpvObj);
}

// IStdMarshalInfo::AddRef method

STDMETHODIMP_(ULONG) SvrDoc_StdMshl_AddRef(LPSTDMARSHALINFO lpThis)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocStdMarshalInfoImpl FAR*)lpThis)->lpServerDoc;

   OleDbgAddRefMethod(lpThis, "IStdMarshalInfo");

   return OleDoc_AddRef((LPOLEDOC)lpServerDoc);
}

// IStdMarshalInfo::Release method

STDMETHODIMP_(ULONG) SvrDoc_StdMshl_Release(LPSTDMARSHALINFO lpThis)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocStdMarshalInfoImpl FAR*)lpThis)->lpServerDoc;

   OleDbgReleaseMethod(lpThis, "IStdMarshalInfo");

   return OleDoc_Release((LPOLEDOC)lpServerDoc);
}

// IStdMarshalInfo::GetClassForHandler

STDMETHODIMP SvrDoc_StdMshl_GetClassForHandler(
      LPSTDMARSHALINFO        lpThis,
      DWORD                   dwDestContext,
      LPVOID                  pvDestContext,
      LPCLSID                 lpClassID
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocStdMarshalInfoImpl FAR*)lpThis)->lpServerDoc;
   OleDbgOut2("SvrDoc_StdMshl_GetClassForHandler\r\n");

   // NOTE: we only handle LOCAL marshal context.
   if (dwDestContext != MSHCTX_LOCAL || pvDestContext != NULL)
      return E_INVALIDARG;

   /* NOTE: we must return our REAL clsid, NOT the clsid that we
   **    are pretending to be if a "TreatAs" is in effect.
   */
   *lpClassID = CLSID_APP;
   return NOERROR;
}
#endif  // SVR_TREATAS

/*************************************************************************
** ServerDoc Support Functions
*************************************************************************/

/* ServerDoc_Init
 * --------------
 *
 *  Initialize the fields of a new ServerDoc object. The object is initially
 *  not associated with a file or an (Untitled) document. This function sets
 *  the docInitType to DOCTYPE_UNKNOWN. After calling this function the
 *  caller should call:
 *      1.) OutlineDoc_InitNewFile to set the ServerDoc to (Untitled)
 *      2.) OutlineDoc_LoadFromFile to associate the ServerDoc with a file.
 *  This function creates a new window for the document.
 *
 *  NOTE: the window is initially created with a NIL size. it must be
 *        sized and positioned by the caller. also the document is initially
 *        created invisible. the caller must call OutlineDoc_ShowWindow
 *        after sizing it to make the document window visible.
 */
BOOL ServerDoc_Init(LPSERVERDOC lpServerDoc, BOOL fDataTransferDoc)
{
   lpServerDoc->m_cPseudoObj                   = 0;
   lpServerDoc->m_lpOleClientSite              = NULL;
   lpServerDoc->m_lpOleAdviseHldr              = NULL;
   lpServerDoc->m_lpDataAdviseHldr             = NULL;

   // initialy doc does not have any storage
   lpServerDoc->m_fNoScribbleMode              = FALSE;
   lpServerDoc->m_fSaveWithSameAsLoad          = FALSE;
   lpServerDoc->m_szContainerApp[0]            = '\0';
   lpServerDoc->m_szContainerObj[0]            = '\0';
   lpServerDoc->m_nNextRangeNo                 = 0L;
   lpServerDoc->m_lrSrcSelOfCopy.m_nStartLine  = -1;
   lpServerDoc->m_lrSrcSelOfCopy.m_nEndLine    = -1;
   lpServerDoc->m_fDataChanged                 = FALSE;
   lpServerDoc->m_fSizeChanged                 = FALSE;
   lpServerDoc->m_fSendDataOnStop              = FALSE;

#if defined( SVR_TREATAS )
   lpServerDoc->m_clsidTreatAs                 = CLSID_NULL;
   lpServerDoc->m_lpszTreatAsType              = NULL;
#endif  // SVR_TREATAS

#if defined( INPLACE_SVR )
   lpServerDoc->m_hWndHatch                    =
         CreateHatchWindow(
               OutlineApp_GetWindow(g_lpApp),
               OutlineApp_GetInstance(g_lpApp)
         );
   if (!lpServerDoc->m_hWndHatch)
      return FALSE;

   lpServerDoc->m_fInPlaceActive               = FALSE;
   lpServerDoc->m_fInPlaceVisible              = FALSE;
   lpServerDoc->m_fUIActive                    = FALSE;
   lpServerDoc->m_lpIPData                     = NULL;
   lpServerDoc->m_fMenuHelpMode                = FALSE; // F1 pressed in menu

   INIT_INTERFACEIMPL(
         &lpServerDoc->m_OleInPlaceObject,
         &g_SvrDoc_OleInPlaceObjectVtbl,
         lpServerDoc
   );
   INIT_INTERFACEIMPL(
         &lpServerDoc->m_OleInPlaceActiveObject,
         &g_SvrDoc_OleInPlaceActiveObjectVtbl,
         lpServerDoc
   );
#endif // INPLACE_SVR

   INIT_INTERFACEIMPL(
         &lpServerDoc->m_OleObject,
         &g_SvrDoc_OleObjectVtbl,
         lpServerDoc
   );

   INIT_INTERFACEIMPL(
         &lpServerDoc->m_PersistStorage,
         &g_SvrDoc_PersistStorageVtbl,
         lpServerDoc
   );

#if defined( SVR_TREATAS )

   INIT_INTERFACEIMPL(
         &lpServerDoc->m_StdMarshalInfo,
         &g_SvrDoc_StdMarshalInfoVtbl,
         lpServerDoc
   );
#endif  // SVR_TREATAS
   return TRUE;
}

/* ServerDoc_InitNewEmbed
 * ----------------------
 *
 *  Initialize the ServerDoc object to be a new embedded object document.
 *  This function sets the docInitType to DOCTYPE_EMBED.
 */
BOOL ServerDoc_InitNewEmbed(LPSERVERDOC lpServerDoc)
{
   char szAnsiStr[256];
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;

   OleDbgAssert(lpOutlineDoc->m_docInitType == DOCTYPE_UNKNOWN);

   lpOutlineDoc->m_docInitType = DOCTYPE_EMBEDDED;

   /* The Window title for an embedded object is constructed as
   **    follows:
   **      <server app name> - <obj short type> in <cont. doc name>
   **
   **    here we construct the current document title portion of the
   **    name which follows the '-'. OutlineDoc_SetTitle prepends the
   **    "<server app name> - " to the document title.
   */
   // REVIEW: this string should be loaded from string resource
   wsprintf(szAnsiStr, "%s in %s",
      (LPSTR)SHORTUSERTYPENAME,
      (LPSTR)DEFCONTAINERNAME);
   A2W (szAnsiStr, lpOutlineDoc->m_szFileName, OLEUI_CCHPATHMAX);
   lpOutlineDoc->m_lpszDocTitle = lpOutlineDoc->m_szFileName;

   /* NOTE: an embedding should be marked as initially dirty so
   **    that on close we always call IOleClientSite::SaveObject.
   */
   OutlineDoc_SetModified(lpOutlineDoc, TRUE, FALSE, FALSE);

   OutlineDoc_SetTitle(lpOutlineDoc, FALSE /*fMakeUpperCase*/);

   return TRUE;
}

/* ServerDoc_SendAdvise
 * --------------------

*
 * This function sends an advise notification on behalf of a specific
 *  doc object to all its clients.
 */
void ServerDoc_SendAdvise(
      LPSERVERDOC     lpServerDoc,
      WORD            wAdvise,
      LPMONIKER       lpmkDoc,
      DWORD           dwAdvf
)
{
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   LPOLEDOC lpOleDoc = (LPOLEDOC)lpServerDoc;

   switch (wAdvise) {

      case OLE_ONDATACHANGE:

         // inform clients that the data of the object has changed

         if (lpOutlineDoc->m_nDisableDraw == 0) {
            /* drawing is currently enabled. inform clients that
            **    the data of the object has changed
            */

            lpServerDoc->m_fDataChanged = FALSE;

            /* NOTE: we must note the time of last change
            **    for our object in the RunningObjectTable.
            **    this is used as the basis to answer
            **    IOleObject::IsUpToDate. we only want to note
            **    the change time when an actual change takes
            **    place. we do NOT want to set it when we are
            **    notifying clients of ADVF_DATAONSTOP
            */
            if (dwAdvf == 0)
               OleStdNoteObjectChangeTime(lpOleDoc->m_dwRegROT);

            if (lpServerDoc->m_lpDataAdviseHldr) {
               OLEDBG_BEGIN2("IDataAdviseHolder::SendOnDataChange called\r\n");
               lpServerDoc->m_lpDataAdviseHldr->lpVtbl->SendOnDataChange(
                     lpServerDoc->m_lpDataAdviseHldr,
                     (LPDATAOBJECT)&lpOleDoc->m_DataObject,
                     0,
                     dwAdvf
               );
               OLEDBG_END2

            }

#if defined( INPLACE_SVR )
            /* NOTE: if the ServerDoc is currently in-place UI active,
            **    then is it important to renegotiate the size for the
            **    in-place document window BEFORE sending OnDataChange
            **    (which will cause the window to repaint).
            */
            if (lpServerDoc->m_fSizeChanged) {
               lpServerDoc->m_fSizeChanged = FALSE;
               if (lpServerDoc->m_fInPlaceActive)
                  ServerDoc_UpdateInPlaceWindowOnExtentChange(lpServerDoc);
            }
#endif

            /* NOTE: we do NOT need to tell our pseudo objects to
            **    broadcast OnDataChange notification because
            **    they will do it automatically when an editing
            **    change in the document affects a PseudoObj.
            **    (see OutlineNameTable_AddLineUpdate,
            **         OutlineNameTable_DeleteLineUpdate,
            **    and  ServerNameTable_EditLineUpdate)
            */

         } else {
            /* drawing is currently disabled. do not send
            **    notifications or call
            **    IOleInPlaceObject::OnPosRectChange until drawing
            **    is re-enabled.
            */
         }
         break;

      case OLE_ONCLOSE:

         // inform clients that the document is shutting down

         if (lpServerDoc->m_lpOleAdviseHldr) {
            OLEDBG_BEGIN2("IOleAdviseHolder::SendOnClose called\r\n");
            lpServerDoc->m_lpOleAdviseHldr->lpVtbl->SendOnClose(
                  lpServerDoc->m_lpOleAdviseHldr
            );
            OLEDBG_END2
         }

         /* NOTE: we do NOT need to tell our pseudo objects to
         **    broadcast OnClose notification because they will do
         **    it automatically when the pseudo object is closed.
         **    (see PseudoObj_Close)
         */

         break;

      case OLE_ONSAVE:

         // inform clients that the object has been saved

         OLEDBG_BEGIN3("ServerDoc_SendAdvise ONSAVE\r\n");

         if (lpServerDoc->m_lpOleAdviseHldr) {
            OLEDBG_BEGIN2("IOleAdviseHolder::SendOnSave called\r\n");
            lpServerDoc->m_lpOleAdviseHldr->lpVtbl->SendOnSave(
                  lpServerDoc->m_lpOleAdviseHldr
            );
            OLEDBG_END2
         }

         /* NOTE: inform any clients of pseudo objects
         **    within our document, that our document has been
         **    saved.
         */
         ServerNameTable_InformAllPseudoObjectsDocSaved(
               (LPSERVERNAMETABLE)lpOutlineDoc->m_lpNameTable,
               lpmkDoc
         );
         OLEDBG_END3
         break;

      case OLE_ONRENAME:

         // inform clients that the object's name has changed

         OLEDBG_BEGIN3("ServerDoc_SendAdvise ONRENAME\r\n");

         if (lpmkDoc && lpServerDoc->m_lpOleAdviseHldr) {
            OLEDBG_BEGIN2("IOleAdviseHolder::SendOnRename called\r\n");
            lpServerDoc->m_lpOleAdviseHldr->lpVtbl->SendOnRename(
                  lpServerDoc->m_lpOleAdviseHldr,
                  lpmkDoc
            );
            OLEDBG_END2
         }

         OLEDBG_END3
         break;
   }
}

/* ServerDoc_GetClassID
** --------------------
**    Return the class ID corresponding to the bits in the storage.
**    normally this will be our application's given CLSID. but if a
**    "TreateAs (aka. ActivateAs)" operation is taking place, then our
**    application needs to pretend to be the class of the object that
**    we are emulating. this is also the class that will be written
**    into the storage.
*/
HRESULT ServerDoc_GetClassID(LPSERVERDOC lpServerDoc, LPCLSID lpclsid)
{
#if defined( SVR_TREATAS )
   if (! IsEqualCLSID(&lpServerDoc->m_clsidTreatAs, &CLSID_NULL))
      *lpclsid = lpServerDoc->m_clsidTreatAs;
   else
#endif  // SVR_TREATAS
      *lpclsid = CLSID_APP;

   return NOERROR;
}

/* ServerDoc_UpdateMenu
 * --------------------
 *
 *  Update menu for embedding mode. the changes include:
 *      1 Remove File/New and File/Open (SDI ONLY)
 *      2 Change File/Save As.. to File/Save Copy As..
 *      3 Change File menu so it contains "Update" instead of "Save"
 *      4 Change File/Exit to File/Exit & Return to <client doc>"
 */
void ServerDoc_UpdateMenu(LPSERVERDOC lpServerDoc)
{
   char    str[256];
   HWND    hWndMain;
   HMENU   hMenu;
   char    szAnsiStr[256];

   OleDbgOut2("ServerDoc_UpdateMenu\r\n");

   hWndMain=g_lpApp->m_hWndApp;
   hMenu=GetMenu(hWndMain);

#if defined( SDI_VERSION )
   /* SDI ONLY: Remove File/New and File/Open */
   DeleteMenu(hMenu, IDM_F_NEW, MF_BYCOMMAND);
   DeleteMenu(hMenu, IDM_F_OPEN, MF_BYCOMMAND);
#endif

   // Change File.Save As.. to File.Save Copy As.. */
   ModifyMenu(hMenu,IDM_F_SAVEAS, MF_STRING, IDM_F_SAVEAS, "Save Copy As..");

   // Change File.Save to "&Update <container doc>"
   W2A (lpServerDoc->m_szContainerObj, szAnsiStr, 256);
   wsprintf(str, g_szUpdateCntrDoc, szAnsiStr);
   ModifyMenu(hMenu, IDM_F_SAVE, MF_STRING, IDM_F_SAVE, str);

   // Change File/Exit to File/Exit & Return to <container doc>" */
   W2A (lpServerDoc->m_szContainerObj, szAnsiStr, 256);
   wsprintf(str, g_szExitNReturnToCntrDoc, szAnsiStr);
   ModifyMenu(hMenu, IDM_F_EXIT, MF_STRING, IDM_F_EXIT, str);

   DrawMenuBar(hWndMain);
}

#if defined( MDI_VERSION )

// NOTE: ServerDoc_RestoreMenu is actually redundant because the
//          app is dying when the function is called.  (In SDI, the
//          app will terminate when the ref counter of the server doc
//          is zero). However, it is important for MDI.

/* ServerDoc_RestoreMenu
 * ---------------------
 *
 *      Reset the menu to non-embedding mode
 */
void ServerDoc_RestoreMenu(LPSERVERDOC lpServerDoc)
{
   LPOUTLINEAPP    lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   HWND            hWndMain;
   HMENU           hMenu;

   OleDbgOut2("ServerDoc_RestoreMenu\r\n");

   hWndMain = lpOutlineApp->m_hWndApp;
   hMenu = GetMenu(hWndMain);

   /* Add back File/New, File/Open.. and File/Save */
   InsertMenu(hMenu, IDM_F_SAVEAS, MF_BYCOMMAND | MF_ENABLED | MF_STRING,
      IDM_F_NEW, "&New");
   InsertMenu(hMenu, IDM_F_SAVEAS, MF_BYCOMMAND | MF_ENABLED | MF_STRING,
      IDM_F_OPEN, "&Open...");

   /* Change File menu so it contains "Save As..." instead of */
   /* "Save Copy As..." */
   ModifyMenu(hMenu, IDM_F_SAVEAS, MF_STRING, IDM_F_SAVEAS, "Save &As..");

   /* Change File menu so it contains "Save" instead of "Update" */
   ModifyMenu(hMenu, IDM_F_SAVE, MF_STRING, IDM_F_SAVE, "&Save");

   /* Change File menu so it contains "Exit" */
   /* instead of just "Exit & Return to <client doc>" */
   ModifyMenu(hMenu, IDM_F_EXIT, MF_STRING, IDM_F_EXIT, "E&xit");

   DrawMenuBar (hWndMain);

}

#endif  // MDI_VERSION


SVRINPL.C

/*************************************************************************
**
**    OLE 2 Server Sample Code
**
**    svrinpl.c
**
**    This file contains all interfaces, methods and related support
**    functions for an In-Place Object (Server) application (aka. Visual
**    Editing). The in-place Object application includes the following
**    implementation objects:
**
**    ServerDoc Object
**      exposed interfaces:
**          IOleInPlaceObject
**          IOleInPlaceActiveObject
**
**    ServerApp Object
**      exposed interfaces:
**          IUnknown
**
**    (c) Copyright Microsoft Corp. 1992 - 1997 All Rights Reserved
**
*************************************************************************/

#include "outline.h"

OLEDBGDATA

extern LPOUTLINEAPP             g_lpApp;

/* NOTE: the object should compose a string that is used by
**    in-place containers to be used for the window titles. this string
**    is passed to the container application via
**    IOleInPlaceUIWindow::SetActiveObject. the string should have the
**    following form:
**          <application name> - <object short type name>
**    SDI containers can use the string directly to display in the
**    frame window title. the container would concatenate the string
**    " in <container doc name>".
**    an MDI container with the MDI child window maximized can do the
**    same as the SDI container. an MDI container with the MDI child
**    windows NOT maximized can look for the " - " in the string from
**    the object. the first part of the string (app name) would be put
**    as the frame window title; the second part would be composed with
**    " in <container doc name>" and used as the MDI child window
**    title.
*/

// REVIEW: should use string resource for messages
OLECHAR g_szIPObjectTitle[] = APPNAME OLESTR(" - ") SHORTUSERTYPENAME;

extern RECT g_rectNull;

/*************************************************************************
** ServerDoc::IOleInPlaceObject interface implementation
*************************************************************************/

// IOleInPlaceObject::QueryInterface method

STDMETHODIMP SvrDoc_IPObj_QueryInterface(
      LPOLEINPLACEOBJECT  lpThis,
      REFIID              riid,
      LPVOID FAR *        lplpvObj
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;

   return OleDoc_QueryInterface((LPOLEDOC)lpServerDoc, riid, lplpvObj);
}

// IOleInPlaceObject::AddRef method

STDMETHODIMP_(ULONG) SvrDoc_IPObj_AddRef(LPOLEINPLACEOBJECT lpThis)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;

   OleDbgAddRefMethod(lpThis, "IOleInPlaceObject");

   return OleDoc_AddRef((LPOLEDOC)lpServerDoc);
}

// IOleInPlaceObject::Release method

STDMETHODIMP_(ULONG) SvrDoc_IPObj_Release(LPOLEINPLACEOBJECT lpThis)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;

   OleDbgReleaseMethod(lpThis, "IOleInPlaceObject");

   return OleDoc_Release((LPOLEDOC)lpServerDoc);
}

// IOleInPlaceObject::GetWindow method

STDMETHODIMP SvrDoc_IPObj_GetWindow(
      LPOLEINPLACEOBJECT  lpThis,
      HWND FAR*           lphwnd
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;

   OLEDBG_BEGIN2("SvrDoc_IPObj_GetWindow\r\n")

   *lphwnd = ((LPOUTLINEDOC)lpServerDoc)->m_hWndDoc;

   OLEDBG_END2
   return S_OK;
}

// IOleInPlaceObject::ContextSensitiveHelp method

STDMETHODIMP SvrDoc_IPObj_ContextSensitiveHelp(
      LPOLEINPLACEOBJECT  lpThis,
      BOOL                fEnable
)
{
   LPOLEDOC lpOleDoc =
         (LPOLEDOC)((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;
   OleDbgOut2("SvrDoc_IPObj_ContextSensitiveHelp\r\n");

   /* NOTE: see context sensitive help technote (CSHELP.DOC).
   **    This method is called when SHIFT-F1 context sensitive help is
   **    entered. the cursor should then change to a question mark
   **    cursor and the app should enter a modal state where the next
   **    mouse click does not perform its normal action but rather
   **    gives help corresponding to the location clicked. if the app
   **    does not implement a help system, it should at least eat the
   **    click and do nothing.
   */
   lpOleDoc->m_fCSHelpMode = fEnable;

   return S_OK;
}

// IOleInPlaceObject::InPlaceDeactivate method

STDMETHODIMP SvrDoc_IPObj_InPlaceDeactivate(LPOLEINPLACEOBJECT lpThis)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;
   HRESULT hrErr;

   // artificial AddRef in case object is deleted during call
   SvrDoc_IPObj_AddRef(lpThis);

   OLEDBG_BEGIN2("SvrDoc_IPObj_InPlaceDeactivate\r\n")

   hrErr = ServerDoc_DoInPlaceDeactivate(lpServerDoc);

   OLEDBG_END2

   // release artificial AddRef
   SvrDoc_IPObj_Release(lpThis);

   return hrErr;
}

// IOleInPlaceObject::UIDeactivate method

STDMETHODIMP SvrDoc_IPObj_UIDeactivate(LPOLEINPLACEOBJECT lpThis)
{
   LPSERVERDOC     lpServerDoc =
                  ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;
   LPSERVERAPP     lpServerApp = (LPSERVERAPP)g_lpApp;
   LPOUTLINEDOC    lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   LPINPLACEDATA   lpIPData = lpServerDoc->m_lpIPData;
   LPLINELIST      lpLL = (LPLINELIST)&((LPOUTLINEDOC)lpServerDoc)->m_LineList;
   HWND            hWndApp = OutlineApp_GetWindow(g_lpApp);

   OLEDBG_BEGIN2("SvrDoc_IPObj_UIDeactivate\r\n");

   if (!lpServerDoc->m_fUIActive) {
      OLEDBG_END2
      return NOERROR;
   }

   // artificial AddRef in case object is deleted during call
   SvrDoc_IPObj_AddRef(lpThis);

   lpServerDoc->m_fUIActive = FALSE;

   // Clip the hatch window to the size of pos rect so, that the object
   // adornments and hatch border will not be visible.
   ServerDoc_ResizeInPlaceWindow(lpServerDoc,
         (LPRECT)&(lpServerDoc->m_lpIPData->rcPosRect),
         (LPRECT)&(lpServerDoc->m_lpIPData->rcPosRect)
   );

   if (lpIPData->lpDoc)
      lpIPData->lpDoc->lpVtbl->SetActiveObject(lpIPData->lpDoc, NULL, NULL);

   if (lpIPData->lpFrame) {
      lpIPData->lpFrame->lpVtbl->SetActiveObject(
         lpIPData->lpFrame,
         NULL,
         NULL
      );
   }

#if defined( USE_FRAMETOOLS )
   /* NOTE: we must hide our frame tools here but NOT call
   **    IOleInPlaceFrame::SetBorderSpace(NULL) or SetMenu(NULL).
   **    we must hide our tools BEFORE calling
   **    IOleInPlaceSite::OnUIDeactivate. the container will put
   **    his menus and tools back when OnUIDeactivate is called.
   */
   ServerDoc_RemoveFrameLevelTools(lpServerDoc);
#endif

   OLEDBG_BEGIN2("IOleInPlaceSite::OnUIDeactivate called\r\n");
   lpIPData->lpSite->lpVtbl->OnUIDeactivate(lpIPData->lpSite, FALSE);
   OLEDBG_END2

   /* Reset to use our normal app's accelerator table */
   g_lpApp->m_hAccelApp = lpServerApp->m_hAccelBaseApp;
   g_lpApp->m_hAccel = lpServerApp->m_hAccelBaseApp;
   g_lpApp->m_hWndAccelTarget = hWndApp;

   OLEDBG_END2

#if !defined( SVR_INSIDEOUT )
   /* NOTE: an "outside-in" style in-place server would hide its
   **    window here. an "inside-out" style server leaves its window
   **    visible when it is UIDeactivated. it would only hide its
   **    window when InPlaceDeactivated. this app is an "inside-out"
   **    style server. it is recommended for most server to support
   **    inside-out behavior if possible.
   */
   ServerDoc_DoInPlaceHide(lpServerDoc);
#endif // INSIEDOUT
   // release artificial AddRef
   SvrDoc_IPObj_Release(lpThis);

   return NOERROR;
}

// IOleInPlaceObject::SetObjectRects method

STDMETHODIMP SvrDoc_IPObj_SetObjectRects(
      LPOLEINPLACEOBJECT  lpThis,
      LPCRECT             lprcPosRect,
      LPCRECT             lprcClipRect
)
{
   LPSERVERDOC  lpServerDoc =
               ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;
   LPINPLACEDATA lpIPData = lpServerDoc->m_lpIPData;
   LPLINELIST   lpLL = OutlineDoc_GetLineList((LPOUTLINEDOC)lpServerDoc);

   // artificial AddRef in case object is deleted during call
   SvrDoc_IPObj_AddRef(lpThis);

   OLEDBG_BEGIN2("SvrDoc_IPObj_SetObjectRects\r\n")

#if defined( _DEBUG )
   OleDbgOutRect3("SvrDoc_IPObj_SetObjectRects (PosRect)",
         (LPRECT)lprcPosRect);
   OleDbgOutRect3("SvrDoc_IPObj_SetObjectRects (ClipRect)",
         (LPRECT)lprcClipRect);
#endif
   // save the current PosRect and ClipRect
   lpIPData->rcPosRect = *lprcPosRect;
   lpIPData->rcClipRect = *lprcClipRect;

   if (! lpServerDoc->m_fUIActive) // hatch and adornaments must not be drawn
      lprcClipRect = lprcPosRect;

   ServerDoc_ResizeInPlaceWindow(
         lpServerDoc, (LPRECT)lprcPosRect, (LPRECT)lprcClipRect);

   OLEDBG_END2

   // release artificial AddRef
   SvrDoc_IPObj_Release(lpThis);

   return NOERROR;
}

// IOleInPlaceObject::ReactivateAndUndo method

STDMETHODIMP SvrDoc_IPObj_ReactivateAndUndo(LPOLEINPLACEOBJECT lpThis)
{
   OLEDBG_BEGIN2("SvrDoc_IPObj_ReactivateAndUndo\r\n")

   // We do not support support UNDO.

   /* REVIEW: for debugging purposes it would be useful to give a
   **    message box indicating that this method has been called.
   */

   OLEDBG_END2
   return NOERROR;
}

/*************************************************************************
** ServerDoc::IOleInPlaceActiveObject interface implementation
*************************************************************************/

// IOleInPlaceActiveObject::QueryInterface method

STDMETHODIMP SvrDoc_IPActiveObj_QueryInterface(
      LPOLEINPLACEACTIVEOBJECT    lpThis,
      REFIID                      riid,
      LPVOID FAR *                lplpvObj
)
{
   SCODE sc = E_NOINTERFACE;
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;

   /* The container should not be able to access the other interfaces
   ** of our object by doing QI on this interface.
   */

   *lplpvObj = NULL;
   if (IsEqualIID(riid, &IID_IUnknown) ||
      IsEqualIID(riid, &IID_IOleWindow) ||
      IsEqualIID(riid, &IID_IOleInPlaceActiveObject)) {
      OleDbgOut4("OleDoc_QueryInterface: IOleInPlaceActiveObject* RETURNED\r\n");

      *lplpvObj = lpThis;
      OleDoc_AddRef((LPOLEDOC)lpServerDoc);
      sc = NOERROR;
   }

   OleDbgQueryInterfaceMethod(*lplpvObj);

   return sc;
}

// IOleInPlaceActiveObject::AddRef method

STDMETHODIMP_(ULONG) SvrDoc_IPActiveObj_AddRef(
      LPOLEINPLACEACTIVEOBJECT lpThis
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;

   OleDbgAddRefMethod(lpThis, "IOleInPlaceActiveObject");

   return OleDoc_AddRef((LPOLEDOC)lpServerDoc);
}

// IOleInPlaceActiveObject::Release method

STDMETHODIMP_(ULONG) SvrDoc_IPActiveObj_Release(
      LPOLEINPLACEACTIVEOBJECT lpThis
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;

   OleDbgReleaseMethod(lpThis, "IOleInPlaceActiveObject");

   return OleDoc_Release((LPOLEDOC)lpServerDoc);
}

// IOleInPlaceActiveObject::GetWindow method

STDMETHODIMP SvrDoc_IPActiveObj_GetWindow(
      LPOLEINPLACEACTIVEOBJECT    lpThis,
      HWND FAR*                   lphwnd
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;

   OLEDBG_BEGIN2("SvrDoc_IPActiveObj_GetWindow\r\n")

   *lphwnd = ((LPOUTLINEDOC)lpServerDoc)->m_hWndDoc;

   OLEDBG_END2
   return NOERROR;
}

// IOleInPlaceActiveObject::ContextSensitiveHelp method

STDMETHODIMP SvrDoc_IPActiveObj_ContextSensitiveHelp(
      LPOLEINPLACEACTIVEOBJECT    lpThis,
      BOOL                        fEnterMode
)
{
   LPSERVERDOC lpServerDoc =
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;
   OleDbgOut2("SvrDoc_IPActiveObj_ContextSensitiveHelp\r\n");

   /* NOTE: see context sensitive help technote (CSHELP.DOC)
   **    This method is called when F1 is pressed when a menu item is
   **    selected. this tells the in-place server application to give
   **    help rather than execute the next menu command. at a minimum,
   **    even if the in-place server application does not implement a
   **    help system, it should NOT execute the next command when
   **    fEnable==TRUE. We set the active object's m_fMenuMode flag here.
   **    later, in WM_COMMAND processing in the DocWndProc, if this
   **    flag is set then the command is NOT executed (and help could
   **    be given if we had a help system....but we don't.)
   */
   lpServerDoc->m_fMenuHelpMode = fEnterMode;

#if !defined( HACK )
   ((LPOLEDOC)lpServerDoc)->m_fCSHelpMode = fEnterMode;
#endif
   return NOERROR;
}

// IOleInPlaceActiveObject::TranslateAccelerator method

STDMETHODIMP SvrDoc_IPActiveObj_TranslateAccelerator(
      LPOLEINPLACEACTIVEOBJECT    lpThis,
      LPMSG                       lpmsg
)
{
   // This will never be called because this server is implemented as an EXE
   return NOERROR;
}

// IOleInPlaceActiveObject::OnFrameWindowActivate method

STDMETHODIMP SvrDoc_IPActiveObj_OnFrameWindowActivate(
      LPOLEINPLACEACTIVEOBJECT    lpThis,
      BOOL                        fActivate
)
{
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)
         ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;
   HWND hWndDoc = OutlineDoc_GetWindow(lpOutlineDoc);
#if defined( _DEBUG )
   if (fActivate)
      OleDbgOut2("SvrDoc_IPActiveObj_OnFrameWindowActivate(TRUE)\r\n");
   else
      OleDbgOut2("SvrDoc_IPActiveObj_OnFrameWindowActivate(FALSE)\r\n");
#endif  // _DEBUG

   /* NOTE: this is a notification of the container application's
   **    WM_ACTIVATEAPP status. some applications may find this
   **    important. we need to update the enable/disable status of our
   **    tool bar buttons.
   */

   // NOTE: We can't call OutlineDoc_UpdateFrameToolButtons
   //           right away which
   //           would generate some OLE calls and eventually
   //           WM_ACTIVATEAPP and a loop was formed. Therefore, we
   //           should delay the frame tool initialization until
   //           WM_ACTIVATEAPP is finished by posting a message
   //           to ourselves.

   /* Update enable/disable state of buttons in toolbar */
   if (fActivate)
      PostMessage(hWndDoc, WM_U_INITFRAMETOOLS, 0, 0L);

   return NOERROR;
}

// IOleInPlaceActiveObject::OnDocWindowActivate method

STDMETHODIMP SvrDoc_IPActiveObj_OnDocWindowActivate(
      LPOLEINPLACEACTIVEOBJECT    lpThis,
      BOOL                        fActivate
)
{
   LPSERVERDOC     lpServerDoc =
                  ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;
   LPINPLACEDATA   lpIPData = lpServerDoc->m_lpIPData;
#if defined( _DEBUG )
   if (fActivate)
      OleDbgOut2("SvrDoc_IPActiveObj_OnDocWindowActivate(TRUE)\r\n");
   else
      OleDbgOut2("SvrDoc_IPActiveObj_OnDocWindowActivate(FALSE)\r\n");
#endif

   if (fActivate) {
      ServerDoc_AddFrameLevelUI(lpServerDoc);
   }
   else {
#if defined( USE_FRAMETOOLS )
      /* NOTE: we must NOT call IOleInPlaceFrame::SetBorderSpace(NULL)
      **    or SetMenu(NULL) here. we should simply hide our tools.
      */
      ServerDoc_RemoveFrameLevelTools(lpServerDoc);
#endif
   }

   OLEDBG_END2
   return NOERROR;
}

// IOleInPlaceActiveObject::ResizeBorder method

STDMETHODIMP SvrDoc_IPActiveObj_ResizeBorder(
      LPOLEINPLACEACTIVEOBJECT    lpThis,
      LPCRECT                     lprectBorder,
      LPOLEINPLACEUIWINDOW        lpIPUiWnd,
      BOOL                        fFrameWindow
)
{
   LPSERVERDOC lpServerDoc =
               ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;

   OLEDBG_BEGIN2("SvrDoc_IPActiveObj_ResizeBorder\r\n")

#if defined( USE_FRAMETOOLS )

   if (fFrameWindow) {
      FrameTools_NegotiateForSpaceAndShow(
            lpOutlineDoc->m_lpFrameTools,
            (LPRECT)lprectBorder,
            (LPOLEINPLACEFRAME)lpIPUiWnd
      );
   }

#endif

   OLEDBG_END2
   return NOERROR;
}

// IOleInPlaceActiveObject::EnableModeless method

STDMETHODIMP SvrDoc_IPActiveObj_EnableModeless(
      LPOLEINPLACEACTIVEOBJECT    lpThis,
      BOOL                        fEnable
)
{
#if defined( USE_FRAMETOOLS )
   LPSERVERDOC lpServerDoc =
            ((struct CDocOleObjectImpl FAR*)lpThis)->lpServerDoc;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   LPFRAMETOOLS lptb;

   /* NOTE: we must enable/disable mouse and keyboard input to our
   **    floating tool palette
   */
   if (lpOutlineDoc) {
      lptb = lpOutlineDoc->m_lpFrameTools;
      if (lptb)
         FrameTools_EnableWindow(lptb, fEnable);
   }
#endif  // USE_FRAMETOOLS

#if defined( _DEBUG )
   if (fEnable)
      OleDbgOut2("SvrDoc_IPActiveObj_EnableModeless(TRUE)\r\n");
   else
      OleDbgOut2("SvrDoc_IPActiveObj_EnableModeless(FALSE)\r\n");
#endif  // _DEBUG

   /* NOTE: this method is called when the top-level, in-place
   **    container puts up a modal dialog. it tells the UIActive
   **    object to disable it modeless dialogs for the duration that
   **    the container is displaying a modal dialog.
   **
   **    ISVROTL does not use any modeless dialogs, thus we can
   **    ignore this method.
   */
   return NOERROR;
}

/*************************************************************************
** Support Functions
*************************************************************************/

HRESULT ServerDoc_DoInPlaceActivate(
      LPSERVERDOC     lpServerDoc,
      LONG            lVerb,
      LPMSG           lpmsg,
      LPOLECLIENTSITE lpActiveSite
)
{
   LPOUTLINEAPP            lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPSERVERAPP             lpServerApp = (LPSERVERAPP)g_lpApp;
   LPOLEAPP                lpOleApp = (LPOLEAPP)g_lpApp;
   SCODE                   sc = E_FAIL;
   RECT                    rcPos;
   RECT                    rcClip;
   LPINPLACEDATA           lpIPData = lpServerDoc->m_lpIPData;
   LPOUTLINEDOC            lpOutlineDoc=(LPOUTLINEDOC)lpServerDoc;
   HWND                    hWndDoc = lpOutlineDoc->m_hWndDoc;
   HWND                    hWndHatch = lpServerDoc->m_hWndHatch;
   HRESULT                 hrErr;
   LPLINELIST              lpLL=(LPLINELIST)&lpOutlineDoc->m_LineList;
   LPOLEINPLACESITE    lpIPSite = NULL;

   /* NOTE: lpActiveSite should be used only for InPlace PLAYing.
   **    This app does not do inplace PLAYing, so it never uses
   **    lpActiveSite.
   */

   /* InPlace activation can only be done if the ClientSite is non-NULL. */
   if (! lpServerDoc->m_lpOleClientSite)
      return NOERROR;

   if (! lpServerDoc->m_fInPlaceActive) {

      // if the object is in open mode then we do not want to do inplace
      // activation.
      if (IsWindowVisible(lpOutlineDoc->m_hWndDoc))
      {
         return NOERROR;
      }

      lpIPSite = (LPOLEINPLACESITE)OleStdQueryInterface(
            (LPUNKNOWN)lpServerDoc->m_lpOleClientSite,
            &IID_IOleInPlaceSite
      );

      if (! lpIPSite)
         goto errActivate;

      OLEDBG_BEGIN2("IOleInPlaceSite::CanInPlaceActivate called\r\n");
      hrErr = lpIPSite->lpVtbl->CanInPlaceActivate(lpIPSite);
      OLEDBG_END2
      if (hrErr != NOERROR)
         goto errActivate;

      lpServerDoc->m_fInPlaceActive = TRUE;
      OLEDBG_BEGIN2("IOleInPlaceSite::OnInPlaceActivate called\r\n");
      hrErr = lpIPSite->lpVtbl->OnInPlaceActivate(lpIPSite);
      OLEDBG_END2
      if (hrErr != NOERROR)
         goto errActivate;

      if (! ServerDoc_AllocInPlaceData(lpServerDoc)) {
         sc = E_OUTOFMEMORY;
         OLEDBG_BEGIN2("IOleInPlaceSite::OnInPlaceDeactivate called\r\n");
         lpIPSite->lpVtbl->OnInPlaceDeactivate(lpIPSite);
         OLEDBG_END2
         goto errActivate;
      }

      (lpIPData = lpServerDoc->m_lpIPData)->lpSite = lpIPSite;
      goto InPlaceActive;

   errActivate:
      lpServerDoc->m_fInPlaceActive = FALSE;
      if (lpIPSite)
         OleStdRelease((LPUNKNOWN)lpIPSite);

      return sc;
   }

InPlaceActive:

   if (! lpServerDoc->m_fInPlaceVisible) {
      lpServerDoc->m_fInPlaceVisible = TRUE;

      OLEDBG_BEGIN2("IOleInPlaceSite::GetWindow called\r\n");
      hrErr = lpIPData->lpSite->lpVtbl->GetWindow(
               lpIPData->lpSite, &lpServerDoc->m_hWndParent);
      OLEDBG_END2
      if (hrErr != NOERROR) {
         sc = hrErr;
         goto errRtn;
      }

      if (! lpServerDoc->m_hWndParent)
         goto errRtn;

      /* NOTE: The server should fill in the "cb" field so that the
      **    container can tell what size structure the server is
      **    expecting. this enables this structure to be easily extended
      **    in future releases of OLE. the container should check this
      **    field so that it doesn't try to use fields that do not exist
      **    since the server may be using an old structure definition.
      */
      _fmemset(
         (LPOLEINPLACEFRAMEINFO)&lpIPData->frameInfo,
         0,
         sizeof(OLEINPLACEFRAMEINFO)
      );
      lpIPData->frameInfo.cb = sizeof(OLEINPLACEFRAMEINFO);

      OLEDBG_BEGIN2("IOleInPlaceSite::GetWindowContext called\r\n");
      hrErr = lpIPData->lpSite->lpVtbl->GetWindowContext(lpIPData->lpSite,
               (LPOLEINPLACEFRAME FAR*) &lpIPData->lpFrame,
               (LPOLEINPLACEUIWINDOW FAR*)&lpIPData->lpDoc,
               (LPRECT)&rcPos,
               (LPRECT)&rcClip,
               (LPOLEINPLACEFRAMEINFO)&lpIPData->frameInfo);
      OLEDBG_END2

      if (hrErr != NOERROR) {
         sc = hrErr;
         goto errRtn;
      }

      lpServerApp->m_lpIPData = lpIPData;
      ShowWindow(hWndDoc, SW_HIDE);   // make sure we are hidden

      /* NOTE: reparent in-place server document's window to the
      **    special in-place hatch border window. set the in-place site's
      **    window as the parent of the hatch window. position the
      **    in-place and hatch border windows using the PosRect and
      **    ClipRect.
      **    it is important to properly parent and position the in-place
      **    server window BEFORE calling IOleInPlaceFrame::SetMenu and
      **    SetBorderSpace.
      */
      ShowWindow(lpServerDoc->m_hWndHatch, SW_SHOW);
      // make sure App busy/blocked dialogs are parented to our
      // new hWndFrame
      OleStdMsgFilter_SetParentWindow(
         lpOleApp->m_lpMsgFilter,lpIPData->frameInfo.hwndFrame);
      SetParent(lpServerDoc->m_hWndHatch, lpServerDoc->m_hWndParent);
      SetParent(hWndDoc, lpServerDoc->m_hWndHatch);

#if defined( _DEBUG )
      OleDbgOutRect3("IOleInPlaceSite::GetWindowContext (PosRect)",
            (LPRECT)&rcPos);
      OleDbgOutRect3("IOleInPlaceSite::GetWindowContext (ClipRect)",
            (LPRECT)&rcClip);
#endif
      // save the current PosRect and ClipRect
      lpIPData->rcPosRect  = rcPos;
      lpIPData->rcClipRect = rcClip;

      /* NOTE: build the shared menu for the in-place container and
      **    the server.
      */
      if (ServerDoc_AssembleMenus (lpServerDoc) != NOERROR)
         goto errRtn;

#if defined( SVR_INSIDEOUT )
      if (lVerb == OLEIVERB_INPLACEACTIVATE) {
         // Clip the hatch window to the size of pos rect so, that
         // hatch and object adornments  will not be visible.
         ServerDoc_ResizeInPlaceWindow(lpServerDoc,
            (LPRECT)&(lpServerDoc->m_lpIPData->rcPosRect),
            (LPRECT)&(lpServerDoc->m_lpIPData->rcPosRect)
         );
      }
#endif  // SVR_INSIDEOUT
   }

#if defined( SVR_INSIDEOUT )
   // NOTE: if verb is OLEIVERB_INPLACEACTIVATE we do NOT want to
   // show our UI
   if (lVerb == OLEIVERB_INPLACEACTIVATE) {

      return NOERROR;
   }
#endif  // SVR_INSIDEOUT

   if (! lpServerDoc->m_fUIActive) {
      lpServerDoc->m_fUIActive = TRUE;
      OLEDBG_BEGIN2("IOleInPlaceSite::OnUIActivate called\r\n");
      hrErr = lpIPData->lpSite->lpVtbl->OnUIActivate(lpIPData->lpSite);
      OLEDBG_END2
      if (hrErr != NOERROR) {
         lpServerDoc->m_fUIActive = FALSE;
         goto errRtn;
      }

      SetFocus(hWndDoc);

      // Show the object adornments and hacth border around them.
      ServerDoc_ResizeInPlaceWindow(lpServerDoc,
               (LPRECT)&lpIPData->rcPosRect,
               (LPRECT)&lpIPData->rcClipRect
      );

      /* NOTE: IOleInPlaceFrame::SetActiveObject must be called BEFORE
      **    IOleInPlaceFrame::SetMenu.
      */
      OLEDBG_BEGIN2("IOleInPlaceSite::SetActiveObject called\r\n");
      lpIPData->lpFrame->lpVtbl->SetActiveObject(
         lpIPData->lpFrame,
         (LPOLEINPLACEACTIVEOBJECT) &lpServerDoc->m_OleInPlaceActiveObject,
         /*(LPOLESTR)*/g_szIPObjectTitle
      );
      OLEDBG_END2

      /* NOTE: If the container wants to give ownership of the
      **    palette then he would sendmessage WM_QUEYNEWPALETTE to
      **    the object window proc, before returning from
      **    IOleInPlaceFrame::SetActiveObject. Those objects which
      **    want to be edited inplace only if they have the ownership of
      **    the palette, can check at this point in the code whether
      **    they got WM_QUERYNEWPALETTE or not. If they didn't get
      **    the message, then they can inplace deactivate and do open
      **    editing instead.
      */

      if (lpIPData->lpDoc) {
         lpIPData->lpDoc->lpVtbl->SetActiveObject(
            lpIPData->lpDoc,
            (LPOLEINPLACEACTIVEOBJECT)&lpServerDoc->m_OleInPlaceActiveObject,
            /*(LPOLESTR)*/g_szIPObjectTitle
         );
      }

      /* NOTE: install the menu and frame-level tools on the in-place
      **    frame.
      */
      ServerDoc_AddFrameLevelUI(lpServerDoc);
   }

   return NOERROR;

errRtn:
   ServerDoc_DoInPlaceDeactivate(lpServerDoc);

   return sc;
}

HRESULT ServerDoc_DoInPlaceDeactivate(LPSERVERDOC lpServerDoc)
{
   LPINPLACEDATA   lpIPData = lpServerDoc->m_lpIPData;
   LPOUTLINEDOC    lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;

   if (!lpServerDoc->m_fInPlaceActive)
      return S_OK;

   lpServerDoc->m_fInPlaceActive = FALSE;

   SvrDoc_IPObj_UIDeactivate(
         (LPOLEINPLACEOBJECT)&lpServerDoc->m_OleInPlaceObject);

   /* NOTE: an inside-out style in-place server will
   **    NOT hide its window in UIDeactive (an outside-in
   **    style object will hide its window in UIDeactivate).
   **    thus, an inside-out server must explicitly hide
   **    its window in InPlaceDeactivate. it is ALSO important for an
   **    outside-in style object to call ServerDoc_DoInPlaceHide here
   **    BEFORE freeing the InPlaceData structure. it will be common
   **    for in-place containers to call IOleInPlaceObject::
   **    InPlaceDeactivate in their IOleInPlaceSite::OnUIDeactiate
   **    implementation.
   */
   ServerDoc_DoInPlaceHide(lpServerDoc);

   OLEDBG_BEGIN2("IOleInPlaceSite::OnInPlaceDeactivate called\r\n");
   lpIPData->lpSite->lpVtbl->OnInPlaceDeactivate(lpIPData->lpSite);
   OLEDBG_END2

   OleStdRelease((LPUNKNOWN)lpIPData->lpSite);
   lpIPData->lpSite = NULL;

   ServerDoc_FreeInPlaceData(lpServerDoc);

   return NOERROR;
}

HRESULT ServerDoc_DoInPlaceHide(LPSERVERDOC lpServerDoc)
{
   LPINPLACEDATA   lpIPData = lpServerDoc->m_lpIPData;
   LPOUTLINEDOC    lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   LPOLEAPP        lpOleApp = (LPOLEAPP)g_lpApp;
   HWND            hWndApp = OutlineApp_GetWindow(g_lpApp);

   if (! lpServerDoc->m_fInPlaceVisible)
      return NOERROR;

   // Set the parent back to server app's window
   OleDoc_HideWindow((LPOLEDOC)lpServerDoc, FALSE /* fShutdown */);

   /* we need to enusure that our window is set to normal 100% zoom.
   **    if the window is next shown in open mode it should start out
   **    at normal zoom factor. our window may have been set to a
   **    different zoom factor while it was in-place active.
   */
   OutlineDoc_SetCurrentZoomCommand(lpOutlineDoc,IDM_V_ZOOM_100);

   lpServerDoc->m_fInPlaceVisible = FALSE;

   lpServerDoc->m_hWndParent = hWndApp;
   SetParent(
      lpOutlineDoc->m_hWndDoc,
      lpServerDoc->m_hWndParent
   );

   // make sure App busy/blocked dialogs are parented to our own hWndApp
   OleStdMsgFilter_SetParentWindow(lpOleApp->m_lpMsgFilter, hWndApp);

   // Hide the in-place hatch border window.
   ShowWindow(lpServerDoc->m_hWndHatch, SW_HIDE);

   ServerDoc_DisassembleMenus(lpServerDoc);

   /* we no longer need the IOleInPlaceFrame* or the doc's

**    IOleInPlaceWindow* interface pointers.
   */
   if (lpIPData->lpDoc) {
      OleStdRelease((LPUNKNOWN)lpIPData->lpDoc);
      lpIPData->lpDoc = NULL;
   }

   if (lpIPData->lpFrame) {
      OleStdRelease((LPUNKNOWN)lpIPData->lpFrame);
      lpIPData->lpFrame = NULL;
   }

   ((LPSERVERAPP)g_lpApp)->m_lpIPData = NULL;

   return NOERROR;
}

BOOL ServerDoc_AllocInPlaceData(LPSERVERDOC lpServerDoc)
{
   LPINPLACEDATA   lpIPData;

   if (!(lpIPData = (LPINPLACEDATA) New(sizeof(INPLACEDATA))))
      return FALSE;

   lpIPData->lpFrame       = NULL;
   lpIPData->lpDoc         = NULL;
   lpIPData->lpSite        = NULL;
   lpIPData->hOlemenu      = NULL;
   lpIPData->hMenuShared   = NULL;

   lpServerDoc->m_lpIPData = lpIPData;
   return TRUE;
}

void ServerDoc_FreeInPlaceData(LPSERVERDOC lpServerDoc)
{
   Delete(lpServerDoc->m_lpIPData);
   lpServerDoc->m_lpIPData = NULL;
}

HRESULT ServerDoc_AssembleMenus(LPSERVERDOC lpServerDoc)
{
   HMENU           hMenuShared;
   LONG FAR*       lpWidths;
   UINT            uPosition;
   UINT            uPositionStart;
   LPSERVERAPP     lpServerApp = (LPSERVERAPP) g_lpApp;
   LPINPLACEDATA   lpIPData = lpServerDoc->m_lpIPData;
   HRESULT         hresult;
   BOOL            fNoError = TRUE;

   lpWidths = lpIPData->menuGroupWidths.width;
   hMenuShared = CreateMenu();

   if (hMenuShared &&
      (hresult = lpIPData->lpFrame->lpVtbl->InsertMenus(
         lpIPData->lpFrame, hMenuShared,
         &lpIPData->menuGroupWidths)) == NOERROR) {

      /* Insert EDIT group menus */

      uPosition = (UINT)lpWidths[0]; /* # of menus in the FILE group */
      uPositionStart = uPosition;

      fNoError &= InsertMenu(
            hMenuShared,
            (UINT)uPosition,
            (UINT)(MF_BYPOSITION | MF_POPUP),
            (UINT)lpServerApp->m_hMenuEdit,
            (LPCSTR)"&Edit"
      );
      uPosition++;

      lpWidths[1] = uPosition - uPositionStart;

      /* Insert OBJECT group menus */

      uPosition += (UINT)lpWidths[2];
      uPositionStart = uPosition;

      fNoError &= InsertMenu(
            hMenuShared,
            (UINT)uPosition,
            (UINT)(MF_BYPOSITION | MF_POPUP),
            (UINT)lpServerApp->m_hMenuLine,
            (LPCSTR)"&Line"
      );
      uPosition++;

      fNoError &= InsertMenu(
            hMenuShared,
            (UINT)uPosition,
            (UINT)(MF_BYPOSITION | MF_POPUP),
            (UINT)lpServerApp->m_hMenuName,
            (LPCSTR)"&Name"
      );
      uPosition++;

      fNoError &= InsertMenu(
            hMenuShared,
            (UINT)uPosition,
            (UINT)(MF_BYPOSITION | MF_POPUP),
            (UINT)lpServerApp->m_hMenuOptions,
            (LPCSTR)"&Options"
      );
      uPosition++;

      fNoError &= InsertMenu(
            hMenuShared,
            (UINT)uPosition,
            (UINT)(MF_BYPOSITION | MF_POPUP),
            (UINT)lpServerApp->m_hMenuDebug,
            (LPCSTR)"DbgI&Svr"
      );
      uPosition++;

      lpWidths[3] = uPosition - uPositionStart;

      /* Insert HELP group menus */

      uPosition += (UINT) lpWidths[4]; /* # of menus in WINDOW group */
      uPositionStart = uPosition;

      fNoError &= InsertMenu(
            hMenuShared,
            (UINT)uPosition,
            (UINT)(MF_BYPOSITION | MF_POPUP),
            (UINT)lpServerApp->m_hMenuHelp,
            (LPCSTR)"&Help"
      );
      uPosition++;

      lpWidths[5] = uPosition - uPositionStart;

      OleDbgAssert(fNoError == TRUE);

   } else {
      /* In-place container does not allow us to add menus to the
      **    frame.
      ** NOTE: even when the in-place container does NOT allow
      **    the building of a merged menu bar, it is CRITICAL that
      **    the in-place server still call OleCreateMenuDescriptor
      **    passing NULL for hMenuShared.
      */
      if (hMenuShared) {
         DestroyMenu(hMenuShared);
         hMenuShared = NULL;
      }
   }

   lpIPData->hMenuShared = hMenuShared;

   if (!(lpIPData->hOlemenu = OleCreateMenuDescriptor(hMenuShared,
                                 &lpIPData->menuGroupWidths)))
    {
      return E_OUTOFMEMORY;
    }

   return NOERROR;
}

void ServerDoc_DisassembleMenus(LPSERVERDOC lpServerDoc)
{
   UINT             uCount;
   UINT            uGroup;
   UINT            uDeleteAt;
   LPINPLACEDATA   lpIPData = lpServerDoc->m_lpIPData;
   LONG FAR*       lpWidths = lpIPData->menuGroupWidths.width;
   BOOL            fNoError = TRUE;

   /* NOTE: even when hMenuShared is NULL (ie. the server has no
   **    Menu), there is still an hOleMenu created that must be destroyed.
   */
   if (lpIPData->hOlemenu) {
      OleDestroyMenuDescriptor (lpIPData->hOlemenu);
      lpIPData->hOlemenu = NULL;
   }

   if (! lpIPData->hMenuShared)
      return;     // no menus to be destroyed

   /* Remove server group menus. */
   uDeleteAt = 0;
   for (uGroup = 0; uGroup < 6; uGroup++) {
      uDeleteAt += (UINT)lpWidths[uGroup++];
      for (uCount = 0; uCount < (UINT)lpWidths[uGroup]; uCount++)
         fNoError &= RemoveMenu(lpIPData->hMenuShared, uDeleteAt,
                        MF_BYPOSITION);
   }

   /* Remove container group menus */
   fNoError &= (lpIPData->lpFrame->lpVtbl->RemoveMenus(
      lpIPData->lpFrame,
      lpIPData->hMenuShared) == NOERROR);

   OleDbgAssert(fNoError == TRUE);

   DestroyMenu(lpIPData->hMenuShared);
   lpIPData->hMenuShared = NULL;
}

/* ServerDoc_UpdateInPlaceWindowOnExtentChange
** -------------------------------------------
**    The size of the in-place window needs to be changed.
**    calculate the size required in Client coordinates (taking into
**    account the current scale factor imposed by the in-place
**    container) and ask our in-place container to allow us to resize.
**    our container must call us back via
**    IOleInPlaceObject::SetObjectRects for the actual sizing to take
**    place.
**
**    NOTE: the rectangle that we ask for from our in-place
**    container is always the rectangle required for the object display
**    itself (in our case the size of the LineList contents). it does
**    NOT include the space we require for object frame adornments.
*/
void ServerDoc_UpdateInPlaceWindowOnExtentChange(LPSERVERDOC lpServerDoc)
{
   SIZEL       sizelHim;
   SIZEL       sizelPix;
   RECT        rcPosRect;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   LPLINELIST  lpLL=(LPLINELIST)&lpOutlineDoc->m_LineList;
   HWND        hWndLL = lpLL->m_hWndListBox;
   LPSCALEFACTOR lpscale = (LPSCALEFACTOR)&lpOutlineDoc->m_scale;

   if (!lpServerDoc->m_fInPlaceActive)
      return;

   OleDoc_GetExtent((LPOLEDOC)lpServerDoc, (LPSIZEL)&sizelHim);

   // apply current scale factor
   sizelHim.cx = sizelHim.cx * lpscale->dwSxN / lpscale->dwSxD;
   sizelHim.cy = sizelHim.cy * lpscale->dwSxN / lpscale->dwSxD;
   XformSizeInHimetricToPixels(NULL, (LPSIZEL)&sizelHim, (LPSIZEL)&sizelPix);

   GetWindowRect(hWndLL, (LPRECT)&rcPosRect);
   ScreenToClient(lpServerDoc->m_hWndParent, (POINT FAR *)&rcPosRect);

   rcPosRect.right = rcPosRect.left + (int) sizelPix.cx;
   rcPosRect.bottom = rcPosRect.top + (int) sizelPix.cy;
   OleDbgOutRect3("ServerDoc_UpdateInPlaceWindowOnExtentChange: (PosRect)", (LPRECT)&rcPosRect);

   OLEDBG_BEGIN2("IOleInPlaceSite::OnPosRectChange called\r\n");
   lpServerDoc->m_lpIPData->lpSite->lpVtbl->OnPosRectChange(
         lpServerDoc->m_lpIPData->lpSite,
         (LPRECT) &rcPosRect
   );
   OLEDBG_END2
}

/* ServerDoc_CalcInPlaceWindowPos
 * ------------------------------
 *
 *  Move (and re-scale) the ServerDoc to the specified rectangle.
 *
 *  Parameters:
 *      lprcListBox - rect in client coordinate in which the listbox will fit
 *      lprcDoc     - corresponding size of the Doc in client coordinate
 *
 */
void ServerDoc_CalcInPlaceWindowPos(
      LPSERVERDOC         lpServerDoc,
      LPRECT              lprcListBox,
      LPRECT              lprcDoc,
      LPSCALEFACTOR       lpscale
)
{
   SIZEL sizelHim;
   SIZEL sizelPix;
   LPLINELIST lpLL;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   LPHEADING lphead;

   if (!lpServerDoc || !lprcListBox || !lprcDoc)
      return;
   lphead = (LPHEADING)&lpOutlineDoc->m_heading;

   lpLL = OutlineDoc_GetLineList(lpOutlineDoc);
   OleDoc_GetExtent((LPOLEDOC)lpServerDoc, (LPSIZEL)&sizelHim);
   XformSizeInHimetricToPixels(NULL, &sizelHim, &sizelPix);

   if (sizelHim.cx == 0 || sizelPix.cx == 0) {
      lpscale->dwSxN = 1;
      lpscale->dwSxD = 1;
   } else {
      lpscale->dwSxN = lprcListBox->right - lprcListBox->left;
      lpscale->dwSxD = sizelPix.cx;
   }

   if (sizelHim.cy == 0 || sizelPix.cy == 0) {
      lpscale->dwSyN = 1;
      lpscale->dwSyD = 1;
   } else {
      lpscale->dwSyN = lprcListBox->bottom - lprcListBox->top;
      lpscale->dwSyD = sizelPix.cy;
   }

   lprcDoc->left = lprcListBox->left - Heading_RH_GetWidth(lphead,lpscale);
   lprcDoc->right = lprcListBox->right;
   lprcDoc->top = lprcListBox->top - Heading_CH_GetHeight(lphead,lpscale);
   lprcDoc->bottom = lprcListBox->bottom;
}

/* ServerDoc_ResizeInPlaceWindow
** -----------------------------
**    Actually resize the in-place ServerDoc windows according to the
**    PosRect and ClipRect allowed by our in-place container.
**
**    NOTE: the PosRect rectangle that our in-place container tells
**    us is always the rectangle required for the object display
**    itself (in our case the size of the LineList contents). it does
**    NOT include the space we require for object frame adornments.
*/
void ServerDoc_ResizeInPlaceWindow(
      LPSERVERDOC         lpServerDoc,
      LPCRECT             lprcPosRect,
      LPCRECT             lprcClipRect
)
{
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   LPLINELIST   lpLL = (LPLINELIST)&lpOutlineDoc->m_LineList;
   SCALEFACTOR  scale;
   RECT         rcDoc;
   POINT        ptOffset;

   /* NOTE: calculate the space needed for our object frame
   **    adornments. our in-place container tells us the size that our
   **    object should take in window client coordinates
   **    (lprcPosRect). the rectangle cooresponds to the size that our
   **    LineList ListBox should be. our Doc window must the correct
   **    amount larger to accomodate our row/column headings.
   **    then move all windows into position.
   */
   ServerDoc_CalcInPlaceWindowPos(
         lpServerDoc,
         (LPRECT)lprcPosRect,
         (LPRECT)&rcDoc,
         (LPSCALEFACTOR)&scale
   );

   /* NOTE: we need to honor the lprcClipRect specified by our
   **    in-place container. we must NOT draw outside of the ClipRect.
   **    in order to achieve this, we will size the hatch window to be
   **    exactly the size that should be visible (rcVisRect). the
   **    rcVisRect is defined as the intersection of the full size of
   **    the in-place server window and the lprcClipRect.
   **    the ClipRect could infact clip the HatchRect on the
   **    right/bottom and/or on the top/left. if it is clipped on the
   **    right/bottom then it is sufficient to simply resize the hatch
   **    window. but if the HatchRect is clipped on the top/left then
   **    we must "move" the ServerDoc window (child of HatchWindow) by
   **    the delta that was clipped. the window origin of the
   **    ServerDoc window will then have negative coordinates relative
   **    to its parent HatchWindow.
   */
   SetHatchWindowSize(
         lpServerDoc->m_hWndHatch,
         (LPRECT)&rcDoc,
         (LPRECT)lprcClipRect,
         (LPPOINT)&ptOffset
   );

   // shift Doc window to account for hatch frame being drawn
   OffsetRect((LPRECT)&rcDoc, ptOffset.x, ptOffset.y);

   // move/size/set scale factor of ServerDoc window.
   OutlineDoc_SetScaleFactor(
         lpOutlineDoc, (LPSCALEFACTOR)&scale, (LPRECT)&rcDoc);

   /* reset the horizontal extent of the listbox. this makes
   **    the listbox realize that a scroll bar is not needed.
   */
   SendMessage(
         lpLL->m_hWndListBox,
         LB_SETHORIZONTALEXTENT,
         (int) 0,
         0L
   );
   SendMessage(
         lpLL->m_hWndListBox,
         LB_SETHORIZONTALEXTENT,
         (int) (lprcPosRect->right - lprcPosRect->left),
         0L
   );
}

/* ServerDoc_SetStatusText
**    Tell the active in-place frame to display a status message.
*/
void ServerDoc_SetStatusText(LPSERVERDOC lpServerDoc, LPOLESTR lpszMessage)
{
   if (lpServerDoc && lpServerDoc->m_fUIActive &&
      lpServerDoc->m_lpIPData != NULL) {

      OLEDBG_BEGIN2("IOleInPlaceFrame::SetStatusText called\r\n")
      lpServerDoc->m_lpIPData->lpFrame->lpVtbl->SetStatusText
            (lpServerDoc->m_lpIPData->lpFrame, lpszMessage);
      OLEDBG_END2
   }
}

/* ServerDoc_GetTopInPlaceFrame
** ----------------------------
**    returns NON-AddRef'ed pointer to Top In-Place Frame interface
*/
LPOLEINPLACEFRAME ServerDoc_GetTopInPlaceFrame(LPSERVERDOC lpServerDoc)
{
   if (lpServerDoc->m_lpIPData)
      return lpServerDoc->m_lpIPData->lpFrame;
   else
      return NULL;
}

void ServerDoc_GetSharedMenuHandles(
      LPSERVERDOC lpServerDoc,
      HMENU FAR*      lphSharedMenu,
      HOLEMENU FAR*   lphOleMenu
)
{
   if (lpServerDoc->m_lpIPData) {
      *lphSharedMenu = lpServerDoc->m_lpIPData->hMenuShared;
      *lphOleMenu = lpServerDoc->m_lpIPData->hOlemenu;
   } else {
      *lphSharedMenu = NULL;
      *lphOleMenu = NULL;
   }
}

void ServerDoc_AddFrameLevelUI(LPSERVERDOC lpServerDoc)
{
   LPOUTLINEAPP lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPSERVERAPP lpServerApp = (LPSERVERAPP)g_lpApp;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   LPOLEINPLACEFRAME lpTopIPFrame=ServerDoc_GetTopInPlaceFrame(lpServerDoc);
   HMENU           hSharedMenu;            // combined obj/cntr menu
   HOLEMENU        hOleMenu;               // returned by OleCreateMenuDesc.

   ServerDoc_GetSharedMenuHandles(
         lpServerDoc,
         &hSharedMenu,
         &hOleMenu
   );

   lpTopIPFrame->lpVtbl->SetMenu(
         lpTopIPFrame,
         hSharedMenu,
         hOleMenu,
         lpOutlineDoc->m_hWndDoc
   );

   // save normal accelerator table
   lpServerApp->m_hAccelBaseApp = lpOutlineApp->m_hAccelApp;

   // install accelerator table for UIActive server (w/ active editor cmds)
   lpOutlineApp->m_hAccel = lpServerApp->m_hAccelIPSvr;
   lpOutlineApp->m_hAccelApp = lpServerApp->m_hAccelIPSvr;
   lpOutlineApp->m_hWndAccelTarget = lpOutlineDoc->m_hWndDoc;

#if defined( USE_FRAMETOOLS )
   ServerDoc_AddFrameLevelTools(lpServerDoc);

   // update toolbar button enable states
   OutlineDoc_UpdateFrameToolButtons(lpOutlineDoc);
#endif
}

void ServerDoc_AddFrameLevelTools(LPSERVERDOC lpServerDoc)
{
   LPOUTLINEAPP    lpOutlineApp = (LPOUTLINEAPP)g_lpApp;
   LPSERVERAPP     lpServerApp = (LPSERVERAPP)g_lpApp;
   LPOUTLINEDOC    lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   LPOLEINPLACEFRAME lpTopIPFrame=ServerDoc_GetTopInPlaceFrame(lpServerDoc);

#if defined( USE_FRAMETOOLS )
   HWND            hWndFrame;

   FrameTools_Enable(lpOutlineDoc->m_lpFrameTools, TRUE);

   // if not in-place UI active, add our tools to our own frame.
   if (! lpServerDoc->m_fUIActive) {
      OutlineDoc_AddFrameLevelTools(lpOutlineDoc);
      return;
   }

   if ((hWndFrame = OutlineApp_GetFrameWindow(lpOutlineApp)) == NULL) {
      /* we could NOT get a valid frame window, so POP our tools up. */

      /* NOTE: since we are poping up our tools, we MUST inform
      **    the top in-place frame window that we need NO tool space
      **    BUT that it should NOT put its own tools up. if we were
      **    to pass NULL instead of (0,0,0,0), then the container
      **    would have the option to leave its own tools up.
      */
      lpTopIPFrame->lpVtbl->SetBorderSpace(
            lpTopIPFrame,
            (LPCBORDERWIDTHS)&g_rectNull
      );
      FrameTools_PopupTools(lpOutlineDoc->m_lpFrameTools);
   } else {

      /* NOTE: we need to negotiate for space and attach our frame
      **    level tools to the top-level in-place container's frame window.
      */
      FrameTools_AttachToFrame(lpOutlineDoc->m_lpFrameTools, hWndFrame);

      FrameTools_NegotiateForSpaceAndShow(
            lpOutlineDoc->m_lpFrameTools,
            NULL,
            lpTopIPFrame
      );
   }

#else   // ! USE_FRAMETOOLS
   /* NOTE: if you do NOT use frame tools, you MUST inform the top
   **    in-place frame window so that it can put back its own tools.
   */
   lpTopIPFrame->lpVtbl->SetBorderSpace(lpIPData->lpFrame, NULL);
#endif  // ! USE_FRAMETOOLS
}

#if defined( USE_FRAMETOOLS )

void ServerDoc_RemoveFrameLevelTools(LPSERVERDOC lpServerDoc)
{
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   OleDbgAssert(lpOutlineDoc->m_lpFrameTools != NULL);

    // Reparent our tools back to one of our own windows
    FrameTools_AttachToFrame(lpOutlineDoc->m_lpFrameTools,g_lpApp->m_hWndApp);

   FrameTools_Enable(lpOutlineDoc->m_lpFrameTools, FALSE);
}
#endif  // USE_FRAMETOOLS

void ServerDoc_UIActivate (LPSERVERDOC lpServerDoc)
{
   if (lpServerDoc->m_fInPlaceActive && !lpServerDoc->m_fUIActive) {
      ServerDoc_DoInPlaceActivate(lpServerDoc,
            OLEIVERB_UIACTIVATE,
            NULL /*lpmsg*/,
            lpServerDoc->m_lpOleClientSite
      );
      OutlineDoc_ShowWindow((LPOUTLINEDOC)lpServerDoc);
   }
}


SVROUTL.C

/*************************************************************************
**
**    OLE 2.0 Sample Code
**
**    precomp.c
**
**    This file is used to precompile the OUTLINE.H header file
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#include "outline.h"


SVROUTL.H

/*************************************************************************
**
**    OLE 2.0 Server Sample Code
**
**    svroutl.h
**
**    This file contains file contains data structure defintions,
**    function prototypes, constants, etc. used by the OLE 2.0 server
**    app version of the Outline series of sample applications:
**          Outline -- base version of the app (without OLE functionality)
**          SvrOutl -- OLE 2.0 Server sample app
**          CntrOutl -- OLE 2.0 Containter sample app
**
**    (c) Copyright Microsoft Corp. 1992 - 1996 All Rights Reserved
**
*************************************************************************/

#if !defined( _SVROUTL_H_ )
#define _SVROUTL_H_

#ifndef RC_INVOKED
#pragma message ("INCLUDING SVROUTL.H from " __FILE__)
#endif  /* RC_INVOKED */

#include "oleoutl.h"
#include "ansiapi.h"

/* Defines */

// Enable SVROUTL and ISVROTL to emulate each other (TreatAs aka. ActivateAs)
#define SVR_TREATAS     1

// Enable SVROUTL and ISVROTL to convert each other (TreatAs aka. ActivateAs)
#define SVR_CONVERTTO   1

// Enable ISVROTL to operate as in inside-out style in-place object
#define SVR_INSIDEOUT   1

/* Default name used for container of the embedded object. used if
**    container forgets to call IOleObject::SetHostNames
*/
// REVIEW: should load from string resource
#define DEFCONTAINERNAME    "Unknown Document"

/* Default prefix for auto-generated range names. This is used with
**    links to unnamed ranges (pseudo objects).
*/
// REVIEW: should load from string resource
#define DEFRANGENAMEPREFIX  "Range"

// Maximum length of strings accepted through IOleObject::SetHostNames
//      (note: this is rather arbitrary; a better strategy would be to
//             dynamically allocated buffers for these strings.)
#define MAXAPPNAME  80
#define MAXCONTAINERNAME    80

// Menu option in embedding mode
#define IDM_F_UPDATE    1151

/* Types */

/* Codes for CallBack events */
typedef enum tagOLE_NOTIFICATION {
   OLE_ONDATACHANGE,        // 0
   OLE_ONSAVE,              // 1
   OLE_ONRENAME,            // 2
   OLE_ONCLOSE              // 3
} OLE_NOTIFICATION;

/* Codes to indicate mode of storage for an object.
**    Mode of the storage is modified by the IPersistStorage methods:
**      Save, HandsOffStorage, and SaveCompleted.
*/
typedef enum tagSTGMODE {
   STGMODE_NORMAL      = 0,
   STGMODE_NOSCRIBBLE  = 1,
   STGMODE_HANDSOFF    = 2
} STGMODE;

/* Forward type definitions */
typedef struct tagSERVERAPP FAR* LPSERVERAPP;
typedef struct tagSERVERDOC FAR* LPSERVERDOC;
typedef struct tagPSEUDOOBJ FAR* LPPSEUDOOBJ;

typedef struct tagINPLACEDATA {
   OLEMENUGROUPWIDTHS      menuGroupWidths;
   HOLEMENU                hOlemenu;
   HMENU                   hMenuShared;
   LPOLEINPLACESITE        lpSite;
   LPOLEINPLACEUIWINDOW    lpDoc;
   LPOLEINPLACEFRAME       lpFrame;
   OLEINPLACEFRAMEINFO     frameInfo;
   HWND                    hWndFrame;
   BOOL                    fBorderOn;
   RECT                    rcPosRect;
   RECT                    rcClipRect;
} INPLACEDATA, FAR* LPINPLACEDATA;

/*************************************************************************
** class SERVERDOC : OLEDOC
**    SERVERDOC is an extention to the abstract base OLEDOC class.
**    The OLEDOC class defines the fields, methods and interfaces that
**    are common to both server and client implementations. The
**    SERVERDOC class adds the fields, methods and interfaces that are
**    specific to OLE 2.0 Server functionality. There is one instance
**    of SERVERDOC object created per document open in the app. The SDI
**    version of the app supports one SERVERDOC at a time. The MDI
**    version of the app can manage multiple documents at one time.
**    The SERVERDOC class inherits all fields from the OLEDOC class.
**    This inheritance is achieved by including a member variable of
**    type OLEDOC as the first field in the SERVERDOC structure. Thus a
**    pointer to a SERVERDOC object can be cast to be a pointer to a
**    OLEDOC object or an OUTLINEDOC object
*************************************************************************/

typedef struct tagSERVERDOC {
   OLEDOC              m_OleDoc;           // ServerDoc inherits from OleDoc
   ULONG               m_cPseudoObj;       // total count of pseudo obj's
   LPOLECLIENTSITE     m_lpOleClientSite;  // Client associated with the obj
   LPOLEADVISEHOLDER   m_lpOleAdviseHldr;  // helper obj to hold ole advises
   LPDATAADVISEHOLDER  m_lpDataAdviseHldr; // helper obj to hold data advises
   BOOL                m_fNoScribbleMode;  // was IPS::Save called
   BOOL                m_fSaveWithSameAsLoad;  // was IPS::Save called with
                                 // fSameAsLoad==TRUE.
   OLECHAR             m_szContainerApp[MAXAPPNAME];
   OLECHAR             m_szContainerObj[MAXCONTAINERNAME];
   ULONG               m_nNextRangeNo;     // next no. for unnamed range
   LINERANGE           m_lrSrcSelOfCopy;   // src sel if doc created for copy
   BOOL                m_fDataChanged;     // data changed when draw disabled
   BOOL                m_fSizeChanged;     // size changed when draw disabled
   BOOL                m_fSendDataOnStop;  // did data ever change?
#if defined( SVR_TREATAS )
   CLSID               m_clsidTreatAs;     // clsid to pretend to be
   LPOLESTR            m_lpszTreatAsType;  // user type name to pretend to be
#endif  // SVR_TREATAS

#if defined( LATER )
   // REVIEW: is it necessary to register a WildCard Moniker
   DWORD               m_dwWildCardRegROT; // key if wildcard reg'ed in ROT
#endif

#if defined( INPLACE_SVR )
   BOOL                m_fInPlaceActive;
   BOOL                m_fInPlaceVisible;
   BOOL                m_fUIActive;
   HWND                m_hWndParent;
   HWND                m_hWndHatch;
   LPINPLACEDATA       m_lpIPData;
   BOOL                m_fMenuHelpMode;// is F1 pressed in menu, give help

   struct CDocOleInPlaceObjectImpl {
      IOleInPlaceObjectVtbl FAR*  lpVtbl;
      LPSERVERDOC                 lpServerDoc;
      int                         cRef;   // interface specific ref count.
   } m_OleInPlaceObject;

   struct CDocOleInPlaceActiveObjectImpl {
      IOleInPlaceActiveObjectVtbl FAR* lpVtbl;
      LPSERVERDOC                      lpServerDoc;
      int                              cRef;// interface specific ref count.
   } m_OleInPlaceActiveObject;
#endif // INPLACE_SVR

   struct CDocOleObjectImpl {
      IOleObjectVtbl FAR*         lpVtbl;
      LPSERVERDOC                 lpServerDoc;
      int                         cRef;   // interface specific ref count.
   } m_OleObject;

   struct CDocPersistStorageImpl {
      IPersistStorageVtbl FAR*    lpVtbl;
      LPSERVERDOC                 lpServerDoc;
      int                         cRef;   // interface specific ref count.
   } m_PersistStorage;

#if defined( SVR_TREATAS )
   struct CDocStdMarshalInfoImpl {
      IStdMarshalInfoVtbl FAR*    lpVtbl;
      LPSERVERDOC                 lpServerDoc;
      int                         cRef;   // interface specific ref count.
   } m_StdMarshalInfo;
#endif  // SVR_TREATAS

} SERVERDOC;

/* ServerDoc methods (functions) */
BOOL ServerDoc_Init(LPSERVERDOC lpServerDoc, BOOL fDataTransferDoc);
BOOL ServerDoc_InitNewEmbed(LPSERVERDOC lpServerDoc);
void ServerDoc_PseudoObjUnlockDoc(
      LPSERVERDOC         lpServerDoc,
      LPPSEUDOOBJ         lpPseudoObj
);
void ServerDoc_PseudoObjLockDoc(LPSERVERDOC lpServerDoc);
BOOL ServerDoc_PasteFormatFromData(
      LPSERVERDOC             lpServerDoc,
      CLIPFORMAT              cfFormat,
      LPDATAOBJECT            lpSrcDataObj,
      BOOL                    fLocalDataObj,
      BOOL                    fLink
);
BOOL ServerDoc_QueryPasteFromData(
      LPSERVERDOC             lpServerDoc,
      LPDATAOBJECT            lpSrcDataObj,
      BOOL                    fLink
);
HRESULT ServerDoc_GetClassID(LPSERVERDOC lpServerDoc, LPCLSID lpclsid);
void ServerDoc_UpdateMenu(LPSERVERDOC lpServerDoc);
void ServerDoc_RestoreMenu(LPSERVERDOC lpServerDoc);
HRESULT ServerDoc_GetData (
      LPSERVERDOC             lpServerDoc,
      LPFORMATETC             lpformatetc,
      LPSTGMEDIUM             lpMedium
);
HRESULT ServerDoc_GetDataHere (
      LPSERVERDOC             lpServerDoc,
      LPFORMATETC             lpformatetc,
      LPSTGMEDIUM             lpMedium
);
HRESULT ServerDoc_QueryGetData(LPSERVERDOC lpServerDoc,LPFORMATETC lpformatetc);
HRESULT ServerDoc_EnumFormatEtc(
      LPSERVERDOC             lpServerDoc,
      DWORD                   dwDirection,
      LPENUMFORMATETC FAR*    lplpenumFormatEtc
);
HANDLE ServerDoc_GetMetafilePictData(
      LPSERVERDOC             lpServerDoc,
      LPLINERANGE             lplrSel
);
void ServerDoc_SendAdvise(
      LPSERVERDOC     lpServerDoc,
      WORD            wAdvise,
      LPMONIKER       lpmkDoc,
      DWORD           dwAdvf
);
HRESULT ServerDoc_GetObject(
      LPSERVERDOC             lpServerDoc,
      LPOLESTR                   lpszItem,
      REFIID                  riid,
      LPVOID FAR*             lplpvObject
);
HRESULT ServerDoc_IsRunning(LPSERVERDOC lpServerDoc, LPOLESTR lpszItem);
LPMONIKER ServerDoc_GetSelRelMoniker(
      LPSERVERDOC             lpServerDoc,
      LPLINERANGE             lplrSel,
      DWORD                   dwAssign
);
LPMONIKER ServerDoc_GetSelFullMoniker(
      LPSERVERDOC             lpServerDoc,
      LPLINERANGE             lplrSel,
      DWORD                   dwAssign
);

#if defined( INPLACE_SVR )
HRESULT ServerDoc_DoInPlaceActivate(
      LPSERVERDOC     lpServerDoc,
      LONG            lVerb,
      LPMSG           lpmsg,
      LPOLECLIENTSITE lpActiveSite
);
HRESULT ServerDoc_DoInPlaceDeactivate(LPSERVERDOC lpServerDoc);
HRESULT ServerDoc_DoInPlaceHide(LPSERVERDOC lpServerDoc);
BOOL ServerDoc_AllocInPlaceData(LPSERVERDOC lpServerDoc);
void ServerDoc_FreeInPlaceData(LPSERVERDOC lpServerDoc);

HRESULT ServerDoc_AssembleMenus(LPSERVERDOC lpServerDoc);
void    ServerDoc_DisassembleMenus(LPSERVERDOC lpServerDoc);
void ServerDoc_CalcInPlaceWindowPos(
      LPSERVERDOC         lpServerDoc,
      LPRECT              lprcListBox,
      LPRECT              lprcDoc,
      LPSCALEFACTOR       lpscale
);
void ServerDoc_UpdateInPlaceWindowOnExtentChange(LPSERVERDOC lpServerDoc);
void ServerDoc_ResizeInPlaceWindow(
      LPSERVERDOC         lpServerDoc,
      LPCRECT             lprcPosRect,
      LPCRECT             lprcClipRect
);
void ServerDoc_ShadeInPlaceBorder(LPSERVERDOC lpServerDoc, BOOL fShadeOn);
void ServerDoc_SetStatusText(LPSERVERDOC lpServerDoc, LPOLESTR lpszMessage);
LPOLEINPLACEFRAME ServerDoc_GetTopInPlaceFrame(LPSERVERDOC lpServerDoc);
void ServerDoc_GetSharedMenuHandles(
      LPSERVERDOC lpServerDoc,
      HMENU FAR*      lphSharedMenu,
      HOLEMENU FAR*   lphOleMenu
);
void ServerDoc_AddFrameLevelUI(LPSERVERDOC lpServerDoc);
void ServerDoc_AddFrameLevelTools(LPSERVERDOC lpServerDoc);
void ServerDoc_UIActivate (LPSERVERDOC lpServerDoc);

#if defined( USE_FRAMETOOLS )
void ServerDoc_RemoveFrameLevelTools(LPSERVERDOC lpServerDoc);
#endif // USE_FRAMETOOLS

#endif // INPLACE_SVR

/* ServerDoc::IOleObject methods (functions) */
STDMETHODIMP SvrDoc_OleObj_QueryInterface(
      LPOLEOBJECT             lpThis,
      REFIID                  riid,
      LPVOID FAR*             lplpvObj
);
STDMETHODIMP_(ULONG) SvrDoc_OleObj_AddRef(LPOLEOBJECT lpThis);
STDMETHODIMP_(ULONG) SvrDoc_OleObj_Release(LPOLEOBJECT lpThis);
STDMETHODIMP SvrDoc_OleObj_SetClientSite(
      LPOLEOBJECT             lpThis,
      LPOLECLIENTSITE         lpclientSite
);
STDMETHODIMP SvrDoc_OleObj_GetClientSite(
      LPOLEOBJECT             lpThis,
      LPOLECLIENTSITE FAR*    lplpClientSite
);
STDMETHODIMP SvrDoc_OleObj_SetHostNames(
      LPOLEOBJECT             lpThis,
      LPCOLESTR               szContainerApp,
      LPCOLESTR               szContainerObj
);
STDMETHODIMP SvrDoc_OleObj_Close(
      LPOLEOBJECT             lpThis,
      DWORD                   dwSaveOption
);
STDMETHODIMP SvrDoc_OleObj_SetMoniker(
      LPOLEOBJECT             lpThis,
      DWORD                   dwWhichMoniker,
      LPMONIKER               lpmk
);
STDMETHODIMP SvrDoc_OleObj_GetMoniker(
      LPOLEOBJECT             lpThis,
      DWORD                   dwAssign,
      DWORD                   dwWhichMoniker,
      LPMONIKER FAR*          lplpmk
);
STDMETHODIMP SvrDoc_OleObj_InitFromData(
      LPOLEOBJECT             lpThis,
      LPDATAOBJECT            lpDataObject,
      BOOL                    fCreation,
      DWORD                   reserved
);
STDMETHODIMP SvrDoc_OleObj_GetClipboardData(
      LPOLEOBJECT             lpThis,
      DWORD                   reserved,
      LPDATAOBJECT FAR*       lplpDataObject
);
STDMETHODIMP SvrDoc_OleObj_DoVerb(
      LPOLEOBJECT             lpThis,
      LONG                    lVerb,
      LPMSG                   lpmsg,
      LPOLECLIENTSITE         lpActiveSite,
      LONG                    lindex,
      HWND                    hwndParent,
      LPCRECT                 lprcPosRect
);
STDMETHODIMP SvrDoc_OleObj_EnumVerbs(
      LPOLEOBJECT             lpThis,
      LPENUMOLEVERB FAR*      lplpenumOleVerb
);
STDMETHODIMP SvrDoc_OleObj_Update(LPOLEOBJECT lpThis);
STDMETHODIMP SvrDoc_OleObj_IsUpToDate(LPOLEOBJECT lpThis);
STDMETHODIMP SvrDoc_OleObj_GetUserClassID(
      LPOLEOBJECT             lpThis,
      LPCLSID                 lpclsid
);
STDMETHODIMP SvrDoc_OleObj_GetUserType(
      LPOLEOBJECT             lpThis,
      DWORD                   dwFormOfType,
      LPOLESTR FAR*           lpszUserType
);
STDMETHODIMP SvrDoc_OleObj_SetExtent(
      LPOLEOBJECT             lpThis,
      DWORD                   dwDrawAspect,
      LPSIZEL                 lplgrc
);
STDMETHODIMP SvrDoc_OleObj_GetExtent(
      LPOLEOBJECT             lpThis,
      DWORD                   dwDrawAspect,
      LPSIZEL                 lplgrc
);
STDMETHODIMP SvrDoc_OleObj_Advise(
      LPOLEOBJECT             lpThis,
      LPADVISESINK            lpAdvSink,
      LPDWORD                 lpdwConnection
);
STDMETHODIMP SvrDoc_OleObj_Unadvise(LPOLEOBJECT lpThis, DWORD dwConnection);
STDMETHODIMP SvrDoc_OleObj_EnumAdvise(
      LPOLEOBJECT             lpThis,
      LPENUMSTATDATA FAR*     lplpenumAdvise
);
STDMETHODIMP SvrDoc_OleObj_GetMiscStatus(
      LPOLEOBJECT             lpThis,
      DWORD                   dwAspect,
      DWORD FAR*              lpdwStatus
);
STDMETHODIMP SvrDoc_OleObj_SetColorScheme(
      LPOLEOBJECT             lpThis,
      LPLOGPALETTE            lpLogpal
);
STDMETHODIMP SvrDoc_OleObj_LockObject(
      LPOLEOBJECT             lpThis,
      BOOL                    fLock
);

/* ServerDoc::IPersistStorage methods (functions) */
STDMETHODIMP SvrDoc_PStg_QueryInterface(
      LPPERSISTSTORAGE        lpThis,
      REFIID                  riid,
      LPVOID FAR*             lplpvObj
);
STDMETHODIMP_(ULONG) SvrDoc_PStg_AddRef(LPPERSISTSTORAGE lpThis);
STDMETHODIMP_(ULONG) SvrDoc_PStg_Release(LPPERSISTSTORAGE lpThis);
STDMETHODIMP SvrDoc_PStg_GetClassID(
      LPPERSISTSTORAGE        lpThis,
      LPCLSID                 lpClassID
);
STDMETHODIMP  SvrDoc_PStg_IsDirty(LPPERSISTSTORAGE  lpThis);
STDMETHODIMP SvrDoc_PStg_InitNew(
      LPPERSISTSTORAGE        lpThis,
      LPSTORAGE               lpStg
);
STDMETHODIMP SvrDoc_PStg_Load(
      LPPERSISTSTORAGE        lpThis,
      LPSTORAGE               lpStg
);
STDMETHODIMP SvrDoc_PStg_Save(
      LPPERSISTSTORAGE        lpThis,
      LPSTORAGE               lpStg,
      BOOL                    fSameAsLoad
);
STDMETHODIMP SvrDoc_PStg_SaveCompleted(
      LPPERSISTSTORAGE        lpThis,
      LPSTORAGE               lpStgNew
);
STDMETHODIMP SvrDoc_PStg_HandsOffStorage(LPPERSISTSTORAGE lpThis);

#if defined( SVR_TREATAS )

/* ServerDoc::IStdMarshalInfo methods (functions) */
STDMETHODIMP SvrDoc_StdMshl_QueryInterface(
      LPSTDMARSHALINFO        lpThis,
      REFIID                  riid,
      LPVOID FAR*             lplpvObj
);
STDMETHODIMP_(ULONG) SvrDoc_StdMshl_AddRef(LPSTDMARSHALINFO lpThis);
STDMETHODIMP_(ULONG) SvrDoc_StdMshl_Release(LPSTDMARSHALINFO lpThis);
STDMETHODIMP SvrDoc_StdMshl_GetClassForHandler(
      LPSTDMARSHALINFO        lpThis,
      DWORD                   dwDestContext,
      LPVOID                  pvDestContext,
      LPCLSID                 lpClassID
);
#endif  // SVR_TREATAS

/*************************************************************************
** class SERVERAPP : OLEAPP
**    SERVERAPP is an extention to the abstract base OLEAPP class.
**    The OLEAPP class defines the fields, methods and interfaces that
**    are common to both server and client implementations. The
**    SERVERAPP class adds the fields and methods that are specific to
**    OLE 2.0 Server functionality. There is one instance of
**    SERVERAPP object created per running application instance. This
**    object holds many fields that could otherwise be organized as
**    global variables. The SERVERAPP class inherits all fields
**    from the OLEAPP class. This inheritance is achieved by including a
**    member variable of type OLEAPP as the first field in the SERVERAPP
**    structure. OLEAPP inherits from OLEAPP. This inheritance is
**    achieved in the same manner. Thus a pointer to a SERVERAPP object
**    can be cast to be a pointer to an OLEAPP or an OUTLINEAPP object
*************************************************************************/

typedef struct tagSERVERAPP {
   OLEAPP      m_OleApp;       // ServerApp inherits all fields of OleApp

#if defined( INPLACE_SVR )
   HACCEL  m_hAccelIPSvr; // accelerators for server's active object commands
   HACCEL  m_hAccelBaseApp;    // normal accel for non-inplace server mode
   HMENU   m_hMenuEdit;   // handle to Edit menu of the server app
   HMENU   m_hMenuLine;   // handle to Line menu of the server app
   HMENU   m_hMenuName;   // handle to Name menu of the server app
   HMENU   m_hMenuOptions; // handle to Options menu of the server app
   HMENU   m_hMenuDebug;       // handle to Debug menu of the server app
   HMENU   m_hMenuHelp;   // handle to Help menu of the server app
   LPINPLACEDATA   m_lpIPData;
#endif

} SERVERAPP;

/* ServerApp methods (functions) */
BOOL ServerApp_InitInstance(
      LPSERVERAPP             lpServerApp,
      HINSTANCE               hInst,
      int                     nCmdShow
);
BOOL ServerApp_InitVtbls (LPSERVERAPP lpServerApp);

/*************************************************************************
** class SERVERNAME : OUTLINENAME
**    SERVERNAME class is an extension to the OUTLINENAME base class that
**    adds functionallity required to support linking to ranges (pseudo
**    objects). Pseudo objects are used to allow linking to a range
**    (sub-selection) of a SERVERDOC document. The base class OUTLINENAME
**    stores a particular named selection in the document. The
**    NAMETABLE class holds all of the names defined in a particular
**    document. Each OUTLINENAME object has a string as its key and a
**    starting line index and an ending line index for the named range.
**    The SERVERNAME class, also, stores a pointer to a PSEUDOOBJ if one
**    has been allocated that corresponds to the named selection.
**    The SERVERNAME class inherits all fields from the OUTLINENAME class.
**    This inheritance is achieved by including a member variable of
**    type OUTLINENAME as the first field in the SERVERNAME
**    structure. Thus a pointer to an SERVERNAME object can be cast to be
**    a pointer to a OUTLINENAME object.
*************************************************************************/

typedef struct tagSERVERNAME {
   OUTLINENAME     m_Name;         // ServerName inherits all fields of Name
   LPPSEUDOOBJ m_lpPseudoObj;  // ptr to pseudo object if allocated
} SERVERNAME, FAR* LPSERVERNAME;

/* ServerName methods (functions) */
void ServerName_SetSel(
      LPSERVERNAME            lpServerName,
      LPLINERANGE             lplrSel,
      BOOL                    fRangeModified
);
void ServerName_SendPendingAdvises(LPSERVERNAME lpServerName);
LPPSEUDOOBJ ServerName_GetPseudoObj(
      LPSERVERNAME            lpServerName,
      LPSERVERDOC             lpServerDoc
);
void ServerName_ClosePseudoObj(LPSERVERNAME lpServerName);

/*************************************************************************
** class PSEUDOOBJ
**    The PSEUDOOBJ (pseudo object) is a concrete class. A pseudo object
**    is created when a link is made to a range of lines within an
**    SERVERDOC document. A pseudo object is dependent on the existance
**    of the SERVERDOC which represents the whole document.
*************************************************************************/

typedef struct tagPSEUDOOBJ {
   ULONG               m_cRef;             // total ref count for obj
   BOOL                m_fObjIsClosing;    // flag to guard recursive close
   LPSERVERNAME        m_lpName;           // named range for this pseudo obj
   LPSERVERDOC         m_lpDoc;            // ptr to whole document
   LPOLEADVISEHOLDER   m_lpOleAdviseHldr;  // helper obj to hold ole advises
   LPDATAADVISEHOLDER  m_lpDataAdviseHldr; // helper obj to hold data advises
   BOOL                m_fDataChanged;     // data changed when draw disabled

   struct CPseudoObjUnknownImpl {
      IUnknownVtbl FAR*       lpVtbl;
      LPPSEUDOOBJ             lpPseudoObj;
      int                     cRef;   // interface specific ref count.
   } m_Unknown;

   struct CPseudoObjOleObjectImpl {
      IOleObjectVtbl FAR*     lpVtbl;
      LPPSEUDOOBJ             lpPseudoObj;
      int                     cRef;   // interface specific ref count.
   } m_OleObject;

   struct CPseudoObjDataObjectImpl {
      IDataObjectVtbl FAR*    lpVtbl;
      LPPSEUDOOBJ             lpPseudoObj;
      int                     cRef;   // interface specific ref count.
   } m_DataObject;

} PSEUDOOBJ;

/* PseudoObj methods (functions) */
void PseudoObj_Init(
      LPPSEUDOOBJ             lpPseudoObj,
      LPSERVERNAME            lpServerName,
      LPSERVERDOC             lpServerDoc
);
ULONG PseudoObj_AddRef(LPPSEUDOOBJ lpPseudoObj);
ULONG PseudoObj_Release(LPPSEUDOOBJ lpPseudoObj);
HRESULT PseudoObj_QueryInterface(
      LPPSEUDOOBJ         lpPseudoObj,
      REFIID              riid,
      LPVOID FAR*         lplpUnk
);
BOOL PseudoObj_Close(LPPSEUDOOBJ lpPseudoObj);
void PseudoObj_Destroy(LPPSEUDOOBJ lpPseudoObj);
void PseudoObj_GetSel(LPPSEUDOOBJ lpPseudoObj, LPLINERANGE lplrSel);
void PseudoObj_GetExtent(LPPSEUDOOBJ lpPseudoObj, LPSIZEL lpsizel);
void PseudoObj_GetExtent(LPPSEUDOOBJ lpPseudoObj, LPSIZEL lpsizel);
void PseudoObj_SendAdvise(
      LPPSEUDOOBJ lpPseudoObj,
      WORD        wAdvise,
      LPMONIKER   lpmkObj,
      DWORD       dwAdvf
);
LPMONIKER PseudoObj_GetFullMoniker(LPPSEUDOOBJ lpPseudoObj, LPMONIKER lpmkDoc);

/* PseudoObj::IUnknown methods (functions) */
STDMETHODIMP PseudoObj_Unk_QueryInterface(
      LPUNKNOWN         lpThis,
      REFIID            riid,
      LPVOID FAR*       lplpvObj
);
STDMETHODIMP_(ULONG) PseudoObj_Unk_AddRef(LPUNKNOWN lpThis);
STDMETHODIMP_(ULONG) PseudoObj_Unk_Release (LPUNKNOWN lpThis);

/* PseudoObj::IOleObject methods (functions) */
STDMETHODIMP PseudoObj_OleObj_QueryInterface(
      LPOLEOBJECT     lpThis,
      REFIID          riid,
      LPVOID FAR*     lplpvObj
);
STDMETHODIMP_(ULONG) PseudoObj_OleObj_AddRef(LPOLEOBJECT lpThis);
STDMETHODIMP_(ULONG) PseudoObj_OleObj_Release(LPOLEOBJECT lpThis);
STDMETHODIMP PseudoObj_OleObj_SetClientSite(
      LPOLEOBJECT         lpThis,
      LPOLECLIENTSITE     lpClientSite
);
STDMETHODIMP PseudoObj_OleObj_GetClientSite(
      LPOLEOBJECT             lpThis,
      LPOLECLIENTSITE FAR*    lplpClientSite
);
STDMETHODIMP PseudoObj_OleObj_SetHostNames(
      LPOLEOBJECT             lpThis,
      LPCOLESTR               szContainerApp,
      LPCOLESTR               szContainerObj
);
STDMETHODIMP PseudoObj_OleObj_Close(
      LPOLEOBJECT             lpThis,
      DWORD                   dwSaveOption
);
STDMETHODIMP PseudoObj_OleObj_SetMoniker(
      LPOLEOBJECT lpThis,
      DWORD       dwWhichMoniker,
      LPMONIKER   lpmk
);
STDMETHODIMP PseudoObj_OleObj_GetMoniker(
      LPOLEOBJECT     lpThis,
      DWORD           dwAssign,
      DWORD           dwWhichMoniker,
      LPMONIKER FAR*  lplpmk
);
STDMETHODIMP PseudoObj_OleObj_InitFromData(
      LPOLEOBJECT             lpThis,
      LPDATAOBJECT            lpDataObject,
      BOOL                    fCreation,
      DWORD                   reserved
);
STDMETHODIMP PseudoObj_OleObj_GetClipboardData(
      LPOLEOBJECT             lpThis,
      DWORD                   reserved,
      LPDATAOBJECT FAR*       lplpDataObject
);
STDMETHODIMP PseudoObj_OleObj_DoVerb(
      LPOLEOBJECT             lpThis,
      LONG                    lVerb,
      LPMSG                   lpmsg,
      LPOLECLIENTSITE         lpActiveSite,
      LONG                    lindex,
      HWND                    hwndParent,
      LPCRECT                 lprcPosRect
);
STDMETHODIMP PseudoObj_OleObj_EnumVerbs(
      LPOLEOBJECT         lpThis,
      LPENUMOLEVERB FAR*  lplpenumOleVerb
);
STDMETHODIMP PseudoObj_OleObj_Update(LPOLEOBJECT lpThis);
STDMETHODIMP PseudoObj_OleObj_IsUpToDate(LPOLEOBJECT lpThis);
STDMETHODIMP PseudoObj_OleObj_GetUserClassID(
      LPOLEOBJECT             lpThis,
      LPCLSID                 lpclsid
);
STDMETHODIMP PseudoObj_OleObj_GetUserType(
      LPOLEOBJECT             lpThis,
      DWORD                   dwFormOfType,
      LPOLESTR FAR*           lpszUserType
);
STDMETHODIMP PseudoObj_OleObj_SetExtent(
      LPOLEOBJECT             lpThis,
      DWORD                   dwDrawAspect,
      LPSIZEL                 lplgrc
);
STDMETHODIMP PseudoObj_OleObj_GetExtent(
      LPOLEOBJECT             lpThis,
      DWORD                   dwDrawAspect,
      LPSIZEL                 lplgrc
);
STDMETHODIMP PseudoObj_OleObj_Advise(
      LPOLEOBJECT lpThis,
      LPADVISESINK lpAdvSink,
      LPDWORD lpdwConnection
);
STDMETHODIMP PseudoObj_OleObj_Unadvise(LPOLEOBJECT lpThis,DWORD dwConnection);
STDMETHODIMP PseudoObj_OleObj_EnumAdvise(
      LPOLEOBJECT lpThis,
      LPENUMSTATDATA FAR* lplpenumAdvise
);
STDMETHODIMP PseudoObj_OleObj_GetMiscStatus(
      LPOLEOBJECT             lpThis,
      DWORD                   dwAspect,
      DWORD FAR*              lpdwStatus
);
STDMETHODIMP PseudoObj_OleObj_SetColorScheme(
      LPOLEOBJECT             lpThis,
      LPLOGPALETTE            lpLogpal
);
STDMETHODIMP PseudoObj_OleObj_LockObject(
      LPOLEOBJECT             lpThis,
      BOOL                    fLock
);

/* PseudoObj::IDataObject methods (functions) */
STDMETHODIMP PseudoObj_DataObj_QueryInterface (
      LPDATAOBJECT      lpThis,
      REFIID            riid,
      LPVOID FAR*       lplpvObj
);
STDMETHODIMP_(ULONG) PseudoObj_DataObj_AddRef(LPDATAOBJECT lpThis);
STDMETHODIMP_(ULONG) PseudoObj_DataObj_Release (LPDATAOBJECT lpThis);
STDMETHODIMP PseudoObj_DataObj_GetData (
      LPDATAOBJECT    lpThis,
      LPFORMATETC     lpformatetc,
      LPSTGMEDIUM     lpMedium
);
STDMETHODIMP PseudoObj_DataObj_GetDataHere (
      LPDATAOBJECT    lpThis,
      LPFORMATETC     lpformatetc,
      LPSTGMEDIUM     lpMedium
);
STDMETHODIMP PseudoObj_DataObj_QueryGetData (
      LPDATAOBJECT    lpThis,
      LPFORMATETC     lpformatetc
);
STDMETHODIMP PseudoObj_DataObj_GetCanonicalFormatEtc (
      LPDATAOBJECT    lpThis,
      LPFORMATETC     lpformatetc,
      LPFORMATETC     lpformatetcOut
);
STDMETHODIMP PseudoObj_DataObj_SetData (
      LPDATAOBJECT    lpThis,
      LPFORMATETC     lpformatetc,
      LPSTGMEDIUM     lpmedium,
      BOOL            fRelease
);
STDMETHODIMP PseudoObj_DataObj_EnumFormatEtc(
      LPDATAOBJECT            lpThis,
      DWORD                   dwDirection,
      LPENUMFORMATETC FAR*    lplpenumFormatEtc
);
STDMETHODIMP PseudoObj_DataObj_DAdvise(
      LPDATAOBJECT    lpThis,
      FORMATETC FAR*  lpFormatetc,
      DWORD           advf,
      LPADVISESINK    lpAdvSink,
      DWORD FAR*      lpdwConnection
);
STDMETHODIMP PseudoObj_DataObj_DUnadvise(LPDATAOBJECT lpThis, DWORD dwConnection);
STDMETHODIMP PseudoObj_DataObj_EnumAdvise(
      LPDATAOBJECT lpThis,
      LPENUMSTATDATA FAR* lplpenumAdvise
);

/*************************************************************************
** class SERVERNAMETABLE : OUTLINENAMETABLE
**    SERVERNAMETABLE class is an extension to the OUTLINENAMETABLE
**    base class that adds functionallity required to support linking
**    to ranges (pseudo objects). The name table manages the table of
**    named selections in the document. Each name table entry has a
**    string as its key and a starting line index and an ending line
**    index for the named range. The SERVERNAMETABLE entries, in

**    addition, maintain a pointer to a PSEUDOOBJ pseudo object if one
**    has been already allocated. There is always one instance of
**    SERVERNAMETABLE for each SERVERDOC object created.
**    The SERVERNAME class inherits all fields from the NAME class.
**    This inheritance is achieved by including a member variable of
**    type NAME as the first field in the SERVERNAME
**    structure. Thus a pointer to an SERVERNAME object can be cast to be
**    a pointer to a NAME object.
*************************************************************************/

typedef struct tagSERVERNAMETABLE {
   OUTLINENAMETABLE    m_NameTable;    // we inherit from OUTLINENAMETABLE

   // ServerNameTable does NOT add any fields

} SERVERNAMETABLE, FAR* LPSERVERNAMETABLE;

/* ServerNameTable methods (functions) */
void ServerNameTable_EditLineUpdate(
      LPSERVERNAMETABLE       lpServerNameTable,
      int                     nEditIndex
);
void ServerNameTable_InformAllPseudoObjectsDocRenamed(
      LPSERVERNAMETABLE       lpServerNameTable,
      LPMONIKER               lpmkDoc
);
void ServerNameTable_InformAllPseudoObjectsDocSaved(
      LPSERVERNAMETABLE       lpServerNameTable,
      LPMONIKER               lpmkDoc
);
void ServerNameTable_SendPendingAdvises(LPSERVERNAMETABLE lpServerNameTable);
LPPSEUDOOBJ ServerNameTable_GetPseudoObj(
      LPSERVERNAMETABLE       lpServerNameTable,
      LPOLESTR                lpszItem,
      LPSERVERDOC             lpServerDoc
);
void ServerNameTable_CloseAllPseudoObjs(LPSERVERNAMETABLE lpServerNameTable);

#if defined( INPLACE_SVR)

/* ServerDoc::IOleInPlaceObject methods (functions) */

STDMETHODIMP SvrDoc_IPObj_QueryInterface(
      LPOLEINPLACEOBJECT  lpThis,
      REFIID              riid,
      LPVOID FAR *        lplpvObj
);
STDMETHODIMP_(ULONG) SvrDoc_IPObj_AddRef(LPOLEINPLACEOBJECT lpThis);
STDMETHODIMP_(ULONG) SvrDoc_IPObj_Release(LPOLEINPLACEOBJECT lpThis);
STDMETHODIMP SvrDoc_IPObj_GetWindow(
      LPOLEINPLACEOBJECT  lpThis,
      HWND FAR*           lphwnd
);
STDMETHODIMP SvrDoc_IPObj_ContextSensitiveHelp(
      LPOLEINPLACEOBJECT  lpThis,
      BOOL                fEnable
);
STDMETHODIMP SvrDoc_IPObj_InPlaceDeactivate(LPOLEINPLACEOBJECT lpThis);
STDMETHODIMP SvrDoc_IPObj_UIDeactivate(LPOLEINPLACEOBJECT lpThis);
STDMETHODIMP SvrDoc_IPObj_SetObjectRects(
      LPOLEINPLACEOBJECT  lpThis,
      LPCRECT             lprcPosRect,
      LPCRECT             lprcClipRect
);
STDMETHODIMP SvrDoc_IPObj_ReactivateAndUndo(LPOLEINPLACEOBJECT lpThis);

/* ServerDoc::IOleInPlaceActiveObject methods (functions) */

STDMETHODIMP SvrDoc_IPActiveObj_QueryInterface(
      LPOLEINPLACEACTIVEOBJECT    lpThis,
      REFIID                      riidReq,
      LPVOID FAR *                lplpUnk
);
STDMETHODIMP_(ULONG) SvrDoc_IPActiveObj_AddRef(
      LPOLEINPLACEACTIVEOBJECT lpThis
);
STDMETHODIMP_(ULONG) SvrDoc_IPActiveObj_Release(
      LPOLEINPLACEACTIVEOBJECT lpThis
);
STDMETHODIMP SvrDoc_IPActiveObj_GetWindow(
      LPOLEINPLACEACTIVEOBJECT    lpThis,
      HWND FAR*                   lphwnd
);
STDMETHODIMP SvrDoc_IPActiveObj_ContextSensitiveHelp(
      LPOLEINPLACEACTIVEOBJECT    lpThis,
      BOOL                        fEnable
);
STDMETHODIMP SvrDoc_IPActiveObj_TranslateAccelerator(
      LPOLEINPLACEACTIVEOBJECT    lpThis,
      LPMSG                       lpmsg
);
STDMETHODIMP SvrDoc_IPActiveObj_OnFrameWindowActivate(
      LPOLEINPLACEACTIVEOBJECT    lpThis,
      BOOL                        fActivate
);
STDMETHODIMP SvrDoc_IPActiveObj_OnDocWindowActivate(
      LPOLEINPLACEACTIVEOBJECT    lpThis,
      BOOL                        fActivate
);
STDMETHODIMP SvrDoc_IPActiveObj_ResizeBorder(
      LPOLEINPLACEACTIVEOBJECT    lpThis,
      LPCRECT                     lprectBorder,
      LPOLEINPLACEUIWINDOW        lpIPUiWnd,
      BOOL                        fFrameWindow
);
STDMETHODIMP SvrDoc_IPActiveObj_EnableModeless(
      LPOLEINPLACEACTIVEOBJECT    lpThis,
      BOOL                        fEnable
);

#endif // INPLACE_SVR

#endif // _SVROUTL_H_


SVRPSOBJ.C

/*************************************************************************
**
**    OLE 2 Server Sample Code
**
**    svrpsobj.c
**
**    This file contains all PseudoObj methods and related support
**    functions.
**
**    (c) Copyright Microsoft Corp. 1992 - 1997 All Rights Reserved
**
*************************************************************************/

#include "outline.h"

OLEDBGDATA

extern LPOUTLINEAPP             g_lpApp;
extern IUnknownVtbl             g_PseudoObj_UnknownVtbl;
extern IOleObjectVtbl           g_PseudoObj_OleObjectVtbl;
extern IDataObjectVtbl          g_PseudoObj_DataObjectVtbl;

/* PseudoObj_Init
** --------------
**  Initialize fields in a newly constructed PseudoObj.
**  NOTE: ref cnt of PseudoObj initialized to 0
*/
void PseudoObj_Init(
      LPPSEUDOOBJ             lpPseudoObj,
      LPSERVERNAME            lpServerName,
      LPSERVERDOC             lpServerDoc
)
{
   OleDbgOut2("++PseudoObj Created\r\n");

   lpPseudoObj->m_cRef             = 0;
   lpPseudoObj->m_lpName           = lpServerName;
   lpPseudoObj->m_lpDoc            = lpServerDoc;
   lpPseudoObj->m_lpOleAdviseHldr  = NULL;
   lpPseudoObj->m_lpDataAdviseHldr = NULL;
   lpPseudoObj->m_fObjIsClosing    = FALSE;

   INIT_INTERFACEIMPL(
         &lpPseudoObj->m_Unknown,
         &g_PseudoObj_UnknownVtbl,
         lpPseudoObj
   );

   INIT_INTERFACEIMPL(
         &lpPseudoObj->m_OleObject,
         &g_PseudoObj_OleObjectVtbl,
         lpPseudoObj
   );

   INIT_INTERFACEIMPL(
         &lpPseudoObj->m_DataObject,
         &g_PseudoObj_DataObjectVtbl,
         lpPseudoObj
   );

   /* NOTE: Increment the refcnt of the Doc on behalf of the
   **    PseudoObj. the Document should not shut down unless all
   **    pseudo objects are closed. when a pseudo object is destroyed,
   **    it calls ServerDoc_PseudoObjUnlockDoc to release this hold on
   **    the document.
   */
   ServerDoc_PseudoObjLockDoc(lpServerDoc);
}

/* PseudoObj_AddRef
** ----------------
**
**  increment the ref count of the PseudoObj object.
**
**    Returns the new ref count on the object
*/
ULONG PseudoObj_AddRef(LPPSEUDOOBJ lpPseudoObj)
{
   ++lpPseudoObj->m_cRef;

#if defined( _DEBUG )
   OleDbgOutRefCnt4(
         "PseudoObj_AddRef: cRef++\r\n",
         lpPseudoObj,
         lpPseudoObj->m_cRef
   );
#endif
   return lpPseudoObj->m_cRef;
}

/* PseudoObj_Release
** -----------------
**
**  decrement the ref count of the PseudoObj object.
**    if the ref count goes to 0, then the PseudoObj is destroyed.
**
**    Returns the remaining ref count on the object
*/
ULONG PseudoObj_Release(LPPSEUDOOBJ lpPseudoObj)
{
   ULONG cRef;

   /*********************************************************************
   ** NOTE: when the obj refcnt == 0, then destroy the object.     **
   **     otherwise the object is still in use.                        **
   *********************************************************************/

   cRef = --lpPseudoObj->m_cRef;

#if defined( _DEBUG )
   OleDbgAssertSz(lpPseudoObj->m_cRef >= 0,"Release called with cRef == 0");

   OleDbgOutRefCnt4(
         "PseudoObj_Release: cRef--\r\n", lpPseudoObj,cRef);
#endif

   if (cRef == 0)
      PseudoObj_Destroy(lpPseudoObj);

   return cRef;
}

/* PseudoObj_QueryInterface
** ------------------------
**
** Retrieve a pointer to an interface on the PseudoObj object.
**
**    Returns S_OK if interface is successfully retrieved.
**            E_NOINTERFACE if the interface is not supported
*/
HRESULT PseudoObj_QueryInterface(
      LPPSEUDOOBJ         lpPseudoObj,
      REFIID              riid,
      LPVOID FAR*         lplpvObj
)
{
   SCODE sc = E_NOINTERFACE;

   /* NOTE: we must make sure to set all out ptr parameters to NULL. */
   *lplpvObj = NULL;

   if (IsEqualIID(riid, &IID_IUnknown)) {
      OleDbgOut4("PseudoObj_QueryInterface: IUnknown* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpPseudoObj->m_Unknown;
      PseudoObj_AddRef(lpPseudoObj);
      sc = S_OK;
   }
   else if (IsEqualIID(riid, &IID_IOleObject)) {
      OleDbgOut4("PseudoObj_QueryInterface: IOleObject* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpPseudoObj->m_OleObject;
      PseudoObj_AddRef(lpPseudoObj);
      sc = S_OK;
   }
   else if (IsEqualIID(riid, &IID_IDataObject)) {
      OleDbgOut4("PseudoObj_QueryInterface: IDataObject* RETURNED\r\n");

      *lplpvObj = (LPVOID) &lpPseudoObj->m_DataObject;
      PseudoObj_AddRef(lpPseudoObj);
      sc = S_OK;
   }

   OleDbgQueryInterfaceMethod(*lplpvObj);

   return sc;
}

/* PseudoObj_Close
 * ---------------
 *
 *  Close the pseudo object. Force all external connections to close
 *      down. This causes link clients to release this PseudoObj. when
 *      the refcount actually reaches 0, then the PseudoObj will be
 *      destroyed.
 *
 *  Returns:
 *      FALSE -- user canceled the closing of the doc.
 *      TRUE -- the doc was successfully closed
 */

BOOL PseudoObj_Close(LPPSEUDOOBJ lpPseudoObj)
{
   LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpPseudoObj->m_lpDoc;
   LPSERVERNAME lpServerName = (LPSERVERNAME)lpPseudoObj->m_lpName;
   LPOLEAPP lpOleApp = (LPOLEAPP)g_lpApp;
   LPOLEDOC lpOleDoc = (LPOLEDOC)lpServerDoc;
   BOOL fStatus = TRUE;

   if (lpPseudoObj->m_fObjIsClosing)
      return TRUE;    // Closing is already in progress

   lpPseudoObj->m_fObjIsClosing = TRUE;   // guard against recursive call

   OLEDBG_BEGIN3("PseudoObj_Close\r\n")

   /* NOTE: in order to have a stable App, Doc, AND pseudo object
   **    during the process of closing, we intially AddRef the App,
   **    Doc, and PseudoObj ref counts and later Release them. These
   **    initial AddRefs are artificial; they are simply done to
   **    guarantee that these objects do not get destroyed until the
   **    end of this routine.
   */
   OleApp_AddRef(lpOleApp);
   OleDoc_AddRef(lpOleDoc);
   PseudoObj_AddRef(lpPseudoObj);

   if (lpPseudoObj->m_lpDataAdviseHldr) {
      /* NOTE: send last OnDataChange notification to clients
      **    that have registered for data notifications when object
      **    stops running (ADVF_DATAONSTOP)
      */
      PseudoObj_SendAdvise(
            lpPseudoObj,
            OLE_ONDATACHANGE,
            NULL,   /* lpmkObj -- not relevant here */
            ADVF_DATAONSTOP
      );

      /* NOTE: we just sent the last data notification that we
      **    need to send; release our DataAdviseHolder. we SHOULD be
      **    the only one using it.
      */
      OleStdVerifyRelease(
            (LPUNKNOWN)lpPseudoObj->m_lpDataAdviseHldr,
            OLESTR("DataAdviseHldr not released properly")
      );
      lpPseudoObj->m_lpDataAdviseHldr = NULL;
   }

   if (lpPseudoObj->m_lpOleAdviseHldr) {
      // NOTE: inform all of our linking clients that we are closing.
      PseudoObj_SendAdvise(
            lpPseudoObj,
            OLE_ONCLOSE,
            NULL,   /* lpmkObj -- not relevant here */
            0       /* advf -- not relevant here */
      );

      /* NOTE: OnClose is the last notification that we need to
      **    send; release our OleAdviseHolder. we SHOULD be the only
      **    one using it. this will make our destructor realize that
      **    OnClose notification has already been sent.
      */
      OleStdVerifyRelease(
            (LPUNKNOWN)lpPseudoObj->m_lpOleAdviseHldr,
            OLESTR("OleAdviseHldr not released properly")
      );
      lpPseudoObj->m_lpOleAdviseHldr = NULL;
   }

   /* NOTE: this call forces all external connections to our
   **    object to close down and therefore guarantees that we receive
   **    all releases associated with those external connections.
   */
   OLEDBG_BEGIN2("CoDisconnectObject called\r\n")
   CoDisconnectObject((LPUNKNOWN)&lpPseudoObj->m_Unknown, 0);
   OLEDBG_END2

   PseudoObj_Release(lpPseudoObj);     // release artificial AddRef above
   OleDoc_Release(lpOleDoc);           // release artificial AddRef above
   OleApp_Release(lpOleApp);           // release artificial AddRef above

   OLEDBG_END3
   return fStatus;
}

/* PseudoObj_Destroy
** -----------------
**    Destroy (Free) the memory used by a PseudoObj structure.
**    This function is called when the ref count of the PseudoObj goes
**    to zero. the ref cnt goes to zero after PseudoObj_Delete forces
**    the OleObject to unload and release its pointers to the
**    PseudoObj IOleClientSite and IAdviseSink interfaces.
*/

void PseudoObj_Destroy(LPPSEUDOOBJ lpPseudoObj)
{
   LPSERVERDOC lpServerDoc = lpPseudoObj->m_lpDoc;
   LPOLEAPP    lpOleApp = (LPOLEAPP)g_lpApp;
   LPOLEDOC    lpOleDoc = (LPOLEDOC)lpServerDoc;

   OLEDBG_BEGIN3("PseudoObj_Destroy\r\n")

   /* NOTE: in order to have a stable App, Doc, AND pseudo object
   **    during the process of closing, we intially AddRef the App,
   **    Doc ref counts and later Release them. These
   **    initial AddRefs are artificial; they are simply done to
   **    guarantee that these objects do not get destroyed until the
   **    end of this routine.
   */
   OleApp_AddRef(lpOleApp);
   OleDoc_AddRef(lpOleDoc);

   /******************************************************************
   ** NOTE: we no longer need the advise and enum holder objects,
   **    so release them.
   ******************************************************************/

   if (lpPseudoObj->m_lpDataAdviseHldr) {
      /* release DataAdviseHldr; we SHOULD be the only one using it. */
      OleStdVerifyRelease(
            (LPUNKNOWN)lpPseudoObj->m_lpDataAdviseHldr,
            OLESTR("DataAdviseHldr not released properly")
         );
      lpPseudoObj->m_lpDataAdviseHldr = NULL;
   }

   if (lpPseudoObj->m_lpOleAdviseHldr) {
      /* release OleAdviseHldr; we SHOULD be the only one using it. */
      OleStdVerifyRelease(
            (LPUNKNOWN)lpPseudoObj->m_lpOleAdviseHldr,
            OLESTR("OleAdviseHldr not released properly")
         );
      lpPseudoObj->m_lpOleAdviseHldr = NULL;
   }

   /* forget the pointer to destroyed PseudoObj in NameTable */
   if (lpPseudoObj->m_lpName)
      lpPseudoObj->m_lpName->m_lpPseudoObj = NULL;

   /* NOTE: release the lock on the Doc held on behalf of the
   **    PseudoObj. the Document should not shut down unless all
   **    pseudo objects are closed. when a pseudo object is first
   **    created, it calls ServerDoc_PseudoObjLockDoc to guarantee
   **    that the document stays alive (called from PseudoObj_Init).
   */
   ServerDoc_PseudoObjUnlockDoc(lpServerDoc, lpPseudoObj);

   Delete(lpPseudoObj);        // Free the memory for the structure itself

   OleDoc_Release(lpOleDoc);       // release artificial AddRef above
   OleApp_Release(lpOleApp);       // release artificial AddRef above

   OLEDBG_END3
}

/* PseudoObj_GetSel
** ----------------
**    Return the line range for the pseudo object
*/
void PseudoObj_GetSel(LPPSEUDOOBJ lpPseudoObj, LPLINERANGE lplrSel)
{
   LPOUTLINENAME lpOutlineName = (LPOUTLINENAME)lpPseudoObj->m_lpName;
   lplrSel->m_nStartLine = lpOutlineName->m_nStartLine;
   lplrSel->m_nEndLine = lpOutlineName->m_nEndLine;
}

/* PseudoObj_GetExtent
 * -------------------
 *
 *      Get the extent (width, height) of the entire document.
 */
void PseudoObj_GetExtent(LPPSEUDOOBJ lpPseudoObj, LPSIZEL lpsizel)
{
   LPOLEDOC lpOleDoc = (LPOLEDOC)lpPseudoObj->m_lpDoc;
   LPLINELIST lpLL = (LPLINELIST)&((LPOUTLINEDOC)lpOleDoc)->m_LineList;
   LINERANGE lrSel;

   PseudoObj_GetSel(lpPseudoObj, (LPLINERANGE)&lrSel);

   LineList_CalcSelExtentInHimetric(lpLL, (LPLINERANGE)&lrSel, lpsizel);
}

/* PseudoObj_SendAdvise
 * --------------------
 *
 * This function sends an advise notification on behalf of a specific
 *  doc object to all its clients.
 */
void PseudoObj_SendAdvise(
      LPPSEUDOOBJ lpPseudoObj,
      WORD        wAdvise,
      LPMONIKER   lpmkObj,
      DWORD       dwAdvf
)
{
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpPseudoObj->m_lpDoc;

   // artificial AddRef in case object is destroyed during call
   PseudoObj_AddRef(lpPseudoObj);

   switch (wAdvise) {

      case OLE_ONDATACHANGE:

         // inform clients that the data of the object has changed

         if (lpOutlineDoc->m_nDisableDraw == 0) {
            /* drawing is currently enabled. inform clients that
            **    the data of the object has changed
            */

            lpPseudoObj->m_fDataChanged = FALSE;
            if (lpPseudoObj->m_lpDataAdviseHldr) {

               OLEDBG_BEGIN2("IDataAdviseHolder::SendOnDataChange called\r\n");
               lpPseudoObj->m_lpDataAdviseHldr->lpVtbl->SendOnDataChange(
                     lpPseudoObj->m_lpDataAdviseHldr,
                     (LPDATAOBJECT)&lpPseudoObj->m_DataObject,
                     0,
                     dwAdvf
               );
               OLEDBG_END2
            }

         } else {
            /* drawing is currently disabled. do not send
            **    notifications until drawing is re-enabled.
            */
            lpPseudoObj->m_fDataChanged = TRUE;
         }
         break;

      case OLE_ONCLOSE:

         // inform clients that the object is shutting down

         if (lpPseudoObj->m_lpOleAdviseHldr) {

            OLEDBG_BEGIN2("IOleAdviseHolder::SendOnClose called\r\n");
            lpPseudoObj->m_lpOleAdviseHldr->lpVtbl->SendOnClose(
                  lpPseudoObj->m_lpOleAdviseHldr
            );
            OLEDBG_END2
         }
         break;

      case OLE_ONSAVE:

         // inform clients that the object has been saved

         if (lpPseudoObj->m_lpOleAdviseHldr) {

            OLEDBG_BEGIN2("IOleAdviseHolder::SendOnClose called\r\n");
            lpPseudoObj->m_lpOleAdviseHldr->lpVtbl->SendOnSave(
                  lpPseudoObj->m_lpOleAdviseHldr
            );
            OLEDBG_END2
         }
         break;

      case OLE_ONRENAME:

         // inform clients that the object's name has changed
         if (lpmkObj && lpPseudoObj->m_lpOleAdviseHldr) {

            OLEDBG_BEGIN2("IOleAdviseHolder::SendOnRename called\r\n");
            if (lpPseudoObj->m_lpOleAdviseHldr)
               lpPseudoObj->m_lpOleAdviseHldr->lpVtbl->SendOnRename(
                     lpPseudoObj->m_lpOleAdviseHldr,
                     lpmkObj
               );
            OLEDBG_END2
         }
         break;
   }
   // release artificial AddRef
   PseudoObj_Release(lpPseudoObj);
}

/* PseudoObj_GetFullMoniker
 * ------------------------
 *
 * Returns the Full, absolute Moniker which identifies this pseudo object.
 */
LPMONIKER PseudoObj_GetFullMoniker(LPPSEUDOOBJ lpPseudoObj, LPMONIKER lpmkDoc)
{
   LPOUTLINENAME lpOutlineName = (LPOUTLINENAME)lpPseudoObj->m_lpName;
   LPMONIKER lpmkItem = NULL;
   LPMONIKER lpmkPseudoObj = NULL;

   if (lpmkDoc != NULL) {
      CreateItemMoniker(OLESTR("\\"), lpOutlineName->m_szName, &lpmkItem);

      /* NOTE: create an absolute moniker which identifies the
      **    pseudo object. this moniker is created as a composite of
      **    the absolute moniker for the entire document appended
      **    with an item moniker which identifies the selection of
      **    the pseudo object relative to the document.
      */
      CreateGenericComposite(lpmkDoc, lpmkItem, &lpmkPseudoObj);

      if (lpmkItem)
         OleStdRelease((LPUNKNOWN)lpmkItem);

      return lpmkPseudoObj;
   } else {
      return NULL;
   }
}

/*************************************************************************
** PseudoObj::IUnknown interface implementation
*************************************************************************/

STDMETHODIMP PseudoObj_Unk_QueryInterface(
      LPUNKNOWN         lpThis,
      REFIID            riid,
      LPVOID FAR*       lplpvObj
)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjUnknownImpl FAR*)lpThis)->lpPseudoObj;

   return PseudoObj_QueryInterface(lpPseudoObj, riid, lplpvObj);
}

STDMETHODIMP_(ULONG) PseudoObj_Unk_AddRef(LPUNKNOWN lpThis)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjUnknownImpl FAR*)lpThis)->lpPseudoObj;

   OleDbgAddRefMethod(lpThis, "IUnknown");

   return PseudoObj_AddRef(lpPseudoObj);
}

STDMETHODIMP_(ULONG) PseudoObj_Unk_Release (LPUNKNOWN lpThis)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjUnknownImpl FAR*)lpThis)->lpPseudoObj;

   OleDbgReleaseMethod(lpThis, "IUnknown");

   return PseudoObj_Release(lpPseudoObj);
}

/*************************************************************************
** PseudoObj::IOleObject interface implementation
*************************************************************************/

STDMETHODIMP PseudoObj_OleObj_QueryInterface(
      LPOLEOBJECT     lpThis,
      REFIID          riid,
      LPVOID FAR*     lplpvObj
)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjOleObjectImpl FAR*)lpThis)->lpPseudoObj;

   return PseudoObj_QueryInterface(lpPseudoObj, riid, lplpvObj);
}

STDMETHODIMP_(ULONG) PseudoObj_OleObj_AddRef(LPOLEOBJECT lpThis)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjOleObjectImpl FAR*)lpThis)->lpPseudoObj;

   OleDbgAddRefMethod(lpThis, "IOleObject");

   return PseudoObj_AddRef((LPPSEUDOOBJ)lpPseudoObj);
}

STDMETHODIMP_(ULONG) PseudoObj_OleObj_Release(LPOLEOBJECT lpThis)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjOleObjectImpl FAR*)lpThis)->lpPseudoObj;

   OleDbgReleaseMethod(lpThis, "IOleObject");

   return PseudoObj_Release((LPPSEUDOOBJ)lpPseudoObj);
}

STDMETHODIMP PseudoObj_OleObj_SetClientSite(
      LPOLEOBJECT         lpThis,
      LPOLECLIENTSITE     lpClientSite
)
{
   OleDbgOut2("PseudoObj_OleObj_SetClientSite\r\n");

   // NOTE: a pseudo object does NOT support SetExtent

   return E_FAIL;
}

STDMETHODIMP PseudoObj_OleObj_GetClientSite(
      LPOLEOBJECT             lpThis,
      LPOLECLIENTSITE FAR*    lplpClientSite
)
{
   OleDbgOut2("PseudoObj_OleObj_GetClientSite\r\n");

   *lplpClientSite = NULL;

   // NOTE: a pseudo object does NOT support SetExtent

   return E_FAIL;
}

STDMETHODIMP PseudoObj_OleObj_SetHostNames(
      LPOLEOBJECT             lpThis,
      LPCOLESTR               szContainerApp,
      LPCOLESTR               szContainerObj
)
{
   OleDbgOut2("PseudoObj_OleObj_SetHostNames\r\n");

   // NOTE: a pseudo object does NOT support SetExtent

   return E_FAIL;
}

STDMETHODIMP PseudoObj_OleObj_Close(
      LPOLEOBJECT             lpThis,
      DWORD                   dwSaveOption
)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjOleObjectImpl FAR*)lpThis)->lpPseudoObj;
   BOOL fStatus;

   OLEDBG_BEGIN2("PseudoObj_OleObj_Close\r\n")

   /* NOTE: a pseudo object's implementation of IOleObject::Close
   **    should ignore the dwSaveOption parameter. it is NOT
   **    applicable to pseudo objects.
   */

   fStatus = PseudoObj_Close(lpPseudoObj);
   OleDbgAssertSz(fStatus == TRUE, "PseudoObj_OleObj_Close failed\r\n");

   OLEDBG_END2
   return NOERROR;
}

STDMETHODIMP PseudoObj_OleObj_SetMoniker(
      LPOLEOBJECT lpThis,
      DWORD       dwWhichMoniker,
      LPMONIKER   lpmk
)
{
   OleDbgOut2("PseudoObj_OleObj_SetMoniker\r\n");

   // NOTE: a pseudo object does NOT support SetMoniker

   return E_FAIL;
}

STDMETHODIMP PseudoObj_OleObj_GetMoniker(
      LPOLEOBJECT     lpThis,
      DWORD           dwAssign,
      DWORD           dwWhichMoniker,
      LPMONIKER FAR*  lplpmk
)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjOleObjectImpl FAR*)lpThis)->lpPseudoObj;
   LPOLEDOC lpOleDoc = (LPOLEDOC)lpPseudoObj->m_lpDoc;
   LPMONIKER lpmkDoc;

   // artificial AddRef in case object is destroyed during call
   PseudoObj_OleObj_AddRef(lpThis);

   OLEDBG_BEGIN2("PseudoObj_OleObj_GetMoniker\r\n")

   lpmkDoc = OleDoc_GetFullMoniker(lpOleDoc, GETMONIKER_ONLYIFTHERE);
   *lplpmk = PseudoObj_GetFullMoniker(lpPseudoObj, lpmkDoc);

   OLEDBG_END2

   if (*lplpmk != NULL)
   {
      // release artificial AddRef
      PseudoObj_OleObj_Release(lpThis);
      return NOERROR;
   }
   else
   {
      // release artificial AddRef
      PseudoObj_OleObj_Release(lpThis);
      return E_FAIL;
   }
}

STDMETHODIMP PseudoObj_OleObj_InitFromData(
      LPOLEOBJECT             lpThis,
      LPDATAOBJECT            lpDataObject,
      BOOL                    fCreation,
      DWORD                   reserved
)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjOleObjectImpl FAR*)lpThis)->lpPseudoObj;
   OleDbgOut2("PseudoObj_OleObj_InitFromData\r\n");

   // REVIEW: NOT YET IMPLEMENTED

   return E_NOTIMPL;
}

STDMETHODIMP PseudoObj_OleObj_GetClipboardData(
      LPOLEOBJECT             lpThis,
      DWORD                   reserved,
      LPDATAOBJECT FAR*       lplpDataObject
)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjOleObjectImpl FAR*)lpThis)->lpPseudoObj;
   OleDbgOut2("PseudoObj_OleObj_GetClipboardData\r\n");

   // REVIEW: NOT YET IMPLEMENTED

   return E_NOTIMPL;
}

STDMETHODIMP PseudoObj_OleObj_DoVerb(
      LPOLEOBJECT             lpThis,
      LONG                    lVerb,
      LPMSG                   lpmsg,
      LPOLECLIENTSITE         lpActiveSite,
      LONG                    lindex,
      HWND                    hwndParent,
      LPCRECT                 lprcPosRect
)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjOleObjectImpl FAR*)lpThis)->lpPseudoObj;
   LPOUTLINEDOC lpOutlineDoc = (LPOUTLINEDOC)lpPseudoObj->m_lpDoc;
   LPSERVERDOC lpServerDoc = lpPseudoObj->m_lpDoc;
   LINERANGE lrSel;
   HRESULT hrErr;

   // artificial AddRef in case object is destroyed during call
   PseudoObj_OleObj_AddRef(lpThis);

   OLEDBG_BEGIN2("PseudoObj_OleObj_DoVerb\r\n");

   /* NOTE: we must first ask our Document to perform the same
   **    verb. then if the verb is NOT OLEIVERB_HIDE we should also
   **    select the range of our pseudo object.
   **    however, we must give our document its own embedding site as
   **    its active site.
   */
   hrErr = SvrDoc_OleObj_DoVerb(
         (LPOLEOBJECT)&lpServerDoc->m_OleObject,
         lVerb,
         lpmsg,
         lpServerDoc->m_lpOleClientSite,
         lindex,
         NULL,   /* we have no hwndParent to give */
         NULL    /* we have no lprcPosRect to give */
   );
   if (FAILED(hrErr)) {
      OLEDBG_END2
      // release artificial AddRef
      PseudoObj_OleObj_Release(lpThis);
      return hrErr;
   }

   if (lVerb != OLEIVERB_HIDE) {
      PseudoObj_GetSel(lpPseudoObj, &lrSel);
      OutlineDoc_SetSel(lpOutlineDoc, &lrSel);
   }

   OLEDBG_END2
   // release artificial AddRef
   PseudoObj_OleObj_Release(lpThis);
   return NOERROR;
}

STDMETHODIMP PseudoObj_OleObj_EnumVerbs(
      LPOLEOBJECT         lpThis,
      LPENUMOLEVERB FAR*  lplpenumOleVerb
)
{
   OleDbgOut2("PseudoObj_OleObj_EnumVerbs\r\n");

   /* NOTE: we must make sure to set all out parameters to NULL. */
   *lplpenumOleVerb = NULL;

   /* A pseudo object may NOT return OLE_S_USEREG; they must call the
   **    OleReg* API or provide their own implementation. Because this
   **    pseudo object does NOT implement IPersist, simply a low-level
   **    remoting handler (ProxyManager) object as opposed to a
   **    DefHandler object is used as the handler for the pseudo
   **    object in a clients process space. The ProxyManager does NOT
   **    handle the OLE_S_USEREG return values.
   */
   return OleRegEnumVerbs((REFCLSID)&CLSID_APP, lplpenumOleVerb);
}

STDMETHODIMP PseudoObj_OleObj_Update(LPOLEOBJECT lpThis)
{
   OleDbgOut2("PseudoObj_OleObj_Update\r\n");

   /* NOTE: a server-only app is always "up-to-date".
   **    a container-app which contains links where the link source
   **    has changed since the last update of the link would be
   **    considered "out-of-date". the "Update" method instructs the
   **    object to get an update from any out-of-date links.
   */

   return NOERROR;
}

STDMETHODIMP PseudoObj_OleObj_IsUpToDate(LPOLEOBJECT lpThis)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjOleObjectImpl FAR*)lpThis)->lpPseudoObj;
   OleDbgOut2("PseudoObj_OleObj_IsUpToDate\r\n");

   /* NOTE: a server-only app is always "up-to-date".
   **    a container-app which contains links where the link source
   **    has changed since the last update of the link would be
   **    considered "out-of-date".
   */
   return NOERROR;
}

STDMETHODIMP PseudoObj_OleObj_GetUserClassID(
      LPOLEOBJECT             lpThis,
      LPCLSID                 lpclsid
)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjOleObjectImpl FAR*)lpThis)->lpPseudoObj;
   LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpPseudoObj->m_lpDoc;
   OleDbgOut2("PseudoObj_OleObj_GetUserClassID\r\n");

   /* NOTE: we must be carefull to return the correct CLSID here.
   **    if we are currently preforming a "TreatAs (aka. ActivateAs)"
   **    operation then we need to return the class of the object
   **    written in the storage of the object. otherwise we would
   **    return our own class id.
   */
   return ServerDoc_GetClassID(lpServerDoc, lpclsid);
}

STDMETHODIMP PseudoObj_OleObj_GetUserType(
      LPOLEOBJECT             lpThis,
      DWORD                   dwFormOfType,
      LPOLESTR FAR*           lpszUserType
)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjOleObjectImpl FAR*)lpThis)->lpPseudoObj;
   LPSERVERDOC lpServerDoc = (LPSERVERDOC)lpPseudoObj->m_lpDoc;
   OleDbgOut2("PseudoObj_OleObj_GetUserType\r\n");

   /* NOTE: we must make sure to set all out parameters to NULL. */
   *lpszUserType = NULL;

   /* NOTE: we must be carefull to return the correct user type here.
   **    if we are currently preforming a "TreatAs (aka. ActivateAs)"
   **    operation then we need to return the user type name that
   **    corresponds to the class of the object we are currently
   **    emmulating. otherwise we should return our normal user type
   **    name corresponding to our own class. This routine determines
   **    the current clsid in effect.
   **
   **    A pseudo object may NOT return OLE_S_USEREG; they must call the
   **    OleReg* API or provide their own implementation. Because this
   **    pseudo object does NOT implement IPersist, simply a low-level
   **    remoting handler (ProxyManager) object as opposed to a
   **    DefHandler object is used as the handler for the pseudo
   **    object in a clients process space. The ProxyManager does NOT
   **    handle the OLE_S_USEREG return values.
   */
#if defined( SVR_TREATAS )
   if (! IsEqualCLSID(&lpServerDoc->m_clsidTreatAs, &CLSID_NULL) )
      return OleRegGetUserType(
         (REFCLSID)&lpServerDoc->m_clsidTreatAs,dwFormOfType,lpszUserType);
   else
#endif  // SVR_TREATAS

   return OleRegGetUserType((REFCLSID)&CLSID_APP,dwFormOfType,lpszUserType);
}

STDMETHODIMP PseudoObj_OleObj_SetExtent(
      LPOLEOBJECT             lpThis,
      DWORD                   dwDrawAspect,
      LPSIZEL                 lplgrc
)
{
   OleDbgOut2("PseudoObj_OleObj_SetExtent\r\n");

   // NOTE: a pseudo object does NOT support SetExtent

   return E_FAIL;
}

STDMETHODIMP PseudoObj_OleObj_GetExtent(
      LPOLEOBJECT             lpThis,
      DWORD                   dwDrawAspect,
      LPSIZEL                 lpsizel
)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjOleObjectImpl FAR*)lpThis)->lpPseudoObj;
   OleDbgOut2("PseudoObj_OleObj_GetExtent\r\n");

   /* NOTE: it is VERY important to check which aspect the caller
   **    is asking about. an object implemented by a server EXE MAY
   **    fail to return extents when asked for DVASPECT_ICON.
   */
   if (dwDrawAspect == DVASPECT_CONTENT) {
      PseudoObj_GetExtent(lpPseudoObj, lpsizel);
      return NOERROR;
   }
   else
   {
      return E_FAIL;
   }
}

STDMETHODIMP PseudoObj_OleObj_Advise(
      LPOLEOBJECT lpThis,
      LPADVISESINK lpAdvSink,
      LPDWORD lpdwConnection
)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjOleObjectImpl FAR*)lpThis)->lpPseudoObj;
   HRESULT hrErr;
   SCODE   sc;
   OLEDBG_BEGIN2("PseudoObj_OleObj_Advise\r\n");

   if (lpPseudoObj->m_lpOleAdviseHldr == NULL &&
      CreateOleAdviseHolder(&lpPseudoObj->m_lpOleAdviseHldr) != NOERROR) {

sc = E_OUTOFMEMORY;
      goto error;
   }

   OLEDBG_BEGIN2("IOleAdviseHolder::Advise called\r\n")
   hrErr = lpPseudoObj->m_lpOleAdviseHldr->lpVtbl->Advise(
         lpPseudoObj->m_lpOleAdviseHldr,
         lpAdvSink,
         lpdwConnection
   );
   OLEDBG_END2

   OLEDBG_END2
   return hrErr;

error:
   OLEDBG_END2
   return sc;
}

STDMETHODIMP PseudoObj_OleObj_Unadvise(LPOLEOBJECT lpThis, DWORD dwConnection)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjOleObjectImpl FAR*)lpThis)->lpPseudoObj;
   HRESULT hrErr;
   SCODE   sc;

   // artificial AddRef in case object is destroyed during call
   PseudoObj_OleObj_AddRef(lpThis);

   OLEDBG_BEGIN2("PseudoObj_OleObj_Unadvise\r\n");

   if (lpPseudoObj->m_lpOleAdviseHldr == NULL) {
      sc = E_FAIL;
      goto error;
   }

   OLEDBG_BEGIN2("IOleAdviseHolder::Unadvise called\r\n")
   hrErr = lpPseudoObj->m_lpOleAdviseHldr->lpVtbl->Unadvise(
         lpPseudoObj->m_lpOleAdviseHldr,
         dwConnection
   );
   OLEDBG_END2

   OLEDBG_END2
   // release artificial AddRef
   PseudoObj_OleObj_Release(lpThis);
   return hrErr;

error:
   OLEDBG_END2
   // release artificial AddRef
   PseudoObj_OleObj_Release(lpThis);
   return sc;
}

STDMETHODIMP PseudoObj_OleObj_EnumAdvise(
      LPOLEOBJECT lpThis,
      LPENUMSTATDATA FAR* lplpenumAdvise
)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjOleObjectImpl FAR*)lpThis)->lpPseudoObj;
   HRESULT hrErr;
   SCODE   sc;

   // artificial AddRef in case object is destroyed during call
   PseudoObj_OleObj_AddRef(lpThis);

   OLEDBG_BEGIN2("PseudoObj_OleObj_EnumAdvise\r\n");

   /* NOTE: we must make sure to set all out parameters to NULL. */
   *lplpenumAdvise = NULL;

   if (lpPseudoObj->m_lpOleAdviseHldr == NULL) {
      sc = E_FAIL;
      goto error;
   }

   OLEDBG_BEGIN2("IOleAdviseHolder::EnumAdvise called\r\n")
   hrErr = lpPseudoObj->m_lpOleAdviseHldr->lpVtbl->EnumAdvise(
         lpPseudoObj->m_lpOleAdviseHldr,
         lplpenumAdvise
   );
   OLEDBG_END2

   OLEDBG_END2
   // release artificial AddRef
   PseudoObj_OleObj_Release(lpThis);
   return hrErr;

error:
   OLEDBG_END2
   // release artificial AddRef
   PseudoObj_OleObj_Release(lpThis);
   return sc;
}

STDMETHODIMP PseudoObj_OleObj_GetMiscStatus(
      LPOLEOBJECT             lpThis,
      DWORD                   dwAspect,
      DWORD FAR*              lpdwStatus
)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjOleObjectImpl FAR*)lpThis)->lpPseudoObj;
   LPOUTLINEDOC  lpOutlineDoc = (LPOUTLINEDOC)lpPseudoObj->m_lpDoc;
   OleDbgOut2("PseudoObj_OleObj_GetMiscStatus\r\n");

   /* Get our default MiscStatus for the given Aspect. this
   **    information is registered in the RegDB. We query the RegDB
   **    here to guarantee that the value returned from this method
   **    agrees with the values in RegDB. in this way we only have to
   **    maintain the info in one place (in the RegDB). Alternatively
   **    we could have the values hard coded here.
   **
   ** NOTE: A pseudo object may NOT return OLE_S_USEREG; they must
   **    call the
   **    OleReg* API or provide their own implementation. Because this
   **    pseudo object does NOT implement IPersist, simply a low-level
   **    remoting handler (ProxyManager) object as opposed to a
   **    DefHandler object is used as the handler for the pseudo
   **    object in a clients process space. The ProxyManager does NOT
   **    handle the OLE_S_USEREG return values.
   */
   OleRegGetMiscStatus((REFCLSID)&CLSID_APP, dwAspect, lpdwStatus);

   /* NOTE: check if the pseudo object is compatible to be
   **    linked by an OLE 1.0 container. it is compatible if
   **    either the pseudo object is an untitled document or a
   **    file-based document. if the pseudo object is part of
   **    an embedded object, then it is NOT compatible to be
   **    linked by an OLE 1.0 container. if it is compatible then
   **    we should include OLEMISC_CANLINKBYOLE1 as part of the
   **    dwStatus flags.
   */
   if (lpOutlineDoc->m_docInitType == DOCTYPE_NEW ||
      lpOutlineDoc->m_docInitType == DOCTYPE_FROMFILE)
      *lpdwStatus |= OLEMISC_CANLINKBYOLE1;

   return NOERROR;
}

STDMETHODIMP PseudoObj_OleObj_SetColorScheme(
      LPOLEOBJECT             lpThis,
      LPLOGPALETTE            lpLogpal
)
{
   OleDbgOut2("PseudoObj_OleObj_SetColorScheme\r\n");

   // REVIEW: NOT YET IMPLEMENTED

   return E_NOTIMPL;
}

/*************************************************************************
** PseudoObj::IDataObject interface implementation
*************************************************************************/

STDMETHODIMP PseudoObj_DataObj_QueryInterface (
      LPDATAOBJECT      lpThis,
      REFIID            riid,
      LPVOID FAR*       lplpvObj
)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjDataObjectImpl FAR*)lpThis)->lpPseudoObj;

   return PseudoObj_QueryInterface(lpPseudoObj, riid, lplpvObj);
}

STDMETHODIMP_(ULONG) PseudoObj_DataObj_AddRef(LPDATAOBJECT lpThis)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjDataObjectImpl FAR*)lpThis)->lpPseudoObj;

   OleDbgAddRefMethod(lpThis, "IDataObject");

   return PseudoObj_AddRef((LPPSEUDOOBJ)lpPseudoObj);
}

STDMETHODIMP_(ULONG) PseudoObj_DataObj_Release (LPDATAOBJECT lpThis)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjDataObjectImpl FAR*)lpThis)->lpPseudoObj;

   OleDbgReleaseMethod(lpThis, "IDataObject");

   return PseudoObj_Release((LPPSEUDOOBJ)lpPseudoObj);
}

STDMETHODIMP PseudoObj_DataObj_GetData (
      LPDATAOBJECT    lpThis,
      LPFORMATETC     lpformatetc,
      LPSTGMEDIUM     lpMedium
)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjDataObjectImpl FAR*)lpThis)->lpPseudoObj;
   LPSERVERDOC  lpServerDoc = lpPseudoObj->m_lpDoc;
   LPOUTLINEDOC  lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   LPSERVERAPP lpServerApp = (LPSERVERAPP)g_lpApp;
   LPOLEAPP  lpOleApp = (LPOLEAPP)lpServerApp;
   LPOUTLINEAPP  lpOutlineApp = (LPOUTLINEAPP)lpServerApp;
   LINERANGE lrSel;
   SCODE sc = S_OK;

   // artificial AddRef in case object is destroyed during call
   PseudoObj_DataObj_AddRef(lpThis);

   OLEDBG_BEGIN2("PseudoObj_DataObj_GetData\r\n")

   PseudoObj_GetSel(lpPseudoObj, &lrSel);

   /* NOTE: we must make sure to set all out parameters to NULL. */
   lpMedium->tymed = TYMED_NULL;
   lpMedium->pUnkForRelease = NULL;    // we transfer ownership to caller
   lpMedium->u.hGlobal = NULL;

   if (lpformatetc->cfFormat == lpOutlineApp->m_cfOutline) {
      // Verify caller asked for correct medium
      if (!(lpformatetc->tymed & TYMED_HGLOBAL)) {
         sc = DATA_E_FORMATETC;
         goto error;
      }

      lpMedium->u.hGlobal = OutlineDoc_GetOutlineData (lpOutlineDoc,&lrSel);
      if (! lpMedium->u.hGlobal) return E_OUTOFMEMORY;
      lpMedium->tymed = TYMED_HGLOBAL;
      OleDbgOut3("PseudoObj_DataObj_GetData: rendered CF_OUTLINE\r\n");

   } else if(lpformatetc->cfFormat == CF_METAFILEPICT &&
      (lpformatetc->dwAspect & DVASPECT_CONTENT) ) {
      // Verify caller asked for correct medium
      if (!(lpformatetc->tymed & TYMED_MFPICT)) {
         sc = DATA_E_FORMATETC;
         goto error;
      }

      lpMedium->u.hGlobal=ServerDoc_GetMetafilePictData(lpServerDoc,&lrSel);
      if (! lpMedium->u.hGlobal) {
         sc = E_OUTOFMEMORY;
         goto error;
      }
      lpMedium->tymed = TYMED_MFPICT;
      OleDbgOut3("PseudoObj_DataObj_GetData: rendered CF_METAFILEPICT\r\n");

   } else if (lpformatetc->cfFormat == CF_METAFILEPICT &&
      (lpformatetc->dwAspect & DVASPECT_ICON) ) {
      CLSID clsid;
      // Verify caller asked for correct medium
      if (!(lpformatetc->tymed & TYMED_MFPICT)) {
         sc = DATA_E_FORMATETC;
         goto error;
      }

      /* NOTE: we should return the default icon for our class.
      **    we must be carefull to use the correct CLSID here.
      **    if we are currently preforming a "TreatAs (aka. ActivateAs)"
      **    operation then we need to use the class of the object
      **    written in the storage of the object. otherwise we would
      **    use our own class id.
      */
      if (ServerDoc_GetClassID(lpServerDoc, (LPCLSID)&clsid) != NOERROR) {
         sc = DATA_E_FORMATETC;
         goto error;
      }

      lpMedium->u.hGlobal=GetIconOfClass(
            g_lpApp->m_hInst,(REFCLSID)&clsid, NULL, FALSE);
      if (! lpMedium->u.hGlobal) {
         sc = E_OUTOFMEMORY;
         goto error;
      }

      lpMedium->tymed = TYMED_MFPICT;
      OleDbgOut3("PseudoObj_DataObj_GetData: rendered CF_METAFILEPICT (icon)\r\n");
      // release artificial AddRef
      PseudoObj_DataObj_Release(lpThis);
      return NOERROR;

   } else if (lpformatetc->cfFormat == CF_TEXT) {
      // Verify caller asked for correct medium
      if (!(lpformatetc->tymed & TYMED_HGLOBAL)) {
         sc = DATA_E_FORMATETC;
         goto error;
      }

      lpMedium->u.hGlobal = OutlineDoc_GetTextData (lpOutlineDoc, &lrSel);
      if (! lpMedium->u.hGlobal) {
         sc = E_OUTOFMEMORY;
         goto error;
      }
      lpMedium->tymed = TYMED_HGLOBAL;
      OleDbgOut3("PseudoObj_DataObj_GetData: rendered CF_TEXT\r\n");

   } else {
      sc = DATA_E_FORMATETC;
      goto error;
   }

   OLEDBG_END2
   // release artificial AddRef
   PseudoObj_DataObj_Release(lpThis);
   return NOERROR;

error:
   OLEDBG_END2
   // release artificial AddRef
   PseudoObj_DataObj_Release(lpThis);
   return sc;
}

STDMETHODIMP PseudoObj_DataObj_GetDataHere (
      LPDATAOBJECT    lpThis,
      LPFORMATETC     lpformatetc,
      LPSTGMEDIUM     lpMedium
)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjDataObjectImpl FAR*)lpThis)->lpPseudoObj;
   LPSERVERDOC  lpServerDoc = lpPseudoObj->m_lpDoc;
   LPOUTLINEDOC  lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   LPSERVERAPP lpServerApp = (LPSERVERAPP)g_lpApp;
   LPOLEAPP  lpOleApp = (LPOLEAPP)lpServerApp;
   LPOUTLINEAPP  lpOutlineApp = (LPOUTLINEAPP)lpServerApp;

   OleDbgOut("PseudoObj_DataObj_GetDataHere\r\n");

   /* Caller is requesting data to be returned in Caller allocated
   **    medium, but we do NOT support this. we only support
   **    global memory blocks that WE allocate for the caller.
   */
   return DATA_E_FORMATETC;
}

STDMETHODIMP PseudoObj_DataObj_QueryGetData (
      LPDATAOBJECT    lpThis,
      LPFORMATETC     lpformatetc
)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjDataObjectImpl FAR*)lpThis)->lpPseudoObj;
   LPSERVERDOC  lpServerDoc = lpPseudoObj->m_lpDoc;
   LPOUTLINEDOC  lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   LPSERVERAPP lpServerApp = (LPSERVERAPP)g_lpApp;
   LPOLEAPP  lpOleApp = (LPOLEAPP)lpServerApp;
   LPOUTLINEAPP  lpOutlineApp = (LPOUTLINEAPP)lpServerApp;

   // artificial AddRef in case object is destroyed during call
   PseudoObj_DataObj_AddRef(lpThis);

   OleDbgOut2("PseudoObj_DataObj_QueryGetData\r\n");

   /* Caller is querying if we support certain format but does not
   **    want any data actually returned.
   */
   if (lpformatetc->cfFormat == CF_METAFILEPICT &&
      (lpformatetc->dwAspect & (DVASPECT_CONTENT | DVASPECT_ICON)) ) {
      // release artificial AddRef
      PseudoObj_DataObj_Release(lpThis);
      return OleStdQueryFormatMedium(lpformatetc, TYMED_MFPICT);

   } else if (lpformatetc->cfFormat == (lpOutlineApp)->m_cfOutline ||
         lpformatetc->cfFormat == CF_TEXT) {
      // release artificial AddRef
      PseudoObj_DataObj_Release(lpThis);
      return OleStdQueryFormatMedium(lpformatetc, TYMED_HGLOBAL);
   }

   // release artificial AddRef
   PseudoObj_DataObj_Release(lpThis);
   return DATA_E_FORMATETC;
}

STDMETHODIMP PseudoObj_DataObj_GetCanonicalFormatEtc(
      LPDATAOBJECT    lpThis,
      LPFORMATETC     lpformatetc,
      LPFORMATETC     lpformatetcOut
)
{
   HRESULT hrErr;
   OleDbgOut2("PseudoObj_DataObj_GetCanonicalFormatEtc\r\n");

   if (!lpformatetcOut)
      return E_INVALIDARG;

   /* NOTE: we must make sure to set all out parameters to NULL. */
   lpformatetcOut->ptd = NULL;

   if (!lpformatetc)
      return E_INVALIDARG;

   // NOTE: we must validate that the format requested is supported
   if ((hrErr=lpThis->lpVtbl->QueryGetData(lpThis,lpformatetc)) != NOERROR)
      return hrErr;

   /* NOTE: an app that is insensitive to target device (as the
   **    Outline Sample is) should fill in the lpformatOut parameter
   **    but NULL out the "ptd" field; it should return NOERROR if the
   **    input formatetc->ptd what non-NULL. this tells the caller
   **    that it is NOT necessary to maintain a separate screen
   **    rendering and printer rendering. if should return
   **    DATA_S_SAMEFORMATETC if the input and output formatetc's are
   **    identical.
   */

   *lpformatetcOut = *lpformatetc;
   if (lpformatetc->ptd == NULL)
      return DATA_S_SAMEFORMATETC;
   else {
      lpformatetcOut->ptd = NULL;
      return NOERROR;
   }
}

STDMETHODIMP PseudoObj_DataObj_SetData (
      LPDATAOBJECT    lpThis,
      LPFORMATETC     lpformatetc,
      LPSTGMEDIUM     lpmedium,
      BOOL            fRelease
)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjDataObjectImpl FAR*)lpThis)->lpPseudoObj;
   LPSERVERDOC  lpServerDoc = lpPseudoObj->m_lpDoc;
   LPOUTLINEDOC  lpOutlineDoc = (LPOUTLINEDOC)lpServerDoc;
   LPSERVERAPP lpServerApp = (LPSERVERAPP)g_lpApp;

   OleDbgOut2("PseudoObj_DataObj_SetData\r\n");

   // REVIEW: NOT-YET-IMPLEMENTED
   return E_NOTIMPL;
}

STDMETHODIMP PseudoObj_DataObj_EnumFormatEtc(
      LPDATAOBJECT            lpThis,
      DWORD                   dwDirection,
      LPENUMFORMATETC FAR*    lplpenumFormatEtc
)
{
   SCODE sc;
   OleDbgOut2("PseudoObj_DataObj_EnumFormatEtc\r\n");

   /* NOTE: a pseudo object only needs to enumerate the static list
   **    of formats that are registered for our app in the
   **    registration database. it is NOT
   **    required that a pseudo object (ie. non-DataTransferDoc)
   **    enumerate the OLE formats: CF_LINKSOURCE, CF_EMBEDSOURCE, or
   **    CF_EMBEDDEDOBJECT. we do NOT use pseudo objects for data
   **    transfers.
   **
   **    A pseudo object may NOT return OLE_S_USEREG; they must call the
   **    OleReg* API or provide their own implementation. Because this
   **    pseudo object does NOT implement IPersist, simply a low-level
   **    remoting handler (ProxyManager) object as opposed to a
   **    DefHandler object is used as the handler for the pseudo
   **    object in a clients process space. The ProxyManager does NOT
   **    handle the OLE_S_USEREG return values.
   */
   if (dwDirection == DATADIR_GET)
      return OleRegEnumFormatEtc(
            (REFCLSID)&CLSID_APP, dwDirection, lplpenumFormatEtc);
   else if (dwDirection == DATADIR_SET)
      sc = E_NOTIMPL;
   else
      sc = E_INVALIDARG;

   return sc;
}

STDMETHODIMP PseudoObj_DataObj_DAdvise(
      LPDATAOBJECT    lpThis,
      FORMATETC FAR*  lpFormatetc,
      DWORD           advf,
      LPADVISESINK    lpAdvSink,
      DWORD FAR*      lpdwConnection
)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjDataObjectImpl FAR*)lpThis)->lpPseudoObj;
   HRESULT hrErr;
   SCODE   sc;

   // artificial AddRef in case object is destroyed during call
   PseudoObj_DataObj_AddRef(lpThis);

   OLEDBG_BEGIN2("PseudoObj_DataObj_DAdvise\r\n")

   /* NOTE: we must make sure to set all out parameters to NULL. */
   *lpdwConnection = 0;

   /* NOTE: we should validate if the caller is setting up an
   **    Advise for a data type that we support. we must
   **    explicitly allow an advise for the "wildcard" advise.
   */
#ifdef WIN32
   if ( !( lpFormatetc->cfFormat == 0 &&
      lpFormatetc->ptd == 0 &&
#else
   if ( !( lpFormatetc->cfFormat == NULL &&
      lpFormatetc->ptd == NULL &&
#endif
      lpFormatetc->dwAspect == -1L &&
      lpFormatetc->lindex == -1L &&
      lpFormatetc->tymed == -1L) &&
      (hrErr = PseudoObj_DataObj_QueryGetData(lpThis, lpFormatetc))
         != NOERROR) {
      sc = hrErr;
      goto error;
   }

   if (lpPseudoObj->m_lpDataAdviseHldr == NULL &&
      CreateDataAdviseHolder(&lpPseudoObj->m_lpDataAdviseHldr) != NOERROR) {
      sc = E_OUTOFMEMORY;
      goto error;
   }

   OLEDBG_BEGIN2("IOleAdviseHolder::Advise called\r\n")
   hrErr = lpPseudoObj->m_lpDataAdviseHldr->lpVtbl->Advise(
         lpPseudoObj->m_lpDataAdviseHldr,
         (LPDATAOBJECT)&lpPseudoObj->m_DataObject,
         lpFormatetc,
         advf,
         lpAdvSink,
         lpdwConnection
   );
   OLEDBG_END2

   OLEDBG_END2
   // release artificial AddRef
   PseudoObj_DataObj_Release(lpThis);
   return hrErr;

error:
   OLEDBG_END2
   // release artificial AddRef
   PseudoObj_DataObj_Release(lpThis);
   return sc;
}

STDMETHODIMP PseudoObj_DataObj_DUnadvise(LPDATAOBJECT lpThis, DWORD dwConnection)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjDataObjectImpl FAR*)lpThis)->lpPseudoObj;
   HRESULT hrErr;
   SCODE   sc;

   // artificial AddRef in case object is destroyed during call
   PseudoObj_DataObj_AddRef(lpThis);
   OLEDBG_BEGIN2("PseudoObj_DataObj_Unadvise\r\n");

   // no one registered
   if (lpPseudoObj->m_lpDataAdviseHldr == NULL) {
      sc = E_FAIL;
      goto error;
   }

   OLEDBG_BEGIN2("IOleAdviseHolder::DUnadvise called\r\n")
   hrErr = lpPseudoObj->m_lpDataAdviseHldr->lpVtbl->Unadvise(
         lpPseudoObj->m_lpDataAdviseHldr,
         dwConnection
   );
   OLEDBG_END2

   OLEDBG_END2
   // release artificial AddRef
   PseudoObj_DataObj_Release(lpThis);
   return hrErr;

error:
   OLEDBG_END2
   // release artificial AddRef
   PseudoObj_DataObj_Release(lpThis);
   return sc;
}

STDMETHODIMP PseudoObj_DataObj_EnumAdvise(
      LPDATAOBJECT lpThis,
      LPENUMSTATDATA FAR* lplpenumAdvise
)
{
   LPPSEUDOOBJ lpPseudoObj =
         ((struct CPseudoObjDataObjectImpl FAR*)lpThis)->lpPseudoObj;
   HRESULT hrErr;
   SCODE   sc;

   // artificial AddRef in case object is destroyed during call
   PseudoObj_DataObj_AddRef(lpThis);

   OLEDBG_BEGIN2("PseudoObj_DataObj_EnumAdvise\r\n");

   /* NOTE: we must make sure to set all out parameters to NULL. */
   *lplpenumAdvise = NULL;

   if (lpPseudoObj->m_lpDataAdviseHldr == NULL) {
      sc = E_FAIL;
      goto error;
   }

   OLEDBG_BEGIN2("IOleAdviseHolder::EnumAdvise called\r\n")
   hrErr = lpPseudoObj->m_lpDataAdviseHldr->lpVtbl->EnumAdvise(
         lpPseudoObj->m_lpDataAdviseHldr,
         lplpenumAdvise
   );
   OLEDBG_END2

   OLEDBG_END2
   // release artificial AddRef
   PseudoObj_DataObj_Release(lpThis);
   return hrErr;

error:
   OLEDBG_END2
   // release artificial AddRef
   PseudoObj_DataObj_Release(lpThis);
   return sc;
}


README.TXT

Simple OLE Container

The SimpCntr sample is the simplest OLE 2.0 container that can be written
that supports the visual editing feature. The sample has no native file
format, and can only support one OLE object at a time. Use the Insert Object
command to create an object.


APP.H

//**********************************************************************
// File name: app.h
//
//      Definition of CSimpleApp
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#if !defined( _APP_H_)
#define _APP_H_

#include <ole2.h>
#include <stablize.h>
#include "ioipf.h"

class CSimpleDoc;

class CSimpleApp : public IUnknown, public CSafeRefCount
{
public:

        HWND m_hAppWnd;         // main window handle
        HINSTANCE m_hInst;          // application instance
        HMENU           m_hMainMenu;
        HMENU           m_hFileMenu;
        HMENU           m_hEditMenu;
        HMENU           m_hHelpMenu;
        HMENU           m_hCascadeMenu;

        COleInPlaceFrame m_OleInPlaceFrame; // IOleInPlaceFrame Implementation

        CSimpleDoc FAR * m_lpDoc;   // pointer to document object
        BOOL m_fInitialized;        // OLE initialization flag
        BOOL m_fCSHMode;
        BOOL m_fMenuMode;
        HWND m_hwndUIActiveObj; // HWND of UIActive Object
        HPALETTE m_hStdPal;     // Color palette used by container
        BOOL m_fAppActive;      // TRUE if app is active

        CSimpleApp();           // Constructor
        ~CSimpleApp();          // Destructor
        RECT nullRect;

        // IUnknown Interfaces
        STDMETHODIMP QueryInterface(REFIID riid, LPVOID FAR* ppvObj);
        STDMETHODIMP_(ULONG) AddRef();
        STDMETHODIMP_(ULONG) Release();

        // Initialization methods

        BOOL fInitApplication (HANDLE hInstance);
        BOOL fInitInstance (HANDLE hInstance, int nCmdShow);

        // Message handling methods

        long lCommandHandler (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
        long lSizeHandler (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
        long lCreateDoc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
        BOOL HandleAccelerators (LPMSG lpMsg);
        void PaintApp(HDC hDC);
        void DestroyDocs();

        // In-Place support functions
        void AddFrameLevelUI();
        void AddFrameLevelTools();
        void ContextSensitiveHelp (BOOL fEnterMode);
        LRESULT QueryNewPalette(void);
};

LRESULT wSelectPalette(HWND hWnd, HPALETTE hPal, BOOL fBackground);

#endif


DOC.CPP

//**********************************************************************
// File name: DOC.CPP
//
//      Implementation file for CSimpleDoc.
//
// Functions:
//
//      See DOC.H for Class Definition
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "iocs.h"
#include "ias.h"
#include "ioipf.h"
#include "ioips.h"
#include "app.h"
#include "site.h"
#include "doc.h"

//**********************************************************************
//
// CSimpleDoc::Create
//
// Purpose:
//
//      Creation for the CSimpleDoc Class
//
// Parameters:
//
//      CSimpleApp FAR * lpApp  -   Pointer to the CSimpleApp Class
//
//      LPRECT lpRect           -   Client area rect of "frame" window
//
//      HWND hWnd               -   Window Handle of "frame" window
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      StgCreateDocfile            OLE API
//      CreateWindow                Windows API
//      ShowWindow                  Windows API
//      UpdateWindow                Windows API
//
// Comments:
//
//      This routine was added so that failure could be returned
//      from object creation.
//
//********************************************************************

CSimpleDoc FAR * CSimpleDoc::Create(CSimpleApp FAR *lpApp, LPRECT lpRect,HWND hWnd)
{
        CSimpleDoc FAR * lpTemp = new CSimpleDoc(lpApp, hWnd);

        if (!lpTemp)
                return NULL;

        // create storage for the doc.
        HRESULT hErr = StgCreateDocfile (NULL, STGM_READWRITE | STGM_TRANSACTED | STGM_SHARE_EXCLUSIVE,
                                                                         0, &lpTemp->m_lpStorage);

        if (hErr != NOERROR)
                goto error;

        // create the document Window
        lpTemp->m_hDocWnd = CreateWindow(
                        "SimpCntrDocWClass",
                        NULL,
                        WS_CHILD | WS_CLIPCHILDREN,
                        lpRect->left,
                        lpRect->top,
                        lpRect->right,
                        lpRect->bottom,
                        hWnd,
                        NULL,
                        lpApp->m_hInst,
                        NULL);

        if (!lpTemp->m_hDocWnd)
                goto error;

        ShowWindow(lpTemp->m_hDocWnd, SW_SHOWNORMAL);  // Show the window
        UpdateWindow(lpTemp->m_hDocWnd);               // Sends WM_PAINT message

        // Ensable InsertObject menu choice
        EnableMenuItem( lpApp->m_hEditMenu, 0, MF_BYPOSITION | MF_ENABLED);

        // we will add one ref count on our document. later in CSimpleDoc::Close
        // we will release this  ref count. when the document's ref count goes
        // to 0, the document will be deleted.
        lpTemp->AddRef();

        return (lpTemp);

error:
        delete (lpTemp);
        return NULL;

}

//**********************************************************************
//
// CSimpleDoc::Close
//
// Purpose:
//
//      Close CSimpleDoc object.
//      when the document's reference count goes to 0, the document
//      will be destroyed.
//
// Parameters:
//
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      RevokeDragDrop              OLE API
//      CoLockObjectExternal        OLE API
//      OleFlushClipboard           OLE API
//      ShowWindow                  Windows API
//
// Comments:
//
//********************************************************************

void CSimpleDoc::Close(void)
{
        OutputDebugString("In CSimpleDoc::Close\r\n");

        ShowWindow(m_hDocWnd, SW_HIDE);  // Hide the window

        // Close the OLE object in our document
        if (m_lpSite)
                m_lpSite->CloseOleObject();

        // Release the ref count added in CSimpleDoc::Create. this will make
        // the document's ref count go to 0, and the document will be deleted.
        Release();
}

//**********************************************************************
//
// CSimpleDoc::CSimpleDoc
//
// Purpose:
//
//      Constructor for the CSimpleDoc Class
//
// Parameters:
//
//      CSimpleApp FAR * lpApp  -   Pointer to the CSimpleApp Class
//
//      HWND hWnd               -   Window Handle of "frame" window
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      GetMenu                     Windows API
//      GetSubMenu                  Windows API
//
// Comments:
//
//********************************************************************

CSimpleDoc::CSimpleDoc(CSimpleApp FAR * lpApp,HWND hWnd)
{
        OutputDebugString("In CSimpleDoc's Constructor\r\n");
        m_lpApp = lpApp;
        m_lpSite = NULL;
        // set up menu handles
        m_lpApp->m_hMainMenu = GetMenu(hWnd);
        m_lpApp->m_hFileMenu = GetSubMenu(m_lpApp->m_hMainMenu, 0);
        m_lpApp->m_hEditMenu = GetSubMenu(m_lpApp->m_hMainMenu, 1);
        m_lpApp->m_hHelpMenu = GetSubMenu(m_lpApp->m_hMainMenu, 2);
        m_lpApp->m_hCascadeMenu = NULL;

        m_lpActiveObject = NULL;

        // flags
        m_fInPlaceActive = FALSE;
        m_fAddMyUI = FALSE;
        m_fModifiedMenu = FALSE;
}

//**********************************************************************
//
// CSimpleDoc::~CSimpleDoc
//
// Purpose:
//
//      Destructor for CSimpleDoc
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpleSite::Release        SITE.CPP
//      IStorage::Release           OLE API
//
// Comments:
//
//********************************************************************

CSimpleDoc::~CSimpleDoc()
{
        OutputDebugString("In CSimpleDoc's Destructor\r\n");

        // Release all pointers we hold to the OLE object. also release
        // the ref count added in CSimpleSite::Create. this will make
        // the Site's ref count go to 0, and the Site will be deleted.
        if (m_lpSite) {
                m_lpSite->UnloadOleObject();
                m_lpSite->Release();
                m_lpSite = NULL;
        }

        // Release the Storage
        if (m_lpStorage) {
                m_lpStorage->Release();
                m_lpStorage = NULL;
        }

        // if the edit menu was modified, remove the menu item and
        // destroy the popup if it exists
        if (m_fModifiedMenu)
                {
                int nCount = GetMenuItemCount(m_lpApp->m_hEditMenu);
                RemoveMenu(m_lpApp->m_hEditMenu, nCount-1, MF_BYPOSITION);
                if (m_lpApp->m_hCascadeMenu)
                        DestroyMenu(m_lpApp->m_hCascadeMenu);
                }

        DestroyWindow(m_hDocWnd);
}

//**********************************************************************
//
// CSimpleDoc::QueryInterface
//
// Purpose:
//
//      Return a pointer to a requested interface
//
// Parameters:
//
//      REFIID riid         -   ID of interface to be returned
//      LPVOID FAR* ppvObj  -   Location to return the interface
//
// Return Value:
//
//      S_FALSE -   Always
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//                   OLE API
//
// Comments:
//
//      In this implementation, there are no doc level interfaces.
//      In an MDI application, there would be an IOleInPlaceUIWindow
//      associated with the document to provide document level tool
 //     space negotiation.
//
//********************************************************************

STDMETHODIMP CSimpleDoc::QueryInterface(REFIID riid, LPVOID FAR* ppvObj)
{
        OutputDebugString("In CSimpleDoc::QueryInterface\r\n");

        *ppvObj = NULL;     // must set out pointer parameters to NULL

        // Not a supported interface
        return E_NOINTERFACE;
}

//**********************************************************************
//
// CSimpleDoc::AddRef
//
// Purpose:
//
//      Increments the document reference count
//
// Parameters:
//
//      None
//
// Return Value:
//
//      UINT    -   The current reference count on the document
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpleApp::AddRef          APP.CPP
//
// Comments:
//
//********************************************************************

STDMETHODIMP_(ULONG) CSimpleDoc::AddRef()
{
        OutputDebugString("In CSimpleDoc::AddRef\r\n");
        return SafeAddRef();
}

//**********************************************************************
//
// CSimpleDoc::Release
//
// Purpose:
//
//      Decrements the document reference count
//
// Parameters:
//
//      None
//
// Return Value:
//
//      UINT    -   The current reference count on the document
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//********************************************************************

STDMETHODIMP_(ULONG) CSimpleDoc::Release()
{
        OutputDebugString("In CSimpleDoc::Release\r\n");

        return SafeRelease();
}

//**********************************************************************
//
// CSimpleDoc::InsertObject
//
// Purpose:
//
//      Inserts a new object to this document
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      CSimpleSite::CSimpleSite    SITE.CPP
//      CSimpleSite::InitObject     SITE.CPP
//      memset                      C Runtime
//      OleUIInsertObject           OUTLUI function
//      CSimpleDoc::DisableInsertObject DOC.CPP
//
// Comments:
//
//      This implementation only allows one object to be inserted
//      into a document.  Once the object has been inserted, then
//      the Insert Object menu choice is greyed out, to prevent
//      the user from inserting another.
//
//********************************************************************

void CSimpleDoc::InsertObject()
{
        CStabilize stabilize(this);
        OLEUIINSERTOBJECT io;
        UINT iret;
  //@@WTK WIN32, UNICODE
        //char szFile[MAX_PATH];
        TCHAR szFile[MAX_PATH];

        m_lpSite = CSimpleSite::Create(this);

        // clear the structure
        memset(&io, 0, sizeof(OLEUIINSERTOBJECT));

        // fill the structure
        io.cbStruct = sizeof(OLEUIINSERTOBJECT);
        io.dwFlags = IOF_SELECTCREATENEW |
                                        IOF_DISABLELINK | IOF_DISABLEDISPLAYASICON |
                                        IOF_CREATENEWOBJECT | IOF_CREATEFILEOBJECT;
        io.hWndOwner = m_hDocWnd;
  //@@WTK WIN32, UNICODE
        //io.lpszCaption = (LPSTR)"Insert Object";
        io.lpszCaption = _T("Insert Object");
        io.iid = IID_IOleObject;
        io.oleRender = OLERENDER_DRAW;
        io.lpIOleClientSite = &m_lpSite->m_OleClientSite;
        io.lpIStorage = m_lpSite->m_lpObjStorage;
        io.ppvObj = (LPVOID FAR *)&m_lpSite->m_lpOleObject;
        io.lpszFile = szFile;
  //@@WTK WIN32, UNICODE
        //io.cchFile = sizeof(szFile);
        io.cchFile = sizeof(szFile) / sizeof *szFile;
  //@@WTK WIN32, UNICODE
        //_fmemset((LPSTR)szFile, 0, sizeof(szFile));
        memset(szFile, 0, sizeof(szFile));

        // call OUTLUI to do all the hard work
        iret = OleUIInsertObject(&io);

        if (iret == OLEUI_OK)
                {
                m_lpSite->InitObject((BOOL)(io.dwFlags & IOF_SELECTCREATENEW));
                // disable Insert Object menu item
                DisableInsertObject();
                }
        else
                {
                m_lpSite->Release();
                m_lpSite = NULL;
                m_lpStorage->Revert();
                }

}

//**********************************************************************
//
// CSimpleDoc::lResizeDoc
//
// Purpose:
//
//      Resizes the document
//
// Parameters:
//
//      LPRECT lpRect   -   The size of the client are of the "frame"
//                          Window.
//
// Return Value:
//
//      NULL
//
// Function Calls:
//      Function                                Location
//
//      IOleInPlaceActiveObject::ResizeBorder   Object
//      MoveWindow                              Windows API
//
// Comments:
//
//********************************************************************

long CSimpleDoc::lResizeDoc(LPRECT lpRect)
{
        CStabilize stabilize(this);
        // if we are InPlace, then call ResizeBorder on the object, otherwise
        // just move the document window.
        if (m_fInPlaceActive)
                m_lpActiveObject->ResizeBorder(lpRect, &m_lpApp->m_OleInPlaceFrame, TRUE);
        else
                MoveWindow(m_hDocWnd, lpRect->left, lpRect->top, lpRect->right, lpRect->bottom, TRUE);

        return NULL;
}

//**********************************************************************
//
// CSimpleDoc::lAddVerbs
//
// Purpose:
//
//      Adds the objects verbs to the edit menu.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      NULL
//
// Function Calls:
//      Function                    Location
//
//      GetMenuItemCount            Windows API
//      OleUIAddVerbMenu            OUTLUI function
//
// Comments:
//
//********************************************************************

long CSimpleDoc::lAddVerbs(void)
{
        CStabilize stabilize(this);
        // m_fModifiedMenu is TRUE if the menu has already been modified
        // once.  Since we only support one obect every time the application
        // is run, then once the menu is modified, it doesn't have
        // to be done again.
        if (m_lpSite && !m_fInPlaceActive  && !m_fModifiedMenu)
                {
                int nCount = GetMenuItemCount(m_lpApp->m_hEditMenu);

                OleUIAddVerbMenu ( m_lpSite->m_lpOleObject,
                                                   NULL,
                                                   m_lpApp->m_hEditMenu,
                                                   nCount + 1,
                                                   IDM_VERB0,
                                                   0,           // no maximum verb IDM enforced
                                                   FALSE,
                                                   0,
                                                   &m_lpApp->m_hCascadeMenu);

                m_fModifiedMenu = TRUE;
                }
        return (NULL);
}

//**********************************************************************
//
// CSimpleDoc::PaintDoc
//
// Purpose:
//
//      Paints the Document
//
// Parameters:
//
//      HDC hDC -   hDC of the document Window
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      CSimpleSite::PaintObj       SITE.CPP
//
// Comments:
//
//********************************************************************

void CSimpleDoc::PaintDoc (HDC hDC)
{
        CStabilize stabilize(this);
        // if we supported multiple objects, then we would enumerate
        // the objects and call paint on each of them from here.

        if (m_lpSite)
                m_lpSite->PaintObj(hDC);

}

//**********************************************************************
//
// CSimpleDoc::DisableInsertObject
//
// Purpose:
//
//      Disable the ability to insert a new object in this document.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      EnableMenuItem              Windows API
//
// Comments:
//
//      This implementation only allows one object to be inserted
//      into a document.  Once the object has been inserted, then
//      the Insert Object menu choice is greyed out, to prevent
//      the user from inserting another.
//
//********************************************************************

void CSimpleDoc::DisableInsertObject(void)
{
        // Disable InsertObject menu choice
        EnableMenuItem( m_lpApp->m_hEditMenu, 0, MF_BYPOSITION | MF_DISABLED | MF_GRAYED);
}


DOC.H

//**********************************************************************
// File name: doc.h
//
//      Definition of CSimpleDoc
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#if !defined( _DOC_H_ )
#define _DOC_H_

#include <stablize.h>

class CSimpleSite;
class CSimpleApp;

class CSimpleDoc : public IUnknown, public CSafeRefCount
{
public:
        LPSTORAGE       m_lpStorage;
        LPOLEINPLACEACTIVEOBJECT m_lpActiveObject;
        BOOL            m_fInPlaceActive;
        BOOL            m_fAddMyUI;
        BOOL            m_fModifiedMenu;

        CSimpleSite FAR * m_lpSite;
        CSimpleApp FAR * m_lpApp;

        HWND m_hDocWnd;

        static CSimpleDoc FAR * Create(CSimpleApp FAR *lpApp, LPRECT lpRect,HWND hWnd);

        void Close(void);

        CSimpleDoc();
        CSimpleDoc(CSimpleApp FAR *lpApp, HWND hWnd);
        ~CSimpleDoc();

        // IUnknown Interface
        STDMETHODIMP QueryInterface(REFIID riid, LPVOID FAR* ppvObj);
        STDMETHODIMP_(ULONG) AddRef();
        STDMETHODIMP_(ULONG) Release();

        void InsertObject(void);
        void DisableInsertObject(void);
        long lResizeDoc(LPRECT lpRect);
        long lAddVerbs(void);
        void PaintDoc(HDC hDC);
};

#endif


IAS.CPP

//**********************************************************************
// File name: IAS.CPP
//
//      Implementation file of CAdviseSink
//
//
// Functions:
//
//      See IAS.H for Class Definition
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "iocs.h"
#include "ias.h"
#include "ioipf.h"
#include "ioips.h"
#include "app.h"
#include "site.h"
#include "doc.h"

//**********************************************************************
//
// CAdviseSink::QueryInterface
//
// Purpose:
//
//      Returns a pointer to a requested interface.
//
// Parameters:
//
//      REFIID riid         - The requested interface
//
//      LPVOID FAR* ppvObj  - Place to return the interface
//
// Return Value:
//
//      HRESULT from CSimpleSite::QueryInterface
//
// Function Calls:
//      Function                    Location
//
//      CSimpleSite::QueryInterface SITE.CPP
//      OutputDebugString           Windows API
//
// Comments:
//
//      This function simply delegates to the Object class, which is
//      aware of the supported interfaces.
//
//********************************************************************

STDMETHODIMP CAdviseSink::QueryInterface(REFIID riid, LPVOID FAR* ppvObj)
{
        OutputDebugString("In IAS::QueryInterface\r\n");

        // delegate to the document Object
        return m_pSite->QueryInterface(riid, ppvObj);
}

//**********************************************************************
//
// CAdviseSink::AddRef
//
// Purpose:
//
//      Increments the reference count on this interface
//
// Parameters:
//
//      None
//
// Return Value:
//
//      The current reference count on this interface.
//
// Function Calls:
//      Function                    Location
//
//      CSimpleSite::AddReff        SITE.CPP
//      OutputDebugString           Windows API
//
// Comments:
//
//      This function adds one to the ref count of the interface,
//      and calls then calls CSimpleSite to increment its ref.
//      count.
//
//********************************************************************

STDMETHODIMP_(ULONG) CAdviseSink::AddRef()
{
        OutputDebugString("In IAS::AddRef\r\n");

        // increment the interface reference count (for debugging only)
        ++m_nCount;

        // delegate to the container Site
        return m_pSite->AddRef();
}

//**********************************************************************
//
// CAdviseSink::Release
//
// Purpose:
//
//      Decrements the reference count on this interface
//
// Parameters:
//
//      None
//
// Return Value:
//
//      The current reference count on this interface.
//
// Function Calls:
//      Function                    Location
//
//      CSimpleSite::Release        SITE.CPP
//      OutputDebugString           Windows API
//
// Comments:
//
//      This function subtracts one from the ref count of the interface,
//      and calls then calls CSimpleSite to decrement its ref.
//      count.
//
//********************************************************************

STDMETHODIMP_(ULONG) CAdviseSink::Release()
{
        OutputDebugString("In IAS::Release\r\n");

        // decrement the interface reference count (for debugging only)
        m_nCount--;

        // delegate to the container Site
        return m_pSite->Release();
}

//**********************************************************************
//
// CAdviseSink::OnDataChange
//
// Purpose:
//
//      Not Implemented (needs to be stubbed out)
//
// Parameters:
//
//      Not Implemented (needs to be stubbed out)
//
// Return Value:
//
//      Not Implemented (needs to be stubbed out)
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Not Implemented (needs to be stubbed out)
//
//********************************************************************

STDMETHODIMP_(void) CAdviseSink::OnDataChange (FORMATETC FAR* pFormatetc, STGMEDIUM FAR* pStgmed)
{
        OutputDebugString("In IAS::OnDataChange\r\n");
}

//**********************************************************************
//
// CAdviseSink::OnViewChange
//
// Purpose:
//
//      Notifies us that the view has changed and needs to be updated.
//
// Parameters:
//
//      DWORD dwAspect  - Aspect that has changed
//
//      LONG lindex     - Index that has changed
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      InvalidateRect              Windows API
//      IViewObject2::GetExtent     Object
//
// Comments:
//
//********************************************************************

STDMETHODIMP_(void) CAdviseSink::OnViewChange (DWORD dwAspect, LONG lindex)
{
        CStabilize stabilize(m_pSite);
        LPVIEWOBJECT2 lpViewObject2;
        OutputDebugString("In IAS::OnViewChange\r\n");

        // get a pointer to IViewObject2
        HRESULT hErr = m_pSite->m_lpOleObject->QueryInterface(
                        IID_IViewObject2,(LPVOID FAR *)&lpViewObject2);

        if (hErr == NOERROR) {
                // get extent of the object
                // NOTE: this method will never be remoted; it can be called w/i this async method
                lpViewObject2->GetExtent(DVASPECT_CONTENT, -1 , NULL, &m_pSite->m_sizel);
                lpViewObject2->Release();
        }

        InvalidateRect(m_pSite->m_lpDoc->m_hDocWnd, NULL, TRUE);
}

//**********************************************************************
//
// CAdviseSink::OnRename
//
// Purpose:
//
//      Not Implemented (needs to be stubbed out)
//
// Parameters:
//
//      Not Implemented (needs to be stubbed out)
//
// Return Value:
//
//      Not Implemented (needs to be stubbed out)
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Not Implemented (needs to be stubbed out)
//
//********************************************************************

STDMETHODIMP_(void) CAdviseSink::OnRename (LPMONIKER pmk)
{
        OutputDebugString("In IAS::OnRename\r\n");
}

//**********************************************************************
//
// CAdviseSink::OnSave
//
// Purpose:
//
//      Not Implemented (needs to be stubbed out)
//
// Parameters:
//
//      Not Implemented (needs to be stubbed out)
//
// Return Value:
//
//      Not Implemented (needs to be stubbed out)
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Not Implemented (needs to be stubbed out)
//
//********************************************************************

STDMETHODIMP_(void) CAdviseSink::OnSave ()
{
        OutputDebugString("In IAS::OnSave\r\n");
}

//**********************************************************************
//
// CAdviseSink::OnClose
//
// Purpose:
//
//      Not Implemented (needs to be stubbed out)
//
// Parameters:
//
//      Not Implemented (needs to be stubbed out)
//
// Return Value:
//
//      Not Implemented (needs to be stubbed out)
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Not Implemented (needs to be stubbed out)
//
//********************************************************************

STDMETHODIMP_(void) CAdviseSink::OnClose()
{
        OutputDebugString("In IAS::OnClose\r\n");
}


IAS.H

//**********************************************************************
// File name: IAS.H
//
//      Definition of CAdviseSink
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************
#if !defined( _IAS_H_ )
#define _IAS_H_

#include <assert.h>

class CSimpleSite;

interface CAdviseSink : public IAdviseSink
{
int m_nCount;
CSimpleSite FAR * m_pSite;

CAdviseSink(CSimpleSite FAR * pSite) {
OutputDebugString("In IAS's constructor\r\n");
m_pSite = pSite;
m_nCount = 0;
};

~CAdviseSink() {
OutputDebugString("In IAS's destructor\r\n");
assert(m_nCount == 0);
} ;

STDMETHODIMP QueryInterface (REFIID riid, LPVOID FAR* ppv);
STDMETHODIMP_(ULONG) AddRef ();
STDMETHODIMP_(ULONG) Release ();

// *** IAdviseSink methods ***
STDMETHODIMP_(void) OnDataChange (FORMATETC FAR* pFormatetc, STGMEDIUM FAR* pStgmed);
STDMETHODIMP_(void) OnViewChange (DWORD dwAspect, LONG lindex);
STDMETHODIMP_(void) OnRename (LPMONIKER pmk);
STDMETHODIMP_(void) OnSave ();
STDMETHODIMP_(void) OnClose ();
};

#endif


IOCS.CPP

//**********************************************************************
// File name: IOCS.CPP
//
//      Implementation file for COleClientSite
//
// Functions:
//
//      See IOCS.H for class definition
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "iocs.h"
#include "ias.h"
#include "ioipf.h"
#include "ioips.h"
#include "app.h"
#include "site.h"
#include "doc.h"

//**********************************************************************
//
// COleClientSite::QueryInterface
//
// Purpose:
//
//      Used for interface negotiation at this interface
//
// Parameters:
//
//      REFIID riid         -   A reference to the interface that is
//                              being queried.
//
//      LPVOID FAR* ppvObj  -   An out parameter to return a pointer to
//                              the interface.
//
// Return Value:
//
//      S_OK                -   The interface is supported.
//      E_NOINTERFACE       -   The interface is not supported
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpleSite::QueryInterface SITE.CPP
//
// Comments:
//
//********************************************************************

STDMETHODIMP COleClientSite::QueryInterface(REFIID riid, LPVOID FAR* ppvObj)
{
        OutputDebugString("In IOCS::QueryInterface\r\n");

        // delegate to the container Site
        return m_pSite->QueryInterface(riid, ppvObj);
}

//**********************************************************************
//
// CSimpleApp::AddRef
//
// Purpose:
//
//      Adds to the reference count at the interface level.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      ULONG   -   The new reference count of the interface
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//********************************************************************

STDMETHODIMP_(ULONG) COleClientSite::AddRef()
{
        OutputDebugString("In IOCS::AddRef\r\n");

        // increment the interface reference count (for debugging only)
        ++m_nCount;

        // delegate to the container Site
        return m_pSite->AddRef();
}

//**********************************************************************
//
// CSimpleApp::Release
//
// Purpose:
//
//      Decrements the reference count at this level
//
// Parameters:
//
//      None
//
// Return Value:
//
//      ULONG   -   The new reference count of the interface.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//********************************************************************

STDMETHODIMP_(ULONG) COleClientSite::Release()
{
        OutputDebugString("In IOCS::Release\r\n");

        // decrement the interface reference count (for debugging only)
        --m_nCount;

        // delegate to the container Site
        return m_pSite->Release();
}

//**********************************************************************
//
// COleClientSite::SaveObject
//
// Purpose:
//
//      Called by the object when it wants to be saved to persistant
//      storage
//
// Parameters:
//
//      None
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                            Location
//
//      OutputDebugString                   Windows API
//      IOleObject::QueryInterface          Object
//      IPersistStorage::SaveCompleted      Object
//      IPersistStorage::Release            Object
//      OleSave                             OLE API
//                           OLE API
//
// Comments:
//
//********************************************************************

STDMETHODIMP COleClientSite::SaveObject()
{
        CStabilize stabilize(m_pSite);
        LPPERSISTSTORAGE lpPS;
        SCODE sc = E_FAIL;

        OutputDebugString("In IOCS::SaveObject\r\n");

        // get a pointer to IPersistStorage
        HRESULT hErr = m_pSite->m_lpOleObject->QueryInterface(IID_IPersistStorage, (LPVOID FAR *)&lpPS);

        // save the object
        if (hErr == NOERROR)
                {
                sc =  OleSave(lpPS, m_pSite->m_lpObjStorage, TRUE) ;
                lpPS->SaveCompleted(NULL);
                lpPS->Release();
                }

        return sc;
}

//**********************************************************************
//
// COleClientSite::GetMoniker
//
// Purpose:
//
//      Not Implemented
//
// Parameters:
//
//      Not Implemented
//
// Return Value:
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      This function is not implemented because we don't support
//      linking.
//
//********************************************************************

STDMETHODIMP COleClientSite::GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker, LPMONIKER FAR* ppmk)
{
        OutputDebugString("In IOCS::GetMoniker\r\n");

        // need to null the out pointer
        *ppmk = NULL;

        return E_NOTIMPL;
}

//**********************************************************************
//
// COleClientSite::GetContainer
//
// Purpose:
//
//      Not Implemented
//
// Parameters:
//
//      Not Implemented
//
// Return Value:
//
//      Not Implemented
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Not Implemented
//
//********************************************************************

STDMETHODIMP COleClientSite::GetContainer(LPOLECONTAINER FAR* ppContainer)
{
        OutputDebugString("In IOCS::GetContainer\r\n");

        // NULL the out pointer
        *ppContainer = NULL;

        return E_NOTIMPL;
}

//**********************************************************************
//
// COleClientSite::ShowObject
//
// Purpose:
//
//      Not Implemented
//
// Parameters:
//
//      Not Implemented
//
// Return Value:
//
//      Not Implemented
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      This function is not implemented because we don't support
//      linking.
//
//********************************************************************

STDMETHODIMP COleClientSite::ShowObject()
{
        OutputDebugString("In IOCS::ShowObject\r\n");
        return NOERROR;
}

//**********************************************************************
//
// COleClientSite::OnShowWindow
//
// Purpose:
//
//      Object calls this method when it is opening/closing non-InPlace
//      Window
//
// Parameters:
//
//      BOOL fShow  - TRUE if Window is opening, FALSE if closing
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      InvalidateRect              Windows API
//                   OLE API
//
// Comments:
//
//********************************************************************

STDMETHODIMP COleClientSite::OnShowWindow(BOOL fShow)
{
        CStabilize stabilize(m_pSite);
        OutputDebugString("In IOCS::OnShowWindow\r\n");
        m_pSite->m_fObjectOpen = fShow;
        InvalidateRect(m_pSite->m_lpDoc->m_hDocWnd, NULL, TRUE);

        // if object window is closing, then bring container window to top
        if (! fShow) {
                BringWindowToTop(m_pSite->m_lpDoc->m_hDocWnd);
                SetFocus(m_pSite->m_lpDoc->m_hDocWnd);
        }
        return S_OK;
}

//**********************************************************************
//
// COleClientSite::RequestNewObjectLayout
//
// Purpose:
//
//      Not Implemented
//
// Parameters:
//
//      Not Implemented
//
// Return Value:
//
//      Not Implemented
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Not Implemented
//
//********************************************************************

STDMETHODIMP COleClientSite::RequestNewObjectLayout()
{
        OutputDebugString("In IOCS::RequestNewObjectLayout\r\n");
        return E_NOTIMPL;
}


IOCS.H

//**********************************************************************
// File name: IOCS.H
//
//      Definition of COleClientSite
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************
#if !defined( _IOCS_H_ )
#define _IOCS_H_

#include <assert.h>

class CSimpleSite;

interface COleClientSite : public IOleClientSite
{
int m_nCount;
CSimpleSite FAR * m_pSite;

COleClientSite(CSimpleSite FAR * pSite) {
OutputDebugString("In IOCS's constructor\r\n");
m_pSite = pSite;
m_nCount = 0;
}

~COleClientSite() {
OutputDebugString("In IOCS's destructor\r\n");
assert(m_nCount == 0);
}

STDMETHODIMP QueryInterface(REFIID riid, LPVOID FAR* ppvObj);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();

// *** IOleClientSite methods ***
STDMETHODIMP SaveObject();
STDMETHODIMP GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker, LPMONIKER FAR* ppmk);
STDMETHODIMP GetContainer(LPOLECONTAINER FAR* ppContainer);
STDMETHODIMP ShowObject();
STDMETHODIMP OnShowWindow(BOOL fShow);
STDMETHODIMP RequestNewObjectLayout();
};

#endif


IOIPF.CPP

//**********************************************************************
// File name: IOIPF.CPP
//
//      Implementation file for COleInPlaceFrame
//
// Functions:
//
//      See IOIPF.H for class definition
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "iocs.h"
#include "ias.h"
#include "ioipf.h"
#include "ioips.h"
#include "app.h"
#include "site.h"
#include "doc.h"

//**********************************************************************
//
// CSimpleApp::QueryInterface
//
// Purpose:
//
//      Used for interface negotiation at the Interface level.
//
// Parameters:
//
//      REFIID riid         -   A reference to the interface that is
//                              being queried.
//
//      LPVOID FAR* ppvObj  -   An out parameter to return a pointer to
//                              the interface.
//
// Return Value:
//
//      S_OK    -   The interface is supported.
//      S_FALSE -   The interface is not supported
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpleApp::QueryInterface  APP.CPP
//
// Comments:
//
//********************************************************************

STDMETHODIMP COleInPlaceFrame::QueryInterface(REFIID riid, LPVOID FAR* ppvObj)
{
        OutputDebugString("In IOIPF::QueryInterface\r\n");

// delegate to the document Object
        return m_pApp->QueryInterface(riid, ppvObj);
}

//**********************************************************************
//
// CSimpleApp::AddRef
//
// Purpose:
//
//      Adds to the reference count at the interface level.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      ULONG   -   The new reference count of the interface.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//********************************************************************

STDMETHODIMP_(ULONG) COleInPlaceFrame::AddRef()
{
        OutputDebugString("In IOIPF::AddRef\r\n");

// delegate to the document Object
        m_pApp->AddRef();
// increment the interface reference count
        return ++m_nCount;
}

//**********************************************************************
//
// CSimpleApp::Release
//
// Purpose:
//
//      Decrements the reference count at this level
//
// Parameters:
//
//      None
//
// Return Value:
//
//      ULONG   -   The new reference count of the interface.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpleApp::Release         APP.CPP
//
// Comments:
//
//********************************************************************

STDMETHODIMP_(ULONG) COleInPlaceFrame::Release()
{
        OutputDebugString("In IOIPF::Release\r\n");

// delegate to the document object
        m_pApp->Release();

// decrement the interface reference count
        return --m_nCount;
}

//**********************************************************************
//
// COleInPlaceFrame::GetWindow
//
// Purpose:
//
//      Returns the frame window handle
//
// Parameters:
//
//      HWND FAR* lphwnd    - Location to return the window handle
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//                   OLE API
//
// Comments:
//
//********************************************************************

STDMETHODIMP COleInPlaceFrame::GetWindow (HWND FAR* lphwnd)
{
        OutputDebugString("In IOIPF::GetWindow\r\n");
        CStabilize stabilize(m_pApp);
        *lphwnd = m_pApp->m_hAppWnd;
        return S_OK;
}

//**********************************************************************
//
// COleInPlaceFrame::ContextSensitiveHelp
//
// Purpose:
//
//      Used in implementing Context sensitive help
//
// Parameters:
//
//      BOOL fEnterMode -   TRUE if starting Context Sensitive help mode
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//                   OLE API
//
// Comments:
//
//      Be sure to read the technotes in the OLE toolkit.
//
//********************************************************************

STDMETHODIMP COleInPlaceFrame::ContextSensitiveHelp (BOOL fEnterMode)
{
        OutputDebugString("In IOIPF::ContextSensitiveHelp\r\n");
        CStabilize stabilize(m_pApp);

        m_pApp->m_fMenuMode = fEnterMode;

        return S_OK;
}
//**********************************************************************
//
// COleInPlaceFrame::GetBorder
//
// Purpose:
//
//      Returns the outermost border that frame adornments can be attached
//      during InPlace Activation.
//
// Parameters:
//
//      LPRECT lprectBorder - return parameter to contain the outermost
//                            rect for frame adornments
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      GetClientRect               Windows API
//      CopyRect                    Windows API
//                   OLE API
//
// Comments:
//
//********************************************************************

STDMETHODIMP COleInPlaceFrame::GetBorder (LPRECT lprectBorder)
{
        RECT rect;

        OutputDebugString("In IOIPF::GetBorder\r\n");

        // get the rect for the entire frame.
        GetClientRect(m_pApp->m_hAppWnd, &rect);

        CopyRect(lprectBorder, &rect);

        return S_OK;
}

//**********************************************************************
//
// COleInPlaceFrame::RequestBorderSpace
//
// Purpose:
//
//      Approves/Denies requests for border space during InPlace
//      negotiation.
//
// Parameters:
//
//      LPCBORDERWIDTHS lpborderwidths  - The width in pixels needed on
//                                        each side of the frame.
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//                   OLE API
//
// Comments:
//
//      This implementation doesn't care about how much border space
//      is used.  It always returns S_OK.
//
//********************************************************************

STDMETHODIMP COleInPlaceFrame::RequestBorderSpace (LPCBORDERWIDTHS lpborderwidths)
{
        OutputDebugString("In IOIPF::RequestBorderSpace\r\n");

        // always approve the request
        return S_OK;
}

//**********************************************************************
//
// COleInPlaceFrame::SetBorderSpace
//
// Purpose:
//
//      The object calls this method when it is actually going to
//      start using the border space.
//
// Parameters:
//
//      LPCBORDERWIDTHS lpborderwidths  - Border space actually being used
//                                        by the object
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                        Location
//
//      CSimpleApp::AddFrameLevelTools  APP.CPP
//      OutputDebugString               Windows API
//      GetClientRect                   Windows API
//      MoveWindow                      Windows API
//                       Windows API
//
// Comments:
//
//      This routine could be a little smarter and check to see if
//      the object is requesting the entire client area of the
//      window.
//
//********************************************************************

STDMETHODIMP COleInPlaceFrame::SetBorderSpace (LPCBORDERWIDTHS lpborderwidths)
{

        OutputDebugString("In IOIPF::SetBorderSpace\r\n");
        CStabilize stabilize(m_pApp);

        if (lpborderwidths == NULL)
                m_pApp->AddFrameLevelTools();
        else
                {
                RECT rect;

                GetClientRect(m_pApp->m_hAppWnd, &rect);

                MoveWindow( m_pApp->m_lpDoc->m_hDocWnd,
                                        rect.left + lpborderwidths->left,
                                        rect.top + lpborderwidths->top,
                                        rect.right - lpborderwidths->right - lpborderwidths->left,
                                        rect.bottom - lpborderwidths->bottom - lpborderwidths->top,
                                        TRUE);
                }
        return S_OK;
}

//**********************************************************************
//
// COleInPlaceFrame::SetActiveObject
//
// Purpose:
//
//
// Parameters:
//
//      LPOLEINPLACEACTIVEOBJECT lpActiveObject     -   Pointer to the
//                                                      objects
//                                                      IOleInPlaceActiveObject
//                                                      interface
//
//@@WTK WIN32, UNICODE
//      //LPCSTR lpszObjName                          -   Name of the object
//      LPCOLESTR lpszObjName                          -   Name of the object
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                            Location
//
//      OutputDebugString                   Windows API
//      IOleInPlaceActiveObject::AddRef     Object
//      IOleInPlaceActiveObject::Release    Object
//                           OLE API
//
// Comments:
//
//********************************************************************

//@@WTK WIN32, UNICODE
//STDMETHODIMP COleInPlaceFrame::SetActiveObject (LPOLEINPLACEACTIVEOBJECT lpActiveObject,LPCSTR lpszObjName)
STDMETHODIMP COleInPlaceFrame::SetActiveObject (
LPOLEINPLACEACTIVEOBJECT lpActiveObject,
LPCOLESTR lpszObjName)
{

        OutputDebugString("In IOIPF::SetActiveObject\r\n");
        CStabilize stabilize(m_pApp);

        // AddRef() it and save it...
        if (lpActiveObject)
                {
                lpActiveObject->AddRef();

                lpActiveObject->GetWindow(&m_pApp->m_hwndUIActiveObj);
                if (m_pApp->m_hwndUIActiveObj)
                        SendMessage(m_pApp->m_hwndUIActiveObj, WM_QUERYNEWPALETTE, 0, 0L);
                }
        else
                {
                if (m_pApp->m_lpDoc->m_lpActiveObject)
                        m_pApp->m_lpDoc->m_lpActiveObject->Release();
                m_pApp->m_hwndUIActiveObj = NULL;
                }

        // in an MDI app, this method really shouldn't be called,
        // this method associated with the doc is called instead.

        m_pApp->m_lpDoc->m_lpActiveObject = lpActiveObject;
        // should set window title here

        return S_OK;
}

//**********************************************************************
//
// COleInPlaceFrame::InsertMenus
//
// Purpose:
//
//      Inserts the container menu into the combined menu
//
// Parameters:
//
//      HMENU hmenuShared                   -   Menu Handle to be set.
//      LPOLEMENUGROUPWIDTHS lpMenuWidths   -   Width of menus
//
// Return Value:
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      AppendMenu                  Windows API
//                   OLE API
//
// Comments:
//
//********************************************************************

STDMETHODIMP COleInPlaceFrame::InsertMenus (HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths)
{
        OutputDebugString("In IOIPF::InsertMenus\r\n");
        CStabilize stabilize(m_pApp);

  //@@WTK WIN32, UNICODE
        //AppendMenu(hmenuShared, MF_BYPOSITION | MF_POPUP, m_pApp->m_hFileMenu, "&File");
        //AppendMenu(hmenuShared, MF_BYPOSITION | MF_POPUP, m_pApp->m_hHelpMenu, "&Other");
        AppendMenu(hmenuShared, MF_BYPOSITION | MF_POPUP, (UINT)m_pApp->m_hFileMenu, "&File");
        AppendMenu(hmenuShared, MF_BYPOSITION | MF_POPUP, (UINT)m_pApp->m_hHelpMenu, "&Other");

        lpMenuWidths->width[0] = 1;
        lpMenuWidths->width[2] = 0;
        lpMenuWidths->width[4] = 1;

        return S_OK;
}

//**********************************************************************
//
// COleInPlaceFrame::SetMenu
//
// Purpose:
//
//      Sets the application menu to the combined menu
//
// Parameters:
//
//      HMENU hmenuShared       - The combined menu
//
//      HOLEMENU holemenu       - Used by OLE
//
//      HWND hwndActiveObject   - Used by OLE
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      SetMenu                     Windows API
//      OleSetMenuDescriptor        OLE API
//                   OLE API
//
// Comments:
//
//********************************************************************

STDMETHODIMP COleInPlaceFrame::SetMenu (HMENU hmenuShared, HOLEMENU holemenu, HWND hwndActiveObject)
{

        OutputDebugString("In IOIPF::SetMenu\r\n");
        CStabilize stabilize(m_pApp);

        HMENU hMenu = m_pApp->m_hMainMenu;

        if (holemenu)
                hMenu = hmenuShared;

        // call the windows api, not this method
        ::SetMenu (m_pApp->m_hAppWnd, hMenu);

        OleSetMenuDescriptor(holemenu, m_pApp->m_hAppWnd, hwndActiveObject, this, m_pApp->m_lpDoc->m_lpActiveObject);

        return S_OK;
}

//**********************************************************************
//
// COleInPlaceFrame::RemoveMenus
//
// Purpose:
//
//      Removes the container menus from the combined menu
//
// Parameters:
//
//      HMENU hmenuShared   - Handle to the combined menu.
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      GetMenuItemCount            Windows API
//      RemoveMenu                  Windows API
//                   OLE API
//
// Comments:
//
//********************************************************************

STDMETHODIMP COleInPlaceFrame::RemoveMenus (HMENU hmenuShared)
{
        int retval;

        OutputDebugString("In IOIPF::RemoveMenus\r\n");

        while ((retval = GetMenuItemCount(hmenuShared)) && (retval != -1))
                RemoveMenu(hmenuShared, 0, MF_BYPOSITION);

        return S_OK;
}

//**********************************************************************
//
// COleInPlaceFrame::SetStatusText
//
// Purpose:
//
//      Not Implemented
//
// Parameters:
//
//      Not Implemented
//
// Return Value:
//
//      Not Implemented
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      This function is not implemented due to the fact
//      that this application does not have a status bar.
//
//********************************************************************

//@@WTK WIN32, UNICODE
//STDMETHODIMP COleInPlaceFrame::SetStatusText (LPCSTR lpszStatusText)
STDMETHODIMP COleInPlaceFrame::SetStatusText (LPCOLESTR lpszStatusText)
{
        OutputDebugString("In IOIPF::SetStatusText\r\n");
        return E_FAIL;
}

//**********************************************************************
//
// COleInPlaceFrame::EnableModeless
//
// Purpose:
//
//      Enables/Disables container modeless dialogs
//
// Parameters:
//
//      BOOL fEnable    - Enable/Disable
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      There are no modeless dialogs in this application, so the
//      implementation of this method is trivial.
//
//********************************************************************

STDMETHODIMP COleInPlaceFrame::EnableModeless (BOOL fEnable)
{
        OutputDebugString("In IOIPF::EnableModeless\r\n");
        return S_OK;
}

//**********************************************************************
//
// COleInPlaceFrame::TranslateAccelerator
//
// Purpose:
//
//      Not Implemented
//
// Parameters:
//
//      Not Implemented
//
// Return Value:
//
//      Not Implemented
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Not Implemented
//
//********************************************************************

STDMETHODIMP COleInPlaceFrame::TranslateAccelerator (LPMSG lpmsg, WORD wID)
{
        OutputDebugString("In IOIPF::TranslateAccelerator\r\n");
        return S_FALSE;
}


IOIPF.H

//**********************************************************************
// File name: IOIPF.H
//
//      Definition of COleInPlaceFrame
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************
#if !defined( _IOIPF_H_ )
#define _IOIPF_H_

class CSimpleApp;

interface COleInPlaceFrame : public IOleInPlaceFrame
{
int m_nCount;
CSimpleApp FAR * m_pApp;

COleInPlaceFrame(CSimpleApp FAR * pApp) {
OutputDebugString("In IOIPF's constructor\r\n");
m_pApp = pApp;
m_nCount = 0;
};

~COleInPlaceFrame() {
OutputDebugString("In IOIPFS's destructor\r\n");
assert(m_nCount == 0);
};

STDMETHODIMP QueryInterface (REFIID riid, LPVOID FAR* ppv);
STDMETHODIMP_(ULONG) AddRef ();
STDMETHODIMP_(ULONG) Release ();

STDMETHODIMP GetWindow (HWND FAR* lphwnd);
STDMETHODIMP ContextSensitiveHelp (BOOL fEnterMode);

// *** IOleInPlaceUIWindow methods ***
STDMETHODIMP GetBorder (LPRECT lprectBorder);
STDMETHODIMP RequestBorderSpace (LPCBORDERWIDTHS lpborderwidths);
STDMETHODIMP SetBorderSpace (LPCBORDERWIDTHS lpborderwidths);
  //@@WTK WIN32, UNICODE
//STDMETHODIMP SetActiveObject (LPOLEINPLACEACTIVEOBJECT lpActiveObject,LPCSTR lpszObjName);
STDMETHODIMP SetActiveObject (LPOLEINPLACEACTIVEOBJECT lpActiveObject,LPCOLESTR lpszObjName);

// *** IOleInPlaceFrame methods ***
STDMETHODIMP InsertMenus (HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths);
STDMETHODIMP SetMenu (HMENU hmenuShared, HOLEMENU holemenu, HWND hwndActiveObject);
STDMETHODIMP RemoveMenus (HMENU hmenuShared);
  //@@WTK WIN32, UNICODE
//STDMETHODIMP SetStatusText (LPCSTR lpszStatusText);
STDMETHODIMP SetStatusText (LPCOLESTR lpszStatusText);
STDMETHODIMP EnableModeless (BOOL fEnable);
STDMETHODIMP TranslateAccelerator (LPMSG lpmsg, WORD wID);
};

#endif


IOIPS.CPP

//**********************************************************************
// File name: IOIPS.CPP
//
//      Implementation file for COleInPlaceSite
//
// Functions:
//
//      See IOIPS.H for class Definition
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "iocs.h"
#include "ias.h"
#include "ioipf.h"
#include "ioips.h"
#include "app.h"
#include "site.h"
#include "doc.h"

//**********************************************************************
//
// CSimpleSite::COleInPlaceSite::QueryInterface
//
// Purpose:
//
//      Used for interface negotiation at the interface level.
//
// Parameters:
//
//      REFIID riid         -   A reference to the interface that is
//                              being queried.
//
//      LPVOID FAR* ppvObj  -   An out parameter to return a pointer to
//                              the interface.
//
// Return Value:
//
//      S_OK    -   The interface is supported.
//      S_FALSE -   The interface is not supported
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpleSite::QueryInterface SITE.CPP
//
// Comments:
//
//********************************************************************

STDMETHODIMP COleInPlaceSite::QueryInterface(REFIID riid, LPVOID FAR* ppvObj)
{
        OutputDebugString("In IOIPS::QueryInterface\r\n");
        CStabilize stabilize(m_pSite);

        // delegate to the container Site
        return m_pSite->QueryInterface(riid, ppvObj);
}

//**********************************************************************
//
// CSimpleSite::COleInPlaceSite::AddRef
//
// Purpose:
//
//      Adds to the reference count at the interface level.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      ULONG   -   The new reference count of the interface.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//********************************************************************

STDMETHODIMP_(ULONG) COleInPlaceSite::AddRef()
{
        OutputDebugString("In IOIPS::AddRef\r\n");

        // increment the interface reference count (for debugging only)
        ++m_nCount;

        // delegate to the container Site
        return m_pSite->AddRef();
}

//**********************************************************************
//
// CSimpleSite::COleInPlaceSite::Release
//
// Purpose:
//
//      Decrements the reference count at this level
//
// Parameters:
//
//      None
//
// Return Value:
//
//      ULONG   -   The new reference count of the interface.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpleSite::Release        SITE.CPP
//
// Comments:
//
//********************************************************************

STDMETHODIMP_(ULONG) COleInPlaceSite::Release()
{
        OutputDebugString("In IOIPS::Release\r\n");
        // decrement the interface reference count (for debugging only)
        m_nCount--;

        // delegate to the container Site
        return m_pSite->Release();
}

//**********************************************************************
//
// COleInPlaceSite::GetWindow
//
// Purpose:
//
//      Returns the Window Handle of the client site
//
// Parameters:
//
//      HWND FAR* lphwnd    - place to return the handle
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//                   OLE API
//
// Comments:
//
//********************************************************************

STDMETHODIMP COleInPlaceSite::GetWindow (HWND FAR* lphwnd)
{
        OutputDebugString("In IOIPS::GetWindow\r\n");
        CStabilize stabilize(m_pSite);

        // return the handle to our editing window.
        *lphwnd = m_pSite->m_lpDoc->m_hDocWnd;

        return S_OK;
}

//**********************************************************************
//
// COleInPlaceSite::ContextSensitiveHelp
//
// Purpose:
//
//
// Parameters:
//
//      BOOL fEnterMode - TRUE for entering Context Sensitive help mode
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//                   OLE API
//
// Comments:
//
//      Be sure to read the technotes included with the OLE toolkit.
//
//********************************************************************

STDMETHODIMP COleInPlaceSite::ContextSensitiveHelp (BOOL fEnterMode)
{
        OutputDebugString("In IOIPS::ContextSensitiveHelp\r\n");

        if (m_pSite->m_lpDoc->m_lpApp->m_fCSHMode != fEnterMode)
                m_pSite->m_lpDoc->m_lpApp->m_fCSHMode = fEnterMode;

        return S_OK;
}

//**********************************************************************
//
// COleInPlaceSite::CanInPlaceActivate
//
// Purpose:
//
//      Object calls to find out if the container can InPlace activate
//
// Parameters:
//
//      None
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//                   OLE API
//
// Comments:
//
//********************************************************************

STDMETHODIMP COleInPlaceSite::CanInPlaceActivate ()
{
        OutputDebugString("In IOIPS::CanInPlaceActivate\r\n");

        // return S_OK to indicate we can in-place activate
        return S_OK;
}

//**********************************************************************
//
// COleInPlaceSite::OnInPlaceActivate
//
// Purpose:
//
//      Called by the object on InPlace Activation
//
// Parameters:
//
//      None
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//                   OLE API
//
// Comments:
//
//********************************************************************

STDMETHODIMP COleInPlaceSite::OnInPlaceActivate ()
{
        HRESULT hrErr;
        OutputDebugString("In IOIPS::OnInPlaceActivate\r\n");
        CStabilize stabilize(m_pSite);

        hrErr = m_pSite->m_lpOleObject->QueryInterface(
                        IID_IOleInPlaceObject, (LPVOID FAR *)&m_pSite->m_lpInPlaceObject);
        if (hrErr != NOERROR)
                return E_FAIL;

        // return S_OK to indicate we can in-place activate.
        return S_OK;
}

//**********************************************************************
//
// COleInPlaceSite::OnUIActivate
//
// Purpose:
//
//      Object calls this method when it displays it's UI.
//
// Parameters:
//
//      None.
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//                   OLE API
//
// Comments:
//
//********************************************************************

STDMETHODIMP COleInPlaceSite::OnUIActivate ()
{
        OutputDebugString("In IOIPS::OnUIActivate\r\n");

        m_pSite->m_lpDoc->m_fAddMyUI=FALSE;
        m_pSite->m_lpDoc->m_fInPlaceActive = TRUE;
        m_pSite->m_fInPlaceActive = TRUE;

        m_pSite->m_lpInPlaceObject->GetWindow((HWND FAR*)&m_pSite->m_hwndIPObj);

        // return S_OK to continue in-place activation
        return S_OK;
}

//**********************************************************************
//
// COleInPlaceSite::GetWindowContext
//
// Purpose:
//
//      Called by the object to get information for InPlace Negotiation.
//
// Parameters:
//
//      LPOLEINPLACEFRAME FAR* lplpFrame    - Location to return a pointer
//                                            to IOleInPlaceFrame.
//
//      LPOLEINPLACEUIWINDOW FAR* lplpDoc   - Location to return a pointer
//                                            to IOleInPlaceUIWindow.
//
//      LPRECT lprcPosRect                  - The rect that the object
//                                            occupies
//
//      LPRECT lprcClipRect                 - The clipping rect
//
//      LPOLEINPLACEFRAMEINFO lpFrameInfo   - Pointer to FRAMEINFO
//
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      COleInPlaceFrame::AddRef    IOIPF.CPP
//      CSimpleSite::GetObjRect     SITE.CPP
//      OutputDebugString           Windows API
//      SetMapMode                  Windows API
//      GetDC                       Windows API
//      ReleaseDC                   Windows API
//      CopyRect                    Windows API
//      GetClientRect               Windows API
//                   OLE API
//
// Comments:
//
//********************************************************************

STDMETHODIMP COleInPlaceSite::GetWindowContext (LPOLEINPLACEFRAME FAR* lplpFrame,
                                                           LPOLEINPLACEUIWINDOW FAR* lplpDoc,
                                                           LPRECT lprcPosRect,
                                                           LPRECT lprcClipRect,
                                                           LPOLEINPLACEFRAMEINFO lpFrameInfo)
{
        RECT rect;
        CStabilize stabilize(m_pSite);

        OutputDebugString("In IOIPS::GetWindowContext\r\n");

        // the frame is associated with the application object.
        // need to AddRef() it...
        m_pSite->m_lpDoc->m_lpApp->m_OleInPlaceFrame.AddRef();
        *lplpFrame = &m_pSite->m_lpDoc->m_lpApp->m_OleInPlaceFrame;
        *lplpDoc = NULL;  // must be NULL, cause we're SDI.

        // get the size of the object in pixels
        m_pSite->GetObjRect(&rect);

        // Copy this to the passed buffer
        CopyRect(lprcPosRect, &rect);

        // fill the clipping region
        GetClientRect(m_pSite->m_lpDoc->m_hDocWnd, &rect);
        CopyRect(lprcClipRect, &rect);

        // fill the FRAMEINFO
        lpFrameInfo->fMDIApp = FALSE;
        lpFrameInfo->hwndFrame = m_pSite->m_lpDoc->m_lpApp->m_hAppWnd;
        lpFrameInfo->haccel = NULL;
        lpFrameInfo->cAccelEntries = 0;

        return S_OK;
}

//**********************************************************************
//
// COleInPlaceSite::Scroll
//
// Purpose:
//
//      Not Implemented
//
// Parameters:
//
//      Not Implemented
//
// Return Value:
//
//      Not Implemented
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Not Implemented
//
//********************************************************************

STDMETHODIMP COleInPlaceSite::Scroll (SIZE scrollExtent)
{
        OutputDebugString("In IOIPS::Scroll\r\n");
        return E_FAIL;
}

//**********************************************************************
//
// COleInPlaceSite::OnUIDeactivate
//
// Purpose:
//
//      Called by the object when its UI goes away
//
// Parameters:
//
//       BOOL fUndoable
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpleAPP::AddFrameLevelUI APP.CPP
//                   OLE API
//
// Comments:
//
//********************************************************************

STDMETHODIMP COleInPlaceSite::OnUIDeactivate (BOOL fUndoable)
{
        OutputDebugString("In IOIPS::OnUIDeactivate\r\n");
        CStabilize stabilize(m_pSite);

        // need to clear this flag first
        m_pSite->m_lpDoc->m_fInPlaceActive = FALSE;
        m_pSite->m_fInPlaceActive = FALSE;

        m_pSite->m_lpDoc->m_lpApp->QueryNewPalette();
        m_pSite->m_lpDoc->m_lpApp->AddFrameLevelUI();
        return S_OK;
}

//**********************************************************************
//
// COleInPlaceSite::OnInPlaceDeactivate
//
// Purpose:
//
//      Called when the inplace session is over
//
// Parameters:
//
//      None
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//                   OLE API
//
// Comments:
//
//********************************************************************

STDMETHODIMP COleInPlaceSite::OnInPlaceDeactivate ()
{
        OutputDebugString("In IOIPS::OnInPlaceDeactivate\r\n");

        if (m_pSite->m_lpInPlaceObject) {
                m_pSite->m_lpInPlaceObject->Release();
                m_pSite->m_lpInPlaceObject = NULL;
        }
        return S_OK;
}

//**********************************************************************
//
// COleInPlaceSite::DiscardUndoState
//
// Purpose:
//
//      Not Implemented
//
// Parameters:
//
//      Not Implemented
//
// Return Value:
//
//      Not Implemented
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Not Implemented
//
//********************************************************************

STDMETHODIMP COleInPlaceSite::DiscardUndoState ()
{
        OutputDebugString("In IOIPS::DiscardUndoState\r\n");
        return E_FAIL;
}

//**********************************************************************
//
// COleInPlaceSite::DeactivateAndUndo
//
// Purpose:
//
//      Not Implemented
//
// Parameters:
//
//      Not Implemented
//
// Return Value:
//
//      Not Implemented
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Not Implemented
//
//********************************************************************

STDMETHODIMP COleInPlaceSite::DeactivateAndUndo ()
{
        OutputDebugString("In IOIPS::DeactivateAndUndo\r\n");
        return E_FAIL;
}

//**********************************************************************
//
// COleInPlaceSite::OnPosRectChange
//
// Purpose:
//
//      The object calls this method when it's size changes during an
//      InPlace Session
//
// Parameters:
//
//      LPCRECT lprcPosRect -   The new object rect
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                            Location
//
//      OutputDebugString                   Windows API
//      GetClientRect                       Windows API
//      IOleObject::GetExtent               Object
//      IOleObject::QueryInterface          Object
//      IOleInPlaceObject::SetObjectRects   Object
//      IOleInPlaceObject::Release          Object
//                           OLE API
//
// Comments:
//
//********************************************************************

STDMETHODIMP COleInPlaceSite::OnPosRectChange (LPCRECT lprcPosRect)
{
        OutputDebugString("In IOIPS::OnPosRectChange\r\n");
        CStabilize stabilize(m_pSite);

        // update the size in the document object
        // NOTE: here we must call IOleObject::GetExtent to get actual extents
        //       of the running object. IViewObject2::GetExtent returns the
        //       last cached extents.
        m_pSite->m_lpOleObject->GetExtent(DVASPECT_CONTENT, &m_pSite->m_sizel);
        RECT rect;
        GetClientRect(m_pSite->m_lpDoc->m_hDocWnd, &rect);

        // tell the object its new size
        m_pSite->m_lpInPlaceObject->SetObjectRects(lprcPosRect, &rect);

        return S_OK;
}


IOIPS.H

//**********************************************************************
// File name: IOIPS.H
//
//      Definition of COleInPlaceSite
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************
#if !defined( _IOIPS_H_ )
#define _IOIPS_H_

class CSimpleSite;

interface COleInPlaceSite : public IOleInPlaceSite
{
int m_nCount;
CSimpleSite FAR * m_pSite;

COleInPlaceSite(CSimpleSite FAR *pSite) {
OutputDebugString("In IOIPS's constructor\r\n");
m_pSite = pSite;
m_nCount = 0;
};

~COleInPlaceSite() {
OutputDebugString("In IOIPS;s destructor\r\n");
assert(m_nCount == 0);
};

STDMETHODIMP QueryInterface (REFIID riid, LPVOID FAR* ppv);
STDMETHODIMP_(ULONG) AddRef ();
STDMETHODIMP_(ULONG) Release ();

STDMETHODIMP GetWindow (HWND FAR* lphwnd);
STDMETHODIMP ContextSensitiveHelp (BOOL fEnterMode);

// *** IOleInPlaceSite methods ***
STDMETHODIMP CanInPlaceActivate ();
STDMETHODIMP OnInPlaceActivate ();
STDMETHODIMP OnUIActivate ();
STDMETHODIMP GetWindowContext (LPOLEINPLACEFRAME FAR* lplpFrame,
   LPOLEINPLACEUIWINDOW FAR* lplpDoc,
   LPRECT lprcPosRect,
   LPRECT lprcClipRect,
   LPOLEINPLACEFRAMEINFO lpFrameInfo);
STDMETHODIMP Scroll (SIZE scrollExtent);
STDMETHODIMP OnUIDeactivate (BOOL fUndoable);
STDMETHODIMP OnInPlaceDeactivate ();
STDMETHODIMP DiscardUndoState ();
STDMETHODIMP DeactivateAndUndo ();
STDMETHODIMP OnPosRectChange (LPCRECT lprcPosRect);
};

#endif


PRE.H

//**********************************************************************
// File name: pre.h
//
//      Used for precompiled headers
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#if !defined( _PRE_H_)
#define _PRE_H_

#include <windows.h>
#include <ole2.h>
#include "OLEDLG.h"
#include <assert.h>
#include <string.h>
#include "simpcntr.h"
#include "resource.h"
#include <ole2ver.h>

#endif


APP.CPP

//**********************************************************************
// File name: app.cpp
//
//    Implementation file for the CSimpleApp Class
//
// Functions:
//
//    See app.h for a list of member functions.
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "iocs.h"
#include "ias.h"
#include "ioipf.h"
#include "ioips.h"
#include "app.h"
#include "site.h"
#include "doc.h"
#include "stdpal.h"

#define cpeAppPal 256  // number of colors in our apps palette
typedef struct
   {
   WORD wVersion;
   WORD cpe;
   PALETTEENTRY rgpe[cpeAppPal];
   } LOGPAL;

//**********************************************************************
//
// CreateStandardPalette
//
// Purpose:
//
//      Creates a standard Apps palette.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
//********************************************************************
HPALETTE CreateStandardPalette(void)
   {
   HDC hdc;
   HPALETTE hpal;

   hpal = (HPALETTE) NULL;
   hdc = GetDC(NULL);
   if (hdc != NULL && GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE)
      {
      int cpeSysPal;
      int cpeReserved;

      cpeSysPal = GetDeviceCaps(hdc, SIZEPALETTE);
      cpeReserved = GetDeviceCaps(hdc, NUMRESERVED);
      if (cpeSysPal > cpeReserved)
         {
         int cpeReserved2;
         unsigned char FAR* lpb;
         PALETTEENTRY FAR* ppe;
         PALETTEENTRY FAR* ppeMac;
         LOGPAL logpal;

         cpeReserved2 = cpeReserved/2;

         // Get the system palette entries at the beginning and end.
         GetSystemPaletteEntries(hdc, 0, cpeReserved2, logpal.rgpe);
         GetSystemPaletteEntries(hdc, cpeSysPal - cpeReserved2, cpeReserved2,
            &logpal.rgpe[cpeAppPal-cpeReserved2]);

         logpal.cpe = cpeAppPal;
         logpal.wVersion = 0x300;

         lpb = (BYTE FAR *) &palSVGA[10];
         ppe = (PALETTEENTRY FAR*)&logpal.rgpe[cpeReserved2];
         ppeMac = (PALETTEENTRY FAR*)&logpal.rgpe[cpeAppPal-cpeReserved2];
         while (ppe < ppeMac)
            {
            ppe->peFlags = PC_NOCOLLAPSE;
            ppe->peRed   = *lpb++;
            ppe->peGreen = *lpb++;
            ppe->peBlue  = *lpb++;
            ppe++;
            }
         hpal = CreatePalette((LOGPALETTE FAR *)&logpal);
         }
      }
   ReleaseDC(NULL, hdc);
   return hpal;
   }

//**********************************************************************
//
// CSimpleApp::CSimpleApp()
//
// Purpose:
//
//      Constructor for CSimpleApp
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      SetRectEmpty                Windows API
//
// Comments:
//
//      CSimpleApp has a contained COleInPlaceFrame.  On construction
//      of CSimpleApp, we explicitly call the constructor of this
//      contained class and pass a copy of the this pointer, so that
//      COleInPlaceFrame can refer back to this class
//
//********************************************************************
#pragma warning(disable : 4355)  // turn off this warning.  This warning
                                                                // tells us that we are passing this in
                                                                // an initializer, before "this" is through
                                                                // initializing.  This is ok, because
                                                                // we just store the ptr in the other
                                                                // constructor

CSimpleApp::CSimpleApp() : m_OleInPlaceFrame(this)
#pragma warning (default : 4355)  // Turn the warning back on
{
        OutputDebugString("In CSimpleApp's Constructor \r\n");

        // clear members
        m_hAppWnd = NULL;
        m_hInst = NULL;
        m_lpDoc = NULL;
    m_hwndUIActiveObj = NULL;

        // clear flags
        m_fInitialized = FALSE;
        m_fCSHMode = FALSE;
        m_fMenuMode = FALSE;

        // used for inplace
        SetRectEmpty(&nullRect);
}

//**********************************************************************
//
// CSimpleApp::~CSimpleApp()
//
// Purpose:
//
//      Destructor for CSimpleApp Class.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      OleUninitialize             OLE API
//
// Comments:
//
//********************************************************************

CSimpleApp::~CSimpleApp()
{
        OutputDebugString("In CSimpleApp's Destructor\r\n");

        if (m_hStdPal)
                DeleteObject(m_hStdPal);

        // need to uninit the library...
        if (m_fInitialized)
                OleUninitialize();
}

//**********************************************************************
//
// CSimpleApp::DestroyDocs()
//
// Purpose:
//
//      Destroys all of the open documents in the application (Only one
//      since this is an SDI app, but could easily be modified to
//      support MDI).
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
// Comments:
//
//********************************************************************

void CSimpleApp::DestroyDocs()
{
        CStabilize stabilize(this);
        m_lpDoc->Close();   // we have only 1 document
}

//**********************************************************************
//
// CSimpleApp::QueryInterface
//
// Purpose:
//
//      Used for interface negotiation at the Frame level.
//
// Parameters:
//
//      REFIID riid         -   A reference to the interface that is
//                              being queried.
//
//      LPVOID FAR* ppvObj  -   An out parameter to return a pointer to
//                              the interface.
//
// Return Value:
//
//      S_OK    -   The interface is supported.
//      S_FALSE -   The interface is not supported
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      IsEqualIID                  OLE API
//                   OLE API
//      COleInPlaceFrame::AddRef    IOIPF.CPP
//
// Comments:
//
//      Note that this QueryInterface is associated with the frame.
//      Since the application could potentially have multiple documents
//      and multiple objects, a lot of the interfaces are ambiguous.
//      (ie. which IOleObject is returned?).  For this reason, only
//      pointers to interfaces associated with the frame are returned.
//      In this implementation, Only IOleInPlaceFrame (or one of the
//      interfaces it is derived from) can be returned.
//
//********************************************************************

STDMETHODIMP CSimpleApp::QueryInterface(REFIID riid, LPVOID FAR* ppvObj)
{
        OutputDebugString("In CSimpleApp::QueryInterface\r\n");

        *ppvObj = NULL;     // must set out pointer parameters to NULL

        // looking for IUnknown
        if ( riid == IID_IUnknown)
                {
                AddRef();
                *ppvObj = this;
                return S_OK;
                }

        // looking for IOleWindow
        if ( riid == IID_IOleWindow)
                {
                m_OleInPlaceFrame.AddRef();
                *ppvObj=&m_OleInPlaceFrame;
                return S_OK;
                }

        // looking for IOleInPlaceUIWindow
        if ( riid == IID_IOleInPlaceUIWindow)
                {
                m_OleInPlaceFrame.AddRef();
                *ppvObj=&m_OleInPlaceFrame;
                return S_OK;
                }

        // looking for IOleInPlaceFrame
        if ( riid == IID_IOleInPlaceFrame)
                {
                m_OleInPlaceFrame.AddRef();
                *ppvObj=&m_OleInPlaceFrame;
                return S_OK;
                }

        // Not a supported interface
        return E_NOINTERFACE;
}

//**********************************************************************
//
// CSimpleApp::AddRef
//
// Purpose:
//
//      Adds to the reference count at the Application level.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      ULONG   -   The new reference count of the application.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Due to the reference counting model that is used in this
//      implementation, this reference count is the sum of the
//      reference counts on all interfaces of all objects open
//      in the application.
//
//********************************************************************

STDMETHODIMP_(ULONG) CSimpleApp::AddRef()
{
        OutputDebugString("In CSimpleApp::AddRef\r\n");
        return SafeAddRef();
}

//**********************************************************************
//
// CSimpleApp::Release
//
// Purpose:
//
//      Decrements the reference count at this level
//
// Parameters:
//
//      None
//
// Return Value:
//
//      ULONG   -   The new reference count of the application.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//********************************************************************

STDMETHODIMP_(ULONG) CSimpleApp::Release()
{
        OutputDebugString("In CSimpleApp::Release\r\n");

        return SafeRelease();
}

//**********************************************************************
//
// CSimpleApp::fInitApplication
//
// Purpose:
//
//      Initializes the application
//
// Parameters:
//
//      HANDLE hInstance    -   Instance handle of the application.
//
// Return Value:
//
//      TRUE    -   Application was successfully initialized.
//      FALSE   -   Application could not be initialized
//
// Function Calls:
//      Function                    Location
//
//      LoadIcon                    Windows API
//      LoadCursor                  Windows API
//      GetStockObject              Windows API
//      RegisterClass               Windows API
//
// Comments:
//
//********************************************************************

BOOL CSimpleApp::fInitApplication(HANDLE hInstance)
{
        WNDCLASS  wc;

        // Fill in window class structure with parameters that describe the
        // main window.

        wc.style = NULL;                    // Class style(s).
        wc.lpfnWndProc = MainWndProc;       // Function to retrieve messages for
                                                                                // windows of this class.
        wc.cbClsExtra = 0;                  // No per-class extra data.
        wc.cbWndExtra = 0;                  // No per-window extra data.
        wc.hInstance = (HINSTANCE) hInstance; // Application that owns the class.
        wc.hIcon = LoadIcon((HINSTANCE) hInstance, "SimpCntr");
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
        wc.lpszMenuName =  "SIMPLEMENU";    // Name of menu resource in .RC file.
        wc.lpszClassName = "SimpCntrAppWClass";  // Name used in CreateWindow call

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

        wc.style = CS_DBLCLKS;              // Class style(s). allow DBLCLK's
        wc.lpfnWndProc = DocWndProc;        // Function to retrieve messages for
                                                                                // windows of this class.
        wc.cbClsExtra = 0;                  // No per-class extra data.
        wc.cbWndExtra = 0;                  // No per-window extra data.
        wc.hInstance = (HINSTANCE) hInstance;           // Application that owns the class.
        wc.hIcon = NULL;
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
        wc.lpszMenuName =  NULL;
        wc.lpszClassName = "SimpCntrDocWClass"; // Name used in CreateWindow call.

        // Register the window class and return success/failure code.

        return (RegisterClass(&wc));
}

//**********************************************************************
//
// CSimpleApp::fInitInstance
//
// Purpose:
//
//      Instance initialization.
//
// Parameters:
//
//      HANDLE hInstance    -   App. Instance Handle.
//
//      int nCmdShow        -   Show parameter from WinMain
//
// Return Value:
//
//      TRUE    -   Initialization Successful
//      FALSE   -   Initialization Failed.
//
//
// Function Calls:
//      Function                    Location
//
//      CreateWindow                Windows API
//      ShowWindow                  Windows API
//      UpdateWindow                Windows API
//      OleBuildVersion             OLE API
//      OleInitialize               OLE API
//
// Comments:
//
//      Note that successful Initalization of the OLE libraries
//      is remembered so the UnInit is only called if needed.
//
//********************************************************************

BOOL CSimpleApp::fInitInstance (HANDLE hInstance, int nCmdShow)
{
        DWORD dwVer = OleBuildVersion();
        LPMALLOC lpMalloc = NULL;

        // check to see if we are compatible with this version of the libraries
        if (HIWORD(dwVer) != rmm || LOWORD(dwVer) < rup) {
#ifdef _DEBUG
                OutputDebugString("WARNING: Incompatible OLE library version\r\n");
#else
                return FALSE;
#endif
        }

        if (SUCCEEDED(OleInitialize(lpMalloc)))
        {
                m_fInitialized = TRUE;
        }
        else
        {
            // Replacing the standard allocator may not be legal.
            // Try again using the default allocator.
            if (SUCCEEDED(OleInitialize(NULL)))
            {
                m_fInitialized = TRUE;
            }
        }

        m_hInst = (HINSTANCE) hInstance;

        // Create the "application" windows
        m_hAppWnd = CreateWindow ("SimpCntrAppWClass",
                                                          "Simple OLE 2.0 In-Place Container",
                                                          WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
                                                          CW_USEDEFAULT,
                                                          CW_USEDEFAULT,
                                                          CW_USEDEFAULT,
                                                          CW_USEDEFAULT,
                                                          NULL,
                                                          NULL,
                                                          (HINSTANCE) hInstance,
                                                          NULL);

        if (!m_hAppWnd)
                return FALSE;

        m_hStdPal = CreateStandardPalette();

        ShowWindow (m_hAppWnd, nCmdShow);
        UpdateWindow (m_hAppWnd);

        return m_fInitialized;
}

//**********************************************************************
//
// CSimpleApp::lCommandHandler
//
// Purpose:
//
//      Handles the processing of WM_COMMAND.
//
// Parameters:
//
//      HWND hWnd       -   Handle to the application Window
//
//      UINT message    -   message (always WM_COMMAND)
//
//      WPARAM wParam   -   Same as passed to the WndProc
//
//      LPARAM lParam   -   Same as passed to the WndProc
//
// Return Value:
//
//      NULL
//
// Function Calls:
//      Function                                    Location
//
//      IOleInPlaceActiveObject::QueryInterface     Object
//      IOleInPlaceObject::ContextSensitiveHelp     Object
//      IOleInPlaceObject::Release                  Object
//      IOleObject::DoVerb                          Object
//      GetClientRect                               Windows API
//      MessageBox                                  Windows API
//      DialogBox                                   Windows API
//      MakeProcInstance                            Windows API
//      FreeProcInstance                            Windows API
//      SendMessage                                 Windows API
//      DefWindowProc                               Windows API
//      CSimpleDoc::InsertObject                    DOC.CPP
//
// Comments:
//
//********************************************************************

long CSimpleApp::lCommandHandler (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
        CStabilize stabilize(this);
        RECT rect;

        // context sensitive help...
        if (m_fMenuMode || m_fCSHMode)
                {
                if (m_fCSHMode)
                        {
                        // clear context sensitive help flag
                        m_fCSHMode = FALSE;

                        // if there is an InPlace active object, call its context sensitive help
                        // method with the FALSE parameter to bring the object out of the
                        // csh state.  See the technotes for details.
                        if (m_lpDoc->m_lpActiveObject)
                                {
                                LPOLEINPLACEOBJECT lpInPlaceObject;
                                m_lpDoc->m_lpActiveObject->QueryInterface(IID_IOleInPlaceObject, (LPVOID FAR *)&lpInPlaceObject);
                                lpInPlaceObject->ContextSensitiveHelp(FALSE);
                                lpInPlaceObject->Release();
                                }
                        }

                // see the technotes for details on implementing context sensitive
                // help
                if (m_fMenuMode)
                        {
                        m_fMenuMode = FALSE;

                        if (m_lpDoc->m_lpActiveObject)
                                m_lpDoc->m_lpActiveObject->ContextSensitiveHelp(FALSE);
                        }
                // if we provided help, we would do it here...
                MessageBox (hWnd, "Help", "Help", MB_OK);

                return NULL;
                }

        // see if the command is a verb selections
        //@@WTK WIN32, UNICODE
        //if (wParam >= IDM_VERB0)
        if (LOWORD(wParam) >= IDM_VERB0)
                {
                // get the rectangle of the object
                m_lpDoc->m_lpSite->GetObjRect(&rect);

                //@@WTK WIN32, UNICODE
                //m_lpDoc->m_lpSite->m_lpOleObject->DoVerb(wParam - IDM_VERB0, NULL, &m_lpDoc->m_lpSite->m_OleClientSite, -1, m_lpDoc->m_hDocWnd, &rect);
                m_lpDoc->m_lpSite->m_lpOleObject->DoVerb(LOWORD(wParam) - IDM_VERB0, NULL,
                                &m_lpDoc->m_lpSite->m_OleClientSite, -1, m_lpDoc->m_hDocWnd, &rect);
                }
        else
                {
                //@@WTK WIN32, UNICODE
                //switch (wParam) {
                switch (LOWORD(wParam)) {
                        // bring up the About box
                        case IDM_ABOUT:
                                {

                                DialogBox(m_hInst,               // current instance
                                        "AboutBox",                  // resource to use
                                        m_hAppWnd,                   // parent handle
                                        (DLGPROC) About);                // About() instance address

                                break;
                                }

                        // bring up the InsertObject Dialog
                        case IDM_INSERTOBJECT:
                                m_lpDoc->InsertObject();
                                break;

                        // exit the application
                        case IDM_EXIT:
                                SendMessage(hWnd, WM_SYSCOMMAND, SC_CLOSE, 0L);
                                break;

                        case IDM_NEW:
                                m_lpDoc->Close();
                                m_lpDoc = NULL;
                                lCreateDoc(hWnd, 0, 0, 0);
                                break;

                        default:
                                return (DefWindowProc(hWnd, message, wParam, lParam));
                        }   // end of switch
                }  // end of else
        return NULL;
}

//**********************************************************************
//
// CSimpleApp::lSizeHandler
//
// Purpose:
//
//      Handles the WM_SIZE message
//
// Parameters:
//
//      HWND hWnd       -   Handle to the application Window
//
//      UINT message    -   message (always WM_SIZE)
//
//      WPARAM wParam   -   Same as passed to the WndProc
//
//      LPARAM lParam   -   Same as passed to the WndProc
//
// Return Value:
//
//      LONG    -   returned from the "document" resizing
//
// Function Calls:
//      Function                    Location
//
//      GetClientRect               Windows API
//      CSimpleDoc::lResizeDoc      DOC.CPP
//
// Comments:
//
//********************************************************************

long CSimpleApp::lSizeHandler (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
        CStabilize stabilize(this);
        RECT rect;

        GetClientRect(m_hAppWnd, &rect);
        return m_lpDoc->lResizeDoc(&rect);
}

//**********************************************************************
//
// CSimpleApp::lCreateDoc
//
// Purpose:
//
//      Handles the creation of a document.
//
// Parameters:
//
//      HWND hWnd       -   Handle to the application Window
//
//      UINT message    -   message (always WM_CREATE)
//
//      WPARAM wParam   -   Same as passed to the WndProc
//
//      LPARAM lParam   -   Same as passed to the WndProc
//
// Return Value:
//
//      NULL
//
// Function Calls:
//      Function                    Location
//
//      GetClientRect               Windows API
//      CSimpleDoc::CSimpleDoc      DOC.CPP
//
// Comments:
//
//********************************************************************

long CSimpleApp::lCreateDoc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
        CStabilize stabilize(this);
        RECT rect;

        GetClientRect(hWnd, &rect);

        m_lpDoc = CSimpleDoc::Create(this, &rect, hWnd);

        return NULL;
}

//**********************************************************************
//
// CSimpleApp::AddFrameLevelUI
//
// Purpose:
//
//      Used during InPlace negotiation.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                            Location
//
//      COleInPlaceFrame::SetMenu           IOIPF.CPP
//      CSimpleApp::AddFrameLevelTools      APP.CPP
//
// Comments:
//
//      Be sure to read the Technotes included in the OLE 2.0 toolkit
//
//********************************************************************

void CSimpleApp::AddFrameLevelUI()
{
        CStabilize stabilize(this);
        m_OleInPlaceFrame.SetMenu(NULL, NULL, NULL);
        AddFrameLevelTools();
}

//**********************************************************************
//
// CSimpleApp::AddFrameLevelTools
//
// Purpose:
//
//      Used during InPlace negotiation.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                              Location
//
//      COleInPlaceFrame::SetBorderSpace      IOIPF.CPP
//      InvalidateRect                        Windows API
//
// Comments:
//
//      Be sure to read the Technotes included in the OLE 2.0 toolkit
//
//********************************************************************

void CSimpleApp::AddFrameLevelTools()
{
        CStabilize stabilize(this);
        m_OleInPlaceFrame.SetBorderSpace(&nullRect);
        InvalidateRect(m_hAppWnd, NULL, TRUE);
}

//**********************************************************************
//
// CSimpleApp::HandleAccelerators
//
// Purpose:
//
//      To properly handle accelerators in the Message Loop
//
// Parameters:
//
//      LPMSG lpMsg -   A pointer to the message structure.
//
// Return Value:
//
//      TRUE    -   The accelerator was handled
//      FALSE   -   The accelerator was not handled
//
// Function Calls:
//      Function                                        Location
//
//      IOleInPlaceActiveObject::TranslateAccelerator   Object
//
// Comments:
//
//      If an object is InPlace active, it gets the first shot at
//      handling the accelerators.
//
//********************************************************************

BOOL CSimpleApp::HandleAccelerators(LPMSG lpMsg)
{
        CStabilize stabilize(this);
        HRESULT hResult;
        BOOL retval = FALSE;

        // if we have an InPlace Active Object
        if (m_lpDoc->m_lpActiveObject)
                {
                // Pass the accelerator on...
                hResult = m_lpDoc->m_lpActiveObject->TranslateAccelerator(lpMsg);
                if (hResult == NOERROR)
                        retval = TRUE;
                }

        return retval;
}

//**********************************************************************
//
// CSimpleApp::PaintApp
//
// Purpose:
//
//      Handles the painting of the doc window.
//
//
// Parameters:
//
//      HDC hDC -   hDC to the Doc Window.
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      CSimpleDoc::PaintDoc        DOC.CPP
//
// Comments:
//
//      This is an app level function in case we want to do palette
//      management.
//
//********************************************************************

void CSimpleApp::PaintApp (HDC hDC)
{
        CStabilize stabilize(this);
        // at this level, we could enumerate through all of the
        // visible objects in the application, so that a palette
        // that best fits all of the objects can be built.

        // This app is designed to take on the same palette
        // functionality that was provided in OLE 1.0, the palette
        // of the last object drawn is realized.  Since we only
        // support one object at a time, it shouldn't be a big
        // deal.

        // if we supported multiple documents, we would enumerate
        // through each of the open documents and call paint.

        if (m_lpDoc)
                m_lpDoc->PaintDoc(hDC);

}

//**********************************************************************
//
// CSimpleApp::ContextSensitiveHelp
//
// Purpose:
//      Used in supporting context sensitive haelp at the app level.
//
//
// Parameters:
//
//      BOOL fEnterMode    -   Entering/Exiting Context Sensitive
//                             help mode.
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                                    Location
//
//      IOleInPlaceActiveObject::QueryInterface     Object
//      IOleInPlaceObject::ContextSensitiveHelp     Object
//      IOleInPlaceObject::Release                  Object
//
// Comments:
//
//      This function isn't used because we don't support Shift+F1
//      context sensitive help.  Be sure to look at the technotes
//      in the OLE 2.0 toolkit.
//
//********************************************************************

void CSimpleApp::ContextSensitiveHelp (BOOL fEnterMode)
{
        CStabilize stabilize(this);
        if (m_fCSHMode != fEnterMode)
                {
                m_fCSHMode = fEnterMode;

                // this code "trickles" the context sensitive help via shift+f1
                // to the inplace active object.  See the technotes for implementation
                // details.
                if (m_lpDoc->m_lpActiveObject)
                        {
                        LPOLEINPLACEOBJECT lpInPlaceObject;
                        m_lpDoc->m_lpActiveObject->QueryInterface(IID_IOleInPlaceObject, (LPVOID FAR *)&lpInPlaceObject);

lpInPlaceObject->ContextSensitiveHelp(fEnterMode);
                        lpInPlaceObject->Release();
                        }
                }
}

/* OLE2NOTE: forward the WM_QUERYNEWPALETTE message (via
**    SendMessage) to UIActive in-place object if there is one.
**    this gives the UIActive object the opportunity to select
**    and realize its color palette as the FOREGROUND palette.
**    this is optional for in-place containers. if a container
**    prefers to force its color palette as the foreground
**    palette then it should NOT forward the this message. or
**    the container can give the UIActive object priority; if
**    the UIActive object returns 0 from the WM_QUERYNEWPALETTE
**    message (ie. it did not realize its own palette), then
**    the container can realize its palette.
**    (see ContainerDoc_ForwardPaletteChangedMsg for more info)
**
**    (It is a good idea for containers to use the standard
**    palette even if they do not use colors themselves. this
**    will allow embedded object to get a good distribution of
**    colors when they are being drawn by the container)
**
*/

LRESULT CSimpleApp::QueryNewPalette(void)
{
        if (m_hwndUIActiveObj) {
                if (SendMessage(m_hwndUIActiveObj, WM_QUERYNEWPALETTE,
                                (WPARAM)0, (LPARAM)0)) {
                        /* Object selected its palette as foreground palette */
                        return (LRESULT)1;
                }
        }

        return wSelectPalette(m_hAppWnd, m_hStdPal, FALSE/*fBackground*/);
}

/* This is just a helper routine */

LRESULT wSelectPalette(HWND hWnd, HPALETTE hPal, BOOL fBackground)
{
        HDC hdc;
        HPALETTE hOldPal;
        UINT iPalChg = 0;

        if (hPal == 0)
                return (LRESULT)0;

        hdc = GetDC(hWnd);
        hOldPal = SelectPalette(hdc, hPal, fBackground);
        iPalChg = RealizePalette(hdc);
        SelectPalette(hdc, hOldPal, TRUE /*fBackground*/);
        ReleaseDC(hWnd, hdc);

        if (iPalChg > 0)
                InvalidateRect(hWnd, NULL, TRUE);

        return (LRESULT)1;
}


RESOURCE.H

//{{NO_DEPENDENCIES}}
// App Studio generated include file.
// Used by SIMPCNTR.RC
//
#define IDM_OPEN                        102
#define IDM_SAVE                        103
#define IDM_SAVEAS                      104
#define IDM_PRINT                       105
#define IDM_EXIT                        106
#define IDM_UNDO                        107
#define IDM_CUT                         108
#define IDM_COPY                        109
#define IDM_PASTE                       110
#define ID_EDIT_INSERTOBJECT            111
#define IDM_INSERTOBJECT                111
#define IDM_NEW                         112

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS

#define _APS_NEXT_RESOURCE_VALUE        101
#define _APS_NEXT_COMMAND_VALUE         113
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


SIMPCNTR.CPP

//**********************************************************************
// File name: Simple.cpp
//
//      Main source file for the Simple OLE 2.0 object container
//
// Functions:
//
//      WinMain         - Program entry point
//      MainWndProc     - Processes messages for the frame window
//      About           - Processes messages for the about dialog
//      DocWndProc      - Processes messages for the doc window
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "iocs.h"
#include "ias.h"
#include "ioipf.h"
#include "ioips.h"
#include "app.h"
#include "site.h"
#include "doc.h"

CSimpleApp FAR * lpCSimpleApp;

//**********************************************************************
//
// WinMain
//
// Purpose:
//
//      Program entry point
//
// Parameters:
//
//      HANDLE hInstance        - Instance handle for this instance
//
//      HANDLE hPrevInstance    - Instance handle for the last instance
//
//      LPSTR lpCmdLine         - Pointer to the command line
//
//      int nCmdShow            - Window State
//
// Return Value:
//
//      msg.wParam
//
// Function Calls:
//      Function                        Location
//
//      CSimpleApp::CSimpleApp          APP.CPP
//      CSimpleApp::fInitApplication    APP.CPP
//      CSimpleApp::fInitInstance       APP.CPP
//      CSimpleApp::HandleAccelerators  APP.CPP
//      CSimpleApp::~CSimpleApp         APP.CPP
//      GetMessage                      Windows API
//      TranslateMessage                Windows API
//      DispatchMessage                 Windows API
//
// Comments:
//
//********************************************************************

int PASCAL WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)

{
        MSG msg;

        // needed for LRPC to work properly...
        SetMessageQueue(96);

        lpCSimpleApp = new CSimpleApp;

        // we will add one ref count on our App. later when we want to destroy
        // the App object we will release this  ref count. when the App's ref
        // count goes to 0, it will be deleted.
        lpCSimpleApp->AddRef();

        // app initialization
        if (!hPrevInstance)
                if (!lpCSimpleApp->fInitApplication(hInstance))
                        return (FALSE);

        // instance initialization
        if (!lpCSimpleApp->fInitInstance(hInstance, nCmdShow))
                return (FALSE);

        // message loop
        while (GetMessage(&msg, NULL, NULL, NULL))
                if (!lpCSimpleApp->HandleAccelerators(&msg))
                        {
                        TranslateMessage(&msg);    /* Translates virtual key codes */
                        DispatchMessage(&msg);     /* Dispatches message to window */
                        }

        // Release the ref count added on the App above. this will make
        // the App's ref count go to 0, and the App object will be deleted.
        lpCSimpleApp->Release();

        return (msg.wParam);          /* Returns the value from PostQuitMessage */
}

//**********************************************************************
//
// MainWndProc
//
// Purpose:
//
//      Processes messages for the frame window
//
// Parameters:
//
//      HWND hWnd       - Window handle for frame window
//
//      UINT message    - Message value
//
//      WPARAM wParam   - Message info
//
//      LPARAM lParam   - Message info
//
// Return Value:
//
//      long
//
// Function Calls:
//      Function                        Location
//
//      CSimpleApp::lCommandHandler     APP.CPP
//      CSimpleApp::DestroyDocs         APP.CPP
//      CSimpleApp::lCreateDoc          APP.CPP
//      CSimpleApp::lSizeHandler        APP.CPP
//      CSimpleDoc::lAddVerbs           DOC.CPP
//      PostQuitMessage                 Windows API
//      DefWindowProc                   Windows API
//
// Comments:
//
//********************************************************************

//@@WTK WIN32, UNICODE
//long FAR PASCAL _export MainWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
long FAR PASCAL EXPORT MainWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)

{

        switch (message)
                {
                case WM_COMMAND:           // message: command from application menu
                        return lpCSimpleApp->lCommandHandler(hWnd, message, wParam, lParam);
                        break;

                case WM_CREATE:
                        return lpCSimpleApp->lCreateDoc(hWnd, message, wParam, lParam);
                        break;

                case WM_DESTROY:                  // message: window being destroyed
                        lpCSimpleApp->DestroyDocs();  // need to destroy the doc...
                        PostQuitMessage(0);
                        break;

                case WM_INITMENUPOPUP:
                        // is this the edit menu?
                        // Message packing is the same for WIN16 and 32
                        if ( LOWORD(lParam) == 1)
                                return lpCSimpleApp->m_lpDoc->lAddVerbs();

                        break;

                // this code is needed for 256 color objects to work properly.
                case WM_QUERYNEWPALETTE:
                        if (! lpCSimpleApp->m_fAppActive)
                                return 0L;

                        return lpCSimpleApp->QueryNewPalette();

                case WM_PALETTECHANGED:
                {
                        HWND hWndPalChg = (HWND) wParam;

                        if (hWnd != hWndPalChg)
                                wSelectPalette(hWnd, lpCSimpleApp->m_hStdPal, TRUE/*fBackground*/);

                        /* OLE2NOTE: always forward the WM_PALETTECHANGED message (via
                        **    SendMessage) to any in-place objects that currently have
                        **    their window visible. this gives these objects the chance
                        **    to select their palettes. this is
                        **    REQUIRED by all in-place containers independent of
                        **    whether they use color palettes themselves--their objects
                        **    may use color palettes.
                        **    (see ContainerDoc_ForwardPaletteChangedMsg for more info)
                        */
                        if (lpCSimpleApp->m_lpDoc && lpCSimpleApp->m_lpDoc->m_lpSite &&
                                lpCSimpleApp->m_lpDoc->m_lpSite->m_hwndIPObj)
                                SendMessage(lpCSimpleApp->m_lpDoc->m_lpSite->m_hwndIPObj,
                                                WM_PALETTECHANGED, wParam, lParam);

                        return 0L;
                }

                case WM_ACTIVATEAPP:
                        if ((lpCSimpleApp->m_fAppActive = (BOOL)wParam) == TRUE)
                                lpCSimpleApp->QueryNewPalette();

                        if (lpCSimpleApp->m_lpDoc->m_lpActiveObject) {
                                lpCSimpleApp->m_lpDoc->m_lpActiveObject->OnFrameWindowActivate(
                                                (BOOL)wParam);
                        }
                        break;

                case WM_SIZE:
                        return lpCSimpleApp->lSizeHandler(hWnd, message, wParam, lParam);

                default:                          // Passes it on if unproccessed
                        return (DefWindowProc(hWnd, message, wParam, lParam));
                }
                return (NULL);
}

//**********************************************************************
//
// About
//
// Purpose:
//
//      Processes dialog box messages
//
// Parameters:
//
//      HWND hWnd       - Window handle for dialog box
//
//      UINT message    - Message value
//
//      WPARAM wParam   - Message info
//
//      LPARAM lParam   - Message info
//
// Return Value:
//
// Function Calls:
//      Function                    Location
//
//      EndDialog                   Windows API
//
// Comments:
//
//********************************************************************

//@@WTK WIN32, UNICODE
//BOOL FAR PASCAL _export About(HWND hDlg,unsigned message,WORD wParam,LONG lParam)
BOOL FAR PASCAL EXPORT About(HWND hDlg,UINT message,WPARAM wParam,LPARAM lParam)

{
        switch (message) {
        case WM_INITDIALOG:                /* message: initialize dialog box */
                return (TRUE);

        case WM_COMMAND:                      /* message: received a command */
                //@@WTK WIN32, UNICODE
                if (LOWORD(wParam) == IDOK                /* "OK" box selected?          */
                || LOWORD(wParam) == IDCANCEL) {      /* System menu close command? */
                        EndDialog(hDlg, TRUE);        /* Exits the dialog box        */
                        return (TRUE);
                }
                break;
        }
        return (FALSE);                           /* Didn't process a message    */
}

//**********************************************************************
//
// DocWndProc
//
// Purpose:
//
//      Processes dialog box messages
//
// Parameters:
//
//      HWND hWnd       - Window handle for doc window
//
//      UINT message    - Message value
//
//      WPARAM wParam   - Message info
//
//      LPARAM lParam   - Message info
//
// Return Value:
//
// Function Calls:
//      Function                            Location
//
//      CSimpleApp::PaintApp                APP.CPP
//      BeginPaint                          Windows API
//      EndPaint                            Windows API
//      DefWindowProc                       Windows API
//      IOleObject::QueryInterface          Object
//      IOleInPlaceObject::UIDeactivate     Object
//      IOleObject::DoVerb                  Object
//      IOleInPlaceObject::Release          Object
//
// Comments:
//
//********************************************************************

//@@WTK WIN32, UNICODE
//long FAR PASCAL _export DocWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
long FAR PASCAL EXPORT DocWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
        HDC hDC;
        PAINTSTRUCT ps;

        switch (message) {
                case WM_PAINT:

                        hDC = BeginPaint(hWnd, &ps);

                        if (lpCSimpleApp)
                                lpCSimpleApp->PaintApp (hDC);

                        EndPaint(hWnd, &ps);
                        break;

                case WM_LBUTTONDBLCLK:
                        {
                        POINT pt;

                        pt.x = (int)(short)LOWORD (lParam );
                        pt.y = (int)(short)HIWORD (lParam );

                        if (lpCSimpleApp->m_lpDoc->m_lpSite &&
                                lpCSimpleApp->m_lpDoc->m_lpSite->m_lpOleObject)
                                {
                                RECT rect;

                                lpCSimpleApp->m_lpDoc->m_lpSite->GetObjRect(&rect);

                                if ( PtInRect(&rect, pt) )
                                        {
                                        // Execute object's default verb
                                        lpCSimpleApp->m_lpDoc->m_lpSite->m_lpOleObject->DoVerb(
                                                        OLEIVERB_PRIMARY, (LPMSG)&message,
                                                        &lpCSimpleApp->m_lpDoc->m_lpSite->m_OleClientSite,
                                                        -1, hWnd, &rect);
                                        }
                                }
                        }
                        break;

        // no code is added to WM_LBUTTONDOWN for context sensitive help, because
        // this app does not do context sensitive help.
        case WM_LBUTTONDOWN:

                LPOLEINPLACEOBJECT lpObject;

                if (lpCSimpleApp->m_lpDoc->m_fInPlaceActive)
                        {
                        lpCSimpleApp->m_lpDoc->m_lpSite->m_lpOleObject->QueryInterface(IID_IOleInPlaceObject, (LPVOID FAR *)&lpObject);
                        lpObject->UIDeactivate();

                        // this code is needed because we don't support inside out.
                        RECT rect;
                        lpCSimpleApp->m_lpDoc->m_lpSite->GetObjRect(&rect);
                        lpCSimpleApp->m_lpDoc->m_lpSite->m_lpOleObject->DoVerb(OLEIVERB_HIDE, (LPMSG)&message, &lpCSimpleApp->m_lpDoc->m_lpSite->m_OleClientSite, -1, hWnd, &rect);

                        lpObject->Release();
                        }

                break;

                default:                          /* Passes it on if unproccessed */
                        return (DefWindowProc(hWnd, message, wParam, lParam));
        }
        return (NULL);
}


SIMPCNTR.H

//**********************************************************************
// File name: simple.h
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************
#define IDM_ABOUT 100
#define IDM_INSERT  101
#define IDM_VERB0 1000

//@@WTK WIN32, UNICODE
#ifdef WIN32
#define EXPORT
#else
#define EXPORT __export
#endif

int PASCAL WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow);
BOOL InitApplication(HANDLE hInstance);
BOOL InitInstance(HANDLE hInstance, int nCmdShow);
//@@WTK WIN32, UNICODE _export ==> EXPORT, portable params
long FAR PASCAL EXPORT MainWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
long FAR PASCAL EXPORT DocWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
BOOL FAR PASCAL EXPORT About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);


SITE.CPP

//**********************************************************************
// File name: SITE.CPP
//
//      Implementation file for CSimpleSite
//
// Functions:
//
//      See SITE.H for class definition
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "iocs.h"
#include "ias.h"
#include "ioipf.h"
#include "ioips.h"
#include "app.h"
#include "site.h"
#include "doc.h"

#define HIMETRIC_PER_INCH 2540
#define MAP_LOGHIM_TO_PIX(x,ppli) MulDiv((ppli), (x), HIMETRIC_PER_INCH)

int XformWidthInHimetricToPixels(HDC hDC, int iWidthInHiMetric)
{
    int     iXppli;     //Pixels per logical inch along width
    int     iWidthInPix;
    BOOL    fSystemDC=FALSE;

    if (NULL==hDC)
    {
        hDC=GetDC(NULL);
        fSystemDC=TRUE;
    }

    iXppli = GetDeviceCaps (hDC, LOGPIXELSX);

    //We got logical HIMETRIC along the display, convert them to pixel units
    iWidthInPix = MAP_LOGHIM_TO_PIX(iWidthInHiMetric, iXppli);

    if (fSystemDC)
        ReleaseDC(NULL, hDC);

    return iWidthInPix;
}

int XformHeightInHimetricToPixels(HDC hDC, int iHeightInHiMetric)
{
    int     iYppli;     //Pixels per logical inch along height
    int     iHeightInPix;
    BOOL    fSystemDC=FALSE;

    if (NULL==hDC)
    {
        hDC=GetDC(NULL);
        fSystemDC=TRUE;
    }

    iYppli = GetDeviceCaps (hDC, LOGPIXELSY);

    //* We got logical HIMETRIC along the display, convert them to pixel units
    iHeightInPix = MAP_LOGHIM_TO_PIX(iHeightInHiMetric, iYppli);

    if (fSystemDC)
        ReleaseDC(NULL, hDC);

    return iHeightInPix;
}

//**********************************************************************
//
// OleFree
//
// Purpose:
//
//      free memory using the currently active IMalloc* allocator
//
// Parameters:
//
//      LPVOID pmem - pointer to memory allocated using IMalloc
//
// Return Value:
//
//      None
//
// Comments:
//
//********************************************************************

void OleFree(LPVOID pmem)
{
    LPMALLOC pmalloc;

    if (pmem == NULL)
        return;

    if (FAILED(CoGetMalloc(MEMCTX_TASK, &pmalloc)))
        return;

    pmalloc->Free(pmem);
    pmalloc->Release();
}

//**********************************************************************
//
// CSimpleSite::Create
//
// Purpose:
//
//      Creation routine for CSimpleSite
//
// Parameters:
//
//      CSimpleDoc FAR *lpDoc   - Pointer to CSimpleDoc
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      IStorage::CreateStorage     OLE API
//      assert                      C Runtime
//
// Comments:
//
//********************************************************************

CSimpleSite FAR * CSimpleSite::Create(CSimpleDoc FAR *lpDoc)
{
        CSimpleSite FAR * lpTemp = new CSimpleSite(lpDoc);

        if (!lpTemp)
                return NULL;

        // create a sub-storage for the object
  //@@WTK WIN32, UNICODE
        //HRESULT hErr = lpDoc->m_lpStorage->CreateStorage( "Object",
        HRESULT hErr = lpDoc->m_lpStorage->CreateStorage( OLESTR("Object"),
                                STGM_READWRITE | STGM_TRANSACTED | STGM_SHARE_EXCLUSIVE,
                                0,
                                0,
                                &lpTemp->m_lpObjStorage);

        assert(hErr == NOERROR);

        if (hErr != NOERROR)
                {
                delete lpTemp;
                return NULL;
                }

        // we will add one ref count on our Site. later when we want to destroy
        // the Site object we will release this  ref count. when the Site's ref
        // count goes to 0, it will be deleted.
        lpTemp->AddRef();

        return lpTemp;
}

//**********************************************************************
//
// CSimpleSite::CSimpleSite
//
// Purpose:
//
//      Constructor for CSimpleSite
//
// Parameters:
//
//      CSimpleDoc FAR *lpDoc   - Pointer to CSimpleDoc
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
// Comments:
//
//********************************************************************
#pragma warning(disable : 4355)  // turn off this warning.  This warning
                                                                // tells us that we are passing this in
                                                                // an initializer, before "this" is through
                                                                // initializing.  This is ok, because
                                                                // we just store the ptr in the other
                                                                // constructors

CSimpleSite::CSimpleSite (CSimpleDoc FAR *lpDoc) : m_OleClientSite(this),
                                                                                m_AdviseSink(this),
                                                                                m_OleInPlaceSite(this)
#pragma warning (default : 4355)  // Turn the warning back on
{
        // remember the pointer to the doc
        m_lpDoc = lpDoc;

        // clear the reference count

        m_dwDrawAspect = DVASPECT_CONTENT;
        m_lpOleObject = NULL;
        m_lpInPlaceObject = NULL;
        m_hwndIPObj = NULL;
        m_fInPlaceActive = FALSE;
        m_fObjectOpen = FALSE;

}

//**********************************************************************
//
// CSimpleSite::~CSimpleSite
//
// Purpose:
//
//      Destructor for CSimpleSite
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                                Location
//
//      OutputDebugString                       Windows API
//      IOleObject::Release                     Object
//      IStorage::Release                       OLE API
//
// Comments:
//
//********************************************************************

CSimpleSite::~CSimpleSite ()
{
        OutputDebugString ("In CSimpleSite's Destructor \r\n");

        if (m_lpOleObject)
           m_lpOleObject->Release();

        if (m_lpObjStorage)
           m_lpObjStorage->Release();
}

//**********************************************************************
//
// CSimpleSite::CloseOleObject
//
// Purpose:
//
//      Call IOleObject::Close on the object of the CSimpleSite
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                                Location
//
//      OutputDebugString                       Windows API
//      IOleObject::QueryInterface              Object
//      IOleObject::Close                       Object
//      IOleInPlaceObject::UIDeactivate         Object
//      IOleInPlaceObject::InPlaceDeactivate    Object
//      IOleInPlaceObject::Release              Object
//
// Comments:
//
//********************************************************************

void CSimpleSite::CloseOleObject (void)
{
        CStabilize stabilize(this);
        LPOLEINPLACEOBJECT lpObject;
        LPVIEWOBJECT lpViewObject = NULL;

        OutputDebugString ("In CSimpleSite::CloseOleObject \r\n");

        if (m_lpOleObject)
           {
           if (m_fInPlaceActive)
                   {
                   m_lpOleObject->QueryInterface(IID_IOleInPlaceObject, (LPVOID FAR *)&lpObject);
                   lpObject->UIDeactivate();
                   // don't need to worry about inside-out because the object
                   // is going away.
                   lpObject->InPlaceDeactivate();
                   lpObject->Release();
                   }

           m_lpOleObject->Close(OLECLOSE_NOSAVE);
           }
}

//**********************************************************************
//
// CSimpleSite::UnloadOleObject
//
// Purpose:
//
//      Close and release all pointers to the object of the CSimpleSite
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                                Location
//
//      OutputDebugString                       Windows API
//      CSimpleSite::CloseOleObject             SITE.CPP
//      IOleObject::QueryInterface              Object
//      IViewObject::SetAdvise                  Object
//      IViewObject::Release                    Object
//      IStorage::Release                       OLE API
//
// Comments:
//
//********************************************************************

void CSimpleSite::UnloadOleObject (void)
{
        CStabilize stabilize(this);
        OutputDebugString ("In CSimpleSite::UnloadOleObject \r\n");

        if (m_lpOleObject)
           {
           LPVIEWOBJECT lpViewObject;
           CloseOleObject();    // ensure object is closed; NOP if already closed

           m_lpOleObject->QueryInterface(IID_IViewObject, (LPVOID FAR *)&lpViewObject);

           if (lpViewObject)
                   {
                   // Remove the view advise
                   lpViewObject->SetAdvise(m_dwDrawAspect, 0, NULL);
                   lpViewObject->Release();
                   }

           m_lpOleObject->Release();
           m_lpOleObject = NULL;
           }
}

//**********************************************************************
//
// CSimpleSite::QueryInterface
//
// Purpose:
//
//      Used for interface negotiation of the container Site.
//
// Parameters:
//
//      REFIID riid         -   A reference to the interface that is
//                              being queried.
//
//      LPVOID FAR* ppvObj  -   An out parameter to return a pointer to
//                              the interface.
//
// Return Value:
//
//      S_OK    -   The interface is supported.
//      S_FALSE -   The interface is not supported
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      IsEqualIID                  OLE API
//                   OLE API
//      CSimpleSite::AddRef          OBJ.CPP
//      COleClientSite::AddRef      IOCS.CPP
//      CAdviseSink::AddRef         IAS.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP CSimpleSite::QueryInterface(REFIID riid, LPVOID FAR* ppvObj)
{
        CStabilize stabilize(this);
        OutputDebugString("In CSimpleSite::QueryInterface\r\n");

        *ppvObj = NULL;     // must set out pointer parameters to NULL

        if ( riid == IID_IUnknown)
                {
                AddRef();
                *ppvObj = this;
                return S_OK;
                }

        if ( riid == IID_IOleClientSite)
                {
                m_OleClientSite.AddRef();
                *ppvObj = &m_OleClientSite;
                return S_OK;
                }

        if ( riid == IID_IAdviseSink)
                {
                m_AdviseSink.AddRef();
                *ppvObj = &m_AdviseSink;
                return S_OK;
                }

        if ( riid == IID_IOleInPlaceSite)
                {
                m_OleInPlaceSite.AddRef();
                *ppvObj = &m_OleInPlaceSite;
                return S_OK;
                }

        // Not a supported interface
        return E_NOINTERFACE;
}

//**********************************************************************
//
// CSimpleSite::AddRef
//
// Purpose:
//
//      Increments the reference count of the container Site.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      ULONG   -   The new reference count of the site.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//********************************************************************

STDMETHODIMP_(ULONG) CSimpleSite::AddRef()
{
        OutputDebugString("In CSimpleSite::AddRef\r\n");

        return SafeAddRef();
}

//**********************************************************************
//
// CSimpleSite::Release
//
// Purpose:
//
//      Decrements the reference count of the container Site
//
// Parameters:
//
//      None
//
// Return Value:
//
//      ULONG   -   The new reference count of the Site.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//********************************************************************

STDMETHODIMP_(ULONG) CSimpleSite::Release()
{
        OutputDebugString("In CSimpleSite::Release\r\n");

        return(SafeRelease());
}

//**********************************************************************
//
// CSimpleSite::InitObject
//
// Purpose:
//
//      Used to initialize a newly create object (can't be done in the
//      constructor).
//
// Parameters:
//
//      BOOL fCreateNew -   TRUE if insert NEW object
//                          FALSE if create object FROM FILE
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                        Location
//
//      IOleObject::SetHostNames        Object
//      IOleObject::QueryInterface      Object
//      IViewObject2::GetExtent         Object
//      IOleObject::DoVerb              Object
//      IViewObject::SetAdvise          Object
//      IViewObject::Release            Object
//      GetClientRect                   Windows API
//      OleSetContainedObject           OLE API
//
// Comments:
//
//********************************************************************

void CSimpleSite::InitObject(BOOL fCreateNew)
{
        CStabilize stabilize(this);
        LPVIEWOBJECT2 lpViewObject2;
        RECT rect;

        // Set a View Advise
        m_lpOleObject->QueryInterface(IID_IViewObject2,(LPVOID FAR *)&lpViewObject2);
        lpViewObject2->SetAdvise(m_dwDrawAspect, ADVF_PRIMEFIRST, &m_AdviseSink);

        // get the initial size of the object
        lpViewObject2->GetExtent(m_dwDrawAspect, -1 /*lindex*/, NULL /*ptd*/, &m_sizel);
        GetObjRect(&rect);  // get the rectangle of the object in pixels
        lpViewObject2->Release();

        // give the object the name of the container app/document
  //@@WTK WIN32, UNICODE
        //m_lpOleObject->SetHostNames("Simple Application", "Simple OLE 2.0 In-Place Container");
        m_lpOleObject->SetHostNames(OLESTR("Simple Application"),
                        OLESTR("Simple OLE 2.0 In-Place Container"));

        // inform object handler/DLL object that it is used in the embedding container's context
        OleSetContainedObject(m_lpOleObject, TRUE);

        if (fCreateNew) {
           // force new object to save to guarantee valid object in our storage.
           // OLE 1.0 objects may close w/o saving. this is NOT necessary if the
           // object is created FROM FILE; its data in storage is already valid.
           m_OleClientSite.SaveObject();

           // we only want to DoVerb(SHOW) if this is an InsertNew object.
           // we should NOT DoVerb(SHOW) if the object is created FromFile.
           m_lpOleObject->DoVerb(
                           OLEIVERB_SHOW,
                           NULL,
                           &m_OleClientSite,
                           -1,
                           m_lpDoc->m_hDocWnd,
                           &rect);
        }
}

//**********************************************************************
//
// CSimpleSite::PaintObj
//
// Purpose:
//
//      Paints the object
//
// Parameters:
//
//      HDC hDC     - Device context of the document window
//
// Return Value:
//
// Function Calls:
//      Function                        Location
//
//      IOleObject::QueryInterface      Object
//      IViewObject::GetColorSet        Object
//      IViewObject::Release            Object
//      SetMapMode                      Windows API
//      LPtoDP                          Windows API
//      CreateHatchBrush                Windows API
//      SelectObject                    Windows API
//      DeleteObject                    Windows API
//      CreatePalette                   Windows API
//      SelectPalette                   Windows API
//      RealizePalette                  Windows API
//      OleDraw                         OLE API
//
// Comments:
//
//********************************************************************

void CSimpleSite::PaintObj(HDC hDC)
{
RECT rect;
        CStabilize stabilize(this);

        // need to check to make sure there is a valid object
        // available.  This is needed if there is a paint msg
        // between the time that CSimpleSite is instantiated
        // and OleUIInsertObject returns.
        if (!m_lpOleObject)
                return;

        // convert it to pixels
        GetObjRect(&rect);

        LPLOGPALETTE pColorSet = NULL;
        LPVIEWOBJECT lpView = NULL;

        // get a pointer to IViewObject
        m_lpOleObject->QueryInterface(IID_IViewObject,(LPVOID FAR *) &lpView);

        // if the QI succeeds, get the LOGPALETTE for the object
        if (lpView)
                lpView->GetColorSet(m_dwDrawAspect, -1, NULL, NULL, NULL, &pColorSet);

        HPALETTE hPal=NULL;
        HPALETTE hOldPal=NULL;

        // if a LOGPALETTE was returned (not guarateed), create the palette and
        // realize it.  NOTE: A smarter application would want to get the LOGPALETTE
        // for each of its visible objects, and try to create a palette that
        // satisfies all of the visible objects.  ALSO: OleFree() is used to
        // free the returned LOGPALETTE.
        if ((pColorSet))
                {
                //@@WTK WIN32, UNICODE
                //hPal = CreatePalette((const LPLOGPALETTE) pColorSet);
                hPal = CreatePalette( pColorSet);
                hOldPal = SelectPalette(hDC, hPal, FALSE);
                RealizePalette(hDC);
                OleFree(pColorSet);
                }

        // draw the object
        OleDraw(m_lpOleObject, m_dwDrawAspect, hDC, &rect);

        // if the object is open, draw a hatch rect.
        if (m_fObjectOpen)
                {
                HBRUSH hBrush = CreateHatchBrush ( HS_BDIAGONAL, RGB(0,0,0) );
                HBRUSH hOldBrush = (HBRUSH) SelectObject (hDC, hBrush);
                SetROP2(hDC, R2_MASKPEN);
                Rectangle (hDC, rect.left, rect.top, rect.right, rect.bottom);
                SelectObject(hDC, hOldBrush);
                DeleteObject(hBrush);
                }

        // if we created a palette, restore the old one, and destroy
        // the object.
        if (hPal)
                {
                SelectPalette(hDC,hOldPal,FALSE);
                DeleteObject(hPal);
                }

        // if a view pointer was successfully returned, it needs to be released.
        if (lpView)
                lpView->Release();
}

//**********************************************************************
//
// CSimpleSite::GetObjRect
//
// Purpose:
//
//      Retrieves the rect of the object in pixels
//
// Parameters:
//
//      LPRECT lpRect - Rect structure filled with object's rect in pixels
//
// Return Value:
//
// Function Calls:
//      Function                        Location
//
//      XformWidthInHimetricToPixels    OUTLUI Function
//      XformHeightInHimetricToPixels   OUTLUI Function
//
// Comments:
//
//********************************************************************

void CSimpleSite::GetObjRect(LPRECT lpRect)
{
        // convert it to pixels
        lpRect->left = lpRect->top = 0;
        lpRect->right = XformWidthInHimetricToPixels(NULL,(int)m_sizel.cx);
        lpRect->bottom = XformHeightInHimetricToPixels(NULL,(int)m_sizel.cy);
}


SITE.H

//**********************************************************************
// File name: SITE.H
//
//      Definition of CSimpleSite
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************
#if !defined( _SITE_H_ )
#define _SITE_H_

#include <ole2.h>
#include <stablize.h>
#include "ias.h"
#include "ioips.h"
#include "iocs.h"

class CSimpleDoc;

class CSimpleSite : public IUnknown, public CSafeRefCount
{
public:
        DWORD m_dwConnection;
        LPOLEOBJECT m_lpOleObject;
        LPOLEINPLACEOBJECT m_lpInPlaceObject;
        HWND m_hwndIPObj;
        DWORD m_dwDrawAspect;
        SIZEL m_sizel;
        BOOL m_fInPlaceActive;
        BOOL m_fObjectOpen;
        LPSTORAGE m_lpObjStorage;

        CAdviseSink m_AdviseSink;
        COleInPlaceSite m_OleInPlaceSite;
        COleClientSite m_OleClientSite;

        CSimpleDoc FAR * m_lpDoc;

        // IUnknown Interfaces
        STDMETHODIMP QueryInterface(REFIID riid, LPVOID FAR* ppvObj);
        STDMETHODIMP_(ULONG) AddRef();
        STDMETHODIMP_(ULONG) Release();

        void InitObject(BOOL fCreateNew);
        static CSimpleSite FAR * Create(CSimpleDoc FAR *lpDoc);
        CSimpleSite(CSimpleDoc FAR *lpDoc);
        ~CSimpleSite();
        void PaintObj(HDC hDC);
        void GetObjRect(LPRECT lpRect);
        void CloseOleObject(void);
        void UnloadOleObject(void);
};

#endif


STDPAL.H

/*-----------------------------------------------------------------------
|   stdpal.h
|
|   Standard App Palette useful for OLE applications.  v 1.01
|
|   #include this file in the same file as HpalCreateAppPalette
|
|   NOTE:  Palette MUST be created with HpalCreateAppPalette
|
|   Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
|
-----------------------------------------------------------------------*/

unsigned char palSVGA[256][3] =
        {
        // R     G     B
        {0x00, 0x00, 0x00}, // 0 Sys Black  gray 0
        {0x80, 0x00, 0x00}, // 1 Sys Dk Red
        {0x00, 0x80, 0x00}, // 2 Sys Dk Green
        {0x80, 0x80, 0x00}, // 3 Sys Dk Yellow
        {0x00, 0x00, 0x80}, // 4 Sys Dk Blue
        {0x80, 0x00, 0x80}, // 5 Sys Dk Violet
        {0x00, 0x80, 0x80}, // 6 Sys Dk Cyan
        {0xc0, 0xc0, 0xc0}, // 7 Sys Lt Grey    gray 192
        {0xc0, 0xdc, 0xc0}, // 8 Sys 8
        {0xa6, 0xca, 0xf0}, // 9 Sys 9 (the first 10 are fixed by Windows)

        {0x80, 0x00, 0x00}, // 10 Sys Dk Red repeat
        {0x00, 0x80, 0x00}, // 11 Sys Dk Green repeat
        {0x80, 0x80, 0x00}, // 12 Sys Dk Yellow repeat
        {0x00, 0x00, 0x80}, // 13 Sys Dk Blue repeat
        {0x80, 0x00, 0x80}, // 14 Sys Dk Violet repeat
        {0x00, 0x80, 0x80}, // 15 Sys Dk Cyan repeat
        {0x80, 0x80, 0x80}, // 16 Sys Dk Grey repeat    gray 128
        {0x80, 0x80, 0xff}, // 17 Excel Chart Fill 1
        {0x80, 0x20, 0x60}, // 18 Excel Chart Fill 2
        {0xff, 0xff, 0xc0}, // 19 Excel Chart Fill 3
        {0xa0, 0xe0, 0xe0}, // 20 Excel Chart Fill 4
        {0x60, 0x00, 0x80}, // 21 Excel Chart Fill 4
        {0xff, 0x80, 0x80}, // 22 Excel Chart Fill 6
        {0x00, 0x80, 0xc0}, // 23 Excel Chart Fill 7
        {0xc0, 0xc0, 0xff}, // 24 Excel Chart Fill 8
        {0x00, 0xcf, 0xff}, // 25 Excel clrt entry
        {0x69, 0xff, 0xff}, // 26 Excel clrt entry
        {0xe0, 0xff, 0xe0}, // 27 Excel clrt entry
        {0xdd, 0x9c, 0xb3}, // 28 Excel clrt entry
        {0xb3, 0x8f, 0xee}, // 29 Excel clrt entry
        {0x2a, 0x6f, 0xf9}, // 30 Excel clrt entry
        {0x3f, 0xb8, 0xcd}, // 31 Excel clrt entry
        {0x48, 0x84, 0x36}, // 32 Excel clrt entry
        {0x95, 0x8c, 0x41}, // 33 Excel clrt entry
        {0x8e, 0x5e, 0x42}, // 34 Excel clrt entry
        {0xa0, 0x62, 0x7a}, // 35 Excel clrt entry
        {0x62, 0x4f, 0xac}, // 36 Excel clrt entry
        {0x1d, 0x2f, 0xbe}, // 37 Excel clrt entry
        {0x28, 0x66, 0x76}, // 38 Excel clrt entry
        {0x00, 0x45, 0x00}, // 39 Excel clrt entry
        {0x45, 0x3e, 0x01}, // 40 Excel clrt entry
        {0x6a, 0x28, 0x13}, // 41 Excel clrt entry
        {0x85, 0x39, 0x6a}, // 42 Excel clrt entry
        {0x4a, 0x32, 0x85}, // 43 Excel clrt entry
        {0x04, 0x04, 0x04}, // 44   gray 4
        {0x08, 0x08, 0x08}, // 45   gray 8
        {0x0c, 0x0c, 0x0c}, // 46   gray 12
        {0x11, 0x11, 0x11}, // 47   gray 17
        {0x16, 0x16, 0x16}, // 48   gray 22
        {0x1c, 0x1c, 0x1c}, // 49   gray 28
        {0x22, 0x22, 0x22}, // 50   gray 34
        {0x29, 0x29, 0x29}, // 51   gray 41
        {0x30, 0x30, 0x30}, // 52   gray 48
        {0x5f, 0x5f, 0x5f}, // 53 swapped so inversions look good   gray 95
        {0x55, 0x55, 0x55}, // 54 swapped so inversions look good   gray 85
        {0x4d, 0x4d, 0x4d}, // 55 swapped so inversions look good   gray 77
        {0x42, 0x42, 0x42}, // 56 swapped so inversions look good   gray 66
        {0x39, 0x39, 0x39}, // 57 swapped so inversions look good   gray 57
        {0x00, 0x07, 0x00}, // 58
        {0x0d, 0x00, 0x00}, // 59
        {0xb7, 0x99, 0x81}, // 60
        {0x84, 0x99, 0xb4}, // 61
        {0xbd, 0xbd, 0x90}, // 62
        {0x7f, 0x7f, 0x60}, // 63
        {0x60, 0x60, 0x7f}, // 64
        {0x00, 0x0e, 0x00}, // 65
        {0x1b, 0x00, 0x00}, // 66
        {0x28, 0x00, 0x00}, // 67
        {0x08, 0x09, 0x2b}, // 68
        {0x00, 0x1d, 0x00}, // 69
        {0x39, 0x00, 0x00}, // 70
        {0x00, 0x00, 0x9b}, // 71
        {0x00, 0x25, 0x00}, // 72
        {0x49, 0x00, 0x00}, // 73
        {0x11, 0x11, 0x3b}, // 74
        {0x00, 0x2f, 0x00}, // 75
        {0x5d, 0x00, 0x00}, // 76
        {0x17, 0x17, 0x45}, // 77
        {0x00, 0x3a, 0x00}, // 78
        {0x49, 0x11, 0x11}, // 79
        {0x1c, 0x1c, 0x53}, // 80
        {0x00, 0x16, 0xff}, // 81
        {0x2b, 0x00, 0xff}, // 82
        {0x21, 0x21, 0x6c}, // 83
        {0x59, 0x14, 0x14}, // 84
        {0x00, 0x51, 0x00}, // 85
        {0x47, 0x1a, 0x6a}, // 86
        {0x19, 0x32, 0x67}, // 87
        {0x00, 0x61, 0x00}, // 88
        {0x00, 0x31, 0xff}, // 89
        {0x61, 0x00, 0xff}, // 90
        {0x53, 0x20, 0x7b}, // 91
        {0x16, 0x43, 0x67}, // 92
        {0x2e, 0x2e, 0xe2}, // 93
        {0x26, 0x59, 0x16}, // 94
        {0x51, 0x46, 0x04}, // 95
        {0x68, 0x2e, 0x49}, // 96
        {0x07, 0x52, 0x8f}, // 97
        {0x6a, 0x18, 0xb8}, // 98
        {0x90, 0x23, 0x15}, // 99
        {0x00, 0x53, 0xff}, // 100
        {0xa3, 0x00, 0xff}, // 101
        {0x6a, 0x4a, 0x12}, // 102
        {0x75, 0x33, 0x6c}, // 103
        {0x4a, 0x41, 0x9a}, // 104
        {0x37, 0x65, 0x0b}, // 105
        {0xa4, 0x2c, 0x15}, // 106
        {0x83, 0x1f, 0xb1}, // 107
        {0x4e, 0x2c, 0xff}, // 108
        {0x20, 0x51, 0xb6}, // 109
        {0x08, 0x64, 0x92}, // 110
        {0x6f, 0x56, 0x0b}, // 111
        {0x59, 0x43, 0xad}, // 112
        {0x36, 0x72, 0x12}, // 113
        {0xb0, 0x33, 0x17}, // 114
        {0x00, 0xa1, 0x00}, // 115
        {0x77, 0x5f, 0x1f}, // 116
        {0x89, 0x47, 0x71}, // 117
        {0xb0, 0x43, 0x1c}, // 118
        {0xb7, 0x2d, 0x7d}, // 119
        {0x00, 0x86, 0x95}, // 120
        {0x7a, 0x6e, 0x23}, // 121
        {0x26, 0x9f, 0x00}, // 122
        {0x73, 0xa9, 0x01}, // 123
        {0x00, 0x00, 0x00}, // 124 free 0   gray 0
        {0x00, 0x00, 0x00}, // 125 free 2   gray 0
        {0x00, 0x00, 0x00}, // 126 free 4   gray 0
        {0x00, 0x00, 0x00}, // 127 free 6   gray 0
        {0x00, 0x00, 0x00}, // 128 free 7   gray 0
        {0x00, 0x00, 0x00}, // 129 free 5   gray 0
        {0x00, 0x00, 0x00}, // 130 free 3   gray 0
        {0x00, 0x00, 0x00}, // 131 free 1   gray 0
        {0x00, 0xca, 0x00}, // 132
        {0xac, 0x5b, 0x01}, // 133
        {0x20, 0x1d, 0xc2}, // 134
        {0x94, 0x52, 0x70}, // 135
        {0x24, 0xaa, 0x4c}, // 136
        {0x0a, 0x94, 0x89}, // 137
        {0x36, 0x6e, 0x7b}, // 138
        {0x44, 0x75, 0x90}, // 139
        {0xff, 0x00, 0xa8}, // 140
        {0x00, 0x71, 0xff}, // 141
        {0xdf, 0x00, 0xff}, // 142
        {0x56, 0x91, 0x4a}, // 143
        {0x34, 0x48, 0xf8}, // 144
        {0xcc, 0x32, 0x82}, // 145
        {0xe4, 0x41, 0x70}, // 146
        {0x68, 0xca, 0x01}, // 147
        {0x36, 0xbc, 0x42}, // 148
        {0x00, 0x9a, 0xff}, // 149
        {0x96, 0x22, 0xb7}, // 150
        {0x85, 0x7d, 0x33}, // 151
        {0x25, 0xb7, 0x8c}, // 152
        {0x36, 0x5a, 0xed}, // 153
        {0x5c, 0xff, 0x00}, // 154
        {0xff, 0x48, 0x00}, // 155
        {0x22, 0x9b, 0xa2}, // 156
        {0x42, 0xcf, 0x4d}, // 157
        {0xc2, 0x58, 0x52}, // 158
        {0x20, 0xd3, 0x95}, // 159
        {0xa5, 0x24, 0xe0}, // 160
        {0x73, 0x56, 0xb5}, // 161
        {0xa9, 0xa9, 0x00}, // 162
        {0xd0, 0x6f, 0x3c}, // 163
        {0x67, 0x9f, 0x58}, // 164
        {0x89, 0xcf, 0x0b}, // 165
        {0xff, 0xac, 0x00}, // 166
        {0xa7, 0x2e, 0xfe}, // 167
        {0xe2, 0x59, 0x7f}, // 168
        {0x4c, 0xdc, 0x67}, // 169
        {0xff, 0x18, 0xff}, // 170
        {0x3a, 0x7d, 0xff}, // 171
        {0xb1, 0xd0, 0x18}, // 172
        {0xc7, 0xff, 0x00}, // 173
        {0xff, 0xe2, 0x00}, // 174
        {0xdf, 0x9a, 0x3d}, // 175
        {0x56, 0x81, 0x9f}, // 176
        {0xc6, 0x43, 0xba}, // 177
        {0xaf, 0x71, 0x8b}, // 178
        {0x38, 0xa2, 0xc9}, // 179
        {0xd1, 0x53, 0xce}, // 180
        {0xff, 0x9a, 0x65}, // 181
        {0x46, 0xca, 0xdb}, // 182
        {0xff, 0x4d, 0xff}, // 183
        {0xc8, 0xe9, 0x6a}, // 184
        {0x4c, 0xde, 0xe0}, // 185
        {0xff, 0x98, 0xff}, // 186
        {0xdf, 0xc0, 0x82}, // 187
        {0xe9, 0xec, 0xa5}, // 188
        {0xf5, 0xf6, 0xcd}, // 189
        {0xff, 0xd0, 0xff}, // 190
        {0xb1, 0xac, 0x5a}, // 191
        {0x63, 0x91, 0xae}, // 192
        {0x22, 0x4c, 0x65}, // 193
        {0x8d, 0x4e, 0x3f}, // 194
        {0x50, 0x70, 0x70}, // 195
        {0xd0, 0xff, 0xff}, // 196
        {0xff, 0xe7, 0xff}, // 197
        {0x69, 0x69, 0x69}, // 198  gray 105
        {0x77, 0x77, 0x77}, // 199  gray 119
        {0x86, 0x86, 0x86}, // 200  gray 134
        {0x96, 0x96, 0x96}, // 201  gray 150
        {0x9d, 0x9d, 0x9d}, // 202  gray 157
        {0xa4, 0xa4, 0xa4}, // 203  gray 164
        {0xb2, 0xb2, 0xb2}, // 204  gray 178
        {0xcb, 0xcb, 0xcb}, // 205  gray 203
        {0xd7, 0xd7, 0xd7}, // 206  gray 215
        {0xdd, 0xdd, 0xdd}, // 207  gray 221
        {0xe3, 0xe3, 0xe3}, // 208  gray 227
        {0xea, 0xea, 0xea}, // 209  gray 234
        {0xf1, 0xf1, 0xf1}, // 210  gray 241
        {0xf8, 0xf8, 0xf8}, // 211  gray 248
        {0xb2, 0xc1, 0x66}, // 212
        {0x80, 0xbf, 0x78}, // 213
        {0xc6, 0xf0, 0xf0}, // 214
        {0xb2, 0xa4, 0xff}, // 215
        {0xff, 0xb3, 0xff}, // 216
        {0xd1, 0x8e, 0xa3}, // 217
        {0xc3, 0xdc, 0x37}, // 218
        {0xa0, 0x9e, 0x54}, // 219
        {0x76, 0xae, 0x70}, // 220
        {0x78, 0x9e, 0xc1}, // 221
        {0x83, 0x64, 0xbf}, // 222
        {0xa4, 0x83, 0xd3}, // 223
        {0xd1, 0x3f, 0x32}, // 224
        {0xff, 0x7d, 0x00}, // 225
        {0x44, 0x78, 0x23}, // 226
        {0x24, 0x5f, 0x60}, // 227
        {0x0e, 0x0e, 0x2c}, // 228
        {0xbe, 0x00, 0x00}, // 229
        {0xff, 0x1f, 0x00}, // 230
        {0x31, 0x39, 0x00}, // 231
        {0xd9, 0x85, 0x3e}, // 232
        {0x02, 0x77, 0x85}, // 233
        {0xb0, 0xd8, 0x81}, // 234
        {0x56, 0x21, 0x1d}, // 235
        {0x00, 0x00, 0x30}, // 236
        {0x88, 0xc8, 0xb3}, // 237
        {0xa0, 0x79, 0x00}, // 238
        {0xc0, 0xc0, 0xc0}, // 239 Sys Dk Grey repeat inversion gray 192
        {0xea, 0x70, 0x81}, // 240
        {0x51, 0xf1, 0x69}, // 241
        {0xff, 0xff, 0x80}, // 242
        {0x91, 0x74, 0xcd}, // 243
        {0xff, 0x7c, 0xff}, // 244
        {0xa2, 0xff, 0xff}, // 245

        {0xff, 0xfb, 0xf0}, // 246 Sys Reserved
        {0xa0, 0xa0, 0xa4}, // 247 Sys Reserved
        {0x80, 0x80, 0x80}, // 248 Sys Lt Gray  gray 128
        {0xff, 0x00, 0x00}, // 249 Sys Red
        {0x00, 0xff, 0x00}, // 250 Sys Green
        {0xff, 0xff, 0x00}, // 251 Sys Yellow
        {0x00, 0x00, 0xff}, // 252 Sys Blue
        {0xff, 0x00, 0xff}, // 253 Sys Violet
        {0x00, 0xff, 0xff}, // 254 Sys Cyan
        {0xff, 0xff, 0xff} // 255 Sys White gray 255
        };


README.TXT

Simple OLE Drag and Drop

The SimpDnD sample provides a simple implementation of OLE 2 Drag and Drop.


APP.H

//**********************************************************************
// File name: app.h
//
//      Definition of CSimpleApp
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#if !defined( _APP_H_)
#define _APP_H_

#include <ole2.h>
#include <stablize.h>

class CSimpleDoc;

class CSimpleApp : public IUnknown, public CSafeRefCount
{
public:

        HWND m_hAppWnd;         // main window handle
        HINSTANCE m_hInst;      // application instance
        CSimpleDoc FAR * m_lpDoc;   // pointer to document object
        BOOL m_fInitialized;    // OLE initialization flag
        BOOL m_fOleStdInit;     // OleStd library initialization flag
        HMENU           m_hMainMenu;
        HMENU           m_hFileMenu;
        HMENU           m_hEditMenu;
        HMENU           m_hHelpMenu;
        HMENU           m_hCascadeMenu;     // OLE object's verb

        // Drag/Drop related fields
        int m_nDragDelay;       // time delay (in msec) before drag should start
        int m_nDragMinDist;     // min. distance (radius) before drag should start
        int m_nScrollDelay;     // time delay (in msec) before scroll should start
        int m_nScrollInset;     // Border inset distance to start drag scroll
        int m_nScrollInterval;  // scroll interval time (in msec)

        CSimpleApp();           // Constructor
        ~CSimpleApp();          // Destructor

        // IUnknown Interfaces
        STDMETHODIMP QueryInterface(REFIID riid, LPVOID FAR* ppvObj);
        STDMETHODIMP_(ULONG) AddRef();
        STDMETHODIMP_(ULONG) Release();

        // Initialization methods

        BOOL fInitApplication (HANDLE hInstance);
        BOOL fInitInstance (HANDLE hInstance, int nCmdShow);

        // Message handling methods

        long lCommandHandler (HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);
        long lSizeHandler (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
        long lCreateDoc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
        BOOL HandleAccelerators (LPMSG lpMsg);
        void PaintApp(HDC hDC);
        void DestroyDocs();
};

#endif  // _APP_H_


DOC.CPP

//**********************************************************************
// File name: DOC.CPP
//
//      Implementation file for CSimpleDoc.
//
// Functions:
//
//      See DOC.H for Class Definition
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "iocs.h"
#include "ias.h"
#include "app.h"
#include "site.h"
#include "doc.h"
#include "idt.h"
#include "dxferobj.h"

//**********************************************************************
//
// CSimpleDoc::Create
//
// Purpose:
//
//      Creation for the CSimpleDoc Class
//
// Parameters:
//
//      CSimpleApp FAR * lpApp  -   Pointer to the CSimpleApp Class
//
//      LPRECT lpRect           -   Client area rect of "frame" window
//
//      HWND hWnd               -   Window Handle of "frame" window
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      StgCreateDocfile            OLE API
//      RegisterDragDrop            OLE API
//      CoLockObjectExternal        OLE API
//      CreateWindow                Windows API
//      ShowWindow                  Windows API
//      UpdateWindow                Windows API
//
// Comments:
//
//      This routine was added so that failure could be returned
//      from object creation.
//
//********************************************************************

CSimpleDoc FAR * CSimpleDoc::Create(CSimpleApp FAR *lpApp, LPRECT lpRect,HWND hWnd)
{
        CSimpleDoc FAR * lpTemp = new CSimpleDoc(lpApp, hWnd);

        if (!lpTemp)
                return NULL;

        // create storage for the doc.
        HRESULT hErr = StgCreateDocfile (
                NULL,       // generate temp name
                STGM_READWRITE | STGM_TRANSACTED | STGM_SHARE_EXCLUSIVE,
                0, &lpTemp->m_lpStorage);

        if (hErr != NOERROR)
                goto error;

        // create the document Window
        lpTemp->m_hDocWnd = CreateWindow(
                        "SimpDndDocWClass",
                        NULL,
                        WS_CHILD | WS_CLIPCHILDREN,
                        lpRect->left,
                        lpRect->top,
                        lpRect->right,
                        lpRect->bottom,
                        hWnd,
                        NULL,
                        lpApp->m_hInst,
                        NULL);

        if (!lpTemp->m_hDocWnd)
                goto error;

        ShowWindow(lpTemp->m_hDocWnd, SW_SHOWNORMAL);  // Show the window
        UpdateWindow(lpTemp->m_hDocWnd);               // Sends WM_PAINT message

        // Ensable InsertObject menu choice
        EnableMenuItem( lpApp->m_hEditMenu, 1, MF_BYPOSITION | MF_ENABLED);
        // Disable Copy menu choice
        EnableMenuItem( lpApp->m_hEditMenu, 0, MF_BYPOSITION | MF_DISABLED | MF_GRAYED);

        // It is *REQUIRED* to hold a strong LOCK on the object that is
        // registered as drop target. this call will result in at least one
        // ref count held on our document. later in CSimpleDoc::Close we will
        // unlock this lock which will make our document's ref count go to 0.
        // when the document's ref count goes to 0, it will be deleted.
        CoLockObjectExternal (&lpTemp->m_DropTarget, TRUE, 0);

        // Register our window as a DropTarget
        RegisterDragDrop(lpTemp->m_hDocWnd, &lpTemp->m_DropTarget);
        lpTemp->m_fRegDragDrop = TRUE;

        return (lpTemp);

error:
        delete (lpTemp);
        return NULL;

}

//**********************************************************************
//
// CSimpleDoc::Close
//
// Purpose:
//
//      Close CSimpleDoc object.
//      when the document's reference count goes to 0, the document
//      will be destroyed.
//
// Parameters:
//
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      RevokeDragDrop              OLE API
//      CoLockObjectExternal        OLE API
//      OleFlushClipboard           OLE API
//      ShowWindow                  Windows API
//
// Comments:
//
//********************************************************************

void CSimpleDoc::Close(void)
{
        OutputDebugString("In CSimpleDoc::Close\r\n");
        CStabilize stabilize(this);
        ShowWindow(m_hDocWnd, SW_HIDE);  // Hide the window

        // Remove our data transfer object from clipboard if it is there.
        //  this will leave HGLOBAL based data behind on the clipboard
        //  including OLE 1.0 compatibility formats.
        OleFlushClipboard();

        // Revoke our window as a DropTarget
        if (m_fRegDragDrop) {
                RevokeDragDrop(m_hDocWnd);
                m_fRegDragDrop = FALSE;
        }

        // Close the OLE object in our document
        if (m_lpSite)
                m_lpSite->CloseOleObject();

        // Unlock the lock added in CSimpleDoc::Create. this will make
        // the document's ref count go to 0, and the document will be deleted.
        CoLockObjectExternal (&m_DropTarget, FALSE, TRUE);
}

//**********************************************************************
//
// CSimpleDoc::CSimpleDoc
//
// Purpose:
//
//      Constructor for the CSimpleDoc Class
//
// Parameters:
//
//      CSimpleApp FAR * lpApp  -   Pointer to the CSimpleApp Class
//
//      HWND hWnd               -   Window Handle of "frame" window
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      GetMenu                     Windows API
//      GetSubMenu                  Windows API
//
// Comments:
//
//********************************************************************
#pragma warning(disable : 4355)  // turn off this warning.  This warning
                                                                // tells us that we are passing this in
                                                                // an initializer, before "this" is through
                                                                // initializing.  This is ok, because
                                                                // we just store the ptr in the other
                                                                // constructor

CSimpleDoc::CSimpleDoc(CSimpleApp FAR * lpApp,HWND hWnd)
                : m_DropTarget(this), m_DropSource(this)
#pragma warning (default : 4355)  // Turn the warning back on
{
        OutputDebugString("In CSimpleDoc's Constructor\r\n");
        m_lpApp = lpApp;
        m_lpSite = NULL;
        // set up menu handles
        lpApp->m_hMainMenu = GetMenu(hWnd);
        lpApp->m_hFileMenu = GetSubMenu(lpApp->m_hMainMenu, 0);
        lpApp->m_hEditMenu = GetSubMenu(lpApp->m_hMainMenu, 1);
        lpApp->m_hHelpMenu = GetSubMenu(lpApp->m_hMainMenu, 2);
        lpApp->m_hCascadeMenu = NULL;
        m_fModifiedMenu = FALSE;

        // drag/drop related stuff
        m_fRegDragDrop = FALSE;       // is doc registered as drop target?
        m_fLocalDrag = FALSE;         // is doc source of the drag
        m_fLocalDrop = FALSE;         // was doc target of the drop
        m_fCanDropCopy = FALSE;       // is Drag/Drop copy/move possible?
        m_fCanDropLink = FALSE;       // is Drag/Drop link possible?
        m_fDragLeave = FALSE;         // has drag left
        m_fPendingDrag = FALSE;       // LButtonDown--possible drag pending
        m_ptButDown.x = m_ptButDown.y = 0; // LButtonDown coordinates
}

//**********************************************************************
//
// CSimpleDoc::~CSimpleDoc
//
// Purpose:
//
//      Destructor for CSimpleDoc
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpleSite::Release        SITE.CPP
//      IStorage::Release           OLE API
//
// Comments:
//
//********************************************************************

CSimpleDoc::~CSimpleDoc()
{
        OutputDebugString("In CSimpleDoc's Destructor\r\n");

        // Release all pointers we hold to the OLE object. also release
        // the ref count added in CSimpleSite::Create. this will make
        // the Site's ref count go to 0, and the Site will be deleted.
        if (m_lpSite) {
                m_lpSite->UnloadOleObject();
                m_lpSite->Release();
                m_lpSite = NULL;
        }

        // Release the Storage
        if (m_lpStorage) {
                m_lpStorage->Release();
                m_lpStorage = NULL;
        }

        // if the edit menu was modified, remove the menu item and
        // destroy the popup if it exists
        if (m_fModifiedMenu)
                {
                int nCount = GetMenuItemCount(m_lpApp->m_hEditMenu);
                RemoveMenu(m_lpApp->m_hEditMenu, nCount-1, MF_BYPOSITION);
                if (m_lpApp->m_hCascadeMenu)
                        DestroyMenu(m_lpApp->m_hCascadeMenu);
                }

        DestroyWindow(m_hDocWnd);
}

//**********************************************************************
//
// CSimpleDoc::QueryInterface
//
// Purpose:
//
//      Return a pointer to a requested interface
//
// Parameters:
//
//      REFIID riid         -   ID of interface to be returned
//      LPVOID FAR* ppvObj  -   Location to return the interface
//
// Return Value:
//
//      S_OK                -   Interface supported
//      E_NOINTERFACE       -   Interface NOT supported
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//                   OLE API
//
// Comments:
//
//********************************************************************

STDMETHODIMP CSimpleDoc::QueryInterface(REFIID riid, LPVOID FAR* ppvObj)
{
        OutputDebugString("In CSimpleDoc::QueryInterface\r\n");

        *ppvObj = NULL;     // must set out pointer parameters to NULL

        // looking for IUnknown
        if ( riid == IID_IUnknown)
                {
                AddRef();
                *ppvObj = this;
                return S_OK;
                }

        // looking for IDropTarget
        if ( riid == IID_IDropTarget)
                {
                m_DropTarget.AddRef();
                *ppvObj=&m_DropTarget;
                return S_OK;
                }

        // looking for IDropSource
        if ( riid == IID_IDropSource)
                {
                m_DropSource.AddRef();
                *ppvObj=&m_DropSource;
                return S_OK;
                }

        // Not a supported interface
        return E_NOINTERFACE;
}

//**********************************************************************
//
// CSimpleDoc::AddRef
//
// Purpose:
//
//      Increments the document reference count
//
// Parameters:
//
//      None
//
// Return Value:
//
//      UINT    -   The current reference count on the document
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpleApp::AddRef          APP.CPP
//
// Comments:
//
//********************************************************************

STDMETHODIMP_(ULONG) CSimpleDoc::AddRef()
{
        OutputDebugString("In CSimpleDoc::AddRef\r\n");
        return SafeAddRef();
}

//**********************************************************************
//
// CSimpleDoc::Release
//
// Purpose:
//
//      Decrements the document reference count
//
// Parameters:
//
//      None
//
// Return Value:
//
//      UINT    -   The current reference count on the document
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//********************************************************************

STDMETHODIMP_(ULONG) CSimpleDoc::Release()
{
        OutputDebugString("In CSimpleDoc::Release\r\n");

        return SafeRelease();
}

//**********************************************************************
//
// CSimpleDoc::InsertObject
//
// Purpose:
//
//      Inserts a new object to this document
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      CSimpleSite::CSimpleSite    SITE.CPP
//      CSimpleSite::InitObject     SITE.CPP
//      memset                      C Runtime
//      OleUIInsertObject           OUTLUI function
//      CSimpleDoc::DisableInsertObject DOC.CPP
//
// Comments:
//
//      This implementation only allows one object to be inserted
//      into a document.  Once the object has been inserted, then
//      the Insert Object menu choice is greyed out, to prevent
//      the user from inserting another.
//
//********************************************************************

void CSimpleDoc::InsertObject()
{
        OLEUIINSERTOBJECT io;
        CStabilize stabilize(this);
        UINT iret;
        //@@WTK WIN32, UNICODE
        //char szFile[OLEUI_CCHPATHMAX];
        char szFile[OLEUI_CCHPATHMAX];

        m_lpSite = CSimpleSite::Create(this);

        // clear the structure
        _fmemset(&io, 0, sizeof(OLEUIINSERTOBJECT));

        // fill the structure
        io.cbStruct = sizeof(OLEUIINSERTOBJECT);
        io.dwFlags = IOF_SELECTCREATENEW |
                                        IOF_DISABLELINK | IOF_DISABLEDISPLAYASICON |
                                        IOF_CREATENEWOBJECT | IOF_CREATEFILEOBJECT;
        io.hWndOwner = m_hDocWnd;
        io.lpszCaption = "Insert Object";
        io.iid = IID_IOleObject;
        io.oleRender = OLERENDER_DRAW;
        io.lpIOleClientSite = &m_lpSite->m_OleClientSite;
        io.lpIStorage = m_lpSite->m_lpObjStorage;
        io.ppvObj = (LPVOID FAR *)&m_lpSite->m_lpOleObject;
        io.lpszFile = szFile;
        io.cchFile = sizeof(szFile) / sizeof *szFile;
        _fmemset(szFile, 0, sizeof(szFile));

        // call OUTLUI to do all the hard work
        iret = OleUIInsertObject(&io);

        if (iret == OLEUI_OK)
                {
                m_lpSite->InitObject((BOOL)(io.dwFlags & IOF_SELECTCREATENEW));
                // disable Insert Object menu item
                DisableInsertObject();
                }
        else
                {
                m_lpSite->Release();
                m_lpSite = NULL;
                m_lpStorage->Revert();
                }

}

//**********************************************************************
//
// CSimpleDoc::lResizeDoc
//
// Purpose:
//
//      Resizes the document
//
// Parameters:
//
//      LPRECT lpRect   -   The size of the client are of the "frame"
//                          Window.
//
// Return Value:
//
//      NULL
//
// Function Calls:
//      Function                                Location
//
//      MoveWindow                              Windows API
//
// Comments:
//
//********************************************************************

long CSimpleDoc::lResizeDoc(LPRECT lpRect)
{
        MoveWindow(
                        m_hDocWnd,
                        lpRect->left, lpRect->top,
                        lpRect->right, lpRect->bottom, TRUE);

        return NULL;
}

//**********************************************************************
//
// CSimpleDoc::lAddVerbs
//
// Purpose:
//
//      Adds the objects verbs to the edit menu.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      NULL
//
// Function Calls:
//      Function                    Location
//
//      GetMenuItemCount            Windows API
//      OleUIAddVerbMenu            OUTLUI function
//
// Comments:
//
//********************************************************************

long CSimpleDoc::lAddVerbs(void)
{
        CStabilize stabilize(this);
        // m_fModifiedMenu is TRUE if the menu has already been modified
        // once.  Since we only support one obect every time the application
        // is run, then once the menu is modified, it doesn't have
        // to be done again.
        if (m_lpSite && !m_fModifiedMenu)
                {
                int nCount = GetMenuItemCount(m_lpApp->m_hEditMenu);

                OleUIAddVerbMenu ( m_lpSite->m_lpOleObject,
                                                   NULL,
                                                   m_lpApp->m_hEditMenu,
                                                   nCount + 1,
                                                   IDM_VERB0,
                                                   0,           // no maximum verb IDM enforced
                                                   FALSE,
                                                   1,
                                                   &m_lpApp->m_hCascadeMenu);

                m_fModifiedMenu = TRUE;
                }
        return (NULL);
}

//**********************************************************************
//
// CSimpleDoc::PaintDoc
//
// Purpose:
//
//      Paints the Document
//
// Parameters:
//
//      HDC hDC -   hDC of the document Window
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      CSimpleSite::PaintObj       SITE.CPP
//
// Comments:
//
//********************************************************************

void CSimpleDoc::PaintDoc (HDC hDC)
{
        CStabilize stabilize(this);
        // if we supported multiple objects, then we would enumerate
        // the objects and call paint on each of them from here.

        if (m_lpSite)
                m_lpSite->PaintObj(hDC);

}

//**********************************************************************
//
// CSimpleDoc::DisableInsertObject
//
// Purpose:
//
//      Disable the ability to insert a new object in this document.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      RevokeDragDrop              OLE API
//      EnableMenuItem              Windows API
//
// Comments:
//
//      This implementation only allows one object to be inserted
//      into a document.  Once the object has been inserted, then
//      the Insert Object menu choice is greyed out, to prevent
//      the user from inserting another. Also we revoke ourself as
//      a potential drop target.
//
//********************************************************************

void CSimpleDoc::DisableInsertObject(void)
{
        // Disable InsertObject menu choice
        EnableMenuItem( m_lpApp->m_hEditMenu, 1, MF_BYPOSITION | MF_DISABLED | MF_GRAYED);
        // Enable Copy menu choice
        EnableMenuItem( m_lpApp->m_hEditMenu, 0, MF_BYPOSITION | MF_ENABLED);

        // We no longer accept dropping of objects
        if (m_fRegDragDrop) {
                RevokeDragDrop(m_hDocWnd);
                m_fRegDragDrop = FALSE;
        }
}

//**********************************************************************
//
// CSimpleDoc::CopyObjectToClip
//
// Purpose:
//
//      Copy the embedded OLE object to the clipboard
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      CDataXferObj::Create        DXFEROBJ.CPP
//      CDataXferObj::QueryInterface DXFEROBJ.CPP
//      OleSetClipboard             OLE API
//
// Comments:
//
//      This implementation only allows one object to be inserted
//      into a document.  Once the object has been inserted, then
//      the Copy menu choice is enabled.
//
//********************************************************************

void CSimpleDoc::CopyObjectToClip(void)
{
        LPDATAOBJECT lpDataObj;

        // Create a data transfer object by cloning the existing OLE object
        CDataXferObj FAR* pDataXferObj = CDataXferObj::Create(m_lpSite,NULL);
        if (! pDataXferObj) {
                MessageBox(NULL,"Out-of-memory","SimpDnD",MB_SYSTEMMODAL|MB_ICONHAND);
                return;
        }
        // initially obj is created with 0 refcnt. this QI will make it go to 1.
        pDataXferObj->QueryInterface(IID_IDataObject, (LPVOID FAR*)&lpDataObj);

        // put out data transfer object on the clipboard. this API will AddRef.
        OleSetClipboard(lpDataObj);

        // Give ownership of data transfer object to clipboard
        pDataXferObj->Release();
}


DOC.H

//**********************************************************************
// File name: doc.h
//
//      Definition of CSimpleDoc
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#if !defined( _DOC_H_ )
#define _DOC_H_

#include "idt.h"
#include "ids.h"
#include <stablize.h>

class CSimpleSite;
class CSimpleApp;

class CSimpleDoc : public IUnknown, public CSafeRefCount
{
public:
        LPSTORAGE       m_lpStorage;        // IStorage* pointer for Doc
        BOOL            m_fModifiedMenu;    // is object's verb menu on menu

        // Drag/Drop related fields
        BOOL            m_fRegDragDrop;     // is doc registered as drop target?
        BOOL            m_fLocalDrag;       // is doc source of the drag
        BOOL            m_fLocalDrop;       // was doc target of the drop
        BOOL            m_fCanDropCopy;     // is Drag/Drop copy/move possible?
        BOOL            m_fCanDropLink;     // is Drag/Drop link possible?
        BOOL            m_fDragLeave;       // has drag left
        BOOL            m_fPendingDrag;     // LButtonDown--possible drag pending
        POINT           m_ptButDown;        // LButtonDown coordinates

        CSimpleSite FAR * m_lpSite;
        CSimpleApp FAR * m_lpApp;

        HWND m_hDocWnd;

        CDropTarget m_DropTarget;
        CDropSource m_DropSource;

        static CSimpleDoc FAR* Create(CSimpleApp FAR *lpApp, LPRECT lpRect,
                        HWND hWnd);

        void Close(void);

        CSimpleDoc();
        CSimpleDoc(CSimpleApp FAR *lpApp, HWND hWnd);
        ~CSimpleDoc();

        // IUnknown Interface
        STDMETHODIMP QueryInterface(REFIID riid, LPVOID FAR* ppvObj);
        STDMETHODIMP_(ULONG) AddRef();
        STDMETHODIMP_(ULONG) Release();

        void InsertObject(void);
        void DisableInsertObject(void);
        long lResizeDoc(LPRECT lpRect);
        long lAddVerbs(void);
        void PaintDoc(HDC hDC);

        // Drag/Drop and clipboard support methods
        void CopyObjectToClip(void);
        BOOL QueryDrag(POINT pt);
        DWORD DoDragDrop(void);
        void Scroll(DWORD dwScrollDir) { /*...scroll Doc here...*/ }
};

#endif  // _DOC_H_


DXFEROBJ.CPP

//**********************************************************************
// File name: DXFEROBJ.CPP
//
//       Implementation file for CDataXferObj, data transfer object
//     implementation of IDataObject interface.
//
// Functions:
//
//      See DXFEROBJ.H for class definition
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
//@@WTK WIN32, UNICODE
//#include "..\include\enumfetc.h"
#include <assert.h>
#include "dxferobj.h"
#include "site.h"

CLIPFORMAT g_cfEmbeddedObject = RegisterClipboardFormat(CF_EMBEDDEDOBJECT);
CLIPFORMAT g_cfObjectDescriptor =RegisterClipboardFormat(CF_OBJECTDESCRIPTOR);

// List of formats offered by our data transfer object via EnumFormatEtc
static FORMATETC s_arrGetFmtEtcs[] =
{
        { g_cfEmbeddedObject, NULL, DVASPECT_CONTENT, -1, TYMED_ISTORAGE},
        { g_cfObjectDescriptor, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL},
        { CF_METAFILEPICT, NULL, DVASPECT_CONTENT, -1, TYMED_MFPICT}
};

//**********************************************************************
//
// CDataXferObj::Create
//
// Purpose:
//
//      Creation routine for CDataXferObj
//
// Parameters:
//
//      CSimpleSite FAR *lpSite   - Pointer to source CSimpleSite
//                                  this is the container site of the
//                                  source OLE object to be transfered
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      StgCreateDocfile            OLE API
//      assert                      C Runtime
//
// Comments:
//      reference count of CDataXferObj will be 0 on return.
//
//********************************************************************

CDataXferObj FAR * CDataXferObj::Create(
                CSimpleSite FAR *lpSite,
                POINTL FAR* pPointl
)
{
        CDataXferObj FAR * lpTemp = new CDataXferObj();

        if (!lpTemp)
                return NULL;

        // create a sub-storage for the object
        HRESULT hErr = StgCreateDocfile(
                                NULL,
                                STGM_READWRITE | STGM_DIRECT | STGM_SHARE_EXCLUSIVE |
                                        STGM_DELETEONRELEASE,
                                0,
                                &lpTemp->m_lpObjStorage);

        assert(hErr == NOERROR);

        if (hErr != NOERROR)
                {
                delete lpTemp;
                return NULL;
                }

        // Clone the source object
        if (lpSite->m_lpOleObject) {
                // Object is loaded; ask the object to save into the new storage
                LPPERSISTSTORAGE pPersistStorage;

                lpSite->m_lpOleObject->QueryInterface(IID_IPersistStorage,
                                (LPVOID FAR*)&pPersistStorage);
                assert(pPersistStorage);
                OleSave(pPersistStorage, lpTemp->m_lpObjStorage, FALSE);

                // pass NULL so that object application won't forget the real stg
                pPersistStorage->SaveCompleted(NULL);
                pPersistStorage->Release();
        } else {
                // Object not loaded so use cheaper IStorage CopyTo operation
                lpSite->m_lpObjStorage->CopyTo(0, NULL, NULL, lpTemp->m_lpObjStorage);
        }

        OleLoad(lpTemp->m_lpObjStorage, IID_IOleObject, NULL,
                        (LPVOID FAR*)&lpTemp->m_lpOleObject);
        assert(lpTemp->m_lpOleObject);

        lpTemp->m_sizel = lpSite->m_sizel;
        if (pPointl)
                lpTemp->m_pointl = *pPointl;
        else
                lpTemp->m_pointl.x = lpTemp->m_pointl.y = 0;
        return lpTemp;
}

//**********************************************************************
//
// CDataXferObj::CDataXferObj
//
// Purpose:
//
//      Constructor for CDataXferObj
//
// Parameters:
//
//      CSimpleDoc FAR *lpDoc   - Pointer to CSimpleDoc
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
// Comments:
//
//********************************************************************

CDataXferObj::CDataXferObj (void)
{
        // clear the reference count
        m_nCount = 0;

        m_lpObjStorage = NULL;
        m_lpOleObject = NULL;
        m_sizel.cx = m_sizel.cy = 0;
        m_pointl.x = m_pointl.y = 0;
}

//**********************************************************************
//
// CDataXferObj::~CDataXferObj
//
// Purpose:
//
//      Destructor for CDataXferObj
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                                Location
//
//      OutputDebugString                       Windows API
//      IOleObject::Release                     Object
//      IStorage::Release                       OLE API
//
// Comments:
//
//********************************************************************

CDataXferObj::~CDataXferObj ()
{
        OutputDebugString ("In CDataXferObj's Destructor \r\n");

        if (m_lpOleObject)
           {
           m_lpOleObject->Release();
           m_lpOleObject = NULL;

           // Release the storage for this object
           m_lpObjStorage->Release();
           m_lpObjStorage = NULL;
           }
}

//**********************************************************************
//
// CDataXferObj::QueryInterface
//
// Purpose:
//
//      Used for interface negotiation of the CDataXferObj instance
//
// Parameters:
//
//      REFIID riid         -   A reference to the interface that is
//                              being queried.
//
//      LPVOID FAR* ppvObj  -   An out parameter to return a pointer to
//                              the interface.
//
// Return Value:
//
//      S_OK            -   The interface is supported.
//      E_NOINTERFACE   -   The interface is not supported
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      IsEqualIID                  OLE API
//                   OLE API
//      CDataXferObj::AddRef        DXFEROBJ.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP CDataXferObj::QueryInterface(REFIID riid, LPVOID FAR* ppvObj)
{
        OutputDebugString("In CDataXferObj::QueryInterface\r\n");

        if ( riid == IID_IUnknown || riid == IID_IDataObject)
                {
                AddRef();
                *ppvObj = this;
                return NOERROR;
                }

        // unknown interface requested
        *ppvObj = NULL;     // must set out pointer parameters to NULL
        return E_NOINTERFACE;
}

//**********************************************************************
//
// CDataXferObj::AddRef
//
// Purpose:
//
//      Increments the reference count of the CDataXferObj instance
//
// Parameters:
//
//      None
//
// Return Value:
//
//      ULONG   -   The new reference count of the object
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//********************************************************************

STDMETHODIMP_(ULONG) CDataXferObj::AddRef()
{
        OutputDebugString("In CDataXferObj::AddRef\r\n");

        return ++m_nCount;
}

//**********************************************************************
//
// CDataXferObj::Release
//
// Purpose:
//
//      Decrements the reference count of the CDataXferObj object
//
// Parameters:
//
//      None
//
// Return Value:
//
//      ULONG   -   The new reference count of the object.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//********************************************************************

STDMETHODIMP_(ULONG) CDataXferObj::Release()
{
        OutputDebugString("In CDataXferObj::Release\r\n");

        if (--m_nCount == 0) {
                delete this;
                return 0;
        }
        return m_nCount;
}

/********************************************************************
** This IDataObject implementation is used for data transfer.
**
** The following methods are NOT supported for data transfer:
**      IDataObject::SetData    -- return E_NOTIMPL
**      IDataObject::DAdvise    -- return OLE_E_ADVISENOTSUPPORTED
**                 ::DUnadvise
**                 ::EnumDAdvise
**      IDataObject::GetCanonicalFormatEtc -- return E_NOTIMPL
**                      (NOTE: must set pformatetcOut->ptd = NULL)
*********************************************************************/

//**********************************************************************
//
// CDataXferObj::QueryGetData
//
// Purpose:
//
//      Called to determine if our object supports a particular
//      FORMATETC.
//
// Parameters:
//
//      LPFORMATETC pformatetc  - Pointer to the FORMATETC being queried for.
//
// Return Value:
//
//      DV_E_FORMATETC    - The FORMATETC is not supported
//      S_OK              - The FORMATETC is supported.
//
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//                   OLE API
//
// Comments:
//      we support the following formats:
//          "Embedded Object"
//          "Object Descriptor"
//          CF_METAFILEPICT
//
//********************************************************************
STDMETHODIMP CDataXferObj::QueryGetData (LPFORMATETC pformatetc)
{
        SCODE sc = DV_E_FORMATETC;

        OutputDebugString("In CDataXferObj::QueryGetData\r\n");

        // check the validity of the formatetc.

        if ( (pformatetc->cfFormat == g_cfEmbeddedObject) &&
                 (pformatetc->dwAspect == DVASPECT_CONTENT) &&
                 (pformatetc->tymed == TYMED_ISTORAGE) )
                sc = S_OK;

        else if ( (pformatetc->cfFormat == g_cfObjectDescriptor) &&
                 (pformatetc->dwAspect == DVASPECT_CONTENT) &&
                 (pformatetc->tymed == TYMED_HGLOBAL) )
                sc = S_OK;

        else if ( (pformatetc->cfFormat == CF_METAFILEPICT) &&
                 (pformatetc->dwAspect == DVASPECT_CONTENT) &&
                 (pformatetc->tymed == TYMED_MFPICT) )
                sc = S_OK;

        return sc;
}

STDMETHODIMP CDataXferObj::EnumFormatEtc(
                DWORD dwDirection,
                LPENUMFORMATETC FAR* ppenumFormatEtc
)
{
        SCODE sc = E_NOTIMPL;

        OutputDebugString("In CDataXferObj::EnumFormatEtc\r\n");
        *ppenumFormatEtc = NULL;

        if (dwDirection == DATADIR_GET) {
                *ppenumFormatEtc = OleStdEnumFmtEtc_Create(
                                sizeof(s_arrGetFmtEtcs)/sizeof(s_arrGetFmtEtcs[0]),
                                s_arrGetFmtEtcs);
                if (*ppenumFormatEtc == NULL)
                        sc = E_OUTOFMEMORY;
                else
                        sc = S_OK;
        }
        return sc;
}

//**********************************************************************
//
// CDataXferObj::GetData
//
// Purpose:
//
//      Returns the data in the format specified in pformatetcIn.
//
// Parameters:
//
//      LPFORMATETC pformatetcIn    -   The format requested by the caller
//
//      LPSTGMEDIUM pmedium         -   The medium requested by the caller
//
// Return Value:
//
//      DV_E_FORMATETC    - Format not supported
//      S_OK                - Success
//
// Function Calls:
//      Function                        Location
//
//      OutputDebugString               Windows API
//      OleStdGetOleObjectData          OLESTD API
//      OleStdGetMetafilePictFromOleObject OLESTD API
//                       OLE API
//
// Comments:
//      we support GetData for the following formats:
//          "Embedded Object"
//          "Object Descriptor"
//          CF_METAFILEPICT
//
//********************************************************************

STDMETHODIMP CDataXferObj::GetData (
                LPFORMATETC pformatetcIn,
                LPSTGMEDIUM pmedium
)
{
        SCODE sc = DV_E_FORMATETC;

        OutputDebugString("In CDataXferObj::GetData\r\n");

        // we must set all out pointer parameters to NULL. */
        pmedium->tymed = TYMED_NULL;
        pmedium->pUnkForRelease = NULL;    // we transfer ownership to caller
        pmedium->hGlobal = NULL;

        // Check the FORMATETC and fill pmedium if valid.
        if ( (pformatetcIn->cfFormat == g_cfEmbeddedObject) &&
                 (pformatetcIn->dwAspect == DVASPECT_CONTENT) &&
                 (pformatetcIn->tymed == TYMED_ISTORAGE) ) {
                 LPPERSISTSTORAGE pPersistStorage;

                 /* render CF_EMBEDDEDOBJECT by asking the object to save
                 **    into a temporary, DELETEONRELEASE IStorage allocated by us.
                 */
                 m_lpOleObject->QueryInterface(
                                 IID_IPersistStorage, (LPVOID FAR*)&pPersistStorage);
                 assert(pPersistStorage);
                 HRESULT hrErr = OleStdGetOleObjectData(
                                        pPersistStorage,
                                        pformatetcIn,
                                        pmedium,
                                        FALSE   /* fUseMemory -- (use file-base stg) */
                 );
                 pPersistStorage->Release();
                 sc =  hrErr ;

        } else if ( (pformatetcIn->cfFormat == g_cfObjectDescriptor) &&
                 (pformatetcIn->dwAspect == DVASPECT_CONTENT) &&
                 (pformatetcIn->tymed == TYMED_HGLOBAL) ) {

                 // render CF_OBJECTDESCRIPTOR data
                 pmedium->hGlobal = OleStdGetObjectDescriptorDataFromOleObject(
                                 m_lpOleObject,
                                 //@@WTK WIN32, UNICODE
                                 //"Simple OLE 2.0 Container",    // string to identify source
                                 OLESTR("Simple OLE 2.0 Container"),    // string to identify source
                                 DVASPECT_CONTENT,
                                 m_pointl,
                                 (LPSIZEL)&m_sizel
                        );
                 if (! pmedium->hGlobal)
                         sc = E_OUTOFMEMORY;
                 else {
                         pmedium->tymed = TYMED_HGLOBAL;
                         sc = S_OK;
                 }

        } else if ( (pformatetcIn->cfFormat == CF_METAFILEPICT) &&
                 (pformatetcIn->dwAspect == DVASPECT_CONTENT) &&
                 (pformatetcIn->tymed == TYMED_MFPICT) ) {

                 // render CF_METAFILEPICT by drawing the object into a metafile DC
                 pmedium->hGlobal = OleStdGetMetafilePictFromOleObject(
                                 m_lpOleObject, DVASPECT_CONTENT, NULL, pformatetcIn->ptd);
                 if (! pmedium->hGlobal)
                         sc = E_OUTOFMEMORY;
                 else {
                         pmedium->tymed = TYMED_MFPICT;
                         sc = S_OK;
                 }
        }

        return  sc ;
}

//**********************************************************************
//
// CDataXferObj::GetDataHere
//
// Purpose:
//
//      Called to get a data format in a caller supplied location
//
// Parameters:
//
//      LPFORMATETC pformatetc  - FORMATETC requested
//
//      LPSTGMEDIUM pmedium     - Medium to return the data
//
// Return Value:
//
//      DATA_E_FORMATETC    - We don't support the requested format
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      OleStdGetOleObjectData      OLESTD API
//
// Comments:
//
//********************************************************************

STDMETHODIMP CDataXferObj::GetDataHere (
                LPFORMATETC pformatetc,
                LPSTGMEDIUM pmedium
)
{
        SCODE sc = DV_E_FORMATETC;

        OutputDebugString("In CDataXferObj::GetDataHere\r\n");

        // NOTE: pmedium is an IN parameter. we should NOT set
        //           pmedium->pUnkForRelease to NULL

        // Check the FORMATETC and fill pmedium if valid.
        if ( (pformatetc->cfFormat == g_cfEmbeddedObject) &&
                 (pformatetc->dwAspect == DVASPECT_CONTENT) &&
                 (pformatetc->tymed == TYMED_ISTORAGE) ) {
                 LPPERSISTSTORAGE pPersistStorage;

                 /* render CF_EMBEDDEDOBJECT by asking the object to save
                 **    into the IStorage allocated by the caller.
                 */
                 m_lpOleObject->QueryInterface(
                                 IID_IPersistStorage, (LPVOID FAR*)&pPersistStorage);
                 assert(pPersistStorage);
                 HRESULT hrErr = OleStdGetOleObjectData(
                                 pPersistStorage, pformatetc, pmedium,0 /*fUseMemory--N/A*/ );
                 pPersistStorage->Release();
                 sc =  hrErr ;
        }
        return  sc ;
}


DXFEROBJ.H

//**********************************************************************
// File name: dxferobj.h
//
//      Definition of CDataXferObj
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#if !defined( _DATAXFEROBJ_H_)
#define _DATAXFEROBJ_H_

class CSimpleSite;

interface CDataObject;

class CDataXferObj : public IDataObject
{
private:
int m_nCount;                       // reference count
SIZEL m_sizel;
POINTL m_pointl;
LPSTORAGE m_lpObjStorage;
LPOLEOBJECT m_lpOleObject;

// construction/destruction
CDataXferObj();
~CDataXferObj();

public:
STDMETHODIMP QueryInterface (REFIID riid, LPVOID FAR* ppvObj);
STDMETHODIMP_(ULONG) AddRef ();
STDMETHODIMP_(ULONG) Release ();

STDMETHODIMP DAdvise  ( FORMATETC FAR* pFormatetc, DWORD advf,
LPADVISESINK pAdvSink, DWORD FAR* pdwConnection)
{ return ResultFromScode(OLE_E_ADVISENOTSUPPORTED); }
STDMETHODIMP DUnadvise  ( DWORD dwConnection)
{ return ResultFromScode(OLE_E_ADVISENOTSUPPORTED); }
STDMETHODIMP EnumDAdvise  ( LPENUMSTATDATA FAR* ppenumAdvise)
{ return ResultFromScode(OLE_E_ADVISENOTSUPPORTED); }
STDMETHODIMP EnumFormatEtc  ( DWORD dwDirection,
LPENUMFORMATETC FAR* ppenumFormatEtc);
STDMETHODIMP GetCanonicalFormatEtc  ( LPFORMATETC pformatetc,
LPFORMATETC pformatetcOut)
{ pformatetcOut->ptd = NULL; return ResultFromScode(E_NOTIMPL); }
STDMETHODIMP GetData  (LPFORMATETC pformatetcIn, LPSTGMEDIUM pmedium );
STDMETHODIMP GetDataHere  (LPFORMATETC pformatetc, LPSTGMEDIUM pmedium);
STDMETHODIMP QueryGetData  (LPFORMATETC pformatetc );
STDMETHODIMP SetData  (LPFORMATETC pformatetc, STGMEDIUM FAR * pmedium,
BOOL fRelease)
{ return ResultFromScode(E_NOTIMPL); }

static CDataXferObj FAR* Create(CSimpleSite FAR* lpSite,
POINTL FAR* pPointl);

};
#endif  // _DATAXFEROBJ_H_


IAS.CPP

//**********************************************************************
// File name: IAS.CPP
//
//      Implementation file of CAdviseSink
//
//
// Functions:
//
//      See IAS.H for Class Definition
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "iocs.h"
#include "ias.h"
#include "app.h"
#include "site.h"
#include "doc.h"

//**********************************************************************
//
// CAdviseSink::QueryInterface
//
// Purpose:
//
//      Returns a pointer to a requested interface.
//
// Parameters:
//
//      REFIID riid         - The requested interface
//
//      LPVOID FAR* ppvObj  - Place to return the interface
//
// Return Value:
//
//      HRESULT from CSimpleSite::QueryInterface
//
// Function Calls:
//      Function                    Location
//
//      CSimpleSite::QueryInterface SITE.CPP
//      OutputDebugString           Windows API
//
// Comments:
//
//      This function simply delegates to the Object class, which is
//      aware of the supported interfaces.
//
//********************************************************************

STDMETHODIMP CAdviseSink::QueryInterface(REFIID riid, LPVOID FAR* ppvObj)
{
OutputDebugString("In IAS::QueryInterface\r\n");

// delegate to the document Object
return m_pSite->QueryInterface(riid, ppvObj);
}

//**********************************************************************
//
// CAdviseSink::AddRef
//
// Purpose:
//
//      Increments the reference count on this interface
//
// Parameters:
//
//      None
//
// Return Value:
//
//      The current reference count on this interface.
//
// Function Calls:
//      Function                    Location
//
//      CSimpleSite::AddReff        SITE.CPP
//      OutputDebugString           Windows API
//
// Comments:
//
//      This function adds one to the ref count of the interface,
//      and calls then calls CSimpleSite to increment its ref.
//      count.
//
//********************************************************************

STDMETHODIMP_(ULONG) CAdviseSink::AddRef()
{
OutputDebugString("In IAS::AddRef\r\n");

// increment the interface reference count (for debugging only)
++m_nCount;

// delegate to the container Site
return m_pSite->AddRef();
}

//**********************************************************************
//
// CAdviseSink::Release
//
// Purpose:
//
//      Decrements the reference count on this interface
//
// Parameters:
//
//      None
//
// Return Value:
//
//      The current reference count on this interface.
//
// Function Calls:
//      Function                    Location
//
//      CSimpleSite::Release        SITE.CPP
//      OutputDebugString           Windows API
//
// Comments:
//
//      This function subtracts one from the ref count of the interface,
//      and calls then calls CSimpleSite to decrement its ref.
//      count.
//
//********************************************************************

STDMETHODIMP_(ULONG) CAdviseSink::Release()
{
OutputDebugString("In IAS::Release\r\n");

// decrement the interface reference count (for debugging only)
m_nCount--;

// delegate to the container Site
return m_pSite->Release();
}

//**********************************************************************
//
// CAdviseSink::OnDataChange
//
// Purpose:
//
//      Not Implemented (needs to be stubbed out)
//
// Parameters:
//
//      Not Implemented (needs to be stubbed out)
//
// Return Value:
//
//      Not Implemented (needs to be stubbed out)
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Not Implemented (needs to be stubbed out)
//
//********************************************************************

STDMETHODIMP_(void) CAdviseSink::OnDataChange (FORMATETC FAR* pFormatetc, STGMEDIUM FAR* pStgmed)
{
OutputDebugString("In IAS::OnDataChange\r\n");
}

//**********************************************************************
//
// CAdviseSink::OnViewChange
//
// Purpose:
//
//      Notifies us that the view has changed and needs to be updated.
//
// Parameters:
//
//      DWORD dwAspect  - Aspect that has changed
//
//      LONG lindex     - Index that has changed
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      InvalidateRect              Windows API
//      IViewObject2::GetExtent     Object
//
// Comments:
//
//********************************************************************

STDMETHODIMP_(void) CAdviseSink::OnViewChange (DWORD dwAspect, LONG lindex)
{
LPVIEWOBJECT2 lpViewObject2;
OutputDebugString("In IAS::OnViewChange\r\n");

// get a pointer to IViewObject2
HRESULT hErr = m_pSite->m_lpOleObject->QueryInterface(
IID_IViewObject2,(LPVOID FAR *)&lpViewObject2);

if (hErr == NOERROR) {
// get extent of the object
// NOTE: this method will never be remoted; it can be called w/i this async method
lpViewObject2->GetExtent(DVASPECT_CONTENT, -1 , NULL, &m_pSite->m_sizel);
lpViewObject2->Release();
}

InvalidateRect(m_pSite->m_lpDoc->m_hDocWnd, NULL, TRUE);
}

//**********************************************************************
//
// CAdviseSink::OnRename
//
// Purpose:
//
//      Not Implemented (needs to be stubbed out)
//
// Parameters:
//
//      Not Implemented (needs to be stubbed out)
//
// Return Value:
//
//      Not Implemented (needs to be stubbed out)
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Not Implemented (needs to be stubbed out)
//
//********************************************************************

STDMETHODIMP_(void) CAdviseSink::OnRename (LPMONIKER pmk)
{
OutputDebugString("In IAS::OnRename\r\n");
}

//**********************************************************************
//
// CAdviseSink::OnSave
//
// Purpose:
//
//      Not Implemented (needs to be stubbed out)
//
// Parameters:
//
//      Not Implemented (needs to be stubbed out)
//
// Return Value:
//
//      Not Implemented (needs to be stubbed out)
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Not Implemented (needs to be stubbed out)
//
//********************************************************************

STDMETHODIMP_(void) CAdviseSink::OnSave ()
{
OutputDebugString("In IAS::OnSave\r\n");
}

//**********************************************************************
//
// CAdviseSink::OnClose
//
// Purpose:
//
//      Not Implemented (needs to be stubbed out)
//
// Parameters:
//
//      Not Implemented (needs to be stubbed out)
//
// Return Value:
//
//      Not Implemented (needs to be stubbed out)
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Not Implemented (needs to be stubbed out)
//
//********************************************************************

STDMETHODIMP_(void) CAdviseSink::OnClose()
{
OutputDebugString("In IAS::OnClose\r\n");
}


IDS.CPP

//**********************************************************************
// File name: IDS.CPP
//
//      Implementation file for CDropSource
//
// Functions:
//
//      See IDS.H for class definition
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "doc.h"
#include "site.h"
#include "dxferobj.h"

//**********************************************************************
//
// CSimpleDoc::QueryDrag
//
// Purpose:
//
//      Check to see if Drag operation should be initiated based on the
//      current position of the mouse.
//
// Parameters:
//
//      POINT pt                - position of mouse
//
// Return Value:
//
//      BOOL                    - TRUE if drag should take place,
//                                else FALSE
//
// Function Calls:
//      Function                    Location
//
//      CSimpleSite::GetObjRect     SITE.CPP
//      PtInRect                    Windows API
//
// Comments:
//
//********************************************************************

BOOL CSimpleDoc::QueryDrag(POINT pt)
{
// if pt is within rect of object, then start drag
if (m_lpSite)
{
RECT rect;
m_lpSite->GetObjRect(&rect);
return ( PtInRect(&rect, pt) ? TRUE : FALSE );
}
else
return FALSE;
}

//**********************************************************************
//
// CSimpleDoc::DoDragDrop
//
// Purpose:
//
//      Actually perform a drag/drop operation with the current
//      selection in the source document.
//
// Parameters:
//
//      none.
//
// Return Value:
//
//      DWORD                    - returns the result effect of the
//                                 drag/drop operation:
//                                      DROPEFFECT_NONE,
//                                      DROPEFFECT_COPY,
//                                      DROPEFFECT_MOVE, or
//                                      DROPEFFECT_LINK
//
// Function Calls:
//      Function                    Location
//
//      CDataXferObj::Create        DXFEROBJ.CPP
//      CDataXferObj::QueryInterface DXFEROBJ.CPP
//      CDataXferObj::Release       DXFEROBJ.CPP
//      DoDragDrop                  OLE API
//      OutputDebugString           Windows API
//
// Comments:
//
//********************************************************************

DWORD CSimpleDoc::DoDragDrop (void)
{
DWORD       dwEffect     = 0;
LPDATAOBJECT lpDataObj;

OutputDebugString("In CSimpleDoc::DoDragDrop\r\n");

// Create a data transfer object by cloning the existing OLE object
CDataXferObj FAR* pDataXferObj = CDataXferObj::Create(m_lpSite,NULL);

if (! pDataXferObj) {
MessageBox(NULL,"Out-of-memory","SimpDnD",MB_SYSTEMMODAL|MB_ICONHAND);
return DROPEFFECT_NONE;
}

// initially obj is created with 0 refcnt. this QI will make it go to 1.
pDataXferObj->QueryInterface(IID_IDataObject, (LPVOID FAR*)&lpDataObj);
assert(lpDataObj);

m_fLocalDrop     = FALSE;
m_fLocalDrag     = TRUE;

::DoDragDrop ( lpDataObj,
 &m_DropSource,
 DROPEFFECT_COPY,   // we only allow copy
 &dwEffect
);

m_fLocalDrag     = FALSE;

/* if after the Drag/Drop modal (mouse capture) loop is finished
**    and a drag MOVE operation was performed, then we must delete
**    the selection that was dragged.
*/
if ( (dwEffect & DROPEFFECT_MOVE) != 0 ) {
// ... delete source object here (we do NOT support MOVE)
}

pDataXferObj->Release();    // this should destroy the DataXferObj
return dwEffect;
}

//**********************************************************************
//
// CDropSource::QueryInterface
//
// Purpose:
//
//      Return a pointer to a requested interface
//
// Parameters:
//
//      REFIID riid         -   ID of interface to be returned
//      LPVOID FAR* ppvObj  -   Location to return the interface
//
// Return Value:
//
//      S_OK                -   Interface supported
//      E_NOINTERFACE       -   Interface NOT supported
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpleDoc::QueryInterface  DOC.CPP
//
// Comments:
//
//********************************************************************

STDMETHODIMP CDropSource::QueryInterface(REFIID riid, LPVOID FAR* ppvObj)
{
OutputDebugString("In IDS::QueryInterface\r\n");

// delegate to the document
return m_pDoc->QueryInterface(riid, ppvObj);
}

//**********************************************************************
//
// CDropSource::AddRef
//
// Purpose:
//
//      Increments the reference count on this interface
//
// Parameters:
//
//      None
//
// Return Value:
//
//      The current reference count on this interface.
//
// Function Calls:
//      Function                    Location
//
//      CSimpleObj::AddReff         OBJ.CPP
//      OutputDebugString           Windows API
//
// Comments:
//
//      This function adds one to the ref count of the interface,
//      and calls then calls CSimpleDoc to increment its ref.
//      count.
//
//********************************************************************

STDMETHODIMP_(ULONG) CDropSource::AddRef()
{
OutputDebugString("In IDS::AddRef\r\n");

// increment the interface reference count (for debugging only)
++m_nCount;

// delegate to the document Object
return m_pDoc->AddRef();
}

//**********************************************************************
//
// CDropSource::Release
//
// Purpose:
//
//      Decrements the reference count on this interface
//
// Parameters:
//
//      None
//
// Return Value:
//
//      The current reference count on this interface.
//
// Function Calls:
//      Function                    Location
//
//      CSimpleObj::Release         OBJ.CPP
//      OutputDebugString           Windows API
//
// Comments:
//
//      This function subtracts one from the ref count of the interface,
//      and calls then calls CSimpleDoc to decrement its ref.
//      count.
//
//********************************************************************

STDMETHODIMP_(ULONG) CDropSource::Release()
{
OutputDebugString("In IDS::Release\r\n");

// decrement the interface reference count (for debugging only)
--m_nCount;

// delegate to the document object
return m_pDoc->Release();
}

//**********************************************************************
//
// CDropSource::QueryContinueDrag
//
// Purpose:
//
//      Called to determine if a drop should take place or be canceled.
//
// Parameters:
//
//      BOOL fEscapePressed - TRUE if ESCAPE key has been pressed
//      DWORD grfKeyState   - key state
//
// Return Value:
//
//      DRAGDROP_S_CANCEL   - drag operation should be canceled
//      DRAGDROP_S_DROP     - drop operation should be performed
//      S_OK                - dragging should continue
//
//
// Function Calls:
//      Function                    Location
//
//                   OLE API
//
// Comments:
//
//********************************************************************

STDMETHODIMP CDropSource::QueryContinueDrag (
BOOL    fEscapePressed,
DWORD   grfKeyState
)
{
if (fEscapePressed)
return DRAGDROP_S_CANCEL;
else if (!(grfKeyState & MK_LBUTTON))
return DRAGDROP_S_DROP;
else
return NOERROR;
}

//**********************************************************************
//
// CDropSource::GiveFeedback
//
// Purpose:
//
//      Called to set cursor feedback
//
// Parameters:
//
//      DWORD dwEffect      - drop operation to give feedback for
//
// Return Value:
//
//      DRAGDROP_S_USEDEFAULTCURSORS  - tells OLE to use standard cursors
//
// Function Calls:
//      Function                    Location
//
//                   OLE API
//
// Comments:
//
//********************************************************************

STDMETHODIMP CDropSource::GiveFeedback (DWORD dwEffect)
{
return DRAGDROP_S_USEDEFAULTCURSORS;
}


IDS.H

//**********************************************************************
// File name: ids.h
//
//      Definition of CDropSource
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************
#if !defined( _IDS_H_ )
#define _IDS_H_

#include <assert.h>

class CSimpleDoc;

interface CDropSource : public IDropSource
{
int m_nCount;
CSimpleDoc FAR * m_pDoc;

CDropSource(CSimpleDoc FAR * pDoc) {
OutputDebugString("In IDS's constructor\r\n");
m_pDoc = pDoc;
m_nCount = 0;
};

~CDropSource() {
OutputDebugString("In IDS's destructor\r\n");
} ;

STDMETHODIMP QueryInterface (REFIID riid, LPVOID FAR* ppv);
STDMETHODIMP_(ULONG) AddRef ();
STDMETHODIMP_(ULONG) Release ();

   // *** IDropSource methods ***
STDMETHODIMP QueryContinueDrag (BOOL fEscapePressed, DWORD grfKeyState);
STDMETHODIMP GiveFeedback (DWORD dwEffect);

private:

};

#endif


IDT.CPP

//**********************************************************************
// File name: IDT.CPP
//
//      Implementation file for CDropTarget
//
// Functions:
//
//      See IDT.H for class definition
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "iocs.h"
#include "ias.h"
#include "app.h"
#include "site.h"
#include "doc.h"
#include "idt.h"

extern CLIPFORMAT g_cfObjectDescriptor;

//**********************************************************************
//
// CDropTarget::QueryDrop
//
// Purpose:
//
//      Check if the desired drop operation (identified by the given key
//      state) is possible at the current mouse position (pointl).
//
// Parameters:
//
//      DWORD grfKeyState       - current key state
//      POINTL pointl           - position of mouse
//      BOOL fDragScroll        - TRUE if drag scrolling cursor should
//                                be shown.
//      LPDWORD pdwEffect       - (OUT) drag effect that should occur
//
// Return Value:
//
//      BOOL                    - TRUE if drop could take place,
//                                else FALSE
//
// Function Calls:
//      Function                    Location
//
//      OleStdGetDropEffect         OLESTD API
//
// Comments:
//
//********************************************************************

BOOL CDropTarget::QueryDrop (
        DWORD           grfKeyState,
        POINTL          pointl,
        BOOL            fDragScroll,
        LPDWORD         pdwEffect
)
{
        DWORD      dwScrollEffect = 0L;
        DWORD      dwOKEffects = *pdwEffect;

        /* check if the cursor is in the active scroll area, if so need the
        **    special scroll cursor.
        */
        if (fDragScroll)
                dwScrollEffect = DROPEFFECT_SCROLL;

        /* if we have already determined that the source does NOT have any
        **    acceptable data for us, the return NO-DROP
        */
        if (! m_fCanDropCopy && ! m_fCanDropLink)
                goto dropeffect_none;

        /* OLE2NOTE: determine what type of drop should be performed given
        **    the current modifier key state. we rely on the standard
        **    interpretation of the modifier keys:
        **          no modifier -- DROPEFFECT_MOVE or whatever is allowed by src
        **          SHIFT       -- DROPEFFECT_MOVE
        **          CTRL        -- DROPEFFECT_COPY
        **          CTRL-SHIFT  -- DROPEFFECT_LINK
        */

        *pdwEffect = OleStdGetDropEffect(grfKeyState);
        if (*pdwEffect == 0) {
                // No modifier keys given. Try in order MOVE, COPY, LINK.
                if ((DROPEFFECT_MOVE & dwOKEffects) && m_fCanDropCopy)
                        *pdwEffect = DROPEFFECT_MOVE;
                else if ((DROPEFFECT_COPY & dwOKEffects) && m_fCanDropCopy)
                        *pdwEffect = DROPEFFECT_COPY;
                else if ((DROPEFFECT_LINK & dwOKEffects) && m_fCanDropLink)
                        *pdwEffect = DROPEFFECT_LINK;
                else
                        goto dropeffect_none;
        } else {
                /* OLE2NOTE: we should check if the drag source application allows
                **    the desired drop effect.
                */
                if (!(*pdwEffect & dwOKEffects))
                        goto dropeffect_none;

                if ((*pdwEffect == DROPEFFECT_COPY || *pdwEffect == DROPEFFECT_MOVE)
                                && ! m_fCanDropCopy)
                        goto dropeffect_none;

                if (*pdwEffect == DROPEFFECT_LINK && ! m_fCanDropLink)
                        goto dropeffect_none;
        }

        *pdwEffect |= dwScrollEffect;
        return TRUE;

dropeffect_none:

        *pdwEffect = DROPEFFECT_NONE;
        return FALSE;
}

//**********************************************************************
//
// CDropTarget::QueryDrop
//
// Purpose:
//
//     Check to see if Drag scroll operation should be initiated.
//
// Parameters:
//
//      POINTL pointl           - position of mouse
//
// Return Value:
//
//      BOOL                    - TRUE if scroll cursor should be given
//                                else FALSE
//
// Function Calls:
//      Function                    Location
//
//      ScreenToClient              WINDOWS API
//      GetClientRect               WINDOWS API
//
// Comments:
//     A Drag scroll operation should be initiated when the mouse has
//     remained in the active scroll area (11 pixels frame around border
//     of window) for a specified amount of time (50ms).
//
//********************************************************************

BOOL CDropTarget::DoDragScroll (POINTL pointl)
{
        DWORD dwScrollDir = SCROLLDIR_NULL;
        DWORD dwTime = GetCurrentTime();
        int nScrollInset = m_pDoc->m_lpApp->m_nScrollInset;
        int nScrollDelay = m_pDoc->m_lpApp->m_nScrollDelay;
        int nScrollInterval = m_pDoc->m_lpApp->m_nScrollInterval;
        POINT point;
        RECT rect;

        point.x = (int)pointl.x;
        point.y = (int)pointl.y;

        ScreenToClient( m_pDoc->m_hDocWnd, &point);
        GetClientRect ( m_pDoc->m_hDocWnd, (LPRECT) &rect );

        if (rect.top <= point.y && point.y<=(rect.top+nScrollInset))
                dwScrollDir = SCROLLDIR_UP;
        else if ((rect.bottom-nScrollInset) <= point.y && point.y <= rect.bottom)
                dwScrollDir = SCROLLDIR_DOWN;
        else if (rect.left <= point.x && point.x <= (rect.left+nScrollInset))
                dwScrollDir = SCROLLDIR_LEFT;
        else if ((rect.right-nScrollInset) <= point.x && point.x <= rect.right)
                dwScrollDir = SCROLLDIR_RIGHT;

        if (m_dwTimeEnterScrollArea) {

                /* cursor was already in Scroll Area */

                if (! dwScrollDir) {
                        /* cusor moved OUT of scroll area.
                        **      clear "EnterScrollArea" time.
                        */
                        m_dwTimeEnterScrollArea = 0L;
                        m_dwNextScrollTime = 0L;
                        m_dwLastScrollDir = SCROLLDIR_NULL;

                } else if (dwScrollDir != m_dwLastScrollDir) {
                        /* cusor moved into a different direction scroll area.
                        **      reset "EnterScrollArea" time to start a new 50ms delay.
                        */
                        m_dwTimeEnterScrollArea = dwTime;
                        m_dwNextScrollTime = dwTime + (DWORD)nScrollDelay;
                        m_dwLastScrollDir = dwScrollDir;

                } else if (dwTime && dwTime >= m_dwNextScrollTime) {
                        m_pDoc->Scroll ( dwScrollDir );    // Scroll document now
                        m_dwNextScrollTime = dwTime + (DWORD)nScrollInterval;
                }
        } else {
                if (dwScrollDir) {
                        /* cusor moved INTO a scroll area.
                        **      reset "EnterScrollArea" time to start a new 50ms delay.
                        */
                        m_dwTimeEnterScrollArea = dwTime;
                        m_dwNextScrollTime = dwTime + (DWORD)nScrollDelay;
                        m_dwLastScrollDir = dwScrollDir;
                }
        }

        return (dwScrollDir ? TRUE : FALSE);
}

// Support functions/macros
#define SetTopLeft(rc, pt)      \
        ((rc)->top = (pt)->y,(rc)->left = (pt)->x)
#define SetBottomRight(rc, pt)      \
        ((rc)->bottom = (pt)->y,(rc)->right = (pt)->x)
#define OffsetPoint(pt, dx, dy)     \
        ((pt)->x += dx, (pt)->y += dy)

/* HighlightRect
** -------------
**    Invert rectangle on screen. used for drop target feedback.
*/

static int HighlightRect(HWND hwnd, HDC hdc, LPRECT rc)
{
        POINT pt1, pt2;
        int old = SetROP2(hdc, R2_NOT);
        HPEN hpen;
        HGDIOBJ hold;

        pt1.x = rc->left;
        pt1.y = rc->top;
        pt2.x = rc->right;
        pt2.y = rc->bottom;

        ScreenToClient(hwnd, &pt1);
        ScreenToClient(hwnd, &pt2);

        hold = SelectObject(hdc, GetStockObject(HOLLOW_BRUSH));
        hpen = (HPEN) SelectObject(hdc, CreatePen(PS_SOLID, 2,
                                                  GetSysColor(COLOR_ACTIVEBORDER)));

        Rectangle(hdc, pt1.x, pt1.y, pt2.x, pt2.y);

        SetROP2(hdc, old);

        hold = SelectObject(hdc, hold);
        hpen = (HPEN) SelectObject(hdc, hpen);

        DeleteObject(hpen);

  return 0;
}

//**********************************************************************
//
// CDropTarget::InitDragFeedback
//
// Purpose:
//
//      Initialize data used to draw drop target feedback.
//      As feedback we draw a rectangle the size of the object.
//
// Parameters:
//
//      LPDATAOBJECT pDataObj   - IDataObject from Drop source
//      POINTL pointl           - position of mouse
//
// Return Value:
//
//      none.
//
// Function Calls:
//      Function                    Location
//
//      IDataObject::GetData        Object
//      XformSizeInHimetricToPixels OLESTD Library
//      GlobalLock                  WINDOWS API
//      GlobalUnlock                WINDOWS API
//      ReleaseStgMedium            OLE2 API
//      OffsetPoint                 IDT.CPP
//      SetTopLeft                  IDT.CPP
//      SetBottomRight              IDT.CPP
//
// Comments:
//      In order to known the size of the object before the object
//      is actually dropped, we render CF_OBJECTDESCRIPTOR format.
//      this data format tells us both the size of the object as
//      well as which aspect is the object is displayed as in the
//      source. if the object is currently displayed as DVASPECT_ICON
//      then we want to create the object also as DVASPECT_ICON.
//
//********************************************************************

void CDropTarget::InitDragFeedback(LPDATAOBJECT pDataObj, POINTL pointl)
{
        FORMATETC fmtetc;
        STGMEDIUM stgmed;
        POINT pt;
        int height, width;
        HRESULT hrErr;

        height = width = 100; // some default values
        pt.x = (int)pointl.x;
        pt.y = (int)pointl.y;

        // do a GetData for CF_OBJECTDESCRIPTOR format to get the size of the
        // object as displayed in the source. using this size, initialize the
        // size for the drag feedback rectangle.
        fmtetc.cfFormat = g_cfObjectDescriptor;
        fmtetc.ptd = NULL;
        fmtetc.lindex = -1;
        fmtetc.dwAspect = DVASPECT_CONTENT;
        fmtetc.tymed = TYMED_HGLOBAL;

        hrErr = pDataObj->GetData(&fmtetc, &stgmed);
        if (hrErr == NOERROR) {
                LPOBJECTDESCRIPTOR pOD=(LPOBJECTDESCRIPTOR)GlobalLock(stgmed.hGlobal);
                if (pOD != NULL) {
                        XformSizeInHimetricToPixels(NULL, &pOD->sizel, &pOD->sizel);

                        width = (int)pOD->sizel.cx;
                        height = (int)pOD->sizel.cy;
                        m_dwSrcAspect = pOD->dwDrawAspect;
                }

                GlobalUnlock(stgmed.hGlobal);
                ReleaseStgMedium(&stgmed);
        }

        m_ptLast = pt;
        m_fDragFeedbackDrawn = FALSE;

        OffsetPoint(&pt, -(width/2), -(height/2));
        SetTopLeft(&m_rcDragRect, &pt);

        OffsetPoint(&pt, width, height);
        SetBottomRight(&m_rcDragRect, &pt);
}

//**********************************************************************
//
// CDropTarget::UndrawDragFeedback
//
// Purpose:
//
//      Erase any drop target feedback.
//      As feedback we draw a rectangle the size of the object.
//
// Parameters:
//
//      none.
//
// Return Value:
//
//      none.
//
// Function Calls:
//      Function                    Location
//
//      GetDC                       WINDOWS API
//      ReleaseDC                   WINDOWS API
//      GlobalUnlock                WINDOWS API
//      HighlightRect               IDT.CPP
//
// Comments:
//      In order to known the size of the object before the object
//      is actually dropped, we render CF_OBJECTDESCRIPTOR format.
//      this data format tells us both the size of the object as
//      well as which aspect is the object is displayed as in the
//      source. if the object is currently displayed as DVASPECT_ICON
//      then we want to create the object also as DVASPECT_ICON.
//
//********************************************************************

void CDropTarget::UndrawDragFeedback( void )
{
        if (m_fDragFeedbackDrawn) {
                m_fDragFeedbackDrawn = FALSE;
                HDC hDC = GetDC(m_pDoc->m_hDocWnd);
                HighlightRect(m_pDoc->m_hDocWnd, hDC, &m_rcDragRect);
                ReleaseDC(m_pDoc->m_hDocWnd, hDC);
        }
}

//**********************************************************************
//
// CDropTarget::DrawDragFeedback
//
// Purpose:
//
//      Compute new position of drop target feedback rectangle and
//      erase old rectangle and draw new rectangle.
//      As feedback we draw a rectangle the size of the object.
//
// Parameters:
//
//      POINTL pointl           - position of mouse
//
// Return Value:
//
//      none.
//
// Function Calls:
//      Function                    Location
//
//      OffsetPoint                 IDT.CPP
//      HighlightRect               IDT.CPP
//      GetDC                       WINDOWS API
//      ReleaseDC                   WINDOWS API
//
// Comments:
//
//********************************************************************

void CDropTarget::DrawDragFeedback( POINTL pointl )
{
        POINT ptDiff;

        ptDiff.x = (int)pointl.x - m_ptLast.x;
        ptDiff.y = (int)pointl.y - m_ptLast.y;

        if (m_fDragFeedbackDrawn && (ptDiff.x == 0 && ptDiff.y == 0))
                return;     // mouse did not move; leave rectangle as drawn

        HDC hDC = GetDC(m_pDoc->m_hDocWnd);
        if (m_fDragFeedbackDrawn) {
                m_fDragFeedbackDrawn = FALSE;
                HighlightRect(m_pDoc->m_hDocWnd, hDC, &m_rcDragRect);
        }

        OffsetRect(&m_rcDragRect, ptDiff.x, ptDiff.y);
        HighlightRect(m_pDoc->m_hDocWnd, hDC, &m_rcDragRect);
        m_fDragFeedbackDrawn = TRUE;
        m_ptLast.x = (int)pointl.x;
        m_ptLast.y = (int)pointl.y;
        ReleaseDC(m_pDoc->m_hDocWnd, hDC);
}

//**********************************************************************
//
// CDropTarget::QueryInterface
//
// Purpose:
//
//      Return a pointer to a requested interface
//
// Parameters:
//
//      REFIID riid         -   ID of interface to be returned
//      LPVOID FAR* ppvObj  -   Location to return the interface
//
// Return Value:
//
//      S_OK                -   Interface supported
//      E_NOINTERFACE       -   Interface NOT supported
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpleDoc::QueryInterface  DOC.CPP
//
// Comments:
//
//********************************************************************

STDMETHODIMP CDropTarget::QueryInterface(REFIID riid, LPVOID FAR* ppvObj)
{
        OutputDebugString("In IDT::QueryInterface\r\n");

        // delegate to the document
        return m_pDoc->QueryInterface(riid, ppvObj);
}

//**********************************************************************
//
// CDropTarget::AddRef
//
// Purpose:
//
//      Increments the reference count on this interface
//
// Parameters:
//
//      None
//
// Return Value:
//
//      The current reference count on this interface.
//
// Function Calls:
//      Function                    Location
//
//      CSimpleDoc::AddReff         DOC.CPP
//      OutputDebugString           Windows API
//
// Comments:
//
//      This function adds one to the ref count of the interface,
//      and calls then calls CSimpleObj to increment its ref.
//      count.
//
//********************************************************************

STDMETHODIMP_(ULONG) CDropTarget::AddRef()
{
        OutputDebugString("In IDT::AddRef\r\n");

        // increment the interface reference count (for debugging only)
        ++m_nCount;

        // delegate to the document Object
        return m_pDoc->AddRef();
}

//**********************************************************************
//
// CDropTarget::Release
//
// Purpose:
//
//      Decrements the reference count on this interface
//
// Parameters:
//
//      None
//
// Return Value:
//
//      The current reference count on this interface.
//
// Function Calls:
//      Function                    Location
//
//      CSimpleDoc::Release         DOC.CPP
//      OutputDebugString           Windows API
//
// Comments:
//
//      This function subtracts one from the ref count of the interface,
//      and calls then calls CSimpleDoc to decrement its ref.
//      count.
//
//********************************************************************

STDMETHODIMP_(ULONG) CDropTarget::Release()
{
        OutputDebugString("In IDT::Release\r\n");

        // decrement the interface reference count (for debugging only)
        --m_nCount;

        // delegate to the document object
        return m_pDoc->Release();
}

//**********************************************************************
//
// CDropTarget::DragEnter
//
// Purpose:
//
//      Called when the mouse first enters our DropTarget window
//
// Parameters:
//
//      LPDATAOBJECT pDataObj   - IDataObject from Drop source
//      DWORD grfKeyState       - current key state
//      POINTL pointl           - position of mouse
//      LPDWORD pdwEffect       - (IN-OUT) drag effect that should occur
//                                ON INPUT, this is dwOKEffects that source
//                                passed to DoDragDrop API.
//                                ON OUTPUT, this is the effect that we
//                                want to take effect (used to determine
//                                cursor feedback).
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      OleQueryCreateFromData      OLE2 API
//      DoDragScroll                IDT.CPP
//      QueryDrop                   IDT.CPP
//      InitDragFeedback            IDT.CPP
//      DrawDragFeedback            IDT.CPP
//      UndrawDragFeedback          IDT.CPP
//
// Comments:
//      Callee should honor the dwEffects as passed in to determine
//      if the caller allows DROPEFFECT_MOVE.
//
//********************************************************************

STDMETHODIMP CDropTarget::DragEnter (LPDATAOBJECT pDataObj, DWORD grfKeyState, POINTL pointl, LPDWORD pdwEffect)
{
        OutputDebugString("In IDT::DragEnter\r\n");

        /* Determine if the drag source data object offers a data format
        **  that we understand. we accept only creating embedded objects.
        */
        m_fCanDropCopy = ((OleQueryCreateFromData(pDataObj) == NOERROR) ?
                        TRUE : FALSE);
        m_fCanDropLink = FALSE; // linking NOT supported in this simple sample

        if (m_fCanDropCopy || m_fCanDropLink)
                InitDragFeedback(pDataObj, pointl);

        BOOL fDragScroll = DoDragScroll ( pointl );

        if (QueryDrop(grfKeyState,pointl,fDragScroll,pdwEffect))
                DrawDragFeedback( pointl );

        return NOERROR;
}

//**********************************************************************
//
// CDropTarget::DragOver
//
// Purpose:
//
//      Called when the mouse moves, key state changes, or a time
//      interval passes while the mouse is still within our DropTarget
//      window.
//
// Parameters:
//
//      DWORD grfKeyState       - current key state
//      POINTL pointl           - position of mouse
//      LPDWORD pdwEffect       - (IN-OUT) drag effect that should occur
//                                ON INPUT, this is dwOKEffects that source
//                                passed to DoDragDrop API.
//                                ON OUTPUT, this is the effect that we
//                                want to take effect (used to determine
//                                cursor feedback).
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      DoDragScroll                IDT.CPP
//      QueryDrop                   IDT.CPP
//      DrawDragFeedback            IDT.CPP
//      UndrawDragFeedback          IDT.CPP
//
// Comments:
//      Callee should honor the dwEffects as passed in to determine
//      if the caller allows DROPEFFECT_MOVE. OLE pulses the DragOver
//      calls in order that the DropTarget can implement drag scrolling
//
//********************************************************************

STDMETHODIMP CDropTarget::DragOver  (DWORD grfKeyState, POINTL pointl, LPDWORD pdwEffect)
{
        OutputDebugString("In IDT::DragOver\r\n");

        BOOL fDragScroll = DoDragScroll ( pointl );

        if (QueryDrop(grfKeyState,pointl,fDragScroll,pdwEffect))
                DrawDragFeedback( pointl );
        else
                UndrawDragFeedback();

        return NOERROR;
}

//**********************************************************************
//
// CDropTarget::DragLeave
//
// Purpose:
//
//      Called when the mouse leaves our DropTarget window
//
// Parameters:
//
//      none.
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      UndrawDragFeedback          IDT.CPP
//                   OLE2 API
//
// Comments:
//
//********************************************************************

STDMETHODIMP CDropTarget::DragLeave ()
{
        OutputDebugString("In IDT::DragLeave\r\n");

        UndrawDragFeedback();

        return S_OK;
}

//**********************************************************************
//
// CDropTarget::Drop
//
// Purpose:
//
//      Called when a Drop operation should take place.
//
// Parameters:
//
//      LPDATAOBJECT pDataObj   - IDataObject from Drop source
//      DWORD grfKeyState       - current key state
//      POINTL pointl           - position of mouse
//      LPDWORD pdwEffect       - (IN-OUT) drag effect that should occur
//                                ON INPUT, this is dwOKEffects that source
//                                passed to DoDragDrop API.
//                                ON OUTPUT, this is the effect that we
//                                want to take effect (used to determine
//                                cursor feedback).
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpleSite::Create         SITE.CPP
//      CSimpleSite::InitObject     SITE.CPP
//      OleCreateFromData           OLE2 API
//      DoDragScroll                IDT.CPP
//      QueryDrop                   IDT.CPP
//      InitDragFeedback            IDT.CPP
//      DrawDragFeedback            IDT.CPP
//      UndrawDragFeedback          IDT.CPP
//                          OLE2 API
//                   OLE2 API
//
// Comments:
//      Callee should honor the dwEffects as passed in to determine
//      if the caller allows DROPEFFECT_MOVE.
//
//********************************************************************

STDMETHODIMP CDropTarget::Drop (LPDATAOBJECT pDataObj, DWORD grfKeyState, POINTL pointl, LPDWORD pdwEffect)
{
        FORMATETC fmtetc;
        SCODE sc = S_OK;

        OutputDebugString("In IDT::Drop\r\n");

        UndrawDragFeedback();

        if (pDataObj && QueryDrop(grfKeyState,pointl,FALSE,pdwEffect))
                {
                m_pDoc->m_lpSite = CSimpleSite::Create(m_pDoc);
                // keep same aspect as drop source
                m_pDoc->m_lpSite->m_dwDrawAspect = m_dwSrcAspect;

                // in order to specify a particular drawing Aspect we must
                // pass a FORMATETC* to OleCreateFromData
                fmtetc.cfFormat = NULL;             // use whatever for drawing
                fmtetc.ptd = NULL;
                fmtetc.lindex = -1;
                fmtetc.dwAspect = m_dwSrcAspect;    // desired drawing aspect
                fmtetc.tymed = TYMED_NULL;

                HRESULT hrErr = OleCreateFromData (
                                                        pDataObj,
                                                        IID_IOleObject,
                                                        OLERENDER_DRAW,
                                                        &fmtetc,
                                                        &m_pDoc->m_lpSite->m_OleClientSite,
                                                        m_pDoc->m_lpSite->m_lpObjStorage,
                                                        (LPVOID FAR *)&m_pDoc->m_lpSite->m_lpOleObject);

                if (hrErr == NOERROR)
                        {
                        m_pDoc->m_lpSite->InitObject(FALSE /* fCreateNew */);
                        m_pDoc->DisableInsertObject();
                        }
                else
                        sc = hrErr;
                }

        return sc;
}


IDT.H

//**********************************************************************
// File name: idt.h
//
//      Definition of CDropTarget
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************
#if !defined( _IDT_H_ )
#define _IDT_H_

#include <assert.h>

class CSimpleDoc;

/* Flags to control direction for drag scrolling */
typedef enum tagSCROLLDIR {
SCROLLDIR_NULL          = 0,
SCROLLDIR_UP            = 1,
SCROLLDIR_DOWN          = 2,
SCROLLDIR_RIGHT         = 3,
SCROLLDIR_LEFT          = 4
} SCROLLDIR;

interface CDropTarget : public IDropTarget
{
int   m_nCount;                 // reference count
CSimpleDoc FAR * m_pDoc;
BOOL  m_fCanDropCopy;
BOOL  m_fCanDropLink;
DWORD m_dwSrcAspect;
RECT  m_rcDragRect;
POINT m_ptLast;
BOOL  m_fDragFeedbackDrawn;
DWORD m_dwTimeEnterScrollArea;  // time of entering scroll border region
DWORD m_dwLastScrollDir;        // current dir for drag scroll
DWORD m_dwNextScrollTime;       // time for next scroll

CDropTarget(CSimpleDoc FAR * pDoc) {
OutputDebugString("In IDT's constructor\r\n");
m_pDoc = pDoc;
m_nCount = 0;
m_fCanDropCopy = FALSE;
m_fCanDropLink = FALSE;
m_fDragFeedbackDrawn = FALSE;
m_dwTimeEnterScrollArea = 0L;
m_dwNextScrollTime = 0L;
m_dwLastScrollDir = SCROLLDIR_NULL;
};

~CDropTarget() {
OutputDebugString("In IDT's destructor\r\n");
assert(m_nCount == 0);
} ;

STDMETHODIMP QueryInterface (REFIID riid, LPVOID FAR* ppv);
STDMETHODIMP_(ULONG) AddRef ();
STDMETHODIMP_(ULONG) Release ();

   // *** IDropTarget methods ***
STDMETHODIMP DragEnter (LPDATAOBJECT pDataObj, DWORD grfKeyState,
POINTL pt, LPDWORD pdwEffect);
STDMETHODIMP DragOver  (DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect);
STDMETHODIMP DragLeave ();
STDMETHODIMP Drop (LPDATAOBJECT pDataObj, DWORD grfKeyState, POINTL pt,
LPDWORD pdwEffect);

private:
// Drag/Drop support methods
BOOL QueryDrop (DWORD grfKeyState, POINTL pointl, BOOL fDragScroll,
LPDWORD lpdwEffect);
BOOL DoDragScroll( POINTL pointl );
void InitDragFeedback(LPDATAOBJECT pDataObj, POINTL pointl);
void DrawDragFeedback( POINTL pointl );
void UndrawDragFeedback( void );
};

#endif  // _IDT_H_


IOCS.CPP

//**********************************************************************
// File name: IOCS.CPP
//
//      Implementation file for COleClientSite
//
// Functions:
//
//      See IOCS.H for class definition
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "iocs.h"
#include "ias.h"
#include "app.h"
#include "site.h"
#include "doc.h"

//**********************************************************************
//
// COleClientSite::QueryInterface
//
// Purpose:
//
//      Used for interface negotiation at this interface
//
// Parameters:
//
//      REFIID riid         -   A reference to the interface that is
//                              being queried.
//
//      LPVOID FAR* ppvObj  -   An out parameter to return a pointer to
//                              the interface.
//
// Return Value:
//
//      S_OK                -   The interface is supported.
//      E_NOINTERFACE       -   The interface is not supported
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpleSite::QueryInterface SITE.CPP
//
// Comments:
//
//********************************************************************

STDMETHODIMP COleClientSite::QueryInterface(REFIID riid, LPVOID FAR* ppvObj)
{
OutputDebugString("In IOCS::QueryInterface\r\n");

// delegate to the container Site
return m_pSite->QueryInterface(riid, ppvObj);
}

//**********************************************************************
//
// CSimpleApp::AddRef
//
// Purpose:
//
//      Adds to the reference count at the interface level.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      ULONG   -   The new reference count of the interface
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//********************************************************************

STDMETHODIMP_(ULONG) COleClientSite::AddRef()
{
OutputDebugString("In IOCS::AddRef\r\n");

// increment the interface reference count (for debugging only)
++m_nCount;

// delegate to the container Site
return m_pSite->AddRef();
}

//**********************************************************************
//
// CSimpleApp::Release
//
// Purpose:
//
//      Decrements the reference count at this level
//
// Parameters:
//
//      None
//
// Return Value:
//
//      ULONG   -   The new reference count of the interface.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//********************************************************************

STDMETHODIMP_(ULONG) COleClientSite::Release()
{
OutputDebugString("In IOCS::Release\r\n");

// decrement the interface reference count (for debugging only)
--m_nCount;

// delegate to the container Site
return m_pSite->Release();
}

//**********************************************************************
//
// COleClientSite::SaveObject
//
// Purpose:
//
//      Called by the object when it wants to be saved to persistant
//      storage
//
// Parameters:
//
//      None
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                            Location
//
//      OutputDebugString                   Windows API
//      IOleObject::QueryInterface          Object
//      IPersistStorage::SaveCompleted      Object
//      IPersistStorage::Release            Object
//      OleSave                             OLE API
//                           OLE API
//
// Comments:
//
//********************************************************************

STDMETHODIMP COleClientSite::SaveObject()
{
LPPERSISTSTORAGE lpPS;
SCODE sc = E_FAIL;

OutputDebugString("In IOCS::SaveObject\r\n");

// get a pointer to IPersistStorage
HRESULT hErr = m_pSite->m_lpOleObject->QueryInterface(IID_IPersistStorage, (LPVOID FAR *)&lpPS);

// save the object
if (hErr == NOERROR)
{
sc =  OleSave(lpPS, m_pSite->m_lpObjStorage, TRUE) ;
lpPS->SaveCompleted(NULL);
lpPS->Release();
}

return sc;
}

//**********************************************************************
//
// COleClientSite::GetMoniker
//
// Purpose:
//
//      Not Implemented
//
// Parameters:
//
//      Not Implemented
//
// Return Value:
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      This function is not implemented because we don't support
//      linking.
//
//********************************************************************

STDMETHODIMP COleClientSite::GetMoniker(DWORD dwAssign, DWORD dwWhichMoniker, LPMONIKER FAR* ppmk)
{
OutputDebugString("In IOCS::GetMoniker\r\n");

// need to null the out pointer
*ppmk = NULL;

return E_NOTIMPL;
}

//**********************************************************************
//
// COleClientSite::GetContainer
//
// Purpose:
//
//      Not Implemented
//
// Parameters:
//
//      Not Implemented
//
// Return Value:
//
//      Not Implemented
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Not Implemented
//
//********************************************************************

STDMETHODIMP COleClientSite::GetContainer(LPOLECONTAINER FAR* ppContainer)
{
OutputDebugString("In IOCS::GetContainer\r\n");

// NULL the out pointer
*ppContainer = NULL;

return E_NOTIMPL;
}

//**********************************************************************
//
// COleClientSite::ShowObject
//
// Purpose:
//
//      Not Implemented
//
// Parameters:
//
//      Not Implemented
//
// Return Value:
//
//      Not Implemented
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      This function is not implemented because we don't support
//      linking.
//
//********************************************************************

STDMETHODIMP COleClientSite::ShowObject()
{
OutputDebugString("In IOCS::ShowObject\r\n");
return NOERROR;
}

//**********************************************************************
//
// COleClientSite::OnShowWindow
//
// Purpose:
//
//      Object calls this method when it is opening/closing non-InPlace
//      Window
//
// Parameters:
//
//      BOOL fShow  - TRUE if Window is opening, FALSE if closing
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      InvalidateRect              Windows API
//                   OLE API
//
// Comments:
//
//********************************************************************

STDMETHODIMP COleClientSite::OnShowWindow(BOOL fShow)
{
OutputDebugString("In IOCS::OnShowWindow\r\n");
m_pSite->m_fObjectOpen = fShow;
InvalidateRect(m_pSite->m_lpDoc->m_hDocWnd, NULL, TRUE);

// if object window is closing, then bring container window to top
if (! fShow) {
BringWindowToTop(m_pSite->m_lpDoc->m_hDocWnd);
SetFocus(m_pSite->m_lpDoc->m_hDocWnd);
}
return S_OK;
}

//**********************************************************************
//
// COleClientSite::RequestNewObjectLayout
//
// Purpose:
//
//      Not Implemented
//
// Parameters:
//
//      Not Implemented
//
// Return Value:
//
//      Not Implemented
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Not Implemented
//
//********************************************************************

STDMETHODIMP COleClientSite::RequestNewObjectLayout()
{
OutputDebugString("In IOCS::RequestNewObjectLayout\r\n");
return E_NOTIMPL;
}


PRE.H

//**********************************************************************
// File name: pre.h
//
//      Used for precompiled headers
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#if !defined( _PRE_H_)
#define _PRE_H_

#include <windows.h>
#include <ole2.h>
#include <olestd.h>
#include <assert.h>
#include <string.h>
#include "simpdnd.h"
#include "resource.h"
#include <ole2ver.h>

#endif


APP.CPP

//**********************************************************************
// File name: app.cpp
//
//    Implementation file for the CSimpleApp Class
//
// Functions:
//
//    See app.h for a list of member functions.
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "iocs.h"
#include "ias.h"
#include "app.h"
#include "site.h"
#include "doc.h"

//**********************************************************************
//
// CSimpleApp::CSimpleApp()
//
// Purpose:
//
//      Constructor for CSimpleApp
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      SetRectEmpty                Windows API
//
// Comments:
//
//********************************************************************
CSimpleApp::CSimpleApp()
{
        OutputDebugString("In CSimpleApp's Constructor \r\n");

        // clear members
        m_hAppWnd = NULL;
        m_hInst = NULL;
        m_lpDoc = NULL;

        // clear flags
        m_fInitialized = FALSE;
        m_fOleStdInit = FALSE;
}

//**********************************************************************
//
// CSimpleApp::~CSimpleApp()
//
// Purpose:
//
//      Destructor for CSimpleApp Class.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      OleUninitialize             OLE API
//
// Comments:
//
//********************************************************************

CSimpleApp::~CSimpleApp()
{
        OutputDebugString("In CSimpleApp's Destructor\r\n");

        // need to uninit the library...
        if (m_fOleStdInit)
                OleStdUninitialize();
        if (m_fInitialized)
                OleUninitialize();
}

//**********************************************************************
//
// CSimpleApp::DestroyDocs()
//
// Purpose:
//
//      Destroys all of the open documents in the application (Only one
//      since this is an SDI app, but could easily be modified to
//      support MDI).
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
// Comments:
//
//********************************************************************

void CSimpleApp::DestroyDocs()
{
        CStabilize stabilize(this);
        m_lpDoc->Close();   // we have only 1 document
}

//**********************************************************************
//
// CSimpleApp::QueryInterface
//
// Purpose:
//
//      Used for interface negotiation at the Frame level.
//
// Parameters:
//
//      REFIID riid         -   A reference to the interface that is
//                              being queried.
//
//      LPVOID FAR* ppvObj  -   An out parameter to return a pointer to
//                              the interface.
//
// Return Value:
//
//      S_OK    -   The interface is supported.
//      S_FALSE -   The interface is not supported
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//                   OLE API
//
// Comments:
//
//********************************************************************

STDMETHODIMP CSimpleApp::QueryInterface(REFIID riid, LPVOID FAR* ppvObj)
{
        OutputDebugString("In CSimpleApp::QueryInterface\r\n");

        *ppvObj = NULL;     // must set out pointer parameters to NULL

        // Not a supported interface
        return E_NOINTERFACE;
}

//**********************************************************************
//
// CSimpleApp::AddRef
//
// Purpose:
//
//      Adds to the reference count at the Application level.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      ULONG   -   The new reference count of the application.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Due to the reference counting model that is used in this
//      implementation, this reference count is the sum of the
//      reference counts on all interfaces of all objects open
//      in the application.
//
//********************************************************************

STDMETHODIMP_(ULONG) CSimpleApp::AddRef()
{
        OutputDebugString("In CSimpleApp::AddRef\r\n");
        return SafeAddRef();
}

//**********************************************************************
//
// CSimpleApp::Release
//
// Purpose:
//
//      Decrements the reference count at this level
//
// Parameters:
//
//      None
//
// Return Value:
//
//      ULONG   -   The new reference count of the application.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//********************************************************************

STDMETHODIMP_(ULONG) CSimpleApp::Release()
{
        OutputDebugString("In CSimpleApp::Release\r\n");

        return SafeRelease();
}

//**********************************************************************
//
// CSimpleApp::fInitApplication
//
// Purpose:
//
//      Initializes the application
//
// Parameters:
//
//      HANDLE hInstance    -   Instance handle of the application.
//
// Return Value:
//
//      TRUE    -   Application was successfully initialized.
//      FALSE   -   Application could not be initialized
//
// Function Calls:
//      Function                    Location
//
//      LoadIcon                    Windows API
//      LoadCursor                  Windows API
//      GetStockObject              Windows API
//      RegisterClass               Windows API
//
// Comments:
//
//********************************************************************

BOOL CSimpleApp::fInitApplication(HANDLE hInstance)
{
        WNDCLASS  wc;
        CStabilize stabilize(this);

        // Fill in window class structure with parameters that describe the
        // main window.

        wc.style = NULL;                    // Class style(s).
        wc.lpfnWndProc = MainWndProc;       // Function to retrieve messages for
                                                                                // windows of this class.
        wc.cbClsExtra = 0;                  // No per-class extra data.
        wc.cbWndExtra = 0;                  // No per-window extra data.
        wc.hInstance = (HINSTANCE) hInstance; // Application that owns the class.
        wc.hIcon = LoadIcon((HINSTANCE) hInstance,"SimpDnd");
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
        wc.lpszMenuName =  "SIMPLEMENU";    // Name of menu resource in .RC file.
        wc.lpszClassName = "SimpDndAppWClass";  // Name used in CreateWindow call.

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

        wc.style = CS_DBLCLKS;              // Class style(s). allow DBLCLK's
        wc.lpfnWndProc = DocWndProc;        // Function to retrieve messages for
                                                                                // windows of this class.
        wc.cbClsExtra = 0;                  // No per-class extra data.
        wc.cbWndExtra = 0;                  // No per-window extra data.
        wc.hInstance = (HINSTANCE) hInstance; // Application that owns the class.
        wc.hIcon = NULL;
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
        wc.lpszMenuName =  NULL;
        wc.lpszClassName = "SimpDndDocWClass"; // Name used in CreateWindow call.

        // Register the window class and return success/failure code.

        return (RegisterClass(&wc));
}

//**********************************************************************
//
// CSimpleApp::fInitInstance
//
// Purpose:
//
//      Instance initialization.
//
// Parameters:
//
//      HANDLE hInstance    -   App. Instance Handle.
//
//      int nCmdShow        -   Show parameter from WinMain
//
// Return Value:
//
//      TRUE    -   Initialization Successful
//      FALSE   -   Initialization Failed.
//
//
// Function Calls:
//      Function                    Location
//
//      CreateWindow                Windows API
//      ShowWindow                  Windows API
//      UpdateWindow                Windows API
//      OleBuildVersion             OLE API
//      OleInitialize               OLE API
//
// Comments:
//
//      Note that successful Initalization of the OLE libraries
//      is remembered so the UnInit is only called if needed.
//
//********************************************************************

BOOL CSimpleApp::fInitInstance (HANDLE hInstance, int nCmdShow)
{
        CStabilize stabilize(this);
        DWORD dwVer = OleBuildVersion();
        LPMALLOC lpMalloc = NULL;

        // check to see if we are compatible with this version of the libraries
        if (HIWORD(dwVer) != rmm || LOWORD(dwVer) < rup) {
#ifdef _DEBUG
                OutputDebugString("WARNING: Incompatible OLE library version\r\n");
#else
                return FALSE;
#endif
        }

        if (SUCCEEDED(OleInitialize(NULL)))
        {
            m_fInitialized = TRUE;
        }

        m_fOleStdInit = OleStdInitialize((HINSTANCE) hInstance);
        OleDbgSetDbgLevel(10);

        m_hInst = (HINSTANCE) hInstance;

        // Create the "application" windows
        m_hAppWnd = CreateWindow ("SimpDndAppWClass",
                                                          "Simple OLE 2.0 Drag/Drop Container",
                                                          WS_OVERLAPPEDWINDOW,
                                                          CW_USEDEFAULT,
                                                          CW_USEDEFAULT,
                                                          CW_USEDEFAULT,
                                                          CW_USEDEFAULT,
                                                          NULL,
                                                          NULL,
                                                          (HINSTANCE) hInstance,
                                                          NULL);

        if (!m_hAppWnd)
                return FALSE;

        // delay before dragging should start, in milliseconds
        m_nDragDelay = GetProfileInt(
                        "windows",
                        "DragDelay",
                        DD_DEFDRAGDELAY
        );

        // minimum distance (radius) before drag should start, in pixels
        m_nDragMinDist = GetProfileInt(
                        "windows",
                        "DragMinDist",
                        DD_DEFDRAGMINDIST
        );

        // delay before scrolling, in milliseconds
        m_nScrollDelay = GetProfileInt(
                        "windows",
                        "DragScrollDelay",
                        DD_DEFSCROLLDELAY
        );

        // inset-width of the hot zone, in pixels
        m_nScrollInset = GetProfileInt(
                        "windows",
                        "DragScrollInset",
                        DD_DEFSCROLLINSET
        );

        // scroll interval, in milliseconds
        m_nScrollInterval = GetProfileInt(
                        "windows",
                        "DragScrollInterval",
                        DD_DEFSCROLLINTERVAL
        );

        ShowWindow (m_hAppWnd, nCmdShow);
        UpdateWindow (m_hAppWnd);

        return m_fInitialized && m_fOleStdInit;
}

//**********************************************************************
//
// CSimpleApp::lCommandHandler
//
// Purpose:
//
//      Handles the processing of WM_COMMAND.
//
// Parameters:
//
//      HWND hWnd       -   Handle to the application Window
//
//      UINT message    -   message (always WM_COMMAND)
//
//      WPARAM wParam   -   Same as passed to the WndProc
//
//      LPARAM lParam   -   Same as passed to the WndProc
//
// Return Value:
//
//      NULL
//
// Function Calls:
//      Function                                    Location
//
//      IOleObject::DoVerb                          Object
//      GetClientRect                               Windows API
//      MessageBox                                  Windows API
//      DialogBox                                   Windows API
//      MakeProcInstance                            Windows API
//      FreeProcInstance                            Windows API
//      SendMessage                                 Windows API
//      DefWindowProc                               Windows API
//      CSimpleDoc::InsertObject                    DOC.CPP
//
// Comments:
//
//********************************************************************

long CSimpleApp::lCommandHandler (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
        RECT rect;
        CStabilize stabilize(this);

        // see if the command is a verb selections
        //@@WTK WIN32, UNICODE
        //if (wParam >= IDM_VERB0)
        if (LOWORD(wParam) >= IDM_VERB0)
                {
                // get the rectangle of the object
                m_lpDoc->m_lpSite->GetObjRect(&rect);

                m_lpDoc->m_lpSite->m_lpOleObject->DoVerb(
                                LOWORD(wParam) - IDM_VERB0, NULL,
                                &m_lpDoc->m_lpSite->m_OleClientSite, -1,
                                m_lpDoc->m_hDocWnd, &rect);
                }
        else
                {
                //@@WTK WIN32, UNICODE
                //switch (wParam) {
                switch (LOWORD(wParam)) {
                        // bring up the About box
                        case IDM_ABOUT:
                                {

                                DialogBox(m_hInst,               // current instance
                                        "AboutBox",                  // resource to use
                                        m_hAppWnd,                   // parent handle
                                        (DLGPROC) About);                // About() instance address

                                break;
                                }

                        // bring up the InsertObject Dialog
                        case IDM_INSERTOBJECT:
                                m_lpDoc->InsertObject();
                                break;

                        // Copy the object to the Clipboard
                        case IDM_COPY:
                                m_lpDoc->CopyObjectToClip();
                                break;

                        // exit the application
                        case IDM_EXIT:
                                SendMessage(hWnd, WM_SYSCOMMAND, SC_CLOSE, 0L);
                                break;

                        case IDM_NEW:
                                m_lpDoc->Close();
                                m_lpDoc = NULL;
                                lCreateDoc(hWnd, 0, 0, 0);
                                break;

                        default:
                                return (DefWindowProc(hWnd, message, wParam, lParam));
                        }   // end of switch
                }  // end of else
        return NULL;
}

//**********************************************************************
//
// CSimpleApp::lSizeHandler
//
// Purpose:
//
//      Handles the WM_SIZE message
//
// Parameters:
//
//      HWND hWnd       -   Handle to the application Window
//
//      UINT message    -   message (always WM_SIZE)
//
//      WPARAM wParam   -   Same as passed to the WndProc
//
//      LPARAM lParam   -   Same as passed to the WndProc
//
// Return Value:
//
//      LONG    -   returned from the "document" resizing
//
// Function Calls:
//      Function                    Location
//
//      GetClientRect               Windows API
//      CSimpleDoc::lResizeDoc      DOC.CPP
//
// Comments:
//
//********************************************************************

long CSimpleApp::lSizeHandler (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
        RECT rect;

        CStabilize stabilize(this);
        GetClientRect(m_hAppWnd, &rect);
        return m_lpDoc->lResizeDoc(&rect);
}

//**********************************************************************
//
// CSimpleApp::lCreateDoc
//
// Purpose:
//
//      Handles the creation of a document.
//
// Parameters:
//
//      HWND hWnd       -   Handle to the application Window
//
//      UINT message    -   message (always WM_CREATE)
//
//      WPARAM wParam   -   Same as passed to the WndProc
//
//      LPARAM lParam   -   Same as passed to the WndProc
//
// Return Value:
//
//      NULL
//
// Function Calls:
//      Function                    Location
//
//      GetClientRect               Windows API
//      CSimpleDoc::CSimpleDoc      DOC.CPP
//
// Comments:
//
//********************************************************************

long CSimpleApp::lCreateDoc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
        RECT rect;

        CStabilize stabilize(this);
        GetClientRect(hWnd, &rect);

        m_lpDoc = CSimpleDoc::Create(this, &rect, hWnd);

        return NULL;
}

//**********************************************************************
//
// CSimpleApp::HandleAccelerators
//
// Purpose:
//
//      To properly handle accelerators in the Message Loop
//
// Parameters:
//
//      LPMSG lpMsg -   A pointer to the message structure.
//
// Return Value:
//
//      TRUE    -   The accelerator was handled
//      FALSE   -   The accelerator was not handled
//
// Function Calls:
//      Function                                        Location
//
// Comments:
//
//********************************************************************

BOOL CSimpleApp::HandleAccelerators(LPMSG lpMsg)
{
        BOOL retval = FALSE;

        // we do not have any accelerators

        return retval;
}

//**********************************************************************
//
// CSimpleApp::PaintApp
//
// Purpose:
//
//      Handles the painting of the doc window.
//
//
// Parameters:
//
//      HDC hDC -   hDC to the Doc Window.
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      CSimpleDoc::PaintDoc        DOC.CPP
//
// Comments:
//
//      This is an app level function in case we want to do palette
//      management.
//
//********************************************************************

void CSimpleApp::PaintApp (HDC hDC)
{
        CStabilize stabilize(this);
        // at this level, we could enumerate through all of the
        // visible objects in the application, so that a palette
        // that best fits all of the objects can be built.

        // This app is designed to take on the same palette
        // functionality that was provided in OLE 1.0, the palette
        // of the last object drawn is realized.  Since we only
        // support one object at a time, it shouldn't be a big
        // deal.

        // if we supported multiple documents, we would enumerate
        // through each of the open documents and call paint.

        if (m_lpDoc)
                m_lpDoc->PaintDoc(hDC);

}


RESOURCE.H

//{{NO_DEPENDENCIES}}
// App Studio generated include file.
// Used by SIMPDND.RC
//
#define IDM_OPEN                        102
#define IDC_DRAGPENDING                 102
#define IDM_SAVE                        103
#define IDM_SAVEAS                      104
#define IDM_PRINT                       105
#define IDM_EXIT                        106
#define IDM_UNDO                        107
#define IDM_CUT                         108
#define IDM_COPY                        109
#define IDM_PASTE                       110
#define ID_EDIT_INSERTOBJECT            111
#define IDM_INSERTOBJECT                111
#define IDM_NEW                         112

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS

#define _APS_NEXT_RESOURCE_VALUE        103
#define _APS_NEXT_COMMAND_VALUE         113
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


SIMPDND.CPP

//**********************************************************************
// File name: Simple.cpp
//
//      Main source file for the Simple OLE 2.0 object container
//
// Functions:
//
//      WinMain         - Program entry point
//      MainWndProc     - Processes messages for the frame window
//      About           - Processes messages for the about dialog
//      DocWndProc      - Processes messages for the doc window
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "iocs.h"
#include "ias.h"
#include "app.h"
#include "site.h"
#include "doc.h"

// This line is needed for the debug utilities in OLESTD
extern "C" {
        OLEDBGDATA_MAIN("SIMPDND")
}

CSimpleApp FAR * lpCSimpleApp;

//**********************************************************************
//
// WinMain
//
// Purpose:
//
//      Program entry point
//
// Parameters:
//
//      HANDLE hInstance        - Instance handle for this instance
//
//      HANDLE hPrevInstance    - Instance handle for the last instance
//
//      LPSTR lpCmdLine         - Pointer to the command line
//
//      int nCmdShow            - Window State
//
// Return Value:
//
//      msg.wParam
//
// Function Calls:
//      Function                        Location
//
//      CSimpleApp::CSimpleApp          APP.CPP
//      CSimpleApp::fInitApplication    APP.CPP
//      CSimpleApp::fInitInstance       APP.CPP
//      CSimpleApp::HandleAccelerators  APP.CPP
//      CSimpleApp::~CSimpleApp         APP.CPP
//      OleUIInitialize                 OLESTD
//      OleUIUninitialize               OLESTD
//      GetMessage                      Windows API
//      TranslateMessage                Windows API
//      DispatchMessage                 Windows API
//
// Comments:
//
//********************************************************************

int PASCAL WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)

{
        MSG msg;

        // needed for LRPC to work properly...
        SetMessageQueue(96);

        lpCSimpleApp = new CSimpleApp;

        // we will add one ref count on our App. later when we want to destroy
        // the App object we will release this  ref count. when the App's ref
        // count goes to 0, it will be deleted.
        lpCSimpleApp->AddRef();

        // app initialization
        if (!hPrevInstance)
                if (!lpCSimpleApp->fInitApplication(hInstance))
                        return (FALSE);

        // instance initialization
        if (!lpCSimpleApp->fInitInstance(hInstance, nCmdShow))
                return (FALSE);

        /* Initialization required for OLE 2 UI library.  This call is
        **    needed ONLY if we are using the static link version of the UI
        **    library. If we are using the DLL version, we should NOT call
        **    this function in our application.
        */
#if 0
        if (!OleUIInitialize(hInstance, hPrevInstance))
                {
                OleDbgOut("Could not initialize OLEUI library\n");
                return FALSE;
                }
#endif
        // message loop
        while (GetMessage(&msg, NULL, NULL, NULL))
                if (!lpCSimpleApp->HandleAccelerators(&msg))
                        {
                        TranslateMessage(&msg);    /* Translates virtual key codes */
                        DispatchMessage(&msg);     /* Dispatches message to window */
                        }

        // De-initialization for UI libraries.  Just like OleUIInitialize, this
        // funciton is needed ONLY if we are using the static link version of the
        // OLE UI library.
#if 0
        OleUIUninitialize();
#endif

        // Release the ref count added on the App above. this will make
        // the App's ref count go to 0, and the App object will be deleted.
        lpCSimpleApp->Release();

        return (msg.wParam);          /* Returns the value from PostQuitMessage */
}

//**********************************************************************
//
// MainWndProc
//
// Purpose:
//
//      Processes messages for the frame window
//
// Parameters:
//
//      HWND hWnd       - Window handle for frame window
//
//      UINT message    - Message value
//
//      WPARAM wParam   - Message info
//
//      LPARAM lParam   - Message info
//
// Return Value:
//
//      long
//
// Function Calls:
//      Function                        Location
//
//      CSimpleApp::lCommandHandler     APP.CPP
//      CSimpleApp::DestroyDocs         APP.CPP
//      CSimpleApp::lCreateDoc          APP.CPP
//      CSimpleApp::lSizeHandler        APP.CPP
//      CSimpleDoc::lAddVerbs           DOC.CPP
//      PostQuitMessage                 Windows API
//      DefWindowProc                   Windows API
//
// Comments:
//
//********************************************************************

//@@WTK WIN32, UNICODE
//long FAR PASCAL _export MainWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
long FAR PASCAL EXPORT MainWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)

{

        switch (message)
                {
                case WM_COMMAND:           // message: command from application menu
                        return lpCSimpleApp->lCommandHandler(hWnd, message, wParam, lParam);
                        break;

                case WM_CREATE:
                        return lpCSimpleApp->lCreateDoc(hWnd, message, wParam, lParam);
                        break;

                case WM_DESTROY:                  // message: window being destroyed
                        lpCSimpleApp->DestroyDocs();  // need to destroy the doc...
                        PostQuitMessage(0);
                        break;

                case WM_INITMENUPOPUP:
                        // is this the edit menu?
                        if ( LOWORD(lParam) == 1)
                                return lpCSimpleApp->m_lpDoc->lAddVerbs();

                        break;

                case WM_SIZE:
                        return lpCSimpleApp->lSizeHandler(hWnd, message, wParam, lParam);

                default:                          // Passes it on if unproccessed
                        return (DefWindowProc(hWnd, message, wParam, lParam));
                }
                return (NULL);
}

//**********************************************************************
//
// About
//
// Purpose:
//
//      Processes dialog box messages
//
// Parameters:
//
//      HWND hWnd       - Window handle for dialog box
//
//      UINT message    - Message value
//
//      WPARAM wParam   - Message info
//
//      LPARAM lParam   - Message info
//
// Return Value:
//
// Function Calls:
//      Function                    Location
//
//      EndDialog                   Windows API
//
// Comments:
//
//********************************************************************

//@@WTK WIN32, UNICODE
//BOOL FAR PASCAL _export About(HWND hDlg,unsigned message,WORD wParam,LONG lParam)
BOOL FAR PASCAL EXPORT About(HWND hDlg,UINT message,WPARAM wParam,LPARAM lParam)

{
        switch (message) {
        case WM_INITDIALOG:                /* message: initialize dialog box */
                return (TRUE);

        case WM_COMMAND:                      /* message: received a command */
                //@@WTK WIN32, UNICODE
                //if (wParam == IDOK                /* "OK" box selected?          */
                if (LOWORD(wParam) == IDOK                /* "OK" box selected?          */
                //@@WTK WIN32, UNICODE
                //|| wParam == IDCANCEL) {      /* System menu close command? */
                || LOWORD(wParam) == IDCANCEL) {      /* System menu close command? */
                EndDialog(hDlg, TRUE);        /* Exits the dialog box        */
                return (TRUE);
                }
                break;
        }
        return (FALSE);                           /* Didn't process a message    */
}

//**********************************************************************
//
// DocWndProc
//
// Purpose:
//
//      Processes dialog box messages
//
// Parameters:
//
//      HWND hWnd       - Window handle for doc window
//
//      UINT message    - Message value
//
//      WPARAM wParam   - Message info
//
//      LPARAM lParam   - Message info
//
// Return Value:
//
// Function Calls:
//      Function                            Location
//
//      CSimpleApp::PaintApp                APP.CPP
//      BeginPaint                          Windows API
//      EndPaint                            Windows API
//      DefWindowProc                       Windows API
//      IOleObject::QueryInterface          Object
//      IOleObject::DoVerb                  Object
//      CSimpleSite::GetObjRect             SITE.CPP
//      CSimpleDoc::QueryDrag               DOC.CPP
//      CSimpleDoc::DoDragDrop              DOC.CPP
//      SetTimer                            Windows API
//      KillTimer                           Windows API
//      SetCapture                          Windows API
//      ReleaseCapture                      Windows API
//
// Comments:
//
//********************************************************************

//@@WTK WIN32, UNICODE
//long FAR PASCAL _export DocWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
long FAR PASCAL EXPORT DocWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
        HDC hDC;
        PAINTSTRUCT ps;

        switch (message) {
                case WM_PAINT:

                        hDC = BeginPaint(hWnd, &ps);

                        if (lpCSimpleApp)
                                lpCSimpleApp->PaintApp (hDC);

                        EndPaint(hWnd, &ps);
                        break;

                case WM_LBUTTONDBLCLK:
                        {
                        POINT pt;

                        pt.x = (int)(short)LOWORD (lParam );
                        pt.y = (int)(short)HIWORD (lParam );

                        if (lpCSimpleApp->m_lpDoc->m_lpSite &&
                                lpCSimpleApp->m_lpDoc->m_lpSite->m_lpOleObject)
                                {
                                RECT rect;

                                lpCSimpleApp->m_lpDoc->m_lpSite->GetObjRect(&rect);

                                if ( PtInRect(&rect, pt) )
                                        {
                                        // Execute object's default verb
                                        lpCSimpleApp->m_lpDoc->m_lpSite->m_lpOleObject->DoVerb(
                                                        OLEIVERB_PRIMARY, (LPMSG)&message,
                                                        &lpCSimpleApp->m_lpDoc->m_lpSite->m_OleClientSite,
                                                        -1, hWnd, &rect);
                                        }
                                }
                        }
                        break;

                case WM_LBUTTONDOWN:
                        {
                        POINT pt;

                        pt.x = (int)(short)LOWORD (lParam );
                        pt.y = (int)(short)HIWORD (lParam );

                        /* OLE2NOTE: check if this is a button down on the region
                        **    that is a handle to start a drag operation. for us,
                        **    this this is any where in the window. we
                        **    do NOT want to start a drag immediately; we want to
                        **    wait until the mouse moves a certain threshold. or a
                        **    certain amount of time has elapsed. if
                        **    LButtonUp comes before the drag is started, then
                        **    the fPendingDrag state is cleared. we must capture
                        **    the mouse to ensure the modal state is handled
                        **    properly.
                        */
                        if (lpCSimpleApp->m_lpDoc->QueryDrag(pt) )
                                {
                                lpCSimpleApp->m_lpDoc->m_fPendingDrag = TRUE;
                                lpCSimpleApp->m_lpDoc->m_ptButDown = pt;
                                SetTimer(hWnd, 1, lpCSimpleApp->m_nDragDelay, NULL);
                                SetCapture(hWnd);
                                }
                        break;
                        }

                case WM_LBUTTONUP:

                        if (lpCSimpleApp->m_lpDoc->m_fPendingDrag)
                                {
                                /* ButtonUP came BEFORE distance/time threshholds were
                                **    exceeded. clear fPendingDrag state.
                                */
                                ReleaseCapture();
                                KillTimer(hWnd, 1);
                                lpCSimpleApp->m_lpDoc->m_fPendingDrag = FALSE;
                                }
                        break;

                case WM_MOUSEMOVE:
                        {
                        if (lpCSimpleApp->m_lpDoc->m_fPendingDrag)
                                {
                                int  x = (int)(short)LOWORD (lParam );
                                int  y = (int)(short)HIWORD (lParam );
                                POINT pt = lpCSimpleApp->m_lpDoc->m_ptButDown;
                                int nDragMinDist = lpCSimpleApp->m_nDragMinDist;

                                if (! ( ((pt.x - nDragMinDist) <= x)
                                                && (x <= (pt.x + nDragMinDist))
                                                && ((pt.y - nDragMinDist) <= y)
                                                && (y <= (pt.y + nDragMinDist)) ) )
                                        {
                                        // mouse moved beyond threshhold to start drag
                                        ReleaseCapture();
                                        KillTimer(hWnd, 1);
                                        lpCSimpleApp->m_lpDoc->m_fPendingDrag = FALSE;

                                        // perform the modal drag/drop operation.
                                        lpCSimpleApp->m_lpDoc->DoDragDrop( );
                                        }
                                }
                        break;
                        }

                case WM_TIMER:
                        {
                        // drag time delay threshhold exceeded -- start drag
                        ReleaseCapture();
                        KillTimer(hWnd, 1);
                        lpCSimpleApp->m_lpDoc->m_fPendingDrag = FALSE;

                        // perform the modal drag/drop operation.
                        lpCSimpleApp->m_lpDoc->DoDragDrop( );
                        break;
                        }

                default:                          /* Passes it on if unproccessed */
                        return (DefWindowProc(hWnd, message, wParam, lParam));
        }
        return (NULL);
}


SIMPDND.H

//**********************************************************************
// File name: simpdnd.h
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************
#define IDM_ABOUT 100
#define IDM_INSERT  101
#define IDM_VERB0 1000

int PASCAL WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow);
BOOL InitApplication(HANDLE hInstance);
BOOL InitInstance(HANDLE hInstance, int nCmdShow);
//@@WTK WIN32, UNICODE
//long FAR PASCAL _export MainWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
long FAR PASCAL EXPORT MainWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
//@@WTK WIN32, UNICODE
//long FAR PASCAL _export DocWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
long FAR PASCAL EXPORT DocWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
//@@WTK WIN32, UNICODE
//BOOL FAR PASCAL _export About(HWND hDlg, unsigned message, WORD wParam, LONG lParam);
BOOL FAR PASCAL EXPORT About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);


SITE.CPP

//**********************************************************************
// File name: SITE.CPP
//
//      Implementation file for CSimpleSite
//
// Functions:
//
//      See SITE.H for class definition
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "iocs.h"
#include "ias.h"
#include "app.h"
#include "site.h"
#include "doc.h"

//**********************************************************************
//
// CSimpleSite::Create
//
// Purpose:
//
//      Creation routine for CSimpleSite
//
// Parameters:
//
//      CSimpleDoc FAR *lpDoc   - Pointer to CSimpleDoc
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      IStorage::CreateStorage     OLE API
//      assert                      C Runtime
//
// Comments:
//
//********************************************************************

CSimpleSite FAR * CSimpleSite::Create(CSimpleDoc FAR *lpDoc)
{
CSimpleSite FAR * lpTemp = new CSimpleSite(lpDoc);

if (!lpTemp)
return NULL;

// create a sub-storage for the object
//@@WTK WIN32, UNICODE
//HRESULT hErr = lpDoc->m_lpStorage->CreateStorage( "Object",
HRESULT hErr = lpDoc->m_lpStorage->CreateStorage( OLESTR("Object"),
STGM_READWRITE | STGM_TRANSACTED | STGM_SHARE_EXCLUSIVE,
0,
0,
&lpTemp->m_lpObjStorage);

assert(hErr == NOERROR);

if (hErr != NOERROR)
{
delete lpTemp;
return NULL;
}

// we will add one ref count on our Site. later when we want to destroy
// the Site object we will release this  ref count. when the Site's ref
// count goes to 0, it will be deleted.
lpTemp->AddRef();

return lpTemp;
}

//**********************************************************************
//
// CSimpleSite::CSimpleSite
//
// Purpose:
//
//      Constructor for CSimpleSite
//
// Parameters:
//
//      CSimpleDoc FAR *lpDoc   - Pointer to CSimpleDoc
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
// Comments:
//
//********************************************************************
#pragma warning(disable : 4355)  // turn off this warning.  This warning
// tells us that we are passing this in
// an initializer, before "this" is through
// initializing.  This is ok, because
// we just store the ptr in the other
// constructors

CSimpleSite::CSimpleSite (CSimpleDoc FAR *lpDoc) : m_OleClientSite(this),
 m_AdviseSink(this)
#pragma warning (default : 4355)  // Turn the warning back on
{
// remember the pointer to the doc
m_lpDoc = lpDoc;

// clear the reference count
m_nCount = 0;

m_dwDrawAspect = DVASPECT_CONTENT;
m_lpOleObject = NULL;
m_fObjectOpen = FALSE;
}

//**********************************************************************
//
// CSimpleSite::~CSimpleSite
//
// Purpose:
//
//      Destructor for CSimpleSite
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                                Location
//
//      OutputDebugString                       Windows API
//      IOleObject::Release                     Object
//      IStorage::Release                       OLE API
//
// Comments:
//
//********************************************************************

CSimpleSite::~CSimpleSite ()
{
OutputDebugString ("In CSimpleSite's Destructor \r\n");

if (m_lpOleObject)
   m_lpOleObject->Release();

if (m_lpObjStorage)
   m_lpObjStorage->Release();
}

//**********************************************************************
//
// CSimpleSite::CloseOleObject
//
// Purpose:
//
//      Call IOleObject::Close on the object of the CSimpleSite
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                                Location
//
//      OutputDebugString                       Windows API
//      IOleObject::Close                       Object
//
// Comments:
//
//********************************************************************

void CSimpleSite::CloseOleObject (void)
{
LPVIEWOBJECT lpViewObject = NULL;

OutputDebugString ("In CSimpleSite::CloseOleObject \r\n");

if (m_lpOleObject)
   {
   m_lpOleObject->Close(OLECLOSE_NOSAVE);
   }
}

//**********************************************************************
//
// CSimpleSite::UnloadOleObject
//
// Purpose:
//
//      Close and release all pointers to the object of the CSimpleSite
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                                Location
//
//      OutputDebugString                       Windows API
//      CSimpleSite::CloseOleObject             SITE.CPP
//      IOleObject::QueryInterface              Object
//      IViewObject::SetAdvise                  Object
//      IViewObject::Release                    Object
//      IStorage::Release                       OLE API
//
// Comments:
//
//********************************************************************

void CSimpleSite::UnloadOleObject (void)
{
OutputDebugString ("In CSimpleSite::UnloadOleObject \r\n");

if (m_lpOleObject)
   {
   LPVIEWOBJECT lpViewObject;
   CloseOleObject();    // ensure object is closed; NOP if already closed

   m_lpOleObject->QueryInterface(IID_IViewObject, (LPVOID FAR *)&lpViewObject);

   if (lpViewObject)
   {
   // Remove the view advise
   lpViewObject->SetAdvise(m_dwDrawAspect, 0, NULL);
   lpViewObject->Release();
   }

   m_lpOleObject->Release();
   m_lpOleObject = NULL;
   }
}

//**********************************************************************
//
// CSimpleSite::QueryInterface
//
// Purpose:
//
//      Used for interface negotiation of the container Site.
//
// Parameters:
//
//      REFIID riid         -   A reference to the interface that is
//                              being queried.
//
//      LPVOID FAR* ppvObj  -   An out parameter to return a pointer to
//                              the interface.
//
// Return Value:
//
//      S_OK    -   The interface is supported.
//      S_FALSE -   The interface is not supported
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      IsEqualIID                  OLE API
//                   OLE API
//      CSimpleSite::AddRef          OBJ.CPP
//      COleClientSite::AddRef      IOCS.CPP
//      CAdviseSink::AddRef         IAS.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP CSimpleSite::QueryInterface(REFIID riid, LPVOID FAR* ppvObj)
{
OutputDebugString("In CSimpleSite::QueryInterface\r\n");

*ppvObj = NULL;     // must set out pointer parameters to NULL

if ( riid == IID_IUnknown)
{
AddRef();
*ppvObj = this;
return S_OK;
}

if ( riid == IID_IOleClientSite)
{
m_OleClientSite.AddRef();
*ppvObj = &m_OleClientSite;
return S_OK;
}

if ( riid == IID_IAdviseSink)
{
m_AdviseSink.AddRef();
*ppvObj = &m_AdviseSink;
return S_OK;
}

// Not a supported interface
return E_NOINTERFACE;
}

//**********************************************************************
//
// CSimpleSite::AddRef
//
// Purpose:
//
//      Increments the reference count of the container Site.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      ULONG   -   The new reference count of the site.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//********************************************************************

STDMETHODIMP_(ULONG) CSimpleSite::AddRef()
{
OutputDebugString("In CSimpleSite::AddRef\r\n");

return ++m_nCount;
}

//**********************************************************************
//
// CSimpleSite::Release
//
// Purpose:
//
//      Decrements the reference count of the container Site
//
// Parameters:
//
//      None
//
// Return Value:
//
//      ULONG   -   The new reference count of the Site.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//********************************************************************

STDMETHODIMP_(ULONG) CSimpleSite::Release()
{
OutputDebugString("In CSimpleSite::Release\r\n");

if (--m_nCount == 0) {
delete this;
return 0;
}
return m_nCount;
}

//**********************************************************************
//
// CSimpleSite::InitObject
//
// Purpose:
//
//      Used to initialize a newly create object (can't be done in the
//      constructor).
//
// Parameters:
//
//      BOOL fCreateNew -   TRUE if insert NEW object
//                          FALSE if create object FROM FILE
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                        Location
//
//      IOleObject::SetHostNames        Object
//      IOleObject::QueryInterface      Object
//      IViewObject2::GetExtent         Object
//      IOleObject::DoVerb              Object
//      IViewObject::SetAdvise          Object
//      IViewObject::Release            Object
//      GetClientRect                   Windows API
//      OleSetContainedObject           OLE API
//
// Comments:
//
//********************************************************************

void CSimpleSite::InitObject(BOOL fCreateNew)
{
LPVIEWOBJECT2 lpViewObject2;
RECT rect;

// Set a View Advise
m_lpOleObject->QueryInterface(IID_IViewObject2,(LPVOID FAR *)&lpViewObject2);
lpViewObject2->SetAdvise(m_dwDrawAspect, ADVF_PRIMEFIRST, &m_AdviseSink);

// get the initial size of the object
lpViewObject2->GetExtent(m_dwDrawAspect, -1 /*lindex*/, NULL /*ptd*/, &m_sizel);
GetObjRect(&rect);  // get the rectangle of the object in pixels
lpViewObject2->Release();

// give the object the name of the container app/document
//@@WTK WIN32, UNICODE
//m_lpOleObject->SetHostNames("Simple Application", "Simple OLE 2.0 Drag/Drop Container");
m_lpOleObject->SetHostNames(OLESTR("Simple Application"), OLESTR("Simple OLE 2.0 Drag/Drop Container"));

// inform object handler/DLL object that it is used in the embedding container's context
OleSetContainedObject(m_lpOleObject, TRUE);

if (fCreateNew) {
   // force new object to save to guarantee valid object in our storage.
   // OLE 1.0 objects may close w/o saving. this is NOT necessary if the
   // object is created FROM FILE; its data in storage is already valid.
   m_OleClientSite.SaveObject();

   // we only want to DoVerb(SHOW) if this is an InsertNew object.
   // we should NOT DoVerb(SHOW) if the object is created FromFile.
   m_lpOleObject->DoVerb(
   OLEIVERB_SHOW,
   NULL,
   &m_OleClientSite,
   -1,
   m_lpDoc->m_hDocWnd,
   &rect);
}
}

//**********************************************************************
//
// CSimpleSite::PaintObj
//
// Purpose:
//
//      Paints the object
//
// Parameters:
//
//      HDC hDC     - Device context of the document window
//
// Return Value:
//
// Function Calls:
//      Function                        Location
//
//      IOleObject::QueryInterface      Object
//      IViewObject::GetColorSet        Object
//      IViewObject::Release            Object
//      SetMapMode                      Windows API
//      LPtoDP                          Windows API
//      CreateHatchBrush                Windows API
//      SelectObject                    Windows API
//      DeleteObject                    Windows API
//      CreatePalette                   Windows API
//      SelectPalette                   Windows API
//      RealizePalette                  Windows API
//      OleStdFree                      OUTLUI Function
//      OleDraw                         OLE API
//
// Comments:
//
//********************************************************************

void CSimpleSite::PaintObj(HDC hDC)
{
RECT rect;

// need to check to make sure there is a valid object
// available.  This is needed if there is a paint msg
// between the time that CSimpleSite is instantiated
// and OleUIInsertObject returns.
if (!m_lpOleObject)
return;

// convert it to pixels
GetObjRect(&rect);

LPLOGPALETTE pColorSet = NULL;
LPVIEWOBJECT lpView = NULL;

// get a pointer to IViewObject
m_lpOleObject->QueryInterface(IID_IViewObject,(LPVOID FAR *) &lpView);

// if the QI succeeds, get the LOGPALETTE for the object
if (lpView)
lpView->GetColorSet(m_dwDrawAspect, -1, NULL, NULL, NULL, &pColorSet);

HPALETTE hPal=NULL;
HPALETTE hOldPal=NULL;

// if a LOGPALETTE was returned (not guarateed), create the palette and
// realize it.  NOTE: A smarter application would want to get the LOGPALETTE
// for each of its visible objects, and try to create a palette that
// satisfies all of the visible objects.  ALSO: OleStdFree() is use to
// free the returned LOGPALETTE.
if ((pColorSet))
{
hPal = CreatePalette((const LPLOGPALETTE) pColorSet);
hOldPal = SelectPalette(hDC, hPal, FALSE);
RealizePalette(hDC);
OleStdFree(pColorSet);
}

// draw the object
OleDraw(m_lpOleObject, m_dwDrawAspect, hDC, &rect);

// if the object is open, draw a hatch rect.
if (m_fObjectOpen)
{
HBRUSH hBrush = CreateHatchBrush ( HS_BDIAGONAL, RGB(0,0,0) );
HBRUSH hOldBrush = (HBRUSH) SelectObject (hDC, hBrush);
SetROP2(hDC, R2_MASKPEN);
Rectangle (hDC, rect.left, rect.top, rect.right, rect.bottom);
SelectObject(hDC, hOldBrush);
DeleteObject(hBrush);
}

// if we created a palette, restore the old one, and destroy
// the object.
if (hPal)
{
SelectPalette(hDC,hOldPal,FALSE);
DeleteObject(hPal);
}

// if a view pointer was successfully returned, it needs to be released.
if (lpView)
lpView->Release();
}

//**********************************************************************
//
// CSimpleSite::GetObjRect
//
// Purpose:
//
//      Retrieves the rect of the object in pixels
//
// Parameters:
//
//      LPRECT lpRect - Rect structure filled with object's rect in pixels
//
// Return Value:
//
// Function Calls:
//      Function                        Location
//
//      XformWidthInHimetricToPixels    OUTLUI Function
//      XformHeightInHimetricToPixels   OUTLUI Function
//
// Comments:
//
//********************************************************************

void CSimpleSite::GetObjRect(LPRECT lpRect)
{
// convert it to pixels
lpRect->left = lpRect->top = 0;
lpRect->right = XformWidthInHimetricToPixels(NULL,(int)m_sizel.cx);
lpRect->bottom = XformHeightInHimetricToPixels(NULL,(int)m_sizel.cy);
}


SITE.H

//**********************************************************************
// File name: SITE.H
//
//      Definition of CSimpleSite
//
// Copyright 1992 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************
#if !defined( _SITE_H_ )
#define _SITE_H_

#include <ole2.h>
#include "ias.h"
#include "iocs.h"

class CSimpleDoc;

class CSimpleSite : public IUnknown
{
public:
int m_nCount;
LPOLEOBJECT m_lpOleObject;
DWORD m_dwDrawAspect;
SIZEL m_sizel;
BOOL m_fObjectOpen;
LPSTORAGE m_lpObjStorage;

CAdviseSink m_AdviseSink;
COleClientSite m_OleClientSite;

CSimpleDoc FAR * m_lpDoc;

// IUnknown Interfaces
STDMETHODIMP QueryInterface(REFIID riid, LPVOID FAR* ppvObj);
STDMETHODIMP_(ULONG) AddRef();
STDMETHODIMP_(ULONG) Release();

void InitObject(BOOL fCreateNew);
static CSimpleSite FAR * Create(CSimpleDoc FAR *lpDoc);
CSimpleSite(CSimpleDoc FAR *lpDoc);
~CSimpleSite();
void PaintObj(HDC hDC);
void GetObjRect(LPRECT lpRect);
void CloseOleObject(void);
void UnloadOleObject(void);
};

#endif  // _SITE_H_


README.TXT

Simple Visual Editing

The SimpSvr sample implements the simplest OLE 2.0 object that supports the
visual editing feature. The object is a colored square with a black border.


APP.H

//**********************************************************************
// File name: app.h
//
//      Definition of CSimpSvrApp
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#if !defined( _APP_H_)
#define _APP_H_
#include <stablize.h>

class CSimpSvrDoc;
interface CClassFactory;

class CSimpSvrApp : public IUnknown, public CSafeRefCount
{
private:

        HWND m_hAppWnd;             // main window handle
        HINSTANCE m_hInst;          // application instance
        BOOL m_fStartByOle;         // TRUE if app started by OLE
        DWORD m_dwRegisterClass;    // returned by RegisterClassFactory

        HMENU m_hMainMenu;
        HMENU m_hColorMenu;
        HMENU m_hHelpMenu;

        LPOLEOBJECT m_OleObject;    // pointer to "dummy" object

        CSimpSvrDoc FAR * m_lpDoc;   // pointer to document object
        BOOL m_fInitialized;         // OLE initialization flag
        BOOL m_fOleStdInit;          // OleStd initialization flag

        RECT nullRect;               // used in inplace negotiation

public:
        // IUnknown Interfaces
        STDMETHODIMP QueryInterface(REFIID riid, LPVOID FAR* ppvObj);
        STDMETHODIMP_(ULONG) AddRef();
        STDMETHODIMP_(ULONG) Release();

        // Initialization methods

        CSimpSvrApp();           // Constructor
        ~CSimpSvrApp();          // Destructor

BOOL fInitApplication (HINSTANCE  hInstance);
BOOL fInitInstance (HINSTANCE  hInstance, int nCmdShow, CClassFactory FAR * lpClassFactory);

        // Message handling methods

        long lCommandHandler (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
        long lSizeHandler (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
        long lCreateDoc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
        void PaintApp(HDC hDC);

        //  Utility functions
        void ParseCmdLine(LPSTR lpCmdLine);
        void SetStatusText();
        BOOL IsInPlaceActive();
        void ShowAppWnd(int nCmdShow=SW_SHOWNORMAL);
        void HideAppWnd();

        // member variable access
        inline HWND GethAppWnd() { return m_hAppWnd; };
        inline HINSTANCE GethInst() { return m_hInst; };
        inline BOOL IsStartedByOle() { return m_fStartByOle; };
        inline BOOL IsInitialized() { return m_fInitialized; };
        inline DWORD GetRegisterClass() { return m_dwRegisterClass; };
        inline CSimpSvrDoc FAR * GetDoc() { return m_lpDoc; };
        inline void ClearDoc() { m_lpDoc = NULL; };
        inline LPOLEOBJECT GetOleObject() { return m_OleObject; };
                inline HMENU GetMainMenu() { return m_hMainMenu; };
        inline HMENU GetColorMenu() { return m_hColorMenu; };
        inline HMENU GetHelpMenu() { return m_hHelpMenu; } ;

        friend interface CClassFactory;  // make the contained class a friend
};

#endif


DOC.CPP

//**********************************************************************
// File name: DOC.CPP
//
//      Implementation file for CSimpSvrDoc.
//
// Functions:
//
//      See DOC.H for Class Definition
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "obj.h"
#include "app.h"
#include "doc.h"

//**********************************************************************
//
// CSimpSvrDoc::Create
//
// Purpose:
//
//      Creation for the CSimpSvrDoc Class
//
// Parameters:
//
//      CSimpSvrApp FAR * lpApp  -   Pointer to the CSimpSvrApp Class
//
//      LPRECT lpRect           -   Client area rect of "frame" window
//
//      HWND hWnd               -   Window Handle of "frame" window
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      StgCreateDocfile            OLE API
//      CreateWindow                Windows API
//      ShowWindow                  Windows API
//      UpdateWindow                Windows API
//      CSimpSvrDoc::CSimpSvrDoc    DOC.CPP
//      CreateHatchWindow           OLESTD
//
// Comments:
//
//      This routine was added so that failure could be returned
//      from object creation.
//
//********************************************************************

CSimpSvrDoc FAR * CSimpSvrDoc::Create(CSimpSvrApp FAR *lpApp, LPRECT lpRect,HWND hWnd)
{
        CSimpSvrDoc FAR * lpTemp = new CSimpSvrDoc(lpApp, hWnd);

        if (!lpTemp)
                return NULL;

        // create the document Window
        lpTemp->m_hDocWnd = CreateWindow(
                        "DocWClass",
                        NULL,
                        WS_CHILD | WS_CLIPSIBLINGS,
                        lpRect->left,
                        lpRect->top,
                        lpRect->right,
                        lpRect->bottom,
                        hWnd,
                        NULL,
                        lpApp->GethInst(),
                        NULL);

        if (!lpTemp->m_hDocWnd)
                goto error;

        lpTemp->ShowDocWnd();

        lpTemp->m_hHatchWnd = CreateHatchWindow( lpTemp->m_hDocWnd, lpApp->GethInst());

        lpTemp->HideHatchWnd();

        return (lpTemp);

error:
        delete (lpTemp);
        return NULL;

}

//**********************************************************************
//
// CSimpSvrDoc::CSimpSvrDoc
//
// Purpose:
//
//      Constructor for the CSimpSvrDoc Class
//
// Parameters:
//
//      CSimpSvrApp FAR * lpApp  -   Pointer to the CSimpSvrApp Class
//
//      HWND hWnd               -   Window Handle of "frame" window
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      GetMenu                     Windows API
//      GetSubMenu                  Windows API
//
// Comments:
//
//********************************************************************

CSimpSvrDoc::CSimpSvrDoc(CSimpSvrApp FAR * lpApp,HWND hWnd)
{
        OutputDebugString("In CSimpSvrDoc's Constructor\r\n");
        m_lpApp = lpApp;
        m_lpObj = NULL;
        m_fClosing = FALSE;

        // set up menu handles

}

//**********************************************************************
//
// CSimpSvrDoc::~CSimpSvrDoc
//
// Purpose:
//
//      Destructor for CSimpSvrDoc
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      DestroyWindow               Windows API
//      CSimpSvrApp::ClearDoc       APP.CPP
//
// Comments:
//
//********************************************************************

CSimpSvrDoc::~CSimpSvrDoc()
{
        OutputDebugString("In CSimpSvrDoc's Destructor\r\n");
        m_lpApp->ClearDoc();
        DestroyWindow(m_hHatchWnd);
}

//**********************************************************************
//
// CSimpSvrDoc::QueryInterface
//
// Purpose:
//
//      Return a pointer to a requested interface
//
// Parameters:
//
//      REFIID riid         -   ID of interface to be returned
//      LPVOID FAR* ppvObj  -   Location to return the interface
//
// Return Value:
//
//      E_NOINTERFACE -   Always
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpSvrApp::QueryInterface APP.CPP
//
// Comments:
//
//      Since the document could contain multiple objects, all
//      interfaces except those associated with the document should
//      be returned.  In this implementation, there are no doc level
//      interfaces.
//
//********************************************************************

STDMETHODIMP CSimpSvrDoc::QueryInterface(REFIID riid, LPVOID FAR* ppvObj)
{
        OutputDebugString("In CSimpSvrDoc::QueryInterface\r\n");

        SCODE sc = E_NOINTERFACE;

        if ( (riid == IID_IUnknown) )
           {
           AddRef();
           *ppvObj = this;
           sc = S_OK;
           }

        return sc;
}

//**********************************************************************
//
// CSimpSvrDoc::AddRef
//
// Purpose:
//
//      Increments the document level reference count
//
// Parameters:
//
//      None
//
// Return Value:
//
//      UINT    -   The current reference count on the document
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpSvrApp::AddRef         APP.CPP
//
// Comments:
//
//      The reference count at this level reflects the total ref.
//      count of all interfaces on all objects contained within
//      this document.  Note that it also "trickles up" the
//      ref count to the app level.
//
//********************************************************************

STDMETHODIMP_(ULONG) CSimpSvrDoc::AddRef()
{
        OutputDebugString("In CSimpSvrDoc::AddRef\r\n");
        // AddRef the app, but return the doc count
        m_lpApp->AddRef();

        return SafeAddRef();
}

//**********************************************************************
//
// CSimpSvrDoc::Release
//
// Purpose:
//
//      Decrements the document level reference count
//
// Parameters:
//
//      None
//
// Return Value:
//
//      UINT    -   The current reference count on the document
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpSvrApp::Release         APP.CPP
//
// Comments:
//
//      The reference count at this level reflects the total ref.
//      count of all interfaces on all objects contained within
//      this document.  Note that it also "trickles up" the
//      ref count to the app level.
//
//********************************************************************

STDMETHODIMP_(ULONG) CSimpSvrDoc::Release()
{
        OutputDebugString("In CSimpSvrDoc::Release\r\n");
        // Release the app, but return the app count
        m_lpApp->Release();

        return SafeRelease();
}

//**********************************************************************
//
// CSimpSvrDoc::lResizeDoc
//
// Purpose:
//
//      Resizes the document
//
// Parameters:
//
//      LPRECT lpRect   -   The size of the client are of the "frame"
//                          Window.
//
// Return Value:
//
//      NULL
//
// Function Calls:
//      Function                                Location
//
//      MoveWindow                              Windows API
//
// Comments:
//
//********************************************************************

long CSimpSvrDoc::lResizeDoc(LPRECT lpRect)
{
        MoveWindow(m_hDocWnd, lpRect->left, lpRect->top, lpRect->right, lpRect->bottom, TRUE);
        return NULL;
}

//**********************************************************************
//
// CSimpSvrDoc::PaintDoc
//
// Purpose:
//
//      Paints the Document
//
// Parameters:
//
//      HDC hDC -   hDC of the document Window
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      CSimpSvrObj::Draw                   OBJ.CPP
//      CSimpSvrObj::GetDataAdviseHolder    OBJ.H
//      CSimpSvrObj::GetDataObject          OBJ.H
//      CSimpAppObj::IsStartedByOle         APP.CPP
//      IDataAdviseHolder::SendOnDataChange OLE API
//
// Comments:
//
//********************************************************************

void CSimpSvrDoc::PaintDoc (HDC hDC)
{
        // i f the object hasn't been created yet, then don't draw
        if (m_lpObj)
                m_lpObj->Draw(hDC,FALSE);
        else
                return;

        CStabilize stabilize(this);

        // Sending a data change every time we paint, but only if we
        // were started by OLE
        if (m_lpApp->IsStartedByOle())
                        m_lpObj->SendOnDataChange( );
}

//**********************************************************************
//
// CSimpSvrDoc::CreateObject
//
// Purpose:
//
//
// Parameters:
//
//
// Return Value:
//
//      NOERROR if the function succeeds, otherwise E_FAIL
//
// Function Calls:
//      Function                    Location
//
//      CSimpSvrObj::CSimpSvrObj    OBJ.CPP
//      CSimpSvrOjb::QueryInterface OBJ.CPP
//
// Comments:
//
//********************************************************************

HRESULT CSimpSvrDoc::CreateObject(REFIID riid, LPVOID FAR *ppvObject)
{
        SCODE sc = E_FAIL;

        m_lpObj = new CSimpSvrObj(this);

        if (m_lpObj)
                {
                m_lpObj->QueryInterface(riid, ppvObject);
                sc = S_OK;
                }

        return sc;
}

//**********************************************************************
//
// CSimpSvrDoc::Close
//
// Purpose:
//
//      Closes the object
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                                Location
//
//      OutputDebugString                       Windows API
//      CSimpSvrObj::AddRef                     OBJ.CPP
//      CSimpSvrObj::Release                    OBJ.CPP
//      CSimpSvrObj::IsInPlaceActive            OBJ.H
//      CSimpSvrObj::GetOleInPlaceObject        OBJ.H
//      CSimpSvrObj::ClearOleClientSite         OBJ.H
//      CSimpSvrObj::GetDataAdviseHolder        OBJ.H
//      CSimpSvrObj::GetOleClientSite           OBJ.H
//      CSimpSvrObj::ClearDataAdviseHolder      OBJ.H
//      CSimpSvrObj::GetOleAdviseHolder         OBJ.H
//      CSimpSvrObj::ClearOleAdviseHolder       OBJ.H
//      IOleInPlaceObject::InPlaceDeactivate    Container
//      IOleClientSite::SaveObject              Container
//      IOleClientSite::OnShowWindow            Container
//      IOleClientSite::Release                 Container
//      IDataAdviseHolder::SendOnDataChange     OLE
//      IDataAdviseHolder::Release              OLE
//      IOleAdviseHolder::SendOnClose           OLE
//      IOleAdviseHolder::Release               OLE
//
// Comments:
//
//********************************************************************

void CSimpSvrDoc::Close()
{
        OutputDebugString("In CSimpSvrDoc::Close() \r\n");
        CStabilize stabilize(this);

        m_lpObj->AddRef(); // hold object alive

        if (m_fClosing)
                return;

        m_fClosing = TRUE;

        // if the object is currently inplace active, then deactivate
        if (m_lpObj->IsInPlaceActive())
                m_lpObj->GetOleInPlaceObject()->InPlaceDeactivate();

        // unregister from the ROT...
        if (m_lpObj->GetRotRegister())
                {
                LPRUNNINGOBJECTTABLE lpRot;

                if (GetRunningObjectTable (0, &lpRot) == NOERROR )
                        {
                        lpRot->Revoke(m_lpObj->GetRotRegister());
                        lpRot->Release();
                        }
                }

        // if we have a clientsite, instruct it to save the object
        if (m_lpObj->GetOleClientSite())
                {
                m_lpObj->GetOleClientSite()->SaveObject();
                m_lpObj->GetOleClientSite()->OnShowWindow(FALSE);
                }

        // Do a final SendOnDataChange for those containers that have specified the
        // ADF_DATAONSTOP flag.
        if (m_lpObj->GetDataAdviseHolder())
                {
                m_lpObj->GetDataAdviseHolder()->SendOnDataChange( m_lpObj->GetDataObject(), 0, ADVF_DATAONSTOP);
                m_lpObj->GetDataAdviseHolder()->Release();
                m_lpObj->ClearDataAdviseHolder();
                }

        // Tell the container that we are shutting down.
        if (m_lpObj->GetOleAdviseHolder())
                {
                m_lpObj->GetOleAdviseHolder()->SendOnClose();
                m_lpObj->GetOleAdviseHolder()->Release();
                m_lpObj->ClearOleAdviseHolder();
                }

        if (m_lpObj->GetOleClientSite())
                {
                m_lpObj->GetOleClientSite()->Release();
                m_lpObj->ClearOleClientSite();
                }

        // release our streams and storage
        m_lpObj->GetPersistStorage()->ReleaseStreamsAndStorage();

        // Disconnect the object.  NOTE: This call should not do anything
        // unless the container has cause a GP Fault or some other problem
        // has occured...
        OutputDebugString("*** Before CoDisconnectObject *** \r\n");
        CoDisconnectObject((LPUNKNOWN)m_lpObj, 0);
        OutputDebugString("*** After CoDisconnectObject *** \r\n");

        m_lpObj->Release(); // let object close

}

//**********************************************************************
//
// CSimpSvrDoc::SetStatusText
//
// Purpose:
//
//      Sets the Container's status bar text
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                        Location
//
//      CSimpSvrObj::IsInPlaceActive    OBJ.CPP
//      IOleInPlaceFrame::SetStatusText Container
//
// Comments:
//
//      Even though there is no status line in this sample, this
//      method must be called on WM_MENUSELECT to clear the last
//      message in the status line.
//
//********************************************************************

void CSimpSvrDoc::SetStatusText()
{
        CStabilize stabilize(this);
        if (m_lpObj->IsInPlaceActive())
                //@@WTK WIN32, UNICODE
                //m_lpObj->GetInPlaceFrame()->SetStatusText("\0");
                m_lpObj->GetInPlaceFrame()->SetStatusText(OLESTR("\0"));
}

//**********************************************************************
//
// CSimpSvrDoc::ShowDocWnd
//
// Purpose:
//
//      Shows the Document Window
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                        Location
//
//      ShowWindow                      Windows API
//      UpdateWindow                    Windows API
//
// Comments:
//
//********************************************************************

void CSimpSvrDoc::ShowDocWnd()
{
        ShowWindow(m_hDocWnd, SW_SHOWNORMAL);  // Show the window
        UpdateWindow(m_hDocWnd);               // Sends WM_PAINT message
}

//**********************************************************************
//
// CSimpSvrDoc::ShowHatchWnd
//
// Purpose:
//
//      Shows the hatch Window
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                        Location
//
//      ShowWindow                      Windows API
//
// Comments:
//
//********************************************************************

void CSimpSvrDoc::ShowHatchWnd()
{
        ShowWindow(m_hHatchWnd, SW_SHOW);
}

//**********************************************************************
//
// CSimpSvrDoc::HideDocWnd
//
// Purpose:
//
//      Hides the DocumentWindow
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                        Location
//
//      ShowWindow                      Windows API
//
// Comments:
//
//********************************************************************

void CSimpSvrDoc::HideDocWnd()
{
        ShowWindow(m_hDocWnd, SW_HIDE);
}

//**********************************************************************
//
// CSimpSvrDoc::HideHatchWnd
//
// Purpose:
//
//      Hides the Hatch Window
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                        Location
//
//      ShowWindow                      Windows API
//
// Comments:
//
//********************************************************************

void CSimpSvrDoc::HideHatchWnd()
{
        ShowWindow(m_hHatchWnd, SW_HIDE);
}


DOC.H

//**********************************************************************
// File name: doc.h
//
//      Definition of CSimpSvrDoc
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#if !defined( _DOC_H_ )
#define _DOC_H_
#include <stablize.h>

class CSimpSvrApp;
class CSimpSvrObj;

class CSimpSvrDoc : IUnknown, public CSafeRefCount
{
private:
        CSimpSvrApp FAR * m_lpApp;
        CSimpSvrObj FAR * m_lpObj;

        HWND m_hDocWnd;
        HWND m_hHatchWnd;
        BOOL m_fClosing;

public:
        static CSimpSvrDoc FAR * Create(CSimpSvrApp FAR *lpApp, LPRECT lpRect,HWND hWnd);

        CSimpSvrDoc();
        CSimpSvrDoc(CSimpSvrApp FAR *lpApp, HWND hWnd);
        ~CSimpSvrDoc();

// IUnknown Interfaces
        STDMETHODIMP QueryInterface(REFIID riid, LPVOID FAR* ppvObj);
        STDMETHODIMP_(ULONG) AddRef();
        STDMETHODIMP_(ULONG) Release();

        long lResizeDoc(LPRECT lpRect);
        long lAddVerbs();

        BOOL Load(LPSTR lpszFileName);
        void PaintDoc(HDC hDC);
        void lButtonDown(WPARAM wParam,LPARAM lParam);

        HRESULT CreateObject(REFIID riid, LPVOID FAR *ppvObject);

        void Close();
        void SetStatusText();
        void ShowDocWnd();
        void ShowHatchWnd();
        void CSimpSvrDoc::HideDocWnd();
        void CSimpSvrDoc::HideHatchWnd();

// member access
        inline HWND GethDocWnd() { return m_hDocWnd; };
        inline HWND GethHatchWnd() { return m_hHatchWnd; };
        inline HWND GethAppWnd() { return m_lpApp->GethAppWnd(); };
        inline CSimpSvrApp FAR * GetApp() { return m_lpApp; };
        inline CSimpSvrObj FAR * GetObj() { return m_lpObj; };
        inline void ClearObj() { m_lpObj = NULL; };

};

#endif


ICF.CPP

//**********************************************************************
// File name: ICF.CPP
//
//    Implementation file for the CClassFactory Class
//
// Functions:
//
//    See icf.h for a list of member functions.
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "app.h"
#include "doc.h"
#include "icf.h"

//**********************************************************************
//
// CClassFactory::QueryInterface
//
// Purpose:
//
//
// Parameters:
//
//      REFIID riid         -   Interface being queried for.
//
//      LPVOID FAR *ppvObj  -   Out pointer for the interface.
//
// Return Value:
//
//      S_OK            - Success
//      E_NOINTERFACE   - Failure
//
// Function Calls:
//      Function                    Location
//
//      CSimpSvrApp::QueryInterface APP.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP CClassFactory::QueryInterface  ( REFIID riid, LPVOID FAR* ppvObj)
{
OutputDebugString("In CClassFactory::QueryInterface\r\n");

SCODE sc = S_OK;

if ( (riid == IID_IUnknown) ||
 (riid == IID_IClassFactory) )
*ppvObj = this;
else
{
*ppvObj = NULL;
sc = E_NOINTERFACE;
}

if (*ppvObj)
((LPUNKNOWN)*ppvObj)->AddRef();

// pass it on to the Application object
return sc;
};

//**********************************************************************
//
// CClassFactory::AddRef
//
// Purpose:
//
//      Increments the reference count on CClassFactory and the application
//      object.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      The Reference count on CClassFactory
//
// Function Calls:
//      Function                    Location
//
//      OuputDebugString            Windows API
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP_(ULONG) CClassFactory::AddRef ()
{
OutputDebugString("In CClassFactory::AddRef\r\n");

return ++m_nCount;
};

//**********************************************************************
//
// CClassFactory::Release
//
// Purpose:
//
//      Decrements the reference count of CClassFactory and the
//      application object.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      The new reference count
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP_(ULONG) CClassFactory::Release ()
{
OutputDebugString("In CClassFactory::Release\r\n");

if (--m_nCount == 0) {
delete this;
        return 0;
    }

return m_nCount;
};

//**********************************************************************
//
// CClassFactory::CreateInstance
//
// Purpose:
//
//      Instantiates a new OLE object
//
// Parameters:
//
//      LPUNKNOWN pUnkOuter     - Pointer to the controlling unknown
//
//      REFIID riid             - The interface type to fill in ppvObject
//
//      LPVOID FAR* ppvObject   - Out pointer for the object
//
// Return Value:
//
//      S_OK                    - Creation was successful
//      CLASS_E_NOAGGREGATION   - Tried to be created as part of an aggregate
//
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpSvrDoc::CreateObject   DOC.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP CClassFactory::CreateInstance ( LPUNKNOWN pUnkOuter,
  REFIID riid,
  LPVOID FAR* ppvObject)
{
HRESULT hErr;

OutputDebugString("In CClassFactory::CreateInstance\r\n");

// need to NULL the out parameter
*ppvObject = NULL;

// we don't support aggregation...
if (pUnkOuter)
{
hErr = CLASS_E_NOAGGREGATION;
goto error;
}

hErr = m_lpApp->m_lpDoc->CreateObject(riid, ppvObject);

error:
return hErr;
};

//**********************************************************************
//
// CClassFactory::LockServer
//
// Purpose:
//
//
// Parameters:
//
//      BOOL fLock      - TRUE to lock the server, FALSE to unlock it
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CoLockObjectExternal        OLE API
//                   OLE API
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP CClassFactory::LockServer ( BOOL fLock)
{
OutputDebugString("In CClassFactory::LockServer\r\n");
CoLockObjectExternal(m_lpApp, fLock, TRUE);

return  S_OK;
};


ICF.H

//**********************************************************************
// File name: icf.h
//
//      Definition of CClassFactory
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#if !defined( _ICF_H_)
#define _ICF_H_

class CSimpSvrApp;

interface CClassFactory :  IClassFactory
{
private:
int m_nCount;               // reference count
CSimpSvrApp FAR * m_lpApp;

public:
CClassFactory::CClassFactory(CSimpSvrApp FAR * lpApp)
{
OutputDebugString("In CClassFactory's Constructor\r\n");
m_lpApp = lpApp;
m_nCount = 0;
};
CClassFactory::~CClassFactory()
   {
   OutputDebugString("In CClassFactory's Destructor\r\n");
   };

// IUnknown Methods

STDMETHODIMP QueryInterface (REFIID riid, LPVOID FAR* ppvObj);
STDMETHODIMP_(ULONG) AddRef ();
STDMETHODIMP_(ULONG) Release ();

STDMETHODIMP CreateInstance (LPUNKNOWN pUnkOuter,
  REFIID riid,
  LPVOID FAR* ppvObject);
STDMETHODIMP LockServer ( BOOL fLock);

};

#endif


IDO.CPP

//**********************************************************************
// File name: IDO.CPP
//
//    Implementation file for the CDataObject Class
//
// Functions:
//
//    See ido.h for a list of member functions.
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "obj.h"
#include "ido.h"
#include "app.h"
#include "doc.h"

//**********************************************************************
//
// CDataObject::QueryInterface
//
// Purpose:
//
//
// Parameters:
//
//      REFIID riid         -   Interface being queried for.
//
//      LPVOID FAR *ppvObj  -   Out pointer for the interface.
//
// Return Value:
//
//      S_OK            - Success
//      E_NOINTERFACE   - Failure
//
// Function Calls:
//      Function                    Location
//
//      CSimpSvrObj::QueryInterface OBJ.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP CDataObject::QueryInterface ( REFIID riid, LPVOID FAR* ppvObj)
{
OutputDebugString("In CDataObject::QueryInterface\r\n");

return m_lpObj->QueryInterface(riid, ppvObj);
};

//**********************************************************************
//
// CDataObject::AddRef
//
// Purpose:
//
//      Increments the reference count on CClassFactory and the application
//      object.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      The Reference count on CDataObject
//
// Function Calls:
//      Function                    Location
//
//      OuputDebugString            Windows API
//      CSimpSvrObj::AddRef         OBJ.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP_(ULONG) CDataObject::AddRef ()
{
OutputDebugString("In CDataObject::AddRef\r\n");
++m_nCount;
return m_lpObj->AddRef();
};

//**********************************************************************
//
// CDataObject::Release
//
// Purpose:
//
//      Decrements the reference count of CDataObject
//
// Parameters:
//
//      None
//
// Return Value:
//
//      The new reference count
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpSvrObj::Release        OBJ.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP_(ULONG) CDataObject::Release ()
{
OutputDebugString("In CDataObject::Release\r\n");
--m_nCount;
return m_lpObj->Release();
};

//**********************************************************************
//
// CDataObject::QueryGetData
//
// Purpose:
//
//      Called to determine if our object supports a particular
//      FORMATETC.
//
// Parameters:
//
//      LPFORMATETC pformatetc  - Pointer to the FORMATETC being queried for.
//
// Return Value:
//
//      DATA_E_FORMATETC    - The FORMATETC is not supported
//      S_OK                - The FORMATETC is supported.
//
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//                   OLE API
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP CDataObject::QueryGetData  ( LPFORMATETC pformatetc )
{
SCODE sc = DATA_E_FORMATETC;

OutputDebugString("In CDataObject::QueryGetData\r\n");

// check the validity of the formatetc.
if ( (pformatetc->cfFormat == CF_METAFILEPICT)  &&
 (pformatetc->dwAspect == DVASPECT_CONTENT) &&
 (pformatetc->tymed == TYMED_MFPICT) )
sc = S_OK;

return sc;
};

//**********************************************************************
//
// CDataObject::DAdvise
//
// Purpose:
//
//      Called by the container when it would like to be notified of
//      changes in the object data.
//
// Parameters:
//
//      FORMATETC FAR* pFormatetc   - The format the container is interested in.
//
//      DWORD advf                  - The type of advise to be set up.
//
//      LPADVISESINK pAdvSink       - Pointer to the containers IAdviseSink
//
//      DWORD FAR* pdwConnection    - Out parameter to return a unique connection id.
//
// Return Value:
//
//      passed on from IDataAdviseHolder
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CreateDataAdviseHolder      OLE API
//      IDataAdviseHolder::Advise   OLE API
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP CDataObject::DAdvise  ( FORMATETC FAR* pFormatetc, DWORD advf,
 LPADVISESINK pAdvSink, DWORD FAR* pdwConnection)
{
OutputDebugString("In CDataObject::DAdvise\r\n");

// if no DataAdviseHolder has been created, then create one.
if (!m_lpObj->m_lpDataAdviseHolder)
CreateDataAdviseHolder(&m_lpObj->m_lpDataAdviseHolder);

// pass on to the DataAdviseHolder
return m_lpObj->m_lpDataAdviseHolder->Advise( this, pFormatetc, advf,
  pAdvSink, pdwConnection);
}

//**********************************************************************
//
// CDataObject::GetData
//
// Purpose:
//
//      Returns the data in the format specified in pformatetcIn.
//
// Parameters:
//
//      LPFORMATETC pformatetcIn    -   The format requested by the caller
//
//      LPSTGMEDIUM pmedium         -   The medium requested by the caller
//
// Return Value:
//
//      DATA_E_FORMATETC    - Format not supported
//      S_OK                - Success
//
// Function Calls:
//      Function                        Location
//
//      OutputDebugString               Windows API
//      CSimpSvrObj::GetMetaFilePict()  OBJ.CPP
//                       OLE API
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP CDataObject::GetData  ( LPFORMATETC pformatetcIn, LPSTGMEDIUM pmedium )
{
SCODE sc = DATA_E_FORMATETC;

OutputDebugString("In CDataObject::GetData\r\n");

// Check to the FORMATETC and fill pmedium if valid.
if ( (pformatetcIn->cfFormat == CF_METAFILEPICT)  &&
 (pformatetcIn->dwAspect == DVASPECT_CONTENT) &&
 (pformatetcIn->tymed & TYMED_MFPICT) )
{
HANDLE hmfPict = m_lpObj->GetMetaFilePict();
pmedium->tymed = TYMED_MFPICT;
pmedium->hGlobal = hmfPict;
pmedium->pUnkForRelease = NULL;
sc = S_OK;
}

return  sc ;
};

//**********************************************************************
//
// CDataObject::DUnadvise
//
// Purpose:
//
//      Breaks down an Advise connection.
//
// Parameters:
//
//      DWORD dwConnection  - Advise connection ID.
//
// Return Value:
//
//      Returned from the DataAdviseHolder.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      IDataAdviseHolder::Unadvise OLE
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP CDataObject::DUnadvise  ( DWORD dwConnection)
{
OutputDebugString("In CDataObject::DUnadvise\r\n");

return m_lpObj->m_lpDataAdviseHolder->Unadvise(dwConnection);
};

//**********************************************************************
//
// CDataObject::GetDataHere
//
// Purpose:
//
//      Called to get a data format in a caller supplied location
//
// Parameters:
//
//      LPFORMATETC pformatetc  - FORMATETC requested
//
//      LPSTGMEDIUM pmedium     - Medium to return the data
//
// Return Value:
//
//      DATA_E_FORMATETC    - We don't support the requested format
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      In this simple implementation, we don't really support this
//      method, we just always return DATA_E_FORMATETC.
//
//********************************************************************

STDMETHODIMP CDataObject::GetDataHere  ( LPFORMATETC pformatetc,
 LPSTGMEDIUM pmedium )
{
OutputDebugString("In CDataObject::GetDataHere\r\n");
return  DATA_E_FORMATETC;
};

//**********************************************************************
//
// CDataObject::GetCanonicalFormatEtc
//
// Purpose:
//
//      Returns a FORMATETC that is equivalent to the one passed in.
//
// Parameters:
//
//      LPFORMATETC pformatetc      - FORMATETC to be tested.
//
//      LPFORMATETC pformatetcOut   - Out ptr for returned FORMATETC.
//
// Return Value:
//
//      DATA_S_SAMEFORMATETC    - Use the same formatetc as was passed.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CoGetMalloc                 OLE API
//      IMalloc::Alloc              OLE
//      IMalloc::Release            OLE
//      _fmemcpy                    C run-time
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP CDataObject::GetCanonicalFormatEtc  ( LPFORMATETC pformatetc,
   LPFORMATETC pformatetcOut)
{
HRESULT hresult;
OutputDebugString("In CDataObject::GetCanonicalFormatEtc\r\n");

if (!pformatetcOut)
return E_INVALIDARG;

/* OLE2NOTE: we must make sure to set all out parameters to NULL. */
pformatetcOut->ptd = NULL;

if (!pformatetc)
return E_INVALIDARG;

// OLE2NOTE: we must validate that the format requested is supported
if ((hresult = QueryGetData(pformatetc)) != NOERROR)
return hresult;

/* OLE2NOTE: an app that is insensitive to target device (as
**    SimpSvr is) should fill in the lpformatOut parameter
**    but NULL out the "ptd" field; it should return NOERROR if the
**    input formatetc->ptd what non-NULL. this tells the caller
**    that it is NOT necessary to maintain a separate screen
**    rendering and printer rendering. if should return
**    DATA_S_SAMEFORMATETC if the input and output formatetc's are
**    identical.
*/

*pformatetcOut = *pformatetc;
if (pformatetc->ptd == NULL)
return DATA_S_SAMEFORMATETC;
else
{
pformatetcOut->ptd = NULL;
return NOERROR;
}
};

//**********************************************************************
//
// CDataObject::SetData
//
// Purpose:
//
//      Called to set the data for the object.
//
// Parameters:
//
//      LPFORMATETC pformatetc      - the format of the data being passed
//
//      STGMEDIUM FAR * pmedium     - the location of the data.
//
//      BOOL fRelease               - Defines the ownership of the medium
//
// Return Value:
//
//      DATA_E_FORMATETC    - Not a valid FORMATETC for this object
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      This simple object does not support having its data set, so an
//      error value is always returned.
//
//********************************************************************

STDMETHODIMP CDataObject::SetData  ( LPFORMATETC pformatetc, STGMEDIUM FAR * pmedium,
 BOOL fRelease)
{
OutputDebugString("In CDataObject::SetData\r\n");
return  DATA_E_FORMATETC ;
};

//**********************************************************************
//
// CDataObject::EnumFormatEtc
//
// Purpose:
//
//      Enumerates the formats supported by this object.
//
// Parameters:
//
//      DWORD dwDirection                       - Order of enumeration.
//
//      LPENUMFORMATETC FAR* ppenumFormatEtc    - Place to return a pointer
//                                                to the enumerator.
//
// Return Value:
//
//      OLE_S_USEREG    - Indicates that OLE should consult the REG DB
//                        to enumerate the formats.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP CDataObject::EnumFormatEtc  ( DWORD dwDirection,
   LPENUMFORMATETC FAR* ppenumFormatEtc)
{
OutputDebugString("In CDataObject::EnumFormatEtc\r\n");
// need to NULL the out parameter
*ppenumFormatEtc = NULL;
return  OLE_S_USEREG ;
};

//**********************************************************************
//
// CDataObject::EnumDAdvise
//
// Purpose:
//
//      Returns an enumerator that enumerates all of the advises
//      set up on this data object.
//
// Parameters:
//
//      LPENUMSTATDATA FAR* ppenumAdvise    - An out ptr in which to
//                                            return the enumerator.
//
// Return Value:
//
//      Passed back from IDataAdviseHolder::EnumAdvise
//
// Function Calls:
//      Function                        Location
//
//      OutputDebugString               Windows API
//      IDAtaAdviseHolder::EnumAdvise   OLE
//
// Comments:
//
//      This just delegates to the DataAdviseHolder.
//
//********************************************************************

STDMETHODIMP CDataObject::EnumDAdvise  ( LPENUMSTATDATA FAR* ppenumAdvise)
{
OutputDebugString("In CDataObject::EnumDAdvise\r\n");
// need to NULL the out parameter
*ppenumAdvise = NULL;

return m_lpObj->m_lpDataAdviseHolder->EnumAdvise(ppenumAdvise);
};


IDO.H

//**********************************************************************
// File name: ido.h
//
//      Definition of CDataObject
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************
#if !defined( _IDO_H_)
#define _IDO_H_

#include <ole2.h>
#include "obj.h"

class CSimpSvrObj;

interface CDataObject : public IDataObject
{
private:
CSimpSvrObj FAR * m_lpObj;
int m_nCount;

public:
CDataObject::CDataObject(CSimpSvrObj FAR * lpSimpSvrObj)
{
m_lpObj = lpSimpSvrObj;
m_nCount = 0;
};

CDataObject::~CDataObject() {};

STDMETHODIMP QueryInterface (REFIID riid, LPVOID FAR* ppvObj);
STDMETHODIMP_(ULONG) AddRef ();
STDMETHODIMP_(ULONG) Release ();

STDMETHODIMP DAdvise  ( FORMATETC FAR* pFormatetc, DWORD advf,
LPADVISESINK pAdvSink, DWORD FAR* pdwConnection);
STDMETHODIMP DUnadvise  ( DWORD dwConnection);
STDMETHODIMP EnumDAdvise  ( LPENUMSTATDATA FAR* ppenumAdvise);
STDMETHODIMP EnumFormatEtc  ( DWORD dwDirection,
  LPENUMFORMATETC FAR* ppenumFormatEtc);
STDMETHODIMP GetCanonicalFormatEtc  ( LPFORMATETC pformatetc,
  LPFORMATETC pformatetcOut);
STDMETHODIMP GetData  ( LPFORMATETC pformatetcIn, LPSTGMEDIUM pmedium );
STDMETHODIMP GetDataHere  ( LPFORMATETC pformatetc, LPSTGMEDIUM pmedium );
STDMETHODIMP QueryGetData  ( LPFORMATETC pformatetc );
STDMETHODIMP SetData  ( LPFORMATETC pformatetc, STGMEDIUM FAR * pmedium,
BOOL fRelease);

};

#endif


IEC.CPP

//**********************************************************************
// File name: IEC.CPP
//
//    Implementation file for the CExternalConnection Class
//
// Functions:
//
//    See iec.h for a list of member functions.
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "obj.h"
#include "iec.h"
#include "app.h"
#include "doc.h"

//**********************************************************************
//
// CExternalConnection::QueryInterface
//
// Purpose:
//
//
// Parameters:
//
//      REFIID riid         -   Interface being queried for.
//
//      LPVOID FAR *ppvObj  -   Out pointer for the interface.
//
// Return Value:
//
//      S_OK            - Success
//      E_NOINTERFACE   - Failure
//
// Function Calls:
//      Function                    Location
//
//      CSimpSvrObj::QueryInterface OBJ.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP CExternalConnection::QueryInterface (REFIID riid, LPVOID FAR* ppvObj)
{
OutputDebugString("In CExternalConnection::QueryInterface\r\n");

return m_lpObj->QueryInterface(riid, ppvObj);
}

//**********************************************************************
//
// CExternalConnection::AddRef
//
// Purpose:
//
//      Increments the reference count on CExternalConnection and the "object"
//      object.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      The Reference count on the Object.
//
// Function Calls:
//      Function                    Location
//
//      OuputDebugString            Windows API
//      CSimpSvrObj::AddRef         OBJ.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP_(ULONG) CExternalConnection::AddRef ()
{
OutputDebugString("In CExternalConnection::AddRef\r\n");
++m_nCount;
return m_lpObj->AddRef();
};

//**********************************************************************
//
// CExternalConnection::Release
//
// Purpose:
//
//      Decrements the reference count of COleObject and the
//      "object" object.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      The new reference count
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpSvrObj::Release        OBJ.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP_(ULONG) CExternalConnection::Release ()
{
OutputDebugString("In CExternalConnection::Release\r\n");
--m_nCount;
return m_lpObj->Release();
};

//**********************************************************************
//
// CExternalConnection::AddConnection
//
// Purpose:
//
//      Called when another connection is made to the object.
//
// Parameters:
//
//      DWORD extconn   -   Type of connection
//
//      DWORD reserved  -   Reserved
//
// Return Value:
//
//      Strong connection count
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP_(DWORD) CExternalConnection::AddConnection (DWORD extconn, DWORD reserved)
{
OutputDebugString("In CExternalConnection::AddConnection\r\n");

if (extconn & EXTCONN_STRONG)
return ++m_dwStrong;

return 0;
}

//**********************************************************************
//
// CExternalConnection::ReleaseConnection
//
// Purpose:
//
//      Called when a connection to the object is released.
//
// Parameters:
//
//      DWORD extconn               - Type of Connection
//
//      DWORD reserved              - Reserved
//
//      BOOL fLastReleaseCloses     - Close flag
//
// Return Value:
//
//      The new reference count
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      COleObject::Close           IOO.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP_(DWORD) CExternalConnection::ReleaseConnection (DWORD extconn, DWORD reserved, BOOL fLastReleaseCloses)
{
OutputDebugString("In CExternalConnection::ReleaseConnection\r\n");

if (extconn & EXTCONN_STRONG)
{
DWORD dwSave = --m_dwStrong;

if (!m_dwStrong && fLastReleaseCloses)
m_lpObj->m_OleObject.Close(OLECLOSE_SAVEIFDIRTY);

return dwSave;
}
return 0;
}


IEC.H

//**********************************************************************
// File name: iec.h
//
//      Definition of CExternalConnection
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************
#if !defined( _IEC_H_)
#define _IEC_H_

#include <ole2.h>
#include "obj.h"

class CSimpSvrObj;

interface CExternalConnection : public IExternalConnection
{
private:
CSimpSvrObj FAR * m_lpObj;  // Ptr to object
int m_nCount;               // Ref count
DWORD m_dwStrong;           // Connection Count

public:
CExternalConnection::CExternalConnection(CSimpSvrObj FAR * lpSimpSvrObj)
{
m_lpObj = lpSimpSvrObj;
m_nCount = 0;
m_dwStrong = 0;
};

CExternalConnection::~CExternalConnection() {};

// *** IUnknown methods ***
STDMETHODIMP QueryInterface (REFIID riid, LPVOID FAR* ppvObj);
STDMETHODIMP_(ULONG) AddRef ();
STDMETHODIMP_(ULONG) Release ();

// *** IExternalConnection methods ***
STDMETHODIMP_(DWORD) AddConnection (DWORD extconn, DWORD reserved);
STDMETHODIMP_(DWORD) ReleaseConnection (DWORD extconn, DWORD reserved, BOOL fLastReleaseCloses);
};

#endif


IOIPAO.CPP

//**********************************************************************
// File name: IOIPAO.CPP
//
//    Implementation file for the CClassFactory Class
//
// Functions:
//
//    See ioipao.h for a list of member functions.
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "obj.h"
#include "ioipao.h"
#include "app.h"
#include "doc.h"

//**********************************************************************
//
// COleInPlaceActiveObject::QueryInterface
//
// Purpose:
//
//
// Parameters:
//
//      REFIID riid         -   Interface being queried for.
//
//      LPVOID FAR *ppvObj  -   Out pointer for the interface.
//
// Return Value:
//
//      S_OK            - Success
//      E_NOINTERFACE   - Failure
//
// Function Calls:
//      Function                    Location
//
//      CSimpSvrObj::QueryInterface OBJ.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP COleInPlaceActiveObject::QueryInterface ( REFIID riid, LPVOID FAR* ppvObj)
{
OutputDebugString("In COleInPlaceActiveObject::QueryInterface\r\n");
// need to NULL the out parameter
return m_lpObj->QueryInterface(riid, ppvObj);
}

//**********************************************************************
//
// COleInPlaceActiveObject::AddRef
//
// Purpose:
//
//      Increments the reference count on COleInPlaceActiveObject and the
//      "object" object.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      The Reference count on the "object" object.
//
// Function Calls:
//      Function                    Location
//
//      OuputDebugString            Windows API
//      CSimpSvrObj::AddRef         OBJ.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP_(ULONG) COleInPlaceActiveObject::AddRef ()
{
OutputDebugString("In COleInPlaceActiveObject::AddRef\r\n");

++m_nCount;

return m_lpObj->AddRef();
}

//**********************************************************************
//
// COleInPlaceActiveObject::Release
//
// Purpose:
//
//      Decrements the reference count of COleInPlaceActiveObject.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      The new reference count
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpSvrObj::Release        OBJ.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP_(ULONG) COleInPlaceActiveObject::Release ()
{
OutputDebugString("In COleInPlaceActiveObject::Release\r\n");

--m_nCount;

return m_lpObj->Release();
}

//**********************************************************************
//
// COleInPlaceActiveObject::OnDocWindowActivate
//
// Purpose:
//
//      Called when the doc window (in an MDI App) is (de)activated.
//
// Parameters:
//
//      BOOL fActivate  - TRUE if activating, FALSE if deactivating
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                            Location
//
//      OutputDebugString                   Windows API
//      IOleInPlaceFrame::SetActiveObject   Container
//      CSimpSvrObject::AddFrameLevelUI     OBJ.CPP
//
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP COleInPlaceActiveObject::OnDocWindowActivate  ( BOOL fActivate )
{
OutputDebugString("In COleInPlaceActiveObject::OnDocWindowActivate\r\n");

// Activating?
if (fActivate)
m_lpObj->AddFrameLevelUI();

// No frame level tools to remove...

return S_OK;
};

//**********************************************************************
//
// COleInPlaceActiveObject::OnFrameWindowActivate
//
// Purpose:
//
//      Called when the Frame window is (de)activating
//
// Parameters:
//
//      BOOL fActivate  - TRUE if activating, FALSE if Deactivating
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      SetFocus                    Windows API
//
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP COleInPlaceActiveObject::OnFrameWindowActivate  ( BOOL fActivate)
{
OutputDebugString("In COleInPlaceActiveObject::OnFrameWindowActivate\r\n");

// set the focus to the object window if we are activating.
/*    if (fActivate)
SetFocus(m_lpObj->m_lpDoc->GethDocWnd()); */

return  S_OK ;
};

//**********************************************************************
//
// COleInPlaceActiveObject::GetWindow
//
// Purpose:
//
//      Gets the objects Window Handle.
//
// Parameters:
//
//      HWND FAR* lphwnd    - Location to return the window handle.
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpSvrDoc::GethDocWnd     DOC.H
//
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP COleInPlaceActiveObject::GetWindow  ( HWND FAR* lphwnd)
{
OutputDebugString("In COleInPlaceActiveObject::GetWindow\r\n");
// need to NULL the out parameter
*lphwnd = m_lpObj->m_lpDoc->GethDocWnd();
return  S_OK ;
};

//**********************************************************************
//
// COleInPlaceActiveObject::ContextSensitiveHelp
//
// Purpose:
//
//      Used to implement Context Sensitive help
//
// Parameters:
//
//      None
//
// Return Value:
//
//      E_NOTIMPL
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
//
// Comments:
//
//      See TECHNOTES.WRI include with the OLE SDK for proper
//      implementation of this function.
//
//********************************************************************

STDMETHODIMP COleInPlaceActiveObject::ContextSensitiveHelp  ( BOOL fEnterMode )
{
OutputDebugString("In COleInPlaceActiveObject::ContextSensitiveHelp\r\n");
return  E_NOTIMPL;
};

//**********************************************************************
//
// COleInPlaceActiveObject::TranslateAccelerator
//
// Purpose:
//
//      Used for translating accelerators in .DLL objects.
//
// Parameters:
//
//      LPMSG lpmsg - Pointer to a message
//
// Return Value:
//
//      S_FALSE
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
//
// Comments:
//
//      This method should never be called since we are implemented
//      in an executable.
//
//********************************************************************

STDMETHODIMP COleInPlaceActiveObject::TranslateAccelerator  ( LPMSG lpmsg)
{
OutputDebugString("In COleInPlaceActiveObject::TranslateAccelerator\r\n");
// no accelerator table, return FALSE
return  S_FALSE ;
};

//**********************************************************************
//
// COleInPlaceActiveObject::ResizeBorder
//
// Purpose:
//
//      Called when the border changes size.
//
// Parameters:
//
//      LPCRECT lprectBorder                - New Border
//
//      LPOLEINPLACEUIWINDOW lpUIWindow     - Pointer to UIWindow
//
//      BOOL fFrameWindow                   - True if lpUIWindow is the
//                                            frame window.
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
//
// Comments:
//
//      Need to call SetBorderSpace again...
//
//********************************************************************

STDMETHODIMP COleInPlaceActiveObject::ResizeBorder  ( LPCRECT lprectBorder,
  LPOLEINPLACEUIWINDOW lpUIWindow,
  BOOL fFrameWindow)
{
OutputDebugString("In COleInPlaceActiveObject::ResizeBorder\r\n");

// should always have an inplace frame...
m_lpObj->GetInPlaceFrame()->SetBorderSpace(NULL);

// There will only be a UIWindow if in an MDI container
if (m_lpObj->GetUIWindow())
m_lpObj->GetUIWindow()->SetBorderSpace(NULL);

return  S_OK ;
};

//**********************************************************************
//
// COleInPlaceActiveObject::EnableModeless
//
// Purpose:
//
//      Called to enable/disable modeless dialogs.
//
// Parameters:
//
//      BOOL fEnable    - TRUE to enable, FALSE to disable
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
//
// Comments:
//
//      Called by the container when a model dialog box is added/removed
//      from the screen.  The appropriate action for a server application
//      is to disable/enable any modeless dialogs currently being displayed.
//      Since this application doesn't display any modeless dialogs,
//      this method is essentially ignored.
//
//********************************************************************

STDMETHODIMP COleInPlaceActiveObject::EnableModeless  ( BOOL fEnable)
{
OutputDebugString("In COleInPlaceActiveObject::EnableModeless\r\n");
return  S_OK ;
};


IOIPAO.H

//**********************************************************************
// File name: IOIPAO.H
//
//      Definition of COleInPlaceActiveObject
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#if !defined( _IOIPAO_H_)
#define _IOIPAO_H_

#include <ole2.h>
#include "obj.h"

class CSimpSvrObj;

interface COleInPlaceActiveObject : public IOleInPlaceActiveObject
{
private:
CSimpSvrObj FAR * m_lpObj;
int m_nCount;

public:
COleInPlaceActiveObject::COleInPlaceActiveObject(CSimpSvrObj FAR * lpSimpSvrObj)
{
m_lpObj = lpSimpSvrObj;     // set up the back ptr
m_nCount = 0;            // clear the ref count.
};
COleInPlaceActiveObject::~COleInPlaceActiveObject() {};   // destructor

// IUnknown Methods

STDMETHODIMP QueryInterface (REFIID riid, LPVOID FAR* ppvObj);
STDMETHODIMP_(ULONG) AddRef ();
STDMETHODIMP_(ULONG) Release ();

STDMETHODIMP OnDocWindowActivate  ( BOOL fActivate) ;
STDMETHODIMP OnFrameWindowActivate  ( BOOL fActivate) ;
STDMETHODIMP GetWindow  ( HWND FAR* lphwnd);
STDMETHODIMP ContextSensitiveHelp  ( BOOL fEnterMode);
STDMETHODIMP TranslateAccelerator  ( LPMSG lpmsg);
STDMETHODIMP ResizeBorder  ( LPCRECT lprectBorder,
 LPOLEINPLACEUIWINDOW lpUIWindow,
 BOOL fFrameWindow);
STDMETHODIMP EnableModeless  ( BOOL fEnable);

};

#endif


IOIPO.CPP

//**********************************************************************
// File name: IOIPO.CPP
//
//    Implementation file for the CClassFactory Class
//
// Functions:
//
//    See ioipo.h for a list of member functions.
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "obj.h"
#include "ioipo.h"
#include "app.h"
#include "doc.h"
#include "math.h"

//**********************************************************************
//
// COleInPlaceObject::QueryInterface
//
// Purpose:
//
//
// Parameters:
//
//      REFIID riid         -   Interface being queried for.
//
//      LPVOID FAR *ppvObj  -   Out pointer for the interface.
//
// Return Value:
//
//      S_OK            - Success
//      E_NOINTERFACE   - Failure
//
// Function Calls:
//      Function                    Location
//
//      CSimpSvrObj::QueryInterface OBJ.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP COleInPlaceObject::QueryInterface ( REFIID riid, LPVOID FAR* ppvObj)
{
        OutputDebugString("In COleInPlaceObject::QueryInterface\r\n");
        // need to NULL the out parameter
        *ppvObj = NULL;
        return m_lpObj->QueryInterface(riid, ppvObj);
}

//**********************************************************************
//
// COleInPlaceObject::AddRef
//
// Purpose:
//
//      Increments the reference count on COleInPlaceObject and the "object"
//      object.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      The Reference count on the Object
//
// Function Calls:
//      Function                    Location
//
//      OuputDebugString            Windows API
//      CSimpSvrObj::AddRef         OBJ.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP_(ULONG) COleInPlaceObject::AddRef ()
{
        OutputDebugString("In COleInPlaceObject::AddRef\r\n");
        ++m_nCount;
        return m_lpObj->AddRef();
}

//**********************************************************************
//
// COleInPlaceObject::Release
//
// Purpose:
//
//      Decrements the reference count of COleInPlaceObject and the
//      object.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      The new reference count
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpSvrObj::Release        OBJ.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP_(ULONG) COleInPlaceObject::Release ()
{
        OutputDebugString("In COleInPlaceObject::Release\r\n");
        --m_nCount;
        return m_lpObj->Release();
}

//**********************************************************************
//
// COleInPlaceObject::InPlaceDeactivate
//
// Purpose:
//
//      Called to deactivat the object
//
// Parameters:
//
//      None
//
// Return Value:
//
//
//
// Function Calls:
//      Function                                Location
//
//      OutputDebugString                       Windows API
//      IOleClientSite::QueryInterface          Container
//      IOleInPlaceSite::OnInPlaceDeactivate    Container
//      IOleInPlaceSite::Release                Container
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP COleInPlaceObject::InPlaceDeactivate()
{
         OutputDebugString("In COleInPlaceObject::InPlaceDeactivate\r\n");

         // if not inplace active, return NOERROR
         if (!m_lpObj->m_fInPlaceActive)
                 return NOERROR;

         // clear inplace flag
         m_lpObj->m_fInPlaceActive = FALSE;

         // deactivate the UI
         m_lpObj->DeactivateUI();
         m_lpObj->DoInPlaceHide();

         // tell the container that we are deactivating.
         if (m_lpObj->m_lpIPSite)
                 {
                 m_lpObj->m_lpIPSite->OnInPlaceDeactivate();
                 m_lpObj->m_lpIPSite->Release();
                 m_lpObj->m_lpIPSite =NULL;
                 }

        return S_OK;
}

//**********************************************************************
//
// COleInPlaceObject::UIDeactivate
//
// Purpose:
//
//      Instructs us to remove our UI.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      NOERROR
//
// Function Calls:
//      Function                                Location
//
//      OutputDebugString                       Windows API
//      IOleInPlaceUIWindow::SetActiveObject    Container
//      IOleInPlaceFrame::SetActiveObject       Container
//      IOleClientSite::QueryInterface          Container
//      IOleInPlaceSite::OnUIDeactivate         Container
//      IOleInPlaceSite::Release                Container
//      CSimpSvrObj::DoInPlaceHide              OBJ.H
//      IDataAdviseHolder::SendOnDataChange     OLE
//
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP COleInPlaceObject::UIDeactivate()
{
        OutputDebugString("In COleInPlaceObject::UIDeactivate\r\n");

        m_lpObj->DeactivateUI();

        return  S_OK;
}

//**********************************************************************
//
// COleInPlaceObject::SetObjectRects
//
// Purpose:
//
//      Called when the container clipping region or the object position
//      changes.
//
// Parameters:
//
//      LPCRECT lprcPosRect     - New Position Rect.
//
//      LPCRECT lprcClipRect    - New Clipping Rect.
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      IntersectRect               Windows API
//      OffsetRect                  Windows API
//      CopyRect                    Windows API
//      MoveWindow                  Windows API
//      CSimpSvrDoc::GethHatchWnd   DOC.H
//      CSimpSvrDoc::gethDocWnd     DOC.h
//      SetHatchWindowSize          OLESTD
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP COleInPlaceObject::SetObjectRects  ( LPCRECT lprcPosRect, LPCRECT lprcClipRect)
{
        OutputDebugString("In COleInPlaceObject::SetObjectRects\r\n");

        RECT resRect;
        POINT pt;

        // Get the intersection of the clipping rect and the position rect.
        IntersectRect(&resRect, lprcPosRect, lprcClipRect);

        m_lpObj->m_xOffset = abs (resRect.left - lprcPosRect->left);
        m_lpObj->m_yOffset = abs (resRect.top - lprcPosRect->top);

        m_lpObj->m_scale = (float)(lprcPosRect->right - lprcPosRect->left)/m_lpObj->m_size.x;

        if (m_lpObj->m_scale == 0)
                m_lpObj->m_scale = (float) 1.0;

        char szBuffer[255];
        wsprintf(szBuffer,"New Scale %3d\r\n",m_lpObj->m_scale);
        OutputDebugString(szBuffer);

        // Adjust the size of the Hatch Window.
        SetHatchWindowSize(m_lpObj->m_lpDoc->GethHatchWnd(),(LPRECT) lprcPosRect, (LPRECT) lprcClipRect, &pt);

        // offset the rect
        OffsetRect(&resRect, pt.x, pt.y);

        CopyRect(&m_lpObj->m_posRect, lprcPosRect);

        // Move the actual object window
        MoveWindow(m_lpObj->m_lpDoc->GethDocWnd(),
                                   resRect.left,
                                   resRect.top,
                                   resRect.right - resRect.left,
                                   resRect.bottom - resRect.top,
                                   TRUE);

        return  S_OK ;
};

//**********************************************************************
//
// COleInPlaceObject::GetWindow
//
// Purpose:
//
//      Returns the Window handle of the inplace object
//
// Parameters:
//
//      HWND FAR* lphwnd    - Out pointer in which to return the window
//                            Handle.
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpleDoc::GethDocWnd      DOC.H
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP COleInPlaceObject::GetWindow  ( HWND FAR* lphwnd)
{
        OutputDebugString("In COleInPlaceObject::GetWindow\r\n");
        *lphwnd = m_lpObj->m_lpDoc->GethDocWnd();

        return  S_OK ;
};

//**********************************************************************
//
// COleInPlaceObject::ContextSensitiveHelp
//
// Purpose:
//
//      Used in performing Context Sensitive Help
//
// Parameters:
//
//      BOOL fEnterMode     - Flag to determine if enter or exiting
//                            Context Sensitive Help.
//
// Return Value:
//
//      E_NOTIMPL
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      This function is not implemented due to the fact that it is
//      beyond the scope of a simple object.  All *real* applications
//      are going to want to implement this function, otherwise any
//      container that supports context sensitive help will not work
//      properly while the object is in place.
//
//      See TECHNOTES.WRI include with the OLE SDK for details on
//      Implementing this method.
//
//********************************************************************

STDMETHODIMP COleInPlaceObject::ContextSensitiveHelp  ( BOOL fEnterMode)
{
        OutputDebugString("In COleInPlaceObject::ContextSensitiveHelp\r\n");
        return  E_NOTIMPL;
};

//**********************************************************************
//
// COleInPlaceObject::ReactivateAndUndo
//
// Purpose:
//
//      Called when the container wants to undo the last edit made in
//      the object.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      INPLACE_E_NOTUNDOABLE
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Since this server does not support undo, the value
//      INPLACE_E_NOTUNDOABLE is always returned.
//
//********************************************************************

STDMETHODIMP COleInPlaceObject::ReactivateAndUndo  ()
{
        OutputDebugString("In COleInPlaceObject::ReactivateAndUndo\r\n");
        return  INPLACE_E_NOTUNDOABLE ;
};


IOIPO.H

//**********************************************************************
// File name: ioipo.h
//
//      Definition of COleInPlaceObject
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#if !defined( _IOIPO_H_)
#define _IOIPO_H_

#include <ole2.h>
#include "obj.h"

class CSimpSvrObj;

interface COleInPlaceObject : public IOleInPlaceObject
{
private:
CSimpSvrObj FAR * m_lpObj;
int m_nCount;

public:
COleInPlaceObject::COleInPlaceObject(CSimpSvrObj FAR * lpSimpSvrObj)
{
m_lpObj = lpSimpSvrObj;
m_nCount = 0;
};
COleInPlaceObject::~COleInPlaceObject() {};

//  IUnknown Methods
STDMETHODIMP QueryInterface (REFIID riid, LPVOID FAR* ppvObj);
STDMETHODIMP_(ULONG) AddRef ();
STDMETHODIMP_(ULONG) Release ();

STDMETHODIMP InPlaceDeactivate  ();
STDMETHODIMP UIDeactivate  () ;
STDMETHODIMP SetObjectRects  ( LPCRECT lprcPosRect, LPCRECT lprcClipRect);
STDMETHODIMP GetWindow  ( HWND FAR* lphwnd) ;
STDMETHODIMP ContextSensitiveHelp  ( BOOL fEnterMode);
STDMETHODIMP ReactivateAndUndo  ();
};

#endif


IOO.CPP

//**********************************************************************
// File name: IOO.CPP
//
//    Implementation file for the COleObject Class
//
// Functions:
//
//    See ioo.h for a list of member functions.
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "obj.h"
#include "ioo.h"
#include "app.h"
#include "doc.h"

#define VERB_OPEN 1

//**********************************************************************
//
// COleObject::QueryInterface
//
// Purpose:
//
//
// Parameters:
//
//      REFIID riid         -   Interface being queried for.
//
//      LPVOID FAR *ppvObj  -   Out pointer for the interface.
//
// Return Value:
//
//      S_OK            - Success
//      E_NOINTERFACE   - Failure
//
// Function Calls:
//      Function                    Location
//
//      CSimpSvrObj::QueryInterface OBJ.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP COleObject::QueryInterface ( REFIID riid, LPVOID FAR* ppvObj)
{
        OutputDebugString("In COleObject::QueryInterface\r\n");
        return m_lpObj->QueryInterface(riid, ppvObj);
}

//**********************************************************************
//
// COleObject::AddRef
//
// Purpose:
//
//      Increments the reference count on COleObject and the "object"
//      object.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      The Reference count on the Object.
//
// Function Calls:
//      Function                    Location
//
//      OuputDebugString            Windows API
//      CSimpSvrObj::AddRef         OBJ.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP_(ULONG) COleObject::AddRef ()
{
        OutputDebugString("In COleObject::AddRef\r\n");
        ++m_nCount;
        return m_lpObj->AddRef();
}

//**********************************************************************
//
// COleObject::Release
//
// Purpose:
//
//      Decrements the reference count of COleObject and the
//      "object" object.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      The new reference count
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpSvrObj::Release        OBJ.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP_(ULONG) COleObject::Release ()
{
        OutputDebugString("In COleObject::Release\r\n");
        --m_nCount;
        return m_lpObj->Release();
}

//**********************************************************************
//
// COleObject::SetClientSite
//
// Purpose:
//
//      Called to notify the object of it's client site.
//
// Parameters:
//
//      LPOLECLIENTSITE pClientSite     - ptr to new client site
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      IOleClientSite::Release     Container
//      IOleClientSite::AddRef      Container
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP COleObject::SetClientSite  ( LPOLECLIENTSITE pClientSite)
{
        OutputDebugString("In COleObject::SetClientSite\r\n");

        // if we already have a client site, release it.
        if (m_lpObj->m_lpOleClientSite)
                {
                m_lpObj->m_lpOleClientSite->Release();
                m_lpObj->m_lpOleClientSite = NULL;
                }

        // store copy of the client site.
        m_lpObj->m_lpOleClientSite = pClientSite;

        // AddRef it so it doesn't go away.
        if (m_lpObj->m_lpOleClientSite)
                m_lpObj->m_lpOleClientSite->AddRef();

        return S_OK;
}

//**********************************************************************
//
// COleObject::Advise
//
// Purpose:
//
//      Called to set up an advise on the OLE object.
//
// Parameters:
//
//      LPADVISESINK pAdvSink       - ptr to the Advise Sink for notification
//
//      DWORD FAR* pdwConnection    - place to return the connection ID.
//
// Return Value:
//
//      Passed back from IOleAdviseHolder::Advise.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CreateOleAdviseHolder       OLE API
//      IOleAdviseHolder::Advise    OLE
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP COleObject::Advise ( LPADVISESINK pAdvSink, DWORD FAR* pdwConnection)
{
        OutputDebugString("In COleObject::Advise\r\n");

        // if we haven't made an OleAdviseHolder yet, make one.
        if (!m_lpObj->m_lpOleAdviseHolder)
                CreateOleAdviseHolder(&m_lpObj->m_lpOleAdviseHolder);

        // pass this call onto the OleAdviseHolder.
        return m_lpObj->m_lpOleAdviseHolder->Advise(pAdvSink, pdwConnection);
}

//**********************************************************************
//
// COleObject::SetHostNames
//
// Purpose:
//
//      Called to pass strings for Window titles.
//
// Parameters:
//
//      LPCSTR szContainerApp   -   ptr to string describing Container App
//
//      LPCSTR szContainerObj   -   ptr to string describing Object
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      This routine is called so that the server application can
//      set the window title appropriately.
//
//********************************************************************

//@@WTK WIN32, UNICODE
//STDMETHODIMP COleObject::SetHostNames  ( LPCSTR szContainerApp, LPCSTR szContainerObj)
STDMETHODIMP COleObject::SetHostNames  ( LPCOLESTR szContainerApp, LPCOLESTR szContainerObj)
{
        OutputDebugString("In COleObject::SetHostNames\r\n");

        return  S_OK;
};

//**********************************************************************
//
// COleObject::DoVerb
//
// Purpose:
//
//      Called by the container application to invoke a verb.
//
// Parameters:
//
//      LONG iVerb                  - The value of the verb to be
//                                    invoked.
//
//      LPMSG lpmsg                 - The message that caused the
//                                    verb to be invoked.
//
//      LPOLECLIENTSITE pActiveSite - Ptr to the active client site.
//
//      LONG lindex                 - Used in extended layout
//
//      HWND hwndParent             - This should be the window handle of
//                                    the window in which we are contained.
//                                    This value could be used to "fake"
//                                    inplace activation in a manner similar
//                                    to Video for Windows in OLE 1.0.
//
//      LPCRECT lprcPosRect         - The rectangle that contains the object
//                                    within hwndParent.  Also used to
//                                    "fake" inplace activation.
//
// Return Value:
//
//      OLE_E_NOTINPLACEACTIVE      - Returned if attempted to undo while not
//                                    inplace active.
//      S_OK
//
// Function Calls:
//      Function                            Location
//
//      OutputDebugString                   Windows API
//      ShowWindow                          Windows API
//      CSimpSvrObj::DoInPlaceActivate      OBJ.CPP
//      CSimpSvrObj::DoInPlaceHide          OBJ.CPP
//      COleObject::OpenEdit                IOO.CPP
//      CSimpSvrDoc::GethDocWnd             DOC.H
//      COleInPlaceObj::InPlaceDeactivate   IOIPO.CPP
//
// Comments:
//
//      Be sure to look at TECHNOTES.WRI included with the OLE
//      SDK for a description of handling the inplace verbs
//      properly.
//
//********************************************************************

STDMETHODIMP COleObject::DoVerb  (  LONG iVerb,
                                                                        LPMSG lpmsg,
                                                                        LPOLECLIENTSITE pActiveSite,
                                                                        LONG lindex,
                                                                        HWND hwndParent,
                                                                        LPCRECT lprcPosRect)
{
        OutputDebugString("In COleObject::DoVerb\r\n");

        switch (iVerb)
                {
                case OLEIVERB_SHOW:
                case OLEIVERB_PRIMARY:
                        if (m_fOpen)
                                SetFocus(m_lpObj->m_lpDoc->GethAppWnd());
                        else if (m_lpObj->DoInPlaceActivate(iVerb) == FALSE)
                                OpenEdit(pActiveSite);
                        break;

                case OLEIVERB_UIACTIVATE:
                        if (m_fOpen)
                                return  E_FAIL;

                        // inplace activate
                        if (!m_lpObj->DoInPlaceActivate(iVerb))
                                return  E_FAIL;
                        break;

                case OLEIVERB_DISCARDUNDOSTATE:
                        // don't have to worry about this situation as we don't
                        // support an undo state.
                        if (!m_lpObj->m_fInPlaceActive)
                                return OLE_E_NOT_INPLACEACTIVE;
                        break;

                case OLEIVERB_HIDE:
                        // if inplace active, do an "inplace" hide, otherwise
                        // just hide the app window.
                        if (m_lpObj->m_fInPlaceActive)
                                {
                                m_lpObj->DeactivateUI();
                                m_lpObj->DoInPlaceHide();
                                }
                        else
                                m_lpObj->m_lpDoc->GetApp()->HideAppWnd();
                        break;

                case OLEIVERB_OPEN:
                case VERB_OPEN:
                        // if inplace active, deactivate
                        if (m_lpObj->m_fInPlaceActive)
                                m_lpObj->m_OleInPlaceObject.InPlaceDeactivate();

                        // open into another window.
                        OpenEdit(pActiveSite);
                        break;

                default:
                        if (iVerb < 0)
                                return E_FAIL;
                }

        return  S_OK;
};

//**********************************************************************
//
// COleObject::GetExtent
//
// Purpose:
//
//      Returns the extent of the object.
//
// Parameters:
//
//      DWORD dwDrawAspect  - The aspect in which to get the size.
//
//      LPSIZEL lpsizel     - Out ptr to return the size.
//
// Return Value:
//
//
//
// Function Calls:
//      Function                        Location
//
//      OutputDebugString               Windows API
//      XformWidthInPixelsToHimetric    OLESTD
//      XformHeightInPixelsToHimetric   OLESTD
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP COleObject::GetExtent  ( DWORD dwDrawAspect, LPSIZEL lpsizel)
{
        OutputDebugString("In COleObject::GetExtent\r\n");

        SCODE sc = E_FAIL;

        // Only DVASPECT_CONTENT is supported....
        if (dwDrawAspect == DVASPECT_CONTENT)
                {
                sc = S_OK;

                // return the correct size in HIMETRIC...
                lpsizel->cx = XformWidthInPixelsToHimetric(NULL, m_lpObj->m_size.x);
                lpsizel->cy = XformHeightInPixelsToHimetric(NULL, m_lpObj->m_size.y);
                }

        return  sc ;
};

//**********************************************************************
//
// COleObject::Update
//
// Purpose:
//
//      Called to get the most up to date data
//
// Parameters:
//
//      None
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                            Location
//
//      OutputDebugString                   Windows API
//      IDataAdviseHolder::SendOnDataChange OLE
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP COleObject::Update()
{
        OutputDebugString("In COleObject::Update\r\n");

        // force an update
        m_lpObj->SendOnDataChange();

        return  S_OK ;
};

//**********************************************************************
//
// COleObject::Close
//
// Purpose:
//
//      Called when the OLE object needs to be closed
//
// Parameters:
//
//      DWORD dwSaveOption  - Flags to instruct the server how to prompt
//                            the user.
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpSvrDoc::Close          DOC.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP COleObject::Close  ( DWORD dwSaveOption)
{
        OutputDebugString("In COleObject::Close\r\n");

        // delegate to the document object.
        m_lpObj->m_lpDoc->Close();

        return  S_OK ;
};

//**********************************************************************
//
// COleObject::Unadvise
//
// Purpose:
//
//      Breaks down an OLE advise that has been set up on this object.
//
// Parameters:
//
//      DWORD dwConnection  - Connection that needs to be broken down
//
// Return Value:
//
//      Passed back from IOleAdviseHolder::Unadvise
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      IOleAdviseHolder::Unadvise  OLE
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP COleObject::Unadvise ( DWORD dwConnection)
{
        OutputDebugString("In COleObject::Unadvise\r\n");

        // pass on to OleAdviseHolder.
        return m_lpObj->m_lpOleAdviseHolder->Unadvise(dwConnection);
};

//**********************************************************************
//
// COleObject::EnumVerbs
//
// Purpose:
//
//      Enumerates the verbs associated with this object.
//
// Parameters:
//
//      LPENUMOLEVERB FAR* ppenumOleVerb    - Out ptr in which to return
//                                            the enumerator
//
// Return Value:
//
//      OLE_S_USEREG    - Instructs OLE to use the verbs found in the
//                        REG DB for this server.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      In a .DLL, an application cannot return OLE_S_USEREG.  This is
//      due to the fact that the default object handler is not being
//      used, and the container is really making direct function calls
//      into the server .DLL.
//
//********************************************************************

STDMETHODIMP COleObject::EnumVerbs  ( LPENUMOLEVERB FAR* ppenumOleVerb)
{
        OutputDebugString("In COleObject::EnumVerbs\r\n");

        return  OLE_S_USEREG ;
};

//**********************************************************************
//
// COleObject::GetClientSite
//
// Purpose:
//
//      Called to get the current client site of the object.
//
// Parameters:
//
//      LPOLECLIENTSITE FAR* ppClientSite   - Out ptr in which to return the
//                                            client site.
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP COleObject::GetClientSite  ( LPOLECLIENTSITE FAR* ppClientSite)
{
        OutputDebugString("In COleObject::GetClientSite\r\n");
        *ppClientSite = m_lpObj->m_lpOleClientSite;
        return  S_OK ;
}

//**********************************************************************
//
// COleObject::SetMoniker
//
// Purpose:
//
//      Used to set the objects moniker
//
// Parameters:
//
//      DWORD dwWhichMoniker    - Type of moniker being set
//
//      LPMONIKER pmk           - Pointer to the moniker
//
// Return Value:
//
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP COleObject::SetMoniker  ( DWORD dwWhichMoniker, LPMONIKER pmk)
{
        OutputDebugString("In COleObject::SetMoniker\r\n");

        LPMONIKER lpmk;

        if (! m_lpObj->GetOleClientSite())
                return  E_FAIL;

        if (m_lpObj->GetOleClientSite()->GetMoniker (OLEGETMONIKER_ONLYIFTHERE, OLEWHICHMK_OBJFULL, &lpmk) != NOERROR)
                return  E_FAIL;

        if (m_lpObj->GetOleAdviseHolder())
                m_lpObj->GetOleAdviseHolder()->SendOnRename(lpmk);

        LPRUNNINGOBJECTTABLE lpRot;

        if (GetRunningObjectTable(0, &lpRot) == NOERROR)
                {
                if (m_lpObj->m_dwRegister)
                        lpRot->Revoke(m_lpObj->m_dwRegister);

                lpRot->Register(0, m_lpObj, lpmk, &m_lpObj->m_dwRegister);

                lpRot->Release();
                }

        return  S_OK ;
};

//**********************************************************************
//
// COleObject::GetMoniker
//
// Purpose:
//
////
// Parameters:
//
//      DWORD dwAssign          - Assignment for the moniker
//
//      DWORD dwWhichMoniker    - Which moniker to return
//
//      LPMONIKER FAR* ppmk     - An out ptr to return the moniker
//
// Return Value:
//
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP COleObject::GetMoniker  (  DWORD dwAssign, DWORD dwWhichMoniker,
                                                                                LPMONIKER FAR* ppmk)
{
        OutputDebugString("In COleObject::GetMoniker\r\n");
        // need to NULL the out parameter
        *ppmk = NULL;

        return m_lpObj->GetOleClientSite()->GetMoniker (OLEGETMONIKER_ONLYIFTHERE, OLEWHICHMK_OBJFULL, ppmk);
};

//**********************************************************************
//
// COleObject::InitFromData
//
// Purpose:
//
//      Initialize the object from the passed pDataObject.
//
// Parameters:
//
//      LPDATAOBJECT pDataObject    - Pointer to data transfer object
//                                    to be used in the initialization
//
//      BOOL fCreation              - TRUE if the object is currently being
//                                    created.
//
//      DWORD dwReserved            - Reserved
//
// Return Value:
//
//      S_FALSE
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      We don't support this functionality, so we will always return
//      error.
//
//********************************************************************

STDMETHODIMP COleObject::InitFromData  ( LPDATAOBJECT pDataObject,
                                                                                BOOL fCreation,
                                                                                DWORD dwReserved)
{
        OutputDebugString("In COleObject::InitFromData\r\n");

        return  S_FALSE ;
};

//**********************************************************************
//
// COleObject::GetClipboardData
//
// Purpose:
//
//      Returns an IDataObject that is the same as doing an OleSetClipboard
//
// Parameters:
//
//      DWORD dwReserved                - Reserved
//
//      LPDATAOBJECT FAR* ppDataObject  - Out ptr for the Data Object.
//
// Return Value:
//
//      OLE_E_NOTSUPPORTED
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Support of this method is optional.
//
//********************************************************************

STDMETHODIMP COleObject::GetClipboardData  ( DWORD dwReserved,
                                                                                LPDATAOBJECT FAR* ppDataObject)
{
        OutputDebugString("In COleObject::GetClipboardData\r\n");
        // NULL the out ptr
        *ppDataObject = NULL;
        return  E_NOTIMPL ;
};

//**********************************************************************
//
// COleObject::IsUpToDate
//
// Purpose:
//
//      Determines if an object is up to date
//
// Parameters:
//
//      None
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Our embedded object is always up to date.  This function is
//      particularly useful in linking situations.
//
//********************************************************************

STDMETHODIMP COleObject::IsUpToDate()
{
        OutputDebugString("In COleObject::IsUpToDate\r\n");
        return  S_OK ;
};

//**********************************************************************
//
// COleObject::GetUserClassID
//
// Purpose:
//
//      Returns the applications CLSID
//
// Parameters:
//
//      CLSID FAR* pClsid   - Out ptr to return the CLSID
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CPersistStorage::GetClassID IPS.CPP
//
// Comments:
//
//      This function is just delegated to IPS::GetClassID.
//
//********************************************************************

STDMETHODIMP COleObject::GetUserClassID  ( CLSID FAR* pClsid)
{
        OutputDebugString("In COleObject::GetUserClassID\r\n");

        m_lpObj->m_PersistStorage.GetClassID(pClsid);
        return  S_OK ;
};

//**********************************************************************
//
// COleObject::GetUserType
//
// Purpose:
//
//      Used to get a user presentable id for this object
//
// Parameters:
//
//      DWORD dwFormOfType      - The ID requested
//
//      LPSTR FAR* pszUserType  - Out ptr to return the string
//
// Return Value:
//
//      OLE_S_USEREG    - Use the reg db to get these entries.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//
//********************************************************************

//@@WTK WIN32, UNICODE
//STDMETHODIMP COleObject::GetUserType  ( DWORD dwFormOfType, LPSTR FAR* pszUserType)
STDMETHODIMP COleObject::GetUserType  ( DWORD dwFormOfType, LPOLESTR FAR* pszUserType)
{
        OutputDebugString("In COleObject::GetUserType\r\n");

        return  OLE_S_USEREG ;
};

//**********************************************************************
//
// COleObject::SetExtent
//
// Purpose:
//
//      Called to set the extent of the object.
//
// Parameters:
//
//      DWORD dwDrawAspect  - Aspect to have its size set
//
//      LPSIZEL lpsizel     - New size of the object.
//
// Return Value:
//
//      E_NOTIMPL   - This function is not curently implemented.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP COleObject::SetExtent  ( DWORD dwDrawAspect, LPSIZEL lpsizel)
{
        OutputDebugString("In COleObject::SetExtent\r\n");
        return  E_NOTIMPL;
};

//**********************************************************************
//
// COleObject::EnumAdvise
//
// Purpose:
//
//      Returns an enumerate which enumerates the outstanding advises
//      associated with this OLE object.
//
// Parameters:
//
//      LPENUMSTATDATA FAR* ppenumAdvise - Out ptr in which to return
//                                         the enumerator.
//
// Return Value:
//
//      Passed on from IOleAdviseHolder::EnumAdvise.
//
// Function Calls:
//      Function                        Location
//
//      OutputDebugString               Windows API
//      IOleAdviseHolder::EnumAdvise    OLE
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP COleObject::EnumAdvise  ( LPENUMSTATDATA FAR* ppenumAdvise)
{
        OutputDebugString("In COleObject::EnumAdvise\r\n");
        // need to NULL the out parameter
        *ppenumAdvise = NULL;

        // pass on to the OLE Advise holder.
        return m_lpObj->m_lpOleAdviseHolder->EnumAdvise(ppenumAdvise);
};

//**********************************************************************
//
// COleObject::GetMiscStatus
//
// Purpose:
//
//      Return status information about the object
//
// Parameters:
//
//      DWORD dwAspect          - Aspect interested in.
//
//      DWORD FAR* pdwStatus    - Out ptr in which to return the bits.
//
// Return Value:
//
//      CO_E_READREGDB  - Tell the library to use the reg DB.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP COleObject::GetMiscStatus  ( DWORD dwAspect, DWORD FAR* pdwStatus)
{
        OutputDebugString("In COleObject::GetMiscStatus\r\n");
        // need to NULL the out parameter
        *pdwStatus = NULL;
        return  OLE_S_USEREG ;
};

//**********************************************************************
//
// COleObject::SetColorScheme
//
// Purpose:
//
//      Used to set the palette for the object to use.
//
// Parameters:
//
//      LPLOGPALETTE lpLogpal   - Pointer to the LOGPALETTE to be used.
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      This server ignores this method.
//
//********************************************************************

STDMETHODIMP COleObject::SetColorScheme  ( LPLOGPALETTE lpLogpal)
{
        OutputDebugString("In COleObject::SetColorScheme\r\n");
        return  S_OK ;
};

//**********************************************************************
//
// COleObject::OpenEdit
//
// Purpose:
//
//      Used to Open the object into a seperate window.
//
// Parameters:
//
//      LPOLECLIENTSITE pActiveSite - Pointer to the Active clientsite.
//
// Return Value:
//
//      None.
//
// Function Calls:
//      Function                        Location
//
//      IOleClientSite::OnShowWindow    Container
//      ShowWindow                      Windows API
//      UpdateWindow                    Windows API
//      OutputDebugString               Windows API
//      CSimpSvrDoc::GethAppWnd         DOC.H
//      CSimpSvrDoc::GethHatchWnd       DOC.H
//
// Comments:
//
//
//********************************************************************

void COleObject::OpenEdit(LPOLECLIENTSITE pActiveSite)
{
   if (m_lpObj->GetOleClientSite())
           m_lpObj->GetOleClientSite()->ShowObject();

        m_fOpen = TRUE;

        // tell the site we are opening so the object can be hatched out.
        if (m_lpObj->GetOleClientSite())
                m_lpObj->GetOleClientSite()->OnShowWindow(TRUE);

        m_lpObj->m_lpDoc->ShowDocWnd();

        m_lpObj->m_lpDoc->HideHatchWnd();

        // Show app window.
        m_lpObj->m_lpDoc->GetApp()->ShowAppWnd();

        SetFocus(m_lpObj->m_lpDoc->GethAppWnd());
}


IOO.H

//**********************************************************************
// File name: ioo.h
//
//      Definition of COleObject
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************
#if !defined( _IOO_H_)
#define _IOO_H_

#include <ole2.h>
#include "obj.h"

class CSimpSvrObj;

interface COleObject : public IOleObject
{
private:
CSimpSvrObj FAR * m_lpObj;
int m_nCount;
BOOL m_fOpen;

public:
COleObject::COleObject(CSimpSvrObj FAR * lpSimpSvrObj)
{
m_lpObj = lpSimpSvrObj;
m_nCount = 0;
m_fOpen = FALSE;
};
COleObject::~COleObject()
{
};
STDMETHODIMP QueryInterface (REFIID riid, LPVOID FAR* ppvObj);
STDMETHODIMP_(ULONG) AddRef ();
STDMETHODIMP_(ULONG) Release ();

STDMETHODIMP SetClientSite (LPOLECLIENTSITE pClientSite);
STDMETHODIMP Advise (LPADVISESINK pAdvSink, DWORD FAR* pdwConnection);
//@@WTK WIN32, UNICODE
//STDMETHODIMP SetHostNames  ( LPCSTR szContainerApp, LPCSTR szContainerObj);
STDMETHODIMP SetHostNames  ( LPCOLESTR szContainerApp, LPCOLESTR szContainerObj);
STDMETHODIMP DoVerb  (  LONG iVerb,
LPMSG lpmsg,
LPOLECLIENTSITE pActiveSite,
LONG lindex,
HWND hwndParent,
LPCRECT lprcPosRect);
STDMETHODIMP GetExtent  ( DWORD dwDrawAspect, LPSIZEL lpsizel);
STDMETHODIMP Update  () ;
STDMETHODIMP Close  ( DWORD dwSaveOption) ;
STDMETHODIMP Unadvise ( DWORD dwConnection);
STDMETHODIMP EnumVerbs  ( LPENUMOLEVERB FAR* ppenumOleVerb) ;
STDMETHODIMP GetClientSite  ( LPOLECLIENTSITE FAR* ppClientSite);
STDMETHODIMP SetMoniker  ( DWORD dwWhichMoniker, LPMONIKER pmk);
STDMETHODIMP GetMoniker  ( DWORD dwAssign, DWORD dwWhichMoniker,
   LPMONIKER FAR* ppmk);
STDMETHODIMP InitFromData  ( LPDATAOBJECT pDataObject,
 BOOL fCreation,
 DWORD dwReserved);
STDMETHODIMP GetClipboardData  ( DWORD dwReserved,
 LPDATAOBJECT FAR* ppDataObject);
STDMETHODIMP IsUpToDate  ();
STDMETHODIMP GetUserClassID  ( CLSID FAR* pClsid);
//@@WTK WIN32, UNICODE
//STDMETHODIMP GetUserType  ( DWORD dwFormOfType, LPSTR FAR* pszUserType);
STDMETHODIMP GetUserType  ( DWORD dwFormOfType, LPOLESTR FAR* pszUserType);
STDMETHODIMP SetExtent  ( DWORD dwDrawAspect, LPSIZEL lpsizel);
STDMETHODIMP EnumAdvise  ( LPENUMSTATDATA FAR* ppenumAdvise);
STDMETHODIMP GetMiscStatus  ( DWORD dwAspect, DWORD FAR* pdwStatus);
STDMETHODIMP SetColorScheme  ( LPLOGPALETTE lpLogpal);

void OpenEdit(LPOLECLIENTSITE pActiveSite);

};

#endif


IPS.CPP

//**********************************************************************
// File name: ips.cpp
//
//    Implementation file for the CSimpSvrApp Class
//
// Functions:
//
//    See ips.h for a list of member functions.
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "obj.h"
#include "ips.h"
#include "app.h"
#include "doc.h"

//@@WTK WIN32, UNICODE
//#include <storage.h>

DEFINE_GUID(GUID_SIMPLE, 0xBCF6D4A0, 0xBE8C, 0x1068, 0xB6, 0xD4, 0x00, 0xDD, 0x01, 0x0C, 0x05, 0x09);

//**********************************************************************
//
// CPersistStorage::QueryInterface
//
// Purpose:
//
//
// Parameters:
//
//      REFIID riid         -   Interface being queried for.
//
//      LPVOID FAR *ppvObj  -   Out pointer for the interface.
//
// Return Value:
//
//      S_OK            - Success
//      E_NOINTERFACE   - Failure
//
// Function Calls:
//      Function                    Location
//
//      CSimpSvrObj::QueryInterface OBJ.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP CPersistStorage::QueryInterface ( REFIID riid, LPVOID FAR* ppvObj)
{
OutputDebugString("In CPersistStorage::QueryInterface\r\n");
// need to NULL the out parameter
return m_lpObj->QueryInterface(riid, ppvObj);
};

//**********************************************************************
//
// CPersistStorage::AddRef
//
// Purpose:
//
//      Increments the reference count on CPersistStorage and the "object"
//      object.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      The Reference count on the Object.
//
// Function Calls:
//      Function                    Location
//
//      OuputDebugString            Windows API
//      CSimpSvrObj::AddRef         OBJ.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP_(ULONG) CPersistStorage::AddRef ()
{
OutputDebugString("In CPersistStorage::AddRef\r\n");
++m_nCount;
return m_lpObj->AddRef();
};

//**********************************************************************
//
// CPersistStorage::Release
//
// Purpose:
//
//      Decrements the reference count of CPersistStorage and the
//      "object" object.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      The new reference count
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CSimpSvrObj::Release        OBJ.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP_(ULONG) CPersistStorage::Release ()
{
OutputDebugString("In CPersistStorage::Release\r\n");
--m_nCount;
return m_lpObj->Release();
};

//**********************************************************************
//
// CPersistStorage::InitNew
//
// Purpose:
//
//      Used to give a new OLE object a ptr to its storage.
//
// Parameters:
//
//      LPSTORAGE pStg  - Pointer to the storage
//
// Return Value:
//
//
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      IStorage::Release           OLE
//      IStorage::AddRef            OLE
//
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP CPersistStorage::InitNew (LPSTORAGE pStg)
{
OutputDebugString("In CPersistStorage::InitNew\r\n");

// release any streams and storages that may be open
ReleaseStreamsAndStorage();

m_lpObj->m_lpStorage = pStg;

// AddRef the new Storage
if (m_lpObj->m_lpStorage)
m_lpObj->m_lpStorage->AddRef();

CreateStreams(m_lpObj->m_lpStorage);

return S_OK;
}

//**********************************************************************
//
// CPersistStorage::GetClassID
//
// Purpose:
//
//      Returns the CLSID of this object.
//
// Parameters:
//
//      LPCLSID lpClassID   - Out ptr in which to return the CLSID
//
// Return Value:
//
//       S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP CPersistStorage::GetClassID  ( LPCLSID lpClassID)
{
OutputDebugString("In CPersistStorage::GetClassID\r\n");

*lpClassID = GUID_SIMPLE;

return  S_OK ;
};

//**********************************************************************
//
// CPersistStorage::Save
//
// Purpose:
//
//      Instructs the object to save itself into the storage.
//
// Parameters:
//
//      LPSTORAGE pStgSave  - Storage in which the object should be saved
//
//      BOOL fSameAsLoad    - TRUE if pStgSave is the same as the storage
//                            that the object was originally created with.
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CPersistStorage::InitNew    IPS.CPP
//      CSimpSvrObj::SaveToStorage  OBJ.CPP
//
//
// Comments:
//
//      A real app will want better error checking in this method.
//
//********************************************************************

STDMETHODIMP CPersistStorage::Save  ( LPSTORAGE pStgSave, BOOL fSameAsLoad)
{
OutputDebugString("In CPersistStorage::Save\r\n");

// save the data
m_lpObj->SaveToStorage (pStgSave, fSameAsLoad);

m_lpObj->m_fSaveWithSameAsLoad = fSameAsLoad;
m_lpObj->m_fNoScribbleMode = TRUE;

return  S_OK ;
};

//**********************************************************************
//
// CPersistStorage::SaveCompleted
//
// Purpose:
//
//      Called when the container is finished saving the object
//
// Parameters:
//
//      LPSTORAGE pStgNew   - ptr to the new storage
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP CPersistStorage::SaveCompleted  ( LPSTORAGE pStgNew)
{
OutputDebugString("In CPersistStorage::SaveCompleted\r\n");

if (pStgNew)
{
ReleaseStreamsAndStorage();
m_lpObj->m_lpStorage = pStgNew;
m_lpObj->m_lpStorage->AddRef();
OpenStreams(pStgNew);
}

/* OLE2NOTE: it is only legal to perform a Save or SaveAs operation
**    on an embedded object. if the document is a file-based document
**    then we can not be changed to a IStorage-base object.
**
**      fSameAsLoad   lpStgNew     Type of Save     Send OnSave
**    ---------------------------------------------------------
**         TRUE        NULL        SAVE             YES
**         TRUE        ! NULL      SAVE *           YES
**         FALSE       ! NULL      SAVE AS          YES
**         FALSE       NULL        SAVE COPY AS     NO
**
**    * this is a strange case that is possible. it is inefficient
**    for the caller; it would be better to pass lpStgNew==NULL for
**    the Save operation.
*/

if ( pStgNew || m_lpObj->m_fSaveWithSameAsLoad)
{
if (m_lpObj->m_fNoScribbleMode)
m_lpObj->GetOleAdviseHolder()->SendOnSave();  // normally would clear a
  // dirty bit
m_lpObj->m_fSaveWithSameAsLoad = FALSE;
}

m_lpObj->m_fNoScribbleMode = FALSE;

return  S_OK ;
};

//**********************************************************************
//
// CPersistStorage::Load
//
// Purpose:
//
//      Instructs the object to be loaded from storage.
//
// Parameters:
//
//      LPSTORAGE pStg  - Ptr to the storage in which to be loaded
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                        Location
//
//      OutputDebugString               Windows API
//      CSimpSvrObj::LoadFromStorage    OBJ.CPP
//
//
// Comments:
//
//      A real app will want better error checking in this method.
//
//********************************************************************

STDMETHODIMP CPersistStorage::Load  ( LPSTORAGE pStg)
{
OutputDebugString("In CPersistStorage::Load\r\n");

// remember the storage
if (m_lpObj->m_lpStorage)
{
m_lpObj->m_lpStorage->Release();
m_lpObj->m_lpStorage = NULL;
}

m_lpObj->m_lpStorage = pStg;

m_lpObj->m_lpStorage->AddRef();

OpenStreams(m_lpObj->m_lpStorage);

m_lpObj->LoadFromStorage();

return  S_OK ;
};

//**********************************************************************
//
// CPersistStorage::IsDirty
//
// Purpose:
//
//      Returns whether or not the object is dirty w/respect to its
//      Storage
//
// Parameters:
//
//      None
//
// Return Value:
//
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
//
// Comments:
//
//      This sample does not implement this function, although a
//      real application should.
//
//********************************************************************

STDMETHODIMP CPersistStorage::IsDirty()
{
OutputDebugString("In CPersistStorage::IsDirty\r\n");
return  S_OK ;
};

//**********************************************************************
//
// CPersistStorage::HandsOffStorage
//
// Purpose:
//
//      Forces the object to release its handle to its storage.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      IStorage::Release           OLE
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP CPersistStorage::HandsOffStorage  ()
{
OutputDebugString("In CPersistStorage::HandsOffStorage\r\n");

ReleaseStreamsAndStorage();

return  S_OK ;
};

//**********************************************************************
//
// CPersistStorage::CreateStreams
//
// Purpose:
//
//      Creates the streams that are held open for the object's lifetime.
//
// Parameters:
//
//      LPSTORAGE lpStg -   Storage in which to create the streams
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      IStorage::Release           OLE
//      IStream::Release            OLE
//      IStorage::CreateStream      OLE
//
// Comments:
//
//
//********************************************************************

void CPersistStorage::CreateStreams(LPSTORAGE lpStg)
{
if (m_lpObj->m_lpColorStm)
m_lpObj->m_lpColorStm->Release();

if (m_lpObj->m_lpSizeStm)
m_lpObj->m_lpSizeStm->Release();

// create a stream to save the colors
//@@WTK WIN32, UNICODE
//lpStg->CreateStream ( "RGB",
lpStg->CreateStream ( OLESTR("RGB"),
   STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE,
   0,
   0,
   &m_lpObj->m_lpColorStm);

// create a stream to save the size
//@@WTK WIN32, UNICODE
//lpStg->CreateStream ( "size",
lpStg->CreateStream ( OLESTR("size"),
   STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE,
   0,
   0,
   &m_lpObj->m_lpSizeStm);
}

//**********************************************************************
//
// CPersistStorage::OpenStreams
//
// Purpose:
//
//      Opens the streams in a storage.
//
// Parameters:
//
//      LPSTORAGE lpStg -   Storage in which to open the streams.
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      IStorage::Release           OLE
//
// Comments:
//
//
//********************************************************************

void CPersistStorage::OpenStreams(LPSTORAGE lpStg)
{
if (m_lpObj->m_lpColorStm)
m_lpObj->m_lpColorStm->Release();

if (m_lpObj->m_lpSizeStm)
m_lpObj->m_lpSizeStm->Release();

// open the color stream
//@@WTK WIN32, UNICODE
//lpStg->OpenStream ( "RGB",
lpStg->OpenStream ( OLESTR("RGB"),
   0,
   STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
   0,
   &m_lpObj->m_lpColorStm);

// open the color stream
//@@WTK WIN32, UNICODE
//lpStg->OpenStream ( "size",
lpStg->OpenStream ( OLESTR("size"),
   0,
   STGM_READWRITE | STGM_SHARE_EXCLUSIVE,
   0,
   &m_lpObj->m_lpSizeStm);
}

//**********************************************************************
//
// CPersistStorage::ReleaseStreamsAndStorage
//
// Purpose:
//
//      Releases the stream and storage ptrs
//
// Parameters:
//
//      None
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      IStorage::Release           OLE
//
// Comments:
//
//
//********************************************************************

void CPersistStorage::ReleaseStreamsAndStorage()
{
if (m_lpObj->m_lpColorStm)
{
m_lpObj->m_lpColorStm->Release();
m_lpObj->m_lpColorStm = NULL;
}

if (m_lpObj->m_lpSizeStm)
{
m_lpObj->m_lpSizeStm->Release();
m_lpObj->m_lpSizeStm = NULL;
}

if (m_lpObj->m_lpStorage)
{
m_lpObj->m_lpStorage->Release();
m_lpObj->m_lpStorage = NULL;
}
}

//**********************************************************************
//
// CPersistStorage::CreateStreams
//
// Purpose:
//
//      Creates temporary streams in a storage.
//
// Parameters:
//
//      LPSTORAGE lpStg                 - Pointer to the storage
//
//      LPSTREAM FAR* lplpTempColor     - Color Stream
//
//      LPSTREAM FAR* lplpTempSize      - Size Stream
//
// Return Value:
//
//      S_OK
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      IStorage::Release           OLE
//
// Comments:
//
//
//********************************************************************

void CPersistStorage::CreateStreams(LPSTORAGE lpStg, LPSTREAM FAR* lplpTempColor,LPSTREAM FAR* lplpTempSize)
{
// create a stream to save the colors
 //@@WTK WIN32, UNICODE
 //lpStg->CreateStream ( "RGB",
 lpStg->CreateStream ( OLESTR("RGB"),
   STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE,
   0,
   0,
   lplpTempColor);

// create a stream to save the size
//@@WTK WIN32, UNICODE
//lpStg->CreateStream ( "size",
lpStg->CreateStream ( OLESTR("size"),
   STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE,
   0,
   0,
   lplpTempSize);

}


IPS.H

//**********************************************************************
// File name: ips.h
//
//      Definition of CPersistStorage
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#if !defined( _IPS_H_)
#define _IPS_H_

#include <ole2.h>
//@@WTK WIN32, UNICODE
//#include <storage.h>
#include "obj.h"

class CSimpSvrObj;

interface CPersistStorage : IPersistStorage
{
private:
CSimpSvrObj FAR * m_lpObj;
int m_nCount;
BOOL m_fSameAsLoad;

public:
CPersistStorage::CPersistStorage(CSimpSvrObj FAR * lpSimpSvrObj)
{
m_lpObj = lpSimpSvrObj;
m_nCount = 0;
};
CPersistStorage::~CPersistStorage() {};

STDMETHODIMP QueryInterface (REFIID riid, LPVOID FAR* ppvObj);
STDMETHODIMP_(ULONG) AddRef ();
STDMETHODIMP_(ULONG) Release ();

STDMETHODIMP InitNew (LPSTORAGE pStg);
STDMETHODIMP GetClassID  ( LPCLSID lpClassID) ;
STDMETHODIMP Save  ( LPSTORAGE pStgSave, BOOL fSameAsLoad) ;
STDMETHODIMP SaveCompleted  ( LPSTORAGE pStgNew);
STDMETHODIMP Load  ( LPSTORAGE pStg);
STDMETHODIMP IsDirty  ();
STDMETHODIMP HandsOffStorage  ();

void ReleaseStreamsAndStorage();
void OpenStreams(LPSTORAGE lpStg);
void CreateStreams(LPSTORAGE lpStg);
void CreateStreams(LPSTORAGE lpStg, LPSTREAM FAR *lpTempColor, LPSTREAM FAR *lpTempSize);

};

#endif


OBJ.CPP

//**********************************************************************
// File name: obj.cpp
//
//    Implementation file for the CSimpSvrApp Class
//
// Functions:
//
//    See obj.h for a list of member functions.
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "obj.h"
#include "ioo.h"
#include "ido.h"
#include "ips.h"
#include "icf.h"
#include "ioipao.h"
#include "ioipo.h"
#include "app.h"
#include "doc.h"

//**********************************************************************
//
// CSimpSvrObj::QueryInterface
//
// Purpose:
//
//      Used for interface negotiation at the "Object" level.
//
// Parameters:
//
//      REFIID riid         -   A reference to the interface that is
//                              being queried.
//
//      LPVOID FAR* ppvObj  -   An out parameter to return a pointer to
//                              the interface.
//
// Return Value:
//
//      S_OK          -   The interface is supported.
//      E_NOINTERFACE -   The interface is not supported
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//                   OLE API
//      IUnknown::AddRef            OBJ.CPP, IOO.CPP, IDO.CPP, IPS.CPP
//                                  IOIPO.CPP, IOIPAO.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP CSimpSvrObj::QueryInterface ( REFIID riid, LPVOID FAR* ppvObj)
{
        OutputDebugString("In CSimpSvrObj::QueryInterface\r\n");

        SCODE sc = S_OK;

        if (riid == IID_IUnknown)
                *ppvObj = this;
        else if (riid == IID_IOleObject)
                *ppvObj = &m_OleObject;
        else if (riid == IID_IDataObject)
                *ppvObj = &m_DataObject;
        else if ( (riid == IID_IPersistStorage) || (riid == IID_IPersist) )
                *ppvObj = &m_PersistStorage;
        else if (riid == IID_IOleInPlaceObject)
                *ppvObj = &m_OleInPlaceObject;
        else if (riid == IID_IOleInPlaceActiveObject)
                *ppvObj = &m_OleInPlaceActiveObject;
        else if (riid == IID_IExternalConnection)
                 *ppvObj = &m_ExternalConnection;
        else
                {
                *ppvObj = NULL;
                sc = E_NOINTERFACE;
                }

        if (*ppvObj)
                ((LPUNKNOWN)*ppvObj)->AddRef();

        return  sc ;
};

//**********************************************************************
//
// CSimpSvrObj::AddRef
//
// Purpose:
//
//      Adds to the reference count at the Object level.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      ULONG   -   The new reference count of the Object.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Due to the reference counting model that is used in this
//      implementation, this reference count is the sum of the
//      reference counts on all interfaces
//
//********************************************************************

STDMETHODIMP_(ULONG) CSimpSvrObj::AddRef ()
{
        OutputDebugString("In CSimpSvrObj::AddRef\r\n");

        m_lpDoc->AddRef();

        return ++m_nCount;
};

//**********************************************************************
//
// CSimpSvrObj::Release
//
// Purpose:
//
//      Decrements the reference count at this level
//
// Parameters:
//
//      None
//
// Return Value:
//
//      ULONG   -   The new reference count of the object.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Due to the reference counting model that is used in this
//      implementation, this reference count is the sum of the
//      reference counts on all interfaces
//
//********************************************************************

STDMETHODIMP_(ULONG) CSimpSvrObj::Release ()
{
        OutputDebugString("In CSimpSvrObj::Release\r\n");

        m_lpDoc->Release();

        if (--m_nCount == 0) {
                delete this;
        return 0;
    }

        return m_nCount;
};

//**********************************************************************
//
// CSimpSvrObj::CSimpSvrObj
//
// Purpose:
//
//      Constructor for CSimpSvrObj
//
// Parameters:
//
//      CSimpSvrDoc FAR * lpSimpSvrDoc - ptr to the doc object
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//
// Comments:
//
//
//********************************************************************
#pragma warning (disable : 4355) // "this" used in base initializer list warning.  This
                                                                 // can be disabled because we are not using "this" in
                                                                 // the constructor for these objects, rather we are
                                                                 // just storing it for future use...
CSimpSvrObj::CSimpSvrObj(CSimpSvrDoc FAR * lpSimpSvrDoc) : m_OleObject(this),
                                                                                m_DataObject(this),
                                                                                m_PersistStorage(this),
                                                                                m_OleInPlaceActiveObject(this),
                                                                                m_OleInPlaceObject(this),
                                                                                m_ExternalConnection(this)
#pragma warning (default : 4355) // Turn the warning back on

{
        m_lpDoc = lpSimpSvrDoc;
        m_nCount = 0;
        m_fInPlaceActive = FALSE;
        m_fInPlaceVisible = FALSE;
        m_fUIActive = FALSE;
        m_hmenuShared = NULL;
        m_hOleMenu = NULL;

        m_dwRegister = 0;

        m_lpFrame = NULL;
        m_lpCntrDoc = NULL;

        m_lpStorage = NULL;
        m_lpColorStm = NULL;
        m_lpSizeStm = NULL;
        m_lpOleClientSite = NULL;
        m_lpOleAdviseHolder = NULL;
        m_lpDataAdviseHolder = NULL;
        m_lpIPSite = NULL;

        m_red = 128;
        m_green = 0;
        m_blue = 0;

        m_size.x = 100;
        m_size.y = 100;

        m_xOffset = 0;
        m_yOffset = 0;

        m_scale = (float) 1.0;

        m_fSaveWithSameAsLoad = FALSE;
        m_fNoScribbleMode = FALSE;

}

//**********************************************************************
//
// CSimpSvrObj::~CSimpSvrObj
//
// Purpose:
//
//      Destructor for CSimpSvrObj
//
// Parameters:
//
//      None
//
// Return Value:
//
//
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      PostMessage                 Windows API
//      CSimpSvrDoc::GetApp         DOC.H
//      CSimpSvrDoc::GethAppWnd     DOC.H
//      CSimpSvrDoc::ClearObj       DOC.H
//      CSimpSvrApp::IsStartedByOle APP.CPP
//
// Comments:
//
//
//********************************************************************

CSimpSvrObj::~CSimpSvrObj()
{
        OutputDebugString("In CSimpSvrObj's Destructor \r\n");

        // if we were started by ole, post ourselves a close message
        if (m_lpDoc->GetApp()->IsStartedByOle())
                PostMessage(m_lpDoc->GethAppWnd(), WM_SYSCOMMAND, SC_CLOSE, 0L);

        // clear the OBJ ptr in the doc class
        m_lpDoc->ClearObj();

}

//**********************************************************************
//
// CSimpSvrObj::Draw
//
// Purpose:
//
//      Draws the object into an arbitrary DC
//
// Parameters:
//
//      HDC hDC - DC to draw into
//
// Return Value:
//
//      NONE
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      CreateBrushIndirect         Windows API
//      SelectObject                Windows API
//      Rectangle                   Windows API
//      DeleteObject                Windows API
//
// Comments:
//
//
//********************************************************************

void CSimpSvrObj::Draw (HDC hDC, BOOL m_fMeta)
{
        LOGBRUSH lb;

        OutputDebugString("In CSimpSvrObj::Draw\r\n");

        char szBuffer[255];
        wsprintf(szBuffer,"Drawing Scale %3d\r\n",m_scale);
        OutputDebugString(szBuffer);

        if (!m_fMeta)
                {
                SetMapMode(hDC, MM_ANISOTROPIC);
                //@@WTK WIN32, UNICODE
                //SetWindowOrg(hDC, (int)(m_xOffset/m_scale), (int)(m_yOffset/m_scale));
                SetWindowOrgEx(hDC, (int)(m_xOffset/m_scale),
                        (int)(m_yOffset/m_scale), NULL);
                //@@WTK WIN32, UNICODE
                //SetWindowExt(hDC, m_size.x, m_size.y);
                SetWindowExtEx(hDC, m_size.x, m_size.y, NULL);
                //@@WTK WIN32, UNICODE
                //SetViewportExt(hDC, (int)(m_size.x*m_scale), (int)(m_size.y*m_scale));
                SetViewportExtEx(hDC, (int)(m_size.x*m_scale),
                        (int)(m_size.y*m_scale), NULL);
                }

        // fill out a LOGBRUSH
        lb.lbStyle = BS_SOLID;
        lb.lbColor = RGB(m_red, m_green, m_blue);
        lb.lbHatch = 0;

        // create the brush
        HBRUSH hBrush = CreateBrushIndirect(&lb);

        // select the brush
        HBRUSH hOldBrush = (HBRUSH) SelectObject(hDC, hBrush);
        HPEN hPen = CreatePen(PS_INSIDEFRAME, 6, RGB(0, 0, 0));

        HPEN hOldPen = (HPEN) SelectObject(hDC, hPen);

        // draw the rectangle
        Rectangle (hDC, 0, 0, m_size.x, m_size.y);

        // restore the pen
        hPen = (HPEN) SelectObject(hDC, hOldPen);

        // free the pen
        DeleteObject(hPen);

        // restore the old brush
        hBrush = (HBRUSH) SelectObject(hDC, hOldBrush);

        // free the brush
        DeleteObject(hBrush);
}

//**********************************************************************
//
// CSimpSvrObj::GetMetaFilePict
//
// Purpose:
//
//      Returns a handle to a metafile representation of the object.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      Handle to the metafile.
//
// Function Calls:
//      Function                        Location
//
//      OutputDebugString               Windows API
//      GlobalAlloc                     Windows API
//      GlobalLock                      Windows API
//      SetWindowOrg                    Windows API
//      SetWindowExt                    Windows API
//      CreateMetaFile                  Windows API
//      CloseMetaFile                   Windows API
//      GlobalUnlock                    Windows API
//      XformWidthInPixelsToHimetric    OLESTD
//      XformHeightInPixelsToHimetric   OLESTD
//      CSimpSvrObj::Draw               OBJ.CPP
//
// Comments:
//
//
//********************************************************************

HANDLE CSimpSvrObj::GetMetaFilePict()
{
        HANDLE hMFP;
        METAFILEPICT FAR * lpMFP;
        POINT pt;

        OutputDebugString("In CSimpSvrObj::GetMetaFilePict\r\n");

        // allocate the memory for the METAFILEPICT structure
        hMFP = GlobalAlloc (GMEM_SHARE | GHND, sizeof (METAFILEPICT) );
        lpMFP = (METAFILEPICT FAR*) GlobalLock(hMFP);

        // get the size of the object in HIMETRIC
        pt.x = XformWidthInPixelsToHimetric(NULL, m_size.x);
        pt.y = XformHeightInPixelsToHimetric(NULL, m_size.y);

        // fill out the METAFILEPICT structure
        lpMFP->mm = MM_ANISOTROPIC;
        lpMFP->xExt = pt.x;
        lpMFP->yExt = pt.y;

        // Create the metafile
        HDC hDC = CreateMetaFile(NULL);

        //@@WTK WIN32, UNICODE
        //SetWindowOrg (hDC, 0, 0);
        SetWindowOrgEx (hDC, 0, 0, NULL);
        //@@WTK WIN32, UNICODE
        //SetWindowExt (hDC, m_size.x,
        SetWindowExtEx (hDC, m_size.x,
                                           m_size.y, NULL);

        Draw(hDC);

        lpMFP->hMF = CloseMetaFile(hDC);

        // unlock the metafilepict
        GlobalUnlock(hMFP);

        return hMFP;
}

//**********************************************************************
//
// CSimpSvrObj::SaveToStorage
//
// Purpose:
//
//      Saves the object to the passed storage
//
// Parameters:
//
//      LPSTORAGE lpStg - Storage in which to save the object
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      IStorage::CreateStream      OLE
//      IStream::Write              OLE
//      IStream::Release            OLE
//
// Comments:
//
//      A real app will want to do better error checking / returning
//
//********************************************************************

void CSimpSvrObj::SaveToStorage (LPSTORAGE lpStg, BOOL fSameAsLoad)
{
        OutputDebugString("In CSimpSvrObj::SaveToStorage\r\n");

        LPSTREAM lpTempColor, lpTempSize;

        if (!fSameAsLoad)
                m_PersistStorage.CreateStreams( lpStg, &lpTempColor, &lpTempSize);
        else
                {
                lpTempColor = m_lpColorStm;
                lpTempColor->AddRef();
                lpTempSize = m_lpSizeStm;
                lpTempSize->AddRef();
                }

        ULARGE_INTEGER uli;

        uli.LowPart = 0;
        uli.HighPart = 0;

        lpTempColor->SetSize(uli);
        lpTempSize->SetSize(uli);

        LARGE_INTEGER li;

        li.LowPart = 0;
        li.HighPart = 0;

        lpTempColor->Seek(li, STREAM_SEEK_SET, NULL);
        lpTempSize->Seek(li, STREAM_SEEK_SET, NULL);

        // write the colors to the stream
        lpTempColor->Write(&m_red, sizeof(m_red), NULL);
        lpTempColor->Write(&m_green, sizeof(m_green), NULL);
        lpTempColor->Write(&m_blue, sizeof(m_blue), NULL);

        // write the size to the stream
        lpTempSize->Write(&m_size, sizeof(m_size), NULL);

        lpTempColor->Release();
        lpTempSize->Release();
}

//**********************************************************************
//
// CSimpSvrObj::LoadFromStorage
//
// Purpose:
//
//      Loads the object from the passed storage
//
// Parameters:
//
//      LPSTORAGE lpStg     - Storage in which to load the object from
//
// Return Value:
//
//      None.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      IStorage::OpenStream        OLE
//      IStream::Read               OLE
//      IStream::Release            OLE
//
// Comments:
//
//
//********************************************************************

void CSimpSvrObj::LoadFromStorage ()
{
        OutputDebugString("In CSimpSvrObj::LoadFromStorage\r\n");

        // Read the colors
        m_lpColorStm->Read(&m_red, sizeof(m_red), NULL);
        m_lpColorStm->Read(&m_green, sizeof(m_green), NULL);
        m_lpColorStm->Read(&m_blue, sizeof(m_blue), NULL);

        // read the size
        m_lpSizeStm->Read(&m_size, sizeof(m_size), NULL);

}

//**********************************************************************
//
// CSimpSvrObj::DoInPlaceActivate
//
// Purpose:
//
//      Does the inplace activation for the object
//
// Parameters:
//
//      LONG lVerb  - Verb that caused this function to be called
//
// Return Value:
//
//      TRUE/FALSE depending on success or failure.
//
// Function Calls:
//      Function                                Location
//
//      IOleClientSite::QueryInterface          Container
//      IOleClientSite::ShowObject              Container
//      IOleInPlaceSite::CanInPlaceActivate     Container
//      IOleInPlaceSite::Release                Container
//      IOleInPlaceSite::OnInPlaceActivate      Container
//      IOleInPlaceSite::GetWindow              Container
//      IOleInPlaceSite::GetWindowContext       Container
//      IOleInPlaceSite::OnUIActivate           Container
//      IOleInPlaceSite::Release                Container
//      IOleInPlaceFrame::SetActiveObject       Container
//      IOleInPlaceUIWindow::SetActiveObject    Container
//      OutputDebugString                       Windows API
//      ShowWindow                              Windows API
//      SetParent                               Windows API
//      IntersectRect                           Windows API
//      OffsetRect                              Windows API
//      MoveWindow                              Windows API
//      CopyRect                                Windows API
//      SetFocus                                Windows API
//      SetHatchWindowSize                      OLE2UI
//      CSimpSvrObj::AssembleMenus              OBJ.CPP
//      CSimpSvrObj::AddFrameLevelUI            OBJ.CPP
//
//
// Comments:
//
//      Be sure to read TECHNOTES.WRI included with the OLE SDK
//      for details on implementing inplace activation.
//
//********************************************************************

BOOL CSimpSvrObj::DoInPlaceActivate (LONG lVerb)
{
        BOOL retval = FALSE;
        RECT posRect, clipRect;

        OutputDebugString("In CSimpSvrObj::DoInPlaceActivate\r\n");

        // if not currently in place active
        if (!m_fInPlaceActive)
                {
                // get the inplace site
                if (m_lpOleClientSite->QueryInterface(IID_IOleInPlaceSite,
                                                                                (LPVOID FAR *)&m_lpIPSite) != NOERROR)
                        goto error;

                // if the inplace site could not be obtained, or refuses to inplace
                // activate then goto error.
                if (m_lpIPSite == NULL || m_lpIPSite->CanInPlaceActivate() != NOERROR)
                        {
                        if (m_lpIPSite)
                                m_lpIPSite->Release();
                        m_lpIPSite == NULL;
                        goto error;
                        }

                // tell the site that we are activating.
                m_lpIPSite->OnInPlaceActivate();
                m_fInPlaceActive = TRUE;
                }

        // if not currently inplace visibl
        if (!m_fInPlaceVisible)
                {
                m_fInPlaceVisible = TRUE;

                // get the window handle of the site
                m_lpIPSite->GetWindow(&m_hWndParent);

                // get window context from the container
                m_lpIPSite->GetWindowContext ( &m_lpFrame,
                                                                         &m_lpCntrDoc,
                                                                         &posRect,
                                                                         &clipRect,
                                                                         &m_FrameInfo);

                // show the hatch window
                m_lpDoc->ShowHatchWnd();

                // Set the parenting
                SetParent (m_lpDoc->GethHatchWnd(), m_hWndParent);
                SetParent (m_lpDoc->GethDocWnd(), m_lpDoc->GethHatchWnd());

                // tell the client site to show the object
                m_lpOleClientSite->ShowObject();

                RECT resRect;

                // figure out the "real" size of the object
                IntersectRect(&resRect, &posRect, &clipRect);
                CopyRect(&m_posRect, &posRect);

                POINT pt;

                // adjust our hatch window size
                SetHatchWindowSize ( m_lpDoc->GethHatchWnd(),
                                                         &resRect,
                                                         &posRect,
                                                         &pt);

                // calculate the actual object rect inside the hatchwnd.
                OffsetRect (&resRect, pt.x, pt.y);

                // move the object window
                MoveWindow(m_lpDoc->GethDocWnd(),
                                   resRect.left,
                                   resRect.top,
                                   resRect.right - resRect.left,
                                   resRect.bottom - resRect.top,
                                   FALSE);

                // create the combined window
                AssembleMenus();
                }

        // if not UIActive
        if (!m_fUIActive)
                {
                m_fUIActive = TRUE;

                // tell the inplace site that we are activating
                m_lpIPSite->OnUIActivate();

                // set the focus to our object window
                SetFocus(m_lpDoc->GethDocWnd());

                // set the active object on the frame
                //@@WTK WIN32, UNICODE
                //m_lpFrame->SetActiveObject(&m_OleInPlaceActiveObject, "Simple OLE 2.0 Server");
                m_lpFrame->SetActiveObject(&m_OleInPlaceActiveObject, OLESTR("Simple OLE 2.0 Server"));

                // set the active object on the Doc, if available.
                if (m_lpCntrDoc)
                        //@@WTK WIN32, UNICODE
                        //m_lpCntrDoc->SetActiveObject(&m_OleInPlaceActiveObject, "Simple OLE 2.0 Server");
                        m_lpCntrDoc->SetActiveObject(&m_OleInPlaceActiveObject, OLESTR("Simple OLE 2.0 Server"));

                // add the frame level UI.
                AddFrameLevelUI();
                }

        retval = TRUE;
error:
        return retval;
}

//**********************************************************************
//
// CSimpSvrObj::AssembleMenus
//
// Purpose:
//
//      Creates the combined menus used during inplace activation.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                        Location
//
//      OutputDebugString               Windows API
//      CreateMenu                      Windows API
//      IOleInPlaceFrame::InsertMenus   Container
//      InsertMenu                      Windows API
//      DestroyMenu                     Windows API
//      OleCreateMenuDescriptor         OLE API
//
// Comments:
//
//
//********************************************************************

void CSimpSvrObj::AssembleMenus()
{
        OutputDebugString("In CSimpSvrObj::AssembleMenus\r\n");
        OLEMENUGROUPWIDTHS menugroupwidths;

        m_hmenuShared = NULL;

        //  Create the menu resource
        m_hmenuShared = CreateMenu();

        // have the contaner insert its menus
        if (m_lpFrame->InsertMenus (m_hmenuShared, &menugroupwidths) == NOERROR)
                {
                int nFirstGroup = (int) menugroupwidths.width[0];

                // insert the server menus
                //@@WTK WIN32, UNICODE
                //InsertMenu( m_hmenuShared, nFirstGroup, MF_BYPOSITION | MF_POPUP, m_lpDoc->GetApp()->GetColorMenu(), "&Color");
                InsertMenu( m_hmenuShared, nFirstGroup, MF_BYPOSITION | MF_POPUP,
                        (UINT)m_lpDoc->GetApp()->GetColorMenu(), "&Color");
                menugroupwidths.width[1] = 1;
                menugroupwidths.width[3] = 0;
                menugroupwidths.width[5] = 0;
                }
        else
                {
                // Destroy the menu resource
                DestroyMenu(m_hmenuShared);
                m_hmenuShared = NULL;
                }

        // tell OLE to create the menu descriptor
        m_hOleMenu = OleCreateMenuDescriptor(m_hmenuShared, &menugroupwidths);
}

//**********************************************************************
//
// CSimpSvrObj::AddFrameLevelUI
//
// Purpose:
//
//      Adds the Frame level user interface
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString                   Windows API
//      IOleInPlaceFrame::SetMenu           Container
//      IOleInPlaceFrame::SetBorderSpace    Container
//      IOleInPlaceUIWindow::SetBorderSpace Container
//      CSimpSvrDoc::GethDocWnd             DOC.H
//
// Comments:
//
//
//********************************************************************

void CSimpSvrObj::AddFrameLevelUI()
{
        OutputDebugString("In CSimpSvrObj::AddFrameLevelUI\r\n");

        // add the combined menu
        m_lpFrame->SetMenu(m_hmenuShared, m_hOleMenu, m_lpDoc->GethDocWnd());

        // do hatched border
        SetParent (m_lpDoc->GethHatchWnd(), m_hWndParent);
        SetParent (m_lpDoc->GethDocWnd(), m_lpDoc->GethHatchWnd());

        // set the border space.  Normally we would negotiate for toolbar
        // space at this point.  Since this server doesn't have a toolbar,
        // this isn't needed...
        if (m_lpFrame)
                m_lpFrame->SetBorderSpace(NULL);

        if (m_lpCntrDoc)
                m_lpCntrDoc->SetBorderSpace(NULL);
}

//**********************************************************************
//
// CSimpSvrObj::DoInPlaceHide
//
// Purpose:
//
//      Hides the object while inplace actvie
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                        Location
//
//      OutputDebugString               Windows API
//      SetParent                       Windows API
//      CSimpSvrDoc::GethDocWnd         DOC.H
//      CSimpSvrDoc::GethAppWnd         DOC.H
//      CSimpSvrDoc::GethHatchWnd       DOC.H
//      CSimpSvrObj::DisassembleMenus   OBJ.CPP
//      IOleInPlaceFrame::Release       Container
//      IOleInPlaceUIWindow::Release    Container
//
//
// Comments:
//
//      Be sure to read TECHNOTES.WRI included with the OLE SDK
//      for details on implementing inplace activation.
//
//********************************************************************

void CSimpSvrObj::DoInPlaceHide()
{
        OutputDebugString("In CSimpSvrObj::DoInPlaceHide\r\n");

        // if we aren't inplace visible, then this routine is a NOP,
        if (!m_fInPlaceVisible)
                return;

        m_fInPlaceVisible = FALSE;

        // change the parenting
        SetParent (m_lpDoc->GethDocWnd(), m_lpDoc->GethAppWnd());
        SetParent (m_lpDoc->GethHatchWnd(),m_lpDoc->GethDocWnd());

        // rip down the combined menus
        DisassembleMenus();

        // release the inplace frame
        m_lpFrame->Release();

        m_lpFrame = NULL;  // only holding one ref. to frame.

        // release the UIWindow if it is there.
        if (m_lpCntrDoc)
                m_lpCntrDoc->Release();

        m_lpCntrDoc = NULL;

}

//**********************************************************************
//
// CSimpSvrObj::DisassembleMenus
//
// Purpose:
//
//      Disassembles the combined menus used in inplace activation
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString               Windows API
//      OleDestroyMenuDescriptor        OLE API
//      RemoveMenu                      Windows API
//      IOleInPlaceFrame::RemoveMenus   Container
//      DestroyMenu                     Windows API
//
// Comments:
//
//      Be sure to read TECHNOTES.WRI included with the OLE SDK
//      for details on implementing inplace activation.
//
//********************************************************************

void CSimpSvrObj::DisassembleMenus()
{
        // destroy the menu descriptor
        OleDestroyMenuDescriptor(m_hOleMenu);

        if (m_hmenuShared)
                {
                // remove the menus that we added
                RemoveMenu( m_hmenuShared, 1, MF_BYPOSITION);

                // have the container remove its menus

m_lpFrame->RemoveMenus(m_hmenuShared);

                // Destroy the menu resource
                DestroyMenu(m_hmenuShared);

                m_hmenuShared = NULL;
                }
}

//**********************************************************************
//
// CSimpSvrObj::SendOnDataChange
//
// Purpose:
//
//      Uses the data advise holder to send a data change, then updates
//      the ROT to note the time of change.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                                Location
//
//      IDataAdviseHolder::SendOnDataChange     OLE API
//      GetRunningObjectTable                   OLE API
//      CoFileTimeNow                           OLE API
//      IRunningObjectTable::NoteChangeTime     OLE API
//
// Comments:
//
//
//********************************************************************

void CSimpSvrObj::SendOnDataChange()
{
        if (m_lpDataAdviseHolder)
                m_lpDataAdviseHolder->SendOnDataChange( (LPDATAOBJECT)&m_DataObject, 0, 0);

        LPRUNNINGOBJECTTABLE lpRot;

        GetRunningObjectTable(0, &lpRot);

        if ( lpRot && m_dwRegister)
                {
                FILETIME ft;
                CoFileTimeNow(&ft);

                lpRot->NoteChangeTime(m_dwRegister, &ft);
                lpRot->Release();
                }
}

//**********************************************************************
//
// CSimpSvrObj::DeactivateUI
//
// Purpose:
//
//      Breaks down the inplace ui
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                                Location
//
//      SetParent                               Windows API
//      IOleInPlaceUIWindow::SetActiveObject    Container
//      IOleInPlaceFrame::SetActiveObject       Container
//      IOleInPlaceSite::UIDeactivate           Container
//
// Comments:
//
//
//********************************************************************

void CSimpSvrObj::DeactivateUI()
{
        // if not UI active, or no pointer to IOleInPlaceFrame, then
        // return NOERROR
        if (!(m_fUIActive || m_lpFrame))
                return;
        else
                {
                m_fUIActive = FALSE;

                // remove hatching
                SetParent (m_lpDoc->GethDocWnd(), m_lpDoc->GethAppWnd());
                SetParent (m_lpDoc->GethHatchWnd(),m_lpDoc->GethDocWnd());

                // if in an MDI container, call SetActiveObject on the DOC.
                if (m_lpCntrDoc)
                        m_lpCntrDoc->SetActiveObject(NULL, NULL);

                m_lpFrame->SetActiveObject(NULL, NULL);

                // tell the container that our UI is going away.
                if (m_lpIPSite)
                        m_lpIPSite->OnUIDeactivate(FALSE);
                }
}


OBJ.H

//**********************************************************************
// File name: obj.h
//
//      Definition of CSimpSvrObj
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#if !defined( _OBJ_H_)
#define _OBJ_H_

#include "ioipao.h"
#include "ioipo.h"
#include "ioo.h"
#include "ips.h"
#include "ido.h"
#include "iec.h"

class CSimpSvrDoc;
interface COleObject;
interface CPersistStorage;
interface CDataObject;
interface COleInPlaceActiveObject;
interface COleInPlaceObject;
interface CExternalConnection;

class CSimpSvrObj : public IUnknown
{
private:
CSimpSvrDoc FAR * m_lpDoc;      // Back pointer
int m_nCount;                   // reference count
BOOL m_fInPlaceActive;          // Used during InPlace Negotiation
BOOL m_fInPlaceVisible;         // "  "  "  "   "   "   "   "   "
BOOL m_fUIActive;               // "  "  "  "   "   "   "   "   "
HMENU m_hmenuShared;            // "  "  "  "   "   "   "   "   "
HOLEMENU m_hOleMenu;            // "  "  "  "   "   "   "   "   "
RECT m_posRect;                 // "  "  "  "   "   "   "   "   "
OLEINPLACEFRAMEINFO m_FrameInfo;
BOOL m_fSaveWithSameAsLoad;
BOOL m_fNoScribbleMode;

DWORD m_dwRegister;             // Registered in ROT

int m_red, m_green, m_blue;     // current color
POINT m_size;                   // current size
int m_xOffset;
int m_yOffset;
float m_scale;

HWND m_hWndParent;              // parent window handle

// interfaces used
LPSTORAGE m_lpStorage;
LPSTREAM m_lpColorStm, m_lpSizeStm;
LPOLECLIENTSITE m_lpOleClientSite;          // IOleClientSite
LPOLEADVISEHOLDER m_lpOleAdviseHolder;      // IOleAdviseHolder
LPDATAADVISEHOLDER m_lpDataAdviseHolder;    // IDataAdviseHolder
LPOLEINPLACEFRAME m_lpFrame;                // IOleInPlaceFrame
LPOLEINPLACEUIWINDOW m_lpCntrDoc;           // IOleInPlaceUIWindow
LPOLEINPLACESITE m_lpIPSite;                // IOleInPlaceSite

// interface implemented
COleObject m_OleObject;                             // IOleObject
CPersistStorage m_PersistStorage;                   // IPersistStorage
CDataObject m_DataObject;                           // IDataObject
COleInPlaceActiveObject m_OleInPlaceActiveObject;   // IOleInPlaceActiveObject
COleInPlaceObject m_OleInPlaceObject;               // IOleInPlaceObject
CExternalConnection m_ExternalConnection;

public:
STDMETHODIMP QueryInterface (REFIID riid, LPVOID FAR* ppvObj);
STDMETHODIMP_(ULONG) AddRef ();
STDMETHODIMP_(ULONG) Release ();

// construction/destruction
CSimpSvrObj(CSimpSvrDoc FAR * lpSimpSvrDoc);
~CSimpSvrObj();

// utility functions
void Draw(HDC hDC, BOOL fMetaDC = TRUE);
void PaintObj(HDC hDC);
void lButtonDown(WPARAM wParam,LPARAM lParam);
HANDLE GetMetaFilePict();
void SaveToStorage (LPSTORAGE lpStg, BOOL fSameAsLoad);
void LoadFromStorage ();

// visual editing helper functions
BOOL DoInPlaceActivate (LONG lVerb);
void AssembleMenus();
void AddFrameLevelUI();
void DoInPlaceHide();
void DisassembleMenus();
void SendOnDataChange();
void DeactivateUI();

// member variable access
inline BOOL IsInPlaceActive() { return m_fInPlaceActive; };
inline BOOL IsInPlaceVisible() { return m_fInPlaceVisible; };
inline BOOL IsUIActive() { return m_fUIActive; };
inline HWND GetParent() { return m_hWndParent; };
inline LPSTORAGE GetStorage() { return m_lpStorage; };
inline LPOLECLIENTSITE GetOleClientSite() { return m_lpOleClientSite; };
inline LPDATAADVISEHOLDER GetDataAdviseHolder() { return m_lpDataAdviseHolder; };
inline LPOLEADVISEHOLDER GetOleAdviseHolder() { return m_lpOleAdviseHolder; };
inline LPOLEINPLACEFRAME GetInPlaceFrame() { return m_lpFrame; };
inline LPOLEINPLACEUIWINDOW GetUIWindow() { return m_lpCntrDoc; };
inline LPOLEINPLACESITE GetInPlaceSite() { return m_lpIPSite; };
inline COleObject FAR * GetOleObject() { return &m_OleObject; };
inline CPersistStorage FAR * GetPersistStorage() { return &m_PersistStorage; };
inline CDataObject FAR * GetDataObject() { return &m_DataObject; };
inline COleInPlaceActiveObject FAR * GetOleInPlaceActiveObject() { return &m_OleInPlaceActiveObject; };
inline COleInPlaceObject FAR * GetOleInPlaceObject() { return &m_OleInPlaceObject; };
inline void ClearOleClientSite() { m_lpOleClientSite = NULL; };
inline void ClearDataAdviseHolder() { m_lpDataAdviseHolder = NULL; };
inline void ClearOleAdviseHolder() { m_lpOleAdviseHolder = NULL; };
inline LPRECT GetPosRect() { return &m_posRect; };
inline LPPOINT GetSize() { return &m_size; };
inline LPOLEINPLACEFRAMEINFO GetFrameInfo() {return &m_FrameInfo;};
inline DWORD GetRotRegister() { return m_dwRegister; };

// member manipulation
inline void SetColor (int nRed, int nGreen, int nBlue)
{ m_red = nRed; m_green = nGreen; m_blue = nBlue; };

inline void RotateColor()
{ m_red+=10; m_green+=10; m_blue+=10;};

// all of the interface implementations should be friends of this
// class
friend interface COleObject;
friend interface CPersistStorage;
friend interface CDataObject;
friend interface COleInPlaceActiveObject;
friend interface COleInPlaceObject;
friend interface CExternalConnection;

};
#endif


PRE.H

//**********************************************************************
// File name: pre.h
//
//      Used for precompiled headers
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#if !defined( _PRE_H_)
#define _PRE_H_

#include <windows.h>
#include <ole2.h>
#include <olestd.h>
#include <assert.h>
#include <string.h>
#include "simpsvr.h"
#include "resource.h"
#include <ole2ver.h>

#endif


APP.CPP

//**********************************************************************
// File name: app.cpp
//
//    Implementation file for the CSimpSvrApp Class
//
// Functions:
//
//    See app.h for a list of member functions.
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "obj.h"
#include "app.h"
#include "doc.h"
#include "icf.h"

#include "initguid.h"
DEFINE_GUID(GUID_SIMPLE, 0xBCF6D4A0, 0xBE8C, 0x1068, 0xB6, 0xD4, 0x00, 0xDD, 0x01, 0x0C, 0x05, 0x09);

//**********************************************************************
//
// CSimpSvrApp::CSimpSvrApp()
//
// Purpose:
//
//      Constructor for CSimpSvrApp
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      SetRectEmpty                Windows API
//
// Comments:
//
//
//********************************************************************

CSimpSvrApp::CSimpSvrApp()
{
        OutputDebugString("In CSimpSvrApp's Constructor \r\n");

        // clear members
        m_hAppWnd = NULL;
        m_hInst = NULL;
        m_lpDoc = NULL;

        // clear flags
        m_fInitialized = m_fOleStdInit = FALSE;

        // used for inplace
        SetRectEmpty(&nullRect);
}

//**********************************************************************
//
// CSimpSvrApp::~CSimpSvrApp()
//
// Purpose:
//
//      Destructor for CSimpSvrApp Class.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//      DestroyWindow               Windows API
//      CSimpSvrApp::IsInitialized  APP.H
//      OleUninitialize             OLE API
//
// Comments:
//
//********************************************************************

CSimpSvrApp::~CSimpSvrApp()
{
        OutputDebugString("In CSimpSvrApp's Destructor\r\n");

        if (m_fOleStdInit)
            OleStdUninitialize();

        // need to uninit the library...
        if (IsInitialized())
                OleUninitialize();

        DestroyWindow(m_hAppWnd);
}

//**********************************************************************
//
// CSimpSvrApp::QueryInterface
//
// Purpose:
//
//      Used for interface negotiation at the Application level.
//
// Parameters:
//
//      REFIID riid         -   A reference to the interface that is
//                              being queried.
//
//      LPVOID FAR* ppvObj  -   An out parameter to return a pointer to
//                              the interface.
//
// Return Value:
//
//      S_OK          -   The interface is supported.
//      E_NOINTERFACE -   The interface is not supported
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//                   OLE API
//      IUnknown::AddRef            APP.CPP
//
// Comments:
//
//
//********************************************************************

STDMETHODIMP CSimpSvrApp::QueryInterface(REFIID riid, LPVOID FAR* ppvObj)
{
        OutputDebugString("In CSimpSvrApp::QueryInterface\r\n");

        SCODE sc = S_OK;

        if (riid == IID_IUnknown)
                *ppvObj = this;
        else
                {
                *ppvObj = NULL;
                sc = E_NOINTERFACE;
                }

        if (*ppvObj)
                ((LPUNKNOWN)*ppvObj)->AddRef();

        // asking for something we don't understand at this level.
        return sc;
}

//**********************************************************************
//
// CSimpSvrApp::AddRef
//
// Purpose:
//
//      Adds to the reference count at the Application level.
//
// Parameters:
//
//      None
//
// Return Value:
//
//      ULONG   -   The new reference count of the application.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Due to the reference counting model that is used in this
//      implementation, this reference count is the sum of the
//      reference counts on all interfaces of all objects open
//      in the application.
//
//********************************************************************

STDMETHODIMP_(ULONG) CSimpSvrApp::AddRef()
{
        OutputDebugString("In CSimpSvrApp::AddRef\r\n");
        return SafeAddRef();
}

//**********************************************************************
//
// CSimpSvrApp::Release
//
// Purpose:
//
//      Decrements the reference count at this level
//
// Parameters:
//
//      None
//
// Return Value:
//
//      ULONG   -   The new reference count of the application.
//
// Function Calls:
//      Function                    Location
//
//      OutputDebugString           Windows API
//
// Comments:
//
//      Due to the reference counting model that is used in this
//      implementation, this reference count is the sum of the
//      reference counts on all interfaces of all objects open
//      in the application.
//
//********************************************************************

STDMETHODIMP_(ULONG) CSimpSvrApp::Release()
{
        OutputDebugString("In CSimpSvrApp::Release\r\n");

        return SafeRelease();
}

//**********************************************************************
//
// CSimpSvrApp::fInitApplication
//
// Purpose:
//
//      Initializes the application
//
// Parameters:
//
//      HANDLE hInstance    -   Instance handle of the application.
//
// Return Value:
//
//      TRUE    -   Application was successfully initialized.
//      FALSE   -   Application could not be initialized
//
// Function Calls:
//      Function                    Location
//
//      LoadIcon                    Windows API
//      LoadCursor                  Windows API
//      GetStockObject              Windows API
//      RegisterClass               Windows API
//      RegisterHatchWindowClass    OUTLUI.DLL
//
// Comments:
//
//********************************************************************

BOOL CSimpSvrApp::fInitApplication(HINSTANCE hInstance)
{
        CStabilize stabilize(this);
        WNDCLASS  wc;

        // Fill in window class structure with parameters that describe the
        // main window.

        wc.style = NULL;                    // Class style(s).
        wc.lpfnWndProc = MainWndProc;       // Function to retrieve messages for
                                                                                // windows of this class.
        wc.cbClsExtra = 0;                  // No per-class extra data.
        wc.cbWndExtra = 0;                  // No per-window extra data.
        wc.hInstance = hInstance;           // Application that owns the class.
        wc.hIcon = LoadIcon(hInstance, "SimpSvr");
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
        wc.lpszMenuName =  "SimpSvrMENU";    // Name of menu resource in .RC file.
        wc.lpszClassName = "SimpSvrWClass";  // Name used in call to CreateWindow.

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

        wc.style = CS_VREDRAW | CS_HREDRAW;                    // Class style(s).
        wc.lpfnWndProc = DocWndProc;        // Function to retrieve messages for
                                                                                // windows of this class.
        wc.cbClsExtra = 0;                  // No per-class extra data.
        wc.cbWndExtra = 0;                  // No per-window extra data.
        wc.hInstance = hInstance;           // Application that owns the class.
        wc.hIcon = NULL;
        wc.hCursor = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH);
        wc.lpszMenuName =  NULL;
        wc.lpszClassName = "DocWClass";     // Name used in call to CreateWindow.

        // Register the window class and return success/failure code.

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

        return (RegisterHatchWindowClass(hInstance));
}

//**********************************************************************
//
// CSimpSvrApp::fInitInstance
//
// Purpose:
//
//      Instance initialization.
//
// Parameters:
//
//      HANDLE hInstance    -   App. Instance Handle.
//
//      int nCmdShow        -   Show parameter from WinMain
//
// Return Value:
//
//      TRUE    -   Initialization Successful
//      FALSE   -   Initialization Failed.
//
//
// Function Calls:
//      Function                    Location
//
//      CreateWindow                Windows API
//      InvalidateRect              Windows API
//      ShowWindow                  Windows API
//      UpdateWindow                Windows API
//      CoRegisterClassObject       OLE API
//      OleBuildVersion             OLE API
//      OleInitialize               OLE API
//      CSimpSvrDoc::CreateObject   DOC.CPP
//
// Comments:
//
//      Note that successful Initalization of the OLE libraries
//      is remembered so the UnInit is only called if needed.
//
//********************************************************************

BOOL CSimpSvrApp::fInitInstance (HINSTANCE hInstance, int nCmdShow, CClassFactory FAR * lpClassFactory)
{
        CStabilize stabilize(this);
        m_hInst = hInstance;

                DWORD dwVer = OleBuildVersion();

        // check to see if we are compatible with this version of the libraries
        if (HIWORD(dwVer) != rmm || LOWORD(dwVer) < rup)
                OutputDebugString("*** WARNING:  Not compatible with current libs ***\r\n");

        // initialize the libraries
        if (OleInitialize(NULL) == NOERROR)
                m_fInitialized = TRUE;

        m_fOleStdInit = OleStdInitialize(hInstance);

        // Create the "application" windows
        m_hAppWnd = CreateWindow ("SimpSvrWClass",
                                                          "Simple OLE 2.0 Server",
                                                          WS_OVERLAPPEDWINDOW,
                                                          CW_USEDEFAULT,
                                                          CW_USEDEFAULT,
                                                          CW_USEDEFAULT,
                                                          CW_USEDEFAULT,
                                                          NULL,
                                                          NULL,
                                                          hInstance,
                                                          NULL);

        if (!m_hAppWnd)
                return FALSE;

        // if not started by OLE, then show the Window, and create a "fake" object, else
        // Register a pointer to IClassFactory so that OLE can instruct us to make an
        // object at the appropriate time.
        if (!m_fStartByOle)
                {
                ShowAppWnd(nCmdShow);
                m_lpDoc->CreateObject(IID_IOleObject, (LPVOID FAR *)&m_OleObject);
                InvalidateRect(m_lpDoc->GethDocWnd(), NULL, TRUE);
                }
        else
                {
                lpClassFactory = new CClassFactory(this);

                // shouldn't pass an API an object with a zero ref count
                lpClassFactory->AddRef();

                CoRegisterClassObject(GUID_SIMPLE,(IUnknown FAR *)lpClassFactory, CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE, &m_dwRegisterClass);

                // remove artificial Ref. count
                lpClassFactory->Release();
                }

        m_hMainMenu = GetMenu(m_hAppWnd);
        m_hColorMenu = GetSubMenu(m_hMainMenu, 1);
        m_hHelpMenu = GetSubMenu(m_hMainMenu, 2);

        return m_fInitialized;
}

//**********************************************************************
//
// CSimpSvrApp::lCommandHandler
//
// Purpose:
//
//      Handles the processing of WM_COMMAND.
//
// Parameters:
//
//      HWND hWnd       -   Handle to the application Window
//
//      UINT message    -   message (always WM_COMMAND)
//
//      WPARAM wParam   -   Same as passed to the WndProc
//
//      LPARAM lParam   -   Same as passed to the WndProc
//
// Return Value:
//
//      NULL
//
// Function Calls:
//      Function                                    Location
//
//      GetClientRect                               Windows API
//      MessageBox                                  Windows API
//      DialogBox                                   Windows API
//      MakeProcInstance                            Windows API
//      FreeProcInstance                            Windows API
//      SendMessage                                 Windows API
//      DefWindowProc                               Windows API
//      InvalidateRect                              Windows API
//      CSimpSvrDoc::InsertObject                   DOC.CPP
//      CSimpSvrObj::SetColor                       OBJ.CPP
//      CSimpSvrObj::RotateColor                    OBJ.CPP
//
// Comments:
//
//********************************************************************

long CSimpSvrApp::lCommandHandler (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
        CStabilize stabilize(this);
        //@@WTK WIN32, UNICODE
        //switch (wParam) {
        switch (LOWORD(wParam)) {
                // bring up the About box
                case IDM_ABOUT:
                        {

                        DialogBox(m_hInst,               // current instance
                                        "AboutBox",                  // resource to use
                                        m_hAppWnd,                   // parent handle
                                        (DLGPROC) About);                // About() instance address

                        break;
                        }

                // exit the application
                case IDM_EXIT:
                        SendMessage(hWnd, WM_SYSCOMMAND, SC_CLOSE, 0L);
                        break;

                case IDM_RED:
                        m_lpDoc->GetObj()->SetColor (128, 0, 0);
                        InvalidateRect(m_lpDoc->GethDocWnd(), NULL, TRUE);
                        break;

                case IDM_GREEN:
                        m_lpDoc->GetObj()->SetColor (0,128, 0);
                        InvalidateRect(m_lpDoc->GethDocWnd(), NULL, TRUE);
                        break;

                case IDM_BLUE:
                        m_lpDoc->GetObj()->SetColor (0, 0, 128);
                        InvalidateRect(m_lpDoc->GethDocWnd(), NULL, TRUE);
                        break;

                case IDM_ROTATE:
                        m_lpDoc->GetObj()->RotateColor();
                        InvalidateRect(m_lpDoc->GethDocWnd(), NULL, TRUE);
                        break;

                default:
                        return (DefWindowProc(hWnd, message, wParam, lParam));
                   }   // end of switch
        return NULL;
}

//**********************************************************************
//
// CSimpSvrApp::lSizeHandler
//
// Purpose:
//
//      Handles the WM_SIZE message
//
// Parameters:
//
//      HWND hWnd       -   Handle to the application Window
//
//      UINT message    -   message (always WM_SIZE)
//
//      WPARAM wParam   -   Same as passed to the WndProc
//
//      LPARAM lParam   -   Same as passed to the WndProc
//
// Return Value:
//
//      LONG    -   returned from the "document" resizing
//
// Function Calls:
//      Function                    Location
//
//      GetClientRect               Windows API
//      CSimpSvrDoc::lResizeDoc      DOC.CPP
//
// Comments:
//
//********************************************************************

long CSimpSvrApp::lSizeHandler (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
        RECT rect;
        CStabilize stabilize(this);

        GetClientRect(m_hAppWnd, &rect);
        return m_lpDoc->lResizeDoc(&rect);
}

//**********************************************************************
//
// CSimpSvrApp::lCreateDoc
//                                                         d
// Purpose:
//
//      Handles the creation of a document.
//
// Parameters:
//
//      HWND hWnd       -   Handle to the application Window
//
//      UINT message    -   message (always WM_CREATE)
//
//      WPARAM wParam   -   Same as passed to the WndProc
//
//      LPARAM lParam   -   Same as passed to the WndProc
//
// Return Value:
//
//      NULL
//
// Function Calls:
//      Function                    Location
//
//      GetClientRect               Windows API
//      CSimpSvrDoc::Create         DOC.CPP
//
// Comments:
//
//********************************************************************

long CSimpSvrApp::lCreateDoc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
        RECT rect;
        CStabilize stabilize(this);

        GetClientRect(hWnd, &rect);

        m_lpDoc = CSimpSvrDoc::Create(this, &rect, hWnd);

        return NULL;
}

//**********************************************************************
//
// CSimpSvrApp::PaintApp
//
// Purpose:
//
//      Handles the painting of the doc window.
//
//
// Parameters:
//
//      HDC hDC -   hDC to the Doc Window.
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      CSimpSvrDoc::PaintDoc        DOC.CPP
//
// Comments:
//
//
//********************************************************************

void CSimpSvrApp::PaintApp (HDC hDC)
{
        CStabilize stabilize(this);

        // if we supported multiple documents, we would enumerate
        // through each of the open documents and call paint.

        if (m_lpDoc)
                m_lpDoc->PaintDoc(hDC);

}

//**********************************************************************
//
// CSimpSvrApp::ParseCmdLine
//
// Purpose:
//
//      Determines if the app was started by OLE
//
//
// Parameters:
//
//      LPSTR lpCmdLine -   Pointer to the command line
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      lstrlen                     Windows API
//      lstrcmp                     Windows API
//
//
// Comments:
//
//      Parses the command line looking for the -Embedding or /Embedding
//      flag.
//
//********************************************************************

void CSimpSvrApp::ParseCmdLine(LPSTR lpCmdLine)
{
        char szTemp[255];

        m_fStartByOle = TRUE;

        ::ParseCmdLine (lpCmdLine, &m_fStartByOle, szTemp);
}

//**********************************************************************
//
// CSimpSvrApp::SetStatusText
//
// Purpose:
//
//      Blanks out the text in the status bar
//
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                    Location
//
//      CSimpSvrDoc::SetStatusText  DOC.CPP
//
//
// Comments:
//
//
//********************************************************************

void CSimpSvrApp::SetStatusText()
{
        CStabilize stabilize(this);
        m_lpDoc->SetStatusText();
}

//**********************************************************************
//
// CSimpSvrApp::IsInPlaceActive
//
// Purpose:
//
//      Safely determines from the app level if currently inplace active.
//
//
// Parameters:
//
//      None
//
// Return Value:
//
//      TRUE    - Inplace active
//      FALSE   - Not Inplace active
//
// Function Calls:
//      Function                    Location
//
//      CSimpSvrDoc::GetObject      OBJ.H
//      CSimpSvrObj:IsInPlaceActive OBJ.H
//
//
// Comments:
//
//
//********************************************************************

BOOL CSimpSvrApp::IsInPlaceActive()
{
        CStabilize stabilize(this);
        BOOL retval = FALSE;

        if (m_lpDoc)
                if (m_lpDoc->GetObj())
                        retval = m_lpDoc->GetObj()->IsInPlaceActive();

        return retval;
}

//**********************************************************************
//
// CSimpSvrApp::ShowAppWnd
//
// Purpose:
//
//      Shows the Application Window
//
// Parameters:
//
//      int nCmdShow    - Window State
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                        Location
//
//      ShowWindow                      Windows API
//      UpdateWindow                    Windows API
//      CoLockObjectExternal            OLE API
//
// Comments:
//
//********************************************************************

void CSimpSvrApp::ShowAppWnd(int nCmdShow)
{
        CoLockObjectExternal(this, TRUE, FALSE);
        ShowWindow (m_hAppWnd, nCmdShow);
        UpdateWindow (m_hAppWnd);
}

//**********************************************************************
//
// CSimpSvrApp::ShowAppWnd
//
// Purpose:
//
//      Hides the Application Window
//
// Parameters:
//
//      None
//
// Return Value:
//
//      None
//
// Function Calls:
//      Function                        Location
//
//      ShowWindow                      Windows API
//      CoLockObjectExternal            OLE API
//
// Comments:
//
//********************************************************************

void CSimpSvrApp::HideAppWnd()
{
        CoLockObjectExternal(this, FALSE, TRUE);
        ShowWindow (m_hAppWnd, SW_HIDE);
}


RESOURCE.H

//{{NO_DEPENDENCIES}}
// App Studio generated include file.
// Used by SIMPSVR.RC
//
#define IDM_OPEN                        102
#define IDM_SAVE                        103
#define IDM_SAVEAS                      104
#define IDM_PRINT                       105
#define IDM_EXIT                        106
#define IDM_UNDO                        107
#define IDM_CUT                         108
#define IDM_COPY                        109
#define IDM_PASTE                       110
#define ID_EDIT_INSERTOBJECT            111
#define IDM_INSERTOBJECT                111
#define IDM_NEW                         112
#define IDM_RED                         113
#define IDM_GREEN                       114
#define IDM_BLUE                        115
#define IDM_ROTATE                      116

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS

#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         117
#define _APS_NEXT_CONTROL_VALUE         1000
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


SIMPSVR.CPP

//**********************************************************************
// File name: simpsvr.cpp
//
//      Main source file for the simple OLE 2.0 server
//
// Functions:
//
//      WinMain         - Program entry point
//      MainWndProc     - Processes messages for the frame window
//      About           - Processes messages for the about dialog
//      DocWndProc      - Processes messages for the doc window
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************

#include "pre.h"
#include "obj.h"
#include "app.h"
#include "doc.h"
#include "icf.h"

// This line is needed for the debug utilities in OLESTD
extern "C" {
        OLEDBGDATA_MAIN("SIMPSVR")
}

CSimpSvrApp FAR * lpCSimpSvrApp;
CClassFactory FAR * lpClassFactory;

//**********************************************************************
//
// WinMain
//
// Purpose:
//
//      Program entry point
//
// Parameters:
//
//      HANDLE hInstance        - Instance handle for this instance
//
//      HANDLE hPrevInstance    - Instance handle for the last instance
//
//      LPSTR lpCmdLine         - Pointer to the command line
//
//      int nCmdShow            - Window State
//
// Return Value:
//
//      msg.wParam
//
// Function Calls:
//      Function                        Location
//
//      CSimpSvrApp::CSimpSvrApp          APP.CPP
//      CSimpSvrApp::fInitApplication    APP.CPP
//      CSimpSvrApp::fInitInstance       APP.CPP
//      CSimpSvrApp::HandleAccelerators  APP.CPP
//      CSimpSvrApp::~CSimpSvrApp         APP.CPP
//      GetMessage                      Windows API
//      TranslateMessage                Windows API
//      DispatchMessage                 Windows API
//
// Comments:
//
//********************************************************************

int PASCAL WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)

{
        MSG msg;

        // recommended size for OLE apps
        SetMessageQueue(96);

        lpCSimpSvrApp = new CSimpSvrApp;

        lpCSimpSvrApp->AddRef();      // need the app ref. count at 1 to hold the
                                                                  // app alive.

        lpCSimpSvrApp->ParseCmdLine(lpCmdLine);

        // app initialization
        if (!hPrevInstance)
                if (!lpCSimpSvrApp->fInitApplication(hInstance))
                        return (FALSE);

        // instance initialization
        if (!lpCSimpSvrApp->fInitInstance(hInstance, nCmdShow, lpClassFactory))
                return (FALSE);

        /* Initialization required for OLE 2 UI library.  This call is
        **    needed ONLY if we are using the static link version of the UI
        **    library. If we are using the DLL version, we should NOT call
        **    this function in our application.
        */
#if 0
        if (!OleUIInitialize(hInstance, hPrevInstance))
                {
                OleDbgOut("Could not initialize OLEUI library\n");
                return FALSE;
                }
#endif
        // message loop
        while (GetMessage(&msg, NULL, NULL, NULL))
                {
                if (lpCSimpSvrApp->IsInPlaceActive())

                        // Only key messages need to be sent to OleTranslateAccelerator.  Any other message
                        // would result in an extra FAR call to occur for that message processing...

                        if ( (msg.message >= WM_KEYFIRST) && (msg.message <= WM_KEYLAST) )

                                // OleTranslateAccelerator MUST be called, even though this application does
                                // not have an accelerator table.  This has to be done in order for the
                                // mneumonics for the top level menu items to work properly.

                                if ( OleTranslateAccelerator ( lpCSimpSvrApp->GetDoc()->GetObj()->GetInPlaceFrame(),
                                                                                lpCSimpSvrApp->GetDoc()->GetObj()->GetFrameInfo(),
                                                                                &msg) == NOERROR)
                                        continue;

                TranslateMessage(&msg);    /* Translates virtual key codes           */
                DispatchMessage(&msg);     /* Dispatches message to window           */
                }

        // De-initialization for UI libraries.  Just like OleUIInitialize, this
        // funciton is needed ONLY if we are using the static link version of the
        // OLE UI library.
#if 0
        OleUIUninitialize();
#endif

        return (msg.wParam);           /* Returns the value from PostQuitMessage */
}

//**********************************************************************
//
// MainWndProc
//
// Purpose:
//
//      Processes messages for the frame window
//
// Parameters:
//
//      HWND hWnd       - Window handle for frame window
//
//      UINT message    - Message value
//
//      WPARAM wParam   - Message info
//
//      LPARAM lParam   - Message info
//
// Return Value:
//
//      long
//
// Function Calls:
//      Function                        Location
//
//      CSimpSvrApp::lCommandHandler     APP.CPP
//      CSimpSvrApp::DestroyDocs         APP.CPP
//      CSimpSvrApp::lCreateDoc          APP.CPP
//      CSimpSvrApp::lSizeHandler        APP.CPP
//      CGameDoc::lAddVerbs           DOC.CPP
//      PostQuitMessage                 Windows API
//      DefWindowProc                   Windows API
//
// Comments:
//
//********************************************************************

//@@WTK WIN32, UNICODE
//long FAR PASCAL _export MainWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
long FAR PASCAL EXPORT MainWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)

{

        switch (message)
                {
                case WM_CLOSE:
                        OutputDebugString("*** In WM_CLOSE *** \r\n");

                        // if there is still a document
                        if (lpCSimpSvrApp->GetDoc())

                                // if there is still an object within a document
                                if (lpCSimpSvrApp->GetDoc()->GetObj())   // this case occurs if there is still
                                                                                // an outstanding Ref count on the object
                                                                                // when the app is trying to go away.
                                                                                // typically this case will occur in
                                                                                // the "open" editing mode.
                                        //  Close the document
                                        lpCSimpSvrApp->GetDoc()->Close();

                        // hide the app window
                        lpCSimpSvrApp->HideAppWnd();

                        // if we were started by ole, unregister the class factory, otherwise
                        // remove the ref count on our dummy OLE object
                        if (lpCSimpSvrApp->IsStartedByOle())
                                CoRevokeClassObject(lpCSimpSvrApp->GetRegisterClass());
                        else
                                lpCSimpSvrApp->GetOleObject()->Release();

                        lpCSimpSvrApp->Release();  // This should close the app.

                        break;

                case WM_COMMAND:           // message: command from application menu
                        return lpCSimpSvrApp->lCommandHandler(hWnd, message, wParam, lParam);
                        break;

                case WM_CREATE:
                        return lpCSimpSvrApp->lCreateDoc(hWnd, message, wParam, lParam);
                        break;

                case WM_DESTROY:                  // message: window being destroyed
                        PostQuitMessage(0);
                        break;

                case WM_SIZE:
                        return lpCSimpSvrApp->lSizeHandler(hWnd, message, wParam, lParam);

                default:                          // Passes it on if unproccessed
                        return (DefWindowProc(hWnd, message, wParam, lParam));
                }
                return (NULL);
}

//**********************************************************************
//
// About
//
// Purpose:
//
//      Processes dialog box messages
//
// Parameters:
//
//      HWND hWnd       - Window handle for dialog box
//
//      UINT message    - Message value
//
//      WPARAM wParam   - Message info
//
//      LPARAM lParam   - Message info
//
// Return Value:
//
// Function Calls:
//      Function                    Location
//
//      EndDialog                   Windows API
//
// Comments:
//
//********************************************************************

//@@WTK WIN32, UNICODE
//BOOL FAR PASCAL _export About(HWND hDlg,unsigned message,WORD wParam,LONG lParam)
BOOL FAR PASCAL EXPORT About(HWND hDlg,UINT message,WPARAM wParam,LPARAM lParam)

{
        switch (message) {
        case WM_INITDIALOG:                /* message: initialize dialog box */
                return (TRUE);

        case WM_COMMAND:                      /* message: received a command */
                //@@WTK WIN32, UNICODE
                //if (wParam == IDOK                /* "OK" box selected?          */
                if (LOWORD(wParam) == IDOK                /* "OK" box selected?          */
                || LOWORD(wParam) == IDCANCEL) {      /* System menu close command? */
                        EndDialog(hDlg, TRUE);        /* Exits the dialog box        */
                        return (TRUE);
                }
                break;
        }
        return (FALSE);                           /* Didn't process a message    */
}

//**********************************************************************
//
// DocWndProc
//
// Purpose:
//
//      Processes dialog box messages
//
// Parameters:
//
//      HWND hWnd       - Window handle for doc window
//
//      UINT message    - Message value
//
//      WPARAM wParam   - Message info
//
//      LPARAM lParam   - Message info
//
// Return Value:
//
// Function Calls:
//      Function                            Location
//
//      CSimpSvrApp::PaintApp                APP.CPP
//      BeginPaint                          Windows API
//      EndPaint                            Windows API
//      DefWindowProc                       Windows API
//      IOleObject::QueryInterface          Object
//      IOleInPlaceObject::UIDeactivate     Object
//      IOleObject::DoVerb                  Object
//      IOleInPlaceObject::Release          Object
//
// Comments:
//
//********************************************************************

//@@WTK WIN32, UNICODE
//long FAR PASCAL _export DocWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
long FAR PASCAL EXPORT DocWndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
        HDC hDC;
        PAINTSTRUCT ps;

        switch (message) {
                case WM_COMMAND:           // message: command from application menu
                        return lpCSimpSvrApp->lCommandHandler(hWnd, message, wParam, lParam);
                        break;

                case WM_PAINT:
                        hDC = BeginPaint(hWnd, &ps);

                        // tell the app class to paint itself
                        if (lpCSimpSvrApp)
                                lpCSimpSvrApp->PaintApp (hDC);

                        EndPaint(hWnd, &ps);
                        break;

                case WM_MENUSELECT:
                        lpCSimpSvrApp->SetStatusText();
                        break;

        default:                          /* Passes it on if unproccessed    */
                return (DefWindowProc(hWnd, message, wParam, lParam));
        }
        return (NULL);
}


SIMPSVR.H

//**********************************************************************
// File name: simpsvr.h
//
// Copyright 1993 - 1998 Microsoft Corporation. All rights reserved.
//**********************************************************************
#define IDM_ABOUT 100
#define IDM_INSERT  101
#define IDM_VERB0 1000

int PASCAL WinMain(HINSTANCE  hInstance,HINSTANCE  hPrevInstance,LPSTR lpCmdLine,int nCmdShow);
BOOL InitApplication(HINSTANCEhInstance);
BOOL InitInstance(HINSTANCE  hInstance, int nCmdShow);
//@@WTK WIN32, UNICODE _export EXPORT
long FAR PASCAL EXPORT MainWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
long FAR PASCAL EXPORT DocWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
//@@WTK WIN32, UNICODE
//BOOL FAR PASCAL export About(HWND hDlg, unsigned message, WORD wParam, LONG lParam);
BOOL FAR PASCAL EXPORT About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam);


CDIALOG.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       dialog.cxx
//
//  Contents:
//
//  Classes:    CHlprDialog
//
//  Functions:  DialogProc
//
//  History:    4-12-94   stevebl   Created
//
//----------------------------------------------------------------------------

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

//+---------------------------------------------------------------------------
//
//  Member:     CHlprDialog::ShowDialog
//
//  Synopsis:   Creates the dialog so that it's DialogProc member function can
//              be invoked.
//
//  Arguments:  [hinst]        - handle of the application instance
//              [lpszTemplate] - identifies the dialog box template
//              [hwndOwner]    - handle of the owner window
//
//  Returns:    return value from the dialog box
//
//  History:    4-12-94   stevebl   Created
//
//  Notes:      The dialog box object exists until deleted by the caller.
//              It can be shown any number of times.
//
//              This function is analgous to Windows' DialogBox function.  The
//              main difference being that you don't specify a DialogProc;
//              you override the pure virtal function CHlprDialog::DialogProc.
//
//----------------------------------------------------------------------------

int CHlprDialog::ShowDialog(HINSTANCE hinst, LPCTSTR lpszTemplate, HWND hwndOwner)
{
    _hInstance = hinst;
    return(DialogBoxParam(hinst, lpszTemplate, hwndOwner, (DLGPROC)::DialogProc, (long)this));
}

//+---------------------------------------------------------------------------
//
//  Function:   DialogProc
//
//  Synopsis:   Common DialogProc used by all CHlprDialog class objects.
//
//  Arguments:  [hwndDlg] - handle of dialog box
//              [uMsg]    - message
//              [wParam]  - first message parameter
//              [lParam]  - second message parameter
//
//  Returns:    response from the CHlprDialog::DialogProc method
//
//  History:    4-12-94   stevebl   Created
//
//  Notes:      This procedure is the DialogProc registered for all dialogs
//              created with the CHlprDialog class.  It uses the parameter
//              passed with the WM_INITDIALOG message to identify the dialog
//              classes' "this" pointer which it then stores in the window
//              structure's GWL_USERDATA field.  All subsequent messages
//              can then be forwarded on to the correct dialog class's
//              DialogProc method by using the pointer stored in the
//              GWL_USERDATA field.
//
//----------------------------------------------------------------------------

BOOL CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    CHlprDialog * pdlg;
    switch (uMsg)
    {
    case WM_INITDIALOG:
        // This message is how we identify the dialog object.

        // get a pointer to the window class object
        pdlg = (CHlprDialog *) lParam;
        // set its USERDATA word to point to the class object
        SetWindowLong(hwndDlg, GWL_USERDATA, (long) pdlg);
        break;
    default:
        // get a pointer to the window class object
        pdlg = (CHlprDialog *) GetWindowLong(hwndDlg, GWL_USERDATA);
        break;
    }
    // and call its message proc method
    if (pdlg != (CHlprDialog *) 0)
    {
        return(pdlg->DialogProc(hwndDlg, uMsg, wParam, lParam));
    }
    else
    {
        return(FALSE);
    }
}


CDIALOG.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       cdialog.h
//
//  Contents:   definition for common dialog functionality
//
//  Classes:    CHlprDialog (pure virtual class)
//
//  Functions:  DialogProc
//
//  History:    4-12-94   stevebl   Created
//
//----------------------------------------------------------------------------

#ifndef __CDIALOG_H__
#define __CDIALOG_H__

#ifdef __cplusplus
extern "C" {
#endif

BOOL CALLBACK DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);

#ifdef __cplusplus
}

//+---------------------------------------------------------------------------
//
//  Class:      CHlprDialog
//
//  Purpose:    virtual base class for wrapping Windows' dialog functionality
//
//  Interface:  ShowDialog -- analagous to the Windows DialogBox function
//              DialogProc -- pure virtual DialogProc for the dialog box
//              ~CHlprDialog   -- destructor
//
//  History:    4-12-94   stevebl   Created
//
//  Notes:      This class allows a dialog box to be cleanly wrapped in
//              a c++ class.  Specifically, it provides a way for a c++ class
//              to use one of its methods as a DialogProc, giving it a "this"
//              pointer and allowing it to have direct access to all of its
//              private members.
//
//----------------------------------------------------------------------------

class CHlprDialog
{
public:
    virtual int ShowDialog(HINSTANCE hinst, LPCTSTR lpszTemplate, HWND hwndOwner);
    virtual BOOL DialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) = 0;
    virtual ~CHlprDialog(){};
protected:
    HINSTANCE _hInstance;
};

#endif //__cplusplus

#endif //__CDIALOG_H__


CWINDOW.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       cwindow.cxx
//
//  Contents:   implementation for a window class
//
//  Classes:    CHlprWindow
//
//  Functions:  WindowProc
//
//  History:    4-12-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include "cwindow.h"

//+---------------------------------------------------------------------------
//
//  Member:     CHlprWindow::Create
//
//  Synopsis:   Special version of CreateWindow.
//
//  Arguments:  [lpszClassName]  - address of registered class name
//              [lpszWindowName] - address of window name
//              [dwStyle]        - window style
//              [x]              - horizontal position of window
//              [y]              - vertical position of window
//              [nWidth]         - window width
//              [nHeight]        - window height
//              [hwndParent]     - handle of parent or owner window
//              [hmenu]          - handle of menu, or child window identifier
//              [hinst]          - handle of application instance
//
//  Returns:    HWND of the created window
//
//  Modifies:   _hwnd, _hInstance
//
//  History:    4-12-94   stevebl   Created
//
//  Notes:      The window class must have been previously registered (as
//              is normal Windows procedure) and the callback function
//              must have been registered as ::WindowProc.  ::WindowProc will
//              then forward all messages on to the CHlprWindow::WindowProc
//              method, allowing the window to directly access class members
//              (i.e. giving the WindowProc access to the "this" pointer).
//
//----------------------------------------------------------------------------

HWND CHlprWindow::Create(
    LPCTSTR lpszClassName,
    LPCTSTR lpszWindowName,
    DWORD dwStyle,
    int x,
    int y,
    int nWidth,
    int nHeight,
    HWND hwndParent,
    HMENU hmenu,
    HINSTANCE hinst)
{
    _hInstance = hinst;
    return(_hwnd =
        CreateWindow(
            lpszClassName,
            lpszWindowName,
            dwStyle,
            x,
            y,
            nWidth,
            nHeight,
            hwndParent,
            hmenu,
            hinst,
            this));
}

//+---------------------------------------------------------------------------
//
//  Function:   WindowProc
//
//  Synopsis:   Standard WindowProc that forwards Windows messages on to the
//              CHlprWindow::WindowProc method.
//
//  Arguments:  [hwnd]   - window handle
//              [uMsg]   - message
//              [wParam] - first message parameter
//              [lParam] - second message parameter
//
//  History:    4-12-94   stevebl   Created
//
//  Notes:      This Window procedure expects that it will receive a "this"
//              pointer as the lpCreateParams member passed as part of the
//              WM_CREATE message.  It saves the "this" pointer in the
//              GWL_USERDATA field of the window structure.
//
//----------------------------------------------------------------------------

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    CHlprWindow * pw;
    switch (uMsg)
    {
    case WM_CREATE:
        // Since this is the first time that we can get ahold of
        // a pointer to the window class object, all messages that might
        // have been sent before this are never seen by the Windows object
        // and only get passed on to te DefWindowProc

        // get a pointer to the window class object
        pw = (CHlprWindow *) ((CREATESTRUCT *)lParam)->lpCreateParams;
        // set its USERDATA DWORD to point to the class object
        SetWindowLong(hwnd, GWL_USERDATA, (long) pw);
        // Set it's protected _hwnd member variable to ensure that
        // member functions have access to the correct window handle.
        pw->_hwnd = hwnd;
        break;
    case WM_DESTROY:
        // This is our signal to destroy the window class object.

        pw = (CHlprWindow *) GetWindowLong(hwnd, GWL_USERDATA);
        SetWindowLong(hwnd, GWL_USERDATA, 0);
        delete pw;
        pw = (CHlprWindow *) 0;
        break;
    default:
        // get a pointer to the window class object
        pw = (CHlprWindow *) GetWindowLong(hwnd, GWL_USERDATA);
        break;
    }
    // and call its message proc method
    if (pw != (CHlprWindow *) 0)
    {
        return(pw->WindowProc(uMsg, wParam, lParam));
    }
    else
    {
        return(DefWindowProc(hwnd, uMsg, wParam, lParam));
    }
}


CWINDOW.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       cwindow.h
//
//  Contents:   definition of a virtual window class
//
//  Classes:    CHlprWindow
//
//  Functions:  WindowProc
//
//  History:    4-12-94   stevebl   Created
//
//----------------------------------------------------------------------------

#ifndef __CWINDOW_H__
#define __CWINDOW_H__

#include <windows.h>

#ifdef __cplusplus
extern "C" {
#endif

LRESULT CALLBACK WindowProc(
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam);

#ifdef __cplusplus
}

//+---------------------------------------------------------------------------
//
//  Class:      CHlprWindow
//
//  Purpose:    virtual base class for wrapping a window
//
//  Interface:  Create     -- analagous to Windows' CreateWindow function
//              WindowProc -- pure virtual WindowProc for the window
//              ~CHlprWindow   -- destructor
//              CHlprWindow    -- constructor
//
//  History:    4-12-94   stevebl   Created
//
//  Notes:      This class allows a window to be cleanly wrapped in a
//              c++ class.  Specifically, it provides a way for a c++ class
//              to use one of its methods as a WindowProc, giving it a "this"
//              pointer and allowing it to have direct access to all of its
//              private members.
//
//----------------------------------------------------------------------------

class CHlprWindow
{
public:
    HWND Create(
        LPCTSTR lpszClassName,
        LPCTSTR lpszWindowName,
        DWORD dwStyle,
        int x,
        int y,
        int nWidth,
        int nHeight,
        HWND hwndParent,
        HMENU hmenu,
        HINSTANCE hinst);
    virtual LRESULT WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam) = 0;
    virtual ~CHlprWindow(){};
    HWND GetHwnd(void)
    {
        return(_hwnd);
    }
    CHlprWindow()
    {
        _hwnd = NULL;
        _hInstance = NULL;
    };
protected:
friend LRESULT CALLBACK ::WindowProc(
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam);
    HWND _hwnd;
    HINSTANCE _hInstance;
};

#endif

#endif //__CWINDOW_H__


STRMHELP.CXX

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       strmhelp.cxx
//
//  Contents:   Routines for writting variables to a stream in an
//              architecture independent manner.
//
//  Classes:
//
//  Functions:  WriteDouble
//              ReadDouble
//              SizeDouble
//
//  History:    4-26-94   stevebl   Created
//
//----------------------------------------------------------------------------

#include <windows.h>
#include <ole2.h>
#include "strmhelp.h"
#include <stdio.h>

#define MAX_SIZE 128

//+---------------------------------------------------------------------------
//
//  Function:   SizeDouble
//
//  Synopsis:   returns the maximum number of bytes that WriteDouble will
//              take to write a double to an IStream
//
//  Arguments:  none
//
//  Returns:    maximum number of bytes needed to write a double to an IStream
//
//  History:    4-26-94   stevebl   Created
//
//----------------------------------------------------------------------------

DWORD SizeDouble(void)
{
    return(MAX_SIZE);
}

//+---------------------------------------------------------------------------
//
//  Function:   WriteDouble
//
//  Synopsis:   rrites a double to an OLE stream in a machine independent manner
//
//  Arguments:  [pstm]       - pointer to an IStream
//              [d]          - the double to be written
//              [pcbWritten] - pointer to the number of bytes actually written
//                             (may be NULL)
//
//  Returns:    same return values as IStream::Write
//
//  History:    4-26-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT WriteDouble(IStream * pstm, double d, ULONG FAR * pcbWritten)
{
    char szTemp[MAX_SIZE];
    sprintf(szTemp,"%.100lg", d);
    WORD cch = strlen(szTemp);
    return(pstm->Write(szTemp, cch + 1, pcbWritten));
}

//+---------------------------------------------------------------------------
//
//  Function:   ReadDouble
//
//  Synopsis:   reads a double from an OLE stream in a machine independent
//              manner
//
//  Arguments:  [pstm]    - pointer to an IStream
//              [pd]      - pointer to the double to be read
//              [pcbRead] - pointer to the number of bytes actually read
//                          (may be NULL)
//
//  Returns:    same return values as IStream::Read
//
//  History:    4-26-94   stevebl   Created
//
//----------------------------------------------------------------------------

HRESULT ReadDouble(IStream * pstm, double * pd, ULONG FAR * pcbRead)
{
    HRESULT hr;
    char szTemp[MAX_SIZE];
    ULONG cbRead;
    DWORD cch = 0;
    do
    {
        hr = pstm->Read(&szTemp[cch], sizeof(char), &cbRead);
        if (pcbRead && SUCCEEDED(hr))
        {
            pcbRead += cbRead;
        }

    } while (SUCCEEDED(hr) && szTemp[cch++]);
    if (SUCCEEDED(hr))
    {
        sscanf(szTemp, "%lf",pd);
    }
    return(hr);
}


STRMHELP.H

//+---------------------------------------------------------------------------
//
//  Microsoft Windows
//  Copyright 1992 - 1998 Microsoft Corporation.
//
//  File:       strmhelp.h
//
//  Contents:   Routines for writting variables to a stream in an
//              architecture independent manner.
//
//  Classes:
//
//  Functions:  WriteDouble
//              ReadDouble
//              SizeDouble
//
//  History:    4-26-94   stevebl   Created
//
//----------------------------------------------------------------------------

#ifndef __STRMHELP_H__
#define __STRMHELP_H__

#ifdef __cplusplus
extern "C" {
#endif

DWORD SizeDouble(void);

HRESULT WriteDouble(IStream * pstm, double d, ULONG FAR * pcbWritten);

HRESULT ReadDouble(IStream * pstm, double * pd, ULONG FAR * pcbRead);

#ifdef __cplusplus
}
#endif

#endif //__STRMHELP_H__


README.TXT

DCOM samples in the Win32 SDK.

OLEIMP   -- The OLEIMP sample demonstrates a method for emulating auto
            impersonation of clients.

SIMPLE   -- The SIMPLE sample demonstrates the minimum requirements to use
            a COM object, whether it is on the same machine or on a different
            machine.  This sample consists of a client and a server portion.

SECURE   -- The SECURE sample consists of a client portion and a server
            portion.  Its purpose is to demonstrate client-side and server-
            side security in a Distributed COM application.

COMPERF  -- The COMPERF sample demonstrates the performance of DCOM in
            several different programming scenarios.

DCOMPERM -- The DCOMPERM sample provides source code and several wrapper
            functions to simplify the process of manipulating the access
            and launch permissions for a COM server. Additionally, this
            sample provides code which demonstrates how to set and retrieve
            the RunAs password for a COM server.
            (sample located in \mstools\samples\win32\winnt\security\dcomperm)


README.TXT

COM Performance Sample

PURPOSE:
This is the reference performance sample for COM/DCOM run on local
and remote machines. This program is both a client and a server.
It launches as a client by default, registering its executable as a
server with COM and creating other instances of itself on a remote and
local machine to be servers. A single command-line argument is allowed
for a remote server name. Performance numbers in calls/sec are output
in tabular form. Tests COM and IDispatch method calls with two security
levels: default/min and full. Use DCOMCNFG to set default security to
Authn Lvl to NONE for best comparison. Parameter sizes are varied to see
effect on security levels(~4/50/4k bytes). Uses psperf.dll MIDL generated
proxy/stub implementation. Put psperf.dll in same dir as this exe on
all machines. Run exe on each machine to automatically register class code
and proxy/stub dll. You can then run on either machine and pass a remote
machine name (DNS or IP address) as single cmd-line parameter.

Instructions:
Install on one or more machines as described above. Run on command-
line as "comperf".
A single command-line argument is allowed for a remote server name.
 E.g. "comperf MyComputer" or "comperf 123.44.44.234" using IP address.
This sample may be compiled as UNICODE or ANSI

Usage: COMPERF [machine name | IP address | /? | -?]
Where:
/?Displays help screen
machine nameName of remote machine
IP addressIP address of remote machine


PSPERF.IDL

// FILE: psperf.idl

[
uuid(DDC68871-E08E-11cf-A535-00AA00615B03),
dual,
oleautomation
]
interface ICOMPerformance : IUnknown
{
import "oaidl.idl";
[id(11)]
HRESULT Test1([in] int l);
[id(12)]
HRESULT Test23([in] BSTR szDesc);
}

[
uuid(DDC68870-E08E-11cf-A535-00AA00615B03)
]
coclass TestCOMPerformance
{
   [default] interface ICOMPerformance;
};


COMPERF.CPP

// ===========================================================================
// File: C O M P E R F . C P P
#define PURPOSE "This is the reference performance sample for COM/DCOM run on local\n\
and remote machines. This program is both a client and a server.\n\
It launches as a client by default, registering its executable as a\n\
server with COM and creating other instances of itself on a remote and\n\
local machine to be servers. A single command-line argument is allowed\n\
for a remote server name. Performance numbers in calls/sec are output\n\
in tabular form. Tests COM and IDispatch method calls with two security\n\
levels: default/min and full. Use DCOMCNFG to set default security to \n\
Authn Lvl to NONE for best comparison. Parameter sizes are varied to see\n\
effect on security levels(~4/50/4k bytes). Uses psoleperf.dll MIDL generated\n\
proxy/stub implementation. Put pscomperf.dll in same dir as this exe on\n\
all machines. Run exe on each machine to automatically register class code\n\
and proxy/stub dll. You can then run on either machine and pass a remote\n\
machine name (DNS or IP address) as single cmd-line parameter.\n\n"

// Instructions:
// Install on one or more machines as described above. Run on command-line as "comperf".
// A single command-line argument is allowed for a remote server name. E.g.
// "comperf MyComputer" or "comperf 123.44.44.234" using IP address.
// This sample may be compiled as UNICODE or ANSI
//
// Copyright 1996 Microsoft Corporation.  All Rights Reserved.
// ===========================================================================

// %%Includes: ---------------------------------------------------------------
#define INC_OLE2
#define STRICT
#define LOOPS 2000  // default number of test function calls
#include <windows.h>
#include <initguid.h>
#include <tchar.h>
#include <stdio.h>
#include "psperf.h" // MIDL generated header

// %%Constants: --------------------------------------------------------------
#define cServerThreads  3 // number support threads each server will generate
#define dispidICOMPerformance_Test1  11
#define dispidICOMPerformance_Test23  12
#define szTitleServer   TEXT("SERVER: COM Performance Sample")
#define szTitleClient   TEXT("COM Performance Sample")
const LARGE_INTEGER     bZero = {0,0};

// %%IDispatch support
PARAMDATA paramdata[2] = {{L"i", VT_I4},{L"bstr", VT_BSTR}};
METHODDATA methoddata[2] = {{L"Test1", &paramdata[0], 11, 3, CC_CDECL, 1, DISPATCH_METHOD, VT_I4},
{L"Test23", &paramdata[1], 12, 4, CC_CDECL, 1, DISPATCH_METHOD, VT_I4}};
INTERFACEDATA interfacedata = { methoddata, 2 };

// %%Guids: ------------------------------------------------------------------
// {DDC68870-E08E-11cf-A535-00AA00615B03}
DEFINE_GUID(CLSID_CTestCOMPerformance,0xddc68870,0xe08e,0x11cf,0xa5,0x35,0x0,0xaa,0x0,0x61,0x5b,0x3);

// %%typedefs: --------------------------------------------------------------
typedef HRESULT (WINAPI *LPFNREG)();
typedef struct  perf
{
    TCHAR   *szTest;
    float    sec[3]; // time for all three methods in ICOMPerformance
} PERF;

// %%Globals: ----------------------------------------------------------------
BOOL        vfServer = FALSE;               // is this instance a server or client?
BOOL        vfRemote = FALSE;               // is there a remote server?
HANDLE      vrghThread[cServerThreads];     // worker thread handles
DWORD       vrgtid[cServerThreads];         // worker thread id's
HANDLE      vrghEvent[cServerThreads];      // creation event for each worker
HANDLE          vhEventCliDone;
HANDLE      vhEventServer;                  // creation-complete event for class-factory
HANDLE          vhEventCliStart;
HANDLE          hKillSvr;                       // shuts down the server
UINT        viNextThread;                   // next worker to create an object on
UINT            g_cObjectCount = 0;
LPSTREAM    vpstmMarshalling;               // scratch stream used for cross-apt marshalling
HRESULT     vhrThreadStatus;                // signals status to class-factory

LPWSTR pszDesc1 = L"String is passed to Test methd of ICOMPerformance";

PERF    vrgperfloc[] = {
    { TEXT("COM FreeToApt"), {-1.0f,-1.0f,-1.0f}},
    { TEXT("IDisp FreeToApt"), {-1.0f,-1.0f,-1.0f}},
    { TEXT("COM FreeTo Free"), {-1.0f,-1.0f,-1.0f}},
    { TEXT("IDispFreeToFree"), {-1.0f,-1.0f,-1.0f}},
    { TEXT("COM AptToApt"), {-1.0f,-1.0f,-1.0f}},
    { TEXT("IDispAptToApt"), {-1.0f,-1.0f,-1.0f}},
    { NULL, {-1.0f,-1.0f,-1.0f}} };

PERF    vrgperfrmt[] = {
    { TEXT("COM FreeToApt"), {-1.0f,-1.0f,-1.0f}},
    { TEXT("IDisp FreeToApt"), {-1.0f,-1.0f,-1.0f}},
    { TEXT("COM FreeTo Free"), {-1.0f,-1.0f,-1.0f}},
    { TEXT("IDispFreeToFree"), {-1.0f,-1.0f,-1.0f}},
    { TEXT("COM AptToApt"), {-1.0f,-1.0f,-1.0f}},
    { TEXT("IDispAptToApt"), {-1.0f,-1.0f,-1.0f}},
    { NULL, {-1.0f,-1.0f,-1.0f}} };

// %%Prototypes: -------------------------------------------------------------
LRESULT     ServerThreadProc(LPARAM lParam);
LRESULT     ClientThreadProc(LPARAM lParam);
BOOL        FAutoRegister();
void            Message(LPTSTR szPrefix, HRESULT hr);
void            FPrintResults(void);
BOOL        AptFreeCOMTest(int cLoops);
BOOL            AptFreeAutoTest(int cLoops);
BOOL            FreeFreeCOMTest(int cLoops);
BOOL            FreeFreeAutoTest(int cLoops);
BOOL            DoTests(void);
void            Usage(void);

typedef IClientSecurity *LPCLIENTSECURITY;
typedef ICOMPerformance *LPCOMPERFORMANCE;
LPCOMPERFORMANCE pCOMApt, pCOMFree, pCOMAptRmt, pCOMFreeRmt;
LPDISPATCH pAutoApt, pAutoFree, pAutoAptRmt, pAutoFreeRmt;

// %%Classes: ----------------------------------------------------------------
// the class-factory object exists in the main application apartment/thread
// and is used to create instances of the worker objects on worker threads.
class CClassFactory : public IClassFactory
{
public:
// IClassFactory
    STDMETHODIMP            QueryInterface(REFIID iid, void **ppv);
    STDMETHODIMP_(ULONG)    AddRef(void)  { return m_cRef++; }
    STDMETHODIMP_(ULONG)    Release(void) { if (--m_cRef == 0){ delete this; return 0; } return m_cRef;}
    STDMETHODIMP            CreateInstance(LPUNKNOWN punkOuter, REFIID iid, void **ppv);
    STDMETHODIMP            LockServer(BOOL fLock);

    CClassFactory() { m_cRef = 1; }
private:
ULONG   m_cRef;
};

// this worker object is simple: it simply supports IUnknown. more interesting
// interfaces can be readily added here and implemented for the worker.
class CTestCOMPerformance : public ICOMPerformance , public IDispatch
{
public:
// IUnknown
    STDMETHODIMP            QueryInterface(REFIID iid, void **ppv);
    STDMETHODIMP_(ULONG)    AddRef(void)    { return m_cRef++; }
    STDMETHODIMP_(ULONG)    Release(void);

// ICOMPerformance
STDMETHODIMP                    Test1(int l);
    STDMETHODIMP                        Test23(BSTR szDesc);

// IDispatch
STDMETHODIMP  GetTypeInfoCount(unsigned int  *pcti);
    STDMETHODIMP  GetTypeInfo(unsigned int iti, LCID lcid, LPTYPEINFO  *ppti);
    STDMETHODIMP  GetIDsOfNames(REFIID riid, WCHAR  *  *rgszNames, unsigned int cNames, LCID lcid, DISPID  *pdispid);
    STDMETHODIMP  Invoke(DISPID dispidMember, REFIID riid, LCID lcid, unsigned short wFlags,
 DISPPARAMS  *pdispparams, VARIANT  *pvarResult, EXCEPINFO  *pexcepinfo, unsigned int  *puArgErr);

    CTestCOMPerformance() { m_cRef = 1;}
private:
    ULONG   m_cRef;
};

class CTIMER
{
  public:
    inline CTIMER()              { memset(this, 0, sizeof(*this)); }
    inline void Start()  { QueryPerformanceCounter(&m_sStart); }
    inline void Stop()   { QueryPerformanceCounter(&m_sStop); }
inline float OutputTime()       { QueryPerformanceFrequency(&m_liFreq);
return (float)(( m_sStop.LowPart - m_sStart.LowPart)/(float)m_liFreq.LowPart);}
    // data members
    LARGE_INTEGER   m_sStart, m_sStop, m_liFreq;
};

// ---------------------------------------------------------------------------
// %%Function: Message
//
//  Formats and displays a message to the console.
// ---------------------------------------------------------------------------
 void
Message(LPTSTR szPrefix, HRESULT hr)
{
    LPTSTR   szMessage;

    if (hr == S_OK)
    {
_tprintf(szPrefix);
_tprintf(TEXT("\n"));
return;
    }

    if (HRESULT_FACILITY(hr) == FACILITY_WINDOWS)
hr = HRESULT_CODE(hr);

    FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
hr,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //The user default language
(LPTSTR)&szMessage,
0,
NULL );

_tprintf(TEXT("%s: %s(%lx)\n"), szPrefix, szMessage, hr);
    LocalFree(szMessage);
}  // Message

// ---------------------------------------------------------------------------
// %%Function: main
// ---------------------------------------------------------------------------
 int __cdecl
main(int argc, CHAR **argv)
{
    HRESULT hr;
    int     i;
    DWORD   dwRegister = 0;
    COSERVERINFO csi, *pcsi=NULL;
    WCHAR wsz [MAX_PATH];
CClassFactory   *pcf = NULL;
LPCLASSFACTORY pcflocal = NULL;
LPCLASSFACTORY pcfrmt = NULL;
LPCLIENTSECURITY pclntsec = NULL;
TCHAR    rgch[32];

DWORD AuthnSvc, AuthzSvc, AuthnLvl, ImpLvl, Capabilities;

if(!FAutoRegister())
{
exit(-1);
}
// parse command-line
    if (argc > 1)
    {
// either started as server or passing in server name
MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, argv[1], -1,
    wsz, MAX_PATH);

// register the CTestCOMPerformance class in the registry so
// that the client can create instances of the server
if((!lstrcmpW(L"-?", wsz)) || (!lstrcmpW(L"/?", wsz)))
{
Usage();
return 0;
}
if(!lstrcmpW(L"-Embedding", wsz))
vfServer = TRUE;
else
{
// allow a machine-name as the command-line argument
csi.dwReserved1 = 0;
csi.pAuthInfo = NULL;
csi.dwReserved2 = 0;
csi.pwszName = wsz;
pcsi = &csi;
vfRemote = TRUE;
}
    }
if(vfServer)
Message(szTitleServer, S_OK);
else
Message(szTitleClient, S_OK);

    // initialize COM
    hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    if (FAILED(hr))
    {
Message(TEXT("CoInitializeEx"), hr);
exit(hr);
    }

// create an IStream to be used for marshalling interfaces
    hr = CreateStreamOnHGlobal(NULL, TRUE, &vpstmMarshalling);
    if (FAILED(hr))
{
Message(TEXT("CreateStreamOnHGlobal"), hr);
goto LCleanup;
}

    if (vfServer)
    {
// create the threads and synchronization events
// which the server will need
for (i=0; i<cServerThreads; i++)
{
    // create the thread suspended so its event can be
    // created using its thread-id and it will be able to
    // use it as soon as it runs
    vrghThread[i] = CreateThread(NULL,
0,
(LPTHREAD_START_ROUTINE)&ServerThreadProc,
0,
CREATE_SUSPENDED,
&vrgtid[i]);
    if (vrghThread[i] == NULL)
{
hr = GetLastError();
goto LCleanup;
}

    // this event signals to a worker thread to create a new CTestCOMPerformance
    wsprintf(rgch, TEXT("Thread_%d"), vrgtid[i]);
    vrghEvent[i] = CreateEvent(NULL, FALSE, FALSE, rgch);
    if (vrghEvent[i] == NULL)
    {
hr = GetLastError();
goto LCleanup;
    }
    // now that the event is available, let the thread run
    ResumeThread(vrghThread[i]);
}
hKillSvr = CreateEvent(NULL, FALSE, FALSE, NULL);
if (hKillSvr == NULL)
    {
    hr = GetLastError();
    goto LCleanup;
 }

// this signals the status of a worker threads creation after
// receiving its create signal via vrghEvent[i]
vhEventServer = CreateEvent(NULL, FALSE, FALSE, TEXT("Server"));
if (vhEventServer == NULL)
{
    hr = GetLastError();
    goto LCleanup;
}
pcf = new CClassFactory;
if(pcf == NULL)
{
    hr = E_OUTOFMEMORY;
    goto LCleanup;
}

// register the class-factory with COM
hr = CoRegisterClassObject(CLSID_CTestCOMPerformance,
    (IUnknown *)pcf,
    CLSCTX_LOCAL_SERVER,
    REGCLS_MULTIPLEUSE,
    &dwRegister);
if (FAILED(hr))
{
Message(TEXT("CoRegisterClassObject"), hr);
    goto LCleanup;
}

Message(TEXT("Server waiting"), S_OK);
WaitForSingleObject(hKillSvr, INFINITE);
Sleep(7000); // allow time for last Release call processing
 delete pcf;
pcf = NULL;
}

    else // client case
    {
pCOMApt = pCOMFree = pCOMAptRmt =pCOMFreeRmt = NULL;
pAutoFree = pAutoFree = pAutoAptRmt = pAutoFreeRmt = NULL;

// get local class factory
hr = CoGetClassObject(CLSID_CTestCOMPerformance, CLSCTX_LOCAL_SERVER, NULL,
IID_IClassFactory, (void**)&pcflocal);
if(FAILED(hr))
{
Message(TEXT("CoGetClassObject:"), hr);
goto LCleanup;
}
// apt-model obj
hr = pcflocal->CreateInstance(NULL, IID_ICOMPerformance, (void**)&pCOMApt);
if (FAILED(hr))
{
Message(TEXT("Create Local Apt Instance:"), hr);
goto LCleanup;
}
hr = pCOMApt->QueryInterface(IID_IDispatch, (void**)&pAutoApt);
if (FAILED(hr))
{
Message(TEXT("QI for pAutoApt:"), hr);
goto LCleanup;
}
// get free-threaded obj
hr = pcflocal->CreateInstance(NULL, IID_ICOMPerformance, (void**)&pCOMFree);
if (FAILED(hr))
{
Message(TEXT("Create Local Free Instance"), hr);
goto LCleanup;
}
hr = pCOMFree->QueryInterface(IID_IDispatch, (void**)&pAutoFree);
if (FAILED(hr))
{
Message(TEXT("QI for pAutoFree"), hr);
goto LCleanup;
}
hr = pcflocal->Release();
pcflocal = NULL;
if(vfRemote)
{
hr = CoGetClassObject(CLSID_CTestCOMPerformance, CLSCTX_REMOTE_SERVER, pcsi,
IID_IClassFactory, (void**)&pcfrmt);
if(FAILED(hr))
{
Message(TEXT("CoGetClassObject for remote CF"), hr);
vfRemote = FALSE;
goto LContinue;
}
// apt-model obj
hr = pcfrmt->CreateInstance(NULL, IID_ICOMPerformance, (void**)&pCOMAptRmt);
if (FAILED(hr))
{
Message(TEXT("Create Remote Instance"), hr);
goto LCleanup;
}
hr = pCOMAptRmt->QueryInterface(IID_IDispatch, (void**)&pAutoAptRmt);
if (FAILED(hr))
{
Message(TEXT("QI for pAutoAptFreeRmt"), hr);
goto LCleanup;
}
// get free-threaded obj
hr = pcfrmt->CreateInstance(NULL, IID_ICOMPerformance, (void**)&pCOMFreeRmt);
if (FAILED(hr))
{
Message(TEXT("Create Remote Free Instance"), hr);
goto LCleanup;
}
hr = pCOMFreeRmt->QueryInterface(IID_IDispatch, (void**)&pAutoFreeRmt);
if (FAILED(hr))
{
Message(TEXT("QI for pAutoFreeFreeRmt"), hr);
goto LCleanup;
}
hr = pcfrmt->Release();
pcfrmt = NULL;
}
LContinue:
// create apartment thread for client
DWORD   dwClienttid;
// create the thread suspended so its event can be
// created using its thread-id and it will be able to
// use it as soon as it runs
HANDLE hClientThread = CreateThread(NULL,
    0,
    (LPTHREAD_START_ROUTINE)&ClientThreadProc,
    0,
    CREATE_SUSPENDED,
    &dwClienttid);
if (hClientThread == NULL)
{
    hr = GetLastError();
goto LCleanup;
}
wsprintf(rgch, TEXT("Thread_%d"), dwClienttid);

vhEventCliStart = CreateEvent(NULL, FALSE, FALSE, rgch);
if (vhEventCliStart == NULL)
{
    hr = GetLastError();
    goto LCleanup;
}
vhEventCliDone = CreateEvent(NULL, FALSE, FALSE, NULL);
if (vhEventCliDone == NULL)
{
    hr = GetLastError();
    goto LCleanup;
}
// Marshall local apt thread
hr = vpstmMarshalling->Seek(bZero, STREAM_SEEK_SET, NULL);
if (FAILED(hr))
    throw hr;
hr = CoMarshalInterface(vpstmMarshalling,
    IID_IUnknown,
    (ICOMPerformance*)pCOMApt,
    MSHCTX_INPROC,
    NULL,
    MSHLFLAGS_NORMAL);
if (FAILED(hr))
    throw hr;
// now that the event is available, let the thread run
ResumeThread(hClientThread);
SetEvent(vhEventCliStart);
WaitForSingleObject(vhEventCliDone, INFINITE);

if(vfRemote && NULL != pCOMAptRmt)
{
// Marshall local apt thread
hr = vpstmMarshalling->Seek(bZero, STREAM_SEEK_SET, NULL);
if (FAILED(hr))
throw hr;
hr = CoMarshalInterface(vpstmMarshalling,
IID_IUnknown,
(ICOMPerformance*)pCOMAptRmt,
MSHCTX_INPROC,
NULL,
MSHLFLAGS_NORMAL);
if (FAILED(hr))
throw hr;
SetEvent(vhEventCliStart);
WaitForSingleObject(vhEventCliDone, INFINITE);
}

// Test
_tprintf(TEXT("Output in Calls per sec\n"));
_tprintf(TEXT("DEF Sec:\t\tLOCAL\t\t\tREMOTE\n"));
// Do all tests
if(!DoTests())
goto LCleanup;
// Up security on proxies
// Get current security
hr = pCOMApt->QueryInterface(IID_IClientSecurity, (void**)&pclntsec);
hr = pclntsec->QueryBlanket(pCOMApt, &AuthnSvc, &AuthzSvc, NULL, &AuthnLvl, &ImpLvl, NULL,
&Capabilities);

_tprintf(TEXT("Increasing local security from AuthnLvl:%d  ImpLvl:%d\n"), AuthnLvl, ImpLvl);

hr = pclntsec->SetBlanket(pCOMApt, AuthnSvc, AuthzSvc, NULL, 6,
4, NULL, Capabilities);
hr = pclntsec->QueryBlanket(pCOMApt, &AuthnSvc, &AuthzSvc, NULL, &AuthnLvl, &ImpLvl, NULL,
&Capabilities);

_tprintf(TEXT("Local security is now AuthnLvl:%d  ImpLvl:%d\n"), AuthnLvl, ImpLvl);

pclntsec->Release();
pclntsec = NULL;
hr = pCOMFree->QueryInterface(IID_IClientSecurity, (void**)&pclntsec);
hr = pclntsec->SetBlanket(pCOMFree, AuthnSvc, AuthzSvc, NULL, 6,
4, NULL, Capabilities);
pclntsec->Release();
pclntsec = NULL;
hr = pAutoApt->QueryInterface(IID_IClientSecurity, (void**)&pclntsec);
hr = pclntsec->SetBlanket(pAutoApt, AuthnSvc, AuthzSvc, NULL, 6,
4, NULL, Capabilities);
pclntsec->Release();
pclntsec = NULL;
hr = pAutoFree->QueryInterface(IID_IClientSecurity, (void**)&pclntsec);
hr = pclntsec->SetBlanket(pAutoFree, AuthnSvc, AuthzSvc, NULL, 6,
4, NULL, Capabilities);
pclntsec->Release();
pclntsec = NULL;
if(vfRemote)
{
hr = pCOMAptRmt->QueryInterface(IID_IClientSecurity, (void**)&pclntsec);
hr = pclntsec->QueryBlanket(pCOMAptRmt, &AuthnSvc, &AuthzSvc, NULL, &AuthnLvl, &ImpLvl, NULL,
&Capabilities);

_tprintf(TEXT("Increasing remote security from AuthnLvl:%d  ImpLvl:%d\n"), AuthnLvl, ImpLvl);

hr = pclntsec->SetBlanket(pCOMAptRmt, AuthnSvc, AuthzSvc, NULL, 6,
4, NULL, Capabilities);

_tprintf(TEXT("Remote security is now AuthnLvl:%d  ImpLvl:%d\n"), AuthnLvl, ImpLvl);

pclntsec->Release();
pclntsec = NULL;
hr = pCOMFreeRmt->QueryInterface(IID_IClientSecurity, (void**)&pclntsec);
hr = pclntsec->SetBlanket(pCOMFreeRmt, AuthnSvc, AuthzSvc, NULL, 6,
4, NULL, Capabilities);
pclntsec->Release();
pclntsec = NULL;
hr = pAutoAptRmt->QueryInterface(IID_IClientSecurity, (void**)&pclntsec);
hr = pclntsec->SetBlanket(pAutoAptRmt, AuthnSvc, AuthzSvc, NULL, 6,
4, NULL, Capabilities);
pclntsec->Release();
pclntsec = NULL;
hr = pAutoFreeRmt->QueryInterface(IID_IClientSecurity, (void**)&pclntsec);
hr = pclntsec->SetBlanket(pAutoFreeRmt, AuthnSvc, AuthzSvc, NULL, 6,
4, NULL, Capabilities);
pclntsec->Release();
pclntsec = NULL;
}
// Do all tests with FULL sec
// Apt to Apt first
// Marshall local apt thread
hr = vpstmMarshalling->Seek(bZero, STREAM_SEEK_SET, NULL);
if (FAILED(hr))
    throw hr;
hr = CoMarshalInterface(vpstmMarshalling,
    IID_IUnknown,
    (ICOMPerformance*)pCOMApt,
    MSHCTX_INPROC,
    NULL,
    MSHLFLAGS_NORMAL);
if (FAILED(hr))
    throw hr;
// now that the event is available, let the thread run
ResumeThread(hClientThread);
SetEvent(vhEventCliStart);
WaitForSingleObject(vhEventCliDone, INFINITE);

if(vfRemote && NULL != pCOMAptRmt)
{
// Marshall local apt thread
hr = vpstmMarshalling->Seek(bZero, STREAM_SEEK_SET, NULL);
if (FAILED(hr))
throw hr;
hr = CoMarshalInterface(vpstmMarshalling,
IID_IUnknown,
(ICOMPerformance*)pCOMAptRmt,
MSHCTX_INPROC,
NULL,
MSHLFLAGS_NORMAL);
if (FAILED(hr))
throw hr;
SetEvent(vhEventCliStart);
WaitForSingleObject(vhEventCliDone, INFINITE);
}
_tprintf(TEXT("Modified Sec:\t\tLOCAL\t\t\tREMOTE\n"));
DoTests();
    }
LCleanup:
if (vpstmMarshalling != NULL)
vpstmMarshalling->Release();
if (vfServer)
    {
// we explicitly don't clean up threads and events
if (dwRegister != 0)
CoRevokeClassObject(dwRegister);
if(NULL != pcf)
delete pcf;
    }
    else // client
{
// client case: release objects
if(NULL != pcflocal)
hr = pcflocal->Release();
if(NULL != pcfrmt)
hr = pcfrmt->Release();
if(NULL != pCOMFree)
hr = pCOMFree->Release();
if(NULL != pAutoFree)
hr = pAutoFree->Release();
if(NULL != pCOMApt)
hr = pCOMApt->Release();
if(NULL != pAutoApt)
hr = pAutoApt->Release();
if(NULL != pCOMFreeRmt)
hr = pCOMFreeRmt->Release();
if(NULL != pAutoFreeRmt)
hr = pAutoFreeRmt->Release();
if(NULL != pCOMAptRmt)
hr = pCOMAptRmt->Release();
if(NULL != pAutoAptRmt)
hr = pAutoAptRmt->Release();
    }
    CoUninitialize();
    return hr;
}  // main

// ---------------------------------------------------------------------------
// %%Function: FAutoRegister
//  Registers the CTestCOMPerformance class in the registry.
// ---------------------------------------------------------------------------
 BOOL
FAutoRegister()
{
    static TCHAR szClassDesc[] = TEXT("COM Performance Sample");
static TCHAR szCLSIDEntry[] = TEXT("CLSID\\{DDC68870-E08E-11cf-A535-00AA00615B03}");
    TCHAR       szBuf[512];
    TCHAR       szPath[512];
    HKEY        hkeyT = NULL;

HRESULT hr;
LPFNREG lpfn = NULL;
HINSTANCE hLib = NULL;

    // register class code
    if ((RegSetValue(HKEY_CLASSES_ROOT, szCLSIDEntry, REG_SZ, szClassDesc,
    lstrlen(szClassDesc)) != ERROR_SUCCESS) ||
(RegCreateKey(HKEY_CLASSES_ROOT, szCLSIDEntry, &hkeyT)
    != ERROR_SUCCESS) ||
!GetModuleFileName(NULL, szBuf, sizeof(szBuf)))
return FALSE;
    lstrcpy(szPath, szBuf);
    if (RegSetValue(hkeyT, TEXT("LocalServer32"), REG_SZ, szBuf, lstrlen(szBuf))
    != ERROR_SUCCESS)
goto LErrExit;
    RegCloseKey(hkeyT);
hkeyT = NULL;
// Register the ICOMPerformance MIDL-generated proxy-stub component
hLib = LoadLibrary(TEXT("psperf.dll"));
if (NULL == hLib)
{
goto  LErrExit;
}
// Find entry point.
lpfn = (LPFNREG)GetProcAddress(hLib, TEXT("DllRegisterServer"));
if (lpfn == NULL)
{
//Message(_T("Couldn't find entry point in DLL"), S_OK); //unable to locate entry point
goto LErrExit;
}
hr = (*lpfn)();

FreeLibrary(hLib);
if(SUCCEEDED(hr))
return TRUE;
LErrExit:
if(NULL != hkeyT)
RegCloseKey(hkeyT);
return FALSE;
}  // FAutoRegister

// ===========================================================================
//                          C C L A S S F A C T O R Y
// ===========================================================================

// ---------------------------------------------------------------------------
// %%Function: CClassFactory::QueryInterface
//  Returns a new reference of the specified iid-type to a CClassFactory.
// ---------------------------------------------------------------------------
 STDMETHODIMP
CClassFactory::QueryInterface(REFIID iid, void **ppv)
{
    *ppv = NULL;

    if (iid == IID_IClassFactory || iid == IID_IUnknown)
{
*ppv = (IClassFactory *)this;
}
    if (*ppv != NULL)
{
AddRef();
return S_OK;
}
    return E_NOINTERFACE;
}  // CClassFactory::QueryInterface

// ---------------------------------------------------------------------------
// %%Function: CClassFactory::CreateInstance
//  Creates a new instance of a CTestCOMPerformance on the next worker thread.
// ---------------------------------------------------------------------------
 STDMETHODIMP
CClassFactory::CreateInstance(LPUNKNOWN punkOuter, REFIID iid, void **ppv)
{
    LPUNKNOWN   punk;
    HRESULT     hr;

    *ppv = NULL;
    if (punkOuter != NULL)
return CLASS_E_NOAGGREGATION;

    // trigger the worker thread that we want to create an object
    SetEvent(vrghEvent[viNextThread]);

    // now wait for the object to signal its completion
    WaitForSingleObject(vhEventServer, INFINITE);

    // once the worker thread signals completion, vhrThreadStatus
    // lets us know if the creation process was successful, and if
    // vpstmMarshalling creates a marshalled interface pointer
    if (FAILED(vhrThreadStatus))
return vhrThreadStatus;

    // unmarshal an IUnknown from the scratch stream. if unmarshaling
    // fails, it takes care of releasing the object inside the marshal-data
    hr = vpstmMarshalling->Seek(bZero, STREAM_SEEK_SET, NULL);
    if (FAILED(hr))
return hr;
    hr = CoUnmarshalInterface(vpstmMarshalling, IID_IUnknown, (void **)&punk);
    if (FAILED(hr))
return hr;

    // get a reference to the interface asked for
    hr = punk->QueryInterface(iid, ppv);
    punk->Release();
++g_cObjectCount;
    viNextThread++;
//    viNextThread %= cServerThreads;

    return hr;
}  // CClassFactory::CreateInstance

 STDMETHODIMP
CClassFactory::LockServer(BOOL fLock)
{
    // there's no need to support this for this sample
    return E_FAIL;
}  // CClassFactory::LockServer

// ===========================================================================
//                               C O B J E C T
// ===========================================================================

// ---------------------------------------------------------------------------
// %%Function: ServerThreadProc
//  The worker thread function. Handles messages for objects of its thread/apt
// and creates new objects.
// ---------------------------------------------------------------------------
 LRESULT
ServerThreadProc(LPARAM lParam)
{
    HRESULT hr;
    MSG     msg;
    int     iThread;

    // figure out which thread this is: it needs its synchronization event
    for (iThread=0; iThread<cServerThreads; iThread++)
{
if (vrgtid[iThread] == GetCurrentThreadId())
    break;
}
    if (iThread==cServerThreads)
return E_UNEXPECTED;

    // initialize COM
if((0 == iThread) || (2 == iThread))
{
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
Message(TEXT("Apartment Thread"), hr);
}
else
{
hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
Message(TEXT("Free-threaded Model"), hr);
}
    if (FAILED(hr))
{
MessageBeep(0);
return hr;
}

    // apartment message/event loop
    // here worker message loops last forever. in situations without a
    // static number of worker threads, the loop could easily be terminated by
    // WM_QUITs sent from the main thread which might manage the worker thread
    // pool more carefully.
    while (TRUE)
{
DWORD dwWaitResult;

// wait for any message sent or posted to this queue
// or for one of the passed handles to become signaled
dwWaitResult = MsgWaitForMultipleObjects(1, &vrghEvent[iThread],
    FALSE, INFINITE, QS_ALLINPUT);

// result tells us the type of event we have:
// a message or a signaled handle

// if there are one or more messages in the queue ...
if (dwWaitResult == (WAIT_OBJECT_0 + 1))
    {
    // dispatch all of the messages in this next loop
    // (here is where we'd check for WM_QUITs to end this
    // worker thread if we wanted to)
    while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
DispatchMessage(&msg);
    }
else
    {
    // this thread was signaled to create a new object
    try
{
LPUNKNOWN   punk;

// create a new CTestCOMPerformance
punk = (ICOMPerformance *)new CTestCOMPerformance;
if (punk == NULL)
    throw E_OUTOFMEMORY;

hr = vpstmMarshalling->Seek(bZero, STREAM_SEEK_SET, NULL);
if (FAILED(hr))
    throw hr;
hr = CoMarshalInterface(vpstmMarshalling,
    IID_IUnknown,
    punk,
    MSHCTX_INPROC,
    NULL,
    MSHLFLAGS_NORMAL);
if (FAILED(hr))
    throw hr;

// punk is now referenced by its marshal-data in vpstmMarshalling.
// we release our local reference here so the unmarshaller will
// have the sole reference. a common mistake is to forget this
// release and end up with orphaned objects in the server.
punk->Release();
vhrThreadStatus = S_OK;
}
    catch (HRESULT hr)
{
vhrThreadStatus = hr;
}
    SetEvent(vhEventServer);
    }

}

    CoUninitialize();
    return msg.wParam;
}  // ServerThreadProc

// ---------------------------------------------------------------------------
// %%Function: ServerThreadProc
//  The worker thread function. Handles messages for objects of its thread/apt
// and creates new objects.

// ---------------------------------------------------------------------------
 LRESULT
ClientThreadProc(LPARAM lParam)
{
    HRESULT hr;
    MSG     msg;
UINT cCount = 0;
hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
    if (FAILED(hr))
    {
return hr;
    }
    while (TRUE)
    {
DWORD dwWaitResult;

// wait for any message sent or posted to this queue
// or for one of the passed handles to become signaled
dwWaitResult = MsgWaitForMultipleObjectsEx(1, &vhEventCliStart,
    INFINITE, QS_SENDMESSAGE, 0);

// result tells us the type of event we have:
// a message or a signaled handle

// if there are one or more messages in the queue ...
if (dwWaitResult == (WAIT_OBJECT_0 + 1))
{
    // dispatch all of the messages in this next loop
    // (here is where we'd check for WM_QUITs to end this
    // worker thread if we wanted to)
    while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
DispatchMessage(&msg);
}
else
{
    // this thread was signaled to test Apt to Apt
    try
    {
//Message(TEXT("Testing Apt to Apt"), hr);
LPUNKNOWN   punk;
LPDISPATCH   pdisp;
// unmarshal an IUnknown from the scratch stream. if unmarshaling
// fails, it takes care of releasing the object inside the marshal-data
hr = vpstmMarshalling->Seek(bZero, STREAM_SEEK_SET, NULL);
if (FAILED(hr))
return hr;
hr = CoUnmarshalInterface(vpstmMarshalling, IID_IUnknown, (void **)&punk);
if (FAILED(hr))
return hr;
ICOMPerformance *pcomperf = NULL;
// get a reference to ICOMPerformance and IDispatch interface
hr = punk->QueryInterface(IID_ICOMPerformance, (void**)&pcomperf);
hr = punk->QueryInterface(IID_IDispatch, (void**)&pdisp);
LONG    i, k;
CTIMER  tmElapsed;
BSTR bstr1, bstr2;
bstr1 = SysAllocString(pszDesc1);
bstr2 = SysAllocStringByteLen((const char *)pszDesc1, 4096);
// Test1
tmElapsed.Start();
for (i=0; i<LOOPS; i++)
k = (LONG)pcomperf->Test1(i);
tmElapsed.Stop();
if(cCount && vfRemote)
vrgperfrmt[4].sec[0] = tmElapsed.OutputTime();
else
vrgperfloc[4].sec[0] = tmElapsed.OutputTime();
// Test2
tmElapsed.Start();
for (i=0; i<LOOPS; i++)
k = (LONG)pcomperf->Test23(bstr1);
tmElapsed.Stop();
if(cCount && vfRemote)
vrgperfrmt[4].sec[1] = tmElapsed.OutputTime();
else
vrgperfloc[4].sec[1] = tmElapsed.OutputTime();
// Test3
tmElapsed.Start();
for (i=0; i<LOOPS; i++)
k = (LONG)pcomperf->Test23(bstr2);
tmElapsed.Stop();
if(cCount && vfRemote)
vrgperfrmt[4].sec[2] = tmElapsed.OutputTime();
else
vrgperfloc[4].sec[2] = tmElapsed.OutputTime();
// Now IDispatch
VARIANTARG  rgvt[1];
DISPPARAMS  dispparams = { rgvt, NULL, 1, 0 };
EXCEPINFO  excepinfo;

VARIANT vtResult;
UINT    argerr;
// Test1
tmElapsed.Start();
V_VT(&vtResult) = VT_HRESULT;
for (i=0; i<LOOPS; i++)
{
V_VT(&rgvt[0]) = VT_I4;
V_I4(&rgvt[0]) = i;
hr = pdisp->Invoke(dispidICOMPerformance_Test1, IID_NULL, 0, DISPATCH_METHOD, &dispparams,
&vtResult, &excepinfo, &argerr);
k = V_I4(&vtResult);
}
tmElapsed.Stop();
if(cCount && vfRemote)
vrgperfrmt[5].sec[0] = tmElapsed.OutputTime();
else
vrgperfloc[5].sec[0] = tmElapsed.OutputTime();
// Test2
tmElapsed.Start();
V_VT(&vtResult) = VT_HRESULT;
for (i=0; i<LOOPS; i++)
{
V_VT(&rgvt[0]) = VT_BSTR;
V_BSTR(&rgvt[0]) = bstr1;
hr = pdisp->Invoke(dispidICOMPerformance_Test23, IID_NULL, 0, DISPATCH_METHOD, &dispparams,
&vtResult, &excepinfo, &argerr);
k = V_I4(&vtResult);
}
tmElapsed.Stop();
if(cCount && vfRemote)
vrgperfrmt[5].sec[1] = tmElapsed.OutputTime();
else
vrgperfloc[5].sec[1] = tmElapsed.OutputTime();
// Test3
tmElapsed.Start();
V_VT(&vtResult) = VT_HRESULT;
for (i=0; i<LOOPS; i++)
{
V_VT(&rgvt[0]) = VT_BSTR;
V_BSTR(&rgvt[0]) = bstr2;
hr = pdisp->Invoke(dispidICOMPerformance_Test23, IID_NULL, 0, DISPATCH_METHOD, &dispparams,
&vtResult, &excepinfo, &argerr);
k = V_I4(&vtResult);
}
tmElapsed.Stop();
if(cCount && vfRemote)
vrgperfrmt[5].sec[2] = tmElapsed.OutputTime();
else
vrgperfloc[5].sec[2] = tmElapsed.OutputTime();
SysFreeString(bstr1);
SysFreeString(bstr2);
hr = pcomperf->Release();
hr = pdisp->Release();
hr = punk->Release();

vhrThreadStatus = S_OK;
    }
    catch (HRESULT hr)
    {
vhrThreadStatus = hr;
}
    SetEvent(vhEventCliDone);
if(cCount)
--cCount;
else
++cCount;
}
    }
    CoUninitialize();
    return msg.wParam;
}  // ClientThreadProc
// ---------------------------------------------------------------------------
// %%Function: CTestCOMPerformance::QueryInterface
//  Returns a new reference of the specified iid-type to a CTestCOMPerformance.
// ---------------------------------------------------------------------------
 STDMETHODIMP
CTestCOMPerformance::QueryInterface(REFIID iid, void **ppv)
{
    *ppv = NULL;

    if (iid == IID_IUnknown || iid == IID_ICOMPerformance)
    {
*ppv = (ICOMPerformance *)this;
    }
else if (iid == IID_IDispatch)
*ppv = (IDispatch *)this;
    if (*ppv == NULL)
    {
return E_NOINTERFACE;
    }
AddRef();
    return S_OK;
}  // CTestCOMPerformance::QueryInterface

// ---------------------------------------------------------------------------
// %%Function: CTestCOMPerformance::Release
//  Handles releases of references to a CTestCOMPerformance. Purpose here is to have code
// which alters the global state which is displayed in the servers UI.
// ---------------------------------------------------------------------------
 STDMETHODIMP_(ULONG)
CTestCOMPerformance::Release(void)
{
if (--m_cRef == 0)
    {
--g_cObjectCount;
delete this;
Message(TEXT("Object Deleted"), S_OK);
if(g_cObjectCount == 0) SetEvent(hKillSvr);
return 0;
    }
    return m_cRef;
}  // CTestCOMPerformance::Release

// ---------------------------------------------------------------------------
// %%Function: CTestCOMPerformance::GetTypeInfoCount
// ---------------------------------------------------------------------------
 STDMETHODIMP
CTestCOMPerformance::GetTypeInfoCount(UINT  *pctInfo)
{
    *pctInfo = 1;
    return S_OK;
}  // CTestCOMPerformance::GetTypeInfoCount

// ---------------------------------------------------------------------------
// %%Function: CTestCOMPerformance::GetTypeInfo
// ---------------------------------------------------------------------------
 STDMETHODIMP
CTestCOMPerformance::GetTypeInfo(unsigned int itInfo, LCID lcid, LPTYPEINFO  *ppti)
{
HRESULT hr;

    if (ppti == NULL)
return E_POINTER;
hr = CreateDispTypeInfo( &interfacedata, 0, ppti);
    return hr;
}  // CTestCOMPerformance::GetTypeInfo

// ---------------------------------------------------------------------------
// %%Function: CTestCOMPerformance::GetIDsOfNames
// ---------------------------------------------------------------------------
 STDMETHODIMP
CTestCOMPerformance::GetIDsOfNames(REFIID riid, WCHAR  *  *rgszNames, unsigned int cNames, LCID lcid, DISPID  *pdispid)
{
    return E_NOTIMPL;
}  // CTestCOMPerformance::GetIDsOfNames

// ---------------------------------------------------------------------------
// %%Function: CTestCOMPerformance::Invoke
// ---------------------------------------------------------------------------
 STDMETHODIMP
CTestCOMPerformance::Invoke(DISPID dispidMember, REFIID riid, LCID lcid, unsigned short wFlags, DISPPARAMS  *pdispparams, VARIANT  *pvarResult,
  EXCEPINFO  *pexcepinfo, unsigned int  *puArgErr)
{
    LPTYPEINFO pti = NULL;
HRESULT hr = GetTypeInfo(0, lcid, &pti);
if(FAILED(hr))
return hr;

hr =  pti->Invoke((ICOMPerformance*)this, dispidMember,
    wFlags, pdispparams, pvarResult,
    pexcepinfo, puArgErr);
hr = pti->Release();
return hr;
}  // CTestCOMPerformance::Invoke

 STDMETHODIMP
CTestCOMPerformance::Test1(int l)
{
 return S_OK;
}

 STDMETHODIMP
CTestCOMPerformance::Test23(BSTR bstr)
{
 return S_OK;
}

// Tests
 BOOL
AptFreeCOMTest(int cLoops)
{
LONG    i, k;
    CTIMER  tmElapsed;
BSTR bstr1, bstr2;
bstr1 = SysAllocString(pszDesc1);
bstr2 = SysAllocStringByteLen((const char *)pszDesc1, 4096);
// Local
// Test1
    tmElapsed.Start();
    for (i=0; i<cLoops; i++)
k = (LONG)pCOMApt->Test1(i);
    tmElapsed.Stop();
    vrgperfloc[0].sec[0] = tmElapsed.OutputTime();
// Test2
    tmElapsed.Start();
    for (i=0; i<cLoops; i++)
k = (LONG)pCOMApt->Test23(bstr1);
    tmElapsed.Stop();
    vrgperfloc[0].sec[1] = tmElapsed.OutputTime();
// Test3
    tmElapsed.Start();
for (i=0; i<cLoops; i++)
k = (LONG)pCOMApt->Test23(bstr2);
    tmElapsed.Stop();
    vrgperfloc[0].sec[2] = tmElapsed.OutputTime();

// Remote
if(vfRemote)
{
tmElapsed.Start();
for (i=0; i<cLoops; i++)
k = (LONG)pCOMAptRmt->Test1(i);
tmElapsed.Stop();
vrgperfrmt[0].sec[0] = tmElapsed.OutputTime();
// Test2
tmElapsed.Start();
for (i=0; i<cLoops; i++)
k = (LONG)pCOMAptRmt->Test23(bstr1);
tmElapsed.Stop();
vrgperfrmt[0].sec[1] = tmElapsed.OutputTime();
// Test3
tmElapsed.Start();
for (i=0; i<cLoops; i++)
k = (LONG)pCOMAptRmt->Test23(bstr2);
tmElapsed.Stop();
vrgperfrmt[0].sec[2] = tmElapsed.OutputTime();

}
SysFreeString(bstr1);
SysFreeString(bstr2);
return TRUE;
}

 BOOL
AptFreeAutoTest(int cLoops)
{
HRESULT hr;
static VARIANTARG  rgvt[1];
    static DISPPARAMS  dispparams = { rgvt, NULL, 1, 0 };
    static EXCEPINFO  excepinfo;
    VARIANT vtResult;
    UINT    argerr;
    LONG    i, k;
    CTIMER  tmElapsed;
BSTR bstr1, bstr2;
bstr1 = SysAllocString(pszDesc1);
bstr2 = SysAllocStringByteLen((const char *)pszDesc1, 4096);

// Test1
    tmElapsed.Start();
    V_VT(&vtResult) = VT_HRESULT;
    for (i=0; i<cLoops; i++)
{
V_VT(&rgvt[0]) = VT_I4;
V_I4(&rgvt[0]) = i;
hr = pAutoApt->Invoke(dispidICOMPerformance_Test1, IID_NULL, 0, DISPATCH_METHOD, &dispparams,
&vtResult, &excepinfo, &argerr);
k = V_I4(&vtResult);
}
    tmElapsed.Stop();
    vrgperfloc[1].sec[0] = tmElapsed.OutputTime();
// Test2
tmElapsed.Start();
    V_VT(&vtResult) = VT_HRESULT;
    for (i=0; i<cLoops; i++)
{
V_VT(&rgvt[0]) = VT_BSTR;
V_BSTR(&rgvt[0]) = bstr1;
hr = pAutoApt->Invoke(dispidICOMPerformance_Test23, IID_NULL, 0, DISPATCH_METHOD, &dispparams,
&vtResult, &excepinfo, &argerr);
k = V_I4(&vtResult);
}
    tmElapsed.Stop();
    vrgperfloc[1].sec[1] = tmElapsed.OutputTime();
// Test3
tmElapsed.Start();
    V_VT(&vtResult) = VT_HRESULT;
    for (i=0; i<cLoops; i++)
{
V_VT(&rgvt[0]) = VT_BSTR;
V_BSTR(&rgvt[0]) = bstr2;
hr = pAutoApt->Invoke(dispidICOMPerformance_Test23, IID_NULL, 0, DISPATCH_METHOD, &dispparams,
&vtResult, &excepinfo, &argerr);
k = V_I4(&vtResult);
}
    tmElapsed.Stop();
    vrgperfloc[1].sec[2] = tmElapsed.OutputTime();
// Remote
if(vfRemote)
{
// Test1
tmElapsed.Start();
V_VT(&vtResult) = VT_HRESULT;
for (i=0; i<cLoops; i++)
{
V_VT(&rgvt[0]) = VT_I4;
V_I4(&rgvt[0]) = i;
hr = pAutoAptRmt->Invoke(dispidICOMPerformance_Test1, IID_NULL, 0, DISPATCH_METHOD, &dispparams,
&vtResult, &excepinfo, &argerr);
k = V_I4(&vtResult);
}
tmElapsed.Stop();
vrgperfrmt[1].sec[0] = tmElapsed.OutputTime();
// Test2
tmElapsed.Start();
V_VT(&vtResult) = VT_HRESULT;
for (i=0; i<cLoops; i++)
{
V_VT(&rgvt[0]) = VT_BSTR;
V_BSTR(&rgvt[0]) = bstr1;
hr = pAutoAptRmt->Invoke(dispidICOMPerformance_Test23, IID_NULL, 0, DISPATCH_METHOD, &dispparams,
&vtResult, &excepinfo, &argerr);
k = V_I4(&vtResult);
}
tmElapsed.Stop();
vrgperfrmt[1].sec[1] = tmElapsed.OutputTime();
// Test3
tmElapsed.Start();
V_VT(&vtResult) = VT_HRESULT;
for (i=0; i<cLoops; i++)
{
V_VT(&rgvt[0]) = VT_BSTR;
V_BSTR(&rgvt[0]) = bstr2;
hr = pAutoAptRmt->Invoke(dispidICOMPerformance_Test23, IID_NULL, 0, DISPATCH_METHOD, &dispparams,
&vtResult, &excepinfo, &argerr);
k = V_I4(&vtResult);
}
tmElapsed.Stop();
vrgperfrmt[1].sec[2] = tmElapsed.OutputTime();
}

SysFreeString(bstr1);
SysFreeString(bstr2);
return TRUE;
}

 BOOL
FreeFreeCOMTest(int cLoops)
{
LONG    i, k;
    CTIMER  tmElapsed;
BSTR bstr1, bstr2;
bstr1 = SysAllocString(pszDesc1);
bstr2 = SysAllocStringByteLen((const char *)pszDesc1, 4096);

// Test1
    tmElapsed.Start();
    for (i=0; i<cLoops; i++)
k = (LONG)pCOMFree->Test1(i);
    tmElapsed.Stop();
    vrgperfloc[2].sec[0] = tmElapsed.OutputTime();
// Test2
    tmElapsed.Start();
    for (i=0; i<cLoops; i++)
k = (LONG)pCOMFree->Test23(bstr1);
    tmElapsed.Stop();
    vrgperfloc[2].sec[1] = tmElapsed.OutputTime();
// Test3
    tmElapsed.Start();
for (i=0; i<cLoops; i++)
{
k = (LONG)pCOMFree->Test23(bstr2);
}
    tmElapsed.Stop();
    vrgperfloc[2].sec[2] = tmElapsed.OutputTime();

// Remote
if(vfRemote)
{
// Test1
    tmElapsed.Start();
    for (i=0; i<cLoops; i++)
k = (LONG)pCOMFreeRmt->Test1(i);
tmElapsed.Stop();
vrgperfrmt[2].sec[0] = tmElapsed.OutputTime();
// Test2
tmElapsed.Start();
for (i=0; i<cLoops; i++)
k = (LONG)pCOMFreeRmt->Test23(bstr1);
tmElapsed.Stop();
vrgperfrmt[2].sec[1] = tmElapsed.OutputTime();
// Test3
tmElapsed.Start();
for (i=0; i<cLoops; i++)
{
k = (LONG)pCOMFreeRmt->Test23(bstr2);
}
tmElapsed.Stop();
vrgperfrmt[2].sec[2] = tmElapsed.OutputTime();
}

SysFreeString(bstr1);
SysFreeString(bstr2);
return TRUE;
}

 BOOL
FreeFreeAutoTest(int cLoops)
{
HRESULT hr;
static VARIANTARG  rgvt[1];
    static DISPPARAMS  dispparams = { rgvt, NULL, 1, 0 };
    static EXCEPINFO  excepinfo;
    VARIANT vtResult;
    UINT    argerr;
    LONG    i, k;
    CTIMER  tmElapsed;
BSTR bstr1, bstr2;
bstr1 = SysAllocString(pszDesc1);
bstr2 = SysAllocStringByteLen((const char *)pszDesc1, 4096);

// Local
// Test1
    tmElapsed.Start();
    V_VT(&vtResult) = VT_HRESULT;
    for (i=0; i<cLoops; i++)
{
V_VT(&rgvt[0]) = VT_I4;
V_I4(&rgvt[0]) = i;
hr = pAutoFree->Invoke(dispidICOMPerformance_Test1, IID_NULL, 0, DISPATCH_METHOD, &dispparams,
&vtResult, &excepinfo, &argerr);
k = V_I4(&vtResult);
}
    tmElapsed.Stop();
    vrgperfloc[3].sec[0] = tmElapsed.OutputTime();
// Test2
tmElapsed.Start();
    V_VT(&vtResult) = VT_HRESULT;
    for (i=0; i<cLoops; i++)
{
V_VT(&rgvt[0]) = VT_BSTR;
V_BSTR(&rgvt[0]) = bstr1;
hr = pAutoFree->Invoke(dispidICOMPerformance_Test23, IID_NULL, 0, DISPATCH_METHOD, &dispparams,
&vtResult, &excepinfo, &argerr);
k = V_I4(&vtResult);
}
    tmElapsed.Stop();
    vrgperfloc[3].sec[1] = tmElapsed.OutputTime();
// Test3
tmElapsed.Start();
    V_VT(&vtResult) = VT_HRESULT;
    for (i=0; i<cLoops; i++)
{
V_VT(&rgvt[0]) = VT_BSTR;
V_BSTR(&rgvt[0]) = bstr2;
hr = pAutoFree->Invoke(dispidICOMPerformance_Test23, IID_NULL, 0, DISPATCH_METHOD, &dispparams,
&vtResult, &excepinfo, &argerr);
k = V_I4(&vtResult);
}
    tmElapsed.Stop();
    vrgperfloc[3].sec[2] = tmElapsed.OutputTime();

// Remote
if(vfRemote)
{
// Test1
tmElapsed.Start();
V_VT(&vtResult) = VT_HRESULT;
for (i=0; i<cLoops; i++)
{
V_VT(&rgvt[0]) = VT_I4;
V_I4(&rgvt[0]) = i;
hr = pAutoFreeRmt->Invoke(dispidICOMPerformance_Test1, IID_NULL, 0, DISPATCH_METHOD, &dispparams,
&vtResult, &excepinfo, &argerr);
k = V_I4(&vtResult);
}
tmElapsed.Stop();
vrgperfrmt[3].sec[0] = tmElapsed.OutputTime();
// Test2
tmElapsed.Start();
V_VT(&vtResult) = VT_HRESULT;
for (i=0; i<cLoops; i++)
{
V_VT(&rgvt[0]) = VT_BSTR;
V_BSTR(&rgvt[0]) = bstr1;
hr = pAutoFreeRmt->Invoke(dispidICOMPerformance_Test23, IID_NULL, 0, DISPATCH_METHOD, &dispparams,
&vtResult, &excepinfo, &argerr);
k = V_I4(&vtResult);
}
tmElapsed.Stop();
vrgperfrmt[3].sec[1] = tmElapsed.OutputTime();
// Test3
tmElapsed.Start();
V_VT(&vtResult) = VT_HRESULT;
for (i=0; i<cLoops; i++)
{
V_VT(&rgvt[0]) = VT_BSTR;
V_BSTR(&rgvt[0]) = bstr2;
hr = pAutoFreeRmt->Invoke(dispidICOMPerformance_Test23, IID_NULL, 0, DISPATCH_METHOD, &dispparams,
&vtResult, &excepinfo, &argerr);
k = V_I4(&vtResult);
}
tmElapsed.Stop();
vrgperfrmt[3].sec[2] = tmElapsed.OutputTime();
}
SysFreeString(bstr1);
SysFreeString(bstr2);
return TRUE;
}

 void
FPrintResults()
{
_tprintf(TEXT("Data Size\t\t4\t50\t4k\t4\t50\t4k\n"));
_tprintf(TEXT("%s\t\t"), vrgperfloc[0].szTest);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfloc[0].sec[0]);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfloc[0].sec[1]);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfloc[0].sec[2]);
if(vfRemote)
{
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfrmt[0].sec[0]);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfrmt[0].sec[1]);
_tprintf(TEXT("%4.2f\n"), LOOPS/vrgperfrmt[0].sec[2]);
}
else
{
_tprintf(TEXT("--\t"));
_tprintf(TEXT("--\t"));
_tprintf(TEXT("--\n"));
}
_tprintf(TEXT("%s\t\t"), vrgperfloc[1].szTest);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfloc[1].sec[0]);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfloc[1].sec[1]);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfloc[1].sec[2]);
if(vfRemote)
{
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfrmt[1].sec[0]);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfrmt[1].sec[1]);
_tprintf(TEXT("%4.2f\n"), LOOPS/vrgperfrmt[1].sec[2]);
}
else
{
_tprintf(TEXT("--\t"));
_tprintf(TEXT("--\t"));
_tprintf(TEXT("--\n"));
}
_tprintf(TEXT("%s\t\t"), vrgperfloc[2].szTest);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfloc[2].sec[0]);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfloc[2].sec[1]);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfloc[2].sec[2]);
if(vfRemote)
{
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfrmt[2].sec[0]);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfrmt[2].sec[1]);
_tprintf(TEXT("%4.2f\n"), LOOPS/vrgperfrmt[2].sec[2]);
}
else
{
_tprintf(TEXT("--\t"));
_tprintf(TEXT("--\t"));
_tprintf(TEXT("--\n"));
}
_tprintf(TEXT("%s\t\t"), vrgperfloc[3].szTest);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfloc[3].sec[0]);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfloc[3].sec[1]);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfloc[3].sec[2]);
if(vfRemote)
{
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfrmt[3].sec[0]);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfrmt[3].sec[1]);
_tprintf(TEXT("%4.2f\n"), LOOPS/vrgperfrmt[3].sec[2]);
}
else
{
_tprintf(TEXT("--\t"));
_tprintf(TEXT("--\t"));
_tprintf(TEXT("--\n"));
}
_tprintf(TEXT("%s\t\t"), vrgperfloc[4].szTest);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfloc[4].sec[0]);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfloc[4].sec[1]);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfloc[4].sec[2]);
if(vfRemote)
{
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfrmt[4].sec[0]);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfrmt[4].sec[1]);
_tprintf(TEXT("%4.2f\n"), LOOPS/vrgperfrmt[4].sec[2]);
}
else
{
_tprintf(TEXT("--\t"));
_tprintf(TEXT("--\t"));
_tprintf(TEXT("--\n"));
}
_tprintf(TEXT("%s\t\t"), vrgperfloc[5].szTest);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfloc[5].sec[0]);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfloc[5].sec[1]);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfloc[5].sec[2]);
if(vfRemote)
{
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfrmt[5].sec[0]);
_tprintf(TEXT("%4.2f\t"), LOOPS/vrgperfrmt[5].sec[1]);
_tprintf(TEXT("%4.2f\n"), LOOPS/vrgperfrmt[5].sec[2]);
}
else
{
_tprintf(TEXT("--\t"));
_tprintf(TEXT("--\t"));
_tprintf(TEXT("--\n"));
}

}

 BOOL
DoTests(void)
{
if(!AptFreeCOMTest(LOOPS))
return FALSE;
if(!AptFreeAutoTest(LOOPS))
return FALSE;
if(!FreeFreeCOMTest(LOOPS))
return FALSE;
if(!FreeFreeAutoTest(LOOPS))
return FALSE;
FPrintResults();
return TRUE;
} // DoTests

void Usage()
{
_tprintf(TEXT("Usage: COMPERF [machine name | IP address | /? | -?]\n\n"));
_tprintf(TEXT("/?\t\t\tDisplays this help screen.\n"));
_tprintf(TEXT("machine name\t\tName of remote machine.\n"));
_tprintf(TEXT("IP address\t\tIP address of remote machine.\n\n"));
_tprintf("%s", PURPOSE);
}

// EOF =======================================================================


README.TXT

DCOMPERM
========

The DCOMPERM sample provides source code and several wrapper functions to
simplify the process of manipulating the access and launch permissions for
a COM server. Additionally, this sample provides code which demonstrates how
to set and retrieve the RunAs password for a COM server.

Wrapper Functions
-----------------

ChangeDefaultAccessACL ------------------------------------------------------

DWORD
ChangeDefaultAccessACL (
    LPTSTR Principal,
    BOOL SetPrincipal,
    BOOL Permit
    );

Description:

Modify the default access access control list. The system uses the default
access ACL to determine if a principal is allowed to access the COM server
if the COM server does not have its own access ACL in the AppID section of
the registry.

Parameters:

    Principal               Name of user or group (e.g. "redmond\johndoe")

    SetPrincipal            TRUE if you want to add/update the principal's
                            entry in the ACL
                            FALSE if you want to remove the principal from
                            the ACL

    Permit                  TRUE if you want to allow the principal to access
                            the object
                            FALSE if you want to prevent the principal from
                            accessing the object

    (Note that the Permit flag applies only when SetPrincipal is TRUE.)

ChangeAppIDAccessACL --------------------------------------------------------

DWORD
ChangeAppIDAccessACL (
    LPTSTR AppID,
    LPTSTR Principal,
    BOOL SetPrincipal,
    BOOL Permit
    );

Modify an AppID's access access control list. The system uses the AppID
access ACL to determine if a principal is allowed to access the COM server
associated with the AppID.

Parameters:
    AppID                   The Application ID you wish to modify
                            (e.g. "{99999999-9999-9999-9999-00AA00BBF7C7}")

    Principal               Name of user or group (e.g. "redmond\johndoe")

    SetPrincipal            TRUE if you want to add the principal to the ACL
                            FALSE if you want to remove the principal from
                            the ACL

    Permit                  TRUE if you want to allow the principal to access
                            the object
                            FALSE if you want to prevent the principal from
                            accessing the object

    (Note that the Permit flag applies only when SetPrincipal is TRUE.)

ChangeDefaultLaunchACL ------------------------------------------------------

DWORD
ChangeDefaultLaunchACL (
    LPTSTR Principal,
    BOOL SetPrincipal,
    BOOL Permit
    );

Modify the default launch access control list. The system uses the
default launch ACL to determine if a principal is allowed to launch a
COM server if the COM server does not have its own launch ACL in the AppID
section of the registry.

Parameters:

    Principal               Name of user or group (e.g. "redmond\johndoe")

    SetPrincipal            TRUE if you want to add/update the principal's
                            entry in the ACL
                            FALSE if you want to remove the principal from
                            the ACL

    Permit                  TRUE if you want to allow the principal to launch
                            the object
                            FALSE if you want to prevent the principal from
                            launching the object

    (Note that the Permit flag applies only when SetPrincipal is TRUE.)

ChangeAppIDLaunchACL --------------------------------------------------------

DWORD
ChangeAppIDLaunchACL (
    LPTSTR AppID,
    LPTSTR Principal,
    BOOL SetPrincipal,
    BOOL Permit
    );

Modify an AppID's launch access control list. The system uses the AppID
launch ACL to determine if a principal (a user or group of users) is allowed
to launch the COM server associated with the AppID.

Parameters:
    AppID                   The Application ID you set permissions for
                            (e.g. "{99999999-9999-9999-9999-00AA00BBF7C7}")

    Principal               Name of user or group (e.g. "redmond\johndoe")

    SetPrincipal            TRUE if you want to add the principal to the ACL
                            FALSE if you want to remove the principal from
                            the ACL

    Permit                  TRUE if you want to allow the principal to launch
                            the object
                            FALSE if you want to prevent the principal from
                            launching the object

    (Note that the Permit flag applies only when SetPrincipal is TRUE.)

GetRunAsPassword ------------------------------------------------------------

DWORD GetRunAsPassword (
    LPTSTR AppID,
    LPTSTR Password
    );

Description:

Retrieves the RunAs password for an AppID.

Parameters:

    AppID                   The Application ID you wish to configure
                            (e.g. "{99999999-9999-9999-9999-00AA00BBF7C7}")

    Password                Password of the user you have specified in
                            the RunAs named value under the AppID registry
                            key.

SetRunAsPassword ------------------------------------------------------------

DWORD SetRunAsPassword (
    LPTSTR AppID,
    LPTSTR Principal,
    LPTSTR Password
    );

Description:

Sets the RunAs password for an AppID. Note that if you have specified the
RunAs named value to "Interactive User" you do not need to set the RunAs
password.

Parameters:

    AppID                   The Application ID you wish to configure
                            (e.g. "{99999999-9999-9999-9999-00AA00BBF7C7}")

    Principal    Name of the principal you have specified in the
    RunAs named value under the AppID registry key

    Password                Password of the principal you have specified in
                            the RunAs named value under the AppID registry
                            key.


DCOMPERM.CPP

/*++

DCOM Permission Configuration Sample
Copyright (c) 1996, Microsoft Corporation. All rights reserved.

Module Name:

    dcomperm.cpp

Abstract:

    Main module for DCOM Permission Configuration Sample

Author:

    Michael Nelson

Environment:

    Windows NT

--*/

#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <tchar.h>
#include "ntsecapi.h"
#include "dcomperm.h"

void
ShowUsage (
    LPTSTR ErrorString
    )
{
    _tprintf (TEXT("%s\n"), ErrorString);
    _tprintf (TEXT("Syntax: dcomperm <option> [...]\n\n"));

    _tprintf (TEXT("Options:\n"));

    _tprintf (TEXT("   -da <\"set\" or \"remove\"> <Principal Name> [\"permit\" or \"deny\"]\n"));
    _tprintf (TEXT("   -da list\n"));
    _tprintf (TEXT("       Modify or list the default access permission list\n\n"));

    _tprintf (TEXT("   -dl <\"set\" or \"remove\"> <Principal Name> [\"permit\" or \"deny\"]\n"));
    _tprintf (TEXT("   -dl list\n"));
    _tprintf (TEXT("       Modify or list the default launch permission list\n\n"));

    _tprintf (TEXT("   -aa <AppID> <\"set\" or \"remove\"> <Principal Name> [\"permit\" or \"deny\"]\n"));
    _tprintf (TEXT("   -aa <AppID> default\n"));
    _tprintf (TEXT("   -aa <AppID> list\n"));
    _tprintf (TEXT("       Modify or list the access permission list for a specific AppID\n\n"));

    _tprintf (TEXT("   -al <AppID> <\"set\" or \"remove\"> <Principal Name> [\"permit\" or \"deny\"]\n"));
    _tprintf (TEXT("   -al <AppID> default\n"));
    _tprintf (TEXT("   -al <AppID> list\n"));
    _tprintf (TEXT("       Modify or list the launch permission list for a specific AppID\n\n"));

    _tprintf (TEXT("Press any key to continue. . ."));
    _getch();
    _tprintf (TEXT("\r                               \r"));

    _tprintf (TEXT("   -runas <AppID> <Principal Name> <Password>\n"));
    _tprintf (TEXT("   -runas <AppID> \"Interactive User\"\n"));
    _tprintf (TEXT("       Set the RunAs information for a specific AppID\n\n"));

    _tprintf (TEXT("Examples:\n"));
    _tprintf (TEXT("   dcomperm -da set redmond\\t-miken permit\n"));
    _tprintf (TEXT("   dcomperm -dl set redmond\\jdoe deny\n"));
    _tprintf (TEXT("   dcomperm -aa {12345678-1234-1234-1234-00aa00bbf7c7} list\n"));
    _tprintf (TEXT("   dcomperm -al {12345678-1234-1234-1234-00aa00bbf7c7} remove redmond\\t-miken\n"));
    _tprintf (TEXT("   dcomperm -runas {12345678-1234-1234-1234-00aa00bbf7c7} redmond\\jdoe password\n"));

    exit (0);
}

void
Error (
    LPTSTR ErrorMessage,
    DWORD ErrorCode
    )
{
    TCHAR messageBuffer [255];

    _tprintf (TEXT("%s\n%s"), ErrorMessage, SystemMessage (messageBuffer, ErrorCode));
    exit (0);
}

void
HandleDAOption (
    int argc,
    TCHAR **argv
    )
{
    DWORD returnValue;

    if (argc < 3)
        ShowUsage (TEXT("Invalid number of arguments."));

    if (_tcscmp (_tcsupr (argv [2]), TEXT("LIST")) == 0)
    {
        _tprintf (TEXT("Default access permission list:\n\n"));
        ListDefaultAccessACL();
        return;
    }

    if (argc < 4)
        ShowUsage (TEXT("Invalid number of arguments."));

    if (_tcscmp (_tcsupr (argv [2]), TEXT("SET")) == 0)
    {
        if (argc < 5)
            ShowUsage (TEXT("Invalid number of arguments."));

        if (_tcscmp (_tcsupr (argv [4]), TEXT("PERMIT")) == 0)
            returnValue = ChangeDefaultAccessACL (argv [3], TRUE, TRUE); else

        if (_tcscmp (_tcsupr (argv [4]), TEXT("DENY")) == 0)
            returnValue = ChangeDefaultAccessACL (argv [3], TRUE, FALSE); else
        {
            ShowUsage (TEXT("You can only set a user's permissions to \"permit\" or \"deny\".\n\n"));
        }

        if (returnValue != ERROR_SUCCESS)
            Error (TEXT("ERROR: Cannot add user to default access ACL."), returnValue);
    } else
    if (_tcscmp (_tcsupr (argv [2]), TEXT("REMOVE")) == 0)
    {
        returnValue = ChangeDefaultAccessACL (argv[3], FALSE, FALSE);

        if (returnValue != ERROR_SUCCESS)
            Error (TEXT("ERROR: Cannot remove user from default access ACL."), returnValue);
    } else
        ShowUsage (TEXT("You can only \"set\" or \"remove\" a user."));
}

void
HandleDLOption (
    int argc,
    TCHAR **argv
    )
{
    DWORD returnValue;

    if (argc < 3)
        ShowUsage (TEXT("Invalid number of arguments."));

    if (_tcscmp (_tcsupr (argv [2]), TEXT("LIST")) == 0)
    {
        _tprintf (TEXT("Default launch permission list:\n\n"));
        ListDefaultLaunchACL();
        return;
    }

    if (argc < 4)
        ShowUsage (TEXT("Invalid number of arguments."));

    if (_tcscmp (_tcsupr (argv [2]), TEXT("SET")) == 0)
    {
        if (argc < 5)
            ShowUsage (TEXT("Invalid number of arguments."));

        if (_tcscmp (_tcsupr (argv [4]), TEXT("PERMIT")) == 0)
            returnValue = ChangeDefaultLaunchACL (argv [3], TRUE, TRUE); else

        if (_tcscmp (_tcsupr (argv [4]), TEXT("DENY")) == 0)
            returnValue = ChangeDefaultLaunchACL (argv [3], TRUE, FALSE); else
        {
            ShowUsage (TEXT("You can only set a user's permissions to \"permit\" or \"deny\".\n\n"));
        }

        if (returnValue != ERROR_SUCCESS)
            Error (TEXT("ERROR: Cannot add user to default launch ACL."), returnValue);
    } else
    if (_tcscmp (_tcsupr (argv [3]), TEXT("REMOVE")) == 0)
    {
        returnValue = ChangeDefaultLaunchACL (argv[3], FALSE, FALSE);

        if (returnValue != ERROR_SUCCESS)
            Error (TEXT("ERROR: Cannot remove user from default launch ACL."), returnValue);
    } else
        ShowUsage (TEXT("You can only \"set\" or \"remove\" a user."));
}

void
HandleAAOption (
    int argc,
    TCHAR **argv
    )
{
    DWORD returnValue;
    HKEY  registryKey;
    TCHAR appid [256];
    TCHAR keyName [256];

    if (argc < 4)
        ShowUsage (TEXT("Invalid number of arguments."));

    if (_tcscmp (_tcsupr (argv[3]), TEXT("LIST")) == 0)
    {
        if (argc < 4)
            ShowUsage (TEXT("Invalid number of arguments."));

        _tprintf (TEXT("Access permission list for AppID %s:\n\n"), argv[2]);
        ListAppIDAccessACL (argv[2]);
        return;
    }

    if (_tcscmp (_tcsupr (argv[3]), TEXT("DEFAULT")) == 0)
    {
        if (argv [2][0] == '{')
            wsprintf (appid, TEXT("%s"), argv [2]); else
            wsprintf (appid, TEXT("{%s}"), argv [2]);

        wsprintf (keyName, TEXT("APPID\\%s"), appid);

        returnValue = RegOpenKeyEx (HKEY_CLASSES_ROOT, keyName, 0, KEY_ALL_ACCESS, &registryKey);
        if (returnValue != ERROR_SUCCESS && returnValue != ERROR_FILE_NOT_FOUND)
            Error (TEXT("ERROR: Cannot open AppID registry key."), returnValue);

        returnValue = RegDeleteValue (registryKey, TEXT("AccessPermission"));
        if (returnValue != ERROR_SUCCESS && returnValue != ERROR_FILE_NOT_FOUND)
            Error (TEXT("ERROR: Cannot delete AccessPermission value."), returnValue);

        RegCloseKey (registryKey);
        return;
    }

    if (argc < 5)
        ShowUsage (TEXT("Invalid number of arguments."));

    if (_tcscmp (_tcsupr (argv [3]), TEXT("SET")) == 0)
    {
        if (argc < 6)
            ShowUsage (TEXT("Invalid number of arguments."));

        if (_tcscmp (_tcsupr (argv [5]), TEXT("PERMIT")) == 0)
            returnValue = ChangeAppIDAccessACL (argv[2], argv [4], TRUE, TRUE); else

        if (_tcscmp (_tcsupr (argv [5]), TEXT("DENY")) == 0)
            returnValue = ChangeAppIDAccessACL (argv[2], argv [4], TRUE, FALSE); else
        {
            ShowUsage (TEXT("You can only set a user's permissions to \"permit\" or \"deny\".\n\n"));
        }

        if (returnValue != ERROR_SUCCESS)
            Error (TEXT("ERROR: Cannot add user to application access ACL."), returnValue);
    } else
    if (_tcscmp (_tcsupr (argv [3]), TEXT("REMOVE")) == 0)
    {
        returnValue = ChangeAppIDAccessACL (argv[2], argv[4], FALSE, FALSE);

        if (returnValue != ERROR_SUCCESS)
            Error (TEXT("ERROR: Cannot remove user from application access ACL."), returnValue);
    } else
        ShowUsage (TEXT("You can only \"set\" or \"remove\" a user."));
}

void
HandleALOption (
    int argc,
    TCHAR **argv
    )
{
    DWORD returnValue;
    HKEY  registryKey;
    TCHAR appid [256];
    TCHAR keyName [256];

    if (argc < 4)
        ShowUsage (TEXT("Invalid number of arguments."));

    if (_tcscmp (_tcsupr (argv[3]), TEXT("LIST")) == 0)
    {
        if (argc < 4)
            ShowUsage (TEXT("Invalid number of arguments.\n"));

        _tprintf (TEXT("Launch permission list for AppID %s:\n\n"), argv[2]);
        ListAppIDLaunchACL (argv[2]);
        return;
    }

    if (_tcscmp (_tcsupr (argv[3]), TEXT("DEFAULT")) == 0)
    {
        if (argv [2][0] == '{')
            wsprintf (appid, TEXT("%s"), argv [2]); else
            wsprintf (appid, TEXT("{%s}"), argv [2]);

        wsprintf (keyName, TEXT("APPID\\%s"), appid);

        returnValue = RegOpenKeyEx (HKEY_CLASSES_ROOT, keyName, 0, KEY_ALL_ACCESS, &registryKey);
        if (returnValue != ERROR_SUCCESS && returnValue != ERROR_FILE_NOT_FOUND)
            Error (TEXT("ERROR: Cannot open AppID registry key."), returnValue);

        returnValue = RegDeleteValue (registryKey, TEXT("LaunchPermission"));
        if (returnValue != ERROR_SUCCESS && returnValue != ERROR_FILE_NOT_FOUND)
            Error (TEXT("ERROR: Cannot delete LaunchPermission value."), returnValue);

        RegCloseKey (registryKey);
        return;
    }

    if (argc < 5)
        ShowUsage (TEXT("Invalid number of arguments."));

    if (_tcscmp (_tcsupr (argv [3]), TEXT("SET")) == 0)
    {
        if (argc < 6)
            ShowUsage (TEXT("Invalid number of arguments."));

        if (_tcscmp (_tcsupr (argv [5]), TEXT("PERMIT")) == 0)
            returnValue = ChangeAppIDLaunchACL (argv[2], argv [4], TRUE, TRUE); else

        if (_tcscmp (_tcsupr (argv [5]), TEXT("DENY")) == 0)
            returnValue = ChangeAppIDLaunchACL (argv[2], argv [4], TRUE, FALSE); else
        {
            ShowUsage (TEXT("You can only set a user's permissions to \"permit\" or \"deny\".\n\n"));
        }

        if (returnValue != ERROR_SUCCESS)
            Error (TEXT("ERROR: Cannot add user to application launch ACL."), returnValue);
    } else
    if (_tcscmp (_tcsupr (argv [3]), TEXT("REMOVE")) == 0)
    {
        returnValue = ChangeAppIDLaunchACL (argv[2], argv[4], FALSE, FALSE);

        if (returnValue != ERROR_SUCCESS)
            Error (TEXT("ERROR: Cannot remove user from application launch ACL."), returnValue);
    } else
        ShowUsage (TEXT("You can only \"set\" or \"remove\" a user."));
}

void
HandleRunAsOption (
    int argc,
    TCHAR **argv
    )
{
    DWORD returnValue;
    HKEY  registryKey;
    TCHAR appid [256];
    TCHAR keyName [256];

    if (argc < 4)
        ShowUsage (TEXT("Invalid number of arguments."));

    if (argv [2][0] == '{')
        wsprintf (appid, TEXT("%s"), argv [2]); else
        wsprintf (appid, TEXT("{%s}"), argv [2]);

    wsprintf (keyName, TEXT("APPID\\%s"), appid);

    returnValue = RegOpenKeyEx (HKEY_CLASSES_ROOT, keyName, 0, KEY_ALL_ACCESS, &registryKey);
    if (returnValue != ERROR_SUCCESS)
        Error (TEXT("ERROR: Cannot open AppID registry key."), returnValue);

    returnValue = RegSetValueEx (registryKey, TEXT("RunAs"), 0, REG_SZ, (LPBYTE) argv [3], _tcslen (argv[3]) * sizeof (TCHAR));
    if (returnValue != ERROR_SUCCESS)
        Error (TEXT("ERROR: Cannot set RunAs registry value."), returnValue);

    RegCloseKey (registryKey);

    if (_tcscmp (_tcsupr (argv[3]), TEXT("INTERACTIVE USER")) != 0)
    {
        if (argc < 5)
            ShowUsage (TEXT("Invalid number of arguments."));

        returnValue = SetRunAsPassword (argv[2], argv[3], argv[4]);
        if (returnValue != ERROR_SUCCESS)
            Error (TEXT("ERROR: Cannot set RunAs password."), returnValue);
    }
}

extern "C" void
_tmain (
    int argc,
    TCHAR **argv
    )
{
    if (argc < 2)
        ShowUsage (TEXT("No option specified."));

    if (_tcscmp (_tcsupr (argv [1]), TEXT("-DA")) == 0)
        HandleDAOption (argc, argv); else

    if (_tcscmp (_tcsupr (argv [1]), TEXT("-DL")) == 0)
        HandleDLOption (argc, argv); else

    if (_tcscmp (_tcsupr (argv [1]), TEXT("-AA")) == 0)
        HandleAAOption (argc, argv); else

    if (_tcscmp (_tcsupr (argv [1]), TEXT("-AL")) == 0)
        HandleALOption (argc, argv); else

    if (_tcscmp (_tcsupr (argv [1]), TEXT("-RUNAS")) == 0)
        HandleRunAsOption (argc, argv); else

    ShowUsage (TEXT("Invalid option specified."));
}


DCOMPERM.H

/*++

DCOM Permission Configuration Sample
Copyright (c) 1996, Microsoft Corporation. All rights reserved.

Module Name:

    dcomperm.h

Abstract:

    Include file for DCOM Permission Configuration sample

Author:

    Michael Nelson

Environment:

    Windows NT

--*/

#define GUIDSTR_MAX 38

#ifndef STR2UNI

#define STR2UNI(unistr, regstr) \
        mbstowcs (unistr, regstr, strlen (regstr)+1);

#define UNI2STR(regstr, unistr) \
        wcstombs (regstr, unistr, wcslen (unistr)+1);

#endif

//
// Wrappers
//

DWORD
ListDefaultAccessACL();

DWORD
ListDefaultLaunchACL();

DWORD
ListAppIDAccessACL (
    LPTSTR AppID
    );

DWORD
ListAppIDLaunchACL (
    LPTSTR AppID
    );

DWORD
ChangeDefaultAccessACL (
    LPTSTR Principal,
    BOOL SetPrincipal,
    BOOL Permit
    );

DWORD
ChangeDefaultLaunchACL (
    LPTSTR Principal,
    BOOL SetPrincipal,
    BOOL Permit
    );

DWORD
ChangeAppIDAccessACL (
    LPTSTR AppID,
    LPTSTR Principal,
    BOOL SetPrincipal,
    BOOL Permit
    );

DWORD
ChangeAppIDLaunchACL (
    LPTSTR AppID,
    LPTSTR Principal,
    BOOL SetPrincipal,
    BOOL Permit
    );

DWORD GetRunAsPassword (
    LPTSTR AppID,
    LPTSTR Password
    );

DWORD SetRunAsPassword (
    LPTSTR AppID,
    LPTSTR Principal,
    LPTSTR Password
    );

DWORD GetRunAsPassword (
    LPTSTR AppID,
    LPTSTR Password
    );

DWORD SetRunAsPassword (
    LPTSTR AppID,
    LPTSTR Password
    );

//
// Internal functions
//

DWORD
CreateNewSD (
    SECURITY_DESCRIPTOR **SD
    );

DWORD
MakeSDAbsolute (
    PSECURITY_DESCRIPTOR OldSD,
    PSECURITY_DESCRIPTOR *NewSD
    );

DWORD
SetNamedValueSD (
    HKEY RootKey,
    LPTSTR KeyName,
    LPTSTR ValueName,
    SECURITY_DESCRIPTOR *SD
    );

DWORD
GetNamedValueSD (
    HKEY RootKey,
    LPTSTR KeyName,
    LPTSTR ValueName,
    SECURITY_DESCRIPTOR **SD,
    BOOL *NewSD
    );

DWORD
ListNamedValueSD (
    HKEY RootKey,
    LPTSTR KeyName,
    LPTSTR ValueName
    );

DWORD
AddPrincipalToNamedValueSD (
    HKEY RootKey,
    LPTSTR KeyName,
    LPTSTR ValueName,
    LPTSTR Principal,
    BOOL Permit
    );

DWORD
RemovePrincipalFromNamedValueSD (
    HKEY RootKey,
    LPTSTR KeyName,
    LPTSTR ValueName,
    LPTSTR Principal
    );

DWORD
GetCurrentUserSID (
    PSID *Sid
    );

DWORD
GetPrincipalSID (
    LPTSTR Principal,
    PSID *Sid
    );

DWORD
CopyACL (
    PACL OldACL,
    PACL NewACL
    );

DWORD
AddAccessDeniedACEToACL (
    PACL *Acl,
    DWORD PermissionMask,
    LPTSTR Principal
    );

DWORD
AddAccessAllowedACEToACL (
    PACL *Acl,
    DWORD PermissionMask,
    LPTSTR Principal
    );

DWORD
RemovePrincipalFromACL (
    PACL Acl,
    LPTSTR Principal
    );

void
ListACL (
    PACL Acl
    );

DWORD
SetAccountRights (
    LPTSTR User,
    LPTSTR Privilege
    );

//
// Utility Functions
//

LPTSTR
SystemMessage (
    LPTSTR szBuffer,
    HRESULT hr
    );


LISTACL.CPP

/*++

DCOM Permission Configuration Sample
Copyright (c) 1996, Microsoft Corporation. All rights reserved.

Module Name:

    listacl.cpp

Abstract:

    Code to list ACL information

Author:

    Michael Nelson

Environment:

    Windows NT

--*/

#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <tchar.h>
#include "ntsecapi.h"
#include "dcomperm.h"

void
ListACL (
    PACL Acl
    )
{
    ACL_SIZE_INFORMATION     aclSizeInfo;
    ACL_REVISION_INFORMATION aclRevInfo;
    ULONG                    i;
    LPVOID                   ace;
    ACE_HEADER               *aceHeader;
    ACCESS_ALLOWED_ACE       *paaace;
    ACCESS_DENIED_ACE        *padace;
    TCHAR                    domainName [256];
    TCHAR                    userName [256];
    DWORD                    nameLength;
    SID_NAME_USE             snu;

    if (!GetAclInformation (Acl,
                            &aclSizeInfo,
                            sizeof (ACL_SIZE_INFORMATION),
                            AclSizeInformation))
    {
        _tprintf (TEXT("Could not get AclSizeInformation"));
        return;
    }

    if (!GetAclInformation (Acl,
                            &aclRevInfo,
                            sizeof (ACL_REVISION_INFORMATION),
                            AclRevisionInformation))
    {
        _tprintf (TEXT("Could not get AclRevisionInformation"));
        return;
    }

    for (i = 0; i < aclSizeInfo.AceCount; i++)
    {
        if (!GetAce (Acl, i, &ace))
            return;

        aceHeader = (ACE_HEADER *) ace;

        if (aceHeader->AceType == ACCESS_ALLOWED_ACE_TYPE)
        {
            paaace = (ACCESS_ALLOWED_ACE *) ace;
            nameLength = 255;
            LookupAccountSid (NULL,
                              &paaace->SidStart,
                              userName,
                              &nameLength,
                              domainName,
                              &nameLength,
                              &snu);

            _tprintf (TEXT("Access permitted to %s\\%s.\n"), domainName, userName);
        } else
        if (aceHeader->AceType == ACCESS_DENIED_ACE_TYPE)
        {
            padace = (ACCESS_DENIED_ACE *) ace;
            nameLength = 255;
            LookupAccountSid (NULL,
                              &padace->SidStart,
                              userName,
                              &nameLength,
                              domainName,
                              &nameLength,
                              &snu);

            _tprintf (TEXT("Access denied to %s\\%s.\n"), domainName, userName);

        }
   }
}


ACLMGMT.CPP

/*++

DCOM Permission Configuration Sample
Copyright (c) 1996, Microsoft Corporation. All rights reserved.

Module Name:

    aclmgmt.cpp

Abstract:

    Routines to manage access control lists

Author:

    Michael Nelson

Environment:

    Windows NT

--*/

#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <tchar.h>
#include "ntsecapi.h"
#include "dcomperm.h"

DWORD
CopyACL (
    PACL OldACL,
    PACL NewACL
    )
{
    ACL_SIZE_INFORMATION  aclSizeInfo;
    LPVOID                ace;
    ACE_HEADER            *aceHeader;
    ULONG                 i;

    GetAclInformation (OldACL, (LPVOID) &aclSizeInfo, (DWORD) sizeof (aclSizeInfo), AclSizeInformation);

    //
    // Copy all of the ACEs to the new ACL
    //

    for (i = 0; i < aclSizeInfo.AceCount; i++)
    {
        //
        // Get the ACE and header info
        //

        if (!GetAce (OldACL, i, &ace))
            return GetLastError();

        aceHeader = (ACE_HEADER *) ace;

        //
        // Add the ACE to the new list
        //

        if (!AddAce (NewACL, ACL_REVISION, 0xffffffff, ace, aceHeader->AceSize))
            return GetLastError();
    }

    return ERROR_SUCCESS;
}

DWORD
AddAccessDeniedACEToACL (
    PACL *Acl,
    DWORD PermissionMask,
    LPTSTR Principal
    )
{
    ACL_SIZE_INFORMATION  aclSizeInfo;
    int                   aclSize;
    DWORD                 returnValue;
    PSID                  principalSID;
    PACL                  oldACL, newACL;

    oldACL = *Acl;

    returnValue = GetPrincipalSID (Principal, &principalSID);
    if (returnValue != ERROR_SUCCESS)
        return returnValue;

    GetAclInformation (oldACL, (LPVOID) &aclSizeInfo, (DWORD) sizeof (ACL_SIZE_INFORMATION), AclSizeInformation);

    aclSize = aclSizeInfo.AclBytesInUse +
              sizeof (ACL) + sizeof (ACCESS_DENIED_ACE) +
              GetLengthSid (principalSID) - sizeof (DWORD);

    newACL = (PACL) new BYTE [aclSize];

    if (!InitializeAcl (newACL, aclSize, ACL_REVISION))
    {
        free (principalSID);
        return GetLastError();
    }

    if (!AddAccessDeniedAce (newACL, ACL_REVISION2, PermissionMask, principalSID))
    {
        free (principalSID);
        return GetLastError();
    }

    returnValue = CopyACL (oldACL, newACL);
    if (returnValue != ERROR_SUCCESS)
    {
        free (principalSID);
        return returnValue;
    }

    *Acl = newACL;

    free (principalSID);
    return ERROR_SUCCESS;
}

DWORD
AddAccessAllowedACEToACL (
    PACL *Acl,
    DWORD PermissionMask,
    LPTSTR Principal
    )
{
    ACL_SIZE_INFORMATION  aclSizeInfo;
    int                   aclSize;
    DWORD                 returnValue;
    PSID                  principalSID;
    PACL                  oldACL, newACL;

    oldACL = *Acl;

    returnValue = GetPrincipalSID (Principal, &principalSID);
    if (returnValue != ERROR_SUCCESS)
        return returnValue;

    GetAclInformation (oldACL, (LPVOID) &aclSizeInfo, (DWORD) sizeof (ACL_SIZE_INFORMATION), AclSizeInformation);

    aclSize = aclSizeInfo.AclBytesInUse +
              sizeof (ACL) + sizeof (ACCESS_ALLOWED_ACE) +
              GetLengthSid (principalSID) - sizeof (DWORD);

    newACL = (PACL) new BYTE [aclSize];

    if (!InitializeAcl (newACL, aclSize, ACL_REVISION))
    {
        free (principalSID);
        return GetLastError();
    }

    returnValue = CopyACL (oldACL, newACL);
    if (returnValue != ERROR_SUCCESS)
    {
        free (principalSID);
        return returnValue;
    }

    if (!AddAccessAllowedAce (newACL, ACL_REVISION2, PermissionMask, principalSID))
    {
        free (principalSID);
        return GetLastError();
    }

    *Acl = newACL;

    free (principalSID);
    return ERROR_SUCCESS;
}

DWORD
RemovePrincipalFromACL (
    PACL Acl,
    LPTSTR Principal
    )
{
    ACL_SIZE_INFORMATION    aclSizeInfo;
    ULONG                   i;
    LPVOID                  ace;
    ACCESS_ALLOWED_ACE      *accessAllowedAce;
    ACCESS_DENIED_ACE       *accessDeniedAce;
    SYSTEM_AUDIT_ACE        *systemAuditAce;
    PSID                    principalSID;
    DWORD                   returnValue;
    ACE_HEADER              *aceHeader;

    returnValue = GetPrincipalSID (Principal, &principalSID);
    if (returnValue != ERROR_SUCCESS)
        return returnValue;

    GetAclInformation (Acl, (LPVOID) &aclSizeInfo, (DWORD) sizeof (ACL_SIZE_INFORMATION), AclSizeInformation);

    for (i = 0; i < aclSizeInfo.AceCount; i++)
    {
        if (!GetAce (Acl, i, &ace))
        {
            free (principalSID);
            return GetLastError();
        }

        aceHeader = (ACE_HEADER *) ace;

        if (aceHeader->AceType == ACCESS_ALLOWED_ACE_TYPE)
        {
            accessAllowedAce = (ACCESS_ALLOWED_ACE *) ace;

            if (EqualSid (principalSID, (PSID) &accessAllowedAce->SidStart))
            {
                DeleteAce (Acl, i);
                free (principalSID);
                return ERROR_SUCCESS;
            }
        } else

        if (aceHeader->AceType == ACCESS_DENIED_ACE_TYPE)
        {
            accessDeniedAce = (ACCESS_DENIED_ACE *) ace;

            if (EqualSid (principalSID, (PSID) &accessDeniedAce->SidStart))
            {
                DeleteAce (Acl, i);
                free (principalSID);
                return ERROR_SUCCESS;
            }
        } else

        if (aceHeader->AceType == SYSTEM_AUDIT_ACE_TYPE)
        {
            systemAuditAce = (SYSTEM_AUDIT_ACE *) ace;

            if (EqualSid (principalSID, (PSID) &systemAuditAce->SidStart))
            {
                DeleteAce (Acl, i);
                free (principalSID);
                return ERROR_SUCCESS;
            }
        }
    }

    free (principalSID);
    return ERROR_SUCCESS;
}


SDMGMT.CPP

/*++

DCOM Permission Configuration Sample
Copyright (c) 1996, Microsoft Corporation. All rights reserved.

Module Name:

    sdmgmt.cpp

Abstract:

    Routines to manage security descriptors

Author:

    Michael Nelson

Environment:

    Windows NT

--*/

#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <tchar.h>
#include "ntsecapi.h"
#include "dcomperm.h"

DWORD
CreateNewSD (
    SECURITY_DESCRIPTOR **SD
    )
{
    PACL    dacl;
    DWORD   sidLength;
    PSID    sid;
    PSID    groupSID;
    PSID    ownerSID;
    DWORD   returnValue;

    *SD = NULL;

    returnValue = GetCurrentUserSID (&sid);
    if (returnValue != ERROR_SUCCESS)
        return returnValue;

    sidLength = GetLengthSid (sid);

    *SD = (SECURITY_DESCRIPTOR *) malloc (
        (sizeof (ACL)+sizeof (ACCESS_ALLOWED_ACE)+sidLength) +
        (2 * sidLength) +
        sizeof (SECURITY_DESCRIPTOR));

    groupSID = (SID *) (*SD + 1);
    ownerSID = (SID *) (((BYTE *) groupSID) + sidLength);
    dacl = (ACL *) (((BYTE *) ownerSID) + sidLength);

    if (!InitializeSecurityDescriptor (*SD, SECURITY_DESCRIPTOR_REVISION))
    {
        free (*SD);
        free (sid);
        return GetLastError();
    }

    if (!InitializeAcl (dacl,
                        sizeof (ACL)+sizeof (ACCESS_ALLOWED_ACE)+sidLength,
                        ACL_REVISION2))
    {
        free (*SD);
        free (sid);
        return GetLastError();
    }

    if (!AddAccessAllowedAce (dacl,
                              ACL_REVISION2,
                              COM_RIGHTS_EXECUTE,
                              sid))
    {
        free (*SD);
        free (sid);
        return GetLastError();
    }

    if (!SetSecurityDescriptorDacl (*SD, TRUE, dacl, FALSE))
    {
        free (*SD);
        free (sid);
        return GetLastError();
    }

    memcpy (groupSID, sid, sidLength);
    if (!SetSecurityDescriptorGroup (*SD, groupSID, FALSE))
    {
        free (*SD);
        free (sid);
        return GetLastError();
    }

    memcpy (ownerSID, sid, sidLength);
    if (!SetSecurityDescriptorOwner (*SD, ownerSID, FALSE))
    {
        free (*SD);
        free (sid);
        return GetLastError();
    }
    return ERROR_SUCCESS;
}

DWORD
MakeSDAbsolute (
    PSECURITY_DESCRIPTOR OldSD,
    PSECURITY_DESCRIPTOR *NewSD
    )
{
    PSECURITY_DESCRIPTOR  sd;
    DWORD                 descriptorSize;
    DWORD                 daclSize;
    DWORD                 saclSize;
    DWORD                 ownerSIDSize;
    DWORD                 groupSIDSize;
    PACL                  dacl;
    PACL                  sacl;
    PSID                  ownerSID;
    PSID                  groupSID;
    BOOL                  present;
    BOOL                  systemDefault;

    //
    // Get SACL
    //

    if (!GetSecurityDescriptorSacl (OldSD, &present, &sacl, &systemDefault))
        return GetLastError();

    if (sacl && present)
    {
        saclSize = sacl->AclSize;
    } else saclSize = 0;

    //
    // Get DACL
    //

    if (!GetSecurityDescriptorDacl (OldSD, &present, &dacl, &systemDefault))
        return GetLastError();

    if (dacl && present)
    {
        daclSize = dacl->AclSize;
    } else daclSize = 0;

    //
    // Get Owner
    //

    if (!GetSecurityDescriptorOwner (OldSD, &ownerSID, &systemDefault))
        return GetLastError();

    ownerSIDSize = GetLengthSid (ownerSID);

    //
    // Get Group
    //

    if (!GetSecurityDescriptorGroup (OldSD, &groupSID, &systemDefault))
        return GetLastError();

    groupSIDSize = GetLengthSid (groupSID);

    //
    // Do the conversion
    //

    descriptorSize = 0;

    MakeAbsoluteSD (OldSD, sd, &descriptorSize, dacl, &daclSize, sacl,
                    &saclSize, ownerSID, &ownerSIDSize, groupSID,
                    &groupSIDSize);

    sd = (PSECURITY_DESCRIPTOR) new BYTE [SECURITY_DESCRIPTOR_MIN_LENGTH];
    if (!InitializeSecurityDescriptor (sd, SECURITY_DESCRIPTOR_REVISION))
        return GetLastError();

    if (!MakeAbsoluteSD (OldSD, sd, &descriptorSize, dacl, &daclSize, sacl,
                         &saclSize, ownerSID, &ownerSIDSize, groupSID,
                         &groupSIDSize))
        return GetLastError();

    *NewSD = sd;
    return ERROR_SUCCESS;
}

DWORD
SetNamedValueSD (
    HKEY RootKey,
    LPTSTR KeyName,
    LPTSTR ValueName,
    SECURITY_DESCRIPTOR *SD
    )
{
    DWORD   returnValue;
    DWORD   disposition;
    HKEY    registryKey;

    //
    // Create new key or open existing key
    //

    returnValue = RegCreateKeyEx (RootKey, KeyName, 0, TEXT(""), 0, KEY_ALL_ACCESS, NULL, &registryKey, &disposition);
    if (returnValue != ERROR_SUCCESS)
        return returnValue;

    //
    // Write the security descriptor
    //

    returnValue = RegSetValueEx (registryKey, ValueName, 0, REG_BINARY, (LPBYTE) SD, GetSecurityDescriptorLength (SD));
    if (returnValue != ERROR_SUCCESS)
        return returnValue;

    RegCloseKey (registryKey);

    return ERROR_SUCCESS;
}

DWORD
GetNamedValueSD (
    HKEY RootKey,
    LPTSTR KeyName,
    LPTSTR ValueName,
    SECURITY_DESCRIPTOR **SD,
    BOOL *NewSD
    )
{
    DWORD               returnValue;
    HKEY                registryKey;
    DWORD               valueType;
    DWORD               valueSize;

    *NewSD = FALSE;

    //
    // Get the security descriptor from the named value. If it doesn't
    // exist, create a fresh one.
    //

    returnValue = RegOpenKeyEx (RootKey, KeyName, 0, KEY_ALL_ACCESS, &registryKey);

    if (returnValue != ERROR_SUCCESS)
    {
        if (returnValue == ERROR_FILE_NOT_FOUND)
        {
            *SD = NULL;
            returnValue = CreateNewSD (SD);
            if (returnValue != ERROR_SUCCESS)
                return returnValue;

            *NewSD = TRUE;
            return ERROR_SUCCESS;
        } else
            return returnValue;
    }

    returnValue = RegQueryValueEx (registryKey, ValueName, NULL, &valueType, NULL, &valueSize);

    if (returnValue && returnValue != ERROR_INSUFFICIENT_BUFFER)
    {
        *SD = NULL;
        returnValue = CreateNewSD (SD);
        if (returnValue != ERROR_SUCCESS)
            return returnValue;

        *NewSD = TRUE;
    } else
    {
        *SD = (SECURITY_DESCRIPTOR *) malloc (valueSize);

        returnValue = RegQueryValueEx (registryKey, ValueName, NULL, &valueType, (LPBYTE) *SD, &valueSize);
        if (returnValue)
        {
            free (*SD);

            *SD = NULL;
            returnValue = CreateNewSD (SD);
            if (returnValue != ERROR_SUCCESS)
                return returnValue;

            *NewSD = TRUE;
        }
    }

    RegCloseKey (registryKey);

    return ERROR_SUCCESS;
}

DWORD
ListNamedValueSD (
    HKEY RootKey,
    LPTSTR KeyName,
    LPTSTR ValueName
    )
{
    DWORD               returnValue;
    SECURITY_DESCRIPTOR *sd;
    BOOL                present;
    BOOL                defaultDACL;
    PACL                dacl;
    BOOL                newSD = FALSE;

    returnValue = GetNamedValueSD (RootKey, KeyName, ValueName, &sd, &newSD);

    if ((returnValue != ERROR_SUCCESS) || (newSD == TRUE))
    {
        _tprintf (TEXT("<Using Default Permissions>\n"));
        free (sd);
        return returnValue;
    }

    if (!GetSecurityDescriptorDacl (sd, &present, &dacl, &defaultDACL))
    {
        free (sd);
        return GetLastError();
    }

    if (!present)
    {
        _tprintf (TEXT("<Access is denied to everyone>\n"));
        free (sd);
        return ERROR_SUCCESS;
    }

    ListACL (dacl);

    free (sd);

    return ERROR_SUCCESS;
}

DWORD
AddPrincipalToNamedValueSD (
    HKEY RootKey,
    LPTSTR KeyName,
    LPTSTR ValueName,
    LPTSTR Principal,
    BOOL Permit
    )
{
    DWORD               returnValue;
    SECURITY_DESCRIPTOR *sd;
    SECURITY_DESCRIPTOR *sdSelfRelative;
    SECURITY_DESCRIPTOR *sdAbsolute;
    DWORD               secDescSize;
    BOOL                present;
    BOOL                defaultDACL;
    PACL                dacl;
    BOOL                newSD = FALSE;

    returnValue = GetNamedValueSD (RootKey, KeyName, ValueName, &sd, &newSD);

    //
    // Get security descriptor from registry or create a new one
    //

    if (returnValue != ERROR_SUCCESS)
        return returnValue;

    if (!GetSecurityDescriptorDacl (sd, &present, &dacl, &defaultDACL))
        return GetLastError();

    if (newSD)
    {
        AddAccessAllowedACEToACL (&dacl, COM_RIGHTS_EXECUTE, TEXT("SYSTEM"));
        AddAccessAllowedACEToACL (&dacl, COM_RIGHTS_EXECUTE, TEXT("INTERACTIVE"));
    }

    //
    // Add the Principal that the caller wants added
    //

    if (Permit)
        returnValue = AddAccessAllowedACEToACL (&dacl, COM_RIGHTS_EXECUTE, Principal); else
        returnValue = AddAccessDeniedACEToACL (&dacl, GENERIC_ALL, Principal);

    if (returnValue != ERROR_SUCCESS)
    {
        free (sd);
        return returnValue;
    }

    //
    // Make the security descriptor absolute if it isn't new
    //

    if (!newSD)
        MakeSDAbsolute ((PSECURITY_DESCRIPTOR) sd, (PSECURITY_DESCRIPTOR *) &sdAbsolute); else
        sdAbsolute = sd;

    //
    // Set the discretionary ACL on the security descriptor
    //

    if (!SetSecurityDescriptorDacl (sdAbsolute, TRUE, dacl, FALSE))
        return GetLastError();

    //
    // Make the security descriptor self-relative so that we can
    // store it in the registry
    //

    secDescSize = 0;
    MakeSelfRelativeSD (sdAbsolute, sdSelfRelative, &secDescSize);
    sdSelfRelative = (SECURITY_DESCRIPTOR *) malloc (secDescSize);
    if (!MakeSelfRelativeSD (sdAbsolute, sdSelfRelative, &secDescSize))
        return GetLastError();

    //
    // Store the security descriptor in the registry
    //

    SetNamedValueSD (RootKey, KeyName, ValueName, sdSelfRelative);

    free (sd);
    free (sdSelfRelative);
    free (sdAbsolute);

    return ERROR_SUCCESS;
}

DWORD
RemovePrincipalFromNamedValueSD (
    HKEY RootKey,
    LPTSTR KeyName,
    LPTSTR ValueName,
    LPTSTR Principal
    )
{
    DWORD               returnValue;
    SECURITY_DESCRIPTOR *sd;
    SECURITY_DESCRIPTOR *sdSelfRelative;
    SECURITY_DESCRIPTOR *sdAbsolute;
    DWORD               secDescSize;
    BOOL                present;
    BOOL                defaultDACL;
    PACL                dacl;
    BOOL                newSD = FALSE;

    returnValue = GetNamedValueSD (RootKey, KeyName, ValueName, &sd, &newSD);

    //
    // Get security descriptor from registry or create a new one
    //

    if (returnValue != ERROR_SUCCESS)
        return returnValue;

    if (!GetSecurityDescriptorDacl (sd, &present, &dacl, &defaultDACL))
        return GetLastError();

    //
    // If the security descriptor is new, add the required Principals to it
    //

    if (newSD)
    {
        AddAccessAllowedACEToACL (&dacl, COM_RIGHTS_EXECUTE, TEXT("SYSTEM"));
        AddAccessAllowedACEToACL (&dacl, COM_RIGHTS_EXECUTE, TEXT("INTERACTIVE"));
    }

    //
    // Remove the Principal that the caller wants removed
    //

    returnValue = RemovePrincipalFromACL (dacl, Principal);
    if (returnValue != ERROR_SUCCESS)
    {
        free (sd);
        return returnValue;
    }

    //
    // Make the security descriptor absolute if it isn't new
    //

    if (!newSD)
        MakeSDAbsolute ((PSECURITY_DESCRIPTOR) sd, (PSECURITY_DESCRIPTOR *) &sdAbsolute); else
        sdAbsolute = sd;

    //
    // Set the discretionary ACL on the security descriptor
    //

    if (!SetSecurityDescriptorDacl (sdAbsolute, TRUE, dacl, FALSE))
        return GetLastError();

    //
    // Make the security descriptor self-relative so that we can
    // store it in the registry
    //

    secDescSize = 0;
    MakeSelfRelativeSD (sdAbsolute, sdSelfRelative, &secDescSize);
    sdSelfRelative = (SECURITY_DESCRIPTOR *) malloc (secDescSize);
    if (!MakeSelfRelativeSD (sdAbsolute, sdSelfRelative, &secDescSize))
        return GetLastError();

    //
    // Store the security descriptor in the registry
    //

    SetNamedValueSD (RootKey, KeyName, ValueName, sdSelfRelative);

    free (sd);
    free (sdSelfRelative);
    free (sdAbsolute);

    return ERROR_SUCCESS;
}


SRVCMGMT.CPP

/*++

DCOM Permission Configuration Sample
Copyright (c) 1996, Microsoft Corporation. All rights reserved.

Module Name:

    srvcmgmt.cpp

Abstract:

    Routines to manage RunAs and Service settings for DCOM servers

Author:

    Michael Nelson

Environment:

    Windows NT

--*/

#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <tchar.h>
#include "ntsecapi.h"
#include "dcomperm.h"

DWORD GetRunAsPassword (
    LPTSTR AppID,
    LPTSTR Password
    )
{
    LSA_OBJECT_ATTRIBUTES objectAttributes;
    HANDLE                policyHandle = NULL;
    LSA_UNICODE_STRING    lsaKeyString;
    PLSA_UNICODE_STRING   lsaPasswordString;
    WCHAR                 key [4 + GUIDSTR_MAX + 1];
    WCHAR                 wideAppID [GUIDSTR_MAX + 1];
    ULONG                 returnValue;

#ifndef UNICODE
    STR2UNI (wideAppID, AppID);
#else
    lstrcpy (wideAppID, AppID);
#endif

    wcscpy (key, L"SCM:");
    wcscat (key, wideAppID);

    lsaKeyString.Length = (USHORT) ((wcslen (key) + 1) * sizeof (WCHAR));
    lsaKeyString.MaximumLength = (GUIDSTR_MAX + 5) * sizeof (WCHAR);
    lsaKeyString.Buffer = key;

    //
    // Open the local security policy
    //

    memset (&objectAttributes, 0x00, sizeof (LSA_OBJECT_ATTRIBUTES));
    objectAttributes.Length = sizeof (LSA_OBJECT_ATTRIBUTES);

    returnValue = LsaOpenPolicy (NULL,
                                 &objectAttributes,
                                 POLICY_GET_PRIVATE_INFORMATION,
                                 &policyHandle);

    if (returnValue != ERROR_SUCCESS)
        return returnValue;

    //
    // Read the user's password
    //

    returnValue = LsaRetrievePrivateData (policyHandle,
                                          &lsaKeyString,
                                          &lsaPasswordString);

    if (returnValue != ERROR_SUCCESS)
    {
        LsaClose (policyHandle);
        return returnValue;
    }

    LsaClose (policyHandle);

#ifndef UNICODE
    UNI2STR (Password, lsaPasswordString->Buffer);
#else
    wcscpy (Password, lsaPasswordString->Buffer);
#endif

    return ERROR_SUCCESS;
}

DWORD SetRunAsPassword (
    LPTSTR AppID,
    LPTSTR Principal,
    LPTSTR Password
    )
{
    LSA_OBJECT_ATTRIBUTES objectAttributes;
    HANDLE                policyHandle = NULL;
    LSA_UNICODE_STRING    lsaKeyString;
    LSA_UNICODE_STRING    lsaPasswordString;
    WCHAR                 key [4 + GUIDSTR_MAX + 1];
    WCHAR                 wideAppID [GUIDSTR_MAX + 1];
    WCHAR                 widePassword [256];
    DWORD                 returnValue;

#ifndef UNICODE
    STR2UNI (wideAppID, AppID);
    STR2UNI (widePassword, Password);
#else
    wcscpy (wideAppID, AppID);
    wcscpy (widePassword, Password);
#endif

    wcscpy (key, L"SCM:");
    wcscat (key, wideAppID);

    lsaKeyString.Length = (USHORT) ((wcslen (key) + 1) * sizeof (WCHAR));
    lsaKeyString.MaximumLength = (GUIDSTR_MAX + 5) * sizeof (WCHAR);
    lsaKeyString.Buffer = key;

    lsaPasswordString.Length = (USHORT) ((wcslen (widePassword) + 1) * sizeof (WCHAR));
    lsaPasswordString.Buffer = widePassword;
    lsaPasswordString.MaximumLength = lsaPasswordString.Length;

    //
    // Open the local security policy
    //

    memset (&objectAttributes, 0x00, sizeof (LSA_OBJECT_ATTRIBUTES));
    objectAttributes.Length = sizeof (LSA_OBJECT_ATTRIBUTES);

    returnValue = LsaOpenPolicy (NULL,
                                 &objectAttributes,
                                 POLICY_CREATE_SECRET,
                                 &policyHandle);

    if (returnValue != ERROR_SUCCESS)
        return returnValue;

    //
    // Store the user's password
    //

    returnValue = LsaStorePrivateData (policyHandle,
                                       &lsaKeyString,
                                       &lsaPasswordString);

    if (returnValue != ERROR_SUCCESS)
    {
        LsaClose (policyHandle);
        return returnValue;
    }

    LsaClose (policyHandle);

    returnValue = SetAccountRights (Principal, TEXT("SeBatchLogonRight"));
    if (returnValue != ERROR_SUCCESS)
        return returnValue;

    return ERROR_SUCCESS;
}

DWORD
SetAccountRights (
    LPTSTR User,
    LPTSTR Privilege
    )
{
    LSA_HANDLE            policyHandle;
    LSA_OBJECT_ATTRIBUTES objectAttributes;
    PSID                  principalSID;
    LSA_UNICODE_STRING    lsaPrivilegeString;
    WCHAR                 widePrivilege [256];

#ifdef _UNICODE
    lstrcpy (widePrivilege, Privilege);
#else
    STR2UNI (widePrivilege, Privilege);
#endif

    memset (&objectAttributes, 0, sizeof(LSA_OBJECT_ATTRIBUTES));
    if (LsaOpenPolicy (NULL,
                       &objectAttributes,
                       POLICY_CREATE_ACCOUNT | POLICY_LOOKUP_NAMES,
                       &policyHandle) != ERROR_SUCCESS)
    {
        return GetLastError();
    }

    GetPrincipalSID (User, &principalSID);

    lsaPrivilegeString.Length = (USHORT) (wcslen (widePrivilege) * sizeof (WCHAR));
    lsaPrivilegeString.MaximumLength = (USHORT) (lsaPrivilegeString.Length + sizeof (WCHAR));
    lsaPrivilegeString.Buffer = widePrivilege;

    if (LsaAddAccountRights (policyHandle,
                             principalSID,
                             &lsaPrivilegeString,
                             1) != ERROR_SUCCESS)
    {
        free (principalSID);
        LsaClose (policyHandle);
        return GetLastError();
    }

    free (principalSID);
    LsaClose (policyHandle);

    return ERROR_SUCCESS;
}


UTILS.CPP

/*++

DCOM Permission Configuration Sample
Copyright (c) 1996, Microsoft Corporation. All rights reserved.

Module Name:

    utils.cpp

Abstract:

    Miscellaneous utility functions

Author:

    Michael Nelson

Environment:

    Windows NT

--*/

#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <tchar.h>
#include "ntsecapi.h"
#include "dcomperm.h"

DWORD
GetCurrentUserSID (
    PSID *Sid
    )
{
    TOKEN_USER  *tokenUser;
    HANDLE      tokenHandle;
    DWORD       tokenSize;
    DWORD       sidLength;

    if (OpenProcessToken (GetCurrentProcess(), TOKEN_QUERY, &tokenHandle))
    {
        GetTokenInformation (tokenHandle,
                             TokenUser,
                             tokenUser,
                             0,
                             &tokenSize);

        tokenUser = (TOKEN_USER *) malloc (tokenSize);

        if (GetTokenInformation (tokenHandle,
                                 TokenUser,
                                 tokenUser,
                                 tokenSize,
                                 &tokenSize))
        {
            sidLength = GetLengthSid (tokenUser->User.Sid);
            *Sid = (PSID) malloc (sidLength);

            memcpy (*Sid, tokenUser->User.Sid, sidLength);
            CloseHandle (tokenHandle);
        } else
        {
            free (tokenUser);
            return GetLastError();
        }
    } else
    {
        free (tokenUser);
        return GetLastError();
    }

    free (tokenUser);
    return ERROR_SUCCESS;
}

DWORD
GetPrincipalSID (
    LPTSTR Principal,
    PSID *Sid
    )
{
    DWORD        sidSize;
    TCHAR        refDomain [256];
    DWORD        refDomainSize;
    DWORD        returnValue;
    SID_NAME_USE snu;

    sidSize = 0;
    refDomainSize = 255;

    LookupAccountName (NULL,
                       Principal,
                       *Sid,
                       &sidSize,
                       refDomain,
                       &refDomainSize,
                       &snu);

    returnValue = GetLastError();
    if (returnValue != ERROR_INSUFFICIENT_BUFFER)
        return returnValue;

    *Sid = (PSID) malloc (sidSize);
    refDomainSize = 255;

    if (!LookupAccountName (NULL,
                            Principal,
                            *Sid,
                            &sidSize,
                            refDomain,
                            &refDomainSize,
                            &snu))
    {
        return GetLastError();
    }

    return ERROR_SUCCESS;
}

LPTSTR
SystemMessage (
    LPTSTR Buffer,
    HRESULT hr
    )
{
    LPTSTR   message;

    FormatMessage (FORMAT_MESSAGE_ALLOCATE_BUFFER |
                   FORMAT_MESSAGE_FROM_SYSTEM,
                   NULL,
                   hr,
                   MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
                   (LPTSTR) &message,
                   0,
                   NULL);

    wsprintf (Buffer, TEXT("%s(%lx)\n"), message, hr);

    LocalFree (message);
    return Buffer;
}


WRAPPERS.CPP

/*++

DCOM Permission Configuration Sample
Copyright (c) 1996, Microsoft Corporation. All rights reserved.

Module Name:

    wrappers.cpp

Abstract:

    Wrappers for low-level security and registry functions

Author:

    Michael Nelson

Environment:

    Windows NT

--*/

#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <tchar.h>
#include "ntsecapi.h"
#include "dcomperm.h"

DWORD
ListDefaultAccessACL()
{
    return ListNamedValueSD (HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Ole"), TEXT("DefaultAccessPermission"));
}

DWORD
ListDefaultLaunchACL()
{
    return ListNamedValueSD (HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Ole"), TEXT("DefaultLaunchPermission"));
}

DWORD
ListAppIDAccessACL (
    LPTSTR AppID
    )
{
    TCHAR   keyName [256];

    if (AppID [0] == '{')
        wsprintf (keyName, TEXT("APPID\\%s"), AppID); else
        wsprintf (keyName, TEXT("APPID\\{%s}"), AppID);

    return ListNamedValueSD (HKEY_CLASSES_ROOT, keyName, TEXT("AccessPermission"));
}

DWORD
ListAppIDLaunchACL (
    LPTSTR AppID
    )
{
    TCHAR   keyName [256];

    if (AppID [0] == '{')
        wsprintf (keyName, TEXT("APPID\\%s"), AppID); else
        wsprintf (keyName, TEXT("APPID\\{%s}"), AppID);

    return ListNamedValueSD (HKEY_CLASSES_ROOT, keyName, TEXT("LaunchPermission"));
}

DWORD
ChangeDefaultAccessACL (
    LPTSTR Principal,
    BOOL SetPrincipal,
    BOOL Permit
    )
{
    if (SetPrincipal)
    {
        RemovePrincipalFromNamedValueSD (HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Ole"), TEXT("DefaultAccessPermission"), Principal);
        return AddPrincipalToNamedValueSD (HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Ole"), TEXT("DefaultAccessPermission"), Principal, Permit);
    } else
        return RemovePrincipalFromNamedValueSD (HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Ole"), TEXT("DefaultAccessPermission"), Principal);
}

DWORD
ChangeDefaultLaunchACL (
    LPTSTR Principal,
    BOOL SetPrincipal,
    BOOL Permit
    )
{
    if (SetPrincipal)
    {
        RemovePrincipalFromNamedValueSD (HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Ole"), TEXT("DefaultLaunchPermission"), Principal);
        return AddPrincipalToNamedValueSD (HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Ole"), TEXT("DefaultLaunchPermission"), Principal, Permit);
    } else
        return RemovePrincipalFromNamedValueSD (HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Ole"), TEXT("DefaultLaunchPermission"), Principal);
}

DWORD
ChangeAppIDAccessACL (
    LPTSTR AppID,
    LPTSTR Principal,
    BOOL SetPrincipal,
    BOOL Permit
    )
{
    TCHAR   keyName [256];

    if (AppID [0] == '{')
        wsprintf (keyName, TEXT("APPID\\%s"), AppID); else
        wsprintf (keyName, TEXT("APPID\\{%s}"), AppID);

    if (SetPrincipal)
    {
        RemovePrincipalFromNamedValueSD (HKEY_CLASSES_ROOT, keyName, TEXT("AccessPermission"), Principal);
        return AddPrincipalToNamedValueSD (HKEY_CLASSES_ROOT, keyName, TEXT("AccessPermission"), Principal, Permit);
    } else
        return RemovePrincipalFromNamedValueSD (HKEY_CLASSES_ROOT, keyName, TEXT("AccessPermission"), Principal);
}

DWORD
ChangeAppIDLaunchACL (
    LPTSTR AppID,
    LPTSTR Principal,
    BOOL SetPrincipal,
    BOOL Permit
    )
{
    TCHAR   keyName [256];

    if (AppID [0] == '{')
        wsprintf (keyName, TEXT("APPID\\%s"), AppID); else
        wsprintf (keyName, TEXT("APPID\\{%s}"), AppID);

    if (SetPrincipal)
    {
        RemovePrincipalFromNamedValueSD (HKEY_CLASSES_ROOT, keyName, TEXT("LaunchPermission"), Principal);
        return AddPrincipalToNamedValueSD (HKEY_CLASSES_ROOT, keyName, TEXT("LaunchPermission"), Principal, Permit);
    } else
        return RemovePrincipalFromNamedValueSD (HKEY_CLASSES_ROOT, keyName, TEXT("LaunchPermission"), Principal);
}


README.TXT

DCOM Impersonation

SUMMARY
=======

The OLEIMP sample demonstrates a method for emulating auto impersonation
of clients. Normally, objects created on the server will run in the
context of the server.  An object method must explicitly impersonate the
client if the method is to run in the context of the client.  This
sample demonstrates a way to arrange for methods to "automatically" run
in the context of the client.

MORE INFORMATION
================

This program is based on the oleapt sample.  It uses a set of apartment
threads that each potentially impersonate a client. All objects created
for a user run in the same apartment.

The server registers a class-factory on the main application thread and
also spawns several worker threads. When requests arrive from clients
to create instances of the class, the server class-factory picks a
worker thread for the object in the following manner:

  - If there is a thread currently impersonating that user, use that
    thread.
  - Otherwise, if there is a free thread, set that thread up to
    impersonate the client and use that thread.
  - Otherwise fail the CreateInstance call.

The server and then goes through the process of having the object
created within the thread and properly marshalled from the worker thread
back to the class factory where it can be returned to the caller. Note
that this marshalling to the main thread is fairly transient, it lasts
for creation only: subsequent calls from the client to the object go
straight from the client's process into the worker apartment.

This sample may be compiled as either UNICODE or ANSI.

Instructions:

 To use this sample:
  * build it using the NMAKE command. NMAKE will create OLEIMP.EXE and
    CLIENT.EXE.
  * install the OLEIMP.EXE on the current machine or on a remote
    machine according to the installation instructions found in
    OLEIMP.CPP.
  * run CLIENT.EXE. use no command-line arguments to instantiate the
    object on the current machine. use a single command-line argument of
    the remote machine-name (UNC or DNS) to instantiate the object on a
    remote machine.
  * CLIENT.EXE displays some simple information about the calls it is
    making on the object.
  * OLEIMP.EXE displays information about the worker threads.


OLEIMP.CPP

// ===========================================================================
// File: O L E I M P . C P P
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
//  The OLEIMP sample demonstrates a method for emulating auto impersonation
// of clients. Normally, objects created on the server will run in the
// context of the server.  An object method must explicitly impersonate the
// client if the method is to run in the context of the client.  This
// sample demonstrates a way to arrange for methods to "automatically" run
// in the context of the client.
//
// This program is based on the oleapt sample.  It uses a set of apartment
// threads that each potentially impersonate a client. All objects created
// for a user run in the same apartment.  The security for the server
// is explicitly set up to allow the world call access.  Launch permission
// will still need to be set using the DCOMCNFG.EXE utility if you want
// other than the default launch permission.
//
// The server registers a class-factory on the main application thread and
// also spawns several worker threads. When requests arrive from clients
// to create instances of the class, the server class-factory picks a
// worker thread for the object in the following manner:
//
//   - If there is a thread currently impersonating that user, use that
//     thread.
//   - Otherwise, if there is a free thread, set that thread up to
//     impersonate the client and use that thread.
//   - Otherwise fail the CreateInstance call.
//
// The server then goes through the process of having the object created
// within the thread and properly marshalled from the worker thread
// back to the class factory where it can be returned to the caller. Note
// that this marshalling to the main thread is fairly transient, it lasts
// for creation only: subsequent calls from the client to the object go
// straight from the client's process into the worker apartment.
//
// To register this server run OLEIMP.EXE with the -AutoRegister command
// line switch.  OLEIMP.EXE registers itself to run as the interactive
// user.  For more information about the activation options for DCOM
// applications see the DCOM document on the Win32 SDK CD in the
// \doc\spec\ole directory.
//
// This sample may be compiled as either UNICODE or ANSI.
//
// Copyright 1996 Microsoft Corporation.  All Rights Reserved.
// ===========================================================================

// %%Includes: ---------------------------------------------------------------
#define INC_OLE2
#define STRICT
#include <windows.h>
#include <initguid.h>
#include <stdio.h>
#include <string.h>
#include <tchar.h>
#include "util.h"

// %%Constants: --------------------------------------------------------------
#define cServerThreads  5
#define szWindowClass   TEXT("OLEIMP_CLASS")
#define szTitleServer   TEXT("SERVER: OLE Apartment Impersonation Sample")
#define szCloseQuery    TEXT("Client references still exist. Are you sure you want to close?")
#define COINIT_APARTMENTTHREADED    2
#define MAX_USER_NAME50
const LARGE_INTEGER     bZero = {0,0};

// %%Guids: ------------------------------------------------------------------
DEFINE_GUID(CLSID_CObject, 0x35b79d1, 0xd6d3, 0x11cf, 0xb9, 0xd4, 0x0, 0xaa, 0x0, 0xa2, 0x16, 0xe0);

// %%Classes: ----------------------------------------------------------------
// the class-factory object exists in the main application apartment/thread
// and is used to create instances of the worker objects on worker threads.
class CClassFactory : public IClassFactory
    {
  // IClassFactory
    STDMETHODIMP            QueryInterface(REFIID iid, void **ppv);
    STDMETHODIMP_(ULONG)    AddRef(void)    { return mcRef++; }
    STDMETHODIMP_(ULONG)    Release(void)   { if (--mcRef == 0) { delete this; return 0; } return mcRef; }
    STDMETHODIMP            CreateInstance(LPUNKNOWN punkOuter, REFIID iid, LPVOID FAR *ppv);
    STDMETHODIMP            LockServer(BOOL fLock);

  // data members
    ULONG   mcRef;

  // constructors/destructors
  public:
    CClassFactory() { mcRef = 1; }
    };

// this worker object is simple: it simply supports IUnknown. more interesting
// interfaces can be readily added here and implemented for the worker.
class CObject : public IUnknown
    {
  // IUnknown
    STDMETHODIMP            QueryInterface(REFIID iid, void **ppv);
    STDMETHODIMP_(ULONG)    AddRef(void)    { return mcRef++; }
    STDMETHODIMP_(ULONG)    Release(void);

  // data members
    ULONG   mcRef;

  // constructors/destructors
  public:
    CObject() { mcRef = 1; }
    };

// %%Globals: ----------------------------------------------------------------
BOOL        vfServer;                       // is this instance a server or client?
HANDLE      vrghThread[cServerThreads];     // worker thread handles
DWORD       vrgtid[cServerThreads];         // worker thread id's
HANDLE      vrghEvent[cServerThreads];      // creation event for each worker
INT         vrgcObjects[cServerThreads];    // # of objects created on each thread
PSIDvrgpSid[cServerThreads];// SID of user in each apt.
LPTSTRvrgpszAptUser[cServerThreads];// name of each apt. user
HANDLE      vhEventServer;                  // creation-complete event for class-factory
CClassFactory   vclassfactory;
HWND        vhwndMain;
LPSTREAM    vpstmMarshalling;               // scratch stream used for cross-apt marshalling
HRESULT     vhrThreadStatus;                // signals status to class-factory

// %%Prototypes: -------------------------------------------------------------
LRESULT CALLBACK    MainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT             ServerThreadProc(LPARAM lParam);
BOOL                FAutoRegister(HINSTANCE hinst);
BOOLFindSlot(PSID psid, int *piThread);

// ---------------------------------------------------------------------------
// %%Function: MainWndProc
// ---------------------------------------------------------------------------
 LRESULT CALLBACK
MainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    LRESULT lresult = TRUE;
int i;

    switch (message)
        {
        case WM_PAINT:
            {
            HDC hdc;
            PAINTSTRUCT ps;
            int i;

            if ((hdc = BeginPaint(hwnd, &ps)) != NULL)
                {
                for (i=0; i<cServerThreads; i++)
                    {
                    TCHAR    rgch[200];

// if a thread which is impersonating a user no
// longer has any objects, free up the resources
// and stop impersonating
if (0 == vrgcObjects[i] && NULL != vrgpSid[i])
{
UtilFreeMem(vrgpszAptUser[i]);
vrgpszAptUser[i] = NULL;
UtilFreeMem(vrgpSid[i]);
vrgpSid[i] = NULL;
SetThreadToken (&vrghThread[i], NULL);
}

                    wsprintf(rgch, TEXT("Thread #%d (%s): %d objects"),
                    i,
                    (NULL == vrgpszAptUser[i])?
                    TEXT("No User") :
                    vrgpszAptUser[i],
                    vrgcObjects[i]);
                    TextOut(hdc, 20, 10+(i*25), rgch, lstrlen(rgch));
                    }
                }
            EndPaint(hwnd, &ps);
            }
            break;

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_CLOSE:
            // check for clients and prompt in the UI.
            for (i=0; i<cServerThreads; i++)
                {
                if (vrgcObjects[i] > 0)
                    {
                    if (MessageBox(hwnd, szCloseQuery,
                            szTitleServer, MB_YESNO) == IDNO)
                        return FALSE;
                    else
                        break;
                    }
                }

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

    return lresult;
}  // MainWndProc

// ---------------------------------------------------------------------------
// %%Function: WinMain
//  Initialization and main message-pump
// ---------------------------------------------------------------------------
 int PASCAL
WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR szCmdLine, int nCmdShow)
{
    WNDCLASS wndclass;
    HRESULT hr;
    int     i;
    DWORD   dwRegister = 0;
    MSG     msg;
PSECURITY_DESCRIPTOR psd = NULL;

    // parse the command-line
    if (szCmdLine)
        {
        LPSTR   sz = strtok(szCmdLine, " \t");

if (sz)
{
if (!strcmpi(sz, "-AutoRegister"))
{
    if (!FAutoRegister(hinst))
        {
        hr = GetLastError();
        goto LCleanup;
        }
else
{
MessageBox (GetDesktopWindow(),
TEXT("Registered Successfully!"),
szTitleServer, MB_OK);
return S_OK;
}
}
else
vfServer = TRUE;
}
        }

    // initialize COM
    hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
    if (FAILED(hr))
        {
        ErrorMessage(NULL, TEXT("CoInitializeEx"), hr);
        return hr;
        }

// psd must remain valid until after we uninit COM
if (!CreateAppSecurityDescriptor(&psd))
{
return E_FAIL;
}

// initialize the security layer
hr = CoInitializeSecurity (
psd,
-1,
NULL,
NULL,
RPC_C_AUTHN_LEVEL_CONNECT,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL,
0,
NULL);
    if (FAILED(hr))
        {
        ErrorMessage(NULL, TEXT("CoInitializeSecurity"), hr);
        return hr;
        }

    // create the threads and synchronization events
    // which the server will need
    for (i=0; i<cServerThreads; i++)
        {
        TCHAR    rgch[32];

        // create the thread suspended so its event can be
        // created using its thread-id and it will be able to
        // use it as soon as it runs
        vrghThread[i] = CreateThread(NULL,
            0,
            (LPTHREAD_START_ROUTINE)&ServerThreadProc,
            0,
            CREATE_SUSPENDED,
            &vrgtid[i]);
        if (vrghThread[i] == NULL)
            {
            hr = GetLastError();
            goto LCleanup;
            }

        // a thread can use its thread-id to OpenEvent this existing
        // event at any time. it can also look for its thread-id in
        // vrgtid[] to determine its index for use with vrghEvent.
        // this event signals to a worker thread to create a new CObject
        wsprintf(rgch, TEXT("Thread_%d"), vrgtid[i]);
        vrghEvent[i] = CreateEvent(NULL, FALSE, FALSE, rgch);
        if (vrghEvent[i] == NULL)
            {
            hr = GetLastError();
            goto LCleanup;
            }

// initially no user is associated with a thread
vrgpSid[i] = NULL;
vrgpszAptUser[i] = NULL;

        // now that the event is available, let the thread run
        ResumeThread(vrghThread[i]);
        }

    // this signals the status of a worker threads creation after
    // receiving its create signal via vrghEvent[i]
    vhEventServer = CreateEvent(NULL, FALSE, FALSE, TEXT("Server"));
    if (vhEventServer == NULL)
        {
        hr = GetLastError();
        goto LCleanup;
        }

    // register the class-factory with COM
    hr = CoRegisterClassObject(CLSID_CObject,
        (IUnknown *)&vclassfactory,
        CLSCTX_LOCAL_SERVER,
        REGCLS_MULTIPLEUSE,
        &dwRegister);
    if (FAILED(hr))
        goto LCleanup;

    // create an IStream to be used for marshalling new objects between
    // the worker and the CClassFactory
    hr = CreateStreamOnHGlobal(NULL, TRUE, &vpstmMarshalling);
    if (FAILED(hr))
        goto LCleanup;

    // create the server UI
    memset(&wndclass, 0, sizeof(wndclass));
    wndclass.lpszClassName = szWindowClass;
    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = MainWndProc;
    wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
    wndclass.hInstance = hinst;

    if (!RegisterClass(&wndclass) ||
        (vhwndMain = CreateWindow(szWindowClass,
            szTitleServer,
            WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
            HWND_DESKTOP, NULL, hinst, NULL)) == NULL)
        {
        hr = GetLastError();
        goto LCleanup;
        }

    ShowWindow(vhwndMain, nCmdShow);

    while (GetMessage(&msg, NULL, 0, 0))
        {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
        }
    hr = msg.wParam;

LCleanup:
    if (hr != S_OK)
        ErrorMessage(NULL, TEXT("CoInitializeEx"), hr);

    // we explicitly don't clean up threads and events
    if (dwRegister != 0)
        CoRevokeClassObject(dwRegister);
    if (vpstmMarshalling != NULL)
        vpstmMarshalling->Release();

    CoUninitialize();

FreeAppSecurityDescriptor (psd);

    return hr;
}  // WinMain

// ---------------------------------------------------------------------------
// %%Function: FAutoRegister
//  Registers the CObject class in the registry.
// ---------------------------------------------------------------------------
 BOOL
FAutoRegister(HINSTANCE hinst)
{
    static const TCHAR szClassDesc[]= TEXT("Ole Apartment Impersonation Sample");
    static const TCHAR szCLSIDEntry[]= TEXT("CLSID\\{035B79D1-D6D3-11cf-B9D4-00AA00A216E0}");
static const TCHAR szAppIDEntry[]= TEXT("APPID\\{035B79D1-D6D3-11cf-B9D4-00AA00A216E0}");
static const TCHAR szAppIDString[]= TEXT("{035B79D1-D6D3-11cf-B9D4-00AA00A216E0}");
static const TCHAR szAppID[]= TEXT("AppID");
static const TCHAR szLocalServer32[]= TEXT("LocalServer32");
static const TCHAR szRunAs[]= TEXT("RunAs");
static const TCHAR szInteractiveUser[]= TEXT("Interactive User");
    TCHAR       szPath[512];
TCHARszModuleEntry[512];
LPTSTRindex;
    HKEY        hkeyT = NULL;

    // install the CLSID key and get the FQP for this executable
    if ((RegSetValue(HKEY_CLASSES_ROOT, szCLSIDEntry, REG_SZ, szClassDesc,
            lstrlen(szClassDesc)) != ERROR_SUCCESS) ||
        (RegCreateKey(HKEY_CLASSES_ROOT, szCLSIDEntry, &hkeyT)
            != ERROR_SUCCESS) ||
        !GetModuleFileName(hinst, szPath, sizeof(szPath)))
        return FALSE;

// install the LocalServer32 value
    if (RegSetValue(hkeyT, szLocalServer32, REG_SZ, szPath, lstrlen(szPath))
            != ERROR_SUCCESS)
        goto LErrExit;

// install a pointer to our App ID
    if (RegSetValueEx(hkeyT, szAppID, 0, REG_SZ, (LPBYTE)szAppIDString,
    sizeof(szAppIDString)) != ERROR_SUCCESS)
        goto LErrExit;

    RegCloseKey(hkeyT);

// install the App ID
if ((RegSetValue(HKEY_CLASSES_ROOT, szAppIDEntry, REG_SZ, szClassDesc,
lstrlen(szClassDesc)) != ERROR_SUCCESS) ||
(RegCreateKey(HKEY_CLASSES_ROOT, szAppIDEntry, &hkeyT)
!= ERROR_SUCCESS))
goto LErrExit;

// indicate that we should run as the interactive user
if (RegSetValueEx(hkeyT, szRunAs, 0, REG_SZ, (LPBYTE)szInteractiveUser,
sizeof(szInteractiveUser)) != ERROR_SUCCESS)
goto LErrExit;

    RegCloseKey(hkeyT);

// get the App ID module entry
index = _tcsrchr (szPath, TEXT('\\'));
if (NULL == index)
index = szPath;
else
index += 1;

lstrcpy (szModuleEntry, TEXT("APPID\\"));
lstrcat (szModuleEntry, index);

// Create the module entry in the APPID tree
if (RegCreateKey(HKEY_CLASSES_ROOT, szModuleEntry, &hkeyT)
!= ERROR_SUCCESS)
goto LErrExit;

// associate the App ID with the module
if (RegSetValueEx(hkeyT, szAppID, 0, REG_SZ, (LPBYTE)szAppIDString,
sizeof(szAppIDString)) != ERROR_SUCCESS)
goto LErrExit;

    RegCloseKey(hkeyT);

    return TRUE;

LErrExit:
    RegCloseKey(hkeyT);
    return FALSE;
}  // FAutoRegister

// ===========================================================================
//                          C C L A S S F A C T O R Y
// ===========================================================================

// ---------------------------------------------------------------------------
// %%Function: CClassFactory::QueryInterface
//  Returns a new reference of the specified iid-type to a CClassFactory.
// ---------------------------------------------------------------------------
 STDMETHODIMP
CClassFactory::QueryInterface(REFIID iid, void **ppv)
{
    *ppv = NULL;

    if (iid == IID_IClassFactory || iid == IID_IUnknown)
        {
        *ppv = (IClassFactory *)this;
        }
    if (*ppv != NULL)
        {
        AddRef();
        return S_OK;
        }
    return E_NOINTERFACE;
}  // CClassFactory::QueryInterface

// ---------------------------------------------------------------------------
// %%Function: CClassFactory::CreateInstance
//  Creates a new instance of a CObject on a worker thread.
// ---------------------------------------------------------------------------
 STDMETHODIMP
CClassFactory::CreateInstance(LPUNKNOWN punkOuter, REFIID iid, void **ppv)
{
    LPUNKNOWN   punk;
    HRESULT     hr;
HANDLEhToken = NULL, hPrimaryToken = NULL;
PSIDpsidUser = NULL;
intiThread;

// Get the client's token
hr = CoImpersonateClient ();
    if (FAILED(hr))
    {
        ErrorMessage(NULL, TEXT("CoImpersonateClient"), hr);
    return hr;
}

if (!OpenThreadToken(GetCurrentThread(), TOKEN_ALL_ACCESS, TRUE, &hToken))
{
hr = GetLastError();
ErrorMessage(NULL, TEXT("OpenThreadToken"), hr);
return hr;
}

CoRevertToSelf ();

__try
{
if (!GetUserSidFromToken (hToken, &psidUser))
{
ErrorMessage(NULL, TEXT("GetUserSidFromToken"), 0);
return(E_FAIL);
}

// find an index for this user
if (!FindSlot(psidUser, &iThread))
return(E_OUTOFMEMORY);// not enough "resources"

// if this is a previously free slot, set it up
if (NULL == vrgpSid[iThread])
{
// The impersonation token we first get disappears after
// the call completes.  We need to get a more permanent
// token for the worker thread.
if (!DuplicateTokenEx(hToken, TOKEN_ALL_ACCESS, NULL,
SecurityImpersonation, TokenPrimary, &hPrimaryToken))
    {
hr = GetLastError ();
        ErrorMessage(NULL, TEXT("DuplicateTokenEx"), hr);
return(hr);
}

CloseHandle (hToken);
hToken = NULL;

if (!DuplicateTokenEx(hPrimaryToken, TOKEN_ALL_ACCESS, NULL,
SecurityImpersonation, TokenImpersonation, &hToken))
    {
hr = GetLastError ();
        ErrorMessage(NULL, TEXT("DuplicateTokenEx"), hr);
return(hr);
}

if (!SetThreadToken (&vrghThread[iThread], hToken))
{
hr = GetLastError ();
ErrorMessage(NULL, TEXT("SetThreadToken"), hr);
return(hr);
}

// store and null out stuff we don't want freed or closed
vrgpSid[iThread] = psidUser;
psidUser = NULL;
}
}
__finally
{
UtilFreeMem(psidUser);
if (NULL != hToken)
CloseHandle (hToken);
if (NULL != hPrimaryToken)
CloseHandle (hPrimaryToken);
}

    *ppv = NULL;
    if (punkOuter != NULL)
        return CLASS_E_NOAGGREGATION;

    // trigger the worker thread that we want to create an object
    SetEvent(vrghEvent[iThread]);

    // now wait for the object to signal its completion
    WaitForSingleObject(vhEventServer, INFINITE);

    // once the worker thread signals completion, vhrThreadStatus
    // lets us know if the creation process was successful, and if
    // vpstmMarshalling creates a marshalled interface pointer
    if (FAILED(vhrThreadStatus))
        return vhrThreadStatus;

    // unmarshal an IUnknown from the scratch stream. if unmarshaling
    // fails, it takes care of releasing the object inside the marshal-data
    hr = vpstmMarshalling->Seek(bZero, STREAM_SEEK_SET, NULL);
    if (FAILED(hr))
        return hr;
    hr = CoUnmarshalInterface(vpstmMarshalling, IID_IUnknown, (void **)&punk);
    if (FAILED(hr))
        return hr;

    // get a reference to the interface asked for
    hr = punk->QueryInterface(iid, ppv);
    punk->Release();

    return hr;
}  // CClassFactory::CreateInstance

 STDMETHODIMP
CClassFactory::LockServer(BOOL fLock)
{
    // there's no need to support this for this sample
    return E_FAIL;
}  // CClassFactory::LockServer

// ===========================================================================
//                               C O B J E C T
// ===========================================================================

// ---------------------------------------------------------------------------
// %%Function: ServerThreadProc
//  The worker thread function. Handles messages for objects of its thread/apt
// and creates new objects.
// ---------------------------------------------------------------------------
 LRESULT
ServerThreadProc(LPARAM lParam)
{
    HRESULT hr;
    MSG     msg;
    int     iThread;

    // figure out which thread this is: it needs its synchronization event
    for (iThread=0; iThread<cServerThreads; iThread++)
        {
        if (vrgtid[iThread] == GetCurrentThreadId())
            break;
        }
    if (iThread==cServerThreads)
        return E_UNEXPECTED;

    // initialize COM
    hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
    if (FAILED(hr))
        {
        MessageBeep(0);
        return hr;
        }

    // apartment message/event loop
    // (see SDK documentation for MsgWaitForMultipleObjects)
    // here worker message loops last forever. in situations without a
    // static number of worker threads, the loop could easily be terminated by
    // WM_QUITs sent from the main thread which might manage the worker thread
    // pool more carefully.
    while (TRUE)
        {
        DWORD dwWaitResult;

        // wait for any message sent or posted to this queue
        // or for one of the passed handles to become signaled
        dwWaitResult = MsgWaitForMultipleObjects(1, &vrghEvent[iThread],
            FALSE, INFINITE, QS_ALLINPUT);

        // result tells us the type of event we have:
        // a message or a signaled handle

        // if there are one or more messages in the queue ...
        if (dwWaitResult == (WAIT_OBJECT_0 + 1))
            {
            // dispatch all of the messages in this next loop
            // (here is where we'd check for WM_QUITs to end this
            // worker thread if we wanted to)
            while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
                DispatchMessage(&msg);
            }
        else
            {
            // this thread was signaled to create a new object
            try
                {
                LPUNKNOWN   punk;

// if the user name isn't set, look it up
// we could do this in the class factory, but this gives
// us a sanity check on the impersonation
if (NULL == vrgpszAptUser[iThread])
{
LPTSTR pszUser;
DWORD cchUser = MAX_USER_NAME;

if (!UtilAllocMem ((PVOID*)&pszUser, cchUser*sizeof(TCHAR)))
throw E_OUTOFMEMORY;

if (!GetUserName (pszUser, &cchUser))
lstrcpy (pszUser, TEXT("Name not found"));

vrgpszAptUser[iThread] = pszUser;
}

                // create a new CObject
                punk = (IUnknown *)new CObject;
                if (punk == NULL)
                    throw E_OUTOFMEMORY;

                hr = vpstmMarshalling->Seek(bZero, STREAM_SEEK_SET, NULL);
                if (FAILED(hr))
                    throw hr;
                hr = CoMarshalInterface(vpstmMarshalling,
                    IID_IUnknown,
                    punk,
                    MSHCTX_INPROC,
                    NULL,
                    MSHLFLAGS_NORMAL);
                if (FAILED(hr))
                    throw hr;

                // punk is now referenced by its marshal-data in vpstmMarshalling.
                // we release our local reference here so the unmarshaller will
                // have the sole reference. a common mistake is to forget this
                // release and end up with orphaned objects in the server.
                punk->Release();

                // modify the global state -- the # of objects/thread -- which is
                // displayed in the server UI
                vrgcObjects[iThread]++;
                InvalidateRect(vhwndMain, NULL, TRUE);

                vhrThreadStatus = S_OK;
                }
            catch (HRESULT hr)
                {
                vhrThreadStatus = hr;
                }
            SetEvent(vhEventServer);
            }

        }

    CoUninitialize();
    return msg.wParam;
}  // ServerThreadProc

// ---------------------------------------------------------------------------
// %%Function: CObject::QueryInterface
//  Returns a new reference of the specified iid-type to a CObject.
// ---------------------------------------------------------------------------
 STDMETHODIMP
CObject::QueryInterface(REFIID iid, void **ppv)
{
    *ppv = NULL;

    if (iid == IID_IUnknown)
        {
        *ppv = (IUnknown *)this;
        }
    if (*ppv != NULL)
        {
        AddRef();
        return S_OK;
        }
    return E_NOINTERFACE;
}  // CObject::QueryInterface

// ---------------------------------------------------------------------------
// %%Function: CObject::Release
//  Handles releases of references to a CObject. Purpose here is to have code
// which alters the global state which is displayed in the servers UI.
// ---------------------------------------------------------------------------
 STDMETHODIMP_(ULONG)
CObject::Release(void)
{
    if (--mcRef == 0)
        {
        // change the global server state -- the # of objects/thread --
        // reflected in the server UI
        for (int i=0; i<cServerThreads; i++)
            {
            if (vrgtid[i] == GetCurrentThreadId())
                {
// A sanity check to see if we're still impersonating...
TCHAR szUser[MAX_USER_NAME];
DWORD cchUser = MAX_USER_NAME;

if (!GetUserName (szUser, &cchUser))
ErrorMessage(NULL, TEXT("GetUserName"), GetLastError ());
else
{
if (lstrcmp (szUser, vrgpszAptUser[i]))
ErrorMessage (NULL, TEXT("Impersonation error."), S_OK);
}

                vrgcObjects[i]--;
                InvalidateRect(vhwndMain, NULL, TRUE);
                break;
                }
            }

        delete this;
        return 0;
        }
    return mcRef;
}  // CObject::Release

// ---------------------------------------------------------------------------
// %%Function: FindSlot
//  Determines what slot in the array of worker threads may be used by the
// user represented by psid.
// ---------------------------------------------------------------------------
BOOL FindSlot(PSID psid, int *piThread)
{
int iThread;

// see if there is already an apartment impersonating this user
for (iThread = 0; iThread < cServerThreads; iThread++)
{
if ((NULL != vrgpSid[iThread]) &&
(EqualSid (psid, vrgpSid[iThread])))
break;
}

// if this user didn't have a thread already, find a free one
if (iThread == cServerThreads)
{
for (iThread = 0; iThread < cServerThreads; iThread++)
{
if (NULL == vrgpSid[iThread])
break;
}

if (iThread == cServerThreads) // no resources available...
return(FALSE);
}

// return the index to use
*piThread = iThread;
return(TRUE);
}

// EOF =======================================================================


CLIENT.CPP

// ===========================================================================
// File: C L I E N T . C P P
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
//  This is the client-portion of the OLE impersonation sample. This
// application uses the CLSID_CObject class implemented by the OLEIMP.CPP
// module. Pass the machine-name to instantiate the object on, or pass no
// arguments to instantiate the object on the same machine. See the README.TXT
// file for further details.
//
// This sample may be compiled as either UNICODE or ANSI.
//
// Copyright 1996 Microsoft Corporation.  All Rights Reserved.
// ===========================================================================

// %%Includes: ---------------------------------------------------------------
#define INC_OLE2
#include <stdio.h>
#include <windows.h>
#include <initguid.h>
#include <tchar.h>
#include <conio.h>

// %%GUIDs: ------------------------------------------------------------------
DEFINE_GUID(CLSID_CObject, 0x35b79d1, 0xd6d3, 0x11cf, 0xb9, 0xd4, 0x0, 0xaa, 0x0, 0xa2, 0x16, 0xe0);

// ---------------------------------------------------------------------------
// %%Function: Message
//
//  Formats and displays a message to the console.
// ---------------------------------------------------------------------------
 void
Message(LPTSTR szPrefix, HRESULT hr)
{
    LPTSTR   szMessage;

    if (hr == S_OK)
        {
        _tprintf(szPrefix);
        _tprintf(TEXT("\n"));
        return;
        }

    if (HRESULT_FACILITY(hr) == FACILITY_WINDOWS)
        hr = HRESULT_CODE(hr);

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL,
        hr,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //The user default language
        (LPTSTR)&szMessage,
        0,
        NULL );

    _tprintf (TEXT("%s: %s(%lx)\n"), szPrefix, szMessage, hr);

    LocalFree(szMessage);
}  // Message

// ---------------------------------------------------------------------------
// %%Function: main
// ---------------------------------------------------------------------------
 void __cdecl
main(int argc, CHAR **argv)
{
    HRESULT hr;
    COSERVERINFO csi, *pcsi=NULL;
    WCHAR wsz [MAX_PATH];
LPCLASSFACTORY pcf = NULL;

    // initialize COM for free-threaded use
    hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    if (FAILED(hr))
        {
        Message(TEXT("Client: CoInitializeEx"), hr);
        exit(hr);
        }

// initialize the security layer
hr = CoInitializeSecurity (
NULL,
-1,
NULL,
NULL,
RPC_C_AUTHN_LEVEL_CONNECT,
RPC_C_IMP_LEVEL_IMPERSONATE,
NULL,
0,
NULL);
if (FAILED(hr))
{
Message(TEXT("Client: CoInitializeSecurity"), hr);
exit(hr);
}

    // allow a machine-name as the command-line argument
    if (argc > 1)
        {
        MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, argv[1], -1,
            wsz, MAX_PATH);
memset (&csi, 0, sizeof (csi));
        csi.pwszName = wsz;
        pcsi = &csi;
        }

// get the class factory for CObject on the argv[1] machine
hr = CoGetClassObject (CLSID_CObject, CLSCTX_SERVER, pcsi,
IID_IClassFactory, (LPVOID*)&pcf);
if (FAILED(hr))
Message(TEXT("Client: CoGetClassObject"), hr);
else
{
        LPUNKNOWN   punk = NULL;

    // create a remote instance of the object
    Message(TEXT("Client: Creating Instance..."), S_OK);
    hr = pcf->CreateInstance(NULL, IID_IUnknown, (LPVOID*)&punk);
    if (FAILED(hr))
        Message(TEXT("Client: CoCreateInstanceEx"), hr);
    else
        {
// wait until the user wants us to release the object
Message(TEXT("Client: Press any key to release object..."), S_OK);
getch();

        // let go of the object
        punk->Release();
        }
pcf->Release();
}
    CoUninitialize();
    Message(TEXT("Client: Done"), S_OK);
}  // main

// EOF =======================================================================


UTIL.CPP

// ===========================================================================
// File: U T I L . C P P
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright 1996 Microsoft Corporation.  All Rights Reserved.
// ===========================================================================

// %%Includes: ---------------------------------------------------------------
#define INC_OLE2
#define STRICT
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include "util.h"

// ---------------------------------------------------------------------------
// %%Function: UtilAllocMem
// ---------------------------------------------------------------------------
BOOL UtilAllocMem (PVOID *ppv, DWORD cb)
{
*ppv = malloc (cb);
return (NULL != *ppv);
}

// ---------------------------------------------------------------------------
// %%Function: UtilFreeMem
// ---------------------------------------------------------------------------
void UtilFreeMem (PVOID pv)
{
if (NULL != pv)
free (pv);
}

// ---------------------------------------------------------------------------
// %%Function: GetUserSidFromToken
//  Returns the SID of the user identified by the supplied token.
// ---------------------------------------------------------------------------
BOOL GetUserSidFromToken (HANDLE hToken, PSID *ppSid)
{
TOKEN_USER *pUser = NULL;
PSID psidUser = NULL;
DWORD cbSid;
DWORD cbRequired;

if (GetTokenInformation (hToken, TokenUser, NULL, 0, &cbRequired))
return(FALSE);// we shouldn't succeed without a buffer...

if (ERROR_INSUFFICIENT_BUFFER != GetLastError ())
return(FALSE);

if (!UtilAllocMem ((PVOID*)&pUser, cbRequired))
return(FALSE);

__try  {
if (!GetTokenInformation (hToken, TokenUser, pUser, cbRequired,
&cbRequired))
return(FALSE);

// Need a buffer with just the SID, so allocate and copy
cbSid = GetLengthSid (pUser->User.Sid);
if (!UtilAllocMem ((PVOID*)&psidUser, cbSid))
return(FALSE);

if (!CopySid (cbSid, psidUser, pUser->User.Sid))
return(FALSE);

*ppSid = psidUser;
psidUser = NULL;// so we don't free the sid

return(TRUE);
} __finally  {
UtilFreeMem (psidUser);
UtilFreeMem (pUser);
}
}

// ---------------------------------------------------------------------------
// %%Function: GetGroupSidFromToken
//  Returns the SID for the primary group in the supplied token
// ---------------------------------------------------------------------------
BOOL GetGroupSidFromToken (HANDLE hToken, PSID *ppSid)
{
TOKEN_PRIMARY_GROUP *pGroup = NULL;
PSID psidGroup = NULL;
DWORD cbSid;
DWORD cbRequired;

if (GetTokenInformation (hToken, TokenPrimaryGroup, NULL, 0, &cbRequired))
return(FALSE);// something's really screwy

if (ERROR_INSUFFICIENT_BUFFER != GetLastError ())
return(FALSE);

if (!UtilAllocMem ((PVOID*)&pGroup, cbRequired))
return(FALSE);

__try  {
if (!GetTokenInformation (hToken, TokenPrimaryGroup, pGroup,
cbRequired, &cbRequired))
return(FALSE);

// Need a buffer with just the SID, so allocate and copy
cbSid = GetLengthSid (pGroup->PrimaryGroup);
if (!UtilAllocMem ((PVOID*)&psidGroup, cbSid))
return(FALSE);

if (!CopySid (cbSid, psidGroup, pGroup->PrimaryGroup))
return(FALSE);

*ppSid = psidGroup;
psidGroup = NULL;// so we don't free the sid

return(TRUE);
} __finally  {
UtilFreeMem (psidGroup);
UtilFreeMem (pGroup);
}
}

// ---------------------------------------------------------------------------
// %%Function: CreateAppSecurityDescriptor
//  Creates a security descriptor that allows the world access and is
// owned by the current user.  AccessCheck is used to verify the SD
// because an SD can be valid without having everything required by
// CoInitializeSecurity.
// ---------------------------------------------------------------------------
BOOL CreateAppSecurityDescriptor(PSECURITY_DESCRIPTOR *ppsd)
{
PSECURITY_DESCRIPTOR psd = NULL;
PSID psidUser = NULL, psidGroup = NULL;
HANDLE hToken;
GENERIC_MAPPING gm =  {1, 2, 4, 8};// dummy GM for AccessCheck
DWORD dwDesired = 1;// MUST be a value from GM
BOOL fAccess = FALSE;
DWORD dwGranted;
PRIVILEGE_SET ps;
DWORD cbPriv = sizeof (ps);

// allocate the security descriptor
if (!UtilAllocMem ((PVOID*)&psd, SECURITY_DESCRIPTOR_MIN_LENGTH))
return(FALSE);

// we have to impersonate ourselves for the AccessCheck later
if (!ImpersonateSelf(SecurityImpersonation))
return(FALSE);

__try  {

if (!InitializeSecurityDescriptor(psd, SECURITY_DESCRIPTOR_REVISION))
return(FALSE);

// give it a NULL DACL -- allow all access
if (!SetSecurityDescriptorDacl (psd, TRUE, NULL, FALSE))
return(FALSE);

// open the token to get the user and group for the new SD
if (!OpenThreadToken (GetCurrentThread(), TOKEN_QUERY, TRUE, &hToken))
return(FALSE);

// set the SD owner
if (!GetUserSidFromToken(hToken, &psidUser))
return(FALSE);
if (!SetSecurityDescriptorOwner(psd, psidUser, FALSE))
return(FALSE);

// set the SD group
if (!GetGroupSidFromToken(hToken, &psidGroup))
return(FALSE);
if (!SetSecurityDescriptorGroup(psd, psidGroup, FALSE))
return(FALSE);

// use AccessCheck to verify that we have a good SD
// using dummy values since we don't really have anything to check
if (!AccessCheck(psd, hToken, dwDesired, &gm, &ps, &cbPriv,
&dwGranted, &fAccess))
return(FALSE);

*ppsd = psd;
psd = NULL;//
psidUser = NULL;// don't free these...
psidGroup = NULL;//

return(TRUE);

} __finally  {
UtilFreeMem (psd);
UtilFreeMem (psidUser);
UtilFreeMem (psidGroup);
RevertToSelf ();
}
}

// ---------------------------------------------------------------------------
// %%Function: FreeAppSecurityDescriptor
//  Releases resources used by the security descriptor.
// ---------------------------------------------------------------------------
BOOL FreeAppSecurityDescriptor (PSECURITY_DESCRIPTOR psd)
{
PSID psid;
BOOL fDefaulted;

if (!GetSecurityDescriptorOwner(psd, &psid, &fDefaulted))
return(FALSE);
UtilFreeMem (psid);

if (!GetSecurityDescriptorGroup(psd, &psid, &fDefaulted))
return(FALSE);
UtilFreeMem (psid);

UtilFreeMem (psd);

return(TRUE);
}

// ---------------------------------------------------------------------------
// %%Function: ErrorMessage
// ---------------------------------------------------------------------------
void ErrorMessage(HWND hwnd, LPCTSTR szFunction, HRESULT hr)
{
LPTSTR   szMessage;

if (HRESULT_FACILITY(hr) == FACILITY_WINDOWS)
hr = HRESULT_CODE(hr);

if (!FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL, hr,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //The user default
(LPTSTR)&szMessage, 0, NULL))
return;

if (hwnd == NULL)
{
OutputDebugString(szFunction);
OutputDebugString(TEXT(": "));
OutputDebugString(szMessage);
OutputDebugString(TEXT("\n"));
}
else
MessageBox(hwnd, szMessage, szFunction, MB_OK);

LocalFree(szMessage);
}  // ErrorMessage

// EOF =======================================================================


UTIL.H

// ===========================================================================
// File: U T I L . H
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright 1996 Microsoft Corporation.  All Rights Reserved.
// ===========================================================================

BOOL UtilAllocMem (PVOID *ppv, DWORD cb);
void UtilFreeMem (PVOID pv);
BOOL GetUserSidFromToken (HANDLE hToken, PSID *ppSid);
BOOL GetGroupSidFromToken (HANDLE hToken, PSID *ppSid);
BOOL CreateAppSecurityDescriptor(PSECURITY_DESCRIPTOR *ppsd);
BOOL FreeAppSecurityDescriptor (PSECURITY_DESCRIPTOR psd);
void ErrorMessage(HWND hwnd, LPCTSTR szFunction, HRESULT hr);

// EOF =======================================================================


README.TXT

Secure Network OLE

DCOM is implemented only on Windows NT 4.0 and later versions.  This sample
will not work correctly on Windows 95 or on earlier versions of Windows NT.

The SECURE sample consists of a client portion and a server portion. Its
purpose is to demonstrate client-side and server-side security in a Network
OLE application.

The server application, SECSVR.EXE, demonstrates the packaging options
available to server writers and the call-security capabilities. It implements
the following server capabilities:

 - A free-threaded server implemented as a service. The application implements
   the class "LocalService" (CLSID_SecureObjectService).
 - A free-threaded server implemented as a standard executable. The application
   implements the class "LocalServer32" (CLSID_SecureObject).
 - An apartment-threaded server implemented as a standard executable. The
   application implements the class "LocalServer32" (CLSID_SecureObject).

The client application, SECCLNT.EXE, allows you to select security settings
for the client application's connections to the server application.

This sample also include PSMYPROP.DLL, which supplies the remoting proxies
and stubs for the custom interface used between the client and the server.

To compile
----------

Build the client and the server by running NMAKE.

To run
------

The run the client and server, use the following steps:

 - Install the server by running INSTALL.BAT. Note that SECSVR.EXE and
   PSMYPROP.DLL must be in the same directory as INSTALL.BAT.

 - To run the apartment-threaded server, use the command "SECSVR -Interactive".
   Choose options to initialize security and view objects used by the clients.

 - Run SECCLNT.EXE. The user interface has three different sections.
    - In section one, choose the security parameters to CoInitializeSecurity
      from the two drop-down lists. Click the Initialize button.
    - In section two, choose a machine name to instantiate the object on or
      leave blank to use the current machine. In addition, choose between the
      LocalServer32 or LocalService objects. Click the Create Now button to
      create an instance.
    - In section three, set the color and the name of the object. This results
      in changes to the IMyProperties interface.
   Click the Release button in section two to release the object and connect to
   a different object. Click the Exit button in section one at any time to exit.

 - Once a client has created an instance of the "LocalServer32" class, the
   object will be listed in the interface of the server. Select the object to
   be viewed from the list. Use the Lock button to prevent the client from
   setting the color or name of the object. The client will display an
   access-denied message if it fails to set the color or name of the object.


MYPROP.IDL

// ===========================================================================
// File: M Y P R O P S . I D L
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright 1996 Microsoft Corporation. All Rights Reserved.
// ===========================================================================

#ifndef DO_NO_IMPORTS
import "unknwn.idl";
#endif

cpp_quote("DEFINE_GUID(CLSID_SecureObject, 0x28f64ee0, 0x4656, 0x11cf, 0x81, 0x10, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);")
cpp_quote("DEFINE_GUID(CLSID_SecureObjectService, 0x28f64ee2, 0x4656, 0x11cf, 0x81, 0x10, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);")
cpp_quote("DEFINE_GUID(IID_IMyProperties, 0x28f64ee1, 0x4656, 0x11cf, 0x81, 0x10, 0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71);")

[
object,
uuid(28f64ee1-4656-11cf-8110-00aa00389b71)
]
interface IMyProperties : IUnknown
{
typedef [unique] IMyProperties* LPMYPROPERTIES;

HRESULT GetColor
(
[out] COLORREF* pcr
);
HRESULT PutColor
(
[in] COLORREF cr
);
HRESULT GetUserName
(
[in, out] WCHAR szUserName[20]
);
HRESULT PutUserName
(
[in] WCHAR szUserName[20]
);
}

// EOF =======================================================================


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by secsvr.rc
//
#define IDC_COLOR                       1003
#define IDC_CHOOSECOLOR                 1004
#define IDC_COLORTEXT                   1005

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC                     1
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1006
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


SECCLNT.CPP

// ===========================================================================
// File: S E C C L N T . C P P
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Description:
//
//  This is the client-portion of the SECURE Network OLE sample. This
// application displays UI to allow you to select security settings for
// the client process's connections to the server, connect to local or remote
// instances of either the "LocalServer32" (CLSID_SecureObject) or "LocalService"
// (CLSID_SecureObjectService) objects.
//
//  The purpose of this sample is to demonstrate client-side security
// capabilities.
//
// Instructions:
//
//  To use this sample:
//   * build the samples using the NMAKE command. NMAKE will create SECCLNT.EXE,
//     SECSVR.EXE, and PSMYPROP.DLL. PSMYPROP.DLL is the remoting proxies and
//     stubs for the custom interface used between the client and the server.
//   * install the server on the current machine or on a remote machine
//     by running INSTALL.BAT in the same directory with SECSVR.EXE and
//     PSMYPROP.DLL. This program registers the proxy-stub DLL using the
//     REGSVR32 tool (included in the \mstools\bin directory of the SDK) and
//     runs the SECSVR.EXE program with "-AutoRegister" on the command-line,
//     which forces it to install itself.
//   * next, run SECCLNT.EXE. it will bring up a user interface with 3 different
//     sections.
//   * In Section 1, choose the security parameters to CoInitializeSecurity from
//     the two drop-down lists, and press the Initialize button.
//   * Next, in Section 2, choose a machine name to instantiate the object on
//     (leave this field blank to use the current machine) and choose between the
//     "LocalService" or the standard "LocalServer32" implementation of the object.
//     Then, press the "Create Now" button to create an instance.
//   * With some security settings, you will receive a message box at this point
//     telling you that the connection to the object has no authentication. In this
//     case, the call-level security choices in Section 3 will not be made available.
//   * Next, go to Section 3 and set the color and the "Name" on the object.
//     These UI elements make calls on the custom IMyProperties interface. If you
//     have pre-launched SECSVR.EXE with the "-Interactive" flag on the local or
//     remote machine that you are connected to, you will be able to see the
//     changes you make to the color and the name reflected in its UI, and may
//     adjust some simple security settings (see the Instructions section of
//     SECSVR.CPP for details).
//   * Push the "Release" button in Section 2 to release the object and connect to
//     a different object. When you are done, close the dialog box or push the
//     "Exit" button in Section 1 at any time to exit.
//
// Copyright 1996 Microsoft Corporation. All Rights Reserved.
// ===========================================================================

// %%Includes: ---------------------------------------------------------------
#define INC_OLE2
#define UNICODE
#include <windows.h>
#include <initguid.h>
#include "myprop.h"
#include <rpcdce.h>
#include "secclnt.h"

// %%Macros: -----------------------------------------------------------------
#define ENTRY(sz, val)      { sz, val }
#define RGSIZEOF(rg)        (sizeof(rg)/sizeof((rg)[0]))

// %%Constants: --------------------------------------------------------------
#define cServerEntries      RGSIZEOF(rgServerEntries)
#define cAuthnLevelEntries  RGSIZEOF(rgAuthnLevelEntries)
#define cAuthzEntries       RGSIZEOF(rgAuthzEntries)
#define cImpLevelEntries    RGSIZEOF(rgImpLevelEntries)
#define cAuthnEntries       RGSIZEOF(rgAuthnEntries)

// %%Types: ------------------------------------------------------------------
typedef struct tagENTRY{
    TCHAR   *szName;
    DWORD   dwVal;
} ENTRY, *PENTRY;

// %%Globals: ----------------------------------------------------------------
COLORREF s_crCurrent;
HBRUSH   s_hbrCurrent = NULL;

ENTRY   rgServerEntries[] = {
    ENTRY(TEXT("Secure Object"), (DWORD)(LPVOID)&CLSID_SecureObject),
    ENTRY(TEXT("Secure Object Service"), (DWORD)(LPVOID)&CLSID_SecureObjectService),
    };

ENTRY   rgAuthnLevelEntries[] = {
    ENTRY(TEXT("Default    "), RPC_C_AUTHN_DEFAULT),
    ENTRY(TEXT("None       "), RPC_C_AUTHN_LEVEL_NONE),
    ENTRY(TEXT("Connect    "), RPC_C_AUTHN_LEVEL_CONNECT),
    ENTRY(TEXT("Call       "), RPC_C_AUTHN_LEVEL_CALL),
    ENTRY(TEXT("Packet     "), RPC_C_AUTHN_LEVEL_PKT),
    ENTRY(TEXT("Integrity  "), RPC_C_AUTHN_LEVEL_PKT_INTEGRITY),
    ENTRY(TEXT("Privacy    "), RPC_C_AUTHN_LEVEL_PKT_PRIVACY),
    };
ENTRY   rgAuthzEntries[] = {
    ENTRY(TEXT("None       "), RPC_C_AUTHZ_NONE),
    ENTRY(TEXT("Name       "), RPC_C_AUTHZ_NAME),
    ENTRY(TEXT("DCE        "), RPC_C_AUTHZ_DCE),
    };

ENTRY   rgImpLevelEntries[] = {
    ENTRY(TEXT("Anonymous  "), RPC_C_IMP_LEVEL_ANONYMOUS),
    ENTRY(TEXT("Identify   "), RPC_C_IMP_LEVEL_IDENTIFY),
    ENTRY(TEXT("Impersonate"), RPC_C_IMP_LEVEL_IMPERSONATE),
    ENTRY(TEXT("Delegate   "), RPC_C_IMP_LEVEL_DELEGATE),
    };

ENTRY   rgAuthnEntries[] = {
    ENTRY(TEXT("None       "), RPC_C_AUTHN_NONE),
    ENTRY(TEXT("DCE Private"), RPC_C_AUTHN_DCE_PRIVATE),
    ENTRY(TEXT("DCE Public "), RPC_C_AUTHN_DCE_PUBLIC),
    ENTRY(TEXT("DEC Public "), RPC_C_AUTHN_DEC_PUBLIC),
    ENTRY(TEXT("Windows NT "), RPC_C_AUTHN_WINNT),
    ENTRY(TEXT("Default    "), RPC_C_AUTHN_DEFAULT),
    };

// ---------------------------------------------------------------------------
// %%Function: ErrorMessage
// ---------------------------------------------------------------------------
 void
ErrorMessage(HWND hwnd, LPTSTR szFunction, HRESULT hr)
{
    LPTSTR   szMessage;

    if (HRESULT_FACILITY(hr) == FACILITY_WINDOWS)
        hr = HRESULT_CODE(hr);

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL,
        hr,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //The user default language
        (LPTSTR)&szMessage,
        0,
        NULL );

    MessageBox(hwnd, szMessage, szFunction, MB_OK);

    LocalFree(szMessage);
}  // ErrorMessage

// ---------------------------------------------------------------------------
// %%Function: SelectEntry
// ---------------------------------------------------------------------------
 void
SelectEntry(HWND hwnd, ENTRY* rgEntries, int cEntries, DWORD dwVal)
{
    for (int i=0; i<cEntries; i++)
        {
        if (rgEntries[i].dwVal == dwVal)
            {
            SendMessage(hwnd, CB_SETCURSEL, i, 0);
            return;
            }
        }
}  // SelectEntry

// ---------------------------------------------------------------------------
// %%Function: GetField
// ---------------------------------------------------------------------------
 DWORD
GetField(HWND hwndDlg, UINT idItem)
{
    HWND    hwnd;
    int     iCur;

    hwnd = GetDlgItem(hwndDlg, idItem);
    iCur = SendMessage(hwnd, CB_GETCURSEL, 0, 0);
    return SendMessage(hwnd, CB_GETITEMDATA, iCur, 0);
}  // GetField

// ---------------------------------------------------------------------------
// %%Function: UpdateSecurityFields
// ---------------------------------------------------------------------------
 void
UpdateSecurityFields(HWND hwndDlg, LPUNKNOWN punk, BOOL fSet)
{
    HWND    hwnd;
    DWORD   dwAuthn, dwAuthz, dwAuthnLevel, dwImpLevel;
    LPOLESTR wszPrinciple;
    HRESULT hr;

    // in response to a CBN_SELCHANGE, we try to set the new proxy
    // security parameters first
    if (fSet)
        {
        hr = CoSetProxyBlanket(punk,
            GetField(hwndDlg, IDC_PROXY_AUTHN),
            GetField(hwndDlg, IDC_PROXY_AUTHZ),
            NULL,
            GetField(hwndDlg, IDC_PROXY_AUTHNLEVEL),
            GetField(hwndDlg, IDC_PROXY_IMPLEVEL),
            NULL,
            NULL);
        if (FAILED(hr))
            {
            ErrorMessage(hwndDlg, TEXT("CoSetProxyBlanket"), hr);
            }
        }

    // in all cases, update the fields to reflect the actual state of
    // security on the proxy
    hr = CoQueryProxyBlanket(punk,
        &dwAuthn, &dwAuthz, &wszPrinciple, &dwAuthnLevel, &dwImpLevel,
        NULL, NULL);
    if (FAILED(hr))
        {
        ErrorMessage(hwndDlg, TEXT("CoQueryProxyBlanket"), hr);
        }
    else
        {
        hwnd = GetDlgItem(hwndDlg, IDC_PROXY_AUTHN);
        EnableWindow(hwnd, TRUE);
        SelectEntry(hwnd, rgAuthnEntries, cAuthnEntries, dwAuthn);

        hwnd = GetDlgItem(hwndDlg, IDC_PROXY_AUTHZ);
        EnableWindow(hwnd, TRUE);
        SelectEntry(hwnd, rgAuthzEntries, cAuthzEntries, dwAuthz);

        hwnd = GetDlgItem(hwndDlg, IDC_PROXY_AUTHNLEVEL);
        EnableWindow(hwnd, TRUE);
        SelectEntry(hwnd, rgAuthnLevelEntries, cAuthnLevelEntries, dwAuthnLevel);

        hwnd = GetDlgItem(hwndDlg, IDC_PROXY_IMPLEVEL);
        EnableWindow(hwnd, TRUE);
        SelectEntry(hwnd, rgImpLevelEntries, cImpLevelEntries, dwImpLevel);

        SetWindowTextW(GetDlgItem(hwndDlg, IDC_PROXY_PRINCIPLENAME),
            wszPrinciple);
        }
}  // UpdateSecurityFields

// ---------------------------------------------------------------------------
// %%Function: UpdatePropertyFields
// ---------------------------------------------------------------------------
 void
UpdatePropertyFields(HWND hwndDlg, LPMYPROPERTIES pmp)
{
    COLORREF    cr;
    WCHAR       rgchUserName[20];
    HRESULT     hr;

    hr = pmp->GetColor(&cr);
    if (FAILED(hr))
        {
        ErrorMessage(hwndDlg, TEXT("Get Color"), hr);
        }
    else
        {
        s_crCurrent = cr;
        if (s_hbrCurrent != NULL)
            DeleteObject(s_hbrCurrent);
        s_hbrCurrent = CreateSolidBrush(s_crCurrent);
        InvalidateRect(GetDlgItem(hwndDlg, IDC_COLOR), NULL, TRUE);
        }

    hr = pmp->GetUserName(rgchUserName);
    if (FAILED(hr))
        {
        ErrorMessage(hwndDlg, TEXT("Get User Name"), hr);
        }
    else
        {
        SetWindowTextW(GetDlgItem(hwndDlg, IDC_USERNAME), rgchUserName);
        }
}  // UpdatePropertyFields

// ---------------------------------------------------------------------------
// %%Function: ClientDialogProc
// ---------------------------------------------------------------------------
 BOOL CALLBACK
ClientDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    static BOOL s_fInitialized = FALSE;
    static LPMYPROPERTIES s_pmp = NULL;
    static COLORREF s_rgcrCustom[16];
    HRESULT     hr;

    switch (message)
        {
        case WM_INITDIALOG:
            {
            HWND    hwnd1, hwnd2;
            int     i, cxScreen, cyScreen;
            RECT    rcDlg;

            cxScreen = GetSystemMetrics(SM_CXFULLSCREEN);
            cyScreen = GetSystemMetrics(SM_CYFULLSCREEN);

            GetWindowRect(hwndDlg, &rcDlg);
            OffsetRect(&rcDlg,
                (cxScreen - rcDlg.right - rcDlg.left)/2,
                (cyScreen - rcDlg.bottom - rcDlg.top)/2);

            MoveWindow(hwndDlg, rcDlg.left, rcDlg.top,
                rcDlg.right-rcDlg.left, rcDlg.bottom-rcDlg.top, TRUE);

            // build the list of servers, select the Remote Server by default
            hwnd1 = GetDlgItem(hwndDlg, IDC_SERVER_CLASS);
            for (i=0; i<cServerEntries; i++)
                {
                SendMessage(hwnd1, CB_INSERTSTRING, i, (LPARAM)rgServerEntries[i].szName);
                SendMessage(hwnd1, CB_SETITEMDATA, i, rgServerEntries[i].dwVal);
                }
            SendMessage(hwnd1, CB_SETCURSEL, 0, 0);

            // build the lists of authentication levels. choose None for the
            // process default. the proxy setting will be retrieved via CoQueryProxyBlanket
            // once the object is instantiated
            hwnd1 = GetDlgItem(hwndDlg, IDC_CLIENT_AUTHNLEVEL);
            hwnd2 = GetDlgItem(hwndDlg, IDC_PROXY_AUTHNLEVEL);
            for (i=0; i<cAuthnLevelEntries; i++)
                {
                SendMessage(hwnd1, CB_INSERTSTRING, i, (LPARAM)rgAuthnLevelEntries[i].szName);
                SendMessage(hwnd1, CB_SETITEMDATA, i, rgAuthnLevelEntries[i].dwVal);
                SendMessage(hwnd2, CB_INSERTSTRING, i, (LPARAM)rgAuthnLevelEntries[i].szName);
                SendMessage(hwnd2, CB_SETITEMDATA, i, rgAuthnLevelEntries[i].dwVal);
                }
            SelectEntry(hwnd1, rgAuthnLevelEntries, cAuthnLevelEntries, RPC_C_AUTHN_LEVEL_NONE);

            // build the lists of impersonation levels. choose Anonymous for the
            // process default. the proxy setting will be retrieved via CoQueryProxyBlanket
            // once the object is instantiated
            hwnd1 = GetDlgItem(hwndDlg, IDC_CLIENT_IMPLEVEL);
            hwnd2 = GetDlgItem(hwndDlg, IDC_PROXY_IMPLEVEL);
            for (i=0; i<cImpLevelEntries; i++)
                {
                SendMessage(hwnd1, CB_INSERTSTRING, i, (LPARAM)rgImpLevelEntries[i].szName);
                SendMessage(hwnd1, CB_SETITEMDATA, i, rgImpLevelEntries[i].dwVal);
                SendMessage(hwnd2, CB_INSERTSTRING, i, (LPARAM)rgImpLevelEntries[i].szName);
                SendMessage(hwnd2, CB_SETITEMDATA, i, rgImpLevelEntries[i].dwVal);
                }
            SelectEntry(hwnd1, rgImpLevelEntries, cImpLevelEntries, RPC_C_IMP_LEVEL_ANONYMOUS);

            // build the list of authorization services. the actual setting will
            // be retrieved via CoQueryProxyBlanket once the object is instantiated,
            // and can be modified after that.
            hwnd1 = GetDlgItem(hwndDlg, IDC_PROXY_AUTHZ);
            for (i=0; i<cAuthzEntries; i++)
                {
                SendMessage(hwnd1, CB_INSERTSTRING, i, (LPARAM)rgAuthzEntries[i].szName);
                SendMessage(hwnd1, CB_SETITEMDATA, i, rgAuthzEntries[i].dwVal);
                }

            // build the list of authentication services. the actual setting will
            // be retrieved via CoQueryProxyBlanket once the object is instantiated,
            // and can be modified after that.
            hwnd1 = GetDlgItem(hwndDlg, IDC_PROXY_AUTHN);
            for (i=0; i<cAuthnEntries; i++)
                {
                SendMessage(hwnd1, CB_INSERTSTRING, i, (LPARAM)rgAuthnEntries[i].szName);
                SendMessage(hwnd1, CB_SETITEMDATA, i, rgAuthnEntries[i].dwVal);
                }
            return 1;
            }

        case WM_SYSCOMMAND:
            if (wParam == SC_CLOSE)
                goto LClose;
            break;

        case WM_COMMAND:
            if (HIWORD(wParam) == CBN_SELCHANGE && s_pmp != NULL)
                {
                UpdateSecurityFields(hwndDlg, s_pmp, TRUE);
                }
            switch (LOWORD(wParam))
                {
                case IDCANCEL:
LClose:
                    if (s_pmp != NULL)
                        {
                        s_pmp->Release();
                        s_pmp = NULL;
                        }
                    if (s_fInitialized == TRUE)
                        {
                        CoUninitialize();
                        s_fInitialized = FALSE;
                        }
                    if (s_hbrCurrent != NULL)
                        {
                        DeleteObject(s_hbrCurrent);
                        s_hbrCurrent = NULL;
                        }
                    EndDialog(hwndDlg, 0);
                    break;

                case IDC_INITIALIZE:
                    hr = CoInitialize(NULL);
                    if (FAILED(hr))
                        {
                        ErrorMessage(hwndDlg, TEXT("CoInitialize"), hr);
                        }
                    else
                        {
                        hr = CoInitializeSecurity(NULL,
                            -1,
                            NULL,
                            NULL,
                            GetField(hwndDlg, IDC_CLIENT_AUTHNLEVEL),
                            GetField(hwndDlg, IDC_CLIENT_IMPLEVEL),
                            NULL,
                            0,
                            NULL);
                        if (FAILED(hr))
                            {
                            ErrorMessage(hwndDlg, TEXT("CoInitializeSecurity"), hr);
                            }
                        else
                            {
                            // Disable Initialization Step (#1)
                            EnableWindow(GetDlgItem(hwndDlg, IDC_INITIALIZE), FALSE);
                            EnableWindow(GetDlgItem(hwndDlg, IDC_CLIENT_AUTHNLEVEL), FALSE);
                            EnableWindow(GetDlgItem(hwndDlg, IDC_CLIENT_IMPLEVEL), FALSE);

                            // Enable Object Creation Step (#2)
                            EnableWindow(GetDlgItem(hwndDlg, IDC_SERVER_CLASS), TRUE);
                            EnableWindow(GetDlgItem(hwndDlg, IDC_MACHINENAME), TRUE);
                            EnableWindow(GetDlgItem(hwndDlg, IDC_CREATE), TRUE);
                            s_fInitialized = TRUE;
                            }
                        }
                    break;

                case IDC_CREATE:
                    {
                    OLECHAR rgchName[MAX_PATH];
                    LPCLASSFACTORY  pcf;
                    COSERVERINFO    csi, *pcsi;
                    CLSID   *pclsid;
                    HWND    hwnd;
                    int     iCur;

                    // retrieve the class to instantiate
                    hwnd = GetDlgItem(hwndDlg, IDC_SERVER_CLASS);
                    iCur = SendMessage(hwnd, CB_GETCURSEL, 0, 0);
                    pclsid = (LPCLSID)SendMessage(hwnd, CB_GETITEMDATA, iCur, 0);

                    // retrieve the machine name to run the server on. if this
                    // is blank or ".", instantiate locally by passing a NULL ComServerInfo
                    memset(&csi, 0, sizeof(COSERVERINFO));
                    csi.pwszName = rgchName;
                    GetWindowTextW(GetDlgItem(hwndDlg, IDC_MACHINENAME),
                        rgchName, RGSIZEOF(rgchName));
                    if (lstrlenW(rgchName) == 0 || lstrcmpiW(rgchName, L".") == 0)
                        pcsi = NULL;
                    else
                        pcsi = &csi;

                    // create the object
                    hr = CoGetClassObject(*pclsid, CLSCTX_SERVER,
                        pcsi, IID_IClassFactory, (void**)&pcf);
                    if (FAILED(hr))
                        {
                        ErrorMessage(hwndDlg, TEXT("CoGetClassObject"), hr);
                        break;
                        }
                    hr = pcf->CreateInstance(NULL, IID_IMyProperties, (void**)&s_pmp);
                    pcf->Release();
                    if (FAILED(hr))
                        {
                        ErrorMessage(hwndDlg, TEXT("IClassFactory::CreateInstance"), hr);
                        break;
                        }

                    // Allow a different instance to get created only if the Release
                    // button is pushed first
                    EnableWindow(GetDlgItem(hwndDlg, IDC_CREATE), FALSE);
                    EnableWindow(GetDlgItem(hwndDlg, IDC_SERVER_CLASS), FALSE);
                    EnableWindow(GetDlgItem(hwndDlg, IDC_MACHINENAME), FALSE);
                    EnableWindow(GetDlgItem(hwndDlg, IDC_RELEASE), TRUE);

                    // Enable manipulation of the object's properties. Allow
                    // this even if the values of the object's call-security
                    // parameters can not be determined, due to errors in
                    // UpdateSecurityFields
                    EnableWindow(GetDlgItem(hwndDlg, IDC_CHOOSECOLOR), TRUE);
                    EnableWindow(GetDlgItem(hwndDlg, IDC_USERNAME), TRUE);

                    UpdateSecurityFields(hwndDlg, s_pmp, FALSE);
                    UpdatePropertyFields(hwndDlg, s_pmp);
                    break;
                    }

                case IDC_RELEASE:
                    s_pmp->Release();
                    s_pmp = NULL;

                    // Disable manipulating the properties and selecting the call
                    // security parameters
                    EnableWindow(GetDlgItem(hwndDlg, IDC_CHOOSECOLOR), FALSE);
                    EnableWindow(GetDlgItem(hwndDlg, IDC_USERNAME), FALSE);
                    EnableWindow(GetDlgItem(hwndDlg, IDC_PROXY_AUTHN), FALSE);
                    EnableWindow(GetDlgItem(hwndDlg, IDC_PROXY_AUTHN), FALSE);
                    EnableWindow(GetDlgItem(hwndDlg, IDC_PROXY_AUTHNLEVEL), FALSE);
                    EnableWindow(GetDlgItem(hwndDlg, IDC_PROXY_IMPLEVEL), FALSE);

                    // Enable a new object to be created
                    EnableWindow(GetDlgItem(hwndDlg, IDC_RELEASE), FALSE);
                    EnableWindow(GetDlgItem(hwndDlg, IDC_CREATE), TRUE);
                    EnableWindow(GetDlgItem(hwndDlg, IDC_SERVER_CLASS), TRUE);
                    EnableWindow(GetDlgItem(hwndDlg, IDC_MACHINENAME), TRUE);
                    break;

                case IDC_CHOOSECOLOR:
                    {
                    CHOOSECOLOR cc;

                    cc.lStructSize = sizeof(CHOOSECOLOR);
                    cc.hwndOwner = hwndDlg;
                    cc.rgbResult = s_crCurrent;
                    cc.lpCustColors = s_rgcrCustom;
                    cc.Flags = CC_RGBINIT | CC_SHOWHELP;

                    if (ChooseColor(&cc))
                        {
                        hr = s_pmp->PutColor(cc.rgbResult);
                        if (FAILED(hr))
                            {
                            ErrorMessage(hwndDlg, TEXT("Put Color"), hr);
                            }
                        else
                            {
                            UpdatePropertyFields(hwndDlg, s_pmp);
                            }
                        }
                    break;
                    }

                case IDC_USERNAME:
                    {
                    static s_fDirty = FALSE;

                    if (HIWORD(wParam) == EN_CHANGE)
                        {
                        s_fDirty = TRUE;
                        }
                    else if (HIWORD(wParam) == EN_KILLFOCUS && s_fDirty)
                        {
                        WCHAR   rgchUserName[20];

                        GetWindowTextW(GetDlgItem(hwndDlg, IDC_USERNAME), rgchUserName, 20);
                        hr = s_pmp->PutUserName(rgchUserName);
                        if (FAILED(hr))
                            ErrorMessage(hwndDlg, TEXT("Put User Name"), hr);
                        UpdatePropertyFields(hwndDlg, s_pmp);
                        s_fDirty = FALSE;
                        }
                    break;
                    }
                }
            break;

        case WM_DRAWITEM:
            {
            LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT)lParam;

            FillRect(lpdis->hDC, &lpdis->rcItem,
                s_hbrCurrent != NULL ? s_hbrCurrent : (HBRUSH)GetStockObject(WHITE_BRUSH));
            FrameRect(lpdis->hDC, &lpdis->rcItem, (HBRUSH)GetStockObject(BLACK_BRUSH));
            if (lpdis->itemState & ODS_SELECTED)
                InvertRect(lpdis->hDC, (LPRECT)&lpdis->rcItem);
            if (lpdis->itemState & ODS_FOCUS)
                DrawFocusRect(lpdis->hDC, (LPRECT) &lpdis->rcItem);
            return TRUE;
            }
        }
    return FALSE;
}  // ClientDialogProc

// ---------------------------------------------------------------------------
// %%Function: WinMain
// ---------------------------------------------------------------------------
 int WINAPI
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
{
    return DialogBox(hInstance, MAKEINTRESOURCE(IDD_SECCLNT), GetDesktopWindow(), (DLGPROC)ClientDialogProc);
}  // WinMain

// EOF =======================================================================


SECCLNT.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by SECCLNT.RC
//
#define IDD_SECCLNT                     101
#define IDC_MACHINENAME                 1000
#define IDC_CREATE                      1001
#define IDC_RELEASE                     1002
#define IDC_PROXY_AUTHNLEVEL            1003
#define IDC_PROXY_IMPLEVEL              1004
#define IDC_INITIALIZE                  1005
#define IDC_CLIENT_AUTHNLEVEL           1007
#define IDC_CLIENT_IMPLEVEL             1008
#define IDC_SERVER_CLASS                1009
#define IDC_PROXY_PRINCIPLENAME         1010
#define IDC_CHOOSECOLOR                 1011
#define IDC_USERNAME                    1013
#define IDC_PROXY_AUTHN                 1014
#define IDC_PROXY_AUTHZ                 1015
#define IDC_COMBO1                      1016
#define IDC_COLOR                       1017

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC                     1
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1018
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


SECSVR.CPP

// ===========================================================================
// File: S E C S V R . C P P
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Description:
//
//  This is the server-portion of the SECURE Network OLE sample. This
// application implements several different server capabilities:
//   * Free-threaded server implemented as a Service. The application
//     implements a "LocalService" (CLSID_SecureObjectService) class.
//   * Free-threaded server implemented as a standard executable. The
//     application implements a "LocalServer32" (CLSID_SecureObject) class
//     and runs without user-interface.
//   * Apartment-threaded server implemented as a standard executable. The
//     application implements a "LocalServer32" (CLSID_SecureObject) class
//     and runs with a dialog-box user-interface showing the status of the
//     objects that it has handed out to clients.
//
//  The purpose of this sample is to demonstrate the different packaging
// options available to server writers, and to demonstrate call-security
// capabilities.
//
// Instructions:
//
//  To use this sample:
//   * build the samples using the NMAKE command. NMAKE will create SECCLNT.EXE,
//     SECSVR.EXE, and PSMYPROP.DLL. PSMYPROP.DLL is the remoting proxies and
//     stubs for the custom interface used between the client and the server.
//   * install the server on the current machine or on a remote machine
//     by running INSTALL.BAT in the same directory with SECSVR.EXE and
//     PSMYPROP.DLL. This program registers the proxy-stub DLL using the
//     REGSVR32 tool (included in the \mstools\bin directory of the SDK) and
//     runs the SECSVR.EXE program with "-AutoRegister" on the command-line,
//     which forces it to install itself.
//   * to run the apartment-threaded server, use "SECSVR -Interactive" to launch
//     the server from the command-line. the application will present UI allowing
//     you to choose how to initialize security for the server process, and
//     to view objects currently being used by clients.
//   * In Section 1, choose the security parameters to CoInitializeSecurity from
//     the two drop-down lists, and press the Initialize button.
//   * next, run SECCLNT.EXE from the same machine or from a remote machine. Follow
//     the instructions listed in SECCLNT.CPP to connect to this instance of the
//     SECSVR.EXE application.
//   * once a client has created an instance of the CLSID_SecureObject class, the
//     object will be listed in the drop-down list in Section 2 of the SECSVR.EXE
//     UI. Select the object to be viewed from this list. The current color and
//     "name" established by the client will be displayed in Section 2.
//   * use the "Lock" button to prevent the client from setting the color or "UserName"
//     of the object. the client will display an "access-denied" message if it
//     fails to set the color or "UserName". in the future this sample will include
//     the ability to establish a security-descriptor on the color and Name, and
//     will demonstrate impersonating the client and performing an AccessCheck()
//     during the PutColor and PutUserName methods.
//   * Push the "Exit" button in Section 1 to shut the server down.
//
// Copyright 1996 Microsoft Corporation. All Rights Reserved.
// ===========================================================================

// %%Includes: ---------------------------------------------------------------
#define INC_OLE2
#define STRICT
#define UNICODE
#include <windows.h>
#include <initguid.h>
#include "myprop.h"
#include "secsvr.h"

// %%Macros: -----------------------------------------------------------------
#define ENTRY(sz, val)      { sz, val }
#define RGSIZEOF(rg)        (sizeof(rg)/sizeof((rg)[0]))

// %%Constants: --------------------------------------------------------------
const TCHAR szServiceName[] = TEXT("SecSvr");
const TCHAR szDescription[] = TEXT("Network OLE Security Sample");
#define cAuthnLevelEntries  RGSIZEOF(rgAuthnLevelEntries)
#define cImpLevelEntries    RGSIZEOF(rgImpLevelEntries)

// %%Types: ------------------------------------------------------------------
typedef struct tagENTRY{
    TCHAR   *szName;
    DWORD   dwVal;
} ENTRY, *PENTRY;

// %%Classes: ----------------------------------------------------------------
class CClassFactory : public IClassFactory
    {
  // IClassFactory
    STDMETHODIMP            QueryInterface(REFIID iid, void **ppv);
    STDMETHODIMP_(ULONG)    AddRef(void)    { return 1; }
    STDMETHODIMP_(ULONG)    Release(void)   { return 1; }
    STDMETHODIMP            CreateInstance(LPUNKNOWN punkOuter, REFIID iid, LPVOID FAR *ppv);
    STDMETHODIMP            LockServer(BOOL fLock);
    };

class CSecureObject : public IMyProperties
    {
  // IUnknown
    STDMETHODIMP    QueryInterface(REFIID iid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef(void)    { return InterlockedIncrement(&m_cRef); }
    STDMETHODIMP_(ULONG) Release(void)   { if (InterlockedDecrement(&m_cRef) == 0) { delete this; return 0; } return 1; }

  // IMyProperties
    STDMETHODIMP    GetColor(COLORREF* pcr);
    STDMETHODIMP    PutColor(COLORREF cr);
    STDMETHODIMP    GetUserName(WCHAR szUserName[20]);
    STDMETHODIMP    PutUserName(WCHAR szUserName[20]);

  // data members
    LONG        m_cRef;
  public:
    COLORREF    m_crColor;
    BOOL        m_fDenyPutColor;
    WCHAR       m_rgchUserName[20];
    BOOL        m_fDenyPutUserName;

  // constructors/destructors
    CSecureObject();
    ~CSecureObject();
    };

// %%Prototypes: -------------------------------------------------------------
void    MyServiceMain(DWORD cArg, LPTSTR *rgszArg);
void    MyServiceHandler(DWORD dwControl);
void    ErrorMessage(HWND hwnd, LPTSTR szFunction, HRESULT hr);

// %%Globals: ----------------------------------------------------------------
CClassFactory   s_ClassFactory;
struct {
    SERVICE_STATUS          Status;
    SERVICE_STATUS_HANDLE   hStatus;
    DWORD                   dwRegister;
    } s_Service;
SERVICE_TABLE_ENTRY     s_rgsteServices[2] = { { (TCHAR*)szServiceName, MyServiceMain },
      { NULL, NULL } };
HWND            s_hwndDlg;
UINT            s_cInstances;
HANDLE          s_hevtDone;
CRITICAL_SECTION s_csDone;
BOOL            s_fDone, s_fServer, s_fService;
ENTRY   rgAuthnLevelEntries[] = {
    ENTRY(TEXT("Default    "), RPC_C_AUTHN_DEFAULT),
    ENTRY(TEXT("None       "), RPC_C_AUTHN_LEVEL_NONE),
    ENTRY(TEXT("Connect    "), RPC_C_AUTHN_LEVEL_CONNECT),
    ENTRY(TEXT("Call       "), RPC_C_AUTHN_LEVEL_CALL),
    ENTRY(TEXT("Packet     "), RPC_C_AUTHN_LEVEL_PKT),
    ENTRY(TEXT("Integrity  "), RPC_C_AUTHN_LEVEL_PKT_INTEGRITY),
    ENTRY(TEXT("Privacy    "), RPC_C_AUTHN_LEVEL_PKT_PRIVACY),
    };

ENTRY   rgImpLevelEntries[] = {
    ENTRY(TEXT("Anonymous  "), RPC_C_IMP_LEVEL_ANONYMOUS),
    ENTRY(TEXT("Identify   "), RPC_C_IMP_LEVEL_IDENTIFY),
    ENTRY(TEXT("Impersonate"), RPC_C_IMP_LEVEL_IMPERSONATE),
    ENTRY(TEXT("Delegate   "), RPC_C_IMP_LEVEL_DELEGATE),
    };

// ===========================================================================
//                          C C L A S S F A C T O R Y
// ===========================================================================

// ---------------------------------------------------------------------------
// %%Function: CClassFactory::QueryInterface
// ---------------------------------------------------------------------------
 STDMETHODIMP
CClassFactory::QueryInterface(REFIID iid, void **ppv)
{
    if (iid == IID_IClassFactory || iid == IID_IUnknown)
{
*ppv = (IClassFactory *)this;
AddRef();
return S_OK;
}
    *ppv = NULL;
    return E_NOINTERFACE;
}  // CClassFactory::QueryInterface

// ---------------------------------------------------------------------------
// %%Function: CClassFactory::CreateInstance
// ---------------------------------------------------------------------------
 STDMETHODIMP
CClassFactory::CreateInstance(LPUNKNOWN punkOuter, REFIID riid, void **ppv)
{
    LPUNKNOWN   punk;
    HRESULT     hr;

    *ppv = NULL;

    if (punkOuter != NULL)
return CLASS_E_NOAGGREGATION;

    if (s_fServer)
{
EnterCriticalSection(&s_csDone);
if (s_fDone == TRUE)
    {
    LeaveCriticalSection(&s_csDone);
    return E_FAIL;
    }
LeaveCriticalSection(&s_csDone);
}

    punk = new CSecureObject;
    if (punk == NULL)
return E_OUTOFMEMORY;

    hr = punk->QueryInterface(riid, ppv);
    punk->Release();
    return hr;
}  // CClassFactory::CreateInstance

// ---------------------------------------------------------------------------
// %%Function: CClassFactory::LockServer
// ---------------------------------------------------------------------------
 STDMETHODIMP
CClassFactory::LockServer(BOOL fLock)
{
    // LockServer not supported by the GUI server -- shutdown is
    // completely controlled by the UI's Exit command
    if (!s_fServer)
return E_FAIL;

    // LockServer acts like having an artificial object outstanding:
    // this code mimics the lifetime management found in the constructor
    // and destructor of CSecureObject
    EnterCriticalSection(&s_csDone);
    if (fLock)
s_cInstances++;
    else
{
if (!--s_cInstances && !s_fService) /* could perform other shutdown management here */
    {
    s_fDone = TRUE;
    SetEvent(s_hevtDone);
    }
}
    LeaveCriticalSection(&s_csDone);
    return S_OK;
}  // CClassFactory::LockServer

// ===========================================================================
//                        C S e c u r e O b j e c t
// ===========================================================================

// ---------------------------------------------------------------------------
// %%Function: CSecureObject::CSecureObject
// ---------------------------------------------------------------------------
CSecureObject::CSecureObject()
{
    static int s_ID = 0;

    // initialize the object's state
    m_cRef = 1;
    m_crColor = 0;
    m_fDenyPutColor = FALSE;
    lstrcpyW(m_rgchUserName, L"Default");
    m_fDenyPutUserName = FALSE;

    if (!s_fServer)
{
TCHAR   rgchName[20];
HWND    hwnd;
int     iPos;

// insert the object into the server UI. put our this-pointer into the item
// data field of the entry so that we can remove the entry in our destructor
wsprintf(rgchName, TEXT("Object %d"), s_ID++);
hwnd = GetDlgItem(s_hwndDlg, IDC_OBJECTS);
iPos = SendMessage(hwnd, CB_ADDSTRING, 0, (LPARAM)rgchName);
SendMessage(hwnd, CB_SETITEMDATA, iPos, (LPARAM)this);
}
    else
{
EnterCriticalSection(&s_csDone);
s_cInstances++;
LeaveCriticalSection(&s_csDone);
}
}  // CSecureObject::CSecureObject

CSecureObject::~CSecureObject()
{
    if (!s_fServer)
{
HWND    hwnd;
int     i, iSel, iMac;

// remove the reference to us from the server UI. our this-pointer is in
// the item-data field of the entry -- find it.
hwnd = GetDlgItem(s_hwndDlg, IDC_OBJECTS);
iSel = SendMessage(hwnd, CB_GETCURSEL, 0, 0);
iMac = SendMessage(hwnd, CB_GETCOUNT, 0, 0);
for (i=0; i<iMac; i++)
    {
    if ((LRESULT)this == SendMessage(hwnd, CB_GETITEMDATA, i, 0))
{
SendMessage(hwnd, CB_DELETESTRING, i, 0);

// if the item being deleted is the current selection,
// change the selection
if (i == iSel)
    {
    SendMessage(hwnd, CB_SETCURSEL, 0, 0);
    PostMessage(s_hwndDlg, WM_COMMAND, MAKELONG(IDC_OBJECTS, CBN_SELCHANGE),
(LPARAM)hwnd);
    }
break;
}
    }
}
    else
{
EnterCriticalSection(&s_csDone);
if (!--s_cInstances && !s_fService) /* could perform other shutdown management here */
    {
    s_fDone = TRUE;
    SetEvent(s_hevtDone);
    }
LeaveCriticalSection(&s_csDone);
}
}  // CSecureObject::~CSecureObject

// ---------------------------------------------------------------------------
// %%Function: CSecureObject::QueryInterface
// ---------------------------------------------------------------------------
 STDMETHODIMP
CSecureObject::QueryInterface(REFIID iid, void **ppv)
{
    if (iid == IID_IMyProperties || iid == IID_IUnknown)
{
*ppv = (IMyProperties *)this;
AddRef();
return S_OK;
}
    *ppv = NULL;
    return E_NOINTERFACE;
}  // CSecureObject::QueryInterface

// ---------------------------------------------------------------------------
// %%Function: CSecureObject::GetColor
// ---------------------------------------------------------------------------
 STDMETHODIMP
CSecureObject::GetColor(COLORREF* pcr)
{
    *pcr = m_crColor;
    return S_OK;
}  // CSecureObject::GetColor

// ---------------------------------------------------------------------------
// %%Function: CSecureObject::PutColor
// ---------------------------------------------------------------------------
 STDMETHODIMP
CSecureObject::PutColor(COLORREF cr)
{
    // a naive security check under control of the local-server's UI
    if (m_fDenyPutColor == TRUE)
return E_ACCESSDENIED;

    m_crColor = cr;

    // update the UI to reflect a valid PutColor occurred
    if (!s_fServer)
PostMessage(s_hwndDlg, WM_COMMAND, MAKELONG(IDC_OBJECTS,CBN_SELCHANGE),
    (LPARAM)GetDlgItem(s_hwndDlg, IDC_OBJECTS));

    return S_OK;
}  // CSecureObject::PutColor

// ---------------------------------------------------------------------------
// %%Function: CSecureObject::GetUserName
// ---------------------------------------------------------------------------
 STDMETHODIMP
CSecureObject::GetUserName(WCHAR szUserName[20])
{
    lstrcpynW(szUserName, m_rgchUserName, 20);
    return S_OK;
}  // CSecureObject::GetUserName

// ---------------------------------------------------------------------------
// %%Function: CSecureObject::PutUserName
// ---------------------------------------------------------------------------
 STDMETHODIMP
CSecureObject::PutUserName(WCHAR szUserName[20])
{
    // a naive security check under control of the local-server's UI
    if (m_fDenyPutUserName == TRUE)
return E_ACCESSDENIED;

    lstrcpynW(m_rgchUserName, szUserName, 20);

    // update the UI to reflect a valid PutUserName occurred
    if (!s_fServer)
PostMessage(s_hwndDlg, WM_COMMAND, MAKELONG(IDC_OBJECTS,CBN_SELCHANGE),
    (LPARAM)GetDlgItem(s_hwndDlg, IDC_OBJECTS));

    return S_OK;
}  // CSecureObject::PutUserName

// ---------------------------------------------------------------------------
// %%Function: FAutoRegister
// ---------------------------------------------------------------------------
 BOOL
FAutoRegister(HINSTANCE hinst)
{
    static const TCHAR szCLSIDEntryServer[]     = TEXT("CLSID\\{28f64ee0-4656-11cf-8110-00aa00389b71}");
    static const TCHAR szLocalServer32[]        = TEXT("LocalServer32");
    static const TCHAR szAllowRemoteActivation[] = TEXT("AllowRemoteActivation");
    static const TCHAR szCLSIDEntryService[]    = TEXT("CLSID\\{28f64ee2-4656-11cf-8110-00aa00389b71}");
    static const TCHAR szLocalService[]         = TEXT("LocalService");
    static const TCHAR szServiceParameters[]    = TEXT("ServiceParameters");
    static const TCHAR szParameters[]           = TEXT("-Service");
    static const TCHAR szEmpty[]                = TEXT("");
    SC_HANDLE   hManager = NULL, hService = NULL;
    TCHAR       szPath[MAX_PATH], *szError = TEXT("Registry Manipulation");
    HKEY        hkeyT = NULL, hkey2;

    // install the CLSID_SecureObject key and get the FQP to this executable
    if ((RegSetValue(HKEY_CLASSES_ROOT, szCLSIDEntryServer, REG_SZ, szDescription,
    lstrlen(szDescription)) != ERROR_SUCCESS) ||
(RegCreateKey(HKEY_CLASSES_ROOT, szCLSIDEntryServer, &hkeyT) != ERROR_SUCCESS) ||
!GetModuleFileName(hinst, szPath, sizeof(szPath)/sizeof(TCHAR)))
goto LErrExit;

    // install the LocalServer32 key and the AllowRemoteActivation key
    if ((RegSetValue(hkeyT, szLocalServer32, REG_SZ, szPath, lstrlen(szPath))
    != ERROR_SUCCESS) ||
(RegSetValue(hkeyT, szAllowRemoteActivation, REG_SZ, szEmpty, lstrlen(szEmpty))
    != ERROR_SUCCESS))
goto LErrExit;
    RegCloseKey(hkeyT);
    hkeyT = NULL;

    // install the CLSID_SecureObjectService key
    if ((RegSetValue(HKEY_CLASSES_ROOT, szCLSIDEntryService, REG_SZ, szDescription,
    lstrlen(szDescription)) != ERROR_SUCCESS) ||
(RegCreateKey(HKEY_CLASSES_ROOT, szCLSIDEntryService, &hkeyT) != ERROR_SUCCESS))
goto LErrExit;

    // install the LocalService key and the ServiceParameters named-value
    if ((RegSetValue(hkeyT, szLocalService, REG_SZ, szServiceName, lstrlen(szServiceName))
    != ERROR_SUCCESS) ||
(RegOpenKey(hkeyT, szLocalService, &hkey2) != ERROR_SUCCESS))
goto LErrExit;
    RegCloseKey(hkeyT);
    hkeyT = hkey2;
    if (RegSetValueEx(hkeyT, szServiceParameters, 0, REG_SZ, (const BYTE*)szParameters,
    (lstrlen(szParameters)+1) * sizeof(TCHAR)) != ERROR_SUCCESS)
goto LErrExit;

    RegCloseKey(hkeyT);
    hkeyT = NULL;

    // install the application to run as a service
    hManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    if (hManager == NULL)
{
szError = TEXT("OpenSCManager");
goto LErrExit;
}

    hService = OpenService(hManager, szServiceName, SERVICE_ALL_ACCESS);
    if (hService != NULL)
{
if (!ChangeServiceConfig(hService,
    SERVICE_WIN32_OWN_PROCESS,
    SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, szPath,
    NULL, NULL, NULL, NULL, NULL, szDescription))
    {
    szError = TEXT("ChangeServiceConfig");
    goto LErrExit;
    }
return TRUE;
}

    hService = CreateService(hManager, szServiceName, szDescription,
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, szPath,
NULL, NULL, NULL,  NULL, NULL);
    if (hService == NULL)
{
szError = TEXT("CreateService");
goto LErrExit;
}

    CloseServiceHandle(hService);
    CloseServiceHandle(hManager);
    return TRUE;

LErrExit:
    ErrorMessage(GetDesktopWindow(), szError, GetLastError());
    if (hkeyT != NULL)
RegCloseKey(hkeyT);
    if (hService != NULL)
CloseServiceHandle(hService);
    if (hManager != NULL)
CloseServiceHandle(hManager);
    return FALSE;
}  // FAutoRegister

// ---------------------------------------------------------------------------
// %%Function: ErrorMessage
// ---------------------------------------------------------------------------
 void
ErrorMessage(HWND hwnd, LPTSTR szFunction, HRESULT hr)
{
    LPTSTR   szMessage;

    if (HRESULT_FACILITY(hr) == FACILITY_WINDOWS)
hr = HRESULT_CODE(hr);

    if (!FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL, hr,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //The user default language
(LPTSTR)&szMessage, 0, NULL))
return;

    if (hwnd == NULL)
{
OutputDebugString(szFunction);
OutputDebugString(TEXT(": "));
OutputDebugString(szMessage);
OutputDebugString(TEXT("\n"));
}
    else
MessageBox(hwnd, szMessage, szFunction, MB_OK);

    LocalFree(szMessage);
}  // ErrorMessage

// ---------------------------------------------------------------------------
// %%Function: SelectEntry
// ---------------------------------------------------------------------------
 void
SelectEntry(HWND hwnd, ENTRY* rgEntries, int cEntries, DWORD dwVal)
{
    for (int i=0; i<cEntries; i++)
{
if (rgEntries[i].dwVal == dwVal)
    {
    SendMessage(hwnd, CB_SETCURSEL, i, 0);
    return;
    }
}
}  // SelectEntry

// ---------------------------------------------------------------------------
// %%Function: GetField
// ---------------------------------------------------------------------------
 DWORD
GetField(HWND hwndDlg, UINT idItem)
{
    HWND    hwnd;
    int     iCur;

    hwnd = GetDlgItem(hwndDlg, idItem);
    iCur = SendMessage(hwnd, CB_GETCURSEL, 0, 0);
    return SendMessage(hwnd, CB_GETITEMDATA, iCur, 0);
}  // GetField

// ---------------------------------------------------------------------------
// %%Function: ServerDialogProc
// ---------------------------------------------------------------------------
 BOOL CALLBACK
ServerDialogProc(HWND hwndDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    static DWORD    s_dwRegister = 0;
    static BOOL     s_fInitialized = FALSE;
    static CSecureObject* s_pcso = NULL;
    static COLORREF s_rgcrCustom[16];
    static HBRUSH   s_hbrCurrent = NULL;
    HRESULT hr;

    switch (message)
{
case WM_INITDIALOG:
    {
    HWND    hwnd;
    int     cxScreen, cyScreen, i;
    RECT    rcDlg;

    // save the hwndDlg into a global
    s_hwndDlg = hwndDlg;

    // Center the UI on screen
    cxScreen = GetSystemMetrics(SM_CXFULLSCREEN);
    cyScreen = GetSystemMetrics(SM_CYFULLSCREEN);
    GetWindowRect(hwndDlg, &rcDlg);
    OffsetRect(&rcDlg,
(cxScreen - rcDlg.right - rcDlg.left)/2,
(cyScreen - rcDlg.bottom - rcDlg.top)/2);
    MoveWindow(hwndDlg, rcDlg.left, rcDlg.top,
rcDlg.right-rcDlg.left, rcDlg.bottom-rcDlg.top, TRUE);

    // build the lists of authentication levels. choose None for the
    // process default.
    hwnd = GetDlgItem(hwndDlg, IDC_SERVER_AUTHNLEVEL);
    for (i=0; i<cAuthnLevelEntries; i++)
{
SendMessage(hwnd, CB_INSERTSTRING, i, (LPARAM)rgAuthnLevelEntries[i].szName);
SendMessage(hwnd, CB_SETITEMDATA, i, rgAuthnLevelEntries[i].dwVal);
}
    SelectEntry(hwnd, rgAuthnLevelEntries, cAuthnLevelEntries, RPC_C_AUTHN_LEVEL_NONE);

    // build the lists of impersonation levels. choose Anonymous for the
    // process default.
    hwnd = GetDlgItem(hwndDlg, IDC_SERVER_IMPLEVEL);
    for (i=0; i<cImpLevelEntries; i++)
{
SendMessage(hwnd, CB_INSERTSTRING, i, (LPARAM)rgImpLevelEntries[i].szName);
SendMessage(hwnd, CB_SETITEMDATA, i, rgImpLevelEntries[i].dwVal);
}
    SelectEntry(hwnd, rgImpLevelEntries, cImpLevelEntries, RPC_C_IMP_LEVEL_ANONYMOUS);
    return 1;
    }

case WM_SYSCOMMAND:
    if (wParam == SC_CLOSE)
goto LClose;
    break;

case WM_COMMAND:
    switch (LOWORD(wParam))
{
case IDCANCEL:
LClose:
    if (s_hbrCurrent != NULL)
{
DeleteObject(s_hbrCurrent);
s_hbrCurrent = NULL;
}
    if (s_dwRegister != 0)
{
CoRevokeClassObject(s_dwRegister);
s_dwRegister = 0;
}
    if (s_fInitialized == TRUE)
{
CoUninitialize();
s_fInitialized = FALSE;
}
    EndDialog(hwndDlg, 0);
    break;

case IDC_INITIALIZE:
    // initialize as apartment-threaded to synchronize the UI
    // with calls to the objects
    hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
    if (FAILED(hr))
{
ErrorMessage(hwndDlg, TEXT("CoInitialize"), hr);
break;
}

                    // initialize security layer with our choices
    hr = CoInitializeSecurity(NULL,
                        -1,
                        NULL,
                        NULL,
GetField(hwndDlg, IDC_SERVER_AUTHNLEVEL),
GetField(hwndDlg, IDC_SERVER_IMPLEVEL),
                        NULL,
                        0,
NULL);
    if (FAILED(hr))
{
ErrorMessage(hwndDlg, TEXT("CoInitializeSecurity"), hr);
break;
}

    hr = CoRegisterClassObject(CLSID_SecureObject, &s_ClassFactory,
CLSCTX_SERVER, REGCLS_MULTIPLEUSE, &s_dwRegister);
    if (FAILED(hr))
{
ErrorMessage(hwndDlg, TEXT("CoRegisterClassObject"), hr);
CoUninitialize();
break;
}

    // Disable Initialization Step (#1)
    EnableWindow(GetDlgItem(hwndDlg, IDC_INITIALIZE), FALSE);
    EnableWindow(GetDlgItem(hwndDlg, IDC_SERVER_AUTHNLEVEL), FALSE);
    EnableWindow(GetDlgItem(hwndDlg, IDC_SERVER_IMPLEVEL), FALSE);

    // Enable Object Viewing Steps (#2)
    // (choosing a valid object in the list box will enable the
    //  other controls)
    EnableWindow(GetDlgItem(hwndDlg, IDC_OBJECTS), TRUE);

    s_fInitialized = TRUE;
    break;

case IDC_OBJECTS:
    {
    int     iCur;

    if (HIWORD(wParam) != CBN_SELCHANGE)
break;

    // a different object was chosen, or a property has changed.
    // retrieve the object's pointer from the item-data of the
    // current list-box item and update the UI.
    iCur = SendMessage(GetDlgItem(hwndDlg, IDC_OBJECTS),
CB_GETCURSEL, 0, 0);
    if (iCur == CB_ERR)
s_pcso = NULL;
    else
{
s_pcso = (CSecureObject*)SendMessage(GetDlgItem(hwndDlg, IDC_OBJECTS),
    CB_GETITEMDATA, iCur, 0);
if (s_pcso == (LPVOID)CB_ERR)
    s_pcso = NULL;
}

    if (s_hbrCurrent != NULL)
{
DeleteObject(s_hbrCurrent);
s_hbrCurrent = NULL;
}
    SetWindowText(GetDlgItem(hwndDlg, IDC_USERNAME), TEXT(""));
    InvalidateRect(GetDlgItem(hwndDlg, IDC_CHOOSECOLOR), NULL, TRUE);
    InvalidateRect(GetDlgItem(hwndDlg, IDC_COLOR), NULL, TRUE);
    InvalidateRect(GetDlgItem(hwndDlg, IDC_USERNAME), NULL, TRUE);
    InvalidateRect(GetDlgItem(hwndDlg, IDC_NOPUTCOLOR), NULL, TRUE);
    InvalidateRect(GetDlgItem(hwndDlg, IDC_NOPUTUSERNAME), NULL, TRUE);

    if (s_pcso != NULL)
{
SetWindowTextW(GetDlgItem(hwndDlg, IDC_USERNAME), s_pcso->m_rgchUserName);
s_hbrCurrent = CreateSolidBrush(s_pcso->m_crColor);
}

    // enable/disable the object-specific windows
    EnableWindow(GetDlgItem(hwndDlg, IDC_CHOOSECOLOR), s_pcso != NULL);
    EnableWindow(GetDlgItem(hwndDlg, IDC_COLOR), s_pcso != NULL);
    EnableWindow(GetDlgItem(hwndDlg, IDC_USERNAME), s_pcso != NULL);
    EnableWindow(GetDlgItem(hwndDlg, IDC_NOPUTCOLOR), s_pcso != NULL);
    EnableWindow(GetDlgItem(hwndDlg, IDC_NOPUTUSERNAME), s_pcso != NULL);
    break;
    }

case IDC_NOPUTCOLOR:
    s_pcso->m_fDenyPutColor = SendMessage(
GetDlgItem(hwndDlg, IDC_NOPUTCOLOR), BM_GETCHECK, 0, 0);
    break;

case IDC_NOPUTUSERNAME:
    s_pcso->m_fDenyPutUserName = SendMessage(
GetDlgItem(hwndDlg, IDC_NOPUTUSERNAME), BM_GETCHECK, 0, 0);
    break;

case IDC_CHOOSECOLOR:
    {
    CHOOSECOLOR cc;

    cc.lStructSize = sizeof(CHOOSECOLOR);
    cc.hwndOwner = hwndDlg;
    cc.rgbResult = s_pcso->m_crColor;
    cc.lpCustColors = s_rgcrCustom;
    cc.Flags = CC_RGBINIT | CC_SHOWHELP;

    if (ChooseColor(&cc))
{
s_pcso->m_crColor = cc.rgbResult;
if (s_hbrCurrent != NULL)
    DeleteObject(s_hbrCurrent);
s_hbrCurrent = CreateSolidBrush(cc.rgbResult);
InvalidateRect((HWND)lParam, NULL, TRUE);
}
    break;
    }

case IDC_USERNAME:
    {
    static s_fDirty = FALSE;

    if (HIWORD(wParam) == EN_CHANGE)
{
s_fDirty = TRUE;
}
    else if (HIWORD(wParam) == EN_KILLFOCUS && s_fDirty)
{
GetWindowTextW(GetDlgItem(hwndDlg, IDC_USERNAME),
    s_pcso->m_rgchUserName, 20);
s_fDirty = FALSE;
}
    break;
    }
}
    break;

case WM_DRAWITEM:
    {
    LPDRAWITEMSTRUCT lpdis = (LPDRAWITEMSTRUCT)lParam;

    FillRect(lpdis->hDC, &lpdis->rcItem,
s_hbrCurrent != NULL ? s_hbrCurrent : (HBRUSH)GetStockObject(WHITE_BRUSH));
    FrameRect(lpdis->hDC, &lpdis->rcItem, (HBRUSH)GetStockObject(BLACK_BRUSH));
    if (lpdis->itemState & ODS_SELECTED)
InvertRect(lpdis->hDC, (LPRECT)&lpdis->rcItem);
    if (lpdis->itemState & ODS_FOCUS)
DrawFocusRect(lpdis->hDC, (LPRECT) &lpdis->rcItem);
    return TRUE;
    }
}
    return FALSE;
}  // ServerDialogProc

// ---------------------------------------------------------------------------
// %%Function: MyServiceMain
// ---------------------------------------------------------------------------
 void
MyServiceMain(DWORD dwArgc, LPTSTR *pszArgv)
{
    HRESULT hr;

    s_Service.Status.dwServiceType        = SERVICE_WIN32_OWN_PROCESS;
    s_Service.Status.dwCurrentState       = SERVICE_START_PENDING;
    s_Service.Status.dwControlsAccepted   = SERVICE_ACCEPT_STOP;
    s_Service.Status.dwWin32ExitCode      = 0;
    s_Service.Status.dwServiceSpecificExitCode = 0;
    s_Service.Status.dwCheckPoint         = 0;
    s_Service.Status.dwWaitHint           = 0;

    s_Service.hStatus = RegisterServiceCtrlHandler(szServiceName, MyServiceHandler);

    if (s_Service.hStatus == NULL)
return;

    // used to gaurd the s_fDone variable that prevents new instances
    // from being created in IClassFactory::CreateInstance during shutdown
    InitializeCriticalSection(&s_csDone);

    // event used to signal the service has stopped
    s_hevtDone = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (s_hevtDone == NULL)
{
ErrorMessage(NULL, TEXT("CreateEvent"), GetLastError());
goto LErrExit;
}

    hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    if (FAILED(hr))

{
ErrorMessage(NULL, TEXT("CoInitialize"), hr);
goto LErrExit;
}

    // the Service CLSID is used here, different from the LocalServer case
    hr = CoRegisterClassObject(CLSID_SecureObjectService, &s_ClassFactory,
CLSCTX_SERVER, REGCLS_MULTIPLEUSE, &s_Service.dwRegister);

    if (FAILED(hr))
{
ErrorMessage(NULL, TEXT("CoRegisterClassObject"), hr);
CoUninitialize();
goto LErrExit;
}

    // Initialization complete - report running status
    s_Service.Status.dwCurrentState       = SERVICE_RUNNING;
    s_Service.Status.dwCheckPoint         = 0;
    s_Service.Status.dwWaitHint           = 0;

    // notify the service-manager that the service is running
    if (!SetServiceStatus(s_Service.hStatus, &s_Service.Status))
ErrorMessage(NULL, TEXT("SetServiceStatus(SERVICE_RUNNING)"), GetLastError());

    // wait for the signal from MyServiceHandler:SERVICE_CONTROL_STOP
    WaitForSingleObject(s_hevtDone, INFINITE);

    CloseHandle(s_hevtDone);
    s_hevtDone = NULL;

    CoRevokeClassObject(s_Service.dwRegister);
    CoUninitialize();

    // notify the service-manager that the service has totally stopped
    s_Service.Status.dwCurrentState = SERVICE_STOPPED;
    if (!SetServiceStatus(s_Service.hStatus, &s_Service.Status))
ErrorMessage(NULL, TEXT("SetServiceStatus(SERVICE_STOPPED)"), GetLastError());
    return;

LErrExit:
    if (s_hevtDone != NULL)
{
CloseHandle(s_hevtDone);
s_hevtDone = NULL;
}
    s_Service.Status.dwCurrentState       = SERVICE_STOPPED;
    s_Service.Status.dwCheckPoint         = 0;
    s_Service.Status.dwWaitHint           = 0;
    s_Service.Status.dwWin32ExitCode      = hr;
    s_Service.Status.dwServiceSpecificExitCode = hr;
    SetServiceStatus(s_Service.hStatus, &s_Service.Status);
    return;
}  // MyServiceMain

// ---------------------------------------------------------------------------
// %%Function: MyServiceHandler
// ---------------------------------------------------------------------------
 void
MyServiceHandler(DWORD dwControl)
{
    switch (dwControl)
{
case SERVICE_CONTROL_STOP:
    s_Service.Status.dwWin32ExitCode = 0;
    s_Service.Status.dwCurrentState  = SERVICE_STOP_PENDING;
    s_Service.Status.dwCheckPoint    = 0;
    s_Service.Status.dwWaitHint      = 0;
    if (!SetServiceStatus (s_Service.hStatus, &s_Service.Status))
ErrorMessage(NULL, TEXT("SetServiceStatus"), GetLastError());

    EnterCriticalSection(&s_csDone);

    // prevent further instances from being created in IClassFactory::CreateInstance
    s_fDone = TRUE;

    // restart the waiting MyServiceMain thread and allow it to uninitialize and exit.
    SetEvent(s_hevtDone);

    LeaveCriticalSection(&s_csDone);
    return;

case SERVICE_CONTROL_INTERROGATE:
    // fall out and send the current status of s_Service.Status
    break;

default:
    break;
}

    // send current status.
    if (!SetServiceStatus(s_Service.hStatus,  &s_Service.Status))
ErrorMessage(NULL, TEXT("SetServiceStatus"), GetLastError());

    return;
}  // MyServiceHandler

// ---------------------------------------------------------------------------
// %%Function: WinMain
// ---------------------------------------------------------------------------
 int WINAPI
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLine, int nCmdShow)
{
    HRESULT hr;

    // DebugBreak();

    // parse the command-line
    if (szCmdLine)
{
char    *sz = strtok(szCmdLine, " \t");
if (sz)
    {
    if (!strcmpi(sz, "-Embedding"))
s_fServer = TRUE;

    // Auto-register, display success or failure, and exit
    else if (!strcmpi(sz, "-AutoRegister"))
{
if (FAutoRegister(hInstance))
    MessageBox(GetDesktopWindow(),
TEXT("Registered Successfully!"),
szDescription,
MB_OK);
return S_OK;
}

    // any other argument besides interactive causes help to be displayed
    else if (strcmpi(sz, "-Interactive"))
{
// MessageBox some help here
}
    }
else
    {
    s_fService = TRUE;
    s_fServer = TRUE;
    }
}

    // when launched without command-line args, run as a service, without any UI
    // and running multi-threaded
    if (s_fService)
return StartServiceCtrlDispatcher(s_rgsteServices) ? 0 : GetLastError();

    // when launched with -Embedding, come up
    // without any UI and run multi-threaded
    else if (s_fServer)
{
DWORD   dwRegister;

InitializeCriticalSection(&s_csDone);

s_hevtDone = CreateEvent(NULL, FALSE, FALSE, NULL);
if (s_hevtDone == NULL)
    return HRESULT_FROM_WIN32(GetLastError());

hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
if (FAILED(hr))
    return hr;

hr = CoRegisterClassObject(CLSID_SecureObject, &s_ClassFactory,
    CLSCTX_SERVER, REGCLS_MULTIPLEUSE, &dwRegister);
if (FAILED(hr))
    goto LServerExit;

// wait for all open connections to the server to go away
WaitForSingleObject(s_hevtDone, INFINITE);

// Destroy the event handle
CloseHandle(s_hevtDone);
s_hevtDone = NULL;

// destroy the critsec used to determine when we're done
DeleteCriticalSection(&s_csDone);

CoRevokeClassObject(dwRegister);

LServerExit:
CoUninitialize();
return hr;
}

    // when launched with -Interactive, run with UI.
    // this allows the user to adjust the exact security parameters of the
    // process and to monitor individual client objects. in this case, we will
    // eventually initialize COM (once the user selects security parameters
    // and hits the Initialize button) to use the apartment model in order
    // to synchronize calls with our UI.
    DialogBox(hInstance, MAKEINTRESOURCE(IDD_SECSVR), GetDesktopWindow(),
(DLGPROC)ServerDialogProc);

    return 0;
} // WinMain

// EOF =======================================================================


SECSVR.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by SECSVR.RC
//
#define IDD_SECSVR                      101
#define IDC_USERNAME                    1000
#define IDC_COLOR                       1001
#define IDC_CHOOSECOLOR                 1002
#define IDC_COLORTEXT                   1003
#define IDC_NOPUTCOLOR                  1004
#define IDC_INITIALIZE                  1005
#define IDC_NOPUTUSERNAME               1006
#define IDC_SERVER_AUTHNLEVEL           1007
#define IDC_SERVER_IMPLEVEL             1008
#define IDC_OBJECTS                     1009

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NO_MFC                     1
#define _APS_NEXT_RESOURCE_VALUE        102
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1005
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


README.TXT

Simple Network OLE Sample

DCOM is implemented only on Windows NT 4.0 and later versions.  This sample
will not work correctly on Windows 95 or on earlier versions of Windows NT.

The SIMPLE sample demonstrates the minimum requirements to use a COM object,
whether it is on the same machine or on a different machine. This sample
consists of a client and a server portion.

The server application, SSERVER.EXE, implements the CLSID_SimpleObject class as
a LocalServer. Instances of the class supports a limited form of the IStream
interface. Calls to Read and Write succeed, but they do nothing. Calls to other
methods fail with E_NOTIMPL.

The client application, SCLIENT.EXE, uses the CLSID_SimpleObject class
implemented by the server.

To compile
----------

Build the sample using the NMAKE command.

To run
------

The run the sample, use the following steps.

 - Edit the SSERVER.REG file to make the LocalServer32 key point to the location
   of SSERVER.EXE.
 - Install the server on the current machine or a remote machine by running
   INSTALL.BAT. This copies SSERVER.REG to the registry.
 - Run SSERVER. It should display the message "Waiting...".
 - Run SCLIENT. Use no command-line arguments to instantiate the object on the
   current machine. Use the name of a remote machine (UNC or DNS) as an argument
   to instantiate the object on the remote machine. SCLIENT displays simple
   information about the calls it is making on the object.


SCLIENT.CPP

// ===========================================================================
// File: S C L I E N T . C P P
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Description:
//
//  This is the client-portion of the SIMPLE Distributed COM sample. This
// application uses the CLSID_SimpleObject class implemented by the SSERVER.CPP
// module. Pass the machine-name to instantiate the object on, or pass no
// arguments to instantiate the object on the same machine. See the comments
// in SSERVER.CPP for further details.
//
//  The purpose of this sample is to demonstrate what is minimally required
// to use a COM object, whether it runs on the same machine or on a different
// machine.
//
// Instructions:
//
//  To use this sample:
//   * build it using the NMAKE command. NMAKE will create SSERVER.EXE and
//     SCLIENT.EXE.
//   * install the SSERVER.EXE on the current machine or on a remote machine
//     according to the installation instructions found in SSERVER.CPP.
//   * run SCLIENT.EXE. use no command-line arguments to instantiate the object
//     on the current machine. use a single command-line argument of the remote
//     machine-name (UNC or DNS) to instantiate the object on a remote machine.
//   * SCLIENT.EXE displays some simple information about the calls it is
//     making on the object.
//
// Copyright 1996 Microsoft Corporation.  All Rights Reserved.
// ===========================================================================

// %%Includes: ---------------------------------------------------------------
#define INC_OLE2
#include <stdio.h>
#include <windows.h>
#include <initguid.h>
#include <tchar.h>
#include <conio.h>

// %%GUIDs: ------------------------------------------------------------------
DEFINE_GUID(CLSID_SimpleObject, 0x5e9ddec7, 0x5767, 0x11cf, 0xbe, 0xab, 0x0, 0xaa, 0x0, 0x6c, 0x36, 0x6);

// %%Constants: --------------------------------------------------------------
const ULONG cbDefault = 4096;    // default # of bytes to Read/Write

// ---------------------------------------------------------------------------
// %%Function: Message
//
//  Formats and displays a message to the console.
// ---------------------------------------------------------------------------
 void
Message(LPTSTR szPrefix, HRESULT hr)
{
    LPTSTR   szMessage;

    if (hr == S_OK)
        {
        wprintf(szPrefix);
        return;
        }

    if (HRESULT_FACILITY(hr) == FACILITY_WINDOWS)
        hr = HRESULT_CODE(hr);

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL,
        hr,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //The user default language
        (LPTSTR)&szMessage,
        0,
        NULL);

    wprintf(TEXT("%s: %s(%lx)\n"), szPrefix, szMessage, hr);

    LocalFree(szMessage);
}  // Message

// ---------------------------------------------------------------------------
// %%Function: OutputTime
// ---------------------------------------------------------------------------
 void
OutputTime(LARGE_INTEGER* pliStart, LARGE_INTEGER* pliFinish)
{
    LARGE_INTEGER liFreq;

    QueryPerformanceFrequency(&liFreq);
    wprintf(TEXT("%0.4f seconds\n"),
        (float)(pliFinish->LowPart - pliStart->LowPart)/(float)liFreq.LowPart);
}  // OutputTime

// ---------------------------------------------------------------------------
// %%Function: main
// ---------------------------------------------------------------------------
 void __cdecl
main(int argc, CHAR **argv)
{
    HRESULT hr;
    MULTI_QI    mq;
    COSERVERINFO csi, *pcsi=NULL;
    WCHAR wsz [MAX_PATH];
    ULONG cb = cbDefault;
    LARGE_INTEGER liStart, liFinish;

    // allow a machine-name as the command-line argument
    if (argc > 1)
        {
        MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, argv[1], -1,
            wsz, MAX_PATH);
        memset(&csi, 0, sizeof(COSERVERINFO));
        csi.pwszName = wsz;
        pcsi = &csi;
        }

    // allow a byte-size to be passed on the command-line
    if (argc > 2)
        cb = atol(argv[2]);

    // initialize COM for free-threaded use
    hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    if (FAILED(hr))
        {
        Message(TEXT("Client: CoInitializeEx"), hr);
        exit(hr);
        }

    // create a remote instance of the object on the argv[1] machine
    Message(TEXT("Client: Creating Instance..."), S_OK);
    mq.pIID = &IID_IStream;
    mq.pItf = NULL;
    mq.hr = S_OK;
    QueryPerformanceCounter(&liStart);
    hr = CoCreateInstanceEx(CLSID_SimpleObject, NULL, CLSCTX_SERVER, pcsi, 1, &mq);
    QueryPerformanceCounter(&liFinish);
    OutputTime(&liStart, &liFinish);

    if (FAILED(hr))
        Message(TEXT("Client: CoCreateInstanceEx"), hr);
    else
        {
        LPSTREAM    pstm = (IStream*)mq.pItf;
        LPVOID      pv;

        // "read" some data from the object
        Message(TEXT("Client: Reading data..."), S_OK);
        pv = CoTaskMemAlloc(cb);
        QueryPerformanceCounter(&liStart);
        hr = pstm->Read(pv, cb, NULL);
        QueryPerformanceCounter(&liFinish);
        OutputTime(&liStart, &liFinish);
        if (FAILED(hr))
            Message(TEXT("Client: IStream::Read"), hr);

        // "write" some data to the object
        Message(TEXT("Client: Writing data..."), S_OK);
        QueryPerformanceCounter(&liStart);
        hr = pstm->Write(pv, cb, NULL);
        QueryPerformanceCounter(&liFinish);
        OutputTime(&liStart, &liFinish);
        if (FAILED(hr))
            Message(TEXT("Client: IStream::Write"), hr);

        // let go of the object
        pstm->Release();
        }

    CoUninitialize();
    Message(TEXT("Client: Done"), S_OK);
}  // main

// EOF =======================================================================


SSERVER.CPP

// ===========================================================================
// File: S S E R V E R . C P P
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Description:
//
//  This is the server-portion of the SIMPLE Network OLE sample. This
// application implements the CLSID_SimpleObject class as a LocalServer.
// Instances of this class support a limited form of the IStream interface --
// calls to IStream::Read and IStream::Write will "succeed" (they do nothing),
// and calls on any other methods fail with E_NOTIMPL.
//
//  The purpose of this sample is to demonstrate what is minimally required
// to implement an object that can be used by clients (both those on the same
// machine using OLE and those using Network OLE across the network).
//
// Instructions:
//
//  To use this sample:
//   * build it using the NMAKE command. NMAKE will create SSERVER.EXE and
//     SCLIENT.EXE.
//   * edit the SSERVER.REG file to make the LocalServer32 key point to the
//     location of SSERVER.EXE, and run the INSTALL.BAT command (it simply
//     performs REGEDIT SSERVER.REG)
//   * run SSERVER.EXE. it should display the message "Waiting..."
//   * run SCLIENT.EXE on the same machine using no command-line arguments,
//     or from another machine using the machine-name (UNC or DNS) as the sole
//     command-line argument. it will connect to the server, perform some read
//     and write calls, and disconnect. both SSERVER.EXE and SCLIENT.EXE will
//     automatically terminate. both applications will display some status text.
//   * you can also run SCLIENT.EXE from a different machine without having first
//     run SSERVER.EXE on the machine. in this case, SSERVER.EXE will be launched
//     by OLE in the background and you will be able to watch the output of
//     SCLIENT.EXE but the output of SSERVER.EXE will be hidden.
//   * to examine the automatic launch-security features of Network OLE, try
//     using the '...\CLSID\{...}\LaunchPermission = Y' key commented out in
//     the SSERVER.REG file and reinstalling it. by setting different read-access
//     privileges on this key (using the Security/Permissions... dialog in the
//     REGEDT32 registry tool built into the system) you can allow other
//     users to run the SCLIENT.EXE program from their accounts.
//
// Copyright 1996 Microsoft Corporation.  All Rights Reserved.
// ===========================================================================

// %%Includes: ---------------------------------------------------------------
#define INC_OLE2
#define STRICT
#include <stdio.h>
#include <windows.h>
#include <initguid.h>

// %%GUIDs: ------------------------------------------------------------------
DEFINE_GUID(CLSID_SimpleObject, 0x5e9ddec7, 0x5767, 0x11cf, 0xbe, 0xab, 0x0, 0xaa, 0x0, 0x6c, 0x36, 0x6);

// %%Globals: ----------------------------------------------------------------
HANDLE          hevtDone;

// %%Classes: ----------------------------------------------------------------
// simple class-factory: only knows how to create CSimpleObject instances
class CClassFactory : public IClassFactory {
  public:
    // IUnknown
    STDMETHODIMP    QueryInterface (REFIID riid, void** ppv);
    STDMETHODIMP_(ULONG) AddRef(void)  { return 1; };
    STDMETHODIMP_(ULONG) Release(void) { return 1; }

    // IClassFactory
    STDMETHODIMP    CreateInstance (LPUNKNOWN punkOuter, REFIID iid, void **ppv);
    STDMETHODIMP    LockServer (BOOL fLock) { return E_FAIL; };
    };

// simple object supporting a dummy IStream
class CSimpleObject : public IStream {
  public:
    // IUnknown
    STDMETHODIMP    QueryInterface (REFIID iid, void **ppv);
    STDMETHODIMP_(ULONG) AddRef(void)  { return InterlockedIncrement(&m_cRef); };
    STDMETHODIMP_(ULONG) Release(void) { if (InterlockedDecrement(&m_cRef) == 0) { delete this; return 0; } return 1; }

    // IStream
    STDMETHODIMP    Read(void *pv, ULONG cb, ULONG *pcbRead);
    STDMETHODIMP    Write(VOID const *pv, ULONG cb, ULONG *pcbWritten);
    STDMETHODIMP    Seek(LARGE_INTEGER dbMove, DWORD dwOrigin, ULARGE_INTEGER *pbNewPosition)
        { return E_FAIL; }
    STDMETHODIMP    SetSize(ULARGE_INTEGER cbNewSize)
        { return E_FAIL; }
    STDMETHODIMP    CopyTo(IStream *pstm, ULARGE_INTEGER cb, ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten)
        { return E_FAIL; }
    STDMETHODIMP    Commit(DWORD grfCommitFlags)
        { return E_FAIL; }
    STDMETHODIMP    Revert(void)
        { return E_FAIL; }
    STDMETHODIMP    LockRegion(ULARGE_INTEGER bOffset, ULARGE_INTEGER cb, DWORD dwLockType)
        { return E_FAIL; }
    STDMETHODIMP    UnlockRegion(ULARGE_INTEGER bOffset, ULARGE_INTEGER cb, DWORD dwLockType)
        { return E_FAIL; }
    STDMETHODIMP    Stat(STATSTG *pstatstg, DWORD grfStatFlag)
        { return E_FAIL; }
    STDMETHODIMP    Clone(IStream **ppstm)
        { return E_FAIL; }

    // constructors/destructors
    CSimpleObject()     { m_cRef = 1; }
    ~CSimpleObject()    { SetEvent(hevtDone); }

  private:
    LONG        m_cRef;
    };

// %%Globals: ----------------------------------------------------------------
CClassFactory   g_ClassFactory;

// ---------------------------------------------------------------------------
// %%Function: Message
//
//  Formats and displays a message to the console.
// ---------------------------------------------------------------------------
 void
Message(LPTSTR szPrefix, HRESULT hr)
{
    LPTSTR   szMessage;

    if (hr == S_OK)
        {
        wprintf(szPrefix);
        wprintf(TEXT("\n"));
        return;
        }

    if (HRESULT_FACILITY(hr) == FACILITY_WINDOWS)
        hr = HRESULT_CODE(hr);

    FormatMessage(
        FORMAT_MESSAGE_ALLOCATE_BUFFER |
        FORMAT_MESSAGE_FROM_SYSTEM,
        NULL,
        hr,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //The user default language
        (LPTSTR)&szMessage,
        0,
        NULL );

    wprintf(TEXT("%s: %s(%lx)\n"), szPrefix, szMessage, hr);

    LocalFree(szMessage);
}  // Message

// ---------------------------------------------------------------------------
// %%Function: CSimpleObject::QueryInterface
// ---------------------------------------------------------------------------
 STDMETHODIMP
CSimpleObject::QueryInterface(REFIID riid, void** ppv)
{
    if (ppv == NULL)
        return E_INVALIDARG;
    if (riid == IID_IUnknown || riid == IID_IStream)
        {
        *ppv = (IUnknown *) this;
        AddRef();
        return S_OK;
        }
    *ppv = NULL;
    return E_NOINTERFACE;
}  // CSimpleObject::QueryInterface

// ---------------------------------------------------------------------------
// %%Function: CSimpleObject::Read
// ---------------------------------------------------------------------------
 STDMETHODIMP
CSimpleObject::Read(void *pv, ULONG cb, ULONG *pcbRead)
{
    Message(TEXT("Server: IStream:Read"), S_OK);
    if (!pv && cb != 0)
        return E_INVALIDARG;

    // fill the buffer with FF's. we could read it from somewhere.
    if (cb != 0)
        memset(pv, 0xFF, cb);

    if (pcbRead)
        *pcbRead = cb;
    return S_OK;
}  // CSimpleObject::Read

// ---------------------------------------------------------------------------
// %%Function: CSimpleObject::Write
// ---------------------------------------------------------------------------
 STDMETHODIMP
CSimpleObject::Write(VOID const *pv, ULONG cb, ULONG *pcbWritten)
{
    Message(TEXT("Server: IStream:Write"), S_OK);
    if (!pv && cb != 0)
        return E_INVALIDARG;
    // ignore the data, but we could examine it or put it somewhere
    if (pcbWritten)
        *pcbWritten = cb;
    return S_OK;
}  // CSimpleObject::Write

// ---------------------------------------------------------------------------
// %%Function: CClassFactory::QueryInterface
// ---------------------------------------------------------------------------
 STDMETHODIMP
CClassFactory::QueryInterface(REFIID riid, void** ppv)
{
    if (ppv == NULL)
        return E_INVALIDARG;
    if (riid == IID_IClassFactory || riid == IID_IUnknown)
        {
        *ppv = (IClassFactory *) this;
        AddRef();
        return S_OK;
        }
    *ppv = NULL;
    return E_NOINTERFACE;
}  // CClassFactory::QueryInterface

// ---------------------------------------------------------------------------
// %%Function: CClassFactory::CreateInstance
// ---------------------------------------------------------------------------
 STDMETHODIMP
CClassFactory::CreateInstance(LPUNKNOWN punkOuter, REFIID riid, void** ppv)
{
    LPUNKNOWN   punk;
    HRESULT     hr;

    *ppv = NULL;

    if (punkOuter != NULL)
        return CLASS_E_NOAGGREGATION;

    Message(TEXT("Server: IClassFactory:CreateInstance"), S_OK);

    punk = new CSimpleObject;

    if (punk == NULL)
        return E_OUTOFMEMORY;

    hr = punk->QueryInterface(riid, ppv);
    punk->Release();
    return hr;
}  // CClassFactory::CreateInstance

// ---------------------------------------------------------------------------
// %%Function: main
// ---------------------------------------------------------------------------
 void __cdecl
main()
{
    HRESULT hr;
    DWORD   dwRegister;

    // create the thread which is signaled when the instance is deleted
    hevtDone = CreateEvent(NULL, FALSE, FALSE, NULL);
    if (hevtDone == NULL)
        {
        hr = HRESULT_FROM_WIN32(GetLastError());
        Message(TEXT("Server: CreateEvent"), hr);
        exit(hr);
        }

    // initialize COM for free-threading
    hr = CoInitializeEx(NULL, COINIT_MULTITHREADED);
    if (FAILED(hr))
        {
        Message(TEXT("Server: CoInitializeEx"), hr);
        exit(hr);
        }

    // register the class-object with OLE
    hr = CoRegisterClassObject(CLSID_SimpleObject, &g_ClassFactory,
        CLSCTX_SERVER, REGCLS_SINGLEUSE, &dwRegister);
    if (FAILED(hr))
        {
        Message(TEXT("Server: CoRegisterClassObject"), hr);
        exit(hr);
        }

    Message(TEXT("Server: Waiting"), S_OK);

    // wait until an object is created and deleted.
    WaitForSingleObject(hevtDone, INFINITE);

    CloseHandle(hevtDone);

    CoUninitialize();
    Message(TEXT("Server: Done"), S_OK);
}  // main

// EOF =======================================================================


README.TXT

===========================================================
           INSIDE OLE BY KRAIG BROCKSCHMIDT
                    COMPANION DISC
       COPYRIGHT (C) 1995 BY KRAIG BROCKSCHMIDT
===========================================================

This companion CD contains the sample source code and
related support files for the examples presented in Inside
OLE by Kraig Brockschmidt. We recommend that you store this
disc in a safe place after you install the files.

     INSTALLATION

The files on this disc are not compressed and can be copied
to your hard disk by running the Setup program (under Windows
NT or Windows 95) or by dragging the files in the CODE directory
to an INOLE directory on your hard disk using File Manager or
another utility (under 16-bit Windows). You must have at least
7.5 MB of free space on your hard disk to install the files.

**************************************************************
*                                                            *
*                        IMPORTANT                           *
*                                                            *
**************************************************************

Many of the samples include a REG (registration) file that
must be "run" before the samples will work. Double click
on the file in FIle Manager or Explorer to "run" them.  If you
are working on Win32 platforms, use WIN32.REG.  If you are
working on Windows 3.1x, use WIN16.REG.

The REG files assume that you install the files to C:\INOLE;
you'll need to edit the REG files if you install the files
to another directory.  Simply replace all occurances of
"c:\inole" with the actual installation directory.

             DISC CONTENTS

The sample source code and support files for each chapter
are stored under CHAPXX, where XX is the chapter number.
For example, all files for Chapter 2 are in the CHAP02
directory. Several other directories contain files that you
will need for compilation or that you might want to study.
They include:

DIRECTORY     CONTENTS
---------     --------
BUILD         Contains the  DLL and EXE files created by
              MAKEALL.BAT files. It is recommended that you
              add this directory to your path.

CLASSLIB      C++ class library created for this book.

INC           Include files (H) needed for this book.
              Must be in your INCLUDE environment path.

INTERFAC      OLE template interface implementations.

LIB           Library files (LIB) needed for this book.
              Must be in your LIB environment path.

See the file CONTENTS.TXT for a full description of the
contents of the companion disC.

Be sure that all environment variables are set correctly for
your compiler. For example, if you have the Inside OLE files,
Visual C++ 2.0, the Win32 SDK, and the OLE controls development
kit installed on drive C, your environment variables should
include the following (long lines have been broken):

INCLUDE=C:\MSVC20\INCLUDE;C:\MSVC20\MFC\INCLUDE;C:\MSVC20\CDK32\INCLUDE;
        C:\MSTOOLS\IDL;C:\MSTOOLS\H;C:\INOLE\INC
LIB=C:\MSVC20\LIB;C:\MSVC20\MFC\LIB;C:\MSVC20\CDK32\LIB;
    C:\MSTOOLS\LIB;C:\INOLE\LIB
PATH=C:\MSVC20\BIN;C:\MSVC20\CDK32\BIN;C:\MSTOOLS\BIN;C:\INOLE\BUILD

Check your compiler's documentation for more information.

NOTE: You might need to increase your environment size to accommodate
the environment variables.

         BUILDING THE SAMPLE PROGRAMS

To compile all the sample programs on this disc, run the MAKEALL.BAT
batch file in the installation directory. You must add the INCLUDE
and LIB directories from this installation to the INCLUDE and LIB
environment variables as detailed above.

For detailed information on the build process and the files
involved, see the file BUILD.TXT.  This file also includes
information on the OLE UI Library and the MIDL compiler which
may be important for your work with these samples

        KNOWN ERRORS AND PROBLEMS

As the printed copy of this book was finalized, extensive testing
and debugging were performed on the samples on Windows 3.1x (16-bit),
Windows NT 3.5 and 3.51, and Windows 95, the latter two platforms
being still in pre-release form at the time of production.  As bugs
are invariably found, there are several places where the book text
does not match the sample code.  The file ERRATA.TXT points out these
discrepancies and contains the most up-to-date information.  Please
take a few moments to review ERRATA.TXT and mark the printed
book as appropriate.

  IF THERE ARE PROBLEMS WITH THIS DISC OR THE SAMPLES

Every effort was made to ensure the accuracy of the book and
this disc. Should you encounter a problem, please contact
us at the following address:

    Microsoft Press
    Attn: Inside OLE Editor
    One Microsoft Way
    Redmond, WA  98052-6399

No telephone calls, please.

The author also welcomes your comments through email at
kraigb@microsoft.com.  Due to the potential volume of email,
responses are not guaranteed.  For timely support of OLE
questions or problems, contact Microsoft Developer Support
Services.


CLIENT.CPP

/*
 * CLIENT.CPP
 * Cosmo Chapter 1
 *
 * Implementation of the CCosmoClient class that just makes sure
 * we get a CCosmoDoc on doc creation and that we initialize fully.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CCosmoClient::CCosmoClient
 * CCosmoClient::~CCosmoClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame to the frame object.
 */

CCosmoClient::CCosmoClient(HINSTANCE hInst, PCFrame pFR)
    : CClient(hInst, pFR)
    {
    return;
    }

CCosmoClient::~CCosmoClient(void)
    {
    return;
    }

/*
 * CCosmoClient::CreateCDocument
 *
 * Purpose:
 *  Constructs a new document specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::CreateCDocument(void)
    {
    return (PCDocument)(new CCosmoDoc(m_hInst, m_pFR, m_pAdv));
    }

/*
 * CCosmoClient::NewDocument
 *
 * Purpose:
 *  Small override of the CClient::NewDocument that we have just
 *  to check the initial line selection on new document creation.
 *
 * Parameters:
 *  fVisible        BOOL indicating if the document is to be
 *                  visible or not.
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::NewDocument(BOOL fVisible)
    {
    PCDocument  pDoc;

    //Perform default NewDocument first
    pDoc=CClient::NewDocument(fVisible);

    //We know that m_pFR is actually a CCosmoFrame, so type is safe.
    ((PCCosmoFrame)m_pFR)->CheckLineSelection(IDM_LINESOLID);

    return pDoc;
    }


COSMO.CPP

/*
 * COSMO.CPP
 * Cosmo Chapter 1
 *
 * WinMain and CCosmoFrame implementations.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCCosmoFrame    pFR;
    FRAMEINIT       fi;
    WPARAM          wRet;

    //Attempt to allocate and initialize the application
    pFR=new CCosmoFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return -1;

    fi.idsMin=IDS_FRAMEMIN;
    fi.idsMax=IDS_FRAMEMAX;
    fi.idsStatMin=IDS_STATMESSAGEMIN;
    fi.idsStatMax=IDS_STATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=440;
    fi.cy=460;

    //If we can initialize pFR, start chugging messages
    if (pFR->Init(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

/*
 * CCosmoFrame::CCosmoFrame
 * CCosmoFrame::~CCosmoFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WInMain
 */

CCosmoFrame::CCosmoFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    : CFrame(hInst, hInstPrev, pszCmdLine, nCmdShow)
    {
    UINT        i;

    for (i=0; i<5; i++)
        m_hBmpLines[i]=NULL;

    m_uIDCurLine=0;
    return;
    }

CCosmoFrame::~CCosmoFrame(void)
    {
    UINT        i;

    for (i=0; i<5; i++)
        {
        if (NULL!=m_hBmpLines[i])
            DeleteObject(m_hBmpLines[i]);
        }

    return;
    }

/*
 * CCosmoFrame::CreateCClient
 *
 * Purpose:
 *  Constructs a new client specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient        Pointer to the new client object.
 */

PCClient CCosmoFrame::CreateCClient(void)
    {
    return (PCClient)(new CCosmoClient(m_hInst, this));
    }

/*
 * CCosmoFrame::RegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CCosmoFrame::RegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //First let the standard frame do its thing
    if (!CFrame::RegisterAllClasses())
        return FALSE;

    /*
     * We want a different background color for the document
     * because the Polyline we put in the document will paint
     * with COLOR_WINDOW which by default which is CLASSLIB's
     * default document color.
     */

    GetClassInfo(m_hInst, SZCLASSDOCUMENT, &wc);
    UnregisterClass(SZCLASSDOCUMENT, m_hInst);

    wc.hbrBackground=(HBRUSH)(COLOR_APPWORKSPACE+1);

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

    //Register the Polyline window.
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.hInstance     = m_hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PolylineWndProc;
    wc.cbWndExtra    = CBPOLYLINEWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_CROSS);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPOLYLINE;

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

    return TRUE;
    }

/*
 * CCosmoFrame::PreShowInit
 *
 * Purpose:
 *  Called from Init before intially showing the window.  We do
 *  whatever else we want here, modifying nCmdShow as necessary
 *  which affects ShowWindow in Init.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if this initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CCosmoFrame::PreShowInit(void)
    {
    CreateLineMenu();
    CheckLineSelection(IDM_LINESOLID);
    return TRUE;
    }

/*
 * CCosmoFrame::CreateLineMenu
 *
 * Purpose:
 *  Initializes the bitmaps used to create the Line menu and
 *  replaces the text items defined in the application resources
 *  with these bitmaps.  Note that the contents of m_hBmpLines
 *  must be cleaned up when the application terminates.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CreateLineMenu(void)
    {
    HMENU       hMenu;
    HDC         hDC, hMemDC;
    HPEN        hPen;
    HGDIOBJ     hObj;
    TEXTMETRIC  tm;
    UINT        i, cx, cy;

    hMenu=GetSubMenu(GetMenu(m_hWnd), 3);   //Line menu.
    hDC=GetDC(m_hWnd);

    //Create each line in a menu item 8 chars wide, one char high.
    GetTextMetrics(hDC, &tm);
    cx=tm.tmAveCharWidth*8;
    cy=tm.tmHeight;

    /*
     * Create a memory DC in which to draw lines, and bitmaps
     * for each line.
     */
    hMemDC=CreateCompatibleDC(hDC);
    ReleaseDC(m_hWnd, hDC);

    for (i=0; i<5; i++)
        {
        m_hBmpLines[i]=CreateCompatibleBitmap(hMemDC, cx, cy);
        SelectObject(hMemDC, m_hBmpLines[i]);

        PatBlt(hMemDC, 0, 0, cx, cy, WHITENESS);

        hPen=CreatePen(i, 1, 0L);       //i=line style like PS_SOLID
        hObj=SelectObject(hMemDC, hPen);

        MoveToEx(hMemDC, 0, cy/2, NULL);
        LineTo(hMemDC, cx, cy/2);

        ModifyMenu(hMenu, IDM_LINEMIN+i, MF_BYCOMMAND | MF_BITMAP
            , IDM_LINEMIN+i, (LPTSTR)(LONG)(UINT)m_hBmpLines[i]);

        SelectObject(hMemDC, hObj);
        DeleteObject(hPen);
        }

    CheckMenuItem(hMenu, IDM_LINESOLID, MF_CHECKED);
    DeleteDC(hMemDC);

    return;
    }

/*
 * CCosmoFrame::CreateToolbar
 *
 * Purpose:
 *  Procedure to create all the necessary toolbar buttons.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of tools added to the bar.
 */

UINT CCosmoFrame::CreateToolbar(void)
    {
    UINT            iLast;
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;
    UINT            utEx  =GIZMOTYPE_BUTTONATTRIBUTEEX;

    //Insert the standard ones.
    iLast=CFrame::CreateToolbar();

    /*
     * Insert File Import in the 5th position and account for
     * it in iLast.
     */
    m_pTB->Add(utCmd, 4, IDM_FILEIMPORT, m_dxB, m_dyB
        , NULL, m_hBmp, 2, uState);
    iLast++;

    //Separator
    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    /*
     * For the Background bitmap, preserve our use of black
     * (part of the image)
     */
    m_pTB->Add(utCmd, iLast++, IDM_COLORBACKGROUND, m_dxB, m_dyB
        , NULL, m_hBmp, 3, GIZMO_NORMAL | PRESERVE_BLACK);

    m_pTB->Add(utCmd, iLast++, IDM_COLORLINE, m_dxB, m_dyB
        , NULL, m_hBmp, 4, uState);

    //Separator
    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //Line styles.
    m_pTB->Add(utEx, iLast++, IDM_LINESOLID, m_dxB, m_dyB
        , NULL, m_hBmp, 5, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASH, m_dxB, m_dyB
        , NULL, m_hBmp, 6, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 7, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASHDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 8, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASHDOTDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 9, uState);

    return iLast;
    }

/*
 * CCosmoFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the Cosmo frame window that just
 *  processes the line menu and the color menu leaving the
 *  CFrame to do everything else.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CCosmoFrame::OnCommand(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PCCosmoDoc      pDoc;
    TCHAR           szFile[CCHPATHMAX];
    BOOL            fOK;
    UINT            i, uTemp;
    COLORREF        rgColors[16];
    CHOOSECOLOR     cc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many toolbar notifications.
     */
    if (m_fInit)
        return 0L;

    pDoc=(PCCosmoDoc)m_pCL->ActiveDocument();

    /*
     * Check for the line style commands which are
     * IDM_LINEMIN+<style>.  We handle this by changing the menu
     * and toolbar, then we pass it to the document for real
     * processing.
     */
    if (NULL!=pDoc && IDM_LINEMIN <= wID && IDM_LINEMAX >=wID)
        {
        CheckLineSelection(wID);
        pDoc->LineStyleSet(wID-IDM_LINEMIN);
        return 0L;
        }

    switch (wID)
        {
        case IDM_FILEIMPORT:
            szFile[0]=0;
            fOK=SaveOpenDialog(szFile, CCHPATHMAX, IDS_FILEIMPORT
                , TRUE, &i);

            if (fOK)
                {
                uTemp=pDoc->Load(FALSE, szFile);
                pDoc->ErrorMessage(uTemp);
                }

            return (LRESULT)fOK;

        case IDM_COLORBACKGROUND:
        case IDM_COLORLINE:
            //Invoke the color chooser for either color
            uTemp=(IDM_COLORBACKGROUND==wID)
                ? DOCCOLOR_BACKGROUND : DOCCOLOR_LINE;

            for (i=0; i<16; i++)
                rgColors[i]=RGB(0, 0, i*16);

            memset(&cc, 0, sizeof(CHOOSECOLOR));
            cc.lStructSize=sizeof(CHOOSECOLOR);
            cc.lpCustColors=rgColors;
            cc.hwndOwner=hWnd;
            cc.Flags=CC_RGBINIT;
            cc.rgbResult=pDoc->ColorGet(uTemp);

            if (ChooseColor(&cc))
                pDoc->ColorSet(uTemp, cc.rgbResult);

            break;

        default:
           CFrame::OnCommand(hWnd, wParam, lParam);
        }

    return 0L;
    }

/*
 * CCosmoFrame::OnDocumentDataChange
 *
 * Purpose:
 *  Update the Line menu and toolbar if the style in the data
 *  changes.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentDataChange(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::OnDocumentActivate
 *
 * Purpose:
 *  Informs us that document activation changed, so update the UI
 *  for that new document.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentActivate(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    PCDocument  pDoc;
    BOOL        fOK=FALSE;
    BOOL        fCallDefault=TRUE;
    UINT        i;
    UINT        uTemp;
    UINT        uTempE;
    UINT        uTempD;

    pDoc=m_pCL->ActiveDocument();

    uTempE=MF_ENABLED | MF_BYCOMMAND;
    uTempD=MF_DISABLED | MF_GRAYED | MF_BYCOMMAND;
    uTemp=((NULL!=pDoc) ? uTempE : uTempD);

    //File menu:  If there is document window, disable Import.
    if (m_phMenu[0]==hMenu)
        EnableMenuItem(hMenu, IDM_FILEIMPORT, uTemp);

    //Color menu:  no document, no commands
    if (m_phMenu[2]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_COLORBACKGROUND, uTemp);
        EnableMenuItem(hMenu, IDM_COLORLINE,       uTemp);
        fCallDefault=FALSE;
        }

    //Line menu:  no document, no commands
    if (m_phMenu[3]==hMenu)
        {
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            EnableMenuItem(hMenu, i, uTemp);

        fCallDefault=FALSE;
        }

    if (fCallDefault)
        CFrame::UpdateMenus(hMenu, iMenu);

    return;
    }

/*
 * CCosmoFrame::UpdateToolbar
 *
 * Purpose:
 *  Enables and disables tools depending on whether we have
 *  a document or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateToolbar(void)
    {
    BOOL        fLast;
    UINT        i;

    //Save the last enabled state before CFrame changes it
    fLast=m_fLastEnable;

    //Let the default hack on its tools
    CFrame::UpdateToolbar();

    /*
     * If CFrame::UpdateToolbar changed anything, then we need
     * to change as well--if nothing changes, nothing to do.
     */
    if (fLast!=m_fLastEnable)
        {
        m_pTB->Enable(IDM_FILEIMPORT, m_fLastEnable);

        m_pTB->Enable(IDM_COLORBACKGROUND, m_fLastEnable);
        m_pTB->Enable(IDM_COLORLINE,       m_fLastEnable);

        for (i=IDM_LINEMIN; i <= IDM_LINEMAX; i++)
            m_pTB->Enable(i, m_fLastEnable);
        }

    return;
    }

/*
 * CCosmoFrame::CheckLineSelection
 *
 * Purpose:
 *  Maintains the bitmap menu and the tools for the line selection.
 *  Both are mutially exclusive option lists where a selection in
 *  one has to affect the other.
 *
 * Parameters:
 *  uID             UINT ID of the item to be selected
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CheckLineSelection(UINT uID)
    {
    UINT        i;
    HMENU       hMenu;

    //Update menus and tools if the selection changed.
    if (uID!=m_uIDCurLine)
        {
        m_uIDCurLine=uID;
        hMenu=GetMenu(m_hWnd);

        //Uncheck all lines initially.
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            CheckMenuItem(hMenu, i, MF_UNCHECKED | MF_BYCOMMAND);

        CheckMenuItem(hMenu, uID, MF_CHECKED | MF_BYCOMMAND);
        m_pTB->Check(uID, TRUE);
        }

    return;
    }


COSMO.H

/*
 * COSMO.H
 * Cosmo Chapter 1
 *
 * Single include file that pulls in everything needed for other
 * source files in the Cosmo application.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _COSMO_H_
#define _COSMO_H_

#define INC_CLASSLIB
#define CHAPTER1
#include <inole.h>
#include "resource.h"

//Get the editor window information.
#include "polyline.h"

//COSMO.CPP:  Frame object that creates a main window

class CCosmoFrame : public CFrame
    {
    private:
        HBITMAP         m_hBmpLines[5];     //Menu item bitmaps
        UINT            m_uIDCurLine;       //Current line selection

    protected:
        //Overridable for creating a CClient for this frame
        virtual PCClient  CreateCClient(void);

        virtual BOOL      RegisterAllClasses(void);
        virtual BOOL      PreShowInit(void);
        virtual UINT      CreateToolbar(void);

        virtual LRESULT   OnCommand(HWND, WPARAM, LPARAM);
        virtual void      OnDocumentDataChange(PCDocument);
        virtual void      OnDocumentActivate(PCDocument);

        //New for this class
        virtual void      CreateLineMenu(void);

    public:
        CCosmoFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CCosmoFrame(void);

        //Overrides
        virtual void      UpdateMenus(HMENU, UINT);
        virtual void      UpdateToolbar(void);

        //New for this class
        virtual void      CheckLineSelection(UINT);
    };

typedef CCosmoFrame *PCCosmoFrame;

//CLIENT.CPP

/*
 * The only reason we have a derived class here is to override
 * CreateCDocument so we can create our own type as well as
 * overriding NewDocument to perform one other piece of work once
 * the document's been created.
 */

class CCosmoClient : public CClient
    {
    protected:
        //Overridable for creating a new CDocument
        virtual PCDocument CreateCDocument(void);

    public:
        CCosmoClient(HINSTANCE, PCFrame);
        virtual ~CCosmoClient(void);

        virtual PCDocument NewDocument(BOOL);
    };

typedef CCosmoClient *PCCosmoClient;

//DOCUMENT.CPP

//Constant ID for the window polyline that lives in a document
#define ID_POLYLINE         10

class CCosmoDoc : public CDocument
    {
    friend class CPolylineAdviseSink;

    protected:
        UINT                    m_uPrevSize;    //Last WM_SIZE wParam
        LONG                    m_lVer;         //Loaded Polyline ver

        PCPolyline              m_pPL;          //Polyline window here
        PCPolylineAdviseSink    m_pPLAdv;       //Advises from Polyline

    protected:
        virtual BOOL     FMessageHook(HWND, UINT, WPARAM, LPARAM
            , LRESULT *);

    public:
        CCosmoDoc(HINSTANCE, PCFrame, PCDocumentAdviseSink);
        virtual ~CCosmoDoc(void);

        virtual BOOL     Init(PDOCUMENTINIT);

        virtual void     Clear(void);

        virtual UINT     Load(BOOL, LPTSTR);
        virtual UINT     Save(UINT, LPTSTR);

        virtual void     Undo(void);
        virtual BOOL     Clip(HWND, BOOL);
        virtual HGLOBAL  RenderFormat(UINT);
        virtual BOOL     FQueryPaste(void);
        virtual BOOL     Paste(HWND);

        virtual COLORREF ColorSet(UINT, COLORREF);
        virtual COLORREF ColorGet(UINT);

        virtual UINT     LineStyleSet(UINT);
        virtual UINT     LineStyleGet(void);
    };

typedef CCosmoDoc *PCCosmoDoc;

//These color indices wrap the polyline definitions
#define DOCCOLOR_BACKGROUND             POLYLINECOLOR_BACKGROUND
#define DOCCOLOR_LINE                   POLYLINECOLOR_LINE

#endif //_COSMO_H_


DOCUMENT.CPP

/*
 * DOCUMENT.CPP
 * Cosmo Chapter 1
 *
 * Implementation of the CCosmoDoc derivation of CDocument as
 * well as an implementation of CPolylineAdviseSink.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CCosmoDoc::CCosmoDoc
 * CCosmoDoc::~CCosmoDoc
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame of the frame object.
 *  pAdv            PCDocumentAdviseSink to notify on events
 */

CCosmoDoc::CCosmoDoc(HINSTANCE hInst, PCFrame pFR
    , PCDocumentAdviseSink pAdv)
    : CDocument(hInst, pFR, pAdv)
    {
    m_pPL=NULL;
    m_pPLAdv=NULL;
    m_uPrevSize=SIZE_RESTORED;
    return;
    }

CCosmoDoc::~CCosmoDoc(void)
    {
    //Clean up the allocations we did in Init
    if (NULL!=m_pPL)
        delete m_pPL;

    if (NULL!=m_pPLAdv)
        delete m_pPLAdv;

    return;
    }

/*
 * CCosmoDoc::Init
 *
 * Purpose:
 *  Initializes an already created document window.  The client
 *  actually creates the window for us, then passes that here for
 *  further initialization.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CCosmoDoc::Init(PDOCUMENTINIT pDI)
    {
    RECT        rc;

    //Change the stringtable range to our customization.
    pDI->idsMin=IDS_DOCUMENTMIN;
    pDI->idsMax=IDS_DOCUMENTMAX;

    //Do default initialization
    if (!CDocument::Init(pDI))
        return FALSE;

    //Add the Polyline stuff we need.
    m_pPLAdv=new CPolylineAdviseSink(this);
    m_pPL   =new CPolyline(m_hInst);

    //Attempt to create our contained Polyline.
    GetClientRect(m_hWnd, &rc);
    InflateRect(&rc, -8, -8);

    if (!m_pPL->Init(m_hWnd, &rc, WS_CHILD | WS_VISIBLE
        , ID_POLYLINE, m_pPLAdv))
        return FALSE;

    return TRUE;
    }

/*
 * CCosmoDoc::FMessageHook
 *
 * Purpose:
 *  Processes WM_SIZE for the document so we can resize
 *  the Polyline.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CCosmoDoc::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    UINT        dx, dy;
    RECT        rc;

    *pLRes=0;

    if (WM_SIZE==iMsg)
        {
        //Don't effect the Polyline size to or from minimized state.
        if (SIZE_MINIMIZED!=wParam && SIZE_MINIMIZED !=m_uPrevSize)
            {
            //When we change size, resize any Polyline we hold.
            dx=LOWORD(lParam);
            dy=HIWORD(lParam);

            /*
             * If we are getting WM_SIZE in response to a Polyline
             * notification, then don't resize the Polyline window
             * again.
             */
            if (!m_fNoSize && NULL!=m_pPL)
                {
                //Resize the polyline to fit the new client
                SetRect(&rc, 8, 8, dx-8, dy-8);
                m_pPL->RectSet(&rc, FALSE);

                /*
                 * We consider sizing something that makes the file
                 * dirty, but not until we've finished the create
                 * process, which is why we set fNoDirty to FALSE
                 * in WM_CREATE since we get a WM_SIZE on the first
                 * creation.
                 */
                if (!m_fNoDirty)
                    FDirtySet(TRUE);

                SetRect(&rc, 0, 0, dx, dy);

                if (NULL!=m_pAdv)
                    m_pAdv->OnSizeChange(this, &rc);

                m_fNoDirty=FALSE;
                }
            }

        m_uPrevSize=wParam;
        }

    /*
     * We return FALSE even on WM_SIZE so we can let the default
     * procedure handle maximized MDI child windows appropriately.
     */
    return FALSE;
    }

/*
 * CCosmoDoc::Clear
 *
 * Purpose:
 *  Sets all contents in the document back to defaults with
 *  no filename.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Clear(void)
    {
    //Completely reset the polyline
    m_pPL->New();

    CDocument::Clear();
    m_lVer=0;
    return;
    }

/*
 * CCosmoDoc::Load
 *
 * Purpose:
 *  Loads a given document without any user interface overwriting
 *  the previous contents of the Polyline window.  We do this by
 *  opening the file and telling the Polyline to load itself from
 *  that file.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load, NULL if the file
 *                  is new and untitled.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CCosmoDoc::Load(BOOL fChangeFile, LPTSTR pszFile)
    {
    if (NULL==pszFile)
        {
        //For a new untitled document, just rename ourselves.
        Rename(NULL);
        m_lVer=VERSIONCURRENT;
        return DOCERR_NONE;
        }

    m_lVer=m_pPL->ReadFromFile(pszFile);

    if (POLYLINE_E_READFAILURE==m_lVer)
        return DOCERR_READFAILURE;

    if (POLYLINE_E_UNSUPPORTEDVERSION==m_lVer)
        return DOCERR_UNSUPPORTEDVERSION;

    if (fChangeFile)
        Rename(pszFile);

    //Importing a file makes things dirty
    FDirtySet(!fChangeFile);

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::Save
 *
 * Purpose:
 *  Writes the file to a known filename, requiring that the user has
 *  previously used FileOpen or FileSaveAs to provide a filename.
 *
 * Parameters:
 *  uType           UINT indicating the type of file the user
 *                  requested to save in the File Save As dialog.
 *  pszFile         LPTSTR under which to save.  If NULL, use the
 *                  current name.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CCosmoDoc::Save(UINT uType, LPTSTR pszFile)
    {
    LONG        lVer, lRet;
    UINT        uTemp;
    BOOL        fRename=TRUE;

    if (NULL==pszFile)
        {
        fRename=FALSE;
        pszFile=m_szFile;
        }

    /*
     * Type 1 is the current version, type 2 is version 1.0 of the
     * Polyline so we use this to send the right version to
     * CPolyline::WriteToFile.
     */

    switch (uType)
        {
        case 0:         //From Save, use loaded version.
            lVer=m_lVer;
            break;

        case 1:
            lVer=VERSIONCURRENT;
            break;

        case 2:
            lVer=MAKELONG(0, 1);    //1.0
            break;

        default:
            return DOCERR_UNSUPPORTEDVERSION;
        }

    /*
     * If the version the user wants to save is different from the
     * version that we loaded and m_lVer is not zero (new doc),
     * then inform the user of the version change and verify.
     */
    if (0!=m_lVer && m_lVer!=lVer)
        {
        TCHAR       szMsg[128];

        wsprintf(szMsg, PSZ(IDS_VERSIONCHANGE)
            , (UINT)HIWORD(m_lVer), (UINT)LOWORD(m_lVer)
            , (UINT)HIWORD(lVer), (UINT)LOWORD(lVer));

        uTemp=MessageBox(m_hWnd, szMsg, PSZ(IDS_DOCUMENTCAPTION)
            , MB_YESNOCANCEL);

        if (IDCANCEL==uTemp)
            return DOCERR_CANCELLED;

        //If the user won't upgrade, revert to loaded version.
        if (IDNO==uTemp)
            lVer=m_lVer;
        }

    lRet=m_pPL->WriteToFile(pszFile, lVer);

    if (POLYLINE_E_NONE!=lRet)
        return DOCERR_WRITEFAILURE;

    //Saving makes us clean
    FDirtySet(FALSE);

    //Update the known version of this document.
    m_lVer=lVer;

    if (fRename)
        Rename(pszFile);

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::Undo
 *
 * Purpose:
 *  Reverses a previous action.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Undo(void)
    {
    m_pPL->Undo();
    return;
    }

/*
 * CCosmoDoc::Clip
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard, optionally implementing Cut by deleting the
 *  data in the current window after rendering.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *  fCut            BOOL indicating cut (TRUE) or copy (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::Clip(HWND hWndFrame, BOOL fCut)
    {
    BOOL            fRet=TRUE;
    HGLOBAL         hMem;
    UINT            i;

    //This array is so we can loop over the formats we provide.
    static UINT     rgcf[3]={0, CF_METAFILEPICT, CF_BITMAP};
    const UINT      cFormats=3;

    if (!OpenClipboard(hWndFrame))
        return FALSE;

    //Clean out whatever junk is in the clipboard.
    EmptyClipboard();

    rgcf[0]=m_cf;

    for (i=0; i < cFormats; i++)
        {
        //Copy private data first.
        hMem=RenderFormat(rgcf[i]);

        if (NULL!=hMem)
            SetClipboardData(rgcf[i], hMem);
        else
            fRet &=FALSE;
        }

    //Free clipboard ownership.
    CloseClipboard();

    //Delete our current data if "cut" succeeded.
    if (fRet && fCut)
        {
        m_pPL->New();
        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CCosmoDoc::RenderFormat
 *
 * Purpose:
 *  Renders a specific clipboard format into global memory.
 *
 * Parameters:
 *  cf              UINT format to render.
 *
 * Return Value:
 *  HGLOBAL         Global memory handle containing the data.
 */

HGLOBAL CCosmoDoc::RenderFormat(UINT cf)
    {
    HGLOBAL     hMem;

    if (cf==m_cf)
        {
        m_pPL->DataGetMem(VERSIONCURRENT, &hMem);
        return hMem;
        }

    switch (cf)
        {
        case CF_METAFILEPICT:
            return m_pPL->RenderMetafilePict();

        case CF_BITMAP:
            return (HGLOBAL)m_pPL->RenderBitmap();
        }

    return NULL;
    }

/*
 * CCosmoDoc::FQueryPaste
 *
 * Purpose:
 *  Determines if we can paste data from the clipboard.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CCosmoDoc::FQueryPaste(void)
    {
    return IsClipboardFormatAvailable(m_cf);
    }

/*
 * CCosmoDoc::Paste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  to the current figure in the editor window.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::Paste(HWND hWndFrame)
    {
    HGLOBAL         hMem;
    PPOLYLINEDATA   ppl;
    BOOL            fRet=FALSE;

    if (!OpenClipboard(hWndFrame))
        return FALSE;

    hMem=GetClipboardData(m_cf);

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);

        //TRUE in wParam to cause PLN_SIZECHANGE notification
        m_pPL->DataSet(ppl, FALSE, TRUE);
        GlobalUnlock(hMem);

        FDirtySet(TRUE);
        fRet=TRUE;
        }

    CloseClipboard();
    return fRet;
    }

/*
 * CCosmoDoc::ColorSet
 *
 * Purpose:
 *  Changes a color used in our contained Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color.
 *
 * Return Value:
 *  COLORREF        Previous color for the given index.
 */

COLORREF CCosmoDoc::ColorSet(UINT iColor, COLORREF cr)
    {
    return m_pPL->ColorSet(iColor, cr);
    }

/*
 * CCosmoDoc::ColorGet
 *
 * Purpose:
 *  Retrieves a color currently in use in the Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to retrieve.
 *
 * Return Value:
 *  COLORREF        Current color for the given index.
 */

COLORREF CCosmoDoc::ColorGet(UINT iColor)
    {
    return m_pPL->ColorGet(iColor);
    }

/*
 * CCosmoDoc::LineStyleSet
 *
 * Purpose:
 *  Changes the line style currently used in the Polyline
 *
 * Parameters:
 *  iStyle          UINT index of the new line style to use.
 *
 * Return Value:
 *  UINT            Previous line style.
 */

UINT CCosmoDoc::LineStyleSet(UINT iStyle)
    {
    return m_pPL->LineStyleSet(iStyle);
    }

/*
 * CCosmoDoc::LineStyleGet
 *
 * Purpose:
 *  Retrieves the line style currently used in the Polyline
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Current line style.
 */

UINT CCosmoDoc::LineStyleGet(void)
    {
    if (NULL==m_pPL)    //m_pPL might not be valid yet
        return 0L;

    return m_pPL->LineStyleGet();
    }

/*
 * CPolylineAdviseSink::CPolylineAdviseSink
 * CPolylineAdviseSink::~CPolylineAdviseSink
 *
 * Constructor Parameters:
 *  pv              LPVOID to store in this object
 */

CPolylineAdviseSink::CPolylineAdviseSink(LPVOID pv)
    {
    m_pv=pv;
    return;
    }

CPolylineAdviseSink::~CPolylineAdviseSink(void)
    {
    return;
    }

/*
 * CPolylineAdviseSink::OnPointChange
 *
 * Purpose:
 *  Informs the document that the polyline added or removed a point.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnPointChange(void)
    {
    PCDocument      pDoc=(PCDocument)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnSizeChange
 *
 * Purpose:
 *  Informs the document that the polyline changed size.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnSizeChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;
    RECT            rc;
    DWORD           dwStyle;

    /*
     * Polyline window is informing us that it changed size in
     * response to setting it's data.  Therefore we have to
     * size ourselves accordingly but without moving the screen
     * position of the polyline window.
     */

    pDoc->m_fNoSize=TRUE;

    //Set the document window size.
    GetWindowRect(pDoc->m_pPL->Window(), &rc);
    InflateRect(&rc, 8, 8);

    //Adjust for a window sans menu
    dwStyle=GetWindowLong(pDoc->m_hWnd, GWL_STYLE);
    AdjustWindowRect(&rc, dwStyle, FALSE);

    SetWindowPos(pDoc->m_hWnd, NULL, 0, 0, rc.right-rc.left
        , rc.bottom-rc.top, SWP_NOMOVE | SWP_NOZORDER);

    if (NULL!=pDoc->m_pAdv)
        pDoc->m_pAdv->OnSizeChange(pDoc, &rc);

    pDoc->m_fNoSize=FALSE;
    pDoc->FDirtySet(TRUE);

    return;
    }

/*
 * CPolylineAdviseSink::OnDataChange
 *
 * Purpose:
 *  Informs the document that the polyline data changed.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnDataChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    if (NULL!=pDoc->m_pAdv)
        pDoc->m_pAdv->OnDataChange(pDoc);

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnColorChange
 *
 * Purpose:
 *  Informs the document that the polyline data changed a color.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnColorChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnLineStyleChange
 *
 * Purpose:
 *  Informs the document that the polyline changed its line style.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnLineStyleChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }


POLYLINE.CPP

/*
 * POLYLINE.CPP
 * Cosmo Chapter 1
 *
 * Implementation of the CPolyline class.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CPolyline:CPolyline
 * CPolyline::~CPolyline
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application we're in.
 */

CPolyline::CPolyline(HINSTANCE hInst)
    : CWindow(hInst)
    {
    m_pAdv=NULL;
    m_hWnd=NULL;
    return;
    }

CPolyline::~CPolyline(void)
    {
    return;
    }

/*
 * CPolyline::Init
 *
 * Purpose:
 *  Instantiates a polyline window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags
 *  uID             UINT ID to associate with this window
 *  pAdv            PCPolylineAdviseSink of the sink wanting our
 *                  notifications.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPolyline::Init(HWND hWndParent, LPRECT pRect, DWORD dwStyle
    , UINT uID, PCPolylineAdviseSink pAdv)
    {
    m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY, SZCLASSPOLYLINE
        , SZCLASSPOLYLINE, dwStyle, pRect->left, pRect->top
        , pRect->right-pRect->left, pRect->bottom-pRect->top
        , hWndParent, (HMENU)uID, m_hInst, this);

    if (NULL!=m_hWnd)
        m_pAdv=pAdv;

    return (NULL!=m_hWnd);
    }

/*
 * CPolyline::New
 *
 * Purpose:
 *  Cleans out and reinitializes the data to defaults.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolyline::New(void)
    {
    UINT        i;
    RECT        rc;

    m_pl.wVerMaj=VERSIONMAJOR;
    m_pl.wVerMin=VERSIONMINOR;

    //Our rectangle is the size of our window's client area.
    GetClientRect(m_hWnd, &rc);
    RECTTORECTS(rc, m_pl.rc);

    //Clean out the POLYLINE structure and repaint the window.
    for (i=0; i< CPOLYLINEPOINTS; i++)
        {
        m_pl.rgpt[i].x=0;
        m_pl.rgpt[i].y=0;
        }

    m_pl.cPoints      =0;
    m_pl.rgbBackground=GetSysColor(COLOR_WINDOW);
    m_pl.rgbLine      =GetSysColor(COLOR_WINDOWTEXT);
    m_pl.iLineStyle   =PS_SOLID;

    InvalidateRect(m_hWnd, NULL, TRUE);
    UpdateWindow(m_hWnd);

    //Inform the advise sink of this data change.
    if (NULL!=m_pAdv)
        m_pAdv->OnDataChange();

    return;
    }

/*
 * CPolyline::Undo
 *
 * Purpose:
 *  Reverses previous actions in a Polyline.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if we can Undo more, FALSE otherwise.
 */

BOOL CPolyline::Undo(void)
    {
    //Decrement the number of active points and repaint.
    if (m_pl.cPoints > 0)
        {
        m_pl.cPoints--;
        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    if (NULL!=m_pAdv)
        m_pAdv->OnPointChange();

    //Return if we can undo any more.
    return (0!=m_pl.cPoints);
    }

/*
 * CPolyline::ReadFromFile
 *
 * Purpose:
 *  Loads our data (any known version) from a file handle returning
 *  the version number of the data or an error value.
 *
 * Parameters:
 *  pszFile         LPTSTR of the file to open.
 *
 * Return Value:
 *  LONG            Version number or negative POLYLINE_E_* value.
 */

LONG CPolyline::ReadFromFile(LPTSTR pszFile)
    {
    OFSTRUCT        of;
    HFILE           hFile;
    POLYLINEDATA    pl;
    UINT            cb=(UINT)-1;
    UINT            cbExpect=0;

    if (NULL==pszFile)
        return POLYLINE_E_READFAILURE;

    //OpenFileW is a member of CPolyline.
    hFile=OpenFileW(pszFile, &of, OF_READ);

    if (HFILE_ERROR==hFile)
        return POLYLINE_E_READFAILURE;

    //Read version numbers and seek back to file beginning.
    cb=_lread(hFile, &pl, 2*sizeof(WORD));
    _llseek(hFile, 0L, 0);

    if (2*sizeof(WORD)!=cb)
        {
        _lclose(hFile);
        return POLYLINE_E_READFAILURE;
        }

    /*
     * For version 2.0, read the entire file.  For version 1.0 read
     * the file up to CBPOLYLINEDATAVER10.  For anything else, give
     * an error.
     */

    switch (pl.wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (pl.wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (pl.wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        {
        _lclose(hFile);
        return POLYLINE_E_UNSUPPORTEDVERSION;
        }

    cb=_lread(hFile, &pl, cbExpect);
    _lclose(hFile);

    if (cbExpect!=cb)
        return POLYLINE_E_READFAILURE;

    /*
     * If we loaded successfully, make the data current.  By using
     * DataSet we centralize our version upgrading.  We size the
     * polyline window to the data AND notify the document so it
     * sizes to the polyline.
     */
    DataSet(&pl, TRUE, TRUE);

    //Return what version we just loaded.
    return MAKELONG(pl.wVerMin, pl.wVerMaj);
    }

/*
 * CPolyline::WriteToFile
 *
 * Purpose:
 *  Ignorantly writes our current data into an opened file in a
 *  particular version.
 *
 * Parameters:
 *  pszFile         LPTSTR filename in which to store the data.
 *  lVer            LONG providing version number Major (HI)
 *                  and Minor (Low)
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::WriteToFile(LPTSTR pszFile, LONG lVer)
    {
    OFSTRUCT        of;
    HFILE           hFile;
    UINT            cb;
    UINT            cbExpect=0;
    WORD            wVerMaj=HIWORD(lVer);
    WORD            wVerMin=LOWORD(lVer);
    POLYLINEDATA    pl;

    if (NULL==pszFile)
        return POLYLINE_E_READFAILURE;

    //Get a copy of our data in the version we're going to save.
    DataGet(&pl, lVer);

    switch (wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    hFile=OpenFileW(pszFile, &of, OF_CREATE | OF_WRITE);

    if (HFILE_ERROR==hFile)
        return DOCERR_COULDNOTOPEN;

    cb=_lwrite(hFile, (LPCSTR)&pl, cbExpect);
    _lclose(hFile);

    return (cbExpect==cb) ? POLYLINE_E_NONE
        : POLYLINE_E_WRITEFAILURE;
    }

/*
 * CPolyline::OpenFileW (Private)
 *
 * Purpose:
 *  Under Win32, OpenFile does not take Unicode strings.  This
 *  function converts a Unicode string into an ANSI string and
 *  calls OpenFile.  This just maps to OpenFile without Unicode.
 *
 * Parameters, Return Value:
 *  Same as OpenFile.
 */

HFILE CPolyline::OpenFileW(LPTSTR pszFile, LPOFSTRUCT pof
    , UINT uFlags)
    {
   #ifdef UNICODE
    CHAR        szTemp[CCHPATHMAX];

    UNICODETOANSI(pszFile, szTemp, CCHPATHMAX);
    return OpenFile(szTemp, pof, uFlags);
   #else
    return OpenFile(pszFile, pof, uFlags);
   #endif
    }

/*
 * CPolyline::DataSet
 *
 * Purpose:
 *  Sets the current data in this Polyline to a given structure.
 *
 * Parameters:
 *  ppl             PPOLYLINEDATA to initialize to.
 *  fSizeToData     BOOL indicating if we're to size to the data or
 *                  scale it.
 *  fNotify         BOOL indicating if we're to send an advise on
 *                  this change.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataSet(PPOLYLINEDATA ppl, BOOL fSizeToData
    , BOOL fNotify)
    {
    RECTS       rcs;
    RECT        rc;
    UINT        i;

    /*
     * Copy the structure in ppl and repaint to reflect the new
     * point set.  Note that unlike the RectSet message, we do no
     * scaling, assuming that the rect in the structure is
     * appropriate for the data.
     */

    if (NULL==ppl)
        return POLYLINE_E_INVALIDPOINTER;

    //Preserve the old rectangle
    rcs=m_pl.rc;

    /*
     * For version 2.0 we perform a straight copy.  For version
     * 1.0 we copy the 1.0 structure and fill in defaults for
     * the 2.0 additions.
     */

    switch (ppl->wVerMaj)
        {
        case VERSIONMAJOR:          //2.x
            switch (ppl->wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    m_pl=*ppl;
                    break;

                default:
                    return POLYLINE_E_UNSUPPORTEDVERSION;
                }
            break;

        case 1:                     //1.x
            switch (ppl->wVerMin)
                {
                case 0:             //1.0
                    *((PPOLYLINEDATA10)&m_pl)=
                        *((PPOLYLINEDATA10)ppl);

                    /*
                     * Update this structure to 2.0.  Note that we
                     * assume whoever loaded us to save the loaded
                     * version so it can later ask what version
                     * the user wants to save.
                     */
                    m_pl.wVerMaj=VERSIONMAJOR;
                    m_pl.wVerMin=VERSIONMINOR;

                    /*
                     * Version 1.0 stored rc in parent coordinates.
                     * We need those now in our client coodinates.
                     */
                    RECTSTORECT(m_pl.rc, rc);
                    OffsetRect(&rc, -m_pl.rc.left, -m_pl.rc.top);

                    /*
                     * 1.0 data had points relative to size of the
                     * rectangle.  We need to scale these to 0-32767
                     * independent of the rectangle for the version
                     * upgrade.
                     */
                    for (i=0; i < m_pl.cPoints; i++)
                        PointScale(&rc, &m_pl.rgpt[i], FALSE);

                    RECTTORECTS(rc, m_pl.rc);

                    //New 2.0 features
                    m_pl.rgbBackground=GetSysColor(COLOR_WINDOW);
                    m_pl.rgbLine=GetSysColor(COLOR_WINDOWTEXT);
                    m_pl.iLineStyle=PS_SOLID;
                    break;

                default:
                    return POLYLINE_E_UNSUPPORTEDVERSION;
                }
            break;

        default:
            return POLYLINE_E_UNSUPPORTEDVERSION;
        }

    //Inform our parent of the data change
    if (NULL!=m_pAdv)
        m_pAdv->OnDataChange();

    /*
     * If we're scaling the window to fit the data, then use
     * RectSet passing our current rectangle as the new one.
     * That makes sure that the data won't change but that the
     * window is resized.
     */

    if (fSizeToData)
        {
        POINT       pt;

        /*
         * Get our offset in the parent window so we can RectSet
         * to the right place since RectSet expects rectangle in
         * parent coordinates and we get it in client coordinates.
         */
        GetWindowRect(m_hWnd, &rc);
        pt.x=rc.left;
        pt.y=rc.top;
        ScreenToClient(GetParent(m_hWnd), &pt);
        RECTSTORECT(m_pl.rc, rc);
        OffsetRect(&rc, pt.x, pt.y);

        //This will also cause a repaint.
        RectSet(&rc, fNotify);
        }
    else
        {
        //Make sure we're updated.
        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    return POLYLINE_E_NONE;
    }

/*
 * CPolyline::DataGet
 *
 * Purpose:
 *  Retrieves the Polyline's current data.
 *
 * Parameters:
 *  ppl             PPOLYLINEDATA into which we copy the data.
 *  lVer            LONG version of the data to retrieve.  Use
 *                  VERSIONCURRENT to retrieve the most current.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value
 */

LONG CPolyline::DataGet(PPOLYLINEDATA ppl, LONG lVer)
    {
    UINT        i;
    RECT        rc;

    //Retieve the current version
    if (lVer==MAKELONG(VERSIONMINOR, VERSIONMAJOR)
        || VERSIONCURRENT==lVer)
        {
        *ppl=m_pl;
        return POLYLINE_E_NONE;
        }

    //Check for versions we support, 1.x
    if (HIWORD(lVer)!=1)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    if (LOWORD(lVer)==0)    //Check for 1.0
        {
        //Do 2.0 to 1.0 conversion
        *((PPOLYLINEDATA10)ppl)=*((PPOLYLINEDATA10)&m_pl);

        RECTSTORECT(ppl->rc, rc);

        //Get the parent coordinates of our rectangle
        if (!IsWindow(m_hWnd))
            OffsetRect(&rc, 8, 8); //This is always the offset.
        else
            RectGet(&rc);

        /*
         * 1.0 data has points relative to size of the rectangle.
         * We need to scale these from 0-32767 so we have the
         * right values.
         */
        for (i=0; i < ppl->cPoints; i++)
            PointScale(&rc, &ppl->rgpt[i], TRUE);

        RECTTORECTS(rc, ppl->rc);

        //Insure old version numbers.
        ppl->wVerMaj=1;
        ppl->wVerMin=0;

        ((PPOLYLINEDATA10)ppl)->fDrawEntire=TRUE;
        return POLYLINE_E_NONE;
        }

    return POLYLINE_E_UNSUPPORTEDVERSION;
    }

/*
 * CPolyline::DataSetMem
 *
 * Purpose:
 *  Sets the Polyline's data using a global memory handle
 *  instead of a pointer.
 *
 * Parameters:
 *  hMem            HGLOBAL containing the data.
 *  fFree           BOOL indicating if we're to free the data.
 *                  The memory will be freed regardless of any
 *                  error returned from here.
 *  fSizeToData     BOOL indicating if we're to size to the data
 *                  or scale it.
 *  fNotify         BOOL indicating if we're to send an advise
 *                  on this change.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataSetMem(HGLOBAL hMem, BOOL fFree
    , BOOL fSizeToData, BOOL fNotify)
    {
    PPOLYLINEDATA   ppl;
    LONG            lRet=POLYLINE_E_INVALIDPOINTER;

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        lRet=DataSet(ppl, fSizeToData, fNotify);

        GlobalUnlock(hMem);

        if (fFree)
            GlobalFree(hMem);
        }

    return lRet;
    }

/*
 * CPolyline::DataGetMem
 *
 * Purpose:
 *  Retrieves the Polyline's data in a global memory handle.
 *
 * Parameters:
 *  lVer            LONG version of data to retrieve.
 *  phMem           HGLOBAL * in which to store the handle.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataGetMem(LONG lVer, HGLOBAL *phMem)
    {
    HGLOBAL         hMem;
    PPOLYLINEDATA   ppl;
    LONG            lRet;

    if (NULL==phMem)
        return POLYLINE_E_INVALIDPOINTER;

    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
        , CBPOLYLINEDATA);

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        lRet=DataGet(ppl, lVer);
        GlobalUnlock(hMem);

        if (POLYLINE_E_NONE!=lRet)
            {
            GlobalFree(hMem);
            hMem=NULL;
            }
        }

    *phMem=hMem;
    return lRet;
    }

/*
 * CPolyline::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectGet(LPRECT pRect)
    {
    RECT        rc;
    POINT       pt;

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_hWnd, &rc);
    pt.x=rc.left;
    pt.y=rc.top;
    ScreenToClient(GetParent(m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return;
    }

/*
 * CPolyline::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  None
 */

void CPolyline::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return;
    }

/*
 * CPolyline::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;
    RECT        rc;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, pRect->left, pRect->top
        , cx, cy, SWP_NOZORDER);

    SetRect(&rc, 0, 0, cx, cy);
    RECTTORECTS(rc, m_pl.rc);

    if (fNotify && NULL!=m_pAdv)
        m_pAdv->OnSizeChange();

    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }

/*
 * CPolyline::SizeSet
 *
 * Purpose:
 *  Sets a new size for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPolyline::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
        , SWP_NOMOVE | SWP_NOZORDER);

    if (fNotify && NULL!=m_pAdv)
        m_pAdv->OnSizeChange();

    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }

/*
 * CPolyline::ColorSet
 *
 * Purpose:
 *  Changes for background or line color in the Polyline
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color to use.
 *
 * Return Value:
 *  COLORREF        Previous color for the index iColor.
 */

COLORREF CPolyline::ColorSet(UINT iColor, COLORREF cr)
    {
    COLORREF    crRet;

    switch (iColor)
        {
        case POLYLINECOLOR_BACKGROUND:
            crRet=m_pl.rgbBackground;
            m_pl.rgbBackground=cr;
            break;

        case POLYLINECOLOR_LINE:
            crRet=m_pl.rgbLine;
            m_pl.rgbLine=cr;
            break;
        }

    //If the color changed, repaint
    if (crRet!=cr)
        {
        if (NULL!=m_pAdv)
            m_pAdv->OnColorChange();

        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    return crRet;
    }

/*
 * CPolyline::ColorGet
 *
 * Purpose:
 *  Retrieves one of the colors currently in use by the Polyline.
 *
 * Parameters:
 *  iColor          UINT identifying the color of interest.
 *
 * Return Value:
 *  COLORREF        Current color for iColor.
 */

COLORREF CPolyline::ColorGet(UINT iColor)
    {
    COLORREF    crRet;

    crRet=(POLYLINECOLOR_BACKGROUND==iColor)
        ? m_pl.rgbBackground : m_pl.rgbLine;

    return crRet;
    }

/*
 * CPolyline::LineStyleSet
 *
 * Purpose:
 *  Changes the line style in use by the Polyline
 *
 * Parameters:
 *  iStyle          UINT style of the line to use.
 *
 * Return Value:
 *  UINT            Previous style.
 */

UINT CPolyline::LineStyleSet(UINT iStyle)
    {
    UINT        uRet=(UINT)m_pl.iLineStyle;

    //Validate the line style
    if (PS_SOLID==iStyle || PS_DASH==iStyle || PS_DOT==iStyle
        || PS_DASHDOT==iStyle || PS_DASHDOTDOT==iStyle)
        {
        m_pl.iLineStyle=iStyle;

        if (uRet!=(UINT)m_pl.iLineStyle)
            {
            if (NULL!=m_pAdv)
                m_pAdv->OnLineStyleChange();

            InvalidateRect(m_hWnd, NULL, TRUE);
            UpdateWindow(m_hWnd);
            }
        }

    return uRet;
    }

/*
 * CPolyline::LineStyleGet
 *
 * Purpose:
 *  Retrieves the current line style in use in the Polyline
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Current line style.
 */

UINT CPolyline::LineStyleGet(void)
    {
    return m_pl.iLineStyle;
    }

/*
 * CPolyline::RenderBitmap
 *
 * Purpose:
 *  Creates a bitmap image of the current Polyline.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HBITMAP         Handle to the newly rendered bitmap.
 */

HBITMAP CPolyline::RenderBitmap(void)
    {
    HDC         hDC;
    HDC         hMemDC;
    HBITMAP     hBmp;
    RECT        rc;
    HGDIOBJ     hObj;

    //Render a bitmap the size of the current rectangle.
    hDC=GetDC(m_hWnd);
    hMemDC=CreateCompatibleDC(hDC);

    GetClientRect(m_hWnd, &rc);
    hBmp=CreateCompatibleBitmap(hDC, rc.right, rc.bottom);

    if (NULL!=hBmp)
        {
        //Draw the POLYLINEDATA into the bitmap.
        hObj=SelectObject(hMemDC, hBmp);
        Draw(hMemDC, FALSE, TRUE);
        SelectObject(hMemDC, hObj);
        }

    DeleteDC(hMemDC);
    ReleaseDC(m_hWnd, hDC);

    return hBmp;
    }

/*
 * CPolyline::RenderMetafile
 *
 * Purpose:
 *  Renders the current image of the Polyline into a metafile.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HMETAFILE       Handle to the newly created metafile.
 */

HMETAFILE CPolyline::RenderMetafile(void)
    {
    HDC         hDC;
    HMETAFILE   hMF;
    RECT        rc;

    //Create a memory metafile and return its handle.
    hDC=(HDC)CreateMetaFile(NULL);
    hMF=NULL;

    if (NULL!=hDC)
        {
        /*
         * This is absolutely essential to the metafile so it
         * can be scaled in the clipboard and any destination
         * application.
         */
        SetMapMode(hDC, MM_ANISOTROPIC);
        GetClientRect(m_hWnd, &rc);
        SetWindowOrgEx(hDC, 0, 0, NULL);
        SetWindowExtEx(hDC, rc.right, rc.bottom, NULL);

        Draw(hDC, TRUE, TRUE);
        hMF=CloseMetaFile(hDC);
        }

    return hMF;
    }

/*
 * CPolyline::RenderMetafilePict
 *
 * Purpose:
 *  Renders the current Polyline into a METAFILEPICT structure in
 *  global memory.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HGLOBAL         Memory containing the METAFILEPICT structure.
 */

HGLOBAL CPolyline::RenderMetafilePict(void)
    {
    HGLOBAL         hMem;
    HMETAFILE       hMF;
    LPMETAFILEPICT  pMF;
    RECT            rc;

    //Get the metafile
    hMF=RenderMetafile();

    if (NULL==hMF)
        return NULL;

    //Allocate the METAFILEPICT structure.
    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
        , sizeof(METAFILEPICT));

    if (NULL==hMem)
        {
        DeleteMetaFile(hMF);
        return NULL;
        }

    /*
     * Global lock only fails in PMODE if the selector is invalid
     * (like it was discarded) or references a 0 length segment,
     * neither of which can happen here.
     */
    pMF=(LPMETAFILEPICT)GlobalLock(hMem);

    pMF->hMF=hMF;
    pMF->mm=MM_ANISOTROPIC;

    //Insert the extents in MM_HIMETRIC units.
    GetClientRect(m_hWnd, &rc);
    RectConvertMappings(&rc, FALSE);
    pMF->xExt=rc.right;
    pMF->yExt=rc.bottom;

    GlobalUnlock(hMem);
    return hMem;
    }

/*
 * CPolyline::RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device (MM_TEXT) or
 *  HIMETRIC to the other.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  fToDevice       BOOL TRUE to convert from HIMETRIC to device,
 *                  FALSE to convert device to HIMETRIC.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectConvertMappings(LPRECT pRect, BOOL fToDevice)
    {
    HDC      hDC;
    int      iLpx, iLpy;

    if (NULL==pRect)
        return;

    hDC=GetDC(NULL);
    iLpx=GetDeviceCaps(hDC, LOGPIXELSX);
    iLpy=GetDeviceCaps(hDC, LOGPIXELSY);
    ReleaseDC(NULL, hDC);

    if (fToDevice)
        {
        pRect->left=MulDiv(iLpx, pRect->left, HIMETRIC_PER_INCH);
        pRect->top =MulDiv(iLpy, pRect->top , HIMETRIC_PER_INCH);

        pRect->right=MulDiv(iLpx, pRect->right,  HIMETRIC_PER_INCH);
        pRect->bottom=MulDiv(iLpy, pRect->bottom,HIMETRIC_PER_INCH);

       #ifdef NEVER
        /*
         * In this conversion we may get situations where the top
         * coordinate is larger than the bottom, which messes us up.
         */
        if (pRect->bottom < pRect->top)
            {
            iLpy=pRect->top;
            pRect->top=pRect->bottom;
            pRect->bottom=iLpy;
            }
       #endif
        }
    else
        {
        pRect->left=MulDiv(pRect->left, HIMETRIC_PER_INCH, iLpx);
        pRect->top =MulDiv(pRect->top , HIMETRIC_PER_INCH, iLpy);

        pRect->right =MulDiv(pRect->right, HIMETRIC_PER_INCH, iLpx);
        pRect->bottom=MulDiv(pRect->bottom,HIMETRIC_PER_INCH, iLpy);
        }

    return;
    }


POLYLINE.H

/*
 * POLYLINE.H
 * Cosmo Chapter 1
 *
 * Definitions and function prototypes for the PolyLine window
 * class that can be treated like its own control.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _POLYLINE_H_
#define _POLYLINE_H_

//Versioning.
#define VERSIONMAJOR                2
#define VERSIONMINOR                0
#define VERSIONCURRENT              0x00020000

//Classname
#define SZCLASSPOLYLINE             TEXT("polyline")

#define HIMETRIC_PER_INCH           2540
#define CPOLYLINEPOINTS             20

//Window extra bytes and offsets
#define CBPOLYLINEWNDEXTRA          (sizeof(LONG))
#define PLWL_STRUCTURE              0

//Version 2.0 Polyline Structure
typedef struct tagPOLYLINEDATA
    {
    WORD        wVerMaj;                //Major version number.
    WORD        wVerMin;                //Minor version number.
    WORD        cPoints;                //Number of points.
    short       fReserved;              //Obsolete from v1.0
    RECTS       rc;                     //Rectangle of this figure
    POINTS      rgpt[CPOLYLINEPOINTS];  //Points (0-32767 grid)

    //Version 2.0 additions
    COLORREF    rgbBackground;          //Background color
    COLORREF    rgbLine;                //Line color
    short       iLineStyle;             //Line style
    } POLYLINEDATA, *PPOLYLINEDATA;

#define CBPOLYLINEDATA   sizeof(POLYLINEDATA)
#define CBPOLYLINEDATA20 sizeof(POLYLINEDATA)

//Version 1.0 Polyline Structure
typedef struct tagPOLYLINEDATA10
    {
    WORD        wVerMaj;                //Major version number.
    WORD        wVerMin;                //Minor version number.
    WORD        cPoints;                //Number of points.
    short       fDrawEntire;            //Flag to draw entire figure
    RECTS       rc;                     //Rectangle of this figure
    POINTS      rgpt[CPOLYLINEPOINTS];  //Points (scaled to rc)
    } POLYLINEDATA10, *PPOLYLINEDATA10;

#define CBPOLYLINEDATA10 sizeof(POLYLINEDATA10)

//POLYWIN.CPP
LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM, LPARAM);

class CPolyline : public CWindow
    {
    friend LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM
        , LPARAM);

    private:
        POLYLINEDATA   m_pl;

        class CPolylineAdviseSink * m_pAdv;

    private:
        HFILE     OpenFileW(LPTSTR, LPOFSTRUCT, UINT);
        void      PointScale(LPRECT, LPPOINTS, BOOL);
        void      Draw(HDC, BOOL, BOOL);
        void      RectConvertMappings(LPRECT, BOOL);

    public:
        CPolyline(HINSTANCE);
        ~CPolyline(void);

        BOOL      Init(HWND, LPRECT, DWORD, UINT
            , class CPolylineAdviseSink *);

        void      New(void);
        BOOL      Undo(void);

        //File functions
        LONG      ReadFromFile(LPTSTR);
        LONG      WriteToFile(LPTSTR, LONG);

        //Data transfer functions
        LONG      DataSet(PPOLYLINEDATA, BOOL, BOOL);
        LONG      DataGet(PPOLYLINEDATA, LONG);
        LONG      DataSetMem(HGLOBAL, BOOL, BOOL, BOOL);
        LONG      DataGetMem(LONG, HGLOBAL *);
        HBITMAP   RenderBitmap(void);
        HMETAFILE RenderMetafile(void);
        HGLOBAL   RenderMetafilePict(void);

        void      RectGet(LPRECT);
        void      SizeGet(LPRECT);
        void      RectSet(LPRECT, BOOL);
        void      SizeSet(LPRECT, BOOL);
        COLORREF  ColorSet(UINT, COLORREF);
        COLORREF  ColorGet(UINT);
        UINT      LineStyleSet(UINT);
        UINT      LineStyleGet(void);
    };

typedef CPolyline *PCPolyline;

//Error values for data transfer functions
#define POLYLINE_E_NONE                    0
#define POLYLINE_E_UNSUPPORTEDVERSION      -1
#define POLYLINE_E_INVALIDPOINTER          -2
#define POLYLINE_E_READFAILURE             -3
#define POLYLINE_E_WRITEFAILURE            -4

class CPolylineAdviseSink
    {
    private:
        LPVOID      m_pv;           //Customizable structure

    public:
        CPolylineAdviseSink(LPVOID);
        ~CPolylineAdviseSink(void);

        void OnPointChange(void);
        void OnSizeChange(void);
        void OnDataChange(void);
        void OnColorChange(void);
        void OnLineStyleChange(void);
    };

typedef CPolylineAdviseSink *PCPolylineAdviseSink;

//Color indices for color messages
#define POLYLINECOLOR_BACKGROUND    0
#define POLYLINECOLOR_LINE          1

#endif  //_POLYLINE_H_


POLYWIN.CPP

/*
 * POLYWIN.CPP
 * Cosmo Chapter 1
 *
 * Window procedure for the polyline drawing window and support
 * functions.  This window is not complicated.  On creation it
 * allocates a block of memory for a POLYLINEDATA structure that
 * contains 20 POINTs.  We do not attempt to reallocate this
 * array at all just to maintain simplicity.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * PolylineWndProc
 *
 * Purpose:
 *  Window procedure for the polyline drawing window.
 */

LRESULT APIENTRY PolylineWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCPolyline      ppl;
    PAINTSTRUCT     ps;
    HDC             hDC;
    POINTS          pt;
    RECT            rc;

    ppl=(PCPolyline)GetWindowLong(hWnd, PLWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppl=(PCPolyline)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PLWL_STRUCTURE, (LONG)ppl);
            ppl->m_hWnd=hWnd;
            ppl->New();
            break;

        case WM_PAINT:
            hDC=BeginPaint(hWnd, &ps);
            ppl->Draw(hDC, FALSE, TRUE);
            EndPaint(hWnd, &ps);
            break;

        case WM_LBUTTONDOWN:
            //Stop if we are already at the limit.
            if (CPOLYLINEPOINTS==ppl->m_pl.cPoints)
                {
                MessageBeep(0);
                break;
                }

            //Convert the points into 0-32767 range
            GetClientRect(hWnd, &rc);
            pt=MAKEPOINTS(lParam);
            ppl->PointScale(&rc, &pt, FALSE);

            ppl->m_pl.rgpt[ppl->m_pl.cPoints++]=pt;

            //Draw the lines to this new point only.
            hDC=GetDC(hWnd);
            ppl->Draw(hDC, FALSE, FALSE);
            ReleaseDC(hWnd, hDC);

            if (NULL!=ppl->m_pAdv)
                ppl->m_pAdv->OnPointChange();

            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPolyline::Draw
 *
 * Purpose:
 *  Paints the current line in the polyline window.
 *
 * Parameters:
 *  hDC             HDC to draw on, could be metafile or printer DC.
 *  fMetafile       BOOL indicating if hDC is a metafile or not, so
 *                  we can avoid operations that RIP.
 *  fEntire         BOOL indicating if we should draw the entire
 *                  figure or not.
 *
 * Return Value:
 *  None
 */

void CPolyline::Draw(HDC hDC, BOOL fMetafile, BOOL fEntire)
    {
    HBRUSH          hBrush;
    HPEN            hPen;
    HGDIOBJ         hObj1, hObj2;
    UINT            i, j;
    UINT            uMM;
    POINTS          pt1, pt2;
    POINT           rgpt[CPOLYLINEPOINTS];
    RECT            rc;

    GetClientRect(m_hWnd, &rc);

    /*
     * Make a 32-bit copy of the points in the POLYLINE itself
     * so we can use DPtoLP and LPtoDP.
     */
    for (i=0; i < m_pl.cPoints; i++)
        {
        rgpt[i].x=m_pl.rgpt[i].x;
        rgpt[i].y=m_pl.rgpt[i].y;
        }

    /*
     * If the mapping mode is not MM_TEXT, convert the points to
     * whatever mapping mode in in effect before drawing.
     * This specifically supports metafiles in MM_ANISOTROPIC.
     */
    uMM=fMetafile ? MM_TEXT : GetMapMode(hDC);

    if (MM_TEXT!=uMM)
        DPtoLP(hDC, rgpt, m_pl.cPoints);

    hPen=CreatePen(m_pl.iLineStyle, 1, m_pl.rgbLine);
    hObj1=SelectObject(hDC, hPen);

    hBrush=CreateSolidBrush(m_pl.rgbBackground);
    hObj2=SelectObject(hDC, hBrush);
    SetBkColor(hDC, m_pl.rgbBackground);

    /*
     * Either draw the entire figure or just a single point.  The
     * entire figure also includes erasing the background
     * completely,since hDC may be a metafile DC.  Drawing a
     * single point just updates the figure for that new point.
     */
    if (fEntire || 0==m_pl.cPoints)
        {
        //Erase the background for bitmaps and metafiles.
        SelectObject(hDC, GetStockObject(NULL_PEN));
        Rectangle(hDC, rc.left, rc.top, rc.right+1, rc.bottom+1);
        SelectObject(hDC, hPen);

        /*
         * If we are drawing the entire figure, then loop through
         * each point drawing a line to each successive point.
         */

        for (i=0; i < m_pl.cPoints; i++)
            {
            for (j=i; j < m_pl.cPoints; j++)
                {
                /*
                 * Not using POINTTOPOINTS since it unfortunately
                 * returns a LONG which doesn't equate to a POINTS.
                 */
                pt1.x=(short)rgpt[i].x;
                pt1.y=(short)rgpt[i].y;
                pt2.x=(short)rgpt[j].x;
                pt2.y=(short)rgpt[j].y;

                PointScale(&rc, &pt1, TRUE);
                PointScale(&rc, &pt2, TRUE);
                MoveToEx(hDC, pt1.x, pt1.y, NULL);
                LineTo(hDC, pt2.x, pt2.y);
                }
            }
        }
    else
        {
        /*
         * If we are only drawing the last point, just cycle once
         * through previous points.
         */

        //Get the last point entered in the array.
        j=m_pl.cPoints-1;
        pt1.x=(short)rgpt[j].x;
        pt1.y=(short)rgpt[j].y;
        PointScale(&rc, &pt1, TRUE);

        for (i=0; i < j; i++)
            {
            pt2.x=(short)rgpt[i].x;
            pt2.y=(short)rgpt[i].y;
            PointScale(&rc, &pt2, TRUE);

            MoveToEx(hDC, pt1.x, pt1.y, NULL);
            LineTo(hDC, pt2.x, pt2.y);
            }
        }

    //If we have one point, draw a dot to indicate it's position.
    if (1==m_pl.cPoints)
        {
        pt1.x=(short)rgpt[0].x;
        pt1.y=(short)rgpt[0].y;
        PointScale(&rc, &pt1, TRUE);
        SetPixel(hDC, pt1.x, pt1.y, m_pl.rgbLine);
        }

    //Note:  m_pl.rgpt is unmodified in this code.

    SelectObject(hDC, hObj1);
    SelectObject(hDC, hObj2);
    DeleteObject(hBrush);
    DeleteObject(hPen);
    return;
    }

/*
 * CPolyline::PointScale
 *
 * Purpose:
 *  Scales a point to or from a relative window coordinate to a
 *  0-32767 coordinate.
 *
 * Parameters:
 *  pRect           LPRECT of the window.
 *  ppt             LPPOINTS to convert
 *  fScaleToWindow  BOOL indicating direction of scaling.
 *
 * Return Value:
 *  None
 */

void CPolyline::PointScale(LPRECT pRect, LPPOINTS ppt
    , BOOL fScaleToWindow)
    {
    DWORD   cx, cy;

    //Window size
    cx=(DWORD)(pRect->right-pRect->left);
    cy=(DWORD)(pRect->bottom-pRect->top);

    //Prevent crashes
    if (0L==cx) cx=1;
    if (0L==cy) cy=1;

    //Must use DWORD to insure proper scaling.
    if (fScaleToWindow)
        {
        ppt->x=(UINT)(((DWORD)ppt->x*cx) >> 15);
        ppt->y=(UINT)(((DWORD)ppt->y*cy) >> 15);
        }
    else
        {
        ppt->x=(UINT)(((DWORD)ppt->x << 15)/cx);
        ppt->y=(UINT)(((DWORD)ppt->y << 15)/cy);
        }

    return;
    }


PRECOMP.CPP

#include "cosmo.h"


RESOURCE.H

/*
 * RESOURCE.H
 * Cosmo Chapter 1
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Note that we augment classlib.h which we include before this.

//Position of Window menu.
#undef  WINDOW_MENU
#define WINDOW_MENU             4

//Total number of menus
#undef  CMENUS
#ifdef MDI
#define CMENUS                  6
#else
#define CMENUS                  5
#endif

//Menu command identifiers.

#define IDM_FILEIMPORT          (IDM_CUSTOMFILEMIN)

#define IDM_COLORBACKGROUND     (IDM_CUSTOMMIN)
#define IDM_COLORLINE           (IDM_CUSTOMMIN+1)

#define IDM_LINEMIN             (IDM_CUSTOMMIN+100)
#define IDM_LINESOLID           (IDM_LINEMIN+0)         //PS_SOLID       0
#define IDM_LINEDASH            (IDM_LINEMIN+1)         //PS_DASH        1
#define IDM_LINEDOT             (IDM_LINEMIN+2)         //PS_DOT         2
#define IDM_LINEDASHDOT         (IDM_LINEMIN+3)         //PS_DASHDOT     3
#define IDM_LINEDASHDOTDOT      (IDM_LINEMIN+4)         //PS_DASHDOTDOT  4
#define IDM_LINEMAX             IDM_LINEDASHDOTDOT

/*
 * IDs for StatStrip handling of popup menus:  must be in order of menu
 * CLASSRES.H already defines ID_MENUFILE and ID_MENUEDIT for us.
 */

#undef  ID_MENUWINDOW
#undef  ID_MENUHELP

#define ID_MENUCOLOR            (ID_MENUCUSTOMMIN+0)
#define ID_MENULINE             (ID_MENUCUSTOMMIN+1)
#define ID_MENUWINDOW           (ID_MENUCUSTOMMIN+2)
#define ID_MENUHELP             (ID_MENUCUSTOMMIN+3)

//Stringtable IDs.  Keep sequential for each group.
#define IDS_FRAMEMIN            IDS_STANDARDFRAMEMIN
#define IDS_FILEIMPORT          (IDS_CUSTOMFRAMEMIN+0)
#define IDS_FRAMEMAX            (IDS_CUSTOMFRAMEMIN+0)

#define IDS_DOCUMENTMIN         IDS_STANDARDDOCMIN
#define IDS_VERSIONCHANGE       (IDS_CUSTOMDOCMIN+0)
#define IDS_DOCUMENTMAX         (IDS_CUSTOMDOCMIN+0)

//These are specifically for the StatStrip
#define IDS_STATMESSAGEMIN                  IDS_STANDARDSTATMESSAGEMIN

#define IDS_ITEMMESSAGEFILEIMPORT           (IDS_CUSTOMSTATMESSAGEMIN+0)

#define IDS_MENUMESSAGECOLOR                (IDS_CUSTOMSTATMESSAGEMIN+1)
#define IDS_ITEMMESSAGECOLORBACKGROUND      (IDS_CUSTOMSTATMESSAGEMIN+2)
#define IDS_ITEMMESSAGECOLORLINE            (IDS_CUSTOMSTATMESSAGEMIN+3)

#define IDS_MENUMESSAGELINE                 (IDS_CUSTOMSTATMESSAGEMIN+4)
#define IDS_ITEMMESSAGELINESOLID            (IDS_CUSTOMSTATMESSAGEMIN+5)
#define IDS_ITEMMESSAGELINEDASH             (IDS_CUSTOMSTATMESSAGEMIN+6)
#define IDS_ITEMMESSAGELINEDOT              (IDS_CUSTOMSTATMESSAGEMIN+7)
#define IDS_ITEMMESSAGELINEDASHDOT          (IDS_CUSTOMSTATMESSAGEMIN+8)
#define IDS_ITEMMESSAGELINEDASHDOTDOT       (IDS_CUSTOMSTATMESSAGEMIN+9)

#define IDS_STATMESSAGEMAX                  (IDS_CUSTOMSTATMESSAGEMIN+9)

#endif //_RESOURCE_H_


CLIENT.CPP

/*
 * CLIENT.CPP
 * Patron Chapter 1
 *
 * Implementation of the CPatronClient class that just makes sure
 * we get a CPatronDoc on doc creation and that we initialize fully.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPatronClient::CPatronClient
 * CPatronClient::~CPatronClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame to the frame object.
 */

CPatronClient::CPatronClient(HINSTANCE hInst, PCFrame pFR)
    : CClient(hInst, pFR)
    {
    return;
    }

CPatronClient::~CPatronClient(void)
    {
    return;
    }

/*
 * CPatronClient::CreateCDocument
 *
 * Purpose:
 *  Constructs a new document specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CPatronClient::CreateCDocument(void)
    {
    return (PCDocument)(new CPatronDoc(m_hInst, m_pFR, m_pAdv));
    }


DOCUMENT.CPP

/*
 * DOCUMENT.CPP
 * Patron Chapter 1
 *
 * Implementation of the CPatronDoc derivation of CDocument that
 * manages pages for us.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"
#include <memory.h>
#include <dlgs.h>       //Pring Dlg button IDs

/*
 * CPatronDoc::CPatronDoc
 * CPatronDoc::~CPatronDoc
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame of the frame object.
 *  pAdv            PCDocumentAdviseSink to notify on events.
 */

CPatronDoc::CPatronDoc(HINSTANCE hInst, PCFrame pFR
    , PCDocumentAdviseSink pAdv)
    : CDocument(hInst, pFR, pAdv)
    {
    m_pPG=NULL;
    m_lVer=VERSIONCURRENT;
    return;
    }

CPatronDoc::~CPatronDoc(void)
    {
    if (NULL!=m_pPG)
        delete m_pPG;

    return;
    }

/*
 * CPatronDoc::Init
 *
 * Purpose:
 *  Initializes an already created document window.  The client
 *  actually creates the window for us, then passes that here for
 *  further initialization.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPatronDoc::Init(PDOCUMENTINIT pDI)
    {
    //Change the stringtable range to our customization.
    pDI->idsMin=IDS_DOCUMENTMIN;
    pDI->idsMax=IDS_DOCUMENTMAX;

    //Do default initialization
    if (!CDocument::Init(pDI))
        return FALSE;

    //Pages are created when we get a Load later.
    return TRUE;
    }

/*
 * CPatronDoc::FMessageHook
 *
 * Purpose:
 *  Processes WM_SIZE for the document so we can resize the Pages
 *  window.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CPatronDoc::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    UINT        dx, dy;
    RECT        rc;

    *pLRes=0;

    //Eat to prevent flickering
    if (WM_ERASEBKGND==iMsg)
        return TRUE;

    if (WM_SIZE==iMsg && NULL!=m_pPG)
        {
        dx=LOWORD(lParam);
        dy=HIWORD(lParam);

        if (SIZE_MINIMIZED!=wParam)
            {
            //Resize Pages window to fit the new document size.
            GetClientRect(hWnd, &rc);
            m_pPG->RectSet(&rc, FALSE);
            }
        }

    /*
     * We return FALSE even on WM_SIZE so we can let the default
     * procedure handle maximized MDI child windows appropriately.
     */
    return FALSE;
    }

/*
 * CPatronDoc::Clear
 *
 * Purpose:
 *  Sets all contents in the document back to defaults with no
 *  filename.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Clear(void)
    {
    //Completely reset the pages
    m_pPG->New();

    CDocument::Clear();
    m_lVer=VERSIONCURRENT;
    return;
    }

/*
 * CPatronDoc::Load
 *
 * Purpose:
 *  Loads a given document without any user interface overwriting
 *  the previous contents of the editor.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load.  Could be NULL
 *                  for an untitled document.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CPatronDoc::Load(BOOL fChangeFile, LPTSTR pszFile)
    {
    RECT        rc;

    //We don't support opening anything yet.
    if (NULL!=pszFile)
        return DOCERR_NONE;

    //Attempt to create our contained Pages window.
    m_pPG=new CPages(m_hInst);
    GetClientRect(m_hWnd, &rc);

    if (!m_pPG->Init(m_hWnd, &rc, WS_CHILD | WS_VISIBLE
        , ID_PAGES, NULL))
        return DOCERR_NOFILE;

    //Go initialize the Pages for the default printer.
    if (!PrinterSetup(NULL, TRUE))
        return DOCERR_COULDNOTOPEN;

    Rename(NULL);

    //Go create an initial page.
    m_pPG->PageInsert(0);

    FDirtySet(FALSE);
    return DOCERR_NONE;
    }

/*
 * CPatronDoc::NewPage
 *
 * Purpose:
 *  Creates a new page in the document's pages control after the
 *  current page.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::NewPage(void)
    {
    FDirtySet(TRUE);
    return m_pPG->PageInsert(0);
    }

/*
 * CPatronDoc::DeletePage
 *
 * Purpose:
 *  Deletes the current page from the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the now current page.
 */

UINT CPatronDoc::DeletePage(void)
    {
    FDirtySet(TRUE);
    return m_pPG->PageDelete(0);
    }

/*
 * CPatronDoc::NextPage
 *
 * Purpose:
 *  Shows the next page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::NextPage(void)
    {
    UINT        iPage;

    iPage=m_pPG->CurPageGet();
    return m_pPG->CurPageSet(++iPage);
    }

/*
 * CPatronDoc::PreviousPage
 *
 * Purpose:
 *  Shows the previous page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::PreviousPage(void)
    {
    UINT        iPage;

    //If iPage is zero, then we wrap around to the end.
    iPage=m_pPG->CurPageGet();
    return m_pPG->CurPageSet(--iPage);
    }

/*
 * CPatronDoc::FirstPage
 *
 * Purpose:
 *  Shows the first page page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::FirstPage(void)
    {
    return m_pPG->CurPageSet(0);
    }

/*
 * CPatronDoc::LastPage
 *
 * Purpose:
 *  Shows the last page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the last page.
 */

UINT CPatronDoc::LastPage(void)
    {
    return m_pPG->CurPageSet(NOVALUE);
    }


PAGE.CPP

/*
 * PAGE.CPP
 * Patron Chapter 1
 *
 * Implementation of the CPage class to manage a single page.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPage::CPage
 * CPage::~CPage
 *
 * Constructor Parameters:
 *  dwID            DWORD identifier for this page.
 */

CPage::CPage(DWORD dwID)
    {
    m_dwID=dwID;
    return;
    }

CPage::~CPage(void)
    {
    return;
    }

/*
 * CPage::GetID
 *
 * Return Value:
 *  DWORD           dwID field in this page.
 */

DWORD CPage::GetID(void)
    {
    return m_dwID;
    }


PAGES.CPP

/*
 * PAGES.CPP
 * Patron Chapter 1
 *
 * Implementation of the CPages class.  See PAGEWIN.CPP and PRINT.CPP
 * for additional member functions.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPages:CPages
 * CPages::~CPages
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application we're in.
 */

CPages::CPages(HINSTANCE hInst)
    : CWindow(hInst)
    {
    m_iPageCur=NOVALUE;  //Pages are 0 indexed, this is one before
    m_cPages=0;
    m_hWndPageList=NULL;
    m_hFont=NULL;
    m_fSystemFont=FALSE;

    //Initialize to 8.5*11 inch with .25 inch margins as a default.
    m_cx=(LOMETRIC_PER_INCH*17)/2;
    m_cy=LOMETRIC_PER_INCH*11;

    m_xMarginLeft=LOMETRIC_PER_INCH/4;
    m_xMarginRight=LOMETRIC_PER_INCH/4;
    m_yMarginTop=LOMETRIC_PER_INCH/4;
    m_yMarginBottom=LOMETRIC_PER_INCH/4;

    m_xPos=0L;
    m_yPos=0L;

    m_dwIDNext=0;
    m_hDevMode=NULL;
    return;
    }

CPages::~CPages(void)
    {
    //Ensure memory is cleaned up in the list
    New();

    if (NULL!=m_hFont && !m_fSystemFont)
        DeleteObject(m_hFont);

    if (NULL!=m_hWndPageList)
        DestroyWindow(m_hWndPageList);

    return;
    }

/*
 * CPages::Init
 *
 * Purpose:
 *  Instantiates a pages window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags.
 *                  Should contain WS_CHILD | WS_VISIBLE in
 *                  typical circumstances.
 *  uID             UINT ID to associate with this window
 *  pv              LPVOID unused for now.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPages::Init(HWND hWndParent, LPRECT pRect, DWORD dwStyle
    , UINT uID, LPVOID pv)
    {
    int     cy;

    m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY, SZCLASSPAGES
        , SZCLASSPAGES, dwStyle, pRect->left, pRect->top
        , pRect->right-pRect->left, pRect->bottom-pRect->top
        , hWndParent, (HMENU)uID, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    /*
     * Create the hidden listbox we'll use to track pages.  We give
     * it the owner-draw style so we can just store pointers in it.
     */
    m_hWndPageList=CreateWindow(TEXT("listbox"), TEXT("Page List")
        , WS_POPUP | LBS_OWNERDRAWFIXED, 0, 0, 100, 100
        , HWND_DESKTOP, NULL, m_hInst, NULL);

    if (NULL==m_hWndPageList)
        return FALSE;

    //Create a 14 point Arial font, or use the system variable font.
    cy=MulDiv(-14, LOMETRIC_PER_INCH, 72);
    m_hFont=CreateFont(cy, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE
        , ANSI_CHARSET, OUT_TT_PRECIS, CLIP_TT_ALWAYS, PROOF_QUALITY
        , VARIABLE_PITCH | FF_SWISS, TEXT("Arial"));

    if (NULL==m_hFont)
        {
        m_hFont=(HFONT)GetStockObject(ANSI_VAR_FONT);
        m_fSystemFont=TRUE;
        }

    return TRUE;
    }

/*
 * CPages::New
 *
 * Purpose:
 *  Cleans out and reinitializes the data to defaults.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPages::New(void)
    {
    PCPage      pPage;
    UINT        i;

    //First walk the page list and clean out the allocations.
    for (i=0; i < m_cPages; i++)
        {
        if (PageGet(i, &pPage, FALSE))
            delete pPage;
        }

    if (NULL!=m_hWndPageList)
        SendMessage(m_hWndPageList, LB_RESETCONTENT, 0, 0L);

    if (NULL!=m_hDevMode)
        GlobalFree(m_hDevMode);

    return;
    }

/*
 * CPages::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Pages window in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  None
 */

void CPages::RectGet(LPRECT pRect)
    {
    RECT        rc;
    POINT       pt;

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_hWnd, &rc);
    pt.x=rc.left;
    pt.y=rc.top;
    ScreenToClient(GetParent(m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return;
    }

/*
 * CPages::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Pages window which sizes to fit.
 *  Coordinates are given in parent terms.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPages::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    if (NULL==pRect)
        return;

    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, pRect->left, pRect->top
        , (UINT)cx, (UINT)cy, SWP_NOZORDER);

    UpdateScrollRanges();
    return;
    }

/*
 * CPages::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the pages window in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  None
 */

void CPages::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return;
    }

/*
 * CPages::SizeSet
 *
 * Purpose:
 *  Sets a new size in parent coordinates for the Pages window.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPages::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    if (NULL==pRect)
        return;

    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
        , SWP_NOMOVE | SWP_NOZORDER);

    UpdateScrollRanges();
    return;
    }

/*
 * CPages::ActivePage
 *
 * Purpose:
 *  Returns a CPage pointer to the current page.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCPage          Pointer to the current page.
 */

PCPage CPages::ActivePage(void)
    {
    PCPage      pPage;
    BOOL        fRet;

    fRet=PageGet(m_iPageCur, &pPage, FALSE);
    return fRet ? pPage : NULL;
    }

/*
 * CPages::PageInsert
 *
 * Purpose:
 *  Creates a new page immediately after the current page.  If
 *  there are no pages then this creates page 1.
 *
 * Parameters:
 *  uReserved       UINT unused
 *
 * Return Value:
 *  UINT            Index of the new page, 0 on failure.
 */

UINT CPages::PageInsert(UINT uReserved)
    {
    //Create and open the new page.
    if (!PageAdd(m_iPageCur, m_dwIDNext, TRUE))
        return 0;

    m_dwIDNext++;
    m_iPageCur++;
    m_cPages++;

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);
    return m_iPageCur;
    }

/*
 * CPages::PageDelete
 *
 * Removes the current page from the page list.
 *
 * Parameters:
 *  uReserved       UINT unused
 *
 * Return Value:
 *  UINT            Index to the now current page from the page
 *                  list, NOVALUE on error.
 */

UINT CPages::PageDelete(UINT uReserved)
    {
    PCPage      pPage;

    if (!PageGet(m_iPageCur, &pPage, FALSE))
        return NOVALUE;

    //Delete the page in both the listbox and in memory.
    SendMessage(m_hWndPageList, LB_DELETESTRING, m_iPageCur, 0L);
    delete pPage;

    /*
     * If this is the last page then the current is one less.  If
     * it's the only page the current is zero.  Otherwise the
     * current is the next page.
     */

    if (m_iPageCur==m_cPages-1)   //Covers last or only page.
        m_iPageCur--;

    m_cPages--;

    if (0!=m_cPages)
        InvalidateRect(m_hWnd, NULL, FALSE);
    else
        InvalidateRect(m_hWnd, NULL, TRUE);

    UpdateWindow(m_hWnd);
    return m_iPageCur;
    }

/*
 * CPages::CurPageGet
 *
 * Purpose:
 *  Retrieves the index of the current page we're viewing.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the current page.
 */

UINT CPages::CurPageGet(void)
    {
    return m_iPageCur;
    }

/*
 * CPages::CurPageSet
 *
 * Purpose:
 *  Sets the index of the current page to view.
 *
 * Parameters:
 *  iPage           UINT index of the page to view. 0 means first
 *                  page, NOVALUE means last page.
 *
 * Return Value:
 *  UINT            Index of the previous current page, NOVALUE on
 *                  error.
 */

UINT CPages::CurPageSet(UINT iPage)
    {
    UINT    iPageNew;
    UINT    iPagePrev=m_iPageCur;

    switch (iPage)
        {
        case 0:
            iPageNew=0;
            break;

        case NOVALUE:
            iPageNew=m_cPages-1;
            break;

        default:
            if (iPage >= m_cPages)
                iPage=0;

            iPageNew=iPage;
            break;
        }

    //No reason to switch to the same page.
    if (iPagePrev==iPageNew)
        return iPage;

    m_iPageCur=iPageNew;

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);
    return iPagePrev;
    }

/*
 * CPages::NumPagesGet
 *
 * Purpose:
 *  Returns the number of pages this object current contains.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of pages we contain.
 */

UINT CPages::NumPagesGet(void)
    {
    return m_cPages;
    }

/*
 * CPages::PageGet
 * (Protected)
 *
 * Purpose:
 *  Returns a page of a given index returning a BOOL so it's simple
 *  to use this function inside if statements.
 *
 * Parameters:
 *  iPage           UINT page to retrieve.
 *  ppPage          PCPage * in which to return the page
 *                  pointer
 *  fReserved       BOOL reserved for future use.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::PageGet(UINT iPage, PCPage *ppPage
    , BOOL fReserved)
    {
    if (NULL==ppPage)
        return FALSE;

    if (LB_ERR!=SendMessage(m_hWndPageList, LB_GETTEXT
        , iPage, (LONG)ppPage))
        return TRUE;

    return FALSE;
    }

/*
 * CPages::PageAdd
 * (Protected)
 *
 * Purpose:
 *  Creates a new page initialized to the given values.
 *
 * Parameters:
 *  iPage           UINT Location at which to insert page; new page
 *                  is inserted after this position.  NOVALUE for
 *                  the end.
 *  cObjects        UINT number of objects on this page.
 *  dwID            DWORD ID for this page.
 *  fReserved       BOOL reserved for future use.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPages::PageAdd(UINT iPage, DWORD dwID, BOOL fReserved)
    {
    PCPage      pPage;
    LRESULT     lr;

    pPage=new CPage(dwID);

    if (NULL==pPage)
        return FALSE;

    if (NOVALUE==iPage)
        iPage--;

    //Now try to add to the listbox.
    lr=SendMessage(m_hWndPageList, LB_INSERTSTRING, iPage+1
        , (LONG)pPage);

    if (LB_ERRSPACE==lr)
        {
        delete pPage;
        return FALSE;
        }

    return TRUE;
    }


PAGES.H

/*
 * PAGES.H
 * Patron Chapter 1
 *
 * Definitions and function prototypes for the Pages window control.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _PAGES_H_
#define _PAGES_H_

//Versioning.
#define VERSIONMAJOR                2
#define VERSIONMINOR                0
#define VERSIONCURRENT              0x00020000

//Classname
#define SZCLASSPAGES                TEXT("pages")

#define HIMETRIC_PER_INCH           2540
#define LOMETRIC_PER_INCH           254
#define LOMETRIC_BORDER             60          //Border around page

//Window extra bytes and offsets
#define CBPAGESWNDEXTRA             (sizeof(LONG))
#define PAGEWL_STRUCTURE            0

/*
 * Page class describing an individual page and what things it
 * contains.
 *
 * A DWORD is used to identify this page as the name of the storage
 * is the string form of this ID.  If we added a page every second,
 * it would take 136 years to overrun this counter, so we can
 * get away with saving it persistently.  I hope this software is
 * obsolete by then.
 */

class CPage
    {
    private:
        DWORD       m_dwID;             //Persistent identifier

    public:
        CPage(DWORD);
        ~CPage(void);

        DWORD           GetID(void);
    };

typedef CPage *PCPage;

//PRINT.CPP
BOOL    APIENTRY AbortProc(HDC, int);
BOOL    APIENTRY PrintDlgProc(HWND, UINT, WPARAM, LPARAM);

//PAGEWIN.CPP
LRESULT APIENTRY PagesWndProc(HWND, UINT, WPARAM, LPARAM);
void             RectConvertMappings(LPRECT, HDC, BOOL);

class CPages : public CWindow
    {
    friend LRESULT APIENTRY PagesWndProc(HWND, UINT, WPARAM, LPARAM);
    friend BOOL    APIENTRY PrintDlgProc(HWND, UINT, WPARAM, LPARAM);

    private:
        UINT        m_iPageCur;             //Current page
        UINT        m_cPages;               //Number of pages

        HWND        m_hWndPageList;         //Listbox with page list
        HFONT       m_hFont;                //Page font
        BOOL        m_fSystemFont;          //m_hFont system object?

        UINT        m_cx;                   //Page size in LOMETRIC
        UINT        m_cy;

        UINT        m_xMarginLeft;          //Unusable margins,
        UINT        m_xMarginRight;         //in LOMETRIC
        UINT        m_yMarginTop;
        UINT        m_yMarginBottom;

        UINT        m_xPos;                 //Viewport scroll pos,
        UINT        m_yPos;                 //both in *PIXELS*

        DWORD       m_dwIDNext;             //Next ID for a page.

        HGLOBAL     m_hDevMode;             //Current DevMode config

        TCHAR       m_szDriver[CCHDEVICENAME];
        TCHAR       m_szDevice[CCHDEVICENAME];
        TCHAR       m_szPort[CCHDEVICENAME];

    private:
        void        Draw(HDC, BOOL, BOOL);
        void        UpdateScrollRanges(void);
        BOOL        ConfigureForDevice(void);
        BOOL        PageGet(UINT, PCPage *, BOOL);
        BOOL        PageAdd(UINT, DWORD, BOOL);

    public:
        CPages(HINSTANCE);
        ~CPages(void);

        BOOL        Init(HWND, LPRECT, DWORD, UINT, LPVOID);

        void        New(void);
        BOOL        Print(HDC, LPTSTR, DWORD, UINT, UINT, UINT);

        void        RectGet(LPRECT);
        void        RectSet(LPRECT, BOOL);
        void        SizeGet(LPRECT);
        void        SizeSet(LPRECT, BOOL);

        PCPage      ActivePage(void);
        UINT        PageInsert(UINT);
        UINT        PageDelete(UINT);
        UINT        CurPageGet(void);
        UINT        CurPageSet(UINT);
        UINT        NumPagesGet(void);

        BOOL        DevModeSet(HGLOBAL, HGLOBAL);
        HGLOBAL     DevModeGet(void);
    };

typedef CPages *PCPages;

#endif  //_PAGES_H_


PAGEWIN.CPP

/*
 * PAGEWIN.CPP
 * Patron Chapter 1
 *
 * Window procedure for the Pages window and support functions.
 * This window manages its own scrollbars and viewport and provides
 * printing capabilities as well.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * PagesWndProc
 *
 * Purpose:
 *  Window procedure for the Pages window.
 */

LRESULT APIENTRY PagesWndProc(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    PCPages         ppg;
    PAINTSTRUCT     ps;
    HDC             hDC;
    int             iPos, iTmp;
    int             iMin, iMax;
    UINT            idScroll;

    ppg=(PCPages)GetWindowLong(hWnd, PAGEWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppg=(PCPages)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PAGEWL_STRUCTURE, (LONG)ppg);

            ppg->m_hWnd=hWnd;
            ppg->New();
            break;

        case WM_PAINT:
            hDC=BeginPaint(hWnd, &ps);

            //Draw only if we have a page to show.
            if (0!=ppg->m_cPages)
                ppg->Draw(hDC, FALSE, FALSE);

            EndPaint(hWnd, &ps);
            break;

        case WM_HSCROLL:
        case WM_VSCROLL:
            idScroll=(WM_HSCROLL==iMsg) ? SB_HORZ : SB_VERT;

            iPos=GetScrollPos(hWnd, idScroll);
            iTmp=iPos;
            GetScrollRange(hWnd, idScroll, &iMin, &iMax);

            switch (LOWORD(wParam))
                {
                case SB_LINEUP:     iPos -= 20;  break;
                case SB_PAGEUP:     iPos -=100;  break;
                case SB_LINEDOWN:   iPos += 20;  break;
                case SB_PAGEDOWN:   iPos +=100;  break;

                case SB_THUMBPOSITION:
                    iPos=ScrollThumbPosition(wParam, lParam);
                    break;

                //We don't want scrolling on this message.
                case SB_THUMBTRACK:
                    return 0L;
                }

            iPos=max(iMin, min(iPos, iMax));

            if (iPos!=iTmp)
                {
                //Set the new position and scroll the window
                SetScrollPos(hWnd, idScroll, iPos, TRUE);

                if (SB_HORZ==idScroll)
                    {
                    ppg->m_xPos=iPos;
                    ScrollWindow(hWnd, iTmp-iPos, 0, NULL, NULL);
                    }
                else
                    {
                    ppg->m_yPos=iPos;
                    ScrollWindow(hWnd, 0, iTmp-iPos, NULL, NULL);
                    }
                }

            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }

/*
 * RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device to logical
 *  coordinates where the hDC defines the logical coordinates.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  hDC             HDC describing the logical coordinate system.
 *                  if NULL, uses a screen DC in MM_LOMETRIC.
 *  fToDevice       BOOL TRUE to convert from uConv to device,
 *                  FALSE to convert device to uConv.
 *
 * Return Value:
 *  None
 */

void RectConvertMappings(LPRECT pRect, HDC hDC, BOOL fToDevice)
    {
    POINT   rgpt[2];
    BOOL    fSysDC=FALSE;

    if (NULL==pRect)
        return;

    rgpt[0].x=pRect->left;
    rgpt[0].y=pRect->top;
    rgpt[1].x=pRect->right;
    rgpt[1].y=pRect->bottom;

    if (NULL==hDC)
        {
        hDC=GetDC(NULL);
        SetMapMode(hDC, MM_LOMETRIC);
        fSysDC=TRUE;
        }

    if (fToDevice)
        LPtoDP(hDC, rgpt, 2);
    else
        DPtoLP(hDC, rgpt, 2);

    if (fSysDC)
        ReleaseDC(NULL, hDC);

    pRect->left=rgpt[0].x;
    pRect->top=rgpt[0].y;
    pRect->right=rgpt[1].x;
    pRect->bottom=rgpt[1].y;

    return;
    }

/*
 * CPages::Draw
 *
 * Purpose:
 *  Paints the current page in the pages window.
 *
 * Parameters:
 *  hDC             HDC to draw on, could be a metafile or printer
 *                  DC or any other type of DC.
 *  fNoColor        BOOL indicating if we should use screen colors
 *                  or printer colors (B&W).  Objects are printed
 *                  as-is, however.  This is TRUE for printer DCs
 *                  or print preview.
 *  fPrinter        BOOL indicating if this is a printer DC in which
 *                  case we eliminate some of the fancy drawing,
 *                  like shadows on the page and so forth.
 *
 * Return Value:
 *  None
 */

void CPages::Draw(HDC hDC, BOOL fNoColor, BOOL fPrinter)
    {
    RECT            rc, rcT;
    UINT            uMM;
    HPEN            hPen;
    HBRUSH          hBrush;
    HGDIOBJ         hObj1, hObj2;
    COLORREF        cr;
    TCHAR           szTemp[20];
    UINT            cch;
    SIZE            sz;

    //Make sure the DC is in LOMETRIC
    uMM=SetMapMode(hDC, MM_LOMETRIC);

    if (!fPrinter)
        {
        /*
         * We maintain a 6mm border around the page on the screen
         * besides 12.7mm margins.  We also have to account for
         * the scroll position with m_*Pos which are in pixels so
         * we have to convert them.
         */

        SetRect(&rcT, m_xPos, m_yPos, 0, 0);
        RectConvertMappings(&rcT, hDC, FALSE);

        rc.left  = LOMETRIC_BORDER-rcT.left;
        rc.top   =-LOMETRIC_BORDER-rcT.top;
        }
    else
        {
        /*
         * We define the corner of the printed paper at a negative
         * offset so rc.right and rc.bottom come out right below.
         */
        SetRect(&rc, -(int)m_xMarginLeft, m_yMarginTop, 0, 0);
        }

    rc.right=rc.left+m_cx+(m_xMarginLeft+m_xMarginRight);
    rc.bottom=rc.top-m_cy-(m_yMarginTop+m_yMarginBottom);

    //Draw a rect filled with the window color to show the page.
    if (!fPrinter)
        {
        if (fNoColor)
            {
            //Black frame, white box for printed colors.
            hPen  =CreatePen(PS_SOLID, 0, RGB(0,0,0));
            hBrush=CreateSolidBrush(RGB(255, 255, 255));
            }
        else
            {
            //Normal colors on display
            hPen=CreatePen(PS_SOLID, 0
                , GetSysColor(COLOR_WINDOWFRAME));
            hBrush=CreateSolidBrush(GetSysColor(COLOR_WINDOW));
            }

        hObj1=SelectObject(hDC, hPen);
        hObj2=SelectObject(hDC, hBrush);

        //Paper boundary
        Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom+1);

        /*
         * Draw a shadow on the *visual* bottom and right edges
         * .5mm wide.  If the button shadow color and workspace
         * colors match, then use black.  We always use black
         * when printing as well.
         */
        if (fNoColor)
            cr=RGB(0,0,0);
        else
            {
            cr=GetSysColor(COLOR_BTNSHADOW);

            if (GetSysColor(COLOR_APPWORKSPACE)==cr)
                cr=RGB(0,0,0);
            }

        cr=SetBkColor(hDC, cr);
        SetRect(&rcT, rc.left+5, rc.bottom, rc.right+5,rc.bottom-5);
        ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rcT, NULL, 0, NULL);

        SetRect(&rcT, rc.right, rc.top-5, rc.right+5, rc.bottom-5);
        ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rcT, NULL, 0, NULL);
        SetBkColor(hDC, cr);

        SelectObject(hDC, hObj1);
        SelectObject(hDC, hObj2);
        DeleteObject(hBrush);
        DeleteObject(hPen);
        }

    //Write the page number in the lower left corner
    if (!fNoColor)
        {
        SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));
        SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
        }

    //Write the page number in our page font.
    cch=wsprintf(szTemp, TEXT("Page %d"), m_iPageCur+1);

    hObj1=SelectObject(hDC, m_hFont);
    GetTextExtentPoint(hDC, szTemp, cch, &sz);

    TextOut(hDC, rc.left+m_xMarginLeft
        , rc.bottom+m_yMarginBottom+sz.cy, szTemp, cch);

    SelectObject(hDC, hObj1);

    //Rectangle to show border.
    MoveToEx(hDC, rc.left+m_xMarginLeft, rc.top-m_yMarginTop, NULL);
    LineTo(hDC, rc.left+m_xMarginLeft,   rc.bottom+m_yMarginBottom);
    LineTo(hDC, rc.right-m_xMarginRight, rc.bottom+m_yMarginBottom);
    LineTo(hDC, rc.right-m_xMarginRight, rc.top-m_yMarginTop);
    LineTo(hDC, rc.left+m_xMarginLeft,   rc.top-m_yMarginTop);

    SetMapMode(hDC, uMM);
    return;
    }

/*
 * CPages::UpdateScrollRanges
 *
 * Purpose:
 *  Reset scrollbar ranges (horizontal and vertical) depending on
 *  the window size and the page size.  This function may remove
 *  the scrollbars altogether.
 *
 * Parameters:
 *  None, but set m_cx, m_cy and size m_hWnd before calling.
 *
 * Return Value:
 *  None
 */

void CPages::UpdateScrollRanges(void)
    {
    UINT        cxSB;   //Scrollbar width and height.
    UINT        cySB;
    UINT        cx, cy;
    UINT        dx, dy;
    UINT        u;
    int         iMin, iMax;
    RECT        rc;
    BOOL        fHScroll;
    BOOL        fVScroll;
    BOOL        fWasThere;

    GetClientRect(m_hWnd, &rc);

    cx=rc.right-rc.left;
    cy=rc.bottom-rc.top;

    //Convert dimensions of the image in LOMETRIC to pixels.
    SetRect(&rc, (m_cx+m_xMarginLeft+m_xMarginRight
        +LOMETRIC_BORDER*2), (m_cy+m_yMarginTop
        +m_yMarginBottom+LOMETRIC_BORDER*2), 0, 0);

    RectConvertMappings(&rc, NULL, TRUE);

    dx=rc.left;
    dy=-rc.top;

    //Assume that both scrollbars will be visible.
    fHScroll=TRUE;
    fVScroll=TRUE;

    /*
     * Determine:
     *  1)  Which scrollbars are needed.
     *  2)  How many divisions to give scrollbars so as to
     *      only scroll as little as necessary.
     */

    //Scrollbar dimensions in our units.
    cxSB=GetSystemMetrics(SM_CXVSCROLL);
    cySB=GetSystemMetrics(SM_CYHSCROLL);

    //Remove horizontal scroll if window >= cxPage+borders
    if (cx >= dx)
        fHScroll=FALSE;

    /*
     * If we still need a horizontal scroll, see if we need a
     * vertical taking the height of the horizontal scroll into
     * account.
     */

    u=fHScroll ? cySB : 0;

    if ((cy-u) >= dy)
        fVScroll=FALSE;

    //Check if adding vert scrollbar necessitates a horz now.
    u=fVScroll ? cxSB : 0;
    fHScroll=((cx-u) < dx);

    /*
     * Modify cx,cy to reflect the new client area before scaling
     * scrollbars.  We only affect the client size if there is a
     * *change* in scrollbar status:  if the scrollbar was there
     * but is no longer, then add to the client size; if it was
     * not there but now is, then subtract.
     */

    //Change cx depending on vertical scrollbar change
    GetScrollRange(m_hWnd, SB_VERT, &iMin, &iMax);
    fWasThere=(0!=iMin || 0!=iMax);

    if (fWasThere && !fVScroll)
        cx+=cxSB;

    if (!fWasThere && fVScroll)
        cx-=cxSB;

    //Change cy depending on horizontal scrollbar change
    GetScrollRange(m_hWnd, SB_HORZ, &iMin, &iMax);
    fWasThere=(0!=iMin || 0!=iMax);

    if (fWasThere && !fHScroll)
        cy+=cySB;

    if (!fWasThere && fHScroll)
        cy-=cySB;

    /*
     * Show/Hide the scrollbars if necessary and set the ranges.
     * The range is the number of units of the page we cannot see.
     */
    if (fHScroll)
        {
        //Convert current scroll position to new range.
        u=GetScrollPos(m_hWnd, SB_HORZ);

        if (0!=u)
            {
            GetScrollRange(m_hWnd, SB_HORZ, &iMin, &iMax);
            u=MulDiv(u, (dx-cx), (iMax-iMin));
            }

        SetScrollRange(m_hWnd, SB_HORZ, 0, dx-cx, FALSE);
        SetScrollPos(m_hWnd, SB_HORZ, u, TRUE);
        m_xPos=u;
        }
    else
        {
        SetScrollRange(m_hWnd, SB_HORZ, 0, 0, TRUE);
        m_xPos=0;
        }

    if (fVScroll)
        {
        //Convert current scroll position to new range.
        u=GetScrollPos(m_hWnd, SB_VERT);

        if (0!=u)
            {
            GetScrollRange(m_hWnd, SB_VERT, &iMin, &iMax);
            u=MulDiv(u, (dy-cy), (iMax-iMin));
            }

        SetScrollRange(m_hWnd, SB_VERT, 0, dy-cy, FALSE);
        SetScrollPos(m_hWnd, SB_VERT, u, TRUE);

        m_yPos=u;
        }
    else
        {
        SetScrollRange(m_hWnd, SB_VERT, 0, 0, TRUE);
        m_yPos=0;
        }

    //Repaint to insure that changes to m_x/yPos are reflected
    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }


PATRON.CPP

/*
 * PATRON.CPP
 * Patron Chapter 1
 *
 * Frame window class for Patron.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCPatronFrame   pFR;
    FRAMEINIT       fi;
    WPARAM          wRet=0;

    //Attempt to allocate and initialize the application
    pFR=new CPatronFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return -1;

    fi.idsMin=IDS_FRAMEMIN;
    fi.idsMax=IDS_FRAMEMAX;
    fi.idsStatMin=IDS_STATMESSAGEMIN;
    fi.idsStatMax=IDS_STATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=CW_USEDEFAULT;
    fi.cy=CW_USEDEFAULT;

    //If we can initialize pFR, start chugging messages
    if (pFR->Init(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

/*
 * CPatronFrame::CPatronFrame
 * CPatronFrame::~CPatronFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WInMain
 */

CPatronFrame::CPatronFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    : CFrame(hInst, hInstPrev, pszCmdLine, nCmdShow)
    {
    return;
    }

CPatronFrame::~CPatronFrame(void)
    {
    return;
    }

/*
 * CPatronFrame::CreateCClient
 *
 * Purpose:
 *  Constructs a new client specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient        Pointer to the new client object.
 */

PCClient CPatronFrame::CreateCClient(void)
    {
    return (PCClient)(new CPatronClient(m_hInst, this));
    }

/*
 * CPatronFrame::RegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CPatronFrame::RegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //First let the standard frame do its thing
    if (!CFrame::RegisterAllClasses())
        return FALSE;

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.hInstance     = m_hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PagesWndProc;
    wc.cbWndExtra    = CBPAGESWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPAGES;

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

    return TRUE;
    }

/*
 * CPatronFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the Patron frame window that processes
 *  extra File menu items as well as the Page menu.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CPatronFrame::OnCommand(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PCPatronDoc     pDoc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many toolbar notifications.
     */
    if (m_fInit)
        return 0L;

    pDoc=(PCPatronDoc)m_pCL->ActiveDocument();

    switch (wID)
        {
        case IDM_FILEPRINT:
            pDoc->Print(m_hWnd);
            return 0L;

        case IDM_FILEPRINTERSETUP:
            pDoc->PrinterSetup(m_hWnd, FALSE);
            return 0L;

        case IDM_PAGENEWPAGE:
            pDoc->NewPage();
            break;

        case IDM_PAGEDELETEPAGE:
            pDoc->DeletePage();
            break;

        case IDM_PAGENEXTPAGE:
            pDoc->NextPage();
            break;

        case IDM_PAGEPREVIOUSPAGE:
            pDoc->PreviousPage();
            break;

        case IDM_PAGEFIRSTPAGE:
            pDoc->FirstPage();
            break;

        case IDM_PAGELASTPAGE:
            pDoc->LastPage();
            break;

        default:
           return CFrame::OnCommand(hWnd, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPatronFrame::CreateToolbar
 *
 * Purpose:
 *  Procedure to create all the necessary toolbar buttons.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of tools added to the bar.
 */

UINT CPatronFrame::CreateToolbar(void)
    {
    UINT            iLast;
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;

    //Insert the standard ones.
    iLast=CFrame::CreateToolbar();

    //Remove Undo:  we don't use it.
    m_pTB->Remove(IDM_EDITUNDO);

    /*
     * Insert Print File Import in the 5th position and account
     * for it in iLast.
     */
    m_pTB->Add(utCmd, 4, IDM_FILEPRINT, m_dxB, m_dyB
        , NULL, NULL, 6, uState);

    iLast++;

    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //Add New Page, and Delete Page
    m_pTB->Add(utCmd, iLast++, IDM_PAGENEWPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 2, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGEDELETEPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 3, uState);

    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //First, Prev, Next, Last pages.
    m_pTB->Add(utCmd, iLast++, IDM_PAGEFIRSTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 4, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGEPREVIOUSPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 5, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGENEXTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 6, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGELASTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 7, uState);

    return iLast;
    }

/*
 * CPatronFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CPatronFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    PCPatronDoc     pDoc;
    BOOL            fOK=FALSE;
    BOOL            fCallDefault=TRUE;
    UINT            uTemp;
    UINT            uTempE;
    UINT            uTempD;

    pDoc=(PCPatronDoc)m_pCL->ActiveDocument();

    uTempE=MF_ENABLED | MF_BYCOMMAND;
    uTempD=MF_DISABLED | MF_GRAYED | MF_BYCOMMAND;
    uTemp=((NULL!=pDoc) ? uTempE : uTempD);

    //File menu
    if (m_phMenu[0]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_FILEPRINT, uTemp);
        EnableMenuItem(hMenu, IDM_FILEPRINTERSETUP, uTemp);
        }

    //Page menu
    if (m_phMenu[2]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_PAGENEWPAGE,      uTemp);
        EnableMenuItem(hMenu, IDM_PAGEDELETEPAGE,   uTemp);
        EnableMenuItem(hMenu, IDM_PAGENEXTPAGE,     uTemp);
        EnableMenuItem(hMenu, IDM_PAGEPREVIOUSPAGE, uTemp);
        EnableMenuItem(hMenu, IDM_PAGEFIRSTPAGE,    uTemp);
        EnableMenuItem(hMenu, IDM_PAGELASTPAGE,     uTemp);
        }

    if (fCallDefault)
        CFrame::UpdateMenus(hMenu, iMenu);

    return;
    }

/*
 * CPatronFrame::UpdateToolbar
 *
 * Purpose:
 *  Enables and disables tools depending on whether we have
 *  a document or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronFrame::UpdateToolbar(void)
    {
    PCDocument  pDoc;
    BOOL        fEnable;

    //Let the default hack on its tools.
    CFrame::UpdateToolbar();

    pDoc=m_pCL->ActiveDocument();
    fEnable=(NULL!=pDoc);

    //No document, disable just about everything
    m_pTB->Enable(IDM_FILEPRINT,        fEnable);
    m_pTB->Enable(IDM_FILEPRINTERSETUP, fEnable);

    m_pTB->Enable(IDM_PAGENEWPAGE,      fEnable);
    m_pTB->Enable(IDM_PAGEDELETEPAGE,   fEnable);
    m_pTB->Enable(IDM_PAGEFIRSTPAGE,    fEnable);
    m_pTB->Enable(IDM_PAGEPREVIOUSPAGE, fEnable);
    m_pTB->Enable(IDM_PAGENEXTPAGE,     fEnable);
    m_pTB->Enable(IDM_PAGELASTPAGE,     fEnable);

    return;
    }


PATRON.H

/*
 * PATRON.H
 * Patron Chapter 1
 *
 * Single include file that pulls in everything needed for other
 * source files in the application.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _PATRON_H_
#define _PATRON_H_

#define INC_CLASSLIB
#define CHAPTER1
#include <inole.h>
#include "resource.h"

//Get editor window information
#include "pages.h"

/*
 * UINT value such that adding one produces zero.  Portable to Win32.
 * This is used to represent a non-existent zero-based UINT value
 */
#define NOVALUE                     ((UINT)-1)

//PATRON.CPP:  Frame object that creates a main window

class CPatronFrame : public CFrame
    {
    protected:
        //Overridable for creating a CPatronClient
        virtual PCClient    CreateCClient(void);

        virtual BOOL        RegisterAllClasses(void);
        virtual UINT        CreateToolbar(void);
        virtual LRESULT     OnCommand(HWND, WPARAM, LPARAM);

    public:
        CPatronFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CPatronFrame(void);

        //Overrides
        virtual void        UpdateMenus(HMENU, UINT);
        virtual void        UpdateToolbar(void);

    };

typedef CPatronFrame *PCPatronFrame;

//CLIENT.CPP

/*
 * The only reason we have a derived class here is to override
 * CreateCDocument so we can create our own type as well as
 * overriding NewDocument to perform one other piece of work once
 * the document's been created.
 */

class CPatronClient : public CClient
    {
    protected:
        //Overridable for creating a new CDocument
        virtual PCDocument CreateCDocument(void);

    public:
        CPatronClient(HINSTANCE, PCFrame);
        virtual ~CPatronClient(void);
    };

typedef CPatronClient *PCPatronClient;

//DOCUMENT.CPP

//Constant ID for the pages window that lives in a document window
#define ID_PAGES            723

class CPatronDoc : public CDocument
    {
    protected:
        LONG            m_lVer;         //Loaded data version
        PCPages         m_pPG;          //Pages window in us

    protected:
        virtual BOOL    FMessageHook(HWND, UINT, WPARAM, LPARAM
            , LRESULT *);

    public:
        CPatronDoc(HINSTANCE, PCFrame, PCDocumentAdviseSink);
        virtual ~CPatronDoc(void);

        virtual BOOL    Init(PDOCUMENTINIT);
        virtual void    Clear(void);

        virtual UINT    Load(BOOL, LPTSTR);

        virtual BOOL    Print(HWND);
        virtual UINT    PrinterSetup(HWND, BOOL);

        virtual UINT    NewPage(void);
        virtual UINT    DeletePage(void);
        virtual UINT    NextPage(void);
        virtual UINT    PreviousPage(void);
        virtual UINT    FirstPage(void);
        virtual UINT    LastPage(void);
    };

typedef CPatronDoc *PCPatronDoc;

//Hook for Print Dialog to hide Setup... button
UINT CALLBACK PrintDlgHook(HWND, UINT, WPARAM, LPARAM);

#endif //_PATRON_H_


PRECOMP.CPP

#include "patron.h"


PRINT.CPP

/*
 * PRINT.CPP
 * Patron Chapter 1
 *
 * Implementation of printing functions for both CPatronDoc
 * and CPages classes.  These functions are here to keep clutter
 * down in document.cpp and pages.cpp.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

static HWND g_hDlgPrint=NULL;
static BOOL g_fCancelPrint=FALSE;

/*
 * CPatronDoc::Print
 *
 * Purpose:
 *  Prints the current document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *
 * Return Value:
 *  BOOL            TRUE if printing happened, FALSE if it didn't
 *                  start or didn't complete.
 */

BOOL CPatronDoc::Print(HWND hWndFrame)
    {
    PRINTDLG        pd;
    BOOL            fSuccess;

    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);
    pd.hwndOwner  =hWndFrame;
    pd.nCopies    =1;
    pd.nFromPage  =(USHORT)-1;
    pd.nToPage    =(USHORT)-1;
    pd.nMinPage   =1;
    pd.nMaxPage   =m_pPG->NumPagesGet();

    pd.lpfnPrintHook=PrintDlgHook;

    //Get the current document printer settings
    pd.hDevMode=m_pPG->DevModeGet();

    pd.Flags=PD_RETURNDC | PD_ALLPAGES | PD_COLLATE
        | PD_HIDEPRINTTOFILE | PD_NOSELECTION | PD_ENABLEPRINTHOOK;

    if (!PrintDlg(&pd))
        return FALSE;

    if (NULL!=pd.hDevMode)
        GlobalFree(pd.hDevMode);

    if (NULL!=pd.hDevNames)
        GlobalFree(pd.hDevNames);

    //Go do the actual printing.
    fSuccess=m_pPG->Print(pd.hDC, PSZ(IDS_DOCUMENTNAME), pd.Flags
        , pd.nFromPage, pd.nToPage, pd.nCopies);

    if (!fSuccess)
        {
        MessageBox(m_hWnd, PSZ(IDS_PRINTERROR)
            , PSZ(IDS_DOCUMENTCAPTION), MB_OK);
        }

    return fSuccess;
    }

/*
 * CPatronDoc::PrinterSetup
 *
 * Purpose:
 *  Selects a new printer and options for this document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *  fDefault        BOOL to avoid any dialog and just use the
 *                  default.
 *
 * Return Value:
 *  UINT            Undefined
 */

UINT CPatronDoc::PrinterSetup(HWND hWndFrame, BOOL fDefault)
    {
    PRINTDLG        pd;

    //Attempt to get printer metrics for the default printer.
    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);

    if (fDefault)
        pd.Flags=PD_RETURNDEFAULT;
    else
        {
        pd.hwndOwner=hWndFrame;
        pd.Flags=PD_PRINTSETUP;

        //Get the current document printer settings
        pd.hDevMode=m_pPG->DevModeGet();
        }

    if (!PrintDlg(&pd))
        return FALSE;

    if (!m_pPG->DevModeSet(pd.hDevMode, pd.hDevNames))
        {
        GlobalFree(pd.hDevNames);
        GlobalFree(pd.hDevMode);
        return FALSE;
        }

    FDirtySet(TRUE);
    return 1;
    }

/*
 * PrintDlgHook
 *
 * Purpose:
 *  Callback hook for the Print Dialog so we can hide the Setup
 *  button.  Patron only allows Setup before anything exists on
 *  the page, and is not written to handle setup at Print time.
 */

UINT CALLBACK PrintDlgHook(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    if (WM_INITDIALOG==iMsg)
        {
        HWND        hWnd;

        hWnd=GetDlgItem(hDlg, psh1);
        ShowWindow(hWnd, SW_HIDE);
        return TRUE;
        }

    return FALSE;
    }

/*
 * CPages::DevModeSet
 *
 * Purpose:
 *  Provides the Pages with the current printer information.
 *
 * Parameters:
 *  hDevMode        HGLOBAL to the memory containing the DEVMODE.
 *                  This function assumes responsibility for this
 *                  handle.
 *  hDevNames       HGLOBAL providing the driver name and device
 *                  name from which we can create a DC for
 *                  information.
 *
 * Return Value:
 *  BOOL            TRUE if we could accept this configuration,
 *                  FALSE otherwise.  If we return TRUE we also
 *                  delete the old memory we hold.
 */

BOOL CPages::DevModeSet(HGLOBAL hDevMode, HGLOBAL hDevNames)
    {
    LPDEVNAMES      pdn;
    LPTSTR          psz;

    if (NULL==hDevMode || NULL==hDevNames)
        return FALSE;

    psz=(LPTSTR)GlobalLock(hDevNames);

    if (NULL==psz)
        return FALSE;

    pdn=(LPDEVNAMES)psz;
    lstrcpy(m_szDriver, psz+pdn->wDriverOffset);
    lstrcpy(m_szDevice, psz+pdn->wDeviceOffset);
    lstrcpy(m_szPort,   psz+pdn->wOutputOffset);
    GlobalUnlock(hDevNames);
    GlobalFree(hDevNames);

    //Save this new memory and get rid of the old.
    if (NULL!=m_hDevMode)
        GlobalFree(m_hDevMode);

    m_hDevMode=hDevMode;

    return ConfigureForDevice();
    }

/*
 * CPages::DevModeGet
 *
 * Purpose:
 *  Retrieves a copy of the current DEVMODE structure for this
 *  Pages window.  The caller is responsible for this memory.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HGLOBAL         Handle to the memory containing the DEVMODE
 *                  structure.
 */

HGLOBAL CPages::DevModeGet(void)
    {
    HGLOBAL     hMem;
    DWORD       cb;
    DWORD       i;
    LPBYTE      pb1, pb2;

    cb=GlobalSize(m_hDevMode);

    if (0==cb)
        return NULL;

    hMem=GlobalAlloc(GHND, cb);

    if (NULL==hMem)
        return NULL;

    pb1=(LPBYTE)GlobalLock(hMem);
    pb2=(LPBYTE)GlobalLock(m_hDevMode);

    //Copy the existing DEVMODE structure
    for (i=0; i < cb; i++)
        *pb1++=*pb2++;

    GlobalUnlock(m_hDevMode);
    GlobalUnlock(hMem);

    return hMem;
    }

/*
 * CPages::ConfigureForDevice
 *
 * Purpose:
 *  Recalculates our drawing configuration based on the contents of
 *  an hDC.  If no HDC is given we use the contents of our DevMode
 *  stream.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::ConfigureForDevice(void)
    {
    POINT           ptOffset, ptPaper;
    RECT            rc;
    HDC             hDC;
    LPDEVMODE       pdm;
    CHourglass      hg;     //Shows wait cursor, automatically destroyed

    pdm=(LPDEVMODE)GlobalLock(m_hDevMode);

    if (NULL==pdm)
        return FALSE;

    //Get the DC then configure
    hDC=CreateIC(m_szDriver, m_szDevice, m_szPort, pdm);

    GlobalUnlock(m_hDevMode);

    if (NULL==hDC)
        return FALSE;

    //Get usable page dimensions:  already sensitive to orientation
    m_cx=GetDeviceCaps(hDC, HORZSIZE)*10-16; //*10: mm to LOMETRIC
    m_cy=GetDeviceCaps(hDC, VERTSIZE)*10-16; //-16: for driver bugs.

    //Calculate the printer-limited margins on sides in LOMETRIC.
    Escape(hDC, GETPRINTINGOFFSET, NULL, NULL, &ptOffset);
    Escape(hDC, GETPHYSPAGESIZE,   NULL, NULL, &ptPaper);

    SetRect(&rc, ptOffset.x, ptOffset.y, ptPaper.x, ptPaper.y);
    SetMapMode(hDC, MM_LOMETRIC);
    RectConvertMappings(&rc, hDC, FALSE);

    //Left and top margins are the printing offset.
    m_xMarginLeft= rc.left+8;   //+8 to match -16 above
    m_yMarginTop =-rc.top+8;    //LOMETRIC makes this negative.

    //Right is (paper width)-(usable width)-(left margin)
    m_xMarginRight =rc.right-m_cx-m_xMarginLeft;

    //Bottom is (paper height)-(usable height)-(top margin)+1
    m_yMarginBottom=-rc.bottom-m_cy-m_yMarginTop+1;

    UpdateScrollRanges();

    DeleteDC(hDC);
    return TRUE;
    }

/*
 * CPages::Print
 *
 * Purpose:
 *  Prints a specified range of pages to a given hDC.  Repeats for
 *  a given number of copies.
 *
 * Parameters:
 *  hDC             HDC to which we print.
 *  pszDoc          LPTSTR providing the document name.
 *  dwFlags         DWORD flags from PrintDlg
 *  iPageStart      UINT starting page index (one based)
 *  iPageEnd        UINT ending page index (one based).  Includes
 *                  this page.
 *  cCopies         UINT number of copies to print.  If PD_COLLATE
 *                  in dwFlags is set, we print multiple copies of
 *                  each page as we cycle through.  Otherwise we
 *                  cycle multiple times.
 *
 * Return Value:
 *  None
 */

BOOL CPages::Print(HDC hDC, LPTSTR pszDoc, DWORD dwFlags
    , UINT iPageStart, UINT iPageEnd, UINT cCopies)
    {
    BOOL        fError=FALSE;
    int         iPage, iPageInc;
    int         iUserPage, cPages;
    UINT        iRepeat, cRepeat;
    UINT        iCycle, cCycles;
    UINT        iPageHold=m_iPageCur;
    HWND        hWndT, hWndTop=NULL;
    DOCINFO     di;
    PCDocument  pDoc;

    //Validate hDC and page ranges
    if (NULL==hDC)
        return FALSE;

    if ((PD_PAGENUMS & dwFlags))
        {
        if (-1==iPageStart)
            iPageStart=0;
        else
            iPageStart--;   //Switch to zero offset.

        if (-1==iPageEnd)
            iPageEnd=m_cPages-1;
        else
            iPageEnd--;     //Switch to zero offset.
        }
    else //Can't test PD_ALLPAGES with & since it's defined as 0L
        {
        iPageStart=0;
        iPageEnd=m_cPages-1;
        }

    //Arrange cycles and repeats depending on cCopies and collating
    if (PD_COLLATE & dwFlags)
        {
        cCycles=cCopies;
        cRepeat=1;
        }
    else
        {
        cCycles=1;
        cRepeat=cCopies;
        }

    //Disable the frame window to prevent reentrancy while printing.
    hWndT=GetParent(m_hWnd);
    pDoc=(PCDocument)SendMessage(hWndT, DOCM_PDOCUMENT, 0, 0L);

    if (NULL!=pDoc)
        {
        PCFrame pFR;

        pFR=pDoc->FrameGet();
        hWndTop=pFR->Window();
        EnableWindow(hWndTop, FALSE);
        }

    SetAbortProc(hDC, AbortProc);
    g_fCancelPrint=FALSE;

    //If these don't work then we'll just live without a dialog.
    g_hDlgPrint=CreateDialog(m_hInst, MAKEINTRESOURCE(IDD_PRINTING)
        , hWndTop, PrintDlgProc);

    //Increment for either direction.
    iPageInc=(iPageStart > iPageEnd) ? -1 : 1;

    //Initial entries in dialog box.
    cPages=1+((int)(iPageEnd-iPageStart)*iPageInc);

    SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE, 1, (LPARAM)cPages);
    SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, 1, (LPARAM)cRepeat);

    di.cbSize=sizeof(DOCINFO);
    di.lpszDocName=pszDoc;
    di.lpszOutput=NULL;

    if (StartDoc(hDC, &di) > 0)
        {
        /*
         * Iterate over the pages, repeating each page depending on
         * the copies we want and if we have collate enabled.
         */

        for (iCycle=1; iCycle <= cCycles; iCycle++)
            {
            if (PD_COLLATE & dwFlags)
                {
                SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, iCycle
                    , (LPARAM)cCycles);
                }

            //iPageInc controls direction
            for (iPage=iPageStart; ; iPage+=iPageInc)
                {
                iUserPage=1+((iPage-(int)iPageStart)*iPageInc);

                SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE
                    , iUserPage, (LPARAM)cPages);

                m_iPageCur=iPage;   //We restore this later.

                for (iRepeat=1; iRepeat <= cRepeat; iRepeat++)
                    {
                    if (!(PD_COLLATE & dwFlags))
                        {
                        SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE
                            , iRepeat, (LPARAM)cRepeat);
                        }

                    StartPage(hDC);
                    Draw(hDC, TRUE, TRUE);

                    if (EndPage(hDC) < 0)
                        fError=TRUE;

                    if (fError || g_fCancelPrint)
                        break;
                    }

                if (fError || g_fCancelPrint)
                    break;

                //If we just printed the last page, time to quit.
                if (iPage==(int)iPageEnd)
                    break;
                }

            if (fError || g_fCancelPrint)
                break;
            }

        if (!fError)
            EndDoc(hDC);
        else
            AbortDoc(hDC);
        }
    else
        fError=TRUE;

    //Set the page back to what it was before all this started.
    m_iPageCur=iPageHold;

    EnableWindow(hWndTop, TRUE);
    SetFocus(hWndTop);
    DestroyWindow(g_hDlgPrint);
    DeleteDC(hDC);
    return !fError;
    }

/*
 * AbortProc
 *
 * Purpose:
 *  Abort procedure for printing the pages.
 *
 * Parameters:
 *  hDC             HDC on which printing is happening.
 *  iErr            int error code.
 *
 * Return Value:
 *  BOOL            TRUE to continue the print job, FALSE otherwise.
 */

BOOL APIENTRY AbortProc(HDC hDC, int iErr)
    {
    MSG     msg;

    while (!g_fCancelPrint
        && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
        if (NULL==g_hDlgPrint
            || !IsDialogMessage(g_hDlgPrint, &msg))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    return !g_fCancelPrint;
    }

/*
 * PrintDlgProc
 *
 * Purpose:
 *  Modeless dialog procedure for the dialog displayed while Patron
 *  is printing pages.
 */

BOOL APIENTRY PrintDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    TCHAR           szFormat[40];
    TCHAR           szOutput[80];

    switch (iMsg)
        {
        case WM_INITDIALOG:
            EnableMenuItem(GetSystemMenu(hDlg, FALSE), SC_CLOSE
                , MF_GRAYED);
            return TRUE;

        case WM_COMMAND:
            //Cancel button was pressed.
            g_fCancelPrint=TRUE;
            ShowWindow(hDlg, SW_HIDE);
            return TRUE;

        case PRINTM_PAGEUPDATE:
            GetDlgItemText(hDlg, ID_PAGESTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTPAGE, szOutput);
            return TRUE;

        case PRINTM_COPYUPDATE:
            GetDlgItemText(hDlg, ID_COPYSTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTCOPY, szOutput);
            return TRUE;
        }

    return FALSE;
    }


RESOURCE.H

/*
 * RESOURCE.H
 * Patron Chapter 1
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Note that we augment classlib.h which we include before this.

//Position of Window menu.
#undef  WINDOW_MENU
#define WINDOW_MENU                 3

//Total number of menus
#undef  CMENUS
#ifdef MDI
#define CMENUS                      5
#else
#define CMENUS                      4
#endif

//Menu command identifiers.

#define IDM_FILEPRINT               (IDM_CUSTOMFILEMIN)
#define IDM_FILEPRINTERSETUP        (IDM_CUSTOMFILEMIN+1)

#define IDM_PAGENEWPAGE             (IDM_CUSTOMMIN)
#define IDM_PAGEDELETEPAGE          (IDM_CUSTOMMIN+1)
#define IDM_PAGENEXTPAGE            (IDM_CUSTOMMIN+2)
#define IDM_PAGEPREVIOUSPAGE        (IDM_CUSTOMMIN+3)
#define IDM_PAGEFIRSTPAGE           (IDM_CUSTOMMIN+4)
#define IDM_PAGELASTPAGE            (IDM_CUSTOMMIN+5)

/*
 * IDs for StatStrip handling of popup menus:  must be in order of menu
 * CLASSRES.H already defines ID_MENUFILE and ID_MENUEDIT for us.
 */

#undef ID_MENUWINDOW
#undef ID_MENUHELP

#define ID_MENUPAGE                 (ID_MENUCUSTOMMIN+0)
#define ID_MENUWINDOW               (ID_MENUCUSTOMMIN+1)
#define ID_MENUHELP                 (ID_MENUCUSTOMMIN+2)

//Stringtable IDs.  Keep sequential for each group.
#define IDS_FRAMEMIN                IDS_STANDARDFRAMEMIN
#define IDS_FRAMEMAX                IDS_STANDARDFRAMEMAX

#define IDS_DOCUMENTMIN             IDS_STANDARDDOCMIN
#define IDS_PRINTERROR              (IDS_CUSTOMDOCMIN+0)
#define IDS_DOCUMENTNAME            (IDS_CUSTOMDOCMIN+1)
#define IDS_DOCUMENTMAX             (IDS_CUSTOMDOCMIN+1)

//These are specifically for the StatStrip
#define IDS_STATMESSAGEMIN                  IDS_STANDARDSTATMESSAGEMIN

#define IDS_ITEMMESSAGEFILEPRINT            (IDS_CUSTOMSTATMESSAGEMIN+0)
#define IDS_ITEMMESSAGEFILEPRINTERSETUP     (IDS_CUSTOMSTATMESSAGEMIN+1)

#define IDS_MENUMESSAGEPAGE                 (IDS_CUSTOMSTATMESSAGEMIN+2)
#define IDS_ITEMMESSAGEPAGENEWPAGE          (IDS_CUSTOMSTATMESSAGEMIN+3)
#define IDS_ITEMMESSAGEPAGEDELETEPAGE       (IDS_CUSTOMSTATMESSAGEMIN+4)
#define IDS_ITEMMESSAGEPAGENEXTPAGE         (IDS_CUSTOMSTATMESSAGEMIN+5)
#define IDS_ITEMMESSAGEPAGEPREVIOUSPAGE     (IDS_CUSTOMSTATMESSAGEMIN+6)
#define IDS_ITEMMESSAGEPAGEFIRSTPAGE        (IDS_CUSTOMSTATMESSAGEMIN+7)
#define IDS_ITEMMESSAGEPAGELASTPAGE         (IDS_CUSTOMSTATMESSAGEMIN+8)

#define IDS_STATMESSAGEMAX                  (IDS_CUSTOMSTATMESSAGEMIN+8)

//Definitions for the printing dialog.

#define IDD_PRINTING                2

#define ID_CURRENTPAGE              101
#define ID_CURRENTCOPY              102
#define ID_COPYSTRING               103
#define ID_PAGESTRING               104

#define PRINTM_PAGEUPDATE           (WM_USER+100)
#define PRINTM_COPYUPDATE           (WM_USER+101)

#endif //_RESOURCE_H_


ENUMC.C

/*
 * ENUMC.C
 * Enumerator in C Chapter 2
 *
 * Implements the RECTENUMERATOR structure and functions (an object).
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "enumrect.h"

//We have to explicitly define the function table for IEnumRECT in C
static IEnumRECTVtbl  vtEnumRect;
static BOOL           g_fVtblInitialized=FALSE;

/*
 * CreateRECTEnumeratorC
 *
 * Purpose:
 *  Creates an enumerator object returning an IEnumRECT interface.
 *
 * Parameters:
 *  ppEnum          PENUMRECT * in which to return the interface
 *                  pointer on the created object.
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CreateRECTEnumeratorC(PENUMRECT *ppEnum)
    {
    PRECTENUMERATOR     pRE;
    HRESULT             hr;

    if (NULL==ppEnum)
        return FALSE;

    //Create the object
    pRE=RECTENUM_Constructor();

    if (NULL==pRE)
        return FALSE;

    //Get the interface, which calls AddRef
    hr=pRE->lpVtbl->QueryInterface((PENUMRECT)pRE, &IID_IEnumRECT
        , (void **)ppEnum);
    return SUCCEEDED(hr);
    }

/*
 * RECTENUM_Constructor
 *
 * Purpose:
 *  Constructor for an IMPIEnumRect structure
 *
 * Parameters:
 *  None
 */

PRECTENUMERATOR RECTENUM_Constructor(void)
    {
    PRECTENUMERATOR     pRE;
    UINT                i;

    /*
     * First time through initialize function table.  Such a table
     * could be defined as a constant instead of doing explicit
     * initialization here.  However, this method shows exactly
     * which pointers are going where and does not depend on knowing
     * the ordering of the functions in the table, just the names.
     */
    if (!g_fVtblInitialized)
        {
        vtEnumRect.QueryInterface=RECTENUM_QueryInterface;
        vtEnumRect.AddRef        =RECTENUM_AddRef;
        vtEnumRect.Release       =RECTENUM_Release;
        vtEnumRect.Next          =RECTENUM_Next;
        vtEnumRect.Skip          =RECTENUM_Skip;
        vtEnumRect.Reset         =RECTENUM_Reset;
        vtEnumRect.Clone         =RECTENUM_Clone;

        g_fVtblInitialized=TRUE;
        }

    pRE=(PRECTENUMERATOR)malloc(sizeof(RECTENUMERATOR));

    if (NULL==pRE)
        return NULL;

    //Initialize function table pointer
    pRE->lpVtbl=&vtEnumRect;

    //Initialize the array of rectangles
    for (i=0; i < CRECTS; i++)
        SetRect(&pRE->m_rgrc[i], i, i*2, i*3, i*4);

    //Ref counts always start at zero
    pRE->m_cRef=0;

    //Current pointer is the first element.
    pRE->m_iCur=0;

    return pRE;
    }

/*
 * RECTENUM_Destructor
 *
 * Purpose:
 *  Destructor for RECTENUMERATOR structures.
 *
 * Parameters:
 *  pRE            PRECTENUMERATOR to free
 */

void RECTENUM_Destructor(PRECTENUMERATOR pRE)
    {
    if (NULL==pRE)
        return;

    free(pRE);
    return;
    }

/*
 * RECTENUM_QueryInterface
 *
 * Purpose:
 *  Manages interfaces for the RECTENUMERATOR object.
 *
 * Parameters:
 *  pEnum           PENUMRECT to affect
 *  riid            REFIID of the interface to return.
 *  ppv             PPVOID in which to return the pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_NOINTERFACE if the
 *                  interface is not supported.
 */

STDMETHODIMP RECTENUM_QueryInterface(PENUMRECT pEnum
    , REFIID riid, PPVOID ppv)
    {
    //Always NULL the out-parameters
    *ppv=NULL;

    if (IsEqualIID(riid, &IID_IUnknown)
        || IsEqualIID(riid, &IID_IEnumRECT))
        *ppv=pEnum;

    if (NULL==*ppv)
        return ResultFromScode(E_NOINTERFACE);

    //AddRef any interface we'll return.
    ((LPUNKNOWN)*ppv)->lpVtbl->AddRef((LPUNKNOWN)*ppv);
    return NOERROR;
    }

/*
 * RECTENUM_AddRef
 *
 * Purpose:
 *  Increments the reference count on the object.
 *
 * Parameters:
 *  pEnum           PENUMRECT to affect
 *
 * Return Value:
 *  ULONG           New reference count.
 */

STDMETHODIMP_(ULONG) RECTENUM_AddRef(PENUMRECT pEnum)
    {
    PRECTENUMERATOR       pRE=(PRECTENUMERATOR)pEnum;

    return ++pRE->m_cRef;
    }

/*
 * RECTENUM_Release
 *
 * Purpose:
 *  Indicates that someone on whose behalf we once AddRef'd has
 *  finished with the object.  We decrement our reference count
 *  and if zero, we delete the object.
 *
 * Parameters:
 *  pEnum           PENUMRECT to affect
 *
 * Return Value:
 *  ULONG           Current reference count after decrement.  If
 *                  this returns zero then the interface is no
 *                  longer valid.
 */

STDMETHODIMP_(ULONG) RECTENUM_Release(PENUMRECT pEnum)
    {
    PRECTENUMERATOR       pRE=(PRECTENUMERATOR)pEnum;

    if (0!=--pRE->m_cRef)
        return pRE->m_cRef;

    RECTENUM_Destructor(pRE);
    return 0;
    }

/*
 * RECTENUM_Next
 *
 * Purpose:
 *  Returns the next rectangle in the enumerator.
 *
 * Parameters:
 *  pEnum           PENUMRECT to affect
 *  cRect           DWORD number of RECTs to return
 *  prc             LPRECT in which to store the returned RECT.
 *  pdwRects        LPDWORD in which to store the number of
 *                  structs returned.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE otherwise,
 */

STDMETHODIMP RECTENUM_Next(PENUMRECT pEnum, DWORD cRect, LPRECT prc
    , LPDWORD pdwRects)
    {
    PRECTENUMERATOR     pRE=(PRECTENUMERATOR)pEnum;
    DWORD               cRectReturn=0L;

    if (NULL==pdwRects)
        {
        if (1L!=cRect)
            return ResultFromScode(S_FALSE);
        }
    else
        *pdwRects=0L;

    if (NULL==prc || (pRE->m_iCur >= CRECTS))
        return ResultFromScode(S_FALSE);

    while (pRE->m_iCur < CRECTS && cRect > 0)
        {
        *prc++=pRE->m_rgrc[pRE->m_iCur++];
        cRectReturn++;
        cRect--;
        }

    if (NULL!=pdwRects)
        *pdwRects=cRectReturn;

    return NOERROR;
    }

/*
 * RECTENUM_Skip
 *
 * Purpose:
 *  Skips the next n elements in the enumerator.
 *
 * Parameters:
 *  pEnum           PENUMRECT to affect
 *  cSkip           DWORD number of elements to skip.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE if we could not
 *                  skip the requested number.
 */

STDMETHODIMP RECTENUM_Skip(PENUMRECT pEnum, DWORD cSkip)
    {
    PRECTENUMERATOR     pRE=(PRECTENUMERATOR)pEnum;

    if ((pRE->m_iCur+cSkip) >= CRECTS)
        return ResultFromScode(S_FALSE);

    pRE->m_iCur+=cSkip;
    return NOERROR;
    }

/*
 * RECTENUM_Reset
 *
 * Purpose:
 *  Resets the current element in the enumerator to zero.
 *
 * Parameters:
 *  pEnum           PENUMRECT to affect
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP RECTENUM_Reset(PENUMRECT pEnum)
    {
    PRECTENUMERATOR     pRE=(PRECTENUMERATOR)pEnum;

    pRE->m_iCur=0;
    return NOERROR;
    }

/*
 * RECTENUM_Clone
 *
 * Purpose:
 *  Creates a copy enumerator.
 *
 * Parameters:
 *  ppEnum          PENUMRECT * in which to store the clone.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, error code otherwise.
 */

STDMETHODIMP RECTENUM_Clone(PENUMRECT pEnum, PENUMRECT *ppEnum)
    {
    PRECTENUMERATOR     pRE=(PRECTENUMERATOR)pEnum;

    if (CreateRECTEnumeratorC(ppEnum))
        {
        /*
         * Copy the current index.  The typecast is safe because
         * we know that the IEnumRECT from the creation function
         * is really a RECTENUMERATOR pointer.
         */
        ((PRECTENUMERATOR)(*ppEnum))->m_iCur=pRE->m_iCur;
        return NOERROR;
        }

    return ResultFromScode(E_OUTOFMEMORY);
    }


ENUMCPP.CPP

/*
 * ENUMCPP.CPP
 * Enumerator in C++ Chapter 2
 *
 * Implements the CEnumRECT class
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "enumrect.h"

/*
 * CreateRECTEnumeratorCPP
 *
 * Purpose:
 *  Creates an enumerator object returning an IEnumRECT interface.
 *
 * Parameters:
 *  ppEnum          PENUMRECT * in which to return the
 *                  interface pointer on the created object.
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CreateRECTEnumeratorCPP(PENUMRECT *ppEnum)
    {
    PCEnumRect  pER;
    HRESULT     hr;

    if (NULL==ppEnum)
        return FALSE;

    //Create the object
    pER=new CEnumRect();

    if (NULL==pER)
        return FALSE;

    //Get the interface, which calls AddRef
    hr=pER->QueryInterface(IID_IEnumRECT, (void **)ppEnum);
    return SUCCEEDED(hr);
    }

/*
 * CEnumRect::CEnumRect
 * CEnumRect::~CEnumRect
 *
 * Constructor Parameters:
 *  None
 */

CEnumRect::CEnumRect(void)
    {
    UINT        i;

    //Initialize the array of rectangles
    for (i=0; i < CRECTS; i++)
        SetRect(&m_rgrc[i], i, i*2, i*3, i*4);

    //Ref counts always start at zero
    m_cRef=0;

    //Current pointer is the first element.
    m_iCur=0;

    return;
    }

CEnumRect::~CEnumRect(void)
    {
    return;
    }

/*
 * CEnumRect::QueryInterface
 *
 * Purpose:
 *  Manages interfaces for the CEnumRect object.
 *
 * Parameters:
 *  riid            REFIID of the interface to return.
 *  ppv             PPVOID in which to return the pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_NOINTERFACE if the
 *                  interface is not supported.
 */

STDMETHODIMP CEnumRect::QueryInterface(REFIID riid, PPVOID ppv)
    {
    //Always NULL the out-parameters
    *ppv=NULL;

    /*
     * No explicit typecast necessary since we singly derive
     * from IEnumRECT.
     */
    if (IID_IUnknown==riid || IID_IEnumRECT==riid)
        *ppv=this;

    if (NULL==*ppv)
        return ResultFromScode(E_NOINTERFACE);

    //AddRef any interface we'll return.
    ((LPUNKNOWN)*ppv)->AddRef();
    return NOERROR;
    }

/*
 * CEnumRect::AddRef
 *
 * Purpose:
 *  Increments the reference count on the object.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  ULONG           New reference count.
 */

STDMETHODIMP_(ULONG) CEnumRect::AddRef(void)
    {
    return ++m_cRef;
    }

/*
 * CEnumRect::Release
 *
 * Purpose:
 *  Indicates that someone on whose behalf we once AddRef'd has
 *  finished with the object.  We decrement our reference count
 *  and if zero, we delete the object.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  ULONG           Current reference count after decrement.  If
 *                  this returns zero then the interface is no
 *                  longer valid.
 */

STDMETHODIMP_(ULONG) CEnumRect::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CEnumRect::Next
 *
 * Purpose:
 *  Returns the next rectangle in the enumerator.
 *
 * Parameters:
 *  cRect           DWORD number of RECTs to return
 *  prc             LPRECT in which to store the returned RECT.
 *  pdwRects        LPDWORD in which to store the number of
 *                  structs returned.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE otherwise,
 */

STDMETHODIMP CEnumRect::Next(DWORD cRect, LPRECT prc, LPDWORD pdwRects)
    {
    DWORD           cRectReturn=0L;

    if (NULL==pdwRects)
        {
        if (1L!=cRect)
            return ResultFromScode(S_FALSE);
        }
    else
        *pdwRects=0L;

    if (NULL==prc || (m_iCur >= CRECTS))
        return ResultFromScode(S_FALSE);

    while (m_iCur < CRECTS && cRect > 0)
        {
        *prc++=m_rgrc[m_iCur++];
        cRectReturn++;
        cRect--;
        }

    if (NULL!=pdwRects)
        *pdwRects=cRectReturn;

    return NOERROR;
    }

/*
 * CEnumRect::Skip
 *
 * Purpose:
 *  Skips the next n elements in the enumerator.
 *
 * Parameters:
 *  cSkip           DWORD number of elements to skip.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE if we could not
 *                  skip the requested number.
 */

STDMETHODIMP CEnumRect::Skip(DWORD cSkip)
    {
    if ((m_iCur+cSkip) >= CRECTS)
        return ResultFromScode(S_FALSE);

    m_iCur+=cSkip;
    return NOERROR;
    }

/*
 * CEnumRect::Reset
 *
 * Purpose:
 *  Resets the current element in the enumerator to zero.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CEnumRect::Reset(void)
    {
    m_iCur=0;
    return NOERROR;
    }

/*
 * CImpEnumRECT::Clone
 *
 * Purpose:
 *  Creates a copy enumerator.
 *
 * Parameters:
 *  ppEnum          PENUMRECT * in which to store the clone.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, error code otherwise.
 */

STDMETHODIMP CEnumRect::Clone(PENUMRECT *ppEnum)
    {
    if (CreateRECTEnumeratorCPP(ppEnum))
        {
        /*
         * Copy the current index.  The typecast is safe because
         * we know that the IEnumRECT from the creation function
         * is really a CEnumRect pointer.
         */
        ((PCEnumRect)(*ppEnum))->m_iCur=m_iCur;
        return NOERROR;
        }

    return ResultFromScode(E_OUTOFMEMORY);
    }


ENUMRECT.CPP

/*
 * ENUMRECT.CPP
 * C/C++ Enumerator Demonstartion Chapter 2
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "enumrect.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    MSG     msg;
    PAPP    pApp;

    pApp=new CApp(hInst, hInstPrev, nCmdShow);

    if (NULL==pApp)
        return -1;

    if (pApp->Init())
        {
        while (GetMessage(&msg, NULL, 0,0 ))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    delete pApp;
    return msg.wParam;
    }

/*
 * EnumWndProc
 *
 * Purpose:
 *  Standard window class procedure.
 */

LRESULT APIENTRY EnumWndProc(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    PAPP        pApp;
    RECT        rc;
    DWORD       cRect;
    BOOL        fRes;
    PENUMRECT   pClone;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    pApp=(PAPP)GetWindowLong(hWnd, ENUMWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pApp=(PAPP)(((LPCREATESTRUCT)lParam)->lpCreateParams);
            SetWindowLong(hWnd, ENUMWL_STRUCTURE, (LONG)pApp);
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_COMMAND:
            switch (wID)
                {
                case IDM_ENUMCREATEC:
                    if (NULL!=pApp->m_pIEnumRect)
                        pApp->m_pIEnumRect->Release();

                    fRes=CreateRECTEnumeratorC(&pApp->m_pIEnumRect);

                    pApp->Message(fRes ? TEXT("C Object created")
                        : TEXT("C Object creation failed"));

                    break;

                case IDM_ENUMCREATECPP:
                    if (NULL!=pApp->m_pIEnumRect)
                        pApp->m_pIEnumRect->Release();

                    fRes=CreateRECTEnumeratorCPP(&pApp->m_pIEnumRect);

                    pApp->Message(fRes ? TEXT("C++ Object created")
                        : TEXT("C++ Object creation failed"));

                    break;

                case IDM_ENUMRELEASE:
                    if (NULL==pApp->m_pIEnumRect)
                        {
                        pApp->Message(TEXT("There is no object"));
                        break;
                        }

                    if (0==pApp->m_pIEnumRect->Release())
                        {
                        pApp->m_pIEnumRect=NULL;
                        pApp->Message(TEXT("Object released"));
                        }

                    break;

                case IDM_ENUMRUNTHROUGH:
                    if (NULL==pApp->m_pIEnumRect)
                        {
                        pApp->Message(TEXT("There is no object"));
                        break;
                        }

                    while (pApp->m_pIEnumRect->Next(1, &rc, &cRect))
                        ;

                    pApp->Message(TEXT("Enumeration complete"));
                    break;

                case IDM_ENUMEVERYTHIRD:
                    if (NULL==pApp->m_pIEnumRect)
                        {
                        pApp->Message(TEXT("There is no object"));
                        break;
                        }

                    while (NOERROR==pApp->m_pIEnumRect->Next(1
                        , &rc, &cRect))
                        {
                        if (NOERROR!=pApp->m_pIEnumRect->Skip(2))
                            break;
                        }

                    pApp->Message(TEXT("Enumeration complete"));
                    break;

                case IDM_ENUMRESET:
                    if (NULL==pApp->m_pIEnumRect)
                        {
                        pApp->Message(TEXT("There is no object"));
                        break;
                        }

                    pApp->m_pIEnumRect->Reset();
                    pApp->Message(TEXT("Reset complete"));
                    break;

                case IDM_ENUMCLONE:
                    if (NULL==pApp->m_pIEnumRect)
                        {
                        pApp->Message(TEXT("There is no object"));
                        break;
                        }

                    pApp->m_pIEnumRect->Clone((PENUMRECT *)&pClone);

                    if (NULL==pClone)
                        pApp->Message(TEXT("Clone failed"));
                    else
                        {
                        pApp->Message(TEXT("Clone succeeded"));
                        pClone->Release();
                        }
                    break;

                case IDM_ENUMEXIT:
                    PostMessage(hWnd, WM_CLOSE, 0, 0L);
                    break;
                }
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * CApp::CApp
 * CApp::~CApp
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the Application from WinMain
 *  hInstPrev       HINSTANCE of a previous instance from WinMain
 *  nCmdShow        UINT specifying how to show the app window,
 *                  from WinMain.
 */

CApp::CApp(HINSTANCE hInst, HINSTANCE hInstPrev
    , UINT nCmdShow)
    {
    //Initialize WinMain parameter holders.
    m_hInst     =hInst;
    m_hInstPrev =hInstPrev;
    m_nCmdShow  =nCmdShow;

    m_hWnd=NULL;
    m_pIEnumRect=NULL;

    return;
    }

CApp::~CApp(void)
    {
    //Free the enumerator object if we have one.
    if (NULL!=m_pIEnumRect)
        m_pIEnumRect->Release();

    return;
    }

/*
 * CApp::Init
 *
 * Purpose:
 *  Initializes an CApp object by registering window classes,
 *  creating the main window, and doing anything else prone to
 *  failure.  If this function fails the caller should guarantee
 *  that the destructor is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::Init(void)
    {
    WNDCLASS    wc;

    if (!m_hInstPrev)
        {
        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = EnumWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = CBWNDEXTRA;
        wc.hInstance      = m_hInst;
        wc.hIcon          = LoadIcon(m_hInst, TEXT("Icon"));
        wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName   = MAKEINTRESOURCE(IDR_MENU);
        wc.lpszClassName  = TEXT("ENUMRECT");

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

    m_hWnd=CreateWindow(TEXT("ENUMRECT"), TEXT("RECT Enumerator")
        , WS_MINIMIZEBOX | WS_OVERLAPPEDWINDOW
        ,35, 35, 350, 250, NULL, NULL, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    ShowWindow(m_hWnd, m_nCmdShow);
    UpdateWindow(m_hWnd);

    return TRUE;
    }

/*
 * CApp::Message
 *
 * Purpose:
 *  Displays a message in the client area of the window.  This is
 *  just to centralize the call to simpify other code.
 *
 * Parameters:
 *  psz             LPTSTR to the string to display.
 *
 * Return Value:
 *  None
 */

void CApp::Message(LPTSTR psz)
    {
    HDC     hDC;
    RECT    rc;

    hDC=GetDC(m_hWnd);
    GetClientRect(m_hWnd, &rc);

    SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
    SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));

    /*
     * We'll just be sloppy and clear the whole window as
     * well as write the string with one ExtTextOut call.
     * No word wrapping here...
     */

    ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rc, psz, lstrlen(psz), NULL);
    ReleaseDC(m_hWnd, hDC);
    return;
    }


ENUMRECT.H

/*
 * ENUMRECT.H
 * C/C++ Enumerator Demonstrtion Chapter 2
 *
 * Definitions, classes, and prototypes
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _ENUMRECT_H_
#define _ENUMRECT_H_

#define CHAPTER2
#include <inole.h>
#ifndef WIN32
#include <malloc.h>
#endif
#include "ienum.h"  //Interface definitions

//Menu Resource ID and Commands
#define IDR_MENU                    1

#define IDM_ENUMCREATEC             100
#define IDM_ENUMCREATECPP           101
#define IDM_ENUMRELEASE             102
#define IDM_ENUMRUNTHROUGH          103
#define IDM_ENUMEVERYTHIRD          104
#define IDM_ENUMRESET               105
#define IDM_ENUMCLONE               106
#define IDM_ENUMEXIT                107

//Number of rects that objects with IEnumRECT support (for demo)
#define CRECTS      15

//Skip the C++ stuff when this file is included from ENUMC.C
#ifdef __cplusplus

//ENUMRECT.CPP
LRESULT APIENTRY EnumWndProc(HWND, UINT, WPARAM, LPARAM);

class CApp
    {
    friend LRESULT APIENTRY EnumWndProc(HWND, UINT, WPARAM, LPARAM);

    protected:
        HINSTANCE       m_hInst;            //WinMain parameters
        HINSTANCE       m_hInstPrev;
        UINT            m_nCmdShow;

        HWND            m_hWnd;             //Main window handle
        PENUMRECT       m_pIEnumRect;       //Enumerator object

    public:
        CApp(HINSTANCE, HINSTANCE, UINT);
        ~CApp(void);

        BOOL Init(void);
        void Message(LPTSTR);
    };

typedef CApp *PAPP;

#define CBWNDEXTRA          sizeof(PAPP)
#define ENUMWL_STRUCTURE    0

//ENUMCPP.CPP

/*
 * The class definition for an object that singly implements
 * IEnumRECT in C++.
 */
class CEnumRect : public IEnumRECT
    {
    private:
        DWORD           m_cRef;         //Reference count
        DWORD           m_iCur;         //Current enum position
        RECT            m_rgrc[CRECTS]; //RECTS we enumerate

    public:
        CEnumRect(void);
        ~CEnumRect(void);

        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IEnumRECT members
        STDMETHODIMP Next(ULONG, LPRECT, ULONG *);
        STDMETHODIMP Skip(ULONG);
        STDMETHODIMP Reset(void);
        STDMETHODIMP Clone(PENUMRECT *);
    };

typedef CEnumRect *PCEnumRect;

//Function that creates one of these objects
BOOL CreateRECTEnumeratorCPP(PENUMRECT *);

//End of __cplusplus

#else

//Start of non __cplusplus definitions

//ENUMC.C

/*
 * The structure definition for an object that singly implements
 * IEnumRECT in C.  We make a class by reusing the elements of
 * the IEnumRECT structure thereby inheriting from it, albeit
 * manually.
 */

typedef struct tagRECTENUMERATOR
    {
    IEnumRECTVtbl  *lpVtbl;
    DWORD           m_cRef;         //Reference count
    DWORD           m_iCur;         //Current enum position
    RECT            m_rgrc[CRECTS]; //RECTS we enumerate
    } RECTENUMERATOR, *PRECTENUMERATOR;

/*
 * In C we have to separately declare member functions with
 * globally unique names, so prefixing with the class name
 * should remove any conflicts.
 */

PRECTENUMERATOR RECTENUM_Constructor(void);
void            RECTENUM_Destructor(PRECTENUMERATOR);

STDMETHODIMP    RECTENUM_QueryInterface(PENUMRECT, REFIID, PPVOID);
STDMETHODIMP_(ULONG) RECTENUM_AddRef(PENUMRECT);
STDMETHODIMP_(ULONG) RECTENUM_Release(PENUMRECT);
STDMETHODIMP    RECTENUM_Next(PENUMRECT, DWORD, LPRECT, LPDWORD);
STDMETHODIMP    RECTENUM_Skip(PENUMRECT, DWORD);
STDMETHODIMP    RECTENUM_Reset(PENUMRECT);
STDMETHODIMP    RECTENUM_Clone(PENUMRECT, PENUMRECT *);

//End of __cplusplus conditions

#endif

#ifdef __cplusplus
extern "C"
    {
#endif
    //Function that creates one of these objects
    BOOL CreateRECTEnumeratorC(PENUMRECT *);
#ifdef __cplusplus
    }
#endif

#endif //_ENUMRECT_H_


IENUM.H

/*
 * IENUM0.H
 *
 * Definition of an IEnumRECT interface as an example of OLE
 * interfaces as they appear in C and C++.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IENUM0_H_
#define _IENUM0_H_

//C++ Definition of an interface.
#ifdef __cplusplus

typedef struct IEnumRECT IEnumRECT;
typedef IEnumRECT *PENUMRECT;

//This is the interface:  a struct of pure virtual functions.
struct IEnumRECT
    {
    STDMETHOD(QueryInterface)(REFIID, PPVOID)=0;
    STDMETHOD_(ULONG,AddRef)(void)=0;
    STDMETHOD_(ULONG,Release)(void)=0;
    STDMETHOD(Next)(DWORD, LPRECT, LPDWORD)=0;
    STDMETHOD(Skip)(DWORD)=0;
    STDMETHOD(Reset)(void)=0;
    STDMETHOD(Clone)(PENUMRECT *)=0;
    };

#else   //!__cplusplus

/*
 * A C interface is explicitly a structure containing a long
 * pointer to a virtual function table that we have to
 * initialize explicitly.
 */

typedef struct
    {
    struct IEnumRECTVtbl FAR *lpVtbl;
    } IEnumRECT, *PENUMRECT;

//This is just a convenient naming
typedef struct IEnumRECTVtbl IEnumRECTVtbl;

struct IEnumRECTVtbl
    {
    STDMETHOD(QueryInterface)(PENUMRECT, REFIID, PPVOID);
    STDMETHOD_(ULONG, AddRef)(PENUMRECT);
    STDMETHOD_(ULONG, Release)(PENUMRECT);
    STDMETHOD(Next)(PENUMRECT, DWORD, LPRECT, LPDWORD);
    STDMETHOD(Skip)(PENUMRECT, DWORD);
    STDMETHOD(Reset)(PENUMRECT);
    STDMETHOD(Clone)(PENUMRECT, PENUMRECT *);
    };

#endif  //!__cplusplus

#endif //_IENUM0_H_


MALLOC1.C

/*
 * MALLOC1.C
 * C Malloc Demonstration Chapter 2
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "malloc1.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    MSG         msg;
    PAPP        pApp;
    DWORD       dwVer;
    HRESULT     hr;

    SETMESSAGEQUEUE;

    //Make sure COM is the right version
    dwVer=CoBuildVersion();

    if (rmm!=HIWORD(dwVer))
        return 0;

    //Initialize OLE
    hr=CoInitialize(NULL);

    if (FAILED(hr))
        return 0;

    pApp=App_Create(hInst, hInstPrev, nCmdShow);

    if (NULL!=pApp)
        {
        if (App_Init(pApp))
            {
            while (GetMessage(&msg, NULL, 0,0 ))
                {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
                }
            }

        App_Destroy(pApp);
        }

    //Only call this if CoInitialize worked
    CoUninitialize();
    return 0;
    }

/*
 * MallocWndProc
 *
 * Purpose:
 *  Standard window class procedure.
 */

LRESULT APIENTRY MallocWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PAPP        pApp;
    ULONG       cb;
    UINT        i;
    BOOL        fResult;

    pApp=(PAPP)GetWindowLong(hWnd, MALLOCWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pApp=(PAPP)(((LPCREATESTRUCT)lParam)->lpCreateParams);
            SetWindowLong(hWnd, MALLOCWL_STRUCTURE, (LONG)pApp);
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_COMMAND:
            switch (LOWORD(wParam))
                {
                case IDM_COGETMALLOC:
                    App_GetAllocator(pApp);
                    break;

                case IDM_RELEASE:
                    if (!App_HaveAllocator(pApp))
                        break;

                    App_FreeAllocations(pApp, TRUE);
                    App_Message(pApp, TEXT("Release complete"));
                    break;

                case IDM_ALLOC:
                    if (!App_HaveAllocator(pApp))
                        break;

                    fResult=App_DoAllocations(pApp, FALSE);

                    App_Message(pApp, fResult
                        ? TEXT("Alloc succeeded")
                        : TEXT("Alloc failed"));

                    break;

                case IDM_FREE:
                    if (!App_HaveAllocations(pApp))
                        break;

                    App_FreeAllocations(pApp, FALSE);
                    App_Message(pApp, TEXT("Free complete"));
                    break;

                case IDM_REALLOC:
                    if (!App_HaveAllocator(pApp))
                        break;

                    fResult=App_DoAllocations(pApp, TRUE);

                    App_Message(pApp, fResult
                        ? TEXT("Realloc succeeded")
                        : TEXT("Realloc failed"));

                    break;

                case IDM_GETSIZE:
                    if (!App_HaveAllocations(pApp))
                        break;

                    fResult=TRUE;

                    for (i=0; i < CALLOCS; i++)
                        {
                        cb=pApp->m_pIMalloc->lpVtbl->GetSize
                            (pApp->m_pIMalloc, pApp->m_rgpv[i]);

                        /*
                         * We test that the size is *at least*
                         * what we wanted.
                         */
                        fResult &= (pApp->m_rgcb[i] <= cb);
                        }

                    App_Message(pApp, fResult
                        ? TEXT("Sizes matched")
                        : TEXT("Sizes mismatch"));

                    break;

                case IDM_DIDALLOC:
                    if (!App_HaveAllocations(pApp))
                        break;

                    /*
                     * DidAlloc may return -1 if it does not know
                     * whether or not it actually allocated
                     * something.  In that case we just blindly
                     * & in a -1 with no affect.
                     */

                    fResult=(BOOL)-1;

                    for (i=0; i < CALLOCS; i++)
                        {
                        fResult &= pApp->m_pIMalloc->lpVtbl->DidAlloc
                            (pApp->m_pIMalloc, pApp->m_rgpv[i]);
                        }

                    if (0==fResult)
                        App_Message(pApp, TEXT("DidAlloc is FALSE"));

                    if (1==fResult)
                        App_Message(pApp, TEXT("DidAlloc is TRUE"));

                    if (-1==fResult)
                        App_Message(pApp, TEXT("DidAlloc is unknown"));

                    break;

                case IDM_HEAPMINIMIZE:
                    if (!App_HaveAllocator(pApp))
                        break;

                    pApp->m_pIMalloc->lpVtbl->HeapMinimize
                        (pApp->m_pIMalloc);
                    App_Message(pApp, TEXT("HeapMinimize finished"));
                    break;

                case IDM_EXIT:
                    PostMessage(hWnd, WM_CLOSE, 0, 0L);
                    break;
                }
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * App_Create
 * App_Destroy
 *
 * Constructor Parameters: (from WinMain)
 *  hInst           HINSTANCE of the application.
 *  hInstPrev       HINSTANCE of a previous instance.
 *  nCmdShow        UINT specifying how to show the app window.
 */

PAPP App_Create(HINSTANCE hInst, HINSTANCE hInstPrev, UINT nCmdShow)
    {
    UINT        i;
    ULONG       cb;
    PAPP        pApp;

    pApp=(PAPP)malloc(sizeof(APP));

    if (NULL==pApp)
        return NULL;

    pApp->m_hInst       =hInst;
    pApp->m_hInstPrev   =hInstPrev;
    pApp->m_nCmdShow    =nCmdShow;

    pApp->m_hWnd        =NULL;
    pApp->m_pIMalloc    =NULL;
    pApp->m_fAllocated  =FALSE;

    //100 is arbitrary.  IMalloc can handle larger.
    cb=100;

    for (i=0; i < CALLOCS; i++)
        {
        pApp->m_rgcb[i]=cb;
        pApp->m_rgpv[i]=NULL;

        cb*=2;
        }

    return pApp;
    }

void App_Destroy(PAPP pApp)
    {
    App_FreeAllocations(pApp, TRUE);
    free(pApp);
    return;
    }

/*
 * App_Init
 *
 * Purpose:
 *  Initializes an CApp object by registering window classes,
 *  creating the main window, and doing anything else prone to
 *  failure.  If this function fails the caller should insure
 *  that the destructor is called.
 *
 * Parameters:
 *  pApp            PAPP identifying the object.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL App_Init(PAPP pApp)
    {
    WNDCLASS    wc;

    if (!pApp->m_hInstPrev)
        {
        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = MallocWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = CBWNDEXTRA;
        wc.hInstance      = pApp->m_hInst;
        wc.hIcon          = LoadIcon(pApp->m_hInst, TEXT("Icon"));
        wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName   = MAKEINTRESOURCE(IDR_MENU);
        wc.lpszClassName  = TEXT("MALLOC");

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

    pApp->m_hWnd=CreateWindow(TEXT("MALLOC")
        , TEXT("C Malloc Demo"), WS_OVERLAPPEDWINDOW, 35, 35
        , 350, 250, NULL, NULL, pApp->m_hInst, pApp);

    if (NULL==pApp->m_hWnd)
        return FALSE;

    ShowWindow(pApp->m_hWnd, pApp->m_nCmdShow);
    UpdateWindow(pApp->m_hWnd);

    return TRUE;
    }

/*
 * App_GetAllocator
 *
 * Purpose:
 *  Retrieves the current allocator.
 *
 * Parameters:
 *  pApp            PAPP identifying the object.
 *
 * Return Value:
 *  None
 */

void App_GetAllocator(PAPP pApp)
    {
    HRESULT     hr;
    TCHAR       szTemp[80];

    //This also releases pApp->m_pIMalloc
    App_FreeAllocations(pApp, TRUE);

    hr=CoGetMalloc(MEMCTX_TASK, &pApp->m_pIMalloc);

    wsprintf(szTemp, TEXT("CoGetMalloc %s")
        , SUCCEEDED(hr) ? TEXT("succeeded") : TEXT("failed"));

    App_Message(pApp, szTemp);
    return;
    }

/*
 * App_HaveAllocator
 *
 * Purpose:
 *  Checks if there's a valid allocator and displays a
 *  message if not.
 *
 * Parameters:
 *  pApp            PAPP identifying the object.
 *
 * Return Value:
 *  BOOL            TRUE if there is an allocator, FALSE otherwise.
 */

BOOL App_HaveAllocator(PAPP pApp)
    {
    if (NULL==pApp->m_pIMalloc)
        {
        App_Message(pApp, TEXT("Create the allocator first"));
        return FALSE;
        }

    return TRUE;
    }

/*
 * App_DoAllocations
 *
 * Purpose:
 *  Centralized place to clean up allocations made on
 *  the current IMalloc.
 *
 * Parameters:
 *  pApp            PAPP identifying the object.
 *  fRelease        BOOL indicating if we're to
 *                  IMalloc::Release as well.
 *
 * Return Value:
 *  BOOL            TRUE if all allocations succeeded.
 */

BOOL App_DoAllocations(PAPP pApp, BOOL fRealloc)
    {
    UINT        i;
    ULONG       iByte;
    BOOL        fResult=TRUE;
    ULONG       cb;
    LPVOID      pv;

    if (!fRealloc)
        App_FreeAllocations(pApp, FALSE);

    for (i=0; i < CALLOCS; i++)
        {
        //cb is set in the code below for later initialization
        if (fRealloc)
            {
            pApp->m_rgcb[i]+=128;
            cb=pApp->m_rgcb[i];

            //Old memory is not freed if Realloc fails
            pv=pApp->m_pIMalloc->lpVtbl->Realloc(pApp->m_pIMalloc
                , pApp->m_rgpv[i], cb);
            }
        else
            {
            cb=pApp->m_rgcb[i];
            pv=pApp->m_pIMalloc->lpVtbl->Alloc(pApp->m_pIMalloc, cb);
            }

        pApp->m_rgpv[i]=pv;

        //Fill the memory with letters.
        if (NULL!=pv)
            {
            LPBYTE  pb=(LPBYTE)pv;

            for (iByte=0; iByte < cb; iByte++)
                *pb++=('a'+i);
            }

        fResult &= (NULL!=pv);
        }

    pApp->m_fAllocated=fResult;

    //Clean up whatever we might have allocated
    if (!fResult)
        App_FreeAllocations(pApp, FALSE);

    return fResult;
    }

/*
 * App_HaveAllocations
 *
 * Purpose:
 *  Checks if we have allocated memory from the current allocator,
 *  displaying a message if not.
 *
 * Parameters:
 *  pApp            PAPP identifying the object.
 *
 * Return Value:
 *  BOOL            TRUE if there are allocations, FALSE otherwise.
 */

BOOL App_HaveAllocations(PAPP pApp)
    {
    if (!App_HaveAllocator(pApp))
        return FALSE;

    if (!pApp->m_fAllocated)
        {
        App_Message(pApp, TEXT("Choose Alloc first"));
        return FALSE;
        }

    return TRUE;
    }

/*
 * App_FreeAllocations
 *
 * Purpose:
 *  Centralized place to clean up allocations made on
 *  the current IMalloc.
 *
 * Parameters:
 *  pApp            PAPP identifying the object.
 *  fRelease        BOOL indicating if we're to
 *                  IMalloc::Release as well.
 *
 * Return Value:
 *  None
 */

void App_FreeAllocations(PAPP pApp, BOOL fRelease)
    {
    UINT    i;

    if (NULL==pApp->m_pIMalloc)
        return;

    if (pApp->m_fAllocated)
        {
        for (i=0; i < CALLOCS; i++)
            {
            if (NULL!=pApp->m_rgpv[i])
                {
                pApp->m_pIMalloc->lpVtbl->Free(pApp->m_pIMalloc
                    , pApp->m_rgpv[i]);
                }

            pApp->m_rgpv[i]=NULL;
            }

        pApp->m_fAllocated=FALSE;
        }

    if (fRelease)
        {
        pApp->m_pIMalloc->lpVtbl->Release(pApp->m_pIMalloc);
        pApp->m_pIMalloc=NULL;
        }

    return;
    }

/*
 * App_Message
 *
 * Purpose:
 *  Displays a message in the client area of the window.  This is
 *  just to centralize the call to simpify other code.
 *
 * Parameters:
 *  pApp            PAPP identifying the object.
 *  psz             LPTSTR to the string to display.
 *
 * Return Value:
 *  None
 */

void App_Message(PAPP pApp, LPTSTR psz)
    {
    HDC     hDC;
    RECT    rc;

    hDC=GetDC(pApp->m_hWnd);
    GetClientRect(pApp->m_hWnd, &rc);

    SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
    SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));

    /*
     * We'll just be sloppy and clear the whole window as
     * well as write the string with one ExtTextOut call.
     * No word wrapping here...
     */

    ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rc, psz, lstrlen(psz), NULL);
    ReleaseDC(pApp->m_hWnd, hDC);
    return;
    }


MALLOC1.H

/*
 * MALLOC1.H
 * C Malloc Demonstration Chapter 2
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _MALLOC1_H_
#define _MALLOC1_H_

#include <inole.h>
#include <malloc.h>

//Menu Resource ID and Commands
#define IDR_MENU                1

#define IDM_COGETMALLOC         100
#define IDM_RELEASE             101
#define IDM_ALLOC               102
#define IDM_FREE                103
#define IDM_REALLOC             104
#define IDM_GETSIZE             105
#define IDM_DIDALLOC            106
#define IDM_HEAPMINIMIZE        107
#define IDM_EXIT                108

//MALLOC1.CPP
LRESULT APIENTRY MallocWndProc(HWND, UINT, WPARAM, LPARAM);

//How many allocations we'll perform
#define CALLOCS 10

typedef struct App
    {
    HINSTANCE       m_hInst;            //WinMain parameters
    HINSTANCE       m_hInstPrev;
    UINT            m_nCmdShow;

    HWND            m_hWnd;             //Main window handle
    IMalloc        *m_pIMalloc;         //From CoGetMalloc

    BOOL            m_fAllocated;       //We have allocations?
    ULONG           m_rgcb[CALLOCS];    //Sizes to allocate
    LPVOID          m_rgpv[CALLOCS];    //Allocated pointers
    } APP, *PAPP;

PAPP App_Create(HINSTANCE, HINSTANCE, UINT);
void App_Destroy(PAPP);
BOOL App_Init(PAPP);
void App_GetAllocator(PAPP);
BOOL App_HaveAllocator(PAPP);
BOOL App_DoAllocations(PAPP, BOOL);
BOOL App_HaveAllocations(PAPP);
void App_FreeAllocations(PAPP, BOOL);
void App_Message(PAPP, LPTSTR);

#define CBWNDEXTRA              sizeof(PAPP)
#define MALLOCWL_STRUCTURE      0

#endif //_MALLOC1_H_


MALLOC2.CPP

/*
 * MALLOC2.CPP
 * C++ Malloc Demonstration Chapter 2
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "malloc2.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    MSG         msg;
    PAPP        pApp;
    DWORD       dwVer;
    HRESULT     hr;

    SETMESSAGEQUEUE;

    //Make sure COM is the right version
    dwVer=CoBuildVersion();

    if (rmm!=HIWORD(dwVer))
        return 0;

    //Initialize OLE
    hr=CoInitialize(NULL);

    if (FAILED(hr))
        return 0;

    pApp=new CApp(hInst, hInstPrev, nCmdShow);

    if (NULL!=pApp)
        {
        if (pApp->Init())
            {
            while (GetMessage(&msg, NULL, 0,0 ))
                {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
                }
            }

        delete pApp;
        }

    //Only call this if CoInitialize worked
    CoUninitialize();
    return 0;
    }

/*
 * MallocWndProc
 *
 * Purpose:
 *  Standard window class procedure.
 */

LRESULT APIENTRY MallocWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PAPP        pApp;
    ULONG       cb;
    UINT        i;
    BOOL        fResult;

    pApp=(PAPP)GetWindowLong(hWnd, MALLOCWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pApp=(PAPP)(((LPCREATESTRUCT)lParam)->lpCreateParams);
            SetWindowLong(hWnd, MALLOCWL_STRUCTURE, (LONG)pApp);
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_COMMAND:
            switch (LOWORD(wParam))
                {
                case IDM_COGETMALLOC:
                    pApp->GetAllocator();
                    break;

                case IDM_RELEASE:
                    if (!pApp->HaveAllocator())
                        break;

                    pApp->FreeAllocations(TRUE);
                    pApp->Message(TEXT("Release complete"));
                    break;

                case IDM_ALLOC:
                    if (!pApp->HaveAllocator())
                        break;

                    fResult=pApp->DoAllocations(FALSE);

                    pApp->Message(fResult ? TEXT("Alloc succeeded")
                        : TEXT("Alloc failed"));

                    break;

                case IDM_FREE:
                    if (!pApp->HaveAllocations())
                        break;

                    pApp->FreeAllocations(FALSE);
                    pApp->Message(TEXT("Free complete"));
                    break;

                case IDM_REALLOC:
                    if (!pApp->HaveAllocator())
                        break;

                    fResult=pApp->DoAllocations(TRUE);

                    pApp->Message(fResult ? TEXT("Realloc succeeded")
                        : TEXT("Realloc failed"));

                    break;

                case IDM_GETSIZE:
                    if (!pApp->HaveAllocations())
                        break;

                    fResult=TRUE;

                    for (i=0; i < CALLOCS; i++)
                        {
                        cb=pApp->m_pIMalloc->GetSize(pApp->m_rgpv[i]);

                        /*
                         * We test that the size is *at least*
                         * what we wanted.
                         */
                        fResult &= (pApp->m_rgcb[i] <= cb);
                        }

                    pApp->Message(fResult ? TEXT("Sizes matched")
                        : TEXT("Sizes mismatch"));

                    break;

                case IDM_DIDALLOC:
                    if (!pApp->HaveAllocations())
                        break;

                    /*
                     * DidAlloc may return -1 if it does not know
                     * whether or not it actually allocated
                     * something.  In that case we just blindly
                     * & in a -1 with no affect.
                     */

                    fResult=(BOOL)-1;

                    for (i=0; i < CALLOCS; i++)
                        {
                        fResult &= pApp->m_pIMalloc
                            ->DidAlloc(pApp->m_rgpv[i]);
                        }

                    if (0==fResult)
                        pApp->Message(TEXT("DidAlloc is FALSE"));

                    if (1==fResult)
                        pApp->Message(TEXT("DidAlloc is TRUE"));

                    if (-1==fResult)
                        pApp->Message(TEXT("DidAlloc is unknown"));

                    break;

                case IDM_HEAPMINIMIZE:
                    if (!pApp->HaveAllocator())
                        break;

                    pApp->m_pIMalloc->HeapMinimize();
                    pApp->Message(TEXT("HeapMinimize finished"));
                    break;

                case IDM_EXIT:
                    PostMessage(hWnd, WM_CLOSE, 0, 0L);
                    break;
                }
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * CApp::CApp
 * CApp::~CApp
 *
 * Constructor Parameters: (from WinMain)
 *  hInst           HINSTANCE of the application.
 *  hInstPrev       HINSTANCE of a previous instance.
 *  nCmdShow        UINT specifying how to show the app window.
 */

CApp::CApp(HINSTANCE hInst, HINSTANCE hInstPrev
    , UINT nCmdShow)
    {
    UINT        i;
    ULONG       cb;

    m_hInst       =hInst;
    m_hInstPrev   =hInstPrev;
    m_nCmdShow    =nCmdShow;

    m_hWnd        =NULL;
    m_pIMalloc    =NULL;
    m_fAllocated  =FALSE;

    //100 is arbitrary.  IMalloc can handle larger.
    cb=100;

    for (i=0; i < CALLOCS; i++)
        {
        m_rgcb[i]=cb;
        m_rgpv[i]=NULL;

        cb*=2;
        }

    return;
    }

CApp::~CApp(void)
    {
    FreeAllocations(TRUE);
    return;
    }

/*
 * CApp::Init
 *
 * Purpose:
 *  Initializes an CApp object by registering window classes,
 *  creating the main window, and doing anything else prone to
 *  failure.  If this function fails the caller should insure
 *  that the destructor is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::Init(void)
    {
    WNDCLASS    wc;

    if (!m_hInstPrev)
        {
        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = MallocWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = CBWNDEXTRA;
        wc.hInstance      = m_hInst;
        wc.hIcon          = LoadIcon(m_hInst, TEXT("Icon"));
        wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName   = MAKEINTRESOURCE(IDR_MENU);
        wc.lpszClassName  = TEXT("MALLOC2");

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

    m_hWnd=CreateWindow(TEXT("MALLOC2"), TEXT("C++ Malloc Demo")
        , WS_OVERLAPPEDWINDOW, 35, 35, 350, 250, NULL, NULL
        , m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    ShowWindow(m_hWnd, m_nCmdShow);
    UpdateWindow(m_hWnd);

    return TRUE;
    }

/*
 * CApp::GetAllocator
 *
 * Purpose:
 *  Retrieves the current allocator.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CApp::GetAllocator(void)
    {
    HRESULT     hr;
    TCHAR       szTemp[80];

    //This also releases m_pIMalloc
    FreeAllocations(TRUE);

    hr=CoGetMalloc(MEMCTX_TASK, &m_pIMalloc);

    wsprintf(szTemp, TEXT("CoGetMalloc %s")
        , SUCCEEDED(hr) ? TEXT("succeeded") : TEXT("failed"));

    Message(szTemp);
    return;
    }

/*
 * CApp::HaveAllocator
 *
 * Purpose:
 *  Checks if there's a valid allocator and displays a
 *  message if not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if there is an allocator, FALSE otherwise.
 */

BOOL CApp::HaveAllocator(void)
    {
    if (NULL==m_pIMalloc)
        {
        Message(TEXT("Create the allocator first"));
        return FALSE;
        }

    return TRUE;
    }

/*
 * CApp::DoAllocations
 *
 * Purpose:
 *  Centralized place to clean up allocations made on
 *  the current IMalloc.
 *
 * Parameters:
 *  fRelease        BOOL indicating if we're to
 *                  IMalloc::Release as well.
 *
 * Return Value:
 *  BOOL            TRUE if all allocations succeeded.
 */

BOOL CApp::DoAllocations(BOOL fRealloc)
    {
    UINT        i;
    ULONG       iByte;
    BOOL        fResult=TRUE;
    ULONG       cb;
    LPVOID      pv;

    if (!fRealloc)
        FreeAllocations(FALSE);

    for (i=0; i < CALLOCS; i++)
        {
        //cb is set in the code below for later initialization
        if (fRealloc)
            {
            m_rgcb[i]+=128;
            cb=m_rgcb[i];

            //Old memory is not freed if Realloc fails
            pv=m_pIMalloc->Realloc(m_rgpv[i], cb);
            }
        else
            {
            cb=m_rgcb[i];
            pv=m_pIMalloc->Alloc(cb);
            }

        m_rgpv[i]=pv;

        //Fill the memory with letters.
        if (NULL!=pv)
            {
            LPBYTE  pb=(LPBYTE)pv;

            for (iByte=0; iByte < cb; iByte++)
                *pb++=('a'+i);
            }

        fResult &= (NULL!=pv);
        }

    m_fAllocated=fResult;

    //Clean up whatever we might have allocated
    if (!fResult)
        FreeAllocations(FALSE);

    return fResult;
    }

/*
 * CApp::HaveAllocations
 *
 * Purpose:
 *  Checks if we have allocated memory from the current allocator,
 *  displaying a message if not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if there are allocations, FALSE otherwise.
 */

BOOL CApp::HaveAllocations(void)
    {
    if (!HaveAllocator())
        return FALSE;

    if (!m_fAllocated)
        {
        Message(TEXT("Choose Alloc first"));
        return FALSE;
        }

    return TRUE;
    }

/*
 * CApp::FreeAllocations
 *
 * Purpose:
 *  Centralized place to clean up allocations made on
 *  the current IMalloc.
 *
 * Parameters:
 *  fRelease        BOOL indicating if we're to
 *                  IMalloc::Release as well.
 *
 * Return Value:
 *  None
 */

void CApp::FreeAllocations(BOOL fRelease)
    {
    UINT    i;

    if (NULL==m_pIMalloc)
        return;

    if (m_fAllocated)
        {
        for (i=0; i < CALLOCS; i++)
            {
            if (NULL!=m_rgpv[i])
                m_pIMalloc->Free(m_rgpv[i]);

            m_rgpv[i]=NULL;
            }

        m_fAllocated=FALSE;
        }

    if (fRelease)
        {
        m_pIMalloc->Release();
        m_pIMalloc=NULL;
        }

    return;
    }

/*
 * CApp::Message
 *
 * Purpose:
 *  Displays a message in the client area of the window.  This is
 *  just to centralize the call to simpify other code.
 *
 * Parameters:
 *  psz             LPTSTR to the string to display.
 *
 * Return Value:
 *  None
 */

void CApp::Message(LPTSTR psz)
    {
    HDC     hDC;
    RECT    rc;

    hDC=GetDC(m_hWnd);
    GetClientRect(m_hWnd, &rc);

    SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
    SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));

    /*
     * We'll just be sloppy and clear the whole window as
     * well as write the string with one ExtTextOut call.
     * No word wrapping here...
     */

    ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rc, psz, lstrlen(psz), NULL);
    ReleaseDC(m_hWnd, hDC);
    return;
    }


MALLOC2.H

/*
 * MALLOC2.H
 * C++ Malloc Demonstration Chapter 2
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _MALLOC2_H_
#define _MALLOC2_H_

#include <inole.h>

//Menu Resource ID and Commands
#define IDR_MENU                1

#define IDM_COGETMALLOC         100
#define IDM_RELEASE             101
#define IDM_ALLOC               102
#define IDM_FREE                103
#define IDM_REALLOC             104
#define IDM_GETSIZE             105
#define IDM_DIDALLOC            106
#define IDM_HEAPMINIMIZE        107
#define IDM_EXIT                108

//MALLOC2.CPP
LRESULT APIENTRY MallocWndProc(HWND, UINT, WPARAM, LPARAM);

//How many allocations we'll perform
#define CALLOCS 10

class CApp
    {
    friend LRESULT APIENTRY MallocWndProc(HWND, UINT, WPARAM, LPARAM);

    protected:
        HINSTANCE       m_hInst;            //WinMain parameters
        HINSTANCE       m_hInstPrev;
        UINT            m_nCmdShow;

        HWND            m_hWnd;             //Main window handle
        IMalloc        *m_pIMalloc;         //From CoGetMalloc

        BOOL            m_fAllocated;       //We have allocations?
        ULONG           m_rgcb[CALLOCS];    //Sizes to allocate
        LPVOID          m_rgpv[CALLOCS];    //Allocated pointers

    public:
        CApp(HINSTANCE, HINSTANCE, UINT);
        ~CApp(void);
        BOOL Init(void);

        void GetAllocator(void);
        BOOL HaveAllocator(void);
        BOOL DoAllocations(BOOL);
        BOOL HaveAllocations(void);
        void FreeAllocations(BOOL);

        void Message(LPTSTR);
    };

typedef CApp *PAPP;

#define CBWNDEXTRA              sizeof(PAPP)
#define MALLOCWL_STRUCTURE      0

#endif //_MALLOC2_H_


INTERFAC.H

/*
 * INTERFAC.H
 *
 * Definition of ISampleOne and ISampleTwo used to demonstrate
 * QueryInterface and different techniques of implementing multiple
 * interfaces on an object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _INTERFAC_H_
#define _INTERFAC_H_

DECLARE_INTERFACE_(ISampleOne, IUnknown)
    {
    //IUnknown members
    STDMETHOD(QueryInterface) (THIS_ REFIID, PPVOID) PURE;
    STDMETHOD_(ULONG,AddRef)  (THIS) PURE;
    STDMETHOD_(ULONG,Release) (THIS) PURE;

    //ISampleOne members
    STDMETHOD(GetMessage) (THIS_ LPTSTR, UINT) PURE;
    };

typedef ISampleOne *PSAMPLEONE;

DECLARE_INTERFACE_(ISampleTwo, IUnknown)
    {
    //IUnknown members
    STDMETHOD(QueryInterface) (THIS_ REFIID, PPVOID) PURE;
    STDMETHOD_(ULONG,AddRef)  (THIS) PURE;
    STDMETHOD_(ULONG,Release) (THIS) PURE;

    //ISampleTwo members
    STDMETHOD(GetString) (THIS_ LPTSTR, UINT) PURE;
    };

typedef ISampleTwo *PSAMPLETWO;

#endif //_INTERFAC_H_


OBJECT1.CPP

/*
 * OBJECT1.CPP
 *
 * Object implementation using interface implementations.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

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

//Message strings for the interface functions
static TCHAR szMessage[]=TEXT("Message from ISampleOne::GetMessage");
static TCHAR szString[] =TEXT("Message from ISampleTwo::GetString");

/*
 * CreateObject1
 *
 * Purpose:
 *  Creates an instance of CObject1 returning an IUnknown
 *  interface.
 *
 * Parameters:
 *  ppUnk           IUnknown ** in which to return the
 *                  interface pointer.
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CreateObject1(IUnknown **ppUnk)
    {
    HRESULT     hr;
    CObject1   *pObj;

    if (NULL==ppUnk)
        return FALSE;

    //Create the new object, which gives us a C++ object pointer
    pObj=new CObject1();

    if (NULL==pObj)
        return FALSE;

    if (!pObj->Init())
        return FALSE;

    /*
     * Now get the IUnknown interface to this object and make
     * sure that it's reference count is correct.  We could either
     * typecast pObj to IUnknown and call AddRef explicitly, or
     * we can simply call pObj->QueryInterface and let it do the
     * typecast and the AddRef for us.
     */
    hr=pObj->QueryInterface(IID_IUnknown, (PPVOID)ppUnk);
    return SUCCEEDED(hr);
    }

/*
 * CObject1::CObject1
 * CObject1::~CObject1
 *
 * Constructor Parameters:
 *  None
 */

CObject1::CObject1(void)
    {
    m_cRef=0;

    m_pImpISampleOne=NULL;
    m_pImpISampleTwo=NULL;

    return;
    }

CObject1::~CObject1(void)
    {
    //Delete the interface implementations created in Init
    DeleteInterfaceImp(m_pImpISampleTwo);
    DeleteInterfaceImp(m_pImpISampleOne);
    return;
    }

/*
 * CObject1::Init
 *
 * Purpose:
 *  Instantiates the interface implementations for this object.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeds, FALSE otherwise.
 */

BOOL CObject1::Init(void)
    {
    /*
     * Creating the interfaces means creating instances of
     * the interface implementation classes.  The constructor
     * parameters is a pointer to CObject1 that has the
     * IUnknown functions to which the interface implementations
     * delegate.
     */

    m_pImpISampleOne=new CImpISampleOne(this);

    if (NULL==m_pImpISampleOne)
        return FALSE;

    m_pImpISampleTwo=new CImpISampleTwo(this);

    if (NULL==m_pImpISampleTwo)
        return FALSE;

    return TRUE;
    }

/*
 * CObject1::QueryInterface
 *
 * Purpose:
 *  Manages the interfaces for this object which supports the
 *  IUnknown, ISampleOne, and ISampleTwo interfaces.
 *
 * Parameters:
 *  riid            REFIID of the interface to return.
 *  ppv             PPVOID in which to store the pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, E_NOINTERFACE if the
 *                  interface is not supported.
 */

STDMETHODIMP CObject1::QueryInterface(REFIID riid, PPVOID ppv)
    {
    //Always NULL the out-parameters
    *ppv=NULL;

    /*
     * IUnknown comes from CObject1.  Note that here and in
     * the lines below we do not need to explicitly typecast
     * the object pointers into interface pointers because
     * the vtables are identical.  If we had additional virtual
     * member functions in the object, we would have to cast
     * in order to set the right vtable.  This is demonstrated
     * in the multiple-inheritance version, CObject3.
     */
    if (IID_IUnknown==riid)
        *ppv=this;

    //Other interfaces come from interface implementations
    if (IID_ISampleOne==riid)
        *ppv=m_pImpISampleOne;

    if (IID_ISampleTwo==riid)
        *ppv=m_pImpISampleTwo;

    if (NULL==*ppv)
        return ResultFromScode(E_NOINTERFACE);

    //AddRef any interface we'll return.
    ((LPUNKNOWN)*ppv)->AddRef();
    return NOERROR;
    }

/*
 * CObject1::AddRef
 * CObject1::Release
 *
 * Reference counting members.  When Release sees a zero count
 * the object destroys itself.
 */

DWORD CObject1::AddRef(void)
    {
    return ++m_cRef;
    }

DWORD CObject1::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

//CImpISampleOne interface implementation

/*
 * CImpISampleOne::CImpISampleOne
 * CImpISampleOne::~CImpISampleOne
 *
 * Constructor Parameters:
 *  pObj            PCObject1 to the outer object
 */

CImpISampleOne::CImpISampleOne(PCObject1 pObj)
    {
    m_cRef=0;
    m_pObj=pObj;
    return;
    }

CImpISampleOne::~CImpISampleOne(void)
    {
    return;
    }

/*
 * CImpISampleOne::QueryInterface
 * CImpISampleOne::AddRef
 * CImpISampleOne::Release
 *
 * IUnknown members that delegate to m_pObj
 */

STDMETHODIMP CImpISampleOne::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pObj->QueryInterface(riid, ppv);
    }

DWORD CImpISampleOne::AddRef(void)
    {
    /*
     * We maintain an "interface reference count" for debugging
     * purposes, because the client of an object should match
     * AddRef and Release calls through each interface pointer.
     */
    ++m_cRef;
    return m_pObj->AddRef();
    }

DWORD CImpISampleOne::Release(void)
    {
    /*
     * m_cRef is again only for debugging.  It doesn't affect
     * CObject1 although the call to m_pObj->Release does.
     */
    --m_cRef;
    return m_pObj->Release();
    }

/*
 * CImpISampleOne::GetMessage
 *
 * Purpose:
 *  Returns a string to the caller.
 *
 * Parameters:
 *  psz             LPTSTR in which to store the string.
 *  cch             UINT size of psz.
 *
 * Return Value:
 *  HRESULT         NOERROR if the string is stored, E_OUTOFMEMORY
 *                  if the buffer is too small.
 */

STDMETHODIMP CImpISampleOne::GetMessage(LPTSTR psz, UINT cch)
    {
    if (NULL==psz)
        return ResultFromScode(E_POINTER);

    if (cch < (UINT)lstrlen(szMessage))
        return ResultFromScode(E_OUTOFMEMORY);

    lstrcpy(psz, szMessage);
    return NOERROR;
    }

//CImpISampleTwo interface implementation

/*
 * CImpISampleTwo::CImpISampleTwo
 * CImpISampleTwo::~CImpISampleTwo
 *
 * Constructor Parameters:
 *  pObj            PCObject1 to the outer object
 */

CImpISampleTwo::CImpISampleTwo(PCObject1 pObj)
    {
    m_cRef=0;
    m_pObj=pObj;
    return;
    }

CImpISampleTwo::~CImpISampleTwo(void)
    {
    return;
    }

/*
 * CImpISampleTwo::QueryInterface
 * CImpISampleTwo::AddRef
 * CImpISampleTwo::Release
 *
 * IUnknown members that delegate to m_pObj
 */

STDMETHODIMP CImpISampleTwo::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pObj->QueryInterface(riid, ppv);
    }

DWORD CImpISampleTwo::AddRef(void)
    {
    /*
     * We maintain an "interface reference count" for debugging
     * purposes, because the client of an object should match
     * AddRef and Release calls through each interface pointer.
     */
    ++m_cRef;
    return m_pObj->AddRef();
    }

DWORD CImpISampleTwo::Release(void)
    {
    /*
     * m_cRef is again only for debugging.  It doesn't affect
     * CObject1 although the call to m_pObj->Release does.
     */
    --m_cRef;
    return m_pObj->Release();
    }

/*
 * CImpISampleTwo::GetString
 *
 * Purpose:
 *  Returns a string to the caller.
 *
 * Parameters:
 *  psz             LPTSTR in which to store the string.
 *  cch             UINT size of psz.
 *
 * Return Value:
 *  HRESULT         NOERROR if the string is stored, E_OUTOFMEMORY
 *                  if the buffer is too small.
 */

STDMETHODIMP CImpISampleTwo::GetString(LPTSTR psz, UINT cch)
    {
    if (NULL==psz)
        return ResultFromScode(E_POINTER);

    if (cch < (UINT)lstrlen(szString))
        return ResultFromScode(E_OUTOFMEMORY);

    lstrcpy(psz, szString);
    return NOERROR;
    }


OBJECT1.H

/*
 * OBJECT1.H
 *
 * Definition of the CObject1 class that uses interface
 * implementations to provide ISampleOne and ISampleTwo.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _OBJECT1_H_
#define _OBJECT1_H_

//Creation function
BOOL CreateObject1(IUnknown **);

/*
 * The object we want to provide in OLE supports the IUnknown,
 * ISampleOne, and ISampleTwo interfaces.
 *
 * The C++ class, CObject1, implements these interfaces with
 * "interface implementations" where the C++ class itself inherits
 * from and implements IUnknown members and then contains
 * other C++ classes that each separately inherit from the other
 * interfaces.  The other classes are the "interface implementations."
 */

/*
 * In this technique you'll generally need forward references
 * like this for use in declaring the object class.
 */
class CImpISampleOne;
typedef CImpISampleOne *PCImpISampleOne;

class CImpISampleTwo;
typedef CImpISampleTwo *PCImpISampleTwo;

//The C++ class that manages the actual object.
class CObject1 : public IUnknown
    {
    /*
     * Usually interface implementations will need back pointers
     * to the object itself since this object usually manages
     * the important data members.  In that case, make the
     * interface implementation classes friends of the object.
     */

    friend CImpISampleOne;
    friend CImpISampleTwo;

    private:
        DWORD           m_cRef;         //Object reference count

        /*
         * I use "m_pImpI" as a prefix to differentiate interface
         * implementations for this object from other interface
         * pointer variables I might hold to other objects, whic
         * would be prefixed with "m_pI".
         */
        PCImpISampleOne  m_pImpISampleOne;
        PCImpISampleTwo  m_pImpISampleTwo;

    public:
        CObject1(void);
        ~CObject1(void);

        BOOL Init(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(DWORD) AddRef(void);
        STDMETHODIMP_(DWORD) Release(void);
    };

typedef CObject1 *PCObject1;

/*
 * Interface implementation classes are C++ classes that
 * each singly inherit from an interface.  Their IUnknown
 * members delegate calls to CObject1's IUnknown members--
 * since IUnknown members affect the entire *object*, and
 * since these interfaces are not the object itself, we must
 * delegate to implement the correct behavior.
 */

class CImpISampleOne : public ISampleOne
    {
    private:
        DWORD       m_cRef;         //For debugging
        PCObject1   m_pObj;         //Back pointer for delegation

    public:
        CImpISampleOne(PCObject1);
        ~CImpISampleOne(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(DWORD) AddRef(void);
        STDMETHODIMP_(DWORD) Release(void);

        //ISampleOne members
        STDMETHODIMP         GetMessage(LPTSTR, UINT);
    };

class CImpISampleTwo : public ISampleTwo
    {
    private:
        DWORD       m_cRef;         //For debugging
        PCObject1   m_pObj;         //Back pointer for delegation

    public:
        CImpISampleTwo(PCObject1);
        ~CImpISampleTwo(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(DWORD) AddRef(void);
        STDMETHODIMP_(DWORD) Release(void);

        //ISampleTwo members
        STDMETHODIMP         GetString(LPTSTR, UINT);
    };

#endif _OBJECT1_H_


OBJECT2.CPP

/*
 * OBJECT2.CPP
 *
 * Object implementation using contained interface classes
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

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

//Message strings for the interface functions
static TCHAR szMessage[]=TEXT("Message from ISampleOne::GetMessage");
static TCHAR szString[] =TEXT("Message from ISampleTwo::GetString");

/*
 * CreateObject2
 *
 * Purpose:
 *  Creates an instance of Object2 returning an IUnknown
 *  interface.
 *
 * Parameters:
 *  ppUnk           IUnknown ** in which to return the
 *                  interface pointer.
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CreateObject2(IUnknown **ppUnk)
    {
    HRESULT     hr;
    CObject2   *pObj;

    if (NULL==ppUnk)
        return FALSE;

    //Create the new object, which gives us a C++ object pointer
    pObj=new CObject2();

    if (NULL==pObj)
        return FALSE;

    /*
     * Now get the IUnknown interface to this object and make
     * sure that it's reference count is correct.  We could either
     * typecast pObj to IUnknown and call AddRef explicitly, or
     * we can simply call pObj->QueryInterface and let it do the
     * typecast and the AddRef for us.
     */
    hr=pObj->QueryInterface(IID_IUnknown, (PPVOID)ppUnk);
    return SUCCEEDED(hr);
    }

/*
 * CObject2::CObject2
 * CObject2::~CObject2
 *
 * Constructor Parameters:
 *  None
 */

CObject2::CObject2(void)
    : m_ImpISampleOne(this), m_ImpISampleTwo(this)
    {
    m_cRef=0;
    return;
    }

CObject2::~CObject2(void)
    {
    //Interfaces cleaned up automatically
    return;
    }

/*
 * CObject2::QueryInterface
 *
 * Purpose:
 *  Manages the interfaces for this object which supports the
 *  IUnknown, ISampleOne, and ISampleTwo interfaces.
 *
 * Parameters:
 *  riid            REFIID of the interface to return.
 *  ppv             PPVOID in which to store the pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, E_NOINTERFACE if the
 *                  interface is not supported.
 */

STDMETHODIMP CObject2::QueryInterface(REFIID riid, PPVOID ppv)
    {
    //Always NULL the out-parameters
    *ppv=NULL;

    /*
     * IUnknown comes from CObject2.  Note that here we do not need
     * to explicitly typecast the object pointer into an interface
     * pointer because the vtables are identical.  If we had
     * additional virtual member functions in the object, we would
     * have to cast in order to set the right vtable.  This is
     * demonstrated in the multiple-inheritance version, CObject3.
     */
    if (IID_IUnknown==riid)
        *ppv=this;

    //Other interfaces come from contained classes
    if (IID_ISampleOne==riid)
        *ppv=&m_ImpISampleOne;

    if (IID_ISampleTwo==riid)
        *ppv=&m_ImpISampleTwo;

    if (NULL==*ppv)
        return ResultFromScode(E_NOINTERFACE);

    //AddRef any interface we'll return.
    ((LPUNKNOWN)*ppv)->AddRef();
    return NOERROR;
    }

/*
 * CObject2::AddRef
 * CObject2::Release
 *
 * Reference counting members.  When Release sees a zero count
 * the object destroys itself.
 */

DWORD CObject2::AddRef(void)
    {
    return ++m_cRef;
    }

DWORD CObject2::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

//CObject2::CImpISampleOne interface implementation

//Constructor/destructor inline in object2.h

/*
 * CObject2::CImpISampleOne::QueryInterface
 * CObject2::CImpISampleOne::AddRef
 * CObject2::CImpISampleOne::Release
 *
 * IUnknown members that delegate to m_pObj
 */

STDMETHODIMP CObject2::CImpISampleOne::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pObj->QueryInterface(riid, ppv);
    }

DWORD CObject2::CImpISampleOne::AddRef(void)
    {
    /*
     * We maintain an "interface reference count" for debugging
     * purposes, because the client of an object should match
     * AddRef and Release calls through each interface pointer.
     */
    ++m_cRef;
    return m_pObj->AddRef();
    }

DWORD CObject2::CImpISampleOne::Release(void)
    {
    /*
     * m_cRef is again only for debugging.  It doesn't affect
     * CObject2 although the call to m_pObj->Release does.
     */
    --m_cRef;
    return m_pObj->Release();
    }

/*
 * CObject2::CImpISampleOne::GetMessage
 *
 * Purpose:
 *  Returns a string to the caller.
 *
 * Parameters:
 *  psz             LPTSTR in which to store the string.
 *  cch             UINT size of psz.
 *
 * Return Value:
 *  HRESULT         NOERROR if the string is stored, E_OUTOFMEMORY
 *                  if the buffer is too small.
 */

STDMETHODIMP CObject2::CImpISampleOne::GetMessage(LPTSTR psz, UINT cch)
    {
    if (NULL==psz)
        return ResultFromScode(E_POINTER);

    if (cch < (UINT)lstrlen(szMessage))
        return ResultFromScode(E_OUTOFMEMORY);

    lstrcpy(psz, szMessage);
    return NOERROR;
    }

//CObject2::CImpISampleTwo interface implementation

//Constructor/destructor inline in object2.h

/*
 * CObject2::CImpISampleTwo::QueryInterface
 * CObject2::CImpISampleTwo::AddRef
 * CObject2::CImpISampleTwo::Release
 *
 * IUnknown members that delegate to m_pObj
 */

STDMETHODIMP CObject2::CImpISampleTwo::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pObj->QueryInterface(riid, ppv);
    }

DWORD CObject2::CImpISampleTwo::AddRef(void)
    {
    /*
     * We maintain an "interface reference count" for debugging
     * purposes, because the client of an object should match
     * AddRef and Release calls through each interface pointer.
     */
    ++m_cRef;
    return m_pObj->AddRef();
    }

DWORD CObject2::CImpISampleTwo::Release(void)
    {
    /*
     * m_cRef is again only for debugging.  It doesn't affect
     * CObject2 although the call to m_pObj->Release does.
     */
    --m_cRef;
    return m_pObj->Release();
    }

/*
 * CObject2::CImpISampleTwo::GetString
 *
 * Purpose:
 *  Returns a string to the caller.
 *
 * Parameters:
 *  psz             LPTSTR in which to store the string.
 *  cch             UINT size of psz.
 *
 * Return Value:
 *  HRESULT         NOERROR if the string is stored, E_OUTOFMEMORY
 *                  if the buffer is too small.
 */

STDMETHODIMP CObject2::CImpISampleTwo::GetString(LPTSTR psz, UINT cch)
    {
    if (NULL==psz)
        return ResultFromScode(E_POINTER);

    if (cch < (UINT)lstrlen(szString))
        return ResultFromScode(E_OUTOFMEMORY);

    lstrcpy(psz, szString);
    return NOERROR;
    }


OBJECT2.H

/*
 * OBJECT2.H
 *
 * Definition of the CObject1 class that uses contained
 * classes to provide ISampleOne and ISampleTwo.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _OBJECT2_H_
#define _OBJECT2_H_

//Creation function
BOOL CreateObject2(IUnknown **);

/*
 * The object we want to provide in OLE supports the IUnknown,
 * ISampleOne, and ISampleTwo interfaces.
 *
 * The C++ class, CObject2, implements these interfaces with
 * contained classes where each contained class inherits singly
 * from an interface.  This is a little different from interface
 * implementations shown in Object1 because contained classes
 * are automatically instantiated along with CObject2.
 */

//The C++ class that manages the actual object.
class CObject2 : public IUnknown
    {
    /*
     * Declare the contained classes, which should be friends.
     * As with interface implementations, these need back
     * pointers to the outer object as well as the IUnknown to
     * which to delegate, which are the constructor parameters.
     */
    class CImpISampleOne : public ISampleOne
        {
        private:
            DWORD       m_cRef;         //For debugging
            CObject2   *m_pObj;         //Back pointer for delegation

        public:
            CImpISampleOne(CObject2 *pObj)
                { m_cRef=0; m_pObj=pObj; }

            ~CImpISampleOne(void)
                { }

            //IUnknown members
            STDMETHODIMP         QueryInterface(REFIID, PPVOID);
            STDMETHODIMP_(DWORD) AddRef(void);
            STDMETHODIMP_(DWORD) Release(void);

            //ISampleOne members
            STDMETHODIMP         GetMessage(LPTSTR, UINT);
        };

    class CImpISampleTwo : public ISampleTwo
        {
        private:
            DWORD       m_cRef;         //For debugging
            CObject2   *m_pObj;         //Back pointer for delegation

        public:
            CImpISampleTwo(CObject2 *pObj)
                { m_cRef=0; m_pObj=pObj; }
            ~CImpISampleTwo(void)
                { }

            //IUnknown members
            STDMETHODIMP         QueryInterface(REFIID, PPVOID);
            STDMETHODIMP_(DWORD) AddRef(void);
            STDMETHODIMP_(DWORD) Release(void);

            //ISampleTwo members
            STDMETHODIMP         GetString(LPTSTR, UINT);
        };

    friend CImpISampleOne;
    friend CImpISampleTwo;

    private:
        DWORD           m_cRef;         //Object reference count

        /*
         * In this technique I still use "ImpI" prefixes to
         * differentiate contained classes.  The difference here
         * from Object1 is that we declare objects instead of
         * pointers to objects, so instantiating CObject2 will
         * automatically instantiate CImpI*.  Destroying CObject2
         * will automatically destroy CImp*.
         */
        CImpISampleOne  m_ImpISampleOne;
        CImpISampleTwo  m_ImpISampleTwo;

    public:
        CObject2(void);
        ~CObject2(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(DWORD) AddRef(void);
        STDMETHODIMP_(DWORD) Release(void);
    };

typedef CObject2 *PCObject2;

#endif _OBJECT2_H_


OBJECT3.CPP

/*
 * OBJECT3.CPP
 *
 * Object implementation using multiple inheritance.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

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

//Message strings for the interface functions
static TCHAR szMessage[]=TEXT("Message from ISampleOne::GetMessage");
static TCHAR szString[] =TEXT("Message from ISampleTwo::GetString");

/*
 * CreateObject3
 *
 * Purpose:
 *  Creates an instance of Object2 returning an IUnknown
 *  interface.
 *
 * Parameters:
 *  ppUnk           IUnknown ** in which to return the
 *                  interface pointer.
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CreateObject3(IUnknown **ppUnk)
    {
    HRESULT     hr;
    CObject3   *pObj;

    if (NULL==ppUnk)
        return FALSE;

    //Create the new object, which gives us a C++ object pointer
    pObj=new CObject3();

    if (NULL==pObj)
        return FALSE;

    /*
     * Now get the IUnknown interface to this object and make
     * sure that it's reference count is correct.  We could either
     * typecast pObj to IUnknown and call AddRef explicitly, or
     * we can simply call pObj->QueryInterface and let it do the
     * typecast and the AddRef for us.
     */
    hr=pObj->QueryInterface(IID_IUnknown, (PPVOID)ppUnk);
    return SUCCEEDED(hr);
    }

/*
 * CObject3::CObject3
 * CObject3::~CObject3
 *
 * Constructor Parameters:
 *  None
 */

CObject3::CObject3(void)
    {
    m_cRef=0;
    return;
    }

CObject3::~CObject3(void)
    {
    return;
    }

/*
 * CObject3::QueryInterface
 *
 * Purpose:
 *  Manages the interfaces for this object which supports the
 *  IUnknown, ISampleOne, and ISampleTwo interfaces.
 *
 * Parameters:
 *  riid            REFIID of the interface to return.
 *  ppv             PPVOID in which to store the pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, E_NOINTERFACE if the
 *                  interface is not supported.
 */

STDMETHODIMP CObject3::QueryInterface(REFIID riid, PPVOID ppv)
    {
    //Always NULL the out-parameters
    *ppv=NULL;

    /*
     * Since all the interfaces are part of this same object,
     * we *must* use typecasts to set up the right vtables from
     * the 'this' pointer.  Casting to an interface will give
     * us a pointer that points to exactly that interface vtable.
     *
     * Note that since we don't have an explicit IUnknown base,
     * responding to IUnknown here simply return ISampleOne, which
     * has the right IUnknown vtable anyway.
     */
    if (IID_IUnknown==riid || IID_ISampleOne==riid)
        *ppv=(ISampleOne *)this;

    if (IID_ISampleTwo==riid)
        *ppv=(ISampleTwo *)this;

    if (NULL==*ppv)
        return ResultFromScode(E_NOINTERFACE);

    //AddRef any interface we'll return.
    ((LPUNKNOWN)*ppv)->AddRef();
    return NOERROR;
    }

/*
 * CObject3::AddRef
 * CObject3::Release
 *
 * Reference counting members.  When Release sees a zero count
 * the object destroys itself.
 */

DWORD CObject3::AddRef(void)
    {
    return ++m_cRef;
    }

DWORD CObject3::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CObject3::GetMessage
 *
 * Purpose:
 *  Returns a string to the caller.  This is the implementation
 *  of ISampleOne::GetMessage.
 *
 * Parameters:
 *  psz             LPTSTR in which to store the string.
 *  cch             UINT size of psz.
 *
 * Return Value:
 *  HRESULT         NOERROR if the string is stored, E_OUTOFMEMORY
 *                  if the buffer is too small.
 */

STDMETHODIMP CObject3::GetMessage(LPTSTR psz, UINT cch)
    {
    if (NULL==psz)
        return ResultFromScode(E_POINTER);

    if (cch < (UINT)lstrlen(szMessage))
        return ResultFromScode(E_OUTOFMEMORY);

    lstrcpy(psz, szMessage);
    return NOERROR;
    }

/*
 * CObject3::GetString
 *
 * Purpose:
 *  Returns a string to the caller.  This is the implementation
 *  of ISampleTwo::GetString.
 *
 * Parameters:
 *  psz             LPTSTR in which to store the string.
 *  cch             UINT size of psz.
 *
 * Return Value:
 *  HRESULT         NOERROR if the string is stored, E_OUTOFMEMORY
 *                  if the buffer is too small.
 */

STDMETHODIMP CObject3::GetString(LPTSTR psz, UINT cch)
    {
    if (NULL==psz)
        return ResultFromScode(E_POINTER);

    if (cch < (UINT)lstrlen(szString))
        return ResultFromScode(E_OUTOFMEMORY);

    lstrcpy(psz, szString);
    return NOERROR;
    }


OBJECT3.H

/*
 * OBJECT3.H
 *
 * Definition of the CObject1 class that uses mutliple
 * inheritance to provide ISampleOne and ISampleTwo.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _OBJECT3_H_
#define _OBJECT3_H_

//Creation function
BOOL CreateObject3(IUnknown **);

/*
 * The object we want to provide in OLE supports the IUnknown,
 * ISampleOne, and ISampleTwo interfaces.
 *
 * The C++ class, CObject3, implements these interfaces through
 * multiple inheritance, so the implementation of all IUnknown
 * members is shared.  The trick to this implementation is that
 * we have to use explicit typecasts in the implementation of
 * QueryInterface in order to create the right vtables for each
 * interface.  See OBJECT3.CPP.
 */

//The C++ class that manages the actual object.
class CObject3 : public ISampleOne, public ISampleTwo
    {
    private:
        DWORD           m_cRef;         //Object reference count

    public:
        CObject3(void);
        ~CObject3(void);

        //Shared IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(DWORD) AddRef(void);
        STDMETHODIMP_(DWORD) Release(void);

        //ISampleOne members
        STDMETHODIMP         GetMessage(LPTSTR, UINT);

        //ISampleTwo members
        STDMETHODIMP         GetString(LPTSTR, UINT);
    };

typedef CObject3 *PCObject3;

#endif _OBJECT3_H_


QUERY.CPP

/*
 * QUERY.CPP
 * Demonstration of QueryInterface and different techniques to
 * implement multiple interfaces.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include <windows.h>
#include "query.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    MSG     msg;
    PAPP    pApp;

    pApp=new CApp(hInst, hInstPrev, nCmdShow);

    if (NULL==pApp)
        return -1;

    if (pApp->Init())
        {
        while (GetMessage(&msg, NULL, 0,0 ))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    delete pApp;
    return msg.wParam;
    }

/*
 * QueryWndProc
 *
 * Purpose:
 *  Standard window class procedure.
 */

LRESULT APIENTRY QueryWndProc(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    PAPP        pApp;
    HRESULT     hr;
    BOOL        fRes;
    ISampleOne *pISampleOne;
    ISampleTwo *pISampleTwo;
    const UINT  CCHTEMP=80;
    TCHAR       szTemp[CCHTEMP];

    COMMANDPARAMS(wID, wCode, hWndMsg);

    pApp=(PAPP)GetWindowLong(hWnd, QUERYWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pApp=(PAPP)(((LPCREATESTRUCT)lParam)->lpCreateParams);
            SetWindowLong(hWnd, QUERYWL_STRUCTURE, (LONG)pApp);
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_COMMAND:
            switch (wID)
                {
                case IDM_OBJECTCREATE1:
                    if (NULL!=pApp->m_pIUnknown)
                        pApp->m_pIUnknown->Release();

                    fRes=CreateObject1(&pApp->m_pIUnknown);

                    pApp->Message(fRes ? TEXT("Object 1 created")
                        : TEXT("Object 1 creation failed"));

                    break;

                case IDM_OBJECTCREATE2:
                    if (NULL!=pApp->m_pIUnknown)
                        pApp->m_pIUnknown->Release();

                    fRes=CreateObject2(&pApp->m_pIUnknown);

                    pApp->Message(fRes ? TEXT("Object 2 created")
                        : TEXT("Object 2 creation failed"));

                    break;

                case IDM_OBJECTCREATE3:
                    if (NULL!=pApp->m_pIUnknown)
                        pApp->m_pIUnknown->Release();

                    fRes=CreateObject3(&pApp->m_pIUnknown);

                    pApp->Message(fRes ? TEXT("Object 3 created")
                        : TEXT("Object 3 creation failed"));

                    break;

                case IDM_OBJECTRELEASE:
                    if (NULL==pApp->m_pIUnknown)
                        {
                        pApp->Message(TEXT("There is no object"));
                        break;
                        }

                    if (0==pApp->m_pIUnknown->Release())
                        {
                        pApp->m_pIUnknown=NULL;
                        pApp->Message(TEXT("Object released"));
                        }

                    break;

                case IDM_OBJECTGETMESSAGE:
                    if (NULL==pApp->m_pIUnknown)
                        {
                        pApp->Message(TEXT("There is no object"));
                        break;
                        }

                    hr=pApp->m_pIUnknown->QueryInterface
                        (IID_ISampleOne, (PPVOID)&pISampleOne);

                    if (FAILED(hr))
                        {
                        pApp->Message(TEXT("Failed to get ISampleOne"));
                        break;
                        }

                    hr=pISampleOne->GetMessage(szTemp, CCHTEMP);

                    pApp->Message(SUCCEEDED(hr) ? szTemp
                        : TEXT("ISampleOne::GetMessage failed"));

                    //m_pIUnknown is still valid after this Release
                    pISampleOne->Release();
                    break;

                case IDM_OBJECTGETSTRING:
                    if (NULL==pApp->m_pIUnknown)
                        {
                        pApp->Message(TEXT("There is no object"));
                        break;
                        }

                    hr=pApp->m_pIUnknown->QueryInterface
                        (IID_ISampleTwo, (PPVOID)&pISampleTwo);

                    if (FAILED(hr))
                        {
                        pApp->Message(TEXT("Failed to get ISampleTwo"));
                        break;
                        }

                    hr=pISampleTwo->GetString(szTemp, CCHTEMP);

                    pApp->Message(SUCCEEDED(hr) ? szTemp
                        : TEXT("ISampleTwp::GetString failed"));

                    //m_pIUnknown is still valid after this Release
                    pISampleTwo->Release();
                    break;

                case IDM_OBJECTEXIT:
                    PostMessage(hWnd, WM_CLOSE, 0, 0L);
                    break;
                }
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * CApp::CApp
 * CApp::~CApp
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the Application from WinMain
 *  hInstPrev       HINSTANCE of a previous instance from WinMain
 *  nCmdShow        UINT specifying how to show the app window,
 *                  from WinMain.
 */

CApp::CApp(HINSTANCE hInst, HINSTANCE hInstPrev
    , UINT nCmdShow)
    {
    //Initialize WinMain parameter holders.
    m_hInst     =hInst;
    m_hInstPrev =hInstPrev;
    m_nCmdShow  =nCmdShow;

    m_hWnd=NULL;
    m_pIUnknown=NULL;

    return;
    }

CApp::~CApp(void)
    {
    //Release the object if we still have it.
    ReleaseInterface(m_pIUnknown);
    return;
    }

/*
 * CApp::Init
 *
 * Purpose:
 *  Initializes an CApp object by registering window classes,
 *  creating the main window, and doing anything else prone to
 *  failure.  If this function fails the caller should guarantee
 *  that the destructor is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::Init(void)
    {
    WNDCLASS    wc;

    if (!m_hInstPrev)
        {
        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = QueryWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = CBWNDEXTRA;
        wc.hInstance      = m_hInst;
        wc.hIcon          = LoadIcon(m_hInst, TEXT("Icon"));
        wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName   = MAKEINTRESOURCE(IDR_MENU);
        wc.lpszClassName  = TEXT("QUERY");

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

    m_hWnd=CreateWindow(TEXT("QUERY"), TEXT("QueryInterface Demo")
        , WS_MINIMIZEBOX | WS_OVERLAPPEDWINDOW
        ,35, 35, 350, 250, NULL, NULL, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    ShowWindow(m_hWnd, m_nCmdShow);
    UpdateWindow(m_hWnd);

    return TRUE;
    }

/*
 * CApp::Message
 *
 * Purpose:
 *  Displays a message in the client area of the window.  This is
 *  just to centralize the call to simpify other code.
 *
 * Parameters:
 *  psz             LPTSTR to the string to display.
 *
 * Return Value:
 *  None
 */

void inline CApp::Message(LPTSTR psz)
    {
    HDC     hDC;
    RECT    rc;

    hDC=GetDC(m_hWnd);
    GetClientRect(m_hWnd, &rc);

    SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
    SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));

    /*
     * We'll just be sloppy and clear the whole window as
     * well as write the string with one ExtTextOut call.
     * No word wrapping here...
     */

    ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rc, psz, lstrlen(psz), NULL);
    ReleaseDC(m_hWnd, hDC);
    return;
    }


QUERY.H

/*
 * QUERY.H
 * QueryInterface and Multiple Interface Sample, Chapter 2
 *
 * Definitions, classes, and prototypes
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _QUERY_H_
#define _QUERY_H_

#define CHAPTER2
#include <inole.h>

#ifndef RC_INVOKED
#include "interfac.h"
#include "object1.h"
#include "object2.h"
#include "object3.h"
#endif

//Menu Resource ID and Commands
#define IDR_MENU                    1

#define IDM_OBJECTCREATE1           100
#define IDM_OBJECTCREATE2           101
#define IDM_OBJECTCREATE3           102
#define IDM_OBJECTRELEASE           103
#define IDM_OBJECTGETMESSAGE        104
#define IDM_OBJECTGETSTRING         105
#define IDM_OBJECTEXIT              106

//QUERY.CPP
LRESULT APIENTRY QueryWndProc(HWND, UINT, WPARAM, LPARAM);

class CApp
    {
    friend LRESULT APIENTRY QueryWndProc(HWND, UINT, WPARAM, LPARAM);

    protected:
        HINSTANCE       m_hInst;            //WinMain parameters
        HINSTANCE       m_hInstPrev;
        UINT            m_nCmdShow;

        HWND            m_hWnd;             //Main window handle
        IUnknown       *m_pIUnknown;        //Current object

    public:
        CApp(HINSTANCE, HINSTANCE, UINT);
        ~CApp(void);

        BOOL        Init(void);
        void        Message(LPTSTR);
    };

typedef CApp *PAPP;

#define CBWNDEXTRA          sizeof(PAPP)
#define QUERYWL_STRUCTURE   0

#endif //_QUERY_H_


ANIMAL.CPP

/*
 * ANIMAL.CPP
 *
 * Aggregatable Animal Object Implementation, Chapter 2
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include <windows.h>
#include "reuse.h"
#include "animal.h"

/*
 * CreateAnimal
 *
 * Purpose:
 *  Creates an instance of Animal returning a requested interface
 *  pointer.
 *
 * Parameters:
 *  pUnkOuter       IUnknown * holding the outer unknown.  Can
 *                  be NULL.
 *  riid            REFIID interface requested by the caller.
 *  ppv             void ** in which to return the interface pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, CLASS_E_NOAGREGATION if
 *                  IUnknown is not requested with non-NULL pUnkOuter,
 *                  or other error as appropriate.
 */

HRESULT CreateAnimal(IUnknown *pUnkOuter, REFIID riid, void **ppv)
    {
    CAnimal    *pObj;

    //If aggregation is on, check riid==IID_IUnknown
    if (NULL!=pUnkOuter && riid!=IID_IUnknown)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    pObj=new CAnimal(pUnkOuter);

    if (NULL==pObj)
        return E_OUTOFMEMORY;

    if (!pObj->Init())
        return E_FAIL;

    return pObj->QueryInterface(riid, (PPVOID)ppv);
    }

/*
 * CAnimal::CAnimal
 * CAnimal::~CAnimal
 *
 * Constructor Parameters:
 *  pUnkOuter       IUnknown * holding the outer unknown.  Can
 *                  be NULL.
 */

CAnimal::CAnimal(IUnknown *pUnkOuter)
    {
    m_cRef=0;
    m_pImpIAnimal=NULL;

    //No AddRef necessary if non-NULL as we're nested.
    m_pUnkOuter=pUnkOuter;
    return;
    }

CAnimal::~CAnimal(void)
    {
    DeleteInterfaceImp(m_pImpIAnimal);
    return;
    }

/*
 * CAnimal::Init
 *
 * Purpose:
 *  Instantiates the interface implementations for this object.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeds, FALSE otherwise.
 */

BOOL CAnimal::Init(void)
    {
    IUnknown   *pUnkOuter=m_pUnkOuter;

    //Set up the right unknown for delegation
    if (NULL==pUnkOuter)
        pUnkOuter=this;

    m_pImpIAnimal=new CImpIAnimal_A(this, pUnkOuter);

    if (NULL==m_pImpIAnimal)
        return FALSE;

    return TRUE;
    }

/*
 * CAnimal::QueryInterface
 * CAnimal::AddRef
 * CAnimal::Release
 *
 * Purpose:
 *  Non-delegating IUnknown implementation.
 */

STDMETHODIMP CAnimal::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IAnimal==riid)
        *ppv=m_pImpIAnimal;

    if (NULL==*ppv)
        return ResultFromScode(E_NOINTERFACE);

    ((LPUNKNOWN)*ppv)->AddRef();
    return NOERROR;
    }

DWORD CAnimal::AddRef(void)
    {
    return ++m_cRef;
    }

DWORD CAnimal::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

//CImpIAnimal_A interface implementation

/*
 * CImpIAnimal_A::CImpIAnimal_A
 * CImpIAnimal_A::~CImpIAnimal_A
 *
 * Constructor Parameters:
 *  pObj            PCAnimal to the outer object
 *  pUnkOuter       IUnknown * to which to delegate.
 */

CImpIAnimal_A::CImpIAnimal_A(PCAnimal pObj, IUnknown *pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;  //No AddRef, we're nested
    return;
    }

CImpIAnimal_A::~CImpIAnimal_A(void)
    {
    return;
    }

/*
 * CImpIAnimal_A::QueryInterface
 * CImpIAnimal_A::AddRef
 * CImpIAnimal_A::Release
 *
 * IUnknown members that delegate to m_pUnkOuter, whatever it is.
 */

STDMETHODIMP CImpIAnimal_A::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

DWORD CImpIAnimal_A::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

DWORD CImpIAnimal_A::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIAnimal_A::Eat
 * CImpIAnimal_A::Sleep
 * CImpIAnimal_A::Procreate
 *
 * Purpose:
 *  Empty sample functions, no parameters, return NOERROR
 */

STDMETHODIMP CImpIAnimal_A::Eat(void)
    {
    ODS("Animal's IAnimal::Eat called");
    return NOERROR;
    }

STDMETHODIMP CImpIAnimal_A::Sleep(void)
    {
    ODS("Animal's IAnimal::Sleep called");
    return NOERROR;
    }

STDMETHODIMP CImpIAnimal_A::Procreate(void)
    {
    ODS("Animal's IAnimal::Procreate called");
    return NOERROR;
    }


ANIMAL.H

/*
 * ANIMAL.H
 *
 * Definition of the aggregatable CAnimal object.
 * Included only from ANIMAL.CPP.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _ANIMAL_H_
#define _ANIMAL_H_

class CImpIAnimal_A;
typedef CImpIAnimal_A *PCImpIAnimal_A;

class CAnimal : public IUnknown
    {
    friend CImpIAnimal_A;

    private:
        DWORD           m_cRef;         //Object reference count
        IUnknown       *m_pUnkOuter;    //Outer unknown, no AddRef

        PCImpIAnimal_A  m_pImpIAnimal;  //Interface implementation

    public:
        CAnimal(IUnknown *);
        ~CAnimal(void);

        BOOL Init(void);

        //IUnknown members, non-delegating
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(DWORD) AddRef(void);
        STDMETHODIMP_(DWORD) Release(void);
    };

typedef CAnimal *PCAnimal;

class CImpIAnimal_A : public IAnimal
    {
    private:
        DWORD       m_cRef;         //For debugging
        PCAnimal    m_pObj;         //Back pointer
        IUnknown   *m_pUnkOuter;    //Outer unknown, for delegation

    public:
        CImpIAnimal_A(PCAnimal, IUnknown *);
        ~CImpIAnimal_A(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(DWORD) AddRef(void);
        STDMETHODIMP_(DWORD) Release(void);

        //IAnimal members
        STDMETHODIMP Eat(void);
        STDMETHODIMP Sleep(void);
        STDMETHODIMP Procreate(void);
    };

#endif _ANIMAL_H_


INTERFAC.H

/*
 * INTERFAC.H
 *
 * Definition of IAnimal and IKoala used to demonstrate
 * reusability mechanisms.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _INTERFAC_H_
#define _INTERFAC_H_

DECLARE_INTERFACE_(IAnimal, IUnknown)
    {
    //IUnknown members
    STDMETHOD(QueryInterface) (THIS_ REFIID, PPVOID) PURE;
    STDMETHOD_(ULONG,AddRef)  (THIS) PURE;
    STDMETHOD_(ULONG,Release) (THIS) PURE;

    //IAnimal members
    STDMETHOD(Eat)(THIS) PURE;
    STDMETHOD(Sleep)(THIS) PURE;
    STDMETHOD(Procreate)(THIS) PURE;
    };

typedef IAnimal *PANIMAL;

DECLARE_INTERFACE_(IKoala, IUnknown)
    {
    //IUnknown members
    STDMETHOD(QueryInterface) (THIS_ REFIID, PPVOID) PURE;
    STDMETHOD_(ULONG,AddRef)  (THIS) PURE;
    STDMETHOD_(ULONG,Release) (THIS) PURE;

    //IKoala members
    STDMETHOD(ClimbEucalyptusTrees)(THIS) PURE;
    STDMETHOD(PouchOpensDown)(THIS) PURE;
    STDMETHOD(SleepForHoursAfterEating)(THIS) PURE;
    };

typedef IKoala *PKOALA;

#endif //_INTERFAC_H_


KOALAA.CPP

/*
 * KOALAA.CPP
 *
 * Koala Object Reusing Animal via Aggregation, Chapter 2
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include <windows.h>
#include "reuse.h"
#include "koalaa.h"

/*
 * CreateKoalaAggregation
 *
 * Purpose:
 *  Creates an instance of KoalaA returning an IUnknown
 *  interface.
 *
 * Parameters:
 *  ppUnk           IUnknown ** in which to return the pointer.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CreateKoalaAggregation(IUnknown **ppUnk)
    {
    CKoalaA    *pObj;
    HRESULT     hr;

    pObj=new CKoalaA();

    if (NULL==pObj)
        return FALSE;

    if (!pObj->Init())
        return FALSE;

    hr=pObj->QueryInterface(IID_IUnknown, (PPVOID)ppUnk);
    return SUCCEEDED(hr);
    }

/*
 * CKoalaA::CKoalaA
 * CKoalaA::~CKoalaA
 *
 * Constructor Parameters:
 *  None
 */

CKoalaA::CKoalaA(void)
    {
    m_cRef=0;
    m_pImpIKoala=NULL;
    m_pIUnknown=NULL;
    m_pIAnimal=NULL;
    return;
    }

CKoalaA::~CKoalaA(void)
    {
    /*
     * Since this KoalaA is not aggregatable, we can use the
     * 32-bit rule under Win16 as well.
     */
    AddRef();
    ReleaseInterface(m_pIAnimal);

    ReleaseInterface(m_pIUnknown);
    DeleteInterfaceImp(m_pImpIKoala);
    return;
    }

/*
 * CKoalaA::Init
 *
 * Purpose:
 *  Instantiates the interface implementations for this object.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeds, FALSE otherwise.
 */

BOOL CKoalaA::Init(void)
    {
    HRESULT     hr;

    //Create our interface
    m_pImpIKoala=new CImpIKoala_A(this);

    if (NULL==m_pImpIKoala)
        return FALSE;

    /*
     * Create an instance of the Animal object, saving an
     * IUnknown but also saving a released IAnimal.  We use
     * Animal's global API function for this.
     */
    hr=CreateAnimal(this, IID_IUnknown, (void **)&m_pIUnknown);

    if (FAILED(hr))
        return FALSE;

    /*
     * Grab an IAnimal to show the Release rule.  We don't do
     * anything with this pointer, but we want to show the Release.
     */
    hr=m_pIUnknown->QueryInterface(IID_IAnimal, (void **)&m_pIAnimal);

    if (FAILED(hr))
        return FALSE;

    /*
     * In caching the IAnimal pointer we have to fix our
     * reference count.  SInce KoalaC is not aggregatable itself,
     * we can use the 32-bit rule for this case in 16-bit
     * compilations as well.
     *
     * Our reference count may be as little as one at this pointer
     * so calling our own Release (or m_pIAnimal's) is a bad idea.
     * Since we just need to fix the count, we can just decrement
     * m_cRef.  If we have to call someone's Release, then we'd
     * need to wrap that call in m_cRef++ and m_cRef--.
     */
    m_cRef--;

    return TRUE;
    }

/*
 * CKoalaA::QueryInterface
 * CKoalaA::AddRef
 * CKoalaA::Release
 *
 * Purpose:
 *  KoalaA's IUnknown implementation which serves as the outer
 *  unknown in the aggregation.
 */

STDMETHODIMP CKoalaA::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    //Alternately *ppv=m_pIAnimal; works for this case
    if (IID_IAnimal==riid)
        return m_pIUnknown->QueryInterface(riid, ppv);

    if (IID_IKoala==riid)
        *ppv=m_pImpIKoala;

    if (NULL==*ppv)
        return ResultFromScode(E_NOINTERFACE);

    ((LPUNKNOWN)*ppv)->AddRef();
    return NOERROR;
    }

DWORD CKoalaA::AddRef(void)
    {
    return ++m_cRef;
    }

DWORD CKoalaA::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    m_cRef++;       //Artificial count preventing reentrancy
    delete this;
    return 0;
    }

//CImpIKoala_A interface implementation

/*
 * CImpIKoala_A::CImpIKoala_A
 * CImpIKoala_A::~CImpIKoala_A
 *
 * Constructor Parameters:
 *  pObj            PCKoalaA to the outer object
 */

CImpIKoala_A::CImpIKoala_A(PCKoalaA pObj)
    {
    m_cRef=0;
    m_pObj=pObj;
    return;
    }

CImpIKoala_A::~CImpIKoala_A(void)
    {
    return;
    }

/*
 * CImpIKoala_A::QueryInterface
 * CImpIKoala_A::AddRef
 * CImpIKoala_A::Release
 *
 * IUnknown members that delegate to KoalaA
 */

STDMETHODIMP CImpIKoala_A::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pObj->QueryInterface(riid, ppv);
    }

DWORD CImpIKoala_A::AddRef(void)
    {
    ++m_cRef;
    return m_pObj->AddRef();
    }

DWORD CImpIKoala_A::Release(void)
    {
    --m_cRef;
    return m_pObj->Release();
    }

/*
 * CImpIKoala_A::ClimbEucalyptusTrees
 * CImpIKoala_A::PouchOpensDown
 * CImpIKoala_A::SleepForHoursAfterEating
 *
 * Purpose:
 *  Empty sample functions, no parameters, return NOERROR
 */

STDMETHODIMP CImpIKoala_A::ClimbEucalyptusTrees(void)
    {
    ODS("KoalaA's IKoala::ClimbEucalyptusTrees called");
    return NOERROR;
    }

STDMETHODIMP CImpIKoala_A::PouchOpensDown(void)
    {
    ODS("KoalaA's IKoala::PouchOpensDown called");
    return NOERROR;
    }

STDMETHODIMP CImpIKoala_A::SleepForHoursAfterEating(void)
    {
    ODS("KoalaA's IKoala::SleepForHoursAfterEating called");
    return NOERROR;
    }


KOALAA.H

/*
 * KOALAA.H
 *
 * Definition of the Koala object using Aggregation, Chapter 2
 * Included only from KOALAA.CPP.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _KOALAA_H_
#define _KOALAA_H_

class CImpIKoala_A;
typedef CImpIKoala_A *PCImpIKoala_A;

class CKoalaA : public IUnknown
    {
    friend CImpIKoala_A;

    private:
        DWORD           m_cRef;         //Object reference count

        PCImpIKoala_A   m_pImpIKoala;   //Interface implementation

        IUnknown       *m_pIUnknown;    //From Animal
        IAnimal        *m_pIAnimal;     //From Animal, released

    public:
        CKoalaA(void);
        ~CKoalaA(void);

        BOOL Init(void);

        //IUnknown members, non-delegating
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(DWORD) AddRef(void);
        STDMETHODIMP_(DWORD) Release(void);
    };

typedef CKoalaA *PCKoalaA;

class CImpIKoala_A : public IKoala
    {
    private:
        DWORD       m_cRef;         //For debugging
        PCKoalaA    m_pObj;         //Back pointer

    public:
        CImpIKoala_A(PCKoalaA);
        ~CImpIKoala_A(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(DWORD) AddRef(void);
        STDMETHODIMP_(DWORD) Release(void);

        //IKoala members
        STDMETHODIMP ClimbEucalyptusTrees(void);
        STDMETHODIMP PouchOpensDown(void);
        STDMETHODIMP SleepForHoursAfterEating(void);
    };

#endif _KOALAA_H_


KOALAC.CPP

/*
 * KOALAC.CPP
 *
 * Koala Object Reusing Animal via Containment, Chapter 2
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include <windows.h>
#include "reuse.h"
#include "koalac.h"

/*
 * CreateKoalaContainment
 *
 * Purpose:
 *  Creates an instance of KoalaC returning an IUnknown
 *  interface.
 *
 * Parameters:
 *  ppUnk           IUnknown ** in which to return the pointer.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CreateKoalaContainment(IUnknown **ppUnk)
    {
    CKoalaC    *pObj;
    HRESULT     hr;

    pObj=new CKoalaC();

    if (NULL==pObj)
        return FALSE;

    if (!pObj->Init())
        return FALSE;

    hr=pObj->QueryInterface(IID_IUnknown, (PPVOID)ppUnk);
    return SUCCEEDED(hr);
    }

/*
 * CKoalaC::CKoalaC
 * CKoalaC::~CKoalaC
 *
 * Constructor Parameters:
 *  None
 */

CKoalaC::CKoalaC(void)
    {
    m_cRef=0;
    m_pImpIAnimal=NULL;
    m_pImpIKoala=NULL;

    m_pIAnimal=NULL;
    return;
    }

CKoalaC::~CKoalaC(void)
    {
    ReleaseInterface(m_pIAnimal);

    DeleteInterfaceImp(m_pImpIKoala);
    DeleteInterfaceImp(m_pImpIAnimal);
    return;
    }

/*
 * CKoalaC::Init
 *
 * Purpose:
 *  Instantiates the interface implementations for this object.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeds, FALSE otherwise.
 */

BOOL CKoalaC::Init(void)
    {
    HRESULT     hr;

    //Create our interface
    m_pImpIAnimal=new CImpIAnimal_K(this);

    if (NULL==m_pImpIAnimal)
        return FALSE;

    m_pImpIKoala=new CImpIKoala_C(this);

    if (NULL==m_pImpIKoala)
        return FALSE;

    /*
     * Create an instance of the Animal object and save an
     * IAnimal pointer.  We use Animal's global API function
     * for this.
     */
    hr=CreateAnimal(NULL, IID_IAnimal, (void **)&m_pIAnimal);

    if (FAILED(hr))
        return FALSE;

    return TRUE;
    }

/*
 * CKoalaC::QueryInterface
 * CKoalaC::AddRef
 * CKoalaC::Release
 *
 * Purpose:
 *  KoalaC's IUnknown implementation.
 */

STDMETHODIMP CKoalaC::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IAnimal==riid)
        *ppv=m_pImpIAnimal;

    if (IID_IKoala==riid)
        *ppv=m_pImpIKoala;

    if (NULL==*ppv)
        return ResultFromScode(E_NOINTERFACE);

    ((LPUNKNOWN)*ppv)->AddRef();
    return NOERROR;
    }

DWORD CKoalaC::AddRef(void)
    {
    return ++m_cRef;
    }

DWORD CKoalaC::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

//CImpIAnimal_K interface implementation

/*
 * CImpIAnimal_K::CImpIAnimal_K
 * CImpIAnimal_K::~CImpIAnimal_K
 *
 * Constructor Parameters:
 *  pObj            PCKoalaC to the outer object
 */

CImpIAnimal_K::CImpIAnimal_K(PCKoalaC pObj)
    {
    m_cRef=0;
    m_pObj=pObj;
    return;
    }

CImpIAnimal_K::~CImpIAnimal_K(void)
    {
    return;
    }

/*
 * CImpIAnimal_K::QueryInterface
 * CImpIAnimal_K::AddRef
 * CImpIAnimal_K::Release
 *
 * IUnknown members that delegate to KoalaC
 */

STDMETHODIMP CImpIAnimal_K::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pObj->QueryInterface(riid, ppv);
    }

DWORD CImpIAnimal_K::AddRef(void)
    {
    ++m_cRef;
    return m_pObj->AddRef();
    }

DWORD CImpIAnimal_K::Release(void)
    {
    --m_cRef;
    return m_pObj->Release();
    }

/*
 * CImpIAnimal_K::Eat
 * CImpIAnimal_K::Sleep
 * CImpIAnimal_K::Procreate
 *
 * Purpose:
 *  Empty sample functions, no parameters, return NOERROR
 */

STDMETHODIMP CImpIAnimal_K::Eat(void)
    {
    ODS("KoalaC's IAnimal::Eat called");

    //Call contained object
    m_pObj->m_pIAnimal->Eat();
    return NOERROR;
    }

STDMETHODIMP CImpIAnimal_K::Sleep(void)
    {
    ODS("KoalaC's IAnimal::Sleep called");

    //Call contained object
    m_pObj->m_pIAnimal->Sleep();
    return NOERROR;
    }

STDMETHODIMP CImpIAnimal_K::Procreate(void)
    {
    ODS("KoalaC's IAnimal::Procreate called");

    //Call contained object
    m_pObj->m_pIAnimal->Procreate();
    return NOERROR;
    }

//CImpIKoala_C interface implementation

/*
 * CImpIKoala_C::CImpIKoala_C
 * CImpIKoala_C::~CImpIKoala_C
 *
 * Constructor Parameters:
 *  pObj            PCKoalaC to the outer object
 */

CImpIKoala_C::CImpIKoala_C(PCKoalaC pObj)
    {
    m_cRef=0;
    m_pObj=pObj;
    return;
    }

CImpIKoala_C::~CImpIKoala_C(void)
    {
    return;
    }

/*
 * CImpIKoala_C::QueryInterface
 * CImpIKoala_C::AddRef
 * CImpIKoala_C::Release
 *
 * IUnknown members that delegate to KoalaC
 */

STDMETHODIMP CImpIKoala_C::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pObj->QueryInterface(riid, ppv);
    }

DWORD CImpIKoala_C::AddRef(void)
    {
    ++m_cRef;
    return m_pObj->AddRef();
    }

DWORD CImpIKoala_C::Release(void)
    {
    --m_cRef;
    return m_pObj->Release();
    }

/*
 * CImpIKoala_C::ClimbEucalyptusTrees
 * CImpIKoala_C::PouchOpensDown
 * CImpIKoala_C::SleepForHoursAfterEating
 *
 * Purpose:
 *  Empty sample functions, no parameters, return NOERROR
 */

STDMETHODIMP CImpIKoala_C::ClimbEucalyptusTrees(void)
    {
    ODS("KoalaC's IKoala::ClimbEucalyptusTrees called");
    return NOERROR;
    }

STDMETHODIMP CImpIKoala_C::PouchOpensDown(void)
    {
    ODS("KoalaC's IKoala::PouchOpensDown called");
    return NOERROR;
    }

STDMETHODIMP CImpIKoala_C::SleepForHoursAfterEating(void)
    {
    ODS("KoalaC's IKoala::SleepForHoursAfterEating called");
    return NOERROR;
    }


KOALAC.H

/*
 * KOALAC.H
 *
 * Definition of the Koala object using containment, Chapter 2
 * Included only from KOALAC.CPP.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _KOALAC_H_
#define _KOALAC_H_

class CImpIAnimal_K;
typedef CImpIAnimal_K *PCImpIAnimal_K;

class CImpIKoala_C;
typedef CImpIKoala_C *PCImpIKoala_C;

class CKoalaC : public IUnknown
    {
    friend CImpIAnimal_K;
    friend CImpIKoala_C;

    private:
        DWORD           m_cRef;         //Object reference count

        PCImpIAnimal_K  m_pImpIAnimal;  //Interface implementation
        PCImpIKoala_C   m_pImpIKoala;   //Interface implementation

        IAnimal        *m_pIAnimal;     //From Animal

    public:
        CKoalaC(void);
        ~CKoalaC(void);

        BOOL Init(void);

        //IUnknown members, non-delegating
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(DWORD) AddRef(void);
        STDMETHODIMP_(DWORD) Release(void);
    };

typedef CKoalaC *PCKoalaC;

class CImpIAnimal_K : public IAnimal
    {
    private:
        DWORD       m_cRef;         //For debugging
        PCKoalaC    m_pObj;         //Back pointer

    public:
        CImpIAnimal_K(PCKoalaC);
        ~CImpIAnimal_K(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(DWORD) AddRef(void);
        STDMETHODIMP_(DWORD) Release(void);

        //IAnimal members
        STDMETHODIMP Eat(void);
        STDMETHODIMP Sleep(void);
        STDMETHODIMP Procreate(void);
    };

class CImpIKoala_C : public IKoala
    {
    private:
        DWORD       m_cRef;         //For debugging
        PCKoalaC    m_pObj;         //Back pointer

    public:
        CImpIKoala_C(PCKoalaC);
        ~CImpIKoala_C(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(DWORD) AddRef(void);
        STDMETHODIMP_(DWORD) Release(void);

        //IKoala members
        STDMETHODIMP ClimbEucalyptusTrees(void);
        STDMETHODIMP PouchOpensDown(void);
        STDMETHODIMP SleepForHoursAfterEating(void);
    };

#endif _KOALAC_H_


REUSE.CPP

/*
 * REUSE.CPP
 * Demonstration of COM Reusability mechanisms.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include <windows.h>
#include "reuse.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    MSG     msg;
    PAPP    pApp;

    pApp=new CApp(hInst, hInstPrev, nCmdShow);

    if (NULL==pApp)
        return -1;

    if (pApp->Init())
        {
        while (GetMessage(&msg, NULL, 0,0 ))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    delete pApp;
    return msg.wParam;
    }

/*
 * ReuseWndProc
 *
 * Purpose:
 *  Standard window class procedure.
 */

LRESULT APIENTRY ReuseWndProc(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    PAPP        pApp;
    BOOL        fRes;
    IAnimal    *pIAnimal;
    IKoala     *pIKoala;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    pApp=(PAPP)GetWindowLong(hWnd, REUSEWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pApp=(PAPP)(((LPCREATESTRUCT)lParam)->lpCreateParams);
            SetWindowLong(hWnd, REUSEWL_STRUCTURE, (LONG)pApp);
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_COMMAND:
            switch (wID)
                {
                case IDM_CREATECONTAINMENT:
                    if (NULL!=pApp->m_pIUnknown)
                        pApp->m_pIUnknown->Release();

                    fRes=CreateKoalaContainment(&pApp->m_pIUnknown);

                    pApp->Message(fRes ? TEXT("KoalaC created")
                        : TEXT("KoalaC creation failed"));

                    break;

                case IDM_CREATEAGGREGATION:
                    if (NULL!=pApp->m_pIUnknown)
                        pApp->m_pIUnknown->Release();

                    fRes=CreateKoalaAggregation(&pApp->m_pIUnknown);

                    pApp->Message(fRes ? TEXT("KoalaA created")
                        : TEXT("KoalaA creation failed"));

                    break;

                case IDM_ANIMALEAT:
                    if (!pApp->GetInterface(IID_IAnimal, (void **)&pIAnimal))
                        break;

                    pIAnimal->Eat();
                    pApp->Message(TEXT("IAnimal::Eat called"));
                    pIAnimal->Release();
                    break;

                case IDM_ANIMALSLEEP:
                    if (!pApp->GetInterface(IID_IAnimal, (void **)&pIAnimal))
                        break;

                    pIAnimal->Sleep();
                    pApp->Message(TEXT("IAnimal::Sleep called"));
                    pIAnimal->Release();
                    break;

                case IDM_ANIMALPROCREATE:
                    if (!pApp->GetInterface(IID_IAnimal, (void **)&pIAnimal))
                        break;

                    pIAnimal->Procreate();
                    pApp->Message(TEXT("IAnimal::Procreate called"));
                    pIAnimal->Release();
                    break;

                case IDM_KOALACLIMBEUCALYPTUSTREES:
                    if (!pApp->GetInterface(IID_IKoala, (void **)&pIKoala))
                        break;

                    pIKoala->ClimbEucalyptusTrees();
                    pApp->Message(TEXT("IKoala::ClimbEucalyptusTrees called"));
                    pIKoala->Release();
                    break;

                case IDM_KOALAPOUCHOPENSDOWN:
                    if (!pApp->GetInterface(IID_IKoala, (void **)&pIKoala))
                        break;

                    pIKoala->PouchOpensDown();
                    pApp->Message(TEXT("IKoala::PouchOpensDown called"));
                    pIKoala->Release();
                    break;

                case IDM_KOALASLEEPFORHOURSAFTEREATING:
                    if (!pApp->GetInterface(IID_IKoala, (void **)&pIKoala))
                        break;

                    pIKoala->SleepForHoursAfterEating();
                    pApp->Message(TEXT("IKoala::SleepForHoursAfterEating called"));
                    pIKoala->Release();
                    break;

                case IDM_RELEASE:
                    if (NULL==pApp->m_pIUnknown)
                        {
                        pApp->Message(TEXT("There is no object"));
                        break;
                        }

                    if (0==pApp->m_pIUnknown->Release())
                        {
                        pApp->m_pIUnknown=NULL;
                        pApp->Message(TEXT("Object released"));
                        }

                    break;

                case IDM_EXIT:
                    PostMessage(hWnd, WM_CLOSE, 0, 0L);
                    break;
                }
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * CApp::CApp
 * CApp::~CApp
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the Application from WinMain
 *  hInstPrev       HINSTANCE of a previous instance from WinMain
 *  nCmdShow        UINT specifying how to show the app window,
 *                  from WinMain.
 */

CApp::CApp(HINSTANCE hInst, HINSTANCE hInstPrev
    , UINT nCmdShow)
    {
    //Initialize WinMain parameter holders.
    m_hInst     =hInst;
    m_hInstPrev =hInstPrev;
    m_nCmdShow  =nCmdShow;

    m_hWnd=NULL;
    m_pIUnknown=NULL;

    return;
    }

CApp::~CApp(void)
    {
    //Release the object if we still have it.
    ReleaseInterface(m_pIUnknown);
    return;
    }

/*
 * CApp::Init
 *
 * Purpose:
 *  Initializes an CApp object by registering window classes,
 *  creating the main window, and doing anything else prone to
 *  failure.  If this function fails the caller should guarantee
 *  that the destructor is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::Init(void)
    {
    WNDCLASS    wc;

    if (!m_hInstPrev)
        {
        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = ReuseWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = CBWNDEXTRA;
        wc.hInstance      = m_hInst;
        wc.hIcon          = LoadIcon(m_hInst, TEXT("Icon"));
        wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName   = MAKEINTRESOURCE(IDR_MENU);
        wc.lpszClassName  = TEXT("REUSE");

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

    m_hWnd=CreateWindow(TEXT("REUSE"), TEXT("COM Reusability Demo")
        , WS_MINIMIZEBOX | WS_OVERLAPPEDWINDOW
        ,35, 35, 350, 250, NULL, NULL, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    ShowWindow(m_hWnd, m_nCmdShow);
    UpdateWindow(m_hWnd);

    return TRUE;
    }

/*
 * GetInterface
 *
 * Purpose:
 *  Centralized function to query for an interface and
 *  return the pointer.
 *
 * Parameters:
 *  riid            REFIID of the interface to query for.
 *  ppv             void ** in which to return the pointer.
 *
 * Return Value:
 *  BOOL            TRUE if the interface was retrieved, FALSE
 *                  otherwise.
 */

BOOL CApp::GetInterface(REFIID riid, void **ppv)
    {
    HRESULT     hr;

    *ppv=NULL;

    if (NULL==m_pIUnknown)
        {
        Message(TEXT("There is no object"));
        return FALSE;
        }

    hr=m_pIUnknown->QueryInterface(riid, ppv);

    if (FAILED(hr))
        {
        Message(TEXT("Failed to get the interface"));
        return FALSE;
        }

    return TRUE;
    }

/*
 * CApp::Message
 *
 * Purpose:
 *  Displays a message in the client area of the window.  This is
 *  just to centralize the call to simpify other code.
 *
 * Parameters:
 *  psz             LPTSTR to the string to display.
 *
 * Return Value:
 *  None
 */

void inline CApp::Message(LPTSTR psz)
    {
    HDC     hDC;
    RECT    rc;

    hDC=GetDC(m_hWnd);
    GetClientRect(m_hWnd, &rc);

    SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
    SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));

    /*
     * We'll just be sloppy and clear the whole window as
     * well as write the string with one ExtTextOut call.
     * No word wrapping here...
     */

    ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rc, psz, lstrlen(psz), NULL);
    ReleaseDC(m_hWnd, hDC);
    return;
    }


REUSE.H

/*
 * REUSE.H
 * COM Reusability Demo, Chapter 2
 *
 * Definitions, classes, and prototypes
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _REUSE_H_
#define _REUSE_H_

#define CHAPTER2
#include <inole.h>

#ifndef RC_INVOKED
#include "interfac.h"
#endif

//Menu Resource ID and Commands
#define IDR_MENU                    1

#define IDM_CREATECONTAINMENT               100
#define IDM_CREATEAGGREGATION               101
#define IDM_ANIMALEAT                       102
#define IDM_ANIMALSLEEP                     103
#define IDM_ANIMALPROCREATE                 104
#define IDM_KOALACLIMBEUCALYPTUSTREES       105
#define IDM_KOALAPOUCHOPENSDOWN             106
#define IDM_KOALASLEEPFORHOURSAFTEREATING   107
#define IDM_RELEASE                         108
#define IDM_EXIT                            109

//Global creation functions
BOOL    CreateKoalaContainment(IUnknown **);
BOOL    CreateKoalaAggregation(IUnknown **);
HRESULT CreateAnimal(IUnknown *, REFIID, void **);

//REUSE.CPP
LRESULT APIENTRY ReuseWndProc(HWND, UINT, WPARAM, LPARAM);

class CApp
    {
    friend LRESULT APIENTRY ReuseWndProc(HWND, UINT, WPARAM, LPARAM);

    protected:
        HINSTANCE       m_hInst;            //WinMain parameters
        HINSTANCE       m_hInstPrev;
        UINT            m_nCmdShow;

        HWND            m_hWnd;             //Main window handle
        IUnknown       *m_pIUnknown;        //Current object

    public:
        CApp(HINSTANCE, HINSTANCE, UINT);
        ~CApp(void);

        BOOL        Init(void);
        BOOL        GetInterface(REFIID, void **);
        void inline Message(LPTSTR);
    };

typedef CApp *PAPP;

#define CBWNDEXTRA          sizeof(PAPP)
#define REUSEWL_STRUCTURE   0

#endif //_REUSE_H_


README.TXT

There are no samples for Chapter 3.


CONNECT.CPP

/*
 * CONNECT.CPP
 * Demonstration of connectable objects and connection points,
 * Chapter 4
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "connect.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    MSG     msg;
    PAPP    pApp;

    pApp=new CApp(hInst, hInstPrev, nCmdShow);

    if (NULL==pApp)
        return -1;

    if (pApp->Init())
        {
        while (GetMessage(&msg, NULL, 0,0 ))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    delete pApp;
    return msg.wParam;
    }

/*
 * ConnectWndProc
 *
 * Purpose:
 *  Standard window class procedure.
 */

LRESULT APIENTRY ConnectWndProc(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    PAPP            pApp;
    BOOL            fRes;
    PCConnObject    pObj;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    pApp=(PAPP)GetWindowLong(hWnd, CONNECTWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pApp=(PAPP)(((LPCREATESTRUCT)lParam)->lpCreateParams);
            SetWindowLong(hWnd, CONNECTWL_STRUCTURE, (LONG)pApp);
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_COMMAND:
            switch (wID)
                {
                case IDM_OBJECTCREATE:
                    if (NULL!=pApp->m_pObj)
                        {
                        //Make sure the sinks disconnect
                        pApp->Disconnect(SINK1);
                        pApp->Disconnect(SINK2);
                        pApp->m_pObj->Release();
                        }

                    fRes=FALSE;

                    /*
                     * OLE clients never have direct access to
                     * an object's C++ class like this, so the
                     * sequence from here through the AddRef will
                     * always be encapsulated within the object's
                     * own creation functions.  This sample is a
                     * little contrived.
                     */
                    pObj=new CConnObject();

                    if (NULL!=pObj)
                        {
                        fRes=pObj->Init();
                        pObj->AddRef();
                        }

                    pApp->m_pObj=pObj;
                    pApp->Message(fRes ? TEXT("Object created")
                        : TEXT("Object creation failed"));

                    break;

                case IDM_OBJECTRELEASE:
                    if (NULL==pApp->m_pObj)
                        {
                        pApp->Message(TEXT("There is no object"));
                        break;
                        }

                    //Make sure the sinks disconnect
                    pApp->Disconnect(SINK1);
                    pApp->Disconnect(SINK2);

                    if (0==pApp->m_pObj->Release())
                        {
                        pApp->m_pObj=NULL;
                        pApp->Message(TEXT("Object released"));
                        }

                    break;

                case IDM_OBJECTSINK1CONNECT:
                    pApp->Connect(SINK1);
                    break;

                case IDM_OBJECTSINK1DISCONNECT:
                    pApp->Disconnect(SINK1);
                    break;

                case IDM_OBJECTSINK2CONNECT:
                    pApp->Connect(SINK2);
                    break;

                case IDM_OBJECTSINK2DISCONNECT:
                    pApp->Disconnect(SINK2);
                    break;

                case IDM_OBJECTEXIT:
                    PostMessage(hWnd, WM_CLOSE, 0, 0L);
                    break;

                case IDM_TRIGGERQUACK:
                case IDM_TRIGGERFLAP:
                case IDM_TRIGGERPADDLE:
                    if (NULL==pApp->m_pObj)
                        {
                        pApp->Message(TEXT("There is no object"));
                        break;
                        }

                    //The math gets the right ID for the event
                    if (!pApp->m_pObj->TriggerEvent
                        (wID-IDM_TRIGGERQUACK+EVENT_QUACK))
                        {
                        pApp->Message
                            (TEXT("There are no connected sinks"));
                        }

                    break;

                }
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * CApp::CApp
 * CApp::~CApp
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the Application from WinMain
 *  hInstPrev       HINSTANCE of a previous instance from WinMain
 *  nCmdShow        UINT specifying how to show the app window,
 *                  from WinMain.
 */

CApp::CApp(HINSTANCE hInst, HINSTANCE hInstPrev
    , UINT nCmdShow)
    {
    //Initialize WinMain parameter holders.
    m_hInst     =hInst;
    m_hInstPrev =hInstPrev;
    m_nCmdShow  =nCmdShow;

    m_hWnd=NULL;
    m_pObj=NULL;

    m_rgpSink[0]=NULL;
    m_rgpSink[1]=NULL;

    return;
    }

CApp::~CApp(void)
    {
    //Release the object if we still have it.
    ReleaseInterface(m_pObj);
    Disconnect(SINK1);
    Disconnect(SINK2);

    ReleaseInterface(m_rgpSink[SINK1]);
    ReleaseInterface(m_rgpSink[SINK2]);
    return;
    }

/*
 * CApp::Connect
 * CApp::Disconnect
 *
 * Purpose:
 *  Connects or disconnects a sink object to or from the
 *  connectable object we have.
 *
 * Parameters:
 *  uID             UINT identifying which sink to use.
 */

void CApp::Connect(UINT uID)
    {
    HRESULT             hr;
    IConnectionPoint   *pCP;

    if (NULL==m_pObj)
        {
        Message(TEXT("There is no object"));
        return;
        }

    if (SINK2 < uID)
        return;

    //Is this sink connected already?
    if (0!=m_rgpSink[uID]->m_dwCookie)
        {
        Message(TEXT("This sink is already connected"));
        return;
        }

    pCP=GetConnectionPoint();

    if (NULL!=pCP)
        {
        hr=pCP->Advise(m_rgpSink[uID]
            , &m_rgpSink[uID]->m_dwCookie);

        if (FAILED(hr))
            Message(TEXT("Connection failed"));
        else
            Message(TEXT("Connection complete"));

        pCP->Release();
        }
    else
        Message(TEXT("Failed to get IConnectionPoint"));

    return;
    }

void CApp::Disconnect(UINT uID)
    {
    HRESULT             hr;
    IConnectionPoint   *pCP;

    if (NULL==m_pObj)
        {
        Message(TEXT("There is no object"));
        return;
        }

    //Is the sink connected at all?
    if (0==m_rgpSink[uID]->m_dwCookie)
        {
        Message(TEXT("This sink is not connected"));
        return;
        }

    pCP=GetConnectionPoint();

    if (NULL!=pCP)
        {
        hr=pCP->Unadvise(m_rgpSink[uID]->m_dwCookie);

        if (FAILED(hr))
            Message(TEXT("Disconnection failed"));
        else
            {
            Message(TEXT("Disconnection complete"));
            m_rgpSink[uID]->m_dwCookie=0;
            }

        pCP->Release();
        }
    else
        Message(TEXT("Failed to get IConnectionPoint"));

    return;
    }

/*
 * CApp::GetConnectionPoint
 *
 * Purpose:
 *  Asks the connectable object for an IConnectionPoint for
 *  IDuckEvents.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  IConnectionPoint * to the interface
 */

IConnectionPoint * CApp::GetConnectionPoint(void)
    {
    HRESULT                     hr;
    IConnectionPointContainer  *pCPCont;
    IConnectionPoint           *pCP=NULL;

    hr=m_pObj->QueryInterface(IID_IConnectionPointContainer
        , (PPVOID)&pCPCont);

    if (FAILED(hr))
        return NULL;

    hr=pCPCont->FindConnectionPoint(IID_IDuckEvents, &pCP);
    pCPCont->Release();

    //This check isn't necessary...just adds clarity
    if (FAILED(hr))
        return NULL;

    return pCP;
    }

/*
 * CApp::Init
 *
 * Purpose:
 *  Initializes an CApp object by registering window classes,
 *  creating the main window, and doing anything else prone to
 *  failure.  If this function fails the caller should guarantee
 *  that the destructor is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::Init(void)
    {
    WNDCLASS    wc;

    //Create the two sink objects we might use
    m_rgpSink[SINK1]=new CDuckEvents(this, SINK1);
    m_rgpSink[SINK2]=new CDuckEvents(this, SINK2);

    if (NULL==m_rgpSink[SINK1] || NULL==m_rgpSink[SINK2])
        return FALSE;

    //Lock the lifetimes of these two objects
    m_rgpSink[SINK1]->AddRef();
    m_rgpSink[SINK2]->AddRef();

    //Create windows and such
    if (!m_hInstPrev)
        {
        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = ConnectWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = CBWNDEXTRA;
        wc.hInstance      = m_hInst;
        wc.hIcon          = LoadIcon(m_hInst, TEXT("Icon"));
        wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName   = MAKEINTRESOURCE(IDR_MENU);
        wc.lpszClassName  = TEXT("CONNECT");

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

    m_hWnd=CreateWindow(TEXT("CONNECT"), TEXT("Connectable Object Demo")
        , WS_MINIMIZEBOX | WS_OVERLAPPEDWINDOW
        ,35, 35, 350, 250, NULL, NULL, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    ShowWindow(m_hWnd, m_nCmdShow);
    UpdateWindow(m_hWnd);

    return TRUE;
    }

/*
 * CApp::Message
 *
 * Purpose:
 *  Displays a message in the client area of the window.  This is
 *  just to centralize the call to simpify other code.
 *
 * Parameters:
 *  psz             LPTSTR to the string to display.
 *
 * Return Value:
 *  None
 */

void inline CApp::Message(LPTSTR psz)
    {
    HDC     hDC;
    RECT    rc;

    hDC=GetDC(m_hWnd);
    GetClientRect(m_hWnd, &rc);

    SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
    SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));

    /*
     * We'll just be sloppy and clear the whole window as
     * well as write the string with one ExtTextOut call.
     * No word wrapping here...
     */

    ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rc, psz, lstrlen(psz), NULL);
    ReleaseDC(m_hWnd, hDC);
    return;
    }


CONNECT.H

/*
 * CONNECT.H
 * Connectable Object Sample, Chapter 4
 *
 * Definitions, classes, and prototypes
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _QUERY_H_
#define _QUERY_H_

#define CHAPTER4
#define INC_CONTROLS
#include <inole.h>

#ifndef RC_INVOKED
#include "interfac.h"
#include "object.h"
#endif

//Menu Resource ID and Commands
#define IDR_MENU                    1

#define IDM_OBJECTCREATE            100
#define IDM_OBJECTRELEASE           101
#define IDM_OBJECTSINK1CONNECT      102
#define IDM_OBJECTSINK1DISCONNECT   103
#define IDM_OBJECTSINK2CONNECT      104
#define IDM_OBJECTSINK2DISCONNECT   105
#define IDM_OBJECTEXIT              106

#define IDM_TRIGGERQUACK            200
#define IDM_TRIGGERFLAP             201
#define IDM_TRIGGERPADDLE           202

//CONNECT.CPP
LRESULT APIENTRY ConnectWndProc(HWND, UINT, WPARAM, LPARAM);

class CDuckEvents;
typedef CDuckEvents *PCDuckEvents;

//Identifiers for sinks, indices into m_rgpSink below
enum
    {
    SINK1=0,
    SINK2
    };

class CApp
    {
    friend LRESULT APIENTRY ConnectWndProc(HWND, UINT, WPARAM, LPARAM);

    protected:
        HINSTANCE       m_hInst;            //WinMain parameters
        HINSTANCE       m_hInstPrev;
        UINT            m_nCmdShow;

        HWND            m_hWnd;             //Main window handle
        PCDuckEvents    m_rgpSink[2];       //Sinks to connect

        /*
         * We need to have a pointer to the full object in
         * this somewhat contrived example because we have to
         * tell it when to fire notifications.  Usually there
         * will be something else besides a menu as used in
         * this sample to trigger those firings.
         */
        PCConnObject    m_pObj;             //Source object

    protected:
        void              Connect(UINT);
        void              Disconnect(UINT);
        IConnectionPoint *GetConnectionPoint(void);

    public:
        CApp(HINSTANCE, HINSTANCE, UINT);
        ~CApp(void);
        BOOL        Init(void);
        void        Message(LPTSTR);
    };

typedef CApp *PAPP;

#define CBWNDEXTRA          sizeof(PAPP)
#define CONNECTWL_STRUCTURE 0

class CDuckEvents : public IDuckEvents
    {
    private:
        ULONG       m_cRef;     //Reference count
        PAPP        m_pApp;     //For calling Message
        UINT        m_uID;      //Sink identifier

    public:
        //Connection key, public for CApp's usage
        DWORD       m_dwCookie;

    public:
        CDuckEvents(PAPP, UINT);
        ~CDuckEvents(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(DWORD) AddRef(void);
        STDMETHODIMP_(DWORD) Release(void);

        //IDuckEvents members
        STDMETHODIMP Quack(void);
        STDMETHODIMP Flap(void);
        STDMETHODIMP Paddle(void);
    };

#endif //_CONNECT_H_


CONNPT.CPP

/*
 * CONNPT.CPP
 *
 * Implemenation of a connection point object that manages
 * IUnknown pointers.  This is a stand-alone object created from
 * the implementation of IConnectionPointContainer.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "connect.h"

/*
 * CConnectionPoint::CConnectionPoint
 * CConnectionPoint::~CConnectionPoint
 *
 * Parameters (Constructor):
 *  pObj            PCConnObject of the object we're in.  We can
 *                  query this for the IConnectionPointContainer
 *                  interface we might need.
 *  riid            REFIID of the interface we're supporting
 */

CConnectionPoint::CConnectionPoint(PCConnObject pObj, REFIID riid)
    {
    UINT        i;

    m_cRef=0;
    m_iid=riid;

    /*
     * Our lifetime is controlled by the connectable object itself,
     * although other external clients will call AddRef and Release.
     * Since we're nested in the connectable object's lifetime,
     * there's no need to call AddRef on pObj.
     */
    m_pObj=pObj;

    for (i=0; i < CCONNMAX; i++)
        {
        m_rgpIUnknown[i]=NULL;
        m_rgdwCookies[i]=0;
        }

    m_cConn=0;
    m_dwCookieNext=100;       //Arbitrary starting cookie value
    return;
    }

CConnectionPoint::~CConnectionPoint(void)
    {
    UINT        i;

    for (i=0; i < CCONNMAX; i++)
        ReleaseInterface(m_rgpIUnknown[i]);

    return;
    }

/*
 * CConnectionPoint::QueryInterface
 * CConnectionPoint::AddRef
 * CConnectionPoint::Release
 *
 * Purpose:
 *  Non-delegating IUnknown members for CConnectionPoint.
 */

STDMETHODIMP CConnectionPoint::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IConnectionPoint==riid)
        *ppv=(LPVOID)this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CConnectionPoint::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CConnectionPoint::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CConnectionPoint::GetConnectionInterface
 *
 * Purpose:
 *  Returns the IID of the outgoing interface supported through
 *  this connection point.
 *
 * Parameters:
 *  pIID            IID * in which to store the IID.
 */

STDMETHODIMP CConnectionPoint::GetConnectionInterface(IID *pIID)
    {
    if (NULL==pIID)
        return ResultFromScode(E_POINTER);

    *pIID=m_iid;
    return NOERROR;
    }

/*
 * CConnectionPoint::GetConnectionPointContainer
 *
 * Purpose:
 *  Returns a pointer to the IConnectionPointContainer that
 *  is manageing this connection point.
 *
 * Parameters:
 *  ppCPC           IConnectionPointContainer ** in which to return
 *                  the pointer after calling AddRef.
 */

STDMETHODIMP CConnectionPoint::GetConnectionPointContainer
    (IConnectionPointContainer **ppCPC)
    {
    return m_pObj->QueryInterface(IID_IConnectionPointContainer
        , (void **)ppCPC);
    }

/*
 * CConnectionPoint::Advise
 *
 * Purpose:
 *  Provides this connection point with a notification sink to
 *  call whenever the appropriate outgoing function/event occurs.
 *
 * Parameters:
 *  pUnkSink        LPUNKNOWN to the sink to notify.  The connection
 *                  point must QueryInterface on this pointer to obtain
 *                  the proper interface to call.  The connection
 *                  point must also insure that any pointer held has
 *                  a reference count (QueryInterface will do it).
 *  pdwCookie       DWORD * in which to store the connection key for
 *                  later calls to Unadvise.
 */

STDMETHODIMP CConnectionPoint::Advise(LPUNKNOWN pUnkSink
    , DWORD *pdwCookie)
    {
    UINT            i;
    IUnknown       *pSink;

    *pdwCookie=0;

    //Check if we're already full of sink pointers
    if (CCONNMAX==m_cConn)
        return ResultFromScode(CONNECT_E_ADVISELIMIT);

    /*
     * Verify that the sink has the interface it's supposed
     * to.  We don't have to know what it is because we have
     * m_iid to describe it.  If this works, then we neatly
     * have a pointer with an AddRef that we can stow away.
     */
    if (FAILED(pUnkSink->QueryInterface(m_iid, (PPVOID)&pSink)))
        return ResultFromScode(CONNECT_E_CANNOTCONNECT);

    /*
     * We got the sink, now store it in our array.  We'll look for
     * the first entry that is NULL, indicating a free spot.
     */
    for (i=0; i < CCONNMAX; i++)
        {
        if (NULL==m_rgpIUnknown[i])
            {
            m_rgpIUnknown[i]=pSink;

            /*
             * Note:  this increment is not thread safe.  Needs
             * to be blocked with a semaphore if this were a
             * multi-threaded object.
             */
            m_rgdwCookies[i]=++m_dwCookieNext;
            *pdwCookie=m_dwCookieNext;
            break;
            }
        }

    m_cConn++;
    return NOERROR;
    }

/*
 * CConnectionPoint::Unadvise
 *
 * Purpose:
 *  Terminates the connection to the notification sink identified
 *  with dwCookie (that was returned from Advise).  The connection
 *  point has to Release any held pointers for that sink.
 *
 * Parameters:
 *  dwCookie        DWORD connection key from Advise.
 */

STDMETHODIMP CConnectionPoint::Unadvise(DWORD dwCookie)
    {
    UINT        i;

    if (0==dwCookie)
        return ResultFromScode(E_INVALIDARG);

    for (i=0; i < CCONNMAX; i++)
        {
        if (dwCookie==m_rgdwCookies[i])
            {
            //This releases the sink and sets the pointer to NULL
            ReleaseInterface(m_rgpIUnknown[i]);
            m_rgdwCookies[i]=0;
            m_cConn--;
            return NOERROR;
            }
        }

    return ResultFromScode(CONNECT_E_NOCONNECTION);
    }

/*
 * CConnectionPoint::EnumConnections
 *
 * Purpose:
 *  Creates and returns an enumerator object with the
 *  IEnumConnections interface that will enumerate the IUnknown
 *  pointers of each connected sink.
 *
 * Parameters:
 *  ppEnum          LPENUMCONNECTIONS in which to store the
 *                  IEnumConnections pointer.
 */

STDMETHODIMP CConnectionPoint::EnumConnections
    (LPENUMCONNECTIONS *ppEnum)
    {
    LPCONNECTDATA       pCD;
    UINT                i, j;
    PCEnumConnections   pEnum;

    *ppEnum=NULL;

    if (0==m_cConn)
        return ResultFromScode(OLE_E_NOCONNECTION);

    /*
     * Create the array of CONNECTDATA structures to give to the
     * enumerator.
     */
    pCD=new CONNECTDATA[(UINT)m_cConn];

    if (NULL==pCD)
        return ResultFromScode(E_OUTOFMEMORY);

    for (i=0, j=0; i < CCONNMAX; i++)
        {
        if (NULL!=m_rgpIUnknown[i])
            {
            pCD[j].pUnk=(LPUNKNOWN)m_rgpIUnknown[i];
            pCD[j].dwCookie=m_rgdwCookies[i];
            j++;
            }
        }

    /*
     * If creation works, it makes a copy pCD, so we can
     * always delete it regardless of the outcome.
     */
    pEnum=new CEnumConnections(this, m_cConn, pCD);
    delete [] pCD;

    if (NULL==pEnum)
        return ResultFromScode(E_OUTOFMEMORY);

    //This does an AddRef for us.
    return pEnum->QueryInterface(IID_IEnumConnections, (PPVOID)ppEnum);
    }

//Connection Enumerator follows

/*
 * CEnumConnections::CEnumConnections
 * CEnumConnections::~CEnumConnections
 *
 * Parameters (Constructor):
 *  pUnkRef         LPUNKNOWN to use for reference counting.
 *  cConn           ULONG number of connections in prgpConn
 *  prgConnData     LPCONNECTDATA to the array to enumerate.
 */

CEnumConnections::CEnumConnections(LPUNKNOWN pUnkRef, ULONG cConn
    , LPCONNECTDATA prgConnData)
    {
    UINT        i;

    m_cRef=0;
    m_pUnkRef=pUnkRef;

    m_iCur=0;
    m_cConn=cConn;

    /*
     * Copy the passed array.  We need to do this because a clone
     * has to have its own copy as well.
     */
    m_rgConnData=new CONNECTDATA[(UINT)cConn];

    if (NULL!=m_rgConnData)
        {
        for (i=0; i < cConn; i++)
            {
            m_rgConnData[i]=prgConnData[i];
            m_rgConnData[i].pUnk->AddRef();
            }
        }

    return;
    }

CEnumConnections::~CEnumConnections(void)
    {
    if (NULL!=m_rgConnData)
        {
        UINT        i;

        for (i=0; i < m_cConn; i++)
            m_rgConnData[i].pUnk->Release();

        delete [] m_rgConnData;
        }

    return;
    }

/*
 * CEnumConnections::QueryInterface
 * CEnumConnections::AddRef
 * CEnumConnections::Release
 *
 * Purpose:
 *  IUnknown members for CEnumConnections object.
 */

STDMETHODIMP CEnumConnections::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IEnumConnections==riid)
        *ppv=(LPVOID)this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CEnumConnections::AddRef(void)
    {
    ++m_cRef;
    m_pUnkRef->AddRef();
    return m_cRef;
    }

STDMETHODIMP_(ULONG) CEnumConnections::Release(void)
    {
    m_pUnkRef->Release();

    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CEnumConnections::Next
 *
 * Purpose:
 *  Returns the next element in the enumeration.
 *
 * Parameters:
 *  cConn           ULONG number of connections to return.
 *  pConnData       LPCONNECTDATA in which to store the returned
 *                  structures.
 *  pulEnum         ULONG * in which to return how many we
 *                  enumerated.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE otherwise,
 */

STDMETHODIMP CEnumConnections::Next(ULONG cConn
    , LPCONNECTDATA pConnData, ULONG *pulEnum)
    {
    ULONG               cReturn=0L;

    if (NULL==m_rgConnData)
        return ResultFromScode(S_FALSE);

    if (NULL==pulEnum)
        {
        if (1L!=cConn)
            return ResultFromScode(E_POINTER);
        }
    else
        *pulEnum=0L;

    if (NULL==pConnData || m_iCur >= m_cConn)
        return ResultFromScode(S_FALSE);

    while (m_iCur < m_cConn && cConn > 0)
        {
        *pConnData++=m_rgConnData[m_iCur];
        m_rgConnData[m_iCur++].pUnk->AddRef();
        cReturn++;
        cConn--;
        }

    if (NULL!=pulEnum)
        *pulEnum=cReturn;

    return NOERROR;
    }

STDMETHODIMP CEnumConnections::Skip(ULONG cSkip)
    {
    if (((m_iCur+cSkip) >= m_cConn) || NULL==m_rgConnData)
        return ResultFromScode(S_FALSE);

    m_iCur+=cSkip;
    return NOERROR;
    }

STDMETHODIMP CEnumConnections::Reset(void)
    {
    m_iCur=0;
    return NOERROR;
    }

STDMETHODIMP CEnumConnections::Clone(LPENUMCONNECTIONS *ppEnum)
    {
    PCEnumConnections   pNew;

    *ppEnum=NULL;

    //Create the clone
    pNew=new CEnumConnections(m_pUnkRef, m_cConn, m_rgConnData);

    if (NULL==pNew)
        return ResultFromScode(E_OUTOFMEMORY);

    pNew->AddRef();
    pNew->m_iCur=m_iCur;

    *ppEnum=pNew;
    return NOERROR;
    }


INTERFAC.H

/*
 * INTERFAC.H
 *
 * Definition of IDuckEvents used to demonstrate a connection
 * point implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _INTERFAC_H_
#define _INTERFAC_H_

DECLARE_INTERFACE_(IDuckEvents, IUnknown)
    {
    //IUnknown members
    STDMETHOD(QueryInterface) (THIS_ REFIID, PPVOID) PURE;
    STDMETHOD_(ULONG,AddRef)  (THIS) PURE;
    STDMETHOD_(ULONG,Release) (THIS) PURE;

    //IDuckEvents members
    STDMETHOD(Quack)(THIS) PURE;
    STDMETHOD(Flap)(THIS) PURE;
    STDMETHOD(Paddle)(THIS) PURE;
    };

typedef IDuckEvents *PDUCKEVENTS;

#endif //_INTERFAC_H_


OBJECT.CPP

/*
 * OBJECT.CPP
 *
 * Connectable Object implementation that supports the sample
 * interface IDuckEvents.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "connect.h"

/*
 * CConnObject::CConnObject
 * CConnObject::~CConnObject
 *
 * Constructor Parameters:
 *  None
 */

CConnObject::CConnObject(void)
    {
    UINT        i;

    m_cRef=0;

    for (i=0; i < CCONNPOINTS; i++)
        m_rgpConnPt[i]=NULL;

    return;
    }

CConnObject::~CConnObject(void)
    {
    UINT    i;

    for (i=0; i < CCONNPOINTS; i++)
        {
        if (NULL!=m_rgpConnPt[i])
            {
            if (NULL!=m_rgpConnPt[i])
                delete m_rgpConnPt[i];
            }
        }

    return;
    }

/*
 * CConnObject::Init
 *
 * Purpose:
 *  Instantiates the interface implementations for this object.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeds, FALSE otherwise.
 */

BOOL CConnObject::Init(void)
    {
    UINT    i;

    //Create our connection points
    for (i=0; i < CCONNPOINTS; i++)
        {
        m_rgpConnPt[i]=new CConnectionPoint(this, IID_IDuckEvents);

        if (NULL==m_rgpConnPt[i])
            return FALSE;

        m_rgpConnPt[i]->AddRef();
        }

    return TRUE;
    }

/*
 * CConnObject::QueryInterface
 *
 * Purpose:
 *  Manages the interfaces for this object which supports the
 *  IUnknown, ISampleOne, and ISampleTwo interfaces.
 *
 * Parameters:
 *  riid            REFIID of the interface to return.
 *  ppv             PPVOID in which to store the pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, E_NOINTERFACE if the
 *                  interface is not supported.
 */

STDMETHODIMP CConnObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    //Always NULL the out-parameters
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IConnectionPointContainer==riid)
        *ppv=this;

    if (NULL==*ppv)
        return ResultFromScode(E_NOINTERFACE);

    ((LPUNKNOWN)*ppv)->AddRef();
    return NOERROR;
    }

/*
 * CConnObject::AddRef
 * CConnObject::Release
 *
 * Reference counting members.  When Release sees a zero count
 * the object destroys itself.
 */

DWORD CConnObject::AddRef(void)
    {
    return ++m_cRef;
    }

DWORD CConnObject::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CConnObject::EnumConnectionPoints
 *
 * Purpose:
 *  Creates and returns an enumerator object with the
 *  IEnumConnectionPoints interface that will enumerate the
 *  individual connection points supported in this object.
 *
 * Parameters:
 *  ppEnum          LPENUMCONNECTIONPOINTS in which to store the
 *                  IEnumConnectionPoints pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, E_OUTOFMEMORY on failure or
 *                  other error code.
 */

STDMETHODIMP CConnObject::EnumConnectionPoints
    (LPENUMCONNECTIONPOINTS *ppEnum)
    {
    IConnectionPoint       *rgCP[CCONNPOINTS];
    UINT                    i;
    PCEnumConnectionPoints  pEnum;

    *ppEnum=NULL;

    for (i=0; i < CCONNPOINTS; i++)
        rgCP[i]=(IConnectionPoint *)m_rgpConnPt[i];

    //Create the enumerator:  we only have one connection point
    pEnum=new CEnumConnectionPoints(this, CCONNPOINTS, rgCP);

    if (NULL==pEnum)
        return ResultFromScode(E_OUTOFMEMORY);

    pEnum->AddRef();
    *ppEnum=pEnum;
    return NOERROR;
    }

/*
 * CConnObject::FindConnectionPoint
 *
 * Purpose:
 *  Returns a pointer to the IConnectionPoint for a given
 *  outgoing IID.
 *
 * Parameters:
 *  riid            REFIID of the outgoing interface for which
 *                  a connection point is desired.
 *  ppCP            IConnectionPoint ** in which to return
 *                  the pointer after calling AddRef.
 *
 * Return Value:
 *  HRESULT         NOERROR if the connection point is found,
 *                  E_NOINTERFACE if it's not supported.
 */

STDMETHODIMP CConnObject::FindConnectionPoint(REFIID riid
    , IConnectionPoint **ppCP)
    {
    *ppCP=NULL;

    if (IID_IDuckEvents==riid)
        {
        return m_rgpConnPt[0]->QueryInterface(IID_IConnectionPoint
            , (PPVOID)ppCP);
        }

    return ResultFromScode(E_NOINTERFACE);
    }

/*
 * CConnObject::TriggerEvent
 *
 * Purpose:
 *  Functions to make each connection point generate calls
 *  to any connected sinks.  Since these functions are specific
 *  to IDuckEvents, they only deal with the connection point
 *  for that one interface
 *
 * Parameters:
 *  iEvent          UINT of the event to trigger, either
 *                  EVENT_QUACK, EVENT_FLAP, or EVENT_PADDLE.
 *
 * Return Value:
 *  BOOL            TRUE events are triggered, FALSE if there
 *                  are no connected sinks.
 */

BOOL CConnObject::TriggerEvent(UINT iEvent)
    {
    IEnumConnections   *pEnum;
    CONNECTDATA         cd;

    if (FAILED(m_rgpConnPt[0]->EnumConnections(&pEnum)))
        return FALSE;

    while (NOERROR==pEnum->Next(1, &cd, NULL))
        {
        IDuckEvents    *pDuck;

        if (SUCCEEDED(cd.pUnk->QueryInterface(IID_IDuckEvents
            , (PPVOID)&pDuck)))
            {
            switch (iEvent)
                {
                case EVENT_QUACK:
                    pDuck->Quack();
                    break;

                case EVENT_FLAP:
                    pDuck->Flap();
                    break;

                case EVENT_PADDLE:
                    pDuck->Paddle();
                    break;
                }

            pDuck->Release();
            }

        cd.pUnk->Release();
        }

    pEnum->Release();
    return TRUE;
    }

//Connection Point Enumerator follows

/*
 * CEnumConnectionPoints::CEnumConnectionPoints
 * CEnumConnectionPoints::~CEnumConnectionPoints
 *
 * Parameters (Constructor):
 *  pUnkRef         LPUNKNOWN to use for reference counting.
 *  cPoints         ULONG number of connection points in prgpCP
 *  rgpCP           IConnectionPoint** to the array to enumerate.
 */

CEnumConnectionPoints::CEnumConnectionPoints(LPUNKNOWN pUnkRef
    , ULONG cPoints, IConnectionPoint **rgpCP)
    {
    UINT        i;

    m_cRef=0;
    m_pUnkRef=pUnkRef;

    m_iCur=0;
    m_cPoints=cPoints;
    m_rgpCP=new IConnectionPoint *[(UINT)cPoints];

    if (NULL!=m_rgpCP)
        {
        for (i=0; i < cPoints; i++)
            {
            m_rgpCP[i]=rgpCP[i];
            m_rgpCP[i]->AddRef();
            }
        }

    return;
    }

CEnumConnectionPoints::~CEnumConnectionPoints(void)
    {
    if (NULL!=m_rgpCP)
        {
        UINT        i;

        for (i=0; i < m_cPoints; i++)
            m_rgpCP[i]->Release();

        delete [] m_rgpCP;
        }

    return;
    }

/*
 * CEnumConnectionPoints::QueryInterface
 * CEnumConnectionPoints::AddRef
 * CEnumConnectionPoints::Release
 *
 * Purpose:
 *  IUnknown members for CEnumConnectionPoints object.
 */

STDMETHODIMP CEnumConnectionPoints::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IEnumConnectionPoints==riid)
        *ppv=(LPVOID)this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CEnumConnectionPoints::AddRef(void)
    {
    ++m_cRef;
    m_pUnkRef->AddRef();
    return m_cRef;
    }

STDMETHODIMP_(ULONG) CEnumConnectionPoints::Release(void)
    {
    m_pUnkRef->Release();

    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CEnumConnectionPoints::Next
 *
 * Purpose:
 *  Returns the next element in the enumeration.
 *
 * Parameters:
 *  cPoints         ULONG number of connection points to return.
 *  ppCP            IConnectionPoint** in which to store the returned
 *                  pointers.
 *  pulEnum         ULONG * in which to return how many we
 *                  enumerated.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE otherwise,
 */

STDMETHODIMP CEnumConnectionPoints::Next(ULONG cPoints
    , IConnectionPoint **ppCP, ULONG *pulEnum)
    {
    ULONG               cReturn=0L;

    if (NULL==m_rgpCP)
        return ResultFromScode(S_FALSE);

    if (NULL==ppCP)
        return ResultFromScode(E_POINTER);

    if (NULL==pulEnum)
        {
        if (1L!=cPoints)
            return ResultFromScode(E_POINTER);
        }
    else
        *pulEnum=0L;

    if (NULL==*ppCP || m_iCur >= m_cPoints)
        return ResultFromScode(S_FALSE);

    while (m_iCur < m_cPoints && cPoints > 0)
        {
        *ppCP=m_rgpCP[m_iCur++];

        if (NULL!=*ppCP)
            (*ppCP)->AddRef();

        ppCP++;
        cReturn++;
        cPoints--;
        }

    if (NULL!=pulEnum)
        *pulEnum=cReturn;

    return NOERROR;
    }

STDMETHODIMP CEnumConnectionPoints::Skip(ULONG cSkip)
    {
    if (((m_iCur+cSkip) >= m_cPoints) || NULL==m_rgpCP)
        return ResultFromScode(S_FALSE);

    m_iCur+=cSkip;
    return NOERROR;
    }

STDMETHODIMP CEnumConnectionPoints::Reset(void)
    {
    m_iCur=0;
    return NOERROR;
    }

STDMETHODIMP CEnumConnectionPoints::Clone
    (LPENUMCONNECTIONPOINTS *ppEnum)
    {
    PCEnumConnectionPoints   pNew;

    *ppEnum=NULL;

    //Create the clone
    pNew=new CEnumConnectionPoints(m_pUnkRef, m_cPoints, m_rgpCP);

    if (NULL==pNew)
        return ResultFromScode(E_OUTOFMEMORY);

    pNew->AddRef();
    pNew->m_iCur=m_iCur;

    *ppEnum=pNew;
    return NOERROR;
    }


OBJECT.H

/*
 * OBJECT.H
 *
 * Definition of the classes CConnObject, CImpIConnPtCont, and
 * CEnumConnectionPoints for the connectable object;
 * CConnectionPoint, CImpIConnectionPoint, CImpIDuckEvents, and
 * CEnumConnections for the connection point.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _OBJECT_H_
#define _OBJECT_H_

//Number of connection points in this implementation
#define CCONNPOINTS     1

/*
 * The connectable object itself implements IUnknown and
 * IConnectionPointContainer.  It is closely associated with
 * the connection point enumerator, CEnumConnectionPoints.
 */

class CConnectionPoint;
typedef CConnectionPoint *PCConnectionPoint;

//ID's for triggering events
enum
    {
    EVENT_QUACK=1, EVENT_FLAP, EVENT_PADDLE
    };

class CConnObject : public IConnectionPointContainer
    {
    private:
        DWORD             m_cRef;         //Object reference count

        //Array holding all the points we have.
        PCConnectionPoint m_rgpConnPt[CCONNPOINTS];

    public:
        CConnObject(void);
        ~CConnObject(void);

        BOOL Init(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(DWORD) AddRef(void);
        STDMETHODIMP_(DWORD) Release(void);

        //IConnectionPointContainer members
        STDMETHODIMP EnumConnectionPoints(IEnumConnectionPoints **);
    STDMETHODIMP FindConnectionPoint(REFIID, IConnectionPoint **);

        //Other members
        BOOL TriggerEvent(UINT);
    };

typedef CConnObject *PCConnObject;

//Enumerator class for EnumConnectionPoints

class CEnumConnectionPoints : public IEnumConnectionPoints
    {
    private:
        ULONG           m_cRef;     //Object reference count
        LPUNKNOWN       m_pUnkRef;  //IUnknown for ref counting
        ULONG           m_iCur;     //Current element
        ULONG           m_cPoints;  //Number of conn points
        IConnectionPoint **m_rgpCP; //Source of conn points

    public:
        CEnumConnectionPoints(LPUNKNOWN, ULONG, IConnectionPoint **);
        ~CEnumConnectionPoints(void);

        //IUnknown members that delegate to m_pUnkRef.
        STDMETHODIMP         QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IEnumConnectionPoints members
        STDMETHODIMP Next(ULONG, IConnectionPoint **, ULONG *);
        STDMETHODIMP Skip(ULONG);
        STDMETHODIMP Reset(void);
        STDMETHODIMP Clone(IEnumConnectionPoints **);
    };

typedef CEnumConnectionPoints *PCEnumConnectionPoints;

/*
 * The connection point object iself is contained within the
 * connection point container, which is the connectable object.
 * It therefore manages a back pointer to that connectable object,
 * and implement IConnectionPoint.  This object has a few
 * member functions besides those in IConnectionPoint that are
 * used to fire the outgoing calls.
 */

#define CCONNMAX    2

class CConnectionPoint : public IConnectionPoint
    {
    private:
        ULONG           m_cRef;     //Object reference count
        PCConnObject    m_pObj;     //Containing object

        IID             m_iid;      //Our relevant interface

        /*
         * To keep things simple we'll only support two
         * advise connections at most.  Production quality
         * connection points should supprt any number of
         * connections.  For each connection we need to maintain
         * the sink pointer and the cookie assigned to it.
         */
        IUnknown       *m_rgpIUnknown[CCONNMAX];
        DWORD           m_rgdwCookies[CCONNMAX];

        UINT            m_cConn;
        DWORD           m_dwCookieNext; //Counter

    public:
        CConnectionPoint(PCConnObject, REFIID);
        ~CConnectionPoint(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IConnectionPoint members
        STDMETHODIMP GetConnectionInterface(IID *);
        STDMETHODIMP GetConnectionPointContainer
            (IConnectionPointContainer **);
        STDMETHODIMP Advise(LPUNKNOWN, DWORD *);
        STDMETHODIMP Unadvise(DWORD);
        STDMETHODIMP EnumConnections(IEnumConnections **);
    };

//Enumeration clas for EnumConnections

class CEnumConnections : public IEnumConnections
    {
    private:
        ULONG           m_cRef;     //Object reference count
        LPUNKNOWN       m_pUnkRef;  //IUnknown for ref counting
        ULONG           m_iCur;     //Current element
        ULONG           m_cConn;    //Number of connections
        LPCONNECTDATA   m_rgConnData; //Source of connections

    public:
        CEnumConnections(LPUNKNOWN, ULONG, LPCONNECTDATA);
        ~CEnumConnections(void);

        //IUnknown members that delegate to m_pUnkRef.
        STDMETHODIMP         QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IEnumConnections members
        STDMETHODIMP Next(ULONG, LPCONNECTDATA, ULONG *);
        STDMETHODIMP Skip(ULONG);
        STDMETHODIMP Reset(void);
        STDMETHODIMP Clone(IEnumConnections **);
    };

typedef CEnumConnections *PCEnumConnections;

#endif _OBJECT_H_


PRECOMP.CPP

#include "connect.h"


SINK.CPP

/*
 * SINK.CPP
 *
 * Implementation of a sink with the IDuckEvents interface.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "connect.h"

/*
 * CDuckEvents::CDuckEvents
 * CDuckEvents::~CDuckEvents
 *
 * Parameters (Constructor):
 *  pApp            PAPP to the app object.
 *  uID             UINT identifier for this sink.
 */

CDuckEvents::CDuckEvents(PAPP pApp, UINT uID)
    {
    m_cRef=0;
    m_pApp=pApp;    //No AddRef needed, we're nested
    m_uID=uID;
    m_dwCookie=0;
    return;
    }

CDuckEvents::~CDuckEvents(void)
    {
    return;
    }

/*
 * CDuckEvents::QueryInterface
 * CDuckEvents::AddRef
 * CDuckEvents::Release
 *
 * Purpose:
 *  Non-delegating IUnknown members for CDuckEvents.
 */

STDMETHODIMP CDuckEvents::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDuckEvents==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDuckEvents::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDuckEvents::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDuckEvents::Quack
 *
 * Purpose:
 *  Event handler for Quacks
 */

STDMETHODIMP CDuckEvents::Quack(void)
    {
    TCHAR       szTemp[100];

    wsprintf(szTemp, TEXT("Sink #%u received Quack"), m_uID+1);
    m_pApp->Message(szTemp);

   #ifdef WIN32
    PlaySound(TEXT("quack.wav"), NULL, SND_SYNC);
   #endif

    return NOERROR;
    }

/*
 * CDuckEvents::Flap
 *
 * Purpose:
 *  Event handler for Flapping
 */

STDMETHODIMP CDuckEvents::Flap(void)
    {
    TCHAR       szTemp[100];

    wsprintf(szTemp, TEXT("Sink #%u received Flap"), m_uID+1);
    m_pApp->Message(szTemp);

   #ifdef WIN32
    PlaySound(TEXT("flap.wav"), NULL, SND_SYNC);
   #endif

    return NOERROR;
    }

/*
 * CDuckEvents::Paddle
 *
 * Purpose:
 *  Event handler for Paddling
 */

STDMETHODIMP CDuckEvents::Paddle(void)
    {
    TCHAR       szTemp[100];

    wsprintf(szTemp, TEXT("Sink #%u received Paddle"), m_uID+1);
    m_pApp->Message(szTemp);

   #ifdef WIN32
    PlaySound(TEXT("paddle.wav"), NULL, SND_SYNC);
   #endif

    return NOERROR;
    }


CLIENT.CPP

/*
 * CLIENT.CPP
 * Component Cosmo Chapter 5
 *
 * Implementation of the CCosmoClient class that just makes sure
 * we get a CCosmoDoc on doc creation and that we initialize fully.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cocosmo.h"

/*
 * CCosmoClient::CCosmoClient
 * CCosmoClient::~CCosmoClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 */

CCosmoClient::CCosmoClient(HINSTANCE hInst)
    : CClient(hInst)
    {
    return;
    }

CCosmoClient::~CCosmoClient(void)
    {
    return;
    }

/*
 * CCosmoClient::CreateCDocument
 *
 * Purpose:
 *  Constructs a new document specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::CreateCDocument(void)
    {
    return (PCDocument)(new CCosmoDoc(m_hInst, m_pFR));
    }

/*
 * CCosmoClient::NewDocument
 *
 * Purpose:
 *  Small override of the CClient::NewDocument that we have just
 *  to check the initial line selection on new document creation.
 *
 * Parameters:
 *  fVisible        BOOL indicating if the document is to be
 *                  visible or not.
 *  pAdv            PCDocumentAdviseSink to set with the new
 *                  document for notifications.  Can be NULL.
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::NewDocument(BOOL fVisible
    , PCDocumentAdviseSink pAdv)
    {
    PCDocument  pDoc;

    //Perform default NewDocument first
    pDoc=CClient::NewDocument(fVisible, pAdv);

    //We know that m_pFR is actually a CCosmoFrame, so type is safe.
    ((PCCosmoFrame)m_pFR)->CheckLineSelection(IDM_LINESOLID);

    return pDoc;
    }


COCOSMO.CPP

/*
 * COCOSMO.CPP
 * Component Cosmo Chapter 5
 *
 * WinMain and CCosmoFrame implementations.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

//Must do this once or we can't define our own GUIDs (see compobj.h)
#define INITGUIDS

#include "cocosmo.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCCosmoFrame    pFR;
    FRAMEINIT       fi;
    WPARAM          wRet;

    //Since we only use one DLL object, don't need SetMessageQueue

    //Attempt to allocate and initialize the application
    pFR=new CCosmoFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return -1;

    fi.idsMin=IDS_FRAMEMIN;
    fi.idsMax=IDS_FRAMEMAX;
    fi.idsStatMin=IDS_STATMESSAGEMIN;
    fi.idsStatMax=IDS_STATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=440;
    fi.cy=460;

    //If we can initialize pFR, start chugging messages
    if (pFR->FInit(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

/*
 * CCosmoFrame::CCosmoFrame
 * CCosmoFrame::~CCosmoFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WInMain
 */

CCosmoFrame::CCosmoFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    : CFrame(hInst, hInstPrev, pszCmdLine, nCmdShow)
    {
    UINT        i;

    for (i=0; i<5; i++)
        m_hBmpLines[i]=NULL;

    m_uIDCurLine=0;
    m_fInitialized=FALSE;
    return;
    }

CCosmoFrame::~CCosmoFrame(void)
    {
    UINT        i;

    for (i=0; i<5; i++)
        {
        if (NULL!=m_hBmpLines[i])
            DeleteObject(m_hBmpLines[i]);
        }

    if (m_fInitialized)
        CoUninitialize();

    return;
    }

/*
 * CCosmoFrame::FInit
 *
 * Purpose:
 *  Call CoInitialize then calling down into the base class
 *  initialization.
 *
 * Parameters:
 *  pFI             PFRAMEINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CCosmoFrame::FInit(PFRAMEINIT pFI)
    {
    DWORD       dwVer;

    //Make sure COMPOBJ.DLL is the right version
    dwVer=CoBuildVersion();

    if (rmm!=HIWORD(dwVer))
        return FALSE;

    if (FAILED(CoInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    return CFrame::FInit(pFI);
    }

/*
 * CCosmoFrame::CreateCClient
 *
 * Purpose:
 *  Constructs a new client specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient        Pointer to the new client object.
 */

PCClient CCosmoFrame::CreateCClient(void)
    {
    return (PCClient)(new CCosmoClient(m_hInst));
    }

/*
 * CCosmoFrame::FRegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CCosmoFrame::FRegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //First let the standard frame do its thing
    if (!CFrame::FRegisterAllClasses())
        return FALSE;

    /*
     * We want a different background color for the document
     * because the Polyline we put in the document will paint
     * with COLOR_WINDOW which by default which is CLASSLIB's
     * default document color.
     */

    GetClassInfo(m_hInst, SZCLASSDOCUMENT, &wc);
    UnregisterClass(SZCLASSDOCUMENT, m_hInst);

    wc.hbrBackground=(HBRUSH)(COLOR_APPWORKSPACE+1);

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

    //No need to register the Polyline window now...

    return TRUE;
    }

/*
 * CCosmoFrame::FPreShowInit
 *
 * Purpose:
 *  Called from FInit before intially showing the window.  We do
 *  whatever else we want here, modifying nCmdShow as necessary
 *  which affects ShowWindow in FInit.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if this initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CCosmoFrame::FPreShowInit(void)
    {
    CreateLineMenu();
    CheckLineSelection(IDM_LINESOLID);
    return TRUE;
    }

/*
 * CCosmoFrame::CreateLineMenu
 *
 * Purpose:
 *  Initializes the bitmaps used to create the Line menu and
 *  replaces the text items defined in the application resources
 *  with these bitmaps.  Note that the contents of m_hBmpLines
 *  must be cleaned up when the application terminates.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CreateLineMenu(void)
    {
    HMENU       hMenu;
    HDC         hDC, hMemDC;
    HPEN        hPen;
    HGDIOBJ     hObj;
    TEXTMETRIC  tm;
    UINT        i, cx, cy;

    hMenu=GetSubMenu(GetMenu(m_hWnd), 3);   //Line menu.
    hDC=GetDC(m_hWnd);

    //Create each line in a menu item 8 chars wide, one char high.
    GetTextMetrics(hDC, &tm);
    cx=tm.tmAveCharWidth*8;
    cy=tm.tmHeight;

    /*
     * Create a memory DC in which to draw lines, and bitmaps
     * for each line.
     */
    hMemDC=CreateCompatibleDC(hDC);
    ReleaseDC(m_hWnd, hDC);

    for (i=0; i<5; i++)
        {
        m_hBmpLines[i]=CreateCompatibleBitmap(hMemDC, cx, cy);
        SelectObject(hMemDC, m_hBmpLines[i]);

        PatBlt(hMemDC, 0, 0, cx, cy, WHITENESS);

        hPen=CreatePen(i, 1, 0L);       //i=line style like PS_SOLID
        hObj=SelectObject(hMemDC, hPen);

        MoveToEx(hMemDC, 0, cy/2, NULL);
        LineTo(hMemDC, cx, cy/2);

        ModifyMenu(hMenu, IDM_LINEMIN+i, MF_BYCOMMAND | MF_BITMAP
            , IDM_LINEMIN+i, (LPTSTR)(LONG)(UINT)m_hBmpLines[i]);

        SelectObject(hMemDC, hObj);
        DeleteObject(hPen);
        }

    CheckMenuItem(hMenu, IDM_LINESOLID, MF_CHECKED);
    DeleteDC(hMemDC);

    return;
    }

/*
 * CCosmoFrame::CreateGizmos
 *
 * Purpose:
 *  Procedure to create all the necessary gizmobar buttons.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of gizmos added to the bar.
 */

UINT CCosmoFrame::CreateGizmos(void)
    {
    UINT            iLast;
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;
    UINT            utEx  =GIZMOTYPE_BUTTONATTRIBUTEEX;

    //Insert the standard ones.
    iLast=CFrame::CreateGizmos();

    /*
     * Insert File Import in the 5th position and account for
     * it in iLast.
     */
    m_pGB->Add(utCmd, 4, IDM_FILEIMPORT, m_dxB, m_dyB
        , NULL, m_hBmp, 2, uState);
    iLast++;

    //Separator
    m_pGB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    /*
     * For the Background bitmap, preserve our use of black
     * (part of the image)
     */
    m_pGB->Add(utCmd, iLast++, IDM_COLORBACKGROUND, m_dxB, m_dyB
        , NULL, m_hBmp, 3, GIZMO_NORMAL | PRESERVE_BLACK);

    m_pGB->Add(utCmd, iLast++, IDM_COLORLINE, m_dxB, m_dyB
        , NULL, m_hBmp, 4, uState);

    //Separator
    m_pGB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //Line styles.
    m_pGB->Add(utEx, iLast++, IDM_LINESOLID, m_dxB, m_dyB
        , NULL, m_hBmp, 5, uState);
    m_pGB->Add(utEx, iLast++, IDM_LINEDASH, m_dxB, m_dyB
        , NULL, m_hBmp, 6, uState);
    m_pGB->Add(utEx, iLast++, IDM_LINEDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 7, uState);
    m_pGB->Add(utEx, iLast++, IDM_LINEDASHDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 8, uState);
    m_pGB->Add(utEx, iLast++, IDM_LINEDASHDOTDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 9, uState);

    return iLast;
    }

/*
 * CCosmoFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the Cosmo frame window that just
 *  processes the line menu and the color menu leaving the
 *  CFrame to do everything else.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CCosmoFrame::OnCommand(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PCCosmoDoc      pDoc;
    TCHAR           szFile[CCHPATHMAX];
    BOOL            fOK;
    UINT            i, uTemp;
    COLORREF        rgColors[16];
    CHOOSECOLOR     cc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many GizmoBar notifications.
     */
    if (m_fInit)
        return 0L;

    pDoc=(PCCosmoDoc)m_pCL->ActiveDocument();

    /*
     * Check for the line style commands which are
     * IDM_LINEMIN+<style>.  We handle this by changing the menu
     * and toolbar, then we pass it to the document for real
     * processing.
     */
    if (NULL!=pDoc && IDM_LINEMIN <= wID && IDM_LINEMAX >=wID)
        {
        CheckLineSelection(wID);
        pDoc->LineStyleSet(wID-IDM_LINEMIN);
        return 0L;
        }

    switch (wID)
        {
        case IDM_FILEIMPORT:
            szFile[0]=0;
            fOK=FSaveOpenDialog(szFile, CCHPATHMAX, IDS_FILEIMPORT
                , TRUE, &i);

            if (fOK)
                {
                uTemp=pDoc->ULoad(FALSE, szFile);
                pDoc->ErrorMessage(uTemp);
                }

            return (LRESULT)fOK;

        case IDM_COLORBACKGROUND:
        case IDM_COLORLINE:
            //Invoke the color chooser for either color
            uTemp=(IDM_COLORBACKGROUND==wID)
                ? DOCCOLOR_BACKGROUND : DOCCOLOR_LINE;

            for (i=0; i<16; i++)
                rgColors[i]=RGB(0, 0, i*16);

            memset(&cc, 0, sizeof(CHOOSECOLOR));
            cc.lStructSize=sizeof(CHOOSECOLOR);
            cc.lpCustColors=rgColors;
            cc.hwndOwner=hWnd;
            cc.Flags=CC_RGBINIT;
            cc.rgbResult=pDoc->ColorGet(uTemp);

            if (ChooseColor(&cc))
                pDoc->ColorSet(uTemp, cc.rgbResult);

            break;

        default:
           CFrame::OnCommand(hWnd, wParam, lParam);
        }

    return 0L;
    }

/*
 * CCosmoFrame::OnDocumentDataChange
 *
 * Purpose:
 *  Update the Line menu and GizmoBar if the style in the data
 *  changes.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentDataChange(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::OnDocumentActivate
 *
 * Purpose:
 *  Informs us that document activation changed, so update the UI
 *  for that new document.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentActivate(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    PCDocument  pDoc;
    BOOL        fOK=FALSE;
    BOOL        fCallDefault=TRUE;
    UINT        i;
    UINT        uTemp;
    UINT        uTempE;
    UINT        uTempD;

    pDoc=m_pCL->ActiveDocument();

    uTempE=MF_ENABLED | MF_BYCOMMAND;
    uTempD=MF_DISABLED | MF_GRAYED | MF_BYCOMMAND;
    uTemp=((NULL!=pDoc) ? uTempE : uTempD);

    //File menu:  If there is document window, disable Import.
    if (m_phMenu[0]==hMenu)
        EnableMenuItem(hMenu, IDM_FILEIMPORT, uTemp);

    //Color menu:  no document, no commands
    if (m_phMenu[2]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_COLORBACKGROUND, uTemp);
        EnableMenuItem(hMenu, IDM_COLORLINE,       uTemp);
        fCallDefault=FALSE;
        }

    //Line menu:  no document, no commands
    if (m_phMenu[3]==hMenu)
        {
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            EnableMenuItem(hMenu, i, uTemp);

        fCallDefault=FALSE;
        }

    if (fCallDefault)
        CFrame::UpdateMenus(hMenu, iMenu);

    return;
    }

/*
 * CCosmoFrame::UpdateGizmos
 *
 * Purpose:
 *  Enables and disables gizmos depending on whether we have
 *  a document or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateGizmos(void)
    {
    BOOL        fLast;
    UINT        i;

    //Save the last enabled state before CFrame changes it
    fLast=m_fLastEnable;

    //Let the default hack on its gizmos.
    CFrame::UpdateGizmos();

    /*
     * If CFrame::UpdateGizmos changed anything, then we neeed
     * to change as well--if nothing changes, nothing to do.
     */
    if (fLast!=m_fLastEnable)
        {
        //No document, disable just about everything
        m_pGB->Enable(IDM_FILEIMPORT, m_fLastEnable);

        m_pGB->Enable(IDM_COLORBACKGROUND, m_fLastEnable);
        m_pGB->Enable(IDM_COLORLINE,       m_fLastEnable);

        for (i=IDM_LINEMIN; i <= IDM_LINEMAX; i++)
            m_pGB->Enable(i, m_fLastEnable);
        }

    return;
    }

/*
 * CCosmoFrame::CheckLineSelection
 *
 * Purpose:
 *  Maintains the bitmap menu and the gizmos for the line selection.
 *  Both are mutially exclusive option lists where a selection in
 *  one has to affect the other.
 *
 * Parameters:
 *  uID             UINT ID of the item to be selected
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CheckLineSelection(UINT uID)
    {
    UINT        i;
    HMENU       hMenu;

    //Update menus and gizmo if the selection changes.
    if (uID!=m_uIDCurLine)
        {
        m_uIDCurLine=uID;
        hMenu=GetMenu(m_hWnd);

        //Uncheck all lines initially.
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            CheckMenuItem(hMenu, i, MF_UNCHECKED | MF_BYCOMMAND);

        CheckMenuItem(hMenu, uID, MF_CHECKED | MF_BYCOMMAND);
        m_pGB->Check(uID, TRUE);
        }

    return;
    }


COCOSMO.H

/*
 * COCOSMO.H
 * Component Cosmo Chapter 5
 *
 * Single include file that pulls in everything needed for other
 * source files in the Cosmo application.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _COCOSMO_H_
#define _COCOSMO_H_

#define INC_OLE2
#include <windows.h>
#include <memory.h>
//CHAPTER5MOD
#include <ole2.h>
#include <ole2ver.h>
#include <bookguid.h>
#include <ipoly5.h>
//End CHAPTER5MOD

extern "C"
    {
    #include <commdlg.h>
    }

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

//COCOSMO.CPP:  Frame object that creates a main window

class CCosmoFrame : public CFrame
    {
    private:
        HBITMAP         m_hBmpLines[5];     //Menu item bitmaps
        UINT            m_uIDCurLine;       //Current line selection
        BOOL            m_fInitialized;     //CoInitialize work?

    protected:
        //Overridable for creating a CClient for this frame
        virtual PCClient  CreateCClient(void);

        virtual BOOL      FRegisterAllClasses(void);
        virtual BOOL      FPreShowInit(void);
        virtual UINT      CreateGizmos(void);

        virtual LRESULT   OnCommand(HWND, WPARAM, LPARAM);
        virtual void      OnDocumentDataChange(PCDocument);
        virtual void      OnDocumentActivate(PCDocument);

        //New for this class
        virtual void      CreateLineMenu(void);

    public:
        CCosmoFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CCosmoFrame(void);

        //Overrides
        virtual BOOL      FInit(PFRAMEINIT);
        virtual void      UpdateMenus(HMENU, UINT);
        virtual void      UpdateGizmos(void);

        //New for this class
        virtual void      CheckLineSelection(UINT);
    };

typedef CCosmoFrame *PCCosmoFrame;

//CLIENT.CPP

/*
 * The only reason we have a derived class here is to override
 * CreateCDocument so we can create our own type as well as
 * overriding NewDocument to perform one other piece of work once
 * the document's been created.
 */

class CCosmoClient : public CClient
    {
    protected:
        //Overridable for creating a new CDocument
        virtual PCDocument CreateCDocument(void);

    public:
        CCosmoClient(HINSTANCE);
        virtual ~CCosmoClient(void);

        virtual PCDocument NewDocument(BOOL, PCDocumentAdviseSink);
    };

typedef CCosmoClient *PCCosmoClient;

//DOCUMENT.CPP

//Explicit CPolyline moved into Polyline DLL and hidden from us.
class CPolylineAdviseSink : public IPolylineAdviseSink5
    {
    private:
        LPVOID      m_pv;             //Customizable structure
        ULONG       m_cRef;

    public:
        CPolylineAdviseSink(LPVOID);
        ~CPolylineAdviseSink(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //Advise members.
        STDMETHODIMP_(void) OnPointChange(void);
        STDMETHODIMP_(void) OnSizeChange(void);
        STDMETHODIMP_(void) OnDataChange(void);
        STDMETHODIMP_(void) OnColorChange(void);
        STDMETHODIMP_(void) OnLineStyleChange(void);
    };

typedef CPolylineAdviseSink *PCPolylineAdviseSink;

//Constant ID for the window polyline that lives in a document
#define ID_POLYLINE         10

class CCosmoDoc : public CDocument
    {
    friend class CPolylineAdviseSink;

    protected:
        UINT                    m_uPrevSize;    //Last WM_SIZE wParam

        PPOLYLINE               m_pPL;          //Polyline window here
        PPOLYLINEADVISESINK     m_pPLAdv;       //Advises from Polyline

        //CHAPTER5MOD
        LPSTORAGE               m_pIStorage;        //Doc storage
        LPPERSISTSTORAGE        m_pIPersistStorage; //Polyline's
        //End CHAPTER5MOD

    protected:
        virtual BOOL    FMessageHook(HWND, UINT, WPARAM, LPARAM
            , LRESULT *);

    public:
        CCosmoDoc(HINSTANCE, PCFrame);
        virtual ~CCosmoDoc(void);

        virtual BOOL     FInit(PDOCUMENTINIT);

        virtual void     Clear(void);

        virtual UINT     ULoad(BOOL, LPTSTR);
        virtual UINT     USave(UINT, LPTSTR);

        virtual void     Undo(void);
        virtual BOOL     FClip(HWND, BOOL);
        virtual HGLOBAL  RenderFormat(UINT);
        virtual BOOL     FQueryPaste(void);
        virtual BOOL     FPaste(HWND);

        virtual COLORREF ColorSet(UINT, COLORREF);
        virtual COLORREF ColorGet(UINT);

        virtual UINT     LineStyleSet(UINT);
        virtual UINT     LineStyleGet(void);
    };

typedef CCosmoDoc *PCCosmoDoc;

//These color indices wrap the polyline definitions
#define DOCCOLOR_BACKGROUND             POLYLINECOLOR_BACKGROUND
#define DOCCOLOR_LINE                   POLYLINECOLOR_LINE

#endif //_COCOSMO_H_


DOCUMENT.CPP

/*
 * DOCUMENT.CPP
 * Component Cosmo Chapter 5
 *
 * Implementation of the CCosmoDoc derivation of CDocument as
 * well as an implementation of CPolylineAdviseSink.
 *
 * Copyright (c)1993-1997 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cocosmo.h"

/*
 * CCosmoDoc::CCosmoDoc
 * CCosmoDoc::~CCosmoDoc
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame of the frame object.
 */

CCosmoDoc::CCosmoDoc(HINSTANCE hInst, PCFrame pFR)
    : CDocument(hInst, pFR)
    {
    m_uPrevSize=SIZE_RESTORED;
    m_pPL=NULL;
    m_pPLAdv=NULL;

    //CHAPTER5MOD
    m_pIStorage=NULL;
    m_pIPersistStorage=NULL;
    //End CHAPTER5MOD
    return;
    }

CCosmoDoc::~CCosmoDoc(void)
    {
    //The client takes care of destroying document windows.

    //CHAPTER5MOD
    if (NULL!=m_pIPersistStorage)
        m_pIPersistStorage->Release();

    if (NULL!=m_pIStorage)
        m_pIStorage->Release();
    //End CHAPTER5MOD

    if (NULL!=m_pPL)
        m_pPL->Release();

    if (NULL!=m_pPLAdv)
        m_pPLAdv->Release;

    CoFreeUnusedLibraries();
    return;
    }

/*
 * CCosmoDoc::FInit
 *
 * Purpose:
 *  Initializes an already created document window.  The client
 *  actually creates the window for us, then passes that here for
 *  further initialization.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CCosmoDoc::FInit(PDOCUMENTINIT pDI)
    {
    RECT        rc;
    HRESULT     hr;

    //Change the stringtable range to our customization.
    pDI->idsMin=IDS_DOCUMENTMIN;
    pDI->idsMax=IDS_DOCUMENTMAX;

    //Do default initialization
    if (!CDocument::FInit(pDI))
        return FALSE;

    //CHAPTER5MOD
    //Create the Polyline Component Object via COMPOBJ.DLL functions
    hr=CoCreateInstance(CLSID_Polyline5, NULL, CLSCTX_INPROC_SERVER
        , IID_IPolyline5, (PPVOID)&m_pPL);
    //End CHAPTER5MOD

    if (FAILED(hr))
        {
        //Warn that we could not load the Polyline
        MessageBox(pDI->hWndDoc, PSZ(IDS_NOPOLYLINE)
            , PSZ(IDS_CAPTION), MB_OK);
        return FALSE;
        }

    //Initialize the contained Polyline which creates a window.
    GetClientRect(m_hWnd, &rc);
    InflateRect(&rc, -8, -8);

    if (FAILED(m_pPL->Init(m_hWnd, &rc, WS_CHILD | WS_VISIBLE
        , ID_POLYLINE)))
        return FALSE;

    //Set up an advise on the Polyline.
    m_pPLAdv=new CPolylineAdviseSink(this);
    m_pPL->SetAdvise(m_pPLAdv);

    //CHAPTER5MOD
    hr=m_pPL->QueryInterface(IID_IPersistStorage
        , (PPVOID)&m_pIPersistStorage);

    if (FAILED(hr))
        return FALSE;
    //End CHAPTER5MOD

    return TRUE;
    }

/*
 * CCosmoDoc::FMessageHook
 *
 * Purpose:
 *  Processes WM_SIZE for the document so we can resize
 *  the Polyline.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CCosmoDoc::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    UINT        dx, dy;
    RECT        rc;

    *pLRes=0;

    if (WM_SIZE==iMsg)
        {
        //Don't effect the Polyline size to or from minimized state.
        if (SIZE_MINIMIZED!=wParam && SIZE_MINIMIZED !=m_uPrevSize)
            {
            //When we change size, resize any Polyline we hold.
            dx=LOWORD(lParam);
            dy=HIWORD(lParam);

            /*
             * If we are getting WM_SIZE in response to a Polyline
             * notification, then don't resize the Polyline window
             * again.
             */
            if (!m_fNoSize && NULL!=m_pPL)
                {
                //Resize the polyline to fit the new client
                SetRect(&rc, 8, 8, dx-8, dy-8);
                m_pPL->RectSet(&rc, FALSE);

                /*
                 * We consider sizing something that makes the file
                 * dirty, but not until we've finished the create
                 * process, which is why we set fNoDirty to FALSE
                 * in WM_CREATE since we get a WM_SIZE on the first
                 * creation.
                 */
                if (!m_fNoDirty)
                    FDirtySet(TRUE);

                SetRect(&rc, 0, 0, dx, dy);

                if (NULL!=m_pAdv)
                    m_pAdv->OnSizeChange(this, &rc);

                m_fNoDirty=FALSE;
                }
            }

        m_uPrevSize=wParam;
        }

    /*
     * We return FALSE even on WM_SIZE so we can let the default
     * procedure handle maximized MDI child windows appropriately.
     */
    return FALSE;
    }

/*
 * CCosmoDoc::Clear
 *
 * Purpose:
 *  Sets all contents in the document back to defaults with
 *  no filename.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Clear(void)
    {
    //Completely reset the polyline
    m_pPL->New();

    CDocument::Clear();
    return;
    }

/*
 * CCosmoDoc::ULoad
 *
 * Purpose:
 *  Loads a given document without any user interface overwriting
 *  the previous contents of the Polyline window.  We do this by
 *  opening the file and telling the Polyline to load itself from
 *  that file.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load, NULL if the file
 *                  is new and untitled.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CCosmoDoc::ULoad(BOOL fChangeFile, LPTSTR pszFile)
    {
    HRESULT             hr;
    //CHAPTER5MOD
    LPSTORAGE           pIStorage;

    if (NULL==pszFile)
        {
        //CHAPTER5MOD
        /*
         * As a user of an IPersistStorage we have to provide all
         * objects with an IStorage they can use for incremental
         * access passing that storage to InitNew.  Here we create
         * a temporary file that we don't bother holding on to.
         * If the object doesn't use it, then our Release destroys
         * it immediately.
         */

        hr=StgCreateDocfile(NULL, STGM_DIRECT | STGM_READWRITE
            | STGM_CREATE | STGM_DELETEONRELEASE
            | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;

        m_pIPersistStorage->InitNew(pIStorage);
        m_pIStorage=pIStorage;
        //End CHAPTER5MOD

        Rename(NULL);
        return DOCERR_NONE;
        }

    //CHAPTER5MOD
    /*
     * Open a storage and pass it to the Polyline via
     * IPersistStorage.  We do not remain compatible with
     * previous files saved with Component Cosmo.
     */

#if defined(WIN32) && !defined(UNICODE)
    OLECHAR pwcsFile[MAX_PATH];
    mbstowcs(pwcsFile, pszFile, MAX_PATH);
    hr=StgOpenStorage(pwcsFile, NULL, STGM_DIRECT | STGM_READWRITE
        | STGM_SHARE_EXCLUSIVE, NULL, 0, &pIStorage);
#else
    hr=StgOpenStorage(pszFile, NULL, STGM_DIRECT | STGM_READWRITE
        | STGM_SHARE_EXCLUSIVE, NULL, 0, &pIStorage);
#endif
    if (FAILED(hr))
        return DOCERR_COULDNOTOPEN;

    hr=m_pIPersistStorage->Load(pIStorage);
    m_pIStorage=pIStorage;
    //End CHAPTER5MOD

    if (FAILED(hr))
        return DOCERR_READFAILURE;

    if (fChangeFile)
        Rename(pszFile);

    //Importing a file makes things dirty
    FDirtySet(!fChangeFile);

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::USave
 *
 * Purpose:
 *  Writes the file to a known filename, requiring that the user
 *  has previously used FileOpen or FileSaveAs in order to have
 *  a filename.
 *
 * Parameters:
 *  uType           UINT indicating the type of file the user
 *                  requested to save in the File Save As dialog.
 *  pszFile         LPTSTR under which to save.  If NULL, use the
 *                  current name.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CCosmoDoc::USave(UINT uType, LPTSTR pszFile)
    {
    BOOL                fRename=TRUE;
    HRESULT             hr;
    //CHAPTER5MOD
    LPSTORAGE           pIStorage;
    BOOL                fSameAsLoad;

    //If Save or Save As under the same name, do Save.
    if (NULL==pszFile || 0==lstrcmpi(pszFile, m_szFile))
        {
        fRename=FALSE;
        pszFile=m_szFile;

        /*
         * If we're saving to an existing storage, just pass
         * the IStorage we have from ULoad along with TRUE
         * in fSameAsLoad.
         */

        fSameAsLoad=TRUE;
        }
    else
        {
        /*
         * In Component Cosmo, we only deal with one version of
         * data; all the code in Chapter 2 Cosmo that dealt with
         * 1.0 and 2.0 files has been removed.
         */
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsFile[MAX_PATH];
        mbstowcs(pwcsFile,pszFile,MAX_PATH);
        hr=StgCreateDocfile(pwcsFile, STGM_DIRECT | STGM_READWRITE
            | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);
#else
        hr=StgCreateDocfile(pszFile, STGM_DIRECT | STGM_READWRITE
            | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);
#endif
        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;

        //Tell the object to save into this new storage
        fSameAsLoad=FALSE;

        //Update our variable
        m_pIStorage->Release();
        m_pIStorage=pIStorage;
        }

    hr=m_pIPersistStorage->Save(m_pIStorage, fSameAsLoad);

    if (SUCCEEDED(hr))
        {
        hr=m_pIPersistStorage->SaveCompleted(fSameAsLoad
            ? NULL : m_pIStorage);
        }
    //End CHAPTER5MOD

    if (FAILED(hr))
        return DOCERR_WRITEFAILURE;

    //Saving makes us clean
    FDirtySet(FALSE);

    if (fRename)
        Rename(pszFile);

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::Undo
 *
 * Purpose:
 *  Reverses a previous action.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Undo(void)
    {
    m_pPL->Undo();
    return;
    }

/*
 * CCosmoDoc::FClip
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard, optionally implementing Cut by deleting the
 *  data in the current window after rendering.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *  fCut            BOOL indicating cut (TRUE) or copy (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::FClip(HWND hWndFrame, BOOL fCut)
    {
    BOOL            fRet=TRUE;
    HGLOBAL         hMem;
    UINT            i;

    //This array is so we can loop over the formats we provide.
    static UINT     rgcf[3]={0, CF_METAFILEPICT, CF_BITMAP};
    const UINT      cFormats=3;

    if (!OpenClipboard(hWndFrame))
        return FALSE;

    //Clean out whatever junk is in the clipboard.
    EmptyClipboard();

    rgcf[0]=m_cf;

    for (i=0; i < cFormats; i++)
        {
        //Copy private data first.
        hMem=RenderFormat(rgcf[i]);

        if (NULL!=hMem)
            SetClipboardData(rgcf[i], hMem);
        else
            fRet &=FALSE;
        }

    //Free clipboard ownership.
    CloseClipboard();

    //Delete our current data if copying succeeded.
    if (fRet && fCut)
        {
        m_pPL->New();
        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CCosmoDoc::RenderFormat
 *
 * Purpose:
 *  Renders a specific clipboard format into global memory.
 *
 * Parameters:
 *  cf              UINT format to render.
 *
 * Return Value:
 *  HGLOBAL         Global memory handle containing the data.
 */

HGLOBAL CCosmoDoc::RenderFormat(UINT cf)
    {
    HGLOBAL     hMem;

    if (cf==m_cf)
        {
        m_pPL->DataGetMem(&hMem);
        return hMem;
        }

    switch (cf)
        {
        case CF_METAFILEPICT:
            m_pPL->RenderMetafilePict(&hMem);
            return hMem;

        case CF_BITMAP:
            m_pPL->RenderBitmap((HBITMAP *)&hMem);
            return hMem;
        }

    return NULL;
    }

/*
 * CCosmoDoc::FQueryPaste
 *
 * Purpose:
 *  Determines if we can paste data from the clipboard.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CCosmoDoc::FQueryPaste(void)
    {
    return IsClipboardFormatAvailable(m_cf);
    }

/*
 * CCosmoDoc::FPaste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  to the current figure in the editor window.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::FPaste(HWND hWndFrame)
    {
    HGLOBAL         hMem;
    PPOLYLINEDATA   ppl;
    BOOL            fRet=FALSE;

    if (!OpenClipboard(hWndFrame))
        return FALSE;

    hMem=GetClipboardData(m_cf);

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);

        //TRUE in wParam to cause PLN_SIZECHANGE notification
        m_pPL->DataSet(ppl, FALSE, TRUE);
        GlobalUnlock(hMem);

        FDirtySet(TRUE);
        fRet=TRUE;
        }

    CloseClipboard();
    return fRet;
    }

/*
 * CCosmoDoc::ColorSet
 *
 * Purpose:
 *  Changes a color used in our contained Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color.
 *
 * Return Value:
 *  COLORREF        Previous color for the given index.
 */

COLORREF CCosmoDoc::ColorSet(UINT iColor, COLORREF cr)
    {
    COLORREF    crRet;

    m_pPL->ColorSet(iColor, cr, &crRet);
    return crRet;
    }

/*
 * CCosmoDoc::ColorGet
 *
 * Purpose:
 *  Retrieves a color currently in use in the Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to retrieve.
 *
 * Return Value:
 *  COLORREF        Current color for the given index.
 */

COLORREF CCosmoDoc::ColorGet(UINT iColor)
    {
    COLORREF    crRet;

    m_pPL->ColorGet(iColor, &crRet);
    return crRet;
    }

/*
 * CCosmoDoc::LineStyleSet
 *
 * Purpose:
 *  Changes the line style currently used in the Polyline
 *
 * Parameters:
 *  iStyle          UINT index of the new line style to use.
 *
 * Return Value:
 *  UINT            Previous line style.
 */

UINT CCosmoDoc::LineStyleSet(UINT iStyle)
    {
    UINT    i;

    m_pPL->LineStyleSet(iStyle, &i);
    return i;
    }

/*
 * CCosmoDoc::LineStyleGet
 *
 * Purpose:
 *  Retrieves the line style currently used in the Polyline
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Current line style.
 */

UINT CCosmoDoc::LineStyleGet(void)
    {
    UINT    i;

    m_pPL->LineStyleGet(&i);
    return i;
    }

/*
 * CPolylineAdviseSink::CPolylineAdviseSink
 *
 * Parameters:
 *  pv              LPVOID to store in this object
 */

CPolylineAdviseSink::CPolylineAdviseSink(LPVOID pv)
    {
    m_pv=pv;
    m_cRef=0;
    AddRef();
    return;
    }

/*
 * CPolylineAdviseSink::~CPolylineAdviseSink
 *
 * Purpose:
 *  Destructor.
 *
 * Parameters:
 *  None
 */

CPolylineAdviseSink::~CPolylineAdviseSink(void)
    {
    return;
    }

/*
 * CPolylineAdviseSink::QueryInterface
 * CPolylineAdviseSink::AddRef
 * CPolylineAdviseSink::Release
 *
 * Purpose:
 *  IUnknown members for this IPolylineAdviseSink implementations.
 */

STDMETHODIMP CPolylineAdviseSink::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    //CHAPTER5MOD
    //Any interface on this object is the object pointer.
    if (IID_IUnknown==riid || IID_IPolylineAdviseSink5==riid)
        *ppv=this;
    //End CHAPTER5MOD

    /*
     * If we actually assign an interface to ppv we need to AddRef
     * it since we're returning a new pointer.
     */
    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return S_FALSE;
    }

STDMETHODIMP_(ULONG) CPolylineAdviseSink::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPolylineAdviseSink::Release(void)
    {
    ULONG   cRefT;

    cRefT=--m_cRef;

    if (0L==m_cRef)
        delete this;

    return cRefT;
    }

/*
 * CPolylineAdviseSink::OnPointChange
 *
 * Purpose:
 *  Informs the document that the polyline added or removed a point.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CPolylineAdviseSink::OnPointChange(void)
    {
    PCDocument      pDoc=(PCDocument)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnSizeChange
 *
 * Purpose:
 *  Informs the document that the polyline changed size.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CPolylineAdviseSink::OnSizeChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;
    RECT            rc;
    DWORD           dwStyle;
    HWND            hWnd;

    /*
     * Polyline window is informing us that it changed size in
     * response to setting it's data.  Therefore we have to
     * size ourselves accordingly but without moving the screen
     * position of the polyline window.
     */

    pDoc->m_fNoSize=TRUE;

    //Set the document window size.
    pDoc->m_pPL->Window(&hWnd);
    GetWindowRect(hWnd, &rc);
    InflateRect(&rc, 8, 8);

    //Adjust for a window sans menu
    dwStyle=GetWindowLong(pDoc->m_hWnd, GWL_STYLE);
    AdjustWindowRect(&rc, dwStyle, FALSE);

    SetWindowPos(pDoc->m_hWnd, NULL, 0, 0, rc.right-rc.left
        , rc.bottom-rc.top, SWP_NOMOVE | SWP_NOZORDER);

    if (NULL!=pDoc->m_pAdv)
        pDoc->m_pAdv->OnSizeChange(pDoc, &rc);

    pDoc->m_fNoSize=FALSE;
    pDoc->FDirtySet(TRUE);

    return;
    }

/*
 * CPolylineAdviseSink::OnDataChange
 *
 * Purpose:
 *  Informs the document that the polyline data changed.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CPolylineAdviseSink::OnDataChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    if (NULL!=pDoc->m_pAdv)
        pDoc->m_pAdv->OnDataChange(pDoc);

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnColorChange
 *
 * Purpose:
 *  Informs the document that the polyline data changed a color.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CPolylineAdviseSink::OnColorChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnLineStyleChange
 *
 * Purpose:
 *  Informs the document that the polyline changed its line style.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CPolylineAdviseSink::OnLineStyleChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }


PRECOMP.CPP

#include "cocosmo.h"


RESOURCE.H

/*
 * RESOURCE.H
 * Component Cosmo Chapter 5
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Note that we augment classlib.h which we include before this.

//Position of Window menu.
#undef  WINDOW_MENU
#define WINDOW_MENU             4

//Total number of menus
#undef  CMENUS
#define CMENUS                  5

//Menu command identifiers.

#define IDM_FILEIMPORT          (IDM_CUSTOMFILEMIN)

#define IDM_COLORBACKGROUND     (IDM_CUSTOMMIN)
#define IDM_COLORLINE           (IDM_CUSTOMMIN+1)

#define IDM_LINEMIN             (IDM_CUSTOMMIN+100)
#define IDM_LINESOLID           (IDM_LINEMIN+0)         //PS_SOLID       0
#define IDM_LINEDASH            (IDM_LINEMIN+1)         //PS_DASH        1
#define IDM_LINEDOT             (IDM_LINEMIN+2)         //PS_DOT         2
#define IDM_LINEDASHDOT         (IDM_LINEMIN+3)         //PS_DASHDOT     3
#define IDM_LINEDASHDOTDOT      (IDM_LINEMIN+4)         //PS_DASHDOTDOT  4
#define IDM_LINEMAX             IDM_LINEDASHDOTDOT

/*
 * IDs for StatStrip handling of popup menus:  must be in order of menu
 * CLASSRES.H already defines ID_MENUFILE and ID_MENUEDIT for us.
 */

#undef  ID_MENUWINDOW
#undef  ID_MENUHELP

#define ID_MENUCOLOR            (ID_MENUCUSTOMMIN+0)
#define ID_MENULINE             (ID_MENUCUSTOMMIN+1)
#define ID_MENUWINDOW           (ID_MENUCUSTOMMIN+2)
#define ID_MENUHELP             (ID_MENUCUSTOMMIN+3)

//Stringtable IDs.  Keep sequential for each group.
#define IDS_FRAMEMIN            IDS_STANDARDFRAMEMIN
#define IDS_FILEIMPORT          (IDS_CUSTOMFRAMEMIN+0)
#define IDS_FRAMEMAX            (IDS_CUSTOMFRAMEMIN+0)

#define IDS_DOCUMENTMIN         IDS_STANDARDDOCMIN
#define IDS_NOPOLYLINE          (IDS_CUSTOMDOCMIN+0)
#define IDS_DOCUMENTMAX         (IDS_CUSTOMDOCMIN+0)

//These are specifically for the StatStrip
#define IDS_STATMESSAGEMIN                  IDS_STANDARDSTATMESSAGEMIN

#define IDS_ITEMMESSAGEFILEIMPORT           (IDS_CUSTOMSTATMESSAGEMIN+0)

#define IDS_MENUMESSAGECOLOR                (IDS_CUSTOMSTATMESSAGEMIN+1)
#define IDS_ITEMMESSAGECOLORBACKGROUND      (IDS_CUSTOMSTATMESSAGEMIN+2)
#define IDS_ITEMMESSAGECOLORLINE            (IDS_CUSTOMSTATMESSAGEMIN+3)

#define IDS_MENUMESSAGELINE                 (IDS_CUSTOMSTATMESSAGEMIN+4)
#define IDS_ITEMMESSAGELINESOLID            (IDS_CUSTOMSTATMESSAGEMIN+5)
#define IDS_ITEMMESSAGELINEDASH             (IDS_CUSTOMSTATMESSAGEMIN+6)
#define IDS_ITEMMESSAGELINEDOT              (IDS_CUSTOMSTATMESSAGEMIN+7)
#define IDS_ITEMMESSAGELINEDASHDOT          (IDS_CUSTOMSTATMESSAGEMIN+8)
#define IDS_ITEMMESSAGELINEDASHDOTDOT       (IDS_CUSTOMSTATMESSAGEMIN+9)

#define IDS_STATMESSAGEMAX                  (IDS_CUSTOMSTATMESSAGEMIN+9)

#endif //_RESOURCE_H_


DKOALA1.CPP

/*
 * DKOALA1.CPP
 * Koala Object DLL Server Chapter 5
 *
 * Example object structured in a DLL server.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "dkoala1.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

/*
 * LibMain(32)
 *
 * Purpose:
 *  Entry point conditionally compiled for Win32 and Win16.
 *  Provides the proper structure for each environment.
 */

#ifdef WIN32
BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    if (DLL_PROCESS_DETACH==ulReason)
        {
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    return TRUE;
    }
#else
int PASCAL LibMain(HINSTANCE hInstance, WORD wDataSeg
    , WORD cbHeapSize, LPSTR lpCmdLine)
    {
    if (0!=cbHeapSize)
        UnlockData(0);

    return (int)hInstance;
    }
#endif

/*
 * DllGetClassObject
 *
 * Purpose:
 *  Provides an IClassFactory for a given CLSID that this DLL is
 *  registered to support.  This DLL is placed under the CLSID
 *  in the registration database as the InProcServer.
 *
 * Parameters:
 *  clsID           REFCLSID that identifies the class factory
 *                  desired.  Since this parameter is passed this
 *                  DLL can handle any number of objects simply
 *                  by returning different class factories here
 *                  for different CLSIDs.
 *
 *  riid            REFIID specifying the interface the caller wants
 *                  on the class object, usually IID_ClassFactory.
 *
 *  ppv             PPVOID in which to return the interface
 *                  pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, otherwise an error code.
 */

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, PPVOID ppv)
    {
    HRESULT             hr;
    CKoalaClassFactory *pObj;

    if (CLSID_Koala!=rclsid)
        return ResultFromScode(E_FAIL);

    pObj=new CKoalaClassFactory();

    if (NULL==pObj)
        return ResultFromScode(E_OUTOFMEMORY);

    hr=pObj->QueryInterface(riid, ppv);

    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

/*
 * DllCanUnloadNow
 *
 * Purpose:
 *  Answers if the DLL can be freed, that is, if there are no
 *  references to anything this DLL provides.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if nothing is using us, FALSE otherwise.
 */

STDAPI DllCanUnloadNow(void)
    {
    SCODE   sc;

    //Our answer is whether there are any object or locks
    sc=(0L==g_cObj && 0L==g_cLock) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Koala object to call when it gets destroyed.
 *  Since we're in a DLL we only track the number of objects here,
 *  letting DllCanUnloadNow take care of the rest.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;
    return;
    }

/*
 * CKoalaClassFactory::CKoalaClassFactory
 * CKoalaClassFactory::~CKoalaClassFactory
 *
 * Constructor Parameters:
 *  None
 */

CKoalaClassFactory::CKoalaClassFactory(void)
    {
    m_cRef=0L;
    return;
    }

CKoalaClassFactory::~CKoalaClassFactory(void)
    {
    return;
    }

/*
 * CKoalaClassFactory::QueryInterface
 * CKoalaClassFactory::AddRef
 * CKoalaClassFactory::Release
 */

STDMETHODIMP CKoalaClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CKoalaClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CKoalaClassFactory::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    ObjectDestroyed();
    return 0L;
    }

/*
 * CKoalaClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a Koala object returning an interface pointer.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we are
 *                  being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_NOINTERFACE
 *                  if we cannot support the requested interface.
 */

STDMETHODIMP CKoalaClassFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCKoala             pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //Verify that a controlling unknown asks for IUnknown
    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    //Create the object passing function to notify on destruction.
    pObj=new CKoala(pUnkOuter, ObjectDestroyed);

    if (NULL==pObj)
        return hr;

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    //Kill the object if initial creation or Init failed.
    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

/*
 * CKoalaClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the DLL.  If the
 *  lock count goes to zero and there are no objects, the DLL
 *  is allowed to unload.  See DllCanUnloadNow.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CKoalaClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        g_cLock--;

    return NOERROR;
    }


DKOALA1.H

/*
 * DKOALA1.H
 * Koala Object DLL Server Chapter 5
 *
 * Definitions, classes, and prototypes for a DLL that
 * provides Koala objects to any other object user.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _DKOALA1_H_
#define _DKOALA1_H_

//Get the object definitions
#include "koala.h"

void ObjectDestroyed(void);

//DKOALA.CPP
//This class factory object creates Koala objects.

class CKoalaClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;

    public:
        CKoalaClassFactory(void);
        ~CKoalaClassFactory(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP         CreateInstance(LPUNKNOWN, REFIID
                                 , PPVOID);
        STDMETHODIMP         LockServer(BOOL);
    };

typedef CKoalaClassFactory *PCKoalaClassFactory;

#endif //_DKOALA1_H_


KOALA.CPP

/*
 * KOALA.CPP
 * Koala Object Independent of DLL/EXE Servers, Chapter 5
 *
 * Implementation of the CKoala object that works in either
 * an EXE or DLL as it only implements IUnknown.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "koala.h"

/*
 * CKoala::CKoala
 * CKoala::~CKoala
 *
 * Parameters (Constructor):
 *  pUnkOuter       LPUNKNOWN of a controlling unknown.
 *  pfnDestroy      PFNDESTROYED to call when an object
 *                  is destroyed.
 */

CKoala::CKoala(LPUNKNOWN pUnkOuter, PFNDESTROYED pfnDestroy)
    {
    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pfnDestroy=pfnDestroy;
    return;
    }

CKoala::~CKoala(void)
    {
    return;
    }

/*
 * CKoala::Init
 *
 * Purpose:
 *  Performs any intiailization of a CKoala that's prone to failure
 *  that we also use internally before exposing the object outside.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CKoala::Init(void)
    {
    //Nothing to do.
    return TRUE;
    }

/*
 * CKoala::QueryInterface
 * CKoala::AddRef
 * CKoala::Release
 *
 * Purpose:
 *  IUnknown members for CKoala object.
 */

STDMETHODIMP CKoala::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    /*
     * The only calls for IUnknown are either in a nonaggregated
     * case or when created in an aggregation, so in either case
     * always return our IUnknown for IID_IUnknown.
     */
    if (IID_IUnknown==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CKoala::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CKoala::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    /*
     * Tell the housing that an object is going away so it can
     * shut down if appropriate.
     */
    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0;
    }


KOALA.H

/*
 * KOALA.H
 * Koala Object Independent of DLL/EXE Servers, Chapter 5
 *
 * Classes that implement the Koala object independent of
 * whether we live in a DLL or EXE.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _KOALA_H_
#define _KOALA_H_

#define CHAPTER5
#include <inole.h>

//KOALA.CPP

class CKoala : public IUnknown
    {
    protected:
        ULONG           m_cRef;         //Object reference count
        LPUNKNOWN       m_pUnkOuter;    //Controlling unknown

        PFNDESTROYED    m_pfnDestroy;   //To call on closure

    public:
        CKoala(LPUNKNOWN, PFNDESTROYED);
        ~CKoala(void);

        BOOL Init(void);

        //Non-delegating object IUnknown
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
    };

typedef CKoala *PCKoala;

#endif //_KOALA_H_


DKOALA2.CPP

/*
 * DKOALA2.CPP
 * Koala Object DLL Self-Registering Server Chapter 5
 *
 * Example object structured in a DLL server.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "dkoala2.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

HINSTANCE   g_hInst=NULL;

/*
 * LibMain(32)
 *
 * Purpose:
 *  Entry point conditionally compiled for Win32 and Win16.
 *  Provides the proper structure for each environment.
 */

#ifdef WIN32
BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    if (DLL_PROCESS_DETACH==ulReason)
        {
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    g_hInst=hInstance;
    return TRUE;
    }
#else
int PASCAL LibMain(HINSTANCE hInstance, WORD wDataSeg
    , WORD cbHeapSize, LPSTR lpCmdLine)
    {
    if (0!=cbHeapSize)
        UnlockData(0);

    g_hInst=hInstance;
    return (int)hInstance;
    }
#endif

/*
 * DllGetClassObject
 *
 * Purpose:
 *  Provides an IClassFactory for a given CLSID that this DLL is
 *  registered to support.  This DLL is placed under the CLSID
 *  in the registration database as the InProcServer.
 *
 * Parameters:
 *  clsID           REFCLSID that identifies the class factory
 *                  desired.  Since this parameter is passed this
 *                  DLL can handle any number of objects simply
 *                  by returning different class factories here
 *                  for different CLSIDs.
 *
 *  riid            REFIID specifying the interface the caller wants
 *                  on the class object, usually IID_ClassFactory.
 *
 *  ppv             PPVOID in which to return the interface
 *                  pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, otherwise an error code.
 */

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, PPVOID ppv)
    {
    HRESULT             hr;
    CKoalaClassFactory *pObj;

    if (CLSID_Koala!=rclsid)
        return ResultFromScode(E_FAIL);

    pObj=new CKoalaClassFactory();

    if (NULL==pObj)
        return ResultFromScode(E_OUTOFMEMORY);

    hr=pObj->QueryInterface(riid, ppv);

    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

/*
 * DllCanUnloadNow
 *
 * Purpose:
 *  Answers if the DLL can be freed, that is, if there are no
 *  references to anything this DLL provides.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if nothing is using us, FALSE otherwise.
 */

STDAPI DllCanUnloadNow(void)
    {
    SCODE   sc;

    //Our answer is whether there are any object or locks
    sc=(0L==g_cObj && 0L==g_cLock) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * DllRegisterServer
 *
 * Purpose:
 *  Instructs the server to create its own registry entries
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR if registration successful, error
 *                  otherwise.
 */

STDAPI DllRegisterServer(void)
    {
    TCHAR       szID[128];
    TCHAR       szCLSID[128];
    TCHAR       szModule[512];

    //Create some base key strings.
    StringFromGUID2(CLSID_Koala, szID, 128);
    lstrcpy(szCLSID, TEXT("CLSID\\"));
    lstrcat(szCLSID, szID);

    //Create ProgID keys
    SetKeyAndValue(TEXT("Koala1.0"), NULL
        , TEXT("Koala Object Chapter 5"));
    SetKeyAndValue(TEXT("Koala1.0"), TEXT("CLSID"), szID);

    //Create VersionIndependentProgID keys
    SetKeyAndValue(TEXT("Koala"), NULL
        , TEXT("Koala Object Chapter 5"));
    SetKeyAndValue(TEXT("Koala"), TEXT("CurVer")
        , TEXT("Koala1.0"));
    SetKeyAndValue(TEXT("Koala"), TEXT("CLSID"), szID);

    //Create entries under CLSID
    SetKeyAndValue(szCLSID, NULL, TEXT("Koala Object Chapter 5"));
    SetKeyAndValue(szCLSID, TEXT("ProgID"), TEXT("Koala1.0"));
    SetKeyAndValue(szCLSID, TEXT("VersionIndependentProgID")
        , TEXT("Koala"));
    SetKeyAndValue(szCLSID, TEXT("NotInsertable"), NULL);

    GetModuleFileName(g_hInst, szModule
        , sizeof(szModule)/sizeof(TCHAR));

   #ifdef WIN32
    SetKeyAndValue(szCLSID, TEXT("InprocServer32"), szModule);
   #else
    SetKeyAndValue(szCLSID, "InprocServer", szModule);
   #endif

return NOERROR;
    }

/*
 * DllRegisterServer
 *
 * Purpose:
 *  Instructs the server to remove its own registry entries
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR if registration successful, error
 *                  otherwise.
 */

STDAPI DllUnregisterServer(void)
    {
    TCHAR       szID[128];
    TCHAR       szCLSID[128];
    TCHAR       szTemp[256];

    //Create some base key strings.
    StringFromGUID2(CLSID_Koala, szID, 128);
    lstrcpy(szCLSID, TEXT("CLSID\\"));
    lstrcat(szCLSID, szID);

    RegDeleteKey(HKEY_CLASSES_ROOT, TEXT("Koala\\CurVer"));
    RegDeleteKey(HKEY_CLASSES_ROOT, TEXT("Koala\\CLSID"));
    RegDeleteKey(HKEY_CLASSES_ROOT, TEXT("Koala"));

    RegDeleteKey(HKEY_CLASSES_ROOT, TEXT("Koala1.0\\CLSID"));
    RegDeleteKey(HKEY_CLASSES_ROOT, TEXT("Koala1.0"));

    wsprintf(szTemp, TEXT("%s\\%s"), szCLSID, TEXT("ProgID"));
    RegDeleteKey(HKEY_CLASSES_ROOT, szTemp);

    wsprintf(szTemp, TEXT("%s\\%s"), szCLSID, TEXT("VersionIndependentProgID"));
    RegDeleteKey(HKEY_CLASSES_ROOT, szTemp);

    wsprintf(szTemp, TEXT("%s\\%s"), szCLSID, TEXT("NotInsertable"));
    RegDeleteKey(HKEY_CLASSES_ROOT, szTemp);

   #ifdef WIN32
    wsprintf(szTemp, TEXT("%s\\%s"), szCLSID, TEXT("InprocServer32"));
   #else
    wsprintf(szTemp, "%s\\%s", szCLSID, "InprocServer");
   #endif
    RegDeleteKey(HKEY_CLASSES_ROOT, szTemp);

    RegDeleteKey(HKEY_CLASSES_ROOT, szCLSID);
    return NOERROR;
    }

/*
 * SetKeyAndValue
 *
 * Purpose:
 *  Private helper function for DllRegisterServer that creates
 *  a key, sets a value, and closes that key.
 *
 * Parameters:
 *  pszKey          LPTSTR to the ame of the key
 *  pszSubkey       LPTSTR ro the name of a subkey
 *  pszValue        LPTSTR to the value to store
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL SetKeyAndValue(LPTSTR pszKey, LPTSTR pszSubkey
    , LPTSTR pszValue)
    {
    HKEY        hKey;
    TCHAR       szKey[256];

    lstrcpy(szKey, pszKey);

    if (NULL!=pszSubkey)
        {
        lstrcat(szKey, TEXT("\\"));
        lstrcat(szKey, pszSubkey);
        }

    if (ERROR_SUCCESS!=RegCreateKeyEx(HKEY_CLASSES_ROOT
        , szKey, 0, NULL, REG_OPTION_NON_VOLATILE
        , KEY_ALL_ACCESS, NULL, &hKey, NULL))
        return FALSE;

    if (NULL!=pszValue)
        {
        RegSetValueEx(hKey, NULL, 0, REG_SZ, (BYTE *)pszValue
            , (lstrlen(pszValue)+1)*sizeof(TCHAR));
        }

    RegCloseKey(hKey);
    return TRUE;
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Koala object to call when it gets destroyed.
 *  Since we're in a DLL we only track the number of objects here,
 *  letting DllCanUnloadNow take care of the rest.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;
    return;
    }

/*
 * CKoalaClassFactory::CKoalaClassFactory
 * CKoalaClassFactory::~CKoalaClassFactory
 *
 * Constructor Parameters:
 *  None
 */

CKoalaClassFactory::CKoalaClassFactory(void)
    {
    m_cRef=0L;
    return;
    }

CKoalaClassFactory::~CKoalaClassFactory(void)
    {
    return;
    }

/*
 * CKoalaClassFactory::QueryInterface
 * CKoalaClassFactory::AddRef
 * CKoalaClassFactory::Release
 */

STDMETHODIMP CKoalaClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CKoalaClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CKoalaClassFactory::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    ObjectDestroyed();
    return 0L;
    }

/*
 * CKoalaClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a Koala object returning an interface pointer.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we are
 *                  being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_NOINTERFACE
 *                  if we cannot support the requested interface.
 */

STDMETHODIMP CKoalaClassFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCKoala             pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //Verify that a controlling unknown asks for IUnknown
    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    //Create the object passing function to notify on destruction.
    pObj=new CKoala(pUnkOuter, ObjectDestroyed);

    if (NULL==pObj)
        return hr;

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    //Kill the object if initial creation or Init failed.
    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

/*
 * CKoalaClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the DLL.  If the
 *  lock count goes to zero and there are no objects, the DLL
 *  is allowed to unload.  See DllCanUnloadNow.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CKoalaClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        g_cLock--;

    return NOERROR;
    }


DKOALA2.H

/*
 * DKOALA2.H
 * Koala Object DLL Self-Registering Server Chapter 5
 *
 * Definitions, classes, and prototypes for a DLL that
 * provides Koala objects to any other object user.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _DKOALA2_H_
#define _DKOALA2_H_

//Get the object definitions
#include "koala.h"

STDAPI DllRegisterServer(void);
STDAPI DllUnregisterServer(void);
BOOL   SetKeyAndValue(LPTSTR, LPTSTR, LPTSTR);

void   ObjectDestroyed(void);

//DKOALA.CPP
//This class factory object creates Koala objects.

class CKoalaClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;

    public:
        CKoalaClassFactory(void);
        ~CKoalaClassFactory(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP         CreateInstance(LPUNKNOWN, REFIID
                                 , PPVOID);
        STDMETHODIMP         LockServer(BOOL);
    };

typedef CKoalaClassFactory *PCKoalaClassFactory;

#endif //_DKOALA2_H_


DKOALA3.CPP

/*
 * DKOALA3.CPP
 * Koala Object DLL Licensed Server Chapter 5
 *
 * Example object structured in a DLL server.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "dkoala3.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

HINSTANCE   g_hInst=NULL;

//License key string, stored in ANSI to match contents of LIC file
char    g_szLic[]="Koala Object #3 Copyright (c)1993-1995 Microsoft Corp.";
BOOL    g_fMachineLicensed=FALSE;

/*
 * LibMain(32)
 *
 * Purpose:
 *  Entry point conditionally compiled for Win32 and Win16.
 *  Provides the proper structure for each environment.
 */

#ifdef WIN32
BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    if (DLL_PROCESS_DETACH==ulReason)
        {
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    g_fMachineLicensed=CheckForLicenseFile(hInstance
        , TEXT("DKOALA3.LIC"), (BYTE *)g_szLic, lstrlenA(g_szLic));

    g_hInst=hInstance;
    return TRUE;
    }
#else
int PASCAL LibMain(HINSTANCE hInstance, WORD wDataSeg
    , WORD cbHeapSize, LPSTR lpCmdLine)
    {
    if (0!=cbHeapSize)
        UnlockData(0);

    g_fMachineLicensed=CheckForLicenseFile(hInstance
        , TEXT("DKOALA3.LIC"), (BYTE *)g_szLic, lstrlen(g_szLic));

    g_hInst=hInstance;
    return (int)hInstance;
    }
#endif

/*
 * CheckForLicenseFile
 *
 * Purpose:
 *  Attempts to load DKOALA3.LIC from the same directory as
 *  this DLL, attempting to match the first part of that file
 *  with our license string (typical licensing scheme).  If
 *  the match is successful, then this DLL can create any
 *  instances of Koala objects without additional trouble.
 *  Otherwise the client has to call IClassFactory2::CreateInstance-
 *  Lic to instantiate anything.
 *
 * Parameters:
 *  hInst           HINSTANCE of the module describing the
 *                  directory in which we expect to find the
 *                  license file.
 *  pszFile         LPTSTR to the name of the file to look for.
 *  pbLic           LPBYTE to the expected contents of the file.
 *  cb              UINT number of butes to compare.
 *
 * Return Value:
 *  BOOL            TRUE if the file is available, FALSE otherwise.
 */

BOOL CheckForLicenseFile(HINSTANCE hInst, LPTSTR pszFile
    , LPBYTE pbLic, UINT cb)
    {
    BOOL        fFound=FALSE;
    TCHAR       szPath[_MAX_PATH];
    LPTSTR      pszTemp;
    LPBYTE      pbCompare;
   #ifdef WIN32
    HANDLE      hFile;
   #else
    OFSTRUCT    of;
    HFILE       hFile;
   #endif
    UINT        cbRead;
    ULONG       cbWasRead;

    //Get the module path, then replace DLL name with LIC filename
GetModuleFileName(hInst, szPath, _MAX_PATH);
pszTemp=_tcsrchr(szPath, '\\')+1;
    lstrcpy(pszTemp, pszFile);

    /*
     * Now open the file and read contents into an allocated
     * pbCompare.  The check if the contents of that file and
     * pbLic match.  If so, then return success, otherwise
     * failure.
     */
   #ifdef WIN32
    hFile=CreateFile(szPath, GENERIC_READ, FILE_SHARE_READ
        , NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
   #else
    hFile=OpenFile(szPath, &of, OF_READ);
   #endif

    /*
     * NOTE:  INVALID_HANDLE_VALUE, ReadFile, and CloseHandle
     * are macros in INC\book1632.h when compiling for Win16.
     */
    if (INVALID_HANDLE_VALUE==hFile)
        return FALSE;

    cbRead=cb*sizeof(BYTE);
    pbCompare=(LPBYTE)malloc(cbRead+4);

    if (NULL!=pbCompare)
        {
        ReadFile(hFile, pbCompare, cbRead, &cbWasRead, NULL);
    fFound=(0==memcmp(pbLic, pbCompare, cb));
    free(pbCompare);
        }

    CloseHandle(hFile);
    return fFound;
    }

/*
 * DllGetClassObject
 *
 * Purpose:
 *  Provides an IClassFactory for a given CLSID that this DLL is
 *  registered to support.  This DLL is placed under the CLSID
 *  in the registration database as the InProcServer.
 *
 * Parameters:
 *  clsID           REFCLSID that identifies the class factory
 *                  desired.  Since this parameter is passed this
 *                  DLL can handle any number of objects simply
 *                  by returning different class factories here
 *                  for different CLSIDs.
 *
 *  riid            REFIID specifying the interface the caller wants
 *                  on the class object, usually IID_ClassFactory.
 *
 *  ppv             PPVOID in which to return the interface
 *                  pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, otherwise an error code.
 */

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, PPVOID ppv)
    {
    HRESULT             hr;
    CKoalaClassFactory *pObj;

    if (CLSID_Koala!=rclsid)
        return ResultFromScode(E_FAIL);

    pObj=new CKoalaClassFactory();

    if (NULL==pObj)
        return ResultFromScode(E_OUTOFMEMORY);

    hr=pObj->QueryInterface(riid, ppv);

    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

/*
 * DllCanUnloadNow
 *
 * Purpose:
 *  Answers if the DLL can be freed, that is, if there are no
 *  references to anything this DLL provides.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if nothing is using us, FALSE otherwise.
 */

STDAPI DllCanUnloadNow(void)
    {
    SCODE   sc;

    //Our answer is whether there are any object or locks
    sc=(0L==g_cObj && 0L==g_cLock) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Koala object to call when it gets destroyed.
 *  Since we're in a DLL we only track the number of objects here,
 *  letting DllCanUnloadNow take care of the rest.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;
    return;
    }

/*
 * CKoalaClassFactory::CKoalaClassFactory
 * CKoalaClassFactory::~CKoalaClassFactory
 *
 * Constructor Parameters:
 *  None
 */

CKoalaClassFactory::CKoalaClassFactory(void)
    {
    m_cRef=0L;
    return;
    }

CKoalaClassFactory::~CKoalaClassFactory(void)
    {
    return;
    }

/*
 * CKoalaClassFactory::QueryInterface
 * CKoalaClassFactory::AddRef
 * CKoalaClassFactory::Release
 */

STDMETHODIMP CKoalaClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid
        || IID_IClassFactory2==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CKoalaClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CKoalaClassFactory::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    ObjectDestroyed();
    return 0L;
    }

/*
 * CKoalaClassFactory::CreateAnObject
 * (Private)
 *
 * Purpose:
 *  Central function to create instances of objects that is called
 *  from CreateInstance and CreateInstanceLic.  This takes the same
 *  parameters as CreateInstance below.
 */

HRESULT CKoalaClassFactory::CreateAnObject(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCKoala             pObj;
    HRESULT             hr;

    //Verify that a controlling unknown asks for IUnknown
    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    hr=ResultFromScode(E_OUTOFMEMORY);

    //Create the object passing function to notify on destruction.
    pObj=new CKoala(pUnkOuter, ObjectDestroyed);

    if (NULL==pObj)
        return hr;

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    //Kill the object if initial creation or Init failed.
    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

/*
 * CKoalaClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a Koala object returning an interface pointer.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we are
 *                  being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_NOINTERFACE
 *                  if we cannot support the requested interface.
 */

STDMETHODIMP CKoalaClassFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    *ppvObj=NULL;

    /*
     * The license file *must* be around for this simple
     * CreateInstance to work.  One we've checked, call the central
     * creation function.
     */
    if (!g_fMachineLicensed)
        return ResultFromScode(CLASS_E_NOTLICENSED);

    return CreateAnObject(pUnkOuter, riid, ppvObj);
    }

/*
 * CKoalaClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the DLL.  If the
 *  lock count goes to zero and there are no objects, the DLL
 *  is allowed to unload.  See DllCanUnloadNow.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CKoalaClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        g_cLock--;

    return NOERROR;
    }

/*
 * CKoalaClassFactory::GetLicInfo
 *
 * Purpose:
 *  Fills a LICINFO structure with license information for
 *  this class factory.
 *
 * Parameters:
 *  pLicInfo        LPLICINFO to the structure to fill
 */

STDMETHODIMP CKoalaClassFactory::GetLicInfo(LPLICINFO pLicInfo)
    {
    if (NULL==pLicInfo)
        return ResultFromScode(E_POINTER);

    pLicInfo->cbLicInfo=sizeof(LICINFO);

    //This says whether RequestLicKey will work
    pLicInfo->fRuntimeKeyAvail=g_fMachineLicensed;

    //This says whether the standard CreateInstance will work
    pLicInfo->fLicVerified=g_fMachineLicensed;

    return NOERROR;
    }

/*
 * CKoalaClassFactory::RequestLicKey
 *
 * Purpose:
 *  Retrieves a license key from this class factory for use with
 *  CreateInstanceLic.
 *
 * Parameters:
 *  dwReserved      DWORD reserved for future use with multiple
 *                  licenses.
 *  pbstrKey        BSTR * in which to return the key.
 */

STDMETHODIMP CKoalaClassFactory::RequestLicKey(DWORD dwReserved
    , BSTR *pbstrKey)
    {
    OLECHAR     szTemp[256];

    //Can't give away a key on an unlicensed machine
    if (!g_fMachineLicensed)
        return ResultFromScode(CLASS_E_NOTLICENSED);

   #ifndef WIN32
    lstrcpy(g_szLic, szTemp);
   #else
    mbstowcs(szTemp, g_szLic, sizeof(g_szLic));
   #endif
    *pbstrKey=SysAllocString(szTemp);
    return (NULL!=*pbstrKey) ? NOERROR : ResultFromScode(E_OUTOFMEMORY);
    }

/*
 * CKoalaClassFactory::CreateInstanceLic
 *
 * Purpose:
 *  Creates and instance of the object given a license key.
 *  Same as CreateInstance, and implementations of this function
 *  will typically just validate the key and call CreateInstance.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we are
 *                  being used in an aggregation.
 *  pUnkReserved    LPUNKNOWN reserved.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  bstrKey         BSTR key used to validate creation.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 */

STDMETHODIMP CKoalaClassFactory::CreateInstanceLic(LPUNKNOWN pUnkOuter
    , LPUNKNOWN pUnkReserved, REFIID riid, BSTR bstrKey
    , PPVOID ppvObj)
    {
    BOOL        fMatch;
    BSTR        bstrTemp;
    UINT        cch;
    OLECHAR     szLic[256];
   #ifdef WIN32ANSI
    char        szTemp[256];
   #endif

    *ppvObj=NULL;

    /*
     * Get our own license key that should match bstrKey exactly.
     * This code is coped from RequestLicKey.
     */
   #ifndef WIN32
    lstrcpy(g_szLic, szLic);
   #else
    mbstowcs(szLic, g_szLic, sizeof(g_szLic));
   #endif
    bstrTemp=SysAllocString(szLic);

    if (NULL==bstrTemp)
        return ResultFromScode(E_OUTOFMEMORY);

   #ifdef WIN32ANSI
    WideCharToMultiByte(CP_ACP, 0, bstrTemp, -1, szTemp
        , 256, NULL, NULL);
    cch=lstrlen(szTemp);
   #else
    cch=lstrlen(bstrTemp);
   #endif
    fMatch=(0==memcmp(bstrTemp, bstrKey, cch*sizeof(OLECHAR)));
    SysFreeString(bstrTemp);

    if (!fMatch)
        return ResultFromScode(CLASS_E_NOTLICENSED);

    return CreateAnObject(pUnkOuter, riid, ppvObj);
    }


DKOALA3.H

/*
 * DKOALA3.H
 * Koala Object DLL Licensed Server Chapter 5
 *
 * Definitions, classes, and prototypes for a DLL that
 * provides Koala objects to any other object user.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _DKOALA3_H_
#define _DKOALA3_H_

//Get the object definitions
#define INC_CONTROLS
#include "koala.h"

BOOL CheckForLicenseFile(HINSTANCE, LPTSTR, LPBYTE, UINT);

void   ObjectDestroyed(void);

//DKOALA.CPP
//This class factory object creates Koala objects.

class CKoalaClassFactory : public IClassFactory2
    {
    protected:
        ULONG           m_cRef;

    private:
        HRESULT CreateAnObject(LPUNKNOWN, REFIID, PPVOID);

    public:
        CKoalaClassFactory(void);
        ~CKoalaClassFactory(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP         CreateInstance(LPUNKNOWN, REFIID
                                 , PPVOID);
        STDMETHODIMP         LockServer(BOOL);

        //IClassFactory2 members
        STDMETHODIMP         GetLicInfo(LPLICINFO);
    STDMETHODIMP         RequestLicKey(DWORD, BSTR *);
    STDMETHODIMP         CreateInstanceLic(LPUNKNOWN
                                , LPUNKNOWN, REFIID, BSTR, PPVOID);
    };

typedef CKoalaClassFactory *PCKoalaClassFactory;

#endif //_DKOALA3_H_


EKOALA1.CPP

/*
 * EKOALA1.CPP
 * Koala Object EXE Server Chapter 5
 *
 * Example object implemented in an EXE.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "ekoala1.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

//Make window handle global so other code can cause a shutdown
HWND        g_hWnd=NULL;

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    MSG         msg;
    PAPP        pApp;

    SETMESSAGEQUEUE;

    pApp=new CApp(hInst, hInstPrev, pszCmdLine, nCmdShow);

    if (NULL==pApp)
        return -1;

    if (pApp->Init())
        {
        while (GetMessage(&msg, NULL, 0,0 ))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    delete pApp;
    return msg.wParam;
    }

/*
 * KoalaWndProc
 *
 * Purpose:
 *  Standard window class procedure.
 */

LRESULT APIENTRY KoalaWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PAPP        pApp;

    pApp=(PAPP)GetWindowLong(hWnd, KOALAWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pApp=(PAPP)(((LPCREATESTRUCT)lParam)->lpCreateParams);
            SetWindowLong(hWnd, KOALAWL_STRUCTURE, (LONG)pApp);
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Koala object to call when it gets destroyed.
 *  We destroy the main window if the proper conditions are met
 *  for shutdown.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;

    //No more objects and no locks, shut the app down.
    if (0L==g_cObj && 0L==g_cLock && IsWindow(g_hWnd))
        PostMessage(g_hWnd, WM_CLOSE, 0, 0L);

    return;
    }

/*
 * CApp::CApp
 * CApp::~CApp
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the Application from WinMain
 *  hInstPrev       HINSTANCE of a previous instance from WinMain
 *  pszCmdLine      LPSTR of the command line.
 *  nCmdShow        UINT specifying how to show the app window,
 *                  from WinMain.
 */

CApp::CApp(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, UINT nCmdShow)
    {
    //Initialize WinMain parameter holders.
    m_hInst     =hInst;
    m_hInstPrev =hInstPrev;
    m_pszCmdLine=pszCmdLine;
    m_nCmdShow  =nCmdShow;

    m_hWnd=NULL;
    m_dwRegCO=0;
    m_pIClassFactory=NULL;
    m_fInitialized=FALSE;
    return;
    }

CApp::~CApp(void)
    {
    //Opposite of CoRegisterClassObject; class factory ref is now 1
    if (0L!=m_dwRegCO)
        CoRevokeClassObject(m_dwRegCO);

    //The last Release, which frees the class factory.
    if (NULL!=m_pIClassFactory)
        m_pIClassFactory->Release();

    if (m_fInitialized)
        CoUninitialize();

    return;
    }

/*
 * CApp::Init
 *
 * Purpose:
 *  Initializes an CApp object by registering window classes,
 *  creating the main window, and doing anything else prone to
 *  failure.  If this function fails the caller should guarantee
 *  that the destructor is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::Init(void)
    {
    WNDCLASS    wc;
    HRESULT     hr;
   #ifndef WIN32
    DWORD       dwVer;
   #endif

    //Fail if we're run outside of CoGetClassObject
    if (lstrcmpiA(m_pszCmdLine, "-Embedding")
        && lstrcmpiA(m_pszCmdLine, "/Embedding"))
        return FALSE;

   #ifndef WIN32
    dwVer=CoBuildVersion();

    if (rmm!=HIWORD(dwVer))
        return FALSE;

    //No need to check minor versions.
   #endif

    //Call CoInitialize so we can call other Co* functions
    if (FAILED(CoInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    if (!m_hInstPrev)
        {
        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = KoalaWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = CBWNDEXTRA;
        wc.hInstance      = m_hInst;
        wc.hIcon          = NULL;
        wc.hCursor        = NULL;
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName   = NULL;
        wc.lpszClassName  = TEXT("Koala");

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

    m_hWnd=CreateWindow(TEXT("Koala"), TEXT("Koala")
        , WS_OVERLAPPEDWINDOW, 35, 35, 350, 250
        , NULL, NULL, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    g_hWnd=m_hWnd;

    /*
     * Create our class factory and register it for this application
     * using CoRegisterClassObject. We are able to service more than
     * one object at a time so we use REGCLS_MULTIPLEUSE.
     */
    m_pIClassFactory=new CKoalaClassFactory();

    if (NULL==m_pIClassFactory)
        return FALSE;

    //Since we hold on to this, we should AddRef it.
    m_pIClassFactory->AddRef();

    hr=CoRegisterClassObject(CLSID_Koala, m_pIClassFactory
        , CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &m_dwRegCO);

    if (FAILED(hr))
        return FALSE;

    return TRUE;
    }

/*
 * CKoalaClassFactory::CKoalaClassFactory
 * CKoalaClassFactory::~CKoalaClassFactory
 *
 * Constructor Parameters:
 *  None
 */

CKoalaClassFactory::CKoalaClassFactory(void)
    {
    m_cRef=0L;
    return;
    }

CKoalaClassFactory::~CKoalaClassFactory(void)
    {
    return;
    }

/*
 * CKoalaClassFactory::QueryInterface
 * CKoalaClassFactory::AddRef
 * CKoalaClassFactory::Release
 */

STDMETHODIMP CKoalaClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CKoalaClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CKoalaClassFactory::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CKoalaClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a Koala object returning an interface pointer.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we are
 *                  being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_NOINTERFACE
 *                  if we cannot support the requested interface.
 */

STDMETHODIMP CKoalaClassFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCKoala             pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //Verify that a controlling unknown asks for IUnknown
    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    //Create the object telling us to notify us when it's gone.
    pObj=new CKoala(pUnkOuter, ObjectDestroyed);

    if (NULL==pObj)
        {
        //This starts shutdown if there are no other objects.
        g_cObj++;
        ObjectDestroyed();
        return hr;
        }

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    g_cObj++;

    /*
     * Kill the object if initial creation or Init failed. If
     * the object failed, we handle the g_cObj increment above
     * in ObjectDestroyed.
     */
    if (FAILED(hr))
        {
        delete pObj;
        ObjectDestroyed();  //Handle shutdown cases.
        }

    return hr;
    }

/*
 * CKoalaClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the serving
 *  IClassFactory object.  When the number of locks goes to
 *  zero and the number of objects is zero, we shut down the
 *  application.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CKoalaClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        {
        g_cLock--;

        /*
         * Fake an object destruction:  this centralizes
         * all the shutdown code in the ObjectDestroyed
         * function, eliminating duplicate code here.
         */
        g_cObj++;
        ObjectDestroyed();
        }

    return NOERROR;
    }


EKOALA1.H

/*
 * EKOALA1.H
 * Koala Object EXE Server Chapter 5
 *
 * Definitions, classes, and prototypes for an application that
 * provides Koala objects to any other object user.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _EKOALA1_H_
#define _EKOALA1_H_

//Get the object definitions
#include "koala.h"

//EKOALA1.CPP
LRESULT APIENTRY KoalaWndProc(HWND, UINT, WPARAM, LPARAM);

class CApp
    {
    friend LRESULT APIENTRY KoalaWndProc(HWND, UINT, WPARAM, LPARAM);

    protected:
        HINSTANCE       m_hInst;            //WinMain parameters
        HINSTANCE       m_hInstPrev;
        LPSTR           m_pszCmdLine;
        UINT            m_nCmdShow;

        HWND            m_hWnd;             //Main window handle

        BOOL            m_fInitialized;     //Did CoInitialize work?
        LPCLASSFACTORY  m_pIClassFactory;   //Our class factory
        DWORD           m_dwRegCO;          //Registration key

    public:
        CApp(HINSTANCE, HINSTANCE, LPSTR, UINT);
        ~CApp(void);
        BOOL Init(void);
    };

typedef CApp *PAPP;

#define CBWNDEXTRA              sizeof(PAPP)
#define KOALAWL_STRUCTURE       0

void ObjectDestroyed(void);

//This class factory object creates Koala objects.

class CKoalaClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;

    public:
        CKoalaClassFactory(void);
        ~CKoalaClassFactory(void);

        //IUnknown members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP CreateInstance(LPUNKNOWN, REFIID, PPVOID);
        STDMETHODIMP LockServer(BOOL);
    };

typedef CKoalaClassFactory *PCKoalaClassFactory;

#endif //_EKOALA1_H_


EKOALA2.CPP

/*
 * EKOALA2.CPP
 * Koala Object EXE Self-Registering Server Chapter 5
 *
 * Example object implemented in an EXE.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "ekoala2.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

//Make window handle global so other code can cause a shutdown
HWND        g_hWnd=NULL;

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    MSG         msg;
    PAPP        pApp;

    SETMESSAGEQUEUE;

    pApp=new CApp(hInst, hInstPrev, pszCmdLine, nCmdShow);

    if (NULL==pApp)
        return -1;

    if (pApp->Init())
        {
        while (GetMessage(&msg, NULL, 0,0 ))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    delete pApp;
    return msg.wParam;
    }

/*
 * KoalaWndProc
 *
 * Purpose:
 *  Standard window class procedure.
 */

LRESULT APIENTRY KoalaWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PAPP        pApp;

    pApp=(PAPP)GetWindowLong(hWnd, KOALAWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pApp=(PAPP)(((LPCREATESTRUCT)lParam)->lpCreateParams);
            SetWindowLong(hWnd, KOALAWL_STRUCTURE, (LONG)pApp);
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Koala object to call when it gets destroyed.
 *  We destroy the main window if the proper conditions are met
 *  for shutdown.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;

    //No more objects and no locks, shut the app down.
    if (0L==g_cObj && 0L==g_cLock && IsWindow(g_hWnd))
        PostMessage(g_hWnd, WM_CLOSE, 0, 0L);

    return;
    }

/*
 * CApp::CApp
 * CApp::~CApp
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the Application from WinMain
 *  hInstPrev       HINSTANCE of a previous instance from WinMain
 *  pszCmdLine      LPSTR of the command line.
 *  nCmdShow        UINT specifying how to show the app window,
 *                  from WinMain.
 */

CApp::CApp(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, UINT nCmdShow)
    {
    //Initialize WinMain parameter holders.
    m_hInst     =hInst;
    m_hInstPrev =hInstPrev;
    m_pszCmdLine=pszCmdLine;
    m_nCmdShow  =nCmdShow;

    m_hWnd=NULL;
    m_dwRegCO=0;
    m_pIClassFactory=NULL;
    m_fInitialized=FALSE;
    return;
    }

CApp::~CApp(void)
    {
    //Opposite of CoRegisterClassObject; class factory ref is now 1
    if (0L!=m_dwRegCO)
        CoRevokeClassObject(m_dwRegCO);

    //The last Release, which frees the class factory.
    if (NULL!=m_pIClassFactory)
        m_pIClassFactory->Release();

    if (m_fInitialized)
        CoUninitialize();

    return;
    }

/*
 * CApp::Init
 *
 * Purpose:
 *  Initializes an CApp object by registering window classes,
 *  creating the main window, and doing anything else prone to
 *  failure.  If this function fails the caller should guarantee
 *  that the destructor is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::Init(void)
    {
    WNDCLASS    wc;
    HRESULT     hr;
   #ifndef WIN32
    DWORD       dwVer;
   #endif

    /*
     * Check if we're being run for self-registration.  If so,
     * do the job, then quit by returning FALSE from here.
     */
    if (0==lstrcmpiA(m_pszCmdLine, "-RegServer")
        || 0==lstrcmpiA(m_pszCmdLine, "/RegServer"))
        {
        RegisterServer();
        return FALSE;
        }

    if (0==lstrcmpiA(m_pszCmdLine, "-UnregServer")
        || 0==lstrcmpiA(m_pszCmdLine, "/UnregServer"))
        {
        UnregisterServer();
        return FALSE;
        }

    //Fail if we're run outside of CoGetClassObject
    if (lstrcmpiA(m_pszCmdLine, "-Embedding")
        && lstrcmpiA(m_pszCmdLine, "/Embedding"))
        return FALSE;

   #ifndef WIN32
    dwVer=CoBuildVersion();

    if (rmm!=HIWORD(dwVer))
        return FALSE;

    //No need to check minor versions.
   #endif

    //Call CoInitialize so we can call other Co* functions
    if (FAILED(CoInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    if (!m_hInstPrev)
        {
        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = KoalaWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = CBWNDEXTRA;
        wc.hInstance      = m_hInst;
        wc.hIcon          = NULL;
        wc.hCursor        = NULL;
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName   = NULL;
        wc.lpszClassName  = TEXT("Koala");

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

    m_hWnd=CreateWindow(TEXT("Koala"), TEXT("Koala")
        , WS_OVERLAPPEDWINDOW, 35, 35, 350, 250
        , NULL, NULL, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    g_hWnd=m_hWnd;

    /*
     * Create our class factory and register it for this application
     * using CoRegisterClassObject. We are able to service more than
     * one object at a time so we use REGCLS_MULTIPLEUSE.
     */
    m_pIClassFactory=new CKoalaClassFactory();

    if (NULL==m_pIClassFactory)
        return FALSE;

    //Since we hold on to this, we should AddRef it.
    m_pIClassFactory->AddRef();

    hr=CoRegisterClassObject(CLSID_Koala, m_pIClassFactory
        , CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &m_dwRegCO);

    if (FAILED(hr))
        return FALSE;

    return TRUE;
    }

/*
 * CApp::RegisterServer
 *
 * Purpose:
 *  Creates registry enties for this server.
 */

void CApp::RegisterServer(void)
    {
    TCHAR       szID[128];
    TCHAR       szCLSID[128];
    TCHAR       szModule[512];

    //Create some bas key strings.
    StringFromGUID2(CLSID_Koala, szID, 128);
    lstrcpy(szCLSID, TEXT("CLSID\\"));
    lstrcat(szCLSID, szID);

    //Create ProgID keys
    SetKeyAndValue(TEXT("Koala1.0"), NULL
        , TEXT("Koala Object Chapter 5"));
    SetKeyAndValue(TEXT("Koala1.0"), TEXT("CLSID"), szID);

    //Create VersionIndependentProgID keys
    SetKeyAndValue(TEXT("Koala"), NULL
        , TEXT("Koala Object Chapter 5"));
    SetKeyAndValue(TEXT("Koala"), TEXT("CurVer")
        , TEXT("Koala1.0"));
    SetKeyAndValue(TEXT("Koala"), TEXT("CLSID"), szID);

    //Create entries under CLSID
    SetKeyAndValue(szCLSID, NULL, TEXT("Koala Object Chapter 5"));
    SetKeyAndValue(szCLSID, TEXT("ProgID"), TEXT("Koala1.0"));
    SetKeyAndValue(szCLSID, TEXT("VersionIndependentProgID")
        , TEXT("Koala"));
    SetKeyAndValue(szCLSID, TEXT("NotInsertable"), NULL);

    GetModuleFileName(m_hInst, szModule
        , sizeof(szModule)/sizeof(TCHAR));

   #ifdef WIN32
    SetKeyAndValue(szCLSID, TEXT("LocalServer32"), szModule);
   #else
    SetKeyAndValue(szCLSID, TEXT("LocalServer"), szModule);
   #endif

return;
    }

/*
 * CApp::RegisterServer
 *
 * Purpose:
 *  Removes registry entries for this server
 */

void CApp::UnregisterServer(void)
    {
    TCHAR       szID[128];
    TCHAR       szCLSID[128];
    TCHAR       szTemp[256];

    //Create some base key strings.
    StringFromGUID2(CLSID_Koala, szID, 128);
    lstrcpy(szCLSID, TEXT("CLSID\\"));
    lstrcat(szCLSID, szID);

    RegDeleteKey(HKEY_CLASSES_ROOT, TEXT("Koala\\CurVer"));
    RegDeleteKey(HKEY_CLASSES_ROOT, TEXT("Koala\\CLSID"));
    RegDeleteKey(HKEY_CLASSES_ROOT, TEXT("Koala"));

    RegDeleteKey(HKEY_CLASSES_ROOT, TEXT("Koala1.0\\CLSID"));
    RegDeleteKey(HKEY_CLASSES_ROOT, TEXT("Koala1.0"));

    wsprintf(szTemp, TEXT("%s\\%s"), szCLSID, TEXT("ProgID"));
    RegDeleteKey(HKEY_CLASSES_ROOT, szTemp);

    wsprintf(szTemp, TEXT("%s\\%s"), szCLSID, TEXT("VersionIndependentProgID"));
    RegDeleteKey(HKEY_CLASSES_ROOT, szTemp);

    wsprintf(szTemp, TEXT("%s\\%s"), szCLSID, TEXT("NotInsertable"));
    RegDeleteKey(HKEY_CLASSES_ROOT, szTemp);

   #ifdef WIN32
    wsprintf(szTemp, TEXT("%s\\%s"), szCLSID, TEXT("LocalServer32"));
   #else
    wsprintf(szTemp, "%s\\%s", szCLSID, "LocalServer");
   #endif
    RegDeleteKey(HKEY_CLASSES_ROOT, szTemp);

    RegDeleteKey(HKEY_CLASSES_ROOT, szCLSID);
    return;
    }

/*
 * CApp::SetKeyAndValue
 *
 * Purpose:
 *  Private helper function for RegisterServer that creates
 *  a key, sets a value, and closes that key.
 *
 * Parameters:
 *  pszKey          LPTSTR to the ame of the key
 *  pszSubkey       LPTSTR ro the name of a subkey
 *  pszValue        LPTSTR to the value to store
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::SetKeyAndValue(LPTSTR pszKey, LPTSTR pszSubkey
    , LPTSTR pszValue)
    {
    HKEY        hKey;
    TCHAR       szKey[256];

    lstrcpy(szKey, pszKey);

    if (NULL!=pszSubkey)
        {
        lstrcat(szKey, TEXT("\\"));
        lstrcat(szKey, pszSubkey);
        }

    if (ERROR_SUCCESS!=RegCreateKeyEx(HKEY_CLASSES_ROOT
        , szKey, 0, NULL, REG_OPTION_NON_VOLATILE
        , KEY_ALL_ACCESS, NULL, &hKey, NULL))
        return FALSE;

    if (NULL!=pszValue)
        {
        RegSetValueEx(hKey, NULL, 0, REG_SZ, (BYTE *)pszValue
            , (lstrlen(pszValue)+1)*sizeof(TCHAR));
        }

    RegCloseKey(hKey);
    return TRUE;
    }

/*
 * CKoalaClassFactory::CKoalaClassFactory
 * CKoalaClassFactory::~CKoalaClassFactory
 *
 * Constructor Parameters:
 *  None
 */

CKoalaClassFactory::CKoalaClassFactory(void)
    {
    m_cRef=0L;
    return;
    }

CKoalaClassFactory::~CKoalaClassFactory(void)
    {
    return;
    }

/*
 * CKoalaClassFactory::QueryInterface
 * CKoalaClassFactory::AddRef
 * CKoalaClassFactory::Release
 */

STDMETHODIMP CKoalaClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CKoalaClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CKoalaClassFactory::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CKoalaClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a Koala object returning an interface pointer.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we are
 *                  being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_NOINTERFACE
 *                  if we cannot support the requested interface.
 */

STDMETHODIMP CKoalaClassFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCKoala             pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //Verify that a controlling unknown asks for IUnknown
    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    //Create the object telling us to notify us when it's gone.
    pObj=new CKoala(pUnkOuter, ObjectDestroyed);

    if (NULL==pObj)
        {
        //This starts shutdown if there are no other objects.
        g_cObj++;
        ObjectDestroyed();
        return hr;
        }

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    g_cObj++;

    /*
     * Kill the object if initial creation or Init failed. If
     * the object failed, we handle the g_cObj increment above
     * in ObjectDestroyed.
     */
    if (FAILED(hr))
        {
        delete pObj;
        ObjectDestroyed();  //Handle shutdown cases.
        }

    return hr;
    }

/*
 * CKoalaClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the serving
 *  IClassFactory object.  When the number of locks goes to
 *  zero and the number of objects is zero, we shut down the
 *  application.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CKoalaClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        {
        g_cLock--;

        /*
         * Fake an object destruction:  this centralizes
         * all the shutdown code in the ObjectDestroyed
         * function, eliminating duplicate code here.
         */
        g_cObj++;
        ObjectDestroyed();
        }

    return NOERROR;
    }


EKOALA2.H

/*
 * EKOALA2.H
 * Koala Object EXE Self-Registering Server Chapter 5
 *
 * Definitions, classes, and prototypes for an application that
 * provides Koala objects to any other object user.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _EKOALA2_H_
#define _EKOALA2_H_

//Get the object definitions
#include "koala.h"

//EKOALA2.CPP
LRESULT APIENTRY KoalaWndProc(HWND, UINT, WPARAM, LPARAM);

class CApp
    {
    friend LRESULT APIENTRY KoalaWndProc(HWND, UINT, WPARAM, LPARAM);

    protected:
        HINSTANCE       m_hInst;            //WinMain parameters
        HINSTANCE       m_hInstPrev;
        LPSTR           m_pszCmdLine;
        UINT            m_nCmdShow;

        HWND            m_hWnd;             //Main window handle

        BOOL            m_fInitialized;     //Did CoInitialize work?
        LPCLASSFACTORY  m_pIClassFactory;   //Our class factory
        DWORD           m_dwRegCO;          //Registration key

    private:
        BOOL SetKeyAndValue(LPTSTR, LPTSTR, LPTSTR);

    public:
        CApp(HINSTANCE, HINSTANCE, LPSTR, UINT);
        ~CApp(void);
        BOOL Init(void);
        void RegisterServer(void);
        void UnregisterServer(void);
    };

typedef CApp *PAPP;

#define CBWNDEXTRA              sizeof(PAPP)
#define KOALAWL_STRUCTURE       0

void ObjectDestroyed(void);

//This class factory object creates Koala objects.

class CKoalaClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;

    public:
        CKoalaClassFactory(void);
        ~CKoalaClassFactory(void);

        //IUnknown members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP CreateInstance(LPUNKNOWN, REFIID, PPVOID);
        STDMETHODIMP LockServer(BOOL);
    };

typedef CKoalaClassFactory *PCKoalaClassFactory;

#endif //_EKOALA2_H_


LICUSER.CPP

/*
 * LICUSER.CPP
 * Licensed Koala Object User/Client Chapter 5
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "licuser.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    MSG         msg;
    PAPP        pApp;

    SETMESSAGEQUEUE;

    pApp=new CApp(hInst, hInstPrev, nCmdShow);

    if (NULL==pApp)
        return -1;

    if (pApp->Init())
        {
        while (GetMessage(&msg, NULL, 0,0 ))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    delete pApp;
    return msg.wParam;
    }

/*
 * LicensedUserWndProc
 *
 * Purpose:
 *  Window class procedure.  Standard callback.
 */

LRESULT APIENTRY LicensedUserWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PAPP            pApp;

    pApp=(PAPP)GetWindowLong(hWnd, LICUSERWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pApp=(PAPP)(((LPCREATESTRUCT)lParam)->lpCreateParams);
            SetWindowLong(hWnd, LICUSERWL_STRUCTURE, (LONG)pApp);
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_COMMAND:
            switch (LOWORD(wParam))
                {
                case IDM_CFOBTAINCF:
                    pApp->ObtainClassFactory(FALSE);
                    break;

                case IDM_CFOBTAINCF2:
                    pApp->ObtainClassFactory(TRUE);
                    break;

                case IDM_CFRELEASE:
                    pApp->ReleaseClassFactory(TRUE);
                    break;

                case IDM_CFREQUESTLICKEY:
                    if (NULL==pApp->m_pIClassFac2)
                        {
                        pApp->Message(TEXT("Obtain IClassFactory2 first"));
                        break;
                        }

                    if (NULL!=pApp->m_bstrKey)
                        {
                        pApp->Message(TEXT("You already have a key"));
                        break;
                        }

                    if (SUCCEEDED(pApp->m_pIClassFac2->RequestLicKey(0
                        , &pApp->m_bstrKey)))
                        pApp->Message(TEXT("Got license key"));
                    else
                        pApp->Message(TEXT("Failed to get license key"));

                    break;

                case IDM_CLEARLICKEY:
                    if (NULL!=pApp->m_bstrKey)
                        {
                        SysFreeString(pApp->m_bstrKey);
                        pApp->m_bstrKey=NULL;
                        pApp->Message(TEXT("Cleared license key"));
                        }
                    else
                        pApp->Message(TEXT("No license key to clear"));

                    break;

                case IDM_CFEXIT:
                    PostMessage(hWnd, WM_CLOSE, 0, 0L);
                    break;

                case IDM_OBJECTCREATEINST:
                    pApp->CreateObject(FALSE);
                    break;

                case IDM_OBJECTCREATEINSTLIC:
                    pApp->CreateObject(TRUE);
                    break;
                }
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * CApp::CApp
 * CApp::~CApp
 *
 * Constructor Parameters: (from WinMain)
 *  hInst           HINSTANCE of the application.
 *  hInstPrev       HINSTANCE of a previous instance.
 *  nCmdShow        UINT specifying how to show the app window.
 *
 */

CApp::CApp(HINSTANCE hInst, HINSTANCE hInstPrev
    , UINT nCmdShow)
    {
    m_hInst=hInst;
    m_hInstPrev=hInstPrev;
    m_nCmdShow=nCmdShow;

    m_hWnd=NULL;
    m_fInitialized=FALSE;

    m_fLic=FALSE;
    m_pIClassFac=NULL;
    m_pIClassFac2=NULL;
    m_bstrKey=NULL;
    return;
    }

CApp::~CApp(void)
    {
    ReleaseClassFactory(FALSE);

    if (NULL!=m_bstrKey)
        SysFreeString(m_bstrKey);

    if (IsWindow(m_hWnd))
        DestroyWindow(m_hWnd);

    if (m_fInitialized)
        CoUninitialize();

    return;
    }

/*
 * CApp::ObtainClassFactory
 *
 * Purpose:
 *  Calls CoGetClassObject to retrieve either IClassFactory or
 *  IClassFactory2, setting the appropriate CApp member variables
 *  in the process and calling that factory's LockServer.  Any
 *  currently held factory is Released as well.
 *
 * Parameters:
 *  fLic            BOOL indicating if we're to get IClassFactory
 *                  (FALSE) or IClassFactory2 (TRUE)
 */

void CApp::ObtainClassFactory(BOOL fLic)
    {
    IClassFactory **ppCF;
    IID             iid;
    UINT            uIDCheck;
    HRESULT         hr;

    ReleaseClassFactory(FALSE);

    if (fLic)
        {
        ppCF=(IClassFactory **)&m_pIClassFac2;
        iid=IID_IClassFactory2;
        uIDCheck=IDM_CFOBTAINCF2;
        }
    else
        {
        ppCF=&m_pIClassFac;
        iid=IID_IClassFactory;
        uIDCheck=IDM_CFOBTAINCF;
        }

    hr=CoGetClassObject(CLSID_Koala, CLSCTX_INPROC_SERVER, NULL
        , iid, (PPVOID)ppCF);

    if (FAILED(hr))
        {
        Message(TEXT("Failed to obtain the class factory"));

        //This unloads any DLL that might have loaded
        CoFreeUnusedLibraries();
        return;
        }

    m_fLic=fLic;
    (*ppCF)->LockServer(TRUE);
    Message(TEXT("Class factory obtained successfully"));

    CheckMenuItem(GetMenu(m_hWnd), uIDCheck, MF_CHECKED);

    //Enable the object menu.
    EnableMenuItem(GetMenu(m_hWnd), 1, MF_BYPOSITION | MF_ENABLED);
    DrawMenuBar(m_hWnd);
    return;
    }

/*
 * CApp::ReleaseClassFactory
 *
 * Purpose:
 *  Releases any held class factory and unlocks the server.
 *
 * Parameters:
 *  fMsg            BOOL indicating whether or not to display
 *                  any messages.
 */

void CApp::ReleaseClassFactory(BOOL fMsg)
    {
    IClassFactory **ppCF;

    ppCF=m_fLic ? (IClassFactory **)&m_pIClassFac2 : &m_pIClassFac;

    if (NULL==*ppCF)
        {
        if (fMsg)
            Message(TEXT("No class factory to Release"));

        return;
        }

    (*ppCF)->LockServer(FALSE);
    (*ppCF)->Release();
    *ppCF=NULL;

    /*
     * This will unload the DLL, allowing you to reload it so
     * it will reinitialize the flag it uses to know if the
     * LIC file was found.  So before obtaining another class
     * factory you can remove the LIC file and see the results
     * without closing this app down.
     */
    CoFreeUnusedLibraries();

    CheckMenuItem(GetMenu(m_hWnd), IDM_CFOBTAINCF,  MF_UNCHECKED);
    CheckMenuItem(GetMenu(m_hWnd), IDM_CFOBTAINCF2, MF_UNCHECKED);

    EnableMenuItem(GetMenu(m_hWnd), 1, MF_BYPOSITION | MF_DISABLED
        | MF_GRAYED);
    DrawMenuBar(m_hWnd);

    if (fMsg)
        Message(TEXT("Class factory released"));

    return;
    }

/*
 * CApp::CreateObject
 *
 * Purpose:
 *  Creates an object with whatever class factory has been obtained,
 *  the releases it right away.  The purpose here is to see whether
 *  or not the object can be created at all.
 *
 * Parameters:
 *  fLic            BOOL indicating if we're to use IClassFactory
 *                  (FALSE) or IClassFactory2 (TRUE)
 */

void CApp::CreateObject(BOOL fLic)
    {
    HRESULT         hr;
    IUnknown       *pObj;

    if ((fLic && NULL==m_pIClassFac2)
        || (!fLic && (NULL==m_pIClassFac && NULL==m_pIClassFac2)))
        {
        Message(TEXT("There is no class factory"));
        return;
        }

    if (fLic && NULL==m_bstrKey)
        {
        Message(TEXT("Get a license key first"));
        return;
        }

    if (fLic)
        {
        hr=m_pIClassFac2->CreateInstanceLic(NULL, NULL, IID_IUnknown
            , m_bstrKey, (PPVOID)&pObj);
        }
    else
        {
        if (NULL!=m_pIClassFac2)
            {
            hr=m_pIClassFac2->CreateInstance(NULL, IID_IUnknown
                , (PPVOID)&pObj);
            }
        else
            {
            hr=m_pIClassFac->CreateInstance(NULL, IID_IUnknown
                , (PPVOID)&pObj);
            }
        }

    if (SUCCEEDED(hr))
        {
        Message(TEXT("Creation successful"));
        pObj->Release();
        return;
        }

    //Tell the user if creation failed for license reasons
    if (CLASS_E_NOTLICENSED==GetScode(hr))
        {
        if (fLic)
            Message(TEXT("Creation failed: license key doesn't match"));
        else
            Message(TEXT("Creation failed: no license"));
        }
    else
        Message(TEXT("Creation licensed, but failed anyway"));

    return;
    }

/*
 * CApp::Init
 *
 * Purpose:
 *  Initializes an CApp object by registering window classes,
 *  creating the main window, and doing anything else prone to
 *  failure such as calling CoInitialize.  If this function fails
 *  the caller should insure that the destructor is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::Init(void)
    {
    WNDCLASS    wc;

    CHECKVER_COM;

    if (FAILED(CoInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    if (!m_hInstPrev)
        {
        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = LicensedUserWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = CBWNDEXTRA;
        wc.hInstance      = m_hInst;
        wc.hIcon          = LoadIcon(m_hInst, TEXT("Icon"));
        wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName   = MAKEINTRESOURCE(IDR_MENU);
        wc.lpszClassName  = TEXT("LICUSER");

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

    m_hWnd=CreateWindow(TEXT("LICUSER")
        , TEXT("Licensed Object User/Client"), WS_OVERLAPPEDWINDOW
        , 35, 35, 350, 250, NULL, NULL, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    //Initially disable the object menu
    EnableMenuItem(GetMenu(m_hWnd), 1, MF_BYPOSITION | MF_DISABLED
        | MF_GRAYED);

    ShowWindow(m_hWnd, m_nCmdShow);
    UpdateWindow(m_hWnd);

    return TRUE;
    }

/*
 * CApp::Message
 *
 * Purpose:
 *  Displays a message in the client area of the window.  This is
 *  just to centralize the call to simpify other code.
 *
 * Parameters:
 *  psz             LPTSTR to the string to display.
 *
 * Return Value:
 *  None
 */

void inline CApp::Message(LPTSTR psz)
    {
    HDC     hDC;
    RECT    rc;

    hDC=GetDC(m_hWnd);
    GetClientRect(m_hWnd, &rc);

    SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
    SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));

    /*
     * We'll just be sloppy and clear the whole window as
     * well as write the string with one ExtTextOut call.
     * No word wrapping here...
     */

    ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rc, psz, lstrlen(psz), NULL);
    ReleaseDC(m_hWnd, hDC);
    return;
    }


LICUSER.H

/*
 * LICUSER.H
 * Licensed Koala Object User/Client Chapter 5
 *
 * Definitions and structures.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _LICUSER_H_
#define _LICUSER_H_

#define CHAPTER5
#define INC_CONTROLS
#include <inole.h>

//Menu Resource ID and Commands
#define IDR_MENU                    1

#define IDM_CFOBTAINCF              100
#define IDM_CFOBTAINCF2             101
#define IDM_CFRELEASE               102
#define IDM_CFREQUESTLICKEY         103
#define IDM_CLEARLICKEY             104
#define IDM_CFEXIT                  105

#define IDM_OBJECTCREATEINST        200
#define IDM_OBJECTCREATEINSTLIC     201

//LICUSER.CPP
LRESULT APIENTRY LicensedUserWndProc(HWND, UINT, WPARAM, LPARAM);

class CApp
    {
    friend LRESULT APIENTRY LicensedUserWndProc(HWND, UINT, WPARAM
        , LPARAM);

    protected:
        HINSTANCE       m_hInst;            //WinMain parameters
        HINSTANCE       m_hInstPrev;
        UINT            m_nCmdShow;

        HWND            m_hWnd;             //Main window handle
        BOOL            m_fInitialized;     //Did CoInitialize work?

        BOOL            m_fLic;             //True for Lic factory
        IClassFactory  *m_pIClassFac;
        IClassFactory2 *m_pIClassFac2;

        BSTR            m_bstrKey;          //Licence key

    public:
        CApp(HINSTANCE, HINSTANCE, UINT);
        ~CApp(void);

        void ObtainClassFactory(BOOL);
        void ReleaseClassFactory(BOOL);
        void CreateObject(BOOL);

        BOOL Init(void);
        void Message(LPTSTR);
    };

typedef CApp *PAPP;

#define CBWNDEXTRA              sizeof(PAPP)
#define LICUSERWL_STRUCTURE     0

#endif //_LICUSER_H_


OBJUSER.CPP

/*
 * OBJUSER.CPP
 * Koala Object User/Client Chapter 5
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "objuser.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    MSG         msg;
    PAPP        pApp;

    SETMESSAGEQUEUE;

    pApp=new CApp(hInst, hInstPrev, nCmdShow);

    if (NULL==pApp)
        return -1;

    if (pApp->Init())
        {
        while (GetMessage(&msg, NULL, 0,0 ))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    delete pApp;
    return msg.wParam;
    }

/*
 * ObjectUserWndProc
 *
 * Purpose:
 *  Window class procedure.  Standard callback.
 */

LRESULT APIENTRY ObjectUserWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    HRESULT         hr;
    PAPP            pApp;
    LPCLASSFACTORY  pIClassFactory;
    DWORD           dwClsCtx;
    ULONG           cRef;
    TCHAR           szTemp[80];

    pApp=(PAPP)GetWindowLong(hWnd, OBJUSERWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pApp=(PAPP)(((LPCREATESTRUCT)lParam)->lpCreateParams);
            SetWindowLong(hWnd, OBJUSERWL_STRUCTURE, (LONG)pApp);
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_COMMAND:
            switch (LOWORD(wParam))
                {
                case IDM_OBJECTUSEDLL:
                    pApp->m_fEXE=FALSE;
                    CheckMenuItem(GetMenu(hWnd), IDM_OBJECTUSEDLL
                        , MF_CHECKED);
                    CheckMenuItem(GetMenu(hWnd), IDM_OBJECTUSEEXE
                        , MF_UNCHECKED);
                    break;

                case IDM_OBJECTUSEEXE:
                    pApp->m_fEXE=TRUE;
                    CheckMenuItem(GetMenu(hWnd), IDM_OBJECTUSEDLL
                        , MF_UNCHECKED);
                    CheckMenuItem(GetMenu(hWnd), IDM_OBJECTUSEEXE
                        , MF_CHECKED);
                    break;

                case IDM_OBJECTCREATECOGCO:
                    if (NULL!=pApp->m_pIUnknown)
                        {
                        while (pApp->m_cRefOurs--)
                            ReleaseInterface(pApp->m_pIUnknown);

                        CoFreeUnusedLibraries();
                        }

                    dwClsCtx=(pApp->m_fEXE) ? CLSCTX_LOCAL_SERVER
                        : CLSCTX_INPROC_SERVER;

                    hr=CoGetClassObject(CLSID_Koala, dwClsCtx, NULL
                        , IID_IClassFactory, (PPVOID)&pIClassFactory);

                    if (SUCCEEDED(hr))
                        {
                        hr=pIClassFactory->CreateInstance(NULL
                            , IID_IUnknown
                            , (PPVOID)&pApp->m_pIUnknown);

                        //Release the class factory when done.
                        pIClassFactory->Release();

                        if (SUCCEEDED(hr))
                            {
                            pApp->Message(TEXT("Creation succeeded"));
                            pApp->m_cRefOurs=1;
                            }
                        else
                            pApp->Message(TEXT("Creation failed"));
                        }
                    else
                        pApp->Message(TEXT("CoGetClassObject failed"));

                    break;

                case IDM_OBJECTCREATECOCI:
                    if (NULL!=pApp->m_pIUnknown)
                        {
                        while (pApp->m_cRefOurs--)
                            ReleaseInterface(pApp->m_pIUnknown);

                        CoFreeUnusedLibraries();
                        }

                    //Simpler creation:  use CoCreateInstance
                    dwClsCtx=(pApp->m_fEXE) ? CLSCTX_LOCAL_SERVER
                        : CLSCTX_INPROC_SERVER;

                    hr=CoCreateInstance(CLSID_Koala, NULL, dwClsCtx
                        , IID_IUnknown, (PPVOID)&pApp->m_pIUnknown);

                    if (SUCCEEDED(hr))
                        {
                        pApp->Message(TEXT("Creation succeeded"));
                        pApp->m_cRefOurs=1;
                        }
                    else
                        pApp->Message(TEXT("Creation failed"));

                    break;

                case IDM_OBJECTADDREF:
                    if (NULL==pApp->m_pIUnknown)
                        {
                        pApp->Message(TEXT("There is no object"));
                        break;
                        }

                    /*
                     * We maintain our own usage count so we
                     * know later when to NULL our pointer.
                     */
                    cRef=pApp->m_pIUnknown->AddRef();
                    pApp->m_cRefOurs++;

                    wsprintf(szTemp, TEXT("Object Count=%lu, Usage=%lu")
                        , cRef, pApp->m_cRefOurs);

                    pApp->Message(szTemp);
                    break;

                case IDM_OBJECTRELEASE:
                    if (NULL==pApp->m_pIUnknown)
                        {
                        pApp->Message(TEXT("There is no object"));
                        break;
                        }

                    cRef=pApp->m_pIUnknown->Release();

                    /*
                     * Our private usage count tells us when we
                     * believe we're done with the object.  We can't
                     * use the ref count from Release to tell us
                     * if the object is really destroyed or not--so
                     * in this case we maintain our own count.
                     */
                    if (0==--pApp->m_cRefOurs)
                        {
                        pApp->m_pIUnknown=NULL;
                        CoFreeUnusedLibraries();
                        }

                    wsprintf(szTemp, TEXT("Object Count=%lu, Usage=%lu")
                        , cRef, pApp->m_cRefOurs);
                    pApp->Message(szTemp);
                    break;

                case IDM_OBJECTEXIT:
                    PostMessage(hWnd, WM_CLOSE, 0, 0L);
                    break;
                }
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * CApp::CApp
 * CApp::~CApp
 *
 * Constructor Parameters: (from WinMain)
 *  hInst           HINSTANCE of the application.
 *  hInstPrev       HINSTANCE of a previous instance.
 *  nCmdShow        UINT specifying how to show the app window.
 *
 */

CApp::CApp(HINSTANCE hInst, HINSTANCE hInstPrev
    , UINT nCmdShow)
    {
    m_hInst       =hInst;
    m_hInstPrev   =hInstPrev;
    m_nCmdShow    =nCmdShow;

    m_hWnd        =NULL;
    m_fEXE        =FALSE;

    m_cRefOurs    =0L;
    m_pIUnknown   =NULL;
    m_fInitialized=FALSE;
    return;
    }

CApp::~CApp(void)
    {
    while (m_cRefOurs-- > 0)
        m_pIUnknown->Release();

    m_pIUnknown=NULL;

    if (IsWindow(m_hWnd))
        DestroyWindow(m_hWnd);

    if (m_fInitialized)
        CoUninitialize();

    return;
    }

/*
 * CApp::Init
 *
 * Purpose:
 *  Initializes an CApp object by registering window classes,
 *  creating the main window, and doing anything else prone to
 *  failure such as calling CoInitialize.  If this function fails
 *  the caller should insure that the destructor is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::Init(void)
    {
    WNDCLASS    wc;

    CHECKVER_COM;

    if (FAILED(CoInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    if (!m_hInstPrev)
        {
        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = ObjectUserWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = CBWNDEXTRA;
        wc.hInstance      = m_hInst;
        wc.hIcon          = LoadIcon(m_hInst, TEXT("Icon"));
        wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName   = MAKEINTRESOURCE(IDR_MENU);
        wc.lpszClassName  = TEXT("OBJUSER");

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

    m_hWnd=CreateWindow(TEXT("OBJUSER")
        , TEXT("DLL and EXE Object User/Client"), WS_OVERLAPPEDWINDOW
        , 35, 35, 350, 250, NULL, NULL, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    ShowWindow(m_hWnd, m_nCmdShow);
    UpdateWindow(m_hWnd);

    CheckMenuItem(GetMenu(m_hWnd), IDM_OBJECTUSEDLL, MF_CHECKED);
    CheckMenuItem(GetMenu(m_hWnd), IDM_OBJECTUSEEXE, MF_UNCHECKED);

    return TRUE;
    }

/*
 * CApp::Message
 *
 * Purpose:
 *  Displays a message in the client area of the window.  This is
 *  just to centralize the call to simpify other code.
 *
 * Parameters:
 *  psz             LPTSTR to the string to display.
 *
 * Return Value:
 *  None
 */

void CApp::Message(LPTSTR psz)
    {
    HDC     hDC;
    RECT    rc;

    hDC=GetDC(m_hWnd);
    GetClientRect(m_hWnd, &rc);

    SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
    SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));

    /*
     * We'll just be sloppy and clear the whole window as
     * well as write the string with one ExtTextOut call.
     * No word wrapping here...
     */

    ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rc, psz, lstrlen(psz), NULL);
    ReleaseDC(m_hWnd, hDC);
    return;
    }


OBJUSER.H

/*
 * OBJUSER.H
 * Koala Object User/Client Chapter 5
 *
 * Definitions and structures.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _OBJUSER_H_
#define _OBJUSER_H_

#define CHAPTER5
#include <inole.h>

//Menu Resource ID and Commands
#define IDR_MENU                    1

#define IDM_OBJECTUSEDLL            100
#define IDM_OBJECTUSEEXE            101
#define IDM_OBJECTCREATECOGCO       102
#define IDM_OBJECTCREATECOCI        103
#define IDM_OBJECTADDREF            104
#define IDM_OBJECTRELEASE           105
#define IDM_OBJECTEXIT              106

//OBJUSER.CPP
LRESULT APIENTRY ObjectUserWndProc(HWND, UINT, WPARAM, LPARAM);

class CApp
    {
    friend LRESULT APIENTRY ObjectUserWndProc(HWND, UINT, WPARAM
        , LPARAM);

    protected:
        HINSTANCE       m_hInst;            //WinMain parameters
        HINSTANCE       m_hInstPrev;
        UINT            m_nCmdShow;

        HWND            m_hWnd;             //Main window handle
        BOOL            m_fEXE;             //Menu selection

        ULONG           m_cRefOurs;         //Our use of the object
        LPUNKNOWN       m_pIUnknown;        //IUnknown interface
        BOOL            m_fInitialized;     //Did CoInitialize work?

    public:
        CApp(HINSTANCE, HINSTANCE, UINT);
        ~CApp(void);

        BOOL Init(void);
        void Message(LPTSTR);
    };

typedef CApp *PAPP;

#define CBWNDEXTRA              sizeof(PAPP)
#define OBJUSERWL_STRUCTURE     0

#endif //_OBJUSER_H_


DLLPOLY.CPP

/*
 * DLLPOLY.CPP
 * Polyline Component Object Chapter 5
 *
 * Polyline component object used in CoCosmo that supports a custom
 * interface IPolyline.  Contains DLL entry code and the component
 * object exports DllGetClassObject and DllCanUnloadNow and the
 * class factory object.
 *
 * Copyright (c)1993-1997 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "polyline.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

//DLL Instance handle
HINSTANCE   g_hInst=0;

/*
 * LibMain32 (Win32)
 *
 * Purpose:
 *  Entry point for Windows NT DLLs that calls the Win16 LibMain.
 */

#ifdef WIN32
BOOL WINAPI DllMain(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    if (DLL_PROCESS_ATTACH==ulReason)
        return (0!=LibMain(hInstance, 0,  0, NULL));

    return TRUE;
    }
#endif

/*
 * LibMain (also called from Win32 LibMain32)
 *
 * Purpose:
 *  DLL-specific entry point called from LibEntry.
 */

int PASCAL LibMain(HINSTANCE hInst, WORD wDataSeg
    , WORD cbHeapSize, LPSTR lpCmdLine)
    {
    WNDCLASS    wc;

    if (GetClassInfo(hInst, SZCLASSPOLYLINE, &wc))
        return (int)hInst;

   #ifndef WIN32
    if (0!=cbHeapSize)
        UnlockData(0);
   #endif

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.hInstance     = hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PolylineWndProc;
    wc.cbWndExtra    = CBPOLYLINEWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_CROSS);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPOLYLINE;

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

    g_hInst=hInst;
    return (int)hInst;
    }

/*
 * DllGetClassObject
 *
 * Purpose:
 *  Provides an IClassFactory for a given CLSID that this DLL is
 *  registered to support.  This DLL is placed under the CLSID
 *  in the registration database as the InProcServer.
 *
 * Parameters:
 *  clsID           REFCLSID that identifies the class factory
 *                  desired.  Since this parameter is passed this
 *                  DLL can handle any number of objects simply
 *                  by returning different class factories here
 *                  for different CLSIDs.
 *
 *  riid            REFIID specifying the interface the caller wants
 *                  on the class object, usually IID_ClassFactory.
 *
 *  ppv             PPVOID in which to return the interface
 *                  pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, otherwise an error code.
 */

HRESULT APIENTRY DllGetClassObject(REFCLSID rclsid
    , REFIID riid, PPVOID ppv)
    {
    //CHAPTER5MOD
    if (CLSID_Polyline5!=rclsid)
        return E_FAIL;
    //End CHAPTER5MOD

    //Check that we can provide the interface
    if (IID_IUnknown!=riid && IID_IClassFactory!=riid)
        return E_NOINTERFACE;

    //Return our IClassFactory for Polyline objects
    *ppv=new CPolylineClassFactory;

    if (NULL==*ppv)
        return E_OUTOFMEMORY;

    //AddRef the object through any interface we return
    ((LPUNKNOWN)*ppv)->AddRef();

    return NOERROR;
    }

/*
 * DllCanUnloadNow
 *
 * Purpose:
 *  Answers if the DLL can be freed, that is, if there are no
 *  references to anything this DLL provides.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if nothing is using us, FALSE otherwise.
 */

STDAPI DllCanUnloadNow(void)
    {
    SCODE   sc;

    //Our answer is whether there are any object or locks
    sc=(0L==g_cObj && 0L==g_cLock) ? S_OK : S_FALSE;
    return sc;
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Polyline object to call when it gets destroyed.
 *  Since we're in a DLL we only track the number of objects here
 *  letting DllCanUnloadNow take care of the rest.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void PASCAL ObjectDestroyed(void)
    {
    g_cObj--;
    return;
    }

/*
 * CPolylineClassFactory::CPolylineClassFactory
 *
 * Purpose:
 *  Constructor for an object supporting an IClassFactory that
 *  instantiates Polyline objects.
 *
 * Parameters:
 *  None
 */

CPolylineClassFactory::CPolylineClassFactory(void)
    {
    m_cRef=0L;
    return;
    }

/*
 * CPolylineClassFactory::~CPolylineClassFactory
 *
 * Purpose:
 *  Destructor for a CPolylineClassFactory object.  This will be
 *  called when we Release the object to a zero reference count.
 */

CPolylineClassFactory::~CPolylineClassFactory(void)
    {
    return;
    }

/*
 * CPolylineClassFactory::QueryInterface
 * CPolylineClassFactory::AddRef
 * CPolylineClassFactory::Release
 *
 * Purpose:
 *  IUnknown implementations for this object.
 */

STDMETHODIMP CPolylineClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    //Any interface on this object is the object pointer.
    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    /*
     * If we actually assign an interface to ppv we need to AddRef
     * it since we're returning a new pointer.
     */
    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return E_NOINTERFACE;
    }

STDMETHODIMP_(ULONG) CPolylineClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPolylineClassFactory::Release(void)
    {
    ULONG           cRefT;

    cRefT=--m_cRef;

    if (0L==m_cRef)
        delete this;

    return cRefT;
    }

/*
 * CPolylineClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a Polyline object that supports the IPolyline
 *  and IUnknown interfaces.  If the caller asks for a different
 *  interface than these two then we fail.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we
 *                  are being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_NOINTERFACE
 *                  if we cannot support the requested interface.
 */

STDMETHODIMP CPolylineClassFactory::CreateInstance
    (LPUNKNOWN pUnkOuter, REFIID riid, PPVOID ppvObj)
    {
    PCPolyline          pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=E_OUTOFMEMORY;

    //Verify that a controlling unknown asks for IUnknown
    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return E_NOINTERFACE;

    //Create the object.  This also creates a window.
    pObj=new CPolyline(pUnkOuter, ObjectDestroyed, g_hInst);

    if (NULL==pObj)
        return hr;

    if (pObj->FInit())
        hr=pObj->QueryInterface(riid, ppvObj);

    //Kill the object if initial creation or FInit failed.
    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

/*
 * CPolylineClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the DLL.  If the lock
 *  count goes to zero and there are no objects, the DLL is allowed
 *  to unload.  See DllCanUnloadNow.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CPolylineClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        g_cLock--;

    return NOERROR;
    }


ICONNPT.CPP

/*
 * ICONNPT.CPP
 * Polyline Component Chapter 5
 *
 * Implementation of CImpIConnectionPoint for the Polyline object
 * as well as CConnectionPoint.
 *
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIConnPtCont:CImpIConnPtCont
 * CImpIConnPtCont::~CImpIConnPtCont
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIConnPtCont::CImpIConnPtCont(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIConnPtCont::~CImpIConnPtCont(void)
    {
    return;
    }

/*
 * CImpIConnPtCont::QueryInterface
 * CImpIConnPtCont::AddRef
 * CImpIConnPtCont::Release
 */

STDMETHODIMP CImpIConnPtCont::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIConnPtCont::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIConnPtCont::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIConnPtCont::EnumConnectionPoints
 *
 * Purpose:
 *  Not implemented.
 *
 * Return Value:
 *  HRESULT         E_NOTIMPL
 */

STDMETHODIMP CImpIConnPtCont::EnumConnectionPoints
    (LPENUMCONNECTIONPOINTS *ppEnum)
    {
    *ppEnum=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIConnPtCont::FindConnectionPoint
 *
 * Purpose:
 *  Returns a pointer to the IConnectionPoint for a given
 *  outgoing IID.
 *
 * Parameters:
 *  riid            REFIID of the outgoing interface for which
 *                  a connection point is desired.
 *  ppCP            IConnectionPoint ** in which to return
 *                  the pointer after calling AddRef.
 *
 * Return Value:
 *  HRESULT         NOERROR if the connection point is found,
 *                  E_NOINTERFACE if it's not supported.
 */

STDMETHODIMP CImpIConnPtCont::FindConnectionPoint(REFIID riid
    , IConnectionPoint **ppCP)
    {
    *ppCP=NULL;

    if (IID_IPolylineAdviseSink5==riid)
        {
        return m_pObj->m_pConnPt->QueryInterface
            (IID_IConnectionPoint, (PPVOID)ppCP);
        }

    return ResultFromScode(E_NOINTERFACE);
    }

//CConnectionPoint implementation

/*
 * CConnectionPoint::CConnectionPoint
 * CConnectionPoint::~CConnectionPoint
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.  We can
 *                  query this for the IConnectionPointContainer
 *                  interface we might need.
 */

CConnectionPoint::CConnectionPoint(PCPolyline pObj)
    {
    m_cRef=0;

    /*
     * Our lifetime is controlled by the connectable object itself,
     * although other external clients will call AddRef and Release.
     * Since we're nested in the connectable object's lifetime,
     * there's no need to call AddRef on pObj.
     */
    m_pObj=pObj;
    return;
    }

CConnectionPoint::~CConnectionPoint(void)
    {
    ReleaseInterface(m_pObj->m_pAdv);
    return;
    }

/*
 * CConnectionPoint::QueryInterface
 * CConnectionPoint::AddRef
 * CConnectionPoint::Release
 *
 * Purpose:
 *  Non-delegating IUnknown members for CConnectionPoint.
 */

STDMETHODIMP CConnectionPoint::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IConnectionPoint==riid)
        *ppv=(LPVOID)this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CConnectionPoint::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CConnectionPoint::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CConnectionPoint::GetConnectionInterface
 *
 * Purpose:
 *  Returns the IID of the outgoing interface supported through
 *  this connection point.
 *
 * Parameters:
 *  pIID            IID * in which to store the IID.
 */

STDMETHODIMP CConnectionPoint::GetConnectionInterface(IID *pIID)
    {
    if (NULL==pIID)
        return ResultFromScode(E_POINTER);

    *pIID=IID_IPolylineAdviseSink5;
    return NOERROR;
    }

/*
 * CConnectionPoint::GetConnectionPointContainer
 *
 * Purpose:
 *  Returns a pointer to the IConnectionPointContainer that
 *  is manageing this connection point.
 *
 * Parameters:
 *  ppCPC           IConnectionPointContainer ** in which to return
 *                  the pointer after calling AddRef.
 */

STDMETHODIMP CConnectionPoint::GetConnectionPointContainer
    (IConnectionPointContainer **ppCPC)
    {
    return m_pObj->QueryInterface(IID_IConnectionPointContainer
        , (void **)ppCPC);
    }

/*
 * CConnectionPoint::Advise
 *
 * Purpose:
 *  Provides this connection point with a notification sink to
 *  call whenever the appropriate outgoing function/event occurs.
 *
 * Parameters:
 *  pUnkSink        LPUNKNOWN to the sink to notify.  The connection
 *                  point must QueryInterface on this pointer to obtain
 *                  the proper interface to call.  The connection
 *                  point must also insure that any pointer held has
 *                  a reference count (QueryInterface will do it).
 *  pdwCookie       DWORD * in which to store the connection key for
 *                  later calls to Unadvise.
 */

STDMETHODIMP CConnectionPoint::Advise(LPUNKNOWN pUnkSink
    , DWORD *pdwCookie)
    {
    IPolylineAdviseSink5   *pSink;

    *pdwCookie=0;

    //Only allow one connection
    if (NULL!=m_pObj->m_pAdv)
        return ResultFromScode(CONNECT_E_ADVISELIMIT);

    //Check for the right interface on the sink.
    if (FAILED(pUnkSink->QueryInterface(IID_IPolylineAdviseSink5
        , (PPVOID)&pSink)))
        return ResultFromScode(CONNECT_E_CANNOTCONNECT);

    *pdwCookie=ADVISEKEY;
    m_pObj->m_pAdv=pSink;
    return NOERROR;
    }

/*
 * CConnectionPoint::Unadvise
 *
 * Purpose:
 *  Terminates the connection to the notification sink identified
 *  with dwCookie (that was returned from Advise).  The connection
 *  point has to Release any held pointers for that sink.
 *
 * Parameters:
 *  dwCookie        DWORD connection key from Advise.
 */

STDMETHODIMP CConnectionPoint::Unadvise(DWORD dwCookie)
    {
    if (0==dwCookie)
        return ResultFromScode(E_INVALIDARG);

    if (ADVISEKEY!=dwCookie)
        ResultFromScode(CONNECT_E_NOCONNECTION);

    ReleaseInterface(m_pObj->m_pAdv);
    return NOERROR;
    }

/*
 * CConnectionPoint::EnumConnections
 *
 * Purpose:
 *  Not implemented.
 */

STDMETHODIMP CConnectionPoint::EnumConnections
    (LPENUMCONNECTIONS *ppEnum)
    {
    *ppEnum=NULL;
    return ResultFromScode(E_NOTIMPL);
    }


IPOLYLIN.CPP

/*
 * IPOLYLIN.CPP
 * Polyline Component Object Chapter 5
 *
 * Implementation of the IPolyline interface that we expose on the
 * CPolyline object.
 *
 * Copyright (c)1993-1997 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIPolyline:CImpIPolyline
 * CImpIPolyline::~CImpIPolyline
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPolyline::CImpIPolyline(PCPolyline pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPolyline::~CImpIPolyline(void)
    {
    return;
    }

/*
 * CImpIPolyline::QueryInterface
 * CImpIPolyline::AddRef
 * CImpIPolyline::Release
 *
 * Purpose:
 *  Standard set of IUnknown members for this interface
 */

STDMETHODIMP CImpIPolyline::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPolyline::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPolyline::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPolyline::Init
 *
 * Purpose:
 *  Instantiates a polyline window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags
 *  uID             UINT ID to associate with this window
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_OUTOFMEMORY
 */

STDMETHODIMP CImpIPolyline::Init(HWND hWndParent, LPRECT pRect
    , DWORD dwStyle, UINT uID)
    {
    SCODE           sc;

    m_pObj->m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY
        , SZCLASSPOLYLINE, SZCLASSPOLYLINE, dwStyle, pRect->left
        , pRect->top, pRect->right-pRect->left
        , pRect->bottom-pRect->top, hWndParent, (HMENU)uID
        , m_pObj->m_hInst, m_pObj);

    sc=(NULL!=m_pObj->m_hWnd) ? S_OK : E_OUTOFMEMORY;
    return sc;
    }

//CHAPTER5MOD
/*
 * ReadFromFile and WriteToFile are replaced by the IPersistStorage
 * implementation.  We no longer read from files, instead we use
 * storages.
 */
//End CHAPTER5MOD

/*
 * CImpIPolyline::DataSet
 *
 * Purpose:
 *  Sets the current data in this Polyline to a given structure.
 *
 * Parameters:
 *  pplIn           PPOLYLINEDATA to initialize to.
 *  fSizeToData     BOOL indicating if we're to size to the data
 *                  or scale it.
 *  fNotify         BOOL indicating if we're to send an advise
 *                  on this change.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::DataSet(PPOLYLINEDATA pplIn
    , BOOL fSizeToData, BOOL fNotify)
    {
    PPOLYLINEDATA   ppl=&m_pObj->m_pl;
    RECT            rc;

    /*
     * Copy the structure in pplIn and repaint to reflect the
     * new point set.  Note that unlike the RectSet message, we
     * do no scaling, assuming that the rect in the structure
     * is appropriate for the data.
     */

    if (NULL==pplIn)
        return POLYLINE_E_INVALIDPOINTER;

    *ppl=*pplIn;

    //Inform our parent of the data change
    if (NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnDataChange();
        }

    /*
     * If we're scaling the window to fit the data, then use
     * RectSet passing our current rectangle as the new one.
     * That makes sure that the data won't change but that the
     * window is resized.
     */

    if (fSizeToData)
        {
        POINT       pt;

        /*
         * Get our offset in the parent window so we can RectSet
         * to the right place since RectSet expects rectangle in
         * parent coordinates and we get it in client coordinates.
         */
        GetWindowRect(m_pObj->m_hWnd, &rc);
        pt.x=rc.left;
        pt.y=rc.top;
        ScreenToClient(GetParent(m_pObj->m_hWnd), &pt);
        RECTSTORECT(ppl->rc, rc);
        OffsetRect(&rc, pt.x, pt.y);

        //This will also cause a repaint.
        RectSet(&rc, fNotify);
        }
    else
        {
        //Make sure we're updated.
        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        UpdateWindow(m_pObj->m_hWnd);
        }

    return NOERROR;
    }

/*
 * CImpIPolyline::DataGet
 *
 * Purpose:
 *  Retrieves the Polyline's current data.
 *
 * Parameters:
 *  pplIn           PPOLYLINEDATA into which we copy the data.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::DataGet(PPOLYLINEDATA pplIn)
    {
    if (NULL==pplIn)
        return POLYLINE_E_INVALIDPOINTER;

    *pplIn=m_pObj->m_pl;
    return NOERROR;
    }

/*
 * CImpIPolyline::DataSetMem
 *
 * Purpose:
 *  Sets the Polyline's data using a global memory handle
 *  instead of a pointer.
 *
 * Parameters:
 *  hMem            HGLOBAL containing the data.
 *  fFree           BOOL indicating if we're to free the data.
 *                  The memory will be freed regardless of any
 *                  error returned from here.
 *  fSizeToData     BOOL indicating if we're to size to the data
 *                  or scale it.
 *  fNotify         BOOL indicating if we're to send an advise
 *                  on this change.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::DataSetMem(HGLOBAL hMem, BOOL fFree
    , BOOL fSizeToData, BOOL fNotify)
    {
    PPOLYLINEDATA   ppl;
    HRESULT         hr=POLYLINE_E_INVALIDPOINTER;

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);

        hr=DataSet(ppl, fSizeToData, fNotify);

        GlobalUnlock(hMem);

        if (fFree)
            GlobalFree(hMem);
        }

    return hr;
    }

/*
 * CImpIPolyline::DataGetMem
 *
 * Purpose:
 *  Retrieves the Polyline's data in a global memory handle.
 *
 * Parameters:
 *  phMem           HGLOBAL * in which to store the handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::DataGetMem(HGLOBAL *phMem)
    {
    HGLOBAL         hMem;
    PPOLYLINEDATA   ppl;
    HRESULT         hr=POLYLINE_E_INVALIDPOINTER;

    if (NULL==phMem)
        return POLYLINE_E_INVALIDPOINTER;

    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, CBPOLYLINEDATA);

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        hr=DataGet(ppl);

        GlobalUnlock(hMem);

        if (FAILED(hr))
            {
            GlobalFree(hMem);
            hMem=NULL;
            }
        }

    *phMem=hMem;
    return hr;
    }

/*
 * CImpIPolyline::RenderBitmap
 *
 * Purpose:
 *  Creates a bitmap image of the current Polyline.
 *
 * Parameters:
 *  phBmp           HBITMAP * in which to return the bitmap.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::RenderBitmap(HBITMAP *phBmp)
    {
    HDC             hDC;
    HDC             hMemDC;
    HBITMAP         hBmp;
    RECT            rc;
    HGDIOBJ         hObj;

    if (NULL==phBmp)
        return POLYLINE_E_INVALIDPOINTER;

    //Render a bitmap the size of the current rectangle.
    hDC=GetDC(m_pObj->m_hWnd);
    hMemDC=CreateCompatibleDC(hDC);

    GetClientRect(m_pObj->m_hWnd, &rc);
    hBmp=CreateCompatibleBitmap(hDC, rc.right, rc.bottom);

    if (NULL!=hBmp)
        {
        //Draw the POLYLINEDATA into the bitmap.
        hObj=SelectObject(hMemDC, hBmp);
        m_pObj->Draw(hMemDC, FALSE, TRUE);
        SelectObject(hMemDC, hObj);
        }

    DeleteDC(hMemDC);
    ReleaseDC(m_pObj->m_hWnd, hDC);

    *phBmp=hBmp;
    return NOERROR;
    }

/*
 * CImpIPolyline::RenderMetafile
 *
 * Purpose:
 *  Renders the current image of the Polyline into a metafile.
 *
 * Parameters:
 *  phMF            HMETAFILE * in which to return the
 *                  new metafile.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::RenderMetafile(HMETAFILE *phMF)
    {
    HDC             hDC;
    HMETAFILE       hMF;
    RECT            rc;

    if (NULL==phMF)
        return POLYLINE_E_INVALIDPOINTER;

    //Create a memory metafile and return its handle.
    hDC=(HDC)CreateMetaFile(NULL);
    hMF=NULL;

    if (NULL!=hDC)
        {
        /*
         * This is absolutely essential to the metafile so it
         * can be scaled in the clipboard and any destination
         * application.
         */
        SetMapMode(hDC, MM_ANISOTROPIC);
        GetClientRect(m_pObj->m_hWnd, &rc);
        SetWindowOrgEx(hDC, 0, 0, NULL);
        SetWindowExtEx(hDC, rc.right, rc.bottom, NULL);

        m_pObj->Draw(hDC, TRUE, TRUE);
        hMF=CloseMetaFile(hDC);
        }

    *phMF=hMF;
    return NOERROR;
    }

/*
 * CImpIPolyline::RenderMetafilePict
 *
 * Purpose:
 *  Renders the current Polyline into a METAFILEPICT structure in
 *  global memory.
 *
 * Parameters:
 *  phMem           HGLOBAL * in which to return the
 *                  METAFILEPICT.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::RenderMetafilePict(HGLOBAL * phMem)
    {
    HGLOBAL         hMem;
    HMETAFILE       hMF;
    LPMETAFILEPICT  pMF;
    RECT            rc;
    HRESULT         hr;

    if (NULL==phMem)
        return POLYLINE_E_INVALIDPOINTER;

    //Get the metafile
    hr=RenderMetafile(&hMF);

    if (FAILED(hr))
        return hr;

    //Allocate the METAFILEPICT structure.
    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
        , sizeof(METAFILEPICT));

    if (NULL==hMem)
        {
        DeleteMetaFile(hMF);
        return E_FAIL;
        }

    /*
     * Global lock only fails in PMODE if the selector is invalid
     * (like it was discarded) or references a 0 length segment,
     * neither of which can happen here.
     */
    pMF=(LPMETAFILEPICT)GlobalLock(hMem);

    pMF->hMF=hMF;
    pMF->mm=MM_ANISOTROPIC;

    //Insert the extents in MM_HIMETRIC units.
    GetClientRect(m_pObj->m_hWnd, &rc);
    m_pObj->RectConvertMappings(&rc, FALSE);
    pMF->xExt=rc.right;
    pMF->yExt=rc.bottom;

    GlobalUnlock(hMem);

    *phMem=hMem;
    return NOERROR;
    }

/*
 * CImpIPolyline::New
 *
 * Purpose:
 *  Cleans out and reinitializes the data to defaults.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::New(void)
    {
    PPOLYLINEDATA   ppl=&m_pObj->m_pl;
    UINT            i;
    RECT            rc;

    ppl->wVerMaj=VERSIONMAJOR;
    ppl->wVerMin=VERSIONMINOR;

    //Our rectangle is the size of our window's client area.
    GetClientRect(m_pObj->m_hWnd, &rc);
    RECTTORECTS(rc, ppl->rc);

    //Clean out the POLYLINEDATA structure and repaint the window.
    for (i=0; i< CPOLYLINEPOINTS; i++)
        {
        ppl->rgpt[i].x=0;
        ppl->rgpt[i].y=0;
        }

    ppl->cPoints      =0;
    ppl->rgbBackground=GetSysColor(COLOR_WINDOW);
    ppl->rgbLine      =GetSysColor(COLOR_WINDOWTEXT);
    ppl->iLineStyle   =PS_SOLID;

    InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
    UpdateWindow(m_pObj->m_hWnd);

    //Inform the advise sink of this data change.
    if (NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnDataChange();
        }

    return NOERROR;
    }

/*
 * CImpIPolyline::Undo
 *
 * Purpose:
 *  Reverses previous actions in a Polyline.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         S_OK if we can Undo more, S_FALSE otherwise.
 */

STDMETHODIMP CImpIPolyline::Undo(void)
    {
    SCODE           sc;

    //Decrement the number of active points and repaint.
    if (m_pObj->m_pl.cPoints > 0)
        {
        m_pObj->m_pl.cPoints--;
        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        UpdateWindow(m_pObj->m_hWnd);
        }

    if (NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnPointChange();
        }

    //Return if we can undo any more.
    sc=(0!=m_pObj->m_pl.cPoints) ? S_OK : S_FALSE;
    return sc;
    }

/*
 * CImpIPolyline::Window
 *
 * Purpose:
 *  Returns the window handle associated with this polyline.
 *
 * Parameters:
 *  phWnd           HWND * in which to return the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CImpIPolyline::Window(HWND *phWnd)
    {
    *phWnd=m_pObj->m_hWnd;
    return NOERROR;
    }

/*
 * CImpIPolyline::SetAdvise
 *
 * Purpose:
 *  Provides this object with an IPolylineAdviseSink that's
 *  interested in our notifications.  We AddRef and store this
 *  pointer, Releasing the old one.
 *
 * Parameters:
 *  pAdv            PPOLYLINEADVISESINK to notify.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CImpIPolyline::SetAdvise(PPOLYLINEADVISESINK pAdv)
    {
    if (NULL!=m_pObj->m_pAdv)
        m_pObj->m_pAdv->Release();

    m_pObj->m_pAdv=pAdv;

    if (NULL!=m_pObj->m_pAdv)
        m_pObj->m_pAdv->AddRef();

    return NOERROR;
    }

/*
 * CImpIPolyline::GetAdvise
 *
 * Purpose:
 *  Returns the IPolylineAdviseSink that was last passed to
 *  SetAdvise.
 *
 * Parameters:
 *  ppAdv           PPOLYLINEADVISESINK * in which to return
 *                  the pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CImpIPolyline::GetAdvise(PPOLYLINEADVISESINK *ppAdv)
    {
    *ppAdv=m_pObj->m_pAdv;
    return NOERROR;
    }

/*
 * CImpIPolyline::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::RectGet(LPRECT pRect)
    {
    RECT            rc;
    POINT           pt;

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_pObj->m_hWnd, &rc);
    pt.x=rc.left;
    pt.y=rc.top;
    ScreenToClient(GetParent(m_pObj->m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return NOERROR;
    }

/*
 * CImpIPolyline::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return NOERROR;
    }

/*
 * CImpIPolyline::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT            cx, cy;
    RECT            rc;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_pObj->m_hWnd, NULL, pRect->left, pRect->top
        , cx, cy, SWP_NOZORDER);

    SetRect(&rc, 0, 0, cx, cy);
    RECTTORECTS(rc, m_pObj->m_pl.rc);

    if (fNotify && NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnSizeChange();
        }

    InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);

    return NOERROR;
    }

/*
 * CImpIPolyline::SizeSet
 *
 * Purpose:
 *  Sets a new size for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT            cx, cy;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_pObj->m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
        , SWP_NOMOVE | SWP_NOZORDER);

    if (fNotify && NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnSizeChange();
        }

    InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);

    return NOERROR;
    }

/*
 * CImpIPolyline::ColorSet
 *
 * Purpose:
 *  Changes for background or line color in the Polyline
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color to use.
 *  pcrPrev         COLORREF * in whch to store the
 *                  previous color.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::ColorSet(UINT iColor, COLORREF cr
    , COLORREF *pcrPrev)
    {
    COLORREF        crRet;

    if (NULL==pcrPrev)
        return POLYLINE_E_INVALIDPOINTER;

    switch (iColor)
        {
        case POLYLINECOLOR_BACKGROUND:
            crRet=m_pObj->m_pl.rgbBackground;
            m_pObj->m_pl.rgbBackground=cr;
            break;

        case POLYLINECOLOR_LINE:
            crRet=m_pObj->m_pl.rgbLine;
            m_pObj->m_pl.rgbLine=cr;
            break;
        }

    //If the color changed, repaint
    if (crRet!=cr)
        {
        if (NULL!=m_pObj->m_pAdv)
            {
            m_pObj->m_fDirty=TRUE;
            m_pObj->m_pAdv->OnColorChange();
            }

        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        UpdateWindow(m_pObj->m_hWnd);
        }

    *pcrPrev=crRet;
    return NOERROR;
    }

/*
 * CImpIPolyline::ColorGet
 *
 * Purpose:
 *  Retrieves one of the colors currently in use by the Polyline.
 *
 * Parameters:
 *  iColor          UINT identifying the color of interest.
 *  pcr             COLORREF * in which to return the color.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::ColorGet(UINT iColor, COLORREF *pcr)
    {
    COLORREF        crRet;

    if (NULL==pcr)
        return POLYLINE_E_INVALIDPOINTER;

    crRet=(POLYLINECOLOR_BACKGROUND==iColor)
        ? m_pObj->m_pl.rgbBackground : m_pObj->m_pl.rgbLine;

    *pcr=crRet;
    return NOERROR;
    }

/*
 * CImpIPolyline::LineStyleSet
 *
 * Purpose:
 *  Changes the line style in use by the Polyline
 *
 * Parameters:
 *  iStyle          UINT style of the line to use.
 *  piPrev          UINT * in which to store the previous style.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::LineStyleSet(UINT iStyle, UINT *piPrev)
    {
    UINT            uRet;

    uRet=(UINT)m_pObj->m_pl.iLineStyle;

    if (NULL==piPrev)
        return POLYLINE_E_INVALIDPOINTER;

    //Validate the line style
    if (PS_SOLID==iStyle || PS_DASH==iStyle || PS_DOT==iStyle
        || PS_DASHDOT==iStyle || PS_DASHDOTDOT==iStyle)
        {
        m_pObj->m_pl.iLineStyle=iStyle;

        if (uRet!=(UINT)m_pObj->m_pl.iLineStyle)
            {
            if (NULL!=m_pObj->m_pAdv)
                {
                m_pObj->m_fDirty=TRUE;
                m_pObj->m_pAdv->OnLineStyleChange();
                }

            InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
            UpdateWindow(m_pObj->m_hWnd);
            }
        }

    *piPrev=uRet;
    return NOERROR;
    }

/*
 * CImpIPolyline::LineStyleGet
 *
 * Purpose:
 *  Retrieves the current line style in use in the Polyline
 *
 * Parameters:
 *  piStyle         UINT * in which to store the style.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::LineStyleGet(UINT *piStyle)
    {
    if (NULL==piStyle)
        return POLYLINE_E_INVALIDPOINTER;

    *piStyle=m_pObj->m_pl.iLineStyle;
    return NOERROR;
    }


POLYLINE.CPP

/*
 * POLYLINE.CPP
 * Polyline Component Object Chapter 5
 *
 * Implementation of the CPolyline class that we expose as a
 * component object.
 *
 * Copyright (c)1993-1997 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CPolyline:CPolyline
 * CPolyline::~CPolyline
 *
 * Constructor Parameters:
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 *  pfnDestroy      PFNDESTROYED to call when an object is
 *                  destroyed.
 *  hInst           HINSTANCE of the application we're in.
 */

CPolyline::CPolyline(LPUNKNOWN pUnkOuter, PFNDESTROYED pfnDestroy
    , HINSTANCE hInst)
    {
    m_hWnd=NULL;
    m_hInst=hInst;

    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pfnDestroy=pfnDestroy;
    m_fDirty=FALSE;

    //CHAPTER5MOD
    m_pST  =NULL;
    m_cf   =0;
    m_clsID=CLSID_Polyline5;

    m_pIStorage=NULL;
    m_pIStream =NULL;

    m_pIPolyline      =NULL;
    m_pIPersistStorage=NULL;
    m_pAdv            =NULL;
    //End CHAPTER5MOD

    return;
    }

CPolyline::~CPolyline(void)
    {
    //CHAPTER5MOD
    if (NULL!=m_pST)
        delete m_pST;

    //Free our contained interfaces
    if (NULL!=m_pIPersistStorage)
        delete m_pIPersistStorage;

    if (NULL!=m_pIStream)
        m_pIStream->Release();

    if (NULL!=m_pIStorage)
        m_pIStorage->Release();
    //End CHAPTER5MOD

    if (NULL!=m_pIPolyline)
        delete m_pIPolyline;    //Interface does not free itself.

    //Reverses the AddRef in IPolyline::SetAdvise
    if (NULL!=m_pAdv)
        m_pAdv->Release();

    return;
    }

/*
 * CPolyline::FInit
 *
 * Purpose:
 *  Performs any intiailization of a CPolyline that's prone to
 *  failure that we also use internally before exposing the
 *  object outside this DLL.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CPolyline::FInit(void)
    {
    LPUNKNOWN       pIUnknown=this;

    if (NULL!=m_pUnkOuter)
        pIUnknown=m_pUnkOuter;

    //CHAPTER5MOD
    m_pST=new CStringTable(m_hInst);

    if (!m_pST->FInit(IDS_POLYLINEMIN, IDS_POLYLINEMAX))
        return FALSE;

    m_cf=RegisterClipboardFormat(PSZ(IDS_STORAGEFORMAT));
    //End CHAPTER5MOD

    //Allocate contained interfaces.
    m_pIPolyline=new CImpIPolyline(this, pIUnknown);

    //CHAPTER5MOD
    if (NULL==m_pIPolyline)
        return FALSE;

    m_pIPersistStorage=new CImpIPersistStorage(this, pIUnknown);

    if (NULL==m_pIPersistStorage)
        return FALSE;
    //End CHAPTER5MOD

    return TRUE;
    }

/*
 * CPolyline::QueryInterface
 * CPolyline::AddRef
 * CPolyline::Release
 *
 * Purpose:
 *  IUnknown members for CPolyline object.
 */

STDMETHODIMP CPolyline::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    /*
     * The only calls we get here for IUnknown are either in a
     * non-aggregated case or when we're created in an
     * aggregation, so in either we always return our IUnknown
     * for IID_IUnknown.
     */
    if (IID_IUnknown==riid)
        *ppv=this;

    //For anything else we return contained interfaces.
    //CHAPTER5MOD
    if (IID_IPolyline5==riid)
        *ppv=m_pIPolyline;

    if (IID_IPersist==riid || IID_IPersistStorage==riid)
        *ppv=m_pIPersistStorage;
    //End CHAPTER5MOD

    //AddRef any interface we'll return.
    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return E_NOINTERFACE;
    }

STDMETHODIMP_(ULONG) CPolyline::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPolyline::Release(void)
    {
    ULONG       cRefT;

    cRefT=--m_cRef;

    if (0L==m_cRef)
        {
        if (NULL!=m_pfnDestroy)
            (*m_pfnDestroy)();

        delete this;
        }

    return cRefT;
    }

/*
 * CPolyline::RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device (MM_TEXT) or
 *  HIMETRIC to the other.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  fToDevice       BOOL TRUE to convert from HIMETRIC to device,
 *                  FALSE to convert device to HIMETRIC.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectConvertMappings(LPRECT pRect, BOOL fToDevice)
    {
    HDC      hDC;
    int      iLpx, iLpy;

    if (NULL==pRect)
        return;

    hDC=GetDC(NULL);
    iLpx=GetDeviceCaps(hDC, LOGPIXELSX);
    iLpy=GetDeviceCaps(hDC, LOGPIXELSY);
    ReleaseDC(NULL, hDC);

    if (fToDevice)
        {
        pRect->left=MulDiv(iLpx, pRect->left, HIMETRIC_PER_INCH);
        pRect->top =MulDiv(iLpy, pRect->top , HIMETRIC_PER_INCH);

        pRect->right =MulDiv(iLpx, pRect->right, HIMETRIC_PER_INCH);
        pRect->bottom=MulDiv(iLpy, pRect->bottom,HIMETRIC_PER_INCH);
        }
    else
        {
        pRect->left=MulDiv(pRect->left, HIMETRIC_PER_INCH, iLpx);
        pRect->top =MulDiv(pRect->top , HIMETRIC_PER_INCH, iLpy);

        pRect->right =MulDiv(pRect->right, HIMETRIC_PER_INCH, iLpx);
        pRect->bottom=MulDiv(pRect->bottom,HIMETRIC_PER_INCH, iLpy);
        }

    return;
    }


POLYLINE.H

/*
 * POLYLINE.H
 * Polyline Component Object Chapter 5
 *
 * Definitions and function prototypes
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _POLYLINE_H_
#define _POLYLINE_H_

#define INC_OLE2
#include <windows.h>
#include <ole2.h>
#include <bookguid.h>
//CHAPTER5MOD
#include <classlib.h>
#include <ipoly5.h>
#include "resource.h"
//End CHAPTER5MOD

//Classname
#define SZCLASSPOLYLINE             TEXT("polyline")

//CHAPTER5MOD
//Stream Name that holds the data
#define SZSTREAM                    OLESTR("CONTENTS")
//End CHAPTER5MOD

#define HIMETRIC_PER_INCH           2540

//Window extra bytes and offsets
#define CBPOLYLINEWNDEXTRA          (sizeof(LONG))
#define PLWL_STRUCTURE              0

//DLLPOLY.CPP
int PASCAL LibMain(HINSTANCE, WORD, WORD, LPSTR);

//This class factory object creates Polyline objects.

class CPolylineClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;

    public:
        CPolylineClassFactory(void);
        ~CPolylineClassFactory(void);

        //IUnknown members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP         CreateInstance(LPUNKNOWN, REFIID
                                 , PPVOID);
        STDMETHODIMP         LockServer(BOOL);
    };

typedef CPolylineClassFactory *PCPolylineClassFactory;

//POLYWIN.CPP
LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM, LPARAM);

//Type for an object-destroyed callback
typedef void (PASCAL *PFNDESTROYED)(void);

//Forward class references
class CImpIPolyline;
typedef class CImpIPolyline *PIMPIPOLYLINE;

//CHAPTER5MOD
class CImpIPersistStorage;
typedef class CImpIPersistStorage *PIMPIPERSISTSTORAGE;
//End CHAPTER5MOD

//POLYLINE.CPP
class CPolyline : public IUnknown
    {
    friend LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM
        , LPARAM);

    friend class CImpIPolyline;
    //CHAPTER5MOD
    friend class CImpIPersistStorage;
    //End CHAPTER5MOD

    protected:
        HWND            m_hWnd;
        HINSTANCE       m_hInst;

        ULONG           m_cRef;         //Object reference count
        LPUNKNOWN       m_pUnkOuter;    //Controlling Unknown
        PFNDESTROYED    m_pfnDestroy;   //Function called on closure
        BOOL            m_fDirty;       //Have we changed?
        POLYLINEDATA    m_pl;           //Our actual data

        //CHAPTER5MOD
        PCStringTable   m_pST;          //Object strings
        UINT            m_cf;           //Object clipboard format
        CLSID           m_clsID;        //Current CLSID

        //We have to hold these for IPersistStorage::Save
        LPSTORAGE       m_pIStorage;
        LPSTREAM        m_pIStream;
        //End CHAPTER5MOD

        //Contained interfaces
        PIMPIPOLYLINE           m_pIPolyline;
        //CHAPTER5MOD
        PIMPIPERSISTSTORAGE     m_pIPersistStorage;
        //End CHAPTER5MOD

        PPOLYLINEADVISESINK     m_pAdv;

    protected:
        void      PointScale(LPRECT, LPPOINTS, BOOL);
        void      Draw(HDC, BOOL, BOOL);
        void      RectConvertMappings(LPRECT, BOOL);

    public:
        CPolyline(LPUNKNOWN, PFNDESTROYED, HINSTANCE);
        ~CPolyline(void);

        BOOL      FInit(void);

        //Non-delegating object IUnknown
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
    };

typedef CPolyline *PCPolyline;

//Interface implementation contained in the Polyline.

class CImpIPolyline : public IPolyline5
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIPolyline(PCPolyline, LPUNKNOWN);
        ~CImpIPolyline(void);

        //IUnknown members.
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //CHAPTER5MOD
        //ReadFromFile and WriteToFile replaced with IPersistStorage.
        //End CHAPTER5MOD

        //Data transfer members:
        STDMETHODIMP DataSet(PPOLYLINEDATA, BOOL, BOOL);
        STDMETHODIMP DataGet(PPOLYLINEDATA);
        STDMETHODIMP DataSetMem(HGLOBAL, BOOL, BOOL, BOOL);
        STDMETHODIMP DataGetMem(HGLOBAL *);

        STDMETHODIMP RenderBitmap(HBITMAP *);
        STDMETHODIMP RenderMetafile(HMETAFILE *);
        STDMETHODIMP RenderMetafilePict(HGLOBAL *);

        //Manipulation members:
        STDMETHODIMP Init(HWND, LPRECT, DWORD, UINT);
        STDMETHODIMP New(void);
        STDMETHODIMP Undo(void);
        STDMETHODIMP Window(HWND *);

        STDMETHODIMP SetAdvise(PPOLYLINEADVISESINK);
        STDMETHODIMP GetAdvise(PPOLYLINEADVISESINK *);

        STDMETHODIMP RectGet(LPRECT);
        STDMETHODIMP SizeGet(LPRECT);
        STDMETHODIMP RectSet(LPRECT, BOOL);
        STDMETHODIMP SizeSet(LPRECT, BOOL);

        STDMETHODIMP ColorSet(UINT, COLORREF, COLORREF *);
        STDMETHODIMP ColorGet(UINT, COLORREF *);

        STDMETHODIMP LineStyleSet(UINT, UINT *);
        STDMETHODIMP LineStyleGet(UINT *);
    };

//CHAPTER5MOD

//Interface implementation contained in the Polyline.

class CImpIPersistStorage : public IPersistStorage
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown
        PSSTATE             m_psState;   //Storage state

    public:
        CImpIPersistStorage(PCPolyline, LPUNKNOWN);
        ~CImpIPersistStorage(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP InitNew(LPSTORAGE);
        STDMETHODIMP Load(LPSTORAGE);
        STDMETHODIMP Save(LPSTORAGE, BOOL);
        STDMETHODIMP SaveCompleted(LPSTORAGE);
        STDMETHODIMP HandsOffStorage(void);
    };

//End CHAPTER5MOD

#endif  //_POLYLINE_H_


POLYWIN.CPP

/*
 * POLYWIN.CPP
 * Polyline Component Object Chapter 5
 *
 * Window procedure for the polyline drawing window and support
 * functions.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * PolylineWndProc
 *
 * Purpose:
 *  Window procedure for the polyline drawing window.
 */

LRESULT APIENTRY PolylineWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCPolyline      ppl;
    PAINTSTRUCT     ps;
    HDC             hDC;
    POINTS          pt;
    RECT            rc;

    ppl=(PCPolyline)GetWindowLong(hWnd, PLWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppl=(PCPolyline)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PLWL_STRUCTURE, (LONG)ppl);

            //New repaints this window, so store the hWnd here.
            ppl->m_hWnd=hWnd;
            ppl->m_pIPolyline->New();
            break;

        case WM_PAINT:
            hDC=BeginPaint(hWnd, &ps);
            ppl->Draw(hDC, FALSE, TRUE);
            EndPaint(hWnd, &ps);
            break;

        case WM_LBUTTONDOWN:
            //Stop if we are already at the limit.
            if (CPOLYLINEPOINTS==ppl->m_pl.cPoints)
                {
                MessageBeep(0);
                break;
                }

            //Convert the points into 0-32767 range
            GetClientRect(hWnd, &rc);
            pt=MAKEPOINTS(lParam);
            ppl->PointScale(&rc, &pt, FALSE);

            ppl->m_pl.rgpt[ppl->m_pl.cPoints++]=pt;

            //Draw the lines to this new point only.
            hDC=GetDC(hWnd);
            ppl->Draw(hDC, FALSE, FALSE);
            ReleaseDC(hWnd, hDC);

            if (NULL!=ppl->m_pAdv)
                ppl->m_pAdv->OnPointChange();

            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPolyline::Draw
 *
 * Purpose:
 *  Paints the current line in the polyline window.
 *
 * Parameters:
 *  hDC             HDC to draw on, a metafile or printer DC.
 *  fMetafile       BOOL indicating if hDC is a metafile or not,
 *                  so we can avoid operations that RIP.
 *  fEntire         BOOL indicating if we should draw the entire
 *                  figure or not.
 *
 * Return Value:
 *  None
 */

void CPolyline::Draw(HDC hDC, BOOL fMetafile, BOOL fEntire)
    {
    HBRUSH          hBrush;
    HPEN            hPen;
    HGDIOBJ         hObj1, hObj2;
    UINT            i, j;
    UINT            uMM;
    POINTS          pt1, pt2;
    POINT           rgpt[CPOLYLINEPOINTS];
    RECT            rc;

    GetClientRect(m_hWnd, &rc);

    /*
     * Make a 32-bit copy of the points in the POLYLINE itself
     * so we can use DPtoLP and LPtoDP.
     */
    for (i=0; i < m_pl.cPoints; i++)
        {
        rgpt[i].x=m_pl.rgpt[i].x;
        rgpt[i].y=m_pl.rgpt[i].y;
        }

    /*
     * If the mapping mode is not MM_TEXT, convert the points to
     * whatever mapping mode in in effect before drawing.
     * This specifically supports metafiles in MM_ANISOTROPIC.
     */
    uMM=fMetafile ? MM_TEXT : GetMapMode(hDC);

    if (MM_TEXT!=uMM)
        DPtoLP(hDC, rgpt, m_pl.cPoints);

    hPen=CreatePen(m_pl.iLineStyle, 1, m_pl.rgbLine);
    hObj1=SelectObject(hDC, hPen);

    hBrush=CreateSolidBrush(m_pl.rgbBackground);
    hObj2=SelectObject(hDC, hBrush);
    SetBkColor(hDC, m_pl.rgbBackground);

    /*
     * Either draw the entire figure or just a single point.  The
     * entire figure also includes erasing the background
     * completely, since hDC may be a metafile DC.  Drawing a single
     * point just updates the figure for that new point.
     */
    if (fEntire || 0==m_pl.cPoints)
        {
        //Erase the background for bitmaps and metafiles.
        SelectObject(hDC, GetStockObject(NULL_PEN));
        Rectangle(hDC, rc.left, rc.top, rc.right+1, rc.bottom+1);
        SelectObject(hDC, hPen);

        /*
         * If we are drawing the entire figure, then loop through
         * each point drawing a line to each successive point.
         */

        for (i=0; i < m_pl.cPoints; i++)
            {
            for (j=i; j < m_pl.cPoints; j++)
                {
                pt1.x=(short)rgpt[i].x;
                pt1.y=(short)rgpt[i].y;
                pt2.x=(short)rgpt[j].x;
                pt2.y=(short)rgpt[j].y;

                PointScale(&rc, &pt1, TRUE);
                PointScale(&rc, &pt2, TRUE);
                MoveToEx(hDC, pt1.x, pt1.y, NULL);
                LineTo(hDC, pt2.x, pt2.y);
                }
            }
        }
    else
        {
        /*
         * If we are only drawing the last point, just cycle once
         * through previous points.
         */

        //Get the last point entered in the array.
        j=m_pl.cPoints-1;
        pt1.x=(short)rgpt[j].x;
        pt1.y=(short)rgpt[j].y;
        PointScale(&rc, &pt1, TRUE);

        for (i=0; i < j; i++)
            {
            pt2.x=(short)rgpt[i].x;
            pt2.y=(short)rgpt[i].y;
            PointScale(&rc, &pt2, TRUE);

            MoveToEx(hDC, pt1.x, pt1.y, NULL);
            LineTo(hDC, pt2.x, pt2.y);
            }
        }

    //If we have one point, draw a dot to indicate it's position.
    if (1==m_pl.cPoints)
        {
        pt1.x=(short)rgpt[0].x;
        pt1.y=(short)rgpt[0].y;
        PointScale(&rc, &pt1, TRUE);
        SetPixel(hDC, pt1.x, pt1.y, m_pl.rgbLine);
        }

    //Restore original points.
    if (MM_TEXT!=uMM)
        LPtoDP(hDC, rgpt, m_pl.cPoints);

    SelectObject(hDC, hObj1);
    SelectObject(hDC, hObj2);
    DeleteObject(hBrush);
    DeleteObject(hPen);
    return;
    }

/*
 * CPolyline::PointScale
 *
 * Purpose:
 *  Scales a point to or from a relative window coordinate to a
 *  0-32767 coordinate.
 *
 * Parameters:
 *  pRect           LPRECT of the window.
 *  ppt             LPPOINTS to convert
 *  fScaleToWindow  BOOL indicating direction of scaling.
 *
 * Return Value:
 *  None
 */

void CPolyline::PointScale(LPRECT pRect, LPPOINTS ppt
    , BOOL fScaleToWindow)
    {
    DWORD   cx, cy;

    //Window size
    cx=(DWORD)(pRect->right-pRect->left);
    cy=(DWORD)(pRect->bottom-pRect->top);

    //Prevent crashes
    if (0L==cx) cx=1;
    if (0L==cy) cy=1;

    //Must use DWORD to insure proper scaling.
    if (fScaleToWindow)
        {
        ppt->x=(UINT)(((DWORD)ppt->x*cx) >> 15);
        ppt->y=(UINT)(((DWORD)ppt->y*cy) >> 15);
        }
    else
        {
        ppt->x=(UINT)(((DWORD)ppt->x << 15)/cx);
        ppt->y=(UINT)(((DWORD)ppt->y << 15)/cy);
        }

    return;
    }


PRECOMP.CPP

#include "polyline.h"


SELFREG.CPP

/*
 * SELFREG.CPP
 * Server Self-Registrtation Utility, Chapter 5
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "selfreg.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    BOOL        fEXE=FALSE;
    BOOL        fUnreg=FALSE;
    LPSTR       psz;
    char        ch;

    /*
     * Walk down the command line looking for /u and /e in
     * any order.  The first non-whitespace character after
     * whitespace that is not a "/" is assumed to be the
     * beginning of the server filename.  This is all done
     * in ANSI because pszCmdLine is in ANSI.
     */

    psz=pszCmdLine;

    while (ch=*psz)
        {
        BOOL    fStop=FALSE;

        switch (ch)
            {
            case '\t':
            case '\n':
            case '\r':
            case ' ':
                //Scan for next non-whitespace and continue
                psz=PszWhiteSpaceScan(psz, TRUE);
                continue;

            case '/':
                /*
                 * Check what flag this is, then skip to next
                 * whitespace.
                 */
                ch=*(++psz);
                if ('u'==ch)
                    fUnreg=TRUE;

                if ('e'==ch)
                    fEXE=TRUE;

                psz=PszWhiteSpaceScan(psz, FALSE);
                continue;

            default:
                fStop=TRUE;
                break;
            }

        if (fStop)
            break;

        psz++;
        }

    if (0==ch)
        return 0;   //Nothing to do

    /*
     * Now launch and EXE or load a DLL and tell them to do
     * their registration or unregistartion.
     */
    if (fEXE)
        {
        char    szEXE[512];

        wsprintfA(szEXE, "%s %s", psz
            , fUnreg ? "/UNREGSERVER" : "/REGSERVER");
        WinExec(szEXE, SW_HIDE);

        MessageBoxA(NULL, fUnreg
            ? "EXE unregistration started"
            : "EXE registration started.", "SelfReg", MB_OK);
        }
    else
        {
        HINSTANCE       hMod;

        //Do this for the sake of DLLs.
        if (FAILED(CoInitialize(NULL)))
            return 0;

        hMod=LoadLibraryA(psz);

        if (hMod > (HINSTANCE)HINSTANCE_ERROR)
            {
            HRESULT (STDAPICALLTYPE *pfn)(void);
            BOOL    fRes=FALSE;

            if (fUnreg)
                {
                (FARPROC&)pfn=GetProcAddress(hMod
                    , "DllUnregisterServer");

                if (NULL!=pfn)
                    fRes=SUCCEEDED((*pfn)());

                MessageBoxA(NULL, fRes
                    ? "DLL unregistration succeeded."
                    : "DLL unregistration failed.", "SelfReg", MB_OK);
                }
            else
                {
                (FARPROC&)pfn=GetProcAddress(hMod
                    , "DllRegisterServer");

                if (NULL!=pfn)
                    fRes=SUCCEEDED((*pfn)());

                MessageBoxA(NULL , fRes
                    ? "DLL registration succeeded."
                    : "DLL registration failed.", "SelfReg", MB_OK);
                }

            CoFreeLibrary(hMod);
            CoUninitialize();
            }
        else
            {
            MessageBoxA(NULL, "LoadLibrary failed.", "SelfReg"
                , MB_OK);
            }
        }

    return 1;
    }

LPSTR PszWhiteSpaceScan(LPSTR psz, BOOL fSkip)
    {
    char        ch;
    BOOL        fWhite;

    while (ch=*psz)
        {
        fWhite=('\n'==ch || '\r'==ch
            || '\t'==ch || ' '==ch);

        //Too bad C doesn't have a logical XOR (^^) operator.
        if ((fSkip && !fWhite) || (!fSkip && fWhite))
            break;

        psz++;
        }

    return psz;
    }


SELFREG.H

/*
 * SELFREG.H
 * Server Self-Registrtation Utility, Chapter 5
 *
 * Definitions and structures.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _SELFREG_H_
#define _SELFREG_H_

#define CHAPTER5
#include <inole.h>

LPSTR PszWhiteSpaceScan(LPSTR, BOOL);

#endif //_SELFREG_H_


EKOALA3.CPP

/*
 * EKOALA3.CPP
 * Koala Object EXE Server Chapter 6
 *
 * Example object implemented in an EXE.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "ekoala3.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

//Make window handle global so other code can cause a shutdown
HWND        g_hWnd=NULL;

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    MSG         msg;
    PAPP        pApp;

    SETMESSAGEQUEUE;

    pApp=new CApp(hInst, hInstPrev, pszCmdLine, nCmdShow);

    if (NULL==pApp)
        return -1;

    if (pApp->Init())
        {
        while (GetMessage(&msg, NULL, 0,0 ))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    delete pApp;
    return msg.wParam;
    }

/*
 * KoalaWndProc
 *
 * Purpose:
 *  Standard window class procedure.
 */

LRESULT APIENTRY KoalaWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PAPP        pApp;

    pApp=(PAPP)GetWindowLong(hWnd, KOALAWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pApp=(PAPP)(((LPCREATESTRUCT)lParam)->lpCreateParams);
            SetWindowLong(hWnd, KOALAWL_STRUCTURE, (LONG)pApp);
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));

        //CHAPTER6MOD
        case WM_COMMAND:
            switch (LOWORD(wParam))
                {
                case IDM_CALLBLOCK:
                    pApp->m_fBlock=!pApp->m_fBlock;
                    CheckMenuItem(GetMenu(hWnd), IDM_CALLBLOCK
                        , pApp->m_fBlock ? MF_CHECKED : MF_UNCHECKED);
                    break;

                case IDM_CALLDELAY:
                    pApp->m_fDelay=!pApp->m_fDelay;
                    CheckMenuItem(GetMenu(hWnd), IDM_CALLDELAY
                        , pApp->m_fDelay ? MF_CHECKED : MF_UNCHECKED);
                    break;
                }
            break;

        case WM_CLOSE:
            //Don't close with object's active
            if (0==g_cObj && 0==g_cLock)
                return (DefWindowProc(hWnd, iMsg, wParam, lParam));

            break;
        //End CHAPTER6MOD

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Koala object to call when it gets destroyed.
 *  We destroy the main window if the proper conditions are met
 *  for shutdown.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;

    //No more objects and no locks, shut the app down.
    if (0L==g_cObj && 0L==g_cLock && IsWindow(g_hWnd))
        PostMessage(g_hWnd, WM_CLOSE, 0, 0L);

    return;
    }

/*
 * CApp::CApp
 * CApp::~CApp
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the Application from WinMain
 *  hInstPrev       HINSTANCE of a previous instance from WinMain
 *  pszCmdLine      LPSTR of the command line.
 *  nCmdShow        UINT specifying how to show the app window,
 *                  from WinMain.
 */

CApp::CApp(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, UINT nCmdShow)
    {
    //Initialize WinMain parameter holders.
    m_hInst     =hInst;
    m_hInstPrev =hInstPrev;
    m_pszCmdLine=pszCmdLine;
    m_nCmdShow  =nCmdShow;

    m_hWnd=NULL;
    m_dwRegCO=0;
    m_pIClassFactory=NULL;
    m_fInitialized=FALSE;

    //CHAPTER6MOD
    m_pMsgFilter=NULL;
    m_fBlock=FALSE;
    m_fDelay=FALSE;
    //End CHAPTER6MOD
    return;
    }

CApp::~CApp(void)
    {
    //CHAPTER6MOD
    if (NULL!=m_pMsgFilter)
        {
        CoRegisterMessageFilter(NULL, NULL);
        ReleaseInterface(m_pMsgFilter);
        }
    //End CHAPTER6MOD

    //Opposite of CoRegisterClassObject; class factory ref is now 1
    if (0L!=m_dwRegCO)
        CoRevokeClassObject(m_dwRegCO);

    //The last Release, which frees the class factory.
    if (NULL!=m_pIClassFactory)
        m_pIClassFactory->Release();

    if (m_fInitialized)
        CoUninitialize();

    return;
    }

/*
 * CApp::Init
 *
 * Purpose:
 *  Initializes an CApp object by registering window classes,
 *  creating the main window, and doing anything else prone to
 *  failure.  If this function fails the caller should guarantee
 *  that the destructor is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::Init(void)
    {
    WNDCLASS    wc;
    HRESULT     hr;
   #ifndef WIN32
    DWORD       dwVer;
   #endif

    //Fail if we're run outside of CoGetClassObject
    if (lstrcmpiA(m_pszCmdLine, "-Embedding")
        && lstrcmpiA(m_pszCmdLine, "/Embedding"))
        return FALSE;

   #ifndef WIN32
    dwVer=CoBuildVersion();

    if (rmm!=HIWORD(dwVer))
        return FALSE;

    //No need to check minor versions.
   #endif

    //Call CoInitialize so we can call other Co* functions
    if (FAILED(CoInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    if (!m_hInstPrev)
        {
        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = KoalaWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = CBWNDEXTRA;
        wc.hInstance      = m_hInst;
        wc.hIcon          = NULL;
        wc.hCursor        = NULL;
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        //CHAPTER6MOD
        wc.lpszMenuName   = MAKEINTRESOURCE(IDR_MENU);
        //End CHAPTER6MOD
        wc.lpszClassName  = TEXT("Koala");

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

    //CHAPTER6MOD
    m_hWnd=CreateWindow(TEXT("Koala"), TEXT("Koala Object")
        , WS_OVERLAPPEDWINDOW, 400, 35, 350, 250
        , NULL, NULL, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    ShowWindow(m_hWnd, SW_SHOW);
    UpdateWindow(m_hWnd);
    //End CHAPTER6MOD

    g_hWnd=m_hWnd;

    /*
     * Create our class factory and register it for this application
     * using CoRegisterClassObject. We are able to service more than
     * one object at a time so we use REGCLS_MULTIPLEUSE.
     */
    m_pIClassFactory=new CKoalaClassFactory();

    if (NULL==m_pIClassFactory)
        return FALSE;

    //Since we hold on to this, we should AddRef it.
    m_pIClassFactory->AddRef();

    hr=CoRegisterClassObject(CLSID_Koala, m_pIClassFactory
        , CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &m_dwRegCO);

    if (FAILED(hr))
        return FALSE;

    //CHAPTER6MOD
    /*
     * Call CoLockObjectExternal to lock and unlock to show
     * its strong lock effect through IExternalConnection.
     */
    CoLockObjectExternal(m_pIClassFactory, TRUE,  FALSE);
    CoLockObjectExternal(m_pIClassFactory, FALSE, FALSE);

    //Create and register the message filter
    m_pMsgFilter=new CMessageFilter(this);

    if (NULL!=m_pMsgFilter)
        {
        m_pMsgFilter->AddRef();

        if (FAILED(CoRegisterMessageFilter(m_pMsgFilter, NULL)))
            ReleaseInterface(m_pMsgFilter);
        }
    //End CHAPTER6MOD

    return TRUE;
    }

/*
 * CKoalaClassFactory::CKoalaClassFactory
 * CKoalaClassFactory::~CKoalaClassFactory
 *
 * Constructor Parameters:
 *  None
 */

CKoalaClassFactory::CKoalaClassFactory(void)
    {
    m_cRef=0L;
    //CHAPTER6MOD
    m_pImpIExtConn=new CImpIExternalConnection(this, this);
    //End CHAPTER6MOD
    return;
    }

CKoalaClassFactory::~CKoalaClassFactory(void)
    {
    //CHAPTER6MOD
    DeleteInterfaceImp(m_pImpIExtConn);
    //End CHAPTER6MOD
    return;
    }

/*
 * CKoalaClassFactory::QueryInterface
 * CKoalaClassFactory::AddRef
 * CKoalaClassFactory::Release
 */

STDMETHODIMP CKoalaClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    //CHAPTER6MOD
    if (IID_IExternalConnection==riid && NULL!=m_pImpIExtConn)
        *ppv=m_pImpIExtConn;
    //End CHAPTER6MOD

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CKoalaClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CKoalaClassFactory::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CKoalaClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a Koala object returning an interface pointer.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we are
 *                  being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_NOINTERFACE
 *                  if we cannot support the requested interface.
 */

STDMETHODIMP CKoalaClassFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCKoala             pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //Verify that a controlling unknown asks for IUnknown
    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    //Create the object telling us to notify us when it's gone.
    pObj=new CKoala(pUnkOuter, ObjectDestroyed);

    if (NULL==pObj)
        {
        //This starts shutdown if there are no other objects.
        g_cObj++;
        ObjectDestroyed();
        return hr;
        }

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    g_cObj++;

    /*
     * Kill the object if initial creation or Init failed. If
     * the object failed, we handle the g_cObj increment above
     * in ObjectDestroyed.
     */
    if (FAILED(hr))
        {
        delete pObj;
        ObjectDestroyed();  //Handle shutdown cases.
        }

    return hr;
    }

/*
 * CKoalaClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the serving
 *  IClassFactory object.  When the number of locks goes to
 *  zero and the number of objects is zero, we shut down the
 *  application.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CKoalaClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        {
        g_cLock--;

        /*
         * Fake an object destruction:  this centralizes
         * all the shutdown code in the ObjectDestroyed
         * function, eliminating duplicate code here.
         */
        g_cObj++;
        ObjectDestroyed();
        }

    return NOERROR;
    }

//CHAPTER6MOD
//Class factory implementation of IExternalConnection

/*
 * CImpIExternalConnection::CImpIExternalConnection
 * CImpIExternalConnection::~CImpIExternalConnection
 *
 * Parameters (Constructor):
 *  pObj            PCKoalaClassFactory of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIExternalConnection::CImpIExternalConnection
    (PCKoalaClassFactory pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;

    m_cStrong=0;
    m_cWeak=0;
    return;
    }

CImpIExternalConnection::~CImpIExternalConnection(void)
    {
    return;
    }

/*
 * CImpIExternalConnection::QueryInterface
 * CImpIExternalConnection::AddRef
 * CImpIExternalConnection::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIExternalConnection.
 */

STDMETHODIMP CImpIExternalConnection::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIExternalConnection::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIExternalConnection::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIExternalConnection::AddConnection
 *
 * Purpose:
 *  Informs the object that a strong connection has been made to it.
 *
 * Parameters:
 *  dwConn          DWORD identifying the type of connection, taken
 *                  from the EXTCONN enumeration.
 *  dwReserved      DWORD reserved.  This is used inside OLE and
 *                  should not be validated.
 *
 * Return Value:
 *  DWORD           The number of connection counts on the
 *                  object, used for debugging purposes only.
 */

STDMETHODIMP_(DWORD) CImpIExternalConnection::AddConnection
    (DWORD dwConn, DWORD dwReserved)
    {
    DWORD       dwRet;
    TCHAR       szTemp[80];

    if (EXTCONN_STRONG & dwConn)
        {
        dwRet=++m_cStrong;
        wsprintf(szTemp
            , TEXT("AddConnection cStrong=%lu"), m_cStrong);
        }

   #ifdef WIN32
    if (EXTCONN_WEAK & dwConn)
        {
        dwRet=++m_cWeak;
        wsprintf(szTemp
            , TEXT("ReleaseConnection cWeak=%lu"), m_cWeak);
        }
   #endif

   #ifndef WIN32
    ODS(szTemp);
   #else
    MessageBox(NULL, szTemp
        , TEXT("EKoala3: CKoalaClassFactory::IExternalConnection")
        , MB_OK);
   #endif

    return dwRet;
    }

/*
 * CImpIExternalConnection::ReleaseConnection
 *
 * Purpose:
 *  Informs an object that a connection has been taken away from
 *  it in which case the object may need to shut down.
 *
 * Parameters:
 *  dwConn              DWORD identifying the type of connection,
 *                      taken from the EXTCONN enumeration.
 *  dwReserved          DWORD reserved.  This is used inside OLE and
 *                      should not be validated.
 *  dwRerved            DWORD reserved
 *  fLastReleaseCloses  BOOL indicating if the last call to this
 *                      function should close the object.
 *
 * Return Value:
 *  DWORD           The number of remaining connection counts on
 *                  the object, used for debugging purposes only.
 */

STDMETHODIMP_(DWORD) CImpIExternalConnection::ReleaseConnection
    (DWORD dwConn, DWORD dwReserved, BOOL fLastReleaseCloses)
    {
    DWORD       dwRet;
    TCHAR       szTemp[80];

    if (EXTCONN_STRONG & dwConn)
        {
        /*
         * Note:  We don't need to close ourselves when the last
         * strong lock is removed; we're just implementing this
         * interface for demonstration.
         */

        dwRet=--m_cStrong;
        wsprintf(szTemp
            , TEXT("ReleaseConnection cStrong=%lu"), m_cStrong);
        }

   #ifdef WIN32
    if (EXTCONN_WEAK & dwConn)
        {
        dwRet=--m_cWeak;
        wsprintf(szTemp
            , TEXT("ReleaseConnection cWeak=%lu"), m_cWeak);
        }
   #endif

   #ifndef WIN32
    ODS(szTemp);
   #else
    MessageBox(NULL, szTemp
        , TEXT("EKoala3: CKoalaClassFactory::IExternalConnection")
        , MB_OK);
   #endif

    return dwRet;
    }

//End CHAPTER6MOD


EKOALA3.H

/*
 * EKOALA3.H
 * Koala Object EXE Server Chapter 6
 *
 * Definitions, classes, and prototypes for an application that
 * provides Koala objects to any other object user.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _EKOALA3_H_
#define _EKOALA3_H_

//Get the object definitions
#include "koala.h"

//CHAPTER6MOD
#define IDR_MENU            1
#define IDM_CALLBLOCK       100
#define IDM_CALLDELAY       101
//End CHAPTER6MOD

//EKOALA3.CPP
LRESULT APIENTRY KoalaWndProc(HWND, UINT, WPARAM, LPARAM);

//CHAPTER6MOD
class CMessageFilter;
typedef CMessageFilter *PCMessageFilter;
//End CHAPTER6MOD

class CApp
    {
    friend LRESULT APIENTRY KoalaWndProc(HWND, UINT, WPARAM, LPARAM);
    friend class CMessageFilter;

    protected:
        HINSTANCE       m_hInst;            //WinMain parameters
        HINSTANCE       m_hInstPrev;
        LPSTR           m_pszCmdLine;
        UINT            m_nCmdShow;

        HWND            m_hWnd;             //Main window handle

        BOOL            m_fInitialized;     //Did CoInitialize work?
        LPCLASSFACTORY  m_pIClassFactory;   //Our class factory
        DWORD           m_dwRegCO;          //Registration key

        //CHAPTER6MOD
        PCMessageFilter m_pMsgFilter;   //Message filter object
        BOOL            m_fBlock;
        BOOL            m_fDelay;
        //End CHAPTER6MOD

    public:
        CApp(HINSTANCE, HINSTANCE, LPSTR, UINT);
        ~CApp(void);
        BOOL Init(void);
    };

typedef CApp *PAPP;

#define CBWNDEXTRA              sizeof(PAPP)
#define KOALAWL_STRUCTURE       0

void ObjectDestroyed(void);

//CHAPTER6MOD
//MSGFILT.CPP
//Message Filter Object

class CMessageFilter : public IMessageFilter
    {
    protected:
        ULONG           m_cRef;      //Object reference count
        PAPP            m_pApp;

    public:
        CMessageFilter(PAPP);
        ~CMessageFilter(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(DWORD) HandleInComingCall(DWORD, HTASK
           #ifdef WIN32
            , DWORD, LPINTERFACEINFO);
           #else
            , DWORD, DWORD);
           #endif
        STDMETHODIMP_(DWORD) RetryRejectedCall(HTASK, DWORD, DWORD);
        STDMETHODIMP_(DWORD) MessagePending(HTASK, DWORD, DWORD);
    };
//End CHAPTER6MOD

//This class factory object creates Koala objects.

//CHAPTER6MOD
//EKOALA3.CPP
class CImpIExternalConnection;
typedef class CImpIExternalConnection *PCImpIExternalConnection;
//End CHAPTER6MOD

class CKoalaClassFactory : public IClassFactory
    {
    //CHAPTER6MOD
    friend class CImpIExternalConnection;
    //End CHAPTER6MOD

    protected:
        ULONG                       m_cRef;
        //CHAPTER6MOD
        PCImpIExternalConnection    m_pImpIExtConn;
        //End CHAPTER6MOD

    public:
        CKoalaClassFactory(void);
        ~CKoalaClassFactory(void);

        //IUnknown members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP CreateInstance(LPUNKNOWN, REFIID, PPVOID);
        STDMETHODIMP LockServer(BOOL);
    };

typedef CKoalaClassFactory *PCKoalaClassFactory;

//CHAPTER6MOD
class CImpIExternalConnection : public IExternalConnection
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCKoalaClassFactory m_pObj;      //Back pointer to the object
        LPUNKNOWN           m_pUnkOuter; //For delegation

        DWORD               m_cStrong;
        DWORD               m_cWeak;

    public:
        CImpIExternalConnection(PCKoalaClassFactory, LPUNKNOWN);
        ~CImpIExternalConnection(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(DWORD) AddConnection(DWORD, DWORD);
        STDMETHODIMP_(DWORD) ReleaseConnection(DWORD, DWORD, BOOL);
    };

//End CHAPTER6MOD

#endif //_EKOALA3_H_


KOALA.CPP

/*
 * KOALA.CPP
 * Koala Object for EXE Servers, Chapter 6
 *
 * Implementation of an object with IExternalConnection.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "koala.h"

/*
 * CKoala::CKoala
 * CKoala::~CKoala
 *
 * Parameters (Constructor):
 *  pUnkOuter       LPUNKNOWN of a controlling unknown.
 *  pfnDestroy      PFNDESTROYED to call when an object
 *                  is destroyed.
 */

CKoala::CKoala(LPUNKNOWN pUnkOuter, PFNDESTROYED pfnDestroy)
    {
    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pfnDestroy=pfnDestroy;

    //CHAPTER6MOD
    m_cStrong=0;
    m_cWeak=0;

    m_pImpIPersist=NULL;
    //End CHAPTER6MOD
    return;
    }

CKoala::~CKoala(void)
    {
    //CHAPTER6MOD
    DeleteInterfaceImp(m_pImpIPersist);
    //End CHAPTER6MOD
    return;
    }

/*
 * CKoala::Init
 *
 * Purpose:
 *  Performs any intiailization of a CKoala that's prone to failure
 *  that we also use internally before exposing the object outside.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CKoala::Init(void)
    {
    //CHAPTER6MOD
    IUnknown   *pUnk=this;

    if (NULL!=m_pUnkOuter)
        pUnk=m_pUnkOuter;

    m_pImpIPersist=new CImpIPersist(this, pUnk);

    //No failure if m_pImpIPersist is NULL, QueryInterface will fail

    return TRUE;
    //End CHAPTER6MOD
    }

/*
 * CKoala::QueryInterface
 * CKoala::AddRef
 * CKoala::Release
 *
 * Purpose:
 *  IUnknown members for CKoala object.
 */

STDMETHODIMP CKoala::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    //CHAPTER6MOD
    if (IID_IUnknown==riid || IID_IExternalConnection==riid)
        *ppv=this;

    if (IID_IPersist==riid && NULL!=m_pImpIPersist)
        *ppv=m_pImpIPersist;
    //End CHAPTER6MOD

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CKoala::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CKoala::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0;
    }

//CHAPTER6MOD
/*
 * CKoala::AddConnection
 *
 * Purpose:
 *  Informs the object that a strong connection has been made to it.
 *
 * Parameters:
 *  dwConn          DWORD identifying the type of connection, taken
 *                  from the EXTCONN enumeration.
 *  dwReserved      DWORD reserved.  This is used inside OLE and
 *                  should not be validated.
 *
 * Return Value:
 *  DWORD           The number of connection counts on the
 *                  object, used for debugging purposes only.
 */

STDMETHODIMP_(DWORD) CKoala::AddConnection(DWORD dwConn
    , DWORD dwReserved)
    {
    DWORD       dwRet;
    TCHAR       szTemp[80];

    if (EXTCONN_STRONG & dwConn)
        {
        dwRet=++m_cStrong;
        wsprintf(szTemp
            , TEXT("AddConnection cStrong=%lu"), m_cStrong);
        }

   #ifdef WIN32
    if (EXTCONN_WEAK & dwConn)
        {
        dwRet=++m_cWeak;
        wsprintf(szTemp
            , TEXT("ReleaseConnection cWeak=%lu"), m_cWeak);
        }
   #endif

   #ifndef WIN32
    ODS(szTemp);
   #else
    MessageBox(NULL, szTemp
        , TEXT("EKoala3: CKoala::IExternalConnection"), MB_OK);
   #endif

    return dwRet;
    }

/*
 * CKoala::ReleaseConnection
 *
 * Purpose:
 *  Informs an object that a connection has been taken away from
 *  it in which case the object may need to shut down.
 *
 * Parameters:
 *  dwConn              DWORD identifying the type of connection,
 *                      taken from the EXTCONN enumeration.
 *  dwReserved          DWORD reserved.  This is used inside OLE and
 *                      should not be validated.
 *  dwRerved            DWORD reserved
 *  fLastReleaseCloses  BOOL indicating if the last call to this
 *                      function should close the object.
 *
 * Return Value:
 *  DWORD           The number of remaining connection counts on
 *                  the object, used for debugging purposes only.
 */

STDMETHODIMP_(DWORD) CKoala::ReleaseConnection(DWORD dwConn
    , DWORD dwReserved, BOOL fLastReleaseCloses)
    {
    DWORD       dwRet;
    TCHAR       szTemp[80];

    if (EXTCONN_STRONG & dwConn)
        {
        /*
         * Note:  We don't need to close ourselves when the last
         * strong lock is removed; we're just implementing this
         * interface for demonstration.
         */

        dwRet=--m_cStrong;
        wsprintf(szTemp
            , TEXT("ReleaseConnection cStrong=%lu"), m_cStrong);
        }

   #ifdef WIN32
    if (EXTCONN_WEAK & dwConn)
        {
        dwRet=--m_cWeak;
        wsprintf(szTemp
            , TEXT("ReleaseConnection cWeak=%lu"), m_cWeak);
        }
   #endif

   #ifndef WIN32
    ODS(szTemp);
   #else
    MessageBox(NULL, szTemp
        , TEXT("EKoala3: CKoala::IExternalConnection"), MB_OK);
   #endif

    return dwRet;
    }

///IPersist implementation

/*
 * CImpIPersist:CImpIPersist
 * CImpIPersist::~CImpIPersist
 *
 * Constructor Parameters:
 *  pObj            PCKoala pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersist::CImpIPersist(PCKoala pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPersist::~CImpIPersist(void)
    {
    return;
    }

/*
 * CImpIPersist::QueryInterface
 * CImpIPersist::AddRef
 * CImpIPersist::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIPersist.
 */

STDMETHODIMP CImpIPersist::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersist::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersist::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersist::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the object represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 */

STDMETHODIMP CImpIPersist::GetClassID(LPCLSID pClsID)
    {
    *pClsID=CLSID_Koala;
    return NOERROR;
    }

//End CHAPTER6MOD


KOALA.H

/*
 * KOALA.H
 * Koala Object for EXE Servers, Chapter 6
 *
 * Classes that implement an object with IExternalConnection.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _KOALA_H_
#define _KOALA_H_

#define CHAPTER6
#include <inole.h>

//KOALA.CPP

//CHAPTER6MOD
class CImpIPersist;
typedef CImpIPersist *PCImpIPersist;

class CKoala : public IExternalConnection
//End CHAPTER6MOD
    {
    protected:
        ULONG           m_cRef;         //Object reference count
        LPUNKNOWN       m_pUnkOuter;    //Controlling unknown
        PFNDESTROYED    m_pfnDestroy;   //To call on closure

        //CHAPTER6MOD
        DWORD           m_cStrong;      //Strong lock count
        DWORD           m_cWeak;        //Weak lock count

        PCImpIPersist   m_pImpIPersist; //Another interface
        //End CHAPTER6MOD

    public:
        CKoala(LPUNKNOWN, PFNDESTROYED);
        ~CKoala(void);

        BOOL Init(void);

        //Non-delegating object IUnknown
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //CHAPTER6MOD
        STDMETHODIMP_(DWORD) AddConnection(DWORD, DWORD);
        STDMETHODIMP_(DWORD) ReleaseConnection(DWORD, DWORD, BOOL);
        //End CHAPTER6MOD
    };

typedef CKoala *PCKoala;

//CHAPTER6MOD

class CImpIPersist : public IPersist
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        PCKoala         m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIPersist(PCKoala, LPUNKNOWN);
        ~CImpIPersist(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);
    };

//End CHAPTER6MOD

#endif //_KOALA_H_


MSGFILT.CPP

/*
 * MSGFILT.CPP
 * Koala Object for EXE Servers, Chapter 6
 *
 * Implementation of a message filter object to block or delay
 * calls.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "ekoala3.h"

/*
 * CMessageFilter::CMessageFilter
 * CMessageFilter::~CMessageFilter
 *
 * Parameters (Constructor):
 *  pApp            PAPP of the application we're in
 */

CMessageFilter::CMessageFilter(PAPP pApp)
    {
    m_cRef=0;
    m_pApp=pApp;
    return;
    }

CMessageFilter::~CMessageFilter(void)
    {
    return;
    }

/*
 * CMessageFilter::QueryInterface
 * CMessageFilter::AddRef
 * CMessageFilter::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CMessageFilter.
 */

STDMETHODIMP CMessageFilter::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IMessageFilter==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CMessageFilter::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CMessageFilter::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CMessageFilter::HandleInComingCall
 *
 * Purpose:
 *  Requests that the container call OleSave for the object that
 *  lives here.  Typically this happens on server shutdown.
 *
 * Parameters:
 *  dwCallType      DWORD indicating the type of call received, from
 *                  the CALLTYPE enumeration
 *  hTaskCaller     HTASK of the caller
 *  dwTickCount     DWORD elapsed tick count since the outgoing call
 *                  was made if dwCallType is not CALLTYPE_TOPLEVEL.
 *                  Ignored for other call types.
 *  pInterfaceInfo  LPINTERFACEINFO providing information about the
 *                  call.  Can be NULL.
 *
 * Return Value:
 *  DWORD           One of SERVERCALL_ISHANDLED (if the call might
 *                  be handled), SERVERCALL_REJECTED (call cannot
 *                  be handled), or SERVERCALL_RETRYLATER (try
 *                  again sometime).
 */

STDMETHODIMP_(DWORD) CMessageFilter::HandleInComingCall
    (DWORD dwCallType, HTASK htaskCaller, DWORD dwTickCount
#ifdef WIN32
    , LPINTERFACEINFO pInterfaceInfo)
#else
    , DWORD dwReserved)
#endif
    {
    if (m_pApp->m_fBlock)
        return SERVERCALL_REJECTED;

    if (m_pApp->m_fDelay)
        return SERVERCALL_RETRYLATER;

    return SERVERCALL_ISHANDLED;
    }

/*
 * CMessageFilter::RetryRejectedCall
 * CMessageFilter::MessagePending
 *
 * Not implemented:  no outgoing calls from this process.
 */

STDMETHODIMP_(DWORD) CMessageFilter::RetryRejectedCall
    (HTASK htaskCallee, DWORD dwTickCount, DWORD dwRejectType)
    {
    return 0;
    }

STDMETHODIMP_(DWORD) CMessageFilter::MessagePending
    (HTASK htaskCallee, DWORD dwTickCount, DWORD dwPendingType)
    {
    return PENDINGMSG_WAITDEFPROCESS;
    }


EKOALA4.CPP

/*
 * EKOALA4.CPP
 * Koala Object EXE Server with Custom Interface Chapter 6
 *
 * Example object with a custom interface implemented in an EXE.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "ekoala4.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

//Make window handle global so other code can cause a shutdown
HWND        g_hWnd=NULL;

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    MSG         msg;
    PAPP        pApp;

    SETMESSAGEQUEUE;

    pApp=new CApp(hInst, hInstPrev, pszCmdLine, nCmdShow);

    if (NULL==pApp)
        return -1;

    if (pApp->Init())
        {
        while (GetMessage(&msg, NULL, 0,0 ))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    delete pApp;
    return msg.wParam;
    }

/*
 * KoalaWndProc
 *
 * Purpose:
 *  Standard window class procedure.
 */

LRESULT APIENTRY KoalaWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PAPP        pApp;

    pApp=(PAPP)GetWindowLong(hWnd, KOALAWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pApp=(PAPP)(((LPCREATESTRUCT)lParam)->lpCreateParams);
            SetWindowLong(hWnd, KOALAWL_STRUCTURE, (LONG)pApp);
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Koala object to call when it gets destroyed.
 *  We destroy the main window if the proper conditions are met
 *  for shutdown.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;

    //No more objects and no locks, shut the app down.
    if (0L==g_cObj && 0L==g_cLock && IsWindow(g_hWnd))
        PostMessage(g_hWnd, WM_CLOSE, 0, 0L);

    return;
    }

/*
 * CApp::CApp
 * CApp::~CApp
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the Application from WinMain
 *  hInstPrev       HINSTANCE of a previous instance from WinMain
 *  pszCmdLine      LPSTR of the command line.
 *  nCmdShow        UINT specifying how to show the app window,
 *                  from WinMain.
 */

CApp::CApp(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, UINT nCmdShow)
    {
    //Initialize WinMain parameter holders.
    m_hInst     =hInst;
    m_hInstPrev =hInstPrev;
    m_pszCmdLine=pszCmdLine;
    m_nCmdShow  =nCmdShow;

    m_hWnd=NULL;
    m_dwRegCO=0;
    m_pIClassFactory=NULL;
    m_fInitialized=FALSE;
    return;
    }

CApp::~CApp(void)
    {
    //Opposite of CoRegisterClassObject; class factory ref is now 1
    if (0L!=m_dwRegCO)
        CoRevokeClassObject(m_dwRegCO);

    //The last Release, which frees the class factory.
    if (NULL!=m_pIClassFactory)
        m_pIClassFactory->Release();

    if (m_fInitialized)
        CoUninitialize();

    return;
    }

/*
 * CApp::Init
 *
 * Purpose:
 *  Initializes an CApp object by registering window classes,
 *  creating the main window, and doing anything else prone to
 *  failure.  If this function fails the caller should guarantee
 *  that the destructor is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::Init(void)
    {
    WNDCLASS    wc;
    HRESULT     hr;
   #ifndef WIN32
    DWORD       dwVer;
   #endif

    //Fail if we're run outside of CoGetClassObject
    if (lstrcmpiA(m_pszCmdLine, "-Embedding")
        && lstrcmpiA(m_pszCmdLine, "/Embedding"))
        return FALSE;

   #ifndef WIN32
    dwVer=CoBuildVersion();

    if (rmm!=HIWORD(dwVer))
        return FALSE;

    //No need to check minor versions.
   #endif

    //Call CoInitialize so we can call other Co* functions
    if (FAILED(CoInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    if (!m_hInstPrev)
        {
        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = KoalaWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = CBWNDEXTRA;
        wc.hInstance      = m_hInst;
        wc.hIcon          = NULL;
        wc.hCursor        = NULL;
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName   = NULL;
        wc.lpszClassName  = TEXT("Koala");

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

    m_hWnd=CreateWindow(TEXT("Koala"), TEXT("Koala")
        , WS_OVERLAPPEDWINDOW, 35, 35, 350, 250
        , NULL, NULL, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    g_hWnd=m_hWnd;

    /*
     * Create our class factory and register it for this application
     * using CoRegisterClassObject. We are able to service more than
     * one object at a time so we use REGCLS_MULTIPLEUSE.
     */
    m_pIClassFactory=new CKoalaClassFactory();

    if (NULL==m_pIClassFactory)
        return FALSE;

    //Since we hold on to this, we should AddRef it.
    m_pIClassFactory->AddRef();

    hr=CoRegisterClassObject(CLSID_Koala, m_pIClassFactory
        , CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &m_dwRegCO);

    if (FAILED(hr))
        return FALSE;

    return TRUE;
    }

/*
 * CKoalaClassFactory::CKoalaClassFactory
 * CKoalaClassFactory::~CKoalaClassFactory
 *
 * Constructor Parameters:
 *  None
 */

CKoalaClassFactory::CKoalaClassFactory(void)
    {
    m_cRef=0L;
    return;
    }

CKoalaClassFactory::~CKoalaClassFactory(void)
    {
    return;
    }

/*
 * CKoalaClassFactory::QueryInterface
 * CKoalaClassFactory::AddRef
 * CKoalaClassFactory::Release
 */

STDMETHODIMP CKoalaClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CKoalaClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CKoalaClassFactory::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CKoalaClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a Koala object returning an interface pointer.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we are
 *                  being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_NOINTERFACE
 *                  if we cannot support the requested interface.
 */

STDMETHODIMP CKoalaClassFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCKoala             pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //Verify that a controlling unknown asks for IUnknown
    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    //Create the object telling us to notify us when it's gone.
    pObj=new CKoala(pUnkOuter, ObjectDestroyed);

    if (NULL==pObj)
        {
        //This starts shutdown if there are no other objects.
        g_cObj++;
        ObjectDestroyed();
        return hr;
        }

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    g_cObj++;

    /*
     * Kill the object if initial creation or Init failed. If
     * the object failed, we handle the g_cObj increment above
     * in ObjectDestroyed.
     */
    if (FAILED(hr))
        {
        delete pObj;
        ObjectDestroyed();  //Handle shutdown cases.
        }

    return hr;
    }

/*
 * CKoalaClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the serving
 *  IClassFactory object.  When the number of locks goes to
 *  zero and the number of objects is zero, we shut down the
 *  application.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CKoalaClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        {
        g_cLock--;

        /*
         * Fake an object destruction:  this centralizes
         * all the shutdown code in the ObjectDestroyed
         * function, eliminating duplicate code here.
         */
        g_cObj++;
        ObjectDestroyed();
        }

    return NOERROR;
    }


EKOALA4.H

/*
 * EKOALA4.H
 * Koala Object EXE Server with Custom Interface Chapter 6
 *
 * Definitions, classes, and prototypes for an application that
 * provides Koala objects to any other object user.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _EKOALA4_H_
#define _EKOALA4_H_

//Get the object definitions
#include "koala.h"

//EKOALA4.CPP
LRESULT APIENTRY KoalaWndProc(HWND, UINT, WPARAM, LPARAM);

class CApp
    {
    friend LRESULT APIENTRY KoalaWndProc(HWND, UINT, WPARAM, LPARAM);

    protected:
        HINSTANCE       m_hInst;            //WinMain parameters
        HINSTANCE       m_hInstPrev;
        LPSTR           m_pszCmdLine;
        UINT            m_nCmdShow;

        HWND            m_hWnd;             //Main window handle

        BOOL            m_fInitialized;     //Did CoInitialize work?
        LPCLASSFACTORY  m_pIClassFactory;   //Our class factory
        DWORD           m_dwRegCO;          //Registration key

    public:
        CApp(HINSTANCE, HINSTANCE, LPSTR, UINT);
        ~CApp(void);
        BOOL Init(void);
    };

typedef CApp *PAPP;

#define CBWNDEXTRA              sizeof(PAPP)
#define KOALAWL_STRUCTURE       0

void ObjectDestroyed(void);

//This class factory object creates Koala objects.

class CKoalaClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;

    public:
        CKoalaClassFactory(void);
        ~CKoalaClassFactory(void);

        //IUnknown members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP CreateInstance(LPUNKNOWN, REFIID, PPVOID);
        STDMETHODIMP LockServer(BOOL);
    };

typedef CKoalaClassFactory *PCKoalaClassFactory;

#endif //_EKOALA4_H_


KOALA.CPP

/*
 * KOALA.CPP
 * Koala Object with Custom Interface, Chapter 6
 *
 * Implementation of the CKoala object with a custom interface
 * to demonstrate local/remote transparency.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "koala.h"

/*
 * CKoala::CKoala
 * CKoala::~CKoala
 *
 * Parameters (Constructor):
 *  pUnkOuter       LPUNKNOWN of a controlling unknown.
 *  pfnDestroy      PFNDESTROYED to call when an object
 *                  is destroyed.
 */

CKoala::CKoala(LPUNKNOWN pUnkOuter, PFNDESTROYED pfnDestroy)
    {
    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pfnDestroy=pfnDestroy;

    //CHAPTER6MOD
    m_pImpIAnimal=NULL;
    m_pImpIKoala=NULL;

    m_fJustAte=FALSE;
    m_cSleepAfterEat=0;
    //End CHAPTER6MOD

    return;
    }

CKoala::~CKoala(void)
    {
    //CHAPTER6MOD
    DeleteInterfaceImp(m_pImpIKoala);
    DeleteInterfaceImp(m_pImpIAnimal);
    //End CHAPTER6MOD
    return;
    }

/*
 * CKoala::Init
 *
 * Purpose:
 *  Performs any intiailization of a CKoala that's prone to failure
 *  that we also use internally before exposing the object outside.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CKoala::Init(void)
    {
    //CHAPTER6MOD
    IUnknown   *pUnkOuter=m_pUnkOuter;

    if (NULL==pUnkOuter)
        pUnkOuter=this;

    m_pImpIAnimal=new CImpIAnimal(this, pUnkOuter);

    if (NULL==m_pImpIAnimal)
        return FALSE;

    m_pImpIKoala=new CImpIKoala(this, pUnkOuter);

    if (NULL==m_pImpIKoala)
        return FALSE;
    //End CHAPTER6MOD

    return TRUE;
    }

/*
 * CKoala::QueryInterface
 * CKoala::AddRef
 * CKoala::Release
 *
 * Purpose:
 *  IUnknown members for CKoala object.
 */

STDMETHODIMP CKoala::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    /*
     * The only calls for IUnknown are either in a nonaggregated
     * case or when created in an aggregation, so in either case
     * always return our IUnknown for IID_IUnknown.
     */
    if (IID_IUnknown==riid)
        *ppv=this;

    //CHAPTER6MOD
    if (IID_IAnimal==riid)
        *ppv=m_pImpIAnimal;

    if (IID_IKoala==riid)
        *ppv=m_pImpIKoala;
    //End CHAPTER6MOD

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CKoala::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CKoala::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0;
    }

//CHAPTER6MOD

//IAnimal implementation

/*
 * CImpIAnimal::CImpIAnimal
 * CImpIAnimal::~CImpIAnimal
 *
 * Constructor Parameters:
 *  pObj            PCKoala of the object containing us.
 *  pUnkOuter       LPUNKNOWN to which we blindly delegate
 *                  all IUnknown calls.
 */

CImpIAnimal::CImpIAnimal(PCKoala pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIAnimal::~CImpIAnimal(void)
    {
    return;
    }

/*
 * CImpIAnimal::QueryInterface
 * CImpIAnimal::AddRef
 * CImpIAnimal::Release
 *
 * Purpose:
 *  Delegating IUnknown members for interface implementation.
 */

STDMETHODIMP CImpIAnimal::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIAnimal::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIAnimal::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIAnimal::Eat
 *
 * Purpose:
 *  Instructs the animal to eat something, returning what the animal
 *  actually ate which usually goes against recommendation (which is
 *  true for the human animal, too).
 *
 * Parameters:
 *  pszFoodRecommended  LPTSTR describing the food that the animal
 *                      should eat.
 *  pszFoodEaten        LPTSTR describing the food the animal actually
 *                      ate, which may not, of course, be the same as
 *                      what it should eat.
 *  cchEaten            short containing the lenght of pszFoodEaten.
 *
 * Return Value:
 *  HRESULT             NOERROR if food is eaten, S_FALSE if not.
 */

STDMETHODIMP CImpIAnimal::Eat(LPTSTR pszFoodRecommended
    , LPTSTR pszFoodEaten, short cchEaten)
    {
    //Koalas aren't don't care what you tell them, they eat one thing
   #ifdef WIN32ANSI
    lstrcpyn(pszFoodEaten, "Eucalyptus Leaves", cchEaten);
   #else
    _tcsncpy(pszFoodEaten, TEXT("Eucalyptus Leaves"), cchEaten);
   #endif
    m_pObj->m_fJustAte=TRUE;
    return NOERROR;
    }

/*
 * CImpIAnimal::Sleep
 *
 * Purpose:
 *  Instructs the animal to sleep for a while.
 *
 * Parameters:
 *  pcMinutes      short * (in-out) containing the number of
 *                 minutes to sleep on entry, then number of
 *                 minutes actually slept on exit
 *
 * Return Value:
 *  HRESULT        NOERROR if sleep taken, S_FALSE if not, where
 *                 *pcMinutes should be zero.
 */

STDMETHODIMP CImpIAnimal::Sleep(short *pcMinutes)
    {
    /*
     * Koalas welcome the opportunity to sleep, and will probably
     * sleep longer than you tell them, so return more time than
     * we were told based on IKoala::SleepAfterEating.
     */
    *pcMinutes=*pcMinutes+m_pObj->m_cSleepAfterEat;
    m_pObj->m_fJustAte=FALSE;     //Probably want to eat again
    return NOERROR;
    }

/*
 * CImpIAnimal::Procreate
 *
 * Purpose:
 *  Instructs the animal to procreate.  On entry, the number of
 *  offstring is unknown, so that's an out parameter.
 *
 * Parameters:
 *  pcOffspring     short * (out) in which to store the number
 *                  of new offspring.
 *
 * Return Value:
 *  HRESULT         NOERROR if offspring created, S_FALSE if not
 *                  where *pcOffspring should be zero.
 */

STDMETHODIMP CImpIAnimal::Procreate(short *pcOffspring)
    {
    DWORD       dw;
    SCODE       sc=S_FALSE;

    /*
     * Koalas like to reproduce when they damn well please and
     * not when someone else tells them, so we'll just check
     * the current time, and if it's appropriate, we'll reproduce.
     * (Simulation:  use GetTickCount to get the system time, then
     * divide by 100 to filter out resolution concerns, then see if
     * the result is an even multiple of 10).
     *
     * Also, Koalas only produce one offspring at a time.  If
     * this was a rabbit, we might get anywhere from 2-20 offspring.
     */

    dw=GetTickCount()/100;

    if ((dw/10)*10==dw)
        {
        *pcOffspring=1;
        sc=S_OK;
        }
    else
        *pcOffspring=0;

    return ResultFromScode(sc);
    }

/*
 * CImpIAnimal::WhatKindOfAnimal
 *
 * Purpose:
 *  Returns the IID of the specific animal interface that describes
 *  the type of animal this really is (a much more complex
 *  classification scheme might have IAnimal::WhatGenus and
 *  IGenus::WhatSpecies, etc., but we're just being simple here).
 *
 * Parameters:
 *  pIID            IID * in which to store the specific
 *                  animal IID.
 *
 * Return Value:
 *  HRESULT         NOERROR if the animal type is known,
 *                  S_FALSE if not with *pIID set to IID_NULL.
 */

STDMETHODIMP CImpIAnimal::WhatKindOfAnimal(IID *pIID)
    {
    *pIID=IID_IKoala;
    return NOERROR;
    }

//IKoala implementation

/*
 * CImpIKoala::CImpIKoala
 * CImpIKoala::~CImpIKoala
 *
 * Constructor Parameters:
 *  pObj            PCKoala of the object containing us.
 *  pUnkOuter       LPUNKNOWN to which we blindly delegate
 *                  all IUnknown calls.
 */

CImpIKoala::CImpIKoala(PCKoala pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIKoala::~CImpIKoala(void)
    {
    return;
    }

/*
 * CImpIKoala::QueryInterface
 * CImpIKoala::AddRef
 * CImpIKoala::Release
 *
 * Purpose:
 *  Delegating IUnknown members for interface implementation.
 */

STDMETHODIMP CImpIKoala::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIKoala::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIKoala::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIKoala::ClimbEucalyptusTree
 *
 * Purpose:
 *  Tells the Koala to go climb a tree, which means eating, which
 *  a koala is probably more than happy to do.
 *
 * Parameters:
 *  iTree           short identifying the tree to climb.
 *
 * Return Value:
 *  HRESULT         NOERROR if tree climbed, S_FALSE if not.
 */

STDMETHODIMP CImpIKoala::ClimbEucalyptusTree(short iTree)
    {
    //Nothing to demonstrate...
    return NOERROR;
    }

/*
 * CImpIKoala::PouchOpensDown
 *
 * Purpose:
 *  Do-nothing function to demonstrate a void argument list.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT             NOERROR
 */

STDMETHODIMP CImpIKoala::PouchOpensDown(void)
    {
    //Nothing to demonstrate...
    return NOERROR;
    }

/*
 * CImpIKoala::SleepAfterEating
 *
 * Purpose:
 *  Tells the Koala to sleep an additional number of minutes after
 *  eating.
 *
 * Parameters:
 *  cMinutes        short * (in) containing the number of
 *                  extra minutes to sleep after eating.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIKoala::SleepAfterEating(short cMinutes)
    {
    m_pObj->m_cSleepAfterEat=cMinutes;
    return NOERROR;
    }

//End CHAPTER6MOD


KOALA.H

/*
 * KOALA.H
 * Koala Object with Custom Interface, Chapter 6
 *
 * Classes that implement the CKoala object with a custom interface
 * to demonstrate local/remote transparency.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _KOALA_H_
#define _KOALA_H_

#define CHAPTER6
#include <inole.h>
#include <ianimal.h>
#include <ikoala.h>

//KOALA.CPP

//CHAPTER6MOD
class CImpIAnimal;
typedef CImpIAnimal *PCImpIAnimal;

class CImpIKoala;
typedef CImpIKoala *PCImpIKoala;
//End CHAPTER6MOD

class CKoala : public IUnknown
    {
    //CHAPTER6MOD
    friend class CImpIAnimal;
    friend class CImpIKoala;
    //End CHAPTER6MOD

    protected:
        ULONG           m_cRef;         //Object reference count
        LPUNKNOWN       m_pUnkOuter;    //Controlling unknown

        PFNDESTROYED    m_pfnDestroy;   //To call on closure

        //CHAPTER6MOD
        PCImpIAnimal    m_pImpIAnimal;  //Our interfaces
        PCImpIKoala     m_pImpIKoala;

        BOOL            m_fJustAte;
        short           m_cSleepAfterEat;
        //CHAPTER6MOD

    public:
        CKoala(LPUNKNOWN, PFNDESTROYED);
        ~CKoala(void);

        BOOL Init(void);

        //Non-delegating object IUnknown
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
    };

typedef CKoala *PCKoala;

//CHAPTER6MOD
//IAnimal interface implementation

class CImpIAnimal : public IAnimal
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        PCKoala         m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIAnimal(PCKoala, LPUNKNOWN);
        ~CImpIAnimal(void);

        //IUnknown members
        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IAnimal members
        STDMETHODIMP Eat(LPTSTR, LPTSTR, short);
        STDMETHODIMP Sleep(short *);
        STDMETHODIMP Procreate(short *);
        STDMETHODIMP WhatKindOfAnimal(IID *);
    };

//IKoala interface implementation

class CImpIKoala : public IKoala
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        PCKoala         m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIKoala(PCKoala, LPUNKNOWN);
        ~CImpIKoala(void);

        //IUnknown members
        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IKoala members
        STDMETHODIMP ClimbEucalyptusTree(short);
        STDMETHODIMP PouchOpensDown(void);
        STDMETHODIMP SleepAfterEating(short);
    };
//End CHAPTER6MOD

#endif //_KOALA_H_


EKOALA5.CPP

/*
 * EKOALA5.CPP
 * Koala Object EXE Server with Custom Marshaling Chapter 6
 *
 * Example object with custom marshaling
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "ekoala5.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

//Make window handle global so other code can cause a shutdown
HWND        g_hWnd=NULL;

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    MSG         msg;
    PAPP        pApp;

    SETMESSAGEQUEUE;

    pApp=new CApp(hInst, hInstPrev, pszCmdLine, nCmdShow);

    if (NULL==pApp)
        return -1;

    if (pApp->Init())
        {
        while (GetMessage(&msg, NULL, 0,0 ))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    delete pApp;
    return msg.wParam;
    }

/*
 * KoalaWndProc
 *
 * Purpose:
 *  Standard window class procedure.
 */

LRESULT APIENTRY KoalaWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PAPP        pApp;

    pApp=(PAPP)GetWindowLong(hWnd, KOALAWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pApp=(PAPP)(((LPCREATESTRUCT)lParam)->lpCreateParams);
            SetWindowLong(hWnd, KOALAWL_STRUCTURE, (LONG)pApp);
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Koala object to call when it gets destroyed.
 *  We destroy the main window if the proper conditions are met
 *  for shutdown.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;

    //No more objects and no locks, shut the app down.
    if (0L==g_cObj && 0L==g_cLock && IsWindow(g_hWnd))
        PostMessage(g_hWnd, WM_CLOSE, 0, 0L);

    return;
    }

/*
 * CApp::CApp
 * CApp::~CApp
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the Application from WinMain
 *  hInstPrev       HINSTANCE of a previous instance from WinMain
 *  pszCmdLine      LPSTR of the command line.
 *  nCmdShow        UINT specifying how to show the app window,
 *                  from WinMain.
 */

CApp::CApp(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, UINT nCmdShow)
    {
    //Initialize WinMain parameter holders.
    m_hInst     =hInst;
    m_hInstPrev =hInstPrev;
    m_pszCmdLine=pszCmdLine;
    m_nCmdShow  =nCmdShow;

    m_hWnd=NULL;
    m_dwRegCO=0;
    m_pIClassFactory=NULL;
    m_fInitialized=FALSE;
    return;
    }

CApp::~CApp(void)
    {
    //Opposite of CoRegisterClassObject; class factory ref is now 1
    if (0L!=m_dwRegCO)
        CoRevokeClassObject(m_dwRegCO);

    //The last Release, which frees the class factory.
    if (NULL!=m_pIClassFactory)
        m_pIClassFactory->Release();

    if (m_fInitialized)
        CoUninitialize();

    return;
    }

/*
 * CApp::Init
 *
 * Purpose:
 *  Initializes an CApp object by registering window classes,
 *  creating the main window, and doing anything else prone to
 *  failure.  If this function fails the caller should guarantee
 *  that the destructor is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::Init(void)
    {
    WNDCLASS    wc;
    HRESULT     hr;
   #ifndef WIN32
    DWORD       dwVer;
   #endif

    //Fail if we're run outside of CoGetClassObject
    if (lstrcmpiA(m_pszCmdLine, "-Embedding")
        && lstrcmpiA(m_pszCmdLine, "/Embedding"))
        return FALSE;

   #ifndef WIN32
    dwVer=CoBuildVersion();

    if (rmm!=HIWORD(dwVer))
        return FALSE;

    //No need to check minor versions.
   #endif

    //Call CoInitialize so we can call other Co* functions
    if (FAILED(CoInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    if (!m_hInstPrev)
        {
        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = KoalaWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = CBWNDEXTRA;
        wc.hInstance      = m_hInst;
        wc.hIcon          = NULL;
        wc.hCursor        = NULL;
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName   = NULL;
        wc.lpszClassName  = TEXT("Koala");

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

        //Window created for each object.
        wc.lpfnWndProc    = ObjectWndProc;
        wc.cbWndExtra     = CBOBJECTWNDEXTRA;
        wc.lpszClassName  = TEXT("KoalaObject");

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

    //Create main window
    m_hWnd=CreateWindow(TEXT("Koala"), TEXT("Koala")
        , WS_OVERLAPPEDWINDOW, 35, 35, 350, 250
        , NULL, NULL, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    g_hWnd=m_hWnd;

    /*
     * Create our class factory and register it for this application
     * using CoRegisterClassObject. We are able to service more than
     * one object at a time so we use REGCLS_MULTIPLEUSE.
     */
    m_pIClassFactory=new CKoalaClassFactory(m_hInst, m_hWnd);

    if (NULL==m_pIClassFactory)
        return FALSE;

    //Since we hold on to this, we should AddRef it.
    m_pIClassFactory->AddRef();

    hr=CoRegisterClassObject(CLSID_Koala, m_pIClassFactory
        , CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &m_dwRegCO);

    if (FAILED(hr))
        return FALSE;

    return TRUE;
    }

/*
 * CKoalaClassFactory::CKoalaClassFactory
 * CKoalaClassFactory::~CKoalaClassFactory
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE to pass on to Koalas.
 *  hWnd            HWND to pass on to Koalas.
 */

CKoalaClassFactory::CKoalaClassFactory(HINSTANCE hInst, HWND hWnd)
    {
    m_cRef=0L;
    m_hInst=hInst;
    m_hWnd=hWnd;
    return;
    }

CKoalaClassFactory::~CKoalaClassFactory(void)
    {
    return;
    }

/*
 * CKoalaClassFactory::QueryInterface
 * CKoalaClassFactory::AddRef
 * CKoalaClassFactory::Release
 */

STDMETHODIMP CKoalaClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CKoalaClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CKoalaClassFactory::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CKoalaClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a Koala object returning an interface pointer.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we are
 *                  being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_NOINTERFACE
 *                  if we cannot support the requested interface.
 */

STDMETHODIMP CKoalaClassFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCKoala             pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //Verify that a controlling unknown asks for IUnknown
    if (NULL!=pUnkOuter)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    //Create the object telling us to notify us when it's gone.
    pObj=new CKoala(ObjectDestroyed);

    if (NULL==pObj)
        {
        //This starts shutdown if there are no other objects.
        g_cObj++;
        ObjectDestroyed();
        return hr;
        }

    if (pObj->Init(m_hInst, m_hWnd))
        hr=pObj->QueryInterface(riid, ppvObj);

    g_cObj++;

    /*
     * Kill the object if initial creation or Init failed. If
     * the object failed, we handle the g_cObj increment above
     * in ObjectDestroyed.
     */
    if (FAILED(hr))
        {
        delete pObj;
        ObjectDestroyed();  //Handle shutdown cases.
        }

    return hr;
    }

/*
 * CKoalaClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the serving
 *  IClassFactory object.  When the number of locks goes to
 *  zero and the number of objects is zero, we shut down the
 *  application.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CKoalaClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        {
        g_cLock--;

        /*
         * Fake an object destruction:  this centralizes
         * all the shutdown code in the ObjectDestroyed
         * function, eliminating duplicate code here.
         */
        g_cObj++;
        ObjectDestroyed();
        }

    return NOERROR;
    }


EKOALA5.H

/*
 * EKOALA5.H
 * Koala Object EXE Server with Custom Marsaling Chapter 6
 *
 * Definitions, classes, and prototypes for an application that
 * provides Koala objects to any other object user.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _EKOALA5_H_
#define _EKOALA5_H_

//Get the object definitions
#include "koala.h"

//EKOALA5.CPP
LRESULT APIENTRY KoalaWndProc(HWND, UINT, WPARAM, LPARAM);

class CApp
    {
    friend LRESULT APIENTRY KoalaWndProc(HWND, UINT, WPARAM, LPARAM);

    protected:
        HINSTANCE       m_hInst;            //WinMain parameters
        HINSTANCE       m_hInstPrev;
        LPSTR           m_pszCmdLine;
        UINT            m_nCmdShow;

        HWND            m_hWnd;             //Main window handle

        BOOL            m_fInitialized;     //Did CoInitialize work?
        LPCLASSFACTORY  m_pIClassFactory;   //Our class factory
        DWORD           m_dwRegCO;          //Registration key

    public:
        CApp(HINSTANCE, HINSTANCE, LPSTR, UINT);
        ~CApp(void);
        BOOL Init(void);
    };

typedef CApp *PAPP;

#define CBWNDEXTRA              sizeof(PAPP)
#define KOALAWL_STRUCTURE       0

void ObjectDestroyed(void);

//This class factory object creates Koala objects.

class CKoalaClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;
        HINSTANCE       m_hInst;        //To give to Koalas
        HWND            m_hWnd;         //To give to Koalas

    public:
        CKoalaClassFactory(HINSTANCE, HWND);
        ~CKoalaClassFactory(void);

        //IUnknown members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP CreateInstance(LPUNKNOWN, REFIID, PPVOID);
        STDMETHODIMP LockServer(BOOL);
    };

typedef CKoalaClassFactory *PCKoalaClassFactory;

#endif //_EKOALA5_H_


KOALA.CPP

/*
 * KOALA.CPP
 * Koala Object with Custom Marshaling, Chapter 6
 *
 * Implementation of the CKoala object with a custom interface
 * to demonstrate local/remote transparency.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "koala.h"

/*
 * ObjectWndProc
 *
 * Purpose:
 *  Standard window class procedure for each object.
 */

LRESULT APIENTRY ObjectWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCKoala     pKoala;

    pKoala=(PCKoala)GetWindowLong(hWnd, OBJECTWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pKoala=(PCKoala)(((LPCREATESTRUCT)lParam)->lpCreateParams);
            SetWindowLong(hWnd, OBJECTWL_STRUCTURE, (LONG)pKoala);
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));

        case WM_COMMAND:
            //Dispatch the call to the right object
            return pKoala->HandleCall(LOWORD(wParam), lParam);

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * CKoala::CKoala
 * CKoala::~CKoala
 *
 * Parameters (Constructor):
 *  pfnDestroy      PFNDESTROYED to call when an object
 *                  is destroyed.
 */

CKoala::CKoala(PFNDESTROYED pfnDestroy)
    {
    m_cRef=0;
    m_pfnDestroy=pfnDestroy;

    m_fJustAte=FALSE;
    m_cSleepAfterEat=0;
    return;
    }

CKoala::~CKoala(void)
    {
    if (NULL!=m_hWnd)
        DestroyWindow(m_hWnd);

    return;
    }

/*
 * CKoala::Init
 *
 * Purpose:
 *  Performs any intiailization of a CKoala that's prone to failure
 *  that we also use internally before exposing the object outside.
 *
 * Parameters:
 *  hInst           HINSTANCE of the application.
 *  hWndParent      HWND in which to create our window
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CKoala::Init(HINSTANCE hInst, HWND hWndParent)
    {
    //Create our window that receives marshaling calls
    m_hWnd=CreateWindow(TEXT("KoalaObject"), TEXT("KoalaObject")
        , WS_CHILD, 35, 35, 35, 25, hWndParent, NULL
        , hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    return TRUE;
    }

/*
 * CKoala::HandleCall
 *
 * Purpose:
 *  Handles messages sent via custom marshaling from the client
 *  side proxy.
 *
 * Parameters:
 *  iMsg            UINT identifying the call made.
 *  lParam          LPARAM with extra information.
 *
 * Return Value:
 *  DWORD           Value to return to the proxy which it uses
 *                  in whatever way it must to return a value to
 *                  the client.
 */

DWORD CKoala::HandleCall(UINT iMsg, LPARAM lParam)
    {
    DWORD       dw;
    short       iRet=0;

    /*
     * You'll notice that all the important server-side
     * implementation of the object exists here instead of
     * separate interface implementations.  Everything
     * else is contained in the client-side proxy, who knows
     * when there's no point at all in calling a function
     * on the server side (such as with some of the IKoala
     * functions that don't do anything.
     */

    switch (iMsg)
        {
        case MSG_RELEASE:          //Last IUnknown::Release
            /*
             * There is only one reference count from
             * IClassFactory::CreateInstance, which the proxy
             * will own.  The proxy sends this message when
             * it detects the last Release from the client.
             * Otherwise it doesn't forward AddRef/Release calls.
             */
            Release();
            break;

        case MSG_EAT:
            m_fJustAte=TRUE;
            break;

        case MSG_SLEEP:            //IAnimal::Sleep
            //Client's in-parameter in LOWORD(lParam)
            iRet=LOWORD(lParam)+m_cSleepAfterEat;
            m_fJustAte=FALSE;     //Probably want to eat again
            break;

        case MSG_PROCREATE:        //IAnimal::Procreate
            dw=GetTickCount()/100;

            iRet=((dw/10)*10==dw) ? 1 : 0;
            break;

        case MSG_SLEEPAFTEREATING: //IKoala::SleepAfterEating
            m_cSleepAfterEat=LOWORD(lParam);
            break;

        default:
            break;
        }

    return iRet;
    }

/*
 * CKoala::QueryInterface
 * CKoala::AddRef
 * CKoala::Release
 *
 * Purpose:
 *  IUnknown members for CKoala object.
 */

STDMETHODIMP CKoala::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IMarshal==riid
        || IID_IAnimal==riid || IID_IKoala==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CKoala::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CKoala::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0;
    }

/*
 * CKoala::GetUnmarshalClass
 *
 * Purpose:
 *  Determines the class of object to be used to create an
 *  uninitalized proxy in the unmarshaling process.
 *
 * Parameters:
 *  riid            REFIID of the interface to be marshaled.
 *  pv              LPVOID to the interface to be marshaled.
 *  dwCtx           DWORD specifying the relation of the processes
 *                  between which the marshaling is occuring, from the
 *                  MSHCTX enumeration.
 *  pvCtx           LPVOID Reserved for future MSHCTX values.
 *  dwFlags         DWORD specifying why marshaling is taking place.
 *  pClsID          LPCLSID in which to store the proxy CLSID.
 */

STDMETHODIMP CKoala::GetUnmarshalClass(REFIID riid
    , LPVOID pv, DWORD dwCtx, LPVOID pvCtx, DWORD dwFlags
    , LPCLSID pClsID)
    {
   #ifdef WIN32
    /*
     * If the context is on a different machine we cannot use
     * our custom marshaling based on SendMessage.
     */
    if (dwCtx & MSHCTX_DIFFERENTMACHINE)
        return ResultFromScode(E_FAIL);
   #endif

    //Same proxy for all interfaces.
    *pClsID=CLSID_KoalaProxy;
    return NOERROR;
    }

/*
 * CKoala::GetMarshalSizeMax
 *
 * Purpose:
 *  Returns the upper memory bound needed to write data into a stream
 *  for IMarshal::MarshalInterface.
 *
 * Parameters:
 *  riid            REFIID of the interface to be marshaled.
 *  pv              LPVOID of the interface to be marshaled.
 *  dwDestCtx       DWORD with the destination context from MSHCTX.
 *  pvDestCtx       LPVOID reserved for future MSHCTX flags.
 *  dwFlags         DWORD specifying why marshaling is taking place.
 *  pdwSize         LPDWORD in which the size is returned.
 */

STDMETHODIMP CKoala::GetMarshalSizeMax(REFIID riid, LPVOID pv
    , DWORD dwDestCtx, LPVOID pvDestCtx, DWORD dwFlags
    , LPDWORD pdwSize)
    {
   #ifdef WIN32
    if (dwDestCtx & MSHCTX_DIFFERENTMACHINE)
        return ResultFromScode(E_FAIL);
   #endif

    *pdwSize=sizeof(KOALAMARSHAL);
    return NOERROR;
    }

/*
 * CKoala::MarshalInterface
 *
 * Purpose:
 *  Stores a marshaling packet in a stream for use by a client-side
 *  proxy.
 *
 * Parameters:
 *  pStm            LPSTREAM into which to marshal the interface.
 *  riid            REFIID of the interface to be marshaled.
 *  pv              LPVOID of the interface to be marshaled.
 *  dwDestCtx       DWORD with the destination context from MSHCTX.
 *  pvDestCtx       LPVOID reserved for future MSHCTX flags.
 *  dwFlags         DWORD specifying why marshaling is taking place.
 */

STDMETHODIMP CKoala::MarshalInterface(LPSTREAM pstm
    , REFIID riid, LPVOID pv, DWORD dwDestCtx, LPVOID pvDestCtx
    , DWORD dwFlags)
    {
    KOALAMARSHAL        km;

   #ifdef WIN32
    if (dwDestCtx & MSHCTX_DIFFERENTMACHINE)
        return ResultFromScode(E_FAIL);
   #endif

    //Proxy only needs to know where to send messages
    km.hWnd=m_hWnd;

    //This is for the client who will call Release when needed
    AddRef();

    //Write the marshaling packet to the stream
    return pstm->Write((void *)&km, sizeof(KOALAMARSHAL), NULL);
    }

/*
 * CKoala::UnmarshalInterface
 *
 * Purpose:
 *  Initializes a newly created proxy the marshaling packet in
 *  the stream created in the server-side implementation of
 *  MarshalInterface.  This is the primary member of this interface
 *  used on the client side proxy.
 *
 * Parameters:
 *  pStm            LPSTREAM to the stream containing marshal
 *                  data.
 *  riid            REFIID of the interface to be marshaled.
 *  pv              LPVOID of the interface to be marshaled.
 */

STDMETHODIMP CKoala::UnmarshalInterface(LPSTREAM pstm
    , REFIID riid, LPVOID *pv)
    {
    //No need to implement on server side
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CKoala::ReleaseMarshalData
 *
 * Purpose:
 *  Destroy a marshaled data packet, client-side only.
 *
 * Parameters:
 *  pStm            LPSTREAM containing the data to release.
 */

STDMETHODIMP CKoala::ReleaseMarshalData(LPSTREAM pstm)
    {
    //Client-side function
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CKoala::Disconnect
 *
 * Purpose:
 *  Instructs an object with custom marshaling that it's being
 *  disconnected.
 *
 * Parameters:
 *  dwReserved      DWORD reserved.
 */

STDMETHODIMP CKoala::DisconnectObject(DWORD dwReserved)
    {
    /*
     * This is generated from within CoDisconnectObject, which
     * our server doesn't call itself.  This would give the object
     * a chance to close its connection and cleanup before going
     * away since the proxy will no longer be calling it.
     * This should also be used to block any additional calls
     * made after this point.
     */

    return NOERROR;
    }


KOALA.H

/*
 * KOALA.H
 * Koala Object with Custom Marshaling, Chapter 6
 *
 * Classes that implement the CKoala object with a custom interface
 * to demonstrate local/remote transparency.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _KOALA_H_
#define _KOALA_H_

#define CHAPTER6
#include <inole.h>

/*
 * Marshaling packet data structure as used by our IMarshal.
 * This is duplicated in ..\koalaprx\koala.h.
 */
typedef struct
    {
    HWND        hWnd;       //Message window
    } KOALAMARSHAL, *PKOALAMARSHAL;

//Intertask messages, duplicated in ..\koalaprx\koala.h.
#define MSG_RELEASE             1000
#define MSG_EAT                 1001
#define MSG_SLEEP               1002
#define MSG_PROCREATE           1003
#define MSG_SLEEPAFTEREATING    1004

//KOALA.CPP

LRESULT APIENTRY ObjectWndProc(HWND, UINT, WPARAM, LPARAM);

class CKoala : public IMarshal
    {
    friend LRESULT APIENTRY KoalaWndProc(HWND, UINT, WPARAM, LPARAM);

    protected:
        ULONG           m_cRef;         //Object reference count
        PFNDESTROYED    m_pfnDestroy;   //To call on closure

        HWND            m_hWnd;         //For marshaling

        BOOL            m_fJustAte;
        short           m_cSleepAfterEat;

    public:
        CKoala(PFNDESTROYED);
        ~CKoala(void);

        BOOL    Init(HINSTANCE, HWND);
        DWORD   HandleCall(UINT, LPARAM);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetUnmarshalClass(REFIID, LPVOID, DWORD
            , LPVOID, DWORD, LPCLSID);
        STDMETHODIMP GetMarshalSizeMax(REFIID, LPVOID, DWORD
            , LPVOID, DWORD, LPDWORD);
        STDMETHODIMP MarshalInterface(LPSTREAM, REFIID, LPVOID
            , DWORD, LPVOID, DWORD);
        STDMETHODIMP UnmarshalInterface(LPSTREAM, REFIID, LPVOID *);
        STDMETHODIMP ReleaseMarshalData(LPSTREAM);
        STDMETHODIMP DisconnectObject(DWORD);
    };

typedef CKoala *PCKoala;

#define CBOBJECTWNDEXTRA        sizeof(PCKoala)
#define OBJECTWL_STRUCTURE      0

#endif //_KOALA_H_


IANIMAL.IDL

/*
 * IANIMAL.IDL
 *
 * Definition of the IAnimal interface for the MIDL compiler.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

[uuid(0002114a-0000-0000-c000-000000000046),
    object
]
interface IAnimal : IUnknown
    {
    import "unknwn.idl";

    HRESULT Eat([in] LPTSTR pszFoodRecommended
        , [in, out] LPTSTR pszFoodEaten, [in] short cchEaten);
    HRESULT Sleep([in, out] short *pcMinutes);
    HRESULT Procreate([out] short *pcOffspring);
    HRESULT WhatKindOfAnimal([out] IID *pIID);
    }


LIBMAIN.CPP

/*
 * LIBMAIN.CPP
 * IAnimal Interface DLL Chapter 6
 *
 * Entry point code
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include <windows.h>

/*
 * LibMain(32)
 *
 * Purpose:
 *  Entry point for Win32 DLLs
 */

BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    if (DLL_PROCESS_DETACH==ulReason)
        {
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    return TRUE;
    }


IKOALA.IDL

/*
 * IKOALA.IDL
 *
 * Definition of the IKoala interface for the MIDL compiler.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

[uuid(0002114b-0000-0000-c000-000000000046),
    object
]
interface IKoala : IUnknown
    {
    import "unknwn.idl";

    HRESULT ClimbEucalyptusTree([in] short iTree);
    HRESULT PouchOpensDown(void);
    HRESULT SleepAfterEating([in] short cMinutes);
    }


LIBMAIN.CPP

/*
 * LIBMAIN.CPP
 * IKoala Interface DLL Chapter 6
 *
 * Entry point code
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include <windows.h>

/*
 * LibMain(32)
 *
 * Purpose:
 *  Entry point for Win32 DLLs
 */

BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    if (DLL_PROCESS_DETACH==ulReason)
        {
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    return TRUE;
    }


IMARSHAL.CPP

/*
 * IMARSHAL.CPP
 * Koala Object with Custom Marshaling, Chapter 6
 *
 * IMarshal interface implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "koala.h"

/*
 * CImpIMarshal::CImpIMarshal
 * CImpIMarshal::~CImpIMarshal
 *
 * Parameters (Constructor):
 *  pObj            PCKoala of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIMarshal::CImpIMarshal(PCKoala pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIMarshal::~CImpIMarshal(void)
    {
    return;
    }

/*
 * CImpIMarshal::QueryInterface
 * CImpIMarshal::AddRef
 * CImpIMarshal::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIMarshal.
 */

STDMETHODIMP CImpIMarshal::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIMarshal::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIMarshal::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIMarshal::GetUnmarshalClass
 * CImpIMarshal::GetMarshalSizeMax
 * CImpIMarshal::MarshalInterface
 * CImpIMarshal::ReleaseMarshalData
 * CImpIMarshal::Disconnect
 *
 * Functions not implemented in a client-side custom marshaling
 * proxy.
 */

STDMETHODIMP CImpIMarshal::GetUnmarshalClass(REFIID riid
    , LPVOID pv, DWORD dwCtx, LPVOID pvCtx, DWORD dwFlags
    , LPCLSID pClsID)
    {
    return ResultFromScode(E_NOTIMPL);
    }

STDMETHODIMP CImpIMarshal::GetMarshalSizeMax(REFIID riid, LPVOID pv
    , DWORD dwDestCtx, LPVOID pvDestCtx, DWORD dwFlags
    , LPDWORD pdwSize)
    {
    return ResultFromScode(E_NOTIMPL);
    }

STDMETHODIMP CImpIMarshal::MarshalInterface(LPSTREAM pstm
    , REFIID riid, LPVOID pv, DWORD dwDestCtx, LPVOID pvDestCtx
    , DWORD dwFlags)
    {
    return ResultFromScode(E_NOTIMPL);
    }

STDMETHODIMP CImpIMarshal::DisconnectObject(DWORD dwReserved)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIMarshal::UnmarshalInterface
 *
 * Purpose:
 *  Initializes a newly created proxy the marshaling packet in
 *  the stream created in the server-side implementation of
 *  MarshalInterface.  This is the primary member of this interface
 *  used on the client side proxy.
 *
 * Parameters:
 *  pStm            LPSTREAM to the stream containing marshal
 *                  data.
 *  riid            REFIID of the interface to be marshaled.
 *  ppv             LPVOID * in which to return the pointer.
 */

STDMETHODIMP CImpIMarshal::UnmarshalInterface(LPSTREAM pstm
    , REFIID riid, LPVOID *ppv)
    {
    KOALAMARSHAL    km;

    pstm->Read((void *)&km, sizeof(KOALAMARSHAL), NULL);
    m_pObj->m_hWndLocal=km.hWnd;

    //Get the pointer to return to the client
    return QueryInterface(riid, ppv);
    }

/*
 * CImpIMarshal::ReleaseMarshalData
 *
 * Purpose:
 *  Destroy a marshaled data packet.
 *
 * Parameters:
 *  pStm            LPSTREAM containing the data to release.
 */

STDMETHODIMP CImpIMarshal::ReleaseMarshalData(LPSTREAM pstm)
    {
    //Nothing for us to do.
    return NOERROR;
    }


KOALA.CPP

/*
 * KOALA.CPP
 * Koala Object with Custom Marshaling, Chapter 6
 *
 * Implementation of the CKoala object with a custom interface
 * to demonstrate local/remote transparency.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "koala.h"

/*
 * CKoala::CKoala
 * CKoala::~CKoala
 *
 * Parameters (Constructor):
 *  pUnkOuter       LPUNKNOWN of a controlling unknown.
 *  pfnDestroy      PFNDESTROYED to call when an object
 *                  is destroyed.
 */

CKoala::CKoala(LPUNKNOWN pUnkOuter, PFNDESTROYED pfnDestroy)
    {
    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pfnDestroy=pfnDestroy;

    m_pImpIAnimal=NULL;
    m_pImpIKoala=NULL;
    m_pImpIMarshal=NULL;

    m_fJustAte=FALSE;
    m_cSleepAfterEat=0;

    return;
    }

CKoala::~CKoala(void)
    {
    DeleteInterfaceImp(m_pImpIMarshal);
    DeleteInterfaceImp(m_pImpIKoala);
    DeleteInterfaceImp(m_pImpIAnimal);
    return;
    }

/*
 * CKoala::Init
 *
 * Purpose:
 *  Performs any intiailization of a CKoala that's prone to failure
 *  that we also use internally before exposing the object outside.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CKoala::Init(void)
    {
    IUnknown   *pUnkOuter=m_pUnkOuter;

    if (NULL==pUnkOuter)
        pUnkOuter=this;

    m_pImpIAnimal=new CImpIAnimal(this, pUnkOuter);

    if (NULL==m_pImpIAnimal)
        return FALSE;

    m_pImpIKoala=new CImpIKoala(this, pUnkOuter);

    if (NULL==m_pImpIKoala)
        return FALSE;

    m_pImpIMarshal=new CImpIMarshal(this, pUnkOuter);

    if (NULL==m_pImpIMarshal)
        return FALSE;

    return TRUE;
    }

/*
 * CKoala::CallLocal
 *
 * Purpose:
 *  Sends or posts a message to the local object.
 *
 * Parameters:
 *  iMsg            UINT identifying the function to call
 *  lParam          LPARAM containing extra information
 *  fAsync          BOOL indicating if this is a Post (TRUE) or
 *                  a send (FALSE).
 *
 * Return Value:
 *  DWORD           Return value from the function or an HRESULT
 *                  on failure.
 */

DWORD CKoala::CallLocal(UINT iMsg, LPARAM lParam, BOOL fAsync)
    {
    DWORD   dwRet=0;

    if (fAsync)
        PostMessage(m_hWndLocal, WM_COMMAND, (WPARAM)iMsg, lParam);
    else
        {
        dwRet=SendMessage(m_hWndLocal, WM_COMMAND, (WPARAM)iMsg
            , lParam);
        }

    return dwRet;
    }

/*
 * CKoala::QueryInterface
 * CKoala::AddRef
 * CKoala::Release
 *
 * Purpose:
 *  IUnknown members for CKoala object.
 */

STDMETHODIMP CKoala::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    /*
     * The only calls for IUnknown are either in a nonaggregated
     * case or when created in an aggregation, so in either case
     * always return our IUnknown for IID_IUnknown.
     */
    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IAnimal==riid)
        *ppv=m_pImpIAnimal;

    if (IID_IKoala==riid)
        *ppv=m_pImpIKoala;

    if (IID_IMarshal==riid)
        *ppv=m_pImpIMarshal;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CKoala::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CKoala::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    /*
     * If this is the last Release, then we have to tell
     * the server to free its object too.  This is an async
     * call as we don't need to hang around for it to quit.
     */
    CallLocal(MSG_RELEASE, 0, TRUE);

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0;
    }

//Proxy-side IAnimal implementation

/*
 * CImpIAnimal::CImpIAnimal
 * CImpIAnimal::~CImpIAnimal
 *
 * Constructor Parameters:
 *  pObj            PCKoala of the object containing us.
 *  pUnkOuter       LPUNKNOWN to which we blindly delegate
 *                  all IUnknown calls.
 */

CImpIAnimal::CImpIAnimal(PCKoala pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIAnimal::~CImpIAnimal(void)
    {
    return;
    }

/*
 * CImpIAnimal::QueryInterface
 * CImpIAnimal::AddRef
 * CImpIAnimal::Release
 *
 * Purpose:
 *  Delegating IUnknown members for interface implementation.
 */

STDMETHODIMP CImpIAnimal::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIAnimal::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIAnimal::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIAnimal::Eat
 *
 * Purpose:
 *  Instructs the animal to eat something, returning what the animal
 *  actually ate which usually goes against recommendation (which is
 *  true for the human animal, too).
 *
 * Parameters:
 *  pszFoodRecommended  LPTSTR describing the food that the animal
 *                      should eat.
 *  pszFoodEaten        LPTSTR describing the food the animal actually
 *                      ate, which may not, of course, be the same as
 *                      what it should eat.
 *  cchEaten            short containing the lenght of pszFoodEaten.
 *
 * Return Value:
 *  HRESULT             NOERROR if food is eaten, S_FALSE if not.
 */

STDMETHODIMP CImpIAnimal::Eat(LPTSTR pszFoodRecommended
    , LPTSTR pszFoodEaten, short cchEaten)
    {
    /*
     * Koalas aren't don't care what you tell them, they eat one
     * thing.  We can handle the return string for the local
     * object, but we still need to tell it that this was
     * called.
     */

    _tcsncpy(pszFoodEaten, TEXT("Eucalyptus Leaves"), cchEaten);
    m_pObj->CallLocal(MSG_EAT, 0L, FALSE);
    return NOERROR;
    }

/*
 * CImpIAnimal::Sleep
 *
 * Purpose:
 *  Instructs the animal to sleep for a while.
 *
 * Parameters:
 *  pcMinutes      short * (in-out) containing the number of
 *                 minutes to sleep on entry, then number of
 *                 minutes actually slept on exit
 *
 * Return Value:
 *  HRESULT        NOERROR if sleep taken, S_FALSE if not, where
 *                 *pcMinutes should be zero.
 */

STDMETHODIMP CImpIAnimal::Sleep(short *pcMinutes)
    {
    DWORD       dwRet;

    //Pass the client's value
    dwRet=m_pObj->CallLocal(MSG_SLEEP, (LPARAM)*pcMinutes, FALSE);

    if (FAILED((HRESULT)dwRet))
        return (HRESULT)dwRet;

    //Store the return value in the client's variable.
    *pcMinutes=LOWORD(dwRet);
    return NOERROR;
    }

/*
 * CImpIAnimal::Procreate
 *
 * Purpose:
 *  Instructs the animal to procreate.  On entry, the number of
 *  offstring is unknown, so that's an out parameter.
 *
 * Parameters:
 *  pcOffspring     short * (out) in which to store the number
 *                  of new offspring.
 *
 * Return Value:
 *  HRESULT         NOERROR if offspring created, S_FALSE if not
 *                  where *pcOffspring should be zero.
 */

STDMETHODIMP CImpIAnimal::Procreate(short *pcOffspring)
    {
    DWORD       dwRet;

    dwRet=m_pObj->CallLocal(MSG_PROCREATE, 0, FALSE);

    if (FAILED((HRESULT)dwRet))
        return (HRESULT)dwRet;

    *pcOffspring=(short)LOWORD(dwRet);
    return ResultFromScode(0==dwRet ? S_FALSE : S_OK);
    }

/*
 * CImpIAnimal::WhatKindOfAnimal
 *
 * Purpose:
 *  Returns the IID of the specific animal interface that describes
 *  the type of animal this really is (a much more complex
 *  classification scheme might have IAnimal::WhatGenus and
 *  IGenus::WhatSpecies, etc., but we're just being simple here).
 *
 * Parameters:
 *  pIID            IID * in which to store the specific
 *                  animal IID.
 *
 * Return Value:
 *  HRESULT         NOERROR if the animal type is known,
 *                  S_FALSE if not with *pIID set to IID_NULL.
 */

STDMETHODIMP CImpIAnimal::WhatKindOfAnimal(IID *pIID)
    {
    //No need to ask the local object for something we know
    *pIID=IID_IKoala;
    return NOERROR;
    }

//IKoala implementation

/*
 * CImpIKoala::CImpIKoala
 * CImpIKoala::~CImpIKoala
 *
 * Constructor Parameters:
 *  pObj            PCKoala of the object containing us.
 *  pUnkOuter       LPUNKNOWN to which we blindly delegate
 *                  all IUnknown calls.
 */

CImpIKoala::CImpIKoala(PCKoala pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIKoala::~CImpIKoala(void)
    {
    return;
    }

/*
 * CImpIKoala::QueryInterface
 * CImpIKoala::AddRef
 * CImpIKoala::Release
 *
 * Purpose:
 *  Delegating IUnknown members for interface implementation.
 */

STDMETHODIMP CImpIKoala::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIKoala::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIKoala::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIKoala::ClimbEucalyptusTree
 *
 * Purpose:
 *  Tells the Koala to go climb a tree, which means eating, which
 *  a koala is probably more than happy to do.
 *
 * Parameters:
 *  iTree           short identifying the tree to climb.
 *
 * Return Value:
 *  HRESULT         NOERROR if tree climbed, S_FALSE if not.
 */

STDMETHODIMP CImpIKoala::ClimbEucalyptusTree(short iTree)
    {
    //We know the server doesn't need this
    return NOERROR;
    }

/*
 * CImpIKoala::PouchOpensDown
 *
 * Purpose:
 *  Do-nothing function to demonstrate a void argument list.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT             NOERROR
 */

STDMETHODIMP CImpIKoala::PouchOpensDown(void)
    {
    //We know the server doesn't need this
    return NOERROR;
    }

/*
 * CImpIKoala::SleepAfterEating
 *
 * Purpose:
 *  Tells the Koala to sleep an additional number of minutes after
 *  eating.
 *
 * Parameters:
 *  cMinutes        short * (in) containing the number of
 *                  extra minutes to sleep after eating.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIKoala::SleepAfterEating(short cMinutes)
    {
    DWORD   dwRet;

    dwRet=m_pObj->CallLocal(MSG_SLEEPAFTEREATING
        , (LPARAM)cMinutes, TRUE);

    if (FAILED((HRESULT)dwRet))
        return (HRESULT)dwRet;

    return NOERROR;
    }


KOALA.H

/*
 * KOALA.H
 * Koala Custom Marshaling Proxy Chapter 6
 *
 * Classes that implement the CKoala object exposed from this
 * proxy server that communicates with a local Koala through
 * custom marshaling.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _KOALA_H_
#define _KOALA_H_

#define CHAPTER6
#include <inole.h>
#ifdef WIN32
#include <ianimal.h>
#include <ikoala.h>
#else
//Non-MIDL generated versions
#include <ianim16.h>
#include <ikoala16.h>
#endif

/*
 * Marshaling packet data structure as used by our IMarshal.
 * This is duplicated in ..\ekoala5\koala.h.
 */
typedef struct
    {
    HWND        hWnd;       //Message window
    } KOALAMARSHAL, *PKOALAMARSHAL;

//Intertask messages, duplicated in ..\ekoala5\koala.h.
#define MSG_RELEASE             1000
#define MSG_EAT                 1001
#define MSG_SLEEP               1002
#define MSG_PROCREATE           1003
#define MSG_SLEEPAFTEREATING    1004

//KOALA.CPP
class CImpIAnimal;
typedef CImpIAnimal *PCImpIAnimal;

class CImpIKoala;
typedef CImpIKoala *PCImpIKoala;

class CImpIMarshal;
typedef CImpIMarshal *PCImpIMarshal;

class CKoala : public IUnknown
    {
    friend class CImpIAnimal;
    friend class CImpIKoala;
    friend class CImpIMarshal;

    protected:
        ULONG           m_cRef;         //Object reference count
        LPUNKNOWN       m_pUnkOuter;    //Controlling unknown

        PFNDESTROYED    m_pfnDestroy;   //To call on closure

        PCImpIAnimal    m_pImpIAnimal;  //Our interfaces
        PCImpIKoala     m_pImpIKoala;
        PCImpIMarshal   m_pImpIMarshal;

        BOOL            m_fJustAte;
        short           m_cSleepAfterEat;

        HWND            m_hWndLocal;    //Local object

    public:
        CKoala(LPUNKNOWN, PFNDESTROYED);
        ~CKoala(void);

        BOOL    Init(void);
        DWORD   CallLocal(UINT, LPARAM, BOOL);

        //Non-delegating object IUnknown
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
    };

typedef CKoala *PCKoala;

//IAnimal interface implementation

class CImpIAnimal : public IAnimal
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        PCKoala         m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIAnimal(PCKoala, LPUNKNOWN);
        ~CImpIAnimal(void);

        //IUnknown members
        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IAnimal members
        STDMETHODIMP Eat(LPTSTR, LPTSTR, short);
        STDMETHODIMP Sleep(short *);
        STDMETHODIMP Procreate(short *);
        STDMETHODIMP WhatKindOfAnimal(IID *);
    };

//IKoala interface implementation

class CImpIKoala : public IKoala
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        PCKoala         m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIKoala(PCKoala, LPUNKNOWN);
        ~CImpIKoala(void);

        //IUnknown members
        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IKoala members
        STDMETHODIMP ClimbEucalyptusTree(short);
        STDMETHODIMP PouchOpensDown(void);
        STDMETHODIMP SleepAfterEating(short);
    };

class CImpIMarshal : public IMarshal
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        PCKoala         m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIMarshal(PCKoala, LPUNKNOWN);
        ~CImpIMarshal(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetUnmarshalClass(REFIID, LPVOID, DWORD
            , LPVOID, DWORD, LPCLSID);
        STDMETHODIMP GetMarshalSizeMax(REFIID, LPVOID, DWORD
            , LPVOID, DWORD, LPDWORD);
        STDMETHODIMP MarshalInterface(LPSTREAM, REFIID, LPVOID
            , DWORD, LPVOID, DWORD);
        STDMETHODIMP UnmarshalInterface(LPSTREAM, REFIID, LPVOID *);
        STDMETHODIMP ReleaseMarshalData(LPSTREAM);
        STDMETHODIMP DisconnectObject(DWORD);
    };

#endif //_KOALA_H_


KOALAPRX.CPP

/*
 * KOALAPRX.CPP
 * Koala Custom Marshaling Proxy Chapter 6
 *
 * Example proxy for an object using custom marshaling
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "koalaprx.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

/*
 * LibMain(32)
 *
 * Purpose:
 *  Entry point conditionally compiled for Win32 and Win16.
 *  Provides the proper structure for each environment.
 */

#ifdef WIN32
BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    if (DLL_PROCESS_DETACH==ulReason)
        {
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    return TRUE;
    }
#else
int PASCAL LibMain(HINSTANCE hInstance, WORD wDataSeg
    , WORD cbHeapSize, LPSTR lpCmdLine)
    {
    if (0!=cbHeapSize)
        UnlockData(0);

    return (int)hInstance;
    }
#endif

/*
 * DllGetClassObject
 *
 * Purpose:
 *  Provides an IClassFactory for a given CLSID that this DLL is
 *  registered to support.  This DLL is placed under the CLSID
 *  in the registration database as the InProcServer.
 *
 * Parameters:
 *  clsID           REFCLSID that identifies the class factory
 *                  desired.  Since this parameter is passed this
 *                  DLL can handle any number of objects simply
 *                  by returning different class factories here
 *                  for different CLSIDs.
 *
 *  riid            REFIID specifying the interface the caller wants
 *                  on the class object, usually IID_ClassFactory.
 *
 *  ppv             PPVOID in which to return the interface
 *                  pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, otherwise an error code.
 */

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, PPVOID ppv)
    {
    HRESULT             hr;
    CKoalaProxyFactory *pObj;

    if (CLSID_KoalaProxy!=rclsid)
        return ResultFromScode(E_FAIL);

    pObj=new CKoalaProxyFactory();

    if (NULL==pObj)
        return ResultFromScode(E_OUTOFMEMORY);

    hr=pObj->QueryInterface(riid, ppv);

    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

/*
 * DllCanUnloadNow
 *
 * Purpose:
 *  Answers if the DLL can be freed, that is, if there are no
 *  references to anything this DLL provides.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if nothing is using us, FALSE otherwise.
 */

STDAPI DllCanUnloadNow(void)
    {
    SCODE   sc;

    //Our answer is whether there are any object or locks
    sc=(0L==g_cObj && 0L==g_cLock) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Koala object to call when it gets destroyed.
 *  Since we're in a DLL we only track the number of objects here,
 *  letting DllCanUnloadNow take care of the rest.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;
    return;
    }

/*
 * CKoalaProxyFactory::CKoalaProxyFactory
 * CKoalaProxyFactory::~CKoalaProxyFactory
 *
 * Constructor Parameters:
 *  None
 */

CKoalaProxyFactory::CKoalaProxyFactory(void)
    {
    m_cRef=0L;
    return;
    }

CKoalaProxyFactory::~CKoalaProxyFactory(void)
    {
    return;
    }

/*
 * CKoalaProxyFactory::QueryInterface
 * CKoalaProxyFactory::AddRef
 * CKoalaProxyFactory::Release
 */

STDMETHODIMP CKoalaProxyFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CKoalaProxyFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CKoalaProxyFactory::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    ObjectDestroyed();
    return 0L;
    }

/*
 * CKoalaProxyFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a Koala object returning an interface pointer.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we are
 *                  being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_NOINTERFACE
 *                  if we cannot support the requested interface.
 */

STDMETHODIMP CKoalaProxyFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCKoala             pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //Verify that a controlling unknown asks for IUnknown
    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    //Create the object passing function to notify on destruction.
    pObj=new CKoala(pUnkOuter, ObjectDestroyed);

    if (NULL==pObj)
        return hr;

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    //Kill the object if initial creation or Init failed.
    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

/*
 * CKoalaProxyFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the DLL.  If the
 *  lock count goes to zero and there are no objects, the DLL
 *  is allowed to unload.  See DllCanUnloadNow.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CKoalaProxyFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        g_cLock--;

    return NOERROR;
    }


KOALAPRX.H

/*
 * KOALAPRX.H
 * Koala Custom Marshaling Proxy Chapter 6
 *
 * Definitions, classes, and prototypes for a proxy for a
 * custom marshaling Koala object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _KOALAPRX_H_
#define _KOALAPRX_H_

//Get the object definitions
#include "koala.h"

void ObjectDestroyed(void);

//DKOALA.CPP
//This class factory object creates Koala objects.

class CKoalaProxyFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;

    public:
        CKoalaProxyFactory(void);
        ~CKoalaProxyFactory(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP         CreateInstance(LPUNKNOWN, REFIID
                                 , PPVOID);
        STDMETHODIMP         LockServer(BOOL);
    };

typedef CKoalaProxyFactory *PCKoalaProxyFactory;

#endif //_KOALAPRX_H_


MSGFILT.CPP

/*
 * MSGFILT.CPP
 * Koala Client #2, Chapter 6
 *
 * Implementation of a message filter object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "objuser2.h"

/*
 * CMessageFilter::CMessageFilter
 * CMessageFilter::~CMessageFilter
 *
 * Parameters (Constructor):
 *  pApp            PAPP of the application
 */

CMessageFilter::CMessageFilter(PAPP pApp)
    {
    m_cRef=0;
    m_pApp=pApp;
    return;
    }

CMessageFilter::~CMessageFilter(void)
    {
    return;
    }

/*
 * CMessageFilter::QueryInterface
 * CMessageFilter::AddRef
 * CMessageFilter::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CMessageFilter.
 */

STDMETHODIMP CMessageFilter::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IMessageFilter==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CMessageFilter::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CMessageFilter::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CMessageFilter::HandleInComingCall
 *
 * Purpose:
 *  Requests that the container call OleSave for the object that
 *  lives here.  Typically this happens on server shutdown.
 *
 * Parameters:
 *  dwCallType      DWORD indicating the type of call received, from
 *                  the CALLTYPE enumeration
 *  hTaskCaller     HTASK of the caller
 *  dwTickCount     DWORD elapsed tick count since the outgoing call
 *                  was made if dwCallType is not CALLTYPE_TOPLEVEL.
 *                  Ignored for other call types.
 *  pInterfaceInfo  LPINTERFACEINFO providing information about the
 *                  call.  Can be NULL.
 *
 * Return Value:
 *  DWORD           One of SERVERCALL_ISHANDLED (if the call might
 *                  be handled), SERVERCALL_REJECTED (call cannot
 *                  be handled), or SERVERCALL_RETRYLATER (try
 *                  again sometime).
 */

STDMETHODIMP_(DWORD) CMessageFilter::HandleInComingCall
    (DWORD dwCallType, HTASK htaskCaller, DWORD dwTickCount
#ifdef WIN32
    , LPINTERFACEINFO pInterfaceInfo)
#else
    , DWORD dwReserved)
#endif
    {
    /*
     * Because ObjectUser2 doesn't serve any objects itself,
     * this should never occur in this message filter.
     */
    m_pApp->Message(TEXT("CMessageFilter::HandleInComingCall called"));
    return SERVERCALL_ISHANDLED;
    }

/*
 * CMessageFilter::RetryRejectedCall
 *
 * Purpose:
 *  Informs the message filter that an call from this process has
 *  been rejected or delayed from a local or remote server, thus
 *  asking the message filter what to do.
 *
 * Parameters:
 *  hTaskCallee     HTASK of the caller
 *  dwTickCount     DWORD elapsed tick count since the call was made
 *  dwRejectType    DWORD either SERVERCALL_REJECTED or
 *                  SERVERCALL_RETRYLATER as returned by
 *                  HandleInComingCall.
 *
 * Return Value:
 *  DWORD           (DWORD)-1 to cancel the call, any number between
 *                  0 and 100 to try the call again immediately, or
 *                  a value over 100 (but not (DWORD)-1) to instruct
 *                  COM to wait that many milliseconds before trying
 *                  again.
 */

STDMETHODIMP_(DWORD) CMessageFilter::RetryRejectedCall
    (HTASK htaskCallee, DWORD dwTickCount, DWORD dwRejectType)
    {
    UINT    uRet;
    TCHAR   szMsg[256];

    /*
     * A message is pointless as ObjectUserWndProc will
     * output a message as soon as the call returns, overwriting
     * anything we might print here.
     */
    if (SERVERCALL_REJECTED==dwRejectType)
        return (DWORD)-1;

    wsprintf(szMsg, TEXT("RetryRejectedCall waiting %lu")
        , dwTickCount);
    m_pApp->Message(szMsg);

    /*
     * If we've waited over 5 seconds, put up the busy dialog.
     * Otherwise continue waiting.
     */
    if (dwTickCount < 5000)
        return 200;

    m_pApp->Message
        (TEXT("CMessageFilter::RetryRejectedCall showing busy dialog"));

    uRet=DisplayBusyDialog(htaskCallee, 0L);

    switch (uRet)
        {
        case OLEUI_CANCEL:
            return (DWORD)-1;

        case OLEUI_BZ_SWITCHTOSELECTED:
            /*
             * This case won't happen without BZ_NOTRESPONDINGDIALOG,
             * but we would wait maybe 10 seconds if it did.
             */
            return 10000;

        case OLEUI_BZ_RETRYSELECTED:
            m_pApp->Message(TEXT("Waiting another second"));
            return 1000;

        default:
            break;
        }

    return 0;
    }

/*
 * CMessageFilter::MessagePending
 *
 * Purpose:
 *  Gives the caller a chance to process messages while waiting for
 *  a call to an object to complete, to handle things like focus
 *  changes and input.  Usually returning PENDINGMSG_DEFPROCESS
 *  takes care of most things, except that it discards input.  This
 *  function is really useful is you want to process input while
 *  a call is in progress.
 *
 * Parameters:
 *  hTaskCallee     HTASK of the caller
 *  dwTickCount     DWORD elapsed tick count since the call was made
 *  dwPendingType   DWORD with the type of call made from the
 *                  PENDINGTYPE enumeration.
 *
 * Return Value:
 *  DWORD           One of PENDINGMSG_CANCELCALL (cancels the call
 *                  under extreme conditions), PENDINGMSG_WAITNO-
 *                  PROCESS (continue waiting), or PENDINGMSG_WAIT-
 *                  DEFPROCESS (invoke default handling).
 */

STDMETHODIMP_(DWORD) CMessageFilter::MessagePending
    (HTASK htaskCallee, DWORD dwTickCount, DWORD dwPendingType)
    {
    return PENDINGMSG_WAITDEFPROCESS;
    }

/*
 * CMessageFilter::DisplayBusyDialog
 *
 * Purpose:
 *  Invokes the standard Busy dialog using hTask to find the
 *  window handle of the server that's busy.
 *
 * Parameters:
 *  hTask           HTASK received in the message filter.
 *  dwFlags         DWORD flags to use when invoking the dialog
 *
 * Return Value:
 *  UINT            Result of the dialog, one of OLEUI_BZ_:
 *                  SWITCHTOSELECTED, RETRYSELECTED, CALLUNBLOCKED.
 */

UINT CMessageFilter::DisplayBusyDialog(HTASK hTask, DWORD dwFlags)
    {
    OLEUIBUSY   bz;

    //Clear out everything we don't use
    memset(&bz, 0, sizeof(bz));

    bz.cbStruct=sizeof(OLEUIBUSY);
    bz.dwFlags=dwFlags;
    bz.hWndOwner=m_pApp->m_hWnd;
    bz.hTask=hTask;
    bz.lphWndDialog=NULL;

    return OleUIBusy(&bz);
    }


OBJUSER2.CPP

/*
 * OBJUSER2.CPP
 * Koala Client #2 Chapter 6
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "objuser2.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    MSG         msg;
    PAPP        pApp;

    SETMESSAGEQUEUE;

    pApp=new CApp(hInst, hInstPrev, nCmdShow);

    if (NULL==pApp)
        return -1;

    if (pApp->Init())
        {
        while (GetMessage(&msg, NULL, 0,0 ))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    delete pApp;
    return msg.wParam;
    }

/*
 * ObjectUserWndProc
 *
 * Purpose:
 *  Window class procedure.  Standard callback.
 */

LRESULT APIENTRY ObjectUserWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    HRESULT         hr;
    PAPP            pApp;
    IUnknown       *pUnk;
    IPersist       *pIPersist;

    pApp=(PAPP)GetWindowLong(hWnd, OBJUSERWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pApp=(PAPP)(((LPCREATESTRUCT)lParam)->lpCreateParams);
            SetWindowLong(hWnd, OBJUSERWL_STRUCTURE, (LONG)pApp);
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_COMMAND:
            if (NULL!=pApp)
                pUnk=pApp->m_pIUnknown;

            switch (LOWORD(wParam))
                {
                case IDM_OBJECTCREATE:
                    if (NULL!=pUnk)
                        ReleaseInterface(pApp->m_pIUnknown);

                    hr=CoCreateInstance(CLSID_Koala, NULL
                        , CLSCTX_LOCAL_SERVER, IID_IUnknown
                        , (PPVOID)&pApp->m_pIUnknown);

                    if (SUCCEEDED(hr))
                        pApp->Message(TEXT("Creation succeeded"));
                    else
                        pApp->Message(TEXT("Creation failed"));

                    break;

                case IDM_OBJECTGETCLASSID:
                    if (NULL==pUnk)
                        {
                        pApp->Message(TEXT("There is no object"));
                        break;
                        }

                    if (SUCCEEDED(pUnk->QueryInterface(IID_IPersist
                        , (void **)&pIPersist)))
                        {
                        CLSID       clsID;
                        HRESULT     hr;
                        TCHAR       szMsg[256];

                        hr=pIPersist->GetClassID(&clsID);
                        pIPersist->Release();

                        wsprintf(szMsg
                            , TEXT("IPersist::GetClassID returned 0x%lX")
                            , (DWORD)hr);
                        pApp->Message(szMsg);
                        }
                    else
                        pApp->Message(TEXT("IPersist not available"));

                    break;

                case IDM_OBJECTRELEASE:
                    if (NULL==pUnk)
                        {
                        pApp->Message(TEXT("There is no object"));
                        break;
                        }

                    ReleaseInterface(pApp->m_pIUnknown);
                    pApp->Message(TEXT("Object released"));
                    break;

                case IDM_OBJECTEXIT:
                    PostMessage(hWnd, WM_CLOSE, 0, 0L);
                    break;
                }
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * CApp::CApp
 * CApp::~CApp
 *
 * Constructor Parameters: (from WinMain)
 *  hInst           HINSTANCE of the application.
 *  hInstPrev       HINSTANCE of a previous instance.
 *  nCmdShow        UINT specifying how to show the app window.
 *
 */

CApp::CApp(HINSTANCE hInst, HINSTANCE hInstPrev
    , UINT nCmdShow)
    {
    m_hInst       =hInst;
    m_hInstPrev   =hInstPrev;
    m_nCmdShow    =nCmdShow;

    m_hWnd        =NULL;

    m_pIUnknown   =NULL;
    m_fInitialized=FALSE;

    m_pMsgFilter  =NULL;
    return;
    }

CApp::~CApp(void)
    {
    if (NULL!=m_pMsgFilter)
        {
        CoRegisterMessageFilter(NULL, NULL);
        ReleaseInterface(m_pMsgFilter);
        }

    ReleaseInterface(m_pIUnknown);

    if (IsWindow(m_hWnd))
        DestroyWindow(m_hWnd);

    if (m_fInitialized)
        CoUninitialize();

    return;
    }

/*
 * CApp::Init
 *
 * Purpose:
 *  Initializes an CApp object by registering window classes,
 *  creating the main window, and doing anything else prone to
 *  failure such as calling CoInitialize.  If this function fails
 *  the caller should insure that the destructor is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::Init(void)
    {
    WNDCLASS    wc;

    CHECKVER_COM;

    if (FAILED(CoInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    if (!m_hInstPrev)
        {
        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = ObjectUserWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = CBWNDEXTRA;
        wc.hInstance      = m_hInst;
        wc.hIcon          = LoadIcon(m_hInst, TEXT("Icon"));
        wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName   = MAKEINTRESOURCE(IDR_MENU);
        wc.lpszClassName  = TEXT("OBJUSER2");

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

    m_hWnd=CreateWindow(TEXT("OBJUSER2")
        , TEXT("Koala Client #2"), WS_OVERLAPPEDWINDOW
        , 35, 35, 350, 250, NULL, NULL, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    //Create and register a message filter.
    m_pMsgFilter=new CMessageFilter(this);

    if (NULL!=m_pMsgFilter)
        {
        m_pMsgFilter->AddRef();

        if (FAILED(CoRegisterMessageFilter(m_pMsgFilter, NULL)))
            ReleaseInterface(m_pMsgFilter);
        }

    ShowWindow(m_hWnd, m_nCmdShow);
    UpdateWindow(m_hWnd);

    return TRUE;
    }

/*
 * CApp::Message
 *
 * Purpose:
 *  Displays a message in the client area of the window.  This is
 *  just to centralize the call to simpify other code.
 *
 * Parameters:
 *  psz             LPTSTR to the string to display.
 *
 * Return Value:
 *  None
 */

void CApp::Message(LPTSTR psz)
    {
    HDC     hDC;
    RECT    rc;

    hDC=GetDC(m_hWnd);
    GetClientRect(m_hWnd, &rc);

    SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
    SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));

    /*
     * We'll just be sloppy and clear the whole window as
     * well as write the string with one ExtTextOut call.
     * No word wrapping here...
     */

    ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rc, psz, lstrlen(psz), NULL);
    ReleaseDC(m_hWnd, hDC);
    return;
    }


OBJUSER2.H

/*
 * OBJUSER2.H
 * Koala Client #2 Chapter 6
 *
 * Definitions and structures.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _OBJUSER2_H_
#define _OBJUSER2_H_

#define CHAPTER6
#define INC_OLEUI
#include <inole.h>

//Menu Resource ID and Commands
#define IDR_MENU                    1

#define IDM_OBJECTCREATE            100
#define IDM_OBJECTGETCLASSID        101
#define IDM_OBJECTRELEASE           102
#define IDM_OBJECTEXIT              103

//OBJUSER.CPP
LRESULT APIENTRY ObjectUserWndProc(HWND, UINT, WPARAM, LPARAM);

class CMessageFilter;
typedef CMessageFilter *PCMessageFilter;

class CApp
    {
    friend LRESULT APIENTRY ObjectUserWndProc(HWND, UINT, WPARAM
        , LPARAM);
    friend class CMessageFilter;

    protected:
        HINSTANCE       m_hInst;            //WinMain parameters
        HINSTANCE       m_hInstPrev;
        UINT            m_nCmdShow;

        HWND            m_hWnd;             //Main window handle

        LPUNKNOWN       m_pIUnknown;        //IUnknown interface
        BOOL            m_fInitialized;     //Did CoInitialize work?

        PCMessageFilter m_pMsgFilter;       //Our message filter

    public:
        CApp(HINSTANCE, HINSTANCE, UINT);
        ~CApp(void);

        BOOL Init(void);
        void Message(LPTSTR);
    };

typedef CApp *PAPP;

#define CBWNDEXTRA              sizeof(PAPP)
#define OBJUSERWL_STRUCTURE     0

//Message Filter Object

class CMessageFilter : public IMessageFilter
    {
    protected:
        ULONG           m_cRef;      //Object reference count
        PAPP            m_pApp;
        HWND            m_hDlg;      //Dialog handle

    private:
        UINT    DisplayBusyDialog(HTASK, DWORD);

    public:
        CMessageFilter(PAPP);
        ~CMessageFilter(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(DWORD) HandleInComingCall(DWORD, HTASK
           #ifdef WIN32
            , DWORD, LPINTERFACEINFO);
           #else
            , DWORD, DWORD);
           #endif
        STDMETHODIMP_(DWORD) RetryRejectedCall(HTASK, DWORD, DWORD);
        STDMETHODIMP_(DWORD) MessagePending(HTASK, DWORD, DWORD);
    };

#endif //_OBJUSER2_H_


OBJUSER3.CPP

/*
 * OBJUSER3.CPP
 * Koala Client #3 Chapter 6
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "objuser3.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    MSG         msg;
    PAPP        pApp;

    SETMESSAGEQUEUE;

    pApp=new CApp(hInst, hInstPrev, nCmdShow);

    if (NULL==pApp)
        return -1;

    if (pApp->Init())
        {
        while (GetMessage(&msg, NULL, 0,0 ))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    delete pApp;
    return msg.wParam;
    }

/*
 * ObjectUserWndProc
 *
 * Purpose:
 *  Window class procedure.  Standard callback.
 */

LRESULT APIENTRY ObjectUserWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    HRESULT         hr;
    PAPP            pApp;
    IUnknown       *pUnk;
    IAnimal        *pIAnimal;
    IKoala         *pIKoala;
    TCHAR           szMsg[256];

    pApp=(PAPP)GetWindowLong(hWnd, OBJUSERWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pApp=(PAPP)(((LPCREATESTRUCT)lParam)->lpCreateParams);
            SetWindowLong(hWnd, OBJUSERWL_STRUCTURE, (LONG)pApp);
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_COMMAND:
            if (NULL!=pApp)
                pUnk=pApp->m_pIUnknown;

            switch (LOWORD(wParam))
                {
                case IDM_OBJECTCREATE:
                    if (NULL!=pUnk)
                        {
                        ReleaseInterface(pUnk);
                        CoFreeUnusedLibraries();
                        }

                    hr=CoCreateInstance(CLSID_Koala, NULL
                        , CLSCTX_LOCAL_SERVER, IID_IUnknown
                        , (PPVOID)&pApp->m_pIUnknown);

                    if (SUCCEEDED(hr))
                        pApp->Message(TEXT("Creation succeeded"));
                    else
                        pApp->Message(TEXT("Creation failed"));

                    break;

                case IDM_OBJECTRELEASE:
                    if (NULL==pUnk)
                        {
                        pApp->Message(TEXT("There is no object"));
                        break;
                        }
                    ReleaseInterface(pApp->m_pIUnknown);
                    pApp->Message(TEXT("Object released"));
                    break;

                case IDM_ANIMALEAT:
                    if (NULL==pUnk)
                        {
                        pApp->Message(TEXT("There is no object"));
                        break;
                        }

                    if (SUCCEEDED(pUnk->QueryInterface(IID_IAnimal
                        , (PPVOID)&pIAnimal)))
                        {
                        const short cch=80;
                        TCHAR       szEaten[cch];

                        pIAnimal->Eat(TEXT("Your Choice"), szEaten
                            , cch);
                        pIAnimal->Release();

                        wsprintf(szMsg, TEXT("Animal ate %s")
                            , szEaten);
                        pApp->Message(szMsg);
                        }
                    else
                        pApp->Message(TEXT("QueryInterface failed"));

                    break;

               case IDM_ANIMALSLEEP:
                    if (NULL==pUnk)
                        {
                        pApp->Message(TEXT("There is no object"));
                        break;
                        }

                    if (SUCCEEDED(pUnk->QueryInterface(IID_IAnimal
                        , (PPVOID)&pIAnimal)))
                        {
                        short   n=120;

                        pIAnimal->Sleep(&n);
                        wsprintf(szMsg, TEXT("Animal slept %u"), n);
                        pApp->Message(szMsg);
                        pIAnimal->Release();
                        }
                    else
                        pApp->Message(TEXT("QueryInterface failed"));

                    break;

               case IDM_ANIMALPROCREATE:
                    if (NULL==pUnk)
                        {
                        pApp->Message(TEXT("There is no object"));
                        break;
                        }

                    if (SUCCEEDED(pUnk->QueryInterface(IID_IAnimal
                        , (PPVOID)&pIAnimal)))
                        {
                        short   n;

                        hr=pIAnimal->Procreate(&n);
                        pIAnimal->Release();

                        if (NOERROR!=hr)
                            {
                            pApp->Message(TEXT("No offspring this time"));
                            break;
                            }

                        wsprintf(szMsg
                            , TEXT("Animal produced %u offspring")
                            , n);
                        pApp->Message(szMsg);
                        }
                    else
                        pApp->Message(TEXT("QueryInterface failed"));

                    break;

               case IDM_ANIMALWHATKIND:
                    if (NULL==pUnk)
                        {
                        pApp->Message(TEXT("There is no object"));
                        break;
                        }

                    if (SUCCEEDED(pUnk->QueryInterface(IID_IAnimal
                        , (PPVOID)&pIAnimal)))
                        {
                        IID     iid;

                        pIAnimal->WhatKindOfAnimal(&iid);
                        pIAnimal->Release();

                        StringFromGUID2(iid, szMsg, 256);
                        pApp->Message(szMsg);
                        }
                    else
                        pApp->Message(TEXT("QueryInterface failed"));

                    break;

               case IDM_KOALACLIMB:
                    if (NULL==pUnk)
                        {
                        pApp->Message(TEXT("There is no object"));
                        break;
                        }

                    if (SUCCEEDED(pUnk->QueryInterface(IID_IKoala
                        , (PPVOID)&pIKoala)))
                        {
                        pIKoala->ClimbEucalyptusTree(0);
                        pIKoala->Release();
                        pApp->Message(TEXT("Tree climbed"));
                        }
                    else
                        pApp->Message(TEXT("QueryInterface failed"));

                    break;

               case IDM_KOALAPOUCH:
                    if (NULL==pUnk)
                        {
                        pApp->Message(TEXT("There is no object"));
                        break;
                        }

                    if (SUCCEEDED(pUnk->QueryInterface(IID_IKoala
                        , (PPVOID)&pIKoala)))
                        {
                        pIKoala->PouchOpensDown();
                        pIKoala->Release();
                        pApp->Message(TEXT("PouchOpenDown called"));
                        }
                    else
                        pApp->Message(TEXT("QueryInterface failed"));

                    break;

               case IDM_KOALASLEEP:
                    if (NULL==pUnk)
                        {
                        pApp->Message(TEXT("There is no object"));
                        break;
                        }

                    if (SUCCEEDED(pUnk->QueryInterface(IID_IKoala
                        , (PPVOID)&pIKoala)))
                        {
                        pIKoala->SleepAfterEating(120);
                        pIKoala->Release();
                        pApp->Message(TEXT("Set extra sleep to 120"));
                        }
                    else
                        pApp->Message(TEXT("QueryInterface failed"));

                    break;

                case IDM_OBJECTEXIT:
                    PostMessage(hWnd, WM_CLOSE, 0, 0L);
                    break;
                }
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * CApp::CApp
 * CApp::~CApp
 *
 * Constructor Parameters: (from WinMain)
 *  hInst           HINSTANCE of the application.
 *  hInstPrev       HINSTANCE of a previous instance.
 *  nCmdShow        UINT specifying how to show the app window.
 *
 */

CApp::CApp(HINSTANCE hInst, HINSTANCE hInstPrev
    , UINT nCmdShow)
    {
    m_hInst       =hInst;
    m_hInstPrev   =hInstPrev;
    m_nCmdShow    =nCmdShow;

    m_hWnd        =NULL;

    m_pIUnknown   =NULL;
    m_fInitialized=FALSE;
    return;
    }

CApp::~CApp(void)
    {
    ReleaseInterface(m_pIUnknown);

    if (IsWindow(m_hWnd))
        DestroyWindow(m_hWnd);

    if (m_fInitialized)
        CoUninitialize();

    return;
    }

/*
 * CApp::Init
 *
 * Purpose:
 *  Initializes an CApp object by registering window classes,
 *  creating the main window, and doing anything else prone to
 *  failure such as calling CoInitialize.  If this function fails
 *  the caller should insure that the destructor is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::Init(void)
    {
    WNDCLASS    wc;

    CHECKVER_COM;

    if (FAILED(CoInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    if (!m_hInstPrev)
        {
        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = ObjectUserWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = CBWNDEXTRA;
        wc.hInstance      = m_hInst;
        wc.hIcon          = LoadIcon(m_hInst, TEXT("Icon"));
        wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName   = MAKEINTRESOURCE(IDR_MENU);
        wc.lpszClassName  = TEXT("OBJUSER3");

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

    m_hWnd=CreateWindow(TEXT("OBJUSER3")
        , TEXT("Koala Client #3"), WS_OVERLAPPEDWINDOW
        , 35, 35, 350, 250, NULL, NULL, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    ShowWindow(m_hWnd, m_nCmdShow);
    UpdateWindow(m_hWnd);

    return TRUE;
    }

/*
 * CApp::Message
 *
 * Purpose:
 *  Displays a message in the client area of the window.  This is
 *  just to centralize the call to simpify other code.
 *
 * Parameters:
 *  psz             LPTSTR to the string to display.
 *
 * Return Value:
 *  None
 */

void CApp::Message(LPTSTR psz)
    {
    HDC     hDC;
    RECT    rc;

    hDC=GetDC(m_hWnd);
    GetClientRect(m_hWnd, &rc);

    SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
    SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));

    /*
     * We'll just be sloppy and clear the whole window as
     * well as write the string with one ExtTextOut call.
     * No word wrapping here...
     */

    ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rc, psz, lstrlen(psz), NULL);
    ReleaseDC(m_hWnd, hDC);
    return;
    }


OBJUSER3.H

/*
 * OBJUSER3.H
 * Koala Client #3 Chapter 6
 *
 * Definitions and structures.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _OBJUSER3_H_
#define _OBJUSER3_H_

#define CHAPTER6
#include <inole.h>
#ifdef WIN32
#include <ianimal.h>
#include <ikoala.h>
#else
//Non-MIDL generated versions
#include <ianim16.h>
#include <ikoala16.h>
#endif

//Menu Resource ID and Commands
#define IDR_MENU                    1

#define IDM_OBJECTCREATE            100
#define IDM_OBJECTRELEASE           101
#define IDM_ANIMALEAT               102
#define IDM_ANIMALSLEEP             103
#define IDM_ANIMALPROCREATE         104
#define IDM_ANIMALWHATKIND          105
#define IDM_KOALACLIMB              106
#define IDM_KOALAPOUCH              107
#define IDM_KOALASLEEP              108
#define IDM_OBJECTEXIT              109

//OBJUSER.CPP
LRESULT APIENTRY ObjectUserWndProc(HWND, UINT, WPARAM, LPARAM);

class CApp
    {
    friend LRESULT APIENTRY ObjectUserWndProc(HWND, UINT, WPARAM
        , LPARAM);

    protected:
        HINSTANCE       m_hInst;            //WinMain parameters
        HINSTANCE       m_hInstPrev;
        UINT            m_nCmdShow;

        HWND            m_hWnd;             //Main window handle

        LPUNKNOWN       m_pIUnknown;        //IUnknown interface
        BOOL            m_fInitialized;     //Did CoInitialize work?

    public:
        CApp(HINSTANCE, HINSTANCE, UINT);
        ~CApp(void);

        BOOL Init(void);
        void Message(LPTSTR);
    };

typedef CApp *PAPP;

#define CBWNDEXTRA              sizeof(PAPP)
#define OBJUSERWL_STRUCTURE     0

#endif //_OBJUSER3_H_


CLIENT.CPP

/*
 * CLIENT.CPP
 * Cosmo Chapter 7
 *
 * Implementation of the CCosmoClient class that just makes sure
 * we get a CCosmoDoc on doc creation and that we initialize fully.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CCosmoClient::CCosmoClient
 * CCosmoClient::~CCosmoClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 */

CCosmoClient::CCosmoClient(HINSTANCE hInst)
    : CClient(hInst)
    {
    return;
    }

CCosmoClient::~CCosmoClient(void)
    {
    return;
    }

/*
 * CCosmoClient::CreateCDocument
 *
 * Purpose:
 *  Constructs a new document specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::CreateCDocument(void)
    {
    return (PCDocument)(new CCosmoDoc(m_hInst, m_pFR));
    }

/*
 * CCosmoClient::NewDocument
 *
 * Purpose:
 *  Small override of the CClient::NewDocument that we have just
 *  to check the initial line selection on new document creation.
 *
 * Parameters:
 *  fVisible        BOOL indicating if the document is to be
 *                  visible or not.
 *  pAdv            PCDocumentAdviseSink to set with the new
 *                  document for notifications.  Can be NULL.
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::NewDocument(BOOL fVisible
    , PCDocumentAdviseSink pAdv)
    {
    PCDocument  pDoc;

    //Perform default NewDocument first
    pDoc=CClient::NewDocument(fVisible, pAdv);

    //We know that m_pFR is actually a CCosmoFrame, so type is safe.
    ((PCCosmoFrame)m_pFR)->CheckLineSelection(IDM_LINESOLID);

    return pDoc;
    }


COSMO.CPP

/*
 * COSMO.CPP
 * Cosmo Chapter 7
 *
 * WinMain and CCosmoFrame implementations.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "cosmo.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCCosmoFrame    pFR;
    FRAMEINIT       fi;
    WPARAM          wRet;

   #ifndef WIN32
    int             cMsg=96;

    while (!SetMessageQueue(cMsg) && (cMsg-=8));
   #endif

    //Attempt to allocate and initialize the application
    pFR=new CCosmoFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return -1;

    fi.idsMin=IDS_FRAMEMIN;
    fi.idsMax=IDS_FRAMEMAX;
    fi.idsStatMin=IDS_STATMESSAGEMIN;
    fi.idsStatMax=IDS_STATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=440;
    fi.cy=460;

    //If we can initialize pFR, start chugging messages
    if (pFR->FInit(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

/*
 * CCosmoFrame::CCosmoFrame
 * CCosmoFrame::~CCosmoFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WInMain
 */

CCosmoFrame::CCosmoFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    : CFrame(hInst, hInstPrev, pszCmdLine, nCmdShow)
    {
    UINT        i;

    for (i=0; i<5; i++)
        m_hBmpLines[i]=NULL;

    m_uIDCurLine=0;
    m_fInitialized=FALSE;

    //CHAPTER7MOD
    m_pIClassDataTran=NULL;
    //End CHAPTER7MOD
    return;
    }

CCosmoFrame::~CCosmoFrame(void)
    {
    UINT        i;

    for (i=0; i<5; i++)
        {
        if (NULL!=m_hBmpLines[i])
            DeleteObject(m_hBmpLines[i]);
        }

    //CHAPTER7MOD
    if (NULL!=m_pIClassDataTran)
        {
        m_pIClassDataTran->LockServer(FALSE);
        m_pIClassDataTran->Release();
        }

    OleFlushClipboard();

    if (m_fInitialized)
        OleUninitialize();
    //End CHAPTER7MOD

    return;
    }

/*
 * CCosmoFrame::FInit
 *
 * Purpose:
 *  Call CoInitialize then calling down into the base class
 *  initialization.
 *
 * Parameters:
 *  pFI             PFRAMEINIT containing initialization parameters.
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded, FALSE otherwise.
 */

BOOL CCosmoFrame::FInit(PFRAMEINIT pFI)
    {
    DWORD       dwVer;
    //CHAPER7MOD
    HRESULT     hr;

    dwVer=OleBuildVersion();

    if (rmm!=HIWORD(dwVer))
        return FALSE;

    if (FAILED(OleInitialize(NULL)))
        return FALSE;
    //End CHAPTER7MOD

    m_fInitialized=TRUE;

    //CHAPTER7MOD
    /*
     * Obtain the Data Transfer Object class factory and lock it.
     * This will improve the speed of clipboard and (later)
     * drag & drop operations that involve this class.
     */
    hr=CoGetClassObject(CLSID_DataTransferObject
        , CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory
        , (PPVOID)&m_pIClassDataTran);

    if (SUCCEEDED(hr))
        m_pIClassDataTran->LockServer(TRUE);
    //End CHAPTER7MOD

    return CFrame::FInit(pFI);
    }

/*
 * CCosmoFrame::CreateCClient
 *
 * Purpose:
 *  Constructs a new client specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient        Pointer to the new client object.
 */

PCClient CCosmoFrame::CreateCClient(void)
    {
    return (PCClient)(new CCosmoClient(m_hInst));
    }

/*
 * CCosmoFrame::FRegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CCosmoFrame::FRegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //First let the standard frame do its thing
    if (!CFrame::FRegisterAllClasses())
        return FALSE;

    /*
     * We want a different background color for the document
     * because the Polyline we put in the document will paint
     * with COLOR_WINDOW which by default which is CLASSLIB's
     * default document color.
     */

    GetClassInfo(m_hInst, SZCLASSDOCUMENT, &wc);
    UnregisterClass(SZCLASSDOCUMENT, m_hInst);

    wc.hbrBackground=(HBRUSH)(COLOR_APPWORKSPACE+1);

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

    //Register the Polyline window.
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.hInstance     = m_hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PolylineWndProc;
    wc.cbWndExtra    = CBPOLYLINEWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_CROSS);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPOLYLINE;

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

    return TRUE;
    }

/*
 * CCosmoFrame::FPreShowInit
 *
 * Purpose:
 *  Called from FInit before intially showing the window.  We do
 *  whatever else we want here, modifying nCmdShow as necessary
 *  which affects ShowWindow in FInit.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if this initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CCosmoFrame::FPreShowInit(void)
    {
    CreateLineMenu();
    CheckLineSelection(IDM_LINESOLID);
    return TRUE;
    }

/*
 * CCosmoFrame::CreateLineMenu
 *
 * Purpose:
 *  Initializes the bitmaps used to create the Line menu and
 *  replaces the text items defined in the application resources
 *  with these bitmaps.  Note that the contents of m_hBmpLines
 *  must be cleaned up when the application terminates.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CreateLineMenu(void)
    {
    HMENU       hMenu;
    HDC         hDC, hMemDC;
    HPEN        hPen;
    HGDIOBJ     hObj;
    TEXTMETRIC  tm;
    UINT        i, cx, cy;

    hMenu=GetSubMenu(GetMenu(m_hWnd), 3);   //Line menu.
    hDC=GetDC(m_hWnd);

    //Create each line in a menu item 8 chars wide, one char high.
    GetTextMetrics(hDC, &tm);
    cx=tm.tmAveCharWidth*8;
    cy=tm.tmHeight;

    /*
     * Create a memory DC in which to draw lines, and bitmaps
     * for each line.
     */
    hMemDC=CreateCompatibleDC(hDC);
    ReleaseDC(m_hWnd, hDC);

    for (i=0; i<5; i++)
        {
        m_hBmpLines[i]=CreateCompatibleBitmap(hMemDC, cx, cy);
        SelectObject(hMemDC, m_hBmpLines[i]);

        PatBlt(hMemDC, 0, 0, cx, cy, WHITENESS);

        hPen=CreatePen(i, 1, 0L);       //i=line style like PS_SOLID
        hObj=SelectObject(hMemDC, hPen);

        MoveToEx(hMemDC, 0, cy/2, NULL);
        LineTo(hMemDC, cx, cy/2);

        ModifyMenu(hMenu, IDM_LINEMIN+i, MF_BYCOMMAND | MF_BITMAP
            , IDM_LINEMIN+i, (LPTSTR)(LONG)(UINT)m_hBmpLines[i]);

        SelectObject(hMemDC, hObj);
        DeleteObject(hPen);
        }

    CheckMenuItem(hMenu, IDM_LINESOLID, MF_CHECKED);
    DeleteDC(hMemDC);

    return;
    }

/*
 * CCosmoFrame::CreateGizmos
 *
 * Purpose:
 *  Procedure to create all the necessary gizmobar buttons.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of gizmos added to the bar.
 */

UINT CCosmoFrame::CreateGizmos(void)
    {
    UINT            iLast;
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;
    UINT            utEx  =GIZMOTYPE_BUTTONATTRIBUTEEX;

    //Insert the standard ones.
    iLast=CFrame::CreateGizmos();

    /*
     * Insert File Import in the 5th position and account for
     * it in iLast.
     */
    m_pGB->Add(utCmd, 4, IDM_FILEIMPORT, m_dxB, m_dyB
        , NULL, m_hBmp, 2, uState);
    iLast++;

    //Separator
    m_pGB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    /*
     * For the Background bitmap, preserve our use of black
     * (part of the image)
     */
    m_pGB->Add(utCmd, iLast++, IDM_COLORBACKGROUND, m_dxB, m_dyB
        , NULL, m_hBmp, 3, GIZMO_NORMAL | PRESERVE_BLACK);

    m_pGB->Add(utCmd, iLast++, IDM_COLORLINE, m_dxB, m_dyB
        , NULL, m_hBmp, 4, uState);

    //Separator
    m_pGB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //Line styles.
    m_pGB->Add(utEx, iLast++, IDM_LINESOLID, m_dxB, m_dyB
        , NULL, m_hBmp, 5, uState);
    m_pGB->Add(utEx, iLast++, IDM_LINEDASH, m_dxB, m_dyB
        , NULL, m_hBmp, 6, uState);
    m_pGB->Add(utEx, iLast++, IDM_LINEDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 7, uState);
    m_pGB->Add(utEx, iLast++, IDM_LINEDASHDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 8, uState);
    m_pGB->Add(utEx, iLast++, IDM_LINEDASHDOTDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 9, uState);

    return iLast;
    }

/*
 * CCosmoFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the Cosmo frame window that just
 *  processes the line menu and the color menu leaving the
 *  CFrame to do everything else.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CCosmoFrame::OnCommand(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PCCosmoDoc      pDoc;
    TCHAR           szFile[CCHPATHMAX];
    BOOL            fOK;
    UINT            i, uTemp;
    COLORREF        rgColors[16];
    CHOOSECOLOR     cc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many GizmoBar notifications.
     */
    if (m_fInit)
        return 0L;

    pDoc=(PCCosmoDoc)m_pCL->ActiveDocument();

    /*
     * Check for the line style commands which are
     * IDM_LINEMIN+<style>.  We handle this by changing the menu
     * and toolbar, then we pass it to the document for real
     * processing.
     */
    if (NULL!=pDoc && IDM_LINEMIN <= wID && IDM_LINEMAX >=wID)
        {
        CheckLineSelection(wID);
        pDoc->LineStyleSet(wID-IDM_LINEMIN);
        return 0L;
        }

    switch (wID)
        {
        case IDM_FILEIMPORT:
            szFile[0]=0;
            fOK=FSaveOpenDialog(szFile, CCHPATHMAX, IDS_FILEIMPORT
                , TRUE, &i);

            if (fOK)
                {
                uTemp=pDoc->ULoad(FALSE, szFile);
                pDoc->ErrorMessage(uTemp);
                }

            return (LRESULT)fOK;

        case IDM_COLORBACKGROUND:
        case IDM_COLORLINE:
            //Invoke the color chooser for either color
            uTemp=(IDM_COLORBACKGROUND==wID)
                ? DOCCOLOR_BACKGROUND : DOCCOLOR_LINE;

            for (i=0; i<16; i++)
                rgColors[i]=RGB(0, 0, i*16);

            memset(&cc, 0, sizeof(CHOOSECOLOR));
            cc.lStructSize=sizeof(CHOOSECOLOR);
            cc.lpCustColors=rgColors;
            cc.hwndOwner=hWnd;
            cc.Flags=CC_RGBINIT;
            cc.rgbResult=pDoc->ColorGet(uTemp);

            if (ChooseColor(&cc))
                pDoc->ColorSet(uTemp, cc.rgbResult);

            break;

        default:
           CFrame::OnCommand(hWnd, wParam, lParam);
        }

    return 0L;
    }

/*
 * CCosmoFrame::OnDocumentDataChange
 *
 * Purpose:
 *  Update the Line menu and GizmoBar if the style in the data
 *  changes.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentDataChange(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::OnDocumentActivate
 *
 * Purpose:
 *  Informs us that document activation changed, so update the UI
 *  for that new document.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentActivate(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    PCDocument  pDoc;
    BOOL        fOK=FALSE;
    BOOL        fCallDefault=TRUE;
    UINT        i;
    UINT        uTemp;
    UINT        uTempE;
    UINT        uTempD;

    pDoc=m_pCL->ActiveDocument();

    uTempE=MF_ENABLED | MF_BYCOMMAND;
    uTempD=MF_DISABLED | MF_GRAYED | MF_BYCOMMAND;
    uTemp=((NULL!=pDoc) ? uTempE : uTempD);

    //File menu:  If there is document window, disable Import.
    if (m_phMenu[0]==hMenu)
        EnableMenuItem(hMenu, IDM_FILEIMPORT, uTemp);

    //Color menu:  no document, no commands
    if (m_phMenu[2]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_COLORBACKGROUND, uTemp);
        EnableMenuItem(hMenu, IDM_COLORLINE,       uTemp);
        fCallDefault=FALSE;
        }

    //Line menu:  no document, no commands
    if (m_phMenu[3]==hMenu)
        {
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            EnableMenuItem(hMenu, i, uTemp);

        fCallDefault=FALSE;
        }

    if (fCallDefault)
        CFrame::UpdateMenus(hMenu, iMenu);

    return;
    }

/*
 * CCosmoFrame::UpdateGizmos
 *
 * Purpose:
 *  Enables and disables gizmos depending on whether we have
 *  a document or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateGizmos(void)
    {
    BOOL        fLast;
    UINT        i;

    //Save the last enabled state before CFrame changes it
    fLast=m_fLastEnable;

    //Let the default hack on its gizmos.
    CFrame::UpdateGizmos();

    /*
     * If CFrame::UpdateGizmos changed anything, then we need
     * to change as well--if nothing changes, nothing to do.
     */
    if (fLast!=m_fLastEnable)
        {
        m_pGB->Enable(IDM_FILEIMPORT, m_fLastEnable);

        m_pGB->Enable(IDM_COLORBACKGROUND, m_fLastEnable);
        m_pGB->Enable(IDM_COLORLINE,       m_fLastEnable);

        for (i=IDM_LINEMIN; i <= IDM_LINEMAX; i++)
            m_pGB->Enable(i, m_fLastEnable);
        }

    return;
    }

/*
 * CCosmoFrame::CheckLineSelection
 *
 * Purpose:
 *  Maintains the bitmap menu and the gizmos for the line selection.
 *  Both are mutially exclusive option lists where a selection in
 *  one has to affect the other.
 *
 * Parameters:
 *  uID             UINT ID of the item to be selected
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CheckLineSelection(UINT uID)
    {
    UINT        i;
    HMENU       hMenu;

    //Update menus and gizmo if the selection changed.
    if (uID!=m_uIDCurLine)
        {
        m_uIDCurLine=uID;
        hMenu=GetMenu(m_hWnd);

        //Uncheck all lines initially.
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            CheckMenuItem(hMenu, i, MF_UNCHECKED | MF_BYCOMMAND);

        CheckMenuItem(hMenu, uID, MF_CHECKED | MF_BYCOMMAND);
        m_pGB->Check(uID, TRUE);
        }

    return;
    }


COSMO.H

/*
 * COSMO.H
 * Cosmo Chapter 7
 *
 * Single include file that pulls in everything needed for other
 * source files in the Cosmo application.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _COSMO_H_
#define _COSMO_H_

#define INC_OLE2
#include <windows.h>
#include <memory.h>
#include <ole2.h>
#include <ole2ver.h>
#include <bookguid.h>

extern "C"
    {
    #include <commdlg.h>
    }

#include <classlib.h>

#include "resource.h"

//Get the editor window information.
#include "polyline.h"

//COSMO.CPP:  Frame object that creates a main window

class CCosmoFrame : public CFrame
    {
    private:
        HBITMAP         m_hBmpLines[5];     //Menu item bitmaps
        UINT            m_uIDCurLine;       //Current line selection
        BOOL            m_fInitialized;     //Did OleInitalize work?

        //CHAPTER7MOD
        //For locking DATATRAN.DLL class factory
        LPCLASSFACTORY  m_pIClassDataTran;
        //End CHAPTER7MOD

    protected:
        //Overridable for creating a CClient for this frame
        virtual PCClient  CreateCClient(void);

        virtual BOOL      FRegisterAllClasses(void);
        virtual BOOL      FPreShowInit(void);
        virtual UINT      CreateGizmos(void);

        virtual LRESULT   OnCommand(HWND, WPARAM, LPARAM);
        virtual void      OnDocumentDataChange(PCDocument);
        virtual void      OnDocumentActivate(PCDocument);

        //New for this class
        virtual void      CreateLineMenu(void);

    public:
        CCosmoFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CCosmoFrame(void);

        //Overrides
        virtual BOOL      FInit(PFRAMEINIT);
        virtual void      UpdateMenus(HMENU, UINT);
        virtual void      UpdateGizmos(void);

        //New for this class
        virtual void      CheckLineSelection(UINT);
    };

typedef CCosmoFrame *PCCosmoFrame;

//CLIENT.CPP

/*
 * The only reason we have a derived class here is to override
 * CreateCDocument so we can create our own type as well as
 * overriding NewDocument to perform one other piece of work once
 * the document's been created.
 */

class CCosmoClient : public CClient
    {
    protected:
        //Overridable for creating a new CDocument
        virtual PCDocument CreateCDocument(void);

    public:
        CCosmoClient(HINSTANCE);
        virtual ~CCosmoClient(void);

        virtual PCDocument NewDocument(BOOL, PCDocumentAdviseSink);
    };

typedef CCosmoClient *PCCosmoClient;

//DOCUMENT.CPP

//Constant ID for the window polyline that lives in a document
#define ID_POLYLINE         10

class CCosmoDoc : public CDocument
    {
    friend class CPolylineAdviseSink;

    protected:
        UINT                    m_uPrevSize;    //Last WM_SIZE wParam
        LONG                    m_lVer;         //Loaded Polyline ver

        PCPolyline              m_pPL;          //Polyline window here
        PCPolylineAdviseSink    m_pPLAdv;       //Advises from Polyline

    protected:
        virtual BOOL    FMessageHook(HWND, UINT, WPARAM, LPARAM
            , LRESULT *);

        //CHAPTER7MOD
        virtual BOOL    FQueryPasteFromData(LPDATAOBJECT);
        virtual BOOL    FPasteFromData(LPDATAOBJECT);
        //End CHAPTER7MOD

    public:
        CCosmoDoc(HINSTANCE, PCFrame);
        virtual ~CCosmoDoc(void);

        virtual BOOL     FInit(PDOCUMENTINIT);

        virtual void     Clear(void);

        virtual UINT     ULoad(BOOL, LPTSTR);
        virtual UINT     USave(UINT, LPTSTR);

        virtual void     Undo(void);
        virtual BOOL     FClip(HWND, BOOL);
        virtual HGLOBAL  RenderFormat(UINT);
        virtual BOOL     FQueryPaste(void);
        virtual BOOL     FPaste(HWND);

        virtual COLORREF ColorSet(UINT, COLORREF);
        virtual COLORREF ColorGet(UINT);

        virtual UINT     LineStyleSet(UINT);
        virtual UINT     LineStyleGet(void);
    };

typedef CCosmoDoc *PCCosmoDoc;

//These color indices wrap the polyline definitions
#define DOCCOLOR_BACKGROUND             POLYLINECOLOR_BACKGROUND
#define DOCCOLOR_LINE                   POLYLINECOLOR_LINE

#endif //_COSMO_H_


DOCUMENT.CPP

/*
 * DOCUMENT.CPP
 * Cosmo Chapter 7
 *
 * Implementation of the CCosmoDoc derivation of CDocument as
 * well as an implementation of CPolylineAdviseSink.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CCosmoDoc::CCosmoDoc
 * CCosmoDoc::~CCosmoDoc
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame of the frame object.
 */

CCosmoDoc::CCosmoDoc(HINSTANCE hInst, PCFrame pFR)
    : CDocument(hInst, pFR)
    {
    m_pPL=NULL;
    m_pPLAdv=NULL;
    m_uPrevSize=SIZE_RESTORED;
    return;
    }

CCosmoDoc::~CCosmoDoc(void)
    {
    //Clean up the allocations we did in FInit
    if (NULL!=m_pPL)
        delete m_pPL;

    if (NULL!=m_pPLAdv)
        delete m_pPLAdv;

    return;
    }

/*
 * CCosmoDoc::FInit
 *
 * Purpose:
 *  Initializes an already created document window.  The client
 *  actually creates the window for us, then passes that here for
 *  further initialization.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CCosmoDoc::FInit(PDOCUMENTINIT pDI)
    {
    RECT        rc;

    //Change the stringtable range to our customization.
    pDI->idsMin=IDS_DOCUMENTMIN;
    pDI->idsMax=IDS_DOCUMENTMAX;

    //Do default initialization
    if (!CDocument::FInit(pDI))
        return FALSE;

    //Add the Polyline stuff we need.
    m_pPLAdv=new CPolylineAdviseSink(this);
    m_pPL   =new CPolyline(m_hInst);

    //Attempt to create our contained Polyline.
    GetClientRect(m_hWnd, &rc);
    InflateRect(&rc, -8, -8);

    if (!m_pPL->FInit(m_hWnd, &rc, WS_CHILD | WS_VISIBLE
        , ID_POLYLINE, m_pPLAdv))
        return FALSE;

    return TRUE;
    }

/*
 * CCosmoDoc::FMessageHook
 *
 * Purpose:
 *  Processes WM_SIZE for the document so we can resize
 *  the Polyline.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CCosmoDoc::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    UINT        dx, dy;
    RECT        rc;

    *pLRes=0;

    if (WM_SIZE==iMsg)
        {
        //Don't effect the Polyline size to or from minimized state.
        if (SIZE_MINIMIZED!=wParam && SIZE_MINIMIZED !=m_uPrevSize)
            {
            //When we change size, resize any Polyline we hold.
            dx=LOWORD(lParam);
            dy=HIWORD(lParam);

            /*
             * If we are getting WM_SIZE in response to a Polyline
             * notification, then don't resize the Polyline window
             * again.
             */
            if (!m_fNoSize && NULL!=m_pPL)
                {
                //Resize the polyline to fit the new client
                SetRect(&rc, 8, 8, dx-8, dy-8);
                m_pPL->RectSet(&rc, FALSE);

                /*
                 * We consider sizing something that makes the file
                 * dirty, but not until we've finished the create
                 * process, which is why we set fNoDirty to FALSE
                 * in WM_CREATE since we get a WM_SIZE on the first
                 * creation.
                 */
                if (!m_fNoDirty)
                    FDirtySet(TRUE);

                SetRect(&rc, 0, 0, dx, dy);

                if (NULL!=m_pAdv)
                    m_pAdv->OnSizeChange(this, &rc);

                m_fNoDirty=FALSE;
                }
            }

        m_uPrevSize=wParam;
        }

    /*
     * We return FALSE even on WM_SIZE so we can let the default
     * procedure handle maximized MDI child windows appropriately.
     */
    return FALSE;
    }

/*
 * CCosmoDoc::Clear
 *
 * Purpose:
 *  Sets all contents in the document back to defaults with
 *  no filename.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Clear(void)
    {
    //Completely reset the polyline
    m_pPL->New();

    CDocument::Clear();
    m_lVer=0;
    return;
    }

/*
 * CCosmoDoc::ULoad
 *
 * Purpose:
 *  Loads a given document without any user interface overwriting
 *  the previous contents of the Polyline window.  We do this by
 *  opening the file and telling the Polyline to load itself from
 *  that file.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load, NULL if the file
 *                  is new and untitled.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_...
 */

UINT CCosmoDoc::ULoad(BOOL fChangeFile, LPTSTR pszFile)
    {
    HRESULT         hr;
    LPSTORAGE       pIStorage;

    if (NULL==pszFile)
        {
        //For a new untitled document, just rename ourselves.
        Rename(NULL);
        m_lVer=VERSIONCURRENT;
        return DOCERR_NONE;
        }

    /*
     * If not a Compound File, open the file using STGM_CONVERT in
     * transacted mode to see old files as a storage with one stream
     * called "CONTENTS" (which is conveniently the name we use
     * in the new files).  We must use STGM_TRANSACTED here or else
     * the old file will be immediately converted on disk:  we only
     * want a converted image in memory from which to read.  In
     * addition, note that we need STGM_READWRITE as well since
     * conversion is inherently a write operation.
     */

    pIStorage=NULL;
    OLECHAR pwcsFile[MAX_PATH];
#if defined(WIN32) && !defined(UNICODE)
    mbstowcs(pwcsFile, pszFile, MAX_PATH);
#else
    pwcsFile = pszFile;
#endif
    if (NOERROR!=StgIsStorageFile(pwcsFile))
        {
        hr=StgCreateDocfile(pwcsFile,STGM_TRANSACTED | STGM_READWRITE
            | STGM_CONVERT | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

        if (FAILED(hr))
            {
            //If denied write access, try to load the old way
            if (STG_E_ACCESSDENIED==GetScode(hr))
                m_lVer=m_pPL->ReadFromFile(pszFile);
            else
                return DOCERR_COULDNOTOPEN;
            }
        }
    else
        {
        hr=StgOpenStorage(pwcsFile, NULL, STGM_DIRECT | STGM_READ
            | STGM_SHARE_EXCLUSIVE, NULL, 0, &pIStorage);

        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;
        }

    if (NULL!=pIStorage)
        {
        m_lVer=m_pPL->ReadFromStorage(pIStorage);
        pIStorage->Release();
        }

    if (POLYLINE_E_READFAILURE==m_lVer)
        return DOCERR_READFAILURE;

    if (POLYLINE_E_UNSUPPORTEDVERSION==m_lVer)
        return DOCERR_UNSUPPORTEDVERSION;

    if (fChangeFile)
        Rename(pszFile);

    //Importing a file makes things dirty
    FDirtySet(!fChangeFile);

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::USave
 *
 * Purpose:
 *  Writes the file to a known filename, requiring that the user has
 *  previously used FileOpen or FileSaveAs to provide a filename.
 *
 * Parameters:
 *  uType           UINT indicating the type of file the user
 *                  requested to save in the File Save As dialog.
 *  pszFile         LPTSTR under which to save.  If NULL, use the
 *                  current name.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_...
 */

UINT CCosmoDoc::USave(UINT uType, LPTSTR pszFile)
    {
    LONG        lVer, lRet;
    UINT        uTemp;
    BOOL        fRename=TRUE;
    HRESULT     hr;
    LPSTORAGE   pIStorage;

    if (NULL==pszFile)
        {
        fRename=FALSE;
        pszFile=m_szFile;
        }

    /*
     * Type 1 is the current version, type 2 is version 1.0 of the
     * Polyline so we use this to send the right version to
     * CPolyline::WriteToFile.
     */

    switch (uType)
        {
        case 0:         //From Save, use loaded version.
            lVer=m_lVer;
            break;

        case 1:
            lVer=VERSIONCURRENT;
            break;

        case 2:
            lVer=MAKELONG(0, 1);    //1.0
            break;

        default:
            return DOCERR_UNSUPPORTEDVERSION;
        }

    /*
     * If the version the user wants to save is different from the
     * version that we loaded and m_lVer is not zero (new doc),
     * then inform the user of the version change and verify.
     */
    if (0!=m_lVer && m_lVer!=lVer)
        {
        TCHAR       szMsg[128];

        wsprintf(szMsg, PSZ(IDS_VERSIONCHANGE)
            , (UINT)HIWORD(m_lVer), (UINT)LOWORD(m_lVer)
            , (UINT)HIWORD(lVer), (UINT)LOWORD(lVer));

        uTemp=MessageBox(m_hWnd, szMsg, PSZ(IDS_DOCUMENTCAPTION)
            , MB_YESNOCANCEL);

        if (IDCANCEL==uTemp)
            return DOCERR_CANCELLED;

        //If the user won't upgrade, revert to loaded version.
        if (IDNO==uTemp)
            lVer=m_lVer;
        }

    /*
     * For 1.0 files, still use the old code.  For new files, use
     * storages instead
     */
    if (lVer==MAKELONG(0, 1))
        lRet=m_pPL->WriteToFile(pszFile, lVer);
    else
        {
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsFile[MAX_PATH];
        mbstowcs(pwcsFile,pszFile,MAX_PATH);
        hr=StgCreateDocfile(pwcsFile, STGM_DIRECT | STGM_READWRITE
            | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);
#else
        hr=StgCreateDocfile(pszFile, STGM_DIRECT | STGM_READWRITE
            | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);
#endif
        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;

        //Mark this as one of our class
        WriteClassStg(pIStorage, CLSID_Cosmo2Figure);

        //Write user-readable class information
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsTemp[256];
        mbstowcs(pwcsTemp, PSZ(IDS_CLIPBOARDFORMAT), 256);
        WriteFmtUserTypeStg(pIStorage, m_cf
            , pwcsTemp);
#else
        WriteFmtUserTypeStg(pIStorage, m_cf
            , PSZ(IDS_CLIPBOARDFORMAT));
#endif

        lRet=m_pPL->WriteToStorage(pIStorage, lVer);
        pIStorage->Release();
        }

    if (POLYLINE_E_NONE!=lRet)
        return DOCERR_WRITEFAILURE;

    //Saving makes us clean
    FDirtySet(FALSE);

    //Update the known version of this document.
    m_lVer=lVer;

    if (fRename)
        Rename(pszFile);

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::Undo
 *
 * Purpose:
 *  Reverses a previous action.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Undo(void)
    {
    m_pPL->Undo();
    return;
    }

/*
 * CCosmoDoc::FClip
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard, optionally implementing Cut by deleting the
 *  data in the current window after rendering.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *  fCut            BOOL indicating cut (TRUE) or copy (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::FClip(HWND hWndFrame, BOOL fCut)
    {
    BOOL            fRet=TRUE;
    HGLOBAL         hMem;
    UINT            i;
    static UINT     rgcf[3]={0, CF_METAFILEPICT, CF_BITMAP};
    const UINT      cFormats=3;

    //CHAPTER7MOD
    static DWORD    rgtm[3]={TYMED_HGLOBAL, TYMED_MFPICT, TYMED_GDI};
    LPDATAOBJECT    pIDataObject;
    HRESULT         hr;
    STGMEDIUM       stm;
    FORMATETC       fe;

    hr=CoCreateInstance(CLSID_DataTransferObject
        , NULL, CLSCTX_INPROC_SERVER
        , IID_IDataObject, (PPVOID)&pIDataObject);

    if (FAILED(hr))
        return NULL;
    //End CHAPTER7MOD

    rgcf[0]=m_cf;

    for (i=0; i < cFormats; i++)
        {
        //Copy private data first.
        hMem=RenderFormat(rgcf[i]);

        if (NULL!=hMem)
            {
            //CHAPTER7MOD
            stm.hGlobal=hMem;
            stm.tymed=rgtm[i];
            stm.pUnkForRelease=NULL;

            SETDefFormatEtc(fe, rgcf[i], rgtm[i]);

            pIDataObject->SetData(&fe, &stm, TRUE);
            //End CHAPTER7MOD
            }
        }

    //CHAPTER7MOD
    fRet=SUCCEEDED(OleSetClipboard(pIDataObject));
    pIDataObject->Release();
    //End CHAPTER7MOD

    //Delete our current data if copying succeeded.
    if (fRet && fCut)
        {
        m_pPL->New();
        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CCosmoDoc::RenderFormat
 *
 * Purpose:
 *  Renders a specific clipboard format into global memory.
 *
 * Parameters:
 *  cf              UINT format to render.
 *
 * Return Value:
 *  HGLOBAL         Global memory handle containing the data.
 */

HGLOBAL CCosmoDoc::RenderFormat(UINT cf)
    {
    HGLOBAL     hMem;

    if (cf==m_cf)
        {
        m_pPL->DataGetMem(VERSIONCURRENT, &hMem);
        return hMem;
        }

    switch (cf)
        {
        case CF_METAFILEPICT:
            return m_pPL->RenderMetafilePict();

        case CF_BITMAP:
            return (HGLOBAL)m_pPL->RenderBitmap();
        }

    return NULL;
    }

/*
 * CCosmoDoc::FQueryPaste
 *
 * Purpose:
 *  Determines if we can paste data from the clipboard.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CCosmoDoc::FQueryPaste(void)
    {
    //CHAPTER7MOD
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    fRet=FQueryPasteFromData(pIDataObject);
    pIDataObject->Release();
    return fRet;
    //End CHAPTER7MOD
    }

//CHAPTER7MOD
/*
 * CCosmoDoc::FQueryPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Determines if we can paste data from a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which we might want to paste.
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CCosmoDoc::FQueryPasteFromData(LPDATAOBJECT pIDataObject)
    {
    FORMATETC       fe;

    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    return (NOERROR==pIDataObject->QueryGetData(&fe));
    }
//End CHAPTER7MOD

/*
 * CCosmoDoc::FPaste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  to the current figure in the editor window.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::FPaste(HWND hWndFrame)
    {
    //CHAPTER7MOD
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    fRet=FPasteFromData(pIDataObject);
    pIDataObject->Release();

    //End CHAPTER7MOD
    return fRet;
    }

//CHAPTER7MOD
/*
 * CCosmoDoc::FPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Retrieves the private data format from a data object and sets
 *  it to the current figure in the editor window.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::FPasteFromData(LPDATAOBJECT pIDataObject)
    {
    FORMATETC       fe;
    STGMEDIUM       stm;
    BOOL            fRet;

    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    fRet=SUCCEEDED(pIDataObject->GetData(&fe, &stm));

    if (fRet && NULL!=stm.hGlobal)
        {
        m_pPL->DataSetMem(stm.hGlobal, FALSE, FALSE, TRUE);
        ReleaseStgMedium(&stm);
        FDirtySet(TRUE);
        }

    return fRet;
    }
//End CHAPTER7MOD

/*
 * CCosmoDoc::ColorSet
 *
 * Purpose:
 *  Changes a color used in our contained Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color.
 *
 * Return Value:
 *  COLORREF        Previous color for the given index.
 */

COLORREF CCosmoDoc::ColorSet(UINT iColor, COLORREF cr)
    {
    return m_pPL->ColorSet(iColor, cr);
    }

/*
 * CCosmoDoc::ColorGet
 *
 * Purpose:
 *  Retrieves a color currently in use in the Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to retrieve.
 *
 * Return Value:
 *  COLORREF        Current color for the given index.
 */

COLORREF CCosmoDoc::ColorGet(UINT iColor)
    {
    return m_pPL->ColorGet(iColor);
    }

/*
 * CCosmoDoc::LineStyleSet
 *
 * Purpose:
 *  Changes the line style currently used in the Polyline
 *
 * Parameters:
 *  iStyle          UINT index of the new line style to use.
 *
 * Return Value:
 *  UINT            Previous line style.
 */

UINT CCosmoDoc::LineStyleSet(UINT iStyle)
    {
    return m_pPL->LineStyleSet(iStyle);
    }

/*
 * CCosmoDoc::LineStyleGet
 *
 * Purpose:
 *  Retrieves the line style currently used in the Polyline
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Current line style.
 */

UINT CCosmoDoc::LineStyleGet(void)
    {
    return m_pPL->LineStyleGet();
    }

/*
 * CPolylineAdviseSink::CPolylineAdviseSink
 * CPolylineAdviseSink::~CPolylineAdviseSink
 *
 * Constructor Parameters:
 *  pv              LPVOID to store in this object
 */

CPolylineAdviseSink::CPolylineAdviseSink(LPVOID pv)
    {
    m_pv=pv;
    return;
    }

CPolylineAdviseSink::~CPolylineAdviseSink(void)
    {
    return;
    }

/*
 * CPolylineAdviseSink::OnPointChange
 *
 * Purpose:
 *  Informs the document that the polyline added or removed a point.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnPointChange(void)
    {
    PCDocument      pDoc=(PCDocument)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnSizeChange
 *
 * Purpose:
 *  Informs the document that the polyline changed size.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnSizeChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;
    RECT            rc;
    DWORD           dwStyle;

    /*
     * Polyline window is informing us that it changed size in
     * response to setting it's data.  Therefore we have to
     * size ourselves accordingly but without moving the screen
     * position of the polyline window.
     */

    pDoc->m_fNoSize=TRUE;

    //Set the document window size.
    GetWindowRect(pDoc->m_pPL->Window(), &rc);
    InflateRect(&rc, 8, 8);

    //Adjust for a window sans menu
    dwStyle=GetWindowLong(pDoc->m_hWnd, GWL_STYLE);
    AdjustWindowRect(&rc, dwStyle, FALSE);

    SetWindowPos(pDoc->m_hWnd, NULL, 0, 0, rc.right-rc.left
        , rc.bottom-rc.top, SWP_NOMOVE | SWP_NOZORDER);

    if (NULL!=pDoc->m_pAdv)
        pDoc->m_pAdv->OnSizeChange(pDoc, &rc);

    pDoc->m_fNoSize=FALSE;
    pDoc->FDirtySet(TRUE);

    return;
    }

/*
 * CPolylineAdviseSink::OnDataChange
 *
 * Purpose:
 *  Informs the document that the polyline data changed.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnDataChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    if (NULL!=pDoc->m_pAdv)
        pDoc->m_pAdv->OnDataChange(pDoc);

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnColorChange
 *
 * Purpose:
 *  Informs the document that the polyline data changed a color.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnColorChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnLineStyleChange
 *
 * Purpose:
 *  Informs the document that the polyline changed its line style.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnLineStyleChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }


POLYLINE.CPP

/*
 * POLYLINE.CPP
 * Cosmo Chapter 7
 *
 * Implementation of the CPolyline class.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CPolyline:CPolyline
 * CPolyline::~CPolyline
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application we're in.
 */

CPolyline::CPolyline(HINSTANCE hInst)
    : CWindow(hInst)
    {
    m_pAdv=NULL;
    m_hWnd=NULL;
    return;
    }

CPolyline::~CPolyline(void)
    {
    return;
    }

/*
 * CPolyline::FInit
 *
 * Purpose:
 *  Instantiates a polyline window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags
 *  uID             UINT ID to associate with this window
 *  pAdv            PCPolylineAdviseSink of the sink wanting our
 *                  notifications.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPolyline::FInit(HWND hWndParent, LPRECT pRect, DWORD dwStyle
    , UINT uID, PCPolylineAdviseSink pAdv)
    {
    m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY, SZCLASSPOLYLINE
        , SZCLASSPOLYLINE, dwStyle, pRect->left, pRect->top
        , pRect->right-pRect->left, pRect->bottom-pRect->top
        , hWndParent, (HMENU)uID, m_hInst, this);

    if (NULL!=m_hWnd)
        m_pAdv=pAdv;

    return (NULL!=m_hWnd);
    }

/*
 * CPolyline::New
 *
 * Purpose:
 *  Cleans out and reinitializes the data to defaults.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolyline::New(void)
    {
    UINT        i;
    RECT        rc;

    m_pl.wVerMaj=VERSIONMAJOR;
    m_pl.wVerMin=VERSIONMINOR;

    //Our rectangle is the size of our window's client area.
    GetClientRect(m_hWnd, &rc);
    RECTTORECTS(rc, m_pl.rc);

    //Clean out the POLYLINE structure and repaint the window.
    for (i=0; i< CPOLYLINEPOINTS; i++)
        {
        m_pl.rgpt[i].x=0;
        m_pl.rgpt[i].y=0;
        }

    m_pl.cPoints      =0;
    m_pl.rgbBackground=GetSysColor(COLOR_WINDOW);
    m_pl.rgbLine      =GetSysColor(COLOR_WINDOWTEXT);
    m_pl.iLineStyle   =PS_SOLID;

    InvalidateRect(m_hWnd, NULL, TRUE);
    UpdateWindow(m_hWnd);

    //Inform the advise sink of this data change.
    if (NULL!=m_pAdv)
        m_pAdv->OnDataChange();

    return;
    }

/*
 * CPolyline::Undo
 *
 * Purpose:
 *  Reverses previous actions in a Polyline.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if we can Undo more, FALSE otherwise.
 */

BOOL CPolyline::Undo(void)
    {
    //Decrement the number of active points and repaint.
    if (m_pl.cPoints > 0)
        {
        m_pl.cPoints--;
        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    if (NULL!=m_pAdv)
        m_pAdv->OnPointChange();

    //Return if we can undo any more.
    return (0!=m_pl.cPoints);
    }

/*
 * CPolyline::ReadFromStorage
 *
 * Purpose:
 *  Loads our data (any known version) from an IStorage returning
 *  the version number of the data or an error value.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE from which we'll read.
 *
 * Return Value:
 *  LONG            Version number or negative POLYLINE_E_* value.
 */

LONG CPolyline::ReadFromStorage(LPSTORAGE pIStorage)
    {
    POLYLINEDATA    pl;
    ULONG           cb=(ULONG)-1;
    ULONG           cbExpect=0;
    LPSTREAM        pIStream;
    HRESULT         hr;
    LARGE_INTEGER   li;

    if (NULL==pIStorage)
        return POLYLINE_E_READFAILURE;

    //Open the CONTENTS stream
    hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT | STGM_READ
        | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return POLYLINE_E_READFAILURE;

    //Read version numbers and seek back to file beginning.
    hr=pIStream->Read(&pl, 2*sizeof(WORD), &cb);

    LISet32(li, 0);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);

    if (FAILED(hr) || 2*sizeof(WORD)!=cb)
        {
        pIStream->Release();
        return POLYLINE_E_READFAILURE;
        }

    /*
     * For version 2.0, read the entire file.  For version 1.0 read
     * the file up to CBPOLYLINEDATAVER10.  For anything else, give
     * an error.
     */

    switch (pl.wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (pl.wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (pl.wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        {
        pIStream->Release();
        return POLYLINE_E_UNSUPPORTEDVERSION;
        }

    hr=pIStream->Read(&pl, cbExpect, &cb);
    pIStream->Release();

    if (cbExpect!=cb)
        return POLYLINE_E_READFAILURE;

    /*
     * If we loaded successfully, make the data current.  By using
     * DataSet we centralize our version upgrading.  We size the
     * polyline window to the data AND notify the document so it
     * sizes to the polyline.
     */
    DataSet(&pl, TRUE, TRUE);

    //Return what version we just loaded.
    return MAKELONG(pl.wVerMin, pl.wVerMaj);
    }

/*
 * CPolyline::WriteToStorage
 *
 * Purpose:
 *  Ignorantly writes our current data into an storage in a
 *  particular version.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which to save.
 *  lVer            LONG providing version number Major (HI) and
 *                  Minor (Low)
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::WriteToStorage(LPSTORAGE pIStorage, LONG lVer)
    {
    ULONG           cb;
    ULONG           cbExpect=0;
    WORD            wVerMaj=HIWORD(lVer);
    WORD            wVerMin=LOWORD(lVer);
    POLYLINEDATA    pl;
    LPSTREAM        pIStream;
    HRESULT         hr;

    if (NULL==pIStorage)
        return POLYLINE_E_READFAILURE;

    //Get a copy of our data in the version we're going to save.
    DataGet(&pl, lVer);

    switch (wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT | STGM_CREATE
        | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pIStream);

    if (FAILED(hr))
        return POLYLINE_E_WRITEFAILURE;

    hr=pIStream->Write(&pl, cbExpect, &cb);
    pIStream->Release();

    if (FAILED(hr) || cbExpect!=cb)
        return POLYLINE_E_WRITEFAILURE;

    return POLYLINE_E_NONE;
    }

/*
 * CPolyline::ReadFromFile
 *
 * Purpose:
 *  Loads our data (any known version) from a file handle returning
 *  the version number of the data or an error value.
 *
 * Parameters:
 *  pszFile         LPTSTR of the file to open.
 *
 * Return Value:
 *  LONG            Version number or negative POLYLINE_E_* value.
 */

LONG CPolyline::ReadFromFile(LPTSTR pszFile)
    {
    OFSTRUCT        of;
    HFILE           hFile;
    POLYLINEDATA    pl;
    UINT            cb=(UINT)-1;
    UINT            cbExpect=0;

    if (NULL==pszFile)
        return POLYLINE_E_READFAILURE;

    //OpenFileW is a member of CPolyline.
    hFile=OpenFileW(pszFile, &of, OF_READ);

    if (HFILE_ERROR==hFile)
        return POLYLINE_E_READFAILURE;

    //Read version numbers and seek back to file beginning.
    cb=_lread(hFile, &pl, 2*sizeof(WORD));
    _llseek(hFile, 0L, 0);

    if (2*sizeof(WORD)!=cb)
        {
        _lclose(hFile);
        return POLYLINE_E_READFAILURE;
        }

    /*
     * For version 2.0, read the entire file.  For version 1.0 read
     * the file up to CBPOLYLINEDATAVER10.  For anything else, give
     * an error.
     */

    switch (pl.wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (pl.wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (pl.wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        {
        _lclose(hFile);
        return POLYLINE_E_UNSUPPORTEDVERSION;
        }

    cb=_lread(hFile, &pl, cbExpect);
    _lclose(hFile);

    if (cbExpect!=cb)
        return POLYLINE_E_READFAILURE;

    /*
     * If we loaded successfully, make the data current.  By using
     * DataSet we centralize our version upgrading.  We size the
     * polyline window to the data AND notify the document so it
     * sizes to the polyline.
     */
    DataSet(&pl, TRUE, TRUE);

    //Return what version we just loaded.
    return MAKELONG(pl.wVerMin, pl.wVerMaj);
    }

/*
 * CPolyline::WriteToFile
 *
 * Purpose:
 *  Ignorantly writes our current data into an opened file in a
 *  particular version.
 *
 * Parameters:
 *  pszFile         LPTSTR filename in which to store the data.
 *  lVer            LONG providing version number Major (HI)
 *                  and Minor (Low)
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::WriteToFile(LPTSTR pszFile, LONG lVer)
    {
    OFSTRUCT        of;
    HFILE           hFile;
    UINT            cb;
    UINT            cbExpect=0;
    WORD            wVerMaj=HIWORD(lVer);
    WORD            wVerMin=LOWORD(lVer);
    POLYLINEDATA    pl;

    if (NULL==pszFile)
        return POLYLINE_E_READFAILURE;

    //Get a copy of our data in the version we're going to save.
    DataGet(&pl, lVer);

    switch (wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    hFile=OpenFileW(pszFile, &of, OF_CREATE | OF_WRITE);

    if (HFILE_ERROR==hFile)
        return DOCERR_COULDNOTOPEN;

    cb=_lwrite(hFile, (LPCSTR)&pl, cbExpect);
    _lclose(hFile);

    return (cbExpect==cb) ? POLYLINE_E_NONE
        : POLYLINE_E_WRITEFAILURE;
    }

/*
 * CPolyline::OpenFileW (Private)
 *
 * Purpose:
 *  Under Win32, OpenFile does not take Unicode strings.  This
 *  function converts a Unicode string into an ANSI string and
 *  calls OpenFile.  This just maps to OpenFile without Unicode.
 *
 * Parameters, Return Value:
 *  Same as OpenFile.
 */

HFILE CPolyline::OpenFileW(LPTSTR pszFile, LPOFSTRUCT pof
    , UINT uFlags)
    {
   #ifdef UNICODE
    CHAR        szTemp[CCHPATHMAX];

    UNICODETOANSI(pszFile, szTemp, CCHPATHMAX);
    return OpenFile(szTemp, pof, uFlags);
   #else
    return OpenFile(pszFile, pof, uFlags);
   #endif
    }

/*
 * CPolyline::DataSet
 *
 * Purpose:
 *  Sets the current data in this Polyline to a given structure.
 *
 * Parameters:
 *  ppl             PPOLYLINEDATA to initialize to.
 *  fSizeToData     BOOL indicating if we're to size to the data or
 *                  scale it.
 *  fNotify         BOOL indicating if we're to send an advise on
 *                  this change.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataSet(PPOLYLINEDATA ppl, BOOL fSizeToData
    , BOOL fNotify)
    {
    RECTS       rcs;
    RECT        rc;
    UINT        i;

    /*
     * Copy the structure in ppl and repaint to reflect the new
     * point set.  Note that unlike the RectSet message, we do no
     * scaling, assuming that the rect in the structure is
     * appropriate for the data.
     */

    if (NULL==ppl)
        return POLYLINE_E_INVALIDPOINTER;

    //Preserve the old rectangle
    rcs=m_pl.rc;

    /*
     * For version 2.0 we perform a straight copy.  For version
     * 1.0 we copy the 1.0 structure and fill in defaults for
     * the 2.0 additions.
     */

    switch (ppl->wVerMaj)
        {
        case VERSIONMAJOR:          //2.x
            switch (ppl->wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    m_pl=*ppl;
                    break;

                default:
                    return POLYLINE_E_UNSUPPORTEDVERSION;
                }
            break;

        case 1:                     //1.x
            switch (ppl->wVerMin)
                {
                case 0:             //1.0
                    *((PPOLYLINEDATA10)&m_pl)=
                        *((PPOLYLINEDATA10)ppl);

                    /*
                     * Update this structure to 2.0.  Note that we
                     * assume whoever loaded us to save the loaded
                     * version so it can later ask what version
                     * the user wants to save.
                     */
                    m_pl.wVerMaj=VERSIONMAJOR;
                    m_pl.wVerMin=VERSIONMINOR;

                    /*
                     * Version 1.0 stored rc in parent coordinates.
                     * We need those now in our client coodinates.
                     */
                    RECTSTORECT(m_pl.rc, rc);
                    OffsetRect(&rc, -m_pl.rc.left, -m_pl.rc.top);

                    /*
                     * 1.0 data had points relative to size of the
                     * rectangle.  We need to scale these to 0-32767
                     * independent of the rectangle for the version
                     * upgrade.
                     */
                    for (i=0; i < m_pl.cPoints; i++)
                        PointScale(&rc, &m_pl.rgpt[i], FALSE);

                    RECTTORECTS(rc, m_pl.rc);

                    //New 2.0 features
                    m_pl.rgbBackground=GetSysColor(COLOR_WINDOW);
                    m_pl.rgbLine=GetSysColor(COLOR_WINDOWTEXT);
                    m_pl.iLineStyle=PS_SOLID;
                    break;

                default:
                    return POLYLINE_E_UNSUPPORTEDVERSION;
                }
            break;

        default:
            return POLYLINE_E_UNSUPPORTEDVERSION;
        }

    //Inform our parent of the data change
    if (NULL!=m_pAdv)
        m_pAdv->OnDataChange();

    /*
     * If we're scaling the window to fit the data, then use
     * RectSet passing our current rectangle as the new one.
     * That makes sure that the data won't change but that the
     * window is resized.
     */

    if (fSizeToData)
        {
        POINT       pt;

        /*
         * Get our offset in the parent window so we can RectSet
         * to the right place since RectSet expects rectangle in
         * parent coordinates and we get it in client coordinates.
         */
        GetWindowRect(m_hWnd, &rc);
        pt.x=rc.left;
        pt.y=rc.top;
        ScreenToClient(GetParent(m_hWnd), &pt);
        RECTSTORECT(m_pl.rc, rc);
        OffsetRect(&rc, pt.x, pt.y);

        //This will also cause a repaint.
        RectSet(&rc, fNotify);
        }
    else
        {
        //Make sure we're updated.
        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    return POLYLINE_E_NONE;
    }

/*
 * CPolyline::DataGet
 *
 * Purpose:
 *  Retrieves the Polyline's current data.
 *
 * Parameters:
 *  ppl             PPOLYLINEDATA into which we copy the data.
 *  lVer            LONG version of the data to retrieve.  Use
 *                  VERSIONCURRENT to retrieve the most current.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value
 */

LONG CPolyline::DataGet(PPOLYLINEDATA ppl, LONG lVer)
    {
    UINT        i;
    RECT        rc;

    //Retieve the current version
    if (lVer==MAKELONG(VERSIONMINOR, VERSIONMAJOR)
        || VERSIONCURRENT==lVer)
        {
        *ppl=m_pl;
        return POLYLINE_E_NONE;
        }

    //Check for versions we support, 1.x
    if (HIWORD(lVer)!=1)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    if (LOWORD(lVer)==0)    //Check for 1.0
        {
        //Do 2.0 to 1.0 conversion
        *((PPOLYLINEDATA10)ppl)=*((PPOLYLINEDATA10)&m_pl);

        RECTSTORECT(ppl->rc, rc);

        //Get the parent coordinates of our rectangle
        if (!IsWindow(m_hWnd))
            OffsetRect(&rc, 8, 8); //This is always the offset.
        else
            RectGet(&rc);

        /*
         * 1.0 data has points relative to size of the rectangle.
         * We need to scale these from 0-32767 so we have the
         * right values.
         */
        for (i=0; i < ppl->cPoints; i++)
            PointScale(&rc, &ppl->rgpt[i], TRUE);

        RECTTORECTS(rc, ppl->rc);

        //Insure old version numbers.
        ppl->wVerMaj=1;
        ppl->wVerMin=0;

        ((PPOLYLINEDATA10)ppl)->fDrawEntire=TRUE;
        return POLYLINE_E_NONE;
        }

    return POLYLINE_E_UNSUPPORTEDVERSION;
    }

/*
 * CPolyline::DataSetMem
 *
 * Purpose:
 *  Sets the Polyline's data using a global memory handle
 *  instead of a pointer.
 *
 * Parameters:
 *  hMem            HGLOBAL containing the data.
 *  fFree           BOOL indicating if we're to free the data.
 *                  The memory will be freed regardless of any
 *                  error returned from here.
 *  fSizeToData     BOOL indicating if we're to size to the data
 *                  or scale it.
 *  fNotify         BOOL indicating if we're to send an advise
 *                  on this change.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataSetMem(HGLOBAL hMem, BOOL fFree
    , BOOL fSizeToData, BOOL fNotify)
    {
    PPOLYLINEDATA   ppl;
    LONG            lRet=POLYLINE_E_INVALIDPOINTER;

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        lRet=DataSet(ppl, fSizeToData, fNotify);

        GlobalUnlock(hMem);

        if (fFree)
            GlobalFree(hMem);
        }

    return lRet;
    }

/*
 * CPolyline::DataGetMem
 *
 * Purpose:
 *  Retrieves the Polyline's data in a global memory handle.
 *
 * Parameters:
 *  lVer            LONG version of data to retrieve.
 *  phMem           HGLOBAL * in which to store the handle.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataGetMem(LONG lVer, HGLOBAL *phMem)
    {
    HGLOBAL         hMem;
    PPOLYLINEDATA   ppl;
    LONG            lRet;

    if (NULL==phMem)
        return POLYLINE_E_INVALIDPOINTER;

    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
        , CBPOLYLINEDATA);

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        lRet=DataGet(ppl, lVer);
        GlobalUnlock(hMem);

        if (POLYLINE_E_NONE!=lRet)
            {
            GlobalFree(hMem);
            hMem=NULL;
            }
        }

    *phMem=hMem;
    return lRet;
    }

/*
 * CPolyline::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectGet(LPRECT pRect)
    {
    RECT        rc;
    POINT       pt;

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_hWnd, &rc);
    pt.x=rc.left;
    pt.y=rc.top;
    ScreenToClient(GetParent(m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return;
    }

/*
 * CPolyline::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  None
 */

void CPolyline::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return;
    }

/*
 * CPolyline::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;
    RECT        rc;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, pRect->left, pRect->top
        , cx, cy, SWP_NOZORDER);

    SetRect(&rc, 0, 0, cx, cy);
    RECTTORECTS(rc, m_pl.rc);

    if (fNotify && NULL!=m_pAdv)
        m_pAdv->OnSizeChange();

    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }

/*
 * CPolyline::SizeSet
 *
 * Purpose:
 *  Sets a new size for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPolyline::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
        , SWP_NOMOVE | SWP_NOZORDER);

    if (fNotify && NULL!=m_pAdv)
        m_pAdv->OnSizeChange();

    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }

/*
 * CPolyline::ColorSet
 *
 * Purpose:
 *  Changes for background or line color in the Polyline
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color to use.
 *
 * Return Value:
 *  COLORREF        Previous color for the index iColor.
 */

COLORREF CPolyline::ColorSet(UINT iColor, COLORREF cr)
    {
    COLORREF    crRet;

    switch (iColor)
        {
        case POLYLINECOLOR_BACKGROUND:
            crRet=m_pl.rgbBackground;
            m_pl.rgbBackground=cr;
            break;

        case POLYLINECOLOR_LINE:
            crRet=m_pl.rgbLine;
            m_pl.rgbLine=cr;
            break;
        }

    //If the color changed, repaint
    if (crRet!=cr)
        {
        if (NULL!=m_pAdv)
            m_pAdv->OnColorChange();

        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    return crRet;
    }

/*
 * CPolyline::ColorGet
 *
 * Purpose:
 *  Retrieves one of the colors currently in use by the Polyline.
 *
 * Parameters:
 *  iColor          UINT identifying the color of interest.
 *
 * Return Value:
 *  COLORREF        Current color for iColor.
 */

COLORREF CPolyline::ColorGet(UINT iColor)
    {
    COLORREF    crRet;

    crRet=(POLYLINECOLOR_BACKGROUND==iColor)
        ? m_pl.rgbBackground : m_pl.rgbLine;

    return crRet;
    }

/*
 * CPolyline::LineStyleSet
 *
 * Purpose:
 *  Changes the line style in use by the Polyline
 *
 * Parameters:
 *  iStyle          UINT style of the line to use.
 *
 * Return Value:
 *  UINT            Previous style.
 */

UINT CPolyline::LineStyleSet(UINT iStyle)
    {
    UINT        uRet=(UINT)m_pl.iLineStyle;

    //Validate the line style
    if (PS_SOLID==iStyle || PS_DASH==iStyle || PS_DOT==iStyle
        || PS_DASHDOT==iStyle || PS_DASHDOTDOT==iStyle)
        {
        m_pl.iLineStyle=iStyle;

        if (uRet!=(UINT)m_pl.iLineStyle)
            {
            if (NULL!=m_pAdv)
                m_pAdv->OnLineStyleChange();

            InvalidateRect(m_hWnd, NULL, TRUE);
            UpdateWindow(m_hWnd);
            }
        }

    return uRet;
    }

/*
 * CPolyline::LineStyleGet
 *
 * Purpose:
 *  Retrieves the current line style in use in the Polyline
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Current line style.
 */

UINT CPolyline::LineStyleGet(void)
    {
    return m_pl.iLineStyle;
    }

/*
 * CPolyline::RenderBitmap
 *
 * Purpose:
 *  Creates a bitmap image of the current Polyline.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HBITMAP         Handle to the newly rendered bitmap.
 */

HBITMAP CPolyline::RenderBitmap(void)
    {
    HDC         hDC;
    HDC         hMemDC;
    HBITMAP     hBmp;
    RECT        rc;
    HGDIOBJ     hObj;

    //Render a bitmap the size of the current rectangle.
    hDC=GetDC(m_hWnd);
    hMemDC=CreateCompatibleDC(hDC);

    GetClientRect(m_hWnd, &rc);
    hBmp=CreateCompatibleBitmap(hDC, rc.right, rc.bottom);

    if (NULL!=hBmp)
        {
        //Draw the POLYLINEDATA into the bitmap.
        hObj=SelectObject(hMemDC, hBmp);
        Draw(hMemDC, FALSE, TRUE);
        SelectObject(hMemDC, hObj);
        }

    DeleteDC(hMemDC);
    ReleaseDC(m_hWnd, hDC);

    return hBmp;
    }

/*
 * CPolyline::RenderMetafile
 *
 * Purpose:
 *  Renders the current image of the Polyline into a metafile.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HMETAFILE       Handle to the newly created metafile.
 */

HMETAFILE CPolyline::RenderMetafile(void)
    {
    HDC         hDC;
    HMETAFILE   hMF;
    RECT        rc;

    //Create a memory metafile and return its handle.
    hDC=(HDC)CreateMetaFile(NULL);
    hMF=NULL;

    if (NULL!=hDC)
        {
        /*
         * This is absolutely essential to the metafile so it
         * can be scaled in the clipboard and any destination

* application.
         */
        SetMapMode(hDC, MM_ANISOTROPIC);
        GetClientRect(m_hWnd, &rc);
        SetWindowOrgEx(hDC, 0, 0, NULL);
        SetWindowExtEx(hDC, rc.right, rc.bottom, NULL);

        Draw(hDC, TRUE, TRUE);
        hMF=CloseMetaFile(hDC);
        }

    return hMF;
    }

/*
 * CPolyline::RenderMetafilePict
 *
 * Purpose:
 *  Renders the current Polyline into a METAFILEPICT structure in
 *  global memory.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HGLOBAL         Memory containing the METAFILEPICT structure.
 */

HGLOBAL CPolyline::RenderMetafilePict(void)
    {
    HGLOBAL         hMem;
    HMETAFILE       hMF;
    LPMETAFILEPICT  pMF;
    RECT            rc;

    //Get the metafile
    hMF=RenderMetafile();

    if (NULL==hMF)
        return NULL;

    //Allocate the METAFILEPICT structure.
    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
        , sizeof(METAFILEPICT));

    if (NULL==hMem)
        {
        DeleteMetaFile(hMF);
        return NULL;
        }

    /*
     * Global lock only fails in PMODE if the selector is invalid
     * (like it was discarded) or references a 0 length segment,
     * neither of which can happen here.
     */
    pMF=(LPMETAFILEPICT)GlobalLock(hMem);

    pMF->hMF=hMF;
    pMF->mm=MM_ANISOTROPIC;

    //Insert the extents in MM_HIMETRIC units.
    GetClientRect(m_hWnd, &rc);
    RectConvertMappings(&rc, FALSE);
    pMF->xExt=rc.right;
    pMF->yExt=rc.bottom;

    GlobalUnlock(hMem);
    return hMem;
    }

/*
 * CPolyline::RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device (MM_TEXT) or
 *  HIMETRIC to the other.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  fToDevice       BOOL TRUE to convert from HIMETRIC to device,
 *                  FALSE to convert device to HIMETRIC.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectConvertMappings(LPRECT pRect, BOOL fToDevice)
    {
    HDC      hDC;
    int      iLpx, iLpy;

    if (NULL==pRect)
        return;

    hDC=GetDC(NULL);
    iLpx=GetDeviceCaps(hDC, LOGPIXELSX);
    iLpy=GetDeviceCaps(hDC, LOGPIXELSY);
    ReleaseDC(NULL, hDC);

    if (fToDevice)
        {
        pRect->left=MulDiv(iLpx, pRect->left, HIMETRIC_PER_INCH);
        pRect->top =MulDiv(iLpy, pRect->top , HIMETRIC_PER_INCH);

        pRect->right=MulDiv(iLpx, pRect->right,  HIMETRIC_PER_INCH);
        pRect->bottom=MulDiv(iLpy, pRect->bottom,HIMETRIC_PER_INCH);

       #ifdef NEVER
        /*
         * In this conversion we may get situations where the top
         * coordinate is larger than the bottom, which messes us up.
         */
        if (pRect->bottom < pRect->top)
            {
            iLpy=pRect->top;
            pRect->top=pRect->bottom;
            pRect->bottom=iLpy;
            }
       #endif
        }
    else
        {
        pRect->left=MulDiv(pRect->left, HIMETRIC_PER_INCH, iLpx);
        pRect->top =MulDiv(pRect->top , HIMETRIC_PER_INCH, iLpy);

        pRect->right =MulDiv(pRect->right, HIMETRIC_PER_INCH, iLpx);
        pRect->bottom=MulDiv(pRect->bottom,HIMETRIC_PER_INCH, iLpy);
        }

    return;
    }


POLYLINE.H

/*
 * POLYLINE.H
 * Cosmo Chapter 7
 *
 * Definitions and function prototypes for the PolyLine window
 * class that can be treated like its own control.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _POLYLINE_H_
#define _POLYLINE_H_

//Versioning.
#define VERSIONMAJOR                2
#define VERSIONMINOR                0
#define VERSIONCURRENT              0x00020000

//Classname
#define SZCLASSPOLYLINE             TEXT("polyline")

//Stream Name that holds the data
#define SZSTREAM                    OLESTR("CONTENTS")

#define HIMETRIC_PER_INCH           2540
#define CPOLYLINEPOINTS             20

//Window extra bytes and offsets
#define CBPOLYLINEWNDEXTRA          (sizeof(LONG))
#define PLWL_STRUCTURE              0

//Version 2.0 Polyline Structure
typedef struct tagPOLYLINEDATA
    {
    WORD        wVerMaj;                //Major version number.
    WORD        wVerMin;                //Minor version number.
    WORD        cPoints;                //Number of points.
    short       fReserved;              //Obsolete from v1.0
    RECTS       rc;                     //Rectangle of this figure
    POINTS      rgpt[CPOLYLINEPOINTS];  //Points (0-32767 grid)

    //Version 2.0 additions
    COLORREF    rgbBackground;          //Background color
    COLORREF    rgbLine;                //Line color
    short       iLineStyle;             //Line style
    } POLYLINEDATA, *PPOLYLINEDATA;

#define CBPOLYLINEDATA   sizeof(POLYLINEDATA)
#define CBPOLYLINEDATA20 sizeof(POLYLINEDATA)

//Version 1.0 Polyline Structure
typedef struct tagPOLYLINEDATA10
    {
    WORD        wVerMaj;                //Major version number.
    WORD        wVerMin;                //Minor version number.
    WORD        cPoints;                //Number of points.
    short       fDrawEntire;            //Flag to draw entire figure
    RECTS       rc;                     //Rectangle of this figure
    POINTS      rgpt[CPOLYLINEPOINTS];  //Points (scaled to rc)
    } POLYLINEDATA10, *PPOLYLINEDATA10;

#define CBPOLYLINEDATA10 sizeof(POLYLINEDATA10)

//POLYWIN.CPP
LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM, LPARAM);

class CPolyline : public CWindow
    {
    friend LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM
        , LPARAM);

    private:
        POLYLINEDATA   m_pl;

        class CPolylineAdviseSink * m_pAdv;

    private:
        HFILE     OpenFileW(LPTSTR, LPOFSTRUCT, UINT);
        void      PointScale(LPRECT, LPPOINTS, BOOL);
        void      Draw(HDC, BOOL, BOOL);
        void      RectConvertMappings(LPRECT, BOOL);

    public:
        CPolyline(HINSTANCE);
        ~CPolyline(void);

        BOOL      FInit(HWND, LPRECT, DWORD, UINT
            , class CPolylineAdviseSink *);

        void      New(void);
        BOOL      Undo(void);

        //File functions
        LONG      ReadFromStorage(LPSTORAGE);
        LONG      WriteToStorage(LPSTORAGE, LONG);

        LONG      ReadFromFile(LPTSTR);
        LONG      WriteToFile(LPTSTR, LONG);

        //Data transfer functions
        LONG      DataSet(PPOLYLINEDATA, BOOL, BOOL);
        LONG      DataGet(PPOLYLINEDATA, LONG);
        LONG      DataSetMem(HGLOBAL, BOOL, BOOL, BOOL);
        LONG      DataGetMem(LONG, HGLOBAL *);
        HBITMAP   RenderBitmap(void);
        HMETAFILE RenderMetafile(void);
        HGLOBAL   RenderMetafilePict(void);

        void      RectGet(LPRECT);
        void      SizeGet(LPRECT);
        void      RectSet(LPRECT, BOOL);
        void      SizeSet(LPRECT, BOOL);
        COLORREF  ColorSet(UINT, COLORREF);
        COLORREF  ColorGet(UINT);
        UINT      LineStyleSet(UINT);
        UINT      LineStyleGet(void);
    };

typedef CPolyline *PCPolyline;

//Error values for data transfer functions
#define POLYLINE_E_NONE                    0
#define POLYLINE_E_UNSUPPORTEDVERSION      -1
#define POLYLINE_E_INVALIDPOINTER          -2
#define POLYLINE_E_READFAILURE             -3
#define POLYLINE_E_WRITEFAILURE            -4

class CPolylineAdviseSink
    {
    private:
        LPVOID      m_pv;           //Customizable structure

    public:
        CPolylineAdviseSink(LPVOID);
        ~CPolylineAdviseSink(void);

        void OnPointChange(void);
        void OnSizeChange(void);
        void OnDataChange(void);
        void OnColorChange(void);
        void OnLineStyleChange(void);
    };

typedef CPolylineAdviseSink *PCPolylineAdviseSink;

//Color indices for color messages
#define POLYLINECOLOR_BACKGROUND    0
#define POLYLINECOLOR_LINE          1

#endif  //_POLYLINE_H_


POLYWIN.CPP

/*
 * POLYWIN.CPP
 * Cosmo Chapter 7
 *
 * Window procedure for the polyline drawing window and support
 * functions.  This window is not complicated.  On creation it
 * allocates a block of memory for a POLYLINEDATA structure that
 * contains 20 POINTs.  We do not attempt to reallocate this
 * array at all just to maintain simplicity.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * PolylineWndProc
 *
 * Purpose:
 *  Window procedure for the polyline drawing window.
 */

LRESULT APIENTRY PolylineWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCPolyline      ppl;
    PAINTSTRUCT     ps;
    HDC             hDC;
    POINTS          pt;
    RECT            rc;

    ppl=(PCPolyline)GetWindowLong(hWnd, PLWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppl=(PCPolyline)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PLWL_STRUCTURE, (LONG)ppl);
            ppl->m_hWnd=hWnd;
            ppl->New();
            break;

        case WM_PAINT:
            hDC=BeginPaint(hWnd, &ps);
            ppl->Draw(hDC, FALSE, TRUE);
            EndPaint(hWnd, &ps);
            break;

        case WM_LBUTTONDOWN:
            //Stop if we are already at the limit.
            if (CPOLYLINEPOINTS==ppl->m_pl.cPoints)
                {
                MessageBeep(0);
                break;
                }

            //Convert the points into 0-32767 range
            GetClientRect(hWnd, &rc);
            pt=MAKEPOINTS(lParam);
            ppl->PointScale(&rc, &pt, FALSE);

            ppl->m_pl.rgpt[ppl->m_pl.cPoints++]=pt;

            //Draw the lines to this new point only.
            hDC=GetDC(hWnd);
            ppl->Draw(hDC, FALSE, FALSE);
            ReleaseDC(hWnd, hDC);

            if (NULL!=ppl->m_pAdv)
                ppl->m_pAdv->OnPointChange();

            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPolyline::Draw
 *
 * Purpose:
 *  Paints the current line in the polyline window.
 *
 * Parameters:
 *  hDC             HDC to draw on, could be metafile or printer DC.
 *  fMetafile       BOOL indicating if hDC is a metafile or not, so
 *                  we can avoid operations that RIP.
 *  fEntire         BOOL indicating if we should draw the entire
 *                  figure or not.
 *
 * Return Value:
 *  None
 */

void CPolyline::Draw(HDC hDC, BOOL fMetafile, BOOL fEntire)
    {
    HBRUSH          hBrush;
    HPEN            hPen;
    HGDIOBJ         hObj1, hObj2;
    UINT            i, j;
    UINT            uMM;
    POINTS          pt1, pt2;
    POINT           rgpt[CPOLYLINEPOINTS];
    RECT            rc;

    GetClientRect(m_hWnd, &rc);

    /*
     * Make a 32-bit copy of the points in the POLYLINE itself
     * so we can use DPtoLP and LPtoDP.
     */
    for (i=0; i < m_pl.cPoints; i++)
        {
        rgpt[i].x=m_pl.rgpt[i].x;
        rgpt[i].y=m_pl.rgpt[i].y;
        }

    /*
     * If the mapping mode is not MM_TEXT, convert the points to
     * whatever mapping mode in in effect before drawing.
     * This specifically supports metafiles in MM_ANISOTROPIC.
     */
    uMM=fMetafile ? MM_TEXT : GetMapMode(hDC);

    if (MM_TEXT!=uMM)
        DPtoLP(hDC, rgpt, m_pl.cPoints);

    hPen=CreatePen(m_pl.iLineStyle, 1, m_pl.rgbLine);
    hObj1=SelectObject(hDC, hPen);

    hBrush=CreateSolidBrush(m_pl.rgbBackground);
    hObj2=SelectObject(hDC, hBrush);
    SetBkColor(hDC, m_pl.rgbBackground);

    /*
     * Either draw the entire figure or just a single point.  The
     * entire figure also includes erasing the background
     * completely,since hDC may be a metafile DC.  Drawing a
     * single point just updates the figure for that new point.
     */
    if (fEntire || 0==m_pl.cPoints)
        {
        //Erase the background for bitmaps and metafiles.
        SelectObject(hDC, GetStockObject(NULL_PEN));
        Rectangle(hDC, rc.left, rc.top, rc.right+1, rc.bottom+1);
        SelectObject(hDC, hPen);

        /*
         * If we are drawing the entire figure, then loop through
         * each point drawing a line to each successive point.
         */

        for (i=0; i < m_pl.cPoints; i++)
            {
            for (j=i; j < m_pl.cPoints; j++)
                {
                /*
                 * Not using POINTTOPOINTS since it unfortunately
                 * returns a LONG which doesn't equate to a POINTS.
                 */
                pt1.x=(short)rgpt[i].x;
                pt1.y=(short)rgpt[i].y;
                pt2.x=(short)rgpt[j].x;
                pt2.y=(short)rgpt[j].y;

                PointScale(&rc, &pt1, TRUE);
                PointScale(&rc, &pt2, TRUE);
                MoveToEx(hDC, pt1.x, pt1.y, NULL);
                LineTo(hDC, pt2.x, pt2.y);
                }
            }
        }
    else
        {
        /*
         * If we are only drawing the last point, just cycle once
         * through previous points.
         */

        //Get the last point entered in the array.
        j=m_pl.cPoints-1;
        pt1.x=(short)rgpt[j].x;
        pt1.y=(short)rgpt[j].y;
        PointScale(&rc, &pt1, TRUE);

        for (i=0; i < j; i++)
            {
            pt2.x=(short)rgpt[i].x;
            pt2.y=(short)rgpt[i].y;
            PointScale(&rc, &pt2, TRUE);

            MoveToEx(hDC, pt1.x, pt1.y, NULL);
            LineTo(hDC, pt2.x, pt2.y);
            }
        }

    //If we have one point, draw a dot to indicate it's position.
    if (1==m_pl.cPoints)
        {
        pt1.x=(short)rgpt[0].x;
        pt1.y=(short)rgpt[0].y;
        PointScale(&rc, &pt1, TRUE);
        SetPixel(hDC, pt1.x, pt1.y, m_pl.rgbLine);
        }

    //Note:  m_pl.rgpt is unmodified in this code.

    SelectObject(hDC, hObj1);
    SelectObject(hDC, hObj2);
    DeleteObject(hBrush);
    DeleteObject(hPen);
    return;
    }

/*
 * CPolyline::PointScale
 *
 * Purpose:
 *  Scales a point to or from a relative window coordinate to a
 *  0-32767 coordinate.
 *
 * Parameters:
 *  pRect           LPRECT of the window.
 *  ppt             LPPOINTS to convert
 *  fScaleToWindow  BOOL indicating direction of scaling.
 *
 * Return Value:
 *  None
 */

void CPolyline::PointScale(LPRECT pRect, LPPOINTS ppt
    , BOOL fScaleToWindow)
    {
    DWORD   cx, cy;

    //Window size
    cx=(DWORD)(pRect->right-pRect->left);
    cy=(DWORD)(pRect->bottom-pRect->top);

    //Prevent crashes
    if (0L==cx) cx=1;
    if (0L==cy) cy=1;

    //Must use DWORD to insure proper scaling.
    if (fScaleToWindow)
        {
        ppt->x=(UINT)(((DWORD)ppt->x*cx) >> 15);
        ppt->y=(UINT)(((DWORD)ppt->y*cy) >> 15);
        }
    else
        {
        ppt->x=(UINT)(((DWORD)ppt->x << 15)/cx);
        ppt->y=(UINT)(((DWORD)ppt->y << 15)/cy);
        }

    return;
    }


RESOURCE.H

/*
 * RESOURCE.H
 * Cosmo Chapter 7
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Note that we augment classlib.h which we include before this.

//Position of Window menu.
#undef  WINDOW_MENU
#define WINDOW_MENU             4

//Total number of menus
#undef  CMENUS
#ifdef MDI
#define CMENUS                  6
#else
#define CMENUS                  5
#endif

//Menu command identifiers.

#define IDM_FILEIMPORT          (IDM_CUSTOMFILEMIN)

#define IDM_COLORBACKGROUND     (IDM_CUSTOMMIN)
#define IDM_COLORLINE           (IDM_CUSTOMMIN+1)

#define IDM_LINEMIN             (IDM_CUSTOMMIN+100)
#define IDM_LINESOLID           (IDM_LINEMIN+0)         //PS_SOLID       0
#define IDM_LINEDASH            (IDM_LINEMIN+1)         //PS_DASH        1
#define IDM_LINEDOT             (IDM_LINEMIN+2)         //PS_DOT         2
#define IDM_LINEDASHDOT         (IDM_LINEMIN+3)         //PS_DASHDOT     3
#define IDM_LINEDASHDOTDOT      (IDM_LINEMIN+4)         //PS_DASHDOTDOT  4
#define IDM_LINEMAX             IDM_LINEDASHDOTDOT

/*
 * IDs for StatStrip handling of popup menus:  must be in order of menu
 * CLASSRES.H already defines ID_MENUFILE and ID_MENUEDIT for us.
 */

#undef  ID_MENUWINDOW
#undef  ID_MENUHELP

#define ID_MENUCOLOR            (ID_MENUCUSTOMMIN+0)
#define ID_MENULINE             (ID_MENUCUSTOMMIN+1)
#define ID_MENUWINDOW           (ID_MENUCUSTOMMIN+2)
#define ID_MENUHELP             (ID_MENUCUSTOMMIN+3)

//Stringtable IDs.  Keep sequential for each group.
#define IDS_FRAMEMIN            IDS_STANDARDFRAMEMIN
#define IDS_FILEIMPORT          (IDS_CUSTOMFRAMEMIN+0)
#define IDS_FRAMEMAX            (IDS_CUSTOMFRAMEMIN+0)

#define IDS_DOCUMENTMIN         IDS_STANDARDDOCMIN
#define IDS_VERSIONCHANGE       (IDS_CUSTOMDOCMIN+0)
#define IDS_DOCUMENTMAX         (IDS_CUSTOMDOCMIN+0)

//These are specifically for the StatStrip
#define IDS_STATMESSAGEMIN                  IDS_STANDARDSTATMESSAGEMIN

#define IDS_ITEMMESSAGEFILEIMPORT           (IDS_CUSTOMSTATMESSAGEMIN+0)

#define IDS_MENUMESSAGECOLOR                (IDS_CUSTOMSTATMESSAGEMIN+1)
#define IDS_ITEMMESSAGECOLORBACKGROUND      (IDS_CUSTOMSTATMESSAGEMIN+2)
#define IDS_ITEMMESSAGECOLORLINE            (IDS_CUSTOMSTATMESSAGEMIN+3)

#define IDS_MENUMESSAGELINE                 (IDS_CUSTOMSTATMESSAGEMIN+4)
#define IDS_ITEMMESSAGELINESOLID            (IDS_CUSTOMSTATMESSAGEMIN+5)
#define IDS_ITEMMESSAGELINEDASH             (IDS_CUSTOMSTATMESSAGEMIN+6)
#define IDS_ITEMMESSAGELINEDOT              (IDS_CUSTOMSTATMESSAGEMIN+7)
#define IDS_ITEMMESSAGELINEDASHDOT          (IDS_CUSTOMSTATMESSAGEMIN+8)
#define IDS_ITEMMESSAGELINEDASHDOTDOT       (IDS_CUSTOMSTATMESSAGEMIN+9)

#define IDS_STATMESSAGEMAX                  (IDS_CUSTOMSTATMESSAGEMIN+9)

#endif //_RESOURCE_H_


FRAGMENT.CPP

/*
 * FRAGMENT.CPP
 * Fragmented File Generator Chapter 7
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "fragment.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  This application does nothing
 *  more than pump out garbage into storage as a framented file test.
 *  It has no UI so does not process any messages.
 *
 *  The file created is always called FRAGMENT.BIN.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    CFragment  *pFrag;

    pFrag=new CFragment();

    if (NULL==pFrag)
        return -1;

    if (pFrag->Init())
        {
        if (pFrag->AllocCharArrays())
            {
            if (pFrag->CreateFragmentedFile())
                {
                pFrag->FreeSpaceInFile();
                pFrag->DefragmentFile();
                }
            }
        }

    delete pFrag;
    return 0;
    }

/*
 * CFragment::CFragment
 * CFragment::~CFragment
 *
 * Constructor and Destructor for this application.  The
 * constructor does not have any parameters.
 */

CFragment::CFragment(void)
    {
    m_fInitialized=FALSE;
    m_pIMalloc=NULL;
    m_pch=NULL;
    m_pszScratch=NULL;

    m_cch=256;
    m_cStreams=CSTREAMS;

    lstrcpy(m_szFile, TEXT("FRAGMENT.BIN"));
    m_hCur=NULL;

    return;
    }

CFragment::~CFragment(void)
    {
    if (NULL!=m_hCur)
        SetCursor(m_hCur);

    if (NULL!=m_pIMalloc)
        {
        if (NULL!=m_pch)
            m_pIMalloc->Free(m_pch);

        if (NULL!=m_pszScratch)
            m_pIMalloc->Free(m_pszScratch);

        m_pIMalloc->Release();
        }

    if (m_fInitialized)
        CoUninitialize();

    return;
    }

/*
 * CFragment::Init
 *
 * Purpose:
 *  Calls CoInitialize and CoGetMalloc saving the task allocator.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if initialization worked, FALSE otherwise.
 */

BOOL CFragment::Init(void)
    {
    CHECKVER_COM;

    if (FAILED(CoInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    if (FAILED(CoGetMalloc(MEMCTX_TASK, &m_pIMalloc)))
        return FALSE;

    m_pszScratch=(TCHAR *)m_pIMalloc->Alloc(1024*sizeof(TCHAR));

    if (NULL==m_pszScratch)
        return FALSE;

    m_hCur=SetCursor(LoadCursor(NULL, MAKEINTRESOURCE(IDC_WAIT)));
    return TRUE;
    }

/*
 * CFragmet::AllocCharArrays
 *
 * Purpose:
 *  Allocates CSTREAMS * 256 characters and initializes each 256
 *  charaters with 'A', 'B', etc.  (ANSI characters, not UNICODE).
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the memory was allocated and initialized
 *                  otherwise FALSE.
 */

BOOL CFragment::AllocCharArrays(void)
    {
    char *      pchT;
    UINT        i, j;

    if (NULL==m_pIMalloc)
        return FALSE;

    m_pch=(char *)m_pIMalloc->Alloc(m_cch*m_cStreams);

    if (NULL==m_pch)
        return FALSE;

    pchT=m_pch;

    //Initialize Character Arrays
    for (i=0; i < m_cStreams; i++)
        {
        char    ch=i+'A';

        for (j=0; j < m_cch; j++)
            *pchT++=ch;
        }

    return TRUE;
    }

/*
 * CFragment::CreateFragmentedFile
 *
 * Purpose:
 *  Creates a file with numerous streams that we can fragment.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the file was created, FALSE otherwise.
 */

BOOL CFragment::CreateFragmentedFile(void)
    {
    HRESULT     hr;
    IStorage   *pIStorage;
    IStream    *rgpIStream[CSTREAMS];
    UINT        i, j;

    //Create the file to build
    hr=StgCreateDocfile(m_szFile, STGM_DIRECT | STGM_READWRITE
        | STGM_SHARE_EXCLUSIVE | STGM_CREATE, 0, &pIStorage);

    if (FAILED(hr))
        return FALSE;

    //Create streams in this file.
    for (i=0; i < m_cStreams; i++)
        StreamCreate(pIStorage, i, &rgpIStream[i]);

    /*
     * Now fill "Stream A" with A's, Stream B with B's, etc.,
     * doing it m_cch characters at a time, a total of 20 times for
     * each stream.  This will allow us to examine the binary
     * format of the file before and after defragmentation.
     *
     * This will create a number of streams that are internally
     * fragmented in the file.
     *
     * Some streams may be invalid, but checks for NULL below
     * avoid problems in such cases.
     */

    for (j=0; j < 20; j++)
        {
        for (i=0; i < m_cStreams; i++)
            {
            if (NULL!=rgpIStream[i])
                rgpIStream[i]->Write((m_pch+(m_cch*i)), m_cch, NULL);
            }
        }

    //Close all the streams
    for (i=0; i < m_cStreams; i++)
        {
        if (NULL!=rgpIStream[i])
            rgpIStream[i]->Release();
        }

    //Close the IStorage to close the file.
    pIStorage->Release();

    Message(TEXT("%s created."));
    return TRUE;
    }

/*
 * CFragment::FreeSpaceInFile
 *
 * Purpose:
 *  Deletes some of the streams in the previously created file.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CFragment::FreeSpaceInFile(void)
    {
    HRESULT     hr;
    IStorage   *pIStorage;

    //Reopen the storage
    hr=StgOpenStorage(m_szFile, NULL, STGM_DIRECT | STGM_READWRITE
        | STGM_SHARE_EXCLUSIVE, NULL, 0, &pIStorage);

    if (SUCCEEDED(hr))
        {
        /*
         * Now create some extra space in the file by destroying
         * streams C E G H J M N T X.
         */
        pIStorage->DestroyElement(OLETEXT("Stream C"));
        pIStorage->DestroyElement(OLETEXT("Stream E"));
        pIStorage->DestroyElement(OLETEXT("Stream G"));
        pIStorage->DestroyElement(OLETEXT("Stream H"));
        pIStorage->DestroyElement(OLETEXT("Stream J"));
        pIStorage->DestroyElement(OLETEXT("Stream M"));
        pIStorage->DestroyElement(OLETEXT("Stream N"));
        pIStorage->DestroyElement(OLETEXT("Stream T"));
        pIStorage->DestroyElement(OLETEXT("Stream X"));

        /*
         * The file size will not have changed here, but there
         * will be unused space.
         */

        pIStorage->Release();
        Message(TEXT("Space has been freed in %s."));
        }
    else
        Message(TEXT("Could not reopen %s."));

    return;
    }

/*
 * CFragment::DefragmentFile
 *
 * Purpose:
 *  Does an IStorage::CopyTo from the existing file to a temp
 *  file then copies that temp file as binary back to the source
 *  file.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CFragment::DefragmentFile(void)
    {
    HRESULT     hr;
    IStorage   *pIStorageOld;
    IStorage   *pIStorageNew;
    STATSTG     st;
   #ifndef WIN32
    OFSTRUCT    of;
   #endif
    TCHAR       szTemp[MAX_PATH];

    /*
     * Create a temporary file.  We don't use DELETEONRELEASE
     * in case we have to save it when copying over the old
     * file fails.
     */
    hr=StgCreateDocfile(NULL, STGM_CREATE | STGM_READWRITE
        | STGM_DIRECT | STGM_SHARE_EXCLUSIVE, 0, &pIStorageNew);

    if (FAILED(hr))
        {
        Message(TEXT("Failed to create temp file to copy %s."));
        return;
        }

    //Open the existing file as read-only
    hr=StgOpenStorage(m_szFile, NULL, STGM_DIRECT | STGM_READ
        | STGM_SHARE_DENY_WRITE, NULL, 0, &pIStorageOld);

    if (FAILED(hr))
        {
        pIStorageNew->Release();
        Message(TEXT("Failed to open %s for CopyTo."));
        return;
        }

    /*
     * Compress with CopyTo.  Since the temp is opened in
     * direct mode, changes are immediate.
     */
    hr=pIStorageOld->CopyTo(NULL, NULL, NULL, pIStorageNew);
    pIStorageOld->Release();

    if (FAILED(hr))
        {
        pIStorageNew->Release();
        Message(TEXT("IStorage::CopyTo from %s failed."));
        return;
        }

    //Get the name of the temp file.
    pIStorageNew->Stat(&st, 0);
    pIStorageNew->Release();

    //Delete the old file before copying back to insure truncation
   #ifdef WIN32
    DeleteFile(m_szFile);

   #ifdef WIN32ANSI
    WideCharToMultiByte(CP_ACP, 0, st.pwcsName, -1, szTemp
        , MAX_PATH, NULL, NULL);
   #else
    lstrcpy(szTemp, st.pwcsName);
   #endif

    MoveFile(szTemp, m_szFile);
   #else
    OpenFile(m_szFile, &of, OF_DELETE);
    MyMoveFile(st.pwcsName, m_szFile);
   #endif

    Message(TEXT("Defragementation complete on %s."));
    return;
    }

/*
 * CFragment::StreamCreate
 *
 * Purpose:
 *  Creates a stream with a given identifier in a given IStorage.
 *
 * Parameters:
 *  pIStorage       IStorage * in which to create the stream
 *  i               UINT ID of the stream
 *  ppIStream       IStream ** in which to return the stream.
 *
 * Return Value:
 *  None
 */

void CFragment::StreamCreate(IStorage *pIStorage, UINT i
    , IStream ** ppIStream)
    {
    OLECHAR  szName[40];

    //Name is "Stream A", "Stream B", etc.
   #ifdef WIN32ANSI
    char        szTemp[40];

    wsprintf(szTemp, "Stream %c", i+'A');
    MultiByteToWideChar(CP_ACP, 0, szTemp, -1, szName, 40);
   #else
    wsprintf(szName, TEXT("Stream %c"), i+'A');
   #endif

    pIStorage->CreateStream(szName, STGM_DIRECT
        | STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE
        , 0, 0, ppIStream);

    return;
    }

/*
 * CFragment::Message
 *
 * Purpose:
 *  Outputs a message with the filename included.
 *
 * Parameters:
 *  pszFmt          LPTSTR to a wsprintf format string.
 *
 * Return Value:
 *  None
 */

void CFragment::Message(LPTSTR pszFmt)
    {
    wsprintf(m_pszScratch, pszFmt, m_szFile);
    MessageBox(NULL, m_pszScratch, TEXT("Fragmenter"), MB_OK);
    return;
    }

#ifndef WIN32
/*
 * CFragment::MyMoveFile
 *
 * Purpose:
 *  Low-level MoveFile function for 16-bit Windows that lack
 *  the MoveFile[Ex] API.
 *
 * Parameters:
 *  pszSrc          char * of the source filename,
 *  pszDst          char * of the destination filename,
 *
 * Return Value:
 *  None
 */

void CFragment::MyMoveFile(char *pszSrc, char *pszDst)
    {
    HFILE       hFile1, hFile2;
    ULONG       cbCopy;
    const UINT  cbBlock=4096;
    LPBYTE      pb;
    OFSTRUCT    of;

    //Do a binary copy between files.
    hFile1=_lopen(pszSrc, READ | OF_SHARE_EXCLUSIVE);

    if (HFILE_ERROR==hFile1)
        return;

    //Recreate this file before copying.
    hFile2=_lcreat(m_szFile, 0);

    if (HFILE_ERROR==hFile2)
        {
        _lclose(hFile1);
        return;
        }

    //Seek to end of source file to find length
    cbCopy=_llseek(hFile1, 0, 2);
    _llseek(hFile1, 0, 0);

    pb=(LPBYTE)m_pIMalloc->Alloc(cbBlock);

    if (NULL!=pb)
        {
        //Copy in 4K increments
        while (cbCopy >= (ULONG)cbBlock)
            {
            _lread(hFile1,  pb, cbBlock);
            _lwrite(hFile2, pb, cbBlock);
            cbCopy-=4096;
            }

        _lread(hFile1,  pb, (UINT)cbCopy);
        _lwrite(hFile2, pb, (UINT)cbCopy);
        m_pIMalloc->Free(pb);
        }

    _lclose(hFile2);
    _lclose(hFile1);

    //Delete the temp file
    OpenFile(pszSrc, &of, OF_DELETE);
    return;
    }
#endif


FRAGMENT.H

/*
 * FRAGMENT.H
 * Fragmented File Generator Chapter 7
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _FRAGMENT_H_
#define _FRAGMENT_H_

#define INC_OLE2
#define CHAPTER7
#include <inole.h>
#include <string.h>

#define CSTREAMS    26
#define CCHPATHMAX  256

class CFragment
    {
    private:
        BOOL        m_fInitialized;         //CoInitilize?
        IMalloc    *m_pIMalloc;             //Task allocator
        char       *m_pch;                  //Char array (8 bits)
        TCHAR      *m_pszScratch;           //Scratch space

        UINT        m_cch;                  //Stream size
        UINT        m_cStreams;             //Stream count
        TCHAR       m_szFile[CCHPATHMAX];   //File in use

        HCURSOR     m_hCur;                 //Saved cursor

    public:
        CFragment::CFragment(void);
        CFragment::~CFragment(void);

        BOOL        Init(void);
        BOOL        AllocCharArrays(void);
        BOOL        CreateFragmentedFile(void);
        void        FreeSpaceInFile(void);
        void        DefragmentFile(void);

    private:
        void        StreamCreate(IStorage *, UINT, IStream **);
        void        Message(LPTSTR);

       #ifndef WIN32
        //Substitute for Win32 MoveFile under Win16
        void        MyMoveFile(char *, char *);
       #endif
    };

#endif //_FRAGMENT_H_


CLIENT.CPP

/*
 * CLIENT.CPP
 * Patron Chapter 7
 *
 * Implementation of the CPatronClient class that just makes sure
 * we get a CPatronDoc on doc creation and that we initialize fully.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPatronClient::CPatronClient
 * CPatronClient::~CPatronClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 */

CPatronClient::CPatronClient(HINSTANCE hInst)
    : CClient(hInst)
    {
    return;
    }

CPatronClient::~CPatronClient(void)
    {
    return;
    }

/*
 * CPatronClient::CreateCDocument
 *
 * Purpose:
 *  Constructs a new document specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CPatronClient::CreateCDocument(void)
    {
    return (PCDocument)(new CPatronDoc(m_hInst, m_pFR));
    }


DOCUMENT.CPP

/*
 * DOCUMENT.CPP
 * Patron Chapter 7
 *
 * Implementation of the CPatronDoc derivation of CDocument that
 * manages pages for us.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"
#include <memory.h>
#include <dlgs.h>       //Pring Dlg button IDs

/*
 * CPatronDoc::CPatronDoc
 * CPatronDoc::~CPatronDoc
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame of the frame object.
 */

CPatronDoc::CPatronDoc(HINSTANCE hInst, PCFrame pFR)
    : CDocument(hInst, pFR)
    {
    m_pPG=NULL;
    m_lVer=VERSIONCURRENT;
    m_pIStorage=NULL;

    //CHAPTER7MOD
    m_fPrintSetup=TRUE;
    //End CHAPTER7MOD
    return;
    }

CPatronDoc::~CPatronDoc(void)
    {
    if (NULL!=m_pPG)
        delete m_pPG;

    if (NULL!=m_pIStorage)
        m_pIStorage->Release();

    CoFreeUnusedLibraries();
    return;
    }

/*
 * CPatronDoc::FInit
 *
 * Purpose:
 *  Initializes an already created document window.  The client
 *  actually creates the window for us, then passes that here for
 *  further initialization.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPatronDoc::FInit(PDOCUMENTINIT pDI)
    {
    //Change the stringtable range to our customization.
    pDI->idsMin=IDS_DOCUMENTMIN;
    pDI->idsMax=IDS_DOCUMENTMAX;

    //Do default initialization
    if (!CDocument::FInit(pDI))
        return FALSE;

    //Pages are created when we get a ULoad later.
    return TRUE;
    }

/*
 * CPatronDoc::FMessageHook
 *
 * Purpose:
 *  Processes WM_SIZE for the document so we can resize the Pages
 *  window.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CPatronDoc::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    UINT        dx, dy;
    RECT        rc;

    *pLRes=0;

    //Eat to prevent flickering
    if (WM_ERASEBKGND==iMsg)
        return TRUE;

    if (WM_SIZE==iMsg && NULL!=m_pPG)
        {
        dx=LOWORD(lParam);
        dy=HIWORD(lParam);

        if (SIZE_MINIMIZED!=wParam)
            {
            //Resize Pages window to fit the new document size.
            GetClientRect(hWnd, &rc);
            m_pPG->RectSet(&rc, FALSE);
            }
        }

    /*
     * We return FALSE even on WM_SIZE so we can let the default
     * procedure handle maximized MDI child windows appropriately.
     */
    return FALSE;
    }

/*
 * CPatronDoc::Clear
 *
 * Purpose:
 *  Sets all contents in the document back to defaults with no
 *  filename.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Clear(void)
    {
    //Completely reset the pages
    if (NULL!=m_pPG)
        m_pPG->FIStorageSet(NULL, FALSE, FALSE);

    CDocument::Clear();
    m_lVer=VERSIONCURRENT;
    return;
    }

//CHAPTER7MOD

/*
 * CPatronDoc::FDirtyGet
 *
 * Purpose:
 *  Returns the current dirty status of the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the file is clean, FALSE otherwise.
 */

BOOL CPatronDoc::FDirtyGet()
    {
    BOOL    fPageDirty;

    fPageDirty=m_pPG->FIsDirty();
    return m_fDirty | fPageDirty;
    }

/*
 * CPatronDoc::Delete
 *
 * Purpose:
 *  Removed the current object from the document.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Delete(void)
    {
    if (NULL!=m_pPG)
        m_pPG->TenantDestroy();

    CoFreeUnusedLibraries();
    return;
    }

/*
 * CPatronDoc::FQueryPrinterSetup
 *
 * Purpose:
 *  Returns whether or not the Printer Setup menu item can be
 *  enabled.  Once you create a tenant in any page, Printer Setup
 *  is voided simply to keep this sample simple, that is, we don't
 *  have to worry about reorganizing potentially large amounts
 *  of layout after we start plopping down objects.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE to enable the menu, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPrinterSetup(void)
    {
    return m_fPrintSetup;
    }

/*
 * CPatronDoc::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected in this
 *  document for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU of the Edit menu.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryObjectSelected(HMENU hMenu)
    {
    return m_pPG->FQueryObjectSelected(hMenu);
    }

//End CHAPTER7MOD

/*
 * CPatronDoc::ULoad
 *
 * Purpose:
 *  Loads a given document without any user interface overwriting
 *  the previous contents of the editor.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load.  Could be NULL
 *                  for an untitled document.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_...
 */

UINT CPatronDoc::ULoad(BOOL fChangeFile, LPTSTR pszFile)
    {
    RECT        rc;
    LPSTORAGE   pIStorage;
    HRESULT     hr;
    CLSID       clsID;
    DWORD       dwMode=STGM_TRANSACTED | STGM_READWRITE
                    | STGM_SHARE_EXCLUSIVE;

    if (NULL==pszFile)
        {
        //Create a new temp file.
        hr=StgCreateDocfile(NULL, dwMode | STGM_CREATE
            | STGM_DELETEONRELEASE, 0, &pIStorage);

        //Mark this our class since we check with ReadClassStg.
        if (SUCCEEDED(hr))
            WriteClassStg(pIStorage, CLSID_PatronPages);
        }
    else
        {
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsFile[MAX_PATH];
        mbstowcs(pwcsFile, pszFile, MAX_PATH);
        hr=StgOpenStorage(pwcsFile, NULL, dwMode, NULL, 0, &pIStorage);
#else
        hr=StgOpenStorage(pszFile, NULL, dwMode, NULL, 0, &pIStorage);
#endif
        }

    if (FAILED(hr))
        return DOCERR_COULDNOTOPEN;

    //Check if this is our type of file and exit if not.
    hr=ReadClassStg(pIStorage, &clsID);

    if (FAILED(hr) || CLSID_PatronPages!=clsID)
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    //Attempt to create our contained Pages window.
    //CHAPTER7MOD
    m_pPG=new CPages(m_hInst, m_cf);
    //End CHAPTER7MOD
    GetClientRect(m_hWnd, &rc);

    if (!m_pPG->FInit(m_hWnd, &rc, WS_CHILD | WS_VISIBLE
        , ID_PAGES, NULL))
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    if (!m_pPG->FIStorageSet(pIStorage, FALSE
        , (BOOL)(NULL==pszFile)))
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    m_pIStorage=pIStorage;
    Rename(pszFile);

    //Do initial setup if new file, otherwise Pages handles things.
    if (NULL==pszFile)
        {
        //Go initialize the Pages for the default printer.
        if (!PrinterSetup(NULL, TRUE))
            return DOCERR_COULDNOTOPEN;

        //Go create an initial page.
        m_pPG->PageInsert(0);
        }
    //CHAPTER7MOD
    else
        {
        //Can't change an already saved configuration
        m_fPrintSetup=FALSE;
        }
    //End CHAPTER7MOD

    FDirtySet(FALSE);
    return DOCERR_NONE;
    }

/*
 * CPatronDoc::USave
 *
 * Purpose:
 *  Writes the file to a known filename, requiring that the user
 *  has previously used FileOpen or FileSaveAs in order to have
 *  a filename.
 *
 * Parameters:
 *  uType           UINT indicating the type of file the user
 *                  requested to save in the File Save As dialog.
 *  pszFile         LPTSTR under which to save.  If NULL, use the
 *                  current name.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_...
 */

UINT CPatronDoc::USave(UINT uType, LPTSTR pszFile)
    {
    HRESULT     hr;
    LPSTORAGE   pIStorage;

    //Save or Save As with the same file is just a commit.
    if (NULL==pszFile
        || (NULL!=pszFile && 0==lstrcmpi(pszFile, m_szFile)))
        {
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsTemp[256];
        mbstowcs(pwcsTemp, PSZ(IDS_CLIPBOARDFORMAT), 256);
        WriteFmtUserTypeStg(m_pIStorage, m_cf
            , pwcsTemp);
#else
        WriteFmtUserTypeStg(m_pIStorage, m_cf
            , PSZ(IDS_CLIPBOARDFORMAT));
#endif

        //Insure pages are up to date.
        m_pPG->FIStorageUpdate(FALSE);

        //Commit everyting
        m_pIStorage->Commit(STGC_ONLYIFCURRENT);

        FDirtySet(FALSE);
        return DOCERR_NONE;
        }

    /*
     * When we're given a name, open the storage, creating it new
     * if it does not exist or overwriting the old one.  Then CopyTo
     * from the current to the new, Commit the new, Release the old.
     */
    OLECHAR pwcsFile[MAX_PATH];
#if defined(WIN32) && !defined(UNICODE)
    mbstowcs(pwcsFile, pszFile, MAX_PATH);
#else
    pwcsFile = pszFile;
#endif
    hr=StgCreateDocfile(pwcsFile, STGM_TRANSACTED | STGM_READWRITE
        | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

    if (FAILED(hr))
        return DOCERR_COULDNOTOPEN;

    WriteClassStg(pIStorage, CLSID_PatronPages);
#if defined(WIN32) && !defined(UNICODE)
    OLECHAR pwcsTemp[256];
    mbstowcs(pwcsTemp, PSZ(IDS_CLIPBOARDFORMAT), 256);
    WriteFmtUserTypeStg(pIStorage, m_cf, pwcsTemp);
#else
    WriteFmtUserTypeStg(pIStorage, m_cf, PSZ(IDS_CLIPBOARDFORMAT));
#endif

    //Insure all pages are up-to-date.
    m_pPG->FIStorageUpdate(TRUE);

    //This also copies the CLSID we stuff in here on file creation.
    hr=m_pIStorage->CopyTo(NULL, NULL, NULL, pIStorage);

    if (FAILED(hr))
        {
        SCODE       sc;

        pIStorage->Release();
        sc=GetScode(hr);

        /*
         * If we failed because of low memory, use IRootStorage
         * to switch into the new file.
         */
        if (E_OUTOFMEMORY==sc)
            {
            LPROOTSTORAGE        pIRoot;

            //Delete file we already created
            DeleteFile(pszFile);

            if (FAILED(m_pIStorage->QueryInterface
                (IID_IRootStorage, (PPVOID)&pIRoot)))
                return DOCERR_WRITEFAILURE;

            hr=pIRoot->SwitchToFile(pwcsFile);
            pIRoot->Release();

            if (FAILED(hr))
                return DOCERR_WRITEFAILURE;

            //If successful, the Commit below finishes the save.
            pIStorage=m_pIStorage;
            m_pIStorage->AddRef();    //Matches Release below
            }
        }

    pIStorage->Commit(STGC_ONLYIFCURRENT);

    /*
     * Revert changes on the original storage.  If this was a temp
     * file, it's deleted since we used STGM_DELETEONRELEASE.
     */
    m_pIStorage->Release();

    //Make this new storage current
    m_pIStorage=pIStorage;
    m_pPG->FIStorageSet(pIStorage, TRUE, FALSE);

    FDirtySet(FALSE);
    Rename(pszFile);    //Update caption bar.

    return DOCERR_NONE;
    }

//CHAPTER7MOD

/*
 * CPatronDoc::FClip
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard, optionally implementing Cut by deleting the
 *  data in the current window after rendering.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *  fCut            BOOL indicating cut (TRUE) or copy (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::FClip(HWND hWndFrame, BOOL fCut)
    {
    if (NULL==m_pPG)
        return FALSE;

    return m_pPG->TenantClip(fCut);
    }

/*
 * CPatronDoc::FPaste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  to the current figure in the editor window.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::FPaste(HWND hWndFrame)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet=FALSE;
    FORMATETC       fe;
    TENANTTYPE      tType;

    if (NULL==m_pPG)
        return FALSE;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    //Go get type and format we *can* paste, then actually paste it.
    if (FQueryPasteFromData(pIDataObject, &fe, &tType))
        fRet=FPasteFromData(pIDataObject, &fe, tType, NULL, 0L);

    pIDataObject->Release();
    return fRet;
    }

/*
 * CPatronDoc::FQueryPaste
 *
 * Purpose:
 *  Determines if we can paste data from the clipboard.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPaste(void)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    fRet=FQueryPasteFromData(pIDataObject, NULL, NULL);
    pIDataObject->Release();
    return fRet;
    }

/*
 * CPatronDoc::FPasteSpecial
 *
 * Purpose:
 *  Retrieves a specific data format from the clipboard and sends
 *  it to the editor window appropriately.
 *
 *  Note that if this function is called, then the appropriate
 *  format is available because the Paste menu item is only
 *  enabled if the format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::FPasteSpecial(HWND hWndFrame)
    {
    OLEUIPASTESPECIAL   ps;
    OLEUIPASTEENTRY     rgPaste[4];
    UINT                uTemp;
    BOOL                fRet=FALSE;

    if (NULL==m_pPG)
        return FALSE;

    memset(&ps, 0, sizeof(ps));

    if (FAILED(OleGetClipboard(&ps.lpSrcDataObj)))
        return FALSE;

    ps.cbStruct=sizeof(ps);
    ps.hWndOwner=hWndFrame;

    ps.dwFlags=PSF_SELECTPASTE;

    ps.arrPasteEntries=rgPaste;
    ps.cPasteEntries=4;

    //Set up Paste Special descriptor arrays.
    SETDefFormatEtc(rgPaste[0].fmtetc, m_cf, TYMED_HGLOBAL);
    rgPaste[0].lpstrFormatName=PSZ(IDS_CLIPBOARDFORMAT);
    rgPaste[0].lpstrResultText=PSZ(IDS_PASTEASPATRON);
    rgPaste[0].dwFlags=OLEUIPASTE_PASTEONLY;

    SETDefFormatEtc(rgPaste[1].fmtetc,CF_METAFILEPICT,TYMED_MFPICT);
    rgPaste[1].lpstrFormatName=PSZ(IDS_PASTEMETAFILE);
    rgPaste[1].lpstrResultText=PSZ(IDS_PASTEASMETAFILE);
    rgPaste[1].dwFlags=OLEUIPASTE_PASTEONLY;

    SETDefFormatEtc(rgPaste[2].fmtetc, CF_DIB, TYMED_HGLOBAL);
    rgPaste[2].lpstrFormatName=PSZ(IDS_PASTEDIB);
    rgPaste[2].lpstrResultText=PSZ(IDS_PASTEASDIB);
    rgPaste[2].dwFlags=OLEUIPASTE_PASTEONLY;

    SETDefFormatEtc(rgPaste[3].fmtetc, CF_BITMAP, TYMED_GDI);
    rgPaste[3].lpstrFormatName=PSZ(IDS_PASTEBITMAP);
    rgPaste[3].lpstrResultText=PSZ(IDS_PASTEASBITMAP);
    rgPaste[3].dwFlags=OLEUIPASTE_PASTEONLY;

    uTemp=OleUIPasteSpecial(&ps);

    if (OLEUI_OK==uTemp)
        {
        fRet=FPasteFromData(ps.lpSrcDataObj
            , &rgPaste[ps.nSelectedIndex].fmtetc
            , TENANTTYPE_STATIC, NULL, 0L);
        }

    ps.lpSrcDataObj->Release();
    return fRet;
    }

/*
 * CPatronDoc::FQueryPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Determines if we can paste data from a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which we might want to paste.
 *  pFE             LPFORMATETC in which to return the first format
 *                  we can use.  Ignored if NULL.
 *  ptType          PTENANTTYPE in which to store the type of
 *                  object we can paste.  Ignored if NULL.
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPasteFromData(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, PTENANTTYPE ptType)
    {
    FORMATETC       fe;
    HRESULT         hr;

    if (NULL!=(LPVOID)ptType)
        *ptType=TENANTTYPE_STATIC;

    //Any of our specific data here?
    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    hr=pIDataObject->QueryGetData(&fe);

    if (NOERROR!=hr)
        {
        //Try metafile, DIB, then bitmap, setting fe each time
        SETDefFormatEtc(fe, CF_METAFILEPICT, TYMED_MFPICT);
        hr=pIDataObject->QueryGetData(&fe);

        if (NOERROR!=hr)
            {
            SETDefFormatEtc(fe, CF_DIB, TYMED_HGLOBAL);
            hr=pIDataObject->QueryGetData(&fe);

            if (NOERROR!=hr)
                {
                SETDefFormatEtc(fe, CF_BITMAP, TYMED_GDI);
                hr=pIDataObject->QueryGetData(&fe);
                }
            }
        }

    if (NOERROR==hr && NULL!=pFE)
        *pFE=fe;

    return (NOERROR==hr);
    }

/*
 * CPatronDoc::FPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Retrieves the private data format from a data object and sets
 *  it to the current figure in the editor window.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *  pFE             LPFORMATETC to use in the paste. Cannot be NULL.
 *  tType           TENANTTYPE to paste.
 *  ppo             PPATRONOBJECT containing placement data.
 *  dwData          DWORD extra data sensitive to tType
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::FPasteFromData(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, TENANTTYPE tType, PPATRONOBJECT ppo
    , DWORD dwData)
    {
    BOOL            fRet;
    HRESULT         hr;
    PATRONOBJECT    po;
    STGMEDIUM       stm;

    if (NULL==pFE)
        return FALSE;

    //If not given any placement data, see if we can retrieve it
    if (pFE->cfFormat==m_cf && NULL==ppo)
        {
        hr=pIDataObject->GetData(pFE, &stm);

        if (SUCCEEDED(hr))
            {
            ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);

            po=*ppo;
            ppo=&po;

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);
            }
        }

    fRet=m_pPG->TenantCreate(tType, pIDataObject, pFE, ppo, dwData);

    if (fRet)
        {
        //Disable Printer Setup once we've created a tenant.
        m_fPrintSetup=FALSE;
        FDirtySet(TRUE);
        }

    return fRet;
    }

//End CHAPTER7MOD

/*
 * CPatronDoc::Print
 *
 * Purpose:
 *  Prints the current document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *
 * Return Value:
 *  BOOL            TRUE if printing happened, FALSE if it didn't
 *                  start or didn't complete.
 */

BOOL CPatronDoc::Print(HWND hWndFrame)
    {
    PRINTDLG        pd;
    BOOL            fSuccess;

    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);
    pd.hwndOwner  =hWndFrame;
    pd.nCopies    =1;
    pd.nFromPage  =(USHORT)-1;
    pd.nToPage    =(USHORT)-1;
    pd.nMinPage   =1;
    pd.nMaxPage   =m_pPG->NumPagesGet();

    pd.lpfnPrintHook=PrintDlgHook;

    //Get the current document printer settings
    pd.hDevMode=m_pPG->DevModeGet();

    pd.Flags=PD_RETURNDC | PD_ALLPAGES | PD_COLLATE
        | PD_HIDEPRINTTOFILE | PD_NOSELECTION | PD_ENABLEPRINTHOOK;

    if (!PrintDlg(&pd))
        return FALSE;

    if (NULL!=pd.hDevMode)
        GlobalFree(pd.hDevMode);

    if (NULL!=pd.hDevNames)
        GlobalFree(pd.hDevNames);

    //Go do the actual printing.
    fSuccess=m_pPG->Print(pd.hDC, PSZ(IDS_DOCUMENTNAME), pd.Flags
        , pd.nFromPage, pd.nToPage, pd.nCopies);

    if (!fSuccess)
        {
        MessageBox(m_hWnd, PSZ(IDS_PRINTERROR)
            , PSZ(IDS_DOCUMENTCAPTION), MB_OK);
        }

    return fSuccess;
    }

/*
 * CPatronDoc::PrinterSetup
 *
 * Purpose:
 *  Selects a new printer and options for this document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *  fDefault        BOOL to avoid any dialog and just use the
 *                  default.
 *
 * Return Value:
 *  UINT            Undefined
 *
 */

UINT CPatronDoc::PrinterSetup(HWND hWndFrame, BOOL fDefault)
    {
    PRINTDLG        pd;

    //Attempt to get printer metrics for the default printer.
    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);

    if (fDefault)
        pd.Flags=PD_RETURNDEFAULT;
    else
        {
        pd.hwndOwner=hWndFrame;
        pd.Flags=PD_PRINTSETUP;

        //Get the current document printer settings
        pd.hDevMode=m_pPG->DevModeGet();
        }

    if (!PrintDlg(&pd))
        return FALSE;

    if (!m_pPG->DevModeSet(pd.hDevMode, pd.hDevNames))
        {
        GlobalFree(pd.hDevNames);
        GlobalFree(pd.hDevMode);
        return FALSE;
        }

    FDirtySet(TRUE);
    return 1;
    }

/*
 * CPatronDoc::NewPage
 *
 * Purpose:
 *  Creates a new page in the document's pages control after the
 *  current page.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::NewPage(void)
    {
    FDirtySet(TRUE);
    return m_pPG->PageInsert(0);
    }

/*
 * CPatronDoc::DeletePage
 *
 * Purpose:
 *  Deletes the current page from the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the now current page.
 */

UINT CPatronDoc::DeletePage(void)
    {
    FDirtySet(TRUE);
    return m_pPG->PageDelete(0);
    }

/*
 * CPatronDoc::NextPage
 *
 * Purpose:
 *  Shows the next page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::NextPage(void)
    {
    UINT        iPage;

    iPage=m_pPG->CurPageGet();
    return m_pPG->CurPageSet(++iPage);
    }

/*
 * CPatronDoc::PreviousPage
 *
 * Purpose:
 *  Shows the previous page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::PreviousPage(void)
    {
    UINT        iPage;

    //If iPage is zero, then we wrap around to the end.
    iPage=m_pPG->CurPageGet();
    return m_pPG->CurPageSet(--iPage);
    }

/*
 * CPatronDoc::FirstPage
 *
 * Purpose:
 *  Shows the first page page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::FirstPage(void)
    {
    return m_pPG->CurPageSet(0);
    }

/*
 * CPatronDoc::LastPage
 *
 * Purpose:
 *  Shows the last page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the last page.
 */

UINT CPatronDoc::LastPage(void)
    {
    return m_pPG->CurPageSet(NOVALUE);
    }

/*
 * PrintDlgHook
 *
 * Purpose:
 *  Callback hook for the Print Dialog so we can hide the Setup
 *  button.  Patron only allows Setup before anything exists on
 *  the page, and is not written to handle setup at Print time.
 */

UINT CALLBACK PrintDlgHook(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    if (WM_INITDIALOG==iMsg)
        {
        HWND        hWnd;

        hWnd=GetDlgItem(hDlg, psh1);
        ShowWindow(hWnd, SW_HIDE);
        return TRUE;
        }

    return FALSE;
    }


PAGE.CPP

/*
 * PAGE.CPP
 * Patron Chapter 7
 *
 * Implementation of parts of the CPage class; those member
 * functions dealing with mouse events are in PAGEMOUS.CPP.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPage::CPage
 * CPage::~CPage
 *
 * Constructor Parameters:
 *  dwID            DWORD identifier for this page.
 *  hWnd            HWND of the pages window (for repaints, etc).
 *  pPG             PCPages to the Pages window.
 */

//CHAPTER7MOD
CPage::CPage(DWORD dwID, HWND hWnd, PCPages pPG)
//End CHAPTER7MOD
    {
    m_dwID     =dwID;
    m_pIStorage=NULL;

    //CHAPTER7MOD
    m_cOpens=0;
    m_hWnd=hWnd;
    m_pPG=pPG;

    m_dwIDNext      =0;
    m_cTenants      =0;
    m_hWndTenantList=NULL;
    m_iTenantCur    =NOVALUE;   //Tenants are zero indexed.
    m_pTenantCur    =NULL;

    m_uHTCode=HTNOWHERE;
    m_uSizingFlags=0;
    m_fTracking=FALSE;
    m_hDC=NULL;

    m_fSizePending=FALSE;
    m_fTimer=FALSE;

    //These will be read from WIN.INI in Chapter 8.
    m_cxyDist=2;
    m_cDelay=200;
    //End CHAPTER7MOD

    return;
    }

CPage::~CPage(void)
    {
    //CHAPTER7MOD
    if (m_fTimer)
        KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
    //End CHAPTER7MOD

    m_hWnd=NULL;
    Close(FALSE);
    return;
    }

/*
 * CPage::GetID
 *
 * Return Value:
 *  DWORD           dwID field in this page.  This function is only
 *                  here to avoid hiding inline implementations
 *                  in pages.h
 */

DWORD CPage::GetID(void)
    {
    return m_dwID;
    }

/*
 * CPage::FOpen
 *
 * Purpose:
 *  Retrieves the IStorage associated with this page.  The IStorage
 *  is owned by the page and thus the page always holds a reference
 *  count.  The caller should call Close or delete this page to
 *  match this open.
 *
 *  This function may be called multiple times resulting in
 *  additional reference counts on the storage each of which must be
 *  matched with a call to Close.  The last Close can be done
 *  through delete.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which this page lives.
 *
 * Return Value:
 *  BOOL            TRUE if opening succeeds, FALSE otherwise.
 */

BOOL CPage::FOpen(LPSTORAGE pIStorage)
    {
    //CHAPTER7MOD
    HRESULT         hr=NOERROR;
    LPSTREAM        pIStream;
    DWORD           dwMode;
    TCHAR           szTemp[32];
    BOOL            fNew;
    BOOL            fCreated=FALSE;
    TENANTLIST      tl;
    PTENANTINFO     pti;
    ULONG           cb;
    LPMALLOC        pIMalloc;
    UINT            i;
    PCTenant        pTenant;

    fNew=(NULL==m_pIStorage);

    if (!fNew)
        {
        m_cOpens++;
        m_pIStorage->AddRef();
        return TRUE;
        }

    if (NULL==pIStorage)
        return FALSE;

    /*
     * Attempt to open the storage under this ID.  If none,
     * create one.  In either case, the IStorage is either
     * saved in pPage or released.
     */

    GetStorageName(szTemp);
    dwMode=STGM_TRANSACTED | STGM_READWRITE | STGM_SHARE_EXCLUSIVE;

    OLECHAR pwcsTemp[MAX_PATH];
#if defined(WIN32) && !defined(UNICODE)
    mbstowcs(pwcsTemp, szTemp, MAX_PATH);
#else
    pwcsTemp = szTemp;
#endif
    hr=pIStorage->OpenStorage(pwcsTemp, NULL, dwMode, NULL, 0
        , &m_pIStorage);

    if (FAILED(hr))
        {
        hr=pIStorage->CreateStorage(pwcsTemp, dwMode, 0, 0
            , &m_pIStorage);
        fCreated=TRUE;
        }

    if (FAILED(hr))
        return FALSE;

    m_cOpens++;

    if (NULL==m_hWndTenantList)
        {
        /*
         * The first time we open this page, create the hidden
         * listbox we'll use to track tenants.  We give it the
         * owner-draw style so we can just store pointers in it.
         */
        m_hWndTenantList=CreateWindow(TEXT("listbox")
            , TEXT("Tenant List"), WS_POPUP | LBS_OWNERDRAWFIXED
            , 0, 0, 100, 100, HWND_DESKTOP, NULL
            , m_pPG->m_hInst, NULL);

        if (NULL==m_hWndTenantList)
            return FALSE;
        }

    //If this is brand-new, we're done.
    if (fCreated)
        return TRUE;

    /*
     * Now open the stream we saved in Close and load all the
     * tenants listed in there.  If there are none, then we don't
     * have to load squat.
     */

    hr=m_pIStorage->OpenStream(SZSTREAMTENANTLIST, NULL, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pIStream->Read(&tl, sizeof(tl), NULL);
        m_cTenants=tl.cTenants;
        m_dwIDNext=tl.dwIDNext;
        m_iTenantCur=0;

        cb=tl.cTenants*sizeof(TENANTINFO);

        if (0!=cb)
            {
            pti=(PTENANTINFO)pIMalloc->Alloc(cb);

            if (NULL!=pti)
                {
                pIStream->Read(pti, cb, NULL);

                for (i=0; i < m_cTenants; i++)
                    {
                    if (FTenantAdd(NOVALUE, (pti+i)->dwID, &pTenant))
                        pTenant->FLoad(m_pIStorage, (pti+i));
                    }

                pIMalloc->Free(pti);
                }
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    //Get and select the first tenant
    if (FTenantGet(0, &m_pTenantCur, FALSE))
        m_pTenantCur->Select(TRUE);

    return TRUE;
    //End CHAPTER7MOD
    }

/*
 * CPage::Close
 *
 * Purpose:
 *  Possibly commits the storage, then releases it reversing the
 *  reference count from FOpen.
 *
 * Parameters:
 *  fCommit         BOOL indicating if we're to commit.
 *
 * Return Value:
 *  None
 */

void CPage::Close(BOOL fCommit)
    {
    if (NULL==m_pIStorage)
        return;

    if (fCommit)
        Update();

    //CHAPTER7MOD
    m_pIStorage->Release();

    //If this was the last close, make all tenants loaded->passive
    if (0==--m_cOpens)
        {
        UINT        i;
        PCTenant    pTenant;

        m_pIStorage=NULL;

        for (i=0; i < m_cTenants; i++)
            {
            if (FTenantGet(i, &pTenant, FALSE))
                {
                if (NULL!=m_hWnd)
                    {
                    //FOpen may select again, so this repaints.
                    pTenant->Select(FALSE);
                    }

                pTenant->Close(FALSE);
                delete pTenant;
                }
            }

        DestroyWindow(m_hWndTenantList);
        m_hWndTenantList=NULL;
        }
    //End CHAPTER7MOD

    return;
    }

/*
 * CPage::Update
 *
 * Purpose:
 *  Forces a common on the page if it's open.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if there are any open objects on this page,
 *                  that is, we should remain open.
 */

BOOL CPage::Update(void)
    {
    //CHAPTER7MOD
    BOOL            fOpen=FALSE;
    UINT            i;
    PCTenant        pTenant;
    HRESULT         hr;
    LPSTREAM        pIStream;
    TENANTLIST      tl;
    PTENANTINFO     pti;
    ULONG           cb;
    LPMALLOC        pIMalloc;

    //Walk the list of objects and update them all as well.
    for (i=0; i < m_cTenants; i++)
        {
        if (FTenantGet(i, &pTenant, FALSE))
            fOpen |= pTenant->Update();
        }

    //Now write our own stream containing the tenant list.
    hr=m_pIStorage->CreateStream(SZSTREAMTENANTLIST, STGM_CREATE
        | STGM_WRITE| STGM_DIRECT | STGM_SHARE_EXCLUSIVE, 0, 0
        , &pIStream);

    if (FAILED(hr))
        return fOpen;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        tl.cTenants=m_cTenants;
        tl.dwIDNext=m_dwIDNext;

        pIStream->Write(&tl, sizeof(TENANTLIST), &cb);

        cb=m_cTenants*sizeof(TENANTINFO);
        pti=(PTENANTINFO)pIMalloc->Alloc(cb);

        if (NULL!=pti)
            {
            for (i=0; i < m_cTenants; i++)
                {
                FTenantGet(i, &pTenant, FALSE);
                pTenant->GetInfo((pti+i));
                }

            pIStream->Write(pti, cb, &cb);
            pIMalloc->Free(pti);
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    //Now commit the whole mess and we're done
    if (NULL!=m_pIStorage)
        m_pIStorage->Commit(STGC_ONLYIFCURRENT);

    return fOpen;
    //End CHAPTER7MOD
    }

/*
 * CPage::Destroy
 *
 * Purpose:
 *  Removes this page from the given storage.  The caller should
 *  eventually delete this Page object to free the storage.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE contianing this page on which to call
 *                  DestroyElement
 *
 * Return Value:
 *  None
 */

void CPage::Destroy(LPSTORAGE pIStorage)
    {
    if (NULL!=pIStorage)
        {
        TCHAR   szTemp[32];

        Close(FALSE);
        GetStorageName(szTemp);
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsTemp[32];
        mbstowcs(pwcsTemp, szTemp, 32);
        pIStorage->DestroyElement(pwcsTemp);
#else
        pIStorage->DestroyElement(szTemp);
#endif
        }

    return;
    }

/*
 * CPage::GetStorageName
 *
 * Parameters:
 *  pszName         LPTSTR to a buffer in which to store the storage
 *                  name for this page.
 *
 * Return Value:
 *  UINT            Number of characters stored.
 */

UINT CPage::GetStorageName(LPTSTR pszName)
    {
    return wsprintf(pszName, TEXT("Page %lu"), m_dwID);
    }

//CHAPTER7MOD

/*
 * CPage::Draw
 *
 * Purpose:
 *  Draws the objects on this page to the given hDC.
 *
 * Parameters:
 *  hDC             HDC on which to draw.
 *  xOff, yOff      int offsets for the page.
 *  fNoColor        BOOL indicating black & white screen rendering.
 *  fPrinter        BOOL indicating hDC is on the printer.
 *
 * Return Value:
 *  None
 */

void CPage::Draw(HDC hDC, int xOff, int yOff, BOOL fNoColor
    , BOOL fPrinter)
    {
    int                 i;
    PCTenant            pTenant;
    HDC                 hIC=NULL;
    PCOMBINEDEVICE      pcd=NULL;
    DVTARGETDEVICE     *ptd=NULL;

    /*
     * If printing, tell the tenant to forget the borders. Otherwise
     * we leave xOff and yOff the same to account for scrolling.
     */
    if (fPrinter)
        {
        xOff=LOMETRIC_BORDER+m_pPG->m_xMarginLeft;
        yOff=-LOMETRIC_BORDER-m_pPG->m_yMarginTop;

        /*
         * Get device information.  If this fails, ptd is
         * NULL which is acceptable.
         */
        if (m_pPG->DevReadConfig(&pcd, &hIC))
            ptd=&(pcd->td);
        }

    for (i=(int)m_cTenants-1; i >=0; i--)
        {
        if (FTenantGet(i, &pTenant, FALSE))
            {
            RECT        rc, rcWin;
            RECTL       rcl;

            //Paint this tenant only if visible.
            pTenant->RectGet(&rcl, TRUE);
            RECTFROMRECTL(rc, rcl);
            OffsetRect(&rc, -(int)m_pPG->m_xPos
                , -(int)m_pPG->m_yPos);
            GetClientRect(m_hWnd, &rcWin);

            if (IntersectRect(&rc, &rc, &rcWin))
                {
                pTenant->Draw(hDC, ptd, hIC, xOff, yOff
                    , fNoColor, fPrinter);
                }
            }
        }

    //Free whatever CPages::DevReadConfig returned.
    if (NULL!=pcd)
        {
        LPMALLOC    pIMalloc;

        if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
            {
            pIMalloc->Free(pcd);
            pIMalloc->Release();
            }
        }

    if (NULL!=hIC)
        DeleteDC(hIC);

    return;
    }

/*
 * CPage::TenantCreate
 *
 * Purpose:
 *  Creates a new tenant of a specific type.
 *
 * Parameters:
 *  tType           TENANTTYPE to create.
 *  pv              LPVOID providing information for the new
 *                  object creation.
 *  pFE             LPFORMATETC describing how we want this
 *                  rendered.
 *  ppo             PPATRONOBJECT with placement data.
 *  dwData          DWORD extra data to pass to the tenant.
 *
 * Return Value:
 *  None
 */

BOOL CPage::TenantCreate(TENANTTYPE tType, LPVOID pv
    , LPFORMATETC pFE, PPATRONOBJECT ppo, DWORD dwData)
    {
    PCTenant    pTenant;
    UINT        uRet;
    int         x, y;
    int         h, v;
    POINTL      ptl;
    SIZEL       szl;
    RECTL       rcl;
    RECT        rc;

    //New tenants go at top of the pile; zero index to FTenantAdd.
    if (!FTenantAdd(0, m_dwIDNext, &pTenant))
        return FALSE;

    uRet=pTenant->UCreate(tType, pv, pFE, &ptl, &szl, m_pIStorage
        , ppo, dwData);

    if (UCREATE_FAILED==uRet)
        {
        //Reverse UCreate AND FTenantAdd
        SendMessage(m_hWndTenantList, LB_DELETESTRING, 0, 0L);
        pTenant->Destroy(m_pIStorage);

        delete pTenant;
        return FALSE;
        }

    m_dwIDNext++;
    m_cTenants++;

    if (NULL!=m_pTenantCur)
        m_pTenantCur->Select(FALSE);

    m_iTenantCur=0;             //First one in the list now.
    m_pTenantCur=pTenant;

    //Tell the tenant where it lives, default is (0,0) in print area
    x=LOMETRIC_BORDER+m_pPG->m_xMarginLeft;
    y=-LOMETRIC_BORDER-m_pPG->m_yMarginTop;

    h=x;
    v=y;

    if (UCREATE_PLACEDOBJECT==uRet)
        {
        SetRect(&rc, 3*CXYHANDLE, 3*CXYHANDLE, 0, 0);
        RectConvertMappings(&rc, NULL, FALSE);

        //Make sure place point is on page, otherwise go to (0,0)
        if (((int)ptl.x > x)
            && ((int)ptl.x < x+(int)m_pPG->m_cx-rc.left))
            x=(int)ptl.x;

        //m_pPG->m_cy is absolute
        if (((int)ptl.y < y)
            && ((int)ptl.y > y-(int)m_pPG->m_cy-rc.top))
            y=(int)ptl.y;
        }

    //Bounds check size of the object and fit to page as necessary.
    if (x+(int)szl.cx > (int)(h+m_pPG->m_cx))
        szl.cx=h+m_pPG->m_cx-x;

    //Remember that szl we get from UCreate is absolute
    if (y-(int)szl.cy < (int)(v-m_pPG->m_cy))
        szl.cy=-(int)(v-m_pPG->m_cy-y);

    SETRECTL(rcl, x, y, x+szl.cx, y-szl.cy);
    m_pTenantCur->RectSet(&rcl, FALSE);

    //Force a repaint on this new guy
    m_pTenantCur->Invalidate();
    UpdateWindow(m_hWnd);

    m_pTenantCur->Select(TRUE);
    return TRUE;
    }

/*
 * CPage::TenantDestroy
 *
 * Purpose:
 *  Destroys the currently selected tenant on this page.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPage::TenantDestroy(void)
    {
    if (NULL==m_pTenantCur)
        {
        MessageBeep(0);
        return FALSE;
        }

    SendMessage(m_hWndTenantList, LB_DELETESTRING
        , m_iTenantCur, 0L);

    m_pTenantCur->Invalidate();
    m_pTenantCur->Destroy(m_pIStorage);

    delete m_pTenantCur;
    m_pTenantCur=NULL;

    //Update counts, etc., and select the next tenant in the list.
    if (m_iTenantCur==m_cTenants-1)
        m_iTenantCur--;

    if (0==--m_cTenants)
        m_pTenantCur=NULL;
    else
        {
        FTenantGet(m_iTenantCur, &m_pTenantCur, TRUE);
        m_pTenantCur->Select(TRUE);
        }

    UpdateWindow(m_hWnd);
    return TRUE;
    }

/*
 * CPage::TenantClip
 *
 * Purpose:
 *  Copies or cuts the currently selected tenant to the clipoard.
 *
 * Parameters:
 *  fCut            BOOL TRUE to cut the object, FALSE to copy.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPage::TenantClip(BOOL fCut)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet=FALSE;

    if (NULL==m_pTenantCur)
        return FALSE;

    /*
     * To perform a data transfer operation, we need to create a
     * data object with the selected object's data inside. To do
     * this we CoCreateInstance on CLSID_DataTransferObject
     * (Also implemented in this chapter), retrieve data from the
     * object we have, stuff that data into the transfer object,
     * then stick that object on the clipboard.
     *
     * Since we'll want an identical object at other times, like for
     * drag-drop, we use a private function to actually create it.
     */

    pIDataObject=TransferObjectCreate(NULL);

    if (NULL!=pIDataObject)
        {
        if (SUCCEEDED(OleSetClipboard(pIDataObject)))
            {
            if (fCut)
                TenantDestroy();

            fRet=TRUE;
            }

        pIDataObject->Release();
        }

    return fRet;
    }

/*
 * CPage::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected on this
 *  page for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU of the Edit menu.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPage::FQueryObjectSelected(HMENU hMenu)
    {
    return (NULL!=m_pTenantCur);
    }

/*
 * CPages::FTenantGet
 * (Protected)
 *
 * Purpose:
 *  Returns a tenant of a given index returning a BOOL so it's
 *  simple to use this function inside if statements.
 *
 * Parameters:
 *  iTenant         UINT tenant to retrieve 0 based.
 *  ppTenant        PCPage * in which to return the tenant
 *                  pointer
 *  fOpen           BOOL indicating if we should open this
 *                  tenant as well.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPage::FTenantGet(UINT iTenant, PCTenant *ppTenant
    , BOOL fOpen)
    {
    if (NULL==ppTenant)
        return FALSE;

    if (LB_ERR!=SendMessage(m_hWndTenantList, LB_GETTEXT
        , iTenant, (LONG)ppTenant))
        {
        if (fOpen)
            (*ppTenant)->FOpen(m_pIStorage);

        return TRUE;
        }

    return FALSE;
    }

/*
 * CPage::FTenantAdd
 * (Protected)
 *
 * Purpose:
 *  Creates a new tenant initialized to the given values.  The new
 *  tenants's storage is created if it does not already exist.  If
 *  fOpenStorage is set the the tenants's storage is opened and left
 *  opened.
 *
 * Parameters:
 *  iTenant         UINT Location at which to insert tenant; new
 *                  tenant is inserted after this position.  NOVALUE
 *                  for the end.
 *  dwID            DWORD ID for this tenant.
 *  ppNew           PCTenant * in which to store the new tenant.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPage::FTenantAdd(UINT iTenant, DWORD dwID
    , PCTenant *ppNew)
    {
    PCTenant    pTenant;
    LRESULT     lr;

    if (NULL!=ppNew)
        *ppNew=NULL;

    pTenant=new CTenant(dwID, m_hWnd, m_pPG);

    if (NULL==pTenant)
        return FALSE;

    //Now try to add to the listbox.
    lr=SendMessage(m_hWndTenantList, LB_INSERTSTRING, iTenant
        , (LONG)pTenant);

    if (lr < 0)
        {
        delete pTenant;
        return FALSE;
        }

    *ppNew=pTenant;
    return TRUE;
    }

/*
 * CPage::TransferObjectCreate
 * (Protected)
 *
 * Purpose:
 *  Creates a DataTransferObject and stuff the current selection's
 *  data into it.
 *
 * Parameters:
 *  pptl            LPPOINTL containing the pick point in device
 *                  units applicable only to drag-drop; since
 *                  drag-drop is inherently mouse oriented, we use
 *                  device units for the point.  Ignored if NULL.
 *
 * Return Value:
 *  LPDATAOBJECT    Pointer to the object created, NULL on failure
 */

LPDATAOBJECT CPage::TransferObjectCreate(LPPOINTL pptl)
    {
    LPDATAOBJECT    pIDataObject;
    LPDATAOBJECT    pIDataT;
    PPATRONOBJECT   ppo;
    RECTL           rcl;
    LPUNKNOWN       pObj;
    FORMATETC       fe;
    STGMEDIUM       stm;
    HRESULT         hr;

    m_pTenantCur->ObjectGet(&pObj);

    hr=CoCreateInstance(CLSID_DataTransferObject, NULL
        , CLSCTX_INPROC_SERVER, IID_IDataObject
        , (PPVOID)&pIDataObject);

    if (FAILED(hr))
        return NULL;

    //Go get the data we should hold on to.
    hr=pObj->QueryInterface(IID_IDataObject, (PPVOID)&pIDataT);

    if (FAILED(hr))
        {
        pIDataObject->Release();
        pObj->Release();
        return NULL;
        }

    //Copy from known obj into transfer obj.  Ordering is important!

    //Generate placeable object structure
    stm.tymed=TYMED_HGLOBAL;
    stm.pUnkForRelease=NULL;
    stm.hGlobal=GlobalAlloc(GHND, sizeof(PATRONOBJECT));

    if (NULL==stm.hGlobal)
        {
        pIDataObject->Release();
        pObj->Release();
        return NULL;
        }

    ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);

    m_pTenantCur->SizeGet(&ppo->szl, FALSE);
    ppo->szl.cy=-ppo->szl.cy; //Negate to make absolute size

    m_pTenantCur->RectGet(&rcl, FALSE);
    ppo->ptl.x=rcl.left;
    ppo->ptl.y=rcl.top;

    if (NULL==pptl)
        {
        ppo->ptlPick.x=0;
        ppo->ptlPick.y=0;
        }
    else
        ppo->ptlPick=*pptl;

    m_pTenantCur->FormatEtcGet(&ppo->fe, FALSE);

    GlobalUnlock(stm.hGlobal);

    SETDefFormatEtc(fe, m_pPG->m_cf, TYMED_HGLOBAL);
    pIDataObject->SetData(&fe, &stm, TRUE);

    //Copy the actual presentation.
    m_pTenantCur->FormatEtcGet(&fe, TRUE);
    pIDataT->GetData(&fe, &stm);
    pIDataObject->SetData(&fe, &stm, TRUE);

    pIDataT->Release();

    pObj->Release();
    return pIDataObject;    //Caller now responsible
    }

//End CHAPTER7MOD


PAGES.CPP

/*
 * PAGES.CPP
 * Patron Chapter 7
 *
 * Implementation of the CPages class.  See PAGEWIN.CPP for
 * additional member functions.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPages:CPages
 * CPages::~CPages
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application we're in.
 *  cf              UINT application clipboard format.
 */

//CHAPTER7MOD
CPages::CPages(HINSTANCE hInst, UINT cf)
    : CWindow(hInst)
    {
    m_pPageCur=NULL;
//End CHAPTER7MOD
    m_iPageCur=NOVALUE;  //Pages are 0 indexed, this is one before
    m_cPages=0;
    m_hWndPageList=NULL;
    m_fSystemFont=FALSE;

    //Initialize to 8.5*11 inch with .25 inch margins as a default.
    m_cx=(LOMETRIC_PER_INCH*17)/2;
    m_cy=LOMETRIC_PER_INCH*11;

    m_xMarginLeft=LOMETRIC_PER_INCH/4;
    m_xMarginRight=LOMETRIC_PER_INCH/4;
    m_yMarginTop=LOMETRIC_PER_INCH/4;
    m_yMarginBottom=LOMETRIC_PER_INCH/4;

    m_xPos=0L;
    m_yPos=0L;

    m_dwIDNext=0;
    m_pIStorage=NULL;

    //CHAPTER7MOD
    m_fDirty=FALSE;
    m_cf=cf;
    //End CHAPTER7MOD
    return;
    }

CPages::~CPages(void)
    {
    //Ensure memory cleaned up in list; do final IStorage::Release
    FIStorageSet(NULL, FALSE, FALSE);

    if (NULL!=m_hFont && !m_fSystemFont)
        DeleteObject(m_hFont);

    if (NULL!=m_hWndPageList)
        DestroyWindow(m_hWndPageList);

    //m_hWnd destroyed with the document.
    return;
    }

//CHAPTER7MOD
/*
 * CPages::FIsDirty
 *
 * Purpose:
 *  Tells the caller (document) if anything's happened to dirty us.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPages::FIsDirty(void)
    {
    return m_fDirty;
    }
//End CHAPTER7MOD

/*
 * CPages::FInit
 *
 * Purpose:
 *  Instantiates a pages window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags.
 *                  Should contain WS_CHILD | WS_VISIBLE in
 *                  typical circumstances.
 *  uID             UINT ID to associate with this window
 *  pv              LPVOID unused for now.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPages::FInit(HWND hWndParent, LPRECT pRect, DWORD dwStyle
    , UINT uID, LPVOID pv)
    {
    int     cy;

    m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY, SZCLASSPAGES
        , SZCLASSPAGES, dwStyle, pRect->left, pRect->top
        , pRect->right-pRect->left, pRect->bottom-pRect->top
        , hWndParent, (HMENU)uID, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    /*
     * Create the hidden listbox we'll use to track pages.  We give
     * it the owner-draw style so we can just store pointers in it.
     * We have to set the parent to NULL such that the window hangs
     * around after the pages window is destroyed so that we can
     * clean up the memory stored in it from the CPages destructor.
     */
    m_hWndPageList=CreateWindow(TEXT("listbox"), TEXT("Page List")
        , WS_POPUP | LBS_OWNERDRAWFIXED, 0, 0, 100, 100
        , HWND_DESKTOP, NULL, m_hInst, NULL);

    if (NULL==m_hWndPageList)
        return FALSE;

    //Create a 14 point Arial font, or use the system variable font.
    cy=MulDiv(-14, LOMETRIC_PER_INCH, 72);
    m_hFont=CreateFont(cy, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE
        , ANSI_CHARSET, OUT_TT_PRECIS, CLIP_TT_ALWAYS, PROOF_QUALITY
        , VARIABLE_PITCH | FF_SWISS, TEXT("Arial"));

    if (NULL==m_hFont)
        {
        m_hFont=(HFONT)GetStockObject(ANSI_VAR_FONT);
        m_fSystemFont=TRUE;
        }

    return TRUE;
    }

/*
 * CPages::FIStorageSet
 *
 * Purpose:
 *  Provides the document's IStorage to the pages for its own use.
 *  If this is a new storage, we initalize it with streams that we
 *  want to always exists.  If this is an open, then we create
 *  our page list from the PageList string we wrote before.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE to the new or opened storage.  If
 *                  NULL, we just clean up and exit.
 *  fChange         BOOL indicating is this was a Save As operation,
 *                  meaning that we have the structure already and
 *                  just need to change the value of m_pIStorage.
 *  fInitNew        BOOL indicating if this is a new storage or one
 *                  opened from a previous save.
 *
 * Return Value:
 *  None
 */

BOOL CPages::FIStorageSet(LPSTORAGE pIStorage, BOOL fChange
    , BOOL fInitNew)
    {
    DWORD           dwMode=STGM_DIRECT | STGM_READWRITE
                        | STGM_SHARE_EXCLUSIVE;
    HRESULT         hr;
    PCPage          pPage;
    BOOL            fRet=FALSE;
    ULONG           cbRead;
    PAGELIST        pgList;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    LPDWORD         pdwID;
    UINT            i;

    //If we're changing saved roots, simply open current page again
    if (fChange)
        {
        if (NULL==pIStorage)
            return FALSE;

        m_pIStorage->Release();
        m_pIStorage=pIStorage;
        m_pIStorage->AddRef();

        //CHAPTER7MOD
        FPageGet(m_iPageCur, &m_pPageCur, TRUE);
        //End CHAPTER7MOD
        return TRUE;
        }

    if (NULL!=m_hWndPageList)
        {
        //On new or open, clean out whatever it is we have.
        for (i=0; i < m_cPages; i++)
            {
            if (FPageGet(i, &pPage, FALSE))
                delete pPage;
            }

        SendMessage(m_hWndPageList, LB_RESETCONTENT, 0, 0L);
        }

    if (NULL!=m_pIStorage)
        m_pIStorage->Release();

    m_pIStorage=NULL;

    //If we're just cleaning up, then we're done.
    if (NULL==pIStorage)
        return TRUE;

    m_pIStorage=pIStorage;
    m_pIStorage->AddRef();

    //If this is a new storage, create the streams we require
    if (fInitNew)
        {
        //Page list header.
        hr=m_pIStorage->CreateStream(SZSTREAMPAGELIST, dwMode
            | STGM_CREATE, 0, 0, &pIStream);

        if (FAILED(hr))
            return FALSE;

        pIStream->Release();

        //Device Configuration
        hr=m_pIStorage->CreateStream(SZSTREAMDEVICECONFIG, dwMode
            | STGM_CREATE, 0, 0, &pIStream);

        if (FAILED(hr))
            return FALSE;

        pIStream->Release();
        return TRUE;
        }

    /*
     * We're opening an existing file:
     *  1)  Configure for the device we're on
     *  2)  Read the Page List and create page entries for each.
     */

    ConfigureForDevice();

    //Read the page list.
    hr=m_pIStorage->OpenStream(SZSTREAMPAGELIST, NULL, dwMode, 0
        , &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pIStream->Read(&pgList, sizeof(PAGELIST), &cbRead);
        m_cPages  =(UINT)pgList.cPages;
        m_iPageCur=(UINT)pgList.iPageCur;
        m_dwIDNext=pgList.dwIDNext;

        fRet=TRUE;
        cbRead=pgList.cPages*sizeof(DWORD);

        if (0!=cbRead)
            {
            pdwID=(LPDWORD)pIMalloc->Alloc(cbRead);

            if (NULL!=pdwID)
                {
                pIStream->Read(pdwID, cbRead, &cbRead);

                for (i=0; i < m_cPages; i++)
                    fRet &=FPageAdd(NOVALUE, *(pdwID+i), FALSE);

                pIMalloc->Free(pdwID);
                }
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    if (!fRet)
        return FALSE;

    //CHAPTER7MOD
    FPageGet(m_iPageCur, &m_pPageCur, TRUE);
    //End CHAPTER7MOD

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);

    return TRUE;
    }

/*
 * CPages::FIStorageUpdate
 *
 * Purpose:
 *  Insures that all pages are committed before a root save.
 *
 * Parameters:
 *  fCloseAll       BOOL directing us to close all open storages
 *                  and streams.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::FIStorageUpdate(BOOL fCloseAll)
    {
    PCPage          pPage;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    LPDWORD         pdwID;
    ULONG           cb;
    HRESULT         hr;
    PAGELIST        pgList;
    BOOL            fRet=FALSE;
    UINT            i;

    //We only need to close the current page--nothing else is open.
    //CHAPTER7MOD
    if (NULL!=m_pPageCur)
        {
        m_pPageCur->Update();

        if (fCloseAll)
            m_pPageCur->Close(FALSE);
        }
    //End CHAPTER7MOD

    //We don't hold anything else open, so write the page list.
    hr=m_pIStorage->OpenStream(SZSTREAMPAGELIST, NULL, STGM_DIRECT
        | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pgList.cPages=m_cPages;
        pgList.iPageCur=m_iPageCur;
        pgList.dwIDNext=m_dwIDNext;

        pIStream->Write(&pgList, sizeof(PAGELIST), &cb);

        cb=m_cPages*sizeof(DWORD);
        pdwID=(LPDWORD)pIMalloc->Alloc(cb);

        if (NULL!=pdwID)
            {
            for (i=0; i < m_cPages; i++)
                {
                FPageGet(i, &pPage, FALSE);
                *(pdwID+i)=pPage->GetID();
                }

            pIStream->Write(pdwID, cb, &cb);
            pIMalloc->Free(pdwID);
            fRet=TRUE;
            }
        pIMalloc->Release();
        }

    pIStream->Release();

    //CHAPTER7MOD
    //Don't forget to clean up the dirty flag when we do an update.
    m_fDirty=!fRet;
    //End CHAPTER7MOD
    return fRet;
    }

/*
 * CPages::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Pages window in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  None
 */

void CPages::RectGet(LPRECT pRect)
    {
    RECT        rc;
    POINT       pt;

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_hWnd, &rc);
    SETPOINT(pt, rc.left, rc.top);
    ScreenToClient(GetParent(m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return;
    }

/*
 * CPages::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Pages window which sizes to fit.
 *  Coordinates are given in parent terms.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPages::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    if (NULL==pRect)
        return;

    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, pRect->left, pRect->top
        , (UINT)cx, (UINT)cy, SWP_NOZORDER);

    UpdateScrollRanges();
    return;
    }

/*
 * CPages::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the pages window in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  None
 */

void CPages::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return;
    }

/*
 * CPages::SizeSet
 *
 * Purpose:
 *  Sets a new size in parent coordinates for the Pages window.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPages::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    if (NULL==pRect)
        return;

    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
        , SWP_NOMOVE | SWP_NOZORDER);

    UpdateScrollRanges();
    return;
    }

/*
 * CPages::PageInsert
 *
 * Purpose:
 *  Creates a new page immediately after the current page.  If
 *  there are no pages then this creates page 1.
 *
 * Parameters:
 *  uReserved       UINT unused
 *
 * Return Value:
 *  UINT            Index of the new page, 0 on failure.
 */

UINT CPages::PageInsert(UINT uReserved)
    {
    //CHAPTER7MOD
    if (0!=m_cPages && NULL!=m_pPageCur)
        {
        //Close the current page, committing changes.
        m_pPageCur->Close(TRUE);
        }
    //End CHAPTER7MOD

    //Create and open the new page.
    if (!FPageAdd(m_iPageCur, m_dwIDNext, TRUE))
        return 0;

    m_dwIDNext++;
    m_iPageCur++;
    m_cPages++;

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);

    //CHAPTER7MOD
    FPageGet(m_iPageCur, &m_pPageCur, FALSE);
    return m_iPageCur;
    //End CHAPTER7MOD
    }

/*
 * CPages::PageDelete
 *
 * Removes the current page from the page list.
 *
 * Parameters:
 *  uReserved       UINT unused
 *
 * Return Value:
 *  UINT            Index to the now current page from the page
 *                  list, NOVALUE on error.
 */

UINT CPages::PageDelete(UINT uReserved)
    {
    PCPage      pPage;

    if (!FPageGet(m_iPageCur, &pPage, FALSE))
        return NOVALUE;

    //CHAPTER7MOD
    //Delete the page in both the storage and in memory.
    SendMessage(m_hWndPageList, LB_DELETESTRING, m_iPageCur, 0L);

    m_pPageCur->Destroy(m_pIStorage);

    delete m_pPageCur;   //Does final pPage->Close
    m_pPageCur=NULL;
    //End CHAPTER7MOD

    /*
     * If this is the last page then the current is one less.  If
     * it's the only page the current is zero.  Otherwise the
     * current is the next page.
     */

    if (m_iPageCur==m_cPages-1)   //Covers last or only page.
        m_iPageCur--;

    m_cPages--;

    //Insure the new visible page is open.
    if (0!=m_cPages)
        {
        //CHAPTER7MOD
        FPageGet(m_iPageCur, &m_pPageCur, TRUE);
        //End CHAPTER7MOD
        InvalidateRect(m_hWnd, NULL, FALSE);
        }
    else
        InvalidateRect(m_hWnd, NULL, TRUE);

    UpdateWindow(m_hWnd);
    return m_iPageCur;
    }

/*
 * CPages::CurPageGet
 *
 * Purpose:
 *  Retrieves the index of the current page we're viewing.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the current page.
 */

UINT CPages::CurPageGet(void)
    {
    return m_iPageCur;
    }

/*
 * CPages::CurPageSet
 *
 * Purpose:
 *  Sets the index of the current page to view.
 *
 * Parameters:
 *  iPage           UINT index of the page to view. 0 means first
 *                  page, NOVALUE means last page.
 *
 * Return Value:
 *  UINT            Index of the previous current page, NOVALUE on
 *                  error.
 */

UINT CPages::CurPageSet(UINT iPage)
    {
    UINT    iPageNew;
    UINT    iPagePrev=m_iPageCur;
    PCPage  pPage;

    switch (iPage)
        {
        case 0:
            iPageNew=0;
            break;

        case NOVALUE:
            iPageNew=m_cPages-1;
            break;

        default:
            if (iPage >= m_cPages)
                iPage=0;

            iPageNew=iPage;
            break;
        }

    //No reason to switch to the same page.
    if (iPagePrev==iPageNew)
        return iPage;

    //Close the old page committing changes.
    if (!FPageGet(iPagePrev, &pPage, FALSE))
        return NOVALUE;

    pPage->Close(TRUE);

    m_iPageCur=iPageNew;

    //Open the new page.
    FPageGet(m_iPageCur, &m_pPageCur, TRUE);

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);
    return iPagePrev;
    }

/*
 * CPages::NumPagesGet
 *
 * Purpose:
 *  Returns the number of pages this object current contains.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of pages we contain.
 */

UINT CPages::NumPagesGet(void)
    {
    return m_cPages;
    }

/*
 * CPages::DevModeSet
 *
 * Purpose:
 *  Provides the Pages with the current printer information.
 *
 * Parameters:
 *  hDevMode        HGLOBAL to the memory containing the DEVMODE.
 *                  This function assumes responsibility for this
 *                  handle.
 *  hDevNames       HGLOBAL providing the driver name and device
 *                  name from which we can create a DC for
 *                  information.
 *
 * Return Value:
 *  BOOL            TRUE if we could accept this configuration,
 *                  FALSE otherwise.  If we return TRUE we also
 *                  delete the old memory we hold.
 */

BOOL CPages::DevModeSet(HGLOBAL hDevMode, HGLOBAL hDevNames)
    {
    LPDEVNAMES      pdn;
    LPTSTR          psz;
    HGLOBAL         hMem;
    PDEVICECONFIG   pdc;
    LPDEVMODE       pdm;
    LPSTREAM        pIStream;
    HRESULT         hr;
    ULONG           cbDevMode, cbWrite;
    BOOL            fRet=FALSE;

    if (NULL==hDevMode || NULL==hDevNames)
        return FALSE;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    /*
     * DEVMODE is variable length--total length in hDevMode, so the
     * amount to write is that plus string space.  We subtract
     * sizeof(DEVMODE) as that is already included from GlobalSize.
     */
    cbDevMode=GlobalSize(hDevMode);
    cbWrite=cbDevMode+sizeof(DEVICECONFIG)-sizeof(DEVMODE);

    hMem=GlobalAlloc(GHND, cbWrite);

    if (NULL==hMem)
        {
        pIStream->Release();
        return FALSE;
        }

    pdc=(PDEVICECONFIG)GlobalLock(hMem);    //This always works
    pdm=(LPDEVMODE)GlobalLock(hDevMode);    //This might not

    if (NULL!=pdm)
        {
        pdc->cb=cbWrite;
        pdc->cbDevMode=cbDevMode;
        memcpy(&pdc->dm, pdm, (int)cbDevMode);
        GlobalUnlock(hDevMode);

        psz=(LPTSTR)GlobalLock(hDevNames);

        if (NULL!=psz)
            {
            pdn=(LPDEVNAMES)psz;
            lstrcpy(pdc->szDriver, psz+pdn->wDriverOffset);
            lstrcpy(pdc->szDevice, psz+pdn->wDeviceOffset);
            lstrcpy(pdc->szPort,   psz+pdn->wOutputOffset);

            pIStream->Write(pdc, cbWrite, &cbWrite);
            GlobalUnlock(hDevNames);
            fRet=TRUE;
            }
        }

    GlobalUnlock(hMem);
    GlobalFree(hMem);

    pIStream->Release();

    if (!fRet)
        return FALSE;

    GlobalFree(hDevNames);
    GlobalFree(hDevMode);

    return ConfigureForDevice();
    }

/*
 * CPages::DevModeGet
 *
 * Purpose:
 *  Retrieves a copy of the current DEVMODE structure for this
 *  Pages window.  The caller is responsible for this memory.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HGLOBAL         Handle to the memory containing the DEVMODE
 *                  structure.
 */

HGLOBAL CPages::DevModeGet(void)
    {
    HGLOBAL         hMem;
    LPVOID          pv;
    ULONG           cbDevMode, cbRead;
    LARGE_INTEGER   li;
    LPSTREAM        pIStream;
    HRESULT         hr;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    //Read how much to allocate for the DEVMODE structure
    LISet32(li, CBSEEKOFFSETCBDEVMODE);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);
    pIStream->Read(&cbDevMode, sizeof(ULONG), &cbRead);

    hMem=GlobalAlloc(GHND, cbDevMode);

    if (NULL!=hMem)
        {
        pv=(LPVOID)GlobalLock(hMem);
        pIStream->Read(pv, cbDevMode, &cbRead);
        GlobalUnlock(hMem);
        }

    pIStream->Release();
    return hMem;
    }

//CHAPTER7MOD
/*
 * CPages::DevReadConfig
 *
 * Purpose:
 *  Public function to read the current device configuration and
 *  optionally return an information context for it.
 *
 *
 * Parameters:
 *  ppcd            PCOMBINEDEVICE * in which to return a pointer
 *                  to an allocated structure that has all the
 *                  device information we want.  Ignored if NULL.
 *                  This is allocated with the task allocator.
 *  phDC            HDC * in which to return the information
 *                  context.  If NULL, no IC is created.  Caller
 *                  becomes responsible for the returned IC.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::DevReadConfig(PCOMBINEDEVICE *ppcd, HDC *phDC)
    {
    HRESULT         hr;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    PCOMBINEDEVICE  pcd;
    ULONG           cb, cbRead;
    LARGE_INTEGER   li;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    /*
     * Allocate the COMBINEDEVICE structure including the variable
     * information past the DEVMODE part.
     */

    hr=CoGetMalloc(MEMCTX_TASK, &pIMalloc);

    if (FAILED(hr))
        {
        pIStream->Release();
        return FALSE;
        }

    /*
     * Read size of the DEVICECONFIG structure including variable
     * portion of DEVMODE.  We need to load all this information
     * for CreateIC.  To this size we'll add the size of
     * DVTARGETDEVICE in order to allocate a COMBINEDEVICE.
     */

    pIStream->Read(&cb, sizeof(DWORD), &cbRead);

    pcd=(PCOMBINEDEVICE)pIMalloc->Alloc(cb+sizeof(DVTARGETDEVICE));

    if (NULL==pcd)
        {
        pIMalloc->Release();
        pIStream->Release();
        return FALSE;
        }

    //Now get the real information.
    LISet32(li, 0);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);
    pIStream->Read(&(pcd->dc), cb, &cbRead);
    pIStream->Release();

    /*
     * If requested, complete the DVTARGETDEVICE structure in
     * pcd and store pcd in *ppcd for return.
     */

    if (NULL!=ppcd)
        {
        WORD    cb=sizeof(DVTARGETDEVICE);

        pcd->td.tdSize=cb;
        pcd->td.tdExtDevmodeOffset=cb;
        pcd->td.tdDriverNameOffset=cb+sizeof(DEVMODE);
        pcd->td.tdDeviceNameOffset=cb+sizeof(DEVMODE)
            +(CCHDEVICENAME*sizeof(TCHAR));
        pcd->td.tdPortNameOffset  =cb+sizeof(DEVMODE)
            +(CCHDEVICENAME*2*sizeof(TCHAR));

        *ppcd=pcd;
        }

    //Create an IC if requested.
    if (NULL!=phDC)
        {
        //Get the DC then configure
        *phDC=CreateIC(pcd->dc.szDriver, pcd->dc.szDevice
            , pcd->dc.szPort, &(pcd->dc.dm));

        if (NULL==*phDC)
            return FALSE;
        }

    //pcd is a temporary allocation in this case
    if (NULL==ppcd)
        pIMalloc->Free(pcd);

    pIMalloc->Release();
    return (NULL!=*phDC);
    }
//End CHAPTER7MOD

/*
 * CPages::ConfigureForDevice
 *
 * Purpose:
 *  Recalculates our drawing configuration based on the contents of
 *  an hDC.  If no HDC is given we use the contents of our DevMode
 *  stream.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::ConfigureForDevice(void)
    {
    POINT           ptOffset, ptPaper;
    RECT            rc;
    HDC             hDC;

    //CHAPTER7MOD
    if (!DevReadConfig(NULL, &hDC))
        return FALSE;
    //End CHAPTER7MOD

    //Get usable page dimensions:  already sensitive to orientation
    m_cx=GetDeviceCaps(hDC, HORZSIZE)*10-16; //*10: mm to LOMETRIC
    m_cy=GetDeviceCaps(hDC, VERTSIZE)*10-16; //-16: for driver bugs.

    //Calculate the printer-limited margins on each side in LOMETRIC
    Escape(hDC, GETPRINTINGOFFSET, NULL, NULL, &ptOffset);
    Escape(hDC, GETPHYSPAGESIZE,   NULL, NULL, &ptPaper);

    SetRect(&rc, ptOffset.x, ptOffset.y, ptPaper.x, ptPaper.y);
    SetMapMode(hDC, MM_LOMETRIC);
    RectConvertMappings(&rc, hDC, FALSE);

    //Left and top margins are the printing offset.
    m_xMarginLeft= rc.left+8;   //+8 to match -16 above
    m_yMarginTop =-rc.top+8;    //LOMETRIC makes this negative.

    //Right is (paper width)-(usable width)-(left margin)
    m_xMarginRight =rc.right-m_cx-m_xMarginLeft;

    //Bottom is (paper height)-(usable height)-(top margin)+1
    m_yMarginBottom=-rc.bottom-m_cy-m_yMarginTop+1;

    UpdateScrollRanges();

    DeleteDC(hDC);
    return TRUE;
    }

//CHAPTER7MOD
/*
 * CPages::TenantCreate
 * CPages::TenantDestroy
 *
 * Purpose:
 *  Pass-throughs for CPage members on the current page.
 */

BOOL CPages::TenantCreate(TENANTTYPE tType, LPVOID pv
    , LPFORMATETC pFE, PPATRONOBJECT ppo, DWORD dwData)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantCreate(tType, pv, pFE, ppo, dwData);
    m_fDirty |= fRet;
    return fRet;
    }

BOOL CPages::TenantDestroy(void)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantDestroy();
    m_fDirty |= fRet;
    return fRet;
    }

/*
 * CPages::TenantClip
 *
 * Purpose:
 *  Copies or cuts the currently selected tenant to the clipoard.
 *
 * Parameters:
 *  fCut            BOOL TRUE to cut the object, FALSE to copy.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::TenantClip(BOOL fCut)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantClip(fCut);
    m_fDirty |= (fRet && fCut);
    return fRet;
    }

/*
 * CPages::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected on this
 *  page for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU of the Edit menu.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPages::FQueryObjectSelected(HMENU hMenu)
    {
    if (NULL==m_pPageCur)
        return FALSE;

    return m_pPageCur->FQueryObjectSelected(hMenu);
    }

/*
 * CPages::CalcBoundingRect
 * (Protected)
 *
 * Purpose:
 *  Calculates a rectangle that bounds the printed page and the
 *  current scroll state of the window.
 *
 * Parameters:
 *  prc             LPRECT to fill with window (device) coordinates.
 *  fWindow         BOOL indicating to include the window in this
 *                  calculation or return only the printed page
 *                  coordinates.
 *
 * Return Value:
 *  None
 */

void CPages::CalcBoundingRect(LPRECT prc, BOOL fWindow)
    {
    RECT        rc, rcT;

    if (NULL==prc)
        return;

    //Calculate the boundaries for sizing: intersect page & screen
    rc.left=LOMETRIC_BORDER+m_xMarginLeft;
    rc.top =-LOMETRIC_BORDER-m_yMarginTop;
    rc.right =rc.left+(UINT)m_cx;
    rc.bottom=rc.top -(UINT)m_cy;
    RectConvertMappings(&rc, NULL, TRUE);
    OffsetRect(&rc, -(int)m_xPos, -(int)m_yPos);

    if (!fWindow)
        {
        *prc=rc;
        return;

}

    //Intersect with window to make the size bounds.
    GetClientRect(m_hWnd, &rcT);
    IntersectRect(prc, &rc, &rcT);
    return;
    }

//End CHAPTER7MOD

/*
 * CPages::FPageGet
 * (Protected)
 *
 * Purpose:
 *  Returns a page of a given index returning a BOOL so it's simple
 *  to use this function inside if statements.
 *
 * Parameters:
 *  iPage           UINT page to retrieve 0 based.
 *  ppPage          PCPage * in which to return the page pointer
 *  fOpen           BOOL indicating if we should open this page as
 *                  well.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::FPageGet(UINT iPage, PCPage *ppPage, BOOL fOpen)
    {
    if (NULL==ppPage)
        return FALSE;

    if (LB_ERR!=SendMessage(m_hWndPageList, LB_GETTEXT
        , iPage, (LONG)ppPage))
        {
        if (fOpen)
            (*ppPage)->FOpen(m_pIStorage);

        return TRUE;
        }

    return FALSE;
    }

/*
 * CPages::FPageAdd
 * (Protected)
 *
 * Purpose:
 *  Creates a new page initialized to the given values.  The new
 *  page's storage is created if it does not already exist.  If
 *  fOpenStorage is set the page's storage is opened and left
 *  opened.
 *
 * Parameters:
 *  iPage           UINT Location at which to insert page; new page
 *                  is inserted after this position.  NOVALUE for
 *                  the end.
 *  dwID            DWORD ID for this page.
 *  fOpenStorage    BOOL indicating if we're to leave the storage
 *                  open.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPages::FPageAdd(UINT iPage, DWORD dwID, BOOL fOpenStorage)
    {
    PCPage      pPage;
    LRESULT     lr;

    //CHAPTER7MOD
    pPage=new CPage(dwID, m_hWnd, this);
    //End CHAPTER7MOD

    if (NULL==pPage)
        return FALSE;

    if (fOpenStorage)
        pPage->FOpen(m_pIStorage);

    if (NOVALUE==iPage)
        iPage--;

    //Now try to add to the listbox.
    lr=SendMessage(m_hWndPageList, LB_INSERTSTRING, iPage+1
        , (LONG)pPage);

    if (LB_ERRSPACE==lr)
        {
        if (fOpenStorage)
            pPage->Close(FALSE);

        delete pPage;
        return FALSE;
        }

    return TRUE;
    }


PAGES.H

/*
 * PAGES.H
 * Patron Chapter 7
 *
 * Definitions and function prototypes for the Pages window control
 * as well as the CPage and CTenant classes.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _PAGES_H_
#define _PAGES_H_

//We need this for UICursorLoad and new-style cursors.
#include <bttncur.h>

//Versioning.
#define VERSIONMAJOR                2
#define VERSIONMINOR                0
#define VERSIONCURRENT              0x00020000

//Classname
#define SZCLASSPAGES                TEXT("pages")

#define HIMETRIC_PER_INCH           2540
#define LOMETRIC_PER_INCH           254
#define LOMETRIC_BORDER             60          //Border around page

//Window extra bytes and offsets
#define CBPAGESWNDEXTRA             (sizeof(LONG))
#define PAGEWL_STRUCTURE            0

//CHAPTER7MOD

/*
 * Tenant class describing an individual piece of data in a page.
 * It knows where it sits, what object is inside of it, and what
 * its identifer is such that it can find it's storage within a
 * page.
 */

//Patron Objects clipboard format
typedef struct tagPATRONOBJECT
    {
    POINTL      ptl;        //Location of object
    POINTL      ptlPick;    //Pick point from drag-drop operation
    SIZEL       szl;        //Extents of object (absolute)
    FORMATETC   fe;         //Actual object format
    } PATRONOBJECT, *PPATRONOBJECT;

//Values for hit-testing, drawing, and resize-tracking tenants
#define CXYHANDLE       5

//Tenant creation types (not persistent)
typedef enum
    {
    TENANTTYPE_NULL=0,
    TENANTTYPE_STATIC,
    } TENANTTYPE, *PTENANTTYPE;

//State flags
#define TENANTSTATE_DEFAULT      0x00000000
#define TENANTSTATE_SELECTED     0x00000001

/*
 * Persistent information we need to save for each tenant, which is
 * done in the tenant list instead of with each tenant.  Since this
 * is a small structure it's best not to blow another stream for it
 * (overhead).  (fSetExtent used in compound documents later on.)
 */
typedef struct tagTENANTINFO
    {
    DWORD       dwID;
    RECTL       rcl;
    FORMATETC   fe;             //Excludes ptd
    short       fSetExtent;     //Call IOleObject::SetExtent on Run
    } TENANTINFO, *PTENANTINFO;

class CTenant
    {
    private:
        HWND            m_hWnd;         //Pages window
        DWORD           m_dwID;         //Persistent DWORD ID
        DWORD           m_cOpens;       //Count calls to FOpen

        BOOL            m_fInitialized; //Something here?
        LPUNKNOWN       m_pObj;         //The object here
        LPSTORAGE       m_pIStorage;    //Sub-storage for tenant

        FORMATETC       m_fe;           //Used to create the object
        DWORD           m_dwState;      //State flags
        RECTL           m_rcl;          //Space of this object
        CLSID           m_clsID;        //Object class (for statics)
        BOOL            m_fSetExtent;   //Call SetExtent on next run

        class CPages   *m_pPG;          //Pages window

    protected:
        HRESULT CreateStatic(LPDATAOBJECT, LPFORMATETC
            , LPUNKNOWN *);

    public:
        CTenant(DWORD, HWND, CPages *);
        ~CTenant(void);

        DWORD       GetID(void);
        UINT        GetStorageName(LPTSTR);
        UINT        UCreate(TENANTTYPE, LPVOID, LPFORMATETC, LPPOINTL
                        , LPSIZEL, LPSTORAGE, PPATRONOBJECT, DWORD);
        BOOL        FLoad(LPSTORAGE, PTENANTINFO);
        void        GetInfo(PTENANTINFO);
        BOOL        FOpen(LPSTORAGE);
        void        Close(BOOL);
        BOOL        Update(void);
        void        Destroy(LPSTORAGE);

        void        Select(BOOL);
        BOOL        Activate(LONG);
        void        Draw(HDC, DVTARGETDEVICE *, HDC, int, int
                        , BOOL, BOOL);
        void        Repaint(void);
        void        Invalidate(void);

        void        ObjectGet(LPUNKNOWN *);
        void        FormatEtcGet(LPFORMATETC, BOOL);
        void        SizeGet(LPSIZEL, BOOL);
        void        SizeSet(LPSIZEL, BOOL);
        void        RectGet(LPRECTL, BOOL);
        void        RectSet(LPRECTL, BOOL);
    };

typedef CTenant *PCTenant;

//Return codes for UCreate
#define UCREATE_FAILED              0
#define UCREATE_GRAPHICONLY         1
#define UCREATE_PLACEDOBJECT        2

typedef struct tagTENANTLIST
    {
    DWORD       cTenants;
    DWORD       dwIDNext;
    } TENANTLIST, *PTENANTLIST;

#define SZSTREAMTENANTLIST        OLESTR("Tenant List")

//Delay timer used in mouse debouncing
#define IDTIMER_DEBOUNCE          120
//End CHAPTER7MOD

/*
 * Page class describing an individual page and what things it
 * contains, managing an IStorage for us.
 *
 * A DWORD is used to identify this page as the name of the storage
 * is the string form of this ID.  If we added a page every second,
 * it would take 136 years to overrun this counter, so we can
 * get away with saving it persistently.  I hope this software is
 * obsolete by then.
 */

class CPage
    {
    private:
        DWORD       m_dwID;             //Persistent identifier
        LPSTORAGE   m_pIStorage;        //Substorage for this page
        //CHAPTER7MOD
        HWND        m_hWnd;             //Pages window
        DWORD       m_cOpens;           //Calls to FOpen

        class CPages *m_pPG;            //Pages window

        DWORD       m_dwIDNext;
        DWORD       m_cTenants;
        HWND        m_hWndTenantList;   //Listbox; our tenant list

        UINT        m_iTenantCur;
        PCTenant    m_pTenantCur;

        UINT        m_uHTCode;          //Last hit-test/mouse move
        UINT        m_uSizingFlags;     //Restrictions on sizing
        BOOL        m_fTracking;        //Tracking resize?
        RECTL       m_rclOrg;           //Original before tracking
        RECTL       m_rcl;              //Tracking rectangle
        RECTL       m_rclBounds;        //Boundaries f/size tracking
        HDC         m_hDC;              //Tracking hDC

        BOOL        m_fSizePending;     //Waiting for debounce?
        int         m_cxyDist;          //Debounce distance
        UINT        m_cDelay;           //Debounce delay
        POINTS      m_ptDown;           //Point of click to debounce
        DWORD       m_fTimer;           //Timer active?

    protected:
        BOOL         FTenantGet(UINT, PCTenant *, BOOL);
        BOOL         FTenantAdd(UINT, DWORD, PCTenant *);
        LPDATAOBJECT TransferObjectCreate(LPPOINTL);

        //PAGEMOUS.CPP
        UINT         TenantFromPoint(UINT, UINT, PCTenant *);
        //End CHAPTER7MOD

    public:
        //CHAPTER7MOD
        CPage(DWORD, HWND, class CPages *);
        //End CHAPTER7MOD
        ~CPage(void);

        DWORD       GetID(void);
        BOOL        FOpen(LPSTORAGE);
        void        Close(BOOL);
        BOOL        Update(void);
        void        Destroy(LPSTORAGE);
        UINT        GetStorageName(LPTSTR);

        //CHAPTER7MOD
        void        Draw(HDC, int, int, BOOL, BOOL);

        BOOL        TenantCreate(TENANTTYPE, LPVOID, LPFORMATETC
                        , PPATRONOBJECT, DWORD);
        BOOL        TenantDestroy(void);
        BOOL        TenantClip(BOOL);
        BOOL        FQueryObjectSelected(HMENU);

        //PAGEMOUSE.CPP
        BOOL        OnLeftDown(UINT, UINT, UINT);
        BOOL        OnLeftDoubleClick(UINT, UINT, UINT);
        BOOL        OnLeftUp(UINT, UINT, UINT);
        void        OnMouseMove(UINT, int, int);
        void        OnTimer(UINT);
        void        StartSizeTracking(void);
        void        OnNCHitTest(UINT, UINT);
        BOOL        OnSetCursor(UINT);
        //End CHAPTER7MOD
    };

typedef CPage *PCPage;

/*
 * Structures to save with the document describing the device
 * configuration and pages that we have.  This is followed by
 * a list of DWORD IDs for the individual pages.
 */

typedef struct tagDEVICECONFIG
    {
    DWORD       cb;                         //Size of structure
    TCHAR       szDriver[CCHDEVICENAME];
    TCHAR       szDevice[CCHDEVICENAME];
    TCHAR       szPort[CCHDEVICENAME];
    DWORD       cbDevMode;                  //Size of actual DEVMODE
    DEVMODE     dm;                         //Variable
    } DEVICECONFIG, *PDEVICECONFIG;

//Offset to cbDevMode
#define CBSEEKOFFSETCBDEVMODE  (sizeof(DWORD)   \
                               +(3*CCHDEVICENAME*sizeof(TCHAR)))

//CHAPTER7MOD
//Combined OLE and Patron device structures.
typedef struct tagCOMBINEDEVICE
    {
    DVTARGETDEVICE  td;
    DEVICECONFIG    dc;
    } COMBINEBDEVICE, *PCOMBINEDEVICE;
//End CHAPTER7MOD

typedef struct tagPAGELIST
    {
    DWORD       cPages;
    DWORD       iPageCur;
    DWORD       dwIDNext;
    } PAGELIST, *PPAGELIST;

//PAGEWIN.CPP
LRESULT APIENTRY PagesWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL    APIENTRY AbortProc(HDC, int);
BOOL    APIENTRY PrintDlgProc(HWND, UINT, WPARAM, LPARAM);
void             RectConvertMappings(LPRECT, HDC, BOOL);

class CPages : public CWindow
    {
    friend LRESULT APIENTRY PagesWndProc(HWND, UINT, WPARAM, LPARAM);
    friend BOOL    APIENTRY PrintDlgProc(HWND, UINT, WPARAM, LPARAM);

    friend class CPage;
    friend class CTenant;

    protected:
        //CHAPTER7MOD
        PCPage      m_pPageCur;             //Current page
        //End CHAPTER7MOD
        UINT        m_iPageCur;             //Current page
        UINT        m_cPages;               //Number of pages

        HWND        m_hWndPageList;         //Listbox with page list
        HFONT       m_hFont;                //Page font
        BOOL        m_fSystemFont;          //m_hFont system object?

        UINT        m_cx;                   //Page size in LOMETRIC
        UINT        m_cy;

        UINT        m_xMarginLeft;          //Unusable margins,
        UINT        m_xMarginRight;         //in LOMETRIC
        UINT        m_yMarginTop;
        UINT        m_yMarginBottom;

        UINT        m_xPos;                 //Viewport scroll pos,
        UINT        m_yPos;                 //both in *PIXELS*

        DWORD       m_dwIDNext;             //Next ID for a page.
        LPSTORAGE   m_pIStorage;            //Root storage

        //CHAPTER7MOD
        UINT        m_cf;                   //Clipboard format
        BOOL        m_fDirty;
        //End CHAPTER7MOD

    protected:
        void      Draw(HDC, BOOL, BOOL);
        void      UpdateScrollRanges(void);
        BOOL      ConfigureForDevice(void);
        BOOL      FPageGet(UINT, PCPage *, BOOL);
        BOOL      FPageAdd(UINT, DWORD, BOOL);

        //CHAPTER7MOD
        void      CalcBoundingRect(LPRECT, BOOL);
        //End CHAPTER7MOD

    public:
        //CHAPTER7MOD
        CPages(HINSTANCE, UINT);
        //End CHAPTER7MOD
        ~CPages(void);

        BOOL      FInit(HWND, LPRECT, DWORD, UINT, LPVOID);

        BOOL      FIStorageSet(LPSTORAGE, BOOL, BOOL);
        BOOL      FIStorageUpdate(BOOL);

        BOOL      Print(HDC, LPTSTR, DWORD, UINT, UINT, UINT);

        void      RectGet(LPRECT);
        void      RectSet(LPRECT, BOOL);
        void      SizeGet(LPRECT);
        void      SizeSet(LPRECT, BOOL);

        UINT      PageInsert(UINT);
        UINT      PageDelete(UINT);
        UINT      CurPageGet(void);
        UINT      CurPageSet(UINT);
        UINT      NumPagesGet(void);

        BOOL      DevModeSet(HGLOBAL, HGLOBAL);
        HGLOBAL   DevModeGet(void);

        //CHAPTER7MOD
        BOOL      FIsDirty(void);
        BOOL      DevReadConfig(PCOMBINEDEVICE *, HDC *);
        BOOL      TenantCreate(TENANTTYPE, LPVOID, LPFORMATETC
                      , PPATRONOBJECT, DWORD);
        BOOL      TenantDestroy(void);
        BOOL      TenantClip(BOOL);
        BOOL      FQueryObjectSelected(HMENU);
        //End CHAPTER7MOD
    };

typedef CPages *PCPages;

//Fixed names of streams in the Pages IStorage
#define SZSTREAMPAGELIST        OLESTR("Page List")
#define SZSTREAMDEVICECONFIG    OLESTR("Device Configuration")

#endif  //_PAGES_H_


PAGEWIN.CPP

/*
 * PAGEWIN.CPP
 * Patron Chapter 7
 *
 * Window procedure for the Pages window and support functions.
 * This window manages its own scrollbars and viewport and provides
 * printing capabilities as well.  The public CPages::Print
 * function lives here.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

static HWND g_hDlgPrint=NULL;
static BOOL g_fCancelPrint=FALSE;

/*
 * PagesWndProc
 *
 * Purpose:
 *  Window procedure for the Pages window.
 */

LRESULT APIENTRY PagesWndProc(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    PCPages         ppg;
    PAINTSTRUCT     ps;
    HDC             hDC;
    int             iPos, iTmp;
    int             iMin, iMax;
    UINT            idScroll;
    //CHAPTER7MOD
    BOOL            fDirty=FALSE;
    //End CHAPTER7MOD

    ppg=(PCPages)GetWindowLong(hWnd, PAGEWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppg=(PCPages)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PAGEWL_STRUCTURE, (LONG)ppg);

            ppg->m_hWnd=hWnd;
            break;

        case WM_PAINT:
            hDC=BeginPaint(hWnd, &ps);

            //Draw only if we have a page to show.
            if (0!=ppg->m_cPages)
                ppg->Draw(hDC, FALSE, FALSE);

            EndPaint(hWnd, &ps);
            break;

        case WM_HSCROLL:
        case WM_VSCROLL:
            idScroll=(WM_HSCROLL==iMsg) ? SB_HORZ : SB_VERT;

            iPos=GetScrollPos(hWnd, idScroll);
            iTmp=iPos;
            GetScrollRange(hWnd, idScroll, &iMin, &iMax);

            switch (LOWORD(wParam))
                {
                case SB_LINEUP:     iPos -= 20;  break;
                case SB_PAGEUP:     iPos -=100;  break;
                case SB_LINEDOWN:   iPos += 20;  break;
                case SB_PAGEDOWN:   iPos +=100;  break;

                case SB_THUMBPOSITION:
                    iPos=ScrollThumbPosition(wParam, lParam);
                    break;

                //We don't want scrolling on this message.
                case SB_THUMBTRACK:
                    return 0L;
                }

            iPos=max(iMin, min(iPos, iMax));

            if (iPos!=iTmp)
                {
                //Set the new position and scroll the window
                SetScrollPos(hWnd, idScroll, iPos, TRUE);

                if (SB_HORZ==idScroll)
                    {
                    ppg->m_xPos=iPos;
                    ScrollWindow(hWnd, iTmp-iPos, 0, NULL, NULL);
                    }
                else
                    {
                    ppg->m_yPos=iPos;
                    ScrollWindow(hWnd, 0, iTmp-iPos, NULL, NULL);
                    }
                }

            break;

        //CHAPTER7MOD
        case WM_LBUTTONDOWN:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftDown(wParam
                , LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_LBUTTONUP:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftUp(wParam
                , LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_LBUTTONDBLCLK:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftDoubleClick(wParam, LOWORD(lParam)
                , HIWORD(lParam));
            break;

        case WM_MOUSEMOVE:
            if (NULL==ppg->m_pPageCur)
                break;

            ppg->m_pPageCur->OnMouseMove(wParam, LOWORD(lParam)
                , HIWORD(lParam));
            break;

        case WM_TIMER:
            if (NULL==ppg->m_pPageCur)
                break;

            ppg->m_pPageCur->OnTimer(wParam);
            break;

        case WM_NCHITTEST:
            if (NULL!=ppg->m_pPageCur)
                {
                /*
                 * This just saves information in the page for
                 * OnSetCursor
                 */
                ppg->m_pPageCur->OnNCHitTest(LOWORD(lParam)
                    , HIWORD(lParam));
                }

            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        case WM_SETCURSOR:
            if (NULL!=ppg->m_pPageCur)
                {
                if (ppg->m_pPageCur->OnSetCursor(LOWORD(lParam)))
                    break;
                }

            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        //End CHAPTER7MOD

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    //CHAPTER7MOD
    ppg->m_fDirty |= fDirty;
    //End CHAPTER7MOD
    return 0L;
    }

/*
 * RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device to logical
 *  coordinates where the hDC defines the logical coordinates.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  hDC             HDC describing the logical coordinate system.
 *                  if NULL, uses a screen DC in MM_LOMETRIC.
 *  fToDevice       BOOL TRUE to convert from uConv to device,
 *                  FALSE to convert device to uConv.
 *
 * Return Value:
 *  None
 */

void RectConvertMappings(LPRECT pRect, HDC hDC, BOOL fToDevice)
    {
    POINT   rgpt[2];
    BOOL    fSysDC=FALSE;

    if (NULL==pRect)
        return;

    rgpt[0].x=pRect->left;
    rgpt[0].y=pRect->top;
    rgpt[1].x=pRect->right;
    rgpt[1].y=pRect->bottom;

    if (NULL==hDC)
        {
        hDC=GetDC(NULL);
        SetMapMode(hDC, MM_LOMETRIC);
        fSysDC=TRUE;
        }

    if (fToDevice)
        LPtoDP(hDC, rgpt, 2);
    else
        DPtoLP(hDC, rgpt, 2);

    if (fSysDC)
        ReleaseDC(NULL, hDC);

    pRect->left=rgpt[0].x;
    pRect->top=rgpt[0].y;
    pRect->right=rgpt[1].x;
    pRect->bottom=rgpt[1].y;

    return;
    }

/*
 * CPages::Draw
 *
 * Purpose:
 *  Paints the current page in the pages window.
 *
 * Parameters:
 *  hDC             HDC to draw on, could be a metafile or printer
 *                  DC or any other type of DC.
 *  fNoColor        BOOL indicating if we should use screen colors
 *                  or printer colors (B&W).  Objects are printed
 *                  as-is, however.  This is TRUE for printer DCs
 *                  or print preview.
 *  fPrinter        BOOL indicating if this is a printer DC in which
 *                  case we eliminate some of the fancy drawing,
 *                  like shadows on the page and so forth.
 *
 * Return Value:
 *  None
 */

void CPages::Draw(HDC hDC, BOOL fNoColor, BOOL fPrinter)
    {
    RECT            rc, rcT;
    UINT            uMM;
    HPEN            hPen;
    HBRUSH          hBrush;
    HGDIOBJ         hObj1, hObj2;
    COLORREF        cr;
    TCHAR           szTemp[20];
    UINT            cch;
    SIZE            sz;
    //CHAPTER7MOD
    PCPage          pPage;
    RECT            rcPos;
    //End CHAPTER7MOD

    //Make sure the DC is in LOMETRIC
    uMM=SetMapMode(hDC, MM_LOMETRIC);

    if (!fPrinter)
        {
        /*
         * We maintain a 6mm border around the page on the screen
         * besides 12.7mm margins.  We also have to account for
         * the scroll position with m_*Pos which are in pixels so
         * we have to convert them.
         */

        //CHAPTER7MOD
        SetRect(&rcPos, m_xPos, m_yPos, 0, 0);
        RectConvertMappings(&rcPos, hDC, FALSE);

        rc.left  = LOMETRIC_BORDER-rcPos.left;
        rc.top   =-LOMETRIC_BORDER-rcPos.top;
        //End CHAPTER7MOD
        }
    else
        {
        /*
         * We define the corner of the printed paper at a negative
         * offset so rc.right and rc.bottom come out right below.
         */
        SetRect(&rc, -(int)m_xMarginLeft, m_yMarginTop, 0, 0);
        }

    rc.right=rc.left+m_cx+(m_xMarginLeft+m_xMarginRight);
    rc.bottom=rc.top-m_cy-(m_yMarginTop+m_yMarginBottom);

    //Draw a rect filled with the window color to show the page.
    if (!fPrinter)
        {
        if (fNoColor)
            {
            //Black frame, white box for printed colors.
            hPen  =CreatePen(PS_SOLID, 0, RGB(0,0,0));
            hBrush=CreateSolidBrush(RGB(255, 255, 255));
            }
        else
            {
            //Normal colors on display
            hPen=CreatePen(PS_SOLID, 0
                , GetSysColor(COLOR_WINDOWFRAME));
            hBrush=CreateSolidBrush(GetSysColor(COLOR_WINDOW));
            }

        hObj1=SelectObject(hDC, hPen);
        hObj2=SelectObject(hDC, hBrush);

        //Paper boundary
        Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom+1);

        /*
         * Draw a shadow on the *visual* bottom and right edges
         * .5mm wide.  If the button shadow color and workspace
         * colors match, then use black.  We always use black
         * when printing as well.
         */
        if (fNoColor)
            cr=RGB(0,0,0);
        else
            {
            cr=GetSysColor(COLOR_BTNSHADOW);

            if (GetSysColor(COLOR_APPWORKSPACE)==cr)
                cr=RGB(0,0,0);
            }

        cr=SetBkColor(hDC, cr);
        SetRect(&rcT, rc.left+5, rc.bottom, rc.right+5,rc.bottom-5);
        ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rcT, NULL, 0, NULL);

        SetRect(&rcT, rc.right, rc.top-5, rc.right+5, rc.bottom-5);
        ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rcT, NULL, 0, NULL);
        SetBkColor(hDC, cr);

        SelectObject(hDC, hObj1);
        SelectObject(hDC, hObj2);
        DeleteObject(hBrush);
        DeleteObject(hPen);
        }

    //Write the page number in the lower left corner
    if (!fNoColor)
        {
        SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));
        SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
        }

    //Write the page number in our page font.
    cch=wsprintf(szTemp, TEXT("Page %d"), m_iPageCur+1);

    hObj1=SelectObject(hDC, m_hFont);
    GetTextExtentPoint(hDC, szTemp, cch, &sz);

    TextOut(hDC, rc.left+m_xMarginLeft
        , rc.bottom+m_yMarginBottom+sz.cy, szTemp, cch);

    SelectObject(hDC, hObj1);

    //Rectangle to show border.
    MoveToEx(hDC, rc.left+m_xMarginLeft, rc.top-m_yMarginTop, NULL);
    LineTo(hDC, rc.left+m_xMarginLeft,   rc.bottom+m_yMarginBottom);
    LineTo(hDC, rc.right-m_xMarginRight, rc.bottom+m_yMarginBottom);
    LineTo(hDC, rc.right-m_xMarginRight, rc.top-m_yMarginTop);
    LineTo(hDC, rc.left+m_xMarginLeft,   rc.top-m_yMarginTop);

    //CHAPTER7MOD
    /*
     * Go draw the objects on this page.  If the page is not open,
     * we open it anyway.  If it is already open, then opening again
     * will bump it's reference count, so the Close in ineffectual.
     */
    if (FPageGet(m_iPageCur, &pPage, TRUE))
        {
        if (!fPrinter)
            {
            pPage->Draw(hDC, rcPos.left, rcPos.top, fNoColor
                , fPrinter);
            }
        else
            pPage->Draw(hDC, 0, 0, fNoColor, fPrinter);

        pPage->Close(FALSE);
        }
    //End CHAPTER7MOD

    SetMapMode(hDC, uMM);
    return;
    }

/*
 * CPages::UpdateScrollRanges
 *
 * Purpose:
 *  Reset scrollbar ranges (horizontal and vertical) depending on
 *  the window size and the page size.  This function may remove
 *  the scrollbars altogether.
 *
 * Parameters:
 *  None, but set m_cx, m_cy and size m_hWnd before calling.
 *
 * Return Value:
 *  None
 */

void CPages::UpdateScrollRanges(void)
    {
    UINT        cxSB;   //Scrollbar width and height.
    UINT        cySB;
    UINT        cx, cy;
    UINT        dx, dy;
    UINT        u;
    int         iMin, iMax;
    RECT        rc;
    BOOL        fHScroll;
    BOOL        fVScroll;
    BOOL        fWasThere;

    GetClientRect(m_hWnd, &rc);

    cx=rc.right-rc.left;
    cy=rc.bottom-rc.top;

    //Convert dimensions of the image in LOMETRIC to pixels.
    SetRect(&rc, (m_cx+m_xMarginLeft+m_xMarginRight
        +LOMETRIC_BORDER*2), (m_cy+m_yMarginTop
        +m_yMarginBottom+LOMETRIC_BORDER*2), 0, 0);

    RectConvertMappings(&rc, NULL, TRUE);

    dx=rc.left;
    dy=-rc.top;

    //Assume that both scrollbars will be visible.
    fHScroll=TRUE;
    fVScroll=TRUE;

    /*
     * Determine:
     *  1)  Which scrollbars are needed.
     *  2)  How many divisions to give scrollbars so as to
     *      only scroll as little as necessary.
     */

    //Scrollbar dimensions in our units.
    cxSB=GetSystemMetrics(SM_CXVSCROLL);
    cySB=GetSystemMetrics(SM_CYHSCROLL);

    //Remove horizontal scroll if window >= cxPage+borders
    if (cx >= dx)
        fHScroll=FALSE;

    /*
     * If we still need a horizontal scroll, see if we need a
     * vertical taking the height of the horizontal scroll into
     * account.
     */

    u=fHScroll ? cySB : 0;

    if ((cy-u) >= dy)
        fVScroll=FALSE;

    //Check if adding vert scrollbar necessitates a horz now.
    u=fVScroll ? cxSB : 0;
    fHScroll=((cx-u) < dx);

    /*
     * Modify cx,cy to reflect the new client area before scaling
     * scrollbars.  We only affect the client size if there is a
     * *change* in scrollbar status:  if the scrollbar was there
     * but is no longer, then add to the client size; if it was
     * not there but now is, then subtract.
     */

    //Change cx depending on vertical scrollbar change
    GetScrollRange(m_hWnd, SB_VERT, &iMin, &iMax);
    fWasThere=(0!=iMin || 0!=iMax);

    if (fWasThere && !fVScroll)
        cx+=cxSB;

    if (!fWasThere && fVScroll)
        cx-=cxSB;

    //Change cy depending on horizontal scrollbar change
    GetScrollRange(m_hWnd, SB_HORZ, &iMin, &iMax);
    fWasThere=(0!=iMin || 0!=iMax);

    if (fWasThere && !fHScroll)
        cy+=cySB;

    if (!fWasThere && fHScroll)
        cy-=cySB;

    /*
     * Show/Hide the scrollbars if necessary and set the ranges.
     * The range is the number of units of the page we cannot see.
     */
    if (fHScroll)
        {
        //Convert current scroll position to new range.
        u=GetScrollPos(m_hWnd, SB_HORZ);

        if (0!=u)
            {
            GetScrollRange(m_hWnd, SB_HORZ, &iMin, &iMax);
            u=MulDiv(u, (dx-cx), (iMax-iMin));
            }

        SetScrollRange(m_hWnd, SB_HORZ, 0, dx-cx, FALSE);
        SetScrollPos(m_hWnd, SB_HORZ, u, TRUE);
        m_xPos=u;
        }
    else
        {
        SetScrollRange(m_hWnd, SB_HORZ, 0, 0, TRUE);
        m_xPos=0;
        }

    if (fVScroll)
        {
        //Convert current scroll position to new range.
        u=GetScrollPos(m_hWnd, SB_VERT);

        if (0!=u)
            {
            GetScrollRange(m_hWnd, SB_VERT, &iMin, &iMax);
            u=MulDiv(u, (dy-cy), (iMax-iMin));
            }

        SetScrollRange(m_hWnd, SB_VERT, 0, dy-cy, FALSE);
        SetScrollPos(m_hWnd, SB_VERT, u, TRUE);

        m_yPos=u;
        }
    else
        {
        SetScrollRange(m_hWnd, SB_VERT, 0, 0, TRUE);
        m_yPos=0;
        }

    //Repaint to insure that changes to m_x/yPos are reflected
    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }

/*
 * CPages::Print
 *
 * Purpose:
 *  Prints a specified range of pages to a given hDC.  Repeats for
 *  a given number of copies.
 *
 * Parameters:
 *  hDC             HDC to which we print.
 *  pszDoc          LPTSTR providing the document name.
 *  dwFlags         DWORD flags from PrintDlg
 *  iPageStart      UINT starting page index (one based)
 *  iPageEnd        UINT ending page index (one based).  Includes
 *                  this page.
 *  cCopies         UINT number of copies to print.  If PD_COLLATE
 *                  in dwFlags is set, we print multiple copies of
 *                  each page as we cycle through.  Otherwise we
 *                  cycle multiple times.
 *
 * Return Value:
 *  None
 */

BOOL CPages::Print(HDC hDC, LPTSTR pszDoc, DWORD dwFlags
    , UINT iPageStart, UINT iPageEnd, UINT cCopies)
    {
    BOOL        fError=FALSE;
    int         iPage, iPageInc;
    int         iUserPage, cPages;
    UINT        iRepeat, cRepeat;
    UINT        iCycle, cCycles;
    UINT        iPageHold=m_iPageCur;
    HWND        hWndT, hWndTop=NULL;
    DOCINFO     di;
    PCDocument  pDoc;

    //Validate hDC and page ranges
    if (NULL==hDC)
        return FALSE;

    if ((PD_PAGENUMS & dwFlags))
        {
        if (-1==iPageStart)
            iPageStart=0;
        else
            iPageStart--;   //Switch to zero offset.

        if (-1==iPageEnd)
            iPageEnd=m_cPages-1;
        else
            iPageEnd--;     //Switch to zero offset.
        }
    else //Can't test PD_ALLPAGES with & since it's defined as 0L
        {
        iPageStart=0;
        iPageEnd=m_cPages-1;
        }

    //Arrange cycles and repeats depending on cCopies and collating
    if (PD_COLLATE & dwFlags)
        {
        cCycles=cCopies;
        cRepeat=1;
        }
    else
        {
        cCycles=1;
        cRepeat=cCopies;
        }

    //Disable the frame window to prevent reentrancy while printing.
    hWndT=GetParent(m_hWnd);
    pDoc=(PCDocument)SendMessage(hWndT, DOCM_PDOCUMENT, 0, 0L);

    if (NULL!=pDoc)
        {
        PCFrame pFR;

        pFR=pDoc->FrameGet();
        hWndTop=pFR->Window();
        EnableWindow(hWndTop, FALSE);
        }

    SetAbortProc(hDC, AbortProc);
    g_fCancelPrint=FALSE;

    //If these don't work then we'll just live without a dialog.
    g_hDlgPrint=CreateDialog(m_hInst, MAKEINTRESOURCE(IDD_PRINTING)
        , hWndTop, PrintDlgProc);

    //Increment for either direction.
    iPageInc=(iPageStart > iPageEnd) ? -1 : 1;

    //Initial entries in dialog box.
    cPages=1+((int)(iPageEnd-iPageStart)*iPageInc);

    SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE, 1, (LPARAM)cPages);
    SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, 1, (LPARAM)cRepeat);

    di.cbSize=sizeof(DOCINFO);
    di.lpszDocName=pszDoc;
    di.lpszOutput=NULL;

    if (StartDoc(hDC, &di) > 0)
        {
        /*
         * Iterate over the pages, repeating each page depending on
         * the copies we want and if we have collate enabled.
         */

        for (iCycle=1; iCycle <= cCycles; iCycle++)
            {
            if (PD_COLLATE & dwFlags)
                {
                SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, iCycle
                    , (LPARAM)cCycles);
                }

            //iPageInc controls direction
            for (iPage=iPageStart; ; iPage+=iPageInc)
                {
                iUserPage=1+((iPage-(int)iPageStart)*iPageInc);

                SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE
                    , iUserPage, (LPARAM)cPages);

                m_iPageCur=iPage;   //We restore this later.

                for (iRepeat=1; iRepeat <= cRepeat; iRepeat++)
                    {
                    if (!(PD_COLLATE & dwFlags))
                        {
                        SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE
                            , iRepeat, (LPARAM)cRepeat);
                        }

                    StartPage(hDC);
                    Draw(hDC, TRUE, TRUE);

                    if (EndPage(hDC) < 0)
                        fError=TRUE;

                    if (fError || g_fCancelPrint)
                        break;
                    }

                if (fError || g_fCancelPrint)
                    break;

                //If we just printed the last page, time to quit.
                if (iPage==(int)iPageEnd)
                    break;
                }

            if (fError || g_fCancelPrint)
                break;
            }

        if (!fError)
            EndDoc(hDC);
        else
            AbortDoc(hDC);
        }
    else
        fError=TRUE;

    //Set the page back to what it was before all this started.
    m_iPageCur=iPageHold;

    EnableWindow(hWndTop, TRUE);
    SetFocus(hWndTop);
    DestroyWindow(g_hDlgPrint);
    DeleteDC(hDC);
    return !fError;
    }

/*
 * AbortProc
 *
 * Purpose:
 *  Abort procedure for printing the pages.
 *
 * Parameters:
 *  hDC             HDC on which printing is happening.
 *  iErr            int error code.
 *
 * Return Value:
 *  BOOL            TRUE to continue the print job, FALSE otherwise.
 */

BOOL APIENTRY AbortProc(HDC hDC, int iErr)
    {
    MSG     msg;

    while (!g_fCancelPrint
        && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
        if (NULL==g_hDlgPrint
            || !IsDialogMessage(g_hDlgPrint, &msg))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    return !g_fCancelPrint;
    }

/*
 * PrintDlgProc
 *
 * Purpose:
 *  Modeless dialog procedure for the dialog displayed while Patron
 *  is printing pages.
 */

BOOL APIENTRY PrintDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    TCHAR           szFormat[40];
    TCHAR           szOutput[80];

    switch (iMsg)
        {
        case WM_INITDIALOG:
            EnableMenuItem(GetSystemMenu(hDlg, FALSE), SC_CLOSE
                , MF_GRAYED);
            return TRUE;

        case WM_COMMAND:
            //Cancel button was pressed.
            g_fCancelPrint=TRUE;
            ShowWindow(hDlg, SW_HIDE);
            return TRUE;

        case PRINTM_PAGEUPDATE:
            GetDlgItemText(hDlg, ID_PAGESTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTPAGE, szOutput);
            return TRUE;

        case PRINTM_COPYUPDATE:
            GetDlgItemText(hDlg, ID_COPYSTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTCOPY, szOutput);
            return TRUE;
        }

    return FALSE;
    }


PATRON.CPP

/*
 * PATRON.CPP
 * Patron Chapter 7
 *
 * WinMain which is all we need for the basic application.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "patron.h"

extern "C"
{
OLEDBGDATA_MAIN("PATRON")
}

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCPatronFrame   pFR;
    FRAMEINIT       fi;
    WPARAM          wRet=0;

    //CHAPTER7MOD
   #ifndef WIN32
    int             cMsg=96;

    while (!SetMessageQueue(cMsg) && (cMsg-=8));
   #endif
    //End CHAPTER7MOD

    //Attempt to allocate and initialize the application
    pFR=new CPatronFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return -1;

    fi.idsMin=IDS_FRAMEMIN;
    fi.idsMax=IDS_FRAMEMAX;
    fi.idsStatMin=IDS_STATMESSAGEMIN;
    fi.idsStatMax=IDS_STATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=CW_USEDEFAULT;
    fi.cy=CW_USEDEFAULT;

    //If we can initialize pFR, start chugging messages
    if (pFR->FInit(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

/*
 * CPatronFrame::CPatronFrame
 * CPatronFrame::~CPatronFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WInMain
 */

CPatronFrame::CPatronFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    : CFrame(hInst, hInstPrev, pszCmdLine, nCmdShow)
    {
    m_fOleStdInit = m_fInitialized=FALSE;
    //CHAPTER7MOD
    m_pIClassDataTran=NULL;
    //End CHAPTER7MOD
    return;
    }

CPatronFrame::~CPatronFrame(void)
    {
    //CHAPTER7MOD
    if (NULL!=m_pIClassDataTran)
        {
        m_pIClassDataTran->LockServer(FALSE);
        m_pIClassDataTran->Release();
        }

    OleFlushClipboard();
    //End CHAPTER7MOD

    if (m_fOleStdInit)
        OleStdUninitialize();

    if (m_fInitialized)
        OleUninitialize();
    return;
    }

/*
 * CPatronFrame::FInit
 *
 * Purpose:
 *  Call OleInitialize then calling down into the base class
 *  initialization.
 *
 * Parameters:
 *  pFI             PFRAMEINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CPatronFrame::FInit(PFRAMEINIT pFI)
    {
    DWORD       dwVer;
    //CHAPER7MOD
    HRESULT     hr;
    //End CHAPTER7MOD

    dwVer=OleBuildVersion();

    if (rmm!=HIWORD(dwVer))
        return FALSE;

    if (FAILED(OleInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    if (!OleStdInitialize(m_hInst))
        return FALSE;

    m_fOleStdInit=TRUE;

    /*
     * Obtain the Data Transfer Object class factory and lock it.
     * This will improve the speed of clipboard and (later)
     * drag & drop operations that involve this class.
     */
    hr=CoGetClassObject(CLSID_DataTransferObject
        , CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory
        , (PPVOID)&m_pIClassDataTran);

    if (SUCCEEDED(hr))
        m_pIClassDataTran->LockServer(TRUE);
    //End CHAPTER7MOD

    return CFrame::FInit(pFI);
    }

/*
 * CPatronFrame::CreateCClient
 *
 * Purpose:
 *  Constructs a new client specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient        Pointer to the new client object.
 */

PCClient CPatronFrame::CreateCClient(void)
    {
    return (PCClient)(new CPatronClient(m_hInst));
    }

/*
 * CPatronFrame::FRegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CPatronFrame::FRegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //First let the standard frame do its thing
    if (!CFrame::FRegisterAllClasses())
        return FALSE;

    //CHAPTER7MOD
    //We need double-clicks now and for object activation later.
    wc.style         = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
    //End CHAPTER7MOD

    wc.hInstance     = m_hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PagesWndProc;
    wc.cbWndExtra    = CBPAGESWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPAGES;

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

    return TRUE;
    }

/*
 * CPatronFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the Patron frame window that processes extra
 *  File menu items as well as the Page menu.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CPatronFrame::OnCommand(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PCPatronDoc     pDoc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many GizmoBar notifications.
     */
    if (m_fInit)
        return 0L;

    pDoc=(PCPatronDoc)m_pCL->ActiveDocument();

    switch (wID)
        {
        case IDM_FILEPRINT:
            pDoc->Print(m_hWnd);
            return 0L;

        case IDM_FILEPRINTERSETUP:
            pDoc->PrinterSetup(m_hWnd, FALSE);
            return 0L;

        //CHAPTER7MOD
        case IDM_EDITPASTESPECIAL:
            pDoc->FPasteSpecial(m_hWnd);
            return 0L;

        case IDM_EDITDELETEOBJECT:
            pDoc->Delete();
            return 0L;
        //End CHAPTER7MOD

        case IDM_PAGENEWPAGE:
            pDoc->NewPage();
            break;

        case IDM_PAGEDELETEPAGE:
            pDoc->DeletePage();
            break;

        case IDM_PAGENEXTPAGE:
            pDoc->NextPage();
            break;

        case IDM_PAGEPREVIOUSPAGE:
            pDoc->PreviousPage();
            break;

        case IDM_PAGEFIRSTPAGE:
            pDoc->FirstPage();
            break;

        case IDM_PAGELASTPAGE:
            pDoc->LastPage();
            break;

        default:
           return CFrame::OnCommand(hWnd, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPatronFrame::CreateGizmos
 *
 * Purpose:
 *  Procedure to create all the necessary gizmobar buttons.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of gizmos added to the bar.
 */

UINT CPatronFrame::CreateGizmos(void)
    {
    UINT            iLast;
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;

    //Insert the standard ones.
    iLast=CFrame::CreateGizmos();

    //Remove Undo:  we don't use it.
    m_pGB->Remove(IDM_EDITUNDO);

    /*
     * Insert Print File Import in the 5th position and account
     * for it in iLast.
     */
    m_pGB->Add(utCmd, 4, IDM_FILEPRINT, m_dxB, m_dyB
        , NULL, NULL, 6, uState);

    iLast++;

    m_pGB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //Add New Page, and Delete Page
    m_pGB->Add(utCmd, iLast++, IDM_PAGENEWPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 2, uState);
    m_pGB->Add(utCmd, iLast++, IDM_PAGEDELETEPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 3, uState);

    m_pGB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //First, Prev, Next, Last pages.
    m_pGB->Add(utCmd, iLast++, IDM_PAGEFIRSTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 4, uState);
    m_pGB->Add(utCmd, iLast++, IDM_PAGEPREVIOUSPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 5, uState);
    m_pGB->Add(utCmd, iLast++, IDM_PAGENEXTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 6, uState);
    m_pGB->Add(utCmd, iLast++, IDM_PAGELASTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 7, uState);

    return iLast;
    }

/*
 * CPatronFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CPatronFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    PCPatronDoc     pDoc;
    BOOL            fOK=FALSE;
    BOOL            fCallDefault=TRUE;
    UINT            uTemp;
    UINT            uTempE;
    UINT            uTempD;

    pDoc=(PCPatronDoc)m_pCL->ActiveDocument();

    uTempE=MF_ENABLED | MF_BYCOMMAND;
    uTempD=MF_DISABLED | MF_GRAYED | MF_BYCOMMAND;
    uTemp=((NULL!=pDoc) ? uTempE : uTempD);

    if (m_phMenu[0]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_FILEPRINT, uTemp);

        //CHAPTER7MOD
        if (NULL!=pDoc)
            fOK=pDoc->FQueryPrinterSetup();

        EnableMenuItem(hMenu, IDM_FILEPRINTERSETUP
            , (fOK) ? uTempE : uTempD);
        //End CHAPTER7MOD
        }

    //CHAPTER7MOD
    if (m_phMenu[1]==hMenu)
        {
        if (NULL!=pDoc)
            fOK=pDoc->FQueryPaste();

        EnableMenuItem(hMenu, IDM_EDITPASTE
            , (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITPASTESPECIAL
            , (fOK) ? uTempE : uTempD);

        //Cut, Copy, Delete depends on there being a selection.
        if (NULL!=pDoc)
            fOK=pDoc->FQueryObjectSelected(hMenu);
        else
            fOK=FALSE;

        EnableMenuItem(hMenu, IDM_EDITCUT, (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITCOPY
            , (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITDELETEOBJECT
            , (fOK) ? uTempE : uTempD);

        //We did the whole menu...
        fCallDefault=FALSE;
        }
    //End CHAPTER7MOD

    //Page menu
    if (m_phMenu[2]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_PAGENEWPAGE,      uTemp);
        EnableMenuItem(hMenu, IDM_PAGEDELETEPAGE,   uTemp);
        EnableMenuItem(hMenu, IDM_PAGENEXTPAGE,     uTemp);
        EnableMenuItem(hMenu, IDM_PAGEPREVIOUSPAGE, uTemp);
        EnableMenuItem(hMenu, IDM_PAGEFIRSTPAGE,    uTemp);
        EnableMenuItem(hMenu, IDM_PAGELASTPAGE,     uTemp);
        }

    if (fCallDefault)
        CFrame::UpdateMenus(hMenu, iMenu);

    return;
    }

/*
 * CPatronFrame::UpdateGizmos
 *
 * Purpose:
 *  Enables and disables gizmos depending on whether we have
 *  a document or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronFrame::UpdateGizmos(void)
    {
    PCDocument  pDoc;
    BOOL        fEnable;

    //Let the default hack on its gizmos.
    CFrame::UpdateGizmos();

    pDoc=m_pCL->ActiveDocument();
    fEnable=(NULL!=pDoc);

    //No document, disable just about everything
    m_pGB->Enable(IDM_FILEPRINT,        fEnable);
    m_pGB->Enable(IDM_FILEPRINTERSETUP, fEnable);

    m_pGB->Enable(IDM_PAGENEWPAGE,      fEnable);
    m_pGB->Enable(IDM_PAGEDELETEPAGE,   fEnable);
    m_pGB->Enable(IDM_PAGEFIRSTPAGE,    fEnable);
    m_pGB->Enable(IDM_PAGEPREVIOUSPAGE, fEnable);
    m_pGB->Enable(IDM_PAGENEXTPAGE,     fEnable);
    m_pGB->Enable(IDM_PAGELASTPAGE,     fEnable);

    return;
    }


PATRON.H

/*
 * PATRON.H
 * Patron Chapter 7
 *
 * Single include file that pulls in everything needed for other
 * source files in the application.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _PATRON_H_
#define _PATRON_H_

#define INC_OLE2
#include <windows.h>
#include <ole2.h>
#include <ole2ver.h>
//CHAPTER7MOD
#include <olestd.h>
//End CHAPTER7MOD
#include <bookguid.h>

extern "C"
    {
    #include <commdlg.h>
   #ifndef WIN32
    #include <print.h>
   #endif
    }

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

//Get editor window information
#include "pages.h"

/*
 * UINT value such that adding one produces zero.  Portable to Win32.
 * This is used to represent a non-existent zero-based UINT value
 */
#define NOVALUE                     ((UINT)-1)

//PATRON.CPP:  Frame object that creates a main window

class CPatronFrame : public CFrame
    {
    private:
        BOOL            m_fInitialized;     //OleInitialize worked
        BOOL            m_fOleStdInit;

        //CHAPTER7MOD
        //For locking DATATRAN.DLL class factory
        LPCLASSFACTORY  m_pIClassDataTran;
        //End CHAPTER7MOD

    protected:
        //Overridable for creating a CPatronClient
        virtual PCClient    CreateCClient(void);

        virtual BOOL        FRegisterAllClasses(void);
        virtual UINT        CreateGizmos(void);
        virtual LRESULT     OnCommand(HWND, WPARAM, LPARAM);

    public:
        CPatronFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CPatronFrame(void);

        //Overrides
        virtual BOOL        FInit(PFRAMEINIT);

        virtual void        UpdateMenus(HMENU, UINT);
        virtual void        UpdateGizmos(void);

    };

typedef CPatronFrame *PCPatronFrame;

//CLIENT.CPP

/*
 * The only reason we have a derived class here is to override
 * CreateCDocument so we can create our own type as well as
 * overriding NewDocument to perform one other piece of work once
 * the document's been created.
 */

class CPatronClient : public CClient
    {
    protected:
        //Overridable for creating a new CDocument
        virtual PCDocument CreateCDocument(void);

    public:
        CPatronClient(HINSTANCE);
        virtual ~CPatronClient(void);
    };

typedef CPatronClient *PCPatronClient;

//DOCUMENT.CPP

//Constant ID for the pages window that lives in a document window
#define ID_PAGES            723

class CPatronDoc : public CDocument
    {
    protected:
        LONG            m_lVer;         //Loaded data version
        PCPages         m_pPG;          //Pages window in us

        LPSTORAGE       m_pIStorage;    //Root storage for document

        //CHAPTER7MOD
        BOOL            m_fPrintSetup;
        //End CHAPTER7MOD

    protected:
        virtual BOOL    FMessageHook(HWND, UINT, WPARAM, LPARAM
            , LRESULT *);

        //CHAPTER7MOD
        BOOL            FQueryPasteFromData(LPDATAOBJECT
                            , LPFORMATETC, PTENANTTYPE);
        BOOL            FPasteFromData(LPDATAOBJECT, LPFORMATETC
                            , TENANTTYPE, PPATRONOBJECT, DWORD);
        //End CHAPTER7MOD

    public:
        CPatronDoc(HINSTANCE, PCFrame);
        virtual ~CPatronDoc(void);

        virtual BOOL    FInit(PDOCUMENTINIT);
        virtual void    Clear(void);

        //CHAPTER7MOD
        virtual BOOL    FDirtyGet(void);
        virtual void    Delete(void);
        virtual BOOL    FQueryPrinterSetup(void);
        virtual BOOL    FQueryObjectSelected(HMENU);
        //End CHAPTER7MOD

        virtual UINT    ULoad(BOOL, LPTSTR);
        virtual UINT    USave(UINT, LPTSTR);

        virtual BOOL    Print(HWND);
        virtual UINT    PrinterSetup(HWND, BOOL);

        //CHAPTER7MOD
        virtual BOOL    FClip(HWND, BOOL);
        virtual BOOL    FQueryPaste(void);
        virtual BOOL    FPaste(HWND);
        virtual BOOL    FPasteSpecial(HWND);
        //End CHAPTER7MOD

        virtual UINT    NewPage(void);
        virtual UINT    DeletePage(void);
        virtual UINT    NextPage(void);
        virtual UINT    PreviousPage(void);
        virtual UINT    FirstPage(void);
        virtual UINT    LastPage(void);
    };

typedef CPatronDoc *PCPatronDoc;

//Hook for Print Dialog to hide Setup... button
UINT CALLBACK PrintDlgHook(HWND, UINT, WPARAM, LPARAM);

#endif //_PATRON_H_


PRINT.CPP

/*
 * PRINT.CPP
 * Patron Chapter 7
 *
 * Implementation of printing functions for both CPatronDoc
 * and CPages classes.  These functions are here to keep clutter
 * down in document.cpp and pages.cpp.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

static HWND g_hDlgPrint=NULL;
static BOOL g_fCancelPrint=FALSE;

/*
 * CPatronDoc::Print
 *
 * Purpose:
 *  Prints the current document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *
 * Return Value:
 *  BOOL            TRUE if printing happened, FALSE if it didn't
 *                  start or didn't complete.
 */

BOOL CPatronDoc::Print(HWND hWndFrame)
    {
    PRINTDLG        pd;
    BOOL            fSuccess;

    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);
    pd.hwndOwner  =hWndFrame;
    pd.nCopies    =1;
    pd.nFromPage  =(USHORT)-1;
    pd.nToPage    =(USHORT)-1;
    pd.nMinPage   =1;
    pd.nMaxPage   =m_pPG->NumPagesGet();

    pd.lpfnPrintHook=PrintDlgHook;

    //Get the current document printer settings
    pd.hDevMode=m_pPG->DevModeGet();

    pd.Flags=PD_RETURNDC | PD_ALLPAGES | PD_COLLATE
        | PD_HIDEPRINTTOFILE | PD_NOSELECTION | PD_ENABLEPRINTHOOK;

    if (!PrintDlg(&pd))
        return FALSE;

    if (NULL!=pd.hDevMode)
        GlobalFree(pd.hDevMode);

    if (NULL!=pd.hDevNames)
        GlobalFree(pd.hDevNames);

    //Go do the actual printing.
    fSuccess=m_pPG->Print(pd.hDC, PSZ(IDS_DOCUMENTNAME), pd.Flags
        , pd.nFromPage, pd.nToPage, pd.nCopies);

    if (!fSuccess)
        {
        MessageBox(m_hWnd, PSZ(IDS_PRINTERROR)
            , PSZ(IDS_DOCUMENTCAPTION), MB_OK);
        }

    return fSuccess;
    }

/*
 * CPatronDoc::PrinterSetup
 *
 * Purpose:
 *  Selects a new printer and options for this document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *  fDefault        BOOL to avoid any dialog and just use the
 *                  default.
 *
 * Return Value:
 *  UINT            Undefined
 */

UINT CPatronDoc::PrinterSetup(HWND hWndFrame, BOOL fDefault)
    {
    PRINTDLG        pd;

    //Attempt to get printer metrics for the default printer.
    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);

    if (fDefault)
        pd.Flags=PD_RETURNDEFAULT;
    else
        {
        pd.hwndOwner=hWndFrame;
        pd.Flags=PD_PRINTSETUP;

        //Get the current document printer settings
        pd.hDevMode=m_pPG->DevModeGet();
        }

    if (!PrintDlg(&pd))
        return FALSE;

    if (!m_pPG->DevModeSet(pd.hDevMode, pd.hDevNames))
        {
        GlobalFree(pd.hDevNames);
        GlobalFree(pd.hDevMode);
        return FALSE;
        }

    FDirtySet(TRUE);
    return 1;
    }

/*
 * PrintDlgHook
 *
 * Purpose:
 *  Callback hook for the Print Dialog so we can hide the Setup
 *  button.  Patron only allows Setup before anything exists on
 *  the page, and is not written to handle setup at Print time.
 */

UINT CALLBACK PrintDlgHook(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    if (WM_INITDIALOG==iMsg)
        {
        HWND        hWnd;

        hWnd=GetDlgItem(hDlg, psh1);
        ShowWindow(hWnd, SW_HIDE);
        return TRUE;
        }

    return FALSE;
    }

/*
 * CPages::DevModeSet
 *
 * Purpose:
 *  Provides the Pages with the current printer information.
 *
 * Parameters:
 *  hDevMode        HGLOBAL to the memory containing the DEVMODE.
 *                  This function assumes responsibility for this
 *                  handle.
 *  hDevNames       HGLOBAL providing the driver name and device
 *                  name from which we can create a DC for
 *                  information.
 *
 * Return Value:
 *  BOOL            TRUE if we could accept this configuration,
 *                  FALSE otherwise.  If we return TRUE we also
 *                  delete the old memory we hold.
 */

BOOL CPages::DevModeSet(HGLOBAL hDevMode, HGLOBAL hDevNames)
    {
    LPDEVNAMES      pdn;
    LPTSTR          psz;
    //CHAPTER7MOD
    HGLOBAL         hMem;
    PDEVICECONFIG   pdc;
    LPDEVMODE       pdm;
    LPSTREAM        pIStream;
    HRESULT         hr;
    ULONG           cbDevMode, cbWrite;
    BOOL            fRet=FALSE;

    if (NULL==hDevMode || NULL==hDevNames)
        return FALSE;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    /*
     * DEVMODE is variable length--total length in hDevMode, so the
     * amount to write is that plus string space.  We subtract
     * sizeof(DEVMODE) as that is already included from GlobalSize.
     */
    cbDevMode=GlobalSize(hDevMode);
    cbWrite=cbDevMode+sizeof(DEVICECONFIG)-sizeof(DEVMODE);

    hMem=GlobalAlloc(GHND, cbWrite);

    if (NULL==hMem)
        {
        pIStream->Release();
        return FALSE;
        }

    pdc=(PDEVICECONFIG)GlobalLock(hMem);    //This always works
    pdm=(LPDEVMODE)GlobalLock(hDevMode);    //This might not

    if (NULL!=pdm)
        {
        pdc->cb=cbWrite;
        pdc->cbDevMode=cbDevMode;
        memcpy(&pdc->dm, pdm, (int)cbDevMode);
        GlobalUnlock(hDevMode);

        psz=(LPTSTR)GlobalLock(hDevNames);

        if (NULL!=psz)
            {
            pdn=(LPDEVNAMES)psz;
            lstrcpy(pdc->szDriver, psz+pdn->wDriverOffset);
            lstrcpy(pdc->szDevice, psz+pdn->wDeviceOffset);
            lstrcpy(pdc->szPort,   psz+pdn->wOutputOffset);

            pIStream->Write(pdc, cbWrite, &cbWrite);
            GlobalUnlock(hDevNames);
            fRet=TRUE;
            }
        }

    GlobalUnlock(hMem);
    GlobalFree(hMem);

    pIStream->Release();

    if (!fRet)
        return FALSE;

    GlobalFree(hDevNames);
    GlobalFree(hDevMode);

    //End CHAPTER7MOD
    return ConfigureForDevice();
    }

/*
 * CPages::DevModeGet
 *
 * Purpose:
 *  Retrieves a copy of the current DEVMODE structure for this
 *  Pages window.  The caller is responsible for this memory.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HGLOBAL         Handle to the memory containing the DEVMODE
 *                  structure.
 */

HGLOBAL CPages::DevModeGet(void)
    {
    //CHAPTER7MOD
    HGLOBAL         hMem;
    LPVOID          pv;
    ULONG           cbDevMode, cbRead;
    LARGE_INTEGER   li;
    LPSTREAM        pIStream;
    HRESULT         hr;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    //Read how much to allocate for the DEVMODE structure
    LISet32(li, CBSEEKOFFSETCBDEVMODE);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);
    pIStream->Read(&cbDevMode, sizeof(ULONG), &cbRead);

    hMem=GlobalAlloc(GHND, cbDevMode);

    if (NULL!=hMem)
        {
        pv=(LPVOID)GlobalLock(hMem);
        pIStream->Read(pv, cbDevMode, &cbRead);
        GlobalUnlock(hMem);
        }

    pIStream->Release();
    //End CHAPTER7MOD
    return hMem;
    }

/*
 * CPages::ConfigureForDevice
 *
 * Purpose:
 *  Recalculates our drawing configuration based on the contents of
 *  an hDC.  If no HDC is given we use the contents of our DevMode
 *  stream.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::ConfigureForDevice(void)
    {
    POINT           ptOffset, ptPaper;
    RECT            rc;
    HDC             hDC;
    CHourglass      hg;
    //CHAPTER7MOD
    HGLOBAL         hMem;
    PDEVICECONFIG   pdc;
    HRESULT         hr;
    LARGE_INTEGER   li;
    LPSTREAM        pIStream;
    ULONG           cb, cbRead;

    //Read the DEVMODE and driver names from the header stream.
    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    //Read size of structure including variable portion of DEVMODE
    pIStream->Read(&cb, sizeof(DWORD), &cbRead);

    hMem=GlobalAlloc(GHND, cb);

    if (NULL==hMem)
        {
        pIStream->Release();
        return FALSE;
        }

    //Now get the real information.
    pdc=(PDEVICECONFIG)GlobalLock(hMem);
    LISet32(li, 0);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);
    pIStream->Read(pdc, cb, &cbRead);
    pIStream->Release();

    //Get the DC then configure
    hDC=CreateIC(pdc->szDriver, pdc->szDevice, pdc->szPort, &pdc->dm);

    //Got what we want, clean up the memory.
    GlobalUnlock(hMem);
    GlobalFree(hMem);
    //End CHAPTER7MOD

    if (NULL==hDC)
        return FALSE;

    //Get usable page dimensions:  already sensitive to orientation
    m_cx=GetDeviceCaps(hDC, HORZSIZE)*10-16; //*10: mm to LOMETRIC
    m_cy=GetDeviceCaps(hDC, VERTSIZE)*10-16; //-16: for driver bugs.

    //Calculate the printer-limited margins on sides in LOMETRIC.
    Escape(hDC, GETPRINTINGOFFSET, NULL, NULL, &ptOffset);
    Escape(hDC, GETPHYSPAGESIZE,   NULL, NULL, &ptPaper);

    SetRect(&rc, ptOffset.x, ptOffset.y, ptPaper.x, ptPaper.y);
    SetMapMode(hDC, MM_LOMETRIC);
    RectConvertMappings(&rc, hDC, FALSE);

    //Left and top margins are the printing offset.
    m_xMarginLeft= rc.left+8;   //+8 to match -16 above
    m_yMarginTop =-rc.top+8;    //LOMETRIC makes this negative.

    //Right is (paper width)-(usable width)-(left margin)
    m_xMarginRight =rc.right-m_cx-m_xMarginLeft;

    //Bottom is (paper height)-(usable height)-(top margin)+1
    m_yMarginBottom=-rc.bottom-m_cy-m_yMarginTop+1;

    UpdateScrollRanges();

    DeleteDC(hDC);
    return TRUE;
    }

/*
 * CPages::Print
 *
 * Purpose:
 *  Prints a specified range of pages to a given hDC.  Repeats for
 *  a given number of copies.
 *
 * Parameters:
 *  hDC             HDC to which we print.
 *  pszDoc          LPTSTR providing the document name.
 *  dwFlags         DWORD flags from PrintDlg
 *  iPageStart      UINT starting page index (one based)
 *  iPageEnd        UINT ending page index (one based).  Includes
 *                  this page.
 *  cCopies         UINT number of copies to print.  If PD_COLLATE
 *                  in dwFlags is set, we print multiple copies of
 *                  each page as we cycle through.  Otherwise we
 *                  cycle multiple times.
 *
 * Return Value:
 *  None
 */

BOOL CPages::Print(HDC hDC, LPTSTR pszDoc, DWORD dwFlags
    , UINT iPageStart, UINT iPageEnd, UINT cCopies)
    {
    BOOL        fError=FALSE;
    int         iPage, iPageInc;
    int         iUserPage, cPages;
    UINT        iRepeat, cRepeat;
    UINT        iCycle, cCycles;
    UINT        iPageHold=m_iPageCur;
    HWND        hWndT, hWndTop=NULL;
    DOCINFO     di;
    PCDocument  pDoc;

    //Validate hDC and page ranges
    if (NULL==hDC)
        return FALSE;

    if ((PD_PAGENUMS & dwFlags))
        {
        if (-1==iPageStart)
            iPageStart=0;
        else
            iPageStart--;   //Switch to zero offset.

        if (-1==iPageEnd)
            iPageEnd=m_cPages-1;
        else
            iPageEnd--;     //Switch to zero offset.
        }
    else //Can't test PD_ALLPAGES with & since it's defined as 0L
        {
        iPageStart=0;
        iPageEnd=m_cPages-1;
        }

    //Arrange cycles and repeats depending on cCopies and collating
    if (PD_COLLATE & dwFlags)
        {
        cCycles=cCopies;
        cRepeat=1;
        }
    else
        {
        cCycles=1;
        cRepeat=cCopies;
        }

    //Disable the frame window to prevent reentrancy while printing.
    hWndT=GetParent(m_hWnd);
    pDoc=(PCDocument)SendMessage(hWndT, DOCM_PDOCUMENT, 0, 0L);

    if (NULL!=pDoc)
        {
        PCFrame pFR;

        pFR=pDoc->FrameGet();
        hWndTop=pFR->Window();
        EnableWindow(hWndTop, FALSE);
        }

    SetAbortProc(hDC, AbortProc);
    g_fCancelPrint=FALSE;

    //If these don't work then we'll just live without a dialog.
    g_hDlgPrint=CreateDialog(m_hInst, MAKEINTRESOURCE(IDD_PRINTING)
        , hWndTop, PrintDlgProc);

    //Increment for either direction.
    iPageInc=(iPageStart > iPageEnd) ? -1 : 1;

    //Initial entries in dialog box.
    cPages=1+((int)(iPageEnd-iPageStart)*iPageInc);

    SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE, 1, (LPARAM)cPages);
    SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, 1, (LPARAM)cRepeat);

    di.cbSize=sizeof(DOCINFO);
    di.lpszDocName=pszDoc;
    di.lpszOutput=NULL;

    if (StartDoc(hDC, &di) > 0)
        {
        /*
         * Iterate over the pages, repeating each page depending on
         * the copies we want and if we have collate enabled.
         */

        for (iCycle=1; iCycle <= cCycles; iCycle++)
            {
            if (PD_COLLATE & dwFlags)
                {
                SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, iCycle
                    , (LPARAM)cCycles);
                }

            //iPageInc controls direction
            for (iPage=iPageStart; ; iPage+=iPageInc)
                {
                iUserPage=1+((iPage-(int)iPageStart)*iPageInc);

                SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE
                    , iUserPage, (LPARAM)cPages);

                m_iPageCur=iPage;   //We restore this later.

                for (iRepeat=1; iRepeat <= cRepeat; iRepeat++)
                    {
                    if (!(PD_COLLATE & dwFlags))
                        {
                        SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE
                            , iRepeat, (LPARAM)cRepeat);
                        }

                    StartPage(hDC);
                    Draw(hDC, TRUE, TRUE);

                    if (EndPage(hDC) < 0)
                        fError=TRUE;

                    if (fError || g_fCancelPrint)
                        break;
                    }

                if (fError || g_fCancelPrint)
                    break;

                //If we just printed the last page, time to quit.
                if (iPage==(int)iPageEnd)
                    break;
                }

            if (fError || g_fCancelPrint)
                break;
            }

        if (!fError)
            EndDoc(hDC);
        else
            AbortDoc(hDC);
        }
    else
        fError=TRUE;

    //Set the page back to what it was before all this started.
    m_iPageCur=iPageHold;

    EnableWindow(hWndTop, TRUE);
    SetFocus(hWndTop);
    DestroyWindow(g_hDlgPrint);
    DeleteDC(hDC);
    return !fError;
    }

/*
 * AbortProc
 *
 * Purpose:
 *  Abort procedure for printing the pages.
 *
 * Parameters:
 *  hDC             HDC on which printing is happening.
 *  iErr            int error code.
 *
 * Return Value:
 *  BOOL            TRUE to continue the print job, FALSE otherwise.
 */

BOOL APIENTRY AbortProc(HDC hDC, int iErr)
    {
    MSG     msg;

    while (!g_fCancelPrint
        && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
        if (NULL==g_hDlgPrint
            || !IsDialogMessage(g_hDlgPrint, &msg))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    return !g_fCancelPrint;
    }

/*
 * PrintDlgProc
 *
 * Purpose:
 *  Modeless dialog procedure for the dialog displayed while Patron
 *  is printing pages.
 */

BOOL APIENTRY PrintDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    TCHAR           szFormat[40];
    TCHAR           szOutput[80];

    switch (iMsg)
        {
        case WM_INITDIALOG:
            EnableMenuItem(GetSystemMenu(hDlg, FALSE), SC_CLOSE
                , MF_GRAYED);
            return TRUE;

        case WM_COMMAND:
            //Cancel button was pressed.
            g_fCancelPrint=TRUE;
            ShowWindow(hDlg, SW_HIDE);
            return TRUE;

        case PRINTM_PAGEUPDATE:
            GetDlgItemText(hDlg, ID_PAGESTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTPAGE, szOutput);
            return TRUE;

        case PRINTM_COPYUPDATE:
            GetDlgItemText(hDlg, ID_COPYSTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTCOPY, szOutput);
            return TRUE;
        }

    return FALSE;
    }


RESOURCE.H

/*
 * RESOURCE.H
 * Patron Chapter 7
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Note that we augment classlib.h which we include before this.

//Position of Window menu.
#undef  WINDOW_MENU
#define WINDOW_MENU                 3

//Total number of menus
#undef  CMENUS
#ifdef MDI
#define CMENUS                      5
#else
#define CMENUS                      4
#endif

//Menu command identifiers.

#define IDM_FILEPRINT               (IDM_CUSTOMFILEMIN)
#define IDM_FILEPRINTERSETUP        (IDM_CUSTOMFILEMIN+1)

//CHAPTER7MOD
#define IDM_EDITPASTESPECIAL        (IDM_CUSTOMEDITMIN)
#define IDM_EDITDELETEOBJECT        (IDM_CUSTOMEDITMIN+1)
//End CHAPTER7MOD

#define IDM_PAGENEWPAGE             (IDM_CUSTOMMIN)
#define IDM_PAGEDELETEPAGE          (IDM_CUSTOMMIN+1)
#define IDM_PAGENEXTPAGE            (IDM_CUSTOMMIN+2)
#define IDM_PAGEPREVIOUSPAGE        (IDM_CUSTOMMIN+3)
#define IDM_PAGEFIRSTPAGE           (IDM_CUSTOMMIN+4)
#define IDM_PAGELASTPAGE            (IDM_CUSTOMMIN+5)

/*
 * IDs for StatStrip handling of popup menus:  must be in order of menu
 * CLASSRES.H already defines ID_MENUFILE and ID_MENUEDIT for us.
 */

#undef ID_MENUWINDOW
#undef ID_MENUHELP

#define ID_MENUPAGE                 (ID_MENUCUSTOMMIN+0)
#define ID_MENUWINDOW               (ID_MENUCUSTOMMIN+1)
#define ID_MENUHELP                 (ID_MENUCUSTOMMIN+2)

//Stringtable IDs.  Keep sequential for each group.
#define IDS_FRAMEMIN                IDS_STANDARDFRAMEMIN
#define IDS_FRAMEMAX                IDS_STANDARDFRAMEMAX

#define IDS_DOCUMENTMIN             IDS_STANDARDDOCMIN
#define IDS_PRINTERROR              (IDS_CUSTOMDOCMIN+0)
#define IDS_DOCUMENTNAME            (IDS_CUSTOMDOCMIN+1)

//CHAPTER7MOD
#define IDS_PASTEASPATRON           (IDS_CUSTOMDOCMIN+2)
#define IDS_PASTEMETAFILE           (IDS_CUSTOMDOCMIN+3)
#define IDS_PASTEASMETAFILE         (IDS_CUSTOMDOCMIN+4)
#define IDS_PASTEDIB                (IDS_CUSTOMDOCMIN+5)
#define IDS_PASTEASDIB              (IDS_CUSTOMDOCMIN+6)
#define IDS_PASTEBITMAP             (IDS_CUSTOMDOCMIN+7)
#define IDS_PASTEASBITMAP           (IDS_CUSTOMDOCMIN+8)
#define IDS_DOCUMENTMAX             (IDS_CUSTOMDOCMIN+8)
//End CHAPTER7MOD

//These are specifically for the StatStrip
#define IDS_STATMESSAGEMIN                  IDS_STANDARDSTATMESSAGEMIN

#define IDS_ITEMMESSAGEFILEPRINT            (IDS_CUSTOMSTATMESSAGEMIN+0)
#define IDS_ITEMMESSAGEFILEPRINTERSETUP     (IDS_CUSTOMSTATMESSAGEMIN+1)

#define IDS_MENUMESSAGEPAGE                 (IDS_CUSTOMSTATMESSAGEMIN+2)
#define IDS_ITEMMESSAGEPAGENEWPAGE          (IDS_CUSTOMSTATMESSAGEMIN+3)
#define IDS_ITEMMESSAGEPAGEDELETEPAGE       (IDS_CUSTOMSTATMESSAGEMIN+4)
#define IDS_ITEMMESSAGEPAGENEXTPAGE         (IDS_CUSTOMSTATMESSAGEMIN+5)
#define IDS_ITEMMESSAGEPAGEPREVIOUSPAGE     (IDS_CUSTOMSTATMESSAGEMIN+6)
#define IDS_ITEMMESSAGEPAGEFIRSTPAGE        (IDS_CUSTOMSTATMESSAGEMIN+7)
#define IDS_ITEMMESSAGEPAGELASTPAGE         (IDS_CUSTOMSTATMESSAGEMIN+8)

//CHAPTER7MOD
#define IDS_ITEMMESSAGEEDITPASTESPECIAL     (IDS_CUSTOMSTATMESSAGEMIN+9)
#define IDS_ITEMMESSAGEEDITDELETEOBJECT     (IDS_CUSTOMSTATMESSAGEMIN+10)
#define IDS_STATMESSAGEMAX                  (IDS_CUSTOMSTATMESSAGEMIN+10)
//End CHAPTER7MOD

//Definitions for the printing dialog.

#define IDD_PRINTING                2

#define ID_CURRENTPAGE              101
#define ID_CURRENTCOPY              102
#define ID_COPYSTRING               103
#define ID_PAGESTRING               104

#define PRINTM_PAGEUPDATE           (WM_USER+100)
#define PRINTM_COPYUPDATE           (WM_USER+101)

#endif //_RESOURCE_H_


CLIENT.CPP

/*
 * CLIENT.CPP
 * Component Cosmo Chapter 8
 *
 * Implementation of the CCosmoClient class that just makes sure
 * we get a CCosmoDoc on doc creation and that we initialize fully.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cocosmo.h"

/*
 * CCosmoClient::CCosmoClient
 * CCosmoClient::~CCosmoClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 */

CCosmoClient::CCosmoClient(HINSTANCE hInst)
    : CClient(hInst)
    {
    return;
    }

CCosmoClient::~CCosmoClient(void)
    {
    return;
    }

/*
 * CCosmoClient::CreateCDocument
 *
 * Purpose:
 *  Constructs a new document specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::CreateCDocument(void)
    {
    return (PCDocument)(new CCosmoDoc(m_hInst, m_pFR));
    }

/*
 * CCosmoClient::NewDocument
 *
 * Purpose:
 *  Small override of the CClient::NewDocument that we have just
 *  to check the initial line selection on new document creation.
 *
 * Parameters:
 *  fVisible        BOOL indicating if the document is to be
 *                  visible or not.
 *  pAdv            PCDocumentAdviseSink to set with the new
 *                  document for notifications.  Can be NULL.
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::NewDocument(BOOL fVisible
    , PCDocumentAdviseSink pAdv)
    {
    PCDocument  pDoc;

    //Perform default NewDocument first
    pDoc=CClient::NewDocument(fVisible, pAdv);

    //We know that m_pFR is actually a CCosmoFrame, so type is safe.
    ((PCCosmoFrame)m_pFR)->CheckLineSelection(IDM_LINESOLID);

    return pDoc;
    }


COCOSMO.CPP

/*
 * COCOSMO.CPP
 * Component Cosmo Chapter 8
 *
 * WinMain and CCosmoFrame implementations.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

//Must do this once or we can't define our own GUIDs (see compobj.h)
#define INITGUIDS

#include "cocosmo.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCCosmoFrame    pFR;
    FRAMEINIT       fi;
    WPARAM          wRet;

    //Since we only use one DLL object, don't need SetMessageQueue

    //Attempt to allocate and initialize the application
    pFR=new CCosmoFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return -1;

    fi.idsMin=IDS_FRAMEMIN;
    fi.idsMax=IDS_FRAMEMAX;
    fi.idsStatMin=IDS_STATMESSAGEMIN;
    fi.idsStatMax=IDS_STATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=440;
    fi.cy=460;

    //If we can initialize pFR, start chugging messages
    if (pFR->FInit(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

/*
 * CCosmoFrame::CCosmoFrame
 * CCosmoFrame::~CCosmoFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WInMain
 */

CCosmoFrame::CCosmoFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    : CFrame(hInst, hInstPrev, pszCmdLine, nCmdShow)
    {
    UINT        i;

    for (i=0; i<5; i++)
        m_hBmpLines[i]=NULL;

    m_uIDCurLine=0;
    m_fInitialized=FALSE;
    return;
    }

CCosmoFrame::~CCosmoFrame(void)
    {
    UINT        i;

    for (i=0; i<5; i++)
        {
        if (NULL!=m_hBmpLines[i])
            DeleteObject(m_hBmpLines[i]);
        }

    if (m_fInitialized)
        OleUninitialize();

    return;
    }

/*
 * CCosmoFrame::FInit
 *
 * Purpose:
 *  Call OleInitialize then calling down into the base class
 *  initialization.
 *
 * Parameters:
 *  pFI             PFRAMEINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CCosmoFrame::FInit(PFRAMEINIT pFI)
    {
    DWORD       dwVer;

    //Make sure OLE, not just COM, is the right version
    dwVer=OleBuildVersion();

    if (rmm!=HIWORD(dwVer))
        return FALSE;

    if (FAILED(OleInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    return CFrame::FInit(pFI);
    }

/*
 * CCosmoFrame::CreateCClient
 *
 * Purpose:
 *  Constructs a new client specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient        Pointer to the new client object.
 */

PCClient CCosmoFrame::CreateCClient(void)
    {
    return (PCClient)(new CCosmoClient(m_hInst));
    }

/*
 * CCosmoFrame::FRegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CCosmoFrame::FRegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //First let the standard frame do its thing
    if (!CFrame::FRegisterAllClasses())
        return FALSE;

    /*
     * We want a different background color for the document
     * because the Polyline we put in the document will paint
     * with COLOR_WINDOW which by default which is CLASSLIB's
     * default document color.
     */

    GetClassInfo(m_hInst, SZCLASSDOCUMENT, &wc);
    UnregisterClass(SZCLASSDOCUMENT, m_hInst);

    wc.hbrBackground=(HBRUSH)(COLOR_APPWORKSPACE+1);

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

    //No need to register the Polyline window now...

    return TRUE;
    }

/*
 * CCosmoFrame::FPreShowInit
 *
 * Purpose:
 *  Called from FInit before intially showing the window.  We do
 *  whatever else we want here, modifying nCmdShow as necessary
 *  which affects ShowWindow in FInit.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if this initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CCosmoFrame::FPreShowInit(void)
    {
    CreateLineMenu();
    CheckLineSelection(IDM_LINESOLID);
    return TRUE;
    }

/*
 * CCosmoFrame::CreateLineMenu
 *
 * Purpose:
 *  Initializes the bitmaps used to create the Line menu and
 *  replaces the text items defined in the application resources
 *  with these bitmaps.  Note that the contents of m_hBmpLines
 *  must be cleaned up when the application terminates.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CreateLineMenu(void)
    {
    HMENU       hMenu;
    HDC         hDC, hMemDC;
    HPEN        hPen;
    HGDIOBJ     hObj;
    TEXTMETRIC  tm;
    UINT        i, cx, cy;

    hMenu=GetSubMenu(GetMenu(m_hWnd), 3);   //Line menu.
    hDC=GetDC(m_hWnd);

    //Create each line in a menu item 8 chars wide, one char high.
    GetTextMetrics(hDC, &tm);
    cx=tm.tmAveCharWidth*8;
    cy=tm.tmHeight;

    /*
     * Create a memory DC in which to draw lines, and bitmaps
     * for each line.
     */
    hMemDC=CreateCompatibleDC(hDC);
    ReleaseDC(m_hWnd, hDC);

    for (i=0; i<5; i++)
        {
        m_hBmpLines[i]=CreateCompatibleBitmap(hMemDC, cx, cy);
        SelectObject(hMemDC, m_hBmpLines[i]);

        PatBlt(hMemDC, 0, 0, cx, cy, WHITENESS);

        hPen=CreatePen(i, 1, 0L);       //i=line style like PS_SOLID
        hObj=SelectObject(hMemDC, hPen);

        MoveToEx(hMemDC, 0, cy/2, NULL);
        LineTo(hMemDC, cx, cy/2);

        ModifyMenu(hMenu, IDM_LINEMIN+i, MF_BYCOMMAND | MF_BITMAP
            , IDM_LINEMIN+i, (LPTSTR)(LONG)(UINT)m_hBmpLines[i]);

        SelectObject(hMemDC, hObj);
        DeleteObject(hPen);
        }

    CheckMenuItem(hMenu, IDM_LINESOLID, MF_CHECKED);
    DeleteDC(hMemDC);

    return;
    }

/*
 * CCosmoFrame::CreateGizmos
 *
 * Purpose:
 *  Procedure to create all the necessary gizmobar buttons.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of gizmos added to the bar.
 */

UINT CCosmoFrame::CreateGizmos(void)
    {
    UINT            iLast;
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;
    UINT            utEx  =GIZMOTYPE_BUTTONATTRIBUTEEX;

    //Insert the standard ones.
    iLast=CFrame::CreateGizmos();

    /*
     * Insert File Import in the 5th position and account for
     * it in iLast.
     */
    m_pGB->Add(utCmd, 4, IDM_FILEIMPORT, m_dxB, m_dyB
        , NULL, m_hBmp, 2, uState);
    iLast++;

    //Separator
    m_pGB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    /*
     * For the Background bitmap, preserve our use of black
     * (part of the image)
     */
    m_pGB->Add(utCmd, iLast++, IDM_COLORBACKGROUND, m_dxB, m_dyB
        , NULL, m_hBmp, 3, GIZMO_NORMAL | PRESERVE_BLACK);

    m_pGB->Add(utCmd, iLast++, IDM_COLORLINE, m_dxB, m_dyB
        , NULL, m_hBmp, 4, uState);

    //Separator
    m_pGB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //Line styles.
    m_pGB->Add(utEx, iLast++, IDM_LINESOLID, m_dxB, m_dyB
        , NULL, m_hBmp, 5, uState);
    m_pGB->Add(utEx, iLast++, IDM_LINEDASH, m_dxB, m_dyB
        , NULL, m_hBmp, 6, uState);
    m_pGB->Add(utEx, iLast++, IDM_LINEDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 7, uState);
    m_pGB->Add(utEx, iLast++, IDM_LINEDASHDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 8, uState);
    m_pGB->Add(utEx, iLast++, IDM_LINEDASHDOTDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 9, uState);

    return iLast;
    }

/*
 * CCosmoFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the Cosmo frame window that just
 *  processes the line menu and the color menu leaving the
 *  CFrame to do everything else.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CCosmoFrame::OnCommand(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PCCosmoDoc      pDoc;
    TCHAR           szFile[CCHPATHMAX];
    BOOL            fOK;
    UINT            i, uTemp;
    COLORREF        rgColors[16];
    CHOOSECOLOR     cc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many GizmoBar notifications.
     */
    if (m_fInit)
        return 0L;

    pDoc=(PCCosmoDoc)m_pCL->ActiveDocument();

    /*
     * Check for the line style commands which are
     * IDM_LINEMIN+<style>.  We handle this by changing the menu
     * and toolbar, then we pass it to the document for real
     * processing.
     */
    if (NULL!=pDoc && IDM_LINEMIN <= wID && IDM_LINEMAX >=wID)
        {
        CheckLineSelection(wID);
        pDoc->LineStyleSet(wID-IDM_LINEMIN);
        return 0L;
        }

    switch (wID)
        {
        case IDM_FILEIMPORT:
            szFile[0]=0;
            fOK=FSaveOpenDialog(szFile, CCHPATHMAX, IDS_FILEIMPORT
                , TRUE, &i);

            if (fOK)
                {
                uTemp=pDoc->ULoad(FALSE, szFile);
                pDoc->ErrorMessage(uTemp);
                }

            return (LRESULT)fOK;

        case IDM_COLORBACKGROUND:
        case IDM_COLORLINE:
            //Invoke the color chooser for either color
            uTemp=(IDM_COLORBACKGROUND==wID)
                ? DOCCOLOR_BACKGROUND : DOCCOLOR_LINE;

            for (i=0; i<16; i++)
                rgColors[i]=RGB(0, 0, i*16);

            memset(&cc, 0, sizeof(CHOOSECOLOR));
            cc.lStructSize=sizeof(CHOOSECOLOR);
            cc.lpCustColors=rgColors;
            cc.hwndOwner=hWnd;
            cc.Flags=CC_RGBINIT;
            cc.rgbResult=pDoc->ColorGet(uTemp);

            if (ChooseColor(&cc))
                pDoc->ColorSet(uTemp, cc.rgbResult);

            break;

        default:
           CFrame::OnCommand(hWnd, wParam, lParam);
        }

    return 0L;
    }

/*
 * CCosmoFrame::OnDocumentDataChange
 *
 * Purpose:
 *  Update the Line menu and GizmoBar if the style in the data
 *  changes.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentDataChange(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::OnDocumentActivate
 *
 * Purpose:
 *  Informs us that document activation changed, so update the UI
 *  for that new document.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentActivate(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    PCDocument  pDoc;
    BOOL        fOK=FALSE;
    BOOL        fCallDefault=TRUE;
    UINT        i;
    UINT        uTemp;
    UINT        uTempE;
    UINT        uTempD;

    pDoc=m_pCL->ActiveDocument();

    uTempE=MF_ENABLED | MF_BYCOMMAND;
    uTempD=MF_DISABLED | MF_GRAYED | MF_BYCOMMAND;
    uTemp=((NULL!=pDoc) ? uTempE : uTempD);

    //File menu:  If there is document window, disable Import.
    if (m_phMenu[0]==hMenu)
        EnableMenuItem(hMenu, IDM_FILEIMPORT, uTemp);

    //Color menu:  no document, no commands
    if (m_phMenu[2]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_COLORBACKGROUND, uTemp);
        EnableMenuItem(hMenu, IDM_COLORLINE,       uTemp);
        fCallDefault=FALSE;
        }

    //Line menu:  no document, no commands
    if (m_phMenu[3]==hMenu)
        {
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            EnableMenuItem(hMenu, i, uTemp);

        fCallDefault=FALSE;
        }

    if (fCallDefault)
        CFrame::UpdateMenus(hMenu, iMenu);

    return;
    }

/*
 * CCosmoFrame::UpdateGizmos
 *
 * Purpose:
 *  Enables and disables gizmos depending on whether we have
 *  a document or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateGizmos(void)
    {
    BOOL        fLast;
    UINT        i;

    //Save the last enabled state before CFrame changes it
    fLast=m_fLastEnable;

    //Let the default hack on its gizmos.
    CFrame::UpdateGizmos();

    /*
     * If CFrame::UpdateGizmos changed anything, then we neeed
     * to change as well--if nothing changes, nothing to do.
     */
    if (fLast!=m_fLastEnable)
        {
        //No document, disable just about everything
        m_pGB->Enable(IDM_FILEIMPORT, m_fLastEnable);

        m_pGB->Enable(IDM_COLORBACKGROUND, m_fLastEnable);
        m_pGB->Enable(IDM_COLORLINE,       m_fLastEnable);

        for (i=IDM_LINEMIN; i <= IDM_LINEMAX; i++)
            m_pGB->Enable(i, m_fLastEnable);
        }

    return;
    }

/*
 * CCosmoFrame::CheckLineSelection
 *
 * Purpose:
 *  Maintains the bitmap menu and the gizmos for the line selection.
 *  Both are mutially exclusive option lists where a selection in
 *  one has to affect the other.
 *
 * Parameters:
 *  uID             UINT ID of the item to be selected
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CheckLineSelection(UINT uID)
    {
    UINT        i;
    HMENU       hMenu;

    //Update menus and gizmo if the selection changes.
    if (uID!=m_uIDCurLine)
        {
        m_uIDCurLine=uID;
        hMenu=GetMenu(m_hWnd);

        //Uncheck all lines initially.
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            CheckMenuItem(hMenu, i, MF_UNCHECKED | MF_BYCOMMAND);

        CheckMenuItem(hMenu, uID, MF_CHECKED | MF_BYCOMMAND);
        m_pGB->Check(uID, TRUE);
        }

    return;
    }


COCOSMO.H

/*
 * COCOSMO.H
 * Component Cosmo Chapter 8
 *
 * Single include file that pulls in everything needed for other
 * source files in the Cosmo application.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _COCOSMO_H_
#define _COCOSMO_H_

#define INC_OLE2
#include <windows.h>
#include <memory.h>
#include <ole2.h>
#include <ole2ver.h>
#include <bookguid.h>
#include <ipoly6.h>

extern "C"
    {
    #include <commdlg.h>
    }

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

//COCOSMO.CPP:  Frame object that creates a main window

class CCosmoFrame : public CFrame
    {
    private:
        HBITMAP         m_hBmpLines[5];     //Menu item bitmaps
        UINT            m_uIDCurLine;       //Current line selection
        BOOL            m_fInitialized;     //OleInitialize work?

    protected:
        //Overridable for creating a CClient for this frame
        virtual PCClient  CreateCClient(void);

        virtual BOOL      FRegisterAllClasses(void);
        virtual BOOL      FPreShowInit(void);
        virtual UINT      CreateGizmos(void);

        virtual LRESULT   OnCommand(HWND, WPARAM, LPARAM);
        virtual void      OnDocumentDataChange(PCDocument);
        virtual void      OnDocumentActivate(PCDocument);

        //New for this class
        virtual void      CreateLineMenu(void);

    public:
        CCosmoFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CCosmoFrame(void);

        //Overrides
        virtual BOOL      FInit(PFRAMEINIT);
        virtual void      UpdateMenus(HMENU, UINT);
        virtual void      UpdateGizmos(void);

        //New for this class
        virtual void      CheckLineSelection(UINT);
    };

typedef CCosmoFrame *PCCosmoFrame;

//CLIENT.CPP

/*
 * The only reason we have a derived class here is to override
 * CreateCDocument so we can create our own type as well as
 * overriding NewDocument to perform one other piece of work once
 * the document's been created.
 */

class CCosmoClient : public CClient
    {
    protected:
        //Overridable for creating a new CDocument
        virtual PCDocument CreateCDocument(void);

    public:
        CCosmoClient(HINSTANCE);
        virtual ~CCosmoClient(void);

        virtual PCDocument NewDocument(BOOL, PCDocumentAdviseSink);
    };

typedef CCosmoClient *PCCosmoClient;

//DOCUMENT.CPP

class CPolylineAdviseSink : public IPolylineAdviseSink6
    {
    private:
        LPVOID              m_pv;        //Customizable structure
        ULONG               m_cRef;
        LPUNKNOWN           m_pUnkOuter; //Unknown for delegation

    public:
        CPolylineAdviseSink(LPVOID, LPUNKNOWN);
        ~CPolylineAdviseSink(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //Advise members.
        STDMETHODIMP_(void) OnPointChange(void);
        STDMETHODIMP_(void) OnSizeChange(void);
        STDMETHODIMP_(void) OnColorChange(void);
        STDMETHODIMP_(void) OnLineStyleChange(void);
    };

typedef CPolylineAdviseSink *PCPolylineAdviseSink;

/*
 * The generic advisory interface.  This object controls it's own
 * lifetime and the document becomes a user of the object with
 * the last reference count.
 */

class CImpIAdviseSink : public IAdviseSink
    {
    protected:
        ULONG               m_cRef;
        LPVOID              m_pObj;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIAdviseSink(LPVOID, LPUNKNOWN);
        ~CImpIAdviseSink(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(void)  OnDataChange(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP_(void)  OnViewChange(DWORD, LONG);
        STDMETHODIMP_(void)  OnRename(LPMONIKER);
        STDMETHODIMP_(void)  OnSave(void);
        STDMETHODIMP_(void)  OnClose(void);
    };

typedef CImpIAdviseSink *PIMPIADVISESINK;

//CHAPTER8MOD
//Drag-drop interfaces we need in the document

class CDropTarget : public IDropTarget
    {
    protected:
        ULONG                m_cRef;
        class CCosmoDoc     *m_pDoc;

        LPDATAOBJECT         m_pIDataObject;

    public:
        CDropTarget(class CCosmoDoc *);
        ~CDropTarget(void);

        //IDropTarget interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP DragEnter(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
        STDMETHODIMP DragOver(DWORD, POINTL, LPDWORD);
        STDMETHODIMP DragLeave(void);
        STDMETHODIMP Drop(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
    };

typedef CDropTarget * PCDropTarget;

class CDropSource : public IDropSource
    {
    protected:
        ULONG                m_cRef;
        class CCosmoDoc     *m_pDoc;

    public:
        CDropSource(class CCosmoDoc *);
        ~CDropSource(void);

        //IDropSource interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP QueryContinueDrag(BOOL, DWORD);
        STDMETHODIMP GiveFeedback(DWORD);
    };

typedef CDropSource * PCDropSource;

//End CHAPTER8MOD

//Constant ID for the window polyline that lives in a document window
#define ID_POLYLINE         10

class CCosmoDoc : public CDocument, public IUnknown
    {
    friend class CPolylineAdviseSink;
    friend class CImpIAdviseSink;

    //CHAPTER8MOD
    //These need access to FQueryPasteFromData, FPasteFromData
    friend class CDropTarget;
    friend class CDropSource;
    //End CHAPTER8MOD

    protected:
        UINT                    m_uPrevSize;

        PPOLYLINE               m_pPL;
        LPSTORAGE               m_pIStorage;
        LPPERSISTSTORAGE        m_pIPersistStorage;

        PPOLYLINEADVISESINK     m_pPLAdv;
        PIMPIADVISESINK         m_pIAdviseSink;

        DWORD                   m_dwConn;   //Advisory connection
        ULONG                   m_cRef;     //Document ref count

        LPDATAOBJECT            m_pIDataClip;   //Clipboard object

        //CHAPTER8MOD
        PCDropTarget            m_pDropTarget;  //Registered target
        BOOL                    m_fDragSource;  //Source==target?
        //End CHAPTER8MOD

    protected:
        virtual BOOL    FMessageHook(HWND, UINT, WPARAM, LPARAM
            , LRESULT *);

        //CHAPTER8MOD
        virtual BOOL    FQueryPasteFromData(LPDATAOBJECT);
        virtual BOOL    FPasteFromData(LPDATAOBJECT);
        LPDATAOBJECT    TransferObjectCreate(void);
        void            DropSelectTargetWindow(void);
        //End CHAPTER8MOD

    public:
        CCosmoDoc(HINSTANCE, PCFrame);
        virtual ~CCosmoDoc(void);

        //Need a controlling unknown for our interfaces
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        virtual BOOL     FInit(PDOCUMENTINIT);
        virtual void     Clear(void);

        virtual UINT     ULoad(BOOL, LPTSTR);
        virtual UINT     USave(UINT, LPTSTR);

        virtual void     Undo(void);
        virtual BOOL     FClip(HWND, BOOL);

        virtual BOOL     FQueryPaste(void);
        virtual BOOL     FPaste(HWND);

        virtual COLORREF ColorSet(UINT, COLORREF);
        virtual COLORREF ColorGet(UINT);

        virtual UINT     LineStyleSet(UINT);
        virtual UINT     LineStyleGet(void);
    };

typedef CCosmoDoc *PCCosmoDoc;

//These color indices wrap the polyline definitions
#define DOCCOLOR_BACKGROUND             POLYLINECOLOR_BACKGROUND
#define DOCCOLOR_LINE                   POLYLINECOLOR_LINE

#endif //_COCOSMO_H_


DOCUMENT.CPP

/*
 * DOCUMENT.CPP
 * Component Cosmo Chapter 8
 *
 * Implementation of the CCosmoDoc derivation of CDocument as
 * well as an implementation of CPolylineAdviseSink.
 *
 * Copyright (c)1993-1997 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cocosmo.h"

/*
 * CCosmoDoc::CCosmoDoc
 * CCosmoDoc::~CCosmoDoc
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame of the frame object.
 */

CCosmoDoc::CCosmoDoc(HINSTANCE hInst, PCFrame pFR)
    : CDocument(hInst, pFR)
    {
    m_uPrevSize=SIZE_RESTORED;
    m_pPL=NULL;
    m_pPLAdv=NULL;
    m_pIStorage=NULL;
    m_pIPersistStorage=NULL;

    m_pIAdviseSink=NULL;
    m_dwConn=0;

    m_pIDataClip=NULL;

    //CHAPTER8MOD
    m_pDropTarget=NULL;
    m_fDragSource=FALSE;
    //End CHAPTER8MOD

    return;
    }

CCosmoDoc::~CCosmoDoc(void)
    {
    LPDATAOBJECT        pIDataObject;
    HRESULT             hr;

    if (NULL!=m_pIDataClip)
        m_pIDataClip->Release();

    //Turn off the advise.
    if (NULL!=m_pPL && 0!=m_dwConn)
        {
        hr=m_pPL->QueryInterface(IID_IDataObject
            , (PPVOID)&pIDataObject);

        if (SUCCEEDED(hr))
            {
            pIDataObject->DUnadvise(m_dwConn);
            pIDataObject->Release();
            }
        }

    if (NULL!=m_pIAdviseSink)
        delete m_pIAdviseSink;

    if (NULL!=m_pIPersistStorage)
        m_pIPersistStorage->Release();

    if (NULL!=m_pIStorage)
        m_pIStorage->Release();

    if (NULL!=m_pPL)
        m_pPL->Release();

    if (NULL!=m_pPLAdv)
        delete m_pPLAdv;

    CoFreeUnusedLibraries();
    return;
    }

/*
 * CCosmoDoc::FInit
 *
 * Purpose:
 *  Initializes an already created document window.  The client
 *  actually creates the window for us, then passes that here for
 *  further initialization.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CCosmoDoc::FInit(PDOCUMENTINIT pDI)
    {
    RECT            rc;
    HRESULT         hr;
    FORMATETC       fe;
    LPDATAOBJECT    pIDataObject;

    //Change the stringtable range to our customization.
    pDI->idsMin=IDS_DOCUMENTMIN;
    pDI->idsMax=IDS_DOCUMENTMAX;

    //Do default initialization
    if (!CDocument::FInit(pDI))
        return FALSE;

    //Create the Polyline Component Object via COMPOBJ.DLL functions
    hr=CoCreateInstance(CLSID_Polyline6, NULL, CLSCTX_INPROC_SERVER
        , IID_IPolyline6, (PPVOID)&m_pPL);

    if (FAILED(hr))
        {
        //Warn that we could not load the Polyline
        MessageBox(pDI->hWndDoc, PSZ(IDS_NOPOLYLINE)
            , PSZ(IDS_CAPTION), MB_OK);
        return FALSE;
        }

    //Initialize the contained Polyline which creates a window.
    GetClientRect(m_hWnd, &rc);
    InflateRect(&rc, -8, -8);

    if (FAILED(m_pPL->Init(m_hWnd, &rc, WS_CHILD | WS_VISIBLE
        , ID_POLYLINE)))
        return FALSE;

    //Set up an advise on the Polyline.
    m_pPLAdv=new CPolylineAdviseSink(this, this);

    if (NULL==m_pPLAdv)
        return FALSE;

    m_pPL->SetAdvise(m_pPLAdv);

    //Get the IPersistStorage interface on the object for loads & saves.
    hr=m_pPL->QueryInterface(IID_IPersistStorage
        , (PPVOID)&m_pIPersistStorage);

    if (FAILED(hr))
        return FALSE;

    /*
     * Create an IAdviseSink and send it to the Polyline's
     * IDataObject with the clipboard format for the Polyline
     * (as in IPOLY6.H).
     */

    //This is a private macro.
    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);

    m_pIAdviseSink=new CImpIAdviseSink(this, this);

    if (NULL==m_pIAdviseSink)
        return FALSE;

    //Set up an advise for the Polyline format
    hr=m_pPL->QueryInterface(IID_IDataObject
        , (PPVOID)&pIDataObject);

    if (FAILED(hr))
        return FALSE;

    pIDataObject->DAdvise(&fe, ADVF_NODATA, m_pIAdviseSink
        , &m_dwConn);
    pIDataObject->Release();

    //CHAPTER8MOD
    m_pDropTarget=new CDropTarget(this);

    if (NULL!=m_pDropTarget)
        {
        m_pDropTarget->AddRef();
        CoLockObjectExternal(m_pDropTarget, TRUE, FALSE);
        RegisterDragDrop(m_hWnd, m_pDropTarget);
        }
    //End CHAPTER8MOD

    return TRUE;
    }

//IUnknown interface for all others we implement in the document

/*
 * CCosmoDoc::QueryInterface
 * CCosmoDoc::AddRef
 * CCosmoDoc::Release
 *
 * Purpose:
 *  IUnknown members for the CCosmoDoc implementation.
 */

STDMETHODIMP CCosmoDoc::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    //The document is the unknown
    if (IID_IUnknown==riid)
        *ppv=(LPUNKNOWN)this;

    //Return contained interfaces for others.
    if (IID_IPolylineAdviseSink6==riid)
        *ppv=m_pPLAdv;

    if (IID_IAdviseSink==riid)
        *ppv=m_pIAdviseSink;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return S_FALSE;
    }

STDMETHODIMP_(ULONG) CCosmoDoc::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CCosmoDoc::Release(void)
    {
    /*
     * Since CoCosmo doesn't use documents like Component Objects,
     * this doesn't do anything except provide a debugging point.
     */
    return --m_cRef;
    }

/*
 * CCosmoDoc::FMessageHook
 *
 * Purpose:
 *  Processes WM_SIZE for the document so we can resize
 *  the Polyline.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CCosmoDoc::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    UINT        dx, dy;
    RECT        rc;

    *pLRes=0;

    if (WM_DESTROY==iMsg)
        {
        if (NULL!=m_pIDataClip)
            {
            if (NOERROR==OleIsCurrentClipboard(m_pIDataClip))
                OleFlushClipboard();
            }

        //CHAPTER8MOD
        /*
         * We have to revoke the drop target here because the window
         * will be destroyed and the property forcefully removed
         * before we could do this in the destructor.
         */
        if (NULL!=m_pDropTarget)
            {
            RevokeDragDrop(m_hWnd);
            CoLockObjectExternal(m_pDropTarget, FALSE, TRUE);
            m_pDropTarget->Release();
            }
        //End CHAPTER8MOD
        }

    if (WM_SIZE==iMsg)
        {
        //Don't effect the Polyline size to or from minimized state.
        if (SIZE_MINIMIZED!=wParam && SIZE_MINIMIZED !=m_uPrevSize)
            {
            //When we change size, resize any Polyline we hold.
            dx=LOWORD(lParam);
            dy=HIWORD(lParam);

            /*
             * If we are getting WM_SIZE in response to a Polyline
             * notification, then don't resize the Polyline window
             * again.
             */
            if (!m_fNoSize && NULL!=m_pPL)
                {
                //Resize the polyline to fit the new client
                SetRect(&rc, 8, 8, dx-8, dy-8);
                m_pPL->RectSet(&rc, FALSE);

                /*
                 * We consider sizing something that makes the file
                 * dirty, but not until we've finished the create
                 * process, which is why we set fNoDirty to FALSE
                 * in WM_CREATE since we get a WM_SIZE on the first
                 * creation.
                 */
                if (!m_fNoDirty)
                    FDirtySet(TRUE);

                SetRect(&rc, 0, 0, dx, dy);

                if (NULL!=m_pAdv)
                    m_pAdv->OnSizeChange(this, &rc);

                m_fNoDirty=FALSE;
                }
            }

        m_uPrevSize=wParam;
        }

    //CHAPTER8MOD
    if (WM_LBUTTONDOWN==iMsg)
        {
        LPDROPSOURCE    pIDropSource;
        LPDATAOBJECT    pIDataObject;
        HRESULT         hr;
        SCODE           sc;
        DWORD           dwEffect;

        pIDropSource=new CDropSource(this);

        if (NULL==pIDropSource)
            return FALSE;

        pIDropSource->AddRef();
        m_fDragSource=TRUE;

        //Go get the data and start the ball rolling.
        pIDataObject=TransferObjectCreate();

        if (NULL!=pIDataObject)
            {
            hr=DoDragDrop(pIDataObject, pIDropSource
                , DROPEFFECT_COPY | DROPEFFECT_MOVE, &dwEffect);

            pIDataObject->Release();
            sc=hr;
            }
        else
            sc=E_FAIL;

        pIDropSource->Release();
        m_fDragSource=FALSE;

        if (DRAGDROP_S_DROP==sc && DROPEFFECT_MOVE==dwEffect)
            {
            m_pPL->New();
            FDirtySet(TRUE);
            }

        return TRUE;
        }
    //End CHAPTER8MOD

    /*
     * We return FALSE even on WM_SIZE so we can let the default
     * procedure handle maximized MDI child windows appropriately.
     */
    return FALSE;
    }

/*
 * CCosmoDoc::Clear
 *
 * Purpose:
 *  Sets all contents in the document back to defaults with
 *  no filename.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Clear(void)
    {
    //Completely reset the polyline
    m_pPL->New();

    CDocument::Clear();
    return;
    }

/*
 * CCosmoDoc::ULoad
 *
 * Purpose:
 *  Loads a given document without any user interface overwriting
 *  the previous contents of the Polyline window.  We do this by
 *  opening the file and telling the Polyline to load itself from
 *  that file.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load, NULL if the file
 *                  is new and untitled.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CCosmoDoc::ULoad(BOOL fChangeFile, LPTSTR pszFile)
    {
    HRESULT             hr;
    LPSTORAGE           pIStorage;

    if (NULL==pszFile)
        {
        /*
         * As a user of an IPersistStorage we have to provide all
         * objects with an IStorage they can use for incremental
         * access passing that storage to InitNew.  Here we create
         * a temporary file that we don't bother holding on to.
         * If the object doesn't use it, then our Release destroys
         * it immediately.
         */

        hr=StgCreateDocfile(NULL, STGM_DIRECT | STGM_READWRITE
            | STGM_CREATE | STGM_DELETEONRELEASE
            | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;

        m_pIPersistStorage->InitNew(pIStorage);
        m_pIStorage=pIStorage;

        Rename(NULL);
        return DOCERR_NONE;
        }

    /*
     * Open a storage and pass it to the Polyline via
     * IPersistStorage.  We do not remain compatible with
     * previous files saved with Component Cosmo.
     */
#if defined(WIN32) && !defined(UNICODE)
    OLECHAR pwcsFile[MAX_PATH];
    mbstowcs(pwcsFile, pszFile, MAX_PATH);
    hr=StgOpenStorage(pwcsFile, NULL, STGM_DIRECT | STGM_READWRITE
        | STGM_SHARE_EXCLUSIVE, NULL, 0, &pIStorage);
#else
    hr=StgOpenStorage(pszFile, NULL, STGM_DIRECT | STGM_READWRITE
        | STGM_SHARE_EXCLUSIVE, NULL, 0, &pIStorage);
#endif
    if (FAILED(hr))
        return DOCERR_COULDNOTOPEN;

    hr=m_pIPersistStorage->Load(pIStorage);
    m_pIStorage=pIStorage;

    if (FAILED(hr))
        return DOCERR_READFAILURE;

    if (fChangeFile)
        Rename(pszFile);

    //Importing a file makes things dirty
    FDirtySet(!fChangeFile);

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::USave
 *
 * Purpose:
 *  Writes the file to a known filename, requiring that the user
 *  has previously used FileOpen or FileSaveAs in order to have
 *  a filename.
 *
 * Parameters:
 *  uType           UINT indicating the type of file the user
 *                  requested to save in the File Save As dialog.
 *  pszFile         LPTSTR under which to save.  If NULL, use the
 *                  current name.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CCosmoDoc::USave(UINT uType, LPTSTR pszFile)
    {
    BOOL                fRename=TRUE;
    HRESULT             hr;
    LPSTORAGE           pIStorage;
    BOOL                fSameAsLoad;

    //If Save or Save As under the same name, do Save.
    if (NULL==pszFile || 0==lstrcmpi(pszFile, m_szFile))
        {
        fRename=FALSE;
        pszFile=m_szFile;

        /*
         * If we're saving to an existing storage, just pass
         * the IStorage we have from ULoad along with TRUE
         * in fSameAsLoad.
         */

        fSameAsLoad=TRUE;
        }
    else
        {
        /*
         * In Component Cosmo, we only deal with one version of
         * data; all the code in Chapter 2 Cosmo that dealt with
         * 1.0 and 2.0 files has been removed.
         */
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsFile[MAX_PATH];
        mbstowcs(pwcsFile, pszFile, MAX_PATH);
        hr=StgCreateDocfile(pwcsFile, STGM_DIRECT | STGM_READWRITE
            | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);
#else
        hr=StgCreateDocfile(pszFile, STGM_DIRECT | STGM_READWRITE
            | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);
#endif
        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;

        //Tell the object to save into this new storage
        fSameAsLoad=FALSE;

        //Update our variable
        m_pIStorage->Release();
        m_pIStorage=pIStorage;
        }

    hr=m_pIPersistStorage->Save(m_pIStorage, fSameAsLoad);

    if (SUCCEEDED(hr))
        {
        hr=m_pIPersistStorage->SaveCompleted(fSameAsLoad
            ? NULL : m_pIStorage);
        }

    if (FAILED(hr))
        return DOCERR_WRITEFAILURE;

    //Saving makes us clean
    FDirtySet(FALSE);

    if (fRename)
        Rename(pszFile);

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::Undo
 *
 * Purpose:
 *  Reverses a previous action.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Undo(void)
    {
    m_pPL->Undo();
    return;
    }

/*
 * CCosmoDoc::FClip
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard, optionally implementing Cut by deleting the
 *  data in the current window after rendering.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *  fCut            BOOL indicating cut (TRUE) or copy (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::FClip(HWND hWndFrame, BOOL fCut)
    {
    //CHAPTER8MOD
    BOOL            fRet=TRUE;
    LPDATAOBJECT    pIDataObject;

    pIDataObject=TransferObjectCreate();

    if (NULL==pIDataObject)
        return FALSE;
    //CHAPTER8MOD

    fRet=SUCCEEDED(OleSetClipboard(pIDataObject));

    if (NULL!=m_pIDataClip)
        m_pIDataClip->Release();

    m_pIDataClip=pIDataObject;

    //Delete our current data if copying succeeded.
    if (fRet && fCut)
        {
        m_pPL->New();
        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CCosmoDoc::FQueryPaste
 *
 * Purpose:
 *  Determines if we can paste data from the clipboard.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CCosmoDoc::FQueryPaste(void)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    //CHAPTER8MOD
    fRet=FQueryPasteFromData(pIDataObject);
    //End CHAPTER8MOD

    pIDataObject->Release();
    return fRet;
    }

//CHAPTER8MOD
/*
 * CCosmoDoc::FQueryPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Determines if we can paste data from a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which we might want to paste.
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CCosmoDoc::FQueryPasteFromData(LPDATAOBJECT pIDataObject)
    {
    FORMATETC       fe;

    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    return (NOERROR==pIDataObject->QueryGetData(&fe));
    }
//End CHAPTER8MOD

/*
 * CCosmoDoc::FPaste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  to the current figure in the editor window.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::FPaste(HWND hWndFrame)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    //CHAPTER8MOD
    fRet=FPasteFromData(pIDataObject);
    //End CHAPTER8MOD

    pIDataObject->Release();
    return TRUE;
    }

//CHAPTER8MOD
/*
 * CCosmoDoc::FPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Retrieves the private data format from a data object and sets
 *  it to the current figure in the editor window.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::FPasteFromData(LPDATAOBJECT pIDataObject)
    {
    FORMATETC       fe;
    STGMEDIUM       stm;
    BOOL            fRet;

    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    fRet=SUCCEEDED(pIDataObject->GetData(&fe, &stm));

    if (!fRet || NULL==stm.hGlobal)
        return FALSE;

    if (fRet && NULL!=stm.hGlobal)
        {
        m_pPL->QueryInterface(IID_IDataObject
            , (PPVOID)&pIDataObject);
        pIDataObject->SetData(&fe, &stm, TRUE);
        pIDataObject->Release();

        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CCosmoDoc::TransferObjectCreate
 * (Protected)
 *
 * Purpose:
 *  Creates a DataTransferObject and stuffs the current Polyline
 *  data into it, used for both clipboard and drag-drop operations.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  LPDATAOBJECT    Pointer to the object created, NULL on failure
 */

LPDATAOBJECT CCosmoDoc::TransferObjectCreate(void)
    {
    UINT            i;
    HRESULT         hr;
    STGMEDIUM       stm;
    FORMATETC       fe;
    LPDATAOBJECT    pIDataSrc;
    LPDATAOBJECT    pIDataObject=NULL;
    const UINT      cFormats=3;
    static UINT     rgcf[3]={0, CF_METAFILEPICT, CF_BITMAP};
    static DWORD    rgtm[3]={TYMED_HGLOBAL, TYMED_MFPICT
                        , TYMED_GDI};

    hr=CoCreateInstance(CLSID_DataTransferObject, NULL
        , CLSCTX_INPROC_SERVER, IID_IDataObject
        , (PPVOID)&pIDataObject);

    if (FAILED(hr))
        return NULL;

    rgcf[0]=m_cf;

    hr=m_pPL->QueryInterface(IID_IDataObject, (PPVOID)&pIDataSrc);

    if (FAILED(hr))
        {
        pIDataObject->Release();
        return NULL;
        }

    for (i=0; i < cFormats; i++)
        {
        //Copy private data first.
        SETDefFormatEtc(fe, rgcf[i], rgtm[i]);

        if (SUCCEEDED(pIDataSrc->GetData(&fe, &stm)))
            pIDataObject->SetData(&fe, &stm, TRUE);
        }

    pIDataSrc->Release();
    return pIDataObject;    //Caller now responsible
    }

/*
 * CCosmoDoc::DropSelectTargetWindow
 * (Protected)
 *
 * Purpose:
 *  Creates a thin inverted frame around a window that we use to
 *  show the window as a drop target.  This is a toggle function:
 *  it uses XOR to create the effect so it must be called twice
 *  to leave the window as it was.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::DropSelectTargetWindow(void)
    {
    HDC         hDC;
    RECT        rc;
    UINT        dd=3;
    HWND        hWnd;

    m_pPL->Window(&hWnd);
    hDC=GetWindowDC(hWnd);
    GetClientRect(hWnd, &rc);

    //Frame this window with inverted pixels

    //Top
    PatBlt(hDC, rc.left, rc.top, rc.right-rc.left, dd, DSTINVERT);

    //Bottom
    PatBlt(hDC, rc.left, rc.bottom-dd, rc.right-rc.left, dd
        , DSTINVERT);

    //Left excluding regions already affected by top and bottom
    PatBlt(hDC, rc.left, rc.top+dd, dd, rc.bottom-rc.top-(2*dd)
        , DSTINVERT);

    //Right excluding regions already affected by top and bottom
    PatBlt(hDC, rc.right-dd, rc.top+dd, dd, rc.bottom-rc.top-(2*dd)
        , DSTINVERT);

    ReleaseDC(hWnd, hDC);
    return;
    }

//End CHAPTER8MOD

/*
 * CCosmoDoc::ColorSet
 *
 * Purpose:
 *  Changes a color used in our contained Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color.
 *
 * Return Value:
 *  COLORREF        Previous color for the given index.
 */

COLORREF CCosmoDoc::ColorSet(UINT iColor, COLORREF cr)
    {
    COLORREF    crRet;

    m_pPL->ColorSet(iColor, cr, &crRet);
    return crRet;
    }

/*
 * CCosmoDoc::ColorGet
 *
 * Purpose:
 *  Retrieves a color currently in use in the Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to retrieve.
 *
 * Return Value:
 *  COLORREF        Current color for the given index.
 */

COLORREF CCosmoDoc::ColorGet(UINT iColor)
    {
    COLORREF    crRet;

    m_pPL->ColorGet(iColor, &crRet);
    return crRet;
    }

/*
 * CCosmoDoc::LineStyleSet
 *
 * Purpose:
 *  Changes the line style currently used in the Polyline
 *
 * Parameters:
 *  iStyle          UINT index of the new line style to use.
 *
 * Return Value:
 *  UINT            Previous line style.
 */

UINT CCosmoDoc::LineStyleSet(UINT iStyle)
    {
    UINT    i;

    m_pPL->LineStyleSet(iStyle, &i);
    return i;
    }

/*
 * CCosmoDoc::LineStyleGet
 *
 * Purpose:
 *  Retrieves the line style currently used in the Polyline
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Current line style.
 */

UINT CCosmoDoc::LineStyleGet(void)
    {
    UINT    i;

    m_pPL->LineStyleGet(&i);
    return i;
    }


RESOURCE.H

/*
 * RESOURCE.H
 * Component Cosmo Chapter 8
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Note that we augment classlib.h which we include before this.

//Position of Window menu.
#undef  WINDOW_MENU
#define WINDOW_MENU             4

//Total number of menus
#undef  CMENUS
#define CMENUS                  5

//Menu command identifiers.

#define IDM_FILEIMPORT          (IDM_CUSTOMFILEMIN)

#define IDM_COLORBACKGROUND     (IDM_CUSTOMMIN)
#define IDM_COLORLINE           (IDM_CUSTOMMIN+1)

#define IDM_LINEMIN             (IDM_CUSTOMMIN+100)
#define IDM_LINESOLID           (IDM_LINEMIN+0)         //PS_SOLID       0
#define IDM_LINEDASH            (IDM_LINEMIN+1)         //PS_DASH        1
#define IDM_LINEDOT             (IDM_LINEMIN+2)         //PS_DOT         2
#define IDM_LINEDASHDOT         (IDM_LINEMIN+3)         //PS_DASHDOT     3
#define IDM_LINEDASHDOTDOT      (IDM_LINEMIN+4)         //PS_DASHDOTDOT  4
#define IDM_LINEMAX             IDM_LINEDASHDOTDOT

/*
 * IDs for StatStrip handling of popup menus:  must be in order of menu
 * CLASSRES.H already defines ID_MENUFILE and ID_MENUEDIT for us.
 */

#undef  ID_MENUWINDOW
#undef  ID_MENUHELP

#define ID_MENUCOLOR            (ID_MENUCUSTOMMIN+0)
#define ID_MENULINE             (ID_MENUCUSTOMMIN+1)
#define ID_MENUWINDOW           (ID_MENUCUSTOMMIN+2)
#define ID_MENUHELP             (ID_MENUCUSTOMMIN+3)

//Stringtable IDs.  Keep sequential for each group.
#define IDS_FRAMEMIN            IDS_STANDARDFRAMEMIN
#define IDS_FILEIMPORT          (IDS_CUSTOMFRAMEMIN+0)
#define IDS_FRAMEMAX            (IDS_CUSTOMFRAMEMIN+0)

#define IDS_DOCUMENTMIN         IDS_STANDARDDOCMIN
#define IDS_NOPOLYLINE          (IDS_CUSTOMDOCMIN+0)
#define IDS_DOCUMENTMAX         (IDS_CUSTOMDOCMIN+0)

//These are specifically for the StatStrip
#define IDS_STATMESSAGEMIN                  IDS_STANDARDSTATMESSAGEMIN

#define IDS_ITEMMESSAGEFILEIMPORT           (IDS_CUSTOMSTATMESSAGEMIN+0)

#define IDS_MENUMESSAGECOLOR                (IDS_CUSTOMSTATMESSAGEMIN+1)
#define IDS_ITEMMESSAGECOLORBACKGROUND      (IDS_CUSTOMSTATMESSAGEMIN+2)
#define IDS_ITEMMESSAGECOLORLINE            (IDS_CUSTOMSTATMESSAGEMIN+3)

#define IDS_MENUMESSAGELINE                 (IDS_CUSTOMSTATMESSAGEMIN+4)
#define IDS_ITEMMESSAGELINESOLID            (IDS_CUSTOMSTATMESSAGEMIN+5)
#define IDS_ITEMMESSAGELINEDASH             (IDS_CUSTOMSTATMESSAGEMIN+6)
#define IDS_ITEMMESSAGELINEDOT              (IDS_CUSTOMSTATMESSAGEMIN+7)
#define IDS_ITEMMESSAGELINEDASHDOT          (IDS_CUSTOMSTATMESSAGEMIN+8)
#define IDS_ITEMMESSAGELINEDASHDOTDOT       (IDS_CUSTOMSTATMESSAGEMIN+9)

#define IDS_STATMESSAGEMAX                  (IDS_CUSTOMSTATMESSAGEMIN+9)

#endif //_RESOURCE_H_


DLLPOLY.CPP

/*
 * DLLPOLY.CPP
 * Polyline Component Chapter 8
 *
 * Polyline component object used in CoCosmo that supports a custom
 * interface IPolyline.  Contains DLL entry code and the component
 * object exports DllGetClassObject and DllCanUnloadNow and the
 * class factory object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "polyline.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

//DLL Instance handle
HINSTANCE   g_hInst=0;

/*
 * LibMain32 (Win32)
 *
 * Purpose:
 *  Entry point for Win32 DLLs that calls the Win16 LibMain.
 */

#ifdef WIN32
BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    if (DLL_PROCESS_DETACH==ulReason)
        {
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    return (0!=LibMain(hInstance, 0,  0, NULL));
    }
#endif

/*
 * LibMain (also called from Win32 LibMain32)
 *
 * Purpose:
 *  DLL-specific entry point called from LibEntry.
 */

int PASCAL LibMain(HINSTANCE hInst, WORD wDataSeg
    , WORD cbHeapSize, LPSTR lpCmdLine)
    {
    WNDCLASS    wc;

    if (GetClassInfo(hInst, SZCLASSPOLYLINE, &wc))
        return (int)hInst;

   #ifndef WIN32
    if (0!=cbHeapSize)
        UnlockData(0);
   #endif

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.hInstance     = hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PolylineWndProc;
    wc.cbWndExtra    = CBPOLYLINEWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_CROSS);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPOLYLINE;

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

    g_hInst=hInst;
    return (int)hInst;
    }

/*
 * DllGetClassObject
 *
 * Purpose:
 *  Provides an IClassFactory for a given CLSID that this DLL is
 *  registered to support.  This DLL is placed under the CLSID
 *  in the registration database as the InProcServer.
 *
 * Parameters:
 *  clsID           REFCLSID that identifies the class factory
 *                  desired.  Since this parameter is passed this
 *                  DLL can handle any number of objects simply
 *                  by returning different class factories here
 *                  for different CLSIDs.
 *
 *  riid            REFIID specifying the interface the caller wants
 *                  on the class object, usually IID_ClassFactory.
 *
 *  ppv             PPVOID in which to return the interface
 *                  pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, otherwise an error code.
 */

HRESULT APIENTRY DllGetClassObject(REFCLSID rclsid
    , REFIID riid, PPVOID ppv)
    {
    //CHAPTER8MOD
    if (CLSID_Polyline8!=rclsid)
        return ResultFromScode(E_FAIL);
    //End CHAPTER8MOD

    //Check that we can provide the interface
    if (IID_IUnknown!=riid && IID_IClassFactory!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Return our IClassFactory for Polyline objects
    *ppv=new CPolylineClassFactory;

    if (NULL==*ppv)
        return ResultFromScode(E_OUTOFMEMORY);

    //AddRef the object through any interface we return
    ((LPUNKNOWN)*ppv)->AddRef();
    g_cObj++;
    return NOERROR;
    }

/*
 * DllCanUnloadNow
 *
 * Purpose:
 *  Answers if the DLL can be freed, that is, if there are no
 *  references to anything this DLL provides.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if nothing is using us, FALSE otherwise.
 */

STDAPI DllCanUnloadNow(void)
    {
    SCODE   sc;

    //Our answer is whether there are any object or locks
    sc=(0L==g_cObj && 0L==g_cLock) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Polyline object to call when it gets destroyed.
 *  Since we're in a DLL we only track the number of objects here
 *  letting DllCanUnloadNow take care of the rest.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;
    return;
    }

/*
 * CPolylineClassFactory::CPolylineClassFactory
 * CPolylineClassFactory::~CPolylineClassFactory
 */

CPolylineClassFactory::CPolylineClassFactory(void)
    {
    m_cRef=0L;
    return;
    }

CPolylineClassFactory::~CPolylineClassFactory(void)
    {
    return;
    }

/*
 * CPolylineClassFactory::QueryInterface
 * CPolylineClassFactory::AddRef
 * CPolylineClassFactory::Release
 */

STDMETHODIMP CPolylineClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CPolylineClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPolylineClassFactory::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    ObjectDestroyed();
    return 0L;
    }

/*
 * CPolylineClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a Polyline object.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we
 *                  are being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_NOINTERFACE
 *                  if we cannot support the requested interface.
 */

STDMETHODIMP CPolylineClassFactory::CreateInstance
    (LPUNKNOWN pUnkOuter, REFIID riid, PPVOID ppvObj)
    {
    PCPolyline          pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //Verify that a controlling unknown asks for IUnknown
    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Create the object.  This also creates a window.
    pObj=new CPolyline(pUnkOuter, ObjectDestroyed, g_hInst);

    if (NULL==pObj)
        return hr;

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    //Kill the object if initial creation or Init failed.
    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

/*
 * CPolylineClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the DLL.  If the lock
 *  count goes to zero and there are no objects, the DLL is allowed
 *  to unload.  See DllCanUnloadNow.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CPolylineClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        g_cLock--;

    return NOERROR;
    }


ICONNPT.CPP

/*
 * ICONNPT.CPP
 * Polyline Component Chapter 8
 *
 * Implementation of CImpIConnectionPoint for the Polyline object
 * as well as CConnectionPoint.
 *
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIConnPtCont:CImpIConnPtCont
 * CImpIConnPtCont::~CImpIConnPtCont
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIConnPtCont::CImpIConnPtCont(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIConnPtCont::~CImpIConnPtCont(void)
    {
    return;
    }

/*
 * CImpIConnPtCont::QueryInterface
 * CImpIConnPtCont::AddRef
 * CImpIConnPtCont::Release
 */

STDMETHODIMP CImpIConnPtCont::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIConnPtCont::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIConnPtCont::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIConnPtCont::EnumConnectionPoints
 *
 * Purpose:
 *  Not implemented.
 *
 * Return Value:
 *  HRESULT         E_NOTIMPL
 */

STDMETHODIMP CImpIConnPtCont::EnumConnectionPoints
    (LPENUMCONNECTIONPOINTS *ppEnum)
    {
    *ppEnum=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIConnPtCont::FindConnectionPoint
 *
 * Purpose:
 *  Returns a pointer to the IConnectionPoint for a given
 *  outgoing IID.
 *
 * Parameters:
 *  riid            REFIID of the outgoing interface for which
 *                  a connection point is desired.
 *  ppCP            IConnectionPoint ** in which to return
 *                  the pointer after calling AddRef.
 *
 * Return Value:
 *  HRESULT         NOERROR if the connection point is found,
 *                  E_NOINTERFACE if it's not supported.
 */

STDMETHODIMP CImpIConnPtCont::FindConnectionPoint(REFIID riid
    , IConnectionPoint **ppCP)
    {
    *ppCP=NULL;

    //CHAPTER8MOD
    if (IID_IPolylineAdviseSink8==riid)
    //End CHAPTER8MOD
        {
        return m_pObj->m_pConnPt->QueryInterface
            (IID_IConnectionPoint, (PPVOID)ppCP);
        }

    return ResultFromScode(E_NOINTERFACE);
    }

//CConnectionPoint implementation

/*
 * CConnectionPoint::CConnectionPoint
 * CConnectionPoint::~CConnectionPoint
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.  We can
 *                  query this for the IConnectionPointContainer
 *                  interface we might need.
 */

CConnectionPoint::CConnectionPoint(PCPolyline pObj)
    {
    m_cRef=0;

    /*
     * Our lifetime is controlled by the connectable object itself,
     * although other external clients will call AddRef and Release.
     * Since we're nested in the connectable object's lifetime,
     * there's no need to call AddRef on pObj.
     */
    m_pObj=pObj;
    return;
    }

CConnectionPoint::~CConnectionPoint(void)
    {
    ReleaseInterface(m_pObj->m_pAdv);
    return;
    }

/*
 * CConnectionPoint::QueryInterface
 * CConnectionPoint::AddRef
 * CConnectionPoint::Release
 *
 * Purpose:
 *  Non-delegating IUnknown members for CConnectionPoint.
 */

STDMETHODIMP CConnectionPoint::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IConnectionPoint==riid)
        *ppv=(LPVOID)this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CConnectionPoint::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CConnectionPoint::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CConnectionPoint::GetConnectionInterface
 *
 * Purpose:
 *  Returns the IID of the outgoing interface supported through
 *  this connection point.
 *
 * Parameters:
 *  pIID            IID * in which to store the IID.
 */

STDMETHODIMP CConnectionPoint::GetConnectionInterface(IID *pIID)
    {
    if (NULL==pIID)
        return ResultFromScode(E_POINTER);

    //CHAPTER8MOD
    *pIID=IID_IPolylineAdviseSink8;
    //End CHAPTER8MOD
    return NOERROR;
    }

/*
 * CConnectionPoint::GetConnectionPointContainer
 *
 * Purpose:
 *  Returns a pointer to the IConnectionPointContainer that
 *  is manageing this connection point.
 *
 * Parameters:
 *  ppCPC           IConnectionPointContainer ** in which to return
 *                  the pointer after calling AddRef.
 */

STDMETHODIMP CConnectionPoint::GetConnectionPointContainer
    (IConnectionPointContainer **ppCPC)
    {
    return m_pObj->QueryInterface(IID_IConnectionPointContainer
        , (void **)ppCPC);
    }

/*
 * CConnectionPoint::Advise
 *
 * Purpose:
 *  Provides this connection point with a notification sink to
 *  call whenever the appropriate outgoing function/event occurs.
 *
 * Parameters:
 *  pUnkSink        LPUNKNOWN to the sink to notify.  The connection
 *                  point must QueryInterface on this pointer to obtain
 *                  the proper interface to call.  The connection
 *                  point must also insure that any pointer held has
 *                  a reference count (QueryInterface will do it).
 *  pdwCookie       DWORD * in which to store the connection key for
 *                  later calls to Unadvise.
 */

STDMETHODIMP CConnectionPoint::Advise(LPUNKNOWN pUnkSink
    , DWORD *pdwCookie)
    {
    //CHAPTER8MOD
    IPolylineAdviseSink8   *pSink;
    //End CHAPTER8MOD

    *pdwCookie=0;

    //Only allow one connection
    if (NULL!=m_pObj->m_pAdv)
        return ResultFromScode(CONNECT_E_ADVISELIMIT);

    //Check for the right interface on the sink.
    //CHAPTER8MOD
    if (FAILED(pUnkSink->QueryInterface(IID_IPolylineAdviseSink8
        , (PPVOID)&pSink)))
    //End CHAPTER8MOD
        return ResultFromScode(CONNECT_E_CANNOTCONNECT);

    *pdwCookie=ADVISEKEY;
    m_pObj->m_pAdv=pSink;
    return NOERROR;
    }

/*
 * CConnectionPoint::Unadvise
 *
 * Purpose:
 *  Terminates the connection to the notification sink identified
 *  with dwCookie (that was returned from Advise).  The connection
 *  point has to Release any held pointers for that sink.
 *
 * Parameters:
 *  dwCookie        DWORD connection key from Advise.
 */

STDMETHODIMP CConnectionPoint::Unadvise(DWORD dwCookie)
    {
    if (0==dwCookie)
        return ResultFromScode(E_INVALIDARG);

    if (ADVISEKEY!=dwCookie)
        ResultFromScode(CONNECT_E_NOCONNECTION);

    ReleaseInterface(m_pObj->m_pAdv);
    return NOERROR;
    }

/*
 * CConnectionPoint::EnumConnections
 *
 * Purpose:
 *  Not implemented.
 */

STDMETHODIMP CConnectionPoint::EnumConnections
    (LPENUMCONNECTIONS *ppEnum)
    {
    *ppEnum=NULL;
    return ResultFromScode(E_NOTIMPL);
    }


IPERSTMI.CPP

/*
 * IPERSTMI.CPP
 * Polyline Component Chapter 8
 *
 * Implementation of the IPersistStreamInit interface exposed on the
 * Polyline object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIPersistStreamInit:CImpIPersistStreamInit
 * CImpIPersistStreamInit::~CImpIPersistStreamInit
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistStreamInit::CImpIPersistStreamInit(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPersistStreamInit::~CImpIPersistStreamInit(void)
    {
    return;
    }

/*
 * CImpIPersistStreamInit::QueryInterface
 * CImpIPersistStreamInit::AddRef
 * CImpIPersistStreamInit::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIPersistStreamInit.
 */

STDMETHODIMP CImpIPersistStreamInit::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistStreamInit::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistStreamInit::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistStreamInit::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the object represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 */

STDMETHODIMP CImpIPersistStreamInit::GetClassID(LPCLSID pClsID)
    {
    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this object since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 */

STDMETHODIMP CImpIPersistStreamInit::IsDirty(void)
    {
    return ResultFromScode(m_pObj->m_fDirty ? S_OK : S_FALSE);
    }

/*
 * CImpIPersistStreamInit::Load
 *
 * Purpose:
 *  Instructs the object to load itself from a previously saved
 *  IStreamInit that was handled by Save in another object lifetime.
 *  The seek pointer in this stream will be exactly the same as
 *  it was when Save was called, and this function must leave
 *  the seek pointer the same as it was on exit from Save, regardless
 *  of success or failure.  This function should not hold on to
 *  pIStream.
 *
 *  This function is called in lieu of IPersistStreamInit::InitNew
 *  when the object already has a persistent state.
 *
 * Parameters:
 *  pIStream        LPSTREAM from which to load.
 */

STDMETHODIMP CImpIPersistStreamInit::Load(LPSTREAM pIStream)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    HRESULT         hr;

    if (NULL==pIStream)
        return ResultFromScode(E_POINTER);

    //Read all the data into the POLYLINEDATA structure.
    hr=pIStream->Read(&pl, CBPOLYLINEDATA, &cb);

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        return hr;

    m_pObj->m_pImpIPolyline->DataSet(&pl, TRUE, TRUE);
    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::Save
 *
 * Purpose:
 *  Saves the data for this object to an IStreamInit.  Be sure not
 *  to change the position of the seek pointer on entry to this
 *  function: the caller will assume that you write from the
 *  current offset.  Leave the stream's seek pointer at the end
 *  of the data written on exit.
 *
 * Parameters:
 *  pIStream        LPSTREAM in which to save our data.
 *  fClearDirty     BOOL indicating if this call should clear
 *                  the object's dirty flag (TRUE) or leave it
 *                  unchanged (FALSE).
 */

STDMETHODIMP CImpIPersistStreamInit::Save(LPSTREAM pIStream
    , BOOL fClearDirty)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    HRESULT         hr;

    if (NULL==pIStream)
        return ResultFromScode(E_POINTER);

    m_pObj->m_pImpIPolyline->DataGet(&pl);

    hr=pIStream->Write(&pl, CBPOLYLINEDATA, &cb);

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        return ResultFromScode(STG_E_WRITEFAULT);

    if (fClearDirty)
        m_pObj->m_fDirty=FALSE;

    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::GetSizeMax
 *
 * Purpose:
 *  Returns the size of the data we would write if Save was
 *  called right now.
 *
 * Parameters:
 *  pcbSize         ULARGE_INTEGER * in which to save the size
 *                  of the stream an immediate call to Save would
 *                  write.
 */

STDMETHODIMP CImpIPersistStreamInit::GetSizeMax(ULARGE_INTEGER
    *pcbSize)
    {
    if (NULL==pcbSize)
        return ResultFromScode(E_POINTER);

    ULISet32(*pcbSize, CBPOLYLINEDATA);
    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::InitNew
 *
 * Purpose:
 *  Informs the object that it is being created new instead of
 *  loaded from a persistent state.  This will be called in lieu
 *  of IPersistStreamInit::Load.
 *
 * Parameters:
 *  None
 */

STDMETHODIMP CImpIPersistStreamInit::InitNew(void)
    {
    //Nothing for us to do
    return NOERROR;
    }


IPERSTOR.CPP

/*
 * IPERSTOR.CPP
 * Polyline Component Chapter 8
 *
 * Implementation of the IPersistStorage interface exposed on the
 * Polyline object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIPersistStorage:CImpIPersistStorage
 * CImpIPersistStorage::~CImpIPersistStorage
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistStorage::CImpIPersistStorage(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    m_psState=PSSTATE_UNINIT;
    return;
    }

CImpIPersistStorage::~CImpIPersistStorage(void)
    {
    return;
    }

/*
 * CImpIPersistStorage::QueryInterface
 * CImpIPersistStorage::AddRef
 * CImpIPersistStorage::Release
 */

STDMETHODIMP CImpIPersistStorage::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistStorage::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistStorage::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistStorage::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the object represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, error code otherwise.
 */

STDMETHODIMP CImpIPersistStorage::GetClassID(LPCLSID pClsID)
    {
    if (PSSTATE_UNINIT==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this object since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 *
 */

STDMETHODIMP CImpIPersistStorage::IsDirty(void)
    {
    if (PSSTATE_UNINIT==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    return ResultFromScode(m_pObj->m_fDirty ? S_OK : S_FALSE);
    }

/*
 * CImpIPersistStorage::InitNew
 *
 * Purpose:
 *  Provides the object with the IStorage to hold on to while the
 *  object is running.  Here we initialize the structure of the
 *  storage and AddRef it for incremental access. This function will
 *  only be called once in the object's lifetime in lieu of Load.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE for the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::InitNew(LPSTORAGE pIStorage)
    {
    HRESULT         hr;
    ULARGE_INTEGER  uli;

    if (PSSTATE_UNINIT!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    if (NULL==pIStorage)
        return ResultFromScode(E_POINTER);

    /*
     * The rules of IPersistStorage mean we hold onto the IStorage
     * and pre-create anything we'd need in Save(...,TRUE) for
     * low-memory situations.  For us this means creating our
     * "CONTENTS" stream and holding onto that IStream as
     * well as the IStorage here (requiring an AddRef call).
     */

    hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
        | STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE
        , 0, 0, &m_pObj->m_pIStream);

    if (FAILED(hr))
        return hr;

    //Preallocate stream space
    ULISet32(uli, CBPOLYLINEDATA);
    m_pObj->m_pIStream->SetSize(uli);

    //We expect that the client has called WriteClassStg
    WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
        , (*m_pObj->m_pST)[IDS_USERTYPE]);

    m_pObj->m_pIStorage=pIStorage;
    pIStorage->AddRef();

    m_psState=PSSTATE_SCRIBBLE;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::Load
 *
 * Purpose:
 *  Instructs the object to load itself from a previously saved
 *  IStorage that was handled by Save in another object lifetime.
 *  This function will only be called once in the object's lifetime
 *  in lieu of InitNew. The object should hold on to pIStorage here
 *  for incremental access and low-memory saves in Save.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE from which to load.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::Load(LPSTORAGE pIStorage)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    LPSTREAM        pIStream;
    HRESULT         hr;

    if (PSSTATE_UNINIT!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    if (NULL==pIStorage)
        return ResultFromScode(E_POINTER);

    //We don't check CLSID to remain compatible with other chapters.

    hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
        | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return ResultFromScode(STG_E_READFAULT);

    //Read all the data into the POLYLINEDATA structure.
    hr=pIStream->Read(&pl, CBPOLYLINEDATA, &cb);

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        {
        pIStream->Release();
        return hr;
        }

    /*
     * We don't call pIStream->Release here because we may need
     * it for a low-memory save in Save.  We also need to
     * hold onto a copy of pIStorage, meaning AddRef.
     */
    m_pObj->m_pIStream=pIStream;
    m_pObj->m_pIStorage=pIStorage;
    pIStorage->AddRef();

    m_pObj->m_pImpIPolyline->DataSet(&pl, TRUE, TRUE);
    m_psState=PSSTATE_SCRIBBLE;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::Save
 *
 * Purpose:
 *  Saves the data for this object to an IStorage which may
 *  or may not be the same as the one previously passed to
 *  Load, indicated with fSameAsLoad.  After this call we may
 *  not write into the storage again until SaveCompleted is
 *  called, although we may still read.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which to save our data.
 *  fSameAsLoad     BOOL indicating if this is the same pIStorage
 *                  that was passed to Load.  If TRUE, then the
 *                  object should write whatever it has *without
 *                  *using any extra memory* as this may be a low
 *                  memory save attempt.  That means that you must
 *                  not try to open or create streams.  If FALSE
 *                  you need to regenerate your whole storage
 *                  structure, being sure to also release any
 *                  pointers held from InitNew and Load.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::Save(LPSTORAGE pIStorage
    , BOOL fSameAsLoad)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    LPSTREAM        pIStream;
    HRESULT         hr;

    //Have to come here from scribble state.
    if (PSSTATE_SCRIBBLE!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    //Must have an IStorage if we're not in SameAsLoad
    if (NULL==pIStorage && !fSameAsLoad)
        return ResultFromScode(E_POINTER);

    /*
     * If we're saving to a new storage, create a new stream.
     * If fSameAsLoad it TRUE, then we write to the
     * stream we already allocated.  We should NOT depends on
     * pIStorage with fSameAsLoad is TRUE.
     */
    if (fSameAsLoad)
        {
        LARGE_INTEGER   li;

        /*
         * Use pre-allocated streams to avoid failures due
         * to low-memory conditions.  Be sure to reset the
         * stream pointer if you used this stream before!!
         */
        pIStream=m_pObj->m_pIStream;
        LISet32(li, 0);
        pIStream->Seek(li, STREAM_SEEK_SET, NULL);

        //This matches the Release below.
        pIStream->AddRef();
        }
    else
        {
        hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
            | STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE
            , 0, 0, &pIStream);

        if (FAILED(hr))
            return hr;

        //Only do this with new storages.
        WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
            , (*m_pObj->m_pST)[IDS_USERTYPE]);
        }

    //DataGet makes not allocations; it's just a memory copy.
    m_pObj->m_pImpIPolyline->DataGet(&pl);

    hr=pIStream->Write(&pl, CBPOLYLINEDATA, &cb);
    pIStream->Release();

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        return ResultFromScode(STG_E_WRITEFAULT);

    m_psState=PSSTATE_ZOMBIE;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::SaveCompleted
 *
 * Purpose:
 *  Notifies the object that the storage in pIStorage has been
 *  completely saved now.  This is called when the user of this
 *  object wants to save us in a completely new storage, and if
 *  we normally hang on to the storage we have to reinitialize
 *  ourselves here for this new one that is now complete.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE of the new storage in which we live.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::SaveCompleted(LPSTORAGE pIStorage)
    {
    HRESULT     hr;
    LPSTREAM    pIStream;

    //Must be called in no-scribble or hands-off state
    if (!(PSSTATE_ZOMBIE==m_psState || PSSTATE_HANDSOFF==m_psState))
        return ResultFromScode(E_UNEXPECTED);

    //If we're coming from Hands-Off, we'd better get a storage
    if (NULL==pIStorage && PSSTATE_HANDSOFF==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    /*
     * If pIStorage is NULL, then we don't need to do anything
     * since we already have all the pointers we need for Save.
     * Otherwise we have to release any held pointers and
     * reinitialize them from pIStorage.
     */

    if (NULL!=pIStorage)
        {
        hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
            | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0
            , &pIStream);

        if (FAILED(hr))
            return hr;

        if (NULL!=m_pObj->m_pIStream)
            m_pObj->m_pIStream->Release();

        m_pObj->m_pIStream=pIStream;

        if (NULL!=m_pObj->m_pIStorage)
            m_pObj->m_pIStorage->Release();

        m_pObj->m_pIStorage=pIStorage;
        m_pObj->m_pIStorage->AddRef();
        }

    //Change state back to scribble.
    m_psState=PSSTATE_SCRIBBLE;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::HandsOffStorage
 *
 * Purpose:
 *  Instructs the object that another agent is interested in having
 *  total access to the storage we might be hanging on to from
 *  InitNew or SaveCompleted.  In this case we must release our hold
 *  and await another call to SaveCompleted before we have a hold
 *  again.  Therefore we cannot read or write after this call until
 *  SaveCompleted.
 *
 *  Situations where this might happen arise in compound document
 *  scenarios where this object might be in-place active but the
 *  application wants to rename and commit the root storage.
 *  Therefore we are asked to close our hold, let the container
 *  party on the storage, then call us again later to tell us the
 *  new storage we can hold.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::HandsOffStorage(void)
    {
    /*
     * Must come from scribble or no-scribble.  A repeated call
     * to HandsOffStorage is an unexpected error (bug in client).
     */
    if (PSSTATE_UNINIT==m_psState || PSSTATE_HANDSOFF==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    //Release held pointers
    if (NULL!=m_pObj->m_pIStream)
        ReleaseInterface(m_pObj->m_pIStream);

    if (NULL!=m_pObj->m_pIStorage)
        ReleaseInterface(m_pObj->m_pIStorage);

    m_psState=PSSTATE_HANDSOFF;
    return NOERROR;
    }


IPOLYLIN.CPP

/*
 * IPOLYLIN.CPP
 * Polyline Component Chapter 8
 *
 * Implementation of the IPolyline8 interface that we expose on the
 * CPolyline object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIPolyline:CImpIPolyline
 * CImpIPolyline::~CImpIPolyline
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPolyline::CImpIPolyline(PCPolyline pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPolyline::~CImpIPolyline(void)
    {
    return;
    }

/*
 * CImpIPolyline::QueryInterface
 * CImpIPolyline::AddRef
 * CImpIPolyline::Release
 */

STDMETHODIMP CImpIPolyline::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPolyline::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPolyline::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPolyline::Init
 *
 * Purpose:
 *  Instantiates a polyline window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags
 *  uID             UINT ID to associate with this window
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_OUTOFMEMORY
 */

STDMETHODIMP CImpIPolyline::Init(HWND hWndParent, LPRECT pRect
    , DWORD dwStyle, UINT uID)
    {
    SCODE           sc;

    m_pObj->m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY
        , SZCLASSPOLYLINE, SZCLASSPOLYLINE, dwStyle, pRect->left
        , pRect->top, pRect->right-pRect->left
        , pRect->bottom-pRect->top, hWndParent, (HMENU)uID
        , m_pObj->m_hInst, m_pObj);

    sc=(NULL!=m_pObj->m_hWnd) ? S_OK : E_OUTOFMEMORY;
    return ResultFromScode(sc);
    }

//CHAPTER8MOD
/*
 * ReadFromFile and WriteToFile are replaced by the IPersistStorage
 * implementation.  We no longer read from files, instead we use
 * storages.
 */
//End CHAPTER8MOD

/*
 * CImpIPolyline::DataSet
 *
 * Purpose:
 *  Sets the current data in this Polyline to a given structure.
 *
 * Parameters:
 *  pplIn           PPOLYLINEDATA to initialize to.
 *  fSizeToData     BOOL indicating if we're to size to the data
 *                  or scale it.
 *  fNotify         BOOL indicating if we're to send an advise
 *                  on this change.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::DataSet(PPOLYLINEDATA pplIn
    , BOOL fSizeToData, BOOL fNotify)
    {
    PPOLYLINEDATA   ppl=&m_pObj->m_pl;
    RECT            rc;

    /*
     * Copy the structure in pplIn and repaint to reflect the
     * new point set.  Note that unlike the RectSet message, we
     * do no scaling, assuming that the rect in the structure
     * is appropriate for the data.
     */

    if (NULL==pplIn)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    *ppl=*pplIn;

    //Inform our parent of the data change
    if (NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnDataChange();
        }

    /*
     * If we're scaling the window to fit the data, then use
     * RectSet passing our current rectangle as the new one.
     * That makes sure that the data won't change but that the
     * window is resized.
     */

    if (fSizeToData)
        {
        POINT       pt;

        /*
         * Get our offset in the parent window so we can RectSet
         * to the right place since RectSet expects rectangle in
         * parent coordinates and we get it in client coordinates.
         */
        GetWindowRect(m_pObj->m_hWnd, &rc);
        pt.x=rc.left;
        pt.y=rc.top;
        ScreenToClient(GetParent(m_pObj->m_hWnd), &pt);
        RECTSTORECT(ppl->rc, rc);
        OffsetRect(&rc, pt.x, pt.y);

        //This will also cause a repaint.
        RectSet(&rc, fNotify);
        }
    else
        {
        //Make sure we're updated.
        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        UpdateWindow(m_pObj->m_hWnd);
        }

    return NOERROR;
    }

/*
 * CImpIPolyline::DataGet
 *
 * Purpose:
 *  Retrieves the Polyline's current data.
 *
 * Parameters:
 *  pplIn           PPOLYLINEDATA into which we copy the data.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::DataGet(PPOLYLINEDATA pplIn)
    {
    if (NULL==pplIn)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    *pplIn=m_pObj->m_pl;
    return NOERROR;
    }

/*
 * CImpIPolyline::DataSetMem
 *
 * Purpose:
 *  Sets the Polyline's data using a global memory handle
 *  instead of a pointer.
 *
 * Parameters:
 *  hMem            HGLOBAL containing the data.
 *  fFree           BOOL indicating if we're to free the data.
 *                  The memory will be freed regardless of any
 *                  error returned from here.
 *  fSizeToData     BOOL indicating if we're to size to the data
 *                  or scale it.
 *  fNotify         BOOL indicating if we're to send an advise
 *                  on this change.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::DataSetMem(HGLOBAL hMem, BOOL fFree
    , BOOL fSizeToData, BOOL fNotify)
    {
    PPOLYLINEDATA   ppl;
    HRESULT         hr=ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        hr=DataSet(ppl, fSizeToData, fNotify);

        GlobalUnlock(hMem);

        if (fFree)
            GlobalFree(hMem);
        }

    return hr;
    }

/*
 * CImpIPolyline::DataGetMem
 *
 * Purpose:
 *  Retrieves the Polyline's data in a global memory handle.
 *
 * Parameters:
 *  phMem           HGLOBAL * in which to store the handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::DataGetMem(HGLOBAL *phMem)
    {
    HGLOBAL         hMem;
    PPOLYLINEDATA   ppl;
    HRESULT         hr=ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    if (NULL==phMem)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, CBPOLYLINEDATA);

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        hr=DataGet(ppl);

        GlobalUnlock(hMem);

        if (FAILED(hr))
            {
            GlobalFree(hMem);
            hMem=NULL;
            }
        }

    *phMem=hMem;
    return hr;
    }

/*
 * CImpIPolyline::RenderBitmap
 *
 * Purpose:
 *  Creates a bitmap image of the current Polyline.
 *
 * Parameters:
 *  phBmp           HBITMAP * in which to return the bitmap.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::RenderBitmap(HBITMAP *phBmp)
    {
    HDC             hDC;
    HDC             hMemDC;
    HBITMAP         hBmp;
    RECT            rc;
    HGDIOBJ         hObj;

    if (NULL==phBmp)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    //Render a bitmap the size of the current rectangle.
    hDC=GetDC(m_pObj->m_hWnd);
    hMemDC=CreateCompatibleDC(hDC);

    GetClientRect(m_pObj->m_hWnd, &rc);
    hBmp=CreateCompatibleBitmap(hDC, rc.right, rc.bottom);

    if (NULL!=hBmp)
        {
        //Draw the POLYLINEDATA into the bitmap.
        hObj=SelectObject(hMemDC, hBmp);
        m_pObj->Draw(hMemDC, FALSE, TRUE);
        SelectObject(hMemDC, hObj);
        }

    DeleteDC(hMemDC);
    ReleaseDC(m_pObj->m_hWnd, hDC);

    *phBmp=hBmp;
    return NOERROR;
    }

/*
 * CImpIPolyline::RenderMetafile
 *
 * Purpose:
 *  Renders the current image of the Polyline into a metafile.
 *
 * Parameters:
 *  phMF            HMETAFILE * in which to return the
 *                  new metafile.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::RenderMetafile(HMETAFILE *phMF)
    {
    HDC             hDC;
    HMETAFILE       hMF;
    RECT            rc;

    if (NULL==phMF)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    //Create a memory metafile and return its handle.
    hDC=(HDC)CreateMetaFile(NULL);
    hMF=NULL;

    if (NULL!=hDC)
        {
        /*
         * This is absolutely essential to the metafile so it
         * can be scaled in the clipboard and any destination
         * application.
         */
        SetMapMode(hDC, MM_ANISOTROPIC);
        GetClientRect(m_pObj->m_hWnd, &rc);
        SetWindowOrgEx(hDC, 0, 0, NULL);
        SetWindowExtEx(hDC, rc.right, rc.bottom, NULL);

        m_pObj->Draw(hDC, TRUE, TRUE);
        hMF=CloseMetaFile(hDC);
        }

    *phMF=hMF;
    return NOERROR;
    }

/*
 * CImpIPolyline::RenderMetafilePict
 *
 * Purpose:
 *  Renders the current Polyline into a METAFILEPICT structure in
 *  global memory.
 *
 * Parameters:
 *  phMem           HGLOBAL * in which to return the
 *                  METAFILEPICT.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::RenderMetafilePict(HGLOBAL *phMem)
    {
    HGLOBAL         hMem;
    HMETAFILE       hMF;
    LPMETAFILEPICT  pMF;
    RECT            rc;
    HRESULT         hr;

    if (NULL==phMem)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    //Get the metafile
    hr=RenderMetafile(&hMF);

    if (FAILED(hr))
        return hr;

    //Allocate the METAFILEPICT structure.
    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
        , sizeof(METAFILEPICT));

    if (NULL==hMem)
        {
        DeleteMetaFile(hMF);
        return ResultFromScode(E_FAIL);
        }

    /*
     * Global lock only fails in PMODE if the selector is invalid
     * (like it was discarded) or references a 0 length segment,
     * neither of which can happen here.
     */
    pMF=(LPMETAFILEPICT)GlobalLock(hMem);

    pMF->hMF=hMF;
    pMF->mm=MM_ANISOTROPIC;

    //Insert the extents in MM_HIMETRIC units.
    GetClientRect(m_pObj->m_hWnd, &rc);
    m_pObj->RectConvertMappings(&rc, FALSE);
    pMF->xExt=rc.right;
    pMF->yExt=rc.bottom;

    GlobalUnlock(hMem);

    *phMem=hMem;
    return NOERROR;
    }

/*
 * CImpIPolyline::New
 *
 * Purpose:
 *  Cleans out and reinitializes the data to defaults.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::New(void)
    {
    PPOLYLINEDATA   ppl=&m_pObj->m_pl;
    UINT            i;
    RECT            rc;

    ppl->wVerMaj=VERSIONMAJOR;
    ppl->wVerMin=VERSIONMINOR;

    //Our rectangle is the size of our window's client area.
    GetClientRect(m_pObj->m_hWnd, &rc);
    RECTTORECTS(rc, ppl->rc);

    //Clean out the POLYLINEDATA structure and repaint the window.
    for (i=0; i< CPOLYLINEPOINTS; i++)
        {
        ppl->rgpt[i].x=0;
        ppl->rgpt[i].y=0;
        }

    ppl->cPoints      =0;
    ppl->rgbBackground=GetSysColor(COLOR_WINDOW);
    ppl->rgbLine      =GetSysColor(COLOR_WINDOWTEXT);
    ppl->iLineStyle   =PS_SOLID;

    InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
    UpdateWindow(m_pObj->m_hWnd);

    //Inform the advise sink of this data change.
    if (NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnDataChange();
        }

    return NOERROR;
    }

/*
 * CImpIPolyline::Undo
 *
 * Purpose:
 *  Reverses previous actions in a Polyline.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         S_OK if we can Undo more, S_FALSE otherwise.
 */

STDMETHODIMP CImpIPolyline::Undo(void)
    {
    SCODE           sc;

    //Decrement the number of active points and repaint.
    if (m_pObj->m_pl.cPoints > 0)
        {
        m_pObj->m_pl.cPoints--;
        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        UpdateWindow(m_pObj->m_hWnd);
        }

    if (NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnPointChange();
        }

    //Return if we can undo any more.
    sc=(0!=m_pObj->m_pl.cPoints) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * CImpIPolyline::Window
 *
 * Purpose:
 *  Returns the window handle associated with this polyline.
 *
 * Parameters:
 *  phWnd           HWND * in which to return the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CImpIPolyline::Window(HWND *phWnd)
    {
    *phWnd=m_pObj->m_hWnd;
    return NOERROR;
    }

/*
 * CImpIPolyline::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::RectGet(LPRECT pRect)
    {
    RECT            rc;
    POINT           pt;

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_pObj->m_hWnd, &rc);
    pt.x=rc.left;
    pt.y=rc.top;
    ScreenToClient(GetParent(m_pObj->m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return NOERROR;
    }

/*
 * CImpIPolyline::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return NOERROR;
    }

/*
 * CImpIPolyline::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT            cx, cy;
    RECT            rc;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_pObj->m_hWnd, NULL, pRect->left, pRect->top
        , cx, cy, SWP_NOZORDER);

    SetRect(&rc, 0, 0, cx, cy);
    RECTTORECTS(rc, m_pObj->m_pl.rc);

    if (fNotify && NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnSizeChange();
        }

    InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);

    return NOERROR;
    }

/*
 * CImpIPolyline::SizeSet
 *
 * Purpose:
 *  Sets a new size for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT            cx, cy;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_pObj->m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
        , SWP_NOMOVE | SWP_NOZORDER);

    if (fNotify && NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnSizeChange();
        }

    InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);

    return NOERROR;
    }

/*
 * CImpIPolyline::ColorSet
 *
 * Purpose:
 *  Changes for background or line color in the Polyline
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color to use.
 *  pcrPrev         COLORREF * in whch to store the
 *                  previous color.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::ColorSet(UINT iColor, COLORREF cr
    , COLORREF *pcrPrev)
    {
    COLORREF        crRet;

    if (NULL==pcrPrev)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    switch (iColor)
        {
        case POLYLINECOLOR_BACKGROUND:
            crRet=m_pObj->m_pl.rgbBackground;
            m_pObj->m_pl.rgbBackground=cr;
            break;

        case POLYLINECOLOR_LINE:
            crRet=m_pObj->m_pl.rgbLine;
            m_pObj->m_pl.rgbLine=cr;
            break;
        }

    //If the color changed, repaint
    if (crRet!=cr)
        {
        if (NULL!=m_pObj->m_pAdv)
            {
            m_pObj->m_fDirty=TRUE;
            m_pObj->m_pAdv->OnColorChange();
            }

        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        UpdateWindow(m_pObj->m_hWnd);
        }

    *pcrPrev=crRet;
    return NOERROR;
    }

/*
 * CImpIPolyline::ColorGet
 *
 * Purpose:
 *  Retrieves one of the colors currently in use by the Polyline.
 *
 * Parameters:
 *  iColor          UINT identifying the color of interest.
 *  pcr             COLORREF * in which to return the color.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::ColorGet(UINT iColor, COLORREF *pcr)
    {
    COLORREF        crRet;

    if (NULL==pcr)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    crRet=(POLYLINECOLOR_BACKGROUND==iColor)
        ? m_pObj->m_pl.rgbBackground : m_pObj->m_pl.rgbLine;

    *pcr=crRet;
    return NOERROR;
    }

/*
 * CImpIPolyline::LineStyleSet
 *
 * Purpose:
 *  Changes the line style in use by the Polyline
 *
 * Parameters:
 *  iStyle          UINT style of the line to use.
 *  piPrev          UINT * in which to store the previous style.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::LineStyleSet(UINT iStyle, UINT *piPrev)
    {
    UINT            uRet;

    uRet=(UINT)m_pObj->m_pl.iLineStyle;

    if (NULL==piPrev)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    //Validate the line style
    if (PS_SOLID==iStyle || PS_DASH==iStyle || PS_DOT==iStyle
        || PS_DASHDOT==iStyle || PS_DASHDOTDOT==iStyle)
        {
        m_pObj->m_pl.iLineStyle=iStyle;

        if (uRet!=(UINT)m_pObj->m_pl.iLineStyle)
            {
            if (NULL!=m_pObj->m_pAdv)
                {
                m_pObj->m_fDirty=TRUE;
                m_pObj->m_pAdv->OnLineStyleChange();
                }

            InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
            UpdateWindow(m_pObj->m_hWnd);
            }
        }

    *piPrev=uRet;
    return NOERROR;
    }

/*
 * CImpIPolyline::LineStyleGet
 *
 * Purpose:
 *  Retrieves the current line style in use in the Polyline
 *
 * Parameters:
 *  piStyle         UINT * in which to store the style.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::LineStyleGet(UINT *piStyle)
    {
    if (NULL==piStyle)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    *piStyle=m_pObj->m_pl.iLineStyle;
    return NOERROR;
    }


POLYLINE.CPP

/*
 * POLYLINE.CPP
 * Polyline Component Object Chapter 8
 *
 * Implementation of the CPolyline class that we expose as a
 * component object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CPolyline:CPolyline
 * CPolyline::~CPolyline
 *
 * Constructor Parameters:
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 *  pfnDestroy      PFNDESTROYED to call when an object is
 *                  destroyed.
 *  hInst           HINSTANCE of the application we're in.
 */

CPolyline::CPolyline(LPUNKNOWN pUnkOuter, PFNDESTROYED pfnDestroy
    , HINSTANCE hInst)
    {
    m_hWnd=NULL;
    m_hInst=hInst;

    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pfnDestroy=pfnDestroy;
    m_fDirty=FALSE;

    m_pImpIPolyline=NULL;
    m_pImpIConnPtCont=NULL;

    m_pAdv=NULL;
    m_pConnPt=NULL;

    //CHAPTER8MOD
    m_pST  =NULL;
    m_cf   =0;
    m_clsID=CLSID_Polyline8;

    m_pIStorage=NULL;
    m_pIStream =NULL;

    m_pImpIPersistStorage=NULL;
    m_pImpIPersistStreamInit=NULL;
    //End CHAPTER8MOD

    return;
    }

CPolyline::~CPolyline(void)
    {
    //CHAPTER8MOD
    if (NULL!=m_pST)
        delete m_pST;

    DeleteInterfaceImp(m_pImpIPersistStreamInit);
    DeleteInterfaceImp(m_pImpIPersistStorage);
    ReleaseInterface(m_pIStream);
    ReleaseInterface(m_pIStorage);
    //End CHAPTER8MOD

    DeleteInterfaceImp(m_pImpIConnPtCont);
    DeleteInterfaceImp(m_pImpIPolyline);

    ReleaseInterface(m_pAdv);
    ReleaseInterface(m_pConnPt);
    return;
    }

/*
 * CPolyline::Init
 *
 * Purpose:
 *  Performs any intiailization of a CPolyline that's prone to
 *  failure that we also use internally before exposing the
 *  object outside this DLL.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CPolyline::Init(void)
    {
    LPUNKNOWN       pIUnknown=this;

    if (NULL!=m_pUnkOuter)
        pIUnknown=m_pUnkOuter;

    //CHAPTER8MOD
    m_pST=new CStringTable(m_hInst);

    if (!m_pST->Init(IDS_POLYLINEMIN, IDS_POLYLINEMAX))
        return FALSE;

    m_cf=RegisterClipboardFormat(PSZ(IDS_STORAGEFORMAT));

    m_pImpIPersistStorage=new CImpIPersistStorage(this, pIUnknown);

    if (NULL==m_pImpIPersistStorage)
        return FALSE;

    m_pImpIPersistStreamInit=new CImpIPersistStreamInit(this
        , pIUnknown);

    if (NULL==m_pImpIPersistStreamInit)
        return FALSE;
    //End CHAPTER8MOD

    m_pImpIPolyline=new CImpIPolyline(this, pIUnknown);

    if (NULL==m_pImpIPolyline)
        return FALSE;

    m_pImpIConnPtCont=new CImpIConnPtCont(this, pIUnknown);

    if (NULL==m_pImpIConnPtCont)
        return FALSE;

    m_pConnPt=new CConnectionPoint(this);

    if (NULL==m_pConnPt)
        return FALSE;

    m_pConnPt->AddRef();    //Reversed in destructor
    return TRUE;
    }

/*
 * CPolyline::QueryInterface
 * CPolyline::AddRef
 * CPolyline::Release
 */

STDMETHODIMP CPolyline::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IConnectionPointContainer==riid)
        *ppv=m_pImpIConnPtCont;

    //CHAPTER8MOD
    if (IID_IPolyline8==riid)
        *ppv=m_pImpIPolyline;

    if (IID_IPersistStorage==riid)
        *ppv=m_pImpIPersistStorage;

    if (IID_IPersist==riid || IID_IPersistStream==riid
        || IID_IPersistStreamInit==riid)
        *ppv=m_pImpIPersistStreamInit;
    //End CHAPTER8MOD

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CPolyline::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPolyline::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0L;
    }

/*
 * CPolyline::RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device (MM_TEXT) or
 *  HIMETRIC to the other.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  fToDevice       BOOL TRUE to convert from HIMETRIC to device,
 *                  FALSE to convert device to HIMETRIC.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectConvertMappings(LPRECT pRect, BOOL fToDevice)
    {
    HDC      hDC;
    int      iLpx, iLpy;

    if (NULL==pRect)
        return;

    hDC=GetDC(NULL);
    iLpx=GetDeviceCaps(hDC, LOGPIXELSX);
    iLpy=GetDeviceCaps(hDC, LOGPIXELSY);
    ReleaseDC(NULL, hDC);

    if (fToDevice)
        {
        pRect->left=MulDiv(iLpx, pRect->left, HIMETRIC_PER_INCH);
        pRect->top =MulDiv(iLpy, pRect->top , HIMETRIC_PER_INCH);

        pRect->right =MulDiv(iLpx, pRect->right, HIMETRIC_PER_INCH);
        pRect->bottom=MulDiv(iLpy, pRect->bottom,HIMETRIC_PER_INCH);
        }
    else
        {
        pRect->left=MulDiv(pRect->left, HIMETRIC_PER_INCH, iLpx);
        pRect->top =MulDiv(pRect->top , HIMETRIC_PER_INCH, iLpy);

        pRect->right =MulDiv(pRect->right, HIMETRIC_PER_INCH, iLpx);
        pRect->bottom=MulDiv(pRect->bottom,HIMETRIC_PER_INCH, iLpy);
        }

    return;
    }


POLYLINE.H

/*
 * POLYLINE.H
 * Polyline Component Chapter 8
 *
 * Definitions and function prototypes
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _POLYLINE_H_
#define _POLYLINE_H_

#define INC_CLASSLIB
#define INC_CONTROLS
#define CHAPTER8
#include <inole.h>
//CHAPTER8MOD
#include <ipoly8.h>
#include "resource.h"
//End CHAPTER8MOD

//Classname
#define SZCLASSPOLYLINE             TEXT("polyline")

//CHAPTER8MOD
//Stream Name that holds the data
#define SZSTREAM                    OLETEXT("CONTENTS")
//End CHAPTER8MOD

#define HIMETRIC_PER_INCH           2540
#define CCHPATHMAX                  256

//Window extra bytes and offsets
#define CBPOLYLINEWNDEXTRA          (sizeof(LONG))
#define PLWL_STRUCTURE              0

//DLLPOLY.CPP
int PASCAL LibMain(HINSTANCE, WORD, WORD, LPSTR);

//This class factory object creates Polyline objects.

class CPolylineClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;

    public:
        CPolylineClassFactory(void);
        ~CPolylineClassFactory(void);

        //IUnknown members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP         CreateInstance(LPUNKNOWN, REFIID
                                 , PPVOID);
        STDMETHODIMP         LockServer(BOOL);
    };

typedef CPolylineClassFactory *PCPolylineClassFactory;

//POLYWIN.CPP
LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM, LPARAM);

//Forward class references
class CImpIPolyline;
typedef class CImpIPolyline *PCImpIPolyline;

class CImpIConnPtCont;
typedef CImpIConnPtCont *PCImpIConnPtCont;

class CConnectionPoint;
typedef CConnectionPoint *PCConnectionPoint;

//CHAPTER8MOD
class CImpIPersistStorage;
typedef class CImpIPersistStorage *PCImpIPersistStorage;

class CImpIPersistStreamInit;
typedef class CImpIPersistStreamInit *PCImpIPersistStreamInit;
//End CHAPTER8MOD

//POLYLINE.CPP
class CPolyline : public IUnknown
    {
    friend LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM
        , LPARAM);

    friend class CImpIPolyline;
    friend class CImpIConnPtCont;
    friend class CConnectionPoint;
    //CHAPTER8MOD
    friend class CImpIPersistStorage;
    friend class CImpIPersistStreamInit;
    //End CHAPTER8MOD

    protected:
        HWND            m_hWnd;
        HINSTANCE       m_hInst;

        ULONG           m_cRef;         //Object reference count
        LPUNKNOWN       m_pUnkOuter;    //Controlling Unknown
        PFNDESTROYED    m_pfnDestroy;   //Function called on closure
        BOOL            m_fDirty;       //Have we changed?
        POLYLINEDATA    m_pl;           //Our actual data

        //Contained interfaces
        PCImpIPolyline          m_pImpIPolyline;
        PCImpIConnPtCont        m_pImpIConnPtCont;
        //CHAPTER8MOD
        PCImpIPersistStorage    m_pImpIPersistStorage;
        PCImpIPersistStreamInit m_pImpIPersistStreamInit;
        //End CHAPTER8MOD

        /*
         * Connected sink:  only one per object.  We get this
         * through our connection point.
         */
        //CHAPTER8MOD
        IPolylineAdviseSink8   *m_pAdv;
        //End CHAPTER8MOD
        PCConnectionPoint       m_pConnPt;

        //CHAPTER8MOD
        PCStringTable   m_pST;          //Object strings
        UINT            m_cf;           //Object clipboard format
        CLSID           m_clsID;        //Current CLSID

        //We have to hold these for IPersistStorage::Save
        LPSTORAGE       m_pIStorage;
        LPSTREAM        m_pIStream;
        //End CHAPTER8MOD

    protected:
        void      PointScale(LPRECT, LPPOINTS, BOOL);
        void      Draw(HDC, BOOL, BOOL);
        void      RectConvertMappings(LPRECT, BOOL);

    public:
        CPolyline(LPUNKNOWN, PFNDESTROYED, HINSTANCE);
        ~CPolyline(void);

        BOOL      Init(void);

        //Non-delegating object IUnknown
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
    };

typedef CPolyline *PCPolyline;

//Interface implementations contained in the Polyline.

//CHAPTER8MOD
class CImpIPolyline : public IPolyline8
//End Chapter8MOD
    {
    protected:
        ULONG               m_cRef;      //Interface ref count
        PCPolyline          m_pObj;      //Backpointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIPolyline(PCPolyline, LPUNKNOWN);
        ~CImpIPolyline(void);

        //IUnknown members.
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //CHAPTER8MOD
        //ReadFromFile and WriteToFile replaced with IPersistStorage.
        //End CHAPTER8MOD

        //Data transfer members:
        STDMETHODIMP DataSet(PPOLYLINEDATA, BOOL, BOOL);
        STDMETHODIMP DataGet(PPOLYLINEDATA);
        STDMETHODIMP DataSetMem(HGLOBAL, BOOL, BOOL, BOOL);
        STDMETHODIMP DataGetMem(HGLOBAL *);

        STDMETHODIMP RenderBitmap(HBITMAP *);
        STDMETHODIMP RenderMetafile(HMETAFILE *);
        STDMETHODIMP RenderMetafilePict(HGLOBAL *);

        //Manipulation members:
        STDMETHODIMP Init(HWND, LPRECT, DWORD, UINT);
        STDMETHODIMP New(void);
        STDMETHODIMP Undo(void);
        STDMETHODIMP Window(HWND *);

        STDMETHODIMP RectGet(LPRECT);
        STDMETHODIMP SizeGet(LPRECT);
        STDMETHODIMP RectSet(LPRECT, BOOL);
        STDMETHODIMP SizeSet(LPRECT, BOOL);

        STDMETHODIMP ColorSet(UINT, COLORREF, COLORREF *);
        STDMETHODIMP ColorGet(UINT, COLORREF *);

        STDMETHODIMP LineStyleSet(UINT, UINT *);
        STDMETHODIMP LineStyleGet(UINT *);
    };

class CImpIConnPtCont : public IConnectionPointContainer
    {
    private:
        ULONG               m_cRef;      //Interface ref count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIConnPtCont(PCPolyline, LPUNKNOWN);
        ~CImpIConnPtCont(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(DWORD) AddRef(void);
        STDMETHODIMP_(DWORD) Release(void);

        //IConnectionPointContainer members
        STDMETHODIMP EnumConnectionPoints(IEnumConnectionPoints **);
    STDMETHODIMP FindConnectionPoint(REFIID, IConnectionPoint **);
    };

/*
 * This connection point will only allow one connection,
 * so it always returns the same connection key.  It also
 * doesn't bother to implement enumerations.
 */

#define CCONNMAX    1
#define ADVISEKEY   72388       //Arbitrary

class CConnectionPoint : public IConnectionPoint
    {
    private:
        ULONG           m_cRef;     //Object reference count
        PCPolyline      m_pObj;     //Containing object.

    public:
        CConnectionPoint(PCPolyline);
        ~CConnectionPoint(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IConnectionPoint members
        STDMETHODIMP GetConnectionInterface(IID *);
        STDMETHODIMP GetConnectionPointContainer
            (IConnectionPointContainer **);
        STDMETHODIMP Advise(LPUNKNOWN, DWORD *);
        STDMETHODIMP Unadvise(DWORD);
        STDMETHODIMP EnumConnections(IEnumConnections **);
    };

//CHAPTER8MOD

class CImpIPersistStorage : public IPersistStorage
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown
        PSSTATE             m_psState;   //Storage state

    public:
        CImpIPersistStorage(PCPolyline, LPUNKNOWN);
        ~CImpIPersistStorage(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP InitNew(LPSTORAGE);
        STDMETHODIMP Load(LPSTORAGE);
        STDMETHODIMP Save(LPSTORAGE, BOOL);
        STDMETHODIMP SaveCompleted(LPSTORAGE);
        STDMETHODIMP HandsOffStorage(void);
    };

class CImpIPersistStreamInit : public IPersistStreamInit
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIPersistStreamInit(PCPolyline, LPUNKNOWN);
        ~CImpIPersistStreamInit(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP Load(LPSTREAM);
        STDMETHODIMP Save(LPSTREAM, BOOL);
        STDMETHODIMP GetSizeMax(ULARGE_INTEGER *);
        STDMETHODIMP InitNew(void);
    };

//End CHAPTER8MOD

#endif  //_POLYLINE_H_


POLYWIN.CPP

/*
 * POLYWIN.CPP
 * Polyline Component Chapter 8
 *
 * Window procedure for the polyline drawing window and support
 * functions.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * PolylineWndProc
 *
 * Purpose:
 *  Window procedure for the polyline drawing window.
 */

LRESULT APIENTRY PolylineWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCPolyline      ppl;
    PAINTSTRUCT     ps;
    HDC             hDC;
    POINTS          pt;
    RECT            rc;

    ppl=(PCPolyline)GetWindowLong(hWnd, PLWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppl=(PCPolyline)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PLWL_STRUCTURE, (LONG)ppl);

            //New repaints this window, so store the hWnd here.
            ppl->m_hWnd=hWnd;
            ppl->m_pImpIPolyline->New();
            break;

        case WM_PAINT:
            hDC=BeginPaint(hWnd, &ps);
            ppl->Draw(hDC, FALSE, TRUE);
            EndPaint(hWnd, &ps);
            break;

        case WM_LBUTTONDOWN:
            //Stop if we are already at the limit.
            if (CPOLYLINEPOINTS==ppl->m_pl.cPoints)
                {
                MessageBeep(0);
                break;
                }

            //Convert the points into 0-32767 range
            GetClientRect(hWnd, &rc);
            pt=MAKEPOINTS(lParam);
            ppl->PointScale(&rc, &pt, FALSE);

            ppl->m_pl.rgpt[ppl->m_pl.cPoints++]=pt;

            //Draw the lines to this new point only.
            hDC=GetDC(hWnd);
            ppl->Draw(hDC, FALSE, FALSE);
            ReleaseDC(hWnd, hDC);

            if (NULL!=ppl->m_pAdv)
                ppl->m_pAdv->OnPointChange();

            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPolyline::Draw
 *
 * Purpose:
 *  Paints the current line in the polyline window.
 *
 * Parameters:
 *  hDC             HDC to draw on, a metafile or printer DC.
 *  fMetafile       BOOL indicating if hDC is a metafile or not,
 *                  so we can avoid operations that RIP.
 *  fEntire         BOOL indicating if we should draw the entire
 *                  figure or not.
 *
 * Return Value:
 *  None
 */

void CPolyline::Draw(HDC hDC, BOOL fMetafile, BOOL fEntire)
    {
    HBRUSH          hBrush;
    HPEN            hPen;
    HGDIOBJ         hObj1, hObj2;
    UINT            i, j;
    UINT            uMM;
    POINTS          pt1, pt2;
    POINT           rgpt[CPOLYLINEPOINTS];
    RECT            rc;

    GetClientRect(m_hWnd, &rc);

    /*
     * Make a 32-bit copy of the points in the POLYLINE itself
     * so we can use DPtoLP and LPtoDP.
     */
    for (i=0; i < m_pl.cPoints; i++)
        {
        rgpt[i].x=m_pl.rgpt[i].x;
        rgpt[i].y=m_pl.rgpt[i].y;
        }

    /*
     * If the mapping mode is not MM_TEXT, convert the points to
     * whatever mapping mode in in effect before drawing.
     * This specifically supports metafiles in MM_ANISOTROPIC.
     */
    uMM=fMetafile ? MM_TEXT : GetMapMode(hDC);

    if (MM_TEXT!=uMM)
        DPtoLP(hDC, rgpt, m_pl.cPoints);

    hPen=CreatePen(m_pl.iLineStyle, 1, m_pl.rgbLine);
    hObj1=SelectObject(hDC, hPen);

    hBrush=CreateSolidBrush(m_pl.rgbBackground);
    hObj2=SelectObject(hDC, hBrush);
    SetBkColor(hDC, m_pl.rgbBackground);

    /*
     * Either draw the entire figure or just a single point.  The
     * entire figure also includes erasing the background
     * completely, since hDC may be a metafile DC.  Drawing a single
     * point just updates the figure for that new point.
     */
    if (fEntire || 0==m_pl.cPoints)
        {
        //Erase the background for bitmaps and metafiles.
        SelectObject(hDC, GetStockObject(NULL_PEN));
        Rectangle(hDC, rc.left, rc.top, rc.right+1, rc.bottom+1);
        SelectObject(hDC, hPen);

        /*
         * If we are drawing the entire figure, then loop through
         * each point drawing a line to each successive point.
         */

        for (i=0; i < m_pl.cPoints; i++)
            {
            for (j=i; j < m_pl.cPoints; j++)
                {
                pt1.x=(short)rgpt[i].x;
                pt1.y=(short)rgpt[i].y;
                pt2.x=(short)rgpt[j].x;
                pt2.y=(short)rgpt[j].y;

                PointScale(&rc, &pt1, TRUE);
                PointScale(&rc, &pt2, TRUE);
                MoveToEx(hDC, pt1.x, pt1.y, NULL);
                LineTo(hDC, pt2.x, pt2.y);
                }
            }
        }
    else
        {
        /*
         * If we are only drawing the last point, just cycle once
         * through previous points.
         */

        //Get the last point entered in the array.
        j=m_pl.cPoints-1;
        pt1.x=(short)rgpt[j].x;
        pt1.y=(short)rgpt[j].y;
        PointScale(&rc, &pt1, TRUE);

        for (i=0; i < j; i++)
            {
            pt2.x=(short)rgpt[i].x;
            pt2.y=(short)rgpt[i].y;
            PointScale(&rc, &pt2, TRUE);

            MoveToEx(hDC, pt1.x, pt1.y, NULL);
            LineTo(hDC, pt2.x, pt2.y);
            }
        }

    //If we have one point, draw a dot to indicate it's position.
    if (1==m_pl.cPoints)
        {
        pt1.x=(short)rgpt[0].x;
        pt1.y=(short)rgpt[0].y;
        PointScale(&rc, &pt1, TRUE);
        SetPixel(hDC, pt1.x, pt1.y, m_pl.rgbLine);
        }

    //Restore original points.
    if (MM_TEXT!=uMM)
        LPtoDP(hDC, rgpt, m_pl.cPoints);

    SelectObject(hDC, hObj1);
    SelectObject(hDC, hObj2);
    DeleteObject(hBrush);
    DeleteObject(hPen);
    return;
    }

/*
 * CPolyline::PointScale
 *
 * Purpose:
 *  Scales a point to or from a relative window coordinate to a
 *  0-32767 coordinate.
 *
 * Parameters:
 *  pRect           LPRECT of the window.
 *  ppt             LPPOINTS to convert
 *  fScaleToWindow  BOOL indicating direction of scaling.
 *
 * Return Value:
 *  None
 */

void CPolyline::PointScale(LPRECT pRect, LPPOINTS ppt
    , BOOL fScaleToWindow)
    {
    DWORD   cx, cy;

    //Window size
    cx=(DWORD)(pRect->right-pRect->left);
    cy=(DWORD)(pRect->bottom-pRect->top);

    //Prevent crashes
    if (0L==cx) cx=1;
    if (0L==cy) cy=1;

    //Must use DWORD to insure proper scaling.
    if (fScaleToWindow)
        {
        ppt->x=(UINT)(((DWORD)ppt->x*cx) >> 15);
        ppt->y=(UINT)(((DWORD)ppt->y*cy) >> 15);
        }
    else
        {
        ppt->x=(UINT)(((DWORD)ppt->x << 15)/cx);
        ppt->y=(UINT)(((DWORD)ppt->y << 15)/cy);
        }

    return;
    }


RESOURCE.H

/*
 * RESOURCE.H
 * Polyline Component Chapter 8
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Object stringtable IDs
#define IDS_POLYLINEMIN     0
#define IDS_STORAGEFORMAT   0
#define IDS_USERTYPE        1
#define IDS_POLYLINEMAX     1

#endif //_RESOURCE_H_


IDESCRIP.IDL

/*
 * IDESCRIP.IDL
 *
 * Definition of the IDescription interface for the MIDL compiler.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

[uuid(00021152-0000-0000-c000-000000000046),
    object,
    pointer_default(unique)
]
interface IDescription : IUnknown
    {
    import "unknwn.idl";

    HRESULT GetText([in, out, size_is(cch)] LPOLESTR pszText, [in] ULONG cch);
    }


LIBMAIN.CPP

/*
 * LIBMAIN.CPP
 * IDescription Interface DLL Chapter 9
 *
 * Entry point code
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include <windows.h>

/*
 * LibMain(32)
 *
 * Purpose:
 *  Entry point for Win32 DLLs
 */

BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    if (DLL_PROCESS_DETACH==ulReason)
        {
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    return TRUE;
    }


CONTITEM.CPP

/*
 * CONTITEM.CPP
 * Container Item Object for Link Source, Chapter 9
 *
 * Implementation of an item object that is itself a container
 * of other items.  It implements IDescription and
 * IOleItemContainer using the shared implemenations in
 * IDESCRIP.CPP and IOLECONT.CPP
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "linksrc.h"

#ifdef WIN32ANSI
/*
 * This is to turn off the mapping to ANSI wrapper APIs because
 * we're actually using wide char strings under Win32 all the time
 * in parts of this code.
 */
#undef CreateItemMoniker
#define CreateItemMoniker CreateItemMoniker

#endif

/*
 * CContainerItem::CContainerItem
 * CContainerItem::~CContainerItem
 *
 * Parameters (Constructor):
 *  pUnkParent      LPUNKNOWN to the parent in which this
 *                  item lives
 *  pfnDestroy      PFNDESTROYED to call when an object
 *                  is destroyed.
 */

CContainerItem::CContainerItem(LPUNKNOWN pUnkParent
    , PFNDESTROYED pfnDestroy)
    {
    m_cRef=0;

    //This keeps the parent storage open as well
    m_pUnkParent=pUnkParent;
    pUnkParent->AddRef();

    m_pfnDestroy=pfnDestroy;

    m_pIStorage=NULL;
    m_pmk=NULL;
    m_dwRegROT=0;

    m_pImpIOleItemContainer=NULL;
    m_pImpIDescription=NULL;

    return;
    }

CContainerItem::~CContainerItem(void)
    {
    //Remove us from the running object table
    if (0!=m_dwRegROT)
        {
        IRunningObjectTable    *pROT;

        if (SUCCEEDED(GetRunningObjectTable(0, &pROT)))
            {
            pROT->Revoke(m_dwRegROT);
            pROT->Release();
            }
        }

    ReleaseInterface(m_pmk);
    ReleaseInterface(m_pIStorage);
    DeleteInterfaceImp(m_pImpIDescription);
    DeleteInterfaceImp(m_pImpIOleItemContainer);

    ReleaseInterface(m_pUnkParent);
    return;
    }

/*
 * CContainerItem::Init
 *
 * Purpose:
 *  Performs any intiailization of a CContainerItem that's prone to
 *  failure that we also use internally before exposing the object
 *  outside.
 *
 * Parameters:
 *  pmkLeft         IMoniker * of our containing object.
 *  pbc             IBindCtx * to use to register ourselves
 *                  as running.
 *  pszItem         LPOLESTR naming this object.
 *  pIStorage       IStorage * to our information.
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CContainerItem::Init(IMoniker *pmkLeft, IBindCtx *pbc
    , LPOLESTR pszItem, IStorage *pIStorage)
    {
    OLECHAR     szDelim[]=OLETEXT("!");
    HRESULT     hr=ResultFromScode(S_FALSE);
    IMoniker   *pmkItem;

    m_pImpIOleItemContainer=new CImpIOleItemContainer(this, this
        , FALSE);

    if (NULL==m_pImpIOleItemContainer)
        return FALSE;

    m_pImpIDescription=new CImpIDescription(this);

    if (NULL==m_pImpIDescription)
        return FALSE;

    m_pIStorage=pIStorage;
    m_pImpIDescription->SetStorage(m_pIStorage);

    /*
     * Create an item moniker for ourselves and register as
     * running.  Failure here is not critical.
     */
    if (FAILED(CreateItemMoniker(szDelim, pszItem, &pmkItem)))
        return TRUE;

    //Create a composite for ourselves
    if (SUCCEEDED(pmkLeft->ComposeWith(pmkItem, FALSE, &m_pmk)))
        {
        IRunningObjectTable *pROT;

        if (SUCCEEDED(pbc->GetRunningObjectTable(&pROT)))
            {
            pROT->Register(0, this, m_pmk, &m_dwRegROT);
            pROT->Release();
            }
        }

    pmkItem->Release();
    return TRUE;
    }

/*
 * CContainerItem::QueryInterface
 * CContainerItem::AddRef
 * CContainerItem::Release
 *
 * Purpose:
 *  IUnknown members for CContainerItem object.
 */

STDMETHODIMP CContainerItem::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IParseDisplayName==riid || IID_IOleContainer==riid
        || IID_IOleItemContainer==riid)
        *ppv=m_pImpIOleItemContainer;

    if (IID_IDescription==riid)
        *ppv=m_pImpIDescription;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CContainerItem::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CContainerItem::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0;
    }


FILEOBJ.CPP

/*
 * FILEOBJ.CPP
 * File Object for Link Source, Chapter 9
 *
 * Implementation of a "file" object named with a file moniker,
 * which implements IPersistFile, IOleItemContainer, and
 * IDescription.  The latter two interfaces are implemented
 * generically in IOLECONT.CPP and IDESCRIP.CPP.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "linksrc.h"

#ifdef WIN32ANSI
/*
 * This is to turn off the mapping to ANSI wrapper APIs because
 * we're actually using wide char strings under Win32 all the time
 * in parts of this code.
 */
#undef StgOpenStorage
#define StgOpenStorage StgOpenStorage

#undef CreateFileMoniker
#define CreateFileMoniker CreateFileMoniker
#endif

/*
 * CFileObject::CFileObject
 * CFileObject::~CFileObject
 *
 * Parameters (Constructor):
 *  pUnkOuter       LPUNKNOWN of a controlling unknown.
 *  pfnDestroy      PFNDESTROYED to call when an object
 *                  is destroyed.
 */

CFileObject::CFileObject(LPUNKNOWN pUnkOuter
    , PFNDESTROYED pfnDestroy)
    {
    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pfnDestroy=pfnDestroy;

    m_clsID=CLSID_LinkedFile;
    m_szFile[0]=(OLECHAR)0;
    m_pIStorage=NULL;
    m_pmk=NULL;
    m_dwRegROT=0;

    m_pImpIPersistFile=NULL;
    m_pImpIOleItemContainer=NULL;
    m_pImpIDescription=NULL;

    return;
    }

CFileObject::~CFileObject(void)
    {
    //Remove us from the running object table
    if (0!=m_dwRegROT)
        {
        IRunningObjectTable    *pROT;

        if (SUCCEEDED(GetRunningObjectTable(0, &pROT)))
            {
            pROT->Revoke(m_dwRegROT);
            pROT->Release();
            }
        }

    ReleaseInterface(m_pmk);
    ReleaseInterface(m_pIStorage);
    DeleteInterfaceImp(m_pImpIDescription);
    DeleteInterfaceImp(m_pImpIOleItemContainer);
    DeleteInterfaceImp(m_pImpIPersistFile);
    return;
    }

/*
 * CFileObject::Init
 *
 * Purpose:
 *  Performs any intiailization of a CFileObject that's prone to
 *  failure that we also use internally before exposing the object
 *  outside.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CFileObject::Init(void)
    {
    IUnknown   *pUnk=this;

    if (NULL!=m_pUnkOuter)
        pUnk=m_pUnkOuter;

    m_pImpIPersistFile=new CImpIPersistFile(this, pUnk);

    if (NULL==m_pImpIPersistFile)
        return FALSE;

    m_pImpIOleItemContainer=new CImpIOleItemContainer(this, pUnk
        , TRUE);

    if (NULL==m_pImpIOleItemContainer)
        return FALSE;

    m_pImpIDescription=new CImpIDescription(pUnk);

    if (NULL==m_pImpIDescription)
        return FALSE;

    return TRUE;
    }

/*
 * CFileObject::QueryInterface
 * CFileObject::AddRef
 * CFileObject::Release
 *
 * Purpose:
 *  IUnknown members for CFileObject object.
 */

STDMETHODIMP CFileObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IPersist==riid || IID_IPersistFile==riid)
        *ppv=m_pImpIPersistFile;

    if (IID_IParseDisplayName==riid || IID_IOleContainer==riid
        || IID_IOleItemContainer==riid)
        *ppv=m_pImpIOleItemContainer;

    if (IID_IDescription==riid)
        *ppv=m_pImpIDescription;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CFileObject::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CFileObject::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0;
    }

/**
 ** IPersistFile implementation
 **/

/*
 * CImpIPersistFile:CImpIPersistFile
 * CImpIPersistFile::~CImpIPersistFile
 * CImpIPersistFile::QueryInterface
 * CImpIPersistFile::AddRef
 * CImpIPersistFile::Release
 *
 * Basic object members.
 */

CImpIPersistFile::CImpIPersistFile(PCFileObject pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPersistFile::~CImpIPersistFile(void)
    {
    return;
    }

STDMETHODIMP CImpIPersistFile::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistFile::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistFile::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistFile::GetClassID
 * CImpIPersistFile::IsDirty
 * CImpIPersistFile::Save
 * CImpIPersistFile::SaveCompleted
 *
 * Trivial or unimplemented members.
 */

STDMETHODIMP CImpIPersistFile::GetClassID(LPCLSID pClsID)
    {
    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

STDMETHODIMP CImpIPersistFile::IsDirty(void)
    {
    //We're never dirty
    return ResultFromScode(S_FALSE);
    }

STDMETHODIMP CImpIPersistFile::Save(LPCOLESTR pszFile, BOOL fRemember)
    {
    return ResultFromScode(E_NOTIMPL);
    }

STDMETHODIMP CImpIPersistFile::SaveCompleted(LPCOLESTR pszFile)
    {
    return NOERROR;
    }

/*
 * CImpIPersistFile::Load
 *
 * Purpose:
 *  Asks the server to load the document for the given filename.
 *
 * Parameters:
 *  pszFile         LPCOLESTR of the filename to load.
 *  grfMode         DWORD flags to use when opening the file.
 */

STDMETHODIMP CImpIPersistFile::Load(LPCOLESTR pszFile, DWORD grfMode)
    {
    const int   cch=512;
    HRESULT     hr;

    //We should only be loaded once; having a moniker tells us
    if (NULL!=m_pObj->m_pmk)
        return ResultFromScode(E_UNEXPECTED);

    /*
     * Since the server is single-use, we can be assured that
     * this Load will only happen once, so we can hold the
     * IStorage until the object is released.
     */
    hr=StgOpenStorage(pszFile, NULL
        , STGM_TRANSACTED | STGM_READ | STGM_SHARE_DENY_WRITE, NULL, 0
        , &m_pObj->m_pIStorage);

    if (FAILED(hr))
        return hr;

    m_pObj->m_pImpIDescription->SetStorage(m_pObj->m_pIStorage);

    /*
     * We opened it successfully, and that's all we have to
     * do, so we can simply save the filename.
     */
   #ifdef WIN32ANSI
    wcsncpy(m_pObj->m_szFile, pszFile, cch);
   #else
    lstrcpyn(m_pObj->m_szFile, pszFile, cch);
   #endif

    /*
     * Create a moniker for this file object and register as
     * running.
     */
    if (SUCCEEDED(CreateFileMoniker(pszFile, &m_pObj->m_pmk)))
        {
        IRunningObjectTable    *pROT;

        if (SUCCEEDED(GetRunningObjectTable(0, &pROT)))
            {
            //Register as weak so clients can free us
            pROT->Register(0, m_pObj, m_pObj->m_pmk
                , &m_pObj->m_dwRegROT);
            pROT->Release();
            }
        }

    return NOERROR;
    }

/*
 * CImpIPersistFile::GetCurFile
 *
 * Purpose:
 *  Returns the current filename.
 *
 * Parameters:
 *  ppszFile        LPOLESTR * into which we store a pointer to
 *                  the filename that should be allocated with the
 *                  shared IMalloc.
 */

STDMETHODIMP CImpIPersistFile::GetCurFile(LPOLESTR *ppszFile)
    {
    LPMALLOC    pIMalloc;
    const int   cch=512;
    LPOLESTR    psz;

    *ppszFile=NULL;

    if (FAILED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        return ResultFromScode(E_FAIL);

    psz=(LPOLESTR)pIMalloc->Alloc(cch*sizeof(OLECHAR));
    pIMalloc->Release();

    if (NULL==psz)
        return ResultFromScode(E_OUTOFMEMORY);

   #ifdef WIN32ANSI
    wcsncpy(psz, m_pObj->m_szFile, cch);
   #else
    lstrcpyn(psz, m_pObj->m_szFile, cch);
   #endif
    *ppszFile=psz;
    return NOERROR;
    }


IDESCRIP.CPP

/*
 * IDESCRIP.CPP
 * Link Source Chapter 9
 *
 * Implementation the IDescription interface that doesn't care
 * what object it works with in this LinkSource sample.  It
 * is given a storage in which it expects to find a stream
 * called "Description" containing the text relevant to the
 * object showing the instance of this interface.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "linksrc.h"

/*
 * CImpIDescription:CImpIDescription
 * CImpIDescription::~CImpIDescription
 * CImpIDescription::QueryInterface
 * CImpIDescription::AddRef
 * CImpIDescription::Release
 *
 * Basic object members.
 */

CImpIDescription::CImpIDescription(LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pIStorage=NULL;
    return;
    }

CImpIDescription::~CImpIDescription(void)
    {
    return;
    }

STDMETHODIMP CImpIDescription::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIDescription::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIDescription::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIDescription::GetText
 *
 * Purpose:
 *  Fills a buffer with our text description.
 *
 * Parameters:
 *  pszText         LPOLESTR to the buffer to fill
 *  cch             ULONG specifying the length of pszText
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, error otherwise.
 */

HRESULT CImpIDescription::GetText(LPOLESTR pszText, ULONG cch)
    {
    HRESULT     hr;
    IStream    *pIStream;

    /*
     * The description text for this object is contained in
     * a stream called "Description" (constant SZDESCRIPTION
     * has this string) found in whatever storage we happen
     * to have.  This implementation is ignorant of the actual
     * object that is exposing it.
     */

    if (NULL==m_pIStorage)
        return ResultFromScode(E_FAIL);

    hr=m_pIStorage->OpenStream(SZDESCRIPTION, 0, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return ResultFromScode(E_FAIL);

   #ifdef WIN32ANSI
    char sz[512];
    hr=pIStream->Read((void *)sz, cch*sizeof(TCHAR), NULL);
MultiByteToWideChar(CP_ACP, 0, sz, -1, pszText, cch);
   #else
    hr=pIStream->Read((void *)pszText, cch*sizeof(WCHAR), NULL);
   #endif
    pIStream->Release();

    return SUCCEEDED(hr) ? NOERROR : ResultFromScode(E_FAIL);
    }

/*
 * (Internal)
 * CImpIDescription::SetStorage
 *
 * Purpose:
 *  Provides objects using this interface implementation to
 *  inform us of the storage in which to look for the
 *  description stream.
 *
 * Parameters:
 *  pIStorage       IStorage * to hold.
 *
 * Return Value:
 *  None
 */

void CImpIDescription::SetStorage(IStorage *pIStorage)
    {
    /*
     * This may be the root storage for the File object, the
     * sub-storage for the first level object, or a sub-storage
     * for a second-level object.  Since the object exposing
     * this interface holds the storage itself, no AddRef is
     * required.
     */
    m_pIStorage=pIStorage;
    return;
    }


IOLECONT.CPP

/*
 * IOLECONT.CPP
 * Link Source Server Chapter 9
 *
 * Implementation of the IOleItemContainer interface for
 * LinkSource's CFileObject and CContainerItem, using constructor
 * arguments to distinguish the relevant object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "linksrc.h"

#ifdef WIN32ANSI
/*
 * This is to turn off the mapping to ANSI wrapper APIs because
 * we're actually using wide char strings under Win32 all the time
 * in parts of this code.
 */
#undef CreateItemMoniker
#define CreateItemMoniker CreateItemMoniker

#endif

extern ULONG g_cObj;

/*
 * CImpIOleItemContainer::CImpIOleItemContainer
 * CImpIOleItemContainer::~CImpIOleItemContainer
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the page or pages.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 *  fFileObj        BOOL indicating if we're in CFileObject.
 */

CImpIOleItemContainer::CImpIOleItemContainer(LPVOID pObj
    , LPUNKNOWN pUnkOuter, BOOL fFileObj)
    {
    m_cRef=0;
    m_fFileObj=fFileObj;

    if (fFileObj)
        {
        m_pObjFile=(PCFileObject)pObj;
        m_pObjCont=NULL;
        }
    else
        {
        m_pObjFile=NULL;
        m_pObjCont=(PCContainerItem)pObj;
        }

    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleItemContainer::~CImpIOleItemContainer(void)
    {
    return;
    }

/*
 * CImpIOleItemContainer::QueryInterface
 * CImpIOleItemContainer::AddRef
 * CImpIOleItemContainer::Release
 */

STDMETHODIMP CImpIOleItemContainer::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleItemContainer::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleItemContainer::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleItemContainer::ParseDisplayName
 *
 * Purpose:
 *  Parse the given name into a moniker as far as we know how
 *  to parse.
 *
 * Parameters:
 *  pbc             LPBC to the binding context
 *  pszName         LPOLESTR to the name to parse.
 *  pchEaten        ULONG * into which to store how many
 *                  characters we scanned in the display name.
 *  ppmk            LPMONIKER * in which to return the moniker.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::ParseDisplayName(LPBC pbc
    , LPOLESTR pszName, ULONG *pchEaten, LPMONIKER *ppmk)
    {
    OLECHAR     ch;
    ULONG       chEaten=0;
    TCHAR       szName[256];
    TCHAR       szComp[15];
    LPTSTR      psz;
    UINT        cch;

    *ppmk=NULL;
    *pchEaten=0;

    /*
     * All we have to look for is the string between the !
     * delimeters (or a null terminator).  pszName should be pointing
     * to a !, so skip it and scan the string for a ! or 0,
     * then pass the result to CreateItemMoniker.
     */

    psz=szName;

    ch=*pszName++;
    chEaten++;

    if ((OLECHAR)'!'!=ch)
        return ResultFromScode(MK_E_SYNTAX);

    ch=*pszName++;

    while ((OLECHAR)0!=ch && (OLECHAR)'!' !=ch)
        {
        *psz++=(TCHAR)ch;
        chEaten++;
        ch=*pszName++;
        }

    *psz=(TCHAR)0;

    /*
     * Syntax check.  If we're the File object, check for "Object n"
     * at the beginning of the string.  Otherwise check for
     * "Sub-Object n".
     */
    lstrcpy(szComp, m_fFileObj ? TEXT("Object ")
        : TEXT("Sub-Object "));

    //Does szName start with szComp?
    cch=lstrlen(szComp);

    if (0!=_tcsncicmp(szName, szComp, cch))
        {
        *pchEaten=1;    //Parsed ! at least
        return ResultFromScode(MK_E_SYNTAX);
        }

    //Check for a number in szName
    if ((TCHAR)'0' != szName[cch])
        {
        if (0==_ttoi(szName+cch))
            {
            *pchEaten=cch;  //Got past name
            return ResultFromScode(MK_E_SYNTAX);
            }
        }

    *pchEaten=chEaten;
   #ifdef WIN32ANSI
    //Use the ANSI version here since szName is ANSI
    return INOLE_CreateItemMoniker(TEXT("!"), szName, ppmk);
   #else
    return CreateItemMoniker(OLETEXT("!"), szName, ppmk);
   #endif
    }

/*
 * CImpIOleItemContainer::EnumObjects
 *
 * Purpose:
 *  Creates and returns an IEnumUnknown object that allows the
 *  caller to walk through the objects in this continer thing.
 *
 * Parameters:
 *  dwFlags         DWORD specifying what kind of objects to
 *                  enumerate.
 *  ppEnum          LPENUMUNKNOWN * into which to return the
 *                  enumerator
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::EnumObjects(DWORD dwFlags
    , LPENUMUNKNOWN *ppEnum)
    {
    *ppEnum=NULL;

    /*
     * We can leave this unimplemented in this sample since
     * we know no one will ever call it.  A real container
     * should be able to enumerate its contents.
     */
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleItemContainer::LockContainer
 *
 * Purpose:
 *  Establishes a lock on the container to prevent it from shutting
 *  down outside of user control.  This is used to control the
 *  lifetime of the container when it's used to update a link to an
 *  embedded object within it.  If we're unlock and the user has not
 *  taken control, we close.
 *
 * Parameters:
 *  fLock           BOOL indicating a lock or unlock.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::LockContainer(BOOL fLock)
    {
    /*
     * For the purposes of this server, we need only AddRef
     * and release ourselves depending on fLock.
     */

    if (fLock)
        AddRef();
    else
        Release();

    return NOERROR;
    }

/*
 * CImpIOleItemContainer::GetObject
 *
 * Purpose:
 *  Returns the requested interface pointer on an object in this
 *  container.
 *
 * Parameters:
 *  pszItem         LPOLESTR to the item we must locate.
 *  dwSpeed         DWORD identifying how long the caller is willing
 *                  to wait.
 *  pcb             LPBINDCTX providing the binding context.
 *  riid            REFIID of the interface requested.
 *  ppv             PPVOID into which to return the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::GetObject(LPOLESTR pszItem
    , DWORD dwSpeed, LPBINDCTX pbc, REFIID riid, PPVOID ppv)
    {
    HRESULT         hr;
    IStorage       *pIStorage;
    PCContainerItem pCI;
    PCSimpleItem    pSI;
    BOOL            fSuccess;
    IUnknown       *pUnk;

    *ppv=NULL;

    /*
     * The item name given to either CFileObj or CContainerItem
     * instantiations of this interface will be the name of
     * a storage below the current object's storage.  So we
     * use m_fFileObj to know which storage to look under
     * (names are either "Object n" or "Sub-Object n").
     *
     * If that storage exists, the object exists.  If it is
     * already running, we can check in the Running Object Table
     * and just return that object.  This is the only case that
     * works when the bind speed in the bind context is
     * immediate.
     *
     * Otherwise we open the storage and hand it to a new
     * instance of the right type of object (CContainerItem or
     * CSimpleItem), and register the new objects as running.
     */

    //Get the object if running
    hr=GetRunning(pszItem, pbc, riid, ppv, FALSE);

    if (BINDSPEED_IMMEDIATE==dwSpeed && NOERROR!=hr)
        return ResultFromScode(MK_E_EXCEEDEDDEADLINE);

    //If object was running, we're done!
    if (NOERROR==hr)
        return NOERROR;

    //Otherwise we need to get the storage.
    hr=GetObjectStorage(pszItem, pbc, IID_IStorage
        , (void **)&pIStorage);

    if (FAILED(hr))
        return hr;

    /*
     * Storage exists, so create an object and give that storage
     * to it.  The new object will also register itself in the
     * Running Object Table.
     */
    fSuccess=FALSE;

    if (m_fFileObj)
        {
        pCI=new CContainerItem(m_pObjFile, m_pObjFile->m_pfnDestroy);

        pUnk=pCI;

        if (NULL!=pCI)
            {
            pUnk->AddRef();
            fSuccess=pCI->Init(m_pObjFile->m_pmk, pbc, pszItem
                , pIStorage);
            }
        }
    else
        {
        pSI=new CSimpleItem(m_pObjCont, m_pObjCont->m_pfnDestroy);

        pUnk=pSI;

        if (NULL!=pSI)
            {
            pUnk->AddRef();
            fSuccess=pSI->Init(m_pObjCont->m_pmk, pbc, pszItem
                , pIStorage);
            }
        }

    if (!fSuccess)
        {
        if (NULL!=pUnk)
            pUnk->Release();

        return ResultFromScode(E_OUTOFMEMORY);
        }

    g_cObj++;

    //If QueryInterface fails, this Release destroys the object
    hr=pUnk->QueryInterface(riid, ppv);
    pUnk->Release();

    if (FAILED(hr))
        return hr;

    return NOERROR;
    }

/*
 * CImpIOleItemContainer::GetObjectStorage
 *
 * Purpose:
 *  Similar to GetObject in that we have to locate the object
 *  described by a given name, but instead of returning any old
 *  interface we return a storage element.
 *
 * Parameters:
 *  pszItem         LPOLESTR to the item we must locate.
 *  pcb             LPBINDCTX providing the binding context.
 *  riid            REFIID of the interface requested.  Usually
 *                  IStorage or IStream.
 *  ppv             PPVOID into which to return the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::GetObjectStorage(LPOLESTR pszItem
    , LPBINDCTX pbc, REFIID riid, PPVOID ppv)
    {
    IStorage       *pIStorageObj;
    IStorage       *pIStorageNew;
    HRESULT         hr;

    if (IID_IStorage!=riid)
        return ResultFromScode(MK_E_NOSTORAGE);

    pIStorageObj=m_fFileObj ? m_pObjFile->m_pIStorage
        : m_pObjCont->m_pIStorage;

    //Check storage existence
    hr=pIStorageObj->OpenStorage(pszItem
        , NULL, STGM_TRANSACTED | STGM_READ | STGM_SHARE_EXCLUSIVE
        , NULL, 0, &pIStorageNew);

    if (FAILED(hr))
        {
        IUnknown   *pUnk;

        /*
         * Because you must open a substorage with exclusive access,
         * we'll run into a problem where an object created for use
         * with parsing will still be running when another object of
         * the same type is used for binding.  The reason is that the
         * bind context holds these thing for optimization purposes.
         * But that does mean that OpenStorage above might fail
         * with STG_E_ACCESSDENIED.  In this case, we'll try to get
         * a previously opened storage from the bind context
         * (see below).
         */
        if (STG_E_ACCESSDENIED!=GetScode(hr))
            return hr;

        if (FAILED(pbc->GetObjectParam(SZOPENSTORAGE, &pUnk)))
            return ResultFromScode(STG_E_ACCESSDENIED);

        //This does the necessary AddRef on the IStorage pointer
        hr=pUnk->QueryInterface(IID_IStorage
            , (void **)&pIStorageNew);
        pUnk->Release();

        //This sets the out-parameter to NULL on failure
        *ppv=pIStorageNew;
        return hr;
        }

    *ppv=pIStorageNew;
    pbc->RegisterObjectParam(SZOPENSTORAGE, pIStorageNew);
    return NOERROR;
    }

/*
 * CImpIOleItemContainer::IsRunning
 *
 * Purpose:
 *  Answers if the object under the given name is currently running.
 *
 * Parameters:
 *  pszItem         LPOLESTR of the item to check
 *
 * Return Value:
 *  HRESULT         NOERROR if the object is running, S_FALSE
 *                  otherwise.  Possibly MK_E_NOOBJECT if the name
 *                  is bogus.
 */

STDMETHODIMP CImpIOleItemContainer::IsRunning(LPOLESTR pszItem)
    {
    return GetRunning(pszItem, NULL, IID_NULL, NULL, TRUE);
    }

/*
 * (Internal)
 * CImpIOleItemContainer::GetRunning
 *
 * Purpose:
 *  Grabs a running object from the running object table or
 *  just checks existence.
 *
 * Parameters:
 *  pszItem         LPOLESTR of the item to check
 *  pbc             IBindCtx * to use, can be NULL.
 *  riid            REFIID to return if fCheck is FALSE.
 *  ppv             void ** in which to return a pointer.
 *  fCheck          BOOL indicating to check (TRUE) or
 *                  retrieve (FALSE).
 *
 * Return Value:
 *  HRESULT         NOERROR if the object is running, S_FALSE
 *                  or an error otherwise.
 */

HRESULT CImpIOleItemContainer::GetRunning(LPOLESTR pszItem
    , IBindCtx *pbc, REFIID riid, void **ppv, BOOL fCheck)
    {
    OLECHAR              szDelim[]=OLETEXT("!");
    HRESULT              hr=ResultFromScode(S_FALSE);
    IMoniker            *pmkBase;
    IMoniker            *pmkItem;
    IMoniker            *pmkComp;
    IRunningObjectTable *pROT;

    pmkBase=m_fFileObj ? m_pObjFile->m_pmk : m_pObjCont->m_pmk;

    if (FAILED(CreateItemMoniker(szDelim, pszItem, &pmkItem)))
        return hr;

    //Create a composite with this item
    hr=pmkBase->ComposeWith(pmkItem, FALSE, &pmkComp);

    if (FAILED(hr))
        {
        pmkItem->Release();
        return hr;
        }

    if (NULL!=pbc)
        hr=pbc->GetRunningObjectTable(&pROT);
    else
        hr=GetRunningObjectTable(0, &pROT);

    if (SUCCEEDED(hr))
        {
        hr=pROT->IsRunning(pmkComp);

        //If running, grab the object if necessary
        if (!fCheck && NOERROR==hr)
            {
            IUnknown *pUnk;

            if(SUCCEEDED(pROT->GetObject(pmkComp, &pUnk)))
                {
                hr=pUnk->QueryInterface(riid, ppv);
                pUnk->Release();
                }
            }

        pROT->Release();
        }

    pmkComp->Release();
    pmkItem->Release();
    return hr;
    }


LINKSRC.CPP

/*
 * LINKSRC.CPP
 * Link Source Server Chapter 9
 *
 * Server of various objects that can be named with monikers.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "linksrc.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;
BOOL        g_fUser=FALSE;

//Make window handle global so other code can cause a shutdown
HWND        g_hWnd=NULL;

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    MSG         msg;
    PAPP        pApp;

    SETMESSAGEQUEUE;

    pApp=new CApp(hInst, hInstPrev, pszCmdLine, nCmdShow);

    if (NULL==pApp)
        return -1;

    if (pApp->Init())
        {
        while (GetMessage(&msg, NULL, 0,0 ))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    delete pApp;
    return msg.wParam;
    }

/*
 * KoalaWndProc
 *
 * Purpose:
 *  Standard window class procedure.
 */

LRESULT APIENTRY LinkSrcWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PAPP        pApp;

    pApp=(PAPP)GetWindowLong(hWnd, LINKSRCWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pApp=(PAPP)(((LPCREATESTRUCT)lParam)->lpCreateParams);
            SetWindowLong(hWnd, LINKSRCWL_STRUCTURE, (LONG)pApp);
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));

        case WM_COMMAND:
            switch (LOWORD(wParam))
                {
                case IDM_FILECREATEGOOP:
                    pApp->CreateSampleFile(TEXT("goop.lks"));
                    break;

                case IDM_FILEEXIT:
                    PostMessage(hWnd, WM_CLOSE, 0, 0L);
                    break;

                default:
                    break;
                }

            break;

        case WM_CLOSE:
            //Don't close with object's active
            if (0==g_cObj && 0==g_cLock)
                return (DefWindowProc(hWnd, iMsg, wParam, lParam));

            break;

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the LinkSrc object to call when it gets destroyed.
 *  We destroy the main window if the proper conditions are met
 *  for shutdown.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;

    /*
     * No more objects and no locks, and user is not in
     * control (CApp::m_fEmbedding), then shut down.
     */
    if (0L==g_cObj && 0L==g_cLock && IsWindow(g_hWnd)
        && !g_fUser)
        PostMessage(g_hWnd, WM_CLOSE, 0, 0L);

    return;
    }

/*
 * CApp::CApp
 * CApp::~CApp
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the Application from WinMain
 *  hInstPrev       HINSTANCE of a previous instance from WinMain
 *  pszCmdLine      LPSTR of the command line.
 *  nCmdShow        UINT specifying how to show the app window,
 *                  from WinMain.
 */

CApp::CApp(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, UINT nCmdShow)
    {
    //Initialize WinMain parameter holders.
    m_hInst     =hInst;
    m_hInstPrev =hInstPrev;
    m_pszCmdLine=pszCmdLine;
    m_nCmdShow  =nCmdShow;

    m_hWnd=NULL;
    m_dwRegCO=0;
    m_pIClassFactory=NULL;
    m_fInitialized=FALSE;

    /*
     * We can run stand-alone or at OLE's request, but we
     * remain hidden if run from OLE.
     */
    m_fEmbedding=TRUE;

    return;
    }

CApp::~CApp(void)
    {
    //Opposite of CoRegisterClassObject; class factory ref is now 1
    if (0L!=m_dwRegCO)
        CoRevokeClassObject(m_dwRegCO);

    //The last Release, which frees the class factory.
    if (NULL!=m_pIClassFactory)
        m_pIClassFactory->Release();

    if (m_fInitialized)
        CoUninitialize();

    return;
    }

/*
 * CApp::Init
 *
 * Purpose:
 *  Initializes an CApp object by registering window classes,
 *  creating the main window, and doing anything else prone to
 *  failure.  If this function fails the caller should guarantee
 *  that the destructor is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::Init(void)
    {
    WNDCLASS    wc;
    HRESULT     hr;

    CHECKVER_COM;

    //Check if we're run stand-alone
    if (lstrcmpiA(m_pszCmdLine, "-Embedding")
        && lstrcmpiA(m_pszCmdLine, "/Embedding"))
        {
        m_fEmbedding=FALSE;
        g_fUser=TRUE;
        }

    //Call CoInitialize so we can call other Co* functions
    if (FAILED(CoInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    if (!m_hInstPrev)
        {
        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = LinkSrcWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = CBWNDEXTRA;
        wc.hInstance      = m_hInst;
        wc.hIcon          = LoadIcon(m_hInst, TEXT("Icon"));
        wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName   = MAKEINTRESOURCE(IDR_MENU);
        wc.lpszClassName  = TEXT("LinkSrc");

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

    m_hWnd=CreateWindow(TEXT("LinkSrc"), TEXT("Link Source")
        , WS_OVERLAPPEDWINDOW, 450, 35, 350, 250
        , NULL, NULL, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    //Don't show the window outside of embedding
    if (!m_fEmbedding)
        {
        ShowWindow(m_hWnd, SW_SHOW);
        UpdateWindow(m_hWnd);
        }

    g_hWnd=m_hWnd;

    /*
     * We only server file objects through a CLSID.  Other
     * items are referenced through the File object.
     */
    m_pIClassFactory=new CFileObjectFactory();

    if (NULL==m_pIClassFactory)
        return FALSE;

    //Since we hold on to this, we should AddRef it.
    m_pIClassFactory->AddRef();

    hr=CoRegisterClassObject(CLSID_LinkedFile, m_pIClassFactory
        , CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &m_dwRegCO);

    if (FAILED(hr))
        return FALSE;

    return TRUE;
    }

/*
 * CApp::CreateSampleFile
 *
 * Purpose:
 *  Creates a file with the structure we can use in binding
 *  monikers.
 *
 * Parameters:
 *  pszFile         LPTSTR to the file name to create.
 *
 * Return Value:
 *  None
 */

void CApp::CreateSampleFile(LPTSTR pszFile)
    {
    HRESULT     hr;
    IStorage   *pIStorage;

    hr=StgCreateDocfile(pszFile, STGM_DIRECT | STGM_READWRITE
        | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

    if (SUCCEEDED(hr))
        {
        UINT        i;
        TCHAR       szTemp[40];
        TCHAR       szDesc[256];

        WriteDescription(pIStorage
            , TEXT("This is a sample compound file from LinkSource that demonstrates binding to a file moniker."));

        for (i=0; i < 4; i++)
            {
            wsprintf(szTemp, TEXT("Object %d"), i);
            wsprintf(szDesc
                , TEXT("This is the first-level item named %s in the file.")
                , szTemp);

            hr=CreateStore(pIStorage, szTemp, szDesc, 5);

            if (FAILED(hr))
                break;
            }
        }

    pIStorage->SetClass(CLSID_LinkedFile);
    pIStorage->Release();

    if (FAILED(hr))
        {
        MessageBox(m_hWnd, TEXT("File creation failed.")
            , TEXT("Link Source"), MB_OK);
        }
    else
        {
        MessageBox(m_hWnd, TEXT("File created successfully.")
            , TEXT("Link Source"), MB_OK);
        }

    return;
    }

/*
 * CApp::CreateStore
 *
 * Purpose:
 *  Creates a sub-storage below pIStorage with the given name and
 *  writes a description stream using the stringtable string idsDesc.
 *
 * Parameters:
 *  pIStorage       IStorage * in which to create the storage.
 *  pszName         LPTSTR naming the storage to create.
 *  pszDesc         LPTSTR holding the description.
 *  cSubs           UINT number of sub-storages to create in this one
 *
 * Return Value:
 *  HRESULT         Result of the operation.
 */

HRESULT CApp::CreateStore(IStorage *pIStorage, LPTSTR pszName
    , LPTSTR pszDesc, UINT cSubs)
    {
    HRESULT     hr;
    IStorage   *pSub;
    TCHAR       szTemp[40];
    TCHAR       szDesc[256];
    UINT        i;

   #ifdef WIN32ANSI
    OLECHAR     szUNI[40];
    MultiByteToWideChar(CP_ACP, 0, pszName, -1, szUNI, 40);

    hr=pIStorage->CreateStorage(szUNI, STGM_DIRECT
        | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pSub);
   #else
    hr=pIStorage->CreateStorage(pszName, STGM_DIRECT
        | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pSub);
   #endif

    if (FAILED(hr))
        return hr;

    hr=WriteDescription(pSub, pszDesc);

    if (cSubs > 255)
        cSubs=255;

    /*
     * If cSubs is zero we'll pass to the return statement
     * which allows recursion in the loop below for 2nd level storages.
     */
    for (i=0; i < cSubs; i++)
        {
        wsprintf(szTemp, TEXT("Sub-Object %d"), i);
        wsprintf(szDesc
            , TEXT("This is the second-level item named %s inside the item named %s in the file.")
            , szTemp, pszName);

        hr=CreateStore(pSub, szTemp, szDesc, 0);

        if (FAILED(hr))
            break;
        }

    pSub->Release();
    return hr;
    }

/*
 * CApp::WriteDescription
 *
 * Purpose:
 *  Creates a stream with the name SZDESCRIPTION in the given
 *  storage and writes into that stream the string identified
 *  by idsDesc that exists in the stringtable.
 *
 * Parameters:
 *  pIStorage       IStorage * in which to create the stream.
 *  pszDesc         LPTSTR describing the storage.
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code.
 */

HRESULT CApp::WriteDescription(IStorage *pIStorage, LPTSTR pszDesc)
    {
    HRESULT     hr;
    IStream    *pIStream;

    hr=pIStorage->CreateStream(SZDESCRIPTION, STGM_DIRECT
        | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pIStream);

    if (FAILED(hr))
        return hr;

    hr=pIStream->Write(pszDesc
        , (lstrlen(pszDesc)+1)*sizeof(TCHAR), NULL);
    pIStream->Release();
    return hr;
    }

/*
 * CFileObjectFactory::CFileObjectFactory
 * CFileObjectFactory::~CFileObjectFactory
 * CFileObjectFactory::QueryInterface
 * CFileObjectFactory::AddRef
 * CFileObjectFactory::Release
 *
 * Basic class factory members
 */

CFileObjectFactory::CFileObjectFactory(void)
    {
    m_cRef=0L;
    return;
    }

CFileObjectFactory::~CFileObjectFactory(void)
    {
    return;
    }

STDMETHODIMP CFileObjectFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CFileObjectFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CFileObjectFactory::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CFileObjectFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a LinkSrc object returning an interface pointer.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we are
 *                  being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_NOINTERFACE
 *                  if we cannot support the requested interface.
 */

STDMETHODIMP CFileObjectFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCFileObject        pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    pObj=new CFileObject(pUnkOuter, ObjectDestroyed);

    if (NULL==pObj)
        {
        //This starts shutdown if there are no other objects.
        g_cObj++;
        ObjectDestroyed();
        return hr;
        }

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    g_cObj++;

    if (FAILED(hr))
        {
        delete pObj;
        ObjectDestroyed();  //Handle shutdown cases.
        }

    return hr;
    }

/*
 * CFileObjectFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the serving
 *  IClassFactory object.  When the number of locks goes to
 *  zero and the number of objects is zero, we shut down the
 *  application.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CFileObjectFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        {
        g_cLock--;
        g_cObj++;
        ObjectDestroyed();
        }

    return NOERROR;
    }


LINKSRC.H

/*
 * LINKSRC.H
 * Link Source Server Chapter 9
 *
 * Definitions, classes, and prototypes for an application that
 * serves file objects and support moniker binding.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _LINKSRC_H_
#define _LINKSRC_H_

#include "objects.h"

#define IDR_MENU            1

#define IDM_FILECREATEGOOP  100
#define IDM_FILEEXIT        101

//Name of streams in the source file that holds description text.
#define SZDESCRIPTION       OLETEXT("Description")

//LINKSRC.CPP
LRESULT APIENTRY LinkSrcWndProc(HWND, UINT, WPARAM, LPARAM);

class CApp
    {
    friend LRESULT APIENTRY LinkSrcWndProc(HWND, UINT, WPARAM, LPARAM);

    protected:
        HINSTANCE       m_hInst;            //WinMain parameters
        HINSTANCE       m_hInstPrev;
        LPSTR           m_pszCmdLine;
        UINT            m_nCmdShow;

        HWND            m_hWnd;             //Main window handle

        BOOL            m_fEmbedding;       //-Embedding found?
        BOOL            m_fInitialized;     //Did CoInitialize work?
        LPCLASSFACTORY  m_pIClassFactory;   //Our class factory
        DWORD           m_dwRegCO;          //Registration key

    public:
        CApp(HINSTANCE, HINSTANCE, LPSTR, UINT);
        ~CApp(void);
        BOOL Init(void);

        void     CreateSampleFile(LPTSTR);
        HRESULT  CreateStore(IStorage *, LPTSTR, LPTSTR, UINT);
        HRESULT  WriteDescription(IStorage *, LPTSTR);
    };

typedef CApp *PAPP;

#define CBWNDEXTRA              sizeof(PAPP)
#define LINKSRCWL_STRUCTURE     0

void ObjectDestroyed(void);

//LINKSRC.CPP

class CFileObjectFactory : public IClassFactory
    {
    protected:
        ULONG                       m_cRef;

    public:
        CFileObjectFactory(void);
        ~CFileObjectFactory(void);

        //IUnknown members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP CreateInstance(LPUNKNOWN, REFIID, PPVOID);
        STDMETHODIMP LockServer(BOOL);
    };

typedef CFileObjectFactory *PCFileObjectFactory;

#endif //_LINKSRC_H_


OBJECTS.H

/*
 * OBJECTS.H
 * File and Item Objects for Link Source, Chapter 9
 *
 * Classes to define the file and item objects supplied by
 * this server as well as their interfaces.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _OBJECTS_H_
#define _OBJECTS_H_

#define CHAPTER9
#include <inole.h>
#include <idescrip.h>

//Shared implementations
class CImpIOleItemContainer;
typedef CImpIOleItemContainer *PCImpIOleItemContainer;

class CImpIDescription;
typedef CImpIDescription *PCImpIDescription;

//FILEOBJ.CPP

class CImpIPersistFile;
typedef CImpIPersistFile *PCImpIPersistFile;

class CFileObject : public IUnknown
    {
    friend class CImpIPersistFile;
    friend class CImpIOleItemContainer;
    friend class CImpIDescription;

    protected:
        ULONG           m_cRef;         //Object reference count
        LPUNKNOWN       m_pUnkOuter;    //Controlling unknown
        PFNDESTROYED    m_pfnDestroy;   //To call on closure

        CLSID           m_clsID;        //Object identity
        OLECHAR         m_szFile[512];  //Current filename
        IStorage       *m_pIStorage;    //Opened file
        IMoniker       *m_pmk;          //Our name
        DWORD           m_dwRegROT;     //ROT registration

        //Interfaces
        PCImpIPersistFile         m_pImpIPersistFile;
        PCImpIOleItemContainer    m_pImpIOleItemContainer;
        PCImpIDescription         m_pImpIDescription;

    public:
        CFileObject(LPUNKNOWN, PFNDESTROYED);
        ~CFileObject(void);

        BOOL Init(void);

        //Non-delegating object IUnknown
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
    };

typedef CFileObject *PCFileObject;

class CImpIPersistFile : public IPersistFile
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        PCFileObject    m_pObj;      //Backpointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIPersistFile(PCFileObject, LPUNKNOWN);
        ~CImpIPersistFile(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);
        STDMETHODIMP IsDirty(void);
        STDMETHODIMP Load(LPCOLESTR, DWORD);
        STDMETHODIMP Save(LPCOLESTR, BOOL);
        STDMETHODIMP SaveCompleted(LPCOLESTR);
        STDMETHODIMP GetCurFile(LPOLESTR *);
    };

//CONTITEM.CPP

/*
 * Implementation of an item object that itself contains other
 * items, thus it implements IOleItemContainer.  IDescription is
 * added only for reason of this sample.
 */

class CContainerItem : public IUnknown
    {
    friend class CImpIOleItemContainer;
    friend class CImpIDescription;

    protected:
        ULONG           m_cRef;         //Object reference count
        LPUNKNOWN       m_pUnkParent;   //CFileObject's pointer
        PFNDESTROYED    m_pfnDestroy;   //To call on closure
        IStorage       *m_pIStorage;    //Our storage
        IMoniker       *m_pmk;          //Our name
        DWORD           m_dwRegROT;     //ROT Registration

        //Interfaces
        PCImpIOleItemContainer    m_pImpIOleItemContainer;
        PCImpIDescription         m_pImpIDescription;

    public:
        CContainerItem(LPUNKNOWN, PFNDESTROYED);
        ~CContainerItem(void);

        BOOL Init(IMoniker *, IBindCtx *, LPOLESTR, IStorage *);

        //Non-delegating object IUnknown
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
    };

typedef CContainerItem *PCContainerItem;

//SIMPITEM.CPP

/*
 * Implementation of a simple item object with only IDescription.
 */

class CSimpleItem : public IUnknown
    {
    friend class CImpIDescription;

    protected:
        ULONG           m_cRef;         //Object reference count
        LPUNKNOWN       m_pUnkParent;   //CContainerItems's pointer
        PFNDESTROYED    m_pfnDestroy;   //To call on closure
        IStorage       *m_pIStorage;    //Our storage
        IMoniker       *m_pmk;          //Our name
        DWORD           m_dwRegROT;     //ROT Registration

        //Interfaces
        PCImpIDescription         m_pImpIDescription;

    public:
        CSimpleItem(LPUNKNOWN, PFNDESTROYED);
        ~CSimpleItem(void);

        BOOL Init(IMoniker *, IBindCtx *, LPOLESTR, IStorage *);

        //Non-delegating object IUnknown
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
    };

typedef CSimpleItem *PCSimpleItem;

//IOLECONT.CPP
/*
 * IOleItemContainer implementation that is shared between
 * CFileObject and CContainerItem.  The flag to the constructor
 * indicates which object exposes any given instantiation.
 */

class CImpIOleItemContainer : public IOleItemContainer
    {
    protected:
        ULONG               m_cRef;
        LPUNKNOWN           m_pUnkOuter;

        BOOL                m_fFileObj;
        PCFileObject        m_pObjFile;
        PCContainerItem     m_pObjCont;

    public:
        CImpIOleItemContainer(LPVOID, LPUNKNOWN, BOOL);
        ~CImpIOleItemContainer(void);

        HRESULT GetRunning(LPOLESTR, IBindCtx *, REFIID, void **
            , BOOL);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP ParseDisplayName(LPBC, LPOLESTR, ULONG *
                         , LPMONIKER *);
        STDMETHODIMP EnumObjects(DWORD, LPENUMUNKNOWN *);
        STDMETHODIMP LockContainer(BOOL);
        STDMETHODIMP GetObject(LPOLESTR, DWORD, LPBINDCTX, REFIID
                         , PPVOID);
        STDMETHODIMP GetObjectStorage(LPOLESTR, LPBINDCTX, REFIID
                         , PPVOID);
        STDMETHODIMP IsRunning(LPOLESTR);
    };

#define SZOPENSTORAGE  OLETEXT("OpenStorage")

//IDESCRIP.CPP
/*
 * IDescription implementation that is shared between
 * CFileObject, CContainerItem, and CSimpleItem.  The
 * IStorage argument to the constructor is all that this
 * implementation needs, so there's no backpointer.
 */

class CImpIDescription : public IDescription
    {
    protected:
        ULONG               m_cRef;
        LPUNKNOWN           m_pUnkOuter;
        IStorage           *m_pIStorage;

    public:
        CImpIDescription(LPUNKNOWN);
        ~CImpIDescription(void);

        void SetStorage(IStorage *);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetText(LPOLESTR, ULONG);
    };

#endif //_OBJECTS_H_


PRECOMP.CPP

#include "linksrc.h"


SIMPITEM.CPP

/*
 * SIMPITEM.CPP
 * Simple Item Object for Link Source, Chapter 9
 *
 * Implementation of an item object with nothing more
 * than the shared IDescription in IDESCRIP.CPP.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "linksrc.h"

#ifdef WIN32ANSI
/*
 * This is to turn off the mapping to ANSI wrapper APIs because
 * we're actually using wide char strings under Win32 all the time
 * in parts of this code.
 */
#undef CreateItemMoniker
#define CreateItemMoniker CreateItemMoniker

#endif

/*
 * CSimpleItem::CSimpleItem
 * CSimpleItem::~CSimpleItem
 *
 * Parameters (Constructor):
 *  pUnkParent      LPUNKNOWN to the parent in which this
 *                  item lives
 *  pfnDestroy      PFNDESTROYED to call when an object
 *                  is destroyed.
 */

CSimpleItem::CSimpleItem(LPUNKNOWN pUnkParent
    , PFNDESTROYED pfnDestroy)
    {
    m_cRef=0;

    //This keeps the parent storage open as well
    m_pUnkParent=pUnkParent;
    pUnkParent->AddRef();

    m_pfnDestroy=pfnDestroy;

    m_pIStorage=NULL;
    m_pmk=NULL;
    m_dwRegROT=0;

    m_pImpIDescription=NULL;
    return;
    }

CSimpleItem::~CSimpleItem(void)
    {
    //Remove us from the running object table
    if (0!=m_dwRegROT)
        {
        IRunningObjectTable    *pROT;

        if (SUCCEEDED(GetRunningObjectTable(0, &pROT)))
            {
            pROT->Revoke(m_dwRegROT);
            pROT->Release();
            }
        }

    ReleaseInterface(m_pmk);
    ReleaseInterface(m_pIStorage);
    DeleteInterfaceImp(m_pImpIDescription);

    ReleaseInterface(m_pUnkParent);
    return;
    }

/*
 * CSimpleItem::Init
 *
 * Purpose:
 *  Performs any intiailization of a CSimpleItem that's prone to
 *  failure that we also use internally before exposing the object
 *  outside.
 *
 * Parameters:
 *  pmkLeft         IMoniker * of our containing object.
 *  pbc             IBindCtx * to use to register ourselves
 *                  as running.
 *  pszItem         LPOLESTR naming this object.
 *  pIStorage       IStorage * to our information.
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CSimpleItem::Init(IMoniker *pmkLeft, IBindCtx *pbc
    , LPOLESTR pszItem, IStorage *pIStorage)
    {
    OLECHAR     szDelim[]=OLETEXT("!");
    HRESULT     hr=ResultFromScode(S_FALSE);
    IMoniker   *pmkItem;

    m_pImpIDescription=new CImpIDescription(this);

    if (NULL==m_pImpIDescription)
        return FALSE;

    m_pIStorage=pIStorage;
    m_pImpIDescription->SetStorage(m_pIStorage);

    /*
     * Create an item moniker for ourselves and register as
     * running.  Failure here is not critical.
     */
    if (FAILED(CreateItemMoniker(szDelim, pszItem, &pmkItem)))
        return TRUE;

    //Create a composite for ourselves
    if (SUCCEEDED(pmkLeft->ComposeWith(pmkItem, FALSE, &m_pmk)))
        {
        IRunningObjectTable *pROT;

        if (SUCCEEDED(pbc->GetRunningObjectTable(&pROT)))
            {
            pROT->Register(0, this, m_pmk, &m_dwRegROT);
            pROT->Release();
            }
        }

    pmkItem->Release();
    return TRUE;
    }

/*
 * CSimpleItem::QueryInterface
 * CSimpleItem::AddRef
 * CSimpleItem::Release
 *
 * Purpose:
 *  IUnknown members for CSimpleItem object.
 */

STDMETHODIMP CSimpleItem::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IDescription==riid)
        *ppv=m_pImpIDescription;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CSimpleItem::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CSimpleItem::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0;
    }


LINKUSER.CPP

/*
 * LINKUSER.CPP
 * Link User Chapter 9
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "linkuser.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    MSG         msg;
    PAPP        pApp;

    SETMESSAGEQUEUE(96);

    pApp=new CApp(hInst, hInstPrev, nCmdShow);

    if (NULL==pApp)
        return -1;

    if (pApp->Init())
        {
        while (GetMessage(&msg, NULL, 0,0 ))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    delete pApp;
    return msg.wParam;
    }

/*
 * DataUserWndProc
 *
 * Purpose:
 *  Window class procedure.  Standard callback.
 */

LRESULT APIENTRY LinkUserWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PAPP    pApp;
    RECT    rc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    pApp=(PAPP)GetWindowLong(hWnd, LINKUSERWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pApp=(PAPP)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, LINKUSERWL_STRUCTURE, (LONG)pApp);
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_SIZE:
            //Keep the listbox filling the client area
            GetClientRect(hWnd, &rc);
            SetWindowPos(pApp->m_hWndList, NULL, rc.left, rc.top
                , rc.right-rc.left, rc.bottom-rc.top
                , SWP_NOZORDER | SWP_NOACTIVATE);
            break;

        case WM_COMMAND:
            wID=LOWORD(wParam);

            switch (wID)
                {
                case ID_LISTOLINKS:
                    switch (wCode)
                        {
                        case LBN_DBLCLK:
                            pApp->ShowDescription();
                            break;
                        }

                    break;

                case IDM_LINKSHOWDESCRIPTION:
                    //Same as double-clicking in listbox
                    pApp->ShowDescription();
                    break;

                case IDM_LINKPARSEANDBIND:
                    pApp->ParseAndBind();
                    break;

                case IDM_LINKEXIT:
                    PostMessage(hWnd, WM_CLOSE, 0, 0L);
                    break;

                default:
                    break;
                }
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * CApp::CApp
 * CApp::~CApp
 *
 * Constructor Parameters: (from WinMain)
 *  hInst           HINSTANCE of the application.
 *  hInstPrev       HINSTANCE of a previous instance.
 *  nCmdShow        UINT specifying how to show the app window.
 */

CApp::CApp(HINSTANCE hInst, HINSTANCE hInstPrev
    , UINT nCmdShow)
    {
    UINT        i;

    m_hInst=hInst;
    m_hInstPrev=hInstPrev;
    m_nCmdShow=nCmdShow;

    m_hWnd=NULL;
    m_fInitialized=FALSE;
    m_hWndList=NULL;

    for (i=0; i < CMONIKERS; i++)
        m_rgpmk[i]=NULL;

    return;
    }

CApp::~CApp(void)
    {
    UINT        i;

    for (i=0; i < CMONIKERS; i++)
        ReleaseInterface(m_rgpmk[i]);

    if (IsWindow(m_hWnd))
        DestroyWindow(m_hWnd);  //Destroys listbox

    if (m_fInitialized)
        CoUninitialize();

    return;
    }

/*
 * CApp::Init
 *
 * Purpose:
 *  Initializes an CApp object by registering window classes,
 *  creating the main window, and doing anything else prone to
 *  failure such as calling CoInitialize.  If this function fails
 *  the caller should insure that the destructor is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::Init(void)
    {
    WNDCLASS    wc;
    BOOL        fRet;
    RECT        rc;

    CHECKVER_COM;

    if (FAILED(CoInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    //Register our window classes.
    if (!m_hInstPrev)
        {
        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = LinkUserWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = CBWNDEXTRA;
        wc.hInstance      = m_hInst;
        wc.hIcon          = LoadIcon(m_hInst, TEXT("Icon"));
        wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName   = MAKEINTRESOURCE(IDR_MENU);
        wc.lpszClassName  = TEXT("LINKUSER");

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

    //Create the main window.
    m_hWnd=CreateWindow(TEXT("LINKUSER"), TEXT("Link User")
        , WS_OVERLAPPEDWINDOW,35, 35, 420, 250, NULL
        , NULL, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    //Create the list of monikers
    GetClientRect(m_hWnd, &rc);
    m_hWndList=CreateWindow(TEXT("listbox"), TEXT("LinkList")
        , WS_CHILD | WS_VISIBLE | LBS_NOTIFY | LBS_NOINTEGRALHEIGHT
        , rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top
        , m_hWnd, (HMENU)ID_LISTOLINKS, m_hInst, NULL);

    if (NULL==m_hWndList)
        return FALSE;

    if (!CreateMonikers())
        return FALSE;

    if (!ListInitialize())
        return FALSE;

    ShowWindow(m_hWnd, m_nCmdShow);
    UpdateWindow(m_hWnd);

    return fRet;
    }

/*
 * CApp::CreateMonikers
 *
 * Purpose:
 *  Creates monikers and stores them in m_rgpmk.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::CreateMonikers(void)
    {
    TCHAR       szFile[]=TEXT("c:\\inole\\chap09\\linksrc\\goop.lks");
    TCHAR       szItem1[]=TEXT("Object 2");
    TCHAR       szItem2[]=TEXT("Sub-Object 3");
    TCHAR       szDelim[]=TEXT("!");
    IMoniker   *pmkItem;
    HRESULT     hr;

    //Create a simple file moniker
    if (FAILED(CreateFileMoniker(szFile, &m_rgpmk[0])))
        return FALSE;

    //Create File!Item moniker, item first, then composite
    if (FAILED(CreateItemMoniker(szDelim, szItem1, &pmkItem)))
        return FALSE;

    //The output here will be the File!Item moniker
    hr=m_rgpmk[0]->ComposeWith(pmkItem, FALSE, &m_rgpmk[1]);
    pmkItem->Release();

    if (FAILED(hr))
        return FALSE;

    /*
     * Now create the File!Item!Item by appending another item
     * onto the File!Item just created.
     */
    if (FAILED(CreateItemMoniker(szDelim, szItem2, &pmkItem)))
        return FALSE;

    hr=m_rgpmk[1]->ComposeWith(pmkItem, FALSE, &m_rgpmk[2]);
    pmkItem->Release();

    if (FAILED(hr))
        return FALSE;

    return TRUE;
    }

/*
 * CApp::ListInitialize
 *
 * Purpose:
 *  Retrieves the display names for each moniker in our
 *  array and displays that name in the listbox.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::ListInitialize(void)
    {
    UINT        i;

    for (i=0; i < CMONIKERS; i++)
        {
        LPOLESTR    pszName;
        HRESULT     hr;
        IBindCtx   *pbc;

        if (FAILED(CreateBindCtx(0, &pbc)))
            return FALSE;

        hr=m_rgpmk[i]->GetDisplayName(pbc, NULL, &pszName);
        pbc->Release();

        if (FAILED(hr))
            return FALSE;

       #ifdef WIN32ANSI
        char    szTemp[256];

        WideCharToMultiByte(CP_ACP, 0, pszName, -1, szTemp, 256
            , NULL, NULL);
        SendMessage(m_hWndList, LB_ADDSTRING, 0, (LPARAM)szTemp);
       #else
        SendMessage(m_hWndList, LB_ADDSTRING, 0, (LPARAM)pszName);
       #endif
        }

    return TRUE;
    }

/*
 * CApp::ShowDescription
 *
 * Purpose:
 *  Tells CApp to retrieve the description text for a particular
 *  item in its listbox.  This is done in response to a double-click
 *  on a moniker display name which we bind to an ask for
 *  IDescription.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CApp::ShowDescription(void)
    {
    int             i;

    i=(int)SendMessage(m_hWndList, LB_GETCURSEL, 0, 0L);

    if (LB_ERR==i)
        return;

    BindAndShow(m_rgpmk[i], NULL);
    return;
    }

/*
 * CApp::ParseAndBind
 *
 * Purpose:
 *  Recreates a moniker from the display name under the current
 *  selection then attempts to bind as with ShowDescription.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CApp::ParseAndBind(void)
    {
    HRESULT         hr;
    IBindCtx       *pbc;
    IMoniker       *pmk;
    int             i;
    TCHAR           szName[512];
    ULONG           chEaten=0;

    i=(int)SendMessage(m_hWndList, LB_GETCURSEL, 0, 0L);

    if (LB_ERR==i)
        return;

    SendMessage(m_hWndList, LB_GETTEXT, i, (LPARAM)(LPTSTR)szName);

    if (FAILED(CreateBindCtx(0, &pbc)))
        {
        MessageBox(m_hWnd, TEXT("Failed to create bind context")
            , TEXT("Link User"), MB_OK);
        return;
        }

    hr=MkParseDisplayName(pbc, szName, &chEaten, &pmk);

    if (SUCCEEDED(hr))
        {
        MessageBox(m_hWnd, TEXT("Name parsed.  Press OK to bind...")
            , TEXT("Link User"), MB_OK);

        BindAndShow(pmk, pbc);
        pmk->Release();
        }
    else
        {
        //Tell the user how far parsing got
        wsprintf(szName, TEXT("Parsing failed after %lu characters")
            , chEaten);

        MessageBox(m_hWnd, szName, TEXT("Link User"), MB_OK);
        }

    pbc->Release();
    return;
    }

/*
 * CApp::BindAndShow
 *
 * Purpose:
 *  Binds to a given moniker to given moniker and requests
 *  IDescription, asking that object for display text.
 *
 * Parameters:
 *  pmk             IMoniker * naming the object.
 *  pbc             IBindCtx * to use.  If NULL, this will use
 *                  a default.
 *
 * Return Value:
 *  None
 */

void CApp::BindAndShow(IMoniker *pmk, IBindCtx *pbc)
    {
    HRESULT         hr;
    IDescription    *pIDescription;

    if (NULL==pbc)
        {
        hr=BindMoniker(pmk, 0, IID_IDescription
            , (void **)&pIDescription);
        }
    else
        {
        hr=pmk->BindToObject(pbc, NULL, IID_IDescription
            , (void **)&pIDescription);
        }

    if (SUCCEEDED(hr))
        {
        const ULONG cch=512;
        TCHAR       szText[cch];

   #ifdef WIN32ANSI
    WCHAR       szw[cch];
hr=pIDescription->GetText(szw, cch);
        WideCharToMultiByte(CP_ACP, 0, szw, -1, szText, cch, NULL, NULL);
   #else
        hr=pIDescription->GetText(szText, cch);
   #endif
        pIDescription->Release();

        if (SUCCEEDED(hr))
            {
            MessageBox(m_hWnd, szText, TEXT("Description Is:")
                , MB_OK);
            }
        else
            {
            MessageBox(m_hWnd, TEXT("Failed to get description text")
                , TEXT("Link User"), MB_OK);
            }
        }
    else
        {
        MessageBox(m_hWnd, TEXT("BindMoniker failed")
            , TEXT("Link User"), MB_OK);
        }

    return;
    }


LINKUSER.H

/*
 * LINKUSER.H
 * Link User Chapter 9
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _LINKUSER_H_
#define _LINKUSER_H_

#define INC_OLE2
#define CHAPTER9
#include <inole.h>
#include <idescrip.h>

//Menu Resource ID and Commands
#define IDR_MENU                    1

#define IDM_LINKSHOWDESCRIPTION     100
#define IDM_LINKPARSEANDBIND        101
#define IDM_LINKEXIT                102

//LINKUSER.CPP
LRESULT APIENTRY LinkUserWndProc(HWND, UINT, WPARAM, LPARAM);

class CAdviseSink;
typedef class CAdviseSink *PCAdviseSink;

/*
 * Application-defined classes and types.
 */

#define CMONIKERS       3
#define ID_LISTOLINKS   1000

class CApp
    {
    friend LRESULT APIENTRY LinkUserWndProc(HWND, UINT, WPARAM
        , LPARAM);

    protected:
        HINSTANCE       m_hInst;            //WinMain parameters
        HINSTANCE       m_hInstPrev;
        UINT            m_nCmdShow;

        HWND            m_hWnd;             //Main window handle
        BOOL            m_fInitialized;     //Did CoInitialize work?

        HWND            m_hWndList;         //Listbox display
        IMoniker       *m_rgpmk[CMONIKERS]; //Held monikers

    public:
        CApp(HINSTANCE, HINSTANCE, UINT);
        ~CApp(void);
        BOOL Init(void);

        BOOL CreateMonikers(void);
        BOOL ListInitialize(void);
        void ShowDescription(void);
        void ParseAndBind(void);
        void BindAndShow(IMoniker *, IBindCtx *);
    };

typedef CApp *PAPP;

#define CBWNDEXTRA               sizeof(PAPP)
#define LINKUSERWL_STRUCTURE     0

#endif //_LINKUSER_H_


CLIENT.CPP

/*
 * CLIENT.CPP
 * Component Cosmo Chapter 10
 *
 * Implementation of the CCosmoClient class that just makes sure
 * we get a CCosmoDoc on doc creation and that we initialize fully.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cocosmo.h"

/*
 * CCosmoClient::CCosmoClient
 * CCosmoClient::~CCosmoClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame to the frame object.
 */

CCosmoClient::CCosmoClient(HINSTANCE hInst, PCFrame pFR)
    : CClient(hInst, pFR)
    {
    return;
    }

CCosmoClient::~CCosmoClient(void)
    {
    return;
    }

/*
 * CCosmoClient::CreateCDocument
 *
 * Purpose:
 *  Constructs a new document specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::CreateCDocument(void)
    {
    return (PCDocument)(new CCosmoDoc(m_hInst, m_pFR, m_pAdv));
    }

/*
 * CCosmoClient::NewDocument
 *
 * Purpose:
 *  Small override of the CClient::NewDocument that we have just
 *  to check the initial line selection on new document creation.
 *
 * Parameters:
 *  fVisible        BOOL indicating if the document is to be
 *                  visible or not.
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::NewDocument(BOOL fVisible)
    {
    PCDocument  pDoc;

    //Perform default NewDocument first
    pDoc=CClient::NewDocument(fVisible);

    //We know that m_pFR is actually a CCosmoFrame, so type is safe.
    ((PCCosmoFrame)m_pFR)->CheckLineSelection(IDM_LINESOLID);

    return pDoc;
    }


COCOSMO.CPP

/*
 * COCOSMO.CPP
 * Component Cosmo Chapter 10
 *
 * WinMain and CCosmoFrame implementations.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "cocosmo.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCCosmoFrame    pFR;
    FRAMEINIT       fi;
    WPARAM          wRet;

    //Attempt to allocate and initialize the application
    pFR=new CCosmoFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return -1;

    fi.idsMin=IDS_FRAMEMIN;
    fi.idsMax=IDS_FRAMEMAX;
    fi.idsStatMin=IDS_STATMESSAGEMIN;
    fi.idsStatMax=IDS_STATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=440;
    fi.cy=460;

    //If we can initialize pFR, start chugging messages
    if (pFR->Init(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

/*
 * CCosmoFrame::CCosmoFrame
 * CCosmoFrame::~CCosmoFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WInMain
 */

CCosmoFrame::CCosmoFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    : CFrame(hInst, hInstPrev, pszCmdLine, nCmdShow)
    {
    UINT        i;

    for (i=0; i<5; i++)
        m_hBmpLines[i]=NULL;

    m_uIDCurLine=0;
    m_fInitialized=FALSE;
    return;
    }

CCosmoFrame::~CCosmoFrame(void)
    {
    UINT        i;

    for (i=0; i<5; i++)
        {
        if (NULL!=m_hBmpLines[i])
            DeleteObject(m_hBmpLines[i]);
        }

    if (m_fInitialized)
        CoUninitialize();

    return;
    }

/*
 * CCosmoFrame::Init
 *
 * Purpose:
 *  Call CoInitialize then calling down into the base class
 *  initialization.
 *
 * Parameters:
 *  pFI             PFRAMEINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CCosmoFrame::Init(PFRAMEINIT pFI)
    {
    CHECKVER_COM;

    if (FAILED(CoInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    return CFrame::Init(pFI);
    }

/*
 * CCosmoFrame::CreateCClient
 *
 * Purpose:
 *  Constructs a new client specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient        Pointer to the new client object.
 */

PCClient CCosmoFrame::CreateCClient(void)
    {
    return (PCClient)(new CCosmoClient(m_hInst, this));
    }

/*
 * CCosmoFrame::RegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CCosmoFrame::RegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //First let the standard frame do its thing
    if (!CFrame::RegisterAllClasses())
        return FALSE;

    /*
     * We want a different background color for the document
     * because the Polyline we put in the document will paint
     * with COLOR_WINDOW which by default which is CLASSLIB's
     * default document color.
     */

    GetClassInfo(m_hInst, SZCLASSDOCUMENT, &wc);
    UnregisterClass(SZCLASSDOCUMENT, m_hInst);

    wc.hbrBackground=(HBRUSH)(COLOR_APPWORKSPACE+1);

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

    //No need to register the Polyline window now...

    return TRUE;
    }

/*
 * CCosmoFrame::PreShowInit
 *
 * Purpose:
 *  Called from Init before intially showing the window.  We do
 *  whatever else we want here, modifying nCmdShow as necessary
 *  which affects ShowWindow in Init.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if this initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CCosmoFrame::PreShowInit(void)
    {
    CreateLineMenu();
    CheckLineSelection(IDM_LINESOLID);
    return TRUE;
    }

/*
 * CCosmoFrame::CreateLineMenu
 *
 * Purpose:
 *  Initializes the bitmaps used to create the Line menu and
 *  replaces the text items defined in the application resources
 *  with these bitmaps.  Note that the contents of m_hBmpLines
 *  must be cleaned up when the application terminates.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CreateLineMenu(void)
    {
    HMENU       hMenu;
    HDC         hDC, hMemDC;
    HPEN        hPen;
    HGDIOBJ     hObj;
    TEXTMETRIC  tm;
    UINT        i, cx, cy;

    hMenu=GetSubMenu(GetMenu(m_hWnd), 3);   //Line menu.
    hDC=GetDC(m_hWnd);

    //Create each line in a menu item 8 chars wide, one char high.
    GetTextMetrics(hDC, &tm);
    cx=tm.tmAveCharWidth*8;
    cy=tm.tmHeight;

    /*
     * Create a memory DC in which to draw lines, and bitmaps
     * for each line.
     */
    hMemDC=CreateCompatibleDC(hDC);
    ReleaseDC(m_hWnd, hDC);

    for (i=0; i<5; i++)
        {
        m_hBmpLines[i]=CreateCompatibleBitmap(hMemDC, cx, cy);
        SelectObject(hMemDC, m_hBmpLines[i]);

        PatBlt(hMemDC, 0, 0, cx, cy, WHITENESS);

        hPen=CreatePen(i, 1, 0L);       //i=line style like PS_SOLID
        hObj=SelectObject(hMemDC, hPen);

        MoveToEx(hMemDC, 0, cy/2, NULL);
        LineTo(hMemDC, cx, cy/2);

        ModifyMenu(hMenu, IDM_LINEMIN+i, MF_BYCOMMAND | MF_BITMAP
            , IDM_LINEMIN+i, (LPTSTR)(LONG)(UINT)m_hBmpLines[i]);

        SelectObject(hMemDC, hObj);
        DeleteObject(hPen);
        }

    CheckMenuItem(hMenu, IDM_LINESOLID, MF_CHECKED);
    DeleteDC(hMemDC);

    return;
    }

/*
 * CCosmoFrame::CreateToolbar
 *
 * Purpose:
 *  Procedure to create all the necessary toolbar buttons.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of tools added to the bar.
 */

UINT CCosmoFrame::CreateToolbar(void)
    {
    UINT            iLast;
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;
    UINT            utEx  =GIZMOTYPE_BUTTONATTRIBUTEEX;

    //Insert the standard ones.
    iLast=CFrame::CreateToolbar();

    /*
     * Insert File Import in the 5th position and account for
     * it in iLast.
     */
    m_pTB->Add(utCmd, 4, IDM_FILEIMPORT, m_dxB, m_dyB
        , NULL, m_hBmp, 2, uState);
    iLast++;

    //Separator
    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    /*
     * For the Background bitmap, preserve our use of black
     * (part of the image)
     */
    m_pTB->Add(utCmd, iLast++, IDM_COLORBACKGROUND, m_dxB, m_dyB
        , NULL, m_hBmp, 3, GIZMO_NORMAL | PRESERVE_BLACK);

    m_pTB->Add(utCmd, iLast++, IDM_COLORLINE, m_dxB, m_dyB
        , NULL, m_hBmp, 4, uState);

    //Separator
    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //Line styles.
    m_pTB->Add(utEx, iLast++, IDM_LINESOLID, m_dxB, m_dyB
        , NULL, m_hBmp, 5, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASH, m_dxB, m_dyB
        , NULL, m_hBmp, 6, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 7, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASHDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 8, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASHDOTDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 9, uState);

    return iLast;
    }

/*
 * CCosmoFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the Cosmo frame window that just
 *  processes the line menu and the color menu leaving the
 *  CFrame to do everything else.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CCosmoFrame::OnCommand(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PCCosmoDoc      pDoc;
    TCHAR           szFile[CCHPATHMAX];
    BOOL            fOK;
    UINT            i, uTemp;
    COLORREF        rgColors[16];
    CHOOSECOLOR     cc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many toolbar notifications.
     */
    if (m_fInit)
        return 0L;

    pDoc=(PCCosmoDoc)m_pCL->ActiveDocument();

    /*
     * Check for the line style commands which are
     * IDM_LINEMIN+<style>.  We handle this by changing the menu
     * and toolbar, then we pass it to the document for real
     * processing.
     */
    if (NULL!=pDoc && IDM_LINEMIN <= wID && IDM_LINEMAX >=wID)
        {
        CheckLineSelection(wID);
        pDoc->LineStyleSet(wID-IDM_LINEMIN);
        return 0L;
        }

    switch (wID)
        {
        case IDM_FILEIMPORT:
            szFile[0]=0;
            fOK=SaveOpenDialog(szFile, CCHPATHMAX, IDS_FILEIMPORT
                , TRUE, &i);

            if (fOK)
                {
                uTemp=pDoc->Load(FALSE, szFile);
                pDoc->ErrorMessage(uTemp);
                }

            return (LRESULT)fOK;

        case IDM_COLORBACKGROUND:
        case IDM_COLORLINE:
            //Invoke the color chooser for either color
            uTemp=(IDM_COLORBACKGROUND==wID)
                ? DOCCOLOR_BACKGROUND : DOCCOLOR_LINE;

            for (i=0; i<16; i++)
                rgColors[i]=RGB(0, 0, i*16);

            memset(&cc, 0, sizeof(CHOOSECOLOR));
            cc.lStructSize=sizeof(CHOOSECOLOR);
            cc.lpCustColors=rgColors;
            cc.hwndOwner=hWnd;
            cc.Flags=CC_RGBINIT;
            cc.rgbResult=pDoc->ColorGet(uTemp);

            if (ChooseColor(&cc))
                pDoc->ColorSet(uTemp, cc.rgbResult);

            break;

        default:
           CFrame::OnCommand(hWnd, wParam, lParam);
        }

    return 0L;
    }

/*
 * CCosmoFrame::OnDocumentDataChange
 *
 * Purpose:
 *  Update the Line menu and toolbar if the style in the data
 *  changes.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentDataChange(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::OnDocumentActivate
 *
 * Purpose:
 *  Informs us that document activation changed, so update the UI
 *  for that new document.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentActivate(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    PCDocument  pDoc;
    BOOL        fOK=FALSE;
    BOOL        fCallDefault=TRUE;
    UINT        i;
    UINT        uTemp;
    UINT        uTempE;
    UINT        uTempD;

    pDoc=m_pCL->ActiveDocument();

    uTempE=MF_ENABLED | MF_BYCOMMAND;
    uTempD=MF_DISABLED | MF_GRAYED | MF_BYCOMMAND;
    uTemp=((NULL!=pDoc) ? uTempE : uTempD);

    //File menu:  If there is document window, disable Import.
    if (m_phMenu[0]==hMenu)
        EnableMenuItem(hMenu, IDM_FILEIMPORT, uTemp);

    //Color menu:  no document, no commands
    if (m_phMenu[2]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_COLORBACKGROUND, uTemp);
        EnableMenuItem(hMenu, IDM_COLORLINE,       uTemp);
        fCallDefault=FALSE;
        }

    //Line menu:  no document, no commands
    if (m_phMenu[3]==hMenu)
        {
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            EnableMenuItem(hMenu, i, uTemp);

        fCallDefault=FALSE;
        }

    if (fCallDefault)
        CFrame::UpdateMenus(hMenu, iMenu);

    return;
    }

/*
 * CCosmoFrame::UpdateToolbar
 *
 * Purpose:
 *  Enables and disables tools depending on whether we have
 *  a document or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateToolbar(void)
    {
    BOOL        fLast;
    UINT        i;

    //Save the last enabled state before CFrame changes it
    fLast=m_fLastEnable;

    //Let the default hack on its tools
    CFrame::UpdateToolbar();

    /*
     * If CFrame::UpdateToolbar changed anything, then we need
     * to change as well--if nothing changes, nothing to do.
     */
    if (fLast!=m_fLastEnable)
        {
        m_pTB->Enable(IDM_FILEIMPORT, m_fLastEnable);

        m_pTB->Enable(IDM_COLORBACKGROUND, m_fLastEnable);
        m_pTB->Enable(IDM_COLORLINE,       m_fLastEnable);

        for (i=IDM_LINEMIN; i <= IDM_LINEMAX; i++)
            m_pTB->Enable(i, m_fLastEnable);
        }

    return;
    }

/*
 * CCosmoFrame::CheckLineSelection
 *
 * Purpose:
 *  Maintains the bitmap menu and the tools for the line selection.
 *  Both are mutially exclusive option lists where a selection in
 *  one has to affect the other.
 *
 * Parameters:
 *  uID             UINT ID of the item to be selected
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CheckLineSelection(UINT uID)
    {
    UINT        i;
    HMENU       hMenu;

    //Update menus and tools if the selection changed.
    if (uID!=m_uIDCurLine)
        {
        m_uIDCurLine=uID;
        hMenu=GetMenu(m_hWnd);

        //Uncheck all lines initially.
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            CheckMenuItem(hMenu, i, MF_UNCHECKED | MF_BYCOMMAND);

        CheckMenuItem(hMenu, uID, MF_CHECKED | MF_BYCOMMAND);
        m_pTB->Check(uID, TRUE);
        }

    return;
    }


COCOSMO.H

/*
 * COCOSMO.H
 * Component Cosmo Chapter 10
 *
 * Single include file that pulls in everything needed for other
 * source files in the Cosmo application.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _COCOSMO_H_
#define _COCOSMO_H_

#define INC_CLASSLIB
#define INC_CONTROLS
#define INC_OLE2
//CHAPTER10MOD
#define CHAPTER10
#include <inole.h>
#include <ipoly10.h>
//End CHAPTER10MOD
#include "resource.h"

//COCOSMO.CPP:  Frame object that creates a main window

class CCosmoFrame : public CFrame
    {
    private:
        HBITMAP         m_hBmpLines[5];     //Menu item bitmaps
        UINT            m_uIDCurLine;       //Current line selection
        BOOL            m_fInitialized;     //CoInitialize work?

    protected:
        //Overridable for creating a CClient for this frame
        virtual PCClient  CreateCClient(void);

        virtual BOOL      RegisterAllClasses(void);
        virtual BOOL      PreShowInit(void);
        virtual UINT      CreateToolbar(void);

        virtual LRESULT   OnCommand(HWND, WPARAM, LPARAM);
        virtual void      OnDocumentDataChange(PCDocument);
        virtual void      OnDocumentActivate(PCDocument);

        //New for this class
        virtual void      CreateLineMenu(void);

    public:
        CCosmoFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CCosmoFrame(void);

        //Overrides
        virtual BOOL      Init(PFRAMEINIT);
        virtual void      UpdateMenus(HMENU, UINT);
        virtual void      UpdateToolbar(void);

        //New for this class
        virtual void      CheckLineSelection(UINT);
    };

typedef CCosmoFrame *PCCosmoFrame;

//CLIENT.CPP

/*
 * The only reason we have a derived class here is to override
 * CreateCDocument so we can create our own type as well as
 * overriding NewDocument to perform one other piece of work once
 * the document's been created.
 */

class CCosmoClient : public CClient
    {
    protected:
        //Overridable for creating a new CDocument
        virtual PCDocument CreateCDocument(void);

    public:
        CCosmoClient(HINSTANCE, PCFrame);
        virtual ~CCosmoClient(void);

        virtual PCDocument NewDocument(BOOL);
    };

typedef CCosmoClient *PCCosmoClient;

//DOCUMENT.CPP

class CCosmoDoc;
typedef CCosmoDoc *PCCosmoDoc;

//CHAPTER10MOD
class CPolylineAdviseSink : public IPolylineAdviseSink10
//End CHAPTER10MOD
    {
    private:
        PCCosmoDoc  m_pDoc;         //Backpointer to document
        ULONG       m_cRef;

    public:
        CPolylineAdviseSink(PCCosmoDoc);
        ~CPolylineAdviseSink(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //Advise members.
        STDMETHODIMP_(void) OnPointChange(void);
        STDMETHODIMP_(void) OnSizeChange(void);
        STDMETHODIMP_(void) OnColorChange(void);
        STDMETHODIMP_(void) OnLineStyleChange(void);
        //CHAPTER10MOD
        //OnDataChange removed to IAdviseSink
        //End CHAPTER10MOD
    };

typedef CPolylineAdviseSink *PCPolylineAdviseSink;

//CHAPTER10MOD
/*
 * The generic advisory interface.  This object controls it's own
 * lifetime and the document becomes a user of the object with
 * the last reference count.
 */

class CImpIAdviseSink : public IAdviseSink
    {
    protected:
        ULONG               m_cRef;
        LPVOID              m_pObj;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIAdviseSink(LPVOID, LPUNKNOWN);
        ~CImpIAdviseSink(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(void)  OnDataChange(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP_(void)  OnViewChange(DWORD, LONG);
        STDMETHODIMP_(void)  OnRename(LPMONIKER);
        STDMETHODIMP_(void)  OnSave(void);
        STDMETHODIMP_(void)  OnClose(void);
    };

typedef CImpIAdviseSink *PCImpIAdviseSink;
//End CHAPTER10MOD

//Constant ID for the window polyline that lives in a document
#define ID_POLYLINE         10

//Stream name to open with IPersistStream[Init]
#define SZSTREAM                    OLETEXT("CONTENTS")

//CHAPTER10MOD
class CCosmoDoc : public CDocument, public IUnknown
//End CHAPTER10MOD
    {
    friend class CPolylineAdviseSink;
    //CHAPTER10MOD
    friend class CImpIAdviseSink;
    //End CHAPTER10MOD

    protected:
        UINT                    m_uPrevSize;    //Last WM_SIZE wParam

        //CHAPTER10MOD
        IPolyline10            *m_pPL;          //Polyline object
        IPolylineAdviseSink10  *m_pPLAdv;
        //End CHAPTER8MOD

        IConnectionPoint       *m_pIConnectPt;
        DWORD                   m_dwCookie;     //Connection key
        IStorage               *m_pIStorage;    //Doc storage
        PERSISTPOINTER          m_pp;

        //CHAPTER10MOD
        PCImpIAdviseSink        m_pImpIAdviseSink;

        DWORD                   m_dwConn;       //Advisory connection
        ULONG                   m_cRef;         //Document ref count
        //EndCHAPTER10MOD

    protected:
        virtual BOOL     FMessageHook(HWND, UINT, WPARAM, LPARAM
            , LRESULT *);

    public:
        CCosmoDoc(HINSTANCE, PCFrame, PCDocumentAdviseSink);
        virtual ~CCosmoDoc(void);

        //CHAPTER10MOD
        //Need a controlling unknown for our interfaces
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
        //End CHAPTER10MOD

        virtual BOOL     Init(PDOCUMENTINIT);
        virtual void     Clear(void);

        virtual UINT     Load(BOOL, LPTSTR);
        virtual UINT     Save(UINT, LPTSTR);

        virtual void     Undo(void);
        virtual BOOL     Clip(HWND, BOOL);
        virtual HGLOBAL  RenderFormat(UINT);
        virtual BOOL     FQueryPaste(void);
        virtual BOOL     Paste(HWND);

        virtual COLORREF ColorSet(UINT, COLORREF);
        virtual COLORREF ColorGet(UINT);

        virtual UINT     LineStyleSet(UINT);
        virtual UINT     LineStyleGet(void);
    };

typedef CCosmoDoc *PCCosmoDoc;

//These color indices wrap the polyline definitions
#define DOCCOLOR_BACKGROUND             POLYLINECOLOR_BACKGROUND
#define DOCCOLOR_LINE                   POLYLINECOLOR_LINE

#endif //_COCOSMO_H_


DOCUMENT.CPP

/*
 * DOCUMENT.CPP
 * Component Cosmo Chapter 10
 *
 * Implementation of the CCosmoDoc derivation of CDocument
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cocosmo.h"

/*
 * CCosmoDoc::CCosmoDoc
 * CCosmoDoc::~CCosmoDoc
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame of the frame object.
 *  pAdv            PCDocumentAdviseSink to notify on events
 */

CCosmoDoc::CCosmoDoc(HINSTANCE hInst, PCFrame pFR
    , PCDocumentAdviseSink pAdv)
    : CDocument(hInst, pFR, pAdv)
    {
    m_pPL=NULL;
    m_pPLAdv=NULL;
    m_uPrevSize=SIZE_RESTORED;
    m_pIConnectPt=NULL;
    m_dwCookie=0;

    m_pIStorage=NULL;
    m_pp.psModel=PERSIST_UNKNOWN;
    m_pp.pIP.pIPersistStorage=NULL; //Affects all pointers

    //CHAPTER10MOD
    m_pImpIAdviseSink=NULL;
    m_dwConn=0;
    //End CHAPTER10MOD
    return;
    }

CCosmoDoc::~CCosmoDoc(void)
    {
    //CHAPTER10MOD
    LPDATAOBJECT        pIDataObject;
    HRESULT             hr;

    //Turn off the advise.
    if (NULL!=m_pPL && 0!=m_dwConn)
        {
        hr=m_pPL->QueryInterface(IID_IDataObject
            , (PPVOID)&pIDataObject);

        if (SUCCEEDED(hr))
            {
            pIDataObject->DUnadvise(m_dwConn);
            pIDataObject->Release();
            }
        }

    DeleteInterfaceImp(m_pImpIAdviseSink);
    //End CHAPTER10MOD

    ReleaseInterface(m_pp.pIP.pIPersistStorage);
    ReleaseInterface(m_pIStorage);

    if (NULL!=m_pIConnectPt)
        {
        m_pIConnectPt->Unadvise(m_dwCookie);
        ReleaseInterface(m_pIConnectPt);
        }

    ReleaseInterface(m_pPL);
    ReleaseInterface(m_pPLAdv);

    CoFreeUnusedLibraries();
    return;
    }

/*
 * CCosmoDoc::Init
 *
 * Purpose:
 *  Initializes an already created document window.  The client
 *  actually creates the window for us, then passes that here for
 *  further initialization.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CCosmoDoc::Init(PDOCUMENTINIT pDI)
    {
    RECT                        rc;
    HRESULT                     hr;
    IConnectionPointContainer  *pCPC;
    //CHAPTER10MOD
    FORMATETC                   fe;
    LPDATAOBJECT                pIDataObject;
    //End CHAPTER10MOD

    //Change the stringtable range to our customization.
    pDI->idsMin=IDS_DOCUMENTMIN;
    pDI->idsMax=IDS_DOCUMENTMAX;

    //Do default initialization
    if (!CDocument::Init(pDI))
        return FALSE;

    //CHAPTER10MOD
    //Create the Polyline Component
    hr=CoCreateInstance(CLSID_Polyline10, NULL, CLSCTX_INPROC_SERVER
        , IID_IPolyline10, (PPVOID)&m_pPL);
    //End CHAPTER10MOD

    if (FAILED(hr))
        {
        //Warn that we could not load the Polyline
        MessageBox(pDI->hWndDoc, PSZ(IDS_NOPOLYLINE)
            , PSZ(IDS_CAPTION), MB_OK);
        return FALSE;
        }

    //Initialize the contained Polyline which creates a window.
    GetClientRect(m_hWnd, &rc);
    InflateRect(&rc, -8, -8);

    if (FAILED(m_pPL->Init(m_hWnd, &rc, WS_CHILD | WS_VISIBLE
        , ID_POLYLINE)))
        return FALSE;

    //Set up an advise on the Polyline.
    m_pPLAdv=new CPolylineAdviseSink(this);
    m_pPLAdv->AddRef();

    if (SUCCEEDED(m_pPL->QueryInterface(IID_IConnectionPointContainer
        , (PPVOID)&pCPC)))
        {
        //CHAPTER10MOD
        if (SUCCEEDED(pCPC->FindConnectionPoint
            (IID_IPolylineAdviseSink10, &m_pIConnectPt)))
        //End CHAPTER10MOD
            {
            m_pIConnectPt->Advise((LPUNKNOWN)m_pPLAdv, &m_dwCookie);
            }

        pCPC->Release();
        }

    /*
     * Check for the storage model being used in Polyline.
     * Any InitNew member will be called from CCosmoDoc::Load.
     */
    hr=m_pPL->QueryInterface(IID_IPersistStorage
        , (PPVOID)&m_pp.pIP.pIPersistStorage);

    if (SUCCEEDED(hr))
        m_pp.psModel=PERSIST_STORAGE;
    else
        {
        hr=m_pPL->QueryInterface(IID_IPersistStreamInit
            , (PPVOID)&m_pp.pIP.pIPersistStreamInit);

        if (SUCCEEDED(hr))
            m_pp.psModel=PERSIST_STREAMINIT;
        else
            {
            hr=m_pPL->QueryInterface(IID_IPersistStream
            , (PPVOID)&m_pp.pIP.pIPersistStream);

            if (SUCCEEDED(hr))
                m_pp.psModel=PERSIST_STREAM;
            else
                return FALSE;
            }
        }

    //CHAPTER10MOD
    /*
     * Create an IAdviseSink and send it to the Polyline's
     * IDataObject with the clipboard format for the Polyline
     * (as in IPOLY10.H).
     */

    //This is a private macro.
    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);

    m_pImpIAdviseSink=new CImpIAdviseSink(this, this);

    if (NULL==m_pImpIAdviseSink)
        return FALSE;

    //Set up an advise for the Polyline format
    hr=m_pPL->QueryInterface(IID_IDataObject, (PPVOID)&pIDataObject);

    if (FAILED(hr))
        return FALSE;

    pIDataObject->DAdvise(&fe, ADVF_NODATA, m_pImpIAdviseSink
        , &m_dwConn);
    pIDataObject->Release();
    //End CHAPTER10MOD

    return TRUE;
    }

//CHAPTER10MOD
//IUnknown interface for all others we implement in the document

/*
 * CCosmoDoc::QueryInterface
 * CCosmoDoc::AddRef
 * CCosmoDoc::Release
 *
 * Purpose:
 *  IUnknown members for the CCosmoDoc implementation.
 */

STDMETHODIMP CCosmoDoc::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    //The document is the unknown
    if (IID_IUnknown==riid)
        *ppv=(LPUNKNOWN)this;

    //CHAPTER10MOD
    if (IID_IPolylineAdviseSink10==riid)
    //End CHAPTER10MOD
        *ppv=m_pPLAdv;

    if (IID_IAdviseSink==riid)
        *ppv=m_pImpIAdviseSink;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(S_FALSE);
    }

STDMETHODIMP_(ULONG) CCosmoDoc::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CCosmoDoc::Release(void)
    {
    /*
     * The client destroys documents when CoCosmo terminates,
     * so we don't need to destroy anything here.
     */
    return --m_cRef;
    }

//End CHAPTER10MOD

/*
 * CCosmoDoc::FMessageHook
 *
 * Purpose:
 *  Processes WM_SIZE for the document so we can resize
 *  the Polyline.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CCosmoDoc::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    UINT        dx, dy;
    RECT        rc;

    *pLRes=0;

    if (WM_SIZE==iMsg)
        {
        //Don't effect the Polyline size to or from minimized state.
        if (SIZE_MINIMIZED!=wParam && SIZE_MINIMIZED !=m_uPrevSize)
            {
            //When we change size, resize any Polyline we hold.
            dx=LOWORD(lParam);
            dy=HIWORD(lParam);

            /*
             * If we are getting WM_SIZE in response to a Polyline
             * notification, then don't resize the Polyline window
             * again.
             */
            if (!m_fNoSize && NULL!=m_pPL)
                {
                //Resize the polyline to fit the new client
                SetRect(&rc, 8, 8, dx-8, dy-8);
                m_pPL->RectSet(&rc, FALSE);

                /*
                 * We consider sizing something that makes the file
                 * dirty, but not until we've finished the create
                 * process, which is why we set fNoDirty to FALSE
                 * in WM_CREATE since we get a WM_SIZE on the first
                 * creation.
                 */
                if (!m_fNoDirty)
                    FDirtySet(TRUE);

                SetRect(&rc, 0, 0, dx, dy);

                if (NULL!=m_pAdv)
                    m_pAdv->OnSizeChange(this, &rc);

                m_fNoDirty=FALSE;
                }
            }

        m_uPrevSize=wParam;
        }

    /*
     * We return FALSE even on WM_SIZE so we can let the default
     * procedure handle maximized MDI child windows appropriately.
     */
    return FALSE;
    }

/*
 * CCosmoDoc::Clear
 *
 * Purpose:
 *  Sets all contents in the document back to defaults with
 *  no filename.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Clear(void)
    {
    //Completely reset the polyline
    m_pPL->New();

    CDocument::Clear();
    return;
    }

/*
 * CCosmoDoc::Load
 *
 * Purpose:
 *  Loads a given document without any user interface overwriting
 *  the previous contents of the Polyline window.  We do this by
 *  opening the file and telling the Polyline to load itself from
 *  that file.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load, NULL if the file
 *                  is new and untitled.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CCosmoDoc::Load(BOOL fChangeFile, LPTSTR pszFile)
    {
    HRESULT             hr;
    LPSTORAGE           pIStorage;

    if (NULL==pszFile)
        {
        /*
         * As a user of an IPersistStorage we have to provide all
         * objects with an IStorage they can use for incremental
         * access passing that storage to InitNew.  Here we create
         * a temporary file that we don't bother holding on to.
         * If the object doesn't use it, then our Release destroys
         * it immediately.
         */

        hr=StgCreateDocfile(NULL, STGM_DIRECT | STGM_READWRITE
            | STGM_CREATE | STGM_DELETEONRELEASE
            | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;

        if (PERSIST_STORAGE==m_pp.psModel)
            m_pp.pIP.pIPersistStorage->InitNew(pIStorage);
        else
            {
            if (PERSIST_STREAMINIT==m_pp.psModel)
                m_pp.pIP.pIPersistStreamInit->InitNew();
            }

        m_pIStorage=pIStorage;

        Rename(NULL);
        return DOCERR_NONE;
        }

    /*
     * Open a storage and have the Polyline read its data
     * using whatever persistence model it employs.
     */
    hr=StgOpenStorage(pszFile, NULL, STGM_DIRECT | STGM_READWRITE
        | STGM_SHARE_EXCLUSIVE, NULL, 0, &pIStorage);

    if (FAILED(hr))
        return DOCERR_COULDNOTOPEN;

    if (PERSIST_STORAGE==m_pp.psModel)
        hr=m_pp.pIP.pIPersistStorage->Load(pIStorage);
    else
        {
        LPSTREAM    pIStream;

        hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
            | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

        if (SUCCEEDED(hr))
            {
            //This also works for PERSIST_STREAMINIT
            hr=m_pp.pIP.pIPersistStream->Load(pIStream);
            pIStream->Release();
            }
        }

    m_pIStorage=pIStorage;

    if (FAILED(hr))
        return DOCERR_READFAILURE;

    if (fChangeFile)
        Rename(pszFile);

    //Importing a file makes things dirty
    FDirtySet(!fChangeFile);

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::Save
 *
 * Purpose:
 *  Writes the file to a known filename, requiring that the user
 *  has previously used FileOpen or FileSaveAs in order to have
 *  a filename.
 *
 * Parameters:
 *  uType           UINT indicating the type of file the user
 *                  requested to save in the File Save As dialog.
 *  pszFile         LPTSTR under which to save.  If NULL, use the
 *                  current name.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CCosmoDoc::Save(UINT uType, LPTSTR pszFile)
    {
    BOOL                fRename=TRUE;
    HRESULT             hr;
    LPSTORAGE           pIStorage;
    BOOL                fSameAsLoad;

    //If Save or Save As under the same name, do Save.
    if (NULL==pszFile || 0==lstrcmpi(pszFile, m_szFile))
        {
        fRename=FALSE;
        pszFile=m_szFile;

        /*
         * If we're saving to an existing storage, just pass
         * the IStorage we have from Load along with TRUE
         * in fSameAsLoad.
         */

        fSameAsLoad=TRUE;
        }
    else
        {
        /*
         * In Component Cosmo, we only deal with one version of
         * data; all the code in Chapter 1 Cosmo that dealt with
         * 1.0 and 2.0 files has been removed.
         */

        hr=StgCreateDocfile(pszFile, STGM_DIRECT | STGM_READWRITE
            | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;

        //Tell the object to save into this new storage
        fSameAsLoad=FALSE;

        //Update our variable
        m_pIStorage->Release();
        m_pIStorage=pIStorage;
        }

    if (PERSIST_STORAGE==m_pp.psModel)
        {
        hr=m_pp.pIP.pIPersistStorage->Save(m_pIStorage, fSameAsLoad);

        if (SUCCEEDED(hr))
            {
            hr=m_pp.pIP.pIPersistStorage->SaveCompleted(fSameAsLoad
                ? NULL : m_pIStorage);
            }
        }
    else
        {
        LPSTREAM    pIStream;

        hr=m_pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
            | STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE
            , 0, 0, &pIStream);

        if (SUCCEEDED(hr))
            {
            //This also works for PERSIST_STREAMINIT
            hr=m_pp.pIP.pIPersistStream->Save(pIStream, TRUE);
            pIStream->Release();
            }
        }

    if (FAILED(hr))
        return DOCERR_WRITEFAILURE;

    //Saving makes us clean
    FDirtySet(FALSE);

    if (fRename)
        Rename(pszFile);

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::Undo
 *
 * Purpose:
 *  Reverses a previous action.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Undo(void)
    {
    m_pPL->Undo();
    return;
    }

/*
 * CCosmoDoc::Clip
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard, optionally implementing Cut by deleting the
 *  data in the current window after rendering.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *  fCut            BOOL indicating cut (TRUE) or copy (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::Clip(HWND hWndFrame, BOOL fCut)
    {
    BOOL            fRet=TRUE;
    HGLOBAL         hMem;
    UINT            i;

    //This array is so we can loop over the formats we provide.
    static UINT     rgcf[3]={0, CF_METAFILEPICT, CF_BITMAP};
    const UINT      cFormats=3;

    if (!OpenClipboard(hWndFrame))
        return FALSE;

    //Clean out whatever junk is in the clipboard.
    EmptyClipboard();

    rgcf[0]=m_cf;

    for (i=0; i < cFormats; i++)
        {
        //Copy private data first.
        hMem=RenderFormat(rgcf[i]);

        if (NULL!=hMem)
            SetClipboardData(rgcf[i], hMem);
        else
            fRet &=FALSE;
        }

    //Free clipboard ownership.
    CloseClipboard();

    //Delete our current data if copying succeeded.
    if (fRet && fCut)
        {
        m_pPL->New();
        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CCosmoDoc::RenderFormat
 *
 * Purpose:
 *  Renders a specific clipboard format into global memory.
 *
 * Parameters:
 *  cf              UINT format to render.
 *
 * Return Value:
 *  HGLOBAL         Global memory handle containing the data.
 */

HGLOBAL CCosmoDoc::RenderFormat(UINT cf)
    {
    //CHAPTER10MOD
    LPDATAOBJECT    pIDataObject;
    FORMATETC       fe;
    STGMEDIUM       stm;
    HRESULT         hr;

    hr=m_pPL->QueryInterface(IID_IDataObject, (PPVOID)&pIDataObject);

    if (FAILED(hr))
        return NULL;

    //Private macro
    SETDefFormatEtc(fe, cf
        , TYMED_HGLOBAL | TYMED_GDI | TYMED_MFPICT);

    pIDataObject->GetData(&fe, &stm);
    pIDataObject->Release();

    return stm.hGlobal;
    //End CHAPTER10MOD
    }

/*
 * CCosmoDoc::FQueryPaste
 *
 * Purpose:
 *  Determines if we can paste data from the clipboard.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CCosmoDoc::FQueryPaste(void)
    {
    return IsClipboardFormatAvailable(m_cf);
    }

/*
 * CCosmoDoc::Paste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  to the current figure in the editor window.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::Paste(HWND hWndFrame)
    {
    HGLOBAL         hMem;
    BOOL            fRet=FALSE;
    //CHAPTER10MOD
    LPDATAOBJECT    pIDataObject;
    HRESULT         hr;
    FORMATETC       fe;
    STGMEDIUM       stm;
    //End CHAPTER10MOD

    if (!OpenClipboard(hWndFrame))
        return FALSE;

    hMem=GetClipboardData(m_cf);
    CloseClipboard();

    if (NULL!=hMem)
        {
        //CHAPTER10MOD
        hr=m_pPL->QueryInterface(IID_IDataObject
            , (PPVOID)&pIDataObject);

        if (FAILED(hr))
            {
            GlobalFree(hMem);
            return FALSE;
            }

        SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);

        stm.tymed=TYMED_HGLOBAL;
        stm.hGlobal=hMem;
        stm.pUnkForRelease=NULL;

        pIDataObject->SetData(&fe, &stm, FALSE);
        pIDataObject->Release();

        FDirtySet(TRUE);
        //End CHAPTER10MOD
        }

    return TRUE;
    }

/*
 * CCosmoDoc::ColorSet
 *
 * Purpose:
 *  Changes a color used in our contained Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color.
 *
 * Return Value:
 *  COLORREF        Previous color for the given index.
 */

COLORREF CCosmoDoc::ColorSet(UINT iColor, COLORREF cr)
    {
    COLORREF    crRet;

    m_pPL->ColorSet(iColor, cr, &crRet);
    return crRet;
    }

/*
 * CCosmoDoc::ColorGet
 *
 * Purpose:
 *  Retrieves a color currently in use in the Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to retrieve.
 *
 * Return Value:
 *  COLORREF        Current color for the given index.
 */

COLORREF CCosmoDoc::ColorGet(UINT iColor)
    {
    COLORREF    crRet;

    m_pPL->ColorGet(iColor, &crRet);
    return crRet;
    }

/*
 * CCosmoDoc::LineStyleSet
 *
 * Purpose:
 *  Changes the line style currently used in the Polyline
 *
 * Parameters:
 *  iStyle          UINT index of the new line style to use.
 *
 * Return Value:
 *  UINT            Previous line style.
 */

UINT CCosmoDoc::LineStyleSet(UINT iStyle)
    {
    UINT    i;

    m_pPL->LineStyleSet(iStyle, &i);
    return i;
    }

/*
 * CCosmoDoc::LineStyleGet
 *
 * Purpose:
 *  Retrieves the line style currently used in the Polyline
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Current line style.
 */

UINT CCosmoDoc::LineStyleGet(void)
    {
    UINT    i=0;

    //m_pPL might not be valid yet.
    if (NULL!=m_pPL)
        m_pPL->LineStyleGet(&i);

    return i;
    }

//CHAPTER10MOD
//CPolylineAdviseSink moved to IADVSINK.CPP
//End CHAPTER10MOD


IADVSINK.CPP

/*
 * IADVSINK.CPP
 * Component Cosmo Chapter 10
 *
 * Implementation of the CPolylineAdviseSink and CImpIAdviseSink
 * interfaces for Component Cosmo.  CPolylineAdviseSink moved
 * here from document.cpp to live with all the advise stuff.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cocosmo.h"

/*
 * CPolylineAdviseSink::CPolylineAdviseSink
 * CPolylineAdviseSink::~CPolylineAdviseSink
 *
 * Constructor Parameters:
 *  pDoc            PCCosmoDoc to store in this object
 */

CPolylineAdviseSink::CPolylineAdviseSink(PCCosmoDoc pDoc)
    {
    m_pDoc=pDoc;
    m_cRef=0;
    AddRef();
    return;
    }

CPolylineAdviseSink::~CPolylineAdviseSink(void)
    {
    return;
    }

/*
 * CPolylineAdviseSink::QueryInterface
 * CPolylineAdviseSink::AddRef
 * CPolylineAdviseSink::Release
 *
 * Purpose:
 *  IUnknown members for this IPolylineAdviseSink implementations.
 */

STDMETHODIMP CPolylineAdviseSink::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    //CHAPTER10MOD
    return m_pDoc->QueryInterface(riid, ppv);
    //End CHAPTER10MOD
    }

STDMETHODIMP_(ULONG) CPolylineAdviseSink::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPolylineAdviseSink::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CPolylineAdviseSink::OnPointChange
 *
 * Purpose:
 *  Informs the document that the polyline added or removed a point.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CPolylineAdviseSink::OnPointChange(void)
    {
    m_pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnSizeChange
 *
 * Purpose:
 *  Informs the document that the polyline changed size.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CPolylineAdviseSink::OnSizeChange(void)
    {
    RECT            rc;
    DWORD           dwStyle;
    HWND            hWnd;

    /*
     * Polyline window is informing us that it changed size in
     * response to setting it's data.  Therefore we have to
     * size ourselves accordingly but without moving the screen
     * position of the polyline window.
     */

    m_pDoc->m_fNoSize=TRUE;

    //Set the document window size.
    m_pDoc->m_pPL->Window(&hWnd);
    GetWindowRect(hWnd, &rc);
    InflateRect(&rc, 8, 8);

    //Adjust for a window sans menu
    dwStyle=GetWindowLong(m_pDoc->m_hWnd, GWL_STYLE);
    AdjustWindowRect(&rc, dwStyle, FALSE);

    SetWindowPos(m_pDoc->m_hWnd, NULL, 0, 0, rc.right-rc.left
        , rc.bottom-rc.top, SWP_NOMOVE | SWP_NOZORDER);

    if (NULL!=m_pDoc->m_pAdv)
        m_pDoc->m_pAdv->OnSizeChange(m_pDoc, &rc);

    m_pDoc->m_fNoSize=FALSE;
    m_pDoc->FDirtySet(TRUE);

    return;
    }

//CHAPTER10MOD
//OnDataChange in CImpIAdviseSink now
//End CHAPTER10MOD

/*
 * CPolylineAdviseSink::OnColorChange
 *
 * Purpose:
 *  Informs the document that the polyline data changed a color.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CPolylineAdviseSink::OnColorChange(void)
    {
    m_pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnLineStyleChange
 *
 * Purpose:
 *  Informs the document that the polyline changed its line style.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CPolylineAdviseSink::OnLineStyleChange(void)
    {
    m_pDoc->FDirtySet(TRUE);
    return;
    }

//CHAPTER10MOD
/*
 * CImpIAdviseSink::CImpIAdviseSink
 * CImpIAdviseSink::~CImpIAdviseSink
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN for delegation of IUnknown members.
 */

CImpIAdviseSink::CImpIAdviseSink(LPVOID pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIAdviseSink::~CImpIAdviseSink(void)
    {
    return;
    }

/*
 * CImpIAdviseSink::QueryInterface
 * CImpIAdviseSink::AddRef
 * CImpIAdviseSink::Release
 *
 * Purpose:
 *  IUnknown members for CImpIAdviseSink object.
 */

STDMETHODIMP CImpIAdviseSink::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIAdviseSink::OnDataChange
 *
 * Purpose:
 *  Notifes the advise sink that data changed in a data object.
 *  On this message you may request a new data rendering and
 *  update your displays as necessary.
 *
 * Parameters:
 *  pFEIn           LPFORMATETC describing format that changed
 *  pSTM            LPSTGMEDIUM providing the medium in which the
 *                  data is provided.
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnDataChange(LPFORMATETC pFEIn
    , LPSTGMEDIUM pSTM)
    {
    PCCosmoDoc      m_pDoc=(PCCosmoDoc)m_pObj;

    /*
     * This code is from former CPolylineAdviseSink::OnDataChange.
     * The only advise we asked for was on the Polyline native
     * format which is all we'll be notified for.
     */
    if (NULL!=m_pDoc->m_pAdv)
        m_pDoc->m_pAdv->OnDataChange(m_pDoc);

    m_pDoc->FDirtySet(TRUE);
    return;
    }

STDMETHODIMP_(void) CImpIAdviseSink::OnViewChange(DWORD dwAspect
    , LONG lindex)
    {
    return;
    }

STDMETHODIMP_(void) CImpIAdviseSink::OnRename(LPMONIKER pmk)
    {
    return;
    }

STDMETHODIMP_(void) CImpIAdviseSink::OnSave(void)
    {
    return;
    }

STDMETHODIMP_(void) CImpIAdviseSink::OnClose(void)
    {
    return;
    }

//End CHAPTER10MOD


RESOURCE.H

/*
 * RESOURCE.H
 * Component Cosmo Chapter 10
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Note that we augment classlib.h which we include before this.

//Position of Window menu.
#undef  WINDOW_MENU
#define WINDOW_MENU             4

//Total number of menus
#undef  CMENUS
#define CMENUS                  5

//Menu command identifiers.

#define IDM_FILEIMPORT          (IDM_CUSTOMFILEMIN)

#define IDM_COLORBACKGROUND     (IDM_CUSTOMMIN)
#define IDM_COLORLINE           (IDM_CUSTOMMIN+1)

#define IDM_LINEMIN             (IDM_CUSTOMMIN+100)
#define IDM_LINESOLID           (IDM_LINEMIN+0)         //PS_SOLID       0
#define IDM_LINEDASH            (IDM_LINEMIN+1)         //PS_DASH        1
#define IDM_LINEDOT             (IDM_LINEMIN+2)         //PS_DOT         2
#define IDM_LINEDASHDOT         (IDM_LINEMIN+3)         //PS_DASHDOT     3
#define IDM_LINEDASHDOTDOT      (IDM_LINEMIN+4)         //PS_DASHDOTDOT  4
#define IDM_LINEMAX             IDM_LINEDASHDOTDOT

/*
 * IDs for StatStrip handling of popup menus:  must be in order of menu
 * CLASSRES.H already defines ID_MENUFILE and ID_MENUEDIT for us.
 */

#undef  ID_MENUWINDOW
#undef  ID_MENUHELP

#define ID_MENUCOLOR            (ID_MENUCUSTOMMIN+0)
#define ID_MENULINE             (ID_MENUCUSTOMMIN+1)
#define ID_MENUWINDOW           (ID_MENUCUSTOMMIN+2)
#define ID_MENUHELP             (ID_MENUCUSTOMMIN+3)

//Stringtable IDs.  Keep sequential for each group.
#define IDS_FRAMEMIN            IDS_STANDARDFRAMEMIN
#define IDS_FILEIMPORT          (IDS_CUSTOMFRAMEMIN+0)
#define IDS_FRAMEMAX            (IDS_CUSTOMFRAMEMIN+0)

#define IDS_DOCUMENTMIN         IDS_STANDARDDOCMIN
#define IDS_NOPOLYLINE          (IDS_CUSTOMDOCMIN+0)
#define IDS_DOCUMENTMAX         (IDS_CUSTOMDOCMIN+0)

//These are specifically for the StatStrip
#define IDS_STATMESSAGEMIN                  IDS_STANDARDSTATMESSAGEMIN

#define IDS_ITEMMESSAGEFILEIMPORT           (IDS_CUSTOMSTATMESSAGEMIN+0)

#define IDS_MENUMESSAGECOLOR                (IDS_CUSTOMSTATMESSAGEMIN+1)
#define IDS_ITEMMESSAGECOLORBACKGROUND      (IDS_CUSTOMSTATMESSAGEMIN+2)
#define IDS_ITEMMESSAGECOLORLINE            (IDS_CUSTOMSTATMESSAGEMIN+3)

#define IDS_MENUMESSAGELINE                 (IDS_CUSTOMSTATMESSAGEMIN+4)
#define IDS_ITEMMESSAGELINESOLID            (IDS_CUSTOMSTATMESSAGEMIN+5)
#define IDS_ITEMMESSAGELINEDASH             (IDS_CUSTOMSTATMESSAGEMIN+6)
#define IDS_ITEMMESSAGELINEDOT              (IDS_CUSTOMSTATMESSAGEMIN+7)
#define IDS_ITEMMESSAGELINEDASHDOT          (IDS_CUSTOMSTATMESSAGEMIN+8)
#define IDS_ITEMMESSAGELINEDASHDOTDOT       (IDS_CUSTOMSTATMESSAGEMIN+9)

#define IDS_STATMESSAGEMAX                  (IDS_CUSTOMSTATMESSAGEMIN+9)

#endif //_RESOURCE_H_


DATAUSER.CPP

/*
 * DATAUSER.CPP
 * Data Object User Chapter 10
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "datauser.h"

//These are for displaying clipboard formats textually.
static TCHAR * rgszCF[13]={TEXT("Unknown"), TEXT("CF_TEXT")
                 , TEXT("CF_BITMAP"), TEXT("CF_METAFILEPICT")
                 , TEXT("CF_SYLK"), TEXT("CF_DIF"), TEXT("CF_TIFF")
                 , TEXT("CF_OEMTEXT"), TEXT("CF_DIB")
                 , TEXT("CF_PALETTE"), TEXT("CF_PENDATA")
                 , TEXT("CF_RIFF"), TEXT("CF_WAVE")};

static TCHAR szSuccess[]    =TEXT("succeeded");
static TCHAR szFailed[]     =TEXT("failed");
static TCHAR szExpected[]   =TEXT("expected");
static TCHAR szUnexpected[] =TEXT("unexpected!");

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    MSG         msg;
    PAPP        pApp;

    SETMESSAGEQUEUE(96);

    pApp=new CApp(hInst, hInstPrev, nCmdShow);

    if (NULL==pApp)
        return -1;

    if (pApp->Init())
        {
        while (GetMessage(&msg, NULL, 0,0 ))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    delete pApp;
    return msg.wParam;
    }

/*
 * DataUserWndProc
 *
 * Purpose:
 *  Window class procedure.  Standard callback.
 */

LRESULT APIENTRY DataUserWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    HRESULT         hr;
    PAPP            pApp;
    HMENU           hMenu;
    FORMATETC       fe;
    WORD            wID;

    pApp=(PAPP)GetWindowLong(hWnd, DATAUSERWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pApp=(PAPP)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, DATAUSERWL_STRUCTURE, (LONG)pApp);
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_PAINT:
            pApp->Paint();
            break;

        case WM_COMMAND:
            SETDefFormatEtc(fe, 0, TYMED_HGLOBAL | TYMED_GDI
                | TYMED_MFPICT);

            hMenu=GetMenu(hWnd);
            wID=LOWORD(wParam);

            switch (wID)
                {
                case IDM_OBJECTUSEDLL:
                    if (!pApp->m_fEXE)
                        break;

                    pApp->m_fEXE=FALSE;
                    pApp->FReloadDataObjects(TRUE);
                    break;

                case IDM_OBJECTUSEEXE:
                    if (pApp->m_fEXE)
                        break;

                    pApp->m_fEXE=TRUE;
                    pApp->FReloadDataObjects(TRUE);
                    break;

                case IDM_OBJECTDATASIZESMALL:
                case IDM_OBJECTDATASIZEMEDIUM:
                case IDM_OBJECTDATASIZELARGE:
                    CheckMenuItem(hMenu
                        , IDM_OBJECTDATASIZESMALL,  MF_UNCHECKED);
                    CheckMenuItem(hMenu
                        , IDM_OBJECTDATASIZEMEDIUM, MF_UNCHECKED);
                    CheckMenuItem(hMenu
                        , IDM_OBJECTDATASIZELARGE,  MF_UNCHECKED);
                    CheckMenuItem(hMenu, wID,  MF_CHECKED);

                    //Kill old advise.
                    if (NULL!=pApp->m_pIDataObject
                        || 0!=pApp->m_dwConn)
                        {
                        pApp->m_pIDataObject->DUnadvise(pApp
                            ->m_dwConn);
                        }

                    if (IDM_OBJECTDATASIZELARGE==wID)
                        pApp->m_pIDataObject=pApp->m_pIDataLarge;
                    else if (IDM_OBJECTDATASIZEMEDIUM==wID)
                        pApp->m_pIDataObject=pApp->m_pIDataMedium;
                    else
                        pApp->m_pIDataObject=pApp->m_pIDataSmall;

                    //Setup new advise.
                    fe.cfFormat=pApp->m_cfAdvise;
                    pApp->m_pIDataObject->DAdvise(&fe, ADVF_NODATA
                        , pApp->m_pIAdviseSink, &pApp->m_dwConn);

                    break;

                case IDM_OBJECTQUERYGETDATA:
                    if (NULL==pApp->m_pIDataObject)
                        break;

                    fe.tymed=TYMED_HGLOBAL | TYMED_GDI
                         | TYMED_MFPICT;

                    pApp->TryQueryGetData(&fe, CF_TEXT, TRUE, 0);
                    pApp->TryQueryGetData(&fe, CF_BITMAP, TRUE, 1);
                    pApp->TryQueryGetData(&fe, CF_DIB, FALSE, 2);
                    pApp->TryQueryGetData(&fe, CF_METAFILEPICT
                        , TRUE, 3);
                    pApp->TryQueryGetData(&fe, CF_WAVE, FALSE, 4);
                    break;

                case IDM_OBJECTGETDATATEXT:
                case IDM_OBJECTGETDATABITMAP:
                case IDM_OBJECTGETDATAMETAFILEPICT:
                    if (NULL==pApp->m_pIDataObject)
                        break;

                    //Clean up whatever we currently have.
                    pApp->m_cf=0;
                    ReleaseStgMedium(&pApp->m_stm);

                    if (IDM_OBJECTGETDATATEXT==wID)
                        SETDefFormatEtc(fe, CF_TEXT, TYMED_HGLOBAL);

                    if (IDM_OBJECTGETDATABITMAP==wID)
                        SETDefFormatEtc(fe, CF_BITMAP, TYMED_GDI);

                    if (IDM_OBJECTGETDATAMETAFILEPICT==wID)
                        {
                        SETDefFormatEtc(fe, CF_METAFILEPICT
                            , TYMED_MFPICT);
                        }

                    hr=pApp->m_pIDataObject->GetData(&fe
                        , &(pApp->m_stm));

                    if (SUCCEEDED(hr))
                        pApp->m_cf=fe.cfFormat;

                    InvalidateRect(hWnd, NULL, TRUE);
                    UpdateWindow(hWnd);
                    break;

                case IDM_OBJECTEXIT:
                    PostMessage(hWnd, WM_CLOSE, 0, 0L);
                    break;

                case IDM_ADVISETEXT:
                case IDM_ADVISEBITMAP:
                case IDM_ADVISEMETAFILEPICT:
                    if (NULL==pApp->m_pIDataObject)
                        break;

                    //Terminate the old connection
                    if (0!=pApp->m_dwConn)
                        {
                        pApp->m_pIDataObject->DUnadvise(pApp
                            ->m_dwConn);
                        }

                    CheckMenuItem(hMenu, pApp->m_cfAdvise
                        +IDM_ADVISEMIN, MF_UNCHECKED);
                    CheckMenuItem(hMenu, wID, MF_CHECKED);

                    //New format is wID-IDM_ADVISEMIN
                    pApp->m_cfAdvise=(UINT)(wID-IDM_ADVISEMIN);
                    fe.cfFormat=pApp->m_cfAdvise;
                    pApp->m_pIDataObject->DAdvise(&fe, ADVF_NODATA
                        , pApp->m_pIAdviseSink, &pApp->m_dwConn);

                    break;

                case IDM_ADVISEGETDATA:
                    pApp->m_fGetData=!pApp->m_fGetData;
                    CheckMenuItem(hMenu, wID, pApp->m_fGetData
                        ? MF_CHECKED : MF_UNCHECKED);
                    break;

                case IDM_ADVISEREPAINT:
                    pApp->m_fRepaint=!pApp->m_fRepaint;
                    CheckMenuItem(hMenu, wID, pApp->m_fRepaint
                        ? MF_CHECKED : MF_UNCHECKED);
                    break;

                default:
                    break;
                }
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * CApp::CApp
 * CApp::~CApp
 *
 * Constructor Parameters: (from WinMain)
 *  hInst           HINSTANCE of the application.
 *  hInstPrev       HINSTANCE of a previous instance.
 *  nCmdShow        UINT specifying how to show the app window.
 */

CApp::CApp(HINSTANCE hInst, HINSTANCE hInstPrev
    , UINT nCmdShow)
    {
    m_hInst=hInst;
    m_hInstPrev=hInstPrev;
    m_nCmdShow=nCmdShow;

    m_hWnd=NULL;
    m_fEXE=FALSE;

    m_pIAdviseSink=NULL;
    m_dwConn=0;
    m_cfAdvise=0;
    m_fGetData=FALSE;
    m_fRepaint=FALSE;

    m_pIDataSmall =NULL;
    m_pIDataMedium=NULL;
    m_pIDataLarge =NULL;
    m_pIDataObject=NULL;

    m_cf=0;
    m_stm.tymed=TYMED_NULL;
    m_stm.lpszFileName=NULL;      //Initializes union to NULL
    m_stm.pUnkForRelease=NULL;

    m_fInitialized=FALSE;
    return;
    }

CApp::~CApp(void)
    {
    //This releases the data object interfaces and advises
    FReloadDataObjects(FALSE);

    ReleaseStgMedium(&m_stm);

    //We called AddRef before so we could do this
    ReleaseInterface(m_pIAdviseSink);

    if (IsWindow(m_hWnd))
        DestroyWindow(m_hWnd);

    if (m_fInitialized)
        CoUninitialize();

    return;
    }

/*
 * CApp::Init
 *
 * Purpose:
 *  Initializes an CApp object by registering window classes,
 *  creating the main window, and doing anything else prone to
 *  failure such as calling CoInitialize.  If this function fails
 *  the caller should insure that the destructor is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::Init(void)
    {
    WNDCLASS    wc;
    BOOL        fRet;

    CHECKVER_COM;

    if (FAILED(CoInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    //Register our window classes.
    if (!m_hInstPrev)
        {
        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = DataUserWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = CBWNDEXTRA;
        wc.hInstance      = m_hInst;
        wc.hIcon          = LoadIcon(m_hInst, TEXT("Icon"));
        wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName   = MAKEINTRESOURCE(IDR_MENU);
        wc.lpszClassName  = TEXT("DATAUSER");

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

    //Create the main window.
    m_hWnd=CreateWindow(TEXT("DATAUSER"), TEXT("Data Object User")
        , WS_OVERLAPPEDWINDOW,35, 35, 350, 250, NULL
        , NULL, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    ShowWindow(m_hWnd, m_nCmdShow);
    UpdateWindow(m_hWnd);

    m_pIAdviseSink=new CAdviseSink(this);

    if (NULL==m_pIAdviseSink)
        return FALSE;

    m_pIAdviseSink->AddRef();

    CheckMenuItem(GetMenu(m_hWnd), IDM_OBJECTUSEDLL, MF_CHECKED);
    CheckMenuItem(GetMenu(m_hWnd), IDM_OBJECTDATASIZESMALL
        , MF_CHECKED);

    //Load the initial objects
    fRet=FReloadDataObjects(TRUE);
    m_pIDataObject=m_pIDataSmall;

    return fRet;
    }

/*
 * CApp::FReloadDataObjects
 *
 * Purpose:
 *  Releases the old data objects we're holding on to and reloads
 *  the new ones from either EXE or DLL depending on m_fEXE.
 *
 * Parameters:
 *  fReload         BOOL indicating if we are to recreate everything
 *                  or just release the old ones (so we can use this
 *                  from the destructor).
 *
 * Return Value:
 *  BOOL            TRUE if there are usable objects in us now.
 */

BOOL CApp::FReloadDataObjects(BOOL fReload)
    {
    HRESULT     hr1, hr2, hr3;
    DWORD       dwClsCtx;
    HCURSOR     hCur, hCurT;
    HMENU       hMenu;
    UINT        uTempD, uTempE;

    //Clean out any data we're holding
    m_cf=0;
    ReleaseStgMedium(&m_stm);

    //Turn off whatever data connection we have
    if (NULL!=m_pIDataObject && 0!=m_dwConn)
        m_pIDataObject->DUnadvise(m_dwConn);

    ReleaseInterface(m_pIDataLarge);
    ReleaseInterface(m_pIDataMedium);
    ReleaseInterface(m_pIDataSmall);

    m_pIDataObject=NULL;
    CoFreeUnusedLibraries();

    //Exit if we just wanted to free.
    if (!fReload)
        return FALSE;

    hCur=LoadCursor(NULL, MAKEINTRESOURCE(IDC_WAIT));
    hCurT=SetCursor(hCur);
    ShowCursor(TRUE);

    dwClsCtx=(m_fEXE) ? CLSCTX_LOCAL_SERVER : CLSCTX_INPROC_SERVER;

    hr1=CoCreateInstance(CLSID_DataObjectSmall, NULL, dwClsCtx
        , IID_IDataObject, (PPVOID)&m_pIDataSmall);

    hr2=CoCreateInstance(CLSID_DataObjectMedium, NULL, dwClsCtx
        , IID_IDataObject, (PPVOID)&m_pIDataMedium);

    hr3=CoCreateInstance(CLSID_DataObjectLarge, NULL, dwClsCtx
        , IID_IDataObject, (PPVOID)&m_pIDataLarge);

    ShowCursor(FALSE);
    SetCursor(hCurT);

    //If anything fails, recurse to clean up...
    if (FAILED(hr1) || FAILED(hr2) || FAILED(hr3))
        return FReloadDataObjects(FALSE);

    //Reset the state of the menus for Small, no advise, no options.
    hMenu=GetMenu(m_hWnd);
    CheckMenuItem(hMenu, IDM_OBJECTDATASIZESMALL,  MF_CHECKED);
    CheckMenuItem(hMenu, IDM_OBJECTDATASIZEMEDIUM, MF_UNCHECKED);
    CheckMenuItem(hMenu, IDM_OBJECTDATASIZELARGE,  MF_UNCHECKED);

    m_pIDataObject=m_pIDataSmall;
    CheckMenuItem(hMenu, m_cfAdvise+IDM_ADVISEMIN, MF_UNCHECKED);

    uTempE=m_fEXE  ? MF_CHECKED : MF_UNCHECKED;
    uTempD=!m_fEXE ? MF_CHECKED : MF_UNCHECKED;

    CheckMenuItem(hMenu, IDM_OBJECTUSEDLL, uTempD);
    CheckMenuItem(hMenu, IDM_OBJECTUSEEXE, uTempE);

    CheckMenuItem(hMenu, IDM_ADVISEGETDATA, MF_UNCHECKED);
    CheckMenuItem(hMenu, IDM_ADVISEREPAINT, MF_UNCHECKED);

    m_fGetData=FALSE;
    m_fRepaint=FALSE;

    //Cannot request data using async advises, so disable these.
    uTempE=m_fEXE  ? MF_DISABLED | MF_GRAYED : MF_ENABLED;
    EnableMenuItem(hMenu,  IDM_ADVISEGETDATA, uTempE);
    EnableMenuItem(hMenu, IDM_ADVISEREPAINT, uTempE);

    return TRUE;
    }

/*
 * CApp::TryQueryGetData
 *
 * Purpose:
 *  Centralized function call and output code for displaying results
 *  of various IDataObject::QueryGetData calls.
 *
 * Parameters:
 *  pFE             LPFORMATETC to test.
 *  cf              UINT specific clipboard format to stuff in pFE
 *                  before calling.  If zero, use whatever is
 *                  already in pFE.
 *  fExpect         BOOL indicating expected results
 *  y               UINT line on which to print results.
 *
 * Return Value:
 *  None
 */

void CApp::TryQueryGetData(LPFORMATETC pFE, UINT cf
    , BOOL fExpect, UINT y)
    {
    TCHAR       szTemp[80];
    LPTSTR      psz1;
    LPTSTR      psz2;
    UINT        cch;
    HRESULT     hr;
    HDC         hDC;

    if (0!=cf)
        pFE->cfFormat=cf;

    hr=m_pIDataObject->QueryGetData(pFE);
    psz1=(NOERROR==hr) ? szSuccess : szFailed;
    psz2=((NOERROR==hr)==fExpect) ? szExpected : szUnexpected;

    hDC=GetDC(m_hWnd);
    SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));
    SetBkColor(hDC, GetSysColor(COLOR_WINDOW));

    if (CF_WAVE < cf || 0==cf)
        {
        cch=wsprintf(szTemp, TEXT("QueryGetData on %d %s (%s)")
            , cf, psz1, psz2);
        }
    else
        {
        cch=wsprintf(szTemp, TEXT("QueryGetData on %s %s (%s)")
            , (LPTSTR)rgszCF[cf], psz1, psz2);
        }

    //Don't overwrite other painted display.
    SetBkMode(hDC, TRANSPARENT);
    TextOut(hDC, 0, 16*y, szTemp, cch);

    ReleaseDC(m_hWnd, hDC);

    return;
    }

/*
 * CApp::Paint
 *
 * Purpose:
 *  Handles WM_PAINT for the main window by drawing whatever
 *  data we have sitting in the STGMEDIUM at this time.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CApp::Paint(void)
    {
    PAINTSTRUCT     ps;
    HDC             hDC;
    HDC             hMemDC;
    LPMETAFILEPICT  pMF;
    LPTSTR          psz;
    RECT            rc;
    FORMATETC       fe;

    GetClientRect(m_hWnd, &rc);

    hDC=BeginPaint(m_hWnd, &ps);

    //May need to retrieve the data with EXE objects
    if (m_fEXE)
        {
        if (TYMED_NULL==m_stm.tymed && 0!=m_cf)
            {
            SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL
                | TYMED_MFPICT | TYMED_GDI);

            if (NULL!=m_pIDataObject)
                m_pIDataObject->GetData(&fe, &m_stm);
            }
        }

    switch (m_cf)
        {
        case CF_TEXT:
            psz=(LPTSTR)GlobalLock(m_stm.hGlobal);

            if (NULL==psz)
                break;

            SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));
            SetBkColor(hDC, GetSysColor(COLOR_WINDOW));

            DrawText(hDC, psz, lstrlen(psz), &rc
                , DT_LEFT | DT_WORDBREAK);
            GlobalUnlock(m_stm.hGlobal);
            break;

        case CF_BITMAP:
            hMemDC=CreateCompatibleDC(hDC);

            if (NULL!=SelectObject(hMemDC, (HGDIOBJ)m_stm.hGlobal))
                {
                BitBlt(hDC, 0, 0, rc.right-rc.left, rc.bottom-rc.top
                    , hMemDC, 0, 0, SRCCOPY);
                }

            DeleteDC(hMemDC);
            break;

        case CF_METAFILEPICT:
            pMF=(LPMETAFILEPICT)GlobalLock(m_stm.hGlobal);

            if (NULL==pMF)
                break;

            SetMapMode(hDC, pMF->mm);
            SetWindowOrgEx(hDC, 0, 0, NULL);
            SetWindowExtEx(hDC, pMF->xExt, pMF->yExt, NULL);

            SetViewportExtEx(hDC, rc.right-rc.left
                , rc.bottom-rc.top, NULL);

            PlayMetaFile(hDC, pMF->hMF);
            GlobalUnlock(m_stm.hGlobal);
            break;

        default:
            break;
        }

    EndPaint(m_hWnd, &ps);
    return;
    }


DATAUSER.H

/*
 * DATAUSER.H
 * Data Object User Chapter 10
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _DATAUSER_H_
#define _DATAUSER_H_

#define INC_OLE2
#define CHAPTER10
#include <inole.h>

//Menu Resource ID and Commands
#define IDR_MENU                    1

#define IDM_OBJECTUSEDLL                100
#define IDM_OBJECTUSEEXE                101
#define IDM_OBJECTDATASIZESMALL         102
#define IDM_OBJECTDATASIZEMEDIUM        103
#define IDM_OBJECTDATASIZELARGE         104
#define IDM_OBJECTQUERYGETDATA          105
#define IDM_OBJECTGETDATATEXT           106
#define IDM_OBJECTGETDATABITMAP         107
#define IDM_OBJECTGETDATAMETAFILEPICT   108
#define IDM_OBJECTEXIT                  109

#define IDM_ADVISEMIN                   200
#define IDM_ADVISETEXT                  (IDM_ADVISEMIN+CF_TEXT)
#define IDM_ADVISEBITMAP                (IDM_ADVISEMIN+CF_BITMAP)
#define IDM_ADVISEMETAFILEPICT          (IDM_ADVISEMIN+CF_METAFILEPICT)
#define IDM_ADVISEGETDATA               300
#define IDM_ADVISEREPAINT               301

//DATAUSER.CPP
LRESULT APIENTRY DataUserWndProc(HWND, UINT, WPARAM, LPARAM);

class CAdviseSink;
typedef class CAdviseSink *PCAdviseSink;

/*
 * Application-defined classes and types.
 */

class CApp
    {
    friend LRESULT APIENTRY DataUserWndProc(HWND, UINT, WPARAM
        , LPARAM);

    friend class CAdviseSink;

    protected:
        HINSTANCE       m_hInst;            //WinMain parameters
        HINSTANCE       m_hInstPrev;
        UINT            m_nCmdShow;

        HWND            m_hWnd;             //Main window handle
        BOOL            m_fEXE;             //For tracking menu

        PCAdviseSink    m_pIAdviseSink;

        DWORD           m_dwConn;           //Advise connection
        UINT            m_cfAdvise;         //Advise format
        BOOL            m_fGetData;         //GetData on data change?
        BOOL            m_fRepaint;         //Repaint on data change?

        LPDATAOBJECT    m_pIDataSmall;
        LPDATAOBJECT    m_pIDataMedium;
        LPDATAOBJECT    m_pIDataLarge;

        LPDATAOBJECT    m_pIDataObject;     //Current selection
        UINT            m_cf;
        STGMEDIUM       m_stm;              //Current rendering

        BOOL            m_fInitialized;     //Did CoInitialize work?

    public:
        CApp(HINSTANCE, HINSTANCE, UINT);
        ~CApp(void);
        BOOL Init(void);
        BOOL FReloadDataObjects(BOOL);
        void TryQueryGetData(LPFORMATETC, UINT, BOOL, UINT);
        void Paint(void);
    };

typedef CApp *PAPP;

#define CBWNDEXTRA               sizeof(PAPP)
#define DATAUSERWL_STRUCTURE     0

//This lives with the app to get OnDataChange notifications.

class CAdviseSink : public IAdviseSink
    {
    protected:
        ULONG               m_cRef;
        PAPP                m_pApp;

    public:
        CAdviseSink(PAPP);
        ~CAdviseSink(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //We only implement OnDataChange for now.
        STDMETHODIMP_(void)  OnDataChange(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP_(void)  OnViewChange(DWORD, LONG);
        STDMETHODIMP_(void)  OnRename(LPMONIKER);
        STDMETHODIMP_(void)  OnSave(void);
        STDMETHODIMP_(void)  OnClose(void);
    };

#endif //_DATAUSER_H_


IADVSINK.CPP

/*
 * IADVSINK.CPP
 * Data Object User Chapter 10
 *
 * Implementation of an object with IAdviseSink.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "datauser.h"

/*
 * CAdviseSink::CAdviseSink
 * CAdviseSink::~CAdviseSink
 *
 * Parameters (Constructor):
 *  pApp            PAPP to the application
 *
 */

CAdviseSink::CAdviseSink(PAPP pApp)
    {
    m_cRef=0;
    m_pApp=pApp;
    return;
    }

CAdviseSink::~CAdviseSink(void)
    {
    return;
    }

/*
 * CAdviseSink::QueryInterface
 * CAdviseSink::AddRef
 * CAdviseSink::Release
 *
 * Purpose:
 *  IUnknown members for CAdviseSink object.
 */

STDMETHODIMP CAdviseSink::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IAdviseSink==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CAdviseSink::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CAdviseSink::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CAdviseSink::OnDataChange
 *
 * Purpose:
 *  Notifes the advise sink that data changed in a data object.
 *  On this message you may request a new data rendering and update
 *  your displays as necessary.  Any data sent to this function is
 *  owned by the caller, not by this advise sink.
 *
 *  All Advise Sink methods are asynchronous and therefore we
 *  should attempt no synchronous calls from within them to an EXE
 *  object.  If we do, we'll get RPC_E_CALLREJECTED as shown below.
 *
 * Parameters:
 *  pFEIn           LPFORMATETC describing format that changed
 *  pSTM            LPSTGMEDIUM providing the medium in which the
 *                  data is provided.
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CAdviseSink::OnDataChange(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM)
    {
    BOOL        fUsable=TRUE;
    UINT        cf;
    STGMEDIUM   stm;

    /*
     * We first check that the changed data is, in fact, a format
     * we're interested in, either CF_TEXT, CF_BITMAP, or
     * CF_METAFILEPICT, then only in the aspects we want.  We check
     * if pSTM->tymed is TYMED_NULL or something else.  If NULL, we
     * just exit so the data object can time ADVF_NODATA trans-
     * actions.  Otherwise we verify that the data is useful and
     * repaint. If there is data in pSTM we are responsible for it.
     */

    //Ignore the m_fGetData flag for EXE objects (we can't GetData)
    if (!m_pApp->m_fGetData && !m_pApp->m_fEXE)
        return;

    //See if we're interested in the format and aspect that changed
    cf=pFE->cfFormat;

    if ((CF_TEXT!=cf && CF_BITMAP!=cf && CF_METAFILEPICT!=cf)
        || !(DVASPECT_CONTENT & pFE->dwAspect))
        return;

    //Check the medium if we got data
    switch (cf)
        {
        case CF_TEXT:
            fUsable=(BOOL)(TYMED_HGLOBAL & pFE->tymed);
            break;

        case CF_BITMAP:
            fUsable=(BOOL)(TYMED_GDI & pFE->tymed);
            break;

        case CF_METAFILEPICT:
            fUsable=(BOOL)(TYMED_MFPICT & pFE->tymed);
            break;

        default:
            break;
        }

    if (!fUsable)
        return;

    if (NULL==m_pApp->m_pIDataObject)
        return;

    /*
     * When dealing with EXE objects, invalidate ourselves
     * after setting TYMED_NULL in our STGMEDIUM that causes
     * CAppVars::Paint to request new data.  We cannot call
     * GetData in here because this is an async call when we're
     * dealing with an EXE.
     */
    if (m_pApp->m_fEXE)
        {
        ReleaseStgMedium(&(m_pApp->m_stm));
        m_pApp->m_cf=cf;
        m_pApp->m_stm.tymed=TYMED_NULL;

        InvalidateRect(m_pApp->m_hWnd, NULL, TRUE);
        return;
        }

    if (FAILED(m_pApp->m_pIDataObject->GetData(pFE, &stm)))
        return;

    //Get rid of old data and update.
    ReleaseStgMedium(&(m_pApp->m_stm));

    m_pApp->m_cf=cf;
    m_pApp->m_stm=stm;

    InvalidateRect(m_pApp->m_hWnd, NULL, TRUE);

    if (m_pApp->m_fRepaint)
        UpdateWindow(m_pApp->m_hWnd);

    return;
    }

/*
 * CAdviseSink::OnViewChange
 * CAdviseSink::OnRename
 * CAdviseSink::OnSave
 * CAdviseSink::OnClose
 *
 * Unimplemented members
 */

STDMETHODIMP_(void) CAdviseSink::OnViewChange(DWORD dwAspect
    , LONG lindex)
    {
    return;
    }

STDMETHODIMP_(void) CAdviseSink::OnRename(LPMONIKER pmk)
    {
    return;
    }

STDMETHODIMP_(void) CAdviseSink::OnSave(void)
    {
    return;
    }

STDMETHODIMP_(void) CAdviseSink::OnClose(void)
    {
    return;
    }


PRECOMP.CPP

#include "datauser.h"


DATAOBJ.CPP

/*
 * DATAOBJ.CPP
 * Data Object Chapter 10
 *
 * Implementation of CDataObject and CImpIDataObject that work
 * in either an EXE or DLL.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "dataobj.h"

extern HINSTANCE   g_hInst;

DWORD              g_dwID=0;

//Names of data sizes
static TCHAR * rgszSize[3]={TEXT("Small"), TEXT("Medium")
    , TEXT("Large")};

/*
 * CDataObject::CDataObject
 * CDataObject::~CDataObject
 *
 * Parameters (Constructor):
 *  pUnkOuter       LPUNKNOWN of controlling unknown, if it exists.
 *  pfnDestroy      PFNDESTROYED to call when object is destroyed.
 *  iSize           UINT specifying the size of the data set to use.
 */

CDataObject::CDataObject(LPUNKNOWN pUnkOuter
    , PFNDESTROYED pfnDestroy, UINT iSize)
    {
    UINT        i;

    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pfnDestroy=pfnDestroy;
    m_iSize=iSize;

    m_hWndAdvise=NULL;
    m_dwAdvFlags=ADVF_NODATA;

    //NULL any contained interfaces initially.
    m_pImpIDataObject=NULL;
    m_pIDataAdviseHolder=NULL;

    //Initilize the FORMATETCs arrays we use for EnumFormatEtc
    m_cfeGet=CFORMATETCGET;

    SETDefFormatEtc(m_rgfeGet[0], CF_METAFILEPICT, TYMED_MFPICT);
    SETDefFormatEtc(m_rgfeGet[1], CF_BITMAP, TYMED_GDI);
    SETDefFormatEtc(m_rgfeGet[2], CF_TEXT, TYMED_HGLOBAL);

    for (i=0; i < DOSIZE_CSIZES; i++)
        m_rghBmp[i]=NULL;

    return;
    }

CDataObject::~CDataObject(void)
    {
    UINT        i;

    for (i=0; i < DOSIZE_CSIZES; i++)
        {
        if (NULL!=m_rghBmp[i])
            DeleteObject(m_rghBmp[i]);
        }

    ReleaseInterface(m_pIDataAdviseHolder);
    DeleteInterfaceImp(m_pImpIDataObject);

    if (NULL!=m_hWndAdvise)
        DestroyWindow(m_hWndAdvise);

    return;
    }

/*
 * CDataObject::Init
 *
 * Purpose:
 *  Performs any intiailization of a CDataObject that's prone to
 *  failure that we also use internally before exposing the
 *  object outside.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CDataObject::Init(void)
    {
    LPUNKNOWN       pIUnknown=this;
    UINT            i;
    TCHAR           szTemp[80];
    UINT            cy;

    if (NULL!=m_pUnkOuter)
        pIUnknown=m_pUnkOuter;

    //Allocate contained interfaces.
    m_pImpIDataObject=new CImpIDataObject(this, pIUnknown);

    if (NULL==m_pImpIDataObject)
        return FALSE;

    for (i=0; i < DOSIZE_CSIZES; i++)
        {
        m_rghBmp[i]=LoadBitmap(g_hInst, MAKEINTRESOURCE(i+IDB_MIN));

        if (NULL==m_rghBmp[i])
            return FALSE;
        }

    /*
     * Register the Advise window class first time through
     * (g_dwID==0)
     */

    if (0L==g_dwID)
        {
        WNDCLASS    wc;

        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = AdvisorWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = sizeof(PCDataObject);
        wc.hInstance      = g_hInst;
        wc.hIcon          = LoadIcon(g_hInst
            , MAKEINTRESOURCE(IDR_ADVISORICON));
        wc.hCursor        = NULL;
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName   = MAKEINTRESOURCE(IDR_MENU);
        wc.lpszClassName  = TEXT("Advisor");

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

    /*
     * Create an advise window with a unique caption:
     * "<size> <module> Advisor #xx" where <size> is Small,
     * Medium, or Large, <module> is EXE or DLL, and xx is
     * counted globally every time a CDataObject is created.
     */

    g_dwID++;
   #ifdef EXEDATAOBJECT
    wsprintf(szTemp, TEXT("%s EXE Advisor #%lu")
        , (LPTSTR)rgszSize[m_iSize], g_dwID);
   #else
    wsprintf(szTemp, TEXT("%s DLL Advisor #%lu")
        , (LPTSTR)rgszSize[m_iSize], g_dwID);
   #endif

    cy=(GetSystemMetrics(SM_CYBORDER)*2)+GetSystemMetrics(SM_CYMENU)
        + GetSystemMetrics(SM_CYCAPTION);

    m_hWndAdvise=CreateWindow(TEXT("Advisor"), szTemp
        , WS_OVERLAPPED | WS_CAPTION | WS_MINIMIZEBOX | WS_BORDER
        | WS_VISIBLE, 400, 35+(g_dwID*(cy+30)), 200, cy
        , HWND_DESKTOP, NULL, g_hInst, this);

    if (NULL==m_hWndAdvise)
        return FALSE;

    return TRUE;
    }

/*
 * CDataObject::QueryInterface
 * CDataObject::AddRef
 * CDataObject::Release
 *
 * Purpose:
 *  IUnknown members for CDataObject object.
 */

STDMETHODIMP CDataObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IDataObject==riid)
        *ppv=m_pImpIDataObject;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDataObject::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDataObject::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0;
    }

/*
 * AdvisorWndProc
 *
 * Purpose:
 *  Standard window class procedure.
 */

LRESULT APIENTRY AdvisorWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCDataObject    pDO;
    DWORD           i;
    DWORD           iAdvise;
    DWORD           dwTime;
    DWORD           dwAvg;
    TCHAR           szTime[128];
    TCHAR           szTitle[80];
    HCURSOR         hCur, hCurT;

    pDO=(PCDataObject)GetWindowLong(hWnd, 0);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pDO=(PCDataObject)(((LPCREATESTRUCT)lParam)
                ->lpCreateParams);
            SetWindowLong(hWnd, 0, (LONG)pDO);
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));

        case WM_CLOSE:
            //Forbid task manager from closing us.
            return 0L;

        case WM_COMMAND:
            if (NULL==pDO->m_pIDataAdviseHolder)
                break;

            //Send IAdviseSink::OnDataChange many times.
            i=(DWORD)(LOWORD(wParam)-IDM_ADVISEITERATIONSMIN+1);
            iAdvise=(i*i)*16;

            hCur=LoadCursor(NULL, MAKEINTRESOURCE(IDC_WAIT));
            hCurT=SetCursor(hCur);
            ShowCursor(TRUE);

            dwTime=GetTickCount();

            i=0;
            while (TRUE)
                {
               #ifdef EXEDATAOBJECT
               #ifndef WIN32
                MSG     msg;

                if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
                    {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                    }
                else
               #endif
               #endif
                    {
                    pDO->m_pIDataAdviseHolder->SendOnDataChange
                        (pDO->m_pImpIDataObject, 0, ADVF_NODATA);

                    if (++i >= iAdvise)
                        break;
                    }
                }

            dwTime=GetTickCount()-dwTime;
            dwAvg=dwTime/iAdvise;

            SetCursor(hCurT);
            ShowCursor(FALSE);

            wsprintf(szTime
                , TEXT("Total=%lu ms\n\rAverage=%lu ms")
                , dwTime, dwAvg);

            GetWindowText(hWnd, szTitle, sizeof(szTitle));
            MessageBox(hWnd, szTime, szTitle, MB_OK);
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }


DATAOBJ.H

/*
 * DATAOBJ.H
 * Data Object Chapter 6
 *
 * Classes that implement the Data Object independent of whether
 * we live in a DLL or EXE.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _DATAOBJ_H_
#define _DATAOBJ_H_

#define INC_OLE2
#define CHAPTER10
#include <inole.h>

//Data size types
#define DOSIZE_SMALL        0
#define DOSIZE_MEDIUM       1
#define DOSIZE_LARGE        2
#define DOSIZE_CSIZES       3

//Data sizes
#define CCHTEXTSMALL        64      //Text grows * 16 charactrs
#define CCHTEXTMEDIUM       1024
#define CCHTEXTLARGE        16384

#define CXYBITMAPSMALL      16      //Bitmaps grow * 4 pixels
#define CXYBITMAPMEDIUM     64
#define CXYBITMAPLARGE      256

#define CRECMETAFILESMALL   4       //Metafiles grows * 9 records;
#define CRECMETAFILEMEDIUM  12      //the number of actual records
#define CRECMETAFILELARGE   36      //is the square of these

//Resources for this data object.
#define IDB_MIN             1       //Keep this DOSIZE_SMALL+1
#define IDB_16BY16          1
#define IDB_64BY64          2
#define IDB_256BY256        3
#define IDB_MAX             3

#define IDR_MENU                        1
#define IDR_ADVISORICON                 1

#define IDM_ADVISEITERATIONSMIN         100
#define IDM_ADVISEITERATIONS16          100
#define IDM_ADVISEITERATIONS64          101
#define IDM_ADVISEITERATIONS144         102
#define IDM_ADVISEITERATIONS256         103
#define IDM_ADVISEITERATIONS400         104
#define IDM_ADVISEITERATIONS572         105

/*
 * The DataObject object is implemented in its own class with its
 * own IUnknown to support aggregation.  It contains one
 * CImpIDataObject object that we use to implement the externally
 * exposed interfaces.
 */

#define CFORMATETCGET       3

//DATAOBJ.CPP
LRESULT APIENTRY AdvisorWndProc(HWND, UINT, WPARAM, LPARAM);

class CImpIDataObject;
typedef class CImpIDataObject *PCImpIDataObject;

class CDataObject : public IUnknown
    {
    friend class CImpIDataObject;
    friend LRESULT APIENTRY AdvisorWndProc(HWND, UINT
        , WPARAM, LPARAM);

    protected:
        ULONG               m_cRef;
        LPUNKNOWN           m_pUnkOuter;
        PFNDESTROYED        m_pfnDestroy;

        UINT                m_iSize;        //Data size for Render*
        HBITMAP             m_rghBmp[DOSIZE_CSIZES]; //Cache bitmaps

        HWND                m_hWndAdvise;   //Popup with Advise menu
        DWORD               m_dwAdvFlags;   //Notification flags

        //Contained interface implemetation
        PCImpIDataObject    m_pImpIDataObject;

        //Other interfaces used, implemented elsewhere
        LPDATAADVISEHOLDER  m_pIDataAdviseHolder;

        //Arrays for IDataObject::EnumFormatEtc
        ULONG               m_cfeGet;
        FORMATETC           m_rgfeGet[CFORMATETCGET];

    protected:
        //Functions for use from IDataObject::GetData
        HRESULT     RenderText(LPSTGMEDIUM);
        HRESULT     RenderBitmap(LPSTGMEDIUM);
        HRESULT     RenderMetafilePict(LPSTGMEDIUM);

    public:
        CDataObject(LPUNKNOWN, PFNDESTROYED, UINT);
        ~CDataObject(void);

        BOOL Init(void);

        //Non-delegating object IUnknown
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
    };

typedef CDataObject *PCDataObject;

/*
 * Interface implementations for the CDataObject object.
 */

class CImpIDataObject : public IDataObject
    {
    private:
        ULONG           m_cRef;
        PCDataObject    m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIDataObject(PCDataObject, LPUNKNOWN);
        ~CImpIDataObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IDataObject members
        STDMETHODIMP GetData(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP GetDataHere(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP QueryGetData(LPFORMATETC);
        STDMETHODIMP GetCanonicalFormatEtc(LPFORMATETC,LPFORMATETC);
        STDMETHODIMP SetData(LPFORMATETC, LPSTGMEDIUM, BOOL);
        STDMETHODIMP EnumFormatEtc(DWORD, LPENUMFORMATETC *);
        STDMETHODIMP DAdvise(LPFORMATETC, DWORD,  LPADVISESINK
            , DWORD *);
        STDMETHODIMP DUnadvise(DWORD);
        STDMETHODIMP EnumDAdvise(LPENUMSTATDATA *);
    };

/*
 * IEnumFORMATETC object that is created from
 * IDataObject::EnumFormatEtc.  This object lives on its own.
 */

class CEnumFormatEtc : public IEnumFORMATETC
    {
    private:
        ULONG           m_cRef;         //Object reference count
        ULONG           m_iCur;         //Current element.
        ULONG           m_cfe;          //Number of FORMATETCs in us
        LPFORMATETC     m_prgfe;        //Source of FORMATETCs

    public:
        CEnumFormatEtc(ULONG, LPFORMATETC);
        ~CEnumFormatEtc(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IEnumFORMATETC members
        STDMETHODIMP Next(ULONG, LPFORMATETC, ULONG *);
        STDMETHODIMP Skip(ULONG);
        STDMETHODIMP Reset(void);
        STDMETHODIMP Clone(IEnumFORMATETC **);
    };

typedef CEnumFormatEtc *PCEnumFormatEtc;

#endif //_DATAOBJ_H_


DDATAOBJ.CPP

/*
 * DDATAOBJ.CPP
 * Data Object DLL Chapter 10
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "ddataobj.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

//Make this global for the data object to load resources.
HINSTANCE   g_hInst=NULL;

/*
 * LibMain(32)
 *
 * Purpose:
 *  Entry point conditionally compiled for Windows NT and Windows
 *  3.1.  Provides the proper structure for each environment.
 */

#ifdef WIN32
BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    if (DLL_PROCESS_DETACH==ulReason)
        {
        //Must unregsiter the class we registered
        UnregisterClass(TEXT("Advisor"), hInstance);
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    g_hInst=hInstance;
    return TRUE;
    }

#else
int PASCAL LibMain(HINSTANCE hInstance, WORD wDataSeg
    , WORD cbHeapSize, LPSTR lpCmdLine)
    {
    if (0!=cbHeapSize)
        UnlockData(0);

    g_hInst=hInstance;
    return (int)hInstance;
    }
#endif

/*
 * DllGetClassObject
 *
 * Purpose:
 *  Provides an IClassFactory for a given CLSID that this DLL is
 *  registered to support.  This DLL is placed under the CLSID
 *  in the registration database as the InProcServer.
 *
 * Parameters:
 *  clsID           REFCLSID that identifies the class factory
 *                  desired.  Since this parameter is passed this
 *                  DLL can handle any number of objects simply
 *                  by returning different class factories here
 *                  for different CLSIDs.
 *
 *  riid            REFIID specifying the interface the caller wants
 *                  on the class object, usually IID_ClassFactory.
 *
 *  ppv             PPVOID in which to return the interface
 *                  pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, otherwise an error code.
 */

HRESULT APIENTRY DllGetClassObject(REFCLSID rclsid
    , REFIID riid, PPVOID ppv)
    {
    HRESULT        hr;
    IClassFactory *pObj;

    *ppv=NULL;

    //Check each of the three CLSIDs we support.
    if (CLSID_DataObjectSmall==rclsid)
        pObj=new CDataObjectClassFactory(DOSIZE_SMALL);

    if (CLSID_DataObjectMedium==rclsid)
        pObj=new CDataObjectClassFactory(DOSIZE_MEDIUM);

    if (CLSID_DataObjectLarge==rclsid)
        pObj=new CDataObjectClassFactory(DOSIZE_LARGE);

    if (NULL==pObj)
        return ResultFromScode(E_OUTOFMEMORY);

    hr=pObj->QueryInterface(riid, ppv);

    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

/*
 * DllCanUnloadNow
 *
 * Purpose:
 *  Answers if the DLL can be freed, that is, if there are no
 *  references to anything this DLL provides.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if nothing is using us, FALSE otherwise.
 */

STDAPI DllCanUnloadNow(void)
    {
    SCODE   sc;

    //Our answer is whether there are any object or locks
    sc=(0L==g_cObj && 0L==g_cLock) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Koala object to call when it gets destroyed.
 *  Since we're in a DLL we only track the number of objects here,
 *  letting DllCanUnloadNow take care of the rest.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;
    return;
    }

/*
 * CDataObjectClassFactory::CDataObjectClassFactory
 * CDataObjectClassFactory::~CDataObjectClassFactory
 *
 * Constructor Parameters:
 *  iSize           UINT specifying the data size for this class.
 */

CDataObjectClassFactory::CDataObjectClassFactory(UINT iSize)
    {
    m_cRef=0L;
    m_iSize=iSize;
    return;
    }

CDataObjectClassFactory::~CDataObjectClassFactory(void)
    {
    return;
    }

/*
 * CDataObjectClassFactory::QueryInterface
 * CDataObjectClassFactory::AddRef
 * CDataObjectClassFactory::Release
 */

STDMETHODIMP CDataObjectClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDataObjectClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDataObjectClassFactory::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    ObjectDestroyed();
    return 0;
    }

/*
 * CDataObjectClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a CDataObject object that supports the IDataObject
 *  and IUnknown interfaces.  If the caller asks for a different
 *  interface than these two then we fail.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we are
 *                  being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_NOINTERFACE
 *                  if we cannot support the requested interface.
 */

STDMETHODIMP CDataObjectClassFactory::CreateInstance
    (LPUNKNOWN pUnkOuter, REFIID riid, PPVOID ppvObj)
    {
    PCDataObject        pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Create the object telling it the data size to work with
    pObj=new CDataObject(pUnkOuter, ObjectDestroyed, m_iSize);

    if (NULL==pObj)
        return hr;

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    //Kill the object if initial creation or FInit failed.
    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

/*
 * CDataObjectClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the serving
 *  IClassFactory object.  When the number of locks goes to
 *  zero and the number of objects is zero, we shut down the
 *  application.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CDataObjectClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        g_cLock--;

    return NOERROR;
    }


DDATAOBJ.H

/*
 * DDATAOBJ.H
 * Data Object DLL Chapter 10
 *
 * Definitions, classes, and prototypes for a DLL that
 * provides DataObject objects to any other object user.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _DDATAOBJ_H_
#define _DDATAOBJ_H_

#include "dataobj.h"

//DDATAOBJ.CPP
void ObjectDestroyed(void);

//This class factory object creates Data Objects.

class CDataObjectClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;
        UINT            m_iSize;        //Data size for this class

    public:
        CDataObjectClassFactory(UINT);
        ~CDataObjectClassFactory(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP         CreateInstance(LPUNKNOWN, REFIID
                                 , PPVOID);
        STDMETHODIMP         LockServer(BOOL);
    };

typedef CDataObjectClassFactory *PCDataObjectClassFactory;

#endif //_DDATAOBJ_H_


IDATAOBJ.CPP

/*
 * IDATAOBJ.CPP
 * Data Object Chapter 10
 *
 * Implementation of the IDataObject interface for CDataObject.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "dataobj.h"

/*
 * CImpIDataObject::CImpIDataObject
 * CImpIDataObject::~CImpIDataObject
 *
 * Parameters (Constructor):
 *  pObj            PCDataObject of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIDataObject::CImpIDataObject(PCDataObject pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIDataObject::~CImpIDataObject(void)
    {
    return;
    }

/*
 * CImpIDataObject::QueryInterface
 * CImpIDataObject::AddRef
 * CImpIDataObject::Release
 *
 * Purpose:
 *  IUnknown members for CImpIDataObject object.
 */

STDMETHODIMP CImpIDataObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIDataObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIDataObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIDataObject::GetData
 *
 * Purpose:
 *  Retrieves data described by a specific FormatEtc into a StgMedium
 *  allocated by this function.  Used like GetClipboardData.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *  pSTM            LPSTGMEDIUM in which to return the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetData(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM)
    {
    UINT            cf=pFE->cfFormat;

    //Check the aspects we support.
    if (!(DVASPECT_CONTENT & pFE->dwAspect))
        return ResultFromScode(DATA_E_FORMATETC);

    switch (cf)
        {
        case CF_METAFILEPICT:
            if (!(TYMED_MFPICT & pFE->tymed))
                break;

            return m_pObj->RenderMetafilePict(pSTM);

        case CF_BITMAP:
            if (!(TYMED_GDI & pFE->tymed))
                break;

            return m_pObj->RenderBitmap(pSTM);

        case CF_TEXT:
            if (!(TYMED_HGLOBAL & pFE->tymed))
                break;

            return m_pObj->RenderText(pSTM);

        default:
            break;
        }

    return ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::GetDataHere
 *
 * Purpose:
 *  Renders the specific FormatEtc into caller-allocated medium
 *  provided in pSTM.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *  pSTM            LPSTGMEDIUM providing the medium into which
 *                  wer render the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetDataHere(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIDataObject::QueryGetData
 *
 * Purpose:
 *  Tests if a call to GetData with this FormatEtc will provide
 *  any rendering; used like IsClipboardFormatAvailable.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::QueryGetData(LPFORMATETC pFE)
    {
    UINT            cf=pFE->cfFormat;
    BOOL            fRet=FALSE;

    //Check the aspects we support.
    if (!(DVASPECT_CONTENT & pFE->dwAspect))
        return ResultFromScode(DATA_E_FORMATETC);

    switch (cf)
        {
        case CF_METAFILEPICT:
            fRet=(BOOL)(pFE->tymed & TYMED_MFPICT);
            break;

        case CF_BITMAP:
            fRet=(BOOL)(pFE->tymed & TYMED_GDI);
            break;

        case CF_TEXT:
            fRet=(BOOL)(pFE->tymed & TYMED_HGLOBAL);
            break;

        default:
            fRet=FALSE;
            break;
        }

    return fRet ? NOERROR : ResultFromScode(S_FALSE);
    }

/*
 * CImpIDataObject::GetCanonicalFormatEtc
 *
 * Purpose:
 *  Provides the caller with an equivalent FormatEtc to the one
 *  provided when different FormatEtcs will produce exactly the
 *  same renderings.
 *
 * Parameters:
 *  pFEIn            LPFORMATETC of the first description.
 *  pFEOut           LPFORMATETC of the equal description.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetCanonicalFormatEtc
    (LPFORMATETC pFEIn, LPFORMATETC pFEOut)
    {
    if (NULL==pFEOut)
        return ResultFromScode(E_INVALIDARG);

    pFEOut->ptd=NULL;
    return ResultFromScode(DATA_S_SAMEFORMATETC);
    }

/*
 * CImpIDataObject::SetData
 *
 * Purpose:
 *  Places data described by a FormatEtc and living in a StgMedium
 *  into the object.  The object may be responsible to clean up the
 *  StgMedium before exiting.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the data to set.
 *  pSTM            LPSTGMEDIUM containing the data.
 *  fRelease        BOOL indicating if this function is responsible
 *                  for freeing the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::SetData(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM, BOOL fRelease)
    {
    return ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::EnumFormatEtc
 *
 * Purpose:
 *  Returns an IEnumFORMATETC object through which the caller can
 *  iterate to learn about all the data formats this object can
 *  provide through either GetData[Here] or SetData.
 *
 * Parameters:
 *  dwDir           DWORD describing a data direction, either
 *                  DATADIR_SET or DATADIR_GET.
 *  ppEnum          LPENUMFORMATETC * in which to return the
 *                  pointer to the enumerator.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::EnumFormatEtc(DWORD dwDir
    , LPENUMFORMATETC *ppEnum)
    {
    switch (dwDir)
        {
        case DATADIR_GET:
            *ppEnum=new CEnumFormatEtc(m_pObj->m_cfeGet
                , m_pObj->m_rgfeGet);
            break;

        case DATADIR_SET:
            *ppEnum=NULL;
            break;

        default:
            *ppEnum=NULL;
            break;
        }

    if (NULL==*ppEnum)
        return ResultFromScode(E_FAIL);
    else
        (*ppEnum)->AddRef();

    return NOERROR;
    }

/*
 * CImpIDataObject::DAdvise
 *
 * Purpose:
 *  Provides the data object with an IAdviseSink object that we are
 *  responsible to notify when the data changes.
 *
 * Parameters:
 *  ppFE            LPFORMATETC
 *  dwFlags         DWORD carrying flags indicating how the advise
 *                  sink wants to be treated.
 *  pIAdviseSink    LPADVISESINK to the object to notify.
 *  pdwConn         LPDWORD into which we store a DWORD key
 *                  identifying the advise connection.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::DAdvise(LPFORMATETC pFE, DWORD dwFlags
    , LPADVISESINK pIAdviseSink, LPDWORD pdwConn)
    {
    HRESULT         hr;

    if (NULL==m_pObj->m_pIDataAdviseHolder)
        {
        hr=CreateDataAdviseHolder(&m_pObj->m_pIDataAdviseHolder);

        if (FAILED(hr))
            return ResultFromScode(E_OUTOFMEMORY);
        }

    hr=m_pObj->m_pIDataAdviseHolder->Advise((LPDATAOBJECT)this, pFE
        , dwFlags, pIAdviseSink, pdwConn);

    return hr;
    }

/*
 * CImpIDataObject::DUnadvise
 *
 * Purpose:
 *  Turns off advising previously set up with Advise.
 *
 * Parameters:
 *  dwConn          DWORD connection key returned from Advise.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::DUnadvise(DWORD dwConn)
    {
    HRESULT         hr;

    if (NULL==m_pObj->m_pIDataAdviseHolder)
        return ResultFromScode(E_FAIL);

    hr=m_pObj->m_pIDataAdviseHolder->Unadvise(dwConn);

    return hr;
    }

/*
 * CImpIDataObject::EnumDAdvise
 *
 * Purpose:
 *  Returns an enumerator object through which the caller can find
 *  all the agents currently receiving advises on this data object.
 *
 * Parameters:
 *  ppEnum          LPENUMSTATDATA * in which to return the
 *                  enumerator.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::EnumDAdvise(LPENUMSTATDATA *ppEnum)
    {
    HRESULT         hr;

    if (NULL==m_pObj->m_pIDataAdviseHolder)
        return ResultFromScode(E_FAIL);

    hr=m_pObj->m_pIDataAdviseHolder->EnumAdvise(ppEnum);
    return hr;
    }


IENUMFE.CPP

/*
 * IENUMFE.CPP
 * Data Object Chapter 10
 *
 * Standard implementation of a FORMATETC enumerator with the
 * IEnumFORMATETC interface that will generally not need
 * modification.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "dataobj.h"

/*
 * CEnumFormatEtc::CEnumFormatEtc
 * CEnumFormatEtc::~CEnumFormatEtc
 *
 * Parameters (Constructor):
 *  cFE             ULONG number of FORMATETCs in pFE
 *  prgFE           LPFORMATETC to the array to enumerate.
 */

CEnumFormatEtc::CEnumFormatEtc(ULONG cFE, LPFORMATETC prgFE)
    {
    UINT        i;

    m_cRef=0;
    m_iCur=0;
    m_cfe=cFE;
    m_prgfe=new FORMATETC[(UINT)cFE];

    if (NULL!=m_prgfe)
        {
        for (i=0; i < cFE; i++)
            m_prgfe[i]=prgFE[i];
        }

    return;
    }

CEnumFormatEtc::~CEnumFormatEtc(void)
    {
    if (NULL!=m_prgfe)
        delete [] m_prgfe;

    return;
    }

/*
 * CEnumFormatEtc::QueryInterface
 * CEnumFormatEtc::AddRef
 * CEnumFormatEtc::Release
 *
 * Purpose:
 *  IUnknown members for CEnumFormatEtc object.  For QueryInterface
 *  we only return out own interfaces and not those of the data
 *  object.  However, since enumerating formats only makes sense
 *  when the data object is around, we insure that it stays as
 *  long as we stay by calling an outer IUnknown for AddRef
 *  and Release.  But since we are not controlled by the lifetime
 *  of the outer object, we still keep our own reference count in
 *  order to free ourselves.
 */

STDMETHODIMP CEnumFormatEtc::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    /*
     * Enumerators are separate objects, not the data object, so
     * we only need to support out IUnknown and IEnumFORMATETC
     * interfaces here with no concern for aggregation.
     */
    if (IID_IUnknown==riid || IID_IEnumFORMATETC==riid)
        *ppv=this;

    //AddRef any interface we'll return.
    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CEnumFormatEtc::AddRef(void)
    {
    ++m_cRef;
    return m_cRef;
    }

STDMETHODIMP_(ULONG) CEnumFormatEtc::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CEnumFormatEtc::Next
 *
 * Purpose:
 *  Returns the next element in the enumeration.
 *
 * Parameters:
 *  cFE             ULONG number of FORMATETCs to return.
 *  pFE             LPFORMATETC in which to store the returned
 *                  structures.
 *  pulFE           ULONG * in which to return how many we
 *                  enumerated.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE otherwise,
 */

STDMETHODIMP CEnumFormatEtc::Next(ULONG cFE, LPFORMATETC pFE
    , ULONG *pulFE)
    {
    ULONG               cReturn=0L;

    if (NULL==m_prgfe)
        return ResultFromScode(S_FALSE);

    if (NULL==pulFE)
        {
        if (1L!=cFE)
            return ResultFromScode(E_POINTER);
        }
    else
        *pulFE=0L;

    if (NULL==pFE || m_iCur >= m_cfe)
        return ResultFromScode(S_FALSE);

    while (m_iCur < m_cfe && cFE > 0)
        {
        *pFE++=m_prgfe[m_iCur++];
        cReturn++;
        cFE--;
        }

    if (NULL!=pulFE)
        *pulFE=cReturn;

    return NOERROR;
    }

/*
 * CEnumFormatEtc::Skip
 *
 * Purpose:
 *  Skips the next n elements in the enumeration.
 *
 * Parameters:
 *  cSkip           ULONG number of elements to skip.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE if we could not
 *                  skip the requested number.
 */

STDMETHODIMP CEnumFormatEtc::Skip(ULONG cSkip)
    {
    if (((m_iCur+cSkip) >= m_cfe) || NULL==m_prgfe)
        return ResultFromScode(S_FALSE);

    m_iCur+=cSkip;
    return NOERROR;
    }

/*
 * CEnumFormatEtc::Reset
 *
 * Purpose:
 *  Resets the current element index in the enumeration to zero.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CEnumFormatEtc::Reset(void)
    {
    m_iCur=0;
    return NOERROR;
    }

/*
 * CEnumFormatEtc::Clone
 *
 * Purpose:
 *  Returns another IEnumFORMATETC with the same state as ourselves.
 *
 * Parameters:
 *  ppEnum          LPENUMFORMATETC * in which to return the
 *                  new object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CEnumFormatEtc::Clone(LPENUMFORMATETC *ppEnum)
    {
    PCEnumFormatEtc     pNew;

    *ppEnum=NULL;

    //Create the clone
    pNew=new CEnumFormatEtc(m_cfe, m_prgfe);

    if (NULL==pNew)
        return ResultFromScode(E_OUTOFMEMORY);

    pNew->AddRef();
    pNew->m_iCur=m_iCur;

    *ppEnum=pNew;
    return NOERROR;
    }


PRECOMP.CPP

#include "dataobj.h"


RENDER.CPP

/*
 * RENDER.CPP
 * Data Object Chapter 10
 *
 * CDataObject::Render* functions to create text, bitmaps, and
 * metafiles in a variety of sizes.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "dataobj.h"
#include <string.h>

/*
 * CDataObject::RenderText
 *
 * Purpose:
 *  Creates a global memory block containing the letter 'k' of sizes
 *  of 64 bytes, 1024 bytes, and 16384 bytes, into a caller-supplied
 *  STGMEDIUM.
 *
 * Parameters:
 *  pSTM            LPSTGMEDIUM in which to render.
 *
 * Return Value:
 *  HRESULT         Return value for GetData
 */

HRESULT CDataObject::RenderText(LPSTGMEDIUM pSTM)
    {
    DWORD       cch;
    HGLOBAL     hMem;
    LPTSTR      psz;
    UINT        i;

    //Get the size of data we're dealing with
    switch (m_iSize)
        {
        case DOSIZE_SMALL:
            cch=CCHTEXTSMALL;
            break;

        case DOSIZE_MEDIUM:
            cch=CCHTEXTMEDIUM;
            break;

        case DOSIZE_LARGE:
            cch=CCHTEXTLARGE;
            break;

        default:
            return ResultFromScode(E_FAIL);
        }

    hMem=GlobalAlloc(GMEM_SHARE | GMEM_MOVEABLE, cch*sizeof(TCHAR));

    if (NULL==hMem)
        return ResultFromScode(STG_E_MEDIUMFULL);

    psz=(LPTSTR)GlobalLock(hMem);

    for (i=0; i < cch-1; i++)
        *(psz+i)=TEXT(' ') + (i % 32);

    *(psz+i)=0;

    GlobalUnlock(hMem);

    pSTM->hGlobal=hMem;
    pSTM->tymed=TYMED_HGLOBAL;
    pSTM->pUnkForRelease=NULL;
    return NOERROR;
    }

/*
 * CDataObject::RenderBitmap
 *
 * Purpose:
 *  Creates a new bitmap into which we copy a bitmap loaded
 *  from our resources.
 *
 * Parameters:
 *  pSTM            LPSTGMEDIUM in which to render.
 *
 * Return Value:
 *  HRESULT         Return value for GetData
 */

HRESULT CDataObject::RenderBitmap(LPSTGMEDIUM pSTM)
    {
    HBITMAP     hBmp, hBmpT;
    UINT        cxy;
    HDC         hDC, hDCSrc, hDCDst;

    //Get the size of bitmap we're dealing with
    switch (m_iSize)
        {
        case DOSIZE_SMALL:
            cxy=CXYBITMAPSMALL;
            break;

        case DOSIZE_MEDIUM:
            cxy=CXYBITMAPMEDIUM;
            break;

        case DOSIZE_LARGE:
            cxy=CXYBITMAPLARGE;
            break;

        default:
            return ResultFromScode(E_FAIL);
        }

    //Get two memory DCs between which to BitBlt.
    hDC=GetDC(NULL);
    hDCSrc=CreateCompatibleDC(hDC);
    hDCDst=CreateCompatibleDC(hDC);
    ReleaseDC(NULL, hDC);

    if (NULL==hDCSrc || NULL==hDCDst)
        {
        if (NULL!=hDCDst)
            DeleteDC(hDCDst);

        if (NULL!=hDCSrc)
            DeleteDC(hDCSrc);

        return ResultFromScode(STG_E_MEDIUMFULL);
        }

    SelectObject(hDCSrc, m_rghBmp[m_iSize-DOSIZE_SMALL]);

    hBmp=CreateCompatibleBitmap(hDCSrc, cxy, cxy);

    if (NULL==hBmp)
        {
        DeleteDC(hDCDst);
        DeleteDC(hDCSrc);

        return ResultFromScode(STG_E_MEDIUMFULL);
        }

    //Copy from the source to destination
    hBmpT=(HBITMAP)SelectObject(hDCDst, hBmp);
    BitBlt(hDCDst, 0, 0, cxy, cxy, hDCSrc, 0, 0, SRCCOPY);
    SelectObject(hDCDst, hBmpT);

    DeleteDC(hDCDst);
    DeleteDC(hDCSrc);

    pSTM->hGlobal=(HGLOBAL)hBmp;
    pSTM->tymed=TYMED_GDI;
    pSTM->pUnkForRelease=NULL;
    return NOERROR;
    }

/*
 * CDataObject::RenderMetafilePict
 *
 * Purpose:
 *  Creates a metafile containing blue shaded bands.
 *
 * Parameters:
 *  pSTM            LPSTGMEDIUM in which to render.
 *
 * Return Value:
 *  HRESULT         Return value for GetData
 */

HRESULT CDataObject::RenderMetafilePict(LPSTGMEDIUM pSTM)
    {
    HDC             hDC;
    HGLOBAL         hMem;
    HMETAFILE       hMF;
    LPMETAFILEPICT  pMF;
    HBRUSH          hBrush;
    HGDIOBJ         hBrT;
    UINT            cRec;
    int             x, y, dxy;
    RECT            rc;

    switch (m_iSize)
        {
        case DOSIZE_SMALL:
            cRec=CRECMETAFILESMALL;
            break;

        case DOSIZE_MEDIUM:
            cRec=CRECMETAFILEMEDIUM;
            break;

        case DOSIZE_LARGE:
            cRec=CRECMETAFILELARGE;
            break;

        default:
            return ResultFromScode(E_FAIL);
        }

    hDC=(HDC)CreateMetaFile(NULL);

    if (NULL!=hDC)
        {
        /*
         * Draw something into the metafile.  For this object we
         * draw some number of rectangles equal to the number of
         * records we want.  So take the square root of the number
         * of records and iterate over that number in both x & y.
         */

        dxy=(int)1024/cRec;

        //This creates a blue shading from light to dark
        for (y=1024; y >=0; y-=dxy)
            {
            hBrush=CreateSolidBrush(RGB(0, 0, (1024-y)/4));
            hBrT=SelectObject(hDC, (HGDIOBJ)hBrush);

            for (x=0; x < 1024; x+=dxy)
                {
                SetRect(&rc, x, y, x+dxy, y+dxy);
                FillRect(hDC, &rc, hBrush);
                }

            SelectObject(hDC, hBrT);
            DeleteObject(hBrush);
            }

        hMF=CloseMetaFile(hDC);
        }

    if (NULL==hMF)
        return ResultFromScode(STG_E_MEDIUMFULL);

    //Allocate the METAFILEPICT structure.
    hMem=GlobalAlloc(GMEM_SHARE | GMEM_MOVEABLE
        , sizeof(METAFILEPICT));

    if (NULL==hMem)
        {
        DeleteMetaFile(hMF);
        return ResultFromScode(STG_E_MEDIUMFULL);
        }

    pMF=(LPMETAFILEPICT)GlobalLock(hMem);

    pMF->hMF=hMF;
    pMF->mm=MM_ANISOTROPIC;
    pMF->xExt=1024;
    pMF->yExt=1024;

    GlobalUnlock(hMem);

    pSTM->hGlobal=hMem;
    pSTM->tymed=TYMED_MFPICT;
    pSTM->pUnkForRelease=NULL;
    return NOERROR;
    }


DATAOBJ.CPP

/*
 * DATAOBJ.CPP
 * Data Object Chapter 19
 *
 * Implementation of CDataObject and CImpIDataObject that work
 * in either an EXE or DLL.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "dataobj.h"

extern HINSTANCE   g_hInst;

DWORD              g_dwID=0;

//Names of data sizes
static TCHAR * rgszSize[3]={TEXT("Small"), TEXT("Medium")
    , TEXT("Large")};

/*
 * CDataObject::CDataObject
 * CDataObject::~CDataObject
 *
 * Parameters (Constructor):
 *  pUnkOuter       LPUNKNOWN of controlling unknown, if it exists.
 *  pfnDestroy      PFNDESTROYED to call when object is destroyed.
 *  iSize           UINT specifying the size of the data set to use.
 */

CDataObject::CDataObject(LPUNKNOWN pUnkOuter
    , PFNDESTROYED pfnDestroy, UINT iSize)
    {
    UINT        i;

    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pfnDestroy=pfnDestroy;
    m_iSize=iSize;

    m_hWndAdvise=NULL;
    m_dwAdvFlags=ADVF_NODATA;

    //NULL any contained interfaces initially.
    m_pImpIDataObject=NULL;
    m_pIDataAdviseHolder=NULL;

    //Initilize the FORMATETCs arrays we use for EnumFormatEtc
    m_cfeGet=CFORMATETCGET;

    SETDefFormatEtc(m_rgfeGet[0], CF_METAFILEPICT, TYMED_MFPICT);
    SETDefFormatEtc(m_rgfeGet[1], CF_BITMAP, TYMED_GDI);
    SETDefFormatEtc(m_rgfeGet[2], CF_TEXT, TYMED_HGLOBAL);

    for (i=0; i < DOSIZE_CSIZES; i++)
        m_rghBmp[i]=NULL;

    return;
    }

CDataObject::~CDataObject(void)
    {
    UINT        i;

    for (i=0; i < DOSIZE_CSIZES; i++)
        {
        if (NULL!=m_rghBmp[i])
            DeleteObject(m_rghBmp[i]);
        }

    ReleaseInterface(m_pIDataAdviseHolder);
    DeleteInterfaceImp(m_pImpIDataObject);

    if (NULL!=m_hWndAdvise)
        DestroyWindow(m_hWndAdvise);

    return;
    }

/*
 * CDataObject::Init
 *
 * Purpose:
 *  Performs any intiailization of a CDataObject that's prone to
 *  failure that we also use internally before exposing the
 *  object outside.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CDataObject::Init(void)
    {
    LPUNKNOWN       pIUnknown=this;
    UINT            i;
    TCHAR           szTemp[80];
    UINT            cy;

    if (NULL!=m_pUnkOuter)
        pIUnknown=m_pUnkOuter;

    //Allocate contained interfaces.
    m_pImpIDataObject=new CImpIDataObject(this, pIUnknown);

    if (NULL==m_pImpIDataObject)
        return FALSE;

    for (i=0; i < DOSIZE_CSIZES; i++)
        {
        m_rghBmp[i]=LoadBitmap(g_hInst, MAKEINTRESOURCE(i+IDB_MIN));

        if (NULL==m_rghBmp[i])
            return FALSE;
        }

    /*
     * Register the Advise window class first time through
     * (g_dwID==0)
     */

    if (0L==g_dwID)
        {
        WNDCLASS    wc;

        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = AdvisorWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = sizeof(PCDataObject);
        wc.hInstance      = g_hInst;
        wc.hIcon          = LoadIcon(g_hInst
            , MAKEINTRESOURCE(IDR_ADVISORICON));
        wc.hCursor        = NULL;
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName   = MAKEINTRESOURCE(IDR_MENU);
        wc.lpszClassName  = TEXT("Advisor");

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

    /*
     * Create an advise window with a unique caption:
     * "<size> <module> Advisor #xx" where <size> is Small,
     * Medium, or Large, <module> is EXE or DLL, and xx is
     * counted globally every time a CDataObject is created.
     */

    g_dwID++;
   #ifdef EXEDATAOBJECT
    wsprintf(szTemp, TEXT("%s EXE Advisor #%lu")
        , (LPTSTR)rgszSize[m_iSize], g_dwID);
   #else
    wsprintf(szTemp, TEXT("%s DLL Advisor #%lu")
        , (LPTSTR)rgszSize[m_iSize], g_dwID);
   #endif

    cy=(GetSystemMetrics(SM_CYBORDER)*2)+GetSystemMetrics(SM_CYMENU)
        + GetSystemMetrics(SM_CYCAPTION);

    m_hWndAdvise=CreateWindow(TEXT("Advisor"), szTemp
        , WS_OVERLAPPED | WS_CAPTION | WS_MINIMIZEBOX | WS_BORDER
        | WS_VISIBLE, 400, 35+(g_dwID*(cy+30)), 200, cy
        , HWND_DESKTOP, NULL, g_hInst, this);

    if (NULL==m_hWndAdvise)
        return FALSE;

    return TRUE;
    }

/*
 * CDataObject::QueryInterface
 * CDataObject::AddRef
 * CDataObject::Release
 *
 * Purpose:
 *  IUnknown members for CDataObject object.
 */

STDMETHODIMP CDataObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IDataObject==riid)
        *ppv=m_pImpIDataObject;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDataObject::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDataObject::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0;
    }

/*
 * AdvisorWndProc
 *
 * Purpose:
 *  Standard window class procedure.
 */

LRESULT APIENTRY AdvisorWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCDataObject    pDO;
    DWORD           i;
    DWORD           iAdvise;
    DWORD           dwTime;
    DWORD           dwAvg;
    TCHAR           szTime[128];
    TCHAR           szTitle[80];
    HCURSOR         hCur, hCurT;

    pDO=(PCDataObject)GetWindowLong(hWnd, 0);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pDO=(PCDataObject)(((LPCREATESTRUCT)lParam)
                ->lpCreateParams);
            SetWindowLong(hWnd, 0, (LONG)pDO);
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));

        case WM_CLOSE:
            //Forbid task manager from closing us.
            return 0L;

        case WM_COMMAND:
            if (NULL==pDO->m_pIDataAdviseHolder)
                break;

            //Send IAdviseSink::OnDataChange many times.
            i=(DWORD)(LOWORD(wParam)-IDM_ADVISEITERATIONSMIN+1);
            iAdvise=(i*i)*16;

            hCur=LoadCursor(NULL, MAKEINTRESOURCE(IDC_WAIT));
            hCurT=SetCursor(hCur);
            ShowCursor(TRUE);

            dwTime=GetTickCount();

            i=0;
            while (TRUE)
                {
               #ifdef EXEDATAOBJECT
               #ifndef WIN32
                MSG     msg;

                if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
                    {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                    }
                else
               #endif
               #endif
                    {
                    pDO->m_pIDataAdviseHolder->SendOnDataChange
                        (pDO->m_pImpIDataObject, 0, ADVF_NODATA);

                    if (++i >= iAdvise)
                        break;
                    }
                }

            dwTime=GetTickCount()-dwTime;
            dwAvg=dwTime/iAdvise;

            SetCursor(hCurT);
            ShowCursor(FALSE);

            wsprintf(szTime
                , TEXT("Total=%lu ms\n\rAverage=%lu ms")
                , dwTime, dwAvg);

            GetWindowText(hWnd, szTitle, sizeof(szTitle));
            MessageBox(hWnd, szTime, szTitle, MB_OK);
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }


DATAOBJ.H

/*
 * DATAOBJ.H
 * Data Object Chapter 10
 *
 * Classes that implement the Data Object independent of whether
 * we live in a DLL or EXE.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _DATAOBJ_H_
#define _DATAOBJ_H_

#define INC_OLE2
#define CHAPTER10
#include <inole.h>

//Data size types
#define DOSIZE_SMALL        0
#define DOSIZE_MEDIUM       1
#define DOSIZE_LARGE        2
#define DOSIZE_CSIZES       3

//Data sizes
#define CCHTEXTSMALL        64      //Text grows * 16 charactrs
#define CCHTEXTMEDIUM       1024
#define CCHTEXTLARGE        16384

#define CXYBITMAPSMALL      16      //Bitmaps grow * 4 pixels
#define CXYBITMAPMEDIUM     64
#define CXYBITMAPLARGE      256

#define CRECMETAFILESMALL   4       //Metafiles grows * 9 records;
#define CRECMETAFILEMEDIUM  12      //the number of actual records
#define CRECMETAFILELARGE   36      //is the square of these

//Resources for this data object.
#define IDB_MIN             1       //Keep this DOSIZE_SMALL+1
#define IDB_16BY16          1
#define IDB_64BY64          2
#define IDB_256BY256        3
#define IDB_MAX             3

#define IDR_MENU                        1
#define IDR_ADVISORICON                 1

#define IDM_ADVISEITERATIONSMIN         100
#define IDM_ADVISEITERATIONS16          100
#define IDM_ADVISEITERATIONS64          101
#define IDM_ADVISEITERATIONS144         102
#define IDM_ADVISEITERATIONS256         103
#define IDM_ADVISEITERATIONS400         104
#define IDM_ADVISEITERATIONS572         105

/*
 * The DataObject object is implemented in its own class with its
 * own IUnknown to support aggregation.  It contains one
 * CImpIDataObject object that we use to implement the externally
 * exposed interfaces.
 */

#define CFORMATETCGET       3

//DATAOBJ.CPP
LRESULT APIENTRY AdvisorWndProc(HWND, UINT, WPARAM, LPARAM);

class CImpIDataObject;
typedef class CImpIDataObject *PCImpIDataObject;

class CDataObject : public IUnknown
    {
    friend class CImpIDataObject;
    friend LRESULT APIENTRY AdvisorWndProc(HWND, UINT
        , WPARAM, LPARAM);

    protected:
        ULONG               m_cRef;
        LPUNKNOWN           m_pUnkOuter;
        PFNDESTROYED        m_pfnDestroy;

        UINT                m_iSize;        //Data size for Render*
        HBITMAP             m_rghBmp[DOSIZE_CSIZES]; //Cache bitmaps

        HWND                m_hWndAdvise;   //Popup with Advise menu
        DWORD               m_dwAdvFlags;   //Notification flags

        //Contained interface implemetation
        PCImpIDataObject    m_pImpIDataObject;

        //Other interfaces used, implemented elsewhere
        LPDATAADVISEHOLDER  m_pIDataAdviseHolder;

        //Arrays for IDataObject::EnumFormatEtc
        ULONG               m_cfeGet;
        FORMATETC           m_rgfeGet[CFORMATETCGET];

    protected:
        //Functions for use from IDataObject::GetData
        HRESULT     RenderText(LPSTGMEDIUM);
        HRESULT     RenderBitmap(LPSTGMEDIUM);
        HRESULT     RenderMetafilePict(LPSTGMEDIUM);

    public:
        CDataObject(LPUNKNOWN, PFNDESTROYED, UINT);
        ~CDataObject(void);

        BOOL Init(void);

        //Non-delegating object IUnknown
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
    };

typedef CDataObject *PCDataObject;

/*
 * Interface implementations for the CDataObject object.
 */

class CImpIDataObject : public IDataObject
    {
    private:
        ULONG           m_cRef;
        PCDataObject    m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIDataObject(PCDataObject, LPUNKNOWN);
        ~CImpIDataObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IDataObject members
        STDMETHODIMP GetData(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP GetDataHere(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP QueryGetData(LPFORMATETC);
        STDMETHODIMP GetCanonicalFormatEtc(LPFORMATETC,LPFORMATETC);
        STDMETHODIMP SetData(LPFORMATETC, LPSTGMEDIUM, BOOL);
        STDMETHODIMP EnumFormatEtc(DWORD, LPENUMFORMATETC *);
        STDMETHODIMP DAdvise(LPFORMATETC, DWORD,  LPADVISESINK
            , DWORD *);
        STDMETHODIMP DUnadvise(DWORD);
        STDMETHODIMP EnumDAdvise(LPENUMSTATDATA *);
    };

/*
 * IEnumFORMATETC object that is created from
 * IDataObject::EnumFormatEtc.  This object lives on its own.
 */

class CEnumFormatEtc : public IEnumFORMATETC
    {
    private:
        ULONG           m_cRef;         //Object reference count
        ULONG           m_iCur;         //Current element.
        ULONG           m_cfe;          //Number of FORMATETCs in us
        LPFORMATETC     m_prgfe;        //Source of FORMATETCs

    public:
        CEnumFormatEtc(ULONG, LPFORMATETC);
        ~CEnumFormatEtc(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IEnumFORMATETC members
        STDMETHODIMP Next(ULONG, LPFORMATETC, ULONG *);
        STDMETHODIMP Skip(ULONG);
        STDMETHODIMP Reset(void);
        STDMETHODIMP Clone(IEnumFORMATETC **);
    };

typedef CEnumFormatEtc *PCEnumFormatEtc;

#endif //_DATAOBJ_H_


EDATAOBJ.CPP

/*
 * EDATAOBJ.CPP
 * Data Object EXE Chapter 10
 *
 * Data Object implemented in an application.  This object supports
 * IUnknown and IDataObject interfaces.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "edataobj.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

//Make window handle global so other code can cause a shutdown
HWND        g_hWnd=NULL;
HINSTANCE   g_hInst=NULL;

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    MSG     msg;
    PAPP    pApp;

    SETMESSAGEQUEUE(96);

    g_hInst=hInst;

    pApp=new CApp(hInst, hInstPrev, pszCmdLine, nCmdShow);

    if (NULL==pApp)
        return -1;

    if (pApp->Init())
        {
        while (GetMessage(&msg, NULL, 0,0 ))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    delete pApp;
    return msg.wParam;
    }

/*
 * DataObjectWndProc
 *
 * Purpose:
 *  Standard window class procedure.
 */

LRESULT APIENTRY DataObjectWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    switch (iMsg)
        {
        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the DataObject object to call when it gets
 *  destroyed. We destroy the main window if the proper
 *  conditions are met for shutdown.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;

    //No more objects and no locks, shut the app down.
    if (0L==g_cObj && 0L==g_cLock && IsWindow(g_hWnd))
        PostMessage(g_hWnd, WM_CLOSE, 0, 0L);

    return;
    }

/*
 * CApp::CApp
 * CApp::~CApp
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the Application from WinMain
 *  hInstPrev       HINSTANCE of a previous instance from WinMain
 *  pszCmdLine      LPSTR of the command line.
 *  nCmdShow        UINT specifying how to show the app window,
 *                  from WinMain.
 */

CApp::CApp(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, UINT nCmdShow)
    {
    UINT        i;

    m_hInst     =hInst;
    m_hInstPrev =hInstPrev;
    m_pszCmdLine=pszCmdLine;

    m_hWnd=NULL;

    for (i=0; i < DOSIZE_CSIZES; i++)
        {
        m_rgdwRegCO[i]=0;
        m_rgpIClassFactory[i]=NULL;
        }

    m_fInitialized=FALSE;
    return;
    }

CApp::~CApp(void)
    {
    UINT        i;

    //Revoke and destroy the class factories of all sizes
    for (i=0; i < DOSIZE_CSIZES; i++)
        {
        if (0L!=m_rgdwRegCO[i])
            CoRevokeClassObject(m_rgdwRegCO[i]);

        ReleaseInterface(m_rgpIClassFactory[i]);
        }

    if (m_fInitialized)
        CoUninitialize();

    return;
    }

/*
 * CApp::Init
 *
 * Purpose:
 *  Initializes an CApp object by registering window classes,
 *  creating the main window, and doing anything else prone to
 *  failure.  If this function fails the caller should guarantee
 *  that the destructor is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::Init(void)
    {
    WNDCLASS        wc;
    HRESULT         hr, hr2, hr3;
    UINT            i;

    CHECKVER_COM;

    //Check command line for -Embedding
    if (lstrcmpiA(m_pszCmdLine, "-Embedding"))
        return FALSE;

    if (FAILED(CoInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    if (!m_hInstPrev)
        {
        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = DataObjectWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = 0;
        wc.hInstance      = m_hInst;
        wc.hIcon          = NULL;
        wc.hCursor        = NULL;
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName   = NULL;
        wc.lpszClassName  = TEXT("EXEDataObject");

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

    m_hWnd=CreateWindow(TEXT("EXEDataObject")
        , TEXT("EXE DataObject"), WS_OVERLAPPEDWINDOW
        , 35, 35, 350, 250, NULL, NULL, m_hInst, NULL);

    if (NULL==m_hWnd)
        return FALSE;

    g_hWnd=m_hWnd;

    /*
     * This code supplies three different classes, one for each type
     * of data object that handles a different size of data. All the
     * class factories share the same implementation, but their
     * instantiations differ by the type passed in the constructor.
     * When the class factories create objects, they pass that size
     * to the CDataObject contstructor as well.
     */

    for (i=0; i < DOSIZE_CSIZES; i++)
        {
        m_rgpIClassFactory[i]=new CDataObjectClassFactory(i);

        if (NULL==m_rgpIClassFactory[i])
            return FALSE;

        m_rgpIClassFactory[i]->AddRef();
        }

    hr=CoRegisterClassObject(CLSID_DataObjectSmall
        , m_rgpIClassFactory[0], CLSCTX_LOCAL_SERVER
        , REGCLS_MULTIPLEUSE, &m_rgdwRegCO[0]);

    hr2=CoRegisterClassObject(CLSID_DataObjectMedium
        , m_rgpIClassFactory[1], CLSCTX_LOCAL_SERVER
        , REGCLS_MULTIPLEUSE, &m_rgdwRegCO[1]);

    hr3=CoRegisterClassObject(CLSID_DataObjectLarge
        , m_rgpIClassFactory[2], CLSCTX_LOCAL_SERVER
        , REGCLS_MULTIPLEUSE, &m_rgdwRegCO[2]);

    if (FAILED(hr) || FAILED(hr2) || FAILED(hr3))
        return FALSE;

    return TRUE;
    }

/*
 * CDataObjectClassFactory::CDataObjectClassFactory
 * CDataObjectClassFactory::~CDataObjectClassFactory
 *
 * Constructor Parameters:
 *  iSize           UINT specifying the data size for this class.
 */

CDataObjectClassFactory::CDataObjectClassFactory(UINT iSize)
    {
    m_cRef=0L;
    m_iSize=iSize;
    return;
    }

CDataObjectClassFactory::~CDataObjectClassFactory(void)
    {
    return;
    }

/*
 * CDataObjectClassFactory::QueryInterface
 * CDataObjectClassFactory::AddRef
 * CDataObjectClassFactory::Release
 */

STDMETHODIMP CDataObjectClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDataObjectClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDataObjectClassFactory::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDataObjectClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a CDataObject object that supports the IDataObject
 *  and IUnknown interfaces.  If the caller asks for a different
 *  interface than these two then we fail.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we are
 *                  being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired interface
 *                  pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise contains
 *                  E_NOINTERFACE if we cannot support the
 *                  requested interface.
 */

STDMETHODIMP CDataObjectClassFactory::CreateInstance
    (LPUNKNOWN pUnkOuter, REFIID riid, PPVOID ppvObj)
    {
    PCDataObject        pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Create the object telling it the data size to work with
    pObj=new CDataObject(pUnkOuter, ObjectDestroyed, m_iSize);

    if (NULL==pObj)
        return hr;

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    g_cObj++;

    if (FAILED(hr))
        {
        delete pObj;
        ObjectDestroyed();  //Decrements g_cObj
        }

    return hr;
    }

/*
 * CDataObjectClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the serving
 *  IClassFactory object.  When the number of locks goes to
 *  zero and the number of objects is zero, we shut down the
 *  application.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CDataObjectClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        {
        g_cLock--;

        g_cObj++;
        ObjectDestroyed();
        }

    return NOERROR;
    }


EDATAOBJ.H

/*
 * EDATAOBJ.H
 * Data Object EXE Chapter 10
 *
 * Definitions, classes, and prototypes for an application that
 * provides DataObject objects to any other object user.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _EDATAOBJ_H_
#define _EDATAOBJ_H_

#include "dataobj.h"

//EDATAOBJ.CPP
LRESULT APIENTRY DataObjectWndProc(HWND, UINT, WPARAM, LPARAM);

class CApp
    {
    friend LRESULT APIENTRY DataObjectWndProc(HWND, UINT
        , WPARAM, LPARAM);

    protected:
        HINSTANCE       m_hInst;            //WinMain parameters
        HINSTANCE       m_hInstPrev;
        LPSTR           m_pszCmdLine;

        HWND            m_hWnd;             //Main window handle
        BOOL            m_fInitialized;     //Did CoInitialize work?

        //We have multiple classes, one for each data size.
        DWORD           m_rgdwRegCO[DOSIZE_CSIZES];
        LPCLASSFACTORY  m_rgpIClassFactory[DOSIZE_CSIZES];

    public:
        CApp(HINSTANCE, HINSTANCE, LPSTR, UINT);
        ~CApp(void);
        BOOL Init(void);
    };

typedef CApp *PAPP;

void ObjectDestroyed(void);

//This class factory object creates Data Objects.

class CDataObjectClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;
        UINT            m_iSize;        //Data size for this class

    public:
        CDataObjectClassFactory(UINT);
        ~CDataObjectClassFactory(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP         CreateInstance(LPUNKNOWN, REFIID
                                 , PPVOID);
        STDMETHODIMP         LockServer(BOOL);
    };

typedef CDataObjectClassFactory *PCDataObjectClassFactory;

#endif //_EDATAOBJ_H_


DLLPOLY.CPP

/*
 * DLLPOLY.CPP
 * Polyline Component Chapter 10
 *
 * Polyline component object used in CoCosmo that supports a custom
 * interface IPolyline.  Contains DLL entry code and the component
 * object exports DllGetClassObject and DllCanUnloadNow and the
 * class factory object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "polyline.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

//DLL Instance handle
HINSTANCE   g_hInst=0;

/*
 * LibMain32 (Win32)
 *
 * Purpose:
 *  Entry point for Win32 DLLs that calls the Win16 LibMain.
 */

#ifdef WIN32
BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    if (DLL_PROCESS_DETACH==ulReason)
        {
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    return (0!=LibMain(hInstance, 0,  0, NULL));
    }
#endif

/*
 * LibMain (also called from Win32 LibMain32)
 *
 * Purpose:
 *  DLL-specific entry point called from LibEntry.
 */

int PASCAL LibMain(HINSTANCE hInst, WORD wDataSeg
    , WORD cbHeapSize, LPSTR lpCmdLine)
    {
    WNDCLASS    wc;

    if (GetClassInfo(hInst, SZCLASSPOLYLINE, &wc))
        return (int)hInst;

   #ifndef WIN32
    if (0!=cbHeapSize)
        UnlockData(0);
   #endif

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.hInstance     = hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PolylineWndProc;
    wc.cbWndExtra    = CBPOLYLINEWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_CROSS);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPOLYLINE;

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

    g_hInst=hInst;
    return (int)hInst;
    }

/*
 * DllGetClassObject
 *
 * Purpose:
 *  Provides an IClassFactory for a given CLSID that this DLL is
 *  registered to support.  This DLL is placed under the CLSID
 *  in the registration database as the InProcServer.
 *
 * Parameters:
 *  clsID           REFCLSID that identifies the class factory
 *                  desired.  Since this parameter is passed this
 *                  DLL can handle any number of objects simply
 *                  by returning different class factories here
 *                  for different CLSIDs.
 *
 *  riid            REFIID specifying the interface the caller wants
 *                  on the class object, usually IID_ClassFactory.
 *
 *  ppv             PPVOID in which to return the interface
 *                  pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, otherwise an error code.
 */

HRESULT APIENTRY DllGetClassObject(REFCLSID rclsid
    , REFIID riid, PPVOID ppv)
    {
    //CHAPTER10MOD
    if (CLSID_Polyline10!=rclsid)
        return ResultFromScode(E_FAIL);
    //End CHAPTER10MOD

    //Check that we can provide the interface
    if (IID_IUnknown!=riid && IID_IClassFactory!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Return our IClassFactory for Polyline objects
    *ppv=new CPolylineClassFactory;

    if (NULL==*ppv)
        return ResultFromScode(E_OUTOFMEMORY);

    //AddRef the object through any interface we return
    ((LPUNKNOWN)*ppv)->AddRef();
    g_cObj++;
    return NOERROR;
    }

/*
 * DllCanUnloadNow
 *
 * Purpose:
 *  Answers if the DLL can be freed, that is, if there are no
 *  references to anything this DLL provides.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if nothing is using us, FALSE otherwise.
 */

STDAPI DllCanUnloadNow(void)
    {
    SCODE   sc;

    //Our answer is whether there are any object or locks
    sc=(0L==g_cObj && 0L==g_cLock) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Polyline object to call when it gets destroyed.
 *  Since we're in a DLL we only track the number of objects here
 *  letting DllCanUnloadNow take care of the rest.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;
    return;
    }

/*
 * CPolylineClassFactory::CPolylineClassFactory
 * CPolylineClassFactory::~CPolylineClassFactory
 */

CPolylineClassFactory::CPolylineClassFactory(void)
    {
    m_cRef=0L;
    return;
    }

CPolylineClassFactory::~CPolylineClassFactory(void)
    {
    return;
    }

/*
 * CPolylineClassFactory::QueryInterface
 * CPolylineClassFactory::AddRef
 * CPolylineClassFactory::Release
 */

STDMETHODIMP CPolylineClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CPolylineClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPolylineClassFactory::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    ObjectDestroyed();
    return 0L;
    }

/*
 * CPolylineClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a Polyline object.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we
 *                  are being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_NOINTERFACE
 *                  if we cannot support the requested interface.
 */

STDMETHODIMP CPolylineClassFactory::CreateInstance
    (LPUNKNOWN pUnkOuter, REFIID riid, PPVOID ppvObj)
    {
    PCPolyline          pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //Verify that a controlling unknown asks for IUnknown
    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Create the object.  This also creates a window.
    pObj=new CPolyline(pUnkOuter, ObjectDestroyed, g_hInst);

    if (NULL==pObj)
        return hr;

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    //Kill the object if initial creation or Init failed.
    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

/*
 * CPolylineClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the DLL.  If the lock
 *  count goes to zero and there are no objects, the DLL is allowed
 *  to unload.  See DllCanUnloadNow.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CPolylineClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        g_cLock--;

    return NOERROR;
    }


ICONNPT.CPP

/*
 * ICONNPT.CPP
 * Polyline Component Chapter 10
 *
 * Implementation of CImpIConnectionPoint for the Polyline object
 * as well as CConnectionPoint.
 *
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIConnPtCont:CImpIConnPtCont
 * CImpIConnPtCont::~CImpIConnPtCont
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIConnPtCont::CImpIConnPtCont(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIConnPtCont::~CImpIConnPtCont(void)
    {
    return;
    }

/*
 * CImpIConnPtCont::QueryInterface
 * CImpIConnPtCont::AddRef
 * CImpIConnPtCont::Release
 */

STDMETHODIMP CImpIConnPtCont::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIConnPtCont::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIConnPtCont::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIConnPtCont::EnumConnectionPoints
 *
 * Purpose:
 *  Not implemented.
 *
 * Return Value:
 *  HRESULT         E_NOTIMPL
 */

STDMETHODIMP CImpIConnPtCont::EnumConnectionPoints
    (LPENUMCONNECTIONPOINTS *ppEnum)
    {
    *ppEnum=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIConnPtCont::FindConnectionPoint
 *
 * Purpose:
 *  Returns a pointer to the IConnectionPoint for a given
 *  outgoing IID.
 *
 * Parameters:
 *  riid            REFIID of the outgoing interface for which
 *                  a connection point is desired.
 *  ppCP            IConnectionPoint ** in which to return
 *                  the pointer after calling AddRef.
 *
 * Return Value:
 *  HRESULT         NOERROR if the connection point is found,
 *                  E_NOINTERFACE if it's not supported.
 */

STDMETHODIMP CImpIConnPtCont::FindConnectionPoint(REFIID riid
    , IConnectionPoint **ppCP)
    {
    *ppCP=NULL;

    //CHAPTER10MOD
    if (IID_IPolylineAdviseSink10==riid)
    //End CHAPTER10MOD
        {
        return m_pObj->m_pConnPt->QueryInterface
            (IID_IConnectionPoint, (PPVOID)ppCP);
        }

    return ResultFromScode(E_NOINTERFACE);
    }

//CConnectionPoint implementation

/*
 * CConnectionPoint::CConnectionPoint
 * CConnectionPoint::~CConnectionPoint
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.  We can
 *                  query this for the IConnectionPointContainer
 *                  interface we might need.
 */

CConnectionPoint::CConnectionPoint(PCPolyline pObj)
    {
    m_cRef=0;

    /*
     * Our lifetime is controlled by the connectable object itself,
     * although other external clients will call AddRef and Release.
     * Since we're nested in the connectable object's lifetime,
     * there's no need to call AddRef on pObj.
     */
    m_pObj=pObj;
    return;
    }

CConnectionPoint::~CConnectionPoint(void)
    {
    ReleaseInterface(m_pObj->m_pAdv);
    return;
    }

/*
 * CConnectionPoint::QueryInterface
 * CConnectionPoint::AddRef
 * CConnectionPoint::Release
 *
 * Purpose:
 *  Non-delegating IUnknown members for CConnectionPoint.
 */

STDMETHODIMP CConnectionPoint::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IConnectionPoint==riid)
        *ppv=(LPVOID)this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CConnectionPoint::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CConnectionPoint::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CConnectionPoint::GetConnectionInterface
 *
 * Purpose:
 *  Returns the IID of the outgoing interface supported through
 *  this connection point.
 *
 * Parameters:
 *  pIID            IID * in which to store the IID.
 */

STDMETHODIMP CConnectionPoint::GetConnectionInterface(IID *pIID)
    {
    if (NULL==pIID)
        return ResultFromScode(E_POINTER);

    //CHAPTER10MOD
    *pIID=IID_IPolylineAdviseSink10;
    //End CHAPTER10MOD
    return NOERROR;
    }

/*
 * CConnectionPoint::GetConnectionPointContainer
 *
 * Purpose:
 *  Returns a pointer to the IConnectionPointContainer that
 *  is manageing this connection point.
 *
 * Parameters:
 *  ppCPC           IConnectionPointContainer ** in which to return
 *                  the pointer after calling AddRef.
 */

STDMETHODIMP CConnectionPoint::GetConnectionPointContainer
    (IConnectionPointContainer **ppCPC)
    {
    return m_pObj->QueryInterface(IID_IConnectionPointContainer
        , (void **)ppCPC);
    }

/*
 * CConnectionPoint::Advise
 *
 * Purpose:
 *  Provides this connection point with a notification sink to
 *  call whenever the appropriate outgoing function/event occurs.
 *
 * Parameters:
 *  pUnkSink        LPUNKNOWN to the sink to notify.  The connection
 *                  point must QueryInterface on this pointer to obtain
 *                  the proper interface to call.  The connection
 *                  point must also insure that any pointer held has
 *                  a reference count (QueryInterface will do it).
 *  pdwCookie       DWORD * in which to store the connection key for
 *                  later calls to Unadvise.
 */

STDMETHODIMP CConnectionPoint::Advise(LPUNKNOWN pUnkSink
    , DWORD *pdwCookie)
    {
    //CHAPTER10MOD
    IPolylineAdviseSink10  *pSink;
    //End CHAPTER10MOD

    *pdwCookie=0;

    //Only allow one connection
    if (NULL!=m_pObj->m_pAdv)
        return ResultFromScode(CONNECT_E_ADVISELIMIT);

    //Check for the right interface on the sink.
    //CHAPTER10MOD
    if (FAILED(pUnkSink->QueryInterface(IID_IPolylineAdviseSink10
        , (PPVOID)&pSink)))
    //End CHAPTER10MOD
        return ResultFromScode(CONNECT_E_CANNOTCONNECT);

    *pdwCookie=ADVISEKEY;
    m_pObj->m_pAdv=pSink;
    return NOERROR;
    }

/*
 * CConnectionPoint::Unadvise
 *
 * Purpose:
 *  Terminates the connection to the notification sink identified
 *  with dwCookie (that was returned from Advise).  The connection
 *  point has to Release any held pointers for that sink.
 *
 * Parameters:
 *  dwCookie        DWORD connection key from Advise.
 */

STDMETHODIMP CConnectionPoint::Unadvise(DWORD dwCookie)
    {
    if (0==dwCookie)
        return ResultFromScode(E_INVALIDARG);

    if (ADVISEKEY!=dwCookie)
        ResultFromScode(CONNECT_E_NOCONNECTION);

    ReleaseInterface(m_pObj->m_pAdv);
    return NOERROR;
    }

/*
 * CConnectionPoint::EnumConnections
 *
 * Purpose:
 *  Not implemented.
 */

STDMETHODIMP CConnectionPoint::EnumConnections
    (LPENUMCONNECTIONS *ppEnum)
    {
    *ppEnum=NULL;
    return ResultFromScode(E_NOTIMPL);
    }


IDATAOBJ.CPP

/*
 * IDATAOBJ.CPP
 * Polyline Component Chapter 10
 *
 * Implementation of the IDataObject interface.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIDataObject::CImpIDataObject
 * CImpIDataObject::~CImpIDataObject
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIDataObject::CImpIDataObject(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIDataObject::~CImpIDataObject(void)
    {
    return;
    }

/*
 * CImpIDataObject::QueryInterface
 * CImpIDataObject::AddRef
 * CImpIDataObject::Release
 */

STDMETHODIMP CImpIDataObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIDataObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIDataObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIDataObject::GetData
 *
 * Purpose:
 *  Retrieves data described by a specific FormatEtc into a StgMedium
 *  allocated by this function.  Used like GetClipboardData.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *  pSTM            LPSTGMEDIUM in which to return the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetData(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM)
    {
    UINT            cf=pFE->cfFormat;

    //Check the aspects we support.
    if (!(DVASPECT_CONTENT & pFE->dwAspect))
        return ResultFromScode(DATA_E_FORMATETC);

    pSTM->pUnkForRelease=NULL;

    //Go render the appropriate data for the format.
    switch (cf)
        {
        case CF_METAFILEPICT:
            pSTM->tymed=TYMED_MFPICT;
            return m_pObj->RenderMetafilePict(&pSTM->hGlobal);

        case CF_BITMAP:
            pSTM->tymed=TYMED_GDI;
            return m_pObj->RenderBitmap((HBITMAP *)&pSTM->hGlobal);

        default:
            if (cf==m_pObj->m_cf)
                {
                pSTM->tymed=TYMED_HGLOBAL;
                return m_pObj->RenderNative(&pSTM->hGlobal);
                }

            break;
        }

    return ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::GetDataHere
 *
 * Purpose:
 *  Renders the specific FormatEtc into caller-allocated medium
 *  provided in pSTM.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *  pSTM            LPSTGMEDIUM providing the medium into which
 *                  wer render the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetDataHere(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM)
    {
    //No support for this function.
    return ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::QueryGetData
 *
 * Purpose:
 *  Tests if a call to GetData with this FormatEtc will provide
 *  any rendering; used like IsClipboardFormatAvailable.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::QueryGetData(LPFORMATETC pFE)
    {
    UINT            cf=pFE->cfFormat;
    BOOL            fRet=FALSE;

    //Check the aspects we support.
    if (!(DVASPECT_CONTENT & pFE->dwAspect))
        return ResultFromScode(DATA_E_FORMATETC);

    switch (cf)
        {
        case CF_METAFILEPICT:
            fRet=(BOOL)(pFE->tymed & TYMED_MFPICT);
            break;

        case CF_BITMAP:
            fRet=(BOOL)(pFE->tymed & TYMED_GDI);
            break;

        default:
            //Check our own format.
            fRet=((cf==m_pObj->m_cf)
                && (BOOL)(pFE->tymed & TYMED_HGLOBAL));
            break;
        }

    return fRet ? NOERROR : ResultFromScode(S_FALSE);
    }

/*
 * CImpIDataObject::GetCanonicalFormatEtc
 *
 * Purpose:
 *  Provides the caller with an equivalent FormatEtc to the one
 *  provided when different FormatEtcs will produce exactly the
 *  same renderings.
 *
 * Parameters:
 *  pFEIn            LPFORMATETC of the first description.
 *  pFEOut           LPFORMATETC of the equal description.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetCanonicalFormatEtc
    (LPFORMATETC pFEIn, LPFORMATETC pFEOut)
    {
    if (NULL==pFEOut)
        return ResultFromScode(E_INVALIDARG);

    pFEOut->ptd=NULL;
    return ResultFromScode(DATA_S_SAMEFORMATETC);
    }

/*
 * CImpIDataObject::SetData
 *
 * Purpose:
 *  Places data described by a FormatEtc and living in a StgMedium
 *  into the object.  The object may be responsible to clean up the
 *  StgMedium before exiting.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the data to set.
 *  pSTM            LPSTGMEDIUM containing the data.
 *  fRelease        BOOL indicating if this function is responsible
 *                  for freeing the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::SetData(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM, BOOL fRelease)
    {
    UINT            cf=pFE->cfFormat;
    BOOL            fRet=FALSE;
    PPOLYLINEDATA   ppl;

    //Check for our own clipboard format and DVASPECT_CONTENT
    if ((cf!=m_pObj->m_cf) || !(DVASPECT_CONTENT & pFE->dwAspect))
        return ResultFromScode(DATA_E_FORMATETC);

    /*
     * Data can only come from global memory containing a
     * POLYLINEDATA structure that we send to the Polyline's
     * DataSet, a now internal function used from here and
     * from IPersistStorage::Load.
     */

    if (TYMED_HGLOBAL!=pSTM->tymed)
        return ResultFromScode(DATA_E_FORMATETC);

    ppl=(PPOLYLINEDATA)GlobalLock(pSTM->hGlobal);

    if (NULL!=ppl)
        {
        m_pObj->DataSet(ppl, TRUE, TRUE);
        GlobalUnlock(pSTM->hGlobal);
        fRet=TRUE;
        }

    if (fRelease)
        ReleaseStgMedium(pSTM);

    return fRet ? NOERROR : ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::EnumFormatEtc
 *
 * Purpose:
 *  Returns an IEnumFORMATETC object through which the caller can
 *  iterate to learn about all the data formats this object can
 *  provide through either GetData[Here] or SetData.
 *
 * Parameters:
 *  dwDir           DWORD describing a data direction, either
 *                  DATADIR_SET or DATADIR_GET.
 *  ppEnum          LPENUMFORMATETC * in which to return the
 *                  pointer to the enumerator.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::EnumFormatEtc(DWORD dwDir
    , LPENUMFORMATETC *ppEnum)
    {
    return OleRegEnumFormatEtc(m_pObj->m_clsID, dwDir, ppEnum);
    }

/*
 * CImpIDataObject::DAdvise
 *
 * Purpose:
 *  Provides the data object with an IAdviseSink object that we are
 *  responsible to notify when the data changes.
 *
 * Parameters:
 *  ppFE            LPFORMATETC
 *  dwFlags         DWORD carrying flags indicating how the advise
 *                  sink wants to be treated.
 *  pIAdviseSink    LPADVISESINK to the object to notify.
 *  pdwConn         LPDWORD into which we store a DWORD key
 *                  identifying the advise connection.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::DAdvise(LPFORMATETC pFE, DWORD dwFlags
    , LPADVISESINK pIAdviseSink, LPDWORD pdwConn)
    {
    HRESULT         hr;

    if (NULL==m_pObj->m_pIDataAdviseHolder)
        {
        hr=CreateDataAdviseHolder(&m_pObj->m_pIDataAdviseHolder);

        if (FAILED(hr))
            return ResultFromScode(E_OUTOFMEMORY);
        }

    hr=m_pObj->m_pIDataAdviseHolder->Advise(this, pFE
        , dwFlags, pIAdviseSink, pdwConn);
    return hr;
    }

/*
 * CImpIDataObject::DUnadvise
 *
 * Purpose:
 *  Turns off advising previously set up with Advise.
 *
 * Parameters:
 *  dwConn          DWORD connection key returned from Advise.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::DUnadvise(DWORD dwConn)
    {
    HRESULT         hr;

    if (NULL==m_pObj->m_pIDataAdviseHolder)
        return ResultFromScode(E_FAIL);

    hr=m_pObj->m_pIDataAdviseHolder->Unadvise(dwConn);
    return hr;
    }

/*
 * CImpIDataObject::EnumDAdvise
 *
 * Purpose:
 *  Returns an enumerator object through which the caller can find
 *  all the agents currently receiving advises on this data object.
 *
 * Parameters:
 *  ppEnum          LPENUMSTATDATA * in which to return the
 *                  enumerator.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::EnumDAdvise(LPENUMSTATDATA *ppEnum)
    {
    HRESULT         hr;

    if (NULL==m_pObj->m_pIDataAdviseHolder)
        return ResultFromScode(E_FAIL);

    hr=m_pObj->m_pIDataAdviseHolder->EnumAdvise(ppEnum);
    return hr;
    }


IPERSTMI.CPP

/*
 * IPERSTMI.CPP
 * Polyline Component Chapter 10
 *
 * Implementation of the IPersistStreamInit interface exposed on the
 * Polyline object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIPersistStreamInit:CImpIPersistStreamInit
 * CImpIPersistStreamInit::~CImpIPersistStreamInit
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistStreamInit::CImpIPersistStreamInit(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPersistStreamInit::~CImpIPersistStreamInit(void)
    {
    return;
    }

/*
 * CImpIPersistStreamInit::QueryInterface
 * CImpIPersistStreamInit::AddRef
 * CImpIPersistStreamInit::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIPersistStreamInit.
 */

STDMETHODIMP CImpIPersistStreamInit::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistStreamInit::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistStreamInit::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistStreamInit::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the object represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 */

STDMETHODIMP CImpIPersistStreamInit::GetClassID(LPCLSID pClsID)
    {
    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this object since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 */

STDMETHODIMP CImpIPersistStreamInit::IsDirty(void)
    {
    return ResultFromScode(m_pObj->m_fDirty ? S_OK : S_FALSE);
    }

/*
 * CImpIPersistStreamInit::Load
 *
 * Purpose:
 *  Instructs the object to load itself from a previously saved
 *  IStreamInit that was handled by Save in another object lifetime.
 *  The seek pointer in this stream will be exactly the same as
 *  it was when Save was called, and this function must leave
 *  the seek pointer the same as it was on exit from Save, regardless
 *  of success or failure.  This function should not hold on to
 *  pIStream.
 *
 *  This function is called in lieu of IPersistStreamInit::InitNew
 *  when the object already has a persistent state.
 *
 * Parameters:
 *  pIStream        LPSTREAM from which to load.
 */

STDMETHODIMP CImpIPersistStreamInit::Load(LPSTREAM pIStream)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    HRESULT         hr;

    if (NULL==pIStream)
        return ResultFromScode(E_POINTER);

    //Read all the data into the POLYLINEDATA structure.
    hr=pIStream->Read(&pl, CBPOLYLINEDATA, &cb);

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        return hr;

    //CHAPTER10MOD
    //DataSet now internal on Polyline
    m_pObj->DataSet(&pl, TRUE, TRUE);
    //End CHAPTER10MOD
    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::Save
 *
 * Purpose:
 *  Saves the data for this object to an IStreamInit.  Be sure not
 *  to change the position of the seek pointer on entry to this
 *  function: the caller will assume that you write from the
 *  current offset.  Leave the stream's seek pointer at the end
 *  of the data written on exit.
 *
 * Parameters:
 *  pIStream        LPSTREAM in which to save our data.
 *  fClearDirty     BOOL indicating if this call should clear
 *                  the object's dirty flag (TRUE) or leave it
 *                  unchanged (FALSE).
 */

STDMETHODIMP CImpIPersistStreamInit::Save(LPSTREAM pIStream
    , BOOL fClearDirty)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    HRESULT         hr;

    if (NULL==pIStream)
        return ResultFromScode(E_POINTER);

    //CHAPTER10MOD
    //DataGet now internal on Polyline
    m_pObj->DataGet(&pl);
    //End CHAPTER10MOD

    hr=pIStream->Write(&pl, CBPOLYLINEDATA, &cb);

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        return ResultFromScode(STG_E_WRITEFAULT);

    if (fClearDirty)
        m_pObj->m_fDirty;

    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::GetSizeMax
 *
 * Purpose:
 *  Returns the size of the data we would write if Save was
 *  called right now.
 *
 * Parameters:
 *  pcbSize         ULARGE_INTEGER * in which to save the size
 *                  of the stream an immediate call to Save would
 *                  write.
 */

STDMETHODIMP CImpIPersistStreamInit::GetSizeMax(ULARGE_INTEGER
    *pcbSize)
    {
    if (NULL==pcbSize)
        return ResultFromScode(E_POINTER);

    ULISet32(*pcbSize, CBPOLYLINEDATA);
    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::InitNew
 *
 * Purpose:
 *  Informs the object that it is being created new instead of
 *  loaded from a persistent state.  This will be called in lieu
 *  of IPersistStreamInit::Load.
 *
 * Parameters:
 *  None
 */

STDMETHODIMP CImpIPersistStreamInit::InitNew(void)
    {
    //Nothing for us to do
    return NOERROR;
    }


IPERSTOR.CPP

/*
 * IPERSTOR.CPP
 * Polyline Component Chapter 10
 *
 * Implementation of the IPersistStorage interface exposed on the
 * Polyline object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIPersistStorage:CImpIPersistStorage
 * CImpIPersistStorage::~CImpIPersistStorage
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistStorage::CImpIPersistStorage(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    m_psState=PSSTATE_UNINIT;
    return;
    }

CImpIPersistStorage::~CImpIPersistStorage(void)
    {
    return;
    }

/*
 * CImpIPersistStorage::QueryInterface
 * CImpIPersistStorage::AddRef
 * CImpIPersistStorage::Release
 */

STDMETHODIMP CImpIPersistStorage::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistStorage::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistStorage::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistStorage::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the object represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, error code otherwise.
 */

STDMETHODIMP CImpIPersistStorage::GetClassID(LPCLSID pClsID)
    {
    if (PSSTATE_UNINIT==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this object since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 *
 */

STDMETHODIMP CImpIPersistStorage::IsDirty(void)
    {
    if (PSSTATE_UNINIT==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    return ResultFromScode(m_pObj->m_fDirty ? S_OK : S_FALSE);
    }

/*
 * CImpIPersistStorage::InitNew
 *
 * Purpose:
 *  Provides the object with the IStorage to hold on to while the
 *  object is running.  Here we initialize the structure of the
 *  storage and AddRef it for incremental access. This function will
 *  only be called once in the object's lifetime in lieu of Load.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE for the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::InitNew(LPSTORAGE pIStorage)
    {
    HRESULT         hr;
    ULARGE_INTEGER  uli;

    if (PSSTATE_UNINIT!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    if (NULL==pIStorage)
        return ResultFromScode(E_POINTER);

    /*
     * The rules of IPersistStorage mean we hold onto the IStorage
     * and pre-create anything we'd need in Save(...,TRUE) for
     * low-memory situations.  For us this means creating our
     * "CONTENTS" stream and holding onto that IStream as
     * well as the IStorage here (requiring an AddRef call).
     */

    hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
        | STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE
        , 0, 0, &m_pObj->m_pIStream);

    if (FAILED(hr))
        return hr;

    //Preallocate stream space
    ULISet32(uli, CBPOLYLINEDATA);
    m_pObj->m_pIStream->SetSize(uli);

    //We expect that the client has called WriteClassStg
    WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
        , (*m_pObj->m_pST)[IDS_USERTYPE]);

    m_pObj->m_pIStorage=pIStorage;
    pIStorage->AddRef();

    m_psState=PSSTATE_SCRIBBLE;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::Load
 *
 * Purpose:
 *  Instructs the object to load itself from a previously saved
 *  IStorage that was handled by Save in another object lifetime.
 *  This function will only be called once in the object's lifetime
 *  in lieu of InitNew. The object should hold on to pIStorage here
 *  for incremental access and low-memory saves in Save.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE from which to load.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::Load(LPSTORAGE pIStorage)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    LPSTREAM        pIStream;
    HRESULT         hr;

    if (PSSTATE_UNINIT!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    if (NULL==pIStorage)
        return ResultFromScode(E_POINTER);

    //We don't check CLSID to remain compatible with other chapters.

    hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
        | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return ResultFromScode(STG_E_READFAULT);

    //Read all the data into the POLYLINEDATA structure.
    hr=pIStream->Read(&pl, CBPOLYLINEDATA, &cb);

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        {
        pIStream->Release();
        return hr;
        }

    /*
     * We don't call pIStream->Release here because we may need
     * it for a low-memory save in Save.  We also need to
     * hold onto a copy of pIStorage, meaning AddRef.
     */
    m_pObj->m_pIStream=pIStream;
    m_pObj->m_pIStorage=pIStorage;
    pIStorage->AddRef();

    //CHAPTER10MOD
    //DataSet now internal on CPolyline
    m_pObj->DataSet(&pl, TRUE, TRUE);
    //End CHAPTER10MOD

    m_psState=PSSTATE_SCRIBBLE;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::Save
 *
 * Purpose:
 *  Saves the data for this object to an IStorage which may
 *  or may not be the same as the one previously passed to
 *  Load, indicated with fSameAsLoad.  After this call we may
 *  not write into the storage again until SaveCompleted is
 *  called, although we may still read.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which to save our data.
 *  fSameAsLoad     BOOL indicating if this is the same pIStorage
 *                  that was passed to Load.  If TRUE, then the
 *                  object should write whatever it has *without
 *                  *using any extra memory* as this may be a low
 *                  memory save attempt.  That means that you must
 *                  not try to open or create streams.  If FALSE
 *                  you need to regenerate your whole storage
 *                  structure, being sure to also release any
 *                  pointers held from InitNew and Load.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::Save(LPSTORAGE pIStorage
    , BOOL fSameAsLoad)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    LPSTREAM        pIStream;
    HRESULT         hr;

    //Have to come here from scribble state.
    if (PSSTATE_SCRIBBLE!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    //Must have an IStorage if we're not in SameAsLoad
    if (NULL==pIStorage && !fSameAsLoad)
        return ResultFromScode(E_POINTER);

    /*
     * If we're saving to a new storage, create a new stream.
     * If fSameAsLoad it TRUE, then we write to the
     * stream we already allocated.  We should NOT depends on
     * pIStorage with fSameAsLoad is TRUE.
     */
    if (fSameAsLoad)
        {
        LARGE_INTEGER   li;

        /*
         * Use pre-allocated streams to avoid failures due
         * to low-memory conditions.  Be sure to reset the
         * stream pointer if you used this stream before!!
         */
        pIStream=m_pObj->m_pIStream;
        LISet32(li, 0);
        pIStream->Seek(li, STREAM_SEEK_SET, NULL);

        //This matches the Release below.
        pIStream->AddRef();
        }
    else
        {
        hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
            | STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE
            , 0, 0, &pIStream);

        if (FAILED(hr))
            return hr;

        //Only do this with new storages.
        WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
            , (*m_pObj->m_pST)[IDS_USERTYPE]);
        }

    //CHAPTER10MOD
    //DataGet now internal on CPolyline
    m_pObj->DataGet(&pl);
    //End CHAPTER10MOD

    hr=pIStream->Write(&pl, CBPOLYLINEDATA, &cb);
    pIStream->Release();

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        return ResultFromScode(STG_E_WRITEFAULT);

    m_psState=PSSTATE_ZOMBIE;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::SaveCompleted
 *
 * Purpose:
 *  Notifies the object that the storage in pIStorage has been
 *  completely saved now.  This is called when the user of this
 *  object wants to save us in a completely new storage, and if
 *  we normally hang on to the storage we have to reinitialize
 *  ourselves here for this new one that is now complete.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE of the new storage in which we live.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::SaveCompleted(LPSTORAGE pIStorage)
    {
    HRESULT     hr;
    LPSTREAM    pIStream;

    //Must be called in no-scribble or hands-off state
    if (!(PSSTATE_ZOMBIE==m_psState || PSSTATE_HANDSOFF==m_psState))
        return ResultFromScode(E_UNEXPECTED);

    //If we're coming from Hands-Off, we'd better get a storage
    if (NULL==pIStorage && PSSTATE_HANDSOFF==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    /*
     * If pIStorage is NULL, then we don't need to do anything
     * since we already have all the pointers we need for Save.
     * Otherwise we have to release any held pointers and
     * reinitialize them from pIStorage.
     */

    if (NULL!=pIStorage)
        {
        hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
            | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0
            , &pIStream);

        if (FAILED(hr))
            return hr;

        if (NULL!=m_pObj->m_pIStream)
            m_pObj->m_pIStream->Release();

        m_pObj->m_pIStream=pIStream;

        if (NULL!=m_pObj->m_pIStorage)
            m_pObj->m_pIStorage->Release();

        m_pObj->m_pIStorage=pIStorage;
        m_pObj->m_pIStorage->AddRef();
        }

    //Change state back to scribble.
    m_psState=PSSTATE_SCRIBBLE;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::HandsOffStorage
 *
 * Purpose:
 *  Instructs the object that another agent is interested in having
 *  total access to the storage we might be hanging on to from
 *  InitNew or SaveCompleted.  In this case we must release our hold
 *  and await another call to SaveCompleted before we have a hold
 *  again.  Therefore we cannot read or write after this call until
 *  SaveCompleted.
 *
 *  Situations where this might happen arise in compound document
 *  scenarios where this object might be in-place active but the
 *  application wants to rename and commit the root storage.
 *  Therefore we are asked to close our hold, let the container
 *  party on the storage, then call us again later to tell us the
 *  new storage we can hold.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::HandsOffStorage(void)
    {
    /*
     * Must come from scribble or no-scribble.  A repeated call
     * to HandsOffStorage is an unexpected error (bug in client).
     */
    if (PSSTATE_UNINIT==m_psState || PSSTATE_HANDSOFF==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    //Release held pointers
    if (NULL!=m_pObj->m_pIStream)
        ReleaseInterface(m_pObj->m_pIStream);

    if (NULL!=m_pObj->m_pIStorage)
        ReleaseInterface(m_pObj->m_pIStorage);

    m_psState=PSSTATE_HANDSOFF;
    return NOERROR;
    }


IPOLYLIN.CPP

/*
 * IPOLYLIN.CPP
 * Polyline Component Chapter 10
 *
 * Implementation of the IPolyline10 interface that we expose on the
 * CPolyline object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIPolyline:CImpIPolyline
 * CImpIPolyline::~CImpIPolyline
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPolyline::CImpIPolyline(PCPolyline pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPolyline::~CImpIPolyline(void)
    {
    return;
    }

/*
 * CImpIPolyline::QueryInterface
 * CImpIPolyline::AddRef
 * CImpIPolyline::Release
 */

STDMETHODIMP CImpIPolyline::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPolyline::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPolyline::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPolyline::Init
 *
 * Purpose:
 *  Instantiates a polyline window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags
 *  uID             UINT ID to associate with this window
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_OUTOFMEMORY
 */

STDMETHODIMP CImpIPolyline::Init(HWND hWndParent, LPRECT pRect
    , DWORD dwStyle, UINT uID)
    {
    SCODE           sc;

    m_pObj->m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY
        , SZCLASSPOLYLINE, SZCLASSPOLYLINE, dwStyle, pRect->left
        , pRect->top, pRect->right-pRect->left
        , pRect->bottom-pRect->top, hWndParent, (HMENU)uID
        , m_pObj->m_hInst, m_pObj);

    sc=(NULL!=m_pObj->m_hWnd) ? S_OK : E_OUTOFMEMORY;
    return ResultFromScode(sc);
    }

//CHAPTER10MOD
/*
 * Data* and Render* memebers moved into IDataObject.
 */
//CHAPTER10MOD

/*
 * CImpIPolyline::New
 *
 * Purpose:
 *  Cleans out and reinitializes the data to defaults.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::New(void)
    {
    PPOLYLINEDATA   ppl=&m_pObj->m_pl;
    UINT            i;
    RECT            rc;

    ppl->wVerMaj=VERSIONMAJOR;
    ppl->wVerMin=VERSIONMINOR;

    //Our rectangle is the size of our window's client area.
    GetClientRect(m_pObj->m_hWnd, &rc);
    RECTTORECTS(rc, ppl->rc);

    //Clean out the POLYLINEDATA structure and repaint the window.
    for (i=0; i< CPOLYLINEPOINTS; i++)
        {
        ppl->rgpt[i].x=0;
        ppl->rgpt[i].y=0;
        }

    ppl->cPoints      =0;
    ppl->rgbBackground=GetSysColor(COLOR_WINDOW);
    ppl->rgbLine      =GetSysColor(COLOR_WINDOWTEXT);
    ppl->iLineStyle   =PS_SOLID;

    InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
    UpdateWindow(m_pObj->m_hWnd);

    //CHAPTER10MOD
    m_pObj->m_fDirty=TRUE;

    //Inform the advise sink of this data change.
    if (NULL!=m_pObj->m_pIDataAdviseHolder)
        {
        m_pObj->m_pIDataAdviseHolder->SendOnDataChange
            (m_pObj->m_pImpIDataObject, DVASPECT_CONTENT, ADVF_NODATA);
        }
    //End CHAPTER10MOD

    return NOERROR;
    }

/*
 * CImpIPolyline::Undo
 *
 * Purpose:
 *  Reverses previous actions in a Polyline.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         S_OK if we can Undo more, S_FALSE otherwise.
 */

STDMETHODIMP CImpIPolyline::Undo(void)
    {
    SCODE           sc;

    //Decrement the number of active points and repaint.
    if (m_pObj->m_pl.cPoints > 0)
        {
        m_pObj->m_pl.cPoints--;
        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        UpdateWindow(m_pObj->m_hWnd);
        }

    if (NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnPointChange();
        }

    //Return if we can undo any more.
    sc=(0!=m_pObj->m_pl.cPoints) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * CImpIPolyline::Window
 *
 * Purpose:
 *  Returns the window handle associated with this polyline.
 *
 * Parameters:
 *  phWnd           HWND * in which to return the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CImpIPolyline::Window(HWND *phWnd)
    {
    *phWnd=m_pObj->m_hWnd;
    return NOERROR;
    }

/*
 * CImpIPolyline::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::RectGet(LPRECT pRect)
    {
    RECT            rc;
    POINT           pt;

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_pObj->m_hWnd, &rc);
    pt.x=rc.left;
    pt.y=rc.top;
    ScreenToClient(GetParent(m_pObj->m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return NOERROR;
    }

/*
 * CImpIPolyline::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return NOERROR;
    }

/*
 * CImpIPolyline::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT            cx, cy;
    RECT            rc;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_pObj->m_hWnd, NULL, pRect->left, pRect->top
        , cx, cy, SWP_NOZORDER);

    SetRect(&rc, 0, 0, cx, cy);
    RECTTORECTS(rc, m_pObj->m_pl.rc);

    if (fNotify && NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnSizeChange();
        }

    InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);

    return NOERROR;
    }

/*
 * CImpIPolyline::SizeSet
 *
 * Purpose:
 *  Sets a new size for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT            cx, cy;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_pObj->m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
        , SWP_NOMOVE | SWP_NOZORDER);

    if (fNotify && NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnSizeChange();
        }

    InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);

    return NOERROR;
    }

/*
 * CImpIPolyline::ColorSet
 *
 * Purpose:
 *  Changes for background or line color in the Polyline
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color to use.
 *  pcrPrev         COLORREF * in whch to store the
 *                  previous color.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::ColorSet(UINT iColor, COLORREF cr
    , COLORREF *pcrPrev)
    {
    COLORREF        crRet;

    if (NULL==pcrPrev)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    switch (iColor)
        {
        case POLYLINECOLOR_BACKGROUND:
            crRet=m_pObj->m_pl.rgbBackground;
            m_pObj->m_pl.rgbBackground=cr;
            break;

        case POLYLINECOLOR_LINE:
            crRet=m_pObj->m_pl.rgbLine;
            m_pObj->m_pl.rgbLine=cr;
            break;
        }

    //If the color changed, repaint
    if (crRet!=cr)
        {
        if (NULL!=m_pObj->m_pAdv)
            {
            m_pObj->m_fDirty=TRUE;
            m_pObj->m_pAdv->OnColorChange();
            }

        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        UpdateWindow(m_pObj->m_hWnd);
        }

    *pcrPrev=crRet;
    return NOERROR;
    }

/*
 * CImpIPolyline::ColorGet
 *
 * Purpose:
 *  Retrieves one of the colors currently in use by the Polyline.
 *
 * Parameters:
 *  iColor          UINT identifying the color of interest.
 *  pcr             COLORREF * in which to return the color.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::ColorGet(UINT iColor, COLORREF *pcr)
    {
    COLORREF        crRet;

    if (NULL==pcr)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    crRet=(POLYLINECOLOR_BACKGROUND==iColor)
        ? m_pObj->m_pl.rgbBackground : m_pObj->m_pl.rgbLine;

    *pcr=crRet;
    return NOERROR;
    }

/*
 * CImpIPolyline::LineStyleSet
 *
 * Purpose:
 *  Changes the line style in use by the Polyline
 *
 * Parameters:
 *  iStyle          UINT style of the line to use.
 *  piPrev          UINT * in which to store the previous style.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::LineStyleSet(UINT iStyle, UINT *piPrev)
    {
    UINT            uRet;

    uRet=(UINT)m_pObj->m_pl.iLineStyle;

    if (NULL==piPrev)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    //Validate the line style
    if (PS_SOLID==iStyle || PS_DASH==iStyle || PS_DOT==iStyle
        || PS_DASHDOT==iStyle || PS_DASHDOTDOT==iStyle)
        {
        m_pObj->m_pl.iLineStyle=iStyle;

        if (uRet!=(UINT)m_pObj->m_pl.iLineStyle)
            {
            if (NULL!=m_pObj->m_pAdv)
                {
                m_pObj->m_fDirty=TRUE;
                m_pObj->m_pAdv->OnLineStyleChange();
                }

            InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
            UpdateWindow(m_pObj->m_hWnd);
            }
        }

    *piPrev=uRet;
    return NOERROR;
    }

/*
 * CImpIPolyline::LineStyleGet
 *
 * Purpose:
 *  Retrieves the current line style in use in the Polyline
 *
 * Parameters:
 *  piStyle         UINT * in which to store the style.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::LineStyleGet(UINT *piStyle)
    {
    if (NULL==piStyle)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    *piStyle=m_pObj->m_pl.iLineStyle;
    return NOERROR;
    }


POLYLINE.CPP

/*
 * POLYLINE.CPP
 * Polyline Component Chapter 10
 *
 * Implementation of the CPolyline class that we expose as a
 * component object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CPolyline:CPolyline
 * CPolyline::~CPolyline
 *
 * Constructor Parameters:
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 *  pfnDestroy      PFNDESTROYED to call when an object is
 *                  destroyed.
 *  hInst           HINSTANCE of the application we're in.
 */

CPolyline::CPolyline(LPUNKNOWN pUnkOuter, PFNDESTROYED pfnDestroy
    , HINSTANCE hInst)
    {
    m_hWnd=NULL;
    m_hInst=hInst;

    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pfnDestroy=pfnDestroy;
    m_fDirty=FALSE;

    m_pImpIPolyline=NULL;
    m_pImpIConnPtCont=NULL;

    m_pAdv=NULL;
    m_pConnPt=NULL;

    m_pST  =NULL;
    m_cf   =0;
    //CHAPTER10MOD
    m_clsID=CLSID_Polyline10;
    //End CHAPTER10MOD

    m_pIStorage=NULL;
    m_pIStream =NULL;

    m_pImpIPersistStorage=NULL;
    m_pImpIPersistStreamInit=NULL;

    //CHAPTER10MOD
    m_pImpIDataObject   =NULL;
    m_pIDataAdviseHolder=NULL;
    //End CHAPTER10MOD
    return;
    }

CPolyline::~CPolyline(void)
    {
    if (NULL!=m_pST)
        delete m_pST;

    //CHAPTER10MOD
    ReleaseInterface(m_pIDataAdviseHolder);

    DeleteInterfaceImp(m_pImpIDataObject);
    //End CHAPTER10MOD

    DeleteInterfaceImp(m_pImpIPersistStreamInit);
    DeleteInterfaceImp(m_pImpIPersistStorage);
    ReleaseInterface(m_pIStream);
    ReleaseInterface(m_pIStorage);

    DeleteInterfaceImp(m_pImpIConnPtCont);
    DeleteInterfaceImp(m_pImpIPolyline);

    ReleaseInterface(m_pAdv);
    ReleaseInterface(m_pConnPt);

    return;
    }

/*
 * CPolyline::Init
 *
 * Purpose:
 *  Performs any intiailization of a CPolyline that's prone to
 *  failure that we also use internally before exposing the
 *  object outside this DLL.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CPolyline::Init(void)
    {
    LPUNKNOWN       pIUnknown=this;

    if (NULL!=m_pUnkOuter)
        pIUnknown=m_pUnkOuter;

    m_pST=new CStringTable(m_hInst);

    if (!m_pST->Init(IDS_POLYLINEMIN, IDS_POLYLINEMAX))
        return FALSE;

    m_cf=RegisterClipboardFormat(SZPOLYLINECLIPFORMAT);

    m_pImpIPersistStorage=new CImpIPersistStorage(this, pIUnknown);

    if (NULL==m_pImpIPersistStorage)
        return FALSE;

    m_pImpIPersistStreamInit=new CImpIPersistStreamInit(this
        , pIUnknown);

    if (NULL==m_pImpIPersistStreamInit)
        return FALSE;

    m_pImpIPolyline=new CImpIPolyline(this, pIUnknown);

    if (NULL==m_pImpIPolyline)
        return FALSE;

    m_pImpIConnPtCont=new CImpIConnPtCont(this, pIUnknown);

    if (NULL==m_pImpIConnPtCont)
        return FALSE;

    m_pConnPt=new CConnectionPoint(this);

    if (NULL==m_pConnPt)
        return FALSE;

    m_pConnPt->AddRef();    //Reversed in destructor

    //CHAPTER10MOD
    m_pImpIDataObject=new CImpIDataObject(this, pIUnknown);

    if (NULL==m_pImpIDataObject)
        return FALSE;
    //End CHAPTER10MOD

    return TRUE;
    }

/*
 * CPolyline::QueryInterface
 * CPolyline::AddRef
 * CPolyline::Release
 *
 * Purpose:
 *  IUnknown members for CPolyline object.
 */

STDMETHODIMP CPolyline::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IConnectionPointContainer==riid)
        *ppv=m_pImpIConnPtCont;

    //CHAPTER10MOD
    if (IID_IPolyline10==riid)
        *ppv=m_pImpIPolyline;
    //End CHAPTER10MOD

    if (IID_IPersistStorage==riid)
        *ppv=m_pImpIPersistStorage;

    if (IID_IPersist==riid || IID_IPersistStream==riid
        || IID_IPersistStreamInit==riid)
        *ppv=m_pImpIPersistStreamInit;

    //CHAPTER10MOD
    if (IID_IDataObject==riid)
        *ppv=m_pImpIDataObject;
    //End CHAPTER10MOD

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CPolyline::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPolyline::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0L;
    }

/*
 * CPolyline::RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device (MM_TEXT) or
 *  HIMETRIC to the other.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  fToDevice       BOOL TRUE to convert from HIMETRIC to device,
 *                  FALSE to convert device to HIMETRIC.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectConvertMappings(LPRECT pRect, BOOL fToDevice)
    {
    HDC      hDC;
    int      iLpx, iLpy;

    if (NULL==pRect)
        return;

    hDC=GetDC(NULL);
    iLpx=GetDeviceCaps(hDC, LOGPIXELSX);
    iLpy=GetDeviceCaps(hDC, LOGPIXELSY);
    ReleaseDC(NULL, hDC);

    if (fToDevice)
        {
        pRect->left=MulDiv(iLpx, pRect->left, HIMETRIC_PER_INCH);
        pRect->top =MulDiv(iLpy, pRect->top , HIMETRIC_PER_INCH);

        pRect->right =MulDiv(iLpx, pRect->right, HIMETRIC_PER_INCH);
        pRect->bottom=MulDiv(iLpy, pRect->bottom,HIMETRIC_PER_INCH);
        }
    else
        {
        pRect->left=MulDiv(pRect->left, HIMETRIC_PER_INCH, iLpx);
        pRect->top =MulDiv(pRect->top , HIMETRIC_PER_INCH, iLpy);

        pRect->right =MulDiv(pRect->right, HIMETRIC_PER_INCH, iLpx);
        pRect->bottom=MulDiv(pRect->bottom,HIMETRIC_PER_INCH, iLpy);
        }

    return;
    }

//CHAPTER10MOD
//Functions now internal due to IDataObject

/*
 * CPolyline::DataSet
 *
 * Purpose:
 *  Sets the current data in this Polyline to a given structure.
 *
 * Parameters:
 *  pplIn           PPOLYLINEDATA to initialize to.
 *  fSizeToData     BOOL indicating if we're to size to the data
 *                  or scale it.
 *  fNotify         BOOL indicating if we're to send an advise
 *                  on this change.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::DataSet(PPOLYLINEDATA pplIn
    , BOOL fSizeToData, BOOL fNotify)
    {
    RECT            rc;

    /*
     * Copy the structure in pplIn and repaint to reflect the
     * new point set.  Note that unlike the RectSet message, we
     * do no scaling, assuming that the rect in the structure
     * is appropriate for the data.
     */

    if (NULL==pplIn)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    m_pl=*pplIn;

    //CHAPTER10MOD
    m_fDirty=TRUE;

    //Inform our parent of the data change
    if (NULL!=m_pIDataAdviseHolder)
        {
        m_pIDataAdviseHolder->SendOnDataChange(m_pImpIDataObject
            , DVASPECT_CONTENT, ADVF_NODATA);
        }
    //End CHAPTER10MOD

    /*
     * If we're scaling the window to fit the data, then use
     * RectSet passing our current rectangle as the new one.
     * That makes sure that the data won't change but that the
     * window is resized.
     */

    if (fSizeToData)
        {
        POINT       pt;

        /*
         * Get our offset in the parent window so we can RectSet
         * to the right place since RectSet expects rectangle in
         * parent coordinates and we get it in client coordinates.
         */
        GetWindowRect(m_hWnd, &rc);
        pt.x=rc.left;
        pt.y=rc.top;
        ScreenToClient(GetParent(m_hWnd), &pt);
        RECTSTORECT(m_pl.rc, rc);
        OffsetRect(&rc, pt.x, pt.y);

        //This will also cause a repaint.
        m_pImpIPolyline->RectSet(&rc, fNotify);
        }
    else
        {
        //Make sure we're updated.
        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    return NOERROR;
    }

/*
 * CPolyline::DataGet
 *
 * Purpose:
 *  Retrieves the Polyline's current data.
 *
 * Parameters:
 *  pplIn           PPOLYLINEDATA into which we copy the data.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::DataGet(PPOLYLINEDATA pplIn)
    {
    if (NULL==pplIn)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    *pplIn=m_pl;
    return NOERROR;
    }

/*
 * DataGet/SetMem eliminated:  no need with IDataObject.
 * DataGetMem is somewhat like RenderNative below.
 */

/*
 * CPolyline::RenderNative
 *
 * Purpose:
 *  Retrieves the Polyline's data in a global memory handle.
 *
 * Parameters:
 *  phMem           HGLOBAL * in which to store the handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::RenderNative(HGLOBAL *phMem)
    {
    HGLOBAL         hMem;
    PPOLYLINEDATA   ppl;
    HRESULT         hr=ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    if (NULL==phMem)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, CBPOLYLINEDATA);

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        hr=DataGet(ppl);

        GlobalUnlock(hMem);

        if (FAILED(hr))
            {
            GlobalFree(hMem);
            hMem=NULL;
            }
        }

    *phMem=hMem;
    return hr;
    }

/*
 * CPolyline::RenderBitmap
 *
 * Purpose:
 *  Creates a bitmap image of the current Polyline.
 *
 * Parameters:
 *  phBmp           HBITMAP * in which to return the bitmap.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::RenderBitmap(HBITMAP *phBmp)
    {
    HDC             hDC;
    HDC             hMemDC;
    HBITMAP         hBmp;
    RECT            rc;
    HGDIOBJ         hObj;

    if (NULL==phBmp)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    //Render a bitmap the size of the current rectangle.
    hDC=GetDC(m_hWnd);
    hMemDC=CreateCompatibleDC(hDC);

    GetClientRect(m_hWnd, &rc);
    hBmp=CreateCompatibleBitmap(hDC, rc.right, rc.bottom);

    if (NULL!=hBmp)
        {
        //Draw the POLYLINEDATA into the bitmap.
        hObj=SelectObject(hMemDC, hBmp);
        Draw(hMemDC, FALSE, TRUE);
        SelectObject(hMemDC, hObj);
        }

    DeleteDC(hMemDC);
    ReleaseDC(m_hWnd, hDC);

    *phBmp=hBmp;
    return NOERROR;
    }

//RenderMetafile not necessary--now part of RenderMetafilePict.

/*
 * CPolyline::RenderMetafilePict
 *
 * Purpose:
 *  Renders the current Polyline into a METAFILEPICT structure in
 *  global memory.
 *
 * Parameters:
 *  phMem           HGLOBAL * in which to return the
 *                  METAFILEPICT.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::RenderMetafilePict(HGLOBAL *phMem)
    {
    HGLOBAL         hMem;
    HMETAFILE       hMF;
    LPMETAFILEPICT  pMF;
    RECT            rc;
    HDC             hDC;

    if (NULL==phMem)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    //Create a memory metafile and return its handle.
    hDC=(HDC)CreateMetaFile(NULL);

    if (NULL==hDC)
        return ResultFromScode(STG_E_MEDIUMFULL);

    SetMapMode(hDC, MM_ANISOTROPIC);
    GetClientRect(m_hWnd, &rc);
    SetWindowOrgEx(hDC, 0, 0, NULL);
    SetWindowExtEx(hDC, rc.right, rc.bottom, NULL);

    Draw(hDC, TRUE, TRUE);
    hMF=CloseMetaFile(hDC);

    if (NULL==hMF)
        return ResultFromScode(STG_E_MEDIUMFULL);

    //Allocate the METAFILEPICT structure.
    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
        , sizeof(METAFILEPICT));

    if (NULL==hMem)
        {
        DeleteMetaFile(hMF);
        return ResultFromScode(E_FAIL);
        }

    /*
     * Global lock only fails in PMODE if the selector is invalid
     * (like it was discarded) or references a 0 length segment,
     * neither of which can happen here.
     */
    pMF=(LPMETAFILEPICT)GlobalLock(hMem);

    pMF->hMF=hMF;
    pMF->mm=MM_ANISOTROPIC;

    //Insert the extents in MM_HIMETRIC units.
    GetClientRect(m_hWnd, &rc);
    RectConvertMappings(&rc, FALSE);
    pMF->xExt=rc.right;
    pMF->yExt=rc.bottom;

    GlobalUnlock(hMem);

    *phMem=hMem;
    return NOERROR;
    }

//End CHAPTER10MOD


POLYLINE.H

/*
 * POLYLINE.H
 * Polyline Component Chapter 10
 *
 * Definitions and function prototypes
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _POLYLINE_H_
#define _POLYLINE_H_

#define INC_CLASSLIB
#define INC_CONTROLS
//CHAPTER10MOD
#define CHAPTER10
#include <inole.h>
#include <ipoly10.h>
#include "resource.h"
//End CHAPTER10MOD

//Classname
#define SZCLASSPOLYLINE             TEXT("polyline")

//Stream Name that holds the data
#define SZSTREAM                    OLETEXT("CONTENTS")

#define HIMETRIC_PER_INCH           2540
#define CCHPATHMAX                  256

//Window extra bytes and offsets
#define CBPOLYLINEWNDEXTRA          (sizeof(LONG))
#define PLWL_STRUCTURE              0

//DLLPOLY.CPP
int PASCAL LibMain(HINSTANCE, WORD, WORD, LPSTR);

//This class factory object creates Polyline objects.

class CPolylineClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;

    public:
        CPolylineClassFactory(void);
        ~CPolylineClassFactory(void);

        //IUnknown members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP         CreateInstance(LPUNKNOWN, REFIID
                                 , PPVOID);
        STDMETHODIMP         LockServer(BOOL);
    };

typedef CPolylineClassFactory *PCPolylineClassFactory;

//POLYWIN.CPP
LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM, LPARAM);

//Forward class references
class CImpIPolyline;
typedef class CImpIPolyline *PCImpIPolyline;

class CImpIConnPtCont;
typedef CImpIConnPtCont *PCImpIConnPtCont;

class CConnectionPoint;
typedef CConnectionPoint *PCConnectionPoint;

class CImpIPersistStorage;
typedef class CImpIPersistStorage *PCImpIPersistStorage;

class CImpIPersistStreamInit;
typedef class CImpIPersistStreamInit *PCImpIPersistStreamInit;

//CHAPTER10MOD
class CImpIDataObject;
typedef class CImpIDataObject *PCImpIDataObject;

#define CFORMATETCGET       3
#define CFORMATETCSET       1
//End CHAPTER10MOD

//POLYLINE.CPP
class CPolyline : public IUnknown
    {
    friend LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM
        , LPARAM);

    friend class CImpIPolyline;
    friend class CImpIConnPtCont;
    friend class CConnectionPoint;
    friend class CImpIPersistStorage;
    friend class CImpIPersistStreamInit;

    //CHAPTER10MOD
    friend class CImpIDataObject;
    //End CHAPTER10MOD

    protected:
        HWND            m_hWnd;
        HINSTANCE       m_hInst;

        ULONG           m_cRef;         //Object reference count
        LPUNKNOWN       m_pUnkOuter;    //Controlling Unknown
        PFNDESTROYED    m_pfnDestroy;   //Function called on closure
        BOOL            m_fDirty;       //Have we changed?
        POLYLINEDATA    m_pl;           //Our actual data

        //Contained interfaces
        PCImpIPolyline          m_pImpIPolyline;
        PCImpIConnPtCont        m_pImpIConnPtCont;
        PCImpIPersistStorage    m_pImpIPersistStorage;
        PCImpIPersistStreamInit m_pImpIPersistStreamInit;
        //CHAPTER10MOD
        PCImpIDataObject        m_pImpIDataObject;
        //End CHAPTER10MOD

        /*
         * Connected sink:  only one per object.  We get this
         * through our connection point.
         */
        //CHAPTER10MOD
        IPolylineAdviseSink10  *m_pAdv;
        //End CHAPTER10MOD
        PCConnectionPoint       m_pConnPt;

        PCStringTable   m_pST;          //Object strings
        UINT            m_cf;           //Object clipboard format
        CLSID           m_clsID;        //Current CLSID

        //We have to hold these for IPersistStorage::Save
        LPSTORAGE       m_pIStorage;
        LPSTREAM        m_pIStream;

        //CHAPTER10MOD
        LPDATAADVISEHOLDER  m_pIDataAdviseHolder;
        //End CHAPTER10MOD

    protected:
        void      PointScale(LPRECT, LPPOINTS, BOOL);
        void      Draw(HDC, BOOL, BOOL);
        void      RectConvertMappings(LPRECT, BOOL);

        //CHAPTER10MOD
        /*
         * These members pulled from IPolyline now serve as a
         * central store for this functionality to be used from
         * other interfaces like IPersistStorage and IDataObject.
         * Other interfaces later may also use them.
         */
        STDMETHODIMP DataSet(PPOLYLINEDATA, BOOL, BOOL);
        STDMETHODIMP DataGet(PPOLYLINEDATA);

        STDMETHODIMP RenderNative(HGLOBAL *);
        STDMETHODIMP RenderBitmap(HBITMAP *);
        STDMETHODIMP RenderMetafilePict(HGLOBAL *);
        //End CHAPTER10MOD

    public:
        CPolyline(LPUNKNOWN, PFNDESTROYED, HINSTANCE);
        ~CPolyline(void);

        BOOL      Init(void);

        //Non-delegating object IUnknown
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
    };

typedef CPolyline *PCPolyline;

//Interface implementations contained in the Polyline.

//CHPATER10MOD
class CImpIPolyline : public IPolyline10
//End CHAPTER10MOD
    {
    protected:
        ULONG               m_cRef;      //Interface ref count
        PCPolyline          m_pObj;      //Backpointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIPolyline(PCPolyline, LPUNKNOWN);
        ~CImpIPolyline(void);

        //IUnknown members.
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //CHAPTER10MOD
        //Data* and Render* replaced with IDataObject
        //End CHAPTER10MOD

        //Manipulation members:
        STDMETHODIMP Init(HWND, LPRECT, DWORD, UINT);
        STDMETHODIMP New(void);
        STDMETHODIMP Undo(void);
        STDMETHODIMP Window(HWND *);

        STDMETHODIMP RectGet(LPRECT);
        STDMETHODIMP SizeGet(LPRECT);
        STDMETHODIMP RectSet(LPRECT, BOOL);
        STDMETHODIMP SizeSet(LPRECT, BOOL);

        STDMETHODIMP ColorSet(UINT, COLORREF, COLORREF *);
        STDMETHODIMP ColorGet(UINT, COLORREF *);

        STDMETHODIMP LineStyleSet(UINT, UINT *);
        STDMETHODIMP LineStyleGet(UINT *);
    };

class CImpIConnPtCont : public IConnectionPointContainer
    {
    private:
        ULONG               m_cRef;      //Interface ref count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIConnPtCont(PCPolyline, LPUNKNOWN);
        ~CImpIConnPtCont(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(DWORD) AddRef(void);
        STDMETHODIMP_(DWORD) Release(void);

        //IConnectionPointContainer members
        STDMETHODIMP EnumConnectionPoints(IEnumConnectionPoints **);
    STDMETHODIMP FindConnectionPoint(REFIID, IConnectionPoint **);
    };

/*
 * This connection point will only allow one connection,
 * so it always returns the same connection key.  It also
 * doesn't bother to implement enumerations.
 */

#define CCONNMAX    1
#define ADVISEKEY   72388       //Arbitrary

class CConnectionPoint : public IConnectionPoint
    {
    private:
        ULONG           m_cRef;     //Object reference count
        PCPolyline      m_pObj;     //Containing object.

    public:
        CConnectionPoint(PCPolyline);
        ~CConnectionPoint(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IConnectionPoint members
        STDMETHODIMP GetConnectionInterface(IID *);
        STDMETHODIMP GetConnectionPointContainer
            (IConnectionPointContainer **);
        STDMETHODIMP Advise(LPUNKNOWN, DWORD *);
        STDMETHODIMP Unadvise(DWORD);
        STDMETHODIMP EnumConnections(IEnumConnections **);
    };

class CImpIPersistStorage : public IPersistStorage
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown
        PSSTATE             m_psState;   //Storage state

    public:
        CImpIPersistStorage(PCPolyline, LPUNKNOWN);
        ~CImpIPersistStorage(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP InitNew(LPSTORAGE);
        STDMETHODIMP Load(LPSTORAGE);
        STDMETHODIMP Save(LPSTORAGE, BOOL);
        STDMETHODIMP SaveCompleted(LPSTORAGE);
        STDMETHODIMP HandsOffStorage(void);
    };

class CImpIPersistStreamInit : public IPersistStreamInit
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIPersistStreamInit(PCPolyline, LPUNKNOWN);
        ~CImpIPersistStreamInit(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP Load(LPSTREAM);
        STDMETHODIMP Save(LPSTREAM, BOOL);
        STDMETHODIMP GetSizeMax(ULARGE_INTEGER *);
        STDMETHODIMP InitNew(void);
    };

//CHAPTER10MOD
//IDATAOBJ.CPP
class CImpIDataObject : public IDataObject
    {
    private:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIDataObject(PCPolyline, LPUNKNOWN);
        ~CImpIDataObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IDataObject members
        STDMETHODIMP GetData(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP GetDataHere(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP QueryGetData(LPFORMATETC);
        STDMETHODIMP GetCanonicalFormatEtc(LPFORMATETC,LPFORMATETC);
        STDMETHODIMP SetData(LPFORMATETC, LPSTGMEDIUM, BOOL);
        STDMETHODIMP EnumFormatEtc(DWORD, LPENUMFORMATETC *);
        STDMETHODIMP DAdvise(LPFORMATETC, DWORD, LPADVISESINK
            , DWORD *);
        STDMETHODIMP DUnadvise(DWORD);
        STDMETHODIMP EnumDAdvise(LPENUMSTATDATA *);
    };

//End CHAPTER10MOD

#endif  //_POLYLINE_H_


POLYWIN.CPP

/*
 * POLYWIN.CPP
 * Polyline Component Chapter 10
 *
 * Window procedure for the polyline drawing window and support
 * functions.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * PolylineWndProc
 *
 * Purpose:
 *  Window procedure for the polyline drawing window.
 */

LRESULT APIENTRY PolylineWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCPolyline      ppl;
    PAINTSTRUCT     ps;
    HDC             hDC;
    POINTS          pt;
    RECT            rc;

    ppl=(PCPolyline)GetWindowLong(hWnd, PLWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppl=(PCPolyline)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PLWL_STRUCTURE, (LONG)ppl);

            //New repaints this window, so store the hWnd here.
            ppl->m_hWnd=hWnd;
            ppl->m_pImpIPolyline->New();
            break;

        case WM_PAINT:
            hDC=BeginPaint(hWnd, &ps);
            ppl->Draw(hDC, FALSE, TRUE);
            EndPaint(hWnd, &ps);
            break;

        case WM_LBUTTONDOWN:
            //Stop if we are already at the limit.
            if (CPOLYLINEPOINTS==ppl->m_pl.cPoints)
                {
                MessageBeep(0);
                break;
                }

            //Convert the points into 0-32767 range
            GetClientRect(hWnd, &rc);
            pt=MAKEPOINTS(lParam);
            ppl->PointScale(&rc, &pt, FALSE);

            ppl->m_pl.rgpt[ppl->m_pl.cPoints++]=pt;

            //Draw the lines to this new point only.
            hDC=GetDC(hWnd);
            ppl->Draw(hDC, FALSE, FALSE);
            ReleaseDC(hWnd, hDC);

            if (NULL!=ppl->m_pAdv)
                ppl->m_pAdv->OnPointChange();

            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPolyline::Draw
 *
 * Purpose:
 *  Paints the current line in the polyline window.
 *
 * Parameters:
 *  hDC             HDC to draw on, a metafile or printer DC.
 *  fMetafile       BOOL indicating if hDC is a metafile or not,
 *                  so we can avoid operations that RIP.
 *  fEntire         BOOL indicating if we should draw the entire
 *                  figure or not.
 *
 * Return Value:
 *  None
 */

void CPolyline::Draw(HDC hDC, BOOL fMetafile, BOOL fEntire)
    {
    HBRUSH          hBrush;
    HPEN            hPen;
    HGDIOBJ         hObj1, hObj2;
    UINT            i, j;
    UINT            uMM;
    POINTS          pt1, pt2;
    POINT           rgpt[CPOLYLINEPOINTS];
    RECT            rc;

    GetClientRect(m_hWnd, &rc);

    /*
     * Make a 32-bit copy of the points in the POLYLINE itself
     * so we can use DPtoLP and LPtoDP.
     */
    for (i=0; i < m_pl.cPoints; i++)
        {
        rgpt[i].x=m_pl.rgpt[i].x;
        rgpt[i].y=m_pl.rgpt[i].y;
        }

    /*
     * If the mapping mode is not MM_TEXT, convert the points to
     * whatever mapping mode in in effect before drawing.
     * This specifically supports metafiles in MM_ANISOTROPIC.
     */
    uMM=fMetafile ? MM_TEXT : GetMapMode(hDC);

    if (MM_TEXT!=uMM)
        DPtoLP(hDC, rgpt, m_pl.cPoints);

    hPen=CreatePen(m_pl.iLineStyle, 1, m_pl.rgbLine);
    hObj1=SelectObject(hDC, hPen);

    hBrush=CreateSolidBrush(m_pl.rgbBackground);
    hObj2=SelectObject(hDC, hBrush);
    SetBkColor(hDC, m_pl.rgbBackground);

    /*
     * Either draw the entire figure or just a single point.  The
     * entire figure also includes erasing the background
     * completely, since hDC may be a metafile DC.  Drawing a single
     * point just updates the figure for that new point.
     */
    if (fEntire || 0==m_pl.cPoints)
        {
        //Erase the background for bitmaps and metafiles.
        SelectObject(hDC, GetStockObject(NULL_PEN));
        Rectangle(hDC, rc.left, rc.top, rc.right+1, rc.bottom+1);
        SelectObject(hDC, hPen);

        /*
         * If we are drawing the entire figure, then loop through
         * each point drawing a line to each successive point.
         */

        for (i=0; i < m_pl.cPoints; i++)
            {
            for (j=i; j < m_pl.cPoints; j++)
                {
                pt1.x=(short)rgpt[i].x;
                pt1.y=(short)rgpt[i].y;
                pt2.x=(short)rgpt[j].x;
                pt2.y=(short)rgpt[j].y;

                PointScale(&rc, &pt1, TRUE);
                PointScale(&rc, &pt2, TRUE);
                MoveToEx(hDC, pt1.x, pt1.y, NULL);
                LineTo(hDC, pt2.x, pt2.y);
                }
            }
        }
    else
        {
        /*
         * If we are only drawing the last point, just cycle once
         * through previous points.
         */

        //Get the last point entered in the array.
        j=m_pl.cPoints-1;
        pt1.x=(short)rgpt[j].x;
        pt1.y=(short)rgpt[j].y;
        PointScale(&rc, &pt1, TRUE);

        for (i=0; i < j; i++)
            {
            pt2.x=(short)rgpt[i].x;
            pt2.y=(short)rgpt[i].y;
            PointScale(&rc, &pt2, TRUE);

            MoveToEx(hDC, pt1.x, pt1.y, NULL);
            LineTo(hDC, pt2.x, pt2.y);
            }
        }

    //If we have one point, draw a dot to indicate it's position.
    if (1==m_pl.cPoints)
        {
        pt1.x=(short)rgpt[0].x;
        pt1.y=(short)rgpt[0].y;
        PointScale(&rc, &pt1, TRUE);
        SetPixel(hDC, pt1.x, pt1.y, m_pl.rgbLine);
        }

    //Restore original points.
    if (MM_TEXT!=uMM)
        LPtoDP(hDC, rgpt, m_pl.cPoints);

    SelectObject(hDC, hObj1);
    SelectObject(hDC, hObj2);
    DeleteObject(hBrush);
    DeleteObject(hPen);
    return;
    }

/*
 * CPolyline::PointScale
 *
 * Purpose:
 *  Scales a point to or from a relative window coordinate to a
 *  0-32767 coordinate.
 *
 * Parameters:
 *  pRect           LPRECT of the window.
 *  ppt             LPPOINTS to convert
 *  fScaleToWindow  BOOL indicating direction of scaling.
 *
 * Return Value:
 *  None
 */

void CPolyline::PointScale(LPRECT pRect, LPPOINTS ppt
    , BOOL fScaleToWindow)
    {
    DWORD   cx, cy;

    //Window size
    cx=(DWORD)(pRect->right-pRect->left);
    cy=(DWORD)(pRect->bottom-pRect->top);

    //Prevent crashes
    if (0L==cx) cx=1;
    if (0L==cy) cy=1;

    //Must use DWORD to insure proper scaling.
    if (fScaleToWindow)
        {
        ppt->x=(UINT)(((DWORD)ppt->x*cx) >> 15);
        ppt->y=(UINT)(((DWORD)ppt->y*cy) >> 15);
        }
    else
        {
        ppt->x=(UINT)(((DWORD)ppt->x << 15)/cx);
        ppt->y=(UINT)(((DWORD)ppt->y << 15)/cy);
        }

    return;
    }


RESOURCE.H

/*
 * RESOURCE.H
 * Polyline Component Chapter 10
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Object stringtable IDs
#define IDS_POLYLINEMIN     0
#define IDS_STORAGEFORMAT   0
#define IDS_USERTYPE        1
#define IDS_POLYLINEMAX     1

#endif //_RESOURCE_H_


TARGDEV.CPP

/*
 * TARGDEV.CPP
 *
 * Sample target device structure handling functions.  This file
 * is intended to be a small repository of functions that might
 * be useful in development:
 *  TargetDeviceToDC          Creates a DC or IC from a
 *                            DVTARGETDEVICE structure.
 *  TargetDeviceFromPrintDlg  Creates a DVTARGETDEVICE structure
 *                            from PrintDlg information.
 *  TargetDeviceCopy          Makes a binary copy of a DVTARGETDEVICE
 *                            structure.
 *  TargerDeviceCompare       Compares two DVTARGETDEVICE structures
 *                            for equality.
 *
 * Copyright (c)1992-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INC_OLE2
#include <windows.h>
#include <ole2.h>
#include <ole2ver.h>
#include <memory.h>
#ifndef WIN32
#include <print.h>
#endif

/*
 * TargetDeviceToDC
 *
 * Purpose:
 *  Given an OLE DVTARGETDEVICE structure, create an hDC for that
 *  device, where that hDC can optionally be an IC only.
 *
 * Parameters:
 *  ptd             DVTARGETDEVICE * describing the device.
 *  fICOnly         BOOL indicating that the caller wants an IC
 *                  not a DC.
 *
 * Return Value:
 *  HDC             Valid DC for the device or NULL.
 */

STDAPI_(HDC) TargetDeviceToDC(DVTARGETDEVICE *ptd, BOOL fICOnly)
    {
    LPDEVNAMES pDevNames;
    LPDEVMODE  pDevMode;
    LPTSTR     pszDriver;
    LPTSTR     pszDevice;
    LPTSTR     pszPort;

    //Screen DC for a NULL device.
    if (NULL==ptd)
        {
        if (fICOnly)
            return CreateIC(TEXT("DISPLAY"), NULL, NULL, NULL);
        else
            return CreateDC(TEXT("DISPLAY"), NULL, NULL, NULL);
        }

    pDevNames=(LPDEVNAMES)ptd; // offset for size field

    pDevMode=(ptd->tdExtDevmodeOffset==0) ? NULL
        : (LPDEVMODE)((LPSTR)ptd+ptd->tdExtDevmodeOffset);

    pszDriver=(LPTSTR)pDevNames+ptd->tdDriverNameOffset;
    pszDevice=(LPTSTR)pDevNames+ptd->tdDeviceNameOffset;
    pszPort  =(LPTSTR)pDevNames+ptd->tdPortNameOffset;

    if (fICOnly)
        return CreateIC(pszDriver, pszDevice, pszPort, pDevMode);
    else
        return CreateDC(pszDriver, pszDevice, pszPort, pDevMode);
    }

/*
 * TargetDeviceFromPrintDlg
 *
 * Purpose:
 *  Creates an OLE DVTARGETDEVICE structure from a PRINTDLG
 *  structure as returned from the common dialog API PrintDlg,
 *  which is commonly used for Printer Setup.
 *
 * Parameters:
 *  pPD             LPPRINTDLG containing device information.
 *
 * Return Value:
 *  DVTARGETDEVICE * pointing to the new structure.  This is
 *  allocated with the task memory allocator and should be
 *  freed with CoTaskMemFree.
 */

STDAPI_(DVTARGETDEVICE *)TargetDeviceFromPrintDlg(LPPRINTDLG pPD)
    {
    DVTARGETDEVICE  *ptd=NULL;
    LPDEVNAMES       pDevNames, pDN;
    LPDEVMODE        pDevMode, pDM;
    UINT             nMaxOffset;
    LPTSTR           pszName;
    DWORD            dwDevNamesSize, dwDevModeSize, dwPtdSize;

    pDN=(LPDEVNAMES)GlobalLock(pPD->hDevNames);

    if (NULL==pDN)
        return NULL;

    pDM=(LPDEVMODE)GlobalLock(pPD->hDevMode);

    if (NULL==pDM)
        {
        GlobalUnlock(pPD->hDevNames);
        return NULL;
        }

    nMaxOffset=(pDN->wDriverOffset > pDN->wDeviceOffset)
        ? pDN->wDriverOffset : pDN->wDeviceOffset ;

    nMaxOffset=(pDN->wOutputOffset > nMaxOffset)
        ? pDN->wOutputOffset : nMaxOffset ;

    pszName=(LPTSTR)pDN+nMaxOffset;

    dwDevNamesSize=(DWORD)(nMaxOffset+lstrlen(pszName)+1);
    dwDevModeSize=(DWORD) (pDM->dmSize+pDM->dmDriverExtra);

    dwPtdSize=sizeof(DWORD)+dwDevNamesSize+dwDevModeSize;

    ptd=(DVTARGETDEVICE *)CoTaskMemAlloc(dwPtdSize);

    if (NULL!=ptd)
        {
       ptd->tdSize=(UINT)dwPtdSize;

       pDevNames=(LPDEVNAMES) &ptd->tdDriverNameOffset;
       #ifdef WIN32
        memcpy(pDevNames, pDN, (size_t)dwDevNamesSize);
       #else
       _fmemcpy(pDevNames, pDN, (size_t)dwDevNamesSize);
       #endif

       pDevMode=(LPDEVMODE)((LPTSTR)&ptd->tdDriverNameOffset
            + dwDevNamesSize);
       #ifdef WIN32
       memcpy(pDevMode, pDM, (size_t)dwDevModeSize);
       #else
        _fmemcpy(pDevMode, pDM, (size_t)dwDevModeSize);
       #endif

        ptd->tdDriverNameOffset +=4 ;
        ptd->tdDeviceNameOffset +=4 ;
        ptd->tdPortNameOffset   +=4 ;
        ptd->tdExtDevmodeOffset=(UINT)dwDevNamesSize+4 ;
        }

    GlobalUnlock(pPD->hDevNames);
    GlobalUnlock(pPD->hDevMode);
    return ptd;
    }

/*
 * TargetDeviceCopy
 *
 * Purpose:
 *  Duplicate a DVTARGETDEVICE structure, allocating new memory
 *  for the copy which the caller must free with CoTaskMemFree.
 *
 * Parameters:
 *  ptdSrc          DVTARGETDEVICE * to copy
 *
 * Return Value:
 *  DVTARGETDEVICE * to the copy, NULL if copy fails or ptdSrc
 *                  is NULL.
 */

STDAPI_(DVTARGETDEVICE *) TargetDeviceCopy(DVTARGETDEVICE * ptdSrc)
    {
    DVTARGETDEVICE * ptdDest=NULL;

    if (NULL==ptdSrc)
    return NULL;

    ptdDest=(DVTARGETDEVICE *)CoTaskMemAlloc(ptdSrc->tdSize);

    if (NULL!=ptdDest)
       #ifdef WIN32
        memcpy(ptdDest, ptdSrc, (size_t)ptdSrc->tdSize);
       #else
        _fmemcpy(ptdDest, ptdSrc, (size_t)ptdSrc->tdSize);
       #endif

    return ptdDest;
    }

/*
 * TargetDeviceCompare
 *
 * Purpose:
 *  Checks if two DVTARGETDEVICE structures are equivalent.
 *
 * Parameters:
 *  ptdLeft         DVTARGETDEVICE to one structure.
 *  ptdRight        DVTARGETDEVICE to another structure.
 *
 * Return Value:
 *  BOOL            TRUE if the structures are equivalent,
 *                  FALSE otherwise.
 */

STDAPI_(BOOL) TargetDeviceCompare(DVTARGETDEVICE *ptdLeft
    , DVTARGETDEVICE * ptdRight)
    {
    //Same address, same devices
    if (ptdLeft==ptdRight)
        return TRUE;

    //Either is NULL but not the other, not equal
    if (NULL==ptdLeft || NULL==ptdRight)
        return FALSE;

    //Different sizes, not equal
    if (ptdLeft->tdSize!=ptdRight->tdSize)
        return FALSE;

    //Fo a binary compare
   #ifdef WIN32
    if (0!=memcmp(ptdLeft, ptdRight, ptdLeft->tdSize))
   #else
    if (0!=_fmemcmp(ptdLeft, ptdRight, (int)ptdLeft->tdSize))
   #endif
    return FALSE;

    return TRUE;
    }


CLIENT.CPP

/*
 * CLIENT.CPP
 * Freeloader Chapter 11
 *
 * Implementation of the CFreeloaderClient class that just makes
 * sure we get a CFreeloaderDoc on doc creation and that we
 * initialize fully.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "freeload.h"

/*
 * CFreeloaderClient::CFreeloaderClient
 * CFreeloaderClient::~CFreeloaderClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame to the frame object.
 */

CFreeloaderClient::CFreeloaderClient(HINSTANCE hInst
    , PCFrame pFR)
    : CClient(hInst, pFR)
    {
    return;
    }

CFreeloaderClient::~CFreeloaderClient(void)
    {
    return;
    }

/*
 * CFreeloaderClient::CreateCDocument
 *
 * Purpose:
 *  Constructs a new document specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CFreeloaderClient::CreateCDocument(void)
    {
    return (PCDocument)(new CFreeloaderDoc(m_hInst, m_pFR, m_pAdv));
    }


DOCUMENT.CPP

/*
 * DOCUMENT.CPP
 * Freeloader Chapter 11
 *
 * Implementation of the CFreeloaderDoc derivation of CDocument.
 * We create a default handler object and use it for drawing, data
 * caching, and serialization.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "freeload.h"

/*
 * CFreeloaderDoc::CFreeloaderDoc
 * CFreeloaderDoc::~CFreeloaderDoc
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame of the frame object.
 *  pAdv            PCDocumentAdviseSink to notify on events
 */

CFreeloaderDoc::CFreeloaderDoc(HINSTANCE hInst, PCFrame pFR
    , PCDocumentAdviseSink pAdv)
    : CDocument(hInst, pFR, pAdv)
    {
    m_pIStorage=NULL;
    m_pIUnknown=NULL;
    m_dwConn=0;
    m_clsID=CLSID_NULL;
    return;
    }

CFreeloaderDoc::~CFreeloaderDoc(void)
    {
    ReleaseObject();
    ReleaseInterface(m_pIStorage);
    return;
    }

/*
 * CFreeloaderDoc::ReleaseObject
 *
 * Purpose:
 *  Centralizes cleanup code for the object and its cache.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CFreeloaderDoc::ReleaseObject(void)
    {
    LPOLECACHE      pIOleCache;
    HRESULT         hr;

    if (0!=m_dwConn)
        {
        hr=m_pIUnknown->QueryInterface(IID_IOleCache
            , (PPVOID)&pIOleCache);

        if (SUCCEEDED(hr))
            {
            pIOleCache->Uncache(m_dwConn);
            pIOleCache->Release();
            }
        }

    ReleaseInterface(m_pIUnknown);
    CoFreeUnusedLibraries();
    m_dwConn=0;
    return;
    }

/*
 * CFreeloaderDoc::FInit
 *
 * Purpose:
 *  Initializes an already created document window.  Here we
 *  only change the stringtable bounds.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CFreeloaderDoc::FInit(PDOCUMENTINIT pDI)
    {
    //Change the stringtable range to our customization.
    pDI->idsMin=IDS_DOCUMENTMIN;
    pDI->idsMax=IDS_DOCUMENTMAX;

    //Do default initialization
    return CDocument::Init(pDI);
    }

/*
 * CFreeloaderDoc::FMessageHook
 *
 * Purpose:
 *  Processes WM_PAINT for the document so we can draw the object.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CFreeloaderDoc::FMessageHook(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam, LRESULT *pLRes)
    {
    PAINTSTRUCT     ps;
    HDC             hDC;
    RECT            rc;
    RECTL           rcl;
    LPVIEWOBJECT2   pIViewObject2;
    HRESULT         hr;

    if (WM_PAINT!=iMsg)
        return FALSE;

    hDC=BeginPaint(hWnd, &ps);
    GetClientRect(hWnd, &rc);

    //Draw the object with IViewObject2::Draw, allowing ESC
    if (NULL!=m_pIUnknown)
        {
        hr=m_pIUnknown->QueryInterface(IID_IViewObject2
            , (PPVOID)&pIViewObject2);

        if (SUCCEEDED(hr))
            {
            //Put "Hit Esc to stop" in the status line
            m_pFR->StatusLine()->MessageSet(PSZ(IDS_HITESCTOSTOP));

            RECTLFROMRECT(rcl, rc);
            pIViewObject2->Draw(DVASPECT_CONTENT, -1, NULL, NULL
                , 0, hDC, &rcl, NULL, ContinuePaint, 0);
            pIViewObject2->Release();

            m_pFR->StatusLine()->MessageDisplay(ID_MESSAGEREADY);
            }
        }

    EndPaint(hWnd, &ps);
    return FALSE;
    }

/*
 * ContinuePaint
 *
 * Purpose:
 *  Callback function for IViewObject2::Draw that allows us to
 *  abort a long repaint.  This implementation watches the
 *  Esc key through GetAsyncKeyState.
 *
 * Parameters:
 *  dwContinue      DWORD custom data passed to IViewObject::Draw
 *                  which in our case holds the document pointer.
 *
 * Return Value:
 *  BOOL            TRUE to continue painting, FALSE to stop it.
 */

BOOL CALLBACK ContinuePaint(DWORD dwContinue)
    {
    return !(GetAsyncKeyState(VK_ESCAPE) < 0);
    }

/*
 * CFreeloaderDoc::Load
 *
 * Purpose:
 *  Loads a given document without any user interface overwriting
 *  the previous contents of the window.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load, NULL if the file
 *                  is new and untitled.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_...
 */

UINT CFreeloaderDoc::Load(BOOL fChangeFile, LPTSTR pszFile)
    {
    HRESULT             hr;
    LPSTORAGE           pIStorage;
    LPUNKNOWN           pIUnknown;
    LPPERSISTSTORAGE    pIPersistStorage;
    DWORD               dwMode=STGM_TRANSACTED | STGM_READWRITE
                            | STGM_SHARE_EXCLUSIVE;
    CLSID               clsID;

    if (NULL==pszFile)
        {
        //Create a new temp file.
        hr=StgCreateDocfile(NULL, dwMode | STGM_CREATE
            | STGM_DELETEONRELEASE, 0, &pIStorage);

        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;

        m_pIStorage=pIStorage;

        FDirtySet(FALSE);
        Rename(NULL);
        return DOCERR_NONE;
        }

    //Attempt to open the storage.
    hr=StgOpenStorage(pszFile, NULL, dwMode, NULL, 0, &pIStorage);

    if (FAILED(hr))
        return DOCERR_COULDNOTOPEN;

    /*
     * When we previously called IPersistStorage::Save, we saved
     * the class of this object type with WriteClassStg.  Now
     * we read that CLSID, create a data cache for it, then
     * have it reload its data into the cache through
     * IPersistStorage::Load.
     */

    hr=ReadClassStg(pIStorage, &clsID);

    hr=CreateDataCache(NULL, clsID, IID_IUnknown
        , (PPVOID)&pIUnknown);

    if (FAILED(hr))
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    //Get IPersistStorage for the data we hold.
    pIUnknown->QueryInterface(IID_IPersistStorage
        , (PPVOID)&pIPersistStorage);

    //Load might fail because the object is already open...
    hr=pIPersistStorage->Load(pIStorage);
    pIPersistStorage->Release();

    if (FAILED(hr))
        {
        pIUnknown->Release();
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    m_pIStorage=pIStorage;
    m_pIUnknown=pIUnknown;

    Rename(pszFile);
    SizeToGraphic(FALSE);
    FDirtySet(FALSE);
    return DOCERR_NONE;
    }

/*
 * CFreeloaderDoc::Save
 *
 * Purpose:
 *  Writes the file to a known filename, requiring that the user
 *  has previously used FileOpen or FileSaveAs in order to have
 *  a filename.
 *
 * Parameters:
 *  uType           UINT indicating the type of file the user
 *                  requested to save in the File Save As dialog.
 *  pszFile         LPTSTR under which to save.  If NULL, use the
 *                  current name.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_...
 */

UINT CFreeloaderDoc::Save(UINT uType, LPTSTR pszFile)
    {
    HRESULT             hr;
    LPSTORAGE           pIStorage;
    LPPERSISTSTORAGE    pIPersistStorage;
    CLSID               clsID;

    //If we have no data object, there's nothing to save.
    if (NULL==m_pIUnknown)
        return DOCERR_WRITEFAILURE;

    //Get IPersistStorage for the data we hold.
    hr=m_pIUnknown->QueryInterface(IID_IPersistStorage
        , (PPVOID)&pIPersistStorage);

    if (FAILED(hr))
        return DOCERR_WRITEFAILURE;

    //Save or Save As with the same file is just a commit.
    if (NULL==pszFile || (NULL!=pszFile
        && 0==lstrcmpi(pszFile, m_szFile)))
        {
        pIPersistStorage->Save(m_pIStorage, TRUE);
        m_pIStorage->Commit(STGC_ONLYIFCURRENT);

        pIPersistStorage->SaveCompleted(NULL);
        pIPersistStorage->Release();

        FDirtySet(FALSE);
        return DOCERR_NONE;
        }

    /*
     * When we're given a name, open the storage, creating it new
     * ifit does not exist or overwriting the old one.  Then CopyTo
     * from the current to the new, Commit the new, then Release
     * the old.
     */

    hr=StgCreateDocfile(pszFile, STGM_TRANSACTED | STGM_READWRITE
        | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

    if (FAILED(hr))
        return DOCERR_COULDNOTOPEN;

    //Insure the image is up to date, then tell it we're changing
    pIPersistStorage->Save(m_pIStorage, TRUE);
    pIPersistStorage->HandsOffStorage();

    //Save the class, bitmap or metafile
    if (FAILED(pIPersistStorage->GetClassID(&clsID)))
        clsID=m_clsID;

    hr=WriteClassStg(m_pIStorage, clsID);

    hr=m_pIStorage->CopyTo(NULL, NULL, NULL, pIStorage);

    if (FAILED(hr))
        {
        pIPersistStorage->SaveCompleted(m_pIStorage);
        pIPersistStorage->Release();
        pIStorage->Release();
        return DOCERR_WRITEFAILURE;
        }

    pIStorage->Commit(STGC_ONLYIFCURRENT);

    /*
     * Revert changes on the original storage.  If this was a temp
     * file, it's deleted since we used STGM_DELETEONRELEASE.
     */
    m_pIStorage->Release();

    //Make this new storage current
    m_pIStorage=pIStorage;
    pIPersistStorage->SaveCompleted(m_pIStorage);
    pIPersistStorage->Release();

    Rename(pszFile);
    FDirtySet(FALSE);
    return DOCERR_NONE;
    }

/*
 * CFreeloaderDoc::Clip
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard, optionally implementing Cut by deleting the
 *  data in the current window after rendering.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *  fCut            BOOL indicating cut (TRUE) or copy (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CFreeloaderDoc::Clip(HWND hWndFrame, BOOL fCut)
    {
    BOOL            fRet=TRUE;
    static UINT     rgcf[3]={CF_METAFILEPICT, CF_DIB, CF_BITMAP};
    const UINT      cFormats=3;
    UINT            i;
    HGLOBAL         hMem;

    if (NULL==m_pIUnknown)
        return FALSE;

    if (!OpenClipboard(hWndFrame))
        return FALSE;

    //Clean out whatever junk is in the clipboard.
    EmptyClipboard();

    for (i=0; i < cFormats; i++)
        {
        hMem=RenderFormat(rgcf[i]);

        if (NULL!=hMem)
            {
            SetClipboardData(rgcf[i], hMem);
            fRet=TRUE;
            break;
            }
        }

    //Free clipboard ownership.
    CloseClipboard();

    //If we're cutting, clean out the cache and the object we hold.
    if (fRet && fCut)
        {
        ReleaseObject();
        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CFreeloaderDoc::RenderFormat
 *
 * Purpose:
 *  Renders a specific clipboard format into global memory.
 *
 * Parameters:
 *  cf              UINT format to render.
 *
 * Return Value:
 *  HGLOBAL         Global memory handle containing the data.
 */

HGLOBAL CFreeloaderDoc::RenderFormat(UINT cf)
    {
    LPDATAOBJECT        pIDataObject;
    FORMATETC           fe;
    STGMEDIUM           stm;

    if (NULL==m_pIUnknown)
        return NULL;

    //We only have to ask the data object (cache) for the data.
    switch (cf)
        {
        case CF_METAFILEPICT:
            stm.tymed=TYMED_MFPICT;
            break;

       case CF_DIB:
            stm.tymed=TYMED_HGLOBAL;
            break;

       case CF_BITMAP:
            stm.tymed=TYMED_GDI;
            break;

        default:
            return NULL;
        }

    stm.hGlobal=NULL;
    SETDefFormatEtc(fe, cf, stm.tymed);

    m_pIUnknown->QueryInterface(IID_IDataObject
        , (PPVOID)&pIDataObject);
    pIDataObject->GetData(&fe, &stm);
    pIDataObject->Release();

    return stm.hGlobal;
    }

/*
 * CFreeloaderDoc::FQueryPaste
 *
 * Purpose:
 *  Determines if we can paste data from the clipboard.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CFreeloaderDoc::FQueryPaste(void)
    {
    return IsClipboardFormatAvailable(CF_BITMAP)
        || IsClipboardFormatAvailable(CF_DIB)
        || IsClipboardFormatAvailable(CF_METAFILEPICT);
    }

/*
 * CFreeloaderDoc::Paste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  to the current figure in the editor window.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CFreeloaderDoc::Paste(HWND hWndFrame)
    {
    UINT                cf=0;
    HGLOBAL             hMem;
    HRESULT             hr;
    DWORD               dwConn;
    LPUNKNOWN           pIUnknown;
    LPOLECACHE          pIOleCache;
    LPPERSISTSTORAGE    pIPersistStorage;
    FORMATETC           fe;
    STGMEDIUM           stm;
    CLSID               clsID;

    if (!OpenClipboard(hWndFrame))
        return FALSE;

    /*
     * Try to get data in order of metafile, dib, bitmap.  We set
     * stm.tymed up front so if we actually get something a call
     * to ReleaseStgMedium will clean it up for us.
     */

    stm.pUnkForRelease=NULL;
    stm.tymed=TYMED_MFPICT;
    hMem=GetClipboardData(CF_METAFILEPICT);

    if (NULL!=hMem)
        cf=CF_METAFILEPICT;

    if (0==cf)
        {
        stm.tymed=TYMED_HGLOBAL;
        hMem=GetClipboardData(CF_DIB);

        if (NULL!=hMem)
            cf=CF_DIB;
        }

    if (0==cf)
        {
        stm.tymed=TYMED_GDI;
        hMem=GetClipboardData(CF_BITMAP);

        if (NULL!=hMem)
            cf=CF_BITMAP;
        }

    stm.hGlobal=OleDuplicateData(hMem, cf, NULL);
    CloseClipboard();

    //Didn't get anything?  Then we're finished.
    if (0==cf)
        return FALSE;

    //This now describes the data we have.
    SETDefFormatEtc(fe, cf, stm.tymed);

    /*
     * Create a data cache to deal with this data using
     * either CoCreateInstance for an OLE-supported CLSID or
     * CreateDataCache.  The first will go through all the
     * exercises of looking up the CLSID in the regDB, finding
     * the OLE DLL (registered for these classes), getting a class
     * factory, and using IClassFactory::CreateInstance.  The
     * second goes into OLE directly, creating the same object
     * with less overhead.  Thus we use CreateDataCache.
     */

    if (CF_METAFILEPICT==cf)
        clsID=CLSID_Picture_Metafile;
    else
        clsID=CLSID_Picture_Dib;

    hr=CreateDataCache(NULL, clsID, IID_IUnknown
        , (PPVOID)&pIUnknown);

    if (FAILED(hr))
        {
        ReleaseStgMedium(&stm);
        return FALSE;
        }

    /*
     * Our contract says we provide storage through
     * IPersistStorage::InitNew.  We know that the object we're
     * dealing with supports IPersistStorage and IOleCache, so
     * we don't bother to check return values.  I guess we're
     * living dangerously...
     */
    pIUnknown->QueryInterface(IID_IPersistStorage
        , (PPVOID)&pIPersistStorage);
    pIPersistStorage->InitNew(m_pIStorage);
    pIPersistStorage->Release();

    /*
     * Now that we have the cache object, shove the data into it.
     * No advise flags are necessary for static data.
     */
    pIUnknown->QueryInterface(IID_IOleCache, (PPVOID)&pIOleCache);
    pIOleCache->Cache(&fe, ADVF_PRIMEFIRST, &dwConn);

    hr=pIOleCache->SetData(&fe, &stm, TRUE);
    pIOleCache->Release();

    if (FAILED(hr))
        {
        ReleaseStgMedium(&stm);
        pIUnknown->Release();
        return FALSE;
        }

    //Now that that's all done, replace our current with the new.
    ReleaseObject();
    m_pIUnknown=pIUnknown;
    m_dwConn=dwConn;
    m_clsID=clsID;

    FDirtySet(TRUE);

    InvalidateRect(m_hWnd, NULL, TRUE);
    UpdateWindow(m_hWnd);
    return TRUE;
    }

/*
 * CFreeloaderDoc::SizeToGraphic
 *
 * Purpose:
 *  Determines if we can size the window to the contained
 *  graphic and alternately performs the operation.
 *
 * Parameters:
 *  fQueryOnly      BOOL indicating if we just want to know
 *                  if sizing is possible (TRUE) or that we
 *                  want to perform the sizing (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CFreeloaderDoc::SizeToGraphic(BOOL fQueryOnly)
    {
    HRESULT             hr;
    LPVIEWOBJECT2       pIViewObject2;
    SIZEL               szl;
    RECT                rc;
    UINT                cx, cy;
    HDC                 hDC;
    DWORD               dwStyle;

    if (NULL==m_pIUnknown)
        return FALSE;

    m_pIUnknown->QueryInterface(IID_IViewObject2
        , (PPVOID)&pIViewObject2);

    if (FAILED(hr))
        return FALSE;

    if (fQueryOnly)
        {
        pIViewObject2->Release();
        return TRUE;
        }

    hr=pIViewObject2->GetExtent(DVASPECT_CONTENT, -1, NULL, &szl);
    pIViewObject2->Release();

    if (FAILED(hr))
        return FALSE;

    //Calculate new doc rectangle based on these extents.

    hDC=GetDC(NULL);
    cx=MulDiv((int)szl.cx, GetDeviceCaps(hDC, LOGPIXELSX)
        , HIMETRIC_PER_INCH);
    cy=MulDiv((int)szl.cy, GetDeviceCaps(hDC, LOGPIXELSY)
        , HIMETRIC_PER_INCH);
    ReleaseDC(NULL, hDC);

    SetRect(&rc, 0, 0, cx, cy);
    dwStyle=GetWindowLong(m_hWnd, GWL_STYLE);
    AdjustWindowRect(&rc, dwStyle, FALSE);

    /*
     * If the window is currently maximized, then we have to
     * restore it first before sizing.
     */
    if (IsZoomed(m_hWnd))
        ShowWindow(m_hWnd, SW_RESTORE);

    SetWindowPos(m_hWnd, NULL, 0, 0
        , rc.right-rc.left , rc.bottom-rc.top
        , SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE);

    return TRUE;
    }


FREELOAD.CPP

/*
 * FREELOAD.CPP
 * Freeloader Chapter 11
 *
 * Freeloader application that uses OLE's data cache
 * to provide free drawing and serialization services for
 * bitmaps and metafiles.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "freeload.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCFreeloaderFrame   pFR;
    FRAMEINIT           fi;
    WPARAM              wRet;

    SETMESSAGEQUEUE(96);

    pFR=new CFreeloaderFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return -1;

    fi.idsMin=IDS_STANDARDFRAMEMIN;
    fi.idsMax=IDS_STANDARDFRAMEMAX;
    fi.idsStatMin=IDS_STATMESSAGEMIN;
    fi.idsStatMax=IDS_STATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=CW_USEDEFAULT;
    fi.cy=CW_USEDEFAULT;

    //If we can initialize pFR, start chugging messages
    if (pFR->FInit(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

/*
 * CFreeloaderFrame::CFreeloaderFrame
 * CFreeloaderFrame::~CFreeloaderFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WInMain
 */

CFreeloaderFrame::CFreeloaderFrame(HINSTANCE hInst
    , HINSTANCE hInstPrev, LPSTR pszCmdLine, int nCmdShow)
    : CFrame(hInst, hInstPrev, pszCmdLine, nCmdShow)
    {
    m_fInitialized=FALSE;
    return;
    }

CFreeloaderFrame::~CFreeloaderFrame(void)
    {
    if (m_fInitialized)
        OleUninitialize();

    return;
    }

/*
 * CFreeloaderFrame::FInit
 *
 * Purpose:
 *  Call CoInitialize then calling down into the base class
 *  initialization.
 *
 * Parameters:
 *  pFI             PFRAMEINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CFreeloaderFrame::FInit(PFRAMEINIT pFI)
    {
    CHECKVER_OLE;

    if (FAILED(OleInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    return CFrame::Init(pFI);
    }

/*
 * CFreeloaderFrame::CreateCClient
 *
 * Purpose:
 *  Constructs a new client specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient        Pointer to the new client object.
 */

PCClient CFreeloaderFrame::CreateCClient(void)
    {
    return (PCClient)(new CFreeloaderClient(m_hInst, this));
    }

/*
 * CFreeloaderFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the Cosmo frame window that just
 *  processes the line menu and the color menu leaving the
 *  CFrame to do everything else.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CFreeloaderFrame::OnCommand(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PCFreeloaderDoc     pDoc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many toolbar notifications.
     */
    if (m_fInit)
        return 0L;

    switch (wID)
        {
        case IDM_EDITSIZETOGRAPHIC:
            pDoc=(PCFreeloaderDoc)m_pCL->ActiveDocument();

            if (NULL!=pDoc)
                pDoc->SizeToGraphic(FALSE);
            break;

        default:
           CFrame::OnCommand(hWnd, wParam, lParam);
        }

    return 0L;
    }

/*
 * CFreeloaderFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CFreeloaderFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    //Edit menu:  check if the document can size to the graphic
    if (m_phMenu[1]==hMenu)
        {
        PCFreeloaderDoc pDoc;
        BOOL            fEnable=FALSE;

        pDoc=(PCFreeloaderDoc)m_pCL->ActiveDocument();

        if (NULL!=pDoc)
            fEnable=pDoc->SizeToGraphic(TRUE);

        EnableMenuItem(hMenu, IDM_EDITSIZETOGRAPHIC, MF_BYCOMMAND
            | ((fEnable) ? MF_ENABLED : MF_DISABLED | MF_GRAYED));
        }

    CFrame::UpdateMenus(hMenu, iMenu);
    return;
    }


FREELOAD.H

/*
 * FREELOAD.H
 * Freeloader Chapter 11
 *
 * Class overrides for the FreeLoader application
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _FREELOAD_H_
#define _FREELOAD_H_

#define INC_OLE2
#define INC_CLASSLIB
#define CHAPTER11
#include <inole.h>
#include "resource.h"

//FREELOAD.CPP:  Frame object that creates a main window

class CFreeloaderFrame : public CFrame
    {
    private:
        BOOL             m_fInitialized;    //OleInitialize work?

    protected:
        //Overridable for creating a CClient for this frame
        virtual PCClient CreateCClient(void);
        virtual LRESULT  OnCommand(HWND, WPARAM, LPARAM);

    public:
        CFreeloaderFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CFreeloaderFrame(void);

        virtual BOOL     FInit(PFRAMEINIT);
        virtual void     UpdateMenus(HMENU, UINT);
    };

typedef CFreeloaderFrame *PCFreeloaderFrame;

//CLIENT.CPP

//Override to create CFreeloaderDoc
class CFreeloaderClient : public CClient
    {
    protected:
        //Overridable for creating a new CDocument
        virtual PCDocument  CreateCDocument(void);

    public:
        CFreeloaderClient(HINSTANCE, PCFrame);
        virtual ~CFreeloaderClient(void);
    };

typedef CFreeloaderClient *PCFreeloaderClient;

//DOCUMENT.CPP

//Paint continuation callback for IViewObject::Draw
BOOL CALLBACK ContinuePaint(DWORD);

class CFreeloaderDoc : public CDocument
    {
    protected:
        LPSTORAGE       m_pIStorage;    //Root storage for this doc
        LPUNKNOWN       m_pIUnknown;    //Static visual object.
        DWORD           m_dwConn;       //From IOleCache::Cache
        CLSID           m_clsID;        //Data type

    protected:
        //Centralized cleanup
        void ReleaseObject(void);

        //To catch WM_PAINT
        virtual BOOL    FMessageHook(HWND, UINT, WPARAM, LPARAM
            , LRESULT *);

    public:
        CFreeloaderDoc(HINSTANCE, PCFrame, PCDocumentAdviseSink);
        virtual ~CFreeloaderDoc(void);

        virtual BOOL     FInit(PDOCUMENTINIT);

        virtual UINT     Load(BOOL, LPTSTR);
        virtual UINT     Save(UINT, LPTSTR);

        virtual BOOL     Clip(HWND, BOOL);
        virtual HGLOBAL  RenderFormat(UINT);
        virtual BOOL     FQueryPaste(void);
        virtual BOOL     Paste(HWND);

        virtual BOOL     SizeToGraphic(BOOL);
    };

typedef CFreeloaderDoc *PCFreeloaderDoc;

//For mapping HIMETRIC to pixels.
#define HIMETRIC_PER_INCH       2540

#endif //_FREELOAD_H_


PRECOMP.CPP

#include "freeload.h"


RESOURCE.H

/*
 * RESOURCE.H
 * Freeloader Chapter 11
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Menu, accelerator commands
#define IDM_EDITSIZETOGRAPHIC   (IDM_CUSTOMEDITMIN)

//Extra document strings
#define IDS_DOCUMENTMIN         IDS_STANDARDDOCMIN
#define IDS_HITESCTOSTOP        (IDS_CUSTOMDOCMIN+0)
#define IDS_DOCUMENTMAX         (IDS_CUSTOMDOCMIN+0)

//Status line messages for menu items
#define IDS_STATMESSAGEMIN                  IDS_STANDARDSTATMESSAGEMIN
#define IDS_ITEMMESSAGEEDITSIZETOGRAPHIC    (IDS_CUSTOMSTATMESSAGEMIN+0)
#define IDS_STATMESSAGEMAX                  (IDS_CUSTOMSTATMESSAGEMIN+0)

#endif //_RESOURCE_H_


CLIENT.CPP

/*
 * CLIENT.CPP
 * Component Cosmo Chapter 12
 *
 * Implementation of the CCosmoClient class that just makes sure
 * we get a CCosmoDoc on doc creation and that we initialize fully.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cocosmo.h"

/*
 * CCosmoClient::CCosmoClient
 * CCosmoClient::~CCosmoClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame to the frame object.
 */

CCosmoClient::CCosmoClient(HINSTANCE hInst, PCFrame pFR)
    : CClient(hInst, pFR)
    {
    return;
    }

CCosmoClient::~CCosmoClient(void)
    {
    return;
    }

/*
 * CCosmoClient::CreateCDocument
 *
 * Purpose:
 *  Constructs a new document specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::CreateCDocument(void)
    {
    return (PCDocument)(new CCosmoDoc(m_hInst, m_pFR, m_pAdv));
    }

/*
 * CCosmoClient::NewDocument
 *
 * Purpose:
 *  Small override of the CClient::NewDocument that we have just
 *  to check the initial line selection on new document creation.
 *
 * Parameters:
 *  fVisible        BOOL indicating if the document is to be
 *                  visible or not.
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::NewDocument(BOOL fVisible)
    {
    PCDocument  pDoc;

    //Perform default NewDocument first
    pDoc=CClient::NewDocument(fVisible);

    //We know that m_pFR is actually a CCosmoFrame, so type is safe.
    ((PCCosmoFrame)m_pFR)->CheckLineSelection(IDM_LINESOLID);

    return pDoc;
    }


COCOSMO.CPP

/*
 * COCOSMO.CPP
 * Component Cosmo Chapter 12
 *
 * WinMain and CCosmoFrame implementations.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "cocosmo.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCCosmoFrame    pFR;
    FRAMEINIT       fi;
    WPARAM          wRet;

    //Attempt to allocate and initialize the application
    pFR=new CCosmoFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return -1;

    fi.idsMin=IDS_FRAMEMIN;
    fi.idsMax=IDS_FRAMEMAX;
    fi.idsStatMin=IDS_STATMESSAGEMIN;
    fi.idsStatMax=IDS_STATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=440;
    fi.cy=460;

    //If we can initialize pFR, start chugging messages
    if (pFR->Init(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

/*
 * CCosmoFrame::CCosmoFrame
 * CCosmoFrame::~CCosmoFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WInMain
 */

CCosmoFrame::CCosmoFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    : CFrame(hInst, hInstPrev, pszCmdLine, nCmdShow)
    {
    UINT        i;

    for (i=0; i<5; i++)
        m_hBmpLines[i]=NULL;

    m_uIDCurLine=0;
    m_fInitialized=FALSE;
    return;
    }

CCosmoFrame::~CCosmoFrame(void)
    {
    UINT        i;

    for (i=0; i<5; i++)
        {
        if (NULL!=m_hBmpLines[i])
            DeleteObject(m_hBmpLines[i]);
        }

    //CHAPTER12MOD
    if (m_fInitialized)
        OleUninitialize();
    //End CHAPTER12MOD

    return;
    }

/*
 * CCosmoFrame::Init
 *
 * Purpose:
 *  Call OleInitialize then calling down into the base class
 *  initialization.
 *
 * Parameters:
 *  pFI             PFRAMEINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CCosmoFrame::Init(PFRAMEINIT pFI)
    {
    //CHAPTER12MOD
    CHECKVER_OLE;

    if (FAILED(OleInitialize(NULL)))
        return FALSE;
    //End CHAPTER12MOD

    m_fInitialized=TRUE;

    return CFrame::Init(pFI);
    }

/*
 * CCosmoFrame::CreateCClient
 *
 * Purpose:
 *  Constructs a new client specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient        Pointer to the new client object.
 */

PCClient CCosmoFrame::CreateCClient(void)
    {
    return (PCClient)(new CCosmoClient(m_hInst, this));
    }

/*
 * CCosmoFrame::RegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CCosmoFrame::RegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //First let the standard frame do its thing
    if (!CFrame::RegisterAllClasses())
        return FALSE;

    /*
     * We want a different background color for the document
     * because the Polyline we put in the document will paint
     * with COLOR_WINDOW which by default which is CLASSLIB's
     * default document color.
     */

    GetClassInfo(m_hInst, SZCLASSDOCUMENT, &wc);
    UnregisterClass(SZCLASSDOCUMENT, m_hInst);

    wc.hbrBackground=(HBRUSH)(COLOR_APPWORKSPACE+1);

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

    //No need to register the Polyline window now...

    return TRUE;
    }

/*
 * CCosmoFrame::PreShowInit
 *
 * Purpose:
 *  Called from Init before intially showing the window.  We do
 *  whatever else we want here, modifying nCmdShow as necessary
 *  which affects ShowWindow in Init.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if this initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CCosmoFrame::PreShowInit(void)
    {
    CreateLineMenu();
    CheckLineSelection(IDM_LINESOLID);
    return TRUE;
    }

/*
 * CCosmoFrame::CreateLineMenu
 *
 * Purpose:
 *  Initializes the bitmaps used to create the Line menu and
 *  replaces the text items defined in the application resources
 *  with these bitmaps.  Note that the contents of m_hBmpLines
 *  must be cleaned up when the application terminates.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CreateLineMenu(void)
    {
    HMENU       hMenu;
    HDC         hDC, hMemDC;
    HPEN        hPen;
    HGDIOBJ     hObj;
    TEXTMETRIC  tm;
    UINT        i, cx, cy;

    hMenu=GetSubMenu(GetMenu(m_hWnd), 3);   //Line menu.
    hDC=GetDC(m_hWnd);

    //Create each line in a menu item 8 chars wide, one char high.
    GetTextMetrics(hDC, &tm);
    cx=tm.tmAveCharWidth*8;
    cy=tm.tmHeight;

    /*
     * Create a memory DC in which to draw lines, and bitmaps
     * for each line.
     */
    hMemDC=CreateCompatibleDC(hDC);
    ReleaseDC(m_hWnd, hDC);

    for (i=0; i<5; i++)
        {
        m_hBmpLines[i]=CreateCompatibleBitmap(hMemDC, cx, cy);
        SelectObject(hMemDC, m_hBmpLines[i]);

        PatBlt(hMemDC, 0, 0, cx, cy, WHITENESS);

        hPen=CreatePen(i, 1, 0L);       //i=line style like PS_SOLID
        hObj=SelectObject(hMemDC, hPen);

        MoveToEx(hMemDC, 0, cy/2, NULL);
        LineTo(hMemDC, cx, cy/2);

        ModifyMenu(hMenu, IDM_LINEMIN+i, MF_BYCOMMAND | MF_BITMAP
            , IDM_LINEMIN+i, (LPTSTR)(LONG)(UINT)m_hBmpLines[i]);

        SelectObject(hMemDC, hObj);
        DeleteObject(hPen);
        }

    CheckMenuItem(hMenu, IDM_LINESOLID, MF_CHECKED);
    DeleteDC(hMemDC);

    return;
    }

/*
 * CCosmoFrame::CreateToolbar
 *
 * Purpose:
 *  Procedure to create all the necessary toolbar buttons.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of tools added to the bar.
 */

UINT CCosmoFrame::CreateToolbar(void)
    {
    UINT            iLast;
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;
    UINT            utEx  =GIZMOTYPE_BUTTONATTRIBUTEEX;

    //Insert the standard ones.
    iLast=CFrame::CreateToolbar();

    /*
     * Insert File Import in the 5th position and account for
     * it in iLast.
     */
    m_pTB->Add(utCmd, 4, IDM_FILEIMPORT, m_dxB, m_dyB
        , NULL, m_hBmp, 2, uState);
    iLast++;

    //Separator
    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    /*
     * For the Background bitmap, preserve our use of black
     * (part of the image)
     */
    m_pTB->Add(utCmd, iLast++, IDM_COLORBACKGROUND, m_dxB, m_dyB
        , NULL, m_hBmp, 3, GIZMO_NORMAL | PRESERVE_BLACK);

    m_pTB->Add(utCmd, iLast++, IDM_COLORLINE, m_dxB, m_dyB
        , NULL, m_hBmp, 4, uState);

    //Separator
    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //Line styles.
    m_pTB->Add(utEx, iLast++, IDM_LINESOLID, m_dxB, m_dyB
        , NULL, m_hBmp, 5, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASH, m_dxB, m_dyB
        , NULL, m_hBmp, 6, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 7, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASHDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 8, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASHDOTDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 9, uState);

    return iLast;
    }

/*
 * CCosmoFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the Cosmo frame window that just
 *  processes the line menu and the color menu leaving the
 *  CFrame to do everything else.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CCosmoFrame::OnCommand(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PCCosmoDoc      pDoc;
    TCHAR           szFile[CCHPATHMAX];
    BOOL            fOK;
    UINT            i, uTemp;
    COLORREF        rgColors[16];
    CHOOSECOLOR     cc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many toolbar notifications.
     */
    if (m_fInit)
        return 0L;

    pDoc=(PCCosmoDoc)m_pCL->ActiveDocument();

    /*
     * Check for the line style commands which are
     * IDM_LINEMIN+<style>.  We handle this by changing the menu
     * and toolbar, then we pass it to the document for real
     * processing.
     */
    if (NULL!=pDoc && IDM_LINEMIN <= wID && IDM_LINEMAX >=wID)
        {
        CheckLineSelection(wID);
        pDoc->LineStyleSet(wID-IDM_LINEMIN);
        return 0L;
        }

    switch (wID)
        {
        case IDM_FILEIMPORT:
            szFile[0]=0;
            fOK=SaveOpenDialog(szFile, CCHPATHMAX, IDS_FILEIMPORT
                , TRUE, &i);

            if (fOK)
                {
                uTemp=pDoc->Load(FALSE, szFile);
                pDoc->ErrorMessage(uTemp);
                }

            return (LRESULT)fOK;

        case IDM_COLORBACKGROUND:
        case IDM_COLORLINE:
            //Invoke the color chooser for either color
            uTemp=(IDM_COLORBACKGROUND==wID)
                ? DOCCOLOR_BACKGROUND : DOCCOLOR_LINE;

            for (i=0; i<16; i++)
                rgColors[i]=RGB(0, 0, i*16);

            memset(&cc, 0, sizeof(CHOOSECOLOR));
            cc.lStructSize=sizeof(CHOOSECOLOR);
            cc.lpCustColors=rgColors;
            cc.hwndOwner=hWnd;
            cc.Flags=CC_RGBINIT;
            cc.rgbResult=pDoc->ColorGet(uTemp);

            if (ChooseColor(&cc))
                pDoc->ColorSet(uTemp, cc.rgbResult);

            break;

        default:
           CFrame::OnCommand(hWnd, wParam, lParam);
        }

    return 0L;
    }

/*
 * CCosmoFrame::OnDocumentDataChange
 *
 * Purpose:
 *  Update the Line menu and toolbar if the style in the data
 *  changes.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentDataChange(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::OnDocumentActivate
 *
 * Purpose:
 *  Informs us that document activation changed, so update the UI
 *  for that new document.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentActivate(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    PCDocument  pDoc;
    BOOL        fOK=FALSE;
    BOOL        fCallDefault=TRUE;
    UINT        i;
    UINT        uTemp;
    UINT        uTempE;
    UINT        uTempD;

    pDoc=m_pCL->ActiveDocument();

    uTempE=MF_ENABLED | MF_BYCOMMAND;
    uTempD=MF_DISABLED | MF_GRAYED | MF_BYCOMMAND;
    uTemp=((NULL!=pDoc) ? uTempE : uTempD);

    //File menu:  If there is document window, disable Import.
    if (m_phMenu[0]==hMenu)
        EnableMenuItem(hMenu, IDM_FILEIMPORT, uTemp);

    //Color menu:  no document, no commands
    if (m_phMenu[2]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_COLORBACKGROUND, uTemp);
        EnableMenuItem(hMenu, IDM_COLORLINE,       uTemp);
        fCallDefault=FALSE;
        }

    //Line menu:  no document, no commands
    if (m_phMenu[3]==hMenu)
        {
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            EnableMenuItem(hMenu, i, uTemp);

        fCallDefault=FALSE;
        }

    if (fCallDefault)
        CFrame::UpdateMenus(hMenu, iMenu);

    return;
    }

/*
 * CCosmoFrame::UpdateToolbar
 *
 * Purpose:
 *  Enables and disables tools depending on whether we have
 *  a document or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateToolbar(void)
    {
    BOOL        fLast;
    UINT        i;

    //Save the last enabled state before CFrame changes it
    fLast=m_fLastEnable;

    //Let the default hack on its tools
    CFrame::UpdateToolbar();

    /*
     * If CFrame::UpdateToolbar changed anything, then we need
     * to change as well--if nothing changes, nothing to do.
     */
    if (fLast!=m_fLastEnable)
        {
        m_pTB->Enable(IDM_FILEIMPORT, m_fLastEnable);

        m_pTB->Enable(IDM_COLORBACKGROUND, m_fLastEnable);
        m_pTB->Enable(IDM_COLORLINE,       m_fLastEnable);

        for (i=IDM_LINEMIN; i <= IDM_LINEMAX; i++)
            m_pTB->Enable(i, m_fLastEnable);
        }

    return;
    }

/*
 * CCosmoFrame::CheckLineSelection
 *
 * Purpose:
 *  Maintains the bitmap menu and the tools for the line selection.
 *  Both are mutially exclusive option lists where a selection in
 *  one has to affect the other.
 *
 * Parameters:
 *  uID             UINT ID of the item to be selected
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CheckLineSelection(UINT uID)
    {
    UINT        i;
    HMENU       hMenu;

    //Update menus and tools if the selection changed.
    if (uID!=m_uIDCurLine)
        {
        m_uIDCurLine=uID;
        hMenu=GetMenu(m_hWnd);

        //Uncheck all lines initially.
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            CheckMenuItem(hMenu, i, MF_UNCHECKED | MF_BYCOMMAND);

        CheckMenuItem(hMenu, uID, MF_CHECKED | MF_BYCOMMAND);
        m_pTB->Check(uID, TRUE);
        }

    return;
    }


COCOSMO.H

/*
 * COCOSMO.H
 * Component Cosmo Chapter 12
 *
 * Single include file that pulls in everything needed for other
 * source files in the Cosmo application.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _COCOSMO_H_
#define _COCOSMO_H_

#define INC_CLASSLIB
#define INC_CONTROLS
#define INC_OLE2
//CHAPTER12MOD
#define CHAPTER12
//CHAPTER12MOD
#include <inole.h>
#include <ipoly10.h>
#include "resource.h"

//COCOSMO.CPP:  Frame object that creates a main window

class CCosmoFrame : public CFrame
    {
    private:
        HBITMAP         m_hBmpLines[5];     //Menu item bitmaps
        UINT            m_uIDCurLine;       //Current line selection
        BOOL            m_fInitialized;     //OleInitialize work?

    protected:
        //Overridable for creating a CClient for this frame
        virtual PCClient  CreateCClient(void);

        virtual BOOL      RegisterAllClasses(void);
        virtual BOOL      PreShowInit(void);
        virtual UINT      CreateToolbar(void);

        virtual LRESULT   OnCommand(HWND, WPARAM, LPARAM);
        virtual void      OnDocumentDataChange(PCDocument);
        virtual void      OnDocumentActivate(PCDocument);

        //New for this class
        virtual void      CreateLineMenu(void);

    public:
        CCosmoFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CCosmoFrame(void);

        //Overrides
        virtual BOOL      Init(PFRAMEINIT);
        virtual void      UpdateMenus(HMENU, UINT);
        virtual void      UpdateToolbar(void);

        //New for this class
        virtual void      CheckLineSelection(UINT);
    };

typedef CCosmoFrame *PCCosmoFrame;

//CLIENT.CPP

/*
 * The only reason we have a derived class here is to override
 * CreateCDocument so we can create our own type as well as
 * overriding NewDocument to perform one other piece of work once
 * the document's been created.
 */

class CCosmoClient : public CClient
    {
    protected:
        //Overridable for creating a new CDocument
        virtual PCDocument CreateCDocument(void);

    public:
        CCosmoClient(HINSTANCE, PCFrame);
        virtual ~CCosmoClient(void);

        virtual PCDocument NewDocument(BOOL);
    };

typedef CCosmoClient *PCCosmoClient;

//DOCUMENT.CPP

class CCosmoDoc;
typedef CCosmoDoc *PCCosmoDoc;

class CPolylineAdviseSink : public IPolylineAdviseSink10
    {
    private:
        PCCosmoDoc  m_pDoc;         //Backpointer to document
        ULONG       m_cRef;

    public:
        CPolylineAdviseSink(PCCosmoDoc);
        ~CPolylineAdviseSink(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //Advise members.
        STDMETHODIMP_(void) OnPointChange(void);
        STDMETHODIMP_(void) OnSizeChange(void);
        STDMETHODIMP_(void) OnColorChange(void);
        STDMETHODIMP_(void) OnLineStyleChange(void);
    };

typedef CPolylineAdviseSink *PCPolylineAdviseSink;

/*
 * The generic advisory interface.  This object controls it's own
 * lifetime and the document becomes a user of the object with
 * the last reference count.
 */

class CImpIAdviseSink : public IAdviseSink
    {
    protected:
        ULONG               m_cRef;
        LPVOID              m_pObj;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIAdviseSink(LPVOID, LPUNKNOWN);
        ~CImpIAdviseSink(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(void)  OnDataChange(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP_(void)  OnViewChange(DWORD, LONG);
        STDMETHODIMP_(void)  OnRename(LPMONIKER);
        STDMETHODIMP_(void)  OnSave(void);
        STDMETHODIMP_(void)  OnClose(void);
    };

typedef CImpIAdviseSink *PCImpIAdviseSink;

//Constant ID for the window polyline that lives in a document
#define ID_POLYLINE         10

//Stream name to open with IPersistStream[Init]
#define SZSTREAM                    OLETEXT("CONTENTS")

class CCosmoDoc : public CDocument, public IUnknown
    {
    friend class CPolylineAdviseSink;
    friend class CImpIAdviseSink;

    protected:
        UINT                    m_uPrevSize;    //Last WM_SIZE wParam

        IPolyline10            *m_pPL;          //Polyline object
        IPolylineAdviseSink10  *m_pPLAdv;

        IConnectionPoint       *m_pIConnectPt;
        DWORD                   m_dwCookie;     //Connection key
        IStorage               *m_pIStorage;    //Doc storage
        PERSISTPOINTER          m_pp;

        PCImpIAdviseSink        m_pImpIAdviseSink;

        DWORD                   m_dwConn;       //Advisory connection
        ULONG                   m_cRef;         //Document ref count

        //CHAPTER12MOD
        LPDATAOBJECT            m_pIDataClip;   //Clipboard object
        //End CHAPTER12MOD

    protected:
        virtual BOOL     FMessageHook(HWND, UINT, WPARAM, LPARAM
            , LRESULT *);

    public:
        CCosmoDoc(HINSTANCE, PCFrame, PCDocumentAdviseSink);
        virtual ~CCosmoDoc(void);

        //Need a controlling unknown for our interfaces
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        virtual BOOL     Init(PDOCUMENTINIT);
        virtual void     Clear(void);

        virtual UINT     Load(BOOL, LPTSTR);
        virtual UINT     Save(UINT, LPTSTR);

        virtual void     Undo(void);
        virtual BOOL     Clip(HWND, BOOL);

        //CHAPTER12MOD
        //RenderFormat no longer necessary.
        //End CHAPTER12MOD

        virtual BOOL     FQueryPaste(void);
        virtual BOOL     Paste(HWND);

        virtual COLORREF ColorSet(UINT, COLORREF);
        virtual COLORREF ColorGet(UINT);

        virtual UINT     LineStyleSet(UINT);
        virtual UINT     LineStyleGet(void);
    };

typedef CCosmoDoc *PCCosmoDoc;

//These color indices wrap the polyline definitions
#define DOCCOLOR_BACKGROUND             POLYLINECOLOR_BACKGROUND
#define DOCCOLOR_LINE                   POLYLINECOLOR_LINE

#endif //_COCOSMO_H_


DOCUMENT.CPP

/*
 * DOCUMENT.CPP
 * Component Cosmo Chapter 12
 *
 * Implementation of the CCosmoDoc derivation of CDocument
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cocosmo.h"

/*
 * CCosmoDoc::CCosmoDoc
 * CCosmoDoc::~CCosmoDoc
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame of the frame object.
 *  pAdv            PCDocumentAdviseSink to notify on events
 */

CCosmoDoc::CCosmoDoc(HINSTANCE hInst, PCFrame pFR
    , PCDocumentAdviseSink pAdv)
    : CDocument(hInst, pFR, pAdv)
    {
    m_pPL=NULL;
    m_pPLAdv=NULL;
    m_uPrevSize=SIZE_RESTORED;
    m_pIConnectPt=NULL;
    m_dwCookie=0;

    m_pIStorage=NULL;
    m_pp.psModel=PERSIST_UNKNOWN;
    m_pp.pIP.pIPersistStorage=NULL; //Affects all pointers

    m_pImpIAdviseSink=NULL;
    m_dwConn=0;

    //CHAPTER12MOD
    m_pIDataClip=NULL;
    //End CHAPTER12MOD
    return;
    }

CCosmoDoc::~CCosmoDoc(void)
    {
    LPDATAOBJECT        pIDataObject;
    HRESULT             hr;

    //CHAPTER12MOD
    if (NULL!=m_pIDataClip)
        m_pIDataClip->Release();
    //End CHAPTER12MOD

    //Turn off the advise.
    if (NULL!=m_pPL && 0!=m_dwConn)
        {
        hr=m_pPL->QueryInterface(IID_IDataObject
            , (PPVOID)&pIDataObject);

        if (SUCCEEDED(hr))
            {
            pIDataObject->DUnadvise(m_dwConn);
            pIDataObject->Release();
            }
        }

    DeleteInterfaceImp(m_pImpIAdviseSink);
    ReleaseInterface(m_pp.pIP.pIPersistStorage);
    ReleaseInterface(m_pIStorage);

    if (NULL!=m_pIConnectPt)
        {
        m_pIConnectPt->Unadvise(m_dwCookie);
        ReleaseInterface(m_pIConnectPt);
        }

    ReleaseInterface(m_pPL);
    ReleaseInterface(m_pPLAdv);

    CoFreeUnusedLibraries();
    return;
    }

/*
 * CCosmoDoc::Init
 *
 * Purpose:
 *  Initializes an already created document window.  The client
 *  actually creates the window for us, then passes that here for
 *  further initialization.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CCosmoDoc::Init(PDOCUMENTINIT pDI)
    {
    RECT                        rc;
    HRESULT                     hr;
    IConnectionPointContainer  *pCPC;
    FORMATETC                   fe;
    LPDATAOBJECT                pIDataObject;

    //Change the stringtable range to our customization.
    pDI->idsMin=IDS_DOCUMENTMIN;
    pDI->idsMax=IDS_DOCUMENTMAX;

    //Do default initialization
    if (!CDocument::Init(pDI))
        return FALSE;

    //Create the Polyline Component
    hr=CoCreateInstance(CLSID_Polyline10, NULL, CLSCTX_INPROC_SERVER
        , IID_IPolyline10, (PPVOID)&m_pPL);

    if (FAILED(hr))
        {
        //Warn that we could not load the Polyline
        MessageBox(pDI->hWndDoc, PSZ(IDS_NOPOLYLINE)
            , PSZ(IDS_CAPTION), MB_OK);
        return FALSE;
        }

    //Initialize the contained Polyline which creates a window.
    GetClientRect(m_hWnd, &rc);
    InflateRect(&rc, -8, -8);

    if (FAILED(m_pPL->Init(m_hWnd, &rc, WS_CHILD | WS_VISIBLE
        , ID_POLYLINE)))
        return FALSE;

    //Set up an advise on the Polyline.
    m_pPLAdv=new CPolylineAdviseSink(this);
    m_pPLAdv->AddRef();

    if (SUCCEEDED(m_pPL->QueryInterface(IID_IConnectionPointContainer
        , (PPVOID)&pCPC)))
        {
        //CHAPTER10MOD
        if (SUCCEEDED(pCPC->FindConnectionPoint
            (IID_IPolylineAdviseSink10, &m_pIConnectPt)))
        //End CHAPTER10MOD
            {
            m_pIConnectPt->Advise((LPUNKNOWN)m_pPLAdv, &m_dwCookie);
            }

        pCPC->Release();
        }

    /*
     * Check for the storage model being used in Polyline.
     * Any InitNew member will be called from CCosmoDoc::Load.
     */
    hr=m_pPL->QueryInterface(IID_IPersistStorage
        , (PPVOID)&m_pp.pIP.pIPersistStorage);

    if (SUCCEEDED(hr))
        m_pp.psModel=PERSIST_STORAGE;
    else
        {
        hr=m_pPL->QueryInterface(IID_IPersistStreamInit
            , (PPVOID)&m_pp.pIP.pIPersistStreamInit);

        if (SUCCEEDED(hr))
            m_pp.psModel=PERSIST_STREAMINIT;
        else
            {
            hr=m_pPL->QueryInterface(IID_IPersistStream
            , (PPVOID)&m_pp.pIP.pIPersistStream);

            if (SUCCEEDED(hr))
                m_pp.psModel=PERSIST_STREAM;
            else
                return FALSE;
            }
        }

    /*
     * Create an IAdviseSink and send it to the Polyline's
     * IDataObject with the clipboard format for the Polyline
     * (as in IPOLY10.H).
     */

    //This is a private macro.
    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);

    m_pImpIAdviseSink=new CImpIAdviseSink(this, this);

    if (NULL==m_pImpIAdviseSink)
        return FALSE;

    //Set up an advise for the Polyline format
    hr=m_pPL->QueryInterface(IID_IDataObject, (PPVOID)&pIDataObject);

    if (FAILED(hr))
        return FALSE;

    pIDataObject->DAdvise(&fe, ADVF_NODATA, m_pImpIAdviseSink
        , &m_dwConn);
    pIDataObject->Release();

    return TRUE;
    }

//IUnknown interface for all others we implement in the document

/*
 * CCosmoDoc::QueryInterface
 * CCosmoDoc::AddRef
 * CCosmoDoc::Release
 *
 * Purpose:
 *  IUnknown members for the CCosmoDoc implementation.
 */

STDMETHODIMP CCosmoDoc::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    //The document is the unknown
    if (IID_IUnknown==riid)
        *ppv=(LPUNKNOWN)this;

    //Return contained interfaces for others.
    if (IID_IPolylineAdviseSink10==riid)
        *ppv=m_pPLAdv;

    if (IID_IAdviseSink==riid)
        *ppv=m_pImpIAdviseSink;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(S_FALSE);
    }

STDMETHODIMP_(ULONG) CCosmoDoc::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CCosmoDoc::Release(void)
    {
    return --m_cRef;
    }

/*
 * CCosmoDoc::FMessageHook
 *
 * Purpose:
 *  Processes WM_SIZE for the document so we can resize
 *  the Polyline.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CCosmoDoc::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    UINT        dx, dy;
    RECT        rc;

    *pLRes=0;

    //CHAPTER12MOD
    if (WM_DESTROY==iMsg)
        {
        /*
         * Flush the clipboard data object if it's still on the
         * clipboard before the document window is destroyed which
         * also destroys the polyline, which is a child.  Flushing
         * now avoid errors that would occur if flushing happened
         * at application shutdown, for the Polyline would still be
         * alive but without a valid window.
         */
        if (NULL!=m_pIDataClip)
            {
            if (NOERROR==OleIsCurrentClipboard(m_pIDataClip))
                OleFlushClipboard();
            }
        }
    //End CHAPTER12MOD

    if (WM_SIZE==iMsg)
        {
        //Don't effect the Polyline size to or from minimized state.
        if (SIZE_MINIMIZED!=wParam && SIZE_MINIMIZED !=m_uPrevSize)
            {
            //When we change size, resize any Polyline we hold.
            dx=LOWORD(lParam);
            dy=HIWORD(lParam);

            /*
             * If we are getting WM_SIZE in response to a Polyline
             * notification, then don't resize the Polyline window
             * again.
             */
            if (!m_fNoSize && NULL!=m_pPL)
                {
                //Resize the polyline to fit the new client
                SetRect(&rc, 8, 8, dx-8, dy-8);
                m_pPL->RectSet(&rc, FALSE);

                /*
                 * We consider sizing something that makes the file
                 * dirty, but not until we've finished the create
                 * process, which is why we set fNoDirty to FALSE
                 * in WM_CREATE since we get a WM_SIZE on the first
                 * creation.
                 */
                if (!m_fNoDirty)
                    FDirtySet(TRUE);

                SetRect(&rc, 0, 0, dx, dy);

                if (NULL!=m_pAdv)
                    m_pAdv->OnSizeChange(this, &rc);

                m_fNoDirty=FALSE;
                }
            }

        m_uPrevSize=wParam;
        }

    /*
     * We return FALSE even on WM_SIZE so we can let the default
     * procedure handle maximized MDI child windows appropriately.
     */
    return FALSE;
    }

/*
 * CCosmoDoc::Clear
 *
 * Purpose:
 *  Sets all contents in the document back to defaults with
 *  no filename.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Clear(void)
    {
    //Completely reset the polyline
    m_pPL->New();

    CDocument::Clear();
    return;
    }

/*
 * CCosmoDoc::Load
 *
 * Purpose:
 *  Loads a given document without any user interface overwriting
 *  the previous contents of the Polyline window.  We do this by
 *  opening the file and telling the Polyline to load itself from
 *  that file.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load, NULL if the file
 *                  is new and untitled.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CCosmoDoc::Load(BOOL fChangeFile, LPTSTR pszFile)
    {
    HRESULT             hr;
    LPSTORAGE           pIStorage;

    if (NULL==pszFile)
        {
        /*
         * As a user of an IPersistStorage we have to provide all
         * objects with an IStorage they can use for incremental
         * access passing that storage to InitNew.  Here we create
         * a temporary file that we don't bother holding on to.
         * If the object doesn't use it, then our Release destroys
         * it immediately.
         */

        hr=StgCreateDocfile(NULL, STGM_DIRECT | STGM_READWRITE
            | STGM_CREATE | STGM_DELETEONRELEASE
            | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;

        if (PERSIST_STORAGE==m_pp.psModel)
            m_pp.pIP.pIPersistStorage->InitNew(pIStorage);
        else
            {
            if (PERSIST_STREAMINIT==m_pp.psModel)
                m_pp.pIP.pIPersistStreamInit->InitNew();
            }

        m_pIStorage=pIStorage;

        Rename(NULL);
        return DOCERR_NONE;
        }

    /*
     * Open a storage and have the Polyline read its data
     * using whatever persistence model it employs.
     */
    hr=StgOpenStorage(pszFile, NULL, STGM_DIRECT | STGM_READWRITE
        | STGM_SHARE_EXCLUSIVE, NULL, 0, &pIStorage);

    if (FAILED(hr))
        return DOCERR_COULDNOTOPEN;

    if (PERSIST_STORAGE==m_pp.psModel)
        hr=m_pp.pIP.pIPersistStorage->Load(pIStorage);
    else
        {
        LPSTREAM    pIStream;

        hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
            | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

        if (SUCCEEDED(hr))
            {
            //This also works for PERSIST_STREAMINIT
            hr=m_pp.pIP.pIPersistStream->Load(pIStream);
            pIStream->Release();
            }
        }

    m_pIStorage=pIStorage;

    if (FAILED(hr))
        return DOCERR_READFAILURE;

    if (fChangeFile)
        Rename(pszFile);

    //Importing a file makes things dirty
    FDirtySet(!fChangeFile);

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::Save
 *
 * Purpose:
 *  Writes the file to a known filename, requiring that the user
 *  has previously used FileOpen or FileSaveAs in order to have
 *  a filename.
 *
 * Parameters:
 *  uType           UINT indicating the type of file the user
 *                  requested to save in the File Save As dialog.
 *  pszFile         LPTSTR under which to save.  If NULL, use the
 *                  current name.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CCosmoDoc::Save(UINT uType, LPTSTR pszFile)
    {
    BOOL                fRename=TRUE;
    HRESULT             hr;
    LPSTORAGE           pIStorage;
    BOOL                fSameAsLoad;

    //If Save or Save As under the same name, do Save.
    if (NULL==pszFile || 0==lstrcmpi(pszFile, m_szFile))
        {
        fRename=FALSE;
        pszFile=m_szFile;

        /*
         * If we're saving to an existing storage, just pass
         * the IStorage we have from Load along with TRUE
         * in fSameAsLoad.
         */

        fSameAsLoad=TRUE;
        }
    else
        {
        /*
         * In Component Cosmo, we only deal with one version of
         * data; all the code in Chapter 1 Cosmo that dealt with
         * 1.0 and 2.0 files has been removed.
         */

        hr=StgCreateDocfile(pszFile, STGM_DIRECT | STGM_READWRITE
            | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;

        //Tell the object to save into this new storage
        fSameAsLoad=FALSE;

        //Update our variable
        m_pIStorage->Release();
        m_pIStorage=pIStorage;
        }

    if (PERSIST_STORAGE==m_pp.psModel)
        {
        hr=m_pp.pIP.pIPersistStorage->Save(m_pIStorage, fSameAsLoad);

        if (SUCCEEDED(hr))
            {
            hr=m_pp.pIP.pIPersistStorage->SaveCompleted(fSameAsLoad
                ? NULL : m_pIStorage);
            }
        }
    else
        {
        LPSTREAM    pIStream;

        hr=m_pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
            | STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE
            , 0, 0, &pIStream);

        if (SUCCEEDED(hr))
            {
            //This also works for PERSIST_STREAMINIT
            hr=m_pp.pIP.pIPersistStream->Save(pIStream, TRUE);
            pIStream->Release();
            }
        }

    if (FAILED(hr))
        return DOCERR_WRITEFAILURE;

    //Saving makes us clean
    FDirtySet(FALSE);

    if (fRename)
        Rename(pszFile);

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::Undo
 *
 * Purpose:
 *  Reverses a previous action.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Undo(void)
    {
    m_pPL->Undo();
    return;
    }

/*
 * CCosmoDoc::Clip
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard, optionally implementing Cut by deleting the
 *  data in the current window after rendering.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *  fCut            BOOL indicating cut (TRUE) or copy (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::Clip(HWND hWndFrame, BOOL fCut)
    {
    //CHAPTER12MOD
    PPOLYLINE               pPL;
    LPDATAOBJECT            pDataSrc, pIDataObject;
    FORMATETC               fe;
    STGMEDIUM               stm;
    BOOL                    fRet=TRUE;
    HRESULT                 hr;
    RECT                    rc;

    //Create a transfer Polyline Object
    hr=CoCreateInstance(CLSID_Polyline10, NULL, CLSCTX_INPROC_SERVER
        , IID_IPolyline10, (PPVOID)&pPL);

    if (FAILED(hr))
        return FALSE;

    //Make the new transfer Polyline same size as the current one.
    m_pPL->RectGet(&rc);

    if (FAILED(pPL->Init(m_hWnd, &rc, WS_CHILD, ID_POLYLINE)))
        {
        pPL->Release();
        return FALSE;
        }

    /*
     * Now that we have the new object, get IDataObject interfaces
     * on both that one and the new one we hold, then copy the
     * data from the current to the new.
     */

    m_pPL->QueryInterface(IID_IDataObject, (PPVOID)&pDataSrc);

    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    fRet=SUCCEEDED(pDataSrc->GetData(&fe, &stm));
    pDataSrc->Release();

    if (!fRet)
        {
        pPL->Release();
        return FALSE;
        }

    pPL->QueryInterface(IID_IDataObject, (PPVOID)&pIDataObject);
    pPL->Release();

    pIDataObject->SetData(&fe, &stm, TRUE);

    fRet=SUCCEEDED(OleSetClipboard(pIDataObject));

    /*
     * OleSetClipboard will call AddRef on pDataDst, and we
     * still own a reference count ourselves.  We thus save
     * this pointer so we can call OleIsCurrentClipboard and
     * OleFlushClipboard when the document is destroyed.
     */

    if (NULL!=m_pIDataClip)
        m_pIDataClip->Release();

    m_pIDataClip=pIDataObject;

    //Delete our current data if a "cut" succeeded.
    if (fRet && fCut)
        {
        m_pPL->New();
        FDirtySet(TRUE);
        }

    return fRet;
    //End CHAPTER12MOD
    }

//CHAPTER12MOD
//CCosmoDoc::RenderFormat no longer necessary.
//End CHAPTER12MOD

/*
 * CCosmoDoc::FQueryPaste
 *
 * Purpose:
 *  Determines if we can paste data from the clipboard.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CCosmoDoc::FQueryPaste(void)
    {
    //CHAPTER12MOD
    LPDATAOBJECT    pIDataObject;
    FORMATETC       fe;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    fRet=(NOERROR==pIDataObject->QueryGetData(&fe));

    pIDataObject->Release();
    return fRet;
    //End CHAPTER12MOD
    }

/*
 * CCosmoDoc::Paste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  to the current figure in the editor window.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::Paste(HWND hWndFrame)
    {
    //CHAPTER12MOD
    LPDATAOBJECT    pIDataObject;
    FORMATETC       fe;
    STGMEDIUM       stm;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    fRet=SUCCEEDED(pIDataObject->GetData(&fe, &stm));
    pIDataObject->Release();

    if (!fRet || NULL==stm.hGlobal)
        return FALSE;

    //Send the data to the Polyline now.
    m_pPL->QueryInterface(IID_IDataObject, (PPVOID)&pIDataObject);
    pIDataObject->SetData(&fe, &stm, TRUE);
    pIDataObject->Release();

    FDirtySet(TRUE);
    //End CHAPTER12MOD

    return TRUE;
    }

/*
 * CCosmoDoc::ColorSet
 *
 * Purpose:
 *  Changes a color used in our contained Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color.
 *
 * Return Value:
 *  COLORREF        Previous color for the given index.
 */

COLORREF CCosmoDoc::ColorSet(UINT iColor, COLORREF cr)
    {
    COLORREF    crRet;

    m_pPL->ColorSet(iColor, cr, &crRet);
    return crRet;
    }

/*
 * CCosmoDoc::ColorGet
 *
 * Purpose:
 *  Retrieves a color currently in use in the Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to retrieve.
 *
 * Return Value:
 *  COLORREF        Current color for the given index.
 */

COLORREF CCosmoDoc::ColorGet(UINT iColor)
    {
    COLORREF    crRet;

    m_pPL->ColorGet(iColor, &crRet);
    return crRet;
    }

/*
 * CCosmoDoc::LineStyleSet
 *
 * Purpose:
 *  Changes the line style currently used in the Polyline
 *
 * Parameters:
 *  iStyle          UINT index of the new line style to use.
 *
 * Return Value:
 *  UINT            Previous line style.
 */

UINT CCosmoDoc::LineStyleSet(UINT iStyle)
    {
    UINT    i;

    m_pPL->LineStyleSet(iStyle, &i);
    return i;
    }

/*
 * CCosmoDoc::LineStyleGet
 *
 * Purpose:
 *  Retrieves the line style currently used in the Polyline
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Current line style.
 */

UINT CCosmoDoc::LineStyleGet(void)
    {
    UINT    i=0;

    //m_pPL might not be valid yet.
    if (NULL!=m_pPL)
        m_pPL->LineStyleGet(&i);

    return i;
    }


IADVSINK.CPP

/*
 * IADVSINK.CPP
 * Component Cosmo Chapter 12
 *
 * Implementation of the CPolylineAdviseSink and CImpIAdviseSink
 * interfaces for Component Cosmo.  CPolylineAdviseSink moved
 * here from document.cpp to live with all the advise stuff.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cocosmo.h"

/*
 * CPolylineAdviseSink::CPolylineAdviseSink
 * CPolylineAdviseSink::~CPolylineAdviseSink
 *
 * Constructor Parameters:
 *  pDoc            PCCosmoDoc to store in this object
 */

CPolylineAdviseSink::CPolylineAdviseSink(PCCosmoDoc pDoc)
    {
    m_pDoc=pDoc;
    m_cRef=0;
    AddRef();
    return;
    }

CPolylineAdviseSink::~CPolylineAdviseSink(void)
    {
    return;
    }

/*
 * CPolylineAdviseSink::QueryInterface
 * CPolylineAdviseSink::AddRef
 * CPolylineAdviseSink::Release
 *
 * Purpose:
 *  IUnknown members for this IPolylineAdviseSink implementations.
 */

STDMETHODIMP CPolylineAdviseSink::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pDoc->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CPolylineAdviseSink::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPolylineAdviseSink::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CPolylineAdviseSink::OnPointChange
 *
 * Purpose:
 *  Informs the document that the polyline added or removed a point.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CPolylineAdviseSink::OnPointChange(void)
    {
    m_pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnSizeChange
 *
 * Purpose:
 *  Informs the document that the polyline changed size.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CPolylineAdviseSink::OnSizeChange(void)
    {
    RECT            rc;
    DWORD           dwStyle;
    HWND            hWnd;

    /*
     * Polyline window is informing us that it changed size in
     * response to setting it's data.  Therefore we have to
     * size ourselves accordingly but without moving the screen
     * position of the polyline window.
     */

    m_pDoc->m_fNoSize=TRUE;

    //Set the document window size.
    m_pDoc->m_pPL->Window(&hWnd);
    GetWindowRect(hWnd, &rc);
    InflateRect(&rc, 8, 8);

    //Adjust for a window sans menu
    dwStyle=GetWindowLong(m_pDoc->m_hWnd, GWL_STYLE);
    AdjustWindowRect(&rc, dwStyle, FALSE);

    SetWindowPos(m_pDoc->m_hWnd, NULL, 0, 0, rc.right-rc.left
        , rc.bottom-rc.top, SWP_NOMOVE | SWP_NOZORDER);

    if (NULL!=m_pDoc->m_pAdv)
        m_pDoc->m_pAdv->OnSizeChange(m_pDoc, &rc);

    m_pDoc->m_fNoSize=FALSE;
    m_pDoc->FDirtySet(TRUE);

    return;
    }

/*
 * CPolylineAdviseSink::OnColorChange
 *
 * Purpose:
 *  Informs the document that the polyline data changed a color.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CPolylineAdviseSink::OnColorChange(void)
    {
    m_pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnLineStyleChange
 *
 * Purpose:
 *  Informs the document that the polyline changed its line style.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CPolylineAdviseSink::OnLineStyleChange(void)
    {
    m_pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CImpIAdviseSink::CImpIAdviseSink
 * CImpIAdviseSink::~CImpIAdviseSink
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN for delegation of IUnknown members.
 */

CImpIAdviseSink::CImpIAdviseSink(LPVOID pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIAdviseSink::~CImpIAdviseSink(void)
    {
    return;
    }

/*
 * CImpIAdviseSink::QueryInterface
 * CImpIAdviseSink::AddRef
 * CImpIAdviseSink::Release
 *
 * Purpose:
 *  IUnknown members for CImpIAdviseSink object.
 */

STDMETHODIMP CImpIAdviseSink::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIAdviseSink::OnDataChange
 *
 * Purpose:
 *  Notifes the advise sink that data changed in a data object.
 *  On this message you may request a new data rendering and
 *  update your displays as necessary.
 *
 * Parameters:
 *  pFEIn           LPFORMATETC describing format that changed
 *  pSTM            LPSTGMEDIUM providing the medium in which the
 *                  data is provided.
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnDataChange(LPFORMATETC pFEIn
    , LPSTGMEDIUM pSTM)
    {
    PCCosmoDoc      m_pDoc=(PCCosmoDoc)m_pObj;

    /*
     * This code is from former CPolylineAdviseSink::OnDataChange.
     * The only advise we asked for was on the Polyline native
     * format which is all we'll be notified for.
     */
    if (NULL!=m_pDoc->m_pAdv)
        m_pDoc->m_pAdv->OnDataChange(m_pDoc);

    m_pDoc->FDirtySet(TRUE);
    return;
    }

STDMETHODIMP_(void) CImpIAdviseSink::OnViewChange(DWORD dwAspect
    , LONG lindex)
    {
    return;
    }

STDMETHODIMP_(void) CImpIAdviseSink::OnRename(LPMONIKER pmk)
    {
    return;
    }

STDMETHODIMP_(void) CImpIAdviseSink::OnSave(void)
    {
    return;
    }

STDMETHODIMP_(void) CImpIAdviseSink::OnClose(void)
    {
    return;
    }


RESOURCE.H

/*
 * RESOURCE.H
 * Component Cosmo Chapter 12
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Note that we augment classlib.h which we include before this.

//Position of Window menu.
#undef  WINDOW_MENU
#define WINDOW_MENU             4

//Total number of menus
#undef  CMENUS
#define CMENUS                  5

//Menu command identifiers.

#define IDM_FILEIMPORT          (IDM_CUSTOMFILEMIN)

#define IDM_COLORBACKGROUND     (IDM_CUSTOMMIN)
#define IDM_COLORLINE           (IDM_CUSTOMMIN+1)

#define IDM_LINEMIN             (IDM_CUSTOMMIN+100)
#define IDM_LINESOLID           (IDM_LINEMIN+0)         //PS_SOLID       0
#define IDM_LINEDASH            (IDM_LINEMIN+1)         //PS_DASH        1
#define IDM_LINEDOT             (IDM_LINEMIN+2)         //PS_DOT         2
#define IDM_LINEDASHDOT         (IDM_LINEMIN+3)         //PS_DASHDOT     3
#define IDM_LINEDASHDOTDOT      (IDM_LINEMIN+4)         //PS_DASHDOTDOT  4
#define IDM_LINEMAX             IDM_LINEDASHDOTDOT

/*
 * IDs for StatStrip handling of popup menus:  must be in order of menu
 * CLASSRES.H already defines ID_MENUFILE and ID_MENUEDIT for us.
 */

#undef  ID_MENUWINDOW
#undef  ID_MENUHELP

#define ID_MENUCOLOR            (ID_MENUCUSTOMMIN+0)
#define ID_MENULINE             (ID_MENUCUSTOMMIN+1)
#define ID_MENUWINDOW           (ID_MENUCUSTOMMIN+2)
#define ID_MENUHELP             (ID_MENUCUSTOMMIN+3)

//Stringtable IDs.  Keep sequential for each group.
#define IDS_FRAMEMIN            IDS_STANDARDFRAMEMIN
#define IDS_FILEIMPORT          (IDS_CUSTOMFRAMEMIN+0)
#define IDS_FRAMEMAX            (IDS_CUSTOMFRAMEMIN+0)

#define IDS_DOCUMENTMIN         IDS_STANDARDDOCMIN
#define IDS_NOPOLYLINE          (IDS_CUSTOMDOCMIN+0)
#define IDS_DOCUMENTMAX         (IDS_CUSTOMDOCMIN+0)

//These are specifically for the StatStrip
#define IDS_STATMESSAGEMIN                  IDS_STANDARDSTATMESSAGEMIN

#define IDS_ITEMMESSAGEFILEIMPORT           (IDS_CUSTOMSTATMESSAGEMIN+0)

#define IDS_MENUMESSAGECOLOR                (IDS_CUSTOMSTATMESSAGEMIN+1)
#define IDS_ITEMMESSAGECOLORBACKGROUND      (IDS_CUSTOMSTATMESSAGEMIN+2)
#define IDS_ITEMMESSAGECOLORLINE            (IDS_CUSTOMSTATMESSAGEMIN+3)

#define IDS_MENUMESSAGELINE                 (IDS_CUSTOMSTATMESSAGEMIN+4)
#define IDS_ITEMMESSAGELINESOLID            (IDS_CUSTOMSTATMESSAGEMIN+5)
#define IDS_ITEMMESSAGELINEDASH             (IDS_CUSTOMSTATMESSAGEMIN+6)
#define IDS_ITEMMESSAGELINEDOT              (IDS_CUSTOMSTATMESSAGEMIN+7)
#define IDS_ITEMMESSAGELINEDASHDOT          (IDS_CUSTOMSTATMESSAGEMIN+8)
#define IDS_ITEMMESSAGELINEDASHDOTDOT       (IDS_CUSTOMSTATMESSAGEMIN+9)

#define IDS_STATMESSAGEMAX                  (IDS_CUSTOMSTATMESSAGEMIN+9)

#endif //_RESOURCE_H_


CLIENT.CPP

/*
 * CLIENT.CPP
 * Cosmo Chapter 12
 *
 * Implementation of the CCosmoClient class that just makes sure
 * we get a CCosmoDoc on doc creation and that we initialize fully.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CCosmoClient::CCosmoClient
 * CCosmoClient::~CCosmoClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame to the frame object.
 */

CCosmoClient::CCosmoClient(HINSTANCE hInst, PCFrame pFR)
    : CClient(hInst, pFR)
    {
    return;
    }

CCosmoClient::~CCosmoClient(void)
    {
    return;
    }

/*
 * CCosmoClient::CreateCDocument
 *
 * Purpose:
 *  Constructs a new document specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::CreateCDocument(void)
    {
    return (PCDocument)(new CCosmoDoc(m_hInst, m_pFR, m_pAdv));
    }

/*
 * CCosmoClient::NewDocument
 *
 * Purpose:
 *  Small override of the CClient::NewDocument that we have just
 *  to check the initial line selection on new document creation.
 *
 * Parameters:
 *  fVisible        BOOL indicating if the document is to be
 *                  visible or not.
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::NewDocument(BOOL fVisible)
    {
    PCDocument  pDoc;

    //Perform default NewDocument first
    pDoc=CClient::NewDocument(fVisible);

    //We know that m_pFR is actually a CCosmoFrame, so type is safe.
    ((PCCosmoFrame)m_pFR)->CheckLineSelection(IDM_LINESOLID);

    return pDoc;
    }


COSMO.CPP

/*
 * COSMO.CPP
 * Cosmo Chapter 12
 *
 * WinMain and CCosmoFrame implementations.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "cosmo.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCCosmoFrame    pFR;
    FRAMEINIT       fi;
    WPARAM          wRet;

    //Attempt to allocate and initialize the application
    pFR=new CCosmoFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return -1;

    fi.idsMin=IDS_FRAMEMIN;
    fi.idsMax=IDS_FRAMEMAX;
    fi.idsStatMin=IDS_STATMESSAGEMIN;
    fi.idsStatMax=IDS_STATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=440;
    fi.cy=460;

    //If we can initialize pFR, start chugging messages
    if (pFR->Init(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

/*
 * CCosmoFrame::CCosmoFrame
 * CCosmoFrame::~CCosmoFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WInMain
 */

CCosmoFrame::CCosmoFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    : CFrame(hInst, hInstPrev, pszCmdLine, nCmdShow)
    {
    UINT        i;

    for (i=0; i<5; i++)
        m_hBmpLines[i]=NULL;

    m_uIDCurLine=0;
    m_fInitialized=FALSE;

    //CHAPTER12MOD
    m_pIClassDataTran=NULL;
    //End CHAPTER12MOD
    return;
    }

CCosmoFrame::~CCosmoFrame(void)
    {
    UINT        i;

    for (i=0; i<5; i++)
        {
        if (NULL!=m_hBmpLines[i])
            DeleteObject(m_hBmpLines[i]);
        }

    //CHAPTER12MOD
    if (NULL!=m_pIClassDataTran)
        {
        m_pIClassDataTran->LockServer(FALSE);
        m_pIClassDataTran->Release();
        }

    OleFlushClipboard();

    if (m_fInitialized)
        OleUninitialize();
    //End CHAPTER12MOD

    return;
    }

/*
 * CCosmoFrame::Init
 *
 * Purpose:
 *  Call CoInitialize then calling down into the base class
 *  initialization.
 *
 * Parameters:
 *  pFI             PFRAMEINIT containing initialization parameters.
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded, FALSE otherwise.
 */

BOOL CCosmoFrame::Init(PFRAMEINIT pFI)
    {
    //CHAPER12MOD
    HRESULT     hr;
    //End CHAPTER12MOD

    CHECKVER_OLE;

    //CHAPTER12MOD
    if (FAILED(OleInitialize(NULL)))
        return FALSE;
    //End CHAPTER12MOD

    m_fInitialized=TRUE;

    //CHAPTER12MOD
    /*
     * Obtain the Data Transfer Object class factory and lock it.
     * This will improve the speed of clipboard and (later)
     * drag & drop operations that involve this class.
     */
    hr=CoGetClassObject(CLSID_DataTransferObject
        , CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory
        , (PPVOID)&m_pIClassDataTran);

    if (SUCCEEDED(hr))
        m_pIClassDataTran->LockServer(TRUE);
    //End CHAPTER12MOD

    return CFrame::Init(pFI);
    }

/*
 * CCosmoFrame::CreateCClient
 *
 * Purpose:
 *  Constructs a new client specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient        Pointer to the new client object.
 */

PCClient CCosmoFrame::CreateCClient(void)
    {
    return (PCClient)(new CCosmoClient(m_hInst, this));
    }

/*
 * CCosmoFrame::RegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CCosmoFrame::RegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //First let the standard frame do its thing
    if (!CFrame::RegisterAllClasses())
        return FALSE;

    /*
     * We want a different background color for the document
     * because the Polyline we put in the document will paint
     * with COLOR_WINDOW which by default which is CLASSLIB's
     * default document color.
     */

    GetClassInfo(m_hInst, SZCLASSDOCUMENT, &wc);
    UnregisterClass(SZCLASSDOCUMENT, m_hInst);

    wc.hbrBackground=(HBRUSH)(COLOR_APPWORKSPACE+1);

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

    //Register the Polyline window.
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.hInstance     = m_hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PolylineWndProc;
    wc.cbWndExtra    = CBPOLYLINEWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_CROSS);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPOLYLINE;

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

    return TRUE;
    }

/*
 * CCosmoFrame::PreShowInit
 *
 * Purpose:
 *  Called from Init before intially showing the window.  We do
 *  whatever else we want here, modifying nCmdShow as necessary
 *  which affects ShowWindow in Init.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if this initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CCosmoFrame::PreShowInit(void)
    {
    CreateLineMenu();
    CheckLineSelection(IDM_LINESOLID);
    return TRUE;
    }

/*
 * CCosmoFrame::CreateLineMenu
 *
 * Purpose:
 *  Initializes the bitmaps used to create the Line menu and
 *  replaces the text items defined in the application resources
 *  with these bitmaps.  Note that the contents of m_hBmpLines
 *  must be cleaned up when the application terminates.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CreateLineMenu(void)
    {
    HMENU       hMenu;
    HDC         hDC, hMemDC;
    HPEN        hPen;
    HGDIOBJ     hObj;
    TEXTMETRIC  tm;
    UINT        i, cx, cy;

    hMenu=GetSubMenu(GetMenu(m_hWnd), 3);   //Line menu.
    hDC=GetDC(m_hWnd);

    //Create each line in a menu item 8 chars wide, one char high.
    GetTextMetrics(hDC, &tm);
    cx=tm.tmAveCharWidth*8;
    cy=tm.tmHeight;

    /*
     * Create a memory DC in which to draw lines, and bitmaps
     * for each line.
     */
    hMemDC=CreateCompatibleDC(hDC);
    ReleaseDC(m_hWnd, hDC);

    for (i=0; i<5; i++)
        {
        m_hBmpLines[i]=CreateCompatibleBitmap(hMemDC, cx, cy);
        SelectObject(hMemDC, m_hBmpLines[i]);

        PatBlt(hMemDC, 0, 0, cx, cy, WHITENESS);

        hPen=CreatePen(i, 1, 0L);       //i=line style like PS_SOLID
        hObj=SelectObject(hMemDC, hPen);

        MoveToEx(hMemDC, 0, cy/2, NULL);
        LineTo(hMemDC, cx, cy/2);

        ModifyMenu(hMenu, IDM_LINEMIN+i, MF_BYCOMMAND | MF_BITMAP
            , IDM_LINEMIN+i, (LPTSTR)(LONG)(UINT)m_hBmpLines[i]);

        SelectObject(hMemDC, hObj);
        DeleteObject(hPen);
        }

    CheckMenuItem(hMenu, IDM_LINESOLID, MF_CHECKED);
    DeleteDC(hMemDC);

    return;
    }

/*
 * CCosmoFrame::CreateToolbar
 *
 * Purpose:
 *  Procedure to create all the necessary toolbar buttons.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of tools added to the bar.
 */

UINT CCosmoFrame::CreateToolbar(void)
    {
    UINT            iLast;
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;
    UINT            utEx  =GIZMOTYPE_BUTTONATTRIBUTEEX;

    //Insert the standard ones.
    iLast=CFrame::CreateToolbar();

    /*
     * Insert File Import in the 5th position and account for
     * it in iLast.
     */
    m_pTB->Add(utCmd, 4, IDM_FILEIMPORT, m_dxB, m_dyB
        , NULL, m_hBmp, 2, uState);
    iLast++;

    //Separator
    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    /*
     * For the Background bitmap, preserve our use of black
     * (part of the image)
     */
    m_pTB->Add(utCmd, iLast++, IDM_COLORBACKGROUND, m_dxB, m_dyB
        , NULL, m_hBmp, 3, GIZMO_NORMAL | PRESERVE_BLACK);

    m_pTB->Add(utCmd, iLast++, IDM_COLORLINE, m_dxB, m_dyB
        , NULL, m_hBmp, 4, uState);

    //Separator
    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //Line styles.
    m_pTB->Add(utEx, iLast++, IDM_LINESOLID, m_dxB, m_dyB
        , NULL, m_hBmp, 5, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASH, m_dxB, m_dyB
        , NULL, m_hBmp, 6, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 7, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASHDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 8, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASHDOTDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 9, uState);

    return iLast;
    }

/*
 * CCosmoFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the Cosmo frame window that just
 *  processes the line menu and the color menu leaving the
 *  CFrame to do everything else.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CCosmoFrame::OnCommand(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PCCosmoDoc      pDoc;
    TCHAR           szFile[CCHPATHMAX];
    BOOL            fOK;
    UINT            i, uTemp;
    COLORREF        rgColors[16];
    CHOOSECOLOR     cc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many toolbar notifications.
     */
    if (m_fInit)
        return 0L;

    pDoc=(PCCosmoDoc)m_pCL->ActiveDocument();

    /*
     * Check for the line style commands which are
     * IDM_LINEMIN+<style>.  We handle this by changing the menu
     * and toolbar, then we pass it to the document for real
     * processing.
     */
    if (NULL!=pDoc && IDM_LINEMIN <= wID && IDM_LINEMAX >=wID)
        {
        CheckLineSelection(wID);
        pDoc->LineStyleSet(wID-IDM_LINEMIN);
        return 0L;
        }

    switch (wID)
        {
        case IDM_FILEIMPORT:
            szFile[0]=0;
            fOK=SaveOpenDialog(szFile, CCHPATHMAX, IDS_FILEIMPORT
                , TRUE, &i);

            if (fOK)
                {
                uTemp=pDoc->Load(FALSE, szFile);
                pDoc->ErrorMessage(uTemp);
                }

            return (LRESULT)fOK;

        case IDM_COLORBACKGROUND:
        case IDM_COLORLINE:
            //Invoke the color chooser for either color
            uTemp=(IDM_COLORBACKGROUND==wID)
                ? DOCCOLOR_BACKGROUND : DOCCOLOR_LINE;

            for (i=0; i<16; i++)
                rgColors[i]=RGB(0, 0, i*16);

            memset(&cc, 0, sizeof(CHOOSECOLOR));
            cc.lStructSize=sizeof(CHOOSECOLOR);
            cc.lpCustColors=rgColors;
            cc.hwndOwner=hWnd;
            cc.Flags=CC_RGBINIT;
            cc.rgbResult=pDoc->ColorGet(uTemp);

            if (ChooseColor(&cc))
                pDoc->ColorSet(uTemp, cc.rgbResult);

            break;

        default:
           CFrame::OnCommand(hWnd, wParam, lParam);
        }

    return 0L;
    }

/*
 * CCosmoFrame::OnDocumentDataChange
 *
 * Purpose:
 *  Update the Line menu and toolbar if the style in the data
 *  changes.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentDataChange(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::OnDocumentActivate
 *
 * Purpose:
 *  Informs us that document activation changed, so update the UI
 *  for that new document.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentActivate(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    PCDocument  pDoc;
    BOOL        fOK=FALSE;
    BOOL        fCallDefault=TRUE;
    UINT        i;
    UINT        uTemp;
    UINT        uTempE;
    UINT        uTempD;

    pDoc=m_pCL->ActiveDocument();

    uTempE=MF_ENABLED | MF_BYCOMMAND;
    uTempD=MF_DISABLED | MF_GRAYED | MF_BYCOMMAND;
    uTemp=((NULL!=pDoc) ? uTempE : uTempD);

    //File menu:  If there is document window, disable Import.
    if (m_phMenu[0]==hMenu)
        EnableMenuItem(hMenu, IDM_FILEIMPORT, uTemp);

    //Color menu:  no document, no commands
    if (m_phMenu[2]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_COLORBACKGROUND, uTemp);
        EnableMenuItem(hMenu, IDM_COLORLINE,       uTemp);
        fCallDefault=FALSE;
        }

    //Line menu:  no document, no commands
    if (m_phMenu[3]==hMenu)
        {
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            EnableMenuItem(hMenu, i, uTemp);

        fCallDefault=FALSE;
        }

    if (fCallDefault)
        CFrame::UpdateMenus(hMenu, iMenu);

    return;
    }

/*
 * CCosmoFrame::UpdateToolbar
 *
 * Purpose:
 *  Enables and disables tools depending on whether we have
 *  a document or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateToolbar(void)
    {
    BOOL        fLast;
    UINT        i;

    //Save the last enabled state before CFrame changes it
    fLast=m_fLastEnable;

    //Let the default hack on its tools
    CFrame::UpdateToolbar();

    /*
     * If CFrame::UpdateToolbar changed anything, then we need
     * to change as well--if nothing changes, nothing to do.
     */
    if (fLast!=m_fLastEnable)
        {
        m_pTB->Enable(IDM_FILEIMPORT, m_fLastEnable);

        m_pTB->Enable(IDM_COLORBACKGROUND, m_fLastEnable);
        m_pTB->Enable(IDM_COLORLINE,       m_fLastEnable);

        for (i=IDM_LINEMIN; i <= IDM_LINEMAX; i++)
            m_pTB->Enable(i, m_fLastEnable);
        }

    return;
    }

/*
 * CCosmoFrame::CheckLineSelection
 *
 * Purpose:
 *  Maintains the bitmap menu and the tools for the line selection.
 *  Both are mutially exclusive option lists where a selection in
 *  one has to affect the other.
 *
 * Parameters:
 *  uID             UINT ID of the item to be selected
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CheckLineSelection(UINT uID)
    {
    UINT        i;
    HMENU       hMenu;

    //Update menus and tools if the selection changed.
    if (uID!=m_uIDCurLine)
        {
        m_uIDCurLine=uID;
        hMenu=GetMenu(m_hWnd);

        //Uncheck all lines initially.
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            CheckMenuItem(hMenu, i, MF_UNCHECKED | MF_BYCOMMAND);

        CheckMenuItem(hMenu, uID, MF_CHECKED | MF_BYCOMMAND);
        m_pTB->Check(uID, TRUE);
        }

    return;
    }


COSMO.H

/*
 * COSMO.H
 * Cosmo Chapter 12
 *
 * Single include file that pulls in everything needed for other
 * source files in the Cosmo application.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _COSMO_H_
#define _COSMO_H_

#define INC_CLASSLIB
//CHAPTER12MOD
#define CHAPTER12
//End CHAPTER12MOD
#include <inole.h>
#include "resource.h"

//Get the editor window information.
#include "polyline.h"

//COSMO.CPP:  Frame object that creates a main window

class CCosmoFrame : public CFrame
    {
    private:
        HBITMAP         m_hBmpLines[5];     //Menu item bitmaps
        UINT            m_uIDCurLine;       //Current line selection
        BOOL            m_fInitialized;     //Did OleInitalize work?

        //CHAPTER12MOD
        //For locking DATATRAN.DLL class factory
        LPCLASSFACTORY  m_pIClassDataTran;
        //End CHAPTER12MOD

    protected:
        //Overridable for creating a CClient for this frame
        virtual PCClient  CreateCClient(void);

        virtual BOOL      RegisterAllClasses(void);
        virtual BOOL      PreShowInit(void);
        virtual UINT      CreateToolbar(void);

        virtual LRESULT   OnCommand(HWND, WPARAM, LPARAM);
        virtual void      OnDocumentDataChange(PCDocument);
        virtual void      OnDocumentActivate(PCDocument);

        //New for this class
        virtual void      CreateLineMenu(void);

    public:
        CCosmoFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CCosmoFrame(void);

        //Overrides
        virtual BOOL      Init(PFRAMEINIT);
        virtual void      UpdateMenus(HMENU, UINT);
        virtual void      UpdateToolbar(void);

        //New for this class
        virtual void      CheckLineSelection(UINT);
    };

typedef CCosmoFrame *PCCosmoFrame;

//CLIENT.CPP

/*
 * The only reason we have a derived class here is to override
 * CreateCDocument so we can create our own type as well as
 * overriding NewDocument to perform one other piece of work once
 * the document's been created.
 */

class CCosmoClient : public CClient
    {
    protected:
        //Overridable for creating a new CDocument
        virtual PCDocument CreateCDocument(void);

    public:
        CCosmoClient(HINSTANCE, PCFrame);
        virtual ~CCosmoClient(void);

        virtual PCDocument NewDocument(BOOL);
    };

typedef CCosmoClient *PCCosmoClient;

//DOCUMENT.CPP

//Constant ID for the window polyline that lives in a document
#define ID_POLYLINE         10

class CCosmoDoc : public CDocument
    {
    friend class CPolylineAdviseSink;

    protected:
        UINT                    m_uPrevSize;    //Last WM_SIZE wParam
        LONG                    m_lVer;         //Loaded Polyline ver

        PCPolyline              m_pPL;          //Polyline window here
        PCPolylineAdviseSink    m_pPLAdv;       //Advises from Polyline

    protected:
        virtual BOOL     FMessageHook(HWND, UINT, WPARAM, LPARAM
            , LRESULT *);

        //CHAPTER12MOD
        virtual BOOL     FQueryPasteFromData(LPDATAOBJECT);
        virtual BOOL     PasteFromData(LPDATAOBJECT);
        //End CHAPTER12MOD

    public:
        CCosmoDoc(HINSTANCE, PCFrame, PCDocumentAdviseSink);
        virtual ~CCosmoDoc(void);

        virtual BOOL     Init(PDOCUMENTINIT);

        virtual void     Clear(void);

        virtual UINT     Load(BOOL, LPTSTR);
        virtual UINT     Save(UINT, LPTSTR);

        virtual void     Undo(void);
        virtual BOOL     Clip(HWND, BOOL);
        virtual HGLOBAL  RenderFormat(UINT);
        virtual BOOL     FQueryPaste(void);
        virtual BOOL     Paste(HWND);

        virtual COLORREF ColorSet(UINT, COLORREF);
        virtual COLORREF ColorGet(UINT);

        virtual UINT     LineStyleSet(UINT);
        virtual UINT     LineStyleGet(void);
    };

typedef CCosmoDoc *PCCosmoDoc;

//These color indices wrap the polyline definitions
#define DOCCOLOR_BACKGROUND             POLYLINECOLOR_BACKGROUND
#define DOCCOLOR_LINE                   POLYLINECOLOR_LINE

#endif //_COSMO_H_


DOCUMENT.CPP

/*
 * DOCUMENT.CPP
 * Cosmo Chapter 12
 *
 * Implementation of the CCosmoDoc derivation of CDocument as
 * well as an implementation of CPolylineAdviseSink.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CCosmoDoc::CCosmoDoc
 * CCosmoDoc::~CCosmoDoc
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame of the frame object.
 *  pAdv            PCDocumentAdviseSink to notify on events
 */

CCosmoDoc::CCosmoDoc(HINSTANCE hInst, PCFrame pFR
    , PCDocumentAdviseSink pAdv)
    : CDocument(hInst, pFR, pAdv)
    {
    m_pPL=NULL;
    m_pPLAdv=NULL;
    m_uPrevSize=SIZE_RESTORED;
    return;
    }

CCosmoDoc::~CCosmoDoc(void)
    {
    //Clean up the allocations we did in Init
    if (NULL!=m_pPL)
        delete m_pPL;

    if (NULL!=m_pPLAdv)
        delete m_pPLAdv;

    return;
    }

/*
 * CCosmoDoc::Init
 *
 * Purpose:
 *  Initializes an already created document window.  The client
 *  actually creates the window for us, then passes that here for
 *  further initialization.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CCosmoDoc::Init(PDOCUMENTINIT pDI)
    {
    RECT        rc;

    //Change the stringtable range to our customization.
    pDI->idsMin=IDS_DOCUMENTMIN;
    pDI->idsMax=IDS_DOCUMENTMAX;

    //Do default initialization
    if (!CDocument::Init(pDI))
        return FALSE;

    //Add the Polyline stuff we need.
    m_pPLAdv=new CPolylineAdviseSink(this);
    m_pPL   =new CPolyline(m_hInst);

    //Attempt to create our contained Polyline.
    GetClientRect(m_hWnd, &rc);
    InflateRect(&rc, -8, -8);

    if (!m_pPL->Init(m_hWnd, &rc, WS_CHILD | WS_VISIBLE
        , ID_POLYLINE, m_pPLAdv))
        return FALSE;

    return TRUE;
    }

/*
 * CCosmoDoc::FMessageHook
 *
 * Purpose:
 *  Processes WM_SIZE for the document so we can resize
 *  the Polyline.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CCosmoDoc::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    UINT        dx, dy;
    RECT        rc;

    *pLRes=0;

    if (WM_SIZE==iMsg)
        {
        //Don't effect the Polyline size to or from minimized state.
        if (SIZE_MINIMIZED!=wParam && SIZE_MINIMIZED !=m_uPrevSize)
            {
            //When we change size, resize any Polyline we hold.
            dx=LOWORD(lParam);
            dy=HIWORD(lParam);

            /*
             * If we are getting WM_SIZE in response to a Polyline
             * notification, then don't resize the Polyline window
             * again.
             */
            if (!m_fNoSize && NULL!=m_pPL)
                {
                //Resize the polyline to fit the new client
                SetRect(&rc, 8, 8, dx-8, dy-8);
                m_pPL->RectSet(&rc, FALSE);

                /*
                 * We consider sizing something that makes the file
                 * dirty, but not until we've finished the create
                 * process, which is why we set fNoDirty to FALSE
                 * in WM_CREATE since we get a WM_SIZE on the first
                 * creation.
                 */
                if (!m_fNoDirty)
                    FDirtySet(TRUE);

                SetRect(&rc, 0, 0, dx, dy);

                if (NULL!=m_pAdv)
                    m_pAdv->OnSizeChange(this, &rc);

                m_fNoDirty=FALSE;
                }
            }

        m_uPrevSize=wParam;
        }

    /*
     * We return FALSE even on WM_SIZE so we can let the default
     * procedure handle maximized MDI child windows appropriately.
     */
    return FALSE;
    }

/*
 * CCosmoDoc::Clear
 *
 * Purpose:
 *  Sets all contents in the document back to defaults with
 *  no filename.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Clear(void)
    {
    //Completely reset the polyline
    m_pPL->New();

    CDocument::Clear();
    m_lVer=0;
    return;
    }

/*
 * CCosmoDoc::Load
 *
 * Purpose:
 *  Loads a given document without any user interface overwriting
 *  the previous contents of the Polyline window.  We do this by
 *  opening the file and telling the Polyline to load itself from
 *  that file.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load, NULL if the file
 *                  is new and untitled.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CCosmoDoc::Load(BOOL fChangeFile, LPTSTR pszFile)
    {
    HRESULT         hr;
    LPSTORAGE       pIStorage;

    if (NULL==pszFile)
        {
        //For a new untitled document, just rename ourselves.
        Rename(NULL);
        m_lVer=VERSIONCURRENT;
        return DOCERR_NONE;
        }

    /*
     * If not a Compound File, open the file using STGM_CONVERT in
     * transacted mode to see old files as a storage with one stream
     * called "CONTENTS" (which is conveniently the name we use
     * in the new files).  We must use STGM_TRANSACTED here or else
     * the old file will be immediately converted on disk:  we only
     * want a converted image in memory from which to read.  In
     * addition, note that we need STGM_READWRITE as well since
     * conversion is inherently a write operation.
     */

    pIStorage=NULL;

    if (NOERROR!=StgIsStorageFile(pszFile))
        {
        hr=StgCreateDocfile(pszFile,STGM_TRANSACTED | STGM_READWRITE
            | STGM_CONVERT | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

        if (FAILED(hr))
            {
            //If denied write access, try to load the old way
            if (STG_E_ACCESSDENIED==GetScode(hr))
                m_lVer=m_pPL->ReadFromFile(pszFile);
            else
                return DOCERR_COULDNOTOPEN;
            }
        }
    else
        {
        hr=StgOpenStorage(pszFile, NULL, STGM_DIRECT | STGM_READ
            | STGM_SHARE_EXCLUSIVE, NULL, 0, &pIStorage);

        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;
        }

    if (NULL!=pIStorage)
        {
        m_lVer=m_pPL->ReadFromStorage(pIStorage);
        pIStorage->Release();
        }

    if (POLYLINE_E_READFAILURE==m_lVer)
        return DOCERR_READFAILURE;

    if (POLYLINE_E_UNSUPPORTEDVERSION==m_lVer)
        return DOCERR_UNSUPPORTEDVERSION;

    if (fChangeFile)
        Rename(pszFile);

    //Importing a file makes things dirty
    FDirtySet(!fChangeFile);

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::Save
 *
 * Purpose:
 *  Writes the file to a known filename, requiring that the user has
 *  previously used FileOpen or FileSaveAs to provide a filename.
 *
 * Parameters:
 *  uType           UINT indicating the type of file the user
 *                  requested to save in the File Save As dialog.
 *  pszFile         LPTSTR under which to save.  If NULL, use the
 *                  current name.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CCosmoDoc::Save(UINT uType, LPTSTR pszFile)
    {
    LONG        lVer, lRet;
    UINT        uTemp;
    BOOL        fRename=TRUE;
    HRESULT     hr;
    LPSTORAGE   pIStorage;

    if (NULL==pszFile)
        {
        fRename=FALSE;
        pszFile=m_szFile;
        }

    /*
     * Type 1 is the current version, type 2 is version 1.0 of the
     * Polyline so we use this to send the right version to
     * CPolyline::WriteToFile/WriteToStorage.
     */

    switch (uType)
        {
        case 0:         //From Save, use loaded version.
            lVer=m_lVer;
            break;

        case 1:
            lVer=VERSIONCURRENT;
            break;

        case 2:
            lVer=MAKELONG(0, 1);    //1.0
            break;

        default:
            return DOCERR_UNSUPPORTEDVERSION;
        }

    /*
     * If the version the user wants to save is different from the
     * version that we loaded and m_lVer is not zero (new doc),
     * then inform the user of the version change and verify.
     */
    if (0!=m_lVer && m_lVer!=lVer)
        {
        TCHAR       szMsg[128];

        wsprintf(szMsg, PSZ(IDS_VERSIONCHANGE)
            , (UINT)HIWORD(m_lVer), (UINT)LOWORD(m_lVer)
            , (UINT)HIWORD(lVer), (UINT)LOWORD(lVer));

        uTemp=MessageBox(m_hWnd, szMsg, PSZ(IDS_DOCUMENTCAPTION)
            , MB_YESNOCANCEL);

        if (IDCANCEL==uTemp)
            return DOCERR_CANCELLED;

        //If the user won't upgrade, revert to loaded version.
        if (IDNO==uTemp)
            lVer=m_lVer;
        }

    /*
     * For 1.0 files, still use the old code.  For new files, use
     * storages instead
     */
    if (lVer==MAKELONG(0, 1))
        lRet=m_pPL->WriteToFile(pszFile, lVer);
    else
        {
        hr=StgCreateDocfile(pszFile, STGM_DIRECT | STGM_READWRITE
            | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;

        //Mark this as one of our class
        WriteClassStg(pIStorage, CLSID_CosmoFigure);

        //Write user-readable class information
        WriteFmtUserTypeStg(pIStorage, m_cf
            , PSZ(IDS_CLIPBOARDFORMAT));

        lRet=m_pPL->WriteToStorage(pIStorage, lVer);
        pIStorage->Release();
        }

    if (POLYLINE_E_NONE!=lRet)
        return DOCERR_WRITEFAILURE;

    //Saving makes us clean
    FDirtySet(FALSE);

    //Update the known version of this document.
    m_lVer=lVer;

    if (fRename)
        Rename(pszFile);

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::Undo
 *
 * Purpose:
 *  Reverses a previous action.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Undo(void)
    {
    m_pPL->Undo();
    return;
    }

/*
 * CCosmoDoc::Clip
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard, optionally implementing Cut by deleting the
 *  data in the current window after rendering.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *  fCut            BOOL indicating cut (TRUE) or copy (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::Clip(HWND hWndFrame, BOOL fCut)
    {
    BOOL            fRet=TRUE;
    HGLOBAL         hMem;
    UINT            i;
    static UINT     rgcf[3]={0, CF_METAFILEPICT, CF_BITMAP};
    const UINT      cFormats=3;

    //CHAPTER12MOD
    static DWORD    rgtm[3]={TYMED_HGLOBAL, TYMED_MFPICT, TYMED_GDI};
    LPDATAOBJECT    pIDataObject;
    HRESULT         hr;
    STGMEDIUM       stm;
    FORMATETC       fe;

    hr=CoCreateInstance(CLSID_DataTransferObject
        , NULL, CLSCTX_INPROC_SERVER
        , IID_IDataObject, (PPVOID)&pIDataObject);

    if (FAILED(hr))
        return NULL;
    //End CHAPTER12MOD

    rgcf[0]=m_cf;

    for (i=0; i < cFormats; i++)
        {
        //Copy private data first.
        hMem=RenderFormat(rgcf[i]);

        if (NULL!=hMem)
            {
            //CHAPTER12MOD
            stm.hGlobal=hMem;
            stm.tymed=rgtm[i];
            stm.pUnkForRelease=NULL;

            SETDefFormatEtc(fe, rgcf[i], rgtm[i]);
            pIDataObject->SetData(&fe, &stm, TRUE);
            //End CHAPTER12MOD
            }
        }

    //CHAPTER12MOD
    fRet=SUCCEEDED(OleSetClipboard(pIDataObject));
    pIDataObject->Release();
    //End CHAPTER12MOD

    //Delete our current data if "cut" succeeded.
    if (fRet && fCut)
        {
        m_pPL->New();
        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CCosmoDoc::RenderFormat
 *
 * Purpose:
 *  Renders a specific clipboard format into global memory.
 *
 * Parameters:
 *  cf              UINT format to render.
 *
 * Return Value:
 *  HGLOBAL         Global memory handle containing the data.
 */

HGLOBAL CCosmoDoc::RenderFormat(UINT cf)
    {
    HGLOBAL     hMem;

    if (cf==m_cf)
        {
        m_pPL->DataGetMem(VERSIONCURRENT, &hMem);
        return hMem;
        }

    switch (cf)
        {
        case CF_METAFILEPICT:
            return m_pPL->RenderMetafilePict();

        case CF_BITMAP:
            return (HGLOBAL)m_pPL->RenderBitmap();
        }

    return NULL;
    }

/*
 * CCosmoDoc::FQueryPaste
 *
 * Purpose:
 *  Determines if we can paste data from the clipboard.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CCosmoDoc::FQueryPaste(void)
    {
    //CHAPTER12MOD
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    fRet=FQueryPasteFromData(pIDataObject);
    pIDataObject->Release();
    return fRet;
    //End CHAPTER12MOD
    }

//CHAPTER12MOD
/*
 * CCosmoDoc::FQueryPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Determines if we can paste data from a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which we might want to paste.
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CCosmoDoc::FQueryPasteFromData(LPDATAOBJECT pIDataObject)
    {
    FORMATETC       fe;

    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    return (NOERROR==pIDataObject->QueryGetData(&fe));
    }
//End CHAPTER12MOD

/*
 * CCosmoDoc::Paste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  to the current figure in the editor window.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::Paste(HWND hWndFrame)
    {
    //CHAPTER12MOD
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    fRet=PasteFromData(pIDataObject);
    pIDataObject->Release();

    //End CHAPTER12MOD
    return fRet;
    }

//CHAPTER12MOD
/*
 * CCosmoDoc::PasteFromData
 * (Protected)
 *
 * Purpose:
 *  Retrieves the private data format from a data object and sets
 *  it to the current figure in the editor window.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::PasteFromData(LPDATAOBJECT pIDataObject)
    {
    FORMATETC       fe;
    STGMEDIUM       stm;
    BOOL            fRet;

    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    fRet=SUCCEEDED(pIDataObject->GetData(&fe, &stm));

    if (fRet && NULL!=stm.hGlobal)
        {
        m_pPL->DataSetMem(stm.hGlobal, FALSE, FALSE, TRUE);
        ReleaseStgMedium(&stm);
        FDirtySet(TRUE);
        }

    return fRet;
    }
//End CHAPTER12MOD

/*
 * CCosmoDoc::ColorSet
 *
 * Purpose:
 *  Changes a color used in our contained Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color.
 *
 * Return Value:
 *  COLORREF        Previous color for the given index.
 */

COLORREF CCosmoDoc::ColorSet(UINT iColor, COLORREF cr)
    {
    return m_pPL->ColorSet(iColor, cr);
    }

/*
 * CCosmoDoc::ColorGet
 *
 * Purpose:
 *  Retrieves a color currently in use in the Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to retrieve.
 *
 * Return Value:
 *  COLORREF        Current color for the given index.
 */

COLORREF CCosmoDoc::ColorGet(UINT iColor)
    {
    return m_pPL->ColorGet(iColor);
    }

/*
 * CCosmoDoc::LineStyleSet
 *
 * Purpose:
 *  Changes the line style currently used in the Polyline
 *
 * Parameters:
 *  iStyle          UINT index of the new line style to use.
 *
 * Return Value:
 *  UINT            Previous line style.
 */

UINT CCosmoDoc::LineStyleSet(UINT iStyle)
    {
    return m_pPL->LineStyleSet(iStyle);
    }

/*
 * CCosmoDoc::LineStyleGet
 *
 * Purpose:
 *  Retrieves the line style currently used in the Polyline
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Current line style.
 */

UINT CCosmoDoc::LineStyleGet(void)
    {
    if (NULL==m_pPL)    //m_pPL might not be valid yet
        return 0L;

    return m_pPL->LineStyleGet();
    }

/*
 * CPolylineAdviseSink::CPolylineAdviseSink
 * CPolylineAdviseSink::~CPolylineAdviseSink
 *
 * Constructor Parameters:
 *  pv              LPVOID to store in this object
 */

CPolylineAdviseSink::CPolylineAdviseSink(LPVOID pv)
    {
    m_pv=pv;
    return;
    }

CPolylineAdviseSink::~CPolylineAdviseSink(void)
    {
    return;
    }

/*
 * CPolylineAdviseSink::OnPointChange
 *
 * Purpose:
 *  Informs the document that the polyline added or removed a point.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnPointChange(void)
    {
    PCDocument      pDoc=(PCDocument)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnSizeChange
 *
 * Purpose:
 *  Informs the document that the polyline changed size.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnSizeChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;
    RECT            rc;
    DWORD           dwStyle;

    /*
     * Polyline window is informing us that it changed size in
     * response to setting it's data.  Therefore we have to
     * size ourselves accordingly but without moving the screen
     * position of the polyline window.
     */

    pDoc->m_fNoSize=TRUE;

    //Set the document window size.
    GetWindowRect(pDoc->m_pPL->Window(), &rc);
    InflateRect(&rc, 8, 8);

    //Adjust for a window sans menu
    dwStyle=GetWindowLong(pDoc->m_hWnd, GWL_STYLE);
    AdjustWindowRect(&rc, dwStyle, FALSE);

    SetWindowPos(pDoc->m_hWnd, NULL, 0, 0, rc.right-rc.left
        , rc.bottom-rc.top, SWP_NOMOVE | SWP_NOZORDER);

    if (NULL!=pDoc->m_pAdv)
        pDoc->m_pAdv->OnSizeChange(pDoc, &rc);

    pDoc->m_fNoSize=FALSE;
    pDoc->FDirtySet(TRUE);

    return;
    }

/*
 * CPolylineAdviseSink::OnDataChange
 *
 * Purpose:
 *  Informs the document that the polyline data changed.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnDataChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    if (NULL!=pDoc->m_pAdv)
        pDoc->m_pAdv->OnDataChange(pDoc);

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnColorChange
 *
 * Purpose:
 *  Informs the document that the polyline data changed a color.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnColorChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnLineStyleChange
 *
 * Purpose:
 *  Informs the document that the polyline changed its line style.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnLineStyleChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }


POLYLINE.CPP

/*
 * POLYLINE.CPP
 * Cosmo Chapter 12
 *
 * Implementation of the CPolyline class.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CPolyline:CPolyline
 * CPolyline::~CPolyline
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application we're in.
 */

CPolyline::CPolyline(HINSTANCE hInst)
    : CWindow(hInst)
    {
    m_pAdv=NULL;
    m_hWnd=NULL;
    return;
    }

CPolyline::~CPolyline(void)
    {
    return;
    }

/*
 * CPolyline::Init
 *
 * Purpose:
 *  Instantiates a polyline window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags
 *  uID             UINT ID to associate with this window
 *  pAdv            PCPolylineAdviseSink of the sink wanting our
 *                  notifications.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPolyline::Init(HWND hWndParent, LPRECT pRect, DWORD dwStyle
    , UINT uID, PCPolylineAdviseSink pAdv)
    {
    m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY, SZCLASSPOLYLINE
        , SZCLASSPOLYLINE, dwStyle, pRect->left, pRect->top
        , pRect->right-pRect->left, pRect->bottom-pRect->top
        , hWndParent, (HMENU)uID, m_hInst, this);

    if (NULL!=m_hWnd)
        m_pAdv=pAdv;

    return (NULL!=m_hWnd);
    }

/*
 * CPolyline::New
 *
 * Purpose:
 *  Cleans out and reinitializes the data to defaults.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolyline::New(void)
    {
    UINT        i;
    RECT        rc;

    m_pl.wVerMaj=VERSIONMAJOR;
    m_pl.wVerMin=VERSIONMINOR;

    //Our rectangle is the size of our window's client area.
    GetClientRect(m_hWnd, &rc);
    RECTTORECTS(rc, m_pl.rc);

    //Clean out the POLYLINE structure and repaint the window.
    for (i=0; i< CPOLYLINEPOINTS; i++)
        {
        m_pl.rgpt[i].x=0;
        m_pl.rgpt[i].y=0;
        }

    m_pl.cPoints      =0;
    m_pl.rgbBackground=GetSysColor(COLOR_WINDOW);
    m_pl.rgbLine      =GetSysColor(COLOR_WINDOWTEXT);
    m_pl.iLineStyle   =PS_SOLID;

    InvalidateRect(m_hWnd, NULL, TRUE);
    UpdateWindow(m_hWnd);

    //Inform the advise sink of this data change.
    if (NULL!=m_pAdv)
        m_pAdv->OnDataChange();

    return;
    }

/*
 * CPolyline::Undo
 *
 * Purpose:
 *  Reverses previous actions in a Polyline.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if we can Undo more, FALSE otherwise.
 */

BOOL CPolyline::Undo(void)
    {
    //Decrement the number of active points and repaint.
    if (m_pl.cPoints > 0)
        {
        m_pl.cPoints--;
        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    if (NULL!=m_pAdv)
        m_pAdv->OnPointChange();

    //Return if we can undo any more.
    return (0!=m_pl.cPoints);
    }

/*
 * CPolyline::ReadFromStorage
 *
 * Purpose:
 *  Loads our data (any known version) from an IStorage returning
 *  the version number of the data or an error value.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE from which we'll read.
 *
 * Return Value:
 *  LONG            Version number or negative POLYLINE_E_* value.
 */

LONG CPolyline::ReadFromStorage(LPSTORAGE pIStorage)
    {
    POLYLINEDATA    pl;
    ULONG           cb=(ULONG)-1;
    ULONG           cbExpect=0;
    LPSTREAM        pIStream;
    HRESULT         hr;
    LARGE_INTEGER   li;

    if (NULL==pIStorage)
        return POLYLINE_E_READFAILURE;

    //Open the CONTENTS stream
    hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT | STGM_READ
        | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return POLYLINE_E_READFAILURE;

    //Read version numbers and seek back to file beginning.
    hr=pIStream->Read(&pl, 2*sizeof(WORD), &cb);

    LISet32(li, 0);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);

    if (FAILED(hr) || 2*sizeof(WORD)!=cb)
        {
        pIStream->Release();
        return POLYLINE_E_READFAILURE;
        }

    /*
     * For version 2.0, read the entire file.  For version 1.0 read
     * the file up to CBPOLYLINEDATAVER10.  For anything else, give
     * an error.
     */

    switch (pl.wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (pl.wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (pl.wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        {
        pIStream->Release();
        return POLYLINE_E_UNSUPPORTEDVERSION;
        }

    hr=pIStream->Read(&pl, cbExpect, &cb);
    pIStream->Release();

    if (cbExpect!=cb)
        return POLYLINE_E_READFAILURE;

    /*
     * If we loaded successfully, make the data current.  By using
     * DataSet we centralize our version upgrading.  We size the
     * polyline window to the data AND notify the document so it
     * sizes to the polyline.
     */
    DataSet(&pl, TRUE, TRUE);

    //Return what version we just loaded.
    return MAKELONG(pl.wVerMin, pl.wVerMaj);
    }

/*
 * CPolyline::WriteToStorage
 *
 * Purpose:
 *  Ignorantly writes our current data into a storage in a
 *  particular version.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which to save.
 *  lVer            LONG providing version number Major (HI) and
 *                  Minor (Low)
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::WriteToStorage(LPSTORAGE pIStorage, LONG lVer)
    {
    ULONG           cb;
    ULONG           cbExpect=0;
    WORD            wVerMaj=HIWORD(lVer);
    WORD            wVerMin=LOWORD(lVer);
    POLYLINEDATA    pl;
    LPSTREAM        pIStream;
    HRESULT         hr;

    if (NULL==pIStorage)
        return POLYLINE_E_READFAILURE;

    //Get a copy of our data in the version we're going to save.
    DataGet(&pl, lVer);

    switch (wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT | STGM_CREATE
        | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pIStream);

    if (FAILED(hr))
        return POLYLINE_E_WRITEFAILURE;

    hr=pIStream->Write(&pl, cbExpect, &cb);
    pIStream->Release();

    if (FAILED(hr) || cbExpect!=cb)
        return POLYLINE_E_WRITEFAILURE;

    return POLYLINE_E_NONE;
    }

/*
 * CPolyline::ReadFromFile
 *
 * Purpose:
 *  Loads our data (any known version) from a file handle returning
 *  the version number of the data or an error value.
 *
 * Parameters:
 *  pszFile         LPTSTR of the file to open.
 *
 * Return Value:
 *  LONG            Version number or negative POLYLINE_E_* value.
 */

LONG CPolyline::ReadFromFile(LPTSTR pszFile)
    {
    OFSTRUCT        of;
    HFILE           hFile;
    POLYLINEDATA    pl;
    UINT            cb=(UINT)-1;
    UINT            cbExpect=0;

    if (NULL==pszFile)
        return POLYLINE_E_READFAILURE;

    //OpenFileW is a member of CPolyline.
    hFile=OpenFileW(pszFile, &of, OF_READ);

    if (HFILE_ERROR==hFile)
        return POLYLINE_E_READFAILURE;

    //Read version numbers and seek back to file beginning.
    cb=_lread(hFile, &pl, 2*sizeof(WORD));
    _llseek(hFile, 0L, 0);

    if (2*sizeof(WORD)!=cb)
        {
        _lclose(hFile);
        return POLYLINE_E_READFAILURE;
        }

    /*
     * For version 2.0, read the entire file.  For version 1.0 read
     * the file up to CBPOLYLINEDATAVER10.  For anything else, give
     * an error.
     */

    switch (pl.wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (pl.wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (pl.wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        {
        _lclose(hFile);
        return POLYLINE_E_UNSUPPORTEDVERSION;
        }

    cb=_lread(hFile, &pl, cbExpect);
    _lclose(hFile);

    if (cbExpect!=cb)
        return POLYLINE_E_READFAILURE;

    /*
     * If we loaded successfully, make the data current.  By using
     * DataSet we centralize our version upgrading.  We size the
     * polyline window to the data AND notify the document so it
     * sizes to the polyline.
     */
    DataSet(&pl, TRUE, TRUE);

    //Return what version we just loaded.
    return MAKELONG(pl.wVerMin, pl.wVerMaj);
    }

/*
 * CPolyline::WriteToFile
 *
 * Purpose:
 *  Ignorantly writes our current data into an opened file in a
 *  particular version.
 *
 * Parameters:
 *  pszFile         LPTSTR filename in which to store the data.
 *  lVer            LONG providing version number Major (HI)
 *                  and Minor (Low)
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::WriteToFile(LPTSTR pszFile, LONG lVer)
    {
    OFSTRUCT        of;
    HFILE           hFile;
    UINT            cb;
    UINT            cbExpect=0;
    WORD            wVerMaj=HIWORD(lVer);
    WORD            wVerMin=LOWORD(lVer);
    POLYLINEDATA    pl;

    if (NULL==pszFile)
        return POLYLINE_E_READFAILURE;

    //Get a copy of our data in the version we're going to save.
    DataGet(&pl, lVer);

    switch (wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    hFile=OpenFileW(pszFile, &of, OF_CREATE | OF_WRITE);

    if (HFILE_ERROR==hFile)
        return DOCERR_COULDNOTOPEN;

    cb=_lwrite(hFile, (LPCSTR)&pl, cbExpect);
    _lclose(hFile);

    return (cbExpect==cb) ? POLYLINE_E_NONE
        : POLYLINE_E_WRITEFAILURE;
    }

/*
 * CPolyline::OpenFileW (Private)
 *
 * Purpose:
 *  Under Win32, OpenFile does not take Unicode strings.  This
 *  function converts a Unicode string into an ANSI string and
 *  calls OpenFile.  This just maps to OpenFile without Unicode.
 *
 * Parameters, Return Value:
 *  Same as OpenFile.
 */

HFILE CPolyline::OpenFileW(LPTSTR pszFile, LPOFSTRUCT pof
    , UINT uFlags)
    {
   #ifdef UNICODE
    CHAR        szTemp[CCHPATHMAX];

    UNICODETOANSI(pszFile, szTemp, CCHPATHMAX);
    return OpenFile(szTemp, pof, uFlags);
   #else
    return OpenFile(pszFile, pof, uFlags);
   #endif
    }

/*
 * CPolyline::DataSet
 *
 * Purpose:
 *  Sets the current data in this Polyline to a given structure.
 *
 * Parameters:
 *  ppl             PPOLYLINEDATA to initialize to.
 *  fSizeToData     BOOL indicating if we're to size to the data or
 *                  scale it.
 *  fNotify         BOOL indicating if we're to send an advise on
 *                  this change.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataSet(PPOLYLINEDATA ppl, BOOL fSizeToData
    , BOOL fNotify)
    {
    RECTS       rcs;
    RECT        rc;
    UINT        i;

    /*
     * Copy the structure in ppl and repaint to reflect the new
     * point set.  Note that unlike the RectSet message, we do no
     * scaling, assuming that the rect in the structure is
     * appropriate for the data.
     */

    if (NULL==ppl)
        return POLYLINE_E_INVALIDPOINTER;

    //Preserve the old rectangle
    rcs=m_pl.rc;

    /*
     * For version 2.0 we perform a straight copy.  For version
     * 1.0 we copy the 1.0 structure and fill in defaults for
     * the 2.0 additions.
     */

    switch (ppl->wVerMaj)
        {
        case VERSIONMAJOR:          //2.x
            switch (ppl->wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    m_pl=*ppl;
                    break;

                default:
                    return POLYLINE_E_UNSUPPORTEDVERSION;
                }
            break;

        case 1:                     //1.x
            switch (ppl->wVerMin)
                {
                case 0:             //1.0
                    *((PPOLYLINEDATA10)&m_pl)=
                        *((PPOLYLINEDATA10)ppl);

                    /*
                     * Update this structure to 2.0.  Note that we
                     * assume whoever loaded us to save the loaded
                     * version so it can later ask what version
                     * the user wants to save.
                     */
                    m_pl.wVerMaj=VERSIONMAJOR;
                    m_pl.wVerMin=VERSIONMINOR;

                    /*
                     * Version 1.0 stored rc in parent coordinates.
                     * We need those now in our client coodinates.
                     */
                    RECTSTORECT(m_pl.rc, rc);
                    OffsetRect(&rc, -m_pl.rc.left, -m_pl.rc.top);

                    /*
                     * 1.0 data had points relative to size of the
                     * rectangle.  We need to scale these to 0-32767
                     * independent of the rectangle for the version
                     * upgrade.
                     */
                    for (i=0; i < m_pl.cPoints; i++)
                        PointScale(&rc, &m_pl.rgpt[i], FALSE);

                    RECTTORECTS(rc, m_pl.rc);

                    //New 2.0 features
                    m_pl.rgbBackground=GetSysColor(COLOR_WINDOW);
                    m_pl.rgbLine=GetSysColor(COLOR_WINDOWTEXT);
                    m_pl.iLineStyle=PS_SOLID;
                    break;

                default:
                    return POLYLINE_E_UNSUPPORTEDVERSION;
                }
            break;

        default:
            return POLYLINE_E_UNSUPPORTEDVERSION;
        }

    //Inform our parent of the data change
    if (NULL!=m_pAdv)
        m_pAdv->OnDataChange();

    /*
     * If we're scaling the window to fit the data, then use
     * RectSet passing our current rectangle as the new one.
     * That makes sure that the data won't change but that the
     * window is resized.
     */

    if (fSizeToData)
        {
        POINT       pt;

        /*
         * Get our offset in the parent window so we can RectSet
         * to the right place since RectSet expects rectangle in
         * parent coordinates and we get it in client coordinates.
         */
        GetWindowRect(m_hWnd, &rc);
        pt.x=rc.left;
        pt.y=rc.top;
        ScreenToClient(GetParent(m_hWnd), &pt);
        RECTSTORECT(m_pl.rc, rc);
        OffsetRect(&rc, pt.x, pt.y);

        //This will also cause a repaint.
        RectSet(&rc, fNotify);
        }
    else
        {
        //Make sure we're updated.
        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    return POLYLINE_E_NONE;
    }

/*
 * CPolyline::DataGet
 *
 * Purpose:
 *  Retrieves the Polyline's current data.
 *
 * Parameters:
 *  ppl             PPOLYLINEDATA into which we copy the data.
 *  lVer            LONG version of the data to retrieve.  Use
 *                  VERSIONCURRENT to retrieve the most current.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value
 */

LONG CPolyline::DataGet(PPOLYLINEDATA ppl, LONG lVer)
    {
    UINT        i;
    RECT        rc;

    //Retieve the current version
    if (lVer==MAKELONG(VERSIONMINOR, VERSIONMAJOR)
        || VERSIONCURRENT==lVer)
        {
        *ppl=m_pl;
        return POLYLINE_E_NONE;
        }

    //Check for versions we support, 1.x
    if (HIWORD(lVer)!=1)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    if (LOWORD(lVer)==0)    //Check for 1.0
        {
        //Do 2.0 to 1.0 conversion
        *((PPOLYLINEDATA10)ppl)=*((PPOLYLINEDATA10)&m_pl);

        RECTSTORECT(ppl->rc, rc);

        //Get the parent coordinates of our rectangle
        if (!IsWindow(m_hWnd))
            OffsetRect(&rc, 8, 8); //This is always the offset.
        else
            RectGet(&rc);

        /*
         * 1.0 data has points relative to size of the rectangle.
         * We need to scale these from 0-32767 so we have the
         * right values.
         */
        for (i=0; i < ppl->cPoints; i++)
            PointScale(&rc, &ppl->rgpt[i], TRUE);

        RECTTORECTS(rc, ppl->rc);

        //Insure old version numbers.
        ppl->wVerMaj=1;
        ppl->wVerMin=0;

        ((PPOLYLINEDATA10)ppl)->fDrawEntire=TRUE;
        return POLYLINE_E_NONE;
        }

    return POLYLINE_E_UNSUPPORTEDVERSION;
    }

/*
 * CPolyline::DataSetMem
 *
 * Purpose:
 *  Sets the Polyline's data using a global memory handle
 *  instead of a pointer.
 *
 * Parameters:
 *  hMem            HGLOBAL containing the data.
 *  fFree           BOOL indicating if we're to free the data.
 *                  The memory will be freed regardless of any
 *                  error returned from here.
 *  fSizeToData     BOOL indicating if we're to size to the data
 *                  or scale it.
 *  fNotify         BOOL indicating if we're to send an advise
 *                  on this change.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataSetMem(HGLOBAL hMem, BOOL fFree
    , BOOL fSizeToData, BOOL fNotify)
    {
    PPOLYLINEDATA   ppl;
    LONG            lRet=POLYLINE_E_INVALIDPOINTER;

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        lRet=DataSet(ppl, fSizeToData, fNotify);

        GlobalUnlock(hMem);

        if (fFree)
            GlobalFree(hMem);
        }

    return lRet;
    }

/*
 * CPolyline::DataGetMem
 *
 * Purpose:
 *  Retrieves the Polyline's data in a global memory handle.
 *
 * Parameters:
 *  lVer            LONG version of data to retrieve.
 *  phMem           HGLOBAL * in which to store the handle.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataGetMem(LONG lVer, HGLOBAL *phMem)
    {
    HGLOBAL         hMem;
    PPOLYLINEDATA   ppl;
    LONG            lRet;

    if (NULL==phMem)
        return POLYLINE_E_INVALIDPOINTER;

    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
        , CBPOLYLINEDATA);

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        lRet=DataGet(ppl, lVer);
        GlobalUnlock(hMem);

        if (POLYLINE_E_NONE!=lRet)
            {
            GlobalFree(hMem);
            hMem=NULL;
            }
        }

    *phMem=hMem;
    return lRet;
    }

/*
 * CPolyline::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectGet(LPRECT pRect)
    {
    RECT        rc;
    POINT       pt;

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_hWnd, &rc);
    pt.x=rc.left;
    pt.y=rc.top;
    ScreenToClient(GetParent(m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return;
    }

/*
 * CPolyline::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  None
 */

void CPolyline::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return;
    }

/*
 * CPolyline::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;
    RECT        rc;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, pRect->left, pRect->top
        , cx, cy, SWP_NOZORDER);

    SetRect(&rc, 0, 0, cx, cy);
    RECTTORECTS(rc, m_pl.rc);

    if (fNotify && NULL!=m_pAdv)
        m_pAdv->OnSizeChange();

    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }

/*
 * CPolyline::SizeSet
 *
 * Purpose:
 *  Sets a new size for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPolyline::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
        , SWP_NOMOVE | SWP_NOZORDER);

    if (fNotify && NULL!=m_pAdv)
        m_pAdv->OnSizeChange();

    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }

/*
 * CPolyline::ColorSet
 *
 * Purpose:
 *  Changes for background or line color in the Polyline
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color to use.
 *
 * Return Value:
 *  COLORREF        Previous color for the index iColor.
 */

COLORREF CPolyline::ColorSet(UINT iColor, COLORREF cr)
    {
    COLORREF    crRet;

    switch (iColor)
        {
        case POLYLINECOLOR_BACKGROUND:
            crRet=m_pl.rgbBackground;
            m_pl.rgbBackground=cr;
            break;

        case POLYLINECOLOR_LINE:
            crRet=m_pl.rgbLine;
            m_pl.rgbLine=cr;
            break;
        }

    //If the color changed, repaint
    if (crRet!=cr)
        {
        if (NULL!=m_pAdv)
            m_pAdv->OnColorChange();

        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    return crRet;
    }

/*
 * CPolyline::ColorGet
 *
 * Purpose:
 *  Retrieves one of the colors currently in use by the Polyline.
 *
 * Parameters:
 *  iColor          UINT identifying the color of interest.
 *
 * Return Value:
 *  COLORREF        Current color for iColor.
 */

COLORREF CPolyline::ColorGet(UINT iColor)
    {
    COLORREF    crRet;

    crRet=(POLYLINECOLOR_BACKGROUND==iColor)
        ? m_pl.rgbBackground : m_pl.rgbLine;

    return crRet;
    }

/*
 * CPolyline::LineStyleSet
 *
 * Purpose:
 *  Changes the line style in use by the Polyline
 *
 * Parameters:
 *  iStyle          UINT style of the line to use.
 *
 * Return Value:
 *  UINT            Previous style.
 */

UINT CPolyline::LineStyleSet(UINT iStyle)
    {
    UINT        uRet=(UINT)m_pl.iLineStyle;

    //Validate the line style
    if (PS_SOLID==iStyle || PS_DASH==iStyle || PS_DOT==iStyle
        || PS_DASHDOT==iStyle || PS_DASHDOTDOT==iStyle)
        {
        m_pl.iLineStyle=iStyle;

        if (uRet!=(UINT)m_pl.iLineStyle)
            {
            if (NULL!=m_pAdv)
                m_pAdv->OnLineStyleChange();

            InvalidateRect(m_hWnd, NULL, TRUE);
            UpdateWindow(m_hWnd);
            }
        }

    return uRet;
    }

/*
 * CPolyline::LineStyleGet
 *
 * Purpose:
 *  Retrieves the current line style in use in the Polyline
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Current line style.
 */

UINT CPolyline::LineStyleGet(void)
    {
    return m_pl.iLineStyle;
    }

/*
 * CPolyline::RenderBitmap
 *
 * Purpose:
 *  Creates a bitmap image of the current Polyline.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HBITMAP         Handle to the newly rendered bitmap.
 */

HBITMAP CPolyline::RenderBitmap(void)
    {
    HDC         hDC;
    HDC         hMemDC;
    HBITMAP     hBmp;
    RECT        rc;
    HGDIOBJ     hObj;

    //Render a bitmap the size of the current rectangle.
    hDC=GetDC(m_hWnd);
    hMemDC=CreateCompatibleDC(hDC);

    GetClientRect(m_hWnd, &rc);
    hBmp=CreateCompatibleBitmap(hDC, rc.right, rc.bottom);

    if (NULL!=hBmp)
        {
        //Draw the POLYLINEDATA into the bitmap.
        hObj=SelectObject(hMemDC, hBmp);
        Draw(hMemDC, FALSE, TRUE);
        SelectObject(hMemDC, hObj);
        }

    DeleteDC(hMemDC);
    ReleaseDC(m_hWnd, hDC);

    return hBmp;
    }

/*
 * CPolyline::RenderMetafile
 *
 * Purpose:
 *  Renders the current image of the Polyline into a metafile.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HMETAFILE       Handle to the newly created metafile.
 */

HMETAFILE CPolyline::RenderMetafile(void)
    {
    HDC         hDC;
    HMETAFILE   hMF;
    RECT        rc;

    //Create a memory metafile and return its handle.
    hDC=(HDC)CreateMetaFile(NULL);
    hMF=NULL;

    if (NULL!=hDC)
        {
        /*
         * This is absolutely essential to the metafile so it
         * can be scaled in the clipboard and any destination
         * application.
         */

SetMapMode(hDC, MM_ANISOTROPIC);
        GetClientRect(m_hWnd, &rc);
        SetWindowOrgEx(hDC, 0, 0, NULL);
        SetWindowExtEx(hDC, rc.right, rc.bottom, NULL);

        Draw(hDC, TRUE, TRUE);
        hMF=CloseMetaFile(hDC);
        }

    return hMF;
    }

/*
 * CPolyline::RenderMetafilePict
 *
 * Purpose:
 *  Renders the current Polyline into a METAFILEPICT structure in
 *  global memory.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HGLOBAL         Memory containing the METAFILEPICT structure.
 */

HGLOBAL CPolyline::RenderMetafilePict(void)
    {
    HGLOBAL         hMem;
    HMETAFILE       hMF;
    LPMETAFILEPICT  pMF;
    RECT            rc;

    //Get the metafile
    hMF=RenderMetafile();

    if (NULL==hMF)
        return NULL;

    //Allocate the METAFILEPICT structure.
    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
        , sizeof(METAFILEPICT));

    if (NULL==hMem)
        {
        DeleteMetaFile(hMF);
        return NULL;
        }

    /*
     * Global lock only fails in PMODE if the selector is invalid
     * (like it was discarded) or references a 0 length segment,
     * neither of which can happen here.
     */
    pMF=(LPMETAFILEPICT)GlobalLock(hMem);

    pMF->hMF=hMF;
    pMF->mm=MM_ANISOTROPIC;

    //Insert the extents in MM_HIMETRIC units.
    GetClientRect(m_hWnd, &rc);
    RectConvertMappings(&rc, FALSE);
    pMF->xExt=rc.right;
    pMF->yExt=rc.bottom;

    GlobalUnlock(hMem);
    return hMem;
    }

/*
 * CPolyline::RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device (MM_TEXT) or
 *  HIMETRIC to the other.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  fToDevice       BOOL TRUE to convert from HIMETRIC to device,
 *                  FALSE to convert device to HIMETRIC.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectConvertMappings(LPRECT pRect, BOOL fToDevice)
    {
    HDC      hDC;
    int      iLpx, iLpy;

    if (NULL==pRect)
        return;

    hDC=GetDC(NULL);
    iLpx=GetDeviceCaps(hDC, LOGPIXELSX);
    iLpy=GetDeviceCaps(hDC, LOGPIXELSY);
    ReleaseDC(NULL, hDC);

    if (fToDevice)
        {
        pRect->left=MulDiv(iLpx, pRect->left, HIMETRIC_PER_INCH);
        pRect->top =MulDiv(iLpy, pRect->top , HIMETRIC_PER_INCH);

        pRect->right=MulDiv(iLpx, pRect->right,  HIMETRIC_PER_INCH);
        pRect->bottom=MulDiv(iLpy, pRect->bottom,HIMETRIC_PER_INCH);

       #ifdef NEVER
        /*
         * In this conversion we may get situations where the top
         * coordinate is larger than the bottom, which messes us up.
         */
        if (pRect->bottom < pRect->top)
            {
            iLpy=pRect->top;
            pRect->top=pRect->bottom;
            pRect->bottom=iLpy;
            }
       #endif
        }
    else
        {
        pRect->left=MulDiv(pRect->left, HIMETRIC_PER_INCH, iLpx);
        pRect->top =MulDiv(pRect->top , HIMETRIC_PER_INCH, iLpy);

        pRect->right =MulDiv(pRect->right, HIMETRIC_PER_INCH, iLpx);
        pRect->bottom=MulDiv(pRect->bottom,HIMETRIC_PER_INCH, iLpy);
        }

    return;
    }


POLYLINE.H

/*
 * POLYLINE.H
 * Cosmo Chapter 12
 *
 * Definitions and function prototypes for the PolyLine window
 * class that can be treated like its own control.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _POLYLINE_H_
#define _POLYLINE_H_

//Versioning.
#define VERSIONMAJOR                2
#define VERSIONMINOR                0
#define VERSIONCURRENT              0x00020000

//Classname
#define SZCLASSPOLYLINE             TEXT("polyline")

//Stream Name that holds the data
#define SZSTREAM                    OLETEXT("CONTENTS")

#define HIMETRIC_PER_INCH           2540
#define CPOLYLINEPOINTS             20

//Window extra bytes and offsets
#define CBPOLYLINEWNDEXTRA          (sizeof(LONG))
#define PLWL_STRUCTURE              0

//Version 2.0 Polyline Structure
typedef struct tagPOLYLINEDATA
    {
    WORD        wVerMaj;                //Major version number.
    WORD        wVerMin;                //Minor version number.
    WORD        cPoints;                //Number of points.
    short       fReserved;              //Obsolete from v1.0
    RECTS       rc;                     //Rectangle of this figure
    POINTS      rgpt[CPOLYLINEPOINTS];  //Points (0-32767 grid)

    //Version 2.0 additions
    COLORREF    rgbBackground;          //Background color
    COLORREF    rgbLine;                //Line color
    short       iLineStyle;             //Line style
    } POLYLINEDATA, *PPOLYLINEDATA;

#define CBPOLYLINEDATA   sizeof(POLYLINEDATA)
#define CBPOLYLINEDATA20 sizeof(POLYLINEDATA)

//Version 1.0 Polyline Structure
typedef struct tagPOLYLINEDATA10
    {
    WORD        wVerMaj;                //Major version number.
    WORD        wVerMin;                //Minor version number.
    WORD        cPoints;                //Number of points.
    short       fDrawEntire;            //Flag to draw entire figure
    RECTS       rc;                     //Rectangle of this figure
    POINTS      rgpt[CPOLYLINEPOINTS];  //Points (scaled to rc)
    } POLYLINEDATA10, *PPOLYLINEDATA10;

#define CBPOLYLINEDATA10 sizeof(POLYLINEDATA10)

//POLYWIN.CPP
LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM, LPARAM);

class CPolyline : public CWindow
    {
    friend LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM
        , LPARAM);

    private:
        POLYLINEDATA   m_pl;

        class CPolylineAdviseSink * m_pAdv;

    private:
        HFILE     OpenFileW(LPTSTR, LPOFSTRUCT, UINT);
        void      PointScale(LPRECT, LPPOINTS, BOOL);
        void      Draw(HDC, BOOL, BOOL);
        void      RectConvertMappings(LPRECT, BOOL);

    public:
        CPolyline(HINSTANCE);
        ~CPolyline(void);

        BOOL      Init(HWND, LPRECT, DWORD, UINT
            , class CPolylineAdviseSink *);

        void      New(void);
        BOOL      Undo(void);

        //File functions
        LONG      ReadFromStorage(LPSTORAGE);
        LONG      WriteToStorage(LPSTORAGE, LONG);

        LONG      ReadFromFile(LPTSTR);
        LONG      WriteToFile(LPTSTR, LONG);

        //Data transfer functions
        LONG      DataSet(PPOLYLINEDATA, BOOL, BOOL);
        LONG      DataGet(PPOLYLINEDATA, LONG);
        LONG      DataSetMem(HGLOBAL, BOOL, BOOL, BOOL);
        LONG      DataGetMem(LONG, HGLOBAL *);
        HBITMAP   RenderBitmap(void);
        HMETAFILE RenderMetafile(void);
        HGLOBAL   RenderMetafilePict(void);

        void      RectGet(LPRECT);
        void      SizeGet(LPRECT);
        void      RectSet(LPRECT, BOOL);
        void      SizeSet(LPRECT, BOOL);
        COLORREF  ColorSet(UINT, COLORREF);
        COLORREF  ColorGet(UINT);
        UINT      LineStyleSet(UINT);
        UINT      LineStyleGet(void);
    };

typedef CPolyline *PCPolyline;

//Error values for data transfer functions
#define POLYLINE_E_NONE                    0
#define POLYLINE_E_UNSUPPORTEDVERSION      -1
#define POLYLINE_E_INVALIDPOINTER          -2
#define POLYLINE_E_READFAILURE             -3
#define POLYLINE_E_WRITEFAILURE            -4

class CPolylineAdviseSink
    {
    private:
        LPVOID      m_pv;           //Customizable structure

    public:
        CPolylineAdviseSink(LPVOID);
        ~CPolylineAdviseSink(void);

        void OnPointChange(void);
        void OnSizeChange(void);
        void OnDataChange(void);
        void OnColorChange(void);
        void OnLineStyleChange(void);
    };

typedef CPolylineAdviseSink *PCPolylineAdviseSink;

//Color indices for color messages
#define POLYLINECOLOR_BACKGROUND    0
#define POLYLINECOLOR_LINE          1

#endif  //_POLYLINE_H_


POLYWIN.CPP

/*
 * POLYWIN.CPP
 * Cosmo Chapter 12
 *
 * Window procedure for the polyline drawing window and support
 * functions.  This window is not complicated.  On creation it
 * allocates a block of memory for a POLYLINEDATA structure that
 * contains 20 POINTs.  We do not attempt to reallocate this
 * array at all just to maintain simplicity.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * PolylineWndProc
 *
 * Purpose:
 *  Window procedure for the polyline drawing window.
 */

LRESULT APIENTRY PolylineWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCPolyline      ppl;
    PAINTSTRUCT     ps;
    HDC             hDC;
    POINTS          pt;
    RECT            rc;

    ppl=(PCPolyline)GetWindowLong(hWnd, PLWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppl=(PCPolyline)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PLWL_STRUCTURE, (LONG)ppl);
            ppl->m_hWnd=hWnd;
            ppl->New();
            break;

        case WM_PAINT:
            hDC=BeginPaint(hWnd, &ps);
            ppl->Draw(hDC, FALSE, TRUE);
            EndPaint(hWnd, &ps);
            break;

        case WM_LBUTTONDOWN:
            //Stop if we are already at the limit.
            if (CPOLYLINEPOINTS==ppl->m_pl.cPoints)
                {
                MessageBeep(0);
                break;
                }

            //Convert the points into 0-32767 range
            GetClientRect(hWnd, &rc);
            pt=MAKEPOINTS(lParam);
            ppl->PointScale(&rc, &pt, FALSE);

            ppl->m_pl.rgpt[ppl->m_pl.cPoints++]=pt;

            //Draw the lines to this new point only.
            hDC=GetDC(hWnd);
            ppl->Draw(hDC, FALSE, FALSE);
            ReleaseDC(hWnd, hDC);

            if (NULL!=ppl->m_pAdv)
                ppl->m_pAdv->OnPointChange();

            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPolyline::Draw
 *
 * Purpose:
 *  Paints the current line in the polyline window.
 *
 * Parameters:
 *  hDC             HDC to draw on, could be metafile or printer DC.
 *  fMetafile       BOOL indicating if hDC is a metafile or not, so
 *                  we can avoid operations that RIP.
 *  fEntire         BOOL indicating if we should draw the entire
 *                  figure or not.
 *
 * Return Value:
 *  None
 */

void CPolyline::Draw(HDC hDC, BOOL fMetafile, BOOL fEntire)
    {
    HBRUSH          hBrush;
    HPEN            hPen;
    HGDIOBJ         hObj1, hObj2;
    UINT            i, j;
    UINT            uMM;
    POINTS          pt1, pt2;
    POINT           rgpt[CPOLYLINEPOINTS];
    RECT            rc;

    GetClientRect(m_hWnd, &rc);

    /*
     * Make a 32-bit copy of the points in the POLYLINE itself
     * so we can use DPtoLP and LPtoDP.
     */
    for (i=0; i < m_pl.cPoints; i++)
        {
        rgpt[i].x=m_pl.rgpt[i].x;
        rgpt[i].y=m_pl.rgpt[i].y;
        }

    /*
     * If the mapping mode is not MM_TEXT, convert the points to
     * whatever mapping mode in in effect before drawing.
     * This specifically supports metafiles in MM_ANISOTROPIC.
     */
    uMM=fMetafile ? MM_TEXT : GetMapMode(hDC);

    if (MM_TEXT!=uMM)
        DPtoLP(hDC, rgpt, m_pl.cPoints);

    hPen=CreatePen(m_pl.iLineStyle, 1, m_pl.rgbLine);
    hObj1=SelectObject(hDC, hPen);

    hBrush=CreateSolidBrush(m_pl.rgbBackground);
    hObj2=SelectObject(hDC, hBrush);
    SetBkColor(hDC, m_pl.rgbBackground);

    /*
     * Either draw the entire figure or just a single point.  The
     * entire figure also includes erasing the background
     * completely,since hDC may be a metafile DC.  Drawing a
     * single point just updates the figure for that new point.
     */
    if (fEntire || 0==m_pl.cPoints)
        {
        //Erase the background for bitmaps and metafiles.
        SelectObject(hDC, GetStockObject(NULL_PEN));
        Rectangle(hDC, rc.left, rc.top, rc.right+1, rc.bottom+1);
        SelectObject(hDC, hPen);

        /*
         * If we are drawing the entire figure, then loop through
         * each point drawing a line to each successive point.
         */

        for (i=0; i < m_pl.cPoints; i++)
            {
            for (j=i; j < m_pl.cPoints; j++)
                {
                /*
                 * Not using POINTTOPOINTS since it unfortunately
                 * returns a LONG which doesn't equate to a POINTS.
                 */
                pt1.x=(short)rgpt[i].x;
                pt1.y=(short)rgpt[i].y;
                pt2.x=(short)rgpt[j].x;
                pt2.y=(short)rgpt[j].y;

                PointScale(&rc, &pt1, TRUE);
                PointScale(&rc, &pt2, TRUE);
                MoveToEx(hDC, pt1.x, pt1.y, NULL);
                LineTo(hDC, pt2.x, pt2.y);
                }
            }
        }
    else
        {
        /*
         * If we are only drawing the last point, just cycle once
         * through previous points.
         */

        //Get the last point entered in the array.
        j=m_pl.cPoints-1;
        pt1.x=(short)rgpt[j].x;
        pt1.y=(short)rgpt[j].y;
        PointScale(&rc, &pt1, TRUE);

        for (i=0; i < j; i++)
            {
            pt2.x=(short)rgpt[i].x;
            pt2.y=(short)rgpt[i].y;
            PointScale(&rc, &pt2, TRUE);

            MoveToEx(hDC, pt1.x, pt1.y, NULL);
            LineTo(hDC, pt2.x, pt2.y);
            }
        }

    //If we have one point, draw a dot to indicate it's position.
    if (1==m_pl.cPoints)
        {
        pt1.x=(short)rgpt[0].x;
        pt1.y=(short)rgpt[0].y;
        PointScale(&rc, &pt1, TRUE);
        SetPixel(hDC, pt1.x, pt1.y, m_pl.rgbLine);
        }

    //Note:  m_pl.rgpt is unmodified in this code.

    SelectObject(hDC, hObj1);
    SelectObject(hDC, hObj2);
    DeleteObject(hBrush);
    DeleteObject(hPen);
    return;
    }

/*
 * CPolyline::PointScale
 *
 * Purpose:
 *  Scales a point to or from a relative window coordinate to a
 *  0-32767 coordinate.
 *
 * Parameters:
 *  pRect           LPRECT of the window.
 *  ppt             LPPOINTS to convert
 *  fScaleToWindow  BOOL indicating direction of scaling.
 *
 * Return Value:
 *  None
 */

void CPolyline::PointScale(LPRECT pRect, LPPOINTS ppt
    , BOOL fScaleToWindow)
    {
    DWORD   cx, cy;

    //Window size
    cx=(DWORD)(pRect->right-pRect->left);
    cy=(DWORD)(pRect->bottom-pRect->top);

    //Prevent crashes
    if (0L==cx) cx=1;
    if (0L==cy) cy=1;

    //Must use DWORD to insure proper scaling.
    if (fScaleToWindow)
        {
        ppt->x=(UINT)(((DWORD)ppt->x*cx) >> 15);
        ppt->y=(UINT)(((DWORD)ppt->y*cy) >> 15);
        }
    else
        {
        ppt->x=(UINT)(((DWORD)ppt->x << 15)/cx);
        ppt->y=(UINT)(((DWORD)ppt->y << 15)/cy);
        }

    return;
    }


RESOURCE.H

/*
 * RESOURCE.H
 * Cosmo Chapter 12
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Note that we augment classlib.h which we include before this.

//Position of Window menu.
#undef  WINDOW_MENU
#define WINDOW_MENU             4

//Total number of menus
#undef  CMENUS
#ifdef MDI
#define CMENUS                  6
#else
#define CMENUS                  5
#endif

//Menu command identifiers.

#define IDM_FILEIMPORT          (IDM_CUSTOMFILEMIN)

#define IDM_COLORBACKGROUND     (IDM_CUSTOMMIN)
#define IDM_COLORLINE           (IDM_CUSTOMMIN+1)

#define IDM_LINEMIN             (IDM_CUSTOMMIN+100)
#define IDM_LINESOLID           (IDM_LINEMIN+0)         //PS_SOLID       0
#define IDM_LINEDASH            (IDM_LINEMIN+1)         //PS_DASH        1
#define IDM_LINEDOT             (IDM_LINEMIN+2)         //PS_DOT         2
#define IDM_LINEDASHDOT         (IDM_LINEMIN+3)         //PS_DASHDOT     3
#define IDM_LINEDASHDOTDOT      (IDM_LINEMIN+4)         //PS_DASHDOTDOT  4
#define IDM_LINEMAX             IDM_LINEDASHDOTDOT

/*
 * IDs for StatStrip handling of popup menus:  must be in order of menu
 * CLASSRES.H already defines ID_MENUFILE and ID_MENUEDIT for us.
 */

#undef  ID_MENUWINDOW
#undef  ID_MENUHELP

#define ID_MENUCOLOR            (ID_MENUCUSTOMMIN+0)
#define ID_MENULINE             (ID_MENUCUSTOMMIN+1)
#define ID_MENUWINDOW           (ID_MENUCUSTOMMIN+2)
#define ID_MENUHELP             (ID_MENUCUSTOMMIN+3)

//Stringtable IDs.  Keep sequential for each group.
#define IDS_FRAMEMIN            IDS_STANDARDFRAMEMIN
#define IDS_FILEIMPORT          (IDS_CUSTOMFRAMEMIN+0)
#define IDS_FRAMEMAX            (IDS_CUSTOMFRAMEMIN+0)

#define IDS_DOCUMENTMIN         IDS_STANDARDDOCMIN
#define IDS_VERSIONCHANGE       (IDS_CUSTOMDOCMIN+0)
#define IDS_DOCUMENTMAX         (IDS_CUSTOMDOCMIN+0)

//These are specifically for the StatStrip
#define IDS_STATMESSAGEMIN                  IDS_STANDARDSTATMESSAGEMIN

#define IDS_ITEMMESSAGEFILEIMPORT           (IDS_CUSTOMSTATMESSAGEMIN+0)

#define IDS_MENUMESSAGECOLOR                (IDS_CUSTOMSTATMESSAGEMIN+1)
#define IDS_ITEMMESSAGECOLORBACKGROUND      (IDS_CUSTOMSTATMESSAGEMIN+2)
#define IDS_ITEMMESSAGECOLORLINE            (IDS_CUSTOMSTATMESSAGEMIN+3)

#define IDS_MENUMESSAGELINE                 (IDS_CUSTOMSTATMESSAGEMIN+4)
#define IDS_ITEMMESSAGELINESOLID            (IDS_CUSTOMSTATMESSAGEMIN+5)
#define IDS_ITEMMESSAGELINEDASH             (IDS_CUSTOMSTATMESSAGEMIN+6)
#define IDS_ITEMMESSAGELINEDOT              (IDS_CUSTOMSTATMESSAGEMIN+7)
#define IDS_ITEMMESSAGELINEDASHDOT          (IDS_CUSTOMSTATMESSAGEMIN+8)
#define IDS_ITEMMESSAGELINEDASHDOTDOT       (IDS_CUSTOMSTATMESSAGEMIN+9)

#define IDS_STATMESSAGEMAX                  (IDS_CUSTOMSTATMESSAGEMIN+9)

#endif //_RESOURCE_H_


DATAOBJ.CPP

/*
 * DATAOBJ.CPP
 * Data Transfer Object Chapter 12
 *
 * Implementation of the CDataObject class.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "dataobj.h"

extern HINSTANCE g_hInst;

/*
 * CDataObject::CDataObject
 * CDataObject::~CDataObject
 *
 * Parameters (Constructor):
 *  pUnkOuter       LPUNKNOWN of a controlling unknown.
 *  pfnDestroy      PFNDESTROYED to call when object is destroyed.
 */

CDataObject::CDataObject(LPUNKNOWN pUnkOuter
    , PFNDESTROYED pfnDestroy)
    {
    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pfnDestroy=pfnDestroy;

    m_hList=NULL;
    m_pIDataObject=NULL;

    return;
    }

CDataObject::~CDataObject(void)
    {
    if (NULL!=m_pIDataObject)
        delete m_pIDataObject;

    Purge();

    if (NULL!=m_hList)
        DestroyWindow(m_hList);

    return;
    }

/*
 * CDataObject::Init
 *
 * Purpose:
 *  Performs any intiailization of a CDataObject that's prone to
 *  failure that we also use internally before exposing the object
 *  outside.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CDataObject::Init(void)
    {
    LPUNKNOWN       pIUnknown=this;

    if (NULL!=m_pUnkOuter)
        pIUnknown=m_pUnkOuter;

    //Allocate contained interfaces.
    m_pIDataObject=new CImpIDataObject(this, pIUnknown);

    if (NULL==m_pIDataObject)
        return FALSE;

    m_hList=CreateWindow(TEXT("listbox"), TEXT("renderings")
        , WS_POPUP | LBS_OWNERDRAWFIXED, 0, 0, 100, 100
        , HWND_DESKTOP, NULL, g_hInst, NULL);

    if (NULL==m_hList)
        return FALSE;

    return TRUE;
    }

/*
 * CDataObject::Purge
 *
 * Purpose:
 *  Cleans out all entries in our listbox.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CDataObject::Purge(void)
    {
    UINT        i, cItems;
    PRENDERING  pRen;
    DWORD       cb;

    if (NULL==m_hList)
        return;

    cItems=(UINT)SendMessage(m_hList, LB_GETCOUNT, 0, 0L);

    for (i=0; i < cItems; i++)
        {
        cb=SendMessage(m_hList, LB_GETTEXT, i, (LPARAM)&pRen);

        if (LB_ERR!=cb)
            {
            /*
             * Release the data completely being sure to reinstate
             * the original pUnkForRelease.
             */
            pRen->stm.pUnkForRelease=pRen->pUnkOrg;
            ReleaseStgMedium(&pRen->stm);
            delete pRen;
            }
        }

    SendMessage(m_hList, LB_RESETCONTENT, 0, 0L);
    return;
    }

/*
 * CDataObject::QueryInterface
 * CDataObject::AddRef
 * CDataObject::Release
 *
 * Purpose:
 *  IUnknown members for CDataObject object.
 */

STDMETHODIMP CDataObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IDataObject==riid)
        *ppv=m_pIDataObject;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDataObject::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDataObject::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0;
    }


DATAOBJ.H

/*
 * DATAOBJ.H
 * Data Transfer Object Chapter 12
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _DATAOBJ_H_
#define _DATAOBJ_H_

#define INC_OLE2
#define CHAPTER12
#include <inole.h>

/*
 * Structure to describe a specific rendering that our user wants
 * us to maintain.
 */

typedef struct tagRENDERING
    {
    FORMATETC       fe;             //The format
    STGMEDIUM       stm;            //The actual data
    LPUNKNOWN       pUnkOrg;        //The real owner
    } RENDERING, *PRENDERING;

/*
 * The DataObject object is implemented in its own class with its
 * own IUnknown to support aggregation.  It contains one
 * CImpIDataObject object that we use to implement the externally
 * exposed interfaces.
 */

//DATAOBJ.CPP

class CImpIDataObject;
typedef class CImpIDataObject *PIMPIDATAOBJECT;

class CDataObject : public IUnknown
    {
    friend class CImpIDataObject;

    protected:
        ULONG               m_cRef;
        LPUNKNOWN           m_pUnkOuter;
        PFNDESTROYED        m_pfnDestroy;

        HWND                m_hList;        //Listbox of RENDERINGs

        //Contained interface implemetation
        PIMPIDATAOBJECT     m_pIDataObject;

    public:
        CDataObject(LPUNKNOWN, PFNDESTROYED);
        ~CDataObject(void);

        BOOL Init(void);
        void Purge(void);

        //Non-delegating object IUnknown
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
    };

typedef CDataObject *PCDataObject;

/*
 * Interface implementations for the CDataObject object.
 */

class CImpIDataObject : public IDataObject
    {
    private:
        ULONG           m_cRef;
        PCDataObject    m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIDataObject(PCDataObject, LPUNKNOWN);
        ~CImpIDataObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IDataObject members
        STDMETHODIMP GetData(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP GetDataHere(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP QueryGetData(LPFORMATETC);
        STDMETHODIMP GetCanonicalFormatEtc(LPFORMATETC, LPFORMATETC);
        STDMETHODIMP SetData(LPFORMATETC, LPSTGMEDIUM, BOOL);
        STDMETHODIMP EnumFormatEtc(DWORD, LPENUMFORMATETC *);
        STDMETHODIMP DAdvise(LPFORMATETC, DWORD
            ,  LPADVISESINK, DWORD *);
        STDMETHODIMP DUnadvise(DWORD);
        STDMETHODIMP EnumDAdvise(LPENUMSTATDATA *);
    };

class CEnumFormatEtc : public IEnumFORMATETC
    {
    private:
        ULONG           m_cRef;
        LPUNKNOWN       m_pUnkRef;
        LPFORMATETC     m_prgfe;
        ULONG           m_iCur;
        ULONG           m_cItems;

    public:
        CEnumFormatEtc(LPUNKNOWN);
        ~CEnumFormatEtc(void);

        BOOL Init(HWND);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IEnumFORMATETC members
        STDMETHODIMP Next(ULONG, LPFORMATETC, ULONG *);
        STDMETHODIMP Skip(ULONG);
        STDMETHODIMP Reset(void);
        STDMETHODIMP Clone(IEnumFORMATETC **);
    };

typedef CEnumFormatEtc *PCEnumFormatEtc;

#endif //_DATAOBJ_H_


DATATRAN.CPP

/*
 * DATATRAN.CPP
 * Data Transfer Object Chapter 12
 *
 * Transfer data object implemented in a DLL.  This data object will
 * cache specific formats and renderings such that its IDataObject
 * interface could be plopped on the clipboard or used in drag-drop.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "datatran.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

//Make this global for the data object to create listboxes.
HINSTANCE   g_hInst=NULL;

/*
 * LibMain(32)
 *
 * Purpose:
 *  Entry point conditionally compiled for Windows NT and Windows
 *  3.1.  Provides the proper structure for each environment.
 */

#ifdef WIN32
BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    if (DLL_PROCESS_DETACH==ulReason)
        {
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    return TRUE;
    }
#else
int PASCAL LibMain(HINSTANCE hInstance, WORD wDataSeg
    , WORD cbHeapSize, LPSTR lpCmdLine)
    {
    if (0!=cbHeapSize)
        UnlockData(0);

    g_hInst=hInstance;
    return (int)hInstance;
    }
#endif

/*
 * DllGetClassObject
 *
 * Purpose:
 *  Provides an IClassFactory for a given CLSID that this DLL is
 *  registered to support.  This DLL is placed under the CLSID
 *  in the registration database as the InProcServer.
 *
 * Parameters:
 *  clsID           REFCLSID that identifies the class factory
 *                  desired.  Since this parameter is passed this
 *                  DLL can handle any number of objects simply
 *                  by returning different class factories here
 *                  for different CLSIDs.
 *
 *  riid            REFIID specifying the interface the caller wants
 *                  on the class object, usually IID_ClassFactory.
 *
 *  ppv             PPVOID in which to return the interface
 *                  pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, otherwise an error code.
 */

HRESULT APIENTRY DllGetClassObject(REFCLSID rclsid
    , REFIID riid, PPVOID ppv)
    {
    //Check that we can provide the interface
    if (IID_IUnknown!=riid && IID_IClassFactory!=riid)
        return ResultFromScode(E_NOINTERFACE);

    *ppv=NULL;

    //Check each of the three CLSIDs we support.
    if (CLSID_DataTransferObject==rclsid)
        *ppv=new CDataTransferClassFactory();

    if (NULL==*ppv)
        return ResultFromScode(E_OUTOFMEMORY);

    ((LPUNKNOWN)*ppv)->AddRef();
    g_cObj++;
    return NOERROR;
    }

/*
 * DllCanUnloadNow
 *
 * Purpose:
 *  Answers if the DLL can be freed, that is, if there are no
 *  references to anything this DLL provides.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if nothing is using us, FALSE otherwise.
 */

STDAPI DllCanUnloadNow(void)
    {
    SCODE   sc;

    //Our answer is whether there are any object or locks
    sc=(0L==g_cObj && 0L==g_cLock) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the DataObject object to call when it gets
 *  destroyed.  Since we're in a DLL we only track the number
 *  of objects here letting DllCanUnloadNow take care of the rest.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;
    return;
    }

/*
 * CDataTransferClassFactory::CDataTransferClassFactory
 * CDataTransferClassFactory::~CDataTransferClassFactory
 *
 * Constructor Parameters:
 *  None
 */

CDataTransferClassFactory::CDataTransferClassFactory(void)
    {
    m_cRef=0L;
    return;
    }

CDataTransferClassFactory::~CDataTransferClassFactory(void)
    {
    return;
    }

/*
 * CDataTransferClassFactory::QueryInterface
 * CDataTransferClassFactory::AddRef
 * CDataTransferClassFactory::Release
 */

STDMETHODIMP CDataTransferClassFactory::QueryInterface
    (REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    //Any interface on this object is the object pointer.
    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDataTransferClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDataTransferClassFactory::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    ObjectDestroyed();
    return 0;
    }

/*
 * CDataTransferClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a DataObject object that supports the IDataObject
 *  and IUnknown interfaces.  If the caller asks for a different
 *  interface than these two then we fail.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we
 *                  are being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_NOINTERFACE
 *                  if we cannot support the requested interface.
 */

STDMETHODIMP CDataTransferClassFactory::CreateInstance
    (LPUNKNOWN pUnkOuter, REFIID riid, PPVOID ppvObj)
    {
    PCDataObject        pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Create the object telling it the data size to work with
    pObj=new CDataObject(pUnkOuter, ObjectDestroyed);

    if (NULL==pObj)
        return hr;

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    //Kill the object if initial creation or Init failed.
    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

/*
 * CDataTransferClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the DLL.  If the lock
 *  count goes to zero and there are no objects, the DLL is allowed
 *  to unload.  See DllCanUnloadNow.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CDataTransferClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        g_cLock--;

    return NOERROR;
    }


DATATRAN.H

/*
 * DATATRAN.H
 * Data Transfer Object Chapter 12
 *
 * Definitions, classes, and prototypes for a DLL that provides
 * data transfer objects.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _DATATRAN_H_
#define _DATATRAN_H_

#include "dataobj.h"

//DATATRAN.CPP
void ObjectDestroyed(void);

//This class factory object creates Data Transfer Objects.

class CDataTransferClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;

    public:
        CDataTransferClassFactory(void);
        ~CDataTransferClassFactory(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP         CreateInstance(LPUNKNOWN, REFIID
                                 , PPVOID);
        STDMETHODIMP         LockServer(BOOL);
    };

typedef CDataTransferClassFactory *PCDataTransferClassFactory;

#endif //_DATATRAN_H_


IDATAOBJ.CPP

/*
 * IDATAOBJ.CPP
 * Data Transfer Object Chapter 12
 *
 * Implementation of the IDataObject interface for CDataObject.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "dataobj.h"

/*
 * CImpIDataObject::CImpIDataObject
 * CImpIDataObject::~CImpIDataObject
 *
 * Parameters (Constructor):
 *  pObj            PCDataObject of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIDataObject::CImpIDataObject(PCDataObject pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIDataObject::~CImpIDataObject(void)
    {
    return;
    }

/*
 * CImpIDataObject::QueryInterface
 * CImpIDataObject::AddRef
 * CImpIDataObject::Release
 *
 * Purpose:
 *  IUnknown members for CImpIDataObject object.
 */

STDMETHODIMP CImpIDataObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIDataObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIDataObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIDataObject::GetData
 *
 * Purpose:
 *  Retrieves data described by a specific FormatEtc into a StgMedium
 *  allocated by this function.  Used like GetClipboardData.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *  pSTM            LPSTGMEDIUM in which to return the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetData(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM)
    {
    UINT        i, cItems;
    PRENDERING  pRen;
    DWORD       cb;
    HWND        hList;

    if (NULL==m_pObj->m_hList || NULL==pFE || NULL==pSTM)
        return ResultFromScode(DATA_E_FORMATETC);

    hList=m_pObj->m_hList;
    cItems=(UINT)SendMessage(hList, LB_GETCOUNT, 0, 0L);

    for (i=0; i < cItems; i++)
        {
        cb=SendMessage(hList, LB_GETTEXT, i, (LPARAM)&pRen);

        if (LB_ERR!=cb)
            {
            /*
             * Check if the requested FORMATETC is the same as one
             * that we already have. If so, then copy that STGMEDIUM
             * to pSTM and AddRef ourselves for pUnkForRelease.
             */
            if (pFE->cfFormat==pRen->fe.cfFormat
                && (pFE->tymed & pRen->fe.tymed)
                && pFE->dwAspect==pRen->fe.dwAspect)
                {
                /*
                 * ReleaseStgMedium will Release both storage
                 * and stream elements regardless of the value
                 * of pUnkForRelease, so we have to AddRef the
                 * element and bump our own ref count here.
                 */
                if (TYMED_ISTORAGE==pRen->fe.tymed)
                      pRen->stm.pstg->AddRef();

                if (TYMED_ISTREAM==pRen->fe.tymed)
                      pRen->stm.pstm->AddRef();

                *pSTM=pRen->stm;
                AddRef();
                return NOERROR;
                }
            }
        }

    return ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::GetDataHere
 *
 * Purpose:
 *  Copies a piece of data in this data object to another
 *  STGMEDIUM.  This is only supported for TYMED_ISTORAGE
 *  and TYMED_ISTREAM.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *  pSTM            LPSTGMEDIUM pointing to the medium into which
 *                  we copy.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetDataHere(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM)
    {
    UINT        i, cItems;
    PRENDERING  pRen;
    DWORD       cb;
    HWND        hList;

    if (NULL==m_pObj->m_hList || NULL==pFE || NULL==pSTM)
        return ResultFromScode(DATA_E_FORMATETC);

    //We only support IStorage and IStream
    if (!(TYMED_ISTORAGE & pFE->tymed)
        && !(TYMED_ISTREAM & pFE->tymed))
        return ResultFromScode(DATA_E_FORMATETC);

    hList=m_pObj->m_hList;
    cItems=(UINT)SendMessage(hList, LB_GETCOUNT, 0, 0L);

    for (i=0; i < cItems; i++)
        {
        cb=SendMessage(hList, LB_GETTEXT, i, (LPARAM)&pRen);

        if (LB_ERR!=cb)
            {
            /*
             * When we find a matching FORMATETC, we know we're
             * only looking for IStorage or IStream (we checked
             * above), so use IStorage::CopyTo or IStream::CopyTo
             * to make the copy.
             */
            if (pFE->cfFormat==pRen->fe.cfFormat
                && (pFE->tymed & pRen->fe.tymed)
                && pFE->dwAspect==pRen->fe.dwAspect)
                {
                if (TYMED_ISTORAGE==pFE->tymed)
                    {
                    pSTM->tymed=TYMED_ISTORAGE;
                    return pRen->stm.pstg->CopyTo(NULL, NULL
                        , NULL, pSTM->pstg);
                    }
                else
                    {
                    STATSTG     st;

                    pRen->stm.pstm->Stat(&st, STATFLAG_NONAME);
                    pSTM->tymed=TYMED_ISTREAM;

                    return pRen->stm.pstm->CopyTo(pSTM->pstm
                        , st.cbSize, NULL, NULL);
                    }
                }
            }
        }

    return ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::QueryGetData
 *
 * Purpose:
 *  Tests if a call to GetData with this FormatEtc will provide
 *  any rendering; used like IsClipboardFormatAvailable.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::QueryGetData(LPFORMATETC pFE)
    {
    UINT        i, cItems;
    PRENDERING  pRen;
    DWORD       cb;
    HWND        hList;

    if (NULL==m_pObj->m_hList || NULL==pFE)
        return ResultFromScode(S_FALSE);

    hList=m_pObj->m_hList;
    cItems=(UINT)SendMessage(hList, LB_GETCOUNT, 0, 0L);

    for (i=0; i < cItems; i++)
        {
        cb=SendMessage(hList, LB_GETTEXT, i, (LPARAM)&pRen);

        if (LB_ERR!=cb)
            {
            /*
             * Check if the requested FORMATETC is the same as one
             * that we already have.
             */
            if (pFE->cfFormat==pRen->fe.cfFormat
                && (pFE->tymed & pRen->fe.tymed)
                && pFE->dwAspect==pRen->fe.dwAspect)
                {
                return NOERROR;
                }
            }
        }

    return ResultFromScode(S_FALSE);
    }

/*
 * CImpIDataObject::SetData
 *
 * Purpose:
 *  Places data described by a FormatEtc and living in a StgMedium
 *  into the object.  The object may be responsible to clean up the
 *  StgMedium before exiting.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the data to set.
 *  pSTM            LPSTGMEDIUM containing the data.
 *  fRelease        BOOL indicating if this function is responsible
 *                  for freeing the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::SetData(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM, BOOL fRelease)
    {
    PRENDERING      prn;

    //We have to remain responsible for the data.
    if (!fRelease)
        return ResultFromScode(E_FAIL);

    //If we're handed NULLs, that means clean out the list.
    if (NULL==pFE || NULL==pSTM)
        {
        m_pObj->Purge();
        return NOERROR;
        }

    /*
     * Here we take the rendering we're given and attach it to the
     * end of the list.  We save the original pSTM->pUnkForRelease
     * and replace it with our own such that each 'copy' of this
     * data is actually just a reference count.
     */

    prn=new RENDERING;

    if (NULL==prn)
        return ResultFromScode(E_OUTOFMEMORY);

    prn->fe=*pFE;
    prn->stm=*pSTM;
    prn->pUnkOrg=pSTM->pUnkForRelease;
    prn->stm.pUnkForRelease=this;

    SendMessage(m_pObj->m_hList, LB_ADDSTRING, 0, (LONG)prn);
    return NOERROR;
    }

/*
 * CImpIDataObject::EnumFormatEtc
 *
 * Purpose:
 *  Returns an IEnumFORMATETC object through which the caller can
 *  iterate to learn about all the data formats this object can
 *  provide through either GetData[Here] or SetData.
 *
 * Parameters:
 *  dwDir           DWORD describing a data direction, either
 *                  DATADIR_SET or DATADIR_GET.
 *  ppEnum          LPENUMFORMATETC * in which to return the
 *                  pointer to the enumerator.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::EnumFormatEtc(DWORD dwDir
    , LPENUMFORMATETC *ppEnum)
    {
    PCEnumFormatEtc     pEnum;

    *ppEnum=NULL;

    /*
     * From an external point of view there are no SET formats,
     * because we want to allow the user of this component object
     * to be able to stuff ANY format in via Set.  Only external
     * users will call EnumFormatEtc and they can only Get.
     */

    switch (dwDir)
        {
        case DATADIR_GET:
             pEnum=new CEnumFormatEtc(m_pUnkOuter);
             break;

        case DATADIR_SET:
        default:
            pEnum=NULL;
            break;
        }

    if (NULL==pEnum)
        return ResultFromScode(E_FAIL);
    else
        {
        //Let the enumerator copy our format list.
        if (!pEnum->Init(m_pObj->m_hList))
            {
            delete pEnum;
            return ResultFromScode(E_FAIL);
            }

        pEnum->AddRef();
        }

    *ppEnum=pEnum;
    return NOERROR;
    }

/*
 * CImpIDataObject::GetCanonicalFormatEtc
 * CImpIDataObject::DAdvise
 * CImpIDataObject::DUnadvise
 * CImpIDataObject::EnumDAdvise
 *
 * Trivial member functions.
 */

STDMETHODIMP CImpIDataObject::GetCanonicalFormatEtc
    (LPFORMATETC pFEIn, LPFORMATETC pFEOut)
    {
    return ResultFromScode(DATA_S_SAMEFORMATETC);
    }

STDMETHODIMP CImpIDataObject::DAdvise(LPFORMATETC pFE
    , DWORD dwFlags, LPADVISESINK pIAdviseSink, LPDWORD pdwConn)
    {
    return ResultFromScode(E_FAIL);
    }

STDMETHODIMP CImpIDataObject::DUnadvise(DWORD dwConn)
    {
    return ResultFromScode(E_FAIL);
    }

STDMETHODIMP CImpIDataObject::EnumDAdvise(LPENUMSTATDATA *ppEnum)
    {
    return ResultFromScode(E_FAIL);
    }


IENUMFE.CPP

/*
 * IENUMFE.CPP
 * Data Tranfer Object for Chapter 12
 *
 * IEnumFORMATETC implementation specifically for the Data Tranfer
 * objects.  This enumerator copies the state of the data list in
 * the data object and uses that to maintain what FORMATETCs it
 * knows.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "dataobj.h"

extern HINSTANCE g_hInst;

/*
 * CEnumFormatEtc::CEnumFormatEtc
 * CEnumFormatEtc::~CEnumFormatEtc
 *
 * Parameters (Constructor):
 *  pUnkRef         LPUNKNOWN to use for reference counting.
 */

CEnumFormatEtc::CEnumFormatEtc(LPUNKNOWN pUnkRef)
    {
    m_cRef=0;
    m_pUnkRef=pUnkRef;
    m_iCur=0;
    m_cItems=0;
    return;
    }

CEnumFormatEtc::~CEnumFormatEtc(void)
    {
    if (NULL!=m_prgfe)
        {
        LPMALLOC    pIMalloc;

        if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
            {
            pIMalloc->Free(m_prgfe);
            pIMalloc->Release();
            }
        }

    return;
    }

/*
 * CEnumFormatEtc::Init
 *
 * Purpose:
 *  Copies the FORMATETCs in the data object's listbox into our
 *  own, that is, we make a snapshot for this enumerator.
 *
 * Parameters:
 *  hList           HWND of the listbox containing RENDERINGs
 *
 * Return Value:
 *  BOOL            TRUE if initialization worked,
 *                  FALSE otherwise.
 */
BOOL CEnumFormatEtc::Init(HWND hList)
    {
    UINT        i, cItems;
    LPMALLOC    pIMalloc;
    PRENDERING  pRen;
    LPFORMATETC pFE;
    DWORD       cb;

    if (NULL==hList)
        return FALSE;

    cItems=(UINT)SendMessage(hList, LB_GETCOUNT, 0, 0L);

    if (FAILED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        return FALSE;

    m_prgfe=(LPFORMATETC)pIMalloc->Alloc(cItems*sizeof(FORMATETC));
    pIMalloc->Release();

    if (NULL!=m_prgfe)
        {
        pFE=m_prgfe;

        for (i=0; i < cItems; i++)
            {
            cb=SendMessage(hList, LB_GETTEXT, i, (LPARAM)&pRen);

            if (LB_ERR!=cb)
                {
                //Copy just the FORMATETC
                *pFE++=pRen->fe;
                m_cItems++;
                }
            }
        }

    return TRUE;
    }

/*
 * CEnumFormatEtc::QueryInterface
 * CEnumFormatEtc::AddRef
 * CEnumFormatEtc::Release
 *
 * Purpose:
 *  IUnknown members for CEnumFormatEtc object.  For QueryInterface
 *  we only return out own interfaces and not those of the data
 *  object.  However, since enumerating formats only makes sense
 *  when the data object is around, we insure that it stays as
 *  long as we stay by calling an outer IUnknown for AddRef and
 *  Release.  But since we are not controlled by the lifetime of
 *  the outer object, we still keep our own reference count in
 *  order to free ourselves.
 */

STDMETHODIMP CEnumFormatEtc::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    /*
     * Enumerators are separate objects, not the data object, so
     * we only need to support out IUnknown and IEnumFORMATETC
     * interfaces here with no concern for aggregation.
     */
    if (IID_IUnknown==riid || IID_IEnumFORMATETC==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CEnumFormatEtc::AddRef(void)
    {
    ++m_cRef;
    m_pUnkRef->AddRef();
    return m_cRef;
    }

STDMETHODIMP_(ULONG) CEnumFormatEtc::Release(void)
    {
    m_pUnkRef->Release();

    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CEnumFormatEtc::Next
 * CEnumFormatEtc::Skip
 * CEnumFormatEtc::Reset
 * CEnumFormatEtc::Clone
 *
 * Standard enumerator members for IEnumFORMATETC
 */

STDMETHODIMP CEnumFormatEtc::Next(ULONG cFE, LPFORMATETC pFE
    , ULONG *pulFE)
    {
    ULONG           cReturn=0L;

    if (NULL==m_prgfe)
        return ResultFromScode(S_FALSE);

    if (NULL==pulFE)
        {
        if (1L!=cFE)
            return ResultFromScode(E_POINTER);
        }
    else
        *pulFE=0L;

    if (NULL==pFE || m_iCur >= m_cItems)
        return ResultFromScode(S_FALSE);

    while (m_iCur < m_cItems && cFE > 0)
        {
        *pFE++=m_prgfe[m_iCur++];
        cReturn++;
        cFE--;
        }

    if (NULL!=pulFE)
        *pulFE=cReturn;

    return NOERROR;
    }

STDMETHODIMP CEnumFormatEtc::Skip(ULONG cSkip)
    {
    if ((m_iCur+cSkip) >= m_cItems)
        return ResultFromScode(S_FALSE);

    m_iCur+=cSkip;
    return NOERROR;
    }

STDMETHODIMP CEnumFormatEtc::Reset(void)
    {
    m_iCur=0;
    return NOERROR;
    }

STDMETHODIMP CEnumFormatEtc::Clone(LPENUMFORMATETC *ppEnum)
    {
    PCEnumFormatEtc     pNew;
    LPMALLOC            pIMalloc;
    LPFORMATETC         prgfe;
    BOOL                fRet=TRUE;
    ULONG               cb;

    *ppEnum=NULL;

    //Copy the memory for the list.
    if (FAILED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        return ResultFromScode(E_OUTOFMEMORY);

    cb=m_cItems*sizeof(FORMATETC);
    prgfe=(LPFORMATETC)pIMalloc->Alloc(cb);

    if (NULL!=prgfe)
        {
        //Copy the formats
        memcpy(prgfe, m_prgfe, (int)cb);

        //Create the clone
        pNew=new CEnumFormatEtc(m_pUnkRef);

        if (NULL!=pNew)
            {
            pNew->m_iCur=m_iCur;
            pNew->m_prgfe=prgfe;
            pNew->AddRef();
            fRet=TRUE;
            }
        }

    pIMalloc->Release();

    *ppEnum=pNew;
    return fRet ? NOERROR : ResultFromScode(E_OUTOFMEMORY);
    }


CLIENT.CPP

/*
 * CLIENT.CPP
 * Patron Chapter 12
 *
 * Implementation of the CPatronClient class that just makes sure
 * we get a CPatronDoc on doc creation and that we initialize fully.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPatronClient::CPatronClient
 * CPatronClient::~CPatronClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 */

CPatronClient::CPatronClient(HINSTANCE hInst)
    : CClient(hInst)
    {
    return;
    }

CPatronClient::~CPatronClient(void)
    {
    return;
    }

/*
 * CPatronClient::CreateCDocument
 *
 * Purpose:
 *  Constructs a new document specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CPatronClient::CreateCDocument(void)
    {
    return (PCDocument)(new CPatronDoc(m_hInst, m_pFR));
    }


DOCUMENT.CPP

/*
 * DOCUMENT.CPP
 * Patron Chapter 12
 *
 * Implementation of the CPatronDoc derivation of CDocument that
 * manages pages for us.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"
#include <memory.h>
#include <dlgs.h>       //Pring Dlg button IDs

/*
 * CPatronDoc::CPatronDoc
 * CPatronDoc::~CPatronDoc
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame of the frame object.
 */

CPatronDoc::CPatronDoc(HINSTANCE hInst, PCFrame pFR)
    : CDocument(hInst, pFR)
    {
    m_pPG=NULL;
    m_lVer=VERSIONCURRENT;
    m_pIStorage=NULL;
    m_fPrintSetup=TRUE;
    m_pDropTarget=NULL;

    m_cfEmbeddedObject=RegisterClipboardFormat(CF_EMBEDDEDOBJECT);
    m_cfObjectDescriptor=RegisterClipboardFormat
        (CF_OBJECTDESCRIPTOR);

    //CHAPTER12MOD
    m_cfLinkSource=RegisterClipboardFormat(CF_LINKSOURCE);
    m_cfLinkSrcDescriptor=RegisterClipboardFormat
        (CF_LINKSRCDESCRIPTOR);

    m_fShowTypes=FALSE;
    //End CHAPTER12MOD
    return;
    }

CPatronDoc::~CPatronDoc(void)
    {
    if (NULL!=m_pPG)
        delete m_pPG;

    if (NULL!=m_pIStorage)
        m_pIStorage->Release();

    CoFreeUnusedLibraries();
    return;
    }

/*
 * CPatronDoc::FInit
 *
 * Purpose:
 *  Initializes an already created document window.  The client
 *  actually creates the window for us, then passes that here for
 *  further initialization.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPatronDoc::FInit(PDOCUMENTINIT pDI)
    {
    //Change the stringtable range to our customization.
    pDI->idsMin=IDS_DOCUMENTMIN;
    pDI->idsMax=IDS_DOCUMENTMAX;

    //Do default initialization
    if (!CDocument::FInit(pDI))
        return FALSE;

    //Pages are created when we get a ULoad later.
    return TRUE;
    }

/*
 * CPatronDoc::FMessageHook
 *
 * Purpose:
 *  Processes WM_SIZE for the document so we can resize the Pages
 *  window.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CPatronDoc::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    UINT        dx, dy;
    RECT        rc;

    *pLRes=0;

    //Eat to prevent flickering
    if (WM_ERASEBKGND==iMsg)
        return TRUE;

    if (WM_SIZE==iMsg && NULL!=m_pPG)
        {
        dx=LOWORD(lParam);
        dy=HIWORD(lParam);

        if (SIZE_MINIMIZED!=wParam)
            {
            //Resize Pages window to fit the new document size.
            GetClientRect(hWnd, &rc);
            m_pPG->RectSet(&rc, FALSE);
            }
        }

    if (WM_DESTROY==iMsg)
        {
        /*
         * We have to revoke the drop target here because the window
         * will be destroyed and the property forcefully removed
         * before we could do this in the destructor.
         */
        if (NULL!=m_pDropTarget)
            {
            RevokeDragDrop(m_hWnd);
            CoLockObjectExternal(m_pDropTarget, FALSE, TRUE);
            m_pDropTarget->Release();
            }

        return FALSE;
        }

    /*
     * We return FALSE even on WM_SIZE so we can let the default
     * procedure handle maximized MDI child windows appropriately.
     */
    return FALSE;
    }

/*
 * CPatronDoc::Clear
 *
 * Purpose:
 *  Sets all contents in the document back to defaults with no
 *  filename.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Clear(void)
    {
    //Completely reset the pages
    if (NULL!=m_pPG)
        m_pPG->FIStorageSet(NULL, FALSE, FALSE);

    CDocument::Clear();
    m_lVer=VERSIONCURRENT;
    return;
    }

/*
 * CPatronDoc::FDirtyGet
 *
 * Purpose:
 *  Returns the current dirty status of the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the file is clean, FALSE otherwise.
 */

BOOL CPatronDoc::FDirtyGet()
    {
    BOOL    fPageDirty;

    fPageDirty=m_pPG->FIsDirty();
    return m_fDirty | fPageDirty;
    }

/*
 * CPatronDoc::Delete
 *
 * Purpose:
 *  Removed the current object from the document.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Delete(void)
    {
    if (NULL!=m_pPG)
        m_pPG->TenantDestroy();

    CoFreeUnusedLibraries();
    return;
    }

/*
 * CPatronDoc::FQueryPrinterSetup
 *
 * Purpose:
 *  Returns whether or not the Printer Setup menu item can be
 *  enabled.  Once you create a tenant in any page, Printer Setup
 *  is voided simply to keep this sample simple, that is, we don't
 *  have to worry about reorganizing potentially large amounts
 *  of layout after we start plopping down objects.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE to enable the menu, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPrinterSetup(void)
    {
    return m_fPrintSetup;
    }

/*
 * CPatronDoc::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected in this
 *  document for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU of the Edit menu.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryObjectSelected(HMENU hMenu)
    {
    return m_pPG->FQueryObjectSelected(hMenu);
    }

/*
 * CPatronDoc::ULoad
 *
 * Purpose:
 *  Loads a given document without any user interface overwriting
 *  the previous contents of the editor.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load.  Could be NULL
 *                  for an untitled document.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_...
 */

UINT CPatronDoc::ULoad(BOOL fChangeFile, LPTSTR pszFile)
    {
    RECT        rc;
    LPSTORAGE   pIStorage;
    HRESULT     hr;
    CLSID       clsID;
    DWORD       dwMode=STGM_TRANSACTED | STGM_READWRITE
                    | STGM_SHARE_EXCLUSIVE;

    if (NULL==pszFile)
        {
        //Create a new temp file.
        hr=StgCreateDocfile(NULL, dwMode | STGM_CREATE
            | STGM_DELETEONRELEASE, 0, &pIStorage);

        //Mark this our class since we check with ReadClassStg.
        if (SUCCEEDED(hr))
            WriteClassStg(pIStorage, CLSID_PatronPages);
        }
    else
        {
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsFile[MAX_PATH];
        mbstowcs(pwcsFile, pszFile, MAX_PATH);
        hr=StgOpenStorage(pwcsFile, NULL, dwMode, NULL, 0, &pIStorage);
#else
        hr=StgOpenStorage(pszFile, NULL, dwMode, NULL, 0, &pIStorage);
#endif
        }

    if (FAILED(hr))
        return DOCERR_COULDNOTOPEN;

    //Check if this is our type of file and exit if not.
    hr=ReadClassStg(pIStorage, &clsID);

    if (FAILED(hr) || CLSID_PatronPages!=clsID)
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    //Attempt to create our contained Pages window.
    m_pPG=new CPages(m_hInst, m_cf);
    GetClientRect(m_hWnd, &rc);

    if (!m_pPG->FInit(m_hWnd, &rc, WS_CHILD | WS_VISIBLE
        , ID_PAGES, NULL))
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    if (!m_pPG->FIStorageSet(pIStorage, FALSE
        , (BOOL)(NULL==pszFile)))
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    //Open the window up for drag-drop
    m_pDropTarget=new CDropTarget(this);

    if (NULL!=m_pDropTarget)
        {
        m_pDropTarget->AddRef();
        CoLockObjectExternal(m_pDropTarget, TRUE, FALSE);
        RegisterDragDrop(m_hWnd, m_pDropTarget);
        }

    m_pIStorage=pIStorage;
    Rename(pszFile);

    //Do initial setup if new file, otherwise Pages handles things.
    if (NULL==pszFile)
        {
        //Go initialize the Pages for the default printer.
        if (!PrinterSetup(NULL, TRUE))
            return DOCERR_COULDNOTOPEN;

        //Go create an initial page.
        m_pPG->PageInsert(0);
        }
    else
        {
        //Can't change an already saved configuration
        m_fPrintSetup=FALSE;
        }

    FDirtySet(FALSE);
    return DOCERR_NONE;
    }

/*
 * CPatronDoc::USave
 *
 * Purpose:
 *  Writes the file to a known filename, requiring that the user
 *  has previously used FileOpen or FileSaveAs in order to have
 *  a filename.
 *
 * Parameters:
 *  uType           UINT indicating the type of file the user
 *                  requested to save in the File Save As dialog.
 *  pszFile         LPTSTR under which to save.  If NULL, use the
 *                  current name.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_...
 */

UINT CPatronDoc::USave(UINT uType, LPTSTR pszFile)
    {
    HRESULT     hr;
    LPSTORAGE   pIStorage;

    //Save or Save As with the same file is just a commit.
    if (NULL==pszFile
        || (NULL!=pszFile && 0==lstrcmpi(pszFile, m_szFile)))
        {
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsTemp[256];
        mbstowcs(pwcsTemp, PSZ(IDS_CLIPBOARDFORMAT), 256);
        WriteFmtUserTypeStg(m_pIStorage, m_cf, pwcsTemp);
#else
        WriteFmtUserTypeStg(m_pIStorage, m_cf
            , PSZ(IDS_CLIPBOARDFORMAT));
#endif
        //Insure pages are up to date.
        m_pPG->FIStorageUpdate(FALSE);

        //Commit everyting
        m_pIStorage->Commit(STGC_ONLYIFCURRENT);

        FDirtySet(FALSE);
        return DOCERR_NONE;
        }

    /*
     * When we're given a name, open the storage, creating it new
     * if it does not exist or overwriting the old one.  Then CopyTo
     * from the current to the new, Commit the new, Release the old.
     */

    OLECHAR pwcsFile[MAX_PATH];
#if defined(WIN32) && !defined(UNICODE)
    mbstowcs(pwcsFile, pszFile, MAX_PATH);
#else
    pwcsFile = pszFile;
#endif
    hr=StgCreateDocfile(pwcsFile, STGM_TRANSACTED | STGM_READWRITE
        | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

    if (FAILED(hr))
        return DOCERR_COULDNOTOPEN;

    WriteClassStg(pIStorage, CLSID_PatronPages);
#if defined(WIN32) && !defined(UNICODE)
    OLECHAR pwcsTemp[256];
    mbstowcs(pwcsTemp, PSZ(IDS_CLIPBOARDFORMAT), 256);
    WriteFmtUserTypeStg(pIStorage, m_cf, pwcsTemp);
#else
    WriteFmtUserTypeStg(pIStorage, m_cf, PSZ(IDS_CLIPBOARDFORMAT));
#endif

    //Insure all pages are up-to-date.
    m_pPG->FIStorageUpdate(TRUE);

    //This also copies the CLSID we stuff in here on file creation.
    hr=m_pIStorage->CopyTo(NULL, NULL, NULL, pIStorage);

    if (FAILED(hr))
        {
        SCODE       sc;

        pIStorage->Release();
        sc=hr;

        /*
         * If we failed because of low memory, use IRootStorage
         * to switch into the new file.
         */
        if (E_OUTOFMEMORY==sc)
            {
            LPROOTSTORAGE        pIRoot;

            //Delete file we already created
            DeleteFile(pszFile);

            if (FAILED(m_pIStorage->QueryInterface
                (IID_IRootStorage, (PPVOID)&pIRoot)))
                return DOCERR_WRITEFAILURE;
            hr=pIRoot->SwitchToFile(pwcsFile);
            pIRoot->Release();

            if (FAILED(hr))
                return DOCERR_WRITEFAILURE;

            //If successful, the Commit below finishes the save.
            pIStorage=m_pIStorage;
            m_pIStorage->AddRef();    //Matches Release below
            }
        }

    pIStorage->Commit(STGC_ONLYIFCURRENT);

    /*
     * Revert changes on the original storage.  If this was a temp
     * file, it's deleted since we used STGM_DELETEONRELEASE.
     */
    m_pIStorage->Release();

    //Make this new storage current
    m_pIStorage=pIStorage;
    m_pPG->FIStorageSet(pIStorage, TRUE, FALSE);

    FDirtySet(FALSE);
    Rename(pszFile);    //Update caption bar.

    return DOCERR_NONE;
    }

/*
 * CPatronDoc::FClip
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard, optionally implementing Cut by deleting the
 *  data in the current window after rendering.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *  fCut            BOOL indicating cut (TRUE) or copy (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::FClip(HWND hWndFrame, BOOL fCut)
    {
    if (NULL==m_pPG)
        return FALSE;

    return m_pPG->TenantClip(fCut);
    }

/*
 * CPatronDoc::FPaste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  to the current figure in the editor window.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::FPaste(HWND hWndFrame)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet=FALSE;
    FORMATETC       fe;
    TENANTTYPE      tType;

    if (NULL==m_pPG)
        return FALSE;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    //Go get type and format we *can* paste, then actually paste it.
    if (FQueryPasteFromData(pIDataObject, &fe, &tType))
        {
        fRet=FPasteFromData(pIDataObject, &fe, tType, NULL
            , 0L, TRUE);
        }

    pIDataObject->Release();
    return fRet;
    }

/*
 * CPatronDoc::FQueryPaste
 *
 * Purpose:
 *  Determines if we can paste data from the clipboard.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPaste(void)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    fRet=FQueryPasteFromData(pIDataObject, NULL, NULL);

    //CHAPTER12MOD
    fRet |= FQueryPasteLinkFromData(pIDataObject, NULL, NULL);
    //End CHAPTER12MOD

    pIDataObject->Release();
    return fRet;
    }

/*
 * CPatronDoc::FPasteSpecial
 *
 * Purpose:
 *  Retrieves a specific data format from the clipboard and sends
 *  it to the editor window appropriately.
 *
 *  Note that if this function is called, then the appropriate
 *  format is available because the Paste menu item is only
 *  enabled if the format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::FPasteSpecial(HWND hWndFrame)
    {
    OLEUIPASTESPECIAL   ps;
    //CHAPTER12MOD
    OLEUIPASTEENTRY     rgPaste[6];
    UINT                rgcf[1];        //For ps.m_arrLinkTypes
    //End CHAPTER12MOD
    DWORD               dwData=0;
    UINT                uTemp;
    BOOL                fRet=FALSE;

    if (NULL==m_pPG)
        return FALSE;

    memset(&ps, 0, sizeof(ps));

    if (FAILED(OleGetClipboard(&ps.lpSrcDataObj)))
        return FALSE;

    ps.cbStruct=sizeof(ps);
    ps.hWndOwner=hWndFrame;

    ps.dwFlags=PSF_SELECTPASTE;

    ps.arrPasteEntries=rgPaste;
    //CHAPTER12MOD
    ps.cPasteEntries=6;
    //End CHAPTER12MOD

    //Set up Paste Special descriptor arrays.
    SETDefFormatEtc(rgPaste[0].fmtetc, m_cf, TYMED_HGLOBAL);
    rgPaste[0].lpstrFormatName=PSZ(IDS_CLIPBOARDFORMAT);
    rgPaste[0].lpstrResultText=PSZ(IDS_PASTEASPATRON);
    rgPaste[0].dwFlags=OLEUIPASTE_PASTEONLY;

    //Embedded objects can be iconic displays if the user wants.
    SETDefFormatEtc(rgPaste[1].fmtetc, m_cfEmbeddedObject
        , TYMED_ISTORAGE);
    rgPaste[1].lpstrFormatName=PSZ(IDS_PASTEOBJECT);
    rgPaste[1].lpstrResultText=PSZ(IDS_PASTEASOBJECT);

    /*
     * CAUTION:  Use OLEUI_PASTE with embedded objects or else
     * this item will not show up in the dialog.  I learned this the
     * hard way (that is, after about 6 hours of pulling hair!).
     */
    rgPaste[1].dwFlags=OLEUIPASTE_PASTE | OLEUIPASTE_ENABLEICON;

    SETDefFormatEtc(rgPaste[2].fmtetc,CF_METAFILEPICT,TYMED_MFPICT);
    rgPaste[2].lpstrFormatName=PSZ(IDS_PASTEMETAFILE);
    rgPaste[2].lpstrResultText=PSZ(IDS_PASTEASMETAFILE);
    rgPaste[2].dwFlags=OLEUIPASTE_PASTEONLY;

    SETDefFormatEtc(rgPaste[3].fmtetc, CF_DIB, TYMED_HGLOBAL);
    rgPaste[3].lpstrFormatName=PSZ(IDS_PASTEDIB);
    rgPaste[3].lpstrResultText=PSZ(IDS_PASTEASDIB);
    rgPaste[3].dwFlags=OLEUIPASTE_PASTEONLY;

    SETDefFormatEtc(rgPaste[4].fmtetc, CF_BITMAP, TYMED_GDI);
    rgPaste[4].lpstrFormatName=PSZ(IDS_PASTEBITMAP);
    rgPaste[4].lpstrResultText=PSZ(IDS_PASTEASBITMAP);
    rgPaste[4].dwFlags=OLEUIPASTE_PASTEONLY;

    //CHAPTER12MOD
    SETDefFormatEtc(rgPaste[5].fmtetc,m_cfLinkSource,TYMED_ISTREAM);
    rgPaste[5].lpstrFormatName=PSZ(IDS_PASTELINK);
    rgPaste[5].lpstrResultText=PSZ(IDS_PASTEASLINK);
    rgPaste[5].dwFlags=OLEUIPASTE_LINKTYPE1 | OLEUIPASTE_ENABLEICON;

    //Types we can Paste Link from the clipboard.
    rgcf[0]=m_cfLinkSource;
    ps.arrLinkTypes=rgcf;
    ps.cLinkTypes=1;
    //End CHAPTER12MOD

    uTemp=OleUIPasteSpecial(&ps);

    if (OLEUI_OK==uTemp)
        {
        UINT        i=ps.nSelectedIndex;
        TENANTTYPE  tType;

        //CHAPTER12MOD
        if (ps.fLink)
            tType=TENANTTYPE_LINKEDOBJECTFROMDATA;
        else
            {
            if (1==ps.nSelectedIndex)
                tType=TENANTTYPE_EMBEDDEDOBJECTFROMDATA;
            else
                tType=TENANTTYPE_STATIC;
            }
        //End CHAPTER12MOD

        //CHAPTER12MOD
        //Handle iconic aspects...from links as well
        if ((1==i || ps.fLink) && (PSF_CHECKDISPLAYASICON
            & ps.dwFlags) && NULL!=ps.hMetaPict)
        //End CHAPTER12MOD
            {
            rgPaste[i].fmtetc.dwAspect=DVASPECT_ICON;
            dwData=(DWORD)(UINT)ps.hMetaPict;
            }

        fRet=FPasteFromData(ps.lpSrcDataObj, &rgPaste[i].fmtetc
            , tType, NULL, dwData, FALSE);

        //Always free this regardless of what we do with it.
        if (NULL!=ps.hMetaPict)
            OleUIMetafilePictIconFree(ps.hMetaPict);
        }

    ps.lpSrcDataObj->Release();
    return fRet;
    }

/*
 * CPatronDoc::FQueryPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Determines if we can paste data from a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which we might want to paste.
 *  pFE             LPFORMATETC in which to return the first format
 *                  we can use.  Ignored if NULL.
 *  ptType          PTENANTTYPE in which to store the type of
 *                  object we can paste.  Ignored if NULL.
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPasteFromData(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, PTENANTTYPE ptType)
    {
    FORMATETC       fe;
    HRESULT         hr, hr2;

    if (NULL!=(LPVOID)ptType)
        *ptType=TENANTTYPE_STATIC;

    //Any of our specific data here?
    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    hr=pIDataObject->QueryGetData(&fe);

    //If embedded object data is available, set the appropriate type
    hr2=OleQueryCreateFromData(pIDataObject);

    if (NOERROR==hr2)
        {
        if (NULL!=pFE)
            {
            /*
             * Default to content.  FPaste will use
             * CF_OBJECTDESCRIPTOR to figure the actual aspect.
             */
            SETDefFormatEtc(*pFE, m_cfEmbeddedObject
                , TYMED_ISTORAGE);
            }

        if (NULL!=(LPVOID)ptType)
            *ptType=TENANTTYPE_EMBEDDEDOBJECTFROMDATA;

        /*
         * Return now if PatronObject wasn't available, otherwise
         * break out so that pFE gets PatronObject format.
         */
        if (NOERROR!=hr)
            return TRUE;
        }

    if (NOERROR!=hr && NOERROR!=hr2)
        {
        //Try metafile, DIB, then bitmap, setting fe each time
        SETDefFormatEtc(fe, CF_METAFILEPICT, TYMED_MFPICT);
        hr=pIDataObject->QueryGetData(&fe);

        if (NOERROR!=hr)
            {
            SETDefFormatEtc(fe, CF_DIB, TYMED_HGLOBAL);
            hr=pIDataObject->QueryGetData(&fe);

            if (NOERROR!=hr)
                {
                SETDefFormatEtc(fe, CF_BITMAP, TYMED_GDI);
                hr=pIDataObject->QueryGetData(&fe);
                }
            }
        }

    if (NOERROR==hr && NULL!=pFE)
        *pFE=fe;

    return (NOERROR==hr);
    }

//CHAPTER12MOD
/*
 * CPatronDoc::FQueryPasteLinkFromData
 * (Protected)
 *
 * Purpose:
 *  Determines if we can paste link from a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which we might want to paste.
 *  pFE             LPFORMATETC in which to return the first format
 *                  we can use.  Ignored if NULL.
 *  ptType          PTENANTTYPE in which to store the type of object
 *                  we can paste.  Ignored if NULL.
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPasteLinkFromData(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, PTENANTTYPE ptType)
    {
    HRESULT         hr;

    if (NULL==pIDataObject)
        return FALSE;

    hr=OleQueryLinkFromData(pIDataObject);

    if (NOERROR!=hr)
        return FALSE;

    if (NULL!=pFE)
        SETDefFormatEtc(*pFE, m_cfLinkSource, TYMED_ISTREAM);

    if (NULL!=(LPVOID)ptType)
        *ptType=TENANTTYPE_LINKEDOBJECTFROMDATA;

    return TRUE;
    }

//End CHAPTER12MOD

/*
 * CPatronDoc::FPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Retrieves the private data format from a data object and sets
 *  it to the current figure in the editor window.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *  pFE             LPFORMATETC to use in the paste. Cannot be NULL.
 *  tType           TENANTTYPE to paste.
 *  ppo             PPATRONOBJECT containing placement data.
 *  dwData          DWORD extra data sensitive to tType
 *  fUseObjDesc     BOOL indicating to use CF_OBJECTDESCRIPTOR
 *                  format for determining the aspect of the object
 *                  if the format is available.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::FPasteFromData(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, TENANTTYPE tType, PPATRONOBJECT ppo
    , DWORD dwData, BOOL fUseObjDesc)
    {
    BOOL                fRet;
    HRESULT             hr;
    PATRONOBJECT        po;
    STGMEDIUM           stm;
    LPOBJECTDESCRIPTOR  pOD;
    FORMATETC           fe;
    BOOL                fRelease=FALSE;

    if (NULL==pFE)
        return FALSE;

    //If not given any placement data, see if we can retrieve it
    if (pFE->cfFormat==m_cf && NULL==ppo)
        {
        hr=pIDataObject->GetData(pFE, &stm);

        if (SUCCEEDED(hr))
            {
            ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);

            po=*ppo;
            ppo=&po;

            //If there's an object here, make sure type is right.
            if (ppo->fe.cfFormat==m_cfEmbeddedObject)
                tType=TENANTTYPE_EMBEDDEDOBJECTFROMDATA;

            //CHAPTER12MOD
            if (ppo->fe.cfFormat==m_cfLinkSource)
                tType=TENANTTYPE_LINKEDOBJECTFROMDATA;
            //End CHAPTER12MOD

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);
            }
        }

    /*
     * If we're told to look at CF_OBJECTDESCRIPTOR, then try to get
     * the data and copy the aspect out of it.  We're not interested
     * in any other part of it, however.
     */
    if (fUseObjDesc)
        {
        SETDefFormatEtc(fe, m_cfObjectDescriptor, TYMED_HGLOBAL);

        //CHAPTER12MOD
        hr=pIDataObject->GetData(&fe, &stm);

        if (FAILED(hr))
            {
            SETDefFormatEtc(fe, m_cfLinkSrcDescriptor
                , TYMED_HGLOBAL);
            hr=pIDataObject->GetData(&fe, &stm);
            }

        if (SUCCEEDED(hr))
        //End CHAPTER12MOD
            {
            pOD=(LPOBJECTDESCRIPTOR)GlobalLock(stm.hGlobal);
            pFE->dwAspect=pOD->dwDrawAspect;

            if (NULL!=ppo)
                ppo->fe.dwAspect=pFE->dwAspect;

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);

            /*
             * Furthermore, if pFE->dwAspect is DVASPECT_ICON, get
             * the metafile which will always be the icon
             * representation.
             */
            SETFormatEtc(fe, CF_METAFILEPICT, DVASPECT_ICON, NULL
                , TYMED_MFPICT, -1);

            if (SUCCEEDED(pIDataObject->GetData(&fe, &stm)))
                {
                dwData=(DWORD)(UINT)stm.hGlobal;
                fRelease=TRUE;
                }
            }
        }

    fRet=m_pPG->TenantCreate(tType, pIDataObject, pFE, ppo, dwData);

    //Release the stm from the last GetData
    if (fRelease)
        ReleaseStgMedium(&stm);

    if (fRet)
        {
        //Disable Printer Setup once we've created a tenant.
        m_fPrintSetup=FALSE;
        FDirtySet(TRUE);
        }

    return fRet;
    }

//CHAPTER12MOD
/*
 * CPatronDoc::FQueryEnableEditLinks
 *
 * Purpose:
 *  Determines whether or not the Edit Links menu item should be
 *  enabled on the frame window menu.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE to enable the item, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryEnableEditLinks(void)
    {
    return m_pPG->FQueryLinksInPage();
    }

/*
 * CPatronDoc::FEditLinks
 *
 * Purpose:
 *  Invokes the links dialog in which the end-user can manipulate
 *  all the links in the current page.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame window to use as the dialog
 *                  parent.
 *
 * Return Value:
 *  BOOL            TRUE if the function was successful,
 *                  FALSE otherwise.
 */

BOOL CPatronDoc::FEditLinks(HWND hWndFrame)
    {
    UINT                    uRet;
    OLEUIEDITLINKS          el;
    PCIOleUILinkContainer   pIUILinks;

    memset(&el, 0, sizeof(el));
    el.cbStruct=sizeof(el);
    el.hWndOwner=hWndFrame;

    if (!m_pPG->FGetUILinkContainer(&pIUILinks))
        return FALSE;

    el.lpOleUILinkContainer=pIUILinks;
    uRet=OleUIEditLinks(&el);

    //Only the IOleUILinkContainer interface will know dirtying.
    m_fDirty=pIUILinks->m_fDirty;

    el.lpOleUILinkContainer->Release();
    return TRUE;
    }

/*
 * CPatronDoc::FShowOrQueryObjectTypes
 *
 * Purpose:
 *  If querying, we return whether or not ShowObjects is active
 *  in this document.  Otherwise pass-through to
 *  CPages::ShowObjectTypes.
 *
 * Parameters:
 *  fShow           BOOL indicating to show the type or not.
 *
 * Return Value:
 *  None
 */

BOOL CPatronDoc::FShowOrQueryObjectTypes(BOOL fQuery, BOOL fShow)
    {
    if (fQuery)
        return m_fShowTypes;

    m_fShowTypes=fShow;
    m_pPG->ShowObjectTypes(fShow);
    return TRUE;
    }

//End CHAPTER12MOD

/*
 * CPatronDoc::Print
 *
 * Purpose:
 *  Prints the current document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *
 * Return Value:
 *  BOOL            TRUE if printing happened, FALSE if it didn't
 *                  start or didn't complete.
 */

BOOL CPatronDoc::Print(HWND hWndFrame)
    {
    PRINTDLG        pd;
    BOOL            fSuccess;

    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);
    pd.hwndOwner  =hWndFrame;
    pd.nCopies    =1;
    pd.nFromPage  =(USHORT)-1;
    pd.nToPage    =(USHORT)-1;
    pd.nMinPage   =1;
    pd.nMaxPage   =m_pPG->NumPagesGet();

    pd.lpfnPrintHook=PrintDlgHook;

    //Get the current document printer settings
    pd.hDevMode=m_pPG->DevModeGet();

    pd.Flags=PD_RETURNDC | PD_ALLPAGES | PD_COLLATE
        | PD_HIDEPRINTTOFILE | PD_NOSELECTION | PD_ENABLEPRINTHOOK;

    if (!PrintDlg(&pd))
        return FALSE;

    if (NULL!=pd.hDevMode)
        GlobalFree(pd.hDevMode);

    if (NULL!=pd.hDevNames)
        GlobalFree(pd.hDevNames);

    //Go do the actual printing.
    fSuccess=m_pPG->Print(pd.hDC, PSZ(IDS_DOCUMENTNAME), pd.Flags
        , pd.nFromPage, pd.nToPage, pd.nCopies);

    if (!fSuccess)
        {
        MessageBox(m_hWnd, PSZ(IDS_PRINTERROR)
            , PSZ(IDS_DOCUMENTCAPTION), MB_OK);
        }

    return fSuccess;
    }

/*
 * CPatronDoc::PrinterSetup
 *
 * Purpose:
 *  Selects a new printer and options for this document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *  fDefault        BOOL to avoid any dialog and just use the
 *                  default.
 *
 * Return Value:
 *  UINT            Undefined
 *
 */

UINT CPatronDoc::PrinterSetup(HWND hWndFrame, BOOL fDefault)
    {
    PRINTDLG        pd;

    //Attempt to get printer metrics for the default printer.
    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);

    if (fDefault)
        pd.Flags=PD_RETURNDEFAULT;
    else
        {
        pd.hwndOwner=hWndFrame;
        pd.Flags=PD_PRINTSETUP;

        //Get the current document printer settings
        pd.hDevMode=m_pPG->DevModeGet();
        }

    if (!PrintDlg(&pd))
        return FALSE;

    if (!m_pPG->DevModeSet(pd.hDevMode, pd.hDevNames))
        {
        GlobalFree(pd.hDevNames);
        GlobalFree(pd.hDevMode);
        return FALSE;
        }

    FDirtySet(TRUE);
    return 1;
    }

/*
 * CPatronDoc::NewPage
 *
 * Purpose:
 *  Creates a new page in the document's pages control after the
 *  current page.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::NewPage(void)
    {
    FDirtySet(TRUE);
    return m_pPG->PageInsert(0);
    }

/*
 * CPatronDoc::DeletePage
 *
 * Purpose:
 *  Deletes the current page from the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the now current page.
 */

UINT CPatronDoc::DeletePage(void)
    {
    FDirtySet(TRUE);
    return m_pPG->PageDelete(0);
    }

/*
 * CPatronDoc::NextPage
 *
 * Purpose:
 *  Shows the next page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::NextPage(void)
    {
    UINT        iPage;

    iPage=m_pPG->CurPageGet();
    return m_pPG->CurPageSet(++iPage);
    }

/*
 * CPatronDoc::PreviousPage
 *
 * Purpose:
 *  Shows the previous page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::PreviousPage(void)
    {
    UINT        iPage;

    //If iPage is zero, then we wrap around to the end.
    iPage=m_pPG->CurPageGet();
    return m_pPG->CurPageSet(--iPage);
    }

/*
 * CPatronDoc::FirstPage
 *
 * Purpose:
 *  Shows the first page page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::FirstPage(void)
    {
    return m_pPG->CurPageSet(0);
    }

/*
 * CPatronDoc::LastPage
 *
 * Purpose:
 *  Shows the last page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the last page.
 */

UINT CPatronDoc::LastPage(void)
    {
    return m_pPG->CurPageSet(NOVALUE);
    }

/*
 * PrintDlgHook
 *
 * Purpose:
 *  Callback hook for the Print Dialog so we can hide the Setup
 *  button.  Patron only allows Setup before anything exists on
 *  the page, and is not written to handle setup at Print time.
 */

UINT CALLBACK PrintDlgHook(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    if (WM_INITDIALOG==iMsg)
        {
        HWND        hWnd;

        hWnd=GetDlgItem(hDlg, psh1);
        ShowWindow(hWnd, SW_HIDE);
        return TRUE;
        }

    return FALSE;
    }

/*
 * CPatronDoc::Rename
 *
 * Purpose:
 *  Overrides the normal rename to include notification of tenants
 *  in this document of the new name.  This is so embedded objects
 *  are told the correct name through IOleObject::SetHostNames.
 *
 * Parameters:
 *  pszFile         LPTSTR to the new filename.
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Rename(LPTSTR pszFile)
    {
    //CHAPTER12MOD
    LPMONIKER   pmk;

    //We don't need to change the base class, just augment...
    CDocument::Rename(pszFile);

    //Give a moniker to linked objects in tenants.
    if (NULL!=pszFile)
        {
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsFile[MAX_PATH];
        mbstowcs(pwcsFile, pszFile, MAX_PATH);
        CreateFileMoniker(pwcsFile, &pmk);
#else
        CreateFileMoniker(pszFile, &pmk);
#endif
        m_pPG->NotifyTenantsOfRename(pszFile, pmk);

        //No need for us to hold on to this.
        pmk->Release();
        }

    //End CHAPTER12MOD
    return;
    }

/*
 * CPatronDoc::FInsertObject
 *
 * Purpose:
 *  Retrieves a CLSID or a filename from the Insert Object dialog
 *  box and creates an object using those identifiers.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::FInsertObject(HWND hWndFrame)
    {
    OLEUIINSERTOBJECT   io;
    DWORD               dwData=0;
    TCHAR               szFile[CCHPATHMAX];
    UINT                uTemp;
    BOOL                fRet=FALSE;

    if (NULL==m_pPG)
        return FALSE;

    memset(&io, 0, sizeof(io));

    io.cbStruct=sizeof(io);
    io.hWndOwner=hWndFrame;
    szFile[0]=0;
    io.lpszFile=szFile;
    io.cchFile=CCHPATHMAX;

    //CHAPTER12MOD
    //We now support linking, so remove the IOF_DISABLELINK flag.
    io.dwFlags=IOF_SELECTCREATENEW;
    //End CHAPTER12MOD

    uTemp=OleUIInsertObject(&io);

    if (OLEUI_OK==uTemp)
        {
        TENANTTYPE      tType;
        LPVOID          pv;
        FORMATETC       fe;

        SETDefFormatEtc(fe, 0, TYMED_NULL);

        if (io.dwFlags & IOF_SELECTCREATENEW)
            {
            tType=TENANTTYPE_EMBEDDEDOBJECT;
            pv=&io.clsid;
            }
        else
            {
            //CHAPTER12MOD
            if (io.dwFlags & IOF_CHECKLINK)
                tType=TENANTTYPE_LINKEDFILE;
            else
                tType=TENANTTYPE_EMBEDDEDFILE;
            //End CHAPTER12MOD
            pv=szFile;
            }

        if ((io.dwFlags & IOF_CHECKDISPLAYASICON)
            && NULL!=io.hMetaPict)
            {
            fe.dwAspect=DVASPECT_ICON;
            dwData=(DWORD)(UINT)io.hMetaPict;
            }

        fRet=m_pPG->TenantCreate(tType, pv, &fe, NULL, dwData);

        //Free this regardless of what we do with it.
        if (NULL!=io.hMetaPict)
            OleUIMetafilePictIconFree(io.hMetaPict);

        if (fRet)
            {
            //Disable Printer Setup once we've created a tenant.
            m_fPrintSetup=FALSE;
            FDirtySet(TRUE);
            }
        }

    return fRet;
    }

/*
 * CPatronDoc::ActivateObject
 *
 * Purpose:
 *  Executes a verb on the currently selected object.
 *
 * Parameters:
 *  iVerb           LONG of the selected verb.
 *
 * Return Value:
 *  None
 */

void CPatronDoc::ActivateObject(LONG iVerb)
    {
    m_pPG->ActivateObject(iVerb);
    return;
    }


PAGE.CPP

/*
 * PAGE.CPP
 * Patron Chapter 12
 *
 * Implementation of parts of the CPage class; those member
 * functions dealing with mouse events are in PAGEMOUS.CPP.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPage::CPage
 * CPage::~CPage
 *
 * Constructor Parameters:
 *  dwID            DWORD identifier for this page.
 *  hWnd            HWND of the pages window (for repaints, etc).
 *  pPG             PCPages to the Pages window.
 */

CPage::CPage(DWORD dwID, HWND hWnd, PCPages pPG)
    {
    m_dwID     =dwID;
    m_pIStorage=NULL;

    m_cOpens=0;
    m_hWnd=hWnd;
    m_pPG=pPG;

    m_dwIDNext      =0;
    m_cTenants      =0;
    m_hWndTenantList=NULL;
    m_iTenantCur    =NOVALUE;   //Tenants are zero indexed.
    m_pTenantCur    =NULL;

    m_uHTCode=HTNOWHERE;
    m_uSizingFlags=0;
    m_fTracking=FALSE;
    m_hDC=NULL;

    m_fDragPending=FALSE;
    m_fSizePending=FALSE;
    m_fTimer=FALSE;

    //Get WIN.INI distance and delay values, with OLE defaults.
    m_cxyDist=GetProfileInt(TEXT("windows"), TEXT("DragMinDist")
        , DD_DEFDRAGMINDIST);
    m_cDelay=GetProfileInt(TEXT("windows"), TEXT("DragDelay")
        , DD_DEFDRAGDELAY);

    //CHAPTER12MOD
    m_fReopen=FALSE;
    //End CHAPTER12MOD

    return;
    }

CPage::~CPage(void)
    {
    if (m_fTimer)
        KillTimer(m_hWnd, IDTIMER_DEBOUNCE);

    m_hWnd=NULL;
    Close(FALSE);
    return;
    }

/*
 * CPage::GetID
 *
 * Return Value:
 *  DWORD           dwID field in this page.  This function is only
 *                  here to avoid hiding inline implementations
 *                  in pages.h
 */

DWORD CPage::GetID(void)
    {
    return m_dwID;
    }

/*
 * CPage::FOpen
 *
 * Purpose:
 *  Retrieves the IStorage associated with this page.  The IStorage
 *  is owned by the page and thus the page always holds a reference
 *  count.  The caller should call Close or delete this page to
 *  match this open.
 *
 *  This function may be called multiple times resulting in
 *  additional reference counts on the storage each of which must be
 *  matched with a call to Close.  The last Close can be done
 *  through delete.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which this page lives.
 *
 * Return Value:
 *  BOOL            TRUE if opening succeeds, FALSE otherwise.
 */

BOOL CPage::FOpen(LPSTORAGE pIStorage)
    {
    HRESULT                 hr=NOERROR;
    LPSTREAM                pIStream;
    DWORD                   dwMode;
    TCHAR                   szTemp[32];
    BOOL                    fNew;
    BOOL                    fCreated=FALSE;
    TENANTLIST              tl;
    PTENANTINFO             pti;
    ULONG                   cb;
    LPMALLOC                pIMalloc;
    UINT                    i;
    PCTenant                pTenant;
    //CHAPTER12MOD
    UINT                    cLinks;
    LPOLELINK               pIOleLink;
    LPUNKNOWN               pIUnknown;
    UINT                    uRet;
    OLEUIEDITLINKS          el;
    PCIOleUILinkContainer   pIUILinks;
    HWND                    hWndDoc;
    //End CHAPTER12MOD

    fNew=(NULL==m_pIStorage);

    if (!fNew)
        {
        m_cOpens++;
        m_pIStorage->AddRef();
        return TRUE;
        }

    if (NULL==pIStorage)
        return FALSE;

    /*
     * Attempt to open the storage under this ID.  If none,
     * create one.  In either case, the IStorage is either
     * saved in pPage or released.
     */

    GetStorageName(szTemp);
    dwMode=STGM_TRANSACTED | STGM_READWRITE | STGM_SHARE_EXCLUSIVE;

    OLECHAR pwcsFile[MAX_PATH];
#if defined(WIN32) && !defined(UNICODE)
    mbstowcs(pwcsFile, szTemp, MAX_PATH);
#else
    pwcsFile = szTemp;
#endif

    hr=pIStorage->OpenStorage(pwcsFile, NULL, dwMode, NULL, 0
        , &m_pIStorage);

    if (FAILED(hr))
        {
        hr=pIStorage->CreateStorage(pwcsFile, dwMode, 0, 0
            , &m_pIStorage);
        fCreated=TRUE;
        }

    if (FAILED(hr))
        return FALSE;

    m_cOpens++;

    if (NULL==m_hWndTenantList)
        {
        /*
         * The first time we open this page, create the hidden
         * listbox we'll use to track tenants.  We give it the
         * owner-draw style so we can just store pointers in it.
         */
        m_hWndTenantList=CreateWindow(TEXT("listbox")
            , TEXT("Tenant List"), WS_POPUP | LBS_OWNERDRAWFIXED
            , 0, 0, 100, 100, HWND_DESKTOP, NULL
            , m_pPG->m_hInst, NULL);

        if (NULL==m_hWndTenantList)
            return FALSE;
        }

    //If this is brand-new, we're done.
    if (fCreated)
        return TRUE;

    /*
     * Now open the stream we saved in Close and load all the
     * tenants listed in there.  If there are none, then we don't
     * have to load squat.
     */

    hr=m_pIStorage->OpenStream(SZSTREAMTENANTLIST, NULL, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pIStream->Read(&tl, sizeof(tl), NULL);
        m_cTenants=tl.cTenants;
        m_dwIDNext=tl.dwIDNext;
        m_iTenantCur=0;

        cb=tl.cTenants*sizeof(TENANTINFO);

        if (0!=cb)
            {
            pti=(PTENANTINFO)pIMalloc->Alloc(cb);

            if (NULL!=pti)
                {
                pIStream->Read(pti, cb, NULL);

                for (i=0; i < m_cTenants; i++)
                    {
                    if (FTenantAdd(NOVALUE, (pti+i)->dwID, &pTenant))
                        {
                        pTenant->FLoad(m_pIStorage, (pti+i));

                        //CHAPTER12MOD
                        //Make sure it knows about the show state.
                        pTenant->ShowObjectType(m_pPG->m_fShowTypes);
                        //End CHAPTER12MOD
                        }
                    }

                pIMalloc->Free(pti);
                }
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    //Get and select the first tenant
    if (FTenantGet(0, &m_pTenantCur, FALSE))
        m_pTenantCur->Select(TRUE);

    //CHAPTER12MOD
    //If we just saved and closed, don't bother with updating links
    if (m_fReopen)
        {
        m_fReopen=FALSE;
        return TRUE;
        }

    /*
     * Update all the links in this page, showing the progress
     * indicator as it's happening.  We use the same
     * IOlUILinkContainer implementation as we do for the links
     * dialog, passing it to OleUIUpdateLinks which does everything
     * for us.
     *
     * We might also optimize this to not do anything if there are
     * no automatic links, but it's not a big concern.
     */

    //First, count the number of automatic links.
    cLinks=0;

    for (i=0; i < m_cTenants; i++)
        {
        if (FTenantGet(i, &pTenant, FALSE))
            {
            DWORD       dw;

            pTenant->ObjectGet(&pIUnknown);
            hr=pIUnknown->QueryInterface(IID_IOleLink
                , (PPVOID)&pIOleLink);
            pIUnknown->Release();

            if (FAILED(hr))
                continue;

            pIOleLink->GetUpdateOptions(&dw);
            pIOleLink->Release();

            if (OLEUPDATE_ALWAYS==dw)
                cLinks++;
            }
        }

    //If we have any automatic links, invoke the update dialog.
    if (0==cLinks)
        return TRUE;

    //Create an IOleUILinkContainer instantiation.
    if (!m_pPG->FGetUILinkContainer(&pIUILinks))
        return TRUE;    //Guess we can't update, oh well.

    hWndDoc=GetParent(m_hWnd);
    LoadString(m_pPG->m_hInst, IDS_CAPTION, szTemp, sizeof(szTemp));
    if (!OleUIUpdateLinks(pIUILinks, hWndDoc, szTemp, cLinks))
        {
        /*
         * If updating failed, ask to show the links dialog.  NOTE:
         * OleUIPromptUser has a variable wsprintf argument list
         * after the hWnd parameter!  Use appropriate typecasting!
         */
        uRet=OleUIPromptUser(IDD_CANNOTUPDATELINK, hWndDoc, szTemp);

        if (ID_PU_LINKS==uRet)
            {
            //Throw up the links dialog.
            memset(&el, 0, sizeof(el));
            el.cbStruct=sizeof(el);
            el.hWndOwner=hWndDoc;
            el.lpOleUILinkContainer=pIUILinks;
            OleUIEditLinks(&el);
            }
        }

    m_pPG->m_fDirty=pIUILinks->m_fDirty;
    pIUILinks->Release();
    //End CHAPTER12MOD
    return TRUE;
    }

/*
 * CPage::Close
 *
 * Purpose:
 *  Possibly commits the storage, then releases it reversing the
 *  reference count from FOpen.
 *
 * Parameters:
 *  fCommit         BOOL indicating if we're to commit.
 *
 * Return Value:
 *  None
 */

void CPage::Close(BOOL fCommit)
    {
    if (NULL==m_pIStorage)
        return;

    if (fCommit)
        Update();

    m_pIStorage->Release();

    //If this was the last close, make all tenants loaded->passive
    if (0==--m_cOpens)
        {
        UINT        i;
        PCTenant    pTenant;

        m_pIStorage=NULL;

        for (i=0; i < m_cTenants; i++)
            {
            if (FTenantGet(i, &pTenant, FALSE))
                {
                if (NULL!=m_hWnd)
                    {
                    //FOpen may select again, so this repaints.
                    pTenant->Select(FALSE);
                    }

                pTenant->Close(FALSE);
                pTenant->Release();
                }
            }

        DestroyWindow(m_hWndTenantList);
        m_hWndTenantList=NULL;

        //CHAPTER12MOD
        /*
         * If we reopen this page right away, this flag will tell
         * us to skip updating links again which would make us
         * dirty right after a SaveAs operation.
         */
        m_fReopen=TRUE;
        //End CHAPTER12MOD
        }

    return;
    }

/*
 * CPage::Update
 *
 * Purpose:
 *  Forces a common on the page if it's open.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if there are any open objects on this page,
 *                  that is, we should remain open.
 */

BOOL CPage::Update(void)
    {
    BOOL            fOpen=FALSE;
    UINT            i;
    PCTenant        pTenant;
    HRESULT         hr;
    LPSTREAM        pIStream;
    TENANTLIST      tl;
    PTENANTINFO     pti;
    ULONG           cb;
    LPMALLOC        pIMalloc;

    //Walk the list of objects and update them all as well.
    for (i=0; i < m_cTenants; i++)
        {
        if (FTenantGet(i, &pTenant, FALSE))
            fOpen |= pTenant->Update();
        }

    //Now write our own stream containing the tenant list.
    hr=m_pIStorage->CreateStream(SZSTREAMTENANTLIST, STGM_CREATE
        | STGM_WRITE| STGM_DIRECT | STGM_SHARE_EXCLUSIVE, 0, 0
        , &pIStream);

    if (FAILED(hr))
        return fOpen;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        tl.cTenants=m_cTenants;
        tl.dwIDNext=m_dwIDNext;

        pIStream->Write(&tl, sizeof(TENANTLIST), &cb);

        cb=m_cTenants*sizeof(TENANTINFO);
        pti=(PTENANTINFO)pIMalloc->Alloc(cb);

        if (NULL!=pti)
            {
            for (i=0; i < m_cTenants; i++)
                {
                FTenantGet(i, &pTenant, FALSE);
                pTenant->GetInfo((pti+i));
                }

            pIStream->Write(pti, cb, &cb);
            pIMalloc->Free(pti);
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    //Now commit the whole mess and we're done
    if (NULL!=m_pIStorage)
        m_pIStorage->Commit(STGC_ONLYIFCURRENT);

    return fOpen;
    }

/*
 * CPage::Destroy
 *
 * Purpose:
 *  Removes this page from the given storage.  The caller should
 *  eventually delete this Page object to free the storage.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE contianing this page on which to call
 *                  DestroyElement
 *
 * Return Value:
 *  None
 */

void CPage::Destroy(LPSTORAGE pIStorage)
    {
    if (NULL!=pIStorage)
        {
        TCHAR   szTemp[32];

        Close(FALSE);
        GetStorageName(szTemp);
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsTemp[32];
        mbstowcs(pwcsTemp, szTemp, 32);
        pIStorage->DestroyElement(pwcsTemp);
#else
        pIStorage->DestroyElement(szTemp);
#endif
        }

    return;
    }

/*
 * CPage::GetStorageName
 *
 * Parameters:
 *  pszName         LPTSTR to a buffer in which to store the storage
 *                  name for this page.
 *
 * Return Value:
 *  UINT            Number of characters stored.
 */

UINT CPage::GetStorageName(LPTSTR pszName)
    {
    return wsprintf(pszName, TEXT("Page %lu"), m_dwID);
    }

/*
 * CPage::Draw
 *
 * Purpose:
 *  Draws the objects on this page to the given hDC.
 *
 * Parameters:
 *  hDC             HDC on which to draw.
 *  xOff, yOff      int offsets for the page.
 *  fNoColor        BOOL indicating black & white screen rendering.
 *  fPrinter        BOOL indicating hDC is on the printer.
 *
 * Return Value:
 *  None
 */

void CPage::Draw(HDC hDC, int xOff, int yOff, BOOL fNoColor
    , BOOL fPrinter)
    {
    int                 i;
    PCTenant            pTenant;
    HDC                 hIC=NULL;
    PCOMBINEDEVICE      pcd=NULL;
    DVTARGETDEVICE     *ptd=NULL;

    /*
     * If printing, tell the tenant to forget the borders. Otherwise
     * we leave xOff and yOff the same to account for scrolling.
     */
    if (fPrinter)
        {
        xOff=LOMETRIC_BORDER+m_pPG->m_xMarginLeft;
        yOff=-LOMETRIC_BORDER-m_pPG->m_yMarginTop;

        /*
         * Get device information.  If this fails, ptd is
         * NULL which is acceptable.
         */
        if (m_pPG->DevReadConfig(&pcd, &hIC))
            ptd=&(pcd->td);
        }

    for (i=(int)m_cTenants-1; i >=0; i--)
        {
        if (FTenantGet(i, &pTenant, FALSE))
            {
            RECT        rc, rcWin;
            RECTL       rcl;

            //Paint this tenant only if visible.
            pTenant->RectGet(&rcl, TRUE);
            RECTFROMRECTL(rc, rcl);
            OffsetRect(&rc, -(int)m_pPG->m_xPos
                , -(int)m_pPG->m_yPos);
            GetClientRect(m_hWnd, &rcWin);

            if (IntersectRect(&rc, &rc, &rcWin))
                {
                pTenant->Draw(hDC, ptd, hIC, xOff, yOff
                    , fNoColor, fPrinter);
                }
            }
        }

    //Free whatever CPages::DevReadConfig returned.
    if (NULL!=pcd)
        {
        LPMALLOC    pIMalloc;

        if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
            {
            pIMalloc->Free(pcd);
            pIMalloc->Release();
            }
        }

    if (NULL!=hIC)
        DeleteDC(hIC);

    return;
    }

/*
 * CPage::TenantCreate
 *
 * Purpose:
 *  Creates a new tenant of a specific type.
 *
 * Parameters:
 *  tType           TENANTTYPE to create.
 *  pv              LPVOID providing information for the new
 *                  object creation.
 *  pFE             LPFORMATETC describing how we want this
 *                  rendered.
 *  ppo             PPATRONOBJECT with placement data.
 *  dwData          DWORD extra data to pass to the tenant.
 *
 * Return Value:
 *  None
 */

BOOL CPage::TenantCreate(TENANTTYPE tType, LPVOID pv
    , LPFORMATETC pFE, PPATRONOBJECT ppo, DWORD dwData)
    {
    PCTenant    pTenant;
    UINT        uRet;
    int         x, y;
    int         h, v;
    POINTL      ptl;
    SIZEL       szl;
    RECTL       rcl;
    RECT        rc;

    //New tenants go at top of the pile; zero index to FTenantAdd.
    if (!FTenantAdd(0, m_dwIDNext, &pTenant))
        return FALSE;

    uRet=pTenant->UCreate(tType, pv, pFE, &ptl, &szl, m_pIStorage
        , ppo, dwData);

    if (UCREATE_FAILED==uRet)
        {
        //Reverse UCreate AND FTenantAdd
        SendMessage(m_hWndTenantList, LB_DELETESTRING, 0, 0L);
        pTenant->Destroy(m_pIStorage);

        pTenant->Release();
        return FALSE;
        }

    m_dwIDNext++;
    m_cTenants++;

    if (NULL!=m_pTenantCur)
        m_pTenantCur->Select(FALSE);

    m_iTenantCur=0;             //First one in the list now.
    m_pTenantCur=pTenant;

    //Tell the tenant where it lives, default is (0,0) in print area
    x=LOMETRIC_BORDER+m_pPG->m_xMarginLeft;
    y=-LOMETRIC_BORDER-m_pPG->m_yMarginTop;

    h=x;
    v=y;

    if (UCREATE_PLACEDOBJECT==uRet)
        {
        SetRect(&rc, 3*CXYHANDLE, 3*CXYHANDLE, 0, 0);
        RectConvertMappings(&rc, NULL, FALSE);

        //Make sure place point is on page, otherwise go to (0,0)
        if (((int)ptl.x > x)
            && ((int)ptl.x < x+(int)m_pPG->m_cx-rc.left))
            x=(int)ptl.x;

        //m_pPG->m_cy is absolute
        if (((int)ptl.y < y)
            && ((int)ptl.y > y-(int)m_pPG->m_cy-rc.top))
            y=(int)ptl.y;
        }

    //Bounds check size of the object and fit to page as necessary.
    if (x+(int)szl.cx > (int)(h+m_pPG->m_cx))
        szl.cx=h+m_pPG->m_cx-x;

    //Remember that szl we get from UCreate is absolute
    if (y-(int)szl.cy < (int)(v-m_pPG->m_cy))
        szl.cy=-(int)(v-m_pPG->m_cy-y);

    SETRECTL(rcl, x, y, x+szl.cx, y-szl.cy);
    m_pTenantCur->RectSet(&rcl, FALSE, TRUE);

    //Force a repaint on this new guy
    m_pTenantCur->Invalidate();
    UpdateWindow(m_hWnd);

    m_pTenantCur->Select(TRUE);

    //CHAPTER12MOD
    //Make sure this new tenant knows about showing it's type.
    m_pTenantCur->ShowObjectType(m_pPG->m_fShowTypes);
    //End CHAPTER12MOD

    //Activate new objects immediately and force a save on them
    if (TENANTTYPE_EMBEDDEDOBJECT==tType)
        {
        m_pTenantCur->Activate(OLEIVERB_SHOW);
        m_pTenantCur->Update();
        }

    return TRUE;
    }

/*
 * CPage::TenantDestroy
 *
 * Purpose:
 *  Destroys the currently selected tenant on this page.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPage::TenantDestroy(void)
    {
    if (NULL==m_pTenantCur)
        {
        MessageBeep(0);
        return FALSE;
        }

    SendMessage(m_hWndTenantList, LB_DELETESTRING
        , m_iTenantCur, 0L);

    m_pTenantCur->Invalidate();
    m_pTenantCur->Destroy(m_pIStorage);

    m_pTenantCur->Release();
    m_pTenantCur=NULL;

    //Update counts, etc., and select the next tenant in the list.
    if (m_iTenantCur==m_cTenants-1)
        m_iTenantCur--;

    if (0==--m_cTenants)
        m_pTenantCur=NULL;
    else
        {
        FTenantGet(m_iTenantCur, &m_pTenantCur, TRUE);
        m_pTenantCur->Select(TRUE);
        }

    UpdateWindow(m_hWnd);
    return TRUE;
    }

/*
 * CPage::TenantClip
 *
 * Purpose:
 *  Copies or cuts the currently selected tenant to the clipoard.
 *
 * Parameters:
 *  fCut            BOOL TRUE to cut the object, FALSE to copy.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPage::TenantClip(BOOL fCut)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet=FALSE;

    if (NULL==m_pTenantCur)
        return FALSE;

    /*
     * To perform a data transfer operation, we need to create a
     * data object with the selected object's data inside. To do
     * this we CoCreateInstance on CLSID_DataTransferObject
     * (Also implemented in this chapter), retrieve data from the
     * object we have, stuff that data into the transfer object,
     * then stick that object on the clipboard.
     *
     * Since we'll want an identical object at other times, like for
     * drag-drop, we use a private function to actually create it.
     */

    pIDataObject=TransferObjectCreate(NULL);

    if (NULL!=pIDataObject)
        {
        if (SUCCEEDED(OleSetClipboard(pIDataObject)))
            {
            if (fCut)
                TenantDestroy();

            fRet=TRUE;
            }

        pIDataObject->Release();
        }

    return fRet;
    }

/*
 * CPage::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected on this
 *  page for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU of the Edit menu.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPage::FQueryObjectSelected(HMENU hMenu)
    {
    HMENU       hMenuTemp;

    /*
     * This will only be called on WM_INITMENUPOPUP, we'll also
     * use this function to create the Verb menu for this object.
     */
    if (NULL!=m_pTenantCur)
        {
        m_pTenantCur->AddVerbMenu(hMenu, MENUPOS_OBJECT);
        return TRUE;
        }

    OleUIAddVerbMenu(NULL, NULL, hMenu, MENUPOS_OBJECT
        , IDM_VERBMIN, IDM_VERBMAX, FALSE, 0, &hMenuTemp);

    return FALSE;
    }

/*
 * CPage::ActivateObject
 *
 * Purpose:
 *  Executes a verb on the currently selected object.
 *
 * Parameters:
 *  iVerb           LONG of the selected verb.
 *
 * Return Value:
 *  None
 */

void CPage::ActivateObject(LONG iVerb)
    {
    if (NULL==m_pTenantCur)
        return;

    m_pTenantCur->Activate(iVerb);
    return;
    }

//CHAPTER12MOD

/*
 * CPage::ShowObjectTypes
 *
 * Purpose:
 *  Loops through all the tenants and tells each one to turn on or
 *  off the Show Objects features.
 *
 * Parameters:
 *  fShow           BOOL indicating to show the type or not.
 *
 * Return Value:
 *  None
 */

void CPage::ShowObjectTypes(BOOL fShow)
    {
    PCTenant    pTenant;
    UINT        i;

    for (i=0; i < m_cTenants; i++)
        {
        if (FTenantGet(i, &pTenant, FALSE))
            pTenant->ShowObjectType(fShow);
        }

    return;
    }

/*
 * CPage::NotifyTenantsOfRename
 *
 * Purpose:
 *  Loops through all the tenants and informs each of the new
 *  document name.
 *
 * Parameters:
 *  pszFile         LPTSTR of the new filename.
 *  pmk             LPMONKIER for the new filename.
 *
 * Return Value:
 *  None
 */

void CPage::NotifyTenantsOfRename(LPTSTR pszFile, LPMONIKER pmk)
    {
    PCTenant    pTenant;
    UINT        i;

    for (i=0; i < m_cTenants; i++)
        {
        if (FTenantGet(i, &pTenant, FALSE))
            pTenant->NotifyOfRename(pszFile, pmk);
        }

    return;
    }

/*
 * CPages::FQueryLinksInPage
 *
 * Purpose:
 *  Pass through to current page to see if there are any linked
 *  objects.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPage::FQueryLinksInPage()
    {
    PCTenant    pTenant;
    UINT        i;
    BOOL        fRet=FALSE;

    for (i=0; i < m_cTenants; i++)
        {
        if (FTenantGet(i, &pTenant, FALSE))
            fRet |= (pTenant->TypeGet()==TENANTTYPE_LINKEDOBJECT);
        }

    return fRet;
    }
//End CHAPTER12MOD

/*
 * CPages::FTenantGet
 * (Protected)
 *
 * Purpose:
 *  Returns a tenant of a given index returning a BOOL so it's
 *  simple to use this function inside if statements.
 *
 * Parameters:
 *  iTenant         UINT tenant to retrieve 0 based.
 *  ppTenant        PCPage * in which to return the tenant
 *                  pointer
 *  fOpen           BOOL indicating if we should open this
 *                  tenant as well.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPage::FTenantGet(UINT iTenant, PCTenant *ppTenant
    , BOOL fOpen)
    {
    if (NULL==ppTenant)
        return FALSE;

    if (LB_ERR!=SendMessage(m_hWndTenantList, LB_GETTEXT
        , iTenant, (LONG)ppTenant))
        {
        if (fOpen)
            (*ppTenant)->FOpen(m_pIStorage);

        return TRUE;
        }

    return FALSE;
    }

/*
 * CPage::FTenantAdd
 * (Protected)
 *
 * Purpose:
 *  Creates a new tenant initialized to the given values.  The new
 *  tenants's storage is created if it does not already exist.  If
 *  fOpenStorage is set the the tenants's storage is opened and left
 *  opened.
 *
 * Parameters:
 *  iTenant         UINT Location at which to insert tenant; new
 *                  tenant is inserted after this position.  NOVALUE
 *                  for the end.
 *  dwID            DWORD ID for this tenant.
 *  ppNew           PCTenant * in which to store the new tenant.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPage::FTenantAdd(UINT iTenant, DWORD dwID
    , PCTenant *ppNew)
    {
    PCTenant    pTenant;
    LRESULT     lr;

    if (NULL!=ppNew)
        *ppNew=NULL;

    pTenant=new CTenant(dwID, m_hWnd, m_pPG);

    if (NULL==pTenant)
        return FALSE;

    //The constructor doesn't AddRef, so we need to.
    pTenant->AddRef();

    //Now try to add to the listbox.
    lr=SendMessage(m_hWndTenantList, LB_INSERTSTRING, iTenant
        , (LONG)pTenant);

    if (lr < 0)
        {
        pTenant->Release();
        return FALSE;
        }

    *ppNew=pTenant;
    return TRUE;
    }

/*
 * CPage::TransferObjectCreate
 * (Protected)
 *
 * Purpose:
 *  Creates a DataTransferObject and stuff the current selection's
 *  data into it.
 *
 * Parameters:
 *  pptl            LPPOINTL containing the pick point in device
 *                  units applicable only to drag-drop; since
 *                  drag-drop is inherently mouse oriented, we use
 *                  device units for the point.  Ignored if NULL.
 *
 * Return Value:
 *  LPDATAOBJECT    Pointer to the object created, NULL on failure
 */

LPDATAOBJECT CPage::TransferObjectCreate(LPPOINTL pptl)
    {
    LPDATAOBJECT    pIDataObject;
    LPDATAOBJECT    pIDataT;
    PPATRONOBJECT   ppo;
    RECTL           rcl;
    LPUNKNOWN       pObj;
    FORMATETC       fe;
    STGMEDIUM       stm;
    HRESULT         hr;

    m_pTenantCur->ObjectGet(&pObj);

    hr=CoCreateInstance(CLSID_DataTransferObject, NULL
        , CLSCTX_INPROC_SERVER, IID_IDataObject
        , (PPVOID)&pIDataObject);

    if (FAILED(hr))
        return NULL;

    //Go get the data we should hold on to.
    hr=pObj->QueryInterface(IID_IDataObject, (PPVOID)&pIDataT);

    if (FAILED(hr))
        {
        pIDataObject->Release();
        pObj->Release();
        return NULL;
        }

    //Copy from known obj into transfer obj.  Ordering is important!

    //Generate placeable object structure
    stm.tymed=TYMED_HGLOBAL;
    stm.pUnkForRelease=NULL;
    stm.hGlobal=GlobalAlloc(GHND, sizeof(PATRONOBJECT));

    if (NULL==stm.hGlobal)
        {
        pIDataObject->Release();
        pObj->Release();
        return NULL;
        }

    ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);

    m_pTenantCur->SizeGet(&ppo->szl, FALSE);
    ppo->szl.cy=-ppo->szl.cy; //Negate to make absolute size

    m_pTenantCur->RectGet(&rcl, FALSE);
    ppo->ptl.x=rcl.left;
    ppo->ptl.y=rcl.top;

    if (NULL==pptl)
        {
        ppo->ptlPick.x=0;
        ppo->ptlPick.y=0;
        }
    else
        ppo->ptlPick=*pptl;

    m_pTenantCur->FormatEtcGet(&ppo->fe, FALSE);

    //CHAPTER12MOD
    //If this is a linked object, just copy a presentation
    if (TENANTTYPE_LINKEDOBJECT==m_pTenantCur->TypeGet())

m_pTenantCur->FormatEtcGet(&ppo->fe, TRUE);
    //End CHAPTER12MOD

    SETDefFormatEtc(fe, m_pPG->m_cf, TYMED_HGLOBAL);
    pIDataObject->SetData(&fe, &stm, TRUE);

    /*
     * Here now we have to include CF_EMBEDDEDOBJECT and
     * CF_OBJECTDESCRIPTOR when what we have selected is, in fact,
     * a compound document object.  We'll just ask the tenant to
     * set these in pIDataObject since it knows what the object.
     * If we copy embedded object data, make sure the PATRONOBJECT
     * structure has the right format in it as well.
     */
    m_pTenantCur->CopyEmbeddedObject(pIDataObject, &ppo->fe, pptl);

    GlobalUnlock(stm.hGlobal);

    //Copy the actual presentation.
    m_pTenantCur->FormatEtcGet(&fe, TRUE);
    pIDataT->GetData(&fe, &stm);
    pIDataObject->SetData(&fe, &stm, TRUE);

    pIDataT->Release();

    pObj->Release();
    return pIDataObject;    //Caller now responsible
    }


PAGEMOUS.CPP

/*
 * PAGEMOUS.CPP
 * Patron Chapter 12
 *
 * Implementation of mouse-related member functions of CPage.
 * The remainder is in PAGE.CPP.  This separate file keeps this
 * grungy hit-testing/drawing code out of our way.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

//Lookups into the array using g_rgHTCode[x+y*3] in PAGEMOUS.CPP
#define YTOP            0
#define YMID            1
#define YBOT            2
#define XLEFT           0
#define XMID            1
#define XRIGHT          2

//Values to restrict sizing in CPage::OnMouseMove
#define SIZINGTOP       0x0001
#define SIZINGBOTTOM    0x0002
#define SIZINGLEFT      0x0004
#define SIZINGRIGHT     0x0008

//This array is for hit-testing lookups
static UINT g_rgHTCode[9]={HTTOPLEFT, HTTOP, HTTOPRIGHT
    , HTLEFT, HTCLIENT, HTRIGHT, HTBOTTOMLEFT, HTBOTTOM
    , HTBOTTOMRIGHT};

//This is for restricting tracking based on the hit-test
static UINT g_rguSizingFlags[9]={SIZINGTOP | SIZINGLEFT, SIZINGTOP
    , SIZINGTOP | SIZINGRIGHT, SIZINGLEFT, 0, SIZINGRIGHT
    , SIZINGBOTTOM | SIZINGLEFT, SIZINGBOTTOM
    , SIZINGBOTTOM | SIZINGRIGHT};

/*
 * CPage::OnRightDown
 *
 * Purpose:
 *  Called when the user clicks with the right button on this
 *  page.  If there is an object here, determined by the last
 *  hit-test code, the we'll make a popup-menu for it.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if the action changed the object.
 */

BOOL CPage::OnRightDown(UINT uKeys, UINT x, UINT y)
    {
    HMENU       hMenu;
    HMENU       hMenuRes;
    HINSTANCE   hInst;
    HWND        hWndFrame, hWndT;
    POINT       pt;
    UINT        i, cItems;

    //Select the tenant under the mouse, if there is one.
    if (!FSelectTenantAtPoint(x, y))
        return FALSE;

    /*
     * Get the top-level window to which menu command will go.  This
     * will be whatever parent doesn't have a parent itself...
     */
    hWndT=GetParent(m_hWnd);

    while (NULL!=hWndT)
        {
        hWndFrame=hWndT;
        hWndT=GetParent(hWndT);
        }

    /*
     * Build a popup menu for this object with Cut, Copy, Delete,
     * and object verbs.
     */
    hInst=GETWINDOWINSTANCE(m_hWnd);    //Macro in BOOK1632.H
    hMenuRes=LoadMenu(hInst, MAKEINTRESOURCE(IDR_RIGHTPOPUPMENU));

    if (NULL==hMenuRes)
        return FALSE;

    hMenu=CreatePopupMenu();
    cItems=GetMenuItemCount(hMenuRes);

    for (i=0; i < cItems; i++)
        {
        TCHAR       szTemp[80];
        int         id, uFlags;

        GetMenuString(hMenuRes, i, szTemp, sizeof(szTemp)
            , MF_BYPOSITION);
        id=GetMenuItemID(hMenuRes, i);

        uFlags=(0==id) ? MF_SEPARATOR : MF_STRING | MF_ENABLED;
        AppendMenu(hMenu, uFlags, id, szTemp);
        }

    DestroyMenu(hMenuRes);

    //Munge the Object menu item
    m_pTenantCur->AddVerbMenu(hMenu, MENUPOS_OBJECTONPOPUP);

    //CHAPTER12MOD
    //Enable or disable the Links item.
    i=FQueryLinksInPage() ? MF_ENABLED : MF_DISABLED | MF_GRAYED;
    EnableMenuItem(hMenu, IDM_EDITLINKS, i | MF_BYCOMMAND);
    //End CHAPTER12MOD

    SETPOINT(pt, x, y);
    ClientToScreen(m_hWnd, &pt);

    TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON
        , pt.x, pt.y, 0, hWndFrame, NULL);

    DestroyMenu(hMenu);
    return FALSE;
    }

/*
 * CPage::FSelectTenantAtPoint
 *
 * Purpose:
 *  Selects whatever tenant is at the point (x,y) if there is one,
 *  deselecting the previously selected tenant.
 *
 * Parameters:
 *  x, y            UINT coordinates of the mouse.
 *
 * Return Value:
 *  BOOL            TRUE if there is a tenant here, FALSE otherwise.
 */

BOOL CPage::FSelectTenantAtPoint(UINT x, UINT y)
    {
    UINT            iTenant;
    PCTenant        pTenant;
    PCDocument      pDoc;

    iTenant=TenantFromPoint(x, y, &pTenant);

    if (NULL==pTenant)
        return FALSE;

    //Make the document window active in any case
    pDoc=(PCDocument)SendMessage(GetParent(m_hWnd), DOCM_PDOCUMENT
        , 0, 0L);

    if (NULL!=pDoc)
        BringWindowToTop(pDoc->Window());

    //If this one is already current, we might be now sizing.
    if (pTenant==m_pTenantCur)
        return TRUE;

    //Deselect the current tenant
    if (NULL!=m_pTenantCur)
        m_pTenantCur->Select(FALSE);

    //Move this tenant to the top of the list
    m_iTenantCur=0;

    SendMessage(m_hWndTenantList, LB_DELETESTRING, iTenant, 0L);
    SendMessage(m_hWndTenantList, LB_INSERTSTRING, 0
        , (LONG)pTenant);

    //Select and repaint the new tenant to show it up front
    m_pTenantCur=pTenant;

    m_pTenantCur->Repaint();
    m_pTenantCur->Select(TRUE);

    return TRUE;
    }

/*
 * CPage::OnLeftDown
 *
 * Purpose:
 *  Called when the user clicks with the left button on this page.
 *  We find the object under that position that is visibly on top
 *  (always the first one under this location in the page list since
 *  we paint in reverse order) and select it.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if the action changed the object.
 */

BOOL CPage::OnLeftDown(UINT uKeys, UINT x, UINT y)
    {
    /*
     * If the mouse is in a position to start dragging,
     * start the timer as with sizing below.
     */
    if (HTCAPTION==m_uHTCode)
        {
        m_fDragPending=TRUE;

        //Save down point and start timer.
        m_ptDown.x=x;
        m_ptDown.y=y;

        m_uKeysDown=uKeys;

        m_fTimer=TRUE;
        SetTimer(m_hWnd, IDTIMER_DEBOUNCE, m_cDelay, NULL);
        return FALSE;
        }

    /*
     * If the mouse is in a position to start sizing, start
     * the debounce timer and note the condition.  The sizing
     * will start in OnTimer or OnMouseMove.  This will always
     * happen on the currently selected tenant, and m_uHTCode is
     * set in OnNCHitTest below.
     */
    if (HTNOWHERE!=m_uHTCode && HTCLIENT!=m_uHTCode)
        {
        m_fSizePending=TRUE;

        //Save down point and start timer.
        m_ptDown.x=x;
        m_ptDown.y=y;

        m_fTimer=TRUE;
        SetTimer(m_hWnd, IDTIMER_DEBOUNCE, m_cDelay, NULL);
        return FALSE;
        }

    FSelectTenantAtPoint(x, y);
    return FALSE;
    }

/*
 * CPage::OnLeftUp
 *
 * Purpose:
 *  Called when the user clicks up with the left button on this
 *  page. We stop tracking on this message, if necessary, and
 *  resize the object.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if this action changed the object.
 */

BOOL CPage::OnLeftUp(UINT uKeys, UINT x, UINT y)
    {
    RECT    rc, rcT;

    if (m_fSizePending || m_fDragPending)
        {
        m_fSizePending=FALSE;
        m_fDragPending=FALSE;

        if (m_fTimer)
            {
            KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
            m_fTimer=FALSE;
            }

        return FALSE;
        }

    if (!m_fTracking)
        return FALSE;

    //Remove the dotted rectangle.
    RECTFROMRECTL(rc, m_rcl)
    DrawFocusRect(m_hDC, &rc);
    ReleaseDC(m_hWnd, m_hDC);

    ReleaseCapture();
    m_fTracking=FALSE;

    //If the original and new rects are the same, nothing happened.
    RECTFROMRECTL(rcT, m_rclOrg);

    if (EqualRect(&rc, &rcT))
        return FALSE;

    RECTFROMRECTL(rcT, m_rclOrg);
    InvalidateRect(m_hWnd, &rcT, TRUE);

    //Invalidate on the screen before accounting for scrolling
    InvalidateRect(m_hWnd, &rc, TRUE);

    //Factor in scrolling and tell the tenant where it now stands.
    OffsetRect(&rc, (int)m_pPG->m_xPos, (int)m_pPG->m_yPos);
    RECTLFROMRECT(m_rcl, rc);
    m_pTenantCur->RectSet(&m_rcl, TRUE, TRUE);

    UpdateWindow(m_hWnd);
    return TRUE;
    }

/*
 * CPage::OnLeftDoubleClick
 *
 * Purpose:
 *  Called when the user double-clicks with the left button on this
 *  page.  We find the object under that position that is visibly on
 *  top (always the first one under this location in the page list
 *  since we paint in reverse order) and activate it.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if the action changed the object.
 */

BOOL CPage::OnLeftDoubleClick(UINT uKeys, UINT x, UINT y)
    {
    /*
     * The current tenant is the only one that can be activated, so
     * we just have to make sure the mouse is there.  For that we
     * can use the last hit-test code we saw since it's updated on
     * every mouse move.
     */

    if (HTNOWHERE!=m_uHTCode)
        return m_pTenantCur->Activate(OLEIVERB_PRIMARY);

    return FALSE;
    }

/*
 * CPage::OnMouseMove
 *
 * Purpose:
 *  Processes WM_MOUSEMOVE on a page so we can handle tracking
 *  resize of a tenant.
 *
 * Parameters:
 *  x, y            int device coordinates to check.
 *
 * Return Value:
 *  None
 */

void CPage::OnMouseMove(UINT uKeys, int x, int y)
    {
    RECT        rc, rcO, rcB;
    int         cxy;

    if (m_fSizePending || m_fDragPending)
        {
        int     dx, dy;

        dx=(x > m_ptDown.x) ? (x-m_ptDown.x) : (m_ptDown.x-x);
        dy=(y > m_ptDown.y) ? (y-m_ptDown.y) : (m_ptDown.y-y);

        /*
         * Has the mouse moved outside the debounce distance?  If
         * so, we can start sizing.  Note that this happens
         * regardless of the timer state.
         */
        if (dx > m_cxyDist || dy > m_cxyDist)
            {
            POINT       pt;
            BOOL        fSize=m_fSizePending;
            BOOL        fDrag=m_fDragPending;

            m_fSizePending=FALSE;
            m_fDragPending=FALSE;

            if (m_fTimer)
                {
                KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
                m_fTimer=FALSE;
                }

            if (fDrag)
                {
                //Set dirty flag if drag & drop changed things.
                m_pPG->m_fDirty |= DragDrop(m_uKeysDown, x, y);
                return;
                }

            if (fSize)
                StartSizeTracking();

            /*
             * Since we might have moved out of the sizing handle
             * in order to start the operation, we need to set the
             * m_uSizingFlags field based on the original down point
             * for subsequent mouse moves to function properly.
             * Note that OnNCHitTest expects screen coordinates.
             */
            SETPOINT(pt, m_ptDown.x, m_ptDown.y);
            ClientToScreen(m_hWnd, &pt);
            OnNCHitTest(pt.x, pt.y);
            OnSetCursor(m_uHTCode);
            return;
            }
        }

    if (!m_fTracking)
        return;

    //Get rid of the old rectangle.
    RECTFROMRECTL(rc, m_rcl)
    DrawFocusRect(m_hDC, &rc);

    /*
     * Calculate the new.  The flags in m_uSizingFlags tell us what
     * to change.  We limit the object by the page margins and a
     * minimum size of 3*CXYHANDLE in either dimension.
     */
    cxy=3*CXYHANDLE;

    RECTFROMRECTL(rcO, m_rclOrg);
    RECTFROMRECTL(rcB, m_rclBounds);

    if (m_uSizingFlags & SIZINGTOP)
        {
        if (y >= rcO.bottom-cxy)
            y=rcO.bottom-cxy;

        if (y <= rcB.top)           //Limit to top of page.
            y=rcB.top;

        m_rcl.top=y;
        }

    if (m_uSizingFlags & SIZINGBOTTOM)
        {
        if (y <= rcO.top+cxy)
            y=rcO.top+cxy;

        if (y >= rcB.bottom)         //Limit to bottom of page.
            y=rcB.bottom;

        m_rcl.bottom=y;
        }

    if (m_uSizingFlags & SIZINGLEFT)
        {
        if (x >= rcO.right-cxy)
            x=rcO.right-cxy;

        if (x <= rcB.left)           //Limit to left of page.
            x=rcB.left;

        m_rcl.left=x;
        }

    if (m_uSizingFlags & SIZINGRIGHT)
        {
        if (x <= rcO.left+cxy)
            x=rcO.left+cxy;

        if (x >= rcB.right)          //Limit to right of page.
            x=rcB.right;

        m_rcl.right=x;
        }

    //Draw the new
    RECTFROMRECTL(rc, m_rcl)
    DrawFocusRect(m_hDC, &rc);

    return;
    }

/*
 * CPage::OnTimer
 *
 * Purpose:
 *  Processes WM_TIMER messages to a page used to perform mouse
 *  debouncing.
 *
 * Parameters:
 *  uID             UINT timer ID.
 *
 * Return Value:
 *  None
 */

void CPage::OnTimer(UINT uID)
    {
    if (m_fSizePending || m_fDragPending)
        {
        BOOL        fSize=m_fSizePending;
        BOOL        fDrag=m_fDragPending;

        /*
         * Having this function called means the delay requirement
         * is satisfied.  Start tracking for sizing or dragging.
         */

        m_fSizePending=FALSE;
        m_fDragPending=FALSE;

        KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
        m_fTimer=FALSE;

        if (fDrag)
            {
            POINT       pt;

            GetCursorPos(&pt);
            m_pPG->m_fDirty |= DragDrop(m_uKeysDown
                , m_ptDown.x, m_ptDown.y);
            return;
            }

        if (fSize)
            StartSizeTracking();
        }

    return;
    }

/*
 * CPage::StartSizeTracking
 *
 * Purpose:
 *  Begins sizing of a tenant when mouse debounce conditions are
 *  met.
 *
 * Parameters:
 *  uID             UINT timer ID.
 *
 * Return Value:
 *  None
 */

void CPage::StartSizeTracking(void)
    {
    RECT        rc;

    m_pTenantCur->RectGet(&m_rcl, TRUE);
    SetCapture(m_hWnd);
    m_fTracking=TRUE;

    m_hDC=GetDC(m_hWnd);

    //Place the rectangle exactly where it is on the screen.
    RECTFROMRECTL(rc, m_rcl)
    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);
    RECTLFROMRECT(m_rcl, rc);
    m_rclOrg=m_rcl;

    DrawFocusRect(m_hDC, &rc);

    m_pPG->CalcBoundingRect(&rc, TRUE);
    RECTLFROMRECT(m_rclBounds, rc);
    return;
    }

/*
 * CPage::OnNCHitTest
 *
 * Purpose:
 *  Processes WM_NCHITTEST on a page so we can check for hits on the
 *  handles of the selected object for resizing.  We only save
 *  information for ourselves and do not interfere with normal
 *  hit-testing.
 *
 * Parameters:
 *  x, y            UINT device coordinates to check.
 *
 * Return Value:
 *  None
 */

void CPage::OnNCHitTest(UINT x, UINT y)
    {
    RECT        rc;
    RECTL       rcl;
    int         iMid1, iMid2;
    int         xHit, yHit;
    POINT       pt;
    int         x0, y0;

    //Default: don't start sizing on a click, don't hit an object.
    m_uSizingFlags=0;
    m_uHTCode=HTNOWHERE;

    if (NULL==m_pTenantCur)
        return;

    //Convert device points to our coordinates
    m_pTenantCur->RectGet(&rcl, FALSE);
    RECTFROMRECTL(rc, rcl);
    RectConvertMappings(&rc, NULL, TRUE);
    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

    SETPOINT(pt, x, y);
    ScreenToClient(m_hWnd, &pt);
    x0=pt.x;
    y0=pt.y;

    if (x0 < rc.left || x0 > rc.right)
        return;

    if (y0 < rc.top || y0 > rc.bottom)
        return;

    //It's at least in the object.
    m_uHTCode=HTCLIENT;

    //Check for hits in horizontal regions
    xHit=NOVALUE;
    iMid1=rc.left+((rc.right-rc.left-CXYHANDLE) >> 1);
    iMid2=rc.left+((rc.right-rc.left+CXYHANDLE) >> 1);

    if (x0 >= rc.left && x0 <= rc.left+CXYHANDLE)
        xHit=XLEFT;
    else if (x0 >= iMid1 && x0 <= iMid2)
        xHit=XMID;
    else if (x0 >= rc.right-CXYHANDLE && x0 <= rc.right)
        xHit=XRIGHT;

    //Don't exit yet if we didn't hit a handle--might hit a y edge.

    //Check for hits in vertical regions
    yHit=NOVALUE;
    iMid1=rc.top+((rc.bottom-rc.top-CXYHANDLE) >> 1);
    iMid2=rc.top+((rc.bottom-rc.top+CXYHANDLE) >> 1);

    if (y0 >= rc.top && y0 <= rc.top+CXYHANDLE)
        yHit=YTOP;
    else if (y0 >= iMid1 && y0 <= iMid2)
        yHit=YMID;
    else if (y0 >= rc.bottom-CXYHANDLE && y0 <= rc.bottom)
        yHit=YBOT;

    /*
     * If we hit any edge, but didn't hit a handle, then one of xHit
     * and yHit will be NOVALUE and the other something else.  When
     * we hit an edge on the 'something else' then we're on a drag
     * point.
     */

    if ((NOVALUE==xHit && NOVALUE==yHit)
        || (XMID==xHit && YMID==yHit)
        || (NOVALUE==xHit && YMID==yHit)
        || (XMID==xHit && NOVALUE==yHit))
        return;

    if ((NOVALUE==xHit && (YTOP==yHit || YBOT==yHit))
        || ((XLEFT==xHit || XRIGHT==xHit) && NOVALUE==yHit))
        {
        m_uHTCode=HTCAPTION;
        return;
        }

    //We hit a handle, so save our HT code
    m_uSizingFlags=g_rguSizingFlags[xHit+(yHit*3)];
    m_uHTCode=g_rgHTCode[xHit+(yHit*3)];
    return;
    }

/*
 * CPage::SetCursor
 *
 * Purpose:
 *  Processes WM_SETCURSOR using the code from OnNCHitTest.
 *
 * Parameters:
 *  x, y            UINT device coordinates to check.
 *
 * Return Value:
 *  LRESULT         HT* code for Windows.
 */

BOOL CPage::OnSetCursor(UINT uHTCode)
    {
    HCURSOR     hCur;
    UINT        iCur;

    /*
     * We really just ignore uHTCode and use the one we saved
     * in OnNCHitTest.
     */

    switch (m_uHTCode)
        {
        case HTTOP:
        case HTBOTTOM:
            iCur=IDC_VARROWS;
            break;

        case HTLEFT:
        case HTRIGHT:
            iCur=IDC_HARROWS;
            break;

        case HTTOPLEFT:
        case HTBOTTOMRIGHT:
            iCur=IDC_NWSEARROWS;
            break;

        case HTTOPRIGHT:
        case HTBOTTOMLEFT:
            iCur=IDC_NESWARROWS;
            break;

        case HTCAPTION:
            iCur=IDC_SMALLARROWS;
            break;

        default:
            return FALSE;
        }

    hCur=UICursorLoad(iCur);
    SetCursor(hCur);

    return TRUE;
    }

/*
 * CPage::TenantFromPoint
 * (Protected)
 *
 * Purpose:
 *  Finds the tenant under the given device coordinates on this
 *  page.
 *
 * Parmeters:
 *  x, y            UINT coordinates.
 *  ppTenant        PCTenant * in which to return the pointer.
 *
 * Return Value:
 *  UINT            Index of the matched tenant, NOVALUE if not
 *                  found.
 */

UINT CPage::TenantFromPoint(UINT x, UINT y, PCTenant *ppTenant)
    {
    PCTenant    pTenant;
    RECTL       rcl;
    UINT        i;
    int         x0, y0;

    x0=x+m_pPG->m_xPos;
    y0=y+m_pPG->m_yPos;

    for (i=0; i < m_cTenants; i++)
        {
        if (!FTenantGet(i, &pTenant, FALSE))
            continue;

        pTenant->RectGet(&rcl, TRUE);

        //Essentially Perform PointInRECTL
        if (x0 >= rcl.left && x0 <= rcl.right)
            {
            if (y0 <=rcl.bottom && y0 >=rcl.top)
                {
                *ppTenant=pTenant;
                return i;
                }
            }
        }

    *ppTenant=NULL;
    return NOVALUE;
    }

/*
 * CPage::DragDrop
 *
 * Purpose:
 *  Performs drag-drop operations from the page window
 *
 * Parmeters:
 *  uKeys           UINT state of the keyboard
 *  x, y            UINT mouse coordinates of the starting click.
 *
 * Return Value:
 *  BOOL            TRUE if we modified the page, FALSE otherwise.
 */

BOOL CPage::DragDrop(UINT uKeys, UINT x, UINT y)
    {
    LPDROPSOURCE    pIDropSource;
    LPDATAOBJECT    pIDataObject;
    HRESULT         hr;
    DWORD           dwEffect;
    POINTL          ptl;
    SIZEL           szl;
    RECTL           rcl;
    RECT            rc, rcT;

    pIDropSource=new CDropSource();

    if (NULL==pIDropSource)
        return FALSE;

    pIDropSource->AddRef();
    m_pPG->m_fDragSource=TRUE;

    /*
     * Store a pick point with the data indicating the offset from
     * the upper left of the rectangle where we grabbed it.  This is
     * so the UI feedback in IDropTarget lines up with this tenant.
     */

    m_pTenantCur->RectGet(&rcl, TRUE);
    ptl.x=x+m_pPG->m_xPos-rcl.left;
    ptl.y=y+m_pPG->m_yPos-rcl.top;
    pIDataObject=TransferObjectCreate(&ptl);

    if (NULL==pIDataObject)
        {
        pIDropSource->Release();
        return FALSE;
        }

    m_pPG->m_fMoveInPage=FALSE;

    dwEffect=DROPEFFECT_COPY | DROPEFFECT_MOVE;
    hr=DoDragDrop(pIDataObject, pIDropSource
        , DROPEFFECT_COPY | DROPEFFECT_MOVE, &dwEffect);

    pIDataObject->Release();
    pIDropSource->Release();

    m_pPG->m_fDragSource=FALSE;

    //No drop-no action.
    if (DRAGDROP_S_DROP!=hr || DROPEFFECT_NONE==dwEffect)
        return FALSE;

    /*
     * If m_pPG->m_fMoveInPage is set, then we just change the
     * coordinates on m_pTenantCur and we're done.
     */
    if (m_pPG->m_fMoveInPage)
        {
        m_pTenantCur->Invalidate();

        /*
         * Clip to page boundaries.  We know that ptDrop has to be
         * in the page somewhere or we would not have dropped
         * (effect was NONE).  So first make sure that ptDrop is
         * within 3*CXYHANDLE of the right or bottom, and if so,
         * pull it out to 3*CXYHANDLE.  Then we can just clip the
         * size to the page rectangle and we'll always be sure to
         * have at least a sizeable object.
         */
        m_pTenantCur->SizeGet(&szl, TRUE);
        SetRect(&rc, (int)m_pPG->m_ptDrop.x, (int)m_pPG->m_ptDrop.y
            , 0, 0);
        RectConvertMappings(&rc, NULL, TRUE);

        m_pPG->CalcBoundingRect(&rcT, FALSE);
        OffsetRect(&rcT, (int)m_pPG->m_xPos, (int)m_pPG->m_yPos);

        if (rc.left >= rcT.right-3*CXYHANDLE)
            rc.left=rcT.right-3*CXYHANDLE;

        if (rc.top >= rcT.bottom-3*CXYHANDLE)
            rc.top=rcT.bottom-3*CXYHANDLE;

        rc.right=rc.left+(int)szl.cx;
        rc.bottom=rc.top+(int)szl.cy;
        IntersectRect(&rc, &rc, &rcT);

        RECTLFROMRECT(rcl, rc);

        m_pTenantCur->RectSet(&rcl, TRUE, FALSE);
        m_pTenantCur->Repaint();
        return TRUE;
        }

    /*
     * Otherwise we may have to delete the old tenant if the effect
     * was move.  This will not happen in the move in page case.
     */

    if (DROPEFFECT_MOVE==dwEffect)
        {
        TenantDestroy();
        return TRUE;
        }

    //Copy is a clean operation
    return FALSE;
    }


PAGES.CPP

/*
 * PAGES.CPP
 * Patron Chapter 12
 *
 * Implementation of the CPages class.  See PAGEWIN.CPP for
 * additional member functions.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPages:CPages
 * CPages::~CPages
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application we're in.
 *  cf              UINT application clipboard format.
 */

CPages::CPages(HINSTANCE hInst, UINT cf)
    : CWindow(hInst)
    {
    m_pPageCur=NULL;
    m_iPageCur=NOVALUE;  //Pages are 0 indexed, this is one before
    m_cPages=0;
    m_hWndPageList=NULL;
    m_fSystemFont=FALSE;

    //Initialize to 8.5*11 inch with .25 inch margins as a default.
    m_cx=(LOMETRIC_PER_INCH*17)/2;
    m_cy=LOMETRIC_PER_INCH*11;

    m_xMarginLeft=LOMETRIC_PER_INCH/4;
    m_xMarginRight=LOMETRIC_PER_INCH/4;
    m_yMarginTop=LOMETRIC_PER_INCH/4;
    m_yMarginBottom=LOMETRIC_PER_INCH/4;

    m_xPos=0L;
    m_yPos=0L;

    m_dwIDNext=0;
    m_pIStorage=NULL;

    m_fDirty=FALSE;
    m_cf=cf;

    m_fDragSource=FALSE;
    m_fMoveInPage=FALSE;
    //CHAPTER12MOD
    m_fLinkAllowed=FALSE;
    //End CHAPTER12MOD

    m_fDragRectShown=FALSE;

    m_uScrollInset=GetProfileInt(TEXT("windows")
        , TEXT("DragScrollInset"), DD_DEFSCROLLINSET);

    m_uScrollDelay=GetProfileInt(TEXT("windows")
        , TEXT("DragScrollDelay"), DD_DEFSCROLLDELAY);

    m_uHScrollCode=0;
    m_uVScrollCode=0;

    //CHAPTER12MOD
    m_fShowTypes=FALSE;
    //End CHAPTER12MOD

    return;
    }

CPages::~CPages(void)
    {
    //Ensure memory cleaned up in list; do final IStorage::Release
    FIStorageSet(NULL, FALSE, FALSE);

    if (NULL!=m_hFont && !m_fSystemFont)
        DeleteObject(m_hFont);

    if (NULL!=m_hWndPageList)
        DestroyWindow(m_hWndPageList);

    //m_hWnd destroyed with the document.
    return;
    }

/*
 * CPages::FIsDirty
 *
 * Purpose:
 *  Tells the caller (document) if anything's happened to dirty us.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPages::FIsDirty(void)
    {
    return m_fDirty;
    }

/*
 * CPages::FInit
 *
 * Purpose:
 *  Instantiates a pages window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags.
 *                  Should contain WS_CHILD | WS_VISIBLE in
 *                  typical circumstances.
 *  uID             UINT ID to associate with this window
 *  pv              LPVOID unused for now.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPages::FInit(HWND hWndParent, LPRECT pRect, DWORD dwStyle
    , UINT uID, LPVOID pv)
    {
    int     cy;

    m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY, SZCLASSPAGES
        , SZCLASSPAGES, dwStyle, pRect->left, pRect->top
        , pRect->right-pRect->left, pRect->bottom-pRect->top
        , hWndParent, (HMENU)uID, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    /*
     * Create the hidden listbox we'll use to track pages.  We give
     * it the owner-draw style so we can just store pointers in it.
     * We have to set the parent to NULL such that the window hangs
     * around after the pages window is destroyed so that we can
     * clean up the memory stored in it from the CPages destructor.
     */
    m_hWndPageList=CreateWindow(TEXT("listbox"), TEXT("Page List")
        , WS_POPUP | LBS_OWNERDRAWFIXED, 0, 0, 100, 100
        , HWND_DESKTOP, NULL, m_hInst, NULL);

    if (NULL==m_hWndPageList)
        return FALSE;

    //Create a 14 point Arial font, or use the system variable font.
    cy=MulDiv(-14, LOMETRIC_PER_INCH, 72);
    m_hFont=CreateFont(cy, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE
        , ANSI_CHARSET, OUT_TT_PRECIS, CLIP_TT_ALWAYS, PROOF_QUALITY
        , VARIABLE_PITCH | FF_SWISS, TEXT("Arial"));

    if (NULL==m_hFont)
        {
        m_hFont=(HFONT)GetStockObject(ANSI_VAR_FONT);
        m_fSystemFont=TRUE;
        }

    return TRUE;
    }

/*
 * CPages::FIStorageSet
 *
 * Purpose:
 *  Provides the document's IStorage to the pages for its own use.
 *  If this is a new storage, we initalize it with streams that we
 *  want to always exists.  If this is an open, then we create
 *  our page list from the PageList string we wrote before.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE to the new or opened storage.  If
 *                  NULL, we just clean up and exit.
 *  fChange         BOOL indicating is this was a Save As operation,
 *                  meaning that we have the structure already and
 *                  just need to change the value of m_pIStorage.
 *  fInitNew        BOOL indicating if this is a new storage or one
 *                  opened from a previous save.
 *
 * Return Value:
 *  None
 */

BOOL CPages::FIStorageSet(LPSTORAGE pIStorage, BOOL fChange
    , BOOL fInitNew)
    {
    DWORD           dwMode=STGM_DIRECT | STGM_READWRITE
                        | STGM_SHARE_EXCLUSIVE;
    HRESULT         hr;
    PCPage          pPage;
    BOOL            fRet=FALSE;
    ULONG           cbRead;
    PAGELIST        pgList;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    LPDWORD         pdwID;
    UINT            i;

    //If we're changing saved roots, simply open current page again
    if (fChange)
        {
        if (NULL==pIStorage)
            return FALSE;

        m_pIStorage->Release();
        m_pIStorage=pIStorage;
        m_pIStorage->AddRef();

        FPageGet(m_iPageCur, &m_pPageCur, TRUE);
        return TRUE;
        }

    if (NULL!=m_hWndPageList)
        {
        //On new or open, clean out whatever it is we have.
        for (i=0; i < m_cPages; i++)
            {
            if (FPageGet(i, &pPage, FALSE))
                delete pPage;
            }

        SendMessage(m_hWndPageList, LB_RESETCONTENT, 0, 0L);
        }

    if (NULL!=m_pIStorage)
        m_pIStorage->Release();

    m_pIStorage=NULL;

    //If we're just cleaning up, then we're done.
    if (NULL==pIStorage)
        return TRUE;

    m_pIStorage=pIStorage;
    m_pIStorage->AddRef();

    //If this is a new storage, create the streams we require
    if (fInitNew)
        {
        //Page list header.
        hr=m_pIStorage->CreateStream(SZSTREAMPAGELIST, dwMode
            | STGM_CREATE, 0, 0, &pIStream);

        if (FAILED(hr))
            return FALSE;

        pIStream->Release();

        //Device Configuration
        hr=m_pIStorage->CreateStream(SZSTREAMDEVICECONFIG, dwMode
            | STGM_CREATE, 0, 0, &pIStream);

        if (FAILED(hr))
            return FALSE;

        pIStream->Release();
        return TRUE;
        }

    /*
     * We're opening an existing file:
     *  1)  Configure for the device we're on
     *  2)  Read the Page List and create page entries for each.
     */

    ConfigureForDevice();

    //Read the page list.
    hr=m_pIStorage->OpenStream(SZSTREAMPAGELIST, NULL, dwMode, 0
        , &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pIStream->Read(&pgList, sizeof(PAGELIST), &cbRead);
        m_cPages  =(UINT)pgList.cPages;
        m_iPageCur=(UINT)pgList.iPageCur;
        m_dwIDNext=pgList.dwIDNext;

        fRet=TRUE;
        cbRead=pgList.cPages*sizeof(DWORD);

        if (0!=cbRead)
            {
            pdwID=(LPDWORD)pIMalloc->Alloc(cbRead);

            if (NULL!=pdwID)
                {
                pIStream->Read(pdwID, cbRead, &cbRead);

                for (i=0; i < m_cPages; i++)
                    fRet &=FPageAdd(NOVALUE, *(pdwID+i), FALSE);

                pIMalloc->Free(pdwID);
                }
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    if (!fRet)
        return FALSE;

    FPageGet(m_iPageCur, &m_pPageCur, TRUE);

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);

    return TRUE;
    }

/*
 * CPages::FIStorageUpdate
 *
 * Purpose:
 *  Insures that all pages are committed before a root save.
 *
 * Parameters:
 *  fCloseAll       BOOL directing us to close all open storages
 *                  and streams.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::FIStorageUpdate(BOOL fCloseAll)
    {
    PCPage          pPage;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    LPDWORD         pdwID;
    ULONG           cb;
    HRESULT         hr;
    PAGELIST        pgList;
    BOOL            fRet=FALSE;
    UINT            i;

    //We only need to close the current page--nothing else is open.
    if (NULL!=m_pPageCur)
        {
        m_pPageCur->Update();

        if (fCloseAll)
            m_pPageCur->Close(FALSE);
        }

    //We don't hold anything else open, so write the page list.
    hr=m_pIStorage->OpenStream(SZSTREAMPAGELIST, NULL, STGM_DIRECT
        | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pgList.cPages=m_cPages;
        pgList.iPageCur=m_iPageCur;
        pgList.dwIDNext=m_dwIDNext;

        pIStream->Write(&pgList, sizeof(PAGELIST), &cb);

        cb=m_cPages*sizeof(DWORD);
        pdwID=(LPDWORD)pIMalloc->Alloc(cb);

        if (NULL!=pdwID)
            {
            for (i=0; i < m_cPages; i++)
                {
                FPageGet(i, &pPage, FALSE);
                *(pdwID+i)=pPage->GetID();
                }

            pIStream->Write(pdwID, cb, &cb);
            pIMalloc->Free(pdwID);
            fRet=TRUE;
            }
        pIMalloc->Release();
        }

    pIStream->Release();

    //Don't forget to clean up the dirty flag when we do an update.
    m_fDirty=!fRet;
    return fRet;
    }

/*
 * CPages::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Pages window in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  None
 */

void CPages::RectGet(LPRECT pRect)
    {
    RECT        rc;
    POINT       pt;

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_hWnd, &rc);
    SETPOINT(pt, rc.left, rc.top);
    ScreenToClient(GetParent(m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return;
    }

/*
 * CPages::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Pages window which sizes to fit.
 *  Coordinates are given in parent terms.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPages::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    if (NULL==pRect)
        return;

    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, pRect->left, pRect->top
        , (UINT)cx, (UINT)cy, SWP_NOZORDER);

    UpdateScrollRanges();
    return;
    }

/*
 * CPages::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the pages window in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  None
 */

void CPages::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return;
    }

/*
 * CPages::SizeSet
 *
 * Purpose:
 *  Sets a new size in parent coordinates for the Pages window.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPages::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    if (NULL==pRect)
        return;

    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
        , SWP_NOMOVE | SWP_NOZORDER);

    UpdateScrollRanges();
    return;
    }

/*
 * CPages::PageInsert
 *
 * Purpose:
 *  Creates a new page immediately after the current page.  If
 *  there are no pages then this creates page 1.
 *
 * Parameters:
 *  uReserved       UINT unused
 *
 * Return Value:
 *  UINT            Index of the new page, 0 on failure.
 */

UINT CPages::PageInsert(UINT uReserved)
    {
    if (0!=m_cPages && NULL!=m_pPageCur)
        {
        //Close the current page, committing changes.
        m_pPageCur->Close(TRUE);
        }

    //Create and open the new page.
    if (!FPageAdd(m_iPageCur, m_dwIDNext, TRUE))
        return 0;

    m_dwIDNext++;
    m_iPageCur++;
    m_cPages++;

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);

    FPageGet(m_iPageCur, &m_pPageCur, FALSE);
    return m_iPageCur;
    }

/*
 * CPages::PageDelete
 *
 * Removes the current page from the page list.
 *
 * Parameters:
 *  uReserved       UINT unused
 *
 * Return Value:
 *  UINT            Index to the now current page from the page
 *                  list, NOVALUE on error.
 */

UINT CPages::PageDelete(UINT uReserved)
    {
    PCPage      pPage;

    if (!FPageGet(m_iPageCur, &pPage, FALSE))
        return NOVALUE;

    //Delete the page in both the storage and in memory.
    SendMessage(m_hWndPageList, LB_DELETESTRING, m_iPageCur, 0L);

    m_pPageCur->Destroy(m_pIStorage);

    delete m_pPageCur;   //Does final pPage->Close
    m_pPageCur=NULL;

    /*
     * If this is the last page then the current is one less.  If
     * it's the only page the current is zero.  Otherwise the
     * current is the next page.
     */

    if (m_iPageCur==m_cPages-1)   //Covers last or only page.
        m_iPageCur--;

    m_cPages--;

    //Insure the new visible page is open.
    if (0!=m_cPages)
        {
        FPageGet(m_iPageCur, &m_pPageCur, TRUE);
        InvalidateRect(m_hWnd, NULL, FALSE);
        }
    else
        InvalidateRect(m_hWnd, NULL, TRUE);

    UpdateWindow(m_hWnd);
    return m_iPageCur;
    }

/*
 * CPages::CurPageGet
 *
 * Purpose:
 *  Retrieves the index of the current page we're viewing.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the current page.
 */

UINT CPages::CurPageGet(void)
    {
    return m_iPageCur;
    }

/*
 * CPages::CurPageSet
 *
 * Purpose:
 *  Sets the index of the current page to view.
 *
 * Parameters:
 *  iPage           UINT index of the page to view. 0 means first
 *                  page, NOVALUE means last page.
 *
 * Return Value:
 *  UINT            Index of the previous current page, NOVALUE on
 *                  error.
 */

UINT CPages::CurPageSet(UINT iPage)
    {
    UINT    iPageNew;
    UINT    iPagePrev=m_iPageCur;
    PCPage  pPage;

    switch (iPage)
        {
        case 0:
            iPageNew=0;
            break;

        case NOVALUE:
            iPageNew=m_cPages-1;
            break;

        default:
            if (iPage >= m_cPages)
                iPage=0;

            iPageNew=iPage;
            break;
        }

    //No reason to switch to the same page.
    if (iPagePrev==iPageNew)
        return iPage;

    //Close the old page committing changes.
    if (!FPageGet(iPagePrev, &pPage, FALSE))
        return NOVALUE;

    pPage->Close(TRUE);

    m_iPageCur=iPageNew;

    //Open the new page.
    FPageGet(m_iPageCur, &m_pPageCur, TRUE);

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);
    return iPagePrev;
    }

/*
 * CPages::NumPagesGet
 *
 * Purpose:
 *  Returns the number of pages this object current contains.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of pages we contain.
 */

UINT CPages::NumPagesGet(void)
    {
    return m_cPages;
    }

/*
 * CPages::DevModeSet
 *
 * Purpose:
 *  Provides the Pages with the current printer information.
 *
 * Parameters:
 *  hDevMode        HGLOBAL to the memory containing the DEVMODE.
 *                  This function assumes responsibility for this
 *                  handle.
 *  hDevNames       HGLOBAL providing the driver name and device
 *                  name from which we can create a DC for
 *                  information.
 *
 * Return Value:
 *  BOOL            TRUE if we could accept this configuration,
 *                  FALSE otherwise.  If we return TRUE we also
 *                  delete the old memory we hold.
 */

BOOL CPages::DevModeSet(HGLOBAL hDevMode, HGLOBAL hDevNames)
    {
    LPDEVNAMES      pdn;
    LPTSTR          psz;
    HGLOBAL         hMem;
    PDEVICECONFIG   pdc;
    LPDEVMODE       pdm;
    LPSTREAM        pIStream;
    HRESULT         hr;
    ULONG           cbDevMode, cbWrite;
    BOOL            fRet=FALSE;

    if (NULL==hDevMode || NULL==hDevNames)
        return FALSE;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    /*
     * DEVMODE is variable length--total length in hDevMode, so the
     * amount to write is that plus string space.  We subtract
     * sizeof(DEVMODE) as that is already included from GlobalSize.
     */
    cbDevMode=GlobalSize(hDevMode);
    cbWrite=cbDevMode+sizeof(DEVICECONFIG)-sizeof(DEVMODE);

    hMem=GlobalAlloc(GHND, cbWrite);

    if (NULL==hMem)
        {
        pIStream->Release();
        return FALSE;
        }

    pdc=(PDEVICECONFIG)GlobalLock(hMem);    //This always works
    pdm=(LPDEVMODE)GlobalLock(hDevMode);    //This might not

    if (NULL!=pdm)
        {
        pdc->cb=cbWrite;
        pdc->cbDevMode=cbDevMode;
        memcpy(&pdc->dm, pdm, (int)cbDevMode);
        GlobalUnlock(hDevMode);

        psz=(LPTSTR)GlobalLock(hDevNames);

        if (NULL!=psz)
            {
            pdn=(LPDEVNAMES)psz;
            lstrcpy(pdc->szDriver, psz+pdn->wDriverOffset);
            lstrcpy(pdc->szDevice, psz+pdn->wDeviceOffset);
            lstrcpy(pdc->szPort,   psz+pdn->wOutputOffset);

            pIStream->Write(pdc, cbWrite, &cbWrite);
            GlobalUnlock(hDevNames);
            fRet=TRUE;
            }
        }

    GlobalUnlock(hMem);
    GlobalFree(hMem);

    pIStream->Release();

    if (!fRet)
        return FALSE;

    GlobalFree(hDevNames);
    GlobalFree(hDevMode);

    return ConfigureForDevice();
    }

/*
 * CPages::DevModeGet
 *
 * Purpose:
 *  Retrieves a copy of the current DEVMODE structure for this
 *  Pages window.  The caller is responsible for this memory.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HGLOBAL         Handle to the memory containing the DEVMODE
 *                  structure.
 */

HGLOBAL CPages::DevModeGet(void)
    {
    HGLOBAL         hMem;
    LPVOID          pv;
    ULONG           cbDevMode, cbRead;
    LARGE_INTEGER   li;
    LPSTREAM        pIStream;
    HRESULT         hr;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    //Read how much to allocate for the DEVMODE structure
    LISet32(li, CBSEEKOFFSETCBDEVMODE);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);
    pIStream->Read(&cbDevMode, sizeof(ULONG), &cbRead);

    hMem=GlobalAlloc(GHND, cbDevMode);

    if (NULL!=hMem)
        {
        pv=(LPVOID)GlobalLock(hMem);
        pIStream->Read(pv, cbDevMode, &cbRead);
        GlobalUnlock(hMem);
        }

    pIStream->Release();
    return hMem;
    }

/*
 * CPages::DevReadConfig
 *
 * Purpose:
 *  Public function to read the current device configuration and
 *  optionally return an information context for it.
 *
 *
 * Parameters:
 *  ppcd            PCOMBINEDEVICE * in which to return a pointer
 *                  to an allocated structure that has all the
 *                  device information we want.  Ignored if NULL.
 *                  This is allocated with the task allocator.
 *  phDC            HDC * in which to return the information
 *                  context.  If NULL, no IC is created.  Caller
 *                  becomes responsible for the returned IC.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::DevReadConfig(PCOMBINEDEVICE *ppcd, HDC *phDC)
    {
    HRESULT         hr;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    PCOMBINEDEVICE  pcd;
    ULONG           cb, cbRead;
    LARGE_INTEGER   li;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    /*
     * Allocate the COMBINEDEVICE structure including the variable
     * information past the DEVMODE part.
     */

    hr=CoGetMalloc(MEMCTX_TASK, &pIMalloc);

    if (FAILED(hr))
        {
        pIStream->Release();
        return FALSE;
        }

    /*
     * Read size of the DEVICECONFIG structure including variable
     * portion of DEVMODE.  We need to load all this information
     * for CreateIC.  To this size we'll add the size of
     * DVTARGETDEVICE in order to allocate a COMBINEDEVICE.
     */

    pIStream->Read(&cb, sizeof(DWORD), &cbRead);

    pcd=(PCOMBINEDEVICE)pIMalloc->Alloc(cb+sizeof(DVTARGETDEVICE));

    if (NULL==pcd)
        {
        pIMalloc->Release();
        pIStream->Release();
        return FALSE;
        }

    //Now get the real information.
    LISet32(li, 0);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);
    pIStream->Read(&(pcd->dc), cb, &cbRead);
    pIStream->Release();

    /*
     * If requested, complete the DVTARGETDEVICE structure in
     * pcd and store pcd in *ppcd for return.
     */

    if (NULL!=ppcd)
        {
        WORD    cb=sizeof(DVTARGETDEVICE);

        pcd->td.tdSize=cb;
        pcd->td.tdExtDevmodeOffset=cb;
        pcd->td.tdDriverNameOffset=cb+sizeof(DEVMODE);
        pcd->td.tdDeviceNameOffset=cb+sizeof(DEVMODE)
            +(CCHDEVICENAME*sizeof(TCHAR));
        pcd->td.tdPortNameOffset  =cb+sizeof(DEVMODE)
            +(CCHDEVICENAME*2*sizeof(TCHAR));

        *ppcd=pcd;
        }

    //Create an IC if requested.
    if (NULL!=phDC)
        {
        //Get the DC then configure
        *phDC=CreateIC(pcd->dc.szDriver, pcd->dc.szDevice
            , pcd->dc.szPort, &(pcd->dc.dm));

        if (NULL==*phDC)
            return FALSE;
        }

    //pcd is a temporary allocation in this case
    if (NULL==ppcd)
        pIMalloc->Free(pcd);

    pIMalloc->Release();
    return (NULL!=*phDC);
    }

/*
 * CPages::ConfigureForDevice
 *
 * Purpose:
 *  Recalculates our drawing configuration based on the contents of
 *  an hDC.  If no HDC is given we use the contents of our DevMode
 *  stream.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::ConfigureForDevice(void)
    {
    POINT           ptOffset, ptPaper;
    RECT            rc;
    HDC             hDC;

    if (!DevReadConfig(NULL, &hDC))
        return FALSE;

    //Get usable page dimensions:  already sensitive to orientation
    m_cx=GetDeviceCaps(hDC, HORZSIZE)*10-16; //*10: mm to LOMETRIC
    m_cy=GetDeviceCaps(hDC, VERTSIZE)*10-16; //-16: for driver bugs.

    //Calculate the printer-limited margins on each side in LOMETRIC
    Escape(hDC, GETPRINTINGOFFSET, NULL, NULL, &ptOffset);
    Escape(hDC, GETPHYSPAGESIZE,   NULL, NULL, &ptPaper);

    SetRect(&rc, ptOffset.x, ptOffset.y, ptPaper.x, ptPaper.y);
    SetMapMode(hDC, MM_LOMETRIC);
    RectConvertMappings(&rc, hDC, FALSE);

    //Left and top margins are the printing offset.
    m_xMarginLeft= rc.left+8;   //+8 to match -16 above
    m_yMarginTop =-rc.top+8;    //LOMETRIC makes this negative.

    //Right is (paper width)-(usable width)-(left margin)
    m_xMarginRight =rc.right-m_cx-m_xMarginLeft;

    //Bottom is (paper height)-(usable height)-(top margin)+1
    m_yMarginBottom=-rc.bottom-m_cy-m_yMarginTop+1;

    UpdateScrollRanges();

    DeleteDC(hDC);
    return TRUE;
    }

/*
 * CPages::TenantCreate
 * CPages::TenantDestroy
 *
 * Purpose:
 *  Pass-throughs for CPage members on the current page.
 */

BOOL CPages::TenantCreate(TENANTTYPE tType, LPVOID pv
    , LPFORMATETC pFE, PPATRONOBJECT ppo, DWORD dwData)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantCreate(tType, pv, pFE, ppo, dwData);
    m_fDirty |= fRet;
    return fRet;
    }

BOOL CPages::TenantDestroy(void)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantDestroy();
    m_fDirty |= fRet;
    return fRet;
    }

/*
 * CPages::TenantClip
 *
 * Purpose:
 *  Copies or cuts the currently selected tenant to the clipoard.
 *
 * Parameters:
 *  fCut            BOOL TRUE to cut the object, FALSE to copy.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::TenantClip(BOOL fCut)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantClip(fCut);
    m_fDirty |= (fRet && fCut);
    return fRet;
    }

/*
 * CPages::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected on this
 *  page for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU on which object related items live.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPages::FQueryObjectSelected(HMENU hMenu)
    {
    if (NULL==m_pPageCur)
        return FALSE;

    return m_pPageCur->FQueryObjectSelected(hMenu);
    }

/*
 * CPages::ActivateObject
 *
 * Purpose:
 *  Executes a verb on the currently selected object.
 *
 * Parameters:
 *  iVerb           LONG of the selected verb.
 *
 * Return Value:
 *  None
 */

void CPages::ActivateObject(LONG iVerb)
    {
    if (NULL==m_pPageCur)
        return;

    m_pPageCur->ActivateObject(iVerb);
    return;
    }

//CHAPTER12MOD

/*
 * CPages::ShowObjectTypes
 *
 * Purpose:
 *  Pass-through to CPage::ShowObjectTypes
 *
 * Parameters:
 *  fShow           BOOL indicating to show the type or not.
 *
 * Return Value:
 *  None
 */

void CPages::ShowObjectTypes(BOOL fShow)
    {
    if (NULL==m_pPageCur)
        return;

    m_fShowTypes=fShow;
    m_pPageCur->ShowObjectTypes(fShow);
    return;
    }

/*
 * CPages::NotifyTenantsOfRename
 *
 * Purpose:
 *  Pass-through to CPage to tell tenants that the document
 *  changes names.
 *
 * Parameters:
 *  pszFile         LPTSTR of the new filename.

*  pmk             LPMONKIER to the new file moniker.
 *
 * Return Value:
 *  None
 */

void CPages::NotifyTenantsOfRename(LPTSTR pszFile, LPMONIKER pmk)
    {
    if (NULL==m_pPageCur)
        return;

    m_pPageCur->NotifyTenantsOfRename(pszFile, pmk);
    return;
    }

/*
 * CPages::FQueryLinksInPage
 *
 * Purpose:
 *  Pass through to current page to see if there are any
 *  linked objects
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPages::FQueryLinksInPage()
    {
    if (NULL==m_pPageCur)
        return FALSE;

    return m_pPageCur->FQueryLinksInPage();
    }

/*
 * CPages::FGetUILinkContainer
 *
 * Purpose:
 *  Creates an object with the IOleUILinkContainer interface for
 *  the links dialog.  We know the current page which is what
 *  we need to create this thing, so we can create it here instead
 *  of bugging the page for it.
 *
 * Parameters:
 *  ppObj           PCIOleUILinkContainer * in which we return
 *                  the pointer.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::FGetUILinkContainer(PCIOleUILinkContainer *ppObj)
    {
    PCIOleUILinkContainer   pObj;

    *ppObj=NULL;

    if (NULL==m_pPageCur)
        return FALSE;

    pObj=new CIOleUILinkContainer(m_pPageCur);

    if (NULL==pObj)
        return FALSE;

    if (!pObj->FInit())
        {
        delete pObj;
        return FALSE;
        }

    pObj->AddRef();
    *ppObj=pObj;
    return TRUE;
    }
//End CHAPTER12MOD

/*
 * CPages::CalcBoundingRect
 * (Protected)
 *
 * Purpose:
 *  Calculates a rectangle that bounds the printed page and the
 *  current scroll state of the window.
 *
 * Parameters:
 *  prc             LPRECT to fill with window (device) coordinates.
 *  fWindow         BOOL indicating to include the window in this
 *                  calculation or return only the printed page
 *                  coordinates.
 *
 * Return Value:
 *  None
 */

void CPages::CalcBoundingRect(LPRECT prc, BOOL fWindow)
    {
    RECT        rc, rcT;

    if (NULL==prc)
        return;

    //Calculate the boundaries for sizing: intersect page & screen
    rc.left=LOMETRIC_BORDER+m_xMarginLeft;
    rc.top =-LOMETRIC_BORDER-m_yMarginTop;
    rc.right =rc.left+(UINT)m_cx;
    rc.bottom=rc.top -(UINT)m_cy;
    RectConvertMappings(&rc, NULL, TRUE);
    OffsetRect(&rc, -(int)m_xPos, -(int)m_yPos);

    if (!fWindow)
        {
        *prc=rc;
        return;
        }

    //Intersect with window to make the size bounds.
    GetClientRect(m_hWnd, &rcT);
    IntersectRect(prc, &rc, &rcT);
    return;
    }

/*
 * CPages::FPageGet
 * (Protected)
 *
 * Purpose:
 *  Returns a page of a given index returning a BOOL so it's simple
 *  to use this function inside if statements.
 *
 * Parameters:
 *  iPage           UINT page to retrieve 0 based.
 *  ppPage          PCPage * in which to return the page pointer
 *  fOpen           BOOL indicating if we should open this page as
 *                  well.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::FPageGet(UINT iPage, PCPage *ppPage, BOOL fOpen)
    {
    if (NULL==ppPage)
        return FALSE;

    if (LB_ERR!=SendMessage(m_hWndPageList, LB_GETTEXT
        , iPage, (LONG)ppPage))
        {
        if (fOpen)
            (*ppPage)->FOpen(m_pIStorage);

        return TRUE;
        }

    return FALSE;
    }

/*
 * CPages::FPageAdd
 * (Protected)
 *
 * Purpose:
 *  Creates a new page initialized to the given values.  The new
 *  page's storage is created if it does not already exist.  If
 *  fOpenStorage is set the page's storage is opened and left
 *  opened.
 *
 * Parameters:
 *  iPage           UINT Location at which to insert page; new page
 *                  is inserted after this position.  NOVALUE for
 *                  the end.
 *  dwID            DWORD ID for this page.
 *  fOpenStorage    BOOL indicating if we're to leave the storage
 *                  open.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPages::FPageAdd(UINT iPage, DWORD dwID, BOOL fOpenStorage)
    {
    PCPage      pPage;
    LRESULT     lr;

    pPage=new CPage(dwID, m_hWnd, this);

    if (NULL==pPage)
        return FALSE;

    if (fOpenStorage)
        pPage->FOpen(m_pIStorage);

    if (NOVALUE==iPage)
        iPage--;

    //Now try to add to the listbox.
    lr=SendMessage(m_hWndPageList, LB_INSERTSTRING, iPage+1
        , (LONG)pPage);

    if (LB_ERRSPACE==lr)
        {
        if (fOpenStorage)
            pPage->Close(FALSE);

        delete pPage;
        return FALSE;
        }

    return TRUE;
    }


PAGES.H

/*
 * PAGES.H
 * Patron Chapter 12
 *
 * Definitions and function prototypes for the Pages window control
 * as well as the CPage class.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _PAGES_H_
#define _PAGES_H_

//We need this for UICursorLoad and new-style cursors.
#include <bttncur.h>

//Versioning.
#define VERSIONMAJOR                2
#define VERSIONMINOR                0
#define VERSIONCURRENT              0x00020000

//Classname
#define SZCLASSPAGES                TEXT("pages")

#define HIMETRIC_PER_INCH           2540
#define LOMETRIC_PER_INCH           254
#define LOMETRIC_BORDER             60          //Border around page

//Window extra bytes and offsets
#define CBPAGESWNDEXTRA             (sizeof(LONG))
#define PAGEWL_STRUCTURE            0

#include "tenant.h"

typedef struct tagTENANTLIST
    {
    DWORD       cTenants;
    DWORD       dwIDNext;
    } TENANTLIST, *PTENANTLIST;

#define SZSTREAMTENANTLIST        OLESTR("Tenant List")

//Delay timer used in mouse debouncing
#define IDTIMER_DEBOUNCE          120

/*
 * Page class describing an individual page and what things it
 * contains, managing an IStorage for us.
 *
 * A DWORD is used to identify this page as the name of the storage
 * is the string form of this ID.  If we added a page every second,
 * it would take 136 years to overrun this counter, so we can
 * get away with saving it persistently.  I hope this software is
 * obsolete by then.
 */

class CPage
    {
    //CHAPTER12MOD
    friend class CIOleUILinkContainer;
    //End CHAPTER12MOD

    private:
        DWORD       m_dwID;             //Persistent identifier
        LPSTORAGE   m_pIStorage;        //Substorage for this page
        HWND        m_hWnd;             //Pages window
        DWORD       m_cOpens;           //Calls to FOpen

        class CPages *m_pPG;            //Pages window

        DWORD       m_dwIDNext;
        DWORD       m_cTenants;
        HWND        m_hWndTenantList;   //Listbox; our tenant list

        UINT        m_iTenantCur;
        PCTenant    m_pTenantCur;

        UINT        m_uHTCode;          //Last hit-test/mouse move
        UINT        m_uSizingFlags;     //Restrictions on sizing
        BOOL        m_fTracking;        //Tracking resize?
        RECTL       m_rclOrg;           //Original before tracking
        RECTL       m_rcl;              //Tracking rectangle
        RECTL       m_rclBounds;        //Boundaries f/size tracking
        HDC         m_hDC;              //Tracking hDC

        BOOL        m_fDragPending;     //Waiting for drag?
        BOOL        m_fSizePending;     //Waiting for debounce?
        int         m_cxyDist;          //Debounce distance
        UINT        m_cDelay;           //Debounce delay
        POINTS      m_ptDown;           //Point of click to debounce
        UINT        m_uKeysDown;        //Keys when click happens
        DWORD       m_fTimer;           //Timer active?

        //CHAPTER12MOD
        BOOL        m_fReopen;          //Did we just close?
        //End CHAPTER12MOD

    protected:
        BOOL         FTenantGet(UINT, PCTenant *, BOOL);
        BOOL         FTenantAdd(UINT, DWORD, PCTenant *);
        LPDATAOBJECT TransferObjectCreate(LPPOINTL);

        //PAGEMOUS.CPP
        BOOL         FSelectTenantAtPoint(UINT, UINT);
        UINT         TenantFromPoint(UINT, UINT, PCTenant *);
        BOOL         DragDrop(UINT, UINT, UINT);

    public:
        CPage(DWORD, HWND, class CPages *);
        ~CPage(void);

        DWORD       GetID(void);
        BOOL        FOpen(LPSTORAGE);
        void        Close(BOOL);
        BOOL        Update(void);
        void        Destroy(LPSTORAGE);
        UINT        GetStorageName(LPTSTR);

        void        Draw(HDC, int, int, BOOL, BOOL);

        BOOL        TenantCreate(TENANTTYPE, LPVOID, LPFORMATETC
                        , PPATRONOBJECT, DWORD);
        BOOL        TenantDestroy(void);
        BOOL        TenantClip(BOOL);
        BOOL        FQueryObjectSelected(HMENU);
        void        ActivateObject(LONG);
        //CHAPTER12MOD
        void        ShowObjectTypes(BOOL);
        void        NotifyTenantsOfRename(LPTSTR, LPMONIKER);
        BOOL        FQueryLinksInPage(void);
        //End CHAPTER12MOD

        //PAGEMOUSE.CPP
        BOOL        OnRightDown(UINT, UINT, UINT);
        BOOL        OnLeftDown(UINT, UINT, UINT);
        BOOL        OnLeftDoubleClick(UINT, UINT, UINT);
        BOOL        OnLeftUp(UINT, UINT, UINT);
        void        OnMouseMove(UINT, int, int);
        void        OnTimer(UINT);
        void        StartSizeTracking(void);
        void        OnNCHitTest(UINT, UINT);
        BOOL        OnSetCursor(UINT);
    };

typedef CPage *PCPage;

/*
 * Structures to save with the document describing the device
 * configuration and pages that we have.  This is followed by
 * a list of DWORD IDs for the individual pages.
 */

typedef struct tagDEVICECONFIG
    {
    DWORD       cb;                         //Size of structure
    TCHAR       szDriver[CCHDEVICENAME];
    TCHAR       szDevice[CCHDEVICENAME];
    TCHAR       szPort[CCHDEVICENAME];
    DWORD       cbDevMode;                  //Size of actual DEVMODE
    DEVMODE     dm;                         //Variable
    } DEVICECONFIG, *PDEVICECONFIG;

//Offset to cbDevMode
#define CBSEEKOFFSETCBDEVMODE  (sizeof(DWORD)   \
                               +(3*CCHDEVICENAME*sizeof(TCHAR)))

typedef struct tagPAGELIST
    {
    DWORD       cPages;
    DWORD       iPageCur;
    DWORD       dwIDNext;
    } PAGELIST, *PPAGELIST;

//Combined OLE and Patron device structures.
typedef struct tagCOMBINEDEVICE
    {
    DVTARGETDEVICE  td;
    DEVICECONFIG    dc;
    } COMBINEBDEVICE, *PCOMBINEDEVICE;

//PAGEWIN.CPP
LRESULT APIENTRY PagesWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL    APIENTRY AbortProc(HDC, int);
BOOL    APIENTRY PrintDlgProc(HWND, UINT, WPARAM, LPARAM);
void             RectConvertMappings(LPRECT, HDC, BOOL);

class CPages : public CWindow
    {
    friend LRESULT APIENTRY PagesWndProc(HWND, UINT, WPARAM, LPARAM);
    friend BOOL    APIENTRY PrintDlgProc(HWND, UINT, WPARAM, LPARAM);

    friend class CPage;
    friend class CTenant;
    friend class CDropTarget;
    friend class CImpIAdviseSink;

    protected:
        PCPage      m_pPageCur;             //Current page
        UINT        m_iPageCur;             //Current page
        UINT        m_cPages;               //Number of pages

        HWND        m_hWndPageList;         //Listbox with page list
        HFONT       m_hFont;                //Page font
        BOOL        m_fSystemFont;          //m_hFont system object?

        UINT        m_cx;                   //Page size in LOMETRIC
        UINT        m_cy;

        UINT        m_xMarginLeft;          //Unusable margins,
        UINT        m_xMarginRight;         //in LOMETRIC
        UINT        m_yMarginTop;
        UINT        m_yMarginBottom;

        UINT        m_xPos;                 //Viewport scroll pos,
        UINT        m_yPos;                 //both in *PIXELS*

        DWORD       m_dwIDNext;             //Next ID for a page.
        LPSTORAGE   m_pIStorage;            //Root storage

        UINT        m_cf;                   //Clipboard format
        BOOL        m_fDirty;

        BOOL        m_fDragSource;          //Source==target?
        BOOL        m_fMoveInPage;          //Moving in same page
        //CHAPTER12MOD
        BOOL        m_fLinkAllowed;         //Linking in drag-drop?
        //End CHAPTER12MOD
        POINTL      m_ptDrop;               //Where to move object

        BOOL        m_fDragRectShown;       //Is rect on the screen?
        UINT        m_uScrollInset;         //Hit-test for drag-drop
        UINT        m_uScrollDelay;         //Delay before repeat
        DWORD       m_dwTimeLast;           //Ticks on last DragOver
        UINT        m_uHScrollCode;         //L/R on scroll repeat?
        UINT        m_uVScrollCode;         //U/D on scroll repeat?
        UINT        m_uLastTest;            //Last test result
        POINTL      m_ptlRect;              //Last feedback rectangle
        SIZEL       m_szlRect;

        //CHAPTER12MOD
        BOOL        m_fShowTypes;           //Show Object active?
        //End CHAPTER12MOD

    private:
        void      Draw(HDC, BOOL, BOOL);
        void      UpdateScrollRanges(void);
        BOOL      ConfigureForDevice(void);
        BOOL      FPageGet(UINT, PCPage *, BOOL);
        BOOL      FPageAdd(UINT, DWORD, BOOL);

        void      CalcBoundingRect(LPRECT, BOOL);

        //DRAGDROP.CPP
        UINT      UTestDroppablePoint(LPPOINTL);
        void      DrawDropTargetRect(LPPOINTL, LPSIZEL);
        void      AdjustPosition(LPPOINTL, LPSIZEL);

    public:
        CPages(HINSTANCE, UINT);
        ~CPages(void);

        BOOL      FInit(HWND, LPRECT, DWORD, UINT, LPVOID);

        BOOL      FIStorageSet(LPSTORAGE, BOOL, BOOL);
        BOOL      FIStorageUpdate(BOOL);

        BOOL      Print(HDC, LPTSTR, DWORD, UINT, UINT, UINT);

        void      RectGet(LPRECT);
        void      RectSet(LPRECT, BOOL);
        void      SizeGet(LPRECT);
        void      SizeSet(LPRECT, BOOL);

        UINT      PageInsert(UINT);
        UINT      PageDelete(UINT);
        UINT      CurPageGet(void);
        UINT      CurPageSet(UINT);
        UINT      NumPagesGet(void);

        BOOL      DevModeSet(HGLOBAL, HGLOBAL);
        HGLOBAL   DevModeGet(void);

        BOOL      FIsDirty(void);
        BOOL      DevReadConfig(PCOMBINEDEVICE *, HDC *);
        BOOL      TenantCreate(TENANTTYPE, LPVOID, LPFORMATETC
                      , PPATRONOBJECT, DWORD);
        BOOL      TenantDestroy(void);
        BOOL      TenantClip(BOOL);
        BOOL      FQueryObjectSelected(HMENU);
        void      ActivateObject(LONG);

        //CHAPTER12MOD
        void      ShowObjectTypes(BOOL);
        void      NotifyTenantsOfRename(LPTSTR, LPMONIKER);
        BOOL      FQueryLinksInPage(void);
        BOOL      FGetUILinkContainer(class CIOleUILinkContainer **);
        //End CHAPTER12MOD
    };

typedef CPages *PCPages;

//Fixed names of streams in the Pages IStorage
#define SZSTREAMPAGELIST        OLESTR("Page List")
#define SZSTREAMDEVICECONFIG    OLESTR("Device Configuration")

//Return values for UTestDroppablePoint
#define UDROP_NONE              0x0000      //Exclusive
#define UDROP_CLIENT            0x0001      //Inclusive
#define UDROP_INSETLEFT         0x0002      //L/R are exclusive
#define UDROP_INSETRIGHT        0x0004
#define UDROP_INSETHORZ         (UDROP_INSETLEFT | UDROP_INSETRIGHT)

#define UDROP_INSETTOP          0x0008      //T/B are exclusive
#define UDROP_INSETBOTTOM       0x0010
#define UDROP_INSETVERT         (UDROP_INSETTOP | UDROP_INSETBOTTOM)

//CHAPTER12MOD
//Object used for the Links dialog

class CIOleUILinkContainer : public IOleUILinkContainer
    {
    private:
        ULONG                   m_cRef;
        PCPage                  m_pPage;
        UINT                    m_iTenant;
        LPOLEUILINKCONTAINER    m_pDelIUILinks;

    public:
        BOOL                    m_fDirty;   //No reason to hide it

    protected:
        STDMETHODIMP GetObjectInterface(DWORD, REFIID, PPVOID);

    public:
        CIOleUILinkContainer(PCPage);
        ~CIOleUILinkContainer(void);

        BOOL FInit(void);
        BOOL IsDirty(void);

        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(DWORD) GetNextLink(DWORD);
        STDMETHODIMP         SetLinkUpdateOptions(DWORD, DWORD);
        STDMETHODIMP         GetLinkUpdateOptions(DWORD, LPDWORD);
        STDMETHODIMP         SetLinkSource(DWORD, LPTSTR, ULONG
                                 , ULONG *, BOOL);
        STDMETHODIMP         GetLinkSource(DWORD, LPTSTR *, ULONG *
                                 , LPTSTR *, LPTSTR *, BOOL *
                                 , BOOL *);
        STDMETHODIMP         OpenLinkSource(DWORD);
        STDMETHODIMP         UpdateLink(DWORD, BOOL, BOOL);
        STDMETHODIMP         CancelLink(DWORD);
    };

typedef CIOleUILinkContainer *PCIOleUILinkContainer;

//End CHAPTER12MOD

#endif  //_PAGES_H_


PAGEWIN.CPP

/*
 * PAGEWIN.CPP
 * Patron Chapter 12
 *
 * Window procedure for the Pages window and support functions.
 * This window manages its own scrollbars and viewport and provides
 * printing capabilities as well.  The public CPages::Print
 * function lives here.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

static HWND g_hDlgPrint=NULL;
static BOOL g_fCancelPrint=FALSE;

/*
 * PagesWndProc
 *
 * Purpose:
 *  Window procedure for the Pages window.
 */

LRESULT APIENTRY PagesWndProc(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    PCPages         ppg;
    PAINTSTRUCT     ps;
    HDC             hDC;
    int             iPos, iTmp;
    int             iMin, iMax;
    UINT            idScroll;
    BOOL            fDirty=FALSE;

    ppg=(PCPages)GetWindowLong(hWnd, PAGEWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppg=(PCPages)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PAGEWL_STRUCTURE, (LONG)ppg);

            ppg->m_hWnd=hWnd;
            break;

        case WM_PAINT:
            /*
             * If there is currently a drag-rectangle showing, then
             * remove it before painting.  This insures that
             * painting doesn't blast part of that rectangle away so
             * that when we draw it next, garbage is left around.
             */
            if (ppg->m_fDragRectShown)
                ppg->DrawDropTargetRect(NULL, NULL);

            hDC=BeginPaint(hWnd, &ps);

            //Draw only if we have a page to show.
            if (0!=ppg->m_cPages)
                ppg->Draw(hDC, FALSE, FALSE);

            EndPaint(hWnd, &ps);

            //Turn the rectangle back on, if necessary.
            if (ppg->m_fDragRectShown)
                ppg->DrawDropTargetRect(NULL, NULL);
            break;

        case WM_HSCROLL:
        case WM_VSCROLL:
            idScroll=(WM_HSCROLL==iMsg) ? SB_HORZ : SB_VERT;

            iPos=GetScrollPos(hWnd, idScroll);
            iTmp=iPos;
            GetScrollRange(hWnd, idScroll, &iMin, &iMax);

            switch (LOWORD(wParam))
                {
                case SB_LINEUP:     iPos -= 20;  break;
                case SB_PAGEUP:     iPos -=100;  break;
                case SB_LINEDOWN:   iPos += 20;  break;
                case SB_PAGEDOWN:   iPos +=100;  break;

                case SB_THUMBPOSITION:
                    iPos=ScrollThumbPosition(wParam, lParam);
                    break;

                //We don't want scrolling on this message.
                case SB_THUMBTRACK:
                    return 0L;
                }

            iPos=max(iMin, min(iPos, iMax));

            if (iPos!=iTmp)
                {
                //Set the new position and scroll the window
                SetScrollPos(hWnd, idScroll, iPos, TRUE);

                if (SB_HORZ==idScroll)
                    {
                    ppg->m_xPos=iPos;
                    ScrollWindow(hWnd, iTmp-iPos, 0, NULL, NULL);
                    }
                else
                    {
                    ppg->m_yPos=iPos;
                    ScrollWindow(hWnd, 0, iTmp-iPos, NULL, NULL);
                    }
                }

            break;

        case WM_RBUTTONDOWN:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnRightDown(wParam
                , LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_LBUTTONDOWN:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftDown(wParam
                , LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_LBUTTONUP:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftUp(wParam
                , LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_LBUTTONDBLCLK:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftDoubleClick(wParam, LOWORD(lParam)
                , HIWORD(lParam));
            break;

        case WM_MOUSEMOVE:
            if (NULL==ppg->m_pPageCur)
                break;

            ppg->m_pPageCur->OnMouseMove(wParam, LOWORD(lParam)
                , HIWORD(lParam));
            break;

        case WM_TIMER:
            if (NULL==ppg->m_pPageCur)
                break;

            ppg->m_pPageCur->OnTimer(wParam);
            break;

        case WM_NCHITTEST:
            if (NULL!=ppg->m_pPageCur)
                {
                /*
                 * This just saves information in the page for
                 * OnSetCursor
                 */
                ppg->m_pPageCur->OnNCHitTest(LOWORD(lParam)
                    , HIWORD(lParam));
                }

            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        case WM_SETCURSOR:
            if (NULL!=ppg->m_pPageCur)
                {
                if (ppg->m_pPageCur->OnSetCursor(LOWORD(lParam)))
                    break;
                }

            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    ppg->m_fDirty |= fDirty;
    return 0L;
    }

/*
 * RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device to logical
 *  coordinates where the hDC defines the logical coordinates.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  hDC             HDC describing the logical coordinate system.
 *                  if NULL, uses a screen DC in MM_LOMETRIC.
 *  fToDevice       BOOL TRUE to convert from uConv to device,
 *                  FALSE to convert device to uConv.
 *
 * Return Value:
 *  None
 */

void RectConvertMappings(LPRECT pRect, HDC hDC, BOOL fToDevice)
    {
    POINT   rgpt[2];
    BOOL    fSysDC=FALSE;

    if (NULL==pRect)
        return;

    rgpt[0].x=pRect->left;
    rgpt[0].y=pRect->top;
    rgpt[1].x=pRect->right;
    rgpt[1].y=pRect->bottom;

    if (NULL==hDC)
        {
        hDC=GetDC(NULL);
        SetMapMode(hDC, MM_LOMETRIC);
        fSysDC=TRUE;
        }

    if (fToDevice)
        LPtoDP(hDC, rgpt, 2);
    else
        DPtoLP(hDC, rgpt, 2);

    if (fSysDC)
        ReleaseDC(NULL, hDC);

    pRect->left=rgpt[0].x;
    pRect->top=rgpt[0].y;
    pRect->right=rgpt[1].x;
    pRect->bottom=rgpt[1].y;

    return;
    }

/*
 * CPages::Draw
 *
 * Purpose:
 *  Paints the current page in the pages window.
 *
 * Parameters:
 *  hDC             HDC to draw on, could be a metafile or printer
 *                  DC or any other type of DC.
 *  fNoColor        BOOL indicating if we should use screen colors
 *                  or printer colors (B&W).  Objects are printed
 *                  as-is, however.  This is TRUE for printer DCs
 *                  or print preview.
 *  fPrinter        BOOL indicating if this is a printer DC in which
 *                  case we eliminate some of the fancy drawing,
 *                  like shadows on the page and so forth.
 *
 * Return Value:
 *  None
 */

void CPages::Draw(HDC hDC, BOOL fNoColor, BOOL fPrinter)
    {
    RECT            rc, rcT;
    UINT            uMM;
    HPEN            hPen;
    HBRUSH          hBrush;
    HGDIOBJ         hObj1, hObj2;
    COLORREF        cr;
    TCHAR           szTemp[20];
    UINT            cch;
    SIZE            sz;
    PCPage          pPage;
    RECT            rcPos;

    //Make sure the DC is in LOMETRIC
    uMM=SetMapMode(hDC, MM_LOMETRIC);

    if (!fPrinter)
        {
        /*
         * We maintain a 6mm border around the page on the screen
         * besides 12.7mm margins.  We also have to account for
         * the scroll position with m_*Pos which are in pixels so
         * we have to convert them.
         */

        SetRect(&rcPos, m_xPos, m_yPos, 0, 0);
        RectConvertMappings(&rcPos, hDC, FALSE);

        rc.left  = LOMETRIC_BORDER-rcPos.left;
        rc.top   =-LOMETRIC_BORDER-rcPos.top;
        }
    else
        {
        /*
         * We define the corner of the printed paper at a negative
         * offset so rc.right and rc.bottom come out right below.
         */
        SetRect(&rc, -(int)m_xMarginLeft, m_yMarginTop, 0, 0);
        }

    rc.right=rc.left+m_cx+(m_xMarginLeft+m_xMarginRight);
    rc.bottom=rc.top-m_cy-(m_yMarginTop+m_yMarginBottom);

    //Draw a rect filled with the window color to show the page.
    if (!fPrinter)
        {
        if (fNoColor)
            {
            //Black frame, white box for printed colors.
            hPen  =CreatePen(PS_SOLID, 0, RGB(0,0,0));
            hBrush=CreateSolidBrush(RGB(255, 255, 255));
            }
        else
            {
            //Normal colors on display
            hPen=CreatePen(PS_SOLID, 0
                , GetSysColor(COLOR_WINDOWFRAME));
            hBrush=CreateSolidBrush(GetSysColor(COLOR_WINDOW));
            }

        hObj1=SelectObject(hDC, hPen);
        hObj2=SelectObject(hDC, hBrush);

        //Paper boundary
        Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom+1);

        /*
         * Draw a shadow on the *visual* bottom and right edges
         * .5mm wide.  If the button shadow color and workspace
         * colors match, then use black.  We always use black
         * when printing as well.
         */
        if (fNoColor)
            cr=RGB(0,0,0);
        else
            {
            cr=GetSysColor(COLOR_BTNSHADOW);

            if (GetSysColor(COLOR_APPWORKSPACE)==cr)
                cr=RGB(0,0,0);
            }

        cr=SetBkColor(hDC, cr);
        SetRect(&rcT, rc.left+5, rc.bottom, rc.right+5,rc.bottom-5);
        ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rcT, NULL, 0, NULL);

        SetRect(&rcT, rc.right, rc.top-5, rc.right+5, rc.bottom-5);
        ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rcT, NULL, 0, NULL);
        SetBkColor(hDC, cr);

        SelectObject(hDC, hObj1);
        SelectObject(hDC, hObj2);
        DeleteObject(hBrush);
        DeleteObject(hPen);
        }

    //Write the page number in the lower left corner
    if (!fNoColor)
        {
        SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));
        SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
        }

    //Write the page number in our page font.
    cch=wsprintf(szTemp, TEXT("Page %d"), m_iPageCur+1);

    hObj1=SelectObject(hDC, m_hFont);
    GetTextExtentPoint(hDC, szTemp, cch, &sz);

    TextOut(hDC, rc.left+m_xMarginLeft
        , rc.bottom+m_yMarginBottom+sz.cy, szTemp, cch);

    SelectObject(hDC, hObj1);

    //Rectangle to show border.
    MoveToEx(hDC, rc.left+m_xMarginLeft, rc.top-m_yMarginTop, NULL);
    LineTo(hDC, rc.left+m_xMarginLeft,   rc.bottom+m_yMarginBottom);
    LineTo(hDC, rc.right-m_xMarginRight, rc.bottom+m_yMarginBottom);
    LineTo(hDC, rc.right-m_xMarginRight, rc.top-m_yMarginTop);
    LineTo(hDC, rc.left+m_xMarginLeft,   rc.top-m_yMarginTop);

    /*
     * Go draw the objects on this page.  If the page is not open,
     * we open it anyway.  If it is already open, then opening again
     * will bump it's reference count, so the Close in ineffectual.
     */
    if (FPageGet(m_iPageCur, &pPage, TRUE))
        {
        if (!fPrinter)
            {
            pPage->Draw(hDC, rcPos.left, rcPos.top, fNoColor
                , fPrinter);
            }
        else
            pPage->Draw(hDC, 0, 0, fNoColor, fPrinter);

        pPage->Close(FALSE);
        }

    SetMapMode(hDC, uMM);
    return;
    }

/*
 * CPages::UpdateScrollRanges
 *
 * Purpose:
 *  Reset scrollbar ranges (horizontal and vertical) depending on
 *  the window size and the page size.  This function may remove
 *  the scrollbars altogether.
 *
 * Parameters:
 *  None, but set m_cx, m_cy and size m_hWnd before calling.
 *
 * Return Value:
 *  None
 */

void CPages::UpdateScrollRanges(void)
    {
    UINT        cxSB;   //Scrollbar width and height.
    UINT        cySB;
    UINT        cx, cy;
    UINT        dx, dy;
    UINT        u;
    int         iMin, iMax;
    RECT        rc;
    BOOL        fHScroll;
    BOOL        fVScroll;
    BOOL        fWasThere;

    GetClientRect(m_hWnd, &rc);

    cx=rc.right-rc.left;
    cy=rc.bottom-rc.top;

    //Convert dimensions of the image in LOMETRIC to pixels.
    SetRect(&rc, (m_cx+m_xMarginLeft+m_xMarginRight
        +LOMETRIC_BORDER*2), (m_cy+m_yMarginTop
        +m_yMarginBottom+LOMETRIC_BORDER*2), 0, 0);

    RectConvertMappings(&rc, NULL, TRUE);

    dx=rc.left;
    dy=-rc.top;

    //Assume that both scrollbars will be visible.
    fHScroll=TRUE;
    fVScroll=TRUE;

    /*
     * Determine:
     *  1)  Which scrollbars are needed.
     *  2)  How many divisions to give scrollbars so as to
     *      only scroll as little as necessary.
     */

    //Scrollbar dimensions in our units.
    cxSB=GetSystemMetrics(SM_CXVSCROLL);
    cySB=GetSystemMetrics(SM_CYHSCROLL);

    //Remove horizontal scroll if window >= cxPage+borders
    if (cx >= dx)
        fHScroll=FALSE;

    /*
     * If we still need a horizontal scroll, see if we need a
     * vertical taking the height of the horizontal scroll into
     * account.
     */

    u=fHScroll ? cySB : 0;

    if ((cy-u) >= dy)
        fVScroll=FALSE;

    //Check if adding vert scrollbar necessitates a horz now.
    u=fVScroll ? cxSB : 0;
    fHScroll=((cx-u) < dx);

    /*
     * Modify cx,cy to reflect the new client area before scaling
     * scrollbars.  We only affect the client size if there is a
     * *change* in scrollbar status:  if the scrollbar was there
     * but is no longer, then add to the client size; if it was
     * not there but now is, then subtract.
     */

    //Change cx depending on vertical scrollbar change
    GetScrollRange(m_hWnd, SB_VERT, &iMin, &iMax);
    fWasThere=(0!=iMin || 0!=iMax);

    if (fWasThere && !fVScroll)
        cx+=cxSB;

    if (!fWasThere && fVScroll)
        cx-=cxSB;

    //Change cy depending on horizontal scrollbar change
    GetScrollRange(m_hWnd, SB_HORZ, &iMin, &iMax);
    fWasThere=(0!=iMin || 0!=iMax);

    if (fWasThere && !fHScroll)
        cy+=cySB;

    if (!fWasThere && fHScroll)
        cy-=cySB;

    /*
     * Show/Hide the scrollbars if necessary and set the ranges.
     * The range is the number of units of the page we cannot see.
     */
    if (fHScroll)
        {
        //Convert current scroll position to new range.
        u=GetScrollPos(m_hWnd, SB_HORZ);

        if (0!=u)
            {
            GetScrollRange(m_hWnd, SB_HORZ, &iMin, &iMax);
            u=MulDiv(u, (dx-cx), (iMax-iMin));
            }

        SetScrollRange(m_hWnd, SB_HORZ, 0, dx-cx, FALSE);
        SetScrollPos(m_hWnd, SB_HORZ, u, TRUE);
        m_xPos=u;
        }
    else
        {
        SetScrollRange(m_hWnd, SB_HORZ, 0, 0, TRUE);
        m_xPos=0;
        }

    if (fVScroll)
        {
        //Convert current scroll position to new range.
        u=GetScrollPos(m_hWnd, SB_VERT);

        if (0!=u)
            {
            GetScrollRange(m_hWnd, SB_VERT, &iMin, &iMax);
            u=MulDiv(u, (dy-cy), (iMax-iMin));
            }

        SetScrollRange(m_hWnd, SB_VERT, 0, dy-cy, FALSE);
        SetScrollPos(m_hWnd, SB_VERT, u, TRUE);

        m_yPos=u;
        }
    else
        {
        SetScrollRange(m_hWnd, SB_VERT, 0, 0, TRUE);
        m_yPos=0;
        }

    //Repaint to insure that changes to m_x/yPos are reflected
    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }

/*
 * CPages::Print
 *
 * Purpose:
 *  Prints a specified range of pages to a given hDC.  Repeats for
 *  a given number of copies.
 *
 * Parameters:
 *  hDC             HDC to which we print.
 *  pszDoc          LPTSTR providing the document name.
 *  dwFlags         DWORD flags from PrintDlg
 *  iPageStart      UINT starting page index (one based)
 *  iPageEnd        UINT ending page index (one based).  Includes
 *                  this page.
 *  cCopies         UINT number of copies to print.  If PD_COLLATE
 *                  in dwFlags is set, we print multiple copies of
 *                  each page as we cycle through.  Otherwise we
 *                  cycle multiple times.
 *
 * Return Value:
 *  None
 */

BOOL CPages::Print(HDC hDC, LPTSTR pszDoc, DWORD dwFlags
    , UINT iPageStart, UINT iPageEnd, UINT cCopies)
    {
    BOOL        fError=FALSE;
    int         iPage, iPageInc;
    int         iUserPage, cPages;
    UINT        iRepeat, cRepeat;
    UINT        iCycle, cCycles;
    UINT        iPageHold=m_iPageCur;
    HWND        hWndT, hWndTop=NULL;
    DOCINFO     di;
    PCDocument  pDoc;

    //Validate hDC and page ranges
    if (NULL==hDC)
        return FALSE;

    if ((PD_PAGENUMS & dwFlags))
        {
        if (-1==iPageStart)
            iPageStart=0;
        else
            iPageStart--;   //Switch to zero offset.

        if (-1==iPageEnd)
            iPageEnd=m_cPages-1;
        else
            iPageEnd--;     //Switch to zero offset.
        }
    else //Can't test PD_ALLPAGES with & since it's defined as 0L
        {
        iPageStart=0;
        iPageEnd=m_cPages-1;
        }

    //Arrange cycles and repeats depending on cCopies and collating
    if (PD_COLLATE & dwFlags)
        {
        cCycles=cCopies;
        cRepeat=1;
        }
    else
        {
        cCycles=1;
        cRepeat=cCopies;
        }

    //Disable the frame window to prevent reentrancy while printing.
    hWndT=GetParent(m_hWnd);
    pDoc=(PCDocument)SendMessage(hWndT, DOCM_PDOCUMENT, 0, 0L);

    if (NULL!=pDoc)
        {
        PCFrame pFR;

        pFR=pDoc->FrameGet();
        hWndTop=pFR->Window();
        EnableWindow(hWndTop, FALSE);
        }

    SetAbortProc(hDC, AbortProc);
    g_fCancelPrint=FALSE;

    //If these don't work then we'll just live without a dialog.
    g_hDlgPrint=CreateDialog(m_hInst, MAKEINTRESOURCE(IDD_PRINTING)
        , hWndTop, PrintDlgProc);

    //Increment for either direction.
    iPageInc=(iPageStart > iPageEnd) ? -1 : 1;

    //Initial entries in dialog box.
    cPages=1+((int)(iPageEnd-iPageStart)*iPageInc);

    SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE, 1, (LPARAM)cPages);
    SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, 1, (LPARAM)cRepeat);

    di.cbSize=sizeof(DOCINFO);
    di.lpszDocName=pszDoc;
    di.lpszOutput=NULL;

    if (StartDoc(hDC, &di) > 0)
        {
        /*
         * Iterate over the pages, repeating each page depending on
         * the copies we want and if we have collate enabled.
         */

        for (iCycle=1; iCycle <= cCycles; iCycle++)
            {
            if (PD_COLLATE & dwFlags)
                {
                SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, iCycle
                    , (LPARAM)cCycles);
                }

            //iPageInc controls direction
            for (iPage=iPageStart; ; iPage+=iPageInc)
                {
                iUserPage=1+((iPage-(int)iPageStart)*iPageInc);

                SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE
                    , iUserPage, (LPARAM)cPages);

                m_iPageCur=iPage;   //We restore this later.

                for (iRepeat=1; iRepeat <= cRepeat; iRepeat++)
                    {
                    if (!(PD_COLLATE & dwFlags))
                        {
                        SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE
                            , iRepeat, (LPARAM)cRepeat);
                        }

                    StartPage(hDC);
                    Draw(hDC, TRUE, TRUE);

                    if (EndPage(hDC) < 0)
                        fError=TRUE;

                    if (fError || g_fCancelPrint)
                        break;
                    }

                if (fError || g_fCancelPrint)
                    break;

                //If we just printed the last page, time to quit.
                if (iPage==(int)iPageEnd)
                    break;
                }

            if (fError || g_fCancelPrint)
                break;
            }

        if (!fError)
            EndDoc(hDC);
        else
            AbortDoc(hDC);
        }
    else
        fError=TRUE;

    //Set the page back to what it was before all this started.
    m_iPageCur=iPageHold;

    EnableWindow(hWndTop, TRUE);
    SetFocus(hWndTop);
    DestroyWindow(g_hDlgPrint);
    DeleteDC(hDC);
    return !fError;
    }

/*
 * AbortProc
 *
 * Purpose:
 *  Abort procedure for printing the pages.
 *
 * Parameters:
 *  hDC             HDC on which printing is happening.
 *  iErr            int error code.
 *
 * Return Value:
 *  BOOL            TRUE to continue the print job, FALSE otherwise.
 */

BOOL APIENTRY AbortProc(HDC hDC, int iErr)
    {
    MSG     msg;

    while (!g_fCancelPrint
        && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
        if (NULL==g_hDlgPrint
            || !IsDialogMessage(g_hDlgPrint, &msg))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    return !g_fCancelPrint;
    }

/*
 * PrintDlgProc
 *
 * Purpose:
 *  Modeless dialog procedure for the dialog displayed while Patron
 *  is printing pages.
 */

BOOL APIENTRY PrintDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    TCHAR           szFormat[40];
    TCHAR           szOutput[80];

    switch (iMsg)
        {
        case WM_INITDIALOG:
            EnableMenuItem(GetSystemMenu(hDlg, FALSE), SC_CLOSE
                , MF_GRAYED);
            return TRUE;

        case WM_COMMAND:
            //Cancel button was pressed.
            g_fCancelPrint=TRUE;
            ShowWindow(hDlg, SW_HIDE);
            return TRUE;

        case PRINTM_PAGEUPDATE:
            GetDlgItemText(hDlg, ID_PAGESTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTPAGE, szOutput);
            return TRUE;

        case PRINTM_COPYUPDATE:
            GetDlgItemText(hDlg, ID_COPYSTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTCOPY, szOutput);
            return TRUE;
        }

    return FALSE;
    }


PATRON.CPP

/*
 * PATRON.CPP
 * Patron Chapter 12
 *
 * WinMain which is all we need for the basic application.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "patron.h"

extern "C"
{
OLEDBGDATA_MAIN("PATRON")
}

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCPatronFrame   pFR;
    FRAMEINIT       fi;
    WPARAM          wRet=0;

   #ifndef WIN32
    int             cMsg=96;

    while (!SetMessageQueue(cMsg) && (cMsg-=8));
   #endif

    //Attempt to allocate and initialize the application
    pFR=new CPatronFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return -1;

    fi.idsMin=IDS_FRAMEMIN;
    fi.idsMax=IDS_FRAMEMAX;
    fi.idsStatMin=IDS_STATMESSAGEMIN;
    fi.idsStatMax=IDS_STATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=CW_USEDEFAULT;
    fi.cy=CW_USEDEFAULT;

    //If we can initialize pFR, start chugging messages
    if (pFR->FInit(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

/*
 * CPatronFrame::CPatronFrame
 * CPatronFrame::~CPatronFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WInMain
 */

CPatronFrame::CPatronFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    : CFrame(hInst, hInstPrev, pszCmdLine, nCmdShow)
    {
    m_fInitialized=FALSE;
    m_fOleStdInit=FALSE;
    m_pIClassDataTran=NULL;
    return;
    }

CPatronFrame::~CPatronFrame(void)
    {
    if (NULL!=m_pIClassDataTran)
        {
        m_pIClassDataTran->LockServer(FALSE);
        m_pIClassDataTran->Release();
        }

    OleFlushClipboard();

    if (m_fOleStdInit)
        OleStdUninitialize();

    if (m_fInitialized)
        OleUninitialize();
    return;
    }

/*
 * CPatronFrame::FInit
 *
 * Purpose:
 *  Call OleInitialize then calling down into the base class
 *  initialization.
 *
 * Parameters:
 *  pFI             PFRAMEINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CPatronFrame::FInit(PFRAMEINIT pFI)
    {
    DWORD       dwVer;
    HRESULT     hr;

    dwVer=OleBuildVersion();

    if (rmm!=HIWORD(dwVer))
        return FALSE;

    if (FAILED(OleInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    if (!OleStdInitialize(m_hInst))
        return FALSE;

    m_fOleStdInit=TRUE;

    //Lock the data transfer object factory as an optimization.
    hr=CoGetClassObject(CLSID_DataTransferObject
        , CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory
        , (PPVOID)&m_pIClassDataTran);

    if (SUCCEEDED(hr))
        m_pIClassDataTran->LockServer(TRUE);

    return CFrame::FInit(pFI);
    }

/*
 * CPatronFrame::CreateCClient
 *
 * Purpose:
 *  Constructs a new client specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient        Pointer to the new client object.
 */

PCClient CPatronFrame::CreateCClient(void)
    {
    return (PCClient)(new CPatronClient(m_hInst));
    }

/*
 * CPatronFrame::FRegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CPatronFrame::FRegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //First let the standard frame do its thing
    if (!CFrame::FRegisterAllClasses())
        return FALSE;

    //We need double-clicks now and for object activation later.
    wc.style         = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
    wc.hInstance     = m_hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PagesWndProc;
    wc.cbWndExtra    = CBPAGESWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPAGES;

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

    return TRUE;
    }

/*
 * CPatronFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the Patron frame window that processes extra
 *  File menu items as well as the Page menu.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CPatronFrame::OnCommand(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PCPatronDoc     pDoc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many GizmoBar notifications.
     */
    if (m_fInit)
        return 0L;

    pDoc=(PCPatronDoc)m_pCL->ActiveDocument();

    if (NULL!=pDoc && (IDM_VERBMIN <= wID) && (IDM_VERBMAX >= wID))
        {
        pDoc->ActivateObject(wID-IDM_VERBMIN);
        return 0L;
        }

    switch (wID)
        {
        case IDM_FILEPRINT:
            pDoc->Print(m_hWnd);
            return 0L;

        case IDM_FILEPRINTERSETUP:
            pDoc->PrinterSetup(m_hWnd, FALSE);
            return 0L;

        case IDM_EDITPASTESPECIAL:
            pDoc->FPasteSpecial(m_hWnd);
            return 0L;

        case IDM_EDITDELETEOBJECT:
            pDoc->Delete();
            return 0L;

        case IDM_EDITINSERTOBJECT:
            pDoc->FInsertObject(m_hWnd);
            return 0L;

        //CHAPTER12MOD
        case IDM_EDITLINKS:
            pDoc->FEditLinks(m_hWnd);
            return 0L;
        //End CHAPTER12MOD

        case IDM_PAGENEWPAGE:
            pDoc->NewPage();
            break;

        case IDM_PAGEDELETEPAGE:
            pDoc->DeletePage();
            break;

        case IDM_PAGENEXTPAGE:
            pDoc->NextPage();
            break;

        case IDM_PAGEPREVIOUSPAGE:
            pDoc->PreviousPage();
            break;

        case IDM_PAGEFIRSTPAGE:
            pDoc->FirstPage();
            break;

        case IDM_PAGELASTPAGE:
            pDoc->LastPage();
            break;

        //CHAPTER12MOD
        case IDM_PAGESHOWOBJECTS:
            {
            BOOL    fTemp;

            //First get the current state, then toggle it.
            fTemp=pDoc->FShowOrQueryObjectTypes(TRUE, FALSE);
            pDoc->FShowOrQueryObjectTypes(FALSE, !fTemp);
            }
            break;
        //End CHAPTER12MOD

        default:
           return CFrame::OnCommand(hWnd, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPatronFrame::CreateGizmos
 *
 * Purpose:
 *  Procedure to create all the necessary gizmobar buttons.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of gizmos added to the bar.
 */

UINT CPatronFrame::CreateGizmos(void)
    {
    UINT            iLast;
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;

    //Insert the standard ones.
    iLast=CFrame::CreateGizmos();

    //Remove Undo:  we don't use it.
    m_pGB->Remove(IDM_EDITUNDO);

    /*
     * Insert Print File Import in the 5th position and account
     * for it in iLast.
     */
    m_pGB->Add(utCmd, 4, IDM_FILEPRINT, m_dxB, m_dyB
        , NULL, NULL, 6, uState);

    iLast++;

    m_pGB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //Add New Page, and Delete Page
    m_pGB->Add(utCmd, iLast++, IDM_PAGENEWPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 2, uState);
    m_pGB->Add(utCmd, iLast++, IDM_PAGEDELETEPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 3, uState);

    m_pGB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //First, Prev, Next, Last pages.
    m_pGB->Add(utCmd, iLast++, IDM_PAGEFIRSTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 4, uState);
    m_pGB->Add(utCmd, iLast++, IDM_PAGEPREVIOUSPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 5, uState);
    m_pGB->Add(utCmd, iLast++, IDM_PAGENEXTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 6, uState);
    m_pGB->Add(utCmd, iLast++, IDM_PAGELASTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 7, uState);

    return iLast;
    }

/*
 * CPatronFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CPatronFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    PCPatronDoc     pDoc;
    BOOL            fOK=FALSE;
    BOOL            fCallDefault=TRUE;
    UINT            uTemp;
    UINT            uTempE;
    UINT            uTempD;

    pDoc=(PCPatronDoc)m_pCL->ActiveDocument();

    uTempE=MF_ENABLED | MF_BYCOMMAND;
    uTempD=MF_DISABLED | MF_GRAYED | MF_BYCOMMAND;
    uTemp=((NULL!=pDoc) ? uTempE : uTempD);

    if (m_phMenu[0]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_FILEPRINT, uTemp);

        if (NULL!=pDoc)
            fOK=pDoc->FQueryPrinterSetup();

        EnableMenuItem(hMenu, IDM_FILEPRINTERSETUP
            , (fOK) ? uTempE : uTempD);
        }

    if (m_phMenu[1]==hMenu)
        {
        if (NULL!=pDoc)
            fOK=pDoc->FQueryPaste();

        EnableMenuItem(hMenu, IDM_EDITPASTE
            , (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITPASTESPECIAL
            , (fOK) ? uTempE : uTempD);

        //Cut, Copy, Delete depends on there being a selection.
        if (NULL!=pDoc)
            fOK=pDoc->FQueryObjectSelected(hMenu);
        else
            fOK=FALSE;

        EnableMenuItem(hMenu, IDM_EDITCUT, (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITCOPY
            , (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITDELETEOBJECT
            , (fOK) ? uTempE : uTempD);

        EnableMenuItem(hMenu, IDM_EDITINSERTOBJECT, uTemp);

        //CHAPTER12MOD
        if (NULL!=pDoc)
            fOK=pDoc->FQueryEnableEditLinks();
        else
            fOK=FALSE;

        EnableMenuItem(hMenu, IDM_EDITLINKS
            , (fOK) ? uTempE : uTempD);
        //End CHAPTER12MOD

        //We did the whole menu...
        fCallDefault=FALSE;
        }

    //Page menu
    if (m_phMenu[2]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_PAGENEWPAGE,      uTemp);
        EnableMenuItem(hMenu, IDM_PAGEDELETEPAGE,   uTemp);
        EnableMenuItem(hMenu, IDM_PAGENEXTPAGE,     uTemp);
        EnableMenuItem(hMenu, IDM_PAGEPREVIOUSPAGE, uTemp);
        EnableMenuItem(hMenu, IDM_PAGEFIRSTPAGE,    uTemp);
        EnableMenuItem(hMenu, IDM_PAGELASTPAGE,     uTemp);

        //CHAPTER12MOD
        //Check the Show Objects command or not.
        if (NULL!=pDoc)
            fOK=pDoc->FShowOrQueryObjectTypes(TRUE, FALSE);
        else
            fOK=FALSE;

        CheckMenuItem(hMenu, IDM_PAGESHOWOBJECTS, MF_BYCOMMAND
            | ((fOK) ? MF_CHECKED : MF_UNCHECKED));
        EnableMenuItem(hMenu, IDM_PAGESHOWOBJECTS, uTemp);
        //End CHAPTER12MOD
        }

    if (fCallDefault)
        CFrame::UpdateMenus(hMenu, iMenu);

    return;
    }

/*
 * CPatronFrame::UpdateGizmos
 *
 * Purpose:
 *  Enables and disables gizmos depending on whether we have
 *  a document or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronFrame::UpdateGizmos(void)
    {
    PCDocument  pDoc;
    BOOL        fEnable;

    //Let the default hack on its gizmos.
    CFrame::UpdateGizmos();

    pDoc=m_pCL->ActiveDocument();
    fEnable=(NULL!=pDoc);

    //No document, disable just about everything
    m_pGB->Enable(IDM_FILEPRINT,        fEnable);
    m_pGB->Enable(IDM_FILEPRINTERSETUP, fEnable);

    m_pGB->Enable(IDM_PAGENEWPAGE,      fEnable);
    m_pGB->Enable(IDM_PAGEDELETEPAGE,   fEnable);
    m_pGB->Enable(IDM_PAGEFIRSTPAGE,    fEnable);
    m_pGB->Enable(IDM_PAGEPREVIOUSPAGE, fEnable);
    m_pGB->Enable(IDM_PAGENEXTPAGE,     fEnable);
    m_pGB->Enable(IDM_PAGELASTPAGE,     fEnable);

    return;
    }

/*
 * CPatronFrame::FMessageHook
 *
 * Purpose:
 *  Override of CFrame::FMessageHook so we can specifically trap
 *  WM_MENUSELECT messages for the Object verb menu to provide some
 *  meaningful information on the status strip.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return value
 *                  for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CPatronFrame::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    BOOL        fRet=FALSE;

    *pLRes=0;

    MENUSELECTPARAMS(wItem, wMenuFlags, hMenu);

    //If this is the wrong message, nothing to do.
    if (WM_MENUSELECT!=iMsg)
        return FALSE;

    //This happens when there's no menu selection.
    if (-1==wMenuFlags)
        return FALSE;

    if (MF_POPUP & wMenuFlags)
        {
        /*
         * If this is the cascade verb menu itself, display the same
         * message.  m_phMenu[1] contains the current edit menu
         * handle.
         */
        fRet=((HMENU)wItem==GetSubMenu(m_phMenu[1],MENUPOS_OBJECT));
        }
    else
        {
        /*
         * If the ID is in the verb range, use
         * IDS_ITEMMESSAGEEDITOBJECT message
         */
        fRet=(IDM_VERBMIN <= wItem && IDM_VERBMAX >= wItem);
        }

    if (fRet)
        m_pSS->MessageDisplay(IDM_EDITOBJECT);

    return fRet;
    }


PATRON.H

/*
 * PATRON.H
 * Patron Chapter 12
 *
 * Single include file that pulls in everything needed for other
 * source files in the application.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _PATRON_H_
#define _PATRON_H_

#define INC_OLE2
#include <windows.h>
#include <ole2.h>
#include <ole2ver.h>
#include <olestd.h>
#include <bookguid.h>

extern "C"
    {
    #include <commdlg.h>
   #ifndef WIN32
    #include <print.h>
   #endif
    }

#include <classlib.h>
#include "resource.h"
#include "pages.h"

/*
 * UINT value such that adding one produces zero.  Portable to Win32.
 * This is used to represent a non-existent zero-based UINT value
 */
#define NOVALUE                     ((UINT)-1)

//PATRON.CPP:  Frame object that creates a main window

class CPatronFrame : public CFrame
    {
    private:
        BOOL            m_fInitialized;     //OleInitialize worked
        BOOL            m_fOleStdInit;      //OleStdInitialize worked
        LPCLASSFACTORY  m_pIClassDataTran;  //For locking.

    protected:
        //Overridable for creating a CPatronClient
        virtual PCClient    CreateCClient(void);

        virtual BOOL        FMessageHook(HWND, UINT, WPARAM, LPARAM
                                , LRESULT *);
        virtual BOOL        FRegisterAllClasses(void);
        virtual UINT        CreateGizmos(void);
        virtual LRESULT     OnCommand(HWND, WPARAM, LPARAM);

    public:
        CPatronFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CPatronFrame(void);

        //Overrides
        virtual BOOL        FInit(PFRAMEINIT);

        virtual void        UpdateMenus(HMENU, UINT);
        virtual void        UpdateGizmos(void);

    };

typedef CPatronFrame *PCPatronFrame;

//CLIENT.CPP

/*
 * The only reason we have a derived class here is to override
 * CreateCDocument so we can create our own type as well as
 * overriding NewDocument to perform one other piece of work once
 * the document's been created.
 */

class CPatronClient : public CClient
    {
    protected:
        //Overridable for creating a new CDocument
        virtual PCDocument CreateCDocument(void);

    public:
        CPatronClient(HINSTANCE);
        virtual ~CPatronClient(void);
    };

typedef CPatronClient *PCPatronClient;

//DOCUMENT.CPP

//Constant ID for the pages window that lives in a document window
#define ID_PAGES            723

class CPatronDoc : public CDocument
    {
    //These need access to FQueryPasteFromData, FPasteFromData
    friend class CDropTarget;
    friend class CDropSource;

    protected:
        LONG            m_lVer;         //Loaded data version
        PCPages         m_pPG;          //Pages window in us
        LPSTORAGE       m_pIStorage;    //Root storage for document
        BOOL            m_fPrintSetup;

        class CDropTarget *m_pDropTarget;   //Registered target

        UINT            m_cfEmbeddedObject; //Clipboard formats
        UINT            m_cfObjectDescriptor;
        //CHAPTER12MOD
        UINT            m_cfLinkSource;
        UINT            m_cfLinkSrcDescriptor;
        BOOL            m_fShowTypes;       //Show Objects active?
        //End CHAPTER12MOD

    protected:
        virtual BOOL    FMessageHook(HWND, UINT, WPARAM, LPARAM
            , LRESULT *);

        BOOL            FQueryPasteFromData(LPDATAOBJECT
                            , LPFORMATETC, PTENANTTYPE);
        //CHAPTER12MOD
        BOOL            FQueryPasteLinkFromData(LPDATAOBJECT
                            , LPFORMATETC, PTENANTTYPE);
        //End CHAPTER12MOD
        BOOL            FPasteFromData(LPDATAOBJECT, LPFORMATETC
                            , TENANTTYPE, PPATRONOBJECT, DWORD
                            , BOOL);

    public:
        CPatronDoc(HINSTANCE, PCFrame);
        virtual ~CPatronDoc(void);

        virtual BOOL    FInit(PDOCUMENTINIT);
        virtual void    Clear(void);

        virtual BOOL    FDirtyGet(void);
        virtual void    Delete(void);
        virtual BOOL    FQueryPrinterSetup(void);
        virtual BOOL    FQueryObjectSelected(HMENU);

        virtual UINT    ULoad(BOOL, LPTSTR);
        virtual UINT    USave(UINT, LPTSTR);

        virtual BOOL    Print(HWND);
        virtual UINT    PrinterSetup(HWND, BOOL);

        virtual BOOL    FClip(HWND, BOOL);
        virtual BOOL    FQueryPaste(void);
        virtual BOOL    FPaste(HWND);
        virtual BOOL    FPasteSpecial(HWND);

        //CHAPTER12MOD
        virtual BOOL    FQueryEnableEditLinks(void);
        virtual BOOL    FEditLinks(HWND);
        virtual BOOL    FShowOrQueryObjectTypes(BOOL, BOOL);
        //End CHAPTER12MOD

        virtual UINT    NewPage(void);
        virtual UINT    DeletePage(void);
        virtual UINT    NextPage(void);
        virtual UINT    PreviousPage(void);
        virtual UINT    FirstPage(void);
        virtual UINT    LastPage(void);

        virtual void    Rename(LPTSTR);
        virtual BOOL    FInsertObject(HWND);
        virtual void    ActivateObject(LONG);
    };

typedef CPatronDoc *PCPatronDoc;

//Hook for Print Dialog to hide Setup... button
UINT CALLBACK PrintDlgHook(HWND, UINT, WPARAM, LPARAM);

//Drag-drop objects we need in the document

class CDropTarget : public IDropTarget
    {
    protected:
        ULONG               m_cRef;
        PCPatronDoc         m_pDoc;

        LPDATAOBJECT        m_pIDataObject;  //From DragEnter
        BOOL                m_fPendingRepaint;
        POINTL              m_ptPick;        //Pick-up offsets
        POINTL              m_ptLast;        //Last drag point
        SIZEL               m_szl;           //Object size
        BOOL                m_fFeedback;     //Draw feedback?
        FORMATETC           m_fe;            //Real dropping format

    public:
        CDropTarget(PCPatronDoc);
        ~CDropTarget(void);

        //IDropTarget interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP DragEnter(LPDATAOBJECT, DWORD, POINTL,LPDWORD);
        STDMETHODIMP DragOver(DWORD, POINTL, LPDWORD);
        STDMETHODIMP DragLeave(void);
        STDMETHODIMP Drop(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
    };

typedef CDropTarget *PCDropTarget;

class CDropSource : public IDropSource
    {
    protected:
        ULONG               m_cRef;

    public:
        CDropSource(void);
        ~CDropSource(void);

        //IDropSource interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP QueryContinueDrag(BOOL, DWORD);
        STDMETHODIMP GiveFeedback(DWORD);
    };

typedef CDropSource *PCDropSource;

#endif //_PATRON_H_


PRINT.CPP

/*
 * PRINT.CPP
 * Patron Chapter 12
 *
 * Implementation of printing functions for both CPatronDoc
 * and CPages classes.  These functions are here to keep clutter
 * down in document.cpp and pages.cpp.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

static HWND g_hDlgPrint=NULL;
static BOOL g_fCancelPrint=FALSE;

/*
 * CPatronDoc::Print
 *
 * Purpose:
 *  Prints the current document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *
 * Return Value:
 *  BOOL            TRUE if printing happened, FALSE if it didn't
 *                  start or didn't complete.
 */

BOOL CPatronDoc::Print(HWND hWndFrame)
    {
    PRINTDLG        pd;
    BOOL            fSuccess;

    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);
    pd.hwndOwner  =hWndFrame;
    pd.nCopies    =1;
    pd.nFromPage  =(USHORT)-1;
    pd.nToPage    =(USHORT)-1;
    pd.nMinPage   =1;
    pd.nMaxPage   =m_pPG->NumPagesGet();

    pd.lpfnPrintHook=PrintDlgHook;

    //Get the current document printer settings
    pd.hDevMode=m_pPG->DevModeGet();

    pd.Flags=PD_RETURNDC | PD_ALLPAGES | PD_COLLATE
        | PD_HIDEPRINTTOFILE | PD_NOSELECTION | PD_ENABLEPRINTHOOK;

    if (!PrintDlg(&pd))
        return FALSE;

    if (NULL!=pd.hDevMode)
        GlobalFree(pd.hDevMode);

    if (NULL!=pd.hDevNames)
        GlobalFree(pd.hDevNames);

    //Go do the actual printing.
    fSuccess=m_pPG->Print(pd.hDC, PSZ(IDS_DOCUMENTNAME), pd.Flags
        , pd.nFromPage, pd.nToPage, pd.nCopies);

    if (!fSuccess)
        {
        MessageBox(m_hWnd, PSZ(IDS_PRINTERROR)
            , PSZ(IDS_DOCUMENTCAPTION), MB_OK);
        }

    return fSuccess;
    }

/*
 * CPatronDoc::PrinterSetup
 *
 * Purpose:
 *  Selects a new printer and options for this document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *  fDefault        BOOL to avoid any dialog and just use the
 *                  default.
 *
 * Return Value:
 *  UINT            Undefined
 */

UINT CPatronDoc::PrinterSetup(HWND hWndFrame, BOOL fDefault)
    {
    PRINTDLG        pd;

    //Attempt to get printer metrics for the default printer.
    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);

    if (fDefault)
        pd.Flags=PD_RETURNDEFAULT;
    else
        {
        pd.hwndOwner=hWndFrame;
        pd.Flags=PD_PRINTSETUP;

        //Get the current document printer settings
        pd.hDevMode=m_pPG->DevModeGet();
        }

    if (!PrintDlg(&pd))
        return FALSE;

    if (!m_pPG->DevModeSet(pd.hDevMode, pd.hDevNames))
        {
        GlobalFree(pd.hDevNames);
        GlobalFree(pd.hDevMode);
        return FALSE;
        }

    FDirtySet(TRUE);
    return 1;
    }

/*
 * PrintDlgHook
 *
 * Purpose:
 *  Callback hook for the Print Dialog so we can hide the Setup
 *  button.  Patron only allows Setup before anything exists on
 *  the page, and is not written to handle setup at Print time.
 */

UINT CALLBACK PrintDlgHook(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    if (WM_INITDIALOG==iMsg)
        {
        HWND        hWnd;

        hWnd=GetDlgItem(hDlg, psh1);
        ShowWindow(hWnd, SW_HIDE);
        return TRUE;
        }

    return FALSE;
    }

//CHAPTER12MOD
/*
 * CPatronDoc::FQueryPrinterSetup
 *
 * Purpose:
 *  Returns whether or not the Printer Setup menu item can be
 *  enabled.  Once you create a tenant in any page, Printer Setup
 *  is voided simply to keep this sample simple, that is, we don't
 *  have to worry about reorganizing potentially large amounts
 *  of layout after we start plopping down objects.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE to enable the menu, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPrinterSetup(void)
    {
    return m_fPrintSetup;
    }
//End CHAPTER12MOD

/*
 * CPages::DevModeSet
 *
 * Purpose:
 *  Provides the Pages with the current printer information.
 *
 * Parameters:
 *  hDevMode        HGLOBAL to the memory containing the DEVMODE.
 *                  This function assumes responsibility for this
 *                  handle.
 *  hDevNames       HGLOBAL providing the driver name and device
 *                  name from which we can create a DC for
 *                  information.
 *
 * Return Value:
 *  BOOL            TRUE if we could accept this configuration,
 *                  FALSE otherwise.  If we return TRUE we also
 *                  delete the old memory we hold.
 */

BOOL CPages::DevModeSet(HGLOBAL hDevMode, HGLOBAL hDevNames)
    {
    LPDEVNAMES      pdn;
    LPTSTR          psz;
    HGLOBAL         hMem;
    PDEVICECONFIG   pdc;
    LPDEVMODE       pdm;
    LPSTREAM        pIStream;
    HRESULT         hr;
    ULONG           cbDevMode, cbWrite;
    BOOL            fRet=FALSE;

    if (NULL==hDevMode || NULL==hDevNames)
        return FALSE;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    /*
     * DEVMODE is variable length--total length in hDevMode, so the
     * amount to write is that plus string space.  We subtract
     * sizeof(DEVMODE) as that is already included from GlobalSize.
     */
    cbDevMode=GlobalSize(hDevMode);
    cbWrite=cbDevMode+sizeof(DEVICECONFIG)-sizeof(DEVMODE);

    hMem=GlobalAlloc(GHND, cbWrite);

    if (NULL==hMem)
        {
        pIStream->Release();
        return FALSE;
        }

    pdc=(PDEVICECONFIG)GlobalLock(hMem);    //This always works
    pdm=(LPDEVMODE)GlobalLock(hDevMode);    //This might not

    if (NULL!=pdm)
        {
        pdc->cb=cbWrite;
        pdc->cbDevMode=cbDevMode;
        memcpy(&pdc->dm, pdm, (int)cbDevMode);
        GlobalUnlock(hDevMode);

        psz=(LPTSTR)GlobalLock(hDevNames);

        if (NULL!=psz)
            {
            pdn=(LPDEVNAMES)psz;
            lstrcpy(pdc->szDriver, psz+pdn->wDriverOffset);
            lstrcpy(pdc->szDevice, psz+pdn->wDeviceOffset);
            lstrcpy(pdc->szPort,   psz+pdn->wOutputOffset);

            pIStream->Write(pdc, cbWrite, &cbWrite);
            GlobalUnlock(hDevNames);
            fRet=TRUE;
            }
        }

    GlobalUnlock(hMem);
    GlobalFree(hMem);

    pIStream->Release();

    if (!fRet)
        return FALSE;

    GlobalFree(hDevNames);
    GlobalFree(hDevMode);

    return ConfigureForDevice();
    }

/*
 * CPages::DevModeGet
 *
 * Purpose:
 *  Retrieves a copy of the current DEVMODE structure for this
 *  Pages window.  The caller is responsible for this memory.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HGLOBAL         Handle to the memory containing the DEVMODE
 *                  structure.
 */

HGLOBAL CPages::DevModeGet(void)
    {
    HGLOBAL         hMem;
    LPVOID          pv;
    ULONG           cbDevMode, cbRead;
    LARGE_INTEGER   li;
    LPSTREAM        pIStream;
    HRESULT         hr;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    //Read how much to allocate for the DEVMODE structure
    LISet32(li, CBSEEKOFFSETCBDEVMODE);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);
    pIStream->Read(&cbDevMode, sizeof(ULONG), &cbRead);

    hMem=GlobalAlloc(GHND, cbDevMode);

    if (NULL!=hMem)
        {
        pv=(LPVOID)GlobalLock(hMem);
        pIStream->Read(pv, cbDevMode, &cbRead);
        GlobalUnlock(hMem);
        }

    pIStream->Release();
    return hMem;
    }

//CHAPTER12MOD
/*
 * CPages::DevReadConfig
 *
 * Purpose:
 *  Public function to read the current device configuration and
 *  optionally return an information context for it.
 *
 *
 * Parameters:
 *  ppcd            PCOMBINEDEVICE * in which to return a pointer
 *                  to an allocated structure that has all the
 *                  device information we want.  Ignored if NULL.
 *                  This is allocated with the task allocator.
 *  phDC            HDC * in which to return the information
 *                  context.  If NULL, no IC is created.  Caller
 *                  becomes responsible for the returned IC.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::DevReadConfig(PCOMBINEDEVICE *ppcd, HDC *phDC)
    {
    HRESULT         hr;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    PCOMBINEDEVICE  pcd;
    ULONG           cb, cbRead;
    LARGE_INTEGER   li;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    /*
     * Allocate the COMBINEDEVICE structure including the variable
     * information past the DEVMODE part.
     */

    hr=CoGetMalloc(MEMCTX_TASK, &pIMalloc);

    if (FAILED(hr))
        {
        pIStream->Release();
        return FALSE;
        }

    /*
     * Read size of the DEVICECONFIG structure including variable
     * portion of DEVMODE.  We need to load all this information
     * for CreateIC.  To this size we'll add the size of
     * DVTARGETDEVICE in order to allocate a COMBINEDEVICE.
     */

    pIStream->Read(&cb, sizeof(DWORD), &cbRead);

    pcd=(PCOMBINEDEVICE)pIMalloc->Alloc(cb+sizeof(DVTARGETDEVICE));

    if (NULL==pcd)
        {
        pIMalloc->Release();
        pIStream->Release();
        return FALSE;
        }

    //Now get the real information.
    LISet32(li, 0);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);
    pIStream->Read(&(pcd->dc), cb, &cbRead);
    pIStream->Release();

    /*
     * If requested, complete the DVTARGETDEVICE structure in
     * pcd and store pcd in *ppcd for return.
     */

    if (NULL!=ppcd)
        {
        WORD    cb=sizeof(DVTARGETDEVICE);

        pcd->td.tdSize=cb;
        pcd->td.tdExtDevmodeOffset=cb;
        pcd->td.tdDriverNameOffset=cb+sizeof(DEVMODE);
        pcd->td.tdDeviceNameOffset=cb+sizeof(DEVMODE)
            +(CCHDEVICENAME*sizeof(TCHAR));
        pcd->td.tdPortNameOffset  =cb+sizeof(DEVMODE)
            +(CCHDEVICENAME*2*sizeof(TCHAR));

        *ppcd=pcd;
        }

    //Create an IC if requested.
    if (NULL!=phDC)
        {
        //Get the DC then configure
        *phDC=CreateIC(pcd->dc.szDriver, pcd->dc.szDevice
            , pcd->dc.szPort, &(pcd->dc.dm));

        if (NULL==*phDC)
            return FALSE;
        }

    //pcd is a temporary allocation in this case
    if (NULL==ppcd)
        pIMalloc->Free(pcd);

    pIMalloc->Release();
    return (NULL!=*phDC);
    }
//End CHAPTER12MOD

/*
 * CPages::ConfigureForDevice
 *
 * Purpose:
 *  Recalculates our drawing configuration based on the contents of
 *  an hDC.  If no HDC is given we use the contents of our DevMode
 *  stream.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::ConfigureForDevice(void)
    {
    POINT           ptOffset, ptPaper;
    RECT            rc;
    HDC             hDC;
    CHourglass      hg;

    //CHAPTER12MOD
    if (!DevReadConfig(NULL, &hDC))
        return FALSE;
    //End CHAPTER12MOD

    //Get usable page dimensions:  already sensitive to orientation
    m_cx=GetDeviceCaps(hDC, HORZSIZE)*10-16; //*10: mm to LOMETRIC
    m_cy=GetDeviceCaps(hDC, VERTSIZE)*10-16; //-16: for driver bugs.

    //Calculate the printer-limited margins on sides in LOMETRIC.
    Escape(hDC, GETPRINTINGOFFSET, NULL, NULL, &ptOffset);
    Escape(hDC, GETPHYSPAGESIZE,   NULL, NULL, &ptPaper);

    SetRect(&rc, ptOffset.x, ptOffset.y, ptPaper.x, ptPaper.y);
    SetMapMode(hDC, MM_LOMETRIC);
    RectConvertMappings(&rc, hDC, FALSE);

    //Left and top margins are the printing offset.
    m_xMarginLeft= rc.left+8;   //+8 to match -16 above
    m_yMarginTop =-rc.top+8;    //LOMETRIC makes this negative.

    //Right is (paper width)-(usable width)-(left margin)
    m_xMarginRight =rc.right-m_cx-m_xMarginLeft;

    //Bottom is (paper height)-(usable height)-(top margin)+1
    m_yMarginBottom=-rc.bottom-m_cy-m_yMarginTop+1;

    UpdateScrollRanges();

    DeleteDC(hDC);
    return TRUE;
    }

/*
 * CPages::Print
 *
 * Purpose:
 *  Prints a specified range of pages to a given hDC.  Repeats for
 *  a given number of copies.
 *
 * Parameters:
 *  hDC             HDC to which we print.
 *  pszDoc          LPTSTR providing the document name.
 *  dwFlags         DWORD flags from PrintDlg
 *  iPageStart      UINT starting page index (one based)
 *  iPageEnd        UINT ending page index (one based).  Includes
 *                  this page.
 *  cCopies         UINT number of copies to print.  If PD_COLLATE
 *                  in dwFlags is set, we print multiple copies of
 *                  each page as we cycle through.  Otherwise we
 *                  cycle multiple times.
 *
 * Return Value:
 *  None
 */

BOOL CPages::Print(HDC hDC, LPTSTR pszDoc, DWORD dwFlags
    , UINT iPageStart, UINT iPageEnd, UINT cCopies)
    {
    BOOL        fError=FALSE;
    int         iPage, iPageInc;
    int         iUserPage, cPages;
    UINT        iRepeat, cRepeat;
    UINT        iCycle, cCycles;
    UINT        iPageHold=m_iPageCur;
    HWND        hWndT, hWndTop=NULL;
    DOCINFO     di;
    PCDocument  pDoc;

    //Validate hDC and page ranges
    if (NULL==hDC)
        return FALSE;

    if ((PD_PAGENUMS & dwFlags))
        {
        if (-1==iPageStart)
            iPageStart=0;
        else
            iPageStart--;   //Switch to zero offset.

        if (-1==iPageEnd)
            iPageEnd=m_cPages-1;
        else
            iPageEnd--;     //Switch to zero offset.
        }
    else //Can't test PD_ALLPAGES with & since it's defined as 0L
        {
        iPageStart=0;
        iPageEnd=m_cPages-1;
        }

    //Arrange cycles and repeats depending on cCopies and collating
    if (PD_COLLATE & dwFlags)
        {
        cCycles=cCopies;
        cRepeat=1;
        }
    else
        {
        cCycles=1;
        cRepeat=cCopies;
        }

    //Disable the frame window to prevent reentrancy while printing.
    hWndT=GetParent(m_hWnd);
    pDoc=(PCDocument)SendMessage(hWndT, DOCM_PDOCUMENT, 0, 0L);

    if (NULL!=pDoc)
        {
        PCFrame pFR;

        pFR=pDoc->FrameGet();
        hWndTop=pFR->Window();
        EnableWindow(hWndTop, FALSE);
        }

    SetAbortProc(hDC, AbortProc);
    g_fCancelPrint=FALSE;

    //If these don't work then we'll just live without a dialog.
    g_hDlgPrint=CreateDialog(m_hInst, MAKEINTRESOURCE(IDD_PRINTING)
        , hWndTop, PrintDlgProc);

    //Increment for either direction.
    iPageInc=(iPageStart > iPageEnd) ? -1 : 1;

    //Initial entries in dialog box.
    cPages=1+((int)(iPageEnd-iPageStart)*iPageInc);

    SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE, 1, (LPARAM)cPages);
    SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, 1, (LPARAM)cRepeat);

    di.cbSize=sizeof(DOCINFO);
    di.lpszDocName=pszDoc;
    di.lpszOutput=NULL;

    if (StartDoc(hDC, &di) > 0)
        {
        /*
         * Iterate over the pages, repeating each page depending on
         * the copies we want and if we have collate enabled.
         */

        for (iCycle=1; iCycle <= cCycles; iCycle++)
            {
            if (PD_COLLATE & dwFlags)
                {
                SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, iCycle
                    , (LPARAM)cCycles);
                }

            //iPageInc controls direction
            for (iPage=iPageStart; ; iPage+=iPageInc)
                {
                iUserPage=1+((iPage-(int)iPageStart)*iPageInc);

                SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE
                    , iUserPage, (LPARAM)cPages);

                m_iPageCur=iPage;   //We restore this later.

                for (iRepeat=1; iRepeat <= cRepeat; iRepeat++)
                    {
                    if (!(PD_COLLATE & dwFlags))
                        {
                        SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE
                            , iRepeat, (LPARAM)cRepeat);
                        }

                    StartPage(hDC);
                    Draw(hDC, TRUE, TRUE);

                    if (EndPage(hDC) < 0)
                        fError=TRUE;

                    if (fError || g_fCancelPrint)
                        break;
                    }

                if (fError || g_fCancelPrint)
                    break;

                //If we just printed the last page, time to quit.
                if (iPage==(int)iPageEnd)
                    break;
                }

            if (fError || g_fCancelPrint)
                break;
            }

        if (!fError)
            EndDoc(hDC);
        else
            AbortDoc(hDC);
        }
    else
        fError=TRUE;

    //Set the page back to what it was before all this started.
    m_iPageCur=iPageHold;

    EnableWindow(hWndTop, TRUE);
    SetFocus(hWndTop);
    DestroyWindow(g_hDlgPrint);
    DeleteDC(hDC);
    return !fError;
    }

/*
 * AbortProc
 *
 * Purpose:
 *  Abort procedure for printing the pages.
 *
 * Parameters:
 *  hDC             HDC on which printing is happening.
 *  iErr            int error code.
 *
 * Return Value:
 *  BOOL            TRUE to continue the print job, FALSE otherwise.
 */

BOOL APIENTRY AbortProc(HDC hDC, int iErr)
    {
    MSG     msg;

    while (!g_fCancelPrint
        && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
        if (NULL==g_hDlgPrint
            || !IsDialogMessage(g_hDlgPrint, &msg))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    return !g_fCancelPrint;
    }

/*
 * PrintDlgProc
 *
 * Purpose:
 *  Modeless dialog procedure for the dialog displayed while Patron
 *  is printing pages.
 */

BOOL APIENTRY PrintDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    TCHAR           szFormat[40];
    TCHAR           szOutput[80];

    switch (iMsg)
        {
        case WM_INITDIALOG:
            EnableMenuItem(GetSystemMenu(hDlg, FALSE), SC_CLOSE
                , MF_GRAYED);
            return TRUE;

        case WM_COMMAND:
            //Cancel button was pressed.
            g_fCancelPrint=TRUE;
            ShowWindow(hDlg, SW_HIDE);
            return TRUE;

        case PRINTM_PAGEUPDATE:
            GetDlgItemText(hDlg, ID_PAGESTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTPAGE, szOutput);
            return TRUE;

        case PRINTM_COPYUPDATE:
            GetDlgItemText(hDlg, ID_COPYSTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTCOPY, szOutput);
            return TRUE;
        }

    return FALSE;
    }


RESOURCE.H

/*
 * RESOURCE.H
 * Patron Chapter 12
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Note that we augment classlib.h which we include before this.

//Position of Window menu.
#undef  WINDOW_MENU
#define WINDOW_MENU                 3

//Position of the "Object" item on the Edit menu.
#define MENUPOS_OBJECT              8
#define MENUPOS_OBJECTONPOPUP       5

//Popup menu
#define IDR_RIGHTPOPUPMENU          2

//Total number of menus
#undef  CMENUS
#ifdef MDI
#define CMENUS                      5
#else
#define CMENUS                      4
#endif

//Menu command identifiers.

#define IDM_FILEPRINT               (IDM_CUSTOMFILEMIN)
#define IDM_FILEPRINTERSETUP        (IDM_CUSTOMFILEMIN+1)

#define IDM_EDITPASTESPECIAL        (IDM_CUSTOMEDITMIN)
#define IDM_EDITDELETEOBJECT        (IDM_CUSTOMEDITMIN+1)
#define IDM_EDITINSERTOBJECT        (IDM_CUSTOMEDITMIN+2)
#define IDM_EDITOBJECT              (IDM_CUSTOMEDITMIN+3)
//CHAPTER12MOD
#define IDM_EDITLINKS               (IDM_CUSTOMEDITMIN+4)
//End CHAPTER12MOD

#define IDM_PAGENEWPAGE             (IDM_CUSTOMMIN)
#define IDM_PAGEDELETEPAGE          (IDM_CUSTOMMIN+1)
#define IDM_PAGENEXTPAGE            (IDM_CUSTOMMIN+2)
#define IDM_PAGEPREVIOUSPAGE        (IDM_CUSTOMMIN+3)
#define IDM_PAGEFIRSTPAGE           (IDM_CUSTOMMIN+4)
#define IDM_PAGELASTPAGE            (IDM_CUSTOMMIN+5)
//CHAPTER12MOD
#define IDM_PAGESHOWOBJECTS         (IDM_CUSTOMMIN+6)
//End CHAPTER12MOD

//This special item is for the dynamic verbs on the Edit/Object item.
#define IDM_VERBMIN                 (IDM_CUSTOMMIN+100)
#define IDM_VERBMAX                 (IDM_CUSTOMMIN+200)

/*
 * IDs for StatStrip handling of popup menus:  must be in order of menu
 * CLASSRES.H already defines ID_MENUFILE and ID_MENUEDIT for us.
 */

#undef ID_MENUWINDOW
#undef ID_MENUHELP

#define ID_MENUPAGE                 (ID_MENUCUSTOMMIN+0)
#define ID_MENUWINDOW               (ID_MENUCUSTOMMIN+1)
#define ID_MENUHELP                 (ID_MENUCUSTOMMIN+2)

//Stringtable IDs.  Keep sequential for each group.
#define IDS_FRAMEMIN                IDS_STANDARDFRAMEMIN
#define IDS_FRAMEMAX                IDS_STANDARDFRAMEMAX

#define IDS_DOCUMENTMIN             IDS_STANDARDDOCMIN
#define IDS_PRINTERROR              (IDS_CUSTOMDOCMIN+0)
#define IDS_DOCUMENTNAME            (IDS_CUSTOMDOCMIN+1)
#define IDS_PASTEASPATRON           (IDS_CUSTOMDOCMIN+2)
#define IDS_PASTEMETAFILE           (IDS_CUSTOMDOCMIN+3)
#define IDS_PASTEASMETAFILE         (IDS_CUSTOMDOCMIN+4)
#define IDS_PASTEDIB                (IDS_CUSTOMDOCMIN+5)
#define IDS_PASTEASDIB              (IDS_CUSTOMDOCMIN+6)
#define IDS_PASTEBITMAP             (IDS_CUSTOMDOCMIN+7)
#define IDS_PASTEASBITMAP           (IDS_CUSTOMDOCMIN+8)
#define IDS_PASTEOBJECT             (IDS_CUSTOMDOCMIN+9)
#define IDS_PASTEASOBJECT           (IDS_CUSTOMDOCMIN+10)
//CHAPTER12MOD
#define IDS_PASTELINK               (IDS_CUSTOMDOCMIN+11)
#define IDS_PASTEASLINK             (IDS_CUSTOMDOCMIN+12)
#define IDS_DOCUMENTMAX             (IDS_CUSTOMDOCMIN+12)
//End CHAPTER12MOD

//These are specifically for the StatStrip
#define IDS_STATMESSAGEMIN                  IDS_STANDARDSTATMESSAGEMIN

#define IDS_ITEMMESSAGEFILEPRINT            (IDS_CUSTOMSTATMESSAGEMIN+0)
#define IDS_ITEMMESSAGEFILEPRINTERSETUP     (IDS_CUSTOMSTATMESSAGEMIN+1)

#define IDS_MENUMESSAGEPAGE                 (IDS_CUSTOMSTATMESSAGEMIN+2)
#define IDS_ITEMMESSAGEPAGENEWPAGE          (IDS_CUSTOMSTATMESSAGEMIN+3)
#define IDS_ITEMMESSAGEPAGEDELETEPAGE       (IDS_CUSTOMSTATMESSAGEMIN+4)
#define IDS_ITEMMESSAGEPAGENEXTPAGE         (IDS_CUSTOMSTATMESSAGEMIN+5)
#define IDS_ITEMMESSAGEPAGEPREVIOUSPAGE     (IDS_CUSTOMSTATMESSAGEMIN+6)
#define IDS_ITEMMESSAGEPAGEFIRSTPAGE        (IDS_CUSTOMSTATMESSAGEMIN+7)
#define IDS_ITEMMESSAGEPAGELASTPAGE         (IDS_CUSTOMSTATMESSAGEMIN+8)
#define IDS_ITEMMESSAGEEDITPASTESPECIAL     (IDS_CUSTOMSTATMESSAGEMIN+9)
#define IDS_ITEMMESSAGEEDITDELETEOBJECT     (IDS_CUSTOMSTATMESSAGEMIN+10)
#define IDS_ITEMMESSAGEEDITINSERTOBJECT     (IDS_CUSTOMSTATMESSAGEMIN+11)
#define IDS_ITEMMESSAGEEDITOBJECT           (IDS_CUSTOMSTATMESSAGEMIN+12)
//CHAPTER12MOD
#define IDS_ITEMMESSAGEEDITLINKS            (IDS_CUSTOMSTATMESSAGEMIN+13)
#define IDS_ITEMMESSAGEPAGESHOWOBJECTS      (IDS_CUSTOMSTATMESSAGEMIN+14)
#define IDS_STATMESSAGEMAX                  (IDS_CUSTOMSTATMESSAGEMIN+14)
//End CHAPTER12MOD

//Definitions for the printing dialog.

#define IDD_PRINTING                2

#define ID_CURRENTPAGE              101
#define ID_CURRENTCOPY              102
#define ID_COPYSTRING               103
#define ID_PAGESTRING               104

#define PRINTM_PAGEUPDATE           (WM_USER+100)
#define PRINTM_COPYUPDATE           (WM_USER+101)

#endif //_RESOURCE_H_


TENANT.CPP

/*
 * TENANT.CPP
 * Patron Chapter 12
 *
 * Implementation of the CTentant class which holds information
 * for a single object on a page.  It maintains position, references
 * to data, and a storage.
 *
 * Copyright (c)1993-1997 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CTenant::CTenant
 * CTenant::~CTenant
 *
 * Constructor Parameters:
 *  dwID            DWORD identifier for this page.
 *  hWnd            HWND of the pages window.
 *  pPG             PCPages to the parent structure.
 */

CTenant::CTenant(DWORD dwID, HWND hWnd, PCPages pPG)
    {
    m_hWnd=hWnd;
    m_dwID=dwID;

    m_fInitialized=0;
    m_pIStorage=NULL;
    m_cOpens=0;

    m_pObj=NULL;
    m_pPG =pPG;
    m_clsID=CLSID_NULL;
    m_fSetExtent=FALSE;

    m_cRef=0;
    m_pIOleObject=NULL;
    m_pIViewObject2=NULL;

    m_pIOleClientSite=NULL;
    m_pIAdviseSink=NULL;

    //CHAPTER12MOD
    m_pmkFile=NULL;
    m_fLinkAvail=TRUE;          //Checked on FLoad
    //End CHAPTER12MOD
    return;
    }

CTenant::~CTenant(void)
    {
    //CHAPTER12MOD
    if (NULL!=m_pmkFile)
        m_pmkFile->Release();
    //End CHAPTER12MOD

    //Object pointers cleaned up in Close.

    //We delete our own interfaces since we control them
    if (NULL!=m_pIAdviseSink)
        delete m_pIAdviseSink;

    if (NULL!=m_pIOleClientSite)
        delete m_pIOleClientSite;

    return;
    }

/*
 * CTenant::QueryInterface
 * CTenant::AddRef
 * CTenant::Release
 *
 * Purpose:
 *  IUnknown members for CTenant object.
 */

STDMETHODIMP CTenant::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IOleClientSite==riid)
        *ppv=m_pIOleClientSite;

    if (IID_IAdviseSink2==riid || IID_IAdviseSink==riid)
        *ppv=m_pIAdviseSink;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return E_NOINTERFACE;
    }

STDMETHODIMP_(ULONG) CTenant::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CTenant::Release(void)
    {
    ULONG           cRefT;

    cRefT=--m_cRef;

    if (0L==m_cRef)
        delete this;

    return cRefT;
    }

/*
 * CTenant::GetID
 *
 * Return Value:
 *  DWORD           dwID field in this tenant.  This function is
 *                  only here to avoid hiding inline implementations
 *                  in tenant.h.
 */

DWORD CTenant::GetID(void)
    {
    return m_dwID;
    }

/*
 * CTenant::GetStorageName
 *
 * Parameters:
 *  pszName         LPTSTR to a buffer in which to store the storage
 *                  name for this tenant.
 *
 * Return Value:
 *  UINT            Number of characters stored.
 */

UINT CTenant::GetStorageName(LPTSTR pszName)
    {
    return wsprintf(pszName, TEXT("Tenant %lu"), m_dwID);
    }

/*
 * CTenant::UCreate
 *
 * Purpose:
 *  Creates a new tenant of the given CLSID, which can be either a
 *  static bitmap or metafile or any compound document object.
 *
 * Parameters:
 *  tType           TENANTTYPE to create, either a static metafile,
 *                  bitmap, or some kind of compound document object
 *                  This determines which OleCreate* call we use.
 *  pvType          LPVOID providing the relevant pointer from which
 *                  to create the tenant, depending on iType.
 *  pFE             LPFORMATETC specifying the type of renderings
 *                  to use.
 *  pptl            LPPOINTL in which we store offset coordinates.
 *  pszl            LPSIZEL where this object should store its
 *                  lometric extents.
 *  pIStorage       LPSTORAGE of the page we live in.  We have to
 *                  create another storage in this for the tenant.
 *  ppo             PPATRONOBJECT containing placement data.
 *  dwData          DWORD with extra data, sensitive to iType.
 *
 * Return Value:
 *  UINT            A UCREATE_* value depending on what we
 *                  actually do.
 */

UINT CTenant::UCreate(TENANTTYPE tType, LPVOID pvType
    , LPFORMATETC pFE, LPPOINTL pptl, LPSIZEL pszl
    , LPSTORAGE pIStorage, PPATRONOBJECT ppo, DWORD dwData)
    {
    HRESULT             hr;
    LPUNKNOWN           pObj;
    UINT                uRet=UCREATE_GRAPHICONLY;

    if (NULL==pvType || NULL==pIStorage)
        return UCREATE_FAILED;

    //Fail if this is called for an already living tenant.
    if (m_fInitialized)
        return UCREATE_FAILED;

    m_fInitialized=TRUE;

    //Create a new storage for this tenant.
    if (!FOpen(pIStorage))
        return UCREATE_FAILED;

    /*
     * Get the placement info if it's here.  We either have a non-
     * NULL PPATRONOBJECT in ppo or we have to use default
     * placement and retrieve the size from the object itself.
     */
    pszl->cx=0;
    pszl->cy=0;

    if (NULL!=ppo)
        {
        *pFE=ppo->fe;
        *pptl=ppo->ptl;
        *pszl=ppo->szl;     //Could be 0,0 , so we ask object

        uRet=UCREATE_PLACEDOBJECT;
        }

    hr=E_FAIL;

    //Now create an object based specifically for the type.
    switch (tType)
        {
        case TENANTTYPE_NULL:
            break;

        case TENANTTYPE_STATIC:
            /*
             * We could use OleCreateStaticFromData here which does
             * pretty much what we're doing below.  However, it does
             * not allow us to control whether we paste a bitmap or
             * a metafile--it uses metafile first, bitmap second.
             * For this reason we'll use code developed in Chapter
             * 6's FreeLoader to affect the paste.
             */
            hr=CreateStatic((LPDATAOBJECT)pvType, pFE, &pObj);
            break;

        case TENANTTYPE_EMBEDDEDOBJECT:
            hr=OleCreate(*((LPCLSID)pvType), IID_IUnknown
                , OLERENDER_DRAW, NULL, NULL, m_pIStorage
                , (PPVOID)&pObj);
            break;

        case TENANTTYPE_EMBEDDEDFILE:
#if defined(WIN32) && !defined(UNICODE)
            OLECHAR pwcsTemp[MAX_PATH];
            mbstowcs(pwcsTemp, (LPTSTR)pvType, MAX_PATH);
            hr=OleCreateFromFile(CLSID_NULL, pwcsTemp
                , IID_IUnknown, OLERENDER_DRAW, NULL, NULL
                , m_pIStorage, (PPVOID)&pObj);
#else
            hr=OleCreateFromFile(CLSID_NULL, (LPTSTR)pvType
                , IID_IUnknown, OLERENDER_DRAW, NULL, NULL
                , m_pIStorage, (PPVOID)&pObj);
#endif
            break;

        case TENANTTYPE_EMBEDDEDOBJECTFROMDATA:
            hr=OleCreateFromData((LPDATAOBJECT)pvType, IID_IUnknown
                , OLERENDER_DRAW, NULL, NULL, m_pIStorage
                , (PPVOID)&pObj);
            break;

        //CHAPTER12MOD
        case TENANTTYPE_LINKEDFILE:
#if defined(WIN32) && !defined(UNICODE)
            OLECHAR pwcsFile[MAX_PATH];
            mbstowcs(pwcsFile, (LPTSTR)pvType, MAX_PATH);
            hr=OleCreateLinkToFile(pwcsFile, IID_IUnknown
                , OLERENDER_DRAW, NULL, NULL, m_pIStorage
                , (PPVOID)&pObj);
#else
            hr=OleCreateLinkToFile((LPTSTR)pvType, IID_IUnknown
                , OLERENDER_DRAW, NULL, NULL, m_pIStorage
                , (PPVOID)&pObj);
#endif
            break;

        case TENANTTYPE_LINKEDOBJECTFROMDATA:
            hr=OleCreateLinkFromData((LPDATAOBJECT)pvType
                , IID_IUnknown, OLERENDER_DRAW, NULL, NULL
                , m_pIStorage, (PPVOID)&pObj);
            break;
        //End CHAPTER12MOD
        default:
            break;
        }

    //If creation didn't work, get rid of the element FOpen created.
    if (FAILED(hr))
        {
        Destroy(pIStorage);
        return UCREATE_FAILED;
        }

    //We don't get the size if PatronObject data was seen already.
    if (!FObjectInitialize(pObj, pFE, dwData))
        {
        Destroy(pIStorage);
        return UCREATE_FAILED;
        }

    if (0==pszl->cx && 0==pszl->cy)
        {
        SIZEL   szl;

        //Try to get the real size of the object, default to 2"*2"
        SETSIZEL((*pszl), 2*LOMETRIC_PER_INCH, 2*LOMETRIC_PER_INCH);
        hr=E_FAIL;

        //Try IViewObject2 first, then IOleObject as a backup.
        if (NULL!=m_pIViewObject2)
            {
            hr=m_pIViewObject2->GetExtent(m_fe.dwAspect, -1, NULL
                , &szl);
            }
        else
            {
            if (NULL!=m_pIOleObject)
                hr=m_pIOleObject->GetExtent(m_fe.dwAspect, &szl);
            }

        if (SUCCEEDED(hr))
            {
            //Convert HIMETRIC to our LOMETRIC mapping
            SETSIZEL((*pszl), szl.cx/10, szl.cy/10);
            }
        }

    return uRet;
    }

/*
 * CTenant::FLoad
 *
 * Purpose:
 *  Recreates the object living in this tenant in place of calling
 *  FCreate.  This is used in loading as opposed to new creation.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE of the page we live in.
 *  pti             PTENTANTINFO containing persistent information.
 *                  The ID value in this structure is ignored.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CTenant::FLoad(LPSTORAGE pIStorage, PTENANTINFO pti)
    {
    HRESULT         hr;
    LPUNKNOWN       pObj;

    if (NULL==pIStorage || NULL==pti)
        return FALSE;

    //Fail if this is called for an already living tenant.
    if (m_fInitialized)
        return FALSE;

    m_fInitialized=TRUE;

    //Open the storage for this tenant.
    if (!FOpen(pIStorage))
        return FALSE;

    //CHAPTER12MOD
    /*
     * NOTE:  If you do not pass an IOleClientSite to OleLoad
     * it will not automatically reconnect a linked object to
     * the running source via IOleLink::BindIfRunning.  Since
     * we do not pass m_pIOleClientSite here, we'll call
     * BindIfRunning ourselves in FObjectInitialize.
     */
    //End CHAPTER12MOD
    hr=OleLoad(m_pIStorage, IID_IUnknown, NULL, (PPVOID)&pObj);

    if (FAILED(hr))
        {
        Destroy(pIStorage);
        return FALSE;
        }

    m_fSetExtent=pti->fSetExtent;
    FObjectInitialize(pObj, &pti->fe, NULL);

    RectSet(&pti->rcl, FALSE, FALSE);
    return TRUE;
    }

/*
 * CTenant::GetInfo
 *
 * Purpose:
 *  Retrieved a TENANTINFO structure for this tenant.
 *
 * Parameters:
 *  pti             PTENANTINFO structure to fill
 *
 * Return Value:
 *  None
 */

void CTenant::GetInfo(PTENANTINFO pti)
    {
    if (NULL!=pti)
        {
        pti->dwID=m_dwID;
        pti->rcl=m_rcl;
        pti->fe=m_fe;
        pti->fSetExtent=m_fSetExtent;
        }

    return;
    }

/*
 * CTenant::FObjectInitialize
 * (Protected)
 *
 * Purpose:
 *  Performs operations necessary after creating an object or
 *  reloading one from storage.
 *
 * Parameters:
 *  pObj            LPUNKNOWN of the object in this tenant.
 *  pFE             LPFORMATETC describing the graphic here.
 *  dwData          DWORD extra data.  If pFE->dwAspect==
 *                  DVASPECT_ICON then this is the iconic metafile.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CTenant::FObjectInitialize(LPUNKNOWN pObj, LPFORMATETC pFE
    , DWORD dwData)
    {
    HRESULT         hr;
    LPPERSIST       pIPersist=NULL;
    DWORD           dw;
    PCDocument      pDoc;
    TCHAR           szFile[CCHPATHMAX];
    //CHAPTER12MOD
    LPOLELINK       pIOleLink=NULL;
    //End CHAPTER12MOD

    if (NULL==pObj || NULL==pFE)
        return FALSE;

    m_pObj=pObj;
    m_fe=*pFE;
    m_fe.ptd=NULL;
    m_dwState=TENANTSTATE_DEFAULT;

    /*
     * Determine the type:  Static or Embedded.  If Static,
     * this will have CLSID_Picture_Metafile or CLSID_Picture_Dib.
     * Otherwise it's embedded.  Later we'll add a case for links.
     */
    m_tType=TENANTTYPE_EMBEDDEDOBJECT;

    if (SUCCEEDED(pObj->QueryInterface(IID_IPersist
        , (PPVOID)&pIPersist)))
        {
        CLSID   clsid=CLSID_NULL;

        hr=pIPersist->GetClassID(&clsid);

        //If we don't have a CLSID, default to static
        if (FAILED(hr) || CLSID_Picture_Metafile==clsid
            || CLSID_Picture_Dib==clsid)
            m_tType=TENANTTYPE_STATIC;

        pIPersist->Release();
        }

    //CHAPTER12MOD
    //Check if this is a linked object.
    if (SUCCEEDED(pObj->QueryInterface(IID_IOleLink
        , (PPVOID)&pIOleLink)))
        {
        LPMONIKER   pmk;

        hr=pIOleLink->GetSourceMoniker(&pmk);

        if (FAILED(hr) || NULL==pmk)
            m_tType=TENANTTYPE_STATIC;
        else
            {
            m_tType=TENANTTYPE_LINKEDOBJECT;
            pmk->Release();

            //Connect to the object if the source is running.
            pIOleLink->BindIfRunning();
            }

        pIOleLink->Release();
        }
    //End CHAPTER12MOD

    m_pIViewObject2=NULL;
    hr=pObj->QueryInterface(IID_IViewObject2
        , (PPVOID)&m_pIViewObject2);

    if (FAILED(hr))
        return FALSE;

    m_pIViewObject2->SetAdvise(m_fe.dwAspect, 0, m_pIAdviseSink);

    //We need an IOleObject most of the time, so get one here.
    m_pIOleObject=NULL;
    hr=pObj->QueryInterface(IID_IOleObject
         , (PPVOID)&m_pIOleObject);

    /*
     * Follow up object creation with advises and so forth.  If
     * we cannot get IOleObject here, then we know we can't do
     * any IOleObject actions from here on--object is static.
     */
    if (FAILED(hr))
        return TRUE;

    /*
     * Get the MiscStatus bits and check for OLEMISC_ONLYICONIC.
     * If set, force dwAspect in m_fe to DVASPECT_ICON so we
     * remember to draw it properly and do extents right.
     */
    m_pIOleObject->GetMiscStatus(m_fe.dwAspect, &m_grfMisc);

    if (OLEMISC_ONLYICONIC & m_grfMisc)
        m_fe.dwAspect=DVASPECT_ICON;

    /*
     * We could pass m_pIOleClientSite in an OleCreate* call, but
     * since this function could be called after OleLoad, we still
     * need to do this here, so it's always done here...
     */
    m_pIOleObject->SetClientSite(m_pIOleClientSite);
    m_pIOleObject->Advise(m_pIAdviseSink, &dw);

    OleSetContainedObject(m_pIOleObject, TRUE);

    /*
     * For IOleObject::SetHostNames we need the application name
     * and the document name (which is passed in the object
     * parameter).  The design of Patron doesn't give us nice
     * structured access to the name of the document we're in, so
     * I grab the parent of the Pages window (the document) and
     * send it DOCM_PDOCUMENT which returns us the pointer.
     * Roundabout, but it works.
     */

    pDoc=(PCDocument)SendMessage(GetParent(m_hWnd), DOCM_PDOCUMENT
        , 0, 0L);

    if (NULL!=pDoc)
        pDoc->FilenameGet(szFile, CCHPATHMAX);
    else
        szFile[0]=0;

    NotifyOfRename(szFile, NULL);

    /*
     * This might have been Display as Icon (or the
     * OLEMISC_ONLYICONIC flag was set) in which case
     * m_fe.dwAspect=DVASPECT_ICON.  If this is coming from
     * a Display As Icon choice, then then dwData is a handle
     * to a metafile with the iconic aspect.  We take this and shove
     * it into the cache for this aspect, releasing the content
     * aspect.  OLESTD has a nice function that does this:
     * OleStdSwitchDisplayAspect, which also handles a later case
     * (Chapter 14) when we might want to switch BACK to content.
     * That, however, requires the Change Type dialog...
     *
     * If the object is marked as OLEMISC_ONLYICONIC, then it
     * is responsible for rendering the iconic image--not us.
     */

    if ((DVASPECT_ICON & m_fe.dwAspect) && NULL!=dwData)
        {
        //Temps to give to OleStdSwitchDisplayAspect
        DWORD       dw=DVASPECT_CONTENT;
        BOOL        fUpdate;

        OleStdSwitchDisplayAspect(m_pIOleObject, &dw, DVASPECT_ICON
            , (HGLOBAL)(UINT)dwData, TRUE, FALSE, NULL, &fUpdate);
        }

    return TRUE;
    }

/*
 * CTenant::FOpen
 *
 * Purpose:
 *  Retrieves the IStorage associated with this tenant.  The
 *  IStorage is owned by the tenant and thus the tenant always
 *  holds a reference count.
 *
 *  If the storage is already open for this tenant, then this
 *  function will AddRef it; therefore the caller must always
 *  match an FOpen with a Close.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE above this tenant (which has its
 *                  own storage).
 *
 * Return Value:
 *  BOOL            TRUE if opening succeeds, FALSE otherwise.
 */

BOOL CTenant::FOpen(LPSTORAGE pIStorage)
    {
    HRESULT     hr=NOERROR;
    DWORD       dwMode=STGM_TRANSACTED | STGM_READWRITE
                    | STGM_SHARE_EXCLUSIVE;
    TCHAR       szTemp[32];

    if (NULL==m_pIStorage)
        {
        if (NULL==pIStorage)
            return FALSE;

        /*
         * Attempt to open the storage under this ID.  If there is
         * none, then create it.  In either case we end up with an
         * IStorage that we either save in pPage or release.
         */

        GetStorageName(szTemp);
        OLECHAR pwcsTemp[32];
#if defined(WIN32) && !defined(UNICODE)
        mbstowcs(pwcsTemp, szTemp, 32);
#else
        pwcsTemp = szTemp;
#endif
        hr=pIStorage->OpenStorage(pwcsTemp, NULL, dwMode, NULL, 0
            , &m_pIStorage);

        if (FAILED(hr))
            {
            hr=pIStorage->CreateStorage(pwcsTemp, dwMode, 0, 0
                , &m_pIStorage);
            }
        }
    else
        m_pIStorage->AddRef();

    if (FAILED(hr))
        return FALSE;

    m_cOpens++;

    m_pIOleClientSite=new CImpIOleClientSite(this, this);
    m_pIAdviseSink=new CImpIAdviseSink(this, this);

    if (NULL==m_pIOleClientSite || NULL==m_pIAdviseSink)
        return FALSE;

    return TRUE;
    }

/*
 * CTenant::Close
 *
 * Purpose:
 *  Possibly commits the storage, then releases it reversing the
 *  reference count from FOpen.  If the reference on the storage
 *  goes to zero, the storage is forgotten.  However, the object we
 *  contain is still held and as long as it's active the storage
 *  remains alive.
 *
 * Parameters:
 *  fCommit         BOOL indicating if we're to commit.
 *
 * Return Value:
 *  None
 */

void CTenant::Close(BOOL fCommit)
    {
    if (fCommit)
        Update();

    if (NULL!=m_pIStorage)
        {
        m_pIStorage->Release();

        /*
         * We can't use a zero reference count to know when to NULL
         * this since other things might have AddRef'd the storage.
         */
        if (0==--m_cOpens)
            {
            m_pIStorage=NULL;

            //Close the object saving if necessary
            if (NULL!=m_pIOleObject)
                {
                m_pIOleObject->Close(OLECLOSE_SAVEIFDIRTY);
                m_pIOleObject->Release();
                m_pIOleObject=NULL;
                }

            //Release all other held pointers
            if (NULL!=m_pIViewObject2)
                {
                m_pIViewObject2->SetAdvise(m_fe.dwAspect, 0, NULL);
                m_pIViewObject2->Release();
                m_pIViewObject2=NULL;
                }

            if (NULL!=m_pObj)
                {
                //We know we only hold one ref from UCreate or FLoad
                m_pObj->Release();
                m_pObj=NULL;
                }
            }
        }

    return;
    }

/*
 * CTenant::Update
 *
 * Purpose:
 *  Forces a common on the page if it's open.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the object is open, FALSE otherwise.
 */

BOOL CTenant::Update(void)
    {
    LPPERSISTSTORAGE    pIPS;

    if (NULL!=m_pIStorage)
        {
        /*
         * We need to OleSave again because we might have changed
         * the size or position of this tenant.  We also need to
         * save the rectangle on the page, since that's not known
         * to OLE.
         */
        m_pObj->QueryInterface(IID_IPersistStorage, (PPVOID)&pIPS);

        //This fails for static objects...so we improvise if so
        if (FAILED(OleSave(pIPS, m_pIStorage, TRUE)))
            {
            //This is essentially what OleSave does.
            WriteClassStg(m_pIStorage, m_clsID);
            pIPS->Save(m_pIStorage, TRUE);
            }

        pIPS->SaveCompleted(NULL);
        pIPS->Release();

        m_pIStorage->Commit(STGC_ONLYIFCURRENT);
        }

    return FALSE;
    }

/*
 * CTenant::Destroy
 *
 * Purpose:
 *  Removes this page from the given storage.  The caller should
 *  eventually delete this CTenant object to free the object herein.
 *  Nothing is committed when being destroyed.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE contianing this page on which to call
 *                  DestroyElement
 *
 * Return Value:
 *  None
 */

void CTenant::Destroy(LPSTORAGE pIStorage)
    {
    TCHAR       szTemp[32];

    if (NULL!=pIStorage)
        {
        if (NULL!=m_pIOleObject)
            m_pIOleObject->Close(OLECLOSE_NOSAVE);

        if (NULL!=m_pIStorage)
            {
            //Remove all reference/open counts on this storage.
            while (0!=m_cOpens)
                {
                m_pIStorage->Release();
                m_cOpens--;
                }
            }

        GetStorageName(szTemp);
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsTemp[32];
        mbstowcs(pwcsTemp, szTemp, 32);
        pIStorage->DestroyElement(pwcsTemp);
#else
        pIStorage->DestroyElement(szTemp);
#endif
        m_pIStorage=NULL;
        }

    //m_pObj is released in destructor.
    return;
    }

/*
 * CTenant::Select
 *
 * Purpose:
 *  Selects or deselects the tenant.
 *
 * Parameters:
 *  fSelect         BOOL indicating the new state of the tenant.
 *
 * Return Value:
 *  None
 */

void CTenant::Select(BOOL fSelect)
    {
    BOOL        fWasSelected;
    DWORD       dwState;
    RECT        rc;
    HDC         hDC;

    fWasSelected=(BOOL)(TENANTSTATE_SELECTED & m_dwState);

    //Nothing to do when there's no change.
    if (fWasSelected==fSelect)
        return;

    dwState=m_dwState & ~TENANTSTATE_SELECTED;
    m_dwState=dwState | ((fSelect) ? TENANTSTATE_SELECTED : 0);

    /*
     * Draw sizing handles to show the selection state.  We convert
     * things to MM_TEXT since that's what this function expects.
     */

    RECTFROMRECTL(rc, m_rcl);
    RectConvertMappings(&rc, NULL, TRUE);
    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

    hDC=GetDC(m_hWnd);

    OleUIDrawHandles(&rc, hDC, OLEUI_HANDLES_INSIDE
        | OLEUI_HANDLES_NOBORDER| OLEUI_HANDLES_USEINVERSE
        , CXYHANDLE, !fWasSelected);

    ReleaseDC(m_hWnd, hDC);
    return;
    }

/*
 * CTenant::ShowAsOpen
 *
 * Purpose:
 *  Draws or removes the hatch pattern over an object.
 *
 * Parameters:
 *  fOpen           BOOL indicating the open state of this tenant.
 *
 * Return Value:
 *  None
 */

void CTenant::ShowAsOpen(BOOL fOpen)
    {
    BOOL        fWasOpen;
    DWORD       dwState;
    RECT        rc;
    HDC         hDC;

    fWasOpen=(BOOL)(TENANTSTATE_OPEN & m_dwState);

    dwState=m_dwState & ~TENANTSTATE_OPEN;
    m_dwState=dwState | ((fOpen) ? TENANTSTATE_OPEN : 0);

    //If this was not open, then just hatch, otherwise repaint.
    if (!fWasOpen && fOpen)
        {
        RECTFROMRECTL(rc, m_rcl);
        RectConvertMappings(&rc, NULL, TRUE);
        OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

        hDC=GetDC(m_hWnd);
        OleUIDrawShading(&rc, hDC, OLEUI_SHADE_FULLRECT, 0);
        ReleaseDC(m_hWnd, hDC);
        }

    if (fWasOpen && !fOpen)
        Repaint();

    return;
    }

/*
 * CTenant::ShowYourself
 *
 * Purpose:
 *  Function that really just implements IOleClientSite::ShowObject.
 *  Here we first check if the tenant is fully visible, and if so,
 *  then nothing needs to happen.  Otherwise, if the upper left
 *  corner of the tenant is in the upper left visible quadrant of
 *  the window, we'll also consider ourselves done.  Otherwise
 *  we'll put the upper left corner of the object at the upper left
 *  corner of the window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CTenant::ShowYourself(void)
    {
    RECTL       rcl;
    RECT        rc;
    POINT       pt1, pt2;

    //Scrolling deals in device units; get our rectangle in those.
    RectGet(&rcl, TRUE);

    //Get the window rectangle offset for the current scroll pos.
    GetClientRect(m_hWnd, &rc);
    OffsetRect(&rc, m_pPG->m_xPos, m_pPG->m_yPos);

    //Check if the object is already visible. (macro in bookguid.h)
    SETPOINT(pt1, (int)rcl.left,  (int)rcl.top);
    SETPOINT(pt2, (int)rcl.right, (int)rcl.bottom);

    if (PtInRect(&rc, pt1) && PtInRect(&rc, pt2))
        return;

    //Check if the upper left is within the upper left quadrant
    if (((int)rcl.left > rc.left
        && (int)rcl.left < ((rc.right+rc.left)/2))
        && ((int)rcl.top > rc.top
        && (int)rcl.top < ((rc.bottom+rc.top)/2)))
        return;

    //These are macros in INC\BOOK1632.H
    SendScrollPosition(m_hWnd, WM_HSCROLL, rcl.left-8);
    SendScrollPosition(m_hWnd, WM_VSCROLL, rcl.top-8);
    return;
    }

/*
 * CTenant::AddVerbMenu
 *
 * Purpose:
 *  Creates the variable verb menu item for the object in this
 *  tenant.
 *
 * Parmeters:
 *  hMenu           HMENU on which to add items.
 *  iPos            UINT position on that menu to add items.
 *
 * Return Value:
 *  None
 */

void CTenant::AddVerbMenu(HMENU hMenu, UINT iPos)
    {
    HMENU       hMenuTemp;
    LPOLEOBJECT pObj=m_pIOleObject;

    //If we're static, say we have no object.
    if (TENANTTYPE_STATIC==m_tType)
        pObj=NULL;

    OleUIAddVerbMenu(pObj, NULL, hMenu, iPos, IDM_VERBMIN
        , IDM_VERBMAX, FALSE, 0, &hMenuTemp);

    return;
    }

/*
 * CTenant::CopyEmbeddedObject
 *
 * Purpose:
 *  Copies an embedded object to the given data object (via SetData,
 *  assuming this is a data transfer object for clipboard/drag-drop)
 *  if that's what we're holding.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT in which to store the copy.
 *  pFE             LPFORMATETC into which to copy CF_EMBEDDEDOBJECT
 *                  if we put that in the data object.
 *  pptl            LPPOINTL to the pick point (NULL outside of
 *                  drag-drop);
 *
 * Return Value:
 *  None
 */

void CTenant::CopyEmbeddedObject(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, LPPOINTL pptl)
    {
    LPPERSISTSTORAGE    pIPS;
    STGMEDIUM           stm;
    FORMATETC           fe;
    HRESULT             hr;
    UINT                cf;
    POINTL              ptl;
    SIZEL               szl;

    //Can only copy embeddings.
    if (TENANTTYPE_EMBEDDEDOBJECT!=m_tType || NULL==m_pIOleObject)
        return;

    if (NULL==pptl)
        {
        SETPOINTL(ptl, 0, 0);
        pptl=&ptl;
        }

    /*
     * Create CF_EMBEDDEDOBJECT.  This is simply an IStorage with a
     * copy of the embedded object in it.  The not-so-simple part is
     * getting an IStorage to stuff it in.  For this operation we'll
     * use a temporary compound file.
     */

    stm.pUnkForRelease=NULL;
    stm.tymed=TYMED_ISTORAGE;
    hr=StgCreateDocfile(NULL, STGM_TRANSACTED | STGM_READWRITE
        | STGM_CREATE| STGM_SHARE_EXCLUSIVE | STGM_DELETEONRELEASE
        , 0, &stm.pstg);

    if (FAILED(hr))
        return;

    m_pObj->QueryInterface(IID_IPersistStorage, (PPVOID)&pIPS);

    if (NOERROR==pIPS->IsDirty())
        {
        OleSave(pIPS, stm.pstg, FALSE);
        pIPS->SaveCompleted(NULL);
        }
    else
        m_pIStorage->CopyTo(0, NULL, NULL, stm.pstg);

    pIPS->Release();

    //stm.pstg now has a copy, so stuff it away.
    cf=RegisterClipboardFormat(CF_EMBEDDEDOBJECT);
    SETDefFormatEtc(fe, cf, TYMED_ISTORAGE);

    if (SUCCEEDED(pIDataObject->SetData(&fe, &stm, TRUE)))
        *pFE=fe;
    else
        ReleaseStgMedium(&stm);

    stm.tymed=TYMED_HGLOBAL;

    /*
     * You want to make sure that if this object is iconic, that you
     * create the object descriptor with DVASPECT_ICON instead of
     * the more typical DVASPECT_CONTENT.  Also remember that
     * the pick point is in HIMETRIC.
     */
    XformSizeInPixelsToHimetric(NULL, (LPSIZEL)pptl, (LPSIZEL)&ptl);

    SETSIZEL(szl, (10*(m_rcl.right-m_rcl.left))
        , (10 * (m_rcl.bottom-m_rcl.top)));

    stm.hGlobal=OleStdGetObjectDescriptorDataFromOleObject
        (m_pIOleObject, NULL, m_fe.dwAspect, ptl, &szl);

    cf=RegisterClipboardFormat(CF_OBJECTDESCRIPTOR);
    SETDefFormatEtc(fe, cf, TYMED_HGLOBAL);

    if (FAILED(pIDataObject->SetData(&fe, &stm, TRUE)))
        ReleaseStgMedium(&stm);

    return;
    }

//CHAPTER12MOD
/*
 * CTenant::ShowObjectType
 *
 * Purpose:
 *  Tells the object to switch on or off an indication of whether
 *  it is linked or embedded.
 *
 * Parameters:
 *  fShow           BOOL indicating to show the type (TRUE) or
 *                  not (FALSE)
 *
 * Return Value:
 *  None
 */

void CTenant::ShowObjectType(BOOL fShow)
    {
    BOOL        fWasShow;
    DWORD       dwState;
    RECT        rc;
    HDC         hDC;

    fWasShow=(BOOL)(TENANTSTATE_SHOWTYPE & m_dwState);

    dwState=m_dwState & ~TENANTSTATE_SHOWTYPE;
    m_dwState=dwState | ((fShow) ? TENANTSTATE_SHOWTYPE : 0);

    /*
     * If this wasn't previously shown, just add the line,
     * otherwise repaint.
     */
    if (!fWasShow && fShow)
        {
        RECTFROMRECTL(rc, m_rcl);
        RectConvertMappings(&rc, NULL, TRUE);
        OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

        hDC=GetDC(m_hWnd);
        OleUIShowObject(&rc, hDC
            , (TENANTTYPE_LINKEDOBJECT==m_tType));
        ReleaseDC(m_hWnd, hDC);
        }

    if (fWasShow && !fShow)
        Repaint();

    return;
    }

/*
 * CTenant::NotifyOfRename
 *
 * Purpose:
 *  Instructs the tenant that the document was saved under a
 *  different name.  In order to keep the right compound document
 *  user interface, this tenant needs to tell its object through
 *  IOleObject::SetHostNames.
 *
 * Parameters:
 *  pszFile         LPTSTR of filename.
 *  pmk             LPMONIKER of the new filename.
 *
 * Return Value:
 *  None
 */

void CTenant::NotifyOfRename(LPTSTR pszFile, LPMONIKER pmk)
    {
    TCHAR       szObj[40];
    TCHAR       szApp[40];

    if (NULL==m_pIOleObject)
        return;

    if (TEXT('\0')==*pszFile)
        {
        LoadString(m_pPG->m_hInst, IDS_UNTITLED, szObj
            , sizeof(szObj));
        }
    else
        {
        GetFileTitle(pszFile, szObj, sizeof(szObj));

       #ifndef WIN32
        //Force filenames to uppercase in DOS versions.
        AnsiUpper(szObj);
       #endif
        }

    LoadString(m_pPG->m_hInst, IDS_CAPTION, szApp, sizeof(szApp));
#if defined(WIN32) && !defined(UNICODE)
    OLECHAR pwcsObj[40];
    OLECHAR pwcsApp[40];
    mbstowcs(pwcsObj,szObj,40);
    mbstowcs(pwcsApp,szApp,40);
    m_pIOleObject->SetHostNames(pwcsApp, pwcsObj);
#else
    m_pIOleObject->SetHostNames(szApp, szObj);
#endif
    if (NULL!=pmk)
        {
        if (NULL!=m_pmkFile)
            m_pmkFile->Release();

        m_pmkFile=pmk;
        m_pmkFile->AddRef();

        m_pIOleObject->SetMoniker(OLEWHICHMK_CONTAINER, pmk);
        }

    return;
    }
//End CHAPTER12MOD

/*
 * CTenant::Activate
 *
 * Purpose:
 *  Activates a verb on the object living in the tenant.  Does
 *  nothing for static objects.
 *
 * Parameters:
 *  iVerb           LONG of the verb to execute.
 *
 * Return Value:
 *  BOOL            TRUE if the object changed due to this verb
 *                  execution.
 */

BOOL CTenant::Activate(LONG iVerb)
    {
    RECT        rc, rcH;
    CHourglass *pHour;
    SIZEL       szl;

    //Can't activate statics.
    if (TENANTTYPE_STATIC==m_tType || NULL==m_pIOleObject)
        {
        MessageBeep(0);
        return FALSE;
        }

    RECTFROMRECTL(rc, m_rcl);
    RectConvertMappings(&rc, NULL, TRUE);
    XformRectInPixelsToHimetric(NULL, &rc, &rcH);

    pHour=new CHourglass;

    //Get the server running first, then do a SetExtent, then show it
    OleRun(m_pIOleObject);

    if (m_fSetExtent)
        {
        SETSIZEL(szl, rcH.right-rcH.left, rcH.bottom-rcH.top);
        m_pIOleObject->SetExtent(m_fe.dwAspect, &szl);
        m_fSetExtent=FALSE;
        }

    m_pIOleObject->DoVerb(iVerb, NULL, m_pIOleClientSite, 0
        , m_hWnd, &rcH);

    delete pHour;

    //If object changes, IAdviseSink::OnViewChange will see it.
    return FALSE;
    }

/*
 * CTenant::Draw
 *
 * Purpose:
 *  Draws the tenant in its rectangle on the given hDC.  We assume
 *  the DC is already set up for the mapping mode in which our
 *  rectangle is expressed, since the Page we're in tells us both
 *  the rect and the hDC.
 *
 * Parameters:
 *  hDC             HDC in which to draw.  Could be a metafile,
 *                  memory DC, screen, or printer.
 *  ptd             DVTARGETDEVICE * describing the device.
 *  hIC             HDC holding an information context (printing).
 *  xOff, yOff      int offsets for the page in lometric
 *  fNoColor        BOOL indicating if we should do B & W
 *  fPrinter        BOOL indicating if we should render for a
 *                  printer.
 *
 * Return Value:
 *  None
 */

void CTenant::Draw(HDC hDC, DVTARGETDEVICE *ptd, HDC hIC
    , int xOff, int yOff, BOOL fNoColor, BOOL fPrinter)
    {
    HRESULT         hr;
    RECT            rc;
    RECTL           rcl;
    UINT            uMM;

    RECTFROMRECTL(rc, m_rcl);
    OffsetRect(&rc, -xOff, -yOff);
    RECTLFROMRECT(rcl, rc);

    //Repaint erases the rectangle to insure full object cleanup
    if (!fNoColor && !fPrinter)
        {
        COLORREF    cr;
        cr=SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
        ExtTextOut(hDC, rc.left, rc.top, ETO_OPAQUE, &rc, NULL
            , 0, NULL);
        SetBkColor(hDC, cr);
        }

    //We have to use Draw since we have a target device and IC.
    hr=m_pIViewObject2->Draw(m_fe.dwAspect, -1, NULL, ptd, hIC, hDC
        , &rcl, NULL, NULL, 0);

    /*
     * If Draw failed, then perhaps it couldn't work for the device,
     * so try good old OleDraw as a last resort.  The code will
     * generally be OLE_E_BLANK.
     */
    if (FAILED(hr))
        OleDraw(m_pObj, m_fe.dwAspect, hDC, &rc);

    //CHAPTER12MOD
    if (!fPrinter)
    //End CHAPTER12MOD
        {
        /*
         * Draw sizing handles to show the selection state.  We
         * convert things to MM_TEXT since that's what this
         * function expects.
         */
        RectConvertMappings(&rc, NULL, TRUE);
        uMM=SetMapMode(hDC, MM_TEXT);

        if (TENANTSTATE_SELECTED & m_dwState)
            {
            OleUIDrawHandles(&rc, hDC, OLEUI_HANDLES_INSIDE
                | OLEUI_HANDLES_NOBORDER | OLEUI_HANDLES_USEINVERSE
                , CXYHANDLE, TRUE);
            }

        if (TENANTSTATE_OPEN & m_dwState)
            OleUIDrawShading(&rc, hDC, OLEUI_SHADE_FULLRECT, 0);

        //CHAPTER12MOD
        //Distinguish linked and embedded objects.
        if (TENANTSTATE_SHOWTYPE & m_dwState)
            {
            OleUIShowObject(&rc, hDC
                , (TENANTTYPE_LINKEDOBJECT==m_tType));
            }
        //End CHAPTER12MOD

        uMM=SetMapMode(hDC, uMM);
        }

    return;
    }

/*
 * CTenant::Repaint
 * CTenant::Invalidate
 *
 * Purpose:
 *  Repaints the tenant where it lies or invalidates its area
 *  for later repainting.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CTenant::Repaint(void)
    {
    RECT        rc;
    HDC         hDC;

    /*
     * We might be asked to repaint from
     * IOleClientSite::OnShowWindow after we've switched pages if
     * our server was runnnig. This check on m_cOpens prevents that.
     */
    if (0==m_cOpens)
        return;

    hDC=GetDC(m_hWnd);
    SetRect(&rc, m_pPG->m_xPos, m_pPG->m_yPos, 0, 0);
    RectConvertMappings(&rc, NULL, FALSE);

    SetMapMode(hDC, MM_LOMETRIC);
    Draw(hDC, NULL, NULL, rc.left, rc.top, FALSE, FALSE);

    ReleaseDC(m_hWnd, hDC);
    return;
    }

void CTenant::Invalidate(void)
    {
    RECTL       rcl;
    RECT        rc;

    RectGet(&rcl, TRUE);
    RECTFROMRECTL(rc, rcl);

    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);
    InvalidateRect(m_hWnd, &rc, FALSE);

    return;
    }

//CHAPTER12MOD
/*
 * CTenant::TypeGet
 *
 * Purpose:
 *  Returns the type of this tenant
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  TENANTTYPE      Type of the tenant.
 */

TENANTTYPE CTenant::TypeGet(void)
    {
    return m_tType;
    }

/*
 * CTenant::FIsSelected
 *
 * Purpose:
 *  Returns the selection state of this tenant.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if selected, FALSE otherwise.
 */

BOOL CTenant::FIsSelected(void)
    {
    return (BOOL)(m_dwState & TENANTSTATE_SELECTED);
    }

/*
 * CTenant::FConvertToStatic
 *
 * Purpose:
 *  Changes the object that lives in this tenant to a static one.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CTenant::FConvertToStatic(void)
    {
    /*
     * If you SetSourceMoniker in IOleLink to NULL, then the link is
     * gone as far as OLE is concerned.  You only need to make sure
     * the user doesn't have access to other functionality for this
     * object, which we insure by changing our internal type.  We
     * set this on loading if GetSourceMoniker returns NULL.
     */
    m_tType=TENANTTYPE_STATIC;
    return TRUE;
    }

//End CHAPTER12MOD

/*
 * CTenant::ObjectGet
 *
 * Purpose:
 *  Retrieves the LPUNKNOWN of the object in use by this tenant
 *
 * Parameters:
 *  ppUnk           LPUNKNOWN * in which to return the object
 *                  pointer.
 *
 * Return Value:
 *  None
 */

void CTenant::ObjectGet(LPUNKNOWN *ppUnk)
    {
    if (NULL!=ppUnk)
        {
        *ppUnk=m_pObj;
        m_pObj->AddRef();
        }

    return;
    }

/*
 * CTenant::FormatEtcGet
 *
 * Purpose:
 *  Retrieves the FORMATETC in use by this tenant
 *
 * Parameters:
 *  pFE             LPFORMATETC in which to store the information.
 *  fPresentation   BOOL indicating if we want the real format or
 *                  that of the presentation.
 *
 * Return Value:
 *  None
 */

void CTenant::FormatEtcGet(LPFORMATETC pFE, BOOL fPresentation)
    {
    if (NULL!=pFE)
        {
        *pFE=m_fe;

        //If there is no format, use metafile (for embedded objects)
        if (fPresentation || 0==pFE->cfFormat)
            {
            //Don't mess with dwAspect; might be icon or content.
            pFE->cfFormat=CF_METAFILEPICT;
            pFE->tymed=TYMED_MFPICT;
            }
        }

    return;
    }

/*
 * CTenant::SizeGet
 * CTenant::SizeSet
 * CTenant::RectGet
 * CTenant::RectSet
 *
 * Purpose:
 *  Returns or sets the size/position of the object contained here.
 *
 * Parameters:
 *  pszl/prcl       LPSIZEL (Size) or LPRECTL (Rect) with the
 *                  extents of interest.  In Get situations,
 *                  this will receive the extents; in Set it
 *                  contains the extents.
 *  fDevice         BOOL indicating that pszl/prcl is expressed
 *                  in device units.  Otherwise it's LOMETRIC.
 *  fInformObj      (Set Only) BOOL indicating if we need to inform
 *                  the object all.
 *
 * Return Value:
 *  None
 */

void CTenant::SizeGet(LPSIZEL pszl, BOOL fDevice)
    {
    if (!fDevice)
        {
        pszl->cx=m_rcl.right-m_rcl.left;
        pszl->cy=m_rcl.bottom-m_rcl.top;
        }
    else
        {
        RECT        rc;

        SetRect(&rc, (int)(m_rcl.right-m_rcl.left)
            , (int)(m_rcl.bottom-m_rcl.top), 0, 0);

        RectConvertMappings(&rc, NULL, TRUE);

        pszl->cx=(long)rc.left;
        pszl->cy=(long)rc.top;
        }

    return;
    }

void CTenant::SizeSet(LPSIZEL pszl, BOOL fDevice, BOOL fInformObj)
    {
    SIZEL           szl;

    if (!fDevice)
        {
        szl=*pszl;
        m_rcl.right =pszl->cx+m_rcl.left;
        m_rcl.bottom=pszl->cy+m_rcl.top;
        }
    else
        {
        RECT        rc;

        SetRect(&rc, (int)pszl->cx, (int)pszl->cy, 0, 0);
        RectConvertMappings(&rc, NULL, FALSE);

        m_rcl.right =(long)rc.left+m_rcl.left;
        m_rcl.bottom=(long)rc.top+m_rcl.top;

        SETSIZEL(szl, (long)rc.left, (long)rc.top);
        }

    //Tell OLE that this object was resized.
    if (NULL!=m_pIOleObject && fInformObj)
        {
        HRESULT     hr;
        BOOL        fRun=FALSE;

        //Convert our LOMETRIC into HIMETRIC by *=10
        szl.cx*=10;
        szl.cy*=-10;    //Our size is stored negative.

        /*
         * If the MiscStatus bit of OLEMISC_RECOMPOSEONRESIZE
         * is set, then we need to run the object before calling
         * SetExtent to make sure it has a real chance to
         * re-render the object.  We have to update and close
         * the object as well after this happens.
         */

        if (OLEMISC_RECOMPOSEONRESIZE & m_grfMisc)
            {
            if (!OleIsRunning(m_pIOleObject))
                {
                OleRun(m_pIOleObject);
                fRun=TRUE;
                }
            }

        hr=m_pIOleObject->SetExtent(m_fe.dwAspect, &szl);

        /*
         * If the object is not running and it does not have
         * RECOMPOSEONRESIZE, then SetExtent fails.  Make
         * sure that we call SetExtent again (by just calling
         * SizeSet here again) when we next run the object.
         */
        if (SUCCEEDED(hr))
            {
            m_fSetExtent=FALSE;

            if (fRun)
                {
                m_pIOleObject->Update();
                m_pIOleObject->Close(OLECLOSE_SAVEIFDIRTY);
                }
            }
        else
            {
            if (OLE_E_NOTRUNNING==hr)
                m_fSetExtent=TRUE;
            }
        }

    return;
    }

void CTenant::RectGet(LPRECTL prcl, BOOL fDevice)
    {
    if (!fDevice)
        *prcl=m_rcl;
    else
        {
        RECT        rc;

        RECTFROMRECTL(rc, m_rcl);
        RectConvertMappings(&rc, NULL, TRUE);
        RECTLFROMRECT(*prcl, rc);
        }

    return;
    }

void CTenant::RectSet(LPRECTL prcl, BOOL fDevice, BOOL fInformObj)
    {
    SIZEL   szl;
    LONG    cx, cy;

    cx=m_rcl.right-m_rcl.left;
    cy=m_rcl.bottom-m_rcl.top;

    if (!fDevice)
        m_rcl=*prcl;
    else
        {
        RECT        rc;

        RECTFROMRECTL(rc, *prcl);
        RectConvertMappings(&rc, NULL, FALSE);
        RECTLFROMRECT(m_rcl, rc);
        }

    /*
     * Tell ourselves that the size changed, if it did.  SizeSet
     * will call IOleObject::SetExtent for us.
     */
    if ((m_rcl.right-m_rcl.left)!=cx || (m_rcl.bottom-m_rcl.top)!=cy)
        {
        SETSIZEL(szl, m_rcl.right-m_rcl.left, m_rcl.bottom-m_rcl.top);
        SizeSet(&szl, FALSE, fInformObj);
        }

    return;
    }

/*
 * CTenant::CreateStatic
 * (Protected)
 *
 * Purpose:
 *  Creates a new static bitmap or metafile object for this tenant
 *  using a freeloading method allowing us to specify exactly which
 *  type of data we want to paste since OleCreateStaticFromData
 *  doesn't.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *  pFE             LPFORMATETC describing the format to paste.
 *  ppObj           LPUNKNOWN * into which we store the
 *                  object pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, error code otherwise.
 */

HRESULT CTenant::CreateStatic(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, LPUNKNOWN *ppObj)
    {
    HRESULT             hr;
    STGMEDIUM           stm;
    LPUNKNOWN           pIUnknown;
    LPOLECACHE          pIOleCache;
    LPPERSISTSTORAGE    pIPersistStorage;
    CLSID               clsID;

    *ppObj=NULL;

    //Try to get the data desired as specified in pFE->cfFormat
    hr=pIDataObject->GetData(pFE, &stm);

    if (FAILED(hr))
        return hr;

    //Create the object to handle this data.
    if (CF_METAFILEPICT==pFE->cfFormat)
        clsID=CLSID_Picture_Metafile;
    else
        clsID=CLSID_Picture_Dib;

    hr=CreateDataCache(NULL, clsID, IID_IUnknown
        , (PPVOID)&pIUnknown);

    if (FAILED(hr))
        {
        ReleaseStgMedium(&stm);
        return hr;
        }

    m_clsID=clsID;

    //Stuff the data into the object
    pIUnknown->QueryInterface(IID_IPersistStorage
        , (PPVOID)&pIPersistStorage);
    pIPersistStorage->InitNew(m_pIStorage);

    //Now that we have the cache object, shove the data into it.
    pIUnknown->QueryInterface(IID_IOleCache, (PPVOID)&pIOleCache);
    pIOleCache->Cache(pFE, ADVF_PRIMEFIRST, NULL);

    hr=pIOleCache->SetData(pFE, &stm, TRUE);
    pIOleCache->Release();

    //Insure there is a persistent copy on the disk
    WriteClassStg(m_pIStorage, m_clsID);
    pIPersistStorage->Save(m_pIStorage, TRUE);
    pIPersistStorage->SaveCompleted(NULL);
    pIPersistStorage->Release();

    //The cache owns this now.
    ReleaseStgMedium(&stm);

    if (FAILED(hr))
        pIUnknown->Release();
    else
        *ppObj=pIUnknown;

    return hr;
    }


CLIENT.CPP

/*
 * CLIENT.CPP
 * Component Cosmo Chapter 13
 *
 * Implementation of the CCosmoClient class that just makes sure
 * we get a CCosmoDoc on doc creation and that we initialize fully.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cocosmo.h"

/*
 * CCosmoClient::CCosmoClient
 * CCosmoClient::~CCosmoClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame to the frame object.
 */

CCosmoClient::CCosmoClient(HINSTANCE hInst, PCFrame pFR)
    : CClient(hInst, pFR)
    {
    return;
    }

CCosmoClient::~CCosmoClient(void)
    {
    return;
    }

/*
 * CCosmoClient::CreateCDocument
 *
 * Purpose:
 *  Constructs a new document specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::CreateCDocument(void)
    {
    return (PCDocument)(new CCosmoDoc(m_hInst, m_pFR, m_pAdv));
    }

/*
 * CCosmoClient::NewDocument
 *
 * Purpose:
 *  Small override of the CClient::NewDocument that we have just
 *  to check the initial line selection on new document creation.
 *
 * Parameters:
 *  fVisible        BOOL indicating if the document is to be
 *                  visible or not.
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::NewDocument(BOOL fVisible)
    {
    PCDocument  pDoc;

    //Perform default NewDocument first
    pDoc=CClient::NewDocument(fVisible);

    //We know that m_pFR is actually a CCosmoFrame, so type is safe.
    ((PCCosmoFrame)m_pFR)->CheckLineSelection(IDM_LINESOLID);

    return pDoc;
    }


COCOSMO.CPP

/*
 * COCOSMO.CPP
 * Component Cosmo Chapter 13
 *
 * WinMain and CCosmoFrame implementations.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "cocosmo.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCCosmoFrame    pFR;
    FRAMEINIT       fi;
    WPARAM          wRet;

    //Attempt to allocate and initialize the application
    pFR=new CCosmoFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return -1;

    fi.idsMin=IDS_FRAMEMIN;
    fi.idsMax=IDS_FRAMEMAX;
    fi.idsStatMin=IDS_STATMESSAGEMIN;
    fi.idsStatMax=IDS_STATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=440;
    fi.cy=460;

    //If we can initialize pFR, start chugging messages
    if (pFR->Init(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

/*
 * CCosmoFrame::CCosmoFrame
 * CCosmoFrame::~CCosmoFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WInMain
 */

CCosmoFrame::CCosmoFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    : CFrame(hInst, hInstPrev, pszCmdLine, nCmdShow)
    {
    UINT        i;

    for (i=0; i<5; i++)
        m_hBmpLines[i]=NULL;

    m_uIDCurLine=0;
    m_fInitialized=FALSE;
    return;
    }

CCosmoFrame::~CCosmoFrame(void)
    {
    UINT        i;

    for (i=0; i<5; i++)
        {
        if (NULL!=m_hBmpLines[i])
            DeleteObject(m_hBmpLines[i]);
        }

    if (m_fInitialized)
        OleUninitialize();

    return;
    }

/*
 * CCosmoFrame::Init
 *
 * Purpose:
 *  Call OleInitialize then calling down into the base class
 *  initialization.
 *
 * Parameters:
 *  pFI             PFRAMEINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CCosmoFrame::Init(PFRAMEINIT pFI)
    {
    CHECKVER_OLE;

    if (FAILED(OleInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    return CFrame::Init(pFI);
    }

/*
 * CCosmoFrame::CreateCClient
 *
 * Purpose:
 *  Constructs a new client specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient        Pointer to the new client object.
 */

PCClient CCosmoFrame::CreateCClient(void)
    {
    return (PCClient)(new CCosmoClient(m_hInst, this));
    }

/*
 * CCosmoFrame::RegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CCosmoFrame::RegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //First let the standard frame do its thing
    if (!CFrame::RegisterAllClasses())
        return FALSE;

    /*
     * We want a different background color for the document
     * because the Polyline we put in the document will paint
     * with COLOR_WINDOW which by default which is CLASSLIB's
     * default document color.
     */

    GetClassInfo(m_hInst, SZCLASSDOCUMENT, &wc);
    UnregisterClass(SZCLASSDOCUMENT, m_hInst);

    wc.hbrBackground=(HBRUSH)(COLOR_APPWORKSPACE+1);

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

    //No need to register the Polyline window now...

    return TRUE;
    }

/*
 * CCosmoFrame::PreShowInit
 *
 * Purpose:
 *  Called from Init before intially showing the window.  We do
 *  whatever else we want here, modifying nCmdShow as necessary
 *  which affects ShowWindow in Init.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if this initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CCosmoFrame::PreShowInit(void)
    {
    CreateLineMenu();
    CheckLineSelection(IDM_LINESOLID);
    return TRUE;
    }

/*
 * CCosmoFrame::CreateLineMenu
 *
 * Purpose:
 *  Initializes the bitmaps used to create the Line menu and
 *  replaces the text items defined in the application resources
 *  with these bitmaps.  Note that the contents of m_hBmpLines
 *  must be cleaned up when the application terminates.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CreateLineMenu(void)
    {
    HMENU       hMenu;
    HDC         hDC, hMemDC;
    HPEN        hPen;
    HGDIOBJ     hObj;
    TEXTMETRIC  tm;
    UINT        i, cx, cy;

    hMenu=GetSubMenu(GetMenu(m_hWnd), 3);   //Line menu.
    hDC=GetDC(m_hWnd);

    //Create each line in a menu item 8 chars wide, one char high.
    GetTextMetrics(hDC, &tm);
    cx=tm.tmAveCharWidth*8;
    cy=tm.tmHeight;

    /*
     * Create a memory DC in which to draw lines, and bitmaps
     * for each line.
     */
    hMemDC=CreateCompatibleDC(hDC);
    ReleaseDC(m_hWnd, hDC);

    for (i=0; i<5; i++)
        {
        m_hBmpLines[i]=CreateCompatibleBitmap(hMemDC, cx, cy);
        SelectObject(hMemDC, m_hBmpLines[i]);

        PatBlt(hMemDC, 0, 0, cx, cy, WHITENESS);

        hPen=CreatePen(i, 1, 0L);       //i=line style like PS_SOLID
        hObj=SelectObject(hMemDC, hPen);

        MoveToEx(hMemDC, 0, cy/2, NULL);
        LineTo(hMemDC, cx, cy/2);

        ModifyMenu(hMenu, IDM_LINEMIN+i, MF_BYCOMMAND | MF_BITMAP
            , IDM_LINEMIN+i, (LPTSTR)(LONG)(UINT)m_hBmpLines[i]);

        SelectObject(hMemDC, hObj);
        DeleteObject(hPen);
        }

    CheckMenuItem(hMenu, IDM_LINESOLID, MF_CHECKED);
    DeleteDC(hMemDC);

    return;
    }

/*
 * CCosmoFrame::CreateToolbar
 *
 * Purpose:
 *  Procedure to create all the necessary toolbar buttons.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of tools added to the bar.
 */

UINT CCosmoFrame::CreateToolbar(void)
    {
    UINT            iLast;
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;
    UINT            utEx  =GIZMOTYPE_BUTTONATTRIBUTEEX;

    //Insert the standard ones.
    iLast=CFrame::CreateToolbar();

    /*
     * Insert File Import in the 5th position and account for
     * it in iLast.
     */
    m_pTB->Add(utCmd, 4, IDM_FILEIMPORT, m_dxB, m_dyB
        , NULL, m_hBmp, 2, uState);
    iLast++;

    //Separator
    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    /*
     * For the Background bitmap, preserve our use of black
     * (part of the image)
     */
    m_pTB->Add(utCmd, iLast++, IDM_COLORBACKGROUND, m_dxB, m_dyB
        , NULL, m_hBmp, 3, GIZMO_NORMAL | PRESERVE_BLACK);

    m_pTB->Add(utCmd, iLast++, IDM_COLORLINE, m_dxB, m_dyB
        , NULL, m_hBmp, 4, uState);

    //Separator
    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //Line styles.
    m_pTB->Add(utEx, iLast++, IDM_LINESOLID, m_dxB, m_dyB
        , NULL, m_hBmp, 5, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASH, m_dxB, m_dyB
        , NULL, m_hBmp, 6, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 7, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASHDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 8, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASHDOTDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 9, uState);

    return iLast;
    }

/*
 * CCosmoFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the Cosmo frame window that just
 *  processes the line menu and the color menu leaving the
 *  CFrame to do everything else.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CCosmoFrame::OnCommand(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PCCosmoDoc      pDoc;
    TCHAR           szFile[CCHPATHMAX];
    BOOL            fOK;
    UINT            i, uTemp;
    COLORREF        rgColors[16];
    CHOOSECOLOR     cc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many toolbar notifications.
     */
    if (m_fInit)
        return 0L;

    pDoc=(PCCosmoDoc)m_pCL->ActiveDocument();

    /*
     * Check for the line style commands which are
     * IDM_LINEMIN+<style>.  We handle this by changing the menu
     * and toolbar, then we pass it to the document for real
     * processing.
     */
    if (NULL!=pDoc && IDM_LINEMIN <= wID && IDM_LINEMAX >=wID)
        {
        CheckLineSelection(wID);
        pDoc->LineStyleSet(wID-IDM_LINEMIN);
        return 0L;
        }

    switch (wID)
        {
        case IDM_FILEIMPORT:
            szFile[0]=0;
            fOK=SaveOpenDialog(szFile, CCHPATHMAX, IDS_FILEIMPORT
                , TRUE, &i);

            if (fOK)
                {
                uTemp=pDoc->Load(FALSE, szFile);
                pDoc->ErrorMessage(uTemp);
                }

            return (LRESULT)fOK;

        case IDM_COLORBACKGROUND:
        case IDM_COLORLINE:
            //Invoke the color chooser for either color
            uTemp=(IDM_COLORBACKGROUND==wID)
                ? DOCCOLOR_BACKGROUND : DOCCOLOR_LINE;

            for (i=0; i<16; i++)
                rgColors[i]=RGB(0, 0, i*16);

            memset(&cc, 0, sizeof(CHOOSECOLOR));
            cc.lStructSize=sizeof(CHOOSECOLOR);
            cc.lpCustColors=rgColors;
            cc.hwndOwner=hWnd;
            cc.Flags=CC_RGBINIT;
            cc.rgbResult=pDoc->ColorGet(uTemp);

            if (ChooseColor(&cc))
                pDoc->ColorSet(uTemp, cc.rgbResult);

            break;

        default:
           CFrame::OnCommand(hWnd, wParam, lParam);
        }

    return 0L;
    }

/*
 * CCosmoFrame::OnDocumentDataChange
 *
 * Purpose:
 *  Update the Line menu and toolbar if the style in the data
 *  changes.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentDataChange(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::OnDocumentActivate
 *
 * Purpose:
 *  Informs us that document activation changed, so update the UI
 *  for that new document.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentActivate(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    PCDocument  pDoc;
    BOOL        fOK=FALSE;
    BOOL        fCallDefault=TRUE;
    UINT        i;
    UINT        uTemp;
    UINT        uTempE;
    UINT        uTempD;

    pDoc=m_pCL->ActiveDocument();

    uTempE=MF_ENABLED | MF_BYCOMMAND;
    uTempD=MF_DISABLED | MF_GRAYED | MF_BYCOMMAND;
    uTemp=((NULL!=pDoc) ? uTempE : uTempD);

    //File menu:  If there is document window, disable Import.
    if (m_phMenu[0]==hMenu)
        EnableMenuItem(hMenu, IDM_FILEIMPORT, uTemp);

    //Color menu:  no document, no commands
    if (m_phMenu[2]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_COLORBACKGROUND, uTemp);
        EnableMenuItem(hMenu, IDM_COLORLINE,       uTemp);
        fCallDefault=FALSE;
        }

    //Line menu:  no document, no commands
    if (m_phMenu[3]==hMenu)
        {
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            EnableMenuItem(hMenu, i, uTemp);

        fCallDefault=FALSE;
        }

    if (fCallDefault)
        CFrame::UpdateMenus(hMenu, iMenu);

    return;
    }

/*
 * CCosmoFrame::UpdateToolbar
 *
 * Purpose:
 *  Enables and disables tools depending on whether we have
 *  a document or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateToolbar(void)
    {
    BOOL        fLast;
    UINT        i;

    //Save the last enabled state before CFrame changes it
    fLast=m_fLastEnable;

    //Let the default hack on its tools
    CFrame::UpdateToolbar();

    /*
     * If CFrame::UpdateToolbar changed anything, then we need
     * to change as well--if nothing changes, nothing to do.
     */
    if (fLast!=m_fLastEnable)
        {
        m_pTB->Enable(IDM_FILEIMPORT, m_fLastEnable);

        m_pTB->Enable(IDM_COLORBACKGROUND, m_fLastEnable);
        m_pTB->Enable(IDM_COLORLINE,       m_fLastEnable);

        for (i=IDM_LINEMIN; i <= IDM_LINEMAX; i++)
            m_pTB->Enable(i, m_fLastEnable);
        }

    return;
    }

/*
 * CCosmoFrame::CheckLineSelection
 *
 * Purpose:
 *  Maintains the bitmap menu and the tools for the line selection.
 *  Both are mutially exclusive option lists where a selection in
 *  one has to affect the other.
 *
 * Parameters:
 *  uID             UINT ID of the item to be selected
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CheckLineSelection(UINT uID)
    {
    UINT        i;
    HMENU       hMenu;

    //Update menus and tools if the selection changed.
    if (uID!=m_uIDCurLine)
        {
        m_uIDCurLine=uID;
        hMenu=GetMenu(m_hWnd);

        //Uncheck all lines initially.
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            CheckMenuItem(hMenu, i, MF_UNCHECKED | MF_BYCOMMAND);

        CheckMenuItem(hMenu, uID, MF_CHECKED | MF_BYCOMMAND);
        m_pTB->Check(uID, TRUE);
        }

    return;
    }


COCOSMO.H

/*
 * COCOSMO.H
 * Component Cosmo Chapter 13
 *
 * Single include file that pulls in everything needed for other
 * source files in the Cosmo application.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _COCOSMO_H_
#define _COCOSMO_H_

#define INC_CLASSLIB
#define INC_CONTROLS
#define INC_OLE2
//CHAPTER13MOD
#define CHAPTER13
//CHAPTER13MOD
#include <inole.h>
#include <ipoly10.h>
#include "resource.h"

//COCOSMO.CPP:  Frame object that creates a main window

class CCosmoFrame : public CFrame
    {
    private:
        HBITMAP         m_hBmpLines[5];     //Menu item bitmaps
        UINT            m_uIDCurLine;       //Current line selection
        BOOL            m_fInitialized;     //OleInitialize work?

    protected:
        //Overridable for creating a CClient for this frame
        virtual PCClient  CreateCClient(void);

        virtual BOOL      RegisterAllClasses(void);
        virtual BOOL      PreShowInit(void);
        virtual UINT      CreateToolbar(void);

        virtual LRESULT   OnCommand(HWND, WPARAM, LPARAM);
        virtual void      OnDocumentDataChange(PCDocument);
        virtual void      OnDocumentActivate(PCDocument);

        //New for this class
        virtual void      CreateLineMenu(void);

    public:
        CCosmoFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CCosmoFrame(void);

        //Overrides
        virtual BOOL      Init(PFRAMEINIT);
        virtual void      UpdateMenus(HMENU, UINT);
        virtual void      UpdateToolbar(void);

        //New for this class
        virtual void      CheckLineSelection(UINT);
    };

typedef CCosmoFrame *PCCosmoFrame;

//CLIENT.CPP

/*
 * The only reason we have a derived class here is to override
 * CreateCDocument so we can create our own type as well as
 * overriding NewDocument to perform one other piece of work once
 * the document's been created.
 */

class CCosmoClient : public CClient
    {
    protected:
        //Overridable for creating a new CDocument
        virtual PCDocument CreateCDocument(void);

    public:
        CCosmoClient(HINSTANCE, PCFrame);
        virtual ~CCosmoClient(void);

        virtual PCDocument NewDocument(BOOL);
    };

typedef CCosmoClient *PCCosmoClient;

//DOCUMENT.CPP

class CCosmoDoc;
typedef CCosmoDoc *PCCosmoDoc;

class CPolylineAdviseSink : public IPolylineAdviseSink10
    {
    private:
        PCCosmoDoc  m_pDoc;         //Backpointer to document
        ULONG       m_cRef;

    public:
        CPolylineAdviseSink(PCCosmoDoc);
        ~CPolylineAdviseSink(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //Advise members.
        STDMETHODIMP_(void) OnPointChange(void);
        STDMETHODIMP_(void) OnSizeChange(void);
        STDMETHODIMP_(void) OnColorChange(void);
        STDMETHODIMP_(void) OnLineStyleChange(void);
    };

typedef CPolylineAdviseSink *PCPolylineAdviseSink;

/*
 * The generic advisory interface.  This object controls it's own
 * lifetime and the document becomes a user of the object with
 * the last reference count.
 */

class CImpIAdviseSink : public IAdviseSink
    {
    protected:
        ULONG               m_cRef;
        LPVOID              m_pObj;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIAdviseSink(LPVOID, LPUNKNOWN);
        ~CImpIAdviseSink(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(void)  OnDataChange(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP_(void)  OnViewChange(DWORD, LONG);
        STDMETHODIMP_(void)  OnRename(LPMONIKER);
        STDMETHODIMP_(void)  OnSave(void);
        STDMETHODIMP_(void)  OnClose(void);
    };

typedef CImpIAdviseSink *PCImpIAdviseSink;

//CHAPTER13MOD
//Drag-drop interfaces we need in the document

class CDropTarget : public IDropTarget
    {
    protected:
        ULONG                m_cRef;
        class CCosmoDoc     *m_pDoc;

        LPDATAOBJECT         m_pIDataObject;

    public:
        CDropTarget(class CCosmoDoc *);
        ~CDropTarget(void);

        //IDropTarget interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP DragEnter(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
        STDMETHODIMP DragOver(DWORD, POINTL, LPDWORD);
        STDMETHODIMP DragLeave(void);
        STDMETHODIMP Drop(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
    };

typedef CDropTarget * PCDropTarget;

class CDropSource : public IDropSource
    {
    protected:
        ULONG                m_cRef;
        class CCosmoDoc     *m_pDoc;

    public:
        CDropSource(class CCosmoDoc *);
        ~CDropSource(void);

        //IDropSource interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP QueryContinueDrag(BOOL, DWORD);
        STDMETHODIMP GiveFeedback(DWORD);
    };

typedef CDropSource * PCDropSource;

//End CHAPTER13MOD

//Constant ID for the window polyline that lives in a document window
#define ID_POLYLINE         10

//Stream name to open with IPersistStream[Init]
#define SZSTREAM                    OLETEXT("CONTENTS")

class CCosmoDoc : public CDocument, public IUnknown
    {
    friend class CPolylineAdviseSink;
    friend class CImpIAdviseSink;

    //CHAPTER13MOD
    //These need access to FQueryPasteFromData, PasteFromData
    friend class CDropTarget;
    friend class CDropSource;
    //End CHAPTER13MOD

    protected:
        UINT                    m_uPrevSize;    //Last WM_SIZE wParam

        IPolyline10            *m_pPL;          //Polyline object
        IPolylineAdviseSink10  *m_pPLAdv;

        IConnectionPoint       *m_pIConnectPt;
        DWORD                   m_dwCookie;     //Connection key
        IStorage               *m_pIStorage;    //Doc storage
        PERSISTPOINTER          m_pp;

        PCImpIAdviseSink        m_pImpIAdviseSink;

        DWORD                   m_dwConn;       //Advisory connection
        ULONG                   m_cRef;         //Document ref count
        LPDATAOBJECT            m_pIDataClip;   //Clipboard object

        //CHAPTER13MOD
        PCDropTarget            m_pDropTarget;  //Registered target
        BOOL                    m_fDragSource;  //Source==target?
        //End CHAPTER13MOD

    protected:
        virtual BOOL     FMessageHook(HWND, UINT, WPARAM, LPARAM
            , LRESULT *);

        //CHAPTER13MOD
        virtual BOOL     FQueryPasteFromData(LPDATAOBJECT);
        virtual BOOL     PasteFromData(LPDATAOBJECT);
        LPDATAOBJECT     TransferObjectCreate(void);
        void             DropSelectTargetWindow(void);
        //End CHAPTER13MOD

    public:
        CCosmoDoc(HINSTANCE, PCFrame, PCDocumentAdviseSink);
        virtual ~CCosmoDoc(void);

        //Need a controlling unknown for our interfaces
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        virtual BOOL     Init(PDOCUMENTINIT);
        virtual void     Clear(void);

        virtual UINT     Load(BOOL, LPTSTR);
        virtual UINT     Save(UINT, LPTSTR);

        virtual void     Undo(void);
        virtual BOOL     Clip(HWND, BOOL);

        virtual BOOL     FQueryPaste(void);
        virtual BOOL     Paste(HWND);

        virtual COLORREF ColorSet(UINT, COLORREF);
        virtual COLORREF ColorGet(UINT);

        virtual UINT     LineStyleSet(UINT);
        virtual UINT     LineStyleGet(void);
    };

typedef CCosmoDoc *PCCosmoDoc;

//These color indices wrap the polyline definitions
#define DOCCOLOR_BACKGROUND             POLYLINECOLOR_BACKGROUND
#define DOCCOLOR_LINE                   POLYLINECOLOR_LINE

#endif //_COCOSMO_H_


DOCUMENT.CPP

/*
 * DOCUMENT.CPP
 * Component Cosmo Chapter 13
 *
 * Implementation of the CCosmoDoc derivation of CDocument
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cocosmo.h"

/*
 * CCosmoDoc::CCosmoDoc
 * CCosmoDoc::~CCosmoDoc
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame of the frame object.
 *  pAdv            PCDocumentAdviseSink to notify on events
 */

CCosmoDoc::CCosmoDoc(HINSTANCE hInst, PCFrame pFR
    , PCDocumentAdviseSink pAdv)
    : CDocument(hInst, pFR, pAdv)
    {
    m_pPL=NULL;
    m_pPLAdv=NULL;
    m_uPrevSize=SIZE_RESTORED;
    m_pIConnectPt=NULL;
    m_dwCookie=0;

    m_pIStorage=NULL;
    m_pp.psModel=PERSIST_UNKNOWN;
    m_pp.pIP.pIPersistStorage=NULL; //Affects all pointers

    m_pImpIAdviseSink=NULL;
    m_dwConn=0;
    m_pIDataClip=NULL;

    //CHAPTER13MOD
    m_pDropTarget=NULL;
    m_fDragSource=FALSE;
    //End CHAPTER13MOD

    return;
    }

CCosmoDoc::~CCosmoDoc(void)
    {
    LPDATAOBJECT        pIDataObject;
    HRESULT             hr;

    if (NULL!=m_pIDataClip)
        m_pIDataClip->Release();

    //Turn off the advise.
    if (NULL!=m_pPL && 0!=m_dwConn)
        {
        hr=m_pPL->QueryInterface(IID_IDataObject
            , (PPVOID)&pIDataObject);

        if (SUCCEEDED(hr))
            {
            pIDataObject->DUnadvise(m_dwConn);
            pIDataObject->Release();
            }
        }

    DeleteInterfaceImp(m_pImpIAdviseSink);
    ReleaseInterface(m_pp.pIP.pIPersistStorage);
    ReleaseInterface(m_pIStorage);

    if (NULL!=m_pIConnectPt)
        {
        m_pIConnectPt->Unadvise(m_dwCookie);
        ReleaseInterface(m_pIConnectPt);
        }

    ReleaseInterface(m_pPL);
    ReleaseInterface(m_pPLAdv);

    CoFreeUnusedLibraries();
    return;
    }

/*
 * CCosmoDoc::Init
 *
 * Purpose:
 *  Initializes an already created document window.  The client
 *  actually creates the window for us, then passes that here for
 *  further initialization.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CCosmoDoc::Init(PDOCUMENTINIT pDI)
    {
    RECT                        rc;
    HRESULT                     hr;
    IConnectionPointContainer  *pCPC;
    FORMATETC                   fe;
    LPDATAOBJECT                pIDataObject;

    //Change the stringtable range to our customization.
    pDI->idsMin=IDS_DOCUMENTMIN;
    pDI->idsMax=IDS_DOCUMENTMAX;

    //Do default initialization
    if (!CDocument::Init(pDI))
        return FALSE;

    //Create the Polyline Component
    hr=CoCreateInstance(CLSID_Polyline10, NULL, CLSCTX_INPROC_SERVER
        , IID_IPolyline10, (PPVOID)&m_pPL);

    if (FAILED(hr))
        {
        //Warn that we could not load the Polyline
        MessageBox(pDI->hWndDoc, PSZ(IDS_NOPOLYLINE)
            , PSZ(IDS_CAPTION), MB_OK);
        return FALSE;
        }

    //Initialize the contained Polyline which creates a window.
    GetClientRect(m_hWnd, &rc);
    InflateRect(&rc, -8, -8);

    if (FAILED(m_pPL->Init(m_hWnd, &rc, WS_CHILD | WS_VISIBLE
        , ID_POLYLINE)))
        return FALSE;

    //Set up an advise on the Polyline.
    m_pPLAdv=new CPolylineAdviseSink(this);
    m_pPLAdv->AddRef();

    if (SUCCEEDED(m_pPL->QueryInterface(IID_IConnectionPointContainer
        , (PPVOID)&pCPC)))
        {
        //CHAPTER10MOD
        if (SUCCEEDED(pCPC->FindConnectionPoint
            (IID_IPolylineAdviseSink10, &m_pIConnectPt)))
        //End CHAPTER10MOD
            {
            m_pIConnectPt->Advise((LPUNKNOWN)m_pPLAdv, &m_dwCookie);
            }

        pCPC->Release();
        }

    /*
     * Check for the storage model being used in Polyline.
     * Any InitNew member will be called from CCosmoDoc::Load.
     */
    hr=m_pPL->QueryInterface(IID_IPersistStorage
        , (PPVOID)&m_pp.pIP.pIPersistStorage);

    if (SUCCEEDED(hr))
        m_pp.psModel=PERSIST_STORAGE;
    else
        {
        hr=m_pPL->QueryInterface(IID_IPersistStreamInit
            , (PPVOID)&m_pp.pIP.pIPersistStreamInit);

        if (SUCCEEDED(hr))
            m_pp.psModel=PERSIST_STREAMINIT;
        else
            {
            hr=m_pPL->QueryInterface(IID_IPersistStream
            , (PPVOID)&m_pp.pIP.pIPersistStream);

            if (SUCCEEDED(hr))
                m_pp.psModel=PERSIST_STREAM;
            else
                return FALSE;
            }
        }

    /*
     * Create an IAdviseSink and send it to the Polyline's
     * IDataObject with the clipboard format for the Polyline
     * (as in IPOLY10.H).
     */

    //This is a private macro.
    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);

    m_pImpIAdviseSink=new CImpIAdviseSink(this, this);

    if (NULL==m_pImpIAdviseSink)
        return FALSE;

    //Set up an advise for the Polyline format
    hr=m_pPL->QueryInterface(IID_IDataObject, (PPVOID)&pIDataObject);

    if (FAILED(hr))
        return FALSE;

    pIDataObject->DAdvise(&fe, ADVF_NODATA, m_pImpIAdviseSink
        , &m_dwConn);
    pIDataObject->Release();

    //CHAPTER13MOD
    m_pDropTarget=new CDropTarget(this);

    if (NULL!=m_pDropTarget)
        {
        m_pDropTarget->AddRef();
        CoLockObjectExternal(m_pDropTarget, TRUE, FALSE);
        RegisterDragDrop(m_hWnd, m_pDropTarget);
        }
    //End CHAPTER13MOD

    return TRUE;
    }

//IUnknown interface for all others we implement in the document

/*
 * CCosmoDoc::QueryInterface
 * CCosmoDoc::AddRef
 * CCosmoDoc::Release
 *
 * Purpose:
 *  IUnknown members for the CCosmoDoc implementation.
 */

STDMETHODIMP CCosmoDoc::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    //The document is the unknown
    if (IID_IUnknown==riid)
        *ppv=(LPUNKNOWN)this;

    //Return contained interfaces for others.
    if (IID_IPolylineAdviseSink10==riid)
        *ppv=m_pPLAdv;

    if (IID_IAdviseSink==riid)
        *ppv=m_pImpIAdviseSink;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(S_FALSE);
    }

STDMETHODIMP_(ULONG) CCosmoDoc::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CCosmoDoc::Release(void)
    {
    return --m_cRef;
    }

/*
 * CCosmoDoc::FMessageHook
 *
 * Purpose:
 *  Processes WM_SIZE for the document so we can resize
 *  the Polyline.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CCosmoDoc::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    UINT        dx, dy;
    RECT        rc;

    *pLRes=0;

    if (WM_DESTROY==iMsg)
        {
        /*
         * Flush the clipboard data object if it's still on the
         * clipboard before the document window is destroyed which
         * also destroys the polyline, which is a child.  Flushing
         * now avoid errors that would occur if flushing happened
         * at application shutdown, for the Polyline would still be
         * alive but without a valid window.
         */
        if (NULL!=m_pIDataClip)
            {
            if (NOERROR==OleIsCurrentClipboard(m_pIDataClip))
                OleFlushClipboard();
            }

        //CHAPTER13MOD
        /*
         * We have to revoke the drop target here because the window
         * will be destroyed and the property forcefully removed
         * before we could do this in the destructor.
         */
        if (NULL!=m_pDropTarget)
            {
            RevokeDragDrop(m_hWnd);
            CoLockObjectExternal(m_pDropTarget, FALSE, TRUE);
            m_pDropTarget->Release();
            }
        //End CHAPTER13MOD
        }

    if (WM_SIZE==iMsg)
        {
        //Don't effect the Polyline size to or from minimized state.
        if (SIZE_MINIMIZED!=wParam && SIZE_MINIMIZED !=m_uPrevSize)
            {
            //When we change size, resize any Polyline we hold.
            dx=LOWORD(lParam);
            dy=HIWORD(lParam);

            /*
             * If we are getting WM_SIZE in response to a Polyline
             * notification, then don't resize the Polyline window
             * again.
             */
            if (!m_fNoSize && NULL!=m_pPL)
                {
                //Resize the polyline to fit the new client
                SetRect(&rc, 8, 8, dx-8, dy-8);
                m_pPL->RectSet(&rc, FALSE);

                /*
                 * We consider sizing something that makes the file
                 * dirty, but not until we've finished the create
                 * process, which is why we set fNoDirty to FALSE
                 * in WM_CREATE since we get a WM_SIZE on the first
                 * creation.
                 */
                if (!m_fNoDirty)
                    FDirtySet(TRUE);

                SetRect(&rc, 0, 0, dx, dy);

                if (NULL!=m_pAdv)
                    m_pAdv->OnSizeChange(this, &rc);

                m_fNoDirty=FALSE;
                }
            }

        m_uPrevSize=wParam;
        }

    //CHAPTER13MOD
    if (WM_LBUTTONDOWN==iMsg)
        {
        LPDROPSOURCE    pIDropSource;
        LPDATAOBJECT    pIDataObject;
        HRESULT         hr;
        SCODE           sc;
        DWORD           dwEffect;

        pIDropSource=new CDropSource(this);

        if (NULL==pIDropSource)
            return FALSE;

        pIDropSource->AddRef();
        m_fDragSource=TRUE;

        //Go get the data and start the ball rolling.
        pIDataObject=TransferObjectCreate();

        if (NULL!=pIDataObject)
            {
            hr=DoDragDrop(pIDataObject, pIDropSource
                , DROPEFFECT_COPY | DROPEFFECT_MOVE, &dwEffect);

            pIDataObject->Release();
            sc=GetScode(hr);
            }
        else
            sc=E_FAIL;

        pIDropSource->Release();
        m_fDragSource=FALSE;

        if (DRAGDROP_S_DROP==sc && DROPEFFECT_MOVE==dwEffect)
            {
            m_pPL->New();
            FDirtySet(TRUE);
            }

        return TRUE;
        }
    //End CHAPTER13MOD

    /*
     * We return FALSE even on WM_SIZE so we can let the default
     * procedure handle maximized MDI child windows appropriately.
     */
    return FALSE;
    }

/*
 * CCosmoDoc::Clear
 *
 * Purpose:
 *  Sets all contents in the document back to defaults with
 *  no filename.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Clear(void)
    {
    //Completely reset the polyline
    m_pPL->New();

    CDocument::Clear();
    return;
    }

/*
 * CCosmoDoc::Load
 *
 * Purpose:
 *  Loads a given document without any user interface overwriting
 *  the previous contents of the Polyline window.  We do this by
 *  opening the file and telling the Polyline to load itself from
 *  that file.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load, NULL if the file
 *                  is new and untitled.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CCosmoDoc::Load(BOOL fChangeFile, LPTSTR pszFile)
    {
    HRESULT             hr;
    LPSTORAGE           pIStorage;

    if (NULL==pszFile)
        {
        /*
         * As a user of an IPersistStorage we have to provide all
         * objects with an IStorage they can use for incremental
         * access passing that storage to InitNew.  Here we create
         * a temporary file that we don't bother holding on to.
         * If the object doesn't use it, then our Release destroys
         * it immediately.
         */

        hr=StgCreateDocfile(NULL, STGM_DIRECT | STGM_READWRITE
            | STGM_CREATE | STGM_DELETEONRELEASE
            | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;

        if (PERSIST_STORAGE==m_pp.psModel)
            m_pp.pIP.pIPersistStorage->InitNew(pIStorage);
        else
            {
            if (PERSIST_STREAMINIT==m_pp.psModel)
                m_pp.pIP.pIPersistStreamInit->InitNew();
            }

        m_pIStorage=pIStorage;

        Rename(NULL);
        return DOCERR_NONE;
        }

    /*
     * Open a storage and have the Polyline read its data
     * using whatever persistence model it employs.
     */
    hr=StgOpenStorage(pszFile, NULL, STGM_DIRECT | STGM_READWRITE
        | STGM_SHARE_EXCLUSIVE, NULL, 0, &pIStorage);

    if (FAILED(hr))
        return DOCERR_COULDNOTOPEN;

    if (PERSIST_STORAGE==m_pp.psModel)
        hr=m_pp.pIP.pIPersistStorage->Load(pIStorage);
    else
        {
        LPSTREAM    pIStream;

        hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
            | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

        if (SUCCEEDED(hr))
            {
            //This also works for PERSIST_STREAMINIT
            hr=m_pp.pIP.pIPersistStream->Load(pIStream);
            pIStream->Release();
            }
        }

    m_pIStorage=pIStorage;

    if (FAILED(hr))
        return DOCERR_READFAILURE;

    if (fChangeFile)
        Rename(pszFile);

    //Importing a file makes things dirty
    FDirtySet(!fChangeFile);

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::Save
 *
 * Purpose:
 *  Writes the file to a known filename, requiring that the user
 *  has previously used FileOpen or FileSaveAs in order to have
 *  a filename.
 *
 * Parameters:
 *  uType           UINT indicating the type of file the user
 *                  requested to save in the File Save As dialog.
 *  pszFile         LPTSTR under which to save.  If NULL, use the
 *                  current name.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CCosmoDoc::Save(UINT uType, LPTSTR pszFile)
    {
    BOOL                fRename=TRUE;
    HRESULT             hr;
    LPSTORAGE           pIStorage;
    BOOL                fSameAsLoad;

    //If Save or Save As under the same name, do Save.
    if (NULL==pszFile || 0==lstrcmpi(pszFile, m_szFile))
        {
        fRename=FALSE;
        pszFile=m_szFile;

        /*
         * If we're saving to an existing storage, just pass
         * the IStorage we have from Load along with TRUE
         * in fSameAsLoad.
         */

        fSameAsLoad=TRUE;
        }
    else
        {
        /*
         * In Component Cosmo, we only deal with one version of
         * data; all the code in Chapter 1 Cosmo that dealt with
         * 1.0 and 2.0 files has been removed.
         */

        hr=StgCreateDocfile(pszFile, STGM_DIRECT | STGM_READWRITE
            | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;

        //Tell the object to save into this new storage
        fSameAsLoad=FALSE;

        //Update our variable
        m_pIStorage->Release();
        m_pIStorage=pIStorage;
        }

    if (PERSIST_STORAGE==m_pp.psModel)
        {
        hr=m_pp.pIP.pIPersistStorage->Save(m_pIStorage, fSameAsLoad);

        if (SUCCEEDED(hr))
            {
            hr=m_pp.pIP.pIPersistStorage->SaveCompleted(fSameAsLoad
                ? NULL : m_pIStorage);
            }
        }
    else
        {
        LPSTREAM    pIStream;

        hr=m_pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
            | STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE
            , 0, 0, &pIStream);

        if (SUCCEEDED(hr))
            {
            //This also works for PERSIST_STREAMINIT
            hr=m_pp.pIP.pIPersistStream->Save(pIStream, TRUE);
            pIStream->Release();
            }
        }

    if (FAILED(hr))
        return DOCERR_WRITEFAILURE;

    //Saving makes us clean
    FDirtySet(FALSE);

    if (fRename)
        Rename(pszFile);

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::Undo
 *
 * Purpose:
 *  Reverses a previous action.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Undo(void)
    {
    m_pPL->Undo();
    return;
    }

/*
 * CCosmoDoc::Clip
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard, optionally implementing Cut by deleting the
 *  data in the current window after rendering.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *  fCut            BOOL indicating cut (TRUE) or copy (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::Clip(HWND hWndFrame, BOOL fCut)
    {
    //CHAPTER13MOD
    BOOL            fRet=TRUE;
    LPDATAOBJECT    pIDataObject;

    pIDataObject=TransferObjectCreate();

    if (NULL==pIDataObject)
        return FALSE;
    //CHAPTER13MOD

    fRet=SUCCEEDED(OleSetClipboard(pIDataObject));

    if (NULL!=m_pIDataClip)
        m_pIDataClip->Release();

    m_pIDataClip=pIDataObject;

    //Delete our current data if a "cut" succeeded.
    if (fRet && fCut)
        {
        m_pPL->New();
        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CCosmoDoc::FQueryPaste
 *
 * Purpose:
 *  Determines if we can paste data from the clipboard.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CCosmoDoc::FQueryPaste(void)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    //CHAPTER13MOD
    fRet=FQueryPasteFromData(pIDataObject);
    //End CHAPTER13MOD

    pIDataObject->Release();
    return fRet;
    }

//CHAPTER13MOD
/*
 * CCosmoDoc::FQueryPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Determines if we can paste data from a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which we might want to paste.
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CCosmoDoc::FQueryPasteFromData(LPDATAOBJECT pIDataObject)
    {
    FORMATETC       fe;

    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    return (NOERROR==pIDataObject->QueryGetData(&fe));
    }
//End CHAPTER13MOD

/*
 * CCosmoDoc::Paste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  to the current figure in the editor window.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::Paste(HWND hWndFrame)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    //CHAPTER13MOD
    fRet=PasteFromData(pIDataObject);
    //End CHAPTER13MOD

    pIDataObject->Release();
    return TRUE;
    }

//CHAPTER13MOD
/*
 * CCosmoDoc::PasteFromData
 * (Protected)
 *
 * Purpose:
 *  Retrieves the private data format from a data object and sets
 *  it to the current figure in the editor window.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::PasteFromData(LPDATAOBJECT pIDataObject)
    {
    FORMATETC       fe;
    STGMEDIUM       stm;
    BOOL            fRet;

    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    fRet=SUCCEEDED(pIDataObject->GetData(&fe, &stm));

    if (!fRet || NULL==stm.hGlobal)
        return FALSE;

    if (fRet && NULL!=stm.hGlobal)
        {
        m_pPL->QueryInterface(IID_IDataObject
            , (PPVOID)&pIDataObject);
        pIDataObject->SetData(&fe, &stm, TRUE);
        pIDataObject->Release();

        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CCosmoDoc::TransferObjectCreate
 * (Protected)
 *
 * Purpose:
 *  Creates a DataTransferObject and stuffs the current Polyline
 *  data into it, used for both clipboard and drag-drop operations.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  LPDATAOBJECT    Pointer to the object created, NULL on failure
 */

LPDATAOBJECT CCosmoDoc::TransferObjectCreate(void)
    {
    UINT            i;
    HRESULT         hr;
    STGMEDIUM       stm;
    FORMATETC       fe;
    LPDATAOBJECT    pIDataSrc;
    LPDATAOBJECT    pIDataObject=NULL;
    const UINT      cFormats=3;
    static UINT     rgcf[3]={0, CF_METAFILEPICT, CF_BITMAP};
    static DWORD    rgtm[3]={TYMED_HGLOBAL, TYMED_MFPICT
                        , TYMED_GDI};

    hr=CoCreateInstance(CLSID_DataTransferObject, NULL
        , CLSCTX_INPROC_SERVER, IID_IDataObject
        , (PPVOID)&pIDataObject);

    if (FAILED(hr))
        return NULL;

    rgcf[0]=m_cf;

    hr=m_pPL->QueryInterface(IID_IDataObject, (PPVOID)&pIDataSrc);

    if (FAILED(hr))
        {
        pIDataObject->Release();
        return NULL;
        }

    for (i=0; i < cFormats; i++)
        {
        //Copy private data first.
        SETDefFormatEtc(fe, rgcf[i], rgtm[i]);

        if (SUCCEEDED(pIDataSrc->GetData(&fe, &stm)))
            pIDataObject->SetData(&fe, &stm, TRUE);
        }

    pIDataSrc->Release();
    return pIDataObject;    //Caller now responsible
    }

/*
 * CCosmoDoc::DropSelectTargetWindow
 * (Protected)
 *
 * Purpose:
 *  Creates a thin inverted frame around a window that we use to
 *  show the window as a drop target.  This is a toggle function:
 *  it uses XOR to create the effect so it must be called twice
 *  to leave the window as it was.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::DropSelectTargetWindow(void)
    {
    HDC         hDC;
    RECT        rc;
    UINT        dd=3;
    HWND        hWnd;

    m_pPL->Window(&hWnd);
    hDC=GetWindowDC(hWnd);
    GetClientRect(hWnd, &rc);

    //Frame this window with inverted pixels

    //Top
    PatBlt(hDC, rc.left, rc.top, rc.right-rc.left, dd, DSTINVERT);

    //Bottom
    PatBlt(hDC, rc.left, rc.bottom-dd, rc.right-rc.left, dd
        , DSTINVERT);

    //Left excluding regions already affected by top and bottom
    PatBlt(hDC, rc.left, rc.top+dd, dd, rc.bottom-rc.top-(2*dd)
        , DSTINVERT);

    //Right excluding regions already affected by top and bottom
    PatBlt(hDC, rc.right-dd, rc.top+dd, dd, rc.bottom-rc.top-(2*dd)
        , DSTINVERT);

    ReleaseDC(hWnd, hDC);
    return;
    }

//End CHAPTER13MOD

/*
 * CCosmoDoc::ColorSet
 *
 * Purpose:
 *  Changes a color used in our contained Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color.
 *
 * Return Value:
 *  COLORREF        Previous color for the given index.
 */

COLORREF CCosmoDoc::ColorSet(UINT iColor, COLORREF cr)
    {
    COLORREF    crRet;

    m_pPL->ColorSet(iColor, cr, &crRet);
    return crRet;
    }

/*
 * CCosmoDoc::ColorGet
 *
 * Purpose:
 *  Retrieves a color currently in use in the Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to retrieve.
 *
 * Return Value:
 *  COLORREF        Current color for the given index.
 */

COLORREF CCosmoDoc::ColorGet(UINT iColor)
    {
    COLORREF    crRet;

    m_pPL->ColorGet(iColor, &crRet);
    return crRet;
    }

/*
 * CCosmoDoc::LineStyleSet
 *
 * Purpose:
 *  Changes the line style currently used in the Polyline
 *
 * Parameters:
 *  iStyle          UINT index of the new line style to use.
 *
 * Return Value:
 *  UINT            Previous line style.
 */

UINT CCosmoDoc::LineStyleSet(UINT iStyle)
    {
    UINT    i;

    m_pPL->LineStyleSet(iStyle, &i);
    return i;
    }

/*
 * CCosmoDoc::LineStyleGet
 *
 * Purpose:
 *  Retrieves the line style currently used in the Polyline
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Current line style.
 */

UINT CCosmoDoc::LineStyleGet(void)
    {
    UINT    i=0;

    //m_pPL might not be valid yet.
    if (NULL!=m_pPL)
        m_pPL->LineStyleGet(&i);

    return i;
    }


DROPSRC.CPP

/*
 * DROPSRC.CPP
 * Component Cosmo Chapter 13
 *
 * Implementation of a DropSource object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cocosmo.h"

/*
 * CDropSource::CDropSource
 * CDropSource::~CDropSource
 *
 * Constructor Parameters:
 *  pDoc            PCCosmoDoc containing this interface.
 */

CDropSource::CDropSource(PCCosmoDoc pDoc)
    {
    m_cRef=0;
    m_pDoc=pDoc;
    return;
    }

CDropSource::~CDropSource(void)
    {
    return;
    }

/*
 * CDropSource::QueryInterface
 * CDropSource::AddRef
 * CDropSource::Release
 *
 * Purpose:
 *  IUnknown members for CDropSource object.
 */

STDMETHODIMP CDropSource::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDropSource==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDropSource::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropSource::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDropSource::QueryDragContinue
 *
 * Purpose:
 *  Determines whether to continue a drag operation or cancel it.
 *
 * Parameters:
 *  fEsc            BOOL indicating that the ESC key was pressed.
 *  grfKeyState     DWORD providing states of keys and mouse buttons
 *
 * Return Value:
 *  HRESULT         DRAGDROP_S_CANCEL to stop the drag,
 *                  DRAGDROP_S_DROP to drop the data where it is,
 *                  or NOERROR to continue.
 */

STDMETHODIMP CDropSource::QueryContinueDrag(BOOL fEsc
    , DWORD grfKeyState)
    {
    if (fEsc)
        return ResultFromScode(DRAGDROP_S_CANCEL);

    if (!(grfKeyState & MK_LBUTTON))
        return ResultFromScode(DRAGDROP_S_DROP);

    return NOERROR;
    }

/*
 * CDropSource::GiveFeedback
 *
 * Purpose:
 *  Provides cursor feedback to the user since the source task
 *  always has the mouse capture.  We can also provide any other
 *  type of feedback above cursors if we so desire.
 *
 * Parameters:
 *  dwEffect        DWORD effect flags returned from the last target
 *
 * Return Value:
 *  HRESULT         NOERROR if you set a cursor yourself or
 *                  DRAGDROP_S_USEDEFAULTCURSORS to let OLE do
 *                  the work.
 */

STDMETHODIMP CDropSource::GiveFeedback(DWORD dwEffect)
    {
    return ResultFromScode(DRAGDROP_S_USEDEFAULTCURSORS);
    }


DROPTGT.CPP

/*
 * DROPTGT.CPP
 * Component Cosmo Chapter 13
 *
 * Implementation of the DropTarget object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cocosmo.h"

/*
 * CDropTarget::CDropTarget
 * CDropTarget::~CDropTarget
 *
 * Constructor Parameters:
 *  pDoc            PCCosmoDoc of the document containing us.
 */

CDropTarget::CDropTarget(PCCosmoDoc pDoc)
    {
    m_cRef=0;
    m_pDoc=pDoc;

    m_pIDataObject=NULL;
    return;
    }

CDropTarget::~CDropTarget(void)
    {
    return;
    }

/*
 * CDropTarget::QueryInterface
 * CDropTarget::AddRef
 * CDropTarget::Release
 *
 * Purpose:
 *  IUnknown members for CDropTarget object.
 */

STDMETHODIMP CDropTarget::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDropTarget==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDropTarget::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropTarget::Release(void)
    {
     if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDropTarget::DragEnter
 *
 * Purpose:
 *  Indicates that data in a drag operation has been dragged over
 *  our window that's a potential target.  We are to decide if it's
 *  something in which we're interested.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT providing the source data.
 *  grfKeyState     DWORD flags: states of keys and mouse buttons.
 *  pt              POINTL coordinates in the client space of
 *                  the document.
 *  pdwEffect       LPDWORD into which we'll place the appropriate
 *                  effect flag for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragEnter(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    HWND        hWnd;

    m_pIDataObject=NULL;

    if (!m_pDoc->FQueryPasteFromData(pIDataSource))
        {
        *pdwEffect=DROPEFFECT_NONE;
        return NOERROR;
        }

    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        *pdwEffect=DROPEFFECT_COPY;

    m_pIDataObject=pIDataSource;
    m_pIDataObject->AddRef();

    hWnd=m_pDoc->Window();
    BringWindowToTop(hWnd);
    UpdateWindow(hWnd);
    m_pDoc->DropSelectTargetWindow();

    return NOERROR;
    }

/*
 * CDropTarget::DragOver
 *
 * Purpose:
 *  Indicates that the mouse was moved inside the window represented
 *  by this drop target.  This happens on every WM_MOUSEMOVE, so
 *  this function should be very efficient.
 *
 * Parameters:
 *  grfKeyState     DWORD providing the current keyboard and
 *                  mouse states
 *  pt              POINTL where the mouse currently is.
 *  pdwEffect       LPDWORD in which to store the effect flag
 *                  for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragOver(DWORD grfKeyState, POINTL pt
    , LPDWORD pdwEffect)
    {
    if (NULL==m_pIDataObject)
        {
        *pdwEffect=DROPEFFECT_NONE;
        return NOERROR;
        }

    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        *pdwEffect=DROPEFFECT_COPY;

    return NOERROR;
    }

/*
 * CDropTarget::DragLeave
 *
 * Purpose:
 *  Informs the drop target that the operation has left its window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragLeave(void)
    {
    m_pDoc->DropSelectTargetWindow();
    ReleaseInterface(m_pIDataObject);
    return NOERROR;
    }

/*
 * CDropTarget::Drop
 *
 * Purpose:
 *  Instructs the drop target to paste the data that was just now
 *  dropped on it.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT from which we'll paste.
 *  grfKeyState     DWORD providing current keyboard/mouse state.
 *  pt              POINTL at which the drop occurred.
 *  pdwEffect       LPDWORD in which to store what you did.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::Drop(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    BOOL        fRet=TRUE;

    *pdwEffect=DROPEFFECT_NONE;

    if (NULL==m_pIDataObject)
        return ResultFromScode(E_FAIL);

    DragLeave();

    if (m_pDoc->m_fDragSource)
        return ResultFromScode(E_FAIL);

    fRet=m_pDoc->PasteFromData(pIDataSource);

    if (!fRet)
        return ResultFromScode(E_FAIL);

    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        *pdwEffect=DROPEFFECT_COPY;

    return NOERROR;
    }


IADVSINK.CPP

/*
 * IADVSINK.CPP
 * Component Cosmo Chapter 13
 *
 * Implementation of the CPolylineAdviseSink and CImpIAdviseSink
 * interfaces for Component Cosmo.  CPolylineAdviseSink moved
 * here from document.cpp to live with all the advise stuff.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cocosmo.h"

/*
 * CPolylineAdviseSink::CPolylineAdviseSink
 * CPolylineAdviseSink::~CPolylineAdviseSink
 *
 * Constructor Parameters:
 *  pDoc            PCCosmoDoc to store in this object
 */

CPolylineAdviseSink::CPolylineAdviseSink(PCCosmoDoc pDoc)
    {
    m_pDoc=pDoc;
    m_cRef=0;
    AddRef();
    return;
    }

CPolylineAdviseSink::~CPolylineAdviseSink(void)
    {
    return;
    }

/*
 * CPolylineAdviseSink::QueryInterface
 * CPolylineAdviseSink::AddRef
 * CPolylineAdviseSink::Release
 *
 * Purpose:
 *  IUnknown members for this IPolylineAdviseSink implementations.
 */

STDMETHODIMP CPolylineAdviseSink::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pDoc->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CPolylineAdviseSink::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPolylineAdviseSink::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CPolylineAdviseSink::OnPointChange
 *
 * Purpose:
 *  Informs the document that the polyline added or removed a point.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CPolylineAdviseSink::OnPointChange(void)
    {
    m_pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnSizeChange
 *
 * Purpose:
 *  Informs the document that the polyline changed size.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CPolylineAdviseSink::OnSizeChange(void)
    {
    RECT            rc;
    DWORD           dwStyle;
    HWND            hWnd;

    /*
     * Polyline window is informing us that it changed size in
     * response to setting it's data.  Therefore we have to
     * size ourselves accordingly but without moving the screen
     * position of the polyline window.
     */

    m_pDoc->m_fNoSize=TRUE;

    //Set the document window size.
    m_pDoc->m_pPL->Window(&hWnd);
    GetWindowRect(hWnd, &rc);
    InflateRect(&rc, 8, 8);

    //Adjust for a window sans menu
    dwStyle=GetWindowLong(m_pDoc->m_hWnd, GWL_STYLE);
    AdjustWindowRect(&rc, dwStyle, FALSE);

    SetWindowPos(m_pDoc->m_hWnd, NULL, 0, 0, rc.right-rc.left
        , rc.bottom-rc.top, SWP_NOMOVE | SWP_NOZORDER);

    if (NULL!=m_pDoc->m_pAdv)
        m_pDoc->m_pAdv->OnSizeChange(m_pDoc, &rc);

    m_pDoc->m_fNoSize=FALSE;
    m_pDoc->FDirtySet(TRUE);

    return;
    }

/*
 * CPolylineAdviseSink::OnColorChange
 *
 * Purpose:
 *  Informs the document that the polyline data changed a color.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CPolylineAdviseSink::OnColorChange(void)
    {
    m_pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnLineStyleChange
 *
 * Purpose:
 *  Informs the document that the polyline changed its line style.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CPolylineAdviseSink::OnLineStyleChange(void)
    {
    m_pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CImpIAdviseSink::CImpIAdviseSink
 * CImpIAdviseSink::~CImpIAdviseSink
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN for delegation of IUnknown members.
 */

CImpIAdviseSink::CImpIAdviseSink(LPVOID pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIAdviseSink::~CImpIAdviseSink(void)
    {
    return;
    }

/*
 * CImpIAdviseSink::QueryInterface
 * CImpIAdviseSink::AddRef
 * CImpIAdviseSink::Release
 *
 * Purpose:
 *  IUnknown members for CImpIAdviseSink object.
 */

STDMETHODIMP CImpIAdviseSink::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIAdviseSink::OnDataChange
 *
 * Purpose:
 *  Notifes the advise sink that data changed in a data object.
 *  On this message you may request a new data rendering and
 *  update your displays as necessary.
 *
 * Parameters:
 *  pFEIn           LPFORMATETC describing format that changed
 *  pSTM            LPSTGMEDIUM providing the medium in which the
 *                  data is provided.
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnDataChange(LPFORMATETC pFEIn
    , LPSTGMEDIUM pSTM)
    {
    PCCosmoDoc      m_pDoc=(PCCosmoDoc)m_pObj;

    /*
     * This code is from former CPolylineAdviseSink::OnDataChange.
     * The only advise we asked for was on the Polyline native
     * format which is all we'll be notified for.
     */
    if (NULL!=m_pDoc->m_pAdv)
        m_pDoc->m_pAdv->OnDataChange(m_pDoc);

    m_pDoc->FDirtySet(TRUE);
    return;
    }

STDMETHODIMP_(void) CImpIAdviseSink::OnViewChange(DWORD dwAspect
    , LONG lindex)
    {
    return;
    }

STDMETHODIMP_(void) CImpIAdviseSink::OnRename(LPMONIKER pmk)
    {
    return;
    }

STDMETHODIMP_(void) CImpIAdviseSink::OnSave(void)
    {
    return;
    }

STDMETHODIMP_(void) CImpIAdviseSink::OnClose(void)
    {
    return;
    }


RESOURCE.H

/*
 * RESOURCE.H
 * Component Cosmo Chapter 13
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Note that we augment classlib.h which we include before this.

//Position of Window menu.
#undef  WINDOW_MENU
#define WINDOW_MENU             4

//Total number of menus
#undef  CMENUS
#define CMENUS                  5

//Menu command identifiers.

#define IDM_FILEIMPORT          (IDM_CUSTOMFILEMIN)

#define IDM_COLORBACKGROUND     (IDM_CUSTOMMIN)
#define IDM_COLORLINE           (IDM_CUSTOMMIN+1)

#define IDM_LINEMIN             (IDM_CUSTOMMIN+100)
#define IDM_LINESOLID           (IDM_LINEMIN+0)         //PS_SOLID       0
#define IDM_LINEDASH            (IDM_LINEMIN+1)         //PS_DASH        1
#define IDM_LINEDOT             (IDM_LINEMIN+2)         //PS_DOT         2
#define IDM_LINEDASHDOT         (IDM_LINEMIN+3)         //PS_DASHDOT     3
#define IDM_LINEDASHDOTDOT      (IDM_LINEMIN+4)         //PS_DASHDOTDOT  4
#define IDM_LINEMAX             IDM_LINEDASHDOTDOT

/*
 * IDs for StatStrip handling of popup menus:  must be in order of menu
 * CLASSRES.H already defines ID_MENUFILE and ID_MENUEDIT for us.
 */

#undef  ID_MENUWINDOW
#undef  ID_MENUHELP

#define ID_MENUCOLOR            (ID_MENUCUSTOMMIN+0)
#define ID_MENULINE             (ID_MENUCUSTOMMIN+1)
#define ID_MENUWINDOW           (ID_MENUCUSTOMMIN+2)
#define ID_MENUHELP             (ID_MENUCUSTOMMIN+3)

//Stringtable IDs.  Keep sequential for each group.
#define IDS_FRAMEMIN            IDS_STANDARDFRAMEMIN
#define IDS_FILEIMPORT          (IDS_CUSTOMFRAMEMIN+0)
#define IDS_FRAMEMAX            (IDS_CUSTOMFRAMEMIN+0)

#define IDS_DOCUMENTMIN         IDS_STANDARDDOCMIN
#define IDS_NOPOLYLINE          (IDS_CUSTOMDOCMIN+0)
#define IDS_DOCUMENTMAX         (IDS_CUSTOMDOCMIN+0)

//These are specifically for the StatStrip
#define IDS_STATMESSAGEMIN                  IDS_STANDARDSTATMESSAGEMIN

#define IDS_ITEMMESSAGEFILEIMPORT           (IDS_CUSTOMSTATMESSAGEMIN+0)

#define IDS_MENUMESSAGECOLOR                (IDS_CUSTOMSTATMESSAGEMIN+1)
#define IDS_ITEMMESSAGECOLORBACKGROUND      (IDS_CUSTOMSTATMESSAGEMIN+2)
#define IDS_ITEMMESSAGECOLORLINE            (IDS_CUSTOMSTATMESSAGEMIN+3)

#define IDS_MENUMESSAGELINE                 (IDS_CUSTOMSTATMESSAGEMIN+4)
#define IDS_ITEMMESSAGELINESOLID            (IDS_CUSTOMSTATMESSAGEMIN+5)
#define IDS_ITEMMESSAGELINEDASH             (IDS_CUSTOMSTATMESSAGEMIN+6)
#define IDS_ITEMMESSAGELINEDOT              (IDS_CUSTOMSTATMESSAGEMIN+7)
#define IDS_ITEMMESSAGELINEDASHDOT          (IDS_CUSTOMSTATMESSAGEMIN+8)
#define IDS_ITEMMESSAGELINEDASHDOTDOT       (IDS_CUSTOMSTATMESSAGEMIN+9)

#define IDS_STATMESSAGEMAX                  (IDS_CUSTOMSTATMESSAGEMIN+9)

#endif //_RESOURCE_H_


CLIENT.CPP

/*
 * CLIENT.CPP
 * Cosmo Chapter 13
 *
 * Implementation of the CCosmoClient class that just makes sure
 * we get a CCosmoDoc on doc creation and that we initialize fully.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CCosmoClient::CCosmoClient
 * CCosmoClient::~CCosmoClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 */

CCosmoClient::CCosmoClient(HINSTANCE hInst)
    : CClient(hInst)
    {
    return;
    }

CCosmoClient::~CCosmoClient(void)
    {
    return;
    }

/*
 * CCosmoClient::CreateCDocument
 *
 * Purpose:
 *  Constructs a new document specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::CreateCDocument(void)
    {
    return (PCDocument)(new CCosmoDoc(m_hInst, m_pFR));
    }

/*
 * CCosmoClient::NewDocument
 *
 * Purpose:
 *  Small override of the CClient::NewDocument that we have just
 *  to check the initial line selection on new document creation.
 *
 * Parameters:
 *  fVisible        BOOL indicating if the document is to be
 *                  visible or not.
 *  pAdv            PCDocumentAdviseSink to set with the new
 *                  document for notifications.  Can be NULL.
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::NewDocument(BOOL fVisible
    , PCDocumentAdviseSink pAdv)
    {
    PCDocument  pDoc;

    //Perform default NewDocument first
    pDoc=CClient::NewDocument(fVisible, pAdv);

    //We know that m_pFR is actually a CCosmoFrame, so type is safe.
    ((PCCosmoFrame)m_pFR)->CheckLineSelection(IDM_LINESOLID);

    return pDoc;
    }


COSMO.CPP

/*
 * COSMO.CPP
 * Cosmo Chapter 13
 *
 * WinMain and CCosmoFrame implementations.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "cosmo.h"

extern "C"
{
OLEDBGDATA_MAIN("COSMO")
}

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

//Make window handle global so other code can cause a shutdown
HWND        g_hWnd=NULL;

//Indicate if the user has control
BOOL        g_fUser=TRUE;

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCCosmoFrame    pFR;
    FRAMEINIT       fi;
    WPARAM          wRet;

   #ifndef WIN32
    int             cMsg=96;

    while (!SetMessageQueue(cMsg) && (cMsg-=8));
   #endif

    //Attempt to allocate and initialize the application
    pFR=new CCosmoFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return -1;

    fi.idsMin=IDS_FRAMEMIN;
    fi.idsMax=IDS_FRAMEMAX;
    fi.idsStatMin=IDS_STATMESSAGEMIN;
    fi.idsStatMax=IDS_STATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=440;
    fi.cy=460;

    //If we can initialize pFR, start chugging messages
    if (pFR->FInit(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Cosmo Figure object to call when it gets
 *  destroyed.  We destroy the main window if the proper conditions
 *  are met for shutdown.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void PASCAL ObjectDestroyed(void)
    {
    g_cObj--;

    //No more objects, no locks, no user control, shut the app down.
    if (0L==g_cObj && 0L==g_cLock && IsWindow(g_hWnd) && !g_fUser)
        PostMessage(g_hWnd, WM_CLOSE, 0, 0L);

    return;
    }

/*
 * CCosmoFrame::CCosmoFrame
 * CCosmoFrame::~CCosmoFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WInMain
 */

CCosmoFrame::CCosmoFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    : CFrame(hInst, hInstPrev, pszCmdLine, nCmdShow)
    {
    UINT        i;
    char        szTemp[CCHPATHMAX];     //ParseCmdLine is ANSI

    for (i=0; i<5; i++)
        m_hBmpLines[i]=NULL;

    m_uIDCurLine=0;
    m_fInitialized=FALSE;
    m_fOleStdInit=FALSE;
    m_pIClassDataTran=NULL;

    m_fEmbedding=FALSE;
    //This function is in OLESTD
    ParseCmdLine(pszCmdLine, &m_fEmbedding, szTemp);
    g_fUser=!m_fEmbedding;
    m_dwRegCO=0;
    m_pIClassFactory=NULL;

    return;
    }

CCosmoFrame::~CCosmoFrame(void)
    {
    UINT        i;

    //Reverse CoRegisterClassObject, takes class factory ref to 1
    if (0L!=m_dwRegCO)
        CoRevokeClassObject(m_dwRegCO);

    //Should be last Release, which frees the class factory.
    if (NULL!=m_pIClassFactory)
        m_pIClassFactory->Release();

    for (i=0; i<5; i++)
        {
        if (NULL!=m_hBmpLines[i])
            DeleteObject(m_hBmpLines[i]);
        }

    if (NULL!=m_pIClassDataTran)
        {
        m_pIClassDataTran->LockServer(FALSE);
        m_pIClassDataTran->Release();
        }

    OleFlushClipboard();

    if (m_fOleStdInit)
        OleStdUninitialize();

    if (m_fInitialized)
        OleUninitialize();

    return;
    }

/*
 * CCosmoFrame::FInit
 *
 * Purpose:
 *  Call CoInitialize then calling down into the base class
 *  initialization.
 *
 * Parameters:
 *  pFI             PFRAMEINIT containing initialization parameters.
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded, FALSE otherwise.
 */

BOOL CCosmoFrame::FInit(PFRAMEINIT pFI)
    {
    DWORD       dwVer;
    HRESULT     hr;

    dwVer=OleBuildVersion();

    if (rmm!=HIWORD(dwVer))
        return FALSE;

    if (FAILED(OleInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    if (!OleStdInitialize(m_hInst))
        return FALSE;

    m_fOleStdInit=TRUE;

    hr=CoGetClassObject(CLSID_DataTransferObject
        , CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory
        , (PPVOID)&m_pIClassDataTran);

    if (SUCCEEDED(hr))
        m_pIClassDataTran->LockServer(TRUE);

    /*
     * Create our class factory and register it for this application
     * using CoRegisterClassObject.  The REGCLS_*USE flags have to
     * do with servicable object from this instance, not with MDI or
     * SDI.  Since it's most convenient to be single use, we'll do
     * this in either version.
     *
     * In addition, it only makes sense to do any of this if we're
     * being launched to be a server.
     */
    if (m_fEmbedding)
        {
        m_pIClassFactory=new CFigureClassFactory(this);

        if (NULL==m_pIClassFactory)
            return FALSE;

        //Since we hold on to this, we should AddRef it.
        m_pIClassFactory->AddRef();

        hr=CoRegisterClassObject(CLSID_Cosmo2Figure, m_pIClassFactory
            , CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE, &m_dwRegCO);

        if (FAILED(hr))
            return FALSE;
        }

    return CFrame::FInit(pFI);
    }

/*
 * CCosmoFrame::CreateCClient
 *
 * Purpose:
 *  Constructs a new client specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient        Pointer to the new client object.
 */

PCClient CCosmoFrame::CreateCClient(void)
    {
    return (PCClient)(new CCosmoClient(m_hInst));
    }

/*
 * CCosmoFrame::FRegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CCosmoFrame::FRegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //First let the standard frame do its thing
    if (!CFrame::FRegisterAllClasses())
        return FALSE;

    /*
     * We want a different background color for the document
     * because the Polyline we put in the document will paint
     * with COLOR_WINDOW which by default which is CLASSLIB's
     * default document color.
     */

    GetClassInfo(m_hInst, SZCLASSDOCUMENT, &wc);
    UnregisterClass(SZCLASSDOCUMENT, m_hInst);

    wc.hbrBackground=(HBRUSH)(COLOR_APPWORKSPACE+1);

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

    //Register the Polyline window.
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.hInstance     = m_hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PolylineWndProc;
    wc.cbWndExtra    = CBPOLYLINEWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_CROSS);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPOLYLINE;

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

    return TRUE;
    }

/*
 * CCosmoFrame::FPreShowInit
 *
 * Purpose:
 *  Called from FInit before intially showing the window.  We do
 *  whatever else we want here, modifying nCmdShow as necessary
 *  which affects ShowWindow in FInit.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if this initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CCosmoFrame::FPreShowInit(void)
    {
    CreateLineMenu();
    CheckLineSelection(IDM_LINESOLID);

    //Save the window handle for shutdown if necessary.
    g_hWnd=m_hWnd;

    //If we're -Embedding, don't show the window initially.
    if (m_fEmbedding)
        m_nCmdShow=SW_HIDE;

    return TRUE;
    }

/*
 * CCosmoFrame::ParseCommandLine
 *
 * Purpose:
 *  Allows the application to parse the command line and take action
 *  after the window has possibly been shown. For a compound
 *  document server we need to just make sure that if -Embedding
 *  is there that we take no file action.  FPreShowInit has already
 *  handled the window visibility.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if this initialization succeeded,
 *                  FALSE otherwise.
 */

void CCosmoFrame::ParseCommandLine(void)
    {
    //If -Embedding was there, prevent any attempt to load a file.
    if (m_fEmbedding)
        return;

    CFrame::ParseCommandLine();
    return;
    }

/*
 * CCosmoFrame::CreateLineMenu
 *
 * Purpose:
 *  Initializes the bitmaps used to create the Line menu and
 *  replaces the text items defined in the application resources
 *  with these bitmaps.  Note that the contents of m_hBmpLines
 *  must be cleaned up when the application terminates.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CreateLineMenu(void)
    {
    HMENU       hMenu;
    HDC         hDC, hMemDC;
    HPEN        hPen;
    HGDIOBJ     hObj;
    TEXTMETRIC  tm;
    UINT        i, cx, cy;

    hMenu=GetSubMenu(GetMenu(m_hWnd), 3);   //Line menu.
    hDC=GetDC(m_hWnd);

    //Create each line in a menu item 8 chars wide, one char high.
    GetTextMetrics(hDC, &tm);
    cx=tm.tmAveCharWidth*8;
    cy=tm.tmHeight;

    /*
     * Create a memory DC in which to draw lines, and bitmaps
     * for each line.
     */
    hMemDC=CreateCompatibleDC(hDC);
    ReleaseDC(m_hWnd, hDC);

    for (i=0; i<5; i++)
        {
        m_hBmpLines[i]=CreateCompatibleBitmap(hMemDC, cx, cy);
        SelectObject(hMemDC, m_hBmpLines[i]);

        PatBlt(hMemDC, 0, 0, cx, cy, WHITENESS);

        hPen=CreatePen(i, 1, 0L);       //i=line style like PS_SOLID
        hObj=SelectObject(hMemDC, hPen);

        MoveToEx(hMemDC, 0, cy/2, NULL);
        LineTo(hMemDC, cx, cy/2);

        ModifyMenu(hMenu, IDM_LINEMIN+i, MF_BYCOMMAND | MF_BITMAP
            , IDM_LINEMIN+i, (LPTSTR)(LONG)(UINT)m_hBmpLines[i]);

        SelectObject(hMemDC, hObj);
        DeleteObject(hPen);
        }

    CheckMenuItem(hMenu, IDM_LINESOLID, MF_CHECKED);
    DeleteDC(hMemDC);

    return;
    }

/*
 * CCosmoFrame::CreateGizmos
 *
 * Purpose:
 *  Procedure to create all the necessary gizmobar buttons.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of gizmos added to the bar.
 */

UINT CCosmoFrame::CreateGizmos(void)
    {
    UINT            iLast;
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;
    UINT            utEx  =GIZMOTYPE_BUTTONATTRIBUTEEX;

    //Insert the standard ones.
    iLast=CFrame::CreateGizmos();

    /*
     * Insert File Import in the 5th position and account for
     * it in iLast.
     */
    m_pGB->Add(utCmd, 4, IDM_FILEIMPORT, m_dxB, m_dyB
        , NULL, m_hBmp, 2, uState);
    iLast++;

    //Separator
    m_pGB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    /*
     * For the Background bitmap, preserve our use of black
     * (part of the image)
     */
    m_pGB->Add(utCmd, iLast++, IDM_COLORBACKGROUND, m_dxB, m_dyB
        , NULL, m_hBmp, 3, GIZMO_NORMAL | PRESERVE_BLACK);

    m_pGB->Add(utCmd, iLast++, IDM_COLORLINE, m_dxB, m_dyB
        , NULL, m_hBmp, 4, uState);

    //Separator
    m_pGB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //Line styles.
    m_pGB->Add(utEx, iLast++, IDM_LINESOLID, m_dxB, m_dyB
        , NULL, m_hBmp, 5, uState);
    m_pGB->Add(utEx, iLast++, IDM_LINEDASH, m_dxB, m_dyB
        , NULL, m_hBmp, 6, uState);
    m_pGB->Add(utEx, iLast++, IDM_LINEDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 7, uState);
    m_pGB->Add(utEx, iLast++, IDM_LINEDASHDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 8, uState);
    m_pGB->Add(utEx, iLast++, IDM_LINEDASHDOTDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 9, uState);

    return iLast;
    }

/*
 * CCosmoFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the Cosmo frame window that just
 *  processes the line menu and the color menu leaving the
 *  CFrame to do everything else.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CCosmoFrame::OnCommand(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PCCosmoDoc      pDoc;
    TCHAR           szFile[CCHPATHMAX];
    BOOL            fOK;
    UINT            i, uTemp;
    COLORREF        rgColors[16];
    CHOOSECOLOR     cc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many GizmoBar notifications.
     */
    if (m_fInit)
        return 0L;

    pDoc=(PCCosmoDoc)m_pCL->ActiveDocument();

    /*
     * Check for the line style commands which are
     * IDM_LINEMIN+<style>.  We handle this by changing the menu
     * and toolbar, then we pass it to the document for real
     * processing.
     */
    if (NULL!=pDoc && IDM_LINEMIN <= wID && IDM_LINEMAX >=wID)
        {
        CheckLineSelection(wID);
        pDoc->LineStyleSet(wID-IDM_LINEMIN);
        return 0L;
        }

    switch (wID)
        {
        case IDM_FILEIMPORT:
            szFile[0]=0;
            fOK=FSaveOpenDialog(szFile, CCHPATHMAX, IDS_FILEIMPORT
                , TRUE, &i);

            if (fOK)
                {
                uTemp=pDoc->ULoad(FALSE, szFile);
                pDoc->ErrorMessage(uTemp);
                }

            return (LRESULT)fOK;

        case IDM_COLORBACKGROUND:
        case IDM_COLORLINE:
            //Invoke the color chooser for either color
            uTemp=(IDM_COLORBACKGROUND==wID)
                ? DOCCOLOR_BACKGROUND : DOCCOLOR_LINE;

            for (i=0; i<16; i++)
                rgColors[i]=RGB(0, 0, i*16);

            memset(&cc, 0, sizeof(CHOOSECOLOR));
            cc.lStructSize=sizeof(CHOOSECOLOR);
            cc.lpCustColors=rgColors;
            cc.hwndOwner=hWnd;
            cc.Flags=CC_RGBINIT;
            cc.rgbResult=pDoc->ColorGet(uTemp);

            if (ChooseColor(&cc))
                pDoc->ColorSet(uTemp, cc.rgbResult);

            break;

        default:
           CFrame::OnCommand(hWnd, wParam, lParam);
        }

    return 0L;
    }

/*
 * CCosmoFrame::OnDocumentDataChange
 *
 * Purpose:
 *  Update the Line menu and GizmoBar if the style in the data
 *  changes.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentDataChange(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::OnDocumentActivate
 *
 * Purpose:
 *  Informs us that document activation changed, so update the UI
 *  for that new document.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentActivate(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    PCDocument  pDoc;
    BOOL        fOK=FALSE;
    BOOL        fCallDefault=TRUE;
    UINT        i;
    UINT        uTemp;
    UINT        uTempE;
    UINT        uTempD;

    pDoc=m_pCL->ActiveDocument();

    uTempE=MF_ENABLED | MF_BYCOMMAND;
    uTempD=MF_DISABLED | MF_GRAYED | MF_BYCOMMAND;
    uTemp=((NULL!=pDoc) ? uTempE : uTempD);

    //File menu:  If there is document window, disable Import.
    if (m_phMenu[0]==hMenu)
        EnableMenuItem(hMenu, IDM_FILEIMPORT, uTemp);

    //Color menu:  no document, no commands
    if (m_phMenu[2]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_COLORBACKGROUND, uTemp);
        EnableMenuItem(hMenu, IDM_COLORLINE,       uTemp);
        fCallDefault=FALSE;
        }

    //Line menu:  no document, no commands
    if (m_phMenu[3]==hMenu)
        {
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            EnableMenuItem(hMenu, i, uTemp);

        fCallDefault=FALSE;
        }

    if (fCallDefault)
        CFrame::UpdateMenus(hMenu, iMenu);

    return;
    }

/*
 * CCosmoFrame::UpdateGizmos
 *
 * Purpose:
 *  Enables and disables gizmos depending on whether we have
 *  a document or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateGizmos(void)
    {
    BOOL        fLast;
    UINT        i;

    //Save the last enabled state before CFrame changes it
    fLast=m_fLastEnable;

    //Let the default hack on its gizmos.
    CFrame::UpdateGizmos();

    /*
     * If CFrame::UpdateGizmos changed anything, then we need
     * to change as well--if nothing changes, nothing to do.
     */
    if (fLast!=m_fLastEnable)
        {
        m_pGB->Enable(IDM_FILEIMPORT, m_fLastEnable);

        m_pGB->Enable(IDM_COLORBACKGROUND, m_fLastEnable);
        m_pGB->Enable(IDM_COLORLINE,       m_fLastEnable);

        for (i=IDM_LINEMIN; i <= IDM_LINEMAX; i++)
            m_pGB->Enable(i, m_fLastEnable);
        }

    return;
    }

/*
 * CCosmoFrame::CheckLineSelection
 *
 * Purpose:
 *  Maintains the bitmap menu and the gizmos for the line selection.
 *  Both are mutially exclusive option lists where a selection in
 *  one has to affect the other.
 *
 * Parameters:
 *  uID             UINT ID of the item to be selected
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CheckLineSelection(UINT uID)
    {
    UINT        i;
    HMENU       hMenu;

    //Update menus and gizmo if the selection changed.
    if (uID!=m_uIDCurLine)
        {
        m_uIDCurLine=uID;
        hMenu=GetMenu(m_hWnd);

        //Uncheck all lines initially.
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            CheckMenuItem(hMenu, i, MF_UNCHECKED | MF_BYCOMMAND);

        CheckMenuItem(hMenu, uID, MF_CHECKED | MF_BYCOMMAND);
        m_pGB->Check(uID, TRUE);
        }

    return;
    }

/*
 * CCosmoFrame::UpdateEmbeddingUI
 *
 * Purpose:
 *  Puts the application into the user interface for editing an
 *  embedded object, manipulating menus and title bars.
 *
 * Parameters:
 *  fEmbedding      BOOL TRUE to go in the mode, FALSE to leave it.
 *  pszApp          LPCTSTR name of the container application as
 *                  received in IOleObject::SetHostNames.
 *  pszObj          LPCTSTR name of the object in the container as
 *                  received in IOleObject::SetHostNames.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateEmbeddingUI(BOOL fEmbedding
    , PCDocument pDoc, LPCTSTR pszApp, LPCTSTR pszObj)
    {
    HMENU           hMenu;
    TCHAR           szTemp[256];

    //First let's play with the File menu.
    hMenu=m_phMenu[0];

    //Remove or add the File New, Open, and Save items
    if (fEmbedding)
        {
        DeleteMenu(m_phMenu[0], IDM_FILENEW,   MF_BYCOMMAND);
        DeleteMenu(m_phMenu[0], IDM_FILEOPEN,  MF_BYCOMMAND);
        DeleteMenu(m_phMenu[0], IDM_FILECLOSE, MF_BYCOMMAND);
        DeleteMenu(m_phMenu[0], IDM_FILESAVE,  MF_BYCOMMAND);

        //Save As->Save Copy As
        ModifyMenu(m_phMenu[0], IDM_FILESAVEAS, MF_BYCOMMAND
            , IDM_FILESAVEAS, PSZ(IDS_SAVECOPYAS));

        }
    else
        {
        InsertMenu(m_phMenu[0], 0, MF_BYPOSITION, IDM_FILENEW
            , PSZ(IDS_NEW));
        InsertMenu(m_phMenu[0], 1, MF_BYPOSITION, IDM_FILEOPEN
            , PSZ(IDS_OPEN));
        InsertMenu(m_phMenu[0], 2, MF_BYPOSITION, IDM_FILESAVE
            , PSZ(IDS_SAVE));
        InsertMenu(m_phMenu[0], 3, MF_BYPOSITION, IDM_FILECLOSE
            , PSZ(IDS_SAVE));

        //Save Copy As->Save As
        ModifyMenu(m_phMenu[0], IDM_FILESAVEAS, MF_BYCOMMAND
            , IDM_FILESAVEAS, PSZ(IDS_SAVEAS));
        }

    //Change "Exit" to "Exit & Return to xx" or vice-versa for SDI
    if (fEmbedding)
        wsprintf(szTemp, PSZ(IDS_EXITANDRETURN), (LPSTR)pszObj);
    else
        lstrcpy(szTemp, PSZ(IDS_EXIT));

    ModifyMenu(m_phMenu[0], IDM_FILEEXIT, MF_STRING, IDM_FILEEXIT
        , szTemp);
    DrawMenuBar(m_hWnd);

    //Now let's play with the gizmobar.
    m_pGB->Show(IDM_FILENEW,   !fEmbedding);
    m_pGB->Show(IDM_FILEOPEN,  !fEmbedding);
    m_pGB->Show(IDM_FILECLOSE, !fEmbedding);
    m_pGB->Show(IDM_FILESAVE,  !fEmbedding);

    //Enable what's left appropriately.
    UpdateGizmos();

    //Now play with the title bar.

    //IDS_EMBEDDINGCAPTION is MDI/SDI sensitive in COSMO.RC.
    wsprintf(szTemp, PSZ(IDS_EMBEDDINGCAPTION), pszObj);

    /*
     * Remember that in MDI situations that Windows takes care of
     * the frame window caption bar when the document is maximized.
     */
   #ifdef MDI
    SetWindowText(pDoc->Window(), szTemp);
   #else
    SetWindowText(m_hWnd, szTemp);
   #endif

    return;
    }


COSMO.H

/*
 * COSMO.H
 * Cosmo Chapter 13
 *
 * Single include file that pulls in everything needed for other
 * source files in the Cosmo application.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _COSMO_H_
#define _COSMO_H_

#define INC_OLE2
#include <windows.h>
#include <memory.h>
#include <ole2.h>
#include <ole2ver.h>
#include <olestd.h>
#include <bookguid.h>

extern "C"
    {
    #include <commdlg.h>
    }

#include <classlib.h>

#include "resource.h"

//Get the editor window information.
#include "polyline.h"

//COSMO.CPP:  Frame object that creates a main window

class CCosmoFrame : public CFrame
    {
    friend class CFigureClassFactory;
    friend class CFigure;   //For UI purposes.

    private:
        HBITMAP         m_hBmpLines[5];     //Menu item bitmaps
        UINT            m_uIDCurLine;       //Current line selection
        BOOL            m_fInitialized;     //Did OleInitalize work?
        BOOL            m_fOleStdInit;      //Did OleStdInitialize work?
        LPCLASSFACTORY  m_pIClassDataTran;  //For locking

        BOOL            m_fEmbedding;       //-Embedding found?
        DWORD           m_dwRegCO;          //Registration key
        LPCLASSFACTORY  m_pIClassFactory;

    protected:
        //Overridable for creating a CClient for this frame
        virtual PCClient  CreateCClient(void);

        virtual BOOL      FRegisterAllClasses(void);
        virtual BOOL      FPreShowInit(void);
        virtual UINT      CreateGizmos(void);

        virtual void      ParseCommandLine(void);

        virtual LRESULT   OnCommand(HWND, WPARAM, LPARAM);
        virtual void      OnDocumentDataChange(PCDocument);
        virtual void      OnDocumentActivate(PCDocument);

        //New for this class
        virtual void      CreateLineMenu(void);

    public:
        CCosmoFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CCosmoFrame(void);

        //Overrides
        virtual BOOL      FInit(PFRAMEINIT);
        virtual void      UpdateMenus(HMENU, UINT);
        virtual void      UpdateGizmos(void);

        //New for this class
        virtual void      CheckLineSelection(UINT);
        virtual void      UpdateEmbeddingUI(BOOL, PCDocument
                              , LPCTSTR, LPCTSTR);
    };

typedef CCosmoFrame *PCCosmoFrame;

//CLIENT.CPP

/*
 * The only reason we have a derived class here is to override
 * CreateCDocument so we can create our own type as well as
 * overriding NewDocument to perform one other piece of work once
 * the document's been created.
 */

class CCosmoClient : public CClient
    {
    protected:
        //Overridable for creating a new CDocument
        virtual PCDocument CreateCDocument(void);

    public:
        CCosmoClient(HINSTANCE);
        virtual ~CCosmoClient(void);

        virtual PCDocument NewDocument(BOOL, PCDocumentAdviseSink);
    };

typedef CCosmoClient *PCCosmoClient;

//DOCUMENT.CPP

//Constant ID for the window polyline that lives in a document
#define ID_POLYLINE         10

class CCosmoDoc : public CDocument
    {
    friend class CPolylineAdviseSink;

    //These need access to FQueryPasteFromData, FPasteFromData
    friend class CDropTarget;
    friend class CDropSource;
    friend class CFigureClassFactory;
    friend class CFigure;

    protected:
        UINT                    m_uPrevSize;    //Last WM_SIZE wParam
        LONG                    m_lVer;         //Loaded Polyline ver

        PCPolyline              m_pPL;          //Polyline window here
        PCPolylineAdviseSink    m_pPLAdv;       //Advises from Polyline

        class CDropTarget      *m_pDropTarget;  //Registered target
        BOOL                    m_fDragSource;  //Source==target?

        UINT                    m_cfEmbedSource;
        UINT                    m_cfObjectDescriptor;

        class CFigure          *m_pFigure;      //The object in us.

        //CHAPTER13MOD
        LPMONIKER               m_pMoniker;     //Our file
        DWORD                   m_dwRegROT;     //From IROT::Register

        UINT                    m_cfLinkSource;
        UINT                    m_cfLinkSrcDescriptor;
        //End CHAPTER13MOD

    protected:
        virtual BOOL    FMessageHook(HWND, UINT, WPARAM, LPARAM
            , LRESULT *);

        void            DropSelectTargetWindow(void);

    public:
        CCosmoDoc(HINSTANCE, PCFrame);
        virtual ~CCosmoDoc(void);

        virtual BOOL     FInit(PDOCUMENTINIT);

        virtual void     Clear(void);

        virtual BOOL     FDirtySet(BOOL);
        virtual BOOL     FDirtyGet(void);

        virtual UINT     ULoad(BOOL, LPTSTR);
        virtual UINT     USave(UINT, LPTSTR);
        //CHAPTER13MOD
        virtual void     Rename(LPTSTR);
        //End CHAPTER13MOD

        virtual void     Undo(void);
        virtual BOOL     FClip(HWND, BOOL);
        virtual HGLOBAL  RenderFormat(UINT);
        virtual BOOL     FRenderMedium(UINT, LPSTGMEDIUM);
        virtual BOOL     FQueryPaste(void);
        virtual BOOL     FPaste(HWND);

        //These were protected.  Now for IOleObject, should be public.
        virtual BOOL     FQueryPasteFromData(LPDATAOBJECT);
        virtual BOOL     FPasteFromData(LPDATAOBJECT);
        LPDATAOBJECT     TransferObjectCreate(BOOL);

        virtual COLORREF ColorSet(UINT, COLORREF);
        virtual COLORREF ColorGet(UINT);

        virtual UINT     LineStyleSet(UINT);
        virtual UINT     LineStyleGet(void);
    };

typedef CCosmoDoc *PCCosmoDoc;

//These color indices wrap the polyline definitions
#define DOCCOLOR_BACKGROUND             POLYLINECOLOR_BACKGROUND
#define DOCCOLOR_LINE                   POLYLINECOLOR_LINE

//Drag-drop interfaces we need in the document
class CDropTarget : public IDropTarget
    {
    protected:
        ULONG               m_cRef;
        PCCosmoDoc          m_pDoc;

        LPDATAOBJECT        m_pIDataObject;     //From DragEnter

    public:
        CDropTarget(PCCosmoDoc);
        ~CDropTarget(void);

        //IDropTarget interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP DragEnter(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
        STDMETHODIMP DragOver(DWORD, POINTL, LPDWORD);
        STDMETHODIMP DragLeave(void);
        STDMETHODIMP Drop(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
    };

typedef CDropTarget *PCDropTarget;

class CDropSource : public IDropSource
    {
    protected:
        ULONG               m_cRef;
        PCCosmoDoc          m_pDoc;

    public:
        CDropSource(PCCosmoDoc);
        ~CDropSource(void);

        //IDropSource interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP QueryContinueDrag(BOOL, DWORD);
        STDMETHODIMP GiveFeedback(DWORD);
    };

typedef CDropSource *PCDropSource;

//Include classes necessary to become an OLE 2.0 server.
#include "cosmole.h"

#endif //_COSMO_H_


DOCUMENT.CPP

/*
 * DOCUMENT.CPP
 * Cosmo Chapter 13
 *
 * Implementation of the CCosmoDoc derivation of CDocument as
 * well as an implementation of CPolylineAdviseSink.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CCosmoDoc::CCosmoDoc
 * CCosmoDoc::~CCosmoDoc
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame of the frame object.
 */

CCosmoDoc::CCosmoDoc(HINSTANCE hInst, PCFrame pFR)
    : CDocument(hInst, pFR)
    {
    m_pPL=NULL;
    m_pPLAdv=NULL;
    m_uPrevSize=SIZE_RESTORED;

    m_pDropTarget=NULL;
    m_fDragSource=FALSE;

    m_cfEmbedSource=RegisterClipboardFormat(CF_EMBEDSOURCE);
    m_cfObjectDescriptor=RegisterClipboardFormat
        (CF_OBJECTDESCRIPTOR);

    m_pFigure=NULL;

    //CHAPTER13MOD
    m_pMoniker=NULL;
    m_dwRegROT=0L;

    m_cfLinkSource=RegisterClipboardFormat(CF_LINKSOURCE);
    m_cfLinkSrcDescriptor=RegisterClipboardFormat
        (CF_LINKSRCDESCRIPTOR);
    //End CHAPTER13MOD
    return;
    }

CCosmoDoc::~CCosmoDoc(void)
    {
    //Make sure the object is saved
    m_pFigure->SendAdvise(OBJECTCODE_SAVEOBJECT);
    m_pFigure->SendAdvise(OBJECTCODE_HIDEWINDOW);
    m_pFigure->SendAdvise(OBJECTCODE_CLOSED);

    //CHAPTER13MOD
    //If the document is reg'd as running, revoke and free the moniker
    if (0L!=m_dwRegROT)
        OleStdRevokeAsRunning(&m_dwRegROT);

    if (NULL!=m_pMoniker)
        m_pMoniker->Release();
    //End CHAPTER13MOD

    if (NULL!=m_pFigure)
        {
        m_pFigure->AddRef();
        CoDisconnectObject(m_pFigure, 0L);
        m_pFigure->Release();   //Starts shutdown if necessary.
        delete m_pFigure;
        }

    //Clean up the allocations we did in FInit
    if (NULL!=m_pPL)
        delete m_pPL;

    if (NULL!=m_pPLAdv)
        delete m_pPLAdv;

    return;
    }

/*
 * CCosmoDoc::FInit
 *
 * Purpose:
 *  Initializes an already created document window.  The client
 *  actually creates the window for us, then passes that here for
 *  further initialization.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CCosmoDoc::FInit(PDOCUMENTINIT pDI)
    {
    RECT        rc;

    //Change the stringtable range to our customization.
    pDI->idsMin=IDS_DOCUMENTMIN;
    pDI->idsMax=IDS_DOCUMENTMAX;

    //Do default initialization
    if (!CDocument::FInit(pDI))
        return FALSE;

    //Add the Polyline stuff we need.
    m_pPLAdv=new CPolylineAdviseSink(this);
    m_pPL   =new CPolyline(m_hInst);

    //Attempt to create our contained Polyline.
    GetClientRect(m_hWnd, &rc);
    InflateRect(&rc, -8, -8);

    if (!m_pPL->FInit(m_hWnd, &rc, WS_CHILD | WS_VISIBLE
        , ID_POLYLINE, m_pPLAdv))
        return FALSE;

    m_pDropTarget=new CDropTarget(this);

    if (NULL!=m_pDropTarget)
        {
        m_pDropTarget->AddRef();
        CoLockObjectExternal(m_pDropTarget, TRUE, FALSE);
        RegisterDragDrop(m_hWnd, m_pDropTarget);
        }

    m_pFigure=new CFigure(ObjectDestroyed, this);

    if (NULL==m_pFigure)
        return FALSE;

    //We created an object, so count it.
    g_cObj++;

    if (!m_pFigure->FInit())
        return FALSE;

    return TRUE;
    }

/*
 * CCosmoDoc::FMessageHook
 *
 * Purpose:
 *  Processes WM_SIZE for the document so we can resize
 *  the Polyline.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CCosmoDoc::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    UINT        dx, dy;
    RECT        rc;

    *pLRes=0;

    if (WM_SIZE==iMsg)
        {
        //Don't effect the Polyline size to or from minimized state.
        if (SIZE_MINIMIZED!=wParam && SIZE_MINIMIZED !=m_uPrevSize)
            {
            //When we change size, resize any Polyline we hold.
            dx=LOWORD(lParam);
            dy=HIWORD(lParam);

            /*
             * If we are getting WM_SIZE in response to a Polyline
             * notification, then don't resize the Polyline window
             * again.
             */
            if (!m_fNoSize && NULL!=m_pPL)
                {
                //Resize the polyline to fit the new client
                SetRect(&rc, 8, 8, dx-8, dy-8);
                m_pPL->RectSet(&rc, FALSE);

                /*
                 * We consider sizing something that makes the file
                 * dirty, but not until we've finished the create
                 * process, which is why we set fNoDirty to FALSE
                 * in WM_CREATE since we get a WM_SIZE on the first
                 * creation.
                 */
                if (!m_fNoDirty)
                    FDirtySet(TRUE);

                SetRect(&rc, 0, 0, dx, dy);

                if (NULL!=m_pAdv)
                    m_pAdv->OnSizeChange(this, &rc);

                m_fNoDirty=FALSE;
                }
            }

        m_uPrevSize=wParam;
        }

    if (WM_LBUTTONDOWN==iMsg)
        {
        LPDROPSOURCE    pIDropSource;
        LPDATAOBJECT    pIDataObject;
        HRESULT         hr;
        SCODE           sc;
        DWORD           dwEffect;

        /*
         * The document has an 8 pixel border around the polyline
         * window where we'll see mouse clicks.  A left mouse button
         * click here means the start of a drag-drop operation.
         *
         * Since this is a modal operation, this IDropSource
         * is entirely local.
         */

        pIDropSource=new CDropSource(this);

        if (NULL==pIDropSource)
            return FALSE;

        pIDropSource->AddRef();
        m_fDragSource=TRUE;

        //Go get the data and start the ball rolling.
        pIDataObject=TransferObjectCreate(FALSE);

        if (NULL!=pIDataObject)
            {
            //CHAPTER13MOD
            //Add DROPEFFECT_LINK now that we source links
            hr=DoDragDrop(pIDataObject, pIDropSource
                , DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK
                , &dwEffect);
            //End CHAPTER13MOD

            pIDataObject->Release();
            sc=GetScode(hr);
            }
        else
            sc=E_FAIL;

        /*
         * When we return from DoDragDrop, either cancel or drop.
         * First toss the IDropSource we have here, then bail out
         * on cancel, and possibly clear our data on a move drop.
         */

        pIDropSource->Release();

        /*
         * If dropped on the same document (determined using
         * this flag, then dwEffect will be DROPEFFECT_NONE (see
         * IDropTarget::Drop in IDROPTGT.CPP).  In any case,
         * reset this since the operation is done.
         */

        m_fDragSource=FALSE;

        if (DRAGDROP_S_DROP==sc && DROPEFFECT_MOVE==dwEffect)
            {
            m_pPL->New();
            FDirtySet(TRUE);
            }

        //On a canceled drop or a copy we don't do anything else
        return TRUE;
        }

    if (WM_DESTROY==iMsg)
        {
        /*
         * We have to revoke the drop target here because the window
         * will be destroyed and the property forcefully removed
         * before we could do this in the destructor.
         */
        if (NULL!=m_pDropTarget)
            {
            RevokeDragDrop(m_hWnd);
            CoLockObjectExternal(m_pDropTarget, FALSE, TRUE);
            m_pDropTarget->Release();
            }

        return FALSE;
        }

    /*
     * We return FALSE even on WM_SIZE so we can let the default
     * procedure handle maximized MDI child windows appropriately.
     */
    return FALSE;
    }

/*
 * CCosmoDoc::Clear
 *
 * Purpose:
 *  Sets all contents in the document back to defaults with
 *  no filename.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Clear(void)
    {
    //Completely reset the polyline
    m_pPL->New();

    CDocument::Clear();
    m_lVer=0;
    return;
    }

/*
 * CCosmoDoc::FDirtySet
 *
 * Purpose:
 *  Sets or clears the document 'dirty' flag returning the previous
 *  state of that same flag.  We override this in Cosmo server to
 *  send the OnDataChange notification as necessary.
 *
 * Parameters:
 *  fDirty          BOOL indicating the new contents of the dirty
 *                  flag.
 *
 * Return Value:
 *  BOOL            Previous value of the dirty flag.
 */

BOOL CCosmoDoc::FDirtySet(BOOL fDirty)
    {
    BOOL        fRet;

    fRet=CDocument::FDirtySet(fDirty);
    m_pFigure->SendAdvise(OBJECTCODE_DATACHANGED);

    return fRet;
    }

/*
 * CCosmoDoc::FDirtyGet
 *
 * Purpose:
 *  Override of the normal FDirtyGet such that we never return dirty
 *  for an embedded object we're serving since updates constantly
 *  happen and since the object will be saved on closure.  This then
 *  prevents any message boxes asking the user to save.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the document is dirty, FALSE otherwise.
 */

BOOL CCosmoDoc::FDirtyGet(void)
    {
    if (m_pFigure->FIsEmbedded())
        return FALSE;

    return m_fDirty;
    }

/*
 * CCosmoDoc::ULoad
 *
 * Purpose:
 *  Loads a given document without any user interface overwriting
 *  the previous contents of the Polyline window.  We do this by
 *  opening the file and telling the Polyline to load itself from
 *  that file.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load, NULL if the file
 *                  is new and untitled.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_...
 */

UINT CCosmoDoc::ULoad(BOOL fChangeFile, LPTSTR pszFile)
    {
    HRESULT         hr;
    LPSTORAGE       pIStorage;

    if (NULL==pszFile)
        {
        //For a new untitled document, just rename ourselves.
        Rename(NULL);
        m_lVer=VERSIONCURRENT;
        return DOCERR_NONE;
        }

    /*
     * If not a Compound File, open the file using STGM_CONVERT in
     * transacted mode to see old files as a storage with one stream
     * called "CONTENTS" (which is conveniently the name we use
     * in the new files).  We must use STGM_TRANSACTED here or else
     * the old file will be immediately converted on disk:  we only
     * want a converted image in memory from which to read.  In
     * addition, note that we need STGM_READWRITE as well since
     * conversion is inherently a write operation.
     */

    pIStorage=NULL;
    OLECHAR pwcsFile[MAX_PATH];
#if defined(WIN32) && !defined(UNICODE)
    mbstowcs(pwcsFile, pszFile, MAX_PATH);
#else
    pwcsFile=pszFile;
#endif
    if (NOERROR!=StgIsStorageFile(pwcsFile))
        {
        hr=StgCreateDocfile(pwcsFile,STGM_TRANSACTED | STGM_READWRITE
            | STGM_CONVERT | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

        if (FAILED(hr))
            {
            //If denied write access, try to load the old way
            if (STG_E_ACCESSDENIED==GetScode(hr))
                m_lVer=m_pPL->ReadFromFile(pszFile);
            else
                return DOCERR_COULDNOTOPEN;
            }
        }
    else
        {
        hr=StgOpenStorage(pwcsFile, NULL, STGM_DIRECT | STGM_READ
            | STGM_SHARE_EXCLUSIVE, NULL, 0, &pIStorage);

        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;
        }

    if (NULL!=pIStorage)
        {
        m_lVer=m_pPL->ReadFromStorage(pIStorage);
        pIStorage->Release();
        }

    if (POLYLINE_E_READFAILURE==m_lVer)
        return DOCERR_READFAILURE;

    if (POLYLINE_E_UNSUPPORTEDVERSION==m_lVer)
        return DOCERR_UNSUPPORTEDVERSION;

    if (fChangeFile)
        Rename(pszFile);

    //Importing a file makes things dirty
    FDirtySet(!fChangeFile);

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::USave
 *
 * Purpose:
 *  Writes the file to a known filename, requiring that the user has
 *  previously used FileOpen or FileSaveAs to provide a filename.
 *
 * Parameters:
 *  uType           UINT indicating the type of file the user
 *                  requested to save in the File Save As dialog.
 *  pszFile         LPTSTR under which to save.  If NULL, use the
 *                  current name.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_...
 */

UINT CCosmoDoc::USave(UINT uType, LPTSTR pszFile)
    {
    LONG        lVer, lRet;
    UINT        uTemp;
    BOOL        fRename=TRUE;
    HRESULT     hr;
    LPSTORAGE   pIStorage;
    BOOL        fEmbedding;

    fEmbedding=m_pFigure->FIsEmbedded();

    if (NULL==pszFile)
        {
        fRename=FALSE;
        pszFile=m_szFile;
        }

    /*
     * Type 1 is the current version, type 2 is version 1.0 of the
     * Polyline so we use this to send the right version to
     * CPolyline::WriteToFile.
     */

    switch (uType)
        {
        case 0:         //From Save, use loaded version.
            lVer=m_lVer;
            break;

        case 1:
            lVer=VERSIONCURRENT;
            break;

        case 2:
            lVer=MAKELONG(0, 1);    //1.0
            break;

        default:
            return DOCERR_UNSUPPORTEDVERSION;
        }

    /*
     * If the version the user wants to save is different from the
     * version that we loaded and m_lVer is not zero (new doc),
     * then inform the user of the version change and verify.
     */

    //For embedding, this is Save Copy As, so don't ask about versions.
    if (0!=m_lVer && m_lVer!=lVer && !fEmbedding)
        {
        TCHAR       szMsg[128];

        wsprintf(szMsg, PSZ(IDS_VERSIONCHANGE)
            , (UINT)HIWORD(m_lVer), (UINT)LOWORD(m_lVer)
            , (UINT)HIWORD(lVer), (UINT)LOWORD(lVer));

        uTemp=MessageBox(m_hWnd, szMsg, PSZ(IDS_DOCUMENTCAPTION)
            , MB_YESNOCANCEL);

        if (IDCANCEL==uTemp)
            return DOCERR_CANCELLED;

        //If the user won't upgrade, revert to loaded version.
        if (IDNO==uTemp)
            lVer=m_lVer;
        }

    /*
     * For 1.0 files, still use the old code.  For new files, use
     * storages instead
     */
    if (lVer==MAKELONG(0, 1))
        lRet=m_pPL->WriteToFile(pszFile, lVer);
    else
        {
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsFile[MAX_PATH];
        mbstowcs(pwcsFile, pszFile, MAX_PATH);
        hr=StgCreateDocfile(pwcsFile, STGM_DIRECT | STGM_READWRITE
            | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);
#else
        hr=StgCreateDocfile(pszFile, STGM_DIRECT | STGM_READWRITE
            | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);
#endif
        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;

        //Mark this as one of our class
        WriteClassStg(pIStorage, CLSID_Cosmo2Figure);

        //Write user-readable class information
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsTemp[256];
        mbstowcs(pwcsTemp, PSZ(IDS_CLIPBOARDFORMAT), 256);
        WriteFmtUserTypeStg(pIStorage, m_cf, pwcsTemp);
#else
        WriteFmtUserTypeStg(pIStorage, m_cf
            , PSZ(IDS_CLIPBOARDFORMAT));
#endif
        lRet=m_pPL->WriteToStorage(pIStorage, lVer);
        pIStorage->Release();
        }

    if (POLYLINE_E_NONE!=lRet)
        return DOCERR_WRITEFAILURE;

    //Saving makes us clean, but this doesn't apply to embedding.
    if (!fEmbedding)
        FDirtySet(FALSE);

    //Update the known version of this document.
    m_lVer=lVer;

    /*
     * If we're embedding, this is Save Copy As, so no rename.
     * Note that we also don't care about having been set to clean
     * since we're always 'clean' as an embedded object from
     * the user's perspective.
     */
    if (fRename && !fEmbedding)
        Rename(pszFile);

    return DOCERR_NONE;
    }

//CHAPTER13MOD
/*
 * CCosmoDoc::Rename
 *
 * Purpose:
 *  Overrides the normal rename to manage a moniker for the open
 *  file.  We maintain no moniker for untitled documents, and
 *  therefore do not allow linking to such documents.
 *
 * Parameters:
 *  pszFile         LPTSTR to the new filename.
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Rename(LPTSTR pszFile)
    {
    LPMONIKER   pmk;

    //We don't need to change the base class, just augment...
    CDocument::Rename(pszFile);

    /*
     * Get rid of the old moniker.  The OldStd function
     * sets the key to zero.
     */
    if (0L!=m_dwRegROT)
        OleStdRevokeAsRunning(&m_dwRegROT);

    if (NULL!=m_pMoniker)
        {
        m_pMoniker->Release();
        m_pMoniker=NULL;
        }

    if (NULL!=pszFile)
        {
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsFile[MAX_PATH];
        mbstowcs(pwcsFile, pszFile, MAX_PATH);
        CreateFileMoniker(pwcsFile, &pmk);
#else
        CreateFileMoniker(pszFile, &pmk);
#endif

        if (NULL!=pmk)
            {
            m_pMoniker=pmk;     //pmk AddRef'd in CreateFileMoniker
            OleStdRegisterAsRunning(m_pFigure, m_pMoniker
                , &m_dwRegROT);

            m_pFigure->SendAdvise(OBJECTCODE_RENAMED);
            }
        }

    return;
    }
//End CHAPTER13MOD

/*
 * CCosmoDoc::Undo
 *
 * Purpose:
 *  Reverses a previous action.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Undo(void)
    {
    m_pPL->Undo();
    return;
    }

/*
 * CCosmoDoc::FClip
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard, optionally implementing Cut by deleting the
 *  data in the current window after rendering.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *  fCut            BOOL indicating cut (TRUE) or copy (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::FClip(HWND hWndFrame, BOOL fCut)
    {
    BOOL            fRet=TRUE;
    LPDATAOBJECT    pIDataObject;

    pIDataObject=TransferObjectCreate(fCut);

    if (NULL==pIDataObject)
        return FALSE;

    fRet=SUCCEEDED(OleSetClipboard(pIDataObject));
    pIDataObject->Release();

    //Delete our current data if copying succeeded.
    if (fRet && fCut)
        {
        m_pPL->New();
        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CCosmoDoc::RenderFormat
 *
 * Purpose:
 *  Renders a specific clipboard format into global memory.
 *
 * Parameters:
 *  cf              UINT format to render.
 *
 * Return Value:
 *  HGLOBAL         Global memory handle containing the data.
 */

HGLOBAL CCosmoDoc::RenderFormat(UINT cf)
    {
    HGLOBAL     hMem;

    if (cf==m_cf)
        {
        m_pPL->DataGetMem(VERSIONCURRENT, &hMem);
        return hMem;
        }

    switch (cf)
        {
        case CF_METAFILEPICT:
            return m_pPL->RenderMetafilePict();

        case CF_BITMAP:
            return (HGLOBAL)m_pPL->RenderBitmap();
        }

    return NULL;
    }

/*
 * CCosmoDoc::FRenderMedium
 *
 * Purpose:
 *  Like RenderFormat, this function creates a specific data format
 *  based on the cf parameter.  Unlike RenderFormat, we store the
 *  result in a STGMEDIUM in case it has a medium other than
 *  TYMED_HGLOBAL.  For conveniece we'll centralize all compound
 *  document formats here, hGlobal or not.
 *
 * Parameters:
 *  cf              UINT clipboard format of interest.
 *  pSTM            LSTGMEDIUM to fill.  We only fill the union
 *                  and tymed.
 *
 * Return Value:
 *  BOOL            TRUE if we could render the format,
 *                  FALSE otherwise.
 */

BOOL CCosmoDoc::FRenderMedium(UINT cf, LPSTGMEDIUM pSTM)
    {
    if (NULL==pSTM)
        return FALSE;

    if (cf==m_cfEmbedSource)
        {
        /*
         * Embed Source data is an IStorage containing the native
         * data (same as Embedded Object).  Since our data is small,
         * it makes the most sense to create an IStorage in memory
         * and put transfer that instead of a disk-based IStorage.
         */

        pSTM->pstg=OleStdCreateStorageOnHGlobal(NULL, TRUE
            , STGM_DIRECT | STGM_READWRITE | STGM_SHARE_EXCLUSIVE);

        if (NULL==pSTM->pstg)
            return FALSE;

        //Now save the data to the storage.
        WriteClassStg(pSTM->pstg, CLSID_Cosmo2Figure);
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsTemp[256];
        mbstowcs(pwcsTemp, PSZ(IDS_CLIPBOARDFORMAT), 256);
        WriteFmtUserTypeStg(pSTM->pstg, m_cf, pwcsTemp);
#else
        WriteFmtUserTypeStg(pSTM->pstg, m_cf
            , PSZ(IDS_CLIPBOARDFORMAT));
#endif

        if (POLYLINE_E_NONE!=m_pPL->WriteToStorage(pSTM->pstg
            , VERSIONCURRENT))
            {
            /*
             * When someone releases the IStorage, STORAGE.DLL will
             * release the ILockBytes which, having fDeleteOnRelease
             * TRUE (second parameter) will release the memory.
             * That's why we don't have STGM_DELETEONRELEASE on the
             * IStorage.
             */
            pSTM->pstg->Release();
            return FALSE;
            }

        pSTM->tymed=TYMED_ISTORAGE;
        return TRUE;
        }

    //CHAPTER13MOD
    /*
     * CF_OBJECTDESCRIPTOR and CF_LINKSRCDESCRIPTOR are the same
     * formats, but only copy link source if we have a moniker.
     */
    if (cf==m_cfLinkSrcDescriptor && NULL==m_pMoniker)
        return FALSE;

    if (cf==m_cfObjectDescriptor || cf==m_cfLinkSrcDescriptor)
    //End CHAPTER13MOD
        {
        SIZEL   szl, szlT;
        POINTL  ptl;
        RECT    rc;
        //CHAPTER13MOD
        LPOLESTR  psz=NULL;
        //End CHAPTER13MOD

        m_pPL->SizeGet(&rc);
        SETSIZEL(szlT, rc.right, rc.bottom);
        XformSizeInPixelsToHimetric(NULL, &szlT, &szl);

        SETPOINTL(ptl, 0, 0);

        //CHAPTER13MOD
        //Include the moniker display name now, if we have one.
        if (m_pMoniker)
            {
            LPBC    pbc;

            CreateBindCtx(0, &pbc);
            m_pMoniker->GetDisplayName(pbc, NULL, &psz);
            pbc->Release();
            }
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsTemp[256];
        mbstowcs(pwcsTemp, PSZ(IDS_OBJECTDESCRIPTION), 256);
        pSTM->hGlobal=OleStdGetObjectDescriptorData
            (CLSID_Cosmo2Figure, DVASPECT_CONTENT, szl, ptl
            , OLEMISC_RECOMPOSEONRESIZE, pwcsTemp
            , psz);
#else
        pSTM->hGlobal=OleStdGetObjectDescriptorData
            (CLSID_Cosmo2Figure, DVASPECT_CONTENT, szl, ptl
            , OLEMISC_RECOMPOSEONRESIZE, PSZ(IDS_OBJECTDESCRIPTION)
            , psz);
#endif
        //End CHAPTER13MOD

        pSTM->tymed=TYMED_HGLOBAL;
        return (NULL!=pSTM->hGlobal);
        }

    //CHAPTER13MOD
    if (cf==m_cfLinkSource)
        {
        if (NULL!=m_pMoniker)
            {
            FORMATETC   fe;
            HRESULT     hr;

            pSTM->tymed=TYMED_NULL;
            SETDefFormatEtc(fe, cf, TYMED_ISTREAM);
            hr=OleStdGetLinkSourceData(m_pMoniker
                , (LPCLSID)&CLSID_Cosmo2Figure, &fe, pSTM);

            return SUCCEEDED(hr);
            }
        }
    //End CHAPTER13MOD

    return FALSE;
    }

/*
 * CCosmoDoc::FQueryPaste
 *
 * Purpose:
 *  Determines if we can paste data from the clipboard.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CCosmoDoc::FQueryPaste(void)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    fRet=FQueryPasteFromData(pIDataObject);
    pIDataObject->Release();
    return fRet;
    }

/*
 * CCosmoDoc::FQueryPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Determines if we can paste data from a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which we might want to paste.
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CCosmoDoc::FQueryPasteFromData(LPDATAOBJECT pIDataObject)
    {
    FORMATETC       fe;

    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    return (NOERROR==pIDataObject->QueryGetData(&fe));
    }

/*
 * CCosmoDoc::FPaste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  to the current figure in the editor window.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::FPaste(HWND hWndFrame)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    fRet=FPasteFromData(pIDataObject);
    pIDataObject->Release();

    return fRet;
    }

/*
 * CCosmoDoc::FPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Retrieves the private data format from a data object and sets
 *  it to the current figure in the editor window.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::FPasteFromData(LPDATAOBJECT pIDataObject)
    {
    FORMATETC       fe;
    STGMEDIUM       stm;
    BOOL            fRet;

    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    fRet=SUCCEEDED(pIDataObject->GetData(&fe, &stm));

    if (fRet && NULL!=stm.hGlobal)
        {
        m_pPL->DataSetMem(stm.hGlobal, FALSE, FALSE, TRUE);
        ReleaseStgMedium(&stm);
        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CCosmoDoc::TransferObjectCreate
 * (Protected)
 *
 * Purpose:
 *  Creates a DataTransferObject and stuffs the current Polyline
 *  data into it, used for both clipboard and drag-drop operations.
 *
 * Parameters:
 *  fCut            BOOL TRUE if we're cutting, FALSE if we're
 *                  copying.
 *
 * Return Value:
 *  LPDATAOBJECT    Pointer to the object created, NULL on failure
 */

LPDATAOBJECT CCosmoDoc::TransferObjectCreate(BOOL fCut)
    {
    UINT            i;
    HRESULT         hr;
    STGMEDIUM       stm;
    FORMATETC       fe;
    LPDATAOBJECT    pIDataObject=NULL;
    //CHAPTER13MOD
    const UINT      cFormats=7;
    static UINT     rgcf[7]={0, 0, 0, CF_METAFILEPICT, CF_BITMAP, 0, 0};
    static DWORD    rgtm[7]={TYMED_HGLOBAL, TYMED_ISTORAGE, TYMED_HGLOBAL
        , TYMED_MFPICT, TYMED_GDI, TYMED_ISTREAM, TYMED_HGLOBAL};
    //End CHAPTER13MOD

    hr=CoCreateInstance(CLSID_DataTransferObject, NULL
        , CLSCTX_INPROC_SERVER, IID_IDataObject
        , (PPVOID)&pIDataObject);

    if (FAILED(hr))
        return NULL;

    rgcf[0]=m_cf;
    rgcf[1]=m_cfEmbedSource;
    rgcf[2]=m_cfObjectDescriptor;

//CHAPTER13MOD
    //Don't include link stuff for cutting (0 format always fails)
    if (!fCut)
        {
        rgcf[5]=m_cfLinkSource;
        rgcf[6]=m_cfLinkSrcDescriptor;
        }
    //End CHAPTER13MOD

    for (i=0; i < cFormats; i++)
        {
        /*
         * RenderFormat handles memory handles, but for compound doc
         * formats we need something more.  So if RenderFormat fails
         * (which it will for i=1, try our latest addition which
         * writes to a different field in the STGMEDIUM.
         */
        stm.hGlobal=RenderFormat(rgcf[i]);

        if (NULL==stm.hGlobal)
            {
            if (!FRenderMedium(rgcf[i], &stm))
                continue;
            }

        stm.tymed=rgtm[i];
        stm.pUnkForRelease=NULL;
        SETDefFormatEtc(fe, rgcf[i], rgtm[i]);
        pIDataObject->SetData(&fe, &stm, TRUE);
        }

    return pIDataObject;    //Caller now responsible
    }

/*
 * CCosmoDoc::DropSelectTargetWindow
 * (Protected)
 *
 * Purpose:
 *  Creates a thin inverted frame around a window that we use to
 *  show the window as a drop target.  This is a toggle function:
 *  It uses XOR to create the effect so it must be called twice to
 *  leave the window as it was.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::DropSelectTargetWindow(void)
    {
    HDC         hDC;
    RECT        rc;
    UINT        dd=3;
    HWND        hWnd;

    hWnd=m_pPL->Window();
    hDC=GetWindowDC(hWnd);
    GetClientRect(hWnd, &rc);

    //Frame this window with inverted pixels

    //Top
    PatBlt(hDC, rc.left, rc.top, rc.right-rc.left, dd, DSTINVERT);

    //Bottom
    PatBlt(hDC, rc.left, rc.bottom-dd, rc.right-rc.left, dd
        , DSTINVERT);

    //Left excluding regions already affected by top and bottom
    PatBlt(hDC, rc.left, rc.top+dd, dd, rc.bottom-rc.top-(2*dd)
        , DSTINVERT);

    //Right excluding regions already affected by top and bottom
    PatBlt(hDC, rc.right-dd, rc.top+dd, dd, rc.bottom-rc.top-(2*dd)
        , DSTINVERT);

    ReleaseDC(hWnd, hDC);
    return;
    }

/*
 * CCosmoDoc::ColorSet
 *
 * Purpose:
 *  Changes a color used in our contained Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color.
 *
 * Return Value:
 *  COLORREF        Previous color for the given index.
 */

COLORREF CCosmoDoc::ColorSet(UINT iColor, COLORREF cr)
    {
    return m_pPL->ColorSet(iColor, cr);
    }

/*
 * CCosmoDoc::ColorGet
 *
 * Purpose:
 *  Retrieves a color currently in use in the Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to retrieve.
 *
 * Return Value:
 *  COLORREF        Current color for the given index.
 */

COLORREF CCosmoDoc::ColorGet(UINT iColor)
    {
    return m_pPL->ColorGet(iColor);
    }

/*
 * CCosmoDoc::LineStyleSet
 *
 * Purpose:
 *  Changes the line style currently used in the Polyline
 *
 * Parameters:
 *  iStyle          UINT index of the new line style to use.
 *
 * Return Value:
 *  UINT            Previous line style.
 */

UINT CCosmoDoc::LineStyleSet(UINT iStyle)
    {
    return m_pPL->LineStyleSet(iStyle);
    }

/*
 * CCosmoDoc::LineStyleGet
 *
 * Purpose:
 *  Retrieves the line style currently used in the Polyline
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Current line style.
 */

UINT CCosmoDoc::LineStyleGet(void)
    {
    return m_pPL->LineStyleGet();
    }

/*
 * CPolylineAdviseSink::CPolylineAdviseSink
 * CPolylineAdviseSink::~CPolylineAdviseSink
 *
 * Constructor Parameters:
 *  pv              LPVOID to store in this object
 */

CPolylineAdviseSink::CPolylineAdviseSink(LPVOID pv)
    {
    m_pv=pv;
    return;
    }

CPolylineAdviseSink::~CPolylineAdviseSink(void)
    {
    return;
    }

/*
 * CPolylineAdviseSink::OnPointChange
 *
 * Purpose:
 *  Informs the document that the polyline added or removed a point.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnPointChange(void)
    {
    PCDocument      pDoc=(PCDocument)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnSizeChange
 *
 * Purpose:
 *  Informs the document that the polyline changed size.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnSizeChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;
    RECT            rc;
    DWORD           dwStyle;

    /*
     * Polyline window is informing us that it changed size in
     * response to setting it's data.  Therefore we have to
     * size ourselves accordingly but without moving the screen
     * position of the polyline window.
     */

    pDoc->m_fNoSize=TRUE;

    //Set the document window size.
    GetWindowRect(pDoc->m_pPL->Window(), &rc);
    InflateRect(&rc, 8, 8);

    //Adjust for a window sans menu
    dwStyle=GetWindowLong(pDoc->m_hWnd, GWL_STYLE);
    AdjustWindowRect(&rc, dwStyle, FALSE);

    SetWindowPos(pDoc->m_hWnd, NULL, 0, 0, rc.right-rc.left
        , rc.bottom-rc.top, SWP_NOMOVE | SWP_NOZORDER);

    if (NULL!=pDoc->m_pAdv)
        pDoc->m_pAdv->OnSizeChange(pDoc, &rc);

    pDoc->m_fNoSize=FALSE;
    pDoc->FDirtySet(TRUE);

    return;
    }

/*
 * CPolylineAdviseSink::OnDataChange
 *
 * Purpose:
 *  Informs the document that the polyline data changed.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnDataChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    if (NULL!=pDoc->m_pAdv)
        pDoc->m_pAdv->OnDataChange(pDoc);

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnColorChange
 *
 * Purpose:
 *  Informs the document that the polyline data changed a color.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnColorChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnLineStyleChange
 *
 * Purpose:
 *  Informs the document that the polyline changed its line style.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnLineStyleChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }


DROPSRC.CPP

/*
 * DROPSRC.CPP
 * Cosmo Chapter 13
 *
 * Implementation of a DropSource object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CDropSource::CDropSource
 * CDropSource::~CDropSource
 *
 * Constructor Parameters:
 *  pDoc            PCosmoDoc containing this interface.
 */

CDropSource::CDropSource(PCCosmoDoc pDoc)
    {
    m_cRef=0;
    m_pDoc=pDoc;
    return;
    }

CDropSource::~CDropSource(void)
    {
    return;
    }

/*
 * CDropSource::QueryInterface
 * CDropSource::AddRef
 * CDropSource::Release
 *
 * Purpose:
 *  IUnknown members for CDropSource object.
 */

STDMETHODIMP CDropSource::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDropSource==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDropSource::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropSource::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDropSource::QueryDragContinue
 *
 * Purpose:
 *  Determines whether to continue a drag operation or cancel it.
 *
 * Parameters:
 *  fEsc            BOOL indicating that the ESC key was pressed.
 *  grfKeyState     DWORD providing states of keys and mouse buttons
 *
 * Return Value:
 *  HRESULT         DRAGDROP_S_CANCEL to stop the drag,
 *                  DRAGDROP_S_DROP to drop the data where it is,
 *                  or NOERROR to continue.
 */

STDMETHODIMP CDropSource::QueryContinueDrag(BOOL fEsc
    , DWORD grfKeyState)
    {
    if (fEsc)
        return ResultFromScode(DRAGDROP_S_CANCEL);

    if (!(grfKeyState & MK_LBUTTON))
        return ResultFromScode(DRAGDROP_S_DROP);

    return NOERROR;
    }

/*
 * CDropSource::GiveFeedback
 *
 * Purpose:
 *  Provides cursor feedback to the user since the source task
 *  always has the mouse capture.  We can also provide any other
 *  type of feedback above cursors if we so desire.
 *
 * Parameters:
 *  dwEffect        DWORD effect flags returned from the last target
 *
 * Return Value:
 *  HRESULT         NOERROR if you set a cursor yourself or
 *                  DRAGDROP_S_USEDEFAULTCURSORS to let OLE do
 *                  the work.
 */

STDMETHODIMP CDropSource::GiveFeedback(DWORD dwEffect)
    {
    return ResultFromScode(DRAGDROP_S_USEDEFAULTCURSORS);
    }


DROPTGT.CPP

/*
 * DROPTGT.CPP
 * Cosmo Chapter 13
 *
 * Implementation of the IDropTarget interface.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CDropTarget::CDropTarget
 * CDropTarget::~CDropTarget
 *
 * Constructor Parameters:
 *  pDoc            PCCosmoDoc of the document containing us.
 */

CDropTarget::CDropTarget(PCCosmoDoc pDoc)
    {
    m_cRef=0;
    m_pDoc=pDoc;

    m_pIDataObject=NULL;
    return;
    }

CDropTarget::~CDropTarget(void)
    {
    return;
    }

/*
 * CDropTarget::QueryInterface
 * CDropTarget::AddRef
 * CDropTarget::Release
 *
 * Purpose:
 *  IUnknown members for CDropTarget object.
 */

STDMETHODIMP CDropTarget::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDropTarget==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDropTarget::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropTarget::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDropTarget::DragEnter
 *
 * Purpose:
 *  Indicates that data in a drag operation has been dragged over
 *  our window that's a potential target.  We are to decide if it's
 *  something we're interested in or not.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT providing the source data.
 *  grfKeyState     DWORD flags: states of keys and mouse buttons.
 *  pt              POINTL coordinates in the document client space.
 *  pdwEffect       LPDWORD into which we'll place the
 *                  appropriate effect flag for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragEnter(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    HWND        hWnd;

    /*
     * 1.  Check if we can paste from the pIDataSource we're
     *     provided. We already made this nice useful function
     *     to test.
     */

    m_pIDataObject=NULL;

    if (!m_pDoc->FQueryPasteFromData(pIDataSource))
        {
        *pdwEffect=DROPEFFECT_NONE;
        return NOERROR;
        }

    /*
     * 2.  We can always drop anywhere in our document,
     *     so pt is uninteresting.
     */

    /*
     * 3. We return either a COPY or MOVE effect flag, depending on
     *    the state of the grfKeyState flags.  We MOVE on no key or
     *    Shift key, COPY on Ctrl key.
     */

    //Default is move
    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        *pdwEffect=DROPEFFECT_COPY;

    /*
     * 4. We really don't need to keep the IDataObject around since
     *    we're not interested in it in DragOver.  However, we'll
     *    save it just for demonstration.
     */
    m_pIDataObject=pIDataSource;
    m_pIDataObject->AddRef();

    /*
     * 5.  We always accept drops of our data on us, so we only need
     *     to provide some UI feedback here which we do by inverting
     *     the edge of the polyline window in this document.  We'll
     *     remove this in DragLeave and in Drop.  DragOver won't
     *     effect it since we can always drop.
     *
     *     Since we're inverting a border, insure that this window is
     *     on top.
     */

    hWnd=m_pDoc->Window();
    BringWindowToTop(hWnd);
    UpdateWindow(hWnd);
    m_pDoc->DropSelectTargetWindow();

    return NOERROR;
    }

/*
 * CDropTarget::DragOver
 *
 * Purpose:
 *  Indicates that the mouse was moved inside the window represented
 *  by this drop target. This happens on every WM_MOUSEMOVE, so this
 *  function should be very efficient.
 *
 * Parameters:
 *  grfKeyState     DWORD providing current keyboard/mouse states
 *  pt              POINTL where the mouse currently is.
 *  pdwEffect       LPDWORD to store the effect flag for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragOver(DWORD grfKeyState, POINTL pt
    , LPDWORD pdwEffect)
    {
    if (NULL==m_pIDataObject)
        {
        *pdwEffect=DROPEFFECT_NONE;
        return NOERROR;
        }

    //We can always drop; return effect flags based on keys.
    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        *pdwEffect=DROPEFFECT_COPY;

    return NOERROR;
    }

/*
 * CDropTarget::DragLeave
 *
 * Purpose:
 *  Informs the drop target that the operation has left its window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragLeave(void)
    {
    //1.  Remove the UI feedback
    m_pDoc->DropSelectTargetWindow();

    //2.  Release the held IDataObject
    ReleaseInterface(m_pIDataObject);

    return NOERROR;
    }

/*
 * CDropTarget::Drop
 *
 * Purpose:
 *  Instructs the drop target to paste data that was just now
 *  dropped on it.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT from which we'll paste.
 *  grfKeyState     DWORD providing current keyboard/mouse state.
 *  pt              POINTL at which the drop occurred.
 *  pdwEffect       LPDWORD to store what you do with the data.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::Drop(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    BOOL        fRet=TRUE;

    *pdwEffect=DROPEFFECT_NONE;

    if (NULL==m_pIDataObject)
        return ResultFromScode(E_FAIL);

    //1.  Remove the UI feedback, release pointer
    DragLeave();

    //No point in drag-drop to ourselves (for Cosmo, at least)
    if (m_pDoc->m_fDragSource)
        return ResultFromScode(E_FAIL);

    //2.  Try a paste
    fRet=m_pDoc->PasteFromData(pIDataSource);

    //3.  Store the effect
    if (!fRet)
        return ResultFromScode(E_FAIL);

    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        *pdwEffect=DROPEFFECT_COPY;

    return NOERROR;
    }


POLYLINE.CPP

/*
 * POLYLINE.CPP
 * Cosmo Chapter 13
 *
 * Implementation of the CPolyline class.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CPolyline:CPolyline
 * CPolyline::~CPolyline
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application we're in.
 */

CPolyline::CPolyline(HINSTANCE hInst)
    : CWindow(hInst)
    {
    m_pAdv=NULL;
    m_hWnd=NULL;
    return;
    }

CPolyline::~CPolyline(void)
    {
    return;
    }

/*
 * CPolyline::FInit
 *
 * Purpose:
 *  Instantiates a polyline window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags
 *  uID             UINT ID to associate with this window
 *  pAdv            PCPolylineAdviseSink of the sink wanting our
 *                  notifications.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPolyline::FInit(HWND hWndParent, LPRECT pRect, DWORD dwStyle
    , UINT uID, PCPolylineAdviseSink pAdv)
    {
    m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY, SZCLASSPOLYLINE
        , SZCLASSPOLYLINE, dwStyle, pRect->left, pRect->top
        , pRect->right-pRect->left, pRect->bottom-pRect->top
        , hWndParent, (HMENU)uID, m_hInst, this);

    if (NULL!=m_hWnd)
        m_pAdv=pAdv;

    return (NULL!=m_hWnd);
    }

/*
 * CPolyline::New
 *
 * Purpose:
 *  Cleans out and reinitializes the data to defaults.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolyline::New(void)
    {
    UINT        i;
    RECT        rc;

    m_pl.wVerMaj=VERSIONMAJOR;
    m_pl.wVerMin=VERSIONMINOR;

    //Our rectangle is the size of our window's client area.
    GetClientRect(m_hWnd, &rc);
    RECTTORECTS(rc, m_pl.rc);

    //Clean out the POLYLINE structure and repaint the window.
    for (i=0; i< CPOLYLINEPOINTS; i++)
        {
        m_pl.rgpt[i].x=0;
        m_pl.rgpt[i].y=0;
        }

    m_pl.cPoints      =0;
    m_pl.rgbBackground=GetSysColor(COLOR_WINDOW);
    m_pl.rgbLine      =GetSysColor(COLOR_WINDOWTEXT);
    m_pl.iLineStyle   =PS_SOLID;

    InvalidateRect(m_hWnd, NULL, TRUE);
    UpdateWindow(m_hWnd);

    //Inform the advise sink of this data change.
    if (NULL!=m_pAdv)
        m_pAdv->OnDataChange();

    return;
    }

/*
 * CPolyline::Undo
 *
 * Purpose:
 *  Reverses previous actions in a Polyline.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if we can Undo more, FALSE otherwise.
 */

BOOL CPolyline::Undo(void)
    {
    //Decrement the number of active points and repaint.
    if (m_pl.cPoints > 0)
        {
        m_pl.cPoints--;
        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    if (NULL!=m_pAdv)
        m_pAdv->OnPointChange();

    //Return if we can undo any more.
    return (0!=m_pl.cPoints);
    }

/*
 * CPolyline::ReadFromStorage
 *
 * Purpose:
 *  Loads our data (any known version) from an IStorage returning
 *  the version number of the data or an error value.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE from which we'll read.
 *
 * Return Value:
 *  LONG            Version number or negative POLYLINE_E_* value.
 */

LONG CPolyline::ReadFromStorage(LPSTORAGE pIStorage)
    {
    LONG            lRet;
    LPSTREAM        pIStream;
    HRESULT         hr;

    if (NULL==pIStorage)
        return POLYLINE_E_READFAILURE;

    //Open the CONTENTS stream
    hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT | STGM_READ
        | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return POLYLINE_E_READFAILURE;

    /*
     * Since we had to create ReadFromStream for IPersistStorage,
     * use it ourselves.
     */
    lRet=ReadFromStream(pIStream);
    pIStream->Release();

    return lRet;
    }

/*
 * CPolyline::WriteToStorage
 *
 * Purpose:
 *  Ignorantly writes our current data into a storage in a
 *  particular version.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which to save.
 *  lVer            LONG providing version number Major (HI) and
 *                  Minor (Low)
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::WriteToStorage(LPSTORAGE pIStorage, LONG lVer)
    {
    LONG            lRet;
    LPSTREAM        pIStream;
    HRESULT         hr;

    if (NULL==pIStorage)
        return POLYLINE_E_READFAILURE;

    hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT | STGM_CREATE
        | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pIStream);

    if (FAILED(hr))
        return POLYLINE_E_WRITEFAILURE;

    /*
     * Use our function that will save to a stream now that
     * we needed it for IPersistStorage.
     */

    lRet=WriteToStream(pIStream, lVer);
    pIStream->Release();

    return lRet;
    }

/*
 * CPolyline::ReadFromStream
 *
 * Purpose:
 *  Loads our data (any known version) from an IStream that we
 *  do not open nor maintain.  This is for the compound document
 *  object's implementation of IPersistStorage.
 *
 * Parameters:
 *  pIStream        LPSTREAM from which we'll read.
 *
 * Return Value:
 *  LONG            Version number or negative POLYLINE_E_* value.
 */

LONG CPolyline::ReadFromStream(LPSTREAM pIStream)
    {
    POLYLINEDATA    pl;
    ULONG           cb=(ULONG)-1;
    ULONG           cbExpect=0;
    HRESULT         hr;
    LARGE_INTEGER   li;

    if (NULL==pIStream)
        return POLYLINE_E_READFAILURE;

    //Read version numbers and seek back to file beginning.
    hr=pIStream->Read(&pl, 2*sizeof(WORD), &cb);

    LISet32(li, 0);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);

    if (FAILED(hr) || 2*sizeof(WORD)!=cb)
        {
        pIStream->Release();
        return POLYLINE_E_READFAILURE;
        }

    /*
     * For version 2.0, read the entire file.  For version 1.0 read
     * the file up to CBPOLYLINEDATAVER10.  For anything else, give
     * an error.
     */

    switch (pl.wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (pl.wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (pl.wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    hr=pIStream->Read(&pl, cbExpect, &cb);

    if (cbExpect!=cb)
        return POLYLINE_E_READFAILURE;

    /*
     * If we loaded successfully, make the data current.  By using
     * DataSet we centralize our version upgrading.  We size the
     * polyline window to the data AND notify the document so it
     * sizes to the polyline.
     */
    DataSet(&pl, TRUE, TRUE);

    //Return what version we just loaded.
    return MAKELONG(pl.wVerMin, pl.wVerMaj);
    }

/*
 * CPolyline::WriteToStream
 *
 * Purpose:
 *  Ignorantly writes our current data into a stream in a
 *  particular version.
 *
 * Parameters:
 *  pIStream        LPSTREAM in which to save.
 *  lVer            LONG providing version number Major (HI) and
 *                  Minor (Low)
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::WriteToStream(LPSTREAM pIStream, LONG lVer)
    {
    ULONG           cb;
    ULONG           cbExpect=0;
    WORD            wVerMaj=HIWORD(lVer);
    WORD            wVerMin=LOWORD(lVer);
    POLYLINEDATA    pl;
    HRESULT         hr;

    if (NULL==pIStream)
        return POLYLINE_E_READFAILURE;

    //Get a copy of our data in the version we're going to save.
    DataGet(&pl, lVer);

    switch (wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    hr=pIStream->Write(&pl, cbExpect, &cb);

    if (FAILED(hr) || cbExpect!=cb)
        return POLYLINE_E_WRITEFAILURE;

    return POLYLINE_E_NONE;
    }

/*
 * CPolyline::ReadFromFile
 *
 * Purpose:
 *  Loads our data (any known version) from a file handle returning
 *  the version number of the data or an error value.
 *
 * Parameters:
 *  pszFile         LPTSTR of the file to open.
 *
 * Return Value:
 *  LONG            Version number or negative POLYLINE_E_* value.
 */

LONG CPolyline::ReadFromFile(LPTSTR pszFile)
    {
    OFSTRUCT        of;
    HFILE           hFile;
    POLYLINEDATA    pl;
    UINT            cb=(UINT)-1;
    UINT            cbExpect=0;

    if (NULL==pszFile)
        return POLYLINE_E_READFAILURE;

    //OpenFileW is a member of CPolyline.
    hFile=OpenFileW(pszFile, &of, OF_READ);

    if (HFILE_ERROR==hFile)
        return POLYLINE_E_READFAILURE;

    //Read version numbers and seek back to file beginning.
    cb=_lread(hFile, &pl, 2*sizeof(WORD));
    _llseek(hFile, 0L, 0);

    if (2*sizeof(WORD)!=cb)
        {
        _lclose(hFile);
        return POLYLINE_E_READFAILURE;
        }

    /*
     * For version 2.0, read the entire file.  For version 1.0 read
     * the file up to CBPOLYLINEDATAVER10.  For anything else, give
     * an error.
     */

    switch (pl.wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (pl.wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (pl.wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        {
        _lclose(hFile);
        return POLYLINE_E_UNSUPPORTEDVERSION;
        }

    cb=_lread(hFile, &pl, cbExpect);
    _lclose(hFile);

    if (cbExpect!=cb)
        return POLYLINE_E_READFAILURE;

    /*
     * If we loaded successfully, make the data current.  By using
     * DataSet we centralize our version upgrading.  We size the
     * polyline window to the data AND notify the document so it
     * sizes to the polyline.
     */
    DataSet(&pl, TRUE, TRUE);

    //Return what version we just loaded.
    return MAKELONG(pl.wVerMin, pl.wVerMaj);
    }

/*
 * CPolyline::WriteToFile
 *
 * Purpose:
 *  Ignorantly writes our current data into an opened file in a
 *  particular version.
 *
 * Parameters:
 *  pszFile         LPTSTR filename in which to store the data.
 *  lVer            LONG providing version number Major (HI)
 *                  and Minor (Low)
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::WriteToFile(LPTSTR pszFile, LONG lVer)
    {
    OFSTRUCT        of;
    HFILE           hFile;
    UINT            cb;
    UINT            cbExpect=0;
    WORD            wVerMaj=HIWORD(lVer);
    WORD            wVerMin=LOWORD(lVer);
    POLYLINEDATA    pl;

    if (NULL==pszFile)
        return POLYLINE_E_READFAILURE;

    //Get a copy of our data in the version we're going to save.
    DataGet(&pl, lVer);

    switch (wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    hFile=OpenFileW(pszFile, &of, OF_CREATE | OF_WRITE);

    if (HFILE_ERROR==hFile)
        return DOCERR_COULDNOTOPEN;

    cb=_lwrite(hFile, (LPCSTR)&pl, cbExpect);
    _lclose(hFile);

    return (cbExpect==cb) ? POLYLINE_E_NONE
        : POLYLINE_E_WRITEFAILURE;
    }

/*
 * CPolyline::OpenFileW (Private)
 *
 * Purpose:
 *  Under Win32, OpenFile does not take Unicode strings.  This
 *  function converts a Unicode string into an ANSI string and
 *  calls OpenFile.  This just maps to OpenFile without Unicode.
 *
 * Parameters, Return Value:
 *  Same as OpenFile.
 */

HFILE CPolyline::OpenFileW(LPTSTR pszFile, LPOFSTRUCT pof
    , UINT uFlags)
    {
   #ifdef UNICODE
    CHAR        szTemp[CCHPATHMAX];

    UNICODETOANSI(pszFile, szTemp, CCHPATHMAX);
    return OpenFile(szTemp, pof, uFlags);
   #else
    return OpenFile(pszFile, pof, uFlags);
   #endif
    }

/*
 * CPolyline::DataSet
 *
 * Purpose:
 *  Sets the current data in this Polyline to a given structure.
 *
 * Parameters:
 *  ppl             PPOLYLINEDATA to initialize to.
 *  fSizeToData     BOOL indicating if we're to size to the data or
 *                  scale it.
 *  fNotify         BOOL indicating if we're to send an advise on
 *                  this change.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataSet(PPOLYLINEDATA ppl, BOOL fSizeToData
    , BOOL fNotify)
    {
    RECTS       rcs;
    RECT        rc;
    UINT        i;

    /*
     * Copy the structure in ppl and repaint to reflect the new
     * point set.  Note that unlike the RectSet message, we do no
     * scaling, assuming that the rect in the structure is
     * appropriate for the data.
     */

    if (NULL==ppl)
        return POLYLINE_E_INVALIDPOINTER;

    //Preserve the old rectangle
    rcs=m_pl.rc;

    /*
     * For version 2.0 we perform a straight copy.  For version
     * 1.0 we copy the 1.0 structure and fill in defaults for
     * the 2.0 additions.
     */

    switch (ppl->wVerMaj)
        {
        case VERSIONMAJOR:          //2.x
            switch (ppl->wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    m_pl=*ppl;
                    break;

                default:
                    return POLYLINE_E_UNSUPPORTEDVERSION;
                }
            break;

        case 1:                     //1.x
            switch (ppl->wVerMin)
                {
                case 0:             //1.0
                    *((PPOLYLINEDATA10)&m_pl)=
                        *((PPOLYLINEDATA10)ppl);

                    /*
                     * Update this structure to 2.0.  Note that we
                     * assume whoever loaded us to save the loaded
                     * version so it can later ask what version
                     * the user wants to save.
                     */
                    m_pl.wVerMaj=VERSIONMAJOR;
                    m_pl.wVerMin=VERSIONMINOR;

                    /*
                     * Version 1.0 stored rc in parent coordinates.
                     * We need those now in our client coodinates.
                     */
                    RECTSTORECT(m_pl.rc, rc);
                    OffsetRect(&rc, -m_pl.rc.left, -m_pl.rc.top);

                    /*
                     * 1.0 data had points relative to size of the
                     * rectangle.  We need to scale these to 0-32767
                     * independent of the rectangle for the version
                     * upgrade.
                     */
                    for (i=0; i < m_pl.cPoints; i++)
                        PointScale(&rc, &m_pl.rgpt[i], FALSE);

                    RECTTORECTS(rc, m_pl.rc);

                    //New 2.0 features
                    m_pl.rgbBackground=GetSysColor(COLOR_WINDOW);
                    m_pl.rgbLine=GetSysColor(COLOR_WINDOWTEXT);
                    m_pl.iLineStyle=PS_SOLID;
                    break;

                default:
                    return POLYLINE_E_UNSUPPORTEDVERSION;
                }
            break;

        default:
            return POLYLINE_E_UNSUPPORTEDVERSION;
        }

    //Inform our parent of the data change
    if (NULL!=m_pAdv)
        m_pAdv->OnDataChange();

    /*
     * If we're scaling the window to fit the data, then use
     * RectSet passing our current rectangle as the new one.
     * That makes sure that the data won't change but that the
     * window is resized.
     */

    if (fSizeToData)
        {
        POINT       pt;

        /*
         * Get our offset in the parent window so we can RectSet
         * to the right place since RectSet expects rectangle in
         * parent coordinates and we get it in client coordinates.
         */
        GetWindowRect(m_hWnd, &rc);
        pt.x=rc.left;
        pt.y=rc.top;
        ScreenToClient(GetParent(m_hWnd), &pt);
        RECTSTORECT(m_pl.rc, rc);
        OffsetRect(&rc, pt.x, pt.y);

        //This will also cause a repaint.
        RectSet(&rc, fNotify);
        }
    else
        {
        //Make sure we're updated.
        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    return POLYLINE_E_NONE;
    }

/*
 * CPolyline::DataGet
 *
 * Purpose:
 *  Retrieves the Polyline's current data.
 *
 * Parameters:
 *  ppl             PPOLYLINEDATA into which we copy the data.
 *  lVer            LONG version of the data to retrieve.  Use
 *                  VERSIONCURRENT to retrieve the most current.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value
 */

LONG CPolyline::DataGet(PPOLYLINEDATA ppl, LONG lVer)
    {
    UINT        i;
    RECT        rc;

    //Retieve the current version
    if (lVer==MAKELONG(VERSIONMINOR, VERSIONMAJOR)
        || VERSIONCURRENT==lVer)
        {
        *ppl=m_pl;
        return POLYLINE_E_NONE;
        }

    //Check for versions we support, 1.x
    if (HIWORD(lVer)!=1)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    if (LOWORD(lVer)==0)    //Check for 1.0
        {
        //Do 2.0 to 1.0 conversion
        *((PPOLYLINEDATA10)ppl)=*((PPOLYLINEDATA10)&m_pl);

        RECTSTORECT(ppl->rc, rc);

        //Get the parent coordinates of our rectangle
        if (!IsWindow(m_hWnd))
            OffsetRect(&rc, 8, 8); //This is always the offset.
        else
            RectGet(&rc);

        /*
         * 1.0 data has points relative to size of the rectangle.
         * We need to scale these from 0-32767 so we have the
         * right values.
         */
        for (i=0; i < ppl->cPoints; i++)
            PointScale(&rc, &ppl->rgpt[i], TRUE);

        RECTTORECTS(rc, ppl->rc);

        //Insure old version numbers.
        ppl->wVerMaj=1;
        ppl->wVerMin=0;

        ((PPOLYLINEDATA10)ppl)->fDrawEntire=TRUE;
        return POLYLINE_E_NONE;
        }

    return POLYLINE_E_UNSUPPORTEDVERSION;
    }

/*
 * CPolyline::DataSetMem
 *
 * Purpose:
 *  Sets the Polyline's data using a global memory handle
 *  instead of a pointer.
 *
 * Parameters:
 *  hMem            HGLOBAL containing the data.
 *  fFree           BOOL indicating if we're to free the data.
 *                  The memory will be freed regardless of any
 *                  error returned from here.
 *  fSizeToData     BOOL indicating if we're to size to the data
 *                  or scale it.
 *  fNotify         BOOL indicating if we're to send an advise
 *                  on this change.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataSetMem(HGLOBAL hMem, BOOL fFree
    , BOOL fSizeToData, BOOL fNotify)
    {
    PPOLYLINEDATA   ppl;
    LONG            lRet=POLYLINE_E_INVALIDPOINTER;

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        lRet=DataSet(ppl, fSizeToData, fNotify);

        GlobalUnlock(hMem);

        if (fFree)
            GlobalFree(hMem);
        }

    return lRet;
    }

/*
 * CPolyline::DataGetMem
 *
 * Purpose:
 *  Retrieves the Polyline's data in a global memory handle.
 *
 * Parameters:
 *  lVer            LONG version of data to retrieve.
 *  phMem           HGLOBAL * in which to store the handle.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataGetMem(LONG lVer, HGLOBAL *phMem)
    {
    HGLOBAL         hMem;
    PPOLYLINEDATA   ppl;
    LONG            lRet;

    if (NULL==phMem)
        return POLYLINE_E_INVALIDPOINTER;

    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
        , CBPOLYLINEDATA);

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        lRet=DataGet(ppl, lVer);
        GlobalUnlock(hMem);

        if (POLYLINE_E_NONE!=lRet)
            {
            GlobalFree(hMem);
            hMem=NULL;
            }
        }

    *phMem=hMem;
    return lRet;
    }

/*
 * CPolyline::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectGet(LPRECT pRect)
    {
    RECT        rc;
    POINT       pt;

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_hWnd, &rc);
    pt.x=rc.left;
    pt.y=rc.top;
    ScreenToClient(GetParent(m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return;
    }

/*
 * CPolyline::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  None
 */

void CPolyline::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return;
    }

/*
 * CPolyline::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;
    RECT        rc;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, pRect->left, pRect->top
        , cx, cy, SWP_NOZORDER);

    SetRect(&rc, 0, 0, cx, cy);
    RECTTORECTS(rc, m_pl.rc);

    if (fNotify && NULL!=m_pAdv)
        m_pAdv->OnSizeChange();

    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }

/*
 * CPolyline::SizeSet
 *
 * Purpose:
 *  Sets a new size for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPolyline::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
        , SWP_NOMOVE | SWP_NOZORDER);

    if (fNotify && NULL!=m_pAdv)
        m_pAdv->OnSizeChange();

    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }

/*
 * CPolyline::ColorSet
 *
 * Purpose:
 *  Changes for background or line color in the Polyline
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color to use.
 *
 * Return Value:
 *  COLORREF        Previous color for the index iColor.
 */

COLORREF CPolyline::ColorSet(UINT iColor, COLORREF cr)
    {
    COLORREF    crRet;

    switch (iColor)
        {
        case POLYLINECOLOR_BACKGROUND:
            crRet=m_pl.rgbBackground;
            m_pl.rgbBackground=cr;
            break;

        case POLYLINECOLOR_LINE:
            crRet=m_pl.rgbLine;
            m_pl.rgbLine=cr;
            break;
        }

    //If the color changed, repaint
    if (crRet!=cr)
        {
        if (NULL!=m_pAdv)
            m_pAdv->OnColorChange();

        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    return crRet;
    }

/*
 * CPolyline::ColorGet
 *
 * Purpose:
 *  Retrieves one of the colors currently in use by the Polyline.
 *
 * Parameters:
 *  iColor          UINT identifying the color of interest.
 *
 * Return Value:
 *  COLORREF        Current color for iColor.
 */

COLORREF CPolyline::ColorGet(UINT iColor)
    {
    COLORREF    crRet;

    crRet=(POLYLINECOLOR_BACKGROUND==iColor)
        ? m_pl.rgbBackground : m_pl.rgbLine;

    return crRet;
    }

/*
 * CPolyline::LineStyleSet
 *
 * Purpose:
 *  Changes the line style in use by the Polyline
 *
 * Parameters:
 *  iStyle          UINT style of the line to use.
 *
 * Return Value:
 *  UINT            Previous style.
 */

UINT CPolyline::LineStyleSet(UINT iStyle)
    {
    UINT        uRet=(UINT)m_pl.iLineStyle;

    //Validate the line style
    if (PS_SOLID==iStyle || PS_DASH==iStyle || PS_DOT==iStyle
        || PS_DASHDOT==iStyle || PS_DASHDOTDOT==iStyle)
        {
        m_pl.iLineStyle=iStyle;

        if (uRet!=(UINT)m_pl.iLineStyle)
            {
            if (NULL!=m_pAdv)
                m_pAdv->OnLineStyleChange();

            InvalidateRect(m_hWnd, NULL, TRUE);
            UpdateWindow(m_hWnd);
            }
        }

    return uRet;
    }

/*
 * CPolyline::LineStyleGet
 *
 * Purpose:
 *  Retrieves the current line style in use in the Polyline
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Current line style.
 */

UINT CPolyline::LineStyleGet(void)
    {
    return m_pl.iLineStyle;
    }

/*
 * CPolyline::RenderBitmap
 *
 * Purpose:

*  Creates a bitmap image of the current Polyline.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HBITMAP         Handle to the newly rendered bitmap.
 */

HBITMAP CPolyline::RenderBitmap(void)
    {
    HDC         hDC;
    HDC         hMemDC;
    HBITMAP     hBmp;
    RECT        rc;
    HGDIOBJ     hObj;

    //Render a bitmap the size of the current rectangle.
    hDC=GetDC(m_hWnd);
    hMemDC=CreateCompatibleDC(hDC);

    GetClientRect(m_hWnd, &rc);
    hBmp=CreateCompatibleBitmap(hDC, rc.right, rc.bottom);

    if (NULL!=hBmp)
        {
        //Draw the POLYLINEDATA into the bitmap.
        hObj=SelectObject(hMemDC, hBmp);
        Draw(hMemDC, FALSE, TRUE);
        SelectObject(hMemDC, hObj);
        }

    DeleteDC(hMemDC);
    ReleaseDC(m_hWnd, hDC);

    return hBmp;
    }

/*
 * CPolyline::RenderMetafile
 *
 * Purpose:
 *  Renders the current image of the Polyline into a metafile.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HMETAFILE       Handle to the newly created metafile.
 */

HMETAFILE CPolyline::RenderMetafile(void)
    {
    HDC         hDC;
    HMETAFILE   hMF;
    RECT        rc;

    //Create a memory metafile and return its handle.
    hDC=(HDC)CreateMetaFile(NULL);
    hMF=NULL;

    if (NULL!=hDC)
        {
        /*
         * This is absolutely essential to the metafile so it
         * can be scaled in the clipboard and any destination
         * application.
         */
        SetMapMode(hDC, MM_ANISOTROPIC);
        GetClientRect(m_hWnd, &rc);
        SetWindowOrgEx(hDC, 0, 0, NULL);
        SetWindowExtEx(hDC, rc.right, rc.bottom, NULL);

        Draw(hDC, TRUE, TRUE);
        hMF=CloseMetaFile(hDC);
        }

    return hMF;
    }

/*
 * CPolyline::RenderMetafilePict
 *
 * Purpose:
 *  Renders the current Polyline into a METAFILEPICT structure in
 *  global memory.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HGLOBAL         Memory containing the METAFILEPICT structure.
 */

HGLOBAL CPolyline::RenderMetafilePict(void)
    {
    HGLOBAL         hMem;
    HMETAFILE       hMF;
    LPMETAFILEPICT  pMF;
    RECT            rc;

    //Get the metafile
    hMF=RenderMetafile();

    if (NULL==hMF)
        return NULL;

    //Allocate the METAFILEPICT structure.
    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
        , sizeof(METAFILEPICT));

    if (NULL==hMem)
        {
        DeleteMetaFile(hMF);
        return NULL;
        }

    /*
     * Global lock only fails in PMODE if the selector is invalid
     * (like it was discarded) or references a 0 length segment,
     * neither of which can happen here.
     */
    pMF=(LPMETAFILEPICT)GlobalLock(hMem);

    pMF->hMF=hMF;
    pMF->mm=MM_ANISOTROPIC;

    //Insert the extents in MM_HIMETRIC units.
    GetClientRect(m_hWnd, &rc);
    RectConvertMappings(&rc, FALSE);
    pMF->xExt=rc.right;
    pMF->yExt=rc.bottom;

    GlobalUnlock(hMem);
    return hMem;
    }

/*
 * CPolyline::RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device (MM_TEXT) or
 *  HIMETRIC to the other.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  fToDevice       BOOL TRUE to convert from HIMETRIC to device,
 *                  FALSE to convert device to HIMETRIC.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectConvertMappings(LPRECT pRect, BOOL fToDevice)
    {
    HDC      hDC;
    int      iLpx, iLpy;

    if (NULL==pRect)
        return;

    hDC=GetDC(NULL);
    iLpx=GetDeviceCaps(hDC, LOGPIXELSX);
    iLpy=GetDeviceCaps(hDC, LOGPIXELSY);
    ReleaseDC(NULL, hDC);

    if (fToDevice)
        {
        pRect->left=MulDiv(iLpx, pRect->left, HIMETRIC_PER_INCH);
        pRect->top =MulDiv(iLpy, pRect->top , HIMETRIC_PER_INCH);

        pRect->right=MulDiv(iLpx, pRect->right,  HIMETRIC_PER_INCH);
        pRect->bottom=MulDiv(iLpy, pRect->bottom,HIMETRIC_PER_INCH);

       #ifdef NEVER
        /*
         * In this conversion we may get situations where the top
         * coordinate is larger than the bottom, which messes us up.
         */
        if (pRect->bottom < pRect->top)
            {
            iLpy=pRect->top;
            pRect->top=pRect->bottom;
            pRect->bottom=iLpy;
            }
       #endif
        }
    else
        {
        pRect->left=MulDiv(pRect->left, HIMETRIC_PER_INCH, iLpx);
        pRect->top =MulDiv(pRect->top , HIMETRIC_PER_INCH, iLpy);

        pRect->right =MulDiv(pRect->right, HIMETRIC_PER_INCH, iLpx);
        pRect->bottom=MulDiv(pRect->bottom,HIMETRIC_PER_INCH, iLpy);
        }

    return;
    }


POLYLINE.H

/*
 * POLYLINE.H
 * Cosmo Chapter 13
 *
 * Definitions and function prototypes for the PolyLine window
 * class that can be treated like its own control.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _POLYLINE_H_
#define _POLYLINE_H_

//Versioning.
#define VERSIONMAJOR                2
#define VERSIONMINOR                0
#define VERSIONCURRENT              0x00020000

//Classname
#define SZCLASSPOLYLINE             TEXT("polyline")

//Stream Name that holds the data
#define SZSTREAM                    OLESTR("CONTENTS")

#define HIMETRIC_PER_INCH           2540
#define CPOLYLINEPOINTS             20

//Window extra bytes and offsets
#define CBPOLYLINEWNDEXTRA          (sizeof(LONG))
#define PLWL_STRUCTURE              0

//Version 2.0 Polyline Structure
typedef struct tagPOLYLINEDATA
    {
    WORD        wVerMaj;                //Major version number.
    WORD        wVerMin;                //Minor version number.
    WORD        cPoints;                //Number of points.
    short       fReserved;              //Obsolete from v1.0
    RECTS       rc;                     //Rectangle of this figure
    POINTS      rgpt[CPOLYLINEPOINTS];  //Points (0-32767 grid)

    //Version 2.0 additions
    COLORREF    rgbBackground;          //Background color
    COLORREF    rgbLine;                //Line color
    short       iLineStyle;             //Line style
    } POLYLINEDATA, *PPOLYLINEDATA;

#define CBPOLYLINEDATA   sizeof(POLYLINEDATA)
#define CBPOLYLINEDATA20 sizeof(POLYLINEDATA)

//Version 1.0 Polyline Structure
typedef struct tagPOLYLINEDATA10
    {
    WORD        wVerMaj;                //Major version number.
    WORD        wVerMin;                //Minor version number.
    WORD        cPoints;                //Number of points.
    short       fDrawEntire;            //Flag to draw entire figure
    RECTS       rc;                     //Rectangle of this figure
    POINTS      rgpt[CPOLYLINEPOINTS];  //Points (scaled to rc)
    } POLYLINEDATA10, *PPOLYLINEDATA10;

#define CBPOLYLINEDATA10 sizeof(POLYLINEDATA10)

//POLYWIN.CPP
LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM, LPARAM);

class CPolyline : public CWindow
    {
    friend LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM
        , LPARAM);

    private:
        POLYLINEDATA   m_pl;

        class CPolylineAdviseSink * m_pAdv;

    private:
        HFILE     OpenFileW(LPTSTR, LPOFSTRUCT, UINT);
        void      PointScale(LPRECT, LPPOINTS, BOOL);
        void      Draw(HDC, BOOL, BOOL);
        void      RectConvertMappings(LPRECT, BOOL);

    public:
        CPolyline(HINSTANCE);
        ~CPolyline(void);

        BOOL      FInit(HWND, LPRECT, DWORD, UINT
            , class CPolylineAdviseSink *);

        void      New(void);
        BOOL      Undo(void);

        //File functions
        LONG      ReadFromStorage(LPSTORAGE);
        LONG      WriteToStorage(LPSTORAGE, LONG);

        LONG      ReadFromStream(LPSTREAM);
        LONG      WriteToStream(LPSTREAM, LONG);

        LONG      ReadFromFile(LPTSTR);
        LONG      WriteToFile(LPTSTR, LONG);

        //Data transfer functions
        LONG      DataSet(PPOLYLINEDATA, BOOL, BOOL);
        LONG      DataGet(PPOLYLINEDATA, LONG);
        LONG      DataSetMem(HGLOBAL, BOOL, BOOL, BOOL);
        LONG      DataGetMem(LONG, HGLOBAL *);
        HBITMAP   RenderBitmap(void);
        HMETAFILE RenderMetafile(void);
        HGLOBAL   RenderMetafilePict(void);

        void      RectGet(LPRECT);
        void      SizeGet(LPRECT);
        void      RectSet(LPRECT, BOOL);
        void      SizeSet(LPRECT, BOOL);
        COLORREF  ColorSet(UINT, COLORREF);
        COLORREF  ColorGet(UINT);
        UINT      LineStyleSet(UINT);
        UINT      LineStyleGet(void);
    };

typedef CPolyline *PCPolyline;

//Error values for data transfer functions
#define POLYLINE_E_NONE                    0
#define POLYLINE_E_UNSUPPORTEDVERSION      -1
#define POLYLINE_E_INVALIDPOINTER          -2
#define POLYLINE_E_READFAILURE             -3
#define POLYLINE_E_WRITEFAILURE            -4

class CPolylineAdviseSink
    {
    private:
        LPVOID      m_pv;           //Customizable structure

    public:
        CPolylineAdviseSink(LPVOID);
        ~CPolylineAdviseSink(void);

        void OnPointChange(void);
        void OnSizeChange(void);
        void OnDataChange(void);
        void OnColorChange(void);
        void OnLineStyleChange(void);
    };

typedef CPolylineAdviseSink *PCPolylineAdviseSink;

//Color indices for color messages
#define POLYLINECOLOR_BACKGROUND    0
#define POLYLINECOLOR_LINE          1

#endif  //_POLYLINE_H_


POLYWIN.CPP

/*
 * POLYWIN.CPP
 * Cosmo Chapter 13
 *
 * Window procedure for the polyline drawing window and support
 * functions.  This window is not complicated.  On creation it
 * allocates a block of memory for a POLYLINEDATA structure that
 * contains 20 POINTs.  We do not attempt to reallocate this
 * array at all just to maintain simplicity.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * PolylineWndProc
 *
 * Purpose:
 *  Window procedure for the polyline drawing window.
 */

LRESULT APIENTRY PolylineWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCPolyline      ppl;
    PAINTSTRUCT     ps;
    HDC             hDC;
    POINTS          pt;
    RECT            rc;

    ppl=(PCPolyline)GetWindowLong(hWnd, PLWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppl=(PCPolyline)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PLWL_STRUCTURE, (LONG)ppl);
            ppl->m_hWnd=hWnd;
            ppl->New();
            break;

        case WM_PAINT:
            hDC=BeginPaint(hWnd, &ps);
            ppl->Draw(hDC, FALSE, TRUE);
            EndPaint(hWnd, &ps);
            break;

        case WM_LBUTTONDOWN:
            //Stop if we are already at the limit.
            if (CPOLYLINEPOINTS==ppl->m_pl.cPoints)
                {
                MessageBeep(0);
                break;
                }

            //Convert the points into 0-32767 range
            GetClientRect(hWnd, &rc);
            pt=MAKEPOINTS(lParam);
            ppl->PointScale(&rc, &pt, FALSE);

            ppl->m_pl.rgpt[ppl->m_pl.cPoints++]=pt;

            //Draw the lines to this new point only.
            hDC=GetDC(hWnd);
            ppl->Draw(hDC, FALSE, FALSE);
            ReleaseDC(hWnd, hDC);

            if (NULL!=ppl->m_pAdv)
                ppl->m_pAdv->OnPointChange();

            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPolyline::Draw
 *
 * Purpose:
 *  Paints the current line in the polyline window.
 *
 * Parameters:
 *  hDC             HDC to draw on, could be metafile or printer DC.
 *  fMetafile       BOOL indicating if hDC is a metafile or not, so
 *                  we can avoid operations that RIP.
 *  fEntire         BOOL indicating if we should draw the entire
 *                  figure or not.
 *
 * Return Value:
 *  None
 */

void CPolyline::Draw(HDC hDC, BOOL fMetafile, BOOL fEntire)
    {
    HBRUSH          hBrush;
    HPEN            hPen;
    HGDIOBJ         hObj1, hObj2;
    UINT            i, j;
    UINT            uMM;
    POINTS          pt1, pt2;
    POINT           rgpt[CPOLYLINEPOINTS];
    RECT            rc;

    GetClientRect(m_hWnd, &rc);

    /*
     * Make a 32-bit copy of the points in the POLYLINE itself
     * so we can use DPtoLP and LPtoDP.
     */
    for (i=0; i < m_pl.cPoints; i++)
        {
        rgpt[i].x=m_pl.rgpt[i].x;
        rgpt[i].y=m_pl.rgpt[i].y;
        }

    /*
     * If the mapping mode is not MM_TEXT, convert the points to
     * whatever mapping mode in in effect before drawing.
     * This specifically supports metafiles in MM_ANISOTROPIC.
     */
    uMM=fMetafile ? MM_TEXT : GetMapMode(hDC);

    if (MM_TEXT!=uMM)
        DPtoLP(hDC, rgpt, m_pl.cPoints);

    hPen=CreatePen(m_pl.iLineStyle, 1, m_pl.rgbLine);
    hObj1=SelectObject(hDC, hPen);

    hBrush=CreateSolidBrush(m_pl.rgbBackground);
    hObj2=SelectObject(hDC, hBrush);
    SetBkColor(hDC, m_pl.rgbBackground);

    /*
     * Either draw the entire figure or just a single point.  The
     * entire figure also includes erasing the background
     * completely,since hDC may be a metafile DC.  Drawing a
     * single point just updates the figure for that new point.
     */
    if (fEntire || 0==m_pl.cPoints)
        {
        //Erase the background for bitmaps and metafiles.
        SelectObject(hDC, GetStockObject(NULL_PEN));
        Rectangle(hDC, rc.left, rc.top, rc.right+1, rc.bottom+1);
        SelectObject(hDC, hPen);

        /*
         * If we are drawing the entire figure, then loop through
         * each point drawing a line to each successive point.
         */

        for (i=0; i < m_pl.cPoints; i++)
            {
            for (j=i; j < m_pl.cPoints; j++)
                {
                /*
                 * Not using POINTTOPOINTS since it unfortunately
                 * returns a LONG which doesn't equate to a POINTS.
                 */
                pt1.x=(short)rgpt[i].x;
                pt1.y=(short)rgpt[i].y;
                pt2.x=(short)rgpt[j].x;
                pt2.y=(short)rgpt[j].y;

                PointScale(&rc, &pt1, TRUE);
                PointScale(&rc, &pt2, TRUE);
                MoveToEx(hDC, pt1.x, pt1.y, NULL);
                LineTo(hDC, pt2.x, pt2.y);
                }
            }
        }
    else
        {
        /*
         * If we are only drawing the last point, just cycle once
         * through previous points.
         */

        //Get the last point entered in the array.
        j=m_pl.cPoints-1;
        pt1.x=(short)rgpt[j].x;
        pt1.y=(short)rgpt[j].y;
        PointScale(&rc, &pt1, TRUE);

        for (i=0; i < j; i++)
            {
            pt2.x=(short)rgpt[i].x;
            pt2.y=(short)rgpt[i].y;
            PointScale(&rc, &pt2, TRUE);

            MoveToEx(hDC, pt1.x, pt1.y, NULL);
            LineTo(hDC, pt2.x, pt2.y);
            }
        }

    //If we have one point, draw a dot to indicate it's position.
    if (1==m_pl.cPoints)
        {
        pt1.x=(short)rgpt[0].x;
        pt1.y=(short)rgpt[0].y;
        PointScale(&rc, &pt1, TRUE);
        SetPixel(hDC, pt1.x, pt1.y, m_pl.rgbLine);
        }

    //Note:  m_pl.rgpt is unmodified in this code.

    SelectObject(hDC, hObj1);
    SelectObject(hDC, hObj2);
    DeleteObject(hBrush);
    DeleteObject(hPen);
    return;
    }

/*
 * CPolyline::PointScale
 *
 * Purpose:
 *  Scales a point to or from a relative window coordinate to a
 *  0-32767 coordinate.
 *
 * Parameters:
 *  pRect           LPRECT of the window.
 *  ppt             LPPOINTS to convert
 *  fScaleToWindow  BOOL indicating direction of scaling.
 *
 * Return Value:
 *  None
 */

void CPolyline::PointScale(LPRECT pRect, LPPOINTS ppt
    , BOOL fScaleToWindow)
    {
    DWORD   cx, cy;

    //Window size
    cx=(DWORD)(pRect->right-pRect->left);
    cy=(DWORD)(pRect->bottom-pRect->top);

    //Prevent crashes
    if (0L==cx) cx=1;
    if (0L==cy) cy=1;

    //Must use DWORD to insure proper scaling.
    if (fScaleToWindow)
        {
        ppt->x=(UINT)(((DWORD)ppt->x*cx) >> 15);
        ppt->y=(UINT)(((DWORD)ppt->y*cy) >> 15);
        }
    else
        {
        ppt->x=(UINT)(((DWORD)ppt->x << 15)/cx);
        ppt->y=(UINT)(((DWORD)ppt->y << 15)/cy);
        }

    return;
    }


RESOURCE.H

/*
 * RESOURCE.H
 * Cosmo Chapter 13
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Note that we augment classlib.h which we include before this.

//Position of Window menu.
#undef  WINDOW_MENU
#define WINDOW_MENU             4

//Total number of menus
#undef  CMENUS
#ifdef MDI
#define CMENUS                  6
#else
#define CMENUS                  5
#endif

//Menu command identifiers.

#define IDM_FILEIMPORT          (IDM_CUSTOMFILEMIN)

#define IDM_COLORBACKGROUND     (IDM_CUSTOMMIN)
#define IDM_COLORLINE           (IDM_CUSTOMMIN+1)

#define IDM_LINEMIN             (IDM_CUSTOMMIN+100)
#define IDM_LINESOLID           (IDM_LINEMIN+0)         //PS_SOLID       0
#define IDM_LINEDASH            (IDM_LINEMIN+1)         //PS_DASH        1
#define IDM_LINEDOT             (IDM_LINEMIN+2)         //PS_DOT         2
#define IDM_LINEDASHDOT         (IDM_LINEMIN+3)         //PS_DASHDOT     3
#define IDM_LINEDASHDOTDOT      (IDM_LINEMIN+4)         //PS_DASHDOTDOT  4
#define IDM_LINEMAX             IDM_LINEDASHDOTDOT

/*
 * IDs for StatStrip handling of popup menus:  must be in order of menu
 * CLASSRES.H already defines ID_MENUFILE and ID_MENUEDIT for us.
 */

#undef  ID_MENUWINDOW
#undef  ID_MENUHELP

#define ID_MENUCOLOR            (ID_MENUCUSTOMMIN+0)
#define ID_MENULINE             (ID_MENUCUSTOMMIN+1)
#define ID_MENUWINDOW           (ID_MENUCUSTOMMIN+2)
#define ID_MENUHELP             (ID_MENUCUSTOMMIN+3)

//Stringtable IDs.  Keep sequential for each group.
#define IDS_FRAMEMIN            IDS_STANDARDFRAMEMIN
#define IDS_FILEIMPORT          (IDS_CUSTOMFRAMEMIN+0)
#define IDS_FIGUREINCONTAINER   (IDS_CUSTOMFRAMEMIN+1)
#define IDS_EXITANDRETURN       (IDS_CUSTOMFRAMEMIN+2)
#define IDS_EMBEDDINGCAPTION    (IDS_CUSTOMFRAMEMIN+3)
#define IDS_SAVECOPYAS          (IDS_CUSTOMFRAMEMIN+4)
#define IDS_NEW                 (IDS_CUSTOMFRAMEMIN+5)
#define IDS_OPEN                (IDS_CUSTOMFRAMEMIN+6)
#define IDS_CLOSE2              (IDS_CUSTOMFRAMEMIN+7)
#define IDS_SAVE                (IDS_CUSTOMFRAMEMIN+8)
#define IDS_SAVEAS              (IDS_CUSTOMFRAMEMIN+9)
#define IDS_EXIT                (IDS_CUSTOMFRAMEMIN+10)
#define IDS_FRAMEMAX            (IDS_CUSTOMFRAMEMIN+11)

#define IDS_DOCUMENTMIN         IDS_STANDARDDOCMIN
#define IDS_VERSIONCHANGE       (IDS_CUSTOMDOCMIN+0)
#define IDS_OBJECTDESCRIPTION   (IDS_CUSTOMDOCMIN+1)
#define IDS_DOCUMENTMAX         (IDS_CUSTOMDOCMIN+1)

//Strings for the CFigure object implementation.
#define IDS_FIGUREMIN           256
#define IDS_CLOSECAPTION        (IDS_FIGUREMIN+1)
#define IDS_CLOSEPROMPT         (IDS_FIGUREMIN+2)
#define IDS_USERTYPE            (IDS_FIGUREMIN+3)
//CHAPTER13MOD
#define IDS_EXTENSION           (IDS_FIGUREMIN+4)
#define IDS_FIGUREMAX           (IDS_FIGUREMIN+4)
//End CHAPTER13MOD

//These are specifically for the StatStrip
#define IDS_STATMESSAGEMIN                  IDS_STANDARDSTATMESSAGEMIN

#define IDS_ITEMMESSAGEFILEIMPORT           (IDS_CUSTOMSTATMESSAGEMIN+0)

#define IDS_MENUMESSAGECOLOR                (IDS_CUSTOMSTATMESSAGEMIN+1)
#define IDS_ITEMMESSAGECOLORBACKGROUND      (IDS_CUSTOMSTATMESSAGEMIN+2)
#define IDS_ITEMMESSAGECOLORLINE            (IDS_CUSTOMSTATMESSAGEMIN+3)

#define IDS_MENUMESSAGELINE                 (IDS_CUSTOMSTATMESSAGEMIN+4)
#define IDS_ITEMMESSAGELINESOLID            (IDS_CUSTOMSTATMESSAGEMIN+5)
#define IDS_ITEMMESSAGELINEDASH             (IDS_CUSTOMSTATMESSAGEMIN+6)
#define IDS_ITEMMESSAGELINEDOT              (IDS_CUSTOMSTATMESSAGEMIN+7)
#define IDS_ITEMMESSAGELINEDASHDOT          (IDS_CUSTOMSTATMESSAGEMIN+8)
#define IDS_ITEMMESSAGELINEDASHDOTDOT       (IDS_CUSTOMSTATMESSAGEMIN+9)

#define IDS_STATMESSAGEMAX                  (IDS_CUSTOMSTATMESSAGEMIN+9)

#endif //_RESOURCE_H_


CLIENT.CPP

/*
 * CLIENT.CPP
 * Patron Chapter 13
 *
 * Implementation of the CPatronClient class that just makes sure
 * we get a CPatronDoc on doc creation and that we initialize fully.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPatronClient::CPatronClient
 * CPatronClient::~CPatronClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 */

CPatronClient::CPatronClient(HINSTANCE hInst)
    : CClient(hInst)
    {
    return;
    }

CPatronClient::~CPatronClient(void)
    {
    return;
    }

/*
 * CPatronClient::CreateCDocument
 *
 * Purpose:
 *  Constructs a new document specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CPatronClient::CreateCDocument(void)
    {
    return (PCDocument)(new CPatronDoc(m_hInst, m_pFR));
    }


DOCUMENT.CPP

/*
 * DOCUMENT.CPP
 * Patron Chapter 13
 *
 * Implementation of the CPatronDoc derivation of CDocument that
 * manages pages for us.
 *
 * Copyright (c)1993-1997 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"
#include <memory.h>
#include <dlgs.h>       //Pring Dlg button IDs

/*
 * CPatronDoc::CPatronDoc
 * CPatronDoc::~CPatronDoc
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame of the frame object.
 */

CPatronDoc::CPatronDoc(HINSTANCE hInst, PCFrame pFR)
    : CDocument(hInst, pFR)
    {
    m_pPG=NULL;
    m_lVer=VERSIONCURRENT;
    m_pIStorage=NULL;
    m_fPrintSetup=TRUE;
    m_pDropTarget=NULL;

    m_cfEmbeddedObject=RegisterClipboardFormat(CF_EMBEDDEDOBJECT);
    m_cfObjectDescriptor=RegisterClipboardFormat
        (CF_OBJECTDESCRIPTOR);

    m_cfLinkSource=RegisterClipboardFormat(CF_LINKSOURCE);
    m_cfLinkSrcDescriptor=RegisterClipboardFormat
        (CF_LINKSRCDESCRIPTOR);

    m_fShowTypes=FALSE;

    //CHAPTER13MOD
    m_cRef               =1L;
    m_fRename            =TRUE;
    m_dwRegROT           =0L;
    m_pIPersistFile      =NULL;
    m_pIOleItemContainer =NULL;
    //End CHAPTER13MOD

    return;
    }

CPatronDoc::~CPatronDoc(void)
    {
    //CHAPTER13MOD
    if (0L!=m_dwRegROT)
        OleStdRevokeAsRunning(&m_dwRegROT);

    if (NULL!=m_pIPersistFile)
        delete m_pIPersistFile;

    if (NULL!=m_pIOleItemContainer)
        delete m_pIOleItemContainer;
    //End CHAPTER13MOD

    if (NULL!=m_pPG)
        delete m_pPG;

    if (NULL!=m_pIStorage)
        m_pIStorage->Release();

    CoFreeUnusedLibraries();
    return;
    }

//CHAPTER13MOD

/*
 * CPatronDoc::QueryInterface
 * CPatronDoc::AddRef
 * CPatronDoc::Release
 *
 * Purpose:
 *  IUnknown members for CPatronDoc object.
 */

STDMETHODIMP CPatronDoc::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    /*
     * WARNING:  Because the *ppv=this implicitly does
     * *ppv=(LPVOID)this, the this pointer is incorrectly cast
     * from a CPatronDoc pointer to LPVOID where we want an
     * IUnknown pointer instead.  CPatronDoc uses multiple
     * inheritance here which means that we have to explicitly
     * provide the typecast to select the base class like
     * IUnknown.  If you don't do this, calls to AddRef, for
     * example, might call your desctructor!
     */

    if (IID_IUnknown==riid)
        *ppv=(LPUNKNOWN)this;

    if (IID_IOleItemContainer==riid || IID_IOleContainer==riid
        || IID_IParseDisplayName==riid)
        *ppv=m_pIOleItemContainer;

    if (IID_IPersistFile==riid || IID_IPersist==riid)
        *ppv=m_pIPersistFile;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return E_NOINTERFACE;
    }

STDMETHODIMP_(ULONG) CPatronDoc::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPatronDoc::Release(void)
    {
    ULONG           cRefT;

    cRefT=--m_cRef;

    if (0L==m_cRef)
        PostMessage(m_hWnd, WM_CLOSE, 0, 0);

    return cRefT;
    }
//End CHAPTER13MOD

/*
 * CPatronDoc::FInit
 *
 * Purpose:
 *  Initializes an already created document window.  The client
 *  actually creates the window for us, then passes that here for
 *  further initialization.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPatronDoc::FInit(PDOCUMENTINIT pDI)
    {
    //Change the stringtable range to our customization.
    pDI->idsMin=IDS_DOCUMENTMIN;
    pDI->idsMax=IDS_DOCUMENTMAX;

    //Do default initialization
    if (!CDocument::FInit(pDI))
        return FALSE;

    //Pages are created when we get a ULoad later.

    //CHAPTER13MOD
    //Instantiate our interfaces
    m_pIPersistFile=new CImpIPersistFile(this, this);

    if (NULL==m_pIPersistFile)
        return FALSE;

    m_pIOleItemContainer=new CImpIOleItemContainer(this, this, TRUE);

    if (NULL==m_pIOleItemContainer)
        return FALSE;

    //End CHAPTER13MOD

    return TRUE;
    }

/*
 * CPatronDoc::FMessageHook
 *
 * Purpose:
 *  Processes WM_SIZE for the document so we can resize the Pages
 *  window.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CPatronDoc::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    UINT        dx, dy;
    RECT        rc;

    *pLRes=0;

    //Eat to prevent flickering
    if (WM_ERASEBKGND==iMsg)
        return TRUE;

    if (WM_SIZE==iMsg && NULL!=m_pPG)
        {
        dx=LOWORD(lParam);
        dy=HIWORD(lParam);

        if (SIZE_MINIMIZED!=wParam)
            {
            //Resize Pages window to fit the new document size.
            GetClientRect(hWnd, &rc);
            m_pPG->RectSet(&rc, FALSE);
            }
        }

    if (WM_DESTROY==iMsg)
        {
        /*
         * We have to revoke the drop target here because the window
         * will be destroyed and the property forcefully removed
         * before we could do this in the destructor.
         */
        if (NULL!=m_pDropTarget)
            {
            RevokeDragDrop(m_hWnd);
            CoLockObjectExternal(m_pDropTarget, FALSE, TRUE);
            m_pDropTarget->Release();
            }

        return FALSE;
        }

    /*
     * We return FALSE even on WM_SIZE so we can let the default
     * procedure handle maximized MDI child windows appropriately.
     */
    return FALSE;
    }

/*
 * CPatronDoc::Clear
 *
 * Purpose:
 *  Sets all contents in the document back to defaults with no
 *  filename.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Clear(void)
    {
    //Completely reset the pages
    if (NULL!=m_pPG)
        m_pPG->FIStorageSet(NULL, FALSE, FALSE);

    CDocument::Clear();
    m_lVer=VERSIONCURRENT;
    return;
    }

/*
 * CPatronDoc::FDirtyGet
 *
 * Purpose:
 *  Returns the current dirty status of the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the file is clean, FALSE otherwise.
 */

BOOL CPatronDoc::FDirtyGet()
    {
    BOOL    fPageDirty;

    fPageDirty=m_pPG->FIsDirty();
    return m_fDirty | fPageDirty;
    }

/*
 * CPatronDoc::Delete
 *
 * Purpose:
 *  Removed the current object from the document.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Delete(void)
    {
    if (NULL!=m_pPG)
        m_pPG->TenantDestroy();

    CoFreeUnusedLibraries();
    return;
    }

/*
 * CPatronDoc::FQueryPrinterSetup
 *
 * Purpose:
 *  Returns whether or not the Printer Setup menu item can be
 *  enabled.  Once you create a tenant in any page, Printer Setup
 *  is voided simply to keep this sample simple, that is, we don't
 *  have to worry about reorganizing potentially large amounts
 *  of layout after we start plopping down objects.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE to enable the menu, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPrinterSetup(void)
    {
    return m_fPrintSetup;
    }

/*
 * CPatronDoc::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected in this
 *  document for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU of the Edit menu.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryObjectSelected(HMENU hMenu)
    {
    return m_pPG->FQueryObjectSelected(hMenu);
    }

/*
 * CPatronDoc::ULoad
 *
 * Purpose:
 *  Loads a given document without any user interface overwriting
 *  the previous contents of the editor.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load.  Could be NULL
 *                  for an untitled document.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_...
 */

UINT CPatronDoc::ULoad(BOOL fChangeFile, LPTSTR pszFile)
    {
    RECT        rc;
    LPSTORAGE   pIStorage;
    HRESULT     hr;
    CLSID       clsID;
    DWORD       dwMode=STGM_TRANSACTED | STGM_READWRITE
                    | STGM_SHARE_EXCLUSIVE;

    if (NULL==pszFile)
        {
        //Create a new temp file.
        hr=StgCreateDocfile(NULL, dwMode | STGM_CREATE
            | STGM_DELETEONRELEASE, 0, &pIStorage);

        //Mark this our class since we check with ReadClassStg.
        if (SUCCEEDED(hr))
            WriteClassStg(pIStorage, CLSID_PatronPages);
        }
    else
        {
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsFile[MAX_PATH];
        mbstowcs(pwcsFile, pszFile, MAX_PATH);
        hr=StgOpenStorage(pwcsFile, NULL, dwMode, NULL, 0, &pIStorage);
#else
        hr=StgOpenStorage(pszFile, NULL, dwMode, NULL, 0, &pIStorage);
#endif
        }

    if (FAILED(hr))
        return DOCERR_COULDNOTOPEN;

    //Check if this is our type of file and exit if not.
    hr=ReadClassStg(pIStorage, &clsID);

    if (FAILED(hr) || CLSID_PatronPages!=clsID)
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    //Attempt to create our contained Pages window.
    m_pPG=new CPages(m_hInst, m_cf);
    GetClientRect(m_hWnd, &rc);

    if (!m_pPG->FInit(m_hWnd, &rc, WS_CHILD | WS_VISIBLE
        , ID_PAGES, NULL))
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    if (!m_pPG->FIStorageSet(pIStorage, FALSE
        , (BOOL)(NULL==pszFile)))
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    //Open the window up for drag-drop
    m_pDropTarget=new CDropTarget(this);

    if (NULL!=m_pDropTarget)
        {
        m_pDropTarget->AddRef();
        CoLockObjectExternal(m_pDropTarget, TRUE, FALSE);
        RegisterDragDrop(m_hWnd, m_pDropTarget);
        }

    m_pIStorage=pIStorage;
    Rename(pszFile);

    //Do initial setup if new file, otherwise Pages handles things.
    if (NULL==pszFile)
        {
        //Go initialize the Pages for the default printer.
        if (!PrinterSetup(NULL, TRUE))
            return DOCERR_COULDNOTOPEN;

        //Go create an initial page.
        m_pPG->PageInsert(0);
        }
    else
        {
        //Can't change an already saved configuration
        m_fPrintSetup=FALSE;
        }

    FDirtySet(FALSE);
    return DOCERR_NONE;
    }

/*
 * CPatronDoc::USave
 *
 * Purpose:
 *  Writes the file to a known filename, requiring that the user
 *  has previously used FileOpen or FileSaveAs in order to have
 *  a filename.
 *
 * Parameters:
 *  uType           UINT indicating the type of file the user
 *                  requested to save in the File Save As dialog.
 *  pszFile         LPTSTR under which to save.  If NULL, use the
 *                  current name.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_...
 */

UINT CPatronDoc::USave(UINT uType, LPTSTR pszFile)
    {
    HRESULT     hr;
    LPSTORAGE   pIStorage;

    //Save or Save As with the same file is just a commit.
    if (NULL==pszFile
        || (NULL!=pszFile && 0==lstrcmpi(pszFile, m_szFile)))
        {
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsTemp[256];
        mbstowcs(pwcsTemp, PSZ(IDS_CLIPBOARDFORMAT), 256);
        WriteFmtUserTypeStg(m_pIStorage, m_cf, pwcsTemp);
#else
        WriteFmtUserTypeStg(m_pIStorage, m_cf
            , PSZ(IDS_CLIPBOARDFORMAT));
#endif
        //Insure pages are up to date.
        m_pPG->FIStorageUpdate(FALSE);

        //Commit everyting
        m_pIStorage->Commit(STGC_ONLYIFCURRENT);

        FDirtySet(FALSE);
        return DOCERR_NONE;
        }

    /*
     * When we're given a name, open the storage, creating it new
     * if it does not exist or overwriting the old one.  Then CopyTo
     * from the current to the new, Commit the new, Release the old.
     */
    OLECHAR pwcsFile[MAX_PATH];
#if defined(WIN32) && !defined(UNICODE)
    mbstowcs(pwcsFile, pszFile, MAX_PATH);
#else
    pwcsFile = pszFile;
#endif
    hr=StgCreateDocfile(pwcsFile, STGM_TRANSACTED | STGM_READWRITE
        | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

    if (FAILED(hr))
        return DOCERR_COULDNOTOPEN;

    WriteClassStg(pIStorage, CLSID_PatronPages);
#if defined(WIN32) && !defined(UNICODE)
    OLECHAR pwcsTemp[256];
    mbstowcs(pwcsTemp, PSZ(IDS_CLIPBOARDFORMAT), 256);
    WriteFmtUserTypeStg(pIStorage, m_cf, pwcsTemp);
#else
    WriteFmtUserTypeStg(pIStorage, m_cf, PSZ(IDS_CLIPBOARDFORMAT));
#endif
    //Insure all pages are up-to-date.
    m_pPG->FIStorageUpdate(TRUE);

    //This also copies the CLSID we stuff in here on file creation.
    hr=m_pIStorage->CopyTo(NULL, NULL, NULL, pIStorage);

    if (FAILED(hr))
        {
        SCODE       sc;

        pIStorage->Release();
        sc=hr;

        /*
         * If we failed because of low memory, use IRootStorage
         * to switch into the new file.
         */
        if (E_OUTOFMEMORY==sc)
            {
            LPROOTSTORAGE        pIRoot;

            //Delete file we already created
            DeleteFile(pszFile);

            if (FAILED(m_pIStorage->QueryInterface
                (IID_IRootStorage, (PPVOID)&pIRoot)))
                return DOCERR_WRITEFAILURE;

            hr=pIRoot->SwitchToFile(pwcsFile);
            pIRoot->Release();

            if (FAILED(hr))
                return DOCERR_WRITEFAILURE;

            //If successful, the Commit below finishes the save.
            pIStorage=m_pIStorage;
            m_pIStorage->AddRef();    //Matches Release below
            }
        }

    pIStorage->Commit(STGC_ONLYIFCURRENT);

    /*
     * Revert changes on the original storage.  If this was a temp
     * file, it's deleted since we used STGM_DELETEONRELEASE.
     */
    m_pIStorage->Release();

    //Make this new storage current
    m_pIStorage=pIStorage;
    m_pPG->FIStorageSet(pIStorage, TRUE, FALSE);

    FDirtySet(FALSE);

    //CHAPTER13MOD
    if (m_fRename)
        Rename(pszFile);    //Update caption bar.
    //End CHAPTER13MOD

    return DOCERR_NONE;
    }

/*
 * CPatronDoc::FClip
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard, optionally implementing Cut by deleting the
 *  data in the current window after rendering.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *  fCut            BOOL indicating cut (TRUE) or copy (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::FClip(HWND hWndFrame, BOOL fCut)
    {
    if (NULL==m_pPG)
        return FALSE;

    return m_pPG->TenantClip(fCut);
    }

/*
 * CPatronDoc::FPaste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  to the current figure in the editor window.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::FPaste(HWND hWndFrame)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet=FALSE;
    FORMATETC       fe;
    TENANTTYPE      tType;

    if (NULL==m_pPG)
        return FALSE;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    //Go get type and format we *can* paste, then actually paste it.
    if (FQueryPasteFromData(pIDataObject, &fe, &tType))
        {
        fRet=FPasteFromData(pIDataObject, &fe, tType, NULL
            , 0L, TRUE);
        }

    pIDataObject->Release();
    return fRet;
    }

/*
 * CPatronDoc::FQueryPaste
 *
 * Purpose:
 *  Determines if we can paste data from the clipboard.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPaste(void)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    fRet=FQueryPasteFromData(pIDataObject, NULL, NULL);

    fRet |= FQueryPasteLinkFromData(pIDataObject, NULL, NULL);
    pIDataObject->Release();
    return fRet;
    }

/*
 * CPatronDoc::FPasteSpecial
 *
 * Purpose:
 *  Retrieves a specific data format from the clipboard and sends
 *  it to the editor window appropriately.
 *
 *  Note that if this function is called, then the appropriate
 *  format is available because the Paste menu item is only
 *  enabled if the format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::FPasteSpecial(HWND hWndFrame)
    {
    OLEUIPASTESPECIAL   ps;
    OLEUIPASTEENTRY     rgPaste[6];
    UINT                rgcf[1];        //For ps.m_arrLinkTypes
    DWORD               dwData=0;
    UINT                uTemp;
    BOOL                fRet=FALSE;

    if (NULL==m_pPG)
        return FALSE;

    memset(&ps, 0, sizeof(ps));

    if (FAILED(OleGetClipboard(&ps.lpSrcDataObj)))
        return FALSE;

    ps.cbStruct=sizeof(ps);
    ps.hWndOwner=hWndFrame;

    ps.dwFlags=PSF_SELECTPASTE;
    ps.arrPasteEntries=rgPaste;

    //Set up Paste Special descriptor arrays.
    SETDefFormatEtc(rgPaste[0].fmtetc, m_cf, TYMED_HGLOBAL);
    rgPaste[0].lpstrFormatName=PSZ(IDS_CLIPBOARDFORMAT);
    rgPaste[0].lpstrResultText=PSZ(IDS_PASTEASPATRON);
    rgPaste[0].dwFlags=OLEUIPASTE_PASTEONLY;

    //Embedded objects can be iconic displays if the user wants.
    SETDefFormatEtc(rgPaste[1].fmtetc, m_cfEmbeddedObject
        , TYMED_ISTORAGE);
    rgPaste[1].lpstrFormatName=PSZ(IDS_PASTEOBJECT);
    rgPaste[1].lpstrResultText=PSZ(IDS_PASTEASOBJECT);

    /*
     * CAUTION:  Use OLEUI_PASTE with embedded objects or else
     * this item will not show up in the dialog.  I learned this the
     * hard way (that is, after about 6 hours of pulling hair!).
     */
    rgPaste[1].dwFlags=OLEUIPASTE_PASTE | OLEUIPASTE_ENABLEICON;

    SETDefFormatEtc(rgPaste[2].fmtetc,CF_METAFILEPICT,TYMED_MFPICT);
    rgPaste[2].lpstrFormatName=PSZ(IDS_PASTEMETAFILE);
    rgPaste[2].lpstrResultText=PSZ(IDS_PASTEASMETAFILE);
    rgPaste[2].dwFlags=OLEUIPASTE_PASTEONLY;

    SETDefFormatEtc(rgPaste[3].fmtetc, CF_DIB, TYMED_HGLOBAL);
    rgPaste[3].lpstrFormatName=PSZ(IDS_PASTEDIB);
    rgPaste[3].lpstrResultText=PSZ(IDS_PASTEASDIB);
    rgPaste[3].dwFlags=OLEUIPASTE_PASTEONLY;

    SETDefFormatEtc(rgPaste[4].fmtetc, CF_BITMAP, TYMED_GDI);
    rgPaste[4].lpstrFormatName=PSZ(IDS_PASTEBITMAP);
    rgPaste[4].lpstrResultText=PSZ(IDS_PASTEASBITMAP);
    rgPaste[4].dwFlags=OLEUIPASTE_PASTEONLY;

    SETDefFormatEtc(rgPaste[5].fmtetc,m_cfLinkSource,TYMED_ISTREAM);
    rgPaste[5].lpstrFormatName=PSZ(IDS_PASTELINK);
    rgPaste[5].lpstrResultText=PSZ(IDS_PASTEASLINK);
    rgPaste[5].dwFlags=OLEUIPASTE_LINKTYPE1 | OLEUIPASTE_ENABLEICON;

    //Types we can Paste Link from the clipboard.
    rgcf[0]=m_cfLinkSource;
    ps.arrLinkTypes=rgcf;
    ps.cLinkTypes=1;
    ps.cPasteEntries=6;

    uTemp=OleUIPasteSpecial(&ps);

    if (OLEUI_OK==uTemp)
        {
        UINT        i=ps.nSelectedIndex;
        TENANTTYPE  tType;

        if (ps.fLink)
            tType=TENANTTYPE_LINKEDOBJECTFROMDATA;
        else
            {
            if (1==ps.nSelectedIndex)
                tType=TENANTTYPE_EMBEDDEDOBJECTFROMDATA;
            else
                tType=TENANTTYPE_STATIC;
            }

        //Handle iconic aspects...from links as well
        if ((1==i || ps.fLink) && (PSF_CHECKDISPLAYASICON
            & ps.dwFlags) && NULL!=ps.hMetaPict)
            {
            rgPaste[i].fmtetc.dwAspect=DVASPECT_ICON;
            dwData=(DWORD)(UINT)ps.hMetaPict;
            }

        fRet=FPasteFromData(ps.lpSrcDataObj, &rgPaste[i].fmtetc
            , tType, NULL, dwData, FALSE);

        //Always free this regardless of what we do with it.
        if (NULL!=ps.hMetaPict)
            OleUIMetafilePictIconFree(ps.hMetaPict);
        }

    ps.lpSrcDataObj->Release();
    return fRet;
    }

/*
 * CPatronDoc::FQueryPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Determines if we can paste data from a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which we might want to paste.
 *  pFE             LPFORMATETC in which to return the first format
 *                  we can use.  Ignored if NULL.
 *  ptType          PTENANTTYPE in which to store the type of
 *                  object we can paste.  Ignored if NULL.
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPasteFromData(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, PTENANTTYPE ptType)
    {
    FORMATETC       fe;
    HRESULT         hr, hr2;

    if (NULL!=(LPVOID)ptType)
        *ptType=TENANTTYPE_STATIC;

    //Any of our specific data here?
    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    hr=pIDataObject->QueryGetData(&fe);

    //If embedded object data is available, set the appropriate type
    hr2=OleQueryCreateFromData(pIDataObject);

    if (NOERROR==hr2)
        {
        if (NULL!=pFE)
            {
            /*
             * Default to content.  FPaste will use
             * CF_OBJECTDESCRIPTOR to figure the actual aspect.
             */
            SETDefFormatEtc(*pFE, m_cfEmbeddedObject
                , TYMED_ISTORAGE);
            }

        if (NULL!=(LPVOID)ptType)
            *ptType=TENANTTYPE_EMBEDDEDOBJECTFROMDATA;

        /*
         * Return now if PatronObject wasn't available, otherwise
         * break out so that pFE gets PatronObject format.
         */
        if (NOERROR!=hr)
            return TRUE;
        }

    if (NOERROR!=hr && NOERROR!=hr2)
        {
        //Try metafile, DIB, then bitmap, setting fe each time
        SETDefFormatEtc(fe, CF_METAFILEPICT, TYMED_MFPICT);
        hr=pIDataObject->QueryGetData(&fe);

        if (NOERROR!=hr)
            {
            SETDefFormatEtc(fe, CF_DIB, TYMED_HGLOBAL);
            hr=pIDataObject->QueryGetData(&fe);

            if (NOERROR!=hr)
                {
                SETDefFormatEtc(fe, CF_BITMAP, TYMED_GDI);
                hr=pIDataObject->QueryGetData(&fe);
                }
            }
        }

    if (NOERROR==hr && NULL!=pFE)
        *pFE=fe;

    return (NOERROR==hr);
    }

/*
 * CPatronDoc::FQueryPasteLinkFromData
 * (Protected)
 *
 * Purpose:
 *  Determines if we can paste link from a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which we might want to paste.
 *  pFE             LPFORMATETC in which to return the first format
 *                  we can use.  Ignored if NULL.
 *  ptType          PTENANTTYPE in which to store the type of object
 *                  we can paste.  Ignored if NULL.
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPasteLinkFromData(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, PTENANTTYPE ptType)
    {
    HRESULT         hr;

    if (NULL==pIDataObject)
        return FALSE;

    hr=OleQueryLinkFromData(pIDataObject);

    if (NOERROR!=hr)
        return FALSE;

    if (NULL!=pFE)
        SETDefFormatEtc(*pFE, m_cfLinkSource, TYMED_ISTREAM);

    if (NULL!=(LPVOID)ptType)
        *ptType=TENANTTYPE_LINKEDOBJECTFROMDATA;

    return TRUE;
    }

/*
 * CPatronDoc::FPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Retrieves the private data format from a data object and sets
 *  it to the current figure in the editor window.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *  pFE             LPFORMATETC to use in the paste. Cannot be NULL.
 *  tType           TENANTTYPE to paste.
 *  ppo             PPATRONOBJECT containing placement data.
 *  dwData          DWORD extra data sensitive to tType
 *  fUseObjDesc     BOOL indicating to use CF_OBJECTDESCRIPTOR
 *                  format for determining the aspect of the object
 *                  if the format is available.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::FPasteFromData(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, TENANTTYPE tType, PPATRONOBJECT ppo
    , DWORD dwData, BOOL fUseObjDesc)
    {
    BOOL                fRet;
    HRESULT             hr;
    PATRONOBJECT        po;
    STGMEDIUM           stm;
    LPOBJECTDESCRIPTOR  pOD;
    FORMATETC           fe;
    BOOL                fRelease=FALSE;

    if (NULL==pFE)
        return FALSE;

    //If not given any placement data, see if we can retrieve it
    if (pFE->cfFormat==m_cf && NULL==ppo)
        {
        hr=pIDataObject->GetData(pFE, &stm);

        if (SUCCEEDED(hr))
            {
            ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);

            po=*ppo;
            ppo=&po;

            //If there's an object here, make sure type is right.
            if (ppo->fe.cfFormat==m_cfEmbeddedObject)
                tType=TENANTTYPE_EMBEDDEDOBJECTFROMDATA;

            if (ppo->fe.cfFormat==m_cfLinkSource)
                tType=TENANTTYPE_LINKEDOBJECTFROMDATA;

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);
            }
        }

    /*
     * If we're told to look at CF_OBJECTDESCRIPTOR, then try to get
     * the data and copy the aspect out of it.  We're not interested
     * in any other part of it, however.
     */
    if (fUseObjDesc)
        {
        SETDefFormatEtc(fe, m_cfObjectDescriptor, TYMED_HGLOBAL);

        hr=pIDataObject->GetData(&fe, &stm);

        if (FAILED(hr))
            {
            SETDefFormatEtc(fe, m_cfLinkSrcDescriptor
                , TYMED_HGLOBAL);
            hr=pIDataObject->GetData(&fe, &stm);
            }

        if (SUCCEEDED(hr))
            {
            pOD=(LPOBJECTDESCRIPTOR)GlobalLock(stm.hGlobal);
            pFE->dwAspect=pOD->dwDrawAspect;

            if (NULL!=ppo)
                ppo->fe.dwAspect=pFE->dwAspect;

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);

            /*
             * Furthermore, if pFE->dwAspect is DVASPECT_ICON, get
             * the metafile which will always be the icon
             * representation.
             */
            SETFormatEtc(fe, CF_METAFILEPICT, DVASPECT_ICON, NULL
                , TYMED_MFPICT, -1);

            if (SUCCEEDED(pIDataObject->GetData(&fe, &stm)))
                {
                dwData=(DWORD)(UINT)stm.hGlobal;
                fRelease=TRUE;
                }
            }
        }

    fRet=m_pPG->TenantCreate(tType, pIDataObject, pFE, ppo, dwData);

    //Release the stm from the last GetData
    if (fRelease)
        ReleaseStgMedium(&stm);

    if (fRet)
        {

//Disable Printer Setup once we've created a tenant.
        m_fPrintSetup=FALSE;
        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CPatronDoc::FQueryEnableEditLinks
 *
 * Purpose:
 *  Determines whether or not the Edit Links menu item should be
 *  enabled on the frame window menu.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE to enable the item, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryEnableEditLinks(void)
    {
    return m_pPG->FQueryLinksInPage();
    }

/*
 * CPatronDoc::FEditLinks
 *
 * Purpose:
 *  Invokes the links dialog in which the end-user can manipulate
 *  all the links in the current page.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame window to use as the dialog
 *                  parent.
 *
 * Return Value:
 *  BOOL            TRUE if the function was successful,
 *                  FALSE otherwise.
 */

BOOL CPatronDoc::FEditLinks(HWND hWndFrame)
    {
    UINT                    uRet;
    OLEUIEDITLINKS          el;
    PCIOleUILinkContainer   pIUILinks;

    memset(&el, 0, sizeof(el));
    el.cbStruct=sizeof(el);
    el.hWndOwner=hWndFrame;

    if (!m_pPG->FGetUILinkContainer(&pIUILinks))
        return FALSE;

    el.lpOleUILinkContainer=pIUILinks;
    uRet=OleUIEditLinks(&el);

    //Only the IOleUILinkContainer interface will know dirtying.
    m_fDirty=pIUILinks->m_fDirty;

    el.lpOleUILinkContainer->Release();
    return TRUE;
    }

/*
 * CPatronDoc::FShowOrQueryObjectTypes
 *
 * Purpose:
 *  If querying, we return whether or not ShowObjects is active
 *  in this document.  Otherwise pass-through to
 *  CPages::ShowObjectTypes.
 *
 * Parameters:
 *  fShow           BOOL indicating to show the type or not.
 *
 * Return Value:
 *  None
 */

BOOL CPatronDoc::FShowOrQueryObjectTypes(BOOL fQuery, BOOL fShow)
    {
    if (fQuery)
        return m_fShowTypes;

    m_fShowTypes=fShow;
    m_pPG->ShowObjectTypes(fShow);
    return TRUE;
    }

/*
 * CPatronDoc::Print
 *
 * Purpose:
 *  Prints the current document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *
 * Return Value:
 *  BOOL            TRUE if printing happened, FALSE if it didn't
 *                  start or didn't complete.
 */

BOOL CPatronDoc::Print(HWND hWndFrame)
    {
    PRINTDLG        pd;
    BOOL            fSuccess;

    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);
    pd.hwndOwner  =hWndFrame;
    pd.nCopies    =1;
    pd.nFromPage  =(USHORT)-1;
    pd.nToPage    =(USHORT)-1;
    pd.nMinPage   =1;
    pd.nMaxPage   =m_pPG->NumPagesGet();

    pd.lpfnPrintHook=PrintDlgHook;

    //Get the current document printer settings
    pd.hDevMode=m_pPG->DevModeGet();

    pd.Flags=PD_RETURNDC | PD_ALLPAGES | PD_COLLATE
        | PD_HIDEPRINTTOFILE | PD_NOSELECTION | PD_ENABLEPRINTHOOK;

    if (!PrintDlg(&pd))
        return FALSE;

    if (NULL!=pd.hDevMode)
        GlobalFree(pd.hDevMode);

    if (NULL!=pd.hDevNames)
        GlobalFree(pd.hDevNames);

    //Go do the actual printing.
    fSuccess=m_pPG->Print(pd.hDC, PSZ(IDS_DOCUMENTNAME), pd.Flags
        , pd.nFromPage, pd.nToPage, pd.nCopies);

    if (!fSuccess)
        {
        MessageBox(m_hWnd, PSZ(IDS_PRINTERROR)
            , PSZ(IDS_DOCUMENTCAPTION), MB_OK);
        }

    return fSuccess;
    }

/*
 * CPatronDoc::PrinterSetup
 *
 * Purpose:
 *  Selects a new printer and options for this document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *  fDefault        BOOL to avoid any dialog and just use the
 *                  default.
 *
 * Return Value:
 *  UINT            Undefined
 *
 */

UINT CPatronDoc::PrinterSetup(HWND hWndFrame, BOOL fDefault)
    {
    PRINTDLG        pd;

    //Attempt to get printer metrics for the default printer.
    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);

    if (fDefault)
        pd.Flags=PD_RETURNDEFAULT;
    else
        {
        pd.hwndOwner=hWndFrame;
        pd.Flags=PD_PRINTSETUP;

        //Get the current document printer settings
        pd.hDevMode=m_pPG->DevModeGet();
        }

    if (!PrintDlg(&pd))
        return FALSE;

    if (!m_pPG->DevModeSet(pd.hDevMode, pd.hDevNames))
        {
        GlobalFree(pd.hDevNames);
        GlobalFree(pd.hDevMode);
        return FALSE;
        }

    FDirtySet(TRUE);
    return 1;
    }

/*
 * CPatronDoc::NewPage
 *
 * Purpose:
 *  Creates a new page in the document's pages control after the
 *  current page.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::NewPage(void)
    {
    FDirtySet(TRUE);
    return m_pPG->PageInsert(0);
    }

/*
 * CPatronDoc::DeletePage
 *
 * Purpose:
 *  Deletes the current page from the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the now current page.
 */

UINT CPatronDoc::DeletePage(void)
    {
    FDirtySet(TRUE);
    return m_pPG->PageDelete(0);
    }

/*
 * CPatronDoc::NextPage
 *
 * Purpose:
 *  Shows the next page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::NextPage(void)
    {
    UINT        iPage;

    iPage=m_pPG->CurPageGet();
    return m_pPG->CurPageSet(++iPage);
    }

/*
 * CPatronDoc::PreviousPage
 *
 * Purpose:
 *  Shows the previous page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::PreviousPage(void)
    {
    UINT        iPage;

    //If iPage is zero, then we wrap around to the end.
    iPage=m_pPG->CurPageGet();
    return m_pPG->CurPageSet(--iPage);
    }

/*
 * CPatronDoc::FirstPage
 *
 * Purpose:
 *  Shows the first page page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::FirstPage(void)
    {
    return m_pPG->CurPageSet(0);
    }

/*
 * CPatronDoc::LastPage
 *
 * Purpose:
 *  Shows the last page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the last page.
 */

UINT CPatronDoc::LastPage(void)
    {
    return m_pPG->CurPageSet(NOVALUE);
    }

/*
 * PrintDlgHook
 *
 * Purpose:
 *  Callback hook for the Print Dialog so we can hide the Setup
 *  button.  Patron only allows Setup before anything exists on
 *  the page, and is not written to handle setup at Print time.
 */

UINT CALLBACK PrintDlgHook(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    if (WM_INITDIALOG==iMsg)
        {
        HWND        hWnd;

        hWnd=GetDlgItem(hDlg, psh1);
        ShowWindow(hWnd, SW_HIDE);
        return TRUE;
        }

    return FALSE;
    }

/*
 * CPatronDoc::Rename
 *
 * Purpose:
 *  Overrides the normal rename to include notification of tenants
 *  in this document of the new name.  This is so embedded objects
 *  are told the correct name through IOleObject::SetHostNames.
 *
 * Parameters:
 *  pszFile         LPTSTR to the new filename.
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Rename(LPTSTR pszFile)
    {
    LPMONIKER   pmk;

    //We don't need to change the base class, just augment...
    CDocument::Rename(pszFile);

    //CHAPTER13MOD
    /*
     * Get rid of the old moniker.  The OldStd function sets the
     * key to zero.
     */
    if (0L!=m_dwRegROT)
        OleStdRevokeAsRunning(&m_dwRegROT);

    if (NULL==pszFile)
        return;
#if defined(WIN32) && !defined(UNICODE)
    OLECHAR pwcsFile[MAX_PATH];
    mbstowcs(pwcsFile, pszFile, MAX_PATH);
    CreateFileMoniker(pwcsFile, &pmk);
#else
    CreateFileMoniker(pszFile, &pmk);
#endif
    if (NULL!=pmk)
        {
        OleStdRegisterAsRunning(this, pmk, &m_dwRegROT);

        //Give a moniker to linked objects in tenants.
        m_pPG->NotifyTenantsOfRename(pszFile, pmk);

        //No need for us to hold on to this.
        pmk->Release();
        }
    //End CHAPTER13MOD

    return;
    }

/*
 * CPatronDoc::FInsertObject
 *
 * Purpose:
 *  Retrieves a CLSID or a filename from the Insert Object dialog
 *  box and creates an object using those identifiers.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::FInsertObject(HWND hWndFrame)
    {
    OLEUIINSERTOBJECT   io;
    DWORD               dwData=0;
    TCHAR               szFile[CCHPATHMAX];
    UINT                uTemp;
    BOOL                fRet=FALSE;

    if (NULL==m_pPG)
        return FALSE;

    memset(&io, 0, sizeof(io));

    io.cbStruct=sizeof(io);
    io.hWndOwner=hWndFrame;

    szFile[0]=0;
    io.lpszFile=szFile;
    io.cchFile=CCHPATHMAX;

    //We now support linking, so remove the IOF_DISABLELINK flag.
    io.dwFlags=IOF_SELECTCREATENEW;

    uTemp=OleUIInsertObject(&io);

    if (OLEUI_OK==uTemp)
        {
        TENANTTYPE      tType;
        LPVOID          pv;
        FORMATETC       fe;

        SETDefFormatEtc(fe, 0, TYMED_NULL);

        if (io.dwFlags & IOF_SELECTCREATENEW)
            {
            tType=TENANTTYPE_EMBEDDEDOBJECT;
            pv=&io.clsid;
            }
        else
            {
            if (io.dwFlags & IOF_CHECKLINK)
                tType=TENANTTYPE_LINKEDFILE;
            else
                tType=TENANTTYPE_EMBEDDEDFILE;
            pv=szFile;
            }

        if ((io.dwFlags & IOF_CHECKDISPLAYASICON)
            && NULL!=io.hMetaPict)
            {
            fe.dwAspect=DVASPECT_ICON;
            dwData=(DWORD)(UINT)io.hMetaPict;
            }

        fRet=m_pPG->TenantCreate(tType, pv, &fe, NULL, dwData);

        //Free this regardless of what we do with it.
        if (NULL!=io.hMetaPict)
            OleUIMetafilePictIconFree(io.hMetaPict);

        if (fRet)
            {
            //Disable Printer Setup once we've created a tenant.
            m_fPrintSetup=FALSE;
            FDirtySet(TRUE);
            }
        }

    return fRet;
    }

/*
 * CPatronDoc::ActivateObject
 *
 * Purpose:
 *  Executes a verb on the currently selected object.
 *
 * Parameters:
 *  iVerb           LONG of the selected verb.
 *
 * Return Value:
 *  None
 */

void CPatronDoc::ActivateObject(LONG iVerb)
    {
    m_pPG->ActivateObject(iVerb);
    return;
    }


DRAGDROP.CPP

/*
 * DRAGDROP.CPP
 * Patron Chapter 13
 *
 * Member functions of the CPages class concerned with drag-drop
 * and other object-placement functionality.  Moved here to clean
 * up CPAGES.CPP somewhat.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPages::UTestDroppablePoint
 *
 * Purpose:
 *  Returns if the point in pptl is on the paper in the current page
 *  and alternately if it's within horizontal or vertical inset
 *  regions.
 *
 * Parameters:
 *  pptl            LPPOINTL containing the point in screen
 *                  coordinates.
 *
 * Return Value:
 *  UINT            A UDROP_* value.
 */

UINT CPages::UTestDroppablePoint(LPPOINTL pptl)
    {
    POINT       pt;
    RECT        rc, rcT, rcC;
    UINT        uRet;

    POINTFROMPOINTL(pt, *pptl);
    ScreenToClient(m_hWnd, &pt);

    CalcBoundingRect(&rc, FALSE);

    GetClientRect(m_hWnd, &rcC);
    IntersectRect(&rcT, &rc, &rcC);

    //Check for at least a client area hit.
    if (!PtInRect(&rcT, pt))
        return UDROP_NONE;

    uRet=UDROP_CLIENT;

    //Scroll checks happen on client area, not document area
    if (PtInRect(&rcC, pt))
        {
        //Check horizontal inset
        if (pt.x <= rcC.left+(int)m_uScrollInset)
            uRet |= UDROP_INSETLEFT;
        else if (pt.x >= rcC.right-(int)m_uScrollInset)
            uRet |= UDROP_INSETRIGHT;

        //Check vertical inset
        if (pt.y <= rcC.top+(int)m_uScrollInset)
            uRet |= UDROP_INSETTOP;
        else if (pt.y >= rcC.bottom-(int)m_uScrollInset)
            uRet |= UDROP_INSETBOTTOM;
        }

    return uRet;
    }

/*
 * CPages::DrawDropTargetRect
 *
 * Purpose:
 *  Draws a dotted rectangle on the Pages window to show where
 *  a drop might occur.  This is a toggle function.
 *
 * Parameters:
 *  pptl            LPPOINTL containing the upper-left point
 *                  in screen coordinates.
 *  pszl            LPSIZEL containing the rect extents in device
 *                  units.
 *
 * Return Value:
 *  None
 */

void CPages::DrawDropTargetRect(LPPOINTL pptl, LPSIZEL pszl)
    {
    POINT           pt;
    RECT            rc, rcT;
    HDC             hDC;

    if (NULL==pptl && NULL==pszl)
        {
        /*
         * This case is used from WM_PAINT in pagewin.cpp so we can
         * control the proper visibility of the drag rectangle when
         * we're scrolling.  If drag-drop happens between two apps,
         * then any ScrollWindow will cause a WM_PAINT after we
         * leave DragOver, so that paint would normally overwrite
         * the last drawn rectangle.  To alleviate that, WM_PAINT
         * will remove the last rect by sending us NULLs, paint,
         * then reinstate the rectangle.
         */
        pptl=&m_ptlRect;
        pszl=&m_szlRect;
        }
    else
        {
        m_ptlRect.x=pptl->x;
        m_ptlRect.y=pptl->y;
        m_szlRect.cx=pszl->cx;
        m_szlRect.cy=pszl->cy;

        //Flag is only affected from IDropTarget, not WM_PAINT calls
        m_fDragRectShown=!m_fDragRectShown;
        }

    POINTFROMPOINTL(pt, *pptl);
    ScreenToClient(m_hWnd, &pt);
    SetRect(&rc, pt.x, pt.y, pt.x+(int)pszl->cx
        , pt.y+(int)pszl->cy);

    CalcBoundingRect(&rcT, FALSE);
    IntersectRect(&rc, &rc, &rcT);

    if (!IsRectEmpty(&rc))
        {
        hDC=GetDC(m_hWnd);
        DrawFocusRect(hDC, &rc);
        ReleaseDC(m_hWnd, hDC);
        }

    return;
    }

/*
 * CPages::AdjustPosition
 *
 * Purpose:
 *  Adjusts a point for the scrolling offset and then converts it
 *  and a size from device into LOMETRIC units.
 *
 * Parameters:
 *  pptl            LPPOINTL to adject and convert
 *  pszl            LPSIZEL to convert
 *
 * Return Value:
 *  None
 */

void CPages::AdjustPosition(LPPOINTL pptl, LPSIZEL pszl)
    {
    RECT        rc;

    SetRect(&rc, m_xPos+(int)pptl->x
        , m_yPos+(int)pptl->y, (int)pszl->cx, (int)pszl->cy);

    RectConvertMappings(&rc, NULL, FALSE);

    pptl->x=rc.left;
    pptl->y=rc.top;

    pszl->cx=rc.right;
    pszl->cy=rc.bottom;

    return;
    }


DROPSRC.CPP

/*
 * DROPSRC.CPP
 * Patron Chapter 13
 *
 * Implementation of a DropSource object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CDropSource::CDropSource
 * CDropSource::~CDropSource
 *
 * Constructor Parameters:
 *  None
 */

CDropSource::CDropSource(void)
    {
    m_cRef=0;
    return;
    }

CDropSource::~CDropSource(void)
    {
    return;
    }

/*
 * CDropSource::QueryInterface
 * CDropSource::AddRef
 * CDropSource::Release
 *
 * Purpose:
 *  IUnknown members for CDropSource object.
 */

STDMETHODIMP CDropSource::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDropSource==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDropSource::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropSource::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDropSource::QueryDragContinue
 *
 * Purpose:
 *  Determines whether to continue a drag operation or cancel it.
 *
 * Parameters:
 *  fEsc            BOOL indicating that the ESC key was pressed.
 *  grfKeyState     DWORD providing states of keys and mouse buttons
 *
 * Return Value:
 *  HRESULT         DRAGDROP_S_CANCEL to stop the drag,
 *                  DRAGDROP_S_DROP to drop the data where it is,
 *                  or NOERROR to continue.
 */

STDMETHODIMP CDropSource::QueryContinueDrag(BOOL fEsc, DWORD grfKeyState)
    {
    if (fEsc)
        return ResultFromScode(DRAGDROP_S_CANCEL);

    if (!(grfKeyState & MK_LBUTTON))
        return ResultFromScode(DRAGDROP_S_DROP);

    return NOERROR;
    }

/*
 * CDropSource::GiveFeedback
 *
 * Purpose:
 *  Provides cursor feedback to the user since the source task
 *  always has the mouse capture.  We can also provide any other
 *  type of feedback above cursors if we so desire.
 *
 * Parameters:
 *  dwEffect        DWORD effect flags returned from the last target
 *
 * Return Value:
 *  HRESULT         NOERROR if you set a cursor yourself or
 *                  DRAGDROP_S_USEDEFAULTCURSORS to let OLE do
 *                  the work.
 */

STDMETHODIMP CDropSource::GiveFeedback(DWORD dwEffect)
    {
    return ResultFromScode(DRAGDROP_S_USEDEFAULTCURSORS);
    }


DROPTGT.CPP

/*
 * DROPTGT.CPP
 * Patron Chapter 13
 *
 * Implementation of a DropTarget object
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CDropTarget::CDropTarget
 * CDropTarget::~CDropTarget
 *
 * Constructor Parameters:
 *  pDoc            PCPatronDoc of the window containing us.
 */

CDropTarget::CDropTarget(PCPatronDoc pDoc)
    {
    m_cRef=0;
    m_pDoc=pDoc;

    m_pIDataObject=NULL;
    return;
    }

CDropTarget::~CDropTarget(void)
    {
    return;
    }

/*
 * CDropTarget::QueryInterface
 * CDropTarget::AddRef
 * CDropTarget::Release
 *
 * Purpose:
 *  IUnknown members for CDropTarget object.
 */

STDMETHODIMP CDropTarget::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDropTarget==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDropTarget::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropTarget::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDropTarget::DragEnter
 *
 * Purpose:
 *  Indicates that data in a drag operation has been dragged over
 *  our window that's a potential target.  We are to decide if it's
 *  something in which we're interested.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT providing the source data.
 *  grfKeyState     DWORD flags: states of keys and mouse buttons.
 *  pt              POINTL coordinates in the client space of
 *                  the document.
 *  pdwEffect       LPDWORD into which we'll place the appropriate
 *                  effect flag for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragEnter(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    PCPages         ppg=m_pDoc->m_pPG;
    HWND            hWnd;
    FORMATETC       fe;
    STGMEDIUM       stm;
    UINT            uRet;

    m_fFeedback=FALSE;
    m_pIDataObject=NULL;

    if (!m_pDoc->FQueryPasteFromData(pIDataSource, &fe, NULL))
        {
        *pdwEffect=DROPEFFECT_NONE;
        return NOERROR;
        }

    //Check if this is a valid drop point.
    uRet=ppg->UTestDroppablePoint(&pt);
    ppg->m_uLastTest=uRet;

    if (UDROP_NONE==uRet)
        *pdwEffect=DROPEFFECT_NONE;
    else
        {
        //Default is move if we can, in fact drop here.
        *pdwEffect=DROPEFFECT_MOVE;

        if (grfKeyState & MK_CONTROL)
            *pdwEffect=DROPEFFECT_COPY;
        }

    m_pIDataObject=pIDataSource;
    m_pIDataObject->AddRef();

    /*
     * Determine the size of the data, if we can.  The default is
     * a small rectangle since we can't easily tell what size
     * something will be if we're pulling in a metafile or bitmap.
     * It's not a good idea to render it here with GetData just to
     * find that out. We only know the size if it's our own object
     * in which case a GetData will be fast.
     */

    if (fe.cfFormat==m_pDoc->m_cf)
        {
        if (SUCCEEDED(pIDataSource->GetData(&fe, &stm)))
            {
            PPATRONOBJECT   ppo;
            RECT            rc;

            ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);

            SetRect(&rc, (int)ppo->szl.cx, -(int)ppo->szl.cy, 0, 0);
            RectConvertMappings(&rc, NULL, TRUE);
            SETSIZEL(m_szl, rc.left, rc.top);

            m_ptPick=ppo->ptlPick;
            m_fe=ppo->fe;

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);
            }
        }
    else
        {
        SETSIZEL(m_szl, 30, 30);
        m_ptPick.x=0;
        m_ptPick.y=0;
        m_fe.cfFormat=0;
        }

    //Bring the document window up front, show what a drop will do.
    hWnd=m_pDoc->Window();
    BringWindowToTop(hWnd);
    UpdateWindow(hWnd);

    ppg->m_uVScrollCode=NOVALUE;
    ppg->m_uHScrollCode=NOVALUE;
    m_fPendingRepaint=FALSE;

    pt.x-=m_ptPick.x;
    pt.y-=m_ptPick.y;

    m_ptLast=pt;
    m_fFeedback=TRUE;
    ppg->DrawDropTargetRect(&pt, &m_szl);

    return NOERROR;
    }

/*
 * CDropTarget::DragOver
 *
 * Purpose:
 *  Indicates that the mouse was moved inside the window represented
 *  by this drop target.  This happens on every WM_MOUSEMOVE, so
 *  this function should be very efficient.
 *
 * Parameters:
 *  grfKeyState     DWORD providing the current keyboard and
 *                  mouse states
 *  pt              POINTL where the mouse currently is.
 *  pdwEffect       LPDWORD in which to store the effect flag
 *                  for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragOver(DWORD grfKeyState, POINTL pt
    , LPDWORD pdwEffect)
    {
    PCPages     ppg=m_pDoc->m_pPG;
    UINT        uRet, uLast;
    UINT        xPos, yPos;

    *pdwEffect=DROPEFFECT_NONE;

    if (NULL==m_pIDataObject)
        return NOERROR;

    //Check if this is still a valid point.  uRet used below as well
    uRet=ppg->UTestDroppablePoint(&pt);

    if (UDROP_NONE==uRet)
        *pdwEffect=DROPEFFECT_NONE;
    else
        {
        //Store these before possibly ORing in DROPEFFECT_SCROLL
        *pdwEffect=DROPEFFECT_MOVE;

        if (grfKeyState & MK_CONTROL)
            *pdwEffect=DROPEFFECT_COPY;
        }

    //If we haven't moved and we are not scrolling, then we're done.
    if ((pt.x-m_ptPick.x==m_ptLast.x)
        && (pt.y-m_ptPick.y==m_ptLast.y)
        && !((UDROP_INSETHORZ|UDROP_INSETVERT) & ppg->m_uLastTest))
        {
        return NOERROR;
        }

    //Remove the last feedback rectangle.
    if (m_fFeedback)
        ppg->DrawDropTargetRect(&m_ptLast, &m_szl);

    uLast=ppg->m_uLastTest;
    ppg->m_uLastTest=uRet;

    if (UDROP_NONE==uRet)
        {
        //If we're now an invalid point, better repaint as necessary
        if (m_fPendingRepaint)
            {
            UpdateWindow(ppg->m_hWnd);
            m_fPendingRepaint=FALSE;
            }

        ppg->m_uVScrollCode=NOVALUE;
        ppg->m_uHScrollCode=NOVALUE;
        m_fFeedback=FALSE;
        return NOERROR;
        }

    /*
     * Scrolling is a little tricky:  We get a DragOver pulse even
     * if we didn't move.  First we have to delay scrolling for
     * ppg->m_uScrollDelay clock ticks which we can determine using
     * GetTickCount.  Timers do not work here since we may not be
     * yielding to our message loop.
     *
     * Once we know we are scrolling then we determine if we
     * scroll again or if we reset the scrolling state.
     */

    if ((UDROP_INSETHORZ & uLast) && !(UDROP_INSETHORZ & uRet))
        ppg->m_uHScrollCode=NOVALUE;

    if (!(UDROP_INSETHORZ & uLast) && (UDROP_INSETHORZ & uRet))
        {
        ppg->m_dwTimeLast=GetTickCount();
        ppg->m_uHScrollCode=(0!=(UDROP_INSETLEFT & uRet))
            ? SB_LINELEFT : SB_LINERIGHT; //Same as UP & DOWN codes.
        }

    if ((UDROP_INSETVERT & uLast) && !(UDROP_INSETVERT & uRet))
        ppg->m_uVScrollCode=NOVALUE;

    if (!(UDROP_INSETVERT & uLast) && (UDROP_INSETVERT & uRet))
        {
        ppg->m_dwTimeLast=GetTickCount();
        ppg->m_uVScrollCode=(0!=(UDROP_INSETTOP & uRet))
            ? SB_LINEUP : SB_LINEDOWN;
        }

    //Only change the last time if ALL scrolling stops.
    if (NOVALUE==ppg->m_uHScrollCode && NOVALUE==ppg->m_uVScrollCode)
        ppg->m_dwTimeLast=0L;

    //Set the scroll effect on any inset hit.
    if ((UDROP_INSETHORZ | UDROP_INSETVERT) & uRet)
        *pdwEffect |= DROPEFFECT_SCROLL;

    xPos=ppg->m_xPos;
    yPos=ppg->m_yPos;

    //Has the delay elapsed?  We can scroll if so
    if (ppg->m_dwTimeLast!=0
        && (GetTickCount()-ppg->m_dwTimeLast)
        > (DWORD)ppg->m_uScrollDelay)
        {
        if (NOVALUE!=ppg->m_uHScrollCode)
            {
            m_fPendingRepaint=TRUE;
            SendMessage(ppg->m_hWnd, WM_HSCROLL
                , ppg->m_uHScrollCode, 0L);
            }

        if (NOVALUE!=ppg->m_uVScrollCode)
            {
            m_fPendingRepaint=TRUE;
            SendMessage(ppg->m_hWnd, WM_VSCROLL
                , ppg->m_uVScrollCode, 0L);
            }
        }

    //If we didn't scroll but have a pending repaint, do it now.
    if (xPos==ppg->m_xPos && yPos==ppg->m_yPos && m_fPendingRepaint)
        {
        UpdateWindow(ppg->m_hWnd);
        m_fPendingRepaint=FALSE;
        }

    pt.x-=m_ptPick.x;
    pt.y-=m_ptPick.y;

    m_ptLast=pt;
    m_fFeedback=TRUE;
    ppg->DrawDropTargetRect(&pt, &m_szl);

    return NOERROR;
    }

/*
 * CDropTarget::DragLeave
 *
 * Purpose:
 *  Informs the drop target that the operation has left its window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragLeave(void)
    {
    PCPages         ppg=m_pDoc->m_pPG;

    if (NULL==m_pIDataObject)
        return NOERROR;

    //Stop scrolling
    ppg->m_uHScrollCode=NOVALUE;
    ppg->m_uVScrollCode=NOVALUE;

    if (m_fPendingRepaint)
        UpdateWindow(ppg->m_hWnd);

    //Remove the last feedback rectangle.
    if (m_fFeedback)
        ppg->DrawDropTargetRect(&m_ptLast, &m_szl);

    m_fFeedback=FALSE;
    m_pIDataObject->Release();
    return NOERROR;
    }

/*
 * CDropTarget::Drop
 *
 * Purpose:
 *  Instructs the drop target to paste the data that was just now
 *  dropped on it.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT from which we'll paste.
 *  grfKeyState     DWORD providing current keyboard/mouse state.
 *  pt              POINTL at which the drop occurred.
 *  pdwEffect       LPDWORD in which to store what you did.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::Drop(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    PCPages         ppg=m_pDoc->m_pPG;
    BOOL            fRet=TRUE;
    FORMATETC       fe;
    TENANTTYPE      tType;
    PATRONOBJECT    po;
    POINT           ptS;

    *pdwEffect=DROPEFFECT_NONE;

    if (NULL==m_pIDataObject)
        return ResultFromScode(E_FAIL);

    if (UDROP_NONE==ppg->UTestDroppablePoint(&pt))
        return ResultFromScode(E_FAIL);

    //Stop scrolling
    ppg->m_uHScrollCode=NOVALUE;
    ppg->m_uVScrollCode=NOVALUE;

    if (m_fPendingRepaint)
        UpdateWindow(ppg->m_hWnd);

    //2.  Remove the UI feedback
    if (m_fFeedback)
        ppg->DrawDropTargetRect(&m_ptLast, &m_szl);

    m_pIDataObject->Release();

    /*
     * Check if we can do the paste, and if so, tell our pasting
     * mechanism exactly where to place us.
     */
    pt.x-=m_ptPick.x;
    pt.y-=m_ptPick.y;

    POINTFROMPOINTL(ptS, pt);
    ScreenToClient(ppg->Window(), &ptS);
    POINTLFROMPOINT(po.ptl, ptS);

    //This is true if we didn't see placement data in DragEnter
    if (0!=m_fe.cfFormat)
        {
        po.szl.cx=m_szl.cx;         //We stored these positive
        po.szl.cy=-m_szl.cy;
        }
    else
        SETSIZEL(po.szl, 0, 0); //Ask object for its size.

    //Adjust for scrolling and mapping mode.
    ppg->AdjustPosition(&po.ptl, &po.szl);

    /*
     * If we're in the same document and moving, then we can just
     * stuff the Pages' m_ptDrop which will move us and return.
     */
    if (ppg->m_fDragSource && !(grfKeyState & MK_CONTROL))
        {
        *pdwEffect=DROPEFFECT_MOVE;
        ppg->m_fMoveInPage=TRUE;
        ppg->m_ptDrop=po.ptl;
        return NOERROR;
        }

    /*
     * Otherwise, paste either from another document or from
     * the same document which will always be a copy to the new
     * point.
     */

    ppg->m_fMoveInPage=FALSE;
    fRet=m_pDoc->FQueryPasteFromData(pIDataSource, &fe, &tType);

    if (fRet)
        {
        //Copy the real format if we have placement data.
        po.fe=(m_pDoc->m_cf==fe.cfFormat) ? m_fe : fe;

        fRet=m_pDoc->PasteFromData(pIDataSource, &fe, tType
            , &po, 0);
        }

    if (!fRet)
        return ResultFromScode(E_FAIL);

    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        *pdwEffect=DROPEFFECT_COPY;

    return NOERROR;
    }


PAGE.CPP

/*
 * PAGE.CPP
 * Patron Chapter 13
 *
 * Implementation of parts of the CPage class; those member
 * functions dealing with mouse events are in PAGEMOUS.CPP.
 *
 * Copyright (c)1993-1997 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPage::CPage
 * CPage::~CPage
 *
 * Constructor Parameters:
 *  dwID            DWORD identifier for this page.
 *  hWnd            HWND of the pages window (for repaints, etc).
 *  pPG             PCPages to the Pages window.
 */

CPage::CPage(DWORD dwID, HWND hWnd, PCPages pPG)
    {
    m_dwID     =dwID;
    m_pIStorage=NULL;

    m_cOpens=0;
    m_hWnd=hWnd;
    m_pPG=pPG;

    m_dwIDNext      =0;
    m_cTenants      =0;
    m_hWndTenantList=NULL;
    m_iTenantCur    =NOVALUE;   //Tenants are zero indexed.
    m_pTenantCur    =NULL;

    m_uHTCode=HTNOWHERE;
    m_uSizingFlags=0;
    m_fTracking=FALSE;
    m_hDC=NULL;

    m_fDragPending=FALSE;
    m_fSizePending=FALSE;
    m_fTimer=FALSE;

    //Get WIN.INI distance and delay values, with OLE defaults.
    m_cxyDist=GetProfileInt(TEXT("windows"), TEXT("DragMinDist")
        , DD_DEFDRAGMINDIST);
    m_cDelay=GetProfileInt(TEXT("windows"), TEXT("DragDelay")
        , DD_DEFDRAGDELAY);

    m_fReopen=FALSE;

    //CHAPTER13MOD
    m_pmkFile=m_pPG->m_pmkFile;

    if (NULL!=m_pmkFile)
        m_pmkFile->AddRef();

    m_cRef=0L;
    m_pIOleItemContainer=NULL;
    //End CHAPTER13MOD

    return;
    }

CPage::~CPage(void)
    {
    if (m_fTimer)
        KillTimer(m_hWnd, IDTIMER_DEBOUNCE);

    m_hWnd=NULL;
    Close(FALSE);
    return;
    }

//CHAPTER13MOD
/*
 * CPage::QueryInterface
 * CPage::AddRef
 * CPage::Release
 *
 * Purpose:
 *  IUnknown members for CPage object.
 */

STDMETHODIMP CPage::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    //Any interface on this object is the object pointer.
    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IOleItemContainer==riid || IID_IOleContainer==riid
        || IID_IParseDisplayName==riid)
        *ppv=m_pIOleItemContainer;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return E_NOINTERFACE;
    }

STDMETHODIMP_(ULONG) CPage::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPage::Release(void)
    {
    ULONG           cRefT;

    cRefT=--m_cRef;

    if (0L==m_cRef)
        delete this;

    return cRefT;
    }

//End CHAPTER13MOD

/*
 * CPage::GetID
 *
 * Return Value:
 *  DWORD           dwID field in this page.  This function is only
 *                  here to avoid hiding inline implementations
 *                  in pages.h
 */

DWORD CPage::GetID(void)
    {
    return m_dwID;
    }

/*
 * CPage::FOpen
 *
 * Purpose:
 *  Retrieves the IStorage associated with this page.  The IStorage
 *  is owned by the page and thus the page always holds a reference
 *  count.  The caller should call Close or delete this page to
 *  match this open.
 *
 *  This function may be called multiple times resulting in
 *  additional reference counts on the storage each of which must be
 *  matched with a call to Close.  The last Close can be done
 *  through delete.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which this page lives.
 *
 * Return Value:
 *  BOOL            TRUE if opening succeeds, FALSE otherwise.
 */

BOOL CPage::FOpen(LPSTORAGE pIStorage)
    {
    HRESULT                 hr=NOERROR;
    LPSTREAM                pIStream;
    DWORD                   dwMode;
    TCHAR                   szTemp[32];
    BOOL                    fNew;
    BOOL                    fCreated=FALSE;
    TENANTLIST              tl;
    PTENANTINFO             pti;
    ULONG                   cb;
    LPMALLOC                pIMalloc;
    UINT                    i;
    PCTenant                pTenant;
    UINT                    cLinks;
    LPOLELINK               pIOleLink;
    LPUNKNOWN               pIUnknown;
    UINT                    uRet;
    OLEUIEDITLINKS          el;
    PCIOleUILinkContainer   pIUILinks;
    HWND                    hWndDoc;

    fNew=(NULL==m_pIStorage);

    if (!fNew)
        {
        m_cOpens++;
        m_pIStorage->AddRef();
        return TRUE;
        }

    if (NULL==pIStorage)
        return FALSE;

    /*
     * Attempt to open the storage under this ID.  If none,
     * create one.  In either case, the IStorage is either
     * saved in pPage or released.
     */

    GetStorageName(szTemp);
    dwMode=STGM_TRANSACTED | STGM_READWRITE | STGM_SHARE_EXCLUSIVE;

#if defined(WIN32) && !defined(UNICODE)
    OLECHAR pwcsFile[MAX_PATH];
    mbstowcs(pwcsFile, szTemp, MAX_PATH);
#else
    OLECHAR * pwcsFile = szTemp;
#endif
    hr=pIStorage->OpenStorage(pwcsFile, NULL, dwMode, NULL, 0
        , &m_pIStorage);

    if (FAILED(hr))
        {
        hr=pIStorage->CreateStorage(pwcsFile, dwMode, 0, 0
            , &m_pIStorage);
        fCreated=TRUE;
        }

    if (FAILED(hr))
        return FALSE;

    m_cOpens++;

    if (NULL==m_hWndTenantList)
        {
        /*
         * The first time we open this page, create the hidden
         * listbox we'll use to track tenants.  We give it the
         * owner-draw style so we can just store pointers in it.
         */
        m_hWndTenantList=CreateWindow(TEXT("listbox")
            , TEXT("Tenant List"), WS_POPUP | LBS_OWNERDRAWFIXED
            , 0, 0, 100, 100, HWND_DESKTOP, NULL
            , m_pPG->m_hInst, NULL);

        if (NULL==m_hWndTenantList)
            return FALSE;
        }

    //CHAPTER13MOD
    m_pIOleItemContainer=new CImpIOleItemContainer(this, this
        , FALSE);

    if (NULL==m_pIOleItemContainer)
        return FALSE;
    //End CHAPTER13MOD

    //If this is brand-new, we're done.
    if (fCreated)
        return TRUE;

    /*
     * Now open the stream we saved in Close and load all the
     * tenants listed in there.  If there are none, then we don't
     * have to load squat.
     */

    hr=m_pIStorage->OpenStream(SZSTREAMTENANTLIST, NULL, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pIStream->Read(&tl, sizeof(tl), NULL);
        m_cTenants=tl.cTenants;
        m_dwIDNext=tl.dwIDNext;
        m_iTenantCur=0;

        cb=tl.cTenants*sizeof(TENANTINFO);

        if (0!=cb)
            {
            pti=(PTENANTINFO)pIMalloc->Alloc(cb);

            if (NULL!=pti)
                {
                pIStream->Read(pti, cb, NULL);

                for (i=0; i < m_cTenants; i++)
                    {
                    if (FTenantAdd(NOVALUE, (pti+i)->dwID, &pTenant))
                        {
                        pTenant->FLoad(m_pIStorage, (pti+i));

                        //Make sure it knows about the show state.
                        pTenant->ShowObjectType(m_pPG->m_fShowTypes);
                        }
                    }

                pIMalloc->Free(pti);
                }
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    //Get and select the first tenant
    if (FTenantGet(0, &m_pTenantCur, FALSE))
        m_pTenantCur->Select(TRUE);

    //If we just saved and closed, don't bother with updating links
    if (m_fReopen)
        {
        m_fReopen=FALSE;
        return TRUE;
        }

    /*
     * Update all the links in this page, showing the progress
     * indicator as it's happening.  We use the same
     * IOlUILinkContainer implementation as we do for the links
     * dialog, passing it to OleUIUpdateLinks which does everything
     * for us.
     *
     * We might also optimize this to not do anything if there are
     * no automatic links, but it's not a big concern.
     */

    //First, count the number of automatic links.
    cLinks=0;

    for (i=0; i < m_cTenants; i++)
        {
        if (FTenantGet(i, &pTenant, FALSE))
            {
            DWORD       dw;

            pTenant->ObjectGet(&pIUnknown);
            hr=pIUnknown->QueryInterface(IID_IOleLink
                , (PPVOID)&pIOleLink);
            pIUnknown->Release();

            if (FAILED(hr))
                continue;

            pIOleLink->GetUpdateOptions(&dw);
            pIOleLink->Release();

            if (OLEUPDATE_ALWAYS==dw)
                cLinks++;
            }
        }

    //If we have any automatic links, invoke the update dialog.
    if (0==cLinks)
        return TRUE;

    //Create an IOleUILinkContainer instantiation.
    if (!m_pPG->FGetUILinkContainer(&pIUILinks))
        return TRUE;    //Guess we can't update, oh well.

    hWndDoc=GetParent(m_hWnd);
    LoadString(m_pPG->m_hInst, IDS_CAPTION, szTemp, sizeof(szTemp));

    if (!OleUIUpdateLinks(pIUILinks, hWndDoc, szTemp, cLinks))
        {
        /*
         * If updating failed, ask to show the links dialog.  NOTE:
         * OleUIPromptUser has a variable wsprintf argument list
         * after the hWnd parameter!  Use appropriate typecasting!
         */
        uRet=OleUIPromptUser(IDD_CANNOTUPDATELINK, hWndDoc, szTemp);

        if (ID_PU_LINKS==uRet)
            {
            //Throw up the links dialog.
            memset(&el, 0, sizeof(el));
            el.cbStruct=sizeof(el);
            el.hWndOwner=hWndDoc;
            el.lpOleUILinkContainer=pIUILinks;
            OleUIEditLinks(&el);
            }
        }

    m_pPG->m_fDirty=pIUILinks->m_fDirty;
    pIUILinks->Release();
    return TRUE;
    }

/*
 * CPage::Close
 *
 * Purpose:
 *  Possibly commits the storage, then releases it reversing the
 *  reference count from FOpen.
 *
 * Parameters:
 *  fCommit         BOOL indicating if we're to commit.
 *
 * Return Value:
 *  None
 */

void CPage::Close(BOOL fCommit)
    {
    if (NULL==m_pIStorage)
        return;

    if (fCommit)
        Update();

    m_pIStorage->Release();

    //If this was the last close, make all tenants loaded->passive
    if (0==--m_cOpens)
        {
        UINT        i;
        PCTenant    pTenant;

        m_pIStorage=NULL;

        for (i=0; i < m_cTenants; i++)
            {
            if (FTenantGet(i, &pTenant, FALSE))
                {
                if (NULL!=m_hWnd)
                    {
                    //FOpen may select again, so this repaints.
                    pTenant->Select(FALSE);
                    }

                pTenant->Close(FALSE);
                pTenant->Release();
                }
            }

        DestroyWindow(m_hWndTenantList);
        m_hWndTenantList=NULL;
        m_fReopen=TRUE;

        //CHAPTER13MOD
        if (NULL!=m_pmkFile)
            m_pmkFile->Release();

        m_pmkFile=NULL;

        if (NULL!=m_pIOleItemContainer)
            delete m_pIOleItemContainer;

        m_pIOleItemContainer=NULL;
        //End CHAPTER13MOD
        }

    return;
    }

/*
 * CPage::Update
 *
 * Purpose:
 *  Forces a common on the page if it's open.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if there are any open objects on this page,
 *                  that is, we should remain open.
 */

BOOL CPage::Update(void)
    {
    BOOL            fOpen=FALSE;
    UINT            i;
    PCTenant        pTenant;
    HRESULT         hr;
    LPSTREAM        pIStream;
    TENANTLIST      tl;
    PTENANTINFO     pti;
    ULONG           cb;
    LPMALLOC        pIMalloc;

    //Walk the list of objects and update them all as well.
    for (i=0; i < m_cTenants; i++)
        {
        if (FTenantGet(i, &pTenant, FALSE))
            fOpen |= pTenant->Update();
        }

    //Now write our own stream containing the tenant list.
    hr=m_pIStorage->CreateStream(SZSTREAMTENANTLIST, STGM_CREATE
        | STGM_WRITE| STGM_DIRECT | STGM_SHARE_EXCLUSIVE, 0, 0
        , &pIStream);

    if (FAILED(hr))
        return fOpen;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        tl.cTenants=m_cTenants;
        tl.dwIDNext=m_dwIDNext;

        pIStream->Write(&tl, sizeof(TENANTLIST), &cb);

        cb=m_cTenants*sizeof(TENANTINFO);
        pti=(PTENANTINFO)pIMalloc->Alloc(cb);

        if (NULL!=pti)
            {
            for (i=0; i < m_cTenants; i++)
                {
                FTenantGet(i, &pTenant, FALSE);
                pTenant->GetInfo((pti+i));
                }

            pIStream->Write(pti, cb, &cb);
            pIMalloc->Free(pti);
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    //Now commit the whole mess and we're done
    if (NULL!=m_pIStorage)
        m_pIStorage->Commit(STGC_ONLYIFCURRENT);

    return fOpen;
    }

/*
 * CPage::Destroy
 *
 * Purpose:
 *  Removes this page from the given storage.  The caller should
 *  eventually delete this Page object to free the storage.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE contianing this page on which to call
 *                  DestroyElement
 *
 * Return Value:
 *  None
 */

void CPage::Destroy(LPSTORAGE pIStorage)
    {
    if (NULL!=pIStorage)
        {
        TCHAR   szTemp[32];

        Close(FALSE);
        GetStorageName(szTemp);
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsTemp[32];
        mbstowcs(pwcsTemp, szTemp, 32);
        pIStorage->DestroyElement(pwcsTemp);
#else
        pIStorage->DestroyElement(szTemp);
#endif
        }

    return;
    }

/*
 * CPage::GetStorageName
 *
 * Parameters:
 *  pszName         LPTSTR to a buffer in which to store the storage
 *                  name for this page.
 *
 * Return Value:
 *  UINT            Number of characters stored.
 */

UINT CPage::GetStorageName(LPTSTR pszName)
    {
    return wsprintf(pszName, TEXT("Page %lu"), m_dwID);
    }

/*
 * CPage::Draw
 *
 * Purpose:
 *  Draws the objects on this page to the given hDC.
 *
 * Parameters:
 *  hDC             HDC on which to draw.
 *  xOff, yOff      int offsets for the page.
 *  fNoColor        BOOL indicating black & white screen rendering.
 *  fPrinter        BOOL indicating hDC is on the printer.
 *
 * Return Value:
 *  None
 */

void CPage::Draw(HDC hDC, int xOff, int yOff, BOOL fNoColor
    , BOOL fPrinter)
    {
    int                 i;
    PCTenant            pTenant;
    HDC                 hIC=NULL;
    PCOMBINEDEVICE      pcd=NULL;
    DVTARGETDEVICE     *ptd=NULL;

    /*
     * If printing, tell the tenant to forget the borders. Otherwise
     * we leave xOff and yOff the same to account for scrolling.
     */
    if (fPrinter)
        {
        xOff=LOMETRIC_BORDER+m_pPG->m_xMarginLeft;
        yOff=-LOMETRIC_BORDER-m_pPG->m_yMarginTop;

        /*
         * Get device information.  If this fails, ptd is
         * NULL which is acceptable.
         */
        if (m_pPG->DevReadConfig(&pcd, &hIC))
            ptd=&(pcd->td);
        }

    for (i=(int)m_cTenants-1; i >=0; i--)
        {
        if (FTenantGet(i, &pTenant, FALSE))
            {
            RECT        rc, rcWin;
            RECTL       rcl;

            //Paint this tenant only if visible.
            pTenant->RectGet(&rcl, TRUE);
            RECTFROMRECTL(rc, rcl);
            OffsetRect(&rc, -(int)m_pPG->m_xPos
                , -(int)m_pPG->m_yPos);
            GetClientRect(m_hWnd, &rcWin);

            if (IntersectRect(&rc, &rc, &rcWin))
                {
                pTenant->Draw(hDC, ptd, hIC, xOff, yOff
                    , fNoColor, fPrinter);
                }
            }
        }

    //Free whatever CPages::DevReadConfig returned.
    if (NULL!=pcd)
        {
        LPMALLOC    pIMalloc;

        if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
            {
            pIMalloc->Free(pcd);
            pIMalloc->Release();
            }
        }

    if (NULL!=hIC)
        DeleteDC(hIC);

    return;
    }

/*
 * CPage::TenantCreate
 *
 * Purpose:
 *  Creates a new tenant of a specific type.
 *
 * Parameters:
 *  tType           TENANTTYPE to create.
 *  pv              LPVOID providing information for the new
 *                  object creation.
 *  pFE             LPFORMATETC describing how we want this
 *                  rendered.
 *  ppo             PPATRONOBJECT with placement data.
 *  dwData          DWORD extra data to pass to the tenant.
 *
 * Return Value:
 *  None
 */

BOOL CPage::TenantCreate(TENANTTYPE tType, LPVOID pv
    , LPFORMATETC pFE, PPATRONOBJECT ppo, DWORD dwData)
    {
    PCTenant    pTenant;
    UINT        uRet;
    int         x, y;
    int         h, v;
    POINTL      ptl;
    SIZEL       szl;
    RECTL       rcl;
    RECT        rc;

    //New tenants go at top of the pile; zero index to FTenantAdd.
    if (!FTenantAdd(0, m_dwIDNext, &pTenant))
        return FALSE;

    uRet=pTenant->UCreate(tType, pv, pFE, &ptl, &szl, m_pIStorage
        , ppo, dwData);

    if (UCREATE_FAILED==uRet)
        {
        //Reverse UCreate AND FTenantAdd
        SendMessage(m_hWndTenantList, LB_DELETESTRING, 0, 0L);
        pTenant->Destroy(m_pIStorage);

        pTenant->Release();
        return FALSE;
        }

    m_dwIDNext++;
    m_cTenants++;

    if (NULL!=m_pTenantCur)
        m_pTenantCur->Select(FALSE);

    m_iTenantCur=0;             //First one in the list now.
    m_pTenantCur=pTenant;

    //Tell the tenant where it lives, default is (0,0) in print area
    x=LOMETRIC_BORDER+m_pPG->m_xMarginLeft;
    y=-LOMETRIC_BORDER-m_pPG->m_yMarginTop;

    h=x;
    v=y;

    if (UCREATE_PLACEDOBJECT==uRet)
        {
        SetRect(&rc, 3*CXYHANDLE, 3*CXYHANDLE, 0, 0);
        RectConvertMappings(&rc, NULL, FALSE);

        //Make sure place point is on page, otherwise go to (0,0)
        if (((int)ptl.x > x)
            && ((int)ptl.x < x+(int)m_pPG->m_cx-rc.left))
            x=(int)ptl.x;

        //m_pPG->m_cy is absolute
        if (((int)ptl.y < y)
            && ((int)ptl.y > y-(int)m_pPG->m_cy-rc.top))
            y=(int)ptl.y;
        }

    //Bounds check size of the object and fit to page as necessary.
    if (x+(int)szl.cx > (int)(h+m_pPG->m_cx))
        szl.cx=h+m_pPG->m_cx-x;

    //Remember that szl we get from UCreate is absolute
    if (y-(int)szl.cy < (int)(v-m_pPG->m_cy))
        szl.cy=-(int)(v-m_pPG->m_cy-y);

    SETRECTL(rcl, x, y, x+szl.cx, y-szl.cy);
    m_pTenantCur->RectSet(&rcl, FALSE, TRUE);

    //Force a repaint on this new guy
    m_pTenantCur->Invalidate();
    UpdateWindow(m_hWnd);

    m_pTenantCur->Select(TRUE);

    //Make sure this new tenant knows about showing it's type.
    m_pTenantCur->ShowObjectType(m_pPG->m_fShowTypes);

    //CHAPTER13MOD
    //New tenants must know the available monikers
    if (NULL!=m_pmkFile)
        {
        LPBC        pbc;
        LPMONIKER   pmkPage;
        TCHAR       szTemp[32];
        LPOLESTR    pszFile;
        LPMALLOC    pIMalloc;

        GetStorageName(szTemp);
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsTemp[32];
        mbstowcs(pwcsTemp, szTemp, 32);
        CreateItemMoniker(OLESTR("!"), pwcsTemp, &pmkPage);
#else
        CreateItemMoniker(OLESTR("!"), szTemp, &pmkPage);
#endif
        //We can get the filename from our file moniker
        CreateBindCtx(0, &pbc);
        m_pmkFile->GetDisplayName(pbc, NULL, &pszFile);
        pbc->Release();
#if defined(WIN32) && !defined(UNICODE)
        TCHAR pszTemp[MAX_PATH];
        wcstombs(pszTemp, pszFile, MAX_PATH);
        pTenant->NotifyOfRename(pszTemp, m_pmkFile, pmkPage);
#else
        pTenant->NotifyOfRename(pszFile, m_pmkFile, pmkPage);
#endif
        pmkPage->Release();

        if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
            {
            pIMalloc->Free(pszFile);
            pIMalloc->Release();
            }
        }
    //End CHAPTER13MOD

    //Activate new objects immediately and force a save on them
    if (TENANTTYPE_EMBEDDEDOBJECT==tType)
        {
        m_pTenantCur->Activate(OLEIVERB_SHOW);
        m_pTenantCur->Update();
        }

    return TRUE;
    }

/*
 * CPage::TenantDestroy
 *
 * Purpose:
 *  Destroys the currently selected tenant on this page.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPage::TenantDestroy(void)
    {
    if (NULL==m_pTenantCur)
        {
        MessageBeep(0);
        return FALSE;
        }

    SendMessage(m_hWndTenantList, LB_DELETESTRING
        , m_iTenantCur, 0L);

    m_pTenantCur->Invalidate();
    m_pTenantCur->Destroy(m_pIStorage);

    m_pTenantCur->Release();
    m_pTenantCur=NULL;

    //Update counts, etc., and select the next tenant in the list.
    if (m_iTenantCur==m_cTenants-1)
        m_iTenantCur--;

    if (0==--m_cTenants)
        m_pTenantCur=NULL;
    else
        {
        FTenantGet(m_iTenantCur, &m_pTenantCur, TRUE);
        m_pTenantCur->Select(TRUE);
        }

    UpdateWindow(m_hWnd);
    return TRUE;
    }

/*
 * CPage::TenantClip
 *
 * Purpose:
 *  Copies or cuts the currently selected tenant to the clipoard.
 *
 * Parameters:
 *  fCut            BOOL TRUE to cut the object, FALSE to copy.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPage::TenantClip(BOOL fCut)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet=FALSE;

    if (NULL==m_pTenantCur)
        return FALSE;

    /*
     * To perform a data transfer operation, we need to create a
     * data object with the selected object's data inside. To do
     * this we CoCreateInstance on CLSID_DataTransferObject
     * (Also implemented in this chapter), retrieve data from the
     * object we have, stuff that data into the transfer object,
     * then stick that object on the clipboard.
     *
     * Since we'll want an identical object at other times, like for
     * drag-drop, we use a private function to actually create it.
     */

    pIDataObject=TransferObjectCreate(NULL);

    if (NULL!=pIDataObject)
        {
        if (SUCCEEDED(OleSetClipboard(pIDataObject)))
            {
            if (fCut)
                TenantDestroy();

            fRet=TRUE;
            }

        pIDataObject->Release();
        }

    return fRet;
    }

/*
 * CPage::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected on this
 *  page for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU of the Edit menu.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPage::FQueryObjectSelected(HMENU hMenu)
    {
    HMENU       hMenuTemp;

    /*
     * This will only be called on WM_INITMENUPOPUP, we'll also
     * use this function to create the Verb menu for this object.
     */
    if (NULL!=m_pTenantCur)
        {
        m_pTenantCur->AddVerbMenu(hMenu, MENUPOS_OBJECT);
        return TRUE;
        }

    OleUIAddVerbMenu(NULL, NULL, hMenu, MENUPOS_OBJECT
        , IDM_VERBMIN, IDM_VERBMAX, FALSE, 0, &hMenuTemp);

    return FALSE;
    }

/*
 * CPage::ActivateObject
 *
 * Purpose:
 *  Executes a verb on the currently selected object.
 *
 * Parameters:
 *  iVerb           LONG of the selected verb.
 *
 * Return Value:
 *  None
 */

void CPage::ActivateObject(LONG iVerb)
    {
    if (NULL==m_pTenantCur)
        return;

    m_pTenantCur->Activate(iVerb);
    return;
    }

/*
 * CPage::ShowObjectTypes
 *
 * Purpose:
 *  Loops through all the tenants and tells each one to turn on or
 *  off the Show Objects features.
 *
 * Parameters:
 *  fShow           BOOL indicating to show the type or not.
 *
 * Return Value:
 *  None
 */

void CPage::ShowObjectTypes(BOOL fShow)
    {
    PCTenant    pTenant;
    UINT        i;

    for (i=0; i < m_cTenants; i++)
        {
        if (FTenantGet(i, &pTenant, FALSE))
            pTenant->ShowObjectType(fShow);
        }

    return;
    }

/*
 * CPage::NotifyTenantsOfRename
 *
 * Purpose:
 *  Loops through all the tenants and informs each of the new
 *  document name.
 *
 * Parameters:
 *  pszFile         LPTSTR of the new filename.
 *  pmk             LPMONKIER for the new filename.
 *
 * Return Value:
 *  None
 */

void CPage::NotifyTenantsOfRename(LPTSTR pszFile, LPMONIKER pmk)
    {
    //CHAPTER13MOD
    PCTenant    pTenant;
    UINT        i;
    LPMONIKER   pmkPage;
    TCHAR       szTemp[32];

    //Save the file moniker
    if (NULL!=m_pmkFile)
        m_pmkFile->Release();

    m_pmkFile=pmk;
    m_pmkFile->AddRef();

    //Create a page moniker to send to the tenants.
    GetStorageName(szTemp);
#if defined(WIN32) && !defined(UNICODE)
    OLECHAR pwcsTemp[32];
    mbstowcs(pwcsTemp, szTemp, 32);
    CreateItemMoniker(OLESTR("!"), pwcsTemp, &pmkPage);
#else
    CreateItemMoniker(OLESTR("!"), szTemp, &pmkPage);
#endif
    for (i=0; i < m_cTenants; i++)
        {
        if (FTenantGet(i, &pTenant, FALSE))
            pTenant->NotifyOfRename(pszFile, pmk, pmkPage);
        }

    //If anything held onto this, they AddRef'd
    pmkPage->Release();
    //End CHAPTER13MOD
    return;
    }

/*
 * CPages::FQueryLinksInPage
 *
 * Purpose:
 *  Pass through to current page to see if there are any linked
 *  objects.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPage::FQueryLinksInPage()
    {
    PCTenant    pTenant;
    UINT        i;
    BOOL        fRet=FALSE;

    for (i=0; i < m_cTenants; i++)
        {
        if (FTenantGet(i, &pTenant, FALSE))
            fRet |= (pTenant->TypeGet()==TENANTTYPE_LINKEDOBJECT);
        }

    return fRet;
    }

/*
 * CPages::FTenantGet
 * (Protected)
 *
 * Purpose:
 *  Returns a tenant of a given index returning a BOOL so it's
 *  simple to use this function inside if statements.
 *
 * Parameters:
 *  iTenant         UINT tenant to retrieve 0 based.
 *  ppTenant        PCPage * in which to return the tenant
 *                  pointer
 *  fOpen           BOOL indicating if we should open this
 *                  tenant as well.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPage::FTenantGet(UINT iTenant, PCTenant *ppTenant
    , BOOL fOpen)
    {
    if (NULL==ppTenant)
        return FALSE;

    if (LB_ERR!=SendMessage(m_hWndTenantList, LB_GETTEXT
        , iTenant, (LONG)ppTenant))
        {
        if (fOpen)
            (*ppTenant)->FOpen(m_pIStorage);

        return TRUE;
        }

    return FALSE;
    }

//CHAPTER13MOD
/*
 * CPage::FTenantGetFromID
 * (Protected)
 *
 * Purpose:
 *  Finds a tenant pointer from an ID for use from
 *  IOleItemContainer::GetObject
 *
 * Parameters:
 *  dwID            DWORD identifier of the tenant to find.
 *  ppTenant        PCTenant * in which to return the tenant
 *                  pointer
 *  fOpen           BOOL indicating if we should open this tenant as
 *                  well.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPage::FTenantGetFromID(DWORD dwID, PCTenant *ppTenant
    , BOOL fOpen)
    {
    UINT        i;
    PCTenant    pTenant;

    if (NULL==ppTenant)
        return FALSE;

    for (i=0; i < m_cTenants; i++)
        {
        if (!FTenantGet(i, &pTenant, FALSE))
            continue;

        if (pTenant->GetID()==dwID)
            {
            if (fOpen)
                pTenant->FOpen(m_pIStorage);

            *ppTenant=pTenant;
            return TRUE;
            }
        }

    return FALSE;
    }

//End CHAPTER13MOD

/*
 * CPage::FTenantAdd
 * (Protected)
 *
 * Purpose:
 *  Creates a new tenant initialized to the given values.  The new
 *  tenants's storage is created if it does not already exist.  If
 *  fOpenStorage is set the the tenants's storage is opened and left
 *  opened.
 *
 * Parameters:
 *  iTenant         UINT Location at which to insert tenant; new
 *                  tenant is inserted after this position.  NOVALUE
 *                  for the end.
 *  dwID            DWORD ID for this tenant.
 *  ppNew           PCTenant * in which to store the new tenant.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPage::FTenantAdd(UINT iTenant, DWORD dwID
    , PCTenant *ppNew)
    {
    PCTenant    pTenant;
    LRESULT     lr;

    if (NULL!=ppNew)
        *ppNew=NULL;

    pTenant=new CTenant(dwID, m_hWnd, m_pPG);

    if (NULL==pTenant)
        return FALSE;

    //The constructor doesn't AddRef, so we need to.
    pTenant->AddRef();

    //Now try to add to the listbox.
    lr=SendMessage(m_hWndTenantList, LB_INSERTSTRING, iTenant
        , (LONG)pTenant);

    if (lr < 0)
        {
        pTenant->Release();
        return FALSE;
        }

    *ppNew=pTenant;
    return TRUE;
    }

/*
 * CPage::TransferObjectCreate
 * (Protected)
 *
 * Purpose:
 *  Creates a DataTransferObject and stuff the current selection's
 *  data into it.
 *
 * Parameters:
 *  pptl            LPPOINTL containing the pick point in device
 *                  units applicable only to drag-drop; since
 *                  drag-drop is inherently mouse oriented, we use
 *                  device units for the point.  Ignored if NULL.
 *
 * Return Value:
 *  LPDATAOBJECT    Pointer to the object created, NULL on failure
 */

LPDATAOBJECT CPage::TransferObjectCreate(LPPOINTL pptl)
    {
    LPDATAOBJECT    pIDataObject;
    LPDATAOBJECT    pIDataT;
    PPATRONOBJECT   ppo;
    RECTL           rcl;
    LPUNKNOWN       pObj;
    FORMATETC       fe;
    STGMEDIUM       stm;
    HRESULT         hr;
    //CHAPTER13MOD
    HGLOBAL         hMem;
    //End CHAPTER13MOD

    m_pTenantCur->ObjectGet(&pObj);

    hr=CoCreateInstance(CLSID_DataTransferObject, NULL
        , CLSCTX_INPROC_SERVER, IID_IDataObject
        , (PPVOID)&pIDataObject);

    if (FAILED(hr))
        return NULL;

    //Go get the data we should hold on to.
    hr=pObj->QueryInterface(IID_IDataObject, (PPVOID)&pIDataT);

    if (FAILED(hr))
        {
        pIDataObject->Release();
        pObj->Release();
        return NULL;
        }

    //Copy from known obj into transfer obj.  Ordering is important!

    //Generate placeable object structure
    stm.tymed=TYMED_HGLOBAL;
    stm.pUnkForRelease=NULL;
    stm.hGlobal=GlobalAlloc(GHND, sizeof(PATRONOBJECT));

    if (NULL==stm.hGlobal)
        {
        pIDataObject->Release();
        pObj->Release();
        return NULL;
        }

    ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);

    m_pTenantCur->SizeGet(&ppo->szl, FALSE);
    ppo->szl.cy=-ppo->szl.cy; //Negate to make absolute size

    m_pTenantCur->RectGet(&rcl, FALSE);
    ppo->ptl.x=rcl.left;
    ppo->ptl.y=rcl.top;

    if (NULL==pptl)
        {
        ppo->ptlPick.x=0;
        ppo->ptlPick.y=0;
        }
    else
        ppo->ptlPick=*pptl;

    m_pTenantCur->FormatEtcGet(&ppo->fe, FALSE);

    //If this is a linked object, just copy a presentation
    if (TENANTTYPE_LINKEDOBJECT==m_pTenantCur->TypeGet())
        m_pTenantCur->FormatEtcGet(&ppo->fe, TRUE);

    SETDefFormatEtc(fe, m_pPG->m_cf, TYMED_HGLOBAL);
    pIDataObject->SetData(&fe, &stm, TRUE);

    /*
     * Here now we have to include CF_EMBEDDEDOBJECT and
     * CF_OBJECTDESCRIPTOR when what we have selected is, in fact,
     * a compound document object.  We'll just ask the tenant to
     * set these in pIDataObject since it knows what the object.
     * If we copy embedded object data, make sure the PATRONOBJECT
     * structure has the right format in it as well.
     */
    m_pTenantCur->CopyEmbeddedObject(pIDataObject, &ppo->fe, pptl);
    //CHAPTER13MOD
    hMem=stm.hGlobal;
    //End CHAPTER13MOD

    //Copy the actual presentation.
    m_pTenantCur->FormatEtcGet(&fe, TRUE);
    pIDataT->GetData(&fe, &stm);
    pIDataObject->SetData(&fe, &stm, TRUE);

    //CHAPTER13MOD
    //Copy a link to this tenant if it's embedded
    m_pTenantCur->CopyLinkedObject(pIDataObject, &ppo->fe, pptl);
    GlobalUnlock(hMem); //ppo
    //End CHAPTER13MOD

    pIDataT->Release();
    pObj->Release();
    return pIDataObject;    //Caller now responsible
    }


PAGEMOUS.CPP

/*
 * PAGEMOUS.CPP
 * Patron Chapter 13
 *
 * Implementation of mouse-related member functions of CPage.
 * The remainder is in PAGE.CPP.  This separate file keeps this
 * grungy hit-testing/drawing code out of our way.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

//Lookups into the array using g_rgHTCode[x+y*3] in PAGEMOUS.CPP
#define YTOP            0
#define YMID            1
#define YBOT            2
#define XLEFT           0
#define XMID            1
#define XRIGHT          2

//Values to restrict sizing in CPage::OnMouseMove
#define SIZINGTOP       0x0001
#define SIZINGBOTTOM    0x0002
#define SIZINGLEFT      0x0004
#define SIZINGRIGHT     0x0008

//This array is for hit-testing lookups
static UINT g_rgHTCode[9]={HTTOPLEFT, HTTOP, HTTOPRIGHT
    , HTLEFT, HTCLIENT, HTRIGHT, HTBOTTOMLEFT, HTBOTTOM
    , HTBOTTOMRIGHT};

//This is for restricting tracking based on the hit-test
static UINT g_rguSizingFlags[9]={SIZINGTOP | SIZINGLEFT, SIZINGTOP
    , SIZINGTOP | SIZINGRIGHT, SIZINGLEFT, 0, SIZINGRIGHT
    , SIZINGBOTTOM | SIZINGLEFT, SIZINGBOTTOM
    , SIZINGBOTTOM | SIZINGRIGHT};

/*
 * CPage::OnRightDown
 *
 * Purpose:
 *  Called when the user clicks with the right button on this
 *  page.  If there is an object here, determined by the last
 *  hit-test code, the we'll make a popup-menu for it.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if the action changed the object.
 */

BOOL CPage::OnRightDown(UINT uKeys, UINT x, UINT y)
    {
    HMENU       hMenu;
    HMENU       hMenuRes;
    HINSTANCE   hInst;
    HWND        hWndFrame, hWndT;
    POINT       pt;
    UINT        i, cItems;

    //Select the tenant under the mouse, if there is one.
    if (!FSelectTenantAtPoint(x, y))
        return FALSE;

    /*
     * Get the top-level window to which menu command will go.  This
     * will be whatever parent doesn't have a parent itself...
     */
    hWndT=GetParent(m_hWnd);

    while (NULL!=hWndT)
        {
        hWndFrame=hWndT;
        hWndT=GetParent(hWndT);
        }

    /*
     * Build a popup menu for this object with Cut, Copy, Delete,
     * and object verbs.
     */
    hInst=GETWINDOWINSTANCE(m_hWnd);    //Macro in BOOK1632.H
    hMenuRes=LoadMenu(hInst, MAKEINTRESOURCE(IDR_RIGHTPOPUPMENU));

    if (NULL==hMenuRes)
        return FALSE;

    hMenu=CreatePopupMenu();
    cItems=GetMenuItemCount(hMenuRes);

    for (i=0; i < cItems; i++)
        {
        TCHAR       szTemp[80];
        int         id, uFlags;

        GetMenuString(hMenuRes, i, szTemp, sizeof(szTemp)
            , MF_BYPOSITION);
        id=GetMenuItemID(hMenuRes, i);

        uFlags=(0==id) ? MF_SEPARATOR : MF_STRING | MF_ENABLED;
        AppendMenu(hMenu, uFlags, id, szTemp);
        }

    DestroyMenu(hMenuRes);

    //Munge the Object menu item
    m_pTenantCur->AddVerbMenu(hMenu, MENUPOS_OBJECTONPOPUP);

    //Enable or disable the Links item.
    i=FQueryLinksInPage() ? MF_ENABLED : MF_DISABLED | MF_GRAYED;
    EnableMenuItem(hMenu, IDM_EDITLINKS, i | MF_BYCOMMAND);

    SETPOINT(pt, x, y);
    ClientToScreen(m_hWnd, &pt);

    TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON
        , pt.x, pt.y, 0, hWndFrame, NULL);

    DestroyMenu(hMenu);
    return FALSE;
    }

/*
 * CPage::FSelectTenantAtPoint
 *
 * Purpose:
 *  Selects whatever tenant is at the point (x,y) if there is one,
 *  deselecting the previously selected tenant.
 *
 * Parameters:
 *  x, y            UINT coordinates of the mouse.
 *
 * Return Value:
 *  BOOL            TRUE if there is a tenant here, FALSE otherwise.
 */

BOOL CPage::FSelectTenantAtPoint(UINT x, UINT y)
    {
    UINT            iTenant;
    PCTenant        pTenant;
    PCDocument      pDoc;

    iTenant=TenantFromPoint(x, y, &pTenant);

    if (NULL==pTenant)
        return FALSE;

    //Make the document window active in any case
    pDoc=(PCDocument)SendMessage(GetParent(m_hWnd), DOCM_PDOCUMENT
        , 0, 0L);

    if (NULL!=pDoc)
        BringWindowToTop(pDoc->Window());

    //If this one is already current, we might be now sizing.
    if (pTenant==m_pTenantCur)
        return TRUE;

    //Deselect the current tenant
    if (NULL!=m_pTenantCur)
        m_pTenantCur->Select(FALSE);

    //Move this tenant to the top of the list
    m_iTenantCur=0;

    SendMessage(m_hWndTenantList, LB_DELETESTRING, iTenant, 0L);
    SendMessage(m_hWndTenantList, LB_INSERTSTRING, 0
        , (LONG)pTenant);

    //Select and repaint the new tenant to show it up front
    m_pTenantCur=pTenant;

    m_pTenantCur->Repaint();
    m_pTenantCur->Select(TRUE);

    return TRUE;
    }

/*
 * CPage::OnLeftDown
 *
 * Purpose:
 *  Called when the user clicks with the left button on this page.
 *  We find the object under that position that is visibly on top
 *  (always the first one under this location in the page list since
 *  we paint in reverse order) and select it.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if the action changed the object.
 */

BOOL CPage::OnLeftDown(UINT uKeys, UINT x, UINT y)
    {
    /*
     * If the mouse is in a position to start dragging,
     * start the timer as with sizing below.
     */
    if (HTCAPTION==m_uHTCode)
        {
        m_fDragPending=TRUE;

        //Save down point and start timer.
        m_ptDown.x=x;
        m_ptDown.y=y;

        m_uKeysDown=uKeys;

        m_fTimer=TRUE;
        SetTimer(m_hWnd, IDTIMER_DEBOUNCE, m_cDelay, NULL);
        return FALSE;
        }

    /*
     * If the mouse is in a position to start sizing, start
     * the debounce timer and note the condition.  The sizing
     * will start in OnTimer or OnMouseMove.  This will always
     * happen on the currently selected tenant, and m_uHTCode is
     * set in OnNCHitTest below.
     */
    if (HTNOWHERE!=m_uHTCode && HTCLIENT!=m_uHTCode)
        {
        m_fSizePending=TRUE;

        //Save down point and start timer.
        m_ptDown.x=x;
        m_ptDown.y=y;

        m_fTimer=TRUE;
        SetTimer(m_hWnd, IDTIMER_DEBOUNCE, m_cDelay, NULL);
        return FALSE;
        }

    FSelectTenantAtPoint(x, y);
    return FALSE;
    }

/*
 * CPage::OnLeftUp
 *
 * Purpose:
 *  Called when the user clicks up with the left button on this
 *  page. We stop tracking on this message, if necessary, and
 *  resize the object.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if this action changed the object.
 */

BOOL CPage::OnLeftUp(UINT uKeys, UINT x, UINT y)
    {
    RECT    rc, rcT;

    if (m_fSizePending || m_fDragPending)
        {
        m_fSizePending=FALSE;
        m_fDragPending=FALSE;

        if (m_fTimer)
            {
            KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
            m_fTimer=FALSE;
            }

        return FALSE;
        }

    if (!m_fTracking)
        return FALSE;

    //Remove the dotted rectangle.
    RECTFROMRECTL(rc, m_rcl)
    DrawFocusRect(m_hDC, &rc);
    ReleaseDC(m_hWnd, m_hDC);

    ReleaseCapture();
    m_fTracking=FALSE;

    //If the original and new rects are the same, nothing happened.
    RECTFROMRECTL(rcT, m_rclOrg);

    if (EqualRect(&rc, &rcT))
        return FALSE;

    RECTFROMRECTL(rcT, m_rclOrg);
    InvalidateRect(m_hWnd, &rcT, TRUE);

    //Invalidate on the screen before accounting for scrolling
    InvalidateRect(m_hWnd, &rc, TRUE);

    //Factor in scrolling and tell the tenant where it now stands.
    OffsetRect(&rc, (int)m_pPG->m_xPos, (int)m_pPG->m_yPos);
    RECTLFROMRECT(m_rcl, rc);
    m_pTenantCur->RectSet(&m_rcl, TRUE, TRUE);

    UpdateWindow(m_hWnd);
    return TRUE;
    }

/*
 * CPage::OnLeftDoubleClick
 *
 * Purpose:
 *  Called when the user double-clicks with the left button on this
 *  page.  We find the object under that position that is visibly on
 *  top (always the first one under this location in the page list
 *  since we paint in reverse order) and activate it.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if the action changed the object.
 */

BOOL CPage::OnLeftDoubleClick(UINT uKeys, UINT x, UINT y)
    {
    /*
     * The current tenant is the only one that can be activated, so
     * we just have to make sure the mouse is there.  For that we
     * can use the last hit-test code we saw since it's updated on
     * every mouse move.
     */

    if (HTNOWHERE!=m_uHTCode)
        return m_pTenantCur->Activate(OLEIVERB_PRIMARY);

    return FALSE;
    }

/*
 * CPage::OnMouseMove
 *
 * Purpose:
 *  Processes WM_MOUSEMOVE on a page so we can handle tracking
 *  resize of a tenant.
 *
 * Parameters:
 *  x, y            int device coordinates to check.
 *
 * Return Value:
 *  None
 */

void CPage::OnMouseMove(UINT uKeys, int x, int y)
    {
    RECT        rc, rcO, rcB;
    int         cxy;

    if (m_fSizePending || m_fDragPending)
        {
        int     dx, dy;

        dx=(x > m_ptDown.x) ? (x-m_ptDown.x) : (m_ptDown.x-x);
        dy=(y > m_ptDown.y) ? (y-m_ptDown.y) : (m_ptDown.y-y);

        /*
         * Has the mouse moved outside the debounce distance?  If
         * so, we can start sizing.  Note that this happens
         * regardless of the timer state.
         */
        if (dx > m_cxyDist || dy > m_cxyDist)
            {
            POINT       pt;
            BOOL        fSize=m_fSizePending;
            BOOL        fDrag=m_fDragPending;

            m_fSizePending=FALSE;
            m_fDragPending=FALSE;

            if (m_fTimer)
                {
                KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
                m_fTimer=FALSE;
                }

            if (fDrag)
                {
                //Set dirty flag if drag & drop changed things.
                m_pPG->m_fDirty |= DragDrop(m_uKeysDown, x, y);
                return;
                }

            if (fSize)
                StartSizeTracking();

            /*
             * Since we might have moved out of the sizing handle
             * in order to start the operation, we need to set the
             * m_uSizingFlags field based on the original down point
             * for subsequent mouse moves to function properly.
             * Note that OnNCHitTest expects screen coordinates.
             */
            SETPOINT(pt, m_ptDown.x, m_ptDown.y);
            ClientToScreen(m_hWnd, &pt);
            OnNCHitTest(pt.x, pt.y);
            OnSetCursor(m_uHTCode);
            return;
            }
        }

    if (!m_fTracking)
        return;

    //Get rid of the old rectangle.
    RECTFROMRECTL(rc, m_rcl)
    DrawFocusRect(m_hDC, &rc);

    /*
     * Calculate the new.  The flags in m_uSizingFlags tell us what
     * to change.  We limit the object by the page margins and a
     * minimum size of 3*CXYHANDLE in either dimension.
     */
    cxy=3*CXYHANDLE;

    RECTFROMRECTL(rcO, m_rclOrg);
    RECTFROMRECTL(rcB, m_rclBounds);

    if (m_uSizingFlags & SIZINGTOP)
        {
        if (y >= rcO.bottom-cxy)
            y=rcO.bottom-cxy;

        if (y <= rcB.top)           //Limit to top of page.
            y=rcB.top;

        m_rcl.top=y;
        }

    if (m_uSizingFlags & SIZINGBOTTOM)
        {
        if (y <= rcO.top+cxy)
            y=rcO.top+cxy;

        if (y >= rcB.bottom)         //Limit to bottom of page.
            y=rcB.bottom;

        m_rcl.bottom=y;
        }

    if (m_uSizingFlags & SIZINGLEFT)
        {
        if (x >= rcO.right-cxy)
            x=rcO.right-cxy;

        if (x <= rcB.left)           //Limit to left of page.
            x=rcB.left;

        m_rcl.left=x;
        }

    if (m_uSizingFlags & SIZINGRIGHT)
        {
        if (x <= rcO.left+cxy)
            x=rcO.left+cxy;

        if (x >= rcB.right)          //Limit to right of page.
            x=rcB.right;

        m_rcl.right=x;
        }

    //Draw the new
    RECTFROMRECTL(rc, m_rcl)
    DrawFocusRect(m_hDC, &rc);

    return;
    }

/*
 * CPage::OnTimer
 *
 * Purpose:
 *  Processes WM_TIMER messages to a page used to perform mouse
 *  debouncing.
 *
 * Parameters:
 *  uID             UINT timer ID.
 *
 * Return Value:
 *  None
 */

void CPage::OnTimer(UINT uID)
    {
    if (m_fSizePending || m_fDragPending)
        {
        BOOL        fSize=m_fSizePending;
        BOOL        fDrag=m_fDragPending;

        /*
         * Having this function called means the delay requirement
         * is satisfied.  Start tracking for sizing or dragging.
         */

        m_fSizePending=FALSE;
        m_fDragPending=FALSE;

        KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
        m_fTimer=FALSE;

        if (fDrag)
            {
            POINT       pt;

            GetCursorPos(&pt);
            m_pPG->m_fDirty |= DragDrop(m_uKeysDown
                , m_ptDown.x, m_ptDown.y);
            return;
            }

        if (fSize)
            StartSizeTracking();
        }

    return;
    }

/*
 * CPage::StartSizeTracking
 *
 * Purpose:
 *  Begins sizing of a tenant when mouse debounce conditions are
 *  met.
 *
 * Parameters:
 *  uID             UINT timer ID.
 *
 * Return Value:
 *  None
 */

void CPage::StartSizeTracking(void)
    {
    RECT        rc;

    m_pTenantCur->RectGet(&m_rcl, TRUE);
    SetCapture(m_hWnd);
    m_fTracking=TRUE;

    m_hDC=GetDC(m_hWnd);

    //Place the rectangle exactly where it is on the screen.
    RECTFROMRECTL(rc, m_rcl)
    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);
    RECTLFROMRECT(m_rcl, rc);
    m_rclOrg=m_rcl;

    DrawFocusRect(m_hDC, &rc);

    m_pPG->CalcBoundingRect(&rc, TRUE);
    RECTLFROMRECT(m_rclBounds, rc);
    return;
    }

/*
 * CPage::OnNCHitTest
 *
 * Purpose:
 *  Processes WM_NCHITTEST on a page so we can check for hits on the
 *  handles of the selected object for resizing.  We only save
 *  information for ourselves and do not interfere with normal
 *  hit-testing.
 *
 * Parameters:
 *  x, y            UINT device coordinates to check.
 *
 * Return Value:
 *  None
 */

void CPage::OnNCHitTest(UINT x, UINT y)
    {
    RECT        rc;
    RECTL       rcl;
    int         iMid1, iMid2;
    int         xHit, yHit;
    POINT       pt;
    int         x0, y0;

    //Default: don't start sizing on a click, don't hit an object.
    m_uSizingFlags=0;
    m_uHTCode=HTNOWHERE;

    if (NULL==m_pTenantCur)
        return;

    //Convert device points to our coordinates
    m_pTenantCur->RectGet(&rcl, FALSE);
    RECTFROMRECTL(rc, rcl);
    RectConvertMappings(&rc, NULL, TRUE);
    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

    SETPOINT(pt, x, y);
    ScreenToClient(m_hWnd, &pt);
    x0=pt.x;
    y0=pt.y;

    if (x0 < rc.left || x0 > rc.right)
        return;

    if (y0 < rc.top || y0 > rc.bottom)
        return;

    //It's at least in the object.
    m_uHTCode=HTCLIENT;

    //Check for hits in horizontal regions
    xHit=NOVALUE;
    iMid1=rc.left+((rc.right-rc.left-CXYHANDLE) >> 1);
    iMid2=rc.left+((rc.right-rc.left+CXYHANDLE) >> 1);

    if (x0 >= rc.left && x0 <= rc.left+CXYHANDLE)
        xHit=XLEFT;
    else if (x0 >= iMid1 && x0 <= iMid2)
        xHit=XMID;
    else if (x0 >= rc.right-CXYHANDLE && x0 <= rc.right)
        xHit=XRIGHT;

    //Don't exit yet if we didn't hit a handle--might hit a y edge.

    //Check for hits in vertical regions
    yHit=NOVALUE;
    iMid1=rc.top+((rc.bottom-rc.top-CXYHANDLE) >> 1);
    iMid2=rc.top+((rc.bottom-rc.top+CXYHANDLE) >> 1);

    if (y0 >= rc.top && y0 <= rc.top+CXYHANDLE)
        yHit=YTOP;
    else if (y0 >= iMid1 && y0 <= iMid2)
        yHit=YMID;
    else if (y0 >= rc.bottom-CXYHANDLE && y0 <= rc.bottom)
        yHit=YBOT;

    /*
     * If we hit any edge, but didn't hit a handle, then one of xHit
     * and yHit will be NOVALUE and the other something else.  When
     * we hit an edge on the 'something else' then we're on a drag
     * point.
     */

    if ((NOVALUE==xHit && NOVALUE==yHit)
        || (XMID==xHit && YMID==yHit)
        || (NOVALUE==xHit && YMID==yHit)
        || (XMID==xHit && NOVALUE==yHit))
        return;

    if ((NOVALUE==xHit && (YTOP==yHit || YBOT==yHit))
        || ((XLEFT==xHit || XRIGHT==xHit) && NOVALUE==yHit))
        {
        m_uHTCode=HTCAPTION;
        return;
        }

    //We hit a handle, so save our HT code
    m_uSizingFlags=g_rguSizingFlags[xHit+(yHit*3)];
    m_uHTCode=g_rgHTCode[xHit+(yHit*3)];
    return;
    }

/*
 * CPage::SetCursor
 *
 * Purpose:
 *  Processes WM_SETCURSOR using the code from OnNCHitTest.
 *
 * Parameters:
 *  x, y            UINT device coordinates to check.
 *
 * Return Value:
 *  LRESULT         HT* code for Windows.
 */

BOOL CPage::OnSetCursor(UINT uHTCode)
    {
    HCURSOR     hCur;
    UINT        iCur;

    /*
     * We really just ignore uHTCode and use the one we saved
     * in OnNCHitTest.
     */

    switch (m_uHTCode)
        {
        case HTTOP:
        case HTBOTTOM:
            iCur=IDC_VARROWS;
            break;

        case HTLEFT:
        case HTRIGHT:
            iCur=IDC_HARROWS;
            break;

        case HTTOPLEFT:
        case HTBOTTOMRIGHT:
            iCur=IDC_NWSEARROWS;
            break;

        case HTTOPRIGHT:
        case HTBOTTOMLEFT:
            iCur=IDC_NESWARROWS;
            break;

        case HTCAPTION:
            iCur=IDC_SMALLARROWS;
            break;

        default:
            return FALSE;
        }

    hCur=UICursorLoad(iCur);
    SetCursor(hCur);

    return TRUE;
    }

/*
 * CPage::TenantFromPoint
 * (Protected)
 *
 * Purpose:
 *  Finds the tenant under the given device coordinates on this
 *  page.
 *
 * Parmeters:
 *  x, y            UINT coordinates.
 *  ppTenant        PCTenant * in which to return the pointer.
 *
 * Return Value:
 *  UINT            Index of the matched tenant, NOVALUE if not
 *                  found.
 */

UINT CPage::TenantFromPoint(UINT x, UINT y, PCTenant *ppTenant)
    {
    PCTenant    pTenant;
    RECTL       rcl;
    UINT        i;
    int         x0, y0;

    x0=x+m_pPG->m_xPos;
    y0=y+m_pPG->m_yPos;

    for (i=0; i < m_cTenants; i++)
        {
        if (!FTenantGet(i, &pTenant, FALSE))
            continue;

        pTenant->RectGet(&rcl, TRUE);

        //Essentially Perform PointInRECTL
        if (x0 >= rcl.left && x0 <= rcl.right)
            {
            if (y0 <=rcl.bottom && y0 >=rcl.top)
                {
                *ppTenant=pTenant;
                return i;
                }
            }
        }

    *ppTenant=NULL;
    return NOVALUE;
    }

/*
 * CPage::DragDrop
 *
 * Purpose:
 *  Performs drag-drop operations from the page window
 *
 * Parmeters:
 *  uKeys           UINT state of the keyboard
 *  x, y            UINT mouse coordinates of the starting click.
 *
 * Return Value:
 *  BOOL            TRUE if we modified the page, FALSE otherwise.
 */

BOOL CPage::DragDrop(UINT uKeys, UINT x, UINT y)
    {
    LPDROPSOURCE    pIDropSource;
    LPDATAOBJECT    pIDataObject;
    HRESULT         hr;
    DWORD           dwEffect;
    POINTL          ptl;
    SIZEL           szl;
    RECTL           rcl;
    RECT            rc, rcT;

    pIDropSource=new CDropSource();

    if (NULL==pIDropSource)
        return FALSE;

    pIDropSource->AddRef();
    m_pPG->m_fDragSource=TRUE;

    /*
     * Store a pick point with the data indicating the offset from
     * the upper left of the rectangle where we grabbed it.  This is
     * so the UI feedback in IDropTarget lines up with this tenant.
     */

    m_pTenantCur->RectGet(&rcl, TRUE);
    ptl.x=x+m_pPG->m_xPos-rcl.left;
    ptl.y=y+m_pPG->m_yPos-rcl.top;
    pIDataObject=TransferObjectCreate(&ptl);

    if (NULL==pIDataObject)
        {
        pIDropSource->Release();
        return FALSE;
        }

    m_pPG->m_fMoveInPage=FALSE;

    dwEffect=DROPEFFECT_COPY | DROPEFFECT_MOVE;
    hr=DoDragDrop(pIDataObject, pIDropSource
        , DROPEFFECT_COPY | DROPEFFECT_MOVE, &dwEffect);

    pIDataObject->Release();
    pIDropSource->Release();

    m_pPG->m_fDragSource=FALSE;

    //No drop-no action.
    if (DRAGDROP_S_DROP!=GetScode(hr) || DROPEFFECT_NONE==dwEffect)
        return FALSE;

    /*
     * If m_pPG->m_fMoveInPage is set, then we just change the
     * coordinates on m_pTenantCur and we're done.
     */
    if (m_pPG->m_fMoveInPage)
        {
        m_pTenantCur->Invalidate();

        /*
         * Clip to page boundaries.  We know that ptDrop has to be
         * in the page somewhere or we would not have dropped
         * (effect was NONE).  So first make sure that ptDrop is
         * within 3*CXYHANDLE of the right or bottom, and if so,
         * pull it out to 3*CXYHANDLE.  Then we can just clip the
         * size to the page rectangle and we'll always be sure to
         * have at least a sizeable object.
         */
        m_pTenantCur->SizeGet(&szl, TRUE);
        SetRect(&rc, (int)m_pPG->m_ptDrop.x, (int)m_pPG->m_ptDrop.y
            , 0, 0);
        RectConvertMappings(&rc, NULL, TRUE);

        m_pPG->CalcBoundingRect(&rcT, FALSE);
        OffsetRect(&rcT, (int)m_pPG->m_xPos, (int)m_pPG->m_yPos);

        if (rc.left >= rcT.right-3*CXYHANDLE)
            rc.left=rcT.right-3*CXYHANDLE;

        if (rc.top >= rcT.bottom-3*CXYHANDLE)
            rc.top=rcT.bottom-3*CXYHANDLE;

        rc.right=rc.left+(int)szl.cx;
        rc.bottom=rc.top+(int)szl.cy;
        IntersectRect(&rc, &rc, &rcT);

        RECTLFROMRECT(rcl, rc);

        m_pTenantCur->RectSet(&rcl, TRUE, FALSE);
        m_pTenantCur->Repaint();
        return TRUE;
        }

    /*
     * Otherwise we may have to delete the old tenant if the effect
     * was move.  This will not happen in the move in page case.
     */

    if (DROPEFFECT_MOVE==dwEffect)
        {
        TenantDestroy();
        return TRUE;
        }

    //Copy is a clean operation
    return FALSE;
    }


PAGES.CPP

/*
 * PAGES.CPP
 * Patron Chapter 13
 *
 * Implementation of the CPages class.  See PAGEWIN.CPP for
 * additional member functions.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPages:CPages
 * CPages::~CPages
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application we're in.
 *  cf              UINT application clipboard format.
 */

CPages::CPages(HINSTANCE hInst, UINT cf)
    : CWindow(hInst)
    {
    m_pPageCur=NULL;
    m_iPageCur=NOVALUE;  //Pages are 0 indexed, this is one before
    m_cPages=0;
    m_hWndPageList=NULL;
    m_fSystemFont=FALSE;

    //Initialize to 8.5*11 inch with .25 inch margins as a default.
    m_cx=(LOMETRIC_PER_INCH*17)/2;
    m_cy=LOMETRIC_PER_INCH*11;

    m_xMarginLeft=LOMETRIC_PER_INCH/4;
    m_xMarginRight=LOMETRIC_PER_INCH/4;
    m_yMarginTop=LOMETRIC_PER_INCH/4;
    m_yMarginBottom=LOMETRIC_PER_INCH/4;

    m_xPos=0L;
    m_yPos=0L;

    m_dwIDNext=0;
    m_pIStorage=NULL;

    m_fDirty=FALSE;
    m_cf=cf;

    m_fDragSource=FALSE;
    m_fMoveInPage=FALSE;
    m_fLinkAllowed=FALSE;

    m_fDragRectShown=FALSE;

    m_uScrollInset=GetProfileInt(TEXT("windows")
        , TEXT("DragScrollInset"), DD_DEFSCROLLINSET);

    m_uScrollDelay=GetProfileInt(TEXT("windows")
        , TEXT("DragScrollDelay"), DD_DEFSCROLLDELAY);

    m_uHScrollCode=0;
    m_uVScrollCode=0;
    m_fShowTypes=FALSE;

    //CHAPTER13MOD
    m_pmkFile=NULL;
    //End CHAPTER13MOD
    return;
    }

CPages::~CPages(void)
    {
    //Ensure memory cleaned up in list; do final IStorage::Release
    FIStorageSet(NULL, FALSE, FALSE);

    //CHAPTER13MOD
    if (NULL!=m_pmkFile)
        m_pmkFile->Release();
    //End CHAPTER13MOD

    if (NULL!=m_hFont && !m_fSystemFont)
        DeleteObject(m_hFont);

    if (NULL!=m_hWndPageList)
        DestroyWindow(m_hWndPageList);

    //m_hWnd destroyed with the document.
    return;
    }

/*
 * CPages::FIsDirty
 *
 * Purpose:
 *  Tells the caller (document) if anything's happened to dirty us.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPages::FIsDirty(void)
    {
    return m_fDirty;
    }

/*
 * CPages::FInit
 *
 * Purpose:
 *  Instantiates a pages window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags.
 *                  Should contain WS_CHILD | WS_VISIBLE in
 *                  typical circumstances.
 *  uID             UINT ID to associate with this window
 *  pv              LPVOID unused for now.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPages::FInit(HWND hWndParent, LPRECT pRect, DWORD dwStyle
    , UINT uID, LPVOID pv)
    {
    int     cy;

    m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY, SZCLASSPAGES
        , SZCLASSPAGES, dwStyle, pRect->left, pRect->top
        , pRect->right-pRect->left, pRect->bottom-pRect->top
        , hWndParent, (HMENU)uID, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    /*
     * Create the hidden listbox we'll use to track pages.  We give
     * it the owner-draw style so we can just store pointers in it.
     * We have to set the parent to NULL such that the window hangs
     * around after the pages window is destroyed so that we can
     * clean up the memory stored in it from the CPages destructor.
     */
    m_hWndPageList=CreateWindow(TEXT("listbox"), TEXT("Page List")
        , WS_POPUP | LBS_OWNERDRAWFIXED, 0, 0, 100, 100
        , HWND_DESKTOP, NULL, m_hInst, NULL);

    if (NULL==m_hWndPageList)
        return FALSE;

    //Create a 14 point Arial font, or use the system variable font.
    cy=MulDiv(-14, LOMETRIC_PER_INCH, 72);
    m_hFont=CreateFont(cy, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE
        , ANSI_CHARSET, OUT_TT_PRECIS, CLIP_TT_ALWAYS, PROOF_QUALITY
        , VARIABLE_PITCH | FF_SWISS, TEXT("Arial"));

    if (NULL==m_hFont)
        {
        m_hFont=(HFONT)GetStockObject(ANSI_VAR_FONT);
        m_fSystemFont=TRUE;
        }

    return TRUE;
    }

/*
 * CPages::FIStorageSet
 *
 * Purpose:
 *  Provides the document's IStorage to the pages for its own use.
 *  If this is a new storage, we initalize it with streams that we
 *  want to always exists.  If this is an open, then we create
 *  our page list from the PageList string we wrote before.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE to the new or opened storage.  If
 *                  NULL, we just clean up and exit.
 *  fChange         BOOL indicating is this was a Save As operation,
 *                  meaning that we have the structure already and
 *                  just need to change the value of m_pIStorage.
 *  fInitNew        BOOL indicating if this is a new storage or one
 *                  opened from a previous save.
 *
 * Return Value:
 *  None
 */

BOOL CPages::FIStorageSet(LPSTORAGE pIStorage, BOOL fChange
    , BOOL fInitNew)
    {
    DWORD           dwMode=STGM_DIRECT | STGM_READWRITE
                        | STGM_SHARE_EXCLUSIVE;
    HRESULT         hr;
    PCPage          pPage;
    BOOL            fRet=FALSE;
    ULONG           cbRead;
    PAGELIST        pgList;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    LPDWORD         pdwID;
    UINT            i;

    //If we're changing saved roots, simply open current page again
    if (fChange)
        {
        if (NULL==pIStorage)
            return FALSE;

        m_pIStorage->Release();
        m_pIStorage=pIStorage;
        m_pIStorage->AddRef();

        FPageGet(m_iPageCur, &m_pPageCur, TRUE);
        return TRUE;
        }

    if (NULL!=m_hWndPageList)
        {
        //On new or open, clean out whatever it is we have.
        for (i=0; i < m_cPages; i++)
            {
            //CHAPTER13MOD
            if (FPageGet(i, &pPage, FALSE))
                pPage->Release();
            //End CHAPTER13MOD
            }

        SendMessage(m_hWndPageList, LB_RESETCONTENT, 0, 0L);
        }

    if (NULL!=m_pIStorage)
        m_pIStorage->Release();

    m_pIStorage=NULL;

    //If we're just cleaning up, then we're done.
    if (NULL==pIStorage)
        return TRUE;

    m_pIStorage=pIStorage;
    m_pIStorage->AddRef();

    //If this is a new storage, create the streams we require
    if (fInitNew)
        {
        //Page list header.
        hr=m_pIStorage->CreateStream(SZSTREAMPAGELIST, dwMode
            | STGM_CREATE, 0, 0, &pIStream);

        if (FAILED(hr))
            return FALSE;

        pIStream->Release();

        //Device Configuration
        hr=m_pIStorage->CreateStream(SZSTREAMDEVICECONFIG, dwMode
            | STGM_CREATE, 0, 0, &pIStream);

        if (FAILED(hr))
            return FALSE;

        pIStream->Release();
        return TRUE;
        }

    /*
     * We're opening an existing file:
     *  1)  Configure for the device we're on
     *  2)  Read the Page List and create page entries for each.
     */

    ConfigureForDevice();

    //Read the page list.
    hr=m_pIStorage->OpenStream(SZSTREAMPAGELIST, NULL, dwMode, 0
        , &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pIStream->Read(&pgList, sizeof(PAGELIST), &cbRead);
        m_cPages  =(UINT)pgList.cPages;
        m_iPageCur=(UINT)pgList.iPageCur;
        m_dwIDNext=pgList.dwIDNext;

        fRet=TRUE;
        cbRead=pgList.cPages*sizeof(DWORD);

        if (0!=cbRead)
            {
            pdwID=(LPDWORD)pIMalloc->Alloc(cbRead);

            if (NULL!=pdwID)
                {
                pIStream->Read(pdwID, cbRead, &cbRead);

                for (i=0; i < m_cPages; i++)
                    fRet &=FPageAdd(NOVALUE, *(pdwID+i), FALSE);

                pIMalloc->Free(pdwID);
                }
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    if (!fRet)
        return FALSE;

    FPageGet(m_iPageCur, &m_pPageCur, TRUE);

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);

    return TRUE;
    }

/*
 * CPages::FIStorageUpdate
 *
 * Purpose:
 *  Insures that all pages are committed before a root save.
 *
 * Parameters:
 *  fCloseAll       BOOL directing us to close all open storages
 *                  and streams.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::FIStorageUpdate(BOOL fCloseAll)
    {
    PCPage          pPage;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    LPDWORD         pdwID;
    ULONG           cb;
    HRESULT         hr;
    PAGELIST        pgList;
    BOOL            fRet=FALSE;
    UINT            i;

    //We only need to close the current page--nothing else is open.
    if (NULL!=m_pPageCur)
        {
        m_pPageCur->Update();

        if (fCloseAll)
            m_pPageCur->Close(FALSE);
        }

    //We don't hold anything else open, so write the page list.
    hr=m_pIStorage->OpenStream(SZSTREAMPAGELIST, NULL, STGM_DIRECT
        | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pgList.cPages=m_cPages;
        pgList.iPageCur=m_iPageCur;
        pgList.dwIDNext=m_dwIDNext;

        pIStream->Write(&pgList, sizeof(PAGELIST), &cb);

        cb=m_cPages*sizeof(DWORD);
        pdwID=(LPDWORD)pIMalloc->Alloc(cb);

        if (NULL!=pdwID)
            {
            for (i=0; i < m_cPages; i++)
                {
                FPageGet(i, &pPage, FALSE);
                *(pdwID+i)=pPage->GetID();
                }

            pIStream->Write(pdwID, cb, &cb);
            pIMalloc->Free(pdwID);
            fRet=TRUE;
            }
        pIMalloc->Release();
        }

    pIStream->Release();

    //Don't forget to clean up the dirty flag when we do an update.
    m_fDirty=!fRet;
    return fRet;
    }

/*
 * CPages::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Pages window in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  None
 */

void CPages::RectGet(LPRECT pRect)
    {
    RECT        rc;
    POINT       pt;

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_hWnd, &rc);
    SETPOINT(pt, rc.left, rc.top);
    ScreenToClient(GetParent(m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return;
    }

/*
 * CPages::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Pages window which sizes to fit.
 *  Coordinates are given in parent terms.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPages::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    if (NULL==pRect)
        return;

    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, pRect->left, pRect->top
        , (UINT)cx, (UINT)cy, SWP_NOZORDER);

    UpdateScrollRanges();
    return;
    }

/*
 * CPages::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the pages window in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  None
 */

void CPages::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return;
    }

/*
 * CPages::SizeSet
 *
 * Purpose:
 *  Sets a new size in parent coordinates for the Pages window.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPages::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    if (NULL==pRect)
        return;

    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
        , SWP_NOMOVE | SWP_NOZORDER);

    UpdateScrollRanges();
    return;
    }

/*
 * CPages::PageInsert
 *
 * Purpose:
 *  Creates a new page immediately after the current page.  If
 *  there are no pages then this creates page 1.
 *
 * Parameters:
 *  uReserved       UINT unused
 *
 * Return Value:
 *  UINT            Index of the new page, 0 on failure.
 */

UINT CPages::PageInsert(UINT uReserved)
    {
    if (0!=m_cPages && NULL!=m_pPageCur)
        {
        //Close the current page, committing changes.
        m_pPageCur->Close(TRUE);
        }

    //Create and open the new page.
    if (!FPageAdd(m_iPageCur, m_dwIDNext, TRUE))
        return 0;

    m_dwIDNext++;
    m_iPageCur++;
    m_cPages++;

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);

    FPageGet(m_iPageCur, &m_pPageCur, FALSE);
    return m_iPageCur;
    }

/*
 * CPages::PageDelete
 *
 * Removes the current page from the page list.
 *
 * Parameters:
 *  uReserved       UINT unused
 *
 * Return Value:
 *  UINT            Index to the now current page from the page
 *                  list, NOVALUE on error.
 */

UINT CPages::PageDelete(UINT uReserved)
    {
    PCPage      pPage;

    if (!FPageGet(m_iPageCur, &pPage, FALSE))
        return NOVALUE;

    //Delete the page in both the storage and in memory.
    SendMessage(m_hWndPageList, LB_DELETESTRING, m_iPageCur, 0L);

    m_pPageCur->Destroy(m_pIStorage);

    //CHAPTER13MOD
    m_pPageCur->Release();   //Does final pPage->Close
    //End CHAPTER13MOD
    m_pPageCur=NULL;

    /*
     * If this is the last page then the current is one less.  If
     * it's the only page the current is zero.  Otherwise the
     * current is the next page.
     */

    if (m_iPageCur==m_cPages-1)   //Covers last or only page.
        m_iPageCur--;

    m_cPages--;

    //Insure the new visible page is open.
    if (0!=m_cPages)
        {
        FPageGet(m_iPageCur, &m_pPageCur, TRUE);
        InvalidateRect(m_hWnd, NULL, FALSE);
        }
    else
        InvalidateRect(m_hWnd, NULL, TRUE);

    UpdateWindow(m_hWnd);
    return m_iPageCur;
    }

/*
 * CPages::CurPageGet
 *
 * Purpose:
 *  Retrieves the index of the current page we're viewing.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the current page.
 */

UINT CPages::CurPageGet(void)
    {
    return m_iPageCur;
    }

/*
 * CPages::CurPageSet
 *
 * Purpose:
 *  Sets the index of the current page to view.
 *
 * Parameters:
 *  iPage           UINT index of the page to view. 0 means first
 *                  page, NOVALUE means last page.
 *
 * Return Value:
 *  UINT            Index of the previous current page, NOVALUE on
 *                  error.
 */

UINT CPages::CurPageSet(UINT iPage)
    {
    UINT    iPageNew;
    UINT    iPagePrev=m_iPageCur;
    PCPage  pPage;

    switch (iPage)
        {
        case 0:
            iPageNew=0;
            break;

        case NOVALUE:
            iPageNew=m_cPages-1;
            break;

        default:
            if (iPage >= m_cPages)
                iPage=0;

            iPageNew=iPage;
            break;
        }

    //No reason to switch to the same page.
    if (iPagePrev==iPageNew)
        return iPage;

    //Close the old page committing changes.
    if (!FPageGet(iPagePrev, &pPage, FALSE))
        return NOVALUE;

    pPage->Close(TRUE);

    m_iPageCur=iPageNew;

    //Open the new page.
    FPageGet(m_iPageCur, &m_pPageCur, TRUE);

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);
    return iPagePrev;
    }

/*
 * CPages::NumPagesGet
 *
 * Purpose:
 *  Returns the number of pages this object current contains.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of pages we contain.
 */

UINT CPages::NumPagesGet(void)
    {
    return m_cPages;
    }

/*
 * CPages::DevModeSet
 *
 * Purpose:
 *  Provides the Pages with the current printer information.
 *
 * Parameters:
 *  hDevMode        HGLOBAL to the memory containing the DEVMODE.
 *                  This function assumes responsibility for this
 *                  handle.
 *  hDevNames       HGLOBAL providing the driver name and device
 *                  name from which we can create a DC for
 *                  information.
 *
 * Return Value:
 *  BOOL            TRUE if we could accept this configuration,
 *                  FALSE otherwise.  If we return TRUE we also
 *                  delete the old memory we hold.
 */

BOOL CPages::DevModeSet(HGLOBAL hDevMode, HGLOBAL hDevNames)
    {
    LPDEVNAMES      pdn;
    LPTSTR          psz;
    HGLOBAL         hMem;
    PDEVICECONFIG   pdc;
    LPDEVMODE       pdm;
    LPSTREAM        pIStream;
    HRESULT         hr;
    ULONG           cbDevMode, cbWrite;
    BOOL            fRet=FALSE;

    if (NULL==hDevMode || NULL==hDevNames)
        return FALSE;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    /*
     * DEVMODE is variable length--total length in hDevMode, so the
     * amount to write is that plus string space.  We subtract
     * sizeof(DEVMODE) as that is already included from GlobalSize.
     */
    cbDevMode=GlobalSize(hDevMode);
    cbWrite=cbDevMode+sizeof(DEVICECONFIG)-sizeof(DEVMODE);

    hMem=GlobalAlloc(GHND, cbWrite);

    if (NULL==hMem)
        {
        pIStream->Release();
        return FALSE;
        }

    pdc=(PDEVICECONFIG)GlobalLock(hMem);    //This always works
    pdm=(LPDEVMODE)GlobalLock(hDevMode);    //This might not

    if (NULL!=pdm)
        {
        pdc->cb=cbWrite;
        pdc->cbDevMode=cbDevMode;
        memcpy(&pdc->dm, pdm, (int)cbDevMode);
        GlobalUnlock(hDevMode);

        psz=(LPTSTR)GlobalLock(hDevNames);

        if (NULL!=psz)
            {
            pdn=(LPDEVNAMES)psz;
            lstrcpy(pdc->szDriver, psz+pdn->wDriverOffset);
            lstrcpy(pdc->szDevice, psz+pdn->wDeviceOffset);
            lstrcpy(pdc->szPort,   psz+pdn->wOutputOffset);

            pIStream->Write(pdc, cbWrite, &cbWrite);
            GlobalUnlock(hDevNames);
            fRet=TRUE;
            }
        }

    GlobalUnlock(hMem);
    GlobalFree(hMem);

    pIStream->Release();

    if (!fRet)
        return FALSE;

    GlobalFree(hDevNames);
    GlobalFree(hDevMode);

    return ConfigureForDevice();
    }

/*
 * CPages::DevModeGet
 *
 * Purpose:
 *  Retrieves a copy of the current DEVMODE structure for this
 *  Pages window.  The caller is responsible for this memory.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HGLOBAL         Handle to the memory containing the DEVMODE
 *                  structure.
 */

HGLOBAL CPages::DevModeGet(void)
    {
    HGLOBAL         hMem;
    LPVOID          pv;
    ULONG           cbDevMode, cbRead;
    LARGE_INTEGER   li;
    LPSTREAM        pIStream;
    HRESULT         hr;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    //Read how much to allocate for the DEVMODE structure
    LISet32(li, CBSEEKOFFSETCBDEVMODE);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);
    pIStream->Read(&cbDevMode, sizeof(ULONG), &cbRead);

    hMem=GlobalAlloc(GHND, cbDevMode);

    if (NULL!=hMem)
        {
        pv=(LPVOID)GlobalLock(hMem);
        pIStream->Read(pv, cbDevMode, &cbRead);
        GlobalUnlock(hMem);
        }

    pIStream->Release();
    return hMem;
    }

/*
 * CPages::DevReadConfig
 *
 * Purpose:
 *  Public function to read the current device configuration and
 *  optionally return an information context for it.
 *
 *
 * Parameters:
 *  ppcd            PCOMBINEDEVICE * in which to return a pointer
 *                  to an allocated structure that has all the
 *                  device information we want.  Ignored if NULL.
 *                  This is allocated with the task allocator.
 *  phDC            HDC * in which to return the information
 *                  context.  If NULL, no IC is created.  Caller
 *                  becomes responsible for the returned IC.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::DevReadConfig(PCOMBINEDEVICE *ppcd, HDC *phDC)
    {
    HRESULT         hr;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    PCOMBINEDEVICE  pcd;
    ULONG           cb, cbRead;
    LARGE_INTEGER   li;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    /*
     * Allocate the COMBINEDEVICE structure including the variable
     * information past the DEVMODE part.
     */

    hr=CoGetMalloc(MEMCTX_TASK, &pIMalloc);

    if (FAILED(hr))
        {
        pIStream->Release();
        return FALSE;
        }

    /*
     * Read size of the DEVICECONFIG structure including variable
     * portion of DEVMODE.  We need to load all this information
     * for CreateIC.  To this size we'll add the size of
     * DVTARGETDEVICE in order to allocate a COMBINEDEVICE.
     */

    pIStream->Read(&cb, sizeof(DWORD), &cbRead);

    pcd=(PCOMBINEDEVICE)pIMalloc->Alloc(cb+sizeof(DVTARGETDEVICE));

    if (NULL==pcd)
        {
        pIMalloc->Release();
        pIStream->Release();
        return FALSE;
        }

    //Now get the real information.
    LISet32(li, 0);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);
    pIStream->Read(&(pcd->dc), cb, &cbRead);
    pIStream->Release();

    /*
     * If requested, complete the DVTARGETDEVICE structure in
     * pcd and store pcd in *ppcd for return.
     */

    if (NULL!=ppcd)
        {
        WORD    cb=sizeof(DVTARGETDEVICE);

        pcd->td.tdSize=cb;
        pcd->td.tdExtDevmodeOffset=cb;
        pcd->td.tdDriverNameOffset=cb+sizeof(DEVMODE);
        pcd->td.tdDeviceNameOffset=cb+sizeof(DEVMODE)
            +(CCHDEVICENAME*sizeof(TCHAR));
        pcd->td.tdPortNameOffset  =cb+sizeof(DEVMODE)
            +(CCHDEVICENAME*2*sizeof(TCHAR));

        *ppcd=pcd;
        }

    //Create an IC if requested.
    if (NULL!=phDC)
        {
        //Get the DC then configure
        *phDC=CreateIC(pcd->dc.szDriver, pcd->dc.szDevice
            , pcd->dc.szPort, &(pcd->dc.dm));

        if (NULL==*phDC)
            return FALSE;
        }

    //pcd is a temporary allocation in this case
    if (NULL==ppcd)
        pIMalloc->Free(pcd);

    pIMalloc->Release();
    return (NULL!=*phDC);
    }

/*
 * CPages::ConfigureForDevice
 *
 * Purpose:
 *  Recalculates our drawing configuration based on the contents of
 *  an hDC.  If no HDC is given we use the contents of our DevMode
 *  stream.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::ConfigureForDevice(void)
    {
    POINT           ptOffset, ptPaper;
    RECT            rc;
    HDC             hDC;

    if (!DevReadConfig(NULL, &hDC))
        return FALSE;

    //Get usable page dimensions:  already sensitive to orientation
    m_cx=GetDeviceCaps(hDC, HORZSIZE)*10-16; //*10: mm to LOMETRIC
    m_cy=GetDeviceCaps(hDC, VERTSIZE)*10-16; //-16: for driver bugs.

    //Calculate the printer-limited margins on each side in LOMETRIC
    Escape(hDC, GETPRINTINGOFFSET, NULL, NULL, &ptOffset);
    Escape(hDC, GETPHYSPAGESIZE,   NULL, NULL, &ptPaper);

    SetRect(&rc, ptOffset.x, ptOffset.y, ptPaper.x, ptPaper.y);
    SetMapMode(hDC, MM_LOMETRIC);
    RectConvertMappings(&rc, hDC, FALSE);

    //Left and top margins are the printing offset.
    m_xMarginLeft= rc.left+8;   //+8 to match -16 above
    m_yMarginTop =-rc.top+8;    //LOMETRIC makes this negative.

    //Right is (paper width)-(usable width)-(left margin)
    m_xMarginRight =rc.right-m_cx-m_xMarginLeft;

    //Bottom is (paper height)-(usable height)-(top margin)+1
    m_yMarginBottom=-rc.bottom-m_cy-m_yMarginTop+1;

    UpdateScrollRanges();

    DeleteDC(hDC);
    return TRUE;
    }

/*
 * CPages::TenantCreate
 * CPages::TenantDestroy
 *
 * Purpose:
 *  Pass-throughs for CPage members on the current page.
 */

BOOL CPages::TenantCreate(TENANTTYPE tType, LPVOID pv
    , LPFORMATETC pFE, PPATRONOBJECT ppo, DWORD dwData)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantCreate(tType, pv, pFE, ppo, dwData);
    m_fDirty |= fRet;
    return fRet;
    }

BOOL CPages::TenantDestroy(void)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantDestroy();
    m_fDirty |= fRet;
    return fRet;
    }

/*
 * CPages::TenantClip
 *
 * Purpose:
 *  Copies or cuts the currently selected tenant to the clipoard.
 *
 * Parameters:
 *  fCut            BOOL TRUE to cut the object, FALSE to copy.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::TenantClip(BOOL fCut)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantClip(fCut);
    m_fDirty |= (fRet && fCut);
    return fRet;
    }

/*
 * CPages::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected on this
 *  page for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU on which object related items live.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPages::FQueryObjectSelected(HMENU hMenu)
    {
    if (NULL==m_pPageCur)
        return FALSE;

    return m_pPageCur->FQueryObjectSelected(hMenu);
    }

/*
 * CPages::ActivateObject
 *
 * Purpose:
 *  Executes a verb on the currently selected object.
 *
 * Parameters:
 *  iVerb           LONG of the selected verb.
 *
 * Return Value:
 *  None
 */

void CPages::ActivateObject(LONG iVerb)
    {
    if (NULL==m_pPageCur)
        return;

    m_pPageCur->ActivateObject(iVerb);
    return;
    }

/*
 * CPages::ShowObjectTypes
 *
 * Purpose:
 *  Pass-through to CPage::ShowObjectTypes
 *
 * Parameters:
 *  fShow           BOOL indicating to show the type or not.
 *
 * Return Value:
 *  None
 */

void CPages::ShowObjectTypes(BOOL fShow)
    {
    if (NULL==m_pPageCur)
        return;

    m_fShowTypes=fShow;
    m_pPageCur->ShowObjectTypes(fShow);
    return;
    }

/*

* CPages::NotifyTenantsOfRename
 *
 * Purpose:
 *  Pass-through to CPage to tell tenants that the document
 *  changes names.
 *
 * Parameters:
 *  pszFile         LPTSTR of the new filename.
 *  pmk             LPMONKIER to the new file moniker.
 *
 * Return Value:
 *  None
 */

void CPages::NotifyTenantsOfRename(LPTSTR pszFile, LPMONIKER pmk)
    {
    if (NULL==m_pPageCur)
        return;

    if (NULL!=m_pmkFile)
        m_pmkFile->Release();

    m_pmkFile=pmk;
    m_pmkFile->AddRef();
    m_pPageCur->NotifyTenantsOfRename(pszFile, pmk);
    return;
    }

/*
 * CPages::FQueryLinksInPage
 *
 * Purpose:
 *  Pass through to current page to see if there are any
 *  linked objects
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPages::FQueryLinksInPage()
    {
    if (NULL==m_pPageCur)
        return FALSE;

    return m_pPageCur->FQueryLinksInPage();
    }

/*
 * CPages::FGetUILinkContainer
 *
 * Purpose:
 *  Creates an object with the IOleUILinkContainer interface for
 *  the links dialog.  We know the current page which is what
 *  we need to create this thing, so we can create it here instead
 *  of bugging the page for it.
 *
 * Parameters:
 *  ppObj           PCIOleUILinkContainer * in which we return
 *                  the pointer.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::FGetUILinkContainer(PCIOleUILinkContainer *ppObj)
    {
    PCIOleUILinkContainer   pObj;

    *ppObj=NULL;

    if (NULL==m_pPageCur)
        return FALSE;

    pObj=new CIOleUILinkContainer(m_pPageCur);

    if (NULL==pObj)
        return FALSE;

    if (!pObj->FInit())
        {
        delete pObj;
        return FALSE;
        }

    pObj->AddRef();
    *ppObj=pObj;
    return TRUE;
    }

/*
 * CPages::CalcBoundingRect
 * (Protected)
 *
 * Purpose:
 *  Calculates a rectangle that bounds the printed page and the
 *  current scroll state of the window.
 *
 * Parameters:
 *  prc             LPRECT to fill with window (device) coordinates.
 *  fWindow         BOOL indicating to include the window in this
 *                  calculation or return only the printed page
 *                  coordinates.
 *
 * Return Value:
 *  None
 */

void CPages::CalcBoundingRect(LPRECT prc, BOOL fWindow)
    {
    RECT        rc, rcT;

    if (NULL==prc)
        return;

    //Calculate the boundaries for sizing: intersect page & screen
    rc.left=LOMETRIC_BORDER+m_xMarginLeft;
    rc.top =-LOMETRIC_BORDER-m_yMarginTop;
    rc.right =rc.left+(UINT)m_cx;
    rc.bottom=rc.top -(UINT)m_cy;
    RectConvertMappings(&rc, NULL, TRUE);
    OffsetRect(&rc, -(int)m_xPos, -(int)m_yPos);

    if (!fWindow)
        {
        *prc=rc;
        return;
        }

    //Intersect with window to make the size bounds.
    GetClientRect(m_hWnd, &rcT);
    IntersectRect(prc, &rc, &rcT);
    return;
    }

/*
 * CPages::FPageGet
 * (Protected)
 *
 * Purpose:
 *  Returns a page of a given index returning a BOOL so it's simple
 *  to use this function inside if statements.
 *
 * Parameters:
 *  iPage           UINT page to retrieve 0 based.
 *  ppPage          PCPage * in which to return the page pointer
 *  fOpen           BOOL indicating if we should open this page as
 *                  well.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::FPageGet(UINT iPage, PCPage *ppPage, BOOL fOpen)
    {
    if (NULL==ppPage)
        return FALSE;

    if (LB_ERR!=SendMessage(m_hWndPageList, LB_GETTEXT
        , iPage, (LONG)ppPage))
        {
        if (fOpen)
            (*ppPage)->FOpen(m_pIStorage);

        return TRUE;
        }

    return FALSE;
    }

/*
 * CPages::FPageAdd
 * (Protected)
 *
 * Purpose:
 *  Creates a new page initialized to the given values.  The new
 *  page's storage is created if it does not already exist.  If
 *  fOpenStorage is set the page's storage is opened and left
 *  opened.
 *
 * Parameters:
 *  iPage           UINT Location at which to insert page; new page
 *                  is inserted after this position.  NOVALUE for
 *                  the end.
 *  dwID            DWORD ID for this page.
 *  fOpenStorage    BOOL indicating if we're to leave the storage
 *                  open.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPages::FPageAdd(UINT iPage, DWORD dwID, BOOL fOpenStorage)
    {
    PCPage      pPage;
    LRESULT     lr;

    pPage=new CPage(dwID, m_hWnd, this);

    if (NULL==pPage)
        return FALSE;

    //CHAPTER13MOD
    pPage->AddRef();
    //End CHAPTER13MOD

    if (fOpenStorage)
        pPage->FOpen(m_pIStorage);

    if (NOVALUE==iPage)
        iPage--;

    //Now try to add to the listbox.
    lr=SendMessage(m_hWndPageList, LB_INSERTSTRING, iPage+1
        , (LONG)pPage);

    if (LB_ERRSPACE==lr)
        {
        if (fOpenStorage)
            pPage->Close(FALSE);

        //CHAPTER13MOD
        pPage->Release();
        //End CHAPTER13MOD
        return FALSE;
        }

    return TRUE;
    }

//CHAPTER13MOD
/*
 * CPages::IPageGetFromID
 * (Protected)
 *
 * Purpose:
 *  Returns a page of a given identifier
 *
 * Parameters:
 *  dwID            DWORD identifier of the page to retrieve.  If
 *                  NOVALUE, then we return the current page.
 *  ppPage          PCPage * in which to return the page
 *                  pointer
 *  fOpen           BOOL indicating if we should open this page as
 *                  well.
 *
 * Return Value:
 *  UINT            Index of the page if successful, NOVALUE
 *                  otherwise.
 */

UINT CPages::IPageGetFromID(DWORD dwID, PCPage *ppPage
    , BOOL fOpen)
    {
    UINT        i;
    PCPage      pPage;

    if (NULL==ppPage)
        return FALSE;

    if ((LONG)-1==(LONG)dwID)
        {
        *ppPage=m_pPageCur;
        return m_iPageCur;
        }

    *ppPage=NULL;

    for (i=0; i < m_cPages; i++)
        {
        if (!FPageGet(i, &pPage, FALSE))
            continue;

        if (pPage->GetID()==dwID)
            {
            if (fOpen)
                pPage->FOpen(m_pIStorage);

            *ppPage=pPage;
            return i;
            }
        }

    return NOVALUE;
    }

//End CHAPTER13MOD


PAGES.H

/*
 * PAGES.H
 * Patron Chapter 13
 *
 * Definitions and function prototypes for the Pages window control
 * as well as the CPage class.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _PAGES_H_
#define _PAGES_H_

//We need this for UICursorLoad and new-style cursors.
#include <bttncur.h>

//CHAPTER13MOD
#include <stdlib.h>     //For atol
//End CHAPTER13MOD

//Versioning.
#define VERSIONMAJOR                2
#define VERSIONMINOR                0
#define VERSIONCURRENT              0x00020000

//Classname
#define SZCLASSPAGES                TEXT("pages")

#define HIMETRIC_PER_INCH           2540
#define LOMETRIC_PER_INCH           254
#define LOMETRIC_BORDER             60          //Border around page

//Window extra bytes and offsets
#define CBPAGESWNDEXTRA             (sizeof(LONG))
#define PAGEWL_STRUCTURE            0

#include "tenant.h"

typedef struct tagTENANTLIST
    {
    DWORD       cTenants;
    DWORD       dwIDNext;
    } TENANTLIST, *PTENANTLIST;

#define SZSTREAMTENANTLIST        OLESTR("Tenant List")

//Delay timer used in mouse debouncing
#define IDTIMER_DEBOUNCE          120

/*
 * Page class describing an individual page and what things it
 * contains, managing an IStorage for us.
 *
 * A DWORD is used to identify this page as the name of the storage
 * is the string form of this ID.  If we added a page every second,
 * it would take 136 years to overrun this counter, so we can
 * get away with saving it persistently.  I hope this software is
 * obsolete by then.
 */

//CHAPTER13MOD
class CPage : public IUnknown
//End CHAPTER13MOD
    {
    friend class CIOleUILinkContainer;
    //CHAPTER13MOD
    friend class CImpIOleItemContainer;
    //End CHAPTER13MOD

    private:
        DWORD       m_dwID;             //Persistent identifier
        LPSTORAGE   m_pIStorage;        //Substorage for this page
        HWND        m_hWnd;             //Pages window
        DWORD       m_cOpens;           //Calls to FOpen

        class CPages *m_pPG;            //Pages window

        DWORD       m_dwIDNext;
        DWORD       m_cTenants;
        HWND        m_hWndTenantList;   //Listbox; our tenant list

        UINT        m_iTenantCur;
        PCTenant    m_pTenantCur;

        UINT        m_uHTCode;          //Last hit-test/mouse move
        UINT        m_uSizingFlags;     //Restrictions on sizing
        BOOL        m_fTracking;        //Tracking resize?
        RECTL       m_rclOrg;           //Original before tracking
        RECTL       m_rcl;              //Tracking rectangle
        RECTL       m_rclBounds;        //Boundaries f/size tracking
        HDC         m_hDC;              //Tracking hDC

        BOOL        m_fDragPending;     //Waiting for drag?
        BOOL        m_fSizePending;     //Waiting for debounce?
        int         m_cxyDist;          //Debounce distance
        UINT        m_cDelay;           //Debounce delay
        POINTS      m_ptDown;           //Point of click to debounce
        UINT        m_uKeysDown;        //Keys when click happens
        DWORD       m_fTimer;           //Timer active?
        BOOL        m_fReopen;          //Did we just close?

        //CHAPTER13MOD
        LPMONIKER           m_pmkFile;  //Document name
        ULONG               m_cRef;
        LPOLEITEMCONTAINER  m_pIOleItemContainer;
        //End CHAPTER13MOD

    protected:
        BOOL         FTenantGet(UINT, PCTenant *, BOOL);
        //CHAPTER13MOD
        BOOL         FTenantGetFromID(DWORD, PCTenant *, BOOL);
        //End CHAPTER13MOD
        BOOL         FTenantAdd(UINT, DWORD, PCTenant *);
        LPDATAOBJECT TransferObjectCreate(LPPOINTL);

        //PAGEMOUS.CPP
        BOOL         FSelectTenantAtPoint(UINT, UINT);
        UINT         TenantFromPoint(UINT, UINT, PCTenant *);
        BOOL         DragDrop(UINT, UINT, UINT);

    public:
        CPage(DWORD, HWND, class CPages *);
        ~CPage(void);

        //CHAPTER13MOD
        //IUnknown for delegation
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
        //End CHAPTER13MOD

        DWORD       GetID(void);
        BOOL        FOpen(LPSTORAGE);
        void        Close(BOOL);
        BOOL        Update(void);
        void        Destroy(LPSTORAGE);
        UINT        GetStorageName(LPTSTR);

        void        Draw(HDC, int, int, BOOL, BOOL);

        BOOL        TenantCreate(TENANTTYPE, LPVOID, LPFORMATETC
                        , PPATRONOBJECT, DWORD);
        BOOL        TenantDestroy(void);
        BOOL        TenantClip(BOOL);
        BOOL        FQueryObjectSelected(HMENU);
        void        ActivateObject(LONG);
        void        ShowObjectTypes(BOOL);
        void        NotifyTenantsOfRename(LPTSTR, LPMONIKER);
        BOOL        FQueryLinksInPage(void);

        //PAGEMOUSE.CPP
        BOOL        OnRightDown(UINT, UINT, UINT);
        BOOL        OnLeftDown(UINT, UINT, UINT);
        BOOL        OnLeftDoubleClick(UINT, UINT, UINT);
        BOOL        OnLeftUp(UINT, UINT, UINT);
        void        OnMouseMove(UINT, int, int);
        void        OnTimer(UINT);
        void        StartSizeTracking(void);
        void        OnNCHitTest(UINT, UINT);
        BOOL        OnSetCursor(UINT);
    };

typedef CPage *PCPage;

/*
 * Structures to save with the document describing the device
 * configuration and pages that we have.  This is followed by
 * a list of DWORD IDs for the individual pages.
 */

typedef struct tagDEVICECONFIG
    {
    DWORD       cb;                         //Size of structure
    TCHAR       szDriver[CCHDEVICENAME];
    TCHAR       szDevice[CCHDEVICENAME];
    TCHAR       szPort[CCHDEVICENAME];
    DWORD       cbDevMode;                  //Size of actual DEVMODE
    DEVMODE     dm;                         //Variable
    } DEVICECONFIG, *PDEVICECONFIG;

//Offset to cbDevMode
#define CBSEEKOFFSETCBDEVMODE  (sizeof(DWORD)   \
                               +(3*CCHDEVICENAME*sizeof(TCHAR)))

typedef struct tagPAGELIST
    {
    DWORD       cPages;
    DWORD       iPageCur;
    DWORD       dwIDNext;
    } PAGELIST, *PPAGELIST;

//Combined OLE and Patron device structures.
typedef struct tagCOMBINEDEVICE
    {
    DVTARGETDEVICE  td;
    DEVICECONFIG    dc;
    } COMBINEBDEVICE, *PCOMBINEDEVICE;

//PAGEWIN.CPP
LRESULT APIENTRY PagesWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL    APIENTRY AbortProc(HDC, int);
BOOL    APIENTRY PrintDlgProc(HWND, UINT, WPARAM, LPARAM);
void             RectConvertMappings(LPRECT, HDC, BOOL);

class CPages : public CWindow
    {
    friend LRESULT APIENTRY PagesWndProc(HWND, UINT, WPARAM, LPARAM);
    friend BOOL    APIENTRY PrintDlgProc(HWND, UINT, WPARAM, LPARAM);

    friend class CPage;
    friend class CTenant;
    friend class CDropTarget;
    friend class CImpIAdviseSink;

    //CHAPTER13MOD
    friend class CImpIOleItemContainer;
    //End CHAPTER13MOD

    protected:
        PCPage      m_pPageCur;             //Current page
        UINT        m_iPageCur;             //Current page
        UINT        m_cPages;               //Number of pages

        HWND        m_hWndPageList;         //Listbox with page list
        HFONT       m_hFont;                //Page font
        BOOL        m_fSystemFont;          //m_hFont system object?

        UINT        m_cx;                   //Page size in LOMETRIC
        UINT        m_cy;

        UINT        m_xMarginLeft;          //Unusable margins,
        UINT        m_xMarginRight;         //in LOMETRIC
        UINT        m_yMarginTop;
        UINT        m_yMarginBottom;

        UINT        m_xPos;                 //Viewport scroll pos,
        UINT        m_yPos;                 //both in *PIXELS*

        DWORD       m_dwIDNext;             //Next ID for a page.
        LPSTORAGE   m_pIStorage;            //Root storage

        UINT        m_cf;                   //Clipboard format
        BOOL        m_fDirty;

        BOOL        m_fDragSource;          //Source==target?
        BOOL        m_fMoveInPage;          //Moving in same page
        BOOL        m_fLinkAllowed;         //Linking in drag-drop?
        POINTL      m_ptDrop;               //Where to move object

        BOOL        m_fDragRectShown;       //Is rect on the screen?
        UINT        m_uScrollInset;         //Hit-test for drag-drop
        UINT        m_uScrollDelay;         //Delay before repeat
        DWORD       m_dwTimeLast;           //Ticks on last DragOver
        UINT        m_uHScrollCode;         //L/R on scroll repeat?
        UINT        m_uVScrollCode;         //U/D on scroll repeat?
        UINT        m_uLastTest;            //Last test result
        POINTL      m_ptlRect;              //Last feedback rectangle
        SIZEL       m_szlRect;

        BOOL        m_fShowTypes;           //Show Object active?

        //CHAPTER13MOD
        LPMONIKER   m_pmkFile;
        //End CHAPTER13MOD

    private:
        void      Draw(HDC, BOOL, BOOL);
        void      UpdateScrollRanges(void);
        BOOL      ConfigureForDevice(void);
        BOOL      FPageGet(UINT, PCPage *, BOOL);
        BOOL      FPageAdd(UINT, DWORD, BOOL);

        void      CalcBoundingRect(LPRECT, BOOL);

        //DRAGDROP.CPP
        UINT      UTestDroppablePoint(LPPOINTL);
        void      DrawDropTargetRect(LPPOINTL, LPSIZEL);
        void      AdjustPosition(LPPOINTL, LPSIZEL);

    public:
        CPages(HINSTANCE, UINT);
        ~CPages(void);

        BOOL      FInit(HWND, LPRECT, DWORD, UINT, LPVOID);

        BOOL      FIStorageSet(LPSTORAGE, BOOL, BOOL);
        BOOL      FIStorageUpdate(BOOL);

        BOOL      Print(HDC, LPTSTR, DWORD, UINT, UINT, UINT);

        void      RectGet(LPRECT);
        void      RectSet(LPRECT, BOOL);
        void      SizeGet(LPRECT);
        void      SizeSet(LPRECT, BOOL);

        UINT      PageInsert(UINT);
        UINT      PageDelete(UINT);
        UINT      CurPageGet(void);
        UINT      CurPageSet(UINT);
        UINT      NumPagesGet(void);

        BOOL      DevModeSet(HGLOBAL, HGLOBAL);
        HGLOBAL   DevModeGet(void);

        BOOL      FIsDirty(void);
        BOOL      DevReadConfig(PCOMBINEDEVICE *, HDC *);
        BOOL      TenantCreate(TENANTTYPE, LPVOID, LPFORMATETC
                      , PPATRONOBJECT, DWORD);
        BOOL      TenantDestroy(void);
        BOOL      TenantClip(BOOL);
        BOOL      FQueryObjectSelected(HMENU);
        void      ActivateObject(LONG);

        void      ShowObjectTypes(BOOL);
        void      NotifyTenantsOfRename(LPTSTR, LPMONIKER);
        BOOL      FQueryLinksInPage(void);
        BOOL      FGetUILinkContainer(class CIOleUILinkContainer **);
        //CHAPTER13MOD
        UINT      IPageGetFromID(DWORD, PCPage *, BOOL);
        //End CHAPTER13MOD
    };

typedef CPages *PCPages;

//Fixed names of streams in the Pages IStorage
#define SZSTREAMPAGELIST        OLESTR("Page List")
#define SZSTREAMDEVICECONFIG    OLESTR("Device Configuration")

//Return values for UTestDroppablePoint
#define UDROP_NONE              0x0000      //Exclusive
#define UDROP_CLIENT            0x0001      //Inclusive
#define UDROP_INSETLEFT         0x0002      //L/R are exclusive
#define UDROP_INSETRIGHT        0x0004
#define UDROP_INSETHORZ         (UDROP_INSETLEFT | UDROP_INSETRIGHT)

#define UDROP_INSETTOP          0x0008      //T/B are exclusive
#define UDROP_INSETBOTTOM       0x0010
#define UDROP_INSETVERT         (UDROP_INSETTOP | UDROP_INSETBOTTOM)

//Object used for the Links dialog

class CIOleUILinkContainer : public IOleUILinkContainer
    {
    private:
        ULONG                   m_cRef;
        PCPage                  m_pPage;
        UINT                    m_iTenant;
        LPOLEUILINKCONTAINER    m_pDelIUILinks;

    public:
        BOOL                    m_fDirty;   //No reason to hide it

    protected:
        STDMETHODIMP GetObjectInterface(DWORD, REFIID, PPVOID);

    public:
        CIOleUILinkContainer(PCPage);
        ~CIOleUILinkContainer(void);

        BOOL FInit(void);
        BOOL IsDirty(void);

        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(DWORD) GetNextLink(DWORD);
        STDMETHODIMP         SetLinkUpdateOptions(DWORD, DWORD);
        STDMETHODIMP         GetLinkUpdateOptions(DWORD, LPDWORD);
        STDMETHODIMP         SetLinkSource(DWORD, LPTSTR, ULONG
                                 , ULONG *, BOOL);
        STDMETHODIMP         GetLinkSource(DWORD, LPTSTR *, ULONG *
                                 , LPTSTR *, LPTSTR *, BOOL *
                                 , BOOL *);
        STDMETHODIMP         OpenLinkSource(DWORD);
        STDMETHODIMP         UpdateLink(DWORD, BOOL, BOOL);
        STDMETHODIMP         CancelLink(DWORD);
    };

typedef CIOleUILinkContainer *PCIOleUILinkContainer;

#endif  //_PAGES_H_


PAGEWIN.CPP

/*
 * PAGEWIN.CPP
 * Patron Chapter 13
 *
 * Window procedure for the Pages window and support functions.
 * This window manages its own scrollbars and viewport and provides
 * printing capabilities as well.  The public CPages::Print
 * function lives here.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

static HWND g_hDlgPrint=NULL;
static BOOL g_fCancelPrint=FALSE;

/*
 * PagesWndProc
 *
 * Purpose:
 *  Window procedure for the Pages window.
 */

LRESULT APIENTRY PagesWndProc(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    PCPages         ppg;
    PAINTSTRUCT     ps;
    HDC             hDC;
    int             iPos, iTmp;
    int             iMin, iMax;
    UINT            idScroll;
    BOOL            fDirty=FALSE;

    ppg=(PCPages)GetWindowLong(hWnd, PAGEWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppg=(PCPages)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PAGEWL_STRUCTURE, (LONG)ppg);

            ppg->m_hWnd=hWnd;
            break;

        case WM_PAINT:
            /*
             * If there is currently a drag-rectangle showing, then
             * remove it before painting.  This insures that
             * painting doesn't blast part of that rectangle away so
             * that when we draw it next, garbage is left around.
             */
            if (ppg->m_fDragRectShown)
                ppg->DrawDropTargetRect(NULL, NULL);

            hDC=BeginPaint(hWnd, &ps);

            //Draw only if we have a page to show.
            if (0!=ppg->m_cPages)
                ppg->Draw(hDC, FALSE, FALSE);

            EndPaint(hWnd, &ps);

            //Turn the rectangle back on, if necessary.
            if (ppg->m_fDragRectShown)
                ppg->DrawDropTargetRect(NULL, NULL);
            break;

        case WM_HSCROLL:
        case WM_VSCROLL:
            idScroll=(WM_HSCROLL==iMsg) ? SB_HORZ : SB_VERT;

            iPos=GetScrollPos(hWnd, idScroll);
            iTmp=iPos;
            GetScrollRange(hWnd, idScroll, &iMin, &iMax);

            switch (LOWORD(wParam))
                {
                case SB_LINEUP:     iPos -= 20;  break;
                case SB_PAGEUP:     iPos -=100;  break;
                case SB_LINEDOWN:   iPos += 20;  break;
                case SB_PAGEDOWN:   iPos +=100;  break;

                case SB_THUMBPOSITION:
                    iPos=ScrollThumbPosition(wParam, lParam);
                    break;

                //We don't want scrolling on this message.
                case SB_THUMBTRACK:
                    return 0L;
                }

            iPos=max(iMin, min(iPos, iMax));

            if (iPos!=iTmp)
                {
                //Set the new position and scroll the window
                SetScrollPos(hWnd, idScroll, iPos, TRUE);

                if (SB_HORZ==idScroll)
                    {
                    ppg->m_xPos=iPos;
                    ScrollWindow(hWnd, iTmp-iPos, 0, NULL, NULL);
                    }
                else
                    {
                    ppg->m_yPos=iPos;
                    ScrollWindow(hWnd, 0, iTmp-iPos, NULL, NULL);
                    }
                }

            break;

        case WM_RBUTTONDOWN:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnRightDown(wParam
                , LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_LBUTTONDOWN:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftDown(wParam
                , LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_LBUTTONUP:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftUp(wParam
                , LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_LBUTTONDBLCLK:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftDoubleClick(wParam, LOWORD(lParam)
                , HIWORD(lParam));
            break;

        case WM_MOUSEMOVE:
            if (NULL==ppg->m_pPageCur)
                break;

            ppg->m_pPageCur->OnMouseMove(wParam, LOWORD(lParam)
                , HIWORD(lParam));
            break;

        case WM_TIMER:
            if (NULL==ppg->m_pPageCur)
                break;

            ppg->m_pPageCur->OnTimer(wParam);
            break;

        case WM_NCHITTEST:
            if (NULL!=ppg->m_pPageCur)
                {
                /*
                 * This just saves information in the page for
                 * OnSetCursor
                 */
                ppg->m_pPageCur->OnNCHitTest(LOWORD(lParam)
                    , HIWORD(lParam));
                }

            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        case WM_SETCURSOR:
            if (NULL!=ppg->m_pPageCur)
                {
                if (ppg->m_pPageCur->OnSetCursor(LOWORD(lParam)))
                    break;
                }

            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    ppg->m_fDirty |= fDirty;
    return 0L;
    }

/*
 * RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device to logical
 *  coordinates where the hDC defines the logical coordinates.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  hDC             HDC describing the logical coordinate system.
 *                  if NULL, uses a screen DC in MM_LOMETRIC.
 *  fToDevice       BOOL TRUE to convert from uConv to device,
 *                  FALSE to convert device to uConv.
 *
 * Return Value:
 *  None
 */

void RectConvertMappings(LPRECT pRect, HDC hDC, BOOL fToDevice)
    {
    POINT   rgpt[2];
    BOOL    fSysDC=FALSE;

    if (NULL==pRect)
        return;

    rgpt[0].x=pRect->left;
    rgpt[0].y=pRect->top;
    rgpt[1].x=pRect->right;
    rgpt[1].y=pRect->bottom;

    if (NULL==hDC)
        {
        hDC=GetDC(NULL);
        SetMapMode(hDC, MM_LOMETRIC);
        fSysDC=TRUE;
        }

    if (fToDevice)
        LPtoDP(hDC, rgpt, 2);
    else
        DPtoLP(hDC, rgpt, 2);

    if (fSysDC)
        ReleaseDC(NULL, hDC);

    pRect->left=rgpt[0].x;
    pRect->top=rgpt[0].y;
    pRect->right=rgpt[1].x;
    pRect->bottom=rgpt[1].y;

    return;
    }

/*
 * CPages::Draw
 *
 * Purpose:
 *  Paints the current page in the pages window.
 *
 * Parameters:
 *  hDC             HDC to draw on, could be a metafile or printer
 *                  DC or any other type of DC.
 *  fNoColor        BOOL indicating if we should use screen colors
 *                  or printer colors (B&W).  Objects are printed
 *                  as-is, however.  This is TRUE for printer DCs
 *                  or print preview.
 *  fPrinter        BOOL indicating if this is a printer DC in which
 *                  case we eliminate some of the fancy drawing,
 *                  like shadows on the page and so forth.
 *
 * Return Value:
 *  None
 */

void CPages::Draw(HDC hDC, BOOL fNoColor, BOOL fPrinter)
    {
    RECT            rc, rcT;
    UINT            uMM;
    HPEN            hPen;
    HBRUSH          hBrush;
    HGDIOBJ         hObj1, hObj2;
    COLORREF        cr;
    TCHAR           szTemp[20];
    UINT            cch;
    SIZE            sz;
    PCPage          pPage;
    RECT            rcPos;

    //Make sure the DC is in LOMETRIC
    uMM=SetMapMode(hDC, MM_LOMETRIC);

    if (!fPrinter)
        {
        /*
         * We maintain a 6mm border around the page on the screen
         * besides 12.7mm margins.  We also have to account for
         * the scroll position with m_*Pos which are in pixels so
         * we have to convert them.
         */

        SetRect(&rcPos, m_xPos, m_yPos, 0, 0);
        RectConvertMappings(&rcPos, hDC, FALSE);

        rc.left  = LOMETRIC_BORDER-rcPos.left;
        rc.top   =-LOMETRIC_BORDER-rcPos.top;
        }
    else
        {
        /*
         * We define the corner of the printed paper at a negative
         * offset so rc.right and rc.bottom come out right below.
         */
        SetRect(&rc, -(int)m_xMarginLeft, m_yMarginTop, 0, 0);
        }

    rc.right=rc.left+m_cx+(m_xMarginLeft+m_xMarginRight);
    rc.bottom=rc.top-m_cy-(m_yMarginTop+m_yMarginBottom);

    //Draw a rect filled with the window color to show the page.
    if (!fPrinter)
        {
        if (fNoColor)
            {
            //Black frame, white box for printed colors.
            hPen  =CreatePen(PS_SOLID, 0, RGB(0,0,0));
            hBrush=CreateSolidBrush(RGB(255, 255, 255));
            }
        else
            {
            //Normal colors on display
            hPen=CreatePen(PS_SOLID, 0
                , GetSysColor(COLOR_WINDOWFRAME));
            hBrush=CreateSolidBrush(GetSysColor(COLOR_WINDOW));
            }

        hObj1=SelectObject(hDC, hPen);
        hObj2=SelectObject(hDC, hBrush);

        //Paper boundary
        Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom+1);

        /*
         * Draw a shadow on the *visual* bottom and right edges
         * .5mm wide.  If the button shadow color and workspace
         * colors match, then use black.  We always use black
         * when printing as well.
         */
        if (fNoColor)
            cr=RGB(0,0,0);
        else
            {
            cr=GetSysColor(COLOR_BTNSHADOW);

            if (GetSysColor(COLOR_APPWORKSPACE)==cr)
                cr=RGB(0,0,0);
            }

        cr=SetBkColor(hDC, cr);
        SetRect(&rcT, rc.left+5, rc.bottom, rc.right+5,rc.bottom-5);
        ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rcT, NULL, 0, NULL);

        SetRect(&rcT, rc.right, rc.top-5, rc.right+5, rc.bottom-5);
        ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rcT, NULL, 0, NULL);
        SetBkColor(hDC, cr);

        SelectObject(hDC, hObj1);
        SelectObject(hDC, hObj2);
        DeleteObject(hBrush);
        DeleteObject(hPen);
        }

    //Write the page number in the lower left corner
    if (!fNoColor)
        {
        SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));
        SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
        }

    //Write the page number in our page font.
    cch=wsprintf(szTemp, TEXT("Page %d"), m_iPageCur+1);

    hObj1=SelectObject(hDC, m_hFont);
    GetTextExtentPoint(hDC, szTemp, cch, &sz);

    TextOut(hDC, rc.left+m_xMarginLeft
        , rc.bottom+m_yMarginBottom+sz.cy, szTemp, cch);

    SelectObject(hDC, hObj1);

    //Rectangle to show border.
    MoveToEx(hDC, rc.left+m_xMarginLeft, rc.top-m_yMarginTop, NULL);
    LineTo(hDC, rc.left+m_xMarginLeft,   rc.bottom+m_yMarginBottom);
    LineTo(hDC, rc.right-m_xMarginRight, rc.bottom+m_yMarginBottom);
    LineTo(hDC, rc.right-m_xMarginRight, rc.top-m_yMarginTop);
    LineTo(hDC, rc.left+m_xMarginLeft,   rc.top-m_yMarginTop);

    /*
     * Go draw the objects on this page.  If the page is not open,
     * we open it anyway.  If it is already open, then opening again
     * will bump it's reference count, so the Close in ineffectual.
     */
    if (FPageGet(m_iPageCur, &pPage, TRUE))
        {
        if (!fPrinter)
            {
            pPage->Draw(hDC, rcPos.left, rcPos.top, fNoColor
                , fPrinter);
            }
        else
            pPage->Draw(hDC, 0, 0, fNoColor, fPrinter);

        pPage->Close(FALSE);
        }

    SetMapMode(hDC, uMM);
    return;
    }

/*
 * CPages::UpdateScrollRanges
 *
 * Purpose:
 *  Reset scrollbar ranges (horizontal and vertical) depending on
 *  the window size and the page size.  This function may remove
 *  the scrollbars altogether.
 *
 * Parameters:
 *  None, but set m_cx, m_cy and size m_hWnd before calling.
 *
 * Return Value:
 *  None
 */

void CPages::UpdateScrollRanges(void)
    {
    UINT        cxSB;   //Scrollbar width and height.
    UINT        cySB;
    UINT        cx, cy;
    UINT        dx, dy;
    UINT        u;
    int         iMin, iMax;
    RECT        rc;
    BOOL        fHScroll;
    BOOL        fVScroll;
    BOOL        fWasThere;

    GetClientRect(m_hWnd, &rc);

    cx=rc.right-rc.left;
    cy=rc.bottom-rc.top;

    //Convert dimensions of the image in LOMETRIC to pixels.
    SetRect(&rc, (m_cx+m_xMarginLeft+m_xMarginRight
        +LOMETRIC_BORDER*2), (m_cy+m_yMarginTop
        +m_yMarginBottom+LOMETRIC_BORDER*2), 0, 0);

    RectConvertMappings(&rc, NULL, TRUE);

    dx=rc.left;
    dy=-rc.top;

    //Assume that both scrollbars will be visible.
    fHScroll=TRUE;
    fVScroll=TRUE;

    /*
     * Determine:
     *  1)  Which scrollbars are needed.
     *  2)  How many divisions to give scrollbars so as to
     *      only scroll as little as necessary.
     */

    //Scrollbar dimensions in our units.
    cxSB=GetSystemMetrics(SM_CXVSCROLL);
    cySB=GetSystemMetrics(SM_CYHSCROLL);

    //Remove horizontal scroll if window >= cxPage+borders
    if (cx >= dx)
        fHScroll=FALSE;

    /*
     * If we still need a horizontal scroll, see if we need a
     * vertical taking the height of the horizontal scroll into
     * account.
     */

    u=fHScroll ? cySB : 0;

    if ((cy-u) >= dy)
        fVScroll=FALSE;

    //Check if adding vert scrollbar necessitates a horz now.
    u=fVScroll ? cxSB : 0;
    fHScroll=((cx-u) < dx);

    /*
     * Modify cx,cy to reflect the new client area before scaling
     * scrollbars.  We only affect the client size if there is a
     * *change* in scrollbar status:  if the scrollbar was there
     * but is no longer, then add to the client size; if it was
     * not there but now is, then subtract.
     */

    //Change cx depending on vertical scrollbar change
    GetScrollRange(m_hWnd, SB_VERT, &iMin, &iMax);
    fWasThere=(0!=iMin || 0!=iMax);

    if (fWasThere && !fVScroll)
        cx+=cxSB;

    if (!fWasThere && fVScroll)
        cx-=cxSB;

    //Change cy depending on horizontal scrollbar change
    GetScrollRange(m_hWnd, SB_HORZ, &iMin, &iMax);
    fWasThere=(0!=iMin || 0!=iMax);

    if (fWasThere && !fHScroll)
        cy+=cySB;

    if (!fWasThere && fHScroll)
        cy-=cySB;

    /*
     * Show/Hide the scrollbars if necessary and set the ranges.
     * The range is the number of units of the page we cannot see.
     */
    if (fHScroll)
        {
        //Convert current scroll position to new range.
        u=GetScrollPos(m_hWnd, SB_HORZ);

        if (0!=u)
            {
            GetScrollRange(m_hWnd, SB_HORZ, &iMin, &iMax);
            u=MulDiv(u, (dx-cx), (iMax-iMin));
            }

        SetScrollRange(m_hWnd, SB_HORZ, 0, dx-cx, FALSE);
        SetScrollPos(m_hWnd, SB_HORZ, u, TRUE);
        m_xPos=u;
        }
    else
        {
        SetScrollRange(m_hWnd, SB_HORZ, 0, 0, TRUE);
        m_xPos=0;
        }

    if (fVScroll)
        {
        //Convert current scroll position to new range.
        u=GetScrollPos(m_hWnd, SB_VERT);

        if (0!=u)
            {
            GetScrollRange(m_hWnd, SB_VERT, &iMin, &iMax);
            u=MulDiv(u, (dy-cy), (iMax-iMin));
            }

        SetScrollRange(m_hWnd, SB_VERT, 0, dy-cy, FALSE);
        SetScrollPos(m_hWnd, SB_VERT, u, TRUE);

        m_yPos=u;
        }
    else
        {
        SetScrollRange(m_hWnd, SB_VERT, 0, 0, TRUE);
        m_yPos=0;
        }

    //Repaint to insure that changes to m_x/yPos are reflected
    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }

/*
 * CPages::Print
 *
 * Purpose:
 *  Prints a specified range of pages to a given hDC.  Repeats for
 *  a given number of copies.
 *
 * Parameters:
 *  hDC             HDC to which we print.
 *  pszDoc          LPTSTR providing the document name.
 *  dwFlags         DWORD flags from PrintDlg
 *  iPageStart      UINT starting page index (one based)
 *  iPageEnd        UINT ending page index (one based).  Includes
 *                  this page.
 *  cCopies         UINT number of copies to print.  If PD_COLLATE
 *                  in dwFlags is set, we print multiple copies of
 *                  each page as we cycle through.  Otherwise we
 *                  cycle multiple times.
 *
 * Return Value:
 *  None
 */

BOOL CPages::Print(HDC hDC, LPTSTR pszDoc, DWORD dwFlags
    , UINT iPageStart, UINT iPageEnd, UINT cCopies)
    {
    BOOL        fError=FALSE;
    int         iPage, iPageInc;
    int         iUserPage, cPages;
    UINT        iRepeat, cRepeat;
    UINT        iCycle, cCycles;
    UINT        iPageHold=m_iPageCur;
    HWND        hWndT, hWndTop=NULL;
    DOCINFO     di;
    PCDocument  pDoc;

    //Validate hDC and page ranges
    if (NULL==hDC)
        return FALSE;

    if ((PD_PAGENUMS & dwFlags))
        {
        if (-1==iPageStart)
            iPageStart=0;
        else
            iPageStart--;   //Switch to zero offset.

        if (-1==iPageEnd)
            iPageEnd=m_cPages-1;
        else
            iPageEnd--;     //Switch to zero offset.
        }
    else //Can't test PD_ALLPAGES with & since it's defined as 0L
        {
        iPageStart=0;
        iPageEnd=m_cPages-1;
        }

    //Arrange cycles and repeats depending on cCopies and collating
    if (PD_COLLATE & dwFlags)
        {
        cCycles=cCopies;
        cRepeat=1;
        }
    else
        {
        cCycles=1;
        cRepeat=cCopies;
        }

    //Disable the frame window to prevent reentrancy while printing.
    hWndT=GetParent(m_hWnd);
    pDoc=(PCDocument)SendMessage(hWndT, DOCM_PDOCUMENT, 0, 0L);

    if (NULL!=pDoc)
        {
        PCFrame pFR;

        pFR=pDoc->FrameGet();
        hWndTop=pFR->Window();
        EnableWindow(hWndTop, FALSE);
        }

    SetAbortProc(hDC, AbortProc);
    g_fCancelPrint=FALSE;

    //If these don't work then we'll just live without a dialog.
    g_hDlgPrint=CreateDialog(m_hInst, MAKEINTRESOURCE(IDD_PRINTING)
        , hWndTop, PrintDlgProc);

    //Increment for either direction.
    iPageInc=(iPageStart > iPageEnd) ? -1 : 1;

    //Initial entries in dialog box.
    cPages=1+((int)(iPageEnd-iPageStart)*iPageInc);

    SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE, 1, (LPARAM)cPages);
    SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, 1, (LPARAM)cRepeat);

    di.cbSize=sizeof(DOCINFO);
    di.lpszDocName=pszDoc;
    di.lpszOutput=NULL;

    if (StartDoc(hDC, &di) > 0)
        {
        /*
         * Iterate over the pages, repeating each page depending on
         * the copies we want and if we have collate enabled.
         */

        for (iCycle=1; iCycle <= cCycles; iCycle++)
            {
            if (PD_COLLATE & dwFlags)
                {
                SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, iCycle
                    , (LPARAM)cCycles);
                }

            //iPageInc controls direction
            for (iPage=iPageStart; ; iPage+=iPageInc)
                {
                iUserPage=1+((iPage-(int)iPageStart)*iPageInc);

                SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE
                    , iUserPage, (LPARAM)cPages);

                m_iPageCur=iPage;   //We restore this later.

                for (iRepeat=1; iRepeat <= cRepeat; iRepeat++)
                    {
                    if (!(PD_COLLATE & dwFlags))
                        {
                        SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE
                            , iRepeat, (LPARAM)cRepeat);
                        }

                    StartPage(hDC);
                    Draw(hDC, TRUE, TRUE);

                    if (EndPage(hDC) < 0)
                        fError=TRUE;

                    if (fError || g_fCancelPrint)
                        break;
                    }

                if (fError || g_fCancelPrint)
                    break;

                //If we just printed the last page, time to quit.
                if (iPage==(int)iPageEnd)
                    break;
                }

            if (fError || g_fCancelPrint)
                break;
            }

        if (!fError)
            EndDoc(hDC);
        else
            AbortDoc(hDC);
        }
    else
        fError=TRUE;

    //Set the page back to what it was before all this started.
    m_iPageCur=iPageHold;

    EnableWindow(hWndTop, TRUE);
    SetFocus(hWndTop);
    DestroyWindow(g_hDlgPrint);
    DeleteDC(hDC);
    return !fError;
    }

/*
 * AbortProc
 *
 * Purpose:
 *  Abort procedure for printing the pages.
 *
 * Parameters:
 *  hDC             HDC on which printing is happening.
 *  iErr            int error code.
 *
 * Return Value:
 *  BOOL            TRUE to continue the print job, FALSE otherwise.
 */

BOOL APIENTRY AbortProc(HDC hDC, int iErr)
    {
    MSG     msg;

    while (!g_fCancelPrint
        && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
        if (NULL==g_hDlgPrint
            || !IsDialogMessage(g_hDlgPrint, &msg))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    return !g_fCancelPrint;
    }

/*
 * PrintDlgProc
 *
 * Purpose:
 *  Modeless dialog procedure for the dialog displayed while Patron
 *  is printing pages.
 */

BOOL APIENTRY PrintDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    TCHAR           szFormat[40];
    TCHAR           szOutput[80];

    switch (iMsg)
        {
        case WM_INITDIALOG:
            EnableMenuItem(GetSystemMenu(hDlg, FALSE), SC_CLOSE
                , MF_GRAYED);
            return TRUE;

        case WM_COMMAND:
            //Cancel button was pressed.
            g_fCancelPrint=TRUE;
            ShowWindow(hDlg, SW_HIDE);
            return TRUE;

        case PRINTM_PAGEUPDATE:
            GetDlgItemText(hDlg, ID_PAGESTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTPAGE, szOutput);
            return TRUE;

        case PRINTM_COPYUPDATE:
            GetDlgItemText(hDlg, ID_COPYSTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTCOPY, szOutput);
            return TRUE;
        }

    return FALSE;
    }


PATRON.CPP

/*
 * PATRON.CPP
 * Patron Chapter 13
 *
 * WinMain which is all we need for the basic application.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "patron.h"

extern "C"
{
OLEDBGDATA_MAIN("PATRON")
}

//CHAPTER13MOD
//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

//Make window handle global so other code can cause a shutdown
HWND        g_hWnd=NULL;

//Indicate if the user has control
BOOL        g_fUser=TRUE;
//End CHAPTER13MOD

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCPatronFrame   pFR;
    FRAMEINIT       fi;
    WPARAM          wRet=0;

   #ifndef WIN32
    int             cMsg=96;

    while (!SetMessageQueue(cMsg) && (cMsg-=8));
   #endif

    //Attempt to allocate and initialize the application
    pFR=new CPatronFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return -1;

    fi.idsMin=IDS_FRAMEMIN;
    fi.idsMax=IDS_FRAMEMAX;
    fi.idsStatMin=IDS_STATMESSAGEMIN;
    fi.idsStatMax=IDS_STATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=CW_USEDEFAULT;
    fi.cy=CW_USEDEFAULT;

    //If we can initialize pFR, start chugging messages
    if (pFR->FInit(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

//CHAPTER13MOD
/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Patron Document object to call when it gets
 *  destroyed.  We destroy the main window if the proper conditions
 *  are met for shutdown.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void PASCAL ObjectDestroyed(void)
    {
    g_cObj--;

    //No more objects, no locks, no user control, shut the app down.
    if (0==g_cObj && 0==g_cLock && IsWindow(g_hWnd) && !g_fUser)
        PostMessage(g_hWnd, WM_CLOSE, 0, 0L);

    return;
    }

//End CHAPTER13MOD

/*
 * CPatronFrame::CPatronFrame
 * CPatronFrame::~CPatronFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WInMain
 */

CPatronFrame::CPatronFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    : CFrame(hInst, hInstPrev, pszCmdLine, nCmdShow)
    {
    //CHAPTER13MOD
    char        szTemp[256];        //ParseCmdLine is ANSI

    m_fInitialized=FALSE;
    m_fOleStdInit=FALSE;
    m_pIClassDataTran=NULL;

    m_pDocCreated=NULL;
    m_fEmbedding=FALSE; //-Embedding on command line?

    ParseCmdLine(pszCmdLine, &m_fEmbedding, szTemp);
    g_fUser=!m_fEmbedding;

    m_dwRegCO=0;
    m_pIClassFactory=NULL;
    //End CHAPTER13MOD

    return;
    }

CPatronFrame::~CPatronFrame(void)
    {
    //CHAPTER13MOD
    //Opposite of CoRegisterClassObject, takes class factory ref to 1
    if (0L!=m_dwRegCO)
        CoRevokeClassObject(m_dwRegCO);

    //This should be the last Release, which frees the class factory.
    if (NULL!=m_pIClassFactory)
        m_pIClassFactory->Release();
    //End CHAPTER13MOD

    if (NULL!=m_pIClassDataTran)
        {
        m_pIClassDataTran->LockServer(FALSE);
        m_pIClassDataTran->Release();
        }

    OleFlushClipboard();

    if (m_fOleStdInit)
        OleStdUninitialize();

    if (m_fInitialized)
        OleUninitialize();
    return;
    }

/*
 * CPatronFrame::FInit
 *
 * Purpose:
 *  Call OleInitialize then calling down into the base class
 *  initialization.
 *
 * Parameters:
 *  pFI             PFRAMEINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CPatronFrame::FInit(PFRAMEINIT pFI)
    {
    DWORD       dwVer;
    HRESULT     hr;

    dwVer=OleBuildVersion();

    if (rmm!=HIWORD(dwVer))
        return FALSE;

    if (FAILED(OleInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    if (!OleStdInitialize(m_hInst))
        return FALSE;

    m_fOleStdInit=TRUE;

    //Lock the data transfer object factory as an optimization.
    hr=CoGetClassObject(CLSID_DataTransferObject
        , CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory
        , (PPVOID)&m_pIClassDataTran);

    if (SUCCEEDED(hr))
        m_pIClassDataTran->LockServer(TRUE);

    //CHAPTER13MOD
    if (m_fEmbedding)
        {
        HRESULT     hr;

        m_pIClassFactory=new CLinkClassFactory(this);

        if (NULL==m_pIClassFactory)
            return FALSE;

        //Since we hold on to this, we should AddRef it.
        m_pIClassFactory->AddRef();

        hr=CoRegisterClassObject(CLSID_PatronPages, m_pIClassFactory
            , CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE, &m_dwRegCO);

        if (FAILED(hr))
            return FALSE;
        }
    //End CHAPTER13MOD

    return CFrame::FInit(pFI);
    }

/*
 * CPatronFrame::CreateCClient
 *
 * Purpose:
 *  Constructs a new client specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient        Pointer to the new client object.
 */

PCClient CPatronFrame::CreateCClient(void)
    {
    return (PCClient)(new CPatronClient(m_hInst));
    }

/*
 * CPatronFrame::FRegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CPatronFrame::FRegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //First let the standard frame do its thing
    if (!CFrame::FRegisterAllClasses())
        return FALSE;

    //We need double-clicks now and for object activation later.
    wc.style         = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
    wc.hInstance     = m_hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PagesWndProc;
    wc.cbWndExtra    = CBPAGESWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPAGES;

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

    return TRUE;
    }

//CHAPTER13MOD
/*
 * CPatronFrame::FPreShowInit
 *
 * Purpose:
 *  Called from FInit before intially showing the window.  We do
 *  whatever else we want here, modifying m_nCmdShow as necessary
 *  which affects ShowWindow in FInit.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if this successful, FALSE otherwise.
 */

BOOL CPatronFrame::FPreShowInit(void)
    {
    //Base class does nothing
    CFrame::FPreShowInit();

    //Save the window handle for shutdown if necessary.
    g_hWnd=m_hWnd;

    //If we're -Embedding, don't show the window initially.
    if (m_fEmbedding)
        m_nCmdShow=SW_HIDE;

    return TRUE;
    }

/*
 * CPatronFrame::ParseCommandLine
 *
 * Purpose:
 *  Allows the application to parse the command line and take action
 *  after the window has possibly been shown.  For a compound
 *  document server we need to just make sure that if -Embedding is
 *  there that we take no file action.  FPreShowInit has already
 *  handled the window visibility.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if this successful, FALSE otherwise.
 */

void CPatronFrame::ParseCommandLine(void)
    {
    //If -Embedding was there, prevent any attempt to load a file.
    if (m_fEmbedding)
        return;

    CFrame::ParseCommandLine();
    return;
    }
//End CHAPTER13MOD

/*
 * CPatronFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the Patron frame window that processes extra
 *  File menu items as well as the Page menu.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CPatronFrame::OnCommand(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PCPatronDoc     pDoc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many GizmoBar notifications.
     */
    if (m_fInit)
        return 0L;

    pDoc=(PCPatronDoc)m_pCL->ActiveDocument();

    if (NULL!=pDoc && (IDM_VERBMIN <= wID) && (IDM_VERBMAX >= wID))
        {
        pDoc->ActivateObject(wID-IDM_VERBMIN);
        return 0L;
        }

    switch (wID)
        {
        case IDM_FILEPRINT:
            pDoc->Print(m_hWnd);
            return 0L;

        case IDM_FILEPRINTERSETUP:
            pDoc->PrinterSetup(m_hWnd, FALSE);
            return 0L;

        case IDM_EDITPASTESPECIAL:
            pDoc->FPasteSpecial(m_hWnd);
            return 0L;

        case IDM_EDITDELETEOBJECT:
            pDoc->Delete();
            return 0L;

        case IDM_EDITINSERTOBJECT:
            pDoc->FInsertObject(m_hWnd);
            return 0L;

        case IDM_EDITLINKS:
            pDoc->FEditLinks(m_hWnd);
            return 0L;

        case IDM_PAGENEWPAGE:
            pDoc->NewPage();
            break;

        case IDM_PAGEDELETEPAGE:
            pDoc->DeletePage();
            break;

        case IDM_PAGENEXTPAGE:
            pDoc->NextPage();
            break;

        case IDM_PAGEPREVIOUSPAGE:
            pDoc->PreviousPage();
            break;

        case IDM_PAGEFIRSTPAGE:
            pDoc->FirstPage();
            break;

        case IDM_PAGELASTPAGE:
            pDoc->LastPage();
            break;

        case IDM_PAGESHOWOBJECTS:
            {
            BOOL    fTemp;

            //First get the current state, then toggle it.
            fTemp=pDoc->FShowOrQueryObjectTypes(TRUE, FALSE);
            pDoc->FShowOrQueryObjectTypes(FALSE, !fTemp);
            }
            break;

        default:
           return CFrame::OnCommand(hWnd, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPatronFrame::CreateGizmos
 *
 * Purpose:
 *  Procedure to create all the necessary gizmobar buttons.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of gizmos added to the bar.
 */

UINT CPatronFrame::CreateGizmos(void)
    {
    UINT            iLast;
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;

    //Insert the standard ones.
    iLast=CFrame::CreateGizmos();

    //Remove Undo:  we don't use it.
    m_pGB->Remove(IDM_EDITUNDO);

    /*
     * Insert Print File Import in the 5th position and account
     * for it in iLast.
     */
    m_pGB->Add(utCmd, 4, IDM_FILEPRINT, m_dxB, m_dyB
        , NULL, NULL, 6, uState);

    iLast++;

    m_pGB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //Add New Page, and Delete Page
    m_pGB->Add(utCmd, iLast++, IDM_PAGENEWPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 2, uState);
    m_pGB->Add(utCmd, iLast++, IDM_PAGEDELETEPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 3, uState);

    m_pGB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //First, Prev, Next, Last pages.
    m_pGB->Add(utCmd, iLast++, IDM_PAGEFIRSTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 4, uState);
    m_pGB->Add(utCmd, iLast++, IDM_PAGEPREVIOUSPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 5, uState);
    m_pGB->Add(utCmd, iLast++, IDM_PAGENEXTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 6, uState);
    m_pGB->Add(utCmd, iLast++, IDM_PAGELASTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 7, uState);

    return iLast;
    }

/*
 * CPatronFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CPatronFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    PCPatronDoc     pDoc;
    BOOL            fOK=FALSE;
    BOOL            fCallDefault=TRUE;
    UINT            uTemp;
    UINT            uTempE;
    UINT            uTempD;

    pDoc=(PCPatronDoc)m_pCL->ActiveDocument();

    uTempE=MF_ENABLED | MF_BYCOMMAND;
    uTempD=MF_DISABLED | MF_GRAYED | MF_BYCOMMAND;
    uTemp=((NULL!=pDoc) ? uTempE : uTempD);

    if (m_phMenu[0]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_FILEPRINT, uTemp);

        if (NULL!=pDoc)
            fOK=pDoc->FQueryPrinterSetup();

        EnableMenuItem(hMenu, IDM_FILEPRINTERSETUP
            , (fOK) ? uTempE : uTempD);
        }

    if (m_phMenu[1]==hMenu)
        {
        if (NULL!=pDoc)
            fOK=pDoc->FQueryPaste();

        EnableMenuItem(hMenu, IDM_EDITPASTE
            , (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITPASTESPECIAL
            , (fOK) ? uTempE : uTempD);

        //Cut, Copy, Delete depends on there being a selection.
        if (NULL!=pDoc)
            fOK=pDoc->FQueryObjectSelected(hMenu);
        else
            fOK=FALSE;

        EnableMenuItem(hMenu, IDM_EDITCUT, (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITCOPY
            , (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITDELETEOBJECT
            , (fOK) ? uTempE : uTempD);

        EnableMenuItem(hMenu, IDM_EDITINSERTOBJECT, uTemp);

        if (NULL!=pDoc)
            fOK=pDoc->FQueryEnableEditLinks();
        else
            fOK=FALSE;

        EnableMenuItem(hMenu, IDM_EDITLINKS
            , (fOK) ? uTempE : uTempD);

        //We did the whole menu...
        fCallDefault=FALSE;
        }

    //Page menu
    if (m_phMenu[2]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_PAGENEWPAGE,      uTemp);
        EnableMenuItem(hMenu, IDM_PAGEDELETEPAGE,   uTemp);
        EnableMenuItem(hMenu, IDM_PAGENEXTPAGE,     uTemp);
        EnableMenuItem(hMenu, IDM_PAGEPREVIOUSPAGE, uTemp);
        EnableMenuItem(hMenu, IDM_PAGEFIRSTPAGE,    uTemp);
        EnableMenuItem(hMenu, IDM_PAGELASTPAGE,     uTemp);

        //Check the Show Objects command or not.
        if (NULL!=pDoc)
            fOK=pDoc->FShowOrQueryObjectTypes(TRUE, FALSE);
        else
            fOK=FALSE;

        CheckMenuItem(hMenu, IDM_PAGESHOWOBJECTS, MF_BYCOMMAND
            | ((fOK) ? MF_CHECKED : MF_UNCHECKED));
        EnableMenuItem(hMenu, IDM_PAGESHOWOBJECTS, uTemp);
        }

    if (fCallDefault)
        CFrame::UpdateMenus(hMenu, iMenu);

    return;
    }

/*
 * CPatronFrame::UpdateGizmos
 *
 * Purpose:
 *  Enables and disables gizmos depending on whether we have
 *  a document or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronFrame::UpdateGizmos(void)
    {
    PCDocument  pDoc;
    BOOL        fEnable;

    //Let the default hack on its gizmos.
    CFrame::UpdateGizmos();

    pDoc=m_pCL->ActiveDocument();
    fEnable=(NULL!=pDoc);

    //No document, disable just about everything
    m_pGB->Enable(IDM_FILEPRINT,        fEnable);
    m_pGB->Enable(IDM_FILEPRINTERSETUP, fEnable);

    m_pGB->Enable(IDM_PAGENEWPAGE,      fEnable);
    m_pGB->Enable(IDM_PAGEDELETEPAGE,   fEnable);
    m_pGB->Enable(IDM_PAGEFIRSTPAGE,    fEnable);
    m_pGB->Enable(IDM_PAGEPREVIOUSPAGE, fEnable);
    m_pGB->Enable(IDM_PAGENEXTPAGE,     fEnable);
    m_pGB->Enable(IDM_PAGELASTPAGE,     fEnable);

    return;
    }

/*
 * CPatronFrame::FMessageHook
 *
 * Purpose:
 *  Override of CFrame::FMessageHook so we can specifically trap
 *  WM_MENUSELECT messages for the Object verb menu to provide some
 *  meaningful information on the status strip.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return value
 *                  for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CPatronFrame::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    BOOL        fRet=FALSE;

    *pLRes=0;

    MENUSELECTPARAMS(wItem, wMenuFlags, hMenu);

    //CHAPTER13MOD
    /*
     * When closing, make sure any document create from the
     * class factory is saved since the object in it might
     * have been changed.  We want to save without showing
     * the user any message or asking if the user wants to save.
     */
    if (WM_CLOSE==iMsg)
        {
        if (NULL!=m_pDocCreated)
            {
            if (m_pDocCreated->FDirtyGet())
                {
                CHourglass  wait;
                m_pDocCreated->USave(0, NULL);
                }
            }

        return FALSE;
        }
    //End CHAPTER13MOD

    //If this is the wrong message, nothing to do.
    if (WM_MENUSELECT!=iMsg)
        return FALSE;

    //This happens when there's no menu selection.
    if (-1==wMenuFlags)
        return FALSE;

    if (MF_POPUP & wMenuFlags)
        {
        /*
         * If this is the cascade verb menu itself, display the same
         * message.  m_phMenu[1] contains the current edit menu
         * handle.
         */
        fRet=((HMENU)wItem==GetSubMenu(m_phMenu[1],MENUPOS_OBJECT));
        }
    else
        {
        /*
         * If the ID is in the verb range, use
         * IDS_ITEMMESSAGEEDITOBJECT message
         */
        fRet=(IDM_VERBMIN <= wItem && IDM_VERBMAX >= wItem);
        }

    if (fRet)
        m_pSS->MessageDisplay(IDM_EDITOBJECT);

    return fRet;
    }


PATRON.H

/*
 * PATRON.H
 * Patron Chapter 13
 *
 * Single include file that pulls in everything needed for other
 * source files in the application.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _PATRON_H_
#define _PATRON_H_

#define INC_OLE2
#include <windows.h>
#include <ole2.h>
#include <ole2ver.h>
#include <olestd.h>
#include <bookguid.h>

extern "C"
    {
    #include <commdlg.h>
   #ifndef WIN32
    #include <print.h>
   #endif
    }

#include <classlib.h>
#include "resource.h"
#include "pages.h"

/*
 * UINT value such that adding one produces zero.  Portable to Win32.
 * This is used to represent a non-existent zero-based UINT value
 */
#define NOVALUE                     ((UINT)-1)

//PATRON.CPP:  Frame object that creates a main window

class CPatronFrame : public CFrame
    {
    //CHAPTER13MOD
    //For access to m_pCL for document creation
    friend class CLinkClassFactory;
    //End CHAPTER13MOD

    private:
        BOOL            m_fInitialized;     //OleInitialize worked
        BOOL            m_fOleStdInit;      //OleStdInitialize worked
        LPCLASSFACTORY  m_pIClassDataTran;  //For locking.

        //CHAPTER13MOD
        PCDocument      m_pDocCreated;      //What class factory makes
        BOOL            m_fEmbedding;       //-Embedding on command line?
        DWORD           m_dwRegCO;          //From CoRegisterClassObject
        LPCLASSFACTORY  m_pIClassFactory;
        //End CHAPTER13MOD

    protected:
        //Overridable for creating a CPatronClient
        virtual PCClient    CreateCClient(void);

        virtual BOOL        FMessageHook(HWND, UINT, WPARAM, LPARAM
                                , LRESULT *);

        //CHAPTER13MOD
        virtual BOOL        FPreShowInit(void);
        virtual void        ParseCommandLine(void);
        //End CHAPTER13MOD

        virtual BOOL        FRegisterAllClasses(void);
        virtual UINT        CreateGizmos(void);
        virtual LRESULT     OnCommand(HWND, WPARAM, LPARAM);

    public:
        CPatronFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CPatronFrame(void);

        //Overrides
        virtual BOOL        FInit(PFRAMEINIT);

        virtual void        UpdateMenus(HMENU, UINT);
        virtual void        UpdateGizmos(void);

    };

typedef CPatronFrame *PCPatronFrame;

//CHAPTER13MOD

//These are global for simplification of object implementation.
extern ULONG g_cObj;
extern ULONG g_cLock;
extern HWND  g_hWnd;

//Type for an object-destroyed callback
typedef void (PASCAL *PFNDESTROYED)(void);

//Function for the object to notify on destruction.
void PASCAL ObjectDestroyed(void);

//ICLASSF.CPP

class CLinkClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;
        PCPatronFrame   m_pFR;          //For document creation.

    public:
        CLinkClassFactory(PCPatronFrame);
        ~CLinkClassFactory(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP         CreateInstance(LPUNKNOWN, REFIID
                                 , PPVOID);
        STDMETHODIMP         LockServer(BOOL);
    };

typedef CLinkClassFactory *PCLinkClassFactory;

//End CHAPTER13MOD

//CLIENT.CPP

/*
 * The only reason we have a derived class here is to override
 * CreateCDocument so we can create our own type as well as
 * overriding NewDocument to perform one other piece of work once
 * the document's been created.
 */

class CPatronClient : public CClient
    {
    protected:
        //Overridable for creating a new CDocument
        virtual PCDocument CreateCDocument(void);

    public:
        CPatronClient(HINSTANCE);
        virtual ~CPatronClient(void);
    };

typedef CPatronClient *PCPatronClient;

//DOCUMENT.CPP

//Constant ID for the pages window that lives in a document window
#define ID_PAGES            723

//CHAPTER13MOD
class CImpIPersistFile;
typedef class CImpIPersistFile *PIMPIPERSISTFILE;
class CImpIOleItemContainer;
typedef class CImpIOleItemContainer *PIMPIOLEITEMCONTAINER;

/*
 * To support linking to embeddings, the document needs to have
 * IPersistFile and IOleItemContainer interfaces, so we multiply
 * inherit from IUnknown and provide two interface implementations.
 */

class CPatronDoc : public CDocument, public IUnknown
//End CHAPTER13MOD
    {
    //These need access to FQueryPasteFromData, FPasteFromData
    friend class CDropTarget;
    friend class CDropSource;

    //CHAPTER13MOD
    friend class CImpIPersistFile;
    friend class CImpIOleItemContainer;
    //End CHAPTER13MOD

    protected:
        LONG            m_lVer;         //Loaded data version
        PCPages         m_pPG;          //Pages window in us
        LPSTORAGE       m_pIStorage;    //Root storage for document
        BOOL            m_fPrintSetup;

        class CDropTarget *m_pDropTarget;   //Registered target

        UINT            m_cfEmbeddedObject; //Clipboard formats
        UINT            m_cfObjectDescriptor;
        UINT            m_cfLinkSource;
        UINT            m_cfLinkSrcDescriptor;
        BOOL            m_fShowTypes;       //Show Objects active?

        //CHAPTER13MOD
        ULONG               m_cRef;
        BOOL                m_fRename;          //Rename on USave?
        DWORD               m_dwRegROT;

        PIMPIPERSISTFILE        m_pIPersistFile;
        PIMPIOLEITEMCONTAINER   m_pIOleItemContainer;
        //End CHAPTER13MOD

    protected:
        virtual BOOL    FMessageHook(HWND, UINT, WPARAM, LPARAM
            , LRESULT *);

        BOOL            FQueryPasteFromData(LPDATAOBJECT
                            , LPFORMATETC, PTENANTTYPE);
        BOOL            FQueryPasteLinkFromData(LPDATAOBJECT
                            , LPFORMATETC, PTENANTTYPE);
        BOOL            FPasteFromData(LPDATAOBJECT, LPFORMATETC
                            , TENANTTYPE, PPATRONOBJECT, DWORD
                            , BOOL);

    public:
        CPatronDoc(HINSTANCE, PCFrame);
        virtual ~CPatronDoc(void);

        //CHAPTER13MOD
        //We now need our ubiquitous set of IUnknown members.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
        //End CHAPTER13MOD

        virtual BOOL    FInit(PDOCUMENTINIT);
        virtual void    Clear(void);

        virtual BOOL    FDirtyGet(void);
        virtual void    Delete(void);
        virtual BOOL    FQueryPrinterSetup(void);
        virtual BOOL    FQueryObjectSelected(HMENU);

        virtual UINT    ULoad(BOOL, LPTSTR);
        virtual UINT    USave(UINT, LPTSTR);

        virtual BOOL    Print(HWND);
        virtual UINT    PrinterSetup(HWND, BOOL);

        virtual BOOL    FClip(HWND, BOOL);
        virtual BOOL    FQueryPaste(void);
        virtual BOOL    FPaste(HWND);
        virtual BOOL    FPasteSpecial(HWND);
        virtual BOOL    FQueryEnableEditLinks(void);
        virtual BOOL    FEditLinks(HWND);
        virtual BOOL    FShowOrQueryObjectTypes(BOOL, BOOL);

        virtual UINT    NewPage(void);
        virtual UINT    DeletePage(void);
        virtual UINT    NextPage(void);
        virtual UINT    PreviousPage(void);
        virtual UINT    FirstPage(void);
        virtual UINT    LastPage(void);

        virtual void    Rename(LPTSTR);
        virtual BOOL    FInsertObject(HWND);
        virtual void    ActivateObject(LONG);
    };

typedef CPatronDoc *PCPatronDoc;

//Hook for Print Dialog to hide Setup... button
UINT CALLBACK PrintDlgHook(HWND, UINT, WPARAM, LPARAM);

//CHAPTER13MOD

//IPersistFile implementation for CPatronDoc

class CImpIPersistFile : public IPersistFile
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCPatronDoc         m_pDoc;      //Back pointer to the object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIPersistFile(PCPatronDoc, LPUNKNOWN);
        ~CImpIPersistFile(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP Load(LPCOLESTR, DWORD);
        STDMETHODIMP Save(LPCOLESTR, BOOL);
        STDMETHODIMP SaveCompleted(LPCOLESTR);
        STDMETHODIMP GetCurFile(LPOLESTR *);
    };

/*
 * IOleItemContainer implementation for both CPatronDoc and CPage,
 * therefore have two back pointers.
 */

class CImpIOleItemContainer : public IOleItemContainer
    {
    protected:
        ULONG               m_cRef;
        class CPage        *m_pPage;
        PCPatronDoc         m_pDoc;      //Convenient naming & types
        LPUNKNOWN           m_pUnkOuter;
        BOOL                m_fDoc;      //Document or page?

    private:
        BOOL         FTenantFromName(LPTSTR, PCTenant *);

    public:
        CImpIOleItemContainer(LPVOID, LPUNKNOWN, BOOL);
        ~CImpIOleItemContainer(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP ParseDisplayName(LPBC, LPOLESTR, ULONG *
            , LPMONIKER *);
        STDMETHODIMP EnumObjects(DWORD, LPENUMUNKNOWN *);
        STDMETHODIMP LockContainer(BOOL);
        STDMETHODIMP GetObject(LPOLESTR, DWORD, LPBINDCTX, REFIID
            , PPVOID);
        STDMETHODIMP GetObjectStorage(LPOLESTR, LPBINDCTX, REFIID
            , PPVOID);
        STDMETHODIMP IsRunning(LPOLESTR);
    };

//End CHAPTER13MOD

//Drag-drop objects we need in the document

class CDropTarget : public IDropTarget
    {
    protected:
        ULONG               m_cRef;
        PCPatronDoc         m_pDoc;

        LPDATAOBJECT        m_pIDataObject;  //From DragEnter
        BOOL                m_fPendingRepaint;
        POINTL              m_ptPick;        //Pick-up offsets
        POINTL              m_ptLast;        //Last drag point
        SIZEL               m_szl;           //Object size
        BOOL                m_fFeedback;     //Draw feedback?
        FORMATETC           m_fe;            //Real dropping format

    public:
        CDropTarget(PCPatronDoc);
        ~CDropTarget(void);

        //IDropTarget interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP DragEnter(LPDATAOBJECT, DWORD, POINTL,LPDWORD);
        STDMETHODIMP DragOver(DWORD, POINTL, LPDWORD);
        STDMETHODIMP DragLeave(void);
        STDMETHODIMP Drop(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
    };

typedef CDropTarget *PCDropTarget;

class CDropSource : public IDropSource
    {
    protected:
        ULONG               m_cRef;

    public:
        CDropSource(void);
        ~CDropSource(void);

        //IDropSource interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP QueryContinueDrag(BOOL, DWORD);
        STDMETHODIMP GiveFeedback(DWORD);
    };

typedef CDropSource *PCDropSource;

#endif //_PATRON_H_


PRINT.CPP

/*
 * PRINT.CPP
 * Patron Chapter 13
 *
 * Implementation of printing functions for both CPatronDoc
 * and CPages classes.  These functions are here to keep clutter
 * down in document.cpp and pages.cpp.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

static HWND g_hDlgPrint=NULL;
static BOOL g_fCancelPrint=FALSE;

/*
 * CPatronDoc::Print
 *
 * Purpose:
 *  Prints the current document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *
 * Return Value:
 *  BOOL            TRUE if printing happened, FALSE if it didn't
 *                  start or didn't complete.
 */

BOOL CPatronDoc::Print(HWND hWndFrame)
    {
    PRINTDLG        pd;
    BOOL            fSuccess;

    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);
    pd.hwndOwner  =hWndFrame;
    pd.nCopies    =1;
    pd.nFromPage  =(USHORT)-1;
    pd.nToPage    =(USHORT)-1;
    pd.nMinPage   =1;
    pd.nMaxPage   =m_pPG->NumPagesGet();

    pd.lpfnPrintHook=PrintDlgHook;

    //Get the current document printer settings
    pd.hDevMode=m_pPG->DevModeGet();

    pd.Flags=PD_RETURNDC | PD_ALLPAGES | PD_COLLATE
        | PD_HIDEPRINTTOFILE | PD_NOSELECTION | PD_ENABLEPRINTHOOK;

    if (!PrintDlg(&pd))
        return FALSE;

    if (NULL!=pd.hDevMode)
        GlobalFree(pd.hDevMode);

    if (NULL!=pd.hDevNames)
        GlobalFree(pd.hDevNames);

    //Go do the actual printing.
    fSuccess=m_pPG->Print(pd.hDC, PSZ(IDS_DOCUMENTNAME), pd.Flags
        , pd.nFromPage, pd.nToPage, pd.nCopies);

    if (!fSuccess)
        {
        MessageBox(m_hWnd, PSZ(IDS_PRINTERROR)
            , PSZ(IDS_DOCUMENTCAPTION), MB_OK);
        }

    return fSuccess;
    }

/*
 * CPatronDoc::PrinterSetup
 *
 * Purpose:
 *  Selects a new printer and options for this document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *  fDefault        BOOL to avoid any dialog and just use the
 *                  default.
 *
 * Return Value:
 *  UINT            Undefined
 */

UINT CPatronDoc::PrinterSetup(HWND hWndFrame, BOOL fDefault)
    {
    PRINTDLG        pd;

    //Attempt to get printer metrics for the default printer.
    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);

    if (fDefault)
        pd.Flags=PD_RETURNDEFAULT;
    else
        {
        pd.hwndOwner=hWndFrame;
        pd.Flags=PD_PRINTSETUP;

        //Get the current document printer settings
        pd.hDevMode=m_pPG->DevModeGet();
        }

    if (!PrintDlg(&pd))
        return FALSE;

    if (!m_pPG->DevModeSet(pd.hDevMode, pd.hDevNames))
        {
        GlobalFree(pd.hDevNames);
        GlobalFree(pd.hDevMode);
        return FALSE;
        }

    FDirtySet(TRUE);
    return 1;
    }

/*
 * PrintDlgHook
 *
 * Purpose:
 *  Callback hook for the Print Dialog so we can hide the Setup
 *  button.  Patron only allows Setup before anything exists on
 *  the page, and is not written to handle setup at Print time.
 */

UINT CALLBACK PrintDlgHook(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    if (WM_INITDIALOG==iMsg)
        {
        HWND        hWnd;

        hWnd=GetDlgItem(hDlg, psh1);
        ShowWindow(hWnd, SW_HIDE);
        return TRUE;
        }

    return FALSE;
    }

/*
 * CPatronDoc::FQueryPrinterSetup
 *
 * Purpose:
 *  Returns whether or not the Printer Setup menu item can be
 *  enabled.  Once you create a tenant in any page, Printer Setup
 *  is voided simply to keep this sample simple, that is, we don't
 *  have to worry about reorganizing potentially large amounts
 *  of layout after we start plopping down objects.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE to enable the menu, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPrinterSetup(void)
    {
    return m_fPrintSetup;
    }

/*
 * CPages::DevModeSet
 *
 * Purpose:
 *  Provides the Pages with the current printer information.
 *
 * Parameters:
 *  hDevMode        HGLOBAL to the memory containing the DEVMODE.
 *                  This function assumes responsibility for this
 *                  handle.
 *  hDevNames       HGLOBAL providing the driver name and device
 *                  name from which we can create a DC for
 *                  information.
 *
 * Return Value:
 *  BOOL            TRUE if we could accept this configuration,
 *                  FALSE otherwise.  If we return TRUE we also
 *                  delete the old memory we hold.
 */

BOOL CPages::DevModeSet(HGLOBAL hDevMode, HGLOBAL hDevNames)
    {
    LPDEVNAMES      pdn;
    LPTSTR          psz;
    HGLOBAL         hMem;
    PDEVICECONFIG   pdc;
    LPDEVMODE       pdm;
    LPSTREAM        pIStream;
    HRESULT         hr;
    ULONG           cbDevMode, cbWrite;
    BOOL            fRet=FALSE;

    if (NULL==hDevMode || NULL==hDevNames)
        return FALSE;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    /*
     * DEVMODE is variable length--total length in hDevMode, so the
     * amount to write is that plus string space.  We subtract
     * sizeof(DEVMODE) as that is already included from GlobalSize.
     */
    cbDevMode=GlobalSize(hDevMode);
    cbWrite=cbDevMode+sizeof(DEVICECONFIG)-sizeof(DEVMODE);

    hMem=GlobalAlloc(GHND, cbWrite);

    if (NULL==hMem)
        {
        pIStream->Release();
        return FALSE;
        }

    pdc=(PDEVICECONFIG)GlobalLock(hMem);    //This always works
    pdm=(LPDEVMODE)GlobalLock(hDevMode);    //This might not

    if (NULL!=pdm)
        {
        pdc->cb=cbWrite;
        pdc->cbDevMode=cbDevMode;
        memcpy(&pdc->dm, pdm, (int)cbDevMode);
        GlobalUnlock(hDevMode);

        psz=(LPTSTR)GlobalLock(hDevNames);

        if (NULL!=psz)
            {
            pdn=(LPDEVNAMES)psz;
            lstrcpy(pdc->szDriver, psz+pdn->wDriverOffset);
            lstrcpy(pdc->szDevice, psz+pdn->wDeviceOffset);
            lstrcpy(pdc->szPort,   psz+pdn->wOutputOffset);

            pIStream->Write(pdc, cbWrite, &cbWrite);
            GlobalUnlock(hDevNames);
            fRet=TRUE;
            }
        }

    GlobalUnlock(hMem);
    GlobalFree(hMem);

    pIStream->Release();

    if (!fRet)
        return FALSE;

    GlobalFree(hDevNames);
    GlobalFree(hDevMode);

    return ConfigureForDevice();
    }

/*
 * CPages::DevModeGet
 *
 * Purpose:
 *  Retrieves a copy of the current DEVMODE structure for this
 *  Pages window.  The caller is responsible for this memory.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HGLOBAL         Handle to the memory containing the DEVMODE
 *                  structure.
 */

HGLOBAL CPages::DevModeGet(void)
    {
    HGLOBAL         hMem;
    LPVOID          pv;
    ULONG           cbDevMode, cbRead;
    LARGE_INTEGER   li;
    LPSTREAM        pIStream;
    HRESULT         hr;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    //Read how much to allocate for the DEVMODE structure
    LISet32(li, CBSEEKOFFSETCBDEVMODE);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);
    pIStream->Read(&cbDevMode, sizeof(ULONG), &cbRead);

    hMem=GlobalAlloc(GHND, cbDevMode);

    if (NULL!=hMem)
        {
        pv=(LPVOID)GlobalLock(hMem);
        pIStream->Read(pv, cbDevMode, &cbRead);
        GlobalUnlock(hMem);
        }

    pIStream->Release();
    return hMem;
    }

/*
 * CPages::DevReadConfig
 *
 * Purpose:
 *  Public function to read the current device configuration and
 *  optionally return an information context for it.
 *
 *
 * Parameters:
 *  ppcd            PCOMBINEDEVICE * in which to return a pointer
 *                  to an allocated structure that has all the
 *                  device information we want.  Ignored if NULL.
 *                  This is allocated with the task allocator.
 *  phDC            HDC * in which to return the information
 *                  context.  If NULL, no IC is created.  Caller
 *                  becomes responsible for the returned IC.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::DevReadConfig(PCOMBINEDEVICE *ppcd, HDC *phDC)
    {
    HRESULT         hr;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    PCOMBINEDEVICE  pcd;
    ULONG           cb, cbRead;
    LARGE_INTEGER   li;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    /*
     * Allocate the COMBINEDEVICE structure including the variable
     * information past the DEVMODE part.
     */

    hr=CoGetMalloc(MEMCTX_TASK, &pIMalloc);

    if (FAILED(hr))
        {
        pIStream->Release();
        return FALSE;
        }

    /*
     * Read size of the DEVICECONFIG structure including variable
     * portion of DEVMODE.  We need to load all this information
     * for CreateIC.  To this size we'll add the size of
     * DVTARGETDEVICE in order to allocate a COMBINEDEVICE.
     */

    pIStream->Read(&cb, sizeof(DWORD), &cbRead);

    pcd=(PCOMBINEDEVICE)pIMalloc->Alloc(cb+sizeof(DVTARGETDEVICE));

    if (NULL==pcd)
        {
        pIMalloc->Release();
        pIStream->Release();
        return FALSE;
        }

    //Now get the real information.
    LISet32(li, 0);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);
    pIStream->Read(&(pcd->dc), cb, &cbRead);
    pIStream->Release();

    /*
     * If requested, complete the DVTARGETDEVICE structure in
     * pcd and store pcd in *ppcd for return.
     */

    if (NULL!=ppcd)
        {
        WORD    cb=sizeof(DVTARGETDEVICE);

        pcd->td.tdSize=cb;
        pcd->td.tdExtDevmodeOffset=cb;
        pcd->td.tdDriverNameOffset=cb+sizeof(DEVMODE);
        pcd->td.tdDeviceNameOffset=cb+sizeof(DEVMODE)
            +(CCHDEVICENAME*sizeof(TCHAR));
        pcd->td.tdPortNameOffset  =cb+sizeof(DEVMODE)
            +(CCHDEVICENAME*2*sizeof(TCHAR));

        *ppcd=pcd;
        }

    //Create an IC if requested.
    if (NULL!=phDC)
        {
        //Get the DC then configure
        *phDC=CreateIC(pcd->dc.szDriver, pcd->dc.szDevice
            , pcd->dc.szPort, &(pcd->dc.dm));

        if (NULL==*phDC)
            return FALSE;
        }

    //pcd is a temporary allocation in this case
    if (NULL==ppcd)
        pIMalloc->Free(pcd);

    pIMalloc->Release();
    return (NULL!=*phDC);
    }

/*
 * CPages::ConfigureForDevice
 *
 * Purpose:
 *  Recalculates our drawing configuration based on the contents of
 *  an hDC.  If no HDC is given we use the contents of our DevMode
 *  stream.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::ConfigureForDevice(void)
    {
    POINT           ptOffset, ptPaper;
    RECT            rc;
    HDC             hDC;
    CHourglass      hg;

    if (!DevReadConfig(NULL, &hDC))
        return FALSE;

    //Get usable page dimensions:  already sensitive to orientation
    m_cx=GetDeviceCaps(hDC, HORZSIZE)*10-16; //*10: mm to LOMETRIC
    m_cy=GetDeviceCaps(hDC, VERTSIZE)*10-16; //-16: for driver bugs.

    //Calculate the printer-limited margins on sides in LOMETRIC.
    Escape(hDC, GETPRINTINGOFFSET, NULL, NULL, &ptOffset);
    Escape(hDC, GETPHYSPAGESIZE,   NULL, NULL, &ptPaper);

    SetRect(&rc, ptOffset.x, ptOffset.y, ptPaper.x, ptPaper.y);
    SetMapMode(hDC, MM_LOMETRIC);
    RectConvertMappings(&rc, hDC, FALSE);

    //Left and top margins are the printing offset.
    m_xMarginLeft= rc.left+8;   //+8 to match -16 above
    m_yMarginTop =-rc.top+8;    //LOMETRIC makes this negative.

    //Right is (paper width)-(usable width)-(left margin)
    m_xMarginRight =rc.right-m_cx-m_xMarginLeft;

    //Bottom is (paper height)-(usable height)-(top margin)+1
    m_yMarginBottom=-rc.bottom-m_cy-m_yMarginTop+1;

    UpdateScrollRanges();

    DeleteDC(hDC);
    return TRUE;
    }

/*
 * CPages::Print
 *
 * Purpose:
 *  Prints a specified range of pages to a given hDC.  Repeats for
 *  a given number of copies.
 *
 * Parameters:
 *  hDC             HDC to which we print.
 *  pszDoc          LPTSTR providing the document name.
 *  dwFlags         DWORD flags from PrintDlg
 *  iPageStart      UINT starting page index (one based)
 *  iPageEnd        UINT ending page index (one based).  Includes
 *                  this page.
 *  cCopies         UINT number of copies to print.  If PD_COLLATE
 *                  in dwFlags is set, we print multiple copies of
 *                  each page as we cycle through.  Otherwise we
 *                  cycle multiple times.
 *
 * Return Value:
 *  None
 */

BOOL CPages::Print(HDC hDC, LPTSTR pszDoc, DWORD dwFlags
    , UINT iPageStart, UINT iPageEnd, UINT cCopies)
    {
    BOOL        fError=FALSE;
    int         iPage, iPageInc;
    int         iUserPage, cPages;
    UINT        iRepeat, cRepeat;
    UINT        iCycle, cCycles;
    UINT        iPageHold=m_iPageCur;
    HWND        hWndT, hWndTop=NULL;
    DOCINFO     di;
    PCDocument  pDoc;

    //Validate hDC and page ranges
    if (NULL==hDC)
        return FALSE;

    if ((PD_PAGENUMS & dwFlags))
        {
        if (-1==iPageStart)
            iPageStart=0;
        else
            iPageStart--;   //Switch to zero offset.

        if (-1==iPageEnd)
            iPageEnd=m_cPages-1;
        else
            iPageEnd--;     //Switch to zero offset.
        }
    else //Can't test PD_ALLPAGES with & since it's defined as 0L
        {
        iPageStart=0;
        iPageEnd=m_cPages-1;
        }

    //Arrange cycles and repeats depending on cCopies and collating
    if (PD_COLLATE & dwFlags)
        {
        cCycles=cCopies;
        cRepeat=1;
        }
    else
        {
        cCycles=1;
        cRepeat=cCopies;
        }

    //Disable the frame window to prevent reentrancy while printing.
    hWndT=GetParent(m_hWnd);
    pDoc=(PCDocument)SendMessage(hWndT, DOCM_PDOCUMENT, 0, 0L);

    if (NULL!=pDoc)
        {
        PCFrame pFR;

        pFR=pDoc->FrameGet();
        hWndTop=pFR->Window();
        EnableWindow(hWndTop, FALSE);
        }

    SetAbortProc(hDC, AbortProc);
    g_fCancelPrint=FALSE;

    //If these don't work then we'll just live without a dialog.
    g_hDlgPrint=CreateDialog(m_hInst, MAKEINTRESOURCE(IDD_PRINTING)
        , hWndTop, PrintDlgProc);

    //Increment for either direction.
    iPageInc=(iPageStart > iPageEnd) ? -1 : 1;

    //Initial entries in dialog box.
    cPages=1+((int)(iPageEnd-iPageStart)*iPageInc);

    SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE, 1, (LPARAM)cPages);
    SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, 1, (LPARAM)cRepeat);

    di.cbSize=sizeof(DOCINFO);
    di.lpszDocName=pszDoc;
    di.lpszOutput=NULL;

    if (StartDoc(hDC, &di) > 0)
        {
        /*
         * Iterate over the pages, repeating each page depending on
         * the copies we want and if we have collate enabled.
         */

        for (iCycle=1; iCycle <= cCycles; iCycle++)
            {
            if (PD_COLLATE & dwFlags)
                {
                SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, iCycle
                    , (LPARAM)cCycles);
                }

            //iPageInc controls direction
            for (iPage=iPageStart; ; iPage+=iPageInc)
                {
                iUserPage=1+((iPage-(int)iPageStart)*iPageInc);

                SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE
                    , iUserPage, (LPARAM)cPages);

                m_iPageCur=iPage;   //We restore this later.

                for (iRepeat=1; iRepeat <= cRepeat; iRepeat++)
                    {
                    if (!(PD_COLLATE & dwFlags))
                        {
                        SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE
                            , iRepeat, (LPARAM)cRepeat);
                        }

                    StartPage(hDC);
                    Draw(hDC, TRUE, TRUE);

                    if (EndPage(hDC) < 0)
                        fError=TRUE;

                    if (fError || g_fCancelPrint)
                        break;
                    }

                if (fError || g_fCancelPrint)
                    break;

                //If we just printed the last page, time to quit.
                if (iPage==(int)iPageEnd)
                    break;
                }

            if (fError || g_fCancelPrint)
                break;
            }

        if (!fError)
            EndDoc(hDC);
        else
            AbortDoc(hDC);
        }
    else
        fError=TRUE;

    //Set the page back to what it was before all this started.
    m_iPageCur=iPageHold;

    EnableWindow(hWndTop, TRUE);
    SetFocus(hWndTop);
    DestroyWindow(g_hDlgPrint);
    DeleteDC(hDC);
    return !fError;
    }

/*
 * AbortProc
 *
 * Purpose:
 *  Abort procedure for printing the pages.
 *
 * Parameters:
 *  hDC             HDC on which printing is happening.
 *  iErr            int error code.
 *
 * Return Value:
 *  BOOL            TRUE to continue the print job, FALSE otherwise.
 */

BOOL APIENTRY AbortProc(HDC hDC, int iErr)
    {
    MSG     msg;

    while (!g_fCancelPrint
        && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
        if (NULL==g_hDlgPrint
            || !IsDialogMessage(g_hDlgPrint, &msg))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    return !g_fCancelPrint;
    }

/*
 * PrintDlgProc
 *
 * Purpose:
 *  Modeless dialog procedure for the dialog displayed while Patron
 *  is printing pages.
 */

BOOL APIENTRY PrintDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    TCHAR           szFormat[40];
    TCHAR           szOutput[80];

    switch (iMsg)
        {
        case WM_INITDIALOG:
            EnableMenuItem(GetSystemMenu(hDlg, FALSE), SC_CLOSE
                , MF_GRAYED);
            return TRUE;

        case WM_COMMAND:
            //Cancel button was pressed.
            g_fCancelPrint=TRUE;
            ShowWindow(hDlg, SW_HIDE);
            return TRUE;

        case PRINTM_PAGEUPDATE:
            GetDlgItemText(hDlg, ID_PAGESTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTPAGE, szOutput);
            return TRUE;

        case PRINTM_COPYUPDATE:
            GetDlgItemText(hDlg, ID_COPYSTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTCOPY, szOutput);
            return TRUE;
        }

    return FALSE;
    }


RESOURCE.H

/*
 * RESOURCE.H
 * Patron Chapter 13
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Note that we augment classlib.h which we include before this.

//Position of Window menu.
#undef  WINDOW_MENU
#define WINDOW_MENU                 3

//Position of the "Object" item on the Edit menu.
#define MENUPOS_OBJECT              8
#define MENUPOS_OBJECTONPOPUP       5

//Popup menu
#define IDR_RIGHTPOPUPMENU          2

//Total number of menus
#undef  CMENUS
#ifdef MDI
#define CMENUS                      5
#else
#define CMENUS                      4
#endif

//Menu command identifiers.

#define IDM_FILEPRINT               (IDM_CUSTOMFILEMIN)
#define IDM_FILEPRINTERSETUP        (IDM_CUSTOMFILEMIN+1)

#define IDM_EDITPASTESPECIAL        (IDM_CUSTOMEDITMIN)
#define IDM_EDITDELETEOBJECT        (IDM_CUSTOMEDITMIN+1)
#define IDM_EDITINSERTOBJECT        (IDM_CUSTOMEDITMIN+2)
#define IDM_EDITOBJECT              (IDM_CUSTOMEDITMIN+3)
#define IDM_EDITLINKS               (IDM_CUSTOMEDITMIN+4)

#define IDM_PAGENEWPAGE             (IDM_CUSTOMMIN)
#define IDM_PAGEDELETEPAGE          (IDM_CUSTOMMIN+1)
#define IDM_PAGENEXTPAGE            (IDM_CUSTOMMIN+2)
#define IDM_PAGEPREVIOUSPAGE        (IDM_CUSTOMMIN+3)
#define IDM_PAGEFIRSTPAGE           (IDM_CUSTOMMIN+4)
#define IDM_PAGELASTPAGE            (IDM_CUSTOMMIN+5)
#define IDM_PAGESHOWOBJECTS         (IDM_CUSTOMMIN+6)

//This special item is for the dynamic verbs on the Edit/Object item.
#define IDM_VERBMIN                 (IDM_CUSTOMMIN+100)
#define IDM_VERBMAX                 (IDM_CUSTOMMIN+200)

/*
 * IDs for StatStrip handling of popup menus:  must be in order of menu
 * CLASSRES.H already defines ID_MENUFILE and ID_MENUEDIT for us.
 */

#undef ID_MENUWINDOW
#undef ID_MENUHELP

#define ID_MENUPAGE                 (ID_MENUCUSTOMMIN+0)
#define ID_MENUWINDOW               (ID_MENUCUSTOMMIN+1)
#define ID_MENUHELP                 (ID_MENUCUSTOMMIN+2)

//Stringtable IDs.  Keep sequential for each group.
#define IDS_FRAMEMIN                IDS_STANDARDFRAMEMIN
#define IDS_FRAMEMAX                IDS_STANDARDFRAMEMAX

#define IDS_DOCUMENTMIN             IDS_STANDARDDOCMIN
#define IDS_PRINTERROR              (IDS_CUSTOMDOCMIN+0)
#define IDS_DOCUMENTNAME            (IDS_CUSTOMDOCMIN+1)
#define IDS_PASTEASPATRON           (IDS_CUSTOMDOCMIN+2)
#define IDS_PASTEMETAFILE           (IDS_CUSTOMDOCMIN+3)
#define IDS_PASTEASMETAFILE         (IDS_CUSTOMDOCMIN+4)
#define IDS_PASTEDIB                (IDS_CUSTOMDOCMIN+5)
#define IDS_PASTEASDIB              (IDS_CUSTOMDOCMIN+6)
#define IDS_PASTEBITMAP             (IDS_CUSTOMDOCMIN+7)
#define IDS_PASTEASBITMAP           (IDS_CUSTOMDOCMIN+8)
#define IDS_PASTEOBJECT             (IDS_CUSTOMDOCMIN+9)
#define IDS_PASTEASOBJECT           (IDS_CUSTOMDOCMIN+10)
#define IDS_PASTELINK               (IDS_CUSTOMDOCMIN+11)
#define IDS_PASTEASLINK             (IDS_CUSTOMDOCMIN+12)
//CHAPTER13MOD
#define IDS_EXTENSION               (IDS_CUSTOMDOCMIN+13)
#define IDS_DOCUMENTMAX             (IDS_CUSTOMDOCMIN+13)
//End CHAPTER13MOD

//These are specifically for the StatStrip
#define IDS_STATMESSAGEMIN                  IDS_STANDARDSTATMESSAGEMIN

#define IDS_ITEMMESSAGEFILEPRINT            (IDS_CUSTOMSTATMESSAGEMIN+0)
#define IDS_ITEMMESSAGEFILEPRINTERSETUP     (IDS_CUSTOMSTATMESSAGEMIN+1)

#define IDS_MENUMESSAGEPAGE                 (IDS_CUSTOMSTATMESSAGEMIN+2)
#define IDS_ITEMMESSAGEPAGENEWPAGE          (IDS_CUSTOMSTATMESSAGEMIN+3)
#define IDS_ITEMMESSAGEPAGEDELETEPAGE       (IDS_CUSTOMSTATMESSAGEMIN+4)
#define IDS_ITEMMESSAGEPAGENEXTPAGE         (IDS_CUSTOMSTATMESSAGEMIN+5)
#define IDS_ITEMMESSAGEPAGEPREVIOUSPAGE     (IDS_CUSTOMSTATMESSAGEMIN+6)
#define IDS_ITEMMESSAGEPAGEFIRSTPAGE        (IDS_CUSTOMSTATMESSAGEMIN+7)
#define IDS_ITEMMESSAGEPAGELASTPAGE         (IDS_CUSTOMSTATMESSAGEMIN+8)
#define IDS_ITEMMESSAGEEDITPASTESPECIAL     (IDS_CUSTOMSTATMESSAGEMIN+9)
#define IDS_ITEMMESSAGEEDITDELETEOBJECT     (IDS_CUSTOMSTATMESSAGEMIN+10)
#define IDS_ITEMMESSAGEEDITINSERTOBJECT     (IDS_CUSTOMSTATMESSAGEMIN+11)
#define IDS_ITEMMESSAGEEDITOBJECT           (IDS_CUSTOMSTATMESSAGEMIN+12)
#define IDS_ITEMMESSAGEEDITLINKS            (IDS_CUSTOMSTATMESSAGEMIN+13)
#define IDS_ITEMMESSAGEPAGESHOWOBJECTS      (IDS_CUSTOMSTATMESSAGEMIN+14)
#define IDS_STATMESSAGEMAX                  (IDS_CUSTOMSTATMESSAGEMIN+14)

//Definitions for the printing dialog.

#define IDD_PRINTING                2

#define ID_CURRENTPAGE              101
#define ID_CURRENTCOPY              102
#define ID_COPYSTRING               103
#define ID_PAGESTRING               104

#define PRINTM_PAGEUPDATE           (WM_USER+100)
#define PRINTM_COPYUPDATE           (WM_USER+101)

#endif //_RESOURCE_H_


TENANT.CPP

/*
 * TENANT.CPP
 * Patron Chapter 13
 *
 * Implementation of the CTentant class which holds information
 * for a single object on a page.  It maintains position, references
 * to data, and a storage.
 *
 * Copyright (c)1993-1997 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CTenant::CTenant
 * CTenant::~CTenant
 *
 * Constructor Parameters:
 *  dwID            DWORD identifier for this page.
 *  hWnd            HWND of the pages window.
 *  pPG             PCPages to the parent structure.
 */

CTenant::CTenant(DWORD dwID, HWND hWnd, PCPages pPG)
    {
    m_hWnd=hWnd;
    m_dwID=dwID;

    m_fInitialized=0;
    m_pIStorage=NULL;
    m_cOpens=0;

    m_pObj=NULL;
    m_pPG =pPG;
    m_clsID=CLSID_NULL;
    m_fSetExtent=FALSE;

    m_cRef=0;
    m_pIOleObject=NULL;
    m_pIViewObject2=NULL;

    m_pIOleClientSite=NULL;
    m_pIAdviseSink=NULL;

    m_pmkFile=NULL;
    m_fLinkAvail=TRUE;          //Checked on FLoad

    //CHAPTER13MOD
    m_pmk=NULL;
    //End CHAPTER13MOD
    return;
    }

CTenant::~CTenant(void)
    {
    //CHAPTER13MOD
    if (NULL!=m_pmk)
        m_pmk->Release();
    //End CHAPTER13MOD

    if (NULL!=m_pmkFile)
        m_pmkFile->Release();

    //Object pointers cleaned up in Close.

    //We delete our own interfaces since we control them
    if (NULL!=m_pIAdviseSink)
        delete m_pIAdviseSink;

    if (NULL!=m_pIOleClientSite)
        delete m_pIOleClientSite;

    return;
    }

/*
 * CTenant::QueryInterface
 * CTenant::AddRef
 * CTenant::Release
 *
 * Purpose:
 *  IUnknown members for CTenant object.
 */

STDMETHODIMP CTenant::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IOleClientSite==riid)
        *ppv=m_pIOleClientSite;

    if (IID_IAdviseSink2==riid || IID_IAdviseSink==riid)
        *ppv=m_pIAdviseSink;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return E_NOINTERFACE;
    }

STDMETHODIMP_(ULONG) CTenant::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CTenant::Release(void)
    {
    ULONG           cRefT;

    cRefT=--m_cRef;

    if (0L==m_cRef)
        delete this;

    return cRefT;
    }

/*
 * CTenant::GetID
 *
 * Return Value:
 *  DWORD           dwID field in this tenant.  This function is
 *                  only here to avoid hiding inline implementations
 *                  in tenant.h.
 */

DWORD CTenant::GetID(void)
    {
    return m_dwID;
    }

/*
 * CTenant::GetStorageName
 *
 * Parameters:
 *  pszName         LPTSTR to a buffer in which to store the storage
 *                  name for this tenant.
 *
 * Return Value:
 *  UINT            Number of characters stored.
 */

UINT CTenant::GetStorageName(LPTSTR pszName)
    {
    return wsprintf(pszName, TEXT("Tenant %lu"), m_dwID);
    }

/*
 * CTenant::UCreate
 *
 * Purpose:
 *  Creates a new tenant of the given CLSID, which can be either a
 *  static bitmap or metafile or any compound document object.
 *
 * Parameters:
 *  tType           TENANTTYPE to create, either a static metafile,
 *                  bitmap, or some kind of compound document object
 *                  This determines which OleCreate* call we use.
 *  pvType          LPVOID providing the relevant pointer from which
 *                  to create the tenant, depending on iType.
 *  pFE             LPFORMATETC specifying the type of renderings
 *                  to use.
 *  pptl            LPPOINTL in which we store offset coordinates.
 *  pszl            LPSIZEL where this object should store its
 *                  lometric extents.
 *  pIStorage       LPSTORAGE of the page we live in.  We have to
 *                  create another storage in this for the tenant.
 *  ppo             PPATRONOBJECT containing placement data.
 *  dwData          DWORD with extra data, sensitive to iType.
 *
 * Return Value:
 *  UINT            A UCREATE_* value depending on what we
 *                  actually do.
 */

UINT CTenant::UCreate(TENANTTYPE tType, LPVOID pvType
    , LPFORMATETC pFE, LPPOINTL pptl, LPSIZEL pszl
    , LPSTORAGE pIStorage, PPATRONOBJECT ppo, DWORD dwData)
    {
    HRESULT             hr;
    LPUNKNOWN           pObj;
    UINT                uRet=UCREATE_GRAPHICONLY;

    if (NULL==pvType || NULL==pIStorage)
        return UCREATE_FAILED;

    //Fail if this is called for an already living tenant.
    if (m_fInitialized)
        return UCREATE_FAILED;

    m_fInitialized=TRUE;

    //Create a new storage for this tenant.
    if (!FOpen(pIStorage))
        return UCREATE_FAILED;

    /*
     * Get the placement info if it's here.  We either have a non-
     * NULL PPATRONOBJECT in ppo or we have to use default
     * placement and retrieve the size from the object itself.
     */
    pszl->cx=0;
    pszl->cy=0;

    if (NULL!=ppo)
        {
        *pFE=ppo->fe;
        *pptl=ppo->ptl;
        *pszl=ppo->szl;     //Could be 0,0 , so we ask object

        uRet=UCREATE_PLACEDOBJECT;
        }

    hr=E_FAIL;

    //Now create an object based specifically for the type.
    switch (tType)
        {
        case TENANTTYPE_NULL:
            break;

        case TENANTTYPE_STATIC:
            /*
             * We could use OleCreateStaticFromData here which does
             * pretty much what we're doing below.  However, it does
             * not allow us to control whether we paste a bitmap or
             * a metafile--it uses metafile first, bitmap second.
             * For this reason we'll use code developed in Chapter
             * 6's FreeLoader to affect the paste.
             */
            hr=CreateStatic((LPDATAOBJECT)pvType, pFE, &pObj);
            break;

        case TENANTTYPE_EMBEDDEDOBJECT:
            hr=OleCreate(*((LPCLSID)pvType), IID_IUnknown
                , OLERENDER_DRAW, NULL, NULL, m_pIStorage
                , (PPVOID)&pObj);
            break;

        case TENANTTYPE_EMBEDDEDFILE:
#if defined(WIN32) && !defined(UNICODE)
            OLECHAR pwcsFile[MAX_PATH];
            mbstowcs(pwcsFile, (LPTSTR)pvType, MAX_PATH);
            hr=OleCreateFromFile(CLSID_NULL, pwcsFile
                , IID_IUnknown, OLERENDER_DRAW, NULL, NULL
                , m_pIStorage, (PPVOID)&pObj);
#else
            hr=OleCreateFromFile(CLSID_NULL, (LPTSTR)pvType
                , IID_IUnknown, OLERENDER_DRAW, NULL, NULL
                , m_pIStorage, (PPVOID)&pObj);
#endif
            break;

        case TENANTTYPE_EMBEDDEDOBJECTFROMDATA:
            hr=OleCreateFromData((LPDATAOBJECT)pvType, IID_IUnknown
                , OLERENDER_DRAW, NULL, NULL, m_pIStorage
                , (PPVOID)&pObj);
            break;

        case TENANTTYPE_LINKEDFILE:
#if defined(WIN32) && !defined(UNICODE)
            OLECHAR pwcsType[256];
            mbstowcs(pwcsType, (LPTSTR)pvType, 256);
            hr=OleCreateLinkToFile(pwcsType, IID_IUnknown
                , OLERENDER_DRAW, NULL, NULL, m_pIStorage
                , (PPVOID)&pObj);
#else
            hr=OleCreateLinkToFile((LPTSTR)pvType, IID_IUnknown
                , OLERENDER_DRAW, NULL, NULL, m_pIStorage
                , (PPVOID)&pObj);
#endif
            break;

        case TENANTTYPE_LINKEDOBJECTFROMDATA:
            hr=OleCreateLinkFromData((LPDATAOBJECT)pvType
                , IID_IUnknown, OLERENDER_DRAW, NULL, NULL
                , m_pIStorage, (PPVOID)&pObj);
            break;

        default:
            break;
        }

    //If creation didn't work, get rid of the element FOpen created.
    if (FAILED(hr))
        {
        Destroy(pIStorage);
        return UCREATE_FAILED;
        }

    //We don't get the size if PatronObject data was seen already.
    if (!FObjectInitialize(pObj, pFE, dwData))
        {
        Destroy(pIStorage);
        return UCREATE_FAILED;
        }

    if (0==pszl->cx && 0==pszl->cy)
        {
        SIZEL   szl;

        //Try to get the real size of the object, default to 2"*2"
        SETSIZEL((*pszl), 2*LOMETRIC_PER_INCH, 2*LOMETRIC_PER_INCH);
        hr=E_FAIL;

        //Try IViewObject2 first, then IOleObject as a backup.
        if (NULL!=m_pIViewObject2)
            {
            hr=m_pIViewObject2->GetExtent(m_fe.dwAspect, -1, NULL
                , &szl);
            }
        else
            {
            if (NULL!=m_pIOleObject)
                hr=m_pIOleObject->GetExtent(m_fe.dwAspect, &szl);
            }

        if (SUCCEEDED(hr))
            {
            //Convert HIMETRIC to our LOMETRIC mapping
            SETSIZEL((*pszl), szl.cx/10, szl.cy/10);
            }
        }

    return uRet;
    }

/*
 * CTenant::FLoad
 *
 * Purpose:
 *  Recreates the object living in this tenant in place of calling
 *  FCreate.  This is used in loading as opposed to new creation.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE of the page we live in.
 *  pti             PTENTANTINFO containing persistent information.
 *                  The ID value in this structure is ignored.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CTenant::FLoad(LPSTORAGE pIStorage, PTENANTINFO pti)
    {
    HRESULT         hr;
    LPUNKNOWN       pObj;

    if (NULL==pIStorage || NULL==pti)
        return FALSE;

    //Fail if this is called for an already living tenant.
    if (m_fInitialized)
        return FALSE;

    m_fInitialized=TRUE;

    //Open the storage for this tenant.
    if (!FOpen(pIStorage))
        return FALSE;

    /*
     * NOTE:  If you do not pass an IOleClientSite to OleLoad
     * it will not automatically reconnect a linked object to
     * the running source via IOleLink::BindIfRunning.  Since
     * we do not pass m_pIOleClientSite here, we'll call
     * BindIfRunning ourselves in FObjectInitialize.
     */
    hr=OleLoad(m_pIStorage, IID_IUnknown, NULL, (PPVOID)&pObj);

    if (FAILED(hr))
        {
        Destroy(pIStorage);
        return FALSE;
        }

    m_fSetExtent=pti->fSetExtent;
    FObjectInitialize(pObj, &pti->fe, NULL);

    RectSet(&pti->rcl, FALSE, FALSE);
    return TRUE;
    }

/*
 * CTenant::GetInfo
 *
 * Purpose:
 *  Retrieved a TENANTINFO structure for this tenant.
 *
 * Parameters:
 *  pti             PTENANTINFO structure to fill
 *
 * Return Value:
 *  None
 */

void CTenant::GetInfo(PTENANTINFO pti)
    {
    if (NULL!=pti)
        {
        pti->dwID=m_dwID;
        pti->rcl=m_rcl;
        pti->fe=m_fe;
        pti->fSetExtent=m_fSetExtent;
        }

    return;
    }

/*
 * CTenant::FObjectInitialize
 * (Protected)
 *
 * Purpose:
 *  Performs operations necessary after creating an object or
 *  reloading one from storage.
 *
 * Parameters:
 *  pObj            LPUNKNOWN of the object in this tenant.
 *  pFE             LPFORMATETC describing the graphic here.
 *  dwData          DWORD extra data.  If pFE->dwAspect==
 *                  DVASPECT_ICON then this is the iconic metafile.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CTenant::FObjectInitialize(LPUNKNOWN pObj, LPFORMATETC pFE
    , DWORD dwData)
    {
    HRESULT         hr;
    LPPERSIST       pIPersist=NULL;
    DWORD           dw;
    PCDocument      pDoc;
    TCHAR           szFile[CCHPATHMAX];
    LPOLELINK       pIOleLink=NULL;

    if (NULL==pObj || NULL==pFE)
        return FALSE;

    m_pObj=pObj;
    m_fe=*pFE;
    m_fe.ptd=NULL;
    m_dwState=TENANTSTATE_DEFAULT;

    /*
     * Determine the type:  Static or Embedded.  If Static,
     * this will have CLSID_Picture_Metafile or CLSID_Picture_Dib.
     * Otherwise it's embedded.  Later we'll add a case for links.
     */
    m_tType=TENANTTYPE_EMBEDDEDOBJECT;

    if (SUCCEEDED(pObj->QueryInterface(IID_IPersist
        , (PPVOID)&pIPersist)))
        {
        CLSID   clsid=CLSID_NULL;

        hr=pIPersist->GetClassID(&clsid);

        //If we don't have a CLSID, default to static
        if (FAILED(hr) || CLSID_Picture_Metafile==clsid
            || CLSID_Picture_Dib==clsid)
            m_tType=TENANTTYPE_STATIC;

        pIPersist->Release();
        }

    //Check if this is a linked object.
    if (SUCCEEDED(pObj->QueryInterface(IID_IOleLink
        , (PPVOID)&pIOleLink)))
        {
        LPMONIKER   pmk;

        hr=pIOleLink->GetSourceMoniker(&pmk);

        if (FAILED(hr) || NULL==pmk)
            m_tType=TENANTTYPE_STATIC;
        else
            {
            m_tType=TENANTTYPE_LINKEDOBJECT;
            pmk->Release();

            //Connect to the object if the source is running.
            pIOleLink->BindIfRunning();
            }

        pIOleLink->Release();
        }

    m_pIViewObject2=NULL;
    hr=pObj->QueryInterface(IID_IViewObject2
        , (PPVOID)&m_pIViewObject2);

    if (FAILED(hr))
        return FALSE;

    m_pIViewObject2->SetAdvise(m_fe.dwAspect, 0, m_pIAdviseSink);

    //We need an IOleObject most of the time, so get one here.
    m_pIOleObject=NULL;
    hr=pObj->QueryInterface(IID_IOleObject
         , (PPVOID)&m_pIOleObject);

    /*
     * Follow up object creation with advises and so forth.  If
     * we cannot get IOleObject here, then we know we can't do
     * any IOleObject actions from here on--object is static.
     */
    if (FAILED(hr))
        return TRUE;

    /*
     * Get the MiscStatus bits and check for OLEMISC_ONLYICONIC.
     * If set, force dwAspect in m_fe to DVASPECT_ICON so we
     * remember to draw it properly and do extents right.
     */
    m_pIOleObject->GetMiscStatus(m_fe.dwAspect, &m_grfMisc);

    if (OLEMISC_ONLYICONIC & m_grfMisc)
        m_fe.dwAspect=DVASPECT_ICON;

    /*
     * We could pass m_pIOleClientSite in an OleCreate* call, but
     * since this function could be called after OleLoad, we still
     * need to do this here, so it's always done here...
     */
    m_pIOleObject->SetClientSite(m_pIOleClientSite);
    m_pIOleObject->Advise(m_pIAdviseSink, &dw);

    OleSetContainedObject(m_pIOleObject, TRUE);

    /*
     * For IOleObject::SetHostNames we need the application name
     * and the document name (which is passed in the object
     * parameter).  The design of Patron doesn't give us nice
     * structured access to the name of the document we're in, so
     * I grab the parent of the Pages window (the document) and
     * send it DOCM_PDOCUMENT which returns us the pointer.
     * Roundabout, but it works.
     */

    pDoc=(PCDocument)SendMessage(GetParent(m_hWnd), DOCM_PDOCUMENT
        , 0, 0L);

    if (NULL!=pDoc)
        pDoc->FilenameGet(szFile, CCHPATHMAX);
    else
        szFile[0]=0;

    //CHAPTER13MOD
    NotifyOfRename(szFile, NULL, NULL);
    //End CHAPTER13MOD

    /*
     * This might have been Display as Icon (or the
     * OLEMISC_ONLYICONIC flag was set) in which case
     * m_fe.dwAspect=DVASPECT_ICON.  If this is coming from
     * a Display As Icon choice, then then dwData is a handle
     * to a metafile with the iconic aspect.  We take this and shove
     * it into the cache for this aspect, releasing the content
     * aspect.  OLESTD has a nice function that does this:
     * OleStdSwitchDisplayAspect, which also handles a later case
     * (Chapter 14) when we might want to switch BACK to content.
     * That, however, requires the Change Type dialog...
     *
     * If the object is marked as OLEMISC_ONLYICONIC, then it
     * is responsible for rendering the iconic image--not us.
     */

    if ((DVASPECT_ICON & m_fe.dwAspect) && NULL!=dwData)
        {
        //Temps to give to OleStdSwitchDisplayAspect
        DWORD       dw=DVASPECT_CONTENT;
        BOOL        fUpdate;

        OleStdSwitchDisplayAspect(m_pIOleObject, &dw, DVASPECT_ICON
            , (HGLOBAL)(UINT)dwData, TRUE, FALSE, NULL, &fUpdate);
        }

    return TRUE;
    }

/*
 * CTenant::FOpen
 *
 * Purpose:
 *  Retrieves the IStorage associated with this tenant.  The
 *  IStorage is owned by the tenant and thus the tenant always
 *  holds a reference count.
 *
 *  If the storage is already open for this tenant, then this
 *  function will AddRef it; therefore the caller must always
 *  match an FOpen with a Close.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE above this tenant (which has its
 *                  own storage).
 *
 * Return Value:
 *  BOOL            TRUE if opening succeeds, FALSE otherwise.
 */

BOOL CTenant::FOpen(LPSTORAGE pIStorage)
    {
    HRESULT     hr=NOERROR;
    DWORD       dwMode=STGM_TRANSACTED | STGM_READWRITE
                    | STGM_SHARE_EXCLUSIVE;
    TCHAR       szTemp[32];

    if (NULL==m_pIStorage)
        {
        if (NULL==pIStorage)
            return FALSE;

        /*
         * Attempt to open the storage under this ID.  If there is
         * none, then create it.  In either case we end up with an
         * IStorage that we either save in pPage or release.
         */

        GetStorageName(szTemp);
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsTemp[32];
        mbstowcs(pwcsTemp, szTemp, 32);
#else
        OLECHAR * pwcsTemp = szTemp;
#endif
        hr=pIStorage->OpenStorage(pwcsTemp, NULL, dwMode, NULL, 0
            , &m_pIStorage);

        if (FAILED(hr))
            {
            hr=pIStorage->CreateStorage(pwcsTemp, dwMode, 0, 0
                , &m_pIStorage);
            }
        }
    else
        m_pIStorage->AddRef();

    if (FAILED(hr))
        return FALSE;

    m_cOpens++;

    m_pIOleClientSite=new CImpIOleClientSite(this, this);
    m_pIAdviseSink=new CImpIAdviseSink(this, this);

    if (NULL==m_pIOleClientSite || NULL==m_pIAdviseSink)
        return FALSE;

    return TRUE;
    }

/*
 * CTenant::Close
 *
 * Purpose:
 *  Possibly commits the storage, then releases it reversing the
 *  reference count from FOpen.  If the reference on the storage
 *  goes to zero, the storage is forgotten.  However, the object we
 *  contain is still held and as long as it's active the storage
 *  remains alive.
 *
 * Parameters:
 *  fCommit         BOOL indicating if we're to commit.
 *
 * Return Value:
 *  None
 */

void CTenant::Close(BOOL fCommit)
    {
    if (fCommit)
        Update();

    if (NULL!=m_pIStorage)
        {
        m_pIStorage->Release();

        /*
         * We can't use a zero reference count to know when to NULL
         * this since other things might have AddRef'd the storage.
         */
        if (0==--m_cOpens)
            {
            m_pIStorage=NULL;

            //Close the object saving if necessary
            if (NULL!=m_pIOleObject)
                {
                m_pIOleObject->Close(OLECLOSE_SAVEIFDIRTY);
                m_pIOleObject->Release();
                m_pIOleObject=NULL;
                }

            //Release all other held pointers
            if (NULL!=m_pIViewObject2)
                {
                m_pIViewObject2->SetAdvise(m_fe.dwAspect, 0, NULL);
                m_pIViewObject2->Release();
                m_pIViewObject2=NULL;
                }

            if (NULL!=m_pObj)
                {
                //We know we only hold one ref from UCreate or FLoad
                m_pObj->Release();
                m_pObj=NULL;
                }
            }
        }

    return;
    }

/*
 * CTenant::Update
 *
 * Purpose:
 *  Forces a common on the page if it's open.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the object is open, FALSE otherwise.
 */

BOOL CTenant::Update(void)
    {
    LPPERSISTSTORAGE    pIPS;

    if (NULL!=m_pIStorage)
        {
        /*
         * We need to OleSave again because we might have changed
         * the size or position of this tenant.  We also need to
         * save the rectangle on the page, since that's not known
         * to OLE.
         */
        m_pObj->QueryInterface(IID_IPersistStorage, (PPVOID)&pIPS);

        //This fails for static objects...so we improvise if so
        if (FAILED(OleSave(pIPS, m_pIStorage, TRUE)))
            {
            //This is essentially what OleSave does.
            WriteClassStg(m_pIStorage, m_clsID);
            pIPS->Save(m_pIStorage, TRUE);
            }

        pIPS->SaveCompleted(NULL);
        pIPS->Release();

        m_pIStorage->Commit(STGC_ONLYIFCURRENT);
        }

    return FALSE;
    }

/*
 * CTenant::Destroy
 *
 * Purpose:
 *  Removes this page from the given storage.  The caller should
 *  eventually delete this CTenant object to free the object herein.
 *  Nothing is committed when being destroyed.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE contianing this page on which to call
 *                  DestroyElement
 *
 * Return Value:
 *  None
 */

void CTenant::Destroy(LPSTORAGE pIStorage)
    {
    TCHAR       szTemp[32];

    if (NULL!=pIStorage)
        {
        if (NULL!=m_pIOleObject)
            m_pIOleObject->Close(OLECLOSE_NOSAVE);

        if (NULL!=m_pIStorage)
            {
            //Remove all reference/open counts on this storage.
            while (0!=m_cOpens)
                {
                m_pIStorage->Release();
                m_cOpens--;
                }
            }

        GetStorageName(szTemp);
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsTemp[32];
        mbstowcs(pwcsTemp, szTemp, 32);
        pIStorage->DestroyElement(pwcsTemp);
#else
        pIStorage->DestroyElement(szTemp);
#endif
        m_pIStorage=NULL;
        }

    //m_pObj is released in destructor.
    return;
    }

/*
 * CTenant::Select
 *
 * Purpose:
 *  Selects or deselects the tenant.
 *
 * Parameters:
 *  fSelect         BOOL indicating the new state of the tenant.
 *
 * Return Value:
 *  None
 */

void CTenant::Select(BOOL fSelect)
    {
    BOOL        fWasSelected;
    DWORD       dwState;
    RECT        rc;
    HDC         hDC;

    fWasSelected=(BOOL)(TENANTSTATE_SELECTED & m_dwState);

    //Nothing to do when there's no change.
    if (fWasSelected==fSelect)
        return;

    dwState=m_dwState & ~TENANTSTATE_SELECTED;
    m_dwState=dwState | ((fSelect) ? TENANTSTATE_SELECTED : 0);

    /*
     * Draw sizing handles to show the selection state.  We convert
     * things to MM_TEXT since that's what this function expects.
     */

    RECTFROMRECTL(rc, m_rcl);
    RectConvertMappings(&rc, NULL, TRUE);
    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

    hDC=GetDC(m_hWnd);

    OleUIDrawHandles(&rc, hDC, OLEUI_HANDLES_INSIDE
        | OLEUI_HANDLES_NOBORDER| OLEUI_HANDLES_USEINVERSE
        , CXYHANDLE, !fWasSelected);

    ReleaseDC(m_hWnd, hDC);
    return;
    }

/*
 * CTenant::ShowAsOpen
 *
 * Purpose:
 *  Draws or removes the hatch pattern over an object.
 *
 * Parameters:
 *  fOpen           BOOL indicating the open state of this tenant.
 *
 * Return Value:
 *  None
 */

void CTenant::ShowAsOpen(BOOL fOpen)
    {
    BOOL        fWasOpen;
    DWORD       dwState;
    RECT        rc;
    HDC         hDC;

    fWasOpen=(BOOL)(TENANTSTATE_OPEN & m_dwState);

    dwState=m_dwState & ~TENANTSTATE_OPEN;
    m_dwState=dwState | ((fOpen) ? TENANTSTATE_OPEN : 0);

    //If this was not open, then just hatch, otherwise repaint.
    if (!fWasOpen && fOpen)
        {
        RECTFROMRECTL(rc, m_rcl);
        RectConvertMappings(&rc, NULL, TRUE);
        OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

        hDC=GetDC(m_hWnd);
        OleUIDrawShading(&rc, hDC, OLEUI_SHADE_FULLRECT, 0);
        ReleaseDC(m_hWnd, hDC);
        }

    if (fWasOpen && !fOpen)
        Repaint();

    return;
    }

/*
 * CTenant::ShowYourself
 *
 * Purpose:
 *  Function that really just implements IOleClientSite::ShowObject.
 *  Here we first check if the tenant is fully visible, and if so,
 *  then nothing needs to happen.  Otherwise, if the upper left
 *  corner of the tenant is in the upper left visible quadrant of
 *  the window, we'll also consider ourselves done.  Otherwise
 *  we'll put the upper left corner of the object at the upper left
 *  corner of the window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CTenant::ShowYourself(void)
    {
    RECTL       rcl;
    RECT        rc;
    POINT       pt1, pt2;

    //Scrolling deals in device units; get our rectangle in those.
    RectGet(&rcl, TRUE);

    //Get the window rectangle offset for the current scroll pos.
    GetClientRect(m_hWnd, &rc);
    OffsetRect(&rc, m_pPG->m_xPos, m_pPG->m_yPos);

    //Check if the object is already visible. (macro in bookguid.h)
    SETPOINT(pt1, (int)rcl.left,  (int)rcl.top);
    SETPOINT(pt2, (int)rcl.right, (int)rcl.bottom);

    if (PtInRect(&rc, pt1) && PtInRect(&rc, pt2))
        return;

    //Check if the upper left is within the upper left quadrant
    if (((int)rcl.left > rc.left
        && (int)rcl.left < ((rc.right+rc.left)/2))
        && ((int)rcl.top > rc.top
        && (int)rcl.top < ((rc.bottom+rc.top)/2)))
        return;

    //These are macros in INC\BOOK1632.H
    SendScrollPosition(m_hWnd, WM_HSCROLL, rcl.left-8);
    SendScrollPosition(m_hWnd, WM_VSCROLL, rcl.top-8);
    return;
    }

/*
 * CTenant::AddVerbMenu
 *
 * Purpose:
 *  Creates the variable verb menu item for the object in this
 *  tenant.
 *
 * Parmeters:
 *  hMenu           HMENU on which to add items.
 *  iPos            UINT position on that menu to add items.
 *
 * Return Value:
 *  None
 */

void CTenant::AddVerbMenu(HMENU hMenu, UINT iPos)
    {
    HMENU       hMenuTemp;
    LPOLEOBJECT pObj=m_pIOleObject;

    //If we're static, say we have no object.
    if (TENANTTYPE_STATIC==m_tType)
        pObj=NULL;

    OleUIAddVerbMenu(pObj, NULL, hMenu, iPos, IDM_VERBMIN
        , IDM_VERBMAX, FALSE, 0, &hMenuTemp);

    return;
    }

/*
 * CTenant::CopyEmbeddedObject
 *
 * Purpose:
 *  Copies an embedded object to the given data object (via SetData,
 *  assuming this is a data transfer object for clipboard/drag-drop)
 *  if that's what we're holding.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT in which to store the copy.
 *  pFE             LPFORMATETC into which to copy CF_EMBEDDEDOBJECT
 *                  if we put that in the data object.
 *  pptl            LPPOINTL to the pick point (NULL outside of
 *                  drag-drop);
 *
 * Return Value:
 *  None
 */

void CTenant::CopyEmbeddedObject(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, LPPOINTL pptl)
    {
    LPPERSISTSTORAGE    pIPS;
    STGMEDIUM           stm;
    FORMATETC           fe;
    HRESULT             hr;
    UINT                cf;
    POINTL              ptl;
    SIZEL               szl;

    //Can only copy embeddings.
    if (TENANTTYPE_EMBEDDEDOBJECT!=m_tType || NULL==m_pIOleObject)
        return;

    if (NULL==pptl)
        {
        SETPOINTL(ptl, 0, 0);
        pptl=&ptl;
        }

    /*
     * Create CF_EMBEDDEDOBJECT.  This is simply an IStorage with a
     * copy of the embedded object in it.  The not-so-simple part is
     * getting an IStorage to stuff it in.  For this operation we'll
     * use a temporary compound file.
     */

    stm.pUnkForRelease=NULL;
    stm.tymed=TYMED_ISTORAGE;
    hr=StgCreateDocfile(NULL, STGM_TRANSACTED | STGM_READWRITE
        | STGM_CREATE| STGM_SHARE_EXCLUSIVE | STGM_DELETEONRELEASE
        , 0, &stm.pstg);

    if (FAILED(hr))
        return;

    m_pObj->QueryInterface(IID_IPersistStorage, (PPVOID)&pIPS);

    if (NOERROR==pIPS->IsDirty())
        {
        OleSave(pIPS, stm.pstg, FALSE);
        pIPS->SaveCompleted(NULL);
        }
    else
        m_pIStorage->CopyTo(0, NULL, NULL, stm.pstg);

    pIPS->Release();

    //stm.pstg now has a copy, so stuff it away.
    cf=RegisterClipboardFormat(CF_EMBEDDEDOBJECT);
    SETDefFormatEtc(fe, cf, TYMED_ISTORAGE);

    if (SUCCEEDED(pIDataObject->SetData(&fe, &stm, TRUE)))
        *pFE=fe;
    else
        ReleaseStgMedium(&stm);

    stm.tymed=TYMED_HGLOBAL;

    /*
     * You want to make sure that if this object is iconic, that you
     * create the object descriptor with DVASPECT_ICON instead of
     * the more typical DVASPECT_CONTENT.  Also remember that
     * the pick point is in HIMETRIC.
     */
    XformSizeInPixelsToHimetric(NULL, (LPSIZEL)pptl, (LPSIZEL)&ptl);

    SETSIZEL(szl, (10*(m_rcl.right-m_rcl.left))
        , (10 * (m_rcl.bottom-m_rcl.top)));

    stm.hGlobal=OleStdGetObjectDescriptorDataFromOleObject
        (m_pIOleObject, NULL, m_fe.dwAspect, ptl, &szl);

    cf=RegisterClipboardFormat(CF_OBJECTDESCRIPTOR);
    SETDefFormatEtc(fe, cf, TYMED_HGLOBAL);

    if (FAILED(pIDataObject->SetData(&fe, &stm, TRUE)))
        ReleaseStgMedium(&stm);

    return;
    }

//CHAPTER13MOD
/*
 * CTenant::CopyLinkedObject
 *
 * Purpose:
 *  Copies an linked object to the given data object (via SetData,
 *  assuming this is a data transfer object for clipboard/drag-drop)
 *  if that's what we're holding.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT in which to store the copy.
 *  pFE             LPFORMATETC into which to copy CF_LINKSOURCE
 *                  if we put that in the data object.
 *  pptl            LPPOINTL to the pick point (NULL outside of
 *                  drag-drop);
 *
 * Return Value:
 *  None
 */

void CTenant::CopyLinkedObject(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, LPPOINTL pptl)
    {
    STGMEDIUM           stm;
    FORMATETC           fe;
    HRESULT             hr;
    UINT                cf;
    POINTL              ptl;
    LPMONIKER           pmk;
    CLSID               clsID;
    LPTSTR              psz=NULL;
    SIZEL               szl;

    //Can only copy links to embeddings from here.
    if (TENANTTYPE_EMBEDDEDOBJECT!=m_tType || NULL==m_pIOleObject)
        return;

    //If we don't have a full moniker, no linking allowed
    if (NULL==m_pmk)
        return;

    //If the object doesn't support this, return.
    if (OLEMISC_CANTLINKINSIDE & m_grfMisc)
        return;

    if (NULL==pptl)
        {
        SETPOINTL(ptl, 0, 0);
        pptl=&ptl;
        }

    /*
     * We need to get CF_LINKSOURCE, but the server may not be
     * running, in which case we just grab the moniker and CLSID
     * for this object and call OleStdGetLinkSrcData.
     */

    m_pIOleObject->GetUserClassID(&clsID);
    hr=m_pIOleObject->GetMoniker(0, OLEWHICHMK_OBJFULL, &pmk);

    if (FAILED(hr))
        return;

    //Set these tymeds or brain-dead OleStdGetLinkSourceData fails.
    stm.pUnkForRelease=NULL;
    stm.tymed=TYMED_NULL;
    cf=RegisterClipboardFormat(CF_LINKSOURCE);
    SETDefFormatEtc(fe, cf, TYMED_ISTREAM);
    hr=OleStdGetLinkSourceData(pmk, &clsID, &fe, &stm);

    if (FAILED(hr))
        {
        pmk->Release();
        return;
        }

    //Send it to the data object for transfer
    if (SUCCEEDED(pIDataObject->SetData(&fe, &stm, TRUE)))
        *pFE=fe;
    else
        ReleaseStgMedium(&stm);

    XformSizeInPixelsToHimetric(NULL, (LPSIZEL)pptl, (LPSIZEL)&ptl);

    SETSIZEL(szl, (10*(m_rcl.right-m_rcl.left))
        , (10 * (m_rcl.bottom-m_rcl.top)));

    stm.hGlobal=OleStdGetObjectDescriptorDataFromOleObject
        (m_pIOleObject, NULL, m_fe.dwAspect, ptl, &szl);

    //Better set these properly or errors occur.
    stm.tymed=TYMED_HGLOBAL;
    stm.pUnkForRelease=NULL;

    cf=RegisterClipboardFormat(CF_LINKSRCDESCRIPTOR);
    SETDefFormatEtc(fe, cf, TYMED_HGLOBAL);

    if (FAILED(pIDataObject->SetData(&fe, &stm, TRUE)))
        ReleaseStgMedium(&stm);

    return;
    }
//End CHAPTER13MOD

/*
 * CTenant::ShowObjectType
 *
 * Purpose:
 *  Tells the object to switch on or off an indication of whether
 *  it is linked or embedded.
 *
 * Parameters:
 *  fShow           BOOL indicating to show the type (TRUE) or
 *                  not (FALSE)
 *
 * Return Value:
 *  None
 */

void CTenant::ShowObjectType(BOOL fShow)
    {
    BOOL        fWasShow;
    DWORD       dwState;
    RECT        rc;
    HDC         hDC;

    fWasShow=(BOOL)(TENANTSTATE_SHOWTYPE & m_dwState);

    dwState=m_dwState & ~TENANTSTATE_SHOWTYPE;
    m_dwState=dwState | ((fShow) ? TENANTSTATE_SHOWTYPE : 0);

    /*
     * If this wasn't previously shown, just add the line,
     * otherwise repaint.
     */
    if (!fWasShow && fShow)
        {
        RECTFROMRECTL(rc, m_rcl);
        RectConvertMappings(&rc, NULL, TRUE);
        OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

        hDC=GetDC(m_hWnd);
        OleUIShowObject(&rc, hDC
            , (TENANTTYPE_LINKEDOBJECT==m_tType));
        ReleaseDC(m_hWnd, hDC);
        }

    if (fWasShow && !fShow)
        Repaint();

    return;
    }

/*
 * CTenant::NotifyOfRename
 *
 * Purpose:
 *  Instructs the tenant that the document was saved under a
 *  different name.  In order to keep the right compound document
 *  user interface, this tenant needs to tell its object through
 *  IOleObject::SetHostNames.
 *
 * Parameters:
 *  pszFile         LPTSTR of filename.
 *  pmkFile         LPMONIKER of the new filename.  If this and
 *                  pmkPage are NULL then nothing happens with
 *                  monikers.
 *  pmkPage         LPMONIKER of the page we're in.
 *
 * Return Value:
 *  None
 */

//CHAPTER13MOD
void CTenant::NotifyOfRename(LPTSTR pszFile, LPMONIKER pmkFile
    , LPMONIKER pmkPage)
//End CHAPTER13MOD
    {
    TCHAR       szObj[40];
    TCHAR       szApp[40];

    if (NULL==m_pIOleObject)
        return;

    if (TEXT('\0')==*pszFile)
        {
        LoadString(m_pPG->m_hInst, IDS_UNTITLED, szObj
            , sizeof(szObj));
        }
    else
        {
        GetFileTitle(pszFile, szObj, sizeof(szObj));

       #ifndef WIN32
        //Force filenames to uppercase in DOS versions.
        AnsiUpper(szObj);
       #endif
        }

    LoadString(m_pPG->m_hInst, IDS_CAPTION, szApp, sizeof(szApp));
#if defined(WIN32) && !defined(UNICODE)
    OLECHAR pwcsObj[40];
    OLECHAR pwcsApp[40];
    mbstowcs(pwcsObj, szObj, 40);
    mbstowcs(pwcsApp, szApp, 40);
    m_pIOleObject->SetHostNames(pwcsApp, pwcsObj);
#else
    m_pIOleObject->SetHostNames(szApp, szObj);
#endif
    //CHAPTER13MOD
    if (NULL!=pmkFile)
        {
        if (NULL!=m_pmkFile)
            m_pmkFile->Release();

        m_pmkFile=pmkFile;
        m_pmkFile->AddRef();

        m_pIOleObject->SetMoniker(OLEWHICHMK_CONTAINER, pmkFile);
        }

    if (NULL!=pmkFile && NULL!=pmkPage)
        {
        LPMONIKER   pmkTenant=NULL;
        LPMONIKER   pmkRel=NULL;
        HRESULT     hr;

        //Create the moniker for this tenant.
        GetStorageName(szObj);
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsObj[40];
        mbstowcs(pwcsObj, szObj, 40);
        hr=CreateItemMoniker(OLESTR("!"), pwcsObj, &pmkTenant);
#else
        hr=CreateItemMoniker(OLESTR("!"), szObj, &pmkTenant);
#endif
        if (SUCCEEDED(hr))
            {
            //Create the relative moniker, i.e. no pathname.
            hr=CreateGenericComposite(pmkPage, pmkTenant, &pmkRel);
            pmkTenant->Release();

            if (SUCCEEDED(hr))
                m_pIOleObject->SetMoniker(OLEWHICHMK_OBJREL, pmkRel);

            //Hold on to the relative moniker
            if (NULL!=m_pmk)
                m_pmk->Release();

            m_pmk=pmkRel;
            }
        }
    //End CHAPTER13MOD

    return;
    }

//CHAPTER13MOD
/*
 * CTenant::StorageGet
 *
 * Purpose:
 *  Returns the IStorage pointer maintained by this tenant,
 *  AddRef'd of course.
 *
 * Parameters:
 *  ppStg           LPSTORAGE * in which to return the pointer.
 *
 * Return Value:
 *  None
 */

void CTenant::StorageGet(LPSTORAGE *ppStg)
    {
    if (NULL==ppStg)
        return;

    *ppStg=m_pIStorage;

    if (NULL!=*ppStg)
        (*ppStg)->AddRef();

    return;
    }
//End CHAPTER13MOD

/*
 * CTenant::Activate
 *
 * Purpose:
 *  Activates a verb on the object living in the tenant.  Does
 *  nothing for static objects.
 *
 * Parameters:
 *  iVerb           LONG of the verb to execute.
 *
 * Return Value:
 *  BOOL            TRUE if the object changed due to this verb
 *                  execution.
 */

BOOL CTenant::Activate(LONG iVerb)
    {
    RECT        rc, rcH;
    CHourglass *pHour;
    SIZEL       szl;

    //Can't activate statics.
    if (TENANTTYPE_STATIC==m_tType || NULL==m_pIOleObject)
        {
        MessageBeep(0);
        return FALSE;
        }

    RECTFROMRECTL(rc, m_rcl);
    RectConvertMappings(&rc, NULL, TRUE);
    XformRectInPixelsToHimetric(NULL, &rc, &rcH);

    pHour=new CHourglass;

    //Get the server running first, then do a SetExtent, then show it
    OleRun(m_pIOleObject);

    if (m_fSetExtent)
        {
        SETSIZEL(szl, rcH.right-rcH.left, rcH.bottom-rcH.top);
        m_pIOleObject->SetExtent(m_fe.dwAspect, &szl);
        m_fSetExtent=FALSE;
        }

    m_pIOleObject->DoVerb(iVerb, NULL, m_pIOleClientSite, 0
        , m_hWnd, &rcH);

    delete pHour;

    //If object changes, IAdviseSink::OnViewChange will see it.
    return FALSE;
    }

/*
 * CTenant::Draw
 *
 * Purpose:
 *  Draws the tenant in its rectangle on the given hDC.  We assume
 *  the DC is already set up for the mapping mode in which our
 *  rectangle is expressed, since the Page we're in tells us both
 *  the rect and the hDC.
 *
 * Parameters:
 *  hDC             HDC in which to draw.  Could be a metafile,
 *                  memory DC, screen, or printer.
 *  ptd             DVTARGETDEVICE * describing the device.
 *  hIC             HDC holding an information context (printing).
 *  xOff, yOff      int offsets for the page in lometric
 *  fNoColor        BOOL indicating if we should do B & W
 *  fPrinter        BOOL indicating if we should render for a
 *                  printer.
 *
 * Return Value:
 *  None
 */

void CTenant::Draw(HDC hDC, DVTARGETDEVICE *ptd, HDC hIC
    , int xOff, int yOff, BOOL fNoColor, BOOL fPrinter)
    {
    HRESULT         hr;
    RECT            rc;
    RECTL           rcl;
    UINT            uMM;

    RECTFROMRECTL(rc, m_rcl);
    OffsetRect(&rc, -xOff, -yOff);
    RECTLFROMRECT(rcl, rc);

    //Repaint erases the rectangle to insure full object cleanup
    if (!fNoColor && !fPrinter)
        {
        COLORREF    cr;
        cr=SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
        ExtTextOut(hDC, rc.left, rc.top, ETO_OPAQUE, &rc, NULL
            , 0, NULL);
        SetBkColor(hDC, cr);
        }

    //We have to use Draw since we have a target device and IC.
    hr=m_pIViewObject2->Draw(m_fe.dwAspect, -1, NULL, ptd, hIC, hDC
        , &rcl, NULL, NULL, 0);

    /*
     * If Draw failed, then perhaps it couldn't work for the device,
     * so try good old OleDraw as a last resort.  The code will
     * generally be OLE_E_BLANK.
     */
    if (FAILED(hr))
        OleDraw(m_pObj, m_fe.dwAspect, hDC, &rc);

    if (!fPrinter)
        {
        /*
         * Draw sizing handles to show the selection state.  We
         * convert things to MM_TEXT since that's what this
         * function expects.
         */
        RectConvertMappings(&rc, NULL, TRUE);
        uMM=SetMapMode(hDC, MM_TEXT);

        if (TENANTSTATE_SELECTED & m_dwState)
            {
            OleUIDrawHandles(&rc, hDC, OLEUI_HANDLES_INSIDE
                | OLEUI_HANDLES_NOBORDER | OLEUI_HANDLES_USEINVERSE
                , CXYHANDLE, TRUE);
            }

        if (TENANTSTATE_OPEN & m_dwState)
            OleUIDrawShading(&rc, hDC, OLEUI_SHADE_FULLRECT, 0);

        //Distinguish linked and embedded objects.
        if (TENANTSTATE_SHOWTYPE & m_dwState)
            {
            OleUIShowObject(&rc, hDC
                , (TENANTTYPE_LINKEDOBJECT==m_tType));
            }

        uMM=SetMapMode(hDC, uMM);
        }

    return;
    }

/*
 * CTenant::Repaint
 * CTenant::Invalidate
 *
 * Purpose:
 *  Repaints the tenant where it lies or invalidates its area
 *  for later repainting.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CTenant::Repaint(void)
    {
    RECT        rc;
    HDC         hDC;

    /*
     * We might be asked to repaint from
     * IOleClientSite::OnShowWindow after we've switched pages if
     * our server was runnnig. This check on m_cOpens prevents that.
     */
    if (0==m_cOpens)
        return;

    hDC=GetDC(m_hWnd);
    SetRect(&rc, m_pPG->m_xPos, m_pPG->m_yPos, 0, 0);
    RectConvertMappings(&rc, NULL, FALSE);

    SetMapMode(hDC, MM_LOMETRIC);
    Draw(hDC, NULL, NULL, rc.left, rc.top, FALSE, FALSE);

    ReleaseDC(m_hWnd, hDC);
    return;
    }

void CTenant::Invalidate(void)
    {
    RECTL       rcl;
    RECT        rc;

    RectGet(&rcl, TRUE);
    RECTFROMRECTL(rc, rcl);

    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);
    InvalidateRect(m_hWnd, &rc, FALSE);

    return;
    }

/*
 * CTenant::TypeGet
 *
 * Purpose:
 *  Returns the type of this tenant
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  TENANTTYPE      Type of the tenant.
 */

TENANTTYPE CTenant::TypeGet(void)
    {
    return m_tType;
    }

/*
 * CTenant::FIsSelected
 *
 * Purpose:
 *  Returns the selection state of this tenant.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if selected, FALSE otherwise.
 */

BOOL CTenant::FIsSelected(void)
    {
    return (BOOL)(m_dwState & TENANTSTATE_SELECTED);
    }

/*
 * CTenant::FConvertToStatic
 *
 * Purpose:
 *  Changes the object that lives in this tenant to a static one.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CTenant::FConvertToStatic(void)
    {
    /*
     * If you SetSourceMoniker in IOleLink to NULL, then the link is
     * gone as far as OLE is concerned.  You only need to make sure
     * the user doesn't have access to other functionality for this
     * object, which we insure by changing our internal type.  We
     * set this on loading if GetSourceMoniker returns NULL.
     */
    m_tType=TENANTTYPE_STATIC;
    return TRUE;
    }

/*
 * CTenant::ObjectGet
 *
 * Purpose:
 *  Retrieves the LPUNKNOWN of the object in use by this tenant
 *
 * Parameters:
 *  ppUnk           LPUNKNOWN * in which to return the object
 *                  pointer.
 *
 * Return Value:
 *  None
 */

void CTenant::ObjectGet(LPUNKNOWN *ppUnk)
    {
    if (NULL!=ppUnk)
        {
        *ppUnk=m_pObj;
        m_pObj->AddRef();
        }

    return;
    }

/*
 * CTenant::FormatEtcGet
 *
 * Purpose:
 *  Retrieves the FORMATETC in use by this tenant
 *
 * Parameters:
 *  pFE             LPFORMATETC in which to store the information.
 *  fPresentation   BOOL indicating if we want the real format or
 *                  that of the presentation.
 *
 * Return Value:
 *  None
 */

void CTenant::FormatEtcGet(LPFORMATETC pFE, BOOL fPresentation)
    {
    if (NULL!=pFE)
        {
        *pFE=m_fe;

        //If there is no format, use metafile (for embedded objects)
        if (fPresentation || 0==pFE->cfFormat)
            {
            //Don't mess with dwAspect; might be icon or content.
            pFE->cfFormat=CF_METAFILEPICT;
            pFE->tymed=TYMED_MFPICT;
            }
        }

    return;
    }

/*
 * CTenant::SizeGet
 * CTenant::SizeSet
 * CTenant::RectGet
 * CTenant::RectSet
 *
 * Purpose:
 *  Returns or sets the size/position of the object contained here.
 *
 * Parameters:
 *  pszl/prcl       LPSIZEL (Size) or LPRECTL (Rect) with the
 *                  extents of interest.  In Get situations,
 *                  this will receive the extents; in Set it
 *                  contains the extents.
 *  fDevice         BOOL indicating that pszl/prcl is expressed
 *                  in device units.  Otherwise it's LOMETRIC.
 *  fInformObj      (Set Only) BOOL indicating if we need to inform
 *                  the object all.
 *
 * Return Value:
 *  None
 */

void CTenant::SizeGet(LPSIZEL pszl, BOOL fDevice)
    {
    if (!fDevice)
        {
        pszl->cx=m_rcl.right-m_rcl.left;
        pszl->cy=m_rcl.bottom-m_rcl.top;
        }
    else
        {
        RECT        rc;

        SetRect(&rc, (int)(m_rcl.right-m_rcl.left)
            , (int)(m_rcl.bottom-m_rcl.top), 0, 0);

        RectConvertMappings(&rc, NULL, TRUE);

        pszl->cx=(long)rc.left;
        pszl->cy=(long)rc.top;
        }

    return;
    }

void CTenant::SizeSet(LPSIZEL pszl, BOOL fDevice, BOOL fInformObj)
    {
    SIZEL           szl;

    if (!fDevice)
        {
        szl=*pszl;
        m_rcl.right =pszl->cx+m_rcl.left;
        m_rcl.bottom=pszl->cy+m_rcl.top;
        }
    else
        {
        RECT        rc;

        SetRect(&rc, (int)pszl->cx, (int)pszl->cy, 0, 0);
        RectConvertMappings(&rc, NULL, FALSE);

        m_rcl.right =(long)rc.left+m_rcl.left;
        m_rcl.bottom=(long)rc.top+m_rcl.top;

        SETSIZEL(szl, (long)rc.left, (long)rc.top);
        }

    //Tell OLE that this object was resized.
    if (NULL!=m_pIOleObject && fInformObj)
        {
        HRESULT     hr;
        BOOL        fRun=FALSE;

        //Convert our LOMETRIC into HIMETRIC by *=10
        szl.cx*=10;
        szl.cy*=-10;    //Our size is stored negative.

        /*
         * If the MiscStatus bit of OLEMISC_RECOMPOSEONRESIZE
         * is set, then we need to run the object before calling
         * SetExtent to make sure it has a real chance to
         * re-render the object.  We have to update and close
         * the object as well after this happens.
         */

        if (OLEMISC_RECOMPOSEONRESIZE & m_grfMisc)
            {
            if (!OleIsRunning(m_pIOleObject))
                {
                OleRun(m_pIOleObject);
                fRun=TRUE;
                }
            }

        hr=m_pIOleObject->SetExtent(m_fe.dwAspect, &szl);

        /*
         * If the object is not running and it does not have
         * RECOMPOSEONRESIZE, then SetExtent fails.  Make
         * sure that we call SetExtent again (by just calling
         * SizeSet here again) when we next run the object.
         */
        if (SUCCEEDED(hr))
            {
            m_fSetExtent=FALSE;

            if (fRun)
                {
                m_pIOleObject->Update();
                m_pIOleObject->Close(OLECLOSE_SAVEIFDIRTY);
                }
            }
        else
            {
            if (OLE_E_NOTRUNNING==hr)
                m_fSetExtent=TRUE;
            }
        }

    return;
    }

void CTenant::RectGet(LPRECTL prcl, BOOL fDevice)
    {
    if (!fDevice)
        *prcl=m_rcl;
    else
        {
        RECT        rc;

        RECTFROMRECTL(rc, m_rcl);
        RectConvertMappings(&rc, NULL, TRUE);
        RECTLFROMRECT(*prcl, rc);
        }

    return;
    }

void CTenant::RectSet(LPRECTL prcl, BOOL fDevice, BOOL fInformObj)
    {
    SIZEL   szl;
    LONG    cx, cy;

    cx=m_rcl.right-m_rcl.left;
    cy=m_rcl.bottom-m_rcl.top;

    if (!fDevice)
        m_rcl=*prcl;
    else
        {
        RECT        rc;

        RECTFROMRECTL(rc, *prcl);
        RectConvertMappings(&rc, NULL, FALSE);
        RECTLFROMRECT(m_rcl, rc);
        }

    /*
     * Tell ourselves that the size changed, if it did.  SizeSet
     * will call IOleObject::SetExtent for us.
     */
    if ((m_rcl.right-m_rcl.left)!=cx || (m_rcl.bottom-m_rcl.top)!=cy)
        {
        SETSIZEL(szl, m_rcl.right-m_rcl.left, m_rcl.bottom-m_rcl.top);
        SizeSet(&szl, FALSE, fInformObj);
        }

    return;
    }

/*
 * CTenant::CreateStatic
 * (Protected)
 *
 * Purpose:
 *  Creates a new static bitmap or metafile object for this tenant
 *  using a freeloading method allowing us to specify exactly which
 *  type of data we want to paste since OleCreateStaticFromData
 *  doesn't.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *  pFE             LPFORMATETC describing the format to paste.
 *  ppObj           LPUNKNOWN * into which we store the
 *                  object pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, error code otherwise.
 */

HRESULT CTenant::CreateStatic(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, LPUNKNOWN *ppObj)
    {
    HRESULT             hr;
    STGMEDIUM           stm;
    LPUNKNOWN           pIUnknown;
    LPOLECACHE          pIOleCache;
    LPPERSISTSTORAGE    pIPersistStorage;
    CLSID               clsID;

    *ppObj=NULL;

    //Try to get the data desired as specified in pFE->cfFormat
    hr=pIDataObject->GetData(pFE, &stm);

    if (FAILED(hr))
        return hr;

    //Create the object to handle this data.
    if (CF_METAFILEPICT==pFE->cfFormat)
        clsID=CLSID_Picture_Metafile;
    else
        clsID=CLSID_Picture_Dib;

    hr=CreateDataCache(NULL, clsID, IID_IUnknown
        , (PPVOID)&pIUnknown);

    if (FAILED(hr))
        {
        ReleaseStgMedium(&stm);
        return hr;
        }

    m_clsID=clsID;

    //Stuff the data into the object
    pIUnknown->QueryInterface(IID_IPersistStorage
        , (PPVOID)&pIPersistStorage);
    pIPersistStorage->InitNew(m_pIStorage);

    //Now that we have the cache object, shove the data into it.
    pIUnknown->QueryInterface(IID_IOleCache, (PPVOID)&pIOleCache);
    pIOleCache->Cache(pFE, ADVF_PRIMEFIRST, NULL);

    hr=pIOleCache->SetData(pFE, &stm, TRUE);
    pIOleCache->Release();

    //Insure there is a persistent copy on the disk
    WriteClassStg(m_pIStorage, m_clsID);
    pIPersistStorage->Save(m_pIStorage, TRUE);
    pIPersistStorage->SaveCompleted(NULL);
    pIPersistStorage->Release();

    //The cache owns this now.
    ReleaseStgMedium(&stm);

    if (FAILED(hr))
        pIUnknown->Release();
    else
        *ppObj=pIUnknown;

    return hr;
    }


BEEPER.CPP

/*
 * BEEPER.CPP
 * Beeper Automation Object #1 Chapter 14
 *
 * Implementation of the CBeeper class which demonstrate a fully
 * custom IDispatch implementation that only supports mapping of
 * names to IDs through IDispatch::GetIDsOfNames.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "beeper.h"

extern HINSTANCE g_hInst;

/*
 * CBeeper::CBeeper
 * CBeeper::~CBeeper
 *
 * Parameters (Constructor):
 *  pUnkOuter       LPUNKNOWN of a controlling unknown.
 *  pfnDestroy      PFNDESTROYED to call when an object
 *                  is destroyed.
 */

CBeeper::CBeeper(LPUNKNOWN pUnkOuter, PFNDESTROYED pfnDestroy)
    {
    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pfnDestroy=pfnDestroy;

    m_lSound=0;
    m_pImpIDispatch=NULL;
    return;
    }

CBeeper::~CBeeper(void)
    {
    if (NULL==m_pszScratch)
        free(m_pszScratch);

    DeleteInterfaceImp(m_pImpIDispatch);
    return;
    }

/*
 * CBeeper::Init
 *
 * Purpose:
 *  Performs any intiailization of a CBeeper that's prone to failure
 *  that we also use internally before exposing the object outside.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CBeeper::Init(void)
    {
    LPUNKNOWN       pIUnknown=this;

    if (NULL!=m_pUnkOuter)
        pIUnknown=m_pUnkOuter;

    m_pImpIDispatch=new CImpIDispatch(this, pIUnknown);

    if (NULL==m_pImpIDispatch)
        return FALSE;

    //Pre-allocate scratch space for IDispatch::GetIDsOfNames
    m_pszScratch=(LPTSTR)malloc(256*sizeof(TCHAR));

    if (NULL==m_pszScratch)
        return FALSE;

    return TRUE;
    }

/*
 * CBeeper::QueryInterface
 * CBeeper::AddRef
 * CBeeper::Release
 *
 * Purpose:
 *  IUnknown members for CBeeper object.
 */

STDMETHODIMP CBeeper::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    /*
     * The only calls for IUnknown are either in a nonaggregated
     * case or when created in an aggregation, so in either case
     * always return our IUnknown for IID_IUnknown.
     */
    if (IID_IUnknown==riid)
        *ppv=this;

    /*
     * QueryInterface must respond not only to IID_IDispatch for
     * the primary automation interface, but also to the DIID of the
     * dispinterface itself, which in our case is DIID_DIBeeper.
     */

    if (IID_IDispatch==riid || DIID_DIBeeper==riid)
        *ppv=m_pImpIDispatch;

    //AddRef any interface we'll return.
    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CBeeper::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CBeeper::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    //Inform the server about destruction so it can handle shutdown
    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0L;
    }

//IDispatch interface implementation

/*
 * CImpIDispatch::CImpIDispatch
 * CImpIDispatch::~CImpIDispatch
 *
 * Parameters (Constructor):
 *  pObj            PCBeeper of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIDispatch::CImpIDispatch(PCBeeper pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIDispatch::~CImpIDispatch(void)
    {
    return;
    }

/*
 * CImpIDispatch::QueryInterface
 * CImpIDispatch::AddRef
 * CImpIDispatch::Release
 *
 * Purpose:
 *  IUnknown members for CImpIDispatch object.
 */

STDMETHODIMP CImpIDispatch::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIDispatch::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIDispatch::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIDispatch::GetTypeInfoCount
 *
 * Purpose:
 *  Returns the number of type information (ITypeInfo) interfaces
 *  that the object provides (0 or 1).
 *
 * Parameters:
 *  pctInfo         UINT * to the location to receive
 *                  the count of interfaces.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CImpIDispatch::GetTypeInfoCount(UINT *pctInfo)
    {
    //We don't implement GetTypeInfo, so return 0
    *pctInfo=0;
    return NOERROR;
    }

/*
 * CImpIDispatch::GetTypeInfo
 *
 * Purpose:
 *  Retrieves type information for the automation interface.
 *
 * Parameters:
 *  itinfo          UINT reserved.  Must be zero.
 *  lcid            LCID providing the locale for the type
 *                  information.  If the object does not support
 *                  localization, this is ignored.
 *  pptinfo         ITypeInfo ** in which to store the ITypeInfo
 *                  interface for the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CImpIDispatch::GetTypeInfo(UINT itinfo, LCID lcid
    , ITypeInfo **pptInfo)
    {
    /*
     * Since we returned zero from GetTypeInfoCount, this function
     * should not be called.  If it is, be sure to NULL the pptInfo
     * pointer according to normal out-parameter rules.
     */
    *pptInfo=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIDispatch::GetIDsOfNames
 *
 * Purpose:
 *  Converts text names into DISPIDs to pass to Invoke
 *
 * Parameters:
 *  riid            REFIID reserved.  Must be IID_NULL.
 *  rgszNames       OLECHAR ** pointing to the array of names to be
 *                  mapped.
 *  cNames          UINT number of names to be mapped.
 *  lcid            LCID of the locale.
 *  rgDispID        DISPID * caller allocated array containing IDs
 *                  corresponging to those names in rgszNames.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CImpIDispatch::GetIDsOfNames(REFIID riid
    , OLECHAR **rgszNames, UINT cNames, LCID lcid, DISPID *rgDispID)
    {
    HRESULT     hr;
    int         i;
    int         idsMin;
    LPTSTR      psz;

    if (IID_NULL!=riid)
        return ResultFromScode(DISP_E_UNKNOWNINTERFACE);

    /*
     * This function will support English and German languages,
     * where in English we have a "Sound" property and "Beep"
     * method; in German these are "Ton" and "Peip" This particular
     * coding will handle either language identically, so a
     * controller using even both languages simultaneously can work
     * with this same automation object.
     *
     * To check the passed LCID, we use the PRIMARYLANGID macro
     * to check for LANG_ENGLISH and LANG_GERMAN, which means we
     * support any dialect of english (US, UK, AUS, CAN, NZ, EIRE)
     * and german (GER, SWISS, AUS).  The high byte of an LCID
     * specifies the sub-language, and the macro here strips that
     * differentiation.
     *
     * Note that LANG_NEUTRAL is considered here to be English.
     */

    //Set up idsMin to the right stringtable in our resources
    switch (PRIMARYLANGID(lcid))
        {
        case LANG_NEUTRAL:
        case LANG_ENGLISH:
            idsMin=IDS_0_NAMESMIN;
            break;

        case LANG_GERMAN:
            idsMin=IDS_7_NAMESMIN;
            break;

        default:
            return ResultFromScode(DISP_E_UNKNOWNLCID);
        }

    /*
     * The index in this loop happens to correspond to the DISPIDs
     * for each element which also matches the stringtable entry
     * ordering, where i+idsMin is the string to compare.  If we
     * find a match, i is the DISPID to return.
     */
    rgDispID[0]=DISPID_UNKNOWN;
    hr=ResultFromScode(DISP_E_UNKNOWNNAME);

    psz=m_pObj->m_pszScratch;

    for (i=0; i < CNAMES; i++)
        {
        /*
         * If we had more than one name per method or property,
         * we'd need to loop over the cNames parameter as well.
         */

        LoadString(g_hInst, idsMin+i, psz, 256);

       #ifdef WIN32ANSI
        char        szTemp[80];

        WideCharToMultiByte(CP_ACP, 0, rgszNames[0], -1
            , szTemp, 80, NULL, NULL);
        if (0==lstrcmpi(psz, szTemp))
       #else
        if (0==lstrcmpi(psz, rgszNames[0]))
       #endif
            {
            //Found a match, return the DISPID
            rgDispID[0]=i;
            hr=NOERROR;
            break;
            }
        }

    return hr;
    }

/*
 * CImpIDispatch::Invoke
 *
 * Purpose:
 *  Calls a method in the dispatch interface or manipulates a
 *  property.
 *
 * Parameters:
 *  dispID          DISPID of the method or property of interest.
 *  riid            REFIID reserved, must be IID_NULL.
 *  lcid            LCID of the locale.
 *  wFlags          USHORT describing the context of the invocation.
 *  pDispParams     DISPPARAMS * to the array of arguments.
 *  pVarResult      VARIANT * in which to store the result.  Is
 *                  NULL if the caller is not interested.
 *  pExcepInfo      EXCEPINFO * to exception information.
 *  puArgErr        UINT * in which to store the index of an
 *                  invalid parameter if DISP_E_TYPEMISMATCH
 *                  is returned.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CImpIDispatch::Invoke(DISPID dispID, REFIID riid
    , LCID lcid, unsigned short wFlags, DISPPARAMS *pDispParams
    , VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
    {
    HRESULT     hr;

    //riid is supposed to be IID_NULL always
    if (IID_NULL!=riid)
        return ResultFromScode(DISP_E_UNKNOWNINTERFACE);

    /*
     * There is nothing locale-sensitive in any of our properties
     * or methods.  Some automation objects may have currency,
     * date/time, or string values in properties or methods which
     * would be sensitive to lcid; be sure the handle them properly.
     */

    /*
     * Process the invoked member or property.  For members,
     * call whatever functions are necessary to carry out the
     * action.  For properties, either return the value you have
     * or change it according to wFlags.
     *
     * This object supports one property and one method:
     *  ID 0    "Sound" property, a long that must be one of
     *          MB_OK, MB_ICONHAND, MB_ICONQUESTION,
     *          MB_ICONEXCLAMATION, and MB_ICONASTERISK.
     *  ID 1    "Beep" method, no parameters, return value of type
     *          long which is the sound that was played.
     *
     * Note that the IDs are assigned in the implementation of
     * IDispatch::GetIDsOfNames.
     */

    switch (dispID)
        {
        case PROPERTY_SOUND:
            /*
             * Some controllers might not be able to differentiate
             * between a property get and a function call, so we
             * have to handle both as a property get here.
             */
            if (DISPATCH_PROPERTYGET & wFlags
                || DISPATCH_METHOD & wFlags)
                {
                //Make sure we have a place for the result
                if (NULL==pVarResult)
                    return ResultFromScode(E_INVALIDARG);

                VariantInit(pVarResult);
                V_VT(pVarResult)=VT_I4;
                V_I4(pVarResult)=m_pObj->m_lSound;
                return NOERROR;
                }
            else
                {
                //DISPATCH_PROPERTYPUT
                long        lSound;
                int         c;
                VARIANT     vt;

                //Validate parameter count
                if (1!=pDispParams->cArgs)
                    return ResultFromScode(DISP_E_BADPARAMCOUNT);

                //Check that we have a named DISPID_PROPERTYPUT
                c=pDispParams->cNamedArgs;
                if (1!=c || (1==c && DISPID_PROPERTYPUT
                    !=pDispParams->rgdispidNamedArgs[0]))
                    return ResultFromScode(DISP_E_PARAMNOTOPTIONAL);

                /*
                 * Try to coerce the new property value into a
                 * type VT_I4.  VariantChangeType will do this for
                 * us and return an appropriate error code if the
                 * type cannot be coerced.  On error we store 0
                 * (first parameter) into puArgErr.
                 *
                 * We could also use DispGetParam here to do the
                 * same thing:
                 *   DispGetParam(pDispParams, 0, VT_I4
                 *       , &vtNew, puArgErr);
                 */
                VariantInit(&vt);
                hr=VariantChangeType(&vt, &pDispParams->rgvarg[0]
                    , 0, VT_I4);

                if (FAILED(hr))
                    {
                    if (NULL!=puArgErr)
                        *puArgErr=0;

                    return hr;
                    }

                //With the right type, now check the right value
                lSound=vt.lVal;

                if (MB_OK!=lSound && MB_ICONEXCLAMATION!=lSound
                    && MB_ICONQUESTION!=lSound && MB_ICONHAND!=lSound
                    && MB_ICONASTERISK!=lSound)
                    {
                    if (NULL==pExcepInfo)
                        return ResultFromScode(E_INVALIDARG);

                    /*
                     * This is the right place for an exception--
                     * the best we can tell the caller with a
                     * return value is something like E_INVALIDARG.
                     * But that doesn't at all indiate the problem.
                     * So we use EXCEPTION_INVALIDSOUND and the
                     * FillException callback to fill the EXCEPINFO.
                     *
                     * Note:  DispTest and Visual Basic 3 don't
                     * support deferred filling of the EXCEPINFO
                     * structure; Visual Basic 4 does.  Even if you
                     * don't use deferred filling, a separate
                     * function is still useful as you can just call
                     * it here to fill the structure immediately.
                     *
                     * Deferred fill-in code would appear:
                     *
                     *   INITEXCEPINFO(*pExcepInfo);
                     *   pExcepInfo->scode
                     *       =(SCODE)MAKELONG(EXCEPTION_INVALIDSOUND
                     *       , PRIMARYLANGID(lcid));
                     *   pExcepInfo->pfnDeferredFillIn=FillException;
                     */

                    /*
                     * So we can make a localized exception, we'll
                     * store the language ID and our exception code
                     * into the scode field; in FillException we move
                     * the code into wCode and clear scode.  Otherwise
                     * there's no way to tell FillException about
                     * the locale.
                     */

                    pExcepInfo->scode
                        =(SCODE)MAKELONG(EXCEPTION_INVALIDSOUND
                        , PRIMARYLANGID(lcid));
                    FillException(pExcepInfo);
                    return ResultFromScode(DISP_E_EXCEPTION);
                    }

                //Everything checks out:  save the new value
                m_pObj->m_lSound=lSound;
                }

            break;

        case METHOD_BEEP:
            if (!(DISPATCH_METHOD & wFlags))
                return ResultFromScode(DISP_E_MEMBERNOTFOUND);

            if (0!=pDispParams->cArgs)
                return ResultFromScode(DISP_E_BADPARAMCOUNT);

            MessageBeep((UINT)m_pObj->m_lSound);

            //The result of this method is the sound we played
            if (NULL!=pVarResult)
                {
                VariantInit(pVarResult);
                V_VT(pVarResult)=VT_I4;
                V_I4(pVarResult)=m_pObj->m_lSound;
                }

            break;

        default:
            return ResultFromScode(DISP_E_MEMBERNOTFOUND);
        }

    return NOERROR;
    }

/*
 * FillException
 *
 * Purpose:
 *  Callback function pointed to in IDispatch::Invoke that fills
 *  an EXCEPINFO structure based on the code stored inside
 *  Invoke.  This is a nice mechanism to keep all the management
 *  of error code strings and help IDs centralized in one place,
 *  even across many different automation objects within the same
 *  application.  It also keeps Invoke cleaner.
 *
 * Parameters:
 *  pExcepInfo      EXCEPINFO * to fill.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, error code otherwise.
 */

HRESULT STDAPICALLTYPE FillException(EXCEPINFO *pExcepInfo)
    {
    SCODE       scode;
    LANGID      langID;
    USHORT      wCode;
    HRESULT     hr;
    LPTSTR      psz;
    LPOLESTR    pszHelp;
    UINT        idsSource;
    UINT        idsException;

    if (NULL==pExcepInfo)
        return ResultFromScode(E_INVALIDARG);

    /*
     * Parts of our implementation that raise exceptions put the
     * WORD exception code in the loword of scode and the LANGID
     * in the hiword.
     */
    scode=pExcepInfo->scode;
    langID=HIWORD(scode);
    wCode=LOWORD(scode);

    //Allocate BSTRs for source and description strings
    psz=(LPTSTR)malloc(1024*sizeof(TCHAR));

    if (NULL==psz)
        return ResultFromScode(E_OUTOFMEMORY);

    hr=NOERROR;

    switch (wCode)
        {
        case EXCEPTION_INVALIDSOUND:
            //Fill in unused information, macro in inole.h
            INITEXCEPINFO(*pExcepInfo);
            pExcepInfo->wCode=wCode;

            /*
             * DispTest and Visual Basic 3 ignore the help file and
             * context ID.  A complete controller such as Visual
             * Basic 4 checks if these fields are set, and if so,
             * displays a Help button in a message box.  If Help
             * is pressed, the controller calls WinHelp with this
             * filename and context ID for complete integration.
             *
             * The sources for beeper.hlp are in
             * \inole\chap14\beephelp along with the actual help
             * file.  For this sample I assume it's on C drive.
             * Normally you'll want to read your own HELPDIR
             * registry entry from under TypeLib and prepend that
             * to the name of the help file, but since this sample
             * doesn't have a type library, that entry doesn't
             * exist so I just hard-code it.
             */
            pExcepInfo->dwHelpContext=HID_SOUND_PROPERTY_LIMITATIONS;

            //Set defaults
            pszHelp=OLETEXT("c:\\inole\\chap14\\beephelp\\beep0000.hlp");
            idsSource=IDS_0_EXCEPTIONSOURCE;
            idsException=IDS_0_EXCEPTIONINVALIDSOUND;

            //Get the localized source and exception strings
            switch (langID)
                {
                case LANG_GERMAN:
                    idsSource=IDS_7_EXCEPTIONSOURCE;
                    idsException=IDS_7_EXCEPTIONINVALIDSOUND;
                    pszHelp=OLETEXT("c:\\inole\\chap14\\beephelp\\beep0007.hlp");
                    break;

                case LANG_ENGLISH:
                case LANG_NEUTRAL:
                default:
                    break;
                }

            break;

        default:
            hr=ResultFromScode(E_FAIL);
        }

    if (SUCCEEDED(hr))
        {
        pExcepInfo->bstrHelpFile=SysAllocString(pszHelp);

       #ifdef WIN32ANSI
        OLECHAR     szTemp[256];

        LoadString(g_hInst, idsSource, psz, 256);
        MultiByteToWideChar(CP_ACP, 0, psz, -1, szTemp, 256);
        pExcepInfo->bstrSource=SysAllocString(szTemp);

        LoadString(g_hInst, idsException, psz, 256);
        MultiByteToWideChar(CP_ACP, 0, psz, -1, szTemp, 256);
        pExcepInfo->bstrDescription=SysAllocString(szTemp);
       #else
        LoadString(g_hInst, idsSource, psz, 1024);
        pExcepInfo->bstrSource=SysAllocString(psz);
        LoadString(g_hInst, idsException, psz, 1024);
        pExcepInfo->bstrDescription=SysAllocString(psz);
       #endif
        }

    free(psz);
    return hr;
    }


BEEPER.H

/*
 * BEEPER.H
 * Beeper Automation Object #1 Chapter 14
 *
 * Classes that implement the Beeper object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _BEEPER_H_
#define _BEEPER_H_

#define INC_AUTOMATION
#define CHAPTER14
#include <inole.h>
#include <malloc.h>

//Help context ID for exceptions
#define HID_SOUND_PROPERTY_LIMITATIONS  0x1E100

//English method, property, and exception strings
#define IDS_0_MIN                   16
#define IDS_0_EXCEPTIONSOURCE       (IDS_0_MIN)
#define IDS_0_EXCEPTIONINVALIDSOUND (IDS_0_MIN+1)
#define IDS_0_SOUND                 (IDS_0_MIN+2)
#define IDS_0_BEEP                  (IDS_0_MIN+3)

#define IDS_0_NAMESMIN              (IDS_0_MIN+2)

//German method, property, and exception strings
#define IDS_7_MIN                   32
#define IDS_7_EXCEPTIONSOURCE       (IDS_7_MIN)
#define IDS_7_EXCEPTIONINVALIDSOUND (IDS_7_MIN+1)
#define IDS_7_SOUND                 (IDS_7_MIN+2)
#define IDS_7_BEEP                  (IDS_7_MIN+3)

#define IDS_7_NAMESMIN              (IDS_7_MIN+2)

//Number of names in GetIDsOfNames
#define CNAMES                      2

//Forward class declarations for friend statements
class CImpIDispatch;
typedef CImpIDispatch *PCImpIDispatch;

class CBeeper : public IUnknown
    {
    friend CImpIDispatch;

    protected:
        ULONG           m_cRef;             //Object reference count
        LPUNKNOWN       m_pUnkOuter;        //Controlling unknown
        PFNDESTROYED    m_pfnDestroy;       //To call on closure

        long            m_lSound;           //Type of sound
        PCImpIDispatch  m_pImpIDispatch;    //Our IDispatch
        LPTSTR          m_pszScratch;       //For GetIDsOfNames

    public:
        CBeeper(LPUNKNOWN, PFNDESTROYED);
        ~CBeeper(void);

        BOOL Init(void);

        //Non-delegating object IUnknown
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
    };

typedef CBeeper *PCBeeper;

//DISPIDs for our dispinterface
enum
    {
    PROPERTY_SOUND=0,
    METHOD_BEEP
    };

/*
 * IDispatch interface implementations for the Beeper.
 */

class CImpIDispatch : public IDispatch
    {
    public:
        ULONG           m_cRef;

    private:
        PCBeeper        m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIDispatch(PCBeeper, LPUNKNOWN);
        ~CImpIDispatch(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IDispatch members
        STDMETHODIMP GetTypeInfoCount(UINT *);
        STDMETHODIMP GetTypeInfo(UINT, LCID, ITypeInfo **);
        STDMETHODIMP GetIDsOfNames(REFIID, OLECHAR **, UINT, LCID
            , DISPID *);
        STDMETHODIMP Invoke(DISPID, REFIID, LCID, WORD
            , DISPPARAMS *, VARIANT *, EXCEPINFO *, UINT *);
    };

//Exceptions we can throw from IDispatch::Invoke
enum
    {
    EXCEPTION_NONE=0,
    EXCEPTION_INVALIDSOUND=1000
    };

//Exception filling function for the EXCEPINFO structure.
HRESULT STDAPICALLTYPE FillException(EXCEPINFO *);

#endif //_BEEPER_H_


DBEEPER.CPP

/*
 * DBEEPER.CPP
 * Beeper Automation Object Chapter 14
 *
 * Server module code for the Beeper object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "dbeeper.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;
HINSTANCE   g_hInst=NULL;   //For LoadString

/*
 * LibMain(32)
 *
 * Purpose:
 *  Entry point conditionally compiled for Win32 and Windows
 *  3.1.  Provides the proper structure for each environment.
 */

#ifdef WIN32
BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    g_hInst=hInstance;

    if (DLL_PROCESS_DETACH==ulReason)
        {
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    return TRUE;
    }
#else
int PASCAL LibMain(HINSTANCE hInstance, WORD wDataSeg
    , WORD cbHeapSize, LPSTR lpCmdLine)
    {
    if (0!=cbHeapSize)
        UnlockData(0);

    g_hInst=hInstance;
    return (int)hInstance;
    }
#endif

/*
 * DllGetClassObject
 * DllCanUnloadNow
 * Standard COM exports for DLL servers.
 */

HRESULT APIENTRY DllGetClassObject(REFCLSID rclsid, REFIID riid
    , PPVOID ppv)
    {
    CBeeperFactory *pBF;
    HRESULT         hr;

    if (CLSID_Beeper!=rclsid)
        return ResultFromScode(E_FAIL);

    //Check that we can provide the interface
    if (IID_IUnknown!=riid && IID_IClassFactory!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Return our beeper factory's IClassFactory
    pBF=new CBeeperFactory();

    if (NULL==pBF)
        return ResultFromScode(E_OUTOFMEMORY);

    //If the factory hasn't the interface, delete it
    hr=pBF->QueryInterface(riid, ppv);

    if (FAILED(hr))
        delete pBF;
    else
        g_cObj++;

    return hr;
    }

STDAPI DllCanUnloadNow(void)
    {
    SCODE   sc;

    //Our answer is whether there are any object or locks
    sc=(0L==g_cObj && 0L==g_cLock) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the object to call when it gets destroyed.  Since
 *  we're in a DLL we only track the number of objects here, letting
 *  DllCanUnloadNow take care of the rest.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;
    return;
    }

/*
 * CBeeperFactory::CBeeperFactory
 * CBeeperFactory::~CBeeperFactory
 */

CBeeperFactory::CBeeperFactory(void)
    {
    m_cRef=0L;
    return;
    }

CBeeperFactory::~CBeeperFactory(void)
    {
    return;
    }

/*
 * CBeeperFactory::QueryInterface
 * CBeeperFactory::AddRef
 * CBeeperFactory::Release
 */

STDMETHODIMP CBeeperFactory::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CBeeperFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CBeeperFactory::Release(void)
    {
    if(0!=--m_cRef)
        return 0;

    delete this;
    ObjectDestroyed();
    return 0;
    }

/*
 * CBeeperFactory::CreateInstance
 * CBeeperFactory::LockServer
 */

STDMETHODIMP CBeeperFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCBeeper            pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //Verify that a controlling unknown asks for IUnknown
    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    //Create the object passing function to notify on destruction.
    pObj=new CBeeper(pUnkOuter, ObjectDestroyed);

    if (NULL==pObj)
        return hr;

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    //Kill the object if initial creation or Init failed.
    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

STDMETHODIMP CBeeperFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        g_cLock--;

    return NOERROR;
    }


DBEEPER.H

/*
 * DBEEPER.H
 * Beeper Automation Object Chapter 14
 *
 * Definitions, classes, and prototypes for the server of Beeper
 * objects.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _DBEEPER_H_
#define _DBEEPER_H_

//Get the object definitions
#include "beeper.h"

//DBEEPER.CPP
void ObjectDestroyed(void);

//This class factory object creates other objects

class CBeeperFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;

    public:
        CBeeperFactory(void);
        ~CBeeperFactory(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP     CreateInstance(LPUNKNOWN, REFIID, PPVOID);
        STDMETHODIMP     LockServer(BOOL);
    };

typedef CBeeperFactory *PCBeeperFactory;

#endif //_DBEEPER_H_


BEEPER.CPP

/*
 * BEEPER.CPP
 * Beeper Automation Object #2 Chapter 14
 *
 * Implementation of the CBeeper class demonstrating the use of
 * functions like ITypeInfo::Invoke (same as DispInboke) and
 * ITypeInfo::GetIDOfNames (same as DispGetIDOfNames) where the
 * type information is taken from a type library.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "beeper.h"

extern HINSTANCE g_hInst;

/*
 * CBeeper::CBeeper
 * CBeeper::~CBeeper
 *
 * Parameters (Constructor):
 *  pUnkOuter       LPUNKNOWN of a controlling unknown.
 *  pfnDestroy      PFNDESTROYED to call when an object
 *                  is destroyed.
 */

CBeeper::CBeeper(LPUNKNOWN pUnkOuter, PFNDESTROYED pfnDestroy)
    {
    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pfnDestroy=pfnDestroy;

    m_lSound=0;
    m_pImpIDispatch=NULL;
    return;
    }

CBeeper::~CBeeper(void)
    {
    DeleteInterfaceImp(m_pImpIDispatch);
    return;
    }

/*
 * CBeeper::Init
 *
 * Purpose:
 *  Performs any intiailization of a CBeeper that's prone to failure
 *  that we also use internally before exposing the object outside.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CBeeper::Init(void)
    {
    LPUNKNOWN       pIUnknown=this;

    if (NULL!=m_pUnkOuter)
        pIUnknown=m_pUnkOuter;

    m_pImpIDispatch=new CImpIDispatch(this, pIUnknown);

    if (NULL==m_pImpIDispatch)
        return FALSE;

    return TRUE;
    }

/*
 * CBeeper::QueryInterface
 * CBeeper::AddRef
 * CBeeper::Release
 */

STDMETHODIMP CBeeper::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    /*
     * The only calls for IUnknown are either in a nonaggregated
     * case or when created in an aggregation, so in either case
     * always return our IUnknown for IID_IUnknown.
     *
     * Note that IBeeper is implemented on CBeeper, and we must
     * also expose that custom interface through QueryInterface.
     */
    if (IID_IUnknown==riid || IID_IBeeper==riid)
        *ppv=this;

    /*
     * QueryInterface must respond not only to IID_IDispatch for
     * the primary automation interface, but also to the IID of the
     * dispinterface itself, which in our case is DIID_DIBeeper.
     */
    if (IID_IDispatch==riid || DIID_DIBeeper==riid)
        *ppv=m_pImpIDispatch;

    //AddRef any interface we'll return.
    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CBeeper::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CBeeper::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0L;
    }

//IBeeper interface functions

/*
 * CBeeper::get_Sound
 * CBeeper::put_Sound
 *
 * Purpose:
 *  Functions called from DispInvoke to handle the Sound property.
 *
 * Parameters (Set only):
 *  lSound          long, new sound to save after validation
 *
 * Return Value: (Get only):
 *  long            Current sound.
 */

STDMETHODIMP_(long) CBeeper::get_Sound(void)
    {
    return m_lSound;
    }

STDMETHODIMP_(void) CBeeper::put_Sound(long lSound)
    {
    if (MB_OK!=lSound && MB_ICONEXCLAMATION!=lSound
        && MB_ICONQUESTION!=lSound && MB_ICONHAND!=lSound
        && MB_ICONASTERISK!=lSound)
        {
        /*
         * Since we cannot return a value to ITypeInfo::Invoke to
         * indicate an exception condition, we'll use
         * m_pImpIDispatch->Exception to raise them.  Before
         * CImpIDispatch::Invoke calls ITypeInfo::Invoke, it clears
         * the exception.  CImpIDispatch::Exception sets an
         * exception, so when ITypeInfo::Invoke returns, our Invoke
         * can store exception information as necessary.
         */
        m_pImpIDispatch->Exception(EXCEPTION_INVALIDSOUND);
        return;
        }

    m_lSound=lSound;
    return;
    }

/*
 * CBeeper::Beep
 *
 * Purpose:
 *  Function called from DispInvoke to invoke the Beep method.
 *
 * Return Value:
 *  long           The sound played.
 */

STDMETHODIMP_(long) CBeeper::Beep(void)
    {
    MessageBeep((UINT)m_lSound);
    return m_lSound;
    }

//IDispatch interface implementation

/*
 * CImpIDispatch::CImpIDispatch
 * CImpIDispatch::~CImpIDispatch
 *
 * Parameters (Constructor):
 *  pObj            PCBeeper of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIDispatch::CImpIDispatch(PCBeeper pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;

    //These are created as needed in GetTypeInfo
    m_pITINeutral=NULL;
    m_pITIGerman=NULL;

    return;
    }

CImpIDispatch::~CImpIDispatch(void)
    {
    ReleaseInterface(m_pITIGerman);
    ReleaseInterface(m_pITINeutral);
    return;
    }

/*
 * CImpIDispatch::QueryInterface
 * CImpIDispatch::AddRef
 * CImpIDispatch::Release
 *
 * Purpose:
 *  IUnknown members for CImpIDispatch object.
 */

STDMETHODIMP CImpIDispatch::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIDispatch::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIDispatch::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIDispatch::GetTypeInfoCount
 *
 * Purpose:
 *  Returns the number of type information (ITypeInfo) interfaces
 *  that the object provides (0 or 1).
 *
 * Parameters:
 *  pctInfo         UINT * to the location to receive
 *                  the count of interfaces.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CImpIDispatch::GetTypeInfoCount(UINT *pctInfo)
    {
    //We implement GetTypeInfo so return 1
    *pctInfo=1;
    return NOERROR;
    }

/*
 * CImpIDispatch::GetTypeInfo
 *
 * Purpose:
 *  Retrieves type information for the automation interface.  This
 *  is used anywhere that the right ITypeInfo interface is needed
 *  for whatever LCID is applicable.  Specifically, this is used
 *  from within GetIDsOfNames and Invoke.
 *
 * Parameters:
 *  itInfo          UINT reserved.  Must be zero.
 *  lcid            LCID providing the locale for the type
 *                  information.  If the object does not support
 *                  localization, this is ignored.
 *  ppITypeInfo     ITypeInfo ** in which to store the ITypeInfo
 *                  interface for the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CImpIDispatch::GetTypeInfo(UINT itInfo, LCID lcid
    , ITypeInfo **ppITypeInfo)
    {
    HRESULT     hr;
    ITypeLib   *pITypeLib;
    ITypeInfo **ppITI=NULL;

    if (0!=itInfo)
        return ResultFromScode(TYPE_E_ELEMENTNOTFOUND);

    if (NULL==ppITypeInfo)
        return ResultFromScode(E_POINTER);

    *ppITypeInfo=NULL;

    /*
     * Since we returned one from GetTypeInfoCount, this function
     * can be called for a specific locale.  We support English,
     * German, and neutral (defaults to English) locales.  Anything
     * else is an error.
     *
     * After this switch statement, ppITI will either be NULL or
     * a valid pointer in it after.  If NULL, we know we need to
     * load type information, retrieve the ITypeInfo we want, and
     * then store it in *ppITI.
     */
    switch (PRIMARYLANGID(lcid))
        {
        case LANG_NEUTRAL:
        case LANG_ENGLISH:
            ppITI=&m_pITINeutral;
            break;

        case LANG_GERMAN:
            ppITI=&m_pITIGerman;
            break;

        default:
            return ResultFromScode(DISP_E_UNKNOWNLCID);
        }

    //Load a type lib if we don't have the information already.
    if (NULL==*ppITI)
        {
        /*
         * The type libraries are registered under 0 (neutral),
         * 7 (German), and 9 (English) with no specific sub-
         * language, which would make them 407 or 409 and such.
         * If you are sensitive to sub-languages, then use the
         * full LCID instead of just the LANGID as done here.
         */
        hr=LoadRegTypeLib(LIBID_BeeperTypeLibrary, 1, 0
            , PRIMARYLANGID(lcid), &pITypeLib);

        /*
         * If LoadRegTypeLib fails, try loading directly with
         * LoadTypeLib, which will register the library for us.
         * Note that there's no default case here because the
         * prior switch will have filtered lcid already.
         *
         * NOTE:  You should prepend your DIR registry key to the
         * .TLB name so you don't depend on it being it the PATH.
         * This sample will be updated later to reflect this.
         */
        if (FAILED(hr))
            {
            switch (PRIMARYLANGID(lcid))
                {
                case LANG_NEUTRAL:
                case LANG_ENGLISH:
                    hr=LoadTypeLib(OLETEXT("BEEP0000.TLB"), &pITypeLib);
                    break;

                case LANG_GERMAN:
                    hr=LoadTypeLib(OLETEXT("BEEP0007.TLB"), &pITypeLib);
                    break;
                }
            }

        if (FAILED(hr))
            return hr;

        //Got the type lib, get type info for the interface we want
        hr=pITypeLib->GetTypeInfoOfGuid(DIID_DIBeeper, ppITI);
        pITypeLib->Release();

        if (FAILED(hr))
            return hr;
        }

    /*
     * Note:  the type library is still loaded since we have
     * an ITypeInfo from it.
     */

    (*ppITI)->AddRef();
    *ppITypeInfo=*ppITI;
    return NOERROR;
    }

/*
 * CImpIDispatch::GetIDsOfNames
 *
 * Purpose:
 *  Converts text names into DISPIDs to pass to Invoke
 *
 * Parameters:
 *  riid            REFIID reserved.  Must be IID_NULL.
 *  rgszNames       OLECHAR ** pointing to the array of names to be
 *                  mapped.
 *  cNames          UINT number of names to be mapped.
 *  lcid            LCID of the locale.
 *  rgDispID        DISPID * caller allocated array containing IDs
 *                  corresponging to those names in rgszNames.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CImpIDispatch::GetIDsOfNames(REFIID riid
    , OLECHAR **rgszNames, UINT cNames, LCID lcid, DISPID *rgDispID)
    {
    HRESULT     hr;
    ITypeInfo  *pTI;

    if (IID_NULL!=riid)
        return ResultFromScode(DISP_E_UNKNOWNINTERFACE);

    //Get the right ITypeInfo for lcid.
    hr=GetTypeInfo(0, lcid, &pTI);

    if (SUCCEEDED(hr))
        {
        hr=DispGetIDsOfNames(pTI, rgszNames, cNames, rgDispID);
        pTI->Release();
        }

    return hr;
    }

/*
 * CImpIDispatch::Invoke
 *
 * Purpose:
 *  Calls a method in the dispatch interface or manipulates a
 *  property.
 *
 * Parameters:
 *  dispID          DISPID of the method or property of interest.
 *  riid            REFIID reserved, must be IID_NULL.
 *  lcid            LCID of the locale.
 *  wFlags          USHORT describing the context of the invocation.
 *  pDispParams     DISPPARAMS * to the array of arguments.
 *  pVarResult      VARIANT * in which to store the result.  Is
 *                  NULL if the caller is not interested.
 *  pExcepInfo      EXCEPINFO * to exception information.
 *  puArgErr        UINT * in which to store the index of an
 *                  invalid parameter if DISP_E_TYPEMISMATCH
 *                  is returned.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CImpIDispatch::Invoke(DISPID dispID, REFIID riid
    , LCID lcid, unsigned short wFlags, DISPPARAMS *pDispParams
    , VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
    {
    HRESULT     hr;
    ITypeInfo  *pTI;

    //riid is supposed to be IID_NULL always
    if (IID_NULL!=riid)
        return ResultFromScode(DISP_E_UNKNOWNINTERFACE);

    //Get the ITypeInfo for lcid
    hr=GetTypeInfo(0, lcid, &pTI);

    if (FAILED(hr))
        return hr;

    /*
     * Clear exceptions.  Note that this mechanism only works
     * for a single-thread, which is fine for the initial 32-bit
     * release of OLE but not a long-term solution.  See Beeper3
     * for a way of handling multi-thread exceptions.
     */
    m_wException=EXCEPTION_NONE;

    //This is exactly what DispInvoke does--so skip the overhead.
    hr=pTI->Invoke((IBeeper *)m_pObj, dispID, wFlags
        , pDispParams, pVarResult, pExcepInfo, puArgErr);

    /*
     * Check if an exception was raised.  Again, this is not
     * thread-safe.
     */
    if (EXCEPTION_NONE!=m_wException)
        {
        pExcepInfo->scode
            =(SCODE)MAKELONG(m_wException, PRIMARYLANGID(lcid));
        FillException(pExcepInfo);
        hr=ResultFromScode(DISP_E_EXCEPTION);
        }

    pTI->Release();
    return hr;
    }

/*
 * CImpIDispatch::Exception
 *
 * Purpose:
 *  Raises an exception for CImpIDispatch::Invoke from within
 *  ITypeInfo::Invoke.  This simply sets the m_wException variable
 *  to the exception code.  Invoke will pick this up and fill in
 *  the exception structure as necessary.
 *
 * Parameters:
 *  wException      WORD exception code.
 */

void CImpIDispatch::Exception(WORD wException)
    {
    /*
     * Note: calls to this function will be wrapped in a critical
     * section on Win32 because this is always called from within
     * DispInvoke which we wrapped above in Invoke.
     */
    m_wException=wException;
    return;
    }

/*
 * FillException
 *
 * Purpose:
 *  Callback function pointed to in IDispatch::Invoke that fills
 *  an EXCEPINFO structure based on the code stored inside
 *  Invoke.  This is a nice mechanism to keep all the management
 *  of error code strings and help IDs centralized in one place,
 *  even across many different automation objects within the same
 *  application.  It also keeps Invoke cleaner.
 *
 * Parameters:
 *  pExcepInfo      EXCEPINFO * to fill.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, error code otherwise.
 */

HRESULT STDAPICALLTYPE FillException(EXCEPINFO *pExcepInfo)
    {
    SCODE       scode;
    LANGID      langID;
    USHORT      wCode;
    HRESULT     hr;
    LPTSTR      psz;
    LPOLESTR    pszHelp;
    UINT        idsSource;
    UINT        idsException;

    if (NULL==pExcepInfo)
        return ResultFromScode(E_INVALIDARG);

    scode=pExcepInfo->scode;
    langID=HIWORD(scode);
    wCode=LOWORD(scode);

    //Allocate BSTRs for source and description strings
    psz=(LPTSTR)malloc(1024*sizeof(TCHAR));

    if (NULL==psz)
        return ResultFromScode(E_OUTOFMEMORY);

    hr=NOERROR;

    switch (wCode)
        {
        case EXCEPTION_INVALIDSOUND:
            //Fill in unused information, macro in inole.h
            INITEXCEPINFO(*pExcepInfo);
            pExcepInfo->wCode=wCode;

            pExcepInfo->dwHelpContext=HID_SOUND_PROPERTY_LIMITATIONS;

            //We registered a HELPDIR so we don't include paths
            pszHelp=OLETEXT("beep0000.hlp");
            idsSource=IDS_0_EXCEPTIONSOURCE;
            idsException=IDS_0_EXCEPTIONINVALIDSOUND;

            //Get the localized source and exception strings
            switch (langID)
                {
                case LANG_GERMAN:
                    idsSource=IDS_7_EXCEPTIONSOURCE;
                    idsException=IDS_7_EXCEPTIONINVALIDSOUND;
                    pszHelp=OLETEXT("beep0007.hlp");
                    break;

                case LANG_ENGLISH:
                case LANG_NEUTRAL:
                default:
                    break;
                }

            break;

        default:
            hr=ResultFromScode(E_FAIL);
        }

    if (SUCCEEDED(hr))
        {
        pExcepInfo->bstrHelpFile=SysAllocString(pszHelp);

       #ifdef WIN32ANSI
        OLECHAR     szTemp[256];

        LoadString(g_hInst, idsSource, psz, 256);
        MultiByteToWideChar(CP_ACP, 0, psz, -1, szTemp, 256);
        pExcepInfo->bstrSource=SysAllocString(szTemp);

        LoadString(g_hInst, idsException, psz, 256);
        MultiByteToWideChar(CP_ACP, 0, psz, -1, szTemp, 256);
        pExcepInfo->bstrDescription=SysAllocString(szTemp);
       #else
        LoadString(g_hInst, idsSource, psz, 1024);
        pExcepInfo->bstrSource=SysAllocString(psz);
        LoadString(g_hInst, idsException, psz, 1024);
        pExcepInfo->bstrDescription=SysAllocString(psz);
       #endif
        }

    free(psz);
    return hr;
    }


BEEPER.H

/*
 * BEEPER.H
 * Beeper Automation Object #2 Chapter 14
 *
 * Classes that implement the Beeper object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _BEEPER_H_
#define _BEEPER_H_

/*
 * This #define tells <bookguid.h> to not define GUIDs that will
 * be defined in MKTYPLIB-generated header files, like ibeeper.h.
 */
#define GUIDS_FROM_TYPELIB
#define INC_AUTOMATION
#define CHAPTER14
#include <inole.h>
#include <malloc.h>

//This file is generated from MKTYPLIB
#include "ibeeper.h"

//Help context ID for exceptions
#define HID_SOUND_PROPERTY_LIMITATIONS  0x1E100

//English exception strings
#define IDS_0_MIN                   16
#define IDS_0_EXCEPTIONSOURCE       (IDS_0_MIN)
#define IDS_0_EXCEPTIONINVALIDSOUND (IDS_0_MIN+1)

//German exception strings
#define IDS_7_MIN                   32
#define IDS_7_EXCEPTIONSOURCE       (IDS_7_MIN)
#define IDS_7_EXCEPTIONINVALIDSOUND (IDS_7_MIN+1)

//Forward class declarations for friend statements
class CImpIDispatch;
typedef CImpIDispatch *PCImpIDispatch;

class CBeeper : public IBeeper
    {
    friend CImpIDispatch;

    protected:
        ULONG           m_cRef;             //Object reference count
        LPUNKNOWN       m_pUnkOuter;        //Controlling unknown
        PFNDESTROYED    m_pfnDestroy;       //To call on closure

        long            m_lSound;
        PCImpIDispatch  m_pImpIDispatch;    //Our IDispatch

    public:
        CBeeper(LPUNKNOWN, PFNDESTROYED);
        ~CBeeper(void);

        BOOL         Init(void);

        //Non-delegating object IUnknown
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IBeeper functions
        STDMETHODIMP_(long)  get_Sound(void);
        STDMETHODIMP_(void)  put_Sound(long);
        STDMETHODIMP_(long)  Beep(void);
    };

typedef CBeeper *PCBeeper;

//DISPIDs for our dispinterface
enum
    {
    PROPERTY_SOUND=0,
    METHOD_BEEP
    };

/*
 * IDispatch interface implementations for the Beeper.
 */

class CImpIDispatch : public IDispatch
    {
    public:
        ULONG           m_cRef;     //For debugging

    private:
        PCBeeper        m_pObj;
        LPUNKNOWN       m_pUnkOuter;
        WORD            m_wException;

        ITypeInfo      *m_pITINeutral;      //Type information
        ITypeInfo      *m_pITIGerman;

    public:
        CImpIDispatch(PCBeeper, LPUNKNOWN);
        ~CImpIDispatch(void);

        /*
         * This function is called from CBeeper functions that
         * are called from within DispInvoke to set an exception.
         * This is because the CBeeper functions have no way to
         * tell DispInvoke of such exceptions, so we have to
         * tell our IDispatch::Invoke implementation directly.
         */
        void Exception(WORD);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IDispatch members
        STDMETHODIMP GetTypeInfoCount(UINT *);
        STDMETHODIMP GetTypeInfo(UINT, LCID, ITypeInfo **);
        STDMETHODIMP GetIDsOfNames(REFIID, OLECHAR **, UINT, LCID
            , DISPID *);
        STDMETHODIMP Invoke(DISPID, REFIID, LCID, WORD
            , DISPPARAMS *, VARIANT *, EXCEPINFO *, UINT *);
    };

//Exceptions we can throw from IDispatch::Invoke
enum
    {
    EXCEPTION_NONE=0,
    EXCEPTION_INVALIDSOUND=1000
    };

//Exception filling function for the EXCEPINFO structure.
HRESULT STDAPICALLTYPE FillException(EXCEPINFO *);

#endif //_BEEPER_H_


DBEEPER.CPP

/*
 * DBEEPER.CPP
 * Beeper Automation Object Chapter 14
 *
 * Server module code for the Beeper object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "dbeeper.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;
HINSTANCE   g_hInst=NULL;   //For LoadString

/*
 * LibMain(32)
 *
 * Purpose:
 *  Entry point conditionally compiled for Win32 and Windows
 *  3.1.  Provides the proper structure for each environment.
 */

#ifdef WIN32
BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    g_hInst=hInstance;

    if (DLL_PROCESS_DETACH==ulReason)
        {
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    return TRUE;
    }
#else
int PASCAL LibMain(HINSTANCE hInstance, WORD wDataSeg
    , WORD cbHeapSize, LPSTR lpCmdLine)
    {
    if (0!=cbHeapSize)
        UnlockData(0);

    g_hInst=hInstance;
    return (int)hInstance;
    }
#endif

/*
 * DllGetClassObject
 * DllCanUnloadNow
 * Standard COM exports for DLL servers.
 */

HRESULT APIENTRY DllGetClassObject(REFCLSID rclsid, REFIID riid
    , PPVOID ppv)
    {
    CBeeperFactory *pBF;
    HRESULT         hr;

    if (CLSID_Beeper!=rclsid)
        return ResultFromScode(E_FAIL);

    //Check that we can provide the interface
    if (IID_IUnknown!=riid && IID_IClassFactory!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Return our beeper factory's IClassFactory
    pBF=new CBeeperFactory();

    if (NULL==pBF)
        return ResultFromScode(E_OUTOFMEMORY);

    //If the factory hasn't the interface, delete it
    hr=pBF->QueryInterface(riid, ppv);

    if (FAILED(hr))
        delete pBF;
    else
        g_cObj++;

    return hr;
    }

STDAPI DllCanUnloadNow(void)
    {
    SCODE   sc;

    //Our answer is whether there are any object or locks
    sc=(0L==g_cObj && 0L==g_cLock) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the object to call when it gets destroyed.  Since
 *  we're in a DLL we only track the number of objects here, letting
 *  DllCanUnloadNow take care of the rest.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;
    return;
    }

/*
 * CBeeperFactory::CBeeperFactory
 * CBeeperFactory::~CBeeperFactory
 */

CBeeperFactory::CBeeperFactory(void)
    {
    m_cRef=0L;
    return;
    }

CBeeperFactory::~CBeeperFactory(void)
    {
    return;
    }

/*
 * CBeeperFactory::QueryInterface
 * CBeeperFactory::AddRef
 * CBeeperFactory::Release
 */

STDMETHODIMP CBeeperFactory::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CBeeperFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CBeeperFactory::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    ObjectDestroyed();
    return 0;
    }

/*
 * CBeeperFactory::CreateInstance
 * CBeeperFactory::LockServer
 */

STDMETHODIMP CBeeperFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCBeeper            pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //Verify that a controlling unknown asks for IUnknown
    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    //Create the object passing function to notify on destruction.
    pObj=new CBeeper(pUnkOuter, ObjectDestroyed);

    if (NULL==pObj)
        return hr;

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    //Kill the object if initial creation or Init failed.
    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

STDMETHODIMP CBeeperFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        g_cLock--;

    return NOERROR;
    }


IBEEPER.H

/* This header file machine-generated by mktyplib.exe */
/* Interface to type library: BeeperTypeLibrary */

#ifndef _BeeperTypeLibrary_H_
#define _BeeperTypeLibrary_H_

DEFINE_GUID(LIBID_BeeperTypeLibrary,0x0002115E,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

DEFINE_GUID(IID_IBeeper,0x0002115C,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

/* Definition of interface: IBeeper */
DECLARE_INTERFACE_(IBeeper, IUnknown)
{
#ifndef NO_BASEINTERFACE_FUNCS

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;
#endif

    /* IBeeper methods */
    STDMETHOD_(long, get_Sound)(THIS) PURE;
    STDMETHOD_(void, put_Sound)(THIS_ long lSound) PURE;
    STDMETHOD_(long, Beep)(THIS) PURE;
};

DEFINE_GUID(DIID_DIBeeper,0x0002115D,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

/* Definition of dispatch interface: DIBeeper */
DECLARE_INTERFACE_(DIBeeper, IDispatch)
{
#ifndef NO_BASEINTERFACE_FUNCS

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(THIS_ UINT FAR* pctinfo) PURE;

    STDMETHOD(GetTypeInfo)(
      THIS_
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo) PURE;

    STDMETHOD(GetIDsOfNames)(
      THIS_
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid) PURE;

    STDMETHOD(Invoke)(
      THIS_
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr) PURE;
#endif

/* Capable of dispatching all the methods of interface IBeeper */
};

DEFINE_GUID(CLSID_Beeper,0x0002115B,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

class Beeper;

#endif


BEEPER.CPP

/*
 * BEEPER.CPP
 * Beeper Automation Object #3 Chapter 14
 *
 * Implementation of the CBeeper class demonstrating the use of
 * standard error handling that ties in with ITypeInfo::Invoke
 * (as well as DispInvoke).  This includes an implementation of
 * ISupportErrorInfo.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "beeper.h"

extern HINSTANCE g_hInst;

#ifdef WIN32
extern DWORD     g_dwTLS;
#else
LANGID           g_langID;
#endif

/*
 * CBeeper::CBeeper
 * CBeeper::~CBeeper
 *
 * Parameters (Constructor):
 *  pUnkOuter       LPUNKNOWN of a controlling unknown.
 *  pfnDestroy      PFNDESTROYED to call when an object
 *                  is destroyed.
 */

CBeeper::CBeeper(LPUNKNOWN pUnkOuter, PFNDESTROYED pfnDestroy)
    {
    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pfnDestroy=pfnDestroy;

    m_lSound=0;
    m_pImpIDispatch=NULL;
    return;
    }

CBeeper::~CBeeper(void)
    {
    DeleteInterfaceImp(m_pImpIDispatch);
    return;
    }

/*
 * CBeeper::Init
 *
 * Purpose:
 *  Performs any intiailization of a CBeeper that's prone to failure
 *  that we also use internally before exposing the object outside.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CBeeper::Init(void)
    {
    LPUNKNOWN       pIUnknown=this;

    if (NULL!=m_pUnkOuter)
        pIUnknown=m_pUnkOuter;

    m_pImpIDispatch=new CImpIDispatch(this, pIUnknown);

    if (NULL==m_pImpIDispatch)
        return FALSE;

    return TRUE;
    }

/*
 * CBeeper::QueryInterface
 * CBeeper::AddRef
 * CBeeper::Release
 */

STDMETHODIMP CBeeper::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    /*
     * The only calls for IUnknown are either in a nonaggregated
     * case or when created in an aggregation, so in either case
     * always return our IUnknown for IID_IUnknown.
     *
     * Note that IBeeper is implemented on CBeeper, and we must
     * also expose that custom interface through QueryInterface.
     */
    if (IID_IUnknown==riid || IID_IBeeper==riid)
        *ppv=this;

    /*
     * QueryInterface must respond not only to IID_IDispatch for
     * the primary automation interface, but also to the IID of the
     * dispinterface itself, which in our case is DIID_DIBeeper.
     */
    if (IID_IDispatch==riid || DIID_DIBeeper==riid)
        *ppv=m_pImpIDispatch;

    //Indicate that we support error information
    if (IID_ISupportErrorInfo==riid)
        *ppv=m_pImpISuppErr;

    //AddRef any interface we'll return.
    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CBeeper::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CBeeper::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0L;
    }

//IBeeper interface functions

/*
 * CBeeper::get_Sound
 * CBeeper::put_Sound
 *
 * Purpose:
 *  Functions called from DispInvoke to handle the Sound property.
 *
 * Parameters (Set only):
 *  lSound          long, new sound to save after validation
 *
 * Return Value: (Get only):
 *  long            Current sound.
 */

STDMETHODIMP_(long) CBeeper::get_Sound(void)
    {
    return m_lSound;
    }

STDMETHODIMP_(void) CBeeper::put_Sound(long lSound)
    {
    if (MB_OK!=lSound && MB_ICONEXCLAMATION!=lSound
        && MB_ICONQUESTION!=lSound && MB_ICONHAND!=lSound
        && MB_ICONASTERISK!=lSound)
        {
        /*
         * Since we cannot return a value to ITypeInfo::Invoke to
         * indicate an exception condition, we'll use
         * m_pImpIDispatch->Exception to raise them.  This
         * Exception function now uses the error info mechanism
         * to raise exceptions rather than relying on our own
         * single-threaded model as used in Beeper #2.
         */
        m_pImpIDispatch->Exception(EXCEPTION_INVALIDSOUND);
        return;
        }

    m_lSound=lSound;
    return;
    }

/*
 * CBeeper::Beep
 *
 * Purpose:
 *  Function called from DispInvoke to invoke the Beep method.
 *
 * Return Value:
 *  long           The sound played.
 */

STDMETHODIMP_(long) CBeeper::Beep(void)
    {
    MessageBeep((UINT)m_lSound);
    return m_lSound;
    }

//IDispatch interface implementation

/*
 * CImpIDispatch::CImpIDispatch
 * CImpIDispatch::~CImpIDispatch
 *
 * Parameters (Constructor):
 *  pObj            PCBeeper of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIDispatch::CImpIDispatch(PCBeeper pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;

    //These are created as needed in GetTypeInfo
    m_pITINeutral=NULL;
    m_pITIGerman=NULL;
    return;
    }

CImpIDispatch::~CImpIDispatch(void)
    {
    ReleaseInterface(m_pITIGerman);
    ReleaseInterface(m_pITINeutral);
    return;
    }

/*
 * CImpIDispatch::QueryInterface
 * CImpIDispatch::AddRef
 * CImpIDispatch::Release
 *
 * Purpose:
 *  IUnknown members for CImpIDispatch object.
 */

STDMETHODIMP CImpIDispatch::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIDispatch::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIDispatch::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIDispatch::GetTypeInfoCount
 *
 * Purpose:
 *  Returns the number of type information (ITypeInfo) interfaces
 *  that the object provides (0 or 1).
 *
 * Parameters:
 *  pctInfo         UINT * to the location to receive
 *                  the count of interfaces.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CImpIDispatch::GetTypeInfoCount(UINT *pctInfo)
    {
    //We implement GetTypeInfo so return 1
    *pctInfo=1;
    return NOERROR;
    }

/*
 * CImpIDispatch::GetTypeInfo
 *
 * Purpose:
 *  Retrieves type information for the automation interface.  This
 *  is used anywhere that the right ITypeInfo interface is needed
 *  for whatever LCID is applicable.  Specifically, this is used
 *  from within GetIDsOfNames and Invoke.
 *
 * Parameters:
 *  itInfo          UINT reserved.  Must be zero.
 *  lcid            LCID providing the locale for the type
 *                  information.  If the object does not support
 *                  localization, this is ignored.
 *  ppITypeInfo     ITypeInfo ** in which to store the ITypeInfo
 *                  interface for the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CImpIDispatch::GetTypeInfo(UINT itInfo, LCID lcid
    , ITypeInfo **ppITypeInfo)
    {
    HRESULT     hr;
    ITypeLib   *pITypeLib;
    ITypeInfo **ppITI=NULL;

    if (0!=itInfo)
        return ResultFromScode(TYPE_E_ELEMENTNOTFOUND);

    if (NULL==ppITypeInfo)
        return ResultFromScode(E_POINTER);

    *ppITypeInfo=NULL;

    /*
     * Since we returned one from GetTypeInfoCount, this function
     * can be called for a specific locale.  We support English,
     * German, and neutral (defaults to English) locales.  Anything
     * else is an error.
     *
     * After this switch statement, ppITI will either be NULL or
     * a valid pointer in it after.  If NULL, we know we need to
     * load type information, retrieve the ITypeInfo we want, and
     * then store it in *ppITI.
     */
    switch (PRIMARYLANGID(lcid))
        {
        case LANG_NEUTRAL:
        case LANG_ENGLISH:
            ppITI=&m_pITINeutral;
            break;

        case LANG_GERMAN:
            ppITI=&m_pITIGerman;
            break;

        default:
            return ResultFromScode(DISP_E_UNKNOWNLCID);
        }

    //Load a type lib if we don't have the information already.
    if (NULL==*ppITI)
        {
        /*
         * The type libraries are registered under 0 (neutral),
         * 7 (German), and 9 (English) with no specific sub-
         * language, which would make them 407 or 409 and such.
         * If you are sensitive to sub-languages, then use the
         * full LCID instead of just the LANGID as done here.
         */
        hr=LoadRegTypeLib(LIBID_BeeperTypeLibrary, 1, 0
            , PRIMARYLANGID(lcid), &pITypeLib);

        /*
         * If LoadRegTypeLib fails, try loading directly with
         * LoadTypeLib, which will register the library for us.
         * Note that there's no default case here because the
         * prior switch will have filtered lcid already.
         *
         * NOTE:  You should prepend your DIR registry key to the
         * .TLB name so you don't depend on it being it the PATH.
         * This sample will be updated later to reflect this.
         */
        if (FAILED(hr))
            {
            switch (PRIMARYLANGID(lcid))
                {
                case LANG_NEUTRAL:
                case LANG_ENGLISH:
                    hr=LoadTypeLib(OLETEXT("BEEP0000.TLB"), &pITypeLib);
                    break;

                case LANG_GERMAN:
                    hr=LoadTypeLib(OLETEXT("BEEP0007.TLB"), &pITypeLib);
                    break;
                }
            }

        if (FAILED(hr))
            return hr;

        //Got the type lib, get type info for the interface we want
        hr=pITypeLib->GetTypeInfoOfGuid(DIID_DIBeeper, ppITI);
        pITypeLib->Release();

        if (FAILED(hr))
            return hr;
        }

    /*
     * Note:  the type library is still loaded since we have
     * an ITypeInfo from it.
     */

    (*ppITI)->AddRef();
    *ppITypeInfo=*ppITI;
    return NOERROR;
    }

/*
 * CImpIDispatch::GetIDsOfNames
 *
 * Purpose:
 *  Converts text names into DISPIDs to pass to Invoke
 *
 * Parameters:
 *  riid            REFIID reserved.  Must be IID_NULL.
 *  rgszNames       OLECHAR ** pointing to the array of names to be
 *                  mapped.
 *  cNames          UINT number of names to be mapped.
 *  lcid            LCID of the locale.
 *  rgDispID        DISPID * caller allocated array containing IDs
 *                  corresponging to those names in rgszNames.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CImpIDispatch::GetIDsOfNames(REFIID riid
    , OLECHAR **rgszNames, UINT cNames, LCID lcid, DISPID *rgDispID)
    {
    HRESULT     hr;
    ITypeInfo  *pTI;

    if (IID_NULL!=riid)
        return ResultFromScode(DISP_E_UNKNOWNINTERFACE);

    //Get the right ITypeInfo for lcid.
    hr=GetTypeInfo(0, lcid, &pTI);

    if (SUCCEEDED(hr))
        {
        hr=DispGetIDsOfNames(pTI, rgszNames, cNames, rgDispID);
        pTI->Release();
        }

    return hr;
    }

/*
 * CImpIDispatch::Invoke
 *
 * Purpose:
 *  Calls a method in the dispatch interface or manipulates a
 *  property.
 *
 * Parameters:
 *  dispID          DISPID of the method or property of interest.
 *  riid            REFIID reserved, must be IID_NULL.
 *  lcid            LCID of the locale.
 *  wFlags          USHORT describing the context of the invocation.
 *  pDispParams     DISPPARAMS * to the array of arguments.
 *  pVarResult      VARIANT * in which to store the result.  Is
 *                  NULL if the caller is not interested.
 *  pExcepInfo      EXCEPINFO * to exception information.
 *  puArgErr        UINT * in which to store the index of an
 *                  invalid parameter if DISP_E_TYPEMISMATCH
 *                  is returned.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CImpIDispatch::Invoke(DISPID dispID, REFIID riid
    , LCID lcid, unsigned short wFlags, DISPPARAMS *pDispParams
    , VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
    {
    HRESULT     hr;
    ITypeInfo  *pTI;
    LANGID      langID=PRIMARYLANGID(lcid);

    //riid is supposed to be IID_NULL always
    if (IID_NULL!=riid)
        return ResultFromScode(DISP_E_UNKNOWNINTERFACE);

    //Get the ITypeInfo for lcid
    hr=GetTypeInfo(0, lcid, &pTI);

    if (FAILED(hr))
        return hr;

   #ifdef WIN32
    //This saves the language ID for this thread
    TlsSetValue(g_dwTLS, &langID);
   #else
    g_langID=langID;
   #endif

    //Clear exceptions
    SetErrorInfo(0L, NULL);

    //This is exactly what DispInvoke does--so skip the overhead.
    hr=pTI->Invoke((IBeeper *)m_pObj, dispID, wFlags
        , pDispParams, pVarResult, pExcepInfo, puArgErr);

    //Exception handling is done within ITypeInfo::Invoke

    pTI->Release();
    return hr;
    }

/*
 * CImpIDispatch::Exception
 *
 * Purpose:
 *  Raises an exception for CImpIDispatch::Invoke from within
 *  ITypeInfo::Invoke using the CreateErrorInfo API and the
 *  ICreateErrorInfo interface.
 *
 *  Note that this method doesn't allow for deferred filling
 *  of an EXCEPINFO structure.
 *
 * Parameters:
 *  wException      WORD exception code.
 */

void CImpIDispatch::Exception(WORD wException)
    {
    HRESULT             hr;
    ICreateErrorInfo   *pICreateErr;
    BOOL                fSuccess;
    LPTSTR              psz;
    LPOLESTR            pszHelp;
    UINT                idsSource;
    UINT                idsException;
    DWORD               dwHelpID;
    LANGID              langID=LANG_NEUTRAL;
   #ifdef WIN32
    LANGID             *pLangID;
   #endif

   #ifdef WIN32
    pLangID=(LANGID *)TlsGetValue(g_dwTLS);

    if (NULL!=pLangID)
        langID=*pLangID;
   #else
        langID=g_langID;
   #endif

    /*
     * Thread-safe exception handling means that we call
     * CreateErrorInfo which gives us an ICreateErrorInfo pointer
     * that we then use to set the error information (basically
     * to set the fields of an EXCEPINFO structure.  We then
     * call SetErrorInfo to attach this error to the current
     * thread.  ITypeInfo::Invoke will look for this when it
     * returns from whatever function was invokes by calling
     * GetErrorInfo.
     */

    //Not much we can do if this fails.
    if (FAILED(CreateErrorInfo(&pICreateErr)))
        return;

    psz=(LPTSTR)malloc(1024*sizeof(TCHAR));

    if (NULL==psz)
        {
        pICreateErr->Release();
        return;
        }

    fSuccess=FALSE;

    switch (wException)
        {
        case EXCEPTION_INVALIDSOUND:
            pICreateErr->SetGUID(DIID_DIBeeper);

            dwHelpID=HID_SOUND_PROPERTY_LIMITATIONS;

            pszHelp=OLETEXT("beep0000.hlp");
            idsSource=IDS_0_EXCEPTIONSOURCE;
            idsException=IDS_0_EXCEPTIONINVALIDSOUND;

            switch (langID)
                {
                case LANG_GERMAN:
                    idsSource=IDS_7_EXCEPTIONSOURCE;
                    idsException=IDS_7_EXCEPTIONINVALIDSOUND;
                    pszHelp=OLETEXT("beep0007.hlp");
                    break;

                case LANG_ENGLISH:
                case LANG_NEUTRAL:
                default:
                    break;
                }

            fSuccess=TRUE;
            break;

        default:
            break;
        }

    if (fSuccess)
        {
        IErrorInfo *pIErr;

        /*
         * If you have a help file, call the functions
         * ICreateErrorInfo::SetHelpFile and
         * ICreateErrorInfo::SetHelpContext as well.  If you
         * set the help file to NULL the context is ignored.
         */
        pICreateErr->SetHelpFile(pszHelp);
        pICreateErr->SetHelpContext(dwHelpID);

       #ifdef WIN32ANSI
        OLECHAR     szTemp[256];

        LoadString(g_hInst, idsSource, psz, 256);
        MultiByteToWideChar(CP_ACP, 0, psz, -1, szTemp, 256);
        pICreateErr->SetSource(szTemp);

        LoadString(g_hInst, idsException, psz, 256);
        MultiByteToWideChar(CP_ACP, 0, psz, -1, szTemp, 256);
        pICreateErr->SetDescription(szTemp);
       #else
        LoadString(g_hInst, idsSource, psz, 1024);
        pICreateErr->SetSource(psz);

        LoadString(g_hInst, idsException, psz, 1024);
        pICreateErr->SetDescription(psz);
       #endif

        hr=pICreateErr->QueryInterface(IID_IErrorInfo
            , (PPVOID)&pIErr);

        if (SUCCEEDED(hr))
            {
            SetErrorInfo(0L, pIErr);
            pIErr->Release();
            }
        }

    free(psz);

    //SetErrorInfo holds the object's IErrorInfo
    pICreateErr->Release();
    return;
    }

//ISupportErrorInfo interface implementation

/*
 * CImpISupportErrorInfo::CImpISupportErrorInfo
 * CImpISupportErrorInfo::~CImpISupportErrorInfo
 *
 * Parameters (Constructor):
 *  pObj            PCBeeper of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpISupportErrorInfo::CImpISupportErrorInfo(PCBeeper pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpISupportErrorInfo::~CImpISupportErrorInfo(void)
    {
    return;
    }

/*
 * CImpISupportErrorInfo::QueryInterface
 * CImpISupportErrorInfo::AddRef
 * CImpISupportErrorInfo::Release
 *
 * Purpose:
 *  IUnknown members for CImpISupportErrorInfo object.
 */

STDMETHODIMP CImpISupportErrorInfo::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpISupportErrorInfo::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpISupportErrorInfo::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpISupportErrorInfo::InterfaceSupportsErrorInfo
 *
 * Purpose:
 *  Informs a caller whether or not a specific interface
 *  supports exceptions through the Set/GetErrorInfo mechanism.
 *
 * Parameters:
 *  riid            REFIID of the interface in question.
 *
 * Return Value:
 *  HRESULT         NOERROR if a call to GetErrorInfo will succeed
 *                  for callers of riid.  S_FALSE if not.
 */

STDMETHODIMP CImpISupportErrorInfo::InterfaceSupportsErrorInfo
    (REFIID riid)
    {
    if (DIID_DIBeeper==riid)
        return NOERROR;

    return ResultFromScode(S_FALSE);
    }


BEEPER.H

/*
 * BEEPER.H
 * Beeper Automation Object #3 Chapter 14
 *
 * Classes that implement the Beeper object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _BEEPER_H_
#define _BEEPER_H_

/*
 * This #define tells <bookguid.h> to not define GUIDs that will
 * be defined in MKTYPLIB-generated header files, like ibeeper.h.
 */
#define GUIDS_FROM_TYPELIB
#define INC_AUTOMATION
#define CHAPTER14
#include <inole.h>
#include <malloc.h>

//This file is generated from MKTYPLIB
#include "ibeeper.h"

//Help context ID for exceptions
#define HID_SOUND_PROPERTY_LIMITATIONS  0x1E100

//English exception strings
#define IDS_0_MIN                   16
#define IDS_0_EXCEPTIONSOURCE       (IDS_0_MIN)
#define IDS_0_EXCEPTIONINVALIDSOUND (IDS_0_MIN+1)

//German exception strings
#define IDS_7_MIN                   32
#define IDS_7_EXCEPTIONSOURCE       (IDS_7_MIN)
#define IDS_7_EXCEPTIONINVALIDSOUND (IDS_7_MIN+1)

//Forward class declarations for friend statements
class CImpIDispatch;
typedef CImpIDispatch *PCImpIDispatch;
class CImpISupportErrorInfo;
typedef CImpISupportErrorInfo *PCImpISupportErrorInfo;

class CBeeper : public IBeeper
    {
    friend CImpIDispatch;

    protected:
        ULONG           m_cRef;             //Object reference count
        LPUNKNOWN       m_pUnkOuter;        //Controlling unknown
        PFNDESTROYED    m_pfnDestroy;       //To call on closure

        long            m_lSound;

        //Our interfaces
        PCImpIDispatch         m_pImpIDispatch;
        PCImpISupportErrorInfo m_pImpISuppErr;

    public:
        CBeeper(LPUNKNOWN, PFNDESTROYED);
        ~CBeeper(void);

        BOOL         Init(void);

        //Non-delegating object IUnknown
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IBeeper functions
        STDMETHODIMP_(long)  get_Sound(void);
        STDMETHODIMP_(void)  put_Sound(long);
        STDMETHODIMP_(long)  Beep(void);
    };

typedef CBeeper *PCBeeper;

//DISPIDs for our dispinterface
enum
    {
    PROPERTY_SOUND=0,
    METHOD_BEEP
    };

/*
 * IDispatch interface implementations for the Beeper.
 */

class CImpIDispatch : public IDispatch
    {
    public:
        ULONG           m_cRef;     //For debugging

    private:
        PCBeeper        m_pObj;
        LPUNKNOWN       m_pUnkOuter;

        ITypeInfo      *m_pITINeutral;      //Type information
        ITypeInfo      *m_pITIGerman;

    public:
        CImpIDispatch(PCBeeper, LPUNKNOWN);
        ~CImpIDispatch(void);

        //For raising exceptions
        void                 Exception(WORD);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IDispatch members
        STDMETHODIMP GetTypeInfoCount(UINT *);
        STDMETHODIMP GetTypeInfo(UINT, LCID, ITypeInfo **);
        STDMETHODIMP GetIDsOfNames(REFIID, OLECHAR **, UINT, LCID
            , DISPID *);
        STDMETHODIMP Invoke(DISPID, REFIID, LCID, WORD
            , DISPPARAMS *, VARIANT *, EXCEPINFO *, UINT *);
    };

//Exceptions we can throw from IDispatch::Invoke
enum
    {
    EXCEPTION_NONE=0,
    EXCEPTION_INVALIDSOUND=1000
    };

/*
 * ISupportErrorInfo interface implementations for the Beeper.
 */

class CImpISupportErrorInfo : public ISupportErrorInfo
    {
    public:
        ULONG           m_cRef;     //For debugging

    private:
        PCBeeper        m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpISupportErrorInfo(PCBeeper, LPUNKNOWN);
        ~CImpISupportErrorInfo(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //ISupportErrorInfo members
        STDMETHODIMP InterfaceSupportsErrorInfo(REFIID);
    };

#endif //_BEEPER_H_


DBEEPER.CPP

/*
 * DBEEPER.CPP
 * Beeper Automation Object Chapter 14
 *
 * Server module code for the Beeper object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "dbeeper.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;
HINSTANCE   g_hInst=NULL;   //For LoadString

#ifdef WIN32
DWORD       g_dwTLS;        //For thread-local storage
#endif

/*
 * LibMain(32)
 *
 * Purpose:
 *  Entry point conditionally compiled for Win32 and Windows
 *  3.1.  Provides the proper structure for each environment.
 */

#ifdef WIN32
BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    g_hInst=hInstance;

    if (DLL_PROCESS_DETACH==ulReason)
        {
        TlsFree(g_dwTLS);
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;

        g_dwTLS=TlsAlloc();
        }

    return TRUE;
    }
#else
int PASCAL LibMain(HINSTANCE hInstance, WORD wDataSeg
    , WORD cbHeapSize, LPSTR lpCmdLine)
    {
    if (0!=cbHeapSize)
        UnlockData(0);

    g_hInst=hInstance;
    return (int)hInstance;
    }
#endif

/*
 * DllGetClassObject
 * DllCanUnloadNow
 * Standard COM exports for DLL servers.
 */

HRESULT APIENTRY DllGetClassObject(REFCLSID rclsid, REFIID riid
    , PPVOID ppv)
    {
    CBeeperFactory *pBF;
    HRESULT         hr;

    if (CLSID_Beeper!=rclsid)
        return ResultFromScode(E_FAIL);

    //Check that we can provide the interface
    if (IID_IUnknown!=riid && IID_IClassFactory!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Return our beeper factory's IClassFactory
    pBF=new CBeeperFactory();

    if (NULL==pBF)
        return ResultFromScode(E_OUTOFMEMORY);

    //If the factory hasn't the interface, delete it
    hr=pBF->QueryInterface(riid, ppv);

    if (FAILED(hr))
        delete pBF;
    else
        g_cObj++;

    return hr;
    }

STDAPI DllCanUnloadNow(void)
    {
    SCODE   sc;

    //Our answer is whether there are any object or locks
    sc=(0L==g_cObj && 0L==g_cLock) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the object to call when it gets destroyed.  Since
 *  we're in a DLL we only track the number of objects here, letting
 *  DllCanUnloadNow take care of the rest.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;
    return;
    }

/*
 * CBeeperFactory::CBeeperFactory
 * CBeeperFactory::~CBeeperFactory
 */

CBeeperFactory::CBeeperFactory(void)
    {
    m_cRef=0L;
    return;
    }

CBeeperFactory::~CBeeperFactory(void)
    {
    return;
    }

/*
 * CBeeperFactory::QueryInterface
 * CBeeperFactory::AddRef
 * CBeeperFactory::Release
 */

STDMETHODIMP CBeeperFactory::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CBeeperFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CBeeperFactory::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    ObjectDestroyed();
    return 0;
    }

/*
 * CBeeperFactory::CreateInstance
 * CBeeperFactory::LockServer
 */

STDMETHODIMP CBeeperFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCBeeper            pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //Verify that a controlling unknown asks for IUnknown
    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    //Create the object passing function to notify on destruction.
    pObj=new CBeeper(pUnkOuter, ObjectDestroyed);

    if (NULL==pObj)
        return hr;

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    //Kill the object if initial creation or Init failed.
    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

STDMETHODIMP CBeeperFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        g_cLock--;

    return NOERROR;
    }


BEEPER.CPP

/*
 * BEEPER.CPP
 * Beeper Automation Object #3 Chapter 14
 *
 * Implementation of the CBeeper class demonstrating the use of
 * standard error handling that ties in with ITypeInfo::Invoke
 * (as well as DispInvoke).  This includes an implementation of
 * ISupportErrorInfo.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "beeper.h"

extern HINSTANCE g_hInst;

#ifdef WIN32
extern DWORD     g_dwTLS;
#else
LANGID           g_langID;
#endif

/*
 * CBeeper::CBeeper
 * CBeeper::~CBeeper
 *
 * Parameters (Constructor):
 *  pUnkOuter       LPUNKNOWN of a controlling unknown.
 *  pfnDestroy      PFNDESTROYED to call when an object
 *                  is destroyed.
 */

CBeeper::CBeeper(LPUNKNOWN pUnkOuter, PFNDESTROYED pfnDestroy)
    {
    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pfnDestroy=pfnDestroy;

    m_lSound=0;
    m_pImpIDispatch=NULL;
    return;
    }

CBeeper::~CBeeper(void)
    {
    DeleteInterfaceImp(m_pImpIDispatch);
    return;
    }

/*
 * CBeeper::Init
 *
 * Purpose:
 *  Performs any intiailization of a CBeeper that's prone to failure
 *  that we also use internally before exposing the object outside.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CBeeper::Init(void)
    {
    LPUNKNOWN       pIUnknown=this;

    if (NULL!=m_pUnkOuter)
        pIUnknown=m_pUnkOuter;

    m_pImpIDispatch=new CImpIDispatch(this, pIUnknown);

    if (NULL==m_pImpIDispatch)
        return FALSE;

    return TRUE;
    }

/*
 * CBeeper::QueryInterface
 * CBeeper::AddRef
 * CBeeper::Release
 */

STDMETHODIMP CBeeper::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    /*
     * The only calls for IUnknown are either in a nonaggregated
     * case or when created in an aggregation, so in either case
     * always return our IUnknown for IID_IUnknown.
     *
     * Note that IBeeper is implemented on CBeeper, and we must
     * also expose that custom interface through QueryInterface.
     */
    if (IID_IUnknown==riid || IID_IBeeper==riid)
        *ppv=this;

    /*
     * QueryInterface must respond not only to IID_IDispatch for
     * the primary automation interface, but also to the IID of the
     * dispinterface itself, which in our case is DIID_DIBeeper.
     */
    if (IID_IDispatch==riid || DIID_DIBeeper==riid)
        *ppv=m_pImpIDispatch;

    //Indicate that we support error information
    if (IID_ISupportErrorInfo==riid)
        *ppv=m_pImpISuppErr;

    //AddRef any interface we'll return.
    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CBeeper::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CBeeper::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0L;
    }

//IBeeper interface functions

/*
 * CBeeper::get_Sound
 * CBeeper::put_Sound
 *
 * Purpose:
 *  Functions called from DispInvoke to handle the Sound property.
 *
 * Parameters (Set only):
 *  lSound          long, new sound to save after validation
 *
 * Out Parameters: (Get only):
 *  plSound         long *, the current sound.
 */

STDMETHODIMP CBeeper::get_Sound(long *plSound)
    {
    if (NULL==plSound)
        return ResultFromScode(E_POINTER);

    *plSound=m_lSound;
    return NOERROR;
    }

STDMETHODIMP CBeeper::put_Sound(long lSound)
    {
    if (MB_OK!=lSound && MB_ICONEXCLAMATION!=lSound
        && MB_ICONQUESTION!=lSound && MB_ICONHAND!=lSound
        && MB_ICONASTERISK!=lSound)
        {
        /*
         * If we return an HRESULT type from this method,
         * then ITypeInfo::Invoke will retrieve an error
         * object for us.
         */
        m_pImpIDispatch->Exception(EXCEPTION_INVALIDSOUND);
        return ResultFromScode(DISP_E_EXCEPTION);
        }

    m_lSound=lSound;
    return NOERROR;
    }

/*
 * CBeeper::Beep
 *
 * Purpose:
 *  Function called from DispInvoke to invoke the Beep method.
 *
 * Out Parameter:
 *  plSoundPlayed   long *, the sound played.
 */

STDMETHODIMP CBeeper::Beep(long *plSoundPlayed)
    {
    if (NULL==plSoundPlayed)
        return ResultFromScode(E_POINTER);

    *plSoundPlayed=m_lSound;
    MessageBeep((UINT)m_lSound);
    return NOERROR;
    }

//IDispatch interface implementation

/*
 * CImpIDispatch::CImpIDispatch
 * CImpIDispatch::~CImpIDispatch
 *
 * Parameters (Constructor):
 *  pObj            PCBeeper of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIDispatch::CImpIDispatch(PCBeeper pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;

    //These are created as needed in GetTypeInfo
    m_pITINeutral=NULL;
    m_pITIGerman=NULL;
    return;
    }

CImpIDispatch::~CImpIDispatch(void)
    {
    ReleaseInterface(m_pITIGerman);
    ReleaseInterface(m_pITINeutral);
    return;
    }

/*
 * CImpIDispatch::QueryInterface
 * CImpIDispatch::AddRef
 * CImpIDispatch::Release
 *
 * Purpose:
 *  IUnknown members for CImpIDispatch object.
 */

STDMETHODIMP CImpIDispatch::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIDispatch::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIDispatch::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIDispatch::GetTypeInfoCount
 *
 * Purpose:
 *  Returns the number of type information (ITypeInfo) interfaces
 *  that the object provides (0 or 1).
 *
 * Parameters:
 *  pctInfo         UINT * to the location to receive
 *                  the count of interfaces.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CImpIDispatch::GetTypeInfoCount(UINT *pctInfo)
    {
    //We implement GetTypeInfo so return 1
    *pctInfo=1;
    return NOERROR;
    }

/*
 * CImpIDispatch::GetTypeInfo
 *
 * Purpose:
 *  Retrieves type information for the automation interface.  This
 *  is used anywhere that the right ITypeInfo interface is needed
 *  for whatever LCID is applicable.  Specifically, this is used
 *  from within GetIDsOfNames and Invoke.
 *
 * Parameters:
 *  itInfo          UINT reserved.  Must be zero.
 *  lcid            LCID providing the locale for the type
 *                  information.  If the object does not support
 *                  localization, this is ignored.
 *  ppITypeInfo     ITypeInfo ** in which to store the ITypeInfo
 *                  interface for the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CImpIDispatch::GetTypeInfo(UINT itInfo, LCID lcid
    , ITypeInfo **ppITypeInfo)
    {
    HRESULT     hr;
    ITypeLib   *pITypeLib;
    ITypeInfo **ppITI=NULL;

    if (0!=itInfo)
        return ResultFromScode(TYPE_E_ELEMENTNOTFOUND);

    if (NULL==ppITypeInfo)
        return ResultFromScode(E_POINTER);

    *ppITypeInfo=NULL;

    /*
     * Since we returned one from GetTypeInfoCount, this function
     * can be called for a specific locale.  We support English,
     * German, and neutral (defaults to English) locales.  Anything
     * else is an error.
     *
     * After this switch statement, ppITI will either be NULL or
     * a valid pointer in it after.  If NULL, we know we need to
     * load type information, retrieve the ITypeInfo we want, and
     * then store it in *ppITI.
     */
    switch (PRIMARYLANGID(lcid))
        {
        case LANG_NEUTRAL:
        case LANG_ENGLISH:
            ppITI=&m_pITINeutral;
            break;

        case LANG_GERMAN:
            ppITI=&m_pITIGerman;
            break;

        default:
            return ResultFromScode(DISP_E_UNKNOWNLCID);
        }

    //Load a type lib if we don't have the information already.
    if (NULL==*ppITI)
        {
        /*
         * The type libraries are registered under 0 (neutral),
         * 7 (German), and 9 (English) with no specific sub-
         * language, which would make them 407 or 409 and such.
         * If you are sensitive to sub-languages, then use the
         * full LCID instead of just the LANGID as done here.
         */
        hr=LoadRegTypeLib(LIBID_BeeperTypeLibrary, 1, 0
            , PRIMARYLANGID(lcid), &pITypeLib);

        /*
         * If LoadRegTypeLib fails, try loading directly with
         * LoadTypeLib, which will register the library for us.
         * Note that there's no default case here because the
         * prior switch will have filtered lcid already.
         *
         * NOTE:  You should prepend your DIR registry key to the
         * .TLB name so you don't depend on it being it the PATH.
         * This sample will be updated later to reflect this.
         */
        if (FAILED(hr))
            {
            switch (PRIMARYLANGID(lcid))
                {
                case LANG_NEUTRAL:
                case LANG_ENGLISH:
                    hr=LoadTypeLib(OLETEXT("BEEP0000.TLB"), &pITypeLib);
                    break;

                case LANG_GERMAN:
                    hr=LoadTypeLib(OLETEXT("BEEP0007.TLB"), &pITypeLib);
                    break;
                }
            }

        if (FAILED(hr))
            return hr;

        //Got the type lib, get type info for the interface we want
        hr=pITypeLib->GetTypeInfoOfGuid(DIID_DIBeeper, ppITI);
        pITypeLib->Release();

        if (FAILED(hr))
            return hr;
        }

    /*
     * Note:  the type library is still loaded since we have
     * an ITypeInfo from it.
     */

    (*ppITI)->AddRef();
    *ppITypeInfo=*ppITI;
    return NOERROR;
    }

/*
 * CImpIDispatch::GetIDsOfNames
 *
 * Purpose:
 *  Converts text names into DISPIDs to pass to Invoke
 *
 * Parameters:
 *  riid            REFIID reserved.  Must be IID_NULL.
 *  rgszNames       OLECHAR ** pointing to the array of names to be
 *                  mapped.
 *  cNames          UINT number of names to be mapped.
 *  lcid            LCID of the locale.
 *  rgDispID        DISPID * caller allocated array containing IDs
 *                  corresponging to those names in rgszNames.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CImpIDispatch::GetIDsOfNames(REFIID riid
    , OLECHAR **rgszNames, UINT cNames, LCID lcid, DISPID *rgDispID)
    {
    HRESULT     hr;
    ITypeInfo  *pTI;

    if (IID_NULL!=riid)
        return ResultFromScode(DISP_E_UNKNOWNINTERFACE);

    //Get the right ITypeInfo for lcid.
    hr=GetTypeInfo(0, lcid, &pTI);

    if (SUCCEEDED(hr))
        {
        hr=DispGetIDsOfNames(pTI, rgszNames, cNames, rgDispID);
        pTI->Release();
        }

    return hr;
    }

/*
 * CImpIDispatch::Invoke
 *
 * Purpose:
 *  Calls a method in the dispatch interface or manipulates a
 *  property.
 *
 * Parameters:
 *  dispID          DISPID of the method or property of interest.
 *  riid            REFIID reserved, must be IID_NULL.
 *  lcid            LCID of the locale.
 *  wFlags          USHORT describing the context of the invocation.
 *  pDispParams     DISPPARAMS * to the array of arguments.
 *  pVarResult      VARIANT * in which to store the result.  Is
 *                  NULL if the caller is not interested.
 *  pExcepInfo      EXCEPINFO * to exception information.
 *  puArgErr        UINT * in which to store the index of an
 *                  invalid parameter if DISP_E_TYPEMISMATCH
 *                  is returned.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CImpIDispatch::Invoke(DISPID dispID, REFIID riid
    , LCID lcid, unsigned short wFlags, DISPPARAMS *pDispParams
    , VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
    {
    HRESULT     hr;
    ITypeInfo  *pTI;
    LANGID      langID=PRIMARYLANGID(lcid);

    //riid is supposed to be IID_NULL always
    if (IID_NULL!=riid)
        return ResultFromScode(DISP_E_UNKNOWNINTERFACE);

    //Get the ITypeInfo for lcid
    hr=GetTypeInfo(0, lcid, &pTI);

    if (FAILED(hr))
        return hr;

   #ifdef WIN32
    //This saves the language ID for this thread
    TlsSetValue(g_dwTLS, &langID);
   #else
    g_langID=langID;
   #endif

    //Clear exceptions
    SetErrorInfo(0L, NULL);

    //This is exactly what DispInvoke does--so skip the overhead.
    hr=pTI->Invoke((IBeeper *)m_pObj, dispID, wFlags
        , pDispParams, pVarResult, pExcepInfo, puArgErr);

    //Exception handling is done within ITypeInfo::Invoke

    pTI->Release();
    return hr;
    }

/*
 * CImpIDispatch::Exception
 *
 * Purpose:
 *  Raises an exception for CImpIDispatch::Invoke from within
 *  ITypeInfo::Invoke using the CreateErrorInfo API and the
 *  ICreateErrorInfo interface.
 *
 *  Note that this method doesn't allow for deferred filling
 *  of an EXCEPINFO structure.
 *
 * Parameters:
 *  wException      WORD exception code.
 */

void CImpIDispatch::Exception(WORD wException)
    {
    HRESULT             hr;
    ICreateErrorInfo   *pICreateErr;
    BOOL                fSuccess;
    LPTSTR              psz;
    LPOLESTR            pszHelp;
    UINT                idsSource;
    UINT                idsException;
    DWORD               dwHelpID;
    LANGID              langID=LANG_NEUTRAL;
   #ifdef WIN32
    LANGID             *pLangID;
   #endif

   #ifdef WIN32
    pLangID=(LANGID *)TlsGetValue(g_dwTLS);

    if (NULL!=pLangID)
        langID=*pLangID;
   #else
        langID=g_langID;
   #endif

    /*
     * Thread-safe exception handling means that we call
     * CreateErrorInfo which gives us an ICreateErrorInfo pointer
     * that we then use to set the error information (basically
     * to set the fields of an EXCEPINFO structure.  We then
     * call SetErrorInfo to attach this error to the current
     * thread.  ITypeInfo::Invoke will look for this when it
     * returns from whatever function was invokes by calling
     * GetErrorInfo.
     */

    //Not much we can do if this fails.
    if (FAILED(CreateErrorInfo(&pICreateErr)))
        return;

    psz=(LPTSTR)malloc(1024*sizeof(TCHAR));

    if (NULL==psz)
        {
        pICreateErr->Release();
        return;
        }

    fSuccess=FALSE;

    switch (wException)
        {
        case EXCEPTION_INVALIDSOUND:
            pICreateErr->SetGUID(DIID_DIBeeper);

            dwHelpID=HID_SOUND_PROPERTY_LIMITATIONS;

            pszHelp=OLETEXT("beep0000.hlp");
            idsSource=IDS_0_EXCEPTIONSOURCE;
            idsException=IDS_0_EXCEPTIONINVALIDSOUND;

            switch (langID)
                {
                case LANG_GERMAN:
                    idsSource=IDS_7_EXCEPTIONSOURCE;
                    idsException=IDS_7_EXCEPTIONINVALIDSOUND;
                    pszHelp=OLETEXT("beep0007.hlp");
                    break;

                case LANG_ENGLISH:
                case LANG_NEUTRAL:
                default:
                    break;
                }

            fSuccess=TRUE;
            break;

        default:
            break;
        }

    if (fSuccess)
        {
        IErrorInfo *pIErr;

        /*
         * If you have a help file, call the functions
         * ICreateErrorInfo::SetHelpFile and
         * ICreateErrorInfo::SetHelpContext as well.  If you
         * set the help file to NULL the context is ignored.
         */
        pICreateErr->SetHelpFile(pszHelp);
        pICreateErr->SetHelpContext(dwHelpID);

       #ifdef WIN32ANSI
        OLECHAR     szTemp[256];

        LoadString(g_hInst, idsSource, psz, 256);
        MultiByteToWideChar(CP_ACP, 0, psz, -1, szTemp, 256);
        pICreateErr->SetSource(szTemp);

        LoadString(g_hInst, idsException, psz, 256);
        MultiByteToWideChar(CP_ACP, 0, psz, -1, szTemp, 256);
        pICreateErr->SetDescription(szTemp);
       #else
        LoadString(g_hInst, idsSource, psz, 1024);
        pICreateErr->SetSource(psz);

        LoadString(g_hInst, idsException, psz, 1024);
        pICreateErr->SetDescription(psz);
       #endif

        hr=pICreateErr->QueryInterface(IID_IErrorInfo
            , (PPVOID)&pIErr);

        if (SUCCEEDED(hr))
            {
            SetErrorInfo(0L, pIErr);
            pIErr->Release();
            }
        }

    free(psz);

    //SetErrorInfo holds the object's IErrorInfo
    pICreateErr->Release();
    return;
    }

//ISupportErrorInfo interface implementation

/*
 * CImpISupportErrorInfo::CImpISupportErrorInfo
 * CImpISupportErrorInfo::~CImpISupportErrorInfo
 *
 * Parameters (Constructor):
 *  pObj            PCBeeper of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpISupportErrorInfo::CImpISupportErrorInfo(PCBeeper pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpISupportErrorInfo::~CImpISupportErrorInfo(void)
    {
    return;
    }

/*
 * CImpISupportErrorInfo::QueryInterface
 * CImpISupportErrorInfo::AddRef
 * CImpISupportErrorInfo::Release
 *
 * Purpose:
 *  IUnknown members for CImpISupportErrorInfo object.
 */

STDMETHODIMP CImpISupportErrorInfo::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpISupportErrorInfo::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpISupportErrorInfo::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpISupportErrorInfo::InterfaceSupportsErrorInfo
 *
 * Purpose:
 *  Informs a caller whether or not a specific interface
 *  supports exceptions through the Set/GetErrorInfo mechanism.
 *
 * Parameters:
 *  riid            REFIID of the interface in question.
 *
 * Return Value:
 *  HRESULT         NOERROR if a call to GetErrorInfo will succeed
 *                  for callers of riid.  S_FALSE if not.
 */

STDMETHODIMP CImpISupportErrorInfo::InterfaceSupportsErrorInfo
    (REFIID riid)
    {
    if (DIID_DIBeeper==riid)
        return NOERROR;

    return ResultFromScode(S_FALSE);
    }


BEEPER.H

/*
 * BEEPER.H
 * Beeper Automation Object #3 Chapter 14
 *
 * Classes that implement the Beeper object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _BEEPER_H_
#define _BEEPER_H_

/*
 * This #define tells <bookguid.h> to not define GUIDs that will
 * be defined in MKTYPLIB-generated header files, like ibeeper.h.
 */
#define GUIDS_FROM_TYPELIB
#define INC_AUTOMATION
#define CHAPTER14
#include <inole.h>
#include <malloc.h>

//This file is generated from MKTYPLIB
#include "ibeeper.h"

//Help context ID for exceptions
#define HID_SOUND_PROPERTY_LIMITATIONS  0x1E100

//English exception strings
#define IDS_0_MIN                   16
#define IDS_0_EXCEPTIONSOURCE       (IDS_0_MIN)
#define IDS_0_EXCEPTIONINVALIDSOUND (IDS_0_MIN+1)

//German exception strings
#define IDS_7_MIN                   32
#define IDS_7_EXCEPTIONSOURCE       (IDS_7_MIN)
#define IDS_7_EXCEPTIONINVALIDSOUND (IDS_7_MIN+1)

//Forward class declarations for friend statements
class CImpIDispatch;
typedef CImpIDispatch *PCImpIDispatch;
class CImpISupportErrorInfo;
typedef CImpISupportErrorInfo *PCImpISupportErrorInfo;

class CBeeper : public IBeeper
    {
    friend CImpIDispatch;

    protected:
        ULONG           m_cRef;             //Object reference count
        LPUNKNOWN       m_pUnkOuter;        //Controlling unknown
        PFNDESTROYED    m_pfnDestroy;       //To call on closure

        long            m_lSound;

        //Our interfaces
        PCImpIDispatch         m_pImpIDispatch;
        PCImpISupportErrorInfo m_pImpISuppErr;

    public:
        CBeeper(LPUNKNOWN, PFNDESTROYED);
        ~CBeeper(void);

        BOOL         Init(void);

        //Non-delegating object IUnknown
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IBeeper functions
        STDMETHODIMP get_Sound(long *);
        STDMETHODIMP put_Sound(long);
        STDMETHODIMP Beep(long *);
    };

typedef CBeeper *PCBeeper;

//DISPIDs for our dispinterface
enum
    {
    PROPERTY_SOUND=0,
    METHOD_BEEP
    };

/*
 * IDispatch interface implementations for the Beeper.
 */

class CImpIDispatch : public IDispatch
    {
    public:
        ULONG           m_cRef;     //For debugging

    private:
        PCBeeper        m_pObj;
        LPUNKNOWN       m_pUnkOuter;

        ITypeInfo      *m_pITINeutral;      //Type information
        ITypeInfo      *m_pITIGerman;

    public:
        CImpIDispatch(PCBeeper, LPUNKNOWN);
        ~CImpIDispatch(void);

        //For raising exceptions
        void                 Exception(WORD);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IDispatch members
        STDMETHODIMP GetTypeInfoCount(UINT *);
        STDMETHODIMP GetTypeInfo(UINT, LCID, ITypeInfo **);
        STDMETHODIMP GetIDsOfNames(REFIID, OLECHAR **, UINT, LCID
            , DISPID *);
        STDMETHODIMP Invoke(DISPID, REFIID, LCID, WORD
            , DISPPARAMS *, VARIANT *, EXCEPINFO *, UINT *);
    };

//Exceptions we can throw from IDispatch::Invoke
enum
    {
    EXCEPTION_NONE=0,
    EXCEPTION_INVALIDSOUND=1000
    };

/*
 * ISupportErrorInfo interface implementations for the Beeper.
 */

class CImpISupportErrorInfo : public ISupportErrorInfo
    {
    public:
        ULONG           m_cRef;     //For debugging

    private:
        PCBeeper        m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpISupportErrorInfo(PCBeeper, LPUNKNOWN);
        ~CImpISupportErrorInfo(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //ISupportErrorInfo members
        STDMETHODIMP InterfaceSupportsErrorInfo(REFIID);
    };

#endif //_BEEPER_H_


BEEPER.CPP

/*
 * BEEPER.CPP
 * Beeper Automation Object #4 Chapter 14
 *
 * Implementation of the CBeeper class demonstrating the use of
 * dual interface.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "beeper.h"

extern HINSTANCE g_hInst;

#ifdef WIN32
extern DWORD     g_dwTLS;
#endif

/*
 * CBeeper::CBeeper
 * CBeeper::~CBeeper
 *
 * Parameters (Constructor):
 *  pUnkOuter       LPUNKNOWN of a controlling unknown.
 *  pfnDestroy      PFNDESTROYED to call when an object
 *                  is destroyed.
 */

CBeeper::CBeeper(LPUNKNOWN pUnkOuter, PFNDESTROYED pfnDestroy)
    {
    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pfnDestroy=pfnDestroy;

    m_lSound=0;

    //These are created as needed in GetTypeInfo
    m_pITINeutral=NULL;
    m_pITIGerman=NULL;
    return;
    }

CBeeper::~CBeeper(void)
    {
    ReleaseInterface(m_pITIGerman);
    ReleaseInterface(m_pITINeutral);
    return;
    }

/*
 * CBeeper::Init
 *
 * Purpose:
 *  Performs any intiailization of a CBeeper that's prone to failure
 *  that we also use internally before exposing the object outside.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CBeeper::Init(void)
    {
    LPUNKNOWN       pIUnknown=this;

    if (NULL!=m_pUnkOuter)
        pIUnknown=m_pUnkOuter;

    return TRUE;
    }

/*
 * CBeeper::QueryInterface
 * CBeeper::AddRef
 * CBeeper::Release
 */

STDMETHODIMP CBeeper::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    /*
     * IUnknown, IBeeper, and IDispatch are all
     * part of CBeeper now.
     */
    if (IID_IUnknown==riid || IID_IBeeper==riid
        || IID_IDispatch==riid)
        *ppv=this;

    //Indicate that we support error information
    if (IID_ISupportErrorInfo==riid)
        *ppv=m_pImpISuppErr;

    //AddRef any interface we'll return.
    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CBeeper::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CBeeper::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0L;
    }

//IDispatch functions, now part of CBeeper

/*
 * CBeeper::GetTypeInfoCount
 *
 * Purpose:
 *  Returns the number of type information (ITypeInfo) interfaces
 *  that the object provides (0 or 1).
 *
 * Parameters:
 *  pctInfo         UINT * to the location to receive
 *                  the count of interfaces.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CBeeper::GetTypeInfoCount(UINT *pctInfo)
    {
    //We implement GetTypeInfo so return 1
    *pctInfo=1;
    return NOERROR;
    }

/*
 * CBeeper::GetTypeInfo
 *
 * Purpose:
 *  Retrieves type information for the automation interface.  This
 *  is used anywhere that the right ITypeInfo interface is needed
 *  for whatever LCID is applicable.  Specifically, this is used
 *  from within GetIDsOfNames and Invoke.
 *
 * Parameters:
 *  itInfo          UINT reserved.  Must be zero.
 *  lcid            LCID providing the locale for the type
 *                  information.  If the object does not support
 *                  localization, this is ignored.
 *  ppITypeInfo     ITypeInfo ** in which to store the ITypeInfo
 *                  interface for the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CBeeper::GetTypeInfo(UINT itInfo, LCID lcid
    , ITypeInfo **ppITypeInfo)
    {
    HRESULT     hr;
    ITypeLib   *pITypeLib;
    ITypeInfo **ppITI=NULL;

    if (0!=itInfo)
        return ResultFromScode(TYPE_E_ELEMENTNOTFOUND);

    if (NULL==ppITypeInfo)
        return ResultFromScode(E_POINTER);

    *ppITypeInfo=NULL;

    /*
     * Since we returned one from GetTypeInfoCount, this function
     * can be called for a specific locale.  We support English,
     * German, and neutral (defaults to English) locales.  Anything
     * else is an error.
     *
     * After this switch statement, ppITI will either be NULL or
     * a valid pointer in it after.  If NULL, we know we need to
     * load type information, retrieve the ITypeInfo we want, and
     * then store it in *ppITI.
     */
    switch (PRIMARYLANGID(lcid))
        {
        case LANG_NEUTRAL:
        case LANG_ENGLISH:
            ppITI=&m_pITINeutral;
            break;

        case LANG_GERMAN:
            ppITI=&m_pITIGerman;
            break;

        default:
            return ResultFromScode(DISP_E_UNKNOWNLCID);
        }

    //Load a type lib if we don't have the information already.
    if (NULL==*ppITI)
        {
        /*
         * The type libraries are registered under 0 (neutral),
         * 7 (German), and 9 (English) with no specific sub-
         * language, which would make them 407 or 409 and such.
         * If you are sensitive to sub-languages, then use the
         * full LCID instead of just the LANGID as done here.
         */
        hr=LoadRegTypeLib(LIBID_BeeperTypeLibrary, 1, 0
            , PRIMARYLANGID(lcid), &pITypeLib);

        /*
         * If LoadRegTypeLib fails, try loading directly with
         * LoadTypeLib, which will register the library for us.
         * Note that there's no default case here because the
         * prior switch will have filtered lcid already.
         *
         * NOTE:  You should prepend your DIR registry key to the
         * .TLB name so you don't depend on it being it the PATH.
         * This sample will be updated later to reflect this.
         */
        if (FAILED(hr))
            {
            switch (PRIMARYLANGID(lcid))
                {
                case LANG_NEUTRAL:
                case LANG_ENGLISH:
                    hr=LoadTypeLib(OLETEXT("BEEP0000.TLB"), &pITypeLib);
                    break;

                case LANG_GERMAN:
                    hr=LoadTypeLib(OLETEXT("BEEP0007.TLB"), &pITypeLib);
                    break;
                }
            }

        if (FAILED(hr))
            return hr;

        //Got the type lib, get type info for the interface we want
        hr=pITypeLib->GetTypeInfoOfGuid(IID_IBeeper, ppITI);
        pITypeLib->Release();

        if (FAILED(hr))
            return hr;
        }

    /*
     * Note:  the type library is still loaded since we have
     * an ITypeInfo from it.
     */

    (*ppITI)->AddRef();
    *ppITypeInfo=*ppITI;
    return NOERROR;
    }

/*
 * CBeeper::GetIDsOfNames
 *
 * Purpose:
 *  Converts text names into DISPIDs to pass to Invoke
 *
 * Parameters:
 *  riid            REFIID reserved.  Must be IID_NULL.
 *  rgszNames       OLECHAR ** pointing to the array of names to be
 *                  mapped.
 *  cNames          UINT number of names to be mapped.
 *  lcid            LCID of the locale.
 *  rgDispID        DISPID * caller allocated array containing IDs
 *                  corresponging to those names in rgszNames.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CBeeper::GetIDsOfNames(REFIID riid
    , OLECHAR **rgszNames, UINT cNames, LCID lcid, DISPID *rgDispID)
    {
    HRESULT     hr;
    ITypeInfo  *pTI;

    if (IID_NULL!=riid)
        return ResultFromScode(DISP_E_UNKNOWNINTERFACE);

    //Get the right ITypeInfo for lcid.
    hr=GetTypeInfo(0, lcid, &pTI);

    if (SUCCEEDED(hr))
        {
        hr=DispGetIDsOfNames(pTI, rgszNames, cNames, rgDispID);
        pTI->Release();
        }

    return hr;
    }

/*
 * CBeeper::Invoke
 *
 * Purpose:
 *  Calls a method in the dispatch interface or manipulates a
 *  property.
 *
 * Parameters:
 *  dispID          DISPID of the method or property of interest.
 *  riid            REFIID reserved, must be IID_NULL.
 *  lcid            LCID of the locale.
 *  wFlags          USHORT describing the context of the invocation.
 *  pDispParams     DISPPARAMS * to the array of arguments.
 *  pVarResult      VARIANT * in which to store the result.  Is
 *                  NULL if the caller is not interested.
 *  pExcepInfo      EXCEPINFO * to exception information.
 *  puArgErr        UINT * in which to store the index of an
 *                  invalid parameter if DISP_E_TYPEMISMATCH
 *                  is returned.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CBeeper::Invoke(DISPID dispID, REFIID riid
    , LCID lcid, unsigned short wFlags, DISPPARAMS *pDispParams
    , VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
    {
    HRESULT     hr;
    ITypeInfo  *pTI;
    LANGID      langID=PRIMARYLANGID(lcid);

    //riid is supposed to be IID_NULL always
    if (IID_NULL!=riid)
        return ResultFromScode(DISP_E_UNKNOWNINTERFACE);

    //Get the ITypeInfo for lcid
    hr=GetTypeInfo(0, lcid, &pTI);

    if (FAILED(hr))
        return hr;

   #ifdef WIN32
    //This saves the language ID for this thread
    TlsSetValue(g_dwTLS, &langID);
   #endif

    //Clear exceptions
    SetErrorInfo(0L, NULL);

    //This is exactly what DispInvoke does--so skip the overhead.
    hr=pTI->Invoke((IBeeper *)this, dispID, wFlags
        , pDispParams, pVarResult, pExcepInfo, puArgErr);

    //Exception handling is done within ITypeInfo::Invoke

    pTI->Release();
    return hr;
    }

//IBeeper interface functions

/*
 * CBeeper::get_Sound
 * CBeeper::put_Sound
 *
 * Purpose:
 *  Functions called from DispInvoke to handle the Sound property.
 *
 * Parameters (Set only):
 *  lSound          long, new sound to save after validation
 *
 * Out Parameters: (Get only):
 *  plSound         long *, the current sound.
 */

STDMETHODIMP CBeeper::get_Sound(long *plSound)
    {
    if (NULL==plSound)
        return ResultFromScode(E_POINTER);

    *plSound=m_lSound;
    return NOERROR;
    }

STDMETHODIMP CBeeper::put_Sound(long lSound)
    {
    if (MB_OK!=lSound && MB_ICONEXCLAMATION!=lSound
        && MB_ICONQUESTION!=lSound && MB_ICONHAND!=lSound
        && MB_ICONASTERISK!=lSound)
        {
        /*
         * Since we cannot return a value to ITypeInfo::Invoke to
         * indicate an exception condition, we'll use our Exception
         * function to raise them through the error info mechanism.
         */
        Exception(EXCEPTION_INVALIDSOUND);
        return ResultFromScode(DISP_E_EXCEPTION);
        }

    m_lSound=lSound;
    return NOERROR;
    }

/*
 * CBeeper::Beep
 *
 * Purpose:
 *  Function called from DispInvoke to invoke the Beep method.
 *
 * Out Parameter:
 *  plSoundPlayed   long *, the sound played.
 */

STDMETHODIMP CBeeper::Beep(long *plSoundPlayed)
    {
    if (NULL==plSoundPlayed)
        return ResultFromScode(E_POINTER);

    *plSoundPlayed=m_lSound;
    MessageBeep((UINT)m_lSound);
    return NOERROR;
    }

/*
 * CBeeper::Exception
 *
 * Purpose:
 *  Raises an exception for Invoke from within ITypeInfo::Invoke
 *  using the CreateErrorInfo API and the ICreateErrorInfo interface.
 *  This is part of CBeeper since CBeeper derives from IDispatch
 *  as well.
 *
 * Parameters:
 *  wException      WORD exception code.
 */

void CBeeper::Exception(WORD wException)
    {
    HRESULT             hr;
    ICreateErrorInfo   *pICreateErr;
    BOOL                fSuccess;
    LPTSTR              psz;
    LPOLESTR            pszHelp;
    UINT                idsSource;
    UINT                idsException;
    DWORD               dwHelpID;
    LANGID              langID=LANG_NEUTRAL;
   #ifdef WIN32
    LANGID             *pLangID;
   #endif

   #ifdef WIN32
    pLangID=(LANGID *)TlsGetValue(g_dwTLS);

    if (NULL!=pLangID)
        langID=*pLangID;
   #endif

    /*
     * Thread-safe exception handling means that we call
     * CreateErrorInfo which gives us an ICreateErrorInfo pointer
     * that we then use to set the error information (basically
     * to set the fields of an EXCEPINFO structure.  We then
     * call SetErrorInfo to attach this error to the current
     * thread.  ITypeInfo::Invoke will look for this when it
     * returns from whatever function was invokes by calling
     * GetErrorInfo.
     */

    //Not much we can do if this fails.
    if (FAILED(CreateErrorInfo(&pICreateErr)))
        return;

    psz=(LPTSTR)malloc(1024*sizeof(TCHAR));

    if (NULL==psz)
        {
        pICreateErr->Release();
        return;
        }

    fSuccess=FALSE;

    switch (wException)
        {
        case EXCEPTION_INVALIDSOUND:
            pICreateErr->SetGUID(IID_IBeeper);

            dwHelpID=HID_SOUND_PROPERTY_LIMITATIONS;

            pszHelp=OLETEXT("beep0000.hlp");
            idsSource=IDS_0_EXCEPTIONSOURCE;
            idsException=IDS_0_EXCEPTIONINVALIDSOUND;

            switch (langID)
                {
                case LANG_GERMAN:
                    idsSource=IDS_7_EXCEPTIONSOURCE;
                    idsException=IDS_7_EXCEPTIONINVALIDSOUND;
                    pszHelp=OLETEXT("beep0007.hlp");
                    break;

                case LANG_ENGLISH:
                case LANG_NEUTRAL:
                default:
                    break;
                }

            fSuccess=TRUE;
            break;

        default:
            break;
        }

    if (fSuccess)
        {
        IErrorInfo *pIErr;

        /*
         * If you have a help file, call the functions
         * ICreateErrorInfo::SetHelpFile and
         * ICreateErrorInfo::SetHelpContext as well.  If you
         * set the help file to NULL the context is ignored.
         */
        pICreateErr->SetHelpFile(pszHelp);
        pICreateErr->SetHelpContext(dwHelpID);

       #ifdef WIN32ANSI
        OLECHAR     szTemp[256];

        LoadString(g_hInst, idsSource, psz, 256);
        MultiByteToWideChar(CP_ACP, 0, psz, -1, szTemp, 256);
        pICreateErr->SetSource(szTemp);

        LoadString(g_hInst, idsException, psz, 256);
        MultiByteToWideChar(CP_ACP, 0, psz, -1, szTemp, 256);
        pICreateErr->SetDescription(szTemp);
       #else
        LoadString(g_hInst, idsSource, psz, 1024);
        pICreateErr->SetSource(psz);

        LoadString(g_hInst, idsException, psz, 1024);
        pICreateErr->SetDescription(psz);
       #endif

        hr=pICreateErr->QueryInterface(IID_IErrorInfo
            , (PPVOID)&pIErr);

        if (SUCCEEDED(hr))
            {
            SetErrorInfo(0L, pIErr);
            pIErr->Release();
            }
        }

    free(psz);

    //SetErrorInfo holds the object's IErrorInfo
    pICreateErr->Release();
    return;
    }

//ISupportErrorInfo interface implementation

/*
 * CImpISupportErrorInfo::CImpISupportErrorInfo
 * CImpISupportErrorInfo::~CImpISupportErrorInfo
 *
 * Parameters (Constructor):
 *  pObj            PCBeeper of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpISupportErrorInfo::CImpISupportErrorInfo(PCBeeper pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpISupportErrorInfo::~CImpISupportErrorInfo(void)
    {
    return;
    }

/*
 * CImpISupportErrorInfo::QueryInterface
 * CImpISupportErrorInfo::AddRef
 * CImpISupportErrorInfo::Release
 *
 * Purpose:
 *  IUnknown members for CImpISupportErrorInfo object.
 */

STDMETHODIMP CImpISupportErrorInfo::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpISupportErrorInfo::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpISupportErrorInfo::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpISupportErrorInfo::InterfaceSupportsErrorInfo
 *
 * Purpose:
 *  Informs a caller whether or not a specific interface
 *  supports exceptions through the Set/GetErrorInfo mechanism.
 *
 * Parameters:
 *  riid            REFIID of the interface in question.
 *
 * Return Value:
 *  HRESULT         NOERROR if a call to GetErrorInfo will succeed
 *                  for callers of riid.  S_FALSE if not.
 */

STDMETHODIMP CImpISupportErrorInfo::InterfaceSupportsErrorInfo
    (REFIID riid)
    {
    if (IID_IBeeper==riid)
        return NOERROR;

    return ResultFromScode(S_FALSE);
    }


BEEPER.H

/*
 * BEEPER.H
 * Beeper Automation Object #4 Chapter 14
 *
 * Classes that implement the Beeper object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _BEEPER_H_
#define _BEEPER_H_

/*
 * This #define tells <bookguid.h> to not define GUIDs that will
 * be defined in MKTYPLIB-generated header files, like ibeeper.h.
 */
#define GUIDS_FROM_TYPELIB
#define INC_AUTOMATION
#define CHAPTER14
#include <inole.h>
#include <malloc.h>

//This file is generated from MKTYPLIB
#include "ibeeper.h"

//This one isn't defined in ibeeper.h
DEFINE_GUID(DIID_DIBeeper,0x0002115D,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

//Help context ID for exceptions
#define HID_SOUND_PROPERTY_LIMITATIONS  0x1E100

//English exception strings
#define IDS_0_MIN                   16
#define IDS_0_EXCEPTIONSOURCE       (IDS_0_MIN)
#define IDS_0_EXCEPTIONINVALIDSOUND (IDS_0_MIN+1)

//German exception strings
#define IDS_7_MIN                   32
#define IDS_7_EXCEPTIONSOURCE       (IDS_7_MIN)
#define IDS_7_EXCEPTIONINVALIDSOUND (IDS_7_MIN+1)

//Forward class declarations for friend statements
class CImpISupportErrorInfo;
typedef CImpISupportErrorInfo *PCImpISupportErrorInfo;

class CBeeper : public IBeeper
    {
    protected:
        ULONG           m_cRef;             //Object reference count
        LPUNKNOWN       m_pUnkOuter;        //Controlling unknown
        PFNDESTROYED    m_pfnDestroy;       //To call on closure

        long            m_lSound;

        //Our interfaces
        PCImpISupportErrorInfo m_pImpISuppErr;

        ITypeInfo      *m_pITINeutral;      //Type information
        ITypeInfo      *m_pITIGerman;

    public:
        CBeeper(LPUNKNOWN, PFNDESTROYED);
        ~CBeeper(void);

        BOOL         Init(void);
        void         Exception(WORD);

        //Non-delegating object IUnknown
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IDispatch members
        STDMETHODIMP GetTypeInfoCount(UINT *);
        STDMETHODIMP GetTypeInfo(UINT, LCID, ITypeInfo **);
        STDMETHODIMP GetIDsOfNames(REFIID, OLECHAR **, UINT, LCID
            , DISPID *);
        STDMETHODIMP Invoke(DISPID, REFIID, LCID, WORD
            , DISPPARAMS *, VARIANT *, EXCEPINFO *, UINT *);

        //IBeeper functions
        STDMETHODIMP get_Sound(long *);
        STDMETHODIMP put_Sound(long);
        STDMETHODIMP Beep(long *);
    };

typedef CBeeper *PCBeeper;

//DISPIDs for our dispinterface
enum
    {
    PROPERTY_SOUND=0,
    METHOD_BEEP
    };

//Exceptions we can throw from IDispatch::Invoke
enum
    {
    EXCEPTION_NONE=0,
    EXCEPTION_INVALIDSOUND=1000
    };

/*
 * ISupportErrorInfo interface implementations for the Beeper.
 */

class CImpISupportErrorInfo : public ISupportErrorInfo
    {
    public:
        ULONG           m_cRef;     //For debugging

    private:
        PCBeeper        m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpISupportErrorInfo(PCBeeper, LPUNKNOWN);
        ~CImpISupportErrorInfo(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //ISupportErrorInfo members
        STDMETHODIMP InterfaceSupportsErrorInfo(REFIID);
    };

#endif //_BEEPER_H_


DBEEPER.H

/*
 * DBEEPER.H
 * Beeper Automation Object Chapter 13
 *
 * Definitions, classes, and prototypes for the server of Beeper
 * objects.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _DBEEPER_H_
#define _DBEEPER_H_

//Get the object definitions
#include "beeper.h"

//DBEEPER.CPP
void ObjectDestroyed(void);

//This class factory object creates other objects

class CBeeperFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;

    public:
        CBeeperFactory(void);
        ~CBeeperFactory(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP     CreateInstance(LPUNKNOWN, REFIID, PPVOID);
        STDMETHODIMP     LockServer(BOOL);
    };

typedef CBeeperFactory *PCBeeperFactory;

#endif //_DBEEPER_H_


IBEEPER.H

/* This header file machine-generated by mktyplib.exe */
/* Interface to type library: BeeperTypeLibrary */

#ifndef _BeeperTypeLibrary_H_
#define _BeeperTypeLibrary_H_

DEFINE_GUID(LIBID_BeeperTypeLibrary,0x0002115E,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

DEFINE_GUID(IID_IBeeper,0x0002115C,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

/* Definition of interface: IBeeper */
DECLARE_INTERFACE_(IBeeper, IDispatch)
{
#ifndef NO_BASEINTERFACE_FUNCS

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(THIS_ UINT FAR* pctinfo) PURE;

    STDMETHOD(GetTypeInfo)(
      THIS_
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo) PURE;

    STDMETHOD(GetIDsOfNames)(
      THIS_
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid) PURE;

    STDMETHOD(Invoke)(
      THIS_
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr) PURE;
#endif

    /* IBeeper methods */
    STDMETHOD(get_Sound)(THIS_ long FAR* plSound) PURE;
    STDMETHOD(put_Sound)(THIS_ long lSound) PURE;
    STDMETHOD(Beep)(THIS_ long FAR* plSoundPlayed) PURE;
};

DEFINE_GUID(CLSID_Beeper,0x0002115B,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

class Beeper;

#endif


BEEPER.CPP

/*
 * BEEPER.CPP
 * Simple Automation Object #5 Chapter 14
 *
 * Implementation of the CBeeper class demonstrating the use of
 * CreateStdDispatch instead of DispInvoke, DispGetIDOfNames, and
 * a custom IDispatch implementation.  In this case we don't have
 * our own IDispatch at all, which CreateStdDispatch provides for
 * us, but at the loss of multiple language support and the ability
 * to define exception.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "beeper.h"

/*
 * CBeeper::CBeeper
 * CBeeper::~CBeeper
 *
 * Parameters (Constructor):
 *  pUnkOuter       LPUNKNOWN of a controlling unknown.
 *  pfnDestroy      PFNDESTROYED to call when an object
 *                  is destroyed.
 */

CBeeper::CBeeper(LPUNKNOWN pUnkOuter, PFNDESTROYED pfnDestroy)
    {
    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pfnDestroy=pfnDestroy;

    m_lSound=0;
    m_pITINeutral=NULL;
    m_pIUnkStdDisp=NULL;
    return;
    }

CBeeper::~CBeeper(void)
    {
    ReleaseInterface(m_pIUnkStdDisp);
    ReleaseInterface(m_pITINeutral);
    return;
    }

/*
 * CBeeper::Init
 *
 * Purpose:
 *  Performs any intiailization of a CBeeper that's prone to failure
 *  that we also use internally before exposing the object outside.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CBeeper::Init(void)
    {
    LPUNKNOWN       pIUnknown=this;
    ITypeLib       *pITypeLib;
    HRESULT         hr;

    if (NULL!=m_pUnkOuter)
        pIUnknown=m_pUnkOuter;

    /*
     * Load our type library.  Since this sample uses
     * CreateStdDispatch we can only support one locale,
     * which is neutral (English) in this case.
     */

    if (FAILED(LoadRegTypeLib(LIBID_BeeperTypeLibrary, 1, 0
        , LANG_NEUTRAL, &pITypeLib)))
        {
        /*
         * If LoadRegTypeLib fails, try loading directly with
         * LoadTypeLib, which will register the library for us.
         *
         * NOTE:  You should prepend your DIR registry key to the
         * .TLB name so you don't depend on it being it the PATH.
         * This sample will be updated later to reflect this.
         */
        if (FAILED(LoadTypeLib(OLETEXT("BEEP0000.TLB"), &pITypeLib)))
            return FALSE;
        }

    //Get the type information out of the type library.
    hr=pITypeLib->GetTypeInfoOfGuid(DIID_DIBeeper, &m_pITINeutral);

    if (FAILED(hr))
        {
        pITypeLib->Release();
        return FALSE;
        }

    m_pImpIProvideCI=new CImpIProvideClassInfo(this, pITypeLib);
    pITypeLib->Release();

    if (FAILED(hr))
        return FALSE;

    /*
     * CreateStdDispatch always returns an IUnknown pointer
     * because such is required in aggregation, which is
     * involved here.  In other words, the Beeper is aggregating
     * on the OLE-provided "StdDispatch" object in order to
     * directly expose IDispatch from that object.  See the
     * implementation of QueryInterface.
     */
    hr=CreateStdDispatch(pIUnknown, (IBeeper *)this, m_pITINeutral
        , &m_pIUnkStdDisp);

    if (FAILED(hr))
        return FALSE;

    return TRUE;
    }

/*
 * CBeeper::QueryInterface
 * CBeeper::AddRef
 * CBeeper::Release
 *
 * Purpose:
 *  IUnknown members for CBeeper object.
 */

STDMETHODIMP CBeeper::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    /*
     * The only calls for IUnknown are either in a nonaggregated
     * case or when created in an aggregation, so in either case
     * always return our IUnknown for IID_IUnknown.
     */
    if (IID_IUnknown==riid || IID_IBeeper==riid)
        *ppv=this;

    /*
     * We specifically have to ask the object returned from
     * CreateStdDispatch for our dispinterface.  Note that
     * it only understands IID_IDispatch and not DIID_DIBeeper,
     * so we convert requests for DIID_DIBeeper into IID_IDispatch
     * which amounts to exactly the same thing.
     */
    if (IID_IDispatch==riid || DIID_DIBeeper==riid)
        return m_pIUnkStdDisp->QueryInterface(IID_IDispatch, ppv);

    //AddRef any interface we'll return.
    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CBeeper::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CBeeper::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    //Inform the server about destruction so it can handle shutdown
    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0L;
    }

//IBeeper interface functions

/*
 * CBeeper::get_Sound
 * CBeeper::put_Sound
 *
 * Purpose:
 *  Functions called from DispInvoke to handle the Sound property.
 *
 * Parameters (Set only):
 *  lSound          long, new sound to save after validation.
 *
 * Return Value: (Get only):
 *  ULONG           Current sound.
 */

STDMETHODIMP_(long) CBeeper::get_Sound(void)
    {
    return m_lSound;
    }

STDMETHODIMP_(void) CBeeper::put_Sound(long lSound)
    {
    if (MB_OK!=lSound && MB_ICONEXCLAMATION!=lSound
        && MB_ICONQUESTION!=lSound && MB_ICONHAND!=lSound
        && MB_ICONASTERISK!=lSound)
        {
        /*
         * We cannot return error information to the StdDispatch
         * given the way we've defined the return value of this
         * methods, so we can't raise an exception.
         */
        return;
        }

    m_lSound=lSound;
    return;
    }

/*
 * CBeeper::Beep
 *
 * Purpose:
 *  Function called from DispInvoke to invoke the Beep method.
 *
 * Return Value:
 *  long            The sound played.
 */

STDMETHODIMP_(long) CBeeper::Beep(void)
    {
    MessageBeep((UINT)m_lSound);
    return m_lSound;
    }

/*
 * Since CreateStdDispatch provides an IDispatch implementation
 * for us, there's no specific code for that interface needed here.
 *
 * So to not get rid of too much code, we'll add an
 * IProvideClassInfo
 */

/*
 * CImpIProvideClassInfo::CImpIProvideClassInfo
 * CImpIProvideClassInfo::~CImpIProvideClassInfo
 *
 * Parameters (Constructor):
 *  pUnkOuter       LPUNKNOWN of a controlling unknown.
 *  pITypeLib       ITypeLib * to return from this interface.
 */

CImpIProvideClassInfo::CImpIProvideClassInfo(LPUNKNOWN pUnkOuter
    , ITypeLib *pITypeLib)
    {
    m_cRef=0;
    m_pUnkOuter=pUnkOuter;

    /*
     * Get the coclass type info.  If this fails then we'll
     * just fail GetClassInfo below.
     */
    m_pITI=NULL;
    pITypeLib->GetTypeInfoOfGuid(CLSID_Beeper, &m_pITI);
    return;
    }

CImpIProvideClassInfo::~CImpIProvideClassInfo(void)
    {
    ReleaseInterface(m_pITI);
    return;
    }

/*
 * CImpIProvideClassInfo::QueryInterface
 * CImpIProvideClassInfo::AddRef
 * CImpIProvideClassInfo::Release
 *
 * Purpose:
 *  IUnknown members for CImpIProvideClassInfo object.
 */

STDMETHODIMP CImpIProvideClassInfo::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIProvideClassInfo::AddRef(void)
    {
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIProvideClassInfo::Release(void)
    {
    return m_pUnkOuter->Release();
    }

/*
 * CImpIProvideClassInfo::GetClassInfo
 *
 * Purpose:
 *  Returns the type information for this coclass.
 *
 * Parameters:
 *  ppITI           ITypeInfo ** in which to store the ITypeInfo
 *                  for this coclass.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, error otherwise.
 */

STDMETHODIMP CImpIProvideClassInfo::GetClassInfo(ITypeInfo **ppITI)
    {
    if (NULL==ppITI)
        return ResultFromScode(E_POINTER);

    *ppITI=m_pITI;

    if (NULL!=m_pITI)
        {
        m_pITI->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_FAIL);
    }


BEEPER.H

/*
 * BEEPER.H
 * Beeper Automation Object #5 Chapter 14
 *
 * Classes that implement the Beeper object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _BEEPER_H_
#define _BEEPER_H_

/*
 * This #define tells <bookguid.h> to not define GUIDs that will
 * be defined in MKTYPLIB-generated header files, like ibeeper.h.
 */
#define GUIDS_FROM_TYPELIB
#define INC_AUTOMATION
#define INC_CONTROLS
#define CHAPTER14
#include <inole.h>

//This file is generated from MKTYPLIB
#include "ibeeper.h"

class CImpIProvideClassInfo;
typedef class CImpIProvideClassInfo *PCImpIProvideClassInfo;

class CBeeper : public IBeeper
    {
    protected:
        ULONG           m_cRef;         //Object reference count
        LPUNKNOWN       m_pUnkOuter;    //Controlling unknown
        PFNDESTROYED    m_pfnDestroy;   //To call on closure

        long            m_lSound;       //Type of sound

        ITypeInfo      *m_pITINeutral;  //Type information
        IUnknown       *m_pIUnkStdDisp; //StdDispatch object

        PCImpIProvideClassInfo  m_pImpIProvideCI;

    public:
        CBeeper(LPUNKNOWN, PFNDESTROYED);
        ~CBeeper(void);

        BOOL         Init(void);

        //Non-delegating object IUnknown
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IBeeper functions
        STDMETHODIMP_(long)  get_Sound(void);
        STDMETHODIMP_(void)  put_Sound(long);
        STDMETHODIMP_(long)  Beep(void);
    };

typedef CBeeper *PCBeeper;

class CImpIProvideClassInfo : public IProvideClassInfo
    {
    public:
        ULONG           m_cRef;     //For debugging

    private:
        LPUNKNOWN       m_pUnkOuter;
        ITypeInfo      *m_pITI;     //To return from GetClassInfo

    public:
        CImpIProvideClassInfo(LPUNKNOWN, ITypeLib *);
        ~CImpIProvideClassInfo(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IProvideClassInfo members
        STDMETHODIMP GetClassInfo(ITypeInfo **);

    };

#endif //_BEEPER_H_


DBEEPER.CPP

/*
 * DBEEPER.CPP
 * Beeper Automation Object Chapter 14
 *
 * Server module code for the Beeper object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "dbeeper.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

/*
 * LibMain(32)
 *
 * Purpose:
 *  Entry point conditionally compiled for Win32 and Windows
 *  3.1.  Provides the proper structure for each environment.
 */

#ifdef WIN32
BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    if (DLL_PROCESS_DETACH==ulReason)
        {
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    return TRUE;
    }
#else
int PASCAL LibMain(HINSTANCE hInstance, WORD wDataSeg
    , WORD cbHeapSize, LPSTR lpCmdLine)
    {
    if (0!=cbHeapSize)
        UnlockData(0);

    return (int)hInstance;
    }
#endif

/*
 * DllGetClassObject
 * DllCanUnloadNow
 * Standard COM exports for DLL servers.
 */

HRESULT APIENTRY DllGetClassObject(REFCLSID rclsid, REFIID riid
    , PPVOID ppv)
    {
    CBeeperFactory *pBF;
    HRESULT         hr;

    if (CLSID_Beeper!=rclsid)
        return ResultFromScode(E_FAIL);

    //Check that we can provide the interface
    if (IID_IUnknown!=riid && IID_IClassFactory!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Return our beeper factory's IClassFactory
    pBF=new CBeeperFactory();

    if (NULL==pBF)
        return ResultFromScode(E_OUTOFMEMORY);

    //If the factory hasn't the interface, delete it
    hr=pBF->QueryInterface(riid, ppv);

    if (FAILED(hr))
        delete pBF;

    return hr;
    }

STDAPI DllCanUnloadNow(void)
    {
    SCODE   sc;

    //Our answer is whether there are any object or locks
    sc=(0L==g_cObj && 0L==g_cLock) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the object to call when it gets destroyed.  Since
 *  we're in a DLL we only track the number of objects here, letting
 *  DllCanUnloadNow take care of the rest.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;
    return;
    }

/*
 * CBeeperFactory::CBeeperFactory
 * CBeeperFactory::~CBeeperFactory
 */

CBeeperFactory::CBeeperFactory(void)
    {
    m_cRef=0L;
    return;
    }

CBeeperFactory::~CBeeperFactory(void)
    {
    return;
    }

/*
 * CBeeperFactory::QueryInterface
 * CBeeperFactory::AddRef
 * CBeeperFactory::Release
 */

STDMETHODIMP CBeeperFactory::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CBeeperFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CBeeperFactory::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    ObjectDestroyed();
    return 0;
    }

/*
 * CBeeperFactory::CreateInstance
 * CBeeperFactory::LockServer
 */

STDMETHODIMP CBeeperFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCBeeper            pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //Verify that a controlling unknown asks for IUnknown
    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    //Create the object passing function to notify on destruction.
    pObj=new CBeeper(pUnkOuter, ObjectDestroyed);

    if (NULL==pObj)
        return hr;

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    //Kill the object if initial creation or Init failed.
    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

STDMETHODIMP CBeeperFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        g_cLock--;

    return NOERROR;
    }


TESTBEEP.FRM

VERSION 2.00
Begin Form Form1
   Caption         =   "Beeper Test"
   ClientHeight    =   1725
   ClientLeft      =   1275
   ClientTop       =   1860
   ClientWidth     =   3375
   Height          =   2100
   Left            =   1230
   LinkTopic       =   "Form1"
   ScaleHeight     =   1725
   ScaleWidth      =   3375
   Top             =   1530
   Width           =   3465
   Begin CommandButton GermanNames
      Caption         =   "&German Names"
      Height          =   615
      Left            =   1800
      TabIndex        =   3
      Top             =   960
      Width           =   1455
   End
   Begin CommandButton BogusName
      Caption         =   "Bogus &Name"
      Height          =   615
      Left            =   1800
      TabIndex        =   2
      Top             =   120
      Width           =   1455
   End
   Begin CommandButton Exception
      Caption         =   "E&xception"
      Height          =   615
      Left            =   120
      TabIndex        =   1
      Top             =   960
      Width           =   1455
   End
   Begin CommandButton TestBeeps
      Caption         =   "Test &Beeps"
      Height          =   615
      Left            =   120
      TabIndex        =   0
      Top             =   120
      Width           =   1455
   End
End

Sub BogusName_Click ()
    Dim Beeper As Object
    Set Beeper = CreateObject("Beeper.Object")

    Beeper.Foo = 5  'Invalid property

    Set Beeper = Nothing
End Sub

Sub Exception_Click ()
    Dim Beeper As Object
    Set Beeper = CreateObject("Beeper.Object")

    Beeper.Sound = 555  'Generate an exception

    Set Beeper = Nothing
End Sub

Sub GermanNames_Click ()
    Dim PlayedSound As Integer
    Dim Pieper As Object
    Set Pieper = CreateObject("Beeper.Object")

    Pieper.Ton = 0
    Pieper.Piep

    Pieper.Ton = 16   'MB_ICONHAND
    Ton = Pieper.Piep

    Pieper.Ton = 32   'MB_ICONEXCLAMATION
    Ton = Pieper.Piep

    Pieper.Ton = 48   'MB_ICONQUESTION
    Ton = Pieper.Piep

    Pieper.Ton = 64   'MB_ICONASTERISK
    Ton = Pieper.Piep

    Set Pieper = Nothing
End Sub

Sub TestBeeps_Click ()
    Dim PlayedSound As Integer
    Dim Beeper As Object
    Set Beeper = CreateObject("Beeper.Object")

    Beeper.Sound = 0
    Beeper.Beep

    Beeper.Sound = 16   'MB_ICONHAND
    Sound = Beeper.Beep

    Beeper.Sound = 32   'MB_ICONEXCLAMATION
    Sound = Beeper.Beep

    Beeper.Sound = 48   'MB_ICONQUESTION
    Sound = Beeper.Beep

    Beeper.Sound = 64   'MB_ICONASTERISK
    Sound = Beeper.Beep

    Set Beeper = Nothing
End Sub


TESTBEEP.FRM

VERSION 4.00
Begin VB.Form Form1
   Caption         =   "Beeper Test"
   ClientHeight    =   1725
   ClientLeft      =   1275
   ClientTop       =   1860
   ClientWidth     =   3375
   BeginProperty Font
      name            =   "MS Sans Serif"
      charset         =   1
      weight          =   700
      size            =   8.25
      underline       =   0   'False
      italic          =   0   'False
      strikethrough   =   0   'False
   EndProperty
   Height          =   2100
   Left            =   1230
   LinkTopic       =   "Form1"
   ScaleHeight     =   1725
   ScaleWidth      =   3375
   Top             =   1530
   Width           =   3465
   Begin VB.CommandButton GermanNames
      Caption         =   "&German Names"
      Height          =   615
      Left            =   1800
      TabIndex        =   3
      Top             =   960
      Width           =   1455
   End
   Begin VB.CommandButton BogusName
      Caption         =   "Bogus &Name"
      Height          =   615
      Left            =   1800
      TabIndex        =   2
      Top             =   120
      Width           =   1455
   End
   Begin VB.CommandButton Exception
      Caption         =   "E&xception"
      Height          =   615
      Left            =   120
      TabIndex        =   1
      Top             =   960
      Width           =   1455
   End
   Begin VB.CommandButton TestBeeps
      Caption         =   "Test &Beeps"
      Height          =   615
      Left            =   120
      TabIndex        =   0
      Top             =   120
      Width           =   1455
   End
End
Attribute VB_Name = "Form1"
Attribute VB_Creatable = False
Attribute VB_Exposed = False

Private Sub BogusName_Click()
    Dim Beeper As Object
    Set Beeper = CreateObject("Beeper.Object")

    Beeper.Foo = 5  'Invalid property

    Set Beeper = Nothing
End Sub

Private Sub Exception_Click()
    Dim Beeper As Object
    Set Beeper = CreateObject("Beeper.Object")

    Beeper.Sound = 555  'Generate an exception

    Set Beeper = Nothing
End Sub

Private Sub GermanNames_Click()
    Dim PlayedSound As Integer
    Dim Pieper As Object
    Set Pieper = CreateObject("Beeper.Object")

    Pieper.Ton = 0
    Pieper.Piep

    Pieper.Ton = 16   'MB_ICONHAND
    Ton = Pieper.Piep

    Pieper.Ton = 32   'MB_ICONEXCLAMATION
    Ton = Pieper.Piep

    Pieper.Ton = 48   'MB_ICONQUESTION
    Ton = Pieper.Piep

    Pieper.Ton = 64   'MB_ICONASTERISK
    Ton = Pieper.Piep

    Set Pieper = Nothing
End Sub

Private Sub TestBeeps_Click()
    Dim PlayedSound As Integer
    Dim Beeper As Object
    Set Beeper = CreateObject("Beeper.Object")

    Beeper.Sound = 0
    Beeper.Beep

    Beeper.Sound = 16   'MB_ICONHAND
    Sound = Beeper.Beep

    Beeper.Sound = 32   'MB_ICONEXCLAMATION
    Sound = Beeper.Beep

    Beeper.Sound = 48   'MB_ICONQUESTION
    Sound = Beeper.Beep

    Beeper.Sound = 64   'MB_ICONASTERISK
    Sound = Beeper.Beep

    Set Beeper = Nothing
End Sub


AUTOAPP.CPP

/*
 * AUTOAPP.CPP
 * Cosmo Chapter 14
 *
 * "Application" object for Cosmo's OLE Automation support, based
 * on CAutoBase which handles the type information and IDispatch
 * part of the object.  What is here are the specific properties
 * and methods for the "Application" object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved.
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  INTERNET>kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CAutoApp::CAutoApp
 *
 * Constructor Parameters:
 *  pFR             PCCosmoFrame to the frame object that we use
 *                  to implement much of this interface.
 */

CAutoApp::CAutoApp(PCCosmoFrame pFR)
    : CAutoBase(pFR, pFR->m_hInst, IID_ICosmoApplication
    , DIID_DICosmoApplication, ObjectDestroyed)
    {
    m_fQuitCalled=FALSE;
    return;
    }

/*
 * CAutoApp::QueryInterface
 * CAutoApp::AddRef
 * CAutoApp::Release
 */

STDMETHODIMP CAutoApp::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_ICosmoApplication==riid)
        *ppv=(IUnknown *)this;

    if (IID_IDispatch==riid || m_diid==riid)
        *ppv=m_pImpIDispatch;

    if (IID_IExternalConnection==riid)
        *ppv=m_pImpIExtConn;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CAutoApp::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CAutoApp::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    if (!m_fQuitCalled && NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0L;
    }

/*
 * CAutoApp::VTableInterface
 *
 * Purpose:
 *  Returns the right vtable pointer to use when calling
 *  ITypeInfo::Invoke (see CImpIDispatch::Invoke in AUTOBASE.CPP).
 */

void *CAutoApp::VTableInterface(void)
    {
    return (ICosmoApplication *)this;
    }

//All that follows is the ICosmoApplication implementation

/*
 * CAutoApp::Application
 * Property, read-only
 *
 * Returns our own IDispatch for use with DISPID_VALUE.
 */

STDMETHODIMP_(IDispatch *) CAutoApp::get_Application(void)
    {
    void       *pv;

    //This will set pv to NULL on failure
    QueryInterface(IID_IDispatch, &pv);
    return (IDispatch *)pv;
    }

/*
 * CAutoApp::ActiveFigure
 * Property, read-only
 *
 * Returns the active document's IDispatch
 */

STDMETHODIMP_(IDispatch *) CAutoApp::get_ActiveFigure(void)
    {
    void           *pv=NULL;
    PCCosmoDoc      pDoc;
    PCAutoFigure    pFig;

    pDoc=(PCCosmoDoc)((PCCosmoClient)m_pFR->m_pCL)->ActiveDocument();

    if (NULL==pDoc)
        return NULL;

    pFig=pDoc->AutoFigure();

    if (NULL!=pFig)
        pFig->QueryInterface(IID_IDispatch, &pv);

    return (IDispatch *)pv;
    }

/*
 * CAutoApp::Caption
 * Property, read-only
 *
 * Retrieve the application's caption bar
 */

STDMETHODIMP_(BSTR) CAutoApp::get_Caption(void)
    {
    const int   cch=256;
    TCHAR       szCaption[cch];

    GetWindowText(m_pFR->Window(), szCaption, cch);

   #ifdef WIN32ANSI
    OLECHAR     szTemp[cch];

    MultiByteToWideChar(CP_ACP, 0, szCaption, -1, szTemp, cch);
    return SysAllocString(szTemp);
   #else
    return SysAllocString(szCaption);
   #endif
    }

/*
 * CAutoApp::Figures
 * Property, read-only
 *
 * Retrieve the collection object for figures (documents)
 */

STDMETHODIMP_(IDispatch *) CAutoApp::get_Figures(void)
    {
    void            *pv=NULL;
    PCAutoFigures    pFigs;

    pFigs=((PCCosmoClient)m_pFR->m_pCL)->AutoFigures();

    if (NULL!=pFigs)
        pFigs->QueryInterface(IID_IDispatch, &pv);

    return (IDispatch *)pv;
    }

/*
 * CAutoApp::FullName, Name, Path
 * Properties, read-only
 *
 * Retrieve the full pathname of the application module (FullName),
 * just the module name (Name), or just the path (Path).
 */

STDMETHODIMP_(BSTR) CAutoApp::get_FullName(void)
    {
    const int   cch=512;
    TCHAR       szModule[cch];

    GetModuleFileName(m_pFR->Instance(), szModule, cch);

   #ifdef WIN32ANSI
    OLECHAR     szTemp[cch];
    MultiByteToWideChar(CP_ACP, 0, szModule, -1, szTemp, cch);
    return SysAllocString(szTemp);
   #else
    return SysAllocString(szModule);
   #endif
    }

STDMETHODIMP_(BSTR) CAutoApp::get_Name(void)
    {
    BSTR        bstrFull=NULL;
    BSTR        bstrName=NULL;
    const int   cch=256;
    TCHAR       szName[256];

    //Get the full path
    bstrFull=get_FullName();

    if (NULL==bstrFull)
        return NULL;

    //Now retrieve just the filename
   #ifdef WIN32ANSI
    char        szTemp[cch];
    OLECHAR     szTempW[cch];

    WideCharToMultiByte(CP_ACP, 0, bstrFull, -1, szTemp
        , cch, NULL, NULL);
    if (0==GetFileTitle(szTemp, szName, cch))
        {
        MultiByteToWideChar(CP_ACP, 0, szName, -1, szTempW, cch);
        bstrName=SysAllocString(szTempW);
        }
   #else
    if (0==GetFileTitle(bstrFull, szName, cch))
        bstrName=SysAllocString(szName);
   #endif

    SysFreeString(bstrFull);
    return bstrName;
    }

STDMETHODIMP_(BSTR) CAutoApp::get_Path(void)
    {
    BSTR        bstrFull=NULL;
    BSTR        bstrName=NULL;
    BSTR        bstrPath=NULL;

    bstrFull=get_FullName();

    if (NULL==bstrFull)
        return NULL;

    bstrName=get_Name();

    if (NULL!=bstrName)
        {
        LPOLESTR    psz;

        /*
         * Find the position of bstrName in bstrFull then copy
         * only charaters up to that point into bstrPath.
         */
       #ifdef WIN32ANSI
        psz=wcsstr(bstrFull, bstrName);
       #else
        psz=_tcsstr(bstrFull, bstrName);
       #endif

        //The -1 accounts for the \ before the filename
        bstrPath=SysAllocStringLen(bstrFull
            , ((DWORD)psz-(DWORD)bstrFull)/sizeof(OLECHAR)-1);
        SysFreeString(bstrName);
        }

    SysFreeString(bstrFull);
    return bstrPath;
    }

/*
 * CAutoApp::Left, Top, Width, Height
 * Properties, read-write
 *
 * Horizontal (Left) and vertical (Top) positions of the frame
 * window from the left and top edges of the screen; horizontal
 * (Width) and  vertical (Height) dimensions of the frame
 * window.  All of these functions call our private member
 * MoveSize, a helper function.
 */

STDMETHODIMP_(long) CAutoApp::get_Left(void)
    {
    return MoveSize(MOVESIZEACTION_GETLEFT, 0, 0, 0, 0);
    }

STDMETHODIMP_(void) CAutoApp::put_Left(long x)
    {
    MoveSize(MOVESIZEACTION_LEFT, x, 0, 0, 0);
    return;
    }

STDMETHODIMP_(long) CAutoApp::get_Top(void)
    {
    return MoveSize(MOVESIZEACTION_GETTOP, 0, 0, 0, 0);
    }

STDMETHODIMP_(void) CAutoApp::put_Top(long y)
    {
    MoveSize(MOVESIZEACTION_TOP, 0, y, 0, 0);
    return;
    }

STDMETHODIMP_(long) CAutoApp::get_Width(void)
    {
    return MoveSize(MOVESIZEACTION_GETWIDTH, 0, 0, 0, 0);
    }

STDMETHODIMP_(void) CAutoApp::put_Width(long cx)
    {
    MoveSize(MOVESIZEACTION_WIDTH, 0, 0, cx, 0);
    return;
    }

STDMETHODIMP_(long) CAutoApp::get_Height(void)
    {
    return MoveSize(MOVESIZEACTION_GETHEIGHT, 0, 0, 0, 0);
    }

STDMETHODIMP_(void) CAutoApp::put_Height(long cy)
    {
    MoveSize(MOVESIZEACTION_HEIGHT, 0, 0, 0, cy);
    return;
    }

/*
 * CAutoApp::Visible
 * Properties, read-write
 *
 * Controls visibility of the frame window (which is hidden by
 * default when the application is launched for Automation.
 */

STDMETHODIMP_(VARIANT_BOOL) CAutoApp::get_Visible(void)
    {
    return (VARIANT_BOOL)IsWindowVisible(m_pFR->Window());
    }

STDMETHODIMP_(void) CAutoApp::put_Visible(VARIANT_BOOL fShow)
    {
    ShowWindow(m_pFR->Window(), fShow ? SW_SHOW : SW_HIDE);
    return;
    }

/*
 * CAutoApp::StatusBar
 * Properties, read-write
 *
 * Retrieve or modify the status line text.
 * default when the application is launched for Automation.
 */

STDMETHODIMP_(BSTR) CAutoApp::get_StatusBar(void)
    {
    const int   cch=256;
    TCHAR       szText[cch];
    BSTR        bstrText=NULL;

   #ifdef WIN32ANSI
    if (0!=m_pFR->StatusLine()->MessageGet(szText, cch))
        {
        OLECHAR szTemp[cch];

        MultiByteToWideChar(CP_ACP, 0, szText, -1, szTemp, cch);
        bstrText=SysAllocString(szTemp);
        }
   #else
    if (0!=m_pFR->StatusLine()->MessageGet(szText, cch))
        bstrText=SysAllocString(szText);
   #endif

    return bstrText;
    }

STDMETHODIMP_(void) CAutoApp::put_StatusBar(BSTR bstrText)
    {
    if (NULL==bstrText)
        return;

   #ifdef WIN32ANSI
    char    szTemp[256];

    WideCharToMultiByte(CP_ACP, 0, bstrText, -1, szTemp, 256
        , NULL, NULL);
    m_pFR->StatusLine()->MessageSet(szTemp);
   #else
    m_pFR->StatusLine()->MessageSet(bstrText);
   #endif
    return;
    }

/*
 * CAutoApp::Quit
 * Method
 *
 * Instructs the application to terminate.  This behaves as
 * if the user closes the app directly.
 */

STDMETHODIMP_(void) CAutoApp::Quit(void)
    {
    ObjectDestroyed();
    m_fQuitCalled=TRUE;
    return;
    }

/*
 * CAutoApp::MoveSize
 *
 * Purpose:
 *  Helper function for the Left, Top, Width, and Height properties
 *  that centralizes calls to GetWindowRect and SetWindowPos,
 *  reducing overall code somewhat.
 *
 * Parameters:
 *  iAction         MOVESIZEACTION enum value to indicate what
 *                  type of moving/sizing to perform
 *  x, y            long position coordinates;  x is only meaningful
 *                  with MOVESIZEACTION_LEFT, y only with _TOP
 *  cx, cy          long extents;  cx is only meaningful
 *                  with MOVESIZEACTION_WIDTH, cy only with _HEIGHT
 *
 * Return Value:
 *  long            The current x, y, cx, or cy value depending on
 *                  iAction being _GETLEFT, _GETTOP, _GETWIDTH, or
 *                  _GETHEIGHT.
 */

long CAutoApp::MoveSize(MOVESIZEACTION iAction, long x, long y
    , long cx, long cy)
    {
    RECT        rc;
    long        x1, y1, cx1, cy1;
    UINT        uFlags;

    GetWindowRect(m_pFR->Window(), &rc);

    //By default we'll do nothing
    x1=rc.left;
    y1=rc.top;
    cx1=rc.right-rc.left;
    cy1=rc.bottom-rc.top;
    uFlags=0L;

    switch (iAction)
        {
        /*
         * Each individual property modifies the appropriate
         * variable x1, y1, cx1, cy1, as well as uFlags to set
         * up SetWindowPos call.
         */
        case MOVESIZEACTION_LEFT:
            x1=x;
            uFlags=SWP_NOSIZE;
            break;

        case MOVESIZEACTION_TOP:
            y1=y;
            uFlags=SWP_NOSIZE;
            break;

        case MOVESIZEACTION_WIDTH:
            cx1=cx;
            uFlags=SWP_NOMOVE;
            break;

        case MOVESIZEACTION_HEIGHT:
            cy1=cy;
            uFlags=SWP_NOMOVE;
            break;

        case MOVESIZEACTION_GETLEFT:
            return rc.left;

        case MOVESIZEACTION_GETTOP:
            return rc.top;

        case MOVESIZEACTION_GETWIDTH:
            return rc.right-rc.left;

        case MOVESIZEACTION_GETHEIGHT:
            return rc.bottom-rc.top;

        default:
            return 0;
        }

    //We only get here on propety changes
    SetWindowPos(m_pFR->Window(), NULL
        , (int)x1, (int)y1, (int)cx1, (int)cy1
        , SWP_NOZORDER | SWP_NOACTIVATE | uFlags);

    //Irrelevant for property changes.
    return 0;
    }


AUTOBASE.CPP

/*
 * AUTOBASE.CPP
 * Cosmo Chapter 14
 *
 * Base object for Cosmo's OLE Automation support.  This class
 * provides the implementation of IDispatch as well as the code
 * that loads the appropriate type information.  This leaves
 * each specific object to implement it's specific interface and
 * little else.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved.
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  INTERNET>kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CAutoBase::CAutoBase
 * CAutoBase::~CAutoBase
 *
 * Constructor Parameters:
 *  pv              void * to the frame or document object that
 *                  derived classes will use to access the functions
 *                  they need to implemenet their interface.
 *  hInst           HINSTANCE of the application (for loading
 *                  resources)
 *  riid            REFIID of the interface that this object
 *                  should expose.
 *  rdiid           REFIID of the dispinterface that this
 *                  object should expose.
 *  pfnDestroy      PFNDESTROYED to call when the object
 *                  is destroyed.
 */

CAutoBase::CAutoBase(void *pv, HINSTANCE hInst, REFIID riid
    , REFIID rdiid, PFNDESTROYED pfnDestroy)
    {
    m_cRef=0L;
    m_pfnDestroy=pfnDestroy;

    m_pObj=pv;
    m_hInst=hInst;

    m_iid=riid;
    m_diid=rdiid;

    m_pImpIDispatch=NULL;
    m_pImpIExtConn=NULL;
    return;
    }

CAutoBase::~CAutoBase(void)
    {
    DeleteInterfaceImp(m_pImpIExtConn);
    DeleteInterfaceImp(m_pImpIDispatch);
    return;
    }

/*
 * CAutoBase::Init
 *
 * Purpose:
 *  Performs any intiailization of CAutoBase that's prone to failure
 *  that we also use internally before exposing the object outside.
 *
 * Parameters:
 *  fExtConn        BOOL indicating if we care about external
 *                  connections for shutdown.
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CAutoBase::Init(BOOL fExtConn)
    {
    //The derived class has to supply the IUnknown implementation
    m_pImpIDispatch=new CImpIDispatch(this
        , (IUnknown *)VTableInterface());

    if (NULL==m_pImpIDispatch)
        return FALSE;

    if (fExtConn)
        {
        m_pImpIExtConn=new CImpIExtConn
            ((IUnknown *)VTableInterface());

        if (NULL==m_pImpIExtConn)
            return FALSE;
        }

    return TRUE;
    }

//IDispatch interface implementation

/*
 * CImpIDispatch::CImpIDispatch
 * CImpIDispatch::~CImpIDispatch
 *
 * Parameters (Constructor):
 *  pObj            PCAutoBase to the containing object.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIDispatch::CImpIDispatch(PCAutoBase pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    m_pITypeInfo=NULL;
    return;
    }

CImpIDispatch::~CImpIDispatch(void)
    {
    ReleaseInterface(m_pITypeInfo);
    return;
    }

/*
 * CImpIDispatch::QueryInterface
 * CImpIDispatch::AddRef
 * CImpIDispatch::Release
 *
 * Purpose:
 *  IUnknown members for CImpIDispatch object.
 */

STDMETHODIMP CImpIDispatch::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIDispatch::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIDispatch::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIDispatch::GetTypeInfoCount
 * CImpIDispatch::GetTypeInfo
 */

STDMETHODIMP CImpIDispatch::GetTypeInfoCount(UINT *pctInfo)
    {
    //We implement GetTypeInfo so return 1
    *pctInfo=1;
    return NOERROR;
    }

STDMETHODIMP CImpIDispatch::GetTypeInfo(UINT itInfo, LCID lcid
    , ITypeInfo **ppITypeInfo)
    {
    HRESULT     hr;
    ITypeLib   *pITypeLib;
    ITypeInfo **ppITI=NULL;

    if (0!=itInfo)
        return ResultFromScode(TYPE_E_ELEMENTNOTFOUND);

    if (NULL==ppITypeInfo)
        return ResultFromScode(E_POINTER);

    *ppITypeInfo=NULL;

    /*
     * This switch statement is nicely extensible for other
     * languages and other m_pITypeInfo variables (such as it
     * was used in Beeper #2 (see ..\beeper2\beeper.cpp)
     */
    switch (PRIMARYLANGID(lcid))
        {
        case LANG_NEUTRAL:
        case LANG_ENGLISH:
            ppITI=&m_pITypeInfo;
            break;

        default:
            return ResultFromScode(DISP_E_UNKNOWNLCID);
        }

    //Load a type lib if we don't have the information already.
    if (NULL==*ppITI)
        {
        hr=LoadRegTypeLib(LIBID_CosmoTypeLibrary, 1, 0
            , PRIMARYLANGID(lcid), &pITypeLib);

        /*
         * If LoadRegTypeLib fails, try loading directly from
         * our module with LoadTypeLib.
         */
        if (FAILED(hr))
            {
            const int   cch=512;
            TCHAR       szModule[cch];

            GetModuleFileName(m_pObj->m_hInst, szModule, cch);

            switch (PRIMARYLANGID(lcid))
                {
                case LANG_NEUTRAL:
                case LANG_ENGLISH:
                   #ifdef WIN32ANSI
                    OLECHAR szTemp[512];
                    MultiByteToWideChar(CP_ACP, 0, szModule, -1, szTemp, 512);
                    hr=LoadTypeLib(szTemp, &pITypeLib);
                   #else
                    hr=LoadTypeLib(szModule, &pITypeLib);
                   #endif
                    break;

                /*
                 * Easily extensible with additional languages.
                 * Use LoadTypeLibFromResource or specify
                 * separate .TLB files instead of the module name.
                 * Be sure to use your DIR registry key to find
                 * the right path.
                 */
                }
            }

        if (FAILED(hr))
            return hr;

        //Got the type lib, get type info for the interface we want
        hr=pITypeLib->GetTypeInfoOfGuid(m_pObj->m_iid, ppITI);
        pITypeLib->Release();

        if (FAILED(hr))
            return hr;
        }

    //Ref on ITypeInfo holds type library in memory
    (*ppITI)->AddRef();
    *ppITypeInfo=*ppITI;
    return NOERROR;
    }

/*
 * CImpIDispatch::GetIDsOfNames
 * CImpIDispatch::Invoke
 */

STDMETHODIMP CImpIDispatch::GetIDsOfNames(REFIID riid
    , OLECHAR **rgszNames, UINT cNames, LCID lcid, DISPID *rgDispID)
    {
    HRESULT     hr;
    ITypeInfo  *pTI;

    if (IID_NULL!=riid)
        return ResultFromScode(DISP_E_UNKNOWNINTERFACE);

    hr=GetTypeInfo(0, lcid, &pTI);

    if (SUCCEEDED(hr))
        {
        hr=DispGetIDsOfNames(pTI, rgszNames, cNames, rgDispID);
        pTI->Release();
        }

    return hr;
    }

STDMETHODIMP CImpIDispatch::Invoke(DISPID dispID, REFIID riid
    , LCID lcid, unsigned short wFlags, DISPPARAMS *pDispParams
    , VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
    {
    HRESULT     hr;
    ITypeInfo  *pTI;

    if (IID_NULL!=riid)
        return ResultFromScode(DISP_E_UNKNOWNINTERFACE);

    //Get the ITypeInfo for lcid
    hr=GetTypeInfo(0, lcid, &pTI);

    if (FAILED(hr))
        return hr;

    //Note:  this sample doesn't do anything with exceptions.

    //The VTableInterface function gets us the right pointer
    hr=pTI->Invoke(m_pObj->VTableInterface(), dispID, wFlags
        , pDispParams, pVarResult, pExcepInfo, puArgErr);

    pTI->Release();
    return hr;
    }

//IExternalConnection implementation

/*
 * CImpIExtConn::CImpIExtConn
 * CImpIExtConn::~CImpIExtConn
 *
 * Parameters (Constructor):
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIExtConn::CImpIExtConn(LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_cStrong=0;
    return;
    }

CImpIExtConn::~CImpIExtConn(void)
    {
    return;
    }

/*
 * CImpIExtConn::QueryInterface
 * CImpIExtConn::AddRef
 * CImpIExtConn::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIExtConn.
 */

STDMETHODIMP CImpIExtConn::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIExtConn::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIExtConn::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIExtConn::AddConnection
 *
 * Purpose:
 *  Informs the object that a connection has been made to it.  We
 *  count only strong references.
 *
 * Parameters:
 *  dwConn          DWORD identifying the type of connection, taken
 *                  from the EXTCONN enumeration.
 *  dwReserved      DWORD reserved.  This is used inside OLE and
 *                  should not be validated.
 *
 * Return Value:
 *  DWORD           The number of connection counts on the
 *                  object, used for debugging purposes only.
 */

STDMETHODIMP_(DWORD) CImpIExtConn::AddConnection
    (DWORD dwConn, DWORD dwReserved)
    {
    if (EXTCONN_STRONG==dwConn)
        {
        m_cStrong++;
        return m_cStrong;
        }

    return 0L;
    }

/*
 * CImpIExtConn::ReleaseConnection
 *
 * Purpose:
 *  Informs an object that a connection has been taken away from
 *  it in which case the object may need to shut down.
 *
 * Parameters:
 *  dwConn              DWORD identifying the type of connection,
 *                      taken from the EXTCONN enumeration.
 *  dwReserved          DWORD reserved.  This is used inside OLE and
 *                      should not be validated.
 *  fLastReleaseCloses  BOOL indicating if the last call to this
 *                      function should close the object.
 *
 * Return Value:
 *  DWORD           The number of remaining connection counts on
 *                  the object, used for debugging purposes only.
 */

STDMETHODIMP_(DWORD) CImpIExtConn::ReleaseConnection
    (DWORD dwConn, DWORD dwReserved, BOOL fLastReleaseCloses)
    {
    DWORD       dw=0L;

    if (EXTCONN_STRONG==dwConn)
        {
        dw=--m_cStrong;

        /*
         * Note that in the way we're using this interface we
         * don't care about fLastReleaseCloses.
         */
        if (0L==m_cStrong)
            m_pUnkOuter->Release();     //Closes app or doc
        }

    return dw;
    }


AUTOFIG.CPP

/*
 * AUTOFIG.CPP
 * Cosmo Chapter 14
 *
 * "Figure" object for Cosmo's OLE Automation support, derived
 * from CAutoBase.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved.
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  INTERNET>kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CAutoFigure::CAutoFigure
 * CAutoFigure::~CAutoFigure
 *
 * Constructor Parameters:
 *  pDoc            PCCosmoDoc to the dpcument object that we
 *                  use to implement much of this interface.
 */

CAutoFigure::CAutoFigure(PCCosmoDoc pDoc)
    : CAutoBase(pDoc, pDoc->m_hInst, IID_ICosmoFigure
    , DIID_DICosmoFigure, ObjectDestroyed)
    {
    return;
    }

/*
 * CAutoFigure::QueryInterface
 * CAutoFigure::AddRef
 * CAutoFigure::Release
 */

STDMETHODIMP CAutoFigure::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_ICosmoFigure==riid)
        *ppv=(IUnknown *)this;

    if (IID_IDispatch==riid || m_diid==riid)
        *ppv=m_pImpIDispatch;

    if (IID_IExternalConnection==riid)
        *ppv=m_pImpIExtConn;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CAutoFigure::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CAutoFigure::Release(void)
    {
    /*
     * Since this object might have come from a class factory,
     * we count it's existence (see NewFigure below) for
     * controlling shutdown of the application when we call
     * ObjectDestroyed.  Otherwise we always close the document.
     */
    if (0L!=--m_cRef)
        return m_cRef;

    put_Visible(FALSE);
    SendMessage(m_pDoc->Window(), WM_CLOSE, 0, 0L);

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    return 0L;
    }

/*
 * CAutoFigure::VTableInterface
 *
 * Purpose:
 *  Returns the right vtable pointer to use when calling
 *  ITypeInfo::Invoke (see CImpIDispatch::Invoke in AUTOBASE.CPP).
 */
void *CAutoFigure::VTableInterface(void)
    {
    return (ICosmoFigure *)this;
    }

//All that follows is the ICosmoFigure implementation

/*
 * CAutoFigure::Application
 * CAutoFigure::Parent
 * Properties, read-only
 *
 * The application object (CAutoApp) in which we're contained,
 * which is stored in the frame object
 */

STDMETHODIMP_(IDispatch *) CAutoFigure::get_Application(void)
    {
    PCCosmoFrame    pFR;

    pFR=(PCCosmoFrame)m_pDoc->m_pFR;
    return pFR->AutoApp()->get_Application();
    }

STDMETHODIMP_(IDispatch *) CAutoFigure::get_Parent(void)
    {
    return get_Application();
    }

/*
 * CAutoFigure::FullName, Name, Path
 * Properties, read-only
 *
 * Retrieve the full pathname of the figure file (FullName),
 * just the file name (Name), or just the path (Path).
 *
 * Note that these functions are very similar to the same
 * ones in CAutoApp, and there is probably some code that could
 * be shared between them, but this sample won't bother with
 * such an optimization.
 */

STDMETHODIMP_(BSTR) CAutoFigure::get_FullName(void)
    {
    if ((TCHAR)0==m_pDoc->m_szFile[0])
        return NULL;

   #ifdef WIN32ANSI
    OLECHAR     szTemp[512];

    MultiByteToWideChar(CP_ACP, 0, m_pDoc->m_szFile, -1, szTemp, 512);
    return SysAllocString(szTemp);
   #else
    return SysAllocString(m_pDoc->m_szFile);
   #endif
    }

STDMETHODIMP_(BSTR) CAutoFigure::get_Name(void)
    {
    BSTR        bstrFull=NULL;
    BSTR        bstrName=NULL;
    const int   cch=256;
    TCHAR       szName[256];

    //Get the full path
    bstrFull=get_FullName();

    if (NULL==bstrFull)
        return NULL;

    //Now retrieve just the filename
   #ifdef WIN32ANSI
    char        szTemp[cch];
    OLECHAR     szTempW[cch];

    WideCharToMultiByte(CP_ACP, 0, bstrFull, -1, szTemp
        , cch, NULL, NULL);
    if (0==GetFileTitle(szTemp, szName, cch))
        {
        MultiByteToWideChar(CP_ACP, 0, szName, -1, szTempW, cch);
        bstrName=SysAllocString(szTempW);
        }
   #else
    if (0==GetFileTitle(bstrFull, szName, cch))
        bstrName=SysAllocString(szName);
   #endif

    SysFreeString(bstrFull);
    return bstrName;
    }

STDMETHODIMP_(BSTR) CAutoFigure::get_Path(void)
    {
    BSTR        bstrFull=NULL;
    BSTR        bstrName=NULL;
    BSTR        bstrPath=NULL;

    bstrFull=get_FullName();

    if (NULL==bstrFull)
        return NULL;

    bstrName=get_Name();

    if (NULL!=bstrName)
        {
        LPOLESTR    psz;

        /*
         * Find the position of bstrName in bstrFull then copy
         * only charaters up to that point into bstrPath.
         */
       #ifdef WIN32ANSI
        psz=wcsstr(bstrFull, bstrName);
       #else
        psz=_tcsstr(bstrFull, bstrName);
       #endif

        //The -1 accounts for the \ before the filename
        bstrPath=SysAllocStringLen(bstrFull
            , ((psz-bstrFull)/sizeof(TCHAR))-1);
        SysFreeString(bstrName);
        }

    SysFreeString(bstrFull);
    return bstrPath;
    }

/*
 * CAutoFigure::Saved
 * Property, read-only
 *
 * TRUE if the document is clean, FALSE otherwise.
 */

STDMETHODIMP_(VARIANT_BOOL) CAutoFigure::get_Saved(void)
    {
    return !m_pDoc->FDirtyGet();
    }

/*
 * CAutoFigure::NumberOfPoints
 * Property, read-only
 *
 * Number of points in the current figure.
 */

STDMETHODIMP_(short)CAutoFigure::get_NumberOfPoints(void)
    {
    POLYLINEDATA    pl;

    m_pDoc->m_pPL->DataGet(&pl, VERSIONCURRENT);
    return pl.cPoints;
    }

/*
 * CAutoFigure::BackColor
 * CAutoFigure::LineColor
 * Properties, read-write
 *
 * Colors used in the figure.
 */

STDMETHODIMP_(long) CAutoFigure::get_BackColor(void)
    {
    return m_pDoc->ColorGet(POLYLINECOLOR_BACKGROUND);
    }

STDMETHODIMP_(void) CAutoFigure::put_BackColor(long clrBack)
    {
    m_pDoc->ColorSet(POLYLINECOLOR_BACKGROUND, clrBack);
    return;
    }

STDMETHODIMP_(long) CAutoFigure::get_LineColor(void)
    {
    return m_pDoc->ColorGet(POLYLINECOLOR_LINE);
    }

STDMETHODIMP_(void) CAutoFigure::put_LineColor(long clrLine)
    {
    m_pDoc->ColorSet(POLYLINECOLOR_LINE, clrLine);
    return;
    }

/*
 * CAutoFigure::LineStyle
 * Property, read-write
 *
 * Line style used to draw the figure
 */

STDMETHODIMP_(short)CAutoFigure::get_LineStyle(void)
    {
    return m_pDoc->LineStyleGet();
    }

STDMETHODIMP_(void) CAutoFigure::put_LineStyle(short iStyle)
    {
    m_pDoc->LineStyleSet(iStyle);
    return;
    }

/*
 * CAutoFigure::Left, Top, Width, Height
 * Properties, read-write
 *
 * Horizontal (Left) and vertical (Top) positions of the frame
 * window from the left and top edges of the application client
 * area; horizontal (Width) and vertical (Height) dimensions of
 * document window.  All of these functions call our private member
 * MoveSize, a helper function.
 */

STDMETHODIMP_(long) CAutoFigure::get_Left(void)
    {
    return MoveSize(MOVESIZEACTION_GETLEFT, 0, 0, 0, 0);
    }

STDMETHODIMP_(void) CAutoFigure::put_Left(long x)
    {
    MoveSize(MOVESIZEACTION_LEFT, x, 0, 0, 0);
    return;
    }

STDMETHODIMP_(long) CAutoFigure::get_Top(void)
    {
    return MoveSize(MOVESIZEACTION_GETTOP, 0, 0, 0, 0);
    }

STDMETHODIMP_(void) CAutoFigure::put_Top(long y)
    {
    MoveSize(MOVESIZEACTION_TOP, 0, y, 0, 0);
    return;
    }

STDMETHODIMP_(long) CAutoFigure::get_Width(void)
    {
    return MoveSize(MOVESIZEACTION_GETWIDTH, 0, 0, 0, 0);
    }

STDMETHODIMP_(void) CAutoFigure::put_Width(long cx)
    {
    MoveSize(MOVESIZEACTION_WIDTH, 0, 0, cx, 0);
    return;
    }

STDMETHODIMP_(long) CAutoFigure::get_Height(void)
    {
    return MoveSize(MOVESIZEACTION_GETHEIGHT, 0, 0, 0, 0);
    }

STDMETHODIMP_(void) CAutoFigure::put_Height(long cy)
    {
    MoveSize(MOVESIZEACTION_HEIGHT, 0, 0, 0, cy);
    return;
    }

/*
 * CAutoFigure::Visible
 * Properties, read-write
 *
 * Controls visibility of the figure window (which is hidden by
 * default when created through automation).
 */

STDMETHODIMP_(VARIANT_BOOL)  CAutoFigure::get_Visible(void)
    {
    return (VARIANT_BOOL)IsWindowVisible(m_pDoc->Window());
    }

STDMETHODIMP_(void) CAutoFigure::put_Visible(VARIANT_BOOL fShow)
    {
    ShowWindow(m_pDoc->Window(), fShow ? SW_SHOW : SW_HIDE);
    return;
    }

/*
 * CAutoFigure::Activate
 * Method
 *
 * Activate this window, that is, bring it to the foreground
 */

STDMETHODIMP_(void) CAutoFigure::Activate(void)
    {
    HWND        hWndDoc;

    hWndDoc=m_pDoc->Window();
    SendMessage(GetParent(hWndDoc), WM_MDIACTIVATE
        , (WPARAM)hWndDoc, 0L);
    return;
    }

/*
 * CAutoFigure::Close
 * Method
 *
 * Closes this document.
 *
 * Parameters (optional)
 *  fSaveChanges        BOOL that indicates if we're to save
 *                      changes or not.  If not, then we just nuke
 *                      the figure.
 *  bstrPath            BSTR with the filename into which to save
 *                      the figure if fSaveChanges is TRUE.  Note that
 *                      this overrides any other pathname we are
 *                      already using.
 */

STDMETHODIMP_(void) CAutoFigure::Close(VARIANT varSave
    , VARIANT varPath)
    {
    //If we got varSave and it's TRUE, then save
    if (VT_ERROR!=varSave.vt && varSave.boolVal)
        {
        /*
         * If we got a filename, call SaveAs.  Otherwise
         * call Save.  If we don't have a filename for
         * Save, then Save just fails and we don't have
         * to care.
         */
        if (VT_ERROR!=varPath.vt)
            SaveAs(varPath.bstrVal);
        else
            Save();
        }

    //Hiding the document first suppresses any UI on closure.
    put_Visible(FALSE);
    SendMessage(m_pDoc->Window(), WM_CLOSE, 0, 0L);
    return;
    }

/*
 * CAutoFigure::RevertToSaved
 * Method
 *
 * Reloads the contents of the document from the saved state.
 */

STDMETHODIMP_(void) CAutoFigure::RevertToSaved(void)
    {
    //Can't do this if we don't have a file
    if ((TCHAR)0==m_pDoc->m_szFile[0])
        return;

    /*
     * Since we "open" a document by loading it and closing
     * it, then we can just "re-open" it with Load again.
     */
    m_pDoc->Load(FALSE, m_pDoc->m_szFile);

    /*
     * Load set the document as dirt when loading from a file
     * in this manner (the behavior of Import).  But this actually
     * makes the file clean, so we change the dirty flag here.
     */
    m_pDoc->FDirtySet(FALSE);
    return;
    }

/*
 * CAutoFigure::Save
 * Method
 *
 * Saves the document to a known file if one exists.
 */

STDMETHODIMP_(void) CAutoFigure::Save(void)
    {
    if ((TCHAR)0==m_pDoc->m_szFile[0])
        return;

    m_pDoc->Save(0, (LPTSTR)NULL);
    return;
    }

/*
 * CAutoFigure::SaveAs
 * Method
 *
 * Saves the current figure to a new file.
 */

STDMETHODIMP_(void) CAutoFigure::SaveAs(BSTR bstrPath)
    {
    if (NULL==bstrPath)
        return;

    //This also renames the document
   #ifdef WIN32ANSI
    char        szTemp[512];

    WideCharToMultiByte(CP_ACP, 0, bstrPath, -1, szTemp
        , 512, NULL, NULL);
    m_pDoc->Save(0, szTemp);
   #else
    m_pDoc->Save(0, bstrPath);
   #endif
    return;
    }

/*
 * CAutoFigure::Import
 * Method
 *
 * Initializes the figure from the contents of a file
 */

STDMETHODIMP_(void) CAutoFigure::Import(BSTR bstrImportPath)
    {
    if (NULL==bstrImportPath)
        return;

    /*
     * Like we have for RevertToSaved, we can just "open"
     * this file and the new information becomes current.
     */
   #ifdef WIN32ANSI
    char        szTemp[512];

    WideCharToMultiByte(CP_ACP, 0, bstrImportPath, -1
        , szTemp, 512, NULL, NULL);
    m_pDoc->Load(FALSE, szTemp);
   #else
    m_pDoc->Load(FALSE, bstrImportPath);
   #endif
    return;
    }

/*
 * CAutoFigure::Copy
 * CAutoFigure::Cut
 * CAutoFigure::Paste
 * Methods
 *
 * Perform clipboard operations
 */

STDMETHODIMP_(void) CAutoFigure::Copy(void)
    {
    m_pDoc->Clip(m_pDoc->m_pFR->Window(), FALSE);
    return;
    }

STDMETHODIMP_(void) CAutoFigure::Cut(void)
    {
    m_pDoc->Clip(m_pDoc->m_pFR->Window(), TRUE);
    return;
    }

STDMETHODIMP_(void) CAutoFigure::Paste(void)
    {
    if (m_pDoc->FQueryPaste())
        m_pDoc->Paste(m_pDoc->m_pFR->Window());

    return;
    }

/*
 * CAutoFigure::AddPoint
 * Method
 *
 * Adds a new point, expressed on a 0-32767 scale, to the figure.
 */

STDMETHODIMP_(VARIANT_BOOL) CAutoFigure::AddPoint(short x, short y)
    {
    RECT        rc;
    POINTS      pt;
    HWND        hWndPL;

    /*
     * This is the only semi-tricky method to implement here because
     * normally this processing is done inside the Polyline's
     * WM_LBUTTONDOWN.  In order to get the same behavior, we'll
     * just send the same message to it.  However, we have to
     * convert from a 0-32767 range to a window coordinate range
     * which is nicely handled by CPolyline::PointScale (the only
     * small modification to CPolyline I had to make was to make
     * PointScale public instead of private).
     *
     * This is a non-invasive way to add Automation support.
     * The invasive way would require a change to the CPolyline
     * class and it's window procedure so both this function
     * and the WM_LBUTTONDOWN handling called the same function
     * with 0-32767 scaled points.
     */

    //See if we can add any more
    if (CPOLYLINEPOINTS==get_NumberOfPoints())
        return FALSE;

    hWndPL=m_pDoc->m_pPL->Window();
    GetClientRect(hWndPL, &rc);
    SETPOINT(pt, x, y);
    m_pDoc->m_pPL->PointScale(&rc, &pt, TRUE);
    SendMessage(hWndPL, WM_LBUTTONDOWN, 0, MAKELPARAM(pt.x, pt.y));
    return TRUE;
    }

/*
 * CAutoFigure::RemovePoint
 * Method
 *
 * Removes the last point added to a figure
 */

STDMETHODIMP_(void) CAutoFigure::RemovePoint(void)
    {
    m_pDoc->Undo();
    return;
    }

/*
 * CAutoFigure::MoveSize
 *
 * Purpose:
 *  Helper function for the Left, Top, Width, and Height properties
 *  that centralizes calls to GetWindowRect and SetWindowPos,
 *  reducing overall code somewhat.
 *
 * Parameters:
 *  iAction         MOVESIZEACTION enum value to indicate what
 *                  type of moving/sizing to perform
 *  x, y            long position coordinates;  x is only meaningful
 *                  with MOVESIZEACTION_LEFT, y only with _TOP
 *  cx, cy          long extents;  cx is only meaningful
 *                  with MOVESIZEACTION_WIDTH, cy only with _HEIGHT
 *
 * Return Value:
 *  long            The current x, y, cx, or cy value depending on
 *                  iAction being _GETLEFT, _GETTOP, _GETWIDTH, or
 *                  _GETHEIGHT.
 */

long CAutoFigure::MoveSize(MOVESIZEACTION iAction, long x, long y
    , long cx, long cy)
    {
    RECT        rc;
    POINT       pt1, pt2;
    long        x1, y1, cx1, cy1;
    UINT        uFlags;

    GetWindowRect(m_pDoc->Window(), &rc);
    SETPOINT(pt1, rc.left, rc.top);
    ScreenToClient(GetParent(m_pDoc->Window()), &pt1);
    SETPOINT(pt2, rc.right, rc.bottom);
    ScreenToClient(GetParent(m_pDoc->Window()), &pt2);

    //By default we'll do nothing
    x1=pt1.x;
    y1=pt1.y;
    cx1=pt2.x-pt1.x;
    cy1=pt2.y-pt1.y;
    uFlags=0L;

    switch (iAction)
        {
        /*
         * Each individual property modifies the appropriate
         * variable x1, y1, cx1, cy1, as well as uFlags to set
         * up SetWindowPos call.
         */
        case MOVESIZEACTION_LEFT:
            x1=x;
            uFlags=SWP_NOSIZE;
            break;

        case MOVESIZEACTION_TOP:
            y1=y;
            uFlags=SWP_NOSIZE;
            break;

        case MOVESIZEACTION_WIDTH:
            cx1=cx;
            uFlags=SWP_NOMOVE;
            break;

        case MOVESIZEACTION_HEIGHT:
            cy1=cy;
            uFlags=SWP_NOMOVE;
            break;

        case MOVESIZEACTION_GETLEFT:
            return x1;

        case MOVESIZEACTION_GETTOP:
            return y1;

        case MOVESIZEACTION_GETWIDTH:
            return cx1;

        case MOVESIZEACTION_GETHEIGHT:
            return cy1;

        default:
            return 0;
        }

    //We only get here on propety changes
    SetWindowPos(m_pDoc->Window(), NULL
        , (int)x1, (int)y1, (int)cx1, (int)cy1
        , SWP_NOZORDER | SWP_NOACTIVATE | uFlags);

    //Irrelevant for property changes.
    return 0;
    }


AUTOFIGS.CPP

/*
 * AUTOFIGS.CPP
 * Cosmo Chapter 14
 *
 * "Figures" collection object for Cosmo's OLE Automation support,
 * derived from CAutoBase.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved.
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  INTERNET>kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CAutoFigures::CAutoFigures
 * CAutoFigures::~CAutoFigures
 *
 * Constructor Parameters:
 *  pCL             PCCosmoClient to the client object that we use
 *                  to implement much of this interface.
 */

CAutoFigures::CAutoFigures(PCCosmoClient pCL)
    : CAutoBase(pCL, pCL->m_hInst, IID_ICosmoFigures
    , DIID_DICosmoFigures, NULL)
    {
    return;
    }

CAutoFigures::~CAutoFigures(void)
    {
    return;
    }

/*
 * CAutoFigures::QueryInterface
 * CAutoFigures::AddRef
 * CAutoFigures::Release
 */

STDMETHODIMP CAutoFigures::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_ICosmoFigures==riid)
        *ppv=(IUnknown *)this;

    if (IID_IDispatch==riid || m_diid==riid)
        *ppv=m_pImpIDispatch;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CAutoFigures::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CAutoFigures::Release(void)
    {
    //CCosmoClient deletes this object during shutdown
    if (0==--m_cRef)
        {
        /*
         * Note:  This early release is to avoid a bug in
         * Beta Windows NT 3.51 at the time this code was
         * finalized.  Technically it should be fine to leave
         * the Release call in CImpIDispatch::~CImpIDispatch
         * (see autobase.cpp), but making the call here
         * avoids a crash on a null pointer.
         */
        ReleaseInterface(m_pImpIDispatch->m_pITypeInfo);
        }
    return m_cRef;
    }

/*
 * CAutoFigures::VTableInterface
 *
 * Purpose:
 *  Returns the right vtable pointer to use when calling
 *  ITypeInfo::Invoke (see CImpIDispatch::Invoke in AUTOBASE.CPP).
 */
void *CAutoFigures::VTableInterface(void)
    {
    return (ICosmoFigures *)this;
    }

//The ICosmoFigures implementation

/*
 * CAutoFigures::Application
 * CAutoFigures::Parent
 * Properties, read-only
 *
 * The application object (CAutoApp) in which we're contained,
 * which is stored in the frame object.  Basically we walk
 * up from the client to the frame to it's application object
 * to get the IDispatch we need.
 */

STDMETHODIMP_(IDispatch *) CAutoFigures::get_Application(void)
    {
    PCCosmoFrame    pFR;

    pFR=(PCCosmoFrame)m_pCL->Frame();
    return pFR->AutoApp()->get_Application();
    }

STDMETHODIMP_(IDispatch *) CAutoFigures::get_Parent(void)
    {
    return get_Application();
    }

/*
 * CAutoFigures::Count
 * Property, read-only
 *
 * The number of figures in this collection
 */

STDMETHODIMP_(long) CAutoFigures::get_Count(void)
    {
    //This is easy:  just the number of items in the listbox.
    return m_pCL->m_cDoc;
    }

/*
 * CAutoFigures::Item
 * Method
 *
 * This is essentially an array lookup operator for the collection.
 * Collection.Item by itself the same as the collection itself.
 * Otherwise you can refer to the item by index or by path, which
 * shows up in the VARIANT parameter.  We have to check the type
 * of the variant to see if it's VT_I4 (an index) or VT_BSTR (a
 * path) and do the right thing.
 */

STDMETHODIMP_(IDispatch *) CAutoFigures::Item(VARIANT index)
    {
    IDispatch  *pIDispatch=NULL;
    PCCosmoDoc  pDoc;
    const int   cch=512;
    TCHAR       szPath[cch];
    UINT        i;
    HWND        hList=m_pCL->m_hListDocs;

    /*
     * Each case in this switch gets at a different pointer
     * and stores it in pIDispatch for return.
     */
    switch (index.vt)
        {
        case VT_ERROR:
            /*
             * No parameters, get the "Figures" collection
             * IDispatch, which we can easily retrieve with
             * our own QueryInterface.
             */
            QueryInterface(IID_IDispatch, (PPVOID)&pIDispatch);
            break;

        case VT_I4:
            if (LB_ERR!=SendMessage(hList, LB_GETTEXT
                , (int)index.lVal, (LONG)&pDoc))
                {
                //Sets pIDispatch to NULL on failure.
                pDoc->AutoFigure()->QueryInterface(IID_IDispatch
                    , (PPVOID)&pIDispatch);
                }

            break;

        case VT_BSTR:
            /*
             * First we'll iterate over the pages and check
             * for full path matches.  If that doesn't yield
             * anything, then we'll see if index.bstrVal is
             * just a filename inside one of the paths.
             */
            for (i=0; i < m_pCL->m_cDoc; i++)
                {
                if (LB_ERR!=SendMessage(hList, LB_GETTEXT
                    , i, (LONG)&pDoc))
                    {
                    pDoc->FilenameGet(szPath, cch);

                   #ifdef WIN32ANSI
                    char    szTemp[512];

                    WideCharToMultiByte(CP_ACP, 0, index.bstrVal, -1
                        , szTemp, 512, NULL, NULL);

                    if (0==lstrcmpi(szPath, szTemp))
                   #else
                    if (0==lstrcmpi(szPath, index.bstrVal))
                   #endif
                        {
                        //Found it...
                        pDoc->AutoFigure()->QueryInterface
                            (IID_IDispatch, (PPVOID)&pIDispatch);
                        }
                    }
                }

            //Stop now if we found anything.
            if (NULL!=pIDispatch)
                break;

            //Now look for portions of the path
            for (i=0; i < m_pCL->m_cDoc; i++)
                {
                if (LB_ERR!=SendMessage(hList, LB_GETTEXT
                    , i, (LONG)&pDoc))
                    {
                    LPTSTR  psz;

                    pDoc->FilenameGet(szPath, cch);

                   #ifdef WIN32ANSI
                    char    szTemp[512];

                    WideCharToMultiByte(CP_ACP, 0, index.bstrVal, -1
                        , szTemp, 512, NULL, NULL);
                    psz=_tcsstr(szPath, szTemp);
                   #else
                    psz=_tcsstr(szPath, index.bstrVal);
                   #endif

                    if (NULL!=psz)
                        {
                        //Found it...
                        pDoc->AutoFigure()->QueryInterface
                            (IID_IDispatch, (PPVOID)&pIDispatch);
                        }
                    }
                }

            break;

        default:
            return NULL;
        }

    return pIDispatch;
    }

/*
 * CAutoFigures::Add
 * CAutoFigures::Open
 * Methods
 *
 * Creates a new figure window with an empty figure (Add) or one
 * with the contents of a file (Open), and adds it to the collection.
 */

STDMETHODIMP_(IDispatch *) CAutoFigures::Add(void)
    {
    return NewFigure(NULL);
    }

STDMETHODIMP_(IDispatch *) CAutoFigures::Open(BSTR bstrPath)
    {
   #ifdef WIN32ANSI
    char        szTemp[512];

    WideCharToMultiByte(CP_ACP, 0, bstrPath, -1, szTemp
        , 512, NULL, NULL);
    return NewFigure(szTemp);
   #else
    return NewFigure(bstrPath);
   #endif
    }

/*
 * CAutoFigures:NewFigure
 *
 * Purpose:
 *  Internal helper function to centralize figure creation for
 *  Add and Open.
 *
 * Parameters:
 *  pszPath         LPTSTR to a file to open if this is an open.
 *                  Should be NULL for Add.
 *
 * Return Value:
 *  IDispatch *     Return value for Add and Open
 */

IDispatch * CAutoFigures::NewFigure(LPTSTR pszPath)
    {
    PCCosmoDoc  pDoc;
    IDispatch  *pIDispatch=NULL;
    HRESULT     hr;

    //Try creating a hidden document, which creates the object.
    pDoc=(PCCosmoDoc)m_pCL->NewDocument(FALSE);

    if (NULL==pDoc)
        return NULL;

    hr=ResultFromScode(E_FAIL);

    //Try to load the file and get the IDispatch for the document
    if (DOCERR_NONE==pDoc->Load(TRUE, pszPath))
        {
        hr=pDoc->AutoFigure()->QueryInterface(IID_IDispatch
            , (PPVOID)&pIDispatch);
        }

    if (FAILED(hr))
        m_pCL->CloseDocument(pDoc);
    else
        g_cObj++;

    return pIDispatch;
    }

/*
 * CAutoFigures::Close
 * Method
 *
 * Closes all figure windows in the collection
 */

STDMETHODIMP_(void) CAutoFigures::Close(void)
    {
    m_pCL->QueryCloseAllDocuments(TRUE, FALSE);
    return;
    }

/*
 * CAutoFigures::_NewEnum
 * Method
 *
 * Creates and returns an enumerator of the current list of
 * figures in this collection.
 */

STDMETHODIMP_(IUnknown *) CAutoFigures::_NewEnum(void)
    {
    PCEnumFigures   pNew=NULL;
    BOOL            fRet=TRUE;

    pNew=new CEnumFigures(m_pCL->Instance());

    if (NULL!=pNew)
        {
        if (!pNew->Init(m_pCL->m_hListDocs, FALSE))
            {
            delete pNew;
            pNew=NULL;
            }
        else
            pNew->AddRef();
        }

    return (IUnknown *)pNew;
    }

//CEnumFigures implementation of IEnumVARIANT

/*
 * CEnumFigures::CEnumFigures
 * CEnumFigures::~CEnumFigures
 *
 * Parameters (Constructor):
 *  hInst           HINSTANCE of the application.
 */

CEnumFigures::CEnumFigures(HINSTANCE hInst)
    {
    m_cRef=0;
    m_hList=NULL;
    m_hInst=hInst;
    m_iCur=0;
    m_cItems=0;
    return;
    }

CEnumFigures::~CEnumFigures(void)
    {
    if (NULL!=m_hList)
        DestroyWindow(m_hList);

    return;
    }

/*
 * CEnumFigures::Init
 *
 * Purpose:
 *  Initializes the enumeration with any operations that might fail.
 *
 * Parameters:
 *  hListPDoc       HWND of the listbox containing the current
 *                  set of document pointers.
 *  fClone          BOOL indicating if this is a clone creation
 *                  in which case hListPDoc already has what we want
 *                  and we can just copy it directly.
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded,
 *                  FALSE otherwise.
 */
BOOL CEnumFigures::Init(HWND hListPDoc, BOOL fClone)
    {
    UINT        i;
    UINT        cDoc;
    PCDocument  pDoc;

    //Create our own list
    m_hList=CreateWindow(TEXT("listbox"), TEXT("EnumList")
        , WS_POPUP | LBS_OWNERDRAWFIXED, 0, 0, 100, 100
        , HWND_DESKTOP, NULL, m_hInst, NULL);

    if (NULL==m_hList)
        return FALSE;

    /*
     * An enumeration should not be attached to any sort of
     * dynamic list, so we have to initialize our own list here.
     * Furthermore, the hList we get here has document object
     * pointers which could possibly become invalid and we have
     * no way of knowing it.  So we take each pointer and copy
     * the window handle of that document into our own list.  If
     * the window is destroyed, then we have a way of checking
     * that condition and failing to return a valid pointer
     * in other partsof this enumerator.
     */

    cDoc=(UINT)SendMessage(hListPDoc, LB_GETCOUNT, 0, 0L);

    for (i=0; i < cDoc; i++)
        {
        if (LB_ERR!=SendMessage(hListPDoc, LB_GETTEXT, i
            , (LONG)&pDoc))
            {
            HWND    hWndDoc;

            //When cloning we have the window handle already.
            if (fClone)
                hWndDoc=(HWND)(UINT)(LONG)pDoc;
            else
                hWndDoc=pDoc->Window();

            if (LB_ERR!=SendMessage(m_hList, LB_ADDSTRING, 0
                , (LONG)(UINT)hWndDoc))
                m_cItems++;
            }
        }

    return TRUE;
    }

/*
 * CEnumFigures::QueryInterface
 * CEnumFigures::AddRef
 * CEnumFigures::Release
 */

STDMETHODIMP CEnumFigures::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IEnumVARIANT==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CEnumFigures::AddRef(void)
    {
    ++m_cRef;
    return m_cRef;
    }

STDMETHODIMP_(ULONG) CEnumFigures::Release(void)
    {
    ULONG       cRefT;

    cRefT=--m_cRef;

    if (0L==m_cRef)
        delete this;

    return cRefT;
    }

/*
 * CEnumFigures::Next
 * CEnumFigures::Skip
 * CEnumFigures::Reset
 * CEnumFigures::Clone
 *
 * Standard enumerator members for IEnumVARIANT
 */

STDMETHODIMP CEnumFigures::Next(ULONG cVar, VARIANT *pVar
    , ULONG *pulVar)
    {
    ULONG       cReturn=0L;
    PCCosmoDoc  pDoc;
    HRESULT     hr;
    void       *pv;
    LONG        l;

    if (NULL==pulVar)
        {
        if (1L!=cVar)
            return ResultFromScode(E_POINTER);
        }
    else
        *pulVar=0L;

    if (NULL==pVar || m_iCur >= m_cItems)
        return ResultFromScode(S_FALSE);

    while (m_iCur < m_cItems && cVar > 0)
        {
        if (LB_ERR==SendMessage(m_hList, LB_GETTEXT, (UINT)m_iCur++
            , (LONG)&l))
            continue;

        /*
         * Try getting the document pointer.  If this fails
         * then the window is invalid (document is destroyed)
         * so remove it from the list for future enumerations.
         */
        pDoc=(PCCosmoDoc)SendMessage((HWND)(UINT)l
            , DOCM_PDOCUMENT, 0, 0L);

        if (NULL==pDoc)
            {
            SendMessage(m_hList, LB_DELETESTRING, (UINT)--m_iCur, 0L);
            continue;
            }

        hr=pDoc->AutoFigure()->QueryInterface(IID_IDispatch, &pv);

        if (SUCCEEDED(hr))
            {
            *pVar->ppdispVal=(IDispatch *)pv;
            pVar++;
            cReturn++;
            cVar--;
            }
        }

    if (NULL!=pulVar)
        *pulVar=cReturn;

    return NOERROR;
    }

STDMETHODIMP CEnumFigures::Skip(ULONG cSkip)
    {
    if ((m_iCur+cSkip) >= m_cItems)
        return ResultFromScode(S_FALSE);

    m_iCur+=cSkip;
    return NOERROR;
    }

STDMETHODIMP CEnumFigures::Reset(void)
    {
    m_iCur=0;
    return NOERROR;
    }

STDMETHODIMP CEnumFigures::Clone(LPENUMVARIANT *ppEnum)
    {
    PCEnumFigures   pNew;

    *ppEnum=NULL;

    pNew=new CEnumFigures(m_hInst);

    if (NULL!=pNew)
        {
        if (!pNew->Init(m_hList, TRUE))
            {
            delete pNew;
            pNew=NULL;
            }
        else
            pNew->AddRef();
        }

    *ppEnum=(IEnumVARIANT *)pNew;
    return (NULL!=pNew) ? NOERROR : ResultFromScode(E_OUTOFMEMORY);
    }


CLIENT.CPP

/*
 * CLIENT.CPP
 * Cosmo Chapter 14
 *
 * Implementation of the CCosmoClient class that just makes sure
 * we get a CCosmoDoc on doc creation and that we initialize fully.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CCosmoClient::CCosmoClient
 * CCosmoClient::~CCosmoClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 */

CCosmoClient::CCosmoClient(HINSTANCE hInst)
    : CClient(hInst)
    {
    return;
    }

CCosmoClient::~CCosmoClient(void)
    {
    return;
    }

/*
 * CCosmoClient::CreateCDocument
 *
 * Purpose:
 *  Constructs a new document specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::CreateCDocument(void)
    {
    return (PCDocument)(new CCosmoDoc(m_hInst, m_pFR));
    }

/*
 * CCosmoClient::NewDocument
 *
 * Purpose:
 *  Small override of the CClient::NewDocument that we have just
 *  to check the initial line selection on new document creation.
 *
 * Parameters:
 *  fVisible        BOOL indicating if the document is to be
 *                  visible or not.
 *  pAdv            PCDocumentAdviseSink to set with the new
 *                  document for notifications.  Can be NULL.
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::NewDocument(BOOL fVisible
    , PCDocumentAdviseSink pAdv)
    {
    PCDocument  pDoc;

    //Perform default NewDocument first
    pDoc=CClient::NewDocument(fVisible, pAdv);

    //We know that m_pFR is actually a CCosmoFrame, so type is safe.
    ((PCCosmoFrame)m_pFR)->CheckLineSelection(IDM_LINESOLID);

    return pDoc;
    }


COSMO.CPP

/*
 * COSMO.CPP
 * Cosmo Chapter 14
 *
 * WinMain and CCosmoFrame implementations.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "cosmo.h"

extern "C"
{
OLEDBGDATA_MAIN("COSMO")
}

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

//Make window handle global so other code can cause a shutdown
HWND        g_hWnd=NULL;

//Indicate if the user has control
BOOL        g_fUser=TRUE;

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCCosmoFrame    pFR;
    FRAMEINIT       fi;
    WPARAM          wRet;

   #ifndef WIN32
    int             cMsg=96;

    while (!SetMessageQueue(cMsg) && (cMsg-=8));
   #endif

    //Attempt to allocate and initialize the application
    pFR=new CCosmoFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return -1;

    fi.idsMin=IDS_FRAMEMIN;
    fi.idsMax=IDS_FRAMEMAX;
    fi.idsStatMin=IDS_STATMESSAGEMIN;
    fi.idsStatMax=IDS_STATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=440;
    fi.cy=460;

    //If we can initialize pFR, start chugging messages
    if (pFR->FInit(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Cosmo Figure object to call when it gets
 *  destroyed.  We destroy the main window if the proper conditions
 *  are met for shutdown.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void PASCAL ObjectDestroyed(void)
    {
    g_cObj--;

    //No more objects, no locks, no user control, shut the app down.
    if (0L==g_cObj && 0L==g_cLock && IsWindow(g_hWnd) && !g_fUser)
        PostMessage(g_hWnd, WM_CLOSE, 0, 0L);

    return;
    }

/*
 * CCosmoFrame::CCosmoFrame
 * CCosmoFrame::~CCosmoFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WInMain
 */

CCosmoFrame::CCosmoFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    : CFrame(hInst, hInstPrev, pszCmdLine, nCmdShow)
    {
    UINT        i;
    char        szTemp[CCHPATHMAX];     //ParseCmdLine is ANSI

    for (i=0; i<5; i++)
        m_hBmpLines[i]=NULL;

    m_uIDCurLine=0;
    m_fInitialized=FALSE;
    m_fOleStdInit=FALSE;
    m_pIClassDataTran=NULL;

    m_fEmbedding=FALSE;
    //This function is in OLESTD
    ParseCmdLine(pszCmdLine, &m_fEmbedding, szTemp);
    g_fUser=!m_fEmbedding;
    m_dwRegCO=0;
    m_pIClassFactory=NULL;

    return;
    }

CCosmoFrame::~CCosmoFrame(void)
    {
    UINT        i;

    //Reverse CoRegisterClassObject, takes class factory ref to 1
    if (0L!=m_dwRegCO)
        CoRevokeClassObject(m_dwRegCO);

    //Should be last Release, which frees the class factory.
    if (NULL!=m_pIClassFactory)
        m_pIClassFactory->Release();

    for (i=0; i<5; i++)
        {
        if (NULL!=m_hBmpLines[i])
            DeleteObject(m_hBmpLines[i]);
        }

    if (NULL!=m_pIClassDataTran)
        {
        m_pIClassDataTran->LockServer(FALSE);
        m_pIClassDataTran->Release();
        }

    OleFlushClipboard();

    if (m_fOleStdInit)
        OleStdUninitialize();

    if (m_fInitialized)
        OleUninitialize();

    return;
    }

/*
 * CCosmoFrame::FInit
 *
 * Purpose:
 *  Call CoInitialize then calling down into the base class
 *  initialization.
 *
 * Parameters:
 *  pFI             PFRAMEINIT containing initialization parameters.
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded, FALSE otherwise.
 */

BOOL CCosmoFrame::FInit(PFRAMEINIT pFI)
    {
    DWORD       dwVer;
    HRESULT     hr;

    dwVer=OleBuildVersion();

    if (rmm!=HIWORD(dwVer))
        return FALSE;

    if (FAILED(OleInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    if (!OleStdInitialize(m_hInst))
        return FALSE;

    m_fOleStdInit=TRUE;

    hr=CoGetClassObject(CLSID_DataTransferObject
        , CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory
        , (PPVOID)&m_pIClassDataTran);

    if (SUCCEEDED(hr))
        m_pIClassDataTran->LockServer(TRUE);

    /*
     * Create our class factory and register it for this application
     * using CoRegisterClassObject.  The REGCLS_*USE flags have to
     * do with servicable object from this instance, not with MDI or
     * SDI.  Since it's most convenient to be single use, we'll do
     * this in either version.
     *
     * In addition, it only makes sense to do any of this if we're
     * being launched to be a server.
     */
    if (m_fEmbedding)
        {
        m_pIClassFactory=new CFigureClassFactory(this);

        if (NULL==m_pIClassFactory)
            return FALSE;

        //Since we hold on to this, we should AddRef it.
        m_pIClassFactory->AddRef();

        hr=CoRegisterClassObject(CLSID_Cosmo2Figure, m_pIClassFactory
            , CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE, &m_dwRegCO);

        if (FAILED(hr))
            return FALSE;
        }

    return CFrame::FInit(pFI);
    }

/*
 * CCosmoFrame::CreateCClient
 *
 * Purpose:
 *  Constructs a new client specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient        Pointer to the new client object.
 */

PCClient CCosmoFrame::CreateCClient(void)
    {
    return (PCClient)(new CCosmoClient(m_hInst));
    }

/*
 * CCosmoFrame::FRegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CCosmoFrame::FRegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //First let the standard frame do its thing
    if (!CFrame::FRegisterAllClasses())
        return FALSE;

    /*
     * We want a different background color for the document
     * because the Polyline we put in the document will paint
     * with COLOR_WINDOW which by default which is CLASSLIB's
     * default document color.
     */

    GetClassInfo(m_hInst, SZCLASSDOCUMENT, &wc);
    UnregisterClass(SZCLASSDOCUMENT, m_hInst);

    wc.hbrBackground=(HBRUSH)(COLOR_APPWORKSPACE+1);

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

    //Register the Polyline window.
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.hInstance     = m_hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PolylineWndProc;
    wc.cbWndExtra    = CBPOLYLINEWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_CROSS);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPOLYLINE;

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

    return TRUE;
    }

/*
 * CCosmoFrame::FPreShowInit
 *
 * Purpose:
 *  Called from FInit before intially showing the window.  We do
 *  whatever else we want here, modifying nCmdShow as necessary
 *  which affects ShowWindow in FInit.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if this initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CCosmoFrame::FPreShowInit(void)
    {
    CreateLineMenu();
    CheckLineSelection(IDM_LINESOLID);

    //Save the window handle for shutdown if necessary.
    g_hWnd=m_hWnd;

    //If we're -Embedding, don't show the window initially.
    if (m_fEmbedding)
        m_nCmdShow=SW_HIDE;

    return TRUE;
    }

/*
 * CCosmoFrame::ParseCommandLine
 *
 * Purpose:
 *  Allows the application to parse the command line and take action
 *  after the window has possibly been shown. For a compound
 *  document server we need to just make sure that if -Embedding
 *  is there that we take no file action.  FPreShowInit has already
 *  handled the window visibility.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if this initialization succeeded,
 *                  FALSE otherwise.
 */

void CCosmoFrame::ParseCommandLine(void)
    {
    //If -Embedding was there, prevent any attempt to load a file.
    if (m_fEmbedding)
        return;

    CFrame::ParseCommandLine();
    return;
    }

/*
 * CCosmoFrame::CreateLineMenu
 *
 * Purpose:
 *  Initializes the bitmaps used to create the Line menu and
 *  replaces the text items defined in the application resources
 *  with these bitmaps.  Note that the contents of m_hBmpLines
 *  must be cleaned up when the application terminates.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CreateLineMenu(void)
    {
    HMENU       hMenu;
    HDC         hDC, hMemDC;
    HPEN        hPen;
    HGDIOBJ     hObj;
    TEXTMETRIC  tm;
    UINT        i, cx, cy;

    hMenu=GetSubMenu(GetMenu(m_hWnd), 3);   //Line menu.
    hDC=GetDC(m_hWnd);

    //Create each line in a menu item 8 chars wide, one char high.
    GetTextMetrics(hDC, &tm);
    cx=tm.tmAveCharWidth*8;
    cy=tm.tmHeight;

    /*
     * Create a memory DC in which to draw lines, and bitmaps
     * for each line.
     */
    hMemDC=CreateCompatibleDC(hDC);
    ReleaseDC(m_hWnd, hDC);

    for (i=0; i<5; i++)
        {
        m_hBmpLines[i]=CreateCompatibleBitmap(hMemDC, cx, cy);
        SelectObject(hMemDC, m_hBmpLines[i]);

        PatBlt(hMemDC, 0, 0, cx, cy, WHITENESS);

        hPen=CreatePen(i, 1, 0L);       //i=line style like PS_SOLID
        hObj=SelectObject(hMemDC, hPen);

        MoveToEx(hMemDC, 0, cy/2, NULL);
        LineTo(hMemDC, cx, cy/2);

        ModifyMenu(hMenu, IDM_LINEMIN+i, MF_BYCOMMAND | MF_BITMAP
            , IDM_LINEMIN+i, (LPTSTR)(LONG)(UINT)m_hBmpLines[i]);

        SelectObject(hMemDC, hObj);
        DeleteObject(hPen);
        }

    CheckMenuItem(hMenu, IDM_LINESOLID, MF_CHECKED);
    DeleteDC(hMemDC);

    return;
    }

/*
 * CCosmoFrame::CreateGizmos
 *
 * Purpose:
 *  Procedure to create all the necessary gizmobar buttons.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of gizmos added to the bar.
 */

UINT CCosmoFrame::CreateGizmos(void)
    {
    UINT            iLast;
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;
    UINT            utEx  =GIZMOTYPE_BUTTONATTRIBUTEEX;

    //Insert the standard ones.
    iLast=CFrame::CreateGizmos();

    /*
     * Insert File Import in the 5th position and account for
     * it in iLast.
     */
    m_pGB->Add(utCmd, 4, IDM_FILEIMPORT, m_dxB, m_dyB
        , NULL, m_hBmp, 2, uState);
    iLast++;

    //Separator
    m_pGB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    /*
     * For the Background bitmap, preserve our use of black
     * (part of the image)
     */
    m_pGB->Add(utCmd, iLast++, IDM_COLORBACKGROUND, m_dxB, m_dyB
        , NULL, m_hBmp, 3, GIZMO_NORMAL | PRESERVE_BLACK);

    m_pGB->Add(utCmd, iLast++, IDM_COLORLINE, m_dxB, m_dyB
        , NULL, m_hBmp, 4, uState);

    //Separator
    m_pGB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //Line styles.
    m_pGB->Add(utEx, iLast++, IDM_LINESOLID, m_dxB, m_dyB
        , NULL, m_hBmp, 5, uState);
    m_pGB->Add(utEx, iLast++, IDM_LINEDASH, m_dxB, m_dyB
        , NULL, m_hBmp, 6, uState);
    m_pGB->Add(utEx, iLast++, IDM_LINEDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 7, uState);
    m_pGB->Add(utEx, iLast++, IDM_LINEDASHDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 8, uState);
    m_pGB->Add(utEx, iLast++, IDM_LINEDASHDOTDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 9, uState);

    return iLast;
    }

/*
 * CCosmoFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the Cosmo frame window that just
 *  processes the line menu and the color menu leaving the
 *  CFrame to do everything else.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CCosmoFrame::OnCommand(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PCCosmoDoc      pDoc;
    TCHAR           szFile[CCHPATHMAX];
    BOOL            fOK;
    UINT            i, uTemp;
    COLORREF        rgColors[16];
    CHOOSECOLOR     cc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many GizmoBar notifications.
     */
    if (m_fInit)
        return 0L;

    pDoc=(PCCosmoDoc)m_pCL->ActiveDocument();

    /*
     * Check for the line style commands which are
     * IDM_LINEMIN+<style>.  We handle this by changing the menu
     * and toolbar, then we pass it to the document for real
     * processing.
     */
    if (NULL!=pDoc && IDM_LINEMIN <= wID && IDM_LINEMAX >=wID)
        {
        CheckLineSelection(wID);
        pDoc->LineStyleSet(wID-IDM_LINEMIN);
        return 0L;
        }

    switch (wID)
        {
        case IDM_FILEIMPORT:
            szFile[0]=0;
            fOK=FSaveOpenDialog(szFile, CCHPATHMAX, IDS_FILEIMPORT
                , TRUE, &i);

            if (fOK)
                {
                uTemp=pDoc->ULoad(FALSE, szFile);
                pDoc->ErrorMessage(uTemp);
                }

            return (LRESULT)fOK;

        case IDM_COLORBACKGROUND:
        case IDM_COLORLINE:
            //Invoke the color chooser for either color
            uTemp=(IDM_COLORBACKGROUND==wID)
                ? DOCCOLOR_BACKGROUND : DOCCOLOR_LINE;

            for (i=0; i<16; i++)
                rgColors[i]=RGB(0, 0, i*16);

            memset(&cc, 0, sizeof(CHOOSECOLOR));
            cc.lStructSize=sizeof(CHOOSECOLOR);
            cc.lpCustColors=rgColors;
            cc.hwndOwner=hWnd;
            cc.Flags=CC_RGBINIT;
            cc.rgbResult=pDoc->ColorGet(uTemp);

            if (ChooseColor(&cc))
                pDoc->ColorSet(uTemp, cc.rgbResult);

            break;

        default:
           CFrame::OnCommand(hWnd, wParam, lParam);
        }

    return 0L;
    }

/*
 * CCosmoFrame::OnDocumentDataChange
 *
 * Purpose:
 *  Update the Line menu and GizmoBar if the style in the data
 *  changes.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentDataChange(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::OnDocumentActivate
 *
 * Purpose:
 *  Informs us that document activation changed, so update the UI
 *  for that new document.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentActivate(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    PCDocument  pDoc;
    BOOL        fOK=FALSE;
    BOOL        fCallDefault=TRUE;
    UINT        i;
    UINT        uTemp;
    UINT        uTempE;
    UINT        uTempD;

    pDoc=m_pCL->ActiveDocument();

    uTempE=MF_ENABLED | MF_BYCOMMAND;
    uTempD=MF_DISABLED | MF_GRAYED | MF_BYCOMMAND;
    uTemp=((NULL!=pDoc) ? uTempE : uTempD);

    //File menu:  If there is document window, disable Import.
    if (m_phMenu[0]==hMenu)
        EnableMenuItem(hMenu, IDM_FILEIMPORT, uTemp);

    //Color menu:  no document, no commands
    if (m_phMenu[2]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_COLORBACKGROUND, uTemp);
        EnableMenuItem(hMenu, IDM_COLORLINE,       uTemp);
        fCallDefault=FALSE;
        }

    //Line menu:  no document, no commands
    if (m_phMenu[3]==hMenu)
        {
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            EnableMenuItem(hMenu, i, uTemp);

        fCallDefault=FALSE;
        }

    if (fCallDefault)
        CFrame::UpdateMenus(hMenu, iMenu);

    return;
    }

/*
 * CCosmoFrame::UpdateGizmos
 *
 * Purpose:
 *  Enables and disables gizmos depending on whether we have
 *  a document or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateGizmos(void)
    {
    BOOL        fLast;
    UINT        i;

    //Save the last enabled state before CFrame changes it
    fLast=m_fLastEnable;

    //Let the default hack on its gizmos.
    CFrame::UpdateGizmos();

    /*
     * If CFrame::UpdateGizmos changed anything, then we need
     * to change as well--if nothing changes, nothing to do.
     */
    if (fLast!=m_fLastEnable)
        {
        m_pGB->Enable(IDM_FILEIMPORT, m_fLastEnable);

        m_pGB->Enable(IDM_COLORBACKGROUND, m_fLastEnable);
        m_pGB->Enable(IDM_COLORLINE,       m_fLastEnable);

        for (i=IDM_LINEMIN; i <= IDM_LINEMAX; i++)
            m_pGB->Enable(i, m_fLastEnable);
        }

    return;
    }

/*
 * CCosmoFrame::CheckLineSelection
 *
 * Purpose:
 *  Maintains the bitmap menu and the gizmos for the line selection.
 *  Both are mutially exclusive option lists where a selection in
 *  one has to affect the other.
 *
 * Parameters:
 *  uID             UINT ID of the item to be selected
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CheckLineSelection(UINT uID)
    {
    UINT        i;
    HMENU       hMenu;

    //Update menus and gizmo if the selection changed.
    if (uID!=m_uIDCurLine)
        {
        m_uIDCurLine=uID;
        hMenu=GetMenu(m_hWnd);

        //Uncheck all lines initially.
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            CheckMenuItem(hMenu, i, MF_UNCHECKED | MF_BYCOMMAND);

        CheckMenuItem(hMenu, uID, MF_CHECKED | MF_BYCOMMAND);
        m_pGB->Check(uID, TRUE);
        }

    return;
    }

/*
 * CCosmoFrame::UpdateEmbeddingUI
 *
 * Purpose:
 *  Puts the application into the user interface for editing an
 *  embedded object, manipulating menus and title bars.
 *
 * Parameters:
 *  fEmbedding      BOOL TRUE to go in the mode, FALSE to leave it.
 *  pszApp          LPCTSTR name of the container application as
 *                  received in IOleObject::SetHostNames.
 *  pszObj          LPCTSTR name of the object in the container as
 *                  received in IOleObject::SetHostNames.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateEmbeddingUI(BOOL fEmbedding
    , PCDocument pDoc, LPCTSTR pszApp, LPCTSTR pszObj)
    {
    HMENU           hMenu;
    TCHAR           szTemp[256];

    //First let's play with the File menu.
    hMenu=m_phMenu[0];

    //Remove or add the File New, Open, and Save items
    if (fEmbedding)
        {
        DeleteMenu(m_phMenu[0], IDM_FILENEW,   MF_BYCOMMAND);
        DeleteMenu(m_phMenu[0], IDM_FILEOPEN,  MF_BYCOMMAND);
        DeleteMenu(m_phMenu[0], IDM_FILECLOSE, MF_BYCOMMAND);
        DeleteMenu(m_phMenu[0], IDM_FILESAVE,  MF_BYCOMMAND);

        //Save As->Save Copy As
        ModifyMenu(m_phMenu[0], IDM_FILESAVEAS, MF_BYCOMMAND
            , IDM_FILESAVEAS, PSZ(IDS_SAVECOPYAS));

        }
    else
        {
        InsertMenu(m_phMenu[0], 0, MF_BYPOSITION, IDM_FILENEW
            , PSZ(IDS_NEW));
        InsertMenu(m_phMenu[0], 1, MF_BYPOSITION, IDM_FILEOPEN
            , PSZ(IDS_OPEN));
        InsertMenu(m_phMenu[0], 2, MF_BYPOSITION, IDM_FILESAVE
            , PSZ(IDS_SAVE));
        InsertMenu(m_phMenu[0], 3, MF_BYPOSITION, IDM_FILECLOSE
            , PSZ(IDS_SAVE));

        //Save Copy As->Save As
        ModifyMenu(m_phMenu[0], IDM_FILESAVEAS, MF_BYCOMMAND
            , IDM_FILESAVEAS, PSZ(IDS_SAVEAS));
        }

    //Change "Exit" to "Exit & Return to xx" or vice-versa for SDI
    if (fEmbedding)
        wsprintf(szTemp, PSZ(IDS_EXITANDRETURN), (LPSTR)pszObj);
    else
        lstrcpy(szTemp, PSZ(IDS_EXIT));

    ModifyMenu(m_phMenu[0], IDM_FILEEXIT, MF_STRING, IDM_FILEEXIT
        , szTemp);
    DrawMenuBar(m_hWnd);

    //Now let's play with the gizmobar.
    m_pGB->Show(IDM_FILENEW,   !fEmbedding);
    m_pGB->Show(IDM_FILEOPEN,  !fEmbedding);
    m_pGB->Show(IDM_FILECLOSE, !fEmbedding);
    m_pGB->Show(IDM_FILESAVE,  !fEmbedding);

    //Enable what's left appropriately.
    UpdateGizmos();

    //Now play with the title bar.

    //IDS_EMBEDDINGCAPTION is MDI/SDI sensitive in COSMO.RC.
    wsprintf(szTemp, PSZ(IDS_EMBEDDINGCAPTION), pszObj);

    /*
     * Remember that in MDI situations that Windows takes care of
     * the frame window caption bar when the document is maximized.
     */
   #ifdef MDI
    SetWindowText(pDoc->Window(), szTemp);
   #else
    SetWindowText(m_hWnd, szTemp);
   #endif

    return;
    }


COSMO.H

/*
 * COSMO.H
 * Cosmo Chapter 14
 *
 * Single include file that pulls in everything needed for other
 * source files in the Cosmo application.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _COSMO_H_
#define _COSMO_H_

#define INC_OLE2
#include <windows.h>
#include <memory.h>
#include <ole2.h>
#include <ole2ver.h>
#include <olestd.h>
#include <bookguid.h>

extern "C"
    {
    #include <commdlg.h>
    }

#include <classlib.h>

#include "resource.h"

//Get the editor window information.
#include "polyline.h"

//COSMO.CPP:  Frame object that creates a main window

class CCosmoFrame : public CFrame
    {
    friend class CFigureClassFactory;
    friend class CFigure;   //For UI purposes.

    private:
        HBITMAP         m_hBmpLines[5];     //Menu item bitmaps
        UINT            m_uIDCurLine;       //Current line selection
        BOOL            m_fInitialized;     //Did OleInitalize work?
        BOOL            m_fOleStdInit;      //Did OleStdInitialize work?
        LPCLASSFACTORY  m_pIClassDataTran;  //For locking

        BOOL            m_fEmbedding;       //-Embedding found?
        DWORD           m_dwRegCO;          //Registration key
        LPCLASSFACTORY  m_pIClassFactory;

    protected:
        //Overridable for creating a CClient for this frame
        virtual PCClient  CreateCClient(void);

        virtual BOOL      FRegisterAllClasses(void);
        virtual BOOL      FPreShowInit(void);
        virtual UINT      CreateGizmos(void);

        virtual void      ParseCommandLine(void);

        virtual LRESULT   OnCommand(HWND, WPARAM, LPARAM);
        virtual void      OnDocumentDataChange(PCDocument);
        virtual void      OnDocumentActivate(PCDocument);

        //New for this class
        virtual void      CreateLineMenu(void);

    public:
        CCosmoFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CCosmoFrame(void);

        //Overrides
        virtual BOOL      FInit(PFRAMEINIT);
        virtual void      UpdateMenus(HMENU, UINT);
        virtual void      UpdateGizmos(void);

        //New for this class
        virtual void      CheckLineSelection(UINT);
        virtual void      UpdateEmbeddingUI(BOOL, PCDocument
                              , LPCTSTR, LPCTSTR);
    };

typedef CCosmoFrame *PCCosmoFrame;

//CLIENT.CPP

/*
 * The only reason we have a derived class here is to override
 * CreateCDocument so we can create our own type as well as
 * overriding NewDocument to perform one other piece of work once
 * the document's been created.
 */

class CCosmoClient : public CClient
    {
    protected:
        //Overridable for creating a new CDocument
        virtual PCDocument CreateCDocument(void);

    public:
        CCosmoClient(HINSTANCE);
        virtual ~CCosmoClient(void);

        virtual PCDocument NewDocument(BOOL, PCDocumentAdviseSink);
    };

typedef CCosmoClient *PCCosmoClient;

//DOCUMENT.CPP

//Constant ID for the window polyline that lives in a document
#define ID_POLYLINE         10

class CCosmoDoc : public CDocument
    {
    friend class CPolylineAdviseSink;

    //These need access to FQueryPasteFromData, FPasteFromData
    friend class CDropTarget;
    friend class CDropSource;
    friend class CFigureClassFactory;
    friend class CFigure;

    protected:
        UINT                    m_uPrevSize;    //Last WM_SIZE wParam
        LONG                    m_lVer;         //Loaded Polyline ver

        PCPolyline              m_pPL;          //Polyline window here
        PCPolylineAdviseSink    m_pPLAdv;       //Advises from Polyline

        class CDropTarget      *m_pDropTarget;  //Registered target
        BOOL                    m_fDragSource;  //Source==target?

        UINT                    m_cfEmbedSource;
        UINT                    m_cfObjectDescriptor;

        class CFigure          *m_pFigure;      //The object in us.
        LPMONIKER               m_pMoniker;     //Our file
        DWORD                   m_dwRegROT;     //From IROT::Register

        UINT                    m_cfLinkSource;
        UINT                    m_cfLinkSrcDescriptor;

    protected:
        virtual BOOL    FMessageHook(HWND, UINT, WPARAM, LPARAM
            , LRESULT *);

        void            DropSelectTargetWindow(void);

    public:
        CCosmoDoc(HINSTANCE, PCFrame);
        virtual ~CCosmoDoc(void);

        virtual BOOL     FInit(PDOCUMENTINIT);

        virtual void     Clear(void);

        virtual BOOL     FDirtySet(BOOL);
        virtual BOOL     FDirtyGet(void);

        virtual UINT     ULoad(BOOL, LPTSTR);
        virtual UINT     USave(UINT, LPTSTR);
        virtual void     Rename(LPTSTR);

        virtual void     Undo(void);
        virtual BOOL     FClip(HWND, BOOL);
        virtual HGLOBAL  RenderFormat(UINT);
        virtual BOOL     FRenderMedium(UINT, LPSTGMEDIUM);
        virtual BOOL     FQueryPaste(void);
        virtual BOOL     FPaste(HWND);

        //These were protected.  Now for IOleObject, should be public.
        virtual BOOL     FQueryPasteFromData(LPDATAOBJECT);
        virtual BOOL     FPasteFromData(LPDATAOBJECT);
        LPDATAOBJECT     TransferObjectCreate(BOOL);

        virtual COLORREF ColorSet(UINT, COLORREF);
        virtual COLORREF ColorGet(UINT);

        virtual UINT     LineStyleSet(UINT);
        virtual UINT     LineStyleGet(void);
    };

typedef CCosmoDoc *PCCosmoDoc;

//These color indices wrap the polyline definitions
#define DOCCOLOR_BACKGROUND             POLYLINECOLOR_BACKGROUND
#define DOCCOLOR_LINE                   POLYLINECOLOR_LINE

//Drag-drop interfaces we need in the document
class CDropTarget : public IDropTarget
    {
    protected:
        ULONG               m_cRef;
        PCCosmoDoc          m_pDoc;

        LPDATAOBJECT        m_pIDataObject;     //From DragEnter

    public:
        CDropTarget(PCCosmoDoc);
        ~CDropTarget(void);

        //IDropTarget interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP DragEnter(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
        STDMETHODIMP DragOver(DWORD, POINTL, LPDWORD);
        STDMETHODIMP DragLeave(void);
        STDMETHODIMP Drop(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
    };

typedef CDropTarget *PCDropTarget;

class CDropSource : public IDropSource
    {
    protected:
        ULONG               m_cRef;
        PCCosmoDoc          m_pDoc;

    public:
        CDropSource(PCCosmoDoc);
        ~CDropSource(void);

        //IDropSource interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP QueryContinueDrag(BOOL, DWORD);
        STDMETHODIMP GiveFeedback(DWORD);
    };

typedef CDropSource *PCDropSource;

//Include classes necessary to become an OLE 2.0 server.
#include "cosmole.h"

#endif //_COSMO_H_


DOCUMENT.CPP

/*
 * DOCUMENT.CPP
 * Cosmo Chapter 14
 *
 * Implementation of the CCosmoDoc derivation of CDocument as
 * well as an implementation of CPolylineAdviseSink.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CCosmoDoc::CCosmoDoc
 * CCosmoDoc::~CCosmoDoc
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame of the frame object.
 */

CCosmoDoc::CCosmoDoc(HINSTANCE hInst, PCFrame pFR)
    : CDocument(hInst, pFR)
    {
    m_pPL=NULL;
    m_pPLAdv=NULL;
    m_uPrevSize=SIZE_RESTORED;

    m_pDropTarget=NULL;
    m_fDragSource=FALSE;

    m_cfEmbedSource=RegisterClipboardFormat(CF_EMBEDSOURCE);
    m_cfObjectDescriptor=RegisterClipboardFormat
        (CF_OBJECTDESCRIPTOR);

    m_pFigure=NULL;

    m_pMoniker=NULL;
    m_dwRegROT=0L;

    m_cfLinkSource=RegisterClipboardFormat(CF_LINKSOURCE);
    m_cfLinkSrcDescriptor=RegisterClipboardFormat
        (CF_LINKSRCDESCRIPTOR);
    return;
    }

CCosmoDoc::~CCosmoDoc(void)
    {
    //Make sure the object is saved
    m_pFigure->SendAdvise(OBJECTCODE_SAVEOBJECT);
    m_pFigure->SendAdvise(OBJECTCODE_HIDEWINDOW);
    m_pFigure->SendAdvise(OBJECTCODE_CLOSED);

    //If the document is reg'd as running, revoke and free the moniker
    if (0L!=m_dwRegROT)
        OleStdRevokeAsRunning(&m_dwRegROT);

    if (NULL!=m_pMoniker)
        m_pMoniker->Release();

    if (NULL!=m_pFigure)
        {
        m_pFigure->AddRef();
        CoDisconnectObject(m_pFigure, 0L);
        m_pFigure->Release();   //Starts shutdown if necessary.
        delete m_pFigure;
        }

    //Clean up the allocations we did in FInit
    if (NULL!=m_pPL)
        delete m_pPL;

    if (NULL!=m_pPLAdv)
        delete m_pPLAdv;

    return;
    }

/*
 * CCosmoDoc::FInit
 *
 * Purpose:
 *  Initializes an already created document window.  The client
 *  actually creates the window for us, then passes that here for
 *  further initialization.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CCosmoDoc::FInit(PDOCUMENTINIT pDI)
    {
    RECT        rc;

    //Change the stringtable range to our customization.
    pDI->idsMin=IDS_DOCUMENTMIN;
    pDI->idsMax=IDS_DOCUMENTMAX;

    //Do default initialization
    if (!CDocument::FInit(pDI))
        return FALSE;

    //Add the Polyline stuff we need.
    m_pPLAdv=new CPolylineAdviseSink(this);
    m_pPL   =new CPolyline(m_hInst);

    //Attempt to create our contained Polyline.
    GetClientRect(m_hWnd, &rc);
    InflateRect(&rc, -8, -8);

    if (!m_pPL->FInit(m_hWnd, &rc, WS_CHILD | WS_VISIBLE
        , ID_POLYLINE, m_pPLAdv))
        return FALSE;

    m_pDropTarget=new CDropTarget(this);

    if (NULL!=m_pDropTarget)
        {
        m_pDropTarget->AddRef();
        CoLockObjectExternal(m_pDropTarget, TRUE, FALSE);
        RegisterDragDrop(m_hWnd, m_pDropTarget);
        }

    m_pFigure=new CFigure(ObjectDestroyed, this);

    if (NULL==m_pFigure)
        return FALSE;

    //We created an object, so count it.
    g_cObj++;

    if (!m_pFigure->FInit())
        return FALSE;

    return TRUE;
    }

/*
 * CCosmoDoc::FMessageHook
 *
 * Purpose:
 *  Processes WM_SIZE for the document so we can resize
 *  the Polyline.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CCosmoDoc::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    UINT        dx, dy;
    RECT        rc;

    *pLRes=0;

    if (WM_SIZE==iMsg)
        {
        //Don't effect the Polyline size to or from minimized state.
        if (SIZE_MINIMIZED!=wParam && SIZE_MINIMIZED !=m_uPrevSize)
            {
            //When we change size, resize any Polyline we hold.
            dx=LOWORD(lParam);
            dy=HIWORD(lParam);

            /*
             * If we are getting WM_SIZE in response to a Polyline
             * notification, then don't resize the Polyline window
             * again.
             */
            if (!m_fNoSize && NULL!=m_pPL)
                {
                //Resize the polyline to fit the new client
                SetRect(&rc, 8, 8, dx-8, dy-8);
                m_pPL->RectSet(&rc, FALSE);

                /*
                 * We consider sizing something that makes the file
                 * dirty, but not until we've finished the create
                 * process, which is why we set fNoDirty to FALSE
                 * in WM_CREATE since we get a WM_SIZE on the first
                 * creation.
                 */
                if (!m_fNoDirty)
                    FDirtySet(TRUE);

                SetRect(&rc, 0, 0, dx, dy);

                if (NULL!=m_pAdv)
                    m_pAdv->OnSizeChange(this, &rc);

                m_fNoDirty=FALSE;
                }
            }

        m_uPrevSize=wParam;
        }

    if (WM_LBUTTONDOWN==iMsg)
        {
        LPDROPSOURCE    pIDropSource;
        LPDATAOBJECT    pIDataObject;
        HRESULT         hr;
        SCODE           sc;
        DWORD           dwEffect;

        /*
         * The document has an 8 pixel border around the polyline
         * window where we'll see mouse clicks.  A left mouse button
         * click here means the start of a drag-drop operation.
         *
         * Since this is a modal operation, this IDropSource
         * is entirely local.
         */

        pIDropSource=new CDropSource(this);

        if (NULL==pIDropSource)
            return FALSE;

        pIDropSource->AddRef();
        m_fDragSource=TRUE;

        //Go get the data and start the ball rolling.
        pIDataObject=TransferObjectCreate(FALSE);

        if (NULL!=pIDataObject)
            {
            //Add DROPEFFECT_LINK now that we source links
            hr=DoDragDrop(pIDataObject, pIDropSource
                , DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK
                , &dwEffect);

            pIDataObject->Release();
            sc=GetScode(hr);
            }
        else
            sc=E_FAIL;

        /*
         * When we return from DoDragDrop, either cancel or drop.
         * First toss the IDropSource we have here, then bail out
         * on cancel, and possibly clear our data on a move drop.
         */

        pIDropSource->Release();

        /*
         * If dropped on the same document (determined using
         * this flag, then dwEffect will be DROPEFFECT_NONE (see
         * IDropTarget::Drop in IDROPTGT.CPP).  In any case,
         * reset this since the operation is done.
         */

        m_fDragSource=FALSE;

        if (DRAGDROP_S_DROP==sc && DROPEFFECT_MOVE==dwEffect)
            {
            m_pPL->New();
            FDirtySet(TRUE);
            }

        //On a canceled drop or a copy we don't do anything else
        return TRUE;
        }

    if (WM_DESTROY==iMsg)
        {
        /*
         * We have to revoke the drop target here because the window
         * will be destroyed and the property forcefully removed
         * before we could do this in the destructor.
         */
        if (NULL!=m_pDropTarget)
            {
            RevokeDragDrop(m_hWnd);
            CoLockObjectExternal(m_pDropTarget, FALSE, TRUE);
            m_pDropTarget->Release();
            }

        return FALSE;
        }

    /*
     * We return FALSE even on WM_SIZE so we can let the default
     * procedure handle maximized MDI child windows appropriately.
     */
    return FALSE;
    }

/*
 * CCosmoDoc::Clear
 *
 * Purpose:
 *  Sets all contents in the document back to defaults with
 *  no filename.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Clear(void)
    {
    //Completely reset the polyline
    m_pPL->New();

    CDocument::Clear();
    m_lVer=0;
    return;
    }

/*
 * CCosmoDoc::FDirtySet
 *
 * Purpose:
 *  Sets or clears the document 'dirty' flag returning the previous
 *  state of that same flag.  We override this in Cosmo server to
 *  send the OnDataChange notification as necessary.
 *
 * Parameters:
 *  fDirty          BOOL indicating the new contents of the dirty
 *                  flag.
 *
 * Return Value:
 *  BOOL            Previous value of the dirty flag.
 */

BOOL CCosmoDoc::FDirtySet(BOOL fDirty)
    {
    BOOL        fRet;

    fRet=CDocument::FDirtySet(fDirty);
    m_pFigure->SendAdvise(OBJECTCODE_DATACHANGED);

    return fRet;
    }

/*
 * CCosmoDoc::FDirtyGet
 *
 * Purpose:
 *  Override of the normal FDirtyGet such that we never return dirty
 *  for an embedded object we're serving since updates constantly
 *  happen and since the object will be saved on closure.  This then
 *  prevents any message boxes asking the user to save.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the document is dirty, FALSE otherwise.
 */

BOOL CCosmoDoc::FDirtyGet(void)
    {
    if (m_pFigure->FIsEmbedded())
        return FALSE;

    return m_fDirty;
    }

/*
 * CCosmoDoc::ULoad
 *
 * Purpose:
 *  Loads a given document without any user interface overwriting
 *  the previous contents of the Polyline window.  We do this by
 *  opening the file and telling the Polyline to load itself from
 *  that file.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load, NULL if the file
 *                  is new and untitled.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_...
 */

UINT CCosmoDoc::ULoad(BOOL fChangeFile, LPTSTR pszFile)
    {
    HRESULT         hr;
    LPSTORAGE       pIStorage;

    if (NULL==pszFile)
        {
        //For a new untitled document, just rename ourselves.
        Rename(NULL);
        m_lVer=VERSIONCURRENT;
        return DOCERR_NONE;
        }

    /*
     * If not a Compound File, open the file using STGM_CONVERT in
     * transacted mode to see old files as a storage with one stream
     * called "CONTENTS" (which is conveniently the name we use
     * in the new files).  We must use STGM_TRANSACTED here or else
     * the old file will be immediately converted on disk:  we only
     * want a converted image in memory from which to read.  In
     * addition, note that we need STGM_READWRITE as well since
     * conversion is inherently a write operation.
     */

    pIStorage=NULL;
    OLECHAR pwcsFile[MAX_PATH];
#if defined(WIN32) && !defined(UNICODE)
    mbstowcs(pwcsFile, pszFile, MAX_PATH);
#else
    pwcsFile=pszFile;
#endif
    if (NOERROR!=StgIsStorageFile(pwcsFile))
        {
        hr=StgCreateDocfile(pwcsFile,STGM_TRANSACTED | STGM_READWRITE
            | STGM_CONVERT | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

        if (FAILED(hr))
            {
            //If denied write access, try to load the old way
            if (STG_E_ACCESSDENIED==GetScode(hr))
                m_lVer=m_pPL->ReadFromFile(pszFile);
            else
                return DOCERR_COULDNOTOPEN;
            }
        }
    else
        {
        hr=StgOpenStorage(pwcsFile, NULL, STGM_DIRECT | STGM_READ
            | STGM_SHARE_EXCLUSIVE, NULL, 0, &pIStorage);

        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;
        }

    if (NULL!=pIStorage)
        {
        m_lVer=m_pPL->ReadFromStorage(pIStorage);
        pIStorage->Release();
        }

    if (POLYLINE_E_READFAILURE==m_lVer)
        return DOCERR_READFAILURE;

    if (POLYLINE_E_UNSUPPORTEDVERSION==m_lVer)
        return DOCERR_UNSUPPORTEDVERSION;

    if (fChangeFile)
        Rename(pszFile);

    //Importing a file makes things dirty
    FDirtySet(!fChangeFile);

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::USave
 *
 * Purpose:
 *  Writes the file to a known filename, requiring that the user has
 *  previously used FileOpen or FileSaveAs to provide a filename.
 *
 * Parameters:
 *  uType           UINT indicating the type of file the user
 *                  requested to save in the File Save As dialog.
 *  pszFile         LPTSTR under which to save.  If NULL, use the
 *                  current name.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_...
 */

UINT CCosmoDoc::USave(UINT uType, LPTSTR pszFile)
    {
    LONG        lVer, lRet;
    UINT        uTemp;
    BOOL        fRename=TRUE;
    HRESULT     hr;
    LPSTORAGE   pIStorage;
    BOOL        fEmbedding;

    fEmbedding=m_pFigure->FIsEmbedded();

    if (NULL==pszFile)
        {
        fRename=FALSE;
        pszFile=m_szFile;
        }

    /*
     * Type 1 is the current version, type 2 is version 1.0 of the
     * Polyline so we use this to send the right version to
     * CPolyline::WriteToFile.
     */

    switch (uType)
        {
        case 0:         //From Save, use loaded version.
            lVer=m_lVer;
            break;

        case 1:
            lVer=VERSIONCURRENT;
            break;

        case 2:
            lVer=MAKELONG(0, 1);    //1.0
            break;

        default:
            return DOCERR_UNSUPPORTEDVERSION;
        }

    /*
     * If the version the user wants to save is different from the
     * version that we loaded and m_lVer is not zero (new doc),
     * then inform the user of the version change and verify.
     */

    //For embedding, this is Save Copy As, so don't ask about versions.
    if (0!=m_lVer && m_lVer!=lVer && !fEmbedding)
        {
        TCHAR       szMsg[128];

        wsprintf(szMsg, PSZ(IDS_VERSIONCHANGE)
            , (UINT)HIWORD(m_lVer), (UINT)LOWORD(m_lVer)
            , (UINT)HIWORD(lVer), (UINT)LOWORD(lVer));

        uTemp=MessageBox(m_hWnd, szMsg, PSZ(IDS_DOCUMENTCAPTION)
            , MB_YESNOCANCEL);

        if (IDCANCEL==uTemp)
            return DOCERR_CANCELLED;

        //If the user won't upgrade, revert to loaded version.
        if (IDNO==uTemp)
            lVer=m_lVer;
        }

    /*
     * For 1.0 files, still use the old code.  For new files, use
     * storages instead
     */
    if (lVer==MAKELONG(0, 1))
        lRet=m_pPL->WriteToFile(pszFile, lVer);
    else
        {
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsFile[MAX_PATH];
        mbstowcs(pwcsFile, pszFile, MAX_PATH);
        hr=StgCreateDocfile(pwcsFile, STGM_DIRECT | STGM_READWRITE
            | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);
#else
        hr=StgCreateDocfile(pszFile, STGM_DIRECT | STGM_READWRITE
            | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);
#endif
        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;

        //Mark this as one of our class
        WriteClassStg(pIStorage, CLSID_Cosmo2Figure);

        //Write user-readable class information
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsTemp[256];
        mbstowcs(pwcsTemp, PSZ(IDS_CLIPBOARDFORMAT), 256);
        WriteFmtUserTypeStg(pIStorage, m_cf, pwcsTemp);
#else
        WriteFmtUserTypeStg(pIStorage, m_cf
            , PSZ(IDS_CLIPBOARDFORMAT));
#endif
        lRet=m_pPL->WriteToStorage(pIStorage, lVer);
        pIStorage->Release();
        }

    if (POLYLINE_E_NONE!=lRet)
        return DOCERR_WRITEFAILURE;

    //Saving makes us clean, but this doesn't apply to embedding.
    if (!fEmbedding)
        FDirtySet(FALSE);

    //Update the known version of this document.
    m_lVer=lVer;

    /*
     * If we're embedding, this is Save Copy As, so no rename.
     * Note that we also don't care about having been set to clean
     * since we're always 'clean' as an embedded object from
     * the user's perspective.
     */
    if (fRename && !fEmbedding)
        Rename(pszFile);

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::Rename
 *
 * Purpose:
 *  Overrides the normal rename to manage a moniker for the open
 *  file.  We maintain no moniker for untitled documents, and
 *  therefore do not allow linking to such documents.
 *
 * Parameters:
 *  pszFile         LPTSTR to the new filename.
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Rename(LPTSTR pszFile)
    {
    LPMONIKER   pmk;

    //We don't need to change the base class, just augment...
    CDocument::Rename(pszFile);

    /*
     * Get rid of the old moniker.  The OldStd function
     * sets the key to zero.
     */
    if (0L!=m_dwRegROT)
        OleStdRevokeAsRunning(&m_dwRegROT);

    if (NULL!=m_pMoniker)
        {
        m_pMoniker->Release();
        m_pMoniker=NULL;
        }

    if (NULL!=pszFile)
        {
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsFile[MAX_PATH];
        mbstowcs(pwcsFile, pszFile, MAX_PATH);
        CreateFileMoniker(pwcsFile, &pmk);
#else
        CreateFileMoniker(pszFile, &pmk);
#endif

        if (NULL!=pmk)
            {
            m_pMoniker=pmk;     //pmk AddRef'd in CreateFileMoniker
            OleStdRegisterAsRunning(m_pFigure, m_pMoniker
                , &m_dwRegROT);

            m_pFigure->SendAdvise(OBJECTCODE_RENAMED);
            }
        }

    return;
    }

/*
 * CCosmoDoc::Undo
 *
 * Purpose:
 *  Reverses a previous action.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Undo(void)
    {
    m_pPL->Undo();
    return;
    }

/*
 * CCosmoDoc::FClip
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard, optionally implementing Cut by deleting the
 *  data in the current window after rendering.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *  fCut            BOOL indicating cut (TRUE) or copy (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::FClip(HWND hWndFrame, BOOL fCut)
    {
    BOOL            fRet=TRUE;
    LPDATAOBJECT    pIDataObject;

    pIDataObject=TransferObjectCreate(fCut);

    if (NULL==pIDataObject)
        return FALSE;

    fRet=SUCCEEDED(OleSetClipboard(pIDataObject));
    pIDataObject->Release();

    //Delete our current data if copying succeeded.
    if (fRet && fCut)
        {
        m_pPL->New();
        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CCosmoDoc::RenderFormat
 *
 * Purpose:
 *  Renders a specific clipboard format into global memory.
 *
 * Parameters:
 *  cf              UINT format to render.
 *
 * Return Value:
 *  HGLOBAL         Global memory handle containing the data.
 */

HGLOBAL CCosmoDoc::RenderFormat(UINT cf)
    {
    HGLOBAL     hMem;

    if (cf==m_cf)
        {
        m_pPL->DataGetMem(VERSIONCURRENT, &hMem);
        return hMem;
        }

    switch (cf)
        {
        case CF_METAFILEPICT:
            return m_pPL->RenderMetafilePict();

        case CF_BITMAP:
            return (HGLOBAL)m_pPL->RenderBitmap();
        }

    return NULL;
    }

/*
 * CCosmoDoc::FRenderMedium
 *
 * Purpose:
 *  Like RenderFormat, this function creates a specific data format
 *  based on the cf parameter.  Unlike RenderFormat, we store the
 *  result in a STGMEDIUM in case it has a medium other than
 *  TYMED_HGLOBAL.  For conveniece we'll centralize all compound
 *  document formats here, hGlobal or not.
 *
 * Parameters:
 *  cf              UINT clipboard format of interest.
 *  pSTM            LSTGMEDIUM to fill.  We only fill the union
 *                  and tymed.
 *
 * Return Value:
 *  BOOL            TRUE if we could render the format,
 *                  FALSE otherwise.
 */

BOOL CCosmoDoc::FRenderMedium(UINT cf, LPSTGMEDIUM pSTM)
    {
    if (NULL==pSTM)
        return FALSE;

    if (cf==m_cfEmbedSource)
        {
        /*
         * Embed Source data is an IStorage containing the native
         * data (same as Embedded Object).  Since our data is small,
         * it makes the most sense to create an IStorage in memory
         * and put transfer that instead of a disk-based IStorage.
         */

        pSTM->pstg=OleStdCreateStorageOnHGlobal(NULL, TRUE
            , STGM_DIRECT | STGM_READWRITE | STGM_SHARE_EXCLUSIVE);

        if (NULL==pSTM->pstg)
            return FALSE;

        //Now save the data to the storage.
        WriteClassStg(pSTM->pstg, CLSID_Cosmo2Figure);
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsTemp[256];
        mbstowcs(pwcsTemp, PSZ(IDS_CLIPBOARDFORMAT), 256);
        WriteFmtUserTypeStg(pSTM->pstg, m_cf, pwcsTemp);
#else
        WriteFmtUserTypeStg(pSTM->pstg, m_cf
            , PSZ(IDS_CLIPBOARDFORMAT));
#endif

        if (POLYLINE_E_NONE!=m_pPL->WriteToStorage(pSTM->pstg
            , VERSIONCURRENT))
            {
            /*
             * When someone releases the IStorage, STORAGE.DLL will
             * release the ILockBytes which, having fDeleteOnRelease
             * TRUE (second parameter) will release the memory.
             * That's why we don't have STGM_DELETEONRELEASE on the
             * IStorage.
             */
            pSTM->pstg->Release();
            return FALSE;
            }

        pSTM->tymed=TYMED_ISTORAGE;
        return TRUE;
        }

    /*
     * CF_OBJECTDESCRIPTOR and CF_LINKSRCDESCRIPTOR are the same
     * formats, but only copy link source if we have a moniker.
     */
    if (cf==m_cfLinkSrcDescriptor && NULL==m_pMoniker)
        return FALSE;

    if (cf==m_cfObjectDescriptor || cf==m_cfLinkSrcDescriptor)
        {
        SIZEL   szl, szlT;
        POINTL  ptl;
        RECT    rc;
        LPOLESTR  psz=NULL;

        m_pPL->SizeGet(&rc);
        SETSIZEL(szlT, rc.right, rc.bottom);
        XformSizeInPixelsToHimetric(NULL, &szlT, &szl);

        SETPOINTL(ptl, 0, 0);

        //Include the moniker display name now, if we have one.
        if (m_pMoniker)
            {
            LPBC    pbc;

            CreateBindCtx(0, &pbc);
            m_pMoniker->GetDisplayName(pbc, NULL, &psz);
            pbc->Release();
            }
#if defined(WIN32) && !defined(UNICODE)
        OLECHAR pwcsTemp[256];
        mbstowcs(pwcsTemp, PSZ(IDS_OBJECTDESCRIPTION), 256);
        pSTM->hGlobal=OleStdGetObjectDescriptorData
            (CLSID_Cosmo2Figure, DVASPECT_CONTENT, szl, ptl
            , OLEMISC_RECOMPOSEONRESIZE, pwcsTemp
            , psz);
#else
        pSTM->hGlobal=OleStdGetObjectDescriptorData
            (CLSID_Cosmo2Figure, DVASPECT_CONTENT, szl, ptl
            , OLEMISC_RECOMPOSEONRESIZE, PSZ(IDS_OBJECTDESCRIPTION)
            , psz);
#endif

        pSTM->tymed=TYMED_HGLOBAL;
        return (NULL!=pSTM->hGlobal);
        }

    if (cf==m_cfLinkSource)
        {
        if (NULL!=m_pMoniker)
            {
            FORMATETC   fe;
            HRESULT     hr;

            pSTM->tymed=TYMED_NULL;
            SETDefFormatEtc(fe, cf, TYMED_ISTREAM);
            hr=OleStdGetLinkSourceData(m_pMoniker
                , (LPCLSID)&CLSID_Cosmo2Figure, &fe, pSTM);

            return SUCCEEDED(hr);
            }
        }

    return FALSE;
    }

/*
 * CCosmoDoc::FQueryPaste
 *
 * Purpose:
 *  Determines if we can paste data from the clipboard.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CCosmoDoc::FQueryPaste(void)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    fRet=FQueryPasteFromData(pIDataObject);
    pIDataObject->Release();
    return fRet;
    }

/*
 * CCosmoDoc::FQueryPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Determines if we can paste data from a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which we might want to paste.
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CCosmoDoc::FQueryPasteFromData(LPDATAOBJECT pIDataObject)
    {
    FORMATETC       fe;

    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    return (NOERROR==pIDataObject->QueryGetData(&fe));
    }

/*
 * CCosmoDoc::FPaste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  to the current figure in the editor window.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::FPaste(HWND hWndFrame)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    fRet=FPasteFromData(pIDataObject);
    pIDataObject->Release();

    return fRet;
    }

/*
 * CCosmoDoc::FPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Retrieves the private data format from a data object and sets
 *  it to the current figure in the editor window.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::FPasteFromData(LPDATAOBJECT pIDataObject)
    {
    FORMATETC       fe;
    STGMEDIUM       stm;
    BOOL            fRet;

    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    fRet=SUCCEEDED(pIDataObject->GetData(&fe, &stm));

    if (fRet && NULL!=stm.hGlobal)
        {
        m_pPL->DataSetMem(stm.hGlobal, FALSE, FALSE, TRUE);
        ReleaseStgMedium(&stm);
        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CCosmoDoc::TransferObjectCreate
 * (Protected)
 *
 * Purpose:
 *  Creates a DataTransferObject and stuffs the current Polyline
 *  data into it, used for both clipboard and drag-drop operations.
 *
 * Parameters:
 *  fCut            BOOL TRUE if we're cutting, FALSE if we're
 *                  copying.
 *
 * Return Value:
 *  LPDATAOBJECT    Pointer to the object created, NULL on failure
 */

LPDATAOBJECT CCosmoDoc::TransferObjectCreate(BOOL fCut)
    {
    UINT            i;
    HRESULT         hr;
    STGMEDIUM       stm;
    FORMATETC       fe;
    LPDATAOBJECT    pIDataObject=NULL;
    const UINT      cFormats=7;
    static UINT     rgcf[7]={0, 0, 0, CF_METAFILEPICT, CF_BITMAP, 0, 0};
    static DWORD    rgtm[7]={TYMED_HGLOBAL, TYMED_ISTORAGE, TYMED_HGLOBAL
        , TYMED_MFPICT, TYMED_GDI, TYMED_ISTREAM, TYMED_HGLOBAL};

    hr=CoCreateInstance(CLSID_DataTransferObject, NULL
        , CLSCTX_INPROC_SERVER, IID_IDataObject
        , (PPVOID)&pIDataObject);

    if (FAILED(hr))
        return NULL;

    rgcf[0]=m_cf;
    rgcf[1]=m_cfEmbedSource;
    rgcf[2]=m_cfObjectDescriptor;
    //Don't include link stuff for cutting (0 format always fails)
    if (!fCut)
        {
        rgcf[5]=m_cfLinkSource;
        rgcf[6]=m_cfLinkSrcDescriptor;
        }

    for (i=0; i < cFormats; i++)
        {
        /*
         * RenderFormat handles memory handles, but for compound doc
         * formats we need something more.  So if RenderFormat fails
         * (which it will for i=1, try our latest addition which

* writes to a different field in the STGMEDIUM.
         */
        stm.hGlobal=RenderFormat(rgcf[i]);

        if (NULL==stm.hGlobal)
            {
            if (!FRenderMedium(rgcf[i], &stm))
                continue;
            }

        stm.tymed=rgtm[i];
        stm.pUnkForRelease=NULL;
        SETDefFormatEtc(fe, rgcf[i], rgtm[i]);
        pIDataObject->SetData(&fe, &stm, TRUE);
        }

    return pIDataObject;    //Caller now responsible
    }

/*
 * CCosmoDoc::DropSelectTargetWindow
 * (Protected)
 *
 * Purpose:
 *  Creates a thin inverted frame around a window that we use to
 *  show the window as a drop target.  This is a toggle function:
 *  It uses XOR to create the effect so it must be called twice to
 *  leave the window as it was.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::DropSelectTargetWindow(void)
    {
    HDC         hDC;
    RECT        rc;
    UINT        dd=3;
    HWND        hWnd;

    hWnd=m_pPL->Window();
    hDC=GetWindowDC(hWnd);
    GetClientRect(hWnd, &rc);

    //Frame this window with inverted pixels

    //Top
    PatBlt(hDC, rc.left, rc.top, rc.right-rc.left, dd, DSTINVERT);

    //Bottom
    PatBlt(hDC, rc.left, rc.bottom-dd, rc.right-rc.left, dd
        , DSTINVERT);

    //Left excluding regions already affected by top and bottom
    PatBlt(hDC, rc.left, rc.top+dd, dd, rc.bottom-rc.top-(2*dd)
        , DSTINVERT);

    //Right excluding regions already affected by top and bottom
    PatBlt(hDC, rc.right-dd, rc.top+dd, dd, rc.bottom-rc.top-(2*dd)
        , DSTINVERT);

    ReleaseDC(hWnd, hDC);
    return;
    }

/*
 * CCosmoDoc::ColorSet
 *
 * Purpose:
 *  Changes a color used in our contained Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color.
 *
 * Return Value:
 *  COLORREF        Previous color for the given index.
 */

COLORREF CCosmoDoc::ColorSet(UINT iColor, COLORREF cr)
    {
    return m_pPL->ColorSet(iColor, cr);
    }

/*
 * CCosmoDoc::ColorGet
 *
 * Purpose:
 *  Retrieves a color currently in use in the Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to retrieve.
 *
 * Return Value:
 *  COLORREF        Current color for the given index.
 */

COLORREF CCosmoDoc::ColorGet(UINT iColor)
    {
    return m_pPL->ColorGet(iColor);
    }

/*
 * CCosmoDoc::LineStyleSet
 *
 * Purpose:
 *  Changes the line style currently used in the Polyline
 *
 * Parameters:
 *  iStyle          UINT index of the new line style to use.
 *
 * Return Value:
 *  UINT            Previous line style.
 */

UINT CCosmoDoc::LineStyleSet(UINT iStyle)
    {
    return m_pPL->LineStyleSet(iStyle);
    }

/*
 * CCosmoDoc::LineStyleGet
 *
 * Purpose:
 *  Retrieves the line style currently used in the Polyline
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Current line style.
 */

UINT CCosmoDoc::LineStyleGet(void)
    {
    return m_pPL->LineStyleGet();
    }

/*
 * CPolylineAdviseSink::CPolylineAdviseSink
 * CPolylineAdviseSink::~CPolylineAdviseSink
 *
 * Constructor Parameters:
 *  pv              LPVOID to store in this object
 */

CPolylineAdviseSink::CPolylineAdviseSink(LPVOID pv)
    {
    m_pv=pv;
    return;
    }

CPolylineAdviseSink::~CPolylineAdviseSink(void)
    {
    return;
    }

/*
 * CPolylineAdviseSink::OnPointChange
 *
 * Purpose:
 *  Informs the document that the polyline added or removed a point.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnPointChange(void)
    {
    PCDocument      pDoc=(PCDocument)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnSizeChange
 *
 * Purpose:
 *  Informs the document that the polyline changed size.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnSizeChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;
    RECT            rc;
    DWORD           dwStyle;

    /*
     * Polyline window is informing us that it changed size in
     * response to setting it's data.  Therefore we have to
     * size ourselves accordingly but without moving the screen
     * position of the polyline window.
     */

    pDoc->m_fNoSize=TRUE;

    //Set the document window size.
    GetWindowRect(pDoc->m_pPL->Window(), &rc);
    InflateRect(&rc, 8, 8);

    //Adjust for a window sans menu
    dwStyle=GetWindowLong(pDoc->m_hWnd, GWL_STYLE);
    AdjustWindowRect(&rc, dwStyle, FALSE);

    SetWindowPos(pDoc->m_hWnd, NULL, 0, 0, rc.right-rc.left
        , rc.bottom-rc.top, SWP_NOMOVE | SWP_NOZORDER);

    if (NULL!=pDoc->m_pAdv)
        pDoc->m_pAdv->OnSizeChange(pDoc, &rc);

    pDoc->m_fNoSize=FALSE;
    pDoc->FDirtySet(TRUE);

    return;
    }

/*
 * CPolylineAdviseSink::OnDataChange
 *
 * Purpose:
 *  Informs the document that the polyline data changed.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnDataChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    if (NULL!=pDoc->m_pAdv)
        pDoc->m_pAdv->OnDataChange(pDoc);

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnColorChange
 *
 * Purpose:
 *  Informs the document that the polyline data changed a color.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnColorChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnLineStyleChange
 *
 * Purpose:
 *  Informs the document that the polyline changed its line style.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnLineStyleChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }


ICLASSF.CPP

/*
 * ICLASSF.CPP
 * Cosmo Chapter 14
 *
 * Implementation of an IClassFactory interface for Cosmo.
 *
 * Copyright (c)1993-1997 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CFigureClassFactory::CFigureClassFactory
 * CFigureClassFactory::~CFigureClassFactory
 *
 * Constructor Parameters:
 *  pFR             PCCosmoFrame that can create documents.
 */

CFigureClassFactory::CFigureClassFactory(PCCosmoFrame pFR)
    {
    m_cRef=0L;
    m_pFR=pFR;
    m_fCreated=FALSE;
    return;
    }

CFigureClassFactory::~CFigureClassFactory(void)
    {
    return;
    }

/*
 * CFigureClassFactory::QueryInterface
 * CFigureClassFactory::AddRef
 * CFigureClassFactory::Release
 */

STDMETHODIMP CFigureClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return E_NOINTERFACE;
    }

STDMETHODIMP_(ULONG) CFigureClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CFigureClassFactory::Release(void)
    {
    ULONG           cRefT;

    cRefT=--m_cRef;

    if (0L==m_cRef)
        delete this;

    return cRefT;
    }

/*
 * CFigureClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a Figure object that supports embedding.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we are
 *                  being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise contains
 *                  E_NOINTERFACE if we cannot support the requested
 *                  interface.
 */

STDMETHODIMP CFigureClassFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCCosmoDoc          pDoc;
    HRESULT             hr;

    *ppvObj=NULL;

    //Great idea to protect yourself from multiple creates here.
    if (m_fCreated)
        return E_UNEXPECTED;

    m_fCreated=TRUE;
    hr=E_OUTOFMEMORY;

    //We don't support aggregation
    if (NULL!=pUnkOuter)
        return CLASS_E_NOAGGREGATION;

    //Try creating a new document, which creates the object.
    pDoc=(PCCosmoDoc)m_pFR->m_pCL->NewDocument(TRUE, m_pFR->m_pAdv);

    if (NULL==pDoc)
        {
        //This will cause shutdown; object count will go to zero.
        g_cObj++;
        ObjectDestroyed();
        return hr;
        }

    //Insure the document is untitled; get the requested interface.
    pDoc->ULoad(TRUE, NULL);
    pDoc->m_pFigure->FrameSet(m_pFR);
    hr=pDoc->m_pFigure->QueryInterface(riid, ppvObj);

    //Closing the document will destroy the objec, cause shutdown.
    if (FAILED(hr))
        {
        m_pFR->m_pCL->CloseDocument(pDoc);
        return hr;
        }

    return NOERROR;
    }

/*
 * CFigureClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the serving
 *  IClassFactory object.  When the number of locks goes to
 *  zero and the number of objects is zero, we shut down the
 *  application.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CFigureClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        {
        g_cLock--;

        /*
         * To centralize shutdown, we'll artificially increase the
         * object count here and call ObjectDestroyed, which will
         * decrement the count, see that there are no objects or
         * locks, then shut down.
         */
        g_cObj++;
        ObjectDestroyed();
        }

    return NOERROR;
    }


ICOSMO.H

/* This header file machine-generated by mktyplib.exe */
/* Interface to type library: CosmoTypeLibrary */

#ifndef _CosmoTypeLibrary_H_
#define _CosmoTypeLibrary_H_

DEFINE_GUID(LIBID_CosmoTypeLibrary,0x00021165,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

DEFINE_GUID(IID_ICosmoFigure,0x00021161,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

/* Definition of interface: ICosmoFigure */
DECLARE_INTERFACE_(ICosmoFigure, IUnknown)
{
#ifndef NO_BASEINTERFACE_FUNCS

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;
#endif

    /* ICosmoFigure methods */
    STDMETHOD_(IDispatch *, get_Application)(THIS) PURE;
    STDMETHOD_(IDispatch *, get_Parent)(THIS) PURE;
    STDMETHOD_(BSTR, get_FullName)(THIS) PURE;
    STDMETHOD_(BSTR, get_Name)(THIS) PURE;
    STDMETHOD_(BSTR, get_Path)(THIS) PURE;
    STDMETHOD_(VARIANT_BOOL, get_Saved)(THIS) PURE;
    STDMETHOD_(short, get_NumberOfPoints)(THIS) PURE;
    STDMETHOD_(long, get_BackColor)(THIS) PURE;
    STDMETHOD_(void, put_BackColor)(THIS_ long BackColor) PURE;
    STDMETHOD_(long, get_LineColor)(THIS) PURE;
    STDMETHOD_(void, put_LineColor)(THIS_ long LineColor) PURE;
    STDMETHOD_(short, get_LineStyle)(THIS) PURE;
    STDMETHOD_(void, put_LineStyle)(THIS_ short Style) PURE;
    STDMETHOD_(long, get_Left)(THIS) PURE;
    STDMETHOD_(void, put_Left)(THIS_ long Left) PURE;
    STDMETHOD_(long, get_Top)(THIS) PURE;
    STDMETHOD_(void, put_Top)(THIS_ long Top) PURE;
    STDMETHOD_(long, get_Width)(THIS) PURE;
    STDMETHOD_(void, put_Width)(THIS_ long Width) PURE;
    STDMETHOD_(long, get_Height)(THIS) PURE;
    STDMETHOD_(void, put_Height)(THIS_ long Height) PURE;
    STDMETHOD_(VARIANT_BOOL, get_Visible)(THIS) PURE;
    STDMETHOD_(void, put_Visible)(THIS_ VARIANT_BOOL Visible) PURE;
    STDMETHOD_(void, Activate)(THIS) PURE;
    STDMETHOD_(void, Close)(THIS_ VARIANT SaveChanges, VARIANT Path) PURE;
    STDMETHOD_(void, RevertToSaved)(THIS) PURE;
    STDMETHOD_(void, Save)(THIS) PURE;
    STDMETHOD_(void, SaveAs)(THIS_ BSTR Path) PURE;
    STDMETHOD_(void, Import)(THIS_ BSTR ImportPath) PURE;
    STDMETHOD_(void, Copy)(THIS) PURE;
    STDMETHOD_(void, Cut)(THIS) PURE;
    STDMETHOD_(void, Paste)(THIS) PURE;
    STDMETHOD_(VARIANT_BOOL, AddPoint)(THIS_ short x, short y) PURE;
    STDMETHOD_(void, RemovePoint)(THIS) PURE;
};

DEFINE_GUID(IID_ICosmoFigures,0x00021160,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

/* Definition of interface: ICosmoFigures */
DECLARE_INTERFACE_(ICosmoFigures, IUnknown)
{
#ifndef NO_BASEINTERFACE_FUNCS

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;
#endif

    /* ICosmoFigures methods */
    STDMETHOD_(IDispatch *, get_Application)(THIS) PURE;
    STDMETHOD_(IDispatch *, get_Parent)(THIS) PURE;
    STDMETHOD_(long, get_Count)(THIS) PURE;
    STDMETHOD_(IDispatch *, Item)(THIS_ VARIANT index) PURE;
    STDMETHOD_(IDispatch *, Add)(THIS) PURE;
    STDMETHOD_(IDispatch *, Open)(THIS_ BSTR Path) PURE;
    STDMETHOD_(void, Close)(THIS) PURE;
    STDMETHOD_(IUnknown *, _NewEnum)(THIS) PURE;
};

DEFINE_GUID(IID_ICosmoApplication,0x0002115F,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

/* Definition of interface: ICosmoApplication */
DECLARE_INTERFACE_(ICosmoApplication, IUnknown)
{
#ifndef NO_BASEINTERFACE_FUNCS

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;
#endif

    /* ICosmoApplication methods */
    STDMETHOD_(IDispatch *, get_Application)(THIS) PURE;
    STDMETHOD_(IDispatch *, get_ActiveFigure)(THIS) PURE;
    STDMETHOD_(BSTR, get_Caption)(THIS) PURE;
    STDMETHOD_(IDispatch *, get_Figures)(THIS) PURE;
    STDMETHOD_(BSTR, get_FullName)(THIS) PURE;
    STDMETHOD_(BSTR, get_Name)(THIS) PURE;
    STDMETHOD_(BSTR, get_Path)(THIS) PURE;
    STDMETHOD_(long, get_Left)(THIS) PURE;
    STDMETHOD_(void, put_Left)(THIS_ long Left) PURE;
    STDMETHOD_(long, get_Top)(THIS) PURE;
    STDMETHOD_(void, put_Top)(THIS_ long Top) PURE;
    STDMETHOD_(long, get_Width)(THIS) PURE;
    STDMETHOD_(void, put_Width)(THIS_ long Width) PURE;
    STDMETHOD_(long, get_Height)(THIS) PURE;
    STDMETHOD_(void, put_Height)(THIS_ long Height) PURE;
    STDMETHOD_(VARIANT_BOOL, get_Visible)(THIS) PURE;
    STDMETHOD_(void, put_Visible)(THIS_ VARIANT_BOOL Visible) PURE;
    STDMETHOD_(BSTR, get_StatusBar)(THIS) PURE;
    STDMETHOD_(void, put_StatusBar)(THIS_ BSTR Text) PURE;
    STDMETHOD_(void, Quit)(THIS) PURE;
};

DEFINE_GUID(DIID_DICosmoFigure,0x00021164,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

/* Definition of dispatch interface: DICosmoFigure */
DECLARE_INTERFACE_(DICosmoFigure, IDispatch)
{
#ifndef NO_BASEINTERFACE_FUNCS

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(THIS_ UINT FAR* pctinfo) PURE;

    STDMETHOD(GetTypeInfo)(
      THIS_
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo) PURE;

    STDMETHOD(GetIDsOfNames)(
      THIS_
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid) PURE;

    STDMETHOD(Invoke)(
      THIS_
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr) PURE;
#endif

/* Capable of dispatching all the methods of interface ICosmoFigure */
};

DEFINE_GUID(DIID_DICosmoFigures,0x00021163,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

/* Definition of dispatch interface: DICosmoFigures */
DECLARE_INTERFACE_(DICosmoFigures, IDispatch)
{
#ifndef NO_BASEINTERFACE_FUNCS

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(THIS_ UINT FAR* pctinfo) PURE;

    STDMETHOD(GetTypeInfo)(
      THIS_
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo) PURE;

    STDMETHOD(GetIDsOfNames)(
      THIS_
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid) PURE;

    STDMETHOD(Invoke)(
      THIS_
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr) PURE;
#endif

/* Capable of dispatching all the methods of interface ICosmoFigures */
};

DEFINE_GUID(DIID_DICosmoApplication,0x00021162,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

/* Definition of dispatch interface: DICosmoApplication */
DECLARE_INTERFACE_(DICosmoApplication, IDispatch)
{
#ifndef NO_BASEINTERFACE_FUNCS

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(THIS_ UINT FAR* pctinfo) PURE;

    STDMETHOD(GetTypeInfo)(
      THIS_
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo) PURE;

    STDMETHOD(GetIDsOfNames)(
      THIS_
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid) PURE;

    STDMETHOD(Invoke)(
      THIS_
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr) PURE;
#endif

/* Capable of dispatching all the methods of interface ICosmoApplication */
};

DEFINE_GUID(CLSID_Cosmo2Figure,0x00021107,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

class Cosmo2Figure;

DEFINE_GUID(CLSID_Cosmo2Application,0x00021166,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

class Cosmo2Application;

#endif


POLYLINE.CPP

/*
 * POLYLINE.CPP
 * Cosmo Chapter 14
 *
 * Implementation of the CPolyline class.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CPolyline:CPolyline
 * CPolyline::~CPolyline
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application we're in.
 */

CPolyline::CPolyline(HINSTANCE hInst)
    : CWindow(hInst)
    {
    m_pAdv=NULL;
    m_hWnd=NULL;

    //CHAPTER14MOD
    m_fReadFromOLE10=FALSE;
    //End CHAPTER14MOD

    return;
    }

CPolyline::~CPolyline(void)
    {
    return;
    }

/*
 * CPolyline::FInit
 *
 * Purpose:
 *  Instantiates a polyline window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags
 *  uID             UINT ID to associate with this window
 *  pAdv            PCPolylineAdviseSink of the sink wanting our
 *                  notifications.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPolyline::FInit(HWND hWndParent, LPRECT pRect, DWORD dwStyle
    , UINT uID, PCPolylineAdviseSink pAdv)
    {
    m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY, SZCLASSPOLYLINE
        , SZCLASSPOLYLINE, dwStyle, pRect->left, pRect->top
        , pRect->right-pRect->left, pRect->bottom-pRect->top
        , hWndParent, (HMENU)uID, m_hInst, this);

    if (NULL!=m_hWnd)
        m_pAdv=pAdv;

    return (NULL!=m_hWnd);
    }

/*
 * CPolyline::New
 *
 * Purpose:
 *  Cleans out and reinitializes the data to defaults.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolyline::New(void)
    {
    UINT        i;
    RECT        rc;

    m_pl.wVerMaj=VERSIONMAJOR;
    m_pl.wVerMin=VERSIONMINOR;

    //Our rectangle is the size of our window's client area.
    GetClientRect(m_hWnd, &rc);
    RECTTORECTS(rc, m_pl.rc);

    //Clean out the POLYLINE structure and repaint the window.
    for (i=0; i< CPOLYLINEPOINTS; i++)
        {
        m_pl.rgpt[i].x=0;
        m_pl.rgpt[i].y=0;
        }

    m_pl.cPoints      =0;
    m_pl.rgbBackground=GetSysColor(COLOR_WINDOW);
    m_pl.rgbLine      =GetSysColor(COLOR_WINDOWTEXT);
    m_pl.iLineStyle   =PS_SOLID;

    InvalidateRect(m_hWnd, NULL, TRUE);
    UpdateWindow(m_hWnd);

    //Inform the advise sink of this data change.
    if (NULL!=m_pAdv)
        m_pAdv->OnDataChange();

    return;
    }

/*
 * CPolyline::Undo
 *
 * Purpose:
 *  Reverses previous actions in a Polyline.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if we can Undo more, FALSE otherwise.
 */

BOOL CPolyline::Undo(void)
    {
    //Decrement the number of active points and repaint.
    if (m_pl.cPoints > 0)
        {
        m_pl.cPoints--;
        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    if (NULL!=m_pAdv)
        m_pAdv->OnPointChange();

    //Return if we can undo any more.
    return (0!=m_pl.cPoints);
    }

/*
 * CPolyline::ReadFromStorage
 *
 * Purpose:
 *  Loads our data (any known version) from an IStorage returning
 *  the version number of the data or an error value.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE from which we'll read.
 *
 * Return Value:
 *  LONG            Version number or negative POLYLINE_E_* value.
 */

LONG CPolyline::ReadFromStorage(LPSTORAGE pIStorage)
    {
    LONG            lRet;
    LPSTREAM        pIStream;
    HRESULT         hr;

    if (NULL==pIStorage)
        return POLYLINE_E_READFAILURE;

    //Open the CONTENTS stream
    hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT | STGM_READ
        | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return POLYLINE_E_READFAILURE;

    /*
     * Since we had to create ReadFromStream for IPersistStorage,
     * use it ourselves.
     */
    lRet=ReadFromStream(pIStream);
    pIStream->Release();

    return lRet;
    }

/*
 * CPolyline::WriteToStorage
 *
 * Purpose:
 *  Ignorantly writes our current data into a storage in a
 *  particular version.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which to save.
 *  lVer            LONG providing version number Major (HI) and
 *                  Minor (Low)
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::WriteToStorage(LPSTORAGE pIStorage, LONG lVer)
    {
    LONG            lRet;
    LPSTREAM        pIStream;
    HRESULT         hr;

    if (NULL==pIStorage)
        return POLYLINE_E_READFAILURE;

    hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT | STGM_CREATE
        | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pIStream);

    if (FAILED(hr))
        return POLYLINE_E_WRITEFAILURE;

    /*
     * Use our function that will save to a stream now that
     * we needed it for IPersistStorage.
     */

    lRet=WriteToStream(pIStream, lVer);
    pIStream->Release();

    return lRet;
    }

/*
 * CPolyline::ReadFromStream
 *
 * Purpose:
 *  Loads our data (any known version) from an IStream that we
 *  do not open nor maintain.  This is for the compound document
 *  object's implementation of IPersistStorage.
 *
 * Parameters:
 *  pIStream        LPSTREAM from which we'll read.
 *
 * Return Value:
 *  LONG            Version number or negative POLYLINE_E_* value.
 */

LONG CPolyline::ReadFromStream(LPSTREAM pIStream)
    {
    POLYLINEDATA    pl;
    ULONG           cb=(ULONG)-1;
    ULONG           cbExpect=0;
    HRESULT         hr;
    LARGE_INTEGER   li;

    if (NULL==pIStream)
        return POLYLINE_E_READFAILURE;

    //CHAPTER14MOD
    /*
     * If IPersistStorage::Load failed to find CONTENTS but did
     * open "\1Ole10Native" then m_fReadFromOLE10 is set.  So
     * we have to start reading past the leading DWORD.
     */

    if (m_fReadFromOLE10)
        {
        DWORD       dw;

        /*
         * Skip the DWORD length at the beginning of the
         * Ole10Native stream.
         */
        pIStream->Read(&dw, sizeof(DWORD), &cb);
        }
    //End CHAPTER14MOD

    //Read version numbers and seek back to file beginning.
    hr=pIStream->Read(&pl, 2*sizeof(WORD), &cb);

    //CHAPTER14MOD
    //If we read OLE 1, seek back but skip the size header.
    if (m_fReadFromOLE10)
        LISet32(li, sizeof(DWORD));
    else
        LISet32(li, 0);
    //End CHAPTER14MOD

    pIStream->Seek(li, STREAM_SEEK_SET, NULL);

    if (FAILED(hr) || 2*sizeof(WORD)!=cb)
        {
        pIStream->Release();
        return POLYLINE_E_READFAILURE;
        }

    /*
     * For version 2.0, read the entire file.  For version 1.0 read
     * the file up to CBPOLYLINEDATAVER10.  For anything else, give
     * an error.
     */

    switch (pl.wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (pl.wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (pl.wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    hr=pIStream->Read(&pl, cbExpect, &cb);

    if (cbExpect!=cb)
        return POLYLINE_E_READFAILURE;

    /*
     * If we loaded successfully, make the data current.  By using
     * DataSet we centralize our version upgrading.  We size the
     * polyline window to the data AND notify the document so it
     * sizes to the polyline.
     */
    DataSet(&pl, TRUE, TRUE);

    //Return what version we just loaded.
    return MAKELONG(pl.wVerMin, pl.wVerMaj);
    }

/*
 * CPolyline::WriteToStream
 *
 * Purpose:
 *  Ignorantly writes our current data into a stream in a
 *  particular version.
 *
 * Parameters:
 *  pIStream        LPSTREAM in which to save.
 *  lVer            LONG providing version number Major (HI) and
 *                  Minor (Low)
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::WriteToStream(LPSTREAM pIStream, LONG lVer)
    {
    ULONG           cb;
    ULONG           cbExpect=0;
    WORD            wVerMaj=HIWORD(lVer);
    WORD            wVerMin=LOWORD(lVer);
    POLYLINEDATA    pl;
    HRESULT         hr;

    if (NULL==pIStream)
        return POLYLINE_E_READFAILURE;

    //Get a copy of our data in the version we're going to save.
    DataGet(&pl, lVer);

    switch (wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    //CHAPTER14MOD
    /*
     * If writing to an OLE 1 storage, include the length of
     * data as the first DWORD in the stream.
     */

     if (m_fReadFromOLE10 && CBPOLYLINEDATA10==cbExpect)
        {
        hr=pIStream->Write(&cbExpect, sizeof(DWORD), &cb);

        if (FAILED(hr) || sizeof(DWORD)!=cb)
            return POLYLINE_E_WRITEFAILURE;
        }
    //End CHAPTER14MOD

    hr=pIStream->Write(&pl, cbExpect, &cb);

    if (FAILED(hr) || cbExpect!=cb)
        return POLYLINE_E_WRITEFAILURE;

    return POLYLINE_E_NONE;
    }

/*
 * CPolyline::ReadFromFile
 *
 * Purpose:
 *  Loads our data (any known version) from a file handle returning
 *  the version number of the data or an error value.
 *
 * Parameters:
 *  pszFile         LPTSTR of the file to open.
 *
 * Return Value:
 *  LONG            Version number or negative POLYLINE_E_* value.
 */

LONG CPolyline::ReadFromFile(LPTSTR pszFile)
    {
    OFSTRUCT        of;
    HFILE           hFile;
    POLYLINEDATA    pl;
    UINT            cb=(UINT)-1;
    UINT            cbExpect=0;

    if (NULL==pszFile)
        return POLYLINE_E_READFAILURE;

    //OpenFileW is a member of CPolyline.
    hFile=OpenFileW(pszFile, &of, OF_READ);

    if (HFILE_ERROR==hFile)
        return POLYLINE_E_READFAILURE;

    //Read version numbers and seek back to file beginning.
    cb=_lread(hFile, &pl, 2*sizeof(WORD));
    _llseek(hFile, 0L, 0);

    if (2*sizeof(WORD)!=cb)
        {
        _lclose(hFile);
        return POLYLINE_E_READFAILURE;
        }

    /*
     * For version 2.0, read the entire file.  For version 1.0 read
     * the file up to CBPOLYLINEDATAVER10.  For anything else, give
     * an error.
     */

    switch (pl.wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (pl.wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (pl.wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        {
        _lclose(hFile);
        return POLYLINE_E_UNSUPPORTEDVERSION;
        }

    cb=_lread(hFile, &pl, cbExpect);
    _lclose(hFile);

    if (cbExpect!=cb)
        return POLYLINE_E_READFAILURE;

    /*
     * If we loaded successfully, make the data current.  By using
     * DataSet we centralize our version upgrading.  We size the
     * polyline window to the data AND notify the document so it
     * sizes to the polyline.
     */
    DataSet(&pl, TRUE, TRUE);

    //Return what version we just loaded.
    return MAKELONG(pl.wVerMin, pl.wVerMaj);
    }

/*
 * CPolyline::WriteToFile
 *
 * Purpose:
 *  Ignorantly writes our current data into an opened file in a
 *  particular version.
 *
 * Parameters:
 *  pszFile         LPTSTR filename in which to store the data.
 *  lVer            LONG providing version number Major (HI)
 *                  and Minor (Low)
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::WriteToFile(LPTSTR pszFile, LONG lVer)
    {
    OFSTRUCT        of;
    HFILE           hFile;
    UINT            cb;
    UINT            cbExpect=0;
    WORD            wVerMaj=HIWORD(lVer);
    WORD            wVerMin=LOWORD(lVer);
    POLYLINEDATA    pl;

    if (NULL==pszFile)
        return POLYLINE_E_READFAILURE;

    //Get a copy of our data in the version we're going to save.
    DataGet(&pl, lVer);

    switch (wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    hFile=OpenFileW(pszFile, &of, OF_CREATE | OF_WRITE);

    if (HFILE_ERROR==hFile)
        return DOCERR_COULDNOTOPEN;

    cb=_lwrite(hFile, (LPCSTR)&pl, cbExpect);
    _lclose(hFile);

    return (cbExpect==cb) ? POLYLINE_E_NONE
        : POLYLINE_E_WRITEFAILURE;
    }

/*
 * CPolyline::OpenFileW (Private)
 *
 * Purpose:
 *  Under Win32, OpenFile does not take Unicode strings.  This
 *  function converts a Unicode string into an ANSI string and
 *  calls OpenFile.  This just maps to OpenFile without Unicode.
 *
 * Parameters, Return Value:
 *  Same as OpenFile.
 */

HFILE CPolyline::OpenFileW(LPTSTR pszFile, LPOFSTRUCT pof
    , UINT uFlags)
    {
   #ifdef UNICODE
    CHAR        szTemp[CCHPATHMAX];

    UNICODETOANSI(pszFile, szTemp, CCHPATHMAX);
    return OpenFile(szTemp, pof, uFlags);
   #else
    return OpenFile(pszFile, pof, uFlags);
   #endif
    }

/*
 * CPolyline::DataSet
 *
 * Purpose:
 *  Sets the current data in this Polyline to a given structure.
 *
 * Parameters:
 *  ppl             PPOLYLINEDATA to initialize to.
 *  fSizeToData     BOOL indicating if we're to size to the data or
 *                  scale it.
 *  fNotify         BOOL indicating if we're to send an advise on
 *                  this change.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataSet(PPOLYLINEDATA ppl, BOOL fSizeToData
    , BOOL fNotify)
    {
    RECTS       rcs;
    RECT        rc;
    UINT        i;

    /*
     * Copy the structure in ppl and repaint to reflect the new
     * point set.  Note that unlike the RectSet message, we do no
     * scaling, assuming that the rect in the structure is
     * appropriate for the data.
     */

    if (NULL==ppl)
        return POLYLINE_E_INVALIDPOINTER;

    //Preserve the old rectangle
    rcs=m_pl.rc;

    /*
     * For version 2.0 we perform a straight copy.  For version
     * 1.0 we copy the 1.0 structure and fill in defaults for
     * the 2.0 additions.
     */

    switch (ppl->wVerMaj)
        {
        case VERSIONMAJOR:          //2.x
            switch (ppl->wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    m_pl=*ppl;
                    break;

                default:
                    return POLYLINE_E_UNSUPPORTEDVERSION;
                }
            break;

        case 1:                     //1.x
            switch (ppl->wVerMin)
                {
                case 0:             //1.0
                    *((PPOLYLINEDATA10)&m_pl)=
                        *((PPOLYLINEDATA10)ppl);

                    /*
                     * Update this structure to 2.0.  Note that we
                     * assume whoever loaded us to save the loaded
                     * version so it can later ask what version
                     * the user wants to save.
                     */
                    m_pl.wVerMaj=VERSIONMAJOR;
                    m_pl.wVerMin=VERSIONMINOR;

                    /*
                     * Version 1.0 stored rc in parent coordinates.
                     * We need those now in our client coodinates.
                     */
                    RECTSTORECT(m_pl.rc, rc);
                    OffsetRect(&rc, -m_pl.rc.left, -m_pl.rc.top);

                    /*
                     * 1.0 data had points relative to size of the
                     * rectangle.  We need to scale these to 0-32767
                     * independent of the rectangle for the version
                     * upgrade.
                     */
                    for (i=0; i < m_pl.cPoints; i++)
                        PointScale(&rc, &m_pl.rgpt[i], FALSE);

                    RECTTORECTS(rc, m_pl.rc);

                    //New 2.0 features
                    m_pl.rgbBackground=GetSysColor(COLOR_WINDOW);
                    m_pl.rgbLine=GetSysColor(COLOR_WINDOWTEXT);
                    m_pl.iLineStyle=PS_SOLID;
                    break;

                default:
                    return POLYLINE_E_UNSUPPORTEDVERSION;
                }
            break;

        default:
            return POLYLINE_E_UNSUPPORTEDVERSION;
        }

    //Inform our parent of the data change
    if (NULL!=m_pAdv)
        m_pAdv->OnDataChange();

    /*
     * If we're scaling the window to fit the data, then use
     * RectSet passing our current rectangle as the new one.
     * That makes sure that the data won't change but that the
     * window is resized.
     */

    if (fSizeToData)
        {
        POINT       pt;

        /*
         * Get our offset in the parent window so we can RectSet
         * to the right place since RectSet expects rectangle in
         * parent coordinates and we get it in client coordinates.
         */
        GetWindowRect(m_hWnd, &rc);
        pt.x=rc.left;
        pt.y=rc.top;
        ScreenToClient(GetParent(m_hWnd), &pt);
        RECTSTORECT(m_pl.rc, rc);
        OffsetRect(&rc, pt.x, pt.y);

        //This will also cause a repaint.
        RectSet(&rc, fNotify);
        }
    else
        {
        //Make sure we're updated.
        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    return POLYLINE_E_NONE;
    }

/*
 * CPolyline::DataGet
 *
 * Purpose:
 *  Retrieves the Polyline's current data.
 *
 * Parameters:
 *  ppl             PPOLYLINEDATA into which we copy the data.
 *  lVer            LONG version of the data to retrieve.  Use
 *                  VERSIONCURRENT to retrieve the most current.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value
 */

LONG CPolyline::DataGet(PPOLYLINEDATA ppl, LONG lVer)
    {
    UINT        i;
    RECT        rc;

    //Retieve the current version
    if (lVer==MAKELONG(VERSIONMINOR, VERSIONMAJOR)
        || VERSIONCURRENT==lVer)
        {
        *ppl=m_pl;
        return POLYLINE_E_NONE;
        }

    //Check for versions we support, 1.x
    if (HIWORD(lVer)!=1)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    if (LOWORD(lVer)==0)    //Check for 1.0
        {
        //Do 2.0 to 1.0 conversion
        *((PPOLYLINEDATA10)ppl)=*((PPOLYLINEDATA10)&m_pl);

        RECTSTORECT(ppl->rc, rc);

        //Get the parent coordinates of our rectangle
        if (!IsWindow(m_hWnd))
            OffsetRect(&rc, 8, 8); //This is always the offset.
        else
            RectGet(&rc);

        /*
         * 1.0 data has points relative to size of the rectangle.
         * We need to scale these from 0-32767 so we have the
         * right values.
         */
        for (i=0; i < ppl->cPoints; i++)
            PointScale(&rc, &ppl->rgpt[i], TRUE);

        RECTTORECTS(rc, ppl->rc);

        //Insure old version numbers.
        ppl->wVerMaj=1;
        ppl->wVerMin=0;

        ((PPOLYLINEDATA10)ppl)->fDrawEntire=TRUE;
        return POLYLINE_E_NONE;
        }

    return POLYLINE_E_UNSUPPORTEDVERSION;
    }

/*
 * CPolyline::DataSetMem
 *
 * Purpose:
 *  Sets the Polyline's data using a global memory handle
 *  instead of a pointer.
 *
 * Parameters:
 *  hMem            HGLOBAL containing the data.
 *  fFree           BOOL indicating if we're to free the data.
 *                  The memory will be freed regardless of any
 *                  error returned from here.
 *  fSizeToData     BOOL indicating if we're to size to the data
 *                  or scale it.
 *  fNotify         BOOL indicating if we're to send an advise
 *                  on this change.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataSetMem(HGLOBAL hMem, BOOL fFree
    , BOOL fSizeToData, BOOL fNotify)
    {
    PPOLYLINEDATA   ppl;
    LONG            lRet=POLYLINE_E_INVALIDPOINTER;

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        lRet=DataSet(ppl, fSizeToData, fNotify);

        GlobalUnlock(hMem);

        if (fFree)
            GlobalFree(hMem);
        }

    return lRet;
    }

/*
 * CPolyline::DataGetMem
 *
 * Purpose:
 *  Retrieves the Polyline's data in a global memory handle.
 *
 * Parameters:
 *  lVer            LONG version of data to retrieve.
 *  phMem           HGLOBAL * in which to store the handle.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataGetMem(LONG lVer, HGLOBAL *phMem)
    {
    HGLOBAL         hMem;
    PPOLYLINEDATA   ppl;
    LONG            lRet;

    if (NULL==phMem)
        return POLYLINE_E_INVALIDPOINTER;

    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
        , CBPOLYLINEDATA);

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        lRet=DataGet(ppl, lVer);
        GlobalUnlock(hMem);

        if (POLYLINE_E_NONE!=lRet)
            {
            GlobalFree(hMem);
            hMem=NULL;
            }
        }

    *phMem=hMem;
    return lRet;
    }

/*
 * CPolyline::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectGet(LPRECT pRect)
    {
    RECT        rc;
    POINT       pt;

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_hWnd, &rc);
    pt.x=rc.left;
    pt.y=rc.top;
    ScreenToClient(GetParent(m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return;
    }

/*
 * CPolyline::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  None
 */

void CPolyline::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return;
    }

/*
 * CPolyline::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;
    RECT        rc;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, pRect->left, pRect->top
        , cx, cy, SWP_NOZORDER);

    SetRect(&rc, 0, 0, cx, cy);
    RECTTORECTS(rc, m_pl.rc);

    if (fNotify && NULL!=m_pAdv)
        m_pAdv->OnSizeChange();

    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }

/*
 * CPolyline::SizeSet
 *
 * Purpose:
 *  Sets a new size for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPolyline::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
        , SWP_NOMOVE | SWP_NOZORDER);

    if (fNotify && NULL!=m_pAdv)
        m_pAdv->OnSizeChange();

    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }

/*
 * CPolyline::ColorSet
 *
 * Purpose:
 *  Changes for background or line color in the Polyline
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color to use.
 *
 * Return Value:
 *  COLORREF        Previous color for the index iColor.
 */

COLORREF CPolyline::ColorSet(UINT iColor, COLORREF cr)
    {
    COLORREF    crRet;

    switch (iColor)
        {
        case POLYLINECOLOR_BACKGROUND:
            crRet=m_pl.rgbBackground;
            m_pl.rgbBackground=cr;
            break;

        case POLYLINECOLOR_LINE:
            crRet=m_pl.rgbLine;
            m_pl.rgbLine=cr;
            break;
        }

    //If the color changed, repaint
    if (crRet!=cr)
        {
        if (NULL!=m_pAdv)
            m_pAdv->OnColorChange();

        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    return crRet;
    }

/*
 * CPolyline::ColorGet
 *
 * Purpose:
 *  Retrieves one of the colors currently in use by the Polyline.
 *
 * Parameters:
 *  iColor          UINT identifying the color of interest.
 *
 * Return Value:
 *  COLORREF        Current color for iColor.
 */

COLORREF CPolyline::ColorGet(UINT iColor)
    {
    COLORREF    crRet;

    crRet=(POLYLINECOLOR_BACKGROUND==iColor)
        ? m_pl.rgbBackground : m_pl.rgbLine;

    return crRet;
    }

/*
 * CPolyline::LineStyleSet
 *
 * Purpose:
 *  Changes the line style in use by the Polyline

*
 * Parameters:
 *  iStyle          UINT style of the line to use.
 *
 * Return Value:
 *  UINT            Previous style.
 */

UINT CPolyline::LineStyleSet(UINT iStyle)
    {
    UINT        uRet=(UINT)m_pl.iLineStyle;

    //Validate the line style
    if (PS_SOLID==iStyle || PS_DASH==iStyle || PS_DOT==iStyle
        || PS_DASHDOT==iStyle || PS_DASHDOTDOT==iStyle)
        {
        m_pl.iLineStyle=iStyle;

        if (uRet!=(UINT)m_pl.iLineStyle)
            {
            if (NULL!=m_pAdv)
                m_pAdv->OnLineStyleChange();

            InvalidateRect(m_hWnd, NULL, TRUE);
            UpdateWindow(m_hWnd);
            }
        }

    return uRet;
    }

/*
 * CPolyline::LineStyleGet
 *
 * Purpose:
 *  Retrieves the current line style in use in the Polyline
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Current line style.
 */

UINT CPolyline::LineStyleGet(void)
    {
    return m_pl.iLineStyle;
    }

/*
 * CPolyline::RenderBitmap
 *
 * Purpose:
 *  Creates a bitmap image of the current Polyline.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HBITMAP         Handle to the newly rendered bitmap.
 */

HBITMAP CPolyline::RenderBitmap(void)
    {
    HDC         hDC;
    HDC         hMemDC;
    HBITMAP     hBmp;
    RECT        rc;
    HGDIOBJ     hObj;

    //Render a bitmap the size of the current rectangle.
    hDC=GetDC(m_hWnd);
    hMemDC=CreateCompatibleDC(hDC);

    GetClientRect(m_hWnd, &rc);
    hBmp=CreateCompatibleBitmap(hDC, rc.right, rc.bottom);

    if (NULL!=hBmp)
        {
        //Draw the POLYLINEDATA into the bitmap.
        hObj=SelectObject(hMemDC, hBmp);
        Draw(hMemDC, FALSE, TRUE);
        SelectObject(hMemDC, hObj);
        }

    DeleteDC(hMemDC);
    ReleaseDC(m_hWnd, hDC);

    return hBmp;
    }

/*
 * CPolyline::RenderMetafile
 *
 * Purpose:
 *  Renders the current image of the Polyline into a metafile.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HMETAFILE       Handle to the newly created metafile.
 */

HMETAFILE CPolyline::RenderMetafile(void)
    {
    HDC         hDC;
    HMETAFILE   hMF;
    RECT        rc;

    //Create a memory metafile and return its handle.
    hDC=(HDC)CreateMetaFile(NULL);
    hMF=NULL;

    if (NULL!=hDC)
        {
        /*
         * This is absolutely essential to the metafile so it
         * can be scaled in the clipboard and any destination
         * application.
         */
        SetMapMode(hDC, MM_ANISOTROPIC);
        GetClientRect(m_hWnd, &rc);
        SetWindowOrgEx(hDC, 0, 0, NULL);
        SetWindowExtEx(hDC, rc.right, rc.bottom, NULL);

        Draw(hDC, TRUE, TRUE);
        hMF=CloseMetaFile(hDC);
        }

    return hMF;
    }

/*
 * CPolyline::RenderMetafilePict
 *
 * Purpose:
 *  Renders the current Polyline into a METAFILEPICT structure in
 *  global memory.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HGLOBAL         Memory containing the METAFILEPICT structure.
 */

HGLOBAL CPolyline::RenderMetafilePict(void)
    {
    HGLOBAL         hMem;
    HMETAFILE       hMF;
    LPMETAFILEPICT  pMF;
    RECT            rc;

    //Get the metafile
    hMF=RenderMetafile();

    if (NULL==hMF)
        return NULL;

    //Allocate the METAFILEPICT structure.
    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
        , sizeof(METAFILEPICT));

    if (NULL==hMem)
        {
        DeleteMetaFile(hMF);
        return NULL;
        }

    /*
     * Global lock only fails in PMODE if the selector is invalid
     * (like it was discarded) or references a 0 length segment,
     * neither of which can happen here.
     */
    pMF=(LPMETAFILEPICT)GlobalLock(hMem);

    pMF->hMF=hMF;
    pMF->mm=MM_ANISOTROPIC;

    //Insert the extents in MM_HIMETRIC units.
    GetClientRect(m_hWnd, &rc);
    RectConvertMappings(&rc, FALSE);
    pMF->xExt=rc.right;
    pMF->yExt=rc.bottom;

    GlobalUnlock(hMem);
    return hMem;
    }

/*
 * CPolyline::RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device (MM_TEXT) or
 *  HIMETRIC to the other.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  fToDevice       BOOL TRUE to convert from HIMETRIC to device,
 *                  FALSE to convert device to HIMETRIC.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectConvertMappings(LPRECT pRect, BOOL fToDevice)
    {
    HDC      hDC;
    int      iLpx, iLpy;

    if (NULL==pRect)
        return;

    hDC=GetDC(NULL);
    iLpx=GetDeviceCaps(hDC, LOGPIXELSX);
    iLpy=GetDeviceCaps(hDC, LOGPIXELSY);
    ReleaseDC(NULL, hDC);

    if (fToDevice)
        {
        pRect->left=MulDiv(iLpx, pRect->left, HIMETRIC_PER_INCH);
        pRect->top =MulDiv(iLpy, pRect->top , HIMETRIC_PER_INCH);

        pRect->right=MulDiv(iLpx, pRect->right,  HIMETRIC_PER_INCH);
        pRect->bottom=MulDiv(iLpy, pRect->bottom,HIMETRIC_PER_INCH);

       #ifdef NEVER
        /*
         * In this conversion we may get situations where the top
         * coordinate is larger than the bottom, which messes us up.
         */
        if (pRect->bottom < pRect->top)
            {
            iLpy=pRect->top;
            pRect->top=pRect->bottom;
            pRect->bottom=iLpy;
            }
       #endif
        }
    else
        {
        pRect->left=MulDiv(pRect->left, HIMETRIC_PER_INCH, iLpx);
        pRect->top =MulDiv(pRect->top , HIMETRIC_PER_INCH, iLpy);

        pRect->right =MulDiv(pRect->right, HIMETRIC_PER_INCH, iLpx);
        pRect->bottom=MulDiv(pRect->bottom,HIMETRIC_PER_INCH, iLpy);
        }

    return;
    }


POLYLINE.H

/*
 * POLYLINE.H
 * Cosmo Chapter 14
 *
 * Definitions and function prototypes for the PolyLine window
 * class that can be treated like its own control.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _POLYLINE_H_
#define _POLYLINE_H_

//Versioning.
#define VERSIONMAJOR                2
#define VERSIONMINOR                0
#define VERSIONCURRENT              0x00020000

//Classname
#define SZCLASSPOLYLINE             TEXT("polyline")

//Stream Name that holds the data
#define SZSTREAM                    OLESTR("CONTENTS")
//CHAPTER14MOD
#define SZOLE1STREAM                OLESTR("\1Ole10Native")
//End CHAPTER14MOD

#define HIMETRIC_PER_INCH           2540
#define CPOLYLINEPOINTS             20

//Window extra bytes and offsets
#define CBPOLYLINEWNDEXTRA          (sizeof(LONG))
#define PLWL_STRUCTURE              0

//Version 2.0 Polyline Structure
typedef struct tagPOLYLINEDATA
    {
    WORD        wVerMaj;                //Major version number.
    WORD        wVerMin;                //Minor version number.
    WORD        cPoints;                //Number of points.
    short       fReserved;              //Obsolete from v1.0
    RECTS       rc;                     //Rectangle of this figure
    POINTS      rgpt[CPOLYLINEPOINTS];  //Points (0-32767 grid)

    //Version 2.0 additions
    COLORREF    rgbBackground;          //Background color
    COLORREF    rgbLine;                //Line color
    short       iLineStyle;             //Line style
    } POLYLINEDATA, *PPOLYLINEDATA;

#define CBPOLYLINEDATA   sizeof(POLYLINEDATA)
#define CBPOLYLINEDATA20 sizeof(POLYLINEDATA)

//Version 1.0 Polyline Structure
typedef struct tagPOLYLINEDATA10
    {
    WORD        wVerMaj;                //Major version number.
    WORD        wVerMin;                //Minor version number.
    WORD        cPoints;                //Number of points.
    short       fDrawEntire;            //Flag to draw entire figure
    RECTS       rc;                     //Rectangle of this figure
    POINTS      rgpt[CPOLYLINEPOINTS];  //Points (scaled to rc)
    } POLYLINEDATA10, *PPOLYLINEDATA10;

#define CBPOLYLINEDATA10 sizeof(POLYLINEDATA10)

//POLYWIN.CPP
LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM, LPARAM);

class CPolyline : public CWindow
    {
    friend LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM
        , LPARAM);

    private:
        POLYLINEDATA   m_pl;

        class CPolylineAdviseSink * m_pAdv;

    //CHAPTER14MOD
    public:
        //This is for CFigure to look at when writing its storage.
        BOOL            m_fReadFromOLE10;
    //End CHAPTER14MOD

    private:
        HFILE     OpenFileW(LPTSTR, LPOFSTRUCT, UINT);
        void      PointScale(LPRECT, LPPOINTS, BOOL);
        void      Draw(HDC, BOOL, BOOL);
        void      RectConvertMappings(LPRECT, BOOL);

    public:
        CPolyline(HINSTANCE);
        ~CPolyline(void);

        BOOL      FInit(HWND, LPRECT, DWORD, UINT
            , class CPolylineAdviseSink *);

        void      New(void);
        BOOL      Undo(void);

        //File functions
        LONG      ReadFromStorage(LPSTORAGE);
        LONG      WriteToStorage(LPSTORAGE, LONG);

        LONG      ReadFromStream(LPSTREAM);
        LONG      WriteToStream(LPSTREAM, LONG);

        LONG      ReadFromFile(LPTSTR);
        LONG      WriteToFile(LPTSTR, LONG);

        //Data transfer functions
        LONG      DataSet(PPOLYLINEDATA, BOOL, BOOL);
        LONG      DataGet(PPOLYLINEDATA, LONG);
        LONG      DataSetMem(HGLOBAL, BOOL, BOOL, BOOL);
        LONG      DataGetMem(LONG, HGLOBAL *);
        HBITMAP   RenderBitmap(void);
        HMETAFILE RenderMetafile(void);
        HGLOBAL   RenderMetafilePict(void);

        void      RectGet(LPRECT);
        void      SizeGet(LPRECT);
        void      RectSet(LPRECT, BOOL);
        void      SizeSet(LPRECT, BOOL);
        COLORREF  ColorSet(UINT, COLORREF);
        COLORREF  ColorGet(UINT);
        UINT      LineStyleSet(UINT);
        UINT      LineStyleGet(void);
    };

typedef CPolyline *PCPolyline;

//Error values for data transfer functions
#define POLYLINE_E_NONE                    0
#define POLYLINE_E_UNSUPPORTEDVERSION      -1
#define POLYLINE_E_INVALIDPOINTER          -2
#define POLYLINE_E_READFAILURE             -3
#define POLYLINE_E_WRITEFAILURE            -4

class CPolylineAdviseSink
    {
    private:
        LPVOID      m_pv;           //Customizable structure

    public:
        CPolylineAdviseSink(LPVOID);
        ~CPolylineAdviseSink(void);

        void OnPointChange(void);
        void OnSizeChange(void);
        void OnDataChange(void);
        void OnColorChange(void);
        void OnLineStyleChange(void);
    };

typedef CPolylineAdviseSink *PCPolylineAdviseSink;

//Color indices for color messages
#define POLYLINECOLOR_BACKGROUND    0
#define POLYLINECOLOR_LINE          1

#endif  //_POLYLINE_H_


POLYWIN.CPP

/*
 * POLYWIN.CPP
 * Cosmo Chapter 14
 *
 * Window procedure for the polyline drawing window and support
 * functions.  This window is not complicated.  On creation it
 * allocates a block of memory for a POLYLINEDATA structure that
 * contains 20 POINTs.  We do not attempt to reallocate this
 * array at all just to maintain simplicity.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * PolylineWndProc
 *
 * Purpose:
 *  Window procedure for the polyline drawing window.
 */

LRESULT APIENTRY PolylineWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCPolyline      ppl;
    PAINTSTRUCT     ps;
    HDC             hDC;
    POINTS          pt;
    RECT            rc;

    ppl=(PCPolyline)GetWindowLong(hWnd, PLWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppl=(PCPolyline)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PLWL_STRUCTURE, (LONG)ppl);
            ppl->m_hWnd=hWnd;
            ppl->New();
            break;

        case WM_PAINT:
            hDC=BeginPaint(hWnd, &ps);
            ppl->Draw(hDC, FALSE, TRUE);
            EndPaint(hWnd, &ps);
            break;

        case WM_LBUTTONDOWN:
            //Stop if we are already at the limit.
            if (CPOLYLINEPOINTS==ppl->m_pl.cPoints)
                {
                MessageBeep(0);
                break;
                }

            //Convert the points into 0-32767 range
            GetClientRect(hWnd, &rc);
            pt=MAKEPOINTS(lParam);
            ppl->PointScale(&rc, &pt, FALSE);

            ppl->m_pl.rgpt[ppl->m_pl.cPoints++]=pt;

            //Draw the lines to this new point only.
            hDC=GetDC(hWnd);
            ppl->Draw(hDC, FALSE, FALSE);
            ReleaseDC(hWnd, hDC);

            if (NULL!=ppl->m_pAdv)
                ppl->m_pAdv->OnPointChange();

            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPolyline::Draw
 *
 * Purpose:
 *  Paints the current line in the polyline window.
 *
 * Parameters:
 *  hDC             HDC to draw on, could be metafile or printer DC.
 *  fMetafile       BOOL indicating if hDC is a metafile or not, so
 *                  we can avoid operations that RIP.
 *  fEntire         BOOL indicating if we should draw the entire
 *                  figure or not.
 *
 * Return Value:
 *  None
 */

void CPolyline::Draw(HDC hDC, BOOL fMetafile, BOOL fEntire)
    {
    HBRUSH          hBrush;
    HPEN            hPen;
    HGDIOBJ         hObj1, hObj2;
    UINT            i, j;
    UINT            uMM;
    POINTS          pt1, pt2;
    POINT           rgpt[CPOLYLINEPOINTS];
    RECT            rc;

    GetClientRect(m_hWnd, &rc);

    /*
     * Make a 32-bit copy of the points in the POLYLINE itself
     * so we can use DPtoLP and LPtoDP.
     */
    for (i=0; i < m_pl.cPoints; i++)
        {
        rgpt[i].x=m_pl.rgpt[i].x;
        rgpt[i].y=m_pl.rgpt[i].y;
        }

    /*
     * If the mapping mode is not MM_TEXT, convert the points to
     * whatever mapping mode in in effect before drawing.
     * This specifically supports metafiles in MM_ANISOTROPIC.
     */
    uMM=fMetafile ? MM_TEXT : GetMapMode(hDC);

    if (MM_TEXT!=uMM)
        DPtoLP(hDC, rgpt, m_pl.cPoints);

    hPen=CreatePen(m_pl.iLineStyle, 1, m_pl.rgbLine);
    hObj1=SelectObject(hDC, hPen);

    hBrush=CreateSolidBrush(m_pl.rgbBackground);
    hObj2=SelectObject(hDC, hBrush);
    SetBkColor(hDC, m_pl.rgbBackground);

    /*
     * Either draw the entire figure or just a single point.  The
     * entire figure also includes erasing the background
     * completely,since hDC may be a metafile DC.  Drawing a
     * single point just updates the figure for that new point.
     */
    if (fEntire || 0==m_pl.cPoints)
        {
        //Erase the background for bitmaps and metafiles.
        SelectObject(hDC, GetStockObject(NULL_PEN));
        Rectangle(hDC, rc.left, rc.top, rc.right+1, rc.bottom+1);
        SelectObject(hDC, hPen);

        /*
         * If we are drawing the entire figure, then loop through
         * each point drawing a line to each successive point.
         */

        for (i=0; i < m_pl.cPoints; i++)
            {
            for (j=i; j < m_pl.cPoints; j++)
                {
                /*
                 * Not using POINTTOPOINTS since it unfortunately
                 * returns a LONG which doesn't equate to a POINTS.
                 */
                pt1.x=(short)rgpt[i].x;
                pt1.y=(short)rgpt[i].y;
                pt2.x=(short)rgpt[j].x;
                pt2.y=(short)rgpt[j].y;

                PointScale(&rc, &pt1, TRUE);
                PointScale(&rc, &pt2, TRUE);
                MoveToEx(hDC, pt1.x, pt1.y, NULL);
                LineTo(hDC, pt2.x, pt2.y);
                }
            }
        }
    else
        {
        /*
         * If we are only drawing the last point, just cycle once
         * through previous points.
         */

        //Get the last point entered in the array.
        j=m_pl.cPoints-1;
        pt1.x=(short)rgpt[j].x;
        pt1.y=(short)rgpt[j].y;
        PointScale(&rc, &pt1, TRUE);

        for (i=0; i < j; i++)
            {
            pt2.x=(short)rgpt[i].x;
            pt2.y=(short)rgpt[i].y;
            PointScale(&rc, &pt2, TRUE);

            MoveToEx(hDC, pt1.x, pt1.y, NULL);
            LineTo(hDC, pt2.x, pt2.y);
            }
        }

    //If we have one point, draw a dot to indicate it's position.
    if (1==m_pl.cPoints)
        {
        pt1.x=(short)rgpt[0].x;
        pt1.y=(short)rgpt[0].y;
        PointScale(&rc, &pt1, TRUE);
        SetPixel(hDC, pt1.x, pt1.y, m_pl.rgbLine);
        }

    //Note:  m_pl.rgpt is unmodified in this code.

    SelectObject(hDC, hObj1);
    SelectObject(hDC, hObj2);
    DeleteObject(hBrush);
    DeleteObject(hPen);
    return;
    }

/*
 * CPolyline::PointScale
 *
 * Purpose:
 *  Scales a point to or from a relative window coordinate to a
 *  0-32767 coordinate.
 *
 * Parameters:
 *  pRect           LPRECT of the window.
 *  ppt             LPPOINTS to convert
 *  fScaleToWindow  BOOL indicating direction of scaling.
 *
 * Return Value:
 *  None
 */

void CPolyline::PointScale(LPRECT pRect, LPPOINTS ppt
    , BOOL fScaleToWindow)
    {
    DWORD   cx, cy;

    //Window size
    cx=(DWORD)(pRect->right-pRect->left);
    cy=(DWORD)(pRect->bottom-pRect->top);

    //Prevent crashes
    if (0L==cx) cx=1;
    if (0L==cy) cy=1;

    //Must use DWORD to insure proper scaling.
    if (fScaleToWindow)
        {
        ppt->x=(UINT)(((DWORD)ppt->x*cx) >> 15);
        ppt->y=(UINT)(((DWORD)ppt->y*cy) >> 15);
        }
    else
        {
        ppt->x=(UINT)(((DWORD)ppt->x << 15)/cx);
        ppt->y=(UINT)(((DWORD)ppt->y << 15)/cy);
        }

    return;
    }


RESOURCE.H

/*
 * RESOURCE.H
 * Cosmo Chapter 14
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Note that we augment classlib.h which we include before this.

//Position of Window menu.
#undef  WINDOW_MENU
#define WINDOW_MENU             4

//Total number of menus
#undef  CMENUS
#ifdef MDI
#define CMENUS                  6
#else
#define CMENUS                  5
#endif

//Menu command identifiers.

#define IDM_FILEIMPORT          (IDM_CUSTOMFILEMIN)

#define IDM_COLORBACKGROUND     (IDM_CUSTOMMIN)
#define IDM_COLORLINE           (IDM_CUSTOMMIN+1)

#define IDM_LINEMIN             (IDM_CUSTOMMIN+100)
#define IDM_LINESOLID           (IDM_LINEMIN+0)         //PS_SOLID       0
#define IDM_LINEDASH            (IDM_LINEMIN+1)         //PS_DASH        1
#define IDM_LINEDOT             (IDM_LINEMIN+2)         //PS_DOT         2
#define IDM_LINEDASHDOT         (IDM_LINEMIN+3)         //PS_DASHDOT     3
#define IDM_LINEDASHDOTDOT      (IDM_LINEMIN+4)         //PS_DASHDOTDOT  4
#define IDM_LINEMAX             IDM_LINEDASHDOTDOT

/*
 * IDs for StatStrip handling of popup menus:  must be in order of menu
 * CLASSRES.H already defines ID_MENUFILE and ID_MENUEDIT for us.
 */

#undef  ID_MENUWINDOW
#undef  ID_MENUHELP

#define ID_MENUCOLOR            (ID_MENUCUSTOMMIN+0)
#define ID_MENULINE             (ID_MENUCUSTOMMIN+1)
#define ID_MENUWINDOW           (ID_MENUCUSTOMMIN+2)
#define ID_MENUHELP             (ID_MENUCUSTOMMIN+3)

//Stringtable IDs.  Keep sequential for each group.
#define IDS_FRAMEMIN            IDS_STANDARDFRAMEMIN
#define IDS_FILEIMPORT          (IDS_CUSTOMFRAMEMIN+0)
#define IDS_FIGUREINCONTAINER   (IDS_CUSTOMFRAMEMIN+1)
#define IDS_EXITANDRETURN       (IDS_CUSTOMFRAMEMIN+2)
#define IDS_EMBEDDINGCAPTION    (IDS_CUSTOMFRAMEMIN+3)
#define IDS_SAVECOPYAS          (IDS_CUSTOMFRAMEMIN+4)
#define IDS_NEW                 (IDS_CUSTOMFRAMEMIN+5)
#define IDS_OPEN                (IDS_CUSTOMFRAMEMIN+6)
#define IDS_CLOSE2              (IDS_CUSTOMFRAMEMIN+7)
#define IDS_SAVE                (IDS_CUSTOMFRAMEMIN+8)
#define IDS_SAVEAS              (IDS_CUSTOMFRAMEMIN+9)
#define IDS_EXIT                (IDS_CUSTOMFRAMEMIN+10)
#define IDS_FRAMEMAX            (IDS_CUSTOMFRAMEMIN+11)

#define IDS_DOCUMENTMIN         IDS_STANDARDDOCMIN
#define IDS_VERSIONCHANGE       (IDS_CUSTOMDOCMIN+0)
#define IDS_OBJECTDESCRIPTION   (IDS_CUSTOMDOCMIN+1)
#define IDS_DOCUMENTMAX         (IDS_CUSTOMDOCMIN+1)

//Strings for the CFigure object implementation.
#define IDS_FIGUREMIN           256
#define IDS_CLOSECAPTION        (IDS_FIGUREMIN+1)
#define IDS_CLOSEPROMPT         (IDS_FIGUREMIN+2)
#define IDS_USERTYPE            (IDS_FIGUREMIN+3)
#define IDS_EXTENSION           (IDS_FIGUREMIN+4)
//CHAPTER14MOD
#define IDS_FORMAT              (IDS_FIGUREMIN+5)
#define IDS_FIGUREMAX           (IDS_FIGUREMIN+5)
//End CHAPTER14MOD

//These are specifically for the StatStrip
#define IDS_STATMESSAGEMIN                  IDS_STANDARDSTATMESSAGEMIN

#define IDS_ITEMMESSAGEFILEIMPORT           (IDS_CUSTOMSTATMESSAGEMIN+0)

#define IDS_MENUMESSAGECOLOR                (IDS_CUSTOMSTATMESSAGEMIN+1)
#define IDS_ITEMMESSAGECOLORBACKGROUND      (IDS_CUSTOMSTATMESSAGEMIN+2)
#define IDS_ITEMMESSAGECOLORLINE            (IDS_CUSTOMSTATMESSAGEMIN+3)

#define IDS_MENUMESSAGELINE                 (IDS_CUSTOMSTATMESSAGEMIN+4)
#define IDS_ITEMMESSAGELINESOLID            (IDS_CUSTOMSTATMESSAGEMIN+5)
#define IDS_ITEMMESSAGELINEDASH             (IDS_CUSTOMSTATMESSAGEMIN+6)
#define IDS_ITEMMESSAGELINEDOT              (IDS_CUSTOMSTATMESSAGEMIN+7)
#define IDS_ITEMMESSAGELINEDASHDOT          (IDS_CUSTOMSTATMESSAGEMIN+8)
#define IDS_ITEMMESSAGELINEDASHDOTDOT       (IDS_CUSTOMSTATMESSAGEMIN+9)

#define IDS_STATMESSAGEMAX                  (IDS_CUSTOMSTATMESSAGEMIN+9)

#endif //_RESOURCE_H_


COSMOTST.FRM

VERSION 2.00
Begin Form Form1
   Caption         =   "Test Cosmo"
   ClientHeight    =   3000
   ClientLeft      =   9420
   ClientTop       =   1500
   ClientWidth     =   4800
   Height          =   3375
   Left            =   9375
   LinkTopic       =   "Form1"
   ScaleHeight     =   3000
   ScaleWidth      =   4800
   Top             =   1170
   Width           =   4890
   Begin CommandButton CreateFigure
      Caption         =   "C&reate Figure"
      Height          =   615
      Left            =   120
      TabIndex        =   9
      Top             =   2280
      Width           =   1455
   End
   Begin CommandButton ReleaseFigure
      Caption         =   "Release F&igure"
      Height          =   615
      Left            =   3240
      TabIndex        =   8
      Top             =   1560
      Width           =   1455
   End
   Begin CommandButton TestFigure
      Caption         =   "Test Fi&gure"
      Height          =   615
      Left            =   1680
      TabIndex        =   7
      Top             =   1560
      Width           =   1455
   End
   Begin CommandButton GetFigure
      Caption         =   "Get &Figure"
      Height          =   615
      Left            =   120
      TabIndex        =   6
      Top             =   1560
      Width           =   1455
   End
   Begin CommandButton GetCollection
      Caption         =   "&Get Collection"
      Height          =   615
      Left            =   120
      TabIndex        =   5
      Top             =   840
      Width           =   1455
   End
   Begin CommandButton ReleaseCollection
      Caption         =   "Release C&oll."
      Height          =   615
      Left            =   3240
      TabIndex        =   4
      Top             =   840
      Width           =   1455
   End
   Begin CommandButton TestCollection
      Caption         =   "Test &Collection"
      Height          =   615
      Left            =   1680
      TabIndex        =   3
      Top             =   840
      Width           =   1455
   End
   Begin CommandButton QuitApp
      Caption         =   "&Quit App"
      Height          =   615
      Left            =   3240
      TabIndex        =   2
      Top             =   120
      Width           =   1455
   End
   Begin CommandButton CreateApp
      Caption         =   "&Create App"
      Height          =   615
      Left            =   120
      TabIndex        =   1
      Top             =   120
      Width           =   1455
   End
   Begin CommandButton TestApp
      Caption         =   "&Test App"
      Height          =   615
      Left            =   1680
      TabIndex        =   0
      Top             =   120
      Width           =   1455
   End
End
Dim Cosmo As Object
Dim Figures As Object
Dim CosmoFigure As Object
Dim fCreatedObject As Integer

Sub CreateApp_Click ()
    Set Cosmo = CreateObject("Cosmo.Application.2")
    CreateApp.Enabled = False
    QuitApp.Enabled = True
    TestApp.Enabled = True

    CreateFigure.Enabled = False
    GetCollection.Enabled = True

    fCreatedObject = False
End Sub

Sub CreateFigure_Click ()
    Set CosmoFigure = CreateObject("Cosmo.Figure.2")

    CreateApp.Enabled = False
    QuitApp.Enabled = False
    TestApp.Enabled = False

    GetCollection.Enabled = False
    TestCollection.Enabled = False
    GetFigure.Enabled = False
    ReleaseCollection.Enabled = False

    CreateFigure.Enabled = False
    TestFigure.Enabled = True
    ReleaseFigure.Enabled = True

    fCreatedObject = True

    CosmoFigure.Application.Visible = True
End Sub

Sub Form_Load ()
    QuitApp.Enabled = False
    TestApp.Enabled = False
    GetCollection.Enabled = False
    TestCollection.Enabled = False
    ReleaseCollection.Enabled = False

    GetFigure.Enabled = False
    TestFigure.Enabled = False
    ReleaseFigure.Enabled = False

    fCreatedObject = False
End Sub

Sub GetCollection_Click ()
    Set Figures = Cosmo.Figures

    GetCollection.Enabled = False
    TestCollection.Enabled = True
    ReleaseCollection.Enabled = True

    GetFigure.Enabled = True
End Sub

Sub GetFigure_Click ()
    Set CosmoFigure = Figures.Add

    CreateFigure.Enabled = False
    GetFigure.Enabled = False
    TestFigure.Enabled = True
    ReleaseFigure.Enabled = True
End Sub

Sub QuitApp_Click ()
    Cosmo.Quit
    Set Cosmo = Nothing

    GetCollection.Enabled = False

    CreateApp.Enabled = True
    QuitApp.Enabled = False
    TestApp.Enabled = False
    CreateFigure.Enabled = True
End Sub

Sub ReleaseCollection_Click ()
    Set Figures = Nothing
    GetCollection.Enabled = True
    TestCollection.Enabled = False
    ReleaseCollection.Enabled = False

    GetFigure.Enabled = False
End Sub

Sub ReleaseFigure_Click ()
    CosmoFigure.[Close] (True)
    Set CosmoFigure = Nothing

    If (fCreatedObject) Then
        CreateFigure.Enabled = True
    Else
        GetFigure.Enabled = True
    End If

    TestFigure.Enabled = False
    ReleaseFigure.Enabled = False
End Sub

Sub TestApp_Click ()
    Dim Copy As Object

    'Show the window and hide it to show position
    Cosmo.Visible = True
    Cosmo.Visible = False

    'Move and resize the window and show it again
    Cosmo.Left = 35
    Cosmo.Top = 35
    Cosmo.Width = 450
    Cosmo.Height = 450

    Cosmo.Visible = True

    Debug.Print "Left=" + Str$(Cosmo.Left)
    Debug.Print "Top=" + Str$(Cosmo.Top)
    Debug.Print "Width=" + Str$(Cosmo.Width)
    Debug.Print "Height=" + Str$(Cosmo.Height)

    Debug.Print "Status Bar=" + Cosmo.StatusBar
    Cosmo.StatusBar = "This is a string from DispTest"
    Debug.Print "Status Bar=" + Cosmo.StatusBar

    Debug.Print "FullName=" + Cosmo.FullName
    Debug.Print "Name=" + Cosmo.Name
    Debug.Print "Path=" + Cosmo.Path
    Debug.Print "Caption=" + Cosmo.Caption

    Set Copy = Cosmo    'Should get 'Appication' property
    Debug.Print Copy.Name
    Set Copy = Nothing  'Just calls Release, no shutdown
End Sub

Sub TestCollection_Click ()
    Dim Fig1 As Object
    Dim Fig2 As Object
    Dim Fig3 As Object
    Dim Fig4 As Object
    Dim FiguresCopy As Object

    Set Fig1 = Figures.Open("c:\inole\chap13\cosmotst\disptest\samp1.cos")
    Fig1.Visible = True

    Set Fig2 = Figures.Add
    Fig2.Visible = True

    Set Fig3 = Figures.Open("c:\inole\chap13\cosmotst\disptest\samp2.cos")
    Fig3.Visible = True

    Debug.Print "Figures.Application=" + Figures.Application.Caption
    Debug.Print "Figures.Parent=" + Figures.Parent.Name

    cFigs = Figures.Count
    Debug.Print "Number of Figures=" + Str$(cFigs)

    Set FiguresCopy = Figures
    Set Fig4 = FiguresCopy.Item("c:\inole\chap13\cosmotst\disptest\samp1.cos")
    Debug.Print "Item from pathname: " + Fig4.Name
    Set Fig4 = Nothing
    Set FiguresCopy = Nothing

    'Closes all documents, rendering Fig1, 2, and 3 invalid
    Figures.[Close]
End Sub

Sub TestFigure_Click ()
    Dim FigTemp As Object

    CosmoFigure.Visible = True
    CosmoFigure.Left = 10
    CosmoFigure.Width = 300
    CosmoFigure.Top = 10
    CosmoFigure.Height = 320

    CosmoFigure.Visible = False
    CosmoFigure.Visible = True

    CosmoFigure.AddPoint 2000, 2000
    CosmoFigure.AddPoint 30000, 30000
    CosmoFigure.AddPoint 30000, 2000
    CosmoFigure.AddPoint 2000, 30000
    CosmoFigure.AddPoint 14880, 7522
    CosmoFigure.AddPoint 25463, 7823
    CosmoFigure.AddPoint 3241, 21894
    CosmoFigure.RemovePoint

    CosmoFigure.BackColor = RGB(0, 0, 255)
    CosmoFigure.LineColor = RGB(255, 255, 255)

    Debug.Print CosmoFigure.BackColor
    Debug.Print CosmoFigure.LineColor
    Debug.Print CosmoFigure.LineStyle

    CosmoFigure.LineStyle = 3

    CosmoFigure.Cut
    CosmoFigure.Paste

    Set FigTemp = CosmoFigure.Parent.Figures.Add
    FigTemp.Visible = True
    FigTemp.Activate
    CosmoFigure.Activate
    CosmoFigure.Copy
    FigTemp.Activate
    FigTemp.Paste
    FigTemp.[Close] (False)
    Set FigTemp = Nothing

    CosmoFigure.Activate
    CosmoFigure.SaveAs ("c:\temp\temp.cos")
    CosmoFigure.RemovePoint
    CosmoFigure.RevertToSaved
    CosmoFigure.LineStyle = 0
    CosmoFigure.Save

    CosmoFigure.RemovePoint
    CosmoFigure.RemovePoint
    CosmoFigure.RemovePoint

    CosmoFigure.Import ("c:\inole\chap13\cosmotst\disptest\samp1.cos")

    Set FigTemp = CosmoFigure.Application.ActiveFigure
    Debug.Print "Cosmo.ActiveFigure=" + FigTemp.Name
    Set FigTemp = Nothing
End Sub


COSMOTST.FRM

VERSION 4.00
Begin VB.Form Form1
   Caption         =   "Test Cosmo"
   ClientHeight    =   3000
   ClientLeft      =   9420
   ClientTop       =   1500
   ClientWidth     =   4800
   BeginProperty Font
      name            =   "MS Sans Serif"
      charset         =   1
      weight          =   700
      size            =   7.8
      underline       =   0   'False
      italic          =   0   'False
      strikethrough   =   0   'False
   EndProperty
   Height          =   3348
   Left            =   9384
   LinkTopic       =   "Form1"
   ScaleHeight     =   3000
   ScaleWidth      =   4800
   Top             =   1188
   Width           =   4872
   Begin VB.CommandButton CreateFigure
      Caption         =   "C&reate Figure"
      BeginProperty Font
         name            =   "MS Sans Serif"
         charset         =   1
         weight          =   700
         size            =   7.8
         underline       =   0   'False
         italic          =   0   'False
         strikethrough   =   0   'False
      EndProperty
      Height          =   615
      Left            =   120
      TabIndex        =   9
      Top             =   2280
      Width           =   1455
   End
   Begin VB.CommandButton ReleaseFigure
      Caption         =   "Release F&igure"
      BeginProperty Font
         name            =   "MS Sans Serif"
         charset         =   1
         weight          =   700
         size            =   7.8
         underline       =   0   'False
         italic          =   0   'False
         strikethrough   =   0   'False
      EndProperty
      Height          =   615
      Left            =   3240
      TabIndex        =   8
      Top             =   1560
      Width           =   1455
   End
   Begin VB.CommandButton TestFigure
      Caption         =   "Test Fi&gure"
      BeginProperty Font
         name            =   "MS Sans Serif"
         charset         =   1
         weight          =   700
         size            =   7.8
         underline       =   0   'False
         italic          =   0   'False
         strikethrough   =   0   'False
      EndProperty
      Height          =   615
      Left            =   1680
      TabIndex        =   7
      Top             =   1560
      Width           =   1455
   End
   Begin VB.CommandButton GetFigure
      Caption         =   "Get &Figure"
      BeginProperty Font
         name            =   "MS Sans Serif"
         charset         =   1
         weight          =   700
         size            =   7.8
         underline       =   0   'False
         italic          =   0   'False
         strikethrough   =   0   'False
      EndProperty
      Height          =   615
      Left            =   120
      TabIndex        =   6
      Top             =   1560
      Width           =   1455
   End
   Begin VB.CommandButton GetCollection
      Caption         =   "&Get Collection"
      BeginProperty Font
         name            =   "MS Sans Serif"
         charset         =   1
         weight          =   700
         size            =   7.8
         underline       =   0   'False
         italic          =   0   'False
         strikethrough   =   0   'False
      EndProperty
      Height          =   615
      Left            =   120
      TabIndex        =   5
      Top             =   840
      Width           =   1455
   End
   Begin VB.CommandButton ReleaseCollection
      Caption         =   "Release C&oll."
      BeginProperty Font
         name            =   "MS Sans Serif"
         charset         =   1
         weight          =   700
         size            =   7.8
         underline       =   0   'False
         italic          =   0   'False
         strikethrough   =   0   'False
      EndProperty
      Height          =   615
      Left            =   3240
      TabIndex        =   4
      Top             =   840
      Width           =   1455
   End
   Begin VB.CommandButton TestCollection
      Caption         =   "Test &Collection"
      BeginProperty Font
         name            =   "MS Sans Serif"
         charset         =   1
         weight          =   700
         size            =   7.8
         underline       =   0   'False
         italic          =   0   'False
         strikethrough   =   0   'False
      EndProperty
      Height          =   615
      Left            =   1680
      TabIndex        =   3
      Top             =   840
      Width           =   1455
   End
   Begin VB.CommandButton QuitApp
      Caption         =   "&Quit App"
      BeginProperty Font
         name            =   "MS Sans Serif"
         charset         =   1
         weight          =   700
         size            =   7.8
         underline       =   0   'False
         italic          =   0   'False
         strikethrough   =   0   'False
      EndProperty
      Height          =   615
      Left            =   3240
      TabIndex        =   2
      Top             =   120
      Width           =   1455
   End
   Begin VB.CommandButton CreateApp
      Caption         =   "&Create App"
      BeginProperty Font
         name            =   "MS Sans Serif"
         charset         =   1
         weight          =   700
         size            =   7.8
         underline       =   0   'False
         italic          =   0   'False
         strikethrough   =   0   'False
      EndProperty
      Height          =   615
      Left            =   120
      TabIndex        =   1
      Top             =   120
      Width           =   1455
   End
   Begin VB.CommandButton TestApp
      Caption         =   "&Test App"
      BeginProperty Font
         name            =   "MS Sans Serif"
         charset         =   1
         weight          =   700
         size            =   7.8
         underline       =   0   'False
         italic          =   0   'False
         strikethrough   =   0   'False
      EndProperty
      Height          =   615
      Left            =   1680
      TabIndex        =   0
      Top             =   120
      Width           =   1455
   End
End
Attribute VB_Name = "Form1"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Dim Cosmo As Object
Dim Figures As Object
Dim CosmoFigure As Object
Dim fCreatedObject As Integer

Private Sub CreateApp_Click()
    Set Cosmo = CreateObject("Cosmo.Application.2")
    CreateApp.Enabled = False
    QuitApp.Enabled = True
    TestApp.Enabled = True

    CreateFigure.Enabled = False
    GetCollection.Enabled = True

    fCreatedObject = False
End Sub

Private Sub CreateFigure_Click()
    Set CosmoFigure = CreateObject("Cosmo.Figure.2")

    CreateApp.Enabled = False
    QuitApp.Enabled = False
    TestApp.Enabled = False

    GetCollection.Enabled = False
    TestCollection.Enabled = False
    GetFigure.Enabled = False
    ReleaseCollection.Enabled = False

    CreateFigure.Enabled = False
    TestFigure.Enabled = True
    ReleaseFigure.Enabled = True

    fCreatedObject = True

    CosmoFigure.Application.Visible = True
End Sub

Private Sub Form_Load()
    QuitApp.Enabled = False
    TestApp.Enabled = False
    GetCollection.Enabled = False
    TestCollection.Enabled = False
    ReleaseCollection.Enabled = False

    GetFigure.Enabled = False
    TestFigure.Enabled = False
    ReleaseFigure.Enabled = False

    fCreatedObject = False
End Sub

Private Sub GetCollection_Click()
    Set Figures = Cosmo.Figures

    GetCollection.Enabled = False
    TestCollection.Enabled = True
    ReleaseCollection.Enabled = True

    GetFigure.Enabled = True
End Sub

Private Sub GetFigure_Click()
    Set CosmoFigure = Figures.Add

    CreateFigure.Enabled = False
    GetFigure.Enabled = False
    TestFigure.Enabled = True
    ReleaseFigure.Enabled = True
End Sub

Private Sub QuitApp_Click()
    'You can use either Quit method or set the
    'object variable to "Nothing" to destroy the
    'object and terminate the application
    Cosmo.Quit
    Set Cosmo = Nothing

    GetCollection.Enabled = False

    CreateApp.Enabled = True
    QuitApp.Enabled = False
    TestApp.Enabled = False
    CreateFigure.Enabled = True
End Sub

Private Sub ReleaseCollection_Click()
    Set Figures = Nothing
    GetCollection.Enabled = True
    TestCollection.Enabled = False
    ReleaseCollection.Enabled = False

    GetFigure.Enabled = False
End Sub

Private Sub ReleaseFigure_Click()
    CosmoFigure.Close (True)
    Set CosmoFigure = Nothing

    If (fCreatedObject) Then
        CreateFigure.Enabled = True
    Else
        GetFigure.Enabled = True
    End If

    TestFigure.Enabled = False
    ReleaseFigure.Enabled = False
End Sub

Private Sub TestApp_Click()
    Dim Copy As Object

    'Show the window and hide it to show position
    Cosmo.Visible = True
    Cosmo.Visible = False

    'Move and resize the window and show it again
    Cosmo.Left = 35
    Cosmo.Top = 35
    Cosmo.Width = 450
    Cosmo.Height = 450

    Cosmo.Visible = True

    Debug.Print "Left=" + Str$(Cosmo.Left)
    Debug.Print "Top=" + Str$(Cosmo.Top)
    Debug.Print "Width=" + Str$(Cosmo.Width)
    Debug.Print "Height=" + Str$(Cosmo.Height)

    Debug.Print "Status Bar=" + Cosmo.StatusBar
    Cosmo.StatusBar = "This is a string from VB"
    Debug.Print "Status Bar=" + Cosmo.StatusBar

    Debug.Print "FullName=" + Cosmo.FullName
    Debug.Print "Name=" + Cosmo.Name
    Debug.Print "Path=" + Cosmo.Path
    Debug.Print "Caption=" + Cosmo.Caption

    Set Copy = Cosmo    'Should get 'Appication' property
    Debug.Print Copy.Name
    Set Copy = Nothing  'Just calls Release, no shutdown
End Sub

Private Sub TestCollection_Click()
    Dim Fig1 As Object
    Dim Fig2 As Object
    Dim Fig3 As Object
    Dim Fig4 As Object
    Dim FiguresCopy As Object

    Set Fig1 = Figures.Open("c:\inole\chap14\cosmotst\newvb\samp1.cos")
    Fig1.Visible = True

    Set Fig2 = Figures.Add
    Fig2.Visible = True

    Set Fig3 = Figures.Open("c:\inole\chap14\cosmotst\newvb\samp2.cos")
    Fig3.Visible = True

    Debug.Print "Figures.Application=" + Figures.Application.Caption
    Debug.Print "Figures.Parent=" + Figures.Parent.Name

    cFigs = Figures.Count
    Debug.Print "Number of Figures=" + Str$(cFigs)

    Set FiguresCopy = Figures
    Set Fig4 = FiguresCopy.Item("c:\inole\chap14\cosmotst\newvb\samp1.cos")
    Debug.Print "Item from pathname: " + Fig4.Name
    Set Fig4 = Nothing
    Set FiguresCopy = Nothing

    'Closes all documents, rendering Fig1, 2, and 3 invalid
    Figures.Close
End Sub

Private Sub TestFigure_Click()
    Dim FigTemp As Object

    CosmoFigure.Visible = True
    CosmoFigure.Left = 10
    CosmoFigure.Width = 300
    CosmoFigure.Top = 10
    CosmoFigure.Height = 320

    CosmoFigure.Visible = False
    CosmoFigure.Visible = True

    CosmoFigure.AddPoint 2000, 2000
    CosmoFigure.AddPoint 30000, 30000
    CosmoFigure.AddPoint 30000, 2000
    CosmoFigure.AddPoint 2000, 30000
    CosmoFigure.AddPoint 14880, 7522
    CosmoFigure.AddPoint 25463, 7823
    CosmoFigure.AddPoint 3241, 21894
    CosmoFigure.RemovePoint

    CosmoFigure.BackColor = RGB(0, 0, 255)
    CosmoFigure.LineColor = RGB(255, 255, 255)

    Debug.Print CosmoFigure.BackColor
    Debug.Print CosmoFigure.LineColor
    Debug.Print CosmoFigure.LineStyle

    CosmoFigure.LineStyle = 3

    CosmoFigure.Cut
    CosmoFigure.Paste

    Set FigTemp = CosmoFigure.Application.Figures.Add
    FigTemp.Visible = True
    FigTemp.Activate
    CosmoFigure.Activate
    CosmoFigure.Copy
    FigTemp.Activate
    FigTemp.Paste
    FigTemp.Close (False)
    Set FigTemp = Nothing

    CosmoFigure.Activate
    CosmoFigure.SaveAs ("c:\temp\temp.cos")
    CosmoFigure.RemovePoint
    CosmoFigure.RevertToSaved
    CosmoFigure.LineStyle = 0
    CosmoFigure.Save

    CosmoFigure.RemovePoint
    CosmoFigure.RemovePoint
    CosmoFigure.RemovePoint

    CosmoFigure.Import ("c:\inole\chap14\cosmotst\newvb\samp1.cos")

    Set FigTemp = CosmoFigure.Application.ActiveFigure
    Debug.Print "Cosmo.ActiveFigure=" + FigTemp.Name
    Set FigTemp = Nothing
End Sub


AUTOCLI.CPP

/*
 * AUTOCLI.CPP
 * Automation Client Chapter 15
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "autocli.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    MSG         msg;
    PCApp       pApp;

    SETMESSAGEQUEUE;

    pApp=new CApp(hInst, hInstPrev, nCmdShow);

    if (NULL==pApp)
        return -1;

    if (pApp->Init())
        {
        while (GetMessage(&msg, NULL, 0,0 ))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    delete pApp;
    return msg.wParam;
    }

/*
 * AutoClientWndProc
 *
 * Purpose:
 *  Window class procedure.  Standard callback.
 */

LRESULT APIENTRY AutoClientWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCApp           pApp;
    WORD            wID;
    DISPID          dispID, dispIDParam;
    DISPPARAMS      dp;
    VARIANTARG      va;
    EXCEPINFO       exInfo;
    UINT            uErr;
    HRESULT         hr;
    TCHAR           szMsg[80];

    pApp=(PCApp)GetWindowLong(hWnd, AUTOCLIWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pApp=(PCApp)(((LPCREATESTRUCT)lParam)->lpCreateParams);
            SetWindowLong(hWnd, AUTOCLIWL_STRUCTURE, (LONG)pApp);
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_COMMAND:
            wID=LOWORD(wParam);

            switch (wID)
                {
                case IDM_GETSOUND:
                    //Find the dispID we need
                    hr=pApp->NameToID(OLETEXT("Sound"), &dispID);

                    if (FAILED(hr))
                        break;

                    //Get the property
                    SETNOPARAMS(dp);
                    VariantInit(&va);
                    hr=pApp->Invoke(dispID, DISPATCH_PROPERTYGET
                        , &dp, &va, &exInfo, NULL);

                    if (SUCCEEDED(hr))
                        {
                        wsprintf(szMsg, TEXT("Current 'Sound' is 0x%lX")
                            , va.lVal);
                        }
                    else
                        {
                        wsprintf(szMsg
                            , TEXT("Get 'Sound' failed with 0x%lX")
                            , hr);
                        }

                    pApp->Message(szMsg);
                    break;

                case IDM_SETSOUNDDEFAULT:
                case IDM_SETSOUNDHAND:
                case IDM_SETSOUNDQUESTION:
                case IDM_SETSOUNDEXCLAMATION:
                case IDM_SETSOUNDASTERISK:
                case IDM_SETSOUNDBOGUS:
                    //Find the dispID we need
                    hr=pApp->NameToID(OLETEXT("Sound"), &dispID);

                    if (FAILED(hr))
                        break;

                    /*
                     * Call IDispatch::Invoke passing wID which
                     * is a sound identifier (IDM_SETSOUNDDEFAULT
                     * has to set the sound to zero).  The "bogus"
                     * sound should cause an exception.
                     */

                    //Initialize arguments
                    VariantInit(&va);
                    va.vt=VT_I4;
                    va.lVal=(IDM_SETSOUNDDEFAULT==wID)
                        ? 0L : (long)(wID);

                    /*
                     * Passing a named DISPID_PROPERTYPUT
                     * is required when setting properties.
                     */
                    dispIDParam=DISPID_PROPERTYPUT;
                    SETDISPPARAMS(dp, 1, &va, 1, &dispIDParam);

                    hr=pApp->Invoke(dispID, DISPATCH_PROPERTYPUT
                        , &dp, NULL, &exInfo, NULL);

                    if (SUCCEEDED(hr))
                        {
                        /*
                         * Note that if the automation object can't
                         * raise an exception, setting the bogus
                         * value will return success although there
                         * is no change in the actual property.
                         */
                        lstrcpy(szMsg, TEXT("Set 'Sound' succeeded"));
                        }
                    else
                        {
                        wsprintf(szMsg
                            , TEXT("Set 'Sound' failed with 0x%lX"), hr);
                        }

                    pApp->Message(szMsg);
                    break;

                case IDM_BEEP:
                    if (NULL==pApp->m_pIDispatch)
                        break;

                    hr=pApp->NameToID(OLETEXT("Beep"), &dispID);

                    if (FAILED(hr))
                        break;

                    SETNOPARAMS(dp);
                    VariantInit(&va);
                    hr=pApp->Invoke(dispID, DISPATCH_METHOD, &dp
                        , &va, &exInfo, &uErr);

                    /*
                     * va will have the sound played as the return
                     * value of the Beep method.
                     */
                    if (SUCCEEDED(hr))
                        {
                        wsprintf(szMsg, TEXT("'Beep' played 0x%lX")
                            , va.lVal);
                        }
                    else
                        {
                        wsprintf(szMsg
                            , TEXT("'Beep' failed with 0x%lX"), hr);
                        }

                    pApp->Message(szMsg);
                    break;

                case IDM_EXIT:
                    PostMessage(hWnd, WM_CLOSE, 0, 0L);
                    break;
                }
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * CApp::CApp
 * CApp::~CApp
 *
 * Constructor Parameters: (from WinMain)
 *  hInst           HINSTANCE of the application.
 *  hInstPrev       HINSTANCE of a previous instance.
 *  nCmdShow        UINT specifying how to show the app window.
 *
 */

CApp::CApp(HINSTANCE hInst, HINSTANCE hInstPrev
    , UINT nCmdShow)
    {
    m_hInst=hInst;
    m_hInstPrev=hInstPrev;
    m_nCmdShow=nCmdShow;

    m_hWnd=NULL;
    m_fInitialized=FALSE;

    /*
     * Get the current LCID to send to the automation object.
     * Note that depending on the Beeper installed, this may or
     * may not work, especially if you are in a non-english or
     * non-German speaking locale.
     */
    m_lcid=GetUserDefaultLCID();

    m_szHelpDir[0]=(TCHAR)0;
    m_pIDispatch=NULL;
    return;
    }

CApp::~CApp(void)
    {
    if (NULL!=m_pIDispatch)
        {
        m_pIDispatch->Release();
        m_pIDispatch=NULL;
        }

    if (IsWindow(m_hWnd))
        DestroyWindow(m_hWnd);

    if (m_fInitialized)
        CoUninitialize();

    return;
    }

/*
 * CApp::Init
 *
 * Purpose:
 *  Initializes an CApp object by registering window classes,
 *  creating the main window, and doing anything else prone to
 *  failure such as calling CoInitialize.  If this function fails
 *  the caller should insure that the destructor is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::Init(void)
    {
    WNDCLASS    wc;
    HRESULT     hr;

    CHECKVER_OLE;

    if (FAILED(CoInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    if (!m_hInstPrev)
        {
        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = AutoClientWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = CBWNDEXTRA;
        wc.hInstance      = m_hInst;
        wc.hIcon          = LoadIcon(m_hInst, TEXT("Icon"));
        wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName   = MAKEINTRESOURCE(IDR_MENU);
        wc.lpszClassName  = TEXT("AUTOCLI");

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

    m_hWnd=CreateWindow(TEXT("AUTOCLI")
        , TEXT("Automation Object Client"), WS_OVERLAPPEDWINDOW
        , 35, 35, 450, 250, NULL, NULL, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    //Create the beeper object we want to manipulate.
    hr=CoCreateInstance(CLSID_Beeper, NULL, CLSCTX_INPROC_SERVER
        , IID_IDispatch, (PPVOID)&m_pIDispatch);

    if (FAILED(hr))
        {
        Message(TEXT("Failed to create object--terminating"), MB_OK);
        return FALSE;
        }

    //Try to get the help directory for the object's type information
    HelpDirFromCLSID(CLSID_Beeper, m_szHelpDir);

    ShowWindow(m_hWnd, m_nCmdShow);
    UpdateWindow(m_hWnd);

    return TRUE;
    }

/*
 * CApp::NameToID
 *
 * Purpose:
 *  Calls IDispatch::GetIDsOfNames for a single name to determine
 *  the DISPID to pass to IDispatch::Invoke.
 *
 * Parameters:
 *  pszName         OLECHAR * to the name to map.
 *  pDispID         DISPID * in which to store the dispID.
 *
 * Return Value:
 *  HRESULT         Return value of GetIDsOfNames
 */

HRESULT CApp::NameToID(OLECHAR *pszName, DISPID *pDispID)
    {
    HRESULT     hr;
    TCHAR       szMsg[80];

    hr=m_pIDispatch->GetIDsOfNames(IID_NULL, &pszName, 1
        , m_lcid, pDispID);

    if (FAILED(hr))
        {
       #ifdef WIN32ANSI
        char        szTemp[80];

        WideCharToMultiByte(CP_ACP, 0, pszName, -1, szTemp
            , 80, NULL, NULL);
        wsprintf(szMsg
            , TEXT("GetIDsOfNames on '%s' failed with 0x%lX")
            , szTemp, hr);
       #else
        wsprintf(szMsg
            , TEXT("GetIDsOfNames on '%s' failed with 0x%lX")
            , pszName, hr);
       #endif
        Message(szMsg);
        }

    return hr;
    }

/*
 * CApp::Invoke
 *
 * Purpose:
 *  Calls IDispatch::Invoke using the interface pointer we hold
 *  and using some default parameters.  All the other parameters
 *  to this function are passed straight to Invoke
 *
 * Return Value:
 *  HRESULT         Return value of Invoke.  If DISP_E_EXCEPTION,
 *                  this function generates the appropriate
 *                  message box.
 */

HRESULT CApp::Invoke(DISPID dispID, WORD wFlags, DISPPARAMS *pdp
    , VARIANT *pva, EXCEPINFO *pExInfo, UINT *puErr)
    {
    HRESULT     hr;
    LPTSTR      pszMsg=NULL;
    LPTSTR      pszFmt=NULL;
    UINT        uRet;
    UINT        uStyle;
    TCHAR       szSource[80];

    if (NULL==m_pIDispatch)
        return ResultFromScode(E_POINTER);

    hr=m_pIDispatch->Invoke(dispID, IID_NULL, m_lcid, wFlags
        , pdp, pva, pExInfo, puErr);

    if (DISP_E_EXCEPTION!=GetScode(hr))
        return hr;

    //If we're given a deferred filling function, fill now.
    if (NULL!=pExInfo->pfnDeferredFillIn)
        (*pExInfo->pfnDeferredFillIn)(pExInfo);

    /*
     * To handle the exception, display a message box with the
     * controller's name in the caption and a message:
     *
     *      "Error <code> in <source>:  <description>"
     *
     * where <error> is the exception code in pExInfo->wCode or
     * pExInfo->scode, <source> is the value of the ProdID
     * in pExInfo->bstrSource and <description> is in
     * pExInfo->bstrDescription.
     *
     * For simplicity, we assume that if description is set, so is
     * source.
     *
     * To be complete, if pExInfo->bstrHelpFile is non-NULL,
     * display a Help button.  If Help is pressed, launch WinHelp
     * with that filename and pExInfo->dwHelpContext.
     */

    //Go get the real source name from the ProgID
    lstrcpy(szSource, TEXT("Unknown"));

    if (NULL!=pExInfo->bstrSource)
        {
        LONG    lRet;

        //If this doesn't work, we'll have "Unknown" anyway
       #ifdef WIN32ANSI
        char        szTemp[80];
        WideCharToMultiByte(CP_ACP, 0, pExInfo->bstrSource, -1
            , szTemp, 80, NULL, NULL);
        RegQueryValue(HKEY_CLASSES_ROOT, szTemp, szSource, &lRet);
       #else
        RegQueryValue(HKEY_CLASSES_ROOT, pExInfo->bstrSource
            , szSource, &lRet);
       #endif

        SysFreeString(pExInfo->bstrSource);
        }

    if (NULL!=pExInfo->bstrDescription)
        {
        pszFmt=(LPTSTR)malloc(CCHSTRINGMAX*sizeof(TCHAR));

       #ifdef WIN32ANSI
        UINT    cch;
        char   *pszDesc;

        cch=wcslen(pExInfo->bstrDescription)+1;
        pszDesc=(LPSTR)malloc(cch);

        WideCharToMultiByte(CP_ACP, 0, pExInfo->bstrDescription, -1
            , pszDesc, cch, NULL, NULL);

        pszMsg=(LPTSTR)malloc(CCHSTRINGMAX+lstrlen(szSource)+cch);
       #else
        pszMsg=(LPTSTR)malloc((CCHSTRINGMAX+lstrlen(szSource)
            +lstrlen(pExInfo->bstrDescription))*sizeof(TCHAR));
       #endif

        if (0==pExInfo->wCode)
            {
            //Formatting for SCODE errors
            LoadString(m_hInst, IDS_MESSAGEEXCEPTIONSCODE, pszFmt
                , CCHSTRINGMAX);
            wsprintf(pszMsg, pszFmt, (long)pExInfo->scode
                , (LPTSTR)szSource
               #ifdef WIN32ANSI
                , pszDesc);
               #else
                , (LPTSTR)pExInfo->bstrDescription);
               #endif
            }
        else
            {
            //Formatting for wCode errors
            LoadString(m_hInst, IDS_MESSAGEEXCEPTION, pszFmt
                , CCHSTRINGMAX);
            wsprintf(pszMsg, pszFmt, (UINT)pExInfo->wCode
                , (LPTSTR)szSource
               #ifdef WIN32ANSI
                , pszDesc);
               #else
                , (LPTSTR)pExInfo->bstrDescription);
               #endif
            }

        free(pszFmt);
        }
    else
        {
        pszMsg=(LPTSTR)malloc(CCHSTRINGMAX*sizeof(TCHAR));
        LoadString(m_hInst, IDS_MESSAGEUNKNOWNEXCEPTION, pszMsg
            , CCHSTRINGMAX);
        }

    /*
     * In Windows 95 and Windows NT 3.51 there is an MB_HELP style
     * that we use in the exception message if pExInfo->bstrHelpFile
     * is non-NULL.  For Windows NT 3.5 and Windows 3.1x, we'll just use
     * a Cancel button to demonstrate since making a Help button is too
     * much effort for this sample (requires a custom dialog box and code
     * to resize the dialog based on the length of the description
     * string which MessageBox does automatically...)
     */
    uStyle=MB_OK | MB_ICONEXCLAMATION;

   #ifdef MB_HELP
    uStyle |=(NULL!=pExInfo->bstrHelpFile) ? MB_HELP : 0;
   #else
    uStyle |=(NULL!=pExInfo->bstrHelpFile) ? MB_OKCANCEL : 0;
   #endif

    uRet=Message(pszMsg, uStyle);

    if (NULL!=pszMsg)
        free(pszMsg);

   #ifdef MB_HELP
    if (IDHELP==uRet)
   #else
    if (IDCANCEL==uRet)
   #endif
        {
        TCHAR       szHelp[512];

    /*
         * If we read a HELPDIR, prepend it to the file.  Otherwise
         * just use the string we got since that's all we have.
         */
        if ((TCHAR)0!=m_szHelpDir[0])
            {
   #ifdef WIN32ANSI
    char    szTemp[256];

            WideCharToMultiByte(CP_ACP, 0, pExInfo->bstrHelpFile
                , -1, szTemp, 256, NULL, NULL);
            wsprintf(szHelp, TEXT("%s\\%s"), m_szHelpDir, szTemp);
           #else
            wsprintf(szHelp, TEXT("%s\\%s"), m_szHelpDir
                , pExInfo->bstrHelpFile);
   #endif
            }
        else
           #ifdef WIN32ANSI
            WideCharToMultiByte(CP_ACP, 0, pExInfo->bstrHelpFile
                , -1, szHelp, 512, NULL, NULL);
           #else
            lstrcpy(szHelp, pExInfo->bstrHelpFile);
           #endif

        WinHelp(NULL, szHelp, HELP_CONTEXT, pExInfo->dwHelpContext);
        }

    //We're responsible for cleaning up the strings.
    SysFreeString(pExInfo->bstrDescription);
    SysFreeString(pExInfo->bstrHelpFile);

    return ResultFromScode(DISP_E_EXCEPTION);
    }

/*
 * CApp::Message (overloaded)
 *
 * Purpose:
 *  Scribbles a message onto the client area of the window
 *  or displays the message in a message box if a message
 *  box style is given.
 *
 * Parameters:
 *  pszMsg          LPTSTR to the message string.
 *  uStyle          (message box only) UINT style bits
 *
 * Return Value:
 *  UINT            Return value of MessageBox (MessageBox version
 *                  only)
 */

void CApp::Message(LPTSTR pszMsg)
    {
    HDC     hDC;
    RECT    rc;

    hDC=GetDC(m_hWnd);
    GetClientRect(m_hWnd, &rc);

    SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
    SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));

    /*
     * We'll just be sloppy and clear the whole window as
     * well as write the string with one ExtTextOut call.
     * No word wrapping here...
     */

    ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rc, pszMsg
        , lstrlen(pszMsg), NULL);

    ReleaseDC(m_hWnd, hDC);
    return;
    }

UINT CApp::Message(LPTSTR pszMsg, UINT uStyle)
    {
    return MessageBox(m_hWnd, pszMsg, TEXT("Automation Client")
        , uStyle);
    }

/*
 * HelpDirFromCLSID
 *
 * Purpose:
 *  Given a CLSID, looks up the TypeLib entry in the registry then
 *  extracts the HELPDIR entry for that type information, storing
 *  the path in pszPath.
 *
 * Parameters:
 *  clsID           CLSID of the object we're looking up.
 *  pszPath         LPTSTR buffer in which to store the directory.
 *
 * Return Value:
 *  None
 */

void HelpDirFromCLSID(CLSID clsID, LPTSTR pszPath)
    {
    TCHAR       szCLSID[80];
    TCHAR       szKey[512];
    UINT        cch;
    long        lRet;

    if (NULL==pszPath)
        return;

    *pszPath=0;

    cch=sizeof(szCLSID)/sizeof(TCHAR);
    StringFromGUID2(clsID, szCLSID, cch);
    wsprintf(szKey, TEXT("CLSID\\%s\\TypeLib"), szCLSID);

    //Get LIBID from under CLSID
    if (ERROR_SUCCESS==RegQueryValue(HKEY_CLASSES_ROOT, szKey
        , szCLSID, &lRet))
        {
        //Get HELPDIR from under TypeLib
        wsprintf(szKey, TEXT("TypeLib\\%s\\HELPDIR"), szCLSID);
        RegQueryValue(HKEY_CLASSES_ROOT, szKey, pszPath, &lRet);
        }

    return;
    }


AUTOCLI.H

/*
 * AUTOCLI.H
 * Automation Client Chapter 15
 *
 * Definitions and structures.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _AUTOCLI_H_
#define _AUTOCLI_H_

#define INC_AUTOMATION
#define CHAPTER14
#define CHAPTER15
#include <inole.h>
#include <shellapi.h>
#include <malloc.h>

//Menu Resource ID and Commands
#define IDR_MENU                    1

#define IDM_GETSOUND                0x0001
#define IDM_BEEP                    0x0002
#define IDM_EXIT                    0x0003

//These match MB_* identifiers for MessageBeep
#define IDM_SETSOUNDDEFAULT         0x0008
#define IDM_SETSOUNDHAND            0x0010
#define IDM_SETSOUNDQUESTION        0x0020
#define IDM_SETSOUNDEXCLAMATION     0x0030
#define IDM_SETSOUNDASTERISK        0x0040

#define IDM_SETSOUNDBOGUS           0x0555

//Stringtable IDs
#define CCHSTRINGMAX                80
#define IDS_MESSAGEEXCEPTION        0
#define IDS_MESSAGEEXCEPTIONSCODE   1
#define IDS_MESSAGEUNKNOWNEXCEPTION 2

//AUTOCLI.CPP
LRESULT APIENTRY AutoClientWndProc(HWND, UINT, WPARAM, LPARAM);
void HelpDirFromCLSID(CLSID, LPTSTR);

class CApp
    {
    friend LRESULT APIENTRY AutoClientWndProc(HWND, UINT, WPARAM
        , LPARAM);

    protected:
        HINSTANCE       m_hInst;            //WinMain parameters
        HINSTANCE       m_hInstPrev;
        UINT            m_nCmdShow;

        HWND            m_hWnd;             //Main window handle
        BOOL            m_fInitialized;     //Did CoInitialize work?
        LCID            m_lcid;             //User locale
        TCHAR           m_szHelpDir[256];   //Object's help directory
        IDispatch      *m_pIDispatch;       //Interface we use

    public:
        CApp(HINSTANCE, HINSTANCE, UINT);
        ~CApp(void);

        BOOL    Init(void);
        HRESULT NameToID(OLECHAR *, DISPID *);
        HRESULT Invoke(DISPID, WORD, DISPPARAMS *, VARIANT *
                    , EXCEPINFO *, UINT *);
        void    Message(LPTSTR);
        UINT    Message(LPTSTR, UINT);
    };

typedef CApp *PCApp;

#define CBWNDEXTRA              sizeof(PCApp)
#define AUTOCLIWL_STRUCTURE     0

#endif //_AUTOCLI_H_


ISQUARE.H

/* This header file machine-generated by mktyplib.exe */
/* Interface to type library: SphereSquareLibrary */

#ifndef _SphereSquareLibrary_H_
#define _SphereSquareLibrary_H_

DEFINE_GUID(LIBID_SphereSquareLibrary,0x00021169,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

DEFINE_GUID(IID_ISphereSquare,0x00021168,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

/* Definition of interface: ISphereSquare */
DECLARE_INTERFACE_(ISphereSquare, IUnknown)
{
#ifndef NO_BASEINTERFACE_FUNCS

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;
#endif

    /* ISphereSquare methods */
    STDMETHOD_(double, get_Radius)(THIS) PURE;
    STDMETHOD_(void, put_Radius)(THIS_ double Radius) PURE;
    STDMETHOD_(double, get_Theta)(THIS) PURE;
    STDMETHOD_(void, put_Theta)(THIS_ double Theta) PURE;
    STDMETHOD_(double, get_Declination)(THIS) PURE;
    STDMETHOD_(void, put_Declination)(THIS_ double Declination) PURE;
    STDMETHOD_(long, get_BackColor)(THIS) PURE;
    STDMETHOD_(void, put_BackColor)(THIS_ long BackColor) PURE;
    STDMETHOD_(long, get_LineColorPositive)(THIS) PURE;
    STDMETHOD_(void, put_LineColorPositive)(THIS_ long LineColorPositive) PURE;
    STDMETHOD_(long, get_LineColorNegative)(THIS) PURE;
    STDMETHOD_(void, put_LineColorNegative)(THIS_ long LineColorNegative) PURE;
    STDMETHOD_(void, Draw)(THIS) PURE;
    STDMETHOD_(void, SetCenterPoint)(THIS_ int CenterX, int CenterY) PURE;
    STDMETHOD_(void, ShowWindow)(THIS_ int ShowCommand) PURE;
    STDMETHOD_(void, SetWindowPosition)(THIS_ int Left, int Top) PURE;
    STDMETHOD_(void, SetWindowSize)(THIS_ int Width, int Height) PURE;
};

DEFINE_GUID(DIID_DISphereSquare,0x0002116A,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

/* Definition of dispatch interface: DISphereSquare */
DECLARE_INTERFACE_(DISphereSquare, IDispatch)
{
#ifndef NO_BASEINTERFACE_FUNCS

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(THIS_ UINT FAR* pctinfo) PURE;

    STDMETHOD(GetTypeInfo)(
      THIS_
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo) PURE;

    STDMETHOD(GetIDsOfNames)(
      THIS_
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid) PURE;

    STDMETHOD(Invoke)(
      THIS_
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr) PURE;
#endif

/* Capable of dispatching all the methods of interface ISphereSquare */
};

DEFINE_GUID(CLSID_SphereSquare,0x00021167,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

class SphereSquare;

#endif


SQUARE.CPP

/*
 * SQUARE.CPP
 * Square Rendering/Calculation Automation Object Chapter 15
 *
 * An automation object that draws a square in various rotational
 * states and provides access to it through IDispatch.
 *
 * This file contains all the code for this sample.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "square.h"

ULONG       g_cObj=0;
ULONG       g_cLock=0;
HWND        g_hWnd=NULL;

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    MSG         msg;
    PCApp       pApp;

    SETMESSAGEQUEUE;

    pApp=new CApp(hInst, hInstPrev, pszCmdLine, nCmdShow);

    if (NULL==pApp)
        return -1;

    if (pApp->Init())
        {
        while (GetMessage(&msg, NULL, 0,0 ))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    delete pApp;
    return msg.wParam;
    }

/*
 * MainWndProc
 *
 * Purpose:
 *  Standard window class procedure.
 */

LRESULT APIENTRY MainWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    switch (iMsg)
        {
        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the object to call when it gets destroyed.
 *  We destroy the main window if the proper conditions are met
 *  for shutdown.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;

    //No more objects and no locks, shut the app down.
    if (0L==g_cObj && 0L==g_cLock && IsWindow(g_hWnd))
        PostMessage(g_hWnd, WM_CLOSE, 0, 0L);

    return;
    }

/*
 * CApp::CApp
 * CApp::~CApp
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the Application from WinMain
 *  hInstPrev       HINSTANCE of a previous instance from WinMain
 *  pszCmdLine      LPSTR of the command line.
 *  nCmdShow        UINT specifying how to show the app window,
 *                  from WinMain.
 */

CApp::CApp(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, UINT nCmdShow)
    {
    //Initialize WinMain parameter holders.
    m_hInst     =hInst;
    m_hInstPrev =hInstPrev;
    m_pszCmdLine=pszCmdLine;
    m_nCmdShow  =nCmdShow;

    m_dwRegCO=0;
    m_pIClassFactory=NULL;
    m_fInitialized=FALSE;
    return;
    }

CApp::~CApp(void)
    {
    //Opposite of CoRegisterClassObject; class factory ref is now 1
    if (0L!=m_dwRegCO)
        CoRevokeClassObject(m_dwRegCO);

    //The last Release, which frees the class factory.
    if (NULL!=m_pIClassFactory)
        m_pIClassFactory->Release();

    if (m_fInitialized)
        CoUninitialize();

    return;
    }

/*
 * CApp::Init
 *
 * Purpose:
 *  Initializes an CApp object by registering window classes,
 *  creating the main window, and doing anything else prone to
 *  failure.  If this function fails the caller should guarantee
 *  that the destructor is called.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::Init(void)
    {
    WNDCLASS    wc;
    HRESULT     hr;
    HWND        hWnd;

    if (lstrcmpiA(m_pszCmdLine, "-Embedding"))
        return FALSE;

    CHECKVER_OLE;

    if (FAILED(CoInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    if (!m_hInstPrev)
        {
        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = MainWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = 0;
        wc.hInstance      = m_hInst;
        wc.hIcon          = NULL;
        wc.hCursor        = NULL;
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName   = NULL;
        wc.lpszClassName  = TEXT("SphereSquare");

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

        //Child window in which to draw
        wc.lpfnWndProc    = SquareWndProc;
        wc.cbWndExtra     = CBSQUAREWNDEXTRA;
        wc.hCursor        = (HCURSOR)LoadCursor(NULL, IDC_ARROW);
        wc.lpszClassName  = SZCLASSSQUARE;

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

        }

    //This window is never shown
    hWnd=CreateWindow(TEXT("SphereSquare"), TEXT("SphereSquare")
        , WS_OVERLAPPEDWINDOW, 100, 100, 400, 400
        , NULL, NULL, m_hInst, NULL);

    if (NULL==hWnd)
        return FALSE;

    g_hWnd=hWnd;

    m_pIClassFactory=new CSquareClassFactory(hWnd, m_hInst);

    if (NULL==m_pIClassFactory)
        return FALSE;

    m_pIClassFactory->AddRef();

    hr=CoRegisterClassObject(CLSID_SphereSquare, m_pIClassFactory
        , CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE, &m_dwRegCO);

    if (FAILED(hr))
        return FALSE;

    return TRUE;
    }

//Class Factory implementation

/*
 * CSquareClassFactory::CSquareClassFactory
 * CSquareClassFactory::~CSquareClassFactory
 *
 * Constructor Parameters:
 *  hWnd            HWND of the application.
 *  hInst           HINSTANCE to pass to objects we create.
 */

CSquareClassFactory::CSquareClassFactory(HWND hWnd
    , HINSTANCE hInst)
    {
    m_cRef=0L;
    m_hWnd=hWnd;
    m_hInst=hInst;
    return;
    }

CSquareClassFactory::~CSquareClassFactory(void)
    {
    return;
    }

/*
 * CSquareClassFactory::QueryInterface
 * CSquareClassFactory::AddRef
 * CSquareClassFactory::Release
 */

STDMETHODIMP CSquareClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CSquareClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CSquareClassFactory::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CSquareClassFactory::CreateInstance
 * CSquareClassFactory::LockServer
 */

STDMETHODIMP CSquareClassFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCSquare    pObj;
    HRESULT     hr;

    *ppvObj=NULL;

    if (NULL!=pUnkOuter)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    pObj=new CSquare();

    g_cObj++;
    hr=ResultFromScode(E_OUTOFMEMORY);

    if (NULL!=pObj)
        {
        if (pObj->Init(m_hWnd, m_hInst))
            {
            hr=pObj->QueryInterface(riid, ppvObj);
            }
        }

    if (FAILED(hr))
        {
        if (NULL!=pObj)
            delete pObj;

        ObjectDestroyed();
        }

    return hr;
    }

STDMETHODIMP CSquareClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        {
        g_cLock--;

        //Centralizes shutdown in ObjectDestroyed
        g_cObj++;
        ObjectDestroyed();
        }

    return NOERROR;
    }

//Object implementation

/*
 * CSquare::CSquare
 * CSquare::~CSquare
 */

CSquare::CSquare(void)
    {
    m_cRef=0;
    m_hWnd=NULL;

    m_pITypeInfo=NULL;
    m_pIUnkDisp=NULL;

    m_cRadius=100.0;
    m_dDeclin=0.0;
    m_dTheta=0.0;

    m_xOrg=(int)(1.5*m_cRadius);
    m_yOrg=m_xOrg;

    m_crBack=GetSysColor(COLOR_WINDOW);
    m_crLinePos=RGB(255, 255, 0);
    m_crLineNeg=RGB(255, 0, 0);
    m_hPenPos=NULL;
    m_hPenNeg=NULL;

    m_cx=200;
    m_cy=220;
    m_xPos=100;
    m_yPos=100;

    return;
    }

CSquare::~CSquare(void)
    {
    ReleaseInterface(m_pITypeInfo);
    ReleaseInterface(m_pIUnkDisp);

    if (NULL!=m_hPenPos)
        DeleteObject(m_hPenPos);

    if (NULL!=m_hPenNeg)
        DeleteObject(m_hPenNeg);

    if (NULL!=m_hWnd)
        DestroyWindow(m_hWnd);

    return;
    }

/*
 * CSquare::Init
 *
 * Purpose:
 *  Creates the window in which we draw.
 */

BOOL CSquare::Init(HWND hWndOwner, HINSTANCE hInst)
    {
    ITypeLib       *pITypeLib;
    HRESULT         hr;

    //Create both default pens
    CreatePens(TRUE, TRUE);

    m_hWnd=CreateWindow(SZCLASSSQUARE, SZCLASSSQUARE
        , WS_CAPTION | WS_POPUP, m_xPos, m_yPos, m_cx, m_cy
        , hWndOwner, NULL, hInst, this);

    if (NULL==m_hWnd)
        return NULL;

    if (FAILED(LoadRegTypeLib(LIBID_SphereSquareLibrary, 1, 0
        , LANG_NEUTRAL, &pITypeLib)))
        {
        if (FAILED(LoadTypeLib(OLETEXT("SQUARE.TLB"), &pITypeLib)))
            return FALSE;
        }

    hr=pITypeLib->GetTypeInfoOfGuid(IID_ISphereSquare, &m_pITypeInfo);

    if (FAILED(hr))
        {
        pITypeLib->Release();
        return FALSE;
        }

    hr=CreateStdDispatch((IUnknown *)this, (ISphereSquare *)this
        , m_pITypeInfo, &m_pIUnkDisp);

    pITypeLib->Release();

    if (FAILED(hr))
        return FALSE;

    return TRUE;
    }

/*
 * CSquare::CreatePens
 *
 * Purpose:
 *  Creates one or both line color pens
 *
 * Parameters:
 *  fPositive       BOOL instructing us to create positive line color.
 *  fNegative       BOOL instructing us to create negative line color.
 *
 * Return Value:
 *  None
 */

void CSquare::CreatePens(BOOL fPositive, BOOL fNegative)
    {
    if (fPositive)
        {
        if (NULL!=m_hPenPos)
            DeleteObject(m_hPenPos);

        m_hPenPos=CreatePen(PS_SOLID, 1, m_crLinePos);

        if (NULL==m_hPenPos)
            m_hPenPos=(HPEN)GetStockObject(BLACK_PEN);
        }

    if (fNegative)
        {
        if (NULL!=m_hPenNeg)
            DeleteObject(m_hPenNeg);

        m_hPenNeg=CreatePen(PS_SOLID, 1, m_crLineNeg);

        if (NULL==m_hPenNeg)
            m_hPenNeg=(HPEN)GetStockObject(BLACK_PEN);
        }

    return;
    }

/*
 * CSquare::Draw
 *
 * Purpose:
 *  Draws the image of a square rotated according to m_dDeclin
 *  and m_dTheta with an edge length of m_cRadius.
 *
 * Parameters:
 *  hDC             HDC on which to draw.
 *
 * Return Value;
 *  None
 */

void CSquare::Draw(HDC hDC)
    {
    POINT           pt[5];
    int             x, y;
    double          rad;
    RECT            rc;
    HGDIOBJ         hGDI;

    SetBkColor(hDC, m_crBack);

    //Erase the background
    SetRect(&rc, 0, 0, m_xOrg*2, m_yOrg*2);
    ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);

    rad=cos(m_dDeclin);

    x=(int)(m_cRadius*cos(m_dTheta));
    y=(int)(m_cRadius*rad*sin(m_dTheta));

    SETPOINT(pt[0], m_xOrg+x, m_yOrg+y);
    SETPOINT(pt[2], m_xOrg-x, m_yOrg-y);

    x=(int)(m_cRadius*cos(m_dTheta+PI/2));
    y=(int)(m_cRadius*rad*sin(m_dTheta+PI/2));

    SETPOINT(pt[3], m_xOrg+x, m_yOrg+y);
    SETPOINT(pt[1], m_xOrg-x, m_yOrg-y);

    pt[4]=pt[0];

    /*
     * Set the line color according to which side of the square
     * is facing out of the screen.  We'll draw the positive
     * z lines a yellow (default), the negative face red
     * (default).  Both colors can be changed by the controller.
     *
     * To determine which side is out, we only have to look at
     * the rad value which is cos(m_dDeclin).  If positive, then
     * we're in the positive range.
     */

    hGDI=SelectObject(hDC, (rad > 0) ? m_hPenPos : m_hPenNeg);
    Polyline(hDC, pt, sizeof(pt)/sizeof(POINT));

    SelectObject(hDC, GetStockObject(WHITE_PEN));
    MoveToEx(hDC, m_xOrg, m_yOrg, NULL);
    LineTo(hDC, pt[0].x, pt[0].y);

    SelectObject(hDC, hGDI);
    return;
    }

/*
 * SquareWndProc
 *
 * Purpose:
 *  Window procedure for the Square window.
 */

LRESULT APIENTRY SquareWndProc(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    PCSquare    pcp;
    PAINTSTRUCT         ps;
    HDC                 hDC;
    RECT                rc;

    pcp=(PCSquare)GetWindowLong(hWnd, SQWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            pcp=(PCSquare)((LPCREATESTRUCT)lParam)
                ->lpCreateParams;
            SetWindowLong(hWnd, SQWL_STRUCTURE, (LONG)pcp);
            break;

        case WM_ERASEBKGND:
            hDC=(HDC)wParam;
            SetBkColor(hDC, pcp->m_crBack);
            GetClientRect(hWnd, &rc);
            ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
            break;

        case WM_PAINT:
            hDC=BeginPaint(hWnd, &ps);
            pcp->Draw(hDC);
            EndPaint(hWnd, &ps);
            break;

        case WM_CLOSE:
            /*
             * Eat this or Alt+F4 can destroy the window, even
             * though we don't have a system menu.
             */
            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }

/*
 * CSquare::QueryInterface
 * CSquare::AddRef
 * CSquare::Release
 *
 * Purpose:
 *  IUnknown members for CSquare object.
 */

STDMETHODIMP CSquare::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_ISphereSquare==riid)
        *ppv=this;

    if (IID_IDispatch==riid || DIID_DISphereSquare==riid)
        return m_pIUnkDisp->QueryInterface(IID_IDispatch, ppv);

    if (NULL!=*ppv)
        {
        ((IUnknown *)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CSquare::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CSquare::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    ObjectDestroyed();
    delete this;
    return 0L;
    }

//ISphereSquare members

STDMETHODIMP_(double) CSquare::get_Radius(void)
    {
    return m_cRadius;
    }

STDMETHODIMP_(void) CSquare::put_Radius(double cRadius)
    {
    //Only use positive radii.
    if (cRadius > 0.0)
        m_cRadius=cRadius;

    //We'll be lazy and not throw exceptions.
    return;
    }

STDMETHODIMP_(double) CSquare::get_Theta(void)
    {
    return m_dTheta;
    }

STDMETHODIMP_(void) CSquare::put_Theta(double dTheta)
    {
    //Anything is valid when you do trig
    m_dTheta=dTheta;
    return;
    }

STDMETHODIMP_(double) CSquare::get_Declination(void)
    {
    return m_dDeclin;
    }

STDMETHODIMP_(void) CSquare::put_Declination(double dDeclin)
    {
    m_dDeclin=dDeclin;
    return;
    }

STDMETHODIMP_(long) CSquare::get_BackColor(void)
    {
    return m_crBack;
    }

STDMETHODIMP_(void) CSquare::put_BackColor(long crBack)
    {
    m_crBack=crBack;
    return;
    }

STDMETHODIMP_(long) CSquare::get_LineColorPositive(void)
    {
    return m_crLinePos;
    }

STDMETHODIMP_(void) CSquare::put_LineColorPositive(long crLinePos)
    {
    m_crLinePos=crLinePos;
    return;
    }

STDMETHODIMP_(long) CSquare::get_LineColorNegative(void)
    {
    return m_crLineNeg;
    }

STDMETHODIMP_(void) CSquare::put_LineColorNegative(long crLineNeg)
    {
    m_crLineNeg=crLineNeg;
    return;
    }

STDMETHODIMP_(void) CSquare::Draw(void)
    {
    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);
    return;
    }

STDMETHODIMP_(void) CSquare::SetCenterPoint(int cx, int cy)
    {
    //No validation...we're deing lazy
    m_xOrg=cx;
    m_yOrg=cy;
    return;
    }

STDMETHODIMP_(void) CSquare::ShowWindow(int nCmdShow)
    {
    ::ShowWindow(m_hWnd, nCmdShow);
    UpdateWindow(m_hWnd);
    return;
    }

STDMETHODIMP_(void) CSquare::SetWindowPosition(int xPos, int yPos)
    {
    m_xPos=xPos;
    m_yPos=yPos;

    SetWindowPos(m_hWnd, NULL, m_xPos, m_yPos, 0, 0
        , SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
    return;
    }

STDMETHODIMP_(void) CSquare::SetWindowSize(int cx, int cy)
    {
    m_cx=cx;
    m_cy=cy;

    SetWindowPos(m_hWnd, NULL, 0, 0, m_cx, m_cy
        , SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
    return;
    }


SQUARE.H

/*
 * SQUARE.H
 * Square Automation Object Chapter 15
 *
 * Definitions, classes, and prototypes for an EXE that
 * provides Square objects to automation controllers
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _SQUARE_H_
#define _SQUARE_H_

//Get the object definitions
#define INC_AUTOMATION
#define CHAPTER15
#define GUIDS_FROM_TYPELIB
#include <inole.h>
#include <math.h>

#include "isquare.h"

LRESULT APIENTRY MainWndProc(HWND, UINT, WPARAM, LPARAM);

class CApp
    {
    friend LRESULT APIENTRY MainWndProc(HWND, UINT, WPARAM, LPARAM);

    protected:
        HINSTANCE       m_hInst;            //WinMain parameters
        HINSTANCE       m_hInstPrev;
        LPSTR           m_pszCmdLine;
        UINT            m_nCmdShow;

        BOOL            m_fInitialized;     //Did CoInitialize work?
        LPCLASSFACTORY  m_pIClassFactory;   //Our class factory
        DWORD           m_dwRegCO;          //Registration key

    public:
        CApp(HINSTANCE, HINSTANCE, LPSTR, UINT);
        ~CApp(void);
        BOOL Init(void);
    };

typedef CApp *PCApp;

void ObjectDestroyed(void);

class CSquareClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;
        HWND            m_hWnd;         //Main window (hidden)
        HINSTANCE       m_hInst;        //Module instance

    public:
        CSquareClassFactory(HWND, HINSTANCE);
        ~CSquareClassFactory(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP  CreateInstance(LPUNKNOWN, REFIID, PPVOID);
        STDMETHODIMP  LockServer(BOOL);
    };

typedef CSquareClassFactory *PCSquareClassFactory;

//Information for the window in which we draw
LRESULT APIENTRY SquareWndProc(HWND, UINT, WPARAM, LPARAM);

#define SZCLASSSQUARE       TEXT("SquareWindow")

class CSquare : public ISphereSquare
    {
    friend LRESULT APIENTRY SquareWndProc(HWND, UINT, WPARAM
        , LPARAM);

    protected:
        ULONG           m_cRef;         //Object reference count
        HWND            m_hWnd;         //Drawing window.

        ITypeInfo      *m_pITypeInfo;   //Loaded
        IUnknown       *m_pIUnkDisp;    //From CreateStdDispatch

        //Plotting variables
        double          m_cRadius;      //Edge length
        double          m_dTheta;       //Angle
        double          m_dDeclin;      //Declination

        int             m_xOrg, m_yOrg; //Origin point
        int             m_cx, m_cy;     //Window size
        int             m_xPos, m_yPos; //Window position

        COLORREF        m_crLinePos;    //Positive line color
        HPEN            m_hPenPos;      //Positive line pen

        COLORREF        m_crLineNeg;    //Negative line color
        HPEN            m_hPenNeg;      //Negative line pen

        COLORREF        m_crBack;       //Background color

    public:
        CSquare(void);
        ~CSquare(void);

        BOOL        Init(HWND, HINSTANCE);
        void        CreatePens(BOOL, BOOL);
        void        Draw(HDC);

        //IUnknown Members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //ISphereSquare members
        STDMETHODIMP_(double) get_Radius(void);
        STDMETHODIMP_(void) put_Radius(double);
        STDMETHODIMP_(double) get_Theta(void);
        STDMETHODIMP_(void) put_Theta(double);
        STDMETHODIMP_(double) get_Declination(void);
        STDMETHODIMP_(void) put_Declination(double);
        STDMETHODIMP_(long) get_BackColor(void);
        STDMETHODIMP_(void) put_BackColor(long);
        STDMETHODIMP_(long) get_LineColorPositive(void);
        STDMETHODIMP_(void) put_LineColorPositive(long);
        STDMETHODIMP_(long) get_LineColorNegative(void);
        STDMETHODIMP_(void) put_LineColorNegative(long);
        STDMETHODIMP_(void) Draw(void);
        STDMETHODIMP_(void) SetCenterPoint(int, int);
        STDMETHODIMP_(void) ShowWindow(int);
        STDMETHODIMP_(void) SetWindowPosition(int, int);
        STDMETHODIMP_(void) SetWindowSize(int, int);
    };

typedef CSquare *PCSquare;

#define CBSQUAREWNDEXTRA        sizeof(PCSquare)
#define SQWL_STRUCTURE          0

//Handy constant
#define PI 3.1415926535

#endif //_SQUARE_H_


ANIMATE.FRM

,
    S     AnimateForm
   Animate     7       T      B #    $ Form15    67   7    8T           Theta $        w   0           Animate   7    d    t     "     Stop    S&top x H G          "
 Start    &Start x   G        (     DeclineSpin  SpinButton        w   %     Declination $        w   0     &    ThetaSpin  SpinButton        w   *     DText    &Declination   ,            $     TText    &Theta                 S         _ _            Q QX


GLOBAL.BAS

_ _


SQUARE.FRM

VERSION 2.00
Begin Form RotateControl
   Caption         =   "Rotation Controller"
   ClientHeight    =   1695
   ClientLeft      =   7920
   ClientTop       =   3030
   ClientWidth     =   3795
   Height          =   2070
   Left            =   7875
   LinkTopic       =   "Form1"
   ScaleHeight     =   1695
   ScaleWidth      =   3795
   Top             =   2700
   Width           =   3885
   Begin CommandButton Animate
      Caption         =   "&Animate..."
      Height          =   495
      Left            =   240
      TabIndex        =   4
      Top             =   960
      Width           =   1095
   End
   Begin TextBox Radius
      Alignment       =   1  'Right Justify
      Height          =   375
      Left            =   2640
      TabIndex        =   2
      Text            =   "100"
      Top             =   1140
      Width           =   735
   End
   Begin SpinButton RadiusSpin
      Delay           =   0
      Height          =   375
      Left            =   3360
      Top             =   1140
      Width           =   255
   End
   Begin TextBox Theta
      Alignment       =   1  'Right Justify
      Height          =   375
      Left            =   2640
      TabIndex        =   3
      Text            =   "0"
      Top             =   600
      Width           =   735
   End
   Begin SpinButton ThetaSpin
      Delay           =   0
      Height          =   375
      Left            =   3360
      Top             =   600
      Width           =   255
   End
   Begin TextBox Declination
      Alignment       =   1  'Right Justify
      Height          =   375
      Left            =   2640
      TabIndex        =   1
      Text            =   "0"
      Top             =   120
      Width           =   735
   End
   Begin SpinButton DeclineSpin
      Delay           =   0
      Height          =   375
      Left            =   3360
      Top             =   120
      Width           =   255
   End
   Begin CommandButton Draw
      Caption         =   "&Draw"
      Default         =   -1  'True
      Height          =   495
      Left            =   240
      TabIndex        =   0
      Top             =   240
      Width           =   1095
   End
   Begin Label RadiusText
      Alignment       =   1  'Right Justify
      Caption         =   "&Radius"
      Height          =   255
      Left            =   1440
      TabIndex        =   7
      Top             =   1200
      Width           =   1095
   End
   Begin Label TText
      Alignment       =   1  'Right Justify
      Caption         =   "&Theta"
      Height          =   255
      Left            =   1440
      TabIndex        =   6
      Top             =   660
      Width           =   1095
   End
   Begin Label DText
      Alignment       =   2  'Center
      Caption         =   "&Declination"
      Height          =   255
      Left            =   1440
      TabIndex        =   5
      Top             =   180
      Width           =   1155
   End
End

Sub Animate_Click ()
    AnimateForm.Show
End Sub

Sub Declination_Change ()
    d = Val(Declination.Text)
    Square.Declination = d / 57.29577951
    Square.Draw
End Sub

Sub DeclineSpin_SpinDown ()
    d = Val(Declination.Text) - 1

    If (d < 0) Then
        d = 0
    End If

    Declination.Text = Str$(d)
End Sub

Sub DeclineSpin_SpinUp ()
    d = Val(Declination.Text) + 1

    If (d > 180) Then
        d = 180
    End If

    Declination.Text = Str$(d)
End Sub

Sub Draw_Click ()
    Square.Declination = Val(Declination.Text) / 57.29577951
    Square.Theta = Val(Theta.Text) / 57.29577951
    Square.Radius = Val(Radius.Text)
    Square.Draw
End Sub

Sub Form_Load ()
    Set Square = CreateObject("SphereSquare.Object")
    Declination.Text = Str$(Square.Declination)
    Theta.Text = Str$(Square.Theta)
    Radius.Text = Str$(Square.Radius)

    Square.BackColor = RGB(0, 0, 0)
    Square.LineColorPositive = RGB(255, 255, 0)
    Square.LineColorNegative = RGB(0, 255, 0)

    Square.SetWindowPosition 100, 100
    Square.SetWindowSize 300, 300
    Square.ShowWindow (SW_SHOW)
End Sub

Sub Form_Unload (Cancel As Integer)
    Set Square = Nothing
End Sub

Sub Radius_Change ()
    r = Val(Radius.Text)
    Square.Radius = r
    Square.Draw
End Sub

Sub RadiusSpin_SpinDown ()
    r = Val(Radius.Text) - 1

    If (r < 0) Then
        r = 0
    End If

    Radius.Text = Str$(r)
    'Square.Radius = r
    'Square.Draw
End Sub

Sub RadiusSpin_SpinUp ()
    r = Val(Radius.Text) + 1

    If (r > 500) Then
        r = 500
    End If

    Radius.Text = Str$(r)
    'Square.Radius = r
    'Square.Draw
End Sub

Sub Theta_Change ()
    th = Val(Theta.Text)
    Square.Theta = th / 57.29577951
    Square.Draw
End Sub

Sub ThetaSpin_SpinDown ()
    th = Val(Theta.Text) - 1

    If (th < 0) Then
        th = 359
    End If

    Theta.Text = Str$(th)
End Sub

Sub ThetaSpin_SpinUp ()
    th = Val(Theta.Text) + 1

    If (th > 359) Then
        th = 0
    End If

    Theta.Text = Str$(th)
End Sub


ANIMATE.FRM

VERSION 4.00
Begin VB.Form AnimateForm
   Caption         =   "Animate"
   ClientHeight    =   1620
   ClientLeft      =   7965
   ClientTop       =   5175
   ClientWidth     =   3735
   BeginProperty Font
      name            =   "MS Sans Serif"
      charset         =   1
      weight          =   700
      size            =   8.25
      underline       =   0   'False
      italic          =   0   'False
      strikethrough   =   0   'False
   EndProperty
   Height          =   2025
   Left            =   7905
   LinkTopic       =   "Form1"
   ScaleHeight     =   1620
   ScaleWidth      =   3735
   Top             =   4830
   Width           =   3855
   Begin VB.TextBox Theta
      Alignment       =   1  'Right Justify
      Height          =   375
      Left            =   2520
      TabIndex        =   0
      Text            =   "0"
      Top             =   720
      Width           =   735
   End
   Begin VB.Timer Animate
      Interval        =   1
      Left            =   1380
      Top             =   1140
   End
   Begin VB.CommandButton Stop
      Caption         =   "S&top"
      Default         =   -1  'True
      Height          =   495
      Left            =   120
      TabIndex        =   2
      Top             =   840
      Width           =   1095
   End
   Begin VB.CommandButton Start
      Caption         =   "&Start"
      Height          =   495
      Left            =   120
      TabIndex        =   3
      Top             =   240
      Width           =   1095
   End
   Begin VB.TextBox Declination
      Alignment       =   1  'Right Justify
      Height          =   375
      Left            =   2520
      TabIndex        =   1
      Text            =   "0"
      Top             =   240
      Width           =   735
   End
   Begin Spin.SpinButton ThetaSpin
      Height          =   375
      Left            =   3240
      TabIndex        =   7
      Top             =   720
      Width           =   255
      _version        =   65536
      _extentx        =   450
      _extenty        =   661
      _stockprops     =   73
      delay           =   100
   End
   Begin Spin.SpinButton DeclineSpin
      Height          =   375
      Left            =   3240
      TabIndex        =   6
      Top             =   240
      Width           =   255
      _version        =   65536
      _extentx        =   450
      _extenty        =   661
      _stockprops     =   73
      delay           =   100
   End
   Begin VB.Label DText
      Alignment       =   1  'Right Justify
      Caption         =   "&Declination"
      Height          =   255
      Left            =   1440
      TabIndex        =   5
      Top             =   300
      Width           =   975
   End
   Begin VB.Label TText
      Alignment       =   1  'Right Justify
      Caption         =   "&Theta"
      Height          =   255
      Left            =   1440
      TabIndex        =   4
      Top             =   780
      Width           =   975
   End
End
Attribute VB_Name = "AnimateForm"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
Dim fStart As Integer

Private Sub Animate_Timer()
    If fStart Then
        Square.Declination = Square.Declination + (Val(Declination.Text) / 57.29577951)
        Square.Theta = Square.Theta + (Val(Theta.Text) / 57.29577951)

        Square.Draw
    End If
End Sub

Private Sub DeclineSpin_SpinDown()
    d = Val(Declination.Text) - 1

    If (d < 0) Then
        d = 45
    End If

    Declination.Text = Str$(d)
End Sub

Private Sub DeclineSpin_SpinUp()
    d = Val(Declination.Text) + 1

    If (d > 45) Then
        d = 0
    End If

    Declination.Text = Str$(d)
End Sub

Private Sub DeclineSpin_Click()

End Sub

Private Sub Form_Load()
    fStart = False

    Declination.Text = "0"
    Theta.Text = "0"
End Sub

Private Sub Start_Click()
    fStart = True
End Sub

Private Sub Stop_Click()
    fStart = False
End Sub

Private Sub ThetaSpin_SpinDown()
    th = Val(Theta.Text) - 1

    If (th < 0) Then
        th = 45
    End If

    Theta.Text = Str$(th)
End Sub

Private Sub ThetaSpin_SpinUp()
    th = Val(Theta.Text) + 1

    If (th > 45) Then
        th = 0
    End If

    Theta.Text = Str$(th)
End Sub


GLOBAL.BAS

Attribute VB_Name = "GLOBAL"
Global Square As Object

Global Const SW_HIDE = 0, SW_SHOW = 1
Global Const SW_MINIMIZE = 2, SW_MAXIMIZE = 3


ROTATE.FRM

VERSION 4.00
Begin VB.Form RotateControl
   Caption         =   "Rotation Controller"
   ClientHeight    =   1695
   ClientLeft      =   7920
   ClientTop       =   3030
   ClientWidth     =   3795
   BeginProperty Font
      name            =   "MS Sans Serif"
      charset         =   1
      weight          =   700
      size            =   8.25
      underline       =   0   'False
      italic          =   0   'False
      strikethrough   =   0   'False
   EndProperty
   Height          =   2100
   Left            =   7860
   LinkTopic       =   "Form1"
   ScaleHeight     =   1695
   ScaleWidth      =   3795
   Top             =   2685
   Width           =   3915
   Begin VB.CommandButton Animate
      Caption         =   "&Animate..."
      Height          =   495
      Left            =   240
      TabIndex        =   4
      Top             =   960
      Width           =   1095
   End
   Begin VB.TextBox Radius
      Alignment       =   1  'Right Justify
      Height          =   375
      Left            =   2640
      TabIndex        =   2
      Text            =   "100"
      Top             =   1140
      Width           =   735
   End
   Begin VB.TextBox Theta
      Alignment       =   1  'Right Justify
      Height          =   375
      Left            =   2640
      TabIndex        =   3
      Text            =   "0"
      Top             =   600
      Width           =   735
   End
   Begin VB.TextBox Declination
      Alignment       =   1  'Right Justify
      Height          =   375
      Left            =   2640
      TabIndex        =   1
      Text            =   "0"
      Top             =   120
      Width           =   735
   End
   Begin VB.CommandButton Draw
      Caption         =   "&Draw"
      Default         =   -1  'True
      Height          =   495
      Left            =   240
      TabIndex        =   0
      Top             =   240
      Width           =   1095
   End
   Begin Spin.SpinButton RadiusSpin
      Height          =   375
      Left            =   3360
      TabIndex        =   8
      Top             =   1140
      Width           =   255
      _version        =   65536
      _extentx        =   450
      _extenty        =   661
      _stockprops     =   73
      forecolor       =   -2147483640
      backcolor       =   -2147483643
      delay           =   0
   End
   Begin Spin.SpinButton ThetaSpin
      Height          =   375
      Left            =   3360
      TabIndex        =   9
      Top             =   600
      Width           =   255
      _version        =   65536
      _extentx        =   450
      _extenty        =   661
      _stockprops     =   73
      forecolor       =   -2147483640
      backcolor       =   -2147483643
      delay           =   0
   End
   Begin Spin.SpinButton DeclineSpin
      Height          =   375
      Left            =   3360
      TabIndex        =   10
      Top             =   120
      Width           =   255
      _version        =   65536
      _extentx        =   450
      _extenty        =   661
      _stockprops     =   73
      forecolor       =   -2147483640
      backcolor       =   -2147483643
      delay           =   0
   End
   Begin VB.Label RadiusText
      Alignment       =   1  'Right Justify
      Caption         =   "&Radius"
      Height          =   255
      Left            =   1440
      TabIndex        =   7
      Top             =   1200
      Width           =   1095
   End
   Begin VB.Label TText
      Alignment       =   1  'Right Justify
      Caption         =   "&Theta"
      Height          =   255
      Left            =   1440
      TabIndex        =   6
      Top             =   660
      Width           =   1095
   End
   Begin VB.Label DText
      Alignment       =   2  'Center
      Caption         =   "&Declination"
      Height          =   255
      Left            =   1440
      TabIndex        =   5
      Top             =   180
      Width           =   1155
   End
End
Attribute VB_Name = "RotateControl"
Attribute VB_Creatable = False
Attribute VB_Exposed = False

Private Sub Animate_Click()
    AnimateForm.Show
End Sub

Private Sub Declination_Change()
    d = Val(Declination.Text)
    Square.Declination = d / 57.29577951
    Square.Draw
End Sub

Private Sub DeclineSpin_SpinDown()
    d = Val(Declination.Text) - 1

    If (d < 0) Then
        d = 0
    End If

    Declination.Text = Str$(d)
End Sub

Private Sub DeclineSpin_SpinUp()
    d = Val(Declination.Text) + 1

    If (d > 180) Then
        d = 180
    End If

    Declination.Text = Str$(d)
End Sub

Private Sub Draw_Click()
    Square.Declination = Val(Declination.Text) / 57.29577951
    Square.Theta = Val(Theta.Text) / 57.29577951
    Square.Radius = Val(Radius.Text)
    Square.Draw
End Sub

Private Sub Form_Load()
    Set Square = CreateObject("SphereSquare.Object")
    Declination.Text = Str$(Square.Declination)
    Theta.Text = Str$(Square.Theta)
    Radius.Text = Str$(Square.Radius)

    Square.BackColor = RGB(0, 0, 0)
    Square.LineColorPositive = RGB(255, 255, 0)
    Square.LineColorNegative = RGB(0, 255, 0)

    Square.SetWindowPosition 100, 100
    Square.SetWindowSize 300, 300
    Square.ShowWindow (SW_SHOW)
End Sub

Private Sub Form_Unload(Cancel As Integer)
    Set Square = Nothing
End Sub

Private Sub Radius_Change()
    r = Val(Radius.Text)
    Square.Radius = r
    Square.Draw
End Sub

Private Sub RadiusSpin_SpinDown()
    r = Val(Radius.Text) - 1

    If (r < 0) Then
        r = 0
    End If

    Radius.Text = Str$(r)
    'Square.Radius = r
    'Square.Draw
End Sub

Private Sub RadiusSpin_SpinUp()
    r = Val(Radius.Text) + 1

    If (r > 500) Then
        r = 500
    End If

    Radius.Text = Str$(r)
    'Square.Radius = r
    'Square.Draw
End Sub

Private Sub Theta_Change()
    th = Val(Theta.Text)
    Square.Theta = th / 57.29577951
    Square.Draw
End Sub

Private Sub ThetaSpin_SpinDown()
    th = Val(Theta.Text) - 1

    If (th < 0) Then
        th = 359
    End If

    Theta.Text = Str$(th)
End Sub

Private Sub ThetaSpin_SpinUp()
    th = Val(Theta.Text) + 1

    If (th > 359) Then
        th = 0
    End If

    Theta.Text = Str$(th)
End Sub


AUTOCLI2.CPP

/*
 * AUTOCLI2.CPP
 * Automation Client with Property Pages Chapter 16
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "autocli2.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 */

int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    MSG         msg;
    PCApp       pApp;

    SETMESSAGEQUEUE;

    pApp=new CApp(hInst, hInstPrev, nCmdShow);

    if (NULL==pApp)
        return -1;

    if (pApp->Init())
        {
        while (GetMessage(&msg, NULL, 0,0 ))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    delete pApp;
    return msg.wParam;
    }

/*
 * AutoClientWndProc
 *
 * Purpose:
 *  Window class procedure.  Standard callback.
 */

LRESULT APIENTRY AutoClientWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCApp           pApp;
    WORD            wID;
    DISPID          dispID, dispIDParam;
    DISPPARAMS      dp;
    VARIANTARG      va;
    EXCEPINFO       exInfo;
    UINT            uErr;
    HRESULT         hr;
    TCHAR           szMsg[80];

    pApp=(PCApp)GetWindowLong(hWnd, AUTOCLI2WL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pApp=(PCApp)(((LPCREATESTRUCT)lParam)->lpCreateParams);
            SetWindowLong(hWnd, AUTOCLI2WL_STRUCTURE, (LONG)pApp);
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_COMMAND:
            wID=LOWORD(wParam);

            switch (wID)
                {
                case IDM_GETSOUND:
                    //Find the dispID we need
                    hr=pApp->NameToID(OLETEXT("Sound"), &dispID);

                    if (FAILED(hr))
                        break;

                    //Get the property
                    SETNOPARAMS(dp);
                    hr=pApp->Invoke(dispID, DISPATCH_PROPERTYGET
                        , &dp, &va, &exInfo, NULL);

                    if (SUCCEEDED(hr))
                        {
                        wsprintf(szMsg, TEXT("Current 'Sound' is 0x%lX")
                            , va.lVal);
                        }
                    else
                        {
                        wsprintf(szMsg
                            , TEXT("Get 'Sound' failed with 0x%lX")
                            , hr);
                        }

                    pApp->Message(szMsg);
                    break;

                case IDM_SETSOUNDDEFAULT:
                case IDM_SETSOUNDHAND:
                case IDM_SETSOUNDQUESTION:
                case IDM_SETSOUNDEXCLAMATION:
                case IDM_SETSOUNDASTERISK:
                case IDM_SETSOUNDBOGUS:
                    //Find the dispID we need
                    hr=pApp->NameToID(OLETEXT("Sound"), &dispID);

                    if (FAILED(hr))
                        break;

                    /*
                     * Call IDispatch::Invoke passing wID which
                     * is a sound identifier (IDM_SETSOUNDDEFAULT
                     * has to set the sound to zero).  The "bogus"
                     * sound should cause an exception.
                     */

                    //Initialize arguments
                    VariantInit(&va);
                    va.vt=VT_I4;
                    va.lVal=(IDM_SETSOUNDDEFAULT==wID)
                        ? 0L : (long)(wID);

                    /*
                     * Passing a named DISPID_PROPERTYPUT
                     * is required when setting properties.
                     */
                    dispIDParam=DISPID_PROPERTYPUT;
                    SETDISPPARAMS(dp, 1, &va, 1, &dispIDParam);

                    hr=pApp->Invoke(dispID, DISPATCH_PROPERTYPUT
                        , &dp, NULL, &exInfo, NULL);

                    /*
                     * Success messages come through
                     * IPropertyNotifySink::OnChanged.
                     */
                    if (FAILED(hr))
                        {
                        wsprintf(szMsg
                            , TEXT("Set 'Sound' failed with 0x%lX"), hr);
                        }

                    break;

                case IDM_BEEP:
                    if (NULL==pApp->m_pIDispatch)
                        break;

                    hr=pApp->NameToID(OLETEXT("Beep"), &dispID);

                    if (FAILED(hr))
                        break;

                    SETNOPARAMS(dp);
                    hr=pApp->Invoke(dispID, DISPATCH_METHOD, &dp
                        , &va, &exInfo, &uErr);

                    /*
                     * va will have the sound played as the return
                     * value of the Beep method.
                     */
                    if (SUCCEEDED(hr))
                        {
                        wsprintf(szMsg, TEXT("'Beep' played 0x%lX")
                            , va.lVal);
                        }
                    else
                        {
                        wsprintf(szMsg
                            , TEXT("'Beep' failed with 0x%lX"), hr);
                        }

                    pApp->Message(szMsg);
                    break;

                //CHAPTER16MOD
                case IDM_PROPERTIES:
                    pApp->ShowProperties();
                    break;

                case IDM_ENFORCEREADONLY:
                    pApp->m_fReadOnly=!pApp->m_fReadOnly;
                    CheckMenuItem(GetMenu(hWnd), wID
                        , pApp->m_fReadOnly
                        ? MF_CHECKED : MF_UNCHECKED);
                    break;
                //End CHAPTER16MOD

                case IDM_EXIT:
                    PostMessage(hWnd, WM_CLOSE, 0, 0L);
                    break;
                }
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * CApp::CApp
 * CApp::~CApp
 *
 * Constructor Parameters: (from WinMain)
 *  hInst           HINSTANCE of the application.
 *  hInstPrev       HINSTANCE of a previous instance.
 *  nCmdShow        UINT specifying how to show the app window.
 *
 */

CApp::CApp(HINSTANCE hInst, HINSTANCE hInstPrev
    , UINT nCmdShow)
    {
    m_hInst=hInst;
    m_hInstPrev=hInstPrev;
    m_nCmdShow=nCmdShow;

    m_hWnd=NULL;
    m_fInitialized=FALSE;
    m_lcid=GetUserDefaultLCID();

    m_szHelpDir[0]=(TCHAR)0;
    m_pIDispatch=NULL;

    //CHAPTER16MOD
    m_fReadOnly=FALSE;
    m_pSink=NULL;
    m_pIConnPt=NULL;
    m_dwConn=0L;
    //End CHAPTER16MOD

    return;
    }

CApp::~CApp(void)
    {
    //CHAPTER16MOD
    if (0!=m_dwConn && NULL!=m_pIConnPt)
        {
        m_pIConnPt->Unadvise(m_dwConn);
        ReleaseInterface(m_pIConnPt);
        }

    ReleaseInterface(m_pSink);
    //End CHAPTER16MOD

    if (NULL!=m_pIDispatch)
        {
        m_pIDispatch->Release();
        m_pIDispatch=NULL;
        }

    if (IsWindow(m_hWnd))
        DestroyWindow(m_hWnd);

    if (m_fInitialized)
        CoUninitialize();

    return;
    }

/*
 * CApp::Init
 *
 * Purpose:
 *  Initializes an CApp object by registering window classes,
 *  creating the main window, and doing anything else prone to
 *  failure such as calling CoInitialize.  If this function fails
 *  the caller should insure that the destructor is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CApp::Init(void)
    {
    WNDCLASS    wc;
    HRESULT     hr;

    CHECKVER_OLE;

    if (FAILED(CoInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    if (!m_hInstPrev)
        {
        wc.style          = CS_HREDRAW | CS_VREDRAW;
        wc.lpfnWndProc    = AutoClientWndProc;
        wc.cbClsExtra     = 0;
        wc.cbWndExtra     = CBWNDEXTRA;
        wc.hInstance      = m_hInst;
        wc.hIcon          = LoadIcon(m_hInst, TEXT("Icon"));
        wc.hCursor        = LoadCursor(NULL, IDC_ARROW);
        wc.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
        wc.lpszMenuName   = MAKEINTRESOURCE(IDR_MENU);
        wc.lpszClassName  = TEXT("AUTOCLI");

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

    m_hWnd=CreateWindow(TEXT("AUTOCLI")
        , TEXT("Property Page Client"), WS_OVERLAPPEDWINDOW
        , 35, 35, 450, 250, NULL, NULL, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    //Create the beeper object we want to manipulate.
    hr=CoCreateInstance(CLSID_Beeper, NULL, CLSCTX_INPROC_SERVER
        , IID_IDispatch, (PPVOID)&m_pIDispatch);

    if (FAILED(hr))
        {
        Message(TEXT("Failed to create object--terminating"), MB_OK);
        return FALSE;
        }

    //Try to get the help directory for the object's type information
    HelpDirFromCLSID(CLSID_Beeper, m_szHelpDir);

    //CHAPTER16MOD
    /*
     * Try to hook up our IPropertyNotifySink to the object.
     * On failure, we'll just gray out "Enforce Read-Only" on
     * the menu, but otherwise we continue to run.
     */
    m_pSink=new CPropertyNotifySink(this);

    if (NULL!=m_pSink)
        {
        IConnectionPointContainer  *pICPC;
        HRESULT                     hr;

        m_pSink->AddRef();  //Release destroys it

        hr=m_pIDispatch->QueryInterface(IID_IConnectionPointContainer
            , (void **)&pICPC);

        if (SUCCEEDED(hr))
            {
            if (SUCCEEDED(pICPC->FindConnectionPoint
                (IID_IPropertyNotifySink, &m_pIConnPt)))
                {
                /*
                 * This is the only way m_dwConn is set to non-
                 * zero.  We hold onto m_pIConnPt until we close.
                 */
                m_pIConnPt->Advise(m_pSink, &m_dwConn);
                }

            pICPC->Release();
            }
        }

    if (0==m_dwConn)
        {
        EnableMenuItem(GetMenu(m_hWnd), IDM_ENFORCEREADONLY
            , MF_DISABLED | MF_GRAYED | MF_BYCOMMAND);
        }
    //End CHAPTER16MOD

    ShowWindow(m_hWnd, m_nCmdShow);
    UpdateWindow(m_hWnd);

    return TRUE;
    }

/*
 * CApp::NameToID
 *
 * Purpose:
 *  Calls IDispatch::GetIDsOfNames for a single name to determine
 *  the DISPID to pass to IDispatch::Invoke.
 *
 * Parameters:
 *  pszName         OLECHAR * to the name to map.
 *  pDispID         DISPID * in which to store the dispID.
 *
 * Return Value:
 *  HRESULT         Return value of GetIDsOfNames
 */

HRESULT CApp::NameToID(OLECHAR *pszName, DISPID *pDispID)
    {
    HRESULT     hr;
    TCHAR       szMsg[80];

    hr=m_pIDispatch->GetIDsOfNames(IID_NULL, &pszName, 1
        , m_lcid, pDispID);

    if (FAILED(hr))
        {
        wsprintf(szMsg
            , TEXT("GetIDsOfNames on '%s' failed with 0x%lX")
            , pszName, hr);
        Message(szMsg);
        }

    return hr;
    }

/*
 * CApp::Invoke
 *
 * Purpose:
 *  Calls IDispatch::Invoke using the interface pointer we hold
 *  and using some default parameters.  All the other parameters
 *  to this function are passed straight to Invoke
 *
 * Return Value:
 *  HRESULT         Return value of Invoke.  If DISP_E_EXCEPTION,
 *                  this function generates the appropriate
 *                  message box.
 */

HRESULT CApp::Invoke(DISPID dispID, WORD wFlags, DISPPARAMS *pdp
    , VARIANT *pva, EXCEPINFO *pExInfo, UINT *puErr)
    {
    HRESULT     hr;
    LPTSTR      pszMsg=NULL;
    LPTSTR      pszFmt=NULL;
    UINT        uRet;
    UINT        uStyle;
    TCHAR       szSource[80];

    if (NULL==m_pIDispatch)
        return ResultFromScode(E_POINTER);

    hr=m_pIDispatch->Invoke(dispID, IID_NULL, m_lcid, wFlags
        , pdp, pva, pExInfo, puErr);

    if (DISP_E_EXCEPTION!=GetScode(hr))
        return hr;

    //If we're given a deferred filling function, fill now.
    if (NULL!=pExInfo->pfnDeferredFillIn)
        (*pExInfo->pfnDeferredFillIn)(pExInfo);

    /*
     * To handle the exception, display a message box with the
     * controller's name in the caption and a message:
     *
     *      "Error <code> in <source>:  <description>"
     *
     * where <error> is the exception code in pExInfo->wCode or
     * pExInfo->scode, <source> is the value of the ProdID
     * in pExInfo->bstrSource and <description> is in
     * pExInfo->bstrDescription.
     *
     * For simplicity, we assume that if description is set, so is
     * source.
     *
     * To be complete, if pExInfo->bstrHelpFile is non-NULL,
     * display a Help button.  If Help is pressed, launch WinHelp
     * with that filename and pExInfo->dwHelpContext.
     */

    //Go get the real source name from the ProgID
    lstrcpy(szSource, TEXT("Unknown"));

    if (NULL!=pExInfo->bstrSource)
        {
        LONG    lRet;

        //If this doesn't work, we'll have "Unknown" anyway
       #ifdef WIN32ANSI
        char        szTemp[80];
        WideCharToMultiByte(CP_ACP, 0, pExInfo->bstrSource, -1
            , szTemp, 80, NULL, NULL);
        RegQueryValue(HKEY_CLASSES_ROOT, szTemp, szSource, &lRet);
       #else
        RegQueryValue(HKEY_CLASSES_ROOT, pExInfo->bstrSource
            , szSource, &lRet);
       #endif

        SysFreeString(pExInfo->bstrSource);
        }

    if (NULL!=pExInfo->bstrDescription)
        {
        pszFmt=(LPTSTR)malloc(CCHSTRINGMAX*sizeof(TCHAR));

       #ifdef WIN32ANSI
        UINT    cch;
        char   *pszDesc;

        cch=wcslen(pExInfo->bstrDescription);
        pszDesc=(LPSTR)malloc(cch);

        WideCharToMultiByte(CP_ACP, 0, pExInfo->bstrDescription, -1
            , pszDesc, cch, NULL, NULL);

        pszMsg=(LPTSTR)malloc(CCHSTRINGMAX+lstrlen(szSource)+cch);
       #else
        pszMsg=(LPTSTR)malloc((CCHSTRINGMAX+lstrlen(szSource)
            +lstrlen(pExInfo->bstrDescription))*sizeof(TCHAR));
       #endif

        if (0==pExInfo->wCode)
            {
            //Formatting for SCODE errors
            LoadString(m_hInst, IDS_MESSAGEEXCEPTIONSCODE, pszFmt
                , CCHSTRINGMAX);
            wsprintf(pszMsg, pszFmt, (long)pExInfo->scode
                , (LPTSTR)szSource
               #ifdef WIN32ANSI
                , pszDesc);
               #else
                , (LPTSTR)pExInfo->bstrDescription);
               #endif

            }
        else
            {
            //Formatting for wCode errors
            LoadString(m_hInst, IDS_MESSAGEEXCEPTION, pszFmt
                , CCHSTRINGMAX);
            wsprintf(pszMsg, pszFmt, (UINT)pExInfo->wCode
                , (LPTSTR)szSource
               #ifdef WIN32ANSI
                , pszDesc);
               #else
                , (LPTSTR)pExInfo->bstrDescription);
               #endif
            }

        free(pszFmt);
        }
    else
        {
        pszMsg=(LPTSTR)malloc(CCHSTRINGMAX*sizeof(TCHAR));
        LoadString(m_hInst, IDS_MESSAGEUNKNOWNEXCEPTION, pszMsg
            , CCHSTRINGMAX);
        }

    /*
     * In Windows 95 there is an MB_HELP style that we use in the
     * exception message if pExInfo->bstrHelpFile is non-NULL.  For
     * Windows NT 3.5 and Windows 3.1x, we'll just use a Cancel
     * button to demonstrate since making a Help button is too much
     * effort for this sample (requires a custom dialog box and code
     * to resize the dialog based on the length of the description
     * string which MessageBox does automatically...)
     */
    uStyle=MB_OK | MB_ICONEXCLAMATION;

   #ifdef MB_HELP
    uStyle |=(NULL!=pExInfo->bstrHelpFile) ? MB_HELP : 0;
   #else
    uStyle |=(NULL!=pExInfo->bstrHelpFile) ? MB_OKCANCEL : 0;
   #endif

    uRet=Message(pszMsg, uStyle);

    if (NULL!=pszMsg)
        free(pszMsg);

   #ifdef MB_HELP
    if (IDHELP==uRet)
   #else
    if (IDCANCEL==uRet)
   #endif
        {
        TCHAR       szHelp[512];

        /*
         * If we read a HELPDIR, prepend it to the file.  Otherwise
         * just use the string we got since that's all we have.
         */
        if ((TCHAR)0!=m_szHelpDir[0])
            {
   #ifdef WIN32ANSI
    char    szTemp[256];

            WideCharToMultiByte(CP_ACP, 0, pExInfo->bstrHelpFile
                , -1, szTemp, 256, NULL, NULL);
            wsprintf(szHelp, TEXT("%s\\%s"), m_szHelpDir, szTemp);
           #else
            wsprintf(szHelp, TEXT("%s\\%s"), m_szHelpDir
                , pExInfo->bstrHelpFile);
   #endif
            }
        else
           #ifdef WIN32ANSI
            WideCharToMultiByte(CP_ACP, 0, pExInfo->bstrHelpFile
                , -1, szHelp, 512, NULL, NULL);
           #else
            lstrcpy(szHelp, pExInfo->bstrHelpFile);
           #endif

        WinHelp(NULL, szHelp, HELP_CONTEXT, pExInfo->dwHelpContext);
        }

    //We're responsible for cleaning up the strings.
    SysFreeString(pExInfo->bstrDescription);
    SysFreeString(pExInfo->bstrHelpFile);

    return ResultFromScode(DISP_E_EXCEPTION);
    }

/*
 * CApp::Message (overloaded)
 *
 * Purpose:
 *  Scribbles a message onto the client area of the window
 *  or displays the message in a message box if a message
 *  box style is given.
 *
 * Parameters:
 *  pszMsg          LPTSTR to the message string.
 *  uStyle          (message box only) UINT style bits
 *
 * Return Value:
 *  UINT            Return value of MessageBox (MessageBox version
 *                  only)
 */

void CApp::Message(LPTSTR pszMsg)
    {
    HDC     hDC;
    RECT    rc;

    hDC=GetDC(m_hWnd);
    GetClientRect(m_hWnd, &rc);

    SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
    SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));

    /*
     * We'll just be sloppy and clear the whole window as
     * well as write the string with one ExtTextOut call.
     * No word wrapping here...
     */

    ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rc, pszMsg
        , lstrlen(pszMsg), NULL);

    ReleaseDC(m_hWnd, hDC);
    return;
    }

UINT CApp::Message(LPTSTR pszMsg, UINT uStyle)
    {
    return MessageBox(m_hWnd, pszMsg, TEXT("Automation Client")
        , uStyle);
    }

/*
 * HelpDirFromCLSID
 *
 * Purpose:
 *  Given a CLSID, looks up the TypeLib entry in the registry then
 *  extracts the HELPDIR entry for that type information, storing
 *  the path in pszPath.
 *
 * Parameters:
 *  clsID           CLSID of the object we're looking up.
 *  pszPath         LPTSTR buffer in which to store the directory.
 *
 * Return Value:
 *  None
 */

void HelpDirFromCLSID(CLSID clsID, LPTSTR pszPath)
    {
    TCHAR       szCLSID[80];
    TCHAR       szKey[512];
    UINT        cch;
    long        lRet;

    if (NULL==pszPath)
        return;

    *pszPath=0;

    cch=sizeof(szCLSID)/sizeof(TCHAR);
    StringFromGUID2(clsID, szCLSID, cch);
    wsprintf(szKey, TEXT("CLSID\\%s\\TypeLib"), szCLSID);

    //Get LIBID from under CLSID
    if (ERROR_SUCCESS==RegQueryValue(HKEY_CLASSES_ROOT, szKey
        , szCLSID, &lRet))
        {
        //Get HELPDIR from under TypeLib
        wsprintf(szKey, TEXT("TypeLib\\%s\\HELPDIR"), szCLSID);
        RegQueryValue(HKEY_CLASSES_ROOT, szKey, pszPath, &lRet);
        }

    return;
    }

/*
 * CApp::ShowProperties
 *
 * Purpose:
 *  Invokes the standard property page frame using the
 *  property pages specified by the object we maintain
 *  (only a single one).
 */

void CApp::ShowProperties(void)
    {
    ISpecifyPropertyPages  *pISPP;
    CAUUID                  caGUID;
    HRESULT                 hr;

    if (FAILED(m_pIDispatch->QueryInterface
        (IID_ISpecifyPropertyPages, (void **)&pISPP)))
        {
        Message(TEXT("Object has no property pages"));
        return;
        }

    hr=pISPP->GetPages(&caGUID);
    pISPP->Release();

    if (FAILED(hr))
        {
        Message(TEXT("Failed to retrieve property page GUIDs"));
        return;
        }

    hr=OleCreatePropertyFrame(m_hWnd, 10, 10, OLETEXT("Beeper")
        , 1, (IUnknown **)&m_pIDispatch, caGUID.cElems
        , caGUID.pElems, m_lcid, 0L, NULL);

    if (FAILED(hr))
        Message(TEXT("OleCreatePropertyFrame failed"));

    //Free the GUIDs
    CoTaskMemFree((void *)caGUID.pElems);
    return;
    }

/***
 *** IPropertyNotifySink Object
 ***/

/*
 * CPropertyNotifySink::CPropertyNotifySink
 * CPropertyNotifySink::~CPropertyNotifySink
 *
 * Constructor Parameters:
 *  pApp            PCApp of the application.
 */

CPropertyNotifySink::CPropertyNotifySink(PCApp pApp)
    {
    m_cRef=0;
    m_pApp=pApp;
    return;
    }

CPropertyNotifySink::~CPropertyNotifySink(void)
    {
    return;
    }

/*
 * CPropertyNotifySink::QueryInterface
 * CPropertyNotifySink::AddRef
 * CPropertyNotifySink::Release
 */

STDMETHODIMP CPropertyNotifySink::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IPropertyNotifySink==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CPropertyNotifySink::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPropertyNotifySink::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CPropertyNotifySink::OnChanged
 *
 * Purpose:
 *  Notifies this sink that the property identified with dispID
 *  has changed in whatever object this sink is connected to.
 *
 * Parameters:
 *  dispID          DISPID of the property that changed,
 *                  which can be DISPID_UNKNOWN for unspecified
 *                  changes to multiple properties.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CPropertyNotifySink::OnChanged(DISPID dispID)
    {
    TCHAR       szTemp[200];

    wsprintf(szTemp
        , TEXT("OnChanged notification received for DISPID=%lu")
        , dispID);

    m_pApp->Message(szTemp);
    return NOERROR;
    }

/*
 * CPropertyNotifySink::OnRequestEdit
 *
 * Purpose:
 *  Notifies this sink that the property identified with dispID
 *  is about to change and that the sink can prevent the change
 *  if desired.  This can be used to enforce read-only states or
 *  to save prior states before the change occurs.
 *
 * Parameters:
 *  dispID          DISPID of the property that is changing
 *                  which can be DISPID_UNKNOWN for multiple
 *                  properties.
 *
 * Return Value:
 *  HRESULT         NOERROR if the property can change, S_FALSE
 *                  if it cannot change, error code otherwise.
 */

STDMETHODIMP CPropertyNotifySink::OnRequestEdit(DISPID dispID)
    {
    TCHAR       szTemp[200];

    wsprintf(szTemp
        , TEXT("OnRequestEdit received for DISPID=%lu"), dispID);
    m_pApp->Message(szTemp);

    return ResultFromScode(m_pApp->m_fReadOnly ? S_FALSE : S_OK);
    }


AUTOCLI2.H

/*
 * AUTOCLI2.H
 * Automation Client with Property Pages Chapter 16
 *
 * Definitions and structures.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _AUTOCLI2_H_
#define _AUTOCLI2_H_

#define INC_AUTOMATION
//CHAPTER16MOD
#define INC_CONTROLS
#define CHAPTER16
//End CHAPTER16MOD
#include <inole.h>
#include <shellapi.h>
#include <malloc.h>

//Menu Resource ID and Commands
#define IDR_MENU                    1

#define IDM_GETSOUND                0x0001
#define IDM_BEEP                    0x0002
#define IDM_EXIT                    0x0003

//CHAPTER16MOD
#define IDM_PROPERTIES              0x0004
#define IDM_ENFORCEREADONLY         0x0005
//End CHAPTER16MOD

//These match MB_* identifiers for MessageBeep
#define IDM_SETSOUNDDEFAULT         0x0008
#define IDM_SETSOUNDHAND            0x0010
#define IDM_SETSOUNDQUESTION        0x0020
#define IDM_SETSOUNDEXCLAMATION     0x0030
#define IDM_SETSOUNDASTERISK        0x0040

#define IDM_SETSOUNDBOGUS           0x0555

//Stringtable IDs
#define CCHSTRINGMAX                80
#define IDS_MESSAGEEXCEPTION        0
#define IDS_MESSAGEEXCEPTIONSCODE   1
#define IDS_MESSAGEUNKNOWNEXCEPTION 2

//CHAPTER16MOD
class CPropertyNotifySink;
typedef CPropertyNotifySink *PCPropertyNotifySink;
//End CHAPTER16MOD

//AUTOCLI2.CPP
LRESULT APIENTRY AutoClientWndProc(HWND, UINT, WPARAM, LPARAM);
void HelpDirFromCLSID(CLSID, LPTSTR);

class CApp
    {
    friend LRESULT APIENTRY AutoClientWndProc(HWND, UINT, WPARAM
        , LPARAM);

    //CHAPTER16MOD
    friend CPropertyNotifySink;
    //End CHAPTER16MOD

    protected:
        HINSTANCE       m_hInst;            //WinMain parameters
        HINSTANCE       m_hInstPrev;
        UINT            m_nCmdShow;

        HWND            m_hWnd;             //Main window handle
        BOOL            m_fInitialized;     //Did CoInitialize work?
        LCID            m_lcid;             //User locale
        TCHAR           m_szHelpDir[256];   //Object's help directory
        IDispatch      *m_pIDispatch;       //Object in use

        //CHAPTER16MOD
        BOOL                 m_fReadOnly;   //RequestEdit control
        IPropertyNotifySink *m_pSink;       //Our sink
        IConnectionPoint    *m_pIConnPt;    //Object's connection point
        DWORD                m_dwConn;      //Sink connection
        //End CHAPTER16MOD

    public:
        CApp(HINSTANCE, HINSTANCE, UINT);
        ~CApp(void);

        BOOL    Init(void);
        HRESULT NameToID(OLECHAR *, DISPID *);
        HRESULT Invoke(DISPID, WORD, DISPPARAMS *, VARIANT *
                    , EXCEPINFO *, UINT *);
        void    Message(LPTSTR);
        UINT    Message(LPTSTR, UINT);

        //CHAPTER16MOD
        void    ShowProperties(void);
        //End CHAPTER16MOD
    };

typedef CApp *PCApp;

#define CBWNDEXTRA              sizeof(PCApp)
#define AUTOCLI2WL_STRUCTURE    0

//CHAPTER16MOD

class CPropertyNotifySink : public IPropertyNotifySink
    {
    protected:
        ULONG           m_cRef;      //Reference count
        PCApp           m_pApp;      //Backpointer to the app

    public:
        CPropertyNotifySink(PCApp);
        ~CPropertyNotifySink(void);

        //IPropertyNotifySink interface members
        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP OnChanged(DISPID);
        STDMETHODIMP OnRequestEdit(DISPID);
    };

//End CHAPTER16MOD

#endif //_AUTOCLI2_H_


BEEPER.CPP

/*
 * BEEPER.CPP
 * Beeper Object #6 with Property Pages Chapter 16
 *
 * Implementation of the CBeeper class which supports property
 * pages through ISpecifyPropertyPages and data binding through
 * IPropertyNotifySink.  See also CONNPT.CPP for connection
 * point business.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "beeper.h"

extern HINSTANCE g_hInst;

/*
 * CBeeper::CBeeper
 * CBeeper::~CBeeper
 *
 * Parameters (Constructor):
 *  pUnkOuter       LPUNKNOWN of a controlling unknown.
 *  pfnDestroy      PFNDESTROYED to call when an object
 *                  is destroyed.
 */

CBeeper::CBeeper(LPUNKNOWN pUnkOuter, PFNDESTROYED pfnDestroy)
    {
    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pfnDestroy=pfnDestroy;

    m_lSound=0;
    m_pImpIDispatch=NULL;
    //CHPATER16MOD
    m_pImpISpecifyPP=NULL;
    m_pImpIConnPtCont=NULL;

    //Connection point
    m_pIPropNotifySink=NULL;
    m_pConnPt=NULL;
    //End CHAPTER16MOD
    return;
    }

CBeeper::~CBeeper(void)
    {
    //CHAPTER16MOD
    DeleteInterfaceImp(m_pImpIConnPtCont);
    DeleteInterfaceImp(m_pImpISpecifyPP );

    ReleaseInterface(m_pConnPt);

    //End CHAPTER16MOD
    DeleteInterfaceImp(m_pImpIDispatch);
    return;
    }

/*
 * CBeeper::Init
 *
 * Purpose:
 *  Performs any intiailization of a CBeeper that's prone to failure
 *  that we also use internally before exposing the object outside.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CBeeper::Init(void)
    {
    LPUNKNOWN       pIUnknown=this;

    if (NULL!=m_pUnkOuter)
        pIUnknown=m_pUnkOuter;

    m_pImpIDispatch=new CImpIDispatch(this, pIUnknown);

    if (NULL==m_pImpIDispatch)
        return FALSE;

    //CHAPTER16MOD
    m_pImpISpecifyPP=new CImpISpecifyPP(this, pIUnknown);

    if (NULL==m_pImpISpecifyPP)
        return FALSE;

    m_pImpIConnPtCont=new CImpIConnPtCont(this, pIUnknown);

    if (NULL==m_pImpIConnPtCont)
        return FALSE;

    m_pConnPt=new CConnectionPoint(this);

    if (NULL==m_pConnPt)
        return FALSE;

    m_pConnPt->AddRef();    //Reversed in destructor
    //End CHAPTER16MOD

    return TRUE;
    }

/*
 * CBeeper::QueryInterface
 * CBeeper::AddRef
 * CBeeper::Release
 */

STDMETHODIMP CBeeper::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IBeeper==riid)
        *ppv=this;

    if (IID_IDispatch==riid || DIID_DIBeeper==riid)
        *ppv=m_pImpIDispatch;

    //CHAPTER16MOD
    if (IID_ISpecifyPropertyPages==riid)
        *ppv=m_pImpISpecifyPP;

    if (IID_IConnectionPointContainer==riid)
        *ppv=m_pImpIConnPtCont;
    //End CHAPTER16MOD

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CBeeper::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CBeeper::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0L;
    }

//IBeeper interface functions

/*
 * CBeeper::get_Sound
 * CBeeper::put_Sound
 *
 * Purpose:
 *  Functions called from DispInvoke to handle the Sound property.
 *
 * Parameters (Set only):
 *  lSound          long, new sound to save after validation
 *
 * Return Value: (Get only):
 *  long            Current sound.
 */

STDMETHODIMP_(long) CBeeper::get_Sound(void)
    {
    return m_lSound;
    }

STDMETHODIMP_(void) CBeeper::put_Sound(long lSound)
    {
    if (MB_OK!=lSound && MB_ICONEXCLAMATION!=lSound
        && MB_ICONQUESTION!=lSound && MB_ICONHAND!=lSound
        && MB_ICONASTERISK!=lSound)
        {
        /*
         * Since we cannot return a value to ITypeInfo::Invoke to
         * indicate an exception condition, we'll use
         * m_pImpIDispatch->Exception to raise them.  Before
         * CImpIDispatch::Invoke calls ITypeInfo::Invoke, it clears
         * the exception.  CImpIDispatch::Exception sets an
         * exception, so when ITypeInfo::Invoke returns, our Invoke
         * can store exception information as necessary.
         */
        m_pImpIDispatch->Exception(EXCEPTION_INVALIDSOUND);
        return;
        }

    //CHAPTER16MOD
    /*
     * We marked the "Sound" property as bindable and as
     * requestedit in our type information, so we have to
     * call any sink's OnRequestEdit first, and if that
     * succeeds with S_OK then we have to call OnChanged.
     */
    if (NULL!=m_pIPropNotifySink)
        {
        //If we didn't get permission, stop now
        if (NOERROR!=m_pIPropNotifySink->OnRequestEdit(PROPERTY_SOUND))
            return;
        }
    //End CHAPTER16MOD

    m_lSound=lSound;

    //CHAPTER16MOD
    if (NULL!=m_pIPropNotifySink)
        m_pIPropNotifySink->OnChanged(PROPERTY_SOUND);
    //End CHAPTER16MOD

    return;
    }

/*
 * CBeeper::Beep
 *
 * Purpose:
 *  Function called from DispInvoke to invoke the Beep method.
 *
 * Return Value:
 *  long           The sound played.
 */

STDMETHODIMP_(long) CBeeper::Beep(void)
    {
    MessageBeep((UINT)m_lSound);
    return m_lSound;
    }

//CHAPTER16MOD
//ISpecifyPropertyPages interface implementation

/*
 * CImpISpecifyPP::CImpISpecifyPP
 * CImpISpecifyPP::~CImpISpecifyPP
 *
 * Parameters (Constructor):
 *  pObj            PCBeeper of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpISpecifyPP::CImpISpecifyPP(PCBeeper pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpISpecifyPP::~CImpISpecifyPP(void)
    {
    return;
    }

/*
 * CImpISpecifyPP::QueryInterface
 * CImpISpecifyPP::AddRef
 * CImpISpecifyPP::Release
 */

STDMETHODIMP CImpISpecifyPP::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpISpecifyPP::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpISpecifyPP::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpISpecifyPP::GetPages
 *
 * Purpose:
 *  Returns an array of GUIDs identifying the indivudual property
 *  pages used for this object.
 *
 * Parameters:
 *  pPages          CAUUID * pointing to a counted array of GUIDs.
 *                  This function allocates the array elements
 *                  and stores them in this structure.
 */

STDMETHODIMP CImpISpecifyPP::GetPages(CAUUID *pPages)
    {
    IMalloc *pIMalloc;
    GUID    *pGUID;

    pPages->cElems=0;
    pPages->pElems=NULL;

    if (FAILED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        return ResultFromScode(E_OUTOFMEMORY);

    pGUID=(GUID *)pIMalloc->Alloc(CPROPPAGES*sizeof(GUID));

    if (NULL!=pGUID)
        {
        //Fill the array now that we allocated it.
        pGUID[0]=CLSID_BeeperPropertyPage;

        //Fill the structure and return.
        pPages->cElems=CPROPPAGES;
        pPages->pElems=pGUID;
        }

    pIMalloc->Release();
    return (NULL!=pGUID) ? NOERROR
        : ResultFromScode(E_OUTOFMEMORY);
    }
//End CHAPTER16MOD

//IDispatch interface implementation

/*
 * CImpIDispatch::CImpIDispatch
 * CImpIDispatch::~CImpIDispatch
 *
 * Parameters (Constructor):
 *  pObj            PCBeeper of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIDispatch::CImpIDispatch(PCBeeper pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;

    //These are created as needed in GetTypeInfo
    m_pITINeutral=NULL;
    m_pITIGerman=NULL;

    return;
    }

CImpIDispatch::~CImpIDispatch(void)
    {
    ReleaseInterface(m_pITIGerman);
    ReleaseInterface(m_pITINeutral);
    return;
    }

/*
 * CImpIDispatch::QueryInterface
 * CImpIDispatch::AddRef
 * CImpIDispatch::Release
 *
 * Purpose:
 *  IUnknown members for CImpIDispatch object.
 */

STDMETHODIMP CImpIDispatch::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIDispatch::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIDispatch::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIDispatch::GetTypeInfoCount
 *
 * Purpose:
 *  Returns the number of type information (ITypeInfo) interfaces
 *  that the object provides (0 or 1).
 *
 * Parameters:
 *  pctInfo         UINT * to the location to receive
 *                  the count of interfaces.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CImpIDispatch::GetTypeInfoCount(UINT *pctInfo)
    {
    //We implement GetTypeInfo so return 1
    *pctInfo=1;
    return NOERROR;
    }

/*
 * CImpIDispatch::GetTypeInfo
 *
 * Purpose:
 *  Retrieves type information for the automation interface.  This
 *  is used anywhere that the right ITypeInfo interface is needed
 *  for whatever LCID is applicable.  Specifically, this is used
 *  from within GetIDsOfNames and Invoke.
 *
 * Parameters:
 *  itInfo          UINT reserved.  Must be zero.
 *  lcid            LCID providing the locale for the type
 *                  information.  If the object does not support
 *                  localization, this is ignored.
 *  ppITypeInfo     ITypeInfo ** in which to store the ITypeInfo
 *                  interface for the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CImpIDispatch::GetTypeInfo(UINT itInfo, LCID lcid
    , ITypeInfo **ppITypeInfo)
    {
    HRESULT     hr;
    ITypeLib   *pITypeLib;
    ITypeInfo **ppITI=NULL;

    if (0!=itInfo)
        return ResultFromScode(TYPE_E_ELEMENTNOTFOUND);

    if (NULL==ppITypeInfo)
        return ResultFromScode(E_POINTER);

    *ppITypeInfo=NULL;

    /*
     * Since we returned one from GetTypeInfoCount, this function
     * can be called for a specific locale.  We support English,
     * German, and neutral (defaults to English) locales.  Anything
     * else is an error.
     *
     * After this switch statement, ppITI will either be NULL or
     * a valid pointer in it after.  If NULL, we know we need to
     * load type information, retrieve the ITypeInfo we want, and
     * then store it in *ppITI.
     */
    switch (PRIMARYLANGID(lcid))
        {
        case LANG_NEUTRAL:
        case LANG_ENGLISH:
            ppITI=&m_pITINeutral;
            break;

        case LANG_GERMAN:
            ppITI=&m_pITIGerman;
            break;

        default:
            return ResultFromScode(DISP_E_UNKNOWNLCID);
        }

    //Load a type lib if we don't have the information already.
    if (NULL==*ppITI)
        {
        /*
         * The type libraries are registered under 0 (neutral),
         * 7 (German), and 9 (English) with no specific sub-
         * language, which would make them 407 or 409 and such.
         * If you are sensitive to sub-languages, then use the
         * full LCID instead of just the LANGID as done here.
         */
        hr=LoadRegTypeLib(LIBID_BeeperTypeLibrary, 1, 0
            , PRIMARYLANGID(lcid), &pITypeLib);

        /*
         * If LoadRegTypeLib fails, try loading directly with
         * LoadTypeLib, which will register the library for us.
         * Note that there's no default case here because the
         * prior switch will have filtered lcid already.
         *
         * NOTE:  You should prepend your DIR registry key to the
         * .TLB name so you don't depend on it being it the PATH.
         * This sample will be updated later to reflect this.
         */
        if (FAILED(hr))
            {
            switch (PRIMARYLANGID(lcid))
                {
                case LANG_NEUTRAL:
                case LANG_ENGLISH:
                    hr=LoadTypeLib(OLETEXT("BEEP0000.TLB"), &pITypeLib);
                    break;

                case LANG_GERMAN:
                    hr=LoadTypeLib(OLETEXT("BEEP0007.TLB"), &pITypeLib);
                    break;
                }
            }

        if (FAILED(hr))
            return hr;

        //Got the type lib, get type info for the interface we want
        hr=pITypeLib->GetTypeInfoOfGuid(IID_IBeeper, ppITI);
        pITypeLib->Release();

        if (FAILED(hr))
            return hr;
        }

    /*
     * Note:  the type library is still loaded since we have
     * an ITypeInfo from it.
     */

    (*ppITI)->AddRef();
    *ppITypeInfo=*ppITI;
    return NOERROR;
    }

/*
 * CImpIDispatch::GetIDsOfNames
 *
 * Purpose:
 *  Converts text names into DISPIDs to pass to Invoke
 *
 * Parameters:
 *  riid            REFIID reserved.  Must be IID_NULL.
 *  rgszNames       OLECHAR ** pointing to the array of names to be
 *                  mapped.
 *  cNames          UINT number of names to be mapped.
 *  lcid            LCID of the locale.
 *  rgDispID        DISPID * caller allocated array containing IDs
 *                  corresponging to those names in rgszNames.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CImpIDispatch::GetIDsOfNames(REFIID riid
    , OLECHAR **rgszNames, UINT cNames, LCID lcid, DISPID *rgDispID)
    {
    HRESULT     hr;
    ITypeInfo  *pTI;

    if (IID_NULL!=riid)
        return ResultFromScode(DISP_E_UNKNOWNINTERFACE);

    //Get the right ITypeInfo for lcid.
    hr=GetTypeInfo(0, lcid, &pTI);

    if (SUCCEEDED(hr))
        {
        hr=DispGetIDsOfNames(pTI, rgszNames, cNames, rgDispID);
        pTI->Release();
        }

    return hr;
    }

/*
 * CImpIDispatch::Invoke
 *
 * Purpose:
 *  Calls a method in the dispatch interface or manipulates a
 *  property.
 *
 * Parameters:
 *  dispID          DISPID of the method or property of interest.
 *  riid            REFIID reserved, must be IID_NULL.
 *  lcid            LCID of the locale.
 *  wFlags          USHORT describing the context of the invocation.
 *  pDispParams     DISPPARAMS * to the array of arguments.
 *  pVarResult      VARIANT * in which to store the result.  Is
 *                  NULL if the caller is not interested.
 *  pExcepInfo      EXCEPINFO * to exception information.
 *  puArgErr        UINT * in which to store the index of an
 *                  invalid parameter if DISP_E_TYPEMISMATCH
 *                  is returned.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CImpIDispatch::Invoke(DISPID dispID, REFIID riid
    , LCID lcid, unsigned short wFlags, DISPPARAMS *pDispParams
    , VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
    {
    HRESULT     hr;
    ITypeInfo  *pTI;

    //riid is supposed to be IID_NULL always
    if (IID_NULL!=riid)
        return ResultFromScode(DISP_E_UNKNOWNINTERFACE);

    //Get the ITypeInfo for lcid
    hr=GetTypeInfo(0, lcid, &pTI);

    if (FAILED(hr))
        return hr;

    /*
     * Clear exceptions.  Note that this mechanism only works
     * for a single-thread, which is fine for the initial 32-bit
     * release of OLE but not a long-term solution.
     */
    m_wException=EXCEPTION_NONE;

    //This is exactly what DispInvoke does--so skip the overhead.
    hr=pTI->Invoke((IBeeper *)m_pObj, dispID, wFlags
        , pDispParams, pVarResult, pExcepInfo, puArgErr);

    /*
     * Check if an exception was raised.  Again, this is not
     * thread-safe.
     */
    if (EXCEPTION_NONE!=m_wException)
        {
        pExcepInfo->scode
            =(SCODE)MAKELONG(m_wException, PRIMARYLANGID(lcid));
        FillException(pExcepInfo);
        hr=ResultFromScode(DISP_E_EXCEPTION);
        }

    pTI->Release();
    return hr;
    }

/*
 * CImpIDispatch::Exception
 *
 * Purpose:
 *  Raises an exception for CImpIDispatch::Invoke from within
 *  ITypeInfo::Invoke.  This simply sets the m_wException variable
 *  to the exception code.  Invoke will pick this up and fill in
 *  the exception structure as necessary.
 *
 * Parameters:
 *  wException      WORD exception code.
 */

void CImpIDispatch::Exception(WORD wException)
    {
    /*
     * Note: calls to this function will be wrapped in a critical
     * section on Win32 because this is always called from within
     * DispInvoke which we wrapped above in Invoke.
     */
    m_wException=wException;
    return;
    }

/*
 * FillException
 *
 * Purpose:
 *  Callback function pointed to in IDispatch::Invoke that fills
 *  an EXCEPINFO structure based on the code stored inside
 *  Invoke.  This is a nice mechanism to keep all the management
 *  of error code strings and help IDs centralized in one place,
 *  even across many different automation objects within the same
 *  application.  It also keeps Invoke cleaner.
 *
 * Parameters:
 *  pExcepInfo      EXCEPINFO * to fill.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, error code otherwise.
 */

HRESULT STDAPICALLTYPE FillException(EXCEPINFO *pExcepInfo)
    {
    SCODE       scode;
    LANGID      langID;
    USHORT      wCode;
    HRESULT     hr;
    LPTSTR      psz;
    LPOLESTR    pszHelp;
    UINT        idsSource;
    UINT        idsException;

    if (NULL==pExcepInfo)
        return ResultFromScode(E_INVALIDARG);

    scode=pExcepInfo->scode;
    langID=HIWORD(scode);
    wCode=LOWORD(scode);

    //Allocate BSTRs for source and description strings
    psz=(LPTSTR)malloc(1024*sizeof(TCHAR));

    if (NULL==psz)
        return ResultFromScode(E_OUTOFMEMORY);

    hr=NOERROR;

    switch (wCode)
        {
        case EXCEPTION_INVALIDSOUND:
            //Fill in unused information, macro in inole.h
            INITEXCEPINFO(*pExcepInfo);
            pExcepInfo->wCode=wCode;

            pExcepInfo->dwHelpContext=HID_SOUND_PROPERTY_LIMITATIONS;

            //We registered a HELPDIR so we don't include paths
            pszHelp=OLETEXT("beep0000.hlp");
            idsSource=IDS_0_EXCEPTIONSOURCE;
            idsException=IDS_0_EXCEPTIONINVALIDSOUND;

            //Get the localized source and exception strings
            switch (langID)
                {
                case LANG_GERMAN:
                    idsSource=IDS_7_EXCEPTIONSOURCE;
                    idsException=IDS_7_EXCEPTIONINVALIDSOUND;
                    pszHelp=OLETEXT("beep0007.hlp");
                    break;

                case LANG_ENGLISH:
                case LANG_NEUTRAL:
                default:
                    break;
                }

            break;

        default:
            hr=ResultFromScode(E_FAIL);
        }

    if (SUCCEEDED(hr))
        {
        pExcepInfo->bstrHelpFile=SysAllocString(pszHelp);

       #ifdef WIN32ANSI
        OLECHAR     szTemp[256];

        LoadString(g_hInst, idsSource, psz, 256);
        MultiByteToWideChar(CP_ACP, 0, psz, -1, szTemp, 256);
        pExcepInfo->bstrSource=SysAllocString(szTemp);

        LoadString(g_hInst, idsException, psz, 256);
        MultiByteToWideChar(CP_ACP, 0, psz, -1, szTemp, 256);
        pExcepInfo->bstrDescription=SysAllocString(szTemp);
       #else
        LoadString(g_hInst, idsSource, psz, 1024);
        pExcepInfo->bstrSource=SysAllocString(psz);
        LoadString(g_hInst, idsException, psz, 1024);
        pExcepInfo->bstrDescription=SysAllocString(psz);
       #endif
        }

    free(psz);
    return hr;
    }


BEEPER.H

/*
 * BEEPER.H
 * Beeper Object #6 with Property Pages Chapter 16
 *
 * Classes that implement the Beeper object for supporting
 * property pages.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _BEEPER_H_
#define _BEEPER_H_

/*
 * This #define tells <bookguid.h> to not define GUIDs that will
 * be defined in MKTYPLIB-generated header files, like ibeeper.h.
 */
#define GUIDS_FROM_TYPELIB
#define INC_AUTOMATION
//CHAPTER16MOD
#define INC_CONTROLS
#define CHAPTER16
//End CHAPTER16MOD
#include <inole.h>
#include <malloc.h>

//This file is generated from MKTYPLIB
#include "ibeeper.h"

//Help context ID for exceptions
#define HID_SOUND_PROPERTY_LIMITATIONS  0x1E100

//English exception strings
#define IDS_0_MIN                   16
#define IDS_0_EXCEPTIONSOURCE       (IDS_0_MIN)
#define IDS_0_EXCEPTIONINVALIDSOUND (IDS_0_MIN+1)

//German exception strings
#define IDS_7_MIN                   32
#define IDS_7_EXCEPTIONSOURCE       (IDS_7_MIN)
#define IDS_7_EXCEPTIONINVALIDSOUND (IDS_7_MIN+1)

//Forward class declarations for friend statements
class CImpIDispatch;
typedef CImpIDispatch *PCImpIDispatch;

//CHAPTER16MOD
class CImpISpecifyPP;
typedef CImpISpecifyPP *PCImpISpecifyPP;

class CImpIConnPtCont;
typedef CImpIConnPtCont *PCImpIConnPtCont;

class CConnectionPoint;
typedef CConnectionPoint *PCConnectionPoint;

//Number of property pages we support
#define CPROPPAGES      1
//End CHAPTER16MOD

class CBeeper : public IBeeper
    {
    friend CImpIDispatch;
    //CHAPTER16MOD
    friend CImpISpecifyPP;
    friend CImpIConnPtCont;
    friend CConnectionPoint;
    //End CHAPTER16MOD

    protected:
        ULONG           m_cRef;             //Object reference count
        LPUNKNOWN       m_pUnkOuter;        //Controlling unknown
        PFNDESTROYED    m_pfnDestroy;       //To call on closure

        long            m_lSound;
        PCImpIDispatch  m_pImpIDispatch;    //Our IDispatch

        //CHAPTER16MOD
        PCImpISpecifyPP  m_pImpISpecifyPP;   //Our ISpecifyPropertyPages
        PCImpIConnPtCont m_pImpIConnPtCont;  //Our IConnectionPointContainer

        IPropertyNotifySink *m_pIPropNotifySink;    //Client's
        PCConnectionPoint    m_pConnPt;             //Ours
        //End CHAPTER16MOD

    public:
        CBeeper(LPUNKNOWN, PFNDESTROYED);
        ~CBeeper(void);

        BOOL         Init(void);

        //Non-delegating object IUnknown
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IBeeper functions
        STDMETHODIMP_(long)  get_Sound(void);
        STDMETHODIMP_(void)  put_Sound(long);
        STDMETHODIMP_(long)  Beep(void);
    };

typedef CBeeper *PCBeeper;

//DISPIDs for our dispinterface
enum
    {
    PROPERTY_SOUND=0,
    METHOD_BEEP
    };

/*
 * IDispatch interface implementations for the Beeper.
 */

class CImpIDispatch : public IDispatch
    {
    public:
        ULONG           m_cRef;     //For debugging

    private:
        PCBeeper        m_pObj;
        LPUNKNOWN       m_pUnkOuter;
        WORD            m_wException;

        ITypeInfo      *m_pITINeutral;      //Type information
        ITypeInfo      *m_pITIGerman;

    public:
        CImpIDispatch(PCBeeper, LPUNKNOWN);
        ~CImpIDispatch(void);

        /*
         * This function is called from CBeeper functions that
         * are called from within DispInvoke to set an exception.
         * This is because the CBeeper functions have no way to
         * tell DispInvoke of such exceptions, so we have to
         * tell our IDispatch::Invoke implementation directly.
         */
        void Exception(WORD);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IDispatch members
        STDMETHODIMP GetTypeInfoCount(UINT *);
        STDMETHODIMP GetTypeInfo(UINT, LCID, ITypeInfo **);
        STDMETHODIMP GetIDsOfNames(REFIID, OLECHAR **, UINT, LCID
            , DISPID *);
        STDMETHODIMP Invoke(DISPID, REFIID, LCID, WORD
            , DISPPARAMS *, VARIANT *, EXCEPINFO *, UINT *);
    };

//Exceptions we can throw from IDispatch::Invoke
enum
    {
    EXCEPTION_NONE=0,
    EXCEPTION_INVALIDSOUND=1000
    };

//Exception filling function for the EXCEPINFO structure.
HRESULT STDAPICALLTYPE FillException(EXCEPINFO *);

//CHAPTER16MOD

class CImpISpecifyPP : public ISpecifyPropertyPages
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        PCBeeper        m_pObj;      //Backpointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpISpecifyPP(PCBeeper, LPUNKNOWN);
        ~CImpISpecifyPP(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetPages(CAUUID *);
    };

//CONNPT.CPP

class CImpIConnPtCont : public IConnectionPointContainer
    {
    private:
        ULONG               m_cRef;      //Interface ref count
        PCBeeper            m_pObj;      //Backpointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIConnPtCont(PCBeeper, LPUNKNOWN);
        ~CImpIConnPtCont(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(DWORD) AddRef(void);
        STDMETHODIMP_(DWORD) Release(void);

        //IConnectionPointContainer members
        STDMETHODIMP EnumConnectionPoints(IEnumConnectionPoints **);
    STDMETHODIMP FindConnectionPoint(REFIID, IConnectionPoint **);
    };

//This object only supports one connection per point
#define CCONNMAX    1
#define ADVISEKEY   72388       //Arbitrary

class CConnectionPoint : public IConnectionPoint
    {
    private:
        ULONG           m_cRef;     //Object reference count
        PCBeeper        m_pObj;     //Containing object.

    public:
        CConnectionPoint(PCBeeper);
        ~CConnectionPoint(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IConnectionPoint members
        STDMETHODIMP GetConnectionInterface(IID *);
        STDMETHODIMP GetConnectionPointContainer
            (IConnectionPointContainer **);
        STDMETHODIMP Advise(LPUNKNOWN, DWORD *);
        STDMETHODIMP Unadvise(DWORD);
        STDMETHODIMP EnumConnections(IEnumConnections **);
    };

//End CHAPTER16MOD

#endif //_BEEPER_H_


CONNPT.CPP

/*
 * CONNPT.CPP
 * Beeper Object #6 with Property Pages Chapter 16
 *
 * Implementation of CImpIConnectionPoint for the Beeper object
 * as well as CConnectionPoint for IPropertyNotifySink.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "beeper.h"

/*
 * CImpIConnPtCont:CImpIConnPtCont
 * CImpIConnPtCont::~CImpIConnPtCont
 *
 * Constructor Parameters:
 *  pObj            PCBeeper pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIConnPtCont::CImpIConnPtCont(PCBeeper pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIConnPtCont::~CImpIConnPtCont(void)
    {
    return;
    }

/*
 * CImpIConnPtCont::QueryInterface
 * CImpIConnPtCont::AddRef
 * CImpIConnPtCont::Release
 */

STDMETHODIMP CImpIConnPtCont::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIConnPtCont::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIConnPtCont::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIConnPtCont::EnumConnectionPoints
 *
 * Purpose:
 *  Not implemented.
 *
 * Return Value:
 *  HRESULT         E_NOTIMPL
 */

STDMETHODIMP CImpIConnPtCont::EnumConnectionPoints
    (LPENUMCONNECTIONPOINTS *ppEnum)
    {
    *ppEnum=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIConnPtCont::FindConnectionPoint
 *
 * Purpose:
 *  Returns a pointer to the IConnectionPoint for a given
 *  outgoing IID.
 *
 * Parameters:
 *  riid            REFIID of the outgoing interface for which
 *                  a connection point is desired.
 *  ppCP            IConnectionPoint ** in which to return
 *                  the pointer after calling AddRef.
 *
 * Return Value:
 *  HRESULT         NOERROR if the connection point is found,
 *                  E_NOINTERFACE if it's not supported.
 */

STDMETHODIMP CImpIConnPtCont::FindConnectionPoint(REFIID riid
    , IConnectionPoint **ppCP)
    {
    *ppCP=NULL;

    if (IID_IPropertyNotifySink==riid)
        {
        return m_pObj->m_pConnPt->QueryInterface
            (IID_IConnectionPoint, (PPVOID)ppCP);
        }

    return ResultFromScode(E_NOINTERFACE);
    }

//CConnectionPoint implementation

/*
 * CConnectionPoint::CConnectionPoint
 * CConnectionPoint::~CConnectionPoint
 *
 * Parameters (Constructor):
 *  pObj            PCBeeper of the object we're in.  We can
 *                  query this for the IConnectionPointContainer
 *                  interface we might need.
 */

CConnectionPoint::CConnectionPoint(PCBeeper pObj)
    {
    m_cRef=0;

    /*
     * Our lifetime is controlled by the connectable object itself,
     * although other external clients will call AddRef and Release.
     * Since we're nested in the connectable object's lifetime,
     * there's no need to call AddRef on pObj.
     */
    m_pObj=pObj;
    return;
    }

CConnectionPoint::~CConnectionPoint(void)
    {
    ReleaseInterface(m_pObj->m_pIPropNotifySink);
    return;
    }

/*
 * CConnectionPoint::QueryInterface
 * CConnectionPoint::AddRef
 * CConnectionPoint::Release
 *
 * Purpose:
 *  Non-delegating IUnknown members for CConnectionPoint.
 */

STDMETHODIMP CConnectionPoint::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IConnectionPoint==riid)
        *ppv=(LPVOID)this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CConnectionPoint::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CConnectionPoint::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CConnectionPoint::GetConnectionInterface
 *
 * Purpose:
 *  Returns the IID of the outgoing interface supported through
 *  this connection point.
 *
 * Parameters:
 *  pIID            IID * in which to store the IID.
 */

STDMETHODIMP CConnectionPoint::GetConnectionInterface(IID *pIID)
    {
    if (NULL==pIID)
        return ResultFromScode(E_POINTER);

    *pIID=IID_IPropertyNotifySink;
    return NOERROR;
    }

/*
 * CConnectionPoint::GetConnectionPointContainer
 *
 * Purpose:
 *  Returns a pointer to the IConnectionPointContainer that
 *  is manageing this connection point.
 *
 * Parameters:
 *  ppCPC           IConnectionPointContainer ** in which to return
 *                  the pointer after calling AddRef.
 */

STDMETHODIMP CConnectionPoint::GetConnectionPointContainer
    (IConnectionPointContainer **ppCPC)
    {
    return m_pObj->QueryInterface(IID_IConnectionPointContainer
        , (void **)ppCPC);
    }

/*
 * CConnectionPoint::Advise
 *
 * Purpose:
 *  Provides this connection point with a notification sink to
 *  call whenever the appropriate outgoing function/event occurs.
 *
 * Parameters:
 *  pUnkSink        LPUNKNOWN to the sink to notify.  The connection
 *                  point must QueryInterface on this pointer to obtain
 *                  the proper interface to call.  The connection
 *                  point must also insure that any pointer held has
 *                  a reference count (QueryInterface will do it).
 *  pdwCookie       DWORD * in which to store the connection key for
 *                  later calls to Unadvise.
 */

STDMETHODIMP CConnectionPoint::Advise(LPUNKNOWN pUnkSink
    , DWORD *pdwCookie)
    {
    IPropertyNotifySink    *pSink;

    *pdwCookie=0;

    //Only allow one connection
    if (NULL!=m_pObj->m_pIPropNotifySink)
        return ResultFromScode(CONNECT_E_ADVISELIMIT);

    //Check for the right interface on the sink.
    if (FAILED(pUnkSink->QueryInterface(IID_IPropertyNotifySink
        , (PPVOID)&pSink)))
        return ResultFromScode(CONNECT_E_CANNOTCONNECT);

    *pdwCookie=ADVISEKEY;
    m_pObj->m_pIPropNotifySink=pSink;
    return NOERROR;
    }

/*
 * CConnectionPoint::Unadvise
 *
 * Purpose:
 *  Terminates the connection to the notification sink identified
 *  with dwCookie (that was returned from Advise).  The connection
 *  point has to Release any held pointers for that sink.
 *
 * Parameters:
 *  dwCookie        DWORD connection key from Advise.
 */

STDMETHODIMP CConnectionPoint::Unadvise(DWORD dwCookie)
    {
    if (0==dwCookie)
        return ResultFromScode(E_INVALIDARG);

    if (ADVISEKEY!=dwCookie)
        ResultFromScode(CONNECT_E_NOCONNECTION);

    ReleaseInterface(m_pObj->m_pIPropNotifySink);
    return NOERROR;
    }

/*
 * CConnectionPoint::EnumConnections
 *
 * Purpose:
 *  Not implemented.
 */

STDMETHODIMP CConnectionPoint::EnumConnections
    (LPENUMCONNECTIONS *ppEnum)
    {
    *ppEnum=NULL;
    return ResultFromScode(E_NOTIMPL);
    }


DBEEPER.CPP

/*
 * DBEEPER.CPP
 * Beeper Object #6 with Property Pages Chapter 16
 *
 * Server module code for the Beeper object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "dbeeper.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;
HINSTANCE   g_hInst=NULL;   //For LoadString

/*
 * LibMain(32)
 *
 * Purpose:
 *  Entry point conditionally compiled for Win32 and Windows
 *  3.1.  Provides the proper structure for each environment.
 */

#ifdef WIN32
BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    g_hInst=hInstance;

    if (DLL_PROCESS_DETACH==ulReason)
        {
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    return TRUE;
    }
#else
int PASCAL LibMain(HINSTANCE hInstance, WORD wDataSeg
    , WORD cbHeapSize, LPSTR lpCmdLine)
    {
    if (0!=cbHeapSize)
        UnlockData(0);

    g_hInst=hInstance;
    return (int)hInstance;
    }
#endif

/*
 * DllGetClassObject
 * DllCanUnloadNow
 * Standard COM exports for DLL servers.
 */

HRESULT APIENTRY DllGetClassObject(REFCLSID rclsid, REFIID riid
    , PPVOID ppv)
    {
    CBeeperFactory *pBF;
    HRESULT         hr;

    if (CLSID_Beeper!=rclsid)
        return ResultFromScode(E_FAIL);

    //Check that we can provide the interface
    if (IID_IUnknown!=riid && IID_IClassFactory!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Return our beeper factory's IClassFactory
    pBF=new CBeeperFactory();

    if (NULL==pBF)
        return ResultFromScode(E_OUTOFMEMORY);

    //If the factory hasn't the interface, delete it
    hr=pBF->QueryInterface(riid, ppv);

    if (FAILED(hr))
        delete pBF;
    else
        g_cObj++;

    return hr;
    }

STDAPI DllCanUnloadNow(void)
    {
    SCODE   sc;

    //Our answer is whether there are any object or locks
    sc=(0L==g_cObj && 0L==g_cLock) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the object to call when it gets destroyed.  Since
 *  we're in a DLL we only track the number of objects here, letting
 *  DllCanUnloadNow take care of the rest.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;
    return;
    }

/*
 * CBeeperFactory::CBeeperFactory
 * CBeeperFactory::~CBeeperFactory
 */

CBeeperFactory::CBeeperFactory(void)
    {
    m_cRef=0L;
    return;
    }

CBeeperFactory::~CBeeperFactory(void)
    {
    return;
    }

/*
 * CBeeperFactory::QueryInterface
 * CBeeperFactory::AddRef
 * CBeeperFactory::Release
 */

STDMETHODIMP CBeeperFactory::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CBeeperFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CBeeperFactory::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    ObjectDestroyed();
    return 0;
    }

/*
 * CBeeperFactory::CreateInstance
 * CBeeperFactory::LockServer
 */

STDMETHODIMP CBeeperFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCBeeper            pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //Verify that a controlling unknown asks for IUnknown
    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    //Create the object passing function to notify on destruction.
    pObj=new CBeeper(pUnkOuter, ObjectDestroyed);

    if (NULL==pObj)
        return hr;

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    //Kill the object if initial creation or Init failed.
    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

STDMETHODIMP CBeeperFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        g_cLock--;

    return NOERROR;
    }


DBEEPER.H

/*
 * DBEEPER.H
 * Beeper Object #6 with Property Pages Chapter 16
 *
 * Definitions, classes, and prototypes for the server of Beeper
 * objects.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _DBEEPER_H_
#define _DBEEPER_H_

//Get the object definitions
#include "beeper.h"

//DBEEPER.CPP
void ObjectDestroyed(void);

//This class factory object creates other objects

class CBeeperFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;

    public:
        CBeeperFactory(void);
        ~CBeeperFactory(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP     CreateInstance(LPUNKNOWN, REFIID, PPVOID);
        STDMETHODIMP     LockServer(BOOL);
    };

typedef CBeeperFactory *PCBeeperFactory;

#endif //_DBEEPER_H_


PRECOMP.CPP

#include "beeper.h"


BEEPPROP.CPP

/*
 * BEEPPROP.CPP
 * Beeper Property Page Chapter 16
 *
 * Server module code and class code CBeeperPropertyPage class.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "beepprop.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;
HINSTANCE   g_hInst=NULL;   //For resources

#ifdef WIN32
TCHAR       g_szObj[]=TEXT("Object");
#else
TCHAR       g_szObjHi[]=TEXT("ObjectHi");
TCHAR       g_szObjLo[]=TEXT("ObjectLo");
#endif

/*
 * LibMain(32)
 *
 * Purpose:
 *  Entry point conditionally compiled for Win32 and Windows
 *  3.1.  Provides the proper structure for each environment.
 */

#ifdef WIN32
BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    g_hInst=hInstance;

    if (DLL_PROCESS_DETACH==ulReason)
        {
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    return TRUE;
    }
#else
int PASCAL LibMain(HINSTANCE hInstance, WORD wDataSeg
    , WORD cbHeapSize, LPSTR lpCmdLine)
    {
    if (0!=cbHeapSize)
        UnlockData(0);

    g_hInst=hInstance;
    return (int)hInstance;
    }
#endif

/*
 * DllGetClassObject
 * DllCanUnloadNow
 * Standard COM exports for DLL servers.
 */

HRESULT APIENTRY DllGetClassObject(REFCLSID rclsid, REFIID riid
    , PPVOID ppv)
    {
    CBeeperPPFactory *pBF;
    HRESULT         hr;

    if (CLSID_BeeperPropertyPage!=rclsid)
        return ResultFromScode(E_FAIL);

    //Check that we can provide the interface
    if (IID_IUnknown!=riid && IID_IClassFactory!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Return our beeper factory's IClassFactory
    pBF=new CBeeperPPFactory();

    if (NULL==pBF)
        return ResultFromScode(E_OUTOFMEMORY);

    //If the factory hasn't the interface, delete it
    hr=pBF->QueryInterface(riid, ppv);

    if (FAILED(hr))
        delete pBF;
    else
        g_cObj++;

    return hr;
    }

STDAPI DllCanUnloadNow(void)
    {
    SCODE   sc;

    //Our answer is whether there are any object or locks
    sc=(0L==g_cObj && 0L==g_cLock) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * CBeeperPPFactory::CBeeperPPFactory
 * CBeeperPPFactory::~CBeeperPPFactory
 * CBeeperPPFactory::QueryInterface
 * CBeeperPPFactory::AddRef
 * CBeeperPPFactory::Release
 */

CBeeperPPFactory::CBeeperPPFactory(void)
    {
    m_cRef=0L;
    return;
    }

CBeeperPPFactory::~CBeeperPPFactory(void)
    {
    return;
    }

STDMETHODIMP CBeeperPPFactory::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CBeeperPPFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CBeeperPPFactory::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    g_cObj--;
    return 0;
    }

/*
 * CBeeperPPFactory::CreateInstance
 * CBeeperPPFactory::LockServer
 */

STDMETHODIMP CBeeperPPFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCBeeperPropPage    pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //No aggregation supported
    if (NULL!=pUnkOuter)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    //Create the object passing function to notify on destruction.
    pObj=new CBeeperPropPage(g_hInst);

    if (NULL==pObj)
        return hr;

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    //Kill the object if initial creation or Init failed.
    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

STDMETHODIMP CBeeperPPFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        g_cLock--;

    return NOERROR;
    }

/***
 *** CBeeperPropPage implementation
 ***/

/*
 * CBeeperPropPage::CBeeperPropPage
 * CBeeperPropPage::~CBeeperPropPage
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the module
 */

CBeeperPropPage::CBeeperPropPage(HINSTANCE hInst)
    {
    m_cRef=0L;

    m_hInst=hInst;
    m_uIDTemplate=IDD_BEEPERPROPS_0;
    m_hDlg=NULL;

    //Default sizes
    m_cx=300;
    m_cy=100;

    m_pIPropertyPageSite=NULL;
    m_ppIBeeper=NULL;
    m_cObjects=0;

    m_uIDLastSound=IDC_BEEPDEFAULT;
    m_fDirty=FALSE;

    m_lcid=LOCALE_USER_DEFAULT;
    return;
    }

CBeeperPropPage::~CBeeperPropPage(void)
    {
    if (NULL!=m_hDlg)
        DestroyWindow(m_hDlg);

    FreeAllObjects();
    ReleaseInterface(m_pIPropertyPageSite);
    return;
    }

/*
 * CBeeperPropPage::QueryInterface
 * CBeeperPropPage::AddRef
 * CBeeperPropPage::Release
 */

STDMETHODIMP CBeeperPropPage::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IPropertyPage==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CBeeperPropPage::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CBeeperPropPage::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    g_cObj--;
    delete this;
    return 0;
    }

/*
 * CBeeperPropPage::Init
 *
 * Purpose:
 *  Performs initialization operations that might fail.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if initialization successful, FALSE
 *                  otherwise.
 */

BOOL CBeeperPropPage::Init(void)
    {
    //Nothing to do
    return TRUE;
    }

/*
 * CBeeperPropPage::FreeAllObjects
 *
 * Purpose:
 *  Releases all the objects from IPropertyPage::SetObjects
 *
 * Parameters:
 *  None
 */

void CBeeperPropPage::FreeAllObjects(void)
    {
    UINT        i;

    if (NULL==m_ppIBeeper)
        return;

    for (i=0; i < m_cObjects; i++)
        ReleaseInterface(m_ppIBeeper[i]);

    delete [] m_ppIBeeper;
    m_ppIBeeper=NULL;
    m_cObjects=0;
    return;
    }

/*
 * CBeeperPropPage::SetPageSite
 *
 * Purpose:
 *  Provides the property page with the IPropertyPageSite
 *  that contains it.  SetPageSite(NULL) will be called as
 *  part of the close sequence.
 *
 * Parameters:
 *  pPageSite       LPPROPERTYPAGESITE pointer to the site.
 */

STDMETHODIMP CBeeperPropPage::SetPageSite
    (LPPROPERTYPAGESITE pPageSite)
    {
    if (NULL==pPageSite)
        ReleaseInterface(m_pIPropertyPageSite)
    else
        {
        HWND        hDlg;
        RECT        rc;
        LCID        lcid;

        m_pIPropertyPageSite=pPageSite;
        m_pIPropertyPageSite->AddRef();

        if (SUCCEEDED(m_pIPropertyPageSite->GetLocaleID(&lcid)))
            m_lcid=lcid;

        /*
         * Now that we know the locale we're running under, we can
         * load the dialog and determine the size it will be to
         * return through GetPageSize.  We just create the dialog
         * here and destroy it again to retrieve the size,
         * leaving Activate to create it for real.
         */

        switch (PRIMARYLANGID(m_lcid))
            {
            case LANG_GERMAN:
                m_uIDTemplate=IDD_BEEPERPROPS_7;
                break;

            case LANG_NEUTRAL:
            case LANG_ENGLISH:
            default:
                m_uIDTemplate=IDD_BEEPERPROPS_0;
                break;
            }

        hDlg=CreateDialogParam(m_hInst
            , MAKEINTRESOURCE(m_uIDTemplate), GetDesktopWindow()
            , (DLGPROC)BeepPropPageProc, 0L);

        //If creation fails, use default values set in constructor
        if (NULL!=hDlg)
            {
            GetWindowRect(hDlg, &rc);
            m_cx=rc.right-rc.left;
            m_cy=rc.bottom-rc.top;

            DestroyWindow(hDlg);
            }
        }

    return NOERROR;
    }

/*
 * CBeeperPropPage::Activate
 *
 * Purpose:
 *  Instructs the property page to create a window in which to
 *  display its contents, using the given parent window and
 *  rectangle.  The window should be initially visible.
 *
 * Parameters:
 *  hWndParent      HWND of the parent window.
 *  prc             LPCRECT of the rectangle to use.
 *  fModal          BOOL indicating whether the frame is modal.
 */

STDMETHODIMP CBeeperPropPage::Activate(HWND hWndParent
    , LPCRECT prc, BOOL fModal)
    {
    if (NULL!=m_hDlg)
        return ResultFromScode(E_UNEXPECTED);

    m_hDlg=CreateDialogParam(m_hInst, MAKEINTRESOURCE(m_uIDTemplate)
        , hWndParent, BeepPropPageProc, (LPARAM)this);

    if (NULL==m_hDlg)
        return ResultFromScode(E_OUTOFMEMORY);

    //Move the page into position and show it.
    SetWindowPos(m_hDlg, NULL, prc->left, prc->top
        , prc->right-prc->left, prc->bottom-prc->top, 0);

return NOERROR;
    }

/*
 * CBeeperPropPage::Deactivate
 *
 * Purpose:
 *  Instructs the property page to destroy its window that was
 *  created in Activate.
 *
 * Parameters:
 *  None
 */

STDMETHODIMP CBeeperPropPage::Deactivate(void)
    {
    if (NULL==m_hDlg)
        return ResultFromScode(E_UNEXPECTED);

    DestroyWindow(m_hDlg);
    m_hDlg=NULL;
    return NOERROR;
    }

/*
 * CBeeperPropPage::GetPageInfo
 *
 * Purpose:
 *  Fills a PROPPAGEINFO structure describing the page's size,
 *  contents, and help information.
 *
 * Parameters:
 *  pPageInfo       LPPROPPAGEINFO to the structure to fill.
 */

STDMETHODIMP CBeeperPropPage::GetPageInfo(LPPROPPAGEINFO pPageInfo)
    {
    IMalloc     *pIMalloc;

    if (FAILED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        return ResultFromScode(E_FAIL);

    pPageInfo->pszTitle=(LPOLESTR)pIMalloc->Alloc(CCHSTRINGMAX);

    if (NULL!=pPageInfo->pszTitle)
        {
        UINT        ids=IDS_0_PAGETITLE;

        if (PRIMARYLANGID(m_lcid)==LANG_GERMAN)
            ids=IDS_7_PAGETITLE;

       #ifdef WIN32ANSI
        char        szTemp[80];

        LoadString(m_hInst, ids, szTemp, CCHSTRINGMAX);
        MultiByteToWideChar(CP_ACP, 0, szTemp, -1
           , pPageInfo->pszTitle, 80);
       #else
        LoadString(m_hInst, ids, pPageInfo->pszTitle, CCHSTRINGMAX);
       #endif
        }

    pIMalloc->Release();

pPageInfo->size.cx      = m_cx;
    pPageInfo->size.cy      = m_cy;
pPageInfo->pszDocString = NULL;
pPageInfo->pszHelpFile  = NULL;
pPageInfo->dwHelpContext= 0;
    return NOERROR;
    }

/*
 * CBeeperPropPage::SetObjects
 *
 * Purpose:
 *  Identifies the objects that are being affected by this property
 *  page (and all other pages in the frame).  These are the object
 *  to which to send new property values in the Apply member.
 *
 * Parameters:
 *  cObjects        ULONG number of objects
 *  ppUnk           IUnknown ** to the array of objects being
 *                  passed to the page.
 */

STDMETHODIMP CBeeperPropPage::SetObjects(ULONG cObjects
    , IUnknown **ppUnk)
    {
    BOOL        fRet=TRUE;

    FreeAllObjects();

    if (0!=cObjects)
        {
        UINT        i;
        HRESULT     hr;

        m_ppIBeeper=new IBeeper * [(UINT)cObjects];

    for (i=0; i < cObjects; i++)
        {
            hr=ppUnk[i]->QueryInterface(IID_IBeeper
                , (void **)&m_ppIBeeper[i]);

            if (FAILED(hr))
                fRet=FALSE;
        }
    }

    //If we didn't get one of our objects, fail this call.
    if (!fRet)
        return ResultFromScode(E_FAIL);

    m_cObjects=cObjects;
    return NOERROR;
    }

/*
 * CBeeperPropPage::Show
 *
 * Purpose:
 *  Instructs the page to show or hide its window created in
 *  Activate.
 *
 * Parameters:
 *  nCmdShow        UINT to pass to ShowWindow.
 */

STDMETHODIMP CBeeperPropPage::Show(UINT nCmdShow)
    {
    if (NULL==m_hDlg)
        ResultFromScode(E_UNEXPECTED);

    ShowWindow(m_hDlg, nCmdShow);

    //Take the focus
    if (SW_SHOWNORMAL==nCmdShow || SW_SHOW==nCmdShow)
SetFocus(m_hDlg);

    return NOERROR;
    }

/*
 * CBeeperPropPage::Move
 *
 * Purpose:
 *  Instructs the property page to change its position.
 *
 * Parameters:
 *  prc             LPCRECT containing the new position.
 */

STDMETHODIMP CBeeperPropPage::Move(LPCRECT prc)
    {
    SetWindowPos(m_hDlg, NULL, prc->left, prc->top
        , prc->right-prc->left, prc->bottom-prc->top, 0);

    return NOERROR;
    }

/*
 * CBeeperPropPage::IsPageDirty
 *
 * Purpose:
 *  Asks the page if anything's changed in it, that is, if the
 *  property values in the page are out of sync with the objects
 *  under consideration.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR if dirty, S_FALSE if not.
 */

STDMETHODIMP CBeeperPropPage::IsPageDirty(void)
    {
    return ResultFromScode(m_fDirty ? S_OK : S_FALSE);
    }

/*
 * CBeeperPropPage::Apply
 *
 * Purpose:
 *  Instructs the page to send changes in its page to whatever
 *  objects it knows about through SetObjects.  This is the only
 *  time the page should change the objects' properties, and not
 *  when the value is changed on the page.
 *
 * Parameters:
 *  None
 */

STDMETHODIMP CBeeperPropPage::Apply(void)
    {
    UINT        i;
    UINT        lSound, lSoundNew;
    BOOL        fChanged;

    if (0==m_cObjects)
        return NOERROR;

    /*
     * The dialog's last known Sound selection is in
     * m_uIDLastSound, so we can just send it via
     * IBeeper::put_Sound.  Since this method has no
     * return value, we have to ask for it again to
     * see if it really changed because the client may
     * be blocking changes through IPropertyNotifySink,
     * in which case we do not clear the dirty flag.
     */

    lSound=(IDC_BEEPDEFAULT==m_uIDLastSound) ? 0L : m_uIDLastSound;
    fChanged=TRUE;

    for (i=0; i < m_cObjects; i++)
        {
        m_ppIBeeper[i]->put_Sound(lSound);
        lSoundNew=m_ppIBeeper[i]->get_Sound();

        fChanged &= (lSound==lSoundNew);
        }

    m_fDirty=!fChanged;
    return NOERROR;
    }

/*
 * CBeeperPropPage::Help
 *
 * Purpose:
 *  Invokes help for this property page when the user presses
 *  the Help button.  If you return NULLs for the help file
 *  in GetPageInfo, the button will be grayed.  Otherwise the
 *  page can perform its own help here.
 *
 * Parameters:
 *  pszHelpDir      LPCOLESTR identifying the default location of
 *                  the help information
 *
 * Return Value:
 *  HRESULT         NOERROR to tell the frame that we've done our
 *                  own help.  Returning an error code or S_FALSE
 *                  causes the frame to use any help information
 *                  in PROPPAGEINFO.
 */

STDMETHODIMP CBeeperPropPage::Help(LPCOLESTR pszHelpDir)
    {
    /*
     * We can either provide help ourselves, or rely on the
     * information in PROPPAGEINFO.
     */
    return ResultFromScode(S_FALSE);
    }

/*
 * CBeeperPropPage::TranslateAccelerator
 *
 * Purpose:
 *  Provides the page with the messages that occur in the frame.
 *  This gives the page to do whatever it wants with the message,
 *  such as handle keyboard mnemonics.
 *
 * Parameters:
 *  pMsg            LPMSG containing the keyboard message.
 */

STDMETHODIMP CBeeperPropPage::TranslateAccelerator(LPMSG lpMsg)
    {
    //No keyboard interface supported here
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * BeepPropPageProc
 *
 * Purpose:
 *  Dialog procedure for the Beeper Property Page.
 */

BOOL APIENTRY BeepPropPageProc(HWND hDlg, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCBeeperPropPage    pObj;
    WORD                wID;

   #ifdef WIN32
    pObj=(PCBeeperPropPage)(ULONG)GetProp(hDlg, g_szObj);
   #else
    pObj=(PCBeeperPropPage)MAKELONG((WORD)GetProp(hDlg, g_szObjLo)
       , (WORD)GetProp(hDlg, g_szObjHi));
   #endif

    switch (iMsg)
        {
        case WM_INITDIALOG:
            pObj=(PCBeeperPropPage)(ULONG)lParam;
           #ifdef WIN32
            SetProp(hDlg, g_szObj, (HANDLE)lParam);
           #else
            SetProp(hDlg, g_szObjHi, (HANDLE)HIWORD(lParam));
            SetProp(hDlg, g_szObjLo, (HANDLE)LOWORD(lParam));
           #endif

            if (NULL==pObj)
                return TRUE;

            SetFocus(GetDlgItem(hDlg, IDOK));

            /*
             * If we have one object then we can try to set the
             * right field in the dialog box.  Otherwise we could
             * ask for the value from all of the objects and see
             * if they all match.
             */
            if (1==pObj->m_cObjects)
                {
                UINT        iButton;

                iButton=(UINT)pObj->m_ppIBeeper[0]->get_Sound();

                if (0==iButton)
                    iButton=IDC_BEEPDEFAULT;

                CheckRadioButton(hDlg, IDC_BEEPDEFAULT
                    , IDC_BEEPASTERISK, iButton);

                pObj->m_uIDLastSound=iButton;
                }

            return FALSE;

        case WM_DESTROY:
           #ifdef WIN32
            RemoveProp(hDlg, g_szObj);
           #else
            RemoveProp(hDlg, g_szObjHi);
            RemoveProp(hDlg, g_szObjLo);
           #endif
            return FALSE;

        case WM_COMMAND:
            wID=LOWORD(wParam);

            switch (wID)
                {
                case IDC_BEEPDEFAULT:
                case IDC_BEEPHAND:
                case IDC_BEEPQUESTION:
                case IDC_BEEPEXCLAMATION:
                case IDC_BEEPASTERISK:
                    if (NULL==pObj)
                        break;

                    //Selecting the same one doesn't dirty
                    if (pObj->m_uIDLastSound==wID)
                        break;

                    //Save the most recently selected
                    pObj->m_uIDLastSound=LOWORD(wParam);
                    pObj->m_fDirty=TRUE;

                    if (NULL!=pObj->m_pIPropertyPageSite)
                        {
                        pObj->m_pIPropertyPageSite
                            ->OnStatusChange(PROPPAGESTATUS_DIRTY);
                        }

                    break;

                case IDOK:
                    /*
                     * We could call the object's Beep, but
                     * as it's property page, we know what
                     * it will do so we can just do it.
                     */
                    if (NULL!=pObj)
                        MessageBeep(pObj->m_uIDLastSound);

                    break;
                }
            break;
        }
    return FALSE;
    }


BEEPPROP.H

/*
 * BEEPPROP.H
 * Beeper Property Page, Chapter 16
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _BEEPPROP_H_
#define _BEEPPROP_H_

#define INC_AUTOMATION
#define INC_CONTROLS
#define GUIDS_FROM_TYPELIB
#define CHAPTER16
#include <inole.h>

//Get IBeeper definitions for the object we work with
#include "..\beeper6\ibeeper.h"

//Strings
#define IDS_0_PAGETITLE                 0
#define IDS_7_PAGETITLE                 1

#define CCHSTRINGMAX                    30

//Dialog template
#define IDD_BEEPERPROPS_0               101
#define IDD_BEEPERPROPS_7               102

//These match MB_* identifiers for ease of passing to Windows
#define IDC_BEEPDEFAULT                 0x0008
#define IDC_BEEPHAND                    0x0010
#define IDC_BEEPQUESTION                0x0020
#define IDC_BEEPEXCLAMATION             0x0030
#define IDC_BEEPASTERISK                0x0040

//This class factory object creates other objects

class CBeeperPPFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;

    public:
        CBeeperPPFactory(void);
        ~CBeeperPPFactory(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassPPFactory members
        STDMETHODIMP     CreateInstance(LPUNKNOWN, REFIID, PPVOID);
        STDMETHODIMP     LockServer(BOOL);
    };

typedef CBeeperPPFactory *PCBeeperPPFactory;

//Our property page class

BOOL APIENTRY BeepPropPageProc(HWND, UINT, WPARAM, LPARAM);

class CBeeperPropPage : public IPropertyPage
    {
    friend BOOL APIENTRY BeepPropPageProc(HWND, UINT, WPARAM, LPARAM);

    protected:
        ULONG           m_cRef;         //Reference count
        HINSTANCE       m_hInst;        //Module instance
        UINT            m_uIDTemplate;  //Dialog ID
        HWND            m_hDlg;         //Dialog handle

        ULONG           m_cx;           //Dialog size
        ULONG           m_cy;
        UINT            m_cObjects;     //Number of objects
        LCID            m_lcid;         //Current locale
        UINT            m_uIDLastSound; //Last selected in page
        BOOL            m_fDirty;       //Page dirty?
        IBeeper       **m_ppIBeeper;    //Objects to notify

        IPropertyPageSite *m_pIPropertyPageSite;    //Frame's

    public:
        CBeeperPropPage(HINSTANCE);
        ~CBeeperPropPage(void);

        BOOL Init(void);
        void FreeAllObjects(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

    STDMETHODIMP SetPageSite(LPPROPERTYPAGESITE);
    STDMETHODIMP Activate(HWND, LPCRECT, BOOL);
    STDMETHODIMP Deactivate(void);
    STDMETHODIMP GetPageInfo(LPPROPPAGEINFO);
    STDMETHODIMP SetObjects(ULONG, LPUNKNOWN *);
    STDMETHODIMP Show(UINT);
    STDMETHODIMP Move(LPCRECT);
    STDMETHODIMP IsPageDirty(void);
    STDMETHODIMP Apply(void);
    STDMETHODIMP Help(LPCOLESTR);
    STDMETHODIMP TranslateAccelerator(LPMSG);
    };

typedef CBeeperPropPage *PCBeeperPropPage;

#endif //_BEEPPROP_H_


CLIENT.CPP

/*
 * CLIENT.CPP
 * Patron Chapter 17
 *
 * Implementation of the CPatronClient class that just makes sure
 * we get a CPatronDoc on doc creation and that we initialize fully.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPatronClient::CPatronClient
 * CPatronClient::~CPatronClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame to the frame object.
 */

CPatronClient::CPatronClient(HINSTANCE hInst, PCFrame pFR)
    : CClient(hInst, pFR)
    {
    return;
    }

CPatronClient::~CPatronClient(void)
    {
    return;
    }

/*
 * CPatronClient::CreateCDocument
 *
 * Purpose:
 *  Constructs a new document specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CPatronClient::CreateCDocument(void)
    {
    return (PCDocument)(new CPatronDoc(m_hInst, m_pFR, m_pAdv));
    }


DOCUMENT.CPP

/*
 * DOCUMENT.CPP
 * Patron Chapter 17
 *
 * Implementation of the CPatronDoc derivation of CDocument that
 * manages pages for us.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"
#include <memory.h>
#include <dlgs.h>       //Pring Dlg button IDs

/*
 * CPatronDoc::CPatronDoc
 * CPatronDoc::~CPatronDoc
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame of the frame object.
 *  pAdv            PCDocumentAdviseSink to notify on events.
 */

CPatronDoc::CPatronDoc(HINSTANCE hInst, PCFrame pFR
    , PCDocumentAdviseSink pAdv)
    : CDocument(hInst, pFR, pAdv)
    {
    m_pPG=NULL;
    m_lVer=VERSIONCURRENT;
    m_pIStorage=NULL;
    m_fPrintSetup=TRUE;
    m_pDropTarget=NULL;

    //CHAPTER17MOD
    //These CFSTR_* values are standard (defined in INC\CLASSLIB.H)
    m_cfEmbeddedObject=RegisterClipboardFormat
        (CFSTR_EMBEDDEDOBJECT);
    m_cfObjectDescriptor=RegisterClipboardFormat
        (CFSTR_OBJECTDESCRIPTOR);
    //End CHAPTER17MOD

    return;
    }

CPatronDoc::~CPatronDoc(void)
    {
    if (NULL!=m_pPG)
        delete m_pPG;

    if (NULL!=m_pIStorage)
        m_pIStorage->Release();

    CoFreeUnusedLibraries();
    return;
    }

/*
 * CPatronDoc::Init
 *
 * Purpose:
 *  Initializes an already created document window.  The client
 *  actually creates the window for us, then passes that here for
 *  further initialization.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPatronDoc::Init(PDOCUMENTINIT pDI)
    {
    //Change the stringtable range to our customization.
    pDI->idsMin=IDS_DOCUMENTMIN;
    pDI->idsMax=IDS_DOCUMENTMAX;

    //Do default initialization
    if (!CDocument::Init(pDI))
        return FALSE;

    //Pages are created when we get a Load later.
    return TRUE;
    }

/*
 * CPatronDoc::FMessageHook
 *
 * Purpose:
 *  Processes WM_SIZE for the document so we can resize the Pages
 *  window.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CPatronDoc::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    UINT        dx, dy;
    RECT        rc;

    *pLRes=0;

    //Eat to prevent flickering
    if (WM_ERASEBKGND==iMsg)
        return TRUE;

    if (WM_SIZE==iMsg && NULL!=m_pPG)
        {
        dx=LOWORD(lParam);
        dy=HIWORD(lParam);

        if (SIZE_MINIMIZED!=wParam)
            {
            //Resize Pages window to fit the new document size.
            GetClientRect(hWnd, &rc);
            m_pPG->RectSet(&rc, FALSE);
            }
        }

    if (WM_DESTROY==iMsg)
        {
        /*
         * We have to revoke the drop target here because the window
         * will be destroyed and the property forcefully removed
         * before we could do this in the destructor.
         */
        if (NULL!=m_pDropTarget)
            {
            RevokeDragDrop(m_hWnd);
            CoLockObjectExternal(m_pDropTarget, FALSE, TRUE);
            m_pDropTarget->Release();
            }

        return FALSE;
        }

    /*
     * We return FALSE even on WM_SIZE so we can let the default
     * procedure handle maximized MDI child windows appropriately.
     */
    return FALSE;
    }

/*
 * CPatronDoc::Clear
 *
 * Purpose:
 *  Sets all contents in the document back to defaults with no
 *  filename.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Clear(void)
    {
    //Completely reset the pages
    if (NULL!=m_pPG)
        m_pPG->StorageSet(NULL, FALSE, FALSE);

    CDocument::Clear();
    m_lVer=VERSIONCURRENT;
    return;
    }

/*
 * CPatronDoc::FDirtyGet
 *
 * Purpose:
 *  Returns the current dirty status of the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the file is clean, FALSE otherwise.
 */

BOOL CPatronDoc::FDirtyGet()
    {
    BOOL    fPageDirty;

    fPageDirty=m_pPG->FIsDirty();
    return m_fDirty | fPageDirty;
    }

/*
 * CPatronDoc::Delete
 *
 * Purpose:
 *  Removed the current object from the document.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Delete(void)
    {
    if (NULL!=m_pPG)
        m_pPG->TenantDestroy();

    CoFreeUnusedLibraries();
    return;
    }

/*
 * CPatronDoc::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected in this
 *  document for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU of the Edit menu.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryObjectSelected(HMENU hMenu)
    {
    return m_pPG->FQueryObjectSelected(hMenu);
    }

/*
 * CPatronDoc::Load
 *
 * Purpose:
 *  Loads a given document without any user interface overwriting
 *  the previous contents of the editor.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load.  Could be NULL
 *                  for an untitled document.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CPatronDoc::Load(BOOL fChangeFile, LPTSTR pszFile)
    {
    RECT        rc;
    LPSTORAGE   pIStorage;
    HRESULT     hr;
    CLSID       clsID;
    DWORD       dwMode=STGM_TRANSACTED | STGM_READWRITE
                    | STGM_SHARE_EXCLUSIVE;

    if (NULL==pszFile)
        {
        //Create a new temp file.
        hr=StgCreateDocfile(NULL, dwMode | STGM_CREATE
            | STGM_DELETEONRELEASE, 0, &pIStorage);

        //Mark this our class since we check with ReadClassStg.
        if (SUCCEEDED(hr))
            WriteClassStg(pIStorage, CLSID_PatronPages);
        }
    else
        {
        hr=StgOpenStorage(pszFile, NULL, dwMode, NULL, 0, &pIStorage);
        }

    if (FAILED(hr))
        return DOCERR_COULDNOTOPEN;

    //Check if this is our type of file and exit if not.
    hr=ReadClassStg(pIStorage, &clsID);

    if (FAILED(hr) || CLSID_PatronPages!=clsID)
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    //Attempt to create our contained Pages window.
    m_pPG=new CPages(m_hInst, m_cf);
    GetClientRect(m_hWnd, &rc);

    if (!m_pPG->Init(m_hWnd, &rc, WS_CHILD | WS_VISIBLE
        , ID_PAGES, NULL))
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    if (!m_pPG->StorageSet(pIStorage, FALSE, (NULL==pszFile)))
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    //Open the window up for drag-drop
    m_pDropTarget=new CDropTarget(this);

    if (NULL!=m_pDropTarget)
        {
        m_pDropTarget->AddRef();
        CoLockObjectExternal(m_pDropTarget, TRUE, FALSE);
        RegisterDragDrop(m_hWnd, m_pDropTarget);
        }

    m_pIStorage=pIStorage;
    Rename(pszFile);

    //Do initial setup if new file, otherwise Pages handles things.
    if (NULL==pszFile)
        {
        //Go initialize the Pages for the default printer.
        if (!PrinterSetup(NULL, TRUE))
            return DOCERR_COULDNOTOPEN;

        //Go create an initial page.
        m_pPG->PageInsert(0);
        }
    else
        {
        //Can't change an already saved configuration
        m_fPrintSetup=FALSE;
        }

    FDirtySet(FALSE);
    return DOCERR_NONE;
    }

/*
 * CPatronDoc::Save
 *
 * Purpose:
 *  Writes the file to a known filename, requiring that the user
 *  has previously used FileOpen or FileSaveAs in order to have
 *  a filename.
 *
 * Parameters:
 *  uType           UINT indicating the type of file the user
 *                  requested to save in the File Save As dialog.
 *  pszFile         LPTSTR under which to save.  If NULL, use the
 *                  current name.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CPatronDoc::Save(UINT uType, LPTSTR pszFile)
    {
    HRESULT     hr;
    LPSTORAGE   pIStorage;

    //Save or Save As with the same file is just a commit.
    if (NULL==pszFile
        || (NULL!=pszFile && 0==lstrcmpi(pszFile, m_szFile)))
        {
        WriteFmtUserTypeStg(m_pIStorage, m_cf
            , PSZ(IDS_CLIPBOARDFORMAT));

        //Insure pages are up to date.
        m_pPG->StorageUpdate(FALSE);

        //Commit everyting
        m_pIStorage->Commit(STGC_DEFAULT);

        FDirtySet(FALSE);
        return DOCERR_NONE;
        }

    /*
     * When we're given a name, open the storage, creating it new
     * if it does not exist or overwriting the old one.  Then CopyTo
     * from the current to the new, Commit the new, Release the old.
     */

    hr=StgCreateDocfile(pszFile, STGM_TRANSACTED | STGM_READWRITE
        | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

    if (FAILED(hr))
        return DOCERR_COULDNOTOPEN;

    WriteClassStg(pIStorage, CLSID_PatronPages);
    WriteFmtUserTypeStg(pIStorage, m_cf, PSZ(IDS_CLIPBOARDFORMAT));

    //Insure all pages are up-to-date.
    m_pPG->StorageUpdate(TRUE);

    //This also copies the CLSID we stuff in here on file creation.
    hr=m_pIStorage->CopyTo(NULL, NULL, NULL, pIStorage);

    if (FAILED(hr))
        {
        SCODE       sc;

        pIStorage->Release();
        sc=GetScode(hr);

        /*
         * If we failed because of low memory, use IRootStorage
         * to switch into the new file.
         */
        if (E_OUTOFMEMORY==sc)
            {
            LPROOTSTORAGE        pIRoot;

            //Delete file we already created
            DeleteFile(pszFile);

            if (FAILED(m_pIStorage->QueryInterface
                (IID_IRootStorage, (PPVOID)&pIRoot)))
                return DOCERR_WRITEFAILURE;

           #ifdef WIN32ANSI
            OLECHAR     szTemp[MAX_PATH];

            MultiByteToWideChar(CP_ACP, 0, pszFile, -1, szTemp, MAX_PATH);
            hr=pIRoot->SwitchToFile(szTemp);
           #else
            hr=pIRoot->SwitchToFile(pszFile);
           #endif
            pIRoot->Release();

            if (FAILED(hr))
                return DOCERR_WRITEFAILURE;

            //If successful, the Commit below finishes the save.
            pIStorage=m_pIStorage;
            m_pIStorage->AddRef();    //Matches Release below
            }
        }

    pIStorage->Commit(STGC_DEFAULT);

    /*
     * Revert changes on the original storage.  If this was a temp
     * file, it's deleted since we used STGM_DELETEONRELEASE.
     */
    m_pIStorage->Release();

    //Make this new storage current
    m_pIStorage=pIStorage;
    m_pPG->StorageSet(pIStorage, TRUE, FALSE);

    FDirtySet(FALSE);
    Rename(pszFile);    //Update caption bar.

    return DOCERR_NONE;
    }

/*
 * CPatronDoc::Clip
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard, optionally implementing Cut by deleting the
 *  data in the current window after rendering.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *  fCut            BOOL indicating cut (TRUE) or copy (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::Clip(HWND hWndFrame, BOOL fCut)
    {
    if (NULL==m_pPG)
        return FALSE;

    return m_pPG->TenantClip(fCut);
    }

/*
 * CPatronDoc::Paste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  to the current figure in the editor window.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::Paste(HWND hWndFrame)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet=FALSE;
    FORMATETC       fe;
    TENANTTYPE      tType;

    if (NULL==m_pPG)
        return FALSE;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    //Go get type and format we *can* paste, then actually paste it.
    if (FQueryPasteFromData(pIDataObject, &fe, &tType))
        {
        //CHAPTER17MOD
        fRet=PasteFromData(pIDataObject, &fe, tType, NULL
            , 0L, TRUE);
        //End CHAPTER17MOD
        }

    pIDataObject->Release();
    return fRet;
    }

/*
 * CPatronDoc::FQueryPaste
 *
 * Purpose:
 *  Determines if we can paste data from the clipboard.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPaste(void)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    fRet=FQueryPasteFromData(pIDataObject, NULL, NULL);
    pIDataObject->Release();
    return fRet;
    }

/*
 * CPatronDoc::PasteSpecial
 *
 * Purpose:
 *  Retrieves a specific data format from the clipboard and sends
 *  it to the editor window appropriately.
 *
 *  Note that if this function is called, then the appropriate
 *  format is available because the Paste menu item is only
 *  enabled if the format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::PasteSpecial(HWND hWndFrame)
    {
    OLEUIPASTESPECIAL   ps;
    //CHAPTER17MOD
    OLEUIPASTEENTRY     rgPaste[5];
    DWORD               dwData=0;
    //End CHAPTER17MOD
    UINT                uTemp;
    BOOL                fRet=FALSE;

    if (NULL==m_pPG)
        return FALSE;

    memset(&ps, 0, sizeof(ps));

    if (FAILED(OleGetClipboard(&ps.lpSrcDataObj)))
        return FALSE;

    ps.cbStruct=sizeof(ps);
    ps.hWndOwner=hWndFrame;

    ps.dwFlags=PSF_SELECTPASTE;

    ps.arrPasteEntries=rgPaste;

    //CHAPTER17MOD
    ps.cPasteEntries=5;

    //Set up Paste Special descriptor arrays.
    SETDefFormatEtc(rgPaste[0].fmtetc, m_cf, TYMED_HGLOBAL);
    rgPaste[0].lpstrFormatName=PSZ(IDS_CLIPBOARDFORMAT);
    rgPaste[0].lpstrResultText=PSZ(IDS_PASTEASPATRON);
    rgPaste[0].dwFlags=OLEUIPASTE_PASTEONLY;

    //Embedded objects can be iconic displays if the user wants.
    SETDefFormatEtc(rgPaste[1].fmtetc, m_cfEmbeddedObject
        , TYMED_ISTORAGE);
    rgPaste[1].lpstrFormatName=PSZ(IDS_PASTEOBJECT);
    rgPaste[1].lpstrResultText=PSZ(IDS_PASTEASOBJECT);

    /*
     * CAUTION:  Use OLEUI_PASTE with embedded objects or else
     * this item will not show up in the dialog.  I learned this the
     * hard way (that is, after about 6 hours of pulling hair!).
     */
    rgPaste[1].dwFlags=OLEUIPASTE_PASTE | OLEUIPASTE_ENABLEICON;

    SETDefFormatEtc(rgPaste[2].fmtetc,CF_METAFILEPICT,TYMED_MFPICT);
    rgPaste[2].lpstrFormatName=PSZ(IDS_PASTEMETAFILE);
    rgPaste[2].lpstrResultText=PSZ(IDS_PASTEASMETAFILE);
    rgPaste[2].dwFlags=OLEUIPASTE_PASTEONLY;

    SETDefFormatEtc(rgPaste[3].fmtetc, CF_DIB, TYMED_HGLOBAL);
    rgPaste[3].lpstrFormatName=PSZ(IDS_PASTEDIB);
    rgPaste[3].lpstrResultText=PSZ(IDS_PASTEASDIB);
    rgPaste[3].dwFlags=OLEUIPASTE_PASTEONLY;

    SETDefFormatEtc(rgPaste[4].fmtetc, CF_BITMAP, TYMED_GDI);
    rgPaste[4].lpstrFormatName=PSZ(IDS_PASTEBITMAP);
    rgPaste[4].lpstrResultText=PSZ(IDS_PASTEASBITMAP);
    rgPaste[4].dwFlags=OLEUIPASTE_PASTEONLY;

    uTemp=OleUIPasteSpecial(&ps);

    if (OLEUI_OK==uTemp)
        {
        UINT        i=ps.nSelectedIndex;
        TENANTTYPE  tType;

        if (1==ps.nSelectedIndex)
            tType=TENANTTYPE_EMBEDDEDOBJECTFROMDATA;
        else
            tType=TENANTTYPE_STATIC;

        //Handle iconic aspects...
        if ((1==i) && (PSF_CHECKDISPLAYASICON & ps.dwFlags)
            && NULL!=ps.hMetaPict)
            {
            rgPaste[i].fmtetc.dwAspect=DVASPECT_ICON;
            dwData=(DWORD)(UINT)ps.hMetaPict;
            }

        fRet=PasteFromData(ps.lpSrcDataObj, &rgPaste[i].fmtetc
            , tType, NULL, dwData, FALSE);

        //Always free this regardless of what we do with it.
        INOLE_MetafilePictIconFree(ps.hMetaPict);
        }
    //End CHAPTER17MOD

    ps.lpSrcDataObj->Release();
    return fRet;
    }

/*
 * CPatronDoc::FQueryPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Determines if we can paste data from a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which we might want to paste.
 *  pFE             LPFORMATETC in which to return the first format
 *                  we can use.  Ignored if NULL.
 *  ptType          PTENANTTYPE in which to store the type of
 *                  object we can paste.  Ignored if NULL.
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPasteFromData(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, PTENANTTYPE ptType)
    {
    FORMATETC       fe;
    //CHAPTER17MOD
    HRESULT         hr, hr2;
    //End CHAPTER17MOD

    if (NULL!=(LPVOID)ptType)
        *ptType=TENANTTYPE_STATIC;

    //Any of our specific data here?
    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    hr=pIDataObject->QueryGetData(&fe);

    //CHAPTER17MOD
    //If embedded object data is available, set the appropriate type
    hr2=OleQueryCreateFromData(pIDataObject);

    if (NOERROR==hr2)
        {
        if (NULL!=pFE)
            {
            /*
             * Default to content.  Paste will use
             * CFSTR_OBJECTDESCRIPTOR to figure the actual aspect.
             */
            SETDefFormatEtc(*pFE, m_cfEmbeddedObject
                , TYMED_ISTORAGE);
            }

        if (NULL!=(LPVOID)ptType)
            *ptType=TENANTTYPE_EMBEDDEDOBJECTFROMDATA;

        /*
         * Return now if PatronObject wasn't available, otherwise
         * break out so that pFE gets PatronObject format.
         */
        if (NOERROR!=hr)
            return TRUE;
        }

    if (NOERROR!=hr && NOERROR!=hr2)
    //End CHAPTER17MOD
        {
        //Try metafile, DIB, then bitmap, setting fe each time
        SETDefFormatEtc(fe, CF_METAFILEPICT, TYMED_MFPICT);
        hr=pIDataObject->QueryGetData(&fe);

        if (NOERROR!=hr)
            {
            SETDefFormatEtc(fe, CF_DIB, TYMED_HGLOBAL);
            hr=pIDataObject->QueryGetData(&fe);

            if (NOERROR!=hr)
                {
                SETDefFormatEtc(fe, CF_BITMAP, TYMED_GDI);
                hr=pIDataObject->QueryGetData(&fe);
                }
            }
        }

    if (NOERROR==hr && NULL!=pFE)
        *pFE=fe;

    return (NOERROR==hr);
    }

/*
 * CPatronDoc::PasteFromData
 * (Protected)
 *
 * Purpose:
 *  Retrieves the private data format from a data object and sets
 *  it to the current figure in the editor window.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *  pFE             LPFORMATETC to use in the paste. Cannot be NULL.
 *  tType           TENANTTYPE to paste.
 *  ppo             PPATRONOBJECT containing placement data.
 *  dwData          DWORD extra data sensitive to tType
 *  fUseObjDesc     BOOL indicating to use CFSTR_OBJECTDESCRIPTOR
 *                  format for determining the aspect of the object
 *                  if the format is available.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

//CHAPTER17MOD
BOOL CPatronDoc::PasteFromData(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, TENANTTYPE tType, PPATRONOBJECT ppo
    , DWORD dwData, BOOL fUseObjDesc)
//End CHAPTER17MOD
    {
    BOOL                fRet;
    HRESULT             hr;
    PATRONOBJECT        po;
    STGMEDIUM           stm;
    //CHAPTER17MOD
    LPOBJECTDESCRIPTOR  pOD;
    FORMATETC           fe;
    BOOL                fRelease=FALSE;
    //End CHAPTER17MOD

    if (NULL==pFE)
        return FALSE;

    //If not given any placement data, see if we can retrieve it
    if (pFE->cfFormat==m_cf && NULL==ppo)
        {
        hr=pIDataObject->GetData(pFE, &stm);

        if (SUCCEEDED(hr))
            {
            ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);

            po=*ppo;
            ppo=&po;

            //CHAPTER17MOD
            //If there's an object here, make sure type is right.
            if (ppo->fe.cfFormat==m_cfEmbeddedObject)
                tType=TENANTTYPE_EMBEDDEDOBJECTFROMDATA;
            //End CHAPTER17MOD

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);
            }
        }

    //CHAPTER17MOD
    /*
     * If we're told to look at CFSTR_OBJECTDESCRIPTOR, then try to get
     * the data and copy the aspect out of it.  We're not interested
     * in any other part of it, however.
     */
    if (fUseObjDesc)
        {
        SETDefFormatEtc(fe, m_cfObjectDescriptor, TYMED_HGLOBAL);

        if (SUCCEEDED(pIDataObject->GetData(&fe, &stm)))
            {
            pOD=(LPOBJECTDESCRIPTOR)GlobalLock(stm.hGlobal);
            pFE->dwAspect=pOD->dwDrawAspect;

            if (NULL!=ppo)
                ppo->fe.dwAspect=pFE->dwAspect;

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);

            /*
             * Furthermore, if pFE->dwAspect is DVASPECT_ICON, get
             * the metafile which will always be the icon
             * representation.
             */
            SETFormatEtc(fe, CF_METAFILEPICT, DVASPECT_ICON, NULL
                , TYMED_MFPICT, -1);

            if (SUCCEEDED(pIDataObject->GetData(&fe, &stm)))
                {
                dwData=(DWORD)(UINT)stm.hGlobal;
                fRelease=TRUE;
                }
            }
        }
    //End CHAPTER17MOD

    fRet=m_pPG->TenantCreate(tType, pIDataObject, pFE, ppo, dwData);

    //CHAPTER17MOD
    //Release the stm from the last GetData
    if (fRelease)
        ReleaseStgMedium(&stm);
    //End CHAPTER17MOD

    if (fRet)
        {
        //Disable Printer Setup once we've created a tenant.
        m_fPrintSetup=FALSE;
        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CPatronDoc::NewPage
 *
 * Purpose:
 *  Creates a new page in the document's pages control after the
 *  current page.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::NewPage(void)
    {
    FDirtySet(TRUE);
    return m_pPG->PageInsert(0);
    }

/*
 * CPatronDoc::DeletePage
 *
 * Purpose:
 *  Deletes the current page from the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the now current page.
 */

UINT CPatronDoc::DeletePage(void)
    {
    FDirtySet(TRUE);
    return m_pPG->PageDelete(0);
    }

/*
 * CPatronDoc::NextPage
 *
 * Purpose:
 *  Shows the next page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::NextPage(void)
    {
    UINT        iPage;

    iPage=m_pPG->CurPageGet();
    return m_pPG->CurPageSet(++iPage);
    }

/*
 * CPatronDoc::PreviousPage
 *
 * Purpose:
 *  Shows the previous page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::PreviousPage(void)
    {
    UINT        iPage;

    //If iPage is zero, then we wrap around to the end.
    iPage=m_pPG->CurPageGet();
    return m_pPG->CurPageSet(--iPage);
    }

/*
 * CPatronDoc::FirstPage
 *
 * Purpose:
 *  Shows the first page page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::FirstPage(void)
    {
    return m_pPG->CurPageSet(0);
    }

/*
 * CPatronDoc::LastPage
 *
 * Purpose:
 *  Shows the last page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the last page.
 */

UINT CPatronDoc::LastPage(void)
    {
    return m_pPG->CurPageSet(NOVALUE);
    }

//CHAPTER17MOD

/*
 * CPatronDoc::Rename
 *
 * Purpose:
 *  Overrides the normal rename to include notification of tenants
 *  in this document of the new name.  This is so embedded objects
 *  are told the correct name through IOleObject::SetHostNames.
 *
 * Parameters:
 *  pszFile         LPTSTR to the new filename.
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Rename(LPTSTR pszFile)
    {
    //We don't need to change the base class, just augment...
    CDocument::Rename(pszFile);
    m_pPG->NotifyTenantsOfRename(pszFile, NULL);
    return;
    }

/*
 * CPatronDoc::InsertObject
 *
 * Purpose:
 *  Retrieves a CLSID or a filename from the Insert Object dialog
 *  box and creates an object using those identifiers.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::InsertObject(HWND hWndFrame)
    {
    OLEUIINSERTOBJECT   io;
    DWORD               dwData=0;
    TCHAR               szFile[CCHPATHMAX];
    UINT                uTemp;
    BOOL                fRet=FALSE;

    if (NULL==m_pPG)
        return FALSE;

    memset(&io, 0, sizeof(io));

    io.cbStruct=sizeof(io);
    io.hWndOwner=hWndFrame;

    szFile[0]=0;
    io.lpszFile=szFile;
    io.cchFile=CCHPATHMAX;

    io.dwFlags=IOF_SELECTCREATENEW | IOF_DISABLELINK;

    uTemp=OleUIInsertObject(&io);

    if (OLEUI_OK==uTemp)
        {
        TENANTTYPE      tType;
        LPVOID          pv;
        FORMATETC       fe;

        SETDefFormatEtc(fe, 0, TYMED_NULL);

        if (io.dwFlags & IOF_SELECTCREATENEW)
            {
            tType=TENANTTYPE_EMBEDDEDOBJECT;
            pv=&io.clsid;
            }
        else
            {
            tType=TENANTTYPE_EMBEDDEDFILE;
            pv=szFile;
            }

        if ((io.dwFlags & IOF_CHECKDISPLAYASICON)
            && NULL!=io.hMetaPict)
            {
            fe.dwAspect=DVASPECT_ICON;
            dwData=(DWORD)(UINT)io.hMetaPict;
            }

        fRet=m_pPG->TenantCreate(tType, pv, &fe, NULL, dwData);

        //Free this regardless of what we do with it.
        INOLE_MetafilePictIconFree(io.hMetaPict);

        if (fRet)
            {
            //Disable Printer Setup once we've created a tenant.
            m_fPrintSetup=FALSE;
            FDirtySet(TRUE);
            }
        }

    return fRet;
    }

/*
 * CPatronDoc::ActivateObject
 *
 * Purpose:
 *  Executes a verb on the currently selected object.
 *
 * Parameters:
 *  iVerb           LONG of the selected verb.
 *
 * Return Value:

*  None
 */

void CPatronDoc::ActivateObject(LONG iVerb)
    {
    m_pPG->ActivateObject(iVerb);
    return;
    }

/*
 * CPatronDoc::ConvertObject
 *
 * Purpose:
 *  Invokes the Convert dialog on the current object.  Here it's
 *  just a pass-through to the pages.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame window.
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful, FALSE otherwise.
 */

BOOL CPatronDoc::ConvertObject(HWND hWndFrame)
    {
    return m_pPG->ConvertObject(hWndFrame);
    }

//End CHAPTER17MOD


DRAGDROP.CPP

/*
 * DRAGDROP.CPP
 * Patron Chapter 17
 *
 * Member functions of the CPages class concerned with drag-drop
 * and other object-placement functionality.  Moved here to clean
 * up CPAGES.CPP somewhat.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPages::UTestDroppablePoint
 *
 * Purpose:
 *  Returns if the point in pptl is on the paper in the current page
 *  and alternately if it's within horizontal or vertical inset
 *  regions.
 *
 * Parameters:
 *  pptl            PPOINTL containing the point in screen
 *                  coordinates.
 *
 * Return Value:
 *  UINT            A UDROP_* value.
 */

UINT CPages::UTestDroppablePoint(PPOINTL pptl)
    {
    POINT       pt;
    RECT        rc, rcT, rcC;
    UINT        uRet;

    POINTFROMPOINTL(pt, *pptl);
    ScreenToClient(m_hWnd, &pt);

    CalcBoundingRect(&rc, FALSE);

    GetClientRect(m_hWnd, &rcC);
    IntersectRect(&rcT, &rc, &rcC);

    //Check for at least a client area hit.
    if (!PtInRect(&rcT, pt))
        return UDROP_NONE;

    uRet=UDROP_CLIENT;

    //Scroll checks happen on client area, not document area
    if (PtInRect(&rcC, pt))
        {
        //Check horizontal inset
        if (pt.x <= rcC.left+(int)m_uScrollInset)
            uRet |= UDROP_INSETLEFT;
        else if (pt.x >= rcC.right-(int)m_uScrollInset)
            uRet |= UDROP_INSETRIGHT;

        //Check vertical inset
        if (pt.y <= rcC.top+(int)m_uScrollInset)
            uRet |= UDROP_INSETTOP;
        else if (pt.y >= rcC.bottom-(int)m_uScrollInset)
            uRet |= UDROP_INSETBOTTOM;
        }

    return uRet;
    }

/*
 * CPages::DrawDropTargetRect
 *
 * Purpose:
 *  Draws a dotted rectangle on the Pages window to show where
 *  a drop might occur.  This is a toggle function.
 *
 * Parameters:
 *  pptl            PPOINTL containing the upper-left point
 *                  in screen coordinates.
 *  pszl            LPSIZEL containing the rect extents in device
 *                  units.
 *
 * Return Value:
 *  None
 */

void CPages::DrawDropTargetRect(PPOINTL pptl, LPSIZEL pszl)
    {
    POINT           pt;
    RECT            rc, rcT;
    HDC             hDC;

    if (NULL==pptl && NULL==pszl)
        {
        /*
         * This case is used from WM_PAINT in pagewin.cpp so we can
         * control the proper visibility of the drag rectangle when
         * we're scrolling.  If drag-drop happens between two apps,
         * then any ScrollWindow will cause a WM_PAINT after we
         * leave DragOver, so that paint would normally overwrite
         * the last drawn rectangle.  To alleviate that, WM_PAINT
         * will remove the last rect by sending us NULLs, paint,
         * then reinstate the rectangle.
         */
        pptl=&m_ptlRect;
        pszl=&m_szlRect;
        }
    else
        {
        m_ptlRect.x=pptl->x;
        m_ptlRect.y=pptl->y;
        m_szlRect.cx=pszl->cx;
        m_szlRect.cy=pszl->cy;

        //Flag is only affected from IDropTarget, not WM_PAINT calls
        m_fDragRectShown=!m_fDragRectShown;
        }

    POINTFROMPOINTL(pt, *pptl);
    ScreenToClient(m_hWnd, &pt);
    SetRect(&rc, pt.x, pt.y, pt.x+(int)pszl->cx
        , pt.y+(int)pszl->cy);

    CalcBoundingRect(&rcT, FALSE);
    IntersectRect(&rc, &rc, &rcT);

    if (!IsRectEmpty(&rc))
        {
        hDC=GetDC(m_hWnd);
        DrawFocusRect(hDC, &rc);
        ReleaseDC(m_hWnd, hDC);
        }

    return;
    }

/*
 * CPages::AdjustPosition
 *
 * Purpose:
 *  Adjusts a point for the scrolling offset and then converts it
 *  and a size from device into LOMETRIC units.
 *
 * Parameters:
 *  pptl            PPOINTL to adjust and convert
 *  pszl            LPSIZEL to convert
 *
 * Return Value:
 *  None
 */

void CPages::AdjustPosition(PPOINTL pptl, LPSIZEL pszl)
    {
    RECT        rc;

    SetRect(&rc, m_xPos+(int)pptl->x
        , m_yPos+(int)pptl->y, (int)pszl->cx, (int)pszl->cy);

    RectConvertMappings(&rc, NULL, FALSE);

    pptl->x=rc.left;
    pptl->y=rc.top;

    pszl->cx=rc.right;
    pszl->cy=rc.bottom;

    return;
    }


DROPSRC.CPP

/*
 * DROPSRC.CPP
 * Patron Chapter 17
 *
 * Implementation of a DropSource object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CDropSource::CDropSource
 * CDropSource::~CDropSource
 *
 * Constructor Parameters:
 *  None
 */

CDropSource::CDropSource(void)
    {
    m_cRef=0;
    return;
    }

CDropSource::~CDropSource(void)
    {
    return;
    }

/*
 * CDropSource::QueryInterface
 * CDropSource::AddRef
 * CDropSource::Release
 *
 * Purpose:
 *  IUnknown members for CDropSource object.
 */

STDMETHODIMP CDropSource::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDropSource==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDropSource::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropSource::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDropSource::QueryDragContinue
 *
 * Purpose:
 *  Determines whether to continue a drag operation or cancel it.
 *
 * Parameters:
 *  fEsc            BOOL indicating that the ESC key was pressed.
 *  grfKeyState     DWORD providing states of keys and mouse buttons
 *
 * Return Value:
 *  HRESULT         DRAGDROP_S_CANCEL to stop the drag,
 *                  DRAGDROP_S_DROP to drop the data where it is,
 *                  or NOERROR to continue.
 */

STDMETHODIMP CDropSource::QueryContinueDrag(BOOL fEsc, DWORD grfKeyState)
    {
    if (fEsc)
        return ResultFromScode(DRAGDROP_S_CANCEL);

    if (!(grfKeyState & MK_LBUTTON))
        return ResultFromScode(DRAGDROP_S_DROP);

    return NOERROR;
    }

/*
 * CDropSource::GiveFeedback
 *
 * Purpose:
 *  Provides cursor feedback to the user since the source task
 *  always has the mouse capture.  We can also provide any other
 *  type of feedback above cursors if we so desire.
 *
 * Parameters:
 *  dwEffect        DWORD effect flags returned from the last target
 *
 * Return Value:
 *  HRESULT         NOERROR if you set a cursor yourself or
 *                  DRAGDROP_S_USEDEFAULTCURSORS to let OLE do
 *                  the work.
 */

STDMETHODIMP CDropSource::GiveFeedback(DWORD dwEffect)
    {
    return ResultFromScode(DRAGDROP_S_USEDEFAULTCURSORS);
    }


DROPTGT.CPP

/*
 * DROPTGT.CPP
 * Patron Chapter 17
 *
 * Implementation of a DropTarget object
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CDropTarget::CDropTarget
 * CDropTarget::~CDropTarget
 *
 * Constructor Parameters:
 *  pDoc            PCPatronDoc of the window containing us.
 */

CDropTarget::CDropTarget(PCPatronDoc pDoc)
    {
    m_cRef=0;
    m_pDoc=pDoc;

    m_pIDataObject=NULL;
    return;
    }

CDropTarget::~CDropTarget(void)
    {
    return;
    }

/*
 * CDropTarget::QueryInterface
 * CDropTarget::AddRef
 * CDropTarget::Release
 *
 * Purpose:
 *  IUnknown members for CDropTarget object.
 */

STDMETHODIMP CDropTarget::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDropTarget==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDropTarget::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropTarget::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDropTarget::DragEnter
 *
 * Purpose:
 *  Indicates that data in a drag operation has been dragged over
 *  our window that's a potential target.  We are to decide if it's
 *  something in which we're interested.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT providing the source data.
 *  grfKeyState     DWORD flags: states of keys and mouse buttons.
 *  pt              POINTL coordinates in the client space of
 *                  the document.
 *  pdwEffect       LPDWORD into which we'll place the appropriate
 *                  effect flag for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragEnter(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    PCPages         ppg=m_pDoc->m_pPG;
    HWND            hWnd;
    FORMATETC       fe;
    STGMEDIUM       stm;
    UINT            uRet;

    m_fFeedback=FALSE;
    m_pIDataObject=NULL;

    *pdwEffect=DROPEFFECT_NONE;

    //Don't allow drops on iconic documents
    if (!IsWindowVisible(m_pDoc->Window()))
        return NOERROR;

    if (!m_pDoc->FQueryPasteFromData(pIDataSource, &fe, NULL))
        return NOERROR;

    //Check if this is a valid drop point.
    uRet=ppg->UTestDroppablePoint(&pt);
    ppg->m_uLastTest=uRet;

    if (UDROP_NONE==uRet)
        *pdwEffect=DROPEFFECT_NONE;
    else
        {
        //Default is move if we can, in fact drop here.
        *pdwEffect=DROPEFFECT_MOVE;

        if (grfKeyState & MK_CONTROL)
            *pdwEffect=DROPEFFECT_COPY;
        }

    m_pIDataObject=pIDataSource;
    m_pIDataObject->AddRef();

    /*
     * Determine the size of the data, if we can.  The default is
     * a small rectangle since we can't easily tell what size
     * something will be if we're pulling in a metafile or bitmap.
     * It's not a good idea to render it here with GetData just to
     * find that out. We only know the size if it's our own object
     * in which case a GetData will be fast.
     */

    if (fe.cfFormat==m_pDoc->m_cf)
        {
        if (SUCCEEDED(pIDataSource->GetData(&fe, &stm)))
            {
            PPATRONOBJECT   ppo;
            RECT            rc;

            ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);

            SetRect(&rc, (int)ppo->szl.cx, -(int)ppo->szl.cy, 0, 0);
            RectConvertMappings(&rc, NULL, TRUE);
            SETSIZEL(m_szl, rc.left, rc.top);

            m_ptPick=ppo->ptlPick;
            m_fe=ppo->fe;

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);
            }
        }
    else
        {
        SETSIZEL(m_szl, 30, 30);
        m_ptPick.x=0;
        m_ptPick.y=0;
        m_fe.cfFormat=0;

        //CHAPTER17MOD
        /*
         * Try to get CFSTR_OBJECTDESCRIPTOR which might have a size
         * and a pick point.  If it exists, then always use the
         * point but still default to a 30*30 size if the sizes
         * are zero.
         */
        uRet=RegisterClipboardFormat(CFSTR_OBJECTDESCRIPTOR);
        SETDefFormatEtc(fe, uRet, TYMED_HGLOBAL);

        if (SUCCEEDED(pIDataSource->GetData(&fe, &stm)))
            {
            LPOBJECTDESCRIPTOR  pOD;

            pOD=(LPOBJECTDESCRIPTOR)GlobalLock(stm.hGlobal);

            //Get the size, converting to LOMETRIC.
            if (0!=pOD->sizel.cx && 0!=pOD->sizel.cy)
                {
                XformSizeInHimetricToPixels(NULL, &pOD->sizel
                    , &m_szl);
                }

            //POINTL and SIZEL are interchangeable
            XformSizeInHimetricToPixels(NULL, (LPSIZEL)&pOD->pointl
                , (LPSIZEL)&m_ptPick);

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);
            }
        //End CHAPTER17MOD
        }

    //Bring the document window up front, show what a drop will do.
    hWnd=m_pDoc->Window();
    BringWindowToTop(hWnd);
    UpdateWindow(hWnd);

    ppg->m_uVScrollCode=NOVALUE;
    ppg->m_uHScrollCode=NOVALUE;
    m_fPendingRepaint=FALSE;

    pt.x-=m_ptPick.x;
    pt.y-=m_ptPick.y;

    m_ptLast=pt;
    m_fFeedback=TRUE;
    ppg->DrawDropTargetRect(&pt, &m_szl);

    return NOERROR;
    }

/*
 * CDropTarget::DragOver
 *
 * Purpose:
 *  Indicates that the mouse was moved inside the window represented
 *  by this drop target.  This happens on every WM_MOUSEMOVE, so
 *  this function should be very efficient.
 *
 * Parameters:
 *  grfKeyState     DWORD providing the current keyboard and
 *                  mouse states
 *  pt              POINTL where the mouse currently is.
 *  pdwEffect       LPDWORD in which to store the effect flag
 *                  for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragOver(DWORD grfKeyState, POINTL pt
    , LPDWORD pdwEffect)
    {
    PCPages     ppg=m_pDoc->m_pPG;
    UINT        uRet, uLast;
    UINT        xPos, yPos;

    *pdwEffect=DROPEFFECT_NONE;

    if (NULL==m_pIDataObject)
        return NOERROR;

    //Check if this is still a valid point.  uRet used below as well
    uRet=ppg->UTestDroppablePoint(&pt);

    if (UDROP_NONE==uRet)
        *pdwEffect=DROPEFFECT_NONE;
    else
        {
        //Store these before possibly ORing in DROPEFFECT_SCROLL
        *pdwEffect=DROPEFFECT_MOVE;

        if (grfKeyState & MK_CONTROL)
            *pdwEffect=DROPEFFECT_COPY;
        }

    //If we haven't moved and we are not scrolling, then we're done.
    if ((pt.x-m_ptPick.x==m_ptLast.x)
        && (pt.y-m_ptPick.y==m_ptLast.y)
        && !((UDROP_INSETHORZ|UDROP_INSETVERT) & ppg->m_uLastTest))
        {
        return NOERROR;
        }

    //Remove the last feedback rectangle.
    if (m_fFeedback)
        ppg->DrawDropTargetRect(&m_ptLast, &m_szl);

    uLast=ppg->m_uLastTest;
    ppg->m_uLastTest=uRet;

    if (UDROP_NONE==uRet)
        {
        //If we're now an invalid point, better repaint as necessary
        if (m_fPendingRepaint)
            {
            UpdateWindow(ppg->m_hWnd);
            m_fPendingRepaint=FALSE;
            }

        ppg->m_uVScrollCode=NOVALUE;
        ppg->m_uHScrollCode=NOVALUE;
        m_fFeedback=FALSE;
        return NOERROR;
        }

    /*
     * Scrolling is a little tricky:  We get a DragOver pulse even
     * if we didn't move.  First we have to delay scrolling for
     * ppg->m_uScrollDelay clock ticks which we can determine using
     * GetTickCount.  Timers do not work here since we may not be
     * yielding to our message loop.
     *
     * Once we know we are scrolling then we determine if we
     * scroll again or if we reset the scrolling state.
     */

    if ((UDROP_INSETHORZ & uLast) && !(UDROP_INSETHORZ & uRet))
        ppg->m_uHScrollCode=NOVALUE;

    if (!(UDROP_INSETHORZ & uLast) && (UDROP_INSETHORZ & uRet))
        {
        ppg->m_dwTimeLast=GetTickCount();
        ppg->m_uHScrollCode=(0!=(UDROP_INSETLEFT & uRet))
            ? SB_LINELEFT : SB_LINERIGHT; //Same as UP & DOWN codes.
        }

    if ((UDROP_INSETVERT & uLast) && !(UDROP_INSETVERT & uRet))
        ppg->m_uVScrollCode=NOVALUE;

    if (!(UDROP_INSETVERT & uLast) && (UDROP_INSETVERT & uRet))
        {
        ppg->m_dwTimeLast=GetTickCount();
        ppg->m_uVScrollCode=(0!=(UDROP_INSETTOP & uRet))
            ? SB_LINEUP : SB_LINEDOWN;
        }

    //Only change the last time if ALL scrolling stops.
    if (NOVALUE==ppg->m_uHScrollCode && NOVALUE==ppg->m_uVScrollCode)
        ppg->m_dwTimeLast=0L;

    //Set the scroll effect on any inset hit.
    if ((UDROP_INSETHORZ | UDROP_INSETVERT) & uRet)
        *pdwEffect |= DROPEFFECT_SCROLL;

    xPos=ppg->m_xPos;
    yPos=ppg->m_yPos;

    //Has the delay elapsed?  We can scroll if so
    if (ppg->m_dwTimeLast!=0
        && (GetTickCount()-ppg->m_dwTimeLast)
        > (DWORD)ppg->m_uScrollDelay)
        {
        if (NOVALUE!=ppg->m_uHScrollCode)
            {
            m_fPendingRepaint=TRUE;
            SendMessage(ppg->m_hWnd, WM_HSCROLL
                , ppg->m_uHScrollCode, 0L);
            }

        if (NOVALUE!=ppg->m_uVScrollCode)
            {
            m_fPendingRepaint=TRUE;
            SendMessage(ppg->m_hWnd, WM_VSCROLL
                , ppg->m_uVScrollCode, 0L);
            }
        }

    //If we didn't scroll but have a pending repaint, do it now.
    if (xPos==ppg->m_xPos && yPos==ppg->m_yPos && m_fPendingRepaint)
        {
        UpdateWindow(ppg->m_hWnd);
        m_fPendingRepaint=FALSE;
        }

    pt.x-=m_ptPick.x;
    pt.y-=m_ptPick.y;

    m_ptLast=pt;
    m_fFeedback=TRUE;
    ppg->DrawDropTargetRect(&pt, &m_szl);

    return NOERROR;
    }

/*
 * CDropTarget::DragLeave
 *
 * Purpose:
 *  Informs the drop target that the operation has left its window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragLeave(void)
    {
    PCPages         ppg=m_pDoc->m_pPG;

    if (NULL==m_pIDataObject)
        return NOERROR;

    //Stop scrolling
    ppg->m_uHScrollCode=NOVALUE;
    ppg->m_uVScrollCode=NOVALUE;

    if (m_fPendingRepaint)
        UpdateWindow(ppg->m_hWnd);

    //Remove the last feedback rectangle.
    if (m_fFeedback)
        ppg->DrawDropTargetRect(&m_ptLast, &m_szl);

    m_fFeedback=FALSE;
    m_pIDataObject->Release();
    return NOERROR;
    }

/*
 * CDropTarget::Drop
 *
 * Purpose:
 *  Instructs the drop target to paste the data that was just now
 *  dropped on it.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT from which we'll paste.
 *  grfKeyState     DWORD providing current keyboard/mouse state.
 *  pt              POINTL at which the drop occurred.
 *  pdwEffect       LPDWORD in which to store what you did.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::Drop(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    PCPages         ppg=m_pDoc->m_pPG;
    BOOL            fRet=TRUE;
    FORMATETC       fe;
    TENANTTYPE      tType;
    PATRONOBJECT    po;
    POINT           ptS;

    *pdwEffect=DROPEFFECT_NONE;

    if (NULL==m_pIDataObject)
        return ResultFromScode(E_FAIL);

    if (UDROP_NONE==ppg->UTestDroppablePoint(&pt))
        return ResultFromScode(E_FAIL);

    //Stop scrolling
    ppg->m_uHScrollCode=NOVALUE;
    ppg->m_uVScrollCode=NOVALUE;

    if (m_fPendingRepaint)
        UpdateWindow(ppg->m_hWnd);

    //2.  Remove the UI feedback
    if (m_fFeedback)
        ppg->DrawDropTargetRect(&m_ptLast, &m_szl);

    m_pIDataObject->Release();

    /*
     * Check if we can do the paste, and if so, tell our pasting
     * mechanism exactly where to place us.
     */
    pt.x-=m_ptPick.x;
    pt.y-=m_ptPick.y;

    POINTFROMPOINTL(ptS, pt);
    ScreenToClient(ppg->Window(), &ptS);
    POINTLFROMPOINT(po.ptl, ptS);

    //This is true if we didn't see placement data in DragEnter
    if (0!=m_fe.cfFormat)
        {
        po.szl.cx=m_szl.cx;         //We stored these positive
        po.szl.cy=-m_szl.cy;
        }
    else
        SETSIZEL(po.szl, 0, 0); //Ask object for its size.

    //Adjust for scrolling and mapping mode.
    ppg->AdjustPosition(&po.ptl, &po.szl);

    /*
     * If we're in the same document and moving, then we can just
     * stuff the Pages' m_ptDrop which will move us and return.
     */
    if (ppg->m_fDragSource && !(grfKeyState & MK_CONTROL))
        {
        *pdwEffect=DROPEFFECT_MOVE;
        ppg->m_fMoveInPage=TRUE;
        ppg->m_ptDrop=po.ptl;
        return NOERROR;
        }

    /*
     * Otherwise, paste either from another document or from
     * the same document which will always be a copy to the new
     * point.
     */

    ppg->m_fMoveInPage=FALSE;
    fRet=m_pDoc->FQueryPasteFromData(pIDataSource, &fe, &tType);

    if (fRet)
        {
        //Copy the real format if we have placement data.
        po.fe=(m_pDoc->m_cf==fe.cfFormat) ? m_fe : fe;

        //CHAPTER17MOD
        //Flag PasteFromData to use CFSTR_OBJECTDESCRIPTOR
        fRet=m_pDoc->PasteFromData(pIDataSource, &fe, tType
            , &po, 0, TRUE);
        //End CHAPTER17MOD
        }

    if (!fRet)
        return ResultFromScode(E_FAIL);

    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        *pdwEffect=DROPEFFECT_COPY;

    return NOERROR;
    }


IADVSINK.CPP

/*
 * IADVSINK.CPP
 * Patron Chapter 17
 *
 * Implementation of the IAdviseSink interface for Patron's tenants.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CImpIAdviseSink::CImpIAdviseSink
 * CImpIAdviseSink::~CImpIAdviseSink
 *
 * Parameters (Constructor):
 *  pTenant         PCTenant of the tenant we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIAdviseSink::CImpIAdviseSink(PCTenant pTenant
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pTen=pTenant;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIAdviseSink::~CImpIAdviseSink(void)
    {
    return;
    }

/*
 * CImpIAdviseSink::QueryInterface
 * CImpIAdviseSink::AddRef
 * CImpIAdviseSink::Release
 *
 * Purpose:
 *  IUnknown members for CImpIAdviseSink object.
 */

STDMETHODIMP CImpIAdviseSink::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIAdviseSink::OnDataChange
 *
 * Unused since we don't IDataObject::Advise.
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnDataChange(LPFORMATETC pFEIn
    , LPSTGMEDIUM pSTM)
    {
    return;
    }

/*
 * CImpIAdviseSink::OnViewChange
 *
 * Purpose:
 *  Notifes the advise sink that presentation data changed in the
 *  data object to which we're connected providing the right time
 *  to update displays using such presentations.
 *
 * Parameters:
 *  dwAspect        DWORD indicating which aspect has changed.
 *  lindex          LONG indicating the piece that changed.
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnViewChange(DWORD dwAspect
    , LONG lindex)
    {
    //Repaint only if this is the right aspect
    if (dwAspect==m_pTen->m_fe.dwAspect)
        {
        m_pTen->m_pPG->m_fDirty=TRUE;
        m_pTen->Repaint();
        }

    return;
    }

/*
 * CImpIAdviseSink::OnRename
 *
 * Purpose:
 *  Informs the advise sink that a linked object has been renamed.
 *  Generally only the OLE default handler cares about this.
 *
 * Parameters:
 *  pmk             LPMONIKER providing the new name of the object
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnRename(LPMONIKER pmk)
    {
    /*
     * As a container this is unimportant to us since it really
     * tells the handler's implementation of IOleLink that the
     * object's moniker has changed.  Since we get this call
     * from the handler, we don't have to do anything ourselves.
     */
    return;
    }

/*
 * CImpIAdviseSink::OnSave
 *
 * Purpose:
 *  Informs the advise sink that the OLE object has been saved
 *  persistently.  The primary purpose of this is for containers
 *  that want to make optimizations for objects that are not in a
 *  saved state, so on this you have to disable such optimizations.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnSave(void)
    {
    /*
     * A Container has nothing to do here as this notification is
     * only useful when we have an ADVFCACHE_ONSAVE advise set up,
     * which we don't.  So we ignore it.
     */
    return;
    }

/*
 * CImpIAdviseSink::OnClose
 *
 * Purpose:
 *  Informs the advise sink that the OLE object has closed and is
 *  no longer bound in any way.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnClose(void)
    {
    /*
     * This doesn't have much to do with us again as it's only
     * used to notify the handler's IOleLink implementation of the
     * change in the object.  We don't have to do anything since
     * we'll also get an IOleClientSite::OnShowWindow(FALSE) to
     * tell us to repaint.
     */

    /*
     * If we are dealing with an OLE 1.0 server it may not call
     * IOleClientSite::OnShowWindow(FALSE) properly, so to protect
     * ourselves we make sure the object is drawn as closed on
     * this notification.
     */
    m_pTen->ShowAsOpen(FALSE);
    return;
    }


ICLISITE.CPP

/*
 * ICLISITE.CPP
 * Patron Chapter 17
 *
 * Implementation of the IOleClientSite interface for
 * Patron's tenants.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CImpIOleClientSite::CImpIOleClientSite
 * CImpIOleClientSite::~CImpIOleClientSite
 *
 * Parameters (Constructor):
 *  pTenant         PCTenant of the tenant we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleClientSite::CImpIOleClientSite(PCTenant pTenant
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pTen=pTenant;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleClientSite::~CImpIOleClientSite(void)
    {
    return;
    }

/*
 * CImpIOleClientSite::QueryInterface
 * CImpIOleClientSite::AddRef
 * CImpIOleClientSite::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleClientSite object.
 */

STDMETHODIMP CImpIOleClientSite::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleClientSite::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleClientSite::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleClientSite::SaveObject
 *
 * Purpose:
 *  Requests that the container call OleSave for the object that
 *  lives here.  Typically this happens on server shutdown.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::SaveObject(void)
    {
    //We're already set up with the tenant to save; this is trivial.
    m_pTen->Update();
    return NOERROR;
    }

/*
 * CImpIOleClientSite::GetMoniker
 *
 * Purpose:
 *  Retrieves the moniker for the site in which this object lives,
 *  either the moniker relative to the container or the full
 *  moniker.
 *
 * Parameters:
 *  dwAssign        DWORD specifying that the object wants moniker
 *                  assignment.  Yeah.  Right.  Got any bridges to
 *                  sell?
 *  dwWhich         DWORD identifying which moniker the object
 *                  wants, either the container's moniker, the
 *                  moniker relative to this client site, or the
 *                  full moniker.
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::GetMoniker(DWORD dwAssign
    , DWORD dwWhich, LPMONIKER *ppmk)
    {
    *ppmk=NULL;

    /*
     * We don't yet want to allow for linking to embedded objects
     * within us, so we just fail for now.  If you are only a simple
     * container you never have to implement this function.
     */
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleClientSite::GetContainer
 *
 * Purpose:
 *  Returns a pointer to the document's IOleContainer interface.
 *
 * Parameters:
 *  ppContainer     LPOLECONTAINER * in which to return the
 *                  interface.
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::GetContainer(LPOLECONTAINER
    * ppContainer)
    {
    //Only necessary if you allow linking to embeddings
    *ppContainer=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleClientSite::ShowObject
 *
 * Purpose:
 *  Tells the container to bring the object fully into view as much
 *  as possible, that is, scroll the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::ShowObject(void)
    {
    /*
     * We let the tenant do this, since it can access the current
     * scroll position as a friend of CPages whereas we cannot.
     */
    m_pTen->ShowYourself();
    return NOERROR;
    }

/*
 * CImpIOleClientSite::OnShowWindow
 *
 * Purpose:
 *  Informs the container if the object is showing itself or
 *  hiding itself.  This is done only in the opening mode and allows
 *  the container to know when to shade or unshade the object.
 *
 * Parameters:
 *  fShow           BOOL indiciating that the object is being shown
 *                  (TRUE) or hidden (FALSE).
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::OnShowWindow(BOOL fShow)
    {
    //All we have to do is tell the tenant of the open state change.
    m_pTen->ShowAsOpen(fShow);
    return NOERROR;
    }

/*
 * CImpIOleClientSite::RequestNewObjectLayout
 *
 * Purpose:
 *  Called when the object needs more room in the container.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::RequestNewObjectLayout(void)
    {
    return ResultFromScode(E_NOTIMPL);
    }


PAGE.CPP

/*
 * PAGE.CPP
 * Patron Chapter 17
 *
 * Implementation of parts of the CPage class; those member
 * functions dealing with mouse events are in PAGEMOUS.CPP.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPage::CPage
 * CPage::~CPage
 *
 * Constructor Parameters:
 *  dwID            DWORD identifier for this page.
 *  hWnd            HWND of the pages window (for repaints, etc).
 *  pPG             PCPages to the Pages window.
 */

CPage::CPage(DWORD dwID, HWND hWnd, PCPages pPG)
    {
    m_dwID     =dwID;
    m_pIStorage=NULL;

    m_cOpens=0;
    m_hWnd=hWnd;
    m_pPG=pPG;

    m_dwIDNext      =0;
    m_cTenants      =0;
    m_hWndTenantList=NULL;
    m_iTenantCur    =NOVALUE;   //Tenants are zero indexed.
    m_pTenantCur    =NULL;

    m_uHTCode=HTNOWHERE;
    m_uSizingFlags=0;
    m_fTracking=FALSE;
    m_hDC=NULL;

    m_fDragPending=FALSE;
    m_fSizePending=FALSE;
    m_fTimer=FALSE;

    //Get WIN.INI distance and delay values, with OLE defaults.
    m_cxyDist=GetProfileInt(TEXT("windows"), TEXT("DragMinDist")
        , DD_DEFDRAGMINDIST);
    m_cDelay=GetProfileInt(TEXT("windows"), TEXT("DragDelay")
        , DD_DEFDRAGDELAY);

    return;
    }

CPage::~CPage(void)
    {
    if (m_fTimer)
        KillTimer(m_hWnd, IDTIMER_DEBOUNCE);

    m_hWnd=NULL;
    Close(FALSE);
    return;
    }

/*
 * CPage::GetID
 *
 * Return Value:
 *  DWORD           dwID field in this page.
 */

DWORD CPage::GetID(void)
    {
    return m_dwID;
    }

/*
 * CPage::Open
 *
 * Purpose:
 *  Retrieves the IStorage associated with this page.  The IStorage
 *  is owned by the page and thus the page always holds a reference
 *  count.  The caller should call Close or delete this page to
 *  match this open.
 *
 *  This function may be called multiple times resulting in
 *  additional reference counts on the storage each of which must be
 *  matched with a call to Close.  The last Close can be done
 *  through delete.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which this page lives.
 *
 * Return Value:
 *  BOOL            TRUE if opening succeeds, FALSE otherwise.
 */

BOOL CPage::Open(LPSTORAGE pIStorage)
    {
    HRESULT         hr=NOERROR;
    LPSTREAM        pIStream;
    DWORD           dwMode;
    OLECHAR         szTemp[32];
    BOOL            fNew;
    BOOL            fCreated=FALSE;
    TENANTLIST      tl;
    PTENANTINFO     pti;
    ULONG           cb;
    LPMALLOC        pIMalloc;
    UINT            i;
    PCTenant        pTenant;

    fNew=(NULL==m_pIStorage);

    if (!fNew)
        {
        m_cOpens++;
        m_pIStorage->AddRef();
        return TRUE;
        }

    if (NULL==pIStorage)
        return FALSE;

    /*
     * Attempt to open the storage under this ID.  If none,
     * create one.  In either case, the IStorage is either
     * saved in pPage or released.
     */

    GetStorageName(szTemp);
    dwMode=STGM_TRANSACTED | STGM_READWRITE | STGM_SHARE_EXCLUSIVE;

    hr=pIStorage->OpenStorage(szTemp, NULL, dwMode, NULL, 0
        , &m_pIStorage);

    if (FAILED(hr))
        {
        hr=pIStorage->CreateStorage(szTemp, dwMode, 0, 0
            , &m_pIStorage);
        fCreated=TRUE;
        }

    if (FAILED(hr))
        return FALSE;

    m_cOpens++;

    if (NULL==m_hWndTenantList)
        {
        /*
         * The first time we open this page, create the hidden
         * listbox we'll use to track tenants.  We give it the
         * owner-draw style so we can just store pointers in it.
         */
        m_hWndTenantList=CreateWindow(TEXT("listbox")
            , TEXT("Tenant List"), WS_POPUP | LBS_OWNERDRAWFIXED
            , 0, 0, 100, 100, HWND_DESKTOP, NULL
            , m_pPG->m_hInst, NULL);

        if (NULL==m_hWndTenantList)
            return FALSE;
        }

    //If this is brand-new, we're done.
    if (fCreated)
        return TRUE;

    /*
     * Now open the stream we saved in Close and load all the
     * tenants listed in there.  If there are none, then we don't
     * have to load squat.
     */

    hr=m_pIStorage->OpenStream(SZSTREAMTENANTLIST, NULL, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pIStream->Read(&tl, sizeof(tl), NULL);
        m_cTenants=tl.cTenants;
        m_dwIDNext=tl.dwIDNext;
        m_iTenantCur=0;

        cb=tl.cTenants*sizeof(TENANTINFO);

        if (0!=cb)
            {
            pti=(PTENANTINFO)pIMalloc->Alloc(cb);

            if (NULL!=pti)
                {
                pIStream->Read(pti, cb, NULL);

                for (i=0; i < m_cTenants; i++)
                    {
                    if (TenantAdd(NOVALUE, (pti+i)->dwID, &pTenant))
                        pTenant->Load(m_pIStorage, (pti+i));
                    }

                pIMalloc->Free(pti);
                }
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    //Get and select the first tenant
    if (TenantGet(0, &m_pTenantCur, FALSE))
        m_pTenantCur->Select(TRUE);

    return TRUE;
    }

/*
 * CPage::Close
 *
 * Purpose:
 *  Possibly commits the storage, then releases it reversing the
 *  reference count from Open.
 *
 * Parameters:
 *  fCommit         BOOL indicating if we're to commit.
 *
 * Return Value:
 *  None
 */

void CPage::Close(BOOL fCommit)
    {
    if (NULL==m_pIStorage)
        return;

    if (fCommit)
        Update();

    m_pIStorage->Release();

    //If this was the last close, make all tenants loaded->passive
    if (0==--m_cOpens)
        {
        UINT        i;
        PCTenant    pTenant;

        m_pIStorage=NULL;

        for (i=0; i < m_cTenants; i++)
            {
            if (TenantGet(i, &pTenant, FALSE))
                {
                if (NULL!=m_hWnd)
                    {
                    //Open may select again, so this repaints.
                    pTenant->Select(FALSE);
                    }

                pTenant->Close(FALSE);
                //CHAPTER17MOD
                pTenant->Release();
                //End CHAPTER17MOD
                }
            }

        DestroyWindow(m_hWndTenantList);
        m_hWndTenantList=NULL;
        }

    return;
    }

/*
 * CPage::Update
 *
 * Purpose:
 *  Forces a common on the page if it's open.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if there are any open objects on this page,
 *                  that is, we should remain open.
 */

BOOL CPage::Update(void)
    {
    BOOL            fOpen=FALSE;
    UINT            i;
    PCTenant        pTenant;
    HRESULT         hr;
    LPSTREAM        pIStream;
    TENANTLIST      tl;
    PTENANTINFO     pti;
    ULONG           cb;
    LPMALLOC        pIMalloc;

    //Walk the list of objects and update them all as well.
    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            fOpen |= pTenant->Update();
        }

    //Now write our own stream containing the tenant list.
    hr=m_pIStorage->CreateStream(SZSTREAMTENANTLIST, STGM_CREATE
        | STGM_WRITE| STGM_DIRECT | STGM_SHARE_EXCLUSIVE, 0, 0
        , &pIStream);

    if (FAILED(hr))
        return fOpen;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        tl.cTenants=m_cTenants;
        tl.dwIDNext=m_dwIDNext;

        pIStream->Write(&tl, sizeof(TENANTLIST), &cb);

        cb=m_cTenants*sizeof(TENANTINFO);
        pti=(PTENANTINFO)pIMalloc->Alloc(cb);

        if (NULL!=pti)
            {
            for (i=0; i < m_cTenants; i++)
                {
                TenantGet(i, &pTenant, FALSE);
                pTenant->GetInfo((pti+i));
                }

            pIStream->Write(pti, cb, &cb);
            pIMalloc->Free(pti);
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    //Now commit the whole mess and we're done
    if (NULL!=m_pIStorage)
        m_pIStorage->Commit(STGC_DEFAULT);

    return fOpen;
    }

/*
 * CPage::Destroy
 *
 * Purpose:
 *  Removes this page from the given storage.  The caller should
 *  eventually delete this Page object to free the storage.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE contianing this page on which to call
 *                  DestroyElement
 *
 * Return Value:
 *  None
 */

void CPage::Destroy(LPSTORAGE pIStorage)
    {
    if (NULL!=pIStorage)
        {
        OLECHAR szTemp[32];

        Close(FALSE);
        GetStorageName(szTemp);
        pIStorage->DestroyElement(szTemp);
        }

    return;
    }

/*
 * CPage::GetStorageName
 *
 * Parameters:
 *  pszName         LPOLESTR to a buffer in which to store the
 *                  storage name for this page.
 *
 * Return Value:
 *  UINT            Number of characters stored.
 */

UINT CPage::GetStorageName(LPOLESTR pszName)
    {
   #ifdef WIN32ANSI
    char        szTemp[32];
    UINT        cch;

    cch=wsprintf(szTemp, "Page %lu", m_dwID);
    MultiByteToWideChar(CP_ACP, 0, szTemp, -1, pszName, 32);
    return cch;
   #else
    return wsprintf(pszName, TEXT("Page %lu"), m_dwID);
   #endif
    }

/*
 * CPage::Draw
 *
 * Purpose:
 *  Draws the objects on this page to the given hDC.
 *
 * Parameters:
 *  hDC             HDC on which to draw.
 *  xOff, yOff      int offsets for the page.
 *  fNoColor        BOOL indicating black & white screen rendering.
 *  fPrinter        BOOL indicating hDC is on the printer.
 *
 * Return Value:
 *  None
 */

void CPage::Draw(HDC hDC, int xOff, int yOff, BOOL fNoColor
    , BOOL fPrinter)
    {
    int                 i;
    PCTenant            pTenant;
    HDC                 hIC=NULL;
    PCOMBINEDEVICE      pcd=NULL;
    DVTARGETDEVICE     *ptd=NULL;

    /*
     * If printing, tell the tenant to forget the borders. Otherwise
     * we leave xOff and yOff the same to account for scrolling.
     */
    if (fPrinter)
        {
        xOff=LOMETRIC_BORDER+m_pPG->m_xMarginLeft;
        yOff=-LOMETRIC_BORDER-m_pPG->m_yMarginTop;

        /*
         * Get device information.  If this fails, ptd is
         * NULL which is acceptable.
         */
        if (m_pPG->DevReadConfig(&pcd, &hIC))
            ptd=&(pcd->td);
        }

    for (i=(int)m_cTenants-1; i >=0; i--)
        {
        if (TenantGet(i, &pTenant, FALSE))
            {
            RECT        rc, rcWin;
            RECTL       rcl;

            //Paint this tenant only if visible.
            pTenant->RectGet(&rcl, TRUE);
            RECTFROMRECTL(rc, rcl);
            OffsetRect(&rc, -(int)m_pPG->m_xPos
                , -(int)m_pPG->m_yPos);
            GetClientRect(m_hWnd, &rcWin);

            if (IntersectRect(&rc, &rc, &rcWin))
                {
                pTenant->Draw(hDC, ptd, hIC, xOff, yOff
                    , fNoColor, fPrinter);
                }
            }
        }

    //Free whatever CPages::DevReadConfig returned.
    if (NULL!=pcd)
        {
        LPMALLOC    pIMalloc;

        if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
            {
            pIMalloc->Free(pcd);
            pIMalloc->Release();
            }
        }

    if (NULL!=hIC)
        DeleteDC(hIC);

    return;
    }

/*
 * CPage::TenantCreate
 *
 * Purpose:
 *  Creates a new tenant of a specific type.
 *
 * Parameters:
 *  tType           TENANTTYPE to create.
 *  pv              LPVOID providing information for the new
 *                  object creation.
 *  pFE             LPFORMATETC describing how we want this
 *                  rendered.
 *  ppo             PPATRONOBJECT with placement data.
 *  dwData          DWORD extra data to pass to the tenant.
 *
 * Return Value:
 *  None
 */

BOOL CPage::TenantCreate(TENANTTYPE tType, LPVOID pv
    , LPFORMATETC pFE, PPATRONOBJECT ppo, DWORD dwData)
    {
    PCTenant    pTenant;
    UINT        uRet;
    int         x, y;
    int         h, v;
    POINTL      ptl;
    SIZEL       szl;
    RECTL       rcl;
    RECT        rc;

    //New tenants go at top of the pile; zero index to TenantAdd.
    if (!TenantAdd(0, m_dwIDNext, &pTenant))
        return FALSE;

    uRet=pTenant->Create(tType, pv, pFE, &ptl, &szl, m_pIStorage
        , ppo, dwData);

    if (CREATE_FAILED==uRet)
        {
        //Reverse Create AND TenantAdd
        SendMessage(m_hWndTenantList, LB_DELETESTRING, 0, 0L);
        pTenant->Destroy(m_pIStorage);

        //CHAPTER17MOD
        pTenant->Release();
        //End CHAPTER17MOD
        return FALSE;
        }

    m_dwIDNext++;
    m_cTenants++;

    if (NULL!=m_pTenantCur)
        m_pTenantCur->Select(FALSE);

    m_iTenantCur=0;             //First one in the list now.
    m_pTenantCur=pTenant;

    //Tell the tenant where it lives, default is (0,0) in print area
    x=LOMETRIC_BORDER+m_pPG->m_xMarginLeft;
    y=-LOMETRIC_BORDER-m_pPG->m_yMarginTop;

    h=x;
    v=y;

    if (CREATE_PLACEDOBJECT==uRet)
        {
        SetRect(&rc, 3*CXYHANDLE, 3*CXYHANDLE, 0, 0);
        RectConvertMappings(&rc, NULL, FALSE);

        //Make sure place point is on page, otherwise go to (0,0)
        if (((int)ptl.x > x)
            && ((int)ptl.x < x+(int)m_pPG->m_cx-rc.left))
            x=(int)ptl.x;

        //m_pPG->m_cy is absolute
        if (((int)ptl.y < y)
            && ((int)ptl.y > y-(int)m_pPG->m_cy-rc.top))
            y=(int)ptl.y;
        }

    //Bounds check size of the object and fit to page as necessary.
    if (x+(int)szl.cx > (int)(h+m_pPG->m_cx))
        szl.cx=h+m_pPG->m_cx-x;

    //Remember that szl we get from Create is absolute
    if (y-(int)szl.cy < (int)(v-m_pPG->m_cy))
        szl.cy=-(int)(v-m_pPG->m_cy-y);

    SETRECTL(rcl, x, y, x+szl.cx, y-szl.cy);
    //CHAPTER17MOD
    m_pTenantCur->RectSet(&rcl, FALSE, TRUE);
    //End CHAPTER17MOD

    //Force a repaint on this new guy
    m_pTenantCur->Invalidate();
    UpdateWindow(m_hWnd);

    m_pTenantCur->Select(TRUE);

    //CHAPTER17MOD
    //Activate new objects immediately and force a save on them
    if (TENANTTYPE_EMBEDDEDOBJECT==tType)
        {
        m_pTenantCur->Activate(OLEIVERB_SHOW);
        m_pTenantCur->Update();
        }
    //End CHAPTER17MOD

    return TRUE;
    }

/*
 * CPage::TenantDestroy
 *
 * Purpose:
 *  Destroys the currently selected tenant on this page.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPage::TenantDestroy(void)
    {
    if (NULL==m_pTenantCur)
        {
        MessageBeep(0);
        return FALSE;
        }

    SendMessage(m_hWndTenantList, LB_DELETESTRING
        , m_iTenantCur, 0L);

    m_pTenantCur->Invalidate();
    m_pTenantCur->Destroy(m_pIStorage);

    //CHAPTER17MOD
    m_pTenantCur->Release();
    //End CHAPTER17MOD
    m_pTenantCur=NULL;

    //Update counts, etc., and select the next tenant in the list.
    if (m_iTenantCur==m_cTenants-1)
        m_iTenantCur--;

    if (0==--m_cTenants)
        m_pTenantCur=NULL;
    else
        {
        TenantGet(m_iTenantCur, &m_pTenantCur, TRUE);
        m_pTenantCur->Select(TRUE);
        }

    UpdateWindow(m_hWnd);
    return TRUE;
    }

/*
 * CPage::TenantClip
 *
 * Purpose:
 *  Copies or cuts the currently selected tenant to the clipoard.
 *
 * Parameters:
 *  fCut            BOOL TRUE to cut the object, FALSE to copy.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPage::TenantClip(BOOL fCut)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet=FALSE;

    if (NULL==m_pTenantCur)
        return FALSE;

    /*
     * To perform a data transfer operation, we need to create a
     * data object with the selected object's data inside. To do
     * this we CoCreateInstance on CLSID_DataTransferObject
     * (Also implemented in this chapter), retrieve data from the
     * object we have, stuff that data into the transfer object,
     * then stick that object on the clipboard.
     *
     * Since we'll want an identical object at other times, like for
     * drag-drop, we use a private function to actually create it.
     */

    pIDataObject=TransferObjectCreate(NULL);

    if (NULL!=pIDataObject)
        {
        if (SUCCEEDED(OleSetClipboard(pIDataObject)))
            {
            if (fCut)
                TenantDestroy();

            fRet=TRUE;
            }

        pIDataObject->Release();
        }

    return fRet;
    }

/*
 * CPage::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected on this
 *  page for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU of the Edit menu.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPage::FQueryObjectSelected(HMENU hMenu)
    {
    //CHAPTER17MOD
    HMENU       hMenuTemp;

    /*
     * This will only be called on WM_INITMENUPOPUP, we'll also
     * use this function to create the Verb menu for this object.
     */
    if (NULL!=m_pTenantCur)
        {
        m_pTenantCur->AddVerbMenu(hMenu, MENUPOS_OBJECT);
        return TRUE;
        }

    OleUIAddVerbMenu(NULL, NULL, hMenu, MENUPOS_OBJECT
        , IDM_VERBMIN, IDM_VERBMAX, FALSE, 0, &hMenuTemp);

    return FALSE;
    //End CHAPTER17MOD
    }

//CHAPTER17MOD
/*
 * CPage::ActivateObject
 *
 * Purpose:
 *  Executes a verb on the currently selected object.
 *
 * Parameters:
 *  iVerb           LONG of the selected verb.
 *
 * Return Value:
 *  None
 */

void CPage::ActivateObject(LONG iVerb)
    {
    if (NULL==m_pTenantCur)
        return;

    m_pTenantCur->Activate(iVerb);
    return;
    }

/*
 * CPage::NotifyTenantsOfRename
 *
 * Purpose:
 *  Loops through all the tenants and informs each of the new
 *  document name.
 *
 * Parameters:
 *  pszFile         LPTSTR of the new filename.
 *  pvReserved      LPVOID reserved for future use.
 *
 * Return Value:
 *  None
 */

void CPage::NotifyTenantsOfRename(LPTSTR pszFile, LPVOID pvReserved)
    {
    PCTenant    pTenant;
    UINT        i;

    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            pTenant->NotifyOfRename(pszFile, pvReserved);
        }

    return;
    }

/*
 * CPage::ConvertObject
 *
 * Purpose:
 *  Invokes and handles the results of the Convert dialog
 *
 * Parameters:
 *  hWndFrame       HWND to use as the parent of the dialog.
 *  fNoServer       BOOL indicating if this was called because
 *                  ActivateObject failed.
 *
 * Return Value:
 *  None
 */

BOOL CPage::ConvertObject(HWND hWndFrame, BOOL fNoServer)
    {
    HRESULT         hr;
    OLEUICONVERT    ct;
    TENANTTYPE      tType;
    TENANTINFO      ti;
    UINT            uRet;
    HCURSOR         hCur;
    BOOL            fActivate=fNoServer;
    SIZEL           szl;

    if (NULL==m_pTenantCur)
        return FALSE;

    tType=m_pTenantCur->TypeGet();

    if (TENANTTYPE_STATIC==tType)
        {
        MessageBeep(0);
        return FALSE;
        }

    //Get object information we may want.
    m_pTenantCur->GetInfo(&ti);

    //Fill the structure.
    memset(&ct, 0, sizeof(ct));
    ct.cbStruct=sizeof(OLEUICONVERT);
    ct.hWndOwner=hWndFrame;
    ct.fIsLinkedObject=FALSE;
    ct.dvAspect=ti.fe.dwAspect;

    m_pTenantCur->ObjectClassFormatAndIcon(&ct.clsid, &ct.wFormat
        , &ct.lpszUserType, &ct.hMetaPict, &ct.lpszDefLabel);

    uRet=OleUIConvert(&ct);

    if (OLEUI_OK==uRet)
        {
        //Potentially a long operation.
        hCur=SetCursor(LoadCursor(NULL, IDC_WAIT));

        //Prevent multiple repaints.
        m_pTenantCur->EnableRepaint(FALSE);

        //First, let's bother with the iconic aspect switch.
        if ((DVASPECT_ICON==ct.dvAspect && ct.fObjectsIconChanged)
            || ct.dvAspect!=ti.fe.dwAspect)
            {
            HGLOBAL     hMem=NULL;

            //Only pass non-NULL handle for icon aspects.
            if (DVASPECT_ICON==ct.dvAspect)
                hMem=ct.hMetaPict;

            m_pPG->m_fDirty=m_pTenantCur->SwitchOrUpdateAspect(hMem
                , FALSE);
            }

        //Now change types around.
        if ((CF_SELECTCONVERTTO & ct.dwFlags)
            && ct.clsid!=ct.clsidNew)
            {
            LPSTORAGE   pIStorage;

            /*
             * User selected convert, so:
             *  1.  Unload the object (back to passive state)
             *  2.  Call INOLE_DoConvert, which calls WriteClassStg,
             *      WriteFmtUserTypeStg, and SetConvertStg.
             *  3.  Reload the object and force an update.
             */

            //This should be the only close necessary.
            m_pTenantCur->StorageGet(&pIStorage);
            m_pTenantCur->Close(TRUE);

            hr=INOLE_DoConvert(pIStorage, ct.clsidNew);

            //Need to commit the new type and format
            pIStorage->Commit(STGC_DEFAULT);
            pIStorage->Release();

            if (SUCCEEDED(hr))
                {
                LPUNKNOWN   pObj;
                LPOLEOBJECT pIOleObject;

                //Reload and update.
                m_pTenantCur->Load(m_pIStorage, &ti);

                m_pTenantCur->ObjectGet(&pObj);
                pObj->QueryInterface(IID_IOleObject
                    , (PPVOID)&pIOleObject);
                pIOleObject->Update();
                pIOleObject->Release();
                pObj->Release();
                }

            m_pPG->m_fDirty=TRUE;
            }

        if (CF_SELECTACTIVATEAS & ct.dwFlags)
            {
            /*
             * User selected Activate As, so:
             *  1.  Add the TreatAs entry in the registry
             *      through CoTreatAsClass
             *  2.  Unload all objects of the old CLSID that you
             *      have loaded.
             *  3.  Reload objects as desired
             *  4.  Activate the current object.
             */

            hr=CoTreatAsClass(ct.clsid, ct.clsidNew);

            if (SUCCEEDED(hr))
                {
                PCTenant    pTenant;
                UINT        i;

                for (i=0; i < m_cTenants; i++)
                    {
                    if (TenantGet(i, &pTenant, FALSE))
                        {
                        pTenant->GetInfo(&ti);
                        pTenant->Close(FALSE);
                        pTenant->Load(m_pIStorage, &ti);
                        }
                    }

                fActivate=TRUE;
                }
            }

        //These two steps insure the object knows of the size.
        m_pTenantCur->SizeGet(&szl, FALSE);
        m_pTenantCur->SizeSet(&szl, FALSE, TRUE);

        m_pTenantCur->EnableRepaint(TRUE);
        m_pTenantCur->Repaint();

        if (fActivate)
            m_pTenantCur->Activate(OLEIVERB_SHOW);

        SetCursor(hCur);
        }

    CoTaskMemFree((void*)ct.lpszUserType);
    INOLE_MetafilePictIconFree(ct.hMetaPict);

    return TRUE;
    }

//End CHAPTER17MOD

/*
 * CPage::TenantGet
 * (Protected)
 *
 * Purpose:
 *  Returns a tenant of a given index returning a BOOL so it's
 *  simple to use this function inside if statements.
 *
 * Parameters:
 *  iTenant         UINT tenant to retrieve 0 based.
 *  ppTenant        PCPage * in which to return the tenant
 *                  pointer
 *  fOpen           BOOL indicating if we should open this
 *                  tenant as well.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPage::TenantGet(UINT iTenant, PCTenant *ppTenant
    , BOOL fOpen)
    {
    if (NULL==ppTenant)
        return FALSE;

    if (LB_ERR!=SendMessage(m_hWndTenantList, LB_GETTEXT
        , iTenant, (LONG)ppTenant))
        {
        if (fOpen)
            (*ppTenant)->Open(m_pIStorage);

        return TRUE;
        }

    return FALSE;
    }

/*
 * CPage::TenantAdd
 * (Protected)
 *
 * Purpose:
 *  Creates a new tenant initialized to the given values.  The new
 *  tenants's storage is created if it does not already exist.  If
 *  fOpenStorage is set the the tenants's storage is opened and left
 *  opened.
 *
 * Parameters:
 *  iTenant         UINT Location at which to insert tenant; new
 *                  tenant is inserted after this position.  NOVALUE
 *                  for the end.
 *  dwID            DWORD ID for this tenant.
 *  ppNew           PCTenant * in which to store the new tenant.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPage::TenantAdd(UINT iTenant, DWORD dwID
    , PCTenant *ppNew)
    {
    PCTenant    pTenant;
    LRESULT     lr;

    if (NULL!=ppNew)
        *ppNew=NULL;

    pTenant=new CTenant(dwID, m_hWnd, m_pPG);

    if (NULL==pTenant)
        return FALSE;

    //CHAPTER17MOD
    //The constructor doesn't AddRef, so we need to.
    pTenant->AddRef();
    //End CHAPTER17MOD

    //Now try to add to the listbox.
    lr=SendMessage(m_hWndTenantList, LB_INSERTSTRING, iTenant
        , (LONG)pTenant);

    if (lr < 0)
        {
        //CHAPTER17MOD
        pTenant->Release();
        //End CHAPTER17MOD
        return FALSE;
        }

    *ppNew=pTenant;
    return TRUE;
    }

/*
 * CPage::TransferObjectCreate
 * (Protected)
 *
 * Purpose:
 *  Creates a DataTransferObject and stuff the current selection's
 *  data into it.
 *
 * Parameters:
 *  pptl            PPOINTL containing the pick point in device
 *                  units applicable only to drag-drop; since
 *                  drag-drop is inherently mouse oriented, we use
 *                  device units for the point.  Ignored if NULL.
 *
 * Return Value:
 *  LPDATAOBJECT    Pointer to the object created, NULL on failure
 */

LPDATAOBJECT CPage::TransferObjectCreate(PPOINTL pptl)
    {
    LPDATAOBJECT    pIDataObject;
    LPDATAOBJECT    pIDataT;
    PPATRONOBJECT   ppo;
    RECTL           rcl;
    LPUNKNOWN       pObj;
    FORMATETC       fe;
    STGMEDIUM       stm;
    HRESULT         hr;

    m_pTenantCur->ObjectGet(&pObj);

    hr=CoCreateInstance(CLSID_DataTransferObject, NULL
        , CLSCTX_INPROC_SERVER, IID_IDataObject
        , (PPVOID)&pIDataObject);

    if (FAILED(hr))
        return NULL;

    //Go get the data we should hold on to.
    hr=pObj->QueryInterface(IID_IDataObject, (PPVOID)&pIDataT);

    if (FAILED(hr))
        {
        pIDataObject->Release();
        pObj->Release();
        return NULL;
        }

    //Copy from known obj into transfer obj.  Ordering is important!

    //Generate placeable object structure
    stm.tymed=TYMED_HGLOBAL;
    stm.pUnkForRelease=NULL;
    stm.hGlobal=GlobalAlloc(GHND, sizeof(PATRONOBJECT));

    if (NULL==stm.hGlobal)
        {
        pIDataObject->Release();
        pObj->Release();
        return NULL;
        }

    ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);

    m_pTenantCur->SizeGet(&ppo->szl, FALSE);
    ppo->szl.cy=-ppo->szl.cy; //Negate to make absolute size

    m_pTenantCur->RectGet(&rcl, FALSE);
    ppo->ptl.x=rcl.left;

ppo->ptl.y=rcl.top;

    if (NULL==pptl)
        {
        ppo->ptlPick.x=0;
        ppo->ptlPick.y=0;
        }
    else
        ppo->ptlPick=*pptl;

    m_pTenantCur->FormatEtcGet(&ppo->fe, FALSE);

    //CHAPTER17MOD
    //GlobalUnlock moved down.
    //End CHAPTER17MOD

    SETDefFormatEtc(fe, m_pPG->m_cf, TYMED_HGLOBAL);
    pIDataObject->SetData(&fe, &stm, TRUE);

    //CHAPTER17MOD
    /*
     * Here now we have to include CFSTR_EMBEDDEDOBJECT and
     * CFSTR_OBJECTDESCRIPTOR when what we have selected is, in
     * fact, a compound document object.  We'll just ask the tenant
     * to set these in pIDataObject since it knows what the object.
     * If we copy embedded object data, make sure the PATRONOBJECT
     * structure has the right format in it as well.
     */
    m_pTenantCur->CopyEmbeddedObject(pIDataObject, &ppo->fe, pptl);

    GlobalUnlock(stm.hGlobal);
    //End CHAPTER17MOD

    //Copy the actual presentation.
    m_pTenantCur->FormatEtcGet(&fe, TRUE);
    pIDataT->GetData(&fe, &stm);
    pIDataObject->SetData(&fe, &stm, TRUE);

    pIDataT->Release();

    pObj->Release();
    return pIDataObject;    //Caller now responsible
    }


PAGEMOUS.CPP

/*
 * PAGEMOUS.CPP
 * Patron Chapter 17
 *
 * Implementation of mouse-related member functions of CPage.
 * The remainder is in PAGE.CPP.  This separate file keeps this
 * grungy hit-testing/drawing code out of our way.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

//Lookups into the array using g_rgHTCode[x+y*3] in PAGEMOUS.CPP
#define YTOP            0
#define YMID            1
#define YBOT            2
#define XLEFT           0
#define XMID            1
#define XRIGHT          2

//Values to restrict sizing in CPage::OnMouseMove
#define SIZINGTOP       0x0001
#define SIZINGBOTTOM    0x0002
#define SIZINGLEFT      0x0004
#define SIZINGRIGHT     0x0008

//This array is for hit-testing lookups
static UINT g_rgHTCode[9]={HTTOPLEFT, HTTOP, HTTOPRIGHT
    , HTLEFT, HTCLIENT, HTRIGHT, HTBOTTOMLEFT, HTBOTTOM
    , HTBOTTOMRIGHT};

//This is for restricting tracking based on the hit-test
static UINT g_rguSizingFlags[9]={SIZINGTOP | SIZINGLEFT, SIZINGTOP
    , SIZINGTOP | SIZINGRIGHT, SIZINGLEFT, 0, SIZINGRIGHT
    , SIZINGBOTTOM | SIZINGLEFT, SIZINGBOTTOM
    , SIZINGBOTTOM | SIZINGRIGHT};

//CHAPTER17MOD
/*
 * CPage::OnRightDown
 *
 * Purpose:
 *  Called when the user clicks with the right button on this
 *  page.  If there is an object here, determined by the last
 *  hit-test code, the we'll make a popup-menu for it.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if the action changed the object.
 */

BOOL CPage::OnRightDown(UINT uKeys, UINT x, UINT y)
    {
    HMENU       hMenu;
    HMENU       hMenuRes;
    HINSTANCE   hInst;
    HWND        hWndFrame, hWndT;
    POINT       pt;
    UINT        i, cItems;

    //Select the tenant under the mouse, if there is one.
    if (!SelectTenantAtPoint(x, y))
        return FALSE;

    /*
     * Get the top-level window to which menu command will go.  This
     * will be whatever parent doesn't have a parent itself...
     */
    hWndT=GetParent(m_hWnd);

    while (NULL!=hWndT)
        {
        hWndFrame=hWndT;
        hWndT=GetParent(hWndT);
        }

    /*
     * Build a popup menu for this object with Cut, Copy, Delete,
     * and object verbs.
     */
    hInst=GETWINDOWINSTANCE(m_hWnd);    //Macro in BOOK1632.H
    hMenuRes=LoadMenu(hInst, MAKEINTRESOURCE(IDR_RIGHTPOPUPMENU));

    if (NULL==hMenuRes)
        return FALSE;

    hMenu=CreatePopupMenu();
    cItems=GetMenuItemCount(hMenuRes);

    for (i=0; i < cItems; i++)
        {
        TCHAR       szTemp[80];
        int         id, uFlags;

        GetMenuString(hMenuRes, i, szTemp, sizeof(szTemp)
            , MF_BYPOSITION);
        id=GetMenuItemID(hMenuRes, i);

        uFlags=(0==id) ? MF_SEPARATOR : MF_STRING | MF_ENABLED;
        AppendMenu(hMenu, uFlags, id, szTemp);
        }

    DestroyMenu(hMenuRes);

    //Munge the Object menu item
    m_pTenantCur->AddVerbMenu(hMenu, MENUPOS_OBJECTONPOPUP);

    SETPOINT(pt, x, y);
    ClientToScreen(m_hWnd, &pt);

    TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON
        , pt.x, pt.y, 0, hWndFrame, NULL);

    DestroyMenu(hMenu);
    return FALSE;
    }

/*
 * CPage::SelectTenantAtPoint
 *
 * Purpose:
 *  Selects whatever tenant is at the point (x,y) if there is one,
 *  deselecting the previously selected tenant.
 *
 * Parameters:
 *  x, y            UINT coordinates of the mouse.
 *
 * Return Value:
 *  BOOL            TRUE if there is a tenant here, FALSE otherwise.
 */

BOOL CPage::SelectTenantAtPoint(UINT x, UINT y)
    {
    UINT            iTenant;
    PCTenant        pTenant;
    PCDocument      pDoc;

    iTenant=TenantFromPoint(x, y, &pTenant);

    if (NULL==pTenant)
        return FALSE;

    //Make the document window active in any case
    pDoc=(PCDocument)SendMessage(GetParent(m_hWnd), DOCM_PDOCUMENT
        , 0, 0L);

    if (NULL!=pDoc)
        BringWindowToTop(pDoc->Window());

    //If this one is already current, we might be now sizing.
    if (pTenant==m_pTenantCur)
        return TRUE;

    //Deselect the current tenant
    if (NULL!=m_pTenantCur)
        m_pTenantCur->Select(FALSE);

    //Move this tenant to the top of the list
    m_iTenantCur=0;

    SendMessage(m_hWndTenantList, LB_DELETESTRING, iTenant, 0L);
    SendMessage(m_hWndTenantList, LB_INSERTSTRING, 0
        , (LONG)pTenant);

    //Select and repaint the new tenant to show it up front
    m_pTenantCur=pTenant;

    m_pTenantCur->Repaint();
    m_pTenantCur->Select(TRUE);

    return TRUE;
    }

//End CHAPTER17MOD

/*
 * CPage::OnLeftDown
 *
 * Purpose:
 *  Called when the user clicks with the left button on this page.
 *  We find the object under that position that is visibly on top
 *  (always the first one under this location in the page list since
 *  we paint in reverse order) and select it.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if the action changed the object.
 */

BOOL CPage::OnLeftDown(UINT uKeys, UINT x, UINT y)
    {
    /*
     * If the mouse is in a position to start dragging,
     * start the timer as with sizing below.
     */
    if (HTCAPTION==m_uHTCode)
        {
        m_fDragPending=TRUE;

        //Save down point and start timer.
        m_ptDown.x=x;
        m_ptDown.y=y;

        m_uKeysDown=uKeys;

        m_fTimer=TRUE;
        SetTimer(m_hWnd, IDTIMER_DEBOUNCE, m_cDelay, NULL);
        return FALSE;
        }

    /*
     * If the mouse is in a position to start sizing, start
     * the debounce timer and note the condition.  The sizing
     * will start in OnTimer or OnMouseMove.  This will always
     * happen on the currently selected tenant, and m_uHTCode is
     * set in OnNCHitTest below.
     */
    if (HTNOWHERE!=m_uHTCode && HTCLIENT!=m_uHTCode)
        {
        m_fSizePending=TRUE;

        //Save down point and start timer.
        m_ptDown.x=x;
        m_ptDown.y=y;

        m_fTimer=TRUE;
        SetTimer(m_hWnd, IDTIMER_DEBOUNCE, m_cDelay, NULL);
        return FALSE;
        }

    //CHAPTER17MOD
    //Selection logic moved to a function so OnRightDown can use it.
    SelectTenantAtPoint(x, y);
    //End CHAPTER17MOD

    return FALSE;
    }

/*
 * CPage::OnLeftUp
 *
 * Purpose:
 *  Called when the user clicks up with the left button on this
 *  page. We stop tracking on this message, if necessary, and
 *  resize the object.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if this action changed the object.
 */

BOOL CPage::OnLeftUp(UINT uKeys, UINT x, UINT y)
    {
    RECT    rc, rcT;

    if (m_fSizePending || m_fDragPending)
        {
        m_fSizePending=FALSE;
        m_fDragPending=FALSE;

        if (m_fTimer)
            {
            KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
            m_fTimer=FALSE;
            }

        return FALSE;
        }

    if (!m_fTracking)
        return FALSE;

    //Remove the dotted rectangle.
    RECTFROMRECTL(rc, m_rcl)
    DrawFocusRect(m_hDC, &rc);
    ReleaseDC(m_hWnd, m_hDC);

    ReleaseCapture();
    m_fTracking=FALSE;

    //If the original and new rects are the same, nothing happened.
    RECTFROMRECTL(rcT, m_rclOrg);

    if (EqualRect(&rc, &rcT))
        return FALSE;

    RECTFROMRECTL(rcT, m_rclOrg);
    InvalidateRect(m_hWnd, &rcT, TRUE);

    //Invalidate on the screen before accounting for scrolling
    InvalidateRect(m_hWnd, &rc, TRUE);

    //Factor in scrolling and tell the tenant where it now stands.
    OffsetRect(&rc, (int)m_pPG->m_xPos, (int)m_pPG->m_yPos);
    RECTLFROMRECT(m_rcl, rc);
    //CHPATER17MOD
    m_pTenantCur->RectSet(&m_rcl, TRUE, TRUE);
    //End CHAPTER17MOD

    UpdateWindow(m_hWnd);
    return TRUE;
    }

/*
 * CPage::OnLeftDoubleClick
 *
 * Purpose:
 *  Called when the user double-clicks with the left button on this
 *  page.  We find the object under that position that is visibly on
 *  top (always the first one under this location in the page list
 *  since we paint in reverse order) and activate it.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if the action changed the object.
 */

BOOL CPage::OnLeftDoubleClick(UINT uKeys, UINT x, UINT y)
    {
    /*
     * The current tenant is the only one that can be activated, so
     * we just have to make sure the mouse is there.  For that we
     * can use the last hit-test code we saw since it's updated on
     * every mouse move.
     */

    if (HTNOWHERE!=m_uHTCode)
        return m_pTenantCur->Activate(OLEIVERB_PRIMARY);

    return FALSE;
    }

/*
 * CPage::OnMouseMove
 *
 * Purpose:
 *  Processes WM_MOUSEMOVE on a page so we can handle tracking
 *  resize of a tenant.
 *
 * Parameters:
 *  x, y            int device coordinates to check.
 *
 * Return Value:
 *  None
 */

void CPage::OnMouseMove(UINT uKeys, int x, int y)
    {
    RECT        rc, rcO, rcB;
    int         cxy;

    if (m_fSizePending || m_fDragPending)
        {
        int     dx, dy;

        dx=(x > m_ptDown.x) ? (x-m_ptDown.x) : (m_ptDown.x-x);
        dy=(y > m_ptDown.y) ? (y-m_ptDown.y) : (m_ptDown.y-y);

        /*
         * Has the mouse moved outside the debounce distance?  If
         * so, we can start sizing.  Note that this happens
         * regardless of the timer state.
         */
        if (dx > m_cxyDist || dy > m_cxyDist)
            {
            POINT       pt;
            BOOL        fSize=m_fSizePending;
            BOOL        fDrag=m_fDragPending;

            m_fSizePending=FALSE;
            m_fDragPending=FALSE;

            if (m_fTimer)
                {
                KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
                m_fTimer=FALSE;
                }

            if (fDrag)
                {
                //Set dirty flag if drag & drop changed things.
                m_pPG->m_fDirty |= DragDrop(m_uKeysDown, x, y);
                return;
                }

            if (fSize)
                StartSizeTracking();

            /*
             * Since we might have moved out of the sizing handle
             * in order to start the operation, we need to set the
             * m_uSizingFlags field based on the original down point
             * for subsequent mouse moves to function properly.
             * Note that OnNCHitTest expects screen coordinates.
             */
            SETPOINT(pt, m_ptDown.x, m_ptDown.y);
            ClientToScreen(m_hWnd, &pt);
            OnNCHitTest(pt.x, pt.y);
            OnSetCursor(m_uHTCode);
            return;
            }
        }

    if (!m_fTracking)
        return;

    //Get rid of the old rectangle.
    RECTFROMRECTL(rc, m_rcl)
    DrawFocusRect(m_hDC, &rc);

    /*
     * Calculate the new.  The flags in m_uSizingFlags tell us what
     * to change.  We limit the object by the page margins and a
     * minimum size of 3*CXYHANDLE in either dimension.
     */
    cxy=3*CXYHANDLE;

    RECTFROMRECTL(rcO, m_rclOrg);
    RECTFROMRECTL(rcB, m_rclBounds);

    if (m_uSizingFlags & SIZINGTOP)
        {
        if (y >= rcO.bottom-cxy)
            y=rcO.bottom-cxy;

        if (y <= rcB.top)           //Limit to top of page.
            y=rcB.top;

        m_rcl.top=y;
        }

    if (m_uSizingFlags & SIZINGBOTTOM)
        {
        if (y <= rcO.top+cxy)
            y=rcO.top+cxy;

        if (y >= rcB.bottom)         //Limit to bottom of page.
            y=rcB.bottom;

        m_rcl.bottom=y;
        }

    if (m_uSizingFlags & SIZINGLEFT)
        {
        if (x >= rcO.right-cxy)
            x=rcO.right-cxy;

        if (x <= rcB.left)           //Limit to left of page.
            x=rcB.left;

        m_rcl.left=x;
        }

    if (m_uSizingFlags & SIZINGRIGHT)
        {
        if (x <= rcO.left+cxy)
            x=rcO.left+cxy;

        if (x >= rcB.right)          //Limit to right of page.
            x=rcB.right;

        m_rcl.right=x;
        }

    //Draw the new
    RECTFROMRECTL(rc, m_rcl)
    DrawFocusRect(m_hDC, &rc);

    return;
    }

/*
 * CPage::OnTimer
 *
 * Purpose:
 *  Processes WM_TIMER messages to a page used to perform mouse
 *  debouncing.
 *
 * Parameters:
 *  uID             UINT timer ID.
 *
 * Return Value:
 *  None
 */

void CPage::OnTimer(UINT uID)
    {
    if (m_fSizePending || m_fDragPending)
        {
        BOOL        fSize=m_fSizePending;
        BOOL        fDrag=m_fDragPending;

        /*
         * Having this function called means the delay requirement
         * is satisfied.  Start tracking for sizing or dragging.
         */

        m_fSizePending=FALSE;
        m_fDragPending=FALSE;

        KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
        m_fTimer=FALSE;

        if (fDrag)
            {
            POINT       pt;

            GetCursorPos(&pt);
            m_pPG->m_fDirty |= DragDrop(m_uKeysDown
                , m_ptDown.x, m_ptDown.y);
            return;
            }

        if (fSize)
            StartSizeTracking();
        }

    return;
    }

/*
 * CPage::StartSizeTracking
 *
 * Purpose:
 *  Begins sizing of a tenant when mouse debounce conditions are
 *  met.
 *
 * Parameters:
 *  uID             UINT timer ID.
 *
 * Return Value:
 *  None
 */

void CPage::StartSizeTracking(void)
    {
    RECT        rc;

    m_pTenantCur->RectGet(&m_rcl, TRUE);
    SetCapture(m_hWnd);
    m_fTracking=TRUE;

    m_hDC=GetDC(m_hWnd);

    //Place the rectangle exactly where it is on the screen.
    RECTFROMRECTL(rc, m_rcl)
    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);
    RECTLFROMRECT(m_rcl, rc);
    m_rclOrg=m_rcl;

    DrawFocusRect(m_hDC, &rc);

    m_pPG->CalcBoundingRect(&rc, TRUE);
    RECTLFROMRECT(m_rclBounds, rc);
    return;
    }

/*
 * CPage::OnNCHitTest
 *
 * Purpose:
 *  Processes WM_NCHITTEST on a page so we can check for hits on the
 *  handles of the selected object for resizing.  We only save
 *  information for ourselves and do not interfere with normal
 *  hit-testing.
 *
 * Parameters:
 *  x, y            UINT device coordinates to check.
 *
 * Return Value:
 *  None
 */

void CPage::OnNCHitTest(UINT x, UINT y)
    {
    RECT        rc;
    RECTL       rcl;
    int         iMid1, iMid2;
    int         xHit, yHit;
    POINT       pt;
    int         x0, y0;

    /*
     * Ignore this message if it occurs during tracking to adjust
     * for the behavior of oddball mouse drivers.
     */
    if (m_fSizePending || m_fTracking)
        return;

    //Default: don't start sizing on a click, don't hit an object.
    m_uSizingFlags=0;
    m_uHTCode=HTNOWHERE;

    if (NULL==m_pTenantCur)
        return;

    //Convert device points to our coordinates
    m_pTenantCur->RectGet(&rcl, FALSE);
    RECTFROMRECTL(rc, rcl);
    RectConvertMappings(&rc, NULL, TRUE);
    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

    SETPOINT(pt, x, y);
    ScreenToClient(m_hWnd, &pt);
    x0=pt.x;
    y0=pt.y;

    if (x0 < rc.left || x0 > rc.right)
        return;

    if (y0 < rc.top || y0 > rc.bottom)
        return;

    //It's at least in the object.
    m_uHTCode=HTCLIENT;

    //Check for hits in horizontal regions
    xHit=NOVALUE;
    iMid1=rc.left+((rc.right-rc.left-CXYHANDLE) >> 1);
    iMid2=rc.left+((rc.right-rc.left+CXYHANDLE) >> 1);

    if (x0 >= rc.left && x0 <= rc.left+CXYHANDLE)
        xHit=XLEFT;
    else if (x0 >= iMid1 && x0 <= iMid2)
        xHit=XMID;
    else if (x0 >= rc.right-CXYHANDLE && x0 <= rc.right)
        xHit=XRIGHT;

    //Don't exit yet if we didn't hit a handle--might hit a y edge.

    //Check for hits in vertical regions
    yHit=NOVALUE;
    iMid1=rc.top+((rc.bottom-rc.top-CXYHANDLE) >> 1);
    iMid2=rc.top+((rc.bottom-rc.top+CXYHANDLE) >> 1);

    if (y0 >= rc.top && y0 <= rc.top+CXYHANDLE)
        yHit=YTOP;
    else if (y0 >= iMid1 && y0 <= iMid2)
        yHit=YMID;
    else if (y0 >= rc.bottom-CXYHANDLE && y0 <= rc.bottom)
        yHit=YBOT;

    /*
     * If we hit any edge, but didn't hit a handle, then one of xHit
     * and yHit will be NOVALUE and the other something else.  When
     * we hit an edge on the 'something else' then we're on a drag
     * point.
     */

    if ((NOVALUE==xHit && NOVALUE==yHit)
        || (XMID==xHit && YMID==yHit)
        || (NOVALUE==xHit && YMID==yHit)
        || (XMID==xHit && NOVALUE==yHit))
        return;

    if ((NOVALUE==xHit && (YTOP==yHit || YBOT==yHit))
        || ((XLEFT==xHit || XRIGHT==xHit) && NOVALUE==yHit))
        {
        m_uHTCode=HTCAPTION;
        return;
        }

    //We hit a handle, so save our HT code
    m_uSizingFlags=g_rguSizingFlags[xHit+(yHit*3)];
    m_uHTCode=g_rgHTCode[xHit+(yHit*3)];
    return;
    }

/*
 * CPage::SetCursor
 *
 * Purpose:
 *  Processes WM_SETCURSOR using the code from OnNCHitTest.
 *
 * Parameters:
 *  x, y            UINT device coordinates to check.
 *
 * Return Value:
 *  LRESULT         HT* code for Windows.
 */

BOOL CPage::OnSetCursor(UINT uHTCode)
    {
    HCURSOR     hCur;
    UINT        iCur;

    /*
     * We really just ignore uHTCode and use the one we saved
     * in OnNCHitTest.
     */

    switch (m_uHTCode)
        {
        case HTTOP:
        case HTBOTTOM:
            iCur=IDC_VARROWS;
            break;

        case HTLEFT:
        case HTRIGHT:
            iCur=IDC_HARROWS;
            break;

        case HTTOPLEFT:
        case HTBOTTOMRIGHT:
            iCur=IDC_NWSEARROWS;
            break;

        case HTTOPRIGHT:
        case HTBOTTOMLEFT:
            iCur=IDC_NESWARROWS;
            break;

        case HTCAPTION:
            iCur=IDC_SMALLARROWS;
            break;

        default:
            return FALSE;
        }

    hCur=UICursorLoad(iCur);
    SetCursor(hCur);

    return TRUE;
    }

/*
 * CPage::TenantFromPoint
 * (Protected)
 *
 * Purpose:
 *  Finds the tenant under the given device coordinates on this
 *  page.
 *
 * Parmeters:
 *  x, y            UINT coordinates.
 *  ppTenant        PCTenant * in which to return the pointer.
 *
 * Return Value:
 *  UINT            Index of the matched tenant, NOVALUE if not
 *                  found.
 */

UINT CPage::TenantFromPoint(UINT x, UINT y, PCTenant *ppTenant)
    {
    PCTenant    pTenant;
    RECTL       rcl;
    UINT        i;
    int         x0, y0;

    x0=x+m_pPG->m_xPos;
    y0=y+m_pPG->m_yPos;

    for (i=0; i < m_cTenants; i++)
        {
        if (!TenantGet(i, &pTenant, FALSE))
            continue;

        pTenant->RectGet(&rcl, TRUE);

        //Essentially Perform PointInRECTL
        if (x0 >= rcl.left && x0 <= rcl.right)
            {
            if (y0 <=rcl.bottom && y0 >=rcl.top)
                {
                *ppTenant=pTenant;
                return i;
                }
            }
        }

    *ppTenant=NULL;
    return NOVALUE;
    }

/*
 * CPage::DragDrop
 *
 * Purpose:
 *  Performs drag-drop operations from the page window
 *
 * Parmeters:
 *  uKeys           UINT state of the keyboard
 *  x, y            UINT mouse coordinates of the starting click.
 *
 * Return Value:
 *  BOOL            TRUE if we modified the page, FALSE otherwise.
 */

BOOL CPage::DragDrop(UINT uKeys, UINT x, UINT y)
    {
    LPDROPSOURCE    pIDropSource;
    LPDATAOBJECT    pIDataObject;
    HRESULT         hr;
    DWORD           dwEffect;
    POINTL          ptl;
    SIZEL           szl;
    RECTL           rcl;
    RECT            rc, rcT;

    pIDropSource=new CDropSource();

    if (NULL==pIDropSource)
        return FALSE;

    pIDropSource->AddRef();
    m_pPG->m_fDragSource=TRUE;

    /*
     * Store a pick point with the data indicating the offset from
     * the upper left of the rectangle where we grabbed it.  This is
     * so the UI feedback in IDropTarget lines up with this tenant.
     */

    m_pTenantCur->RectGet(&rcl, TRUE);
    ptl.x=x+m_pPG->m_xPos-rcl.left;
    ptl.y=y+m_pPG->m_yPos-rcl.top;
    pIDataObject=TransferObjectCreate(&ptl);

    if (NULL==pIDataObject)
        {
        pIDropSource->Release();
        return FALSE;
        }

    m_pPG->m_fMoveInPage=FALSE;

    dwEffect=DROPEFFECT_COPY | DROPEFFECT_MOVE;
    hr=DoDragDrop(pIDataObject, pIDropSource
        , DROPEFFECT_COPY | DROPEFFECT_MOVE, &dwEffect);

    pIDataObject->Release();
    pIDropSource->Release();

    m_pPG->m_fDragSource=FALSE;

    //No drop-no action.
    if (DRAGDROP_S_DROP!=GetScode(hr) || DROPEFFECT_NONE==dwEffect)
        return FALSE;

    /*
     * If m_pPG->m_fMoveInPage is set, then we just change the
     * coordinates on m_pTenantCur and we're done.
     */
    if (m_pPG->m_fMoveInPage)
        {
        m_pTenantCur->Invalidate();

        /*
         * Clip to page boundaries.  We know that ptDrop has to be
         * in the page somewhere or we would not have dropped
         * (effect was NONE).  So first make sure that ptDrop is
         * within 3*CXYHANDLE of the right or bottom, and if so,
         * pull it out to 3*CXYHANDLE.  Then we can just clip the
         * size to the page rectangle and we'll always be sure to
         * have at least a sizeable object.
         */
        m_pTenantCur->SizeGet(&szl, TRUE);
        SetRect(&rc, (int)m_pPG->m_ptDrop.x, (int)m_pPG->m_ptDrop.y
            , 0, 0);
        RectConvertMappings(&rc, NULL, TRUE);

        m_pPG->CalcBoundingRect(&rcT, FALSE);
        OffsetRect(&rcT, (int)m_pPG->m_xPos, (int)m_pPG->m_yPos);

        if (rc.left >= rcT.right-3*CXYHANDLE)
            rc.left=rcT.right-3*CXYHANDLE;

        if (rc.top >= rcT.bottom-3*CXYHANDLE)
            rc.top=rcT.bottom-3*CXYHANDLE;

        rc.right=rc.left+(int)szl.cx;
        rc.bottom=rc.top+(int)szl.cy;
        IntersectRect(&rc, &rc, &rcT);

        RECTLFROMRECT(rcl, rc);

        //CHAPTER17MOD
        m_pTenantCur->RectSet(&rcl, TRUE, FALSE);
        //End CHAPTER17MOD
        m_pTenantCur->Repaint();
        return TRUE;
        }

    /*
     * Otherwise we may have to delete the old tenant if the effect
     * was move.  This will not happen in the move in page case.
     */

    if (DROPEFFECT_MOVE==dwEffect)
        {
        TenantDestroy();
        return TRUE;
        }

    //Copy is a clean operation
    return FALSE;
    }


PAGES.CPP

/*
 * PAGES.CPP
 * Patron Chapter 17
 *
 * Implementation of the CPages class.  See PAGEWIN.CPP and PRINT.CPP
 * for additional member functions.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPages:CPages
 * CPages::~CPages
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application we're in.
 *  cf              UINT application clipboard format.
 */

CPages::CPages(HINSTANCE hInst, UINT cf)
    : CWindow(hInst)
    {
    m_pPageCur=NULL;
    m_iPageCur=NOVALUE;  //Pages are 0 indexed, this is one before
    m_cPages=0;
    m_hWndPageList=NULL;
    m_hFont=NULL;
    m_fSystemFont=FALSE;

    //Initialize to 8.5*11 inch with .25 inch margins as a default.
    m_cx=(LOMETRIC_PER_INCH*17)/2;
    m_cy=LOMETRIC_PER_INCH*11;

    m_xMarginLeft=LOMETRIC_PER_INCH/4;
    m_xMarginRight=LOMETRIC_PER_INCH/4;
    m_yMarginTop=LOMETRIC_PER_INCH/4;
    m_yMarginBottom=LOMETRIC_PER_INCH/4;

    m_xPos=0L;
    m_yPos=0L;

    m_dwIDNext=0;
    m_pIStorage=NULL;

    m_fDirty=FALSE;
    m_cf=cf;

    m_fDragSource=FALSE;
    m_fMoveInPage=FALSE;

    m_fDragRectShown=FALSE;

    m_uScrollInset=GetProfileInt(TEXT("windows")
        , TEXT("DragScrollInset"), DD_DEFSCROLLINSET);
    m_uScrollDelay=GetProfileInt(TEXT("windows")
        , TEXT("DragScrollDelay"), DD_DEFSCROLLDELAY);

    m_uHScrollCode=0;
    m_uVScrollCode=0;
    return;
    }

CPages::~CPages(void)
    {
    //Ensure memory cleaned up in list; do final IStorage::Release
    StorageSet(NULL, FALSE, FALSE);

    if (NULL!=m_hFont && !m_fSystemFont)
        DeleteObject(m_hFont);

    if (NULL!=m_hWndPageList)
        DestroyWindow(m_hWndPageList);

    //m_hWnd destroyed with the document.
    return;
    }

/*
 * CPages::FIsDirty
 *
 * Purpose:
 *  Tells the caller (document) if anything's happened to dirty us.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPages::FIsDirty(void)
    {
    return m_fDirty;
    }

/*
 * CPages::Init
 *
 * Purpose:
 *  Instantiates a pages window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags.
 *                  Should contain WS_CHILD | WS_VISIBLE in
 *                  typical circumstances.
 *  uID             UINT ID to associate with this window
 *  pv              LPVOID unused for now.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPages::Init(HWND hWndParent, LPRECT pRect, DWORD dwStyle
    , UINT uID, LPVOID pv)
    {
    int     cy;

    m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY, SZCLASSPAGES
        , SZCLASSPAGES, dwStyle, pRect->left, pRect->top
        , pRect->right-pRect->left, pRect->bottom-pRect->top
        , hWndParent, (HMENU)uID, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    /*
     * Create the hidden listbox we'll use to track pages.  We give
     * it the owner-draw style so we can just store pointers in it.
     */
    m_hWndPageList=CreateWindow(TEXT("listbox"), TEXT("Page List")
        , WS_POPUP | LBS_OWNERDRAWFIXED, 0, 0, 100, 100
        , HWND_DESKTOP, NULL, m_hInst, NULL);

    if (NULL==m_hWndPageList)
        return FALSE;

    //Create a 14 point Arial font, or use the system variable font.
    cy=MulDiv(-14, LOMETRIC_PER_INCH, 72);
    m_hFont=CreateFont(cy, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE
        , ANSI_CHARSET, OUT_TT_PRECIS, CLIP_TT_ALWAYS, PROOF_QUALITY
        , VARIABLE_PITCH | FF_SWISS, TEXT("Arial"));

    if (NULL==m_hFont)
        {
        m_hFont=(HFONT)GetStockObject(ANSI_VAR_FONT);
        m_fSystemFont=TRUE;
        }

    return TRUE;
    }

/*
 * CPages::StorageSet
 *
 * Purpose:
 *  Provides the document's IStorage to the pages for its own use.
 *  If this is a new storage, we initalize it with streams that we
 *  want to always exists.  If this is an open, then we create
 *  our page list from the PageList string we wrote before.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE to the new or opened storage.  If
 *                  NULL, we just clean up and exit.
 *  fChange         BOOL indicating is this was a Save As operation,
 *                  meaning that we have the structure already and
 *                  just need to change the value of m_pIStorage.
 *  fInitNew        BOOL indicating if this is a new storage or one
 *                  opened from a previous save.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::StorageSet(LPSTORAGE pIStorage, BOOL fChange
    , BOOL fInitNew)
    {
    DWORD           dwMode=STGM_DIRECT | STGM_READWRITE
                        | STGM_SHARE_EXCLUSIVE;
    HRESULT         hr;
    PCPage          pPage;
    BOOL            fRet=FALSE;
    ULONG           cbRead;
    PAGELIST        pgList;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    LPDWORD         pdwID;
    UINT            i;

    //If we're changing saved roots, simply open current page again
    if (fChange)
        {
        if (NULL==pIStorage)
            return FALSE;

        m_pIStorage->Release();
        m_pIStorage=pIStorage;
        m_pIStorage->AddRef();

        PageGet(m_iPageCur, &m_pPageCur, TRUE);
        m_fDirty=FALSE;
        return TRUE;
        }

    if (NULL!=m_hWndPageList)
        {
        //On new or open, clean out whatever it is we have.
        for (i=0; i < m_cPages; i++)
            {
            if (PageGet(i, &pPage, FALSE))
                delete pPage;
            }

        SendMessage(m_hWndPageList, LB_RESETCONTENT, 0, 0L);
        }

    if (NULL!=m_pIStorage)
        m_pIStorage->Release();

    m_pIStorage=NULL;

    //If we're just cleaning up, then we're done.
    if (NULL==pIStorage)
        return TRUE;

    m_pIStorage=pIStorage;
    m_pIStorage->AddRef();

    //If this is a new storage, create the streams we require
    if (fInitNew)
        {
        //Page list header.
        hr=m_pIStorage->CreateStream(SZSTREAMPAGELIST, dwMode
            | STGM_CREATE, 0, 0, &pIStream);

        if (FAILED(hr))
            return FALSE;

        pIStream->Release();

        //Device Configuration
        hr=m_pIStorage->CreateStream(SZSTREAMDEVICECONFIG, dwMode
            | STGM_CREATE, 0, 0, &pIStream);

        if (FAILED(hr))
            return FALSE;

        pIStream->Release();
        return TRUE;
        }

    /*
     * We're opening an existing file:
     *  1)  Configure for the device we're on
     *  2)  Read the Page List and create page entries for each.
     */

    ConfigureForDevice();

    //Read the page list.
    hr=m_pIStorage->OpenStream(SZSTREAMPAGELIST, NULL, dwMode, 0
        , &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pIStream->Read(&pgList, sizeof(PAGELIST), &cbRead);
        m_cPages  =(UINT)pgList.cPages;
        m_iPageCur=(UINT)pgList.iPageCur;
        m_dwIDNext=pgList.dwIDNext;

        fRet=TRUE;
        cbRead=pgList.cPages*sizeof(DWORD);

        if (0!=cbRead)
            {
            pdwID=(LPDWORD)pIMalloc->Alloc(cbRead);

            if (NULL!=pdwID)
                {
                pIStream->Read(pdwID, cbRead, &cbRead);

                for (i=0; i < m_cPages; i++)
                    fRet &=PageAdd(NOVALUE, *(pdwID+i), FALSE);

                pIMalloc->Free(pdwID);
                }
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    if (!fRet)
        return FALSE;

    PageGet(m_iPageCur, &m_pPageCur, TRUE);
    m_fDirty=FALSE;

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);

    return TRUE;
    }

/*
 * CPages::StorageUpdate
 *
 * Purpose:
 *  Insures that all pages are committed before a root save.
 *
 * Parameters:
 *  fCloseAll       BOOL directing us to close all open storages
 *                  and streams.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::StorageUpdate(BOOL fCloseAll)
    {
    PCPage          pPage;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    LPDWORD         pdwID;
    ULONG           cb;
    HRESULT         hr;
    PAGELIST        pgList;
    BOOL            fRet=FALSE;
    UINT            i;

    //We only need to close the current page--nothing else is open.
    if (NULL!=m_pPageCur)
        {
        m_pPageCur->Update();

        if (fCloseAll)
            m_pPageCur->Close(FALSE);
        }

    //We don't hold anything else open, so write the page list.
    hr=m_pIStorage->OpenStream(SZSTREAMPAGELIST, NULL, STGM_DIRECT
        | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pgList.cPages=m_cPages;
        pgList.iPageCur=m_iPageCur;
        pgList.dwIDNext=m_dwIDNext;

        pIStream->Write(&pgList, sizeof(PAGELIST), &cb);

        cb=m_cPages*sizeof(DWORD);
        pdwID=(LPDWORD)pIMalloc->Alloc(cb);

        if (NULL!=pdwID)
            {
            for (i=0; i < m_cPages; i++)
                {
                PageGet(i, &pPage, FALSE);
                *(pdwID+i)=pPage->GetID();
                }

            pIStream->Write(pdwID, cb, &cb);
            pIMalloc->Free(pdwID);
            fRet=TRUE;
            }
        pIMalloc->Release();
        }

    pIStream->Release();

    //Clean up the dirty flag when we do an update.
    m_fDirty=!fRet;
    return fRet;
    }

/*
 * CPages::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Pages window in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  None
 */

void CPages::RectGet(LPRECT pRect)
    {
    RECT        rc;
    POINT       pt;

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_hWnd, &rc);
    SETPOINT(pt, rc.left, rc.top);
    ScreenToClient(GetParent(m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return;
    }

/*
 * CPages::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Pages window which sizes to fit.
 *  Coordinates are given in parent terms.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPages::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    if (NULL==pRect)
        return;

    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, pRect->left, pRect->top
        , (UINT)cx, (UINT)cy, SWP_NOZORDER);

    UpdateScrollRanges();
    return;
    }

/*
 * CPages::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the pages window in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  None
 */

void CPages::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return;
    }

/*
 * CPages::SizeSet
 *
 * Purpose:
 *  Sets a new size in parent coordinates for the Pages window.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPages::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    if (NULL==pRect)
        return;

    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
        , SWP_NOMOVE | SWP_NOZORDER);

    UpdateScrollRanges();
    return;
    }

/*
 * CPages::ActivePage
 *
 * Purpose:
 *  Returns a CPage pointer to the current page.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCPage          Pointer to the current page.
 */

PCPage CPages::ActivePage(void)
    {
    PCPage      pPage;
    BOOL        fRet;

    fRet=PageGet(m_iPageCur, &pPage, FALSE);
    return fRet ? pPage : NULL;
    }

/*
 * CPages::PageInsert
 *
 * Purpose:
 *  Creates a new page immediately after the current page.  If
 *  there are no pages then this creates page 1.
 *
 * Parameters:
 *  uReserved       UINT unused
 *
 * Return Value:
 *  UINT            Index of the new page, 0 on failure.
 */

UINT CPages::PageInsert(UINT uReserved)
    {
    if (0!=m_cPages && NULL!=m_pPageCur)
        {
        //Close the current page, committing changes.
        m_pPageCur->Close(TRUE);
        }

    //Create and open the new page.
    if (!PageAdd(m_iPageCur, m_dwIDNext, TRUE))
        return 0;

    m_dwIDNext++;
    m_iPageCur++;
    m_cPages++;

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);

    PageGet(m_iPageCur, &m_pPageCur, FALSE);
    return m_iPageCur;
    }

/*
 * CPages::PageDelete
 *
 * Removes the current page from the page list.
 *
 * Parameters:
 *  uReserved       UINT unused
 *
 * Return Value:
 *  UINT            Index to the now current page from the page
 *                  list, NOVALUE on error.
 */

UINT CPages::PageDelete(UINT uReserved)
    {
    PCPage      pPage;

    if (!PageGet(m_iPageCur, &pPage, FALSE))
        return NOVALUE;

    //Delete the page in both the storage and in memory.
    SendMessage(m_hWndPageList, LB_DELETESTRING, m_iPageCur, 0L);

    m_pPageCur->Destroy(m_pIStorage);

    delete m_pPageCur;   //Does final pPage->Close
    m_pPageCur=NULL;

    /*
     * If this is the last page then the current is one less.  If
     * it's the only page the current is zero.  Otherwise the
     * current is the next page.
     */

    if (m_iPageCur==m_cPages-1)   //Covers last or only page.
        m_iPageCur--;

    m_cPages--;

    //Insure the new visible page is open.
    if (0!=m_cPages)
        {
        PageGet(m_iPageCur, &m_pPageCur, TRUE);
        InvalidateRect(m_hWnd, NULL, FALSE);
        }
    else
        InvalidateRect(m_hWnd, NULL, TRUE);

    UpdateWindow(m_hWnd);
    return m_iPageCur;
    }

/*
 * CPages::CurPageGet
 *
 * Purpose:
 *  Retrieves the index of the current page we're viewing.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the current page.
 */

UINT CPages::CurPageGet(void)
    {
    return m_iPageCur;
    }

/*
 * CPages::CurPageSet
 *
 * Purpose:
 *  Sets the index of the current page to view.
 *
 * Parameters:
 *  iPage           UINT index of the page to view. 0 means first
 *                  page, NOVALUE means last page.
 *
 * Return Value:
 *  UINT            Index of the previous current page, NOVALUE on
 *                  error.
 */

UINT CPages::CurPageSet(UINT iPage)
    {
    UINT    iPageNew;
    UINT    iPagePrev=m_iPageCur;
    PCPage  pPage;

    switch (iPage)
        {
        case 0:
            iPageNew=0;
            break;

        case NOVALUE:
            iPageNew=m_cPages-1;
            break;

        default:
            if (iPage >= m_cPages)
                iPage=0;

            iPageNew=iPage;
            break;
        }

    //No reason to switch to the same page.
    if (iPagePrev==iPageNew)
        return iPage;

    //Close the old page committing changes.
    if (!PageGet(iPagePrev, &pPage, FALSE))
        return NOVALUE;

    pPage->Close(TRUE);

    m_iPageCur=iPageNew;

    //Open the new page.
    PageGet(m_iPageCur, &m_pPageCur, TRUE);

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);
    return iPagePrev;
    }

/*
 * CPages::NumPagesGet
 *
 * Purpose:
 *  Returns the number of pages this object current contains.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of pages we contain.
 */

UINT CPages::NumPagesGet(void)
    {
    return m_cPages;
    }

/*
 * CPages::TenantCreate
 * CPages::TenantDestroy
 *
 * Purpose:
 *  Pass-throughs for CPage members on the current page.
 */

BOOL CPages::TenantCreate(TENANTTYPE tType, LPVOID pv
    , LPFORMATETC pFE, PPATRONOBJECT ppo, DWORD dwData)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantCreate(tType, pv, pFE, ppo, dwData);
    m_fDirty |= fRet;
    return fRet;
    }

BOOL CPages::TenantDestroy(void)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantDestroy();
    m_fDirty |= fRet;
    return fRet;
    }

/*
 * CPages::TenantClip
 *
 * Purpose:
 *  Copies or cuts the currently selected tenant to the clipoard.
 *
 * Parameters:
 *  fCut            BOOL TRUE to cut the object, FALSE to copy.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::TenantClip(BOOL fCut)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantClip(fCut);
    m_fDirty |= (fRet && fCut);
    return fRet;
    }

/*
 * CPages::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected on this
 *  page for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU on which object related items live.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPages::FQueryObjectSelected(HMENU hMenu)
    {
    if (NULL==m_pPageCur)
        return FALSE;

    return m_pPageCur->FQueryObjectSelected(hMenu);
    }

//CHAPTER17MOD
/*
 * CPages::ActivateObject
 *
 * Purpose:
 *  Executes a verb on the currently selected object.
 *
 * Parameters:
 *  iVerb           LONG of the selected verb.
 *
 * Return Value:
 *  None
 */

void CPages::ActivateObject(LONG iVerb)
    {
    if (NULL==m_pPageCur)
        return;

    m_pPageCur->ActivateObject(iVerb);
    return;
    }

/*
 * CPages::NotifyTenantsOfRename
 *
 * Purpose:
 *  Pass-through to CPage to tell tenants that the document
 *  changes names.
 *
 * Parameters:
 *  pszFile         LPTSTR of the new filename.
 *  pvReserved      LPVOID reserved for future use.
 *
 * Return Value:
 *  None
 */

void CPages::NotifyTenantsOfRename(LPTSTR pszFile, LPVOID pvReserved)
    {
    if (NULL==m_pPageCur)
        return;

    m_pPageCur->NotifyTenantsOfRename(pszFile, pvReserved);
    return;
    }

/*
 * CPages::ConvertObject
 *
 * Purpose:
 *  Pass-through to the current page.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame window.
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful, FALSE
 *                  otherwise.
 */

BOOL CPages::ConvertObject(HWND hWndFrame)
    {
    if (NULL==m_pPageCur)
        return FALSE;

    return m_pPageCur->ConvertObject(hWndFrame, FALSE);
    }

//End CHAPTER17MOD

/*
 * CPages::CalcBoundingRect
 * (Protected)
 *
 * Purpose:
 *  Calculates a rectangle that bounds the printed page and the
 *  current scroll state of the window.
 *
 * Parameters:
 *  prc             LPRECT to fill with window (device) coordinates.
 *  fWindow         BOOL indicating to include the window in this
 *                  calculation or return only the printed page
 *                  coordinates.
 *
 * Return Value:
 *  None
 */

void CPages::CalcBoundingRect(LPRECT prc, BOOL fWindow)
    {
    RECT        rc, rcT;

    if (NULL==prc)
        return;

    //Calculate the boundaries for sizing: intersect page & screen
    rc.left=LOMETRIC_BORDER+m_xMarginLeft;
    rc.top =-LOMETRIC_BORDER-m_yMarginTop;
    rc.right =rc.left+(UINT)m_cx;
    rc.bottom=rc.top -(UINT)m_cy;
    RectConvertMappings(&rc, NULL, TRUE);
    OffsetRect(&rc, -(int)m_xPos, -(int)m_yPos);

    if (!fWindow)
        {
        *prc=rc;
        return;
        }

    //Intersect with window to make the size bounds.
    GetClientRect(m_hWnd, &rcT);
    IntersectRect(prc, &rc, &rcT);
    return;
    }

/*
 * CPages::PageGet
 * (Protected)
 *
 * Purpose:
 *  Returns a page of a given index returning a BOOL so it's simple
 *  to use this function inside if statements.
 *
 * Parameters:
 *  iPage           UINT page to retrieve 0 based.
 *  ppPage          PCPage * in which to return the page pointer
 *  fOpen           BOOL indicating if we should open this page as
 *                  well.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::PageGet(UINT iPage, PCPage *ppPage, BOOL fOpen)
    {
    if (NULL==ppPage)
        return FALSE;

    if (LB_ERR!=SendMessage(m_hWndPageList, LB_GETTEXT
        , iPage, (LONG)ppPage))
        {
        if (fOpen)
            (*ppPage)->Open(m_pIStorage);

        return TRUE;
        }

    return FALSE;
    }

/*
 * CPages::PageAdd
 * (Protected)
 *
 * Purpose:
 *  Creates a new page initialized to the given values.  The new
 *  page's storage is created if it does not already exist.  If
 *  fOpenStorage is set the page's storage is opened and left
 *  opened.
 *
 * Parameters:
 *  iPage           UINT Location at which to insert page; new page
 *                  is inserted after this position.  NOVALUE for
 *                  the end.
 *  dwID            DWORD ID for this page.
 *  fOpenStorage    BOOL indicating if we're to leave the storage
 *                  open.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPages::PageAdd(UINT iPage, DWORD dwID, BOOL fOpenStorage)
    {
    PCPage      pPage;
    LRESULT     lr;

    pPage=new CPage(dwID, m_hWnd, this);

    if (NULL==pPage)
        return FALSE;

    if (fOpenStorage)
        pPage->Open(m_pIStorage);

    if (NOVALUE==iPage)
        iPage--;

    //Now try to add to the listbox.
    lr=SendMessage(m_hWndPageList, LB_INSERTSTRING, iPage+1
        , (LONG)pPage);

    if (LB_ERRSPACE==lr)
        {
        if (fOpenStorage)
            pPage->Close(FALSE);

        delete pPage;
        return FALSE;
        }

    return TRUE;
    }


PAGES.H

/*
 * PAGES.H
 * Patron Chapter 17
 *
 * Definitions and function prototypes for the Pages window control
 * as well as the CPage class.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _PAGES_H_
#define _PAGES_H_

//Versioning.
#define VERSIONMAJOR                2
#define VERSIONMINOR                0
#define VERSIONCURRENT              0x00020000

//Classname
#define SZCLASSPAGES                TEXT("pages")

#define HIMETRIC_PER_INCH           2540
#define LOMETRIC_PER_INCH           254
#define LOMETRIC_BORDER             60          //Border around page

//Window extra bytes and offsets
#define CBPAGESWNDEXTRA             (sizeof(LONG))
#define PAGEWL_STRUCTURE            0

//CHAPTER17MOD
//Time to break this stuff out into another file.
#include "tenant.h"
//End CHAPTER17MOD

typedef struct tagTENANTLIST
    {
    DWORD       cTenants;
    DWORD       dwIDNext;
    } TENANTLIST, *PTENANTLIST;

#define SZSTREAMTENANTLIST        OLETEXT("Tenant List")

//Delay timer used in mouse debouncing
#define IDTIMER_DEBOUNCE          120

/*
 * Page class describing an individual page and what things it
 * contains, managing an IStorage for us.
 *
 * A DWORD is used to identify this page as the name of the storage
 * is the string form of this ID.  If we added a page every second,
 * it would take 136 years to overrun this counter, so we can
 * get away with saving it persistently.  I hope this software is
 * obsolete by then.
 */

class CPage
    {
    private:
        DWORD       m_dwID;             //Persistent identifier
        LPSTORAGE   m_pIStorage;        //Substorage for this page
        HWND        m_hWnd;             //Pages window
        DWORD       m_cOpens;           //Calls to Open

        class CPages *m_pPG;            //Pages window

        DWORD       m_dwIDNext;
        DWORD       m_cTenants;
        HWND        m_hWndTenantList;   //Listbox; our tenant list

        UINT        m_iTenantCur;
        PCTenant    m_pTenantCur;

        UINT        m_uHTCode;          //Last hit-test/mouse move
        UINT        m_uSizingFlags;     //Restrictions on sizing
        BOOL        m_fTracking;        //Tracking resize?
        RECTL       m_rclOrg;           //Original before tracking
        RECTL       m_rcl;              //Tracking rectangle
        RECTL       m_rclBounds;        //Boundaries f/size tracking
        HDC         m_hDC;              //Tracking hDC

        BOOL        m_fDragPending;     //Waiting for drag?
        BOOL        m_fSizePending;     //Waiting for debounce?
        int         m_cxyDist;          //Debounce distance
        UINT        m_cDelay;           //Debounce delay
        POINTS      m_ptDown;           //Point of click to debounce
        UINT        m_uKeysDown;        //Keys when click happens
        DWORD       m_fTimer;           //Timer active?

    protected:
        BOOL         TenantGet(UINT, PCTenant *, BOOL);
        BOOL         TenantAdd(UINT, DWORD, PCTenant *);
        LPDATAOBJECT TransferObjectCreate(PPOINTL);

        //PAGEMOUS.CPP
        //CHAPTER17MOD
        BOOL         SelectTenantAtPoint(UINT, UINT);
        //End CHAPTER17MOD

        UINT         TenantFromPoint(UINT, UINT, PCTenant *);
        BOOL         DragDrop(UINT, UINT, UINT);

    public:
        CPage(DWORD, HWND, class CPages *);
        ~CPage(void);

        DWORD       GetID(void);
        BOOL        Open(LPSTORAGE);
        void        Close(BOOL);
        BOOL        Update(void);
        void        Destroy(LPSTORAGE);
        UINT        GetStorageName(LPOLESTR);

        void        Draw(HDC, int, int, BOOL, BOOL);

        BOOL        TenantCreate(TENANTTYPE, LPVOID, LPFORMATETC
                        , PPATRONOBJECT, DWORD);
        BOOL        TenantDestroy(void);
        BOOL        TenantClip(BOOL);
        BOOL        FQueryObjectSelected(HMENU);
        //CHAPTER17MOD
        void        ActivateObject(LONG);
        void        NotifyTenantsOfRename(LPTSTR, LPVOID);
        BOOL        ConvertObject(HWND, BOOL);

        //PAGEMOUSE.CPP
        BOOL        OnRightDown(UINT, UINT, UINT);
        //End CHAPTER17MOD
        BOOL        OnLeftDown(UINT, UINT, UINT);
        BOOL        OnLeftDoubleClick(UINT, UINT, UINT);
        BOOL        OnLeftUp(UINT, UINT, UINT);
        void        OnMouseMove(UINT, int, int);
        void        OnTimer(UINT);
        void        StartSizeTracking(void);
        void        OnNCHitTest(UINT, UINT);
        BOOL        OnSetCursor(UINT);
    };

typedef CPage *PCPage;

/*
 * Structures to save with the document describing the device
 * configuration and pages that we have.  This is followed by
 * a list of DWORD IDs for the individual pages.
 */

typedef struct tagDEVICECONFIG
    {
    DWORD       cb;                         //Size of structure
    TCHAR       szDriver[CCHDEVICENAME];
    TCHAR       szDevice[CCHDEVICENAME];
    TCHAR       szPort[CCHDEVICENAME];
    DWORD       cbDevMode;                  //Size of actual DEVMODE
    DEVMODE     dm;                         //Variable
    } DEVICECONFIG, *PDEVICECONFIG;

//Offset to cbDevMode
#define CBSEEKOFFSETCBDEVMODE  (sizeof(DWORD)   \
                               +(3*CCHDEVICENAME*sizeof(TCHAR)))

//Combined OLE and Patron device structures.
typedef struct tagCOMBINEDEVICE
    {
    DVTARGETDEVICE  td;
    DEVICECONFIG    dc;
    } COMBINEBDEVICE, *PCOMBINEDEVICE;

typedef struct tagPAGELIST
    {
    DWORD       cPages;
    DWORD       iPageCur;
    DWORD       dwIDNext;
    } PAGELIST, *PPAGELIST;

//PRINT.CPP
BOOL    APIENTRY PrintDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL    APIENTRY AbortProc(HDC, int);

//PAGEWIN.CPP
LRESULT APIENTRY PagesWndProc(HWND, UINT, WPARAM, LPARAM);
void             RectConvertMappings(LPRECT, HDC, BOOL);

class CPages : public CWindow
    {
    friend LRESULT APIENTRY PagesWndProc(HWND, UINT, WPARAM, LPARAM);
    friend BOOL    APIENTRY PrintDlgProc(HWND, UINT, WPARAM, LPARAM);

    friend class CPage;
    friend class CTenant;
    friend class CDropTarget;

    //CHAPTER17MOD
    friend class CImpIAdviseSink;
    //End CHAPTER17MOD

    protected:
        PCPage      m_pPageCur;             //Current page
        UINT        m_iPageCur;             //Current page
        UINT        m_cPages;               //Number of pages

        HWND        m_hWndPageList;         //Listbox with page list
        HFONT       m_hFont;                //Page font
        BOOL        m_fSystemFont;          //m_hFont system object?

        UINT        m_cx;                   //Page size in LOMETRIC
        UINT        m_cy;

        UINT        m_xMarginLeft;          //Unusable margins,
        UINT        m_xMarginRight;         //in LOMETRIC
        UINT        m_yMarginTop;
        UINT        m_yMarginBottom;

        UINT        m_xPos;                 //Viewport scroll pos,
        UINT        m_yPos;                 //both in *PIXELS*

        DWORD       m_dwIDNext;             //Next ID for a page.
        LPSTORAGE   m_pIStorage;            //Root storage

        UINT        m_cf;                   //Clipboard format
        BOOL        m_fDirty;

        BOOL        m_fDragSource;          //Source==target?
        BOOL        m_fMoveInPage;          //Moving in same page
        POINTL      m_ptDrop;               //Where to move object

        BOOL        m_fDragRectShown;       //Is rect on the screen?
        UINT        m_uScrollInset;         //Hit-test for drag-drop
        UINT        m_uScrollDelay;         //Delay before repeat
        DWORD       m_dwTimeLast;           //Ticks on last DragOver
        UINT        m_uHScrollCode;         //L/R on scroll repeat?
        UINT        m_uVScrollCode;         //U/D on scroll repeat?
        UINT        m_uLastTest;            //Last test result
        POINTL      m_ptlRect;              //Last feedback rectangle
        SIZEL       m_szlRect;

    private:
        void        Draw(HDC, BOOL, BOOL);
        void        UpdateScrollRanges(void);
        BOOL        ConfigureForDevice(void);
        BOOL        PageGet(UINT, PCPage *, BOOL);
        BOOL        PageAdd(UINT, DWORD, BOOL);

        void        CalcBoundingRect(LPRECT, BOOL);

        //DRAGDROP.CPP
        UINT        UTestDroppablePoint(PPOINTL);
        void        DrawDropTargetRect(PPOINTL, LPSIZEL);
        void        AdjustPosition(PPOINTL, LPSIZEL);

    public:
        CPages(HINSTANCE, UINT);
        ~CPages(void);

        BOOL        Init(HWND, LPRECT, DWORD, UINT, LPVOID);

        BOOL        StorageSet(LPSTORAGE, BOOL, BOOL);
        BOOL        StorageUpdate(BOOL);

        BOOL        Print(HDC, LPTSTR, DWORD, UINT, UINT, UINT);

        void        RectGet(LPRECT);
        void        RectSet(LPRECT, BOOL);
        void        SizeGet(LPRECT);
        void        SizeSet(LPRECT, BOOL);

        PCPage      ActivePage(void);
        UINT        PageInsert(UINT);
        UINT        PageDelete(UINT);
        UINT        CurPageGet(void);
        UINT        CurPageSet(UINT);
        UINT        NumPagesGet(void);

        BOOL        DevModeSet(HGLOBAL, HGLOBAL);
        HGLOBAL     DevModeGet(void);

        BOOL        FIsDirty(void);
        BOOL        DevReadConfig(PCOMBINEDEVICE *, HDC *);
        BOOL        TenantCreate(TENANTTYPE, LPVOID, LPFORMATETC
                        , PPATRONOBJECT, DWORD);
        BOOL        TenantDestroy(void);
        BOOL        TenantClip(BOOL);
        BOOL        FQueryObjectSelected(HMENU);
        //CHAPTER17MOD
        void        ActivateObject(LONG);
        void        NotifyTenantsOfRename(LPTSTR, LPVOID);
        BOOL        ConvertObject(HWND);
        //End CHAPTER17MOD
    };

typedef CPages *PCPages;

//Fixed names of streams in the Pages IStorage
#define SZSTREAMPAGELIST        OLETEXT("Page List")
#define SZSTREAMDEVICECONFIG    OLETEXT("Device Configuration")

//Return values for UTestDroppablePoint
#define UDROP_NONE              0x0000      //Exclusive
#define UDROP_CLIENT            0x0001      //Inclusive
#define UDROP_INSETLEFT         0x0002      //L/R are exclusive
#define UDROP_INSETRIGHT        0x0004
#define UDROP_INSETHORZ         (UDROP_INSETLEFT | UDROP_INSETRIGHT)

#define UDROP_INSETTOP          0x0008      //T/B are exclusive
#define UDROP_INSETBOTTOM       0x0010
#define UDROP_INSETVERT         (UDROP_INSETTOP | UDROP_INSETBOTTOM)

#endif  //_PAGES_H_


PAGEWIN.CPP

/*
 * PAGEWIN.CPP
 * Patron Chapter 17
 *
 * Window procedure for the Pages window and support functions.
 * This window manages its own scrollbars and viewport and provides
 * printing capabilities as well.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * PagesWndProc
 *
 * Purpose:
 *  Window procedure for the Pages window.
 */

LRESULT APIENTRY PagesWndProc(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    PCPages         ppg;
    PAINTSTRUCT     ps;
    HDC             hDC;
    int             iPos, iTmp;
    int             iMin, iMax;
    UINT            idScroll;
    BOOL            fDirty=FALSE;

    ppg=(PCPages)GetWindowLong(hWnd, PAGEWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppg=(PCPages)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PAGEWL_STRUCTURE, (LONG)ppg);

            ppg->m_hWnd=hWnd;
            break;

        case WM_PAINT:
            /*
             * If there is currently a drag-rectangle showing, then
             * remove it before painting.  This insures that
             * painting doesn't blast part of that rectangle away so
             * that when we draw it next, garbage is left around.
             */
            if (ppg->m_fDragRectShown)
                ppg->DrawDropTargetRect(NULL, NULL);

            hDC=BeginPaint(hWnd, &ps);

            //Draw only if we have a page to show.
            if (0!=ppg->m_cPages)
                ppg->Draw(hDC, FALSE, FALSE);

            EndPaint(hWnd, &ps);

            //Turn the rectangle back on, if necessary.
            if (ppg->m_fDragRectShown)
                ppg->DrawDropTargetRect(NULL, NULL);
            break;

        case WM_HSCROLL:
        case WM_VSCROLL:
            idScroll=(WM_HSCROLL==iMsg) ? SB_HORZ : SB_VERT;

            iPos=GetScrollPos(hWnd, idScroll);
            iTmp=iPos;
            GetScrollRange(hWnd, idScroll, &iMin, &iMax);

            switch (LOWORD(wParam))
                {
                case SB_LINEUP:     iPos -= 20;  break;
                case SB_PAGEUP:     iPos -=100;  break;
                case SB_LINEDOWN:   iPos += 20;  break;
                case SB_PAGEDOWN:   iPos +=100;  break;

                case SB_THUMBPOSITION:
                    iPos=ScrollThumbPosition(wParam, lParam);
                    break;

                //We don't want scrolling on this message.
                case SB_THUMBTRACK:
                    return 0L;
                }

            iPos=max(iMin, min(iPos, iMax));

            if (iPos!=iTmp)
                {
                //Set the new position and scroll the window
                SetScrollPos(hWnd, idScroll, iPos, TRUE);

                if (SB_HORZ==idScroll)
                    {
                    ppg->m_xPos=iPos;
                    ScrollWindow(hWnd, iTmp-iPos, 0, NULL, NULL);
                    }
                else
                    {
                    ppg->m_yPos=iPos;
                    ScrollWindow(hWnd, 0, iTmp-iPos, NULL, NULL);
                    }
                }

            break;

        //CHAPTER17MOD
        case WM_RBUTTONDOWN:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnRightDown(wParam
                , LOWORD(lParam), HIWORD(lParam));
            break;
        //End CHAPTER17MOD

        case WM_LBUTTONDOWN:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftDown(wParam
                , LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_LBUTTONUP:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftUp(wParam
                , LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_LBUTTONDBLCLK:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftDoubleClick(wParam, LOWORD(lParam)
                , HIWORD(lParam));
            break;

        case WM_MOUSEMOVE:
            if (NULL==ppg->m_pPageCur)
                break;

            ppg->m_pPageCur->OnMouseMove(wParam, LOWORD(lParam)
                , HIWORD(lParam));
            break;

        case WM_TIMER:
            if (NULL==ppg->m_pPageCur)
                break;

            ppg->m_pPageCur->OnTimer(wParam);
            break;

        case WM_NCHITTEST:
            if (NULL!=ppg->m_pPageCur)
                {
                /*
                 * This just saves information in the page for
                 * OnSetCursor
                 */
                ppg->m_pPageCur->OnNCHitTest(LOWORD(lParam)
                    , HIWORD(lParam));
                }

            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        case WM_SETCURSOR:
            if (NULL!=ppg->m_pPageCur)
                {
                if (ppg->m_pPageCur->OnSetCursor(LOWORD(lParam)))
                    break;
                }

            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    ppg->m_fDirty |= fDirty;
    return 0L;
    }

/*
 * RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device to logical
 *  coordinates where the hDC defines the logical coordinates.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  hDC             HDC describing the logical coordinate system.
 *                  if NULL, uses a screen DC in MM_LOMETRIC.
 *  fToDevice       BOOL TRUE to convert from uConv to device,
 *                  FALSE to convert device to uConv.
 *
 * Return Value:
 *  None
 */

void RectConvertMappings(LPRECT pRect, HDC hDC, BOOL fToDevice)
    {
    POINT   rgpt[2];
    BOOL    fSysDC=FALSE;

    if (NULL==pRect)
        return;

    rgpt[0].x=pRect->left;
    rgpt[0].y=pRect->top;
    rgpt[1].x=pRect->right;
    rgpt[1].y=pRect->bottom;

    if (NULL==hDC)
        {
        hDC=GetDC(NULL);
        SetMapMode(hDC, MM_LOMETRIC);
        fSysDC=TRUE;
        }

    if (fToDevice)
        LPtoDP(hDC, rgpt, 2);
    else
        DPtoLP(hDC, rgpt, 2);

    if (fSysDC)
        ReleaseDC(NULL, hDC);

    pRect->left=rgpt[0].x;
    pRect->top=rgpt[0].y;
    pRect->right=rgpt[1].x;
    pRect->bottom=rgpt[1].y;

    return;
    }

/*
 * CPages::Draw
 *
 * Purpose:
 *  Paints the current page in the pages window.
 *
 * Parameters:
 *  hDC             HDC to draw on, could be a metafile or printer
 *                  DC or any other type of DC.
 *  fNoColor        BOOL indicating if we should use screen colors
 *                  or printer colors (B&W).  Objects are printed
 *                  as-is, however.  This is TRUE for printer DCs
 *                  or print preview.
 *  fPrinter        BOOL indicating if this is a printer DC in which
 *                  case we eliminate some of the fancy drawing,
 *                  like shadows on the page and so forth.
 *
 * Return Value:
 *  None
 */

void CPages::Draw(HDC hDC, BOOL fNoColor, BOOL fPrinter)
    {
    RECT            rc, rcT;
    UINT            uMM;
    HPEN            hPen;
    HBRUSH          hBrush;
    HGDIOBJ         hObj1, hObj2;
    COLORREF        cr;
    TCHAR           szTemp[20];
    UINT            cch;
    SIZE            sz;
    PCPage          pPage;
    RECT            rcPos;

    //Make sure the DC is in LOMETRIC
    uMM=SetMapMode(hDC, MM_LOMETRIC);

    if (!fPrinter)
        {
        /*
         * We maintain a 6mm border around the page on the screen
         * besides 12.7mm margins.  We also have to account for
         * the scroll position with m_*Pos which are in pixels so
         * we have to convert them.
         */

        SetRect(&rcPos, m_xPos, m_yPos, 0, 0);
        RectConvertMappings(&rcPos, hDC, FALSE);

        rc.left  = LOMETRIC_BORDER-rcPos.left;
        rc.top   =-LOMETRIC_BORDER-rcPos.top;
        }
    else
        {
        /*
         * We define the corner of the printed paper at a negative
         * offset so rc.right and rc.bottom come out right below.
         */
        SetRect(&rc, -(int)m_xMarginLeft, m_yMarginTop, 0, 0);
        }

    rc.right=rc.left+m_cx+(m_xMarginLeft+m_xMarginRight);
    rc.bottom=rc.top-m_cy-(m_yMarginTop+m_yMarginBottom);

    //Draw a rect filled with the window color to show the page.
    if (!fPrinter)
        {
        if (fNoColor)
            {
            //Black frame, white box for printed colors.
            hPen  =CreatePen(PS_SOLID, 0, RGB(0,0,0));
            hBrush=CreateSolidBrush(RGB(255, 255, 255));
            }
        else
            {
            //Normal colors on display
            hPen=CreatePen(PS_SOLID, 0
                , GetSysColor(COLOR_WINDOWFRAME));
            hBrush=CreateSolidBrush(GetSysColor(COLOR_WINDOW));
            }

        hObj1=SelectObject(hDC, hPen);
        hObj2=SelectObject(hDC, hBrush);

        //Paper boundary
        Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom+1);

        /*
         * Draw a shadow on the *visual* bottom and right edges
         * .5mm wide.  If the button shadow color and workspace
         * colors match, then use black.  We always use black
         * when printing as well.
         */
        if (fNoColor)
            cr=RGB(0,0,0);
        else
            {
            cr=GetSysColor(COLOR_BTNSHADOW);

            if (GetSysColor(COLOR_APPWORKSPACE)==cr)
                cr=RGB(0,0,0);
            }

        cr=SetBkColor(hDC, cr);
        SetRect(&rcT, rc.left+5, rc.bottom, rc.right+5,rc.bottom-5);
        ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rcT, NULL, 0, NULL);

        SetRect(&rcT, rc.right, rc.top-5, rc.right+5, rc.bottom-5);
        ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rcT, NULL, 0, NULL);
        SetBkColor(hDC, cr);

        SelectObject(hDC, hObj1);
        SelectObject(hDC, hObj2);
        DeleteObject(hBrush);
        DeleteObject(hPen);
        }

    //Write the page number in the lower left corner
    if (!fNoColor)
        {
        SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));
        SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
        }

    //Write the page number in our page font.
    cch=wsprintf(szTemp, TEXT("Page %d"), m_iPageCur+1);

    hObj1=SelectObject(hDC, m_hFont);
    GetTextExtentPoint(hDC, szTemp, cch, &sz);

    TextOut(hDC, rc.left+m_xMarginLeft
        , rc.bottom+m_yMarginBottom+sz.cy, szTemp, cch);

    SelectObject(hDC, hObj1);

    //Rectangle to show border.
    MoveToEx(hDC, rc.left+m_xMarginLeft, rc.top-m_yMarginTop, NULL);
    LineTo(hDC, rc.left+m_xMarginLeft,   rc.bottom+m_yMarginBottom);
    LineTo(hDC, rc.right-m_xMarginRight, rc.bottom+m_yMarginBottom);
    LineTo(hDC, rc.right-m_xMarginRight, rc.top-m_yMarginTop);
    LineTo(hDC, rc.left+m_xMarginLeft,   rc.top-m_yMarginTop);

    /*
     * Go draw the objects on this page.  If the page is not open,
     * we open it anyway.  If it is already open, then opening again
     * will bump it's reference count, so the Close in ineffectual.
     */
    if (PageGet(m_iPageCur, &pPage, TRUE))
        {
        if (!fPrinter)
            {
            pPage->Draw(hDC, rcPos.left, rcPos.top, fNoColor
                , fPrinter);
            }
        else
            pPage->Draw(hDC, 0, 0, fNoColor, fPrinter);

        pPage->Close(FALSE);
        }

    SetMapMode(hDC, uMM);
    return;
    }

/*
 * CPages::UpdateScrollRanges
 *
 * Purpose:
 *  Reset scrollbar ranges (horizontal and vertical) depending on
 *  the window size and the page size.  This function may remove
 *  the scrollbars altogether.
 *
 * Parameters:
 *  None, but set m_cx, m_cy and size m_hWnd before calling.
 *
 * Return Value:
 *  None
 */

void CPages::UpdateScrollRanges(void)
    {
    UINT        cxSB;   //Scrollbar width and height.
    UINT        cySB;
    UINT        cx, cy;
    UINT        dx, dy;
    UINT        u;
    int         iMin, iMax;
    RECT        rc;
    BOOL        fHScroll;
    BOOL        fVScroll;
    BOOL        fWasThere;

    GetClientRect(m_hWnd, &rc);

    cx=rc.right-rc.left;
    cy=rc.bottom-rc.top;

    //Convert dimensions of the image in LOMETRIC to pixels.
    SetRect(&rc, (m_cx+m_xMarginLeft+m_xMarginRight
        +LOMETRIC_BORDER*2), (m_cy+m_yMarginTop
        +m_yMarginBottom+LOMETRIC_BORDER*2), 0, 0);

    RectConvertMappings(&rc, NULL, TRUE);

    dx=rc.left;
    dy=-rc.top;

    //Assume that both scrollbars will be visible.
    fHScroll=TRUE;
    fVScroll=TRUE;

    /*
     * Determine:
     *  1)  Which scrollbars are needed.
     *  2)  How many divisions to give scrollbars so as to
     *      only scroll as little as necessary.
     */

    //Scrollbar dimensions in our units.
    cxSB=GetSystemMetrics(SM_CXVSCROLL);
    cySB=GetSystemMetrics(SM_CYHSCROLL);

    //Remove horizontal scroll if window >= cxPage+borders
    if (cx >= dx)
        fHScroll=FALSE;

    /*
     * If we still need a horizontal scroll, see if we need a
     * vertical taking the height of the horizontal scroll into
     * account.
     */

    u=fHScroll ? cySB : 0;

    if ((cy-u) >= dy)
        fVScroll=FALSE;

    //Check if adding vert scrollbar necessitates a horz now.
    u=fVScroll ? cxSB : 0;
    fHScroll=((cx-u) < dx);

    /*
     * Modify cx,cy to reflect the new client area before scaling
     * scrollbars.  We only affect the client size if there is a
     * *change* in scrollbar status:  if the scrollbar was there
     * but is no longer, then add to the client size; if it was
     * not there but now is, then subtract.
     */

    //Change cx depending on vertical scrollbar change
    GetScrollRange(m_hWnd, SB_VERT, &iMin, &iMax);
    fWasThere=(0!=iMin || 0!=iMax);

    if (fWasThere && !fVScroll)
        cx+=cxSB;

    if (!fWasThere && fVScroll)
        cx-=cxSB;

    //Change cy depending on horizontal scrollbar change
    GetScrollRange(m_hWnd, SB_HORZ, &iMin, &iMax);
    fWasThere=(0!=iMin || 0!=iMax);

    if (fWasThere && !fHScroll)
        cy+=cySB;

    if (!fWasThere && fHScroll)
        cy-=cySB;

    /*
     * Show/Hide the scrollbars if necessary and set the ranges.
     * The range is the number of units of the page we cannot see.
     */
    if (fHScroll)
        {
        //Convert current scroll position to new range.
        u=GetScrollPos(m_hWnd, SB_HORZ);

        if (0!=u)
            {
            GetScrollRange(m_hWnd, SB_HORZ, &iMin, &iMax);
            u=MulDiv(u, (dx-cx), (iMax-iMin));
            }

        SetScrollRange(m_hWnd, SB_HORZ, 0, dx-cx, FALSE);
        SetScrollPos(m_hWnd, SB_HORZ, u, TRUE);
        m_xPos=u;
        }
    else
        {
        SetScrollRange(m_hWnd, SB_HORZ, 0, 0, TRUE);
        m_xPos=0;
        }

    if (fVScroll)
        {
        //Convert current scroll position to new range.
        u=GetScrollPos(m_hWnd, SB_VERT);

        if (0!=u)
            {
            GetScrollRange(m_hWnd, SB_VERT, &iMin, &iMax);
            u=MulDiv(u, (dy-cy), (iMax-iMin));
            }

        SetScrollRange(m_hWnd, SB_VERT, 0, dy-cy, FALSE);
        SetScrollPos(m_hWnd, SB_VERT, u, TRUE);

        m_yPos=u;
        }
    else
        {
        SetScrollRange(m_hWnd, SB_VERT, 0, 0, TRUE);
        m_yPos=0;
        }

    //Repaint to insure that changes to m_x/yPos are reflected
    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }


PATRON.CPP

/*
 * PATRON.CPP
 * Patron Chapter 17
 *
 * WinMain which is all we need for the basic application.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "patron.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCPatronFrame   pFR;
    FRAMEINIT       fi;
    WPARAM          wRet=0;

    SETMESSAGEQUEUE;

    //Attempt to allocate and initialize the application
    pFR=new CPatronFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return -1;

    fi.idsMin=IDS_FRAMEMIN;
    fi.idsMax=IDS_FRAMEMAX;
    fi.idsStatMin=IDS_STATMESSAGEMIN;
    fi.idsStatMax=IDS_STATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=CW_USEDEFAULT;
    fi.cy=CW_USEDEFAULT;

    //If we can initialize pFR, start chugging messages
    if (pFR->Init(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

/*
 * CPatronFrame::CPatronFrame
 * CPatronFrame::~CPatronFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WInMain
 */

CPatronFrame::CPatronFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    : CFrame(hInst, hInstPrev, pszCmdLine, nCmdShow)
    {
    m_fInitialized=FALSE;
    m_pIClassDataTran=NULL;
    return;
    }

CPatronFrame::~CPatronFrame(void)
    {
    if (NULL!=m_pIClassDataTran)
        {
        m_pIClassDataTran->LockServer(FALSE);
        m_pIClassDataTran->Release();
        }

    OleFlushClipboard();

    if (m_fInitialized)
        OleUninitialize();
    return;
    }

/*
 * CPatronFrame::Init
 *
 * Purpose:
 *  Call OleInitialize then calling down into the base class
 *  initialization.
 *
 * Parameters:
 *  pFI             PFRAMEINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CPatronFrame::Init(PFRAMEINIT pFI)
    {
    HRESULT     hr;

    CHECKVER_OLE;

    if (FAILED(OleInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    hr=CoGetClassObject(CLSID_DataTransferObject
        , CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory
        , (PPVOID)&m_pIClassDataTran);

    if (SUCCEEDED(hr))
        m_pIClassDataTran->LockServer(TRUE);

    return CFrame::Init(pFI);
    }

/*
 * CPatronFrame::CreateCClient
 *
 * Purpose:
 *  Constructs a new client specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient        Pointer to the new client object.
 */

PCClient CPatronFrame::CreateCClient(void)
    {
    return (PCClient)(new CPatronClient(m_hInst, this));
    }

/*
 * CPatronFrame::RegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CPatronFrame::RegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //First let the standard frame do its thing
    if (!CFrame::RegisterAllClasses())
        return FALSE;

    //We need double-clicks now and for object activation.
    wc.style         = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
    wc.hInstance     = m_hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PagesWndProc;
    wc.cbWndExtra    = CBPAGESWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPAGES;

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

    return TRUE;
    }

/*
 * CPatronFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the Patron frame window that processes
 *  extra File menu items as well as the Page menu.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CPatronFrame::OnCommand(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PCPatronDoc     pDoc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many toolbar notifications.
     */
    if (m_fInit)
        return 0L;

    pDoc=(PCPatronDoc)m_pCL->ActiveDocument();

    //CHAPTER17MOD
    if (NULL!=pDoc && (IDM_VERBMIN <= wID) && (IDM_VERBMAX >= wID))
        {
        pDoc->ActivateObject(wID-IDM_VERBMIN);
        return 0L;
        }
    //End CHAPTER17MOD

    switch (wID)
        {
        case IDM_FILEPRINT:
            pDoc->Print(m_hWnd);
            return 0L;

        case IDM_FILEPRINTERSETUP:
            pDoc->PrinterSetup(m_hWnd, FALSE);
            return 0L;

        case IDM_EDITPASTESPECIAL:
            pDoc->PasteSpecial(m_hWnd);
            return 0L;

        case IDM_EDITDELETEOBJECT:
            pDoc->Delete();
            return 0L;

        //CHAPTER17MOD
        case IDM_EDITINSERTOBJECT:
            pDoc->InsertObject(m_hWnd);
            return 0L;

        case IDM_EDITCONVERT:
            pDoc->ConvertObject(m_hWnd);
            return 0L;
        //End CHAPTER17MOD

        case IDM_PAGENEWPAGE:
            pDoc->NewPage();
            break;

        case IDM_PAGEDELETEPAGE:
            pDoc->DeletePage();
            break;

        case IDM_PAGENEXTPAGE:
            pDoc->NextPage();
            break;

        case IDM_PAGEPREVIOUSPAGE:
            pDoc->PreviousPage();
            break;

        case IDM_PAGEFIRSTPAGE:
            pDoc->FirstPage();
            break;

        case IDM_PAGELASTPAGE:
            pDoc->LastPage();
            break;

        default:
           return CFrame::OnCommand(hWnd, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPatronFrame::CreateToolbar
 *
 * Purpose:
 *  Procedure to create all the necessary toolbar buttons.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of tools added to the bar.
 */

UINT CPatronFrame::CreateToolbar(void)
    {
    UINT            iLast;
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;

    //Insert the standard ones.
    iLast=CFrame::CreateToolbar();

    //Remove Undo:  we don't use it.
    m_pTB->Remove(IDM_EDITUNDO);

    /*
     * Insert Print File Import in the 5th position and account
     * for it in iLast.
     */
    m_pTB->Add(utCmd, 4, IDM_FILEPRINT, m_dxB, m_dyB
        , NULL, NULL, 6, uState);

    iLast++;

    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //Add New Page, and Delete Page
    m_pTB->Add(utCmd, iLast++, IDM_PAGENEWPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 2, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGEDELETEPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 3, uState);

    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //First, Prev, Next, Last pages.
    m_pTB->Add(utCmd, iLast++, IDM_PAGEFIRSTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 4, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGEPREVIOUSPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 5, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGENEXTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 6, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGELASTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 7, uState);

    return iLast;
    }

/*
 * CPatronFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CPatronFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    PCPatronDoc     pDoc;
    BOOL            fOK=FALSE;
    BOOL            fCallDefault=TRUE;
    UINT            uTemp;
    UINT            uTempE;
    UINT            uTempD;

    pDoc=(PCPatronDoc)m_pCL->ActiveDocument();

    uTempE=MF_ENABLED | MF_BYCOMMAND;
    uTempD=MF_DISABLED | MF_GRAYED | MF_BYCOMMAND;
    uTemp=((NULL!=pDoc) ? uTempE : uTempD);

    if (m_phMenu[0]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_FILEPRINT, uTemp);

        if (NULL!=pDoc)
            fOK=pDoc->FQueryPrinterSetup();

        EnableMenuItem(hMenu, IDM_FILEPRINTERSETUP
            , (fOK) ? uTempE : uTempD);
        }

    if (m_phMenu[1]==hMenu)
        {
        if (NULL!=pDoc)
            fOK=pDoc->FQueryPaste();

        EnableMenuItem(hMenu, IDM_EDITPASTE
            , (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITPASTESPECIAL
            , (fOK) ? uTempE : uTempD);

        //Cut, Copy, Delete depends on there being a selection.
        if (NULL!=pDoc)
            fOK=pDoc->FQueryObjectSelected(hMenu);
        else
            fOK=FALSE;

        EnableMenuItem(hMenu, IDM_EDITCUT, (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITCOPY
            , (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITDELETEOBJECT
            , (fOK) ? uTempE : uTempD);

        //CHAPTER17MOD
        EnableMenuItem(hMenu, IDM_EDITINSERTOBJECT, uTemp);
        //End CHAPTER17MOD

        //We did the whole menu...
        fCallDefault=FALSE;
        }

    //Page menu
    if (m_phMenu[2]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_PAGENEWPAGE,      uTemp);
        EnableMenuItem(hMenu, IDM_PAGEDELETEPAGE,   uTemp);
        EnableMenuItem(hMenu, IDM_PAGENEXTPAGE,     uTemp);
        EnableMenuItem(hMenu, IDM_PAGEPREVIOUSPAGE, uTemp);
        EnableMenuItem(hMenu, IDM_PAGEFIRSTPAGE,    uTemp);
        EnableMenuItem(hMenu, IDM_PAGELASTPAGE,     uTemp);
        }

    if (fCallDefault)
        CFrame::UpdateMenus(hMenu, iMenu);

    return;
    }

/*
 * CPatronFrame::UpdateToolbar
 *
 * Purpose:
 *  Enables and disables tools depending on whether we have
 *  a document or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronFrame::UpdateToolbar(void)
    {
    PCDocument  pDoc;
    BOOL        fEnable;

    //Let the default hack on its tools.
    CFrame::UpdateToolbar();

    pDoc=m_pCL->ActiveDocument();
    fEnable=(NULL!=pDoc);

    //No document, disable just about everything
    m_pTB->Enable(IDM_FILEPRINT,        fEnable);
    m_pTB->Enable(IDM_FILEPRINTERSETUP, fEnable);

    m_pTB->Enable(IDM_PAGENEWPAGE,      fEnable);
    m_pTB->Enable(IDM_PAGEDELETEPAGE,   fEnable);
    m_pTB->Enable(IDM_PAGEFIRSTPAGE,    fEnable);
    m_pTB->Enable(IDM_PAGEPREVIOUSPAGE, fEnable);
    m_pTB->Enable(IDM_PAGENEXTPAGE,     fEnable);
    m_pTB->Enable(IDM_PAGELASTPAGE,     fEnable);

    return;
    }

//CHAPTER17MOD
/*
 * CPatronFrame::FMessageHook
 *
 * Purpose:
 *  Override of CFrame::FMessageHook so we can specifically trap
 *  WM_MENUSELECT messages for the Object verb menu to provide some
 *  meaningful information on the status strip.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return value
 *                  for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CPatronFrame::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    BOOL        fRet=FALSE;

    *pLRes=0;

    MENUSELECTPARAMS(wItem, wMenuFlags, hMenu);

    //If this is the wrong message, nothing to do.
    if (WM_MENUSELECT!=iMsg)
        return FALSE;

    //This happens when there's no menu selection.
    if (-1==wMenuFlags)
        return FALSE;

    if (MF_POPUP & wMenuFlags)
        {
        /*
         * If this is the cascade verb menu itself, display the same
         * message.  m_phMenu[1] contains the current edit menu
         * handle.
         */
        if (0!=wItem)
            {
            fRet=((HMENU)wItem==GetSubMenu(m_phMenu[1]
                , MENUPOS_OBJECT));
            }
        }
    else
        {
        /*
         * If the ID is in the verb range, use
         * IDS_ITEMMESSAGEEDITOBJECT message
         */
        fRet=(IDM_VERBMIN <= wItem && IDM_VERBMAX >= wItem);
        }

    if (fRet)
        m_pSL->MessageDisplay(IDM_EDITOBJECT);

    return fRet;
    }

//End CHAPTER17MOD


PATRON.H

/*
 * PATRON.H
 * Patron Chapter 17
 *
 * Single include file that pulls in everything needed for other
 * source files in the application.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _PATRON_H_
#define _PATRON_H_

#define INC_CLASSLIB
#define INC_OLEUI
//CHAPTER17MOD
#define CHAPTER17
//End CHAPTER17MOD
#include <inole.h>
#include "resource.h"

//Get editor window information
#include "pages.h"

/*
 * UINT value such that adding one produces zero.  Portable to Win32.
 * This is used to represent a non-existent zero-based UINT value
 */
#define NOVALUE                     ((UINT)-1)

//PATRON.CPP:  Frame object that creates a main window

class CPatronFrame : public CFrame
    {
    private:
        BOOL            m_fInitialized;     //OleInitialize worked
        LPCLASSFACTORY  m_pIClassDataTran;  //For locking.

    protected:
        //Overridable for creating a CPatronClient
        virtual PCClient    CreateCClient(void);

        //CHAPTER17MOD
        virtual BOOL        FMessageHook(HWND, UINT, WPARAM, LPARAM
                                , LRESULT *);
        //End CHAPTER17MOD
        virtual BOOL        RegisterAllClasses(void);
        virtual UINT        CreateToolbar(void);
        virtual LRESULT     OnCommand(HWND, WPARAM, LPARAM);

    public:
        CPatronFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CPatronFrame(void);

        //Overrides
        virtual BOOL        Init(PFRAMEINIT);

        virtual void        UpdateMenus(HMENU, UINT);
        virtual void        UpdateToolbar(void);

    };

typedef CPatronFrame *PCPatronFrame;

//CLIENT.CPP

/*
 * The only reason we have a derived class here is to override
 * CreateCDocument so we can create our own type as well as
 * overriding NewDocument to perform one other piece of work once
 * the document's been created.
 */

class CPatronClient : public CClient
    {
    protected:
        //Overridable for creating a new CDocument
        virtual PCDocument CreateCDocument(void);

    public:
        CPatronClient(HINSTANCE, PCFrame);
        virtual ~CPatronClient(void);
    };

typedef CPatronClient *PCPatronClient;

//DOCUMENT.CPP

//Constant ID for the pages window that lives in a document window
#define ID_PAGES            723

class CPatronDoc : public CDocument
    {
    //These need access to FQueryPasteFromData, PasteFromData
    friend class CDropTarget;
    friend class CDropSource;

    protected:
        LONG            m_lVer;         //Loaded data version
        PCPages         m_pPG;          //Pages window in us
        LPSTORAGE       m_pIStorage;    //Root storage for document
        BOOL            m_fPrintSetup;

        class CDropTarget *m_pDropTarget;   //Registered target

        //CHAPTER17MOD
        UINT            m_cfEmbeddedObject; //Clipboard formats
        UINT            m_cfObjectDescriptor;
        //End CHAPTER17MOD

    protected:
        virtual BOOL    FMessageHook(HWND, UINT, WPARAM, LPARAM
            , LRESULT *);

        BOOL            FQueryPasteFromData(LPDATAOBJECT
                            , LPFORMATETC, PTENANTTYPE);
        //CHAPTER17MOD
        BOOL            PasteFromData(LPDATAOBJECT, LPFORMATETC
                            , TENANTTYPE, PPATRONOBJECT, DWORD
                            , BOOL);
        //End CHAPTER17MOD

    public:
        CPatronDoc(HINSTANCE, PCFrame, PCDocumentAdviseSink);
        virtual ~CPatronDoc(void);

        virtual BOOL    Init(PDOCUMENTINIT);
        virtual void    Clear(void);

        virtual BOOL    FDirtyGet(void);
        virtual void    Delete(void);
        virtual BOOL    FQueryPrinterSetup(void);
        virtual BOOL    FQueryObjectSelected(HMENU);

        virtual UINT    Load(BOOL, LPTSTR);
        virtual UINT    Save(UINT, LPTSTR);

        virtual BOOL    Print(HWND);
        virtual UINT    PrinterSetup(HWND, BOOL);

        virtual BOOL    Clip(HWND, BOOL);
        virtual BOOL    FQueryPaste(void);
        virtual BOOL    Paste(HWND);
        virtual BOOL    PasteSpecial(HWND);

        virtual UINT    NewPage(void);
        virtual UINT    DeletePage(void);
        virtual UINT    NextPage(void);
        virtual UINT    PreviousPage(void);
        virtual UINT    FirstPage(void);
        virtual UINT    LastPage(void);

        //CHAPTER17MOD
        virtual void    Rename(LPTSTR);
        virtual BOOL    InsertObject(HWND);
        virtual void    ActivateObject(LONG);
        virtual BOOL    ConvertObject(HWND);
        //End CHAPTER17MOD
    };

typedef CPatronDoc *PCPatronDoc;

//Hook for Print Dialog to hide Setup... button
UINT CALLBACK PrintDlgHook(HWND, UINT, WPARAM, LPARAM);

//Drag-drop objects we need in the document

class CDropTarget : public IDropTarget
    {
    protected:
        ULONG               m_cRef;
        PCPatronDoc         m_pDoc;

        LPDATAOBJECT        m_pIDataObject;  //From DragEnter
        BOOL                m_fPendingRepaint;
        POINTL              m_ptPick;        //Pick-up offsets
        POINTL              m_ptLast;        //Last drag point
        SIZEL               m_szl;           //Object size
        BOOL                m_fFeedback;     //Draw feedback?
        FORMATETC           m_fe;            //Real dropping format

    public:
        CDropTarget(PCPatronDoc);
        ~CDropTarget(void);

        //IDropTarget interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP DragEnter(LPDATAOBJECT, DWORD, POINTL,LPDWORD);
        STDMETHODIMP DragOver(DWORD, POINTL, LPDWORD);
        STDMETHODIMP DragLeave(void);
        STDMETHODIMP Drop(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
    };

typedef CDropTarget *PCDropTarget;

class CDropSource : public IDropSource
    {
    protected:
        ULONG               m_cRef;

    public:
        CDropSource(void);
        ~CDropSource(void);

        //IDropSource interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP QueryContinueDrag(BOOL, DWORD);
        STDMETHODIMP GiveFeedback(DWORD);
    };

typedef CDropSource *PCDropSource;

#endif //_PATRON_H_


PRINT.CPP

/*
 * PRINT.CPP
 * Patron Chapter 17
 *
 * Implementation of printing functions for both CPatronDoc
 * and CPages classes.  These functions are here to keep clutter
 * down in document.cpp and pages.cpp.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

static HWND g_hDlgPrint=NULL;
static BOOL g_fCancelPrint=FALSE;

/*
 * CPatronDoc::Print
 *
 * Purpose:
 *  Prints the current document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *
 * Return Value:
 *  BOOL            TRUE if printing happened, FALSE if it didn't
 *                  start or didn't complete.
 */

BOOL CPatronDoc::Print(HWND hWndFrame)
    {
    PRINTDLG        pd;
    BOOL            fSuccess;

    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);
    pd.hwndOwner  =hWndFrame;
    pd.nCopies    =1;
    pd.nFromPage  =(USHORT)-1;
    pd.nToPage    =(USHORT)-1;
    pd.nMinPage   =1;
    pd.nMaxPage   =m_pPG->NumPagesGet();

    pd.lpfnPrintHook=PrintDlgHook;

    //Get the current document printer settings
    pd.hDevMode=m_pPG->DevModeGet();

    pd.Flags=PD_RETURNDC | PD_ALLPAGES | PD_COLLATE
        | PD_HIDEPRINTTOFILE | PD_NOSELECTION | PD_ENABLEPRINTHOOK;

    if (!PrintDlg(&pd))
        return FALSE;

    if (NULL!=pd.hDevMode)
        GlobalFree(pd.hDevMode);

    if (NULL!=pd.hDevNames)
        GlobalFree(pd.hDevNames);

    //Go do the actual printing.
    fSuccess=m_pPG->Print(pd.hDC, PSZ(IDS_DOCUMENTNAME), pd.Flags
        , pd.nFromPage, pd.nToPage, pd.nCopies);

    if (!fSuccess)
        {
        MessageBox(m_hWnd, PSZ(IDS_PRINTERROR)
            , PSZ(IDS_DOCUMENTCAPTION), MB_OK);
        }

    return fSuccess;
    }

/*
 * CPatronDoc::PrinterSetup
 *
 * Purpose:
 *  Selects a new printer and options for this document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *  fDefault        BOOL to avoid any dialog and just use the
 *                  default.
 *
 * Return Value:
 *  UINT            Undefined
 */

UINT CPatronDoc::PrinterSetup(HWND hWndFrame, BOOL fDefault)
    {
    PRINTDLG        pd;

    //Attempt to get printer metrics for the default printer.
    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);

    if (fDefault)
        pd.Flags=PD_RETURNDEFAULT;
    else
        {
        pd.hwndOwner=hWndFrame;
        pd.Flags=PD_PRINTSETUP;

        //Get the current document printer settings
        pd.hDevMode=m_pPG->DevModeGet();
        }

    if (!PrintDlg(&pd))
        return FALSE;

    if (!m_pPG->DevModeSet(pd.hDevMode, pd.hDevNames))
        {
        GlobalFree(pd.hDevNames);
        GlobalFree(pd.hDevMode);
        return FALSE;
        }

    FDirtySet(TRUE);
    return 1;
    }

/*
 * PrintDlgHook
 *
 * Purpose:
 *  Callback hook for the Print Dialog so we can hide the Setup
 *  button.  Patron only allows Setup before anything exists on
 *  the page, and is not written to handle setup at Print time.
 */

UINT CALLBACK PrintDlgHook(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    if (WM_INITDIALOG==iMsg)
        {
        HWND        hWnd;

        hWnd=GetDlgItem(hDlg, psh1);
        ShowWindow(hWnd, SW_HIDE);
        return TRUE;
        }

    return FALSE;
    }

/*
 * CPatronDoc::FQueryPrinterSetup
 *
 * Purpose:
 *  Returns whether or not the Printer Setup menu item can be
 *  enabled.  Once you create a tenant in any page, Printer Setup
 *  is voided simply to keep this sample simple, that is, we don't
 *  have to worry about reorganizing potentially large amounts
 *  of layout after we start plopping down objects.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE to enable the menu, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPrinterSetup(void)
    {
    return m_fPrintSetup;
    }

/*
 * CPages::DevModeSet
 *
 * Purpose:
 *  Provides the Pages with the current printer information.
 *
 * Parameters:
 *  hDevMode        HGLOBAL to the memory containing the DEVMODE.
 *                  This function assumes responsibility for this
 *                  handle.
 *  hDevNames       HGLOBAL providing the driver name and device
 *                  name from which we can create a DC for
 *                  information.
 *
 * Return Value:
 *  BOOL            TRUE if we could accept this configuration,
 *                  FALSE otherwise.  If we return TRUE we also
 *                  delete the old memory we hold.
 */

BOOL CPages::DevModeSet(HGLOBAL hDevMode, HGLOBAL hDevNames)
    {
    LPDEVNAMES      pdn;
    LPTSTR          psz;
    HGLOBAL         hMem;
    PDEVICECONFIG   pdc;
    LPDEVMODE       pdm;
    LPSTREAM        pIStream;
    HRESULT         hr;
    ULONG           cbDevMode, cbWrite;
    BOOL            fRet=FALSE;

    if (NULL==hDevMode || NULL==hDevNames)
        return FALSE;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    /*
     * DEVMODE is variable length--total length in hDevMode, so the
     * amount to write is that plus string space.  We subtract
     * sizeof(DEVMODE) as that is already included from GlobalSize.
     */
    cbDevMode=GlobalSize(hDevMode);
    cbWrite=cbDevMode+sizeof(DEVICECONFIG)-sizeof(DEVMODE);

    hMem=GlobalAlloc(GHND, cbWrite);

    if (NULL==hMem)
        {
        pIStream->Release();
        return FALSE;
        }

    pdc=(PDEVICECONFIG)GlobalLock(hMem);    //This always works
    pdm=(LPDEVMODE)GlobalLock(hDevMode);    //This might not

    if (NULL!=pdm)
        {
        pdc->cb=cbWrite;
        pdc->cbDevMode=cbDevMode;
        memcpy(&pdc->dm, pdm, (int)cbDevMode);
        GlobalUnlock(hDevMode);

        psz=(LPTSTR)GlobalLock(hDevNames);

        if (NULL!=psz)
            {
            pdn=(LPDEVNAMES)psz;
            lstrcpy(pdc->szDriver, psz+pdn->wDriverOffset);
            lstrcpy(pdc->szDevice, psz+pdn->wDeviceOffset);
            lstrcpy(pdc->szPort,   psz+pdn->wOutputOffset);

            pIStream->Write(pdc, cbWrite, &cbWrite);
            GlobalUnlock(hDevNames);
            fRet=TRUE;
            }
        }

    GlobalUnlock(hMem);
    GlobalFree(hMem);

    pIStream->Release();

    if (!fRet)
        return FALSE;

    GlobalFree(hDevNames);
    GlobalFree(hDevMode);

    return ConfigureForDevice();
    }

/*
 * CPages::DevModeGet
 *
 * Purpose:
 *  Retrieves a copy of the current DEVMODE structure for this
 *  Pages window.  The caller is responsible for this memory.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HGLOBAL         Handle to the memory containing the DEVMODE
 *                  structure.
 */

HGLOBAL CPages::DevModeGet(void)
    {
    HGLOBAL         hMem;
    LPVOID          pv;
    ULONG           cbDevMode, cbRead;
    LARGE_INTEGER   li;
    LPSTREAM        pIStream;
    HRESULT         hr;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    //Read how much to allocate for the DEVMODE structure
    LISet32(li, CBSEEKOFFSETCBDEVMODE);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);
    pIStream->Read(&cbDevMode, sizeof(ULONG), &cbRead);

    hMem=GlobalAlloc(GHND, cbDevMode);

    if (NULL!=hMem)
        {
        pv=(LPVOID)GlobalLock(hMem);
        pIStream->Read(pv, cbDevMode, &cbRead);
        GlobalUnlock(hMem);
        }

    pIStream->Release();
    return hMem;
    }

/*
 * CPages::DevReadConfig
 *
 * Purpose:
 *  Public function to read the current device configuration and
 *  optionally return an information context for it.
 *
 *
 * Parameters:
 *  ppcd            PCOMBINEDEVICE * in which to return a pointer
 *                  to an allocated structure that has all the
 *                  device information we want.  Ignored if NULL.
 *                  This is allocated with the task allocator.
 *  phDC            HDC * in which to return the information
 *                  context.  If NULL, no IC is created.  Caller
 *                  becomes responsible for the returned IC.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::DevReadConfig(PCOMBINEDEVICE *ppcd, HDC *phDC)
    {
    HRESULT         hr;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    PCOMBINEDEVICE  pcd;
    ULONG           cb, cbRead;
    LARGE_INTEGER   li;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    /*
     * Allocate the COMBINEDEVICE structure including the variable
     * information past the DEVMODE part.
     */

    hr=CoGetMalloc(MEMCTX_TASK, &pIMalloc);

    if (FAILED(hr))
        {
        pIStream->Release();
        return FALSE;
        }

    /*
     * Read size of the DEVICECONFIG structure including variable
     * portion of DEVMODE.  We need to load all this information
     * for CreateIC.  To this size we'll add the size of
     * DVTARGETDEVICE in order to allocate a COMBINEDEVICE.
     */

    pIStream->Read(&cb, sizeof(DWORD), &cbRead);

    pcd=(PCOMBINEDEVICE)pIMalloc->Alloc(cb+sizeof(DVTARGETDEVICE));

    if (NULL==pcd)
        {
        pIMalloc->Release();
        pIStream->Release();
        return FALSE;
        }

    //Now get the real information.
    LISet32(li, 0);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);
    pIStream->Read(&(pcd->dc), cb, &cbRead);
    pIStream->Release();

    /*
     * If requested, complete the DVTARGETDEVICE structure in
     * pcd and store pcd in *ppcd for return.
     */

    if (NULL!=ppcd)
        {
        WORD    cb=sizeof(DVTARGETDEVICE);

        pcd->td.tdSize=cb;
        pcd->td.tdExtDevmodeOffset=cb;
        pcd->td.tdDriverNameOffset=cb+sizeof(DEVMODE);
        pcd->td.tdDeviceNameOffset=cb+sizeof(DEVMODE)
            +(CCHDEVICENAME*sizeof(TCHAR));
        pcd->td.tdPortNameOffset  =cb+sizeof(DEVMODE)
            +(CCHDEVICENAME*2*sizeof(TCHAR));

        *ppcd=pcd;
        }

    //Create an IC if requested.
    if (NULL!=phDC)
        {
        //Get the DC then configure
        *phDC=CreateIC(pcd->dc.szDriver, pcd->dc.szDevice
            , pcd->dc.szPort, &(pcd->dc.dm));

        if (NULL==*phDC)
            return FALSE;
        }

    //pcd is a temporary allocation in this case
    if (NULL==ppcd)
        pIMalloc->Free(pcd);

    pIMalloc->Release();
    return (NULL!=*phDC);
    }

/*
 * CPages::ConfigureForDevice
 *
 * Purpose:
 *  Recalculates our drawing configuration based on the contents of
 *  an hDC.  If no HDC is given we use the contents of our DevMode
 *  stream.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::ConfigureForDevice(void)
    {
    POINT           ptOffset, ptPaper;
    RECT            rc;
    HDC             hDC;
    CHourglass      hg;

    if (!DevReadConfig(NULL, &hDC))
        return FALSE;

    //Get usable page dimensions:  already sensitive to orientation
    m_cx=GetDeviceCaps(hDC, HORZSIZE)*10-16; //*10: mm to LOMETRIC
    m_cy=GetDeviceCaps(hDC, VERTSIZE)*10-16; //-16: for driver bugs.

    //Calculate the printer-limited margins on sides in LOMETRIC.
    Escape(hDC, GETPRINTINGOFFSET, NULL, NULL, &ptOffset);
    Escape(hDC, GETPHYSPAGESIZE,   NULL, NULL, &ptPaper);

    SetRect(&rc, ptOffset.x, ptOffset.y, ptPaper.x, ptPaper.y);
    SetMapMode(hDC, MM_LOMETRIC);
    RectConvertMappings(&rc, hDC, FALSE);

    //Left and top margins are the printing offset.
    m_xMarginLeft= rc.left+8;   //+8 to match -16 above
    m_yMarginTop =-rc.top+8;    //LOMETRIC makes this negative.

    //Right is (paper width)-(usable width)-(left margin)
    m_xMarginRight =rc.right-m_cx-m_xMarginLeft;

    //Bottom is (paper height)-(usable height)-(top margin)+1
    m_yMarginBottom=-rc.bottom-m_cy-m_yMarginTop+1;

    UpdateScrollRanges();

    DeleteDC(hDC);
    return TRUE;
    }

/*
 * CPages::Print
 *
 * Purpose:
 *  Prints a specified range of pages to a given hDC.  Repeats for
 *  a given number of copies.
 *
 * Parameters:
 *  hDC             HDC to which we print.
 *  pszDoc          LPTSTR providing the document name.
 *  dwFlags         DWORD flags from PrintDlg
 *  iPageStart      UINT starting page index (one based)
 *  iPageEnd        UINT ending page index (one based).  Includes
 *                  this page.
 *  cCopies         UINT number of copies to print.  If PD_COLLATE
 *                  in dwFlags is set, we print multiple copies of
 *                  each page as we cycle through.  Otherwise we
 *                  cycle multiple times.
 *
 * Return Value:
 *  None
 */

BOOL CPages::Print(HDC hDC, LPTSTR pszDoc, DWORD dwFlags
    , UINT iPageStart, UINT iPageEnd, UINT cCopies)
    {
    BOOL        fError=FALSE;
    int         iPage, iPageInc;
    int         iUserPage, cPages;
    UINT        iRepeat, cRepeat;
    UINT        iCycle, cCycles;
    UINT        iPageHold=m_iPageCur;
    HWND        hWndT, hWndTop=NULL;
    DOCINFO     di;
    PCDocument  pDoc;

    //Validate hDC and page ranges
    if (NULL==hDC)
        return FALSE;

    if ((PD_PAGENUMS & dwFlags))
        {
        if (-1==iPageStart)
            iPageStart=0;
        else
            iPageStart--;   //Switch to zero offset.

        if (-1==iPageEnd)
            iPageEnd=m_cPages-1;
        else
            iPageEnd--;     //Switch to zero offset.
        }
    else //Can't test PD_ALLPAGES with & since it's defined as 0L
        {
        iPageStart=0;
        iPageEnd=m_cPages-1;
        }

    //Arrange cycles and repeats depending on cCopies and collating
    if (PD_COLLATE & dwFlags)
        {
        cCycles=cCopies;
        cRepeat=1;
        }
    else
        {
        cCycles=1;
        cRepeat=cCopies;
        }

    //Disable the frame window to prevent reentrancy while printing.
    hWndT=GetParent(m_hWnd);
    pDoc=(PCDocument)SendMessage(hWndT, DOCM_PDOCUMENT, 0, 0L);

    if (NULL!=pDoc)
        {
        PCFrame pFR;

        pFR=pDoc->FrameGet();
        hWndTop=pFR->Window();
        EnableWindow(hWndTop, FALSE);
        }

    SetAbortProc(hDC, AbortProc);
    g_fCancelPrint=FALSE;

    //If these don't work then we'll just live without a dialog.
    g_hDlgPrint=CreateDialog(m_hInst, MAKEINTRESOURCE(IDD_PRINTING)
        , hWndTop, PrintDlgProc);

    //Increment for either direction.
    iPageInc=(iPageStart > iPageEnd) ? -1 : 1;

    //Initial entries in dialog box.
    cPages=1+((int)(iPageEnd-iPageStart)*iPageInc);

    SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE, 1, (LPARAM)cPages);
    SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, 1, (LPARAM)cRepeat);

    di.cbSize=sizeof(DOCINFO);
    di.lpszDocName=pszDoc;
    di.lpszOutput=NULL;

    if (StartDoc(hDC, &di) > 0)
        {
        /*
         * Iterate over the pages, repeating each page depending on
         * the copies we want and if we have collate enabled.
         */

        for (iCycle=1; iCycle <= cCycles; iCycle++)
            {
            if (PD_COLLATE & dwFlags)
                {
                SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, iCycle
                    , (LPARAM)cCycles);
                }

            //iPageInc controls direction
            for (iPage=iPageStart; ; iPage+=iPageInc)
                {
                iUserPage=1+((iPage-(int)iPageStart)*iPageInc);

                SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE
                    , iUserPage, (LPARAM)cPages);

                m_iPageCur=iPage;   //We restore this later.

                for (iRepeat=1; iRepeat <= cRepeat; iRepeat++)
                    {
                    if (!(PD_COLLATE & dwFlags))
                        {
                        SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE
                            , iRepeat, (LPARAM)cRepeat);
                        }

                    StartPage(hDC);
                    Draw(hDC, TRUE, TRUE);

                    if (EndPage(hDC) < 0)
                        fError=TRUE;

                    if (fError || g_fCancelPrint)
                        break;
                    }

                if (fError || g_fCancelPrint)
                    break;

                //If we just printed the last page, time to quit.
                if (iPage==(int)iPageEnd)
                    break;
                }

            if (fError || g_fCancelPrint)
                break;
            }

        if (!fError)
            EndDoc(hDC);
        else
            AbortDoc(hDC);
        }
    else
        fError=TRUE;

    //Set the page back to what it was before all this started.
    m_iPageCur=iPageHold;

    EnableWindow(hWndTop, TRUE);
    SetFocus(hWndTop);
    DestroyWindow(g_hDlgPrint);
    DeleteDC(hDC);
    return !fError;
    }

/*
 * AbortProc
 *
 * Purpose:
 *  Abort procedure for printing the pages.
 *
 * Parameters:
 *  hDC             HDC on which printing is happening.
 *  iErr            int error code.
 *
 * Return Value:
 *  BOOL            TRUE to continue the print job, FALSE otherwise.
 */

BOOL APIENTRY AbortProc(HDC hDC, int iErr)
    {
    MSG     msg;

    while (!g_fCancelPrint
        && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
        if (NULL==g_hDlgPrint
            || !IsDialogMessage(g_hDlgPrint, &msg))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    return !g_fCancelPrint;
    }

/*
 * PrintDlgProc
 *
 * Purpose:
 *  Modeless dialog procedure for the dialog displayed while Patron
 *  is printing pages.
 */

BOOL APIENTRY PrintDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    TCHAR           szFormat[40];
    TCHAR           szOutput[80];

    switch (iMsg)
        {
        case WM_INITDIALOG:
            EnableMenuItem(GetSystemMenu(hDlg, FALSE), SC_CLOSE
                , MF_GRAYED);
            return TRUE;

        case WM_COMMAND:
            //Cancel button was pressed.
            g_fCancelPrint=TRUE;
            ShowWindow(hDlg, SW_HIDE);
            return TRUE;

        case PRINTM_PAGEUPDATE:
            GetDlgItemText(hDlg, ID_PAGESTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTPAGE, szOutput);
            return TRUE;

        case PRINTM_COPYUPDATE:
            GetDlgItemText(hDlg, ID_COPYSTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTCOPY, szOutput);
            return TRUE;
        }

    return FALSE;
    }


RESOURCE.H

/*
 * RESOURCE.H
 * Patron Chapter 17
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Note that we augment classlib.h which we include before this.

//Position of Window menu.
#undef  WINDOW_MENU
#define WINDOW_MENU                 3

//CHAPTER17MOD
//Position of the "Object" item on the Edit menu.
#define MENUPOS_OBJECT              8
#define MENUPOS_OBJECTONPOPUP       5

//Popup menu
#define IDR_RIGHTPOPUPMENU          2
//End CHAPTER17MOD

//Total number of menus
#undef  CMENUS
#ifdef MDI
#define CMENUS                      5
#else
#define CMENUS                      4
#endif

//Menu command identifiers.

#define IDM_FILEPRINT               (IDM_CUSTOMFILEMIN)
#define IDM_FILEPRINTERSETUP        (IDM_CUSTOMFILEMIN+1)

#define IDM_EDITPASTESPECIAL        (IDM_CUSTOMEDITMIN)
#define IDM_EDITDELETEOBJECT        (IDM_CUSTOMEDITMIN+1)
//CHAPTER17MOD
#define IDM_EDITINSERTOBJECT        (IDM_CUSTOMEDITMIN+2)
#define IDM_EDITOBJECT              (IDM_CUSTOMEDITMIN+3)
#define IDM_EDITCONVERT             (IDM_CUSTOMEDITMIN+4)
//End CHAPTER17MOD

#define IDM_PAGENEWPAGE             (IDM_CUSTOMMIN)
#define IDM_PAGEDELETEPAGE          (IDM_CUSTOMMIN+1)
#define IDM_PAGENEXTPAGE            (IDM_CUSTOMMIN+2)
#define IDM_PAGEPREVIOUSPAGE        (IDM_CUSTOMMIN+3)
#define IDM_PAGEFIRSTPAGE           (IDM_CUSTOMMIN+4)
#define IDM_PAGELASTPAGE            (IDM_CUSTOMMIN+5)

//CHAPTER17MOD
//This special item is for the dynamic verbs on the Edit/Object item.
#define IDM_VERBMIN                 (IDM_CUSTOMMIN+100)
#define IDM_VERBMAX                 (IDM_CUSTOMMIN+200)
//End CHAPTER17MOD

/*
 * IDs for StatStrip handling of popup menus:  must be in order of menu
 * CLASSRES.H already defines ID_MENUFILE and ID_MENUEDIT for us.
 */

#undef ID_MENUWINDOW
#undef ID_MENUHELP

#define ID_MENUPAGE                 (ID_MENUCUSTOMMIN+0)
#define ID_MENUWINDOW               (ID_MENUCUSTOMMIN+1)
#define ID_MENUHELP                 (ID_MENUCUSTOMMIN+2)

//Stringtable IDs.  Keep sequential for each group.
#define IDS_FRAMEMIN                IDS_STANDARDFRAMEMIN
#define IDS_FRAMEMAX                IDS_STANDARDFRAMEMAX

#define IDS_DOCUMENTMIN             IDS_STANDARDDOCMIN
#define IDS_PRINTERROR              (IDS_CUSTOMDOCMIN+0)
#define IDS_DOCUMENTNAME            (IDS_CUSTOMDOCMIN+1)
#define IDS_PASTEASPATRON           (IDS_CUSTOMDOCMIN+2)
#define IDS_PASTEMETAFILE           (IDS_CUSTOMDOCMIN+3)
#define IDS_PASTEASMETAFILE         (IDS_CUSTOMDOCMIN+4)
#define IDS_PASTEDIB                (IDS_CUSTOMDOCMIN+5)
#define IDS_PASTEASDIB              (IDS_CUSTOMDOCMIN+6)
#define IDS_PASTEBITMAP             (IDS_CUSTOMDOCMIN+7)
#define IDS_PASTEASBITMAP           (IDS_CUSTOMDOCMIN+8)
//CHAPTER17MOD
#define IDS_PASTEOBJECT             (IDS_CUSTOMDOCMIN+9)
#define IDS_PASTEASOBJECT           (IDS_CUSTOMDOCMIN+10)
#define IDS_DOCUMENTMAX             (IDS_CUSTOMDOCMIN+10)
//End CHAPTER17MOD

//These are specifically for the StatStrip
#define IDS_STATMESSAGEMIN                  IDS_STANDARDSTATMESSAGEMIN

#define IDS_ITEMMESSAGEFILEPRINT            (IDS_CUSTOMSTATMESSAGEMIN+0)
#define IDS_ITEMMESSAGEFILEPRINTERSETUP     (IDS_CUSTOMSTATMESSAGEMIN+1)

#define IDS_MENUMESSAGEPAGE                 (IDS_CUSTOMSTATMESSAGEMIN+2)
#define IDS_ITEMMESSAGEPAGENEWPAGE          (IDS_CUSTOMSTATMESSAGEMIN+3)
#define IDS_ITEMMESSAGEPAGEDELETEPAGE       (IDS_CUSTOMSTATMESSAGEMIN+4)
#define IDS_ITEMMESSAGEPAGENEXTPAGE         (IDS_CUSTOMSTATMESSAGEMIN+5)
#define IDS_ITEMMESSAGEPAGEPREVIOUSPAGE     (IDS_CUSTOMSTATMESSAGEMIN+6)
#define IDS_ITEMMESSAGEPAGEFIRSTPAGE        (IDS_CUSTOMSTATMESSAGEMIN+7)
#define IDS_ITEMMESSAGEPAGELASTPAGE         (IDS_CUSTOMSTATMESSAGEMIN+8)
#define IDS_ITEMMESSAGEEDITPASTESPECIAL     (IDS_CUSTOMSTATMESSAGEMIN+9)
#define IDS_ITEMMESSAGEEDITDELETEOBJECT     (IDS_CUSTOMSTATMESSAGEMIN+10)

//CHAPTER17MOD
#define IDS_ITEMMESSAGEEDITINSERTOBJECT     (IDS_CUSTOMSTATMESSAGEMIN+11)
#define IDS_ITEMMESSAGEEDITOBJECT           (IDS_CUSTOMSTATMESSAGEMIN+12)
#define IDS_ITEMMESSAGEEDITCONVERT          (IDS_CUSTOMSTATMESSAGEMIN+13)
#define IDS_STATMESSAGEMAX                  (IDS_CUSTOMSTATMESSAGEMIN+13)
//End CHAPTER17MOD

//Definitions for the printing dialog.

#define IDD_PRINTING                2

#define ID_CURRENTPAGE              101
#define ID_CURRENTCOPY              102
#define ID_COPYSTRING               103
#define ID_PAGESTRING               104

#define PRINTM_PAGEUPDATE           (WM_USER+100)
#define PRINTM_COPYUPDATE           (WM_USER+101)

#endif //_RESOURCE_H_


TENANT.CPP

/*
 * TENANT.CPP
 * Patron Chapter 17
 *
 * Implementation of the CTentant class which holds information
 * for a single object on a page.  It maintains position, references
 * to data, and a storage.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CTenant::CTenant
 * CTenant::~CTenant
 *
 * Constructor Parameters:
 *  dwID            DWORD identifier for this page.
 *  hWnd            HWND of the pages window.
 *  pPG             PCPages to the parent structure.
 */

CTenant::CTenant(DWORD dwID, HWND hWnd, PCPages pPG)
    {
    m_hWnd=hWnd;
    m_dwID=dwID;

    m_fInitialized=0;
    m_pIStorage=NULL;
    m_cOpens=0;

    m_pObj=NULL;
    m_pPG =pPG;
    m_clsID=CLSID_NULL;
    m_fSetExtent=FALSE;

    //CHAPTER17MOD
    m_cRef=0;
    m_pIOleObject=NULL;
    m_pIViewObject2=NULL;
    m_grfMisc=0;

    m_pImpIOleClientSite=NULL;
    m_pImpIAdviseSink=NULL;

    m_fRepaintEnabled=TRUE;
    //End CHAPTER17MOD
    return;
    }

CTenant::~CTenant(void)
    {
    //CHAPTER17MOD
    //Object pointers cleaned up in Close.

    DeleteInterfaceImp(m_pImpIAdviseSink);
    DeleteInterfaceImp(m_pImpIOleClientSite);
    //End CHAPTER17MOD

    return;
    }

//CHAPTER17MOD
/*
 * CTenant::QueryInterface
 * CTenant::AddRef
 * CTenant::Release
 *
 * Purpose:
 *  IUnknown members for CTenant object.
 */

STDMETHODIMP CTenant::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IOleClientSite==riid)
        *ppv=m_pImpIOleClientSite;

    if (IID_IAdviseSink==riid)
        *ppv=m_pImpIAdviseSink;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CTenant::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CTenant::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

//End CHAPTER17MOD

/*
 * CTenant::GetID
 *
 * Return Value:
 *  DWORD           dwID field in this tenant.
 */

DWORD CTenant::GetID(void)
    {
    return m_dwID;
    }

/*
 * CTenant::GetStorageName
 *
 * Parameters:
 *  pszName         LPOLESTR to a buffer in which to store the storage
 *                  name for this tenant.
 *
 * Return Value:
 *  UINT            Number of characters stored.
 */

UINT CTenant::GetStorageName(LPOLESTR pszName)
    {
   #ifdef WIN32ANSI
    char        szTemp[32];
    UINT        cch;

    cch=wsprintf(szTemp, "Tenant %lu", m_dwID);
    MultiByteToWideChar(CP_ACP, 0, szTemp, -1, pszName, 32);
    return cch;
   #else
    return wsprintf(pszName, TEXT("Tenant %lu"), m_dwID);
   #endif
    }

//CHAPTER17MOD
/*
 * CTenant::StorageGet
 *
 * Purpose:
 *  Returns the IStorage pointer maintained by this tenant,
 *  AddRef'd of course.
 *
 * Parameters:
 *  ppStg           LPSTORAGE * in which to return the pointer.
 *
 * Return Value:
 *  None
 */

void CTenant::StorageGet(LPSTORAGE *ppStg)
    {
    if (NULL==ppStg)
        return;

    *ppStg=m_pIStorage;

    if (NULL!=*ppStg)
        (*ppStg)->AddRef();

    return;
    }
//End CHAPTER17MOD

/*
 * CTenant::Create
 *
 * Purpose:
 *  Creates a new tenant of the given CLSID, which can be either a
 *  static bitmap or metafile or any compound document object.
 *
 * Parameters:
 *  tType           TENANTTYPE to create, either a static metafile,
 *                  bitmap, or some kind of compound document object
 *                  This determines which OleCreate* call we use.
 *  pvType          LPVOID providing the relevant pointer from which
 *                  to create the tenant, depending on iType.
 *  pFE             LPFORMATETC specifying the type of renderings
 *                  to use.
 *  pptl            PPOINTL in which we store offset coordinates.
 *  pszl            LPSIZEL where this object should store its
 *                  lometric extents.
 *  pIStorage       LPSTORAGE of the page we live in.  We have to
 *                  create another storage in this for the tenant.
 *  ppo             PPATRONOBJECT containing placement data.
 *  dwData          DWORD with extra data, sensitive to iType.
 *
 * Return Value:
 *  UINT            A CREATE_* value depending on what we
 *                  actually do.
 */

UINT CTenant::Create(TENANTTYPE tType, LPVOID pvType
    , LPFORMATETC pFE, PPOINTL pptl, LPSIZEL pszl
    , LPSTORAGE pIStorage, PPATRONOBJECT ppo, DWORD dwData)
    {
    HRESULT             hr;
    LPUNKNOWN           pObj;
    UINT                uRet=CREATE_GRAPHICONLY;
    //CHAPTER17MOD
    //Some things moves to ObjectInitialize
    //End CHAPTER17MOD

    if (NULL==pvType || NULL==pIStorage)
        return CREATE_FAILED;

    //Fail if this is called for an already living tenant.
    if (m_fInitialized)
        return CREATE_FAILED;

    m_fInitialized=TRUE;

    //Create a new storage for this tenant.
    if (!Open(pIStorage))
        return CREATE_FAILED;

    /*
     * Get the placement info if it's here.  We either have a non-
     * NULL PPATRONOBJECT in ppo or we have to use default
     * placement and retrieve the size from the object itself.
     */
    pszl->cx=0;
    pszl->cy=0;

    if (NULL!=ppo)
        {
        *pFE=ppo->fe;
        *pptl=ppo->ptl;
        *pszl=ppo->szl;     //Could be 0,0 , so we ask object

        uRet=CREATE_PLACEDOBJECT;
        }

    hr=ResultFromScode(E_FAIL);

    //Now create an object based specifically for the type.
    switch (tType)
        {
        case TENANTTYPE_NULL:
            break;

        case TENANTTYPE_STATIC:
            /*
             * We could use OleCreateStaticFromData here which does
             * pretty much what we're doing below.  However, it does
             * not allow us to control whether we paste a bitmap or
             * a metafile--it uses metafile first, bitmap second.
             * For this reason we'll use code developed in Chapter
             * 11's FreeLoader to affect the paste.
             */
            hr=CreateStatic((LPDATAOBJECT)pvType, pFE, &pObj);
            break;

        //CHAPTER17MOD
        case TENANTTYPE_EMBEDDEDOBJECT:
            hr=OleCreate(*((LPCLSID)pvType), IID_IUnknown
                , OLERENDER_DRAW, NULL, NULL, m_pIStorage
                , (PPVOID)&pObj);
            break;

        case TENANTTYPE_EMBEDDEDFILE:
            hr=OleCreateFromFile(CLSID_NULL, (LPTSTR)pvType
                , IID_IUnknown, OLERENDER_DRAW, NULL, NULL
                , m_pIStorage, (PPVOID)&pObj);
            break;

        case TENANTTYPE_EMBEDDEDOBJECTFROMDATA:
            hr=OleCreateFromData((LPDATAOBJECT)pvType, IID_IUnknown
                , OLERENDER_DRAW, NULL, NULL, m_pIStorage
                , (PPVOID)&pObj);
            break;
        //End CHAPTER17MOD

        default:
            break;
        }

    //If creation didn't work, get rid of the element Open created.
    if (FAILED(hr))
        {
        Destroy(pIStorage);
        return CREATE_FAILED;
        }

    //CHAPTER17MOD
    //We don't get the size if PatronObject data was seen already.
    if (!ObjectInitialize(pObj, pFE, dwData))
        {
        Destroy(pIStorage);
        return CREATE_FAILED;
        }

    if (0==pszl->cx && 0==pszl->cy)
        {
        SIZEL   szl;

        //Try to get the real size of the object, default to 2"*2"
        SETSIZEL((*pszl), 2*LOMETRIC_PER_INCH, 2*LOMETRIC_PER_INCH);
        hr=ResultFromScode(E_FAIL);

        //Try IViewObject2 first, then IOleObject as a backup.
        if (NULL!=m_pIViewObject2)
            {
            hr=m_pIViewObject2->GetExtent(m_fe.dwAspect, -1, NULL
                , &szl);
            }
        else
            {
            if (NULL!=m_pIOleObject)
                hr=m_pIOleObject->GetExtent(m_fe.dwAspect, &szl);
            }

        if (SUCCEEDED(hr))
            {
            //Convert HIMETRIC to our LOMETRIC mapping
            SETSIZEL((*pszl), szl.cx/10, szl.cy/10);
            }
        }
    //End CHAPTER17MOD

    return uRet;
    }

/*
 * CTenant::Load
 *
 * Purpose:
 *  Recreates the object living in this tenant in place of calling
 *  FCreate.  This is used in loading as opposed to new creation.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE of the page we live in.
 *  pti             PTENTANTINFO containing persistent information.
 *                  The ID value in this structure is ignored.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CTenant::Load(LPSTORAGE pIStorage, PTENANTINFO pti)
    {
    HRESULT         hr;
    LPUNKNOWN       pObj;
    //CHAPTER17MOD
    DWORD           dwState=TENANTSTATE_DEFAULT;
    //End CHAPTER17MOD

    if (NULL==pIStorage || NULL==pti)
        return FALSE;

    //CHAPTER17MOD
    /*
     * If we already initialized once, clean up, releasing
     * everything before we attempt to reload.  This happens
     * when using the Convert Dialog.
     */
    if (m_fInitialized)
        {
        //Preserve all states except open
        dwState=(m_dwState & ~TENANTSTATE_OPEN);
        m_cRef++;   //Prevent accidental closure

        //This should release all holds on our IStorage as well.
        if (NULL!=m_pIViewObject2)
            {
            m_pIViewObject2->SetAdvise(m_fe.dwAspect, 0, NULL);
            ReleaseInterface(m_pIViewObject2);
            }

        ReleaseInterface(m_pIOleObject);
        ReleaseInterface(m_pObj);

        m_pIStorage=NULL;   //We'll have already released this.
        m_cRef--;           //Match safety increment above.
        }
    //End CHAPTER14MOD

    m_fInitialized=TRUE;

    //Open the storage for this tenant.
    if (!Open(pIStorage))
        return FALSE;

    hr=OleLoad(m_pIStorage, IID_IUnknown, NULL, (PPVOID)&pObj);

    if (FAILED(hr))
        {
        Destroy(pIStorage);
        return FALSE;
        }

    //CHAPTER17MOD
    m_fSetExtent=pti->fSetExtent;
    ObjectInitialize(pObj, &pti->fe, NULL);

    //Restore the original state before reloading.
    m_dwState=dwState;
    //End CHAPTER17MOD

    RectSet(&pti->rcl, FALSE, FALSE);
    return TRUE;
    }

/*
 * CTenant::GetInfo
 *
 * Purpose:
 *  Retrieved a TENANTINFO structure for this tenant.
 *
 * Parameters:
 *  pti             PTENANTINFO structure to fill
 *
 * Return Value:
 *  None
 */

void CTenant::GetInfo(PTENANTINFO pti)
    {
    if (NULL!=pti)
        {
        pti->dwID=m_dwID;
        pti->rcl=m_rcl;
        pti->fe=m_fe;
        pti->fSetExtent=m_fSetExtent;
        }

    return;
    }

//CHAPTER17MOD
/*
 * CTenant::ObjectInitialize
 * (Protected)
 *
 * Purpose:
 *  Performs operations necessary after creating an object or
 *  reloading one from storage.
 *
 * Parameters:
 *  pObj            LPUNKNOWN of the object in this tenant.
 *  pFE             LPFORMATETC describing the graphic here.
 *  dwData          DWORD extra data.  If pFE->dwAspect==
 *                  DVASPECT_ICON then this is the iconic metafile.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CTenant::ObjectInitialize(LPUNKNOWN pObj, LPFORMATETC pFE
    , DWORD dwData)
    {
    HRESULT         hr;
    LPPERSIST       pIPersist=NULL;
    DWORD           dw;
    PCDocument      pDoc;
    TCHAR           szFile[CCHPATHMAX];

    if (NULL==pObj || NULL==pFE)
        return FALSE;

    m_pObj=pObj;
    m_fe=*pFE;
    m_fe.ptd=NULL;
    m_dwState=TENANTSTATE_DEFAULT;

    /*
     * Determine the type:  Static or Embedded.  If Static,
     * this will have CLSID_Picture_Metafile or CLSID_Picture_Dib.
     * Otherwise it's embedded.  Later we'll add a case for links.
     */
    m_tType=TENANTTYPE_EMBEDDEDOBJECT;

    if (SUCCEEDED(pObj->QueryInterface(IID_IPersist
        , (PPVOID)&pIPersist)))
        {
        CLSID   clsid=CLSID_NULL;

        hr=pIPersist->GetClassID(&clsid);

        //If we don't have a CLSID, default to static
        if (FAILED(hr) || CLSID_Picture_Metafile==clsid
            || CLSID_Picture_Dib==clsid)
            m_tType=TENANTTYPE_STATIC;

        pIPersist->Release();
        }

    m_pIViewObject2=NULL;
    hr=pObj->QueryInterface(IID_IViewObject2
        , (PPVOID)&m_pIViewObject2);

    if (FAILED(hr))
        return FALSE;

    m_pIViewObject2->SetAdvise(m_fe.dwAspect, 0, m_pImpIAdviseSink);

    //We need an IOleObject most of the time, so get one here.
    m_pIOleObject=NULL;
    hr=pObj->QueryInterface(IID_IOleObject
         , (PPVOID)&m_pIOleObject);

    /*
     * Follow up object creation with advises and so forth.  If
     * we cannot get IOleObject here, then we know we can't do
     * any IOleObject actions from here on--object is static.
     */
    if (FAILED(hr))
        return TRUE;

    /*
     * Get the MiscStatus bits and check for OLEMISC_ONLYICONIC.
     * If set, force dwAspect in m_fe to DVASPECT_ICON so we
     * remember to draw it properly and do extents right.
     */
    m_pIOleObject->GetMiscStatus(m_fe.dwAspect, &m_grfMisc);

    if (OLEMISC_ONLYICONIC & m_grfMisc)
        m_fe.dwAspect=DVASPECT_ICON;

    /*
     * We could pass m_pImpIOleClientSite in an OleCreate* call, but
     * since this function could be called after OleLoad, we still
     * need to do this here, so it's always done here...
     */
    m_pIOleObject->SetClientSite(m_pImpIOleClientSite);
    m_pIOleObject->Advise(m_pImpIAdviseSink, &dw);

    OleSetContainedObject(m_pIOleObject, TRUE);

    /*
     * For IOleObject::SetHostNames we need the application name
     * and the document name (which is passed in the object
     * parameter).  The design of Patron doesn't give us nice
     * structured access to the name of the document we're in, so
     * I grab the parent of the Pages window (the document) and
     * send it DOCM_PDOCUMENT which returns us the pointer.
     * Roundabout, but it works.
     */

    pDoc=(PCDocument)SendMessage(GetParent(m_hWnd), DOCM_PDOCUMENT
        , 0, 0L);

    if (NULL!=pDoc)
        pDoc->FilenameGet(szFile, CCHPATHMAX);
    else
        szFile[0]=0;

    NotifyOfRename(szFile, NULL);

    /*
     * If we're creating an iconic aspect object and we have
     * an object from the Insert Object dialog, then we need to
     * store that iconic presentation in the cache, handled
     * with the utility function INOLE_SwitchDisplayAspect.  In
     * this case dwData is a handle to the metafile containing
     * the icon.  If dwData is NULL then we depend on the
     * server to provide the aspect, in which case we need
     * a view advise.
     */

    if (DVASPECT_ICON & m_fe.dwAspect)
        {
        DWORD           dw=DVASPECT_CONTENT;
        IAdviseSink    *pSink;

        pSink=(NULL==dwData) ? NULL : m_pImpIAdviseSink;

        INOLE_SwitchDisplayAspect(m_pIOleObject, &dw
            , DVASPECT_ICON, (HGLOBAL)(UINT)dwData, FALSE
            , (NULL!=dwData), pSink, NULL);
        }

    return TRUE;
    }
//End CHAPTER17MOD

/*
 * CTenant::Open
 *
 * Purpose:
 *  Retrieves the IStorage associated with this tenant.  The
 *  IStorage is owned by the tenant and thus the tenant always
 *  holds a reference count.
 *
 *  If the storage is already open for this tenant, then this
 *  function will AddRef it; therefore the caller must always
 *  match an Open with a Close.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE above this tenant (which has its
 *                  own storage).
 *
 * Return Value:
 *  BOOL            TRUE if opening succeeds, FALSE otherwise.
 */

BOOL CTenant::Open(LPSTORAGE pIStorage)
    {
    HRESULT     hr=NOERROR;
    DWORD       dwMode=STGM_TRANSACTED | STGM_READWRITE
                    | STGM_SHARE_EXCLUSIVE;
    OLECHAR     szTemp[32];

    if (NULL==m_pIStorage)
        {
        if (NULL==pIStorage)
            return FALSE;

        /*
         * Attempt to open the storage under this ID.  If there is
         * none, then create it.  In either case we end up with an
         * IStorage that we either save in pPage or release.
         */

        GetStorageName(szTemp);
        hr=pIStorage->OpenStorage(szTemp, NULL, dwMode, NULL, 0
            , &m_pIStorage);

        if (FAILED(hr))
            {
            hr=pIStorage->CreateStorage(szTemp, dwMode, 0, 0
                , &m_pIStorage);
            }
        }
    else
        m_pIStorage->AddRef();

    if (FAILED(hr))
        return FALSE;

    m_cOpens++;

    //CHAPTER17MOD
    //Create these if we don't have them already.
    if (NULL==m_pImpIOleClientSite && NULL==m_pImpIAdviseSink)
        {
        m_pImpIOleClientSite=new CImpIOleClientSite(this, this);
        m_pImpIAdviseSink=new CImpIAdviseSink(this, this);

        if (NULL==m_pImpIOleClientSite || NULL==m_pImpIAdviseSink)
            return FALSE;
        }
    //End CHAPTER17MOD

    return TRUE;
    }

/*
 * CTenant::Close
 *
 * Purpose:
 *  Possibly commits the storage, then releases it reversing the
 *  reference count from Open.  If the reference on the storage
 *  goes to zero, the storage is forgotten.  However, the object we
 *  contain is still held and as long as it's active the storage
 *  remains alive.
 *
 * Parameters:
 *  fCommit         BOOL indicating if we're to commit.
 *
 * Return Value:
 *  None
 */

void CTenant::Close(BOOL fCommit)
    {
    if (fCommit)
        Update();

    if (NULL!=m_pIStorage)
        {
        m_pIStorage->Release();

        /*
         * We can't use a zero reference count to know when to NULL
         * this since other things might have AddRef'd the storage.
         */
        //CHAPTER17MOD
        if (0==--m_cOpens)
            {
            m_pIStorage=NULL;

            //Close the object saving if necessary
            if (NULL!=m_pIOleObject)
                {
                m_pIOleObject->Close(OLECLOSE_SAVEIFDIRTY);
                ReleaseInterface(m_pIOleObject);
                }

            //Release all other held pointers
            if (NULL!=m_pIViewObject2)
                {
                m_pIViewObject2->SetAdvise(m_fe.dwAspect, 0, NULL);
                ReleaseInterface(m_pIViewObject2);
                }

            //We know we only hold one ref from Create or Load
            ReleaseInterface(m_pObj);
            }
        //End CHAPTER17MOD
        }

    return;
    }

/*
 * CTenant::Update
 *
 * Purpose:
 *  Forces a common on the page if it's open.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the object is open, FALSE otherwise.
 */

BOOL CTenant::Update(void)
    {
    LPPERSISTSTORAGE    pIPS;

    if (NULL!=m_pIStorage)
        {
        /*
         * We need to OleSave again because we might have changed
         * the size or position of this tenant.  We also need to
         * save the rectangle on the page, since that's not known
         * to OLE.
         */
        m_pObj->QueryInterface(IID_IPersistStorage, (PPVOID)&pIPS);

        //This fails for static objects...so we improvise if so
        if (FAILED(OleSave(pIPS, m_pIStorage, TRUE)))
            {
            //This is essentially what OleSave does.
            WriteClassStg(m_pIStorage, m_clsID);
            pIPS->Save(m_pIStorage, TRUE);
            }

        pIPS->SaveCompleted(NULL);
        pIPS->Release();

        m_pIStorage->Commit(STGC_DEFAULT);
        }

    return FALSE;
    }

/*
 * CTenant::Destroy
 *
 * Purpose:
 *  Removes this page from the given storage.  The caller should
 *  eventually delete this CTenant object to free the object herein.
 *  Nothing is committed when being destroyed.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE contianing this page on which to call
 *                  DestroyElement
 *
 * Return Value:
 *  None
 */

void CTenant::Destroy(LPSTORAGE pIStorage)
    {
    OLECHAR     szTemp[32];

    if (NULL!=pIStorage)
        {
        //CHAPTER17MOD
        if (NULL!=m_pIOleObject)
            m_pIOleObject->Close(OLECLOSE_NOSAVE);
        //End CHAPTER17MOD

        if (NULL!=m_pIStorage)
            {
            //Remove all reference/open counts on this storage.
            while (0!=m_cOpens)
                {
                m_pIStorage->Release();
                m_cOpens--;
                }
            }

        GetStorageName(szTemp);
        pIStorage->DestroyElement(szTemp);

        m_pIStorage=NULL;
        }

    return;
    }

/*
 * CTenant::Select
 *
 * Purpose:
 *  Selects or deselects the tenant.
 *
 * Parameters:
 *  fSelect         BOOL indicating the new state of the tenant.
 *
 * Return Value:
 *  None
 */

void CTenant::Select(BOOL fSelect)
    {
    BOOL        fWasSelected;
    DWORD       dwState;
    RECT        rc;
    HDC         hDC;

    fWasSelected=(BOOL)(TENANTSTATE_SELECTED & m_dwState);

    //Nothing to do when there's no change.
    if (fWasSelected==fSelect)
        return;

    dwState=m_dwState & ~TENANTSTATE_SELECTED;
    m_dwState=dwState | ((fSelect) ? TENANTSTATE_SELECTED : 0);

    /*
     * Draw sizing handles to show the selection state.  We convert
     * things to MM_TEXT since that's what this function expects.
     */

    RECTFROMRECTL(rc, m_rcl);
    RectConvertMappings(&rc, NULL, TRUE);
    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

    hDC=GetDC(m_hWnd);

    UIDrawHandles(&rc, hDC, UI_HANDLES_INSIDE
        | UI_HANDLES_NOBORDER | UI_HANDLES_USEINVERSE
        , CXYHANDLE, !fWasSelected);

    ReleaseDC(m_hWnd, hDC);

    if (fSelect)
        m_pPG->m_fDirty=TRUE;

    return;
    }

//CHAPTER17MOD
/*
 * CTenant::ShowAsOpen
 *
 * Purpose:
 *  Draws or removes the hatch pattern over an object.
 *
 * Parameters:
 *  fOpen           BOOL indicating the open state of this tenant.
 *
 * Return Value:
 *  None
 */

void CTenant::ShowAsOpen(BOOL fOpen)
    {
    BOOL        fWasOpen;
    DWORD       dwState;
    RECT        rc;
    HDC         hDC;

    fWasOpen=(BOOL)(TENANTSTATE_OPEN & m_dwState);

    dwState=m_dwState & ~TENANTSTATE_OPEN;
    m_dwState=dwState | ((fOpen) ? TENANTSTATE_OPEN : 0);

    //If this was not open, then just hatch, otherwise repaint.
    if (!fWasOpen && fOpen)
        {
        RECTFROMRECTL(rc, m_rcl);
        RectConvertMappings(&rc, NULL, TRUE);
        OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

        hDC=GetDC(m_hWnd);
        UIDrawShading(&rc, hDC, UI_SHADE_FULLRECT, 0);
        ReleaseDC(m_hWnd, hDC);
        }

    if (fWasOpen && !fOpen)
        Repaint();

    return;
    }

/*
 * CTenant::ShowYourself
 *
 * Purpose:
 *  Function that really just implements IOleClientSite::ShowObject.
 *  Here we first check if the tenant is fully visible, and if so,
 *  then nothing needs to happen.  Otherwise, if the upper left
 *  corner of the tenant is in the upper left visible quadrant of
 *  the window, we'll also consider ourselves done.  Otherwise
 *  we'll put the upper left corner of the object at the upper left
 *  corner of the window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CTenant::ShowYourself(void)
    {
    RECTL       rcl;
    RECT        rc;
    POINT       pt1, pt2;

    //Scrolling deals in device units; get our rectangle in those.
    RectGet(&rcl, TRUE);

    //Get the window rectangle offset for the current scroll pos.
    GetClientRect(m_hWnd, &rc);
    OffsetRect(&rc, m_pPG->m_xPos, m_pPG->m_yPos);

    //Check if the object is already visible. (macro in bookguid.h)
    SETPOINT(pt1, (int)rcl.left,  (int)rcl.top);
    SETPOINT(pt2, (int)rcl.right, (int)rcl.bottom);

    if (PtInRect(&rc, pt1) && PtInRect(&rc, pt2))
        return;

    //Check if the upper left is within the upper left quadrant
    if (((int)rcl.left > rc.left
        && (int)rcl.left < ((rc.right+rc.left)/2))
        && ((int)rcl.top > rc.top
        && (int)rcl.top < ((rc.bottom+rc.top)/2)))
        return;

    //These are macros in INC\BOOK1632.H
    SendScrollPosition(m_hWnd, WM_HSCROLL, rcl.left-8);
    SendScrollPosition(m_hWnd, WM_VSCROLL, rcl.top-8);
    return;
    }

/*
 * CTenant::AddVerbMenu
 *
 * Purpose:
 *  Creates the variable verb menu item for the object in this
 *  tenant.
 *
 * Parmeters:
 *  hMenu           HMENU on which to add items.
 *  iPos            UINT position on that menu to add items.
 *
 * Return Value:
 *  None
 */

void CTenant::AddVerbMenu(HMENU hMenu, UINT iPos)
    {
    HMENU       hMenuTemp;
    LPOLEOBJECT pObj=m_pIOleObject;

    //If we're static, say we have no object.
    if (TENANTTYPE_STATIC==m_tType)
        pObj=NULL;

    OleUIAddVerbMenu(pObj, NULL, hMenu, iPos, IDM_VERBMIN
        , IDM_VERBMAX, TRUE, IDM_EDITCONVERT, &hMenuTemp);

    return;
    }

/*
 * CTenant::TypeGet
 *
 * Purpose:
 *  Returns the type of this tenant
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  TENANTTYPE      Type of the tenant.
 */

TENANTTYPE CTenant::TypeGet(void)
    {
    return m_tType;
    }

/*
 * CTenant::CopyEmbeddedObject
 *
 * Purpose:
 *  Copies an embedded object to the given data object (via SetData,
 *  assuming this is a data transfer object for clipboard/drag-drop)
 *  if that's what we're holding.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT in which to store the copy.
 *  pFE             LPFORMATETC into which to copy CFSTR_EMBEDDEDOBJECT
 *                  if we put that in the data object.
 *  pptl            PPOINTL to the pick point (NULL outside of
 *                  drag-drop);
 *
 * Return Value:
 *  None
 */

void CTenant::CopyEmbeddedObject(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, PPOINTL pptl)
    {
    LPPERSISTSTORAGE    pIPS;
    STGMEDIUM           stm;
    FORMATETC           fe;
    HRESULT             hr;
    UINT                cf;
    POINTL              ptl;
    SIZEL               szl;

    //Can only copy embeddings.
    if (TENANTTYPE_EMBEDDEDOBJECT!=m_tType || NULL==m_pIOleObject)
        return;

    if (NULL==pptl)
        {
        SETPOINTL(ptl, 0, 0);
        pptl=&ptl;
        }

    /*
     * Create CFSTR_EMBEDDEDOBJECT.  This is simply an IStorage with
     * a copy of the embedded object in it.  The not-so-simple part
     * is getting an IStorage to stuff it in.  For this operation
     * we'll use a temporary compound file.
     */

    stm.pUnkForRelease=NULL;
    stm.tymed=TYMED_ISTORAGE;
    hr=StgCreateDocfile(NULL, STGM_TRANSACTED | STGM_READWRITE
        | STGM_CREATE| STGM_SHARE_EXCLUSIVE | STGM_DELETEONRELEASE
        , 0, &stm.pstg);

    if (FAILED(hr))
        return;

    m_pObj->QueryInterface(IID_IPersistStorage, (PPVOID)&pIPS);

    if (NOERROR==pIPS->IsDirty())
        {
        OleSave(pIPS, stm.pstg, FALSE);
        pIPS->SaveCompleted(NULL);
        }
    else
        m_pIStorage->CopyTo(0, NULL, NULL, stm.pstg);

    pIPS->Release();

    //stm.pstg now has a copy, so stuff it away.
    cf=RegisterClipboardFormat(CFSTR_EMBEDDEDOBJECT);
    SETDefFormatEtc(fe, cf, TYMED_ISTORAGE);

    if (SUCCEEDED(pIDataObject->SetData(&fe, &stm, TRUE)))
        *pFE=fe;
    else
        ReleaseStgMedium(&stm);

    stm.tymed=TYMED_HGLOBAL;

    /*
     * You want to make sure that if this object is iconic, that you

* create the object descriptor with DVASPECT_ICON instead of
     * the more typical DVASPECT_CONTENT.  Also remember that
     * the pick point is in HIMETRIC.
     */
    XformSizeInPixelsToHimetric(NULL, (LPSIZEL)pptl, (LPSIZEL)&ptl);

    SETSIZEL(szl, (10*(m_rcl.right-m_rcl.left))
        , (10 * (m_rcl.bottom-m_rcl.top)));

    stm.hGlobal=INOLE_ObjectDescriptorFromOleObject
        (m_pIOleObject, m_fe.dwAspect, ptl, &szl);

    cf=RegisterClipboardFormat(CFSTR_OBJECTDESCRIPTOR);
    SETDefFormatEtc(fe, cf, TYMED_HGLOBAL);

    if (FAILED(pIDataObject->SetData(&fe, &stm, TRUE)))
        ReleaseStgMedium(&stm);

    return;
    }

/*
 * CTenant::NotifyOfRename
 *
 * Purpose:
 *  Instructs the tenant that the document was saved under a
 *  different name.  In order to keep the right compound document
 *  user interface, this tenant needs to tell its object through
 *  IOleObject::SetHostNames.
 *
 * Parameters:
 *  pszFile         LPTSTR of filename.
 *  pvReserved      LPVOID reserved for future use.
 *
 * Return Value:
 *  None
 */

void CTenant::NotifyOfRename(LPTSTR pszFile, LPVOID pvReserved)
    {
    TCHAR       szObj[40];
    TCHAR       szApp[40];

    if (NULL==m_pIOleObject)
        return;

    if (TEXT('\0')==*pszFile)
        {
        LoadString(m_pPG->m_hInst, IDS_UNTITLED, szObj
            , sizeof(szObj));
        }
    else
        {
        GetFileTitle(pszFile, szObj, sizeof(szObj));

       #ifndef WIN32
        //Force filenames to uppercase in DOS versions.
        AnsiUpper(szObj);
       #endif
        }

    LoadString(m_pPG->m_hInst, IDS_CAPTION, szApp, sizeof(szApp));
   #ifdef WIN32ANSI
    OLECHAR     szObjW[40], szAppW[40];

    MultiByteToWideChar(CP_ACP, 0, szObj, -1, szObjW, 40);
    MultiByteToWideChar(CP_ACP, 0, szApp, -1, szAppW, 40);
    m_pIOleObject->SetHostNames(szAppW, szObjW);
   #else
    m_pIOleObject->SetHostNames(szApp, szObj);
   #endif
    return;
    }

//End CHAPTER17MOD

/*
 * CTenant::Activate
 *
 * Purpose:
 *  Activates a verb on the object living in the tenant.  Does
 *  nothing for static objects.
 *
 * Parameters:
 *  iVerb           LONG of the verb to execute.
 *
 * Return Value:
 *  BOOL            TRUE if the object changed due to this verb
 *                  execution.
 */

BOOL CTenant::Activate(LONG iVerb)
    {
    //CHAPTER17MOD
    RECT        rc, rcH;
    CHourglass *pHour;
    SIZEL       szl;

    //Can't activate statics.
    if (TENANTTYPE_STATIC==m_tType || NULL==m_pIOleObject)
        {
        MessageBeep(0);
        return FALSE;
        }

    RECTFROMRECTL(rc, m_rcl);
    RectConvertMappings(&rc, NULL, TRUE);
    XformRectInPixelsToHimetric(NULL, &rc, &rcH);

    pHour=new CHourglass;

    //Get the server running first, then do a SetExtent, then show it
    OleRun(m_pIOleObject);

    if (m_fSetExtent)
        {
        SETSIZEL(szl, rcH.right-rcH.left, rcH.top-rcH.bottom);
        m_pIOleObject->SetExtent(m_fe.dwAspect, &szl);
        m_fSetExtent=FALSE;
        }

    m_pIOleObject->DoVerb(iVerb, NULL, m_pImpIOleClientSite, 0
        , m_hWnd, &rcH);

    delete pHour;

    //If object changes, IAdviseSink::OnViewChange will see it.
    return FALSE;
    //End CHAPTER17MOD
    }

/*
 * CTenant::Draw
 *
 * Purpose:
 *  Draws the tenant in its rectangle on the given hDC.  We assume
 *  the DC is already set up for the mapping mode in which our
 *  rectangle is expressed, since the Page we're in tells us both
 *  the rect and the hDC.
 *
 * Parameters:
 *  hDC             HDC in which to draw.  Could be a metafile,
 *                  memory DC, screen, or printer.
 *  ptd             DVTARGETDEVICE * describing the device.
 *  hIC             HDC holding an information context (printing).
 *  xOff, yOff      int offsets for the page in lometric
 *  fNoColor        BOOL indicating if we should do B & W
 *  fPrinter        BOOL indicating if we should render for a
 *                  printer.
 *
 * Return Value:
 *  None
 */

void CTenant::Draw(HDC hDC, DVTARGETDEVICE *ptd, HDC hIC
    , int xOff, int yOff, BOOL fNoColor, BOOL fPrinter)
    {
    HRESULT         hr;
    RECT            rc;
    RECTL           rcl;
    UINT            uMM;

    //CHAPTER17MOD
    //We hold IViewObject2 all the time now, so no QueryInterface
    //End CHAPTER17MOD

    RECTFROMRECTL(rc, m_rcl);
    OffsetRect(&rc, -xOff, -yOff);
    RECTLFROMRECT(rcl, rc);

    //CHAPTER17MOD
    //Repaint erases the rectangle to insure full object cleanup
    if (!fNoColor && !fPrinter)
        {
        COLORREF    cr;
        cr=SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
        ExtTextOut(hDC, rc.left, rc.top, ETO_OPAQUE, &rc, NULL
            , 0, NULL);
        SetBkColor(hDC, cr);
        }

    //We have to use Draw since we have a target device and IC.
    hr=m_pIViewObject2->Draw(m_fe.dwAspect, -1, NULL, ptd, hIC, hDC
        , &rcl, NULL, NULL, 0);

    //End CHAPTER17MOD

    /*
     * If Draw failed, then perhaps it couldn't work for the device,
     * so try good old OleDraw as a last resort.  The code will
     * generally be OLE_E_BLANK.
     */
    if (FAILED(hr))
        OleDraw(m_pObj, m_fe.dwAspect, hDC, &rc);

    //CHAPTER17MOD
    if (!fPrinter
        && (TENANTSTATE_SELECTED | TENANTSTATE_OPEN) & m_dwState)
        {
        /*
         * Draw sizing handles to show the selection state.  We
         * convert things to MM_TEXT since that's what this
         * function expects.
         */
        RectConvertMappings(&rc, NULL, TRUE);
        uMM=SetMapMode(hDC, MM_TEXT);

        if (TENANTSTATE_SELECTED & m_dwState)
            {
            UIDrawHandles(&rc, hDC, UI_HANDLES_INSIDE
                | UI_HANDLES_NOBORDER | UI_HANDLES_USEINVERSE
                , CXYHANDLE, TRUE);
            }

        if (TENANTSTATE_OPEN & m_dwState)
            UIDrawShading(&rc, hDC, UI_SHADE_FULLRECT, 0);

        uMM=SetMapMode(hDC, uMM);
        }
    //End CHAPTER17MOD

    return;
    }

/*
 * CTenant::Repaint
 * CTenant::Invalidate
 *
 * Purpose:
 *  Repaints the tenant where it lies or invalidates its area
 *  for later repainting.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CTenant::Repaint(void)
    {
    RECT        rc;
    HDC         hDC;

    //CHAPTER17MOD
    /*
     * We might be asked to repaint from
     * IOleClientSite::OnShowWindow after we've switched pages if
     * our server was running. This check on m_cOpens prevents that.
     */
    if (0==m_cOpens || !m_fRepaintEnabled)
        return;
    //End CHAPTER17MOD

    hDC=GetDC(m_hWnd);
    SetRect(&rc, m_pPG->m_xPos, m_pPG->m_yPos, 0, 0);
    RectConvertMappings(&rc, NULL, FALSE);

    SetMapMode(hDC, MM_LOMETRIC);
    Draw(hDC, NULL, NULL, rc.left, rc.top, FALSE, FALSE);

    ReleaseDC(m_hWnd, hDC);
    return;
    }

void CTenant::Invalidate(void)
    {
    RECTL       rcl;
    RECT        rc;

    RectGet(&rcl, TRUE);
    RECTFROMRECTL(rc, rcl);

    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);
    InvalidateRect(m_hWnd, &rc, FALSE);

    return;
    }

//CHAPTER17MOD
/*
 * CTenant::ObjectClassFormatAndIcon
 *
 * Purpose:
 *  Helper function for CPage::ConvertObject to retrieve necessary
 *  information about the object.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store the CLSID.
 *  pwFormat        LPWORD in which to store the clipboard format
 *                  used.
 *  ppszType        LPTSTR * in which to return a pointer to a
 *                  type string.
 *  phMetaIcon      HGLOBAL * in which to return the metafile
 *                  icon currently in use.
 *
 * Return Value:
 *  None
 */

void CTenant::ObjectClassFormatAndIcon(LPCLSID pClsID
    , LPWORD pwFormat, LPTSTR *ppszType, HGLOBAL *phMetaIcon
    , LPTSTR *ppszLabel)
    {
    HRESULT         hr;
    TCHAR           szType[128];
    LPDATAOBJECT    pIDataObject;
    FORMATETC       fe;
    STGMEDIUM       stm;

    if (TENANTTYPE_EMBEDDEDOBJECT!=m_tType || NULL==m_pIOleObject)
        return;

    if (NULL==pClsID || NULL==pwFormat || NULL==ppszType
        || NULL==phMetaIcon)
        return;

    /*
     * For embedded objects get the real CLSID of the object and
     * its format string.  If this fails then we can try to ask
     * the object, or we can look in the registry.
     */

    hr=ReadClassStg(m_pIStorage, pClsID);

    if (FAILED(hr))
        {
        hr=m_pIOleObject->GetUserClassID(pClsID);

        if (FAILED(hr))
            *pClsID=CLSID_NULL;
        }

    hr=ReadFmtUserTypeStg(m_pIStorage, pwFormat, ppszType);

    if (FAILED(hr))
        {
        *pwFormat=0;
        *ppszType=NULL;

        if (INOLE_GetUserTypeOfClass(*pClsID, 0, szType
            , sizeof(szType)))
            {
            *ppszType=INOLE_CopyString(szType);
            }
        }

    /*
     * Try to get the AuxUserType from the registry, using
     * the short version (registered under AuxUserType\2).
     * If that fails, just copy *ppszType.
     */
    *ppszLabel=NULL;

    if (INOLE_GetUserTypeOfClass(*pClsID, 2, szType
        , sizeof(szType)))
        {
        *ppszLabel=INOLE_CopyString(szType);
        }
    else
        *ppszLabel=INOLE_CopyString(*ppszType);

    //Get the icon for this thing, if we're iconic.
    *phMetaIcon=NULL;

    hr=m_pObj->QueryInterface(IID_IDataObject
        , (PPVOID)&pIDataObject);

    if (SUCCEEDED(hr))
        {
        SETFormatEtc(fe, CF_METAFILEPICT, DVASPECT_ICON, NULL
            , TYMED_MFPICT, -1);
        hr=pIDataObject->GetData(&fe, &stm);
        pIDataObject->Release();

        if (SUCCEEDED(hr))
            *phMetaIcon=stm.hGlobal;
        else
            *phMetaIcon=OleGetIconOfClass(*pClsID, NULL, TRUE);
        }

    return;
    }

/*
 * CTenant::SwitchOrUpdateAspect
 *
 * Purpose:
 *  Switches between DVASPECT_CONTENT and DVASPECT_ICON
 *
 * Parameters:
 *  hMetaIcon       HGLOBAL to the new icon if we're changing the
 *                  icon or switching to DVASPECT_ICON.  NULL to
 *                  change back to content.
 *  fPreserve       BOOL indicating if we're to preserve the old
 *                  aspect after changing.
 *
 * Return Value:
 *  BOOL            TRUE if anything changed, FALSE otherwise.
 */

BOOL CTenant::SwitchOrUpdateAspect(HGLOBAL hMetaIcon
    , BOOL fPreserve)
    {
    HRESULT     hr;
    DWORD       dwAspect;
    BOOL        fUpdate=FALSE;

    //Nothing to do if we're content already and there's no icon.
    if (NULL==hMetaIcon && DVASPECT_CONTENT==m_fe.dwAspect)
        return FALSE;

    //If we're iconic already, just cache the new icon
    if (NULL!=hMetaIcon && DVASPECT_ICON==m_fe.dwAspect)
        hr=INOLE_SetIconInCache(m_pIOleObject, hMetaIcon);
    else
        {
        //Otherwise, switch between iconic and content.
        dwAspect=(NULL==hMetaIcon) ? DVASPECT_CONTENT : DVASPECT_ICON;

        /*
         * Switch between aspects, where dwAspect has the new one
         * and m_fe.dwAspect will be changed in the process.
         */
        hr=INOLE_SwitchDisplayAspect(m_pIOleObject
            , &m_fe.dwAspect, dwAspect, hMetaIcon, !fPreserve
            , TRUE, m_pImpIAdviseSink, &fUpdate);

        if (SUCCEEDED(hr))
            {
            //Update MiscStatus for the new aspect
            m_pIOleObject->GetMiscStatus(m_fe.dwAspect, &m_grfMisc);

            if (fUpdate)
                m_pIOleObject->Update();    //This repaints.
            }
        }

    //If we switched, update our extents.
    if (SUCCEEDED(hr))
        {
        SIZEL       szl;

        m_pIOleObject->GetExtent(m_fe.dwAspect, &szl);

        if (0 > szl.cy)
            szl.cy=-szl.cy;

        //Convert HIMETRIC absolute units to our LOMETRIC mapping
        if (0!=szl.cx && 0!=szl.cy)
            SETSIZEL(szl, szl.cx/10, -szl.cy/10);

        Invalidate();                   //Remove old aspect
        SizeSet(&szl, FALSE, FALSE);    //Change size
        Repaint();                      //Paint the new one
        }

    return SUCCEEDED(hr);
    }

/*
 * CTenant::EnableRepaint
 *
 * Purpose:
 *  Toggles whether the Repaint function does anything.  This
 *  is used during conversion/emulation of an object to disable
 *  repaints until the new object can be given the proper extents.
 *
 * Parameters:
 *  fEnable         TRUE to enable repaints, FALSE to disable.
 *
 * Return Value:
 *  None
 */

void CTenant::EnableRepaint(BOOL fEnable)
    {
    m_fRepaintEnabled=fEnable;
    return;
    }
//End CHAPTER17MOD

/*
 * CTenant::ObjectGet
 *
 * Purpose:
 *  Retrieves the LPUNKNOWN of the object in use by this tenant
 *
 * Parameters:
 *  ppUnk           LPUNKNOWN * in which to return the object
 *                  pointer.
 *
 * Return Value:
 *  None
 */

void CTenant::ObjectGet(LPUNKNOWN *ppUnk)
    {
    if (NULL!=ppUnk)
        {
        *ppUnk=m_pObj;
        m_pObj->AddRef();
        }

    return;
    }

/*
 * CTenant::FormatEtcGet
 *
 * Purpose:
 *  Retrieves the FORMATETC in use by this tenant
 *
 * Parameters:
 *  pFE             LPFORMATETC in which to store the information.
 *  fPresentation   BOOL indicating if we want the real format or
 *                  that of the presentation.
 *
 * Return Value:
 *  None
 */

void CTenant::FormatEtcGet(LPFORMATETC pFE, BOOL fPresentation)
    {
    if (NULL!=pFE)
        {
        *pFE=m_fe;

        //CHAPTER17MOD
        //If there is no format, use metafile (for embedded objects)
        if (fPresentation || 0==pFE->cfFormat)
            {
            //Don't mess with dwAspect; might be icon or content.
            pFE->cfFormat=CF_METAFILEPICT;
            pFE->tymed=TYMED_MFPICT;
            }
        //End CHAPTER17MOD
        }

    return;
    }

/*
 * CTenant::SizeGet
 * CTenant::SizeSet
 * CTenant::RectGet
 * CTenant::RectSet
 *
 * Purpose:
 *  Returns or sets the size/position of the object contained here.
 *
 * Parameters:
 *  pszl/prcl       LPSIZEL (Size) or LPRECTL (Rect) with the
 *                  extents of interest.  In Get situations,
 *                  this will receive the extents; in Set it
 *                  contains the extents.
 *  fDevice         BOOL indicating that pszl/prcl is expressed
 *                  in device units.  Otherwise it's LOMETRIC.
 *  fInformObj      (Set Only) BOOL indicating if we need to inform
 *                  the object all.
 *
 * Return Value:
 *  None
 */

void CTenant::SizeGet(LPSIZEL pszl, BOOL fDevice)
    {
    if (!fDevice)
        {
        pszl->cx=m_rcl.right-m_rcl.left;
        pszl->cy=m_rcl.bottom-m_rcl.top;
        }
    else
        {
        RECT        rc;

        SetRect(&rc, (int)(m_rcl.right-m_rcl.left)
            , (int)(m_rcl.bottom-m_rcl.top), 0, 0);

        RectConvertMappings(&rc, NULL, TRUE);

        pszl->cx=(long)rc.left;
        pszl->cy=(long)rc.top;
        }

    return;
    }

//CHAPTER17MOD
void CTenant::SizeSet(LPSIZEL pszl, BOOL fDevice, BOOL fInformObj)
//End CHAPTER17MOD
    {
    SIZEL           szl;

    if (!fDevice)
        {
        szl=*pszl;
        m_rcl.right =pszl->cx+m_rcl.left;
        m_rcl.bottom=pszl->cy+m_rcl.top;
        }
    else
        {
        RECT        rc;

        SetRect(&rc, (int)pszl->cx, (int)pszl->cy, 0, 0);
        RectConvertMappings(&rc, NULL, FALSE);

        m_rcl.right =(long)rc.left+m_rcl.left;
        m_rcl.bottom=(long)rc.top+m_rcl.top;

        SETSIZEL(szl, (long)rc.left, (long)rc.top);
        }

    //Tell OLE that this object was resized.
    //CHAPTER17MOD
    if (NULL!=m_pIOleObject && fInformObj)
        {
        HRESULT     hr;
        BOOL        fRun=FALSE;

        //Convert our LOMETRIC into HIMETRIC by *=10
        szl.cx*=10;
        szl.cy*=-10;    //Our size is stored negative.

        /*
         * If the MiscStatus bit of OLEMISC_RECOMPOSEONRESIZE
         * is set, then we need to run the object before calling
         * SetExtent to make sure it has a real chance to
         * re-render the object.  We have to update and close
         * the object as well after this happens.
         */

        if (OLEMISC_RECOMPOSEONRESIZE & m_grfMisc)
            {
            if (!OleIsRunning(m_pIOleObject))
                {
                OleRun(m_pIOleObject);
                fRun=TRUE;
                }
            }

        hr=m_pIOleObject->SetExtent(m_fe.dwAspect, &szl);

        /*
         * If the object is not running and it does not have
         * RECOMPOSEONRESIZE, then SetExtent fails.  Make
         * sure that we call SetExtent again (by just calling
         * SizeSet here again) when we next run the object.
         */
        if (SUCCEEDED(hr))
            {
            m_fSetExtent=FALSE;

            if (fRun)
                {
                m_pIOleObject->Update();
                m_pIOleObject->Close(OLECLOSE_SAVEIFDIRTY);
                }
            }
        else
            {
            if (OLE_E_NOTRUNNING==GetScode(hr))
                m_fSetExtent=TRUE;
            }
        }
    //End CHAPTER17MOD

    return;
    }

void CTenant::RectGet(LPRECTL prcl, BOOL fDevice)
    {
    if (!fDevice)
        *prcl=m_rcl;
    else
        {
        RECT        rc;

        RECTFROMRECTL(rc, m_rcl);
        RectConvertMappings(&rc, NULL, TRUE);
        RECTLFROMRECT(*prcl, rc);
        }

    return;
    }

//CHAPTER17MOD
void CTenant::RectSet(LPRECTL prcl, BOOL fDevice, BOOL fInformObj)
//End CHAPTER17MOD
    {
    SIZEL   szl;
    LONG    cx, cy;

    cx=m_rcl.right-m_rcl.left;
    cy=m_rcl.bottom-m_rcl.top;

    if (!fDevice)
        m_rcl=*prcl;
    else
        {
        RECT        rc;

        RECTFROMRECTL(rc, *prcl);
        RectConvertMappings(&rc, NULL, FALSE);
        RECTLFROMRECT(m_rcl, rc);
        }

    /*
     * Tell ourselves that the size changed, if it did.  SizeSet
     * will call IOleObject::SetExtent for us.
     */
    if ((m_rcl.right-m_rcl.left)!=cx || (m_rcl.bottom-m_rcl.top)!=cy)
        {
        SETSIZEL(szl, m_rcl.right-m_rcl.left, m_rcl.bottom-m_rcl.top);
        //CHAPTER17MOD
        SizeSet(&szl, FALSE, fInformObj);
        //End CHAPTER17MOD
        }

    return;
    }

/*
 * CTenant::CreateStatic
 * (Protected)
 *
 * Purpose:
 *  Creates a new static bitmap or metafile object for this tenant
 *  using a freeloading method allowing us to specify exactly which
 *  type of data we want to paste since OleCreateStaticFromData
 *  doesn't.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *  pFE             LPFORMATETC describing the format to paste.
 *  ppObj           LPUNKNOWN * into which we store the
 *                  object pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, error code otherwise.
 */

HRESULT CTenant::CreateStatic(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, LPUNKNOWN *ppObj)
    {
    HRESULT             hr;
    STGMEDIUM           stm;
    LPUNKNOWN           pIUnknown;
    LPOLECACHE          pIOleCache;
    LPPERSISTSTORAGE    pIPersistStorage;
    CLSID               clsID;

    *ppObj=NULL;

    //Try to get the data desired as specified in pFE->cfFormat
    hr=pIDataObject->GetData(pFE, &stm);

    if (FAILED(hr))
        return hr;

    //Create the object to handle this data.
    if (CF_METAFILEPICT==pFE->cfFormat)
        clsID=CLSID_Picture_Metafile;
    else
        clsID=CLSID_Picture_Dib;

    hr=CreateDataCache(NULL, clsID, IID_IUnknown
        , (PPVOID)&pIUnknown);

    if (FAILED(hr))
        {
        ReleaseStgMedium(&stm);
        return hr;
        }

    m_clsID=clsID;

    //Stuff the data into the object
    pIUnknown->QueryInterface(IID_IPersistStorage
        , (PPVOID)&pIPersistStorage);
    pIPersistStorage->InitNew(m_pIStorage);

    //Now that we have the cache object, shove the data into it.
    pIUnknown->QueryInterface(IID_IOleCache, (PPVOID)&pIOleCache);
    pIOleCache->Cache(pFE, ADVF_PRIMEFIRST, NULL);

    hr=pIOleCache->SetData(pFE, &stm, TRUE);
    pIOleCache->Release();

    //Insure there is a persistent copy on the disk
    WriteClassStg(m_pIStorage, m_clsID);
    pIPersistStorage->Save(m_pIStorage, TRUE);
    pIPersistStorage->SaveCompleted(NULL);
    pIPersistStorage->Release();

    //The cache owns this now.
    ReleaseStgMedium(&stm);

    if (FAILED(hr))
        pIUnknown->Release();
    else
        *ppObj=pIUnknown;

    return hr;
    }


TENANT.H

/*
 * TENANT.H
 * Patron Chapter 17
 *
 * Definitions and function prototypes for the CTenant class
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _TENANT_H_
#define _TENANT_H_

//CHAPTER17MOD

class CImpIOleClientSite : public IOleClientSite
    {
    protected:
        ULONG               m_cRef;
        class CTenant      *m_pTen;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIOleClientSite(class CTenant *, LPUNKNOWN);
        ~CImpIOleClientSite(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP SaveObject(void);
        STDMETHODIMP GetMoniker(DWORD, DWORD, LPMONIKER *);
        STDMETHODIMP GetContainer(LPOLECONTAINER *);
        STDMETHODIMP ShowObject(void);
        STDMETHODIMP OnShowWindow(BOOL);
        STDMETHODIMP RequestNewObjectLayout(void);
    };

typedef CImpIOleClientSite *PCImpIOleClientSite;

class CImpIAdviseSink : public IAdviseSink
    {
    protected:
        ULONG               m_cRef;
        class CTenant      *m_pTen;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIAdviseSink(class CTenant *, LPUNKNOWN);
        ~CImpIAdviseSink(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(void)  OnDataChange(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP_(void)  OnViewChange(DWORD, LONG);
        STDMETHODIMP_(void)  OnRename(LPMONIKER);
        STDMETHODIMP_(void)  OnSave(void);
        STDMETHODIMP_(void)  OnClose(void);
    };

typedef CImpIAdviseSink *PCImpIAdviseSink;

//End CHAPTER17MOD

/*
 * Tenant class describing an individual piece of data in a page.
 * It knows where it sits, what object is inside of it, and what
 * its idenitifer is such that it can find it's storage within a
 * page.
 */

//Patron Objects clipboard format
typedef struct tagPATRONOBJECT
    {
    POINTL      ptl;        //Location of object
    POINTL      ptlPick;    //Pick point from drag-drop operation
    SIZEL       szl;        //Extents of object (absolute)
    FORMATETC   fe;         //Actual object format
    } PATRONOBJECT, *PPATRONOBJECT;

//Values for hit-testing, drawing, and resize-tracking tenants
#define CXYHANDLE       5

//Tenant types (not persistent, but determined at load time)
typedef enum
    {
    TENANTTYPE_NULL=0,
    TENANTTYPE_STATIC,
    //CHAPTER17MOD
    TENANTTYPE_EMBEDDEDOBJECT,
    TENANTTYPE_EMBEDDEDFILE,
    TENANTTYPE_EMBEDDEDOBJECTFROMDATA
    //End CHAPTER17MOD
    } TENANTTYPE, *PTENANTTYPE;

//State flags
#define TENANTSTATE_DEFAULT      0x00000000
#define TENANTSTATE_SELECTED     0x00000001

//CHAPTER17MOD
#define TENANTSTATE_OPEN         0x00000002
//End CHAPTER17MOD

/*
 * Persistent information we need to save for each tenant, which is
 * done in the tenant list instead of with each tenant.  Since this
 * is a small structure it's best not to blow another stream for it
 * (overhead).
 */
typedef struct tagTENANTINFO
    {
    DWORD       dwID;
    RECTL       rcl;
    FORMATETC   fe;             //Excludes ptd
    short       fSetExtent;     //Call IOleObject::SetExtent on Run
    } TENANTINFO, *PTENANTINFO;

class CTenant : public IUnknown
    {
    //CHAPTER17MOD
    friend CImpIOleClientSite;
    friend CImpIAdviseSink;
    //End CHAPTER17MOD

    private:
        HWND            m_hWnd;         //Pages window
        DWORD           m_dwID;         //Persistent DWORD ID
        DWORD           m_cOpens;       //Count calls to Open

        BOOL            m_fInitialized; //Something here?
        LPUNKNOWN       m_pObj;         //The object here
        LPSTORAGE       m_pIStorage;    //Sub-storage for tenant

        FORMATETC       m_fe;           //Used to create the object
        DWORD           m_dwState;      //State flags
        RECTL           m_rcl;          //Space of this object
        CLSID           m_clsID;        //Object class (for statics)
        BOOL            m_fSetExtent;   //Call SetExtent on next run

        class CPages   *m_pPG;          //Pages window

        //CHAPTER17MOD
        TENANTTYPE      m_tType;            //Type identifier
        ULONG           m_cRef;             //We're an object now
        LPOLEOBJECT     m_pIOleObject;      //IOleObject on m_pObj
        LPVIEWOBJECT2   m_pIViewObject2;    //IViewObject2 on m_pObj
        ULONG           m_grfMisc;          //OLEMISC flags
        BOOL            m_fRepaintEnabled;  //No redundant paints

        //Our interfaces
        PCImpIOleClientSite m_pImpIOleClientSite;
        PCImpIAdviseSink    m_pImpIAdviseSink;
        //End CHAPTER17MOD

    protected:
        //CHAPTER17MOD
        BOOL    ObjectInitialize(LPUNKNOWN, LPFORMATETC, DWORD);
        //End CHAPTER17MOD
        HRESULT CreateStatic(LPDATAOBJECT, LPFORMATETC
            , LPUNKNOWN *);

    public:
        CTenant(DWORD, HWND, CPages *);
        ~CTenant(void);

        //CHAPTER17MOD
        //Gotta have an IUnknown for delegation
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
        //End CHAPTER17MOD

        DWORD       GetID(void);
        UINT        GetStorageName(LPOLESTR);
        //CHAPTER17MOD
        void        StorageGet(LPSTORAGE *);
        //End CHAPTER17MOD
        UINT        Create(TENANTTYPE, LPVOID, LPFORMATETC, PPOINTL
                        , LPSIZEL, LPSTORAGE, PPATRONOBJECT, DWORD);
        BOOL        Load(LPSTORAGE, PTENANTINFO);
        void        GetInfo(PTENANTINFO);
        BOOL        Open(LPSTORAGE);
        void        Close(BOOL);
        BOOL        Update(void);
        void        Destroy(LPSTORAGE);

        void        Select(BOOL);
        //CHAPTER17MOD
        void        ShowAsOpen(BOOL);
        void        ShowYourself(void);
        void        AddVerbMenu(HMENU, UINT);
        TENANTTYPE  TypeGet(void);
        void        CopyEmbeddedObject(LPDATAOBJECT, LPFORMATETC
                        , PPOINTL);
        void        NotifyOfRename(LPTSTR, LPVOID);
        //End CHAPTER17MOD

        BOOL        Activate(LONG);
        void        Draw(HDC, DVTARGETDEVICE *, HDC, int, int
                        , BOOL, BOOL);
        void        Repaint(void);
        void        Invalidate(void);

        //CHAPTER17MOD
        void        ObjectClassFormatAndIcon(LPCLSID, LPWORD
                        , LPTSTR *, HGLOBAL *, LPTSTR *);
        BOOL        SwitchOrUpdateAspect(HGLOBAL, BOOL);
        void        EnableRepaint(BOOL);
        //End CHAPTER17MOD

        void        ObjectGet(LPUNKNOWN *);
        void        FormatEtcGet(LPFORMATETC, BOOL);
        void        SizeGet(LPSIZEL, BOOL);
        //CHAPTER17MOD
        void        SizeSet(LPSIZEL, BOOL, BOOL);
        //End CHAPTER17MOD
        void        RectGet(LPRECTL, BOOL);
        //CHAPTER17MOD
        void        RectSet(LPRECTL, BOOL, BOOL);
        //End CHAPTER17MOD
    };

typedef CTenant *PCTenant;

//Return codes for Create
#define CREATE_FAILED               0
#define CREATE_GRAPHICONLY          1
#define CREATE_PLACEDOBJECT         2

#endif //_TENANT_H_


CLIENT.CPP

/*
 * CLIENT.CPP
 * Cosmo Chapter 18
 *
 * Implementation of the CCosmoClient class that just makes sure
 * we get a CCosmoDoc on doc creation and that we initialize fully.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CCosmoClient::CCosmoClient
 * CCosmoClient::~CCosmoClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame to the frame object.
 */

CCosmoClient::CCosmoClient(HINSTANCE hInst, PCFrame pFR)
    : CClient(hInst, pFR)
    {
    return;
    }

CCosmoClient::~CCosmoClient(void)
    {
    return;
    }

/*
 * CCosmoClient::CreateCDocument
 *
 * Purpose:
 *  Constructs a new document specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::CreateCDocument(void)
    {
    return (PCDocument)(new CCosmoDoc(m_hInst, m_pFR, m_pAdv));
    }

/*
 * CCosmoClient::NewDocument
 *
 * Purpose:
 *  Small override of the CClient::NewDocument that we have just
 *  to check the initial line selection on new document creation.
 *
 * Parameters:
 *  fVisible        BOOL indicating if the document is to be
 *                  visible or not.
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::NewDocument(BOOL fVisible)
    {
    PCDocument  pDoc;

    //Perform default NewDocument first
    pDoc=CClient::NewDocument(fVisible);

    //We know that m_pFR is actually a CCosmoFrame, so type is safe.
    ((PCCosmoFrame)m_pFR)->CheckLineSelection(IDM_LINESOLID);

    return pDoc;
    }


COSMO.CPP

/*
 * COSMO.CPP
 * Cosmo Chapter 18
 *
 * WinMain and CCosmoFrame implementations.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "cosmo.h"

//CHAPTER18MOD
/*
 * These are for proper implementation of the class factory,
 * OLE Documents support, and shutdown conditions.
 */
ULONG g_cObj=0;
ULONG g_cLock=0;
HWND  g_hWnd=NULL;
BOOL  g_fUser=FALSE;
//End CHAPTER18MOD

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCCosmoFrame    pFR;
    FRAMEINIT       fi;
    WPARAM          wRet;

    SETMESSAGEQUEUE;

    //Attempt to allocate and initialize the application
    pFR=new CCosmoFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return -1;

    fi.idsMin=IDS_FRAMEMIN;
    fi.idsMax=IDS_FRAMEMAX;
    fi.idsStatMin=IDS_STATMESSAGEMIN;
    fi.idsStatMax=IDS_STATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=440;
    fi.cy=460;

    //If we can initialize pFR, start chugging messages
    if (pFR->Init(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

//CHAPTER18MOD
/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Cosmo Figure object to call when it gets
 *  destroyed.  We destroy the main window if the proper conditions
 *  are met for shutdown.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;

    //No more objects, no locks, no user control, shut the app down.
    if (0L==g_cObj && 0L==g_cLock && IsWindow(g_hWnd) && !g_fUser)
        PostMessage(g_hWnd, WM_CLOSE, 0, 0L);

    return;
    }
//CHAPTER18MOD

/*
 * CCosmoFrame::CCosmoFrame
 * CCosmoFrame::~CCosmoFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WInMain
 */

CCosmoFrame::CCosmoFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    : CFrame(hInst, hInstPrev, pszCmdLine, nCmdShow)
    {
    UINT        i;

    for (i=0; i<5; i++)
        m_hBmpLines[i]=NULL;

    m_uIDCurLine=0;
    m_fInitialized=FALSE;
    m_pIClassDataTran=NULL;

    //CHAPTER18MOD
    m_fEmbedding=FALSE;
    m_dwRegCO=0;
    m_pIClassFactory=NULL;
    //End CHAPTER18MOD

    return;
    }

CCosmoFrame::~CCosmoFrame(void)
    {
    UINT        i;

    //CHAPTER18MOD
    //Reverse CoRegisterClassObject, takes class factory ref to 1
    if (0L!=m_dwRegCO)
        CoRevokeClassObject(m_dwRegCO);

    ReleaseInterface(m_pIClassFactory);
    //End CHAPTER18MOD

    for (i=0; i<5; i++)
        {
        if (NULL!=m_hBmpLines[i])
            DeleteObject(m_hBmpLines[i]);
        }

    if (NULL!=m_pIClassDataTran)
        {
        m_pIClassDataTran->LockServer(FALSE);
        m_pIClassDataTran->Release();
        }

    OleFlushClipboard();

    if (m_fInitialized)
        OleUninitialize();

    return;
    }

/*
 * CCosmoFrame::Init
 *
 * Purpose:
 *  Call CoInitialize then calling down into the base class
 *  initialization.
 *
 * Parameters:
 *  pFI             PFRAMEINIT containing initialization parameters.
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded, FALSE otherwise.
 */

BOOL CCosmoFrame::Init(PFRAMEINIT pFI)
    {
    HRESULT     hr;

    CHECKVER_OLE;

    if (FAILED(OleInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    hr=CoGetClassObject(CLSID_DataTransferObject
        , CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory
        , (PPVOID)&m_pIClassDataTran);

    if (SUCCEEDED(hr))
        m_pIClassDataTran->LockServer(TRUE);

    //CHAPTER18MOD
    //Check for command line flags
    ParseCommandLine();

    if (NULL!=m_ppszCmdArgs)
        {
        if(0==lstrcmpi(m_ppszCmdArgs[0], TEXT("-Embedding"))
           || 0==lstrcmpi(m_ppszCmdArgs[0], TEXT("/Embedding")))
            m_fEmbedding=TRUE;
        }

    g_fUser=!m_fEmbedding;

    /*
     * Create our class factory and register it for this application
     * using CoRegisterClassObject.  The REGCLS_*USE flags have to
     * do with servicable object from this instance, not with MDI or
     * SDI.  Since it's most convenient to be single use, we'll do
     * this in either version.
     *
     * In addition, it only makes sense to do any of this if we're
     * being launched to be a server.
     */
    if (m_fEmbedding)
        {
        m_pIClassFactory=new CFigureClassFactory(this);

        if (NULL==m_pIClassFactory)
            return FALSE;

        //Since we hold on to this, we should AddRef it.
        m_pIClassFactory->AddRef();

        hr=CoRegisterClassObject(CLSID_CosmoFigure, m_pIClassFactory
            , CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE, &m_dwRegCO);

        if (FAILED(hr))
            return FALSE;
        }
    //End CHAPTER18MOD
    return CFrame::Init(pFI);
    }

/*
 * CCosmoFrame::CreateCClient
 *
 * Purpose:
 *  Constructs a new client specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient        Pointer to the new client object.
 */

PCClient CCosmoFrame::CreateCClient(void)
    {
    return (PCClient)(new CCosmoClient(m_hInst, this));
    }

/*
 * CCosmoFrame::RegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CCosmoFrame::RegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //First let the standard frame do its thing
    if (!CFrame::RegisterAllClasses())
        return FALSE;

    /*
     * We want a different background color for the document
     * because the Polyline we put in the document will paint
     * with COLOR_WINDOW which by default which is CLASSLIB's
     * default document color.
     */

    GetClassInfo(m_hInst, SZCLASSDOCUMENT, &wc);
    UnregisterClass(SZCLASSDOCUMENT, m_hInst);

    wc.hbrBackground=(HBRUSH)(COLOR_APPWORKSPACE+1);

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

    //Register the Polyline window.
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.hInstance     = m_hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PolylineWndProc;
    wc.cbWndExtra    = CBPOLYLINEWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_CROSS);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPOLYLINE;

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

    return TRUE;
    }

/*
 * CCosmoFrame::PreShowInit
 *
 * Purpose:
 *  Called from Init before intially showing the window.  We do
 *  whatever else we want here, modifying nCmdShow as necessary
 *  which affects ShowWindow in Init.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if this initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CCosmoFrame::PreShowInit(void)
    {
    CreateLineMenu();
    CheckLineSelection(IDM_LINESOLID);

    //CHAPTER18MOD
    //Save the window handle for shutdown if necessary.
    g_hWnd=m_hWnd;

    //If we're under OLE control, don't show the main window.
    if (m_fEmbedding)
        m_nCmdShow=SW_HIDE;
    //End CHAPTER18MOD

    return TRUE;
    }

/*
 * CCosmoFrame::CreateLineMenu
 *
 * Purpose:
 *  Initializes the bitmaps used to create the Line menu and
 *  replaces the text items defined in the application resources
 *  with these bitmaps.  Note that the contents of m_hBmpLines
 *  must be cleaned up when the application terminates.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CreateLineMenu(void)
    {
    HMENU       hMenu;
    HDC         hDC, hMemDC;
    HPEN        hPen;
    HGDIOBJ     hObj;
    TEXTMETRIC  tm;
    UINT        i, cx, cy;

    hMenu=GetSubMenu(GetMenu(m_hWnd), 3);   //Line menu.
    hDC=GetDC(m_hWnd);

    //Create each line in a menu item 8 chars wide, one char high.
    GetTextMetrics(hDC, &tm);
    cx=tm.tmAveCharWidth*8;
    cy=tm.tmHeight;

    /*
     * Create a memory DC in which to draw lines, and bitmaps
     * for each line.
     */
    hMemDC=CreateCompatibleDC(hDC);
    ReleaseDC(m_hWnd, hDC);

    for (i=0; i<5; i++)
        {
        m_hBmpLines[i]=CreateCompatibleBitmap(hMemDC, cx, cy);
        SelectObject(hMemDC, m_hBmpLines[i]);

        PatBlt(hMemDC, 0, 0, cx, cy, WHITENESS);

        hPen=CreatePen(i, 1, 0L);       //i=line style like PS_SOLID
        hObj=SelectObject(hMemDC, hPen);

        MoveToEx(hMemDC, 0, cy/2, NULL);
        LineTo(hMemDC, cx, cy/2);

        ModifyMenu(hMenu, IDM_LINEMIN+i, MF_BYCOMMAND | MF_BITMAP
            , IDM_LINEMIN+i, (LPTSTR)(LONG)(UINT)m_hBmpLines[i]);

        SelectObject(hMemDC, hObj);
        DeleteObject(hPen);
        }

    CheckMenuItem(hMenu, IDM_LINESOLID, MF_CHECKED);
    DeleteDC(hMemDC);

    return;
    }

/*
 * CCosmoFrame::CreateToolbar
 *
 * Purpose:
 *  Procedure to create all the necessary toolbar buttons.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of tools added to the bar.
 */

UINT CCosmoFrame::CreateToolbar(void)
    {
    UINT            iLast;
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;
    UINT            utEx  =GIZMOTYPE_BUTTONATTRIBUTEEX;

    //Insert the standard ones.
    iLast=CFrame::CreateToolbar();

    /*
     * Insert File Import in the 5th position and account for
     * it in iLast.
     */
    m_pTB->Add(utCmd, 4, IDM_FILEIMPORT, m_dxB, m_dyB
        , NULL, m_hBmp, 2, uState);
    iLast++;

    //Separator
    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    /*
     * For the Background bitmap, preserve our use of black
     * (part of the image)
     */
    m_pTB->Add(utCmd, iLast++, IDM_COLORBACKGROUND, m_dxB, m_dyB
        , NULL, m_hBmp, 3, GIZMO_NORMAL | PRESERVE_BLACK);

    m_pTB->Add(utCmd, iLast++, IDM_COLORLINE, m_dxB, m_dyB
        , NULL, m_hBmp, 4, uState);

    //Separator
    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //Line styles.
    m_pTB->Add(utEx, iLast++, IDM_LINESOLID, m_dxB, m_dyB
        , NULL, m_hBmp, 5, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASH, m_dxB, m_dyB
        , NULL, m_hBmp, 6, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 7, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASHDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 8, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASHDOTDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 9, uState);

    return iLast;
    }

/*
 * CCosmoFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the Cosmo frame window that just
 *  processes the line menu and the color menu leaving the
 *  CFrame to do everything else.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CCosmoFrame::OnCommand(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PCCosmoDoc      pDoc;
    TCHAR           szFile[CCHPATHMAX];
    BOOL            fOK;
    UINT            i, uTemp;
    COLORREF        rgColors[16];
    CHOOSECOLOR     cc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many toolbar notifications.
     */
    if (m_fInit)
        return 0L;

    pDoc=(PCCosmoDoc)m_pCL->ActiveDocument();

    /*
     * Check for the line style commands which are
     * IDM_LINEMIN+<style>.  We handle this by changing the menu
     * and toolbar, then we pass it to the document for real
     * processing.
     */
    if (NULL!=pDoc && IDM_LINEMIN <= wID && IDM_LINEMAX >=wID)
        {
        CheckLineSelection(wID);
        pDoc->LineStyleSet(wID-IDM_LINEMIN);
        return 0L;
        }

    switch (wID)
        {
        case IDM_FILEIMPORT:
            szFile[0]=0;
            fOK=SaveOpenDialog(szFile, CCHPATHMAX, IDS_FILEIMPORT
                , TRUE, &i);

            if (fOK)
                {
                uTemp=pDoc->Load(FALSE, szFile);
                pDoc->ErrorMessage(uTemp);
                }

            return (LRESULT)fOK;

        case IDM_COLORBACKGROUND:
        case IDM_COLORLINE:
            //Invoke the color chooser for either color
            uTemp=(IDM_COLORBACKGROUND==wID)
                ? DOCCOLOR_BACKGROUND : DOCCOLOR_LINE;

            for (i=0; i<16; i++)
                rgColors[i]=RGB(0, 0, i*16);

            memset(&cc, 0, sizeof(CHOOSECOLOR));
            cc.lStructSize=sizeof(CHOOSECOLOR);
            cc.lpCustColors=rgColors;
            cc.hwndOwner=hWnd;
            cc.Flags=CC_RGBINIT;
            cc.rgbResult=pDoc->ColorGet(uTemp);

            if (ChooseColor(&cc))
                pDoc->ColorSet(uTemp, cc.rgbResult);

            break;

        default:
           CFrame::OnCommand(hWnd, wParam, lParam);
        }

    return 0L;
    }

/*
 * CCosmoFrame::OnDocumentDataChange
 *
 * Purpose:
 *  Update the Line menu and toolbar if the style in the data
 *  changes.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentDataChange(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::OnDocumentActivate
 *
 * Purpose:
 *  Informs us that document activation changed, so update the UI
 *  for that new document.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentActivate(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    PCDocument  pDoc;
    BOOL        fOK=FALSE;
    BOOL        fCallDefault=TRUE;
    UINT        i;
    UINT        uTemp;
    UINT        uTempE;
    UINT        uTempD;

    pDoc=m_pCL->ActiveDocument();

    uTempE=MF_ENABLED | MF_BYCOMMAND;
    uTempD=MF_DISABLED | MF_GRAYED | MF_BYCOMMAND;
    uTemp=((NULL!=pDoc) ? uTempE : uTempD);

    //File menu:  If there is document window, disable Import.
    if (m_phMenu[0]==hMenu)
        EnableMenuItem(hMenu, IDM_FILEIMPORT, uTemp);

    //Color menu:  no document, no commands
    if (m_phMenu[2]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_COLORBACKGROUND, uTemp);
        EnableMenuItem(hMenu, IDM_COLORLINE,       uTemp);
        fCallDefault=FALSE;
        }

    //Line menu:  no document, no commands
    if (m_phMenu[3]==hMenu)
        {
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            EnableMenuItem(hMenu, i, uTemp);

        fCallDefault=FALSE;
        }

    if (fCallDefault)
        CFrame::UpdateMenus(hMenu, iMenu);

    return;
    }

/*
 * CCosmoFrame::UpdateToolbar
 *
 * Purpose:
 *  Enables and disables tools depending on whether we have
 *  a document or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateToolbar(void)
    {
    BOOL        fLast;
    UINT        i;

    //Save the last enabled state before CFrame changes it
    fLast=m_fLastEnable;

    //Let the default hack on its tools
    CFrame::UpdateToolbar();

    /*
     * If CFrame::UpdateToolbar changed anything, then we need
     * to change as well--if nothing changes, nothing to do.
     */
    if (fLast!=m_fLastEnable)
        {
        m_pTB->Enable(IDM_FILEIMPORT, m_fLastEnable);

        m_pTB->Enable(IDM_COLORBACKGROUND, m_fLastEnable);
        m_pTB->Enable(IDM_COLORLINE,       m_fLastEnable);

        for (i=IDM_LINEMIN; i <= IDM_LINEMAX; i++)
            m_pTB->Enable(i, m_fLastEnable);
        }

    return;
    }

/*
 * CCosmoFrame::CheckLineSelection
 *
 * Purpose:
 *  Maintains the bitmap menu and the tools for the line selection.
 *  Both are mutially exclusive option lists where a selection in
 *  one has to affect the other.
 *
 * Parameters:
 *  uID             UINT ID of the item to be selected
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CheckLineSelection(UINT uID)
    {
    UINT        i;
    HMENU       hMenu;

    //Update menus and tools if the selection changed.
    if (uID!=m_uIDCurLine)
        {
        m_uIDCurLine=uID;
        hMenu=GetMenu(m_hWnd);

        //Uncheck all lines initially.
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            CheckMenuItem(hMenu, i, MF_UNCHECKED | MF_BYCOMMAND);

        CheckMenuItem(hMenu, uID, MF_CHECKED | MF_BYCOMMAND);
        m_pTB->Check(uID, TRUE);
        }

    return;
    }

//CHAPTER18MOD
/*
 * CCosmoFrame::UpdateEmbeddingUI
 *
 * Purpose:
 *  Puts the application into the user interface for editing an
 *  embedded object, manipulating menus and title bars.
 *
 * Parameters:
 *  fEmbedding      BOOL TRUE to go in the mode, FALSE to leave it.
 *  pszApp          LPCTSTR name of the container application as
 *                  received in IOleObject::SetHostNames.
 *  pszObj          LPCTSTR name of the object in the container as
 *                  received in IOleObject::SetHostNames.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateEmbeddingUI(BOOL fEmbedding
    , PCDocument pDoc, LPCTSTR pszApp, LPCTSTR pszObj)
    {
    HMENU           hMenu;
    TCHAR           szTemp[256];

    //First let's play with the File menu.
    hMenu=m_phMenu[0];

    //Remove or add the File New, Open, and Save items
    if (fEmbedding)
        {
        DeleteMenu(m_phMenu[0], IDM_FILENEW,   MF_BYCOMMAND);
        DeleteMenu(m_phMenu[0], IDM_FILEOPEN,  MF_BYCOMMAND);
        DeleteMenu(m_phMenu[0], IDM_FILECLOSE, MF_BYCOMMAND);
        DeleteMenu(m_phMenu[0], IDM_FILESAVE,  MF_BYCOMMAND);

        //Save As->Save Copy As
        ModifyMenu(m_phMenu[0], IDM_FILESAVEAS, MF_BYCOMMAND
            , IDM_FILESAVEAS, PSZ(IDS_SAVECOPYAS));
        }
    else
        {
        InsertMenu(m_phMenu[0], 0, MF_BYPOSITION, IDM_FILENEW
            , PSZ(IDS_NEW));
        InsertMenu(m_phMenu[0], 1, MF_BYPOSITION, IDM_FILEOPEN
            , PSZ(IDS_OPEN));
        InsertMenu(m_phMenu[0], 2, MF_BYPOSITION, IDM_FILESAVE
            , PSZ(IDS_SAVE));
        InsertMenu(m_phMenu[0], 3, MF_BYPOSITION, IDM_FILECLOSE
            , PSZ(IDS_SAVE));

        //Save Copy As->Save As
        ModifyMenu(m_phMenu[0], IDM_FILESAVEAS, MF_BYCOMMAND
            , IDM_FILESAVEAS, PSZ(IDS_SAVEAS));
        }

    //Change "Exit" to "Exit & Return to xx" or vice-versa for SDI
    if (fEmbedding)
        wsprintf(szTemp, PSZ(IDS_EXITANDRETURN), (LPSTR)pszObj);
    else
        lstrcpy(szTemp, PSZ(IDS_EXIT));

    ModifyMenu(m_phMenu[0], IDM_FILEEXIT, MF_STRING, IDM_FILEEXIT
        , szTemp);
    DrawMenuBar(m_hWnd);

    //Now let's play with the toolbar.
    m_pTB->Show(IDM_FILENEW,   !fEmbedding);
    m_pTB->Show(IDM_FILEOPEN,  !fEmbedding);
    m_pTB->Show(IDM_FILECLOSE, !fEmbedding);
    m_pTB->Show(IDM_FILESAVE,  !fEmbedding);

    //Enable what's left appropriately.
    UpdateToolbar();

    //Now play with the title bar.

    //IDS_EMBEDDINGCAPTION is MDI/SDI sensitive in COSMO.RC.
    wsprintf(szTemp, PSZ(IDS_EMBEDDINGCAPTION), pszObj);

    /*
     * Remember that in MDI situations that Windows takes care of
     * the frame window caption bar when the document is maximized.
     */
   #ifdef MDI
    SetWindowText(pDoc->Window(), szTemp);
   #else
    SetWindowText(m_hWnd, szTemp);
   #endif

    return;
    }
//End CHAPTER18MOD


COSMO.H

/*
 * COSMO.H
 * Cosmo Chapter 18
 *
 * Single include file that pulls in everything needed for other
 * source files in the Cosmo application.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _COSMO_H_
#define _COSMO_H_

#define INC_CLASSLIB
//CHAPTER18MOD
#define INC_OLE2UI
#define CHAPTER18
//End CHAPTER18MOD
#include <inole.h>
#include "resource.h"

//Get the editor window information.
#include "polyline.h"

//COSMO.CPP:  Frame object that creates a main window

class CCosmoFrame : public CFrame
    {
    //CHAPTER18MOD
    friend class CFigureClassFactory;
    friend class CFigure;   //For UI purposes.
    //End CHAPTER18MOD

    private:
        HBITMAP         m_hBmpLines[5];     //Menu item bitmaps
        UINT            m_uIDCurLine;       //Current line selection
        BOOL            m_fInitialized;     //Did OleInitalize work?
        LPCLASSFACTORY  m_pIClassDataTran;  //For locking

        //CHAPTER18MOD
        BOOL            m_fEmbedding;       //-Embedding found?
        DWORD           m_dwRegCO;          //Registration key
        LPCLASSFACTORY  m_pIClassFactory;
        //End CHAPTER18MOD

    protected:
        //Overridable for creating a CClient for this frame
        virtual PCClient  CreateCClient(void);

        virtual BOOL      RegisterAllClasses(void);
        virtual BOOL      PreShowInit(void);
        virtual UINT      CreateToolbar(void);

        virtual LRESULT   OnCommand(HWND, WPARAM, LPARAM);
        virtual void      OnDocumentDataChange(PCDocument);
        virtual void      OnDocumentActivate(PCDocument);

        //New for this class
        virtual void      CreateLineMenu(void);

    public:
        CCosmoFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CCosmoFrame(void);

        //Overrides
        virtual BOOL      Init(PFRAMEINIT);
        virtual void      UpdateMenus(HMENU, UINT);
        virtual void      UpdateToolbar(void);

        //New for this class
        virtual void      CheckLineSelection(UINT);

        //CHAPTER18MOD
        virtual void      UpdateEmbeddingUI(BOOL, PCDocument
                              , LPCTSTR, LPCTSTR);
        //End CHAPTER18MOD
    };

typedef CCosmoFrame *PCCosmoFrame;

//CLIENT.CPP

/*
 * The only reason we have a derived class here is to override
 * CreateCDocument so we can create our own type as well as
 * overriding NewDocument to perform one other piece of work once
 * the document's been created.
 */

class CCosmoClient : public CClient
    {
    protected:
        //Overridable for creating a new CDocument
        virtual PCDocument CreateCDocument(void);

    public:
        CCosmoClient(HINSTANCE, PCFrame);
        virtual ~CCosmoClient(void);

        virtual PCDocument NewDocument(BOOL);
    };

typedef CCosmoClient *PCCosmoClient;

//DOCUMENT.CPP

//Constant ID for the window polyline that lives in a document
#define ID_POLYLINE         10

class CCosmoDoc : public CDocument
    {
    friend class CPolylineAdviseSink;

    //These need access to FQueryPasteFromData, PasteFromData
    friend class CDropTarget;
    friend class CDropSource;

    //CHAPTER18MOD
    friend class CFigureClassFactory;
    friend class CFigure;
    //End CHAPTER18MOD

    protected:
        UINT                    m_uPrevSize;    //Last WM_SIZE wParam
        LONG                    m_lVer;         //Loaded Polyline ver

        PCPolyline              m_pPL;          //Polyline window here
        PCPolylineAdviseSink    m_pPLAdv;       //Advises from Polyline

        class CDropTarget      *m_pDropTarget;  //Registered target
        BOOL                    m_fDragSource;  //Source==target?

        //CHAPTER18MOD
        UINT                    m_cfEmbedSource;
        UINT                    m_cfObjectDescriptor;

        class CFigure          *m_pFigure;      //The object in us.
        //End CHAPTER18MOD

    protected:
        virtual BOOL     FMessageHook(HWND, UINT, WPARAM, LPARAM
            , LRESULT *);

        void             DropSelectTargetWindow(void);

    public:
        CCosmoDoc(HINSTANCE, PCFrame, PCDocumentAdviseSink);
        virtual ~CCosmoDoc(void);

        virtual BOOL     Init(PDOCUMENTINIT);

        virtual void     Clear(void);

        //CHAPTER18MOD
        virtual BOOL     FDirtySet(BOOL);
        virtual BOOL     FDirtyGet(void);
        //End CHAPTER18MOD

        virtual UINT     Load(BOOL, LPTSTR);
        virtual UINT     Save(UINT, LPTSTR);

        virtual void     Undo(void);
        virtual BOOL     Clip(HWND, BOOL);
        virtual HGLOBAL  RenderFormat(UINT);
        //CHAPTER18MOD
        virtual BOOL     RenderMedium(UINT, LPSTGMEDIUM);
        //End CHAPTER18MOD
        virtual BOOL     FQueryPaste(void);
        virtual BOOL     Paste(HWND);

        //CHAPTER18MOD
        //These were protected.  Now for IOleObject, should be public.
        virtual BOOL     FQueryPasteFromData(LPDATAOBJECT);
        virtual BOOL     PasteFromData(LPDATAOBJECT);
        LPDATAOBJECT     TransferObjectCreate(BOOL);
        //End CHAPTER18MOD

        virtual COLORREF ColorSet(UINT, COLORREF);
        virtual COLORREF ColorGet(UINT);

        virtual UINT     LineStyleSet(UINT);
        virtual UINT     LineStyleGet(void);
    };

typedef CCosmoDoc *PCCosmoDoc;

//These color indices wrap the polyline definitions
#define DOCCOLOR_BACKGROUND             POLYLINECOLOR_BACKGROUND
#define DOCCOLOR_LINE                   POLYLINECOLOR_LINE

//Drag-drop interfaces we need in the document
class CDropTarget : public IDropTarget
    {
    protected:
        ULONG               m_cRef;
        PCCosmoDoc          m_pDoc;

        LPDATAOBJECT        m_pIDataObject;     //From DragEnter

    public:
        CDropTarget(PCCosmoDoc);
        ~CDropTarget(void);

        //IDropTarget interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP DragEnter(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
        STDMETHODIMP DragOver(DWORD, POINTL, LPDWORD);
        STDMETHODIMP DragLeave(void);
        STDMETHODIMP Drop(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
    };

typedef CDropTarget *PCDropTarget;

class CDropSource : public IDropSource
    {
    protected:
        ULONG               m_cRef;
        PCCosmoDoc          m_pDoc;

    public:
        CDropSource(PCCosmoDoc);
        ~CDropSource(void);

        //IDropSource interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP QueryContinueDrag(BOOL, DWORD);
        STDMETHODIMP GiveFeedback(DWORD);
    };

typedef CDropSource *PCDropSource;

//CHAPTER18MOD
//Include classes necessary to become an OLE Document server.
#include "cosmole.h"
//End CHAPTER18MOD

#endif //_COSMO_H_


COSMOLE.H

/*
 * COSMOLE.H
 * Cosmo Chapter 18
 *
 * Include file containing all compound document related
 * definitions.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _COSMOLE_H_
#define _COSMOLE_H_

//These are global for simplification of object implementation.
extern ULONG g_cObj;
extern ULONG g_cLock;
extern HWND  g_hWnd;

//Function for the object to notify on destruction.
void ObjectDestroyed(void);

//ICLASSF.CPP
class CFigureClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;
        PCCosmoFrame    m_pFR;
        BOOL            m_fCreated;     //Created one object yet?

    public:
        CFigureClassFactory(PCCosmoFrame);
        ~CFigureClassFactory(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP         CreateInstance(LPUNKNOWN, REFIID
                                 , PPVOID);
        STDMETHODIMP         LockServer(BOOL);
    };

typedef CFigureClassFactory *PCFigureClassFactory;

//FIGURE.CPP
//This is what the class factory creates

#define CFORMATETCGET   5

class CImpIPersistStorage;
typedef class CImpIPersistStorage *PCImpIPersistStorage;
class CImpIDataObject;
typedef class CImpIDataObject *PCImpIDataObject;
class CImpIOleObject;
typedef class CImpIOleObject *PCImpIOleObject;

class CFigure : public IUnknown
    {
    friend class CImpIPersistStorage;
    friend class CImpIDataObject;
    friend class CImpIOleObject;

    protected:
        ULONG               m_cRef;
        PCCosmoFrame        m_pFR;      //Frame (for UI changes)
        PCCosmoDoc          m_pDoc;     //What holds real polyline
        PCPolyline          m_pPL;      //Copy of m_pDoc->m_pPL

        BOOL                m_fEmbedded;
        PFNDESTROYED        m_pfnDestroy;

        PCStringTable       m_pST;      //Object strings

        //Things for IPersistStorage
        PCImpIPersistStorage m_pImpIPersistStorage;
        LPSTORAGE           m_pIStorage;
        LPSTREAM            m_pIStream;
        CLSID               m_clsID;

        //Things for IDataObject
        PCImpIDataObject    m_pImpIDataObject;      //Implemented
        LPDATAADVISEHOLDER  m_pIDataAdviseHolder;   //Used

        UINT                m_cf;                   //pDoc->m_cf
        ULONG               m_cfeGet;
        FORMATETC           m_rgfeGet[CFORMATETCGET];

        //Things for IOleObject
        PCImpIOleObject     m_pImpIOleObject;       //Implemented
        LPOLEADVISEHOLDER   m_pIOleAdviseHolder;    //Used
        LPOLECLIENTSITE     m_pIOleClientSite;      //Used

    public:
        CFigure(PFNDESTROYED, PCCosmoDoc);
        ~CFigure(void);

        //Non-delegating IUnknown:  we don't support aggregation
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        BOOL Init(void);
        void FrameSet(PCCosmoFrame);
        BOOL FIsDirty(void);
        BOOL FIsEmbedded(void);
        void SendAdvise(UINT);
    };

typedef CFigure *PCFigure;

//Codes for CFigure::SendAdvise
//......Code......................Method called in CFigureSendAdvise...
#define OBJECTCODE_SAVED       0  //IOleAdviseHolder::SendOnSave
#define OBJECTCODE_CLOSED      1  //IOleAdviseHolder::SendOnClose
#define OBJECTCODE_RENAMED     2  //IOleAdviseHolder::SendOnRename
#define OBJECTCODE_SAVEOBJECT  3  //IOleClientSite::SaveObject
#define OBJECTCODE_DATACHANGED 4  //IDataAdviseHolder::SendOnDataChange
#define OBJECTCODE_SHOWWINDOW  5  //IOleClientSite::OnShowWindow(TRUE)
#define OBJECTCODE_HIDEWINDOW  6  //IOleClientSite::OnShowWindow(FALSE)
#define OBJECTCODE_SHOWOBJECT  7  //IOleClientSite::ShowObject

//IPERSTOR.CPP

class CImpIPersistStorage : public IPersistStorage
    {
    protected:
        ULONG               m_cRef;
        PCFigure            m_pObj;
        LPUNKNOWN           m_pUnkOuter;
        PSSTATE             m_psState;      //Storage state
        BOOL                m_fConvert;     //Are we Converting?

    public:
        CImpIPersistStorage(PCFigure, LPUNKNOWN);
        ~CImpIPersistStorage(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP InitNew(LPSTORAGE);
        STDMETHODIMP Load(LPSTORAGE);
        STDMETHODIMP Save(LPSTORAGE, BOOL);
        STDMETHODIMP SaveCompleted(LPSTORAGE);
        STDMETHODIMP HandsOffStorage(void);
    };

//IDATAOBJ.CPP
class CImpIDataObject : public IDataObject
    {
    private:
        ULONG           m_cRef;
        PCFigure        m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIDataObject(PCFigure, LPUNKNOWN);
        ~CImpIDataObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IDataObject members
        STDMETHODIMP GetData(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP GetDataHere(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP QueryGetData(LPFORMATETC);
        STDMETHODIMP GetCanonicalFormatEtc(LPFORMATETC, LPFORMATETC);
        STDMETHODIMP SetData(LPFORMATETC, LPSTGMEDIUM, BOOL);
        STDMETHODIMP EnumFormatEtc(DWORD, LPENUMFORMATETC *);
        STDMETHODIMP DAdvise(LPFORMATETC, DWORD,  LPADVISESINK
                         , DWORD *);
        STDMETHODIMP DUnadvise(DWORD);
        STDMETHODIMP EnumDAdvise(LPENUMSTATDATA *);
    };

//IOLEOBJ.CPP
class CImpIOleObject : public IOleObject
    {
    private:
        ULONG           m_cRef;
        PCFigure        m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIOleObject(PCFigure, LPUNKNOWN);
        ~CImpIOleObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IOleObject members
        STDMETHODIMP SetClientSite(LPOLECLIENTSITE);
        STDMETHODIMP GetClientSite(LPOLECLIENTSITE *);
        STDMETHODIMP SetHostNames(LPCOLESTR, LPCOLESTR);
        STDMETHODIMP Close(DWORD);
        STDMETHODIMP SetMoniker(DWORD, LPMONIKER);
        STDMETHODIMP GetMoniker(DWORD, DWORD, LPMONIKER *);
        STDMETHODIMP InitFromData(LPDATAOBJECT, BOOL, DWORD);
        STDMETHODIMP GetClipboardData(DWORD, LPDATAOBJECT *);
        STDMETHODIMP DoVerb(LONG, LPMSG, LPOLECLIENTSITE, LONG, HWND
                         , LPCRECT);
        STDMETHODIMP EnumVerbs(LPENUMOLEVERB *);
        STDMETHODIMP Update(void);
        STDMETHODIMP IsUpToDate(void);
        STDMETHODIMP GetUserClassID(CLSID *);
        STDMETHODIMP GetUserType(DWORD, LPOLESTR *);
        STDMETHODIMP SetExtent(DWORD, LPSIZEL);
        STDMETHODIMP GetExtent(DWORD, LPSIZEL);
        STDMETHODIMP Advise(LPADVISESINK, DWORD *);
        STDMETHODIMP Unadvise(DWORD);
        STDMETHODIMP EnumAdvise(LPENUMSTATDATA *);
        STDMETHODIMP GetMiscStatus(DWORD, DWORD *);
        STDMETHODIMP SetColorScheme(LPLOGPALETTE);
    };

#endif _COSMOLE_H_


DOCUMENT.CPP

/*
 * DOCUMENT.CPP
 * Cosmo Chapter 18
 *
 * Implementation of the CCosmoDoc derivation of CDocument as
 * well as an implementation of CPolylineAdviseSink.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CCosmoDoc::CCosmoDoc
 * CCosmoDoc::~CCosmoDoc
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame of the frame object.
 *  pAdv            PCDocumentAdviseSink to notify on events
 */

CCosmoDoc::CCosmoDoc(HINSTANCE hInst, PCFrame pFR
    , PCDocumentAdviseSink pAdv)
    : CDocument(hInst, pFR, pAdv)
    {
    m_pPL=NULL;
    m_pPLAdv=NULL;
    m_uPrevSize=SIZE_RESTORED;

    m_pDropTarget=NULL;
    m_fDragSource=FALSE;

    //CHAPTER18MOD
    m_cfEmbedSource=RegisterClipboardFormat(CFSTR_EMBEDSOURCE);
    m_cfObjectDescriptor=RegisterClipboardFormat
        (CFSTR_OBJECTDESCRIPTOR);

    m_pFigure=NULL;
    //End CHAPTER18MOD
    return;
    }

CCosmoDoc::~CCosmoDoc(void)
    {
    //CHAPTER18MOD
    //Make sure the object is saved
    m_pFigure->SendAdvise(OBJECTCODE_SAVEOBJECT);
    m_pFigure->SendAdvise(OBJECTCODE_HIDEWINDOW);
    m_pFigure->SendAdvise(OBJECTCODE_CLOSED);

    /*
     * The AddRef here preserves a reference count on the object
     * even after our call to CoDisconnectObject.  When we then
     * Release that reference count, the figure will start
     * shutdown.  In some cases shutdown will begin before this
     * time in which case we need to clean up before the
     * Figure is destroyed, so we delete the figure ourselves.
     * This could also be handled with IExternalConnection.
     */

    if (NULL!=m_pFigure)
        {
        m_pFigure->AddRef();
        CoDisconnectObject(m_pFigure, 0L);
        m_pFigure->Release();   //Starts shutdown if necessary.
        delete m_pFigure;
        }
    //End CHAPTER18MOD

    //Clean up the allocations we did in Init
    if (NULL!=m_pPL)
        delete m_pPL;

    if (NULL!=m_pPLAdv)
        delete m_pPLAdv;

    return;
    }

/*
 * CCosmoDoc::Init
 *
 * Purpose:
 *  Initializes an already created document window.  The client
 *  actually creates the window for us, then passes that here for
 *  further initialization.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CCosmoDoc::Init(PDOCUMENTINIT pDI)
    {
    RECT        rc;

    //Change the stringtable range to our customization.
    pDI->idsMin=IDS_DOCUMENTMIN;
    pDI->idsMax=IDS_DOCUMENTMAX;

    //Do default initialization
    if (!CDocument::Init(pDI))
        return FALSE;

    //Add the Polyline stuff we need.
    m_pPLAdv=new CPolylineAdviseSink(this);
    m_pPL   =new CPolyline(m_hInst);

    //Attempt to create our contained Polyline.
    GetClientRect(m_hWnd, &rc);
    InflateRect(&rc, -8, -8);

    if (!m_pPL->Init(m_hWnd, &rc, WS_CHILD | WS_VISIBLE
        , ID_POLYLINE, m_pPLAdv))
        return FALSE;

    m_pDropTarget=new CDropTarget(this);

    if (NULL!=m_pDropTarget)
        {
        m_pDropTarget->AddRef();
        CoLockObjectExternal(m_pDropTarget, TRUE, FALSE);
        RegisterDragDrop(m_hWnd, m_pDropTarget);
        }

    //CHAPTER18MOD
    m_pFigure=new CFigure(ObjectDestroyed, this);

    if (NULL==m_pFigure)
        return FALSE;

    //We created an object, so count it.
    g_cObj++;

    if (!m_pFigure->Init())
        return FALSE;
    //End CHAPTER18MOD

    return TRUE;
    }

/*
 * CCosmoDoc::FMessageHook
 *
 * Purpose:
 *  Processes WM_SIZE for the document so we can resize
 *  the Polyline.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CCosmoDoc::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    UINT        dx, dy;
    RECT        rc;

    *pLRes=0;

    if (WM_SIZE==iMsg)
        {
        //Don't effect the Polyline size to or from minimized state.
        if (SIZE_MINIMIZED!=wParam && SIZE_MINIMIZED !=m_uPrevSize)
            {
            //When we change size, resize any Polyline we hold.
            dx=LOWORD(lParam);
            dy=HIWORD(lParam);

            /*
             * If we are getting WM_SIZE in response to a Polyline
             * notification, then don't resize the Polyline window
             * again.
             */
            if (!m_fNoSize && NULL!=m_pPL)
                {
                //Resize the polyline to fit the new client
                SetRect(&rc, 8, 8, dx-8, dy-8);
                m_pPL->RectSet(&rc, FALSE);

                /*
                 * We consider sizing something that makes the file
                 * dirty, but not until we've finished the create
                 * process, which is why we set fNoDirty to FALSE
                 * in WM_CREATE since we get a WM_SIZE on the first
                 * creation.
                 */
                if (!m_fNoDirty)
                    FDirtySet(TRUE);

                SetRect(&rc, 0, 0, dx, dy);

                if (NULL!=m_pAdv)
                    m_pAdv->OnSizeChange(this, &rc);

                m_fNoDirty=FALSE;
                }
            }

        m_uPrevSize=wParam;
        }

    if (WM_LBUTTONDOWN==iMsg)
        {
        LPDROPSOURCE    pIDropSource;
        LPDATAOBJECT    pIDataObject;
        HRESULT         hr;
        SCODE           sc;
        DWORD           dwEffect;

        /*
         * The document has an 8 pixel border around the polyline
         * window where we'll see mouse clicks.  A left mouse button
         * click here means the start of a drag-drop operation.
         *
         * Since this is a modal operation, this IDropSource
         * is entirely local.
         */

        pIDropSource=new CDropSource(this);

        if (NULL==pIDropSource)
            return FALSE;

        pIDropSource->AddRef();
        m_fDragSource=TRUE;

        //Go get the data and start the ball rolling.
        pIDataObject=TransferObjectCreate(FALSE);

        if (NULL!=pIDataObject)
            {
            hr=DoDragDrop(pIDataObject, pIDropSource
                , DROPEFFECT_COPY | DROPEFFECT_MOVE, &dwEffect);

            pIDataObject->Release();
            sc=GetScode(hr);
            }
        else
            sc=E_FAIL;

        /*
         * When we return from DoDragDrop, either cancel or drop.
         * First toss the IDropSource we have here, then bail out
         * on cancel, and possibly clear our data on a move drop.
         */

        pIDropSource->Release();

        /*
         * If dropped on the same document (determined using
         * this flag, then dwEffect will be DROPEFFECT_NONE (see
         * IDropTarget::Drop in DROPTGT.CPP).  In any case,
         * reset this since the operation is done.
         */

        m_fDragSource=FALSE;

        if (DRAGDROP_S_DROP==sc && DROPEFFECT_MOVE==dwEffect)
            {
            m_pPL->New();
            FDirtySet(TRUE);
            }

        //On a canceled drop or a copy we don't do anything else
        return TRUE;
        }

    if (WM_DESTROY==iMsg)
        {
        /*
         * We have to revoke the drop target here because the window
         * will be destroyed and the property forcefully removed
         * before we could do this in the destructor.
         */
        if (NULL!=m_pDropTarget)
            {
            RevokeDragDrop(m_hWnd);
            CoLockObjectExternal(m_pDropTarget, FALSE, TRUE);
            ReleaseInterface(m_pDropTarget);
            }

        return FALSE;
        }

    /*
     * We return FALSE even on WM_SIZE so we can let the default
     * procedure handle maximized MDI child windows appropriately.
     */
    return FALSE;
    }

/*
 * CCosmoDoc::Clear
 *
 * Purpose:
 *  Sets all contents in the document back to defaults with
 *  no filename.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Clear(void)
    {
    //Completely reset the polyline
    m_pPL->New();

    CDocument::Clear();
    m_lVer=0;
    return;
    }

//CHAPTER18MOD
/*
 * CCosmoDoc::FDirtySet
 *
 * Purpose:
 *  Sets or clears the document 'dirty' flag returning the previous
 *  state of that same flag.  We override this in Cosmo server to
 *  send the OnDataChange notification as necessary.
 *
 * Parameters:
 *  fDirty          BOOL indicating the new contents of the dirty
 *                  flag.
 *
 * Return Value:
 *  BOOL            Previous value of the dirty flag.
 */

BOOL CCosmoDoc::FDirtySet(BOOL fDirty)
    {
    BOOL        fRet;

    fRet=CDocument::FDirtySet(fDirty);
    m_pFigure->SendAdvise(OBJECTCODE_DATACHANGED);

    return fRet;
    }

/*
 * CCosmoDoc::FDirtyGet
 *
 * Purpose:
 *  Override of the normal FDirtyGet such that we never return dirty
 *  for an embedded object we're serving since updates constantly
 *  happen and since the object will be saved on closure.  This then
 *  prevents any message boxes asking the user to save.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the document is dirty, FALSE otherwise.
 */

BOOL CCosmoDoc::FDirtyGet(void)
    {
    if (m_pFigure->FIsEmbedded())
        return FALSE;

    return m_fDirty;
    }
//End CHAPTER18MOD

/*
 * CCosmoDoc::Load
 *
 * Purpose:
 *  Loads a given document without any user interface overwriting
 *  the previous contents of the Polyline window.  We do this by
 *  opening the file and telling the Polyline to load itself from
 *  that file.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load, NULL if the file
 *                  is new and untitled.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CCosmoDoc::Load(BOOL fChangeFile, LPTSTR pszFile)
    {
    HRESULT         hr;
    LPSTORAGE       pIStorage;

    if (NULL==pszFile)
        {
        //For a new untitled document, just rename ourselves.
        Rename(NULL);
        m_lVer=VERSIONCURRENT;
        return DOCERR_NONE;
        }

    /*
     * If not a Compound File, open the file using STGM_CONVERT in
     * transacted mode to see old files as a storage with one stream
     * called "CONTENTS" (which is conveniently the name we use
     * in the new files).  We must use STGM_TRANSACTED here or else
     * the old file will be immediately converted on disk:  we only
     * want a converted image in memory from which to read.  In
     * addition, note that we need STGM_READWRITE as well since
     * conversion is inherently a write operation.
     */

    pIStorage=NULL;

    if (NOERROR!=StgIsStorageFile(pszFile))
        {
        hr=StgCreateDocfile(pszFile,STGM_TRANSACTED | STGM_READWRITE
            | STGM_CONVERT | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

        if (FAILED(hr))
            {
            //If denied write access, try to load the old way
            if (STG_E_ACCESSDENIED==GetScode(hr))
                m_lVer=m_pPL->ReadFromFile(pszFile);
            else
                return DOCERR_COULDNOTOPEN;
            }
        }
    else
        {
        hr=StgOpenStorage(pszFile, NULL, STGM_DIRECT | STGM_READ
            | STGM_SHARE_EXCLUSIVE, NULL, 0, &pIStorage);

        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;
        }

    if (NULL!=pIStorage)
        {
        m_lVer=m_pPL->ReadFromStorage(pIStorage);
        pIStorage->Release();
        }

    if (POLYLINE_E_READFAILURE==m_lVer)
        return DOCERR_READFAILURE;

    if (POLYLINE_E_UNSUPPORTEDVERSION==m_lVer)
        return DOCERR_UNSUPPORTEDVERSION;

    if (fChangeFile)
        Rename(pszFile);

    //Importing a file makes things dirty
    FDirtySet(!fChangeFile);

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::Save
 *
 * Purpose:
 *  Writes the file to a known filename, requiring that the user has
 *  previously used FileOpen or FileSaveAs to provide a filename.
 *
 * Parameters:
 *  uType           UINT indicating the type of file the user
 *                  requested to save in the File Save As dialog.
 *  pszFile         LPTSTR under which to save.  If NULL, use the
 *                  current name.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CCosmoDoc::Save(UINT uType, LPTSTR pszFile)
    {
    LONG        lVer, lRet;
    UINT        uTemp;
    BOOL        fRename=TRUE;
    HRESULT     hr;
    LPSTORAGE   pIStorage;
    //CHAPTER18MOD
    BOOL        fEmbedding;

    fEmbedding=m_pFigure->FIsEmbedded();
    //End CHAPTER18MOD

    if (NULL==pszFile)
        {
        fRename=FALSE;
        pszFile=m_szFile;
        }

    /*
     * Type 1 is the current version, type 2 is version 1.0 of the
     * Polyline so we use this to send the right version to
     * CPolyline::WriteToFile/WriteToStorage.
     */

    switch (uType)
        {
        case 0:         //From Save, use loaded version.
            lVer=m_lVer;
            break;

        case 1:
            lVer=VERSIONCURRENT;
            break;

        case 2:
            lVer=MAKELONG(0, 1);    //1.0
            break;

        default:
            return DOCERR_UNSUPPORTEDVERSION;
        }

    /*
     * If the version the user wants to save is different from the
     * version that we loaded and m_lVer is not zero (new doc),
     * then inform the user of the version change and verify.
     */

    //CHAPTER18MOD
    //For embedding, this is Save Copy As, so don't ask about versions.
    if (0!=m_lVer && m_lVer!=lVer && !fEmbedding)
    //End CHAPTER18MOD
        {
        TCHAR       szMsg[128];

        wsprintf(szMsg, PSZ(IDS_VERSIONCHANGE)
            , (UINT)HIWORD(m_lVer), (UINT)LOWORD(m_lVer)
            , (UINT)HIWORD(lVer), (UINT)LOWORD(lVer));

        uTemp=MessageBox(m_hWnd, szMsg, PSZ(IDS_DOCUMENTCAPTION)
            , MB_YESNOCANCEL);

        if (IDCANCEL==uTemp)
            return DOCERR_CANCELLED;

        //If the user won't upgrade, revert to loaded version.
        if (IDNO==uTemp)
            lVer=m_lVer;
        }

    /*
     * For 1.0 files, still use the old code.  For new files, use
     * storages instead
     */
    if (lVer==MAKELONG(0, 1))
        lRet=m_pPL->WriteToFile(pszFile, lVer);
    else
        {
        hr=StgCreateDocfile(pszFile, STGM_DIRECT | STGM_READWRITE
            | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;

        //Mark this as one of our class
        WriteClassStg(pIStorage, CLSID_CosmoFigure);

        //Write user-readable class information
        WriteFmtUserTypeStg(pIStorage, m_cf
            , PSZ(IDS_CLIPBOARDFORMAT));

        lRet=m_pPL->WriteToStorage(pIStorage, lVer);
        pIStorage->Release();
        }

    if (POLYLINE_E_NONE!=lRet)
        return DOCERR_WRITEFAILURE;

    //CHAPTER18MOD
    //Saving makes us clean, but this doesn't apply to embedding.
    if (!fEmbedding)
        FDirtySet(FALSE);
    //End CHAPTER18MOD

    //Update the known version of this document.
    m_lVer=lVer;

    //CHAPTER18MOD
    /*
     * If we're embedding, this is Save Copy As, so no rename.
     * Note that we also don't care about having been set to clean
     * since we're always 'clean' as an embedded object from
     * the user's perspective.
     */
    if (fRename && !fEmbedding)
        Rename(pszFile);
    //End CHAPTER18MOD

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::Undo
 *
 * Purpose:
 *  Reverses a previous action.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Undo(void)
    {
    m_pPL->Undo();
    return;
    }

/*
 * CCosmoDoc::Clip
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard, optionally implementing Cut by deleting the
 *  data in the current window after rendering.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *  fCut            BOOL indicating cut (TRUE) or copy (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::Clip(HWND hWndFrame, BOOL fCut)
    {
    BOOL            fRet=TRUE;
    LPDATAOBJECT    pIDataObject;

    pIDataObject=TransferObjectCreate(fCut);

    if (NULL==pIDataObject)
        return FALSE;

    fRet=SUCCEEDED(OleSetClipboard(pIDataObject));
    pIDataObject->Release();

    //Delete our current data if "cut" succeeded.
    if (fRet && fCut)
        {
        m_pPL->New();
        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CCosmoDoc::RenderFormat
 *
 * Purpose:
 *  Renders a specific clipboard format into global memory.
 *
 * Parameters:
 *  cf              UINT format to render.
 *
 * Return Value:
 *  HGLOBAL         Global memory handle containing the data.
 */

HGLOBAL CCosmoDoc::RenderFormat(UINT cf)
    {
    HGLOBAL     hMem;

    if (cf==m_cf)
        {
        m_pPL->DataGetMem(VERSIONCURRENT, &hMem);
        return hMem;
        }

    switch (cf)
        {
        case CF_METAFILEPICT:
            return m_pPL->RenderMetafilePict();

        case CF_BITMAP:
            return (HGLOBAL)m_pPL->RenderBitmap();
        }

    return NULL;
    }

//CHAPTER18MOD
/*
 * CCosmoDoc::RenderMedium
 *
 * Purpose:
 *  Like RenderFormat, this function creates a specific data format
 *  based on the cf parameter.  Unlike RenderFormat, we store the
 *  result in a STGMEDIUM in case it has a medium other than
 *  TYMED_HGLOBAL.  For conveniece we'll centralize all compound
 *  document formats here, hGlobal or not.
 *
 * Parameters:
 *  cf              UINT clipboard format of interest.
 *  pSTM            LSTGMEDIUM to fill.  We only fill the union
 *                  and tymed.
 *
 * Return Value:
 *  BOOL            TRUE if we could render the format,
 *                  FALSE otherwise.
 */

BOOL CCosmoDoc::RenderMedium(UINT cf, LPSTGMEDIUM pSTM)
    {
    if (NULL==pSTM)
        return FALSE;

    if (cf==m_cfEmbedSource)
        {
        /*
         * Embed Source data is an IStorage containing the native
         * data (same as Embedded Object).  Since our data is small,
         * it makes the most sense to create an IStorage in memory
         * and put transfer that instead of a disk-based IStorage.
         */

        pSTM->pstg=INOLE_CreateStorageOnHGlobal(STGM_DIRECT
            | STGM_READWRITE | STGM_SHARE_EXCLUSIVE);

        if (NULL==pSTM->pstg)
            return FALSE;

        //Now save the data to the storage.
        WriteClassStg(pSTM->pstg, CLSID_CosmoFigure);
        WriteFmtUserTypeStg(pSTM->pstg, m_cf
            , PSZ(IDS_CLIPBOARDFORMAT));

        if (POLYLINE_E_NONE!=m_pPL->WriteToStorage(pSTM->pstg
            , VERSIONCURRENT))
            {
            /*
             * When someone releases the IStorage, STORAGE.DLL will
             * release the ILockBytes which, having fDeleteOnRelease
             * TRUE (second parameter) will release the memory.
             * That's why we don't have STGM_DELETEONRELEASE on the
             * IStorage.
             */
            pSTM->pstg->Release();
            return FALSE;
            }

        pSTM->tymed=TYMED_ISTORAGE;
        return TRUE;
        }

    if (cf==m_cfObjectDescriptor)
        {
        SIZEL   szl, szlT;
        POINTL  ptl;
        RECT    rc;

        m_pPL->SizeGet(&rc);
        SETSIZEL(szlT, rc.right, rc.bottom);
        XformSizeInPixelsToHimetric(NULL, &szlT, &szl);

        SETPOINTL(ptl, 0, 0);

        pSTM->hGlobal=INOLE_AllocObjectDescriptor
            (CLSID_CosmoFigure, DVASPECT_CONTENT, szl, ptl
            , OLEMISC_RECOMPOSEONRESIZE, PSZ(IDS_OBJECTDESCRIPTION)
            , NULL);

        pSTM->tymed=TYMED_HGLOBAL;
        return (NULL!=pSTM->hGlobal);
        }

    return FALSE;
    }

//End CHAPTER18MOD

/*
 * CCosmoDoc::FQueryPaste
 *
 * Purpose:
 *  Determines if we can paste data from the clipboard.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CCosmoDoc::FQueryPaste(void)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    fRet=FQueryPasteFromData(pIDataObject);
    pIDataObject->Release();
    return fRet;
    }

/*
 * CCosmoDoc::FQueryPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Determines if we can paste data from a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which we might want to paste.
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CCosmoDoc::FQueryPasteFromData(LPDATAOBJECT pIDataObject)
    {
    FORMATETC       fe;

    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    return (NOERROR==pIDataObject->QueryGetData(&fe));
    }

/*
 * CCosmoDoc::Paste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  to the current figure in the editor window.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::Paste(HWND hWndFrame)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    fRet=PasteFromData(pIDataObject);
    pIDataObject->Release();

    return fRet;
    }

/*
 * CCosmoDoc::PasteFromData
 * (Protected)
 *
 * Purpose:
 *  Retrieves the private data format from a data object and sets
 *  it to the current figure in the editor window.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::PasteFromData(LPDATAOBJECT pIDataObject)
    {
    FORMATETC       fe;
    STGMEDIUM       stm;
    BOOL            fRet;

    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    fRet=SUCCEEDED(pIDataObject->GetData(&fe, &stm));

    if (fRet && NULL!=stm.hGlobal)
        {
        m_pPL->DataSetMem(stm.hGlobal, FALSE, FALSE, TRUE);
        ReleaseStgMedium(&stm);
        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CCosmoDoc::TransferObjectCreate
 * (Protected)
 *
 * Purpose:
 *  Creates a DataTransferObject and stuffs the current Polyline
 *  data into it, used for both clipboard and drag-drop operations.
 *
 * Parameters:
 *  fCut            BOOL TRUE if we're cutting, FALSE if we're
 *                  copying.
 *
 * Return Value:
 *  LPDATAOBJECT    Pointer to the object created, NULL on failure
 */

LPDATAOBJECT CCosmoDoc::TransferObjectCreate(BOOL fCut)
    {
    UINT            i;
    HRESULT         hr;
    STGMEDIUM       stm;
    FORMATETC       fe;
    LPDATAOBJECT    pIDataObject=NULL;
    //CHAPTER18MOD
    const UINT      cFormats=5;
    static UINT     rgcf[5]={0, 0, 0, CF_METAFILEPICT, CF_BITMAP};
    static DWORD    rgtm[5]={TYMED_HGLOBAL, TYMED_ISTORAGE
                        , TYMED_HGLOBAL, TYMED_MFPICT, TYMED_GDI};
    //CHAPTER18MOD

    hr=CoCreateInstance(CLSID_DataTransferObject, NULL
        , CLSCTX_INPROC_SERVER, IID_IDataObject
        , (PPVOID)&pIDataObject);

    if (FAILED(hr))
        return NULL;

    rgcf[0]=m_cf;
    //CHAPTER18MOD
    rgcf[1]=m_cfEmbedSource;
    rgcf[2]=m_cfObjectDescriptor;
    //End CHAPTER18MOD

    for (i=0; i < cFormats; i++)
        {
        //CHAPTER18MOD
        /*
         * RenderFormat handles memory handles, but for compound doc
         * formats we need something more.  So if RenderFormat fails
         * (which it will for i=1, try our latest addition which
         * writes to a different field in the STGMEDIUM.
         */
        stm.hGlobal=RenderFormat(rgcf[i]);

        if (NULL==stm.hGlobal)
            {
            if (!RenderMedium(rgcf[i], &stm))
                continue;
            }

        stm.tymed=rgtm[i];
        stm.pUnkForRelease=NULL;
        SETDefFormatEtc(fe, rgcf[i], rgtm[i]);
        pIDataObject->SetData(&fe, &stm, TRUE);
        //End CHAPTER18MOD
        }

    return pIDataObject;    //Caller now responsible
    }

/*
 * CCosmoDoc::DropSelectTargetWindow
 * (Protected)
 *
 * Purpose:
 *  Creates a thin inverted frame around a window that we use to
 *  show the window as a drop target.  This is a toggle function:
 *  It uses XOR to create the effect so it must be called twice to
 *  leave the window as it was.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::DropSelectTargetWindow(void)
    {
    HDC         hDC;
    RECT        rc;
    UINT        dd=3;
    HWND        hWnd;

    hWnd=m_pPL->Window();
    hDC=GetWindowDC(hWnd);
    GetClientRect(hWnd, &rc);

    //Frame this window with inverted pixels

    //Top
    PatBlt(hDC, rc.left, rc.top, rc.right-rc.left, dd, DSTINVERT);

    //Bottom
    PatBlt(hDC, rc.left, rc.bottom-dd, rc.right-rc.left, dd
        , DSTINVERT);

    //Left excluding regions already affected by top and bottom
    PatBlt(hDC, rc.left, rc.top+dd, dd, rc.bottom-rc.top-(2*dd)
        , DSTINVERT);

    //Right excluding regions already affected by top and bottom
    PatBlt(hDC, rc.right-dd, rc.top+dd, dd, rc.bottom-rc.top-(2*dd)
        , DSTINVERT);

    ReleaseDC(hWnd, hDC);
    return;
    }

/*
 * CCosmoDoc::ColorSet
 *
 * Purpose:
 *  Changes a color used in our contained Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color.
 *
 * Return Value:
 *  COLORREF        Previous color for the given index.
 */

COLORREF CCosmoDoc::ColorSet(UINT iColor, COLORREF cr)
    {
    return m_pPL->ColorSet(iColor, cr);
    }

/*
 * CCosmoDoc::ColorGet
 *
 * Purpose:
 *  Retrieves a color currently in use in the Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to retrieve.
 *
 * Return Value:
 *  COLORREF        Current color for the given index.
 */

COLORREF CCosmoDoc::ColorGet(UINT iColor)
    {
    return m_pPL->ColorGet(iColor);
    }

/*
 * CCosmoDoc::LineStyleSet
 *
 * Purpose:
 *  Changes the line style currently used in the Polyline
 *
 * Parameters:
 *  iStyle          UINT index of the new line style to use.
 *
 * Return Value:
 *  UINT            Previous line style.
 */

UINT CCosmoDoc::LineStyleSet(UINT iStyle)
    {
    return m_pPL->LineStyleSet(iStyle);
    }

/*
 * CCosmoDoc::LineStyleGet
 *
 * Purpose:
 *  Retrieves the line style currently used in the Polyline
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Current line style.

*/

UINT CCosmoDoc::LineStyleGet(void)
    {
    if (NULL==m_pPL)    //m_pPL might not be valid yet
        return 0L;

    return m_pPL->LineStyleGet();
    }

/*
 * CPolylineAdviseSink::CPolylineAdviseSink
 * CPolylineAdviseSink::~CPolylineAdviseSink
 *
 * Constructor Parameters:
 *  pv              LPVOID to store in this object
 */

CPolylineAdviseSink::CPolylineAdviseSink(LPVOID pv)
    {
    m_pv=pv;
    return;
    }

CPolylineAdviseSink::~CPolylineAdviseSink(void)
    {
    return;
    }

/*
 * CPolylineAdviseSink::OnPointChange
 *
 * Purpose:
 *  Informs the document that the polyline added or removed a point.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnPointChange(void)
    {
    PCDocument      pDoc=(PCDocument)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnSizeChange
 *
 * Purpose:
 *  Informs the document that the polyline changed size.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnSizeChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;
    RECT            rc;
    DWORD           dwStyle;

    /*
     * Polyline window is informing us that it changed size in
     * response to setting it's data.  Therefore we have to
     * size ourselves accordingly but without moving the screen
     * position of the polyline window.
     */

    pDoc->m_fNoSize=TRUE;

    //Set the document window size.
    GetWindowRect(pDoc->m_pPL->Window(), &rc);
    InflateRect(&rc, 8, 8);

    //Adjust for a window sans menu
    dwStyle=GetWindowLong(pDoc->m_hWnd, GWL_STYLE);
    AdjustWindowRect(&rc, dwStyle, FALSE);

    SetWindowPos(pDoc->m_hWnd, NULL, 0, 0, rc.right-rc.left
        , rc.bottom-rc.top, SWP_NOMOVE | SWP_NOZORDER);

    if (NULL!=pDoc->m_pAdv)
        pDoc->m_pAdv->OnSizeChange(pDoc, &rc);

    pDoc->m_fNoSize=FALSE;
    pDoc->FDirtySet(TRUE);

    return;
    }

/*
 * CPolylineAdviseSink::OnDataChange
 *
 * Purpose:
 *  Informs the document that the polyline data changed.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnDataChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    if (NULL!=pDoc->m_pAdv)
        pDoc->m_pAdv->OnDataChange(pDoc);

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnColorChange
 *
 * Purpose:
 *  Informs the document that the polyline data changed a color.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnColorChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnLineStyleChange
 *
 * Purpose:
 *  Informs the document that the polyline changed its line style.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnLineStyleChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }


DROPSRC.CPP

/*
 * DROPSRC.CPP
 * Cosmo Chapter 18
 *
 * Implementation of a DropSource object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CDropSource::CDropSource
 * CDropSource::~CDropSource
 *
 * Constructor Parameters:
 *  pDoc            PCosmoDoc containing this interface.
 */

CDropSource::CDropSource(PCCosmoDoc pDoc)
    {
    m_cRef=0;
    m_pDoc=pDoc;
    return;
    }

CDropSource::~CDropSource(void)
    {
    return;
    }

/*
 * CDropSource::QueryInterface
 * CDropSource::AddRef
 * CDropSource::Release
 *
 * Purpose:
 *  IUnknown members for CDropSource object.
 */

STDMETHODIMP CDropSource::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDropSource==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDropSource::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropSource::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDropSource::QueryDragContinue
 *
 * Purpose:
 *  Determines whether to continue a drag operation or cancel it.
 *
 * Parameters:
 *  fEsc            BOOL indicating that the ESC key was pressed.
 *  grfKeyState     DWORD providing states of keys and mouse buttons
 *
 * Return Value:
 *  HRESULT         DRAGDROP_S_CANCEL to stop the drag,
 *                  DRAGDROP_S_DROP to drop the data where it is,
 *                  or NOERROR to continue.
 */

STDMETHODIMP CDropSource::QueryContinueDrag(BOOL fEsc
    , DWORD grfKeyState)
    {
    if (fEsc)
        return ResultFromScode(DRAGDROP_S_CANCEL);

    if (!(grfKeyState & MK_LBUTTON))
        return ResultFromScode(DRAGDROP_S_DROP);

    return NOERROR;
    }

/*
 * CDropSource::GiveFeedback
 *
 * Purpose:
 *  Provides cursor feedback to the user since the source task
 *  always has the mouse capture.  We can also provide any other
 *  type of feedback above cursors if we so desire.
 *
 * Parameters:
 *  dwEffect        DWORD effect flags returned from the last target
 *
 * Return Value:
 *  HRESULT         NOERROR if you set a cursor yourself or
 *                  DRAGDROP_S_USEDEFAULTCURSORS to let OLE do
 *                  the work.
 */

STDMETHODIMP CDropSource::GiveFeedback(DWORD dwEffect)
    {
    return ResultFromScode(DRAGDROP_S_USEDEFAULTCURSORS);
    }


DROPTGT.CPP

/*
 * DROPTGT.CPP
 * Cosmo Chapter 18
 *
 * Implementation of the IDropTarget interface.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CDropTarget::CDropTarget
 * CDropTarget::~CDropTarget
 *
 * Constructor Parameters:
 *  pDoc            PCCosmoDoc of the document containing us.
 */

CDropTarget::CDropTarget(PCCosmoDoc pDoc)
    {
    m_cRef=0;
    m_pDoc=pDoc;

    m_pIDataObject=NULL;
    return;
    }

CDropTarget::~CDropTarget(void)
    {
    return;
    }

/*
 * CDropTarget::QueryInterface
 * CDropTarget::AddRef
 * CDropTarget::Release
 *
 * Purpose:
 *  IUnknown members for CDropTarget object.
 */

STDMETHODIMP CDropTarget::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDropTarget==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDropTarget::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropTarget::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDropTarget::DragEnter
 *
 * Purpose:
 *  Indicates that data in a drag operation has been dragged over
 *  our window that's a potential target.  We are to decide if it's
 *  something we're interested in or not.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT providing the source data.
 *  grfKeyState     DWORD flags: states of keys and mouse buttons.
 *  pt              POINTL coordinates in the document client space.
 *  pdwEffect       LPDWORD into which we'll place the
 *                  appropriate effect flag for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragEnter(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    HWND        hWnd;

    /*
     * 1.  Check if we can paste from the pIDataSource we're
     *     provided. We already made this nice useful function
     *     to test.
     */

    m_pIDataObject=NULL;

    if (!m_pDoc->FQueryPasteFromData(pIDataSource))
        {
        *pdwEffect=DROPEFFECT_NONE;
        return NOERROR;
        }

    /*
     * 2.  We can always drop anywhere in our document,
     *     so pt is uninteresting.
     */

    /*
     * 3. We return either a COPY or MOVE effect flag, depending on
     *    the state of the grfKeyState flags.  We MOVE on no key or
     *    Shift key, COPY on Ctrl key.
     */

    //Default is move
    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        *pdwEffect=DROPEFFECT_COPY;

    /*
     * 4. We really don't need to keep the IDataObject around since
     *    we're not interested in it in DragOver.  However, we'll
     *    save it just for demonstration.
     */
    m_pIDataObject=pIDataSource;
    m_pIDataObject->AddRef();

    /*
     * 5.  We always accept drops of our data on us, so we only need
     *     to provide some UI feedback here which we do by inverting
     *     the edge of the polyline window in this document.  We'll
     *     remove this in DragLeave and in Drop.  DragOver won't
     *     effect it since we can always drop.
     *
     *     Since we're inverting a border, insure that this window is
     *     on top.
     */

    hWnd=m_pDoc->Window();
    BringWindowToTop(hWnd);
    UpdateWindow(hWnd);
    m_pDoc->DropSelectTargetWindow();

    return NOERROR;
    }

/*
 * CDropTarget::DragOver
 *
 * Purpose:
 *  Indicates that the mouse was moved inside the window represented
 *  by this drop target. This happens on every WM_MOUSEMOVE, so this
 *  function should be very efficient.
 *
 * Parameters:
 *  grfKeyState     DWORD providing current keyboard/mouse states
 *  pt              POINTL where the mouse currently is.
 *  pdwEffect       LPDWORD to store the effect flag for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragOver(DWORD grfKeyState, POINTL pt
    , LPDWORD pdwEffect)
    {
    if (NULL==m_pIDataObject)
        {
        *pdwEffect=DROPEFFECT_NONE;
        return NOERROR;
        }

    //We can always drop; return effect flags based on keys.
    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        *pdwEffect=DROPEFFECT_COPY;

    return NOERROR;
    }

/*
 * CDropTarget::DragLeave
 *
 * Purpose:
 *  Informs the drop target that the operation has left its window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragLeave(void)
    {
    //1.  Remove the UI feedback
    m_pDoc->DropSelectTargetWindow();

    //2.  Release the held IDataObject
    ReleaseInterface(m_pIDataObject);

    return NOERROR;
    }

/*
 * CDropTarget::Drop
 *
 * Purpose:
 *  Instructs the drop target to paste data that was just now
 *  dropped on it.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT from which we'll paste.
 *  grfKeyState     DWORD providing current keyboard/mouse state.
 *  pt              POINTL at which the drop occurred.
 *  pdwEffect       LPDWORD to store what you do with the data.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::Drop(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    BOOL        fRet=TRUE;

    *pdwEffect=DROPEFFECT_NONE;

    if (NULL==m_pIDataObject)
        return ResultFromScode(E_FAIL);

    //1.  Remove the UI feedback, release pointer
    DragLeave();

    //No point in drag-drop to ourselves (for Cosmo, at least)
    if (m_pDoc->m_fDragSource)
        return ResultFromScode(E_FAIL);

    //2.  Try a paste
    fRet=m_pDoc->PasteFromData(pIDataSource);

    //3.  Store the effect
    if (!fRet)
        return ResultFromScode(E_FAIL);

    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        *pdwEffect=DROPEFFECT_COPY;

    return NOERROR;
    }


FIGURE.CPP

/*
 * FIGURE.CPP
 * Cosmo Chapter 18
 *
 * Implementation of the CFigure object for Cosmo.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CFigure::CFigure
 * CFigure::~CFigure
 *
 * Parameters (Constructor):
 *  pfnDestroy      PFNDESTROYED to call when object is destroyed.
 *  pDoc            PCCosmoDoc we're associated with.
 */

CFigure::CFigure(PFNDESTROYED pfnDestroy, PCCosmoDoc pDoc)
    {
    m_cRef=0;
    m_pfnDestroy=pfnDestroy;

    m_pFR=NULL;     //We get this later through FrameSet.
    m_pDoc=pDoc;
    m_pPL=pDoc->m_pPL;

    m_fEmbedded=FALSE;

    //NULL any contained interfaces initially.
    m_pImpIPersistStorage=NULL;
    m_pIStorage=NULL;
    m_pIStream=NULL;
    m_pImpIDataObject=NULL;
    m_pIDataAdviseHolder=NULL;
    m_pImpIOleObject=NULL;
    m_pIOleAdviseHolder=NULL;
    m_pIOleClientSite=NULL;

    m_clsID=CLSID_CosmoFigure;
    m_cf=pDoc->m_cf;

    //These are for IDataObject::QueryGetData
    m_cfeGet=CFORMATETCGET;

    SETDefFormatEtc(m_rgfeGet[0], pDoc->m_cf, TYMED_HGLOBAL);
    SETDefFormatEtc(m_rgfeGet[1], pDoc->m_cfEmbedSource
        , TYMED_ISTORAGE);
    SETDefFormatEtc(m_rgfeGet[2], pDoc->m_cfObjectDescriptor
        , TYMED_HGLOBAL);
    SETDefFormatEtc(m_rgfeGet[3], CF_METAFILEPICT, TYMED_MFPICT);
    SETDefFormatEtc(m_rgfeGet[4], CF_BITMAP, TYMED_GDI);

    m_pST=NULL;

    return;
    }

CFigure::~CFigure(void)
    {
    ReleaseInterface(m_pIOleClientSite);
    ReleaseInterface(m_pIDataAdviseHolder);
    ReleaseInterface(m_pIOleAdviseHolder);
    ReleaseInterface(m_pIStorage)
    ReleaseInterface(m_pIStream)

    DeleteInterfaceImp(m_pImpIOleObject)
    DeleteInterfaceImp(m_pImpIDataObject)
    DeleteInterfaceImp(m_pImpIPersistStorage);

    //Free strings.
    if (NULL!=m_pST)
        delete m_pST;

    return;
    }

/*
 * CFigure::QueryInterface
 * CFigure::AddRef
 * CFigure::Release
 *
 * Purpose:
 *  IUnknown members for CFigure object.
 */

STDMETHODIMP CFigure::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IPersist==riid || IID_IPersistStorage==riid)
        *ppv=m_pImpIPersistStorage;

    if (IID_IDataObject==riid)
        *ppv=m_pImpIDataObject;

    if (IID_IOleObject==riid)
        *ppv=m_pImpIOleObject;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CFigure::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CFigure::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    //Document deletes us
    return 0;
    }

/*
 * CFigure::Init
 *
 * Purpose:
 *  Performs any initialization of a CFigure that's prone to failure
 *  that we also use internally before exposing the object outside.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CFigure::Init(void)
    {
    m_pST=new CStringTable(m_pDoc->m_hInst);

    if (NULL==m_pST)
        return FALSE;

    if (!m_pST->Init(IDS_FIGUREMIN, IDS_FIGUREMAX))
        return FALSE;

    //Allocate contained interfaces.
    m_pImpIPersistStorage=new CImpIPersistStorage(this, this);

    if (NULL==m_pImpIPersistStorage)
        return FALSE;

    m_pImpIDataObject=new CImpIDataObject(this, this);

    if (NULL==m_pImpIDataObject)
        return FALSE;

    m_pImpIOleObject=new CImpIOleObject(this, this);

    if (NULL==m_pImpIOleObject)
        return FALSE;

    return TRUE;
    }

/*
 * CFigure::FrameSet
 *
 * Purpose:
 *  Provides the compound document object with access to the frame
 *  of this application for UI purposes.
 *
 * Parameters:
 *  pFR             PCCosmoFrame of the frame window.
 *
 * Return Value:
 *  None
 */

void CFigure::FrameSet(PCCosmoFrame pFR)
    {
    m_pFR=pFR;
    return;
    }

/*
 * CFigure::FIsDirty
 *
 * Purpose:
 *  Checks if the document is dirty.  This can be called from
 *  IPersistStorage::IsDirty which doesn't have access to CCosmoDoc.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if dirty, FALSE if clean.
 */

BOOL CFigure::FIsDirty(void)
    {
    return m_pDoc->m_fDirty;
    }

/*
 * CFigure::FIsEmbedded
 *
 * Purpose:
 *  Answers if the object is embedded or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the object is embedded, FALSE otherwise.
 */

BOOL CFigure::FIsEmbedded(void)
    {
    return m_fEmbedded;
    }

/*
 * CFigure::SendAdvise
 *
 * Purpose:
 *  Calls the appropriate IOleClientSite or IAdviseSink member
 *  function for various events such as closure, saving, etc.
 *
 * Parameters:
 *  uCode           UINT OBJECTCODE_* identifying the notification.
 *
 * Return Value:
 *  None
 */

void CFigure::SendAdvise(UINT uCode)
    {
    switch (uCode)
        {
        case OBJECTCODE_SAVED:
            if (NULL!=m_pIOleAdviseHolder)
                m_pIOleAdviseHolder->SendOnSave();

break;

        case OBJECTCODE_CLOSED:
            if (NULL!=m_pIOleAdviseHolder)
                m_pIOleAdviseHolder->SendOnClose();

break;

        case OBJECTCODE_RENAMED:
            //Call IOleAdviseHolder::SendOnRename (later)
            break;

        case OBJECTCODE_SAVEOBJECT:
            if (FIsDirty() && NULL!=m_pIOleClientSite)
                m_pIOleClientSite->SaveObject();

            break;

        case OBJECTCODE_DATACHANGED:
            //No flags are necessary here.
            if (NULL!=m_pIDataAdviseHolder)
                {
                m_pIDataAdviseHolder->SendOnDataChange
                    (m_pImpIDataObject, 0, 0);
                }
            break;

        case OBJECTCODE_SHOWWINDOW:
            if (NULL!=m_pIOleClientSite)
                m_pIOleClientSite->OnShowWindow(TRUE);

            break;

        case OBJECTCODE_HIDEWINDOW:
            if (NULL!=m_pIOleClientSite)
                m_pIOleClientSite->OnShowWindow(FALSE);

            break;

        case OBJECTCODE_SHOWOBJECT:
            if (NULL!=m_pIOleClientSite)
                m_pIOleClientSite->ShowObject();

            break;
        }

    return;
    }


ICLASSF.CPP

/*
 * ICLASSF.CPP
 * Cosmo Chapter 18
 *
 * Implementation of Cosmo's class factory.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CFigureClassFactory::CFigureClassFactory
 * CFigureClassFactory::~CFigureClassFactory
 *
 * Constructor Parameters:
 *  pFR             PCCosmoFrame that can create documents.
 */

CFigureClassFactory::CFigureClassFactory(PCCosmoFrame pFR)
    {
    m_cRef=0L;
    m_pFR=pFR;
    m_fCreated=FALSE;
    return;
    }

CFigureClassFactory::~CFigureClassFactory(void)
    {
    return;
    }

/*
 * CFigureClassFactory::QueryInterface
 * CFigureClassFactory::AddRef
 * CFigureClassFactory::Release
 */

STDMETHODIMP CFigureClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CFigureClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CFigureClassFactory::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CFigureClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a Figure object that supports embedding.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we are
 *                  being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise contains
 *                  E_NOINTERFACE if we cannot support the requested
 *                  interface.
 */

STDMETHODIMP CFigureClassFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCCosmoDoc          pDoc;
    HRESULT             hr;

    *ppvObj=NULL;

    //Great idea to protect yourself from multiple creates here.
    if (m_fCreated)
        return ResultFromScode(E_UNEXPECTED);

    m_fCreated=TRUE;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //We don't support aggregation
    if (NULL!=pUnkOuter)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    //Try creating a new document, which creates the object.
    pDoc=(PCCosmoDoc)m_pFR->m_pCL->NewDocument(TRUE);

    if (NULL==pDoc)
        {
        //This will cause shutdown; object count will go to zero.
        g_cObj++;
        ObjectDestroyed();
        return hr;
        }

    //Insure the document is untitled; get the requested interface.
    pDoc->Load(TRUE, NULL);
    pDoc->m_pFigure->FrameSet(m_pFR);
    hr=pDoc->m_pFigure->QueryInterface(riid, ppvObj);

    //Closing the document will destroy the objec, cause shutdown.
    if (FAILED(hr))
        {
        m_pFR->m_pCL->CloseDocument(pDoc);
        return hr;
        }

    return NOERROR;
    }

/*
 * CFigureClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the serving
 *  IClassFactory object.  When the number of locks goes to
 *  zero and the number of objects is zero, we shut down the
 *  application.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CFigureClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        {
        g_cLock--;
        g_cObj++;
        ObjectDestroyed();
        }

    return NOERROR;
    }


IDATAOBJ.CPP

/*
 * IDATAOBJ.CPP
 * Cosmo Chapter 18
 *
 * Implementation of the IDataObject interface.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CImpIDataObject::CImpIDataObject
 * CImpIDataObject::~CImpIDataObject
 *
 * Parameters (Constructor):
 *  pObj            PCFigure of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIDataObject::CImpIDataObject(PCFigure pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIDataObject::~CImpIDataObject(void)
    {
    return;
    }

/*
 * CImpIDataObject::QueryInterface
 * CImpIDataObject::AddRef
 * CImpIDataObject::Release
 */

STDMETHODIMP CImpIDataObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIDataObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIDataObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIDataObject::GetData
 *
 * Purpose:
 *  Retrieves data described by a specific FormatEtc into a StgMedium
 *  allocated by this function.  Used like GetClipboardData.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *  pSTM            LPSTGMEDIUM in which to return the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetData(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM)
    {
    UINT            cf=pFE->cfFormat;
    BOOL            fRet=FALSE;

    //Another part of us already knows if the format is good.
    if (NOERROR!=QueryGetData(pFE))
        return ResultFromScode(DATA_E_FORMATETC);

    if (CF_METAFILEPICT==cf || CF_BITMAP==cf || m_pObj->m_cf==cf)
        {
        if (CF_METAFILEPICT==cf)
            {
            pSTM->tymed=TYMED_MFPICT;
            }
        else
            pSTM->tymed=TYMED_HGLOBAL;

        pSTM->pUnkForRelease=NULL;
        pSTM->hGlobal=m_pObj->m_pDoc->RenderFormat(cf);
        fRet=(NULL!=pSTM->hGlobal);
        }
    else
        fRet=m_pObj->m_pDoc->RenderMedium(cf, pSTM);

    return fRet ? NOERROR : ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::GetDataHere
 *
 * Purpose:
 *  Renders the specific FormatEtc into caller-allocated medium
 *  provided in pSTM.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *  pSTM            LPSTGMEDIUM providing the medium into which
 *                  wer render the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetDataHere(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM)
    {
    UINT        cf;
    LONG        lRet;

    /*
     * The only reasonable time this is called is for
     * CFSTR_EMBEDSOURCE and TYMED_ISTORAGE (and later for
     * CFSTR_LINKSOURCE).  This means the same as
     * IPersistStorage::Save.
     */

    cf=RegisterClipboardFormat(CFSTR_EMBEDSOURCE);

    //Aspect is unimportant to us here, as is lindex and ptd.
    if (cf==pFE->cfFormat && (TYMED_ISTORAGE & pFE->tymed))
        {
        //We have an IStorage we can write into.
        pSTM->tymed=TYMED_ISTORAGE;
        pSTM->pUnkForRelease=NULL;
        lRet=m_pObj->m_pPL->WriteToStorage(pSTM->pstg
            , VERSIONCURRENT);

        if (lRet >= 0)
            return NOERROR;

        return ResultFromScode(STG_E_WRITEFAULT);
        }

    return ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::QueryGetData
 *
 * Purpose:
 *  Tests if a call to GetData with this FormatEtc will provide
 *  any rendering; used like IsClipboardFormatAvailable.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::QueryGetData(LPFORMATETC pFE)
    {
    UINT            cf=pFE->cfFormat;
    UINT            i;

    //Check the aspects we support.
    if (!(DVASPECT_CONTENT & pFE->dwAspect))
        return ResultFromScode(S_FALSE);

    for (i=0; i < m_pObj->m_cfeGet; i++)
        {
        if (pFE->cfFormat==m_pObj->m_rgfeGet[i].cfFormat
            && pFE->tymed & m_pObj->m_rgfeGet[i].tymed)
            {
            return NOERROR;
            }
        }

    return ResultFromScode(S_FALSE);
    }

/*
 * CImpIDataObject::GetCanonicalFormatEtc
 *
 * Purpose:
 *  Provides the caller with an equivalent FormatEtc to the one
 *  provided when different FormatEtcs will produce exactly the
 *  same renderings.
 *
 * Parameters:
 *  pFEIn            LPFORMATETC of the first description.
 *  pFEOut           LPFORMATETC of the equal description.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetCanonicalFormatEtc
    (LPFORMATETC pFEIn, LPFORMATETC pFEOut)
    {
    if (NULL==pFEOut)
        return ResultFromScode(E_INVALIDARG);

    pFEOut->ptd=NULL;
    return ResultFromScode(DATA_S_SAMEFORMATETC);
    }

/*
 * CImpIDataObject::SetData
 *
 * Purpose:
 *  Places data described by a FormatEtc and living in a StgMedium
 *  into the object.  The object may be responsible to clean up the
 *  StgMedium before exiting.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the data to set.
 *  pSTM            LPSTGMEDIUM containing the data.
 *  fRelease        BOOL indicating if this function is responsible
 *                  for freeing the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::SetData(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM, BOOL fRelease)
    {
    LONG            lRet;

    /*
     * Data can only come from global memory containing a
     * POLYLINEDATA structure that we send to the Polyline's
     * DataSetMem.
     */
    if ((pFE->cfFormat!=m_pObj->m_cf)
        || !(DVASPECT_CONTENT & pFE->dwAspect)
        || (TYMED_HGLOBAL!=pSTM->tymed))
        return ResultFromScode(DATA_E_FORMATETC);

    lRet=m_pObj->m_pPL->DataSetMem(pSTM->hGlobal, FALSE, TRUE
        , TRUE);

    if (fRelease)
        ReleaseStgMedium(pSTM);

    return (POLYLINE_E_NONE==lRet) ?
        NOERROR : ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::EnumFormatEtc
 *
 * Purpose:
 *  Returns an IEnumFORMATETC object through which the caller can
 *  iterate to learn about all the data formats this object can
 *  provide through either GetData[Here] or SetData.
 *
 * Parameters:
 *  dwDir           DWORD describing a data direction, either
 *                  DATADIR_SET or DATADIR_GET.
 *  ppEnum          LPENUMFORMATETC * in which to return the
 *                  pointer to the enumerator.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::EnumFormatEtc(DWORD dwDir
    , LPENUMFORMATETC *ppEnum)
    {
    return ResultFromScode(OLE_S_USEREG);
    }

/*
 * CImpIDataObject::DAdvise
 * CImpIDataObject::DUnadvise
 * CImpIDataObject::EnumDAdvise
 *
 * Pass-throughs to IDataAdviseHolder.
 */

STDMETHODIMP CImpIDataObject::DAdvise(LPFORMATETC pFE, DWORD dwFlags
    , LPADVISESINK pIAdviseSink, LPDWORD pdwConn)
    {
    HRESULT         hr;

    if (NULL==m_pObj->m_pIDataAdviseHolder)
        {
        hr=CreateDataAdviseHolder(&m_pObj->m_pIDataAdviseHolder);

        if (FAILED(hr))
            return ResultFromScode(E_OUTOFMEMORY);
        }

    hr=m_pObj->m_pIDataAdviseHolder->Advise(this, pFE
        , dwFlags, pIAdviseSink, pdwConn);

    return hr;
    }

STDMETHODIMP CImpIDataObject::DUnadvise(DWORD dwConn)
    {
    if (NULL==m_pObj->m_pIDataAdviseHolder)
        return ResultFromScode(E_FAIL);

    return m_pObj->m_pIDataAdviseHolder->Unadvise(dwConn);
    }

STDMETHODIMP CImpIDataObject::EnumDAdvise(LPENUMSTATDATA *ppEnum)
    {
    if (NULL==m_pObj->m_pIDataAdviseHolder)
        return ResultFromScode(E_FAIL);

    return m_pObj->m_pIDataAdviseHolder->EnumAdvise(ppEnum);
    }


IOLEOBJ.CPP

/*
 * IOLEOBJ.CPP
 * Cosmo Chapter 18
 *
 * Implementation of the IOleObject interface for Polyline.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CImpIOleObject::CImpIOleObject
 * CImpIOleObject::~CImpIOleObject
 *
 * Parameters (Constructor):
 *  pObj            PCFigure of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleObject::CImpIOleObject(PCFigure pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleObject::~CImpIOleObject(void)
    {
    return;
    }

/*
 * CImpIOleObject::QueryInterface
 * CImpIOleObject::AddRef
 * CImpIOleObject::Release
 */

STDMETHODIMP CImpIOleObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleObject::SetClientSite
 *
 * Purpose:
 *  Provides the object with a pointer to the IOleClient site
 *  representing the container in which this object resides.
 *
 * Parameters:
 *  pIOleClientSite LPOLECLIENTSITE to the container's interface.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleObject::SetClientSite
    (LPOLECLIENTSITE pIOleClientSite)
    {
    if (NULL!=m_pObj->m_pIOleClientSite)
        m_pObj->m_pIOleClientSite->Release();

    m_pObj->m_pIOleClientSite=pIOleClientSite;
    m_pObj->m_pIOleClientSite->AddRef();
    return NOERROR;
    }

/*
 * CImpIOleObject::GetClientSite
 *
 * Purpose:
 *  Asks the object for the client site provided in SetClientSite.
 *  If you have not seen SetClientSite yet, return a NULL in
 *  ppIOleClientSite.
 *
 * Parameters:
 *  ppSite          LPOLECLIENTSITE * in which to store the
 *                  pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleObject::GetClientSite(LPOLECLIENTSITE
    *ppSite)
    {
    //Be sure to AddRef the new pointer you are giving away.
    *ppSite=m_pObj->m_pIOleClientSite;
    m_pObj->m_pIOleClientSite->AddRef();

    return NOERROR;
    }

/*
 * CImpIOleObject::SetHostNames
 *
 * Purpose:
 *  Provides the object with names of the container application and
 *  the object in the container to use in object user interface.
 *
 * Parameters:
 *  pszApp          LPCOLESTR of the container application.
 *  pszObj          LPCOLESTR of some name useful in window titles.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleObject::SetHostNames(LPCOLESTR pszApp
    , LPCOLESTR pszObj)
    {
    m_pObj->m_fEmbedded=TRUE;
   #ifdef WIN32ANSI
    char        szApp[80], szObj[80];

    szApp[0]=0;
    szObj[0]=0;

    if (NULL!=pszApp)
        {
        WideCharToMultiByte(CP_ACP, 0, pszApp, -1, szApp, 80
            , NULL, NULL);
        }

    if (NULL!=pszObj)
        {
        WideCharToMultiByte(CP_ACP, 0, pszObj, -1, szObj, 80
            , NULL, NULL);
        }

    m_pObj->m_pFR->UpdateEmbeddingUI(TRUE, m_pObj->m_pDoc
        , szApp, szObj);
   #else
    m_pObj->m_pFR->UpdateEmbeddingUI(TRUE, m_pObj->m_pDoc
        , pszApp, pszObj);
   #endif
    return NOERROR;
    }

/*
 * CImpIOleObject::Close
 *
 * Purpose:
 *  Forces the object to close down its user interface and unload.
 *
 * Parameters:
 *  dwSaveOption    DWORD describing the circumstances under which
 *                  the object is being saved and closed.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::Close(DWORD dwSaveOption)
    {
    HWND        hWnd;
    BOOL        fSave=FALSE;

    hWnd=m_pObj->m_pDoc->Window();

    //If object is dirty and we're asked to save, save it and close.
    if (OLECLOSE_SAVEIFDIRTY==dwSaveOption && m_pObj->FIsDirty())
        fSave=TRUE;

    /*
     * If asked to prompt, only do so if dirty, then if we get a
     * YES, save as usual and close.  On NO, just close.  On
     * CANCEL return OLE_E_PROMPTSAVECANCELLED.
     */
    if (OLECLOSE_PROMPTSAVE==dwSaveOption && m_pObj->FIsDirty())
        {
        UINT        uRet;

        uRet=MessageBox(hWnd, (*m_pObj->m_pST)[IDS_CLOSECAPTION]
            , (*m_pObj->m_pST)[IDS_CLOSEPROMPT], MB_YESNOCANCEL);

        if (IDCANCEL==uRet)
            return ResultFromScode(OLE_E_PROMPTSAVECANCELLED);

        if (IDYES==uRet)
            fSave=TRUE;
        }

    if (fSave)
        {
        m_pObj->SendAdvise(OBJECTCODE_SAVEOBJECT);
        m_pObj->SendAdvise(OBJECTCODE_SAVED);
        }

    //We get directly here on OLECLOSE_NOSAVE.
    PostMessage(hWnd, WM_CLOSE, 0, 0L);
    return NOERROR;
    }

/*
 * CImpIOleObject::SetMoniker
 *
 * Purpose:
 *  Informs the object of its moniker or its container's moniker
 *  depending on dwWhich.
 *
 * Parameters:
 *  dwWhich         DWORD describing whether the moniker is the
 *                  object's or the container's.
 *  pmk             LPMONIKER with the name.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::SetMoniker(DWORD dwWhich
    , LPMONIKER pmk)
    {
    //Uninteresting for embeddings only.
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleObject::GetMoniker
 *
 * Purpose:
 *  Asks the object for a moniker that can later be used to
 *  reconnect to it.
 *
 * Parameters:
 *  dwAssign        DWORD determining how to assign the moniker to
 *                  to the object.
 *  dwWhich         DWORD describing which moniker the caller wants.
 *  ppmk            LPMONIKER * into which to store the moniker.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetMoniker(DWORD dwAssign
    , DWORD dwWhich, LPMONIKER * ppmk)
    {
    //Uninteresting for embeddings only.
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleObject::InitFromData
 *
 * Purpose:
 *  Initializes the object from the contents of a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT containing the data.
 *  fCreation       BOOL indicating if this is part of a new
 *                  creation. If FALSE, the container is trying
 *                  to paste here.
 *  dwReserved      DWORD reserved.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::InitFromData(LPDATAOBJECT pIDataObject
    , BOOL fCreation, DWORD dwReserved)
    {
    BOOL    fRet;

    /*
     * If we get a data object here, try to paste from it.  If
     * you've written clipboard code already, this is a snap.
     * We don't really care about fCreation or not since pasting
     * in us blasts away whatever is already here.
     */
    fRet=m_pObj->m_pDoc->PasteFromData(pIDataObject);
    return fRet ? NOERROR : ResultFromScode(E_FAIL);
    }

/*
 * CImpIOleObject::GetClipboardData
 *
 * Purpose:
 *  Returns an IDataObject pointer to the caller representing what
 *  would be on the clipboard if the server did an Edit/Copy using
 *  OleSetClipboard.
 *
 * Parameters:
 *  dwReserved      DWORD reserved.
 *  ppIDataObj      LPDATAOBJECT * into which to store the
 *                  pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetClipboardData(DWORD dwReserved
    , LPDATAOBJECT *ppIDataObj)
    {
    /*
     * Again, if you have a function to create a data object for the
     * clipboard, this is a simple implementation.  The one we have
     * does all the compound document formats already.
     */
    *ppIDataObj=m_pObj->m_pDoc->TransferObjectCreate(FALSE);
    return (NULL!=*ppIDataObj) ? NOERROR : ResultFromScode(E_FAIL);
    }

/*
 * CImpIOleObject::DoVerb
 *
 * Purpose:
 *  Executes an object-defined action.
 *
 * Parameters:
 *  iVerb           LONG index of the verb to execute.
 *  pMSG            LPMSG describing the event causing the
 *                  activation.
 *  pActiveSite     LPOLECLIENTSITE to the site involved.
 *  lIndex          LONG the piece on which execution is happening.
 *  hWndParent      HWND of window in which the object can play
 *                  in-place.
 *  pRectPos        LPRECT of the object in hWndParent where the
 *                  object can play in-place if desired.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::DoVerb(LONG iVerb, LPMSG pMSG
    , LPOLECLIENTSITE pActiveSite, LONG lIndex, HWND hWndParent
    , LPCRECT pRectPos)
    {
    HWND            hWnd, hWndT;

    //Find the upper most window
    hWndT=GetParent(m_pObj->m_pDoc->Window());

    while (NULL!=hWndT)
        {
        hWnd=hWndT;
        hWndT=GetParent(hWndT);
        }

    switch (iVerb)
        {
        case OLEIVERB_HIDE:
            ShowWindow(hWnd, SW_HIDE);
            m_pObj->SendAdvise(OBJECTCODE_HIDEWINDOW);
            break;

        case OLEIVERB_PRIMARY:
        case OLEIVERB_OPEN:
        case OLEIVERB_SHOW:
            ShowWindow(hWnd, SW_SHOW);
            SetForegroundWindow(hWnd);
            SetFocus(hWnd);

            m_pObj->SendAdvise(OBJECTCODE_SHOWOBJECT);
            m_pObj->SendAdvise(OBJECTCODE_SHOWWINDOW);
            break;

        default:
            return ResultFromScode(OLEOBJ_S_INVALIDVERB);
        }

    return NOERROR;
    }

/*
 * CImpIOleObject::EnumVerbs
 *
 * Purpose:
 *  Creates an enumerator that knows the object's verbs.  If you
 *  need to change the verb list dynamically, then you'll need to
 *  implement this, otherwise you can return OLE_S_USEREG.
 *
 * Parameters:
 *  ppEnum          LPENUMOLEVERB * into which to return the
 *                  enum.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::EnumVerbs(LPENUMOLEVERB *ppEnum)
    {
    //Trivial implementation if you fill the regDB.
    return ResultFromScode(OLE_S_USEREG);
    }

/*
 * CImpIOleObject::Update
 *
 * Purpose:
 *  Insures that the object is up to date.  This is mostly used for
 *  caching but you must make sure that you recursively call all
 *  nested objects you contain as well.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::Update(void)
    {
    //We're always updated since we don't contain.
    return NOERROR;
    }

/*
 * CImpIOleObject::IsUpToDate
 *
 * Purpose:
 *  Returns if the object is currently up to date, which involves
 *  asking all contained object inside this object if they are up
 *  to date as well.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE if dirty.
 */

STDMETHODIMP CImpIOleObject::IsUpToDate(void)
    {
    //We're always updated since we don't contain.
    return NOERROR;
    }

/*
 * CImpIOleObject::GetUserClassID
 *
 * Purpose:
 *  Used for linked objects, this returns the class ID of what end
 *  users think they are editing.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store the CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetUserClassID(LPCLSID pClsID)
    {
    /*
     * If you are not registered to handle data other than yourself,
     * then you can just return your class ID here.  If you are
     * registered as usable from Treat-As dialogs, then you need
     * to return the CLSID of what you are really editing.
     */

    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

/*
 * CImpIOleObject::GetUserType
 *
 * Purpose:
 *  Determines the user-presentable name of the object.
 *
 * Parameters:
 *  dwForm          DWORD describing which form of the string is
 *                  desired.
 *  pszType         LPOLESTR * into which to return the pointer to
 *                  the type string.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetUserType(DWORD dwForm
    , LPOLESTR *ppszType)
    {
    return ResultFromScode(OLE_S_USEREG);
    }

/*
 * CImpIOleObject::SetExtent
 *
 * Purpose:
 *  Sets the size of the object in HIMETRIC units.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect affected.
 *  pszl            LPSIZEL containing the new size.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::SetExtent(DWORD dwAspect, LPSIZEL pszl)
    {
    RECT            rc;
    SIZEL           szl;

    if (!(DVASPECT_CONTENT & dwAspect))
        return ResultFromScode(E_FAIL);

    XformSizeInHimetricToPixels(NULL, pszl, &szl);

    //This resizes the window to match the container's size.
    SetRect(&rc, 0, 0, (int)szl.cx, (int)szl.cy);
    m_pObj->m_pPL->SizeSet(&rc, TRUE);

    return NOERROR;
    }

/*
 * CImpIOleObject::GetExtent
 *
 * Purpose:
 *  Retrieves the size of the object in HIMETRIC units.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect requested
 *  pszl            LPSIZEL into which to store the size.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetExtent(DWORD dwAspect, LPSIZEL pszl)
    {
    RECT            rc;
    SIZEL           szl;

    if (!(DVASPECT_CONTENT & dwAspect))
        return ResultFromScode(E_FAIL);

    m_pObj->m_pPL->RectGet(&rc);
    szl.cx=rc.right-rc.left;
    szl.cy=rc.bottom-rc.top;

    XformSizeInPixelsToHimetric(NULL, &szl, pszl);
    return NOERROR;
    }

/*
 * CImpIOleObject::Advise
 *
 * Purpose:
 *  Provides an IAdviseSink to the object for notifications.
 *
 * Parameters:
 *  pIAdviseSink    LPADVISESINK to notify.
 *  pdwConn         LPDWORD into which to store a connection key.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::Advise(LPADVISESINK pIAdviseSink
    , LPDWORD pdwConn)
    {
    if (NULL==m_pObj->m_pIOleAdviseHolder)
        {
        HRESULT     hr;

        hr=CreateOleAdviseHolder(&m_pObj->m_pIOleAdviseHolder);

        if (FAILED(hr))
            return hr;
        }

    return m_pObj->m_pIOleAdviseHolder->Advise(pIAdviseSink
        , pdwConn);
    }

/*
 * CImpIOleObject::Unadvise
 *
 * Purpose:
 *  Terminates a previous advise connection from Advise.
 *
 * Parameters:
 *  dwConn          DWORD connection key from Advise.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::Unadvise(DWORD dwConn)
    {
    if (NULL!=m_pObj->m_pIOleAdviseHolder)
        return m_pObj->m_pIOleAdviseHolder->Unadvise(dwConn);

    return ResultFromScode(E_FAIL);
    }

/*
 * CImpIOleObject::EnumAdvise
 *
 * Purpose:
 *  Creates and returns a enumeration of the advises on this object.
 *
 * Parameters:
 *  ppEnum          LPENUMSTATDATA * in which to return the
 *                  enumerator.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::EnumAdvise(LPENUMSTATDATA *ppEnum)
    {
    if (NULL!=m_pObj->m_pIOleAdviseHolder)
        return m_pObj->m_pIOleAdviseHolder->EnumAdvise(ppEnum);

    return ResultFromScode(E_FAIL);
    }

/*
 * CImpIOleObject::GetMiscStatus
 *
 * Purpose:
 *  Returns a set of miscellaneous status flags for the object.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect in question.
 *  pdwStatus       LPDWORD in which to store the flags.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetMiscStatus(DWORD dwAspect
    , LPDWORD pdwStatus)
    {
    return ResultFromScode(OLE_S_USEREG);
    }

/*
 * CImpIOleObject::SetColorScheme
 *
 * Purpose:
 *  Provides the object with the color palette as recommended by
 *  the container application that also knows the palettes of other
 *  objects.  The object here is not required to use these colors.
 *
 * Parameters:
 *  pLP             LPLOGPALETTE providing the colors.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::SetColorScheme(LPLOGPALETTE pLP)
    {
    return ResultFromScode(E_NOTIMPL);
    }


IPERSTOR.CPP

/*
 * IPERSTOR.CPP
 * Cosmo Chapter 18
 *
 * Implementation of the IPersistStorage interface that we expose on
 * the CFigure compound document object.  This ties into the
 * functionality of CPolyline.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CImpIPersistStorage:CImpIPersistStorage
 * CImpIPersistStorage::~CImpIPersistStorage
 *
 * Constructor Parameters:
 *  pObj            PCFigure associated with this object.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistStorage::CImpIPersistStorage(PCFigure pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    m_psState=PSSTATE_UNINIT;

    m_fConvert=FALSE;
    return;
    }

CImpIPersistStorage::~CImpIPersistStorage(void)
    {
    return;
    }

/*
 * CImpIPersistStorage::QueryInterface
 * CImpIPersistStorage::AddRef
 * CImpIPersistStorage::Release
 */

STDMETHODIMP CImpIPersistStorage::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistStorage::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistStorage::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistStorage::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the object represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::GetClassID(LPCLSID pClsID)
    {
    if (PSSTATE_UNINIT==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this object since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 */

STDMETHODIMP CImpIPersistStorage::IsDirty(void)
    {
    if (PSSTATE_UNINIT==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    //CFigure::FIsDirty returns the document's dirty flag.
    return ResultFromScode(m_pObj->FIsDirty() ? S_OK : S_FALSE);
    }

/*
 * CImpIPersistStorage::InitNew
 *
 * Purpose:
 *  Provides the object with the IStorage to hold on to while the
 *  object is running.  Here we initialize the structure of the
 *  storage and AddRef it for incremental access. This function will
 *  only be called once in the object's lifetime in lieu of Load.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE for the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::InitNew(LPSTORAGE pIStorage)
    {
    HRESULT     hr;

    if (PSSTATE_UNINIT!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    if (NULL==pIStorage)
        return ResultFromScode(E_POINTER);

    /*
     * The rules of IPersistStorage mean we hold onto the IStorage
     * and pre-create anything we'd need in Save(...,TRUE) for
     * low-memory situations.  For us this means creating our
     * "CONTENTS" stream and holding onto that IStream as
     * well as the IStorage here (requiring an AddRef call).
     */

    hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
        | STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE
        , 0, 0, &m_pObj->m_pIStream);

    if (FAILED(hr))
        return hr;

    //We expect that the client has called WriteClassStg
    WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
        , (*m_pObj->m_pST)[IDS_USERTYPE]);

    pIStorage->AddRef();
    m_pObj->m_pIStorage=pIStorage;

    m_psState=PSSTATE_SCRIBBLE;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::Load
 *
 * Purpose:
 *  Instructs the object to load itself from a previously saved
 *  IStorage that was handled by Save in another object lifetime.
 *  This function will only be called once in the object's lifetime
 *  in lieu of InitNew. The object should hold on to pIStorage here
 *  for incremental access and low-memory saves in Save.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE from which to load.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::Load(LPSTORAGE pIStorage)
    {
    HRESULT     hr;
    LONG        lRet;
    LPSTREAM    pIStream;

    if (PSSTATE_UNINIT!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    if (NULL==pIStorage)
        return ResultFromScode(E_POINTER);

    //This tells us if we're coming from another class storage.
    m_fConvert=(NOERROR==GetConvertStg(pIStorage));

    //This is the type of storage we're really messing with in Treat As
    ReadClassStg(pIStorage, &m_pObj->m_clsID);

    hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
        | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    //We might be looking for OLE 1 streams as well.
    if (FAILED(hr))
        {
        hr=pIStorage->OpenStream(SZOLE1STREAM, 0, STGM_DIRECT
            | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

        if (FAILED(hr))
            return ResultFromScode(STG_E_READFAULT);

        m_pObj->m_pPL->m_fReadFromOLE10=TRUE;
        }

    if (FAILED(hr))
        return ResultFromScode(STG_E_READFAULT);

    lRet=m_pObj->m_pPL->ReadFromStream(pIStream);

    if (lRet < 0)
        return ResultFromScode(STG_E_READFAULT);

    /*
     * We don't call pIStream->Release here because we may need
     * it for a low-memory save in Save.  We also need to
     * hold onto a copy of pIStorage, meaning AddRef.
     */
    m_pObj->m_pIStream=pIStream;

    pIStorage->AddRef();
    m_pObj->m_pIStorage=pIStorage;

    m_psState=PSSTATE_SCRIBBLE;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::Save
 *
 * Purpose:
 * Purpose:
 *  Saves the data for this object to an IStorage which may
 *  or may not be the same as the one previously passed to
 *  Load, indicated with fSameAsLoad.  After this call we may
 *  not write into the storage again until SaveCompleted is
 *  called, although we may still read.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which to save our data.
 *  fSameAsLoad     BOOL indicating if this is the same pIStorage
 *                  that was passed to Load.  If TRUE, then the
 *                  object should write whatever it has *without
 *                  *using any extra memory* as this may be a low
 *                  memory save attempt.  That means that you must
 *                  not try to open or create streams.  If FALSE
 *                  you need to regenerate your whole storage
 *                  structure, being sure to also release any
 *                  pointers held from InitNew and Load.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::Save(LPSTORAGE pIStorage
    , BOOL fSameAsLoad)
    {
    LONG        lRet;
    HRESULT     hr;
    LPSTREAM    pIStream;
    LONG        lVer=VERSIONCURRENT;

    //Have to come here from scribble state.
    if (PSSTATE_SCRIBBLE!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    //Must have an IStorage if we're not in SameAsLoad
    if (NULL==pIStorage && !fSameAsLoad)
        return ResultFromScode(E_POINTER);

    /*
     * If this was read from an OLE 1.0 storage, but there is no
     * convert bit, then we have to save the 1.0 format to the
     * "\1Ole10Native" stream.  Otherwise if we were converting
     * from OLE 1.0, we should nuke the stream since it's no longer
     * useful.  To handle this, we call WriteToStorage with
     * VERSIONCURRENT in any convert case.  WriteToStorage will
     * remove the OLE 1.0 stream if it previously read from one, or
     * it will just save normally.
     *
     * The Polyine allows us to look at it's m_fReadFromOLE10 which
     * tells us to pass it 0x00010000 if we're not converting, that
     * is, we're doing Treat As on the OLE 1.0 object and so we
     * need to write the new data in the Ole10Native stream.
     */

    if (!m_fConvert && m_pObj->m_pPL->m_fReadFromOLE10)
        lVer=0x00010000;

    /*
     * If we're saving to a new storage, create a new stream.
     * If fSameAsLoad it TRUE, then we write to the
     * stream we already allocated.  We should NOT depends on
     * pIStorage with fSameAsLoad is TRUE.
     *
     * If we're converting an OLE 1 storage to an OLE 2 storage,
     * then we have to create a new stream (conversion is not
     * guaranteed to succeed in low memory) and delete the old
     * one, so we ignore fSameAsLoad if we're converting.
     */

    if (fSameAsLoad
        && !(m_pObj->m_pPL->m_fReadFromOLE10 && m_fConvert))
        {
        LARGE_INTEGER   li;

        /*
         * Use pre-allocated streams to avoid failures due
         * to low-memory conditions.  Be sure to reset the
         * stream pointer if you used this stream before!!
         */
        pIStream=m_pObj->m_pIStream;
        LISet32(li, 0);
        pIStream->Seek(li, STREAM_SEEK_SET, NULL);

        //This matches the Release below.
        pIStream->AddRef();
        }
    else
        {
        hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
            | STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE
            , 0, 0, &pIStream);

        if (FAILED(hr))
            return hr;

        WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
            , (*m_pObj->m_pST)[IDS_USERTYPE]);
        }

    lRet=m_pObj->m_pPL->WriteToStream(pIStream, lVer);
    pIStream->Release();

    /*
     * If we are overwriting an OLE 1 storage, delete the old
     * Ole10Native stream if writing our CONTENTS worked.
     */
    if (m_pObj->m_pPL->m_fReadFromOLE10 && m_fConvert && (lRet >= 0))
        pIStorage->DestroyElement(SZOLE1STREAM);

    //Clear the convert bit if it was set
    if (m_fConvert)
        {
        UINT        cf;

        cf=RegisterClipboardFormat((*m_pObj->m_pST)[IDS_FORMAT]);
        WriteFmtUserTypeStg(pIStorage, cf
            , (*m_pObj->m_pST)[IDS_USERTYPE]);

        SetConvertStg(pIStorage, FALSE);
        m_fConvert=FALSE;
        }

    if (lRet >= 0)
        {
        m_psState=PSSTATE_ZOMBIE;
        return NOERROR;
        }

    return ResultFromScode(STG_E_WRITEFAULT);
    }

/*
 * CImpIPersistStorage::SaveCompleted
 *
 * Purpose:
 *  Notifies the object that the storage in pIStorage has been
 *  completely saved now.  This is called when the user of this
 *  object wants to save us in a completely new storage, and if
 *  we normally hang on to the storage we have to reinitialize
 *  ourselves here for this new one that is now complete.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE of the new storage in which we live.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::SaveCompleted(LPSTORAGE pIStorage)
    {
    HRESULT     hr;
    LPSTREAM    pIStream;

    //Must be called in no-scribble or hands-off state
    if (!(PSSTATE_ZOMBIE==m_psState || PSSTATE_HANDSOFF==m_psState))
        return ResultFromScode(E_UNEXPECTED);

    //If we're coming from Hands-Off, we'd better get a storage
    if (NULL==pIStorage && PSSTATE_HANDSOFF==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    /*
     * If pIStorage is NULL, then we don't need to do anything
     * since we already have all the pointers we need for Save.
     * Otherwise we have to release any held pointers and
     * reinitialize them from pIStorage.
     */

    if (NULL!=pIStorage)
        {
        hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
            | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0
            , &pIStream);

        if (FAILED(hr))
            return hr;

        if (NULL!=m_pObj->m_pIStream)
            m_pObj->m_pIStream->Release();

        m_pObj->m_pIStream=pIStream;

        if (NULL!=m_pObj->m_pIStorage)
            m_pObj->m_pIStorage->Release();

        m_pObj->m_pIStorage=pIStorage;
        m_pObj->m_pIStorage->AddRef();
        }

    m_pObj->SendAdvise(OBJECTCODE_SAVED);
    m_psState=PSSTATE_SCRIBBLE;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::HandsOffStorage
 *
 * Purpose:
 * Purpose:
 *  Instructs the object that another agent is interested in having
 *  total access to the storage we might be hanging on to from
 *  InitNew or SaveCompleted.  In this case we must release our hold
 *  and await another call to SaveCompleted before we have a hold
 *  again.  Therefore we cannot read or write after this call until
 *  SaveCompleted.
 *
 *  Situations where this might happen arise in compound document
 *  scenarios where this object might be in-place active but the
 *  application wants to rename and commit the root storage.
 *  Therefore we are asked to close our hold, let the container
 *  party on the storage, then call us again later to tell us the
 *  new storage we can hold.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::HandsOffStorage(void)
    {
    /*
     * Must come from scribble or no-scribble.  A repeated call
     * to HandsOffStorage is an unexpected error (bug in client).
     */
    if (PSSTATE_UNINIT==m_psState || PSSTATE_HANDSOFF==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    //Release held pointers
    if (NULL!=m_pObj->m_pIStream)
        {
        m_pObj->m_pIStream->Release();
        m_pObj->m_pIStream=NULL;
        }

    if (NULL!=m_pObj->m_pIStorage)
        {
        m_pObj->m_pIStorage->Release();
        m_pObj->m_pIStorage=NULL;
        }

    m_psState=PSSTATE_HANDSOFF;
    return NOERROR;
    }


POLYLINE.CPP

/*
 * POLYLINE.CPP
 * Cosmo Chapter 18
 *
 * Implementation of the CPolyline class.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CPolyline:CPolyline
 * CPolyline::~CPolyline
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application we're in.
 */

CPolyline::CPolyline(HINSTANCE hInst)
    : CWindow(hInst)
    {
    m_pAdv=NULL;
    m_hWnd=NULL;
    //CHPATER18MOD
    m_fReadFromOLE10=FALSE;
    //End CHAPTER18MOD
    return;
    }

CPolyline::~CPolyline(void)
    {
    return;
    }

/*
 * CPolyline::Init
 *
 * Purpose:
 *  Instantiates a polyline window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags
 *  uID             UINT ID to associate with this window
 *  pAdv            PCPolylineAdviseSink of the sink wanting our
 *                  notifications.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPolyline::Init(HWND hWndParent, LPRECT pRect, DWORD dwStyle
    , UINT uID, PCPolylineAdviseSink pAdv)
    {
    m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY, SZCLASSPOLYLINE
        , SZCLASSPOLYLINE, dwStyle, pRect->left, pRect->top
        , pRect->right-pRect->left, pRect->bottom-pRect->top
        , hWndParent, (HMENU)uID, m_hInst, this);

    if (NULL!=m_hWnd)
        m_pAdv=pAdv;

    return (NULL!=m_hWnd);
    }

/*
 * CPolyline::New
 *
 * Purpose:
 *  Cleans out and reinitializes the data to defaults.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolyline::New(void)
    {
    UINT        i;
    RECT        rc;

    m_pl.wVerMaj=VERSIONMAJOR;
    m_pl.wVerMin=VERSIONMINOR;

    //Our rectangle is the size of our window's client area.
    GetClientRect(m_hWnd, &rc);
    RECTTORECTS(rc, m_pl.rc);

    //Clean out the POLYLINE structure and repaint the window.
    for (i=0; i< CPOLYLINEPOINTS; i++)
        {
        m_pl.rgpt[i].x=0;
        m_pl.rgpt[i].y=0;
        }

    m_pl.cPoints      =0;
    m_pl.rgbBackground=GetSysColor(COLOR_WINDOW);
    m_pl.rgbLine      =GetSysColor(COLOR_WINDOWTEXT);
    m_pl.iLineStyle   =PS_SOLID;

    InvalidateRect(m_hWnd, NULL, TRUE);
    UpdateWindow(m_hWnd);

    //Inform the advise sink of this data change.
    if (NULL!=m_pAdv)
        m_pAdv->OnDataChange();

    return;
    }

/*
 * CPolyline::Undo
 *
 * Purpose:
 *  Reverses previous actions in a Polyline.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if we can Undo more, FALSE otherwise.
 */

BOOL CPolyline::Undo(void)
    {
    //Decrement the number of active points and repaint.
    if (m_pl.cPoints > 0)
        {
        m_pl.cPoints--;
        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    if (NULL!=m_pAdv)
        m_pAdv->OnPointChange();

    //Return if we can undo any more.
    return (0!=m_pl.cPoints);
    }

/*
 * CPolyline::ReadFromStorage
 *
 * Purpose:
 *  Loads our data (any known version) from an IStorage returning
 *  the version number of the data or an error value.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE from which we'll read.
 *
 * Return Value:
 *  LONG            Version number or negative POLYLINE_E_* value.
 */

LONG CPolyline::ReadFromStorage(LPSTORAGE pIStorage)
    {
    //CHAPTER18MOD
    LONG            lRet;
    //End CHAPTER18MOD
    LPSTREAM        pIStream;
    HRESULT         hr;

    if (NULL==pIStorage)
        return POLYLINE_E_READFAILURE;

    //Open the CONTENTS stream
    hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT | STGM_READ
        | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return POLYLINE_E_READFAILURE;

    //CHAPTER18MOD
    /*
     * Since we had to create ReadFromStream for IPersistStorage,
     * use it ourselves.
     */
    lRet=ReadFromStream(pIStream);
    pIStream->Release();

    return lRet;
    //End CHAPTER18MOD
    }

/*
 * CPolyline::WriteToStorage
 *
 * Purpose:
 *  Ignorantly writes our current data into a storage in a
 *  particular version.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which to save.
 *  lVer            LONG providing version number Major (HI) and
 *                  Minor (Low)
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::WriteToStorage(LPSTORAGE pIStorage, LONG lVer)
    {
    //CHAPTER18MOD
    LONG            lRet;
    //End CHAPTER18MOD
    LPSTREAM        pIStream;
    HRESULT         hr;

    if (NULL==pIStorage)
        return POLYLINE_E_READFAILURE;

    hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT | STGM_CREATE
        | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pIStream);

    if (FAILED(hr))
        return POLYLINE_E_WRITEFAILURE;

    //CHAPTER18MOD
    /*
     * Use our function that will save to a stream now that
     * we needed it for IPersistStorage.
     */

    lRet=WriteToStream(pIStream, lVer);
    pIStream->Release();

    return lRet;
    //End CHAPTER18MOD
    }

//CHAPTER18MOD
/*
 * CPolyline::ReadFromStream
 *
 * Purpose:
 *  Loads our data (any known version) from an IStream that we
 *  do not open nor maintain.  This is for the compound document
 *  object's implementation of IPersistStorage.
 *
 * Parameters:
 *  pIStream        LPSTREAM from which we'll read.
 *
 * Return Value:
 *  LONG            Version number or negative POLYLINE_E_* value.
 */

LONG CPolyline::ReadFromStream(LPSTREAM pIStream)
    {
    POLYLINEDATA    pl;
    ULONG           cb=(ULONG)-1;
    ULONG           cbExpect=0;
    HRESULT         hr;
    LARGE_INTEGER   li;

    if (NULL==pIStream)
        return POLYLINE_E_READFAILURE;

    /*
     * If IPersistStorage::Load failed to find CONTENTS but did
     * open "\1Ole10Native" then m_fReadFromOLE10 is set.  So
     * we have to start reading past the leading DWORD.
     */

    if (m_fReadFromOLE10)
        {
        DWORD       dw;

        /*
         * Skip the DWORD length at the beginning of the
         * Ole10Native stream.
         */
        pIStream->Read(&dw, sizeof(DWORD), &cb);
        }

    //Read version numbers and seek back to file beginning.
    hr=pIStream->Read(&pl, 2*sizeof(WORD), &cb);

    //If we read OLE 1, seek back but skip the size header.
    LISet32(li, m_fReadFromOLE10 ? sizeof(DWORD) : 0);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);

    if (FAILED(hr) || 2*sizeof(WORD)!=cb)
        {
        pIStream->Release();
        return POLYLINE_E_READFAILURE;
        }

    /*
     * For version 2.0, read the entire file.  For version 1.0 read
     * the file up to CBPOLYLINEDATAVER10.  For anything else, give
     * an error.
     */

    switch (pl.wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (pl.wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (pl.wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    hr=pIStream->Read(&pl, cbExpect, &cb);

    if (cbExpect!=cb)
        return POLYLINE_E_READFAILURE;

    /*
     * If we loaded successfully, make the data current.  By using
     * DataSet we centralize our version upgrading.  We size the
     * polyline window to the data AND notify the document so it
     * sizes to the polyline.
     */
    DataSet(&pl, TRUE, TRUE);

    //Return what version we just loaded.
    return MAKELONG(pl.wVerMin, pl.wVerMaj);
    }

/*
 * CPolyline::WriteToStream
 *
 * Purpose:
 *  Ignorantly writes our current data into a stream in a
 *  particular version.
 *
 * Parameters:
 *  pIStream        LPSTREAM in which to save.
 *  lVer            LONG providing version number Major (HI) and
 *                  Minor (Low)
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::WriteToStream(LPSTREAM pIStream, LONG lVer)
    {
    ULONG           cb;
    ULONG           cbExpect=0;
    WORD            wVerMaj=HIWORD(lVer);
    WORD            wVerMin=LOWORD(lVer);
    POLYLINEDATA    pl;
    HRESULT         hr;

    if (NULL==pIStream)
        return POLYLINE_E_READFAILURE;

    //Get a copy of our data in the version we're going to save.
    DataGet(&pl, lVer);

    switch (wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    /*
     * If writing to an OLE 1 storage, include the length of
     * data as the first DWORD in the stream.
     */

     if (m_fReadFromOLE10 && CBPOLYLINEDATA10==cbExpect)
        {
        hr=pIStream->Write(&cbExpect, sizeof(DWORD), &cb);

        if (FAILED(hr) || sizeof(DWORD)!=cb)
            return POLYLINE_E_WRITEFAILURE;
        }

    hr=pIStream->Write(&pl, cbExpect, &cb);

    if (FAILED(hr) || cbExpect!=cb)
        return POLYLINE_E_WRITEFAILURE;

    return POLYLINE_E_NONE;
    }

//End CHAPTER18MOD

/*
 * CPolyline::ReadFromFile
 *
 * Purpose:
 *  Loads our data (any known version) from a file handle returning
 *  the version number of the data or an error value.
 *
 * Parameters:
 *  pszFile         LPTSTR of the file to open.
 *
 * Return Value:
 *  LONG            Version number or negative POLYLINE_E_* value.
 */

LONG CPolyline::ReadFromFile(LPTSTR pszFile)
    {
    OFSTRUCT        of;
    HFILE           hFile;
    POLYLINEDATA    pl;
    UINT            cb=(UINT)-1;
    UINT            cbExpect=0;

    if (NULL==pszFile)
        return POLYLINE_E_READFAILURE;

    //OpenFileW is a member of CPolyline.
    hFile=OpenFileW(pszFile, &of, OF_READ);

    if (HFILE_ERROR==hFile)
        return POLYLINE_E_READFAILURE;

    //Read version numbers and seek back to file beginning.
    cb=_lread(hFile, &pl, 2*sizeof(WORD));
    _llseek(hFile, 0L, 0);

    if (2*sizeof(WORD)!=cb)
        {
        _lclose(hFile);
        return POLYLINE_E_READFAILURE;
        }

    /*
     * For version 2.0, read the entire file.  For version 1.0 read
     * the file up to CBPOLYLINEDATAVER10.  For anything else, give
     * an error.
     */

    switch (pl.wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (pl.wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (pl.wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        {
        _lclose(hFile);
        return POLYLINE_E_UNSUPPORTEDVERSION;
        }

    cb=_lread(hFile, &pl, cbExpect);
    _lclose(hFile);

    if (cbExpect!=cb)
        return POLYLINE_E_READFAILURE;

    /*
     * If we loaded successfully, make the data current.  By using
     * DataSet we centralize our version upgrading.  We size the
     * polyline window to the data AND notify the document so it
     * sizes to the polyline.
     */
    DataSet(&pl, TRUE, TRUE);

    //Return what version we just loaded.
    return MAKELONG(pl.wVerMin, pl.wVerMaj);
    }

/*
 * CPolyline::WriteToFile
 *
 * Purpose:
 *  Ignorantly writes our current data into an opened file in a
 *  particular version.
 *
 * Parameters:
 *  pszFile         LPTSTR filename in which to store the data.
 *  lVer            LONG providing version number Major (HI)
 *                  and Minor (Low)
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::WriteToFile(LPTSTR pszFile, LONG lVer)
    {
    OFSTRUCT        of;
    HFILE           hFile;
    UINT            cb;
    UINT            cbExpect=0;
    WORD            wVerMaj=HIWORD(lVer);
    WORD            wVerMin=LOWORD(lVer);
    POLYLINEDATA    pl;

    if (NULL==pszFile)
        return POLYLINE_E_READFAILURE;

    //Get a copy of our data in the version we're going to save.
    DataGet(&pl, lVer);

    switch (wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    hFile=OpenFileW(pszFile, &of, OF_CREATE | OF_WRITE);

    if (HFILE_ERROR==hFile)
        return DOCERR_COULDNOTOPEN;

    cb=_lwrite(hFile, (LPCSTR)&pl, cbExpect);
    _lclose(hFile);

    return (cbExpect==cb) ? POLYLINE_E_NONE
        : POLYLINE_E_WRITEFAILURE;
    }

/*
 * CPolyline::OpenFileW (Private)
 *
 * Purpose:
 *  Under Win32, OpenFile does not take Unicode strings.  This
 *  function converts a Unicode string into an ANSI string and
 *  calls OpenFile.  This just maps to OpenFile without Unicode.
 *
 * Parameters, Return Value:
 *  Same as OpenFile.
 */

HFILE CPolyline::OpenFileW(LPTSTR pszFile, LPOFSTRUCT pof
    , UINT uFlags)
    {
   #ifdef UNICODE
    CHAR        szTemp[CCHPATHMAX];

    UNICODETOANSI(pszFile, szTemp, CCHPATHMAX);
    return OpenFile(szTemp, pof, uFlags);
   #else
    return OpenFile(pszFile, pof, uFlags);
   #endif
    }

/*
 * CPolyline::DataSet
 *
 * Purpose:
 *  Sets the current data in this Polyline to a given structure.
 *
 * Parameters:
 *  ppl             PPOLYLINEDATA to initialize to.
 *  fSizeToData     BOOL indicating if we're to size to the data or
 *                  scale it.
 *  fNotify         BOOL indicating if we're to send an advise on
 *                  this change.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataSet(PPOLYLINEDATA ppl, BOOL fSizeToData
    , BOOL fNotify)
    {
    RECTS       rcs;
    RECT        rc;
    UINT        i;

    /*
     * Copy the structure in ppl and repaint to reflect the new
     * point set.  Note that unlike the RectSet message, we do no
     * scaling, assuming that the rect in the structure is
     * appropriate for the data.
     */

    if (NULL==ppl)
        return POLYLINE_E_INVALIDPOINTER;

    //Preserve the old rectangle
    rcs=m_pl.rc;

    /*
     * For version 2.0 we perform a straight copy.  For version
     * 1.0 we copy the 1.0 structure and fill in defaults for
     * the 2.0 additions.
     */

    switch (ppl->wVerMaj)
        {
        case VERSIONMAJOR:          //2.x
            switch (ppl->wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    m_pl=*ppl;
                    break;

                default:
                    return POLYLINE_E_UNSUPPORTEDVERSION;
                }
            break;

        case 1:                     //1.x
            switch (ppl->wVerMin)
                {
                case 0:             //1.0
                    *((PPOLYLINEDATA10)&m_pl)=
                        *((PPOLYLINEDATA10)ppl);

                    /*
                     * Update this structure to 2.0.  Note that we
                     * assume whoever loaded us to save the loaded
                     * version so it can later ask what version
                     * the user wants to save.
                     */
                    m_pl.wVerMaj=VERSIONMAJOR;
                    m_pl.wVerMin=VERSIONMINOR;

                    /*
                     * Version 1.0 stored rc in parent coordinates.
                     * We need those now in our client coodinates.
                     */
                    RECTSTORECT(m_pl.rc, rc);
                    OffsetRect(&rc, -m_pl.rc.left, -m_pl.rc.top);

                    /*
                     * 1.0 data had points relative to size of the
                     * rectangle.  We need to scale these to 0-32767
                     * independent of the rectangle for the version
                     * upgrade.
                     */
                    for (i=0; i < m_pl.cPoints; i++)
                        PointScale(&rc, &m_pl.rgpt[i], FALSE);

                    RECTTORECTS(rc, m_pl.rc);

                    //New 2.0 features
                    m_pl.rgbBackground=GetSysColor(COLOR_WINDOW);
                    m_pl.rgbLine=GetSysColor(COLOR_WINDOWTEXT);
                    m_pl.iLineStyle=PS_SOLID;
                    break;

                default:
                    return POLYLINE_E_UNSUPPORTEDVERSION;
                }
            break;

        default:
            return POLYLINE_E_UNSUPPORTEDVERSION;
        }

    //Inform our parent of the data change
    if (NULL!=m_pAdv)
        m_pAdv->OnDataChange();

    /*
     * If we're scaling the window to fit the data, then use
     * RectSet passing our current rectangle as the new one.
     * That makes sure that the data won't change but that the
     * window is resized.
     */

    if (fSizeToData)
        {
        POINT       pt;

        /*
         * Get our offset in the parent window so we can RectSet
         * to the right place since RectSet expects rectangle in
         * parent coordinates and we get it in client coordinates.
         */
        GetWindowRect(m_hWnd, &rc);
        pt.x=rc.left;
        pt.y=rc.top;
        ScreenToClient(GetParent(m_hWnd), &pt);
        RECTSTORECT(m_pl.rc, rc);
        OffsetRect(&rc, pt.x, pt.y);

        //This will also cause a repaint.
        RectSet(&rc, fNotify);
        }
    else
        {
        //Make sure we're updated.
        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    return POLYLINE_E_NONE;
    }

/*
 * CPolyline::DataGet
 *
 * Purpose:
 *  Retrieves the Polyline's current data.
 *
 * Parameters:
 *  ppl             PPOLYLINEDATA into which we copy the data.
 *  lVer            LONG version of the data to retrieve.  Use
 *                  VERSIONCURRENT to retrieve the most current.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value
 */

LONG CPolyline::DataGet(PPOLYLINEDATA ppl, LONG lVer)
    {
    UINT        i;
    RECT        rc;

    //Retieve the current version
    if (lVer==MAKELONG(VERSIONMINOR, VERSIONMAJOR)
        || VERSIONCURRENT==lVer)
        {
        *ppl=m_pl;
        return POLYLINE_E_NONE;
        }

    //Check for versions we support, 1.x
    if (HIWORD(lVer)!=1)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    if (LOWORD(lVer)==0)    //Check for 1.0
        {
        //Do 2.0 to 1.0 conversion
        *((PPOLYLINEDATA10)ppl)=*((PPOLYLINEDATA10)&m_pl);

        RECTSTORECT(ppl->rc, rc);

        //Get the parent coordinates of our rectangle
        if (!IsWindow(m_hWnd))
            OffsetRect(&rc, 8, 8); //This is always the offset.
        else
            RectGet(&rc);

        /*
         * 1.0 data has points relative to size of the rectangle.
         * We need to scale these from 0-32767 so we have the
         * right values.
         */
        for (i=0; i < ppl->cPoints; i++)
            PointScale(&rc, &ppl->rgpt[i], TRUE);

        RECTTORECTS(rc, ppl->rc);

        //Insure old version numbers.
        ppl->wVerMaj=1;
        ppl->wVerMin=0;

        ((PPOLYLINEDATA10)ppl)->fDrawEntire=TRUE;
        return POLYLINE_E_NONE;
        }

    return POLYLINE_E_UNSUPPORTEDVERSION;
    }

/*
 * CPolyline::DataSetMem
 *
 * Purpose:
 *  Sets the Polyline's data using a global memory handle
 *  instead of a pointer.
 *
 * Parameters:
 *  hMem            HGLOBAL containing the data.
 *  fFree           BOOL indicating if we're to free the data.
 *                  The memory will be freed regardless of any
 *                  error returned from here.
 *  fSizeToData     BOOL indicating if we're to size to the data
 *                  or scale it.
 *  fNotify         BOOL indicating if we're to send an advise
 *                  on this change.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataSetMem(HGLOBAL hMem, BOOL fFree
    , BOOL fSizeToData, BOOL fNotify)
    {
    PPOLYLINEDATA   ppl;
    LONG            lRet=POLYLINE_E_INVALIDPOINTER;

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        lRet=DataSet(ppl, fSizeToData, fNotify);

        GlobalUnlock(hMem);

        if (fFree)
            GlobalFree(hMem);
        }

    return lRet;
    }

/*
 * CPolyline::DataGetMem
 *
 * Purpose:
 *  Retrieves the Polyline's data in a global memory handle.
 *
 * Parameters:
 *  lVer            LONG version of data to retrieve.
 *  phMem           HGLOBAL * in which to store the handle.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataGetMem(LONG lVer, HGLOBAL *phMem)
    {
    HGLOBAL         hMem;
    PPOLYLINEDATA   ppl;
    LONG            lRet;

    if (NULL==phMem)
        return POLYLINE_E_INVALIDPOINTER;

    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
        , CBPOLYLINEDATA);

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        lRet=DataGet(ppl, lVer);
        GlobalUnlock(hMem);

        if (POLYLINE_E_NONE!=lRet)
            {
            GlobalFree(hMem);
            hMem=NULL;
            }
        }

    *phMem=hMem;
    return lRet;
    }

/*
 * CPolyline::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectGet(LPRECT pRect)
    {
    RECT        rc;
    POINT       pt;

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_hWnd, &rc);
    pt.x=rc.left;
    pt.y=rc.top;
    ScreenToClient(GetParent(m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return;
    }

/*
 * CPolyline::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  None
 */

void CPolyline::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return;
    }

/*
 * CPolyline::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;
    RECT        rc;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, pRect->left, pRect->top
        , cx, cy, SWP_NOZORDER);

    SetRect(&rc, 0, 0, cx, cy);
    RECTTORECTS(rc, m_pl.rc);

    if (fNotify && NULL!=m_pAdv)
        m_pAdv->OnSizeChange();

    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }

/*
 * CPolyline::SizeSet
 *
 * Purpose:
 *  Sets a new size for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPolyline::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
        , SWP_NOMOVE | SWP_NOZORDER);

    if (fNotify && NULL!=m_pAdv)
        m_pAdv->OnSizeChange();

    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }

/*
 * CPolyline::ColorSet
 *
 * Purpose:
 *  Changes for background or line color in the Polyline
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color to use.
 *
 * Return Value:
 *  COLORREF        Previous color for the index iColor.
 */

COLORREF CPolyline::ColorSet(UINT iColor, COLORREF cr)
    {
    COLORREF    crRet;

    switch (iColor)
        {
        case POLYLINECOLOR_BACKGROUND:
            crRet=m_pl.rgbBackground;
            m_pl.rgbBackground=cr;
            break;

        case POLYLINECOLOR_LINE:
            crRet=m_pl.rgbLine;
            m_pl.rgbLine=cr;
            break;
        }

    //If the color changed, repaint
    if (crRet!=cr)
        {
        if (NULL!=m_pAdv)
            m_pAdv->OnColorChange();

        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    return crRet;
    }

/*
 * CPolyline::ColorGet
 *
 * Purpose:
 *  Retrieves one of the colors currently in use by the Polyline.
 *
 * Parameters:
 *  iColor          UINT identifying the color of interest.
 *
 * Return Value:
 *  COLORREF        Current color for iColor.
 */

COLORREF CPolyline::ColorGet(UINT iColor)
    {
    COLORREF    crRet;

    crRet=(POLYLINECOLOR_BACKGROUND==iColor)
        ? m_pl.rgbBackground : m_pl.rgbLine;

    return crRet;
    }

/*
 * CPolyline::LineStyleSet
 *
 * Purpose:
 *  Changes the line style in use by the Polyline

*
 * Parameters:
 *  iStyle          UINT style of the line to use.
 *
 * Return Value:
 *  UINT            Previous style.
 */

UINT CPolyline::LineStyleSet(UINT iStyle)
    {
    UINT        uRet=(UINT)m_pl.iLineStyle;

    //Validate the line style
    if (PS_SOLID==iStyle || PS_DASH==iStyle || PS_DOT==iStyle
        || PS_DASHDOT==iStyle || PS_DASHDOTDOT==iStyle)
        {
        m_pl.iLineStyle=iStyle;

        if (uRet!=(UINT)m_pl.iLineStyle)
            {
            if (NULL!=m_pAdv)
                m_pAdv->OnLineStyleChange();

            InvalidateRect(m_hWnd, NULL, TRUE);
            UpdateWindow(m_hWnd);
            }
        }

    return uRet;
    }

/*
 * CPolyline::LineStyleGet
 *
 * Purpose:
 *  Retrieves the current line style in use in the Polyline
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Current line style.
 */

UINT CPolyline::LineStyleGet(void)
    {
    return m_pl.iLineStyle;
    }

/*
 * CPolyline::RenderBitmap
 *
 * Purpose:
 *  Creates a bitmap image of the current Polyline.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HBITMAP         Handle to the newly rendered bitmap.
 */

HBITMAP CPolyline::RenderBitmap(void)
    {
    HDC         hDC;
    HDC         hMemDC;
    HBITMAP     hBmp;
    RECT        rc;
    HGDIOBJ     hObj;

    //Render a bitmap the size of the current rectangle.
    hDC=GetDC(m_hWnd);
    hMemDC=CreateCompatibleDC(hDC);

    GetClientRect(m_hWnd, &rc);
    hBmp=CreateCompatibleBitmap(hDC, rc.right, rc.bottom);

    if (NULL!=hBmp)
        {
        //Draw the POLYLINEDATA into the bitmap.
        hObj=SelectObject(hMemDC, hBmp);
        Draw(hMemDC, FALSE, TRUE);
        SelectObject(hMemDC, hObj);
        }

    DeleteDC(hMemDC);
    ReleaseDC(m_hWnd, hDC);

    return hBmp;
    }

/*
 * CPolyline::RenderMetafile
 *
 * Purpose:
 *  Renders the current image of the Polyline into a metafile.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HMETAFILE       Handle to the newly created metafile.
 */

HMETAFILE CPolyline::RenderMetafile(void)
    {
    HDC         hDC;
    HMETAFILE   hMF;
    RECT        rc;

    //Create a memory metafile and return its handle.
    hDC=(HDC)CreateMetaFile(NULL);
    hMF=NULL;

    if (NULL!=hDC)
        {
        /*
         * This is absolutely essential to the metafile so it
         * can be scaled in the clipboard and any destination
         * application.
         */
        SetMapMode(hDC, MM_ANISOTROPIC);
        GetClientRect(m_hWnd, &rc);
        SetWindowOrgEx(hDC, 0, 0, NULL);
        SetWindowExtEx(hDC, rc.right, rc.bottom, NULL);

        Draw(hDC, TRUE, TRUE);
        hMF=CloseMetaFile(hDC);
        }

    return hMF;
    }

/*
 * CPolyline::RenderMetafilePict
 *
 * Purpose:
 *  Renders the current Polyline into a METAFILEPICT structure in
 *  global memory.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HGLOBAL         Memory containing the METAFILEPICT structure.
 */

HGLOBAL CPolyline::RenderMetafilePict(void)
    {
    HGLOBAL         hMem;
    HMETAFILE       hMF;
    LPMETAFILEPICT  pMF;
    RECT            rc;

    //Get the metafile
    hMF=RenderMetafile();

    if (NULL==hMF)
        return NULL;

    //Allocate the METAFILEPICT structure.
    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
        , sizeof(METAFILEPICT));

    if (NULL==hMem)
        {
        DeleteMetaFile(hMF);
        return NULL;
        }

    /*
     * Global lock only fails in PMODE if the selector is invalid
     * (like it was discarded) or references a 0 length segment,
     * neither of which can happen here.
     */
    pMF=(LPMETAFILEPICT)GlobalLock(hMem);

    pMF->hMF=hMF;
    pMF->mm=MM_ANISOTROPIC;

    //Insert the extents in MM_HIMETRIC units.
    GetClientRect(m_hWnd, &rc);
    RectConvertMappings(&rc, FALSE);
    pMF->xExt=rc.right;
    pMF->yExt=rc.bottom;

    GlobalUnlock(hMem);
    return hMem;
    }

/*
 * CPolyline::RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device (MM_TEXT) or
 *  HIMETRIC to the other.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  fToDevice       BOOL TRUE to convert from HIMETRIC to device,
 *                  FALSE to convert device to HIMETRIC.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectConvertMappings(LPRECT pRect, BOOL fToDevice)
    {
    HDC      hDC;
    int      iLpx, iLpy;

    if (NULL==pRect)
        return;

    hDC=GetDC(NULL);
    iLpx=GetDeviceCaps(hDC, LOGPIXELSX);
    iLpy=GetDeviceCaps(hDC, LOGPIXELSY);
    ReleaseDC(NULL, hDC);

    if (fToDevice)
        {
        pRect->left=MulDiv(iLpx, pRect->left, HIMETRIC_PER_INCH);
        pRect->top =MulDiv(iLpy, pRect->top , HIMETRIC_PER_INCH);

        pRect->right=MulDiv(iLpx, pRect->right,  HIMETRIC_PER_INCH);
        pRect->bottom=MulDiv(iLpy, pRect->bottom,HIMETRIC_PER_INCH);

       #ifdef NEVER
        /*
         * In this conversion we may get situations where the top
         * coordinate is larger than the bottom, which messes us up.
         */
        if (pRect->bottom < pRect->top)
            {
            iLpy=pRect->top;
            pRect->top=pRect->bottom;
            pRect->bottom=iLpy;
            }
       #endif
        }
    else
        {
        pRect->left=MulDiv(pRect->left, HIMETRIC_PER_INCH, iLpx);
        pRect->top =MulDiv(pRect->top , HIMETRIC_PER_INCH, iLpy);

        pRect->right =MulDiv(pRect->right, HIMETRIC_PER_INCH, iLpx);
        pRect->bottom=MulDiv(pRect->bottom,HIMETRIC_PER_INCH, iLpy);
        }

    return;
    }


POLYLINE.H

/*
 * POLYLINE.H
 * Cosmo Chapter 18
 *
 * Definitions and function prototypes for the PolyLine window
 * class that can be treated like its own control.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _POLYLINE_H_
#define _POLYLINE_H_

//Versioning.
#define VERSIONMAJOR                2
#define VERSIONMINOR                0
#define VERSIONCURRENT              0x00020000

//Classname
#define SZCLASSPOLYLINE             TEXT("polyline")

//Stream Name that holds the data
#define SZSTREAM                    OLETEXT("CONTENTS")
//CHPATER18MOD
#define SZOLE1STREAM                OLETEXT("\1Ole10Native")
//End CHAPTER18MOD

#define HIMETRIC_PER_INCH           2540
#define CPOLYLINEPOINTS             20

//Window extra bytes and offsets
#define CBPOLYLINEWNDEXTRA          (sizeof(LONG))
#define PLWL_STRUCTURE              0

//Version 2.0 Polyline Structure
typedef struct tagPOLYLINEDATA
    {
    WORD        wVerMaj;                //Major version number.
    WORD        wVerMin;                //Minor version number.
    WORD        cPoints;                //Number of points.
    short       fReserved;              //Obsolete from v1.0
    RECTS       rc;                     //Rectangle of this figure
    POINTS      rgpt[CPOLYLINEPOINTS];  //Points (0-32767 grid)

    //Version 2.0 additions
    COLORREF    rgbBackground;          //Background color
    COLORREF    rgbLine;                //Line color
    short       iLineStyle;             //Line style
    } POLYLINEDATA, *PPOLYLINEDATA;

#define CBPOLYLINEDATA   sizeof(POLYLINEDATA)
#define CBPOLYLINEDATA20 sizeof(POLYLINEDATA)

//Version 1.0 Polyline Structure
typedef struct tagPOLYLINEDATA10
    {
    WORD        wVerMaj;                //Major version number.
    WORD        wVerMin;                //Minor version number.
    WORD        cPoints;                //Number of points.
    short       fDrawEntire;            //Flag to draw entire figure
    RECTS       rc;                     //Rectangle of this figure
    POINTS      rgpt[CPOLYLINEPOINTS];  //Points (scaled to rc)
    } POLYLINEDATA10, *PPOLYLINEDATA10;

#define CBPOLYLINEDATA10 sizeof(POLYLINEDATA10)

//POLYWIN.CPP
LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM, LPARAM);

class CPolyline : public CWindow
    {
    friend LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM
        , LPARAM);

    private:
        POLYLINEDATA   m_pl;

        class CPolylineAdviseSink * m_pAdv;

    //CHAPTER18MOD
    public:
        //This is for CFigure to look at when writing its storage.
        BOOL            m_fReadFromOLE10;
    //End CHAPTER18MOD

    private:
        HFILE     OpenFileW(LPTSTR, LPOFSTRUCT, UINT);
        void      PointScale(LPRECT, LPPOINTS, BOOL);
        void      Draw(HDC, BOOL, BOOL);
        void      RectConvertMappings(LPRECT, BOOL);

    public:
        CPolyline(HINSTANCE);
        ~CPolyline(void);

        BOOL      Init(HWND, LPRECT, DWORD, UINT
            , class CPolylineAdviseSink *);

        void      New(void);
        BOOL      Undo(void);

        //File functions
        LONG      ReadFromStorage(LPSTORAGE);
        LONG      WriteToStorage(LPSTORAGE, LONG);

        //CHAPTER18MOD
        LONG      ReadFromStream(LPSTREAM);
        LONG      WriteToStream(LPSTREAM, LONG);
        //End CHAPTER18MOD

        LONG      ReadFromFile(LPTSTR);
        LONG      WriteToFile(LPTSTR, LONG);

        //Data transfer functions
        LONG      DataSet(PPOLYLINEDATA, BOOL, BOOL);
        LONG      DataGet(PPOLYLINEDATA, LONG);
        LONG      DataSetMem(HGLOBAL, BOOL, BOOL, BOOL);
        LONG      DataGetMem(LONG, HGLOBAL *);
        HBITMAP   RenderBitmap(void);
        HMETAFILE RenderMetafile(void);
        HGLOBAL   RenderMetafilePict(void);

        void      RectGet(LPRECT);
        void      SizeGet(LPRECT);
        void      RectSet(LPRECT, BOOL);
        void      SizeSet(LPRECT, BOOL);
        COLORREF  ColorSet(UINT, COLORREF);
        COLORREF  ColorGet(UINT);
        UINT      LineStyleSet(UINT);
        UINT      LineStyleGet(void);
    };

typedef CPolyline *PCPolyline;

//Error values for data transfer functions
#define POLYLINE_E_NONE                    0
#define POLYLINE_E_UNSUPPORTEDVERSION      -1
#define POLYLINE_E_INVALIDPOINTER          -2
#define POLYLINE_E_READFAILURE             -3
#define POLYLINE_E_WRITEFAILURE            -4

class CPolylineAdviseSink
    {
    private:
        LPVOID      m_pv;           //Customizable structure

    public:
        CPolylineAdviseSink(LPVOID);
        ~CPolylineAdviseSink(void);

        void OnPointChange(void);
        void OnSizeChange(void);
        void OnDataChange(void);
        void OnColorChange(void);
        void OnLineStyleChange(void);
    };

typedef CPolylineAdviseSink *PCPolylineAdviseSink;

//Color indices for color messages
#define POLYLINECOLOR_BACKGROUND    0
#define POLYLINECOLOR_LINE          1

#endif  //_POLYLINE_H_


POLYWIN.CPP

/*
 * POLYWIN.CPP
 * Cosmo Chapter 18
 *
 * Window procedure for the polyline drawing window and support
 * functions.  This window is not complicated.  On creation it
 * allocates a block of memory for a POLYLINEDATA structure that
 * contains 20 POINTs.  We do not attempt to reallocate this
 * array at all just to maintain simplicity.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * PolylineWndProc
 *
 * Purpose:
 *  Window procedure for the polyline drawing window.
 */

LRESULT APIENTRY PolylineWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCPolyline      ppl;
    PAINTSTRUCT     ps;
    HDC             hDC;
    POINTS          pt;
    RECT            rc;

    ppl=(PCPolyline)GetWindowLong(hWnd, PLWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppl=(PCPolyline)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PLWL_STRUCTURE, (LONG)ppl);
            ppl->m_hWnd=hWnd;
            ppl->New();
            break;

        case WM_PAINT:
            hDC=BeginPaint(hWnd, &ps);
            ppl->Draw(hDC, FALSE, TRUE);
            EndPaint(hWnd, &ps);
            break;

        case WM_LBUTTONDOWN:
            //Stop if we are already at the limit.
            if (CPOLYLINEPOINTS==ppl->m_pl.cPoints)
                {
                MessageBeep(0);
                break;
                }

            //Convert the points into 0-32767 range
            GetClientRect(hWnd, &rc);
            pt=MAKEPOINTS(lParam);
            ppl->PointScale(&rc, &pt, FALSE);

            ppl->m_pl.rgpt[ppl->m_pl.cPoints++]=pt;

            //Draw the lines to this new point only.
            hDC=GetDC(hWnd);
            ppl->Draw(hDC, FALSE, FALSE);
            ReleaseDC(hWnd, hDC);

            if (NULL!=ppl->m_pAdv)
                ppl->m_pAdv->OnPointChange();

            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPolyline::Draw
 *
 * Purpose:
 *  Paints the current line in the polyline window.
 *
 * Parameters:
 *  hDC             HDC to draw on, could be metafile or printer DC.
 *  fMetafile       BOOL indicating if hDC is a metafile or not, so
 *                  we can avoid operations that RIP.
 *  fEntire         BOOL indicating if we should draw the entire
 *                  figure or not.
 *
 * Return Value:
 *  None
 */

void CPolyline::Draw(HDC hDC, BOOL fMetafile, BOOL fEntire)
    {
    HBRUSH          hBrush;
    HPEN            hPen;
    HGDIOBJ         hObj1, hObj2;
    UINT            i, j;
    UINT            uMM;
    POINTS          pt1, pt2;
    POINT           rgpt[CPOLYLINEPOINTS];
    RECT            rc;

    GetClientRect(m_hWnd, &rc);

    /*
     * Make a 32-bit copy of the points in the POLYLINE itself
     * so we can use DPtoLP and LPtoDP.
     */
    for (i=0; i < m_pl.cPoints; i++)
        {
        rgpt[i].x=m_pl.rgpt[i].x;
        rgpt[i].y=m_pl.rgpt[i].y;
        }

    /*
     * If the mapping mode is not MM_TEXT, convert the points to
     * whatever mapping mode in in effect before drawing.
     * This specifically supports metafiles in MM_ANISOTROPIC.
     */
    uMM=fMetafile ? MM_TEXT : GetMapMode(hDC);

    if (MM_TEXT!=uMM)
        DPtoLP(hDC, rgpt, m_pl.cPoints);

    hPen=CreatePen(m_pl.iLineStyle, 1, m_pl.rgbLine);
    hObj1=SelectObject(hDC, hPen);

    hBrush=CreateSolidBrush(m_pl.rgbBackground);
    hObj2=SelectObject(hDC, hBrush);
    SetBkColor(hDC, m_pl.rgbBackground);

    /*
     * Either draw the entire figure or just a single point.  The
     * entire figure also includes erasing the background
     * completely,since hDC may be a metafile DC.  Drawing a
     * single point just updates the figure for that new point.
     */
    if (fEntire || 0==m_pl.cPoints)
        {
        //Erase the background for bitmaps and metafiles.
        SelectObject(hDC, GetStockObject(NULL_PEN));
        Rectangle(hDC, rc.left, rc.top, rc.right+1, rc.bottom+1);
        SelectObject(hDC, hPen);

        /*
         * If we are drawing the entire figure, then loop through
         * each point drawing a line to each successive point.
         */

        for (i=0; i < m_pl.cPoints; i++)
            {
            for (j=i; j < m_pl.cPoints; j++)
                {
                /*
                 * Not using POINTTOPOINTS since it unfortunately
                 * returns a LONG which doesn't equate to a POINTS.
                 */
                pt1.x=(short)rgpt[i].x;
                pt1.y=(short)rgpt[i].y;
                pt2.x=(short)rgpt[j].x;
                pt2.y=(short)rgpt[j].y;

                PointScale(&rc, &pt1, TRUE);
                PointScale(&rc, &pt2, TRUE);
                MoveToEx(hDC, pt1.x, pt1.y, NULL);
                LineTo(hDC, pt2.x, pt2.y);
                }
            }
        }
    else
        {
        /*
         * If we are only drawing the last point, just cycle once
         * through previous points.
         */

        //Get the last point entered in the array.
        j=m_pl.cPoints-1;
        pt1.x=(short)rgpt[j].x;
        pt1.y=(short)rgpt[j].y;
        PointScale(&rc, &pt1, TRUE);

        for (i=0; i < j; i++)
            {
            pt2.x=(short)rgpt[i].x;
            pt2.y=(short)rgpt[i].y;
            PointScale(&rc, &pt2, TRUE);

            MoveToEx(hDC, pt1.x, pt1.y, NULL);
            LineTo(hDC, pt2.x, pt2.y);
            }
        }

    //If we have one point, draw a dot to indicate it's position.
    if (1==m_pl.cPoints)
        {
        pt1.x=(short)rgpt[0].x;
        pt1.y=(short)rgpt[0].y;
        PointScale(&rc, &pt1, TRUE);
        SetPixel(hDC, pt1.x, pt1.y, m_pl.rgbLine);
        }

    //Note:  m_pl.rgpt is unmodified in this code.

    SelectObject(hDC, hObj1);
    SelectObject(hDC, hObj2);
    DeleteObject(hBrush);
    DeleteObject(hPen);
    return;
    }

/*
 * CPolyline::PointScale
 *
 * Purpose:
 *  Scales a point to or from a relative window coordinate to a
 *  0-32767 coordinate.
 *
 * Parameters:
 *  pRect           LPRECT of the window.
 *  ppt             LPPOINTS to convert
 *  fScaleToWindow  BOOL indicating direction of scaling.
 *
 * Return Value:
 *  None
 */

void CPolyline::PointScale(LPRECT pRect, LPPOINTS ppt
    , BOOL fScaleToWindow)
    {
    DWORD   cx, cy;

    //Window size
    cx=(DWORD)(pRect->right-pRect->left);
    cy=(DWORD)(pRect->bottom-pRect->top);

    //Prevent crashes
    if (0L==cx) cx=1;
    if (0L==cy) cy=1;

    //Must use DWORD to insure proper scaling.
    if (fScaleToWindow)
        {
        ppt->x=(UINT)(((DWORD)ppt->x*cx) >> 15);
        ppt->y=(UINT)(((DWORD)ppt->y*cy) >> 15);
        }
    else
        {
        ppt->x=(UINT)(((DWORD)ppt->x << 15)/cx);
        ppt->y=(UINT)(((DWORD)ppt->y << 15)/cy);
        }

    return;
    }


RESOURCE.H

/*
 * RESOURCE.H
 * Cosmo Chapter 18
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Note that we augment classlib.h which we include before this.

//Position of Window menu.
#undef  WINDOW_MENU
#define WINDOW_MENU             4

//Total number of menus
#undef  CMENUS
#ifdef MDI
#define CMENUS                  6
#else
#define CMENUS                  5
#endif

//Menu command identifiers.

#define IDM_FILEIMPORT          (IDM_CUSTOMFILEMIN)

#define IDM_COLORBACKGROUND     (IDM_CUSTOMMIN)
#define IDM_COLORLINE           (IDM_CUSTOMMIN+1)

#define IDM_LINEMIN             (IDM_CUSTOMMIN+100)
#define IDM_LINESOLID           (IDM_LINEMIN+0)         //PS_SOLID       0
#define IDM_LINEDASH            (IDM_LINEMIN+1)         //PS_DASH        1
#define IDM_LINEDOT             (IDM_LINEMIN+2)         //PS_DOT         2
#define IDM_LINEDASHDOT         (IDM_LINEMIN+3)         //PS_DASHDOT     3
#define IDM_LINEDASHDOTDOT      (IDM_LINEMIN+4)         //PS_DASHDOTDOT  4
#define IDM_LINEMAX             IDM_LINEDASHDOTDOT

/*
 * IDs for StatStrip handling of popup menus:  must be in order of menu
 * CLASSRES.H already defines ID_MENUFILE and ID_MENUEDIT for us.
 */

#undef  ID_MENUWINDOW
#undef  ID_MENUHELP

#define ID_MENUCOLOR            (ID_MENUCUSTOMMIN+0)
#define ID_MENULINE             (ID_MENUCUSTOMMIN+1)
#define ID_MENUWINDOW           (ID_MENUCUSTOMMIN+2)
#define ID_MENUHELP             (ID_MENUCUSTOMMIN+3)

//Stringtable IDs.  Keep sequential for each group.
#define IDS_FRAMEMIN            IDS_STANDARDFRAMEMIN
#define IDS_FILEIMPORT          (IDS_CUSTOMFRAMEMIN+0)
//CHAPTER18MOD
#define IDS_FIGUREINCONTAINER   (IDS_CUSTOMFRAMEMIN+1)
#define IDS_EXITANDRETURN       (IDS_CUSTOMFRAMEMIN+2)
#define IDS_EMBEDDINGCAPTION    (IDS_CUSTOMFRAMEMIN+3)
#define IDS_SAVECOPYAS          (IDS_CUSTOMFRAMEMIN+4)
#define IDS_NEW                 (IDS_CUSTOMFRAMEMIN+5)
#define IDS_OPEN                (IDS_CUSTOMFRAMEMIN+6)
#define IDS_CLOSE2              (IDS_CUSTOMFRAMEMIN+7)
#define IDS_SAVE                (IDS_CUSTOMFRAMEMIN+8)
#define IDS_SAVEAS              (IDS_CUSTOMFRAMEMIN+9)
#define IDS_EXIT                (IDS_CUSTOMFRAMEMIN+10)
#define IDS_FRAMEMAX            (IDS_CUSTOMFRAMEMIN+11)
//End CHAPTER18MOD

#define IDS_DOCUMENTMIN         IDS_STANDARDDOCMIN
#define IDS_VERSIONCHANGE       (IDS_CUSTOMDOCMIN+0)
//CHAPTER18MOD
#define IDS_OBJECTDESCRIPTION   (IDS_CUSTOMDOCMIN+1)
#define IDS_DOCUMENTMAX         (IDS_CUSTOMDOCMIN+1)

//Strings for the CFigure object implementation.
#define IDS_FIGUREMIN           256
#define IDS_CLOSECAPTION        (IDS_FIGUREMIN+1)
#define IDS_CLOSEPROMPT         (IDS_FIGUREMIN+2)
#define IDS_USERTYPE            (IDS_FIGUREMIN+3)
#define IDS_FORMAT              (IDS_FIGUREMIN+4)

#define IDS_FIGUREMAX           (IDS_FIGUREMIN+4)

//End CHAPTER18MOD

//These are specifically for the StatStrip
#define IDS_STATMESSAGEMIN                  IDS_STANDARDSTATMESSAGEMIN

#define IDS_ITEMMESSAGEFILEIMPORT           (IDS_CUSTOMSTATMESSAGEMIN+0)

#define IDS_MENUMESSAGECOLOR                (IDS_CUSTOMSTATMESSAGEMIN+1)
#define IDS_ITEMMESSAGECOLORBACKGROUND      (IDS_CUSTOMSTATMESSAGEMIN+2)
#define IDS_ITEMMESSAGECOLORLINE            (IDS_CUSTOMSTATMESSAGEMIN+3)

#define IDS_MENUMESSAGELINE                 (IDS_CUSTOMSTATMESSAGEMIN+4)
#define IDS_ITEMMESSAGELINESOLID            (IDS_CUSTOMSTATMESSAGEMIN+5)
#define IDS_ITEMMESSAGELINEDASH             (IDS_CUSTOMSTATMESSAGEMIN+6)
#define IDS_ITEMMESSAGELINEDOT              (IDS_CUSTOMSTATMESSAGEMIN+7)
#define IDS_ITEMMESSAGELINEDASHDOT          (IDS_CUSTOMSTATMESSAGEMIN+8)
#define IDS_ITEMMESSAGELINEDASHDOTDOT       (IDS_CUSTOMSTATMESSAGEMIN+9)

#define IDS_STATMESSAGEMAX                  (IDS_CUSTOMSTATMESSAGEMIN+9)

#endif //_RESOURCE_H_


CLIP.C

/*
 * CLIP.C
 *
 * Functions to interact with the clipboard.
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

#include <windows.h>
#include <ole.h>
#include "cosmo.h"
#include "oleglobl.h"

/*
 * FEditCut
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard and clears the editor window with the equivalent
 *  to complete the "Cut" operation.  This operation also sets the
 *  dirty flag through a call to FDirtySet.
 *
 * Parameters:
 *  pGlob           LPGLOBALS to the global variable block.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 *
 */

BOOL WINAPI FEditCut(LPGLOBALS pGlob)
    {
    //Copy editor contents to the clipboard.
    if (!FEditCopy(pGlob, TRUE))
        return FALSE;

    //Clear out the editor contents.
    SendMessage(pGlob->hWndPolyline, PLM_POLYLINENEW, 0, 0L);
    FDirtySet(TRUE);
    return TRUE;
    }

/*
 * FEditCopy
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard.
 *
 *
 * Parameters:
 *  pGlob           LPGLOBALS to the global variable block.
 *  fCut            BOOL indicating if this copy is part of a Cut.
 *
 * Return Value:
 *  BOOL            TRUE if anything was successfully placed on the
 *                  clipboard, FALSE otherwise.
 *
 */

BOOL WINAPI FEditCopy(LPGLOBALS pGlob, BOOL fCut)
    {
    BOOL            fRet=TRUE;
    HGLOBAL         hMem;

    //Attempt to gain clipboard ownership.
    if (!OpenClipboard(pGlob->hWnd))
        return FALSE;

    //Clean out whatever junk is in the clipboard.
    EmptyClipboard();

    //Copy private data first.
    hMem=HGetPolyline(pGlob->hWndPolyline);

    //Copy private data (not "Native") we used prior to OLE.
    if (NULL!=hMem)
        SetClipboardData(pGlob->cfCosmo, hMem);
    else
        fRet &=FALSE;

#ifdef MAKEOLESERVER

    //Copy Native data.
    fRet &=FOLECopyNative(pOLE);

    //Copy "OwnerLink" data.
    fRet &=FOLECopyLink(pOLE, TRUE, pGlob->szFile);

#endif //MAKEOLESERVER

    //Send the METAFILEPICT to the clipboard if we have one.
    hMem=HGetMetafilePict(pGlob->hWndPolyline);

    if (NULL!=hMem)
        SetClipboardData(CF_METAFILEPICT, hMem);
    else
        fRet &=FALSE;

    //Send the bitmap to the clipboard if we can get one.
    hMem=HGetBitmap(pGlob->hWndPolyline);

    if (NULL!=hMem)
        SetClipboardData(CF_BITMAP, hMem);
    else
        fRet &=FALSE;

#ifdef MAKEOLESERVER

    //Copy "ObjectLink" data if we have a filename AND are not cutting.
    if (pGlob->fOpenFile && 0!=pGlob->szFile[0] && !fCut)
        {
        fRet &=FOLECopyLink(pOLE, FALSE, pGlob->szFile);
        }

#endif //MAKEOLESERVER

    //Free clipboard ownership.
    CloseClipboard();
    return fRet;
    }

/*
 * FEditPaste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  to the current figure in the editor window.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  pGlob           LPGLOBALS to the global variable block.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 *
 */

BOOL WINAPI FEditPaste(LPGLOBALS pGlob)
    {
    HGLOBAL         hMem;
    LPPOLYLINE      lppl;

    //Attempt to gain clipboard ownership.
    if (!OpenClipboard(pGlob->hWnd))
        return FALSE;

    hMem=GetClipboardData(pGlob->cfCosmo);

    if (NULL==hMem)
        {
        CloseClipboard();
        return FALSE;
        }

    lppl=(LPPOLYLINE)GlobalLock(hMem);

    //TRUE in wParam to cause PLN_SIZECHANGE notification
    SendMessage(pGlob->hWndPolyline, PLM_POLYLINESET, TRUE, (LONG)lppl);
    GlobalUnlock(hMem);

    FDirtySet(TRUE);
    CloseClipboard();
    return TRUE;
    }

/*
 * HGetPolyline
 *
 * Purpose:
 *  Allocates global memory and copies the current Polyline into it.
 *
 * Parameters:
 *  hWnd            HWND of the PolyLine window whose data we want.
 *
 * Return Value:
 *  HGLOBAL         Global handle if successful, NULL otherwise.
 */

HGLOBAL WINAPI HGetPolyline(HWND hWnd)
    {
    LPPOLYLINE  lppl;
    HGLOBAL     hMem;

    //Allocate a copy of the POLYLINE structure and set it as private data.
    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, CBPOLYLINE);

    if (NULL!=hMem)
        {
        lppl=(LPPOLYLINE)GlobalLock(hMem);
        SendMessage(hWnd, PLM_POLYLINEGET, 0, (LONG)lppl);
        GlobalUnlock(hMem);
        }

    return hMem;
    }

/*
 * HGetMetafilePict
 *
 * Purpose:
 *  Retrieves a metafile for the current polyline image.
 *
 * Parameters:
 *  hWnd            HWND of the PolyLine window whose image we want.
 *
 * Return Value:
 *  HGLOBAL         Global handle to a METAFILEPICT structure if
 *                  successful, NULL otherwise.
 */

HGLOBAL WINAPI HGetMetafilePict(HWND hWnd)
    {
    HGLOBAL     hMem;
    DWORD       dw;

    //Retrieve a METAFILEPICT structure for this object.
    dw=SendMessage(hWnd, PLM_METAFILEPICTGET, 0, 0L);
    hMem=(HGLOBAL)(UINT)dw;

    return hMem;
    }

/*
 * HGetBitmap
 *
 * Purpose:
 *  Retrieves a device-dependent bitmap for the current polyline image.
 *
 * Parameters:
 *  hWnd            HWND of the PolyLine window whose image we want.
 *
 * Return Value:
 *  HGLOBAL         Bitmap handle if successful, NULL otherwise.
 */

HGLOBAL WINAPI HGetBitmap(HWND hWnd)
    {
    HGLOBAL     hMem;
    DWORD       dw;

    dw=SendMessage(hWnd, PLM_BITMAPGET, 0, 0L);
    hMem=(HGLOBAL)(UINT)dw;

    return hMem;
    }


COMMDLG.C

/*
 * COMMDLG.C
 *
 * Routines to interface to the COMMDLG library for File Open and
 * File Save/Save As functions.
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

#include <windows.h>
#include <commdlg.h>
#include "cosmo.h"

/*
 * FSaveOpenDialog
 *
 * Purpose:
 *  Invokes the COMMDLG.DLL GetOpenFileName dialog and retrieves
 *  a filename for saving or opening.
 *
 * Parameters:
 *  hWnd            HWND of the owning application.
 *  hInst           HINSTANCE of the application instance.
 *  pszExt          LPSTR of the default extension
 *  pszFilter       LPSTR of the filter desciption.
 *  pszFile         LPSTR buffer to receive the entered filename.
 *                  Must be at least CCHPATHMAX long.
 *  fOpen           BOOL indicating if we want file open or save.
 *
 * Return Value:
 *  BOOL            TRUE if the function retrieved a filename,
 *                  FALSE if the user pressed CANCEL.
 */

BOOL WINAPI FSaveOpenDialog(HWND hWnd, HINSTANCE hInst, LPSTR pszExt
    , LPSTR pszFilter, LPSTR pszFile, LPSTR pszCaption, BOOL fOpen)
    {
    OPENFILENAME    ofn;
    char            szTitle[CCHFILENAMEMAX];
    char            szFilter[80];
    UINT            cch1;
    UINT            cch2;

    ofn.lStructSize      =sizeof(OPENFILENAME);
    ofn.hwndOwner        =hWnd;
    ofn.hInstance        =hInst;

    ofn.lpstrFilter      =szFilter;
    ofn.lpstrCustomFilter=NULL;
    ofn.nMaxCustFilter   =0L;
    ofn.nFilterIndex     =1L;                //We only have 1 extension.

    ofn.lpstrFile        =pszFile;
    ofn.nMaxFile         =CCHPATHMAX;
    ofn.lpstrFileTitle   =(LPSTR)szTitle;
    ofn.nMaxFileTitle    =CCHFILENAMEMAX;

    ofn.lpstrInitialDir  =NULL;
    ofn.lpstrTitle       =pszCaption;

    ofn.Flags            =OFN_HIDEREADONLY;
    ofn.nFileOffset      =0;
    ofn.nFileExtension   =0;
    ofn.lpstrDefExt      =pszExt;
    ofn.lCustData        =0;
    ofn.lpfnHook         =NULL;
    ofn.lpTemplateName   =NULL;

    //Modify the flags as appropriate.
    if (fOpen)
        ofn.Flags        |= OFN_FILEMUSTEXIST;
    else
        ofn.Flags        |= OFN_PATHMUSTEXIST | OFN_OVERWRITEPROMPT;

    //Build a filter like "pszFilter\0*.pszExt\0\0"
    lstrcpy(szFilter, pszFilter);
    cch1=1+lstrlen(szFilter);

    cch2=wsprintf(pszFile, "*.%s", pszExt);  //Initial edit control contents.
    lstrcpy(szFilter+cch1, pszFile);         //Append to filter.

    //Add the second null-terminator.
    *(szFilter+cch1+cch2+1)=0;

    return GetOpenFileName(&ofn);
    }


COSMO.C

/*
 * COSMO.C
 *
 * Basic Windows application code, with only a few specifics for handling
 * blocking as far as OLE goes.  Most OLE interaction is directed through
 * other functions in other source files that actually make the OLE library
 * calls.
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

#include <windows.h>
#include <ole.h>
#include "cosmo.h"
#include "oleglobl.h"

/*
 * Array of pointers to strings loaded from the resource file.
 * Pointers can be near since we'll use LocalAlloc for
 * the string space.
 */

char NEAR   *rgpsz[CSTRINGS];

//Global variable block.
GLOBALS     stGlobals;
LPGLOBALS   pGlob=&stGlobals;

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.   Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 *
 * Parameters:
 *  See Windows SDK Guide to Programming, page 2-3
 *
 * Return Value:
 *  Value to return to Windows--termination code.
 *
 */

int PASCAL WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance
    , LPSTR lpszCmdLine, int nCmdShow)
    {
    HACCEL      hAccel;
    HWND        hWnd;
    MSG         msg;

    pGlob->hInst=hInstance;
    pGlob->pszCmdLine=lpszCmdLine;
    pGlob->nCmdShow=nCmdShow;

    /*
     * Try to initialize; on failure, clean up anything that might have
     * been allocated.
     */
    if (!FApplicationInit(pGlob, hPrevInstance))
        {
        FApplicationExit(pGlob);
        return FALSE;
        }

    hWnd=CreateWindow(rgpsz[IDS_CLASSCOSMO], rgpsz[IDS_CAPTION]
        , WS_MINIMIZEBOX | WS_OVERLAPPEDWINDOW, CW_USEDEFAULT
        , CW_USEDEFAULT, 400, 350, NULL, NULL, hInstance, NULL);

    if (NULL==hWnd)
        {
        FApplicationExit(pGlob);
        return FALSE;
        }

    //Prevent the WM_SIZE from ShowWindow from making us dirty.
    pGlob->fNoDirty=TRUE;
    ShowWindow(hWnd, pGlob->nCmdShow);
    pGlob->fNoDirty=FALSE;

    if (SW_HIDE != pGlob->nCmdShow)
        UpdateWindow(hWnd);

    hAccel=LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATORS));

    //Non-OLE message loop.
    while (GetMessage(&msg, NULL, 0,0 ))
        {
        if (!TranslateAccelerator(hWnd, hAccel, &msg))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    FApplicationExit(pGlob);
    return msg.wParam;
    }

/*
 * CosmoWndProc
 *
 * Purpose:
 *  Window class procedure.  Standard callback.
 *
 * Parameters:
 *  The standard.
 *
 * Return Value:
 *  The standard.
 *
 */

LRESULT WINAPI CosmoWndProc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam)
    {
    static BOOL fNoSizePoly;
    RECT        rc;
    BOOL        fOK;
    UINT        uTemp;
    DWORD       dwStyle;
    WORD        wID=LOWORD(wParam);

   #ifdef WIN32
    WORD        wCode=HIWORD(wParam);
    HWND        hWndMsg=(HWND)(UINT)lParam;
   #else
    WORD        wCode=HIWORD(lParam);
    HWND        hWndMsg=(HWND)(UINT)lParam;
   #endif

    switch (iMsg)
        {
        case WM_CREATE:
            //Set global variable defaults that pertain to this window.
            pGlob->hWnd=hWnd;
            pGlob->fOpenFile=FALSE;
            pGlob->fNoDirty=FALSE;

            FDirtySet(FALSE);

            //Create the secondary edit window.
            pGlob->hWndPolyline=HPolylineWindowCreate(hWnd, pGlob->hInst);

            //Set the initial window title.
            WindowTitleSet(pGlob->hWnd, rgpsz[IDS_UNTITLED]);

            if (!FFileInit(pGlob))
                PostMessage(hWnd, WM_CLOSE, 0, 0L);

            fNoSizePoly=FALSE;
            break;

        case WM_SIZE:
            /* If we are getting WM_SIZE in response to a Polyline
             * notification, then don't resize it again.
             */

            if (fNoSizePoly)
                break;

            //Resize the polyline editor window to fit the new client area.
            GetClientRect(hWnd, &rc);
            InflateRect(&rc, -8, -8);

            //Tell the Polyline window to resize without notifying us.
            SendMessage(pGlob->hWndPolyline, PLM_RECTSET, FALSE
                , (LONG)(LPSTR)&rc);

            /*
             * We consider sizing something that makes the file dirty, but
             * not until we've finished the create process, which is why
             * we set fDirty to FALSE after the ShowWindow above, because
             * ShowWindow sends a WM_SIZE when the window first appears.
             */
            FDirtySet(TRUE);
            break;

        case WM_GETMINMAXINFO:
            {
            //Limit the smallest tracking size allowable.
           #ifdef WIN32
            LPMINMAXINFO    pmmi=(LPMINMAXINFO)lParam;

            pmmi->ptMinTrackSize.x=158;
            pmmi->ptMinTrackSize.y=168;
           #else
            LPPOINT     ppt=(LPPOINT)lParam;

            ppt[3].x=158;
            ppt[3].y=168;
           #endif
            }
            break;

        case WM_CLOSE:
            if (!FFileExit(pGlob))
                break;

            DestroyWindow(hWnd);    //Same as DefWindowProc's action.
            break;

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_INITMENUPOPUP:
            /*
             * Check for Save possibility in File menu position 0.
             * In and OLE situation, this is always enabled.
             */
            if (!pGlob->fOLE && 0==LOWORD(lParam))
                {
                uTemp=(pGlob->fOpenFile) ? MF_ENABLED : (MF_DISABLED | MF_GRAYED);
                EnableMenuItem((HMENU)wParam, IDM_FILESAVE, uTemp | MF_BYCOMMAND);
                }

            //Check for possibility of Paste for edit menu position 1
            if (1==LOWORD(lParam))
                {
                fOK=IsClipboardFormatAvailable(pGlob->cfCosmo);
                uTemp=(fOK) ? MF_ENABLED : (MF_DISABLED | MF_GRAYED);
                EnableMenuItem((HMENU)wParam, IDM_EDITPASTE, uTemp | MF_BYCOMMAND);
                }

            break;

        case WM_COMMAND:
            switch (wID)
                {
                case ID_POLYLINE:
                    if (PLN_POINTCHANGE==wCode)
                        {
                        FDirtySet(TRUE);
                        break;
                        }

                    /*
                     * Polyline window is informing us that it changed size
                     * in response to setting it's data.  Therefore we
                     * have to size ourselves accordingly.
                     */
                    if (PLN_SIZECHANGE==wCode)
                        {
                        //Calculate new DIMENSIONS (we won't move)
                        GetWindowRect(pGlob->hWndPolyline, &rc);
                        InflateRect(&rc, 8, 8);

                        dwStyle=GetWindowLong(pGlob->hWnd, GWL_STYLE);
                        AdjustWindowRect(&rc, dwStyle, TRUE);

                        fNoSizePoly=TRUE;
                        SetWindowPos(pGlob->hWnd, NULL, 0, 0
                            , rc.right-rc.left, rc.bottom-rc.top
                            , SWP_NOMOVE | SWP_NOZORDER);

                        fNoSizePoly=FALSE;
                        FDirtySet(TRUE);
                        }
                    break;

                case IDM_FILENEW:
                    FFileNew(pGlob);
                    break;

                case IDM_FILEOPEN:
                    FFileOpen(pGlob, FALSE);
                    break;

                case IDM_FILESAVE:
                    FFileSave(pGlob);
                    break;

                case IDM_FILESAVEAS:
                    fOK=FFileSaveAs(pGlob);
                    return MAKELONG(fOK, 0);

                case IDM_FILEIMPORT:
                    FFileOpen(pGlob, TRUE);
                    break;

                case IDM_FILEEXIT:
                    PostMessage(hWnd, WM_CLOSE, 0, 0L);
                    break;

                case IDM_EDITUNDO:
                    SendMessage(pGlob->hWndPolyline, PLM_BACKUPUNDO, 0, 0L);
                    break;

                case IDM_EDITCUT:
                    FEditCut(pGlob);
                    break;

                case IDM_EDITCOPY:
                    FEditCopy(pGlob, FALSE);
                    break;

                case IDM_EDITPASTE:
                    FEditPaste(pGlob);
                    break;

                case IDM_HELPABOUT:
                   #ifdef WIN32
                    DialogBox(pGlob->hInst, MAKEINTRESOURCE(IDD_ABOUT)
                        , pGlob->hWnd, AboutProc);
                   #else
                    {
                    DLGPROC     lpfn;

                    lpfn=(DLGPROC)MakeProcInstance((FARPROC)AboutProc, pGlob->hInst);
                    DialogBox(pGlob->hInst, MAKEINTRESOURCE(IDD_ABOUT)
                        , pGlob->hWnd, lpfn);
                    FreeProcInstance((FARPROC)lpfn);
                    }
                   #endif

                    break;
                }
            break;

        default:
            return (DefWindowProc(hWnd, iMsg, wParam, lParam));
        }

    return 0L;
    }

/*
 * AboutProc
 *
 * Purpose:
 *  Dialog procedure for the omnipresent About box.
 *
 * Parameters:
 *  The standard.
 *
 * Return Value:
 *  The value to be returned through the DialogBox call that
 *  created the dialog.
 *
 */

BOOL WINAPI AboutProc(HWND hDlg, UINT iMsg, UINT wParam, LONG lParam)
    {
    switch (iMsg)
        {
        case WM_INITDIALOG:
            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam))
                {
                case IDOK:
                    EndDialog(hDlg, TRUE);
                }
            break;
        }
    return FALSE;
    }


COSMO.H

/*
 * COSMO.H
 *
 * Definitions and function prototypes for the OLE Cosmo Server.
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

#include "polyline.h"

//Resource identifiers.
#define IDR_MENU            1
#define IDR_ACCELERATORS    1
#define IDD_ABOUT           1

//POLYLINE Window ID
#define ID_POLYLINE         10

//Menu command identifiers.
#define IDM_FILENEW         100
#define IDM_FILEOPEN        101
#define IDM_FILESAVE        102
#define IDM_FILESAVEAS      103
#define IDM_FILEIMPORT      104
#define IDM_FILEEXIT        110

#define IDM_EDITUNDO        200
#define IDM_EDITCUT         201
#define IDM_EDITCOPY        202
#define IDM_EDITPASTE       203

#define IDM_HELPABOUT       300

//File-related string lengths.
#define CCHPATHMAX          256
#define CCHFILENAMEMAX      15

/*
 * CSTRINGS is number of strings to load from the stringtable.
 * CCHSTRINGMAX is the maximum length that any string is allowed.
 *
 * What will happen is that CSTRINGS*CCHSTRINGMAX is allocated to begin
 * with and the stringtable is loaded into that space, Only using what's
 * required for each string.  After that, the space is reallocated to
 * be as small as possible.
 */

#define CSTRINGS            30
#define CCHSTRINGMAX        80

//String ID values.  Keep in SEQUENTIAL order and use 0-n
#define IDS_CAPTION         0
#define IDS_CLASSCOSMO      1
#define IDS_CLASSPOLYLINE   2
#define IDS_FILEDIRTY       3
#define IDS_DEFEXT          4
#define IDS_FILEOPENFILTER  5
#define IDS_FILEOPEN        6
#define IDS_FILESAVEAS      7
#define IDS_FILEIMPORT      8
#define IDS_FULLNAME        9
#define IDS_FIGURE          10
#define IDS_DOTEXT          11
#define IDS_MODULE          12
#define IDS_UNTITLED        13
#define IDS_EMPTY           14

#define IDS_VERBEDIT        15
#define IDS_UPDATE          16
#define IDS_SAVE            17
#define IDS_SAVEAS          18
#define IDS_SAVECOPYAS      19
#define IDS_EXIT            20
#define IDS_EXITANDRETURN   21
#define IDS_EMBEDDING       22
#define IDS_NATIVE          23
#define IDS_DATAFORMATS     24
#define IDS_STDFILEEDITING  25
#define IDS_OWNERLINK       26
#define IDS_OBJECTLINK      27

#define IDS_CLOSEALERT1     28
#define IDS_CLOSEALERT2     29

/*
 * Structure holding the "global" variables.  Creating a structure with
 * has several advantages over separately declaring each field as a
 * global:
 *  1.  Keep source files clean.
 *  2.  Eliminates need for many "extern" declarations.
 *  3.  A single pointer to this structure can be passed throughout
 *      the application, hiding the fact that it's global.
 *  4.  Allows the variables to be allocated dynamically or from
 *      different memory than the application's DS.
 *  5.  Any reference to these variables will have a pointer or
 *      structure dereference, which points to where the variable
 *      actually is defined.  Separate globals are not distinguishable
 *      from locals, making code harder to read.
 *
 * Note that fNoDirty is used from OLEOBJ.C in the ObjShow method
 * to prevent setting fDirty when the window is sized from ObjShow.
 */

typedef struct
    {
    HWND        hWnd;               //Top-level application window.
    HWND        hWndPolyline;       //Editor window.
    HINSTANCE   hInst;              //Application instance handle.
    LPSTR       pszCmdLine;         //Command line passed to WinMain.
    UINT        nCmdShow;           //Initial ShowWindow command.
    HLOCAL      hStringMem;         //Stringtable memory.
    UINT        cfCosmo;            //Private clipboard format.
    BOOL        fDirty;             //Is file dirty?
    BOOL        fNoDirty;           //If TRUE, don't touch dirty flag.
    BOOL        fOpenFile;          //FALSE if File/New used until saved.
    char        szFile[CCHPATHMAX]; //Filename for Save command.

    BOOL        fOLE;               //Indicates if we are linked/embedded.
    } GLOBALS;

typedef GLOBALS FAR * LPGLOBALS;

//External:
extern char NEAR *rgpsz[CSTRINGS];
extern LPGLOBALS pGlob;

//Versioning.
#define VERSIONMAJOR        1
#define VERSIONMINOR        0

/*
 * Function prototypes, organized by source file.  Any small definition
 * required by only one source file is also included here under the
 * same heading.
 */

//CLIP.C
BOOL      WINAPI FEditCut(LPGLOBALS);
BOOL      WINAPI FEditCopy(LPGLOBALS, BOOL);
BOOL      WINAPI FEditPaste(LPGLOBALS);
HGLOBAL   WINAPI HGetPolyline(HWND);
HGLOBAL   WINAPI HGetMetafilePict(HWND);
HGLOBAL   WINAPI HGetBitmap(HWND);

//COMMDLG.C
BOOL     WINAPI FSaveOpenDialog(HWND, HINSTANCE, LPSTR, LPSTR, LPSTR, LPSTR, BOOL);

//EXIT.C
BOOL     WINAPI FApplicationExit(LPGLOBALS);

//FILE.C
BOOL     WINAPI FDirtySet(BOOL);
BOOL     WINAPI FCleanVerify(LPGLOBALS);
BOOL     WINAPI FFileNew(LPGLOBALS);
BOOL     WINAPI FFileOpen(LPGLOBALS, BOOL);
BOOL     WINAPI FFileSave(LPGLOBALS);
BOOL     WINAPI FFileSaveAs(LPGLOBALS);
BOOL     WINAPI FFileExit(LPGLOBALS);

//FILEIO.C
BOOL     WINAPI FCosFileRead(LPGLOBALS, LPSTR, LPPOLYLINE);
BOOL     WINAPI FCosFileWrite(LPGLOBALS, LPSTR, LPPOLYLINE);

//INIT.C
BOOL     WINAPI FApplicationInit(LPGLOBALS, HINSTANCE);
BOOL     WINAPI FClassRegister(LPGLOBALS, HINSTANCE);
BOOL     WINAPI FFileInit(LPGLOBALS);
HLOCAL   WINAPI HLoadAppStrings(LPGLOBALS);
HLOCAL   WINAPI HListParse(LPSTR);
LPSTR    PASCAL PszWhiteSpaceScan(LPSTR, BOOL);

//MISC.C
void     WINAPI WindowTitleSet(HWND, LPSTR);
void     WINAPI RectConvertToHiMetric(HWND, LPRECT);
void     WINAPI RectConvertToDevice(HWND, LPRECT);

//COSMO.C
LRESULT  WINAPI CosmoWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL     WINAPI AboutProc(HWND, UINT, WPARAM, LPARAM);


EXIT.C

/*
 * EXIT.C
 *
 * Functions for final cleanup of the COSMO application.  Calls any
 * OLE specific functions.
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

#include <windows.h>
#include <ole.h>
#include "cosmo.h"
#include "oleglobl.h"

/*
 * FApplicationExit
 *
 * Purpose:
 *  Provides final cleanup of any objects and other actions that
 *  must occur at closing time.
 *
 * Parameters:
 *  pGlob           LPGLOBALS to global variable block.
 *
 * Return Value:
 *  BOOL            TRUE if everything succeeds, FALSE otherwise.
 */

BOOL WINAPI FApplicationExit(LPGLOBALS pGlob)
    {
    BOOL        fRet=TRUE;

    //Free the stringtable.
    if (NULL!=pGlob->hStringMem)
        LocalFree(pGlob->hStringMem);

#ifdef MAKEOLESERVER

    fRet=FOLEExit(pOLE);

#endif //MAKEOLESERVER

    return fRet;
    }


FILE.C

/*
 * FILE.C
 *
 * Functions for handling dirty files and processing File menu commands.
 *
 * Functions:
 *  FDirtySet, FCleanVerify
 *  FFileNew, FFileOpen, FFileSave, FFileSaveAs, FFileExit
 *
 * This file contains the only functions that manipulate the fDirty flag.
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

#include <windows.h>
#include <ole.h>
#include "cosmo.h"
#include "oleglobl.h"

/*
 * FDirtySet
 *
 * Purpose:
 *  Sets or clears the global 'dirty' flag returning the previous state
 *  of that same flag.  Even though the non-OLE part of this function
 *  is trivial, it isolates handling changes, providing a single point
 *  to notify a client app with OLE_CHANGED.
 *
 *  This function does exits if pGlob->fNoDirty is set.
 *
 * Parameters:
 *  fDirty          BOOL used to set the value of the pGLob->fDirty flag.
 *                  This allows us to use this function to both set and
 *                  clear the flag.  OLE_CHANGED is only sent if the
 *                  flag is set to TRUE.
 *
 * Return Value:
 *  BOOL            Previous value of the dirty flag.
 */

BOOL WINAPI FDirtySet(BOOL fDirty)
    {
    BOOL        fPrevious;

#ifdef MAKEOLESERVER
    /*
     * If we are a hidden window, then there's nothing that could make
     * us dirty since there cannot be any user interaction here.  Therefore
     * ignore any changes to the dirty flag, leaving it FALSE.
     */
    if (!IsWindowVisible(pGlob->hWnd))
        return pGlob->fDirty;

#endif //MAKEOLESERVER

    if (pGlob->fNoDirty)
        return pGlob->fDirty;

    fPrevious=pGlob->fDirty;
    pGlob->fDirty=fDirty;

#ifdef MAKEOLESERVER
    if (fDirty)
        //Fun indirection, huh?  That what you get with an OOP.
        OLEClientNotify(pOLE->pSvr->pDoc->pObj, OLE_CHANGED);
#endif //MAKEOLESERVER

    return fPrevious;
    }

/*
 * FCleanVerify
 *
 * Purpose:
 *  Checks the pGLob->fDirty flag, and if set, displays a message
 *  box informing the user that the file is dirty and asking if
 *  the file should be saved or updated.  If YES is chosen, the file
 *  is saved or updated.
 *
 * Parameters:
 *  pGlob           LPGLOBALS to global variable block.
 *
 * Return Value:
 *  BOOL            TRUE if it's safe to proceed with the operation (file
 *                  is clean, user answered NO, or file was saved).
 *                  FALSE if the user wants to cancel the operation or there
 *                  was an error.
 */

BOOL WINAPI FCleanVerify(LPGLOBALS pGlob)
    {
    BOOL        fRet=TRUE;
    UINT        uRet;
    char       *psz;

#ifdef MAKEOLESERVER
    char        szClient[40];
#endif //MAKEOLESERVER

    //Nothing to do if we're clean.
    if (!pGlob->fDirty)
        return TRUE;

    if (!pGlob->fOLE)
        {
        uRet=MessageBox(pGlob->hWnd, rgpsz[IDS_FILEDIRTY]
            , rgpsz[IDS_CAPTION], MB_YESNOCANCEL | MB_ICONEXCLAMATION);
        }
#ifdef MAKEOLESERVER
    if (pGlob->fOLE)
        {
        //Linking case, use the same message as before.
        if (pOLE->pSvr->fLink)
            {
            uRet=MessageBox(pGlob->hWnd, rgpsz[IDS_FILEDIRTY],
                            rgpsz[IDS_CAPTION], MB_YESNOCANCEL);
            }

        //Embedding: Ask the user about updating instead of saving.
        if (pOLE->pSvr->fEmbed)
            {
            //Build the standard string for Updating.
            psz=(PSTR)LocalAlloc(LPTR, 1024);

            if (NULL==psz)
                return FALSE;

            GetAtomName(pOLE->pSvr->pDoc->aClient, szClient, sizeof(szClient));

            lstrcpy(psz, rgpsz[IDS_CLOSEALERT1]);
            lstrcat(psz, szClient);
            lstrcat(psz, rgpsz[IDS_CLOSEALERT2]);

            uRet=MessageBox(pGlob->hWnd, psz, rgpsz[IDS_CAPTION],
                            MB_YESNOCANCEL | MB_ICONEXCLAMATION);

            LocalFree((HLOCAL)psz);
            }
        }
#endif //MAKEOLESERVER

    switch (uRet)
        {
        case IDCANCEL:
            fRet=FALSE;
            break;

        case IDNO:
            fRet=TRUE;
            break;

        case IDYES:
            if (!pGlob->fOLE)
                fRet=FFileSave(pGlob);

#ifdef MAKEOLESERVER
            //Linking same as stand-alone.
            if (pOLE->pSvr->fLink)
                fRet=FFileSave(pGlob);

            if (pOLE->pSvr->fEmbed)
                OLEClientNotify(pOLE->pSvr->pDoc->pObj, OLE_CLOSED);

#endif //MAKEOLESERVER
            break;
        }

    return fRet;
    }

/*
 * FFileNew
 *
 * Purpose:
 *  Confirms the new file with the user and cleans out the Polyline
 *  image.
 *
 * Parameters:
 *  pGlob           LPGLOBALS to the global variable block.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 *
 */

BOOL WINAPI FFileNew(LPGLOBALS pGlob)
    {
    if (!FCleanVerify(pGlob))
        return FALSE;

#ifdef MAKEOLESERVER
    PDocRevokeAndCreate(pOLE);
#endif //MAKEOLESERVER

    pGlob->fOLE=FALSE;

    SendMessage(pGlob->hWndPolyline, PLM_POLYLINENEW, 0, 0L);
    pGlob->fDirty=FALSE;
    pGlob->fOpenFile=FALSE;

    WindowTitleSet(pGlob->hWnd, rgpsz[IDS_UNTITLED]);
    return TRUE;
    }

/*
 * FFileOpen
 *
 * Purpose:
 *  Confirms that the user wants to open a new file and invokes the
 *  common dialog file open to get the filename, then reads the
 *  contents of the file.  If the fImport flag is TRUE, then we
 *  import the contents of the file into the current document,
 *  not changing the document name or any of the UI.
 *
 * Parameters:
 *  pGlob           LPGLOBALS to the global variable block.
 *  fImport         BOOL indicates if we're importing from a file,
 *                  not affecting the current filename.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 *
 */

BOOL WINAPI FFileOpen(LPGLOBALS pGlob, BOOL fImport)
    {
    POLYLINE        pl;
    BOOL            fOK;
    LPSTR           psz;
    char            szTemp[CCHPATHMAX];
    LPSTR           pszFile;

    if (!fImport)
        {
        if (!FCleanVerify(pGlob))
            return FALSE;
        }

    psz=(fImport) ? rgpsz[IDS_FILEIMPORT] : rgpsz[IDS_FILEOPEN];

    //We have to use a temp for Import or else we'll wipe out a real filename.
    pszFile=(fImport) ? (LPSTR)szTemp : (LPSTR)pGlob->szFile;

    fOK=FSaveOpenDialog(pGlob->hWnd, pGlob->hInst, rgpsz[IDS_DEFEXT]
        , rgpsz[IDS_FILEOPENFILTER], pszFile, psz, TRUE);

    if (fOK)
        {
        //Attempt to read the file in and display it.
        if (!FCosFileRead(pGlob, pszFile, &pl))
            return FALSE;

#ifdef MAKEOLESERVER
        if (!fImport)
            PDocRevokeAndCreate(pOLE);
#endif //MAKEOLESERVER

        SendMessage(pGlob->hWndPolyline, PLM_POLYLINESET,
                    TRUE, (LONG)(LPSTR)&pl);

        if (!fImport)
            {
            WindowTitleSet(pGlob->hWnd, pszFile);
            pGlob->fOLE=FALSE;

            pGlob->fOpenFile=TRUE;
            pGlob->fDirty=FALSE;
            }
        else
            pGlob->fDirty=TRUE;
        }

    return fOK;
    }

/*
 * FFileSave
 *
 * Purpose:
 *  Writes the file to a known filename, requiring that the user has
 *  previously used FileOpen or FileSaveAs in order to have a filename.
 *
 * Parameters:
 *  pGlob           LPGLOBALS to the global variable block.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 *
 */

BOOL WINAPI FFileSave(LPGLOBALS pGlob)
    {
    POLYLINE        pl;

#ifdef MAKEOLESERVER
    OLESTATUS       os;

    //In OLE cases, this may be Update; call OleSavedServerDoc
    if (pGlob->fOLE)
        {
        if (pOLE->pSvr->fLink)
            {
            SendMessage(pGlob->hWndPolyline, PLM_POLYLINEGET, 0,
                        (LONG)(LPSTR)&pl);

            if (!FCosFileWrite(pGlob, pGlob->szFile, &pl))
                return FALSE;
            }

        //This notifies the client for us.
        os=OleSavedServerDoc(pOLE->pSvr->pDoc->lh);

        pGlob->fDirty=(BOOL)(OLE_OK!=os);
        return !pGlob->fDirty;
        }

#endif //MAKEOLESERVER

    if (!pGlob->fOpenFile)
        return FFileSaveAs(pGlob);

    SendMessage(pGlob->hWndPolyline, PLM_POLYLINEGET, 0, (LONG)(LPSTR)&pl);

    if (!FCosFileWrite(pGlob, pGlob->szFile, &pl))
        return FALSE;

    pGlob->fOpenFile=TRUE;
    pGlob->fDirty=FALSE;
    return TRUE;
    }

/*
 * FFileSaveAs
 *
 * Purpose:
 *  Invokes the common dialog for Save As to get a filename then
 *  writes the polyline data to that file, creating if necessary.
 *
 * Parameters:
 *  pGlob           LPGLOBALS to the global variable block.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 *
 */

BOOL WINAPI FFileSaveAs(LPGLOBALS pGlob)
    {
    POLYLINE        pl;
    BOOL            fOK;

    fOK=FSaveOpenDialog(pGlob->hWnd, pGlob->hInst, rgpsz[IDS_DEFEXT]
        , rgpsz[IDS_FILEOPENFILTER], pGlob->szFile, rgpsz[IDS_FILESAVEAS]
        , FALSE);

    if (fOK)
        {
        SendMessage(pGlob->hWndPolyline, PLM_POLYLINEGET
            , 0, (LONG)(LPSTR)&pl);

        fOK=FCosFileWrite(pGlob, pGlob->szFile, &pl);

#ifdef MAKEOLESERVER
        /*
         * In the Save Copy As case, when an object is embedded, we
         * don't need to call Rename or Saved since we just saved a
         * silent copy of the data.
         */
        if (pGlob->fOLE && pOLE->pSvr->fEmbed)
            return fOK;

        if (fOK && pGlob->fOLE && pOLE->pSvr->fLink)
            {
            OleRenameServerDoc(pOLE->pSvr->pDoc->lh, pGlob->szFile);
            OleSavedServerDoc(pOLE->pSvr->pDoc->lh);
            }
#endif //MAKEOLESERVER

        pGlob->fOpenFile=fOK;
        WindowTitleSet(pGlob->hWnd, pGlob->szFile);
        pGlob->fDirty=FALSE;
        }

    return fOK;
    }

/*
 * FFileExit
 *
 * Purpose:
 *  Handles the File/Exit menu command, verifying dirty files as necessary
 *  and revoking the server.
 *
 * Parameters:
 *  pGlob           LPGLOBALS to the global variable block.
 *
 * Return Value:
 *  BOOL            TRUE if the application can exit, FALSE otherwise.
 */

BOOL WINAPI FFileExit(LPGLOBALS pGlob)
    {
    BOOL            fRet;
#ifdef MAKEOLESERVER
    OLESTATUS       os;
    LHSERVER        lhT;
#endif //MAKEOLESERVER

    if (!FCleanVerify(pGlob))
        return FALSE;

#ifdef MAKEOLESERVER
    lhT=pOLE->pSvr->lh;
    pOLE->pSvr->lh=0L;
    os=OleRevokeServer(lhT);

    if (OLE_WAIT_FOR_RELEASE==os)
        {
        pOLE->pSvr->fRelease=FALSE;
        FOLEReleaseWait(&pOLE->pSvr->fRelease, lhT);
        fRet=TRUE;
        }
    else
        fRet=(OLE_OK==os);
#endif //MAKEOLESERVER

    return fRet;
    }


FILEIO.C

/*
 * FILEIO.C
 *
 * Functions for low-level reading and writing of .COS files.
 *
 * Functions:
 *  FCosFileRead, FCosFileWrite
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

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

/*
 * FCosFileRead
 *
 * Purpose:
 *  Reads a Cos file into the POLYLINE structure pointed to by lppl.
 *  Clears pGlob->fDirty on success.
 *
 * Parameters:
 *  pGlob           LPGLOBALS to the global variable block.
 *  pszFile         LPSTR of the filename to read.
 *  lppl            LPPOLYLINE to the structure to fill.
 *
 * Return Value:
 *  BOOL            TRUE if the file was successfully read,
 *                  FALSE otherwise.
 */

BOOL WINAPI FCosFileRead(LPGLOBALS pGlob, LPSTR pszFile, LPPOLYLINE lppl)
    {
    int         hFile;
    OFSTRUCT    of;
    UINT        cb;

    hFile=OpenFile(pszFile, &of, OF_READ);

    if (-1==hFile)
        return FALSE;

    cb=_lread(hFile, (LPSTR)lppl, CBPOLYLINE);
    _lclose(hFile);

    if (CBPOLYLINE==cb && VERSIONMAJOR==lppl->wVerMaj
        && VERSIONMINOR==lppl->wVerMin)
        {
        pGlob->fDirty=FALSE;
        return TRUE;
        }

    return FALSE;
    }

/*
 * FCosFileWrite
 *
 * Purpose:
 *  Writes a Cos file into the POLYLINE structure pointed to by lppl.
 *  Clears pGlob->fDirty.
 *
 * Parameters:
 *  pGlob           LPGLOBALS to the global variable block.
 *  pszFile         LPSTR of the filename to read.
 *  lppl            LPPOLYLINE to the structure to write from.
 *
 * Return Value:
 *  BOOL            TRUE if the file was successfully written,
 *                  FALSE otherwise.
 */

BOOL WINAPI FCosFileWrite(LPGLOBALS pGlob, LPSTR pszFile, LPPOLYLINE lppl)
    {
    int         hFile;
    OFSTRUCT    of;
    UINT        cb;

    hFile=OpenFile(pszFile, &of, OF_CREATE | OF_WRITE);

    if (-1==hFile)
        return FALSE;

    cb=_lwrite(hFile, (LPSTR)lppl, CBPOLYLINE);
    _lclose(hFile);

    if (CBPOLYLINE==cb)
        {
        pGlob->fDirty=FALSE;
        return TRUE;
        }

    return FALSE;
    }


INIT.C

/*
 * INIT.C
 *
 * Application (not OLE) specific initialization code.
 *  FApplicationInit
 *  FFileInit
 *  HLoadAppStrings
 *  HListParse
 *  PszWhiteSpaceScan
 *
 * FApplicationInit makes some calls into OLEINST.C and OLEINIT.C
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

#include <windows.h>
#include <ole.h>
#include "cosmo.h"
#include "oleinst.h"
#include "oleglobl.h"

/*
 * FApplicationInit
 *
 * Purpose:
 *  All application specific initialization including loading
 *  the stringtable, registering window classes, and calling any
 *  OLE specific initialziation code.
 *
 * Parameters:
 *  pGlob           LPGLOBALS to global variable block.
 *  hPrevInst       HINSTANCE to the previous application instance, if any.
 *
 * Return Value:
 *  BOOL            TRUE if everything succeeds, FALSE otherwise.
 *                  If FALSE is returned, allocated memory and objects
 *                  are not necessarily freed.  The caller should then
 *                  use the FApplicationExit function to perform cleanup.
 */

BOOL WINAPI FApplicationInit(LPGLOBALS pGlob, HINSTANCE hPrevInst)
    {
    HLOCAL      hMem;
    LPSTR FAR * ppszCmds;
    LPSTR FAR * ppszT;
    BOOL        fRet=TRUE;

#ifdef MAKEOLESERVER
    //Make sure this is NULLed in case we fail
    pOLE->pSvr=NULL;
#endif

    /*
     * InitApp allocates local memory for strings. WinMain must free.
     * If this fails, we should quit BEFORE we register any classes
     * or do anything else to suck up USER or GDI resources.
     */
    hMem=HLoadAppStrings(pGlob);

    if (NULL==hMem)
        return FALSE;

    pGlob->hStringMem=hMem;

    //Classes are only registered if hPrevInstance is NULL.
    if (!FClassRegister(pGlob, hPrevInst))
        {
        LocalFree(pGlob->hStringMem);
        return FALSE;
        }

    //Register a private clipboard format, same as the class name.
    pGlob->cfCosmo=RegisterClipboardFormat(rgpsz[IDS_CLASSCOSMO]);

    hMem=HListParse(pGlob->pszCmdLine);
    ppszCmds=(LPSTR FAR *)(PSTR)hMem;
    ppszT=ppszCmds;

    /*
     * Scan the command line list for the first thing without a
     * / or - which is out initial file.
     */

    while (*ppszT)
        {
        if ('-'!=**ppszT && '/'!=**ppszT)
            break;

        ppszT++;
        }

    //Copy this filename for later loading during WM_CREATE.
    if (NULL==*ppszT)
        pGlob->szFile[0]=0;
    else
        lstrcpy(pGlob->szFile, *ppszT);

#ifdef MAKEOLESERVER
    /*
     * Go do anything that deals with the registration database.
     * Installation could be moved to an installation program
     */
    if (!FRegDBInstall())
        {
        LocalFree(hMem);
        return FALSE;
        }

    //Initialize OLE specific data.  FOLEInstanceInit affects pGlob->fOLE
    if (FOLEInstanceInit(pOLE, pGlob->hInst, rgpsz[IDS_CLASSCOSMO]
        , ppszCmds, pGlob->nCmdShow))
        {
        //We will open any linked file later in WM_CREATE.

        //Copy the new ShowWindow parameter.
        pGlob->nCmdShow=pOLE->pSvr->nCmdShow;
        }
    else
        fRet=FALSE;

#endif //MAKEOLESERVER

    //Free the command-line string list in hMem.
    LocalFree(hMem);
    return fRet;
    }

/*
 * FClassRegister
 *
 * Purpose:
 *  Registers classes used by the application:  "Cosmo" the main
 *  window, and "Polyline" the editing window.
 *
 * Parameters:
 *  pGlob           LPGLOBALS to the global variables block.
 *  hPrevInst       HINSTANCE of any previous application instance.
 *
 * Return Value:
 *  BOOL            TRUE if all classes are successfully registered
 *                  (or if hPrevInstance is non-NULL).  FALSE is
 *                  any registration fails.
 *
 */

BOOL WINAPI FClassRegister(LPGLOBALS pGlob, HINSTANCE hPrevInst)
    {
    WNDCLASS        wc;

    if (hPrevInst)
        return TRUE;

    /*
     * Note that we do not need to unregister classes on a failure
     * since that's part of automatic app cleanup.
     */
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = CosmoWndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = pGlob->hInst;
    wc.hIcon         = LoadIcon(pGlob->hInst, "Icon");
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
    wc.lpszMenuName  = MAKEINTRESOURCE(IDR_MENU);
    wc.lpszClassName = rgpsz[IDS_CLASSCOSMO];

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

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc   = PolylineWndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = CBPOLYLINEWNDEXTRA;
    wc.hInstance     = pGlob->hInst;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_CROSS);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = rgpsz[IDS_CLASSPOLYLINE];

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

    return TRUE;
    }

/*
 * FFileInit
 *
 * Purpose:
 *  Loads a file specified on the command line and sets the polyline
 *  window data to reflect the contents of that file.  This is set
 *  in a different function instead of in FApplicationInit since
 *  it depends on the Polyline window being created which has not
 *  happened by FApplicationInit time.
 *
 * Parameters:
 *  pGlob           LPGLOBALS to global variable block.
 *
 * Return Value:
 *  BOOL            TRUE if everything succeeded, FALSE otherwise.
 *
 */

BOOL WINAPI FFileInit(LPGLOBALS pGlob)
    {
    POLYLINE        pl;

    /*
     * If there is a file to load, then load it and change the
     * window title.  If loading fails, then default to a new
     * untitled document.
     */
    if (0!=pGlob->szFile[0])
        {
        if (!FCosFileRead(pGlob, pGlob->szFile, &pl))
            {
            //If we're in OLE linking mode, return unsuccessful
            if (pGlob->fOLE)
                return FALSE;

            pGlob->szFile[0]=0;
            WindowTitleSet(pGlob->hWnd, rgpsz[IDS_UNTITLED]);
            pGlob->fOpenFile=FALSE;

            }
        else
            {
            SendMessage(pGlob->hWndPolyline, PLM_POLYLINESET, TRUE,
                        (LONG)(LPSTR)&pl);
            WindowTitleSet(pGlob->hWnd, pGlob->szFile);
            pGlob->fOpenFile=TRUE;
            }
        }

    return TRUE;
    }

/*
 * HLoadAppStrings
 *
 * Purpose:
 *  Allocates FIXED local memory and reads the applications
 *  string resources into that memory.  Each string's pointer
 *  is available with rgpsz[i] where i is the ID value of the
 *  string.  The strings must have sequential IDs.
 *
 * Parameters:
 *  pGlob           LPGLOBALS to global variable block.
 *
 * Return Value:
 *  HLOCAL          Handle to the local memory.  NULL if memory could
 *                  not be allocated.
 */

HLOCAL WINAPI HLoadAppStrings(LPGLOBALS pGlob)
    {
    HLOCAL      hLocalMem;
    char NEAR   *pch;
    UINT        cchUsed=0;
    UINT        cch;
    short       i;

    /*
     * Allocate memory and load strings.  NOTE!  The LPTR style
     * specifies FIXED memory.  This should not be a big deal
     * since this is an early allocation into the local heap.
     * But it should be watched if the number of strings becomes
     * large.
     */
    hLocalMem=LocalAlloc(LPTR, CSTRINGS*CCHSTRINGMAX);

    if (hLocalMem==NULL)
        return (HLOCAL)NULL;

    /*
     * This operation is only valid for FIXED memory.  Otherwise use
     * LocalLock.
     */
    pch=(char *)hLocalMem;

    /*
     * Load the strings into the memory and retain the specific
     * pointer to that string.
     */
    for (i=0; i<CSTRINGS; i++)
        {
        cch=LoadString(pGlob->hInst, i, (LPSTR)(pch+cchUsed), CCHSTRINGMAX-1);
        rgpsz[i]=(char *)(pch+cchUsed);

        /*
         * One is added to cch to include a NULL.  The memory was ZEROINITed
         * on allocation so by skipping a byte we get the NULL.
         */
        cchUsed +=cch+1;
        }

    /*
     * We are assuming that no string is over CCHSTRINGMAX, and therefore
     * we did not use all the allocated memory.  Therefore LocalReAlloc
     * will only SHRINK the block, never expand it.  So if it fails, we
     * don't care--all the strings are still there, we just wasted some
     * space.
     */
    LocalReAlloc(hLocalMem, cchUsed+1, LPTR);

    return hLocalMem;
    }

/*
 * HListParse
 *
 * Purpose:
 *  Parses any string containing text separated by whitespace into
 *  a list of pointers into that string as well as overwriting the
 *  whitespace with null terminators.  The result is that each
 *  pointer in the list points to its own null-terminated string,
 *  but those strings are not necessarily contiguous.
 *
 *  Since MS-DOS command lines are limited to 128 characters, this
 *  function limits the number of arguments to 64 separate strings.
 *
 * Parameters:
 *  psz             LPSTR to the string to parse.
 *
 * Return Value:
 *  HLOCAL          Local LMEM_FIXED memory handle containing the
 *                  list of LPSTRs to the list items.  NULL if memory
 *                  could not be allocated.
 */

HLOCAL WINAPI HListParse(LPSTR psz)
    {
    HLOCAL       hMem;
    LPSTR FAR   *ppsz;
    LPSTR        pszT;
    UINT         cp;

    //Allocate space for 64 pointers.
    hMem=LocalAlloc(LPTR, 64*sizeof(LPSTR));

    if (NULL==hMem)
        return NULL;

    ppsz=(LPSTR FAR *)(PSTR)hMem;
    cp=0;

    /*
     * For each string, scan for whitespace, save that pointer in
     * ppsz, and null-terminate that string.  If it was already
     * null-terminated, then there are no more pieces in the list.
     */

    while (0!=*psz)
        {
        //Skip to beginning of first item.
        psz=PszWhiteSpaceScan(psz, TRUE);

        //If it's a zero, stop here.
        if (0==*psz)
            break;

        //Find the end of this item.
        pszT=PszWhiteSpaceScan(psz, FALSE);

        //Null terminate this string and point to next character.
        if (0!=*pszT)
            *pszT++=0;

        //Save this string pointer.
        *ppsz++=psz;
        cp++;

        //Check our limit of 64.
        if (64 <= cp)
            break;

        //Check next item.
        psz=pszT;
        }

    return hMem;
    }

/*
 * PszWhiteSpaceScan
 *
 * Purpose:
 *  Skips characters in a string until a whitespace or non-whitespace
 *  character is seen.  Whitespace is defined as \n, \r, \t, or ' '.
 *
 * NOTE:  This function is not extremely well suited to localization.
 *        It assumes that an existing application seeking to become
 *        and OLE server probably already has such a string function
 *        available.
 *
 * Parameters:
 *  psz             LPSTR to string to manipulate
 *  fSkip           BOOL  TRUE if we want to skip whitespace.
 *                  FALSE if we want to skip anything but whitespace.
 *
 * Return Value:
 *  LPSTR           Pointer to first character in the string that either
 *                  non-whitespace (fSkip=TRUE) or whitespace (fSkip=FALSE),
 *                  which may be the null terminator.
 */

LPSTR PASCAL PszWhiteSpaceScan(LPSTR psz, BOOL fSkip)
    {
    char        ch;
    BOOL        fWhite;

    while (ch=*psz)
        {
        fWhite=('\n'==ch || '\r'==ch || '\t'==ch || ' '==ch);

        //Too bad C doesn't have a logical XOR (^^) operator.
        if ((fSkip && !fWhite) || (!fSkip && fWhite))
            break;

        psz++;
        }

    return psz;
    }


MISC.C

/*
 * MISC.C
 *
 * Functions without any other reasonable home:
 *  WindowTitleSet, RectConvertToHiMetric, RectConvertToDevice
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

#include <windows.h>
#include <ole.h>
#include "cosmo.h"

/*
 * WindowTitleSet
 *
 * Purpose:
 *  Handles changing the window's caption bar depending on the file
 *  that is loaded.
 *
 * Parameters:
 *  hWnd            HWND of the window to change.
 *  pszFile         LPSTR of the file loaded.
 *
 * Return Value:
 *  none.
 */

void WINAPI WindowTitleSet(HWND hWnd, LPSTR pszFile)
    {
    char        szTitle[CCHPATHMAX];

    wsprintf(szTitle, "%s - %s", (LPSTR)rgpsz[IDS_CAPTION], pszFile);
    SetWindowText(hWnd, szTitle);
    return;
    }

/*
 * RectConvertToHiMetric
 *
 * Purpose:
 *  Converts the contents of a rectangle from MM_TEXT into MM_HIMETRIC
 *  specifically for use in converting a RECT of a window into
 *  a RECT for a METAFILEPICT structure.
 *
 * Parameters:
 *  hWnd            HWND of the window providing the display context
 *  pRect           LPRECT containing the rectangle to convert.
 *
 * Return Value:
 *  None.
 */

void WINAPI RectConvertToHiMetric(HWND hWnd, LPRECT pRect)
    {
    HDC      hDC;
    POINT    pt;

    hDC=GetDC(hWnd);
    SetMapMode(hDC, MM_HIMETRIC);

    //Convert upper left corner
    pt.x=pRect->left;
    pt.y=pRect->top;
    DPtoLP(hDC, &pt, 1);
    pRect->left=pt.x;
    pRect->top=-pt.y;

    //Convert lower right corner
    pt.x=pRect->right;
    pt.y=pRect->bottom;
    DPtoLP(hDC, &pt, 1);
    pRect->right=pt.x;
    pRect->bottom=-pt.y;

    ReleaseDC(hWnd, hDC);
    return;
    }

/*
 * RectConvertToDevice
 *
 * Purpose:
 *  Converts the contents of a rectangle from MM_HIMETRIC into MM_TEXT
 *  specifically for use in converting a RECT of an OLE object into
 *  a RECT for a window.
 *
 * Parameters:
 *  hWnd            HWND of the window providing the display context
 *  pRect           LPRECT containing the rectangle to convert.
 *
 * Return Value:
 *  None.
 */

void WINAPI RectConvertToDevice(HWND hWnd, LPRECT pRect)
    {
    HDC      hDC;
    POINT    pt;

    hDC=GetDC(hWnd);
    SetMapMode(hDC, MM_HIMETRIC);

    //Convert upper left corner
    pt.x=pRect->left;
    pt.y=pRect->bottom;
    LPtoDP(hDC, &pt, 1);
    pRect->left=pt.x;
    pRect->bottom=pt.y;

    //Convert lower right corner
    pt.x=pRect->right;
    pt.y=pRect->top;
    LPtoDP(hDC, &pt, 1);
    pRect->right=pt.x;
    pRect->top=pt.y;

    ReleaseDC(hWnd, hDC);
    return;
    }


OLECLIP.C

/*
 * OLECLIP.C
 *
 * Routines to handle placing Native, ObjectLink, and OwnerLink
 * information on the clipboard.
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

#ifdef MAKEOLESERVER

#include <windows.h>
#include <ole.h>
#include "cosmo.h"
#include "oleglobl.h"

/*
 * FOLECopyNative
 *
 * Purpose:
 *  Allocates a memory block for Native data and places it on the clipboard,
 *  assuming that the application has opened the clipboard.
 *
 * Parameters:
 *  pOLE            LPXOLEGLOBALS containing clipboard formats.
 *
 * Return Value:
 *  BOOL            TRUE if the data was copied, FALSE otherwise.
 */

BOOL WINAPI FOLECopyNative(LPXOLEGLOBALS pOLE)
    {
    HGLOBAL      hMem;

    hMem=HGetPolyline(pGlob->hWndPolyline);

    //Place Native data on clipboard.
    if (NULL==hMem)
        return FALSE;

    SetClipboardData(pOLE->cfNative, hMem);
    return TRUE;
    }

/*
 * FOLECopyLink
 *
 * Purpose:
 *  Places ObjectLink OR OwnerLink information on the clipboard.
 *  This function assumes that the application already has the
 *  clipboard open.
 *
 * Parameters:
 *  pOLE            LPXOLEGLOBALS containing clipboard formats.
 *  fOwnerLink      BOOL indicating to set OwnerLink (TRUE)/ObjectLink (FALSE)
 *  pszDoc          LPSTR to the document name.
 *
 * Return Value:
 *  BOOL            TRUE if copying to the clipboard was successful.
 *                  FALSE on any failure.
 */

BOOL WINAPI FOLECopyLink(LPXOLEGLOBALS pOLE, BOOL fOwnerLink, LPSTR pszDoc)
    {
    HGLOBAL         hMem;
    OLECLIPFORMAT   cf;

    //Retrieve a handle to the OwnerLink/ObjectLink format.
    hMem=HLinkConstruct(rgpsz[IDS_CLASSCOSMO], pszDoc, rgpsz[IDS_FIGURE]);

    if (NULL==hMem)
        return FALSE;

    //Set one or the other format.
    cf=(fOwnerLink) ? (pOLE->cfOwnerLink) : (pOLE->cfObjectLink);
    hMem=SetClipboardData(cf, hMem);

    return (NULL!=hMem);
    }

/*
 * HLinkConstruct
 *
 * Purpose:
 *  Builds an ObjectLink and OwnerLink text string for OLE clipboard
 *  interaction in the format of "classname\0document\0object\0\0"
 *
 * Parameters:
 *  pszClass        LPSTR to the classname.
 *  pszDoc          LPSTR to the document name.
 *  pszObj          LPSTR to the object name.
 *
 * Return Value:
 *  HGLOBAL         Global memory handle to an block containing
 *                  the three strings with the appropriate separator.
 */

HGLOBAL WINAPI HLinkConstruct(LPSTR pszClass, LPSTR pszDoc, LPSTR pszObj)
    {
    HGLOBAL     hMem;
    UINT        cch1, cch2, cch3;
    LPSTR       psz;

    if (NULL==pszClass || NULL==pszDoc || NULL==pszObj)
        return NULL;

    //We'll need lengths later.
    cch1=lstrlen(pszClass);
    cch2=lstrlen(pszDoc);
    cch3=lstrlen(pszObj);

    //Extra 4 is for the null-terminators.
    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, (DWORD)(4+cch1+cch2+cch3));

    if (NULL==hMem)
        return NULL;

    psz=GlobalLock(hMem);

    lstrcpy(psz, pszClass);
    psz+=cch1+1;

    lstrcpy(psz, pszDoc);
    psz+=cch2+1;

    lstrcpy(psz, pszObj);
    *(psz+cch3+1)=0;        //Add the final null terminator.

    GlobalUnlock(hMem);
    return hMem;
    }

#endif //MAKEOLESERVER


OLEDOC.C

/*
 * OLEDOC.C
 *
 * Contains all callback functions in the OLESERVERDOCVTBL struture:
 *      DocClose
 *      DocGetObject
 *      DocExecute
 *      DocRelease
 *      DocSave
 *      DocSetColorScheme
 *      DocSetDocDimensions
 *      DocSetHostNames
 *
 * Also contains two helper functions, PDocumentAllocate and DocumentClean.
 * PDocumentAllocate acts like a C++ constructor.
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

#ifdef MAKEOLESERVER

#include <windows.h>
#include <ole.h>
#include "cosmo.h"
#include "oleglobl.h"

/*
 * PDocumentAllocate
 *
 * Purpose:
 *  Allocates a COSMODOC structure and sets the defaults in its fields.
 *  Used from application initialization and various server methods that
 *  create a document.
 *
 * Parameters:
 *  pVtblDoc        LPOLESERVERDOCVTBL used to initialize the pvtbl field.
 *
 * Return Value:
 *  LPCOSMODOC      Pointer to the allocated structure in local memory.
 *                  The hMem field will contain a handle to the structure
 *                  to pass to LocalFree.
 *
 */

LPCOSMODOC WINAPI PDocumentAllocate(LPOLESERVERDOCVTBL pVtblDoc)
    {
    HLOCAL      hMem;
    LPCOSMODOC  pDoc;

    hMem=LocalAlloc(LPTR, CBCOSMODOC);

    if (NULL==hMem)
        return NULL;

    pDoc=(LPCOSMODOC)(PSTR)hMem;

    pDoc->hMem=hMem;
    pDoc->fRelease=TRUE;
    pDoc->pvtbl=pVtblDoc;

    return pDoc;
    }

/*
 * DocClose
 *
 * Purpose:
 *  Instructs the server to unconditionally close the document.  OLESVR
 *  calls DocClose when the client initiates a shutdown.  OLESVR always
 *  calls this function before calling ServerRelease.
 *
 * Parameters:
 *  pDoc            LPCOSMODOC identifying the document affected.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

OLESTATUS WINAPI DocClose(LPCOSMODOC pDoc)
    {
    OLESTATUS       os;

    /*
     * Take no action to notify user--Client will take care of that.
     *
     * 1.   Call OleRevokeServerDoc; resources are freed when OLESVR
     *      calls DocRelease.
     * 2.   Return the return value of OleRevokeServerDoc, which will
     *      generally be OLE_OK.
     */

    os=OleRevokeServerDoc(pDoc->lh);
    return os;
    }

/*
 * DocGetObject
 *
 * Purpose:
 *  Requests the server application to create an OLEOBJECT structure.
 *
 * Parameters:
 *  pDoc            LPCOSMODOC identifying the document affected.
 *  pszItem         OLE_LPCSTR specifying the name of the item in a document
 *                  for which the object is to be created.  If NULL, then
 *                  the entire document is requested.  This could be a
 *                  range of spreadsheet cells like "R1:C3-R10:C50"
 *  ppObj           LPLPOLEOBJECT at which to store a pointer to the
 *                  OLEOBJECT structure.
 *  pClient         LPOLECLIENT that should be associated with
 *                  the object in order to notify OLECLI when the object
 *                  changes.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

OLESTATUS WINAPI DocGetObject(LPCOSMODOC pDoc, OLE_LPCSTR pszItem
    , LPLPOLEOBJECT ppObj, LPOLECLIENT pClient)
    {
    LPCOSMOOBJECT   pObj;

    /*
     * 1.   Allocate and initialize an OLEOBJECT structure.
     * 2.   Store pClient in the object's OLEOBJECT structure for use
     *      in sending notifications to the client.
     * 3.   Store a pointer to the new OLEOBJECT structure in ppObj.
     * 4.   Return OLE_OK if successful, OLE_ERROR_NAME if pszObj is
     *      not recognized, or OLE_ERROR_MEMORY if the object could not
     *      be allocated.
     *
     * This function is called in response to a client calling
     * OleGetObject.
     */

    //Allocate one from local FIXED memory.
    pObj=PObjectAllocate(&pOLE->vtblObj);

    if (NULL==pObj)
        return OLE_ERROR_MEMORY;

    //Remember who we created for freeing in DocRelease.
    pDoc->pObj=pObj;

    //Must save this for sending notifications.
    pObj->pClient=pClient;

    //Pass back the pointer to this object.
    *ppObj=(LPOLEOBJECT)pObj;
    return OLE_OK;
    }

/*
 * DocExecute
 *
 * Purpose:
 *  Passes DDE Execute strings related to the document to the server
 *  application.
 *
 * Parameters:
 *  pDoc            LPCOSMODOC identifying the document affected.
 *  hCommands       HGLOBAL to memory containing DDE execute strings.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

OLESTATUS WINAPI DocExecute(LPCOSMODOC pDoc, HGLOBAL hCommands)
    {
    /*
     * 1.   Lock the hCommands handle to access the execute strings.
     * 2.   Parse and execute the commands as necessary.
     * 3.   DO NOT FREE hCommands.
     * 4.   Return OLE_OK if successful, OLE_ERROR_COMMAND otherwise.
     */

    return OLE_ERROR_COMMAND;
    }

/*
 * DocRelease
 *
 * Purpose:
 *  Notifies the server when a revoked document may be destroyed.
 *
 * Parameters:
 *  pDoc            LPCOSMODOC identifying the document affected.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

OLESTATUS WINAPI DocRelease(LPCOSMODOC pDoc)
    {
    /*
     * 1.   Free any resources allocated for this document, but
     *      DO NOT free the OLESERVERDOC structure itself.  This could
     *      include freeing objects, however.
     * 2.   Set a flag to indicate that Release has been called.
     * 3.   Return OLE_OK if successful, OLE_ERROR_GENERIC otherwise.
     */

    if (0!=pDoc->aObject)
        {
        DeleteAtom(pDoc->aObject);
        pDoc->aObject=0;
        }

    if (0!=pDoc->aClient)
        {
        DeleteAtom(pDoc->aClient);
        pDoc->aClient=0;
        }

    //Release any object stored in this document.
    if (0!=pDoc->pObj)
        {
        if (NULL!=pDoc->pObj->hMem)
            LocalFree(pDoc->pObj->hMem);
        }

    pDoc->pObj=NULL;

    pDoc->fRelease=TRUE;
    return OLE_OK;
    }

/*
 * DocSave
 *
 * Purpose:
 *  Instructs the server application to save the document.  If DocSave is
 *  called you are assumed to have a know filename to which you can save
 *  since this method is only used in linking.
 *
 * Parameters:
 *  pDoc            LPCOSMODOC identifying the document affected.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

OLESTATUS WINAPI DocSave(LPCOSMODOC pDoc)
    {
    /*
     * 1.   Save the document to the known filename.  How you save
     *      documents is application-specific.
     * 2.   Return OLE_OK if the save is successful, OLE_ERROR_GENERIC
     *      otherwise.
     */

    SendMessage(pGlob->hWnd, WM_COMMAND, IDM_FILESAVE, 0L);
    return OLE_OK;
    }

/*
 * DocSetColorScheme
 *
 * Purpose:
 *  Provides a color scheme that the client application recommends for
 *  rendering graphical data.
 *
 * Parameters:
 *  pDoc            LPCOSMODOC identifying the document affected.
 *  pPal            LPLOGPALETTE describing the recommended palette.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

OLESTATUS WINAPI DocSetColorScheme(LPCOSMODOC pDoc
    , OLE_CONST LOGPALETTE FAR *pPal)
    {
    /*
     * Servers are not required to use this palette.  The LPLOGPALETTE
     * only is a convenient structure to contain the color scheme; IT DOES
     * not REPRESENT A PALETTE IN STRICT TERMS!  Do NOT call CreatePalette
     * and try to realize it.
     *
     * The color scheme contained in the LOGPALETTE structure contains
     * a number of colors where the first color is the suggested foreground,
     * the second the suggested background, then the first HALF of those
     * remaining are suggested fill colors and the last half suggested
     * line colors.  If there are an odd number of colors, give the extra
     * color to the fill colors, that is, there is one less line color than
     * fill colors.
     */

    return OLE_ERROR_PALETTE;
    }

/*
 * DocSetDocDimensions
 *
 * Purpose:
 *  Specifies the rectangle on the target device for which EMBEDDED
 *  objects should be formatted.
 *
 * Parameters:
 *  pDoc            LPCOSMODOC identifying the document affected.
 *  pRect           LPRECT to the device rectangle in MM_HIMETRIC units.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

OLESTATUS WINAPI DocSetDocDimensions(LPCOSMODOC pDoc
    , OLE_CONST RECT FAR *pRect)
    {
    /*
     * OLESVR will call this method when the client has resized the
     * object.
     *
     * In this case we try to make the parent window the correct size
     * to just contain the object.
     */

    //First, convert the rectangle to units we can deal with MM_TEXT.
    RectConvertToDevice(pGlob->hWnd, (LPRECT)pRect);

    /*
     * Tell the Polyline document to use this rectangle, also notifying
     * the parent which will then resize itself.
     */
    SendMessage(pGlob->hWndPolyline, PLM_RECTSET, TRUE, (LONG)pRect);

    return OLE_OK;
    }

/*
 * DocSetHostNames
 *
 * Purpose:
 *  Set names that should be used for window titles, only for
 *  embedded objects; linked objects have their own titles as they
 *  are loaded through the server application's usual file loading
 *  mechanism.
 *
 * Parameters:
 *  pDoc            LPCOSMODOC identifying the document affected.
 *  pszClient       OLE_LPCSTR to the name of the client application.
 *  pszObj          OLE_LPCSTR to the name of the embedded object.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

OLESTATUS WINAPI DocSetHostNames(LPCOSMODOC pDoc, OLE_LPCSTR pszClient
    , OLE_LPCSTR pszObj)
    {
    char        szTemp[256];

    /*
     * 1.   Change the title bar to reflect the embedded state with the
     *      appropriate names.
     * 2.   Change the File menu to reflect the embedded state and the name
     *      of the client application.
     * 3.   Store the object and client names in teh OLESERVERDOC structure.
     *      These will be needed later for message boxes where the name of
     *      the client application must be displayed.
     * 4.   Return OLE_OK if successful, OLE_ERROR_GENERIC otherwise.
     */

    wsprintf(szTemp, "%s in %s", pszObj, pszClient);
    WindowTitleSet(pGlob->hWnd, szTemp);
    MenuEmbeddingSet(pGlob->hWnd, (LPSTR)pszClient, TRUE);

    if (0!=pDoc->aObject)
        DeleteAtom(pDoc->aObject);

    pDoc->aObject=AddAtom(pszObj);

    if (0!=pDoc->aClient)
        DeleteAtom(pDoc->aClient);

    pDoc->aClient=AddAtom(pszClient);

    return OLE_OK;
    }

#endif //MAKEOLESERVER


OLEEXIT.C

/*
 * OLEEXIT.C
 *
 * Functions for final OLE cleanup.
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

#ifdef MAKEOLESERVER

#include <windows.h>
#include <ole.h>
#include "cosmo.h"
#include "oleglobl.h"

/*
 * FOLEExit
 *
 * Purpose:
 *  Handles OLE-server specific shutdown
 *    1.  Free the OLESERVER structure.
 *    2.  Free the procedure instances the server's VTBLs.
 *
 * Parameters:
 *  pOLE            LPXOLEGLOBALS to OLE-specific global variable block.
 *
 * Return Value:
 *  BOOL            FALSE if an error occurred, otherwise TRUE.
 */

BOOL WINAPI FOLEExit(LPXOLEGLOBALS pOLE)
    {
    /*
     * Dump the server.  We depend on ServerRelease and DocRelease to
     * clean up documents and objects.  Choosing File/Exit has already
     * revoked the server.  This is simply final cleanup.
     */
    if (NULL!=pOLE)
        {
        if (NULL!=pOLE->pSvr)
            LocalFree(pOLE->pSvr->hMem);
        }

    OLEVtblFreeServer  (&pOLE->vtblSvr);
    OLEVtblFreeDocument(&pOLE->vtblDoc);
    OLEVtblFreeObject  (&pOLE->vtblObj);

    return TRUE;
    }

#endif //MAKEOLESERVER


OLEFILE.C

/*
 * OLEFILE.C
 *
 * Functions for handling special case OLE functions for operations
 * on a File menu such as New, Open, Save, SaveAs, and Exit.
 *
 * Functions:
 *  PDocRevokeAndCreate
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

#ifdef MAKEOLESERVER

#include <windows.h>
#include <ole.h>
#include "cosmo.h"
#include "oleglobl.h"

/*
 * PDocRevokeAndCreate
 *
 * Purpose:
 *  Standard sequence of calling OleRevokeServerDoc, allocating and
 *  initializing a new OLESERVERDOC structure, and calling
 *  OleRegisterServerDoc.
 *
 * Parameters:
 *  pOLE            LPXOLEGLOBALS pointer to OLE global variables.
 *
 * Return Value:
 *  LPCOSMODOC      Pointer to the new document, or NULL if it fails.
 *
 */

LPCOSMODOC WINAPI PDocRevokeAndCreate(LPXOLEGLOBALS pOLE)
    {
    LPCOSMODOC      pDoc;
    OLESTATUS       os;

    os=OleRevokeServerDoc(pOLE->pSvr->pDoc->lh);

    /*
     * Even through we are revoking the document, the server will also
     * be Released (through not revoked) since no OLE connections will
     * exist to the server.  Since ServerRelease in this application
     * frees the allocated document, we need to wait until we pass
     * ServerRelease before allocating a new document.  Otherwise we'd
     * allocate a new one and ServerRelease would immediately free it.
     *
     * So in calling FOLEReleaseWait we watch the server's fRelease flag.
     */

    if (OLE_WAIT_FOR_RELEASE==os)
        {
        pOLE->pSvr->fRelease=FALSE;
        FOLEReleaseWait(&pOLE->pSvr->fRelease, pOLE->pSvr->lh);
        }

    pDoc=PDocumentAllocate(&pOLE->vtblDoc);
    pOLE->pSvr->pDoc=pDoc;

    if (NULL==pDoc)
        return NULL;

    os=OleRegisterServerDoc(pOLE->pSvr->lh, pGlob->szFile,
                            (LPOLESERVERDOC)pOLE->pSvr->pDoc,
                            &pOLE->pSvr->pDoc->lh);

    pOLE->pSvr->fLink=FALSE;
    pOLE->pSvr->fEmbed=FALSE;

    MenuEmbeddingSet(pGlob->hWnd, NULL, FALSE);
    return pDoc;
    }

#endif //MAKEOLESERVER


OLEGLOBL.C

/*
 * OLEGLOBL.C
 *
 * Declarations of OLE-specific global variables.
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

#ifdef MAKEOLESERVER

#include <windows.h>
#include <ole.h>
#include "cosmo.h"
#include "oleglobl.h"

XOLEGLOBALS      stOLE;
LPXOLEGLOBALS    pOLE=&stOLE;

#endif //MAKEOLESERVER


OLEGLOBL.H

/*
 * OLEGLOBL.H
 *
 * Definitions an other globals specific to the OLE code.
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

#ifdef MAKEOLESERVER

#ifndef CCHPATHMAX
#define CCHPATHMAX          256
#endif

#ifndef CCHFILENAME
#define CCHFILENAMEMAX      15
#endif

typedef LPOLESERVER    FAR *LPLPOLESERVER;
typedef LPOLESERVERDOC FAR *LPLPOLESERVERDOC;
typedef LPOLEOBJECT    FAR *LPLPOLEOBJECT;

typedef LPVOID (WINAPI *LPVOIDPROC) (LPOLEOBJECT, LPSTR);

typedef struct
    {
    LPOLEOBJECTVTBL     pvtbl;          //Standard
    BOOL                fRelease;       //Flag to watch if we need to wait
    LPOLECLIENT         pClient;        //Necessary for notifications
    HLOCAL              hMem;           //Memory handle to this structure
    } COSMOOBJECT;

typedef COSMOOBJECT FAR *LPCOSMOOBJECT;
#define CBCOSMOOBJECT sizeof(COSMOOBJECT)

typedef struct
    {
    LPOLESERVERDOCVTBL  pvtbl;          //Standard
    LHSERVERDOC         lh;             //Required by OleRegisterServerDoc
    BOOL                fRelease;       //Flag to watch if we need to wait
    ATOM                aObject;        //Name of the document's object
    ATOM                aClient;        //Name of the connected client
    HLOCAL              hMem;           //Memory handle to this structure
    LPCOSMOOBJECT       pObj;           //Last object we allocated
    } COSMODOC;

typedef COSMODOC FAR *LPCOSMODOC;
#define CBCOSMODOC sizeof(COSMODOC)

typedef struct
    {
    LPOLESERVERVTBL     pvtbl;          //Standard
    LHSERVER            lh;             //Required by OleRegisterServer
    BOOL                fRelease;       //Flag to watch if we need to wait
    BOOL                fEmbed;         //TRUE if we're embedding only
    BOOL                fLink;          //TRUE if we're linking only
    UINT                nCmdShow;       //OLE-modified window show command
    HWND                hWnd;           //Main application window
    HLOCAL              hMem;           //Memory handle to this structure
    LPCOSMODOC          pDoc;           //Last document we allocated
    } COSMOSERVER;

typedef COSMOSERVER FAR *LPCOSMOSERVER;
#define CBCOSMOSERVER sizeof(COSMOSERVER)

/*
 * Single data structure holding any OLE-specific global variables.
 */

typedef struct
    {
    LPCOSMOSERVER       pSvr;

    OLESERVERVTBL       vtblSvr;
    OLESERVERDOCVTBL    vtblDoc;
    OLEOBJECTVTBL       vtblObj;

    UINT                cfNative;
    UINT                cfOwnerLink;
    UINT                cfObjectLink;
    } XOLEGLOBALS;

typedef XOLEGLOBALS FAR *LPXOLEGLOBALS;
#define CBXOLEGLOBALS sizeof(XOLEGLOBALS)

//The single global
extern LPXOLEGLOBALS pOLE;

/*
 * STDTARGETDEVICE structure for ObjSetTargetDevice.
 */

typedef struct
    {
    //Offsets are pointers into the data field.
    UINT    deviceNameOffset;   //szDeviceName
    UINT    driverNameOffset;   //szDriverName
    UINT    portNameOffset;     //szPortName
    UINT    extDevModeOffset;   //DEVMODE structure
    UINT    extDevModeSize;     //Size of the DEVMOCE struct, for versioning.
    UINT    environmentOffset;  //Current MS-DOS environment, in a string
    UINT    environmentSize;    //Length of environment string.
    BYTE    data[];
    } STDTARGETDEVICE;

typedef STDTARGETDEVICE FAR * LPSTDTARGETDEVICE;
#define CBSTDTARGETDEVICE sizeof(STDTARGETDEVICE)

/*
 * Function prototypes for OLE-Specific functions.
 */

//OLECLIP.C
BOOL      WINAPI FOLECopyNative(LPXOLEGLOBALS);
BOOL      WINAPI FOLECopyLink(LPXOLEGLOBALS, BOOL, LPSTR);
HGLOBAL   WINAPI HLinkConstruct(LPSTR, LPSTR, LPSTR);

//OLEDOC.C
LPCOSMODOC  WINAPI PDocumentAllocate(LPOLESERVERDOCVTBL);   //Helper
void        WINAPI DocumentClean(LPCOSMODOC);               //Helper

OLESTATUS WINAPI DocClose(LPCOSMODOC);
OLESTATUS WINAPI DocGetObject(LPCOSMODOC, OLE_LPCSTR, LPLPOLEOBJECT, LPOLECLIENT);
OLESTATUS WINAPI DocExecute(LPCOSMODOC, HGLOBAL);
OLESTATUS WINAPI DocRelease(LPCOSMODOC);
OLESTATUS WINAPI DocSave(LPCOSMODOC);
OLESTATUS WINAPI DocSetColorScheme(LPCOSMODOC, OLE_CONST LOGPALETTE FAR *);
OLESTATUS WINAPI DocSetDocDimensions(LPCOSMODOC, OLE_CONST RECT FAR *);
OLESTATUS WINAPI DocSetHostNames(LPCOSMODOC, OLE_LPCSTR, OLE_LPCSTR);

//OLEEXIT.C
BOOL WINAPI FOLEExit(LPXOLEGLOBALS);

//OLEFILE.C
LPCOSMODOC WINAPI PDocRevokeAndCreate(LPXOLEGLOBALS);

//OLEINIT.C
BOOL      WINAPI FOLEInstanceInit(LPXOLEGLOBALS, HINSTANCE, LPSTR, LPSTR FAR *, UINT);

//OLEMISC.C
void      WINAPI MenuEmbeddingSet(HWND, LPSTR, BOOL);
void      WINAPI OLEClientNotify(LPCOSMOOBJECT, UINT);
BOOL      WINAPI FOLEReleaseWait(BOOL FAR *, LONG);

//OLEOBJ.C
LPCOSMOOBJECT WINAPI PObjectAllocate(LPOLEOBJECTVTBL);

LPVOID        WINAPI ObjQueryProtocol(LPCOSMOOBJECT, OLE_LPCSTR);
OLESTATUS     WINAPI ObjRelease(LPCOSMOOBJECT);
OLESTATUS     WINAPI ObjShow(LPCOSMOOBJECT, BOOL);
OLESTATUS     WINAPI ObjDoVerb(LPCOSMOOBJECT, UINT, BOOL, BOOL);
OLESTATUS     WINAPI ObjGetData(LPCOSMOOBJECT, OLECLIPFORMAT, HGLOBAL FAR *);
OLESTATUS     WINAPI ObjSetData(LPCOSMOOBJECT, OLECLIPFORMAT, HGLOBAL);
OLESTATUS     WINAPI ObjSetTargetDevice(LPCOSMOOBJECT, HGLOBAL);
OLESTATUS     WINAPI ObjSetBounds(LPCOSMOOBJECT, OLE_CONST RECT FAR *);
OLECLIPFORMAT WINAPI ObjEnumFormats(LPCOSMOOBJECT, OLECLIPFORMAT);
OLESTATUS     WINAPI ObjSetColorScheme(LPCOSMOOBJECT, OLE_CONST LOGPALETTE FAR *);

//OLESVR.C
LPCOSMOSERVER WINAPI PServerAllocate(LPOLESERVERVTBL);

OLESTATUS WINAPI ServerCreate(LPCOSMOSERVER, LHSERVERDOC, OLE_LPCSTR, OLE_LPCSTR, LPLPOLESERVERDOC);
OLESTATUS WINAPI ServerCreateFromTemplate(LPCOSMOSERVER, LHSERVERDOC, OLE_LPCSTR, OLE_LPCSTR, OLE_LPCSTR, LPLPOLESERVERDOC);
OLESTATUS WINAPI ServerEdit(LPCOSMOSERVER, LHSERVERDOC, OLE_LPCSTR, OLE_LPCSTR, LPLPOLESERVERDOC);
OLESTATUS WINAPI ServerExecute(LPCOSMOSERVER, HGLOBAL);
OLESTATUS WINAPI ServerExit(LPCOSMOSERVER);
OLESTATUS WINAPI ServerOpen(LPCOSMOSERVER, LHSERVERDOC, OLE_LPCSTR, LPLPOLESERVERDOC);
OLESTATUS WINAPI ServerRelease(LPCOSMOSERVER);

//OLEVTBL.C
BOOL      WINAPI FOLEVtblInitServer(HINSTANCE,   LPOLESERVERVTBL);
BOOL      WINAPI FOLEVtblInitDocument(HINSTANCE, LPOLESERVERDOCVTBL);
BOOL      WINAPI FOLEVtblInitObject(HINSTANCE,   LPOLEOBJECTVTBL);

void      WINAPI OLEVtblFreeServer(LPOLESERVERVTBL);
void      WINAPI OLEVtblFreeDocument(LPOLESERVERDOCVTBL);
void      WINAPI OLEVtblFreeObject(LPOLEOBJECTVTBL);

#endif //MAKEOLESERVER


OLEINIT.C

/*
 * OLEINIT.C
 *
 * Handles all application and instance-specific initialization that
 * is also specific to OLE.  That which is specific only to the
 * application or instance resides in INIT.C
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

#ifdef MAKEOLESERVER

#include <windows.h>
#include <ole.h>
#include "cosmo.h"
#include "oleglobl.h"

/*
 * FOLEInstanceInit
 *
 * Purpose:
 *  Handles OLE-server specific initialziation:
 *      1.  Register clipboard formats for "Native", "OwnerLink",
 *          and "ObjectLink."
 *      2.  Initialize VTBLs for server, document, and object.
 *      3.  Register the server with OLESVR.
 *      4.  Parse the command line to determine initial window state.
 *      5.  Register a document depending on contents of command line.
 *
 * Parameters:
 *  pOLE            LPXOLEGLOBALS to OLE-specific global variable block.
 *  hInstance       HINSTANCE of the application instance.
 *  pszClass        LPSTR to classname of the server.
 *  ppszCmds        LPSTR FAR to command line argument strings.
 *  nCmdShow        UINT initial ShowWindow command passed to WinMain.
 *
 * Return Value:
 *  BOOL            FALSE if an error occurred, otherwise TRUE and
 *                  pOLE->wCmdShow contains a valid ShowWindow command
 *                  for the initial state of the window.
 */

BOOL WINAPI FOLEInstanceInit(LPXOLEGLOBALS pOLE, HINSTANCE hInstance
    , LPSTR pszClass, LPSTR FAR *ppszCmds, UINT nCmdShow)
    {
    OLESTATUS       os;
    LPSTR           pszT;
    LPCOSMOSERVER   pSvr;
    LPCOSMODOC      pDoc;
    BOOL            fTemp;

    //1. Register clipboard formats.
    pOLE->cfNative    =RegisterClipboardFormat(rgpsz[IDS_NATIVE]);
    pOLE->cfOwnerLink =RegisterClipboardFormat(rgpsz[IDS_OWNERLINK]);
    pOLE->cfObjectLink=RegisterClipboardFormat(rgpsz[IDS_OBJECTLINK]);

    if (0==pOLE->cfNative || 0==pOLE->cfOwnerLink || 0==pOLE->cfObjectLink)
        return FALSE;

    /*
     * 2. Initialize the method tables with functions in OLEVTBL.C
     *    and mark the vtbl's as initialized.
     */
    fTemp=TRUE;
    fTemp&=FOLEVtblInitServer  (hInstance, &pOLE->vtblSvr);
    fTemp&=FOLEVtblInitDocument(hInstance, &pOLE->vtblDoc);
    fTemp&=FOLEVtblInitObject  (hInstance, &pOLE->vtblObj);

    if (!fTemp)
        return FALSE;   //Cleanup will follow through FApplicationExit.

    /*
     * 3. Allocate and initialize the OLESERVER structure, in this
     *    application we call it COSMOSERVER.  Uses the server
     *    constructor in OLESVR.C.
     */

    pSvr=PServerAllocate(&pOLE->vtblSvr);

    if ((LPCOSMOSERVER)NULL==pSvr)
        return FALSE;

    pOLE->pSvr=pSvr;
    pSvr->nCmdShow=nCmdShow;        //By default, use what the app was given.

    //4. Register the server application with OLESVR.DLL
    os=OleRegisterServer(pszClass, (LPOLESERVER)pSvr, &pSvr->lh,
                         hInstance, OLE_SERVER_MULTI);

    if (OLE_OK!=os)
        {
        //ServerRelease will NOT be called so we must free the memory.
        LocalFree(pSvr->hMem);
        return FALSE;
        }

    /*
     * 5. Given the pointer to the command-line strings, ppszCmds,
     *    check if we have "[/ | -]Embedding" and a possible filename.
     *
     * NOTE:  We trust whoever called us to find a filename in
     *        the command line, ignoring anything that starts with
     *        a - or / as an argument.
     */

    pszT=*ppszCmds++;

    //Assume stand-alone configuration.
    pGlob->fOLE=FALSE;
    pSvr->fEmbed=FALSE;
    pSvr->fLink=FALSE;

    if (NULL!=pszT)
        {
        /*
         * If the first parameter has - or / leading, skip that character
         * so we can check for "Embedding"
         */
        if('-'==*pszT || '/'==*pszT)
            pszT++;

        //See if we have Embedding at all, in which case we're doing OLE.
        pGlob->fOLE=!lstrcmp(pszT, rgpsz[IDS_EMBEDDING]);

        //Change the ShowWindow command appropriately if we're doing OLE.
        if (pGlob->fOLE)
            pSvr->nCmdShow=SW_HIDE;

        /*
         * Set fLink if there is an additional filename on the
         * command-line, and point pszT there if so.
         */
        if (NULL!=*ppszCmds)
            {
            pSvr->fLink=TRUE;
            pszT=*ppszCmds;
            }
        else
            {
            pSvr->fEmbed=TRUE;
            pszT=NULL;
            }

        //pszT is either NULL or points to a filename.
        }

    /*
     * 6.  Allocate an OLESERVERDOC (COSMODOC) and initialize.  LPTR
     *     to LocalAlloc initializes everything to NULL.
     */
    pDoc=PDocumentAllocate(&pOLE->vtblDoc);

    if ((LPCOSMODOC)NULL==pDoc)
        {
        //DocRelease will not be called, but ServerRelease will.
        LocalFree(pDoc->hMem);
        OleRevokeServer(pSvr->lh);
        return FALSE;
        }

    pSvr->pDoc=pDoc;

    /*
     * 7.  Register documents as necessary.  Remember to register any
     *     stand-alone or linked document regardless of what you are
     *     doing with OLE.  The only case where you do NOT register is
     *     for starting the application as embedded.
     *
     *     In step 5 we set pszT equal to either NULL (embedding or no
     *     file) or to a filename (linking or stand-alone with a file).
     *     So we only have to point pszT to "(Untitled)" if it's NULL
     *     then call OleRegisterServerDoc.
     */

    //Register a document in any case except embedding.
    if (!pSvr->fEmbed)
        {
        pszT=(NULL==pszT) ? rgpsz[IDS_UNTITLED] : pszT;
        os=OleRegisterServerDoc(pSvr->lh, pszT, (LPOLESERVERDOC)pDoc, &pDoc->lh);

        //Must revoke on any error.
        if (OLE_OK!=os)
            {
            OleRevokeServer(pSvr->lh);
            return FALSE;
            }
        }

    //All done!
    return TRUE;
    }

#endif //MAKEOLESERVER


OLEINST.C

/*
 * OLEINST.C
 *
 * OLE-Specific installation-time functions that should be performed
 * once when the application is first added to the system.
 *
 * Note that there are embedded strings in this file.  We do not use
 * strings from the application wide source in rgpsz since we want this
 * module to be reusable in installation programs.
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

#ifdef MAKEOLESERVER

#include <windows.h>
#include "cosmo.h"
#include "oleinst.h"

/*
 * FRegDBInstall
 *
 * Purpose:
 *  Handles any installations the applciation must handle for the
 *  registration database.  In this application, we just set up to
 *  call FOLEInstall that handles the OLE specific strings.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if installation succeeded, FALSE otherwise.
 *
 */

BOOL FAR PASCAL FRegDBInstall(void)
    {
    REGINSTALL  ri;
    HMODULE     hMod;
    LPSTR       rgszVerbs[OBJVERB_MAX];
    char        szPath[CCHPATHMAX];

    /*
     * Fill the REGINSTALL structure for calling FOLEServerInstall.
     * Must do this after the strings are loaded since we use strings
     * from the stringtable.
     */
    ri.pszServerName    =(LPSTR)rgpsz[IDS_FULLNAME];
    ri.pszServerClass   =(LPSTR)rgpsz[IDS_CLASSCOSMO];
    ri.pszHandlerPath   =(LPSTR)NULL;
    ri.pszExt           =(LPSTR)rgpsz[IDS_DOTEXT];
    ri.pszSetFormats    =(LPSTR)rgpsz[IDS_NATIVE];
    ri.pszRequestFormats=(LPSTR)rgpsz[IDS_DATAFORMATS];
    ri.fExecute         =FALSE;

    //Get the module path
    hMod=GetModuleHandle(rgpsz[IDS_MODULE]);

    if (NULL==hMod)
        return FALSE;

    GetModuleFileName(hMod, szPath, CCHPATHMAX);

    ri.pszServerPath    =(LPSTR)szPath;

    //Fill the array of pointers to verbs.
    rgszVerbs[OBJVERB_EDIT]=(LPSTR)rgpsz[IDS_VERBEDIT];

    ri.ppszVerbs        =rgszVerbs;
    ri.cVerbs           =OBJVERB_MAX;

    return FOLEServerInstall(&ri);
    }

/*
 * FOLEServerInstall
 *
 * Purpose:
 *  Registers the server application and it's editable data types
 *  with the registration database through the SHELL.DLL functions.
 *
 *  This function will register the server in the database if it
 *  is not there already.
 *
 * Limitations:
 *  FOLEServerInstall supports only a single class of data.
 *
 * Parameters:
 *  pszServer       LPSTR name of the server.
 *  pszData         LPSTR name of the data edited by the server.
 *
 * Return Value:
 *  BOOL            TRUE if registration took place or the application
 *                  was already registered, FALSE otherwise.
 */

BOOL FAR PASCAL FOLEServerInstall(LPREGINSTALL lpRI)
    {
    char        szKey[128];
    char        szTemp[20];
    DWORD       dw=128;
    LONG        lRet;
    UINT        iVerb;
    LPSTR       *ppsz;

    //Check if this server is already around.
    lRet=RegQueryValue(HKEY_CLASSES_ROOT, lpRI->pszServerClass, szKey, &dw);

    if ((LONG)ERROR_SUCCESS==lRet)
        return TRUE;

    /*
     * Add various strings to the registration database:
     *  1.  English-readable class name.
     *  2.  Simple class name.
     *  3.  Full path to the server executable.
     *  4.  Full path to the handler DLL, if available.
     *  5.  Verbs
     *  6.  Any formats supported through OleSetData and OleRequestData.
     *  7.  Full application path if the server supports StdExecute.
     */

    //Validate essential strings
    if (NULL==lpRI->pszServerClass)
        return FALSE;

    if (NULL==lpRI->pszServerName)
        return FALSE;

    if (NULL==lpRI->pszServerPath)
        return FALSE;

    if (NULL==lpRI->pszExt)
        return FALSE;

    if (NULL==lpRI->ppszVerbs || 0==lpRI->cVerbs)
        return FALSE;

    //1. English class name.  Subkey is ""
    FKeyCreate(lpRI->pszServerClass, rgpsz[IDS_EMPTY], lpRI->pszServerName);

    //2. Simple class name associated with the file extension.  Subkey is ""
    FKeyCreate(lpRI->pszExt, rgpsz[IDS_EMPTY], lpRI->pszServerClass);

    /*
     * The server path, handler path, and verbs are all part of
     * the \\classname\\protocol\\StdFileEditing\\ key.  Before
     * creating any of the keys, create a string with the class
     * name embedded.
     */
    wsprintf(szKey, "%s\\protocol\\StdFileEditing\\", lpRI->pszServerClass);

    //3. Server path, append "server" onto szKey.
    FKeyCreate(szKey, "server", lpRI->pszServerPath);

    //4. Add the handler path, if one exists, appening "handler" on szKey.
    if (NULL!=lpRI->pszHandlerPath)
        FKeyCreate(szKey, "handler", lpRI->pszHandlerPath);

    //5.  Add each verb.  Append to szKey "verb\\n" where n is a simple count.
    ppsz=lpRI->ppszVerbs;

    for (iVerb=0; iVerb < lpRI->cVerbs; iVerb++)
        {
        //Create key and skip to next verb.
        wsprintf(szTemp, "verb\\%d", iVerb);
        FKeyCreate(szKey, szTemp, *ppsz++);
        }

    //6. Add the strings for formats, to "SetDataFormats" and "RequestDataFormats"
    if (NULL!=lpRI->pszSetFormats)
        FKeyCreate(szKey, "SetDataFormats", lpRI->pszSetFormats);

    if (NULL!=lpRI->pszSetFormats)
        FKeyCreate(szKey, "RequestDataFormats", lpRI->pszRequestFormats);

    //7. Add the StdExecute string, appending the server path.
    if (lpRI->fExecute)
        {
        wsprintf(szKey, "%s\\protocol\\StdExecute\\", lpRI->pszServerClass);
        FKeyCreate(szKey, "server", lpRI->pszServerPath);
        }

    return TRUE;
    }

/*
 * FKeyCreate
 *
 * Purpose:
 *  Short function to simplify the key creation process, centralizing
 *  calls to RegCreateKey, RegSetValue, and RegCloseKey.  If the RegSetValue
 *  fails, the key is deleted with RegDeleteKey.
 *
 * Parameters:
 *  pszKey          LPSTR to keyname.
 *  pszSubKey       LPSTR to last word in keyname.  This parameter is
 *                  separate to enable calling RegDeleteKey on error.
 *  pszValue        LPSTR to value to associate with keyname.
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL PASCAL FKeyCreate(LPSTR pszKey, LPSTR pszSubKey, LPSTR pszValue)
    {
    char        szKey[128];
    HKEY        hKey;
    UINT        cch;
    LONG        lRet;

    cch=lstrlen(pszValue)+1;

    lstrcpy(szKey, pszKey);
    lstrcat(szKey, pszSubKey);

    lRet=RegCreateKey(HKEY_CLASSES_ROOT, szKey, &hKey);

    if (lRet!=ERROR_SUCCESS)
        return FALSE;

    lRet=RegSetValue(HKEY_CLASSES_ROOT, szKey, REG_SZ, pszValue, cch);

    if (lRet!=ERROR_SUCCESS)
        {
        //Delete key if we could not set a value.
        RegDeleteKey(hKey, pszSubKey);
        return FALSE;
        }

    lRet=RegCloseKey(hKey);

    if (lRet!=ERROR_SUCCESS)
        return FALSE;

    return TRUE;
    }

#endif   //MAKEOLESERVER


OLEINST.H

/*
 * OLEINST.C
 *
 * OLE-Specific installation data types and function prototypes.
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

#ifdef WIN32
#include <winreg.h>
#else
#include <shellapi.h>
#endif

typedef struct
    {
    LPSTR       pszServerName;      //Full server name.
    LPSTR       pszServerClass;     //Short server class name.
    LPSTR       pszServerPath;      //Full path to server module.
    LPSTR       pszHandlerPath;     //Optional Full path to object handler DLL.
    LPSTR       pszExt;             //File extension for the server.
    LPSTR      *ppszVerbs;          //Pointer to array of LPSTRs to verbs.
    UINT        cVerbs;             //Number of verbs in array.
    LPSTR       pszSetFormats;      //Optional CSV list of accepted formats.
    LPSTR       pszRequestFormats;  //Optional CSV list of requestable formats.
    BOOL        fExecute;           //Is OleExecute supported?
    } REGINSTALL;

typedef REGINSTALL FAR *LPREGINSTALL;
#define CBREGINSTALL sizeof(REGINSTALL)

//Function prototypes
BOOL     WINAPI FRegDBInstall(void);
BOOL     WINAPI FOLEServerInstall(LPREGINSTALL);
BOOL     PASCAL FKeyCreate(LPSTR, LPSTR, LPSTR);

//Verbs
#define OBJVERB_EDIT    0
#define OBJVERB_PLAY    1
#define OBJVERB_MAX     1


OLEMISC.C

/*
 * OLEMISC.C
 *
 * Functions without another approprate home:
 *  MenuEmbeddingSet
 *  OLEClientNotify
 *  FOLEReleaseWait
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

#ifdef MAKEOLESERVER

#include <windows.h>
#include <ole.h>
#include "cosmo.h"
#include "oleglobl.h"

/*
 * MenuEmbeddingSet
 *
 * Purpose:
 *  Modifies the main menu of the application to change "Save" to "Update"
 *  and to change "Exit" to "Exit & return to xx."  Alternately, this
 *  function can change the menus back to the original state, reverting
 *  "Update" to "Save" and setting the Exit item back to plain "Exit."
 *
 * Parameters:
 *  hWnd            HWND of the window with the menu.
 *  pszClient       LPSTR to the client name.
 *  fOLE            BOOL indiciating if we are to set for OLE or to normal.
 *                  If setting to normal, pszClient can be NULL.
 *
 * Return Value:
 *  None.
 *
 */

void WINAPI MenuEmbeddingSet(HWND hWnd, LPSTR pszClient, BOOL fOLE)
    {
    HMENU       hMenu;
    char        szTemp[130];
    LPSTR       pszT;

    hMenu=GetMenu(pGlob->hWnd);

    //Change the File/Save menu to File/Update <client> or vice-versa
    if (fOLE)
        wsprintf(szTemp, rgpsz[IDS_UPDATE], pszClient);
    else
        lstrcpy(szTemp, rgpsz[IDS_SAVE]);

    ModifyMenu(hMenu, IDM_FILESAVE, MF_STRING, IDM_FILESAVE, szTemp);

    //Change the File/Save As menu to File/Save Copy As or vice-versa.
    pszT=(fOLE) ? rgpsz[IDS_SAVECOPYAS] : rgpsz[IDS_SAVEAS];
    ModifyMenu(hMenu, IDM_FILESAVEAS, MF_STRING, IDM_FILESAVEAS, pszT);

    //Change "Exit" to "Exit & return to xx" or vice-versa.
    if (fOLE)
        wsprintf(szTemp, rgpsz[IDS_EXITANDRETURN], pszClient);
    else
        lstrcpy(szTemp, rgpsz[IDS_EXIT]);

    ModifyMenu(hMenu, IDM_FILEEXIT, MF_STRING, IDM_FILEEXIT, szTemp);
    return;
    }

/*
 * OLEClientNotify
 *
 * Purpose:
 *  Performs a direct function call to the single callback in the
 *  client that is communicating with this server application.
 *  This is the only point where there is direct communication
 *  between the two applciations.
 *
 * Parameters:
 *  pObj            LPCOSMOOBJECT that contains a pClient pointer to an
 *                  LPOLECLIENT that holds a pointer to the OLECLIENTVTBL
 *                  that holds a pointer to the CallBack function.
 *  iMsg            UINT, message to send, such as OLE_CLOSED.
 *
 * Return Value:
 *  None.
 */

void WINAPI OLEClientNotify(LPCOSMOOBJECT pObj, UINT iMsg)
    {
    LPOLECLIENT     pClient;
    LPOLECLIENTVTBL pvt;

    if (NULL==pObj)
        return;

    pClient=pObj->pClient;

    if (NULL==pClient)
        return;

    pvt=pClient->lpvtbl;

    if (NULL==pvt)
        return;

    (pvt->CallBack)(pClient, iMsg, (LPOLEOBJECT)pObj);
    return;
    }

/*
 * FOLEReleaseWait
 *
 * Purpose:
 *  Enters a Peek/Translate/Dispatch message loop to process all messages
 *  to the application until a release flag has been sent.  The application
 *  calls this routine whenever it is required to wait until conversations
 *  for OLE have terminated.
 *
 *  Some of the messages processed may be DDE messages (for OLE 1.x) that
 *  are eventually passed to OLESVR which eventually calls the ServerRelease
 *  function (see OLESVR.C).  ServerRelease modifies a BOOL flag indicating
 *  that the server is released.
 *
 *  Therefore we can watch a particular memory location (*pf)
 *  and only exit when that flag is set.
 *
 * Parameters:
 *  pf              BOOL FAR * to the flag to wait on.
 *  lhSvr           LONG for the OLE server
 *
 * Return Value:
 *  BOOL            Contents of *pf.
 *
 */

BOOL WINAPI FOLEReleaseWait(BOOL FAR *pf, LONG lhSvr)
    {
    MSG        msg;

    *pf=FALSE;

    while (FALSE==*pf)
        {
        /*
         * We use PeekMessage here to make a point about power
         * management and ROM Windows--GetMessage, when there's
         * no more messages, will correctly let the system go into
         * a low-power idle state.  PeekMessage by itself will not.
         * If you do background processing in a PeekMessage loop like
         * this, and there's no background processing to be done,
         * then you MUST call WaitMessage to duplicate the idle
         * state like GetMessage.
         */

        if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
            {
            /*
             * We will not see WM_QUIT in the middle of the
             * application since this application MUST call
             * PostQuitMessage to get it in the queue.  Therefore we
             * don't even worry about it.
             */

            TranslateMessage (&msg);
            DispatchMessage (&msg);
            }
        else
            {
            /*
             * If the application has some background processing
             * to do, it should perform a piece of it here.  Otherwise
             * you MUST call WaitMessage or you'll screw up ROM-Windows
             * power-management.
             */

            WaitMessage();
            }
        }

    return *pf;
    }

#endif //MAKEOLESERVER


OLEOBJ.C

/*
 * OLEOBJ.C
 *
 * Contains all callback functions in the OLEOBJECTVTBL struture:
 *      ObjDoVerb
 *      ObjEnumFormats
 *      ObjGetData
 *      ObjQueryProtocol
 *      ObjRelease
 *      ObjSetBounds
 *      ObjSetColorScheme
 *      ObjSetData
 *      ObjSetTargetDevice
 *      ObjShow
 *
 * There are additional callbacks in the OLEOBJECTVTBL.
 * Also contains PObjectAllocate, a constructor for the object.
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

#ifdef MAKEOLESERVER

#include <windows.h>
#include <ole.h>
#include "cosmo.h"
#include "oleglobl.h"
#include "oleinst.h"    //OBJVERB_* defines for ObjDoVerb

/*
 * PObjectAllocate
 *
 * Purpose:
 *  Allocates a COSMOOBJECT structure and sets the defaults in its fields.
 *  Used from DocGetObject.
 *
 * Parameters:
 *  pVtblObj        LPOLESERVERDOCVTBL used to initialize the pvtbl field.
 *
 * Return Value:
 *  LPCOSMOOBJECT   Pointer to the allocated structure in local memory.
 *                  The hMem field will contain a handle to the structure
 *                  to pass to LocalFree.
 */

LPCOSMOOBJECT WINAPI PObjectAllocate(LPOLEOBJECTVTBL pVtblObj)
    {
    HLOCAL          hMem;
    LPCOSMOOBJECT   pObj;

    hMem=LocalAlloc(LPTR, sizeof(COSMOOBJECT));
    pObj=(LPCOSMOOBJECT)(PSTR)hMem;

    //Initialize the object.
    pObj->hMem=hMem;
    pObj->pvtbl=pVtblObj;
    pObj->fRelease=TRUE;

    return pObj;
    }

/*
 * ObjDoVerb
 *
 * Purpose:
 *  Performs actions on an object when the user opens an object
 *  according to the verb given.
 *
 * Parameters:
 *  pObj            LPCOSMOOBJECT specifying the object affected.
 *  iVerb           UINT index to the verb chosen, zero based.
 *  fShow           BOOL--TRUE if the application should show
 *                  itself with ShowWindow.  FALSE means no change.
 *  fFocus          BOOL--TRUE if the server should get the focus.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

OLESTATUS WINAPI ObjDoVerb(LPCOSMOOBJECT pObj, UINT iVerb
    , BOOL fShow, BOOL fFocus)
    {
    /*
     * 1.   Execute the verb.
     *        a.  For a 'Play' verb, a server does not generally show
     *            its window or affect the focus.
     *
     *        b.  For an 'Edit' verb, show the server's window and the
     *            object if fShow is TRUE, and take the focus if fFocus
     *            is TRUE.  An ideal way to accomplish this is to call
     *            the ObjShow method through the OLEOBJECTVTBL since that
     *            method will handle showing the object and taking the
     *            focus itself.
     *
     *        c.  An 'Open' verb is not clearly defined; depending on the
     *            application it may mean the same as 'Play' or 'Edit.'
     *            The Cosmo server, if it had an 'Open' verb, would treat
     *            it like 'Edit.'
     *
     * 2.  Return OLE_OK if the verb was successfully executed, otherwise
     *     OLE_ERROR_DOVERB.
     */

    switch (iVerb)
        {
        case OBJVERB_EDIT:
            /*
             * On edit, simply show yourself and expect a SetData.
             * Best strategy is to use the Show method for this
             * object if necessary, reducing redundancy.
             */
            if (fShow)
                return (pObj->pvtbl->Show)((LPOLEOBJECT)pObj, fShow);

            //Return OLE_OK
            break;

        case OBJVERB_PLAY:
            //Unsupported at this time.
            return OLE_ERROR_DOVERB;

        default:
            return OLE_ERROR_DOVERB;
        }

    return OLE_OK;
    }

/*
 * ObjEnumFormats
 *
 * Purpose:
 *  Requests the server to produce the 'next' supported clipboard
 *  format.  Each format is returned in sequence until the terminator
 *  (a NULL) is returned.
 *
 * Parameters:
 *  pObj            LPCOSMOOBJECT specifying the object affected.
 *  cf              OLECLIPFORMAT the last clipboard format returned by
 *                  this function.  0 indicates the first.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

OLECLIPFORMAT WINAPI ObjEnumFormats(LPCOSMOOBJECT pObj, OLECLIPFORMAT cf)
    {
    /*
     * 1.   Depending on cf, return the 'next' clipboard format in which
     *      the server can render the object's data.
     * 2.   If there are no more supported formats after the format in cf,
     *      return NULL.
     *
     * We must use multiple if's instead of a switch statement because
     * all the cf* values are not constants.
     */

    if (0==cf)
       return pOLE->cfNative;

    if (pOLE->cfNative==cf)
       return pOLE->cfOwnerLink;

    if (pOLE->cfOwnerLink==cf)
       return CF_METAFILEPICT;

    if (CF_METAFILEPICT==cf)
       return CF_BITMAP;

    if (CF_BITMAP==cf)
       return pOLE->cfObjectLink;

    //This IF is here just to be explicit.
    if (pOLE->cfObjectLink==cf)
       return 0;

    return 0;
    }

/*
 * ObjGetData
 *
 * Purpose:
 *  Retrieves data from the object in a specified format, where the
 *  server should allocate memory (GlobalAlloc with GMEM_DDESHARE),
 *  fill the memory, and return the handle.
 *
 * Parameters:
 *  pObj            LPCOSMOOBJECT specifying the object affected.
 *  cf              OLECLIPFORMAT format to return data in, may be "Native."
 *  phData          HGLOBAL FAR * in which to store the allocated handle.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

OLESTATUS WINAPI ObjGetData(LPCOSMOOBJECT pObj, OLECLIPFORMAT cf
    , HGLOBAL FAR * phData)
    {
    HGLOBAL             hMem;

    /*
     * 1.   Allocate the requested data throguh GlobalAlloc (with
     *      GMEM_MOVEABLE and GMEM_DDESHARE).  The exception is data for
     *      CF_BITMAP which uses a call like CreateBitmap.
     * 2.   Lock and fill the memory with the appropriate data.
     * 3.   Unlock the memory and store the handle in *phData.
     * 4.   Return OLE_OK if successful, OLE_ERROR_MEMORY otherwise.
     */

    //Return Native, metafile, or bitmap as requested.
    if (pOLE->cfNative==cf)
        hMem=HGetPolyline(pGlob->hWndPolyline);

    if (CF_METAFILEPICT==cf)
        hMem=HGetMetafilePict(pGlob->hWndPolyline);

    if (CF_BITMAP==cf)
        hMem=HGetBitmap(pGlob->hWndPolyline);

    //Use filename
    if (pOLE->cfObjectLink==cf)
         hMem=HLinkConstruct(rgpsz[IDS_CLASSCOSMO], "", "");

    /*
     * Even though this is exactly like ObjectLink, this is coded as
     * a separate case just in case it changes in the future.
     */
    if (pOLE->cfOwnerLink==cf)
        hMem=HLinkConstruct(rgpsz[IDS_CLASSCOSMO], "", "");

    if (NULL==hMem)
        return OLE_ERROR_MEMORY;

    *phData=hMem;
    return OLE_OK;
    }

/*
 * ObjQueryProtocol
 *
 * Purpose:
 *  Returns a pointer to an COSMOOBJECT with the appropriate VTBL for
 *  the protocol, either StdFileEditing or StdExecute.  Returns NULL
 *  if that protocol is not supported.
 *
 * Parameters:
 *  pObj            LPCOSMOOBJECT specifying the object affected.
 *  pszProtocol     OLE_LPCSTR, the protocol name.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

LPVOID WINAPI ObjQueryProtocol(LPCOSMOOBJECT pObj, OLE_LPCSTR pszProtocol)
    {

    /*
     * 1.   If the protocol in pszProtocol is supported, return a pointer
     *      to an object that contains an appropriate VTBL fot that protocol,
     *      such as the pObj passed to this method.
     * 2.   If the protocol is not supported, return NULL.
     */

    //Check if OLESVR is asking for "StdFileEditing"
    if (0==lstrcmp(pszProtocol, rgpsz[IDS_STDFILEEDITING]))
        return (LPVOID)pObj;

    return (LPVOID)NULL;
    }

/*
 * ObjRelease
 *
 * Purpose:
 *  Notifies a server that is can free any resources associated with an
 *  object.
 *
 * Parameters:
 *  pObj            LPCOSMOOBJECT specifying the object affected.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

OLESTATUS WINAPI ObjRelease(LPCOSMOOBJECT pObj)
    {
    /*
     * 1.   Free any resources allocated for this object, but
     *      DO NOT free the OLEOBJECT structure itself.
     * 2.   Set a flag to indicate that Release has been called.
     * 3.   NULL any saved LPOLECLIENT stores in the OLEOBJECT structure.
     * 4.   Return OLE_OK if successful, OLE_ERROR_GENERIC otherwise.
     *
     * Do not use the Release method to free the memory containing
     * the object since you still need to use its pClient and fRelease.
     * This method simply notifies the object that no one is using it
     * anymore so we don't try to send notifications.
     */

    pObj->fRelease=TRUE;
    pObj->pClient=NULL;
    return OLE_OK;
    }

/*
 * ObjSetBounds
 *
 * Purpose:
 *  Specifies a new boundary rectangle for the object in MM_HIMETRIC
 *  units.  Only useful for embedded objects since a linked object
 *  depends on the file loaded.
 *
 * Parameters:
 *  pObj            LPCOSMOOBJECT specifying the object affected.
 *  pRect           LPRECT containing the new bounds.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

OLESTATUS WINAPI ObjSetBounds(LPCOSMOOBJECT pObj, OLE_CONST RECT FAR *pRect)
    {
    //Unused in OLE1.x
    return OLE_OK;
    }

/*
 * ObjSetColorScheme
 *
 * Purpose:
 *  Provides a color scheme that the client application recommends for
 *  rendering graphical data.
 *
 * Parameters:
 *  pDoc            LPCOSMOOBJECT specifying the object affected.
 *  pPal            LPLOGPALETTE describing the recommended palette.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

OLESTATUS WINAPI ObjSetColorScheme(LPCOSMOOBJECT pObj
    , OLE_CONST LOGPALETTE FAR *pPal)
    {
    /*
     * Servers are not required to use this palette.  The LPLOGPALETTE
     * only is a convenient structure to contain the color scheme; IT DOES
     * not REPRESENT A PALETTE IN STRICT TERMS!  Do NOT call CreatePalette
     * and try to realize it.
     *
     * The color scheme contained in the LOGPALETTE structure contains
     * a number of colors where the first color is the suggested foreground,
     * the second the suggested background, then the first HALF of those
     * remaining are suggested fill colors and the last half suggested
     * line colors.  If there are an odd number of colors, give the extra
     * color to the fill colors, that is, there is one less line color than
     * fill colors.
     */

    return OLE_ERROR_PALETTE;
    }

/*
 * ObjSetData
 *
 * Purpose:
 *  Instructs the object to take the given data as current and copy it
 *  to the object's internal data, use when a client opens an embedded
 *  object or if the client calls OleSetData.
 *
 *  *NOTE: This function MUST be supported even if the registration
 *         database does not contain an entry for SetDataFormats for
 *         this server, because the callback is still used when opening
 *         an embedded object for editing.
 *
 * Parameters:
 *  pObj            LPCOSMOOBJECT specifying the object affected.
 *  cf              OLECLIPFORMAT format to return data in, may be "Native."
 *  hData           HGLOBAL to memory containing the data.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

OLESTATUS WINAPI ObjSetData(LPCOSMOOBJECT pObj, OLECLIPFORMAT cf
    , HGLOBAL hData)
    {
    LPPOLYLINE          lppl;

    /*
     * 1.   If the data format is not supported, return OLE_ERROR_FORMAT.
     * 2.   Attempt to GlobalLock the memory to get a pointer to the data.
     *      If GlobalLock returns NULL, return OLE_ERROR_MEMORY.
     * 3.   Copy the data to the object identified by pObj.
     * 4.   Unlock and GlobalFree the data handle.  The ObjSetData method
     *      is responsible for the memory.
     * 5.   Return OLE_OK.
     */

    //Check if we were given Native data.  We don't support anything else.
    if (pOLE->cfNative!=cf)
        return OLE_ERROR_FORMAT;

    lppl=(LPPOLYLINE)GlobalLock(hData);

    /*
     * CHECK the return from GlobalLock since we don't know where this
     * handle has been.
     */
    if (NULL==lppl)
        return OLE_ERROR_MEMORY;

    //Set the data through the editing window.
    SendMessage(pGlob->hWndPolyline, PLM_POLYLINESET, TRUE, (LONG)lppl);

    //Make sure we are not dirty--no updating is yet necessary.
    FDirtySet(FALSE);

    //Server is responsible for freeing the data.
    GlobalUnlock(hData);
    GlobalFree(hData);
    return OLE_OK;
    }

/*
 * ObjSetTargetDevice
 *
 * Purpose:
 *  Communicates information from the client application to the server
 *  about the device on which the object is drawn.
 *
 * Parameters:
 *  pObj            LPCOSMOOBJECT specifying the object affected.
 *  hData           HGLOBAL containing a STDTARGETDEVICE structure.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

OLESTATUS WINAPI ObjSetTargetDevice(LPCOSMOOBJECT pObj, HGLOBAL hData)
    {

    /*
     * The server is responsible for freeing the the data handle
     * once it has finished using that data.
     *
     * If NULL==hData, then rendering is necessary for the screen.
     */

    if (NULL!=hData)
        GlobalFree(hData);

    return OLE_OK;
    }

/*
 * ObjShow
 *
 * Purpose:
 *  Causes the server to show an object, showing the window and
 *  scrolling it's client area if necessary.
 *
 * Parameters:
 *  pObj            LPCOSMOOBJECT specifying the object affected.
 *  fTakeFocus      BOOL:  TRUE if the server should get the focus,
 *                  FALSE if not.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

OLESTATUS WINAPI ObjShow(LPCOSMOOBJECT pObj, BOOL fTakeFocus)
    {
    /*
     * 1.   Show the application window(s) if not already visible.
     * 2.   Scroll the object identified by pObj into view, if necessary.
     * 3.   Select the object if possible.
     * 4.   If fTakeFocus is TRUE, call SetFocus with the main window handle.
     * 5.   Return OLE_OK if successful, OLE_ERROR_GENERIC otherwise.
     */

    //Keep WM_SIZE on the ShowWindow from making us dirty.
    pGlob->fNoDirty=TRUE;

    //Since we only have one object, we don't care what's in pObj.
    ShowWindow(pGlob->hWnd, SW_NORMAL);

    pGlob->fNoDirty=FALSE;

    if (fTakeFocus)
        SetFocus(pGlob->hWnd);

    return OLE_OK;
    }

#endif //MAKEOLESERVER


OLESVR.C

/*
 * OLESVR.C
 *
 * Contains all callback functions in the OLESERVERVTBL struture:
 *      ServerCreate
 *      ServerCreateFromTemplate
 *      ServerEdit
 *      ServerExecute
 *      ServerExit
 *      ServerOpen
 *      ServerRelease
 *
 * Also includes the constructor function PServerAllocate.
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

#ifdef MAKEOLESERVER

#include <windows.h>
#include <ole.h>
#include "cosmo.h"
#include "oleglobl.h"

/*
 * PServerAllocate
 *
 * Purpose:
 *  Allocates a COSMOSERVER structure and sets the defaults in its fields.
 *  Used from application initialization.
 *
 * Parameters:
 *  pVtblSvr        LPOLESERVERVTBL used to initialize the pvtbl field.
 *
 * Return Value:
 *  LPCOSMOSERVER   Pointer to the allocated structure in local memory.
 *                  The hMem field will contain a handle to the structure
 *                  to pass to LocalFree.
 *
 */

LPCOSMOSERVER WINAPI PServerAllocate(LPOLESERVERVTBL pVtblSvr)
    {
    HLOCAL            hMem;
    LPCOSMOSERVER     pSvr;

    hMem=LocalAlloc(LPTR, CBCOSMOSERVER);

    if (NULL==hMem)
        return FALSE;

    pSvr=(LPCOSMOSERVER)(PSTR)hMem;

    //All fields are initially NULL from LMEM_ZEROINIT.
    pSvr->hMem=hMem;
    pSvr->pvtbl=pVtblSvr;
    pSvr->fRelease=TRUE;            //Indicate a released state.

    return pSvr;
    }

/*
 * ServerCreate
 *
 * Purpose:
 *  Creates a new object of the class in pszClass which will be
 *  embedded in a client application document whose name is in
 *  pszDoc.
 *
 * Parameters:
 *  pSvr            LPCOSMOSERVER to structure identifying the server.
 *  lhDoc           LHSERVERDOC identifying the document.
 *  pszClass        OLE_LPCSTR specifying the class of document to create.
 *  pszDoc          OLE_LPCSTR specifying the document for use in window titles.
 *  ppServerDoc     LPLPOLESERVERDOC in which to store a pointer
 *                  to the OLESERVERDOC structure for this new document.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

OLESTATUS WINAPI ServerCreate(LPCOSMOSERVER pSvr, LHSERVERDOC lhDoc
    , OLE_LPCSTR pszClass, OLE_LPCSTR pszDoc, LPLPOLESERVERDOC ppServerDoc)
    {
    LPCOSMODOC      pDoc;

    /*
     * 1.   Create a document of the specified class.
     * 2.   Allocate and initialize an OLESERVERDOC structure.
     * 3.   Store lhDoc in the OLESERVERDOC structure.
     * 4.   Store a pointer to the new OLESERVERDOC structure in ppServerDoc.
     * 5.   Return OLE_OK if successful, OLE_ERROR_NEW otherwise.
     */

    /*
     * If this were an MDI application, then we would want to create
     * a new window and a new document, including a new OLESERVERDOC
     * structure.  The protocol supports this, however, this application
     * is single-document.
     */

    pDoc=PDocumentAllocate(&pOLE->vtblDoc);

    if ((LPCOSMODOC)NULL==pDoc)
        return OLE_ERROR_NEW;

    pSvr->pDoc=pDoc;

    pDoc->lh=lhDoc;
    *ppServerDoc=(LPOLESERVERDOC)pDoc;
    return OLE_OK;
    }

/*
 * ServerCreateFromTemplate
 *
 * Purpose:
 *  Creates a new document initialized with the data in a specified
 *  file.  Thw new document is opened for editing and is embedded
 *  within the client when the server is updated or closed.
 *
 * Parameters:
 *  pSvr            LPCOSMOSERVER to structure identifying the server.
 *  lhDoc           LHSERVERDOC identifying the document.
 *  pszClass        OLE_LPCSTR specifying the class of document to create.
 *  pszDoc          OLE_LPCSTR to the permanent name of the document to open.
 *  pszTemp         OLE_LPCSTR to the file to use as a template.
 *  ppServerDoc     LPLPOLESERVERDOC in which to store a pointer
 *                  to the OLESERVERDOC structure for this new document.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

OLESTATUS WINAPI ServerCreateFromTemplate(LPCOSMOSERVER pSvr
    , LHSERVERDOC lhDoc, OLE_LPCSTR pszClass, OLE_LPCSTR pszDoc
    , OLE_LPCSTR pszTemp, LPLPOLESERVERDOC ppServerDoc)
    {
    POLYLINE        pl;
    LPCOSMODOC      pDoc;

    /*
     * 1.   Create a document of the specified class.
     * 2.   Read the contents of the specified file and initialize the document.
     * 3.   Allocate and initialize an OLESERVERDOC structure.
     * 4.   Store lhDoc in the OLESERVERDOC structure.
     * 5.   Store a pointer to the new OLESERVERDOC structure in ppServerDoc.
     * 6.   Return OLE_OK if successful, OLE_ERROR_TEMPLATE otherwise.
     */

    //pszTemp contains a filename.
    if (!FCosFileRead(pGlob, (LPSTR)pszTemp, &pl))
        return OLE_ERROR_TEMPLATE;

    pDoc=PDocumentAllocate(&pOLE->vtblDoc);

    if ((LPCOSMODOC)NULL==pDoc)
        return OLE_ERROR_TEMPLATE;

    pSvr->pDoc=pDoc;

    pDoc->lh=lhDoc;
    *ppServerDoc=(LPOLESERVERDOC)pDoc;

    SendMessage(pGlob->hWndPolyline, PLM_POLYLINESET, TRUE, (LONG)(LPSTR)&pl);

    //The titles in this window will be set later through SetHostNames.
    pGlob->fOpenFile=TRUE;
    return OLE_OK;
    }

/*
 * ServerEdit
 *
 * Purpose:
 *  Creates a document initialized with data from a subsequent call
 *  to the SetData function.  The object is embedded in the client
 *  application and the server is not visible.
 *
 * Parameters:
 *  pSvr            LPCOSMOSERVER to structure identifying the server.
 *  lhDoc           LHSERVERDOC identifying the document.
 *  pszClass        OLE_LPCSTR specifying the class of document to create.
 *  pszDoc          OLE_LPCSTR specifying the document for use in window titles.
 *  ppServerDoc     LPLPOLESERVERDOC in which to store a pointer
 *                  to the OLESERVERDOC structure for this new document.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

OLESTATUS WINAPI ServerEdit(LPCOSMOSERVER pSvr, LHSERVERDOC lhDoc
    , OLE_LPCSTR pszClass, OLE_LPCSTR pszDoc, LPLPOLESERVERDOC ppServerDoc)
    {
    LPCOSMODOC      pDoc;

    /*
     * This is the same as Open, but tells the server to expect
     * a SetData call and that the server is initially hidden.
     *
     * 1.   Create a document of the specified class.
     * 2.   Allocate and initialize an OLESERVERDOC structure.
     * 3.   Store lhDoc in the OLESERVERDOC structure.
     * 4.   Store pointer to the new OLESERVERDOC structure in ppServerDoc.
     * 5.   Return OLE_OK if successful, OLE_ERROR_EDIT otherwise.
     */

    pDoc=PDocumentAllocate(&pOLE->vtblDoc);

    if ((LPCOSMODOC)NULL==pDoc)
        return OLE_ERROR_EDIT;

    pSvr->pDoc=pDoc;

    pDoc->lh=lhDoc;
    *ppServerDoc=(LPOLESERVERDOC)pDoc;

    return OLE_OK;
    }

/*
 * ServerExecute
 *
 * Purpose:
 *  Handles DDE Execute commands sent from the server library
 *  from the client application.  Not all applications need to support
 *  DDE Execute.
 *
 * Parameters:
 *  pSvr            LPCOSMOSERVER identifying the server closing.
 *  hCommands       HGLOBAL to memory containing the DDE execute
 *                  string.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 *                  This functions returns OLE_ERROR_COMMAND to indicate
 *                  that it is not implemented.
 */

OLESTATUS WINAPI ServerExecute(LPCOSMOSERVER pSvr, HGLOBAL hCommands)
    {
    /*
     * 1.   Lock the hCommands handle to access the execute strings.
     * 2.   Parse and execute the commands as necessary.
     * 3.   DO NOT FREE hCommands.
     * 4.   Return OLE_OK if successful, OLE_ERROR_COMMAND otherwise.
     */

    return OLE_ERROR_COMMAND;
    }

/*
 * ServerExit
 *
 * Purpose:
 *  Instructs the server application to close documents and shut down.
 *
 * Parameters:
 *  pSvr            LPCOSMOSERVER identifying the server closing.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

OLESTATUS WINAPI ServerExit(LPCOSMOSERVER pSvr)
    {

    /*
     * 1.   Hide the window to prevent any user interaction.
     * 2.   Call OleRevokeServer.  Ignore an OLE_WAIT_FOR_RELEASE return value.
     * 3.   Perform whatever action is necessary to cause the application
     *      to terminate, such as DestroyWindow.
     * 4.   Return OLE_OK if successful, OLE_ERROR_GENERIC otherwise.
     */

    ShowWindow(pGlob->hWnd, SW_HIDE);

    pSvr->fRelease=FALSE;
    OleRevokeServer(pSvr->lh);

    DestroyWindow(pGlob->hWnd);
    return OLE_OK;
    }

/*
 * ServerOpen
 *
 * Purpose:
 *  Opens an exiting file (document) and prepares the document for
 *  editing, generally done when a user double-clicks a linked
 *  object in the client.  Note that the server is hidden at this
 *  point.
 *
 * Parameters:
 *  pSvr            LPCOSMOSERVER to structure identifying the server.
 *  lhDoc           LHSERVERDOC identifying the document.
 *  pszDoc          OLE_LPCSTR to the permanent name of the document to
 *                  be opened.
 *  ppServerDoc     LPLPOLESERVERDOC in which to store a pointer
 *                  to the OLESERVERDOC structure for this new document.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

OLESTATUS WINAPI ServerOpen(LPCOSMOSERVER pSvr, LHSERVERDOC lhDoc
    , OLE_LPCSTR pszDoc, LPLPOLESERVERDOC ppServerDoc)
    {
    POLYLINE        pl;
    LPCOSMODOC      pDoc;

    /*
     * 1.   Create a document of the specified class.
     * 2.   Read the contents of the specified file and initialize the document.
     * 3.   Save the filename of the loaded file with this document,
     *      if necessary.
     * 4.   Allocate and initialize an OLESERVERDOC structure.
     * 5.   Store lhDoc in the OLESERVERDOC structure.
     * 6.   Store a pointer to the new OLESERVERDOC structure in ppServerDoc.
     * 7.   Return OLE_OK if successful, OLE_ERROR_OPEN otherwise.
     */

    if (!FCosFileRead(pGlob, (LPSTR)pszDoc, &pl))
        return OLE_ERROR_OPEN;

    pDoc=PDocumentAllocate(&pOLE->vtblDoc);

    if ((LPCOSMODOC)NULL==pDoc)
        return OLE_ERROR_OPEN;

    //Save the document in the server for later access in ServerRelease.
    pSvr->pDoc=pDoc;

    pDoc->lh=lhDoc;
    *ppServerDoc=(LPOLESERVERDOC)pDoc;

    WindowTitleSet(pGlob->hWnd, (LPSTR)pszDoc);
    SendMessage(pGlob->hWndPolyline, PLM_POLYLINESET, TRUE, (LONG)(LPSTR)&pl);
    pGlob->fOpenFile=TRUE;

    return OLE_OK;
    }

/*
 * ServerRelease
 *
 * Purpose:
 *  Notifies a server that all connections to it have been closed and
 *  that the server application can terminate.  This function should
 *  change the state of a global flag that causes any PeekMessage
 *  waiting loop to exit.
 *
 * Parameters:
 *  pSvr            LPCOSMOSERVER identifying the server.
 *
 * Return Value:
 *  OLESTATUS       OLE_OK if all is well, otherwise an OLE_* error value.
 */

OLESTATUS WINAPI ServerRelease(LPCOSMOSERVER pSvr)
    {
    /*
     * 1.   Set a flag to indicate that Release has been called.
     * 2.   If the application is hidden, we must use ServerRelease to
     *      instruct the application to terminate, by posting a WM_CLOSE
     *      or otherwise effective message, then exit the method with OLE_OK.
     * 3.   Free any resources allocated for this server, like documents,
     *      but DO NOT free the OLESERVER structure itself.
     * 4.   Return OLE_OK if successful, OLE_ERROR_GENERIC otherwise.
     */

    pSvr->fRelease=TRUE;

    /*
     * If we are invisible when we get ServerRelease, that's a flag
     * to us meaning exit.  Posting a WM_CLOSE takes care of all
     * this.  Note that even though WM_CLOSE processing in COSMO.C
     * checks for a dirty file and asks to save if necessary, the
     * file cannot be dirty because there has been no chance for
     * the user to make any changes.
     *
     * ServerRelease may be called twice when the server is opened invisible
     * for an update of a client object.  In this case, we'll get
     * ServerRelease once, where we should post a message to terminate the
     * application.  The second time around we need to handle free resources
     * associated with the server.  We detect this through the validity
     * pOLE->pSvr->lh which we set to 0L in FFileExit before calling
     * OleRevokeServer.  0L in lh signals us that we're in the final
     * revoke and we can free documents.
     *
     */
    if (!IsWindowVisible(pGlob->hWnd) && 0L!=pOLE->pSvr->lh)
        {
        PostMessage(pGlob->hWnd, WM_CLOSE, 0, 0L);
        return OLE_OK;
        }

    /*
     * Free the document if we are closing from OleRevokeServer, not through
     * a client releasing the server.
     */
    if (0L==pOLE->pSvr->lh)
        {
        //Free the document we're holding.
        if (NULL!=pSvr->pDoc)
            {
            if (NULL!=pSvr->pDoc->hMem)
                LocalFree(pSvr->pDoc->hMem);
            }

        pSvr->pDoc=NULL;
        }

    return OLE_OK;
    }

#endif //MAKEOLESERVER


OLEVTBL.C

/*
 * OLEVTBL.C
 *
 * Creates or frees the procedure instances for the server, document,
 * and object VTBLs.  There are two functions for each table:  one
 * to initialize the table, the other to free the instances in the table.
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

#ifdef MAKEOLESERVER

#include <windows.h>
#include <ole.h>
#include "cosmo.h"
#include "oleglobl.h"

#ifdef MSC
/*
 * This disables Microsoft VC++ 1.x warnings we get from assigning
 * functions with our app-specific prototypes to the fixed prototypes
 * in the VTBL structures.  The functionality of this application was
 * verified before adding this pragma.
 */
#pragma warning(disable:4028)
#ifndef WIN32
#pragma warning(disable:4113)
#endif
#endif

/*
 * FOLEVtblInitServer
 *
 * Purpose:
 *  Creates procedure instances for all the OLE methods required
 *  by the server library for standard server methods.
 *
 * Parameters:
 *  hInst           HINSTANCE of the application instance.
 *  pvt             LPOLESERVERVTBL to the VTBL to initialize.
 *
 * Return Value:
 *  None
 *
 * Customization:
 *  None required.
 */

BOOL WINAPI FOLEVtblInitServer(HINSTANCE hInst, LPOLESERVERVTBL pvt)
    {
    BOOL        fRet=TRUE;

   #ifdef WIN32
    pvt->Create            =ServerCreate;
    pvt->CreateFromTemplate=ServerCreateFromTemplate;
    pvt->Edit              =ServerEdit;
    pvt->Execute           =ServerExecute;
    pvt->Exit              =ServerExit;
    pvt->Open              =ServerOpen;
    pvt->Release           =ServerRelease;
   #else
    pvt->Create            =(LPVOID)MakeProcInstance(ServerCreate,             hInst);
    pvt->CreateFromTemplate=(LPVOID)MakeProcInstance(ServerCreateFromTemplate, hInst);
    pvt->Edit              =(LPVOID)MakeProcInstance(ServerEdit,               hInst);
    pvt->Execute           =(LPVOID)MakeProcInstance(ServerExecute,            hInst);
    pvt->Exit              =(LPVOID)MakeProcInstance(ServerExit,               hInst);
    pvt->Open              =(LPVOID)MakeProcInstance(ServerOpen,               hInst);
    pvt->Release           =(LPVOID)MakeProcInstance(ServerRelease,            hInst);

    fRet =(NULL!=pvt->Create);
    fRet&=(NULL!=pvt->CreateFromTemplate);
    fRet&=(NULL!=pvt->Edit);
    fRet&=(NULL!=pvt->Execute);
    fRet&=(NULL!=pvt->Exit);
    fRet&=(NULL!=pvt->Open);
    fRet&=(NULL!=pvt->Release);
   #endif

    return fRet;
    }

/*
 * OLEVtblFreeServer
 *
 * Purpose:
 *  Frees all procedure instances in the server VTBL.
 *
 * Parameters:
 *  pvt             LPOLESERVERVTBL to the VTBL to free.
 *
 * Return Value:
 *  none
 *
 * Customization:
 *  None required.
 */

void WINAPI OLEVtblFreeServer(LPOLESERVERVTBL pvt)
    {
   #ifndef WIN32
    FreeProcInstance(pvt->Create);
    FreeProcInstance(pvt->CreateFromTemplate);
    FreeProcInstance(pvt->Edit);
    FreeProcInstance(pvt->Exit);
    FreeProcInstance(pvt->Open);
    FreeProcInstance(pvt->Release);
   #endif

    return;
    }

/*
 * FOLEVtblInitDocument
 *
 * Purpose:
 *  Creates procedure instances for all the OLE methods required
 *  for document methods.
 *
 * Parameters:
 *  hInst           HINSTANCE of the application instance.
 *  pvt             LPOLESERVERDOCVTBL to the VTBL to initialize.
 *
 * Return Value:
 *  None
 *
 * Customization:
 *  None required.
 */

BOOL WINAPI FOLEVtblInitDocument(HINSTANCE hInst, LPOLESERVERDOCVTBL pvt)
    {
    BOOL        fRet=TRUE;

   #ifdef WIN32
    pvt->Close           =DocClose;
    pvt->GetObject       =DocGetObject;
    pvt->Execute         =DocExecute;
    pvt->Release         =DocRelease;
    pvt->Save            =DocSave;
    pvt->SetColorScheme  =DocSetColorScheme;
    pvt->SetDocDimensions=DocSetDocDimensions;
    pvt->SetHostNames    =DocSetHostNames;
   #else
    pvt->Close           =(LPVOID)MakeProcInstance(DocClose,            hInst);
    pvt->GetObject       =(LPVOID)MakeProcInstance(DocGetObject,        hInst);
    pvt->Execute         =(LPVOID)MakeProcInstance(DocExecute,          hInst);
    pvt->Release         =(LPVOID)MakeProcInstance(DocRelease,          hInst);
    pvt->Save            =(LPVOID)MakeProcInstance(DocSave,             hInst);
    pvt->SetColorScheme  =(LPVOID)MakeProcInstance(DocSetColorScheme,   hInst);
    pvt->SetDocDimensions=(LPVOID)MakeProcInstance(DocSetDocDimensions, hInst);
    pvt->SetHostNames    =(LPVOID)MakeProcInstance(DocSetHostNames,     hInst);

    fRet =(NULL!=pvt->Close);
    fRet&=(NULL!=pvt->GetObject);
    fRet&=(NULL!=pvt->Execute);
    fRet&=(NULL!=pvt->Release);
    fRet&=(NULL!=pvt->Save);
    fRet&=(NULL!=pvt->SetColorScheme);
    fRet&=(NULL!=pvt->SetDocDimensions);
    fRet&=(NULL!=pvt->SetHostNames);
   #endif

    return fRet;
    }

/*
 * OLEVtblFreeDocument
 *
 * Purpose:
 *  Frees all procedure instances in the document VTBL.
 *
 * Parameters:
 *  pvt             LPOLESERVERDOCVTBL to the VTBL to free.
 *
 * Return Value:
 *  none
 *
 * Customization:
 *  None required.
 */

void WINAPI OLEVtblFreeDocument(LPOLESERVERDOCVTBL pvt)
    {
   #ifndef WIN32
    FreeProcInstance(pvt->Close);
    FreeProcInstance(pvt->GetObject);
    FreeProcInstance(pvt->Release);
    FreeProcInstance(pvt->Save);
    FreeProcInstance(pvt->SetColorScheme);
    FreeProcInstance(pvt->SetDocDimensions);
    FreeProcInstance(pvt->SetHostNames);
   #endif

    return;
    }

/*
 * FOLEVtblInitObject
 *
 * Purpose:
 *  Creates procedure instances for all the OLE methods required
 *  for object methods.
 *
 * Parameters:
 *  hInst           HINSTANCE of the application instance.
 *  pvt             LPOLEOBJECTVTBL to free.
 *
 * Return Value:
 *  None
 *
 * Customization:
 *  Your application might not use global variables for srvrvtbl,
 *  docvtbl, and objvtbl.
 */

BOOL WINAPI FOLEVtblInitObject(HINSTANCE hInst, LPOLEOBJECTVTBL pvt)
    {
    BOOL        fRet=TRUE;

    /*
     * Local variables are used here just to make this one assignment
     * more readable since it requires some typecasting to compile clean
     * at warning level 3.
     */
   #ifdef WIN32
    pvt->QueryProtocol  =ObjQueryProtocol;
    pvt->DoVerb         =ObjDoVerb;
    pvt->EnumFormats    =ObjEnumFormats;
    pvt->GetData        =ObjGetData;
    pvt->Release        =ObjRelease;
    pvt->SetBounds      =ObjSetBounds;
    pvt->SetColorScheme =ObjSetColorScheme;
    pvt->SetData        =ObjSetData;
    pvt->SetTargetDevice=ObjSetTargetDevice;
    pvt->Show           =ObjShow;
   #else
    pvt->QueryProtocol  =(LPVOID)MakeProcInstance((FARPROC)ObjQueryProtocol, hInst);;
    pvt->DoVerb         =(LPVOID)MakeProcInstance(ObjDoVerb, hInst);
    pvt->EnumFormats    =(LPVOID)MakeProcInstance(ObjEnumFormats, hInst);
    pvt->GetData        =(LPVOID)MakeProcInstance(ObjGetData, hInst);
    pvt->Release        =(LPVOID)MakeProcInstance(ObjRelease, hInst);
    pvt->SetBounds      =(LPVOID)MakeProcInstance(ObjSetBounds, hInst);
    pvt->SetColorScheme =(LPVOID)MakeProcInstance(ObjSetColorScheme, hInst);
    pvt->SetData        =(LPVOID)MakeProcInstance(ObjSetData, hInst);
    pvt->SetTargetDevice=(LPVOID)MakeProcInstance(ObjSetTargetDevice, hInst);
    pvt->Show           =(LPVOID)MakeProcInstance(ObjShow, hInst);

    fRet =(NULL!=pvt->QueryProtocol);
    fRet&=(NULL!=pvt->DoVerb);
    fRet&=(NULL!=pvt->EnumFormats);
    fRet&=(NULL!=pvt->GetData);
    fRet&=(NULL!=pvt->Release);
    fRet&=(NULL!=pvt->SetBounds);
    fRet =(NULL!=pvt->SetColorScheme);
    fRet&=(NULL!=pvt->SetData);
    fRet&=(NULL!=pvt->SetTargetDevice);
    fRet&=(NULL!=pvt->Show);
   #endif

    return fRet;    //TRUE by default in Win32
    }

/*
 * OLEVtblFreeObject
 *
 * Purpose:
 *  Frees all procedure instances in the object VTBL.
 *
 * Parameters:
 *  pvt             LPOLEOBJECTVTBL to the VTBL to free.
 *
 * Return Value:
 *  none
 *
 * Customization:
 *  None required.
 */

void WINAPI OLEVtblFreeObject(LPOLEOBJECTVTBL pvt)
    {
   #ifndef WIN32
    FreeProcInstance(pvt->DoVerb);
    FreeProcInstance(pvt->EnumFormats);
    FreeProcInstance(pvt->GetData);
    FreeProcInstance((FARPROC)pvt->QueryProtocol);
    FreeProcInstance(pvt->Release);
    FreeProcInstance(pvt->SetBounds);
    FreeProcInstance(pvt->SetColorScheme);
    FreeProcInstance(pvt->SetData);
    FreeProcInstance(pvt->SetTargetDevice);
    FreeProcInstance(pvt->Show);
   #endif

    return;
    }

#endif //MAKEOLESERVER


POLYLINE.C

/*
 * POLYLINE.C
 *
 * Window procedure for the polyline drawing window and support functions.
 * This window is not complicated.  On creation it allocates a block of
 * memory for a POLYLINE structure that contains 20 POINTs.  We do not
 * attempt to reallocate this array at all just to maintain simplicity.
 * This sample is to demonstrate OLE, not LocalReAlloc.
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

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

/*
 * HACK:  To fix some Invalid hDC rips, we need to supress certain
 * operations on metafile DC's: GetMapMode, DPtoLP, LPtoDP.
 * We use this flag to indicate supression.
 */
BOOL fMetaDC=FALSE;

/*
 * HPolylineWindowCreate
 *
 * Purpose:
 *  Creates a Polyline window within the client area of hWndParent.
 *
 * Parameters:
 *  hWndParent      HWND of the parent window.
 *  hInstance       HINSTANCE of the application instance.
 *
 * Return Value:
 *  HWND            Result of the CreateWindowEx call.
 *
 */

HWND WINAPI HPolylineWindowCreate(HWND hWndParent, HINSTANCE hInstance)
    {
    RECT        rc;
    HWND        hWndT;

    /*
     * Create the secondary window for this application in a
     * shrunk client area.
     */
    GetClientRect(hWndParent, &rc);
    InflateRect(&rc, -8, -8);

    //Create the editor window.
    hWndT=CreateWindowEx(WS_EX_NOPARENTNOTIFY, rgpsz[IDS_CLASSPOLYLINE]
        , rgpsz[IDS_CLASSPOLYLINE], WS_CHILD | WS_VISIBLE, rc.left
        , rc.top, rc.right-rc.left, rc.bottom-rc.top
        , hWndParent, (HMENU)ID_POLYLINE, hInstance, NULL);

    return hWndT;
    }

/*
 * PolylineWndProc
 *
 * Purpose:
 *  Window procedure for the polyline drawing window.
 *
 * Parameters:
 *  The standard.
 *
 * Return Value:
 *  Standard.
 */

LRESULT WINAPI PolylineWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PAINTSTRUCT     ps;
    HDC             hDC;
    HWND            hWndParent;
    HLOCAL          hMem;
    LPPOLYLINE      ppl;
    RECT            rc;
    DWORD           dwRet=0L;

   #ifdef WIN32
    ppl=(LPPOLYLINE)(PSTR)GetWindowLong(hWnd, 0);
   #else
    ppl=(LPPOLYLINE)(PSTR)GetWindowWord(hWnd, 0);
   #endif

    if (WM_USER <= iMsg)
        return LPolylineUserMessage(hWnd, iMsg, wParam, lParam, ppl);

    switch (iMsg)
        {

        case WM_NCCREATE:
            hMem=LocalAlloc(LPTR, CBPOLYLINE);

            if (NULL==hMem)
                return 0L;

           #ifdef WIN32
            SetWindowLong(hWnd, 0, (LONG)hMem);
           #else
            SetWindowWord(hWnd, 0, (WORD)hMem);
           #endif
            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        case WM_NCDESTROY:
           #ifdef WIN32
            hMem=(HLOCAL)GetWindowLong(hWnd, 0);
           #else
            hMem=(HLOCAL)GetWindowWord(hWnd, 0);
           #endif
            LocalFree(hMem);
            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        case WM_CREATE:
            //Stash away the current window rectangle.
            GetClientRect(hWnd, &rc);
            RECTTORECTS(rc, ppl->rc);

            ppl->wVerMaj=VERSIONMAJOR;
            ppl->wVerMin=VERSIONMINOR;
            ppl->cPoints=0;
            break;

        case WM_PAINT:
            hDC=BeginPaint(hWnd, &ps);

            if (0!=ppl->cPoints)
                {
                ppl->fDrawEntire=TRUE;
                PolylineDraw(hWnd, hDC, ppl);
                }

            EndPaint(hWnd, &ps);
            break;

        case WM_LBUTTONDOWN:
            //Stop if we are already at the limit.
            if (CPOLYLINEPOINTS==ppl->cPoints)
                {
                MessageBeep(0);
                break;
                }

            //Stuff the new point in the array.
            ppl->rgpt[ppl->cPoints].x=LOWORD(lParam);
            ppl->rgpt[ppl->cPoints].y=HIWORD(lParam);

            ppl->cPoints++;

            //Draw the lines to this new point only.
            hDC=GetDC(hWnd);

            ppl->fDrawEntire=FALSE;
            PolylineDraw(hWnd, hDC, ppl);

            ReleaseDC(hWnd, hDC);

            hWndParent=GetParent(hWnd);

           #ifdef WIN32
            SendMessage(hWndParent, WM_COMMAND
                , MAKELONG(ID_POLYLINE, PLN_POINTCHANGE), (LPARAM)hWnd);
           #else
            SendMessage(hWndParent, WM_COMMAND
                , ID_POLYLINE, MAKELONG(hWnd, PLN_POINTCHANGE));
           #endif
            break;

        default:
            dwRet=DefWindowProc(hWnd, iMsg, wParam, lParam);
            break;
        }

    return dwRet;
    }

/*
 * LPolylineUserMessage
 *
 * Purpose:
 *  Handles all window-specific messages WM_USER and greater,
 *  for the Polyline window:
 *
 *  PLM_RECTSET:        Changes the size of the window and scales the
 *                      data points.
 *
 *  PLM_POLYLINESET:    Sets the current data points and the rectangle
 *                      used to generate the figure.
 *
 *  PLM_POLYLINEGET:    Retrieves the current data points and rectangle
 *                      used to generate the figure.
 *
 *  PLM_POLYLINENEW:    Resets the data points to defaults, meaning
 *                      a blank figure.
 *
 *  PLM_BACKUPUNDO:     Backs the figure up one point.
 *
 *  PLM_BITMAPGET:      Retrieves a bitmap (DDB) of the current image.
 *
 *  PLM_METAFILEGET:    Retrieves a metafile for the current image.
 *
 *  PLM_METAFILEPICTGET:Retrieves a METAFILEPICT structure of the image for
 *                      use in clipboard I/O.
 *
 * Parameters:
 *  hWnd            HWND of the Polyline window.
 *  iMsg            UINT message to process.
 *  wParam          WPARAM parameter of the message.
 *  lParam          LPARAM pameter of the message.
 *  ppl             LPPOLYLINE to the window's extra data structure.
 *
 * Return Value:
 *  DWORD           Value to return from the window procedure
 *                  that recieved the message.
 */

DWORD PASCAL LPolylineUserMessage(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LPPOLYLINE ppl)
    {
    DWORD           dwRet=0L;
    HWND            hWndParent;
    HBITMAP         hBmp, hBmpT;
    HDC             hDC, hMemDC;
    LPPOLYLINE      pplT;
    LPMETAFILEPICT  pMF;
    HGLOBAL         hMem;
    HMETAFILE       hMF;
    RECT            rc;
    LPRECT          pRect;
    UINT            i;
    LONG            l, cx, cy, cxT, cyT;

    hWndParent=GetParent(hWnd);

    switch (iMsg)
        {
        case PLM_RECTSET:
            /*
             * Resize the window to the given size, letting WM_SIZE handlers
             * take care of the rest.
             */
            pRect=(LPRECT)lParam;

            /*
             * Scale all the current points to new dimensions.  ppl->rc
             * has the old dimensions, pRect points to the new.  We
             * force each of cx and cy to 1 if they are zero to prevent
             * exceptions.
             */

            RECTSTORECT(ppl->rc, rc);
            cxT=rc.right  - rc.left;
            cyT=rc.bottom - rc.top;

            RECTTORECTS(ppl->rc, *pRect);
            cx=pRect->right  - pRect->left;
            cy=pRect->bottom - pRect->top;

            //Prevent crashes
            if (0L==cxT)
                cxT=1;

            if (0L==cyT)
                cyT=1;

            //Loop through each point, scaling if necessary.
            for (i=0; i< ppl->cPoints; i++)
                {
                //Must use DWORD to insure proper scaling.
                if (cx!=cxT)
                    {
                    l=((LONG)ppl->rgpt[i].x*cx);
                    ppl->rgpt[i].x=(short)(l/cxT);
                    }

                if (cy!=cyT)
                    {
                    l=((LONG)ppl->rgpt[i].y*cy);
                    ppl->rgpt[i].y=(short)(l/cyT);
                    }
                }

            SetWindowPos(hWnd, NULL, pRect->left, pRect->top, (int)cx
                , (int)cy, SWP_NOMOVE | SWP_NOZORDER);

            //Check if we need to notify the parent.
            if (0!=wParam)
                {
               #ifdef WIN32
                SendMessage(hWndParent, WM_COMMAND
                    , MAKELONG(ID_POLYLINE, PLN_SIZECHANGE), (LPARAM)hWnd);
               #else
                SendMessage(hWndParent, WM_COMMAND
                    , ID_POLYLINE, MAKELONG(hWnd, PLN_SIZECHANGE));
               #endif
                }

            //Insure repaint
            InvalidateRect(hWnd, NULL, TRUE);
            UpdateWindow(hWnd);
            break;

        case PLM_POLYLINESET:
            /*
             * Copy the structure in lParam to ppl and repaint to
             * reflect the new point set.  Note that unlike the
             * PLM_RECTSET message, we do no scaling, assuming that
             * the rectangle in the POLYLINE structure is appropriate
             * for the data.
             */
            pplT=(LPPOLYLINE)lParam;
            *ppl=*pplT;

            //Resize this window to fit the data and notify the parent.
            SetWindowPos(hWnd, NULL, ppl->rc.left, ppl->rc.top
                , ppl->rc.right-ppl->rc.left, ppl->rc.bottom-ppl->rc.top
                , SWP_NOMOVE | SWP_NOZORDER);

            if (0!=wParam)
                {
               #ifdef WIN32
                SendMessage(hWndParent, WM_COMMAND
                    , MAKELONG(ID_POLYLINE, PLN_SIZECHANGE), (LPARAM)hWnd);
               #else
                SendMessage(hWndParent, WM_COMMAND
                    , ID_POLYLINE, MAKELONG(hWnd, PLN_SIZECHANGE));
               #endif
                }

            //Insure a repaint.
            InvalidateRect(hWnd, NULL, TRUE);
            UpdateWindow(hWnd);
            break;

        case PLM_POLYLINEGET:
            //Copy the structure in ppl to lParam.  No repaint needed.
            pplT=(LPPOLYLINE)lParam;
            *pplT=*ppl;
            break;

        case PLM_POLYLINENEW:
            //Clean out the POLYLINE structure and repaint the window.
            for (i=0; i< CPOLYLINEPOINTS; i++)
                {
                ppl->rgpt[i].x=0;
                ppl->rgpt[i].y=0;
                }

            ppl->cPoints=0;

            InvalidateRect(hWnd, NULL, TRUE);
            UpdateWindow(hWnd);
            break;

        case PLM_BACKUPUNDO:
            //Decrement the number of active points and repaint.
            if (ppl->cPoints > 0)
                {
                ppl->cPoints--;
                InvalidateRect(hWnd, NULL, TRUE);
                UpdateWindow(hWnd);
                }

            //Notify parent window of the change.
           #ifdef WIN32
            SendMessage(hWndParent, WM_COMMAND
                , MAKELONG(ID_POLYLINE, PLN_POINTCHANGE), (LPARAM)hWnd);
           #else
            SendMessage(hWndParent, WM_COMMAND
                , ID_POLYLINE, MAKELONG(hWnd, PLN_POINTCHANGE));
           #endif
            break;

        case PLM_BITMAPGET:
            /*
             * Create and return a bitmap of the window contents.
             * The bitmap is the size of the POLYLINE edit window.
             */

            hDC=GetDC(hWnd);
            hMemDC=CreateCompatibleDC(hDC);

            GetClientRect(hWnd, &rc);
            hBmp=CreateCompatibleBitmap(hDC, rc.right, rc.bottom);

            if (NULL!=hBmp)
                {
                //Draw the POLYLINE into the bitmap.
                hBmpT=SelectObject(hMemDC, hBmp);

                ppl->fDrawEntire=TRUE;
                PolylineDraw(hWnd, hMemDC, ppl);
                ppl->fDrawEntire=FALSE;

                SelectObject(hMemDC, hBmpT);
                }

            DeleteDC(hMemDC);
            ReleaseDC(hWnd, hDC);

            //Return the created bitmap handle.
            dwRet=(DWORD)(UINT)hBmp;
            break;

        case PLM_METAFILEGET:
            //Create a memory metafile and return its handle.
            hDC=(HDC)CreateMetaFile(NULL);
            hMF=NULL;

            if (NULL!=hDC)
                {
                /*
                 * This is absolutely essential to the metafile so it
                 * can be scaled in the clipboard and any destination
                 * application.
                 */
                fMetaDC=TRUE;
                SetMapMode(hDC, MM_ANISOTROPIC);
                GetClientRect(hWnd, &rc);
                SetWindowOrgEx(hDC, 0, 0, NULL);
                SetWindowExtEx(hDC, rc.right, rc.bottom, NULL);

                ppl->fDrawEntire=TRUE;
                PolylineDraw(hWnd, hDC, ppl);
                ppl->fDrawEntire=FALSE;

                hMF=CloseMetaFile(hDC);
                fMetaDC=FALSE;
                }

            dwRet=(DWORD)(UINT)hMF;
            break;

        case PLM_METAFILEPICTGET:
            /*
             * Create a METAFILEPICT structure for the clipboard.
             * First attempt to get a metafile.
             */
            lParam=SendMessage(hWnd, PLM_METAFILEGET, 0, 0L);
            hMF=(HMETAFILE)(UINT)lParam;

            if (NULL==hMF)
                break;

            //Allocate the METAFILEPICT structure.
            hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
                , sizeof(METAFILEPICT));

            if (NULL==hMem)
                {
                DeleteMetaFile(hMF);
                break;
                }

            /*
             * Global lock only fails in PMODE if the selector is invalid
             * (like it was discarded) or references a 0 length segment,
             * neither of which can happen here.
             */
            pMF=(LPMETAFILEPICT)GlobalLock(hMem);

            pMF->hMF=hMF;
            pMF->mm=MM_ANISOTROPIC;             //Required by OLE libraries.

            //Insert the extents in MM_HIMETRIC units.
            GetClientRect(hWnd, &rc);

            RectConvertToHiMetric(hWnd, &rc);     //Found in CLIP.C
            pMF->xExt=rc.right;
            pMF->yExt=rc.bottom;

            GlobalUnlock(hMem);

            dwRet=(DWORD)(UINT)hMem;
            break;

        default:
            break;
        }

    return dwRet;
    }

/*
 * PolylineDraw
 *
 * Purpose:
 *  Paints the current line in the polyline window.
 *
 * Parameters:
 *  hWnd            HWND of the polyline window.
 *  hDC             HDC to draw on, could be a metafile or printer DC.
 *  ppl            LPPOLYLINE to the polyline structure.
 *
 * Return Value:
 *  none
 */

void PASCAL PolylineDraw(HWND hWnd, HDC hDC, LPPOLYLINE ppl)
    {

    HBRUSH          hBrush, hBrushT;
    HPEN            hPen, hPenT;
    UINT            i, j;
    UINT            nMM;
    POINTS          pt;
    POINT           rgpt[CPOLYLINEPOINTS];
    RECT            rc;
    COLORREF        cr;

    GetClientRect(hWnd, &rc);

    //Get the line color.
    cr=GetSysColor(COLOR_WINDOWTEXT);

    //Make a 32-bit working copy of the point array for DPtoLP.
    for (i=0; i < ppl->cPoints; i++)
        {
        rgpt[i].x=ppl->rgpt[i].x;
        rgpt[i].y=ppl->rgpt[i].y;
        }

    /*
     * If the mapping mode is not MM_TEXT, convert the points to
     * whatever mapping mode in in effect before drawing.
     * This specifically supports metafiles in MM_ANISOTROPIC.
     */
    nMM=fMetaDC ? MM_TEXT : GetMapMode(hDC);

    if (MM_TEXT!=nMM)
        DPtoLP(hDC, rgpt, ppl->cPoints);

    hPen=CreatePen(PS_SOLID, 1, cr);
    hPenT=SelectObject(hDC, hPen);

    hBrush=CreateSolidBrush(GetSysColor(COLOR_WINDOW));
    hBrushT=SelectObject(hDC, hBrush);

    /*
     * Either draw the entire figure or just a single point.  The
     * entire figure also includes erasing the background completely,
     * since hDC may be a metafile DC.  Drawing a single point just
     * updates the figure for that new point.
     */
    if (ppl->fDrawEntire)
        {
        //Erase the background for bitmaps and metafiles.
        SelectObject(hDC, GetStockObject(NULL_PEN));
        Rectangle(hDC, rc.left, rc.top, rc.right+1, rc.bottom+1);
        SelectObject(hDC, hPen);

        /*
         * If we are drawing the entire figure, then loop through each
         * point drawing a line to each successive point.
         */

        for (i=0; i < ppl->cPoints; i++)
            {
            for (j=i; j < ppl->cPoints; j++)
                {
                MoveToEx(hDC, rgpt[i].x, rgpt[i].y, NULL);
                LineTo(hDC, rgpt[j].x, rgpt[j].y);
                }
            }
        }
    else
        {
        /*
         * If we are only drawing the last point, just cycle once
         * through previous points.
         */

        //Get the last point entered in the array.
        j=ppl->cPoints-1;
        pt.x=(short)rgpt[j].x;
        pt.y=(short)rgpt[j].y;

        for (i=0; i < j; i++)
            {
            MoveToEx(hDC, pt.x, pt.y, NULL);
            LineTo(hDC, rgpt[i].x, rgpt[i].y);
            }
        }

    //If we only had one point, draw a dot to indicate it's position.
    if (1==ppl->cPoints)
        SetPixel(hDC, rgpt[0].x, rgpt[0].y, cr);

    SelectObject(hDC, hPenT);
    SelectObject(hDC, hBrushT);
    DeleteObject(hBrush);
    DeleteObject(hPen);
    return;
    }


POLYLINE.H

/*
 * POLYLINE.H
 *
 * Definitions and function prototypes for the PolyLine window class.
 *
 * Copyright(c) Microsoft Corp. 1992-1994 All Rights Reserved
 * Win32 version, January 1994
 */

//Need some extra types for Windows 3.1
#ifndef WIN32
#ifndef POINTS
typedef POINT POINTS;
#endif  //POINTS

#ifndef LPPOINTS
typedef POINTS FAR *LPPOINTS;
#endif  //LPPOINTS
#endif

typedef struct
    {
    short   left;
    short   top;
    short   right;
    short   bottom;
    } RECTS, FAR *LPRECTS;

#define RECTSTORECT(rs, r) {(r).left=(rs).left;(r).top=(rs).top; \
    (r).right=(rs).right;(r).bottom=(rs).bottom;};

#define RECTTORECTS(r, rs) {(rs).left=(short)(r).left;(rs).top=(short)(r).top; \
    (rs).right=(short)(r).right;(rs).bottom=(short)(r).bottom;};

//Messages
#define PLM_RECTSET         WM_USER
#define PLM_POLYLINESET     WM_USER+1
#define PLM_POLYLINEGET     WM_USER+2
#define PLM_POLYLINENEW     WM_USER+3
#define PLM_BITMAPGET       WM_USER+4
#define PLM_METAFILEGET     WM_USER+5
#define PLM_METAFILEPICTGET WM_USER+6
#define PLM_BACKUPUNDO      WM_USER+7

//Notifications
#define PLN_POINTCHANGE     1
#define PLN_SIZECHANGE      2

//Definitions and structures.
#define CPOLYLINEPOINTS     20
#define CBPOLYLINEWNDEXTRA  sizeof(HLOCAL)

typedef struct
    {
    WORD        wVerMaj;                //Major version number.
    WORD        wVerMin;                //Minor version number.
    WORD        cPoints;                //Number of points.
    WORD        fDrawEntire;            //Flag to draw entire figure.
    RECTS       rc;
    POINTS      rgpt[CPOLYLINEPOINTS];  //Array of points.
    } POLYLINE;

typedef POLYLINE FAR *LPPOLYLINE;
#define CBPOLYLINE sizeof(POLYLINE)

//Function prototypes.
HWND     WINAPI HPolylineWindowCreate(HWND, HINSTANCE);
LONG     WINAPI PolylineWndProc(HWND, UINT, WPARAM, LPARAM);
DWORD    PASCAL LPolylineUserMessage(HWND, UINT, WPARAM, LPARAM, LPPOLYLINE);
void     PASCAL PolylineDraw(HWND, HDC, LPPOLYLINE);


README.TXT

To build a version of Component Cosmo that uses POLY19.DLL, copy
the sources from CHAP13\COCOSMO and modify the function
CCosmoDoc::FInit in DOCUMENT.CPP to use CLSID_Polyline19 instead
of CLSID_Polyline10 in the CoCreateInstance call:

Nothing else in CoCosmo needs to change to use the revised Polyline.
Note that CoCosmo still uses the IPolyline10 interface on the Polyline
object.  For all intents and purposes, nothing should change at all
in the execution of CoCosmo, demonstrating how interfaces do not
interfere with each other:  CoCosmo ignores the compound document
interfaces.


FIGURE.CPP

/*
 * FIGURE.CPP
 * Cosmo Handler Chapter 19
 *
 * Implementation of the CFigure class that we expose as a
 * CosmoFigure Object in this handler.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "hcosmo.h"

/*
 * CFigure:CFigure
 * CFigure::~CFigure
 *
 * Constructor Parameters:
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 *  pfnDestroy      PFNDESTROYED to call when object is destroyed.
 *  hInst           HINSTANCE of the application we're in.
 */

CFigure::CFigure(LPUNKNOWN pUnkOuter, PFNDESTROYED pfnDestroy
    , HINSTANCE hInst)
    {
    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pfnDestroy=pfnDestroy;
    m_clsID=CLSID_CosmoFigure;

    m_cf=RegisterClipboardFormat(SZPOLYLINECLIPFORMAT);

    //NULL any contained interfaces initially.
    m_pImpIOleObject     =NULL;
    m_pImpIViewObject2   =NULL;
    m_pImpIPersistStorage=NULL;
    m_pImpIAdviseSink    =NULL;

    m_pDefIUnknown       =NULL;
    m_pDefIOleObject     =NULL;
    m_pDefIViewObject2   =NULL;
    m_pDefIPersistStorage=NULL;
    m_pDefIDataObject    =NULL;

    m_pIAdvSinkView      =NULL;
    m_dwAdviseFlags      =0;
    m_dwAdviseAspects    =0;
    m_dwFrozenAspects    =0;

    return;
    }

CFigure::~CFigure(void)
    {
    LPUNKNOWN       pIUnknown=this;

    if (NULL!=m_pUnkOuter)
        pIUnknown=m_pUnkOuter;

    /*
     * In aggregation, release cached pointers but
     * AddRef the controlling unknown first.
     */
    if (NULL!=m_pDefIOleObject)
        {
        pIUnknown->AddRef();
        m_pDefIOleObject->Release();
        }

    if (NULL!=m_pDefIViewObject2)
        {
        pIUnknown->AddRef();
        m_pDefIViewObject2->Release();
        }

    if (NULL!=m_pDefIDataObject)
        {
        pIUnknown->AddRef();
        m_pDefIDataObject->Release();
        }

    if (NULL!=m_pDefIPersistStorage)
        {
        pIUnknown->AddRef();
        m_pDefIPersistStorage->Release();
        }

    ReleaseInterface(m_pIAdvSinkView);
    ReleaseInterface(m_pDefIUnknown)

    DeleteInterfaceImp(m_pImpIAdviseSink);
    DeleteInterfaceImp(m_pImpIPersistStorage);
    DeleteInterfaceImp(m_pImpIViewObject2);
    DeleteInterfaceImp(m_pImpIOleObject);

    return;
    }

/*
 * CFigure::Init
 *
 * Purpose:
 *  Performs any intiailization of a CFigure that's prone to failure
 *  that we also use internally before exposing the object outside
 *  this DLL.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CFigure::Init(void)
    {
    LPUNKNOWN       pIUnknown=this;
    HRESULT         hr;
    DWORD           dwConn;
    FORMATETC       fe;

    if (NULL!=m_pUnkOuter)
        pIUnknown=m_pUnkOuter;

    //First create our interfaces.
    m_pImpIOleObject=new CImpIOleObject(this, pIUnknown);

    if (NULL==m_pImpIOleObject)
        return FALSE;

    m_pImpIViewObject2=new CImpIViewObject2(this, pIUnknown);

    if (NULL==m_pImpIViewObject2)
        return FALSE;

    m_pImpIPersistStorage=new CImpIPersistStorage(this, pIUnknown);

    if (NULL==m_pImpIPersistStorage)
        return FALSE;

    m_pImpIAdviseSink=new CImpIAdviseSink(this, pIUnknown);

    if (NULL==m_pImpIAdviseSink)
        return FALSE;

    /*
     * Get an IUnknown on the default handler, passing pIUnknown
     * as the controlling unknown.  The extra reference count is to
     * prevent us from going away accidentally.
     */
    m_cRef++;

    hr=OleCreateDefaultHandler(CLSID_CosmoFigure, pIUnknown
        , IID_IUnknown, (PPVOID)&m_pDefIUnknown);

    if (FAILED(hr))
        return FALSE;

    /*
     * NOTE:  The spec specifically states that any interfaces
     * besides IUnknown that we obtain on an aggregated object
     * should be Released immediately after we QueryInterface for
     * them because the QueryInterface will AddRef us, and since
     * we would not release these interfaces until we were
     * destroyed, we'd never go away because we'd never get a zero
     * ref count.
     */

    //Now try to get other interfaces to which we delegate
    hr=m_pDefIUnknown->QueryInterface(IID_IOleObject
        , (PPVOID)&m_pDefIOleObject);

    if (FAILED(hr))
        return FALSE;

    pIUnknown->Release();

    hr=m_pDefIUnknown->QueryInterface(IID_IViewObject2
        , (PPVOID)&m_pDefIViewObject2);

    if (FAILED(hr))
        return FALSE;

    pIUnknown->Release();

    hr=m_pDefIUnknown->QueryInterface(IID_IDataObject
        , (PPVOID)&m_pDefIDataObject);

    if (FAILED(hr))
        return FALSE;

    pIUnknown->Release();

    hr=m_pDefIUnknown->QueryInterface(IID_IPersistStorage
        , (PPVOID)&m_pDefIPersistStorage);

    if (FAILED(hr))
        return FALSE;

    pIUnknown->Release();

    m_cRef--;

    //Set up an advise on native data so we can keep in sync
    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    m_pDefIDataObject->DAdvise(&fe, 0, m_pImpIAdviseSink, &dwConn);

    return TRUE;
    }

/*
 * CFigure::QueryInterface
 * CFigure::AddRef
 * CFigure::Release
 *
 * Purpose:
 *  IUnknown members for CFigure object.
 */

STDMETHODIMP CFigure::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    /*
     * The only calls we get here for IUnknown are either in a non-
     * aggregated case or when we're created in an aggregation, so
     * in either we always return our IUnknown for IID_IUnknown.
     */
    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IPersist==riid || IID_IPersistStorage==riid)
        *ppv=m_pImpIPersistStorage;

    if (IID_IOleObject==riid)
        *ppv=m_pImpIOleObject;

    if (IID_IViewObject==riid || IID_IViewObject2==riid)
        *ppv=m_pImpIViewObject2;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    /*
     * Only expose default handler interfaces that you explicitly
     * know you are aggregating.  You cannot just blindly
     * delegate any other QueryInterface to the handler because
     * it may return interfaces you do not expect...such as a
     * newer version of an interface you implement in the handler.
     * We know as a handler that we must provide IDataObject,
     * IOleCache[2], IOleCacheControl, and IRunnableObject, and
     * these we get from the default handler.
     */

    if (IID_IDataObject==riid || IID_IOleCache==riid
        || IID_IOleCache2==riid || IID_IOleCacheControl==riid
        || IID_IRunnableObject==riid)
        return m_pDefIUnknown->QueryInterface(riid, ppv);

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CFigure::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CFigure::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    m_cRef++;
    delete this;
    return 0;
    }

/*
 * CFigure::Draw
 *
 * Purpose:
 *  Paints the current window to an hDC which might be a printer.
 *
 * Parameters:
 *  hDC             HDC to draw on, could be a metafile or
 *                  printer DC.
 *  pRect           LPRECT defining hDC bounds in which to draw.
 *  dwAspect        DWORD aspect to draw.
 *  ptd             DVTARGETDEVICE * containing device info.
 *  hICDev          HDC containing the IC for the device.
 *  ppl             PPOLYLINEDATA from which to draw.
 *
 * Return Value:
 *  None
 */

void CFigure::Draw(HDC hDC, LPRECT pRect, DWORD dwAspect
    , DVTARGETDEVICE *ptd, HDC hICDev, PPOLYLINEDATA ppl)
    {
    HBRUSH          hBrush;
    HPEN            hPen;
    HGDIOBJ         hObj1, hObj2;
    UINT            i, j;
    int             nDC;
    POINTS          pt1,pt2;
    POINT           rgpt[CPOLYLINEPOINTS];

    nDC=SaveDC(hDC);

    for (i=0; i < ppl->cPoints; i++)
        {
        rgpt[i].x=ppl->rgpt[i].x;
        rgpt[i].y=ppl->rgpt[i].y;
        }

    hPen=CreatePen(ppl->iLineStyle, 1, ppl->rgbLine);
    hObj1=SelectObject(hDC, hPen);

    hBrush=CreateSolidBrush(ppl->rgbBackground);
    hObj2=SelectObject(hDC, hBrush);
    SetBkColor(hDC, ppl->rgbBackground);

    //If we have one point, draw a dot to indicate it's position.
    if (1==m_pl.cPoints)
        {
        pt1.x=(short)rgpt[0].x;
        pt1.y=(short)rgpt[0].y;
        PointScale(pRect, &pt1, TRUE);
        SetPixel(hDC, pt1.x, pt1.y, m_pl.rgbLine);
        }
    else
        {
        //Erase the background for bitmaps and metafiles.
        SelectObject(hDC, GetStockObject(NULL_PEN));
        Rectangle(hDC, pRect->left, pRect->top, pRect->right+1
            , pRect->bottom+1);
        SelectObject(hDC, hPen);

        for (i=0; i < ppl->cPoints; i++)
            {
            for (j=i; j < ppl->cPoints; j++)
                {
                pt1.x=(short)rgpt[i].x;
                pt1.y=(short)rgpt[i].y;
                pt2.x=(short)rgpt[j].x;
                pt2.y=(short)rgpt[j].y;
                PointScale(pRect, &pt1, TRUE);
                PointScale(pRect, &pt2, TRUE);
                MoveToEx(hDC, pt1.x, pt1.y, NULL);
                LineTo(hDC, pt2.x, pt2.y);
                }
            }
        }

    SelectObject(hDC, hObj1);
    SelectObject(hDC, hObj2);
    DeleteObject(hBrush);
    DeleteObject(hPen);

    RestoreDC(hDC, nDC);
    return;
    }

/*
 * CFIgure::PointScale
 *
 * Purpose:
 *  Scales a point from a 0-32767 coordinate to a rectangle relative
 *  coordinate.
 *
 * Parameters:
 *  pRect           LPRECT in which to scale.
 *  ppt             LPPOINTS to convert
 *  fUnused         BOOL of no use.
 *
 * Return Value:
 *  None
 */

void CFigure::PointScale(LPRECT pRect, LPPOINTS ppt, BOOL fUnused)
    {
    DWORD   cx, cy;

    cx=(DWORD)(pRect->right-pRect->left);
    cy=(DWORD)(pRect->bottom-pRect->top);

    //Must use DWORD to insure proper scaling.
    ppt->x=pRect->left+(UINT)(((DWORD)ppt->x*cx) >> 15);
    ppt->y=pRect->top+(UINT)(((DWORD)ppt->y*cy) >> 15);

    return;
    }


HCOSMO.CPP

/*
 * HCOSMO.CPP
 * Cosmo Handler Chapter 19
 *
 * DLL exports for an object handler as well as class factory.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "hcosmo.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

//DLL Instance handle
HINSTANCE   g_hInst=0;

/*
 * LibMain(32)
 *
 * Purpose:
 *  Entry point for Win32 or Win16 DLLs.
 */

#ifdef WIN32
BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    if (DLL_PROCESS_DETACH==ulReason)
        {
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    g_hInst=hInstance;
    return TRUE;
    }

#else

/*
 * LibMain (also called from Win32 LibMain32)
 *
 * Purpose:
 *  DLL-specific entry point called from LibEntry.
 */

int PASCAL LibMain(HINSTANCE hInst, WORD wDataSeg
    , WORD cbHeapSize, LPSTR lpCmdLine)
    {
    if (0!=cbHeapSize)
        UnlockData(0);

    g_hInst=hInst;
    return (int)hInst;
    }

#endif

/*
 * DllGetClassObject
 *
 * Purpose:
 *  Standard export for DLL component objects.
 */

HRESULT APIENTRY DllGetClassObject(REFCLSID rclsid
    , REFIID riid, PPVOID ppv)
    {
    if (CLSID_CosmoFigure!=rclsid)
        return ResultFromScode(E_FAIL);

    //Check that we can provide the interface
    if (IID_IUnknown!=riid && IID_IClassFactory!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Return our IClassFactory for Figure objects
    *ppv=new CFigureClassFactory;

    if (NULL==*ppv)
        return ResultFromScode(E_OUTOFMEMORY);

    ((LPUNKNOWN)*ppv)->AddRef();
    g_cObj++;
    return NOERROR;
    }

/*
 * DllCanUnloadNow
 *
 * Purpose:
 *  Answers if the DLL can be freed, that is, if there are no
 *  references to anything this DLL provides.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if nothing is using us, FALSE otherwise.
 */

STDAPI DllCanUnloadNow(void)
    {
    SCODE   sc;

    //Our answer is whether there are any object or locks
    sc=(0L==g_cObj && 0==g_cLock) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Figure object to call when it gets destroyed.
 *  Since we're in a DLL we only track the number of objects here
 *  letting DllCanUnloadNow take care of the rest.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;
    return;
    }

/*
 * CFigureClassFactory::CFigureClassFactory
 * CFigureClassFactory::~CFigureClassFactory
 */

CFigureClassFactory::CFigureClassFactory(void)
    {
    m_cRef =0L;
    return;
    }

CFigureClassFactory::~CFigureClassFactory(void)
    {
    return;
    }

/*
 * CFigureClassFactory::QueryInterface
 * CFigureClassFactory::AddRef
 * CFigureClassFactory::Release
 */

STDMETHODIMP CFigureClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    //Any interface on this object is the object pointer.
    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CFigureClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CFigureClassFactory::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    ObjectDestroyed();
    return 0;
    }

/*
 * CFigureClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a Figure object that supports the IFigure
 *  and IUnknown interfaces.  If the caller asks for a different
 *  interface than these two then we fail.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we are
 *                  being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired interface
 *                  pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise contains
 *                  E_NOINTERFACE if we cannot support the requested
 *                  interface.
 */

STDMETHODIMP CFigureClassFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCFigure            pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //Verify that a controlling unknown is asking for IUnknown
    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Create the object.  This also creates a window.
    pObj=new CFigure(pUnkOuter, ObjectDestroyed, g_hInst);

    if (NULL==pObj)
        return hr;

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    //Kill the object if initial creation or Init failed.
    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

/*
 * CFigureClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the DLL.  If the lock
 *  count goes to zero and there are no objects, the DLL is allowed
 *  to unload.  See DllCanUnloadNow.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CFigureClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        g_cLock--;

    return NOERROR;
    }


HCOSMO.H

/*
 * HCOSMO.H
 * Cosmo Handler Chapter 19
 *
 * Definitions and function prototypes for the Cosmo Handler.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _HCOSMO_H_
#define _HCOSMO_H_

#define CHAPTER19
#include <inole.h>
#include <ipoly10.h>      //For POLYLINEDATA definitions

#define HIMETRIC_PER_INCH           2540

//Stream name in the object storage
#define SZSTREAM                    OLETEXT("CONTENTS")

//HCOSMO.CPP

class CFigureClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;         //Reference count on object

    public:
        CFigureClassFactory(void);
        ~CFigureClassFactory(void);

        //IUnknown members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP    CreateInstance(LPUNKNOWN, REFIID, PPVOID);
        STDMETHODIMP    LockServer(BOOL);
    };

typedef CFigureClassFactory *PCFigureClassFactory;

//Magic number to add to aspects returned from IViewObject::Freeze
#define FREEZE_KEY_OFFSET       0x0723

//Forward class references
class CImpIOleObject;
typedef class CImpIOleObject *PCImpIOleObject;
class CImpIViewObject2;
typedef class CImpIViewObject2 *PCImpIViewObject2;
class CImpIPersistStorage;
typedef class CImpIPersistStorage *PCImpIPersistStorage;
class CImpIAdviseSink;
typedef class CImpIAdviseSink *PCImpIAdviseSink;

//HCOSMO.CPP

class CFigure : public IUnknown
    {
    friend class CImpIOleObject;
    friend class CImpIViewObject2;
    friend class CImpIPersistStorage;
    friend class CImpIAdviseSink;

    protected:
        ULONG                m_cRef;
        LPUNKNOWN            m_pUnkOuter;
        PFNDESTROYED         m_pfnDestroy;

        POLYLINEDATA         m_pl;          //Our actual data.
        UINT                 m_cf;          //Obj clipboard format.
        CLSID                m_clsID;       //Current CLSID

        //These are default handler interfaces we use
        LPUNKNOWN            m_pDefIUnknown;
        LPOLEOBJECT          m_pDefIOleObject;
        LPVIEWOBJECT2        m_pDefIViewObject2;
        LPPERSISTSTORAGE     m_pDefIPersistStorage;
        LPDATAOBJECT         m_pDefIDataObject;

        //Implemented interfaces
        PCImpIOleObject      m_pImpIOleObject;
        PCImpIViewObject2    m_pImpIViewObject2;
        PCImpIPersistStorage m_pImpIPersistStorage;
        PCImpIAdviseSink     m_pImpIAdviseSink;

        //Advise sink we get in IViewObject
        LPADVISESINK         m_pIAdvSinkView;
        DWORD                m_dwAdviseFlags;
        DWORD                m_dwAdviseAspects;
        DWORD                m_dwFrozenAspects;

        //Copies of frozen aspects
        POLYLINEDATA         m_plContent;
        POLYLINEDATA         m_plThumbnail;

    protected:
        void      Draw(HDC, LPRECT, DWORD, DVTARGETDEVICE *
                      , HDC, PPOLYLINEDATA);
        void      PointScale(LPRECT, LPPOINTS, BOOL);

    public:
        CFigure(LPUNKNOWN, PFNDESTROYED, HINSTANCE);
        ~CFigure(void);

        BOOL      Init(void);

        //Non-delegating object IUnknown
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
    };

typedef CFigure *PCFigure;

class CImpIPersistStorage : public IPersistStorage
    {
    protected:
        ULONG               m_cRef;
        PCFigure            m_pObj;
        LPUNKNOWN           m_pUnkOuter;
        PSSTATE             m_psState;   //Storage state
        BOOL                m_fConvert;  //Are we Converting?

    public:
        CImpIPersistStorage(PCFigure, LPUNKNOWN);
        ~CImpIPersistStorage(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP InitNew(LPSTORAGE);
        STDMETHODIMP Load(LPSTORAGE);
        STDMETHODIMP Save(LPSTORAGE, BOOL);
        STDMETHODIMP SaveCompleted(LPSTORAGE);
        STDMETHODIMP HandsOffStorage(void);
    };

class CImpIOleObject : public IOleObject
    {
    private:
        ULONG           m_cRef;
        PCFigure        m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIOleObject(PCFigure, LPUNKNOWN);
        ~CImpIOleObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IOleObject members
        STDMETHODIMP SetClientSite(LPOLECLIENTSITE);
        STDMETHODIMP GetClientSite(LPOLECLIENTSITE *);
        STDMETHODIMP SetHostNames(LPCOLESTR, LPCOLESTR);
        STDMETHODIMP Close(DWORD);
        STDMETHODIMP SetMoniker(DWORD, LPMONIKER);
        STDMETHODIMP GetMoniker(DWORD, DWORD, LPMONIKER *);
        STDMETHODIMP InitFromData(LPDATAOBJECT, BOOL, DWORD);
        STDMETHODIMP GetClipboardData(DWORD, LPDATAOBJECT *);
        STDMETHODIMP DoVerb(LONG, LPMSG, LPOLECLIENTSITE, LONG
                         , HWND, LPCRECT);
        STDMETHODIMP EnumVerbs(LPENUMOLEVERB *);
        STDMETHODIMP Update(void);
        STDMETHODIMP IsUpToDate(void);
        STDMETHODIMP GetUserClassID(CLSID *);
        STDMETHODIMP GetUserType(DWORD, LPOLESTR *);
        STDMETHODIMP SetExtent(DWORD, LPSIZEL);
        STDMETHODIMP GetExtent(DWORD, LPSIZEL);
        STDMETHODIMP Advise(LPADVISESINK, DWORD *);
        STDMETHODIMP Unadvise(DWORD);
        STDMETHODIMP EnumAdvise(LPENUMSTATDATA *);
        STDMETHODIMP GetMiscStatus(DWORD, DWORD *);
        STDMETHODIMP SetColorScheme(LPLOGPALETTE);
    };

class CImpIViewObject2 : public IViewObject2
    {
    private:
        ULONG           m_cRef;
        PCFigure        m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIViewObject2(PCFigure, LPUNKNOWN);
        ~CImpIViewObject2(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IViewObject2 members
        STDMETHODIMP Draw(DWORD, LONG, LPVOID, DVTARGETDEVICE *
            , HDC, HDC, LPCRECTL, LPCRECTL
            , BOOL (CALLBACK *)(DWORD), DWORD);
        STDMETHODIMP GetColorSet(DWORD, LONG, LPVOID
            , DVTARGETDEVICE *, HDC, LPLOGPALETTE *);
        STDMETHODIMP Freeze(DWORD, LONG, LPVOID, LPDWORD);
        STDMETHODIMP Unfreeze(DWORD);
        STDMETHODIMP SetAdvise(DWORD, DWORD, LPADVISESINK);
        STDMETHODIMP GetAdvise(LPDWORD, LPDWORD, LPADVISESINK *);
        STDMETHODIMP GetExtent(DWORD, LONG, DVTARGETDEVICE *
            , LPSIZEL);
    };

class CImpIAdviseSink : public IAdviseSink
    {
    protected:
        ULONG           m_cRef;
        PCFigure        m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIAdviseSink(PCFigure, LPUNKNOWN);
        ~CImpIAdviseSink(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(void)  OnDataChange(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP_(void)  OnViewChange(DWORD, LONG);
        STDMETHODIMP_(void)  OnRename(LPMONIKER);
        STDMETHODIMP_(void)  OnSave(void);
        STDMETHODIMP_(void)  OnClose(void);
    };

#endif  //_HCOSMO_H_


IADVSINK.CPP

/*
 * IADVSINK.CPP
 * Cosmo Handler Chapter 19
 *
 * Implementation of the IAdviseSink interface for the Cosmo Handler
 * such that it is notified when data is modified in the server.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "hcosmo.h"

/*
 * CImpIAdviseSink::CImpIAdviseSink
 * CImpIAdviseSink::~CImpIAdviseSink
 *
 * Parameters (Constructor):
 *  pObj            PCFigure of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIAdviseSink::CImpIAdviseSink(PCFigure pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIAdviseSink::~CImpIAdviseSink(void)
    {
    return;
    }

/*
 * CImpIAdviseSink::QueryInterface
 * CImpIAdviseSink::AddRef
 * CImpIAdviseSink::Release
 */

STDMETHODIMP CImpIAdviseSink::QueryInterface(REFIID riid, PPVOID ppv)
    {
    /*
     * Conceptually, and from the view of the container and the
     * local server, the IAdviseSink interface is implemented
     * on a different object than the rest of the handler's
     * interfaces.  For that reason, this QueryInterface only
     * exposes IUnknown and IAdviseSink.  That makes it appear
     * as a separate object although it doesn't have to be an
     * explicitly separate object inside this handler (no one
     * outside the handler cares about our implementation).  Note
     * that reference counting still affects the entire object.
     */
    if (IID_IUnknown==riid || IID_IAdviseSink==riid)
        {
        *ppv=this;
        AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink::Release(void)
    {
    return --m_cRef;
    }

/*
 * CImpIAdviseSink::OnViewChange
 *
 * Purpose:
 *  We don't do anything here because we generate OnViewChange for
 *  the container inside OnDataChange.  The problem is that
 *  OnViewChange will come before OnDataChange, so if we called the
 *  container's OnViewChange here it would turn around and call our
 *  IViewObject::Draw which would draw with outdated data. Therefore
 *  we ignore this notification and wait for OnDataChange, since
 *  that implies a view change as well.  Then we can retrieve the
 *  new data first, then send OnViewChange to the container such
 *  that we'll repaint with the new data.
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnViewChange(DWORD dwAspect
    , LONG lindex)
    {
    return;
    }

/*
 * CImpIAdviseSink::OnDataChange
 *
 * Purpose:
 *  Tells us that things changed in the server.  We asked for data
 *  on the advise so we can copy it from here into our own structure
 *  such that on the next OnViewChange we can repaint with it.
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnDataChange(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM)
    {
    //Get the new data first, then notify the container to repaint.
    if ((pFE->cfFormat==m_pObj->m_cf)
        && (TYMED_HGLOBAL & pSTM->tymed))
        {
        PPOLYLINEDATA      ppl;

        ppl=(PPOLYLINEDATA)GlobalLock(pSTM->hGlobal);
        memcpy(&m_pObj->m_pl, ppl, CBPOLYLINEDATA);
        GlobalUnlock(pSTM->hGlobal);

        /*
         * Now tell the container that the view changed, but only
         * if the view is not frozen.'
         */
        if (pFE->dwAspect & m_pObj->m_dwAdviseAspects
            && !(pFE->dwAspect & m_pObj->m_dwFrozenAspects))
            {
            //Pass this on to the container.
            if (NULL!=m_pObj->m_pIAdvSinkView)
                {
                m_pObj->m_pIAdvSinkView->OnViewChange(pFE->dwAspect
                    , pFE->lindex);
                }
            }
        }

    return;
    }

/*
 * All others are uninteresting because if the container wants these
 * it will have called IOleObject::Advise which we passed on through
 * to the default handler.  IViewObject::SetAdvise is the only one
 * we override.
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnRename(LPMONIKER pmk)
    {
    return;
    }

STDMETHODIMP_(void) CImpIAdviseSink::OnSave(void)
    {
    return;
    }

STDMETHODIMP_(void) CImpIAdviseSink::OnClose(void)
    {
    return;
    }


IOLEOBJ.CPP

/*
 * IOLEOBJ.CPP
 * Cosmo Handler Chapter 19
 *
 * Implementation of the IOleObject interface for Cosmo Handler.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "hcosmo.h"

/*
 * CImpIOleObject::CImpIOleObject
 * CImpIOleObject::~CImpIOleObject
 *
 * Parameters (Constructor):
 *  pObj            PCFigure of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleObject::CImpIOleObject(PCFigure pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleObject::~CImpIOleObject(void)
    {
    return;
    }

/*
 * CImpIOleObject::QueryInterface
 * CImpIOleObject::AddRef
 * CImpIOleObject::Release
 */

STDMETHODIMP CImpIOleObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * The only member function we need to implement in a handler is
 * IOleObject::GetExtent since we know exactly how large our data
 * is.  All others can be delegated.
 */

/*
 * CImpIOleObject::GetExtent
 *
 * Purpose:
 *  Retrieves the size of the object in HIMETRIC units.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect requested
 *  pszl            LPSIZEL into which to store the size.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, error code otherwise.
 */

STDMETHODIMP CImpIOleObject::GetExtent(DWORD dwAspect, LPSIZEL pszl)
    {
    //We can just use IViewObject2 for this.
    return m_pObj->m_pImpIViewObject2->GetExtent(dwAspect, -1
        , NULL, pszl);
    }

/*
 * CImpIOleObject::DoVerb
 *
 * An example of displaying a message when the local server is not
 * present.  This is a good way to get some free advertising if you
 * allow free redistribution of your handler with documents
 * containing your objects.
 */

STDMETHODIMP CImpIOleObject::DoVerb(LONG iVerb, LPMSG pMSG
    , LPOLECLIENTSITE pSite, LONG lIndex, HWND hWnd, LPCRECT prc)
    {
    HRESULT     hr;

    hr=m_pObj->m_pDefIOleObject->DoVerb(iVerb, pMSG, pSite, lIndex
        , hWnd, prc);

    if (FAILED(hr))
        {
        MessageBox(hWnd, TEXT("Local server not present.\nIf\
 I wanted to make money\nI would put some advertising here.")
            , TEXT("Cosmo Handler"), MB_OK);
        }

    return hr;
    }

STDMETHODIMP CImpIOleObject::SetClientSite
    (LPOLECLIENTSITE pIOleClientSite)
    {
    return m_pObj->m_pDefIOleObject->SetClientSite(pIOleClientSite);
    }

STDMETHODIMP CImpIOleObject::GetClientSite
    (LPOLECLIENTSITE *ppSite)
    {
    return m_pObj->m_pDefIOleObject->GetClientSite(ppSite);
    }

STDMETHODIMP CImpIOleObject::SetHostNames(LPCOLESTR pszApp
    , LPCOLESTR pszObj)
    {
    return m_pObj->m_pDefIOleObject->SetHostNames(pszApp, pszObj);
    }

STDMETHODIMP CImpIOleObject::Close(DWORD dwSaveOption)
    {
    return m_pObj->m_pDefIOleObject->Close(dwSaveOption);
    }

STDMETHODIMP CImpIOleObject::SetMoniker(DWORD dwWhich
    , LPMONIKER pmk)
    {
    return m_pObj->m_pDefIOleObject->SetMoniker(dwWhich, pmk);
    }

STDMETHODIMP CImpIOleObject::GetMoniker(DWORD dwAssign
    , DWORD dwWhich, LPMONIKER *ppmk)
    {
    return m_pObj->m_pDefIOleObject->GetMoniker(dwAssign, dwWhich
        , ppmk);
    }

STDMETHODIMP CImpIOleObject::InitFromData(LPDATAOBJECT pIDataObject
    , BOOL fCreation, DWORD dw)
    {
    return m_pObj->m_pDefIOleObject->InitFromData(pIDataObject
        , fCreation, dw);
    }

STDMETHODIMP CImpIOleObject::GetClipboardData(DWORD dwReserved
    , LPDATAOBJECT *ppIDataObj)
    {
    return m_pObj->m_pDefIOleObject->GetClipboardData(dwReserved
        , ppIDataObj);
    }

STDMETHODIMP CImpIOleObject::EnumVerbs(LPENUMOLEVERB *ppEnum)
    {
    return m_pObj->m_pDefIOleObject->EnumVerbs(ppEnum);
    }

STDMETHODIMP CImpIOleObject::Update(void)
    {
    return m_pObj->m_pDefIOleObject->Update();
    }

STDMETHODIMP CImpIOleObject::IsUpToDate(void)
    {
    return m_pObj->m_pDefIOleObject->IsUpToDate();
    }

STDMETHODIMP CImpIOleObject::GetUserClassID(LPCLSID pClsID)
    {
    return m_pObj->m_pDefIOleObject->GetUserClassID(pClsID);
    }

STDMETHODIMP CImpIOleObject::GetUserType(DWORD dwForm
    , LPOLESTR *ppszType)
    {
    return m_pObj->m_pDefIOleObject->GetUserType(dwForm, ppszType);
    }

STDMETHODIMP CImpIOleObject::SetExtent(DWORD dwAspect, LPSIZEL pszl)
    {
    return m_pObj->m_pDefIOleObject->SetExtent(dwAspect, pszl);
    }

STDMETHODIMP CImpIOleObject::Advise(LPADVISESINK pIAdviseSink
    , LPDWORD pdwConn)
    {
    return m_pObj->m_pDefIOleObject->Advise(pIAdviseSink, pdwConn);
    }

STDMETHODIMP CImpIOleObject::Unadvise(DWORD dwConn)
    {
    return m_pObj->m_pDefIOleObject->Unadvise(dwConn);
    }

STDMETHODIMP CImpIOleObject::EnumAdvise(LPENUMSTATDATA *ppEnum)
    {
    return m_pObj->m_pDefIOleObject->EnumAdvise(ppEnum);
    }

STDMETHODIMP CImpIOleObject::GetMiscStatus(DWORD dwAspect
    , LPDWORD pdwStatus)
    {
    return m_pObj->m_pDefIOleObject->GetMiscStatus(dwAspect
        , pdwStatus);
    }

STDMETHODIMP CImpIOleObject::SetColorScheme(LPLOGPALETTE pLP)
    {
    return m_pObj->m_pDefIOleObject->SetColorScheme(pLP);
    }


IPERSTOR.CPP

/*
 * IPERSTOR.CPP
 * Cosmo Handler Chapter 19
 *
 * Implementation of the IPersistStorage interface that we expose on
 * the Figure object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "hcosmo.h"

/*
 * CImpIPersistStorage:CImpIPersistStorage
 * CImpIPersistStorage::~CImpIPersistStorage
 *
 * Constructor Parameters:
 *  pObj            PCFigure pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistStorage::CImpIPersistStorage(PCFigure pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    m_psState=PSSTATE_UNINIT;

    m_fConvert=FALSE;
    return;
    }

CImpIPersistStorage::~CImpIPersistStorage(void)
    {
    return;
    }

/*
 * CImpIPersistStorage::QueryInterface
 * CImpIPersistStorage::AddRef
 * CImpIPersistStorage::Release
 */

STDMETHODIMP CImpIPersistStorage::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistStorage::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistStorage::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistStorage::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the object represented by this interface.
 */

STDMETHODIMP CImpIPersistStorage::GetClassID(LPCLSID pClsID)
    {
    if (PSSTATE_UNINIT==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this object since
 *  it was loaded or initialized new.
 */

STDMETHODIMP CImpIPersistStorage::IsDirty(void)
    {
    if (PSSTATE_UNINIT==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    /*
     * Since we don't edit, we have no idea if this data is dirty.
     * Delegate to the default handler in case it wants to ask the
     * server.
     */
    return m_pObj->m_pDefIPersistStorage->IsDirty();
    }

/*
 * CImpIPersistStorage::InitNew
 *
 * Purpose:
 *  Provides the object with the IStorage they hold on to while
 *  they are running.  Since we don't create data in the handler,
 *  there it nothing to write here.  We only need to initialize
 *  out internal state.  This function is only be called once in
 *  the object's lifetime in lieu of Load.
 */

STDMETHODIMP CImpIPersistStorage::InitNew(LPSTORAGE pIStorage)
    {
    if (PSSTATE_UNINIT!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    if (NULL==pIStorage)
        return ResultFromScode(E_POINTER);

    //Good time to initilize our data
    m_pObj->m_pl.wVerMaj=VERSIONMAJOR;
    m_pObj->m_pl.wVerMin=VERSIONMINOR;
    m_pObj->m_pl.cPoints=0;
    m_pObj->m_pl.rgbBackground=GetSysColor(COLOR_WINDOW);
    m_pObj->m_pl.rgbLine=GetSysColor(COLOR_WINDOWTEXT);
    m_pObj->m_pl.iLineStyle=PS_SOLID;

    //Make sure these aren't filled with trash.
    memcpy(&m_pObj->m_plContent,   &m_pObj->m_pl, CBPOLYLINEDATA);
    memcpy(&m_pObj->m_plThumbnail, &m_pObj->m_pl, CBPOLYLINEDATA);

    m_pObj->m_pDefIPersistStorage->InitNew(pIStorage);

    /*
     * This is just for state validation in other calls.  As
     * a handler we never actually 'scribble.'
     */
    m_psState=PSSTATE_SCRIBBLE;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::Load
 *
 * Purpose:
 *  Instructs the object to load itself from a previously saved
 *  IStorage that was handled by Save in another object lifetime.
 *  This function will only be called once in the object's lifetime
 *  in lieu of InitNew.
 */

STDMETHODIMP CImpIPersistStorage::Load(LPSTORAGE pIStorage)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    LPSTREAM        pIStream;
    HRESULT         hr;

    if (PSSTATE_UNINIT!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    if (NULL==pIStorage)
        return ResultFromScode(E_POINTER);

    //This tells us if we're coming from another class storage.
    m_fConvert=(NOERROR==GetConvertStg(pIStorage));

    //This is the type of storage we're really messing with in Treat As
    ReadClassStg(pIStorage, &m_pObj->m_clsID);

    //Open the CONTENTS stream
    hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT | STGM_READ
        | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return ResultFromScode(STG_E_READFAULT);

    //Read all the data into the POLYLINEDATA structure.
    hr=pIStream->Read(&pl, CBPOLYLINEDATA, &cb);
    pIStream->Release();

    if (CBPOLYLINEDATA!=cb)
        return ResultFromScode(STG_E_READFAULT);

    //Copy into the actual object now.
    memcpy(&m_pObj->m_pl, &pl, CBPOLYLINEDATA);

    m_pObj->m_pDefIPersistStorage->Load(pIStorage);

    //As with InitNew, this is just for validating other calls
    m_psState=PSSTATE_SCRIBBLE;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::Save
 *
 * Purpose:
 *  Saves the data for this object to an IStorage.
 */

STDMETHODIMP CImpIPersistStorage::Save(LPSTORAGE pIStorage
    , BOOL fSameAsLoad)
    {
    ULONG           cb;
    HRESULT         hr;
    LPSTREAM        pIStream;

    //Have to come here from scribble state.
    if (PSSTATE_SCRIBBLE!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    //Must have an IStorage if we're not in SameAsLoad
    if (NULL==pIStorage && !fSameAsLoad)
        return ResultFromScode(E_POINTER);

    /*
     * If the server is running, don't do the save ourselves since
     * we'd end up writing the storage twice with possible conflicts.
     */
    if (OleIsRunning(m_pObj->m_pDefIOleObject))
        {
        hr=m_pObj->m_pDefIPersistStorage->Save(pIStorage
            , fSameAsLoad);

        if (SUCCEEDED(hr))
            m_psState=PSSTATE_ZOMBIE;

        return hr;
        }

    /*
     * Since we don't have any data we modify in the handler,
     * we have nothing to do for same-as-load cases.  The only
     * case we have to handle is writing to a new storage.
     */
    if (!fSameAsLoad)
        {
        /*
         * This code facilitates making copies of an object into
         * a new storage during a container Save As.
         */

        hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT | STGM_CREATE
            | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pIStream);

        if (FAILED(hr))
            return hr;

        WriteClassStg(pIStorage, CLSID_CosmoFigure);
        WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
            , TEXT("Polyline Figure"));

        hr=pIStream->Write(&m_pObj->m_pl, CBPOLYLINEDATA, &cb);
        pIStream->Release();

        if (FAILED(hr) || CBPOLYLINEDATA!=cb)
            return ResultFromScode(STG_E_WRITEFAULT);
        }

    //Clear the convert bit if it was set
    if (m_fConvert)
        {
        WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
            , TEXT("Polyline Figure"));

        SetConvertStg(pIStorage, FALSE);
        m_fConvert=FALSE;
        }

    m_psState=PSSTATE_ZOMBIE;
    m_pObj->m_pDefIPersistStorage->Save(pIStorage, fSameAsLoad);
    return NOERROR;
    }

/*
 * CImpIPersistStorage::SaveCompleted
 * CImpIPersistStorage::HandsOffStorage
 *
 * Purpose:
 *  Pass throughs with typical management of our pointers.
 */

STDMETHODIMP CImpIPersistStorage::SaveCompleted(LPSTORAGE pIStorage)
    {
    //Must be called in no-scribble or hands-off state
    if (!(PSSTATE_ZOMBIE==m_psState || PSSTATE_HANDSOFF==m_psState))
        return ResultFromScode(E_UNEXPECTED);

    //If we're coming from Hands-Off, we'd better get a storage
    if (NULL==pIStorage && PSSTATE_HANDSOFF==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    /*
     * Since this handler modifies nothing, it saves no pointers
     * which it would have to release here.  Nothing to do.
     */

    m_pObj->m_pDefIPersistStorage->SaveCompleted(pIStorage);
    m_psState=PSSTATE_SCRIBBLE;
    return NOERROR;
    }

STDMETHODIMP CImpIPersistStorage::HandsOffStorage(void)
    {
    if (PSSTATE_UNINIT==m_psState || PSSTATE_HANDSOFF==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    /*
     * Since this handler modifies nothing, it saves no pointers
     * which it would have to release here.  Nothing to do.
     */

    m_pObj->m_pDefIPersistStorage->HandsOffStorage();
    m_psState=PSSTATE_HANDSOFF;
    return NOERROR;
    }


IVIEWOBJ.CPP

/*
 * IVIEWOBJ.CPP
 * Cosmo Handler Chapter 19
 *
 * Implementation of the IViewObject2 interface for Cosmo Handler.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "hcosmo.h"

/*
 * CImpIViewObject2::CImpIViewObject2
 * CImpIViewObject2::~CImpIViewObject2
 *
 * Parameters (Constructor):
 *  pObj            PCFigure of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIViewObject2::CImpIViewObject2(PCFigure pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIViewObject2::~CImpIViewObject2(void)
    {
    return;
    }

/*
 * CImpIViewObject2::QueryInterface
 * CImpIViewObject2::AddRef
 * CImpIViewObject2::Release
 */

STDMETHODIMP CImpIViewObject2::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIViewObject2::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIViewObject2::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIViewObject2::Draw
 *
 * Purpose:
 *  Draws the object on the given hDC specifically for the requested
 *  aspect, device, and within the appropriate bounds.
 *
 * Parameters:
 *  dwAspect        DWORD aspect to draw.
 *  lindex          LONG index of the piece to draw.
 *  pvAspect        LPVOID for extra information, always NULL.
 *  ptd             DVTARGETDEVICE * containing device
 *                  information.
 *  hICDev          HDC containing the IC for the device.
 *  hDC             HDC on which to draw.
 *  pRectBounds     LPCRECTL describing the rectangle in which to
 *                  draw.
 *  pRectWBounds    LPCRECTL describing the placement rectangle if
 *                  part of what you draw is another metafile.
 *  pfnContinue     Function to call periodically during long
 *                  repaints.
 *  dwContinue      DWORD extra information for pfnContinue.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject2::Draw(DWORD dwAspect, LONG lindex
    , void *pvAspect, DVTARGETDEVICE *ptd, HDC hICDev
    , HDC hDC, LPCRECTL pRectBounds, LPCRECTL pRectWBounds
    , BOOL (CALLBACK *pfnContinue) (DWORD), DWORD dwContinue)
    {
    RECT            rc;
    POLYLINEDATA    pl;
    PPOLYLINEDATA   ppl=&m_pObj->m_pl;

    RECTFROMRECTL(rc, *pRectBounds);

    //Delegate iconic and printed representations.
    if (!((DVASPECT_CONTENT | DVASPECT_THUMBNAIL) & dwAspect))
        {
        return m_pObj->m_pDefIViewObject2->Draw(dwAspect, lindex
            , pvAspect, ptd, hICDev, hDC, pRectBounds, pRectWBounds
            , pfnContinue, dwContinue);
        }

    /*
     * If we're asked to draw a frozen aspect, use the data from
     * a copy we made in IViewObject2::Freeze.  Otherwise use the
     * current data.
     */
    if (dwAspect & m_pObj->m_dwFrozenAspects)
        {
        //Point to the data to actually use.
        if (DVASPECT_CONTENT==dwAspect)
            ppl=&m_pObj->m_plContent;
        else
            ppl=&m_pObj->m_plThumbnail;
        }

    //Make a copy so we can modify it
    memcpy(&pl, ppl, CBPOLYLINEDATA);

    /*
     * If we're going to a printer, check if it's color capable.
     * if not, then use black on white for this figure.
     */
    if (NULL!=hICDev)
        {
        if (GetDeviceCaps(hICDev, NUMCOLORS) <= 2)
            {
            pl.rgbBackground=RGB(255, 255, 255);
            pl.rgbLine=RGB(0, 0, 0);
            }
        }

    m_pObj->Draw(hDC, &rc, dwAspect, ptd, hICDev, &pl);
    return NOERROR;
    }

/*
 * CImpIViewObject2::GetColorSet
 *
 * Purpose:
 *
 * Parameters:
 *  dwAspect        DWORD aspect of interest.
 *  lindex          LONG piece of interest.
 *  pvAspect        LPVOID containing extra information, always
 *                  NULL.
 *  ptd             DVTARGETDEVICE * containing device
 *                  information.
 *  hICDev          HDC containing the IC for the device.
 *  ppColorSet      LPLOGPALETTE * into which to return the
 *                  pointer to the palette in this color set.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, S_FALSE if not supported.
 */

STDMETHODIMP CImpIViewObject2::GetColorSet(DWORD dwDrawAspect
    , LONG lindex, LPVOID pvAspect, DVTARGETDEVICE *ptd
    , HDC hICDev, LPLOGPALETTE *ppColorSet)
    {
    return ResultFromScode(S_FALSE);
    }

/*
 * CImpIViewObject2::Freeze
 *
 * Purpose:
 *  Freezes the view of a particular aspect such that data changes
 *  do not affect the view.
 *
 * Parameters:
 *  dwAspect        DWORD aspect to freeze.
 *  lindex          LONG piece index under consideration.
 *  pvAspect        LPVOID for further information, always NULL.
 *  pdwFreeze       LPDWORD in which to return the key.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject2::Freeze(DWORD dwAspect, LONG lindex
    , LPVOID pvAspect, LPDWORD pdwFreeze)
    {
    //Delegate any aspect we don't handle.
    if (!((DVASPECT_CONTENT | DVASPECT_THUMBNAIL) & dwAspect))
        {
        return m_pObj->m_pDefIViewObject2->Freeze(dwAspect, lindex
            , pvAspect, pdwFreeze);
        }

    if (dwAspect & m_pObj->m_dwFrozenAspects)
        {
        *pdwFreeze=dwAspect + FREEZE_KEY_OFFSET;
        return ResultFromScode(VIEW_S_ALREADY_FROZEN);
        }

    m_pObj->m_dwFrozenAspects |= dwAspect;

    /*
     * For whatever aspects become frozen, make a copy of the data.
     * Later when drawing, if such a frozen aspect is requested,
     * we'll draw from this data rather than from our current data.
     */
    if (DVASPECT_CONTENT & dwAspect)
        {
        memcpy(&m_pObj->m_plContent, &m_pObj->m_pl
            , CBPOLYLINEDATA);
        }

    if (DVASPECT_THUMBNAIL & dwAspect)
        {
        memcpy(&m_pObj->m_plThumbnail, &m_pObj->m_pl
            , CBPOLYLINEDATA);
        }

    if (NULL!=pdwFreeze)
        *pdwFreeze=dwAspect + FREEZE_KEY_OFFSET;

    return NOERROR;
    }

/*
 * CImpIViewObject2::Unfreeze
 *
 * Purpose:
 *  Thaws an aspect frozen in Freeze.  We expect that a container
 *  will redraw us after freezing if necessary, so we don't send
 *  any sort of notification here.
 *
 * Parameters:
 *  dwFreeze        DWORD key returned from Freeze.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject2::Unfreeze(DWORD dwFreeze)
    {
    DWORD       dwAspect=dwFreeze - FREEZE_KEY_OFFSET;

    //Delegate any aspect we don't handle.
    if (!((DVASPECT_CONTENT | DVASPECT_THUMBNAIL) & dwAspect))
        return m_pObj->m_pDefIViewObject2->Unfreeze(dwFreeze);

    //The aspect to unfreeze is in the key.
    m_pObj->m_dwFrozenAspects &= ~(dwAspect);

    /*
     * Since we always kept our current data up to date, we don't
     * have to do anything here like requesting data again.
     * Because we removed dwAspect from m_dwFrozenAspects, Draw
     * will again use the current data.
     */

    return NOERROR;
    }

/*
 * CImpIViewObject2::SetAdvise
 *
 * Purpose:
 *  Provides an advise sink to the view object enabling
 *  notifications for a specific aspect.
 *
 * Parameters:
 *  dwAspects       DWORD describing the aspects of interest.
 *  dwAdvf          DWORD containing advise flags.
 *  pIAdviseSink    LPADVISESINK to notify.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject2::SetAdvise(DWORD dwAspects
    , DWORD dwAdvf, LPADVISESINK pIAdviseSink)
    {
    //Pass anything we don't support on through
    if (!((DVASPECT_CONTENT | DVASPECT_THUMBNAIL) & dwAspects))
        {
        return m_pObj->m_pDefIViewObject2->SetAdvise(dwAspects
            , dwAdvf, pIAdviseSink);
        }

    if (NULL!=m_pObj->m_pIAdvSinkView)
        m_pObj->m_pIAdvSinkView->Release();

    m_pObj->m_dwAdviseAspects=dwAspects;
    m_pObj->m_dwAdviseFlags=dwAdvf;

    m_pObj->m_pIAdvSinkView=pIAdviseSink;

    if (NULL!=m_pObj->m_pIAdvSinkView)
        m_pObj->m_pIAdvSinkView->AddRef();

    return NOERROR;
    }

/*
 * CImpIViewObject2::GetAdvise
 *
 * Purpose:
 *  Returns the last known IAdviseSink seen by SetAdvise.
 *
 * Parameters:
 *  pdwAspects      LPDWORD in which to store the last requested
 *                  aspects.
 *  pdwAdvf         LPDWORD in which to store the last requested
 *                  flags.
 *  ppIAdvSink      LPADVISESINK * in which to store the
 *                  IAdviseSink.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject2::GetAdvise(LPDWORD pdwAspects
    , LPDWORD pdwAdvf, LPADVISESINK *ppAdvSink)
    {
    if (NULL==m_pObj->m_pIAdvSinkView)
        {
        return m_pObj->m_pDefIViewObject2->GetAdvise(pdwAspects
            , pdwAdvf, ppAdvSink);
        }

    if (NULL==ppAdvSink)
        return ResultFromScode(E_INVALIDARG);
    else
        {
        *ppAdvSink=m_pObj->m_pIAdvSinkView;
        m_pObj->m_pIAdvSinkView->AddRef();
        }

    if (NULL!=pdwAspects)
        *pdwAspects=m_pObj->m_dwAdviseAspects;

    if (NULL!=pdwAdvf)
        *pdwAdvf=m_pObj->m_dwAdviseFlags;

    return NOERROR;
    }

/*
 * CImpIViewObject2::GetExtent
 *
 * Purpose:
 *  Retrieves the extents of the object's display.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect of interest.
 *  lindex          LONG index of the piece of interest.
 *  ptd             DVTARGETDEVICE * with device information.
 *  pszl            LPSIZEL to the structure in which to return
 *                  the extents.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject2::GetExtent(DWORD dwAspect, LONG lindex
    , DVTARGETDEVICE *ptd, LPSIZEL pszl)
    {
    HDC         hDC;
    int         iXppli, iYppli;
    RECT        rc;

    /*
     * We can answer for CONTENT/THUMBNAIL, but try the server for
     * others. In addition, always delegate is the server is running
     * since it has a window to define the size.
     */
    if (!((DVASPECT_CONTENT | DVASPECT_THUMBNAIL) & dwAspect)
        || OleIsRunning(m_pObj->m_pDefIOleObject))
        return m_pObj->m_pDefIOleObject->GetExtent(dwAspect, pszl);

    /*
     * The size is in the rc field of the POLYLINEDATA structure
     * which we now have to convert to HIMETRIC.
     */

    hDC=GetDC(NULL);
    iXppli=GetDeviceCaps(hDC, LOGPIXELSX);
    iYppli=GetDeviceCaps(hDC, LOGPIXELSY);

    RECTSTORECT(m_pObj->m_pl.rc, rc);
    pszl->cx=(long)MulDiv(HIMETRIC_PER_INCH
        , (rc.right-rc.left), iXppli);

    pszl->cy=(long)MulDiv(HIMETRIC_PER_INCH
        , (rc.bottom-rc.top), iYppli);

    ReleaseDC(NULL, hDC);
    return NOERROR;
    }


PRECOMP.CPP

#include "hcosmo.h"


DLLPOLY.CPP

/*
 * DLLPOLY.CPP
 * Polyline Component Chapter 19
 *
 * Polyline component object used in CoCosmo that supports a custom
 * interface IPolyline.  Contains DLL entry code and the component
 * object exports DllGetClassObject and DllCanUnloadNow and the
 * class factory object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "polyline.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

//DLL Instance handle
HINSTANCE   g_hInst=0;

/*
 * LibMain32 (Win32)
 *
 * Purpose:
 *  Entry point for Win32 DLLs that calls the Win16 LibMain.
 */

#ifdef WIN32
BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    if (DLL_PROCESS_DETACH==ulReason)
        {
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    return (0!=LibMain(hInstance, 0,  0, NULL));
    }
#endif

/*
 * LibMain (also called from Win32 LibMain32)
 *
 * Purpose:
 *  DLL-specific entry point called from LibEntry.
 */

int PASCAL LibMain(HINSTANCE hInst, WORD wDataSeg
    , WORD cbHeapSize, LPSTR lpCmdLine)
    {
    WNDCLASS    wc;

    if (GetClassInfo(hInst, SZCLASSPOLYLINE, &wc))
        return (int)hInst;

   #ifndef WIN32
    if (0!=cbHeapSize)
        UnlockData(0);
   #endif

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.hInstance     = hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PolylineWndProc;
    wc.cbWndExtra    = CBPOLYLINEWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_CROSS);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPOLYLINE;

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

    g_hInst=hInst;
    return (int)hInst;
    }

/*
 * DllGetClassObject
 *
 * Purpose:
 *  Provides an IClassFactory for a given CLSID that this DLL is
 *  registered to support.  This DLL is placed under the CLSID
 *  in the registration database as the InProcServer.
 *
 * Parameters:
 *  clsID           REFCLSID that identifies the class factory
 *                  desired.  Since this parameter is passed this
 *                  DLL can handle any number of objects simply
 *                  by returning different class factories here
 *                  for different CLSIDs.
 *
 *  riid            REFIID specifying the interface the caller wants
 *                  on the class object, usually IID_ClassFactory.
 *
 *  ppv             PPVOID in which to return the interface
 *                  pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, otherwise an error code.
 */

HRESULT APIENTRY DllGetClassObject(REFCLSID rclsid
    , REFIID riid, PPVOID ppv)
    {
    //CHAPTER19MOD
    if (CLSID_Polyline19!=rclsid)
        return ResultFromScode(E_FAIL);
    //End CHAPTER19MOD

    //Check that we can provide the interface
    if (IID_IUnknown!=riid && IID_IClassFactory!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Return our IClassFactory for Polyline objects
    *ppv=new CPolylineClassFactory;

    if (NULL==*ppv)
        return ResultFromScode(E_OUTOFMEMORY);

    //AddRef the object through any interface we return
    ((LPUNKNOWN)*ppv)->AddRef();
    g_cObj++;
    return NOERROR;
    }

/*
 * DllCanUnloadNow
 *
 * Purpose:
 *  Answers if the DLL can be freed, that is, if there are no
 *  references to anything this DLL provides.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if nothing is using us, FALSE otherwise.
 */

STDAPI DllCanUnloadNow(void)
    {
    SCODE   sc;

    //Our answer is whether there are any object or locks
    sc=(0L==g_cObj && 0L==g_cLock) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Polyline object to call when it gets destroyed.
 *  Since we're in a DLL we only track the number of objects here
 *  letting DllCanUnloadNow take care of the rest.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;
    return;
    }

/*
 * CPolylineClassFactory::CPolylineClassFactory
 *
 * Purpose:
 *  Constructor for an object supporting an IClassFactory that
 *  instantiates Polyline objects.
 *
 * Parameters:
 *  None
 */

CPolylineClassFactory::CPolylineClassFactory(void)
    {
    m_cRef=0L;
    return;
    }

/*
 * CPolylineClassFactory::~CPolylineClassFactory
 *
 * Purpose:
 *  Destructor for a CPolylineClassFactory object.  This will be
 *  called when we Release the object to a zero reference count.
 */

CPolylineClassFactory::~CPolylineClassFactory(void)
    {
    return;
    }

/*
 * CPolylineClassFactory::QueryInterface
 * CPolylineClassFactory::AddRef
 * CPolylineClassFactory::Release
 */

STDMETHODIMP CPolylineClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CPolylineClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPolylineClassFactory::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    ObjectDestroyed();
    return 0L;
    }

/*
 * CPolylineClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a Polyline object.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we
 *                  are being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_NOINTERFACE
 *                  if we cannot support the requested interface.
 */

STDMETHODIMP CPolylineClassFactory::CreateInstance
    (LPUNKNOWN pUnkOuter, REFIID riid, PPVOID ppvObj)
    {
    PCPolyline          pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //Verify that a controlling unknown asks for IUnknown
    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Create the object.  This also creates a window.
    pObj=new CPolyline(pUnkOuter, ObjectDestroyed, g_hInst);

    if (NULL==pObj)
        return hr;

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    //Kill the object if initial creation or Init failed.
    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

/*
 * CPolylineClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the DLL.  If the lock
 *  count goes to zero and there are no objects, the DLL is allowed
 *  to unload.  See DllCanUnloadNow.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CPolylineClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        g_cLock--;

    return NOERROR;
    }


ICONNPT.CPP

/*
 * ICONNPT.CPP
 * Polyline Component Chapter 19
 *
 * Implementation of CImpIConnectionPoint for the Polyline object
 * as well as CConnectionPoint.
 *
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIConnPtCont:CImpIConnPtCont
 * CImpIConnPtCont::~CImpIConnPtCont
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIConnPtCont::CImpIConnPtCont(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIConnPtCont::~CImpIConnPtCont(void)
    {
    return;
    }

/*
 * CImpIConnPtCont::QueryInterface
 * CImpIConnPtCont::AddRef
 * CImpIConnPtCont::Release
 */

STDMETHODIMP CImpIConnPtCont::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIConnPtCont::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIConnPtCont::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIConnPtCont::EnumConnectionPoints
 *
 * Purpose:
 *  Not implemented.
 *
 * Return Value:
 *  HRESULT         E_NOTIMPL
 */

STDMETHODIMP CImpIConnPtCont::EnumConnectionPoints
    (LPENUMCONNECTIONPOINTS *ppEnum)
    {
    *ppEnum=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIConnPtCont::FindConnectionPoint
 *
 * Purpose:
 *  Returns a pointer to the IConnectionPoint for a given
 *  outgoing IID.
 *
 * Parameters:
 *  riid            REFIID of the outgoing interface for which
 *                  a connection point is desired.
 *  ppCP            IConnectionPoint ** in which to return
 *                  the pointer after calling AddRef.
 *
 * Return Value:
 *  HRESULT         NOERROR if the connection point is found,
 *                  E_NOINTERFACE if it's not supported.
 */

STDMETHODIMP CImpIConnPtCont::FindConnectionPoint(REFIID riid
    , IConnectionPoint **ppCP)
    {
    *ppCP=NULL;

    if (IID_IPolylineAdviseSink10==riid)
        {
        return m_pObj->m_pConnPt->QueryInterface
            (IID_IConnectionPoint, (PPVOID)ppCP);
        }

    return ResultFromScode(E_NOINTERFACE);
    }

//CConnectionPoint implementation

/*
 * CConnectionPoint::CConnectionPoint
 * CConnectionPoint::~CConnectionPoint
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.  We can
 *                  query this for the IConnectionPointContainer
 *                  interface we might need.
 */

CConnectionPoint::CConnectionPoint(PCPolyline pObj)
    {
    m_cRef=0;

    /*
     * Our lifetime is controlled by the connectable object itself,
     * although other external clients will call AddRef and Release.
     * Since we're nested in the connectable object's lifetime,
     * there's no need to call AddRef on pObj.
     */
    m_pObj=pObj;
    return;
    }

CConnectionPoint::~CConnectionPoint(void)
    {
    ReleaseInterface(m_pObj->m_pAdv);
    return;
    }

/*
 * CConnectionPoint::QueryInterface
 * CConnectionPoint::AddRef
 * CConnectionPoint::Release
 *
 * Purpose:
 *  Non-delegating IUnknown members for CConnectionPoint.
 */

STDMETHODIMP CConnectionPoint::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IConnectionPoint==riid)
        *ppv=(LPVOID)this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CConnectionPoint::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CConnectionPoint::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CConnectionPoint::GetConnectionInterface
 *
 * Purpose:
 *  Returns the IID of the outgoing interface supported through
 *  this connection point.
 *
 * Parameters:
 *  pIID            IID * in which to store the IID.
 */

STDMETHODIMP CConnectionPoint::GetConnectionInterface(IID *pIID)
    {
    if (NULL==pIID)
        return ResultFromScode(E_POINTER);

    *pIID=IID_IPolylineAdviseSink10;
    return NOERROR;
    }

/*
 * CConnectionPoint::GetConnectionPointContainer
 *
 * Purpose:
 *  Returns a pointer to the IConnectionPointContainer that
 *  is manageing this connection point.
 *
 * Parameters:
 *  ppCPC           IConnectionPointContainer ** in which to return
 *                  the pointer after calling AddRef.
 */

STDMETHODIMP CConnectionPoint::GetConnectionPointContainer
    (IConnectionPointContainer **ppCPC)
    {
    return m_pObj->QueryInterface(IID_IConnectionPointContainer
        , (void **)ppCPC);
    }

/*
 * CConnectionPoint::Advise
 *
 * Purpose:
 *  Provides this connection point with a notification sink to
 *  call whenever the appropriate outgoing function/event occurs.
 *
 * Parameters:
 *  pUnkSink        LPUNKNOWN to the sink to notify.  The connection
 *                  point must QueryInterface on this pointer to obtain
 *                  the proper interface to call.  The connection
 *                  point must also insure that any pointer held has
 *                  a reference count (QueryInterface will do it).
 *  pdwCookie       DWORD * in which to store the connection key for
 *                  later calls to Unadvise.
 */

STDMETHODIMP CConnectionPoint::Advise(LPUNKNOWN pUnkSink
    , DWORD *pdwCookie)
    {
    IPolylineAdviseSink10  *pSink;

    *pdwCookie=0;

    //Only allow one connection
    if (NULL!=m_pObj->m_pAdv)
        return ResultFromScode(CONNECT_E_ADVISELIMIT);

    //Check for the right interface on the sink.
    if (FAILED(pUnkSink->QueryInterface(IID_IPolylineAdviseSink10
        , (PPVOID)&pSink)))
        return ResultFromScode(CONNECT_E_CANNOTCONNECT);

    *pdwCookie=ADVISEKEY;
    m_pObj->m_pAdv=pSink;
    return NOERROR;
    }

/*
 * CConnectionPoint::Unadvise
 *
 * Purpose:
 *  Terminates the connection to the notification sink identified
 *  with dwCookie (that was returned from Advise).  The connection
 *  point has to Release any held pointers for that sink.
 *
 * Parameters:
 *  dwCookie        DWORD connection key from Advise.
 */

STDMETHODIMP CConnectionPoint::Unadvise(DWORD dwCookie)
    {
    if (0==dwCookie)
        return ResultFromScode(E_INVALIDARG);

    if (ADVISEKEY!=dwCookie)
        ResultFromScode(CONNECT_E_NOCONNECTION);

    ReleaseInterface(m_pObj->m_pAdv);
    return NOERROR;
    }

/*
 * CConnectionPoint::EnumConnections
 *
 * Purpose:
 *  Not implemented.
 */

STDMETHODIMP CConnectionPoint::EnumConnections
    (LPENUMCONNECTIONS *ppEnum)
    {
    *ppEnum=NULL;
    return ResultFromScode(E_NOTIMPL);
    }


IDATAOBJ.CPP

/*
 * IDATAOBJ.CPP
 * Polyline Component Chapter 19
 *
 * Implementation of the IDataObject interface.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIDataObject::CImpIDataObject
 * CImpIDataObject::~CImpIDataObject
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIDataObject::CImpIDataObject(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIDataObject::~CImpIDataObject(void)
    {
    return;
    }

/*
 * CImpIDataObject::QueryInterface
 * CImpIDataObject::AddRef
 * CImpIDataObject::Release
 */

STDMETHODIMP CImpIDataObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIDataObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIDataObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIDataObject::GetData
 *
 * Purpose:
 *  Retrieves data described by a specific FormatEtc into a StgMedium
 *  allocated by this function.  Used like GetClipboardData.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *  pSTM            LPSTGMEDIUM in which to return the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetData(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM)
    {
    UINT            cf=pFE->cfFormat;

    //Check the aspects we support.
    if (!(DVASPECT_CONTENT & pFE->dwAspect))
        return ResultFromScode(DATA_E_FORMATETC);

    pSTM->pUnkForRelease=NULL;

    //Run creates the window to use as a basis for extents
    m_pObj->m_pImpIRunnableObject->Run(NULL);

    //Go render the appropriate data for the format.
    switch (cf)
        {
        case CF_METAFILEPICT:
            pSTM->tymed=TYMED_MFPICT;
            return m_pObj->RenderMetafilePict(&pSTM->hGlobal);

        case CF_BITMAP:
            pSTM->tymed=TYMED_GDI;
            return m_pObj->RenderBitmap((HBITMAP *)&pSTM->hGlobal);

        default:
            if (cf==m_pObj->m_cf)
                {
                pSTM->tymed=TYMED_HGLOBAL;
                return m_pObj->RenderNative(&pSTM->hGlobal);
                }

            break;
        }

    return ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::GetDataHere
 *
 * Purpose:
 *  Renders the specific FormatEtc into caller-allocated medium
 *  provided in pSTM.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *  pSTM            LPSTGMEDIUM providing the medium into which
 *                  wer render the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetDataHere(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM)
    {
    //CHAPTER19MOD
    //We can provide CFSTR_EMBEDSOURCE now.

    UINT        cf;
    HRESULT     hr;

    /*
     * The only reasonable time this is called is for
     * CFSTR_EMBEDSOURCE and TYMED_ISTORAGE (and later for
     * CFSTR_LINKSOURCE).  This means the same as
     * IPersistStorage::Save.
     */

    cf=RegisterClipboardFormat(CFSTR_EMBEDSOURCE);

    //Aspect is unimportant to us here, as is lindex and ptd.
    if (cf==pFE->cfFormat && (TYMED_ISTORAGE & pFE->tymed))
        {
        //We have an IStorage we can write into.
        pSTM->tymed=TYMED_ISTORAGE;
        pSTM->pUnkForRelease=NULL;

        hr=m_pObj->m_pImpIPersistStorage->Save(pSTM->pstg, FALSE);
        m_pObj->m_pImpIPersistStorage->SaveCompleted(NULL);
        return hr;
        }
    //End CHAPTER19MOD

    return ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::QueryGetData
 *
 * Purpose:
 *  Tests if a call to GetData with this FormatEtc will provide
 *  any rendering; used like IsClipboardFormatAvailable.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::QueryGetData(LPFORMATETC pFE)
    {
    UINT            cf=pFE->cfFormat;
    BOOL            fRet=FALSE;

    //Check the aspects we support.
    if (!(DVASPECT_CONTENT & pFE->dwAspect))
        return ResultFromScode(DATA_E_FORMATETC);

    switch (cf)
        {
        case CF_METAFILEPICT:
            fRet=(BOOL)(pFE->tymed & TYMED_MFPICT);
            break;

        case CF_BITMAP:
            fRet=(BOOL)(pFE->tymed & TYMED_GDI);
            break;

        default:
            //Check our own format.
            fRet=((cf==m_pObj->m_cf)
                && (BOOL)(pFE->tymed & TYMED_HGLOBAL));
            break;
        }

    return fRet ? NOERROR : ResultFromScode(S_FALSE);
    }

/*
 * CImpIDataObject::GetCanonicalFormatEtc
 *
 * Purpose:
 *  Provides the caller with an equivalent FormatEtc to the one
 *  provided when different FormatEtcs will produce exactly the
 *  same renderings.
 *
 * Parameters:
 *  pFEIn            LPFORMATETC of the first description.
 *  pFEOut           LPFORMATETC of the equal description.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetCanonicalFormatEtc
    (LPFORMATETC pFEIn, LPFORMATETC pFEOut)
    {
    if (NULL==pFEOut)
        return ResultFromScode(E_INVALIDARG);

    pFEOut->ptd=NULL;
    return ResultFromScode(DATA_S_SAMEFORMATETC);
    }

/*
 * CImpIDataObject::SetData
 *
 * Purpose:
 *  Places data described by a FormatEtc and living in a StgMedium
 *  into the object.  The object may be responsible to clean up the
 *  StgMedium before exiting.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the data to set.
 *  pSTM            LPSTGMEDIUM containing the data.
 *  fRelease        BOOL indicating if this function is responsible
 *                  for freeing the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::SetData(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM, BOOL fRelease)
    {
    UINT            cf=pFE->cfFormat;
    BOOL            fRet=FALSE;
    PPOLYLINEDATA   ppl;

    //Check for our own clipboard format and DVASPECT_CONTENT
    if ((cf!=m_pObj->m_cf) || !(DVASPECT_CONTENT & pFE->dwAspect))
        return ResultFromScode(DATA_E_FORMATETC);

    /*
     * Data can only come from global memory containing a
     * POLYLINEDATA structure that we send to the Polyline's
     * DataSet, a now internal function used from here and
     * from IPersistStorage::Load.
     */

    if (TYMED_HGLOBAL!=pSTM->tymed)
        return ResultFromScode(DATA_E_FORMATETC);

    ppl=(PPOLYLINEDATA)GlobalLock(pSTM->hGlobal);

    if (NULL!=ppl)
        {
        m_pObj->DataSet(ppl, TRUE, TRUE);
        GlobalUnlock(pSTM->hGlobal);
        fRet=TRUE;
        }

    if (fRelease)
        ReleaseStgMedium(pSTM);

    return fRet ? NOERROR : ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::EnumFormatEtc
 *
 * Purpose:
 *  Returns an IEnumFORMATETC object through which the caller can
 *  iterate to learn about all the data formats this object can
 *  provide through either GetData[Here] or SetData.
 *
 * Parameters:
 *  dwDir           DWORD describing a data direction, either
 *                  DATADIR_SET or DATADIR_GET.
 *  ppEnum          LPENUMFORMATETC * in which to return the
 *                  pointer to the enumerator.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::EnumFormatEtc(DWORD dwDir
    , LPENUMFORMATETC *ppEnum)
    {
    //CHAPTER19MOD
    //Can now just tell the default handler to do this busy work.
    return m_pObj->m_pDefIDataObject->EnumFormatEtc(dwDir, ppEnum);
    //CHAPTER19MOD
    }

//CHAPTER19MOD
//Removed some comments from this stuff.

/*
 * CImpIDataObject::DAdvise
 * CImpIDataObject::DUnadvise
 * CImpIDataObject::EnumDAdvise
 */

STDMETHODIMP CImpIDataObject::DAdvise(LPFORMATETC pFE, DWORD dwFlags
    , LPADVISESINK pIAdviseSink, LPDWORD pdwConn)
    {
    HRESULT         hr;

    if (NULL==m_pObj->m_pIDataAdviseHolder)
        {
        hr=CreateDataAdviseHolder(&m_pObj->m_pIDataAdviseHolder);

        if (FAILED(hr))
            return ResultFromScode(E_OUTOFMEMORY);
        }

    hr=m_pObj->m_pIDataAdviseHolder->Advise(this, pFE
        , dwFlags, pIAdviseSink, pdwConn);

    return hr;
    }

STDMETHODIMP CImpIDataObject::DUnadvise(DWORD dwConn)
    {
    HRESULT         hr;

    if (NULL==m_pObj->m_pIDataAdviseHolder)
        return ResultFromScode(E_FAIL);

    hr=m_pObj->m_pIDataAdviseHolder->Unadvise(dwConn);

    return hr;
    }

STDMETHODIMP CImpIDataObject::EnumDAdvise(LPENUMSTATDATA *ppEnum)
    {
    HRESULT         hr;

    if (NULL==m_pObj->m_pIDataAdviseHolder)
        return ResultFromScode(E_FAIL);

    hr=m_pObj->m_pIDataAdviseHolder->EnumAdvise(ppEnum);
    return hr;
    }

//End CHAPTER19MOD


IOLEOBJ.CPP

/*
 * IOLEOBJ.CPP
 * Polyline Component Chapter 19
 *
 * Implementation of the IOleObject interface for Polyline.  Some of
 * these just pass through to the default handler which does default
 * implementations.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIOleObject::CImpIOleObject
 * CImpIOleObject::~CImpIOleObject
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleObject::CImpIOleObject(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleObject::~CImpIOleObject(void)
    {
    return;
    }

/*
 * CImpIOleObject::QueryInterface
 * CImpIOleObject::AddRef
 * CImpIOleObject::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleObject object.
 */

STDMETHODIMP CImpIOleObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleObject::SetClientSite
 * CImpIOleObject::GetClientSite
 *
 * Purpose:
 *  Manages the IOleClientSite pointer of our container.
 */

STDMETHODIMP CImpIOleObject::SetClientSite
    (LPOLECLIENTSITE pIOleClientSite)
    {
    if (NULL!=m_pObj->m_pIOleClientSite)
        m_pObj->m_pIOleClientSite->Release();

    m_pObj->m_pIOleClientSite=pIOleClientSite;

    if (NULL!=m_pObj->m_pIOleClientSite)
        m_pObj->m_pIOleClientSite->AddRef();

    return NOERROR;
    }

STDMETHODIMP CImpIOleObject::GetClientSite(LPOLECLIENTSITE *ppSite)
    {
    //Be sure to AddRef the new pointer you are giving away.
    *ppSite=m_pObj->m_pIOleClientSite;
    m_pObj->m_pIOleClientSite->AddRef();

    return NOERROR;
    }

/*
 * CImpIOleObject::SetHostNames
 *
 * Purpose:
 *  Provides the object with names of the container application and
 *  the object in the container to use in object user interface.
 *
 * Parameters:
 *  pszApp          LPCOLESTR of the container application.
 *  pszObj          LPCOLESTR of some name that is useful in window
 *                  titles.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleObject::SetHostNames(LPCOLESTR pszApp
    , LPCOLESTR pszObj)
    {
    if (NULL!=m_pObj->m_hDlg)
        {
        TCHAR       szTemp[128];

       #ifdef WIN32ANSI
        char        szObj[80];
        WideCharToMultiByte(CP_ACP, 0, pszObj, -1, szObj, 80
            , NULL, NULL);
        wsprintf(szTemp, SZPOLYFRAMETITLE, szObj);
       #else
        wsprintf(szTemp, SZPOLYFRAMETITLE, pszObj);
       #endif
        SetWindowText(m_pObj->m_hDlg, szTemp);
        }

    return NOERROR;
    }

/*
 * CImpIOleObject::Close
 *
 * Purpose:
 *  Forces the object to close down its user interface and unload.
 *
 * Parameters:
 *  dwSaveOption    DWORD describing the circumstances under which
 *                  the object is being saved and closed.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::Close(DWORD dwSaveOption)
    {
    HWND        hWnd;
    BOOL        fSave=FALSE;

    hWnd=m_pObj->m_hDlg;

    //If object is dirty and we're asked to save, save it and close.
    if (OLECLOSE_SAVEIFDIRTY==dwSaveOption && m_pObj->m_fDirty)
        fSave=TRUE;

    /*
     * If asked to prompt, only do so if dirty, then if we get a
     * YES, save as usual and close.  On NO, just close.  On
     * CANCEL return OLE_E_PROMPTSAVECANCELLED.
     */
    if (OLECLOSE_PROMPTSAVE==dwSaveOption && m_pObj->m_fDirty)
        {
        TCHAR       szTitle[20];
        TCHAR       szTemp[80];
        UINT        uRet;

        lstrcpy(szTitle, m_pObj->String(IDS_CLOSECAPTION));
        lstrcpy(szTemp, m_pObj->String(IDS_CLOSEPROMPT));

        uRet=MessageBox(hWnd, szTemp, szTitle, MB_YESNOCANCEL);

        if (IDCANCEL==uRet)
            return ResultFromScode(OLE_E_PROMPTSAVECANCELLED);

        if (IDYES==uRet)
            fSave=TRUE;
        }

    if (fSave)
        {
        m_pObj->SendAdvise(OBJECTCODE_SAVEOBJECT);
        m_pObj->SendAdvise(OBJECTCODE_SAVED);
        }

    //We get directly here on OLECLOSE_NOSAVE.
    if (NULL!=hWnd)
        {
        //This hides the window and sends the appropriate notify.
        DoVerb(OLEIVERB_HIDE, NULL, NULL, -1, NULL, NULL);

        m_pObj->SendAdvise(OBJECTCODE_CLOSED);
        PostMessage(hWnd, POLYM_CLOSE, 0, 0L);
        }

    return NOERROR;
    }

/*
 * CImpIOleObject::DoVerb
 *
 * Purpose:
 *  Executes an object-defined action.
 *
 * Parameters:
 *  iVerb           LONG index of the verb to execute.
 *  pMSG            LPMSG describing the event causing the
 *                  activation.
 *  pActiveSite     LPOLECLIENTSITE to the site involved.
 *  lIndex          LONG the piece on which execution is happening.
 *  hWndParent      HWND of the window in which the object can play
 *                  in-place.
 *  pRectPos        LPRECT of the object in hWndParent where the
 *                  object can play in-place if desired.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::DoVerb(LONG iVerb, LPMSG pMSG
    , LPOLECLIENTSITE pActiveSite, LONG lIndex, HWND hWndParent
    , LPCRECT pRectPos)
    {
    HRESULT     hr;

    switch (iVerb)
        {
        case OLEIVERB_HIDE:
            if (NULL!=m_pObj->m_hDlg)
                {
                ShowWindow(m_pObj->m_hDlg, SW_HIDE);
                m_pObj->SendAdvise(OBJECTCODE_HIDEWINDOW);
                }

            break;

        case OLEIVERB_PRIMARY:
        case OLEIVERB_OPEN:
        case OLEIVERB_SHOW:
            /*
             * If we're not running, make sure we are.  In any
             * case, make the dialog visible and insure it has
             * the right parent now.
             */
            hr=NOERROR;
            if (NULL==m_pObj->m_hDlg)
                hr=m_pObj->m_pImpIRunnableObject->Run(NULL);

            if (FAILED(hr) || NULL==m_pObj->m_hDlg)
                return ResultFromScode(E_OUTOFMEMORY);

            ShowWindow(m_pObj->m_hDlg, SW_SHOW);
            SetFocus(m_pObj->m_hDlg);
            m_pObj->SendAdvise(OBJECTCODE_SHOWOBJECT);
            m_pObj->SendAdvise(OBJECTCODE_SHOWWINDOW);

            break;

        default:
            return ResultFromScode(OLEOBJ_S_INVALIDVERB);
        }

    return NOERROR;
    }

/*
 * CImpIOleObject::GetUserClassID
 *
 * Purpose:
 *  Used for linked objects, this returns the class ID of what end
 *  users think they are editing.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store the CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetUserClassID(LPCLSID pClsID)
    {
    /*
     * If you are not registered to handle data other than yourself,
     * then you can just return your class ID here.  If you are
     * registered as usable from Treat-As dialogs, then you need to
     * return the CLSID of what you are really editing.
     */

    *pClsID=CLSID_Polyline19;
    return NOERROR;
    }

/*
 * CImpIOleObject::SetExtent
 *
 * Purpose:
 *  Sets the size of the object in HIMETRIC units.  Since we're in
 *  a dialog, the size of the object in us is fixed, so we ignore
 *  this call.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect affected.
 *  pszl            LPSIZEL containing the new size.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::SetExtent(DWORD dwAspect
    , LPSIZEL pszl)
    {
    //Ignored:  no size change in the dialog.
    return NOERROR;
    }

/*
 * CImpIOleObject::GetExtent
 *
 * Purpose:
 *  Retrieves the size of the object in HIMETRIC units.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect requested
 *  pszl            LPSIZEL into which to store the size.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetExtent(DWORD dwAspect, LPSIZEL pszl)
    {
    //Delegate directly to IViewObject2::GetExtent
    return m_pObj->m_pImpIViewObject->GetExtent(dwAspect, -1
        , NULL, pszl);
    }

/*
 * CImpIOleObject::Advise
 * CImpIOleObject::Unadvise
 * CImpIOleObject::EnumAdvise
 *
 * Purpose:
 *  Advisory connection functions.
 */

STDMETHODIMP CImpIOleObject::Advise(LPADVISESINK pIAdviseSink
    , LPDWORD pdwConn)
    {
    if (NULL==m_pObj->m_pIOleAdviseHolder)
        {
        HRESULT     hr;

        hr=CreateOleAdviseHolder(&m_pObj->m_pIOleAdviseHolder);

        if (FAILED(hr))
            return hr;
        }

    return m_pObj->m_pIOleAdviseHolder->Advise(pIAdviseSink
        , pdwConn);
    }

STDMETHODIMP CImpIOleObject::Unadvise(DWORD dwConn)
    {
    if (NULL!=m_pObj->m_pIOleAdviseHolder)
        return m_pObj->m_pIOleAdviseHolder->Unadvise(dwConn);

    return ResultFromScode(E_FAIL);
    }

STDMETHODIMP CImpIOleObject::EnumAdvise(LPENUMSTATDATA *ppEnum)
    {
    if (NULL!=m_pObj->m_pIOleAdviseHolder)
        return m_pObj->m_pIOleAdviseHolder->EnumAdvise(ppEnum);

    return ResultFromScode(E_FAIL);
    }

//Methods not implemented or trivial

STDMETHODIMP CImpIOleObject::SetMoniker(DWORD dwWhich
    , LPMONIKER pmk)
    {
    return ResultFromScode(E_NOTIMPL);
    }

STDMETHODIMP CImpIOleObject::GetMoniker(DWORD dwAssign
    , DWORD dwWhich, LPMONIKER *ppmk)
    {
    return ResultFromScode(E_NOTIMPL);
    }

STDMETHODIMP CImpIOleObject::InitFromData(LPDATAOBJECT pIDataObject
    , BOOL fCreation, DWORD dw)
    {
    return ResultFromScode(E_NOTIMPL);
    }

STDMETHODIMP CImpIOleObject::GetClipboardData(DWORD dwReserved
    , LPDATAOBJECT *ppIDataObj)
    {
    return ResultFromScode(E_NOTIMPL);
    }

STDMETHODIMP CImpIOleObject::Update(void)
    {
    return NOERROR;
    }

STDMETHODIMP CImpIOleObject::IsUpToDate(void)
    {
    return NOERROR;
    }

STDMETHODIMP CImpIOleObject::SetColorScheme(LPLOGPALETTE pLP)
    {
    return ResultFromScode(E_NOTIMPL);
    }

//Methods implemented using registry helper functions in OLE.

STDMETHODIMP CImpIOleObject::EnumVerbs(LPENUMOLEVERB *ppEnum)
    {
    return OleRegEnumVerbs(m_pObj->m_clsID, ppEnum);
    }

STDMETHODIMP CImpIOleObject::GetUserType(DWORD dwForm
    , LPOLESTR *ppszType)
    {
    return OleRegGetUserType(m_pObj->m_clsID, dwForm, ppszType);
    }

STDMETHODIMP CImpIOleObject::GetMiscStatus(DWORD dwAspect
    , LPDWORD pdwStatus)
    {
    return OleRegGetMiscStatus(m_pObj->m_clsID, dwAspect
        , pdwStatus);
    }


IPERSTMI.CPP

/*
 * IPERSTMI.CPP
 * Polyline Component Chapter 19
 *
 * Implementation of the IPersistStreamInit interface exposed on the
 * Polyline object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIPersistStreamInit:CImpIPersistStreamInit
 * CImpIPersistStreamInit::~CImpIPersistStreamInit
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistStreamInit::CImpIPersistStreamInit(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPersistStreamInit::~CImpIPersistStreamInit(void)
    {
    return;
    }

/*
 * CImpIPersistStreamInit::QueryInterface
 * CImpIPersistStreamInit::AddRef
 * CImpIPersistStreamInit::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIPersistStreamInit.
 */

STDMETHODIMP CImpIPersistStreamInit::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistStreamInit::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistStreamInit::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistStreamInit::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the object represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 */

STDMETHODIMP CImpIPersistStreamInit::GetClassID(LPCLSID pClsID)
    {
    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this object since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 */

STDMETHODIMP CImpIPersistStreamInit::IsDirty(void)
    {
    return ResultFromScode(m_pObj->m_fDirty ? S_OK : S_FALSE);
    }

/*
 * CImpIPersistStreamInit::Load
 *
 * Purpose:
 *  Instructs the object to load itself from a previously saved
 *  IStreamInit that was handled by Save in another object lifetime.
 *  The seek pointer in this stream will be exactly the same as
 *  it was when Save was called, and this function must leave
 *  the seek pointer the same as it was on exit from Save, regardless
 *  of success or failure.  This function should not hold on to
 *  pIStream.
 *
 *  This function is called in lieu of IPersistStreamInit::InitNew
 *  when the object already has a persistent state.
 *
 * Parameters:
 *  pIStream        LPSTREAM from which to load.
 */

STDMETHODIMP CImpIPersistStreamInit::Load(LPSTREAM pIStream)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    HRESULT         hr;

    if (NULL==pIStream)
        return ResultFromScode(E_POINTER);

    //Read all the data into the POLYLINEDATA structure.
    hr=pIStream->Read(&pl, CBPOLYLINEDATA, &cb);

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        return hr;

    m_pObj->DataSet(&pl, TRUE, TRUE);
    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::Save
 *
 * Purpose:
 *  Saves the data for this object to an IStreamInit.  Be sure not
 *  to change the position of the seek pointer on entry to this
 *  function: the caller will assume that you write from the
 *  current offset.  Leave the stream's seek pointer at the end
 *  of the data written on exit.
 *
 * Parameters:
 *  pIStream        LPSTREAM in which to save our data.
 *  fClearDirty     BOOL indicating if this call should clear
 *                  the object's dirty flag (TRUE) or leave it
 *                  unchanged (FALSE).
 */

STDMETHODIMP CImpIPersistStreamInit::Save(LPSTREAM pIStream
    , BOOL fClearDirty)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    HRESULT         hr;

    if (NULL==pIStream)
        return ResultFromScode(E_POINTER);

    m_pObj->DataGet(&pl);

    hr=pIStream->Write(&pl, CBPOLYLINEDATA, &cb);

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        return ResultFromScode(STG_E_WRITEFAULT);

    if (fClearDirty)
        m_pObj->m_fDirty;

    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::GetSizeMax
 *
 * Purpose:
 *  Returns the size of the data we would write if Save was
 *  called right now.
 *
 * Parameters:
 *  pcbSize         ULARGE_INTEGER * in which to save the size
 *                  of the stream an immediate call to Save would
 *                  write.
 */

STDMETHODIMP CImpIPersistStreamInit::GetSizeMax(ULARGE_INTEGER
    *pcbSize)
    {
    if (NULL==pcbSize)
        return ResultFromScode(E_POINTER);

    ULISet32(*pcbSize, CBPOLYLINEDATA);
    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::InitNew
 *
 * Purpose:
 *  Informs the object that it is being created new instead of
 *  loaded from a persistent state.  This will be called in lieu
 *  of IPersistStreamInit::Load.
 *
 * Parameters:
 *  None
 */

STDMETHODIMP CImpIPersistStreamInit::InitNew(void)
    {
    //Nothing for us to do
    return NOERROR;
    }


IPERSTOR.CPP

/*
 * IPERSTOR.CPP
 * Polyline Component Chapter 19
 *
 * Implementation of the IPersistStorage interface exposed on the
 * Polyline object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIPersistStorage:CImpIPersistStorage
 * CImpIPersistStorage::~CImpIPersistStorage
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistStorage::CImpIPersistStorage(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    m_psState=PSSTATE_UNINIT;
    return;
    }

CImpIPersistStorage::~CImpIPersistStorage(void)
    {
    return;
    }

/*
 * CImpIPersistStorage::QueryInterface
 * CImpIPersistStorage::AddRef
 * CImpIPersistStorage::Release
 */

STDMETHODIMP CImpIPersistStorage::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistStorage::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistStorage::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistStorage::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the object represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, error code otherwise.
 */

STDMETHODIMP CImpIPersistStorage::GetClassID(LPCLSID pClsID)
    {
    if (PSSTATE_UNINIT==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this object since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 *
 */

STDMETHODIMP CImpIPersistStorage::IsDirty(void)
    {
    if (PSSTATE_UNINIT==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    return ResultFromScode(m_pObj->m_fDirty ? S_OK : S_FALSE);
    }

/*
 * CImpIPersistStorage::InitNew
 *
 * Purpose:
 *  Provides the object with the IStorage to hold on to while the
 *  object is running.  Here we initialize the structure of the
 *  storage and AddRef it for incremental access. This function will
 *  only be called once in the object's lifetime in lieu of Load.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE for the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::InitNew(LPSTORAGE pIStorage)
    {
    HRESULT     hr;

    if (PSSTATE_UNINIT!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    if (NULL==pIStorage)
        return ResultFromScode(E_POINTER);

    /*
     * The rules of IPersistStorage mean we hold onto the IStorage
     * and pre-create anything we'd need in Save(...,TRUE) for
     * low-memory situations.  For us this means creating our
     * "CONTENTS" stream and holding onto that IStream as
     * well as the IStorage here (requiring an AddRef call).
     */

    hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
        | STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE
        , 0, 0, &m_pObj->m_pIStream);

    if (FAILED(hr))
        return hr;

    //We expect that the client has called WriteClassStg
    WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
        , (*m_pObj->m_pST)[IDS_USERTYPE]);

    m_pObj->m_pIStorage=pIStorage;
    pIStorage->AddRef();

    m_psState=PSSTATE_SCRIBBLE;

    //CHAPTER19MOD
    //Initialize the cache as needed.
    m_pObj->m_pDefIPersistStorage->InitNew(pIStorage);
    //End CHAPTER19MOD

    return NOERROR;
    }

/*
 * CImpIPersistStorage::Load
 *
 * Purpose:
 *  Instructs the object to load itself from a previously saved
 *  IStorage that was handled by Save in another object lifetime.
 *  This function will only be called once in the object's lifetime
 *  in lieu of InitNew. The object should hold on to pIStorage here
 *  for incremental access and low-memory saves in Save.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE from which to load.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::Load(LPSTORAGE pIStorage)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    LPSTREAM        pIStream;
    HRESULT         hr;

    if (PSSTATE_UNINIT!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    if (NULL==pIStorage)
        return ResultFromScode(E_POINTER);

    //We don't check CLSID to remain compatible with other chapters.

    hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
        | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return ResultFromScode(STG_E_READFAULT);

    //Read all the data into the POLYLINEDATA structure.
    hr=pIStream->Read(&pl, CBPOLYLINEDATA, &cb);

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        {
        pIStream->Release();
        return hr;
        }

    /*
     * We don't call pIStream->Release here because we may need
     * it for a low-memory save in Save.  We also need to
     * hold onto a copy of pIStorage, meaning AddRef.
     */
    m_pObj->m_pIStream=pIStream;

    m_pObj->m_pIStorage=pIStorage;
    pIStorage->AddRef();

    //DataSet now internal on CPolyline
    m_pObj->DataSet(&pl, TRUE, TRUE);

    m_psState=PSSTATE_SCRIBBLE;

    //CHAPTER19MOD
    //We also need to tell the cache to load cached graphics
    m_pObj->m_pDefIPersistStorage->Load(pIStorage);
    //End CHAPTER19MOD

    return NOERROR;
    }

/*
 * CImpIPersistStorage::Save
 *
 * Purpose:
 *  Saves the data for this object to an IStorage which may
 *  or may not be the same as the one previously passed to
 *  Load, indicated with fSameAsLoad.  After this call we may
 *  not write into the storage again until SaveCompleted is
 *  called, although we may still read.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which to save our data.
 *  fSameAsLoad     BOOL indicating if this is the same pIStorage
 *                  that was passed to Load.  If TRUE, then the
 *                  object should write whatever it has *without
 *                  *using any extra memory* as this may be a low
 *                  memory save attempt.  That means that you must
 *                  not try to open or create streams.  If FALSE
 *                  you need to regenerate your whole storage
 *                  structure, being sure to also release any
 *                  pointers held from InitNew and Load.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::Save(LPSTORAGE pIStorage
    , BOOL fSameAsLoad)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    LPSTREAM        pIStream;
    HRESULT         hr;

    //Have to come here from scribble state.
    if (PSSTATE_SCRIBBLE!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    //Must have an IStorage if we're not in SameAsLoad
    if (NULL==pIStorage && !fSameAsLoad)
        return ResultFromScode(E_POINTER);

    /*
     * If we're saving to a new storage, create a new stream.
     * If fSameAsLoad it TRUE, then we write to the
     * stream we already allocated.  We should NOT depends on
     * pIStorage with fSameAsLoad is TRUE.
     */
    if (fSameAsLoad)
        {
        LARGE_INTEGER   li;

        /*
         * Use pre-allocated streams to avoid failures due
         * to low-memory conditions.  Be sure to reset the
         * stream pointer if you used this stream before!!
         */
        pIStream=m_pObj->m_pIStream;
        LISet32(li, 0);
        pIStream->Seek(li, STREAM_SEEK_SET, NULL);

        //This matches the Release below.
        pIStream->AddRef();
        }
    else
        {
        hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
            | STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE
            , 0, 0, &pIStream);

        if (FAILED(hr))
            return hr;

        //Only do this with new storages.
        WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
            , (*m_pObj->m_pST)[IDS_USERTYPE]);
        }

    //DataGet now internal on CPolyline
    m_pObj->DataGet(&pl);

    hr=pIStream->Write(&pl, CBPOLYLINEDATA, &cb);
    pIStream->Release();

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        return ResultFromScode(STG_E_WRITEFAULT);

    m_psState=PSSTATE_ZOMBIE;

    //CHAPTER19MOD
    //We also need to tell the cache to save cached graphics
    m_pObj->m_pDefIPersistStorage->Save(pIStorage, fSameAsLoad);
    //End CHAPTER19MOD

    return NOERROR;
    }

/*
 * CImpIPersistStorage::SaveCompleted
 *
 * Purpose:
 *  Notifies the object that the storage in pIStorage has been
 *  completely saved now.  This is called when the user of this
 *  object wants to save us in a completely new storage, and if
 *  we normally hang on to the storage we have to reinitialize
 *  ourselves here for this new one that is now complete.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE of the new storage in which we live.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::SaveCompleted(LPSTORAGE pIStorage)
    {
    HRESULT     hr;
    LPSTREAM    pIStream;

    //Must be called in no-scribble or hands-off state
    if (!(PSSTATE_ZOMBIE==m_psState || PSSTATE_HANDSOFF==m_psState))
        return ResultFromScode(E_UNEXPECTED);

    //If we're coming from Hands-Off, we'd better get a storage
    if (NULL==pIStorage && PSSTATE_HANDSOFF==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    /*
     * If pIStorage is NULL, then we don't need to do anything
     * since we already have all the pointers we need for Save.
     * Otherwise we have to release any held pointers and
     * reinitialize them from pIStorage.
     */

    if (NULL!=pIStorage)
        {
        hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
            | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0
            , &pIStream);

        if (FAILED(hr))
            return hr;

        if (NULL!=m_pObj->m_pIStream)
            m_pObj->m_pIStream->Release();

        m_pObj->m_pIStream=pIStream;

        if (NULL!=m_pObj->m_pIStorage)
            m_pObj->m_pIStorage->Release();

        m_pObj->m_pIStorage=pIStorage;
        m_pObj->m_pIStorage->AddRef();
        }

    //Change state back to scribble.
    m_psState=PSSTATE_SCRIBBLE;

    //CHAPTER19MOD
    m_pObj->m_pDefIPersistStorage->SaveCompleted(pIStorage);
    //End CHAPTER19MOD

    return NOERROR;
    }

/*
 * CImpIPersistStorage::HandsOffStorage
 *
 * Purpose:
 *  Instructs the object that another agent is interested in having
 *  total access to the storage we might be hanging on to from
 *  InitNew or SaveCompleted.  In this case we must release our hold
 *  and await another call to SaveCompleted before we have a hold
 *  again.  Therefore we cannot read or write after this call until
 *  SaveCompleted.
 *
 *  Situations where this might happen arise in compound document
 *  scenarios where this object might be in-place active but the
 *  application wants to rename and commit the root storage.
 *  Therefore we are asked to close our hold, let the container
 *  party on the storage, then call us again later to tell us the
 *  new storage we can hold.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::HandsOffStorage(void)
    {
    /*
     * Must come from scribble or no-scribble.  A repeated call
     * to HandsOffStorage is an unexpected error (bug in client).
     */
    if (PSSTATE_UNINIT==m_psState || PSSTATE_HANDSOFF==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    //Release held pointers
    if (NULL!=m_pObj->m_pIStream)
        {
        m_pObj->m_pIStream->Release();
        m_pObj->m_pIStream=NULL;
        }

    if (NULL!=m_pObj->m_pIStorage)
        {
        m_pObj->m_pIStorage->Release();
        m_pObj->m_pIStorage=NULL;
        }

    m_psState=PSSTATE_HANDSOFF;

    //CHAPTER19MOD
    m_pObj->m_pDefIPersistStorage->HandsOffStorage();
    //End CHAPTER19MOD

    return NOERROR;
    }


IPOLYLIN.CPP

/*
 * IPOLYLIN.CPP
 * Polyline Component Chapter 19
 *
 * Implementation of the IPolyline10 interface that we expose on the
 * CPolyline object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIPolyline:CImpIPolyline
 * CImpIPolyline::~CImpIPolyline
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPolyline::CImpIPolyline(PCPolyline pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPolyline::~CImpIPolyline(void)
    {
    return;
    }

/*
 * CImpIPolyline::QueryInterface
 * CImpIPolyline::AddRef
 * CImpIPolyline::Release
 */

STDMETHODIMP CImpIPolyline::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPolyline::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPolyline::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPolyline::Init
 *
 * Purpose:
 *  Instantiates a polyline window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags
 *  uID             UINT ID to associate with this window
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_OUTOFMEMORY
 */

STDMETHODIMP CImpIPolyline::Init(HWND hWndParent, LPRECT pRect
    , DWORD dwStyle, UINT uID)
    {
    SCODE           sc;

    m_pObj->m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY
        , SZCLASSPOLYLINE, SZCLASSPOLYLINE, dwStyle, pRect->left
        , pRect->top, pRect->right-pRect->left
        , pRect->bottom-pRect->top, hWndParent, (HMENU)uID
        , m_pObj->m_hInst, m_pObj);

    sc=(NULL!=m_pObj->m_hWnd) ? S_OK : E_OUTOFMEMORY;
    return ResultFromScode(sc);
    }

/*
 * CImpIPolyline::New
 *
 * Purpose:
 *  Cleans out and reinitializes the data to defaults.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::New(void)
    {
    PPOLYLINEDATA   ppl=&m_pObj->m_pl;
    UINT            i;
    RECT            rc;

    ppl->wVerMaj=VERSIONMAJOR;
    ppl->wVerMin=VERSIONMINOR;

    //Our rectangle is the size of our window's client area.
    //CHAPTER19MOD
    if (NULL!=m_pObj->m_hWnd)
        {
        GetClientRect(m_pObj->m_hWnd, &rc);
        RECTTORECTS(rc, ppl->rc);
        }
    else
        {
        SetRect(&rc, 0, 0, 100, 100);       //Something reasonable
        RECTTORECTS(rc, ppl->rc);
        }
    //End CHAPTER19MOD

    //Clean out the POLYLINEDATA structure and repaint the window.
    for (i=0; i< CPOLYLINEPOINTS; i++)
        {
        ppl->rgpt[i].x=0;
        ppl->rgpt[i].y=0;
        }

    ppl->cPoints      =0;
    ppl->rgbBackground=GetSysColor(COLOR_WINDOW);
    ppl->rgbLine      =GetSysColor(COLOR_WINDOWTEXT);
    ppl->iLineStyle   =PS_SOLID;

    //CHAPTER19MOD
    //This is now conditional since we may not yet have a window.
    if (NULL!=m_pObj->m_hWnd)
        {
        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        UpdateWindow(m_pObj->m_hWnd);
        m_pObj->m_fDirty=TRUE;
        }

    m_pObj->SendAdvise(OBJECTCODE_DATACHANGED);
    //End CHAPTER19MOD

    return NOERROR;
    }

/*
 * CImpIPolyline::Undo
 *
 * Purpose:
 *  Reverses previous actions in a Polyline.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         S_OK if we can Undo more, S_FALSE otherwise.
 */

STDMETHODIMP CImpIPolyline::Undo(void)
    {
    SCODE           sc;

    //Decrement the number of active points and repaint.
    if (m_pObj->m_pl.cPoints > 0)
        {
        m_pObj->m_pl.cPoints--;
        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        UpdateWindow(m_pObj->m_hWnd);
        }

    if (NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnPointChange();
        }

    //CHAPTER19MOD
    m_pObj->SendAdvise(OBJECTCODE_DATACHANGED);
    //End CHAPTER19MOD

    //Return if we can undo any more.
    sc=(0!=m_pObj->m_pl.cPoints) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * CImpIPolyline::Window
 *
 * Purpose:
 *  Returns the window handle associated with this polyline.
 *
 * Parameters:
 *  phWnd           HWND * in which to return the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CImpIPolyline::Window(HWND *phWnd)
    {
    *phWnd=m_pObj->m_hWnd;
    return NOERROR;
    }

/*
 * CImpIPolyline::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::RectGet(LPRECT pRect)
    {
    RECT            rc;
    POINT           pt;

    //CHAPTER19MOD
    if (NULL==m_pObj->m_hWnd)
        {
        SetRect(pRect, 0, 0, 150, 150); //Reasonable default
        return NOERROR;
        }
    //End CHAPTER19MOD

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_pObj->m_hWnd, &rc);
    pt.x=rc.left;
    pt.y=rc.top;
    ScreenToClient(GetParent(m_pObj->m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return NOERROR;
    }

/*
 * CImpIPolyline::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return NOERROR;
    }

/*
 * CImpIPolyline::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT            cx, cy;
    RECT            rc;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetRect(&rc, 0, 0, cx, cy);
    RECTTORECTS(rc, m_pObj->m_pl.rc);

    //CHPATER19MOD
    if (NULL!=m_pObj->m_hWnd)
        {
        SetWindowPos(m_pObj->m_hWnd, NULL, pRect->left, pRect->top
            , cx, cy, SWP_NOZORDER);
        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        }
    //End CHAPTER19MOD

    if (fNotify && NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnSizeChange();
        }

    return NOERROR;
    }

/*
 * CImpIPolyline::SizeSet
 *
 * Purpose:
 *  Sets a new size for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT            cx, cy;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    //CHAPTER19MOD
    if (NULL!=m_pObj->m_hWnd)
        {
        SetWindowPos(m_pObj->m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
            , SWP_NOMOVE | SWP_NOZORDER);
        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        }
    //End CHAPTER19MOD

    if (fNotify && NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnSizeChange();
        }

    return NOERROR;
    }

/*
 * CImpIPolyline::ColorSet
 *
 * Purpose:
 *  Changes for background or line color in the Polyline
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color to use.
 *  pcrPrev         COLORREF * in whch to store the
 *                  previous color.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::ColorSet(UINT iColor, COLORREF cr
    , COLORREF *pcrPrev)
    {
    COLORREF        crRet;

    if (NULL==pcrPrev)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    switch (iColor)
        {
        case POLYLINECOLOR_BACKGROUND:
            crRet=m_pObj->m_pl.rgbBackground;
            m_pObj->m_pl.rgbBackground=cr;
            break;

        case POLYLINECOLOR_LINE:
            crRet=m_pObj->m_pl.rgbLine;
            m_pObj->m_pl.rgbLine=cr;
            break;
        }

    //If the color changed, repaint
    if (crRet!=cr)
        {
        if (NULL!=m_pObj->m_pAdv)
            {
            m_pObj->m_fDirty=TRUE;
            m_pObj->m_pAdv->OnColorChange();
            }

        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        UpdateWindow(m_pObj->m_hWnd);

        //CHAPTER19MOD
        m_pObj->SendAdvise(OBJECTCODE_DATACHANGED);
        //End CHAPTER19MOD
        }

    *pcrPrev=crRet;
    return NOERROR;
    }

/*
 * CImpIPolyline::ColorGet
 *
 * Purpose:
 *  Retrieves one of the colors currently in use by the Polyline.
 *
 * Parameters:
 *  iColor          UINT identifying the color of interest.
 *  pcr             COLORREF * in which to return the color.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::ColorGet(UINT iColor, COLORREF *pcr)
    {
    COLORREF        crRet;

    if (NULL==pcr)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    crRet=(POLYLINECOLOR_BACKGROUND==iColor)
        ? m_pObj->m_pl.rgbBackground : m_pObj->m_pl.rgbLine;

    *pcr=crRet;
    return NOERROR;
    }

/*
 * CImpIPolyline::LineStyleSet
 *
 * Purpose:
 *  Changes the line style in use by the Polyline
 *
 * Parameters:
 *  iStyle          UINT style of the line to use.
 *  piPrev          UINT * in which to store the previous style.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::LineStyleSet(UINT iStyle, UINT *piPrev)
    {
    UINT            uRet;

    uRet=(UINT)m_pObj->m_pl.iLineStyle;

    if (NULL==piPrev)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    //Validate the line style
    if (PS_SOLID==iStyle || PS_DASH==iStyle || PS_DOT==iStyle
        || PS_DASHDOT==iStyle || PS_DASHDOTDOT==iStyle)
        {
        m_pObj->m_pl.iLineStyle=iStyle;

        if (uRet!=(UINT)m_pObj->m_pl.iLineStyle)
            {
            if (NULL!=m_pObj->m_pAdv)
                {
                m_pObj->m_fDirty=TRUE;
                m_pObj->m_pAdv->OnLineStyleChange();
                }

            InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
            UpdateWindow(m_pObj->m_hWnd);

            //CHAPTER19MOD
            m_pObj->SendAdvise(OBJECTCODE_DATACHANGED);
            //End CHAPTER19MOD
            }
        }

    *piPrev=uRet;
    return NOERROR;
    }

/*
 * CImpIPolyline::LineStyleGet
 *
 * Purpose:
 *  Retrieves the current line style in use in the Polyline
 *
 * Parameters:
 *  piStyle         UINT * in which to store the style.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::LineStyleGet(UINT *piStyle)
    {
    if (NULL==piStyle)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    *piStyle=m_pObj->m_pl.iLineStyle;
    return NOERROR;
    }


IRUNOBJ.CPP

/*
 * IRUNOBJ.CPP
 * Polyline Component Chapter 19
 *
 * Implementation of the IRunnableObject interface which allows
 * us to enter the "running" state which means Polyline's dialog
 * box is created, but not visible.  This is necessary so that
 * containers can ask for our extents before calling DoVerb.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIRunnableObject::CImpIRunnableObject
 * CImpIRunnableObject::~CImpIRunnableObject
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIRunnableObject::CImpIRunnableObject(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIRunnableObject::~CImpIRunnableObject(void)
    {
    return;
    }

/*
 * CImpIRunnableObject::QueryInterface
 * CImpIRunnableObject::AddRef
 * CImpIRunnableObject::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIRunnableObject.
 */

STDMETHODIMP CImpIRunnableObject::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIRunnableObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIRunnableObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIRunnableObject::GetRunningClass
 *
 * Purpose:
 *  Returns the CLSID of the object.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store the CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIRunnableObject::GetRunningClass(LPCLSID pClsID)
    {
    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

/*
 * CImpIRunnableObject::Run
 *
 * Purpose:
 *  Run an object in the given bind context, that is, put the object
 *  into the running state.
 *
 * Parameters:
 *  pBindCtx        LPBINDCTX of the bind context to use.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIRunnableObject::Run(LPBINDCTX pBindCtx)
    {
    /*
     * Create the dialog but not show it.  This function is called
     * from OleRun which is called from within OleCreate.  So when
     * a container creates this object, we'll create the dialog
     * which is a basis for the object's extents (as returned from
     * IViewObject2::GetExtent.  Our IOleObject::DoVerb will later
     * note that the dialog is already created and will just make
     * it visible and not create it anew.
     *
     * Note that the dialog is unowned--it acts like an overlapped
     * window.
     */

    if (NULL!=m_pObj->m_hDlg)
        return NOERROR;

    //This stores the dialog handle in m_pObj.
    CreateDialogParam(m_pObj->Instance()
        , MAKEINTRESOURCE(IDD_EDITDIALOG), NULL, PolyDlgProc
        , (LPARAM)m_pObj);

    if (NULL==m_pObj->m_hDlg)
        return ResultFromScode(E_OUTOFMEMORY);

    return NOERROR;
    }

/*
 * CImpIRunnableObject::IsRunning
 *
 * Purpose:
 *  Answers whether an object is currently in the running state.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            Indicates the running state of the object.
 */

STDMETHODIMP_(BOOL) CImpIRunnableObject::IsRunning(void)
    {
    return (NULL!=m_pObj->m_hDlg);
    }

/*
 * CImpIRunnableObject::LockRunning
 *
 * Purpose:
 *  Locks an already running object into the running state or unlocks
 *  it from such a state.
 *
 * Parameters:
 *  fLock               BOOL indicating lock (TRUE) or unlock
 *                      (FALSE)
 *  fLastUnlockCloses   BOOL indicating if the last call to this
 *                      function with fLock==FALSE closes the
 *                      object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIRunnableObject::LockRunning(BOOL fLock
    , BOOL fLastUnlockCloses)
    {
    //Calling CoLockObjectExternal is all we have to do here.
    return CoLockObjectExternal(this, fLock, fLastUnlockCloses);
    }

/*
 * CImpIRunnableObject::SetContainedObject
 *
 * Purpose:
 *  Informs the object (embedded object) that it is inside a
 *  compound document container.
 *
 * Parameters:
 *  fContained      BOOL indicating if the object is now contained.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIRunnableObject::SetContainedObject(BOOL fContained)
    {
    //We can ignore this.
    return NOERROR;
    }


IVIEWOBJ.CPP

/*
 * IVIEWOBJ.CPP
 * Polyline Component Chapter 19
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIViewObject::CImpIViewObject
 * CImpIViewObject::~CImpIViewObject
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIViewObject::CImpIViewObject(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIViewObject::~CImpIViewObject(void)
    {
    return;
    }

/*
 * CImpIViewObject::QueryInterface
 * CImpIViewObject::AddRef
 * CImpIViewObject::Release
 *
 * Purpose:
 *  IUnknown members for CImpIViewObject object.
 */

STDMETHODIMP CImpIViewObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIViewObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIViewObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIViewObject::Draw
 *
 * Purpose:
 *  Draws the object on the given hDC specifically for the requested
 *  aspect, device, and within the appropriate bounds.
 *
 * Parameters:
 *  dwAspect        DWORD aspect to draw.
 *  lindex          LONG index of the piece to draw.
 *  pvAspect        LPVOID for extra information, always NULL.
 *  ptd             DVTARGETDEVICE * containing device
 *                  information.
 *  hICDev          HDC containing the IC for the device.
 *  hDC             HDC on which to draw.
 *  pRectBounds     LPCRECTL describing the rectangle in which
 *                  to draw.
 *  pRectWBounds    LPCRECTL describing the placement rectangle
 *                  if part of what you draw is another metafile.
 *  pfnContinue     Function to call periodically during
 *                  long repaints.
 *  dwContinue      DWORD extra information to pass to the
 *                  pfnContinue.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::Draw(DWORD dwAspect, LONG lindex
    , LPVOID pvAspect, DVTARGETDEVICE *ptd, HDC hICDev
    , HDC hDC, LPCRECTL pRectBounds, LPCRECTL pRectWBounds
    , BOOL (CALLBACK *pfnContinue) (DWORD), DWORD dwContinue)
    {
    RECT            rc;
    POLYLINEDATA    pl;
    PPOLYLINEDATA   ppl=&m_pObj->m_pl;

    RECTFROMRECTL(rc, *pRectBounds);

    //Delegate iconic and printed representations.
    if (!((DVASPECT_CONTENT | DVASPECT_THUMBNAIL) & dwAspect))
        {
        return m_pObj->m_pDefIViewObject->Draw(dwAspect, lindex
            , pvAspect, ptd, hICDev, hDC, pRectBounds, pRectWBounds
            , pfnContinue, dwContinue);
        }

    /*
     * If we're asked to draw a frozen aspect, use the data from
     * a copy we made in IViewObject::Freeze.  Otherwise use the
     * current data.
     */
    if (dwAspect & m_pObj->m_dwFrozenAspects)
        {
        //Point to the data to actually use.
        if (DVASPECT_CONTENT==dwAspect)
            ppl=&m_pObj->m_plContent;
        else
            ppl=&m_pObj->m_plThumbnail;
        }

    //Make a copy so we can modify it (and not worry about threads)
    memcpy(&pl, ppl, CBPOLYLINEDATA);

    /*
     * If we're going to a printer, check if it's color capable.
     * if not, then use black on white for this figure.
     */
    if (NULL!=hICDev)
        {
        if (GetDeviceCaps(hICDev, NUMCOLORS) <= 2)
            {
            pl.rgbBackground=RGB(255, 255, 255);
            pl.rgbLine=RGB(0, 0, 0);
            }
        }

    m_pObj->Draw(hDC, FALSE, TRUE, &rc, &pl);
    return NOERROR;
    }

/*
 * CImpIViewObject::GetColorSet
 *
 * Purpose:
 *  Retrieves the color palette used by the object.
 *
 * Parameters:
 *  dwAspect        DWORD aspect of interest.
 *  lindex          LONG piece of interest.
 *  pvAspect        LPVOID with extra information, always NULL.
 *  ptd             DVTARGETDEVICE * containing device info.
 *  hICDev          HDC containing the IC for the device.
 *  ppColorSet      LPLOGPALETTE * into which to return the
 *                  pointer to the palette in this color set.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::GetColorSet(DWORD dwDrawAspect
    , LONG lindex, LPVOID pvAspect, DVTARGETDEVICE *ptd
    , HDC hICDev, LPLOGPALETTE *ppColorSet)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIViewObject::Freeze
 *
 * Purpose:
 *  Freezes the view of a particular aspect such that data
 *  changes do not affect the view.
 *
 * Parameters:
 *  dwAspect        DWORD aspect to freeze.
 *  lindex          LONG piece index under consideration.
 *  pvAspect        LPVOID for further information, always NULL.
 *  pdwFreeze       LPDWORD in which to return the key.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::Freeze(DWORD dwAspect, LONG lindex
    , LPVOID pvAspect, LPDWORD pdwFreeze)
    {
    //Delegate anything for ICON or DOCPRINT aspects
    if (!((DVASPECT_CONTENT | DVASPECT_THUMBNAIL) & dwAspect))
        {
        return m_pObj->m_pDefIViewObject->Freeze(dwAspect, lindex
            , pvAspect, pdwFreeze);
        }

    if (dwAspect & m_pObj->m_dwFrozenAspects)
        {
        *pdwFreeze=dwAspect + FREEZE_KEY_OFFSET;
        return ResultFromScode(VIEW_S_ALREADY_FROZEN);
        }

    m_pObj->m_dwFrozenAspects |= dwAspect;

    /*
     * For whatever aspects become frozen, make a copy of the data.
     * Later when drawing, if such a frozen aspect is requested,
     * we'll draw from this data rather than from our current data.
     */
    if (DVASPECT_CONTENT & dwAspect)
        {
        memcpy(&m_pObj->m_plContent, &m_pObj->m_pl
            , CBPOLYLINEDATA);
        }

    if (DVASPECT_THUMBNAIL & dwAspect)
        {
        memcpy(&m_pObj->m_plThumbnail, &m_pObj->m_pl
            , CBPOLYLINEDATA);
        }

    if (NULL!=pdwFreeze)
        *pdwFreeze=dwAspect + FREEZE_KEY_OFFSET;

    return NOERROR;
    }

/*
 * CImpIViewObject::Unfreeze
 *
 * Purpose:
 *  Thaws an aspect frozen in ::Freeze.  We expect that a container
 *  will redraw us after freezing if necessary, so we don't send
 *  any sort of notification here.
 *
 * Parameters:
 *  dwFreeze        DWORD key returned from ::Freeze.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::Unfreeze(DWORD dwFreeze)
    {
    DWORD       dwAspect=dwFreeze - FREEZE_KEY_OFFSET;

    //Delegate anything for ICON or DOCPRINT aspects
    if (!((DVASPECT_CONTENT | DVASPECT_THUMBNAIL) & dwAspect))
        m_pObj->m_pDefIViewObject->Unfreeze(dwFreeze);

    //The aspect to unfreeze is in the key.
    m_pObj->m_dwFrozenAspects &= ~(dwAspect);

    /*
     * Since we always kept our current data up to date, we don't
     * have to do anything thing here like requesting data again.
     * Because we removed dwAspect from m_dwFrozenAspects, Draw
     * will again use the current data.
     */

    return NOERROR;
    }

/*
 * CImpIViewObject::SetAdvise
 *
 * Purpose:
 *  Provides an advise sink to the view object enabling
 *  notifications for a specific aspect.
 *
 * Parameters:
 *  dwAspects       DWORD describing the aspects of interest.
 *  dwAdvf          DWORD containing advise flags.
 *  pIAdviseSink    LPADVISESINK to notify.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::SetAdvise(DWORD dwAspects, DWORD dwAdvf
    , LPADVISESINK pIAdviseSink)
    {
    //Pass anything with DVASPECT_ICON or _DOCPRINT to the handler.
    if (!((DVASPECT_CONTENT | DVASPECT_THUMBNAIL) & dwAspects))
        {
        m_pObj->m_pDefIViewObject->SetAdvise(dwAspects, dwAdvf
            , pIAdviseSink);
        }

    //We continue because dwAspects may have more than one in it.
    if (NULL!=m_pObj->m_pIAdviseSink)
        m_pObj->m_pIAdviseSink->Release();

    m_pObj->m_pIAdviseSink=pIAdviseSink;
    m_pObj->m_dwAdviseAspects=dwAspects;
    m_pObj->m_dwAdviseFlags=dwAdvf;

    if (NULL!=m_pObj->m_pIAdviseSink)
        m_pObj->m_pIAdviseSink->AddRef();

    return NOERROR;
    }

/*
 * CImpIViewObject::GetAdvise
 *
 * Purpose:
 *  Returns the last known IAdviseSink seen by ::SetAdvise.
 *
 * Parameters:
 *  pdwAspects      LPDWORD in which to store the last
 *                  requested aspects.
 *  pdwAdvf         LPDWORD in which to store the last
 *                  requested flags.
 *  ppIAdvSink      LPADVISESINK * in which to store the
 *                  IAdviseSink.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::GetAdvise(LPDWORD pdwAspects
    , LPDWORD pdwAdvf, LPADVISESINK *ppAdvSink)
    {
    if (NULL!=ppAdvSink)
        {
        *ppAdvSink=m_pObj->m_pIAdviseSink;
        m_pObj->m_pIAdviseSink->AddRef();
        }

    if (NULL!=pdwAspects)
        *pdwAspects=m_pObj->m_dwAdviseAspects;

    if (NULL!=pdwAdvf)
        *pdwAdvf=m_pObj->m_dwAdviseFlags;

    return NOERROR;
    }

/*
 * CImpIViewObject::GetExtent
 *
 * Purpose:
 *  Retrieves the extents of the object's display.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect of interest.
 *  lindex          LONG index of the piece of interest.
 *  ptd             DVTARGETDEVICE * with device information.
 *  pszl            LPSIZEL to the structure in which to return
 *                  the extents.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::GetExtent(DWORD dwAspect, LONG lindex
    , DVTARGETDEVICE *ptd, LPSIZEL pszl)
    {
    RECT            rc;

    if (!(DVASPECT_CONTENT & dwAspect))
        {
        return m_pObj->m_pDefIViewObject->GetExtent(dwAspect
            , lindex, ptd, pszl);
        }

    m_pObj->m_pImpIPolyline->RectGet(&rc);
    m_pObj->RectConvertMappings(&rc, FALSE);

    pszl->cx=rc.right-rc.left;
    pszl->cy=rc.bottom-rc.top;

    return NOERROR;
    }


POLYLINE.CPP

/*
 * POLYLINE.CPP
 * Polyline Component Chapter 19
 *
 * Implementation of the CPolyline class that we expose as a
 * component object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CPolyline:CPolyline
 * CPolyline::~CPolyline
 *
 * Constructor Parameters:
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 *  pfnDestroy      PFNDESTROYED to call when an object is
 *                  destroyed.
 *  hInst           HINSTANCE of the application we're in.
 */

CPolyline::CPolyline(LPUNKNOWN pUnkOuter, PFNDESTROYED pfnDestroy
    , HINSTANCE hInst)
    {
    m_hWnd=NULL;
    m_hInst=hInst;

    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pfnDestroy=pfnDestroy;
    m_fDirty=FALSE;

    m_pImpIPolyline=NULL;
    m_pImpIConnPtCont=NULL;

    m_pAdv=NULL;
    m_pConnPt=NULL;

    m_pST  =NULL;
    m_cf   =0;
    //CHAPTER19MOD
    m_clsID=CLSID_Polyline19;
    //End CHAPTER19MOD

    m_pIStorage=NULL;
    m_pIStream =NULL;

    m_pImpIPersistStorage=NULL;
    m_pImpIPersistStreamInit=NULL;

    m_pImpIDataObject   =NULL;
    m_pIDataAdviseHolder=NULL;

    //CHAPTER19MOD
    m_pDefIUnknown       =NULL;
    m_pDefIDataObject    =NULL;
    m_pDefIViewObject    =NULL;
    m_pDefIPersistStorage=NULL;

    m_pIOleAdviseHolder =NULL;
    m_pImpIOleObject    =NULL;
    m_pIOleClientSite   =NULL;
    m_pImpIViewObject   =NULL;
    m_pIAdviseSink      =NULL;

    m_dwFrozenAspects   =0;
    m_dwAdviseAspects   =0;
    m_dwAdviseFlags     =0;

    m_pImpIRunnableObject=NULL;
    m_hDlg=NULL;
    //End CHAPTER19MOD

    return;
    }

CPolyline::~CPolyline(void)
    {
    //CHAPTER19MOD
    LPUNKNOWN       pIUnknown=this;

    if (NULL!=m_pUnkOuter)
        pIUnknown=m_pUnkOuter;
    //End CHAPTER19MOD

    if (NULL!=m_pST)
        delete m_pST;

    //CHAPTER19MOD
    if (NULL!=m_hDlg)
        DestroyWindow(m_hDlg);

    /*
     * In aggregation, release cached pointers but
     * AddRef the controlling unknown first.  The
     * extra reference count protects us from reentrancy.
     */

    m_cRef++;

    pIUnknown->AddRef();
    pIUnknown->AddRef();
    pIUnknown->AddRef();

    ReleaseInterface(m_pDefIViewObject);
    ReleaseInterface(m_pDefIDataObject);
    ReleaseInterface(m_pDefIPersistStorage);

    m_cRef--;

    //Cached pointer rules do not apply to IUnknown
    ReleaseInterface(m_pDefIUnknown);

    ReleaseInterface(m_pIAdviseSink);
    ReleaseInterface(m_pIOleClientSite);
    ReleaseInterface(m_pIOleAdviseHolder);

    DeleteInterfaceImp(m_pImpIOleObject);
    DeleteInterfaceImp(m_pImpIViewObject);
    DeleteInterfaceImp(m_pImpIRunnableObject);
    //End CHAPTER19MOD

    ReleaseInterface(m_pIDataAdviseHolder);
    DeleteInterfaceImp(m_pImpIDataObject);

    DeleteInterfaceImp(m_pImpIPersistStreamInit);
    DeleteInterfaceImp(m_pImpIPersistStorage);
    ReleaseInterface(m_pIStream);
    ReleaseInterface(m_pIStorage);

    DeleteInterfaceImp(m_pImpIConnPtCont);
    DeleteInterfaceImp(m_pImpIPolyline);

    ReleaseInterface(m_pAdv);
    ReleaseInterface(m_pConnPt);

    return;
    }

/*
 * CPolyline::Init
 *
 * Purpose:
 *  Performs any intiailization of a CPolyline that's prone to
 *  failure that we also use internally before exposing the
 *  object outside this DLL.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CPolyline::Init(void)
    {
    LPUNKNOWN       pIUnknown=this;
    //CHAPTER19MOD
    HRESULT         hr;
    //End CHAPTER19MOD

    if (NULL!=m_pUnkOuter)
        pIUnknown=m_pUnkOuter;

    m_pST=new CStringTable(m_hInst);

    if (!m_pST->Init(IDS_POLYLINEMIN, IDS_POLYLINEMAX))
        return FALSE;

    m_cf=RegisterClipboardFormat(SZPOLYLINECLIPFORMAT);

    m_pImpIPersistStorage=new CImpIPersistStorage(this, pIUnknown);

    if (NULL==m_pImpIPersistStorage)
        return FALSE;

    m_pImpIPersistStreamInit=new CImpIPersistStreamInit(this
        , pIUnknown);

    if (NULL==m_pImpIPersistStreamInit)
        return FALSE;

    m_pImpIPolyline=new CImpIPolyline(this, pIUnknown);

    if (NULL==m_pImpIPolyline)
        return FALSE;

    m_pImpIConnPtCont=new CImpIConnPtCont(this, pIUnknown);

    if (NULL==m_pImpIConnPtCont)
        return FALSE;

    m_pConnPt=new CConnectionPoint(this);

    if (NULL==m_pConnPt)
        return FALSE;

    m_pConnPt->AddRef();    //Reversed in destructor

    m_pImpIDataObject=new CImpIDataObject(this, pIUnknown);

    if (NULL==m_pImpIDataObject)
        return FALSE;

    //CHAPTER19MOD
    //FORMATETC arrays not needed:  using Def Handler

    m_pImpIOleObject=new CImpIOleObject(this, pIUnknown);

    if (NULL==m_pImpIOleObject)
        return FALSE;

    m_pImpIViewObject=new CImpIViewObject(this, pIUnknown);

    if (NULL==m_pImpIViewObject)
        return FALSE;

    m_pImpIRunnableObject=new CImpIRunnableObject(this, pIUnknown);

    if (NULL==m_pImpIRunnableObject)
        return FALSE;

    /*
     * We're sitting at ref count 0 and the next call will AddRef a
     * few times and Release a few times.  This insures we don't
     * delete ourselves prematurely.
     */
    m_cRef++;

    //Aggregate OLE's cache for IOleCache* interfaces.
    hr=CreateDataCache(pIUnknown, CLSID_Polyline19
        , IID_IUnknown, (PPVOID)&m_pDefIUnknown);

    if (FAILED(hr))
        return FALSE;

    /*
     * NOTE:  The spec specifically states that any interfaces
     * besides IUnknown that we obtain on an aggregated object
     * should be Released immediately after we QueryInterface for
     * them because the QueryInterface will AddRef us, and since
     * we would not release these interfaces until we were
     * destroyed, we'd never go away because we'd never get a zero
     * ref count.
     */

    //Now try to get other interfaces to which we delegate
    hr=m_pDefIUnknown->QueryInterface(IID_IViewObject2
        , (PPVOID)&m_pDefIViewObject);

    if (FAILED(hr))
        return FALSE;

    pIUnknown->Release();

    hr=m_pDefIUnknown->QueryInterface(IID_IDataObject
        , (PPVOID)&m_pDefIDataObject);

    if (FAILED(hr))
        return FALSE;

    pIUnknown->Release();

    hr=m_pDefIUnknown->QueryInterface(IID_IPersistStorage
        , (PPVOID)&m_pDefIPersistStorage);

    if (FAILED(hr))
        return FALSE;

    pIUnknown->Release();

    m_cRef--;
    m_pImpIPolyline->New();
    //End CHAPTER19MOD

    return TRUE;
    }

/*
 * CPolyline::QueryInterface
 * CPolyline::AddRef
 * CPolyline::Release
 *
 * Purpose:
 *  IUnknown members for CPolyline object.
 */

STDMETHODIMP CPolyline::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IConnectionPointContainer==riid)
        *ppv=m_pImpIConnPtCont;

    if (IID_IPolyline10==riid)
        *ppv=m_pImpIPolyline;

    if (IID_IPersistStorage==riid)
        *ppv=m_pImpIPersistStorage;

    if (IID_IPersist==riid || IID_IPersistStream==riid
        || IID_IPersistStreamInit==riid)
        *ppv=m_pImpIPersistStreamInit;

    if (IID_IDataObject==riid)
        *ppv=m_pImpIDataObject;

    //CHAPTER19MOD
    if (IID_IOleObject==riid)
        *ppv=m_pImpIOleObject;

    if (IID_IViewObject==riid || IID_IViewObject2==riid)
        *ppv=m_pImpIViewObject;

    if (IID_IRunnableObject==riid)
        *ppv=m_pImpIRunnableObject;

    //Use the default handler's cache.
    if (IID_IOleCache==riid || IID_IOleCache2==riid)
        return m_pDefIUnknown->QueryInterface(riid, ppv);
    //End CHAPTER19MOD

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CPolyline::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPolyline::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0L;
    }

/*
 * CPolyline::RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device (MM_TEXT) or
 *  HIMETRIC to the other.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  fToDevice       BOOL TRUE to convert from HIMETRIC to device,
 *                  FALSE to convert device to HIMETRIC.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectConvertMappings(LPRECT pRect, BOOL fToDevice)
    {
    HDC      hDC;
    int      iLpx, iLpy;

    if (NULL==pRect)
        return;

    hDC=GetDC(NULL);
    iLpx=GetDeviceCaps(hDC, LOGPIXELSX);
    iLpy=GetDeviceCaps(hDC, LOGPIXELSY);
    ReleaseDC(NULL, hDC);

    if (fToDevice)
        {
        pRect->left=MulDiv(iLpx, pRect->left, HIMETRIC_PER_INCH);
        pRect->top =MulDiv(iLpy, pRect->top , HIMETRIC_PER_INCH);

        pRect->right =MulDiv(iLpx, pRect->right, HIMETRIC_PER_INCH);
        pRect->bottom=MulDiv(iLpy, pRect->bottom,HIMETRIC_PER_INCH);
        }
    else
        {
        pRect->left=MulDiv(pRect->left, HIMETRIC_PER_INCH, iLpx);
        pRect->top =MulDiv(pRect->top , HIMETRIC_PER_INCH, iLpy);

        pRect->right =MulDiv(pRect->right, HIMETRIC_PER_INCH, iLpx);
        pRect->bottom=MulDiv(pRect->bottom,HIMETRIC_PER_INCH, iLpy);
        }

    return;
    }

/*
 * CPolyline::DataSet
 *
 * Purpose:
 *  Sets the current data in this Polyline to a given structure.
 *
 * Parameters:
 *  pplIn           PPOLYLINEDATA to initialize to.
 *  fSizeToData     BOOL indicating if we're to size to the data
 *                  or scale it.
 *  fNotify         BOOL indicating if we're to send an advise
 *                  on this change.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::DataSet(PPOLYLINEDATA pplIn
    , BOOL fSizeToData, BOOL fNotify)
    {
    RECT            rc;

    /*
     * Copy the structure in pplIn and repaint to reflect the
     * new point set.  Note that unlike the RectSet message, we
     * do no scaling, assuming that the rect in the structure
     * is appropriate for the data.
     */

    if (NULL==pplIn)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    m_pl=*pplIn;
    m_fDirty=TRUE;

    //CHAPTER19MOD
    //Calling OnDataChange happens below through SendAdvise.
    //End CHAPTER19MOD

    /*
     * If we're scaling the window to fit the data, then use
     * RectSet passing our current rectangle as the new one.
     * That makes sure that the data won't change but that the
     * window is resized.
     */

    if (fSizeToData)
        {
        POINT       pt;

        /*
         * Get our offset in the parent window so we can RectSet
         * to the right place since RectSet expects rectangle in
         * parent coordinates and we get it in client coordinates.
         */
        if (NULL!=m_hWnd)
            {
            GetWindowRect(m_hWnd, &rc);
            pt.x=rc.left;
            pt.y=rc.top;
            ScreenToClient(GetParent(m_hWnd), &pt);
            RECTSTORECT(m_pl.rc, rc);
            OffsetRect(&rc, pt.x, pt.y);

            //This will also cause a repaint.
            m_pImpIPolyline->RectSet(&rc, fNotify);
            }
        }
    else
        {
        if (NULL!=m_hWnd)
            {
            //Make sure we're updated.
            InvalidateRect(m_hWnd, NULL, TRUE);
            UpdateWindow(m_hWnd);
            }
        }

    //CHAPTER19MOD
    SendAdvise(OBJECTCODE_DATACHANGED);
    //End CHAPTER19MOD

    return NOERROR;
    }

/*
 * CPolyline::DataGet
 *
 * Purpose:
 *  Retrieves the Polyline's current data.
 *
 * Parameters:
 *  pplIn           PPOLYLINEDATA into which we copy the data.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::DataGet(PPOLYLINEDATA pplIn)
    {
    if (NULL==pplIn)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    *pplIn=m_pl;
    return NOERROR;
    }

/*
 * CPolyline::RenderNative
 *
 * Purpose:
 *  Retrieves the Polyline's data in a global memory handle.
 *
 * Parameters:
 *  phMem           HGLOBAL * in which to store the handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::RenderNative(HGLOBAL *phMem)
    {
    HGLOBAL         hMem;
    PPOLYLINEDATA   ppl;
    HRESULT         hr=ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    if (NULL==phMem)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, CBPOLYLINEDATA);

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        hr=DataGet(ppl);

        GlobalUnlock(hMem);

        if (FAILED(hr))
            {
            GlobalFree(hMem);
            hMem=NULL;
            }
        }

    *phMem=hMem;
    return hr;
    }

/*
 * CPolyline::RenderBitmap
 *
 * Purpose:
 *  Creates a bitmap image of the current Polyline.
 *
 * Parameters:
 *  phBmp           HBITMAP * in which to return the bitmap.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::RenderBitmap(HBITMAP *phBmp)
    {
    HDC             hDC;
    HDC             hMemDC;
    HBITMAP         hBmp;
    RECT            rc;
    HGDIOBJ         hObj;

    if (NULL==phBmp)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    //Render a bitmap the size of the current rectangle.
    hDC=GetDC(m_hWnd);
    hMemDC=CreateCompatibleDC(hDC);

    GetClientRect(m_hWnd, &rc);
    hBmp=CreateCompatibleBitmap(hDC, rc.right, rc.bottom);

    if (NULL!=hBmp)
        {
        //Draw the POLYLINEDATA into the bitmap.
        hObj=SelectObject(hMemDC, hBmp);
        //CHAPTER19MOD
        Draw(hMemDC, FALSE, TRUE, &rc, NULL);
        //End CHAPTER19MOD
        SelectObject(hMemDC, hObj);
        }

    DeleteDC(hMemDC);
    ReleaseDC(m_hWnd, hDC);

    *phBmp=hBmp;
    return NOERROR;
    }

//RenderMetafile not necessary--now part of RenderMetafilePict.

/*
 * CPolyline::RenderMetafilePict
 *
 * Purpose:
 *  Renders the current Polyline into a METAFILEPICT structure in
 *  global memory.
 *
 * Parameters:
 *  phMem           HGLOBAL * in which to return the
 *                  METAFILEPICT.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::RenderMetafilePict(HGLOBAL *phMem)
    {
    HGLOBAL         hMem;
    HMETAFILE       hMF;
    LPMETAFILEPICT  pMF;
    RECT            rc;
    HDC             hDC;

    if (NULL==phMem)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    //Create a memory metafile and return its handle.
    hDC=(HDC)CreateMetaFile(NULL);

    if (NULL==hDC)
        return ResultFromScode(STG_E_MEDIUMFULL);

    SetMapMode(hDC, MM_ANISOTROPIC);
    GetClientRect(m_hWnd, &rc);
    SetWindowOrgEx(hDC, 0, 0, NULL);
    SetWindowExtEx(hDC, rc.right, rc.bottom, NULL);

    //CHAPTER19MOD
    Draw(hDC, TRUE, TRUE, &rc, NULL);
    //End CHAPTER19MOD
    hMF=CloseMetaFile(hDC);

    if (NULL==hMF)
        return ResultFromScode(STG_E_MEDIUMFULL);

    //Allocate the METAFILEPICT structure.
    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
        , sizeof(METAFILEPICT));

    if (NULL==hMem)
        {
        DeleteMetaFile(hMF);
        return ResultFromScode(E_FAIL);
        }

    /*
     * Global lock only fails in PMODE if the selector is invalid
     * (like it was discarded) or references a 0 length segment,
     * neither of which can happen here.
     */
    pMF=(LPMETAFILEPICT)GlobalLock(hMem);

    pMF->hMF=hMF;
    pMF->mm=MM_ANISOTROPIC;

    //Insert the extents in MM_HIMETRIC units.
    GetClientRect(m_hWnd, &rc);
    RectConvertMappings(&rc, FALSE);
    pMF->xExt=rc.right;
    pMF->yExt=rc.bottom;

    GlobalUnlock(hMem);

    *phMem=hMem;
    return NOERROR;
    }

//CHAPTER19MOD
/*
 * CPolyline::Instance
 *
 * Purpose:
 *  Provides access to the module instance.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HINSTANCE       The instance handle of the module.
 */

HINSTANCE CPolyline::Instance(void)
    {
    return m_hInst;
    }

/*
 * CPolyline::String
 *
 * Purpose:
 *  Provides string lookup in the Polyline string table.
 *
 * Parameters:
 *  uID             UINT of the string to return.
 *
 * Return Value:
 *  LPTSTR          Pointer to the string.
 */

LPTSTR CPolyline::String(UINT uID)
    {
    return PSZ(uID);
    }

/*
 * CPolyline::SendAdvise
 *
 * Purpose:
 *  Calls the appropriate IOleClientSite or IAdviseSink member
 *  function for various events such as closure, renaming, etc.
 *
 * Parameters:
 *  uCode           UINT OBJECTCODE_* identifying the notification.
 *
 * Return Value:
 *  None
 */

void CPolyline::SendAdvise(UINT uCode)
    {
    DWORD       dwAspect=DVASPECT_CONTENT | DVASPECT_THUMBNAIL;

    switch (uCode)
        {
        case OBJECTCODE_SAVED:
            if (NULL!=m_pIOleAdviseHolder)
                m_pIOleAdviseHolder->SendOnSave();
            break;

        case OBJECTCODE_CLOSED:
            if (NULL!=m_pIOleAdviseHolder)
                m_pIOleAdviseHolder->SendOnClose();

            break;

        case OBJECTCODE_RENAMED:
            //Call IOleAdviseHolder::SendOnRename (later)
            break;

        case OBJECTCODE_SAVEOBJECT:
            if (m_fDirty && NULL!=m_pIOleClientSite)
                m_pIOleClientSite->SaveObject();

            m_fDirty=FALSE;
            break;

        case OBJECTCODE_DATACHANGED:
            m_fDirty=TRUE;

            //No flags are necessary here.
            if (NULL!=m_pIDataAdviseHolder)
                {
                m_pIDataAdviseHolder->SendOnDataChange
                    (m_pImpIDataObject, 0, 0);
                }

            if (NULL!=m_pIAdviseSink
                & (dwAspect & m_dwAdviseAspects))
                {
                m_pIAdviseSink->OnViewChange(dwAspect
                    & m_dwAdviseAspects, 0);
                }

            break;

        case OBJECTCODE_SHOWWINDOW:
            if (NULL!=m_pIOleClientSite)
                m_pIOleClientSite->OnShowWindow(TRUE);

            break;

        case OBJECTCODE_HIDEWINDOW:
            if (NULL!=m_pIOleClientSite)
                m_pIOleClientSite->OnShowWindow(FALSE);

            break;

        case OBJECTCODE_SHOWOBJECT:
            if (NULL!=m_pIOleClientSite)
                m_pIOleClientSite->ShowObject();

            break;
        }

    return;
    }

//End CHAPTER19MOD


POLYLINE.H

/*
 * POLYLINE.H
 * Polyline Component Chapter 19
 *
 * Definitions and function prototypes
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _POLYLINE_H_
#define _POLYLINE_H_

#define INC_CLASSLIB
#define INC_CONTROLS
//CHAPTER19MOD
#define INC_OLEUI
#define CHAPTER19
//CHAPTER19MOD
#include <inole.h>
#include <ipoly10.h>
#include "resource.h"

//Classname
#define SZCLASSPOLYLINE             TEXT("polyline")

//Stream Name that holds the data
#define SZSTREAM                    OLETEXT("CONTENTS")

//CHAPTER19MOD
#define SZPOLYFRAMETITLE            TEXT("Polyline Figure in %s")

//Magic number to add to aspects returned from IViewObject::Freeze
#define FREEZE_KEY_OFFSET           0x0723
//End CHAPTER19MOD

#define HIMETRIC_PER_INCH           2540
#define CCHPATHMAX                  256

//Window extra bytes and offsets
#define CBPOLYLINEWNDEXTRA          (sizeof(LONG))
#define PLWL_STRUCTURE              0

//DLLPOLY.CPP
int PASCAL LibMain(HINSTANCE, WORD, WORD, LPSTR);

//This class factory object creates Polyline objects.

class CPolylineClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;

    public:
        CPolylineClassFactory(void);
        ~CPolylineClassFactory(void);

        //IUnknown members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP         CreateInstance(LPUNKNOWN, REFIID
                                 , PPVOID);
        STDMETHODIMP         LockServer(BOOL);
    };

typedef CPolylineClassFactory *PCPolylineClassFactory;

//POLYWIN.CPP
LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM, LPARAM);

//CHAPTER19MOD
#ifdef WIN32
#define PROP_POINTER    TEXT("Pointer")
#else
#define PROP_SELECTOR   "Selector"
#define PROP_OFFSET     "Offset"
#endif

BOOL APIENTRY PolyDlgProc(HWND, UINT, WPARAM, LPARAM);
//End CHAPTER19MOD

//Forward class references
class CImpIPolyline;
typedef class CImpIPolyline *PCImpIPolyline;

class CImpIConnPtCont;
typedef CImpIConnPtCont *PCImpIConnPtCont;

class CConnectionPoint;
typedef CConnectionPoint *PCConnectionPoint;

class CImpIPersistStorage;
typedef class CImpIPersistStorage *PCImpIPersistStorage;

class CImpIPersistStreamInit;
typedef class CImpIPersistStreamInit *PCImpIPersistStreamInit;

class CImpIDataObject;
typedef class CImpIDataObject *PCImpIDataObject;

//CHAPTER19MOD
class CImpIOleObject;
typedef class CImpIOleObject *PCImpIOleObject;

class CImpIViewObject;
typedef class CImpIViewObject *PCImpIViewObject;

class CImpIRunnableObject;
typedef class CImpIRunnableObject *PCImpIRunnableObject;

//FORMATETC array sizes no longer necessary.
//End CHAPTER19MOD

//POLYLINE.CPP
class CPolyline : public IUnknown
    {
    friend LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM
        , LPARAM);

    friend class CImpIPolyline;
    friend class CImpIConnPtCont;
    friend class CConnectionPoint;
    friend class CImpIPersistStorage;
    friend class CImpIPersistStreamInit;
    friend class CImpIDataObject;

    //CHAPTER19MOD
    friend BOOL APIENTRY PolyDlgProc(HWND, UINT, WPARAM, LPARAM);
    friend class CImpIOleObject;
    friend class CImpIViewObject;
    friend class CImpIRunnableObject;
    //End CHAPTER19MOD

    protected:
        HWND            m_hWnd;
        HINSTANCE       m_hInst;

        ULONG           m_cRef;         //Object reference count
        LPUNKNOWN       m_pUnkOuter;    //Controlling Unknown
        PFNDESTROYED    m_pfnDestroy;   //Function called on closure
        BOOL            m_fDirty;       //Have we changed?
        POLYLINEDATA    m_pl;           //Our actual data

        //Contained interfaces
        PCImpIPolyline          m_pImpIPolyline;
        PCImpIConnPtCont        m_pImpIConnPtCont;
        PCImpIPersistStorage    m_pImpIPersistStorage;
        PCImpIPersistStreamInit m_pImpIPersistStreamInit;
        PCImpIDataObject        m_pImpIDataObject;

        /*
         * Connected sink:  only one per object.  We get this
         * through our connection point.
         */
        IPolylineAdviseSink10  *m_pAdv;
        PCConnectionPoint       m_pConnPt;

        PCStringTable   m_pST;          //Object strings
        UINT            m_cf;           //Object clipboard format
        CLSID           m_clsID;        //Current CLSID

        //We have to hold these for IPersistStorage::Save
        LPSTORAGE       m_pIStorage;
        LPSTREAM        m_pIStream;

        LPDATAADVISEHOLDER  m_pIDataAdviseHolder;

        //CHAPTER19MOD
        //FORMATETC arrays no longer necessary.

        //These are default handler interfaces we use
        LPUNKNOWN           m_pDefIUnknown;
        LPVIEWOBJECT2       m_pDefIViewObject;
        LPPERSISTSTORAGE    m_pDefIPersistStorage;
        LPDATAOBJECT        m_pDefIDataObject;

        //Implemented and used interfaces
        PCImpIOleObject     m_pImpIOleObject;       //Implemented
        LPOLEADVISEHOLDER   m_pIOleAdviseHolder;    //Used

        LPOLECLIENTSITE     m_pIOleClientSite;      //Used

        PCImpIViewObject    m_pImpIViewObject;      //Implemented
        LPADVISESINK        m_pIAdviseSink;         //Used
        DWORD               m_dwFrozenAspects;      //Freeze
        DWORD               m_dwAdviseAspects;      //SetAdvise
        DWORD               m_dwAdviseFlags;        //SetAdvise

        POLYLINEDATA        m_plContent;            //For freezing
        POLYLINEDATA        m_plThumbnail;          //For freezing

        PCImpIRunnableObject m_pImpIRunnableObject; //Implemented
        HWND                m_hDlg;                 //Editing window
        //End CHAPTER19MOD

    protected:
        void      PointScale(LPRECT, LPPOINTS, BOOL);
        //CHAPTER19MOD
        void      Draw(HDC, BOOL, BOOL, LPRECT, PPOLYLINEDATA);
        HINSTANCE Instance(void);
        LPTSTR    String(UINT);
        void      SendAdvise(UINT);
        //End CHAPTER19MOD
        void      RectConvertMappings(LPRECT, BOOL);

        /*
         * These members pulled from IPolyline now serve as a
         * central store for this functionality to be used from
         * other interfaces like IPersistStorage and IDataObject.
         * Other interfaces later may also use them.
         */
        STDMETHODIMP DataSet(PPOLYLINEDATA, BOOL, BOOL);
        STDMETHODIMP DataGet(PPOLYLINEDATA);

        STDMETHODIMP RenderNative(HGLOBAL *);
        STDMETHODIMP RenderBitmap(HBITMAP *);
        STDMETHODIMP RenderMetafilePict(HGLOBAL *);

    public:
        CPolyline(LPUNKNOWN, PFNDESTROYED, HINSTANCE);
        ~CPolyline(void);

        BOOL      Init(void);

        //Non-delegating object IUnknown
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
    };

typedef CPolyline *PCPolyline;

//CHAPTER19MOD
//Codes for CPolyline::SendAdvise
//......Code.....................Method called in CPolyline::SendAdvise
#define OBJECTCODE_SAVED       0 //IOleAdviseHolder::SendOnSave
#define OBJECTCODE_CLOSED      1 //IOleAdviseHolder::SendOnClose
#define OBJECTCODE_RENAMED     2 //IOleAdviseHolder::SendOnRename
#define OBJECTCODE_SAVEOBJECT  3 //IOleClientSite::SaveObject
#define OBJECTCODE_DATACHANGED 4 //IDataAdviseHolder::SendOnDataChange
#define OBJECTCODE_SHOWWINDOW  5 //IOleClientSite::OnShowWindow(TRUE)
#define OBJECTCODE_HIDEWINDOW  6 //IOleClientSite::OnShowWindow(FALSE)
#define OBJECTCODE_SHOWOBJECT  7 //IOleClientSite::ShowObject
//End CHAPTER19MOD

//Interface implementation contained in the Polyline.

class CImpIPolyline : public IPolyline10
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIPolyline(PCPolyline, LPUNKNOWN);
        ~CImpIPolyline(void);

        //IUnknown members.
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //Manipulation members:
        STDMETHODIMP Init(HWND, LPRECT, DWORD, UINT);
        STDMETHODIMP New(void);
        STDMETHODIMP Undo(void);
        STDMETHODIMP Window(HWND *);

        STDMETHODIMP RectGet(LPRECT);
        STDMETHODIMP SizeGet(LPRECT);
        STDMETHODIMP RectSet(LPRECT, BOOL);
        STDMETHODIMP SizeSet(LPRECT, BOOL);

        STDMETHODIMP ColorSet(UINT, COLORREF, COLORREF *);
        STDMETHODIMP ColorGet(UINT, COLORREF *);

        STDMETHODIMP LineStyleSet(UINT, UINT *);
        STDMETHODIMP LineStyleGet(UINT *);
    };

class CImpIConnPtCont : public IConnectionPointContainer
    {
    private:
        ULONG               m_cRef;      //Interface ref count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIConnPtCont(PCPolyline, LPUNKNOWN);
        ~CImpIConnPtCont(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(DWORD) AddRef(void);
        STDMETHODIMP_(DWORD) Release(void);

        //IConnectionPointContainer members
        STDMETHODIMP EnumConnectionPoints(IEnumConnectionPoints **);
    STDMETHODIMP FindConnectionPoint(REFIID, IConnectionPoint **);
    };

/*
 * This connection point will only allow one connection,
 * so it always returns the same connection key.  It also
 * doesn't bother to implement enumerations.
 */

#define CCONNMAX    1
#define ADVISEKEY   72388       //Arbitrary

class CConnectionPoint : public IConnectionPoint
    {
    private:
        ULONG           m_cRef;     //Object reference count
        PCPolyline      m_pObj;     //Containing object.

    public:
        CConnectionPoint(PCPolyline);
        ~CConnectionPoint(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IConnectionPoint members
        STDMETHODIMP GetConnectionInterface(IID *);
        STDMETHODIMP GetConnectionPointContainer
            (IConnectionPointContainer **);
        STDMETHODIMP Advise(LPUNKNOWN, DWORD *);
        STDMETHODIMP Unadvise(DWORD);
        STDMETHODIMP EnumConnections(IEnumConnections **);
    };

class CImpIPersistStorage : public IPersistStorage
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown
        PSSTATE             m_psState;   //Storage state

    public:
        CImpIPersistStorage(PCPolyline, LPUNKNOWN);
        ~CImpIPersistStorage(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP InitNew(LPSTORAGE);
        STDMETHODIMP Load(LPSTORAGE);
        STDMETHODIMP Save(LPSTORAGE, BOOL);
        STDMETHODIMP SaveCompleted(LPSTORAGE);
        STDMETHODIMP HandsOffStorage(void);
    };

class CImpIPersistStreamInit : public IPersistStreamInit
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIPersistStreamInit(PCPolyline, LPUNKNOWN);
        ~CImpIPersistStreamInit(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP Load(LPSTREAM);
        STDMETHODIMP Save(LPSTREAM, BOOL);
        STDMETHODIMP GetSizeMax(ULARGE_INTEGER *);
        STDMETHODIMP InitNew(void);
    };

//IDATAOBJ.CPP
class CImpIDataObject : public IDataObject
    {
    private:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIDataObject(PCPolyline, LPUNKNOWN);
        ~CImpIDataObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IDataObject members
        STDMETHODIMP GetData(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP GetDataHere(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP QueryGetData(LPFORMATETC);
        STDMETHODIMP GetCanonicalFormatEtc(LPFORMATETC,LPFORMATETC);
        STDMETHODIMP SetData(LPFORMATETC, LPSTGMEDIUM, BOOL);
        STDMETHODIMP EnumFormatEtc(DWORD, LPENUMFORMATETC *);
        STDMETHODIMP DAdvise(LPFORMATETC, DWORD, LPADVISESINK
            , DWORD *);
        STDMETHODIMP DUnadvise(DWORD);
        STDMETHODIMP EnumDAdvise(LPENUMSTATDATA *);
    };

//IENUMFE.CPP
class CEnumFormatEtc : public IEnumFORMATETC
    {
    private:
        ULONG           m_cRef;
        LPUNKNOWN       m_pUnkRef;
        ULONG           m_iCur;
        ULONG           m_cfe;
        LPFORMATETC     m_prgfe;

    public:
        CEnumFormatEtc(LPUNKNOWN, ULONG, LPFORMATETC);
        ~CEnumFormatEtc(void);

        //IUnknown members that delegate to m_pUnkRef.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IEnumFORMATETC members
        STDMETHODIMP Next(ULONG, LPFORMATETC, ULONG *);
        STDMETHODIMP Skip(ULONG);
        STDMETHODIMP Reset(void);
        STDMETHODIMP Clone(IEnumFORMATETC **);
    };

typedef CEnumFormatEtc *PCEnumFormatEtc;

//CHAPTER19MOD

class CImpIOleObject : public IOleObject
    {
    private:
        ULONG           m_cRef;
        PCPolyline      m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIOleObject(PCPolyline, LPUNKNOWN);
        ~CImpIOleObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IOleObject members
        STDMETHODIMP SetClientSite(LPOLECLIENTSITE);
        STDMETHODIMP GetClientSite(LPOLECLIENTSITE *);
        STDMETHODIMP SetHostNames(LPCOLESTR, LPCOLESTR);
        STDMETHODIMP Close(DWORD);
        STDMETHODIMP SetMoniker(DWORD, LPMONIKER);
        STDMETHODIMP GetMoniker(DWORD, DWORD, LPMONIKER *);
        STDMETHODIMP InitFromData(LPDATAOBJECT, BOOL, DWORD);
        STDMETHODIMP GetClipboardData(DWORD, LPDATAOBJECT *);
        STDMETHODIMP DoVerb(LONG, LPMSG, LPOLECLIENTSITE, LONG
                         , HWND, LPCRECT);
        STDMETHODIMP EnumVerbs(LPENUMOLEVERB *);
        STDMETHODIMP Update(void);
        STDMETHODIMP IsUpToDate(void);
        STDMETHODIMP GetUserClassID(CLSID *);
        STDMETHODIMP GetUserType(DWORD, LPOLESTR *);
        STDMETHODIMP SetExtent(DWORD, LPSIZEL);
        STDMETHODIMP GetExtent(DWORD, LPSIZEL);
        STDMETHODIMP Advise(LPADVISESINK, DWORD *);
        STDMETHODIMP Unadvise(DWORD);
        STDMETHODIMP EnumAdvise(LPENUMSTATDATA *);
        STDMETHODIMP GetMiscStatus(DWORD, DWORD *);
        STDMETHODIMP SetColorScheme(LPLOGPALETTE);
    };

//IVIEWOBJ.CPP
class CImpIViewObject : public IViewObject2
    {
    private:
        ULONG           m_cRef;
        PCPolyline      m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIViewObject(PCPolyline, LPUNKNOWN);
        ~CImpIViewObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IViewObject members
        STDMETHODIMP Draw(DWORD, LONG, LPVOID
            , DVTARGETDEVICE *, HDC, HDC, LPCRECTL
            , LPCRECTL, BOOL (CALLBACK *)(DWORD), DWORD);
        STDMETHODIMP GetColorSet(DWORD, LONG, LPVOID
            , DVTARGETDEVICE *, HDC, LPLOGPALETTE *);
        STDMETHODIMP Freeze(DWORD, LONG, LPVOID, LPDWORD);
        STDMETHODIMP Unfreeze(DWORD);
        STDMETHODIMP SetAdvise(DWORD, DWORD, LPADVISESINK);
        STDMETHODIMP GetAdvise(LPDWORD, LPDWORD, LPADVISESINK *);
        STDMETHODIMP GetExtent(DWORD, LONG, DVTARGETDEVICE *
            , LPSIZEL);
    };

class CImpIRunnableObject : public IRunnableObject
    {
    protected:
        ULONG           m_cRef;
        PCPolyline      m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIRunnableObject(PCPolyline, LPUNKNOWN);
        ~CImpIRunnableObject(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetRunningClass(LPCLSID);
        STDMETHODIMP Run(LPBINDCTX);
        STDMETHODIMP_(BOOL) IsRunning(void);
        STDMETHODIMP LockRunning(BOOL, BOOL);
        STDMETHODIMP SetContainedObject(BOOL);
    };
//End CHAPTER19MOD

#endif  //_POLYLINE_H_


POLYWIN.CPP

/*
 * POLYWIN.CPP
 * Polyline Component Chapter 19
 *
 * Window procedure for the polyline drawing window and support
 * functions.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * PolylineWndProc
 *
 * Purpose:
 *  Window procedure for the polyline drawing window.
 */

LRESULT APIENTRY PolylineWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCPolyline      ppl;
    PAINTSTRUCT     ps;
    HDC             hDC;
    POINTS          pt;
    RECT            rc;

    ppl=(PCPolyline)GetWindowLong(hWnd, PLWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppl=(PCPolyline)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PLWL_STRUCTURE, (LONG)ppl);

            //CHAPTER19MOD
            //Moved call to New() to Init.
            ppl->m_hWnd=hWnd;
            //End CHAPTER19MOD
            break;

        case WM_PAINT:
            hDC=BeginPaint(hWnd, &ps);
            //CHAPTER19MOD
            //Modified Draw a little
            GetClientRect(hWnd, &rc);
            ppl->Draw(hDC, FALSE, TRUE, &rc, NULL);
            //End CHAPTER19MOD
            EndPaint(hWnd, &ps);
            break;

        case WM_LBUTTONDOWN:
            //Stop if we are already at the limit.
            if (CPOLYLINEPOINTS==ppl->m_pl.cPoints)
                {
                MessageBeep(0);
                break;
                }

            //Convert the points into 0-32767 range
            GetClientRect(hWnd, &rc);
            pt=MAKEPOINTS(lParam);
            ppl->PointScale(&rc, &pt, FALSE);

            ppl->m_pl.rgpt[ppl->m_pl.cPoints++]=pt;

            //Draw the lines to this new point only.
            hDC=GetDC(hWnd);
            //CHAPTER19MOD
            ppl->Draw(hDC, FALSE, FALSE, &rc, NULL);
            //End CHAPTER19MOD
            ReleaseDC(hWnd, hDC);

            if (NULL!=ppl->m_pAdv)
                ppl->m_pAdv->OnPointChange();

            //CHAPTER19MOD
            //Notifications necessary to support compound documents.
            ppl->SendAdvise(OBJECTCODE_DATACHANGED);
            //End CHAPTER19MOD
            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPolyline::Draw
 *
 * Purpose:
 *  Paints the current line in the polyline window.
 *
 * Parameters:
 *  hDC             HDC to draw on, a metafile or printer DC.
 *  fMetafile       BOOL indicating if hDC is a metafile or not,
 *                  so we can avoid operations that RIP.
 *  fEntire         BOOL indicating if we should draw the entire
 *                  figure or not.
 *  pRect           LPRECT defining the bounds in which to draw.
 *  ppl             PPOLYLINEDATA to draw.  If NULL, use current.
 *
 * Return Value:
 *  None
 */

void CPolyline::Draw(HDC hDC, BOOL fMetafile, BOOL fEntire
    , LPRECT pRect, PPOLYLINEDATA ppl)
    {
    //CHAPTER19MOD
    HBRUSH          hBrush;
    HPEN            hPen;
    HGDIOBJ         hObj1, hObj2;
    UINT            i, j;
    int             nDC;
    POINTS          pt1,pt2;
    POINT           rgpt[CPOLYLINEPOINTS];

    if (NULL==ppl)
        ppl=&m_pl;

    nDC=SaveDC(hDC);

    for (i=0; i < ppl->cPoints; i++)
        {
        rgpt[i].x=ppl->rgpt[i].x;
        rgpt[i].y=ppl->rgpt[i].y;
        }

    //Printer and frozen differences handled in IViewObject::Draw

    hPen=CreatePen(ppl->iLineStyle, 1, ppl->rgbLine);
    hObj1=SelectObject(hDC, hPen);

    hBrush=CreateSolidBrush(ppl->rgbBackground);
    hObj2=SelectObject(hDC, hBrush);
    SetBkColor(hDC, ppl->rgbBackground);

    /*
     * Either draw the entire figure or just a single point.  The
     * entire figure also includes erasing the background completely,
     * since hDC may be a metafile DC.  Drawing a single point just
     * updates the figure for that new point.
     */
    if (fEntire || 0==ppl->cPoints)
        {
        //Erase the background for bitmaps and metafiles.
        SelectObject(hDC, GetStockObject(NULL_PEN));
        Rectangle(hDC, pRect->left, pRect->top, pRect->right+1
            , pRect->bottom+1);
        SelectObject(hDC, hPen);

        /*
         * If we are drawing the entire figure, then loop through
         * each point drawing a line to each successive point.
         */

        for (i=0; i < ppl->cPoints; i++)
            {
            for (j=i; j < ppl->cPoints; j++)
                {
                pt1.x=(short)rgpt[i].x;
                pt1.y=(short)rgpt[i].y;
                pt2.x=(short)rgpt[j].x;
                pt2.y=(short)rgpt[j].y;

                PointScale(pRect, &pt1, TRUE);
                PointScale(pRect, &pt2, TRUE);
                MoveToEx(hDC, pt1.x, pt1.y, NULL);
                LineTo(hDC, pt2.x, pt2.y);
                }
            }
        }
    else
        {
        /*
         * If we are only drawing the last point, just cycle once
         * through previous points.
         */

        //Get the last point entered in the array.
        j=ppl->cPoints-1;
        pt1.x=(short)rgpt[j].x;
        pt1.y=(short)rgpt[j].y;
        PointScale(pRect, &pt1, TRUE);

        for (i=0; i < j; i++)
            {
            pt2.x=(short)rgpt[i].x;
            pt2.y=(short)rgpt[i].y;
            PointScale(pRect, &pt2, TRUE);

            MoveToEx(hDC, pt1.x, pt1.y, NULL);
            LineTo(hDC, pt2.x, pt2.y);
            }
        }

    //If we have one point, draw a dot to indicate it's position.
    if (1==ppl->cPoints)
        {
        pt1.x=(short)rgpt[0].x;
        pt1.y=(short)rgpt[0].y;
        PointScale(pRect, &pt1, TRUE);
        SetPixel(hDC, pt1.x, pt1.y, m_pl.rgbLine);
        }

    SelectObject(hDC, hObj1);
    SelectObject(hDC, hObj2);
    DeleteObject(hBrush);
    DeleteObject(hPen);

    RestoreDC(hDC, nDC);
    return;
    //End CHAPTER19MOD
    }

/*
 * CPolyline::PointScale
 *
 * Purpose:
 *  Scales a point to or from a relative window coordinate to a
 *  0-32767 coordinate.
 *
 * Parameters:
 *  pRect           LPRECT of the window.
 *  ppt             LPPOINTS to convert
 *  fScaleToWindow  BOOL indicating direction of scaling.
 *
 * Return Value:
 *  None
 */

void CPolyline::PointScale(LPRECT pRect, LPPOINTS ppt
    , BOOL fScaleToWindow)
    {
    DWORD   cx, cy;

    //Window size
    cx=(DWORD)(pRect->right-pRect->left);
    cy=(DWORD)(pRect->bottom-pRect->top);

    //Prevent crashes
    if (0L==cx) cx=1;
    if (0L==cy) cy=1;

    //Must use DWORD to insure proper scaling.
    //CHAPTER19MOD
    /*
     * As an in-proc server we may not have a rectangle where the
     * top left was (0,0) which was always true when we drew to
     * a Polyline window.  But this may be a container's hDC in
     * which case we'd better place the points in the container's
     * rectangle.  That is, we have to add/subtract pRect->left
     * and ->top in these calculations.
     */

    if (fScaleToWindow)
        {
        ppt->x=pRect->left+(UINT)(((DWORD)ppt->x*cx) >> 15);
        ppt->y=pRect->top+(UINT)(((DWORD)ppt->y*cy)  >> 15);
        }
    else
        {
        ppt->x=(UINT)(((DWORD)(ppt->x - pRect->left) << 15)/cx);
        ppt->y=(UINT)(((DWORD)(ppt->y - pRect->top)  << 15)/cy);
        }
    //End CHAPTER19MOD

    return;
    }

//CHAPTER19MOD
/*
 * PolyDlgProc
 *
 * Purpose:
 *  Dialog procedure for a window in which to display the Polyline
 *  for editing.  This pretty much handles all editing functionality
 *  for the embedded object.
 */

BOOL APIENTRY PolyDlgProc(HWND hDlg, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCPolyline      ppl=NULL;
    HWND            hWnd;
    RECT            rc;
    POINT           pt;
    UINT            uID, uTemp;
    UINT            cx, cy;

   #ifdef WIN32
    ppl=(PCPolyline)GetProp(hDlg, PROP_POINTER);
   #else
    WORD            w1, w2;

    w1=(WORD)GetProp(hDlg, PROP_SELECTOR);
    w2=(WORD)GetProp(hDlg, PROP_OFFSET);

    ppl=(PCPolyline)MAKELP(w1, w2);
   #endif

    switch (iMsg)
        {
        case WM_INITDIALOG:
            ppl=(PCPolyline)lParam;
            ppl->m_hDlg=hDlg;

           #ifdef WIN32
            //Properties are 32-bits in Win32
            SetProp(hDlg, PROP_POINTER, (HANDLE)ppl);
           #else
            SetProp(hDlg, PROP_SELECTOR, (HANDLE)SELECTOROF(ppl));
            SetProp(hDlg, PROP_OFFSET,   (HANDLE)OFFSETOF(ppl));
           #endif

            //Create the Polyline to exactly cover the static rect.
            hWnd=GetDlgItem(hDlg, ID_POLYLINERECT);
            GetWindowRect(hWnd, &rc);
            SETPOINT(pt, rc.left, rc.top);
            ScreenToClient(hDlg, &pt);

            //Set the polyline just within the black frame
            SetRect(&rc, pt.x, pt.y, pt.x+(rc.right-rc.left)
                , pt.y+(rc.bottom-rc.top));
            InflateRect(&rc, -1, -1);

            //Try to create the window.
            ppl->m_pImpIPolyline->Init(hDlg, &rc, WS_CHILD | WS_VISIBLE
                , ID_POLYLINE);

            //Set the initial line style radiobutton.
            ppl->m_pImpIPolyline->LineStyleGet(&uTemp);
            CheckRadioButton(hDlg, ID_LINESOLID, ID_LINEDASHDOTDOT
                , uTemp+ID_LINEMIN);

            //Don't set focus--it takes it away from the container.
            return FALSE;

        case WM_SHOWWINDOW:
            if (LOWORD(wParam))
                {
                //Center the dialog on the screen
                cx=GetSystemMetrics(SM_CXSCREEN);
                cy=GetSystemMetrics(SM_CYSCREEN);
                GetWindowRect(hDlg, &rc);
                SetWindowPos(hDlg, NULL, (cx-(rc.right-rc.left))/2
                    , (cy-(rc.bottom-rc.top))/2, 0, 0, SWP_NOZORDER
                    | SWP_NOSIZE);

                //We didn't SetFocus from WM_INITDIALOG.  Do it now.
                SetFocus(GetDlgItem(hDlg, IDOK));
                }

            break;

        case WM_COMMAND:
            uID=LOWORD(wParam);

            switch (uID)
                {
                case IDOK:
                    //Close the dialog, but save first.
                    if (NULL!=ppl)
                        {
                        //IOleObject::Close does what we want
                        ppl->m_pImpIOleObject->Close
                            (OLECLOSE_SAVEIFDIRTY);
                        }

                    break;

                case ID_UNDO:
                    if (NULL!=ppl)
                        ppl->m_pImpIPolyline->Undo();
                    break;

                case ID_COLORLINE:
                case ID_COLORBACK:
                    if (NULL!=ppl)
                        {
                        UINT            i;
                        COLORREF        rgColors[16];
                        CHOOSECOLOR     cc;

                        //Invoke the color chooser for either color
                        uTemp=(ID_COLORBACK==uID)
                            ? POLYLINECOLOR_BACKGROUND
                            : POLYLINECOLOR_LINE;

                        for (i=0; i<16; i++)
                            rgColors[i]=RGB(0, 0, i*16);

                        memset(&cc, 0, sizeof(CHOOSECOLOR));
                        cc.lStructSize=sizeof(CHOOSECOLOR);
                        cc.lpCustColors=rgColors;
                        cc.hwndOwner=hDlg;
                        cc.Flags=CC_RGBINIT;
                        ppl->m_pImpIPolyline->ColorGet(uTemp
                            , &cc.rgbResult);

                        if (ChooseColor(&cc))
                            {
                            //rgColor is just some COLORREF pointer
                            ppl->m_pImpIPolyline->ColorSet(uTemp
                                , cc.rgbResult, rgColors);
                            }
                        }
                    break;

                case ID_LINESOLID:
                case ID_LINEDASH:
                case ID_LINEDOT:
                case ID_LINEDASHDOT:
                case ID_LINEDASHDOTDOT:
                    if (NULL!=ppl)
                        {
                        ppl->m_pImpIPolyline
                            ->LineStyleSet(uID-ID_LINEMIN, &uTemp);
                        }

                    break;
                }
            break;

        case WM_DESTROY:
           #ifdef WIN32
            RemoveProp(hDlg, PROP_POINTER);
           #else
            RemoveProp(hDlg, PROP_SELECTOR);
            RemoveProp(hDlg, PROP_OFFSET);
           #endif
            break;

        case WM_CLOSE:
            //This will do the IDOK handling, then send POLYM_CLOSE
            SendCommand(hDlg, IDOK, 0, NULL);
            break;

        case POLYM_CLOSE:
            ShowWindow(hDlg, SW_HIDE);
            ppl->SendAdvise(OBJECTCODE_HIDEWINDOW);
            break;
        }

    return FALSE;
    }

//End CHAPTER19MOD


RESOURCE.H

/*
 * RESOURCE.H
 * Polyline Component Chapter 19
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Object stringtable IDs
#define IDS_POLYLINEMIN     0
#define IDS_STORAGEFORMAT   0
#define IDS_USERTYPE        1

//CHAPTER19MOD
#define IDS_CLOSECAPTION    2
#define IDS_CLOSEPROMPT     3
#define IDS_POLYLINEMAX     3

#define IDR_ICON            1

#define IDD_EDITDIALOG      1

#define ID_UNDO             100
#define ID_COLORBACK        101
#define ID_COLORLINE        102
#define ID_GROUPCOLORS      103
#define ID_GROUPPREVIEW     104
#define ID_GROUPSTYLES      105
#define ID_GROUPFIGURE      106
#define ID_POLYLINERECT     107
#define ID_POLYLINE         108

#define ID_LINEMIN          200
#define ID_LINESOLID        200     //(ID_LINEMIN+PS_SOLID)
#define ID_LINEDASH         201     //(ID_LINEMIN+PS_DASH)
#define ID_LINEDOT          202     //(ID_LINEMIN+PS_DOT)
#define ID_LINEDASHDOT      203     //(ID_LINEMIN+PS_DASHDOT)
#define ID_LINEDASHDOTDOT   204     //(ID_LINEMIN+PS_DASHDOTDOT)

//Message to close the dialog
#define POLYM_CLOSE         (WM_USER+1000)

//End CHAPTER19MOD

#endif //_RESOURCE_H_


DLLASSIS.CPP

/*
 * DLLASSIS.CPP
 * Links Assistant Chapter 20
 *
 * DLL housing for the LinksAssistant component object for
 * container applications that use the Links dialog.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "dllassis.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

/*
 * LibMain(32)
 *
 * Purpose:
 *  Entry point conditionally compiled for Win32 and Windows
 *  3.1.  Provides the proper structure for each environment.
 */

#ifdef WIN32
BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    if (DLL_PROCESS_DETACH==ulReason)
        {
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    return TRUE;
    }
#else
int PASCAL LibMain(HINSTANCE hInstance, WORD wDataSeg
    , WORD cbHeapSize, LPSTR lpCmdLine)
    {
    if (0!=cbHeapSize)
        UnlockData(0);

    return (int)hInstance;
    }
#endif

/*
 * DllGetClassObject
 * DllCanUnloadNow
 *
 * Purpose:
 *  Standard exports for DLL component objects.
 */

HRESULT APIENTRY DllGetClassObject(REFCLSID rclsid
    , REFIID riid, PPVOID ppv)
    {
    if (CLSID_LinksAssistant!=rclsid)
        return ResultFromScode(E_FAIL);

    if (IID_IUnknown!=riid && IID_IClassFactory!=riid)
        return ResultFromScode(E_NOINTERFACE);

    *ppv=new CLinksClassFactory();

    if (NULL==*ppv)
        return ResultFromScode(E_OUTOFMEMORY);

    ((LPUNKNOWN)*ppv)->AddRef();
    g_cObj++;
    return NOERROR;
    }

STDAPI DllCanUnloadNow(void)
    {
    SCODE   sc;

    sc=(0L==g_cObj && 0L==g_cLock) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the object to call when it gets destroyed.
 *  Since we're in a DLL we only track the number of objects here
 *  letting DllCanUnloadNow take care of the rest.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;
    return;
    }

/*
 * CLinksClassFactory::CLinksClassFactory
 * CLinksClassFactory::~CLinksClassFactory
 */

CLinksClassFactory::CLinksClassFactory(void)
    {
    m_cRef=0L;
    return;
    }

CLinksClassFactory::~CLinksClassFactory(void)
    {
    return;
    }

/*
 * CLinksClassFactory::QueryInterface
 * CLinksClassFactory::AddRef
 * CLinksClassFactory::Release
 */

STDMETHODIMP CLinksClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CLinksClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CLinksClassFactory::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    ObjectDestroyed();
    return 0;
    }

/*
 * CLinksClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a Links Assistant object that supports the
 *  IOleUILinkContainer interface for helping container do the
 *  Links Dialog.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we
 *                  are being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise contains
 *                  E_NOINTERFACE if we cannot support the
 *                  requested interface.
 */

STDMETHODIMP CLinksClassFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCLinks             pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //We don't support aggregation
    if (NULL!=pUnkOuter)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    pObj=new CLinks(ObjectDestroyed);

    if (NULL==pObj)
        return hr;

    hr=pObj->QueryInterface(riid, ppvObj);

    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

/*
 * CLinksClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the DLL.  If the lock
 *  count goes to zero and there are no objects, the DLL is allowed
 *  to unload.  See DllCanUnloadNow.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CLinksClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        g_cLock--;

    return NOERROR;
    }


DLLASSIS.H

/*
 * DLLASSIST.H
 * Links Assistant Chapter 20
 *
 * Definitions, classes, and prototypes for a DLL that provides
 * CLSID_LinksAssistant objects to a linking container applications.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _DLLASSIS_H_
#define _DLLASSIS_H_

#include "lnkassis.h"

void ObjectDestroyed(void);

//DLLASSIS.CPP
//This class factory object creates Links Assistant objects.

class CLinksClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;

    public:
        CLinksClassFactory(void);
        ~CLinksClassFactory(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP         CreateInstance(LPUNKNOWN, REFIID
                                 , PPVOID);
        STDMETHODIMP         LockServer(BOOL);
    };

typedef CLinksClassFactory *PCLinksClassFactory;

#endif //_DLLASSIS_H_


LNKASSIS.CPP

/*
 * LNKASSIS.CPP
 * Links Assistant Chapter 20
 *
 * Implementation of the CLinks object with the IOleUILinkContainer
 * interface to assist handling the Links dialog for linking
 * containers.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "lnkassis.h"

/*
 * CLinks::CLinks
 * CLinks::~CLinks
 *
 * Parameters (Constructor):
 *  pfnDestroy      PFNDESTROYED to call when object is destroyed.
 */

CLinks::CLinks(PFNDESTROYED pfnDestroy)
    {
    m_cRef=0;
    m_pfnDestroy=pfnDestroy;
    return;
    }

CLinks::~CLinks(void)
    {
    return;
    }

/*
 * CLinks::QueryInterface
 * CLinks::AddRef
 * CLinks::Release
 *
 * Purpose:
 *  IUnknown members for CLinks object.
 */

STDMETHODIMP CLinks::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IOleUILinkContainer==riid)
        {
        *ppv=this;
        AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CLinks::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CLinks::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0;
    }

/*
 * CLinks::GetNextLink
 *
 * Purpose:
 *  Function to fill out the IOleUILinkContainer interface.
 *  Does nothing.
 *
 * Parameters:
 *  dwLink          DWORD ignored.
 *
 * Return Value:
 *  DWORD           Alwaus 0L
 *
 */

STDMETHODIMP_(DWORD) CLinks::GetNextLink(DWORD dwLink)
    {
    return 0L;
    }

/*
 * CLinks::SetLinkUpdateOptions
 *
 * Purpose:
 *  Calls IOleLink::SetUpdateOptions for the object identified by
 *  dwLink.
 *
 * Parameters:
 *  dwLink          DWORD, an IOleLink pointer to the object
 *                  affected.
 *  dwOptions       DWORD containing the new options.
 *
 * Return Value:
 *  HRESULT         Return value of IOleLink::SetUpdateOptions.
 */

STDMETHODIMP CLinks::SetLinkUpdateOptions(DWORD dwLink
    , DWORD dwOptions)
    {
    LPOLELINK       pIOleLink=(LPOLELINK)dwLink;

    if (NULL==pIOleLink)
        return ResultFromScode(E_FAIL);

    return pIOleLink->SetUpdateOptions(dwOptions);
    }

/*
 * CLinks::GetLinkUpdateOptions
 *
 * Purpose:
 *  Call IOleLink::GetUpdateOptions for the object identified by
 *  dwLink.
 *
 * Parameters:
 *  dwLink          DWORD, an IOleLink pointer to the object
 *                  affected.
 *  pdwOptions      LPDWORD in which to store the options.
 *
 * Return Value:
 *  HRESULT         Return value of IOleLink::GetUpdateOptions
 */

STDMETHODIMP CLinks::GetLinkUpdateOptions(DWORD dwLink
    , LPDWORD pdwOptions)
    {
    LPOLELINK       pIOleLink=(LPOLELINK)dwLink;

    if (NULL==pIOleLink)
        return ResultFromScode(E_FAIL);

    return pIOleLink->GetUpdateOptions(pdwOptions);
    }

/*
 * CLinks::SetLinkSource
 *
 * Purpose:
 *  Changes the moniker to which an object is linked.
 *
 * Parameters:
 *  dwLink          DWORD, an IOleLink pointer to the object
 *                  affected.
 *  pszName         LPTSTR to the displayable name of the source.
 *  cchName         ULONG length of the file portaion of pszName
 *  pchEaten        ULONG * in which to return the number of
 *                  characters used in parsing pszDisplayName.
 *  fValidate       BOOL indicating if we're to validate that the
 *                  source exists first.
 *
 * Return Value:
 *  HRESULT         If successful, NOERROR indicates that the link
 *                  is available, S_FALSE to indicate it's not.
 *                  This information is later required in
 *                  GetLinkSource.  E_FAIL on failure.
 */

STDMETHODIMP CLinks::SetLinkSource(DWORD dwLink, LPTSTR pszName
    , ULONG cchName, ULONG *pchEaten, BOOL fValidate)
    {
    LPOLELINK       pIOleLink=(LPOLELINK)dwLink;
    HRESULT         hr;
    CLSID           clsID=CLSID_NULL;
    LPMONIKER       pmk=NULL;
    BOOL            fAvail=FALSE;

    if (fValidate)
        {
        //Check things out and get a moniker and CLSID.
        if (!ValidateLinkSource(pszName, pchEaten, &pmk, &clsID))
            return ResultFromScode(E_FAIL);

        //If we got a CLSID, then we found the source.
        if (CLSID_NULL!=clsID)
            fAvail=TRUE;
        }
    else
        {
        if (!CreateNewSourceMoniker(pszName, cchName, &pmk))
            return ResultFromScode(E_FAIL);
        }

    if (NULL==pIOleLink)
        {
        pmk->Release();
        return ResultFromScode(E_FAIL);
        }

    if (NULL!=pmk)
        {
        hr=pIOleLink->SetSourceMoniker(pmk, clsID);
        pmk->Release();
        }
    else
       #ifdef WIN32ANSI
        {
        OLECHAR     szTemp[512];

        MultiByteToWideChar(CP_ACP, 0, pszName, -1, szTemp, 512);
        hr=pIOleLink->SetSourceDisplayName(szTemp);
        }
       #else
        hr=pIOleLink->SetSourceDisplayName(pszName);
       #endif

    if (FAILED(hr))
        return hr;

    return fAvail ? NOERROR : ResultFromScode(S_FALSE);
    }

/*
 * CLinks::GetLinkSource
 *
 * Purpose:
 *  Retrieves various strings and values for this link source.
 *
 * Parameters:
 *  dwLink          DWORD, an IOleLink pointer to the object
 *                  affected.
 *  ppszName        LPTSTR * in which to return the new source
 *                  name
 *  pcchName        ULONG * in which to return the length of
 *                  pszName
 *  ppszFullLink    LPTSTR * in which to return the full name of
 *                  the class of linked object.
 *  ppszShortLink   LPTSTR * in which to return the short name of
 *                  the class of linked object.
 *  pfSourceAvail   BOOL * ignored.
 *  pfSelected      BOOL * ignored.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, error code otherwise.
 */

STDMETHODIMP CLinks::GetLinkSource(DWORD dwLink
    , LPTSTR *ppszName, ULONG *pcchName
    , LPTSTR *ppszFullLink, LPTSTR *ppszShortLink
    , BOOL *pfSourceAvail, BOOL *pfSelected)
    {
    LPOLELINK       pIOleLink=(LPOLELINK)dwLink;
    HRESULT         hr;
    LPOLEOBJECT     pIOleObject=NULL;
    LPMONIKER       pmk=NULL;
    LPMONIKER       pmkFirst=NULL;
    LPBC            pbc=NULL;
   #ifdef WIN32ANSI
    OLECHAR        *pszOut;
    TCHAR          *pszTemp;
   #endif

    if (NULL==pIOleLink)
        return ResultFromScode(E_FAIL);

    *ppszName=NULL;
    *pcchName=0;
    *ppszFullLink=NULL;
    *ppszShortLink=NULL;

    hr=pIOleLink->GetSourceMoniker(&pmk);

    if (SUCCEEDED(hr))
        {
        hr=pIOleLink->QueryInterface(IID_IOleObject
            , (PPVOID)&pIOleObject);

        if (SUCCEEDED(hr))
            {
           #ifdef WIN32ANSI
            pszTemp=(TCHAR *)CoTaskMemAlloc(80);
            pIOleObject->GetUserType(USERCLASSTYPE_FULL
                , &pszOut);
            WideCharToMultiByte(CP_ACP, 0, pszOut, -1
                , pszTemp, 80, NULL, NULL);
            CoTaskMemFree((void *)pszOut);
            *ppszFullLink=pszTemp;

            pszTemp=(TCHAR *)CoTaskMemAlloc(80);
            pIOleObject->GetUserType(USERCLASSTYPE_SHORT
                , &pszOut);
            WideCharToMultiByte(CP_ACP, 0, pszOut, -1
                , pszTemp, 80, NULL, NULL);
            CoTaskMemFree((void *)pszOut);
            *ppszShortLink=pszTemp;
           #else
            pIOleObject->GetUserType(USERCLASSTYPE_FULL
                , ppszFullLink);
            pIOleObject->GetUserType(USERCLASSTYPE_SHORT
                , ppszShortLink);
           #endif
            pIOleObject->Release();
            }

        *pcchName=CchFilePrefix(pmk);
        pmk->Release();
        }

   #ifdef WIN32ANSI
    pszTemp=(TCHAR *)CoTaskMemAlloc(80);
    hr=pIOleLink->GetSourceDisplayName(&pszOut);
    WideCharToMultiByte(CP_ACP, 0, pszOut, -1
        , pszTemp, 80, NULL, NULL);
    CoTaskMemFree((void *)pszOut);
    *ppszName=pszTemp;
    return hr;
   #else
    return pIOleLink->GetSourceDisplayName(ppszName);
   #endif
    }

/*
 * CLinks::OpenLinkSource
 *
 * Purpose:
 *  Does nothing.  The container using this object is the only
 *  one that knows how to activate an object properly.
 *
 * Parameters:
 *  dwLink          DWORD ignored.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CLinks::OpenLinkSource(DWORD dwLink)
    {
    return NOERROR;
    }

/*
 * CLinks::UpdateLink
 *
 * Purpose:
 *  Updates the link for this object.
 *
 * Parameters:
 *  dwLink          DWORD, an IOleLink pointer to the object
 *                  affected.
 *  fErrorMessage   BOOL indicating if we can show errors.
 *  fErrorAction    BOOL making no sense whatsoever.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, error code otherwise. If
 *                  there is an error, the caller should set the
 *                  link availability flag to FALSE.  Otherwise set
 *                  to TRUE.
 */

STDMETHODIMP CLinks::UpdateLink(DWORD dwLink
    , BOOL fErrorMessage, BOOL fErrorAction)
    {
    LPOLELINK       pIOleLink=(LPOLELINK)dwLink;
    HRESULT         hr;
    LPOLEOBJECT     pIOleObject;

    if (NULL==pIOleLink)
        return ResultFromScode(E_FAIL);

    hr=pIOleLink->QueryInterface(IID_IOleObject
        , (PPVOID)&pIOleObject);

    if (FAILED(hr))
        return hr;

    hr=pIOleObject->IsUpToDate();

    if (NOERROR!=hr)
        {
        hr=pIOleObject->Update();

        if (FAILED(hr))
            return hr;
        }

    return NOERROR;
    }

/*
 * CLinks::CancelLink
 *
 * Purpose:
 *  Sets the source moniker in the link to NULL but does nothing
 *  else.  How the container decides to convert this to static
 *  is its choice.
 *
 * Parameters:
 *  dwLink          DWORD, an IOleLink pointer to the object
 *                  affected.
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CLinks::CancelLink(DWORD dwLink)
    {
    LPOLELINK       pIOleLink=(LPOLELINK)dwLink;

    if (NULL!=pIOleLink)
        return pIOleLink->SetSourceMoniker(NULL, CLSID_NULL);

    return NOERROR;
    }

//PROTECTED FUNCTIONS INTERNAL TO CLinks

/*
 * CLinks::ValidateLinkSource
 * (Protected)
 *
 * Purpose:
 *  Given a name of a link source retrieve a moniker for it and
 *  a CLSID if we can bind.
 *
 * Parameters:
 *  pszName         LPTSTR of the source
 *  pchEaten        ULONG * into which to return how many
 *                  characters we parse.
 *  ppmk            LPMONIKER * into which to store the moniker
 *  pclsID          LPCLSID into which to store the clsID.
 *
 * Return Value:
 *  BOOL            TRUE if *ppmk has a valid moniker,
 *                  FALSE otherwise.
 */

BOOL CLinks::ValidateLinkSource(LPTSTR pszName
    , ULONG *pchEaten, LPMONIKER *ppmk, LPCLSID pclsID)
    {
    HRESULT     hr;
    LPBC        pbc=NULL;
    LPOLEOBJECT pIOleObject;

    *ppmk=NULL;
    *pclsID=CLSID_NULL;

    if (FAILED(CreateBindCtx(0, &pbc)))
        return FALSE;

    hr=MkParseDisplayName(pbc, pszName, pchEaten, ppmk);

    if (SUCCEEDED(hr))
        {
        /*
         * Now that we have a moniker for this new source, so try
         * binding to that source and get its CLSID.
         */
        hr=(*ppmk)->BindToObject(pbc, NULL, IID_IOleObject
            , (PPVOID)&pIOleObject);

        if (SUCCEEDED(hr))
            {
            pIOleObject->GetUserClassID(pclsID);
            pIOleObject->Release();
            }

        return TRUE;
        }

    pbc->Release();
    return FALSE;
    }

/*
 * CLinks::CreateNewSourceMoniker
 * (Protected)
 *
 * Purpose:
 *  Given a name of a link source create a moniker for it.
 *
 * Parameters:
 *  pszName         LPTSTR of the source
 *  cchName         ULONG length of the filename in pszName.
 *  ppmk            LPMONIKER * into which to store the moniker
 *
 * Return Value:
 *  BOOL            TRUE if *ppmk has a valid moniker,
 *                  FALSE otherwise.
 */

BOOL CLinks::CreateNewSourceMoniker(LPTSTR pszName
    , ULONG cchName, LPMONIKER *ppmk)
    {
    TCHAR       szName[CCHPATHMAX];
    LPMONIKER   pmkFile=NULL;
    LPMONIKER   pmkItem=NULL;

    *ppmk=NULL;
    lstrcpyn(szName, pszName, (int)cchName+1);
    CreateFileMoniker(szName, &pmkFile);

    if (NULL==pmkFile)
        return FALSE;

    if (lstrlen(pszName) > (int)cchName)
        {
        lstrcpy(szName, pszName+cchName+1);
        CreateItemMoniker(TEXT("!"), szName, &pmkItem);

        if (NULL!=pmkItem)
            {
            CreateGenericComposite(pmkFile, pmkItem, ppmk);
            pmkItem->Release();
            }

        pmkFile->Release();

        if (NULL==*ppmk)
            return FALSE;
        }
    else
        *ppmk=pmkFile;

    return TRUE;
    }

/*
 * CLinks::CchFilePrefix
 * (Protected)
 *
 * Purpose:
 *  Returns the length of a file moniker given that pmk is either
 *  a file moniker or a composite containing a file moniker
 *  as the first item.
 *
 * Parameters:
 *  pmk             IMoniker * to check.
 *
 * Return Value:
 *  UINT            Length of the file moniker text.
 */

UINT CLinks::CchFilePrefix(LPMONIKER pmk)
    {
    LPMONIKER       pmkFirst=NULL;
    LPENUMMONIKER   pEnum;
    DWORD           dwMk;
    LPOLESTR        psz=NULL;
    LPBC            pbc=NULL;
    ULONG           cch=0;
    HRESULT         hr;

    if (NULL==pmk)
       return 0;

    hr=pmk->IsSystemMoniker(&dwMk);

    if (FAILED(hr) || MKSYS_GENERICCOMPOSITE!=dwMk)
        {
        //Already a single moniker
        pmkFirst=pmk;
        pmk->AddRef();
        }
    else
        {
        //Pull off the first moniker in a composite
        hr=pmk->Enum(TRUE, &pEnum);

        if (FAILED(hr))
            return NULL;

        hr=pEnum->Next(1, &pmkFirst, NULL);
        pEnum->Release();
        }

    if (NULL==pmkFirst)
        return 0;

    hr=pmkFirst->IsSystemMoniker(&dwMk);

    if (SUCCEEDED(hr) && MKSYS_FILEMONIKER==dwMk);
        {
        if (SUCCEEDED(CreateBindCtx(0, &pbc)))
            {
            if (SUCCEEDED(pmkFirst->GetDisplayName(pbc, NULL
                , &psz)))
                {
               #ifdef WIN32ANSI
                cch=wcslen(psz);
               #else
                cch=lstrlen(psz);
               #endif
                CoTaskMemFree((void *)psz);
                }

            pbc->Release();
            }
        }

    pmkFirst->Release();
    return cch;
    }


LNKASSIS.H

/*
 * LNKASSIS.H
 * Links Assistant Chapter 20
 *
 * Classes that implement the LinksAssistant object
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _LNKASSIS_H_
#define _LNKASSIS_H_

#define INC_OLEUI
#define CHAPTER20
#include <inole.h>

#define CCHPATHMAX  260

class CLinks : public IOleUILinkContainer
    {
    protected:
        ULONG           m_cRef;
        PFNDESTROYED    m_pfnDestroy;

    protected:
        //Internal functions for implementing IOleUILinkContainer
        BOOL ValidateLinkSource(LPTSTR, ULONG *, LPMONIKER *
            , LPCLSID);
        BOOL CreateNewSourceMoniker(LPTSTR, ULONG, LPMONIKER *);
        UINT CchFilePrefix(LPMONIKER);

    public:
        CLinks(PFNDESTROYED);
        ~CLinks(void);

        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        /*
         * The dwLink parameter to all of these functions is assumed
         * to be an IOleLink pointer for the object in question
         * (GetNextLink is not implelemented in us).  We can
         * QueryInterface IOleLink for anything else we need.
         */

        STDMETHODIMP_(DWORD) GetNextLink(DWORD);
        STDMETHODIMP         SetLinkUpdateOptions(DWORD, DWORD);
        STDMETHODIMP         GetLinkUpdateOptions(DWORD, LPDWORD);
        STDMETHODIMP         SetLinkSource(DWORD, LPTSTR, ULONG
                                 , ULONG *, BOOL);
        STDMETHODIMP         GetLinkSource(DWORD, LPTSTR *, ULONG *
                                 , LPTSTR *, LPTSTR *, BOOL *, BOOL *);
        STDMETHODIMP         OpenLinkSource(DWORD);
        STDMETHODIMP         UpdateLink(DWORD, BOOL, BOOL);
        STDMETHODIMP         CancelLink(DWORD);
    };

typedef CLinks *PCLinks;

#endif //_LNKASSIS_H_


CLIENT.CPP

/*
 * CLIENT.CPP
 * Patron Chapter 20
 *
 * Implementation of the CPatronClient class that just makes sure
 * we get a CPatronDoc on doc creation and that we initialize fully.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPatronClient::CPatronClient
 * CPatronClient::~CPatronClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame to the frame object.
 */

CPatronClient::CPatronClient(HINSTANCE hInst, PCFrame pFR)
    : CClient(hInst, pFR)
    {
    return;
    }

CPatronClient::~CPatronClient(void)
    {
    return;
    }

/*
 * CPatronClient::CreateCDocument
 *
 * Purpose:
 *  Constructs a new document specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CPatronClient::CreateCDocument(void)
    {
    return (PCDocument)(new CPatronDoc(m_hInst, m_pFR, m_pAdv));
    }


DOCUMENT.CPP

/*
 * DOCUMENT.CPP
 * Patron Chapter 20
 *
 * Implementation of the CPatronDoc derivation of CDocument that
 * manages pages for us.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"
#include <memory.h>
#include <dlgs.h>       //Pring Dlg button IDs

/*
 * CPatronDoc::CPatronDoc
 * CPatronDoc::~CPatronDoc
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame of the frame object.
 *  pAdv            PCDocumentAdviseSink to notify on events.
 */

CPatronDoc::CPatronDoc(HINSTANCE hInst, PCFrame pFR
    , PCDocumentAdviseSink pAdv)
    : CDocument(hInst, pFR, pAdv)
    {
    m_pPG=NULL;
    m_lVer=VERSIONCURRENT;
    m_pIStorage=NULL;
    m_fPrintSetup=TRUE;
    m_pDropTarget=NULL;

    //These CFSTR_* values are standard (defined in INC\CLASSLIB.H)
    m_cfEmbeddedObject=RegisterClipboardFormat
        (CFSTR_EMBEDDEDOBJECT);
    m_cfObjectDescriptor=RegisterClipboardFormat
        (CFSTR_OBJECTDESCRIPTOR);

    //CHAPTER20MOD
    m_cfLinkSource=RegisterClipboardFormat(CFSTR_LINKSOURCE);
    m_cfLinkSrcDescriptor=RegisterClipboardFormat
        (CFSTR_LINKSRCDESCRIPTOR);

    m_fShowTypes=FALSE;
    //End CHAPTER20MOD
    return;
    }

CPatronDoc::~CPatronDoc(void)
    {
    if (NULL!=m_pPG)
        delete m_pPG;

    ReleaseInterface(m_pIStorage);
    CoFreeUnusedLibraries();
    return;
    }

/*
 * CPatronDoc::Init
 *
 * Purpose:
 *  Initializes an already created document window.  The client
 *  actually creates the window for us, then passes that here for
 *  further initialization.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPatronDoc::Init(PDOCUMENTINIT pDI)
    {
    //Change the stringtable range to our customization.
    pDI->idsMin=IDS_DOCUMENTMIN;
    pDI->idsMax=IDS_DOCUMENTMAX;

    //Do default initialization
    if (!CDocument::Init(pDI))
        return FALSE;

    //Pages are created when we get a Load later.
    return TRUE;
    }

/*
 * CPatronDoc::FMessageHook
 *
 * Purpose:
 *  Processes WM_SIZE for the document so we can resize the Pages
 *  window.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CPatronDoc::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    UINT        dx, dy;
    RECT        rc;

    *pLRes=0;

    //Eat to prevent flickering
    if (WM_ERASEBKGND==iMsg)
        return TRUE;

    if (WM_SIZE==iMsg && NULL!=m_pPG)
        {
        dx=LOWORD(lParam);
        dy=HIWORD(lParam);

        if (SIZE_MINIMIZED!=wParam)
            {
            //Resize Pages window to fit the new document size.
            GetClientRect(hWnd, &rc);
            m_pPG->RectSet(&rc, FALSE);
            }
        }

    if (WM_DESTROY==iMsg)
        {
        /*
         * We have to revoke the drop target here because the window
         * will be destroyed and the property forcefully removed
         * before we could do this in the destructor.
         */
        if (NULL!=m_pDropTarget)
            {
            RevokeDragDrop(m_hWnd);
            CoLockObjectExternal(m_pDropTarget, FALSE, TRUE);
            m_pDropTarget->Release();
            }

        return FALSE;
        }

    /*
     * We return FALSE even on WM_SIZE so we can let the default
     * procedure handle maximized MDI child windows appropriately.
     */
    return FALSE;
    }

/*
 * CPatronDoc::Clear
 *
 * Purpose:
 *  Sets all contents in the document back to defaults with no
 *  filename.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Clear(void)
    {
    //Completely reset the pages
    if (NULL!=m_pPG)
        m_pPG->StorageSet(NULL, FALSE, FALSE);

    CDocument::Clear();
    m_lVer=VERSIONCURRENT;
    return;
    }

/*
 * CPatronDoc::FDirtyGet
 *
 * Purpose:
 *  Returns the current dirty status of the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the file is clean, FALSE otherwise.
 */

BOOL CPatronDoc::FDirtyGet()
    {
    BOOL    fPageDirty;

    fPageDirty=m_pPG->FIsDirty();
    return m_fDirty | fPageDirty;
    }

/*
 * CPatronDoc::Delete
 *
 * Purpose:
 *  Removed the current object from the document.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Delete(void)
    {
    if (NULL!=m_pPG)
        m_pPG->TenantDestroy();

    CoFreeUnusedLibraries();
    return;
    }

/*
 * CPatronDoc::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected in this
 *  document for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU of the Edit menu.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryObjectSelected(HMENU hMenu)
    {
    return m_pPG->FQueryObjectSelected(hMenu);
    }

/*
 * CPatronDoc::Load
 *
 * Purpose:
 *  Loads a given document without any user interface overwriting
 *  the previous contents of the editor.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load.  Could be NULL
 *                  for an untitled document.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CPatronDoc::Load(BOOL fChangeFile, LPTSTR pszFile)
    {
    RECT        rc;
    LPSTORAGE   pIStorage;
    HRESULT     hr;
    CLSID       clsID;
    DWORD       dwMode=STGM_TRANSACTED | STGM_READWRITE
                    | STGM_SHARE_EXCLUSIVE;

    if (NULL==pszFile)
        {
        //Create a new temp file.
        hr=StgCreateDocfile(NULL, dwMode | STGM_CREATE
            | STGM_DELETEONRELEASE, 0, &pIStorage);

        //Mark this our class since we check with ReadClassStg.
        if (SUCCEEDED(hr))
            WriteClassStg(pIStorage, CLSID_PatronPages);
        }
    else
        {
        hr=StgOpenStorage(pszFile, NULL, dwMode, NULL, 0, &pIStorage);
        }

    if (FAILED(hr))
        return DOCERR_COULDNOTOPEN;

    //Check if this is our type of file and exit if not.
    hr=ReadClassStg(pIStorage, &clsID);

    if (FAILED(hr) || CLSID_PatronPages!=clsID)
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    //Attempt to create our contained Pages window.
    m_pPG=new CPages(m_hInst, m_cf);
    GetClientRect(m_hWnd, &rc);

    if (!m_pPG->Init(m_hWnd, &rc, WS_CHILD | WS_VISIBLE
        , ID_PAGES, NULL))
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    if (!m_pPG->StorageSet(pIStorage, FALSE, (NULL==pszFile)))
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    //Open the window up for drag-drop
    m_pDropTarget=new CDropTarget(this);

    if (NULL!=m_pDropTarget)
        {
        m_pDropTarget->AddRef();
        CoLockObjectExternal(m_pDropTarget, TRUE, FALSE);
        RegisterDragDrop(m_hWnd, m_pDropTarget);
        }

    m_pIStorage=pIStorage;
    Rename(pszFile);

    //Do initial setup if new file, otherwise Pages handles things.
    if (NULL==pszFile)
        {
        //Go initialize the Pages for the default printer.
        if (!PrinterSetup(NULL, TRUE))
            return DOCERR_COULDNOTOPEN;

        //Go create an initial page.
        m_pPG->PageInsert(0);
        }
    else
        {
        //Can't change an already saved configuration
        m_fPrintSetup=FALSE;
        }

    FDirtySet(FALSE);
    return DOCERR_NONE;
    }

/*
 * CPatronDoc::Save
 *
 * Purpose:
 *  Writes the file to a known filename, requiring that the user
 *  has previously used FileOpen or FileSaveAs in order to have
 *  a filename.
 *
 * Parameters:
 *  uType           UINT indicating the type of file the user
 *                  requested to save in the File Save As dialog.
 *  pszFile         LPTSTR under which to save.  If NULL, use the
 *                  current name.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CPatronDoc::Save(UINT uType, LPTSTR pszFile)
    {
    HRESULT     hr;
    LPSTORAGE   pIStorage;

    //Save or Save As with the same file is just a commit.
    if (NULL==pszFile
        || (NULL!=pszFile && 0==lstrcmpi(pszFile, m_szFile)))
        {
        WriteFmtUserTypeStg(m_pIStorage, m_cf
            , PSZ(IDS_CLIPBOARDFORMAT));

        //Insure pages are up to date.
        m_pPG->StorageUpdate(FALSE);

        //Commit everyting
        m_pIStorage->Commit(STGC_DEFAULT);

        FDirtySet(FALSE);
        return DOCERR_NONE;
        }

    /*
     * When we're given a name, open the storage, creating it new
     * if it does not exist or overwriting the old one.  Then CopyTo
     * from the current to the new, Commit the new, Release the old.
     */

    hr=StgCreateDocfile(pszFile, STGM_TRANSACTED | STGM_READWRITE
        | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

    if (FAILED(hr))
        return DOCERR_COULDNOTOPEN;

    WriteClassStg(pIStorage, CLSID_PatronPages);
    WriteFmtUserTypeStg(pIStorage, m_cf, PSZ(IDS_CLIPBOARDFORMAT));

    //Insure all pages are up-to-date.
    m_pPG->StorageUpdate(TRUE);

    //This also copies the CLSID we stuff in here on file creation.
    hr=m_pIStorage->CopyTo(NULL, NULL, NULL, pIStorage);

    if (FAILED(hr))
        {
        SCODE       sc;

        pIStorage->Release();
        sc=GetScode(hr);

        /*
         * If we failed because of low memory, use IRootStorage
         * to switch into the new file.
         */
        if (E_OUTOFMEMORY==sc)
            {
            LPROOTSTORAGE        pIRoot;

            //Delete file we already created
            DeleteFile(pszFile);

            if (FAILED(m_pIStorage->QueryInterface
                (IID_IRootStorage, (PPVOID)&pIRoot)))
                return DOCERR_WRITEFAILURE;

           #ifdef WIN32ANSI
            OLECHAR     szTemp[MAX_PATH];

            MultiByteToWideChar(CP_ACP, 0, pszFile, -1, szTemp, MAX_PATH);
            hr=pIRoot->SwitchToFile(szTemp);
           #else
            hr=pIRoot->SwitchToFile(pszFile);
           #endif
            pIRoot->Release();

            if (FAILED(hr))
                return DOCERR_WRITEFAILURE;

            //If successful, the Commit below finishes the save.
            pIStorage=m_pIStorage;
            m_pIStorage->AddRef();    //Matches Release below
            }
        }

    Rename(pszFile);    //Update caption bar.
    pIStorage->Commit(STGC_DEFAULT);

    /*
     * Revert changes on the original storage.  If this was a temp
     * file, it's deleted since we used STGM_DELETEONRELEASE.
     */
    m_pIStorage->Release();

    //Make this new storage current
    m_pIStorage=pIStorage;
    m_pPG->StorageSet(pIStorage, TRUE, FALSE);

    FDirtySet(FALSE);

    return DOCERR_NONE;
    }

/*
 * CPatronDoc::Clip
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard, optionally implementing Cut by deleting the
 *  data in the current window after rendering.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *  fCut            BOOL indicating cut (TRUE) or copy (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::Clip(HWND hWndFrame, BOOL fCut)
    {
    if (NULL==m_pPG)
        return FALSE;

    return m_pPG->TenantClip(fCut);
    }

/*
 * CPatronDoc::Paste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  to the current figure in the editor window.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::Paste(HWND hWndFrame)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet=FALSE;
    FORMATETC       fe;
    TENANTTYPE      tType;

    if (NULL==m_pPG)
        return FALSE;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    //Go get type and format we *can* paste, then actually paste it.
    if (FQueryPasteFromData(pIDataObject, &fe, &tType))
        {
        fRet=PasteFromData(pIDataObject, &fe, tType, NULL
            , 0L, TRUE);
        }

    pIDataObject->Release();
    return fRet;
    }

/*
 * CPatronDoc::FQueryPaste
 *
 * Purpose:
 *  Determines if we can paste data from the clipboard.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPaste(void)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    fRet=FQueryPasteFromData(pIDataObject, NULL, NULL);

    //CHAPTER20MOD
    fRet |= FQueryPasteLinkFromData(pIDataObject, NULL, NULL);
    //End CHAPTER20MOD

    pIDataObject->Release();
    return fRet;
    }

/*
 * CPatronDoc::PasteSpecial
 *
 * Purpose:
 *  Retrieves a specific data format from the clipboard and sends
 *  it to the editor window appropriately.
 *
 *  Note that if this function is called, then the appropriate
 *  format is available because the Paste menu item is only
 *  enabled if the format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::PasteSpecial(HWND hWndFrame)
    {
    OLEUIPASTESPECIAL   ps;
    //CHAPTER20MOD
    OLEUIPASTEENTRY     rgPaste[6];
    UINT                rgcf[1];        //For ps.m_arrLinkTypes
    //End CHAPTER20MOD
    DWORD               dwData=0;
    UINT                uTemp;
    BOOL                fRet=FALSE;

    if (NULL==m_pPG)
        return FALSE;

    memset(&ps, 0, sizeof(ps));

    if (FAILED(OleGetClipboard(&ps.lpSrcDataObj)))
        return FALSE;

    ps.cbStruct=sizeof(ps);
    ps.hWndOwner=hWndFrame;

    ps.dwFlags=PSF_SELECTPASTE;
    ps.arrPasteEntries=rgPaste;

    //Set up Paste Special descriptor arrays.
    SETDefFormatEtc(rgPaste[0].fmtetc, m_cf, TYMED_HGLOBAL);
    rgPaste[0].lpstrFormatName=PSZ(IDS_CLIPBOARDFORMAT);
    rgPaste[0].lpstrResultText=PSZ(IDS_PASTEASPATRON);
    rgPaste[0].dwFlags=OLEUIPASTE_PASTEONLY;

    //Embedded objects can be iconic displays if the user wants.
    SETDefFormatEtc(rgPaste[1].fmtetc, m_cfEmbeddedObject
        , TYMED_ISTORAGE);
    rgPaste[1].lpstrFormatName=PSZ(IDS_PASTEOBJECT);
    rgPaste[1].lpstrResultText=PSZ(IDS_PASTEASOBJECT);

    /*
     * CAUTION:  Use OLEUI_PASTE with embedded objects or else
     * this item will not show up in the dialog.  I learned this the
     * hard way (that is, after about 6 hours of pulling hair!).
     */
    rgPaste[1].dwFlags=OLEUIPASTE_PASTE | OLEUIPASTE_ENABLEICON;

    SETDefFormatEtc(rgPaste[2].fmtetc,CF_METAFILEPICT,TYMED_MFPICT);
    rgPaste[2].lpstrFormatName=PSZ(IDS_PASTEMETAFILE);
    rgPaste[2].lpstrResultText=PSZ(IDS_PASTEASMETAFILE);
    rgPaste[2].dwFlags=OLEUIPASTE_PASTEONLY;

    SETDefFormatEtc(rgPaste[3].fmtetc, CF_DIB, TYMED_HGLOBAL);
    rgPaste[3].lpstrFormatName=PSZ(IDS_PASTEDIB);
    rgPaste[3].lpstrResultText=PSZ(IDS_PASTEASDIB);
    rgPaste[3].dwFlags=OLEUIPASTE_PASTEONLY;

    SETDefFormatEtc(rgPaste[4].fmtetc, CF_BITMAP, TYMED_GDI);
    rgPaste[4].lpstrFormatName=PSZ(IDS_PASTEBITMAP);
    rgPaste[4].lpstrResultText=PSZ(IDS_PASTEASBITMAP);
    rgPaste[4].dwFlags=OLEUIPASTE_PASTEONLY;

    //CHAPTER20MOD
    SETDefFormatEtc(rgPaste[5].fmtetc,m_cfLinkSource,TYMED_ISTREAM);
    rgPaste[5].lpstrFormatName=PSZ(IDS_PASTELINK);
    rgPaste[5].lpstrResultText=PSZ(IDS_PASTEASLINK);
    rgPaste[5].dwFlags=OLEUIPASTE_LINKTYPE1 | OLEUIPASTE_ENABLEICON;

    //Types we can Paste Link from the clipboard.
    rgcf[0]=m_cfLinkSource;
    ps.arrLinkTypes=rgcf;
    ps.cLinkTypes=1;
    ps.cPasteEntries=6;
    //End CHAPTER20MOD

    uTemp=OleUIPasteSpecial(&ps);

    if (OLEUI_OK==uTemp)
        {
        UINT        i=ps.nSelectedIndex;
        TENANTTYPE  tType;

        //CHAPTER20MOD
        if (ps.fLink)
            tType=TENANTTYPE_LINKEDOBJECTFROMDATA;
        else
            {
            if (1==ps.nSelectedIndex)
                tType=TENANTTYPE_EMBEDDEDOBJECTFROMDATA;
            else
                tType=TENANTTYPE_STATIC;
            }
        //End CHAPTER20MOD

        //CHAPTER20MOD
        //Handle iconic aspects...from links as well
        if ((1==i || ps.fLink) && (PSF_CHECKDISPLAYASICON
            & ps.dwFlags) && NULL!=ps.hMetaPict)
        //End CHAPTER20MOD
            {
            rgPaste[i].fmtetc.dwAspect=DVASPECT_ICON;
            dwData=(DWORD)(UINT)ps.hMetaPict;
            }

        fRet=PasteFromData(ps.lpSrcDataObj, &rgPaste[i].fmtetc
            , tType, NULL, dwData, FALSE);

        //Always free this regardless of what we do with it.
        INOLE_MetafilePictIconFree(ps.hMetaPict);
        }

    ps.lpSrcDataObj->Release();
    return fRet;
    }

/*
 * CPatronDoc::FQueryPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Determines if we can paste data from a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which we might want to paste.
 *  pFE             LPFORMATETC in which to return the first format
 *                  we can use.  Ignored if NULL.
 *  ptType          PTENANTTYPE in which to store the type of
 *                  object we can paste.  Ignored if NULL.
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPasteFromData(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, PTENANTTYPE ptType)
    {
    FORMATETC       fe;
    HRESULT         hr, hr2;

    if (NULL!=(LPVOID)ptType)
        *ptType=TENANTTYPE_STATIC;

    //Any of our specific data here?
    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    hr=pIDataObject->QueryGetData(&fe);

    //If embedded object data is available, set the appropriate type
    hr2=OleQueryCreateFromData(pIDataObject);

    if (NOERROR==hr2)
        {
        if (NULL!=pFE)
            {
            /*
             * Default to content.  Paste will use
             * CFSTR_OBJECTDESCRIPTOR to figure the actual aspect.
             */
            SETDefFormatEtc(*pFE, m_cfEmbeddedObject
                , TYMED_ISTORAGE);
            }

        if (NULL!=(LPVOID)ptType)
            *ptType=TENANTTYPE_EMBEDDEDOBJECTFROMDATA;

        /*
         * Return now if PatronObject wasn't available, otherwise
         * break out so that pFE gets PatronObject format.
         */
        if (NOERROR!=hr)
            return TRUE;
        }

    if (NOERROR!=hr && NOERROR!=hr2)
        {
        //Try metafile, DIB, then bitmap, setting fe each time
        SETDefFormatEtc(fe, CF_METAFILEPICT, TYMED_MFPICT);
        hr=pIDataObject->QueryGetData(&fe);

        if (NOERROR!=hr)
            {
            SETDefFormatEtc(fe, CF_DIB, TYMED_HGLOBAL);
            hr=pIDataObject->QueryGetData(&fe);

            if (NOERROR!=hr)
                {
                SETDefFormatEtc(fe, CF_BITMAP, TYMED_GDI);
                hr=pIDataObject->QueryGetData(&fe);
                }
            }
        }

    if (NOERROR==hr && NULL!=pFE)
        *pFE=fe;

    return (NOERROR==hr);
    }

//CHAPTER20MOD
/*
 * CPatronDoc::FQueryPasteLinkFromData
 * (Protected)
 *
 * Purpose:
 *  Determines if we can paste link from a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which we might want to paste.
 *  pFE             LPFORMATETC in which to return the first format
 *                  we can use.  Ignored if NULL.
 *  ptType          PTENANTTYPE in which to store the type of object
 *                  we can paste.  Ignored if NULL.
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPasteLinkFromData(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, PTENANTTYPE ptType)
    {
    HRESULT         hr;

    if (NULL==pIDataObject)
        return FALSE;

    hr=OleQueryLinkFromData(pIDataObject);

    if (NOERROR!=hr)
        return FALSE;

    if (NULL!=pFE)
        SETDefFormatEtc(*pFE, m_cfLinkSource, TYMED_ISTREAM);

    if (NULL!=(LPVOID)ptType)
        *ptType=TENANTTYPE_LINKEDOBJECTFROMDATA;

    return TRUE;
    }

//End CHAPTER20MOD

/*
 * CPatronDoc::PasteFromData
 * (Protected)
 *
 * Purpose:
 *  Retrieves the private data format from a data object and sets
 *  it to the current figure in the editor window.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *  pFE             LPFORMATETC to use in the paste. Cannot be NULL.
 *  tType           TENANTTYPE to paste.
 *  ppo             PPATRONOBJECT containing placement data.
 *  dwData          DWORD extra data sensitive to tType
 *  fUseObjDesc     BOOL indicating to use CFSTR_OBJECTDESCRIPTOR
 *                  format for determining the aspect of the object
 *                  if the format is available.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::PasteFromData(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, TENANTTYPE tType, PPATRONOBJECT ppo
    , DWORD dwData, BOOL fUseObjDesc)
    {
    BOOL                fRet;
    HRESULT             hr;
    PATRONOBJECT        po;
    STGMEDIUM           stm;
    LPOBJECTDESCRIPTOR  pOD;
    FORMATETC           fe;
    BOOL                fRelease=FALSE;

    if (NULL==pFE)
        return FALSE;

    //If not given any placement data, see if we can retrieve it
    if (pFE->cfFormat==m_cf && NULL==ppo)
        {
        hr=pIDataObject->GetData(pFE, &stm);

        if (SUCCEEDED(hr))
            {
            ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);

            po=*ppo;
            ppo=&po;

            //If there's an object here, make sure type is right.
            if (ppo->fe.cfFormat==m_cfEmbeddedObject)
                tType=TENANTTYPE_EMBEDDEDOBJECTFROMDATA;

            //CHAPTER20MOD
            if (ppo->fe.cfFormat==m_cfLinkSource)
                tType=TENANTTYPE_LINKEDOBJECTFROMDATA;
            //End CHAPTER20MOD

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);
            }
        }

    /*
     * If we're told to look at CFSTR_OBJECTDESCRIPTOR, then try to get
     * the data and copy the aspect out of it.  We're not interested
     * in any other part of it, however.
     */
    if (fUseObjDesc)
        {
        SETDefFormatEtc(fe, m_cfObjectDescriptor, TYMED_HGLOBAL);

        //CHAPTER20MOD
        hr=pIDataObject->GetData(&fe, &stm);

        if (FAILED(hr))
            {
            SETDefFormatEtc(fe, m_cfLinkSrcDescriptor
                , TYMED_HGLOBAL);
            hr=pIDataObject->GetData(&fe, &stm);
            }

        if (SUCCEEDED(hr))
        //End CHAPTER20MOD
            {
            pOD=(LPOBJECTDESCRIPTOR)GlobalLock(stm.hGlobal);
            pFE->dwAspect=pOD->dwDrawAspect;

            if (NULL!=ppo)
                ppo->fe.dwAspect=pFE->dwAspect;

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);

            /*
             * Furthermore, if pFE->dwAspect is DVASPECT_ICON, get
             * the metafile which will always be the icon
             * representation.
             */
            SETFormatEtc(fe, CF_METAFILEPICT, DVASPECT_ICON, NULL
                , TYMED_MFPICT, -1);

            if (SUCCEEDED(pIDataObject->GetData(&fe, &stm)))
                {
                dwData=(DWORD)(UINT)stm.hGlobal;
                fRelease=TRUE;
                }
            }
        }

    fRet=m_pPG->TenantCreate(tType, pIDataObject, pFE, ppo, dwData);

    //Release the stm from the last GetData
    if (fRelease)
        ReleaseStgMedium(&stm);

    if (fRet)
        {
        //Disable Printer Setup once we've created a tenant.
        m_fPrintSetup=FALSE;
        FDirtySet(TRUE);
        }

    return fRet;
    }

//CHAPTER20MOD
/*
 * CPatronDoc::FQueryEnableEditLinks
 *
 * Purpose:
 *  Determines whether or not the Edit Links menu item should be
 *  enabled on the frame window menu.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE to enable the item, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryEnableEditLinks(void)
    {
    return m_pPG->FQueryLinksInPage();
    }

/*
 * CPatronDoc::EditLinks
 *
 * Purpose:
 *  Invokes the links dialog in which the end-user can manipulate
 *  all the links in the current page.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame window to use as the dialog
 *                  parent.
 *
 * Return Value:
 *  BOOL            TRUE if the function was successful,
 *                  FALSE otherwise.
 */

BOOL CPatronDoc::EditLinks(HWND hWndFrame)
    {
    UINT                    uRet;
    OLEUIEDITLINKS          el;
    PCIOleUILinkContainer   pIUILinks;

    memset(&el, 0, sizeof(el));
    el.cbStruct=sizeof(el);
    el.hWndOwner=hWndFrame;

    if (!m_pPG->GetUILinkContainer(&pIUILinks))
        return FALSE;

    el.lpOleUILinkContainer=pIUILinks;
    uRet=OleUIEditLinks(&el);

    //Only the IOleUILinkContainer interface will know dirtying.
    m_fDirty=pIUILinks->m_fDirty;

    el.lpOleUILinkContainer->Release();
    return TRUE;
    }

/*
 * CPatronDoc::ShowOrQueryObjectTypes
 *
 * Purpose:
 *  If querying, we return whether or not ShowObjects is active
 *  in this document.  Otherwise pass-through to
 *  CPages::ShowObjectTypes.
 *
 * Parameters:
 *  fQuery          BOOL indicating query or set.
 *  fShow           BOOL indicating to show the type or not.
 *
 * Return Value:
 *  None
 */

BOOL CPatronDoc::ShowOrQueryObjectTypes(BOOL fQuery, BOOL fShow)
    {
    if (fQuery)
        return m_fShowTypes;

    m_fShowTypes=fShow;
    m_pPG->ShowObjectTypes(fShow);
    return TRUE;
    }

//End CHAPTER20MOD

/*
 * CPatronDoc::NewPage
 *
 * Purpose:
 *  Creates a new page in the document's pages control after the
 *  current page.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::NewPage(void)
    {
    FDirtySet(TRUE);
    return m_pPG->PageInsert(0);
    }

/*
 * CPatronDoc::DeletePage
 *
 * Purpose:
 *  Deletes the current page from the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the now current page.
 */

UINT CPatronDoc::DeletePage(void)
    {
    FDirtySet(TRUE);
    return m_pPG->PageDelete(0);
    }

/*
 * CPatronDoc::NextPage
 *
 * Purpose:
 *  Shows the next page in the pages window.

*
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::NextPage(void)
    {
    UINT        iPage;

    iPage=m_pPG->CurPageGet();
    return m_pPG->CurPageSet(++iPage);
    }

/*
 * CPatronDoc::PreviousPage
 *
 * Purpose:
 *  Shows the previous page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::PreviousPage(void)
    {
    UINT        iPage;

    //If iPage is zero, then we wrap around to the end.
    iPage=m_pPG->CurPageGet();
    return m_pPG->CurPageSet(--iPage);
    }

/*
 * CPatronDoc::FirstPage
 *
 * Purpose:
 *  Shows the first page page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::FirstPage(void)
    {
    return m_pPG->CurPageSet(0);
    }

/*
 * CPatronDoc::LastPage
 *
 * Purpose:
 *  Shows the last page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the last page.
 */

UINT CPatronDoc::LastPage(void)
    {
    return m_pPG->CurPageSet(NOVALUE);
    }

/*
 * CPatronDoc::Rename
 *
 * Purpose:
 *  Overrides the normal rename to include notification of tenants
 *  in this document of the new name.  This is so embedded objects
 *  are told the correct name through IOleObject::SetHostNames.
 *
 * Parameters:
 *  pszFile         LPTSTR to the new filename.
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Rename(LPTSTR pszFile)
    {
    //CHAPTER20MOD
    LPMONIKER   pmk;

    //We don't need to change the base class, just augment...
    CDocument::Rename(pszFile);

    //Give a moniker to linked objects in tenants.
    if (NULL!=pszFile)
        {
        CreateFileMoniker(pszFile, &pmk);
        m_pPG->NotifyTenantsOfRename(pszFile, pmk);

        //No need for us to hold on to this.
        pmk->Release();
        }

    //End CHAPTER20MOD
    return;
    }

/*
 * CPatronDoc::InsertObject
 *
 * Purpose:
 *  Retrieves a CLSID or a filename from the Insert Object dialog
 *  box and creates an object using those identifiers.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::InsertObject(HWND hWndFrame)
    {
    OLEUIINSERTOBJECT   io;
    DWORD               dwData=0;
    TCHAR               szFile[CCHPATHMAX];
    UINT                uTemp;
    BOOL                fRet=FALSE;

    if (NULL==m_pPG)
        return FALSE;

    memset(&io, 0, sizeof(io));

    io.cbStruct=sizeof(io);
    io.hWndOwner=hWndFrame;

    szFile[0]=0;
    io.lpszFile=szFile;
    io.cchFile=CCHPATHMAX;

    //CHAPTER20MOD
    //We now support linking, so remove the IOF_DISABLELINK flag.
    io.dwFlags=IOF_SELECTCREATENEW;
    //End CHAPTER20MOD

    uTemp=OleUIInsertObject(&io);

    if (OLEUI_OK==uTemp)
        {
        TENANTTYPE      tType;
        LPVOID          pv;
        FORMATETC       fe;

        SETDefFormatEtc(fe, 0, TYMED_NULL);

        if (io.dwFlags & IOF_SELECTCREATENEW)
            {
            tType=TENANTTYPE_EMBEDDEDOBJECT;
            pv=&io.clsid;
            }
        else
            {
            //CHAPTER20MOD
            if (io.dwFlags & IOF_CHECKLINK)
                tType=TENANTTYPE_LINKEDFILE;
            else
                tType=TENANTTYPE_EMBEDDEDFILE;
            //End CHAPTER20MOD

            pv=szFile;
            }

        if ((io.dwFlags & IOF_CHECKDISPLAYASICON)
            && NULL!=io.hMetaPict)
            {
            fe.dwAspect=DVASPECT_ICON;
            dwData=(DWORD)(UINT)io.hMetaPict;
            }

        fRet=m_pPG->TenantCreate(tType, pv, &fe, NULL, dwData);

        //Free this regardless of what we do with it.
        INOLE_MetafilePictIconFree(io.hMetaPict);

        if (fRet)
            {
            //Disable Printer Setup once we've created a tenant.
            m_fPrintSetup=FALSE;
            FDirtySet(TRUE);
            }
        }

    return fRet;
    }

/*
 * CPatronDoc::ActivateObject
 *
 * Purpose:
 *  Executes a verb on the currently selected object.
 *
 * Parameters:
 *  iVerb           LONG of the selected verb.
 *
 * Return Value:
 *  None
 */

void CPatronDoc::ActivateObject(LONG iVerb)
    {
    m_pPG->ActivateObject(iVerb);
    return;
    }

/*
 * CPatronDoc::ConvertObject
 *
 * Purpose:
 *  Invokes the Convert dialog on the current object.  Here it's
 *  just a pass-through to the pages.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame window.
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful, FALSE otherwise.
 */

BOOL CPatronDoc::ConvertObject(HWND hWndFrame)
    {
    return m_pPG->ConvertObject(hWndFrame);
    }


DRAGDROP.CPP

/*
 * DRAGDROP.CPP
 * Patron Chapter 20
 *
 * Member functions of the CPages class concerned with drag-drop
 * and other object-placement functionality.  Moved here to clean
 * up CPAGES.CPP somewhat.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPages::UTestDroppablePoint
 *
 * Purpose:
 *  Returns if the point in pptl is on the paper in the current page
 *  and alternately if it's within horizontal or vertical inset
 *  regions.
 *
 * Parameters:
 *  pptl            PPOINTL containing the point in screen
 *                  coordinates.
 *
 * Return Value:
 *  UINT            A UDROP_* value.
 */

UINT CPages::UTestDroppablePoint(PPOINTL pptl)
    {
    POINT       pt;
    RECT        rc, rcT, rcC;
    UINT        uRet;

    POINTFROMPOINTL(pt, *pptl);
    ScreenToClient(m_hWnd, &pt);

    CalcBoundingRect(&rc, FALSE);

    GetClientRect(m_hWnd, &rcC);
    IntersectRect(&rcT, &rc, &rcC);

    //Check for at least a client area hit.
    if (!PtInRect(&rcT, pt))
        return UDROP_NONE;

    uRet=UDROP_CLIENT;

    //Scroll checks happen on client area, not document area
    if (PtInRect(&rcC, pt))
        {
        //Check horizontal inset
        if (pt.x <= rcC.left+(int)m_uScrollInset)
            uRet |= UDROP_INSETLEFT;
        else if (pt.x >= rcC.right-(int)m_uScrollInset)
            uRet |= UDROP_INSETRIGHT;

        //Check vertical inset
        if (pt.y <= rcC.top+(int)m_uScrollInset)
            uRet |= UDROP_INSETTOP;
        else if (pt.y >= rcC.bottom-(int)m_uScrollInset)
            uRet |= UDROP_INSETBOTTOM;
        }

    return uRet;
    }

/*
 * CPages::DrawDropTargetRect
 *
 * Purpose:
 *  Draws a dotted rectangle on the Pages window to show where
 *  a drop might occur.  This is a toggle function.
 *
 * Parameters:
 *  pptl            PPOINTL containing the upper-left point
 *                  in screen coordinates.
 *  pszl            LPSIZEL containing the rect extents in device
 *                  units.
 *
 * Return Value:
 *  None
 */

void CPages::DrawDropTargetRect(PPOINTL pptl, LPSIZEL pszl)
    {
    POINT           pt;
    RECT            rc, rcT;
    HDC             hDC;

    if (NULL==pptl && NULL==pszl)
        {
        /*
         * This case is used from WM_PAINT in pagewin.cpp so we can
         * control the proper visibility of the drag rectangle when
         * we're scrolling.  If drag-drop happens between two apps,
         * then any ScrollWindow will cause a WM_PAINT after we
         * leave DragOver, so that paint would normally overwrite
         * the last drawn rectangle.  To alleviate that, WM_PAINT
         * will remove the last rect by sending us NULLs, paint,
         * then reinstate the rectangle.
         */
        pptl=&m_ptlRect;
        pszl=&m_szlRect;
        }
    else
        {
        m_ptlRect.x=pptl->x;
        m_ptlRect.y=pptl->y;
        m_szlRect.cx=pszl->cx;
        m_szlRect.cy=pszl->cy;

        //Flag is only affected from IDropTarget, not WM_PAINT calls
        m_fDragRectShown=!m_fDragRectShown;
        }

    POINTFROMPOINTL(pt, *pptl);
    ScreenToClient(m_hWnd, &pt);
    SetRect(&rc, pt.x, pt.y, pt.x+(int)pszl->cx
        , pt.y+(int)pszl->cy);

    CalcBoundingRect(&rcT, FALSE);
    IntersectRect(&rc, &rc, &rcT);

    if (!IsRectEmpty(&rc))
        {
        hDC=GetDC(m_hWnd);
        DrawFocusRect(hDC, &rc);
        ReleaseDC(m_hWnd, hDC);
        }

    return;
    }

/*
 * CPages::AdjustPosition
 *
 * Purpose:
 *  Adjusts a point for the scrolling offset and then converts it
 *  and a size from device into LOMETRIC units.
 *
 * Parameters:
 *  pptl            PPOINTL to adjust and convert
 *  pszl            LPSIZEL to convert
 *
 * Return Value:
 *  None
 */

void CPages::AdjustPosition(PPOINTL pptl, LPSIZEL pszl)
    {
    RECT        rc;

    SetRect(&rc, m_xPos+(int)pptl->x
        , m_yPos+(int)pptl->y, (int)pszl->cx, (int)pszl->cy);

    RectConvertMappings(&rc, NULL, FALSE);

    pptl->x=rc.left;
    pptl->y=rc.top;

    pszl->cx=rc.right;
    pszl->cy=rc.bottom;

    return;
    }


DROPSRC.CPP

/*
 * DROPSRC.CPP
 * Patron Chapter 20
 *
 * Implementation of a DropSource object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CDropSource::CDropSource
 * CDropSource::~CDropSource
 *
 * Constructor Parameters:
 *  None
 */

CDropSource::CDropSource(void)
    {
    m_cRef=0;
    return;
    }

CDropSource::~CDropSource(void)
    {
    return;
    }

/*
 * CDropSource::QueryInterface
 * CDropSource::AddRef
 * CDropSource::Release
 *
 * Purpose:
 *  IUnknown members for CDropSource object.
 */

STDMETHODIMP CDropSource::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDropSource==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDropSource::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropSource::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDropSource::QueryDragContinue
 *
 * Purpose:
 *  Determines whether to continue a drag operation or cancel it.
 *
 * Parameters:
 *  fEsc            BOOL indicating that the ESC key was pressed.
 *  grfKeyState     DWORD providing states of keys and mouse buttons
 *
 * Return Value:
 *  HRESULT         DRAGDROP_S_CANCEL to stop the drag,
 *                  DRAGDROP_S_DROP to drop the data where it is,
 *                  or NOERROR to continue.
 */

STDMETHODIMP CDropSource::QueryContinueDrag(BOOL fEsc, DWORD grfKeyState)
    {
    if (fEsc)
        return ResultFromScode(DRAGDROP_S_CANCEL);

    if (!(grfKeyState & MK_LBUTTON))
        return ResultFromScode(DRAGDROP_S_DROP);

    return NOERROR;
    }

/*
 * CDropSource::GiveFeedback
 *
 * Purpose:
 *  Provides cursor feedback to the user since the source task
 *  always has the mouse capture.  We can also provide any other
 *  type of feedback above cursors if we so desire.
 *
 * Parameters:
 *  dwEffect        DWORD effect flags returned from the last target
 *
 * Return Value:
 *  HRESULT         NOERROR if you set a cursor yourself or
 *                  DRAGDROP_S_USEDEFAULTCURSORS to let OLE do
 *                  the work.
 */

STDMETHODIMP CDropSource::GiveFeedback(DWORD dwEffect)
    {
    return ResultFromScode(DRAGDROP_S_USEDEFAULTCURSORS);
    }


DROPTGT.CPP

/*
 * DROPTGT.CPP
 * Patron Chapter 20
 *
 * Implementation of a DropTarget object
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CDropTarget::CDropTarget
 * CDropTarget::~CDropTarget
 *
 * Constructor Parameters:
 *  pDoc            PCPatronDoc of the window containing us.
 */

CDropTarget::CDropTarget(PCPatronDoc pDoc)
    {
    m_cRef=0;
    m_pDoc=pDoc;

    m_pIDataObject=NULL;
    return;
    }

CDropTarget::~CDropTarget(void)
    {
    return;
    }

/*
 * CDropTarget::QueryInterface
 * CDropTarget::AddRef
 * CDropTarget::Release
 *
 * Purpose:
 *  IUnknown members for CDropTarget object.
 */

STDMETHODIMP CDropTarget::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDropTarget==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDropTarget::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropTarget::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDropTarget::DragEnter
 *
 * Purpose:
 *  Indicates that data in a drag operation has been dragged over
 *  our window that's a potential target.  We are to decide if it's
 *  something in which we're interested.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT providing the source data.
 *  grfKeyState     DWORD flags: states of keys and mouse buttons.
 *  pt              POINTL coordinates in the client space of
 *                  the document.
 *  pdwEffect       LPDWORD into which we'll place the appropriate
 *                  effect flag for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragEnter(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    PCPages         ppg=m_pDoc->m_pPG;
    HWND            hWnd;
    FORMATETC       fe;
    STGMEDIUM       stm;
    UINT            uRet;

    m_fFeedback=FALSE;
    m_pIDataObject=NULL;

    if (!m_pDoc->FQueryPasteFromData(pIDataSource, &fe, NULL))
        {
        *pdwEffect=DROPEFFECT_NONE;
        return NOERROR;
        }

    //CHAPTER20MOD
    //Check if we can link from this data object as well.
    ppg->m_fLinkAllowed
        =(NOERROR==OleQueryLinkFromData(pIDataSource));

    //We never allow it dragging in ourselves.
    ppg->m_fLinkAllowed &= !ppg->m_fDragSource;
    //End CHAPTER20MOD

    //Check if this is a valid drop point.
    uRet=ppg->UTestDroppablePoint(&pt);
    ppg->m_uLastTest=uRet;

    if (UDROP_NONE==uRet)
        *pdwEffect=DROPEFFECT_NONE;
    else
        {
        //Default is move if we can, in fact drop here.
        *pdwEffect=DROPEFFECT_MOVE;

        //CHAPTER20MOD
        if (grfKeyState & MK_CONTROL)
            {
            if (ppg->m_fLinkAllowed && (grfKeyState & MK_SHIFT))
                *pdwEffect=DROPEFFECT_LINK;
            else
                *pdwEffect=DROPEFFECT_COPY;
            }
        //End CHAPTER20MOD
        }

    m_pIDataObject=pIDataSource;
    m_pIDataObject->AddRef();

    /*
     * Determine the size of the data, if we can.  The default is
     * a small rectangle since we can't easily tell what size
     * something will be if we're pulling in a metafile or bitmap.
     * It's not a good idea to render it here with GetData just to
     * find that out. We only know the size if it's our own object
     * in which case a GetData will be fast.
     */

    if (fe.cfFormat==m_pDoc->m_cf)
        {
        if (SUCCEEDED(pIDataSource->GetData(&fe, &stm)))
            {
            PPATRONOBJECT   ppo;
            RECT            rc;

            ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);

            SetRect(&rc, (int)ppo->szl.cx, -(int)ppo->szl.cy, 0, 0);
            RectConvertMappings(&rc, NULL, TRUE);
            SETSIZEL(m_szl, rc.left, rc.top);

            m_ptPick=ppo->ptlPick;
            m_fe=ppo->fe;

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);
            }
        }
    else
        {
        SETSIZEL(m_szl, 30, 30);
        m_ptPick.x=0;
        m_ptPick.y=0;
        m_fe.cfFormat=0;

        /*
         * Try to get CFSTR_OBJECTDESCRIPTOR which might have a size
         * and a pick point.  If it exists, then always use the
         * point but still default to a 30*30 size if the sizes
         * are zero.
         */
        uRet=RegisterClipboardFormat(CFSTR_OBJECTDESCRIPTOR);
        SETDefFormatEtc(fe, uRet, TYMED_HGLOBAL);

        if (SUCCEEDED(pIDataSource->GetData(&fe, &stm)))
            {
            LPOBJECTDESCRIPTOR  pOD;

            pOD=(LPOBJECTDESCRIPTOR)GlobalLock(stm.hGlobal);

            //Get the size, converting to LOMETRIC.
            if (0!=pOD->sizel.cx && 0!=pOD->sizel.cy)
                {
                XformSizeInHimetricToPixels(NULL, &pOD->sizel
                    , &m_szl);
                }

            //POINTL and SIZEL are interchangeable
            XformSizeInHimetricToPixels(NULL, (LPSIZEL)&pOD->pointl
                , (LPSIZEL)&m_ptPick);

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);
            }
        }

    //Bring the document window up front, show what a drop will do.
    hWnd=m_pDoc->Window();
    BringWindowToTop(hWnd);
    UpdateWindow(hWnd);

    ppg->m_uVScrollCode=NOVALUE;
    ppg->m_uHScrollCode=NOVALUE;
    m_fPendingRepaint=FALSE;

    pt.x-=m_ptPick.x;
    pt.y-=m_ptPick.y;

    m_ptLast=pt;
    m_fFeedback=TRUE;
    ppg->DrawDropTargetRect(&pt, &m_szl);

    return NOERROR;
    }

/*
 * CDropTarget::DragOver
 *
 * Purpose:
 *  Indicates that the mouse was moved inside the window represented
 *  by this drop target.  This happens on every WM_MOUSEMOVE, so
 *  this function should be very efficient.
 *
 * Parameters:
 *  grfKeyState     DWORD providing the current keyboard and
 *                  mouse states
 *  pt              POINTL where the mouse currently is.
 *  pdwEffect       LPDWORD in which to store the effect flag
 *                  for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragOver(DWORD grfKeyState, POINTL pt
    , LPDWORD pdwEffect)
    {
    PCPages     ppg=m_pDoc->m_pPG;
    UINT        uRet, uLast;
    UINT        xPos, yPos;

    *pdwEffect=DROPEFFECT_NONE;

    if (NULL==m_pIDataObject)
        return NOERROR;

    //Check if this is still a valid point.  uRet used below as well
    uRet=ppg->UTestDroppablePoint(&pt);

    if (UDROP_NONE==uRet)
        *pdwEffect=DROPEFFECT_NONE;
    else
        {
        //Store these before possibly ORing in DROPEFFECT_SCROLL
        *pdwEffect=DROPEFFECT_MOVE;

        //CHAPTER20MOD
        if (grfKeyState & MK_CONTROL)
            {
            if (ppg->m_fLinkAllowed && (grfKeyState & MK_SHIFT))
                *pdwEffect=DROPEFFECT_LINK;
            else
                *pdwEffect=DROPEFFECT_COPY;
            }
        //End CHAPTER20MOD
        }

    //If we haven't moved and we are not scrolling, then we're done.
    if ((pt.x-m_ptPick.x==m_ptLast.x)
        && (pt.y-m_ptPick.y==m_ptLast.y)
        && !((UDROP_INSETHORZ|UDROP_INSETVERT) & ppg->m_uLastTest))
        {
        return NOERROR;
        }

    //Remove the last feedback rectangle.
    if (m_fFeedback)
        ppg->DrawDropTargetRect(&m_ptLast, &m_szl);

    uLast=ppg->m_uLastTest;
    ppg->m_uLastTest=uRet;

    if (UDROP_NONE==uRet)
        {
        //If we're now an invalid point, better repaint as necessary
        if (m_fPendingRepaint)
            {
            UpdateWindow(ppg->m_hWnd);
            m_fPendingRepaint=FALSE;
            }

        ppg->m_uVScrollCode=NOVALUE;
        ppg->m_uHScrollCode=NOVALUE;
        m_fFeedback=FALSE;
        return NOERROR;
        }

    /*
     * Scrolling is a little tricky:  We get a DragOver pulse even
     * if we didn't move.  First we have to delay scrolling for
     * ppg->m_uScrollDelay clock ticks which we can determine using
     * GetTickCount.  Timers do not work here since we may not be
     * yielding to our message loop.
     *
     * Once we know we are scrolling then we determine if we
     * scroll again or if we reset the scrolling state.
     */

    if ((UDROP_INSETHORZ & uLast) && !(UDROP_INSETHORZ & uRet))
        ppg->m_uHScrollCode=NOVALUE;

    if (!(UDROP_INSETHORZ & uLast) && (UDROP_INSETHORZ & uRet))
        {
        ppg->m_dwTimeLast=GetTickCount();
        ppg->m_uHScrollCode=(0!=(UDROP_INSETLEFT & uRet))
            ? SB_LINELEFT : SB_LINERIGHT; //Same as UP & DOWN codes.
        }

    if ((UDROP_INSETVERT & uLast) && !(UDROP_INSETVERT & uRet))
        ppg->m_uVScrollCode=NOVALUE;

    if (!(UDROP_INSETVERT & uLast) && (UDROP_INSETVERT & uRet))
        {
        ppg->m_dwTimeLast=GetTickCount();
        ppg->m_uVScrollCode=(0!=(UDROP_INSETTOP & uRet))
            ? SB_LINEUP : SB_LINEDOWN;
        }

    //Only change the last time if ALL scrolling stops.
    if (NOVALUE==ppg->m_uHScrollCode && NOVALUE==ppg->m_uVScrollCode)
        ppg->m_dwTimeLast=0L;

    //Set the scroll effect on any inset hit.
    if ((UDROP_INSETHORZ | UDROP_INSETVERT) & uRet)
        *pdwEffect |= DROPEFFECT_SCROLL;

    xPos=ppg->m_xPos;
    yPos=ppg->m_yPos;

    //Has the delay elapsed?  We can scroll if so
    if (ppg->m_dwTimeLast!=0
        && (GetTickCount()-ppg->m_dwTimeLast)
        > (DWORD)ppg->m_uScrollDelay)
        {
        if (NOVALUE!=ppg->m_uHScrollCode)
            {
            m_fPendingRepaint=TRUE;
            SendMessage(ppg->m_hWnd, WM_HSCROLL
                , ppg->m_uHScrollCode, 0L);
            }

        if (NOVALUE!=ppg->m_uVScrollCode)
            {
            m_fPendingRepaint=TRUE;
            SendMessage(ppg->m_hWnd, WM_VSCROLL
                , ppg->m_uVScrollCode, 0L);
            }
        }

    //If we didn't scroll but have a pending repaint, do it now.
    if (xPos==ppg->m_xPos && yPos==ppg->m_yPos && m_fPendingRepaint)
        {
        UpdateWindow(ppg->m_hWnd);
        m_fPendingRepaint=FALSE;
        }

    pt.x-=m_ptPick.x;
    pt.y-=m_ptPick.y;

    m_ptLast=pt;
    m_fFeedback=TRUE;
    ppg->DrawDropTargetRect(&pt, &m_szl);

    return NOERROR;
    }

/*
 * CDropTarget::DragLeave
 *
 * Purpose:
 *  Informs the drop target that the operation has left its window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragLeave(void)
    {
    PCPages         ppg=m_pDoc->m_pPG;

    if (NULL==m_pIDataObject)
        return NOERROR;

    //Stop scrolling
    ppg->m_uHScrollCode=NOVALUE;
    ppg->m_uVScrollCode=NOVALUE;

    if (m_fPendingRepaint)
        UpdateWindow(ppg->m_hWnd);

    //Remove the last feedback rectangle.
    if (m_fFeedback)
        ppg->DrawDropTargetRect(&m_ptLast, &m_szl);

    m_fFeedback=FALSE;
    m_pIDataObject->Release();
    return NOERROR;
    }

/*
 * CDropTarget::Drop
 *
 * Purpose:
 *  Instructs the drop target to paste the data that was just now
 *  dropped on it.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT from which we'll paste.
 *  grfKeyState     DWORD providing current keyboard/mouse state.
 *  pt              POINTL at which the drop occurred.
 *  pdwEffect       LPDWORD in which to store what you did.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::Drop(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    PCPages         ppg=m_pDoc->m_pPG;
    BOOL            fRet=TRUE;
    FORMATETC       fe;
    TENANTTYPE      tType;
    PATRONOBJECT    po;
    POINT           ptS;

    *pdwEffect=DROPEFFECT_NONE;

    if (NULL==m_pIDataObject)
        return ResultFromScode(E_FAIL);

    if (UDROP_NONE==ppg->UTestDroppablePoint(&pt))
        return ResultFromScode(E_FAIL);

    //Stop scrolling
    ppg->m_uHScrollCode=NOVALUE;
    ppg->m_uVScrollCode=NOVALUE;

    if (m_fPendingRepaint)
        UpdateWindow(ppg->m_hWnd);

    //2.  Remove the UI feedback
    if (m_fFeedback)
        ppg->DrawDropTargetRect(&m_ptLast, &m_szl);

    m_pIDataObject->Release();

    /*
     * Check if we can do the paste, and if so, tell our pasting
     * mechanism exactly where to place us.
     */
    pt.x-=m_ptPick.x;
    pt.y-=m_ptPick.y;

    POINTFROMPOINTL(ptS, pt);
    ScreenToClient(ppg->Window(), &ptS);
    POINTLFROMPOINT(po.ptl, ptS);

    //This is true if we didn't see placement data in DragEnter
    if (0!=m_fe.cfFormat)
        {
        po.szl.cx=m_szl.cx;         //We stored these positive
        po.szl.cy=-m_szl.cy;
        }
    else
        SETSIZEL(po.szl, 0, 0); //Ask object for its size.

    //Adjust for scrolling and mapping mode.
    ppg->AdjustPosition(&po.ptl, &po.szl);

    /*
     * If we're in the same document and moving, then we can just
     * stuff the Pages' m_ptDrop which will move us and return.
     */
    if (ppg->m_fDragSource && !(grfKeyState & MK_CONTROL))
        {
        *pdwEffect=DROPEFFECT_MOVE;
        ppg->m_fMoveInPage=TRUE;
        ppg->m_ptDrop=po.ptl;
        return NOERROR;
        }

    /*
     * Otherwise, paste either from another document or from
     * the same document which will always be a copy to the new
     * point.
     */

    //CHAPTER20MOD
    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        {
        if (ppg->m_fLinkAllowed && (grfKeyState & MK_SHIFT))
            *pdwEffect=DROPEFFECT_LINK;
        else
            *pdwEffect=DROPEFFECT_COPY;
        }

    ppg->m_fMoveInPage=FALSE;

    /*
     * We know linking is desired if effect flag is set, so this
     * function will get the FORMATETC for linking.  Otherwise
     * FQueryPasteFromData will get the other FORMATETC to use.
     */
    if (DROPEFFECT_LINK==*pdwEffect)
        {
        fRet=m_pDoc->FQueryPasteLinkFromData(pIDataSource, &fe
            , &tType);
        }
    else
        fRet=m_pDoc->FQueryPasteFromData(pIDataSource, &fe, &tType);
    //End CHAPTER20MOD

    if (fRet)
        {
        //Copy the real format if we have placement data.
        po.fe=(m_pDoc->m_cf==fe.cfFormat) ? m_fe : fe;

        //Flag PasteFromData to use CFSTR_OBJECTDESCRIPTOR
        fRet=m_pDoc->PasteFromData(pIDataSource, &fe, tType
            , &po, 0, TRUE);
        }

    if (!fRet)
        return ResultFromScode(E_FAIL);

    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        *pdwEffect=DROPEFFECT_COPY;

    return NOERROR;
    }


IADVSINK.CPP

/*
 * IADVSINK.CPP
 * Patron Chapter 20
 *
 * Implementation of the IAdviseSink interface for Patron's tenants.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CImpIAdviseSink::CImpIAdviseSink
 * CImpIAdviseSink::~CImpIAdviseSink
 *
 * Parameters (Constructor):
 *  pTenant         PCTenant of the tenant we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIAdviseSink::CImpIAdviseSink(PCTenant pTenant
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pTen=pTenant;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIAdviseSink::~CImpIAdviseSink(void)
    {
    return;
    }

/*
 * CImpIAdviseSink::QueryInterface
 * CImpIAdviseSink::AddRef
 * CImpIAdviseSink::Release
 *
 * Purpose:
 *  IUnknown members for CImpIAdviseSink object.
 */

STDMETHODIMP CImpIAdviseSink::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIAdviseSink::OnDataChange
 *
 * Unused since we don't IDataObject::Advise.
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnDataChange(LPFORMATETC pFEIn
    , LPSTGMEDIUM pSTM)
    {
    return;
    }

/*
 * CImpIAdviseSink::OnViewChange
 *
 * Purpose:
 *  Notifes the advise sink that presentation data changed in the
 *  data object to which we're connected providing the right time
 *  to update displays using such presentations.
 *
 * Parameters:
 *  dwAspect        DWORD indicating which aspect has changed.
 *  lindex          LONG indicating the piece that changed.
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnViewChange(DWORD dwAspect
    , LONG lindex)
    {
    //Repaint only if this is the right aspect
    if (dwAspect==m_pTen->m_fe.dwAspect)
        {
        m_pTen->m_pPG->m_fDirty=TRUE;
        m_pTen->Repaint();
        }

    return;
    }

/*
 * CImpIAdviseSink::OnRename
 *
 * Purpose:
 *  Informs the advise sink that a linked object has been renamed.
 *  Generally only the OLE default handler cares about this.
 *
 * Parameters:
 *  pmk             LPMONIKER providing the new name of the object
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnRename(LPMONIKER pmk)
    {
    /*
     * As a container this is unimportant to us since it really
     * tells the handler's implementation of IOleLink that the
     * object's moniker has changed.  Since we get this call
     * from the handler, we don't have to do anything ourselves.
     */
    return;
    }

/*
 * CImpIAdviseSink::OnSave
 *
 * Purpose:
 *  Informs the advise sink that the OLE object has been saved
 *  persistently.  The primary purpose of this is for containers
 *  that want to make optimizations for objects that are not in a
 *  saved state, so on this you have to disable such optimizations.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnSave(void)
    {
    /*
     * A Container has nothing to do here as this notification is
     * only useful when we have an ADVFCACHE_ONSAVE advise set up,
     * which we don't.  So we ignore it.
     */
    return;
    }

/*
 * CImpIAdviseSink::OnClose
 *
 * Purpose:
 *  Informs the advise sink that the OLE object has closed and is
 *  no longer bound in any way.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnClose(void)
    {
    /*
     * This doesn't have much to do with us again as it's only
     * used to notify the handler's IOleLink implementation of the
     * change in the object.  We don't have to do anything since
     * we'll also get an IOleClientSite::OnShowWindow(FALSE) to
     * tell us to repaint.
     */

    /*
     * If we are dealing with an OLE 1.0 server it may not call
     * IOleClientSite::OnShowWindow(FALSE) properly, so to protect
     * ourselves we make sure the object is drawn as closed on
     * this notification.
     */
    m_pTen->ShowAsOpen(FALSE);
    return;
    }


ICLISITE.CPP

/*
 * ICLISITE.CPP
 * Patron Chapter 20
 *
 * Implementation of the IOleClientSite interface for
 * Patron's tenants.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CImpIOleClientSite::CImpIOleClientSite
 * CImpIOleClientSite::~CImpIOleClientSite
 *
 * Parameters (Constructor):
 *  pTenant         PCTenant of the tenant we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleClientSite::CImpIOleClientSite(PCTenant pTenant
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pTen=pTenant;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleClientSite::~CImpIOleClientSite(void)
    {
    return;
    }

/*
 * CImpIOleClientSite::QueryInterface
 * CImpIOleClientSite::AddRef
 * CImpIOleClientSite::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleClientSite object.
 */

STDMETHODIMP CImpIOleClientSite::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleClientSite::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleClientSite::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleClientSite::SaveObject
 *
 * Purpose:
 *  Requests that the container call OleSave for the object that
 *  lives here.  Typically this happens on server shutdown.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::SaveObject(void)
    {
    //We're already set up with the tenant to save; this is trivial.
    m_pTen->Update();
    return NOERROR;
    }

/*
 * CImpIOleClientSite::GetMoniker
 *
 * Purpose:
 *  Retrieves the moniker for the site in which this object lives,
 *  either the moniker relative to the container or the full
 *  moniker.
 *
 * Parameters:
 *  dwAssign        DWORD specifying that the object wants moniker
 *                  assignment.  Yeah.  Right.  Got any bridges to
 *                  sell?
 *  dwWhich         DWORD identifying which moniker the object
 *                  wants, either the container's moniker, the
 *                  moniker relative to this client site, or the
 *                  full moniker.
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::GetMoniker(DWORD dwAssign
    , DWORD dwWhich, LPMONIKER *ppmk)
    {
    *ppmk=NULL;

    //CHAPTER20MOD
    /*
     * To support linking as a container we at least have to
     * implement this for OLEWHICKMK_CONTAINER but not for any
     * others.  This allows OLE to update absolute monikers when
     * it has to use the relative moniker to locate a link source.
     */

    switch (dwWhich)
        {
        case OLEWHICHMK_CONTAINER:
            //This is just the file we're living in.
            if (NULL!=m_pTen->m_pmkFile)
                *ppmk=m_pTen->m_pmkFile;

            break;
        }

    if (NULL==*ppmk)
        return ResultFromScode(E_FAIL);

    (*ppmk)->AddRef();
    return NOERROR;
    //End CHAPTER20MOD
    }

/*
 * CImpIOleClientSite::GetContainer
 *
 * Purpose:
 *  Returns a pointer to the document's IOleContainer interface.
 *
 * Parameters:
 *  ppContainer     LPOLECONTAINER * in which to return the
 *                  interface.
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::GetContainer(LPOLECONTAINER
    * ppContainer)
    {
    //Only necessary if you allow linking to embeddings
    *ppContainer=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleClientSite::ShowObject
 *
 * Purpose:
 *  Tells the container to bring the object fully into view as much
 *  as possible, that is, scroll the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::ShowObject(void)
    {
    /*
     * We let the tenant do this, since it can access the current
     * scroll position as a friend of CPages whereas we cannot.
     */
    m_pTen->ShowYourself();
    return NOERROR;
    }

/*
 * CImpIOleClientSite::OnShowWindow
 *
 * Purpose:
 *  Informs the container if the object is showing itself or
 *  hiding itself.  This is done only in the opening mode and allows
 *  the container to know when to shade or unshade the object.
 *
 * Parameters:
 *  fShow           BOOL indiciating that the object is being shown
 *                  (TRUE) or hidden (FALSE).
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::OnShowWindow(BOOL fShow)
    {
    //All we have to do is tell the tenant of the open state change.
    m_pTen->ShowAsOpen(fShow);
    return NOERROR;
    }

/*
 * CImpIOleClientSite::RequestNewObjectLayout
 *
 * Purpose:
 *  Called when the object needs more room in the container.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::RequestNewObjectLayout(void)
    {
    return ResultFromScode(E_NOTIMPL);
    }


IUILINK.CPP

/*
 * IUILINK.CPP
 * Patron Chapter 20
 *
 * Implementation of an object with the IOleUILinkContainer
 * interface necessary to use the standard Links Dialog.  This
 * is implemented as a stand-along object with access to the CPage
 * with which its associated, primiarily because it is only used
 * for the one dialog.  Therefore this object has it's own IUnknown.
 * In addition, we use the Links Assistant object developed in this
 * chapter to simplify our own code.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CIOleUILinkContainer::CIOleUILinkContainer
 * CIOleUILinkContainer::~CIOleUILinkContainer
 *
 * Parameters (Constructor):
 *  pPage           PCPage of the page we're in.
 */

CIOleUILinkContainer::CIOleUILinkContainer(PCPage pPage)
    {
    m_cRef=0;
    m_pPage=pPage;
    m_iTenant=0;
    m_pDelIUILinks=NULL;
    m_fDirty=FALSE;
    return;
    }

CIOleUILinkContainer::~CIOleUILinkContainer(void)
    {
    ReleaseInterface(m_pDelIUILinks);
    return;
    }

/*
 * CIOleUILinkContainer::Init
 *
 * Purpose:
 *  Performs initialization on the object that might fail.  In
 *  particular this creates an object of CLSID_LinksAssistant that
 *  helps in implementing this interface.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CIOleUILinkContainer::Init(void)
    {
    HRESULT     hr;

    hr=CoCreateInstance(CLSID_LinksAssistant, NULL
        , CLSCTX_INPROC_SERVER, IID_IOleUILinkContainer
        , (PPVOID)&m_pDelIUILinks);

    return SUCCEEDED(hr);
    }

/*
 * CIOleUILinkContainer::QueryInterface
 * CIOleUILinkContainer::AddRef
 * CIOleUILinkContainer::Release
 *
 * Purpose:
 *  IUnknown members for CIOleUILinkContainer object.
 */

STDMETHODIMP CIOleUILinkContainer::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IOleUILinkContainer==riid)
        {
        *ppv=this;
        AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CIOleUILinkContainer::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CIOleUILinkContainer::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CIOleUILinkContainer::GetNextLink
 *
 * Purpose:
 *  Called when the Links dialog is filling its listbox.  Here we
 *  need to return a key for the first link if dwLink is zero, then
 *  return the next links if it's non-zero.
 *
 * Parameters:
 *  dwLink          DWORD last returned from this function.  Zero if
 *                  this is the first call to this function.
 *
 * Return Value:
 *  DWORD           Some value that identifies this object.  Zero
 *                  stops the sequence such that this function is
 *                  no longer called.
 */

STDMETHODIMP_(DWORD) CIOleUILinkContainer::GetNextLink(DWORD dwLink)
    {
    PCTenant        pTenant;

    //If we're told to start the sequence, set index to zero.
    if (0L==dwLink)
        m_iTenant=0;

    /*
     * On each subsequent call, find the next linked object in
     * this document and return it.  Make sure the index is
     * incremented for the next time this function is called.
     */
    for ( ; m_iTenant < m_pPage->m_cTenants; m_iTenant++)
        {
        if (m_pPage->TenantGet(m_iTenant, &pTenant, FALSE))
            {
            if (TENANTTYPE_LINKEDOBJECT==pTenant->TypeGet())
                {
                m_iTenant++;
                return (DWORD)pTenant;
                }
            }
        }

    //If we hit the end of list, this tells the dialog to stop.
    return 0L;
    }

/*
 * CIOleUILinkContainer::SetLinkUpdateOptions
 *
 * Purpose:
 *  Informs the application to call IOleLink::SetUpdateOptions for
 *  the object identified by dwLink.
 *
 * Parameters:
 *  dwLink          DWORD object identifier as returned from
 *                  GetNextLink.
 *  dwOptions       DWORD containing the new options.
 *
 * Return Value:
 *  HRESULT         Return value of IOleLink::SetUpdateOptions.
 */

STDMETHODIMP CIOleUILinkContainer::SetLinkUpdateOptions(DWORD dwLink
    , DWORD dwOptions)
    {
    LPOLELINK       pIOleLink;
    HRESULT         hr;

    if (NULL==dwLink)
        return ResultFromScode(E_FAIL);

    /*
     * Your responsibility is to call the object's
     * IOleLink::SetUpdateOptions function with dwOptions.  Simple?
     *
     * For Patron we must first get the object pointer obtainable
     * from the tenant's ObjectGet function, then QI for IOleLink.
     */

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    hr=pIOleLink->SetUpdateOptions(dwOptions);
    pIOleLink->Release();

    m_fDirty=SUCCEEDED(hr);
    return hr;
    }

/*
 * CIOleUILinkContainer::GetLinkUpdateOptions
 *
 * Purpose:
 *  Requests the container to call IOleLink::GetUpdateOptions for
 *  the object identified by dwLink.
 *
 * Parameters:
 *  dwLink          DWORD identifying the object
 *  pdwOptions      LPDWORD in which to store the options.
 *
 * Return Value:
 *  HRESULT         Return value of IOleLink::GetUpdateOptions
 */

STDMETHODIMP CIOleUILinkContainer::GetLinkUpdateOptions(DWORD dwLink
    , LPDWORD pdwOptions)
    {
    LPOLELINK       pIOleLink;
    HRESULT         hr;

    if (NULL==dwLink)
        return ResultFromScode(E_FAIL);

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    hr=pIOleLink->GetUpdateOptions(pdwOptions);
    pIOleLink->Release();

    return hr;
    }

/*
 * CIOleUILinkContainer::SetLinkSource
 *
 * Purpose:
 *  Changes the moniker to which an object is linked.
 *
 * Parameters:
 *  dwLink          DWORD identifying the object in question.
 *  pszName         LPTSTR to the displayable name of the source.
 *  cchName         ULONG length of the file portaion of pszName
 *  pchEaten        ULONG * in which to return the number of
 *                  characters used in parsing pszDisplayName.
 *  fValidate       BOOL indicating if we're to validate that the
 *                  source exists first.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_FAIL otherwise.
 */

STDMETHODIMP CIOleUILinkContainer::SetLinkSource(DWORD dwLink
    , LPTSTR pszName, ULONG cchName, ULONG *pchEaten
    , BOOL fValidate)
    {
    PCTenant        pTenant=(PCTenant)dwLink;
    HRESULT         hr;
    LPOLELINK       pIOleLink;

    if (NULL==dwLink)
        return ResultFromScode(E_FAIL);

    //This is for use in GetLinkSource, below.
    pTenant->m_fLinkAvail=FALSE;

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    hr=m_pDelIUILinks->SetLinkSource((DWORD)pIOleLink, pszName
        , cchName, pchEaten, fValidate);
    pIOleLink->Release();

    if (FAILED(hr))
        return hr;

    //hr will be S_FALSE if link is unavailable.
    pTenant->Repaint();
    pTenant->m_fLinkAvail=(NOERROR==hr);
    m_fDirty=TRUE;
    return NOERROR;
    }

/*
 * CIOleUILinkContainer::GetLinkSource
 *
 * Purpose:
 *  Retrieves various strings and values for this link source.
 *
 * Parameters:
 *  dwLink          DWORD identifying the object affected.
 *  ppszName        LPTSTR * in which to return the new source
 *                  name
 *  pcchName        ULONG * in which to return the length of
 *                  pszName
 *  ppszFullLink    LPTSTR * in which to return the full name of
 *                  the class of linked object.
 *  ppszShortLink   LPTSTR * in which to return the short name of
 *                  the class of linked object.
 *  pfSourceAvail   BOOL * in which to return if this is an
 *                  available link source.
 *  pfSelected      BOOL * in which to return if this object is
 *                  currently selected in the document.  This
 *                  selects the item in the listbox for this object.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, error code otherwise.
 */

STDMETHODIMP CIOleUILinkContainer::GetLinkSource(DWORD dwLink
    , LPTSTR *ppszName, ULONG *pcchName, LPTSTR *ppszFullLink
    , LPTSTR *ppszShortLink, BOOL *pfSourceAvail, BOOL *pfSelected)
    {
    HRESULT         hr;
    PCTenant        pTenant=(PCTenant)dwLink;
    LPOLELINK       pIOleLink=NULL;
    LPOLEOBJECT     pIOleObject=NULL;
    LPMONIKER       pmk=NULL;
    LPMONIKER       pmkFirst=NULL;
    LPBC            pbc=NULL;

    if (NULL==dwLink)
        return ResultFromScode(E_FAIL);

    //We know what this is from SetLinkSource
    *pfSourceAvail=pTenant->m_fLinkAvail;

    if (pfSelected)
        *pfSelected=pTenant->FIsSelected();

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    hr=m_pDelIUILinks->GetLinkSource((DWORD)pIOleLink, ppszName
        , pcchName, ppszFullLink, ppszShortLink, pfSourceAvail
        , pfSelected);

    pIOleLink->Release();
    return hr;
    }

/*
 * CIOleUILinkContainer::OpenLinkSource
 *
 * Purpose:
 *  Asks the container to call DoVerb on this object with
 *  OLEIVERB_SHOW.
 *
 * Parameters:
 *  dwLink          DWORD identifying the linked object.
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CIOleUILinkContainer::OpenLinkSource(DWORD dwLink)
    {
    PCTenant        pTenant=(PCTenant)dwLink;

    pTenant->Activate(OLEIVERB_OPEN);
    return NOERROR;
    }

/*
 * CIOleUILinkContainer::UpdateLink
 *
 * Purpose:
 *  Asks the container to update the link for this object.
 *
 * Parameters:
 *  dwLink          DWORD identifying the linked object.
 *  fErrorMessage   BOOL indicating if we can show errors.
 *  fErrorAction    BOOL making no sense whatsoever.
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CIOleUILinkContainer::UpdateLink(DWORD dwLink
    , BOOL fErrorMessage, BOOL fErrorAction)
    {
    PCTenant        pTenant=(PCTenant)dwLink;
    LPOLELINK       pIOleLink;
    HRESULT         hr;

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    hr=m_pDelIUILinks->UpdateLink((DWORD)pIOleLink, fErrorMessage
        , fErrorAction);

    pTenant->Repaint();
    pTenant->m_fLinkAvail=SUCCEEDED(hr);
    pIOleLink->Release();

    if (FAILED(hr))
        {
        if (fErrorMessage)
            {
            MessageBox(m_pPage->m_hWnd
                , TEXT("Could not update link."), TEXT("Patron")
                , MB_OK);
            }
        }
    else
        m_fDirty=TRUE;

    return hr;
    }

/*
 * CIOleUILinkContainer::CancelLink
 *
 * Purpose:
 *  Requests that the container turn this linked object into a
 *  static object.
 *
 * Parameters:
 *  dwLink          DWORD identifying the linked object.
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CIOleUILinkContainer::CancelLink(DWORD dwLink)
    {
    PCTenant        pTenant=(PCTenant)dwLink;
    LPOLELINK       pIOleLink;
    HRESULT         hr;

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    //This sets the source moniker to NULL.
    m_pDelIUILinks->CancelLink((DWORD)pIOleLink);
    pIOleLink->Release();

    //Go change this object over to a static one.
    pTenant->ConvertToStatic();

    m_fDirty=TRUE;
    return NOERROR;
    }

//PROTECTED FUNCTIONS INTERNAL TO CIOleUILinkContainer

/*
 * CIOleUILinkContainer::GetObjectInterface
 * (Protected)
 *
 * Purpose:
 *  Retrieves and interface pointer for the object identified by
 *  dwLink
 *
 * Parameters:
 *  dwLink          DWORD identifying the object
 *  riid            REFIID of the interface desired.
 *  ppv             PPVOID into which we return the pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, error code otherwise.
 */

STDMETHODIMP CIOleUILinkContainer::GetObjectInterface(DWORD dwLink
    , REFIID riid, PPVOID ppv)
    {
    PCTenant        pTenant=(PCTenant)dwLink;
    LPUNKNOWN       pIUnknown;
    HRESULT         hr;

    pTenant->ObjectGet(&pIUnknown);
    hr=pIUnknown->QueryInterface(riid, ppv);
    pIUnknown->Release();

    return hr;
    }


PAGE.CPP

/*
 * PAGE.CPP
 * Patron Chapter 20
 *
 * Implementation of parts of the CPage class; those member
 * functions dealing with mouse events are in PAGEMOUS.CPP.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPage::CPage
 * CPage::~CPage
 *
 * Constructor Parameters:
 *  dwID            DWORD identifier for this page.
 *  hWnd            HWND of the pages window (for repaints, etc).
 *  pPG             PCPages to the Pages window.
 */

CPage::CPage(DWORD dwID, HWND hWnd, PCPages pPG)
    {
    m_dwID     =dwID;
    m_pIStorage=NULL;

    m_cOpens=0;
    m_hWnd=hWnd;
    m_pPG=pPG;

    m_dwIDNext      =0;
    m_cTenants      =0;
    m_hWndTenantList=NULL;
    m_iTenantCur    =NOVALUE;   //Tenants are zero indexed.
    m_pTenantCur    =NULL;

    m_uHTCode=HTNOWHERE;
    m_uSizingFlags=0;
    m_fTracking=FALSE;
    m_hDC=NULL;

    m_fDragPending=FALSE;
    m_fSizePending=FALSE;
    m_fTimer=FALSE;

    //Get WIN.INI distance and delay values, with OLE defaults.
    m_cxyDist=GetProfileInt(TEXT("windows"), TEXT("DragMinDist")
        , DD_DEFDRAGMINDIST);
    m_cDelay=GetProfileInt(TEXT("windows"), TEXT("DragDelay")
        , DD_DEFDRAGDELAY);

    //CHAPTER20MOD
    m_fReopen=FALSE;
    //End CHAPTER20MOD

    return;
    }

CPage::~CPage(void)
    {
    if (m_fTimer)
        KillTimer(m_hWnd, IDTIMER_DEBOUNCE);

    m_hWnd=NULL;
    Close(FALSE);
    return;
    }

/*
 * CPage::GetID
 *
 * Return Value:
 *  DWORD           dwID field in this page.
 */

DWORD CPage::GetID(void)
    {
    return m_dwID;
    }

/*
 * CPage::Open
 *
 * Purpose:
 *  Retrieves the IStorage associated with this page.  The IStorage
 *  is owned by the page and thus the page always holds a reference
 *  count.  The caller should call Close or delete this page to
 *  match this open.
 *
 *  This function may be called multiple times resulting in
 *  additional reference counts on the storage each of which must be
 *  matched with a call to Close.  The last Close can be done
 *  through delete.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which this page lives.
 *
 * Return Value:
 *  BOOL            TRUE if opening succeeds, FALSE otherwise.
 */

BOOL CPage::Open(LPSTORAGE pIStorage)
    {
    HRESULT                 hr=NOERROR;
    LPSTREAM                pIStream;
    DWORD                   dwMode;
    OLECHAR                 szTemp[32];
    TCHAR                   szCap[32];
    BOOL                    fNew;
    BOOL                    fCreated=FALSE;
    TENANTLIST              tl;
    PTENANTINFO             pti;
    ULONG                   cb;
    LPMALLOC                pIMalloc;
    UINT                    i;
    PCTenant                pTenant;
    //CHAPTER20MOD
    UINT                    cLinks;
    LPOLELINK               pIOleLink;
    LPUNKNOWN               pIUnknown;
    UINT                    uRet;
    OLEUIEDITLINKS          el;
    PCIOleUILinkContainer   pIUILinks;
    HWND                    hWndDoc;
    //End CHAPTER20MOD

    fNew=(NULL==m_pIStorage);

    if (!fNew)
        {
        m_cOpens++;
        m_pIStorage->AddRef();
        return TRUE;
        }

    if (NULL==pIStorage)
        return FALSE;

    /*
     * Attempt to open the storage under this ID.  If none,
     * create one.  In either case, the IStorage is either
     * saved in pPage or released.
     */

    GetStorageName(szTemp);
    dwMode=STGM_TRANSACTED | STGM_READWRITE | STGM_SHARE_EXCLUSIVE;

    hr=pIStorage->OpenStorage(szTemp, NULL, dwMode, NULL, 0
        , &m_pIStorage);

    if (FAILED(hr))
        {
        hr=pIStorage->CreateStorage(szTemp, dwMode, 0, 0
            , &m_pIStorage);
        fCreated=TRUE;
        }

    if (FAILED(hr))
        return FALSE;

    m_cOpens++;

    if (NULL==m_hWndTenantList)
        {
        /*
         * The first time we open this page, create the hidden
         * listbox we'll use to track tenants.  We give it the
         * owner-draw style so we can just store pointers in it.
         */
        m_hWndTenantList=CreateWindow(TEXT("listbox")
            , TEXT("Tenant List"), WS_POPUP | LBS_OWNERDRAWFIXED
            , 0, 0, 100, 100, HWND_DESKTOP, NULL
            , m_pPG->m_hInst, NULL);

        if (NULL==m_hWndTenantList)
            return FALSE;
        }

    //If this is brand-new, we're done.
    if (fCreated)
        return TRUE;

    /*
     * Now open the stream we saved in Close and load all the
     * tenants listed in there.  If there are none, then we don't
     * have to load squat.
     */

    hr=m_pIStorage->OpenStream(SZSTREAMTENANTLIST, NULL, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pIStream->Read(&tl, sizeof(tl), NULL);
        m_cTenants=tl.cTenants;
        m_dwIDNext=tl.dwIDNext;
        m_iTenantCur=0;

        cb=tl.cTenants*sizeof(TENANTINFO);

        if (0!=cb)
            {
            pti=(PTENANTINFO)pIMalloc->Alloc(cb);

            if (NULL!=pti)
                {
                pIStream->Read(pti, cb, NULL);

                for (i=0; i < m_cTenants; i++)
                    {
                    if (TenantAdd(NOVALUE, (pti+i)->dwID, &pTenant))
                        {
                        pTenant->Load(m_pIStorage, (pti+i));

                        //CHAPTER20MOD
                        //Make sure it knows about the show state.
                        pTenant->ShowObjectType(m_pPG->m_fShowTypes);
                        //End CHAPTER20MOD
                        }
                    }

                pIMalloc->Free(pti);
                }
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    //Get and select the first tenant
    if (TenantGet(0, &m_pTenantCur, FALSE))
        m_pTenantCur->Select(TRUE);

    //CHAPTER20MOD
    //If we just saved and closed, don't bother with updating links
    if (m_fReopen)
        {
        m_fReopen=FALSE;
        return TRUE;
        }

    /*
     * Update all the links in this page, showing the progress
     * indicator as it's happening.  We use the same
     * IOlUILinkContainer implementation as we do for the links
     * dialog, passing it to OleUIUpdateLinks which does everything
     * for us.
     *
     * We might also optimize this to not do anything if there are
     * no automatic links, but it's not a big concern.
     */

    //First, count the number of automatic links.
    cLinks=0;

    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            {
            DWORD       dw;

            pTenant->ObjectGet(&pIUnknown);
            hr=pIUnknown->QueryInterface(IID_IOleLink
                , (PPVOID)&pIOleLink);
            pIUnknown->Release();

            if (FAILED(hr))
                continue;

            pIOleLink->GetUpdateOptions(&dw);
            pIOleLink->Release();

            if (OLEUPDATE_ALWAYS==dw)
                cLinks++;
            }
        }

    //If we have any automatic links, invoke the update dialog.
    if (0==cLinks)
        return TRUE;

    //Create an IOleUILinkContainer instantiation.
    if (!m_pPG->GetUILinkContainer(&pIUILinks))
        return TRUE;    //Guess we can't update, oh well.

    hWndDoc=GetParent(m_hWnd);
    LoadString(m_pPG->m_hInst, IDS_CAPTION, szCap, sizeof(szCap));

    if (!OleUIUpdateLinks(pIUILinks, hWndDoc, szCap, cLinks))
        {
        /*
         * If updating failed, ask to show the links dialog.  NOTE:
         * OleUIPromptUser has a variable wsprintf argument list
         * after the hWnd parameter!  Use appropriate typecasting!
         */
        uRet=OleUIPromptUser(IDD_CANNOTUPDATELINK, hWndDoc, szCap);

       #ifdef IDC_PU_LINKS
        if (IDC_PU_LINKS==uRet)
       #else
        if (ID_PU_LINKS==uRet)      //Win3.1
       #endif
            {
            //Throw up the links dialog.
            memset(&el, 0, sizeof(el));
            el.cbStruct=sizeof(el);
            el.hWndOwner=hWndDoc;
            el.lpOleUILinkContainer=pIUILinks;
            OleUIEditLinks(&el);
            }
        }

    m_pPG->m_fDirty=pIUILinks->m_fDirty;
    pIUILinks->Release();
    //End CHAPTER20MOD
    return TRUE;
    }

/*
 * CPage::Close
 *
 * Purpose:
 *  Possibly commits the storage, then releases it reversing the
 *  reference count from Open.
 *
 * Parameters:
 *  fCommit         BOOL indicating if we're to commit.
 *
 * Return Value:
 *  None
 */

void CPage::Close(BOOL fCommit)
    {
    if (NULL==m_pIStorage)
        return;

    if (fCommit)
        Update();

    m_pIStorage->Release();

    //If this was the last close, make all tenants loaded->passive
    if (0==--m_cOpens)
        {
        UINT        i;
        PCTenant    pTenant;

        m_pIStorage=NULL;

        for (i=0; i < m_cTenants; i++)
            {
            if (TenantGet(i, &pTenant, FALSE))
                {
                if (NULL!=m_hWnd)
                    {
                    //Open may select again, so this repaints.
                    pTenant->Select(FALSE);
                    }

                pTenant->Close(FALSE);
                pTenant->Release();
                }
            }

        DestroyWindow(m_hWndTenantList);
        m_hWndTenantList=NULL;

        //CHAPTER20MOD
        /*
         * If we reopen this page right away, this flag will tell
         * us to skip updating links again which would make us
         * dirty right after a SaveAs operation.
         */
        m_fReopen=TRUE;
        //End CHAPTER20MOD
        }

    return;
    }

/*
 * CPage::Update
 *
 * Purpose:
 *  Forces a common on the page if it's open.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if there are any open objects on this page,
 *                  that is, we should remain open.
 */

BOOL CPage::Update(void)
    {
    BOOL            fOpen=FALSE;
    UINT            i;
    PCTenant        pTenant;
    HRESULT         hr;
    LPSTREAM        pIStream;
    TENANTLIST      tl;
    PTENANTINFO     pti;
    ULONG           cb;
    LPMALLOC        pIMalloc;

    //Walk the list of objects and update them all as well.
    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            fOpen |= pTenant->Update();
        }

    //Now write our own stream containing the tenant list.
    hr=m_pIStorage->CreateStream(SZSTREAMTENANTLIST, STGM_CREATE
        | STGM_WRITE| STGM_DIRECT | STGM_SHARE_EXCLUSIVE, 0, 0
        , &pIStream);

    if (FAILED(hr))
        return fOpen;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        tl.cTenants=m_cTenants;
        tl.dwIDNext=m_dwIDNext;

        pIStream->Write(&tl, sizeof(TENANTLIST), &cb);

        cb=m_cTenants*sizeof(TENANTINFO);
        pti=(PTENANTINFO)pIMalloc->Alloc(cb);

        if (NULL!=pti)
            {
            for (i=0; i < m_cTenants; i++)
                {
                TenantGet(i, &pTenant, FALSE);
                pTenant->GetInfo((pti+i));
                }

            pIStream->Write(pti, cb, &cb);
            pIMalloc->Free(pti);
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    //Now commit the whole mess and we're done
    if (NULL!=m_pIStorage)
        m_pIStorage->Commit(STGC_DEFAULT);

    return fOpen;
    }

/*
 * CPage::Destroy
 *
 * Purpose:
 *  Removes this page from the given storage.  The caller should
 *  eventually delete this Page object to free the storage.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE contianing this page on which to call
 *                  DestroyElement
 *
 * Return Value:
 *  None
 */

void CPage::Destroy(LPSTORAGE pIStorage)
    {
    if (NULL!=pIStorage)
        {
        OLECHAR szTemp[32];

        Close(FALSE);
        GetStorageName(szTemp);
        pIStorage->DestroyElement(szTemp);
        }

    return;
    }

/*
 * CPage::GetStorageName
 *
 * Parameters:
 *  pszName         LPOLESTR to a buffer in which to store the
 *                  storage name for this page.
 *
 * Return Value:
 *  UINT            Number of characters stored.
 */

UINT CPage::GetStorageName(LPOLESTR pszName)
    {
   #ifdef WIN32ANSI
    char        szTemp[32];
    UINT        cch;

    cch=wsprintf(szTemp, "Page %lu", m_dwID);
    MultiByteToWideChar(CP_ACP, 0, szTemp, -1, pszName, 32);
    return cch;
   #else
    return wsprintf(pszName, TEXT("Page %lu"), m_dwID);
   #endif
    }

/*
 * CPage::Draw
 *
 * Purpose:
 *  Draws the objects on this page to the given hDC.
 *
 * Parameters:
 *  hDC             HDC on which to draw.
 *  xOff, yOff      int offsets for the page.
 *  fNoColor        BOOL indicating black & white screen rendering.
 *  fPrinter        BOOL indicating hDC is on the printer.
 *
 * Return Value:
 *  None
 */

void CPage::Draw(HDC hDC, int xOff, int yOff, BOOL fNoColor
    , BOOL fPrinter)
    {
    int                 i;
    PCTenant            pTenant;
    HDC                 hIC=NULL;
    PCOMBINEDEVICE      pcd=NULL;
    DVTARGETDEVICE     *ptd=NULL;

    /*
     * If printing, tell the tenant to forget the borders. Otherwise
     * we leave xOff and yOff the same to account for scrolling.
     */
    if (fPrinter)
        {
        xOff=LOMETRIC_BORDER+m_pPG->m_xMarginLeft;
        yOff=-LOMETRIC_BORDER-m_pPG->m_yMarginTop;

        /*
         * Get device information.  If this fails, ptd is
         * NULL which is acceptable.
         */
        if (m_pPG->DevReadConfig(&pcd, &hIC))
            ptd=&(pcd->td);
        }

    for (i=(int)m_cTenants-1; i >=0; i--)
        {
        if (TenantGet(i, &pTenant, FALSE))
            {
            RECT        rc, rcWin;
            RECTL       rcl;

            //Paint this tenant only if visible.
            pTenant->RectGet(&rcl, TRUE);
            RECTFROMRECTL(rc, rcl);
            OffsetRect(&rc, -(int)m_pPG->m_xPos
                , -(int)m_pPG->m_yPos);
            GetClientRect(m_hWnd, &rcWin);

            if (IntersectRect(&rc, &rc, &rcWin))
                {
                pTenant->Draw(hDC, ptd, hIC, xOff, yOff
                    , fNoColor, fPrinter);
                }
            }
        }

    //Free whatever CPages::DevReadConfig returned.
    if (NULL!=pcd)
        {
        LPMALLOC    pIMalloc;

        if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
            {
            pIMalloc->Free(pcd);
            pIMalloc->Release();
            }
        }

    if (NULL!=hIC)
        DeleteDC(hIC);

    return;
    }

/*
 * CPage::TenantCreate
 *
 * Purpose:
 *  Creates a new tenant of a specific type.
 *
 * Parameters:
 *  tType           TENANTTYPE to create.
 *  pv              LPVOID providing information for the new
 *                  object creation.
 *  pFE             LPFORMATETC describing how we want this
 *                  rendered.
 *  ppo             PPATRONOBJECT with placement data.
 *  dwData          DWORD extra data to pass to the tenant.
 *
 * Return Value:
 *  None
 */

BOOL CPage::TenantCreate(TENANTTYPE tType, LPVOID pv
    , LPFORMATETC pFE, PPATRONOBJECT ppo, DWORD dwData)
    {
    PCTenant    pTenant;
    UINT        uRet;
    int         x, y;
    int         h, v;
    POINTL      ptl;
    SIZEL       szl;
    RECTL       rcl;
    RECT        rc;

    //New tenants go at top of the pile; zero index to TenantAdd.
    if (!TenantAdd(0, m_dwIDNext, &pTenant))
        return FALSE;

    uRet=pTenant->Create(tType, pv, pFE, &ptl, &szl, m_pIStorage
        , ppo, dwData);

    if (CREATE_FAILED==uRet)
        {
        //Reverse Create AND TenantAdd
        SendMessage(m_hWndTenantList, LB_DELETESTRING, 0, 0L);
        pTenant->Destroy(m_pIStorage);

        pTenant->Release();
        return FALSE;
        }

    m_dwIDNext++;
    m_cTenants++;

    if (NULL!=m_pTenantCur)
        m_pTenantCur->Select(FALSE);

    m_iTenantCur=0;             //First one in the list now.
    m_pTenantCur=pTenant;

    //Tell the tenant where it lives, default is (0,0) in print area
    x=LOMETRIC_BORDER+m_pPG->m_xMarginLeft;
    y=-LOMETRIC_BORDER-m_pPG->m_yMarginTop;

    h=x;
    v=y;

    if (CREATE_PLACEDOBJECT==uRet)
        {
        SetRect(&rc, 3*CXYHANDLE, 3*CXYHANDLE, 0, 0);
        RectConvertMappings(&rc, NULL, FALSE);

        //Make sure place point is on page, otherwise go to (0,0)
        if (((int)ptl.x > x)
            && ((int)ptl.x < x+(int)m_pPG->m_cx-rc.left))
            x=(int)ptl.x;

        //m_pPG->m_cy is absolute
        if (((int)ptl.y < y)
            && ((int)ptl.y > y-(int)m_pPG->m_cy-rc.top))
            y=(int)ptl.y;
        }

    //Bounds check size of the object and fit to page as necessary.
    if (x+(int)szl.cx > (int)(h+m_pPG->m_cx))
        szl.cx=h+m_pPG->m_cx-x;

    //Remember that szl we get from Create is absolute
    if (y-(int)szl.cy < (int)(v-m_pPG->m_cy))
        szl.cy=-(int)(v-m_pPG->m_cy-y);

    SETRECTL(rcl, x, y, x+szl.cx, y-szl.cy);
    m_pTenantCur->RectSet(&rcl, FALSE, TRUE);

    //Force a repaint on this new guy
    m_pTenantCur->Invalidate();
    UpdateWindow(m_hWnd);

    m_pTenantCur->Select(TRUE);

    //CHAPTER20MOD
    //Make sure this new tenant knows about showing it's type.
    m_pTenantCur->ShowObjectType(m_pPG->m_fShowTypes);
    //End CHAPTER20MOD

    //Activate new objects immediately and force a save on them
    if (TENANTTYPE_EMBEDDEDOBJECT==tType)
        {
        m_pTenantCur->Activate(OLEIVERB_SHOW);
        m_pTenantCur->Update();
        }

    return TRUE;
    }

/*
 * CPage::TenantDestroy
 *
 * Purpose:
 *  Destroys the currently selected tenant on this page.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPage::TenantDestroy(void)
    {
    if (NULL==m_pTenantCur)
        {
        MessageBeep(0);
        return FALSE;
        }

    SendMessage(m_hWndTenantList, LB_DELETESTRING
        , m_iTenantCur, 0L);

    m_pTenantCur->Invalidate();
    m_pTenantCur->Destroy(m_pIStorage);
    m_pTenantCur->Release();
    m_pTenantCur=NULL;

    //Update counts, etc., and select the next tenant in the list.
    if (m_iTenantCur==m_cTenants-1)
        m_iTenantCur--;

    if (0==--m_cTenants)
        m_pTenantCur=NULL;
    else
        {
        TenantGet(m_iTenantCur, &m_pTenantCur, TRUE);
        m_pTenantCur->Select(TRUE);
        }

    UpdateWindow(m_hWnd);
    return TRUE;
    }

/*
 * CPage::TenantClip
 *
 * Purpose:
 *  Copies or cuts the currently selected tenant to the clipoard.
 *
 * Parameters:
 *  fCut            BOOL TRUE to cut the object, FALSE to copy.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPage::TenantClip(BOOL fCut)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet=FALSE;

    if (NULL==m_pTenantCur)
        return FALSE;

    /*
     * To perform a data transfer operation, we need to create a
     * data object with the selected object's data inside. To do
     * this we CoCreateInstance on CLSID_DataTransferObject
     * (Also implemented in this chapter), retrieve data from the
     * object we have, stuff that data into the transfer object,
     * then stick that object on the clipboard.
     *
     * Since we'll want an identical object at other times, like for
     * drag-drop, we use a private function to actually create it.
     */

    pIDataObject=TransferObjectCreate(NULL);

    if (NULL!=pIDataObject)
        {
        if (SUCCEEDED(OleSetClipboard(pIDataObject)))
            {
            if (fCut)
                TenantDestroy();

            fRet=TRUE;
            }

        pIDataObject->Release();
        }

    return fRet;
    }

/*
 * CPage::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected on this
 *  page for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU of the Edit menu.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPage::FQueryObjectSelected(HMENU hMenu)
    {
    HMENU       hMenuTemp;

    /*
     * This will only be called on WM_INITMENUPOPUP, we'll also
     * use this function to create the Verb menu for this object.
     */
    if (NULL!=m_pTenantCur)
        {
        m_pTenantCur->AddVerbMenu(hMenu, MENUPOS_OBJECT);
        return TRUE;
        }

    OleUIAddVerbMenu(NULL, NULL, hMenu, MENUPOS_OBJECT
        , IDM_VERBMIN, IDM_VERBMAX, FALSE, 0, &hMenuTemp);

    return FALSE;
    }

/*
 * CPage::ActivateObject
 *
 * Purpose:
 *  Executes a verb on the currently selected object.
 *
 * Parameters:
 *  iVerb           LONG of the selected verb.
 *
 * Return Value:
 *  None
 */

void CPage::ActivateObject(LONG iVerb)
    {
    if (NULL==m_pTenantCur)
        return;

    m_pTenantCur->Activate(iVerb);
    return;
    }

//CHAPTER20MOD

/*
 * CPage::ShowObjectTypes
 *
 * Purpose:
 *  Loops through all the tenants and tells each one to turn on or
 *  off the Show Objects features.
 *
 * Parameters:
 *  fShow           BOOL indicating to show the type or not.
 *
 * Return Value:
 *  None
 */

void CPage::ShowObjectTypes(BOOL fShow)
    {
    PCTenant    pTenant;
    UINT        i;

    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            pTenant->ShowObjectType(fShow);
        }

    return;
    }

/*
 * CPage::NotifyTenantsOfRename
 *
 * Purpose:
 *  Loops through all the tenants and informs each of the new
 *  document name.
 *
 * Parameters:
 *  pszFile         LPTSTR of the new filename.
 *  pmk             LPMONKIER for the new filename.
 *
 * Return Value:
 *  None
 */

void CPage::NotifyTenantsOfRename(LPTSTR pszFile, LPMONIKER pmk)
    {
    PCTenant    pTenant;
    UINT        i;

    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            pTenant->NotifyOfRename(pszFile, pmk);
        }

    return;
    }

/*
 * CPage::ConvertObject
 *
 * Purpose:
 *  Invokes and handles the results of the Convert dialog
 *
 * Parameters:
 *  hWndFrame       HWND to use as the parent of the dialog.
 *  fNoServer       BOOL indicating if this was called because
 *                  ActivateObject failed.
 *
 * Return Value:
 *  None
 */

BOOL CPage::ConvertObject(HWND hWndFrame, BOOL fNoServer)
    {
    HRESULT         hr;
    OLEUICONVERT    ct;
    TENANTTYPE      tType;
    TENANTINFO      ti;
    UINT            uRet;
    HCURSOR         hCur;
    BOOL            fActivate=fNoServer;
    SIZEL           szl;

    if (NULL==m_pTenantCur)
        return FALSE;

    tType=m_pTenantCur->TypeGet();

    if (TENANTTYPE_STATIC==tType)
        {
        MessageBeep(0);
        return FALSE;
        }

    //Get object information we may want.
    m_pTenantCur->GetInfo(&ti);

    //Fill the structure.
    memset(&ct, 0, sizeof(ct));
    ct.cbStruct=sizeof(OLEUICONVERT);
    ct.hWndOwner=hWndFrame;
    //CHAPTER20MOD
    ct.fIsLinkedObject=(TENANTTYPE_LINKEDOBJECT==tType);
    //End CHAPTER20MOD
    ct.dvAspect=ti.fe.dwAspect;

    m_pTenantCur->ObjectClassFormatAndIcon(&ct.clsid, &ct.wFormat
        , &ct.lpszUserType, &ct.hMetaPict, &ct.lpszDefLabel);

    uRet=OleUIConvert(&ct);

    if (OLEUI_OK==uRet)
        {
        //Potentially a long operation.
        hCur=SetCursor(LoadCursor(NULL, IDC_WAIT));

        //Prevent multiple repaints.
        m_pTenantCur->EnableRepaint(FALSE);

        //First, let's bother with the iconic aspect switch.
        if ((DVASPECT_ICON==ct.dvAspect && ct.fObjectsIconChanged)
            || ct.dvAspect!=ti.fe.dwAspect)
            {
            HGLOBAL     hMem=NULL;

            //Only pass non-NULL handle for icon aspects.
            if (DVASPECT_ICON==ct.dvAspect)
                hMem=ct.hMetaPict;

            m_pPG->m_fDirty=m_pTenantCur->SwitchOrUpdateAspect(hMem
                , FALSE);
            }

        //Now change types around.
        if ((CF_SELECTCONVERTTO & ct.dwFlags)
            && ct.clsid!=ct.clsidNew)
            {
            LPSTORAGE   pIStorage;

            /*
             * User selected convert, so:
             *  1.  Unload the object (back to passive state)
             *  2.  Call INOLE_DoConvert, which calls WriteClassStg,
             *      WriteFmtUserTypeStg, and SetConvertStg.
             *  3.  Reload the object and force an update.
             */

            //This should be the only close necessary.
            m_pTenantCur->StorageGet(&pIStorage);
            m_pTenantCur->Close(TRUE);

            hr=INOLE_DoConvert(pIStorage, ct.clsidNew);

            //Need to commit the new type and format
            pIStorage->Commit(STGC_DEFAULT);
            pIStorage->Release();

            if (SUCCEEDED(hr))
                {
                LPUNKNOWN   pObj;
                LPOLEOBJECT pIOleObject;

                //Reload and update.
                m_pTenantCur->Load(m_pIStorage, &ti);

                m_pTenantCur->ObjectGet(&pObj);
                pObj->QueryInterface(IID_IOleObject
                    , (PPVOID)&pIOleObject);
                pIOleObject->Update();
                pIOleObject->Release();
                pObj->Release();
                }

            m_pPG->m_fDirty=TRUE;
            }

        if (CF_SELECTACTIVATEAS & ct.dwFlags)
            {
            /*
             * User selected Activate As, so:
             *  1.  Add the TreatAs entry in the registry
             *      through CoTreatAsClass
             *  2.  Unload all objects of the old CLSID that you
             *      have loaded.
             *  3.  Reload objects as desired
             *  4.  Activate the current object.
             */

            hr=CoTreatAsClass(ct.clsid, ct.clsidNew);

            if (SUCCEEDED(hr))
                {
                PCTenant    pTenant;
                UINT        i;

                for (i=0; i < m_cTenants; i++)
                    {
                    if (TenantGet(i, &pTenant, FALSE))
                        {
                        pTenant->GetInfo(&ti);
                        pTenant->Close(FALSE);
                        pTenant->Load(m_pIStorage, &ti);
                        }
                    }

                fActivate=TRUE;
                }
            }

        //These two steps insure the object knows of the size.
        m_pTenantCur->SizeGet(&szl, FALSE);
        m_pTenantCur->SizeSet(&szl, FALSE, TRUE);

        m_pTenantCur->EnableRepaint(TRUE);
        m_pTenantCur->Repaint();

        if (fActivate)
            m_pTenantCur->Activate(OLEIVERB_SHOW);

        SetCursor(hCur);
        }

    CoTaskMemFree((void*)ct.lpszUserType);
    INOLE_MetafilePictIconFree(ct.hMetaPict);

    return TRUE;
    }

//CHAPTER17MOD

/*
 * CPage::FQueryLinksInPage
 *
 * Purpose:
 *  Pass through to current page to see if there are any linked
 *  objects.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPage::FQueryLinksInPage()

{
    PCTenant    pTenant;
    UINT        i;
    BOOL        fRet=FALSE;

    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            fRet |= (pTenant->TypeGet()==TENANTTYPE_LINKEDOBJECT);
        }

    return fRet;
    }
//End CHAPTER20MOD

/*
 * CPage::TenantGet
 * (Protected)
 *
 * Purpose:
 *  Returns a tenant of a given index returning a BOOL so it's
 *  simple to use this function inside if statements.
 *
 * Parameters:
 *  iTenant         UINT tenant to retrieve 0 based.
 *  ppTenant        PCPage * in which to return the tenant
 *                  pointer
 *  fOpen           BOOL indicating if we should open this
 *                  tenant as well.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPage::TenantGet(UINT iTenant, PCTenant *ppTenant
    , BOOL fOpen)
    {
    if (NULL==ppTenant)
        return FALSE;

    if (LB_ERR!=SendMessage(m_hWndTenantList, LB_GETTEXT
        , iTenant, (LONG)ppTenant))
        {
        if (fOpen)
            (*ppTenant)->Open(m_pIStorage);

        return TRUE;
        }

    return FALSE;
    }

/*
 * CPage::TenantAdd
 * (Protected)
 *
 * Purpose:
 *  Creates a new tenant initialized to the given values.  The new
 *  tenants's storage is created if it does not already exist.  If
 *  fOpenStorage is set the the tenants's storage is opened and left
 *  opened.
 *
 * Parameters:
 *  iTenant         UINT Location at which to insert tenant; new
 *                  tenant is inserted after this position.  NOVALUE
 *                  for the end.
 *  dwID            DWORD ID for this tenant.
 *  ppNew           PCTenant * in which to store the new tenant.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPage::TenantAdd(UINT iTenant, DWORD dwID
    , PCTenant *ppNew)
    {
    PCTenant    pTenant;
    LRESULT     lr;

    if (NULL!=ppNew)
        *ppNew=NULL;

    pTenant=new CTenant(dwID, m_hWnd, m_pPG);

    if (NULL==pTenant)
        return FALSE;

    //The constructor doesn't AddRef, so we need to.
    pTenant->AddRef();

    //Now try to add to the listbox.
    lr=SendMessage(m_hWndTenantList, LB_INSERTSTRING, iTenant
        , (LONG)pTenant);

    if (lr < 0)
        {
        pTenant->Release();
        return FALSE;
        }

    *ppNew=pTenant;
    return TRUE;
    }

/*
 * CPage::TransferObjectCreate
 * (Protected)
 *
 * Purpose:
 *  Creates a DataTransferObject and stuff the current selection's
 *  data into it.
 *
 * Parameters:
 *  pptl            PPOINTL containing the pick point in device
 *                  units applicable only to drag-drop; since
 *                  drag-drop is inherently mouse oriented, we use
 *                  device units for the point.  Ignored if NULL.
 *
 * Return Value:
 *  LPDATAOBJECT    Pointer to the object created, NULL on failure
 */

LPDATAOBJECT CPage::TransferObjectCreate(PPOINTL pptl)
    {
    LPDATAOBJECT    pIDataObject;
    LPDATAOBJECT    pIDataT;
    PPATRONOBJECT   ppo;
    RECTL           rcl;
    LPUNKNOWN       pObj;
    FORMATETC       fe;
    STGMEDIUM       stm;
    HRESULT         hr;

    m_pTenantCur->ObjectGet(&pObj);

    hr=CoCreateInstance(CLSID_DataTransferObject, NULL
        , CLSCTX_INPROC_SERVER, IID_IDataObject
        , (PPVOID)&pIDataObject);

    if (FAILED(hr))
        return NULL;

    //Go get the data we should hold on to.
    hr=pObj->QueryInterface(IID_IDataObject, (PPVOID)&pIDataT);

    if (FAILED(hr))
        {
        pIDataObject->Release();
        pObj->Release();
        return NULL;
        }

    //Copy from known obj into transfer obj.  Ordering is important!

    //Generate placeable object structure
    stm.tymed=TYMED_HGLOBAL;
    stm.pUnkForRelease=NULL;
    stm.hGlobal=GlobalAlloc(GHND, sizeof(PATRONOBJECT));

    if (NULL==stm.hGlobal)
        {
        pIDataObject->Release();
        pObj->Release();
        return NULL;
        }

    ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);

    m_pTenantCur->SizeGet(&ppo->szl, FALSE);
    ppo->szl.cy=-ppo->szl.cy; //Negate to make absolute size

    m_pTenantCur->RectGet(&rcl, FALSE);
    ppo->ptl.x=rcl.left;
    ppo->ptl.y=rcl.top;

    if (NULL==pptl)
        {
        ppo->ptlPick.x=0;
        ppo->ptlPick.y=0;
        }
    else
        ppo->ptlPick=*pptl;

    m_pTenantCur->FormatEtcGet(&ppo->fe, FALSE);

    //CHAPTER20MOD
    //If this is a linked object, just copy a presentation
    if (TENANTTYPE_LINKEDOBJECT==m_pTenantCur->TypeGet())
        m_pTenantCur->FormatEtcGet(&ppo->fe, TRUE);
    //End CHAPTER20MOD

    SETDefFormatEtc(fe, m_pPG->m_cf, TYMED_HGLOBAL);
    pIDataObject->SetData(&fe, &stm, TRUE);

    /*
     * Here now we have to include CFSTR_EMBEDDEDOBJECT and
     * CFSTR_OBJECTDESCRIPTOR when what we have selected is, in
     * fact, a compound document object.  We'll just ask the tenant
     * to set these in pIDataObject since it knows what the object.
     * If we copy embedded object data, make sure the PATRONOBJECT
     * structure has the right format in it as well.
     */
    m_pTenantCur->CopyEmbeddedObject(pIDataObject, &ppo->fe, pptl);

    GlobalUnlock(stm.hGlobal);

    //Copy the actual presentation.
    m_pTenantCur->FormatEtcGet(&fe, TRUE);
    pIDataT->GetData(&fe, &stm);
    pIDataObject->SetData(&fe, &stm, TRUE);

    pIDataT->Release();

    pObj->Release();
    return pIDataObject;    //Caller now responsible
    }


PAGEMOUS.CPP

/*
 * PAGEMOUS.CPP
 * Patron Chapter 20
 *
 * Implementation of mouse-related member functions of CPage.
 * The remainder is in PAGE.CPP.  This separate file keeps this
 * grungy hit-testing/drawing code out of our way.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

//Lookups into the array using g_rgHTCode[x+y*3] in PAGEMOUS.CPP
#define YTOP            0
#define YMID            1
#define YBOT            2
#define XLEFT           0
#define XMID            1
#define XRIGHT          2

//Values to restrict sizing in CPage::OnMouseMove
#define SIZINGTOP       0x0001
#define SIZINGBOTTOM    0x0002
#define SIZINGLEFT      0x0004
#define SIZINGRIGHT     0x0008

//This array is for hit-testing lookups
static UINT g_rgHTCode[9]={HTTOPLEFT, HTTOP, HTTOPRIGHT
    , HTLEFT, HTCLIENT, HTRIGHT, HTBOTTOMLEFT, HTBOTTOM
    , HTBOTTOMRIGHT};

//This is for restricting tracking based on the hit-test
static UINT g_rguSizingFlags[9]={SIZINGTOP | SIZINGLEFT, SIZINGTOP
    , SIZINGTOP | SIZINGRIGHT, SIZINGLEFT, 0, SIZINGRIGHT
    , SIZINGBOTTOM | SIZINGLEFT, SIZINGBOTTOM
    , SIZINGBOTTOM | SIZINGRIGHT};

/*
 * CPage::OnRightDown
 *
 * Purpose:
 *  Called when the user clicks with the right button on this
 *  page.  If there is an object here, determined by the last
 *  hit-test code, the we'll make a popup-menu for it.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if the action changed the object.
 */

BOOL CPage::OnRightDown(UINT uKeys, UINT x, UINT y)
    {
    HMENU       hMenu;
    HMENU       hMenuRes;
    HINSTANCE   hInst;
    HWND        hWndFrame, hWndT;
    POINT       pt;
    UINT        i, cItems;

    //Select the tenant under the mouse, if there is one.
    if (!SelectTenantAtPoint(x, y))
        return FALSE;

    /*
     * Get the top-level window to which menu command will go.  This
     * will be whatever parent doesn't have a parent itself...
     */
    hWndT=GetParent(m_hWnd);

    while (NULL!=hWndT)
        {
        hWndFrame=hWndT;
        hWndT=GetParent(hWndT);
        }

    /*
     * Build a popup menu for this object with Cut, Copy, Delete,
     * and object verbs.
     */
    hInst=GETWINDOWINSTANCE(m_hWnd);    //Macro in BOOK1632.H
    hMenuRes=LoadMenu(hInst, MAKEINTRESOURCE(IDR_RIGHTPOPUPMENU));

    if (NULL==hMenuRes)
        return FALSE;

    hMenu=CreatePopupMenu();
    cItems=GetMenuItemCount(hMenuRes);

    for (i=0; i < cItems; i++)
        {
        TCHAR       szTemp[80];
        int         id, uFlags;

        GetMenuString(hMenuRes, i, szTemp, sizeof(szTemp)
            , MF_BYPOSITION);
        id=GetMenuItemID(hMenuRes, i);

        uFlags=(0==id) ? MF_SEPARATOR : MF_STRING | MF_ENABLED;
        AppendMenu(hMenu, uFlags, id, szTemp);
        }

    DestroyMenu(hMenuRes);

    //Munge the Object menu item
    m_pTenantCur->AddVerbMenu(hMenu, MENUPOS_OBJECTONPOPUP);

    //CHAPTER20MOD
    //Enable or disable the Links item.
    i=FQueryLinksInPage() ? MF_ENABLED : MF_DISABLED | MF_GRAYED;
    EnableMenuItem(hMenu, IDM_EDITLINKS, i | MF_BYCOMMAND);
    //End CHAPTER20MOD

    SETPOINT(pt, x, y);
    ClientToScreen(m_hWnd, &pt);

    TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON
        , pt.x, pt.y, 0, hWndFrame, NULL);

    DestroyMenu(hMenu);
    return FALSE;
    }

/*
 * CPage::SelectTenantAtPoint
 *
 * Purpose:
 *  Selects whatever tenant is at the point (x,y) if there is one,
 *  deselecting the previously selected tenant.
 *
 * Parameters:
 *  x, y            UINT coordinates of the mouse.
 *
 * Return Value:
 *  BOOL            TRUE if there is a tenant here, FALSE otherwise.
 */

BOOL CPage::SelectTenantAtPoint(UINT x, UINT y)
    {
    UINT            iTenant;
    PCTenant        pTenant;
    PCDocument      pDoc;

    iTenant=TenantFromPoint(x, y, &pTenant);

    if (NULL==pTenant)
        return FALSE;

    //Make the document window active in any case
    pDoc=(PCDocument)SendMessage(GetParent(m_hWnd), DOCM_PDOCUMENT
        , 0, 0L);

    if (NULL!=pDoc)
        BringWindowToTop(pDoc->Window());

    //If this one is already current, we might be now sizing.
    if (pTenant==m_pTenantCur)
        return TRUE;

    //Deselect the current tenant
    if (NULL!=m_pTenantCur)
        m_pTenantCur->Select(FALSE);

    //Move this tenant to the top of the list
    m_iTenantCur=0;

    SendMessage(m_hWndTenantList, LB_DELETESTRING, iTenant, 0L);
    SendMessage(m_hWndTenantList, LB_INSERTSTRING, 0
        , (LONG)pTenant);

    //Select and repaint the new tenant to show it up front
    m_pTenantCur=pTenant;

    m_pTenantCur->Repaint();
    m_pTenantCur->Select(TRUE);

    return TRUE;
    }

/*
 * CPage::OnLeftDown
 *
 * Purpose:
 *  Called when the user clicks with the left button on this page.
 *  We find the object under that position that is visibly on top
 *  (always the first one under this location in the page list since
 *  we paint in reverse order) and select it.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if the action changed the object.
 */

BOOL CPage::OnLeftDown(UINT uKeys, UINT x, UINT y)
    {
    /*
     * If the mouse is in a position to start dragging,
     * start the timer as with sizing below.
     */
    if (HTCAPTION==m_uHTCode)
        {
        m_fDragPending=TRUE;

        //Save down point and start timer.
        m_ptDown.x=x;
        m_ptDown.y=y;

        m_uKeysDown=uKeys;

        m_fTimer=TRUE;
        SetTimer(m_hWnd, IDTIMER_DEBOUNCE, m_cDelay, NULL);
        return FALSE;
        }

    /*
     * If the mouse is in a position to start sizing, start
     * the debounce timer and note the condition.  The sizing
     * will start in OnTimer or OnMouseMove.  This will always
     * happen on the currently selected tenant, and m_uHTCode is
     * set in OnNCHitTest below.
     */
    if (HTNOWHERE!=m_uHTCode && HTCLIENT!=m_uHTCode)
        {
        m_fSizePending=TRUE;

        //Save down point and start timer.
        m_ptDown.x=x;
        m_ptDown.y=y;

        m_fTimer=TRUE;
        SetTimer(m_hWnd, IDTIMER_DEBOUNCE, m_cDelay, NULL);
        return FALSE;
        }

    SelectTenantAtPoint(x, y);
    return FALSE;
    }

/*
 * CPage::OnLeftUp
 *
 * Purpose:
 *  Called when the user clicks up with the left button on this
 *  page. We stop tracking on this message, if necessary, and
 *  resize the object.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if this action changed the object.
 */

BOOL CPage::OnLeftUp(UINT uKeys, UINT x, UINT y)
    {
    RECT    rc, rcT;

    if (m_fSizePending || m_fDragPending)
        {
        m_fSizePending=FALSE;
        m_fDragPending=FALSE;

        if (m_fTimer)
            {
            KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
            m_fTimer=FALSE;
            }

        return FALSE;
        }

    if (!m_fTracking)
        return FALSE;

    //Remove the dotted rectangle.
    RECTFROMRECTL(rc, m_rcl)
    DrawFocusRect(m_hDC, &rc);
    ReleaseDC(m_hWnd, m_hDC);

    ReleaseCapture();
    m_fTracking=FALSE;

    //If the original and new rects are the same, nothing happened.
    RECTFROMRECTL(rcT, m_rclOrg);

    if (EqualRect(&rc, &rcT))
        return FALSE;

    RECTFROMRECTL(rcT, m_rclOrg);
    InvalidateRect(m_hWnd, &rcT, TRUE);

    //Invalidate on the screen before accounting for scrolling
    InvalidateRect(m_hWnd, &rc, TRUE);

    //Factor in scrolling and tell the tenant where it now stands.
    OffsetRect(&rc, (int)m_pPG->m_xPos, (int)m_pPG->m_yPos);
    RECTLFROMRECT(m_rcl, rc);
    m_pTenantCur->RectSet(&m_rcl, TRUE, TRUE);

    UpdateWindow(m_hWnd);
    return TRUE;
    }

/*
 * CPage::OnLeftDoubleClick
 *
 * Purpose:
 *  Called when the user double-clicks with the left button on this
 *  page.  We find the object under that position that is visibly on
 *  top (always the first one under this location in the page list
 *  since we paint in reverse order) and activate it.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if the action changed the object.
 */

BOOL CPage::OnLeftDoubleClick(UINT uKeys, UINT x, UINT y)
    {
    /*
     * The current tenant is the only one that can be activated, so
     * we just have to make sure the mouse is there.  For that we
     * can use the last hit-test code we saw since it's updated on
     * every mouse move.
     */

    if (HTNOWHERE!=m_uHTCode)
        return m_pTenantCur->Activate(OLEIVERB_PRIMARY);

    return FALSE;
    }

/*
 * CPage::OnMouseMove
 *
 * Purpose:
 *  Processes WM_MOUSEMOVE on a page so we can handle tracking
 *  resize of a tenant.
 *
 * Parameters:
 *  x, y            int device coordinates to check.
 *
 * Return Value:
 *  None
 */

void CPage::OnMouseMove(UINT uKeys, int x, int y)
    {
    RECT        rc, rcO, rcB;
    int         cxy;

    if (m_fSizePending || m_fDragPending)
        {
        int     dx, dy;

        dx=(x > m_ptDown.x) ? (x-m_ptDown.x) : (m_ptDown.x-x);
        dy=(y > m_ptDown.y) ? (y-m_ptDown.y) : (m_ptDown.y-y);

        /*
         * Has the mouse moved outside the debounce distance?  If
         * so, we can start sizing.  Note that this happens
         * regardless of the timer state.
         */
        if (dx > m_cxyDist || dy > m_cxyDist)
            {
            POINT       pt;
            BOOL        fSize=m_fSizePending;
            BOOL        fDrag=m_fDragPending;

            m_fSizePending=FALSE;
            m_fDragPending=FALSE;

            if (m_fTimer)
                {
                KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
                m_fTimer=FALSE;
                }

            if (fDrag)
                {
                //Set dirty flag if drag & drop changed things.
                m_pPG->m_fDirty |= DragDrop(m_uKeysDown, x, y);
                return;
                }

            if (fSize)
                StartSizeTracking();

            /*
             * Since we might have moved out of the sizing handle
             * in order to start the operation, we need to set the
             * m_uSizingFlags field based on the original down point
             * for subsequent mouse moves to function properly.
             * Note that OnNCHitTest expects screen coordinates.
             */
            SETPOINT(pt, m_ptDown.x, m_ptDown.y);
            ClientToScreen(m_hWnd, &pt);
            OnNCHitTest(pt.x, pt.y);
            OnSetCursor(m_uHTCode);
            return;
            }
        }

    if (!m_fTracking)
        return;

    //Get rid of the old rectangle.
    RECTFROMRECTL(rc, m_rcl)
    DrawFocusRect(m_hDC, &rc);

    /*
     * Calculate the new.  The flags in m_uSizingFlags tell us what
     * to change.  We limit the object by the page margins and a
     * minimum size of 3*CXYHANDLE in either dimension.
     */
    cxy=3*CXYHANDLE;

    RECTFROMRECTL(rcO, m_rclOrg);
    RECTFROMRECTL(rcB, m_rclBounds);

    if (m_uSizingFlags & SIZINGTOP)
        {
        if (y >= rcO.bottom-cxy)
            y=rcO.bottom-cxy;

        if (y <= rcB.top)           //Limit to top of page.
            y=rcB.top;

        m_rcl.top=y;
        }

    if (m_uSizingFlags & SIZINGBOTTOM)
        {
        if (y <= rcO.top+cxy)
            y=rcO.top+cxy;

        if (y >= rcB.bottom)         //Limit to bottom of page.
            y=rcB.bottom;

        m_rcl.bottom=y;
        }

    if (m_uSizingFlags & SIZINGLEFT)
        {
        if (x >= rcO.right-cxy)
            x=rcO.right-cxy;

        if (x <= rcB.left)           //Limit to left of page.
            x=rcB.left;

        m_rcl.left=x;
        }

    if (m_uSizingFlags & SIZINGRIGHT)
        {
        if (x <= rcO.left+cxy)
            x=rcO.left+cxy;

        if (x >= rcB.right)          //Limit to right of page.
            x=rcB.right;

        m_rcl.right=x;
        }

    //Draw the new
    RECTFROMRECTL(rc, m_rcl)
    DrawFocusRect(m_hDC, &rc);

    return;
    }

/*
 * CPage::OnTimer
 *
 * Purpose:
 *  Processes WM_TIMER messages to a page used to perform mouse
 *  debouncing.
 *
 * Parameters:
 *  uID             UINT timer ID.
 *
 * Return Value:
 *  None
 */

void CPage::OnTimer(UINT uID)
    {
    if (m_fSizePending || m_fDragPending)
        {
        BOOL        fSize=m_fSizePending;
        BOOL        fDrag=m_fDragPending;

        /*
         * Having this function called means the delay requirement
         * is satisfied.  Start tracking for sizing or dragging.
         */

        m_fSizePending=FALSE;
        m_fDragPending=FALSE;

        KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
        m_fTimer=FALSE;

        if (fDrag)
            {
            POINT       pt;

            GetCursorPos(&pt);
            m_pPG->m_fDirty |= DragDrop(m_uKeysDown
                , m_ptDown.x, m_ptDown.y);
            return;
            }

        if (fSize)
            StartSizeTracking();
        }

    return;
    }

/*
 * CPage::StartSizeTracking
 *
 * Purpose:
 *  Begins sizing of a tenant when mouse debounce conditions are
 *  met.
 *
 * Parameters:
 *  uID             UINT timer ID.
 *
 * Return Value:
 *  None
 */

void CPage::StartSizeTracking(void)
    {
    RECT        rc;

    m_pTenantCur->RectGet(&m_rcl, TRUE);
    SetCapture(m_hWnd);
    m_fTracking=TRUE;

    m_hDC=GetDC(m_hWnd);

    //Place the rectangle exactly where it is on the screen.
    RECTFROMRECTL(rc, m_rcl)
    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);
    RECTLFROMRECT(m_rcl, rc);
    m_rclOrg=m_rcl;

    DrawFocusRect(m_hDC, &rc);

    m_pPG->CalcBoundingRect(&rc, TRUE);
    RECTLFROMRECT(m_rclBounds, rc);
    return;
    }

/*
 * CPage::OnNCHitTest
 *
 * Purpose:
 *  Processes WM_NCHITTEST on a page so we can check for hits on the
 *  handles of the selected object for resizing.  We only save
 *  information for ourselves and do not interfere with normal
 *  hit-testing.
 *
 * Parameters:
 *  x, y            UINT device coordinates to check.
 *
 * Return Value:
 *  None
 */

void CPage::OnNCHitTest(UINT x, UINT y)
    {
    RECT        rc;
    RECTL       rcl;
    int         iMid1, iMid2;
    int         xHit, yHit;
    POINT       pt;
    int         x0, y0;

    /*
     * Ignore this message if it occurs during tracking to adjust
     * for the behavior of oddball mouse drivers.
     */
    if (m_fSizePending || m_fTracking)
        return;

    //Default: don't start sizing on a click, don't hit an object.
    m_uSizingFlags=0;
    m_uHTCode=HTNOWHERE;

    if (NULL==m_pTenantCur)
        return;

    //Convert device points to our coordinates
    m_pTenantCur->RectGet(&rcl, FALSE);
    RECTFROMRECTL(rc, rcl);
    RectConvertMappings(&rc, NULL, TRUE);
    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

    SETPOINT(pt, x, y);
    ScreenToClient(m_hWnd, &pt);
    x0=pt.x;
    y0=pt.y;

    if (x0 < rc.left || x0 > rc.right)
        return;

    if (y0 < rc.top || y0 > rc.bottom)
        return;

    //It's at least in the object.
    m_uHTCode=HTCLIENT;

    //Check for hits in horizontal regions
    xHit=NOVALUE;
    iMid1=rc.left+((rc.right-rc.left-CXYHANDLE) >> 1);
    iMid2=rc.left+((rc.right-rc.left+CXYHANDLE) >> 1);

    if (x0 >= rc.left && x0 <= rc.left+CXYHANDLE)
        xHit=XLEFT;
    else if (x0 >= iMid1 && x0 <= iMid2)
        xHit=XMID;
    else if (x0 >= rc.right-CXYHANDLE && x0 <= rc.right)
        xHit=XRIGHT;

    //Don't exit yet if we didn't hit a handle--might hit a y edge.

    //Check for hits in vertical regions
    yHit=NOVALUE;
    iMid1=rc.top+((rc.bottom-rc.top-CXYHANDLE) >> 1);
    iMid2=rc.top+((rc.bottom-rc.top+CXYHANDLE) >> 1);

    if (y0 >= rc.top && y0 <= rc.top+CXYHANDLE)
        yHit=YTOP;
    else if (y0 >= iMid1 && y0 <= iMid2)
        yHit=YMID;
    else if (y0 >= rc.bottom-CXYHANDLE && y0 <= rc.bottom)
        yHit=YBOT;

    /*
     * If we hit any edge, but didn't hit a handle, then one of xHit
     * and yHit will be NOVALUE and the other something else.  When
     * we hit an edge on the 'something else' then we're on a drag
     * point.
     */

    if ((NOVALUE==xHit && NOVALUE==yHit)
        || (XMID==xHit && YMID==yHit)
        || (NOVALUE==xHit && YMID==yHit)
        || (XMID==xHit && NOVALUE==yHit))
        return;

    if ((NOVALUE==xHit && (YTOP==yHit || YBOT==yHit))
        || ((XLEFT==xHit || XRIGHT==xHit) && NOVALUE==yHit))
        {
        m_uHTCode=HTCAPTION;
        return;
        }

    //We hit a handle, so save our HT code
    m_uSizingFlags=g_rguSizingFlags[xHit+(yHit*3)];
    m_uHTCode=g_rgHTCode[xHit+(yHit*3)];
    return;
    }

/*
 * CPage::SetCursor
 *
 * Purpose:
 *  Processes WM_SETCURSOR using the code from OnNCHitTest.
 *
 * Parameters:
 *  x, y            UINT device coordinates to check.
 *
 * Return Value:
 *  LRESULT         HT* code for Windows.
 */

BOOL CPage::OnSetCursor(UINT uHTCode)
    {
    HCURSOR     hCur;
    UINT        iCur;

    /*
     * We really just ignore uHTCode and use the one we saved
     * in OnNCHitTest.
     */

    switch (m_uHTCode)
        {
        case HTTOP:
        case HTBOTTOM:
            iCur=IDC_VARROWS;
            break;

        case HTLEFT:
        case HTRIGHT:
            iCur=IDC_HARROWS;
            break;

        case HTTOPLEFT:
        case HTBOTTOMRIGHT:
            iCur=IDC_NWSEARROWS;
            break;

        case HTTOPRIGHT:
        case HTBOTTOMLEFT:
            iCur=IDC_NESWARROWS;
            break;

        case HTCAPTION:
            iCur=IDC_SMALLARROWS;
            break;

        default:
            return FALSE;
        }

    hCur=UICursorLoad(iCur);
    SetCursor(hCur);

    return TRUE;
    }

/*
 * CPage::TenantFromPoint
 * (Protected)
 *
 * Purpose:
 *  Finds the tenant under the given device coordinates on this
 *  page.
 *
 * Parmeters:
 *  x, y            UINT coordinates.
 *  ppTenant        PCTenant * in which to return the pointer.
 *
 * Return Value:
 *  UINT            Index of the matched tenant, NOVALUE if not
 *                  found.
 */

UINT CPage::TenantFromPoint(UINT x, UINT y, PCTenant *ppTenant)
    {
    PCTenant    pTenant;
    RECTL       rcl;
    UINT        i;
    int         x0, y0;

    x0=x+m_pPG->m_xPos;
    y0=y+m_pPG->m_yPos;

    for (i=0; i < m_cTenants; i++)
        {
        if (!TenantGet(i, &pTenant, FALSE))
            continue;

        pTenant->RectGet(&rcl, TRUE);

        //Essentially Perform PointInRECTL
        if (x0 >= rcl.left && x0 <= rcl.right)
            {
            if (y0 <=rcl.bottom && y0 >=rcl.top)
                {
                *ppTenant=pTenant;
                return i;
                }
            }
        }

    *ppTenant=NULL;
    return NOVALUE;
    }

/*
 * CPage::DragDrop
 *
 * Purpose:
 *  Performs drag-drop operations from the page window
 *
 * Parmeters:
 *  uKeys           UINT state of the keyboard
 *  x, y            UINT mouse coordinates of the starting click.
 *
 * Return Value:
 *  BOOL            TRUE if we modified the page, FALSE otherwise.
 */

BOOL CPage::DragDrop(UINT uKeys, UINT x, UINT y)
    {
    LPDROPSOURCE    pIDropSource;
    LPDATAOBJECT    pIDataObject;
    HRESULT         hr;
    DWORD           dwEffect;
    POINTL          ptl;
    SIZEL           szl;
    RECTL           rcl;
    RECT            rc, rcT;

    pIDropSource=new CDropSource();

    if (NULL==pIDropSource)
        return FALSE;

    pIDropSource->AddRef();
    m_pPG->m_fDragSource=TRUE;

    /*
     * Store a pick point with the data indicating the offset from
     * the upper left of the rectangle where we grabbed it.  This is
     * so the UI feedback in IDropTarget lines up with this tenant.
     */

    m_pTenantCur->RectGet(&rcl, TRUE);
    ptl.x=x+m_pPG->m_xPos-rcl.left;
    ptl.y=y+m_pPG->m_yPos-rcl.top;
    pIDataObject=TransferObjectCreate(&ptl);

    if (NULL==pIDataObject)
        {
        pIDropSource->Release();
        return FALSE;
        }

    m_pPG->m_fMoveInPage=FALSE;

    dwEffect=DROPEFFECT_COPY | DROPEFFECT_MOVE;
    hr=DoDragDrop(pIDataObject, pIDropSource
        , DROPEFFECT_COPY | DROPEFFECT_MOVE, &dwEffect);

    pIDataObject->Release();
    pIDropSource->Release();

    m_pPG->m_fDragSource=FALSE;

    //No drop-no action.
    if (DRAGDROP_S_DROP!=GetScode(hr) || DROPEFFECT_NONE==dwEffect)
        return FALSE;

    /*
     * If m_pPG->m_fMoveInPage is set, then we just change the
     * coordinates on m_pTenantCur and we're done.
     */
    if (m_pPG->m_fMoveInPage)
        {
        m_pTenantCur->Invalidate();

        /*
         * Clip to page boundaries.  We know that ptDrop has to be
         * in the page somewhere or we would not have dropped
         * (effect was NONE).  So first make sure that ptDrop is
         * within 3*CXYHANDLE of the right or bottom, and if so,
         * pull it out to 3*CXYHANDLE.  Then we can just clip the
         * size to the page rectangle and we'll always be sure to
         * have at least a sizeable object.
         */
        m_pTenantCur->SizeGet(&szl, TRUE);
        SetRect(&rc, (int)m_pPG->m_ptDrop.x, (int)m_pPG->m_ptDrop.y
            , 0, 0);
        RectConvertMappings(&rc, NULL, TRUE);

        m_pPG->CalcBoundingRect(&rcT, FALSE);
        OffsetRect(&rcT, (int)m_pPG->m_xPos, (int)m_pPG->m_yPos);

        if (rc.left >= rcT.right-3*CXYHANDLE)
            rc.left=rcT.right-3*CXYHANDLE;

        if (rc.top >= rcT.bottom-3*CXYHANDLE)
            rc.top=rcT.bottom-3*CXYHANDLE;

        rc.right=rc.left+(int)szl.cx;
        rc.bottom=rc.top+(int)szl.cy;
        IntersectRect(&rc, &rc, &rcT);

        RECTLFROMRECT(rcl, rc);

        m_pTenantCur->RectSet(&rcl, TRUE, FALSE);
        m_pTenantCur->Repaint();
        return TRUE;
        }

    /*
     * Otherwise we may have to delete the old tenant if the effect
     * was move.  This will not happen in the move in page case.
     */

    if (DROPEFFECT_MOVE==dwEffect)
        {
        TenantDestroy();
        return TRUE;
        }

    //Copy is a clean operation
    return FALSE;
    }


PAGES.CPP

/*
 * PAGES.CPP
 * Patron Chapter 20
 *
 * Implementation of the CPages class.  See PAGEWIN.CPP and PRINT.CPP
 * for additional member functions.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPages:CPages
 * CPages::~CPages
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application we're in.
 *  cf              UINT application clipboard format.
 */

CPages::CPages(HINSTANCE hInst, UINT cf)
    : CWindow(hInst)
    {
    m_pPageCur=NULL;
    m_iPageCur=NOVALUE;  //Pages are 0 indexed, this is one before
    m_cPages=0;
    m_hWndPageList=NULL;
    m_hFont=NULL;
    m_fSystemFont=FALSE;

    //Initialize to 8.5*11 inch with .25 inch margins as a default.
    m_cx=(LOMETRIC_PER_INCH*17)/2;
    m_cy=LOMETRIC_PER_INCH*11;

    m_xMarginLeft=LOMETRIC_PER_INCH/4;
    m_xMarginRight=LOMETRIC_PER_INCH/4;
    m_yMarginTop=LOMETRIC_PER_INCH/4;
    m_yMarginBottom=LOMETRIC_PER_INCH/4;

    m_xPos=0L;
    m_yPos=0L;

    m_dwIDNext=0;
    m_pIStorage=NULL;

    m_fDirty=FALSE;
    m_cf=cf;

    m_fDragSource=FALSE;
    m_fMoveInPage=FALSE;
    //CHAPTER20MOD
    m_fLinkAllowed=FALSE;
    //End CHAPTER20MOD

    m_fDragRectShown=FALSE;

    m_uScrollInset=GetProfileInt(TEXT("windows")
        , TEXT("DragScrollInset"), DD_DEFSCROLLINSET);

    m_uScrollDelay=GetProfileInt(TEXT("windows")
        , TEXT("DragScrollDelay"), DD_DEFSCROLLDELAY);

    m_uHScrollCode=0;
    m_uVScrollCode=0;

    //CHAPTER20MOD
    m_fShowTypes=FALSE;
    //End CHAPTER20MOD

    return;
    }

CPages::~CPages(void)
    {
    //Ensure memory cleaned up in list; do final IStorage::Release
    StorageSet(NULL, FALSE, FALSE);

    if (NULL!=m_hFont && !m_fSystemFont)
        DeleteObject(m_hFont);

    if (NULL!=m_hWndPageList)
        DestroyWindow(m_hWndPageList);

    //m_hWnd destroyed with the document.
    return;
    }

/*
 * CPages::FIsDirty
 *
 * Purpose:
 *  Tells the caller (document) if anything's happened to dirty us.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPages::FIsDirty(void)
    {
    return m_fDirty;
    }

/*
 * CPages::Init
 *
 * Purpose:
 *  Instantiates a pages window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags.
 *                  Should contain WS_CHILD | WS_VISIBLE in
 *                  typical circumstances.
 *  uID             UINT ID to associate with this window
 *  pv              LPVOID unused for now.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPages::Init(HWND hWndParent, LPRECT pRect, DWORD dwStyle
    , UINT uID, LPVOID pv)
    {
    int     cy;

    m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY, SZCLASSPAGES
        , SZCLASSPAGES, dwStyle, pRect->left, pRect->top
        , pRect->right-pRect->left, pRect->bottom-pRect->top
        , hWndParent, (HMENU)uID, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    /*
     * Create the hidden listbox we'll use to track pages.  We give
     * it the owner-draw style so we can just store pointers in it.
     */
    m_hWndPageList=CreateWindow(TEXT("listbox"), TEXT("Page List")
        , WS_POPUP | LBS_OWNERDRAWFIXED, 0, 0, 100, 100
        , HWND_DESKTOP, NULL, m_hInst, NULL);

    if (NULL==m_hWndPageList)
        return FALSE;

    //Create a 14 point Arial font, or use the system variable font.
    cy=MulDiv(-14, LOMETRIC_PER_INCH, 72);
    m_hFont=CreateFont(cy, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE
        , ANSI_CHARSET, OUT_TT_PRECIS, CLIP_TT_ALWAYS, PROOF_QUALITY
        , VARIABLE_PITCH | FF_SWISS, TEXT("Arial"));

    if (NULL==m_hFont)
        {
        m_hFont=(HFONT)GetStockObject(ANSI_VAR_FONT);
        m_fSystemFont=TRUE;
        }

    return TRUE;
    }

/*
 * CPages::StorageSet
 *
 * Purpose:
 *  Provides the document's IStorage to the pages for its own use.
 *  If this is a new storage, we initalize it with streams that we
 *  want to always exists.  If this is an open, then we create
 *  our page list from the PageList string we wrote before.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE to the new or opened storage.  If
 *                  NULL, we just clean up and exit.
 *  fChange         BOOL indicating is this was a Save As operation,
 *                  meaning that we have the structure already and
 *                  just need to change the value of m_pIStorage.
 *  fInitNew        BOOL indicating if this is a new storage or one
 *                  opened from a previous save.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::StorageSet(LPSTORAGE pIStorage, BOOL fChange
    , BOOL fInitNew)
    {
    DWORD           dwMode=STGM_DIRECT | STGM_READWRITE
                        | STGM_SHARE_EXCLUSIVE;
    HRESULT         hr;
    PCPage          pPage;
    BOOL            fRet=FALSE;
    ULONG           cbRead;
    PAGELIST        pgList;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    LPDWORD         pdwID;
    UINT            i;

    //If we're changing saved roots, simply open current page again
    if (fChange)
        {
        if (NULL==pIStorage)
            return FALSE;

        m_pIStorage->Release();
        m_pIStorage=pIStorage;
        m_pIStorage->AddRef();

        PageGet(m_iPageCur, &m_pPageCur, TRUE);
        m_fDirty=FALSE;
        return TRUE;
        }

    if (NULL!=m_hWndPageList)
        {
        //On new or open, clean out whatever it is we have.
        for (i=0; i < m_cPages; i++)
            {
            if (PageGet(i, &pPage, FALSE))
                delete pPage;
            }

        SendMessage(m_hWndPageList, LB_RESETCONTENT, 0, 0L);
        }

    if (NULL!=m_pIStorage)
        m_pIStorage->Release();

    m_pIStorage=NULL;

    //If we're just cleaning up, then we're done.
    if (NULL==pIStorage)
        return TRUE;

    m_pIStorage=pIStorage;
    m_pIStorage->AddRef();

    //If this is a new storage, create the streams we require
    if (fInitNew)
        {
        //Page list header.
        hr=m_pIStorage->CreateStream(SZSTREAMPAGELIST, dwMode
            | STGM_CREATE, 0, 0, &pIStream);

        if (FAILED(hr))
            return FALSE;

        pIStream->Release();

        //Device Configuration
        hr=m_pIStorage->CreateStream(SZSTREAMDEVICECONFIG, dwMode
            | STGM_CREATE, 0, 0, &pIStream);

        if (FAILED(hr))
            return FALSE;

        pIStream->Release();
        return TRUE;
        }

    /*
     * We're opening an existing file:
     *  1)  Configure for the device we're on
     *  2)  Read the Page List and create page entries for each.
     */

    ConfigureForDevice();

    //Read the page list.
    hr=m_pIStorage->OpenStream(SZSTREAMPAGELIST, NULL, dwMode, 0
        , &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pIStream->Read(&pgList, sizeof(PAGELIST), &cbRead);
        m_cPages  =(UINT)pgList.cPages;
        m_iPageCur=(UINT)pgList.iPageCur;
        m_dwIDNext=pgList.dwIDNext;

        fRet=TRUE;
        cbRead=pgList.cPages*sizeof(DWORD);

        if (0!=cbRead)
            {
            pdwID=(LPDWORD)pIMalloc->Alloc(cbRead);

            if (NULL!=pdwID)
                {
                pIStream->Read(pdwID, cbRead, &cbRead);

                for (i=0; i < m_cPages; i++)
                    fRet &=PageAdd(NOVALUE, *(pdwID+i), FALSE);

                pIMalloc->Free(pdwID);
                }
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    if (!fRet)
        return FALSE;

    PageGet(m_iPageCur, &m_pPageCur, TRUE);
    m_fDirty=FALSE;

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);

    return TRUE;
    }

/*
 * CPages::StorageUpdate
 *
 * Purpose:
 *  Insures that all pages are committed before a root save.
 *
 * Parameters:
 *  fCloseAll       BOOL directing us to close all open storages
 *                  and streams.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::StorageUpdate(BOOL fCloseAll)
    {
    PCPage          pPage;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    LPDWORD         pdwID;
    ULONG           cb;
    HRESULT         hr;
    PAGELIST        pgList;
    BOOL            fRet=FALSE;
    UINT            i;

    //We only need to close the current page--nothing else is open.
    if (NULL!=m_pPageCur)
        {
        m_pPageCur->Update();

        if (fCloseAll)
            m_pPageCur->Close(FALSE);
        }

    //We don't hold anything else open, so write the page list.
    hr=m_pIStorage->OpenStream(SZSTREAMPAGELIST, NULL, STGM_DIRECT
        | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pgList.cPages=m_cPages;
        pgList.iPageCur=m_iPageCur;
        pgList.dwIDNext=m_dwIDNext;

        pIStream->Write(&pgList, sizeof(PAGELIST), &cb);

        cb=m_cPages*sizeof(DWORD);
        pdwID=(LPDWORD)pIMalloc->Alloc(cb);

        if (NULL!=pdwID)
            {
            for (i=0; i < m_cPages; i++)
                {
                PageGet(i, &pPage, FALSE);
                *(pdwID+i)=pPage->GetID();
                }

            pIStream->Write(pdwID, cb, &cb);
            pIMalloc->Free(pdwID);
            fRet=TRUE;
            }
        pIMalloc->Release();
        }

    pIStream->Release();

    //Clean up the dirty flag when we do an update.
    m_fDirty=!fRet;
    return fRet;
    }

/*
 * CPages::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Pages window in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  None
 */

void CPages::RectGet(LPRECT pRect)
    {
    RECT        rc;
    POINT       pt;

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_hWnd, &rc);
    SETPOINT(pt, rc.left, rc.top);
    ScreenToClient(GetParent(m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return;
    }

/*
 * CPages::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Pages window which sizes to fit.
 *  Coordinates are given in parent terms.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPages::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    if (NULL==pRect)
        return;

    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, pRect->left, pRect->top
        , (UINT)cx, (UINT)cy, SWP_NOZORDER);

    UpdateScrollRanges();
    return;
    }

/*
 * CPages::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the pages window in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  None
 */

void CPages::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return;
    }

/*
 * CPages::SizeSet
 *
 * Purpose:
 *  Sets a new size in parent coordinates for the Pages window.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPages::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    if (NULL==pRect)
        return;

    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
        , SWP_NOMOVE | SWP_NOZORDER);

    UpdateScrollRanges();
    return;
    }

/*
 * CPages::ActivePage
 *
 * Purpose:
 *  Returns a CPage pointer to the current page.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCPage          Pointer to the current page.
 */

PCPage CPages::ActivePage(void)
    {
    PCPage      pPage;
    BOOL        fRet;

    fRet=PageGet(m_iPageCur, &pPage, FALSE);
    return fRet ? pPage : NULL;
    }

/*
 * CPages::PageInsert
 *
 * Purpose:
 *  Creates a new page immediately after the current page.  If
 *  there are no pages then this creates page 1.
 *
 * Parameters:
 *  uReserved       UINT unused
 *
 * Return Value:
 *  UINT            Index of the new page, 0 on failure.
 */

UINT CPages::PageInsert(UINT uReserved)
    {
    if (0!=m_cPages && NULL!=m_pPageCur)
        {
        //Close the current page, committing changes.
        m_pPageCur->Close(TRUE);
        }

    //Create and open the new page.
    if (!PageAdd(m_iPageCur, m_dwIDNext, TRUE))
        return 0;

    m_dwIDNext++;
    m_iPageCur++;
    m_cPages++;

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);

    PageGet(m_iPageCur, &m_pPageCur, FALSE);
    return m_iPageCur;
    }

/*
 * CPages::PageDelete
 *
 * Removes the current page from the page list.
 *
 * Parameters:
 *  uReserved       UINT unused
 *
 * Return Value:
 *  UINT            Index to the now current page from the page
 *                  list, NOVALUE on error.
 */

UINT CPages::PageDelete(UINT uReserved)
    {
    PCPage      pPage;

    if (!PageGet(m_iPageCur, &pPage, FALSE))
        return NOVALUE;

    //Delete the page in both the storage and in memory.
    SendMessage(m_hWndPageList, LB_DELETESTRING, m_iPageCur, 0L);

    m_pPageCur->Destroy(m_pIStorage);

    delete m_pPageCur;   //Does final pPage->Close
    m_pPageCur=NULL;

    /*
     * If this is the last page then the current is one less.  If
     * it's the only page the current is zero.  Otherwise the
     * current is the next page.
     */

    if (m_iPageCur==m_cPages-1)   //Covers last or only page.
        m_iPageCur--;

    m_cPages--;

    //Insure the new visible page is open.
    if (0!=m_cPages)
        {
        PageGet(m_iPageCur, &m_pPageCur, TRUE);
        InvalidateRect(m_hWnd, NULL, FALSE);
        }
    else
        InvalidateRect(m_hWnd, NULL, TRUE);

    UpdateWindow(m_hWnd);
    return m_iPageCur;
    }

/*
 * CPages::CurPageGet
 *
 * Purpose:
 *  Retrieves the index of the current page we're viewing.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the current page.
 */

UINT CPages::CurPageGet(void)
    {
    return m_iPageCur;
    }

/*
 * CPages::CurPageSet
 *
 * Purpose:
 *  Sets the index of the current page to view.
 *
 * Parameters:
 *  iPage           UINT index of the page to view. 0 means first
 *                  page, NOVALUE means last page.
 *
 * Return Value:
 *  UINT            Index of the previous current page, NOVALUE on
 *                  error.
 */

UINT CPages::CurPageSet(UINT iPage)
    {
    UINT    iPageNew;
    UINT    iPagePrev=m_iPageCur;
    PCPage  pPage;

    switch (iPage)
        {
        case 0:
            iPageNew=0;
            break;

        case NOVALUE:
            iPageNew=m_cPages-1;
            break;

        default:
            if (iPage >= m_cPages)
                iPage=0;

            iPageNew=iPage;
            break;
        }

    //No reason to switch to the same page.
    if (iPagePrev==iPageNew)
        return iPage;

    //Close the old page committing changes.
    if (!PageGet(iPagePrev, &pPage, FALSE))
        return NOVALUE;

    pPage->Close(TRUE);

    m_iPageCur=iPageNew;

    //Open the new page.
    PageGet(m_iPageCur, &m_pPageCur, TRUE);

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);
    return iPagePrev;
    }

/*
 * CPages::NumPagesGet
 *
 * Purpose:
 *  Returns the number of pages this object current contains.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of pages we contain.
 */

UINT CPages::NumPagesGet(void)
    {
    return m_cPages;
    }

/*
 * CPages::TenantCreate
 * CPages::TenantDestroy
 *
 * Purpose:
 *  Pass-throughs for CPage members on the current page.
 */

BOOL CPages::TenantCreate(TENANTTYPE tType, LPVOID pv
    , LPFORMATETC pFE, PPATRONOBJECT ppo, DWORD dwData)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantCreate(tType, pv, pFE, ppo, dwData);
    m_fDirty |= fRet;
    return fRet;
    }

BOOL CPages::TenantDestroy(void)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantDestroy();
    m_fDirty |= fRet;
    return fRet;
    }

/*
 * CPages::TenantClip
 *
 * Purpose:
 *  Copies or cuts the currently selected tenant to the clipoard.
 *
 * Parameters:
 *  fCut            BOOL TRUE to cut the object, FALSE to copy.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::TenantClip(BOOL fCut)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantClip(fCut);
    m_fDirty |= (fRet && fCut);
    return fRet;
    }

/*
 * CPages::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected on this
 *  page for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU on which object related items live.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPages::FQueryObjectSelected(HMENU hMenu)
    {
    if (NULL==m_pPageCur)
        return FALSE;

    return m_pPageCur->FQueryObjectSelected(hMenu);
    }

/*
 * CPages::ActivateObject
 *
 * Purpose:
 *  Executes a verb on the currently selected object.
 *
 * Parameters:
 *  iVerb           LONG of the selected verb.
 *
 * Return Value:
 *  None
 */

void CPages::ActivateObject(LONG iVerb)
    {
    if (NULL==m_pPageCur)
        return;

    m_pPageCur->ActivateObject(iVerb);
    return;
    }

//CHAPTER20MOD

/*
 * CPages::ShowObjectTypes
 *
 * Purpose:
 *  Pass-through to CPage::ShowObjectTypes
 *
 * Parameters:
 *  fShow           BOOL indicating to show the type or not.
 *
 * Return Value:
 *  None
 */

void CPages::ShowObjectTypes(BOOL fShow)
    {
    if (NULL==m_pPageCur)
        return;

    m_fShowTypes=fShow;
    m_pPageCur->ShowObjectTypes(fShow);
    return;
    }

/*
 * CPages::NotifyTenantsOfRename
 *
 * Purpose:
 *  Pass-through to CPage to tell tenants that the document
 *  changes names.
 *
 * Parameters:
 *  pszFile         LPTSTR of the new filename.
 *  pmk             LPMONKIER to the new file moniker.
 *
 * Return Value:
 *  None
 */

void CPages::NotifyTenantsOfRename(LPTSTR pszFile, LPMONIKER pmk)
    {
    if (NULL==m_pPageCur)
        return;

    m_pPageCur->NotifyTenantsOfRename(pszFile, pmk);
    return;
    }

/*
 * CPages::FQueryLinksInPage
 *
 * Purpose:
 *  Pass through to current page to see if there are any
 *  linked objects
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPages::FQueryLinksInPage()
    {
    if (NULL==m_pPageCur)
        return FALSE;

    return m_pPageCur->FQueryLinksInPage();
    }

/*
 * CPages::GetUILinkContainer
 *
 * Purpose:
 *  Creates an object with the IOleUILinkContainer interface for
 *  the links dialog.  We know the current page which is what
 *  we need to create this thing, so we can create it here instead
 *  of bugging the page for it.
 *
 * Parameters:
 *  ppObj           PCIOleUILinkContainer * in which we return
 *                  the pointer.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::GetUILinkContainer(PCIOleUILinkContainer *ppObj)
    {
    PCIOleUILinkContainer   pObj;

    *ppObj=NULL;

    if (NULL==m_pPageCur)
        return FALSE;

    pObj=new CIOleUILinkContainer(m_pPageCur);

    if (NULL==pObj)
        return FALSE;

    if (!pObj->Init())
        {
        delete pObj;
        return FALSE;
        }

    pObj->AddRef();
    *ppObj=pObj;
    return TRUE;
    }
//End CHAPTER20MOD

/*
 * CPages::ConvertObject
 *
 * Purpose:
 *  Pass-through to the current page.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame window.
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful, FALSE
 *                  otherwise.
 */

BOOL CPages::ConvertObject(HWND hWndFrame)
    {
    if (NULL==m_pPageCur)
        return FALSE;

    return m_pPageCur->ConvertObject(hWndFrame, FALSE);
    }

/*
 * CPages::CalcBoundingRect
 * (Protected)
 *
 * Purpose:
 *  Calculates a rectangle that bounds the printed page and the
 *  current scroll state of the window.
 *
 * Parameters:
 *  prc             LPRECT to fill with window (device) coordinates.
 *  fWindow         BOOL indicating to include the window in this
 *                  calculation or return only the printed page
 *                  coordinates.
 *
 * Return Value:
 *  None
 */

void CPages::CalcBoundingRect(LPRECT prc, BOOL fWindow)
    {
    RECT        rc, rcT;

    if (NULL==prc)
        return;

    //Calculate the boundaries for sizing: intersect page & screen
    rc.left=LOMETRIC_BORDER+m_xMarginLeft;
    rc.top =-LOMETRIC_BORDER-m_yMarginTop;
    rc.right =rc.left+(UINT)m_cx;
    rc.bottom=rc.top -(UINT)m_cy;
    RectConvertMappings(&rc, NULL, TRUE);
    OffsetRect(&rc, -(int)m_xPos, -(int)m_yPos);

    if (!fWindow)
        {
        *prc=rc;
        return;
        }

    //Intersect with window to make the size bounds.
    GetClientRect(m_hWnd, &rcT);
    IntersectRect(prc, &rc, &rcT);
    return;
    }

/*
 * CPages::PageGet
 * (Protected)
 *
 * Purpose:
 *  Returns a page of a given index returning a BOOL so it's simple
 *  to use this function inside if statements.
 *
 * Parameters:
 *  iPage           UINT page to retrieve 0 based.
 *  ppPage          PCPage * in which to return the page pointer
 *  fOpen           BOOL indicating if we should open this page as
 *                  well.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::PageGet(UINT iPage, PCPage *ppPage, BOOL fOpen)
    {
    if (NULL==ppPage)
        return FALSE;

    if (LB_ERR!=SendMessage(m_hWndPageList, LB_GETTEXT
        , iPage, (LONG)ppPage))
        {
        if (fOpen)
            (*ppPage)->Open(m_pIStorage);

        return TRUE;
        }

    return FALSE;
    }

/*
 * CPages::PageAdd
 * (Protected)
 *
 * Purpose:
 *  Creates a new page initialized to the given values.  The new
 *  page's storage is created if it does not already exist.  If
 *  fOpenStorage is set the page's storage is opened and left
 *  opened.
 *
 * Parameters:
 *  iPage           UINT Location at which to insert page; new page
 *                  is inserted after this position.  NOVALUE for
 *                  the end.
 *  dwID            DWORD ID for this page.
 *  fOpenStorage    BOOL indicating if we're to leave the storage
 *                  open.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPages::PageAdd(UINT iPage, DWORD dwID, BOOL fOpenStorage)
    {
    PCPage      pPage;
    LRESULT     lr;

    pPage=new CPage(dwID, m_hWnd, this);

    if (NULL==pPage)
        return FALSE;

    if (fOpenStorage)
        pPage->Open(m_pIStorage);

    if (NOVALUE==iPage)
        iPage--;

    //Now try to add to the listbox.
    lr=SendMessage(m_hWndPageList, LB_INSERTSTRING, iPage+1
        , (LONG)pPage);

    if (LB_ERRSPACE==lr)
        {
        if (fOpenStorage)
            pPage->Close(FALSE);

        delete pPage;
        return FALSE;
        }

    return TRUE;
    }


PAGES.H

/*
 * PAGES.H
 * Patron Chapter 20
 *
 * Definitions and function prototypes for the Pages window control
 * as well as the CPage class.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _PAGES_H_
#define _PAGES_H_

//Versioning.
#define VERSIONMAJOR                2
#define VERSIONMINOR                0
#define VERSIONCURRENT              0x00020000

//Classname
#define SZCLASSPAGES                TEXT("pages")

#define HIMETRIC_PER_INCH           2540
#define LOMETRIC_PER_INCH           254
#define LOMETRIC_BORDER             60          //Border around page

//Window extra bytes and offsets
#define CBPAGESWNDEXTRA             (sizeof(LONG))
#define PAGEWL_STRUCTURE            0

#include "tenant.h"

typedef struct tagTENANTLIST
    {
    DWORD       cTenants;
    DWORD       dwIDNext;
    } TENANTLIST, *PTENANTLIST;

#define SZSTREAMTENANTLIST        OLETEXT("Tenant List")

//Delay timer used in mouse debouncing
#define IDTIMER_DEBOUNCE          120

/*
 * Page class describing an individual page and what things it
 * contains, managing an IStorage for us.
 *
 * A DWORD is used to identify this page as the name of the storage
 * is the string form of this ID.  If we added a page every second,
 * it would take 136 years to overrun this counter, so we can
 * get away with saving it persistently.  I hope this software is
 * obsolete by then.
 */

class CPage
    {
    //CHAPTER20MOD
    friend class CIOleUILinkContainer;
    //End CHAPTER20MOD

    private:
        DWORD       m_dwID;             //Persistent identifier
        LPSTORAGE   m_pIStorage;        //Substorage for this page
        HWND        m_hWnd;             //Pages window
        DWORD       m_cOpens;           //Calls to Open

        class CPages *m_pPG;            //Pages window

        DWORD       m_dwIDNext;
        DWORD       m_cTenants;
        HWND        m_hWndTenantList;   //Listbox; our tenant list

        UINT        m_iTenantCur;
        PCTenant    m_pTenantCur;

        UINT        m_uHTCode;          //Last hit-test/mouse move
        UINT        m_uSizingFlags;     //Restrictions on sizing
        BOOL        m_fTracking;        //Tracking resize?
        RECTL       m_rclOrg;           //Original before tracking
        RECTL       m_rcl;              //Tracking rectangle
        RECTL       m_rclBounds;        //Boundaries f/size tracking
        HDC         m_hDC;              //Tracking hDC

        BOOL        m_fDragPending;     //Waiting for drag?
        BOOL        m_fSizePending;     //Waiting for debounce?
        int         m_cxyDist;          //Debounce distance
        UINT        m_cDelay;           //Debounce delay
        POINTS      m_ptDown;           //Point of click to debounce
        UINT        m_uKeysDown;        //Keys when click happens
        DWORD       m_fTimer;           //Timer active?

        //CHAPTER20MOD
        BOOL        m_fReopen;          //Did we just close?
        //End CHAPTER20MOD

    protected:
        BOOL         TenantGet(UINT, PCTenant *, BOOL);
        BOOL         TenantAdd(UINT, DWORD, PCTenant *);
        LPDATAOBJECT TransferObjectCreate(PPOINTL);

        //PAGEMOUS.CPP
        BOOL         SelectTenantAtPoint(UINT, UINT);
        UINT         TenantFromPoint(UINT, UINT, PCTenant *);
        BOOL         DragDrop(UINT, UINT, UINT);

    public:
        CPage(DWORD, HWND, class CPages *);
        ~CPage(void);

        DWORD       GetID(void);
        BOOL        Open(LPSTORAGE);
        void        Close(BOOL);
        BOOL        Update(void);
        void        Destroy(LPSTORAGE);
        UINT        GetStorageName(LPOLESTR);

        void        Draw(HDC, int, int, BOOL, BOOL);

        BOOL        TenantCreate(TENANTTYPE, LPVOID, LPFORMATETC
                        , PPATRONOBJECT, DWORD);
        BOOL        TenantDestroy(void);
        BOOL        TenantClip(BOOL);
        BOOL        FQueryObjectSelected(HMENU);
        void        ActivateObject(LONG);
        //CHAPTER20MOD
        void        ShowObjectTypes(BOOL);
        void        NotifyTenantsOfRename(LPTSTR, LPMONIKER);
        BOOL        FQueryLinksInPage(void);
        //End CHAPTER20MOD
        BOOL        ConvertObject(HWND, BOOL);

        //PAGEMOUSE.CPP
        BOOL        OnRightDown(UINT, UINT, UINT);
        BOOL        OnLeftDown(UINT, UINT, UINT);
        BOOL        OnLeftDoubleClick(UINT, UINT, UINT);
        BOOL        OnLeftUp(UINT, UINT, UINT);
        void        OnMouseMove(UINT, int, int);
        void        OnTimer(UINT);
        void        StartSizeTracking(void);
        void        OnNCHitTest(UINT, UINT);
        BOOL        OnSetCursor(UINT);
    };

typedef CPage *PCPage;

/*
 * Structures to save with the document describing the device
 * configuration and pages that we have.  This is followed by
 * a list of DWORD IDs for the individual pages.
 */

typedef struct tagDEVICECONFIG
    {
    DWORD       cb;                         //Size of structure
    TCHAR       szDriver[CCHDEVICENAME];
    TCHAR       szDevice[CCHDEVICENAME];
    TCHAR       szPort[CCHDEVICENAME];
    DWORD       cbDevMode;                  //Size of actual DEVMODE
    DEVMODE     dm;                         //Variable
    } DEVICECONFIG, *PDEVICECONFIG;

//Offset to cbDevMode
#define CBSEEKOFFSETCBDEVMODE  (sizeof(DWORD)   \
                               +(3*CCHDEVICENAME*sizeof(TCHAR)))

//Combined OLE and Patron device structures.
typedef struct tagCOMBINEDEVICE
    {
    DVTARGETDEVICE  td;
    DEVICECONFIG    dc;
    } COMBINEBDEVICE, *PCOMBINEDEVICE;

typedef struct tagPAGELIST
    {
    DWORD       cPages;
    DWORD       iPageCur;
    DWORD       dwIDNext;
    } PAGELIST, *PPAGELIST;

//PRINT.CPP
BOOL    APIENTRY PrintDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL    APIENTRY AbortProc(HDC, int);

//PAGEWIN.CPP
LRESULT APIENTRY PagesWndProc(HWND, UINT, WPARAM, LPARAM);
void             RectConvertMappings(LPRECT, HDC, BOOL);

class CPages : public CWindow
    {
    friend LRESULT APIENTRY PagesWndProc(HWND, UINT, WPARAM, LPARAM);
    friend BOOL    APIENTRY PrintDlgProc(HWND, UINT, WPARAM, LPARAM);

    friend class CPage;
    friend class CTenant;
    friend class CDropTarget;
    friend class CImpIAdviseSink;

    protected:
        PCPage      m_pPageCur;             //Current page
        UINT        m_iPageCur;             //Current page
        UINT        m_cPages;               //Number of pages

        HWND        m_hWndPageList;         //Listbox with page list
        HFONT       m_hFont;                //Page font
        BOOL        m_fSystemFont;          //m_hFont system object?

        UINT        m_cx;                   //Page size in LOMETRIC
        UINT        m_cy;

        UINT        m_xMarginLeft;          //Unusable margins,
        UINT        m_xMarginRight;         //in LOMETRIC
        UINT        m_yMarginTop;
        UINT        m_yMarginBottom;

        UINT        m_xPos;                 //Viewport scroll pos,
        UINT        m_yPos;                 //both in *PIXELS*

        DWORD       m_dwIDNext;             //Next ID for a page.
        LPSTORAGE   m_pIStorage;            //Root storage

        UINT        m_cf;                   //Clipboard format
        BOOL        m_fDirty;

        BOOL        m_fDragSource;          //Source==target?
        BOOL        m_fMoveInPage;          //Moving in same page
        //CHAPTER20MOD
        BOOL        m_fLinkAllowed;         //Linking in drag-drop?
        //End CHAPTER20MOD
        POINTL      m_ptDrop;               //Where to move object

        BOOL        m_fDragRectShown;       //Is rect on the screen?
        UINT        m_uScrollInset;         //Hit-test for drag-drop
        UINT        m_uScrollDelay;         //Delay before repeat
        DWORD       m_dwTimeLast;           //Ticks on last DragOver
        UINT        m_uHScrollCode;         //L/R on scroll repeat?
        UINT        m_uVScrollCode;         //U/D on scroll repeat?
        UINT        m_uLastTest;            //Last test result
        POINTL      m_ptlRect;              //Last feedback rectangle
        SIZEL       m_szlRect;

        //CHAPTER20MOD
        BOOL        m_fShowTypes;           //Show Object active?
        //End CHAPTER20MOD

    private:
        void        Draw(HDC, BOOL, BOOL);
        void        UpdateScrollRanges(void);
        BOOL        ConfigureForDevice(void);
        BOOL        PageGet(UINT, PCPage *, BOOL);
        BOOL        PageAdd(UINT, DWORD, BOOL);

        void        CalcBoundingRect(LPRECT, BOOL);

        //DRAGDROP.CPP
        UINT        UTestDroppablePoint(PPOINTL);
        void        DrawDropTargetRect(PPOINTL, LPSIZEL);
        void        AdjustPosition(PPOINTL, LPSIZEL);

    public:
        CPages(HINSTANCE, UINT);
        ~CPages(void);

        BOOL        Init(HWND, LPRECT, DWORD, UINT, LPVOID);

        BOOL        StorageSet(LPSTORAGE, BOOL, BOOL);
        BOOL        StorageUpdate(BOOL);

        BOOL        Print(HDC, LPTSTR, DWORD, UINT, UINT, UINT);

        void        RectGet(LPRECT);
        void        RectSet(LPRECT, BOOL);
        void        SizeGet(LPRECT);
        void        SizeSet(LPRECT, BOOL);

        PCPage      ActivePage(void);
        UINT        PageInsert(UINT);
        UINT        PageDelete(UINT);
        UINT        CurPageGet(void);
        UINT        CurPageSet(UINT);
        UINT        NumPagesGet(void);

        BOOL        DevModeSet(HGLOBAL, HGLOBAL);
        HGLOBAL     DevModeGet(void);

        BOOL        FIsDirty(void);
        BOOL        DevReadConfig(PCOMBINEDEVICE *, HDC *);
        BOOL        TenantCreate(TENANTTYPE, LPVOID, LPFORMATETC
                        , PPATRONOBJECT, DWORD);
        BOOL        TenantDestroy(void);
        BOOL        TenantClip(BOOL);
        BOOL        FQueryObjectSelected(HMENU);
        void        ActivateObject(LONG);

        //CHAPTER20MOD
        void        ShowObjectTypes(BOOL);
        void        NotifyTenantsOfRename(LPTSTR, LPMONIKER);
        BOOL        FQueryLinksInPage(void);
        BOOL        GetUILinkContainer(class CIOleUILinkContainer **);
        //End CHAPTER20MOD
        BOOL        ConvertObject(HWND);
    };

typedef CPages *PCPages;

//Fixed names of streams in the Pages IStorage
#define SZSTREAMPAGELIST        OLETEXT("Page List")
#define SZSTREAMDEVICECONFIG    OLETEXT("Device Configuration")

//Return values for UTestDroppablePoint
#define UDROP_NONE              0x0000      //Exclusive
#define UDROP_CLIENT            0x0001      //Inclusive
#define UDROP_INSETLEFT         0x0002      //L/R are exclusive
#define UDROP_INSETRIGHT        0x0004
#define UDROP_INSETHORZ         (UDROP_INSETLEFT | UDROP_INSETRIGHT)

#define UDROP_INSETTOP          0x0008      //T/B are exclusive
#define UDROP_INSETBOTTOM       0x0010
#define UDROP_INSETVERT         (UDROP_INSETTOP | UDROP_INSETBOTTOM)

//CHAPTER20MOD
//Object used for the Links dialog

class CIOleUILinkContainer : public IOleUILinkContainer
    {
    private:
        ULONG                   m_cRef;
        PCPage                  m_pPage;
        UINT                    m_iTenant;
        LPOLEUILINKCONTAINER    m_pDelIUILinks;

    public:
        BOOL                    m_fDirty;   //No reason to hide it

    protected:
        STDMETHODIMP GetObjectInterface(DWORD, REFIID, PPVOID);

    public:
        CIOleUILinkContainer(PCPage);
        ~CIOleUILinkContainer(void);

        BOOL Init(void);
        BOOL IsDirty(void);

        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(DWORD) GetNextLink(DWORD);
        STDMETHODIMP         SetLinkUpdateOptions(DWORD, DWORD);
        STDMETHODIMP         GetLinkUpdateOptions(DWORD, LPDWORD);
        STDMETHODIMP         SetLinkSource(DWORD, LPTSTR, ULONG
                                 , ULONG *, BOOL);
        STDMETHODIMP         GetLinkSource(DWORD, LPTSTR *, ULONG *
                                 , LPTSTR *, LPTSTR *, BOOL *
                                 , BOOL *);
        STDMETHODIMP         OpenLinkSource(DWORD);
        STDMETHODIMP         UpdateLink(DWORD, BOOL, BOOL);
        STDMETHODIMP         CancelLink(DWORD);
    };

typedef CIOleUILinkContainer *PCIOleUILinkContainer;

//End CHAPTER20MOD

#endif  //_PAGES_H_


PAGEWIN.CPP

/*
 * PAGEWIN.CPP
 * Patron Chapter 20
 *
 * Window procedure for the Pages window and support functions.
 * This window manages its own scrollbars and viewport and provides
 * printing capabilities as well.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * PagesWndProc
 *
 * Purpose:
 *  Window procedure for the Pages window.
 */

LRESULT APIENTRY PagesWndProc(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    PCPages         ppg;
    PAINTSTRUCT     ps;
    HDC             hDC;
    int             iPos, iTmp;
    int             iMin, iMax;
    UINT            idScroll;
    BOOL            fDirty=FALSE;

    ppg=(PCPages)GetWindowLong(hWnd, PAGEWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppg=(PCPages)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PAGEWL_STRUCTURE, (LONG)ppg);

            ppg->m_hWnd=hWnd;
            break;

        case WM_PAINT:
            /*
             * If there is currently a drag-rectangle showing, then
             * remove it before painting.  This insures that
             * painting doesn't blast part of that rectangle away so
             * that when we draw it next, garbage is left around.
             */
            if (ppg->m_fDragRectShown)
                ppg->DrawDropTargetRect(NULL, NULL);

            hDC=BeginPaint(hWnd, &ps);

            //Draw only if we have a page to show.
            if (0!=ppg->m_cPages)
                ppg->Draw(hDC, FALSE, FALSE);

            EndPaint(hWnd, &ps);

            //Turn the rectangle back on, if necessary.
            if (ppg->m_fDragRectShown)
                ppg->DrawDropTargetRect(NULL, NULL);
            break;

        case WM_HSCROLL:
        case WM_VSCROLL:
            idScroll=(WM_HSCROLL==iMsg) ? SB_HORZ : SB_VERT;

            iPos=GetScrollPos(hWnd, idScroll);
            iTmp=iPos;
            GetScrollRange(hWnd, idScroll, &iMin, &iMax);

            switch (LOWORD(wParam))
                {
                case SB_LINEUP:     iPos -= 20;  break;
                case SB_PAGEUP:     iPos -=100;  break;
                case SB_LINEDOWN:   iPos += 20;  break;
                case SB_PAGEDOWN:   iPos +=100;  break;

                case SB_THUMBPOSITION:
                    iPos=ScrollThumbPosition(wParam, lParam);
                    break;

                //We don't want scrolling on this message.
                case SB_THUMBTRACK:
                    return 0L;
                }

            iPos=max(iMin, min(iPos, iMax));

            if (iPos!=iTmp)
                {
                //Set the new position and scroll the window
                SetScrollPos(hWnd, idScroll, iPos, TRUE);

                if (SB_HORZ==idScroll)
                    {
                    ppg->m_xPos=iPos;
                    ScrollWindow(hWnd, iTmp-iPos, 0, NULL, NULL);
                    }
                else
                    {
                    ppg->m_yPos=iPos;
                    ScrollWindow(hWnd, 0, iTmp-iPos, NULL, NULL);
                    }
                }

            break;

        case WM_RBUTTONDOWN:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnRightDown(wParam
                , LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_LBUTTONDOWN:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftDown(wParam
                , LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_LBUTTONUP:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftUp(wParam
                , LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_LBUTTONDBLCLK:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftDoubleClick(wParam, LOWORD(lParam)
                , HIWORD(lParam));
            break;

        case WM_MOUSEMOVE:
            if (NULL==ppg->m_pPageCur)
                break;

            ppg->m_pPageCur->OnMouseMove(wParam, LOWORD(lParam)
                , HIWORD(lParam));
            break;

        case WM_TIMER:
            if (NULL==ppg->m_pPageCur)
                break;

            ppg->m_pPageCur->OnTimer(wParam);
            break;

        case WM_NCHITTEST:
            if (NULL!=ppg->m_pPageCur)
                {
                /*
                 * This just saves information in the page for
                 * OnSetCursor
                 */
                ppg->m_pPageCur->OnNCHitTest(LOWORD(lParam)
                    , HIWORD(lParam));
                }

            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        case WM_SETCURSOR:
            if (NULL!=ppg->m_pPageCur)
                {
                if (ppg->m_pPageCur->OnSetCursor(LOWORD(lParam)))
                    break;
                }

            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    ppg->m_fDirty |= fDirty;
    return 0L;
    }

/*
 * RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device to logical
 *  coordinates where the hDC defines the logical coordinates.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  hDC             HDC describing the logical coordinate system.
 *                  if NULL, uses a screen DC in MM_LOMETRIC.
 *  fToDevice       BOOL TRUE to convert from uConv to device,
 *                  FALSE to convert device to uConv.
 *
 * Return Value:
 *  None
 */

void RectConvertMappings(LPRECT pRect, HDC hDC, BOOL fToDevice)
    {
    POINT   rgpt[2];
    BOOL    fSysDC=FALSE;

    if (NULL==pRect)
        return;

    rgpt[0].x=pRect->left;
    rgpt[0].y=pRect->top;
    rgpt[1].x=pRect->right;
    rgpt[1].y=pRect->bottom;

    if (NULL==hDC)
        {
        hDC=GetDC(NULL);
        SetMapMode(hDC, MM_LOMETRIC);
        fSysDC=TRUE;
        }

    if (fToDevice)
        LPtoDP(hDC, rgpt, 2);
    else
        DPtoLP(hDC, rgpt, 2);

    if (fSysDC)
        ReleaseDC(NULL, hDC);

    pRect->left=rgpt[0].x;
    pRect->top=rgpt[0].y;
    pRect->right=rgpt[1].x;
    pRect->bottom=rgpt[1].y;

    return;
    }

/*
 * CPages::Draw
 *
 * Purpose:
 *  Paints the current page in the pages window.
 *
 * Parameters:
 *  hDC             HDC to draw on, could be a metafile or printer
 *                  DC or any other type of DC.
 *  fNoColor        BOOL indicating if we should use screen colors
 *                  or printer colors (B&W).  Objects are printed
 *                  as-is, however.  This is TRUE for printer DCs
 *                  or print preview.
 *  fPrinter        BOOL indicating if this is a printer DC in which
 *                  case we eliminate some of the fancy drawing,
 *                  like shadows on the page and so forth.
 *
 * Return Value:
 *  None
 */

void CPages::Draw(HDC hDC, BOOL fNoColor, BOOL fPrinter)
    {
    RECT            rc, rcT;
    UINT            uMM;
    HPEN            hPen;
    HBRUSH          hBrush;
    HGDIOBJ         hObj1, hObj2;
    COLORREF        cr;
    TCHAR           szTemp[20];
    UINT            cch;
    SIZE            sz;
    PCPage          pPage;
    RECT            rcPos;

    //Make sure the DC is in LOMETRIC
    uMM=SetMapMode(hDC, MM_LOMETRIC);

    if (!fPrinter)
        {
        /*
         * We maintain a 6mm border around the page on the screen
         * besides 12.7mm margins.  We also have to account for
         * the scroll position with m_*Pos which are in pixels so
         * we have to convert them.
         */

        SetRect(&rcPos, m_xPos, m_yPos, 0, 0);
        RectConvertMappings(&rcPos, hDC, FALSE);

        rc.left  = LOMETRIC_BORDER-rcPos.left;
        rc.top   =-LOMETRIC_BORDER-rcPos.top;
        }
    else
        {
        /*
         * We define the corner of the printed paper at a negative
         * offset so rc.right and rc.bottom come out right below.
         */
        SetRect(&rc, -(int)m_xMarginLeft, m_yMarginTop, 0, 0);
        }

    rc.right=rc.left+m_cx+(m_xMarginLeft+m_xMarginRight);
    rc.bottom=rc.top-m_cy-(m_yMarginTop+m_yMarginBottom);

    //Draw a rect filled with the window color to show the page.
    if (!fPrinter)
        {
        if (fNoColor)
            {
            //Black frame, white box for printed colors.
            hPen  =CreatePen(PS_SOLID, 0, RGB(0,0,0));
            hBrush=CreateSolidBrush(RGB(255, 255, 255));
            }
        else
            {
            //Normal colors on display
            hPen=CreatePen(PS_SOLID, 0
                , GetSysColor(COLOR_WINDOWFRAME));
            hBrush=CreateSolidBrush(GetSysColor(COLOR_WINDOW));
            }

        hObj1=SelectObject(hDC, hPen);
        hObj2=SelectObject(hDC, hBrush);

        //Paper boundary
        Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom+1);

        /*
         * Draw a shadow on the *visual* bottom and right edges
         * .5mm wide.  If the button shadow color and workspace
         * colors match, then use black.  We always use black
         * when printing as well.
         */
        if (fNoColor)
            cr=RGB(0,0,0);
        else
            {
            cr=GetSysColor(COLOR_BTNSHADOW);

            if (GetSysColor(COLOR_APPWORKSPACE)==cr)
                cr=RGB(0,0,0);
            }

        cr=SetBkColor(hDC, cr);
        SetRect(&rcT, rc.left+5, rc.bottom, rc.right+5,rc.bottom-5);
        ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rcT, NULL, 0, NULL);

        SetRect(&rcT, rc.right, rc.top-5, rc.right+5, rc.bottom-5);
        ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rcT, NULL, 0, NULL);
        SetBkColor(hDC, cr);

        SelectObject(hDC, hObj1);
        SelectObject(hDC, hObj2);
        DeleteObject(hBrush);
        DeleteObject(hPen);
        }

    //Write the page number in the lower left corner
    if (!fNoColor)
        {
        SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));
        SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
        }

    //Write the page number in our page font.
    cch=wsprintf(szTemp, TEXT("Page %d"), m_iPageCur+1);

    hObj1=SelectObject(hDC, m_hFont);
    GetTextExtentPoint(hDC, szTemp, cch, &sz);

    TextOut(hDC, rc.left+m_xMarginLeft
        , rc.bottom+m_yMarginBottom+sz.cy, szTemp, cch);

    SelectObject(hDC, hObj1);

    //Rectangle to show border.
    MoveToEx(hDC, rc.left+m_xMarginLeft, rc.top-m_yMarginTop, NULL);
    LineTo(hDC, rc.left+m_xMarginLeft,   rc.bottom+m_yMarginBottom);
    LineTo(hDC, rc.right-m_xMarginRight, rc.bottom+m_yMarginBottom);
    LineTo(hDC, rc.right-m_xMarginRight, rc.top-m_yMarginTop);
    LineTo(hDC, rc.left+m_xMarginLeft,   rc.top-m_yMarginTop);

    /*
     * Go draw the objects on this page.  If the page is not open,
     * we open it anyway.  If it is already open, then opening again
     * will bump it's reference count, so the Close in ineffectual.
     */
    if (PageGet(m_iPageCur, &pPage, TRUE))
        {
        if (!fPrinter)
            {
            pPage->Draw(hDC, rcPos.left, rcPos.top, fNoColor
                , fPrinter);
            }
        else
            pPage->Draw(hDC, 0, 0, fNoColor, fPrinter);

        pPage->Close(FALSE);
        }

    SetMapMode(hDC, uMM);
    return;
    }

/*
 * CPages::UpdateScrollRanges
 *
 * Purpose:
 *  Reset scrollbar ranges (horizontal and vertical) depending on
 *  the window size and the page size.  This function may remove
 *  the scrollbars altogether.
 *
 * Parameters:
 *  None, but set m_cx, m_cy and size m_hWnd before calling.
 *
 * Return Value:
 *  None
 */

void CPages::UpdateScrollRanges(void)
    {
    UINT        cxSB;   //Scrollbar width and height.
    UINT        cySB;
    UINT        cx, cy;
    UINT        dx, dy;
    UINT        u;
    int         iMin, iMax;
    RECT        rc;
    BOOL        fHScroll;
    BOOL        fVScroll;
    BOOL        fWasThere;

    GetClientRect(m_hWnd, &rc);

    cx=rc.right-rc.left;
    cy=rc.bottom-rc.top;

    //Convert dimensions of the image in LOMETRIC to pixels.
    SetRect(&rc, (m_cx+m_xMarginLeft+m_xMarginRight
        +LOMETRIC_BORDER*2), (m_cy+m_yMarginTop
        +m_yMarginBottom+LOMETRIC_BORDER*2), 0, 0);

    RectConvertMappings(&rc, NULL, TRUE);

    dx=rc.left;
    dy=-rc.top;

    //Assume that both scrollbars will be visible.
    fHScroll=TRUE;
    fVScroll=TRUE;

    /*
     * Determine:
     *  1)  Which scrollbars are needed.
     *  2)  How many divisions to give scrollbars so as to
     *      only scroll as little as necessary.
     */

    //Scrollbar dimensions in our units.
    cxSB=GetSystemMetrics(SM_CXVSCROLL);
    cySB=GetSystemMetrics(SM_CYHSCROLL);

    //Remove horizontal scroll if window >= cxPage+borders
    if (cx >= dx)
        fHScroll=FALSE;

    /*
     * If we still need a horizontal scroll, see if we need a
     * vertical taking the height of the horizontal scroll into
     * account.
     */

    u=fHScroll ? cySB : 0;

    if ((cy-u) >= dy)
        fVScroll=FALSE;

    //Check if adding vert scrollbar necessitates a horz now.
    u=fVScroll ? cxSB : 0;
    fHScroll=((cx-u) < dx);

    /*
     * Modify cx,cy to reflect the new client area before scaling
     * scrollbars.  We only affect the client size if there is a
     * *change* in scrollbar status:  if the scrollbar was there
     * but is no longer, then add to the client size; if it was
     * not there but now is, then subtract.
     */

    //Change cx depending on vertical scrollbar change
    GetScrollRange(m_hWnd, SB_VERT, &iMin, &iMax);
    fWasThere=(0!=iMin || 0!=iMax);

    if (fWasThere && !fVScroll)
        cx+=cxSB;

    if (!fWasThere && fVScroll)
        cx-=cxSB;

    //Change cy depending on horizontal scrollbar change
    GetScrollRange(m_hWnd, SB_HORZ, &iMin, &iMax);
    fWasThere=(0!=iMin || 0!=iMax);

    if (fWasThere && !fHScroll)
        cy+=cySB;

    if (!fWasThere && fHScroll)
        cy-=cySB;

    /*
     * Show/Hide the scrollbars if necessary and set the ranges.
     * The range is the number of units of the page we cannot see.
     */
    if (fHScroll)
        {
        //Convert current scroll position to new range.
        u=GetScrollPos(m_hWnd, SB_HORZ);

        if (0!=u)
            {
            GetScrollRange(m_hWnd, SB_HORZ, &iMin, &iMax);
            u=MulDiv(u, (dx-cx), (iMax-iMin));
            }

        SetScrollRange(m_hWnd, SB_HORZ, 0, dx-cx, FALSE);
        SetScrollPos(m_hWnd, SB_HORZ, u, TRUE);
        m_xPos=u;
        }
    else
        {
        SetScrollRange(m_hWnd, SB_HORZ, 0, 0, TRUE);
        m_xPos=0;
        }

    if (fVScroll)
        {
        //Convert current scroll position to new range.
        u=GetScrollPos(m_hWnd, SB_VERT);

        if (0!=u)
            {
            GetScrollRange(m_hWnd, SB_VERT, &iMin, &iMax);
            u=MulDiv(u, (dy-cy), (iMax-iMin));
            }

        SetScrollRange(m_hWnd, SB_VERT, 0, dy-cy, FALSE);
        SetScrollPos(m_hWnd, SB_VERT, u, TRUE);

        m_yPos=u;
        }
    else
        {
        SetScrollRange(m_hWnd, SB_VERT, 0, 0, TRUE);
        m_yPos=0;
        }

    //Repaint to insure that changes to m_x/yPos are reflected
    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }


PATRON.CPP

/*
 * PATRON.CPP
 * Patron Chapter 20
 *
 * WinMain which is all we need for the basic application.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "patron.h"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCPatronFrame   pFR;
    FRAMEINIT       fi;
    WPARAM          wRet=0;

    SETMESSAGEQUEUE;

    //Attempt to allocate and initialize the application
    pFR=new CPatronFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return -1;

    fi.idsMin=IDS_FRAMEMIN;
    fi.idsMax=IDS_FRAMEMAX;
    fi.idsStatMin=IDS_STATMESSAGEMIN;
    fi.idsStatMax=IDS_STATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=CW_USEDEFAULT;
    fi.cy=CW_USEDEFAULT;

    //If we can initialize pFR, start chugging messages
    if (pFR->Init(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

/*
 * CPatronFrame::CPatronFrame
 * CPatronFrame::~CPatronFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WInMain
 */

CPatronFrame::CPatronFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    : CFrame(hInst, hInstPrev, pszCmdLine, nCmdShow)
    {
    m_fInitialized=FALSE;
    m_pIClassDataTran=NULL;
    return;
    }

CPatronFrame::~CPatronFrame(void)
    {
    if (NULL!=m_pIClassDataTran)
        {
        m_pIClassDataTran->LockServer(FALSE);
        m_pIClassDataTran->Release();
        }

    OleFlushClipboard();

    if (m_fInitialized)
        OleUninitialize();

    return;
    }

/*
 * CPatronFrame::Init
 *
 * Purpose:
 *  Call OleInitialize then calling down into the base class
 *  initialization.
 *
 * Parameters:
 *  pFI             PFRAMEINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CPatronFrame::Init(PFRAMEINIT pFI)
    {
    HRESULT     hr;

    CHECKVER_OLE;

    if (FAILED(OleInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    //Lock the data transfer object factory as an optimization.
    hr=CoGetClassObject(CLSID_DataTransferObject
        , CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory
        , (PPVOID)&m_pIClassDataTran);

    if (SUCCEEDED(hr))
        m_pIClassDataTran->LockServer(TRUE);

    return CFrame::Init(pFI);
    }

/*
 * CPatronFrame::CreateCClient
 *
 * Purpose:
 *  Constructs a new client specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient        Pointer to the new client object.
 */

PCClient CPatronFrame::CreateCClient(void)
    {
    return (PCClient)(new CPatronClient(m_hInst, this));
    }

/*
 * CPatronFrame::RegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CPatronFrame::RegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //First let the standard frame do its thing
    if (!CFrame::RegisterAllClasses())
        return FALSE;

    //We need double-clicks now and for object activation.
    wc.style         = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
    wc.hInstance     = m_hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PagesWndProc;
    wc.cbWndExtra    = CBPAGESWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPAGES;

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

    return TRUE;
    }

/*
 * CPatronFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the Patron frame window that processes
 *  extra File menu items as well as the Page menu.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CPatronFrame::OnCommand(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PCPatronDoc     pDoc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many toolbar notifications.
     */
    if (m_fInit)
        return 0L;

    pDoc=(PCPatronDoc)m_pCL->ActiveDocument();

    if (NULL!=pDoc && (IDM_VERBMIN <= wID) && (IDM_VERBMAX >= wID))
        {
        pDoc->ActivateObject(wID-IDM_VERBMIN);
        return 0L;
        }

    switch (wID)
        {
        case IDM_FILEPRINT:
            pDoc->Print(m_hWnd);
            return 0L;

        case IDM_FILEPRINTERSETUP:
            pDoc->PrinterSetup(m_hWnd, FALSE);
            return 0L;

        case IDM_EDITPASTESPECIAL:
            pDoc->PasteSpecial(m_hWnd);
            return 0L;

        case IDM_EDITDELETEOBJECT:
            pDoc->Delete();
            return 0L;

        case IDM_EDITINSERTOBJECT:
            pDoc->InsertObject(m_hWnd);
            return 0L;

        case IDM_EDITCONVERT:
            pDoc->ConvertObject(m_hWnd);
            return 0L;

        //CHAPTER20MOD
        case IDM_EDITLINKS:
            pDoc->EditLinks(m_hWnd);
            return 0L;
        //End CHAPTER20MOD

        case IDM_PAGENEWPAGE:
            pDoc->NewPage();
            break;

        case IDM_PAGEDELETEPAGE:
            pDoc->DeletePage();
            break;

        case IDM_PAGENEXTPAGE:
            pDoc->NextPage();
            break;

        case IDM_PAGEPREVIOUSPAGE:
            pDoc->PreviousPage();
            break;

        case IDM_PAGEFIRSTPAGE:
            pDoc->FirstPage();
            break;

        case IDM_PAGELASTPAGE:
            pDoc->LastPage();
            break;

        //CHAPTER20MOD
        case IDM_PAGESHOWOBJECTS:
            {
            BOOL    fTemp;

            //First get the current state, then toggle it.
            fTemp=pDoc->ShowOrQueryObjectTypes(TRUE, FALSE);
            pDoc->ShowOrQueryObjectTypes(FALSE, !fTemp);
            }
            break;
        //End CHAPTER20MOD

        default:
           return CFrame::OnCommand(hWnd, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPatronFrame::CreateToolbar
 *
 * Purpose:
 *  Procedure to create all the necessary toolbar buttons.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of tools added to the bar.
 */

UINT CPatronFrame::CreateToolbar(void)
    {
    UINT            iLast;
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;

    //Insert the standard ones.
    iLast=CFrame::CreateToolbar();

    //Remove Undo:  we don't use it.
    m_pTB->Remove(IDM_EDITUNDO);

    /*
     * Insert Print File Import in the 5th position and account
     * for it in iLast.
     */
    m_pTB->Add(utCmd, 4, IDM_FILEPRINT, m_dxB, m_dyB
        , NULL, NULL, 6, uState);

    iLast++;

    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //Add New Page, and Delete Page
    m_pTB->Add(utCmd, iLast++, IDM_PAGENEWPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 2, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGEDELETEPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 3, uState);

    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //First, Prev, Next, Last pages.
    m_pTB->Add(utCmd, iLast++, IDM_PAGEFIRSTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 4, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGEPREVIOUSPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 5, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGENEXTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 6, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGELASTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 7, uState);

    return iLast;
    }

/*
 * CPatronFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CPatronFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    PCPatronDoc     pDoc;
    BOOL            fOK=FALSE;
    BOOL            fCallDefault=TRUE;
    UINT            uTemp;
    UINT            uTempE;
    UINT            uTempD;

    pDoc=(PCPatronDoc)m_pCL->ActiveDocument();

    uTempE=MF_ENABLED | MF_BYCOMMAND;
    uTempD=MF_DISABLED | MF_GRAYED | MF_BYCOMMAND;
    uTemp=((NULL!=pDoc) ? uTempE : uTempD);

    if (m_phMenu[0]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_FILEPRINT, uTemp);

        if (NULL!=pDoc)
            fOK=pDoc->FQueryPrinterSetup();

        EnableMenuItem(hMenu, IDM_FILEPRINTERSETUP
            , (fOK) ? uTempE : uTempD);
        }

    if (m_phMenu[1]==hMenu)
        {
        if (NULL!=pDoc)
            fOK=pDoc->FQueryPaste();

        EnableMenuItem(hMenu, IDM_EDITPASTE
            , (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITPASTESPECIAL
            , (fOK) ? uTempE : uTempD);

        //Cut, Copy, Delete depends on there being a selection.
        if (NULL!=pDoc)
            fOK=pDoc->FQueryObjectSelected(hMenu);
        else
            fOK=FALSE;

        EnableMenuItem(hMenu, IDM_EDITCUT, (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITCOPY
            , (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITDELETEOBJECT
            , (fOK) ? uTempE : uTempD);

        EnableMenuItem(hMenu, IDM_EDITINSERTOBJECT, uTemp);

        //CHAPTER20MOD
        if (NULL!=pDoc)
            fOK=pDoc->FQueryEnableEditLinks();
        else
            fOK=FALSE;

        EnableMenuItem(hMenu, IDM_EDITLINKS
            , (fOK) ? uTempE : uTempD);
        //End CHAPTER20MOD

        //We did the whole menu...
        fCallDefault=FALSE;
        }

    //Page menu
    if (m_phMenu[2]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_PAGENEWPAGE,      uTemp);
        EnableMenuItem(hMenu, IDM_PAGEDELETEPAGE,   uTemp);
        EnableMenuItem(hMenu, IDM_PAGENEXTPAGE,     uTemp);
        EnableMenuItem(hMenu, IDM_PAGEPREVIOUSPAGE, uTemp);
        EnableMenuItem(hMenu, IDM_PAGEFIRSTPAGE,    uTemp);
        EnableMenuItem(hMenu, IDM_PAGELASTPAGE,     uTemp);

        //CHAPTER20MOD
        //Check the Show Objects command or not.
        if (NULL!=pDoc)
            fOK=pDoc->ShowOrQueryObjectTypes(TRUE, FALSE);
        else
            fOK=FALSE;

        CheckMenuItem(hMenu, IDM_PAGESHOWOBJECTS, MF_BYCOMMAND
            | ((fOK) ? MF_CHECKED : MF_UNCHECKED));
        EnableMenuItem(hMenu, IDM_PAGESHOWOBJECTS, uTemp);
        //End CHAPTER20MOD
        }

    if (fCallDefault)
        CFrame::UpdateMenus(hMenu, iMenu);

    return;
    }

/*
 * CPatronFrame::UpdateToolbar
 *
 * Purpose:
 *  Enables and disables tools depending on whether we have
 *  a document or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronFrame::UpdateToolbar(void)
    {
    PCDocument  pDoc;
    BOOL        fEnable;

    //Let the default hack on its tools.
    CFrame::UpdateToolbar();

    pDoc=m_pCL->ActiveDocument();
    fEnable=(NULL!=pDoc);

    //No document, disable just about everything
    m_pTB->Enable(IDM_FILEPRINT,        fEnable);
    m_pTB->Enable(IDM_FILEPRINTERSETUP, fEnable);

    m_pTB->Enable(IDM_PAGENEWPAGE,      fEnable);
    m_pTB->Enable(IDM_PAGEDELETEPAGE,   fEnable);
    m_pTB->Enable(IDM_PAGEFIRSTPAGE,    fEnable);
    m_pTB->Enable(IDM_PAGEPREVIOUSPAGE, fEnable);
    m_pTB->Enable(IDM_PAGENEXTPAGE,     fEnable);
    m_pTB->Enable(IDM_PAGELASTPAGE,     fEnable);

    return;
    }

/*
 * CPatronFrame::FMessageHook
 *
 * Purpose:
 *  Override of CFrame::FMessageHook so we can specifically trap
 *  WM_MENUSELECT messages for the Object verb menu to provide some
 *  meaningful information on the status strip.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return value
 *                  for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CPatronFrame::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    BOOL        fRet=FALSE;

    *pLRes=0;

    MENUSELECTPARAMS(wItem, wMenuFlags, hMenu);

    //If this is the wrong message, nothing to do.
    if (WM_MENUSELECT!=iMsg)
        return FALSE;

    //This happens when there's no menu selection.
    if (-1==wMenuFlags)
        return FALSE;

    if (MF_POPUP & wMenuFlags)
        {
        /*
         * If this is the cascade verb menu itself, display the same
         * message.  m_phMenu[1] contains the current edit menu
         * handle.
         */
        if (0!=wItem)
            {
            fRet=((HMENU)wItem==GetSubMenu(m_phMenu[1]
                , MENUPOS_OBJECT));
            }
        }
    else
        {
        /*
         * If the ID is in the verb range, use
         * IDS_ITEMMESSAGEEDITOBJECT message
         */
        fRet=(IDM_VERBMIN <= wItem && IDM_VERBMAX >= wItem);
        }

    if (fRet)
        m_pSL->MessageDisplay(IDM_EDITOBJECT);

    return fRet;
    }


PATRON.H

/*
 * PATRON.H
 * Patron Chapter 20
 *
 * Single include file that pulls in everything needed for other
 * source files in the application.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _PATRON_H_
#define _PATRON_H_

#define INC_CLASSLIB
#define INC_OLEUI
//CHAPTER20MOD
#define CHAPTER20
//End CHAPTER20MOD
#include <inole.h>
#include "resource.h"

//Get editor window information
#include "pages.h"

/*
 * UINT value such that adding one produces zero.  Portable to Win32.
 * This is used to represent a non-existent zero-based UINT value
 */
#define NOVALUE                     ((UINT)-1)

//PATRON.CPP:  Frame object that creates a main window

class CPatronFrame : public CFrame
    {
    private:
        BOOL            m_fInitialized;     //OleInitialize worked
        LPCLASSFACTORY  m_pIClassDataTran;  //For locking.

    protected:
        //Overridable for creating a CPatronClient
        virtual PCClient    CreateCClient(void);

        virtual BOOL        FMessageHook(HWND, UINT, WPARAM, LPARAM
                                , LRESULT *);
        virtual BOOL        RegisterAllClasses(void);
        virtual UINT        CreateToolbar(void);
        virtual LRESULT     OnCommand(HWND, WPARAM, LPARAM);

    public:
        CPatronFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CPatronFrame(void);

        //Overrides
        virtual BOOL        Init(PFRAMEINIT);

        virtual void        UpdateMenus(HMENU, UINT);
        virtual void        UpdateToolbar(void);

    };

typedef CPatronFrame *PCPatronFrame;

//CLIENT.CPP

/*
 * The only reason we have a derived class here is to override
 * CreateCDocument so we can create our own type as well as
 * overriding NewDocument to perform one other piece of work once
 * the document's been created.
 */

class CPatronClient : public CClient
    {
    protected:
        //Overridable for creating a new CDocument
        virtual PCDocument CreateCDocument(void);

    public:
        CPatronClient(HINSTANCE, PCFrame);
        virtual ~CPatronClient(void);
    };

typedef CPatronClient *PCPatronClient;

//DOCUMENT.CPP

//Constant ID for the pages window that lives in a document window
#define ID_PAGES            723

class CPatronDoc : public CDocument
    {
    //These need access to FQueryPasteFromData, PasteFromData
    friend class CDropTarget;
    friend class CDropSource;

    protected:
        LONG            m_lVer;         //Loaded data version
        PCPages         m_pPG;          //Pages window in us
        LPSTORAGE       m_pIStorage;    //Root storage for document
        BOOL            m_fPrintSetup;

        class CDropTarget *m_pDropTarget;   //Registered target

        UINT            m_cfEmbeddedObject; //Clipboard formats
        UINT            m_cfObjectDescriptor;
        //CHAPTER20MOD
        UINT            m_cfLinkSource;
        UINT            m_cfLinkSrcDescriptor;
        BOOL            m_fShowTypes;       //Show Objects active?
        //End CHAPTER20MOD

    protected:
        virtual BOOL    FMessageHook(HWND, UINT, WPARAM, LPARAM
            , LRESULT *);

        BOOL            FQueryPasteFromData(LPDATAOBJECT
                            , LPFORMATETC, PTENANTTYPE);
        //CHAPTER20MOD
        BOOL            FQueryPasteLinkFromData(LPDATAOBJECT
                            , LPFORMATETC, PTENANTTYPE);
        //End CHAPTER20MOD
        BOOL            PasteFromData(LPDATAOBJECT, LPFORMATETC
                            , TENANTTYPE, PPATRONOBJECT, DWORD
                            , BOOL);

    public:
        CPatronDoc(HINSTANCE, PCFrame, PCDocumentAdviseSink);
        virtual ~CPatronDoc(void);

        virtual BOOL    Init(PDOCUMENTINIT);
        virtual void    Clear(void);

        virtual BOOL    FDirtyGet(void);
        virtual void    Delete(void);
        virtual BOOL    FQueryPrinterSetup(void);
        virtual BOOL    FQueryObjectSelected(HMENU);

        virtual UINT    Load(BOOL, LPTSTR);
        virtual UINT    Save(UINT, LPTSTR);

        virtual BOOL    Print(HWND);
        virtual UINT    PrinterSetup(HWND, BOOL);

        virtual BOOL    Clip(HWND, BOOL);
        virtual BOOL    FQueryPaste(void);
        virtual BOOL    Paste(HWND);
        virtual BOOL    PasteSpecial(HWND);

        //CHAPTER20MOD
        virtual BOOL    FQueryEnableEditLinks(void);
        virtual BOOL    EditLinks(HWND);
        virtual BOOL    ShowOrQueryObjectTypes(BOOL, BOOL);
        //End CHAPTER20MOD

        virtual UINT    NewPage(void);
        virtual UINT    DeletePage(void);
        virtual UINT    NextPage(void);
        virtual UINT    PreviousPage(void);
        virtual UINT    FirstPage(void);
        virtual UINT    LastPage(void);

        virtual void    Rename(LPTSTR);
        virtual BOOL    InsertObject(HWND);
        virtual void    ActivateObject(LONG);
        virtual BOOL    ConvertObject(HWND);
    };

typedef CPatronDoc *PCPatronDoc;

//Hook for Print Dialog to hide Setup... button
UINT CALLBACK PrintDlgHook(HWND, UINT, WPARAM, LPARAM);

//Drag-drop objects we need in the document

class CDropTarget : public IDropTarget
    {
    protected:
        ULONG               m_cRef;
        PCPatronDoc         m_pDoc;

        LPDATAOBJECT        m_pIDataObject;  //From DragEnter
        BOOL                m_fPendingRepaint;
        POINTL              m_ptPick;        //Pick-up offsets
        POINTL              m_ptLast;        //Last drag point
        SIZEL               m_szl;           //Object size
        BOOL                m_fFeedback;     //Draw feedback?
        FORMATETC           m_fe;            //Real dropping format

    public:
        CDropTarget(PCPatronDoc);
        ~CDropTarget(void);

        //IDropTarget interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP DragEnter(LPDATAOBJECT, DWORD, POINTL,LPDWORD);
        STDMETHODIMP DragOver(DWORD, POINTL, LPDWORD);
        STDMETHODIMP DragLeave(void);
        STDMETHODIMP Drop(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
    };

typedef CDropTarget *PCDropTarget;

class CDropSource : public IDropSource
    {
    protected:
        ULONG               m_cRef;

    public:
        CDropSource(void);
        ~CDropSource(void);

        //IDropSource interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP QueryContinueDrag(BOOL, DWORD);
        STDMETHODIMP GiveFeedback(DWORD);
    };

typedef CDropSource *PCDropSource;

#endif //_PATRON_H_


PRINT.CPP

/*
 * PRINT.CPP
 * Patron Chapter 20
 *
 * Implementation of printing functions for both CPatronDoc
 * and CPages classes.  These functions are here to keep clutter
 * down in document.cpp and pages.cpp.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

static HWND g_hDlgPrint=NULL;
static BOOL g_fCancelPrint=FALSE;

/*
 * CPatronDoc::Print
 *
 * Purpose:
 *  Prints the current document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *
 * Return Value:
 *  BOOL            TRUE if printing happened, FALSE if it didn't
 *                  start or didn't complete.
 */

BOOL CPatronDoc::Print(HWND hWndFrame)
    {
    PRINTDLG        pd;
    BOOL            fSuccess;

    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);
    pd.hwndOwner  =hWndFrame;
    pd.nCopies    =1;
    pd.nFromPage  =(USHORT)-1;
    pd.nToPage    =(USHORT)-1;
    pd.nMinPage   =1;
    pd.nMaxPage   =m_pPG->NumPagesGet();

    pd.lpfnPrintHook=PrintDlgHook;

    //Get the current document printer settings
    pd.hDevMode=m_pPG->DevModeGet();

    pd.Flags=PD_RETURNDC | PD_ALLPAGES | PD_COLLATE
        | PD_HIDEPRINTTOFILE | PD_NOSELECTION | PD_ENABLEPRINTHOOK;

    if (!PrintDlg(&pd))
        return FALSE;

    if (NULL!=pd.hDevMode)
        GlobalFree(pd.hDevMode);

    if (NULL!=pd.hDevNames)
        GlobalFree(pd.hDevNames);

    //Go do the actual printing.
    fSuccess=m_pPG->Print(pd.hDC, PSZ(IDS_DOCUMENTNAME), pd.Flags
        , pd.nFromPage, pd.nToPage, pd.nCopies);

    if (!fSuccess)
        {
        MessageBox(m_hWnd, PSZ(IDS_PRINTERROR)
            , PSZ(IDS_DOCUMENTCAPTION), MB_OK);
        }

    return fSuccess;
    }

/*
 * CPatronDoc::PrinterSetup
 *
 * Purpose:
 *  Selects a new printer and options for this document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *  fDefault        BOOL to avoid any dialog and just use the
 *                  default.
 *
 * Return Value:
 *  UINT            Undefined
 */

UINT CPatronDoc::PrinterSetup(HWND hWndFrame, BOOL fDefault)
    {
    PRINTDLG        pd;

    //Attempt to get printer metrics for the default printer.
    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);

    if (fDefault)
        pd.Flags=PD_RETURNDEFAULT;
    else
        {
        pd.hwndOwner=hWndFrame;
        pd.Flags=PD_PRINTSETUP;

        //Get the current document printer settings
        pd.hDevMode=m_pPG->DevModeGet();
        }

    if (!PrintDlg(&pd))
        return FALSE;

    if (!m_pPG->DevModeSet(pd.hDevMode, pd.hDevNames))
        {
        GlobalFree(pd.hDevNames);
        GlobalFree(pd.hDevMode);
        return FALSE;
        }

    FDirtySet(TRUE);
    return 1;
    }

/*
 * PrintDlgHook
 *
 * Purpose:
 *  Callback hook for the Print Dialog so we can hide the Setup
 *  button.  Patron only allows Setup before anything exists on
 *  the page, and is not written to handle setup at Print time.
 */

UINT CALLBACK PrintDlgHook(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    if (WM_INITDIALOG==iMsg)
        {
        HWND        hWnd;

        hWnd=GetDlgItem(hDlg, psh1);
        ShowWindow(hWnd, SW_HIDE);
        return TRUE;
        }

    return FALSE;
    }

/*
 * CPatronDoc::FQueryPrinterSetup
 *
 * Purpose:
 *  Returns whether or not the Printer Setup menu item can be
 *  enabled.  Once you create a tenant in any page, Printer Setup
 *  is voided simply to keep this sample simple, that is, we don't
 *  have to worry about reorganizing potentially large amounts
 *  of layout after we start plopping down objects.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE to enable the menu, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPrinterSetup(void)
    {
    return m_fPrintSetup;
    }

/*
 * CPages::DevModeSet
 *
 * Purpose:
 *  Provides the Pages with the current printer information.
 *
 * Parameters:
 *  hDevMode        HGLOBAL to the memory containing the DEVMODE.
 *                  This function assumes responsibility for this
 *                  handle.
 *  hDevNames       HGLOBAL providing the driver name and device
 *                  name from which we can create a DC for
 *                  information.
 *
 * Return Value:
 *  BOOL            TRUE if we could accept this configuration,
 *                  FALSE otherwise.  If we return TRUE we also
 *                  delete the old memory we hold.
 */

BOOL CPages::DevModeSet(HGLOBAL hDevMode, HGLOBAL hDevNames)
    {
    LPDEVNAMES      pdn;
    LPTSTR          psz;
    HGLOBAL         hMem;
    PDEVICECONFIG   pdc;
    LPDEVMODE       pdm;
    LPSTREAM        pIStream;
    HRESULT         hr;
    ULONG           cbDevMode, cbWrite;
    BOOL            fRet=FALSE;

    if (NULL==hDevMode || NULL==hDevNames)
        return FALSE;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    /*
     * DEVMODE is variable length--total length in hDevMode, so the
     * amount to write is that plus string space.  We subtract
     * sizeof(DEVMODE) as that is already included from GlobalSize.
     */
    cbDevMode=GlobalSize(hDevMode);
    cbWrite=cbDevMode+sizeof(DEVICECONFIG)-sizeof(DEVMODE);

    hMem=GlobalAlloc(GHND, cbWrite);

    if (NULL==hMem)
        {
        pIStream->Release();
        return FALSE;
        }

    pdc=(PDEVICECONFIG)GlobalLock(hMem);    //This always works
    pdm=(LPDEVMODE)GlobalLock(hDevMode);    //This might not

    if (NULL!=pdm)
        {
        pdc->cb=cbWrite;
        pdc->cbDevMode=cbDevMode;
        memcpy(&pdc->dm, pdm, (int)cbDevMode);
        GlobalUnlock(hDevMode);

        psz=(LPTSTR)GlobalLock(hDevNames);

        if (NULL!=psz)
            {
            pdn=(LPDEVNAMES)psz;
            lstrcpy(pdc->szDriver, psz+pdn->wDriverOffset);
            lstrcpy(pdc->szDevice, psz+pdn->wDeviceOffset);
            lstrcpy(pdc->szPort,   psz+pdn->wOutputOffset);

            pIStream->Write(pdc, cbWrite, &cbWrite);
            GlobalUnlock(hDevNames);
            fRet=TRUE;
            }
        }

    GlobalUnlock(hMem);
    GlobalFree(hMem);

    pIStream->Release();

    if (!fRet)
        return FALSE;

    GlobalFree(hDevNames);
    GlobalFree(hDevMode);

    return ConfigureForDevice();
    }

/*
 * CPages::DevModeGet
 *
 * Purpose:
 *  Retrieves a copy of the current DEVMODE structure for this
 *  Pages window.  The caller is responsible for this memory.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HGLOBAL         Handle to the memory containing the DEVMODE
 *                  structure.
 */

HGLOBAL CPages::DevModeGet(void)
    {
    HGLOBAL         hMem;
    LPVOID          pv;
    ULONG           cbDevMode, cbRead;
    LARGE_INTEGER   li;
    LPSTREAM        pIStream;
    HRESULT         hr;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    //Read how much to allocate for the DEVMODE structure
    LISet32(li, CBSEEKOFFSETCBDEVMODE);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);
    pIStream->Read(&cbDevMode, sizeof(ULONG), &cbRead);

    hMem=GlobalAlloc(GHND, cbDevMode);

    if (NULL!=hMem)
        {
        pv=(LPVOID)GlobalLock(hMem);
        pIStream->Read(pv, cbDevMode, &cbRead);
        GlobalUnlock(hMem);
        }

    pIStream->Release();
    return hMem;
    }

/*
 * CPages::DevReadConfig
 *
 * Purpose:
 *  Public function to read the current device configuration and
 *  optionally return an information context for it.
 *
 *
 * Parameters:
 *  ppcd            PCOMBINEDEVICE * in which to return a pointer
 *                  to an allocated structure that has all the
 *                  device information we want.  Ignored if NULL.
 *                  This is allocated with the task allocator.
 *  phDC            HDC * in which to return the information
 *                  context.  If NULL, no IC is created.  Caller
 *                  becomes responsible for the returned IC.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::DevReadConfig(PCOMBINEDEVICE *ppcd, HDC *phDC)
    {
    HRESULT         hr;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    PCOMBINEDEVICE  pcd;
    ULONG           cb, cbRead;
    LARGE_INTEGER   li;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    /*
     * Allocate the COMBINEDEVICE structure including the variable
     * information past the DEVMODE part.
     */

    hr=CoGetMalloc(MEMCTX_TASK, &pIMalloc);

    if (FAILED(hr))
        {
        pIStream->Release();
        return FALSE;
        }

    /*
     * Read size of the DEVICECONFIG structure including variable
     * portion of DEVMODE.  We need to load all this information
     * for CreateIC.  To this size we'll add the size of
     * DVTARGETDEVICE in order to allocate a COMBINEDEVICE.
     */

    pIStream->Read(&cb, sizeof(DWORD), &cbRead);

    pcd=(PCOMBINEDEVICE)pIMalloc->Alloc(cb+sizeof(DVTARGETDEVICE));

    if (NULL==pcd)
        {
        pIMalloc->Release();
        pIStream->Release();
        return FALSE;
        }

    //Now get the real information.
    LISet32(li, 0);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);
    pIStream->Read(&(pcd->dc), cb, &cbRead);
    pIStream->Release();

    /*
     * If requested, complete the DVTARGETDEVICE structure in
     * pcd and store pcd in *ppcd for return.
     */

    if (NULL!=ppcd)
        {
        WORD    cb=sizeof(DVTARGETDEVICE);

        pcd->td.tdSize=cb;
        pcd->td.tdExtDevmodeOffset=cb;
        pcd->td.tdDriverNameOffset=cb+sizeof(DEVMODE);
        pcd->td.tdDeviceNameOffset=cb+sizeof(DEVMODE)
            +(CCHDEVICENAME*sizeof(TCHAR));
        pcd->td.tdPortNameOffset  =cb+sizeof(DEVMODE)
            +(CCHDEVICENAME*2*sizeof(TCHAR));

        *ppcd=pcd;
        }

    //Create an IC if requested.
    if (NULL!=phDC)
        {
        //Get the DC then configure
        *phDC=CreateIC(pcd->dc.szDriver, pcd->dc.szDevice
            , pcd->dc.szPort, &(pcd->dc.dm));

        if (NULL==*phDC)
            return FALSE;
        }

    //pcd is a temporary allocation in this case
    if (NULL==ppcd)
        pIMalloc->Free(pcd);

    pIMalloc->Release();
    return (NULL!=*phDC);
    }

/*
 * CPages::ConfigureForDevice
 *
 * Purpose:
 *  Recalculates our drawing configuration based on the contents of
 *  an hDC.  If no HDC is given we use the contents of our DevMode
 *  stream.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::ConfigureForDevice(void)
    {
    POINT           ptOffset, ptPaper;
    RECT            rc;
    HDC             hDC;
    CHourglass      hg;

    if (!DevReadConfig(NULL, &hDC))
        return FALSE;

    //Get usable page dimensions:  already sensitive to orientation
    m_cx=GetDeviceCaps(hDC, HORZSIZE)*10-16; //*10: mm to LOMETRIC
    m_cy=GetDeviceCaps(hDC, VERTSIZE)*10-16; //-16: for driver bugs.

    //Calculate the printer-limited margins on sides in LOMETRIC.
    Escape(hDC, GETPRINTINGOFFSET, NULL, NULL, &ptOffset);
    Escape(hDC, GETPHYSPAGESIZE,   NULL, NULL, &ptPaper);

    SetRect(&rc, ptOffset.x, ptOffset.y, ptPaper.x, ptPaper.y);
    SetMapMode(hDC, MM_LOMETRIC);
    RectConvertMappings(&rc, hDC, FALSE);

    //Left and top margins are the printing offset.
    m_xMarginLeft= rc.left+8;   //+8 to match -16 above
    m_yMarginTop =-rc.top+8;    //LOMETRIC makes this negative.

    //Right is (paper width)-(usable width)-(left margin)
    m_xMarginRight =rc.right-m_cx-m_xMarginLeft;

    //Bottom is (paper height)-(usable height)-(top margin)+1
    m_yMarginBottom=-rc.bottom-m_cy-m_yMarginTop+1;

    UpdateScrollRanges();

    DeleteDC(hDC);
    return TRUE;
    }

/*
 * CPages::Print
 *
 * Purpose:
 *  Prints a specified range of pages to a given hDC.  Repeats for
 *  a given number of copies.
 *
 * Parameters:
 *  hDC             HDC to which we print.
 *  pszDoc          LPTSTR providing the document name.
 *  dwFlags         DWORD flags from PrintDlg
 *  iPageStart      UINT starting page index (one based)
 *  iPageEnd        UINT ending page index (one based).  Includes
 *                  this page.
 *  cCopies         UINT number of copies to print.  If PD_COLLATE
 *                  in dwFlags is set, we print multiple copies of
 *                  each page as we cycle through.  Otherwise we
 *                  cycle multiple times.
 *
 * Return Value:
 *  None
 */

BOOL CPages::Print(HDC hDC, LPTSTR pszDoc, DWORD dwFlags
    , UINT iPageStart, UINT iPageEnd, UINT cCopies)
    {
    BOOL        fError=FALSE;
    int         iPage, iPageInc;
    int         iUserPage, cPages;
    UINT        iRepeat, cRepeat;
    UINT        iCycle, cCycles;
    UINT        iPageHold=m_iPageCur;
    HWND        hWndT, hWndTop=NULL;
    DOCINFO     di;
    PCDocument  pDoc;

    //Validate hDC and page ranges
    if (NULL==hDC)
        return FALSE;

    if ((PD_PAGENUMS & dwFlags))
        {
        if (-1==iPageStart)
            iPageStart=0;
        else
            iPageStart--;   //Switch to zero offset.

        if (-1==iPageEnd)
            iPageEnd=m_cPages-1;
        else
            iPageEnd--;     //Switch to zero offset.
        }
    else //Can't test PD_ALLPAGES with & since it's defined as 0L
        {
        iPageStart=0;
        iPageEnd=m_cPages-1;
        }

    //Arrange cycles and repeats depending on cCopies and collating
    if (PD_COLLATE & dwFlags)
        {
        cCycles=cCopies;
        cRepeat=1;
        }
    else
        {
        cCycles=1;
        cRepeat=cCopies;
        }

    //Disable the frame window to prevent reentrancy while printing.
    hWndT=GetParent(m_hWnd);
    pDoc=(PCDocument)SendMessage(hWndT, DOCM_PDOCUMENT, 0, 0L);

    if (NULL!=pDoc)
        {
        PCFrame pFR;

        pFR=pDoc->FrameGet();
        hWndTop=pFR->Window();
        EnableWindow(hWndTop, FALSE);
        }

    SetAbortProc(hDC, AbortProc);
    g_fCancelPrint=FALSE;

    //If these don't work then we'll just live without a dialog.
    g_hDlgPrint=CreateDialog(m_hInst, MAKEINTRESOURCE(IDD_PRINTING)
        , hWndTop, PrintDlgProc);

    //Increment for either direction.
    iPageInc=(iPageStart > iPageEnd) ? -1 : 1;

    //Initial entries in dialog box.
    cPages=1+((int)(iPageEnd-iPageStart)*iPageInc);

    SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE, 1, (LPARAM)cPages);
    SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, 1, (LPARAM)cRepeat);

    di.cbSize=sizeof(DOCINFO);
    di.lpszDocName=pszDoc;
    di.lpszOutput=NULL;

    if (StartDoc(hDC, &di) > 0)
        {
        /*
         * Iterate over the pages, repeating each page depending on
         * the copies we want and if we have collate enabled.
         */

        for (iCycle=1; iCycle <= cCycles; iCycle++)
            {
            if (PD_COLLATE & dwFlags)
                {
                SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, iCycle
                    , (LPARAM)cCycles);
                }

            //iPageInc controls direction
            for (iPage=iPageStart; ; iPage+=iPageInc)
                {
                iUserPage=1+((iPage-(int)iPageStart)*iPageInc);

                SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE
                    , iUserPage, (LPARAM)cPages);

                m_iPageCur=iPage;   //We restore this later.

                for (iRepeat=1; iRepeat <= cRepeat; iRepeat++)
                    {
                    if (!(PD_COLLATE & dwFlags))
                        {
                        SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE
                            , iRepeat, (LPARAM)cRepeat);
                        }

                    StartPage(hDC);
                    Draw(hDC, TRUE, TRUE);

                    if (EndPage(hDC) < 0)
                        fError=TRUE;

                    if (fError || g_fCancelPrint)
                        break;
                    }

                if (fError || g_fCancelPrint)
                    break;

                //If we just printed the last page, time to quit.
                if (iPage==(int)iPageEnd)
                    break;
                }

            if (fError || g_fCancelPrint)
                break;
            }

        if (!fError)
            EndDoc(hDC);
        else
            AbortDoc(hDC);
        }
    else
        fError=TRUE;

    //Set the page back to what it was before all this started.
    m_iPageCur=iPageHold;

    EnableWindow(hWndTop, TRUE);
    SetFocus(hWndTop);
    DestroyWindow(g_hDlgPrint);
    DeleteDC(hDC);
    return !fError;
    }

/*
 * AbortProc
 *
 * Purpose:
 *  Abort procedure for printing the pages.
 *
 * Parameters:
 *  hDC             HDC on which printing is happening.
 *  iErr            int error code.
 *
 * Return Value:
 *  BOOL            TRUE to continue the print job, FALSE otherwise.
 */

BOOL APIENTRY AbortProc(HDC hDC, int iErr)
    {
    MSG     msg;

    while (!g_fCancelPrint
        && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
        if (NULL==g_hDlgPrint
            || !IsDialogMessage(g_hDlgPrint, &msg))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    return !g_fCancelPrint;
    }

/*
 * PrintDlgProc
 *
 * Purpose:
 *  Modeless dialog procedure for the dialog displayed while Patron
 *  is printing pages.
 */

BOOL APIENTRY PrintDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    TCHAR           szFormat[40];
    TCHAR           szOutput[80];

    switch (iMsg)
        {
        case WM_INITDIALOG:
            EnableMenuItem(GetSystemMenu(hDlg, FALSE), SC_CLOSE
                , MF_GRAYED);
            return TRUE;

        case WM_COMMAND:
            //Cancel button was pressed.
            g_fCancelPrint=TRUE;
            ShowWindow(hDlg, SW_HIDE);
            return TRUE;

        case PRINTM_PAGEUPDATE:
            GetDlgItemText(hDlg, ID_PAGESTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTPAGE, szOutput);
            return TRUE;

        case PRINTM_COPYUPDATE:
            GetDlgItemText(hDlg, ID_COPYSTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTCOPY, szOutput);
            return TRUE;
        }

    return FALSE;
    }


RESOURCE.H

/*
 * RESOURCE.H
 * Patron Chapter 20
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Note that we augment classlib.h which we include before this.

//Position of Window menu.
#undef  WINDOW_MENU
#define WINDOW_MENU                 3

//Position of the "Object" item on the Edit menu.
#define MENUPOS_OBJECT              8
#define MENUPOS_OBJECTONPOPUP       5

//Popup menu
#define IDR_RIGHTPOPUPMENU          2

//Total number of menus
#undef  CMENUS
#ifdef MDI
#define CMENUS                      5
#else
#define CMENUS                      4
#endif

//Menu command identifiers.

#define IDM_FILEPRINT               (IDM_CUSTOMFILEMIN)
#define IDM_FILEPRINTERSETUP        (IDM_CUSTOMFILEMIN+1)

#define IDM_EDITPASTESPECIAL        (IDM_CUSTOMEDITMIN)
#define IDM_EDITDELETEOBJECT        (IDM_CUSTOMEDITMIN+1)
#define IDM_EDITINSERTOBJECT        (IDM_CUSTOMEDITMIN+2)
#define IDM_EDITOBJECT              (IDM_CUSTOMEDITMIN+3)
#define IDM_EDITCONVERT             (IDM_CUSTOMEDITMIN+4)
//CHAPTER20MOD
#define IDM_EDITLINKS               (IDM_CUSTOMEDITMIN+5)
//End CHAPTER20MOD

#define IDM_PAGENEWPAGE             (IDM_CUSTOMMIN)
#define IDM_PAGEDELETEPAGE          (IDM_CUSTOMMIN+1)
#define IDM_PAGENEXTPAGE            (IDM_CUSTOMMIN+2)
#define IDM_PAGEPREVIOUSPAGE        (IDM_CUSTOMMIN+3)
#define IDM_PAGEFIRSTPAGE           (IDM_CUSTOMMIN+4)
#define IDM_PAGELASTPAGE            (IDM_CUSTOMMIN+5)
//CHAPTER20MOD
#define IDM_PAGESHOWOBJECTS         (IDM_CUSTOMMIN+6)
//End CHAPTER20MOD

//This special item is for the dynamic verbs on the Edit/Object item.
#define IDM_VERBMIN                 (IDM_CUSTOMMIN+100)
#define IDM_VERBMAX                 (IDM_CUSTOMMIN+200)

/*
 * IDs for StatStrip handling of popup menus:  must be in order of menu
 * CLASSRES.H already defines ID_MENUFILE and ID_MENUEDIT for us.
 */

#undef ID_MENUWINDOW
#undef ID_MENUHELP

#define ID_MENUPAGE                 (ID_MENUCUSTOMMIN+0)
#define ID_MENUWINDOW               (ID_MENUCUSTOMMIN+1)
#define ID_MENUHELP                 (ID_MENUCUSTOMMIN+2)

//Stringtable IDs.  Keep sequential for each group.
#define IDS_FRAMEMIN                IDS_STANDARDFRAMEMIN
#define IDS_FRAMEMAX                IDS_STANDARDFRAMEMAX

#define IDS_DOCUMENTMIN             IDS_STANDARDDOCMIN
#define IDS_PRINTERROR              (IDS_CUSTOMDOCMIN+0)
#define IDS_DOCUMENTNAME            (IDS_CUSTOMDOCMIN+1)
#define IDS_PASTEASPATRON           (IDS_CUSTOMDOCMIN+2)
#define IDS_PASTEMETAFILE           (IDS_CUSTOMDOCMIN+3)
#define IDS_PASTEASMETAFILE         (IDS_CUSTOMDOCMIN+4)
#define IDS_PASTEDIB                (IDS_CUSTOMDOCMIN+5)
#define IDS_PASTEASDIB              (IDS_CUSTOMDOCMIN+6)
#define IDS_PASTEBITMAP             (IDS_CUSTOMDOCMIN+7)
#define IDS_PASTEASBITMAP           (IDS_CUSTOMDOCMIN+8)
#define IDS_PASTEOBJECT             (IDS_CUSTOMDOCMIN+9)
#define IDS_PASTEASOBJECT           (IDS_CUSTOMDOCMIN+10)
//CHAPTER20MOD
#define IDS_PASTELINK               (IDS_CUSTOMDOCMIN+11)
#define IDS_PASTEASLINK             (IDS_CUSTOMDOCMIN+12)
#define IDS_DOCUMENTMAX             (IDS_CUSTOMDOCMIN+12)
//End CHAPTER20MOD

//These are specifically for the StatStrip
#define IDS_STATMESSAGEMIN                  IDS_STANDARDSTATMESSAGEMIN

#define IDS_ITEMMESSAGEFILEPRINT            (IDS_CUSTOMSTATMESSAGEMIN+0)
#define IDS_ITEMMESSAGEFILEPRINTERSETUP     (IDS_CUSTOMSTATMESSAGEMIN+1)

#define IDS_MENUMESSAGEPAGE                 (IDS_CUSTOMSTATMESSAGEMIN+2)
#define IDS_ITEMMESSAGEPAGENEWPAGE          (IDS_CUSTOMSTATMESSAGEMIN+3)
#define IDS_ITEMMESSAGEPAGEDELETEPAGE       (IDS_CUSTOMSTATMESSAGEMIN+4)
#define IDS_ITEMMESSAGEPAGENEXTPAGE         (IDS_CUSTOMSTATMESSAGEMIN+5)
#define IDS_ITEMMESSAGEPAGEPREVIOUSPAGE     (IDS_CUSTOMSTATMESSAGEMIN+6)
#define IDS_ITEMMESSAGEPAGEFIRSTPAGE        (IDS_CUSTOMSTATMESSAGEMIN+7)
#define IDS_ITEMMESSAGEPAGELASTPAGE         (IDS_CUSTOMSTATMESSAGEMIN+8)
#define IDS_ITEMMESSAGEEDITPASTESPECIAL     (IDS_CUSTOMSTATMESSAGEMIN+9)
#define IDS_ITEMMESSAGEEDITDELETEOBJECT     (IDS_CUSTOMSTATMESSAGEMIN+10)
#define IDS_ITEMMESSAGEEDITINSERTOBJECT     (IDS_CUSTOMSTATMESSAGEMIN+11)
#define IDS_ITEMMESSAGEEDITOBJECT           (IDS_CUSTOMSTATMESSAGEMIN+12)
#define IDS_ITEMMESSAGEEDITCONVERT          (IDS_CUSTOMSTATMESSAGEMIN+13)
//CHAPTER20MOD
#define IDS_ITEMMESSAGEEDITLINKS            (IDS_CUSTOMSTATMESSAGEMIN+14)
#define IDS_ITEMMESSAGEPAGESHOWOBJECTS      (IDS_CUSTOMSTATMESSAGEMIN+15)
#define IDS_STATMESSAGEMAX                  (IDS_CUSTOMSTATMESSAGEMIN+15)
//End CHAPTER20MOD

//Definitions for the printing dialog.

#define IDD_PRINTING                2

#define ID_CURRENTPAGE              101
#define ID_CURRENTCOPY              102
#define ID_COPYSTRING               103
#define ID_PAGESTRING               104

#define PRINTM_PAGEUPDATE           (WM_USER+100)
#define PRINTM_COPYUPDATE           (WM_USER+101)

#endif //_RESOURCE_H_


TENANT.CPP

/*
 * TENANT.CPP
 * Patron Chapter 20
 *
 * Implementation of the CTentant class which holds information
 * for a single object on a page.  It maintains position, references
 * to data, and a storage.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CTenant::CTenant
 * CTenant::~CTenant
 *
 * Constructor Parameters:
 *  dwID            DWORD identifier for this page.
 *  hWnd            HWND of the pages window.
 *  pPG             PCPages to the parent structure.
 */

CTenant::CTenant(DWORD dwID, HWND hWnd, PCPages pPG)
    {
    m_hWnd=hWnd;
    m_dwID=dwID;

    m_fInitialized=0;
    m_pIStorage=NULL;
    m_cOpens=0;

    m_pObj=NULL;
    m_pPG =pPG;
    m_clsID=CLSID_NULL;
    m_fSetExtent=FALSE;

    m_cRef=0;
    m_pIOleObject=NULL;
    m_pIViewObject2=NULL;
    m_grfMisc=0;

    m_pImpIOleClientSite=NULL;
    m_pImpIAdviseSink=NULL;

    m_fRepaintEnabled=TRUE;
    //CHAPTER20MOD
    m_pmkFile=NULL;
    m_fLinkAvail=TRUE;          //Checked on Load
    //End CHAPTER20MOD
    return;
    }

CTenant::~CTenant(void)
    {
    //CHAPTER20MOD
    ReleaseInterface(m_pmkFile);
    //End CHAPTER20MOD

    //Object pointers cleaned up in Close.

    DeleteInterfaceImp(m_pImpIAdviseSink);
    DeleteInterfaceImp(m_pImpIOleClientSite);
    return;
    }

/*
 * CTenant::QueryInterface
 * CTenant::AddRef
 * CTenant::Release
 *
 * Purpose:
 *  IUnknown members for CTenant object.
 */

STDMETHODIMP CTenant::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IOleClientSite==riid)
        *ppv=m_pImpIOleClientSite;

    if (IID_IAdviseSink==riid)
        *ppv=m_pImpIAdviseSink;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CTenant::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CTenant::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CTenant::GetID
 *
 * Return Value:
 *  DWORD           dwID field in this tenant.
 */

DWORD CTenant::GetID(void)
    {
    return m_dwID;
    }

/*
 * CTenant::GetStorageName
 *
 * Parameters:
 *  pszName         LPOLESTR to a buffer in which to store the storage
 *                  name for this tenant.
 *
 * Return Value:
 *  UINT            Number of characters stored.
 */

UINT CTenant::GetStorageName(LPOLESTR pszName)
    {
   #ifdef WIN32ANSI
    char        szTemp[32];
    UINT        cch;

    cch=wsprintf(szTemp, "Tenant %lu", m_dwID);
    MultiByteToWideChar(CP_ACP, 0, szTemp, -1, pszName, 32);
    return cch;
   #else
    return wsprintf(pszName, TEXT("Tenant %lu"), m_dwID);
   #endif
    }

/*
 * CTenant::StorageGet
 *
 * Purpose:
 *  Returns the IStorage pointer maintained by this tenant,
 *  AddRef'd of course.
 *
 * Parameters:
 *  ppStg           LPSTORAGE * in which to return the pointer.
 *
 * Return Value:
 *  None
 */

void CTenant::StorageGet(LPSTORAGE *ppStg)
    {
    if (NULL==ppStg)
        return;

    *ppStg=m_pIStorage;

    if (NULL!=*ppStg)
        (*ppStg)->AddRef();

    return;
    }

/*
 * CTenant::Create
 *
 * Purpose:
 *  Creates a new tenant of the given CLSID, which can be either a
 *  static bitmap or metafile or any compound document object.
 *
 * Parameters:
 *  tType           TENANTTYPE to create, either a static metafile,
 *                  bitmap, or some kind of compound document object
 *                  This determines which OleCreate* call we use.
 *  pvType          LPVOID providing the relevant pointer from which
 *                  to create the tenant, depending on iType.
 *  pFE             LPFORMATETC specifying the type of renderings
 *                  to use.
 *  pptl            PPOINTL in which we store offset coordinates.
 *  pszl            LPSIZEL where this object should store its
 *                  lometric extents.
 *  pIStorage       LPSTORAGE of the page we live in.  We have to
 *                  create another storage in this for the tenant.
 *  ppo             PPATRONOBJECT containing placement data.
 *  dwData          DWORD with extra data, sensitive to iType.
 *
 * Return Value:
 *  UINT            A CREATE_* value depending on what we
 *                  actually do.
 */

UINT CTenant::Create(TENANTTYPE tType, LPVOID pvType
    , LPFORMATETC pFE, PPOINTL pptl, LPSIZEL pszl
    , LPSTORAGE pIStorage, PPATRONOBJECT ppo, DWORD dwData)
    {
    HRESULT             hr;
    LPUNKNOWN           pObj;
    UINT                uRet=CREATE_GRAPHICONLY;

    if (NULL==pvType || NULL==pIStorage)
        return CREATE_FAILED;

    //Fail if this is called for an already living tenant.
    if (m_fInitialized)
        return CREATE_FAILED;

    m_fInitialized=TRUE;

    //Create a new storage for this tenant.
    if (!Open(pIStorage))
        return CREATE_FAILED;

    /*
     * Get the placement info if it's here.  We either have a non-
     * NULL PPATRONOBJECT in ppo or we have to use default
     * placement and retrieve the size from the object itself.
     */
    pszl->cx=0;
    pszl->cy=0;

    if (NULL!=ppo)
        {
        *pFE=ppo->fe;
        *pptl=ppo->ptl;
        *pszl=ppo->szl;     //Could be 0,0 , so we ask object

        uRet=CREATE_PLACEDOBJECT;
        }

    hr=ResultFromScode(E_FAIL);

    //Now create an object based specifically for the type.
    switch (tType)
        {
        case TENANTTYPE_NULL:
            break;

        case TENANTTYPE_STATIC:
            /*
             * We could use OleCreateStaticFromData here which does
             * pretty much what we're doing below.  However, it does
             * not allow us to control whether we paste a bitmap or
             * a metafile--it uses metafile first, bitmap second.
             * For this reason we'll use code developed in Chapter
             * 11's FreeLoader to affect the paste.
             */
            hr=CreateStatic((LPDATAOBJECT)pvType, pFE, &pObj);
            break;

        case TENANTTYPE_EMBEDDEDOBJECT:
            hr=OleCreate(*((LPCLSID)pvType), IID_IUnknown
                , OLERENDER_DRAW, NULL, NULL, m_pIStorage
                , (PPVOID)&pObj);
            break;

        case TENANTTYPE_EMBEDDEDFILE:
            hr=OleCreateFromFile(CLSID_NULL, (LPTSTR)pvType
                , IID_IUnknown, OLERENDER_DRAW, NULL, NULL
                , m_pIStorage, (PPVOID)&pObj);
            break;

        case TENANTTYPE_EMBEDDEDOBJECTFROMDATA:
            hr=OleCreateFromData((LPDATAOBJECT)pvType, IID_IUnknown
                , OLERENDER_DRAW, NULL, NULL, m_pIStorage
                , (PPVOID)&pObj);
            break;

        //CHAPTER20MOD
        case TENANTTYPE_LINKEDFILE:
            hr=OleCreateLinkToFile((LPTSTR)pvType, IID_IUnknown
                , OLERENDER_DRAW, NULL, NULL, m_pIStorage
                , (PPVOID)&pObj);
            break;

        case TENANTTYPE_LINKEDOBJECTFROMDATA:
            hr=OleCreateLinkFromData((LPDATAOBJECT)pvType
                , IID_IUnknown, OLERENDER_DRAW, NULL, NULL
                , m_pIStorage, (PPVOID)&pObj);
            break;
        //End CHAPTER20MOD
        default:
            break;
        }

    //If creation didn't work, get rid of the element Open created.
    if (FAILED(hr))
        {
        Destroy(pIStorage);
        return CREATE_FAILED;
        }

    //We don't get the size if PatronObject data was seen already.
    if (!ObjectInitialize(pObj, pFE, dwData))
        {
        Destroy(pIStorage);
        return CREATE_FAILED;
        }

    if (0==pszl->cx && 0==pszl->cy)
        {
        SIZEL   szl;

        //Try to get the real size of the object, default to 2"*2"
        SETSIZEL((*pszl), 2*LOMETRIC_PER_INCH, 2*LOMETRIC_PER_INCH);
        hr=ResultFromScode(E_FAIL);

        //Try IViewObject2 first, then IOleObject as a backup.
        if (NULL!=m_pIViewObject2)
            {
            hr=m_pIViewObject2->GetExtent(m_fe.dwAspect, -1, NULL
                , &szl);
            }
        else
            {
            if (NULL!=m_pIOleObject)
                hr=m_pIOleObject->GetExtent(m_fe.dwAspect, &szl);
            }

        if (SUCCEEDED(hr))
            {
            //Convert HIMETRIC to our LOMETRIC mapping
            SETSIZEL((*pszl), szl.cx/10, szl.cy/10);
            }
        }

    return uRet;
    }

/*
 * CTenant::Load
 *
 * Purpose:
 *  Recreates the object living in this tenant in place of calling
 *  FCreate.  This is used in loading as opposed to new creation.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE of the page we live in.
 *  pti             PTENTANTINFO containing persistent information.
 *                  The ID value in this structure is ignored.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CTenant::Load(LPSTORAGE pIStorage, PTENANTINFO pti)
    {
    HRESULT         hr;
    LPUNKNOWN       pObj;
    DWORD           dwState=TENANTSTATE_DEFAULT;

    if (NULL==pIStorage || NULL==pti)
        return FALSE;

    /*
     * If we already initialized once, clean up, releasing
     * everything before we attempt to reload.  This happens
     * when using the Convert Dialog.
     */
    if (m_fInitialized)
        {
        //Preserve all states except open
        dwState=(m_dwState & ~TENANTSTATE_OPEN);
        m_cRef++;   //Prevent accidental closure

        //This should release all holds on our IStorage as well.
        if (NULL!=m_pIViewObject2)
            {
            m_pIViewObject2->SetAdvise(m_fe.dwAspect, 0, NULL);
            ReleaseInterface(m_pIViewObject2);
            }

        ReleaseInterface(m_pIOleObject);
        ReleaseInterface(m_pObj);

        m_pIStorage=NULL;   //We'll have already released this.
        m_cRef--;           //Match safety increment above.
        }

    m_fInitialized=TRUE;

    //Open the storage for this tenant.
    if (!Open(pIStorage))
        return FALSE;

    //CHAPTER20MOD
    /*
     * NOTE:  If you do not pass an IOleClientSite to OleLoad
     * it will not automatically reconnect a linked object to
     * the running source via IOleLink::BindIfRunning.  Since
     * we do not pass m_pImpIOleClientSite here, we'll call
     * BindIfRunning ourselves in ObjectInitialize.
     */
    //End CHAPTER20MOD
    hr=OleLoad(m_pIStorage, IID_IUnknown, NULL, (PPVOID)&pObj);

    if (FAILED(hr))
        {
        Destroy(pIStorage);
        return FALSE;
        }

    m_fSetExtent=pti->fSetExtent;
    ObjectInitialize(pObj, &pti->fe, NULL);

    //Restore the original state before reloading.
    m_dwState=dwState;

    RectSet(&pti->rcl, FALSE, FALSE);
    return TRUE;
    }

/*
 * CTenant::GetInfo
 *
 * Purpose:
 *  Retrieved a TENANTINFO structure for this tenant.
 *
 * Parameters:
 *  pti             PTENANTINFO structure to fill
 *
 * Return Value:
 *  None
 */

void CTenant::GetInfo(PTENANTINFO pti)
    {
    if (NULL!=pti)
        {
        pti->dwID=m_dwID;
        pti->rcl=m_rcl;
        pti->fe=m_fe;
        pti->fSetExtent=m_fSetExtent;
        }

    return;
    }

/*
 * CTenant::ObjectInitialize
 * (Protected)
 *
 * Purpose:
 *  Performs operations necessary after creating an object or
 *  reloading one from storage.
 *
 * Parameters:
 *  pObj            LPUNKNOWN of the object in this tenant.
 *  pFE             LPFORMATETC describing the graphic here.
 *  dwData          DWORD extra data.  If pFE->dwAspect==
 *                  DVASPECT_ICON then this is the iconic metafile.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CTenant::ObjectInitialize(LPUNKNOWN pObj, LPFORMATETC pFE
    , DWORD dwData)
    {
    HRESULT         hr;
    LPPERSIST       pIPersist=NULL;
    DWORD           dw;
    PCDocument      pDoc;
    TCHAR           szFile[CCHPATHMAX];
    //CHAPTER20MOD
    LPOLELINK       pIOleLink=NULL;
    //End CHAPTER20MOD

    if (NULL==pObj || NULL==pFE)
        return FALSE;

    m_pObj=pObj;
    m_fe=*pFE;
    m_fe.ptd=NULL;
    m_dwState=TENANTSTATE_DEFAULT;

    /*
     * Determine the type:  Static or Embedded.  If Static,
     * this will have CLSID_Picture_Metafile or CLSID_Picture_Dib.
     * Otherwise it's embedded.  Later we'll add a case for links.
     */
    m_tType=TENANTTYPE_EMBEDDEDOBJECT;

    if (SUCCEEDED(pObj->QueryInterface(IID_IPersist
        , (PPVOID)&pIPersist)))
        {
        CLSID   clsid=CLSID_NULL;

        hr=pIPersist->GetClassID(&clsid);

        //If we don't have a CLSID, default to static
        if (FAILED(hr) || CLSID_Picture_Metafile==clsid
            || CLSID_Picture_Dib==clsid)
            m_tType=TENANTTYPE_STATIC;

        pIPersist->Release();
        }

    //CHAPTER20MOD
    //Check if this is a linked object.
    if (SUCCEEDED(pObj->QueryInterface(IID_IOleLink
        , (PPVOID)&pIOleLink)))
        {
        LPMONIKER   pmk;

        hr=pIOleLink->GetSourceMoniker(&pmk);

        if (FAILED(hr) || NULL==pmk)
            m_tType=TENANTTYPE_STATIC;
        else
            {
            m_tType=TENANTTYPE_LINKEDOBJECT;
            pmk->Release();

            //Connect to the object if the source is running.
            pIOleLink->BindIfRunning();
            }

        pIOleLink->Release();
        }
    //End CHAPTER20MOD

    m_pIViewObject2=NULL;
    hr=pObj->QueryInterface(IID_IViewObject2
        , (PPVOID)&m_pIViewObject2);

    if (FAILED(hr))
        return FALSE;

    m_pIViewObject2->SetAdvise(m_fe.dwAspect, 0, m_pImpIAdviseSink);

    //We need an IOleObject most of the time, so get one here.
    m_pIOleObject=NULL;
    hr=pObj->QueryInterface(IID_IOleObject
         , (PPVOID)&m_pIOleObject);

    /*
     * Follow up object creation with advises and so forth.  If
     * we cannot get IOleObject here, then we know we can't do
     * any IOleObject actions from here on--object is static.
     */
    if (FAILED(hr))
        return TRUE;

    /*
     * Get the MiscStatus bits and check for OLEMISC_ONLYICONIC.
     * If set, force dwAspect in m_fe to DVASPECT_ICON so we
     * remember to draw it properly and do extents right.
     */
    m_pIOleObject->GetMiscStatus(m_fe.dwAspect, &m_grfMisc);

    if (OLEMISC_ONLYICONIC & m_grfMisc)
        m_fe.dwAspect=DVASPECT_ICON;

    /*
     * We could pass m_pImpIOleClientSite in an OleCreate* call, but
     * since this function could be called after OleLoad, we still
     * need to do this here, so it's always done here...
     */
    m_pIOleObject->SetClientSite(m_pImpIOleClientSite);
    m_pIOleObject->Advise(m_pImpIAdviseSink, &dw);

    OleSetContainedObject(m_pIOleObject, TRUE);

    /*
     * For IOleObject::SetHostNames we need the application name
     * and the document name (which is passed in the object
     * parameter).  The design of Patron doesn't give us nice
     * structured access to the name of the document we're in, so
     * I grab the parent of the Pages window (the document) and
     * send it DOCM_PDOCUMENT which returns us the pointer.
     * Roundabout, but it works.
     */

    pDoc=(PCDocument)SendMessage(GetParent(m_hWnd), DOCM_PDOCUMENT
        , 0, 0L);

    if (NULL!=pDoc)
        pDoc->FilenameGet(szFile, CCHPATHMAX);
    else
        szFile[0]=0;

    NotifyOfRename(szFile, NULL);

    /*
     * If we're creating an iconic aspect object and we have
     * an object from the Insert Object dialog, then we need to
     * store that iconic presentation in the cache, handled
     * with the utility function INOLE_SwitchDisplayAspect.  In
     * this case dwData is a handle to the metafile containing
     * the icon.  If dwData is NULL then we depend on the
     * server to provide the aspect, in which case we need
     * a view advise.
     */

    if (DVASPECT_ICON & m_fe.dwAspect)
        {
        DWORD           dw=DVASPECT_CONTENT;
        IAdviseSink    *pSink;

        pSink=(NULL==dwData) ? NULL : m_pImpIAdviseSink;

        INOLE_SwitchDisplayAspect(m_pIOleObject, &dw
            , DVASPECT_ICON, (HGLOBAL)(UINT)dwData, FALSE
            , (NULL!=dwData), pSink, NULL);
        }

    return TRUE;
    }

/*
 * CTenant::Open
 *
 * Purpose:
 *  Retrieves the IStorage associated with this tenant.  The
 *  IStorage is owned by the tenant and thus the tenant always
 *  holds a reference count.
 *
 *  If the storage is already open for this tenant, then this
 *  function will AddRef it; therefore the caller must always
 *  match an Open with a Close.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE above this tenant (which has its
 *                  own storage).
 *
 * Return Value:
 *  BOOL            TRUE if opening succeeds, FALSE otherwise.
 */

BOOL CTenant::Open(LPSTORAGE pIStorage)
    {
    HRESULT     hr=NOERROR;
    DWORD       dwMode=STGM_TRANSACTED | STGM_READWRITE
                    | STGM_SHARE_EXCLUSIVE;
    OLECHAR     szTemp[32];

    if (NULL==m_pIStorage)
        {
        if (NULL==pIStorage)
            return FALSE;

        /*
         * Attempt to open the storage under this ID.  If there is
         * none, then create it.  In either case we end up with an
         * IStorage that we either save in pPage or release.
         */

        GetStorageName(szTemp);
        hr=pIStorage->OpenStorage(szTemp, NULL, dwMode, NULL, 0
            , &m_pIStorage);

        if (FAILED(hr))
            {
            hr=pIStorage->CreateStorage(szTemp, dwMode, 0, 0
                , &m_pIStorage);
            }
        }
    else
        m_pIStorage->AddRef();

    if (FAILED(hr))
        return FALSE;

    m_cOpens++;

    //Create these if we don't have them already.
    if (NULL==m_pImpIOleClientSite && NULL==m_pImpIAdviseSink)
        {
        m_pImpIOleClientSite=new CImpIOleClientSite(this, this);
        m_pImpIAdviseSink=new CImpIAdviseSink(this, this);

        if (NULL==m_pImpIOleClientSite || NULL==m_pImpIAdviseSink)
            return FALSE;
        }

    return TRUE;
    }

/*
 * CTenant::Close
 *
 * Purpose:
 *  Possibly commits the storage, then releases it reversing the
 *  reference count from Open.  If the reference on the storage
 *  goes to zero, the storage is forgotten.  However, the object we
 *  contain is still held and as long as it's active the storage
 *  remains alive.
 *
 * Parameters:
 *  fCommit         BOOL indicating if we're to commit.
 *
 * Return Value:
 *  None
 */

void CTenant::Close(BOOL fCommit)
    {
    if (fCommit)
        Update();

    if (NULL!=m_pIStorage)
        {
        m_pIStorage->Release();

        /*
         * We can't use a zero reference count to know when to NULL
         * this since other things might have AddRef'd the storage.
         */
        if (0==--m_cOpens)
            {
            m_pIStorage=NULL;

            //Close the object saving if necessary
            if (NULL!=m_pIOleObject)
                {
                m_pIOleObject->Close(OLECLOSE_SAVEIFDIRTY);
                ReleaseInterface(m_pIOleObject);
                }

            //Release all other held pointers
            if (NULL!=m_pIViewObject2)
                {
                m_pIViewObject2->SetAdvise(m_fe.dwAspect, 0, NULL);
                ReleaseInterface(m_pIViewObject2);
                }

            //We know we only hold one ref from Create or Load
            ReleaseInterface(m_pObj);
            }
        }

    return;
    }

/*
 * CTenant::Update
 *
 * Purpose:
 *  Forces a common on the page if it's open.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the object is open, FALSE otherwise.
 */

BOOL CTenant::Update(void)
    {
    LPPERSISTSTORAGE    pIPS;

    if (NULL!=m_pIStorage)
        {
        /*
         * We need to OleSave again because we might have changed
         * the size or position of this tenant.  We also need to
         * save the rectangle on the page, since that's not known
         * to OLE.
         */
        m_pObj->QueryInterface(IID_IPersistStorage, (PPVOID)&pIPS);

        //This fails for static objects...so we improvise if so
        if (FAILED(OleSave(pIPS, m_pIStorage, TRUE)))
            {
            //This is essentially what OleSave does.
            WriteClassStg(m_pIStorage, m_clsID);
            pIPS->Save(m_pIStorage, TRUE);
            }

        pIPS->SaveCompleted(NULL);
        pIPS->Release();

        m_pIStorage->Commit(STGC_DEFAULT);
        }

    return FALSE;
    }

/*
 * CTenant::Destroy
 *
 * Purpose:
 *  Removes this page from the given storage.  The caller should
 *  eventually delete this CTenant object to free the object herein.
 *  Nothing is committed when being destroyed.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE contianing this page on which to call
 *                  DestroyElement
 *
 * Return Value:
 *  None
 */

void CTenant::Destroy(LPSTORAGE pIStorage)
    {
    OLECHAR     szTemp[32];

    if (NULL!=pIStorage)
        {
        if (NULL!=m_pIOleObject)
            m_pIOleObject->Close(OLECLOSE_NOSAVE);

        if (NULL!=m_pIStorage)
            {
            //Remove all reference/open counts on this storage.
            while (0!=m_cOpens)
                {
                m_pIStorage->Release();
                m_cOpens--;
                }
            }

        GetStorageName(szTemp);
        pIStorage->DestroyElement(szTemp);

        m_pIStorage=NULL;
        }

    return;
    }

/*
 * CTenant::Select
 *
 * Purpose:
 *  Selects or deselects the tenant.
 *
 * Parameters:
 *  fSelect         BOOL indicating the new state of the tenant.
 *
 * Return Value:
 *  None
 */

void CTenant::Select(BOOL fSelect)
    {
    BOOL        fWasSelected;
    DWORD       dwState;
    RECT        rc;
    HDC         hDC;

    fWasSelected=(BOOL)(TENANTSTATE_SELECTED & m_dwState);

    //Nothing to do when there's no change.
    if (fWasSelected==fSelect)
        return;

    dwState=m_dwState & ~TENANTSTATE_SELECTED;
    m_dwState=dwState | ((fSelect) ? TENANTSTATE_SELECTED : 0);

    /*
     * Draw sizing handles to show the selection state.  We convert
     * things to MM_TEXT since that's what this function expects.
     */

    RECTFROMRECTL(rc, m_rcl);
    RectConvertMappings(&rc, NULL, TRUE);
    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

    hDC=GetDC(m_hWnd);

    UIDrawHandles(&rc, hDC, UI_HANDLES_INSIDE
        | UI_HANDLES_NOBORDER | UI_HANDLES_USEINVERSE
        , CXYHANDLE, !fWasSelected);

    ReleaseDC(m_hWnd, hDC);

    if (fSelect)
        m_pPG->m_fDirty=TRUE;

    return;
    }

/*
 * CTenant::ShowAsOpen
 *
 * Purpose:
 *  Draws or removes the hatch pattern over an object.
 *
 * Parameters:
 *  fOpen           BOOL indicating the open state of this tenant.
 *
 * Return Value:
 *  None
 */

void CTenant::ShowAsOpen(BOOL fOpen)
    {
    BOOL        fWasOpen;
    DWORD       dwState;
    RECT        rc;
    HDC         hDC;

    fWasOpen=(BOOL)(TENANTSTATE_OPEN & m_dwState);

    dwState=m_dwState & ~TENANTSTATE_OPEN;
    m_dwState=dwState | ((fOpen) ? TENANTSTATE_OPEN : 0);

    //If this was not open, then just hatch, otherwise repaint.
    if (!fWasOpen && fOpen)
        {
        RECTFROMRECTL(rc, m_rcl);
        RectConvertMappings(&rc, NULL, TRUE);
        OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

        hDC=GetDC(m_hWnd);
        UIDrawShading(&rc, hDC, UI_SHADE_FULLRECT, 0);
        ReleaseDC(m_hWnd, hDC);
        }

    if (fWasOpen && !fOpen)
        Repaint();

    return;
    }

/*
 * CTenant::ShowYourself
 *
 * Purpose:
 *  Function that really just implements IOleClientSite::ShowObject.
 *  Here we first check if the tenant is fully visible, and if so,
 *  then nothing needs to happen.  Otherwise, if the upper left
 *  corner of the tenant is in the upper left visible quadrant of
 *  the window, we'll also consider ourselves done.  Otherwise
 *  we'll put the upper left corner of the object at the upper left
 *  corner of the window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CTenant::ShowYourself(void)
    {
    RECTL       rcl;
    RECT        rc;
    POINT       pt1, pt2;

    //Scrolling deals in device units; get our rectangle in those.
    RectGet(&rcl, TRUE);

    //Get the window rectangle offset for the current scroll pos.
    GetClientRect(m_hWnd, &rc);
    OffsetRect(&rc, m_pPG->m_xPos, m_pPG->m_yPos);

    //Check if the object is already visible. (macro in bookguid.h)
    SETPOINT(pt1, (int)rcl.left,  (int)rcl.top);
    SETPOINT(pt2, (int)rcl.right, (int)rcl.bottom);

    if (PtInRect(&rc, pt1) && PtInRect(&rc, pt2))
        return;

    //Check if the upper left is within the upper left quadrant
    if (((int)rcl.left > rc.left
        && (int)rcl.left < ((rc.right+rc.left)/2))
        && ((int)rcl.top > rc.top
        && (int)rcl.top < ((rc.bottom+rc.top)/2)))
        return;

    //These are macros in INC\BOOK1632.H
    SendScrollPosition(m_hWnd, WM_HSCROLL, rcl.left-8);
    SendScrollPosition(m_hWnd, WM_VSCROLL, rcl.top-8);
    return;
    }

/*
 * CTenant::AddVerbMenu
 *
 * Purpose:
 *  Creates the variable verb menu item for the object in this
 *  tenant.
 *
 * Parmeters:
 *  hMenu           HMENU on which to add items.
 *  iPos            UINT position on that menu to add items.
 *
 * Return Value:
 *  None
 */

void CTenant::AddVerbMenu(HMENU hMenu, UINT iPos)
    {
    HMENU       hMenuTemp;
    LPOLEOBJECT pObj=m_pIOleObject;

    //If we're static, say we have no object.
    if (TENANTTYPE_STATIC==m_tType)
        pObj=NULL;

    OleUIAddVerbMenu(pObj, NULL, hMenu, iPos, IDM_VERBMIN
        , IDM_VERBMAX, TRUE, IDM_EDITCONVERT, &hMenuTemp);

    return;
    }

/*
 * CTenant::TypeGet
 *
 * Purpose:
 *  Returns the type of this tenant
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  TENANTTYPE      Type of the tenant.
 */

TENANTTYPE CTenant::TypeGet(void)
    {
    return m_tType;
    }

/*
 * CTenant::CopyEmbeddedObject
 *
 * Purpose:
 *  Copies an embedded object to the given data object (via SetData,
 *  assuming this is a data transfer object for clipboard/drag-drop)
 *  if that's what we're holding.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT in which to store the copy.
 *  pFE             LPFORMATETC into which to copy CFSTR_EMBEDDEDOBJECT
 *                  if we put that in the data object.
 *  pptl            PPOINTL to the pick point (NULL outside of
 *                  drag-drop);
 *
 * Return Value:
 *  None
 */

void CTenant::CopyEmbeddedObject(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, PPOINTL pptl)
    {
    LPPERSISTSTORAGE    pIPS;
    STGMEDIUM           stm;
    FORMATETC           fe;
    HRESULT             hr;
    UINT                cf;
    POINTL              ptl;
    SIZEL               szl;

    //Can only copy embeddings.
    if (TENANTTYPE_EMBEDDEDOBJECT!=m_tType || NULL==m_pIOleObject)
        return;

    if (NULL==pptl)
        {
        SETPOINTL(ptl, 0, 0);
        pptl=&ptl;
        }

    /*
     * Create CFSTR_EMBEDDEDOBJECT.  This is simply an IStorage with
     * a copy of the embedded object in it.  The not-so-simple part

* is getting an IStorage to stuff it in.  For this operation
     * we'll use a temporary compound file.
     */

    stm.pUnkForRelease=NULL;
    stm.tymed=TYMED_ISTORAGE;
    hr=StgCreateDocfile(NULL, STGM_TRANSACTED | STGM_READWRITE
        | STGM_CREATE| STGM_SHARE_EXCLUSIVE | STGM_DELETEONRELEASE
        , 0, &stm.pstg);

    if (FAILED(hr))
        return;

    m_pObj->QueryInterface(IID_IPersistStorage, (PPVOID)&pIPS);

    if (NOERROR==pIPS->IsDirty())
        {
        OleSave(pIPS, stm.pstg, FALSE);
        pIPS->SaveCompleted(NULL);
        }
    else
        m_pIStorage->CopyTo(0, NULL, NULL, stm.pstg);

    pIPS->Release();

    //stm.pstg now has a copy, so stuff it away.
    cf=RegisterClipboardFormat(CFSTR_EMBEDDEDOBJECT);
    SETDefFormatEtc(fe, cf, TYMED_ISTORAGE);

    if (SUCCEEDED(pIDataObject->SetData(&fe, &stm, TRUE)))
        *pFE=fe;
    else
        ReleaseStgMedium(&stm);

    stm.tymed=TYMED_HGLOBAL;

    /*
     * You want to make sure that if this object is iconic, that you
     * create the object descriptor with DVASPECT_ICON instead of
     * the more typical DVASPECT_CONTENT.  Also remember that
     * the pick point is in HIMETRIC.
     */
    XformSizeInPixelsToHimetric(NULL, (LPSIZEL)pptl, (LPSIZEL)&ptl);

    SETSIZEL(szl, (10*(m_rcl.right-m_rcl.left))
        , (10 * (m_rcl.bottom-m_rcl.top)));

    stm.hGlobal=INOLE_ObjectDescriptorFromOleObject
        (m_pIOleObject, m_fe.dwAspect, ptl, &szl);

    cf=RegisterClipboardFormat(CFSTR_OBJECTDESCRIPTOR);
    SETDefFormatEtc(fe, cf, TYMED_HGLOBAL);

    if (FAILED(pIDataObject->SetData(&fe, &stm, TRUE)))
        ReleaseStgMedium(&stm);

    return;
    }

//CHAPTER20MOD
/*
 * CTenant::ShowObjectType
 *
 * Purpose:
 *  Tells the object to switch on or off an indication of whether
 *  it is linked or embedded.
 *
 * Parameters:
 *  fShow           BOOL indicating to show the type (TRUE) or
 *                  not (FALSE)
 *
 * Return Value:
 *  None
 */

void CTenant::ShowObjectType(BOOL fShow)
    {
    BOOL        fWasShow;
    DWORD       dwState;
    RECT        rc;
    HDC         hDC;

    fWasShow=(BOOL)(TENANTSTATE_SHOWTYPE & m_dwState);

    dwState=m_dwState & ~TENANTSTATE_SHOWTYPE;
    m_dwState=dwState | ((fShow) ? TENANTSTATE_SHOWTYPE : 0);

    /*
     * If this wasn't previously shown, just add the line,
     * otherwise repaint.
     */
    if (!fWasShow && fShow)
        {
        RECTFROMRECTL(rc, m_rcl);
        RectConvertMappings(&rc, NULL, TRUE);
        OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

        hDC=GetDC(m_hWnd);
        UIShowObject(&rc, hDC, (TENANTTYPE_LINKEDOBJECT==m_tType));
        ReleaseDC(m_hWnd, hDC);
        }

    if (fWasShow && !fShow)
        Repaint();

    return;
    }

/*
 * CTenant::NotifyOfRename
 *
 * Purpose:
 *  Instructs the tenant that the document was saved under a
 *  different name.  In order to keep the right compound document
 *  user interface, this tenant needs to tell its object through
 *  IOleObject::SetHostNames.
 *
 * Parameters:
 *  pszFile         LPTSTR of filename.
 *  pmk             LPMONIKER of the new filename.
 *
 * Return Value:
 *  None
 */

void CTenant::NotifyOfRename(LPTSTR pszFile, LPMONIKER pmk)
    {
    TCHAR       szObj[40];
    TCHAR       szApp[40];

    if (NULL==m_pIOleObject)
        return;

    if (TEXT('\0')==*pszFile)
        {
        LoadString(m_pPG->m_hInst, IDS_UNTITLED, szObj
            , sizeof(szObj));
        }
    else
        {
        GetFileTitle(pszFile, szObj, sizeof(szObj));

       #ifndef WIN32
        //Force filenames to uppercase in DOS versions.
        AnsiUpper(szObj);
       #endif
        }

    LoadString(m_pPG->m_hInst, IDS_CAPTION, szApp, sizeof(szApp));
   #ifdef WIN32ANSI
    OLECHAR     szObjW[40], szAppW[40];

    MultiByteToWideChar(CP_ACP, 0, szObj, -1, szObjW, 40);
    MultiByteToWideChar(CP_ACP, 0, szApp, -1, szAppW, 40);
    m_pIOleObject->SetHostNames(szAppW, szObjW);
   #else
    m_pIOleObject->SetHostNames(szApp, szObj);
   #endif

    if (NULL!=pmk)
        {
        if (NULL!=m_pmkFile)
            m_pmkFile->Release();

        m_pmkFile=pmk;
        m_pmkFile->AddRef();

        m_pIOleObject->SetMoniker(OLEWHICHMK_CONTAINER, pmk);
        }

    return;
    }
//End CHAPTER20MOD

/*
 * CTenant::Activate
 *
 * Purpose:
 *  Activates a verb on the object living in the tenant.  Does
 *  nothing for static objects.
 *
 * Parameters:
 *  iVerb           LONG of the verb to execute.
 *
 * Return Value:
 *  BOOL            TRUE if the object changed due to this verb
 *                  execution.
 */

BOOL CTenant::Activate(LONG iVerb)
    {
    RECT        rc, rcH;
    CHourglass *pHour;
    SIZEL       szl;

    //Can't activate statics.
    if (TENANTTYPE_STATIC==m_tType || NULL==m_pIOleObject)
        {
        MessageBeep(0);
        return FALSE;
        }

    RECTFROMRECTL(rc, m_rcl);
    RectConvertMappings(&rc, NULL, TRUE);
    XformRectInPixelsToHimetric(NULL, &rc, &rcH);

    pHour=new CHourglass;

    //Get the server running first, then do a SetExtent, then show it
    OleRun(m_pIOleObject);

    if (m_fSetExtent)
        {
        SETSIZEL(szl, rcH.right-rcH.left, rcH.top-rcH.bottom);
        m_pIOleObject->SetExtent(m_fe.dwAspect, &szl);
        m_fSetExtent=FALSE;
        }

    m_pIOleObject->DoVerb(iVerb, NULL, m_pImpIOleClientSite, 0
        , m_hWnd, &rcH);

    delete pHour;

    //If object changes, IAdviseSink::OnViewChange will see it.
    return FALSE;
    }

/*
 * CTenant::Draw
 *
 * Purpose:
 *  Draws the tenant in its rectangle on the given hDC.  We assume
 *  the DC is already set up for the mapping mode in which our
 *  rectangle is expressed, since the Page we're in tells us both
 *  the rect and the hDC.
 *
 * Parameters:
 *  hDC             HDC in which to draw.  Could be a metafile,
 *                  memory DC, screen, or printer.
 *  ptd             DVTARGETDEVICE * describing the device.
 *  hIC             HDC holding an information context (printing).
 *  xOff, yOff      int offsets for the page in lometric
 *  fNoColor        BOOL indicating if we should do B & W
 *  fPrinter        BOOL indicating if we should render for a
 *                  printer.
 *
 * Return Value:
 *  None
 */

void CTenant::Draw(HDC hDC, DVTARGETDEVICE *ptd, HDC hIC
    , int xOff, int yOff, BOOL fNoColor, BOOL fPrinter)
    {
    HRESULT         hr;
    RECT            rc;
    RECTL           rcl;
    UINT            uMM;

    RECTFROMRECTL(rc, m_rcl);
    OffsetRect(&rc, -xOff, -yOff);
    RECTLFROMRECT(rcl, rc);

    //Repaint erases the rectangle to insure full object cleanup
    if (!fNoColor && !fPrinter)
        {
        COLORREF    cr;
        cr=SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
        ExtTextOut(hDC, rc.left, rc.top, ETO_OPAQUE, &rc, NULL
            , 0, NULL);
        SetBkColor(hDC, cr);
        }

    //We have to use Draw since we have a target device and IC.
    hr=m_pIViewObject2->Draw(m_fe.dwAspect, -1, NULL, ptd, hIC, hDC
        , &rcl, NULL, NULL, 0);

    /*
     * If Draw failed, then perhaps it couldn't work for the device,
     * so try good old OleDraw as a last resort.  The code will
     * generally be OLE_E_BLANK.
     */
    if (FAILED(hr))
        OleDraw(m_pObj, m_fe.dwAspect, hDC, &rc);

    //CHAPTER20MOD
    if (!fPrinter)
    //End CHAPTER20MOD
        {
        /*
         * Draw sizing handles to show the selection state.  We
         * convert things to MM_TEXT since that's what this
         * function expects.
         */
        RectConvertMappings(&rc, NULL, TRUE);
        uMM=SetMapMode(hDC, MM_TEXT);

        if (TENANTSTATE_SELECTED & m_dwState)
            {
            UIDrawHandles(&rc, hDC, UI_HANDLES_INSIDE
                | UI_HANDLES_NOBORDER | UI_HANDLES_USEINVERSE
                , CXYHANDLE, TRUE);
            }

        if (TENANTSTATE_OPEN & m_dwState)
            UIDrawShading(&rc, hDC, UI_SHADE_FULLRECT, 0);

        //CHAPTER20MOD
        //Distinguish linked and embedded objects.
        if (TENANTSTATE_SHOWTYPE & m_dwState)
            {
            UIShowObject(&rc, hDC
                , (TENANTTYPE_LINKEDOBJECT==m_tType));
            }
        //End CHAPTER20MOD

        uMM=SetMapMode(hDC, uMM);
        }

    return;
    }

/*
 * CTenant::Repaint
 * CTenant::Invalidate
 *
 * Purpose:
 *  Repaints the tenant where it lies or invalidates its area
 *  for later repainting.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CTenant::Repaint(void)
    {
    RECT        rc;
    HDC         hDC;

    /*
     * We might be asked to repaint from
     * IOleClientSite::OnShowWindow after we've switched pages if
     * our server was running. This check on m_cOpens prevents that.
     */
    if (0==m_cOpens || !m_fRepaintEnabled)
        return;

    hDC=GetDC(m_hWnd);
    SetRect(&rc, m_pPG->m_xPos, m_pPG->m_yPos, 0, 0);
    RectConvertMappings(&rc, NULL, FALSE);

    SetMapMode(hDC, MM_LOMETRIC);
    Draw(hDC, NULL, NULL, rc.left, rc.top, FALSE, FALSE);

    ReleaseDC(m_hWnd, hDC);
    return;
    }

void CTenant::Invalidate(void)
    {
    RECTL       rcl;
    RECT        rc;

    RectGet(&rcl, TRUE);
    RECTFROMRECTL(rc, rcl);

    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);
    InvalidateRect(m_hWnd, &rc, FALSE);

    return;
    }

//CHAPTER20MOD
/*
 * CTenant::FIsSelected
 *
 * Purpose:
 *  Returns the selection state of this tenant.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if selected, FALSE otherwise.
 */

BOOL CTenant::FIsSelected(void)
    {
    return (BOOL)(m_dwState & TENANTSTATE_SELECTED);
    }

/*
 * CTenant::ConvertToStatic
 *
 * Purpose:
 *  Changes the object that lives in this tenant to a static one.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CTenant::ConvertToStatic(void)
    {
    /*
     * If you SetSourceMoniker in IOleLink to NULL, then the link is
     * gone as far as OLE is concerned.  You only need to make sure
     * the user doesn't have access to other functionality for this
     * object, which we insure by changing our internal type.  We
     * set this on loading if GetSourceMoniker returns NULL.
     */
    m_tType=TENANTTYPE_STATIC;
    return TRUE;
    }

//End CHAPTER20MOD

/*
 * CTenant::ObjectClassFormatAndIcon
 *
 * Purpose:
 *  Helper function for CPage::ConvertObject to retrieve necessary
 *  information about the object.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store the CLSID.
 *  pwFormat        LPWORD in which to store the clipboard format
 *                  used.
 *  ppszType        LPTSTR * in which to return a pointer to a
 *                  type string.
 *  phMetaIcon      HGLOBAL * in which to return the metafile
 *                  icon currently in use.
 *
 * Return Value:
 *  None
 */

void CTenant::ObjectClassFormatAndIcon(LPCLSID pClsID
    , LPWORD pwFormat, LPTSTR *ppszType, HGLOBAL *phMetaIcon
    , LPTSTR *ppszLabel)
    {
    HRESULT         hr;
    TCHAR           szType[128];
    LPDATAOBJECT    pIDataObject;
    FORMATETC       fe;
    STGMEDIUM       stm;

    if (TENANTTYPE_EMBEDDEDOBJECT!=m_tType || NULL==m_pIOleObject)
        return;

    if (NULL==pClsID || NULL==pwFormat || NULL==ppszType
        || NULL==phMetaIcon)
        return;

    /*
     * For embedded objects get the real CLSID of the object and
     * its format string.  If this fails then we can try to ask
     * the object, or we can look in the registry.
     */

    hr=ReadClassStg(m_pIStorage, pClsID);

    if (FAILED(hr))
        {
        hr=m_pIOleObject->GetUserClassID(pClsID);

        if (FAILED(hr))
            *pClsID=CLSID_NULL;
        }

    hr=ReadFmtUserTypeStg(m_pIStorage, pwFormat, ppszType);

    if (FAILED(hr))
        {
        *pwFormat=0;
        *ppszType=NULL;

        if (INOLE_GetUserTypeOfClass(*pClsID, 0, szType
            , sizeof(szType)))
            {
            *ppszType=INOLE_CopyString(szType);
            }
        }

    /*
     * Try to get the AuxUserType from the registry, using
     * the short version (registered under AuxUserType\2).
     * If that fails, just copy *ppszType.
     */
    *ppszLabel=NULL;

    if (INOLE_GetUserTypeOfClass(*pClsID, 2, szType
        , sizeof(szType)))
        {
        *ppszLabel=INOLE_CopyString(szType);
        }
    else
        *ppszLabel=INOLE_CopyString(*ppszType);

    //Get the icon for this thing, if we're iconic.
    *phMetaIcon=NULL;

    hr=m_pObj->QueryInterface(IID_IDataObject
        , (PPVOID)&pIDataObject);

    if (SUCCEEDED(hr))
        {
        SETFormatEtc(fe, CF_METAFILEPICT, DVASPECT_ICON, NULL
            , TYMED_MFPICT, -1);
        hr=pIDataObject->GetData(&fe, &stm);
        pIDataObject->Release();

        if (SUCCEEDED(hr))
            *phMetaIcon=stm.hGlobal;
        else
            *phMetaIcon=OleGetIconOfClass(*pClsID, NULL, TRUE);
        }

    return;
    }

/*
 * CTenant::SwitchOrUpdateAspect
 *
 * Purpose:
 *  Switches between DVASPECT_CONTENT and DVASPECT_ICON
 *
 * Parameters:
 *  hMetaIcon       HGLOBAL to the new icon if we're changing the
 *                  icon or switching to DVASPECT_ICON.  NULL to
 *                  change back to content.
 *  fPreserve       BOOL indicating if we're to preserve the old
 *                  aspect after changing.
 *
 * Return Value:
 *  BOOL            TRUE if anything changed, FALSE otherwise.
 */

BOOL CTenant::SwitchOrUpdateAspect(HGLOBAL hMetaIcon
    , BOOL fPreserve)
    {
    HRESULT     hr;
    DWORD       dwAspect;
    BOOL        fUpdate=FALSE;

    //Nothing to do if we're content already and there's no icon.
    if (NULL==hMetaIcon && DVASPECT_CONTENT==m_fe.dwAspect)
        return FALSE;

    //If we're iconic already, just cache the new icon
    if (NULL!=hMetaIcon && DVASPECT_ICON==m_fe.dwAspect)
        hr=INOLE_SetIconInCache(m_pIOleObject, hMetaIcon);
    else
        {
        //Otherwise, switch between iconic and content.
        dwAspect=(NULL==hMetaIcon) ? DVASPECT_CONTENT : DVASPECT_ICON;

        /*
         * Switch between aspects, where dwAspect has the new one
         * and m_fe.dwAspect will be changed in the process.
         */
        hr=INOLE_SwitchDisplayAspect(m_pIOleObject
            , &m_fe.dwAspect, dwAspect, hMetaIcon, !fPreserve
            , TRUE, m_pImpIAdviseSink, &fUpdate);

        if (SUCCEEDED(hr))
            {
            //Update MiscStatus for the new aspect
            m_pIOleObject->GetMiscStatus(m_fe.dwAspect, &m_grfMisc);

            if (fUpdate)
                m_pIOleObject->Update();    //This repaints.
            }
        }

    //If we switched, update our extents.
    if (SUCCEEDED(hr))
        {
        SIZEL       szl;

        m_pIOleObject->GetExtent(m_fe.dwAspect, &szl);

        if (0 > szl.cy)
            szl.cy=-szl.cy;

        //Convert HIMETRIC absolute units to our LOMETRIC mapping
        if (0!=szl.cx && 0!=szl.cy)
            SETSIZEL(szl, szl.cx/10, -szl.cy/10);

        Invalidate();                   //Remove old aspect
        SizeSet(&szl, FALSE, FALSE);    //Change size
        Repaint();                      //Paint the new one
        }

    return SUCCEEDED(hr);
    }

/*
 * CTenant::EnableRepaint
 *
 * Purpose:
 *  Toggles whether the Repaint function does anything.  This
 *  is used during conversion/emulation of an object to disable
 *  repaints until the new object can be given the proper extents.
 *
 * Parameters:
 *  fEnable         TRUE to enable repaints, FALSE to disable.
 *
 * Return Value:
 *  None
 */

void CTenant::EnableRepaint(BOOL fEnable)
    {
    m_fRepaintEnabled=fEnable;
    return;
    }

/*
 * CTenant::ObjectGet
 *
 * Purpose:
 *  Retrieves the LPUNKNOWN of the object in use by this tenant
 *
 * Parameters:
 *  ppUnk           LPUNKNOWN * in which to return the object
 *                  pointer.
 *
 * Return Value:
 *  None
 */

void CTenant::ObjectGet(LPUNKNOWN *ppUnk)
    {
    if (NULL!=ppUnk)
        {
        *ppUnk=m_pObj;
        m_pObj->AddRef();
        }

    return;
    }

/*
 * CTenant::FormatEtcGet
 *
 * Purpose:
 *  Retrieves the FORMATETC in use by this tenant
 *
 * Parameters:
 *  pFE             LPFORMATETC in which to store the information.
 *  fPresentation   BOOL indicating if we want the real format or
 *                  that of the presentation.
 *
 * Return Value:
 *  None
 */

void CTenant::FormatEtcGet(LPFORMATETC pFE, BOOL fPresentation)
    {
    if (NULL!=pFE)
        {
        *pFE=m_fe;

        //If there is no format, use metafile (for embedded objects)
        if (fPresentation || 0==pFE->cfFormat)
            {
            //Don't mess with dwAspect; might be icon or content.
            pFE->cfFormat=CF_METAFILEPICT;
            pFE->tymed=TYMED_MFPICT;
            }
        }

    return;
    }

/*
 * CTenant::SizeGet
 * CTenant::SizeSet
 * CTenant::RectGet
 * CTenant::RectSet
 *
 * Purpose:
 *  Returns or sets the size/position of the object contained here.
 *
 * Parameters:
 *  pszl/prcl       LPSIZEL (Size) or LPRECTL (Rect) with the
 *                  extents of interest.  In Get situations,
 *                  this will receive the extents; in Set it
 *                  contains the extents.
 *  fDevice         BOOL indicating that pszl/prcl is expressed
 *                  in device units.  Otherwise it's LOMETRIC.
 *  fInformObj      (Set Only) BOOL indicating if we need to inform
 *                  the object all.
 *
 * Return Value:
 *  None
 */

void CTenant::SizeGet(LPSIZEL pszl, BOOL fDevice)
    {
    if (!fDevice)
        {
        pszl->cx=m_rcl.right-m_rcl.left;
        pszl->cy=m_rcl.bottom-m_rcl.top;
        }
    else
        {
        RECT        rc;

        SetRect(&rc, (int)(m_rcl.right-m_rcl.left)
            , (int)(m_rcl.bottom-m_rcl.top), 0, 0);

        RectConvertMappings(&rc, NULL, TRUE);

        pszl->cx=(long)rc.left;
        pszl->cy=(long)rc.top;
        }

    return;
    }

void CTenant::SizeSet(LPSIZEL pszl, BOOL fDevice, BOOL fInformObj)
    {
    SIZEL           szl;

    if (!fDevice)
        {
        szl=*pszl;
        m_rcl.right =pszl->cx+m_rcl.left;
        m_rcl.bottom=pszl->cy+m_rcl.top;
        }
    else
        {
        RECT        rc;

        SetRect(&rc, (int)pszl->cx, (int)pszl->cy, 0, 0);
        RectConvertMappings(&rc, NULL, FALSE);

        m_rcl.right =(long)rc.left+m_rcl.left;
        m_rcl.bottom=(long)rc.top+m_rcl.top;

        SETSIZEL(szl, (long)rc.left, (long)rc.top);
        }

    //Tell OLE that this object was resized.
    if (NULL!=m_pIOleObject && fInformObj)
        {
        HRESULT     hr;
        BOOL        fRun=FALSE;

        //Convert our LOMETRIC into HIMETRIC by *=10
        szl.cx*=10;
        szl.cy*=-10;    //Our size is stored negative.

        /*
         * If the MiscStatus bit of OLEMISC_RECOMPOSEONRESIZE
         * is set, then we need to run the object before calling
         * SetExtent to make sure it has a real chance to
         * re-render the object.  We have to update and close
         * the object as well after this happens.
         */

        if (OLEMISC_RECOMPOSEONRESIZE & m_grfMisc)
            {
            if (!OleIsRunning(m_pIOleObject))
                {
                OleRun(m_pIOleObject);
                fRun=TRUE;
                }
            }

        hr=m_pIOleObject->SetExtent(m_fe.dwAspect, &szl);

        /*
         * If the object is not running and it does not have
         * RECOMPOSEONRESIZE, then SetExtent fails.  Make
         * sure that we call SetExtent again (by just calling
         * SizeSet here again) when we next run the object.
         */
        if (SUCCEEDED(hr))
            {
            m_fSetExtent=FALSE;

            if (fRun)
                {
                m_pIOleObject->Update();
                m_pIOleObject->Close(OLECLOSE_SAVEIFDIRTY);
                }
            }
        else
            {
            if (OLE_E_NOTRUNNING==GetScode(hr))
                m_fSetExtent=TRUE;
            }
        }

    return;
    }

void CTenant::RectGet(LPRECTL prcl, BOOL fDevice)
    {
    if (!fDevice)
        *prcl=m_rcl;
    else
        {
        RECT        rc;

        RECTFROMRECTL(rc, m_rcl);
        RectConvertMappings(&rc, NULL, TRUE);
        RECTLFROMRECT(*prcl, rc);
        }

    return;
    }

void CTenant::RectSet(LPRECTL prcl, BOOL fDevice, BOOL fInformObj)
    {
    SIZEL   szl;
    LONG    cx, cy;

    cx=m_rcl.right-m_rcl.left;
    cy=m_rcl.bottom-m_rcl.top;

    if (!fDevice)
        m_rcl=*prcl;
    else
        {
        RECT        rc;

        RECTFROMRECTL(rc, *prcl);
        RectConvertMappings(&rc, NULL, FALSE);
        RECTLFROMRECT(m_rcl, rc);
        }

    /*
     * Tell ourselves that the size changed, if it did.  SizeSet
     * will call IOleObject::SetExtent for us.
     */
    if ((m_rcl.right-m_rcl.left)!=cx || (m_rcl.bottom-m_rcl.top)!=cy)
        {
        SETSIZEL(szl, m_rcl.right-m_rcl.left, m_rcl.bottom-m_rcl.top);
        SizeSet(&szl, FALSE, fInformObj);
        }

    return;
    }

/*
 * CTenant::CreateStatic
 * (Protected)
 *
 * Purpose:
 *  Creates a new static bitmap or metafile object for this tenant
 *  using a freeloading method allowing us to specify exactly which
 *  type of data we want to paste since OleCreateStaticFromData
 *  doesn't.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *  pFE             LPFORMATETC describing the format to paste.
 *  ppObj           LPUNKNOWN * into which we store the
 *                  object pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, error code otherwise.
 */

HRESULT CTenant::CreateStatic(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, LPUNKNOWN *ppObj)
    {
    HRESULT             hr;
    STGMEDIUM           stm;
    LPUNKNOWN           pIUnknown;
    LPOLECACHE          pIOleCache;
    LPPERSISTSTORAGE    pIPersistStorage;
    CLSID               clsID;

    *ppObj=NULL;

    //Try to get the data desired as specified in pFE->cfFormat
    hr=pIDataObject->GetData(pFE, &stm);

    if (FAILED(hr))
        return hr;

    //Create the object to handle this data.
    if (CF_METAFILEPICT==pFE->cfFormat)
        clsID=CLSID_Picture_Metafile;
    else
        clsID=CLSID_Picture_Dib;

    hr=CreateDataCache(NULL, clsID, IID_IUnknown
        , (PPVOID)&pIUnknown);

    if (FAILED(hr))
        {
        ReleaseStgMedium(&stm);
        return hr;
        }

    m_clsID=clsID;

    //Stuff the data into the object
    pIUnknown->QueryInterface(IID_IPersistStorage
        , (PPVOID)&pIPersistStorage);
    pIPersistStorage->InitNew(m_pIStorage);

    //Now that we have the cache object, shove the data into it.
    pIUnknown->QueryInterface(IID_IOleCache, (PPVOID)&pIOleCache);
    pIOleCache->Cache(pFE, ADVF_PRIMEFIRST, NULL);

    hr=pIOleCache->SetData(pFE, &stm, TRUE);
    pIOleCache->Release();

    //Insure there is a persistent copy on the disk
    WriteClassStg(m_pIStorage, m_clsID);
    pIPersistStorage->Save(m_pIStorage, TRUE);
    pIPersistStorage->SaveCompleted(NULL);
    pIPersistStorage->Release();

    //The cache owns this now.
    ReleaseStgMedium(&stm);

    if (FAILED(hr))
        pIUnknown->Release();
    else
        *ppObj=pIUnknown;

    return hr;
    }


TENANT.H

/*
 * TENANT.H
 * Patron Chapter 20
 *
 * Definitions and function prototypes for the CTenant class
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _TENANT_H_
#define _TENANT_H_

class CImpIOleClientSite : public IOleClientSite
    {
    protected:
        ULONG               m_cRef;
        class CTenant      *m_pTen;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIOleClientSite(class CTenant *, LPUNKNOWN);
        ~CImpIOleClientSite(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP SaveObject(void);
        STDMETHODIMP GetMoniker(DWORD, DWORD, LPMONIKER *);
        STDMETHODIMP GetContainer(LPOLECONTAINER *);
        STDMETHODIMP ShowObject(void);
        STDMETHODIMP OnShowWindow(BOOL);
        STDMETHODIMP RequestNewObjectLayout(void);
    };

typedef CImpIOleClientSite *PCImpIOleClientSite;

class CImpIAdviseSink : public IAdviseSink
    {
    protected:
        ULONG               m_cRef;
        class CTenant      *m_pTen;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIAdviseSink(class CTenant *, LPUNKNOWN);
        ~CImpIAdviseSink(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(void)  OnDataChange(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP_(void)  OnViewChange(DWORD, LONG);
        STDMETHODIMP_(void)  OnRename(LPMONIKER);
        STDMETHODIMP_(void)  OnSave(void);
        STDMETHODIMP_(void)  OnClose(void);
    };

typedef CImpIAdviseSink *PCImpIAdviseSink;

/*
 * Tenant class describing an individual piece of data in a page.
 * It knows where it sits, what object is inside of it, and what
 * its idenitifer is such that it can find it's storage within a
 * page.
 */

//Patron Objects clipboard format
typedef struct tagPATRONOBJECT
    {
    POINTL      ptl;        //Location of object
    POINTL      ptlPick;    //Pick point from drag-drop operation
    SIZEL       szl;        //Extents of object (absolute)
    FORMATETC   fe;         //Actual object format
    } PATRONOBJECT, *PPATRONOBJECT;

//Values for hit-testing, drawing, and resize-tracking tenants
#define CXYHANDLE       5

//Tenant types (not persistent, but determined at load time)
typedef enum
    {
    TENANTTYPE_NULL=0,
    TENANTTYPE_STATIC,
    TENANTTYPE_EMBEDDEDOBJECT,
    TENANTTYPE_EMBEDDEDFILE,
    TENANTTYPE_EMBEDDEDOBJECTFROMDATA,
    //CHAPTER20MOD
    TENANTTYPE_LINKEDOBJECT,
    TENANTTYPE_LINKEDFILE,
    TENANTTYPE_LINKEDOBJECTFROMDATA
    //End CHAPTER20MOD
    } TENANTTYPE, *PTENANTTYPE;

//State flags
#define TENANTSTATE_DEFAULT      0x00000000
#define TENANTSTATE_SELECTED     0x00000001
#define TENANTSTATE_OPEN         0x00000002
//CHAPTER20MOD
#define TENANTSTATE_SHOWTYPE     0x00000004
//End CHAPTER20MOD

/*
 * Persistent information we need to save for each tenant, which is
 * done in the tenant list instead of with each tenant.  Since this
 * is a small structure it's best not to blow another stream for it
 * (overhead).
 */
typedef struct tagTENANTINFO
    {
    DWORD       dwID;
    RECTL       rcl;
    FORMATETC   fe;             //Excludes ptd
    short       fSetExtent;     //Call IOleObject::SetExtent on Run
    } TENANTINFO, *PTENANTINFO;

class CTenant : public IUnknown
    {
    friend CImpIOleClientSite;
    friend CImpIAdviseSink;

    private:
        HWND            m_hWnd;         //Pages window
        DWORD           m_dwID;         //Persistent DWORD ID
        DWORD           m_cOpens;       //Count calls to Open

        BOOL            m_fInitialized; //Something here?
        LPUNKNOWN       m_pObj;         //The object here
        LPSTORAGE       m_pIStorage;    //Sub-storage for tenant

        FORMATETC       m_fe;           //Used to create the object
        DWORD           m_dwState;      //State flags
        RECTL           m_rcl;          //Space of this object
        CLSID           m_clsID;        //Object class (for statics)
        BOOL            m_fSetExtent;   //Call SetExtent on next run

        class CPages   *m_pPG;          //Pages window

        TENANTTYPE      m_tType;            //Type identifier
        ULONG           m_cRef;             //We're an object now
        LPOLEOBJECT     m_pIOleObject;      //IOleObject on m_pObj
        LPVIEWOBJECT2   m_pIViewObject2;    //IViewObject2 on m_pObj
        ULONG           m_grfMisc;          //OLEMISC flags
        BOOL            m_fRepaintEnabled;  //No redundant paints

        //Our interfaces
        PCImpIOleClientSite m_pImpIOleClientSite;
        PCImpIAdviseSink    m_pImpIAdviseSink;

        //CHAPTER20MOD
        LPMONIKER       m_pmkFile;  //For IOleClientSite::GetMoniker
        //End CHAPTER20MOD

    //CHAPTER20MOD
    /*
     * This flag is used exculsively by the implementation of
     * IOleUILinkContainer on the page we're in for the Links
     * Dialog. Since we never use it ourselves, public here
     * is no big deal.
     */
    public:
        BOOL            m_fLinkAvail;
    //End CHAPTER20MOD

    protected:
        BOOL    ObjectInitialize(LPUNKNOWN, LPFORMATETC, DWORD);
        HRESULT CreateStatic(LPDATAOBJECT, LPFORMATETC
            , LPUNKNOWN *);

    public:
        CTenant(DWORD, HWND, CPages *);
        ~CTenant(void);

        //Gotta have an IUnknown for delegation
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        DWORD       GetID(void);
        UINT        GetStorageName(LPOLESTR);
        void        StorageGet(LPSTORAGE *);
        UINT        Create(TENANTTYPE, LPVOID, LPFORMATETC, PPOINTL
                        , LPSIZEL, LPSTORAGE, PPATRONOBJECT, DWORD);
        BOOL        Load(LPSTORAGE, PTENANTINFO);
        void        GetInfo(PTENANTINFO);
        BOOL        Open(LPSTORAGE);
        void        Close(BOOL);
        BOOL        Update(void);
        void        Destroy(LPSTORAGE);

        void        Select(BOOL);
        void        ShowAsOpen(BOOL);
        void        ShowYourself(void);
        void        AddVerbMenu(HMENU, UINT);
        TENANTTYPE  TypeGet(void);
        void        CopyEmbeddedObject(LPDATAOBJECT, LPFORMATETC
                        , PPOINTL);
        //CHAPTER20MOD
        void        ShowObjectType(BOOL);
        void        NotifyOfRename(LPTSTR, LPMONIKER);
        //End CHAPTER20MOD

        BOOL        Activate(LONG);
        void        Draw(HDC, DVTARGETDEVICE *, HDC, int, int
                        , BOOL, BOOL);
        void        Repaint(void);
        void        Invalidate(void);

        void        ObjectClassFormatAndIcon(LPCLSID, LPWORD
                        , LPTSTR *, HGLOBAL *, LPTSTR *);
        BOOL        SwitchOrUpdateAspect(HGLOBAL, BOOL);
        void        EnableRepaint(BOOL);

        //CHAPTER20MOD
        BOOL        FIsSelected(void);
        BOOL        ConvertToStatic(void);
        //End CHAPTER20MOD

        void        ObjectGet(LPUNKNOWN *);
        void        FormatEtcGet(LPFORMATETC, BOOL);
        void        SizeGet(LPSIZEL, BOOL);
        void        SizeSet(LPSIZEL, BOOL, BOOL);
        void        RectGet(LPRECTL, BOOL);
        void        RectSet(LPRECTL, BOOL, BOOL);
    };

typedef CTenant *PCTenant;

//Return codes for Create
#define CREATE_FAILED               0
#define CREATE_GRAPHICONLY          1
#define CREATE_PLACEDOBJECT         2

#endif //_TENANT_H_


README.TXT

To build a version of Component Cosmo that uses POLY21.DLL, copy
the sources from CHAP13\COCOSMO and modify the function
CCosmoDoc::FInit in DOCUMENT.CPP to use CLSID_Polyline19
(it doesn't change from Chapter 21) instead of CLSID_Polyline10 in
the CoCreateInstance call:

Nothing else in CoCosmo needs to change to use the revised Polyline.
Note that CoCosmo still uses the IPolyline10 interface on the Polyline
object.  For all intents and purposes, nothing should change at all
in the execution of CoCosmo, demonstrating how interfaces do not
interfere with each other:  CoCosmo ignores the compound document
interfaces.


CLIENT.CPP

/*
 * CLIENT.CPP
 * Cosmo Chapter 21
 *
 * Implementation of the CCosmoClient class that just makes sure
 * we get a CCosmoDoc on doc creation and that we initialize fully.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CCosmoClient::CCosmoClient
 * CCosmoClient::~CCosmoClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame to the frame object.
 */

CCosmoClient::CCosmoClient(HINSTANCE hInst, PCFrame pFR)
    : CClient(hInst, pFR)
    {
    return;
    }

CCosmoClient::~CCosmoClient(void)
    {
    return;
    }

/*
 * CCosmoClient::CreateCDocument
 *
 * Purpose:
 *  Constructs a new document specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::CreateCDocument(void)
    {
    return (PCDocument)(new CCosmoDoc(m_hInst, m_pFR, m_pAdv));
    }

/*
 * CCosmoClient::NewDocument
 *
 * Purpose:
 *  Small override of the CClient::NewDocument that we have just
 *  to check the initial line selection on new document creation.
 *
 * Parameters:
 *  fVisible        BOOL indicating if the document is to be
 *                  visible or not.
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::NewDocument(BOOL fVisible)
    {
    PCDocument  pDoc;

    //Perform default NewDocument first
    pDoc=CClient::NewDocument(fVisible);

    //We know that m_pFR is actually a CCosmoFrame, so type is safe.
    ((PCCosmoFrame)m_pFR)->CheckLineSelection(IDM_LINESOLID);

    return pDoc;
    }


COSMO.CPP

/*
 * COSMO.CPP
 * Cosmo Chapter 21
 *
 * WinMain and CCosmoFrame implementations.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "cosmo.h"

/*
 * These are for proper implementation of the class factory,
 * OLE Documents support, and shutdown conditions.
 */
ULONG g_cObj=0;
ULONG g_cLock=0;
HWND  g_hWnd=NULL;
BOOL  g_fUser=FALSE;

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCCosmoFrame    pFR;
    FRAMEINIT       fi;
    WPARAM          wRet;

    SETMESSAGEQUEUE;

    //Attempt to allocate and initialize the application
    pFR=new CCosmoFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return -1;

    fi.idsMin=IDS_FRAMEMIN;
    fi.idsMax=IDS_FRAMEMAX;
    fi.idsStatMin=IDS_STATMESSAGEMIN;
    fi.idsStatMax=IDS_STATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=440;
    fi.cy=460;

    //If we can initialize pFR, start chugging messages
    if (pFR->Init(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Cosmo Figure object to call when it gets
 *  destroyed.  We destroy the main window if the proper conditions
 *  are met for shutdown.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;

    //No more objects, no locks, no user control, shut the app down.
    if (0L==g_cObj && 0L==g_cLock && IsWindow(g_hWnd) && !g_fUser)
        PostMessage(g_hWnd, WM_CLOSE, 0, 0L);

    return;
    }

/*
 * CCosmoFrame::CCosmoFrame
 * CCosmoFrame::~CCosmoFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WInMain
 */

CCosmoFrame::CCosmoFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    : CFrame(hInst, hInstPrev, pszCmdLine, nCmdShow)
    {
    UINT        i;

    for (i=0; i<5; i++)
        m_hBmpLines[i]=NULL;

    m_uIDCurLine=0;
    m_fInitialized=FALSE;
    m_pIClassDataTran=NULL;

    m_fEmbedding=FALSE;
    m_dwRegCO=0;
    m_pIClassFactory=NULL;

    return;
    }

CCosmoFrame::~CCosmoFrame(void)
    {
    UINT        i;

    //Reverse CoRegisterClassObject, takes class factory ref to 1
    if (0L!=m_dwRegCO)
        CoRevokeClassObject(m_dwRegCO);

    ReleaseInterface(m_pIClassFactory);

    for (i=0; i<5; i++)
        {
        if (NULL!=m_hBmpLines[i])
            DeleteObject(m_hBmpLines[i]);
        }

    if (NULL!=m_pIClassDataTran)
        {
        m_pIClassDataTran->LockServer(FALSE);
        m_pIClassDataTran->Release();
        }

    OleFlushClipboard();

    if (m_fInitialized)
        OleUninitialize();

    return;
    }

/*
 * CCosmoFrame::Init
 *
 * Purpose:
 *  Call CoInitialize then calling down into the base class
 *  initialization.
 *
 * Parameters:
 *  pFI             PFRAMEINIT containing initialization parameters.
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded, FALSE otherwise.
 */

BOOL CCosmoFrame::Init(PFRAMEINIT pFI)
    {
    HRESULT     hr;

    CHECKVER_OLE;

    if (FAILED(OleInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    hr=CoGetClassObject(CLSID_DataTransferObject
        , CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory
        , (PPVOID)&m_pIClassDataTran);

    if (SUCCEEDED(hr))
        m_pIClassDataTran->LockServer(TRUE);

    //Check for command line flags
    ParseCommandLine();

    if (NULL!=m_ppszCmdArgs)
        {
        if(0==lstrcmpi(m_ppszCmdArgs[0], TEXT("-Embedding"))
           || 0==lstrcmpi(m_ppszCmdArgs[0], TEXT("/Embedding")))
            m_fEmbedding=TRUE;
        }

    g_fUser=!m_fEmbedding;

    /*
     * Create our class factory and register it for this application
     * using CoRegisterClassObject.  The REGCLS_*USE flags have to
     * do with servicable object from this instance, not with MDI or
     * SDI.  Since it's most convenient to be single use, we'll do
     * this in either version.
     *
     * In addition, it only makes sense to do any of this if we're
     * being launched to be a server.
     */
    if (m_fEmbedding)
        {
        m_pIClassFactory=new CFigureClassFactory(this);

        if (NULL==m_pIClassFactory)
            return FALSE;

        //Since we hold on to this, we should AddRef it.
        m_pIClassFactory->AddRef();

        hr=CoRegisterClassObject(CLSID_CosmoFigure, m_pIClassFactory
            , CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE, &m_dwRegCO);

        if (FAILED(hr))
            return FALSE;
        }

    return CFrame::Init(pFI);
    }

/*
 * CCosmoFrame::CreateCClient
 *
 * Purpose:
 *  Constructs a new client specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient        Pointer to the new client object.
 */

PCClient CCosmoFrame::CreateCClient(void)
    {
    return (PCClient)(new CCosmoClient(m_hInst, this));
    }

/*
 * CCosmoFrame::RegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CCosmoFrame::RegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //First let the standard frame do its thing
    if (!CFrame::RegisterAllClasses())
        return FALSE;

    /*
     * We want a different background color for the document
     * because the Polyline we put in the document will paint
     * with COLOR_WINDOW which by default which is CLASSLIB's
     * default document color.
     */

    GetClassInfo(m_hInst, SZCLASSDOCUMENT, &wc);
    UnregisterClass(SZCLASSDOCUMENT, m_hInst);

    wc.hbrBackground=(HBRUSH)(COLOR_APPWORKSPACE+1);

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

    //Register the Polyline window.
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.hInstance     = m_hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PolylineWndProc;
    wc.cbWndExtra    = CBPOLYLINEWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_CROSS);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPOLYLINE;

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

    return TRUE;
    }

/*
 * CCosmoFrame::PreShowInit
 *
 * Purpose:
 *  Called from Init before intially showing the window.  We do
 *  whatever else we want here, modifying nCmdShow as necessary
 *  which affects ShowWindow in Init.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if this initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CCosmoFrame::PreShowInit(void)
    {
    CreateLineMenu();
    CheckLineSelection(IDM_LINESOLID);

    //Save the window handle for shutdown if necessary.
    g_hWnd=m_hWnd;

    //If we're under OLE control, don't show the main window.
    if (m_fEmbedding)
        m_nCmdShow=SW_HIDE;

    return TRUE;
    }

/*
 * CCosmoFrame::CreateLineMenu
 *
 * Purpose:
 *  Initializes the bitmaps used to create the Line menu and
 *  replaces the text items defined in the application resources
 *  with these bitmaps.  Note that the contents of m_hBmpLines
 *  must be cleaned up when the application terminates.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CreateLineMenu(void)
    {
    HMENU       hMenu;
    HDC         hDC, hMemDC;
    HPEN        hPen;
    HGDIOBJ     hObj;
    TEXTMETRIC  tm;
    UINT        i, cx, cy;

    hMenu=GetSubMenu(GetMenu(m_hWnd), 3);   //Line menu.
    hDC=GetDC(m_hWnd);

    //Create each line in a menu item 8 chars wide, one char high.
    GetTextMetrics(hDC, &tm);
    cx=tm.tmAveCharWidth*8;
    cy=tm.tmHeight;

    /*
     * Create a memory DC in which to draw lines, and bitmaps
     * for each line.
     */
    hMemDC=CreateCompatibleDC(hDC);
    ReleaseDC(m_hWnd, hDC);

    for (i=0; i<5; i++)
        {
        m_hBmpLines[i]=CreateCompatibleBitmap(hMemDC, cx, cy);
        SelectObject(hMemDC, m_hBmpLines[i]);

        PatBlt(hMemDC, 0, 0, cx, cy, WHITENESS);

        hPen=CreatePen(i, 1, 0L);       //i=line style like PS_SOLID
        hObj=SelectObject(hMemDC, hPen);

        MoveToEx(hMemDC, 0, cy/2, NULL);
        LineTo(hMemDC, cx, cy/2);

        ModifyMenu(hMenu, IDM_LINEMIN+i, MF_BYCOMMAND | MF_BITMAP
            , IDM_LINEMIN+i, (LPTSTR)(LONG)(UINT)m_hBmpLines[i]);

        SelectObject(hMemDC, hObj);
        DeleteObject(hPen);
        }

    CheckMenuItem(hMenu, IDM_LINESOLID, MF_CHECKED);
    DeleteDC(hMemDC);

    return;
    }

/*
 * CCosmoFrame::CreateToolbar
 *
 * Purpose:
 *  Procedure to create all the necessary toolbar buttons.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of tools added to the bar.
 */

UINT CCosmoFrame::CreateToolbar(void)
    {
    UINT            iLast;
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;
    UINT            utEx  =GIZMOTYPE_BUTTONATTRIBUTEEX;

    //Insert the standard ones.
    iLast=CFrame::CreateToolbar();

    /*
     * Insert File Import in the 5th position and account for
     * it in iLast.
     */
    m_pTB->Add(utCmd, 4, IDM_FILEIMPORT, m_dxB, m_dyB
        , NULL, m_hBmp, 2, uState);
    iLast++;

    //Separator
    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    /*
     * For the Background bitmap, preserve our use of black
     * (part of the image)
     */
    m_pTB->Add(utCmd, iLast++, IDM_COLORBACKGROUND, m_dxB, m_dyB
        , NULL, m_hBmp, 3, GIZMO_NORMAL | PRESERVE_BLACK);

    m_pTB->Add(utCmd, iLast++, IDM_COLORLINE, m_dxB, m_dyB
        , NULL, m_hBmp, 4, uState);

    //Separator
    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //Line styles.
    m_pTB->Add(utEx, iLast++, IDM_LINESOLID, m_dxB, m_dyB
        , NULL, m_hBmp, 5, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASH, m_dxB, m_dyB
        , NULL, m_hBmp, 6, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 7, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASHDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 8, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASHDOTDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 9, uState);

    return iLast;
    }

/*
 * CCosmoFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the Cosmo frame window that just
 *  processes the line menu and the color menu leaving the
 *  CFrame to do everything else.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CCosmoFrame::OnCommand(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PCCosmoDoc      pDoc;
    TCHAR           szFile[CCHPATHMAX];
    BOOL            fOK;
    UINT            i, uTemp;
    COLORREF        rgColors[16];
    CHOOSECOLOR     cc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many toolbar notifications.
     */
    if (m_fInit)
        return 0L;

    pDoc=(PCCosmoDoc)m_pCL->ActiveDocument();

    /*
     * Check for the line style commands which are
     * IDM_LINEMIN+<style>.  We handle this by changing the menu
     * and toolbar, then we pass it to the document for real
     * processing.
     */
    if (NULL!=pDoc && IDM_LINEMIN <= wID && IDM_LINEMAX >=wID)
        {
        CheckLineSelection(wID);
        pDoc->LineStyleSet(wID-IDM_LINEMIN);
        return 0L;
        }

    switch (wID)
        {
        case IDM_FILEIMPORT:
            szFile[0]=0;
            fOK=SaveOpenDialog(szFile, CCHPATHMAX, IDS_FILEIMPORT
                , TRUE, &i);

            if (fOK)
                {
                uTemp=pDoc->Load(FALSE, szFile);
                pDoc->ErrorMessage(uTemp);
                }

            return (LRESULT)fOK;

        case IDM_COLORBACKGROUND:
        case IDM_COLORLINE:
            //Invoke the color chooser for either color
            uTemp=(IDM_COLORBACKGROUND==wID)
                ? DOCCOLOR_BACKGROUND : DOCCOLOR_LINE;

            for (i=0; i<16; i++)
                rgColors[i]=RGB(0, 0, i*16);

            memset(&cc, 0, sizeof(CHOOSECOLOR));
            cc.lStructSize=sizeof(CHOOSECOLOR);
            cc.lpCustColors=rgColors;
            cc.hwndOwner=hWnd;
            cc.Flags=CC_RGBINIT;
            cc.rgbResult=pDoc->ColorGet(uTemp);

            if (ChooseColor(&cc))
                pDoc->ColorSet(uTemp, cc.rgbResult);

            break;

        default:
           CFrame::OnCommand(hWnd, wParam, lParam);
        }

    return 0L;
    }

/*
 * CCosmoFrame::OnDocumentDataChange
 *
 * Purpose:
 *  Update the Line menu and toolbar if the style in the data
 *  changes.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentDataChange(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::OnDocumentActivate
 *
 * Purpose:
 *  Informs us that document activation changed, so update the UI
 *  for that new document.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentActivate(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    PCDocument  pDoc;
    BOOL        fOK=FALSE;
    BOOL        fCallDefault=TRUE;
    UINT        i;
    UINT        uTemp;
    UINT        uTempE;
    UINT        uTempD;

    pDoc=m_pCL->ActiveDocument();

    uTempE=MF_ENABLED | MF_BYCOMMAND;
    uTempD=MF_DISABLED | MF_GRAYED | MF_BYCOMMAND;
    uTemp=((NULL!=pDoc) ? uTempE : uTempD);

    //File menu:  If there is document window, disable Import.
    if (m_phMenu[0]==hMenu)
        EnableMenuItem(hMenu, IDM_FILEIMPORT, uTemp);

    //Color menu:  no document, no commands
    if (m_phMenu[2]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_COLORBACKGROUND, uTemp);
        EnableMenuItem(hMenu, IDM_COLORLINE,       uTemp);
        fCallDefault=FALSE;
        }

    //Line menu:  no document, no commands
    if (m_phMenu[3]==hMenu)
        {
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            EnableMenuItem(hMenu, i, uTemp);

        fCallDefault=FALSE;
        }

    if (fCallDefault)
        CFrame::UpdateMenus(hMenu, iMenu);

    return;
    }

/*
 * CCosmoFrame::UpdateToolbar
 *
 * Purpose:
 *  Enables and disables tools depending on whether we have
 *  a document or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateToolbar(void)
    {
    BOOL        fLast;
    UINT        i;

    //Save the last enabled state before CFrame changes it
    fLast=m_fLastEnable;

    //Let the default hack on its tools
    CFrame::UpdateToolbar();

    /*
     * If CFrame::UpdateToolbar changed anything, then we need
     * to change as well--if nothing changes, nothing to do.
     */
    if (fLast!=m_fLastEnable)
        {
        m_pTB->Enable(IDM_FILEIMPORT, m_fLastEnable);

        m_pTB->Enable(IDM_COLORBACKGROUND, m_fLastEnable);
        m_pTB->Enable(IDM_COLORLINE,       m_fLastEnable);

        for (i=IDM_LINEMIN; i <= IDM_LINEMAX; i++)
            m_pTB->Enable(i, m_fLastEnable);
        }

    return;
    }

/*
 * CCosmoFrame::CheckLineSelection
 *
 * Purpose:
 *  Maintains the bitmap menu and the tools for the line selection.
 *  Both are mutially exclusive option lists where a selection in
 *  one has to affect the other.
 *
 * Parameters:
 *  uID             UINT ID of the item to be selected
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CheckLineSelection(UINT uID)
    {
    UINT        i;
    HMENU       hMenu;

    //Update menus and tools if the selection changed.
    if (uID!=m_uIDCurLine)
        {
        m_uIDCurLine=uID;
        hMenu=GetMenu(m_hWnd);

        //Uncheck all lines initially.
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            CheckMenuItem(hMenu, i, MF_UNCHECKED | MF_BYCOMMAND);

        CheckMenuItem(hMenu, uID, MF_CHECKED | MF_BYCOMMAND);
        m_pTB->Check(uID, TRUE);
        }

    return;
    }

/*
 * CCosmoFrame::UpdateEmbeddingUI
 *
 * Purpose:
 *  Puts the application into the user interface for editing an
 *  embedded object, manipulating menus and title bars.
 *
 * Parameters:
 *  fEmbedding      BOOL TRUE to go in the mode, FALSE to leave it.
 *  pszApp          LPCTSTR name of the container application as
 *                  received in IOleObject::SetHostNames.
 *  pszObj          LPCTSTR name of the object in the container as
 *                  received in IOleObject::SetHostNames.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateEmbeddingUI(BOOL fEmbedding
    , PCDocument pDoc, LPCTSTR pszApp, LPCTSTR pszObj)
    {
    HMENU           hMenu;
    TCHAR           szTemp[256];

    //First let's play with the File menu.
    hMenu=m_phMenu[0];

    //Remove or add the File New, Open, and Save items
    if (fEmbedding)
        {
        DeleteMenu(m_phMenu[0], IDM_FILENEW,   MF_BYCOMMAND);
        DeleteMenu(m_phMenu[0], IDM_FILEOPEN,  MF_BYCOMMAND);
        DeleteMenu(m_phMenu[0], IDM_FILECLOSE, MF_BYCOMMAND);
        DeleteMenu(m_phMenu[0], IDM_FILESAVE,  MF_BYCOMMAND);

        //Save As->Save Copy As
        ModifyMenu(m_phMenu[0], IDM_FILESAVEAS, MF_BYCOMMAND
            , IDM_FILESAVEAS, PSZ(IDS_SAVECOPYAS));
        }
    else
        {
        InsertMenu(m_phMenu[0], 0, MF_BYPOSITION, IDM_FILENEW
            , PSZ(IDS_NEW));
        InsertMenu(m_phMenu[0], 1, MF_BYPOSITION, IDM_FILEOPEN
            , PSZ(IDS_OPEN));
        InsertMenu(m_phMenu[0], 2, MF_BYPOSITION, IDM_FILESAVE
            , PSZ(IDS_SAVE));
        InsertMenu(m_phMenu[0], 3, MF_BYPOSITION, IDM_FILECLOSE
            , PSZ(IDS_SAVE));

        //Save Copy As->Save As
        ModifyMenu(m_phMenu[0], IDM_FILESAVEAS, MF_BYCOMMAND
            , IDM_FILESAVEAS, PSZ(IDS_SAVEAS));
        }

    //Change "Exit" to "Exit & Return to xx" or vice-versa for SDI
    if (fEmbedding)
        wsprintf(szTemp, PSZ(IDS_EXITANDRETURN), (LPSTR)pszObj);
    else
        lstrcpy(szTemp, PSZ(IDS_EXIT));

    ModifyMenu(m_phMenu[0], IDM_FILEEXIT, MF_STRING, IDM_FILEEXIT
        , szTemp);
    DrawMenuBar(m_hWnd);

    //Now let's play with the toolbar.
    m_pTB->Show(IDM_FILENEW,   !fEmbedding);
    m_pTB->Show(IDM_FILEOPEN,  !fEmbedding);
    m_pTB->Show(IDM_FILECLOSE, !fEmbedding);
    m_pTB->Show(IDM_FILESAVE,  !fEmbedding);

    //Enable what's left appropriately.
    UpdateToolbar();

    //Now play with the title bar.

    //IDS_EMBEDDINGCAPTION is MDI/SDI sensitive in COSMO.RC.
    wsprintf(szTemp, PSZ(IDS_EMBEDDINGCAPTION), pszObj);

    /*
     * Remember that in MDI situations that Windows takes care of
     * the frame window caption bar when the document is maximized.
     */
   #ifdef MDI
    SetWindowText(pDoc->Window(), szTemp);
   #else
    SetWindowText(m_hWnd, szTemp);
   #endif

    return;
    }


COSMO.H

/*
 * COSMO.H
 * Cosmo Chapter 21
 *
 * Single include file that pulls in everything needed for other
 * source files in the Cosmo application.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _COSMO_H_
#define _COSMO_H_

#define INC_CLASSLIB
#define INC_OLE2UI
//CHAPTER21MOD
#define CHAPTER21
//End CHAPTER21MOD
#include <inole.h>
#include "resource.h"

//Get the editor window information.
#include "polyline.h"

//COSMO.CPP:  Frame object that creates a main window

class CCosmoFrame : public CFrame
    {
    friend class CFigureClassFactory;
    friend class CFigure;   //For UI purposes.

    private:
        HBITMAP         m_hBmpLines[5];     //Menu item bitmaps
        UINT            m_uIDCurLine;       //Current line selection
        BOOL            m_fInitialized;     //Did OleInitalize work?
        LPCLASSFACTORY  m_pIClassDataTran;  //For locking

        BOOL            m_fEmbedding;       //-Embedding found?
        DWORD           m_dwRegCO;          //Registration key
        LPCLASSFACTORY  m_pIClassFactory;

    protected:
        //Overridable for creating a CClient for this frame
        virtual PCClient  CreateCClient(void);

        virtual BOOL      RegisterAllClasses(void);
        virtual BOOL      PreShowInit(void);
        virtual UINT      CreateToolbar(void);

        virtual LRESULT   OnCommand(HWND, WPARAM, LPARAM);
        virtual void      OnDocumentDataChange(PCDocument);
        virtual void      OnDocumentActivate(PCDocument);

        //New for this class
        virtual void      CreateLineMenu(void);

    public:
        CCosmoFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CCosmoFrame(void);

        //Overrides
        virtual BOOL      Init(PFRAMEINIT);
        virtual void      UpdateMenus(HMENU, UINT);
        virtual void      UpdateToolbar(void);

        //New for this class
        virtual void      CheckLineSelection(UINT);
        virtual void      UpdateEmbeddingUI(BOOL, PCDocument
                              , LPCTSTR, LPCTSTR);
    };

typedef CCosmoFrame *PCCosmoFrame;

//CLIENT.CPP

/*
 * The only reason we have a derived class here is to override
 * CreateCDocument so we can create our own type as well as
 * overriding NewDocument to perform one other piece of work once
 * the document's been created.
 */

class CCosmoClient : public CClient
    {
    protected:
        //Overridable for creating a new CDocument
        virtual PCDocument CreateCDocument(void);

    public:
        CCosmoClient(HINSTANCE, PCFrame);
        virtual ~CCosmoClient(void);

        virtual PCDocument NewDocument(BOOL);
    };

typedef CCosmoClient *PCCosmoClient;

//DOCUMENT.CPP

//Constant ID for the window polyline that lives in a document
#define ID_POLYLINE         10

class CCosmoDoc : public CDocument
    {
    friend class CPolylineAdviseSink;

    //These need access to FQueryPasteFromData, PasteFromData
    friend class CDropTarget;
    friend class CDropSource;

    friend class CFigureClassFactory;
    friend class CFigure;

    protected:
        UINT                    m_uPrevSize;    //Last WM_SIZE wParam
        LONG                    m_lVer;         //Loaded Polyline ver

        PCPolyline              m_pPL;          //Polyline window here
        PCPolylineAdviseSink    m_pPLAdv;       //Advises from Polyline

        class CDropTarget      *m_pDropTarget;  //Registered target
        BOOL                    m_fDragSource;  //Source==target?

        UINT                    m_cfEmbedSource;
        UINT                    m_cfObjectDescriptor;

        class CFigure          *m_pFigure;      //The object in us.

        //CHAPTER21MOD
        LPMONIKER               m_pMoniker;     //Our file
        DWORD                   m_dwRegROT;     //From IROT::Register

        UINT                    m_cfLinkSource;
        UINT                    m_cfLinkSrcDescriptor;
        //End CHAPTER21MOD

    protected:
        virtual BOOL     FMessageHook(HWND, UINT, WPARAM, LPARAM
            , LRESULT *);

        void             DropSelectTargetWindow(void);

    public:
        CCosmoDoc(HINSTANCE, PCFrame, PCDocumentAdviseSink);
        virtual ~CCosmoDoc(void);

        virtual BOOL     Init(PDOCUMENTINIT);

        virtual void     Clear(void);

        virtual BOOL     FDirtySet(BOOL);
        virtual BOOL     FDirtyGet(void);

        virtual UINT     Load(BOOL, LPTSTR);
        virtual UINT     Save(UINT, LPTSTR);
        //CHAPTER21MOD
        virtual void     Rename(LPTSTR);
        //End CHAPTER21MOD

        virtual void     Undo(void);
        virtual BOOL     Clip(HWND, BOOL);
        virtual HGLOBAL  RenderFormat(UINT);
        virtual BOOL     RenderMedium(UINT, LPSTGMEDIUM);
        virtual BOOL     FQueryPaste(void);
        virtual BOOL     Paste(HWND);

        //These were protected.  Now for IOleObject, should be public.
        virtual BOOL     FQueryPasteFromData(LPDATAOBJECT);
        virtual BOOL     PasteFromData(LPDATAOBJECT);
        LPDATAOBJECT     TransferObjectCreate(BOOL);

        virtual COLORREF ColorSet(UINT, COLORREF);
        virtual COLORREF ColorGet(UINT);

        virtual UINT     LineStyleSet(UINT);
        virtual UINT     LineStyleGet(void);
    };

typedef CCosmoDoc *PCCosmoDoc;

//These color indices wrap the polyline definitions
#define DOCCOLOR_BACKGROUND             POLYLINECOLOR_BACKGROUND
#define DOCCOLOR_LINE                   POLYLINECOLOR_LINE

//Drag-drop interfaces we need in the document
class CDropTarget : public IDropTarget
    {
    protected:
        ULONG               m_cRef;
        PCCosmoDoc          m_pDoc;

        LPDATAOBJECT        m_pIDataObject;     //From DragEnter

    public:
        CDropTarget(PCCosmoDoc);
        ~CDropTarget(void);

        //IDropTarget interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP DragEnter(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
        STDMETHODIMP DragOver(DWORD, POINTL, LPDWORD);
        STDMETHODIMP DragLeave(void);
        STDMETHODIMP Drop(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
    };

typedef CDropTarget *PCDropTarget;

class CDropSource : public IDropSource
    {
    protected:
        ULONG               m_cRef;
        PCCosmoDoc          m_pDoc;

    public:
        CDropSource(PCCosmoDoc);
        ~CDropSource(void);

        //IDropSource interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP QueryContinueDrag(BOOL, DWORD);
        STDMETHODIMP GiveFeedback(DWORD);
    };

typedef CDropSource *PCDropSource;

//Include classes necessary to become an OLE Document server.
#include "cosmole.h"

#endif //_COSMO_H_


COSMOLE.H

/*
 * COSMOLE.H
 * Cosmo Chapter 21
 *
 * Include file containing all compound document related
 * definitions.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _COSMOLE_H_
#define _COSMOLE_H_

//These are global for simplification of object implementation.
extern ULONG g_cObj;
extern ULONG g_cLock;
extern HWND  g_hWnd;

//Function for the object to notify on destruction.
void ObjectDestroyed(void);

//ICLASSF.CPP
class CFigureClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;
        PCCosmoFrame    m_pFR;
        BOOL            m_fCreated;     //Created one object yet?

    public:
        CFigureClassFactory(PCCosmoFrame);
        ~CFigureClassFactory(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP         CreateInstance(LPUNKNOWN, REFIID
                                 , PPVOID);
        STDMETHODIMP         LockServer(BOOL);
    };

typedef CFigureClassFactory *PCFigureClassFactory;

//FIGURE.CPP
//This is what the class factory creates

#define CFORMATETCGET   5

class CImpIPersistStorage;
typedef class CImpIPersistStorage *PCImpIPersistStorage;
class CImpIDataObject;
typedef class CImpIDataObject *PCImpIDataObject;
class CImpIOleObject;
typedef class CImpIOleObject *PCImpIOleObject;
//CHAPTER21MOD
class CImpIPersistFile;
typedef class CImpIPersistFile *PCImpIPersistFile;
//End CHAPTER21MOD

class CFigure : public IUnknown
    {
    friend class CImpIPersistStorage;
    friend class CImpIDataObject;
    friend class CImpIOleObject;
    //CHAPTER21MOD
    friend class CImpIPersistFile;
    //End CHAPTER21MOD

    protected:
        ULONG               m_cRef;
        PCCosmoFrame        m_pFR;      //Frame (for UI changes)
        PCCosmoDoc          m_pDoc;     //What holds real polyline
        PCPolyline          m_pPL;      //Copy of m_pDoc->m_pPL

        BOOL                m_fEmbedded;
        PFNDESTROYED        m_pfnDestroy;

        PCStringTable       m_pST;      //Object strings

        //Things for IPersistStorage
        PCImpIPersistStorage m_pImpIPersistStorage;
        LPSTORAGE           m_pIStorage;
        LPSTREAM            m_pIStream;
        CLSID               m_clsID;

        //Things for IDataObject
        PCImpIDataObject    m_pImpIDataObject;      //Implemented
        LPDATAADVISEHOLDER  m_pIDataAdviseHolder;   //Used

        UINT                m_cf;                   //pDoc->m_cf
        ULONG               m_cfeGet;
        FORMATETC           m_rgfeGet[CFORMATETCGET];

        //Things for IOleObject
        PCImpIOleObject     m_pImpIOleObject;       //Implemented
        LPOLEADVISEHOLDER   m_pIOleAdviseHolder;    //Used
        LPOLECLIENTSITE     m_pIOleClientSite;      //Used

        //CHAPTER21MOD
        LPMONIKER           m_pMoniker;
        DWORD               m_dwRegROT;
        PCImpIPersistFile   m_pImpIPersistFile;     //Implemented
        //End CHAPTER21MOD

    public:
        CFigure(PFNDESTROYED, PCCosmoDoc);
        ~CFigure(void);

        //Non-delegating IUnknown:  we don't support aggregation
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        BOOL Init(void);
        void FrameSet(PCCosmoFrame);
        BOOL FIsDirty(void);
        BOOL FIsEmbedded(void);
        void SendAdvise(UINT);
    };

typedef CFigure *PCFigure;

//Codes for CFigure::SendAdvise
//......Code......................Method called in CFigureSendAdvise...
#define OBJECTCODE_SAVED       0  //IOleAdviseHolder::SendOnSave
#define OBJECTCODE_CLOSED      1  //IOleAdviseHolder::SendOnClose
#define OBJECTCODE_RENAMED     2  //IOleAdviseHolder::SendOnRename
#define OBJECTCODE_SAVEOBJECT  3  //IOleClientSite::SaveObject
#define OBJECTCODE_DATACHANGED 4  //IDataAdviseHolder::SendOnDataChange
#define OBJECTCODE_SHOWWINDOW  5  //IOleClientSite::OnShowWindow(TRUE)
#define OBJECTCODE_HIDEWINDOW  6  //IOleClientSite::OnShowWindow(FALSE)
#define OBJECTCODE_SHOWOBJECT  7  //IOleClientSite::ShowObject

//IPERSTOR.CPP

class CImpIPersistStorage : public IPersistStorage
    {
    protected:
        ULONG               m_cRef;
        PCFigure            m_pObj;
        LPUNKNOWN           m_pUnkOuter;
        PSSTATE             m_psState;      //Storage state
        BOOL                m_fConvert;     //Are we Converting?

    public:
        CImpIPersistStorage(PCFigure, LPUNKNOWN);
        ~CImpIPersistStorage(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP InitNew(LPSTORAGE);
        STDMETHODIMP Load(LPSTORAGE);
        STDMETHODIMP Save(LPSTORAGE, BOOL);
        STDMETHODIMP SaveCompleted(LPSTORAGE);
        STDMETHODIMP HandsOffStorage(void);
    };

//IDATAOBJ.CPP
class CImpIDataObject : public IDataObject
    {
    private:
        ULONG           m_cRef;
        PCFigure        m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIDataObject(PCFigure, LPUNKNOWN);
        ~CImpIDataObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IDataObject members
        STDMETHODIMP GetData(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP GetDataHere(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP QueryGetData(LPFORMATETC);
        STDMETHODIMP GetCanonicalFormatEtc(LPFORMATETC, LPFORMATETC);
        STDMETHODIMP SetData(LPFORMATETC, LPSTGMEDIUM, BOOL);
        STDMETHODIMP EnumFormatEtc(DWORD, LPENUMFORMATETC *);
        STDMETHODIMP DAdvise(LPFORMATETC, DWORD,  LPADVISESINK
                         , DWORD *);
        STDMETHODIMP DUnadvise(DWORD);
        STDMETHODIMP EnumDAdvise(LPENUMSTATDATA *);
    };

//IOLEOBJ.CPP
class CImpIOleObject : public IOleObject
    {
    private:
        ULONG           m_cRef;
        PCFigure        m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIOleObject(PCFigure, LPUNKNOWN);
        ~CImpIOleObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IOleObject members
        STDMETHODIMP SetClientSite(LPOLECLIENTSITE);
        STDMETHODIMP GetClientSite(LPOLECLIENTSITE *);
        STDMETHODIMP SetHostNames(LPCOLESTR, LPCOLESTR);
        STDMETHODIMP Close(DWORD);
        STDMETHODIMP SetMoniker(DWORD, LPMONIKER);
        STDMETHODIMP GetMoniker(DWORD, DWORD, LPMONIKER *);
        STDMETHODIMP InitFromData(LPDATAOBJECT, BOOL, DWORD);
        STDMETHODIMP GetClipboardData(DWORD, LPDATAOBJECT *);
        STDMETHODIMP DoVerb(LONG, LPMSG, LPOLECLIENTSITE, LONG, HWND
                         , LPCRECT);
        STDMETHODIMP EnumVerbs(LPENUMOLEVERB *);
        STDMETHODIMP Update(void);
        STDMETHODIMP IsUpToDate(void);
        STDMETHODIMP GetUserClassID(CLSID *);
        STDMETHODIMP GetUserType(DWORD, LPOLESTR *);
        STDMETHODIMP SetExtent(DWORD, LPSIZEL);
        STDMETHODIMP GetExtent(DWORD, LPSIZEL);
        STDMETHODIMP Advise(LPADVISESINK, DWORD *);
        STDMETHODIMP Unadvise(DWORD);
        STDMETHODIMP EnumAdvise(LPENUMSTATDATA *);
        STDMETHODIMP GetMiscStatus(DWORD, DWORD *);
        STDMETHODIMP SetColorScheme(LPLOGPALETTE);
    };

//CHAPTER21MOD

class CImpIPersistFile : public IPersistFile
    {
    protected:
        ULONG               m_cRef;
        PCFigure            m_pObj;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIPersistFile(PCFigure, LPUNKNOWN);
        ~CImpIPersistFile(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP Load(LPCOLESTR, DWORD);
        STDMETHODIMP Save(LPCOLESTR, BOOL);
        STDMETHODIMP SaveCompleted(LPCOLESTR);
        STDMETHODIMP GetCurFile(LPOLESTR *);
    };

//End CHAPTER21MOD

#endif _COSMOLE_H_


DOCUMENT.CPP

/*
 * DOCUMENT.CPP
 * Cosmo Chapter 21
 *
 * Implementation of the CCosmoDoc derivation of CDocument as
 * well as an implementation of CPolylineAdviseSink.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CCosmoDoc::CCosmoDoc
 * CCosmoDoc::~CCosmoDoc
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame of the frame object.
 *  pAdv            PCDocumentAdviseSink to notify on events
 */

CCosmoDoc::CCosmoDoc(HINSTANCE hInst, PCFrame pFR
    , PCDocumentAdviseSink pAdv)
    : CDocument(hInst, pFR, pAdv)
    {
    m_pPL=NULL;
    m_pPLAdv=NULL;
    m_uPrevSize=SIZE_RESTORED;

    m_pDropTarget=NULL;
    m_fDragSource=FALSE;

    m_cfEmbedSource=RegisterClipboardFormat(CFSTR_EMBEDSOURCE);
    m_cfObjectDescriptor=RegisterClipboardFormat
        (CFSTR_OBJECTDESCRIPTOR);

    m_pFigure=NULL;

    //CHAPTER21MOD
    m_pMoniker=NULL;
    m_dwRegROT=0L;

    m_cfLinkSource=RegisterClipboardFormat(CFSTR_LINKSOURCE);
    m_cfLinkSrcDescriptor=RegisterClipboardFormat
        (CFSTR_LINKSRCDESCRIPTOR);
    //End CHAPTER21MOD
    return;
    }

CCosmoDoc::~CCosmoDoc(void)
    {
    //Make sure the object is saved
    m_pFigure->SendAdvise(OBJECTCODE_SAVEOBJECT);
    m_pFigure->SendAdvise(OBJECTCODE_HIDEWINDOW);
    m_pFigure->SendAdvise(OBJECTCODE_CLOSED);

    //CHAPTER21MOD
    //If the document is reg'd as running, revoke and free the moniker
    INOLE_RevokeAsRunning(&m_dwRegROT);

    if (NULL!=m_pMoniker)
        m_pMoniker->Release();
    //End CHAPTER21MOD

    if (NULL!=m_pFigure)
        {
        m_pFigure->AddRef();
        CoDisconnectObject(m_pFigure, 0L);
        m_pFigure->Release();   //Starts shutdown if necessary.
        delete m_pFigure;
        }

    //Clean up the allocations we did in Init
    if (NULL!=m_pPL)
        delete m_pPL;

    if (NULL!=m_pPLAdv)
        delete m_pPLAdv;

    return;
    }

/*
 * CCosmoDoc::Init
 *
 * Purpose:
 *  Initializes an already created document window.  The client
 *  actually creates the window for us, then passes that here for
 *  further initialization.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CCosmoDoc::Init(PDOCUMENTINIT pDI)
    {
    RECT        rc;

    //Change the stringtable range to our customization.
    pDI->idsMin=IDS_DOCUMENTMIN;
    pDI->idsMax=IDS_DOCUMENTMAX;

    //Do default initialization
    if (!CDocument::Init(pDI))
        return FALSE;

    //Add the Polyline stuff we need.
    m_pPLAdv=new CPolylineAdviseSink(this);
    m_pPL   =new CPolyline(m_hInst);

    //Attempt to create our contained Polyline.
    GetClientRect(m_hWnd, &rc);
    InflateRect(&rc, -8, -8);

    if (!m_pPL->Init(m_hWnd, &rc, WS_CHILD | WS_VISIBLE
        , ID_POLYLINE, m_pPLAdv))
        return FALSE;

    m_pDropTarget=new CDropTarget(this);

    if (NULL!=m_pDropTarget)
        {
        m_pDropTarget->AddRef();
        CoLockObjectExternal(m_pDropTarget, TRUE, FALSE);
        RegisterDragDrop(m_hWnd, m_pDropTarget);
        }

    m_pFigure=new CFigure(ObjectDestroyed, this);

    if (NULL==m_pFigure)
        return FALSE;

    //We created an object, so count it.
    g_cObj++;

    if (!m_pFigure->Init())
        return FALSE;

    return TRUE;
    }

/*
 * CCosmoDoc::FMessageHook
 *
 * Purpose:
 *  Processes WM_SIZE for the document so we can resize
 *  the Polyline.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CCosmoDoc::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    UINT        dx, dy;
    RECT        rc;

    *pLRes=0;

    if (WM_SIZE==iMsg)
        {
        //Don't effect the Polyline size to or from minimized state.
        if (SIZE_MINIMIZED!=wParam && SIZE_MINIMIZED !=m_uPrevSize)
            {
            //When we change size, resize any Polyline we hold.
            dx=LOWORD(lParam);
            dy=HIWORD(lParam);

            /*
             * If we are getting WM_SIZE in response to a Polyline
             * notification, then don't resize the Polyline window
             * again.
             */
            if (!m_fNoSize && NULL!=m_pPL)
                {
                //Resize the polyline to fit the new client
                SetRect(&rc, 8, 8, dx-8, dy-8);
                m_pPL->RectSet(&rc, FALSE);

                /*
                 * We consider sizing something that makes the file
                 * dirty, but not until we've finished the create
                 * process, which is why we set fNoDirty to FALSE
                 * in WM_CREATE since we get a WM_SIZE on the first
                 * creation.
                 */
                if (!m_fNoDirty)
                    FDirtySet(TRUE);

                SetRect(&rc, 0, 0, dx, dy);

                if (NULL!=m_pAdv)
                    m_pAdv->OnSizeChange(this, &rc);

                m_fNoDirty=FALSE;
                }
            }

        m_uPrevSize=wParam;
        }

    if (WM_LBUTTONDOWN==iMsg)
        {
        LPDROPSOURCE    pIDropSource;
        LPDATAOBJECT    pIDataObject;
        HRESULT         hr;
        SCODE           sc;
        DWORD           dwEffect;

        /*
         * The document has an 8 pixel border around the polyline
         * window where we'll see mouse clicks.  A left mouse button
         * click here means the start of a drag-drop operation.
         *
         * Since this is a modal operation, this IDropSource
         * is entirely local.
         */

        pIDropSource=new CDropSource(this);

        if (NULL==pIDropSource)
            return FALSE;

        pIDropSource->AddRef();
        m_fDragSource=TRUE;

        //Go get the data and start the ball rolling.
        pIDataObject=TransferObjectCreate(FALSE);

        if (NULL!=pIDataObject)
            {
            //CHAPTER21MOD
            //Add DROPEFFECT_LINK now that we source links
            hr=DoDragDrop(pIDataObject, pIDropSource
                , DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK
                , &dwEffect);
            //End CHAPTER21MOD

            pIDataObject->Release();
            sc=GetScode(hr);
            }
        else
            sc=E_FAIL;

        /*
         * When we return from DoDragDrop, either cancel or drop.
         * First toss the IDropSource we have here, then bail out
         * on cancel, and possibly clear our data on a move drop.
         */

        pIDropSource->Release();

        /*
         * If dropped on the same document (determined using
         * this flag, then dwEffect will be DROPEFFECT_NONE (see
         * IDropTarget::Drop in DROPTGT.CPP).  In any case,
         * reset this since the operation is done.
         */

        m_fDragSource=FALSE;

        if (DRAGDROP_S_DROP==sc && DROPEFFECT_MOVE==dwEffect)
            {
            m_pPL->New();
            FDirtySet(TRUE);
            }

        //On a canceled drop or a copy we don't do anything else
        return TRUE;
        }

    if (WM_DESTROY==iMsg)
        {
        /*
         * We have to revoke the drop target here because the window
         * will be destroyed and the property forcefully removed
         * before we could do this in the destructor.
         */
        if (NULL!=m_pDropTarget)
            {
            RevokeDragDrop(m_hWnd);
            CoLockObjectExternal(m_pDropTarget, FALSE, TRUE);
            ReleaseInterface(m_pDropTarget);
            }

        return FALSE;
        }

    /*
     * We return FALSE even on WM_SIZE so we can let the default
     * procedure handle maximized MDI child windows appropriately.
     */
    return FALSE;
    }

/*
 * CCosmoDoc::Clear
 *
 * Purpose:
 *  Sets all contents in the document back to defaults with
 *  no filename.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Clear(void)
    {
    //Completely reset the polyline
    m_pPL->New();

    CDocument::Clear();
    m_lVer=0;
    return;
    }

/*
 * CCosmoDoc::FDirtySet
 *
 * Purpose:
 *  Sets or clears the document 'dirty' flag returning the previous
 *  state of that same flag.  We override this in Cosmo server to
 *  send the OnDataChange notification as necessary.
 *
 * Parameters:
 *  fDirty          BOOL indicating the new contents of the dirty
 *                  flag.
 *
 * Return Value:
 *  BOOL            Previous value of the dirty flag.
 */

BOOL CCosmoDoc::FDirtySet(BOOL fDirty)
    {
    BOOL        fRet;

    fRet=CDocument::FDirtySet(fDirty);
    m_pFigure->SendAdvise(OBJECTCODE_DATACHANGED);

    return fRet;
    }

/*
 * CCosmoDoc::FDirtyGet
 *
 * Purpose:
 *  Override of the normal FDirtyGet such that we never return dirty
 *  for an embedded object we're serving since updates constantly
 *  happen and since the object will be saved on closure.  This then
 *  prevents any message boxes asking the user to save.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the document is dirty, FALSE otherwise.
 */

BOOL CCosmoDoc::FDirtyGet(void)
    {
    if (m_pFigure->FIsEmbedded())
        return FALSE;

    return m_fDirty;
    }

/*
 * CCosmoDoc::Load
 *
 * Purpose:
 *  Loads a given document without any user interface overwriting
 *  the previous contents of the Polyline window.  We do this by
 *  opening the file and telling the Polyline to load itself from
 *  that file.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load, NULL if the file
 *                  is new and untitled.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CCosmoDoc::Load(BOOL fChangeFile, LPTSTR pszFile)
    {
    HRESULT         hr;
    LPSTORAGE       pIStorage;

    if (NULL==pszFile)
        {
        //For a new untitled document, just rename ourselves.
        Rename(NULL);
        m_lVer=VERSIONCURRENT;
        return DOCERR_NONE;
        }

    /*
     * If not a Compound File, open the file using STGM_CONVERT in
     * transacted mode to see old files as a storage with one stream
     * called "CONTENTS" (which is conveniently the name we use
     * in the new files).  We must use STGM_TRANSACTED here or else
     * the old file will be immediately converted on disk:  we only
     * want a converted image in memory from which to read.  In
     * addition, note that we need STGM_READWRITE as well since
     * conversion is inherently a write operation.
     */

    pIStorage=NULL;

    if (NOERROR!=StgIsStorageFile(pszFile))
        {
        hr=StgCreateDocfile(pszFile,STGM_TRANSACTED | STGM_READWRITE
            | STGM_CONVERT | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

        if (FAILED(hr))
            {
            //If denied write access, try to load the old way
            if (STG_E_ACCESSDENIED==GetScode(hr))
                m_lVer=m_pPL->ReadFromFile(pszFile);
            else
                return DOCERR_COULDNOTOPEN;
            }
        }
    else
        {
        hr=StgOpenStorage(pszFile, NULL, STGM_DIRECT | STGM_READ
            | STGM_SHARE_EXCLUSIVE, NULL, 0, &pIStorage);

        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;
        }

    if (NULL!=pIStorage)
        {
        m_lVer=m_pPL->ReadFromStorage(pIStorage);
        pIStorage->Release();
        }

    if (POLYLINE_E_READFAILURE==m_lVer)
        return DOCERR_READFAILURE;

    if (POLYLINE_E_UNSUPPORTEDVERSION==m_lVer)
        return DOCERR_UNSUPPORTEDVERSION;

    if (fChangeFile)
        Rename(pszFile);

    //Importing a file makes things dirty
    FDirtySet(!fChangeFile);

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::Save
 *
 * Purpose:
 *  Writes the file to a known filename, requiring that the user has
 *  previously used FileOpen or FileSaveAs to provide a filename.
 *
 * Parameters:
 *  uType           UINT indicating the type of file the user
 *                  requested to save in the File Save As dialog.
 *  pszFile         LPTSTR under which to save.  If NULL, use the
 *                  current name.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CCosmoDoc::Save(UINT uType, LPTSTR pszFile)
    {
    LONG        lVer, lRet;
    UINT        uTemp;
    BOOL        fRename=TRUE;
    HRESULT     hr;
    LPSTORAGE   pIStorage;
    BOOL        fEmbedding;

    fEmbedding=m_pFigure->FIsEmbedded();

    if (NULL==pszFile)
        {
        fRename=FALSE;
        pszFile=m_szFile;
        }

    /*
     * Type 1 is the current version, type 2 is version 1.0 of the
     * Polyline so we use this to send the right version to
     * CPolyline::WriteToFile/WriteToStorage.
     */

    switch (uType)
        {
        case 0:         //From Save, use loaded version.
            lVer=m_lVer;
            break;

        case 1:
            lVer=VERSIONCURRENT;
            break;

        case 2:
            lVer=MAKELONG(0, 1);    //1.0
            break;

        default:
            return DOCERR_UNSUPPORTEDVERSION;
        }

    /*
     * If the version the user wants to save is different from the
     * version that we loaded and m_lVer is not zero (new doc),
     * then inform the user of the version change and verify.
     */

    //For embedding, this is Save Copy As, so don't ask about versions.
    if (0!=m_lVer && m_lVer!=lVer && !fEmbedding)
        {
        TCHAR       szMsg[128];

        wsprintf(szMsg, PSZ(IDS_VERSIONCHANGE)
            , (UINT)HIWORD(m_lVer), (UINT)LOWORD(m_lVer)
            , (UINT)HIWORD(lVer), (UINT)LOWORD(lVer));

        uTemp=MessageBox(m_hWnd, szMsg, PSZ(IDS_DOCUMENTCAPTION)
            , MB_YESNOCANCEL);

        if (IDCANCEL==uTemp)
            return DOCERR_CANCELLED;

        //If the user won't upgrade, revert to loaded version.
        if (IDNO==uTemp)
            lVer=m_lVer;
        }

    /*
     * For 1.0 files, still use the old code.  For new files, use
     * storages instead
     */
    if (lVer==MAKELONG(0, 1))
        lRet=m_pPL->WriteToFile(pszFile, lVer);
    else
        {
        hr=StgCreateDocfile(pszFile, STGM_DIRECT | STGM_READWRITE
            | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;

        //Mark this as one of our class
        WriteClassStg(pIStorage, CLSID_CosmoFigure);

        //Write user-readable class information
        WriteFmtUserTypeStg(pIStorage, m_cf
            , PSZ(IDS_CLIPBOARDFORMAT));

        lRet=m_pPL->WriteToStorage(pIStorage, lVer);
        pIStorage->Release();
        }

    if (POLYLINE_E_NONE!=lRet)
        return DOCERR_WRITEFAILURE;

    //Saving makes us clean, but this doesn't apply to embedding.
    if (!fEmbedding)
        FDirtySet(FALSE);

    //Update the known version of this document.
    m_lVer=lVer;

    /*
     * If we're embedding, this is Save Copy As, so no rename.
     * Note that we also don't care about having been set to clean
     * since we're always 'clean' as an embedded object from
     * the user's perspective.
     */
    if (fRename && !fEmbedding)
        Rename(pszFile);

    return DOCERR_NONE;
    }

//CHAPTER21MOD
/*
 * CCosmoDoc::Rename
 *
 * Purpose:
 *  Overrides the normal rename to manage a moniker for the open
 *  file.  We maintain no moniker for untitled documents, and
 *  therefore do not allow linking to such documents.
 *
 * Parameters:
 *  pszFile         LPTSTR to the new filename.
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Rename(LPTSTR pszFile)
    {
    LPMONIKER   pmk;

    //We don't need to change the base class, just augment...
    CDocument::Rename(pszFile);

    //Unregister the old moniker (m_dwRegROT set to zero).
    INOLE_RevokeAsRunning(&m_dwRegROT);

    ReleaseInterface(m_pMoniker);

    if (NULL!=pszFile)
        {
        CreateFileMoniker(pszFile, &pmk);

        if (NULL!=pmk)
            {
            m_pMoniker=pmk;     //pmk AddRef'd in CreateFileMoniker
            INOLE_RegisterAsRunning(m_pFigure, m_pMoniker
                , 0, &m_dwRegROT);

            m_pFigure->SendAdvise(OBJECTCODE_RENAMED);
            }
        }

    return;
    }
//End CHAPTER21MOD

/*
 * CCosmoDoc::Undo
 *
 * Purpose:
 *  Reverses a previous action.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Undo(void)
    {
    m_pPL->Undo();
    return;
    }

/*
 * CCosmoDoc::Clip
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard, optionally implementing Cut by deleting the
 *  data in the current window after rendering.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *  fCut            BOOL indicating cut (TRUE) or copy (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::Clip(HWND hWndFrame, BOOL fCut)
    {
    BOOL            fRet=TRUE;
    LPDATAOBJECT    pIDataObject;

    pIDataObject=TransferObjectCreate(fCut);

    if (NULL==pIDataObject)
        return FALSE;

    fRet=SUCCEEDED(OleSetClipboard(pIDataObject));
    pIDataObject->Release();

    //Delete our current data if "cut" succeeded.
    if (fRet && fCut)
        {
        m_pPL->New();
        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CCosmoDoc::RenderFormat
 *
 * Purpose:
 *  Renders a specific clipboard format into global memory.
 *
 * Parameters:
 *  cf              UINT format to render.
 *
 * Return Value:
 *  HGLOBAL         Global memory handle containing the data.
 */

HGLOBAL CCosmoDoc::RenderFormat(UINT cf)
    {
    HGLOBAL     hMem;

    if (cf==m_cf)
        {
        m_pPL->DataGetMem(VERSIONCURRENT, &hMem);
        return hMem;
        }

    switch (cf)
        {
        case CF_METAFILEPICT:
            return m_pPL->RenderMetafilePict();

        case CF_BITMAP:
            return (HGLOBAL)m_pPL->RenderBitmap();
        }

    return NULL;
    }

/*
 * CCosmoDoc::RenderMedium
 *
 * Purpose:
 *  Like RenderFormat, this function creates a specific data format
 *  based on the cf parameter.  Unlike RenderFormat, we store the
 *  result in a STGMEDIUM in case it has a medium other than
 *  TYMED_HGLOBAL.  For conveniece we'll centralize all compound
 *  document formats here, hGlobal or not.
 *
 * Parameters:
 *  cf              UINT clipboard format of interest.
 *  pSTM            LSTGMEDIUM to fill.  We only fill the union
 *                  and tymed.
 *
 * Return Value:
 *  BOOL            TRUE if we could render the format,
 *                  FALSE otherwise.
 */

BOOL CCosmoDoc::RenderMedium(UINT cf, LPSTGMEDIUM pSTM)
    {
    if (NULL==pSTM)
        return FALSE;

    if (cf==m_cfEmbedSource)
        {
        /*
         * Embed Source data is an IStorage containing the native
         * data (same as Embedded Object).  Since our data is small,
         * it makes the most sense to create an IStorage in memory
         * and put transfer that instead of a disk-based IStorage.
         */

        pSTM->pstg=INOLE_CreateStorageOnHGlobal(STGM_DIRECT
            | STGM_READWRITE | STGM_SHARE_EXCLUSIVE);

        if (NULL==pSTM->pstg)
            return FALSE;

        //Now save the data to the storage.
        WriteClassStg(pSTM->pstg, CLSID_CosmoFigure);
        WriteFmtUserTypeStg(pSTM->pstg, m_cf
            , PSZ(IDS_CLIPBOARDFORMAT));

        if (POLYLINE_E_NONE!=m_pPL->WriteToStorage(pSTM->pstg
            , VERSIONCURRENT))
            {
            /*
             * When someone releases the IStorage, STORAGE.DLL will
             * release the ILockBytes which, having fDeleteOnRelease
             * TRUE (second parameter) will release the memory.
             * That's why we don't have STGM_DELETEONRELEASE on the
             * IStorage.
             */
            pSTM->pstg->Release();
            return FALSE;
            }

        pSTM->tymed=TYMED_ISTORAGE;
        return TRUE;
        }

    //CHAPTER21MOD
    /*
     * CFSTR_OBJECTDESCRIPTOR and CFSTR_LINKSRCDESCRIPTOR are the
     * same formats, but only copy link source if we have a moniker.
     */
    if (cf==m_cfLinkSrcDescriptor && NULL==m_pMoniker)
        return FALSE;

    if (cf==m_cfObjectDescriptor || cf==m_cfLinkSrcDescriptor)
    //End CHAPTER21MOD
        {
        SIZEL   szl, szlT;
        POINTL  ptl;
        RECT    rc;
        //CHAPTER21MOD
        LPTSTR  psz=NULL;
        //End CHAPTER21MOD

        m_pPL->SizeGet(&rc);
        SETSIZEL(szlT, rc.right, rc.bottom);
        XformSizeInPixelsToHimetric(NULL, &szlT, &szl);

        SETPOINTL(ptl, 0, 0);

        //CHAPTER21MOD
        //Include the moniker display name now, if we have one.
        if (m_pMoniker)
            {
            LPBC    pbc;

            CreateBindCtx(0, &pbc);
           #ifdef WIN32ANSI
            LPOLESTR    pszW;

            m_pMoniker->GetDisplayName(pbc, NULL, &pszW);
            psz=(LPTSTR)CoTaskMemAlloc(512);
            WideCharToMultiByte(CP_ACP, 0, pszW, -1, psz, 512
                , NULL, NULL);
            CoTaskMemFree((void *)pszW);
           #else
            m_pMoniker->GetDisplayName(pbc, NULL, &psz);
           #endif
            pbc->Release();
            }

        pSTM->hGlobal=INOLE_AllocObjectDescriptor
            (CLSID_CosmoFigure, DVASPECT_CONTENT, szl, ptl
            , OLEMISC_RECOMPOSEONRESIZE, PSZ(IDS_OBJECTDESCRIPTION)
            , psz);

        CoTaskMemFree((void *)psz);
        //End CHAPTER21MOD

        pSTM->tymed=TYMED_HGLOBAL;
        return (NULL!=pSTM->hGlobal);
        }

    //CHAPTER21MOD
    if (cf==m_cfLinkSource)
        {
        if (NULL!=m_pMoniker)
            {
            FORMATETC   fe;
            HRESULT     hr;

            pSTM->tymed=TYMED_NULL;
            SETDefFormatEtc(fe, cf, TYMED_ISTREAM);
            hr=INOLE_GetLinkSourceData(m_pMoniker
                , (LPCLSID)&CLSID_CosmoFigure, &fe, pSTM);

            return SUCCEEDED(hr);
            }
        }
    //End CHAPTER21MOD

    return FALSE;
    }

/*
 * CCosmoDoc::FQueryPaste
 *
 * Purpose:
 *  Determines if we can paste data from the clipboard.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CCosmoDoc::FQueryPaste(void)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    fRet=FQueryPasteFromData(pIDataObject);
    pIDataObject->Release();
    return fRet;
    }

/*
 * CCosmoDoc::FQueryPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Determines if we can paste data from a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which we might want to paste.
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CCosmoDoc::FQueryPasteFromData(LPDATAOBJECT pIDataObject)
    {
    FORMATETC       fe;

    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    return (NOERROR==pIDataObject->QueryGetData(&fe));
    }

/*
 * CCosmoDoc::Paste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  to the current figure in the editor window.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::Paste(HWND hWndFrame)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    fRet=PasteFromData(pIDataObject);
    pIDataObject->Release();

    return fRet;
    }

/*
 * CCosmoDoc::PasteFromData
 * (Protected)
 *
 * Purpose:
 *  Retrieves the private data format from a data object and sets
 *  it to the current figure in the editor window.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::PasteFromData(LPDATAOBJECT pIDataObject)
    {
    FORMATETC       fe;
    STGMEDIUM       stm;
    BOOL            fRet;

    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    fRet=SUCCEEDED(pIDataObject->GetData(&fe, &stm));

    if (fRet && NULL!=stm.hGlobal)
        {
        m_pPL->DataSetMem(stm.hGlobal, FALSE, FALSE, TRUE);
        ReleaseStgMedium(&stm);
        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CCosmoDoc::TransferObjectCreate
 * (Protected)
 *
 * Purpose:
 *  Creates a DataTransferObject and stuffs the current Polyline
 *  data into it, used for both clipboard and drag-drop operations.
 *
 * Parameters:
 *  fCut            BOOL TRUE if we're cutting, FALSE if we're
 *                  copying.
 *
 * Return Value:
 *  LPDATAOBJECT    Pointer to the object created, NULL on failure
 */

LPDATAOBJECT CCosmoDoc::TransferObjectCreate(BOOL fCut)
    {
    UINT            i;
    HRESULT         hr;
    STGMEDIUM       stm;
    FORMATETC       fe;
    LPDATAOBJECT    pIDataObject=NULL;
    //CHAPTER21MOD
    const UINT      cFormats=7;
    static UINT     rgcf[7]={0, 0, 0, CF_METAFILEPICT, CF_BITMAP, 0, 0};
    static DWORD    rgtm[7]={TYMED_HGLOBAL, TYMED_ISTORAGE, TYMED_HGLOBAL
        , TYMED_MFPICT, TYMED_GDI, TYMED_ISTREAM, TYMED_HGLOBAL};
    //End CHAPTER21MOD

    hr=CoCreateInstance(CLSID_DataTransferObject, NULL
        , CLSCTX_INPROC_SERVER, IID_IDataObject
        , (PPVOID)&pIDataObject);

    if (FAILED(hr))
        return NULL;

    rgcf[0]=m_cf;
    rgcf[1]=m_cfEmbedSource;
    rgcf[2]=m_cfObjectDescriptor;
    //CHAPTER21MOD
    //Don't include link stuff for cutting (0 format always fails)
    if (!fCut)
        {
        rgcf[5]=m_cfLinkSource;
        rgcf[6]=m_cfLinkSrcDescriptor;
        }
    //End CHAPTER21MOD

    for (i=0; i < cFormats; i++)
        {
        /*
         * RenderFormat handles memory handles, but for compound doc
         * formats we need something more.  So if RenderFormat fails
         * (which it will for i=1, try our latest addition which
         * writes to a different field in the STGMEDIUM.
         */
        stm.hGlobal=RenderFormat(rgcf[i]);

        if (NULL==stm.hGlobal)
            {
            if (!RenderMedium(rgcf[i], &stm))
                continue;
            }

        stm.tymed=rgtm[i];
        stm.pUnkForRelease=NULL;
        SETDefFormatEtc(fe, rgcf[i], rgtm[i]);
        pIDataObject->SetData(&fe, &stm, TRUE);
        }

    return pIDataObject;    //Caller now responsible
    }

/*
 * CCosmoDoc::DropSelectTargetWindow
 * (Protected)
 *
 * Purpose:
 *  Creates a thin inverted frame around a window that we use to

*  show the window as a drop target.  This is a toggle function:
 *  It uses XOR to create the effect so it must be called twice to
 *  leave the window as it was.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::DropSelectTargetWindow(void)
    {
    HDC         hDC;
    RECT        rc;
    UINT        dd=3;
    HWND        hWnd;

    hWnd=m_pPL->Window();
    hDC=GetWindowDC(hWnd);
    GetClientRect(hWnd, &rc);

    //Frame this window with inverted pixels

    //Top
    PatBlt(hDC, rc.left, rc.top, rc.right-rc.left, dd, DSTINVERT);

    //Bottom
    PatBlt(hDC, rc.left, rc.bottom-dd, rc.right-rc.left, dd
        , DSTINVERT);

    //Left excluding regions already affected by top and bottom
    PatBlt(hDC, rc.left, rc.top+dd, dd, rc.bottom-rc.top-(2*dd)
        , DSTINVERT);

    //Right excluding regions already affected by top and bottom
    PatBlt(hDC, rc.right-dd, rc.top+dd, dd, rc.bottom-rc.top-(2*dd)
        , DSTINVERT);

    ReleaseDC(hWnd, hDC);
    return;
    }

/*
 * CCosmoDoc::ColorSet
 *
 * Purpose:
 *  Changes a color used in our contained Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color.
 *
 * Return Value:
 *  COLORREF        Previous color for the given index.
 */

COLORREF CCosmoDoc::ColorSet(UINT iColor, COLORREF cr)
    {
    return m_pPL->ColorSet(iColor, cr);
    }

/*
 * CCosmoDoc::ColorGet
 *
 * Purpose:
 *  Retrieves a color currently in use in the Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to retrieve.
 *
 * Return Value:
 *  COLORREF        Current color for the given index.
 */

COLORREF CCosmoDoc::ColorGet(UINT iColor)
    {
    return m_pPL->ColorGet(iColor);
    }

/*
 * CCosmoDoc::LineStyleSet
 *
 * Purpose:
 *  Changes the line style currently used in the Polyline
 *
 * Parameters:
 *  iStyle          UINT index of the new line style to use.
 *
 * Return Value:
 *  UINT            Previous line style.
 */

UINT CCosmoDoc::LineStyleSet(UINT iStyle)
    {
    return m_pPL->LineStyleSet(iStyle);
    }

/*
 * CCosmoDoc::LineStyleGet
 *
 * Purpose:
 *  Retrieves the line style currently used in the Polyline
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Current line style.
 */

UINT CCosmoDoc::LineStyleGet(void)
    {
    if (NULL==m_pPL)    //m_pPL might not be valid yet
        return 0L;

    return m_pPL->LineStyleGet();
    }

/*
 * CPolylineAdviseSink::CPolylineAdviseSink
 * CPolylineAdviseSink::~CPolylineAdviseSink
 *
 * Constructor Parameters:
 *  pv              LPVOID to store in this object
 */

CPolylineAdviseSink::CPolylineAdviseSink(LPVOID pv)
    {
    m_pv=pv;
    return;
    }

CPolylineAdviseSink::~CPolylineAdviseSink(void)
    {
    return;
    }

/*
 * CPolylineAdviseSink::OnPointChange
 *
 * Purpose:
 *  Informs the document that the polyline added or removed a point.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnPointChange(void)
    {
    PCDocument      pDoc=(PCDocument)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnSizeChange
 *
 * Purpose:
 *  Informs the document that the polyline changed size.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnSizeChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;
    RECT            rc;
    DWORD           dwStyle;

    /*
     * Polyline window is informing us that it changed size in
     * response to setting it's data.  Therefore we have to
     * size ourselves accordingly but without moving the screen
     * position of the polyline window.
     */

    pDoc->m_fNoSize=TRUE;

    //Set the document window size.
    GetWindowRect(pDoc->m_pPL->Window(), &rc);
    InflateRect(&rc, 8, 8);

    //Adjust for a window sans menu
    dwStyle=GetWindowLong(pDoc->m_hWnd, GWL_STYLE);
    AdjustWindowRect(&rc, dwStyle, FALSE);

    SetWindowPos(pDoc->m_hWnd, NULL, 0, 0, rc.right-rc.left
        , rc.bottom-rc.top, SWP_NOMOVE | SWP_NOZORDER);

    if (NULL!=pDoc->m_pAdv)
        pDoc->m_pAdv->OnSizeChange(pDoc, &rc);

    pDoc->m_fNoSize=FALSE;
    pDoc->FDirtySet(TRUE);

    return;
    }

/*
 * CPolylineAdviseSink::OnDataChange
 *
 * Purpose:
 *  Informs the document that the polyline data changed.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnDataChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    if (NULL!=pDoc->m_pAdv)
        pDoc->m_pAdv->OnDataChange(pDoc);

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnColorChange
 *
 * Purpose:
 *  Informs the document that the polyline data changed a color.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnColorChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnLineStyleChange
 *
 * Purpose:
 *  Informs the document that the polyline changed its line style.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnLineStyleChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }


DROPSRC.CPP

/*
 * DROPSRC.CPP
 * Cosmo Chapter 21
 *
 * Implementation of a DropSource object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CDropSource::CDropSource
 * CDropSource::~CDropSource
 *
 * Constructor Parameters:
 *  pDoc            PCosmoDoc containing this interface.
 */

CDropSource::CDropSource(PCCosmoDoc pDoc)
    {
    m_cRef=0;
    m_pDoc=pDoc;
    return;
    }

CDropSource::~CDropSource(void)
    {
    return;
    }

/*
 * CDropSource::QueryInterface
 * CDropSource::AddRef
 * CDropSource::Release
 *
 * Purpose:
 *  IUnknown members for CDropSource object.
 */

STDMETHODIMP CDropSource::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDropSource==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDropSource::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropSource::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDropSource::QueryDragContinue
 *
 * Purpose:
 *  Determines whether to continue a drag operation or cancel it.
 *
 * Parameters:
 *  fEsc            BOOL indicating that the ESC key was pressed.
 *  grfKeyState     DWORD providing states of keys and mouse buttons
 *
 * Return Value:
 *  HRESULT         DRAGDROP_S_CANCEL to stop the drag,
 *                  DRAGDROP_S_DROP to drop the data where it is,
 *                  or NOERROR to continue.
 */

STDMETHODIMP CDropSource::QueryContinueDrag(BOOL fEsc
    , DWORD grfKeyState)
    {
    if (fEsc)
        return ResultFromScode(DRAGDROP_S_CANCEL);

    if (!(grfKeyState & MK_LBUTTON))
        return ResultFromScode(DRAGDROP_S_DROP);

    return NOERROR;
    }

/*
 * CDropSource::GiveFeedback
 *
 * Purpose:
 *  Provides cursor feedback to the user since the source task
 *  always has the mouse capture.  We can also provide any other
 *  type of feedback above cursors if we so desire.
 *
 * Parameters:
 *  dwEffect        DWORD effect flags returned from the last target
 *
 * Return Value:
 *  HRESULT         NOERROR if you set a cursor yourself or
 *                  DRAGDROP_S_USEDEFAULTCURSORS to let OLE do
 *                  the work.
 */

STDMETHODIMP CDropSource::GiveFeedback(DWORD dwEffect)
    {
    return ResultFromScode(DRAGDROP_S_USEDEFAULTCURSORS);
    }


DROPTGT.CPP

/*
 * DROPTGT.CPP
 * Cosmo Chapter 21
 *
 * Implementation of the IDropTarget interface.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CDropTarget::CDropTarget
 * CDropTarget::~CDropTarget
 *
 * Constructor Parameters:
 *  pDoc            PCCosmoDoc of the document containing us.
 */

CDropTarget::CDropTarget(PCCosmoDoc pDoc)
    {
    m_cRef=0;
    m_pDoc=pDoc;

    m_pIDataObject=NULL;
    return;
    }

CDropTarget::~CDropTarget(void)
    {
    return;
    }

/*
 * CDropTarget::QueryInterface
 * CDropTarget::AddRef
 * CDropTarget::Release
 *
 * Purpose:
 *  IUnknown members for CDropTarget object.
 */

STDMETHODIMP CDropTarget::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDropTarget==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDropTarget::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropTarget::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDropTarget::DragEnter
 *
 * Purpose:
 *  Indicates that data in a drag operation has been dragged over
 *  our window that's a potential target.  We are to decide if it's
 *  something we're interested in or not.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT providing the source data.
 *  grfKeyState     DWORD flags: states of keys and mouse buttons.
 *  pt              POINTL coordinates in the document client space.
 *  pdwEffect       LPDWORD into which we'll place the
 *                  appropriate effect flag for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragEnter(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    HWND        hWnd;

    /*
     * 1.  Check if we can paste from the pIDataSource we're
     *     provided. We already made this nice useful function
     *     to test.
     */

    m_pIDataObject=NULL;

    if (!m_pDoc->FQueryPasteFromData(pIDataSource))
        {
        *pdwEffect=DROPEFFECT_NONE;
        return NOERROR;
        }

    /*
     * 2.  We can always drop anywhere in our document,
     *     so pt is uninteresting.
     */

    /*
     * 3. We return either a COPY or MOVE effect flag, depending on
     *    the state of the grfKeyState flags.  We MOVE on no key or
     *    Shift key, COPY on Ctrl key.
     */

    //Default is move
    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        *pdwEffect=DROPEFFECT_COPY;

    /*
     * 4. We really don't need to keep the IDataObject around since
     *    we're not interested in it in DragOver.  However, we'll
     *    save it just for demonstration.
     */
    m_pIDataObject=pIDataSource;
    m_pIDataObject->AddRef();

    /*
     * 5.  We always accept drops of our data on us, so we only need
     *     to provide some UI feedback here which we do by inverting
     *     the edge of the polyline window in this document.  We'll
     *     remove this in DragLeave and in Drop.  DragOver won't
     *     effect it since we can always drop.
     *
     *     Since we're inverting a border, insure that this window is
     *     on top.
     */

    hWnd=m_pDoc->Window();
    BringWindowToTop(hWnd);
    UpdateWindow(hWnd);
    m_pDoc->DropSelectTargetWindow();

    return NOERROR;
    }

/*
 * CDropTarget::DragOver
 *
 * Purpose:
 *  Indicates that the mouse was moved inside the window represented
 *  by this drop target. This happens on every WM_MOUSEMOVE, so this
 *  function should be very efficient.
 *
 * Parameters:
 *  grfKeyState     DWORD providing current keyboard/mouse states
 *  pt              POINTL where the mouse currently is.
 *  pdwEffect       LPDWORD to store the effect flag for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragOver(DWORD grfKeyState, POINTL pt
    , LPDWORD pdwEffect)
    {
    if (NULL==m_pIDataObject)
        {
        *pdwEffect=DROPEFFECT_NONE;
        return NOERROR;
        }

    //We can always drop; return effect flags based on keys.
    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        *pdwEffect=DROPEFFECT_COPY;

    return NOERROR;
    }

/*
 * CDropTarget::DragLeave
 *
 * Purpose:
 *  Informs the drop target that the operation has left its window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragLeave(void)
    {
    //1.  Remove the UI feedback
   m_pDoc->DropSelectTargetWindow();

   //2.  Release the held IDataObject
   ReleaseInterface(m_pIDataObject);

    return NOERROR;
    }

/*
 * CDropTarget::Drop
 *
 * Purpose:
 *  Instructs the drop target to paste data that was just now
 *  dropped on it.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT from which we'll paste.
 *  grfKeyState     DWORD providing current keyboard/mouse state.
 *  pt              POINTL at which the drop occurred.
 *  pdwEffect       LPDWORD to store what you do with the data.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::Drop(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    BOOL        fRet=TRUE;

    *pdwEffect=DROPEFFECT_NONE;

    if (NULL==m_pIDataObject)
        return ResultFromScode(E_FAIL);

    //1.  Remove the UI feedback, release pointer
    DragLeave();

    //No point in drag-drop to ourselves (for Cosmo, at least)
    if (m_pDoc->m_fDragSource)
        return ResultFromScode(E_FAIL);

    //2.  Try a paste
    fRet=m_pDoc->PasteFromData(pIDataSource);

    //3.  Store the effect
    if (!fRet)
        return ResultFromScode(E_FAIL);

    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        *pdwEffect=DROPEFFECT_COPY;

    return NOERROR;
    }


FIGURE.CPP

/*
 * FIGURE.CPP
 * Cosmo Chapter 21
 *
 * Implementation of the CFigure object for Cosmo.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CFigure::CFigure
 * CFigure::~CFigure
 *
 * Parameters (Constructor):
 *  pfnDestroy      PFNDESTROYED to call when object is destroyed.
 *  pDoc            PCCosmoDoc we're associated with.
 */

CFigure::CFigure(PFNDESTROYED pfnDestroy, PCCosmoDoc pDoc)
    {
    m_cRef=0;
    m_pfnDestroy=pfnDestroy;

    m_pFR=NULL;     //We get this later through FrameSet.
    m_pDoc=pDoc;
    m_pPL=pDoc->m_pPL;

    m_fEmbedded=FALSE;

    //NULL any contained interfaces initially.
    m_pImpIPersistStorage=NULL;
    m_pIStorage=NULL;
    m_pIStream=NULL;
    m_pImpIDataObject=NULL;
    m_pIDataAdviseHolder=NULL;
    m_pImpIOleObject=NULL;
    m_pIOleAdviseHolder=NULL;
    m_pIOleClientSite=NULL;

    m_clsID=CLSID_CosmoFigure;
    m_cf=pDoc->m_cf;

    //These are for IDataObject::QueryGetData
    m_cfeGet=CFORMATETCGET;

    SETDefFormatEtc(m_rgfeGet[0], pDoc->m_cf, TYMED_HGLOBAL);
    SETDefFormatEtc(m_rgfeGet[1], pDoc->m_cfEmbedSource
        , TYMED_ISTORAGE);
    SETDefFormatEtc(m_rgfeGet[2], pDoc->m_cfObjectDescriptor
        , TYMED_HGLOBAL);
    SETDefFormatEtc(m_rgfeGet[3], CF_METAFILEPICT, TYMED_MFPICT);
    SETDefFormatEtc(m_rgfeGet[4], CF_BITMAP, TYMED_GDI);

    m_pST=NULL;

    //CHAPTER21MOD
    m_pImpIPersistFile=NULL;

    //We live in the document's lifetime, so no need to AddRef here.
    m_pMoniker=m_pDoc->m_pMoniker;
    m_dwRegROT=0L;
    //End CHAPTER21MOD

    return;
    }

CFigure::~CFigure(void)
    {
    //CHAPTER21MOD
    //Make sure no one thinks we're still running
    INOLE_RevokeAsRunning(&m_dwRegROT);
    //End CHAPTER21MOD

    ReleaseInterface(m_pIOleClientSite);
    ReleaseInterface(m_pIDataAdviseHolder);
    ReleaseInterface(m_pIOleAdviseHolder);
    ReleaseInterface(m_pIStorage)
    ReleaseInterface(m_pIStream)

    //CHAPTER21MOD
    DeleteInterfaceImp(m_pImpIPersistFile);
    //End CHAPTER21MOD
    DeleteInterfaceImp(m_pImpIOleObject)
    DeleteInterfaceImp(m_pImpIDataObject)
    DeleteInterfaceImp(m_pImpIPersistStorage);

    //Free strings.
    if (NULL!=m_pST)
        delete m_pST;

    return;
    }

/*
 * CFigure::QueryInterface
 * CFigure::AddRef
 * CFigure::Release
 *
 * Purpose:
 *  IUnknown members for CFigure object.
 */

STDMETHODIMP CFigure::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IPersist==riid || IID_IPersistStorage==riid)
        *ppv=m_pImpIPersistStorage;

    if (IID_IDataObject==riid)
        *ppv=m_pImpIDataObject;

    if (IID_IOleObject==riid)
        *ppv=m_pImpIOleObject;

    //CHAPTER21MOD
    if (IID_IPersistFile==riid)
        *ppv=m_pImpIPersistFile;
    //End CHAPTER21MOD

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CFigure::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CFigure::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    //Document deletes us
    return 0;
    }

/*
 * CFigure::Init
 *
 * Purpose:
 *  Performs any initialization of a CFigure that's prone to failure
 *  that we also use internally before exposing the object outside.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CFigure::Init(void)
    {
    m_pST=new CStringTable(m_pDoc->m_hInst);

    if (NULL==m_pST)
        return FALSE;

    if (!m_pST->Init(IDS_FIGUREMIN, IDS_FIGUREMAX))
        return FALSE;

    //Allocate contained interfaces.
    m_pImpIPersistStorage=new CImpIPersistStorage(this, this);

    if (NULL==m_pImpIPersistStorage)
        return FALSE;

    m_pImpIDataObject=new CImpIDataObject(this, this);

    if (NULL==m_pImpIDataObject)
        return FALSE;

    m_pImpIOleObject=new CImpIOleObject(this, this);

    if (NULL==m_pImpIOleObject)
        return FALSE;

    //CHAPTER21MOD
    m_pImpIPersistFile=new CImpIPersistFile(this, this);

    if (NULL==m_pImpIPersistFile)
        return FALSE;
    //End CHAPTER21MOD

    return TRUE;
    }

/*
 * CFigure::FrameSet
 *
 * Purpose:
 *  Provides the compound document object with access to the frame
 *  of this application for UI purposes.
 *
 * Parameters:
 *  pFR             PCCosmoFrame of the frame window.
 *
 * Return Value:
 *  None
 */

void CFigure::FrameSet(PCCosmoFrame pFR)
    {
    m_pFR=pFR;
    return;
    }

/*
 * CFigure::FIsDirty
 *
 * Purpose:
 *  Checks if the document is dirty.  This can be called from
 *  IPersistStorage::IsDirty which doesn't have access to CCosmoDoc.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if dirty, FALSE if clean.
 */

BOOL CFigure::FIsDirty(void)
    {
    return m_pDoc->m_fDirty;
    }

/*
 * CFigure::FIsEmbedded
 *
 * Purpose:
 *  Answers if the object is embedded or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the object is embedded, FALSE otherwise.
 */

BOOL CFigure::FIsEmbedded(void)
    {
    return m_fEmbedded;
    }

/*
 * CFigure::SendAdvise
 *
 * Purpose:
 *  Calls the appropriate IOleClientSite or IAdviseSink member
 *  function for various events such as closure, saving, etc.
 *
 * Parameters:
 *  uCode           UINT OBJECTCODE_* identifying the notification.
 *
 * Return Value:
 *  None
 */

void CFigure::SendAdvise(UINT uCode)
    {
    switch (uCode)
        {
        case OBJECTCODE_SAVED:
            if (NULL!=m_pIOleAdviseHolder)
                m_pIOleAdviseHolder->SendOnSave();

break;

        case OBJECTCODE_CLOSED:
            if (NULL!=m_pIOleAdviseHolder)
                m_pIOleAdviseHolder->SendOnClose();

break;

        case OBJECTCODE_RENAMED:
            //CHAPTER21MOD
            m_pMoniker=m_pDoc->m_pMoniker;  //For IOleObject::GetMoniker
            m_dwRegROT=m_pDoc->m_dwRegROT;

            if (NULL!=m_pIOleAdviseHolder)
                m_pIOleAdviseHolder->SendOnRename(m_pMoniker);
            //End CHAPTER21MOD
            break;

        case OBJECTCODE_SAVEOBJECT:
            if (FIsDirty() && NULL!=m_pIOleClientSite)
                m_pIOleClientSite->SaveObject();

            break;

        case OBJECTCODE_DATACHANGED:
            //No flags are necessary here.
            if (NULL!=m_pIDataAdviseHolder)
                {
                m_pIDataAdviseHolder->SendOnDataChange
                    (m_pImpIDataObject, 0, 0);
                }

            //CHAPTER21MOD
            //Tell the running object table of the change
            if (0!=m_dwRegROT)
                INOLE_NoteChangeTime(m_dwRegROT, NULL, NULL);
            //End CHAPTER21MOD

            break;

        case OBJECTCODE_SHOWWINDOW:
            if (NULL!=m_pIOleClientSite)
                m_pIOleClientSite->OnShowWindow(TRUE);

            break;

        case OBJECTCODE_HIDEWINDOW:
            if (NULL!=m_pIOleClientSite)
                m_pIOleClientSite->OnShowWindow(FALSE);

            break;

        case OBJECTCODE_SHOWOBJECT:
            if (NULL!=m_pIOleClientSite)
                m_pIOleClientSite->ShowObject();

            break;
        }

    return;
    }


ICLASSF.CPP

/*
 * ICLASSF.CPP
 * Cosmo Chapter 21
 *
 * Implementation of Cosmo's class factory.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CFigureClassFactory::CFigureClassFactory
 * CFigureClassFactory::~CFigureClassFactory
 *
 * Constructor Parameters:
 *  pFR             PCCosmoFrame that can create documents.
 */

CFigureClassFactory::CFigureClassFactory(PCCosmoFrame pFR)
    {
    m_cRef=0L;
    m_pFR=pFR;
    m_fCreated=FALSE;
    return;
    }

CFigureClassFactory::~CFigureClassFactory(void)
    {
    return;
    }

/*
 * CFigureClassFactory::QueryInterface
 * CFigureClassFactory::AddRef
 * CFigureClassFactory::Release
 */

STDMETHODIMP CFigureClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CFigureClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CFigureClassFactory::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CFigureClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a Figure object that supports embedding.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we are
 *                  being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise contains
 *                  E_NOINTERFACE if we cannot support the requested
 *                  interface.
 */

STDMETHODIMP CFigureClassFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCCosmoDoc          pDoc;
    HRESULT             hr;

    *ppvObj=NULL;

    //Great idea to protect yourself from multiple creates here.
    if (m_fCreated)
        return ResultFromScode(E_UNEXPECTED);

    m_fCreated=TRUE;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //We don't support aggregation
    if (NULL!=pUnkOuter)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    //Try creating a new document, which creates the object.
    pDoc=(PCCosmoDoc)m_pFR->m_pCL->NewDocument(TRUE);

    if (NULL==pDoc)
        {
        //This will cause shutdown; object count will go to zero.
        g_cObj++;
        ObjectDestroyed();
        return hr;
        }

    //Insure the document is untitled; get the requested interface.
    pDoc->Load(TRUE, NULL);
    pDoc->m_pFigure->FrameSet(m_pFR);
    hr=pDoc->m_pFigure->QueryInterface(riid, ppvObj);

    //Closing the document will destroy the objec, cause shutdown.
    if (FAILED(hr))
        {
        m_pFR->m_pCL->CloseDocument(pDoc);
        return hr;
        }

    return NOERROR;
    }

/*
 * CFigureClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the serving
 *  IClassFactory object.  When the number of locks goes to
 *  zero and the number of objects is zero, we shut down the
 *  application.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CFigureClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        {
        g_cLock--;
        g_cObj++;
        ObjectDestroyed();
        }

    return NOERROR;
    }


IDATAOBJ.CPP

/*
 * IDATAOBJ.CPP
 * Cosmo Chapter 21
 *
 * Implementation of the IDataObject interface.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CImpIDataObject::CImpIDataObject
 * CImpIDataObject::~CImpIDataObject
 *
 * Parameters (Constructor):
 *  pObj            PCFigure of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIDataObject::CImpIDataObject(PCFigure pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIDataObject::~CImpIDataObject(void)
    {
    return;
    }

/*
 * CImpIDataObject::QueryInterface
 * CImpIDataObject::AddRef
 * CImpIDataObject::Release
 */

STDMETHODIMP CImpIDataObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIDataObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIDataObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIDataObject::GetData
 *
 * Purpose:
 *  Retrieves data described by a specific FormatEtc into a StgMedium
 *  allocated by this function.  Used like GetClipboardData.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *  pSTM            LPSTGMEDIUM in which to return the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetData(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM)
    {
    UINT            cf=pFE->cfFormat;
    BOOL            fRet=FALSE;

    //Another part of us already knows if the format is good.
    if (NOERROR!=QueryGetData(pFE))
        return ResultFromScode(DATA_E_FORMATETC);

    if (CF_METAFILEPICT==cf || CF_BITMAP==cf || m_pObj->m_cf==cf)
        {
        if (CF_METAFILEPICT==cf)
            {
            pSTM->tymed=TYMED_MFPICT;
            }
        else
            pSTM->tymed=TYMED_HGLOBAL;

        pSTM->pUnkForRelease=NULL;
        pSTM->hGlobal=m_pObj->m_pDoc->RenderFormat(cf);
        fRet=(NULL!=pSTM->hGlobal);
        }
    else
        fRet=m_pObj->m_pDoc->RenderMedium(cf, pSTM);

    return fRet ? NOERROR : ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::GetDataHere
 *
 * Purpose:
 *  Renders the specific FormatEtc into caller-allocated medium
 *  provided in pSTM.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *  pSTM            LPSTGMEDIUM providing the medium into which
 *                  wer render the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetDataHere(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM)
    {
    UINT        cf;
    LONG        lRet;

    /*
     * The only reasonable time this is called is for
     * CFSTR_EMBEDSOURCE and TYMED_ISTORAGE (and later for
     * CFSTR_LINKSOURCE).  This means the same as
     * IPersistStorage::Save.
     */

    cf=RegisterClipboardFormat(CFSTR_EMBEDSOURCE);

    //Aspect is unimportant to us here, as is lindex and ptd.
    if (cf==pFE->cfFormat && (TYMED_ISTORAGE & pFE->tymed))
        {
        //We have an IStorage we can write into.
        pSTM->tymed=TYMED_ISTORAGE;
        pSTM->pUnkForRelease=NULL;
        lRet=m_pObj->m_pPL->WriteToStorage(pSTM->pstg
            , VERSIONCURRENT);

        //CHAPTER21MOD
        //These are for creating an embedded object from a file
        WriteClassStg(pSTM->pstg, CLSID_CosmoFigure);
        WriteFmtUserTypeStg(pSTM->pstg, m_pObj->m_cf
            , (*m_pObj->m_pST)[IDS_USERTYPE]);
        //End CHAPTER21MOD

        if (lRet >= 0)
            return NOERROR;

        return ResultFromScode(STG_E_WRITEFAULT);
        }

    return ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::QueryGetData
 *
 * Purpose:
 *  Tests if a call to GetData with this FormatEtc will provide
 *  any rendering; used like IsClipboardFormatAvailable.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::QueryGetData(LPFORMATETC pFE)
    {
    UINT            cf=pFE->cfFormat;
    UINT            i;

    //Check the aspects we support.
    if (!(DVASPECT_CONTENT & pFE->dwAspect))
        return ResultFromScode(S_FALSE);

    for (i=0; i < m_pObj->m_cfeGet; i++)
        {
        if (pFE->cfFormat==m_pObj->m_rgfeGet[i].cfFormat
            && pFE->tymed & m_pObj->m_rgfeGet[i].tymed)
            {
            return NOERROR;
            }
        }

    return ResultFromScode(S_FALSE);
    }

/*
 * CImpIDataObject::GetCanonicalFormatEtc
 *
 * Purpose:
 *  Provides the caller with an equivalent FormatEtc to the one
 *  provided when different FormatEtcs will produce exactly the
 *  same renderings.
 *
 * Parameters:
 *  pFEIn            LPFORMATETC of the first description.
 *  pFEOut           LPFORMATETC of the equal description.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetCanonicalFormatEtc
    (LPFORMATETC pFEIn, LPFORMATETC pFEOut)
    {
    if (NULL==pFEOut)
        return ResultFromScode(E_INVALIDARG);

    pFEOut->ptd=NULL;
    return ResultFromScode(DATA_S_SAMEFORMATETC);
    }

/*
 * CImpIDataObject::SetData
 *
 * Purpose:
 *  Places data described by a FormatEtc and living in a StgMedium
 *  into the object.  The object may be responsible to clean up the
 *  StgMedium before exiting.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the data to set.
 *  pSTM            LPSTGMEDIUM containing the data.
 *  fRelease        BOOL indicating if this function is responsible
 *                  for freeing the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::SetData(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM, BOOL fRelease)
    {
    LONG            lRet;

    /*
     * Data can only come from global memory containing a
     * POLYLINEDATA structure that we send to the Polyline's
     * DataSetMem.
     */
    if ((pFE->cfFormat!=m_pObj->m_cf)
        || !(DVASPECT_CONTENT & pFE->dwAspect)
        || (TYMED_HGLOBAL!=pSTM->tymed))
        return ResultFromScode(DATA_E_FORMATETC);

    lRet=m_pObj->m_pPL->DataSetMem(pSTM->hGlobal, FALSE, TRUE
        , TRUE);

    if (fRelease)
        ReleaseStgMedium(pSTM);

    return (POLYLINE_E_NONE==lRet) ?
        NOERROR : ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::EnumFormatEtc
 *
 * Purpose:
 *  Returns an IEnumFORMATETC object through which the caller can
 *  iterate to learn about all the data formats this object can
 *  provide through either GetData[Here] or SetData.
 *
 * Parameters:
 *  dwDir           DWORD describing a data direction, either
 *                  DATADIR_SET or DATADIR_GET.
 *  ppEnum          LPENUMFORMATETC * in which to return the
 *                  pointer to the enumerator.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::EnumFormatEtc(DWORD dwDir
    , LPENUMFORMATETC *ppEnum)
    {
    return ResultFromScode(OLE_S_USEREG);
    }

/*
 * CImpIDataObject::DAdvise
 * CImpIDataObject::DUnadvise
 * CImpIDataObject::EnumDAdvise
 *
 * Pass-throughs to IDataAdviseHolder.
 */

STDMETHODIMP CImpIDataObject::DAdvise(LPFORMATETC pFE, DWORD dwFlags
    , LPADVISESINK pIAdviseSink, LPDWORD pdwConn)
    {
    HRESULT         hr;

    if (NULL==m_pObj->m_pIDataAdviseHolder)
        {
        hr=CreateDataAdviseHolder(&m_pObj->m_pIDataAdviseHolder);

        if (FAILED(hr))
            return ResultFromScode(E_OUTOFMEMORY);
        }

    hr=m_pObj->m_pIDataAdviseHolder->Advise(this, pFE
        , dwFlags, pIAdviseSink, pdwConn);

    return hr;
    }

STDMETHODIMP CImpIDataObject::DUnadvise(DWORD dwConn)
    {
    if (NULL==m_pObj->m_pIDataAdviseHolder)
        return ResultFromScode(E_FAIL);

    return m_pObj->m_pIDataAdviseHolder->Unadvise(dwConn);
    }

STDMETHODIMP CImpIDataObject::EnumDAdvise(LPENUMSTATDATA *ppEnum)
    {
    if (NULL==m_pObj->m_pIDataAdviseHolder)
        return ResultFromScode(E_FAIL);

    return m_pObj->m_pIDataAdviseHolder->EnumAdvise(ppEnum);
    }


IOLEOBJ.CPP

/*
 * IOLEOBJ.CPP
 * Cosmo Chapter 21
 *
 * Implementation of the IOleObject interface for Polyline.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CImpIOleObject::CImpIOleObject
 * CImpIOleObject::~CImpIOleObject
 *
 * Parameters (Constructor):
 *  pObj            PCFigure of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleObject::CImpIOleObject(PCFigure pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleObject::~CImpIOleObject(void)
    {
    return;
    }

/*
 * CImpIOleObject::QueryInterface
 * CImpIOleObject::AddRef
 * CImpIOleObject::Release
 */

STDMETHODIMP CImpIOleObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleObject::SetClientSite
 *
 * Purpose:
 *  Provides the object with a pointer to the IOleClient site
 *  representing the container in which this object resides.
 *
 * Parameters:
 *  pIOleClientSite LPOLECLIENTSITE to the container's interface.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleObject::SetClientSite
    (LPOLECLIENTSITE pIOleClientSite)
    {
    if (NULL!=m_pObj->m_pIOleClientSite)
        m_pObj->m_pIOleClientSite->Release();

    m_pObj->m_pIOleClientSite=pIOleClientSite;
    m_pObj->m_pIOleClientSite->AddRef();
    return NOERROR;
    }

/*
 * CImpIOleObject::GetClientSite
 *
 * Purpose:
 *  Asks the object for the client site provided in SetClientSite.
 *  If you have not seen SetClientSite yet, return a NULL in
 *  ppIOleClientSite.
 *
 * Parameters:
 *  ppSite          LPOLECLIENTSITE * in which to store the
 *                  pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleObject::GetClientSite(LPOLECLIENTSITE
    *ppSite)
    {
    //Be sure to AddRef the new pointer you are giving away.
    *ppSite=m_pObj->m_pIOleClientSite;
    m_pObj->m_pIOleClientSite->AddRef();

    return NOERROR;
    }

/*
 * CImpIOleObject::SetHostNames
 *
 * Purpose:
 *  Provides the object with names of the container application and
 *  the object in the container to use in object user interface.
 *
 * Parameters:
 *  pszApp          LPCOLESTR of the container application.
 *  pszObj          LPCOLESTR of some name useful in window titles.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleObject::SetHostNames(LPCOLESTR pszApp
    , LPCOLESTR pszObj)
    {
    m_pObj->m_fEmbedded=TRUE;
   #ifdef WIN32ANSI
    char        szApp[80], szObj[80];

    szApp[0]=0;
    szObj[0]=0;

    if (NULL!=pszApp)
        {
        WideCharToMultiByte(CP_ACP, 0, pszApp, -1, szApp, 80
            , NULL, NULL);
        }

    if (NULL!=pszObj)
        {
        WideCharToMultiByte(CP_ACP, 0, pszObj, -1, szObj, 80
            , NULL, NULL);
        }

    m_pObj->m_pFR->UpdateEmbeddingUI(TRUE, m_pObj->m_pDoc
        , szApp, szObj);
   #else
    m_pObj->m_pFR->UpdateEmbeddingUI(TRUE, m_pObj->m_pDoc
        , pszApp, pszObj);
   #endif
    return NOERROR;
    }

/*
 * CImpIOleObject::Close
 *
 * Purpose:
 *  Forces the object to close down its user interface and unload.
 *
 * Parameters:
 *  dwSaveOption    DWORD describing the circumstances under which
 *                  the object is being saved and closed.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::Close(DWORD dwSaveOption)
    {
    HWND        hWnd;
    BOOL        fSave=FALSE;

    hWnd=m_pObj->m_pDoc->Window();

    //If object is dirty and we're asked to save, save it and close.
    if (OLECLOSE_SAVEIFDIRTY==dwSaveOption && m_pObj->FIsDirty())
        fSave=TRUE;

    /*
     * If asked to prompt, only do so if dirty, then if we get a
     * YES, save as usual and close.  On NO, just close.  On
     * CANCEL return OLE_E_PROMPTSAVECANCELLED.
     */
    if (OLECLOSE_PROMPTSAVE==dwSaveOption && m_pObj->FIsDirty())
        {
        UINT        uRet;

        uRet=MessageBox(hWnd, (*m_pObj->m_pST)[IDS_CLOSECAPTION]
            , (*m_pObj->m_pST)[IDS_CLOSEPROMPT], MB_YESNOCANCEL);

        if (IDCANCEL==uRet)
            return ResultFromScode(OLE_E_PROMPTSAVECANCELLED);

        if (IDYES==uRet)
            fSave=TRUE;
        }

    if (fSave)
        {
        m_pObj->SendAdvise(OBJECTCODE_SAVEOBJECT);
        m_pObj->SendAdvise(OBJECTCODE_SAVED);
        }

    //We get directly here on OLECLOSE_NOSAVE.
    PostMessage(hWnd, WM_CLOSE, 0, 0L);
    return NOERROR;
    }

/*
 * CImpIOleObject::SetMoniker
 *
 * Purpose:
 *  Informs the object of its moniker or its container's moniker
 *  depending on dwWhich.
 *
 * Parameters:
 *  dwWhich         DWORD describing whether the moniker is the
 *                  object's or the container's.
 *  pmk             LPMONIKER with the name.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::SetMoniker(DWORD dwWhich
    , LPMONIKER pmk)
    {
    //CHAPTER21MOD
    LPMONIKER       pmkFull;
    HRESULT         hr=ResultFromScode(E_FAIL);

    /*
     * For an embedded object we might be living in a container that
     * has given us away as a link.  This is our indication to
     * register the full moniker for this object that we obtain from
     * IOleClientSite::GetMoniker(OLEWHICHMK_FULL).
     */

    if (NULL!=m_pObj->m_pIOleClientSite)
        {
        hr=m_pObj->m_pIOleClientSite->GetMoniker
            (OLEGETMONIKER_ONLYIFTHERE, OLEWHICHMK_OBJFULL
            , &pmkFull);
        }

    if (SUCCEEDED(hr))
        {
        /*
         * If this moniker is already running then we don't
         * need to revoke and re-register the same thing again.
         */
        if (NOERROR==pmkFull->IsRunning(NULL, NULL, NULL))
            {
            pmkFull->Release();
            return NOERROR;
            }

        //This will revoke the old one if m_dwRegROT is nonzero.
        INOLE_RegisterAsRunning(m_pObj, pmkFull
            , 0, &m_pObj->m_dwRegROT);

        //Inform clients of the new moniker
        if (NULL!=m_pObj->m_pIOleAdviseHolder)
            m_pObj->m_pIOleAdviseHolder->SendOnRename(pmkFull);

        pmkFull->Release();
        }

    return hr;
    //End CHAPTER21MOD
    }

/*
 * CImpIOleObject::GetMoniker
 *
 * Purpose:
 *  Asks the object for a moniker that can later be used to
 *  reconnect to it.
 *
 * Parameters:
 *  dwAssign        DWORD determining how to assign the moniker to
 *                  to the object.
 *  dwWhich         DWORD describing which moniker the caller wants.
 *  ppmk            LPMONIKER * into which to store the moniker.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetMoniker(DWORD dwAssign
    , DWORD dwWhich, LPMONIKER *ppmk)
    {
    //CHAPTER21MOD
    HRESULT         hr=ResultFromScode(E_FAIL);

    *ppmk=NULL;

    /*
     * When we support linking we either return our file moniker if
     * we're linked, or out full moniker from the container if we're
     * embedded.
     */

    if (NULL!=m_pObj->m_pMoniker)
        {
        *ppmk=m_pObj->m_pMoniker;       //Document file moniker
        m_pObj->m_pMoniker->AddRef();
        }
    else
        {
        //Get the full container:object moniker if we're embedded
        if (NULL!=m_pObj->m_pIOleClientSite)
            {
            hr=m_pObj->m_pIOleClientSite->GetMoniker
                (OLEGETMONIKER_ONLYIFTHERE, OLEWHICHMK_OBJFULL
                , ppmk);
            }
        }

    return (NULL!=*ppmk) ? NOERROR : hr;
    //End CHAPTER21MOD
    }

/*
 * CImpIOleObject::InitFromData
 *
 * Purpose:
 *  Initializes the object from the contents of a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT containing the data.
 *  fCreation       BOOL indicating if this is part of a new
 *                  creation. If FALSE, the container is trying
 *                  to paste here.
 *  dwReserved      DWORD reserved.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::InitFromData(LPDATAOBJECT pIDataObject
    , BOOL fCreation, DWORD dwReserved)
    {
    BOOL    fRet;

    /*
     * If we get a data object here, try to paste from it.  If
     * you've written clipboard code already, this is a snap.
     * We don't really care about fCreation or not since pasting
     * in us blasts away whatever is already here.
     */
    fRet=m_pObj->m_pDoc->PasteFromData(pIDataObject);
    return fRet ? NOERROR : ResultFromScode(E_FAIL);
    }

/*
 * CImpIOleObject::GetClipboardData
 *
 * Purpose:
 *  Returns an IDataObject pointer to the caller representing what
 *  would be on the clipboard if the server did an Edit/Copy using
 *  OleSetClipboard.
 *
 * Parameters:
 *  dwReserved      DWORD reserved.
 *  ppIDataObj      LPDATAOBJECT * into which to store the
 *                  pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetClipboardData(DWORD dwReserved
    , LPDATAOBJECT *ppIDataObj)
    {
    /*
     * Again, if you have a function to create a data object for the
     * clipboard, this is a simple implementation.  The one we have
     * does all the compound document formats already.
     */
    *ppIDataObj=m_pObj->m_pDoc->TransferObjectCreate(FALSE);
    return (NULL!=*ppIDataObj) ? NOERROR : ResultFromScode(E_FAIL);
    }

/*
 * CImpIOleObject::DoVerb
 *
 * Purpose:
 *  Executes an object-defined action.
 *
 * Parameters:
 *  iVerb           LONG index of the verb to execute.
 *  pMSG            LPMSG describing the event causing the
 *                  activation.
 *  pActiveSite     LPOLECLIENTSITE to the site involved.
 *  lIndex          LONG the piece on which execution is happening.
 *  hWndParent      HWND of window in which the object can play
 *                  in-place.
 *  pRectPos        LPRECT of the object in hWndParent where the
 *                  object can play in-place if desired.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::DoVerb(LONG iVerb, LPMSG pMSG
    , LPOLECLIENTSITE pActiveSite, LONG lIndex, HWND hWndParent
    , LPCRECT pRectPos)
    {
    HWND            hWnd, hWndT;

    //Find the upper most window
    hWndT=GetParent(m_pObj->m_pDoc->Window());

    while (NULL!=hWndT)
        {
        hWnd=hWndT;
        hWndT=GetParent(hWndT);
        }

    switch (iVerb)
        {
        case OLEIVERB_HIDE:
            ShowWindow(hWnd, SW_HIDE);
            m_pObj->SendAdvise(OBJECTCODE_HIDEWINDOW);
            break;

        case OLEIVERB_PRIMARY:
        case OLEIVERB_OPEN:
        case OLEIVERB_SHOW:
            ShowWindow(hWnd, SW_SHOW);
            SetForegroundWindow(hWnd);
            SetFocus(hWnd);

            m_pObj->SendAdvise(OBJECTCODE_SHOWOBJECT);
            m_pObj->SendAdvise(OBJECTCODE_SHOWWINDOW);
            break;

        default:
            return ResultFromScode(OLEOBJ_S_INVALIDVERB);
        }

    return NOERROR;
    }

/*
 * CImpIOleObject::EnumVerbs
 *
 * Purpose:
 *  Creates an enumerator that knows the object's verbs.  If you
 *  need to change the verb list dynamically, then you'll need to
 *  implement this, otherwise you can return OLE_S_USEREG.
 *
 * Parameters:
 *  ppEnum          LPENUMOLEVERB * into which to return the
 *                  enum.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::EnumVerbs(LPENUMOLEVERB *ppEnum)
    {
    //Trivial implementation if you fill the regDB.
    return ResultFromScode(OLE_S_USEREG);
    }

/*
 * CImpIOleObject::Update
 *
 * Purpose:
 *  Insures that the object is up to date.  This is mostly used for
 *  caching but you must make sure that you recursively call all
 *  nested objects you contain as well.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::Update(void)
    {
    //We're always updated since we don't contain.
    return NOERROR;
    }

/*
 * CImpIOleObject::IsUpToDate
 *
 * Purpose:
 *  Returns if the object is currently up to date, which involves
 *  asking all contained object inside this object if they are up
 *  to date as well.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE if dirty.
 */

STDMETHODIMP CImpIOleObject::IsUpToDate(void)
    {
    //We're always updated since we don't contain.
    return NOERROR;
    }

/*
 * CImpIOleObject::GetUserClassID
 *
 * Purpose:
 *  Used for linked objects, this returns the class ID of what end
 *  users think they are editing.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store the CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetUserClassID(LPCLSID pClsID)
    {
    /*
     * If you are not registered to handle data other than yourself,
     * then you can just return your class ID here.  If you are
     * registered as usable from Treat-As dialogs, then you need
     * to return the CLSID of what you are really editing.
     */

    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

/*
 * CImpIOleObject::GetUserType
 *
 * Purpose:
 *  Determines the user-presentable name of the object.
 *
 * Parameters:
 *  dwForm          DWORD describing which form of the string is
 *                  desired.
 *  pszType         LPOLESTR * into which to return the pointer to
 *                  the type string.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetUserType(DWORD dwForm
    , LPOLESTR *ppszType)
    {
    return ResultFromScode(OLE_S_USEREG);
    }

/*
 * CImpIOleObject::SetExtent
 *
 * Purpose:
 *  Sets the size of the object in HIMETRIC units.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect affected.
 *  pszl            LPSIZEL containing the new size.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::SetExtent(DWORD dwAspect, LPSIZEL pszl)
    {
    RECT            rc;
    SIZEL           szl;

    if (!(DVASPECT_CONTENT & dwAspect))
        return ResultFromScode(E_FAIL);

    XformSizeInHimetricToPixels(NULL, pszl, &szl);

    //This resizes the window to match the container's size.
    SetRect(&rc, 0, 0, (int)szl.cx, (int)szl.cy);
    m_pObj->m_pPL->SizeSet(&rc, TRUE);

    return NOERROR;
    }

/*
 * CImpIOleObject::GetExtent
 *
 * Purpose:
 *  Retrieves the size of the object in HIMETRIC units.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect requested
 *  pszl            LPSIZEL into which to store the size.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetExtent(DWORD dwAspect, LPSIZEL pszl)
    {
    RECT            rc;
    SIZEL           szl;

    if (!(DVASPECT_CONTENT & dwAspect))
        return ResultFromScode(E_FAIL);

    m_pObj->m_pPL->RectGet(&rc);
    szl.cx=rc.right-rc.left;
    szl.cy=rc.bottom-rc.top;

    XformSizeInPixelsToHimetric(NULL, &szl, pszl);
    return NOERROR;
    }

/*
 * CImpIOleObject::Advise
 *
 * Purpose:
 *  Provides an IAdviseSink to the object for notifications.
 *
 * Parameters:
 *  pIAdviseSink    LPADVISESINK to notify.
 *  pdwConn         LPDWORD into which to store a connection key.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::Advise(LPADVISESINK pIAdviseSink
    , LPDWORD pdwConn)
    {
    if (NULL==m_pObj->m_pIOleAdviseHolder)
        {
        HRESULT     hr;

        hr=CreateOleAdviseHolder(&m_pObj->m_pIOleAdviseHolder);

        if (FAILED(hr))
            return hr;
        }

    return m_pObj->m_pIOleAdviseHolder->Advise(pIAdviseSink
        , pdwConn);
    }

/*
 * CImpIOleObject::Unadvise
 *
 * Purpose:
 *  Terminates a previous advise connection from Advise.
 *
 * Parameters:
 *  dwConn          DWORD connection key from Advise.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::Unadvise(DWORD dwConn)
    {
    if (NULL!=m_pObj->m_pIOleAdviseHolder)
        return m_pObj->m_pIOleAdviseHolder->Unadvise(dwConn);

    return ResultFromScode(E_FAIL);
    }

/*
 * CImpIOleObject::EnumAdvise
 *
 * Purpose:
 *  Creates and returns a enumeration of the advises on this object.
 *
 * Parameters:
 *  ppEnum          LPENUMSTATDATA * in which to return the
 *                  enumerator.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::EnumAdvise(LPENUMSTATDATA *ppEnum)
    {
    if (NULL!=m_pObj->m_pIOleAdviseHolder)
        return m_pObj->m_pIOleAdviseHolder->EnumAdvise(ppEnum);

    return ResultFromScode(E_FAIL);
    }

/*
 * CImpIOleObject::GetMiscStatus
 *
 * Purpose:
 *  Returns a set of miscellaneous status flags for the object.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect in question.
 *  pdwStatus       LPDWORD in which to store the flags.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetMiscStatus(DWORD dwAspect
    , LPDWORD pdwStatus)
    {
    return ResultFromScode(OLE_S_USEREG);
    }

/*
 * CImpIOleObject::SetColorScheme
 *
 * Purpose:
 *  Provides the object with the color palette as recommended by
 *  the container application that also knows the palettes of other
 *  objects.  The object here is not required to use these colors.
 *
 * Parameters:
 *  pLP             LPLOGPALETTE providing the colors.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::SetColorScheme(LPLOGPALETTE pLP)
    {
    return ResultFromScode(E_NOTIMPL);
    }


IPERFILE.CPP

/*
 * IPERFILE.CPP
 * Cosmo Chapter 21
 *
 * Implementation of the IPersistFile interface for Cosmo
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CImpIPersistFile:CImpIPersistFile
 * CImpIPersistFile::~CImpIPersistFile
 *
 * Constructor Parameters:
 *  pObj            PCFigure associated with this object.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistFile::CImpIPersistFile(PCFigure pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPersistFile::~CImpIPersistFile(void)
    {
    return;
    }

/*
 * CImpIPersistFile::QueryInterface
 * CImpIPersistFile::AddRef
 * CImpIPersistFile::Release
 */

STDMETHODIMP CImpIPersistFile::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistFile::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistFile::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistFile::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the file represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::GetClassID(LPCLSID pClsID)
    {
    *pClsID=CLSID_CosmoFigure;
    return NOERROR;
    }

/*
 * CImpIPersistFile::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this file since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 */

STDMETHODIMP CImpIPersistFile::IsDirty(void)
    {
    return ResultFromScode(m_pObj->FIsDirty() ? S_OK : S_FALSE);
    }

/*
 * IPersistFile::Load
 *
 * Purpose:
 *  Asks the server to load the document for the given filename.
 *
 * Parameters:
 *  pszFile         LPCOLESTR to the filename to load.
 *  grfMode         DWORD containing open flags requested from the
 *                  caller.  Currently these are safely ignored.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::Load(LPCOLESTR pszFile, DWORD grfMode)
    {
    UINT        uRet;

   #ifdef WIN32ANSI
    TCHAR       szTemp[CCHPATHMAX];
    WideCharToMultiByte(CP_ACP, 0, pszFile, -1, szTemp, CCHPATHMAX
        , NULL, NULL);
    uRet=m_pObj->m_pDoc->Load(TRUE, (LPTSTR)szTemp);
   #else
    uRet=m_pObj->m_pDoc->Load(TRUE, (LPTSTR)pszFile);
   #endif
    return (DOCERR_NONE==uRet) ? NOERROR
        : ResultFromScode(STG_E_READFAULT);
    }

/*
 * IPersistFile::Save
 *
 * Purpose:
 * Purpose:
 *  Instructs the server to write the current file into a new
 *  filename, possibly then using that filename as the current one.
 *
 * Parameters:
 *  pszFile         LPCOLESTR of the file into which we save.  If NULL,
 *                  this means save the current file.
 *  fRemember       BOOL indicating if we're to use this filename as
 *                  the current file now (Save As instead of Save
 *                  Copy As).
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::Save(LPCOLESTR pszFile, BOOL fRemember)
    {
    UINT        uRet;
    BOOL        fTemp;

    /*
     * We set CFigure::m_fEmbedding here to TRUE if we don't want to
     * remember this file, as that cons Save into ignoring this
     * filename.  This is not something you have to do in your own
     * code the same way, but be sure to pay attention to fRemember.
     */
    fTemp=m_pObj->m_fEmbedded;
    m_pObj->m_fEmbedded=!fRemember;
   #ifdef WIN32ANSI
    char        szTemp[CCHPATHMAX];

    WideCharToMultiByte(CP_ACP, 0, pszFile, -1, szTemp, CCHPATHMAX
       , NULL, NULL);
    uRet=m_pObj->m_pDoc->Save(0, szTemp);
   #else
    uRet=m_pObj->m_pDoc->Save(0, (LPTSTR)pszFile);
   #endif

    m_pObj->m_fEmbedded=fTemp;

    return (DOCERR_NONE==uRet) ? NOERROR
        : ResultFromScode(STG_E_WRITEFAULT);
    }

/*
 * IPersistFile::SaveCompleted
 *
 * Purpose:
 *  Informs us that the operation that called Save is now finished
 *  and we can access the file again.
 *
 * Parameters:
 *  pszFile         LPCOLESTR of the file in which we can start
 *                  writing again.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::SaveCompleted(LPCOLESTR pszFile)
    {
    return NOERROR;
    }

/*
 * IPersistFile::GetCurFile
 *
 * Purpose:
 *  Retrieves the name of the current file.
 *
 * Parameters:
 *  ppszFile        LPOLESTR * into which we store a pointer to
 *                  the filename that should be allocated with the
 *                  shared IMalloc.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::GetCurFile(LPOLESTR *ppszFile)
    {
    LPMALLOC    pIMalloc;
    LPOLESTR    psz;
    UINT        uRet;

    *ppszFile=NULL;

    if (FAILED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        return ResultFromScode(E_FAIL);

    psz=(LPOLESTR)pIMalloc->Alloc(CCHPATHMAX*sizeof(OLECHAR));
    pIMalloc->Release();

   #ifdef WIN32ANSI
    char    szTemp[CCHPATHMAX];

    uRet=m_pObj->m_pDoc->FilenameGet(szTemp, CCHPATHMAX);
    MultiByteToWideChar(CP_ACP, 0, szTemp, -1, psz, CCHPATHMAX);
   #else
    uRet=m_pObj->m_pDoc->FilenameGet(psz, CCHPATHMAX);
   #endif

    //If we have no filename, return the prompt for File Open/Save.
    if (0==uRet)
       #ifdef WIN32ANSI
        MultiByteToWideChar(CP_ACP, 0
            , (*m_pObj->m_pST)[IDS_EXTENSION], -1, psz, CCHPATHMAX);
       #else
        lstrcpy(psz, (*m_pObj->m_pST)[IDS_EXTENSION]);
       #endif

    *ppszFile=psz;
    return (0==uRet) ? ResultFromScode(S_FALSE) : NOERROR;
    }


IPERSTOR.CPP

/*
 * IPERSTOR.CPP
 * Cosmo Chapter 21
 *
 * Implementation of the IPersistStorage interface that we expose on
 * the CFigure compound document object.  This ties into the
 * functionality of CPolyline.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CImpIPersistStorage:CImpIPersistStorage
 * CImpIPersistStorage::~CImpIPersistStorage
 *
 * Constructor Parameters:
 *  pObj            PCFigure associated with this object.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistStorage::CImpIPersistStorage(PCFigure pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    m_psState=PSSTATE_UNINIT;

    m_fConvert=FALSE;
    return;
    }

CImpIPersistStorage::~CImpIPersistStorage(void)
    {
    return;
    }

/*
 * CImpIPersistStorage::QueryInterface
 * CImpIPersistStorage::AddRef
 * CImpIPersistStorage::Release
 */

STDMETHODIMP CImpIPersistStorage::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistStorage::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistStorage::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistStorage::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the object represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::GetClassID(LPCLSID pClsID)
    {
    if (PSSTATE_UNINIT==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this object since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 */

STDMETHODIMP CImpIPersistStorage::IsDirty(void)
    {
    if (PSSTATE_UNINIT==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    //CFigure::FIsDirty returns the document's dirty flag.
    return ResultFromScode(m_pObj->FIsDirty() ? S_OK : S_FALSE);
    }

/*
 * CImpIPersistStorage::InitNew
 *
 * Purpose:
 *  Provides the object with the IStorage to hold on to while the
 *  object is running.  Here we initialize the structure of the
 *  storage and AddRef it for incremental access. This function will
 *  only be called once in the object's lifetime in lieu of Load.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE for the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::InitNew(LPSTORAGE pIStorage)
    {
    HRESULT     hr;

    if (PSSTATE_UNINIT!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    if (NULL==pIStorage)
        return ResultFromScode(E_POINTER);

    /*
     * The rules of IPersistStorage mean we hold onto the IStorage
     * and pre-create anything we'd need in Save(...,TRUE) for
     * low-memory situations.  For us this means creating our
     * "CONTENTS" stream and holding onto that IStream as
     * well as the IStorage here (requiring an AddRef call).
     */

    hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
        | STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE
        , 0, 0, &m_pObj->m_pIStream);

    if (FAILED(hr))
        return hr;

    //We expect that the client has called WriteClassStg
    WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
        , (*m_pObj->m_pST)[IDS_USERTYPE]);

    pIStorage->AddRef();
    m_pObj->m_pIStorage=pIStorage;

    m_psState=PSSTATE_SCRIBBLE;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::Load
 *
 * Purpose:
 *  Instructs the object to load itself from a previously saved
 *  IStorage that was handled by Save in another object lifetime.
 *  This function will only be called once in the object's lifetime
 *  in lieu of InitNew. The object should hold on to pIStorage here
 *  for incremental access and low-memory saves in Save.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE from which to load.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::Load(LPSTORAGE pIStorage)
    {
    HRESULT     hr;
    LONG        lRet;
    LPSTREAM    pIStream;

    if (PSSTATE_UNINIT!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    if (NULL==pIStorage)
        return ResultFromScode(E_POINTER);

    //This tells us if we're coming from another class storage.
    m_fConvert=(NOERROR==GetConvertStg(pIStorage));

    //This is the type of storage we're really messing with in Treat As
    ReadClassStg(pIStorage, &m_pObj->m_clsID);

    hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
        | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    //We might be looking for OLE 1 streams as well.
    if (FAILED(hr))
        {
        hr=pIStorage->OpenStream(SZOLE1STREAM, 0, STGM_DIRECT
            | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

        if (FAILED(hr))
            return ResultFromScode(STG_E_READFAULT);

        m_pObj->m_pPL->m_fReadFromOLE10=TRUE;
        }

    if (FAILED(hr))
        return ResultFromScode(STG_E_READFAULT);

    lRet=m_pObj->m_pPL->ReadFromStream(pIStream);

    if (lRet < 0)
        return ResultFromScode(STG_E_READFAULT);

    /*
     * We don't call pIStream->Release here because we may need
     * it for a low-memory save in Save.  We also need to
     * hold onto a copy of pIStorage, meaning AddRef.
     */
    m_pObj->m_pIStream=pIStream;

    pIStorage->AddRef();
    m_pObj->m_pIStorage=pIStorage;

    m_psState=PSSTATE_SCRIBBLE;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::Save
 *
 * Purpose:
 * Purpose:
 *  Saves the data for this object to an IStorage which may
 *  or may not be the same as the one previously passed to
 *  Load, indicated with fSameAsLoad.  After this call we may
 *  not write into the storage again until SaveCompleted is
 *  called, although we may still read.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which to save our data.
 *  fSameAsLoad     BOOL indicating if this is the same pIStorage
 *                  that was passed to Load.  If TRUE, then the
 *                  object should write whatever it has *without
 *                  *using any extra memory* as this may be a low
 *                  memory save attempt.  That means that you must
 *                  not try to open or create streams.  If FALSE
 *                  you need to regenerate your whole storage
 *                  structure, being sure to also release any
 *                  pointers held from InitNew and Load.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::Save(LPSTORAGE pIStorage
    , BOOL fSameAsLoad)
    {
    LONG        lRet;
    HRESULT     hr;
    LPSTREAM    pIStream;
    LONG        lVer=VERSIONCURRENT;

    //Have to come here from scribble state.
    if (PSSTATE_SCRIBBLE!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    //Must have an IStorage if we're not in SameAsLoad
    if (NULL==pIStorage && !fSameAsLoad)
        return ResultFromScode(E_POINTER);

    /*
     * If this was read from an OLE 1.0 storage, but there is no
     * convert bit, then we have to save the 1.0 format to the
     * "\1Ole10Native" stream.  Otherwise if we were converting
     * from OLE 1.0, we should nuke the stream since it's no longer
     * useful.  To handle this, we call WriteToStorage with
     * VERSIONCURRENT in any convert case.  WriteToStorage will
     * remove the OLE 1.0 stream if it previously read from one, or
     * it will just save normally.
     *
     * The Polyine allows us to look at it's m_fReadFromOLE10 which
     * tells us to pass it 0x00010000 if we're not converting, that
     * is, we're doing Treat As on the OLE 1.0 object and so we
     * need to write the new data in the Ole10Native stream.
     */

    if (!m_fConvert && m_pObj->m_pPL->m_fReadFromOLE10)
        lVer=0x00010000;

    /*
     * If we're saving to a new storage, create a new stream.
     * If fSameAsLoad it TRUE, then we write to the
     * stream we already allocated.  We should NOT depends on
     * pIStorage with fSameAsLoad is TRUE.
     *
     * If we're converting an OLE 1 storage to an OLE 2 storage,
     * then we have to create a new stream (conversion is not
     * guaranteed to succeed in low memory) and delete the old
     * one, so we ignore fSameAsLoad if we're converting.
     */

    if (fSameAsLoad
        && !(m_pObj->m_pPL->m_fReadFromOLE10 && m_fConvert))
        {
        LARGE_INTEGER   li;

        /*
         * Use pre-allocated streams to avoid failures due
         * to low-memory conditions.  Be sure to reset the
         * stream pointer if you used this stream before!!
         */
        pIStream=m_pObj->m_pIStream;
        LISet32(li, 0);
        pIStream->Seek(li, STREAM_SEEK_SET, NULL);

        //This matches the Release below.
        pIStream->AddRef();
        }
    else
        {
        hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
            | STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE
            , 0, 0, &pIStream);

        if (FAILED(hr))
            return hr;

        WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
            , (*m_pObj->m_pST)[IDS_USERTYPE]);
        }

    lRet=m_pObj->m_pPL->WriteToStream(pIStream, lVer);
    pIStream->Release();

    /*
     * If we are overwriting an OLE 1 storage, delete the old
     * Ole10Native stream if writing our CONTENTS worked.
     */
    if (m_pObj->m_pPL->m_fReadFromOLE10 && m_fConvert && (lRet >= 0))
        pIStorage->DestroyElement(SZOLE1STREAM);

    //Clear the convert bit if it was set
    if (m_fConvert)
        {
        UINT        cf;

        cf=RegisterClipboardFormat((*m_pObj->m_pST)[IDS_FORMAT]);
        WriteFmtUserTypeStg(pIStorage, cf
            , (*m_pObj->m_pST)[IDS_USERTYPE]);

        SetConvertStg(pIStorage, FALSE);
        m_fConvert=FALSE;
        }

    if (lRet >= 0)
        {
        m_psState=PSSTATE_ZOMBIE;
        return NOERROR;
        }

    return ResultFromScode(STG_E_WRITEFAULT);
    }

/*
 * CImpIPersistStorage::SaveCompleted
 *
 * Purpose:
 *  Notifies the object that the storage in pIStorage has been
 *  completely saved now.  This is called when the user of this
 *  object wants to save us in a completely new storage, and if
 *  we normally hang on to the storage we have to reinitialize
 *  ourselves here for this new one that is now complete.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE of the new storage in which we live.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::SaveCompleted(LPSTORAGE pIStorage)
    {
    HRESULT     hr;
    LPSTREAM    pIStream;

    //Must be called in no-scribble or hands-off state
    if (!(PSSTATE_ZOMBIE==m_psState || PSSTATE_HANDSOFF==m_psState))
        return ResultFromScode(E_UNEXPECTED);

    //If we're coming from Hands-Off, we'd better get a storage
    if (NULL==pIStorage && PSSTATE_HANDSOFF==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    /*
     * If pIStorage is NULL, then we don't need to do anything
     * since we already have all the pointers we need for Save.
     * Otherwise we have to release any held pointers and
     * reinitialize them from pIStorage.
     */

    if (NULL!=pIStorage)
        {
        hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
            | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0
            , &pIStream);

        if (FAILED(hr))
            return hr;

        if (NULL!=m_pObj->m_pIStream)
            m_pObj->m_pIStream->Release();

        m_pObj->m_pIStream=pIStream;

        if (NULL!=m_pObj->m_pIStorage)
            m_pObj->m_pIStorage->Release();

        m_pObj->m_pIStorage=pIStorage;
        m_pObj->m_pIStorage->AddRef();
        }

    m_pObj->SendAdvise(OBJECTCODE_SAVED);
    m_psState=PSSTATE_SCRIBBLE;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::HandsOffStorage
 *
 * Purpose:
 * Purpose:
 *  Instructs the object that another agent is interested in having
 *  total access to the storage we might be hanging on to from
 *  InitNew or SaveCompleted.  In this case we must release our hold
 *  and await another call to SaveCompleted before we have a hold
 *  again.  Therefore we cannot read or write after this call until
 *  SaveCompleted.
 *
 *  Situations where this might happen arise in compound document
 *  scenarios where this object might be in-place active but the
 *  application wants to rename and commit the root storage.
 *  Therefore we are asked to close our hold, let the container
 *  party on the storage, then call us again later to tell us the
 *  new storage we can hold.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::HandsOffStorage(void)
    {
    /*
     * Must come from scribble or no-scribble.  A repeated call
     * to HandsOffStorage is an unexpected error (bug in client).
     */
    if (PSSTATE_UNINIT==m_psState || PSSTATE_HANDSOFF==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    //Release held pointers
    if (NULL!=m_pObj->m_pIStream)
        {
        m_pObj->m_pIStream->Release();
        m_pObj->m_pIStream=NULL;
        }

    if (NULL!=m_pObj->m_pIStorage)
        {
        m_pObj->m_pIStorage->Release();
        m_pObj->m_pIStorage=NULL;
        }

    m_psState=PSSTATE_HANDSOFF;
    return NOERROR;
    }


POLYLINE.CPP

/*
 * POLYLINE.CPP
 * Cosmo Chapter 21
 *
 * Implementation of the CPolyline class.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CPolyline:CPolyline
 * CPolyline::~CPolyline
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application we're in.
 */

CPolyline::CPolyline(HINSTANCE hInst)
    : CWindow(hInst)
    {
    m_pAdv=NULL;
    m_hWnd=NULL;
    m_fReadFromOLE10=FALSE;
    return;
    }

CPolyline::~CPolyline(void)
    {
    return;
    }

/*
 * CPolyline::Init
 *
 * Purpose:
 *  Instantiates a polyline window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags
 *  uID             UINT ID to associate with this window
 *  pAdv            PCPolylineAdviseSink of the sink wanting our
 *                  notifications.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPolyline::Init(HWND hWndParent, LPRECT pRect, DWORD dwStyle
    , UINT uID, PCPolylineAdviseSink pAdv)
    {
    m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY, SZCLASSPOLYLINE
        , SZCLASSPOLYLINE, dwStyle, pRect->left, pRect->top
        , pRect->right-pRect->left, pRect->bottom-pRect->top
        , hWndParent, (HMENU)uID, m_hInst, this);

    if (NULL!=m_hWnd)
        m_pAdv=pAdv;

    return (NULL!=m_hWnd);
    }

/*
 * CPolyline::New
 *
 * Purpose:
 *  Cleans out and reinitializes the data to defaults.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolyline::New(void)
    {
    UINT        i;
    RECT        rc;

    m_pl.wVerMaj=VERSIONMAJOR;
    m_pl.wVerMin=VERSIONMINOR;

    //Our rectangle is the size of our window's client area.
    GetClientRect(m_hWnd, &rc);
    RECTTORECTS(rc, m_pl.rc);

    //Clean out the POLYLINE structure and repaint the window.
    for (i=0; i< CPOLYLINEPOINTS; i++)
        {
        m_pl.rgpt[i].x=0;
        m_pl.rgpt[i].y=0;
        }

    m_pl.cPoints      =0;
    m_pl.rgbBackground=GetSysColor(COLOR_WINDOW);
    m_pl.rgbLine      =GetSysColor(COLOR_WINDOWTEXT);
    m_pl.iLineStyle   =PS_SOLID;

    InvalidateRect(m_hWnd, NULL, TRUE);
    UpdateWindow(m_hWnd);

    //Inform the advise sink of this data change.
    if (NULL!=m_pAdv)
        m_pAdv->OnDataChange();

    return;
    }

/*
 * CPolyline::Undo
 *
 * Purpose:
 *  Reverses previous actions in a Polyline.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if we can Undo more, FALSE otherwise.
 */

BOOL CPolyline::Undo(void)
    {
    //Decrement the number of active points and repaint.
    if (m_pl.cPoints > 0)
        {
        m_pl.cPoints--;
        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    if (NULL!=m_pAdv)
        m_pAdv->OnPointChange();

    //Return if we can undo any more.
    return (0!=m_pl.cPoints);
    }

/*
 * CPolyline::ReadFromStorage
 *
 * Purpose:
 *  Loads our data (any known version) from an IStorage returning
 *  the version number of the data or an error value.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE from which we'll read.
 *
 * Return Value:
 *  LONG            Version number or negative POLYLINE_E_* value.
 */

LONG CPolyline::ReadFromStorage(LPSTORAGE pIStorage)
    {
    LONG            lRet;
    LPSTREAM        pIStream;
    HRESULT         hr;

    if (NULL==pIStorage)
        return POLYLINE_E_READFAILURE;

    //Open the CONTENTS stream
    hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT | STGM_READ
        | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return POLYLINE_E_READFAILURE;

    /*
     * Since we had to create ReadFromStream for IPersistStorage,
     * use it ourselves.
     */
    lRet=ReadFromStream(pIStream);
    pIStream->Release();

    return lRet;
    }

/*
 * CPolyline::WriteToStorage
 *
 * Purpose:
 *  Ignorantly writes our current data into a storage in a
 *  particular version.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which to save.
 *  lVer            LONG providing version number Major (HI) and
 *                  Minor (Low)
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::WriteToStorage(LPSTORAGE pIStorage, LONG lVer)
    {
    LONG            lRet;
    LPSTREAM        pIStream;
    HRESULT         hr;

    if (NULL==pIStorage)
        return POLYLINE_E_READFAILURE;

    hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT | STGM_CREATE
        | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pIStream);

    if (FAILED(hr))
        return POLYLINE_E_WRITEFAILURE;

    /*
     * Use our function that will save to a stream now that
     * we needed it for IPersistStorage.
     */

    lRet=WriteToStream(pIStream, lVer);
    pIStream->Release();

    return lRet;
    }

/*
 * CPolyline::ReadFromStream
 *
 * Purpose:
 *  Loads our data (any known version) from an IStream that we
 *  do not open nor maintain.  This is for the compound document
 *  object's implementation of IPersistStorage.
 *
 * Parameters:
 *  pIStream        LPSTREAM from which we'll read.
 *
 * Return Value:
 *  LONG            Version number or negative POLYLINE_E_* value.
 */

LONG CPolyline::ReadFromStream(LPSTREAM pIStream)
    {
    POLYLINEDATA    pl;
    ULONG           cb=(ULONG)-1;
    ULONG           cbExpect=0;
    HRESULT         hr;
    LARGE_INTEGER   li;

    if (NULL==pIStream)
        return POLYLINE_E_READFAILURE;

    /*
     * If IPersistStorage::Load failed to find CONTENTS but did
     * open "\1Ole10Native" then m_fReadFromOLE10 is set.  So
     * we have to start reading past the leading DWORD.
     */

    if (m_fReadFromOLE10)
        {
        DWORD       dw;

        /*
         * Skip the DWORD length at the beginning of the
         * Ole10Native stream.
         */
        pIStream->Read(&dw, sizeof(DWORD), &cb);
        }

    //Read version numbers and seek back to file beginning.
    hr=pIStream->Read(&pl, 2*sizeof(WORD), &cb);

    //If we read OLE 1, seek back but skip the size header.
    LISet32(li, m_fReadFromOLE10 ? sizeof(DWORD) : 0);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);

    if (FAILED(hr) || 2*sizeof(WORD)!=cb)
        {
        pIStream->Release();
        return POLYLINE_E_READFAILURE;
        }

    /*
     * For version 2.0, read the entire file.  For version 1.0 read
     * the file up to CBPOLYLINEDATAVER10.  For anything else, give
     * an error.
     */

    switch (pl.wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (pl.wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (pl.wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    hr=pIStream->Read(&pl, cbExpect, &cb);

    if (cbExpect!=cb)
        return POLYLINE_E_READFAILURE;

    /*
     * If we loaded successfully, make the data current.  By using
     * DataSet we centralize our version upgrading.  We size the
     * polyline window to the data AND notify the document so it
     * sizes to the polyline.
     */
    DataSet(&pl, TRUE, TRUE);

    //Return what version we just loaded.
    return MAKELONG(pl.wVerMin, pl.wVerMaj);
    }

/*
 * CPolyline::WriteToStream
 *
 * Purpose:
 *  Ignorantly writes our current data into a stream in a
 *  particular version.
 *
 * Parameters:
 *  pIStream        LPSTREAM in which to save.
 *  lVer            LONG providing version number Major (HI) and
 *                  Minor (Low)
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::WriteToStream(LPSTREAM pIStream, LONG lVer)
    {
    ULONG           cb;
    ULONG           cbExpect=0;
    WORD            wVerMaj=HIWORD(lVer);
    WORD            wVerMin=LOWORD(lVer);
    POLYLINEDATA    pl;
    HRESULT         hr;

    if (NULL==pIStream)
        return POLYLINE_E_READFAILURE;

    //Get a copy of our data in the version we're going to save.
    DataGet(&pl, lVer);

    switch (wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    /*
     * If writing to an OLE 1 storage, include the length of
     * data as the first DWORD in the stream.
     */

     if (m_fReadFromOLE10 && CBPOLYLINEDATA10==cbExpect)
        {
        hr=pIStream->Write(&cbExpect, sizeof(DWORD), &cb);

        if (FAILED(hr) || sizeof(DWORD)!=cb)
            return POLYLINE_E_WRITEFAILURE;
        }

    hr=pIStream->Write(&pl, cbExpect, &cb);

    if (FAILED(hr) || cbExpect!=cb)
        return POLYLINE_E_WRITEFAILURE;

    return POLYLINE_E_NONE;
    }

/*
 * CPolyline::ReadFromFile
 *
 * Purpose:
 *  Loads our data (any known version) from a file handle returning
 *  the version number of the data or an error value.
 *
 * Parameters:
 *  pszFile         LPTSTR of the file to open.
 *
 * Return Value:
 *  LONG            Version number or negative POLYLINE_E_* value.
 */

LONG CPolyline::ReadFromFile(LPTSTR pszFile)
    {
    OFSTRUCT        of;
    HFILE           hFile;
    POLYLINEDATA    pl;
    UINT            cb=(UINT)-1;
    UINT            cbExpect=0;

    if (NULL==pszFile)
        return POLYLINE_E_READFAILURE;

    //OpenFileW is a member of CPolyline.
    hFile=OpenFileW(pszFile, &of, OF_READ);

    if (HFILE_ERROR==hFile)
        return POLYLINE_E_READFAILURE;

    //Read version numbers and seek back to file beginning.
    cb=_lread(hFile, &pl, 2*sizeof(WORD));
    _llseek(hFile, 0L, 0);

    if (2*sizeof(WORD)!=cb)
        {
        _lclose(hFile);
        return POLYLINE_E_READFAILURE;
        }

    /*
     * For version 2.0, read the entire file.  For version 1.0 read
     * the file up to CBPOLYLINEDATAVER10.  For anything else, give
     * an error.
     */

    switch (pl.wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (pl.wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (pl.wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        {
        _lclose(hFile);
        return POLYLINE_E_UNSUPPORTEDVERSION;
        }

    cb=_lread(hFile, &pl, cbExpect);
    _lclose(hFile);

    if (cbExpect!=cb)
        return POLYLINE_E_READFAILURE;

    /*
     * If we loaded successfully, make the data current.  By using
     * DataSet we centralize our version upgrading.  We size the
     * polyline window to the data AND notify the document so it
     * sizes to the polyline.
     */
    DataSet(&pl, TRUE, TRUE);

    //Return what version we just loaded.
    return MAKELONG(pl.wVerMin, pl.wVerMaj);
    }

/*
 * CPolyline::WriteToFile
 *
 * Purpose:
 *  Ignorantly writes our current data into an opened file in a
 *  particular version.
 *
 * Parameters:
 *  pszFile         LPTSTR filename in which to store the data.
 *  lVer            LONG providing version number Major (HI)
 *                  and Minor (Low)
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::WriteToFile(LPTSTR pszFile, LONG lVer)
    {
    OFSTRUCT        of;
    HFILE           hFile;
    UINT            cb;
    UINT            cbExpect=0;
    WORD            wVerMaj=HIWORD(lVer);
    WORD            wVerMin=LOWORD(lVer);
    POLYLINEDATA    pl;

    if (NULL==pszFile)
        return POLYLINE_E_READFAILURE;

    //Get a copy of our data in the version we're going to save.
    DataGet(&pl, lVer);

    switch (wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    hFile=OpenFileW(pszFile, &of, OF_CREATE | OF_WRITE);

    if (HFILE_ERROR==hFile)
        return DOCERR_COULDNOTOPEN;

    cb=_lwrite(hFile, (LPCSTR)&pl, cbExpect);
    _lclose(hFile);

    return (cbExpect==cb) ? POLYLINE_E_NONE
        : POLYLINE_E_WRITEFAILURE;
    }

/*
 * CPolyline::OpenFileW (Private)
 *
 * Purpose:
 *  Under Win32, OpenFile does not take Unicode strings.  This
 *  function converts a Unicode string into an ANSI string and
 *  calls OpenFile.  This just maps to OpenFile without Unicode.
 *
 * Parameters, Return Value:
 *  Same as OpenFile.
 */

HFILE CPolyline::OpenFileW(LPTSTR pszFile, LPOFSTRUCT pof
    , UINT uFlags)
    {
   #ifdef UNICODE
    CHAR        szTemp[CCHPATHMAX];

    UNICODETOANSI(pszFile, szTemp, CCHPATHMAX);
    return OpenFile(szTemp, pof, uFlags);
   #else
    return OpenFile(pszFile, pof, uFlags);
   #endif
    }

/*
 * CPolyline::DataSet
 *
 * Purpose:
 *  Sets the current data in this Polyline to a given structure.
 *
 * Parameters:
 *  ppl             PPOLYLINEDATA to initialize to.
 *  fSizeToData     BOOL indicating if we're to size to the data or
 *                  scale it.
 *  fNotify         BOOL indicating if we're to send an advise on
 *                  this change.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataSet(PPOLYLINEDATA ppl, BOOL fSizeToData
    , BOOL fNotify)
    {
    RECTS       rcs;
    RECT        rc;
    UINT        i;

    /*
     * Copy the structure in ppl and repaint to reflect the new
     * point set.  Note that unlike the RectSet message, we do no
     * scaling, assuming that the rect in the structure is
     * appropriate for the data.
     */

    if (NULL==ppl)
        return POLYLINE_E_INVALIDPOINTER;

    //Preserve the old rectangle
    rcs=m_pl.rc;

    /*
     * For version 2.0 we perform a straight copy.  For version
     * 1.0 we copy the 1.0 structure and fill in defaults for
     * the 2.0 additions.
     */

    switch (ppl->wVerMaj)
        {
        case VERSIONMAJOR:          //2.x
            switch (ppl->wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    m_pl=*ppl;
                    break;

                default:
                    return POLYLINE_E_UNSUPPORTEDVERSION;
                }
            break;

        case 1:                     //1.x
            switch (ppl->wVerMin)
                {
                case 0:             //1.0
                    *((PPOLYLINEDATA10)&m_pl)=
                        *((PPOLYLINEDATA10)ppl);

                    /*
                     * Update this structure to 2.0.  Note that we
                     * assume whoever loaded us to save the loaded
                     * version so it can later ask what version
                     * the user wants to save.
                     */
                    m_pl.wVerMaj=VERSIONMAJOR;
                    m_pl.wVerMin=VERSIONMINOR;

                    /*
                     * Version 1.0 stored rc in parent coordinates.
                     * We need those now in our client coodinates.
                     */
                    RECTSTORECT(m_pl.rc, rc);
                    OffsetRect(&rc, -m_pl.rc.left, -m_pl.rc.top);

                    /*
                     * 1.0 data had points relative to size of the
                     * rectangle.  We need to scale these to 0-32767
                     * independent of the rectangle for the version
                     * upgrade.
                     */
                    for (i=0; i < m_pl.cPoints; i++)
                        PointScale(&rc, &m_pl.rgpt[i], FALSE);

                    RECTTORECTS(rc, m_pl.rc);

                    //New 2.0 features
                    m_pl.rgbBackground=GetSysColor(COLOR_WINDOW);
                    m_pl.rgbLine=GetSysColor(COLOR_WINDOWTEXT);
                    m_pl.iLineStyle=PS_SOLID;
                    break;

                default:
                    return POLYLINE_E_UNSUPPORTEDVERSION;
                }
            break;

        default:
            return POLYLINE_E_UNSUPPORTEDVERSION;
        }

    //Inform our parent of the data change
    if (NULL!=m_pAdv)
        m_pAdv->OnDataChange();

    /*
     * If we're scaling the window to fit the data, then use
     * RectSet passing our current rectangle as the new one.
     * That makes sure that the data won't change but that the
     * window is resized.
     */

    if (fSizeToData)
        {
        POINT       pt;

        /*
         * Get our offset in the parent window so we can RectSet
         * to the right place since RectSet expects rectangle in
         * parent coordinates and we get it in client coordinates.
         */
        GetWindowRect(m_hWnd, &rc);
        pt.x=rc.left;
        pt.y=rc.top;
        ScreenToClient(GetParent(m_hWnd), &pt);
        RECTSTORECT(m_pl.rc, rc);
        OffsetRect(&rc, pt.x, pt.y);

        //This will also cause a repaint.
        RectSet(&rc, fNotify);
        }
    else
        {
        //Make sure we're updated.
        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    return POLYLINE_E_NONE;
    }

/*
 * CPolyline::DataGet
 *
 * Purpose:
 *  Retrieves the Polyline's current data.
 *
 * Parameters:
 *  ppl             PPOLYLINEDATA into which we copy the data.
 *  lVer            LONG version of the data to retrieve.  Use
 *                  VERSIONCURRENT to retrieve the most current.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value
 */

LONG CPolyline::DataGet(PPOLYLINEDATA ppl, LONG lVer)
    {
    UINT        i;
    RECT        rc;

    //Retieve the current version
    if (lVer==MAKELONG(VERSIONMINOR, VERSIONMAJOR)
        || VERSIONCURRENT==lVer)
        {
        *ppl=m_pl;
        return POLYLINE_E_NONE;
        }

    //Check for versions we support, 1.x
    if (HIWORD(lVer)!=1)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    if (LOWORD(lVer)==0)    //Check for 1.0
        {
        //Do 2.0 to 1.0 conversion
        *((PPOLYLINEDATA10)ppl)=*((PPOLYLINEDATA10)&m_pl);

        RECTSTORECT(ppl->rc, rc);

        //Get the parent coordinates of our rectangle
        if (!IsWindow(m_hWnd))
            OffsetRect(&rc, 8, 8); //This is always the offset.
        else
            RectGet(&rc);

        /*
         * 1.0 data has points relative to size of the rectangle.
         * We need to scale these from 0-32767 so we have the
         * right values.
         */
        for (i=0; i < ppl->cPoints; i++)
            PointScale(&rc, &ppl->rgpt[i], TRUE);

        RECTTORECTS(rc, ppl->rc);

        //Insure old version numbers.
        ppl->wVerMaj=1;
        ppl->wVerMin=0;

        ((PPOLYLINEDATA10)ppl)->fDrawEntire=TRUE;
        return POLYLINE_E_NONE;
        }

    return POLYLINE_E_UNSUPPORTEDVERSION;
    }

/*
 * CPolyline::DataSetMem
 *
 * Purpose:
 *  Sets the Polyline's data using a global memory handle
 *  instead of a pointer.
 *
 * Parameters:
 *  hMem            HGLOBAL containing the data.
 *  fFree           BOOL indicating if we're to free the data.
 *                  The memory will be freed regardless of any
 *                  error returned from here.
 *  fSizeToData     BOOL indicating if we're to size to the data
 *                  or scale it.
 *  fNotify         BOOL indicating if we're to send an advise
 *                  on this change.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataSetMem(HGLOBAL hMem, BOOL fFree
    , BOOL fSizeToData, BOOL fNotify)
    {
    PPOLYLINEDATA   ppl;
    LONG            lRet=POLYLINE_E_INVALIDPOINTER;

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        lRet=DataSet(ppl, fSizeToData, fNotify);

        GlobalUnlock(hMem);

        if (fFree)
            GlobalFree(hMem);
        }

    return lRet;
    }

/*
 * CPolyline::DataGetMem
 *
 * Purpose:
 *  Retrieves the Polyline's data in a global memory handle.
 *
 * Parameters:
 *  lVer            LONG version of data to retrieve.
 *  phMem           HGLOBAL * in which to store the handle.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataGetMem(LONG lVer, HGLOBAL *phMem)
    {
    HGLOBAL         hMem;
    PPOLYLINEDATA   ppl;
    LONG            lRet;

    if (NULL==phMem)
        return POLYLINE_E_INVALIDPOINTER;

    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
        , CBPOLYLINEDATA);

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        lRet=DataGet(ppl, lVer);
        GlobalUnlock(hMem);

        if (POLYLINE_E_NONE!=lRet)
            {
            GlobalFree(hMem);
            hMem=NULL;
            }
        }

    *phMem=hMem;
    return lRet;
    }

/*
 * CPolyline::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectGet(LPRECT pRect)
    {
    RECT        rc;
    POINT       pt;

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_hWnd, &rc);
    pt.x=rc.left;
    pt.y=rc.top;
    ScreenToClient(GetParent(m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return;
    }

/*
 * CPolyline::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  None
 */

void CPolyline::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return;
    }

/*
 * CPolyline::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;
    RECT        rc;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, pRect->left, pRect->top
        , cx, cy, SWP_NOZORDER);

    SetRect(&rc, 0, 0, cx, cy);
    RECTTORECTS(rc, m_pl.rc);

    if (fNotify && NULL!=m_pAdv)
        m_pAdv->OnSizeChange();

    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }

/*
 * CPolyline::SizeSet
 *
 * Purpose:
 *  Sets a new size for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPolyline::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
        , SWP_NOMOVE | SWP_NOZORDER);

    if (fNotify && NULL!=m_pAdv)
        m_pAdv->OnSizeChange();

    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }

/*
 * CPolyline::ColorSet
 *
 * Purpose:
 *  Changes for background or line color in the Polyline
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color to use.
 *
 * Return Value:
 *  COLORREF        Previous color for the index iColor.
 */

COLORREF CPolyline::ColorSet(UINT iColor, COLORREF cr)
    {
    COLORREF    crRet;

    switch (iColor)
        {
        case POLYLINECOLOR_BACKGROUND:
            crRet=m_pl.rgbBackground;
            m_pl.rgbBackground=cr;
            break;

        case POLYLINECOLOR_LINE:
            crRet=m_pl.rgbLine;
            m_pl.rgbLine=cr;
            break;
        }

    //If the color changed, repaint
    if (crRet!=cr)
        {
        if (NULL!=m_pAdv)
            m_pAdv->OnColorChange();

        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    return crRet;
    }

/*
 * CPolyline::ColorGet
 *
 * Purpose:
 *  Retrieves one of the colors currently in use by the Polyline.
 *
 * Parameters:
 *  iColor          UINT identifying the color of interest.
 *
 * Return Value:
 *  COLORREF        Current color for iColor.
 */

COLORREF CPolyline::ColorGet(UINT iColor)
    {
    COLORREF    crRet;

    crRet=(POLYLINECOLOR_BACKGROUND==iColor)
        ? m_pl.rgbBackground : m_pl.rgbLine;

    return crRet;
    }

/*
 * CPolyline::LineStyleSet
 *
 * Purpose:
 *  Changes the line style in use by the Polyline
 *
 * Parameters:
 *  iStyle          UINT style of the line to use.
 *
 * Return Value:
 *  UINT            Previous style.
 */

UINT CPolyline::LineStyleSet(UINT iStyle)
    {
    UINT        uRet=(UINT)m_pl.iLineStyle;

    //Validate the line style

if (PS_SOLID==iStyle || PS_DASH==iStyle || PS_DOT==iStyle
        || PS_DASHDOT==iStyle || PS_DASHDOTDOT==iStyle)
        {
        m_pl.iLineStyle=iStyle;

        if (uRet!=(UINT)m_pl.iLineStyle)
            {
            if (NULL!=m_pAdv)
                m_pAdv->OnLineStyleChange();

            InvalidateRect(m_hWnd, NULL, TRUE);
            UpdateWindow(m_hWnd);
            }
        }

    return uRet;
    }

/*
 * CPolyline::LineStyleGet
 *
 * Purpose:
 *  Retrieves the current line style in use in the Polyline
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Current line style.
 */

UINT CPolyline::LineStyleGet(void)
    {
    return m_pl.iLineStyle;
    }

/*
 * CPolyline::RenderBitmap
 *
 * Purpose:
 *  Creates a bitmap image of the current Polyline.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HBITMAP         Handle to the newly rendered bitmap.
 */

HBITMAP CPolyline::RenderBitmap(void)
    {
    HDC         hDC;
    HDC         hMemDC;
    HBITMAP     hBmp;
    RECT        rc;
    HGDIOBJ     hObj;

    //Render a bitmap the size of the current rectangle.
    hDC=GetDC(m_hWnd);
    hMemDC=CreateCompatibleDC(hDC);

    GetClientRect(m_hWnd, &rc);
    hBmp=CreateCompatibleBitmap(hDC, rc.right, rc.bottom);

    if (NULL!=hBmp)
        {
        //Draw the POLYLINEDATA into the bitmap.
        hObj=SelectObject(hMemDC, hBmp);
        Draw(hMemDC, FALSE, TRUE);
        SelectObject(hMemDC, hObj);
        }

    DeleteDC(hMemDC);
    ReleaseDC(m_hWnd, hDC);

    return hBmp;
    }

/*
 * CPolyline::RenderMetafile
 *
 * Purpose:
 *  Renders the current image of the Polyline into a metafile.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HMETAFILE       Handle to the newly created metafile.
 */

HMETAFILE CPolyline::RenderMetafile(void)
    {
    HDC         hDC;
    HMETAFILE   hMF;
    RECT        rc;

    //Create a memory metafile and return its handle.
    hDC=(HDC)CreateMetaFile(NULL);
    hMF=NULL;

    if (NULL!=hDC)
        {
        /*
         * This is absolutely essential to the metafile so it
         * can be scaled in the clipboard and any destination
         * application.
         */
        SetMapMode(hDC, MM_ANISOTROPIC);
        GetClientRect(m_hWnd, &rc);
        SetWindowOrgEx(hDC, 0, 0, NULL);
        SetWindowExtEx(hDC, rc.right, rc.bottom, NULL);

        Draw(hDC, TRUE, TRUE);
        hMF=CloseMetaFile(hDC);
        }

    return hMF;
    }

/*
 * CPolyline::RenderMetafilePict
 *
 * Purpose:
 *  Renders the current Polyline into a METAFILEPICT structure in
 *  global memory.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HGLOBAL         Memory containing the METAFILEPICT structure.
 */

HGLOBAL CPolyline::RenderMetafilePict(void)
    {
    HGLOBAL         hMem;
    HMETAFILE       hMF;
    LPMETAFILEPICT  pMF;
    RECT            rc;

    //Get the metafile
    hMF=RenderMetafile();

    if (NULL==hMF)
        return NULL;

    //Allocate the METAFILEPICT structure.
    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
        , sizeof(METAFILEPICT));

    if (NULL==hMem)
        {
        DeleteMetaFile(hMF);
        return NULL;
        }

    /*
     * Global lock only fails in PMODE if the selector is invalid
     * (like it was discarded) or references a 0 length segment,
     * neither of which can happen here.
     */
    pMF=(LPMETAFILEPICT)GlobalLock(hMem);

    pMF->hMF=hMF;
    pMF->mm=MM_ANISOTROPIC;

    //Insert the extents in MM_HIMETRIC units.
    GetClientRect(m_hWnd, &rc);
    RectConvertMappings(&rc, FALSE);
    pMF->xExt=rc.right;
    pMF->yExt=rc.bottom;

    GlobalUnlock(hMem);
    return hMem;
    }

/*
 * CPolyline::RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device (MM_TEXT) or
 *  HIMETRIC to the other.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  fToDevice       BOOL TRUE to convert from HIMETRIC to device,
 *                  FALSE to convert device to HIMETRIC.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectConvertMappings(LPRECT pRect, BOOL fToDevice)
    {
    HDC      hDC;
    int      iLpx, iLpy;

    if (NULL==pRect)
        return;

    hDC=GetDC(NULL);
    iLpx=GetDeviceCaps(hDC, LOGPIXELSX);
    iLpy=GetDeviceCaps(hDC, LOGPIXELSY);
    ReleaseDC(NULL, hDC);

    if (fToDevice)
        {
        pRect->left=MulDiv(iLpx, pRect->left, HIMETRIC_PER_INCH);
        pRect->top =MulDiv(iLpy, pRect->top , HIMETRIC_PER_INCH);

        pRect->right=MulDiv(iLpx, pRect->right,  HIMETRIC_PER_INCH);
        pRect->bottom=MulDiv(iLpy, pRect->bottom,HIMETRIC_PER_INCH);

       #ifdef NEVER
        /*
         * In this conversion we may get situations where the top
         * coordinate is larger than the bottom, which messes us up.
         */
        if (pRect->bottom < pRect->top)
            {
            iLpy=pRect->top;
            pRect->top=pRect->bottom;
            pRect->bottom=iLpy;
            }
       #endif
        }
    else
        {
        pRect->left=MulDiv(pRect->left, HIMETRIC_PER_INCH, iLpx);
        pRect->top =MulDiv(pRect->top , HIMETRIC_PER_INCH, iLpy);

        pRect->right =MulDiv(pRect->right, HIMETRIC_PER_INCH, iLpx);
        pRect->bottom=MulDiv(pRect->bottom,HIMETRIC_PER_INCH, iLpy);
        }

    return;
    }


POLYLINE.H

/*
 * POLYLINE.H
 * Cosmo Chapter 21
 *
 * Definitions and function prototypes for the PolyLine window
 * class that can be treated like its own control.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _POLYLINE_H_
#define _POLYLINE_H_

//Versioning.
#define VERSIONMAJOR                2
#define VERSIONMINOR                0
#define VERSIONCURRENT              0x00020000

//Classname
#define SZCLASSPOLYLINE             TEXT("polyline")

//Stream Name that holds the data
#define SZSTREAM                    OLETEXT("CONTENTS")
#define SZOLE1STREAM                OLETEXT("\1Ole10Native")

#define HIMETRIC_PER_INCH           2540
#define CPOLYLINEPOINTS             20

//Window extra bytes and offsets
#define CBPOLYLINEWNDEXTRA          (sizeof(LONG))
#define PLWL_STRUCTURE              0

//Version 2.0 Polyline Structure
typedef struct tagPOLYLINEDATA
    {
    WORD        wVerMaj;                //Major version number.
    WORD        wVerMin;                //Minor version number.
    WORD        cPoints;                //Number of points.
    short       fReserved;              //Obsolete from v1.0
    RECTS       rc;                     //Rectangle of this figure
    POINTS      rgpt[CPOLYLINEPOINTS];  //Points (0-32767 grid)

    //Version 2.0 additions
    COLORREF    rgbBackground;          //Background color
    COLORREF    rgbLine;                //Line color
    short       iLineStyle;             //Line style
    } POLYLINEDATA, *PPOLYLINEDATA;

#define CBPOLYLINEDATA   sizeof(POLYLINEDATA)
#define CBPOLYLINEDATA20 sizeof(POLYLINEDATA)

//Version 1.0 Polyline Structure
typedef struct tagPOLYLINEDATA10
    {
    WORD        wVerMaj;                //Major version number.
    WORD        wVerMin;                //Minor version number.
    WORD        cPoints;                //Number of points.
    short       fDrawEntire;            //Flag to draw entire figure
    RECTS       rc;                     //Rectangle of this figure
    POINTS      rgpt[CPOLYLINEPOINTS];  //Points (scaled to rc)
    } POLYLINEDATA10, *PPOLYLINEDATA10;

#define CBPOLYLINEDATA10 sizeof(POLYLINEDATA10)

//POLYWIN.CPP
LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM, LPARAM);

class CPolyline : public CWindow
    {
    friend LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM
        , LPARAM);

    private:
        POLYLINEDATA   m_pl;

        class CPolylineAdviseSink * m_pAdv;

    public:
        //This is for CFigure to look at when writing its storage.
        BOOL            m_fReadFromOLE10;

    private:
        HFILE     OpenFileW(LPTSTR, LPOFSTRUCT, UINT);
        void      PointScale(LPRECT, LPPOINTS, BOOL);
        void      Draw(HDC, BOOL, BOOL);
        void      RectConvertMappings(LPRECT, BOOL);

    public:
        CPolyline(HINSTANCE);
        ~CPolyline(void);

        BOOL      Init(HWND, LPRECT, DWORD, UINT
            , class CPolylineAdviseSink *);

        void      New(void);
        BOOL      Undo(void);

        //File functions
        LONG      ReadFromStorage(LPSTORAGE);
        LONG      WriteToStorage(LPSTORAGE, LONG);

        LONG      ReadFromStream(LPSTREAM);
        LONG      WriteToStream(LPSTREAM, LONG);

        LONG      ReadFromFile(LPTSTR);
        LONG      WriteToFile(LPTSTR, LONG);

        //Data transfer functions
        LONG      DataSet(PPOLYLINEDATA, BOOL, BOOL);
        LONG      DataGet(PPOLYLINEDATA, LONG);
        LONG      DataSetMem(HGLOBAL, BOOL, BOOL, BOOL);
        LONG      DataGetMem(LONG, HGLOBAL *);
        HBITMAP   RenderBitmap(void);
        HMETAFILE RenderMetafile(void);
        HGLOBAL   RenderMetafilePict(void);

        void      RectGet(LPRECT);
        void      SizeGet(LPRECT);
        void      RectSet(LPRECT, BOOL);
        void      SizeSet(LPRECT, BOOL);
        COLORREF  ColorSet(UINT, COLORREF);
        COLORREF  ColorGet(UINT);
        UINT      LineStyleSet(UINT);
        UINT      LineStyleGet(void);
    };

typedef CPolyline *PCPolyline;

//Error values for data transfer functions
#define POLYLINE_E_NONE                    0
#define POLYLINE_E_UNSUPPORTEDVERSION      -1
#define POLYLINE_E_INVALIDPOINTER          -2
#define POLYLINE_E_READFAILURE             -3
#define POLYLINE_E_WRITEFAILURE            -4

class CPolylineAdviseSink
    {
    private:
        LPVOID      m_pv;           //Customizable structure

    public:
        CPolylineAdviseSink(LPVOID);
        ~CPolylineAdviseSink(void);

        void OnPointChange(void);
        void OnSizeChange(void);
        void OnDataChange(void);
        void OnColorChange(void);
        void OnLineStyleChange(void);
    };

typedef CPolylineAdviseSink *PCPolylineAdviseSink;

//Color indices for color messages
#define POLYLINECOLOR_BACKGROUND    0
#define POLYLINECOLOR_LINE          1

#endif  //_POLYLINE_H_


POLYWIN.CPP

/*
 * POLYWIN.CPP
 * Cosmo Chapter 21
 *
 * Window procedure for the polyline drawing window and support
 * functions.  This window is not complicated.  On creation it
 * allocates a block of memory for a POLYLINEDATA structure that
 * contains 20 POINTs.  We do not attempt to reallocate this
 * array at all just to maintain simplicity.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * PolylineWndProc
 *
 * Purpose:
 *  Window procedure for the polyline drawing window.
 */

LRESULT APIENTRY PolylineWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCPolyline      ppl;
    PAINTSTRUCT     ps;
    HDC             hDC;
    POINTS          pt;
    RECT            rc;

    ppl=(PCPolyline)GetWindowLong(hWnd, PLWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppl=(PCPolyline)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PLWL_STRUCTURE, (LONG)ppl);
            ppl->m_hWnd=hWnd;
            ppl->New();
            break;

        case WM_PAINT:
            hDC=BeginPaint(hWnd, &ps);
            ppl->Draw(hDC, FALSE, TRUE);
            EndPaint(hWnd, &ps);
            break;

        case WM_LBUTTONDOWN:
            //Stop if we are already at the limit.
            if (CPOLYLINEPOINTS==ppl->m_pl.cPoints)
                {
                MessageBeep(0);
                break;
                }

            //Convert the points into 0-32767 range
            GetClientRect(hWnd, &rc);
            pt=MAKEPOINTS(lParam);
            ppl->PointScale(&rc, &pt, FALSE);

            ppl->m_pl.rgpt[ppl->m_pl.cPoints++]=pt;

            //Draw the lines to this new point only.
            hDC=GetDC(hWnd);
            ppl->Draw(hDC, FALSE, FALSE);
            ReleaseDC(hWnd, hDC);

            if (NULL!=ppl->m_pAdv)
                ppl->m_pAdv->OnPointChange();

            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPolyline::Draw
 *
 * Purpose:
 *  Paints the current line in the polyline window.
 *
 * Parameters:
 *  hDC             HDC to draw on, could be metafile or printer DC.
 *  fMetafile       BOOL indicating if hDC is a metafile or not, so
 *                  we can avoid operations that RIP.
 *  fEntire         BOOL indicating if we should draw the entire
 *                  figure or not.
 *
 * Return Value:
 *  None
 */

void CPolyline::Draw(HDC hDC, BOOL fMetafile, BOOL fEntire)
    {
    HBRUSH          hBrush;
    HPEN            hPen;
    HGDIOBJ         hObj1, hObj2;
    UINT            i, j;
    UINT            uMM;
    POINTS          pt1, pt2;
    POINT           rgpt[CPOLYLINEPOINTS];
    RECT            rc;

    GetClientRect(m_hWnd, &rc);

    /*
     * Make a 32-bit copy of the points in the POLYLINE itself
     * so we can use DPtoLP and LPtoDP.
     */
    for (i=0; i < m_pl.cPoints; i++)
        {
        rgpt[i].x=m_pl.rgpt[i].x;
        rgpt[i].y=m_pl.rgpt[i].y;
        }

    /*
     * If the mapping mode is not MM_TEXT, convert the points to
     * whatever mapping mode in in effect before drawing.
     * This specifically supports metafiles in MM_ANISOTROPIC.
     */
    uMM=fMetafile ? MM_TEXT : GetMapMode(hDC);

    if (MM_TEXT!=uMM)
        DPtoLP(hDC, rgpt, m_pl.cPoints);

    hPen=CreatePen(m_pl.iLineStyle, 1, m_pl.rgbLine);
    hObj1=SelectObject(hDC, hPen);

    hBrush=CreateSolidBrush(m_pl.rgbBackground);
    hObj2=SelectObject(hDC, hBrush);
    SetBkColor(hDC, m_pl.rgbBackground);

    /*
     * Either draw the entire figure or just a single point.  The
     * entire figure also includes erasing the background
     * completely,since hDC may be a metafile DC.  Drawing a
     * single point just updates the figure for that new point.
     */
    if (fEntire || 0==m_pl.cPoints)
        {
        //Erase the background for bitmaps and metafiles.
        SelectObject(hDC, GetStockObject(NULL_PEN));
        Rectangle(hDC, rc.left, rc.top, rc.right+1, rc.bottom+1);
        SelectObject(hDC, hPen);

        /*
         * If we are drawing the entire figure, then loop through
         * each point drawing a line to each successive point.
         */

        for (i=0; i < m_pl.cPoints; i++)
            {
            for (j=i; j < m_pl.cPoints; j++)
                {
                /*
                 * Not using POINTTOPOINTS since it unfortunately
                 * returns a LONG which doesn't equate to a POINTS.
                 */
                pt1.x=(short)rgpt[i].x;
                pt1.y=(short)rgpt[i].y;
                pt2.x=(short)rgpt[j].x;
                pt2.y=(short)rgpt[j].y;

                PointScale(&rc, &pt1, TRUE);
                PointScale(&rc, &pt2, TRUE);
                MoveToEx(hDC, pt1.x, pt1.y, NULL);
                LineTo(hDC, pt2.x, pt2.y);
                }
            }
        }
    else
        {
        /*
         * If we are only drawing the last point, just cycle once
         * through previous points.
         */

        //Get the last point entered in the array.
        j=m_pl.cPoints-1;
        pt1.x=(short)rgpt[j].x;
        pt1.y=(short)rgpt[j].y;
        PointScale(&rc, &pt1, TRUE);

        for (i=0; i < j; i++)
            {
            pt2.x=(short)rgpt[i].x;
            pt2.y=(short)rgpt[i].y;
            PointScale(&rc, &pt2, TRUE);

            MoveToEx(hDC, pt1.x, pt1.y, NULL);
            LineTo(hDC, pt2.x, pt2.y);
            }
        }

    //If we have one point, draw a dot to indicate it's position.
    if (1==m_pl.cPoints)
        {
        pt1.x=(short)rgpt[0].x;
        pt1.y=(short)rgpt[0].y;
        PointScale(&rc, &pt1, TRUE);
        SetPixel(hDC, pt1.x, pt1.y, m_pl.rgbLine);
        }

    //Note:  m_pl.rgpt is unmodified in this code.

    SelectObject(hDC, hObj1);
    SelectObject(hDC, hObj2);
    DeleteObject(hBrush);
    DeleteObject(hPen);
    return;
    }

/*
 * CPolyline::PointScale
 *
 * Purpose:
 *  Scales a point to or from a relative window coordinate to a
 *  0-32767 coordinate.
 *
 * Parameters:
 *  pRect           LPRECT of the window.
 *  ppt             LPPOINTS to convert
 *  fScaleToWindow  BOOL indicating direction of scaling.
 *
 * Return Value:
 *  None
 */

void CPolyline::PointScale(LPRECT pRect, LPPOINTS ppt
    , BOOL fScaleToWindow)
    {
    DWORD   cx, cy;

    //Window size
    cx=(DWORD)(pRect->right-pRect->left);
    cy=(DWORD)(pRect->bottom-pRect->top);

    //Prevent crashes
    if (0L==cx) cx=1;
    if (0L==cy) cy=1;

    //Must use DWORD to insure proper scaling.
    if (fScaleToWindow)
        {
        ppt->x=(UINT)(((DWORD)ppt->x*cx) >> 15);
        ppt->y=(UINT)(((DWORD)ppt->y*cy) >> 15);
        }
    else
        {
        ppt->x=(UINT)(((DWORD)ppt->x << 15)/cx);
        ppt->y=(UINT)(((DWORD)ppt->y << 15)/cy);
        }

    return;
    }


RESOURCE.H

/*
 * RESOURCE.H
 * Cosmo Chapter 21
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Note that we augment classlib.h which we include before this.

//Position of Window menu.
#undef  WINDOW_MENU
#define WINDOW_MENU             4

//Total number of menus
#undef  CMENUS
#ifdef MDI
#define CMENUS                  6
#else
#define CMENUS                  5
#endif

//Menu command identifiers.

#define IDM_FILEIMPORT          (IDM_CUSTOMFILEMIN)

#define IDM_COLORBACKGROUND     (IDM_CUSTOMMIN)
#define IDM_COLORLINE           (IDM_CUSTOMMIN+1)

#define IDM_LINEMIN             (IDM_CUSTOMMIN+100)
#define IDM_LINESOLID           (IDM_LINEMIN+0)         //PS_SOLID       0
#define IDM_LINEDASH            (IDM_LINEMIN+1)         //PS_DASH        1
#define IDM_LINEDOT             (IDM_LINEMIN+2)         //PS_DOT         2
#define IDM_LINEDASHDOT         (IDM_LINEMIN+3)         //PS_DASHDOT     3
#define IDM_LINEDASHDOTDOT      (IDM_LINEMIN+4)         //PS_DASHDOTDOT  4
#define IDM_LINEMAX             IDM_LINEDASHDOTDOT

/*
 * IDs for StatStrip handling of popup menus:  must be in order of menu
 * CLASSRES.H already defines ID_MENUFILE and ID_MENUEDIT for us.
 */

#undef  ID_MENUWINDOW
#undef  ID_MENUHELP

#define ID_MENUCOLOR            (ID_MENUCUSTOMMIN+0)
#define ID_MENULINE             (ID_MENUCUSTOMMIN+1)
#define ID_MENUWINDOW           (ID_MENUCUSTOMMIN+2)
#define ID_MENUHELP             (ID_MENUCUSTOMMIN+3)

//Stringtable IDs.  Keep sequential for each group.
#define IDS_FRAMEMIN            IDS_STANDARDFRAMEMIN
#define IDS_FILEIMPORT          (IDS_CUSTOMFRAMEMIN+0)
#define IDS_FIGUREINCONTAINER   (IDS_CUSTOMFRAMEMIN+1)
#define IDS_EXITANDRETURN       (IDS_CUSTOMFRAMEMIN+2)
#define IDS_EMBEDDINGCAPTION    (IDS_CUSTOMFRAMEMIN+3)
#define IDS_SAVECOPYAS          (IDS_CUSTOMFRAMEMIN+4)
#define IDS_NEW                 (IDS_CUSTOMFRAMEMIN+5)
#define IDS_OPEN                (IDS_CUSTOMFRAMEMIN+6)
#define IDS_CLOSE2              (IDS_CUSTOMFRAMEMIN+7)
#define IDS_SAVE                (IDS_CUSTOMFRAMEMIN+8)
#define IDS_SAVEAS              (IDS_CUSTOMFRAMEMIN+9)
#define IDS_EXIT                (IDS_CUSTOMFRAMEMIN+10)
#define IDS_FRAMEMAX            (IDS_CUSTOMFRAMEMIN+11)

#define IDS_DOCUMENTMIN         IDS_STANDARDDOCMIN
#define IDS_VERSIONCHANGE       (IDS_CUSTOMDOCMIN+0)
#define IDS_OBJECTDESCRIPTION   (IDS_CUSTOMDOCMIN+1)
#define IDS_DOCUMENTMAX         (IDS_CUSTOMDOCMIN+1)

//Strings for the CFigure object implementation.
#define IDS_FIGUREMIN           256
#define IDS_CLOSECAPTION        (IDS_FIGUREMIN+1)
#define IDS_CLOSEPROMPT         (IDS_FIGUREMIN+2)
#define IDS_USERTYPE            (IDS_FIGUREMIN+3)
#define IDS_FORMAT              (IDS_FIGUREMIN+4)
//CHAPTER21MOD
#define IDS_EXTENSION           (IDS_FIGUREMIN+5)
#define IDS_FIGUREMAX           (IDS_FIGUREMIN+5)
//End CHAPTER21MOD

//These are specifically for the StatStrip
#define IDS_STATMESSAGEMIN                  IDS_STANDARDSTATMESSAGEMIN

#define IDS_ITEMMESSAGEFILEIMPORT           (IDS_CUSTOMSTATMESSAGEMIN+0)

#define IDS_MENUMESSAGECOLOR                (IDS_CUSTOMSTATMESSAGEMIN+1)
#define IDS_ITEMMESSAGECOLORBACKGROUND      (IDS_CUSTOMSTATMESSAGEMIN+2)
#define IDS_ITEMMESSAGECOLORLINE            (IDS_CUSTOMSTATMESSAGEMIN+3)

#define IDS_MENUMESSAGELINE                 (IDS_CUSTOMSTATMESSAGEMIN+4)
#define IDS_ITEMMESSAGELINESOLID            (IDS_CUSTOMSTATMESSAGEMIN+5)
#define IDS_ITEMMESSAGELINEDASH             (IDS_CUSTOMSTATMESSAGEMIN+6)
#define IDS_ITEMMESSAGELINEDOT              (IDS_CUSTOMSTATMESSAGEMIN+7)
#define IDS_ITEMMESSAGELINEDASHDOT          (IDS_CUSTOMSTATMESSAGEMIN+8)
#define IDS_ITEMMESSAGELINEDASHDOTDOT       (IDS_CUSTOMSTATMESSAGEMIN+9)

#define IDS_STATMESSAGEMAX                  (IDS_CUSTOMSTATMESSAGEMIN+9)

#endif //_RESOURCE_H_


CLIENT.CPP

/*
 * CLIENT.CPP
 * Patron Chapter 21
 *
 * Implementation of the CPatronClient class that just makes sure
 * we get a CPatronDoc on doc creation and that we initialize fully.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPatronClient::CPatronClient
 * CPatronClient::~CPatronClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame to the frame object.
 */

CPatronClient::CPatronClient(HINSTANCE hInst, PCFrame pFR)
    : CClient(hInst, pFR)
    {
    return;
    }

CPatronClient::~CPatronClient(void)
    {
    return;
    }

/*
 * CPatronClient::CreateCDocument
 *
 * Purpose:
 *  Constructs a new document specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CPatronClient::CreateCDocument(void)
    {
    return (PCDocument)(new CPatronDoc(m_hInst, m_pFR, m_pAdv));
    }


DOCUMENT.CPP

/*
 * DOCUMENT.CPP
 * Patron Chapter 21
 *
 * Implementation of the CPatronDoc derivation of CDocument that
 * manages pages for us.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"
#include <memory.h>
#include <dlgs.h>       //Pring Dlg button IDs

/*
 * CPatronDoc::CPatronDoc
 * CPatronDoc::~CPatronDoc
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame of the frame object.
 *  pAdv            PCDocumentAdviseSink to notify on events.
 */

CPatronDoc::CPatronDoc(HINSTANCE hInst, PCFrame pFR
    , PCDocumentAdviseSink pAdv)
    : CDocument(hInst, pFR, pAdv)
    {
    m_pPG=NULL;
    m_lVer=VERSIONCURRENT;
    m_pIStorage=NULL;
    m_fPrintSetup=TRUE;
    m_pDropTarget=NULL;

    //These CFSTR_* values are standard (defined in INC\CLASSLIB.H)
    m_cfEmbeddedObject=RegisterClipboardFormat
        (CFSTR_EMBEDDEDOBJECT);
    m_cfObjectDescriptor=RegisterClipboardFormat
        (CFSTR_OBJECTDESCRIPTOR);

    //CHAPTER20MOD
    m_cfLinkSource=RegisterClipboardFormat(CFSTR_LINKSOURCE);
    m_cfLinkSrcDescriptor=RegisterClipboardFormat
        (CFSTR_LINKSRCDESCRIPTOR);

    m_fShowTypes=FALSE;

    //CHAPTER21MOD
    m_cRef=1L;
    m_fRename=TRUE;
    m_dwRegROT=0L;
    m_dwRegROTWild=0L;
    m_pImpIPersistFile=NULL;
    m_pImpIOleItemContainer=NULL;
    //End CHAPTER21MOD

    return;
    }

CPatronDoc::~CPatronDoc(void)
    {
    //CHAPTER21MOD
    INOLE_RevokeAsRunning(&m_dwRegROT);
    INOLE_RevokeAsRunning(&m_dwRegROTWild);

    DeleteInterfaceImp(m_pImpIPersistFile);
    DeleteInterfaceImp(m_pImpIOleItemContainer);
    //End CHAPTER21MOD

    if (NULL!=m_pPG)
        delete m_pPG;

    ReleaseInterface(m_pIStorage);
    CoFreeUnusedLibraries();
    return;
    }

//CHAPTER21MOD

/*
 * CPatronDoc::QueryInterface
 * CPatronDoc::AddRef
 * CPatronDoc::Release
 */

STDMETHODIMP CPatronDoc::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    /*
     * WARNING:  Because the *ppv=this implicitly does
     * *ppv=(LPVOID)this, the this pointer is incorrectly cast
     * from a CPatronDoc pointer to LPVOID where we want an
     * IUnknown pointer instead.  CPatronDoc uses multiple
     * inheritance here which means that we have to explicitly
     * provide the typecast to select the base class like
     * IUnknown.  If you don't do this, calls to AddRef, for
     * example, might call your desctructor!
     */

    if (IID_IUnknown==riid)
        *ppv=(LPUNKNOWN)this;

    if (IID_IOleItemContainer==riid || IID_IOleContainer==riid
        || IID_IParseDisplayName==riid)
        *ppv=m_pImpIOleItemContainer;

    if (IID_IPersistFile==riid || IID_IPersist==riid)
        *ppv=m_pImpIPersistFile;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CPatronDoc::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPatronDoc::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    PostMessage(m_hWnd, WM_CLOSE, 0, 0);
    return 0;
    }
//End CHAPTER21MOD

/*
 * CPatronDoc::Init
 *
 * Purpose:
 *  Initializes an already created document window.  The client
 *  actually creates the window for us, then passes that here for
 *  further initialization.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPatronDoc::Init(PDOCUMENTINIT pDI)
    {
    //Change the stringtable range to our customization.
    pDI->idsMin=IDS_DOCUMENTMIN;
    pDI->idsMax=IDS_DOCUMENTMAX;

    //Do default initialization
    if (!CDocument::Init(pDI))
        return FALSE;

    //Pages are created when we get a Load later.

    //CHAPTER21MOD
    //Instantiate our interfaces
    m_pImpIPersistFile=new CImpIPersistFile(this, this);

    if (NULL==m_pImpIPersistFile)
        return FALSE;

    m_pImpIOleItemContainer=new CImpIOleItemContainer(this, this
        , TRUE);

    if (NULL==m_pImpIOleItemContainer)
        return FALSE;

    //End CHAPTER21MOD

    return TRUE;
    }

/*
 * CPatronDoc::FMessageHook
 *
 * Purpose:
 *  Processes WM_SIZE for the document so we can resize the Pages
 *  window.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CPatronDoc::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    UINT        dx, dy;
    RECT        rc;

    *pLRes=0;

    //Eat to prevent flickering
    if (WM_ERASEBKGND==iMsg)
        return TRUE;

    if (WM_SIZE==iMsg && NULL!=m_pPG)
        {
        dx=LOWORD(lParam);
        dy=HIWORD(lParam);

        if (SIZE_MINIMIZED!=wParam)
            {
            //Resize Pages window to fit the new document size.
            GetClientRect(hWnd, &rc);
            m_pPG->RectSet(&rc, FALSE);
            }
        }

    if (WM_DESTROY==iMsg)
        {
        /*
         * We have to revoke the drop target here because the window
         * will be destroyed and the property forcefully removed
         * before we could do this in the destructor.
         */
        if (NULL!=m_pDropTarget)
            {
            RevokeDragDrop(m_hWnd);
            CoLockObjectExternal(m_pDropTarget, FALSE, TRUE);
            m_pDropTarget->Release();
            }

        return FALSE;
        }

    /*
     * We return FALSE even on WM_SIZE so we can let the default
     * procedure handle maximized MDI child windows appropriately.
     */
    return FALSE;
    }

/*
 * CPatronDoc::Clear
 *
 * Purpose:
 *  Sets all contents in the document back to defaults with no
 *  filename.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Clear(void)
    {
    //Completely reset the pages
    if (NULL!=m_pPG)
        m_pPG->StorageSet(NULL, FALSE, FALSE);

    CDocument::Clear();
    m_lVer=VERSIONCURRENT;
    return;
    }

//CHAPTER21MOD
/*
 * CPatronDoc::FDirtySet
 *
 * Purpose:
 *  Sets or clears the document 'dirty' flag returning the previous
 *  state of that same flag.  We override this to note change
 *  times in the running object table.
 *
 * Parameters:
 *  fDirty          BOOL indicating the new contents of the dirty
 *                  flag.
 *
 * Return Value:
 *  BOOL            Previous value of the dirty flag.
 */

BOOL CPatronDoc::FDirtySet(BOOL fDirty)
    {
    BOOL        fRet;

    fRet=CDocument::FDirtySet(fDirty);
    INOLE_NoteChangeTime(m_dwRegROT, NULL, NULL);
    INOLE_NoteChangeTime(m_dwRegROTWild, NULL, NULL);

    return fRet;
    }
//End CHAPTER21MOD

/*
 * CPatronDoc::FDirtyGet
 *
 * Purpose:
 *  Returns the current dirty status of the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the file is clean, FALSE otherwise.
 */

BOOL CPatronDoc::FDirtyGet()
    {
    BOOL    fPageDirty;

    fPageDirty=m_pPG->FIsDirty();
    return m_fDirty | fPageDirty;
    }

/*
 * CPatronDoc::Delete
 *
 * Purpose:
 *  Removed the current object from the document.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Delete(void)
    {
    if (NULL!=m_pPG)
        m_pPG->TenantDestroy();

    CoFreeUnusedLibraries();
    return;
    }

/*
 * CPatronDoc::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected in this
 *  document for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU of the Edit menu.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryObjectSelected(HMENU hMenu)
    {
    return m_pPG->FQueryObjectSelected(hMenu);
    }

/*
 * CPatronDoc::Load
 *
 * Purpose:
 *  Loads a given document without any user interface overwriting
 *  the previous contents of the editor.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load.  Could be NULL
 *                  for an untitled document.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CPatronDoc::Load(BOOL fChangeFile, LPTSTR pszFile)
    {
    RECT        rc;
    LPSTORAGE   pIStorage;
    HRESULT     hr;
    CLSID       clsID;
    DWORD       dwMode=STGM_TRANSACTED | STGM_READWRITE
                    | STGM_SHARE_EXCLUSIVE;
    //CHAPTER21MOD
    LPMONIKER   pmk;
    //End CHAPTER21MOD

    if (NULL==pszFile)
        {
        //Create a new temp file.
        hr=StgCreateDocfile(NULL, dwMode | STGM_CREATE
            | STGM_DELETEONRELEASE, 0, &pIStorage);

        //Mark this our class since we check with ReadClassStg.
        if (SUCCEEDED(hr))
            WriteClassStg(pIStorage, CLSID_PatronPages);
        }
    else
        {
        hr=StgOpenStorage(pszFile, NULL, dwMode, NULL, 0, &pIStorage);
        }

    if (FAILED(hr))
        return DOCERR_COULDNOTOPEN;

    //Check if this is our type of file and exit if not.
    hr=ReadClassStg(pIStorage, &clsID);

    if (FAILED(hr) || CLSID_PatronPages!=clsID)
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    //Attempt to create our contained Pages window.
    m_pPG=new CPages(m_hInst, m_cf);
    GetClientRect(m_hWnd, &rc);

    if (!m_pPG->Init(m_hWnd, &rc, WS_CHILD | WS_VISIBLE
        , ID_PAGES, NULL))
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    //CHAPTER21MOD
    /*
     * This moniker registration insures that all the file opening
     * sequence will see this instance of the document as running
     * and be able to communicate with it.  This is so a link to
     * an embedding on the same page of this document will hook
     * up correctly.
     */
    if (NULL!=pszFile)
        {
        CreateFileMoniker(pszFile, &pmk);

        if (NULL!=pmk)
            {
            //This will be revoked and re-registered in Rename below
            INOLE_RegisterAsRunning(this, pmk, 0, &m_dwRegROT);
            pmk->Release();
            }
        }
    //End CHAPTER21MOD

    if (!m_pPG->StorageSet(pIStorage, FALSE, (NULL==pszFile)))
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    //Open the window up for drag-drop
    m_pDropTarget=new CDropTarget(this);

    if (NULL!=m_pDropTarget)
        {
        m_pDropTarget->AddRef();
        CoLockObjectExternal(m_pDropTarget, TRUE, FALSE);
        RegisterDragDrop(m_hWnd, m_pDropTarget);
        }

    m_pIStorage=pIStorage;
    Rename(pszFile);

    //Do initial setup if new file, otherwise Pages handles things.
    if (NULL==pszFile)
        {
        //Go initialize the Pages for the default printer.
        if (!PrinterSetup(NULL, TRUE))
            return DOCERR_COULDNOTOPEN;

        //Go create an initial page.
        m_pPG->PageInsert(0);
        }
    else
        {
        //Can't change an already saved configuration
        m_fPrintSetup=FALSE;
        }

    FDirtySet(FALSE);
    return DOCERR_NONE;
    }

/*
 * CPatronDoc::Save
 *
 * Purpose:
 *  Writes the file to a known filename, requiring that the user
 *  has previously used FileOpen or FileSaveAs in order to have
 *  a filename.
 *
 * Parameters:
 *  uType           UINT indicating the type of file the user
 *                  requested to save in the File Save As dialog.
 *  pszFile         LPTSTR under which to save.  If NULL, use the
 *                  current name.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CPatronDoc::Save(UINT uType, LPTSTR pszFile)
    {
    HRESULT     hr;
    LPSTORAGE   pIStorage;

    //Save or Save As with the same file is just a commit.
    if (NULL==pszFile
        || (NULL!=pszFile && 0==lstrcmpi(pszFile, m_szFile)))
        {
        WriteFmtUserTypeStg(m_pIStorage, m_cf
            , PSZ(IDS_CLIPBOARDFORMAT));

        //Insure pages are up to date.
        m_pPG->StorageUpdate(FALSE);

        //Commit everyting
        m_pIStorage->Commit(STGC_DEFAULT);

        FDirtySet(FALSE);
        return DOCERR_NONE;
        }

    /*
     * When we're given a name, open the storage, creating it new
     * if it does not exist or overwriting the old one.  Then CopyTo
     * from the current to the new, Commit the new, Release the old.
     */

    hr=StgCreateDocfile(pszFile, STGM_TRANSACTED | STGM_READWRITE
        | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

    if (FAILED(hr))
        return DOCERR_COULDNOTOPEN;

    WriteClassStg(pIStorage, CLSID_PatronPages);
    WriteFmtUserTypeStg(pIStorage, m_cf, PSZ(IDS_CLIPBOARDFORMAT));

    //Insure all pages are up-to-date.
    m_pPG->StorageUpdate(TRUE);

    //This also copies the CLSID we stuff in here on file creation.
    hr=m_pIStorage->CopyTo(NULL, NULL, NULL, pIStorage);

    if (FAILED(hr))
        {
        SCODE       sc;

        pIStorage->Release();
        sc=GetScode(hr);

        /*
         * If we failed because of low memory, use IRootStorage
         * to switch into the new file.
         */
        if (E_OUTOFMEMORY==sc)
            {
            LPROOTSTORAGE        pIRoot;

            //Delete file we already created
            DeleteFile(pszFile);

            if (FAILED(m_pIStorage->QueryInterface
                (IID_IRootStorage, (PPVOID)&pIRoot)))
                return DOCERR_WRITEFAILURE;

           #ifdef WIN32ANSI
            OLECHAR     szTemp[MAX_PATH];

            MultiByteToWideChar(CP_ACP, 0, pszFile, -1, szTemp, MAX_PATH);
            hr=pIRoot->SwitchToFile(szTemp);
           #else
            hr=pIRoot->SwitchToFile(pszFile);
           #endif
            pIRoot->Release();

            if (FAILED(hr))
                return DOCERR_WRITEFAILURE;

            //If successful, the Commit below finishes the save.
            pIStorage=m_pIStorage;
            m_pIStorage->AddRef();    //Matches Release below
            }
        }

    //CHAPTER21MOD
    if (m_fRename)
        Rename(pszFile);    //Update caption bar.
    //End CHAPTER21MOD

    pIStorage->Commit(STGC_DEFAULT);

    /*
     * Revert changes on the original storage.  If this was a temp
     * file, it's deleted since we used STGM_DELETEONRELEASE.
     */
    m_pIStorage->Release();

    //Make this new storage current
    m_pIStorage=pIStorage;
    m_pPG->StorageSet(pIStorage, TRUE, FALSE);

    FDirtySet(FALSE);

    return DOCERR_NONE;
    }

/*
 * CPatronDoc::Clip
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard, optionally implementing Cut by deleting the
 *  data in the current window after rendering.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *  fCut            BOOL indicating cut (TRUE) or copy (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::Clip(HWND hWndFrame, BOOL fCut)
    {
    if (NULL==m_pPG)
        return FALSE;

    return m_pPG->TenantClip(fCut);
    }

/*
 * CPatronDoc::Paste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  to the current figure in the editor window.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::Paste(HWND hWndFrame)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet=FALSE;
    FORMATETC       fe;
    TENANTTYPE      tType;

    if (NULL==m_pPG)
        return FALSE;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    //Go get type and format we *can* paste, then actually paste it.
    if (FQueryPasteFromData(pIDataObject, &fe, &tType))
        {
        fRet=PasteFromData(pIDataObject, &fe, tType, NULL
            , 0L, TRUE);
        }

    pIDataObject->Release();
    return fRet;
    }

/*
 * CPatronDoc::FQueryPaste
 *
 * Purpose:
 *  Determines if we can paste data from the clipboard.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPaste(void)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    fRet=FQueryPasteFromData(pIDataObject, NULL, NULL);

    fRet |= FQueryPasteLinkFromData(pIDataObject, NULL, NULL);
    pIDataObject->Release();
    return fRet;
    }

/*
 * CPatronDoc::PasteSpecial
 *
 * Purpose:
 *  Retrieves a specific data format from the clipboard and sends
 *  it to the editor window appropriately.
 *
 *  Note that if this function is called, then the appropriate
 *  format is available because the Paste menu item is only
 *  enabled if the format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::PasteSpecial(HWND hWndFrame)
    {
    OLEUIPASTESPECIAL   ps;
    OLEUIPASTEENTRY     rgPaste[6];
    UINT                rgcf[1];        //For ps.m_arrLinkTypes
    DWORD               dwData=0;
    UINT                uTemp;
    BOOL                fRet=FALSE;

    if (NULL==m_pPG)
        return FALSE;

    memset(&ps, 0, sizeof(ps));

    if (FAILED(OleGetClipboard(&ps.lpSrcDataObj)))
        return FALSE;

    ps.cbStruct=sizeof(ps);
    ps.hWndOwner=hWndFrame;

    ps.dwFlags=PSF_SELECTPASTE;
    ps.arrPasteEntries=rgPaste;

    //Set up Paste Special descriptor arrays.
    SETDefFormatEtc(rgPaste[0].fmtetc, m_cf, TYMED_HGLOBAL);
    rgPaste[0].lpstrFormatName=PSZ(IDS_CLIPBOARDFORMAT);
    rgPaste[0].lpstrResultText=PSZ(IDS_PASTEASPATRON);
    rgPaste[0].dwFlags=OLEUIPASTE_PASTEONLY;

    //Embedded objects can be iconic displays if the user wants.
    SETDefFormatEtc(rgPaste[1].fmtetc, m_cfEmbeddedObject
        , TYMED_ISTORAGE);
    rgPaste[1].lpstrFormatName=PSZ(IDS_PASTEOBJECT);
    rgPaste[1].lpstrResultText=PSZ(IDS_PASTEASOBJECT);

    /*
     * CAUTION:  Use OLEUI_PASTE with embedded objects or else
     * this item will not show up in the dialog.  I learned this the
     * hard way (that is, after about 6 hours of pulling hair!).
     */
    rgPaste[1].dwFlags=OLEUIPASTE_PASTE | OLEUIPASTE_ENABLEICON;

    SETDefFormatEtc(rgPaste[2].fmtetc,CF_METAFILEPICT,TYMED_MFPICT);
    rgPaste[2].lpstrFormatName=PSZ(IDS_PASTEMETAFILE);
    rgPaste[2].lpstrResultText=PSZ(IDS_PASTEASMETAFILE);
    rgPaste[2].dwFlags=OLEUIPASTE_PASTEONLY;

    SETDefFormatEtc(rgPaste[3].fmtetc, CF_DIB, TYMED_HGLOBAL);
    rgPaste[3].lpstrFormatName=PSZ(IDS_PASTEDIB);
    rgPaste[3].lpstrResultText=PSZ(IDS_PASTEASDIB);
    rgPaste[3].dwFlags=OLEUIPASTE_PASTEONLY;

    SETDefFormatEtc(rgPaste[4].fmtetc, CF_BITMAP, TYMED_GDI);
    rgPaste[4].lpstrFormatName=PSZ(IDS_PASTEBITMAP);
    rgPaste[4].lpstrResultText=PSZ(IDS_PASTEASBITMAP);
    rgPaste[4].dwFlags=OLEUIPASTE_PASTEONLY;

    SETDefFormatEtc(rgPaste[5].fmtetc,m_cfLinkSource,TYMED_ISTREAM);
    rgPaste[5].lpstrFormatName=PSZ(IDS_PASTELINK);
    rgPaste[5].lpstrResultText=PSZ(IDS_PASTEASLINK);
    rgPaste[5].dwFlags=OLEUIPASTE_LINKTYPE1 | OLEUIPASTE_ENABLEICON;

    //Types we can Paste Link from the clipboard.
    rgcf[0]=m_cfLinkSource;
    ps.arrLinkTypes=rgcf;
    ps.cLinkTypes=1;
    ps.cPasteEntries=6;

    uTemp=OleUIPasteSpecial(&ps);

    if (OLEUI_OK==uTemp)
        {
        UINT        i=ps.nSelectedIndex;
        TENANTTYPE  tType;

        if (ps.fLink)
            tType=TENANTTYPE_LINKEDOBJECTFROMDATA;
        else
            {
            if (1==ps.nSelectedIndex)
                tType=TENANTTYPE_EMBEDDEDOBJECTFROMDATA;
            else
                tType=TENANTTYPE_STATIC;
            }

        //Handle iconic aspects...from links as well
        if ((1==i || ps.fLink) && (PSF_CHECKDISPLAYASICON
            & ps.dwFlags) && NULL!=ps.hMetaPict)
            {
            rgPaste[i].fmtetc.dwAspect=DVASPECT_ICON;
            dwData=(DWORD)(UINT)ps.hMetaPict;
            }

        fRet=PasteFromData(ps.lpSrcDataObj, &rgPaste[i].fmtetc
            , tType, NULL, dwData, FALSE);

        //Always free this regardless of what we do with it.
        INOLE_MetafilePictIconFree(ps.hMetaPict);
        }

    ps.lpSrcDataObj->Release();
    return fRet;
    }

/*
 * CPatronDoc::FQueryPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Determines if we can paste data from a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which we might want to paste.
 *  pFE             LPFORMATETC in which to return the first format
 *                  we can use.  Ignored if NULL.
 *  ptType          PTENANTTYPE in which to store the type of
 *                  object we can paste.  Ignored if NULL.
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPasteFromData(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, PTENANTTYPE ptType)
    {
    FORMATETC       fe;
    HRESULT         hr, hr2;

    if (NULL!=(LPVOID)ptType)
        *ptType=TENANTTYPE_STATIC;

    //Any of our specific data here?
    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    hr=pIDataObject->QueryGetData(&fe);

    //If embedded object data is available, set the appropriate type
    hr2=OleQueryCreateFromData(pIDataObject);

    if (NOERROR==hr2)
        {
        if (NULL!=pFE)
            {
            /*
             * Default to content.  Paste will use
             * CFSTR_OBJECTDESCRIPTOR to figure the actual aspect.
             */
            SETDefFormatEtc(*pFE, m_cfEmbeddedObject
                , TYMED_ISTORAGE);
            }

        if (NULL!=(LPVOID)ptType)
            *ptType=TENANTTYPE_EMBEDDEDOBJECTFROMDATA;

        /*
         * Return now if PatronObject wasn't available, otherwise
         * break out so that pFE gets PatronObject format.
         */
        if (NOERROR!=hr)
            return TRUE;
        }

    if (NOERROR!=hr && NOERROR!=hr2)
        {
        //Try metafile, DIB, then bitmap, setting fe each time
        SETDefFormatEtc(fe, CF_METAFILEPICT, TYMED_MFPICT);
        hr=pIDataObject->QueryGetData(&fe);

        if (NOERROR!=hr)
            {
            SETDefFormatEtc(fe, CF_DIB, TYMED_HGLOBAL);
            hr=pIDataObject->QueryGetData(&fe);

            if (NOERROR!=hr)
                {
                SETDefFormatEtc(fe, CF_BITMAP, TYMED_GDI);
                hr=pIDataObject->QueryGetData(&fe);
                }
            }
        }

    if (NOERROR==hr && NULL!=pFE)
        *pFE=fe;

    return (NOERROR==hr);
    }

/*
 * CPatronDoc::FQueryPasteLinkFromData
 * (Protected)
 *
 * Purpose:
 *  Determines if we can paste link from a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which we might want to paste.
 *  pFE             LPFORMATETC in which to return the first format
 *                  we can use.  Ignored if NULL.
 *  ptType          PTENANTTYPE in which to store the type of object
 *                  we can paste.  Ignored if NULL.
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPasteLinkFromData(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, PTENANTTYPE ptType)
    {
    HRESULT         hr;

    if (NULL==pIDataObject)
        return FALSE;

    hr=OleQueryLinkFromData(pIDataObject);

    if (NOERROR!=hr)
        return FALSE;

    if (NULL!=pFE)
        SETDefFormatEtc(*pFE, m_cfLinkSource, TYMED_ISTREAM);

    if (NULL!=(LPVOID)ptType)
        *ptType=TENANTTYPE_LINKEDOBJECTFROMDATA;

    return TRUE;
    }

/*
 * CPatronDoc::PasteFromData
 * (Protected)
 *
 * Purpose:
 *  Retrieves the private data format from a data object and sets
 *  it to the current figure in the editor window.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *  pFE             LPFORMATETC to use in the paste. Cannot be NULL.
 *  tType           TENANTTYPE to paste.
 *  ppo             PPATRONOBJECT containing placement data.
 *  dwData          DWORD extra data sensitive to tType
 *  fUseObjDesc     BOOL indicating to use CFSTR_OBJECTDESCRIPTOR
 *                  format for determining the aspect of the object
 *                  if the format is available.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::PasteFromData(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, TENANTTYPE tType, PPATRONOBJECT ppo
    , DWORD dwData, BOOL fUseObjDesc)
    {
    BOOL                fRet;
    HRESULT             hr;
    PATRONOBJECT        po;
    STGMEDIUM           stm;
    LPOBJECTDESCRIPTOR  pOD;
    FORMATETC           fe;
    BOOL                fRelease=FALSE;

    if (NULL==pFE)
        return FALSE;

    //If not given any placement data, see if we can retrieve it
    if (pFE->cfFormat==m_cf && NULL==ppo)
        {
        hr=pIDataObject->GetData(pFE, &stm);

        if (SUCCEEDED(hr))
            {
            ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);

            po=*ppo;
            ppo=&po;

            //If there's an object here, make sure type is right.
            if (ppo->fe.cfFormat==m_cfEmbeddedObject)
                tType=TENANTTYPE_EMBEDDEDOBJECTFROMDATA;

            if (ppo->fe.cfFormat==m_cfLinkSource)
                tType=TENANTTYPE_LINKEDOBJECTFROMDATA;

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);
            }
        }

    /*
     * If we're told to look at CFSTR_OBJECTDESCRIPTOR, then try to get
     * the data and copy the aspect out of it.  We're not interested
     * in any other part of it, however.
     */
    if (fUseObjDesc)
        {
        SETDefFormatEtc(fe, m_cfObjectDescriptor, TYMED_HGLOBAL);

        hr=pIDataObject->GetData(&fe, &stm);

        if (FAILED(hr))
            {
            SETDefFormatEtc(fe, m_cfLinkSrcDescriptor
                , TYMED_HGLOBAL);
            hr=pIDataObject->GetData(&fe, &stm);
            }

        if (SUCCEEDED(hr))
            {
            pOD=(LPOBJECTDESCRIPTOR)GlobalLock(stm.hGlobal);
            pFE->dwAspect=pOD->dwDrawAspect;

            if (NULL!=ppo)
                ppo->fe.dwAspect=pFE->dwAspect;

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);

            /*
             * Furthermore, if pFE->dwAspect is DVASPECT_ICON, get
             * the metafile which will always be the icon
             * representation.
             */
            SETFormatEtc(fe, CF_METAFILEPICT, DVASPECT_ICON, NULL
                , TYMED_MFPICT, -1);

            if (SUCCEEDED(pIDataObject->GetData(&fe, &stm)))
                {
                dwData=(DWORD)(UINT)stm.hGlobal;
                fRelease=TRUE;

}
            }
        }

    fRet=m_pPG->TenantCreate(tType, pIDataObject, pFE, ppo, dwData);

    //Release the stm from the last GetData
    if (fRelease)
        ReleaseStgMedium(&stm);

    if (fRet)
        {
        //Disable Printer Setup once we've created a tenant.
        m_fPrintSetup=FALSE;
        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CPatronDoc::FQueryEnableEditLinks
 *
 * Purpose:
 *  Determines whether or not the Edit Links menu item should be
 *  enabled on the frame window menu.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE to enable the item, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryEnableEditLinks(void)
    {
    return m_pPG->FQueryLinksInPage();
    }

/*
 * CPatronDoc::EditLinks
 *
 * Purpose:
 *  Invokes the links dialog in which the end-user can manipulate
 *  all the links in the current page.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame window to use as the dialog
 *                  parent.
 *
 * Return Value:
 *  BOOL            TRUE if the function was successful,
 *                  FALSE otherwise.
 */

BOOL CPatronDoc::EditLinks(HWND hWndFrame)
    {
    UINT                    uRet;
    OLEUIEDITLINKS          el;
    PCIOleUILinkContainer   pIUILinks;

    memset(&el, 0, sizeof(el));
    el.cbStruct=sizeof(el);
    el.hWndOwner=hWndFrame;

    if (!m_pPG->GetUILinkContainer(&pIUILinks))
        return FALSE;

    el.lpOleUILinkContainer=pIUILinks;
    uRet=OleUIEditLinks(&el);

    //Only the IOleUILinkContainer interface will know dirtying.
    m_fDirty=pIUILinks->m_fDirty;

    el.lpOleUILinkContainer->Release();
    return TRUE;
    }

/*
 * CPatronDoc::ShowOrQueryObjectTypes
 *
 * Purpose:
 *  If querying, we return whether or not ShowObjects is active
 *  in this document.  Otherwise pass-through to
 *  CPages::ShowObjectTypes.
 *
 * Parameters:
 *  fQuery          BOOL indicating query or set.
 *  fShow           BOOL indicating to show the type or not.
 *
 * Return Value:
 *  None
 */

BOOL CPatronDoc::ShowOrQueryObjectTypes(BOOL fQuery, BOOL fShow)
    {
    if (fQuery)
        return m_fShowTypes;

    m_fShowTypes=fShow;
    m_pPG->ShowObjectTypes(fShow);
    return TRUE;
    }

/*
 * CPatronDoc::NewPage
 *
 * Purpose:
 *  Creates a new page in the document's pages control after the
 *  current page.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::NewPage(void)
    {
    FDirtySet(TRUE);
    return m_pPG->PageInsert(0);
    }

/*
 * CPatronDoc::DeletePage
 *
 * Purpose:
 *  Deletes the current page from the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the now current page.
 */

UINT CPatronDoc::DeletePage(void)
    {
    FDirtySet(TRUE);
    return m_pPG->PageDelete(0);
    }

/*
 * CPatronDoc::NextPage
 *
 * Purpose:
 *  Shows the next page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::NextPage(void)
    {
    UINT        iPage;

    iPage=m_pPG->CurPageGet();
    return m_pPG->CurPageSet(++iPage);
    }

/*
 * CPatronDoc::PreviousPage
 *
 * Purpose:
 *  Shows the previous page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::PreviousPage(void)
    {
    UINT        iPage;

    //If iPage is zero, then we wrap around to the end.
    iPage=m_pPG->CurPageGet();
    return m_pPG->CurPageSet(--iPage);
    }

/*
 * CPatronDoc::FirstPage
 *
 * Purpose:
 *  Shows the first page page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::FirstPage(void)
    {
    return m_pPG->CurPageSet(0);
    }

/*
 * CPatronDoc::LastPage
 *
 * Purpose:
 *  Shows the last page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the last page.
 */

UINT CPatronDoc::LastPage(void)
    {
    return m_pPG->CurPageSet(NOVALUE);
    }

/*
 * CPatronDoc::Rename
 *
 * Purpose:
 *  Overrides the normal rename to include notification of tenants
 *  in this document of the new name.  This is so embedded objects
 *  are told the correct name through IOleObject::SetHostNames.
 *
 * Parameters:
 *  pszFile         LPTSTR to the new filename.
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Rename(LPTSTR pszFile)
    {
    LPMONIKER   pmk;

    //We don't need to change the base class, just augment...
    CDocument::Rename(pszFile);

    //CHAPTER21MOD
    //Unregister the old moniker (m_dwRegROT set to zero).
    INOLE_RevokeAsRunning(&m_dwRegROT);

    if (NULL==pszFile)
        return;

    CreateFileMoniker(pszFile, &pmk);

    if (NULL!=pmk)
        {
        LPMONIKER   pmkAll;

        INOLE_RegisterAsRunning(this, pmk
            , ROTFLAGS_REGISTRATIONKEEPSALIVE, &m_dwRegROT);

        //Give a moniker to linked objects in tenants.
        m_pPG->NotifyTenantsOfRename(pszFile, pmk);

        //Register a File!"\" wildcard moniker as well
        CreateItemMoniker(TEXT("!"), TEXT("\\"), &pmkAll);

        if (NULL!=pmkAll)
            {
            LPMONIKER   pmkWild;

            INOLE_RevokeAsRunning(&m_dwRegROTWild);
            pmk->ComposeWith(pmkAll, FALSE, &pmkWild);

            if (NULL!=pmkWild)
                {
                INOLE_RegisterAsRunning(this, pmkWild, 0
                    , &m_dwRegROTWild);
                pmkWild->Release();
                }

            pmkAll->Release();
            }

        //No need for us to hold on to this.
        pmk->Release();
        }
    //End CHAPTER21MOD

    return;
    }

/*
 * CPatronDoc::InsertObject
 *
 * Purpose:
 *  Retrieves a CLSID or a filename from the Insert Object dialog
 *  box and creates an object using those identifiers.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::InsertObject(HWND hWndFrame)
    {
    OLEUIINSERTOBJECT   io;
    DWORD               dwData=0;
    TCHAR               szFile[CCHPATHMAX];
    UINT                uTemp;
    BOOL                fRet=FALSE;

    if (NULL==m_pPG)
        return FALSE;

    memset(&io, 0, sizeof(io));

    io.cbStruct=sizeof(io);
    io.hWndOwner=hWndFrame;

    szFile[0]=0;
    io.lpszFile=szFile;
    io.cchFile=CCHPATHMAX;

    //We now support linking, so remove the IOF_DISABLELINK flag.
    io.dwFlags=IOF_SELECTCREATENEW;

    uTemp=OleUIInsertObject(&io);

    if (OLEUI_OK==uTemp)
        {
        TENANTTYPE      tType;
        LPVOID          pv;
        FORMATETC       fe;

        SETDefFormatEtc(fe, 0, TYMED_NULL);

        if (io.dwFlags & IOF_SELECTCREATENEW)
            {
            tType=TENANTTYPE_EMBEDDEDOBJECT;
            pv=&io.clsid;
            }
        else
            {
            if (io.dwFlags & IOF_CHECKLINK)
                tType=TENANTTYPE_LINKEDFILE;
            else
                tType=TENANTTYPE_EMBEDDEDFILE;

            pv=szFile;
            }

        if ((io.dwFlags & IOF_CHECKDISPLAYASICON)
            && NULL!=io.hMetaPict)
            {
            fe.dwAspect=DVASPECT_ICON;
            dwData=(DWORD)(UINT)io.hMetaPict;
            }

        fRet=m_pPG->TenantCreate(tType, pv, &fe, NULL, dwData);

        //Free this regardless of what we do with it.
        INOLE_MetafilePictIconFree(io.hMetaPict);

        if (fRet)
            {
            //Disable Printer Setup once we've created a tenant.
            m_fPrintSetup=FALSE;
            FDirtySet(TRUE);
            }
        }

    return fRet;
    }

/*
 * CPatronDoc::ActivateObject
 *
 * Purpose:
 *  Executes a verb on the currently selected object.
 *
 * Parameters:
 *  iVerb           LONG of the selected verb.
 *
 * Return Value:
 *  None
 */

void CPatronDoc::ActivateObject(LONG iVerb)
    {
    m_pPG->ActivateObject(iVerb);
    return;
    }

/*
 * CPatronDoc::ConvertObject
 *
 * Purpose:
 *  Invokes the Convert dialog on the current object.  Here it's
 *  just a pass-through to the pages.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame window.
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful, FALSE otherwise.
 */

BOOL CPatronDoc::ConvertObject(HWND hWndFrame)
    {
    return m_pPG->ConvertObject(hWndFrame);
    }


DRAGDROP.CPP

/*
 * DRAGDROP.CPP
 * Patron Chapter 21
 *
 * Member functions of the CPages class concerned with drag-drop
 * and other object-placement functionality.  Moved here to clean
 * up CPAGES.CPP somewhat.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPages::UTestDroppablePoint
 *
 * Purpose:
 *  Returns if the point in pptl is on the paper in the current page
 *  and alternately if it's within horizontal or vertical inset
 *  regions.
 *
 * Parameters:
 *  pptl            PPOINTL containing the point in screen
 *                  coordinates.
 *
 * Return Value:
 *  UINT            A UDROP_* value.
 */

UINT CPages::UTestDroppablePoint(PPOINTL pptl)
    {
    POINT       pt;
    RECT        rc, rcT, rcC;
    UINT        uRet;

    POINTFROMPOINTL(pt, *pptl);
    ScreenToClient(m_hWnd, &pt);

    CalcBoundingRect(&rc, FALSE);

    GetClientRect(m_hWnd, &rcC);
    IntersectRect(&rcT, &rc, &rcC);

    //Check for at least a client area hit.
    if (!PtInRect(&rcT, pt))
        return UDROP_NONE;

    uRet=UDROP_CLIENT;

    //Scroll checks happen on client area, not document area
    if (PtInRect(&rcC, pt))
        {
        //Check horizontal inset
        if (pt.x <= rcC.left+(int)m_uScrollInset)
            uRet |= UDROP_INSETLEFT;
        else if (pt.x >= rcC.right-(int)m_uScrollInset)
            uRet |= UDROP_INSETRIGHT;

        //Check vertical inset
        if (pt.y <= rcC.top+(int)m_uScrollInset)
            uRet |= UDROP_INSETTOP;
        else if (pt.y >= rcC.bottom-(int)m_uScrollInset)
            uRet |= UDROP_INSETBOTTOM;
        }

    return uRet;
    }

/*
 * CPages::DrawDropTargetRect
 *
 * Purpose:
 *  Draws a dotted rectangle on the Pages window to show where
 *  a drop might occur.  This is a toggle function.
 *
 * Parameters:
 *  pptl            PPOINTL containing the upper-left point
 *                  in screen coordinates.
 *  pszl            LPSIZEL containing the rect extents in device
 *                  units.
 *
 * Return Value:
 *  None
 */

void CPages::DrawDropTargetRect(PPOINTL pptl, LPSIZEL pszl)
    {
    POINT           pt;
    RECT            rc, rcT;
    HDC             hDC;

    if (NULL==pptl && NULL==pszl)
        {
        /*
         * This case is used from WM_PAINT in pagewin.cpp so we can
         * control the proper visibility of the drag rectangle when
         * we're scrolling.  If drag-drop happens between two apps,
         * then any ScrollWindow will cause a WM_PAINT after we
         * leave DragOver, so that paint would normally overwrite
         * the last drawn rectangle.  To alleviate that, WM_PAINT
         * will remove the last rect by sending us NULLs, paint,
         * then reinstate the rectangle.
         */
        pptl=&m_ptlRect;
        pszl=&m_szlRect;
        }
    else
        {
        m_ptlRect.x=pptl->x;
        m_ptlRect.y=pptl->y;
        m_szlRect.cx=pszl->cx;
        m_szlRect.cy=pszl->cy;

        //Flag is only affected from IDropTarget, not WM_PAINT calls
        m_fDragRectShown=!m_fDragRectShown;
        }

    POINTFROMPOINTL(pt, *pptl);
    ScreenToClient(m_hWnd, &pt);
    SetRect(&rc, pt.x, pt.y, pt.x+(int)pszl->cx
        , pt.y+(int)pszl->cy);

    CalcBoundingRect(&rcT, FALSE);
    IntersectRect(&rc, &rc, &rcT);

    if (!IsRectEmpty(&rc))
        {
        hDC=GetDC(m_hWnd);
        DrawFocusRect(hDC, &rc);
        ReleaseDC(m_hWnd, hDC);
        }

    return;
    }

/*
 * CPages::AdjustPosition
 *
 * Purpose:
 *  Adjusts a point for the scrolling offset and then converts it
 *  and a size from device into LOMETRIC units.
 *
 * Parameters:
 *  pptl            PPOINTL to adjust and convert
 *  pszl            LPSIZEL to convert
 *
 * Return Value:
 *  None
 */

void CPages::AdjustPosition(PPOINTL pptl, LPSIZEL pszl)
    {
    RECT        rc;

    SetRect(&rc, m_xPos+(int)pptl->x
        , m_yPos+(int)pptl->y, (int)pszl->cx, (int)pszl->cy);

    RectConvertMappings(&rc, NULL, FALSE);

    pptl->x=rc.left;
    pptl->y=rc.top;

    pszl->cx=rc.right;
    pszl->cy=rc.bottom;

    return;
    }


DROPSRC.CPP

/*
 * DROPSRC.CPP
 * Patron Chapter 21
 *
 * Implementation of a DropSource object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CDropSource::CDropSource
 * CDropSource::~CDropSource
 *
 * Constructor Parameters:
 *  None
 */

CDropSource::CDropSource(void)
    {
    m_cRef=0;
    return;
    }

CDropSource::~CDropSource(void)
    {
    return;
    }

/*
 * CDropSource::QueryInterface
 * CDropSource::AddRef
 * CDropSource::Release
 *
 * Purpose:
 *  IUnknown members for CDropSource object.
 */

STDMETHODIMP CDropSource::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDropSource==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDropSource::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropSource::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDropSource::QueryDragContinue
 *
 * Purpose:
 *  Determines whether to continue a drag operation or cancel it.
 *
 * Parameters:
 *  fEsc            BOOL indicating that the ESC key was pressed.
 *  grfKeyState     DWORD providing states of keys and mouse buttons
 *
 * Return Value:
 *  HRESULT         DRAGDROP_S_CANCEL to stop the drag,
 *                  DRAGDROP_S_DROP to drop the data where it is,
 *                  or NOERROR to continue.
 */

STDMETHODIMP CDropSource::QueryContinueDrag(BOOL fEsc, DWORD grfKeyState)
    {
    if (fEsc)
        return ResultFromScode(DRAGDROP_S_CANCEL);

    if (!(grfKeyState & MK_LBUTTON))
        return ResultFromScode(DRAGDROP_S_DROP);

    return NOERROR;
    }

/*
 * CDropSource::GiveFeedback
 *
 * Purpose:
 *  Provides cursor feedback to the user since the source task
 *  always has the mouse capture.  We can also provide any other
 *  type of feedback above cursors if we so desire.
 *
 * Parameters:
 *  dwEffect        DWORD effect flags returned from the last target
 *
 * Return Value:
 *  HRESULT         NOERROR if you set a cursor yourself or
 *                  DRAGDROP_S_USEDEFAULTCURSORS to let OLE do
 *                  the work.
 */

STDMETHODIMP CDropSource::GiveFeedback(DWORD dwEffect)
    {
    return ResultFromScode(DRAGDROP_S_USEDEFAULTCURSORS);
    }


DROPTGT.CPP

/*
 * DROPTGT.CPP
 * Patron Chapter 21
 *
 * Implementation of a DropTarget object
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CDropTarget::CDropTarget
 * CDropTarget::~CDropTarget
 *
 * Constructor Parameters:
 *  pDoc            PCPatronDoc of the window containing us.
 */

CDropTarget::CDropTarget(PCPatronDoc pDoc)
    {
    m_cRef=0;
    m_pDoc=pDoc;

    m_pIDataObject=NULL;
    return;
    }

CDropTarget::~CDropTarget(void)
    {
    return;
    }

/*
 * CDropTarget::QueryInterface
 * CDropTarget::AddRef
 * CDropTarget::Release
 *
 * Purpose:
 *  IUnknown members for CDropTarget object.
 */

STDMETHODIMP CDropTarget::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDropTarget==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDropTarget::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropTarget::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDropTarget::DragEnter
 *
 * Purpose:
 *  Indicates that data in a drag operation has been dragged over
 *  our window that's a potential target.  We are to decide if it's
 *  something in which we're interested.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT providing the source data.
 *  grfKeyState     DWORD flags: states of keys and mouse buttons.
 *  pt              POINTL coordinates in the client space of
 *                  the document.
 *  pdwEffect       LPDWORD into which we'll place the appropriate
 *                  effect flag for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragEnter(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    PCPages         ppg=m_pDoc->m_pPG;
    HWND            hWnd;
    FORMATETC       fe;
    STGMEDIUM       stm;
    UINT            uRet;

    m_fFeedback=FALSE;
    m_pIDataObject=NULL;

    if (!m_pDoc->FQueryPasteFromData(pIDataSource, &fe, NULL))
        {
        *pdwEffect=DROPEFFECT_NONE;
        return NOERROR;
        }

    //Check if we can link from this data object as well.
    ppg->m_fLinkAllowed
        =(NOERROR==OleQueryLinkFromData(pIDataSource));

    //We never allow it dragging in ourselves.
    ppg->m_fLinkAllowed &= !ppg->m_fDragSource;

    //Check if this is a valid drop point.
    uRet=ppg->UTestDroppablePoint(&pt);
    ppg->m_uLastTest=uRet;

    if (UDROP_NONE==uRet)
        *pdwEffect=DROPEFFECT_NONE;
    else
        {
        //Default is move if we can, in fact drop here.
        *pdwEffect=DROPEFFECT_MOVE;

        if (grfKeyState & MK_CONTROL)
            {
            if (ppg->m_fLinkAllowed && (grfKeyState & MK_SHIFT))
                *pdwEffect=DROPEFFECT_LINK;
            else
                *pdwEffect=DROPEFFECT_COPY;
            }
        }

    m_pIDataObject=pIDataSource;
    m_pIDataObject->AddRef();

    /*
     * Determine the size of the data, if we can.  The default is
     * a small rectangle since we can't easily tell what size
     * something will be if we're pulling in a metafile or bitmap.
     * It's not a good idea to render it here with GetData just to
     * find that out. We only know the size if it's our own object
     * in which case a GetData will be fast.
     */

    if (fe.cfFormat==m_pDoc->m_cf)
        {
        if (SUCCEEDED(pIDataSource->GetData(&fe, &stm)))
            {
            PPATRONOBJECT   ppo;
            RECT            rc;

            ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);

            SetRect(&rc, (int)ppo->szl.cx, -(int)ppo->szl.cy, 0, 0);
            RectConvertMappings(&rc, NULL, TRUE);
            SETSIZEL(m_szl, rc.left, rc.top);

            m_ptPick=ppo->ptlPick;
            m_fe=ppo->fe;

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);
            }
        }
    else
        {
        SETSIZEL(m_szl, 30, 30);
        m_ptPick.x=0;
        m_ptPick.y=0;
        m_fe.cfFormat=0;

        /*
         * Try to get CFSTR_OBJECTDESCRIPTOR which might have a size
         * and a pick point.  If it exists, then always use the
         * point but still default to a 30*30 size if the sizes
         * are zero.
         */
        uRet=RegisterClipboardFormat(CFSTR_OBJECTDESCRIPTOR);
        SETDefFormatEtc(fe, uRet, TYMED_HGLOBAL);

        if (SUCCEEDED(pIDataSource->GetData(&fe, &stm)))
            {
            LPOBJECTDESCRIPTOR  pOD;

            pOD=(LPOBJECTDESCRIPTOR)GlobalLock(stm.hGlobal);

            //Get the size, converting to LOMETRIC.
            if (0!=pOD->sizel.cx && 0!=pOD->sizel.cy)
                {
                XformSizeInHimetricToPixels(NULL, &pOD->sizel
                    , &m_szl);
                }

            //POINTL and SIZEL are interchangeable
            XformSizeInHimetricToPixels(NULL, (LPSIZEL)&pOD->pointl
                , (LPSIZEL)&m_ptPick);

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);
            }
        }

    //Bring the document window up front, show what a drop will do.
    hWnd=m_pDoc->Window();
    BringWindowToTop(hWnd);
    UpdateWindow(hWnd);

    ppg->m_uVScrollCode=NOVALUE;
    ppg->m_uHScrollCode=NOVALUE;
    m_fPendingRepaint=FALSE;

    pt.x-=m_ptPick.x;
    pt.y-=m_ptPick.y;

    m_ptLast=pt;
    m_fFeedback=TRUE;
    ppg->DrawDropTargetRect(&pt, &m_szl);

    return NOERROR;
    }

/*
 * CDropTarget::DragOver
 *
 * Purpose:
 *  Indicates that the mouse was moved inside the window represented
 *  by this drop target.  This happens on every WM_MOUSEMOVE, so
 *  this function should be very efficient.
 *
 * Parameters:
 *  grfKeyState     DWORD providing the current keyboard and
 *                  mouse states
 *  pt              POINTL where the mouse currently is.
 *  pdwEffect       LPDWORD in which to store the effect flag
 *                  for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragOver(DWORD grfKeyState, POINTL pt
    , LPDWORD pdwEffect)
    {
    PCPages     ppg=m_pDoc->m_pPG;
    UINT        uRet, uLast;
    UINT        xPos, yPos;

    *pdwEffect=DROPEFFECT_NONE;

    if (NULL==m_pIDataObject)
        return NOERROR;

    //Check if this is still a valid point.  uRet used below as well
    uRet=ppg->UTestDroppablePoint(&pt);

    if (UDROP_NONE==uRet)
        *pdwEffect=DROPEFFECT_NONE;
    else
        {
        //Store these before possibly ORing in DROPEFFECT_SCROLL
        *pdwEffect=DROPEFFECT_MOVE;

        if (grfKeyState & MK_CONTROL)
            {
            if (ppg->m_fLinkAllowed && (grfKeyState & MK_SHIFT))
                *pdwEffect=DROPEFFECT_LINK;
            else
                *pdwEffect=DROPEFFECT_COPY;
            }
        }

    //If we haven't moved and we are not scrolling, then we're done.
    if ((pt.x-m_ptPick.x==m_ptLast.x)
        && (pt.y-m_ptPick.y==m_ptLast.y)
        && !((UDROP_INSETHORZ|UDROP_INSETVERT) & ppg->m_uLastTest))
        {
        return NOERROR;
        }

    //Remove the last feedback rectangle.
    if (m_fFeedback)
        ppg->DrawDropTargetRect(&m_ptLast, &m_szl);

    uLast=ppg->m_uLastTest;
    ppg->m_uLastTest=uRet;

    if (UDROP_NONE==uRet)
        {
        //If we're now an invalid point, better repaint as necessary
        if (m_fPendingRepaint)
            {
            UpdateWindow(ppg->m_hWnd);
            m_fPendingRepaint=FALSE;
            }

        ppg->m_uVScrollCode=NOVALUE;
        ppg->m_uHScrollCode=NOVALUE;
        m_fFeedback=FALSE;
        return NOERROR;
        }

    /*
     * Scrolling is a little tricky:  We get a DragOver pulse even
     * if we didn't move.  First we have to delay scrolling for
     * ppg->m_uScrollDelay clock ticks which we can determine using
     * GetTickCount.  Timers do not work here since we may not be
     * yielding to our message loop.
     *
     * Once we know we are scrolling then we determine if we
     * scroll again or if we reset the scrolling state.
     */

    if ((UDROP_INSETHORZ & uLast) && !(UDROP_INSETHORZ & uRet))
        ppg->m_uHScrollCode=NOVALUE;

    if (!(UDROP_INSETHORZ & uLast) && (UDROP_INSETHORZ & uRet))
        {
        ppg->m_dwTimeLast=GetTickCount();
        ppg->m_uHScrollCode=(0!=(UDROP_INSETLEFT & uRet))
            ? SB_LINELEFT : SB_LINERIGHT; //Same as UP & DOWN codes.
        }

    if ((UDROP_INSETVERT & uLast) && !(UDROP_INSETVERT & uRet))
        ppg->m_uVScrollCode=NOVALUE;

    if (!(UDROP_INSETVERT & uLast) && (UDROP_INSETVERT & uRet))
        {
        ppg->m_dwTimeLast=GetTickCount();
        ppg->m_uVScrollCode=(0!=(UDROP_INSETTOP & uRet))
            ? SB_LINEUP : SB_LINEDOWN;
        }

    //Only change the last time if ALL scrolling stops.
    if (NOVALUE==ppg->m_uHScrollCode && NOVALUE==ppg->m_uVScrollCode)
        ppg->m_dwTimeLast=0L;

    //Set the scroll effect on any inset hit.
    if ((UDROP_INSETHORZ | UDROP_INSETVERT) & uRet)
        *pdwEffect |= DROPEFFECT_SCROLL;

    xPos=ppg->m_xPos;
    yPos=ppg->m_yPos;

    //Has the delay elapsed?  We can scroll if so
    if (ppg->m_dwTimeLast!=0
        && (GetTickCount()-ppg->m_dwTimeLast)
        > (DWORD)ppg->m_uScrollDelay)
        {
        if (NOVALUE!=ppg->m_uHScrollCode)
            {
            m_fPendingRepaint=TRUE;
            SendMessage(ppg->m_hWnd, WM_HSCROLL
                , ppg->m_uHScrollCode, 0L);
            }

        if (NOVALUE!=ppg->m_uVScrollCode)
            {
            m_fPendingRepaint=TRUE;
            SendMessage(ppg->m_hWnd, WM_VSCROLL
                , ppg->m_uVScrollCode, 0L);
            }
        }

    //If we didn't scroll but have a pending repaint, do it now.
    if (xPos==ppg->m_xPos && yPos==ppg->m_yPos && m_fPendingRepaint)
        {
        UpdateWindow(ppg->m_hWnd);
        m_fPendingRepaint=FALSE;
        }

    pt.x-=m_ptPick.x;
    pt.y-=m_ptPick.y;

    m_ptLast=pt;
    m_fFeedback=TRUE;
    ppg->DrawDropTargetRect(&pt, &m_szl);

    return NOERROR;
    }

/*
 * CDropTarget::DragLeave
 *
 * Purpose:
 *  Informs the drop target that the operation has left its window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragLeave(void)
    {
    PCPages         ppg=m_pDoc->m_pPG;

    if (NULL==m_pIDataObject)
        return NOERROR;

    //Stop scrolling
    ppg->m_uHScrollCode=NOVALUE;
    ppg->m_uVScrollCode=NOVALUE;

    if (m_fPendingRepaint)
        UpdateWindow(ppg->m_hWnd);

    //Remove the last feedback rectangle.
    if (m_fFeedback)
        ppg->DrawDropTargetRect(&m_ptLast, &m_szl);

    m_fFeedback=FALSE;
    m_pIDataObject->Release();
    return NOERROR;
    }

/*
 * CDropTarget::Drop
 *
 * Purpose:
 *  Instructs the drop target to paste the data that was just now
 *  dropped on it.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT from which we'll paste.
 *  grfKeyState     DWORD providing current keyboard/mouse state.
 *  pt              POINTL at which the drop occurred.
 *  pdwEffect       LPDWORD in which to store what you did.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::Drop(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    PCPages         ppg=m_pDoc->m_pPG;
    BOOL            fRet=TRUE;
    FORMATETC       fe;
    TENANTTYPE      tType;
    PATRONOBJECT    po;
    POINT           ptS;

    *pdwEffect=DROPEFFECT_NONE;

    if (NULL==m_pIDataObject)
        return ResultFromScode(E_FAIL);

    if (UDROP_NONE==ppg->UTestDroppablePoint(&pt))
        return ResultFromScode(E_FAIL);

    //Stop scrolling
    ppg->m_uHScrollCode=NOVALUE;
    ppg->m_uVScrollCode=NOVALUE;

    if (m_fPendingRepaint)
        UpdateWindow(ppg->m_hWnd);

    //2.  Remove the UI feedback
    if (m_fFeedback)
        ppg->DrawDropTargetRect(&m_ptLast, &m_szl);

    m_pIDataObject->Release();

    /*
     * Check if we can do the paste, and if so, tell our pasting
     * mechanism exactly where to place us.
     */
    pt.x-=m_ptPick.x;
    pt.y-=m_ptPick.y;

    POINTFROMPOINTL(ptS, pt);
    ScreenToClient(ppg->Window(), &ptS);
    POINTLFROMPOINT(po.ptl, ptS);

    //This is true if we didn't see placement data in DragEnter
    if (0!=m_fe.cfFormat)
        {
        po.szl.cx=m_szl.cx;         //We stored these positive
        po.szl.cy=-m_szl.cy;
        }
    else
        SETSIZEL(po.szl, 0, 0); //Ask object for its size.

    //Adjust for scrolling and mapping mode.
    ppg->AdjustPosition(&po.ptl, &po.szl);

    /*
     * If we're in the same document and moving, then we can just
     * stuff the Pages' m_ptDrop which will move us and return.
     */
    if (ppg->m_fDragSource && !(grfKeyState & MK_CONTROL))
        {
        *pdwEffect=DROPEFFECT_MOVE;
        ppg->m_fMoveInPage=TRUE;
        ppg->m_ptDrop=po.ptl;
        return NOERROR;
        }

    /*
     * Otherwise, paste either from another document or from
     * the same document which will always be a copy to the new
     * point.
     */

    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        {
        if (ppg->m_fLinkAllowed && (grfKeyState & MK_SHIFT))
            *pdwEffect=DROPEFFECT_LINK;
        else
            *pdwEffect=DROPEFFECT_COPY;
        }

    ppg->m_fMoveInPage=FALSE;

    /*
     * We know linking is desired if effect flag is set, so this
     * function will get the FORMATETC for linking.  Otherwise
     * FQueryPasteFromData will get the other FORMATETC to use.
     */
    if (DROPEFFECT_LINK==*pdwEffect)
        {
        fRet=m_pDoc->FQueryPasteLinkFromData(pIDataSource, &fe
            , &tType);
        }
    else
        fRet=m_pDoc->FQueryPasteFromData(pIDataSource, &fe, &tType);

    if (fRet)
        {
        //Copy the real format if we have placement data.
        po.fe=(m_pDoc->m_cf==fe.cfFormat) ? m_fe : fe;

        //Flag PasteFromData to use CFSTR_OBJECTDESCRIPTOR
        fRet=m_pDoc->PasteFromData(pIDataSource, &fe, tType
            , &po, 0, TRUE);
        }

    if (!fRet)
        return ResultFromScode(E_FAIL);

    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        *pdwEffect=DROPEFFECT_COPY;

    return NOERROR;
    }


IADVSINK.CPP

/*
 * IADVSINK.CPP
 * Patron Chapter 21
 *
 * Implementation of the IAdviseSink interface for Patron's tenants.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CImpIAdviseSink::CImpIAdviseSink
 * CImpIAdviseSink::~CImpIAdviseSink
 *
 * Parameters (Constructor):
 *  pTenant         PCTenant of the tenant we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIAdviseSink::CImpIAdviseSink(PCTenant pTenant
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pTen=pTenant;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIAdviseSink::~CImpIAdviseSink(void)
    {
    return;
    }

/*
 * CImpIAdviseSink::QueryInterface
 * CImpIAdviseSink::AddRef
 * CImpIAdviseSink::Release
 *
 * Purpose:
 *  IUnknown members for CImpIAdviseSink object.
 */

STDMETHODIMP CImpIAdviseSink::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIAdviseSink::OnDataChange
 *
 * Unused since we don't IDataObject::Advise.
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnDataChange(LPFORMATETC pFEIn
    , LPSTGMEDIUM pSTM)
    {
    return;
    }

/*
 * CImpIAdviseSink::OnViewChange
 *
 * Purpose:
 *  Notifes the advise sink that presentation data changed in the
 *  data object to which we're connected providing the right time
 *  to update displays using such presentations.
 *
 * Parameters:
 *  dwAspect        DWORD indicating which aspect has changed.
 *  lindex          LONG indicating the piece that changed.
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnViewChange(DWORD dwAspect
    , LONG lindex)
    {
    //Repaint only if this is the right aspect
    if (dwAspect==m_pTen->m_fe.dwAspect)
        {
        m_pTen->m_pPG->m_fDirty=TRUE;
        m_pTen->Repaint();
        }

    return;
    }

/*
 * CImpIAdviseSink::OnRename
 *
 * Purpose:
 *  Informs the advise sink that a linked object has been renamed.
 *  Generally only the OLE default handler cares about this.
 *
 * Parameters:
 *  pmk             LPMONIKER providing the new name of the object
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnRename(LPMONIKER pmk)
    {
    /*
     * As a container this is unimportant to us since it really
     * tells the handler's implementation of IOleLink that the
     * object's moniker has changed.  Since we get this call
     * from the handler, we don't have to do anything ourselves.
     */
    return;
    }

/*
 * CImpIAdviseSink::OnSave
 *
 * Purpose:
 *  Informs the advise sink that the OLE object has been saved
 *  persistently.  The primary purpose of this is for containers
 *  that want to make optimizations for objects that are not in a
 *  saved state, so on this you have to disable such optimizations.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnSave(void)
    {
    /*
     * A Container has nothing to do here as this notification is
     * only useful when we have an ADVFCACHE_ONSAVE advise set up,
     * which we don't.  So we ignore it.
     */
    return;
    }

/*
 * CImpIAdviseSink::OnClose
 *
 * Purpose:
 *  Informs the advise sink that the OLE object has closed and is
 *  no longer bound in any way.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnClose(void)
    {
    /*
     * This doesn't have much to do with us again as it's only
     * used to notify the handler's IOleLink implementation of the
     * change in the object.  We don't have to do anything since
     * we'll also get an IOleClientSite::OnShowWindow(FALSE) to
     * tell us to repaint.
     */

    /*
     * If we are dealing with an OLE 1.0 server it may not call
     * IOleClientSite::OnShowWindow(FALSE) properly, so to protect
     * ourselves we make sure the object is drawn as closed on
     * this notification.
     */
    m_pTen->ShowAsOpen(FALSE);
    return;
    }


ICLASSF.CPP

/*
 * ICLASSF.CPP
 * Patron Chapter 21
 *
 * Implementation of an IClassFactory interface for Patron to
 * enable linking to its embedded objects.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CLinkClassFactory::CLinkClassFactory
 * CLinkClassFactory::~CLinkClassFactory
 *
 * Constructor Parameters:
 *  pFR             PCLinkFrame that can create documents.
 */

CLinkClassFactory::CLinkClassFactory(PCPatronFrame pFR)
    {
    m_cRef=0L;
    m_pFR=pFR;
    return;
    }

CLinkClassFactory::~CLinkClassFactory(void)
    {
    return;
    }

/*
 * CLinkClassFactory::QueryInterface
 * CLinkClassFactory::AddRef
 * CLinkClassFactory::Release
 */

STDMETHODIMP CLinkClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CLinkClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CLinkClassFactory::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CLinkClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates an object to resolve a link to an embedding.
 */

STDMETHODIMP CLinkClassFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCPatronDoc         pDoc;
    HRESULT             hr;

    *ppvObj=NULL;

    hr=ResultFromScode(E_OUTOFMEMORY);

    //We don't support aggregation
    if (NULL!=pUnkOuter)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    //Try creating a new document, which creates the object.
    pDoc=(PCPatronDoc)m_pFR->m_pCL->NewDocument(TRUE);

    //ObjectDestroyed has already been called.
    if (NULL==pDoc)
        {
        g_cObj++;
        ObjectDestroyed();
        return hr;
        }

    /*
     * We don't want to do any file initialization here because we
     * want to wait for IPersistFile calls.  If we called Load(NULL)
     * we'd create storages and whatnot that we don't want in this
     * case.
     */

    hr=pDoc->QueryInterface(riid, ppvObj);

    if (FAILED(hr))
        {
        m_pFR->m_pCL->CloseDocument(pDoc);
        g_cObj++;
        ObjectDestroyed();
        return hr;
        }

    m_pFR->m_pDocCreated=pDoc;
    return NOERROR;
    }

/*
 * CLinkClassFactory::LockServer
 *
 * Purpose:
 *  Provides control of the application in memory.
 */

STDMETHODIMP CLinkClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        {
        g_cLock--;
        g_cObj++;
        ObjectDestroyed();
        }

    return NOERROR;
    }


ICLISITE.CPP

/*
 * ICLISITE.CPP
 * Patron Chapter 21
 *
 * Implementation of the IOleClientSite interface for
 * Patron's tenants.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CImpIOleClientSite::CImpIOleClientSite
 * CImpIOleClientSite::~CImpIOleClientSite
 *
 * Parameters (Constructor):
 *  pTenant         PCTenant of the tenant we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleClientSite::CImpIOleClientSite(PCTenant pTenant
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pTen=pTenant;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleClientSite::~CImpIOleClientSite(void)
    {
    return;
    }

/*
 * CImpIOleClientSite::QueryInterface
 * CImpIOleClientSite::AddRef
 * CImpIOleClientSite::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleClientSite object.
 */

STDMETHODIMP CImpIOleClientSite::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleClientSite::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleClientSite::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleClientSite::SaveObject
 *
 * Purpose:
 *  Requests that the container call OleSave for the object that
 *  lives here.  Typically this happens on server shutdown.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::SaveObject(void)
    {
    //We're already set up with the tenant to save; this is trivial.
    m_pTen->Update();
    return NOERROR;
    }

/*
 * CImpIOleClientSite::GetMoniker
 *
 * Purpose:
 *  Retrieves the moniker for the site in which this object lives,
 *  either the moniker relative to the container or the full
 *  moniker.
 *
 * Parameters:
 *  dwAssign        DWORD specifying that the object wants moniker
 *                  assignment.  Yeah.  Right.  Got any bridges to
 *                  sell?
 *  dwWhich         DWORD identifying which moniker the object
 *                  wants, either the container's moniker, the
 *                  moniker relative to this client site, or the
 *                  full moniker.
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::GetMoniker(DWORD dwAssign
    , DWORD dwWhich, LPMONIKER *ppmk)
    {
    //CHAPTER21MOD
    *ppmk=NULL;

    switch (dwWhich)
        {
        case OLEWHICHMK_CONTAINER:
            //This is just the file we're living in.
            if (NULL!=m_pTen->m_pmkFile)
                *ppmk=m_pTen->m_pmkFile;

            break;

        case OLEWHICHMK_OBJREL:
            //This is everything but the filename.
            if (NULL!=m_pTen->m_pmk)
                *ppmk=m_pTen->m_pmk;

            break;

        case OLEWHICHMK_OBJFULL:
            //Concatenate file and relative monikers for this one.
            if (NULL!=m_pTen->m_pmkFile && NULL!=m_pTen->m_pmk)
                {
                return m_pTen->m_pmkFile->ComposeWith
                    (m_pTen->m_pmk, FALSE, ppmk);
                }

            break;
        }

    if (NULL==*ppmk)
        return ResultFromScode(E_FAIL);

    (*ppmk)->AddRef();
    return NOERROR;
    //End CHAPTER21MOD
    }

/*
 * CImpIOleClientSite::GetContainer
 *
 * Purpose:
 *  Returns a pointer to the document's IOleContainer interface.
 *
 * Parameters:
 *  ppContainer     LPOLECONTAINER * in which to return the
 *                  interface.
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::GetContainer(LPOLECONTAINER
    * ppContainer)
    {
    //CHAPTER21MOD
    PCPage  pPage;

    *ppContainer=NULL;

    /*
     * Here we want to get the interface on the page we're in.
     * The function we call in CPages does not AddRef the page, but
     * QueryInterface does.
     */
    m_pTen->m_pPG->IPageGetFromID((DWORD)-1L, &pPage, FALSE);

    if (NULL!=pPage)
        {
        return pPage->QueryInterface(IID_IOleItemContainer
            , (PPVOID)ppContainer);
        }

    return ResultFromScode(E_FAIL);
    //End CHAPTER21MOD
    }

/*
 * CImpIOleClientSite::ShowObject
 *
 * Purpose:
 *  Tells the container to bring the object fully into view as much
 *  as possible, that is, scroll the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::ShowObject(void)
    {
    //CHAPTER21MOD
    HWND        hWnd, hWndT;
    //End CHAPTER21MOD

    /*
     * We let the tenant do this, since it can access the current
     * scroll position as a friend of CPages whereas we cannot.
     */
    m_pTen->ShowYourself();

    //CHAPTER21MOD
    //For linking to embedding, show the main window.
    hWndT=GetParent(m_pTen->m_hWnd);

    while (NULL!=hWndT)
        {
        hWnd=hWndT;
        hWndT=GetParent(hWnd);
        }

    ShowWindow(hWnd, SW_SHOWNOACTIVATE);
    //End CHAPTER21MOD
    return NOERROR;
    }

/*
 * CImpIOleClientSite::OnShowWindow
 *
 * Purpose:
 *  Informs the container if the object is showing itself or
 *  hiding itself.  This is done only in the opening mode and allows
 *  the container to know when to shade or unshade the object.
 *
 * Parameters:
 *  fShow           BOOL indiciating that the object is being shown
 *                  (TRUE) or hidden (FALSE).
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::OnShowWindow(BOOL fShow)
    {
    //All we have to do is tell the tenant of the open state change.
    m_pTen->ShowAsOpen(fShow);
    return NOERROR;
    }

/*
 * CImpIOleClientSite::RequestNewObjectLayout
 *
 * Purpose:
 *  Called when the object needs more room in the container.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::RequestNewObjectLayout(void)
    {
    return ResultFromScode(E_NOTIMPL);
    }


IOLECONT.CPP

/*
 * IOLECONT.CPP
 * Patron Chapter 21
 *
 * Implementation of the IOleItemContainer interface for Patron's
 * CPage and CPatronDoc alike, using the constructor parameter fDoc
 * to differentiate.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include <stdlib.h>
#include "patron.h"

/*
 * CImpIOleItemContainer::CImpIOleItemContainer
 * CImpIOleItemContainer::~CImpIOleItemContainer
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the page or pages.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 *  fDoc            BOOL indicating if we're in CPatronDoc or CPage
 */

CImpIOleItemContainer::CImpIOleItemContainer(LPVOID pObj
    , LPUNKNOWN pUnkOuter, BOOL fDoc)
    {
    m_cRef=0;
    m_fDoc=fDoc;

    if (fDoc)
        {
        m_pDoc=(PCPatronDoc)pObj;
        m_pPage=NULL;
        }
    else
        {
        m_pDoc=NULL;
        m_pPage=(PCPage)pObj;
        }

    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleItemContainer::~CImpIOleItemContainer(void)
    {
    return;
    }

/*
 * CImpIOleItemContainer::QueryInterface
 * CImpIOleItemContainer::AddRef
 * CImpIOleItemContainer::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleItemContainer object.
 */

STDMETHODIMP CImpIOleItemContainer::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleItemContainer::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleItemContainer::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleItemContainer::ParseDisplayName
 *
 * Purpose:
 *  Inherited member of IParseDisplayName that takes a string name
 *  and turns out a moniker for it.
 *
 * Parameters:
 *  pbc             LPBC to the binding context
 *  pszName         LPOLESTR to the name to parse.
 *  pchEaten        ULONG * into which to store how many
 *                  characters we scanned in the display name.
 *  ppmk            LPMONIKER * in which to return the moniker.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::ParseDisplayName(LPBC pbc
    , LPOLESTR pszName, ULONG *pchEaten, LPMONIKER *ppmk)
    {
    OLECHAR     ch;
    ULONG       chEaten=0;
    TCHAR       szName[256];
    TCHAR       szComp[15];
    LPTSTR      psz;
    UINT        cch;

    *ppmk=NULL;
    *pchEaten=0;

    /*
     * All we have to look for is the string between the !
     * delimeters (or a null terminator).  pszName should be pointing
     * to a !, so skip it and scan the string for a ! or 0,
     * then pass the result to CreateItemMoniker.
     */

    psz=szName;

    ch=*pszName++;
    chEaten++;

    if ((OLECHAR)'!'!=ch)
        return ResultFromScode(MK_E_SYNTAX);

    ch=*pszName++;

    while ((OLECHAR)0!=ch && (OLECHAR)'!' !=ch)
        {
        *psz++=(TCHAR)ch;
        chEaten++;
        ch=*pszName++;
        }

    *psz=(TCHAR)0;

    /*
     * Syntax check.  If we're the DOcument object, check for
     * "Page n" at the beginning of the string.  Otherwise check
     * for "Tenant n".
     */
    lstrcpy(szComp, m_fDoc ? TEXT("Page ") : TEXT("Tenant "));

    //Does szName start with szComp?
    cch=lstrlen(szComp);

    if (0!=_tcsncicmp(szName, szComp, cch))
        {
        *pchEaten=1;    //Parsed ! at least
        return ResultFromScode(MK_E_SYNTAX);
        }

    //Check for a number in szName
    if ((TCHAR)'0' != szName[cch])
        {
        if (0==_ttoi(szName+cch))
            {
            *pchEaten=cch;  //Got past name
            return ResultFromScode(MK_E_SYNTAX);
            }
        }

    *pchEaten=chEaten;
   #ifdef WIN32ANSI
    //Use the ANSI version here since szName is ANSI
    return INOLE_CreateItemMoniker(TEXT("!"), szName, ppmk);
   #else
    return CreateItemMoniker(OLETEXT("!"), szName, ppmk);
   #endif
    }

/*
 * CImpIOleItemContainer::EnumObjects
 *
 * Purpose:
 *  Creates and returns an IEnumUnknown object that allows the
 *  caller to walk through the objects in this continer thing.
 *
 * Parameters:
 *  dwFlags         DWORD specifying what kind of objects to
 *                  enumerate.
 *  ppEnum          LPENUMUNKNOWN * into which to return the
 *                  enumerator
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::EnumObjects(DWORD dwFlags
    , LPENUMUNKNOWN *ppEnum)
    {
    *ppEnum=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleItemContainer::LockContainer
 *
 * Purpose:
 *  Establishes a lock on the container to prevent it from shutting
 *  down outside of user control.  This is used to control the
 *  lifetime of the container when it's used to update a link to an
 *  embedded object within it.  If we're unlock and the user has not
 *  taken control, we close.
 *
 * Parameters:
 *  fLock           BOOL indicating a lock or unlock.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::LockContainer(BOOL fLock)
    {
    /*
     * This is pretty much the same implementation as
     * IClassFactory::LockServer, and we can use the same lock
     * count to accomplish our goal.
     */

    if (fLock)
        g_cLock++;
    else
        {
        g_cLock--;
        g_cObj++;
        ObjectDestroyed();
        }

    return NOERROR;
    }

/*
 * CImpIOleItemContainer::GetObject
 *
 * Purpose:
 *  Returns the requested interface pointer on an object in this
 *  container.
 *
 * Parameters:
 *  pszItem         LPOLESTR to the item we must locate.
 *  dwSpeed         DWORD identifying how long the caller is willing
 *                  to wait.
 *  pcb             LPBINDCTX providing the binding context.
 *  riid            REFIID of the interface requested.
 *  ppv             PPVOID into which to return the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::GetObject(LPOLESTR pszItem
    , DWORD dwSpeed, LPBINDCTX pbc, REFIID riid, PPVOID ppv)
    {
    DWORD       dw;
    char        szTemp[40];     //ANSI for atol
    HRESULT     hr=ResultFromScode(E_FAIL);
    PCPage      pPage;
    PCTenant    pTenant;
    LPUNKNOWN   pObj;
    UINT        i, iCur;

    *ppv=NULL;

    if (m_fDoc)
        {
        /*
         * The item name should be "Page n", so we'll do it the
         * easy way:  call atol on pszItem+5 (we always know that
         * we'll have "Page " there since we put it there (see
         * CPage::GetStorageName).
         */

        UNICODETOANSI((pszItem+5), szTemp, sizeof(szTemp));
        dw=atol(szTemp);

        i=m_pDoc->m_pPG->IPageGetFromID(dw, &pPage, FALSE);

        if (NOVALUE==i)
            return hr;

        /*
         * If we're asked for immediate speed, only do this if the
         * page is already current, i.e. everything is loaded.
         */
        iCur=m_pDoc->m_pPG->CurPageGet();

        if (BINDSPEED_IMMEDIATE==dwSpeed && iCur!=i)
            return ResultFromScode(MK_E_EXCEEDEDDEADLINE);

        m_pDoc->m_pPG->CurPageSet(i);

        //This will have changed to be the current page now.
        if (NULL!=m_pDoc->m_pPG->m_pPageCur)
            hr=m_pDoc->m_pPG->m_pPageCur->QueryInterface(riid, ppv);
        }
    else
        {
        if (TenantFromName(pszItem, &pTenant))
            {
            pTenant->ObjectGet(&pObj);

            /*
             * If we're asked for immediate or moderate, only work
             * if the object is already running.
             */
            hr=IsRunning(pszItem);  //This is the function below

            if ((BINDSPEED_IMMEDIATE==dwSpeed
                || BINDSPEED_MODERATE==dwSpeed) && NOERROR!=hr)
                hr=ResultFromScode(MK_E_EXCEEDEDDEADLINE);
            else
                {
                //IMPORTANT:  Make sure this object is running first
                OleRun(pObj);
                hr=pObj->QueryInterface(riid, ppv);
                }

            pObj->Release();
            }
        else
            hr=ResultFromScode(MK_E_NOOBJECT);
        }

    return hr;
    }

/*
 * CImpIOleItemContainer::GetObjectStorage
 *
 * Purpose:
 *  Similar to get Object in that we have to locate the object
 *  described by a given name, but instead of returning any old
 *  interface we return a storage element.
 *
 * Parameters:
 *  pszItem         LPOLESTR to the item we must locate.
 *  pcb             LPBINDCTX providing the binding context.
 *  riid            REFIID of the interface requested.  Usually
 *                  IStorage or IStream.
 *  ppv             PPVOID into which to return the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::GetObjectStorage(LPOLESTR pszItem
    , LPBINDCTX pbc, REFIID riid, PPVOID ppv)
    {
    PCTenant    pTenant;

    *ppv=NULL;

    if (m_fDoc)
        return ResultFromScode(E_NOTIMPL);

    //Can only handle IStorage.
    if (IID_IStorage!=riid)
        return ResultFromScode(E_NOINTERFACE);

    if (TenantFromName(pszItem, &pTenant))
        pTenant->StorageGet((LPSTORAGE *)ppv);

    return (NULL!=*ppv) ? NOERROR : ResultFromScode(E_FAIL);
    }

/*
 * CImpIOleItemContainer::IsRunning
 *
 * Purpose:
 *  Answers if the object under the given name is currently running.
 *
 * Parameters:
 *  pszItem         LPOLESTR of the item to check
 *
 * Return Value:
 *  HRESULT         NOERROR if the object is running, S_FALSE
 *                  otherwise.  Possibly MK_E_NOOBJECT if the name
 *                  is bogus.
 */

STDMETHODIMP CImpIOleItemContainer::IsRunning(LPOLESTR pszItem)
    {
    HRESULT     hr;
    PCTenant    pTenant;
    LPUNKNOWN   pObj;
    LPOLEOBJECT pIOleObject;

    /*
     * If this is the document's container interface, the object
     * is a page and the page is always running.
     */
    if (m_fDoc)
        return NOERROR;
    else
        {
        if (TenantFromName(pszItem, &pTenant))
            {
            //Ask the actual object if its running.
            pTenant->ObjectGet(&pObj);
            hr=pObj->QueryInterface(IID_IOleObject
                , (PPVOID)&pIOleObject);
            pObj->Release();

            if (SUCCEEDED(hr))
                {
                hr=(OleIsRunning(pIOleObject))
                    ? NOERROR : ResultFromScode(S_FALSE);
                pIOleObject->Release();
                }
            }
        else
            hr=ResultFromScode(MK_E_NOOBJECT);
        }

    return hr;
    }

/*
 * CImpIOleItemContainer::TenantFromName
 * (Private)
 *
 * Purpose:
 *  This function which is NOT part of the interface retrieves
 *  a tenant pointer from a tenant name.
 *
 * Parameters:
 *  pszItem         LPOLESTR of the tenant to locate.
 *  ppTenant        PCTenant * in which to return the pointer.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CImpIOleItemContainer::TenantFromName(LPOLESTR pszItem
    , PCTenant *ppTenant)
    {
    DWORD       dw;
    char        szTemp[40];     //ANSI for atol

    if (m_fDoc)
        return FALSE;

    //The item name should be "Tenant xxxx", so use pszItem+7.
    UNICODETOANSI((pszItem+7), szTemp, sizeof(szTemp));
    dw=atol(szTemp);

    *ppTenant=NULL;

    return m_pPage->TenantGetFromID(dw, ppTenant, FALSE);
    }


IPERFILE.CPP

/*
 * IPERFILE.CPP
 * Patron Chapter 21
 *
 * Implementation of the IPersistFile interface for Patron's
 * documents.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CImpIPersistFile:CImpIPersistFile
 * CImpIPersistFile::~CImpIPersistFile
 *
 * Constructor Parameters:
 *  pDoc            PCPatronDoc associated with this interface
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistFile::CImpIPersistFile(PCPatronDoc pDoc
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pDoc=pDoc;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPersistFile::~CImpIPersistFile(void)
    {
    return;
    }

/*
 * CImpIPersistFile::QueryInterface
 * CImpIPersistFile::AddRef
 * CImpIPersistFile::Release
 *
 * Purpose:
 *  Standard set of IUnknown members for this interface
 */

STDMETHODIMP CImpIPersistFile::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistFile::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistFile::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistFile::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the file represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::GetClassID(LPCLSID pClsID)
    {
    *pClsID=CLSID_PatronPages;
    return NOERROR;
    }

/*
 * CImpIPersistFile::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this file since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 */

STDMETHODIMP CImpIPersistFile::IsDirty(void)
    {
    return ResultFromScode(m_pDoc->FDirtyGet() ? S_OK : S_FALSE);
    }

/*
 * IPersistFile::Load
 *
 * Purpose:
 *  Asks the server to load the document for the given filename.
 *
 * Parameters:
 *  pszFile         LPCOLESTR to the filename to load.
 *  grfMode         DWORD containing open flags requested from the
 *                  caller.  Currently these are safely ignored.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::Load(LPCOLESTR pszFile, DWORD grfMode)
    {
    UINT        uRet;

   #ifdef WIN32ANSI
    char        szTemp[CCHPATHMAX];

    WideCharToMultiByte(CP_ACP, 0, pszFile, -1, szTemp, CCHPATHMAX
       , NULL, NULL);
    uRet=m_pDoc->Load(TRUE, szTemp);
   #else
    uRet=m_pDoc->Load(TRUE, (LPTSTR)pszFile);
   #endif
    return (DOCERR_NONE==uRet) ? NOERROR
        : ResultFromScode(STG_E_READFAULT);
    }

/*
 * IPersistFile::Save
 *
 * Purpose:
 *  Instructs the server to write the current file into a new
 *  filename, possibly then using that filename as the current one.
 *
 * Parameters:
 *  pszFile         LPCOLESTR of the file into which we save.  If NULL,
 *                  this means save the current file.
 *  fRemember       BOOL indicating if we're to use this filename as
 *                  the current file now (Save As instead of Save
 *                  Copy As).
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::Save(LPCOLESTR pszFile, BOOL fRemember)
    {
    UINT        uRet;

    /*
     * Since we don't want to mess with changing Save (which would
     * require changes to CLASSLIB, urk) we instead save fRemember
     * in the document before calling Save which supresses the call
     * to CPatronDoc::Rename if FALSE.
     */

    m_pDoc->m_fRename=fRemember;
   #ifdef WIN32ANSI
    char        szTemp[CCHPATHMAX];

    WideCharToMultiByte(CP_ACP, 0, pszFile, -1, szTemp, CCHPATHMAX
       , NULL, NULL);
    uRet=m_pDoc->Save(0, szTemp);
   #else
    uRet=m_pDoc->Save(0, (LPTSTR)pszFile);
   #endif
    m_pDoc->m_fRename=TRUE;

    return (DOCERR_NONE==uRet) ? NOERROR
        : ResultFromScode(STG_E_WRITEFAULT);
    }

/*
 * IPersistFile::SaveCompleted
 *
 * Purpose:
 *  Informs us that the operation that called Save is now finished
 *  and we can access the file again.
 *
 * Parameters:
 *  pszFile         LPCOLESTR of the file in which we can start
 *                  writing again.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::SaveCompleted(LPCOLESTR pszFile)
    {
    return NOERROR;
    }

/*
 * IPersistFile::GetCurFile
 *
 * Purpose:
 *  Retrieves the name of the current file.
 *
 * Parameters:
 *  ppszFile        LPOLESTR * into which we store a pointer to
 *                  the filename that should be allocated with the
 *                  shared IMalloc.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::GetCurFile(LPOLESTR *ppszFile)
    {
    LPMALLOC    pIMalloc;
    LPOLESTR    psz;
    UINT        uRet;

    *ppszFile=NULL;

    if (FAILED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        return ResultFromScode(E_FAIL);

    psz=(LPOLESTR)pIMalloc->Alloc(CCHPATHMAX*sizeof(OLECHAR));
    pIMalloc->Release();

   #ifdef WIN32ANSI
    char    szTemp[CCHPATHMAX];

    uRet=m_pDoc->FilenameGet(szTemp, CCHPATHMAX);
    MultiByteToWideChar(CP_ACP, 0, szTemp, -1, psz, CCHPATHMAX);
   #else
    uRet=m_pDoc->FilenameGet(psz, CCHPATHMAX);
   #endif

    //If we have no filename, return the prompt for File Open/Save.
    if (0==uRet)
       #ifdef WIN32ANSI
        MultiByteToWideChar(CP_ACP, 0
            , (*m_pDoc->m_pST)[IDS_EXTENSION], -1, psz, CCHPATHMAX);
       #else
        lstrcpy(psz, (*m_pDoc->m_pST)[IDS_EXTENSION]);
       #endif

    *ppszFile=psz;
    return (0==uRet) ? ResultFromScode(S_FALSE) : NOERROR;
    }


IUILINK.CPP

/*
 * IUILINK.CPP
 * Patron Chapter 21
 *
 * Implementation of an object with the IOleUILinkContainer
 * interface necessary to use the standard Links Dialog.  This
 * is implemented as a stand-along object with access to the CPage
 * with which its associated, primiarily because it is only used
 * for the one dialog.  Therefore this object has it's own IUnknown.
 * In addition, we use the Links Assistant object developed in this
 * chapter to simplify our own code.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CIOleUILinkContainer::CIOleUILinkContainer
 * CIOleUILinkContainer::~CIOleUILinkContainer
 *
 * Parameters (Constructor):
 *  pPage           PCPage of the page we're in.
 */

CIOleUILinkContainer::CIOleUILinkContainer(PCPage pPage)
    {
    m_cRef=0;
    m_pPage=pPage;
    m_iTenant=0;
    m_pDelIUILinks=NULL;
    m_fDirty=FALSE;
    return;
    }

CIOleUILinkContainer::~CIOleUILinkContainer(void)
    {
    ReleaseInterface(m_pDelIUILinks);
    return;
    }

/*
 * CIOleUILinkContainer::Init
 *
 * Purpose:
 *  Performs initialization on the object that might fail.  In
 *  particular this creates an object of CLSID_LinksAssistant that
 *  helps in implementing this interface.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CIOleUILinkContainer::Init(void)
    {
    HRESULT     hr;

    hr=CoCreateInstance(CLSID_LinksAssistant, NULL
        , CLSCTX_INPROC_SERVER, IID_IOleUILinkContainer
        , (PPVOID)&m_pDelIUILinks);

    return SUCCEEDED(hr);
    }

/*
 * CIOleUILinkContainer::QueryInterface
 * CIOleUILinkContainer::AddRef
 * CIOleUILinkContainer::Release
 *
 * Purpose:
 *  IUnknown members for CIOleUILinkContainer object.
 */

STDMETHODIMP CIOleUILinkContainer::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IOleUILinkContainer==riid)
        {
        *ppv=this;
        AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CIOleUILinkContainer::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CIOleUILinkContainer::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CIOleUILinkContainer::GetNextLink
 *
 * Purpose:
 *  Called when the Links dialog is filling its listbox.  Here we
 *  need to return a key for the first link if dwLink is zero, then
 *  return the next links if it's non-zero.
 *
 * Parameters:
 *  dwLink          DWORD last returned from this function.  Zero if
 *                  this is the first call to this function.
 *
 * Return Value:
 *  DWORD           Some value that identifies this object.  Zero
 *                  stops the sequence such that this function is
 *                  no longer called.
 */

STDMETHODIMP_(DWORD) CIOleUILinkContainer::GetNextLink(DWORD dwLink)
    {
    PCTenant        pTenant;

    //If we're told to start the sequence, set index to zero.
    if (0L==dwLink)
        m_iTenant=0;

    /*
     * On each subsequent call, find the next linked object in
     * this document and return it.  Make sure the index is
     * incremented for the next time this function is called.
     */
    for ( ; m_iTenant < m_pPage->m_cTenants; m_iTenant++)
        {
        if (m_pPage->TenantGet(m_iTenant, &pTenant, FALSE))
            {
            if (TENANTTYPE_LINKEDOBJECT==pTenant->TypeGet())
                {
                m_iTenant++;
                return (DWORD)pTenant;
                }
            }
        }

    //If we hit the end of list, this tells the dialog to stop.
    return 0L;
    }

/*
 * CIOleUILinkContainer::SetLinkUpdateOptions
 *
 * Purpose:
 *  Informs the application to call IOleLink::SetUpdateOptions for
 *  the object identified by dwLink.
 *
 * Parameters:
 *  dwLink          DWORD object identifier as returned from
 *                  GetNextLink.
 *  dwOptions       DWORD containing the new options.
 *
 * Return Value:
 *  HRESULT         Return value of IOleLink::SetUpdateOptions.
 */

STDMETHODIMP CIOleUILinkContainer::SetLinkUpdateOptions(DWORD dwLink
    , DWORD dwOptions)
    {
    LPOLELINK       pIOleLink;
    HRESULT         hr;

    if (NULL==dwLink)
        return ResultFromScode(E_FAIL);

    /*
     * Your responsibility is to call the object's
     * IOleLink::SetUpdateOptions function with dwOptions.  Simple?
     *
     * For Patron we must first get the object pointer obtainable
     * from the tenant's ObjectGet function, then QI for IOleLink.
     */

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    hr=pIOleLink->SetUpdateOptions(dwOptions);
    pIOleLink->Release();

    m_fDirty=SUCCEEDED(hr);
    return hr;
    }

/*
 * CIOleUILinkContainer::GetLinkUpdateOptions
 *
 * Purpose:
 *  Requests the container to call IOleLink::GetUpdateOptions for
 *  the object identified by dwLink.
 *
 * Parameters:
 *  dwLink          DWORD identifying the object
 *  pdwOptions      LPDWORD in which to store the options.
 *
 * Return Value:
 *  HRESULT         Return value of IOleLink::GetUpdateOptions
 */

STDMETHODIMP CIOleUILinkContainer::GetLinkUpdateOptions(DWORD dwLink
    , LPDWORD pdwOptions)
    {
    LPOLELINK       pIOleLink;
    HRESULT         hr;

    if (NULL==dwLink)
        return ResultFromScode(E_FAIL);

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    hr=pIOleLink->GetUpdateOptions(pdwOptions);
    pIOleLink->Release();

    return hr;
    }

/*
 * CIOleUILinkContainer::SetLinkSource
 *
 * Purpose:
 *  Changes the moniker to which an object is linked.
 *
 * Parameters:
 *  dwLink          DWORD identifying the object in question.
 *  pszName         LPTSTR to the displayable name of the source.
 *  cchName         ULONG length of the file portaion of pszName
 *  pchEaten        ULONG * in which to return the number of
 *                  characters used in parsing pszDisplayName.
 *  fValidate       BOOL indicating if we're to validate that the
 *                  source exists first.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_FAIL otherwise.
 */

STDMETHODIMP CIOleUILinkContainer::SetLinkSource(DWORD dwLink
    , LPTSTR pszName, ULONG cchName, ULONG *pchEaten
    , BOOL fValidate)
    {
    PCTenant        pTenant=(PCTenant)dwLink;
    HRESULT         hr;
    LPOLELINK       pIOleLink;

    if (NULL==dwLink)
        return ResultFromScode(E_FAIL);

    //This is for use in GetLinkSource, below.
    pTenant->m_fLinkAvail=FALSE;

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    hr=m_pDelIUILinks->SetLinkSource((DWORD)pIOleLink, pszName
        , cchName, pchEaten, fValidate);
    pIOleLink->Release();

    if (FAILED(hr))
        return hr;

    //hr will be S_FALSE if link is unavailable.
    pTenant->Repaint();
    pTenant->m_fLinkAvail=(NOERROR==hr);
    m_fDirty=TRUE;
    return NOERROR;
    }

/*
 * CIOleUILinkContainer::GetLinkSource
 *
 * Purpose:
 *  Retrieves various strings and values for this link source.
 *
 * Parameters:
 *  dwLink          DWORD identifying the object affected.
 *  ppszName        LPTSTR * in which to return the new source
 *                  name
 *  pcchName        ULONG * in which to return the length of
 *                  pszName
 *  ppszFullLink    LPTSTR * in which to return the full name of
 *                  the class of linked object.
 *  ppszShortLink   LPTSTR * in which to return the short name of
 *                  the class of linked object.
 *  pfSourceAvail   BOOL * in which to return if this is an
 *                  available link source.
 *  pfSelected      BOOL * in which to return if this object is
 *                  currently selected in the document.  This
 *                  selects the item in the listbox for this object.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, error code otherwise.
 */

STDMETHODIMP CIOleUILinkContainer::GetLinkSource(DWORD dwLink
    , LPTSTR *ppszName, ULONG *pcchName, LPTSTR *ppszFullLink
    , LPTSTR *ppszShortLink, BOOL *pfSourceAvail, BOOL *pfSelected)
    {
    HRESULT         hr;
    PCTenant        pTenant=(PCTenant)dwLink;
    LPOLELINK       pIOleLink=NULL;
    LPOLEOBJECT     pIOleObject=NULL;
    LPMONIKER       pmk=NULL;
    LPMONIKER       pmkFirst=NULL;
    LPBC            pbc=NULL;

    if (NULL==dwLink)
        return ResultFromScode(E_FAIL);

    //We know what this is from SetLinkSource
    *pfSourceAvail=pTenant->m_fLinkAvail;

    if (pfSelected)
        *pfSelected=pTenant->FIsSelected();

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    hr=m_pDelIUILinks->GetLinkSource((DWORD)pIOleLink, ppszName
        , pcchName, ppszFullLink, ppszShortLink, pfSourceAvail
        , pfSelected);

    pIOleLink->Release();
    return hr;
    }

/*
 * CIOleUILinkContainer::OpenLinkSource
 *
 * Purpose:
 *  Asks the container to call DoVerb on this object with
 *  OLEIVERB_SHOW.
 *
 * Parameters:
 *  dwLink          DWORD identifying the linked object.
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CIOleUILinkContainer::OpenLinkSource(DWORD dwLink)
    {
    PCTenant        pTenant=(PCTenant)dwLink;

    pTenant->Activate(OLEIVERB_OPEN);
    return NOERROR;
    }

/*
 * CIOleUILinkContainer::UpdateLink
 *
 * Purpose:
 *  Asks the container to update the link for this object.
 *
 * Parameters:
 *  dwLink          DWORD identifying the linked object.
 *  fErrorMessage   BOOL indicating if we can show errors.
 *  fErrorAction    BOOL making no sense whatsoever.
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CIOleUILinkContainer::UpdateLink(DWORD dwLink
    , BOOL fErrorMessage, BOOL fErrorAction)
    {
    PCTenant        pTenant=(PCTenant)dwLink;
    LPOLELINK       pIOleLink;
    HRESULT         hr;

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    hr=m_pDelIUILinks->UpdateLink((DWORD)pIOleLink, fErrorMessage
        , fErrorAction);

    pTenant->Repaint();
    pTenant->m_fLinkAvail=SUCCEEDED(hr);
    pIOleLink->Release();

    if (FAILED(hr))
        {
        if (fErrorMessage)
            {
            MessageBox(m_pPage->m_hWnd
                , TEXT("Could not update link."), TEXT("Patron")
                , MB_OK);
            }
        }
    else
        m_fDirty=TRUE;

    return hr;
    }

/*
 * CIOleUILinkContainer::CancelLink
 *
 * Purpose:
 *  Requests that the container turn this linked object into a
 *  static object.
 *
 * Parameters:
 *  dwLink          DWORD identifying the linked object.
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CIOleUILinkContainer::CancelLink(DWORD dwLink)
    {
    PCTenant        pTenant=(PCTenant)dwLink;
    LPOLELINK       pIOleLink;
    HRESULT         hr;

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    //This sets the source moniker to NULL.
    m_pDelIUILinks->CancelLink((DWORD)pIOleLink);
    pIOleLink->Release();

    //Go change this object over to a static one.
    pTenant->ConvertToStatic();

    m_fDirty=TRUE;
    return NOERROR;
    }

//PROTECTED FUNCTIONS INTERNAL TO CIOleUILinkContainer

/*
 * CIOleUILinkContainer::GetObjectInterface
 * (Protected)
 *
 * Purpose:
 *  Retrieves and interface pointer for the object identified by
 *  dwLink
 *
 * Parameters:
 *  dwLink          DWORD identifying the object
 *  riid            REFIID of the interface desired.
 *  ppv             PPVOID into which we return the pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, error code otherwise.
 */

STDMETHODIMP CIOleUILinkContainer::GetObjectInterface(DWORD dwLink
    , REFIID riid, PPVOID ppv)
    {
    PCTenant        pTenant=(PCTenant)dwLink;
    LPUNKNOWN       pIUnknown;
    HRESULT         hr;

    pTenant->ObjectGet(&pIUnknown);
    hr=pIUnknown->QueryInterface(riid, ppv);
    pIUnknown->Release();

    return hr;
    }


PAGE.CPP

/*
 * PAGE.CPP
 * Patron Chapter 21
 *
 * Implementation of parts of the CPage class; those member
 * functions dealing with mouse events are in PAGEMOUS.CPP.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPage::CPage
 * CPage::~CPage
 *
 * Constructor Parameters:
 *  dwID            DWORD identifier for this page.
 *  hWnd            HWND of the pages window (for repaints, etc).
 *  pPG             PCPages to the Pages window.
 */

CPage::CPage(DWORD dwID, HWND hWnd, PCPages pPG)
    {
    m_dwID     =dwID;
    m_pIStorage=NULL;

    m_cOpens=0;
    m_hWnd=hWnd;
    m_pPG=pPG;

    m_dwIDNext      =0;
    m_cTenants      =0;
    m_hWndTenantList=NULL;
    m_iTenantCur    =NOVALUE;   //Tenants are zero indexed.
    m_pTenantCur    =NULL;

    m_uHTCode=HTNOWHERE;
    m_uSizingFlags=0;
    m_fTracking=FALSE;
    m_hDC=NULL;

    m_fDragPending=FALSE;
    m_fSizePending=FALSE;
    m_fTimer=FALSE;

    //Get WIN.INI distance and delay values, with OLE defaults.
    m_cxyDist=GetProfileInt(TEXT("windows"), TEXT("DragMinDist")
        , DD_DEFDRAGMINDIST);
    m_cDelay=GetProfileInt(TEXT("windows"), TEXT("DragDelay")
        , DD_DEFDRAGDELAY);

    m_fReopen=FALSE;

    //CHAPTER21MOD
    m_pmkFile=m_pPG->m_pmkFile;

    if (NULL!=m_pmkFile)
        m_pmkFile->AddRef();

    m_cRef=0L;
    m_dwRegROTWild=0L;
    m_pImpIOleItemContainer=NULL;
    //End CHAPTER21MOD

    return;
    }

CPage::~CPage(void)
    {
    //CHAPTER21MOD
    INOLE_RevokeAsRunning(&m_dwRegROTWild);
    //End CHAPTER21MOD

    if (m_fTimer)
        KillTimer(m_hWnd, IDTIMER_DEBOUNCE);

    m_hWnd=NULL;
    Close(FALSE);
    return;
    }

//CHAPTER21MOD
/*
 * CPage::QueryInterface
 * CPage::AddRef
 * CPage::Release
 *
 * Purpose:
 *  IUnknown members for CPage object.
 */

STDMETHODIMP CPage::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IOleItemContainer==riid || IID_IOleContainer==riid
        || IID_IParseDisplayName==riid)
        *ppv=m_pImpIOleItemContainer;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CPage::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPage::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

//End CHAPTER21MOD

/*
 * CPage::GetID
 *
 * Return Value:
 *  DWORD           dwID field in this page.
 */

DWORD CPage::GetID(void)
    {
    return m_dwID;
    }

/*
 * CPage::Open
 *
 * Purpose:
 *  Retrieves the IStorage associated with this page.  The IStorage
 *  is owned by the page and thus the page always holds a reference
 *  count.  The caller should call Close or delete this page to
 *  match this open.
 *
 *  This function may be called multiple times resulting in
 *  additional reference counts on the storage each of which must be
 *  matched with a call to Close.  The last Close can be done
 *  through delete.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which this page lives.
 *
 * Return Value:
 *  BOOL            TRUE if opening succeeds, FALSE otherwise.
 */

BOOL CPage::Open(LPSTORAGE pIStorage)
    {
    HRESULT                 hr=NOERROR;
    LPSTREAM                pIStream;
    DWORD                   dwMode;
    OLECHAR                 szTemp[32];
    TCHAR                   szCap[32];
    BOOL                    fNew;
    BOOL                    fCreated=FALSE;
    TENANTLIST              tl;
    PTENANTINFO             pti;
    ULONG                   cb;
    LPMALLOC                pIMalloc;
    UINT                    i;
    PCTenant                pTenant;
    UINT                    cLinks;
    LPOLELINK               pIOleLink;
    LPUNKNOWN               pIUnknown;
    UINT                    uRet;
    OLEUIEDITLINKS          el;
    PCIOleUILinkContainer   pIUILinks;
    HWND                    hWndDoc;

    fNew=(NULL==m_pIStorage);

    if (!fNew)
        {
        m_cOpens++;
        m_pIStorage->AddRef();
        return TRUE;
        }

    if (NULL==pIStorage)
        return FALSE;

    /*
     * Attempt to open the storage under this ID.  If none,
     * create one.  In either case, the IStorage is either
     * saved in pPage or released.
     */

    GetStorageName(szTemp);
    dwMode=STGM_TRANSACTED | STGM_READWRITE | STGM_SHARE_EXCLUSIVE;

    hr=pIStorage->OpenStorage(szTemp, NULL, dwMode, NULL, 0
        , &m_pIStorage);

    if (FAILED(hr))
        {
        hr=pIStorage->CreateStorage(szTemp, dwMode, 0, 0
            , &m_pIStorage);
        fCreated=TRUE;
        }

    if (FAILED(hr))
        return FALSE;

    m_cOpens++;

    if (NULL==m_hWndTenantList)
        {
        /*
         * The first time we open this page, create the hidden
         * listbox we'll use to track tenants.  We give it the
         * owner-draw style so we can just store pointers in it.
         */
        m_hWndTenantList=CreateWindow(TEXT("listbox")
            , TEXT("Tenant List"), WS_POPUP | LBS_OWNERDRAWFIXED
            , 0, 0, 100, 100, HWND_DESKTOP, NULL
            , m_pPG->m_hInst, NULL);

        if (NULL==m_hWndTenantList)
            return FALSE;
        }

    //CHAPTER21MOD
    m_pImpIOleItemContainer=new CImpIOleItemContainer(this, this
        , FALSE);

    if (NULL==m_pImpIOleItemContainer)
        return FALSE;
    //End CHAPTER21MOD

    //If this is brand-new, we're done.
    if (fCreated)
        return TRUE;

    /*
     * Now open the stream we saved in Close and load all the
     * tenants listed in there.  If there are none, then we don't
     * have to load squat.
     */

    hr=m_pIStorage->OpenStream(SZSTREAMTENANTLIST, NULL, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pIStream->Read(&tl, sizeof(tl), NULL);
        m_cTenants=tl.cTenants;
        m_dwIDNext=tl.dwIDNext;
        m_iTenantCur=0;

        cb=tl.cTenants*sizeof(TENANTINFO);

        if (0!=cb)
            {
            pti=(PTENANTINFO)pIMalloc->Alloc(cb);

            if (NULL!=pti)
                {
                pIStream->Read(pti, cb, NULL);

                for (i=0; i < m_cTenants; i++)
                    {
                    if (TenantAdd(NOVALUE, (pti+i)->dwID, &pTenant))
                        {
                        pTenant->Load(m_pIStorage, (pti+i));

                        //Make sure it knows about the show state.
                        pTenant->ShowObjectType(m_pPG->m_fShowTypes);
                        }
                    }

                pIMalloc->Free(pti);
                }
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    //Get and select the first tenant
    if (TenantGet(0, &m_pTenantCur, FALSE))
        m_pTenantCur->Select(TRUE);

    //If we just saved and closed, don't bother with updating links
    if (m_fReopen)
        {
        m_fReopen=FALSE;
        return TRUE;
        }

    /*
     * Update all the links in this page, showing the progress
     * indicator as it's happening.  We use the same
     * IOlUILinkContainer implementation as we do for the links
     * dialog, passing it to OleUIUpdateLinks which does everything
     * for us.
     *
     * We might also optimize this to not do anything if there are
     * no automatic links, but it's not a big concern.
     */

    //First, count the number of automatic links.
    cLinks=0;

    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            {
            DWORD       dw;

            pTenant->ObjectGet(&pIUnknown);
            hr=pIUnknown->QueryInterface(IID_IOleLink
                , (PPVOID)&pIOleLink);
            pIUnknown->Release();

            if (FAILED(hr))
                continue;

            pIOleLink->GetUpdateOptions(&dw);
            pIOleLink->Release();

            if (OLEUPDATE_ALWAYS==dw)
                cLinks++;
            }
        }

    //If we have any automatic links, invoke the update dialog.
    if (0==cLinks)
        return TRUE;

    //Create an IOleUILinkContainer instantiation.
    if (!m_pPG->GetUILinkContainer(&pIUILinks))
        return TRUE;    //Guess we can't update, oh well.

    hWndDoc=GetParent(m_hWnd);
    LoadString(m_pPG->m_hInst, IDS_CAPTION, szCap, sizeof(szCap));

    if (!OleUIUpdateLinks(pIUILinks, hWndDoc, szCap, cLinks))
        {
        /*
         * If updating failed, ask to show the links dialog.  NOTE:
         * OleUIPromptUser has a variable wsprintf argument list
         * after the hWnd parameter!  Use appropriate typecasting!
         */
        uRet=OleUIPromptUser(IDD_CANNOTUPDATELINK, hWndDoc, szCap);

       #ifdef IDC_PU_LINKS
        if (IDC_PU_LINKS==uRet)
       #else
        if (ID_PU_LINKS==uRet)  //Win3.1
       #endif
            {
            //Throw up the links dialog.
            memset(&el, 0, sizeof(el));
            el.cbStruct=sizeof(el);
            el.hWndOwner=hWndDoc;
            el.lpOleUILinkContainer=pIUILinks;
            OleUIEditLinks(&el);
            }
        }

    m_pPG->m_fDirty=pIUILinks->m_fDirty;
    pIUILinks->Release();
    return TRUE;
    }

/*
 * CPage::Close
 *
 * Purpose:
 *  Possibly commits the storage, then releases it reversing the
 *  reference count from Open.
 *
 * Parameters:
 *  fCommit         BOOL indicating if we're to commit.
 *
 * Return Value:
 *  None
 */

void CPage::Close(BOOL fCommit)
    {
    if (NULL==m_pIStorage)
        return;

    if (fCommit)
        Update();

    m_pIStorage->Release();

    //If this was the last close, make all tenants loaded->passive
    if (0==--m_cOpens)
        {
        UINT        i;
        PCTenant    pTenant;

        m_pIStorage=NULL;

        for (i=0; i < m_cTenants; i++)
            {
            if (TenantGet(i, &pTenant, FALSE))
                {
                if (NULL!=m_hWnd)
                    {
                    //Open may select again, so this repaints.
                    pTenant->Select(FALSE);
                    }

                pTenant->Close(FALSE);
                pTenant->Release();
                }
            }

        DestroyWindow(m_hWndTenantList);
        m_hWndTenantList=NULL;
        m_fReopen=TRUE;

        //CHAPTER21MOD
        if (NULL!=m_pmkFile)
            m_pmkFile->Release();

        m_pmkFile=NULL;

        DeleteInterfaceImp(m_pImpIOleItemContainer);
        //End CHAPTER21MOD
        }

    return;
    }

/*
 * CPage::Update
 *
 * Purpose:
 *  Forces a common on the page if it's open.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if there are any open objects on this page,
 *                  that is, we should remain open.
 */

BOOL CPage::Update(void)
    {
    BOOL            fOpen=FALSE;
    UINT            i;
    PCTenant        pTenant;
    HRESULT         hr;
    LPSTREAM        pIStream;
    TENANTLIST      tl;
    PTENANTINFO     pti;
    ULONG           cb;
    LPMALLOC        pIMalloc;

    //Walk the list of objects and update them all as well.
    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            fOpen |= pTenant->Update();
        }

    //Now write our own stream containing the tenant list.
    hr=m_pIStorage->CreateStream(SZSTREAMTENANTLIST, STGM_CREATE
        | STGM_WRITE| STGM_DIRECT | STGM_SHARE_EXCLUSIVE, 0, 0
        , &pIStream);

    if (FAILED(hr))
        return fOpen;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        tl.cTenants=m_cTenants;
        tl.dwIDNext=m_dwIDNext;

        pIStream->Write(&tl, sizeof(TENANTLIST), &cb);

        cb=m_cTenants*sizeof(TENANTINFO);
        pti=(PTENANTINFO)pIMalloc->Alloc(cb);

        if (NULL!=pti)
            {
            for (i=0; i < m_cTenants; i++)
                {
                TenantGet(i, &pTenant, FALSE);
                pTenant->GetInfo((pti+i));
                }

            pIStream->Write(pti, cb, &cb);
            pIMalloc->Free(pti);
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    //Now commit the whole mess and we're done
    if (NULL!=m_pIStorage)
        m_pIStorage->Commit(STGC_DEFAULT);

    return fOpen;
    }

/*
 * CPage::Destroy
 *
 * Purpose:
 *  Removes this page from the given storage.  The caller should
 *  eventually delete this Page object to free the storage.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE contianing this page on which to call
 *                  DestroyElement
 *
 * Return Value:
 *  None
 */

void CPage::Destroy(LPSTORAGE pIStorage)
    {
    if (NULL!=pIStorage)
        {
        OLECHAR szTemp[32];

        Close(FALSE);
        GetStorageName(szTemp);
        pIStorage->DestroyElement(szTemp);
        }

    return;
    }

/*
 * CPage::GetStorageName
 *
 * Parameters:
 *  pszName         LPOLESTR to a buffer in which to store the
 *                  storage name for this page.
 *
 * Return Value:
 *  UINT            Number of characters stored.
 */

UINT CPage::GetStorageName(LPOLESTR pszName)
    {
   #ifdef WIN32ANSI
    char        szTemp[32];
    UINT        cch;

    cch=wsprintf(szTemp, "Page %lu", m_dwID);
    MultiByteToWideChar(CP_ACP, 0, szTemp, -1, pszName, 32);
    return cch;
   #else
    return wsprintf(pszName, TEXT("Page %lu"), m_dwID);
   #endif
    }

/*
 * CPage::Draw
 *
 * Purpose:
 *  Draws the objects on this page to the given hDC.
 *
 * Parameters:
 *  hDC             HDC on which to draw.
 *  xOff, yOff      int offsets for the page.
 *  fNoColor        BOOL indicating black & white screen rendering.
 *  fPrinter        BOOL indicating hDC is on the printer.
 *
 * Return Value:
 *  None
 */

void CPage::Draw(HDC hDC, int xOff, int yOff, BOOL fNoColor
    , BOOL fPrinter)
    {
    int                 i;
    PCTenant            pTenant;
    HDC                 hIC=NULL;
    PCOMBINEDEVICE      pcd=NULL;
    DVTARGETDEVICE     *ptd=NULL;

    /*
     * If printing, tell the tenant to forget the borders. Otherwise
     * we leave xOff and yOff the same to account for scrolling.
     */
    if (fPrinter)
        {
        xOff=LOMETRIC_BORDER+m_pPG->m_xMarginLeft;
        yOff=-LOMETRIC_BORDER-m_pPG->m_yMarginTop;

        /*
         * Get device information.  If this fails, ptd is
         * NULL which is acceptable.
         */
        if (m_pPG->DevReadConfig(&pcd, &hIC))
            ptd=&(pcd->td);
        }

    for (i=(int)m_cTenants-1; i >=0; i--)
        {
        if (TenantGet(i, &pTenant, FALSE))
            {
            RECT        rc, rcWin;
            RECTL       rcl;

            //Paint this tenant only if visible.
            pTenant->RectGet(&rcl, TRUE);
            RECTFROMRECTL(rc, rcl);
            OffsetRect(&rc, -(int)m_pPG->m_xPos
                , -(int)m_pPG->m_yPos);
            GetClientRect(m_hWnd, &rcWin);

            if (IntersectRect(&rc, &rc, &rcWin))
                {
                pTenant->Draw(hDC, ptd, hIC, xOff, yOff
                    , fNoColor, fPrinter);
                }
            }
        }

    //Free whatever CPages::DevReadConfig returned.
    if (NULL!=pcd)
        {
        LPMALLOC    pIMalloc;

        if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
            {
            pIMalloc->Free(pcd);
            pIMalloc->Release();
            }
        }

    if (NULL!=hIC)
        DeleteDC(hIC);

    return;
    }

/*
 * CPage::TenantCreate
 *
 * Purpose:
 *  Creates a new tenant of a specific type.
 *
 * Parameters:
 *  tType           TENANTTYPE to create.
 *  pv              LPVOID providing information for the new
 *                  object creation.
 *  pFE             LPFORMATETC describing how we want this
 *                  rendered.
 *  ppo             PPATRONOBJECT with placement data.
 *  dwData          DWORD extra data to pass to the tenant.
 *
 * Return Value:
 *  None
 */

BOOL CPage::TenantCreate(TENANTTYPE tType, LPVOID pv
    , LPFORMATETC pFE, PPATRONOBJECT ppo, DWORD dwData)
    {
    PCTenant    pTenant;
    UINT        uRet;
    int         x, y;
    int         h, v;
    POINTL      ptl;
    SIZEL       szl;
    RECTL       rcl;
    RECT        rc;

    //New tenants go at top of the pile; zero index to TenantAdd.
    if (!TenantAdd(0, m_dwIDNext, &pTenant))
        return FALSE;

    uRet=pTenant->Create(tType, pv, pFE, &ptl, &szl, m_pIStorage
        , ppo, dwData);

    if (CREATE_FAILED==uRet)
        {
        //Reverse Create AND TenantAdd
        SendMessage(m_hWndTenantList, LB_DELETESTRING, 0, 0L);
        pTenant->Destroy(m_pIStorage);

        pTenant->Release();
        return FALSE;
        }

    m_dwIDNext++;
    m_cTenants++;

    if (NULL!=m_pTenantCur)
        m_pTenantCur->Select(FALSE);

    m_iTenantCur=0;             //First one in the list now.
    m_pTenantCur=pTenant;

    //Tell the tenant where it lives, default is (0,0) in print area
    x=LOMETRIC_BORDER+m_pPG->m_xMarginLeft;
    y=-LOMETRIC_BORDER-m_pPG->m_yMarginTop;

    h=x;
    v=y;

    if (CREATE_PLACEDOBJECT==uRet)
        {
        SetRect(&rc, 3*CXYHANDLE, 3*CXYHANDLE, 0, 0);
        RectConvertMappings(&rc, NULL, FALSE);

        //Make sure place point is on page, otherwise go to (0,0)
        if (((int)ptl.x > x)
            && ((int)ptl.x < x+(int)m_pPG->m_cx-rc.left))
            x=(int)ptl.x;

        //m_pPG->m_cy is absolute
        if (((int)ptl.y < y)
            && ((int)ptl.y > y-(int)m_pPG->m_cy-rc.top))
            y=(int)ptl.y;
        }

    //Bounds check size of the object and fit to page as necessary.
    if (x+(int)szl.cx > (int)(h+m_pPG->m_cx))
        szl.cx=h+m_pPG->m_cx-x;

    //Remember that szl we get from Create is absolute
    if (y-(int)szl.cy < (int)(v-m_pPG->m_cy))
        szl.cy=-(int)(v-m_pPG->m_cy-y);

    SETRECTL(rcl, x, y, x+szl.cx, y-szl.cy);
    m_pTenantCur->RectSet(&rcl, FALSE, TRUE);

    //Force a repaint on this new guy
    m_pTenantCur->Invalidate();
    UpdateWindow(m_hWnd);

    m_pTenantCur->Select(TRUE);

    //Make sure this new tenant knows about showing it's type.
    m_pTenantCur->ShowObjectType(m_pPG->m_fShowTypes);

    //CHAPTER21MOD
    //New tenants must know the available monikers
    if (NULL!=m_pmkFile)
        {
        LPBC        pbc;
        LPMONIKER   pmkPage;
        TCHAR       szTemp[32];
        LPTSTR      pszFile;

       #ifdef WIN32ANSI
        OLECHAR     szW[32];

        GetStorageName(szW);
        WideCharToMultiByte(CP_ACP, 0, szW, -1, szTemp, 32
           , NULL, NULL);
       #else
        GetStorageName(szTemp);
       #endif
        CreateItemMoniker(TEXT("!"), szTemp, &pmkPage);

        //We can get the filename from our file moniker
        CreateBindCtx(0, &pbc);
       #ifdef WIN32ANSI
        LPOLESTR    pszTemp;
        m_pmkFile->GetDisplayName(pbc, NULL, &pszTemp);
        pszFile=(LPTSTR)CoTaskMemAlloc(512);
        WideCharToMultiByte(CP_ACP, 0, pszTemp, -1, pszFile
           , 512, NULL, NULL);
        CoTaskMemFree((void *)pszTemp);
       #else
        m_pmkFile->GetDisplayName(pbc, NULL, &pszFile);
       #endif
        pbc->Release();

        pTenant->NotifyOfRename(pszFile, m_pmkFile, pmkPage);
        pmkPage->Release();
        CoTaskMemFree((void *)pszFile);
        }
    //End CHAPTER21MOD

    //Activate new objects immediately and force a save on them
    if (TENANTTYPE_EMBEDDEDOBJECT==tType)
        {
        m_pTenantCur->Activate(OLEIVERB_SHOW);
        m_pTenantCur->Update();
        }

    return TRUE;
    }

/*
 * CPage::TenantDestroy
 *
 * Purpose:
 *  Destroys the currently selected tenant on this page.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPage::TenantDestroy(void)
    {
    if (NULL==m_pTenantCur)
        {
        MessageBeep(0);
        return FALSE;
        }

    SendMessage(m_hWndTenantList, LB_DELETESTRING
        , m_iTenantCur, 0L);

    m_pTenantCur->Invalidate();
    m_pTenantCur->Destroy(m_pIStorage);
    m_pTenantCur->Release();
    m_pTenantCur=NULL;

    //Update counts, etc., and select the next tenant in the list.
    if (m_iTenantCur==m_cTenants-1)
        m_iTenantCur--;

    if (0==--m_cTenants)
        m_pTenantCur=NULL;
    else
        {
        TenantGet(m_iTenantCur, &m_pTenantCur, TRUE);
        m_pTenantCur->Select(TRUE);
        }

    UpdateWindow(m_hWnd);
    return TRUE;
    }

/*
 * CPage::TenantClip
 *
 * Purpose:
 *  Copies or cuts the currently selected tenant to the clipoard.
 *
 * Parameters:
 *  fCut            BOOL TRUE to cut the object, FALSE to copy.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPage::TenantClip(BOOL fCut)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet=FALSE;

    if (NULL==m_pTenantCur)
        return FALSE;

    /*
     * To perform a data transfer operation, we need to create a
     * data object with the selected object's data inside. To do
     * this we CoCreateInstance on CLSID_DataTransferObject
     * (Also implemented in this chapter), retrieve data from the
     * object we have, stuff that data into the transfer object,
     * then stick that object on the clipboard.
     *
     * Since we'll want an identical object at other times, like for
     * drag-drop, we use a private function to actually create it.
     */

    pIDataObject=TransferObjectCreate(NULL);

    if (NULL!=pIDataObject)
        {
        if (SUCCEEDED(OleSetClipboard(pIDataObject)))
            {
            if (fCut)
                TenantDestroy();

            fRet=TRUE;
            }

        pIDataObject->Release();
        }

    return fRet;
    }

/*
 * CPage::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected on this
 *  page for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU of the Edit menu.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPage::FQueryObjectSelected(HMENU hMenu)
    {
    HMENU       hMenuTemp;

    /*
     * This will only be called on WM_INITMENUPOPUP, we'll also
     * use this function to create the Verb menu for this object.
     */
    if (NULL!=m_pTenantCur)
        {
        m_pTenantCur->AddVerbMenu(hMenu, MENUPOS_OBJECT);
        return TRUE;
        }

    OleUIAddVerbMenu(NULL, NULL, hMenu, MENUPOS_OBJECT
        , IDM_VERBMIN, IDM_VERBMAX, FALSE, 0, &hMenuTemp);

    return FALSE;
    }

/*
 * CPage::ActivateObject
 *
 * Purpose:
 *  Executes a verb on the currently selected object.
 *
 * Parameters:
 *  iVerb           LONG of the selected verb.
 *
 * Return Value:
 *  None
 */

void CPage::ActivateObject(LONG iVerb)
    {
    if (NULL==m_pTenantCur)
        return;

    m_pTenantCur->Activate(iVerb);
    return;
    }

/*
 * CPage::ShowObjectTypes
 *
 * Purpose:
 *  Loops through all the tenants and tells each one to turn on or
 *  off the Show Objects features.
 *
 * Parameters:
 *  fShow           BOOL indicating to show the type or not.
 *
 * Return Value:
 *  None
 */

void CPage::ShowObjectTypes(BOOL fShow)
    {
    PCTenant    pTenant;
    UINT        i;

    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            pTenant->ShowObjectType(fShow);
        }

    return;
    }

/*
 * CPage::NotifyTenantsOfRename
 *
 * Purpose:
 *  Loops through all the tenants and informs each of the new
 *  document name.
 *
 * Parameters:
 *  pszFile         LPTSTR of the new filename.
 *  pmk             LPMONKIER for the new filename.
 *
 * Return Value:
 *  None
 */

void CPage::NotifyTenantsOfRename(LPTSTR pszFile, LPMONIKER pmk)
    {
    //CHAPTER21MOD
    PCTenant    pTenant;
    UINT        i;
    LPMONIKER   pmkPage;
    LPMONIKER   pmkAll;
    TCHAR       szTemp[32];

    //Save the file moniker
    ReleaseInterface(m_pmkFile);
    m_pmkFile=pmk;
    m_pmkFile->AddRef();

    //Create a page moniker to send to the tenants.
   #ifdef WIN32ANSI
    OLECHAR     szW[32];

    GetStorageName(szW);
    WideCharToMultiByte(CP_ACP, 0, szW, -1, szTemp, 32
       , NULL, NULL);
   #else
    GetStorageName(szTemp);
   #endif
    CreateItemMoniker(TEXT("!"), szTemp, &pmkPage);

    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            pTenant->NotifyOfRename(pszFile, pmk, pmkPage);
        }

    /*
     * Register a File!Page!"\" wildcard moniker as well.
     * Note that the page is already marked as running
     * with the document's wildcard moniker.
     */
    CreateItemMoniker(TEXT("!"), TEXT("\\"), &pmkAll);

    if (NULL!=pmkAll)
        {
        LPMONIKER   pmkWild=NULL;
        LPMONIKER   pmkTemp=NULL;

        INOLE_RevokeAsRunning(&m_dwRegROTWild);
        pmk->ComposeWith(pmkPage, FALSE, &pmkTemp);

        if (NULL!=pmkTemp)
            {
            pmkTemp->ComposeWith(pmkAll, FALSE, &pmkWild);
            pmkTemp->Release();
            }

        if (NULL!=pmkWild)
            {
            INOLE_RegisterAsRunning(this, pmkWild
                , ROTFLAGS_REGISTRATIONKEEPSALIVE, &m_dwRegROTWild);
            pmkWild->Release();
            }

        pmkAll->Release();
        }

    //If anything held onto this, they AddRef'd
    pmkPage->Release();
    //End CHAPTER21MOD
    return;
    }

/*
 * CPage::ConvertObject
 *
 * Purpose:
 *  Invokes and handles the results of the Convert dialog
 *
 * Parameters:
 *  hWndFrame       HWND to use as the parent of the dialog.
 *  fNoServer       BOOL indicating if this was called because
 *                  ActivateObject failed.
 *
 * Return Value:
 *  None
 */

BOOL CPage::ConvertObject(HWND hWndFrame, BOOL fNoServer)
    {
    HRESULT         hr;
    OLEUICONVERT    ct;
    TENANTTYPE      tType;
    TENANTINFO      ti;
    UINT            uRet;
    HCURSOR         hCur;
    BOOL            fActivate=fNoServer;
    SIZEL           szl;

    if (NULL==m_pTenantCur)
        return FALSE;

    tType=m_pTenantCur->TypeGet();

    if (TENANTTYPE_STATIC==tType)
        {
        MessageBeep(0);
        return FALSE;
        }

    //Get object information we may want.
    m_pTenantCur->GetInfo(&ti);

    //Fill the structure.
    memset(&ct, 0, sizeof(ct));
    ct.cbStruct=sizeof(OLEUICONVERT);
    ct.hWndOwner=hWndFrame;
    ct.fIsLinkedObject=(TENANTTYPE_LINKEDOBJECT==tType);
    ct.dvAspect=ti.fe.dwAspect;

    m_pTenantCur->ObjectClassFormatAndIcon(&ct.clsid, &ct.wFormat
        , &ct.lpszUserType, &ct.hMetaPict, &ct.lpszDefLabel);

    uRet=OleUIConvert(&ct);

    if (OLEUI_OK==uRet)
        {
        //Potentially a long operation.
        hCur=SetCursor(LoadCursor(NULL, IDC_WAIT));

        //Prevent multiple repaints.
        m_pTenantCur->EnableRepaint(FALSE);

        //First, let's bother with the iconic aspect switch.
        if ((DVASPECT_ICON==ct.dvAspect && ct.fObjectsIconChanged)
            || ct.dvAspect!=ti.fe.dwAspect)
            {
            HGLOBAL     hMem=NULL;

            //Only pass non-NULL handle for icon aspects.
            if (DVASPECT_ICON==ct.dvAspect)
                hMem=ct.hMetaPict;

            m_pPG->m_fDirty=m_pTenantCur->SwitchOrUpdateAspect(hMem
                , FALSE);
            }

        //Now change types around.
        if ((CF_SELECTCONVERTTO & ct.dwFlags)
            && ct.clsid!=ct.clsidNew)
            {
            LPSTORAGE   pIStorage;

            /*
             * User selected convert, so:
             *  1.  Unload the object (back to passive state)
             *  2.  Call INOLE_DoConvert, which calls WriteClassStg,
             *      WriteFmtUserTypeStg, and SetConvertStg.
             *  3.  Reload the object and force an update.
             */

            //This should be the only close necessary.
            m_pTenantCur->StorageGet(&pIStorage);
            m_pTenantCur->Close(TRUE);

            hr=INOLE_DoConvert(pIStorage, ct.clsidNew);

            //Need to commit the new type and format
            pIStorage->Commit(STGC_DEFAULT);
            pIStorage->Release();

            if (SUCCEEDED(hr))
                {
                LPUNKNOWN   pObj;
                LPOLEOBJECT pIOleObject;

                //Reload and update.
                m_pTenantCur->Load(m_pIStorage, &ti);

                m_pTenantCur->ObjectGet(&pObj);
                pObj->QueryInterface(IID_IOleObject
                    , (PPVOID)&pIOleObject);
                pIOleObject->Update();
                pIOleObject->Release();
                pObj->Release();
                }

            m_pPG->m_fDirty=TRUE;
            }

        if (CF_SELECTACTIVATEAS & ct.dwFlags)
            {
            /*
             * User selected Activate As, so:
             *  1.  Add the TreatAs entry in the registry
             *      through CoTreatAsClass
             *  2.  Unload all objects of the old CLSID that you
             *      have loaded.
             *  3.  Reload objects as desired
             *  4.  Activate the current object.
             */

            hr=CoTreatAsClass(ct.clsid, ct.clsidNew);

            if (SUCCEEDED(hr))
                {
                PCTenant    pTenant;
                UINT        i;

                for (i=0; i < m_cTenants; i++)
                    {
                    if (TenantGet(i, &pTenant, FALSE))
                        {
                        pTenant->GetInfo(&ti);
                        pTenant->Close(FALSE);
                        pTenant->Load(m_pIStorage, &ti);
                        }
                    }

                fActivate=TRUE;
                }
            }

        //These two steps insure the object knows of the size.
        m_pTenantCur->SizeGet(&szl, FALSE);
        m_pTenantCur->SizeSet(&szl, FALSE, TRUE);

        m_pTenantCur->EnableRepaint(TRUE);
        m_pTenantCur->Repaint();

        if (fActivate)
            m_pTenantCur->Activate(OLEIVERB_SHOW);

        SetCursor(hCur);
        }

    CoTaskMemFree((void*)ct.lpszUserType);
    INOLE_MetafilePictIconFree(ct.hMetaPict);

    return TRUE;
    }

/*
 * CPage::FQueryLinksInPage
 *
 * Purpose:
 *  Pass through to current page to see if there are any linked
 *  objects.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPage::FQueryLinksInPage()
    {
    PCTenant    pTenant;
    UINT        i;
    BOOL        fRet=FALSE;

    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            fRet |= (pTenant->TypeGet()==TENANTTYPE_LINKEDOBJECT);
        }

    return fRet;
    }

/*
 * CPage::TenantGet
 * (Protected)
 *
 * Purpose:
 *  Returns a tenant of a given index returning a BOOL so it's
 *  simple to use this function inside if statements.
 *
 * Parameters:
 *  iTenant         UINT tenant to retrieve 0 based.
 *  ppTenant        PCPage * in which to return the tenant
 *                  pointer
 *  fOpen           BOOL indicating if we should open this
 *                  tenant as well.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPage::TenantGet(UINT iTenant, PCTenant *ppTenant
    , BOOL fOpen)
    {
    if (NULL==ppTenant)
        return FALSE;

    if (LB_ERR!=SendMessage(m_hWndTenantList, LB_GETTEXT
        , iTenant, (LONG)ppTenant))
        {
        if (fOpen)
            (*ppTenant)->Open(m_pIStorage);

        return TRUE;
        }

    return FALSE;
    }

//CHAPTER21MOD
/*
 * CPage::TenantGetFromID
 * (Protected)
 *
 * Purpose:
 *  Finds a tenant pointer from an ID for use from
 *  IOleItemContainer::GetObject
 *
 * Parameters:
 *  dwID            DWORD identifier of the tenant to find.
 *  ppTenant        PCTenant * in which to return the tenant
 *                  pointer
 *  fOpen           BOOL indicating if we should open this tenant as
 *                  well.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPage::TenantGetFromID(DWORD dwID, PCTenant *ppTenant
    , BOOL fOpen)
    {
    UINT        i;
    PCTenant    pTenant;

    if (NULL==ppTenant)
        return FALSE;

    for (i=0; i < m_cTenants; i++)
        {
        if (!TenantGet(i, &pTenant, FALSE))
            continue;

        if (pTenant->GetID()==dwID)
            {
            if (fOpen)
                pTenant->Open(m_pIStorage);

            *ppTenant=pTenant;
            return TRUE;
            }
        }

    return FALSE;
    }

//End CHAPTER21MOD

/*
 * CPage::TenantAdd
 * (Protected)
 *
 * Purpose:
 *  Creates a new tenant initialized to the given values.  The new
 *  tenants's storage is created if it does not already exist.  If
 *  fOpenStorage is set the the tenants's storage is opened and left
 *  opened.
 *
 * Parameters:
 *  iTenant         UINT Location at which to insert tenant; new
 *                  tenant is inserted after this position.  NOVALUE
 *                  for the end.
 *  dwID            DWORD ID for this tenant.
 *  ppNew           PCTenant * in which to store the new tenant.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPage::TenantAdd(UINT iTenant, DWORD dwID
    , PCTenant *ppNew)
    {
    PCTenant    pTenant;
    LRESULT     lr;

    if (NULL!=ppNew)
        *ppNew=NULL;

    pTenant=new CTenant(dwID, m_hWnd, m_pPG);

    if (NULL==pTenant)
        return FALSE;

    //The constructor doesn't AddRef, so we need to.
    pTenant->AddRef();

    //Now try to add to the listbox.
    lr=SendMessage(m_hWndTenantList, LB_INSERTSTRING, iTenant
        , (LONG)pTenant);

    if (lr < 0)
        {
        pTenant->Release();
        return FALSE;
        }

    *ppNew=pTenant;
    return TRUE;
    }

/*
 * CPage::TransferObjectCreate
 * (Protected)
 *
 * Purpose:
 *  Creates a DataTransferObject and stuff the current selection's
 *  data into it.
 *
 * Parameters:
 *  pptl            PPOINTL containing the pick point in device
 *                  units applicable only to drag-drop; since
 *                  drag-drop is inherently mouse oriented, we use
 *                  device units for the point.  Ignored if NULL.
 *
 * Return Value:
 *  LPDATAOBJECT    Pointer to the object created, NULL on failure
 */

LPDATAOBJECT CPage::TransferObjectCreate(PPOINTL pptl)
    {
    LPDATAOBJECT    pIDataObject;
    LPDATAOBJECT    pIDataT;
    PPATRONOBJECT   ppo;
    RECTL           rcl;
    LPUNKNOWN       pObj;
    FORMATETC       fe;
    STGMEDIUM       stm;
    HRESULT         hr;
    //CHAPTER21MOD
    HGLOBAL         hMem;
    //End CHAPTER21MOD

    m_pTenantCur->ObjectGet(&pObj);

    hr=CoCreateInstance(CLSID_DataTransferObject, NULL
        , CLSCTX_INPROC_SERVER, IID_IDataObject
        , (PPVOID)&pIDataObject);

    if (FAILED(hr))
        return NULL;

    //Go get the data we should hold on to.
    hr=pObj->QueryInterface(IID_IDataObject, (PPVOID)&pIDataT);

    if (FAILED(hr))
        {
        pIDataObject->Release();
        pObj->Release();
        return NULL;
        }

    //Copy from known obj into transfer obj.  Ordering is important!

    //Generate placeable object structure
    stm.tymed=TYMED_HGLOBAL;
    stm.pUnkForRelease=NULL;
    stm.hGlobal=GlobalAlloc(GHND, sizeof(PATRONOBJECT));

    if (NULL==stm.hGlobal)
        {
        pIDataObject->Release();
        pObj->Release();
        return NULL;
        }

    ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);

    m_pTenantCur->SizeGet(&ppo->szl, FALSE);
    ppo->szl.cy=-ppo->szl.cy; //Negate to make absolute size

    m_pTenantCur->RectGet(&rcl, FALSE);
    ppo->ptl.x=rcl.left;
    ppo->ptl.y=rcl.top;

    if (NULL==pptl)
        {
        ppo->ptlPick.x=0;
        ppo->ptlPick.y=0;
        }
    else
        ppo->ptlPick=*pptl;

    m_pTenantCur->FormatEtcGet(&ppo->fe, FALSE);

    //If this is a linked object, just copy a presentation
    if (TENANTTYPE_LINKEDOBJECT==m_pTenantCur->TypeGet())
        m_pTenantCur->FormatEtcGet(&ppo->fe, TRUE);

    SETDefFormatEtc(fe, m_pPG->m_cf, TYMED_HGLOBAL);
    pIDataObject->SetData(&fe, &stm, TRUE);

    /*
     * Here now we have to include CFSTR_EMBEDDEDOBJECT and
     * CFSTR_OBJECTDESCRIPTOR when what we have selected is, in
     * fact, a compound document object.  We'll just ask the tenant
     * to set these in pIDataObject since it knows what the object.
     * If we copy embedded object data, make sure the PATRONOBJECT
     * structure has the right format in it as well.
     */
    m_pTenantCur->CopyEmbeddedObject(pIDataObject, &ppo->fe, pptl);
    //CHAPTER21MOD
    hMem=stm.hGlobal;
    //End CHAPTER21MOD

    //Copy the actual presentation.
    m_pTenantCur->FormatEtcGet(&fe, TRUE);
    pIDataT->GetData(&fe, &stm);
    pIDataObject->SetData(&fe, &stm, TRUE);

    //CHAPTER21MOD
    //Copy a link to this tenant if it's embedded
    m_pTenantCur->CopyLinkedObject(pIDataObject, &ppo->fe, pptl);
    GlobalUnlock(hMem); //ppo
    //End CHAPTER21MOD

    pIDataT->Release();
    pObj->Release();
    return pIDataObject;    //Caller now responsible
    }


PAGEMOUS.CPP

/*
 * PAGEMOUS.CPP
 * Patron Chapter 21
 *
 * Implementation of mouse-related member functions of CPage.
 * The remainder is in PAGE.CPP.  This separate file keeps this
 * grungy hit-testing/drawing code out of our way.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

//Lookups into the array using g_rgHTCode[x+y*3] in PAGEMOUS.CPP
#define YTOP            0
#define YMID            1
#define YBOT            2
#define XLEFT           0
#define XMID            1
#define XRIGHT          2

//Values to restrict sizing in CPage::OnMouseMove
#define SIZINGTOP       0x0001
#define SIZINGBOTTOM    0x0002
#define SIZINGLEFT      0x0004
#define SIZINGRIGHT     0x0008

//This array is for hit-testing lookups
static UINT g_rgHTCode[9]={HTTOPLEFT, HTTOP, HTTOPRIGHT
    , HTLEFT, HTCLIENT, HTRIGHT, HTBOTTOMLEFT, HTBOTTOM
    , HTBOTTOMRIGHT};

//This is for restricting tracking based on the hit-test
static UINT g_rguSizingFlags[9]={SIZINGTOP | SIZINGLEFT, SIZINGTOP
    , SIZINGTOP | SIZINGRIGHT, SIZINGLEFT, 0, SIZINGRIGHT
    , SIZINGBOTTOM | SIZINGLEFT, SIZINGBOTTOM
    , SIZINGBOTTOM | SIZINGRIGHT};

/*
 * CPage::OnRightDown
 *
 * Purpose:
 *  Called when the user clicks with the right button on this
 *  page.  If there is an object here, determined by the last
 *  hit-test code, the we'll make a popup-menu for it.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if the action changed the object.
 */

BOOL CPage::OnRightDown(UINT uKeys, UINT x, UINT y)
    {
    HMENU       hMenu;
    HMENU       hMenuRes;
    HINSTANCE   hInst;
    HWND        hWndFrame, hWndT;
    POINT       pt;
    UINT        i, cItems;

    //Select the tenant under the mouse, if there is one.
    if (!SelectTenantAtPoint(x, y))
        return FALSE;

    /*
     * Get the top-level window to which menu command will go.  This
     * will be whatever parent doesn't have a parent itself...
     */
    hWndT=GetParent(m_hWnd);

    while (NULL!=hWndT)
        {
        hWndFrame=hWndT;
        hWndT=GetParent(hWndT);
        }

    /*
     * Build a popup menu for this object with Cut, Copy, Delete,
     * and object verbs.
     */
    hInst=GETWINDOWINSTANCE(m_hWnd);    //Macro in BOOK1632.H
    hMenuRes=LoadMenu(hInst, MAKEINTRESOURCE(IDR_RIGHTPOPUPMENU));

    if (NULL==hMenuRes)
        return FALSE;

    hMenu=CreatePopupMenu();
    cItems=GetMenuItemCount(hMenuRes);

    for (i=0; i < cItems; i++)
        {
        TCHAR       szTemp[80];
        int         id, uFlags;

        GetMenuString(hMenuRes, i, szTemp, sizeof(szTemp)
            , MF_BYPOSITION);
        id=GetMenuItemID(hMenuRes, i);

        uFlags=(0==id) ? MF_SEPARATOR : MF_STRING | MF_ENABLED;
        AppendMenu(hMenu, uFlags, id, szTemp);
        }

    DestroyMenu(hMenuRes);

    //Munge the Object menu item
    m_pTenantCur->AddVerbMenu(hMenu, MENUPOS_OBJECTONPOPUP);

    //Enable or disable the Links item.
    i=FQueryLinksInPage() ? MF_ENABLED : MF_DISABLED | MF_GRAYED;
    EnableMenuItem(hMenu, IDM_EDITLINKS, i | MF_BYCOMMAND);

    SETPOINT(pt, x, y);
    ClientToScreen(m_hWnd, &pt);

    TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON
        , pt.x, pt.y, 0, hWndFrame, NULL);

    DestroyMenu(hMenu);
    return FALSE;
    }

/*
 * CPage::SelectTenantAtPoint
 *
 * Purpose:
 *  Selects whatever tenant is at the point (x,y) if there is one,
 *  deselecting the previously selected tenant.
 *
 * Parameters:
 *  x, y            UINT coordinates of the mouse.
 *
 * Return Value:
 *  BOOL            TRUE if there is a tenant here, FALSE otherwise.
 */

BOOL CPage::SelectTenantAtPoint(UINT x, UINT y)
    {
    UINT            iTenant;
    PCTenant        pTenant;
    PCDocument      pDoc;

    iTenant=TenantFromPoint(x, y, &pTenant);

    if (NULL==pTenant)
        return FALSE;

    //Make the document window active in any case
    pDoc=(PCDocument)SendMessage(GetParent(m_hWnd), DOCM_PDOCUMENT
        , 0, 0L);

    if (NULL!=pDoc)
        BringWindowToTop(pDoc->Window());

    //If this one is already current, we might be now sizing.
    if (pTenant==m_pTenantCur)
        return TRUE;

    //Deselect the current tenant
    if (NULL!=m_pTenantCur)
        m_pTenantCur->Select(FALSE);

    //Move this tenant to the top of the list
    m_iTenantCur=0;

    SendMessage(m_hWndTenantList, LB_DELETESTRING, iTenant, 0L);
    SendMessage(m_hWndTenantList, LB_INSERTSTRING, 0
        , (LONG)pTenant);

    //Select and repaint the new tenant to show it up front
    m_pTenantCur=pTenant;

    m_pTenantCur->Repaint();
    m_pTenantCur->Select(TRUE);

    return TRUE;
    }

/*
 * CPage::OnLeftDown
 *
 * Purpose:
 *  Called when the user clicks with the left button on this page.
 *  We find the object under that position that is visibly on top
 *  (always the first one under this location in the page list since
 *  we paint in reverse order) and select it.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if the action changed the object.
 */

BOOL CPage::OnLeftDown(UINT uKeys, UINT x, UINT y)
    {
    /*
     * If the mouse is in a position to start dragging,
     * start the timer as with sizing below.
     */
    if (HTCAPTION==m_uHTCode)
        {
        m_fDragPending=TRUE;

        //Save down point and start timer.
        m_ptDown.x=x;
        m_ptDown.y=y;

        m_uKeysDown=uKeys;

        m_fTimer=TRUE;
        SetTimer(m_hWnd, IDTIMER_DEBOUNCE, m_cDelay, NULL);
        return FALSE;
        }

    /*
     * If the mouse is in a position to start sizing, start
     * the debounce timer and note the condition.  The sizing
     * will start in OnTimer or OnMouseMove.  This will always
     * happen on the currently selected tenant, and m_uHTCode is
     * set in OnNCHitTest below.
     */
    if (HTNOWHERE!=m_uHTCode && HTCLIENT!=m_uHTCode)
        {
        m_fSizePending=TRUE;

        //Save down point and start timer.
        m_ptDown.x=x;
        m_ptDown.y=y;

        m_fTimer=TRUE;
        SetTimer(m_hWnd, IDTIMER_DEBOUNCE, m_cDelay, NULL);
        return FALSE;
        }

    SelectTenantAtPoint(x, y);
    return FALSE;
    }

/*
 * CPage::OnLeftUp
 *
 * Purpose:
 *  Called when the user clicks up with the left button on this
 *  page. We stop tracking on this message, if necessary, and
 *  resize the object.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if this action changed the object.
 */

BOOL CPage::OnLeftUp(UINT uKeys, UINT x, UINT y)
    {
    RECT    rc, rcT;

    if (m_fSizePending || m_fDragPending)
        {
        m_fSizePending=FALSE;
        m_fDragPending=FALSE;

        if (m_fTimer)
            {
            KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
            m_fTimer=FALSE;
            }

        return FALSE;
        }

    if (!m_fTracking)
        return FALSE;

    //Remove the dotted rectangle.
    RECTFROMRECTL(rc, m_rcl)
    DrawFocusRect(m_hDC, &rc);
    ReleaseDC(m_hWnd, m_hDC);

    ReleaseCapture();
    m_fTracking=FALSE;

    //If the original and new rects are the same, nothing happened.
    RECTFROMRECTL(rcT, m_rclOrg);

    if (EqualRect(&rc, &rcT))
        return FALSE;

    RECTFROMRECTL(rcT, m_rclOrg);
    InvalidateRect(m_hWnd, &rcT, TRUE);

    //Invalidate on the screen before accounting for scrolling
    InvalidateRect(m_hWnd, &rc, TRUE);

    //Factor in scrolling and tell the tenant where it now stands.
    OffsetRect(&rc, (int)m_pPG->m_xPos, (int)m_pPG->m_yPos);
    RECTLFROMRECT(m_rcl, rc);
    m_pTenantCur->RectSet(&m_rcl, TRUE, TRUE);

    UpdateWindow(m_hWnd);
    return TRUE;
    }

/*
 * CPage::OnLeftDoubleClick
 *
 * Purpose:
 *  Called when the user double-clicks with the left button on this
 *  page.  We find the object under that position that is visibly on
 *  top (always the first one under this location in the page list
 *  since we paint in reverse order) and activate it.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if the action changed the object.
 */

BOOL CPage::OnLeftDoubleClick(UINT uKeys, UINT x, UINT y)
    {
    /*
     * The current tenant is the only one that can be activated, so
     * we just have to make sure the mouse is there.  For that we
     * can use the last hit-test code we saw since it's updated on
     * every mouse move.
     */

    if (HTNOWHERE!=m_uHTCode)
        return m_pTenantCur->Activate(OLEIVERB_PRIMARY);

    return FALSE;
    }

/*
 * CPage::OnMouseMove
 *
 * Purpose:
 *  Processes WM_MOUSEMOVE on a page so we can handle tracking
 *  resize of a tenant.
 *
 * Parameters:
 *  x, y            int device coordinates to check.
 *
 * Return Value:
 *  None
 */

void CPage::OnMouseMove(UINT uKeys, int x, int y)
    {
    RECT        rc, rcO, rcB;
    int         cxy;

    if (m_fSizePending || m_fDragPending)
        {
        int     dx, dy;

        dx=(x > m_ptDown.x) ? (x-m_ptDown.x) : (m_ptDown.x-x);
        dy=(y > m_ptDown.y) ? (y-m_ptDown.y) : (m_ptDown.y-y);

        /*
         * Has the mouse moved outside the debounce distance?  If
         * so, we can start sizing.  Note that this happens
         * regardless of the timer state.
         */
        if (dx > m_cxyDist || dy > m_cxyDist)
            {
            POINT       pt;
            BOOL        fSize=m_fSizePending;
            BOOL        fDrag=m_fDragPending;

            m_fSizePending=FALSE;
            m_fDragPending=FALSE;

            if (m_fTimer)
                {
                KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
                m_fTimer=FALSE;
                }

            if (fDrag)
                {
                //Set dirty flag if drag & drop changed things.
                m_pPG->m_fDirty |= DragDrop(m_uKeysDown, x, y);
                return;
                }

            if (fSize)
                StartSizeTracking();

            /*
             * Since we might have moved out of the sizing handle
             * in order to start the operation, we need to set the
             * m_uSizingFlags field based on the original down point
             * for subsequent mouse moves to function properly.
             * Note that OnNCHitTest expects screen coordinates.
             */
            SETPOINT(pt, m_ptDown.x, m_ptDown.y);
            ClientToScreen(m_hWnd, &pt);
            OnNCHitTest(pt.x, pt.y);
            OnSetCursor(m_uHTCode);
            return;
            }
        }

    if (!m_fTracking)
        return;

    //Get rid of the old rectangle.
    RECTFROMRECTL(rc, m_rcl)
    DrawFocusRect(m_hDC, &rc);

    /*
     * Calculate the new.  The flags in m_uSizingFlags tell us what
     * to change.  We limit the object by the page margins and a
     * minimum size of 3*CXYHANDLE in either dimension.
     */
    cxy=3*CXYHANDLE;

    RECTFROMRECTL(rcO, m_rclOrg);
    RECTFROMRECTL(rcB, m_rclBounds);

    if (m_uSizingFlags & SIZINGTOP)
        {
        if (y >= rcO.bottom-cxy)
            y=rcO.bottom-cxy;

        if (y <= rcB.top)           //Limit to top of page.
            y=rcB.top;

        m_rcl.top=y;
        }

    if (m_uSizingFlags & SIZINGBOTTOM)
        {
        if (y <= rcO.top+cxy)
            y=rcO.top+cxy;

        if (y >= rcB.bottom)         //Limit to bottom of page.
            y=rcB.bottom;

        m_rcl.bottom=y;
        }

    if (m_uSizingFlags & SIZINGLEFT)
        {
        if (x >= rcO.right-cxy)
            x=rcO.right-cxy;

        if (x <= rcB.left)           //Limit to left of page.
            x=rcB.left;

        m_rcl.left=x;
        }

    if (m_uSizingFlags & SIZINGRIGHT)
        {
        if (x <= rcO.left+cxy)
            x=rcO.left+cxy;

        if (x >= rcB.right)          //Limit to right of page.
            x=rcB.right;

        m_rcl.right=x;
        }

    //Draw the new
    RECTFROMRECTL(rc, m_rcl)
    DrawFocusRect(m_hDC, &rc);

    return;
    }

/*
 * CPage::OnTimer
 *
 * Purpose:
 *  Processes WM_TIMER messages to a page used to perform mouse
 *  debouncing.
 *
 * Parameters:
 *  uID             UINT timer ID.
 *
 * Return Value:
 *  None
 */

void CPage::OnTimer(UINT uID)
    {
    if (m_fSizePending || m_fDragPending)
        {
        BOOL        fSize=m_fSizePending;
        BOOL        fDrag=m_fDragPending;

        /*
         * Having this function called means the delay requirement
         * is satisfied.  Start tracking for sizing or dragging.
         */

        m_fSizePending=FALSE;
        m_fDragPending=FALSE;

        KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
        m_fTimer=FALSE;

        if (fDrag)
            {
            POINT       pt;

            GetCursorPos(&pt);
            m_pPG->m_fDirty |= DragDrop(m_uKeysDown
                , m_ptDown.x, m_ptDown.y);
            return;
            }

        if (fSize)
            StartSizeTracking();
        }

    return;
    }

/*
 * CPage::StartSizeTracking
 *
 * Purpose:
 *  Begins sizing of a tenant when mouse debounce conditions are
 *  met.
 *
 * Parameters:
 *  uID             UINT timer ID.
 *
 * Return Value:
 *  None
 */

void CPage::StartSizeTracking(void)
    {
    RECT        rc;

    m_pTenantCur->RectGet(&m_rcl, TRUE);
    SetCapture(m_hWnd);
    m_fTracking=TRUE;

    m_hDC=GetDC(m_hWnd);

    //Place the rectangle exactly where it is on the screen.
    RECTFROMRECTL(rc, m_rcl)
    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);
    RECTLFROMRECT(m_rcl, rc);
    m_rclOrg=m_rcl;

    DrawFocusRect(m_hDC, &rc);

    m_pPG->CalcBoundingRect(&rc, TRUE);
    RECTLFROMRECT(m_rclBounds, rc);
    return;
    }

/*
 * CPage::OnNCHitTest
 *
 * Purpose:
 *  Processes WM_NCHITTEST on a page so we can check for hits on the
 *  handles of the selected object for resizing.  We only save
 *  information for ourselves and do not interfere with normal
 *  hit-testing.
 *
 * Parameters:
 *  x, y            UINT device coordinates to check.
 *
 * Return Value:
 *  None
 */

void CPage::OnNCHitTest(UINT x, UINT y)
    {
    RECT        rc;
    RECTL       rcl;
    int         iMid1, iMid2;
    int         xHit, yHit;
    POINT       pt;
    int         x0, y0;

    /*
     * Ignore this message if it occurs during tracking to adjust
     * for the behavior of oddball mouse drivers.
     */
    if (m_fSizePending || m_fTracking)
        return;

    //Default: don't start sizing on a click, don't hit an object.
    m_uSizingFlags=0;
    m_uHTCode=HTNOWHERE;

    if (NULL==m_pTenantCur)
        return;

    //Convert device points to our coordinates
    m_pTenantCur->RectGet(&rcl, FALSE);
    RECTFROMRECTL(rc, rcl);
    RectConvertMappings(&rc, NULL, TRUE);
    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

    SETPOINT(pt, x, y);
    ScreenToClient(m_hWnd, &pt);
    x0=pt.x;
    y0=pt.y;

    if (x0 < rc.left || x0 > rc.right)
        return;

    if (y0 < rc.top || y0 > rc.bottom)
        return;

    //It's at least in the object.
    m_uHTCode=HTCLIENT;

    //Check for hits in horizontal regions
    xHit=NOVALUE;
    iMid1=rc.left+((rc.right-rc.left-CXYHANDLE) >> 1);
    iMid2=rc.left+((rc.right-rc.left+CXYHANDLE) >> 1);

    if (x0 >= rc.left && x0 <= rc.left+CXYHANDLE)
        xHit=XLEFT;
    else if (x0 >= iMid1 && x0 <= iMid2)
        xHit=XMID;
    else if (x0 >= rc.right-CXYHANDLE && x0 <= rc.right)
        xHit=XRIGHT;

    //Don't exit yet if we didn't hit a handle--might hit a y edge.

    //Check for hits in vertical regions
    yHit=NOVALUE;
    iMid1=rc.top+((rc.bottom-rc.top-CXYHANDLE) >> 1);
    iMid2=rc.top+((rc.bottom-rc.top+CXYHANDLE) >> 1);

    if (y0 >= rc.top && y0 <= rc.top+CXYHANDLE)
        yHit=YTOP;
    else if (y0 >= iMid1 && y0 <= iMid2)
        yHit=YMID;
    else if (y0 >= rc.bottom-CXYHANDLE && y0 <= rc.bottom)
        yHit=YBOT;

    /*
     * If we hit any edge, but didn't hit a handle, then one of xHit
     * and yHit will be NOVALUE and the other something else.  When
     * we hit an edge on the 'something else' then we're on a drag
     * point.
     */

    if ((NOVALUE==xHit && NOVALUE==yHit)
        || (XMID==xHit && YMID==yHit)
        || (NOVALUE==xHit && YMID==yHit)
        || (XMID==xHit && NOVALUE==yHit))
        return;

    if ((NOVALUE==xHit && (YTOP==yHit || YBOT==yHit))
        || ((XLEFT==xHit || XRIGHT==xHit) && NOVALUE==yHit))
        {
        m_uHTCode=HTCAPTION;
        return;
        }

    //We hit a handle, so save our HT code
    m_uSizingFlags=g_rguSizingFlags[xHit+(yHit*3)];
    m_uHTCode=g_rgHTCode[xHit+(yHit*3)];
    return;
    }

/*
 * CPage::SetCursor
 *
 * Purpose:
 *  Processes WM_SETCURSOR using the code from OnNCHitTest.
 *
 * Parameters:
 *  x, y            UINT device coordinates to check.
 *
 * Return Value:
 *  LRESULT         HT* code for Windows.
 */

BOOL CPage::OnSetCursor(UINT uHTCode)
    {
    HCURSOR     hCur;
    UINT        iCur;

    /*
     * We really just ignore uHTCode and use the one we saved
     * in OnNCHitTest.
     */

    switch (m_uHTCode)
        {
        case HTTOP:
        case HTBOTTOM:
            iCur=IDC_VARROWS;
            break;

        case HTLEFT:
        case HTRIGHT:
            iCur=IDC_HARROWS;
            break;

        case HTTOPLEFT:
        case HTBOTTOMRIGHT:
            iCur=IDC_NWSEARROWS;
            break;

        case HTTOPRIGHT:
        case HTBOTTOMLEFT:
            iCur=IDC_NESWARROWS;
            break;

        case HTCAPTION:
            iCur=IDC_SMALLARROWS;
            break;

        default:
            return FALSE;
        }

    hCur=UICursorLoad(iCur);
    SetCursor(hCur);

    return TRUE;
    }

/*
 * CPage::TenantFromPoint
 * (Protected)
 *
 * Purpose:
 *  Finds the tenant under the given device coordinates on this
 *  page.
 *
 * Parmeters:
 *  x, y            UINT coordinates.
 *  ppTenant        PCTenant * in which to return the pointer.
 *
 * Return Value:
 *  UINT            Index of the matched tenant, NOVALUE if not
 *                  found.
 */

UINT CPage::TenantFromPoint(UINT x, UINT y, PCTenant *ppTenant)
    {
    PCTenant    pTenant;
    RECTL       rcl;
    UINT        i;
    int         x0, y0;

    x0=x+m_pPG->m_xPos;
    y0=y+m_pPG->m_yPos;

    for (i=0; i < m_cTenants; i++)
        {
        if (!TenantGet(i, &pTenant, FALSE))
            continue;

        pTenant->RectGet(&rcl, TRUE);

        //Essentially Perform PointInRECTL
        if (x0 >= rcl.left && x0 <= rcl.right)
            {
            if (y0 <=rcl.bottom && y0 >=rcl.top)
                {
                *ppTenant=pTenant;
                return i;
                }
            }
        }

    *ppTenant=NULL;
    return NOVALUE;
    }

/*
 * CPage::DragDrop
 *
 * Purpose:
 *  Performs drag-drop operations from the page window
 *
 * Parmeters:
 *  uKeys           UINT state of the keyboard
 *  x, y            UINT mouse coordinates of the starting click.
 *
 * Return Value:
 *  BOOL            TRUE if we modified the page, FALSE otherwise.
 */

BOOL CPage::DragDrop(UINT uKeys, UINT x, UINT y)
    {
    LPDROPSOURCE    pIDropSource;
    LPDATAOBJECT    pIDataObject;
    HRESULT         hr;
    DWORD           dwEffect;
    POINTL          ptl;
    SIZEL           szl;
    RECTL           rcl;
    RECT            rc, rcT;

    pIDropSource=new CDropSource();

    if (NULL==pIDropSource)
        return FALSE;

    pIDropSource->AddRef();
    m_pPG->m_fDragSource=TRUE;

    /*
     * Store a pick point with the data indicating the offset from
     * the upper left of the rectangle where we grabbed it.  This is
     * so the UI feedback in IDropTarget lines up with this tenant.
     */

    m_pTenantCur->RectGet(&rcl, TRUE);
    ptl.x=x+m_pPG->m_xPos-rcl.left;
    ptl.y=y+m_pPG->m_yPos-rcl.top;
    pIDataObject=TransferObjectCreate(&ptl);

    if (NULL==pIDataObject)
        {
        pIDropSource->Release();
        return FALSE;
        }

    m_pPG->m_fMoveInPage=FALSE;

    dwEffect=DROPEFFECT_COPY | DROPEFFECT_MOVE;
    hr=DoDragDrop(pIDataObject, pIDropSource
        , DROPEFFECT_COPY | DROPEFFECT_MOVE, &dwEffect);

    pIDataObject->Release();
    pIDropSource->Release();

    m_pPG->m_fDragSource=FALSE;

    //No drop-no action.
    if (DRAGDROP_S_DROP!=GetScode(hr) || DROPEFFECT_NONE==dwEffect)
        return FALSE;

    /*
     * If m_pPG->m_fMoveInPage is set, then we just change the
     * coordinates on m_pTenantCur and we're done.
     */
    if (m_pPG->m_fMoveInPage)
        {
        m_pTenantCur->Invalidate();

        /*
         * Clip to page boundaries.  We know that ptDrop has to be
         * in the page somewhere or we would not have dropped
         * (effect was NONE).  So first make sure that ptDrop is
         * within 3*CXYHANDLE of the right or bottom, and if so,
         * pull it out to 3*CXYHANDLE.  Then we can just clip the
         * size to the page rectangle and we'll always be sure to
         * have at least a sizeable object.
         */
        m_pTenantCur->SizeGet(&szl, TRUE);
        SetRect(&rc, (int)m_pPG->m_ptDrop.x, (int)m_pPG->m_ptDrop.y
            , 0, 0);
        RectConvertMappings(&rc, NULL, TRUE);

        m_pPG->CalcBoundingRect(&rcT, FALSE);
        OffsetRect(&rcT, (int)m_pPG->m_xPos, (int)m_pPG->m_yPos);

        if (rc.left >= rcT.right-3*CXYHANDLE)
            rc.left=rcT.right-3*CXYHANDLE;

        if (rc.top >= rcT.bottom-3*CXYHANDLE)
            rc.top=rcT.bottom-3*CXYHANDLE;

        rc.right=rc.left+(int)szl.cx;
        rc.bottom=rc.top+(int)szl.cy;
        IntersectRect(&rc, &rc, &rcT);

        RECTLFROMRECT(rcl, rc);

        m_pTenantCur->RectSet(&rcl, TRUE, FALSE);
        m_pTenantCur->Repaint();
        return TRUE;
        }

    /*
     * Otherwise we may have to delete the old tenant if the effect
     * was move.  This will not happen in the move in page case.
     */

    if (DROPEFFECT_MOVE==dwEffect)
        {
        TenantDestroy();
        return TRUE;
        }

    //Copy is a clean operation
    return FALSE;
    }


PAGES.CPP

/*
 * PAGES.CPP
 * Patron Chapter 21
 *
 * Implementation of the CPages class.  See PAGEWIN.CPP and PRINT.CPP
 * for additional member functions.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPages:CPages
 * CPages::~CPages
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application we're in.
 *  cf              UINT application clipboard format.
 */

CPages::CPages(HINSTANCE hInst, UINT cf)
    : CWindow(hInst)
    {
    m_pPageCur=NULL;
    m_iPageCur=NOVALUE;  //Pages are 0 indexed, this is one before
    m_cPages=0;
    m_hWndPageList=NULL;
    m_hFont=NULL;
    m_fSystemFont=FALSE;

    //Initialize to 8.5*11 inch with .25 inch margins as a default.
    m_cx=(LOMETRIC_PER_INCH*17)/2;
    m_cy=LOMETRIC_PER_INCH*11;

    m_xMarginLeft=LOMETRIC_PER_INCH/4;
    m_xMarginRight=LOMETRIC_PER_INCH/4;
    m_yMarginTop=LOMETRIC_PER_INCH/4;
    m_yMarginBottom=LOMETRIC_PER_INCH/4;

    m_xPos=0L;
    m_yPos=0L;

    m_dwIDNext=0;
    m_pIStorage=NULL;

    m_fDirty=FALSE;
    m_cf=cf;

    m_fDragSource=FALSE;
    m_fMoveInPage=FALSE;
    m_fLinkAllowed=FALSE;

    m_fDragRectShown=FALSE;

    m_uScrollInset=GetProfileInt(TEXT("windows")
        , TEXT("DragScrollInset"), DD_DEFSCROLLINSET);

    m_uScrollDelay=GetProfileInt(TEXT("windows")
        , TEXT("DragScrollDelay"), DD_DEFSCROLLDELAY);

    m_uHScrollCode=0;
    m_uVScrollCode=0;
    m_fShowTypes=FALSE;

    //CHAPTER21MOD
    m_pmkFile=NULL;
    //End CHAPTER21MOD
    return;
    }

CPages::~CPages(void)
    {
    //Ensure memory cleaned up in list; do final IStorage::Release
    StorageSet(NULL, FALSE, FALSE);

    //CHAPTER21MOD
    ReleaseInterface(m_pmkFile);
    //End CHAPTER21MOD

    if (NULL!=m_hFont && !m_fSystemFont)
        DeleteObject(m_hFont);

    if (NULL!=m_hWndPageList)
        DestroyWindow(m_hWndPageList);

    //m_hWnd destroyed with the document.
    return;
    }

/*
 * CPages::FIsDirty
 *
 * Purpose:
 *  Tells the caller (document) if anything's happened to dirty us.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPages::FIsDirty(void)
    {
    return m_fDirty;
    }

/*
 * CPages::Init
 *
 * Purpose:
 *  Instantiates a pages window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags.
 *                  Should contain WS_CHILD | WS_VISIBLE in
 *                  typical circumstances.
 *  uID             UINT ID to associate with this window
 *  pv              LPVOID unused for now.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPages::Init(HWND hWndParent, LPRECT pRect, DWORD dwStyle
    , UINT uID, LPVOID pv)
    {
    int     cy;

    m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY, SZCLASSPAGES
        , SZCLASSPAGES, dwStyle, pRect->left, pRect->top
        , pRect->right-pRect->left, pRect->bottom-pRect->top
        , hWndParent, (HMENU)uID, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    /*
     * Create the hidden listbox we'll use to track pages.  We give
     * it the owner-draw style so we can just store pointers in it.
     */
    m_hWndPageList=CreateWindow(TEXT("listbox"), TEXT("Page List")
        , WS_POPUP | LBS_OWNERDRAWFIXED, 0, 0, 100, 100
        , HWND_DESKTOP, NULL, m_hInst, NULL);

    if (NULL==m_hWndPageList)
        return FALSE;

    //Create a 14 point Arial font, or use the system variable font.
    cy=MulDiv(-14, LOMETRIC_PER_INCH, 72);
    m_hFont=CreateFont(cy, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE
        , ANSI_CHARSET, OUT_TT_PRECIS, CLIP_TT_ALWAYS, PROOF_QUALITY
        , VARIABLE_PITCH | FF_SWISS, TEXT("Arial"));

    if (NULL==m_hFont)
        {
        m_hFont=(HFONT)GetStockObject(ANSI_VAR_FONT);
        m_fSystemFont=TRUE;
        }

    return TRUE;
    }

/*
 * CPages::StorageSet
 *
 * Purpose:
 *  Provides the document's IStorage to the pages for its own use.
 *  If this is a new storage, we initalize it with streams that we
 *  want to always exists.  If this is an open, then we create
 *  our page list from the PageList string we wrote before.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE to the new or opened storage.  If
 *                  NULL, we just clean up and exit.
 *  fChange         BOOL indicating is this was a Save As operation,
 *                  meaning that we have the structure already and
 *                  just need to change the value of m_pIStorage.
 *  fInitNew        BOOL indicating if this is a new storage or one
 *                  opened from a previous save.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::StorageSet(LPSTORAGE pIStorage, BOOL fChange
    , BOOL fInitNew)
    {
    DWORD           dwMode=STGM_DIRECT | STGM_READWRITE
                        | STGM_SHARE_EXCLUSIVE;
    HRESULT         hr;
    PCPage          pPage;
    BOOL            fRet=FALSE;
    ULONG           cbRead;
    PAGELIST        pgList;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    LPDWORD         pdwID;
    UINT            i;

    //If we're changing saved roots, simply open current page again
    if (fChange)
        {
        if (NULL==pIStorage)
            return FALSE;

        m_pIStorage->Release();
        m_pIStorage=pIStorage;
        m_pIStorage->AddRef();

        PageGet(m_iPageCur, &m_pPageCur, TRUE);
        m_fDirty=FALSE;
        return TRUE;
        }

    if (NULL!=m_hWndPageList)
        {
        //On new or open, clean out whatever it is we have.
        for (i=0; i < m_cPages; i++)
            {
            //CHAPTER21MOD
            if (PageGet(i, &pPage, FALSE))
                pPage->Release();
            //End CHAPTER21MOD
            }

        SendMessage(m_hWndPageList, LB_RESETCONTENT, 0, 0L);
        }

    if (NULL!=m_pIStorage)
        m_pIStorage->Release();

    m_pIStorage=NULL;

    //If we're just cleaning up, then we're done.
    if (NULL==pIStorage)
        return TRUE;

    m_pIStorage=pIStorage;
    m_pIStorage->AddRef();

    //If this is a new storage, create the streams we require
    if (fInitNew)
        {
        //Page list header.
        hr=m_pIStorage->CreateStream(SZSTREAMPAGELIST, dwMode
            | STGM_CREATE, 0, 0, &pIStream);

        if (FAILED(hr))
            return FALSE;

        pIStream->Release();

        //Device Configuration
        hr=m_pIStorage->CreateStream(SZSTREAMDEVICECONFIG, dwMode
            | STGM_CREATE, 0, 0, &pIStream);

        if (FAILED(hr))
            return FALSE;

        pIStream->Release();
        return TRUE;
        }

    /*
     * We're opening an existing file:
     *  1)  Configure for the device we're on
     *  2)  Read the Page List and create page entries for each.
     */

    ConfigureForDevice();

    //Read the page list.
    hr=m_pIStorage->OpenStream(SZSTREAMPAGELIST, NULL, dwMode, 0
        , &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pIStream->Read(&pgList, sizeof(PAGELIST), &cbRead);
        m_cPages  =(UINT)pgList.cPages;
        m_iPageCur=(UINT)pgList.iPageCur;
        m_dwIDNext=pgList.dwIDNext;

        fRet=TRUE;
        cbRead=pgList.cPages*sizeof(DWORD);

        if (0!=cbRead)
            {
            pdwID=(LPDWORD)pIMalloc->Alloc(cbRead);

            if (NULL!=pdwID)
                {
                pIStream->Read(pdwID, cbRead, &cbRead);

                for (i=0; i < m_cPages; i++)
                    fRet &=PageAdd(NOVALUE, *(pdwID+i), FALSE);

                pIMalloc->Free(pdwID);
                }
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    if (!fRet)
        return FALSE;

    PageGet(m_iPageCur, &m_pPageCur, TRUE);
    m_fDirty=FALSE;

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);

    return TRUE;
    }

/*
 * CPages::StorageUpdate
 *
 * Purpose:
 *  Insures that all pages are committed before a root save.
 *
 * Parameters:
 *  fCloseAll       BOOL directing us to close all open storages
 *                  and streams.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::StorageUpdate(BOOL fCloseAll)
    {
    PCPage          pPage;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    LPDWORD         pdwID;
    ULONG           cb;
    HRESULT         hr;
    PAGELIST        pgList;
    BOOL            fRet=FALSE;
    UINT            i;

    //We only need to close the current page--nothing else is open.
    if (NULL!=m_pPageCur)
        {
        m_pPageCur->Update();

        if (fCloseAll)
            m_pPageCur->Close(FALSE);
        }

    //We don't hold anything else open, so write the page list.
    hr=m_pIStorage->OpenStream(SZSTREAMPAGELIST, NULL, STGM_DIRECT
        | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pgList.cPages=m_cPages;
        pgList.iPageCur=m_iPageCur;
        pgList.dwIDNext=m_dwIDNext;

        pIStream->Write(&pgList, sizeof(PAGELIST), &cb);

        cb=m_cPages*sizeof(DWORD);
        pdwID=(LPDWORD)pIMalloc->Alloc(cb);

        if (NULL!=pdwID)
            {
            for (i=0; i < m_cPages; i++)
                {
                PageGet(i, &pPage, FALSE);
                *(pdwID+i)=pPage->GetID();
                }

            pIStream->Write(pdwID, cb, &cb);
            pIMalloc->Free(pdwID);
            fRet=TRUE;
            }
        pIMalloc->Release();
        }

    pIStream->Release();

    //Clean up the dirty flag when we do an update.
    m_fDirty=!fRet;
    return fRet;
    }

/*
 * CPages::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Pages window in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  None
 */

void CPages::RectGet(LPRECT pRect)
    {
    RECT        rc;
    POINT       pt;

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_hWnd, &rc);
    SETPOINT(pt, rc.left, rc.top);
    ScreenToClient(GetParent(m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return;
    }

/*
 * CPages::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Pages window which sizes to fit.
 *  Coordinates are given in parent terms.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPages::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    if (NULL==pRect)
        return;

    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, pRect->left, pRect->top
        , (UINT)cx, (UINT)cy, SWP_NOZORDER);

    UpdateScrollRanges();
    return;
    }

/*
 * CPages::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the pages window in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  None
 */

void CPages::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return;
    }

/*
 * CPages::SizeSet
 *
 * Purpose:
 *  Sets a new size in parent coordinates for the Pages window.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPages::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    if (NULL==pRect)
        return;

    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
        , SWP_NOMOVE | SWP_NOZORDER);

    UpdateScrollRanges();
    return;
    }

/*
 * CPages::ActivePage
 *
 * Purpose:
 *  Returns a CPage pointer to the current page.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCPage          Pointer to the current page.
 */

PCPage CPages::ActivePage(void)
    {
    PCPage      pPage;
    BOOL        fRet;

    fRet=PageGet(m_iPageCur, &pPage, FALSE);
    return fRet ? pPage : NULL;
    }

/*
 * CPages::PageInsert
 *
 * Purpose:
 *  Creates a new page immediately after the current page.  If
 *  there are no pages then this creates page 1.
 *
 * Parameters:
 *  uReserved       UINT unused
 *
 * Return Value:
 *  UINT            Index of the new page, 0 on failure.
 */

UINT CPages::PageInsert(UINT uReserved)
    {
    if (0!=m_cPages && NULL!=m_pPageCur)
        {
        //Close the current page, committing changes.
        m_pPageCur->Close(TRUE);
        }

    //Create and open the new page.
    if (!PageAdd(m_iPageCur, m_dwIDNext, TRUE))
        return 0;

    m_dwIDNext++;
    m_iPageCur++;
    m_cPages++;

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);

    PageGet(m_iPageCur, &m_pPageCur, FALSE);
    return m_iPageCur;
    }

/*
 * CPages::PageDelete
 *
 * Removes the current page from the page list.
 *
 * Parameters:
 *  uReserved       UINT unused
 *
 * Return Value:
 *  UINT            Index to the now current page from the page
 *                  list, NOVALUE on error.
 */

UINT CPages::PageDelete(UINT uReserved)
    {
    PCPage      pPage;

    if (!PageGet(m_iPageCur, &pPage, FALSE))
        return NOVALUE;

    //Delete the page in both the storage and in memory.
    SendMessage(m_hWndPageList, LB_DELETESTRING, m_iPageCur, 0L);

    m_pPageCur->Destroy(m_pIStorage);

    //CHAPTER21MOD
    m_pPageCur->Release();   //Does final pPage->Close
    //End CHAPTER21MOD
    m_pPageCur=NULL;

    /*
     * If this is the last page then the current is one less.  If
     * it's the only page the current is zero.  Otherwise the
     * current is the next page.
     */

    if (m_iPageCur==m_cPages-1)   //Covers last or only page.
        m_iPageCur--;

    m_cPages--;

    //Insure the new visible page is open.
    if (0!=m_cPages)
        {
        PageGet(m_iPageCur, &m_pPageCur, TRUE);
        InvalidateRect(m_hWnd, NULL, FALSE);
        }
    else
        InvalidateRect(m_hWnd, NULL, TRUE);

    UpdateWindow(m_hWnd);
    return m_iPageCur;
    }

/*
 * CPages::CurPageGet
 *
 * Purpose:
 *  Retrieves the index of the current page we're viewing.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the current page.
 */

UINT CPages::CurPageGet(void)
    {
    return m_iPageCur;
    }

/*
 * CPages::CurPageSet
 *
 * Purpose:
 *  Sets the index of the current page to view.
 *
 * Parameters:
 *  iPage           UINT index of the page to view. 0 means first
 *                  page, NOVALUE means last page.
 *
 * Return Value:
 *  UINT            Index of the previous current page, NOVALUE on
 *                  error.
 */

UINT CPages::CurPageSet(UINT iPage)
    {
    UINT    iPageNew;
    UINT    iPagePrev=m_iPageCur;
    PCPage  pPage;

    switch (iPage)
        {
        case 0:
            iPageNew=0;
            break;

        case NOVALUE:
            iPageNew=m_cPages-1;
            break;

        default:
            if (iPage >= m_cPages)
                iPage=0;

            iPageNew=iPage;
            break;
        }

    //No reason to switch to the same page.
    if (iPagePrev==iPageNew)
        return iPage;

    //Close the old page committing changes.
    if (!PageGet(iPagePrev, &pPage, FALSE))
        return NOVALUE;

    pPage->Close(TRUE);

    m_iPageCur=iPageNew;

    //Open the new page.
    PageGet(m_iPageCur, &m_pPageCur, TRUE);

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);
    return iPagePrev;
    }

/*
 * CPages::NumPagesGet
 *
 * Purpose:
 *  Returns the number of pages this object current contains.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of pages we contain.
 */

UINT CPages::NumPagesGet(void)
    {
    return m_cPages;
    }

/*
 * CPages::TenantCreate
 * CPages::TenantDestroy
 *
 * Purpose:
 *  Pass-throughs for CPage members on the current page.
 */

BOOL CPages::TenantCreate(TENANTTYPE tType, LPVOID pv
    , LPFORMATETC pFE, PPATRONOBJECT ppo, DWORD dwData)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantCreate(tType, pv, pFE, ppo, dwData);
    m_fDirty |= fRet;
    return fRet;
    }

BOOL CPages::TenantDestroy(void)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantDestroy();
    m_fDirty |= fRet;
    return fRet;
    }

/*
 * CPages::TenantClip
 *
 * Purpose:
 *  Copies or cuts the currently selected tenant to the clipoard.
 *
 * Parameters:
 *  fCut            BOOL TRUE to cut the object, FALSE to copy.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::TenantClip(BOOL fCut)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantClip(fCut);
    m_fDirty |= (fRet && fCut);
    return fRet;
    }

/*
 * CPages::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected on this
 *  page for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU on which object related items live.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPages::FQueryObjectSelected(HMENU hMenu)
    {
    if (NULL==m_pPageCur)
        return FALSE;

    return m_pPageCur->FQueryObjectSelected(hMenu);
    }

/*
 * CPages::ActivateObject
 *
 * Purpose:
 *  Executes a verb on the currently selected object.
 *
 * Parameters:
 *  iVerb           LONG of the selected verb.
 *
 * Return Value:
 *  None
 */

void CPages::ActivateObject(LONG iVerb)
    {
    if (NULL==m_pPageCur)
        return;

    m_pPageCur->ActivateObject(iVerb);
    return;
    }

/*
 * CPages::ShowObjectTypes
 *
 * Purpose:
 *  Pass-through to CPage::ShowObjectTypes
 *
 * Parameters:
 *  fShow           BOOL indicating to show the type or not.
 *
 * Return Value:
 *  None
 */

void CPages::ShowObjectTypes(BOOL fShow)
    {
    if (NULL==m_pPageCur)
        return;

    m_fShowTypes=fShow;
    m_pPageCur->ShowObjectTypes(fShow);
    return;
    }

/*
 * CPages::NotifyTenantsOfRename
 *
 * Purpose:
 *  Pass-through to CPage to tell tenants that the document
 *  changes names.
 *
 * Parameters:
 *  pszFile         LPTSTR of the new filename.
 *  pmk             LPMONKIER to the new file moniker.
 *
 * Return Value:
 *  None
 */

void CPages::NotifyTenantsOfRename(LPTSTR pszFile, LPMONIKER pmk)
    {
    if (NULL==m_pPageCur)
        return;

    //CHAPTER21MOD
    ReleaseInterface(m_pmkFile);
    m_pmkFile=pmk;
    m_pmkFile->AddRef();
    //End CHAPTER21MOD
    m_pPageCur->NotifyTenantsOfRename(pszFile, pmk);
    return;
    }

/*
 * CPages::FQueryLinksInPage
 *
 * Purpose:
 *  Pass through to current page to see if there are any
 *  linked objects
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPages::FQueryLinksInPage()
    {
    if (NULL==m_pPageCur)
        return FALSE;

    return m_pPageCur->FQueryLinksInPage();
    }

/*
 * CPages::GetUILinkContainer
 *
 * Purpose:
 *  Creates an object with the IOleUILinkContainer interface for
 *  the links dialog.  We know the current page which is what
 *  we need to create this thing, so we can create it here instead
 *  of bugging the page for it.
 *
 * Parameters:
 *  ppObj           PCIOleUILinkContainer * in which we return
 *                  the pointer.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::GetUILinkContainer(PCIOleUILinkContainer *ppObj)
    {
    PCIOleUILinkContainer   pObj;

    *ppObj=NULL;

    if (NULL==m_pPageCur)
        return FALSE;

    pObj=new CIOleUILinkContainer(m_pPageCur);

    if (NULL==pObj)
        return FALSE;

    if (!pObj->Init())
        {
        delete pObj;
        return FALSE;
        }

    pObj->AddRef();
    *ppObj=pObj;
    return TRUE;
    }

/*
 * CPages::ConvertObject
 *
 * Purpose:
 *  Pass-through to the current page.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame window.
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful, FALSE
 *                  otherwise.
 */

BOOL CPages::ConvertObject(HWND hWndFrame)
    {
    if (NULL==m_pPageCur)
        return FALSE;

    return m_pPageCur->ConvertObject(hWndFrame, FALSE);
    }

/*
 * CPages::CalcBoundingRect
 * (Protected)
 *
 * Purpose:
 *  Calculates a rectangle that bounds the printed page and the
 *  current scroll state of the window.
 *
 * Parameters:
 *  prc             LPRECT to fill with window (device) coordinates.
 *  fWindow         BOOL indicating to include the window in this
 *                  calculation or return only the printed page
 *                  coordinates.
 *
 * Return Value:
 *  None
 */

void CPages::CalcBoundingRect(LPRECT prc, BOOL fWindow)
    {
    RECT        rc, rcT;

    if (NULL==prc)
        return;

    //Calculate the boundaries for sizing: intersect page & screen
    rc.left=LOMETRIC_BORDER+m_xMarginLeft;
    rc.top =-LOMETRIC_BORDER-m_yMarginTop;
    rc.right =rc.left+(UINT)m_cx;
    rc.bottom=rc.top -(UINT)m_cy;
    RectConvertMappings(&rc, NULL, TRUE);
    OffsetRect(&rc, -(int)m_xPos, -(int)m_yPos);

    if (!fWindow)
        {
        *prc=rc;
        return;
        }

    //Intersect with window to make the size bounds.
    GetClientRect(m_hWnd, &rcT);
    IntersectRect(prc, &rc, &rcT);
    return;
    }

/*
 * CPages::PageGet
 * (Protected)
 *
 * Purpose:
 *  Returns a page of a given index returning a BOOL so it's simple
 *  to use this function inside if statements.
 *
 * Parameters:
 *  iPage           UINT page to retrieve 0 based.
 *  ppPage          PCPage * in which to return the page pointer
 *  fOpen           BOOL indicating if we should open this page as
 *                  well.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::PageGet(UINT iPage, PCPage *ppPage, BOOL fOpen)
    {
    if (NULL==ppPage)
        return FALSE;

    if (LB_ERR!=SendMessage(m_hWndPageList, LB_GETTEXT
        , iPage, (LONG)ppPage))
        {
        if (fOpen)
            (*ppPage)->Open(m_pIStorage);

        return TRUE;
        }

    return FALSE;
    }

/*
 * CPages::PageAdd
 * (Protected)
 *
 * Purpose:
 *  Creates a new page initialized to the given values.  The new
 *  page's storage is created if it does not already exist.  If
 *  fOpenStorage is set the page's storage is opened and left
 *  opened.
 *
 * Parameters:
 *  iPage           UINT Location at which to insert page; new page
 *                  is inserted after this position.  NOVALUE for
 *                  the end.
 *  dwID            DWORD ID for this page.
 *  fOpenStorage    BOOL indicating if we're to leave the storage
 *                  open.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPages::PageAdd(UINT iPage, DWORD dwID, BOOL fOpenStorage)
    {
    PCPage      pPage;
    LRESULT     lr;

    pPage=new CPage(dwID, m_hWnd, this);

    if (NULL==pPage)
        return FALSE;

    //CHAPTER21MOD
    pPage->AddRef();
    //End CHAPTER21MOD

    if (fOpenStorage)
        pPage->Open(m_pIStorage);

    if (NOVALUE==iPage)
        iPage--;

    //Now try to add to the listbox.
    lr=SendMessage(m_hWndPageList, LB_INSERTSTRING, iPage+1
        , (LONG)pPage);

    if (LB_ERRSPACE==lr)
        {
        if (fOpenStorage)
            pPage->Close(FALSE);

        //CHAPTER21MOD
        pPage->Release();
        //End CHAPTER21MOD
        return FALSE;
        }

    return TRUE;
    }

//CHAPTER21MOD
/*
 * CPages::IPageGetFromID
 * (Protected)
 *
 * Purpose:
 *  Returns a page of a given identifier
 *
 * Parameters:
 *  dwID            DWORD identifier of the page to retrieve.  If
 *                  NOVALUE, then we return the current page.
 *  ppPage          PCPage * in which to return the page
 *                  pointer
 *  fOpen           BOOL indicating if we should open this page as
 *                  well.
 *
 * Return Value:
 *  UINT            Index of the page if successful, NOVALUE
 *                  otherwise.
 */

UINT CPages::IPageGetFromID(DWORD dwID, PCPage *ppPage
    , BOOL fOpen)
    {
    UINT        i;
    PCPage      pPage;

    if (NULL==ppPage)
        return FALSE;

    if ((LONG)-1==(LONG)dwID)
        {
        *ppPage=m_pPageCur;
        return m_iPageCur;
        }

    *ppPage=NULL;

    for (i=0; i < m_cPages; i++)
        {
        if (!PageGet(i, &pPage, FALSE))
            continue;

        if (pPage->GetID()==dwID)
            {
            if (fOpen)
                pPage->Open(m_pIStorage);

            *ppPage=pPage;
            return i;
            }
        }

    return NOVALUE;
    }

//End CHAPTER21MOD


PAGES.H

/*
 * PAGES.H
 * Patron Chapter 21
 *
 * Definitions and function prototypes for the Pages window control
 * as well as the CPage class.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _PAGES_H_
#define _PAGES_H_

//CHAPTER21MOD
#include <stdlib.h>     //For atol
//End CHAPTER21MOD

//Versioning.
#define VERSIONMAJOR                2
#define VERSIONMINOR                0
#define VERSIONCURRENT              0x00020000

//Classname
#define SZCLASSPAGES                TEXT("pages")

#define HIMETRIC_PER_INCH           2540
#define LOMETRIC_PER_INCH           254
#define LOMETRIC_BORDER             60          //Border around page

//Window extra bytes and offsets
#define CBPAGESWNDEXTRA             (sizeof(LONG))
#define PAGEWL_STRUCTURE            0

#include "tenant.h"

typedef struct tagTENANTLIST
    {
    DWORD       cTenants;
    DWORD       dwIDNext;
    } TENANTLIST, *PTENANTLIST;

#define SZSTREAMTENANTLIST        OLETEXT("Tenant List")

//Delay timer used in mouse debouncing
#define IDTIMER_DEBOUNCE          120

/*
 * Page class describing an individual page and what things it
 * contains, managing an IStorage for us.
 *
 * A DWORD is used to identify this page as the name of the storage
 * is the string form of this ID.  If we added a page every second,
 * it would take 136 years to overrun this counter, so we can
 * get away with saving it persistently.  I hope this software is
 * obsolete by then.
 */

//CHAPTER21MOD
class CPage : public IUnknown
//End CHAPTER21MOD
    {
    friend class CIOleUILinkContainer;
    //CHAPTER21MOD
    friend class CImpIOleItemContainer;
    //End CHAPTER21MOD

    private:
        DWORD       m_dwID;             //Persistent identifier
        LPSTORAGE   m_pIStorage;        //Substorage for this page
        HWND        m_hWnd;             //Pages window
        DWORD       m_cOpens;           //Calls to Open

        class CPages *m_pPG;            //Pages window

        DWORD       m_dwIDNext;
        DWORD       m_cTenants;
        HWND        m_hWndTenantList;   //Listbox; our tenant list

        UINT        m_iTenantCur;
        PCTenant    m_pTenantCur;

        UINT        m_uHTCode;          //Last hit-test/mouse move
        UINT        m_uSizingFlags;     //Restrictions on sizing
        BOOL        m_fTracking;        //Tracking resize?
        RECTL       m_rclOrg;           //Original before tracking
        RECTL       m_rcl;              //Tracking rectangle
        RECTL       m_rclBounds;        //Boundaries f/size tracking
        HDC         m_hDC;              //Tracking hDC

        BOOL        m_fDragPending;     //Waiting for drag?
        BOOL        m_fSizePending;     //Waiting for debounce?
        int         m_cxyDist;          //Debounce distance
        UINT        m_cDelay;           //Debounce delay
        POINTS      m_ptDown;           //Point of click to debounce
        UINT        m_uKeysDown;        //Keys when click happens
        DWORD       m_fTimer;           //Timer active?
        BOOL        m_fReopen;          //Did we just close?

        //CHAPTER21MOD
        LPMONIKER               m_pmkFile;  //Document name
        ULONG                   m_cRef;
        DWORD                   m_dwRegROTWild;

        class CImpIOleItemContainer *m_pImpIOleItemContainer;
        //End CHAPTER21MOD

    protected:
        BOOL         TenantGet(UINT, PCTenant *, BOOL);
        //CHAPTER21MOD
        BOOL         TenantGetFromID(DWORD, PCTenant *, BOOL);
        //End CHAPTER21MOD
        BOOL         TenantAdd(UINT, DWORD, PCTenant *);
        LPDATAOBJECT TransferObjectCreate(PPOINTL);

        //PAGEMOUS.CPP
        BOOL         SelectTenantAtPoint(UINT, UINT);
        UINT         TenantFromPoint(UINT, UINT, PCTenant *);
        BOOL         DragDrop(UINT, UINT, UINT);

    public:
        CPage(DWORD, HWND, class CPages *);
        ~CPage(void);

        //CHAPTER21MOD
        //IUnknown for delegation
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
        //End CHAPTER21MOD

        DWORD       GetID(void);
        BOOL        Open(LPSTORAGE);
        void        Close(BOOL);
        BOOL        Update(void);
        void        Destroy(LPSTORAGE);
        UINT        GetStorageName(LPOLESTR);

        void        Draw(HDC, int, int, BOOL, BOOL);

        BOOL        TenantCreate(TENANTTYPE, LPVOID, LPFORMATETC
                        , PPATRONOBJECT, DWORD);
        BOOL        TenantDestroy(void);
        BOOL        TenantClip(BOOL);
        BOOL        FQueryObjectSelected(HMENU);
        void        ActivateObject(LONG);
        void        ShowObjectTypes(BOOL);
        void        NotifyTenantsOfRename(LPTSTR, LPMONIKER);
        BOOL        FQueryLinksInPage(void);
        BOOL        ConvertObject(HWND, BOOL);

        //PAGEMOUSE.CPP
        BOOL        OnRightDown(UINT, UINT, UINT);
        BOOL        OnLeftDown(UINT, UINT, UINT);
        BOOL        OnLeftDoubleClick(UINT, UINT, UINT);
        BOOL        OnLeftUp(UINT, UINT, UINT);
        void        OnMouseMove(UINT, int, int);
        void        OnTimer(UINT);
        void        StartSizeTracking(void);
        void        OnNCHitTest(UINT, UINT);
        BOOL        OnSetCursor(UINT);
    };

typedef CPage *PCPage;

/*
 * Structures to save with the document describing the device
 * configuration and pages that we have.  This is followed by
 * a list of DWORD IDs for the individual pages.
 */

typedef struct tagDEVICECONFIG
    {
    DWORD       cb;                         //Size of structure
    TCHAR       szDriver[CCHDEVICENAME];
    TCHAR       szDevice[CCHDEVICENAME];
    TCHAR       szPort[CCHDEVICENAME];
    DWORD       cbDevMode;                  //Size of actual DEVMODE
    DEVMODE     dm;                         //Variable
    } DEVICECONFIG, *PDEVICECONFIG;

//Offset to cbDevMode
#define CBSEEKOFFSETCBDEVMODE  (sizeof(DWORD)   \
                               +(3*CCHDEVICENAME*sizeof(TCHAR)))

//Combined OLE and Patron device structures.
typedef struct tagCOMBINEDEVICE
    {
    DVTARGETDEVICE  td;
    DEVICECONFIG    dc;
    } COMBINEBDEVICE, *PCOMBINEDEVICE;

typedef struct tagPAGELIST
    {
    DWORD       cPages;
    DWORD       iPageCur;
    DWORD       dwIDNext;
    } PAGELIST, *PPAGELIST;

//PRINT.CPP
BOOL    APIENTRY PrintDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL    APIENTRY AbortProc(HDC, int);

//PAGEWIN.CPP
LRESULT APIENTRY PagesWndProc(HWND, UINT, WPARAM, LPARAM);
void             RectConvertMappings(LPRECT, HDC, BOOL);

class CPages : public CWindow
    {
    friend LRESULT APIENTRY PagesWndProc(HWND, UINT, WPARAM, LPARAM);
    friend BOOL    APIENTRY PrintDlgProc(HWND, UINT, WPARAM, LPARAM);

    friend class CPage;
    friend class CTenant;
    friend class CDropTarget;
    friend class CImpIAdviseSink;

    //CHAPTER21MOD
    friend class CImpIOleItemContainer;
    //End CHAPTER21MOD

    protected:
        PCPage      m_pPageCur;             //Current page
        UINT        m_iPageCur;             //Current page
        UINT        m_cPages;               //Number of pages

        HWND        m_hWndPageList;         //Listbox with page list
        HFONT       m_hFont;                //Page font
        BOOL        m_fSystemFont;          //m_hFont system object?

        UINT        m_cx;                   //Page size in LOMETRIC
        UINT        m_cy;

        UINT        m_xMarginLeft;          //Unusable margins,
        UINT        m_xMarginRight;         //in LOMETRIC
        UINT        m_yMarginTop;
        UINT        m_yMarginBottom;

        UINT        m_xPos;                 //Viewport scroll pos,
        UINT        m_yPos;                 //both in *PIXELS*

        DWORD       m_dwIDNext;             //Next ID for a page.
        LPSTORAGE   m_pIStorage;            //Root storage

        UINT        m_cf;                   //Clipboard format
        BOOL        m_fDirty;

        BOOL        m_fDragSource;          //Source==target?
        BOOL        m_fMoveInPage;          //Moving in same page
        BOOL        m_fLinkAllowed;         //Linking in drag-drop?
        POINTL      m_ptDrop;               //Where to move object

        BOOL        m_fDragRectShown;       //Is rect on the screen?
        UINT        m_uScrollInset;         //Hit-test for drag-drop
        UINT        m_uScrollDelay;         //Delay before repeat
        DWORD       m_dwTimeLast;           //Ticks on last DragOver
        UINT        m_uHScrollCode;         //L/R on scroll repeat?
        UINT        m_uVScrollCode;         //U/D on scroll repeat?
        UINT        m_uLastTest;            //Last test result
        POINTL      m_ptlRect;              //Last feedback rectangle
        SIZEL       m_szlRect;

        BOOL        m_fShowTypes;           //Show Object active?

        //CHAPTER21MOD
        LPMONIKER   m_pmkFile;
        //End CHAPTER21MOD

    private:
        void        Draw(HDC, BOOL, BOOL);
        void        UpdateScrollRanges(void);
        BOOL        ConfigureForDevice(void);
        BOOL        PageGet(UINT, PCPage *, BOOL);
        BOOL        PageAdd(UINT, DWORD, BOOL);

        void        CalcBoundingRect(LPRECT, BOOL);

        //DRAGDROP.CPP
        UINT        UTestDroppablePoint(PPOINTL);
        void        DrawDropTargetRect(PPOINTL, LPSIZEL);
        void        AdjustPosition(PPOINTL, LPSIZEL);

    public:
        CPages(HINSTANCE, UINT);
        ~CPages(void);

        BOOL        Init(HWND, LPRECT, DWORD, UINT, LPVOID);

        BOOL        StorageSet(LPSTORAGE, BOOL, BOOL);
        BOOL        StorageUpdate(BOOL);

        BOOL        Print(HDC, LPTSTR, DWORD, UINT, UINT, UINT);

        void        RectGet(LPRECT);
        void        RectSet(LPRECT, BOOL);
        void        SizeGet(LPRECT);
        void        SizeSet(LPRECT, BOOL);

        PCPage      ActivePage(void);
        UINT        PageInsert(UINT);
        UINT        PageDelete(UINT);
        UINT        CurPageGet(void);
        UINT        CurPageSet(UINT);
        UINT        NumPagesGet(void);

        BOOL        DevModeSet(HGLOBAL, HGLOBAL);
        HGLOBAL     DevModeGet(void);

        BOOL        FIsDirty(void);
        BOOL        DevReadConfig(PCOMBINEDEVICE *, HDC *);
        BOOL        TenantCreate(TENANTTYPE, LPVOID, LPFORMATETC
                        , PPATRONOBJECT, DWORD);
        BOOL        TenantDestroy(void);
        BOOL        TenantClip(BOOL);
        BOOL        FQueryObjectSelected(HMENU);
        void        ActivateObject(LONG);

        void        ShowObjectTypes(BOOL);
        void        NotifyTenantsOfRename(LPTSTR, LPMONIKER);
        BOOL        FQueryLinksInPage(void);
        BOOL        GetUILinkContainer(class CIOleUILinkContainer **);
        BOOL        ConvertObject(HWND);
        //CHAPTER21MOD
        UINT        IPageGetFromID(DWORD, PCPage *, BOOL);
        //End CHAPTER21MOD
    };

typedef CPages *PCPages;

//Fixed names of streams in the Pages IStorage
#define SZSTREAMPAGELIST        OLETEXT("Page List")
#define SZSTREAMDEVICECONFIG    OLETEXT("Device Configuration")

//Return values for UTestDroppablePoint
#define UDROP_NONE              0x0000      //Exclusive
#define UDROP_CLIENT            0x0001      //Inclusive
#define UDROP_INSETLEFT         0x0002      //L/R are exclusive
#define UDROP_INSETRIGHT        0x0004
#define UDROP_INSETHORZ         (UDROP_INSETLEFT | UDROP_INSETRIGHT)

#define UDROP_INSETTOP          0x0008      //T/B are exclusive
#define UDROP_INSETBOTTOM       0x0010
#define UDROP_INSETVERT         (UDROP_INSETTOP | UDROP_INSETBOTTOM)

//Object used for the Links dialog

class CIOleUILinkContainer : public IOleUILinkContainer
    {
    private:
        ULONG                   m_cRef;
        PCPage                  m_pPage;
        UINT                    m_iTenant;
        LPOLEUILINKCONTAINER    m_pDelIUILinks;

    public:
        BOOL                    m_fDirty;   //No reason to hide it

    protected:
        STDMETHODIMP GetObjectInterface(DWORD, REFIID, PPVOID);

    public:
        CIOleUILinkContainer(PCPage);
        ~CIOleUILinkContainer(void);

        BOOL Init(void);
        BOOL IsDirty(void);

        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(DWORD) GetNextLink(DWORD);
        STDMETHODIMP         SetLinkUpdateOptions(DWORD, DWORD);
        STDMETHODIMP         GetLinkUpdateOptions(DWORD, LPDWORD);
        STDMETHODIMP         SetLinkSource(DWORD, LPTSTR, ULONG
                                 , ULONG *, BOOL);
        STDMETHODIMP         GetLinkSource(DWORD, LPTSTR *, ULONG *
                                 , LPTSTR *, LPTSTR *, BOOL *
                                 , BOOL *);
        STDMETHODIMP         OpenLinkSource(DWORD);
        STDMETHODIMP         UpdateLink(DWORD, BOOL, BOOL);
        STDMETHODIMP         CancelLink(DWORD);
    };

typedef CIOleUILinkContainer *PCIOleUILinkContainer;

#endif  //_PAGES_H_


PAGEWIN.CPP

/*
 * PAGEWIN.CPP
 * Patron Chapter 21
 *
 * Window procedure for the Pages window and support functions.
 * This window manages its own scrollbars and viewport and provides
 * printing capabilities as well.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * PagesWndProc
 *
 * Purpose:
 *  Window procedure for the Pages window.
 */

LRESULT APIENTRY PagesWndProc(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    PCPages         ppg;
    PAINTSTRUCT     ps;
    HDC             hDC;
    int             iPos, iTmp;
    int             iMin, iMax;
    UINT            idScroll;
    BOOL            fDirty=FALSE;

    ppg=(PCPages)GetWindowLong(hWnd, PAGEWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppg=(PCPages)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PAGEWL_STRUCTURE, (LONG)ppg);

            ppg->m_hWnd=hWnd;
            break;

        case WM_PAINT:
            /*
             * If there is currently a drag-rectangle showing, then
             * remove it before painting.  This insures that
             * painting doesn't blast part of that rectangle away so
             * that when we draw it next, garbage is left around.
             */
            if (ppg->m_fDragRectShown)
                ppg->DrawDropTargetRect(NULL, NULL);

            hDC=BeginPaint(hWnd, &ps);

            //Draw only if we have a page to show.
            if (0!=ppg->m_cPages)
                ppg->Draw(hDC, FALSE, FALSE);

            EndPaint(hWnd, &ps);

            //Turn the rectangle back on, if necessary.
            if (ppg->m_fDragRectShown)
                ppg->DrawDropTargetRect(NULL, NULL);
            break;

        case WM_HSCROLL:
        case WM_VSCROLL:
            idScroll=(WM_HSCROLL==iMsg) ? SB_HORZ : SB_VERT;

            iPos=GetScrollPos(hWnd, idScroll);
            iTmp=iPos;
            GetScrollRange(hWnd, idScroll, &iMin, &iMax);

            switch (LOWORD(wParam))
                {
                case SB_LINEUP:     iPos -= 20;  break;
                case SB_PAGEUP:     iPos -=100;  break;
                case SB_LINEDOWN:   iPos += 20;  break;
                case SB_PAGEDOWN:   iPos +=100;  break;

                case SB_THUMBPOSITION:
                    iPos=ScrollThumbPosition(wParam, lParam);
                    break;

                //We don't want scrolling on this message.
                case SB_THUMBTRACK:
                    return 0L;
                }

            iPos=max(iMin, min(iPos, iMax));

            if (iPos!=iTmp)
                {
                //Set the new position and scroll the window
                SetScrollPos(hWnd, idScroll, iPos, TRUE);

                if (SB_HORZ==idScroll)
                    {
                    ppg->m_xPos=iPos;
                    ScrollWindow(hWnd, iTmp-iPos, 0, NULL, NULL);
                    }
                else
                    {
                    ppg->m_yPos=iPos;
                    ScrollWindow(hWnd, 0, iTmp-iPos, NULL, NULL);
                    }
                }

            break;

        case WM_RBUTTONDOWN:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnRightDown(wParam
                , LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_LBUTTONDOWN:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftDown(wParam
                , LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_LBUTTONUP:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftUp(wParam
                , LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_LBUTTONDBLCLK:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftDoubleClick(wParam, LOWORD(lParam)
                , HIWORD(lParam));
            break;

        case WM_MOUSEMOVE:
            if (NULL==ppg->m_pPageCur)
                break;

            ppg->m_pPageCur->OnMouseMove(wParam, LOWORD(lParam)
                , HIWORD(lParam));
            break;

        case WM_TIMER:
            if (NULL==ppg->m_pPageCur)
                break;

            ppg->m_pPageCur->OnTimer(wParam);
            break;

        case WM_NCHITTEST:
            if (NULL!=ppg->m_pPageCur)
                {
                /*
                 * This just saves information in the page for
                 * OnSetCursor
                 */
                ppg->m_pPageCur->OnNCHitTest(LOWORD(lParam)
                    , HIWORD(lParam));
                }

            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        case WM_SETCURSOR:
            if (NULL!=ppg->m_pPageCur)
                {
                if (ppg->m_pPageCur->OnSetCursor(LOWORD(lParam)))
                    break;
                }

            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    ppg->m_fDirty |= fDirty;
    return 0L;
    }

/*
 * RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device to logical
 *  coordinates where the hDC defines the logical coordinates.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  hDC             HDC describing the logical coordinate system.
 *                  if NULL, uses a screen DC in MM_LOMETRIC.
 *  fToDevice       BOOL TRUE to convert from uConv to device,
 *                  FALSE to convert device to uConv.
 *
 * Return Value:
 *  None
 */

void RectConvertMappings(LPRECT pRect, HDC hDC, BOOL fToDevice)
    {
    POINT   rgpt[2];
    BOOL    fSysDC=FALSE;

    if (NULL==pRect)
        return;

    rgpt[0].x=pRect->left;
    rgpt[0].y=pRect->top;
    rgpt[1].x=pRect->right;
    rgpt[1].y=pRect->bottom;

    if (NULL==hDC)
        {
        hDC=GetDC(NULL);
        SetMapMode(hDC, MM_LOMETRIC);
        fSysDC=TRUE;
        }

    if (fToDevice)
        LPtoDP(hDC, rgpt, 2);
    else
        DPtoLP(hDC, rgpt, 2);

    if (fSysDC)
        ReleaseDC(NULL, hDC);

    pRect->left=rgpt[0].x;
    pRect->top=rgpt[0].y;
    pRect->right=rgpt[1].x;
    pRect->bottom=rgpt[1].y;

    return;
    }

/*
 * CPages::Draw
 *
 * Purpose:
 *  Paints the current page in the pages window.
 *
 * Parameters:
 *  hDC             HDC to draw on, could be a metafile or printer
 *                  DC or any other type of DC.
 *  fNoColor        BOOL indicating if we should use screen colors
 *                  or printer colors (B&W).  Objects are printed
 *                  as-is, however.  This is TRUE for printer DCs
 *                  or print preview.
 *  fPrinter        BOOL indicating if this is a printer DC in which
 *                  case we eliminate some of the fancy drawing,
 *                  like shadows on the page and so forth.
 *
 * Return Value:
 *  None
 */

void CPages::Draw(HDC hDC, BOOL fNoColor, BOOL fPrinter)
    {
    RECT            rc, rcT;
    UINT            uMM;
    HPEN            hPen;
    HBRUSH          hBrush;
    HGDIOBJ         hObj1, hObj2;
    COLORREF        cr;
    TCHAR           szTemp[20];
    UINT            cch;
    SIZE            sz;
    PCPage          pPage;
    RECT            rcPos;

    //Make sure the DC is in LOMETRIC
    uMM=SetMapMode(hDC, MM_LOMETRIC);

    if (!fPrinter)
        {
        /*
         * We maintain a 6mm border around the page on the screen
         * besides 12.7mm margins.  We also have to account for
         * the scroll position with m_*Pos which are in pixels so
         * we have to convert them.
         */

        SetRect(&rcPos, m_xPos, m_yPos, 0, 0);
        RectConvertMappings(&rcPos, hDC, FALSE);

        rc.left  = LOMETRIC_BORDER-rcPos.left;
        rc.top   =-LOMETRIC_BORDER-rcPos.top;
        }
    else
        {
        /*
         * We define the corner of the printed paper at a negative
         * offset so rc.right and rc.bottom come out right below.
         */
        SetRect(&rc, -(int)m_xMarginLeft, m_yMarginTop, 0, 0);
        }

    rc.right=rc.left+m_cx+(m_xMarginLeft+m_xMarginRight);
    rc.bottom=rc.top-m_cy-(m_yMarginTop+m_yMarginBottom);

    //Draw a rect filled with the window color to show the page.
    if (!fPrinter)
        {
        if (fNoColor)
            {
            //Black frame, white box for printed colors.
            hPen  =CreatePen(PS_SOLID, 0, RGB(0,0,0));
            hBrush=CreateSolidBrush(RGB(255, 255, 255));
            }
        else
            {
            //Normal colors on display
            hPen=CreatePen(PS_SOLID, 0
                , GetSysColor(COLOR_WINDOWFRAME));
            hBrush=CreateSolidBrush(GetSysColor(COLOR_WINDOW));
            }

        hObj1=SelectObject(hDC, hPen);
        hObj2=SelectObject(hDC, hBrush);

        //Paper boundary
        Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom+1);

        /*
         * Draw a shadow on the *visual* bottom and right edges
         * .5mm wide.  If the button shadow color and workspace
         * colors match, then use black.  We always use black
         * when printing as well.
         */
        if (fNoColor)
            cr=RGB(0,0,0);
        else
            {
            cr=GetSysColor(COLOR_BTNSHADOW);

            if (GetSysColor(COLOR_APPWORKSPACE)==cr)
                cr=RGB(0,0,0);
            }

        cr=SetBkColor(hDC, cr);
        SetRect(&rcT, rc.left+5, rc.bottom, rc.right+5,rc.bottom-5);
        ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rcT, NULL, 0, NULL);

        SetRect(&rcT, rc.right, rc.top-5, rc.right+5, rc.bottom-5);
        ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rcT, NULL, 0, NULL);
        SetBkColor(hDC, cr);

        SelectObject(hDC, hObj1);
        SelectObject(hDC, hObj2);
        DeleteObject(hBrush);
        DeleteObject(hPen);
        }

    //Write the page number in the lower left corner
    if (!fNoColor)
        {
        SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));
        SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
        }

    //Write the page number in our page font.
    cch=wsprintf(szTemp, TEXT("Page %d"), m_iPageCur+1);

    hObj1=SelectObject(hDC, m_hFont);
    GetTextExtentPoint(hDC, szTemp, cch, &sz);

    TextOut(hDC, rc.left+m_xMarginLeft
        , rc.bottom+m_yMarginBottom+sz.cy, szTemp, cch);

    SelectObject(hDC, hObj1);

    //Rectangle to show border.
    MoveToEx(hDC, rc.left+m_xMarginLeft, rc.top-m_yMarginTop, NULL);
    LineTo(hDC, rc.left+m_xMarginLeft,   rc.bottom+m_yMarginBottom);
    LineTo(hDC, rc.right-m_xMarginRight, rc.bottom+m_yMarginBottom);
    LineTo(hDC, rc.right-m_xMarginRight, rc.top-m_yMarginTop);
    LineTo(hDC, rc.left+m_xMarginLeft,   rc.top-m_yMarginTop);

    /*
     * Go draw the objects on this page.  If the page is not open,
     * we open it anyway.  If it is already open, then opening again
     * will bump it's reference count, so the Close in ineffectual.
     */
    if (PageGet(m_iPageCur, &pPage, TRUE))
        {
        if (!fPrinter)
            {
            pPage->Draw(hDC, rcPos.left, rcPos.top, fNoColor
                , fPrinter);
            }
        else
            pPage->Draw(hDC, 0, 0, fNoColor, fPrinter);

        pPage->Close(FALSE);
        }

    SetMapMode(hDC, uMM);
    return;
    }

/*
 * CPages::UpdateScrollRanges
 *
 * Purpose:
 *  Reset scrollbar ranges (horizontal and vertical) depending on
 *  the window size and the page size.  This function may remove
 *  the scrollbars altogether.
 *
 * Parameters:
 *  None, but set m_cx, m_cy and size m_hWnd before calling.
 *
 * Return Value:
 *  None
 */

void CPages::UpdateScrollRanges(void)
    {
    UINT        cxSB;   //Scrollbar width and height.
    UINT        cySB;
    UINT        cx, cy;
    UINT        dx, dy;
    UINT        u;
    int         iMin, iMax;
    RECT        rc;
    BOOL        fHScroll;
    BOOL        fVScroll;
    BOOL        fWasThere;

    GetClientRect(m_hWnd, &rc);

    cx=rc.right-rc.left;
    cy=rc.bottom-rc.top;

    //Convert dimensions of the image in LOMETRIC to pixels.
    SetRect(&rc, (m_cx+m_xMarginLeft+m_xMarginRight
        +LOMETRIC_BORDER*2), (m_cy+m_yMarginTop
        +m_yMarginBottom+LOMETRIC_BORDER*2), 0, 0);

    RectConvertMappings(&rc, NULL, TRUE);

    dx=rc.left;
    dy=-rc.top;

    //Assume that both scrollbars will be visible.
    fHScroll=TRUE;
    fVScroll=TRUE;

    /*
     * Determine:
     *  1)  Which scrollbars are needed.
     *  2)  How many divisions to give scrollbars so as to
     *      only scroll as little as necessary.
     */

    //Scrollbar dimensions in our units.
    cxSB=GetSystemMetrics(SM_CXVSCROLL);
    cySB=GetSystemMetrics(SM_CYHSCROLL);

    //Remove horizontal scroll if window >= cxPage+borders
    if (cx >= dx)
        fHScroll=FALSE;

    /*
     * If we still need a horizontal scroll, see if we need a
     * vertical taking the height of the horizontal scroll into
     * account.
     */

    u=fHScroll ? cySB : 0;

    if ((cy-u) >= dy)
        fVScroll=FALSE;

    //Check if adding vert scrollbar necessitates a horz now.
    u=fVScroll ? cxSB : 0;
    fHScroll=((cx-u) < dx);

    /*
     * Modify cx,cy to reflect the new client area before scaling
     * scrollbars.  We only affect the client size if there is a
     * *change* in scrollbar status:  if the scrollbar was there
     * but is no longer, then add to the client size; if it was
     * not there but now is, then subtract.
     */

    //Change cx depending on vertical scrollbar change
    GetScrollRange(m_hWnd, SB_VERT, &iMin, &iMax);
    fWasThere=(0!=iMin || 0!=iMax);

    if (fWasThere && !fVScroll)
        cx+=cxSB;

    if (!fWasThere && fVScroll)
        cx-=cxSB;

    //Change cy depending on horizontal scrollbar change
    GetScrollRange(m_hWnd, SB_HORZ, &iMin, &iMax);
    fWasThere=(0!=iMin || 0!=iMax);

    if (fWasThere && !fHScroll)
        cy+=cySB;

    if (!fWasThere && fHScroll)
        cy-=cySB;

    /*
     * Show/Hide the scrollbars if necessary and set the ranges.
     * The range is the number of units of the page we cannot see.
     */
    if (fHScroll)
        {
        //Convert current scroll position to new range.
        u=GetScrollPos(m_hWnd, SB_HORZ);

        if (0!=u)
            {
            GetScrollRange(m_hWnd, SB_HORZ, &iMin, &iMax);
            u=MulDiv(u, (dx-cx), (iMax-iMin));
            }

        SetScrollRange(m_hWnd, SB_HORZ, 0, dx-cx, FALSE);
        SetScrollPos(m_hWnd, SB_HORZ, u, TRUE);
        m_xPos=u;
        }
    else
        {
        SetScrollRange(m_hWnd, SB_HORZ, 0, 0, TRUE);
        m_xPos=0;
        }

    if (fVScroll)
        {
        //Convert current scroll position to new range.
        u=GetScrollPos(m_hWnd, SB_VERT);

        if (0!=u)
            {
            GetScrollRange(m_hWnd, SB_VERT, &iMin, &iMax);
            u=MulDiv(u, (dy-cy), (iMax-iMin));
            }

        SetScrollRange(m_hWnd, SB_VERT, 0, dy-cy, FALSE);
        SetScrollPos(m_hWnd, SB_VERT, u, TRUE);

        m_yPos=u;
        }
    else
        {
        SetScrollRange(m_hWnd, SB_VERT, 0, 0, TRUE);
        m_yPos=0;
        }

    //Repaint to insure that changes to m_x/yPos are reflected
    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }


PATRON.CPP

/*
 * PATRON.CPP
 * Patron Chapter 21
 *
 * WinMain which is all we need for the basic application.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "patron.h"

//CHAPTER21MOD
ULONG       g_cObj=0;
ULONG       g_cLock=0;
HWND        g_hWnd=NULL;
BOOL        g_fUser=TRUE;
//End CHAPTER21MOD

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCPatronFrame   pFR;
    FRAMEINIT       fi;
    WPARAM          wRet=0;

    SETMESSAGEQUEUE;

    //Attempt to allocate and initialize the application
    pFR=new CPatronFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return -1;

    fi.idsMin=IDS_FRAMEMIN;
    fi.idsMax=IDS_FRAMEMAX;
    fi.idsStatMin=IDS_STATMESSAGEMIN;
    fi.idsStatMax=IDS_STATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=CW_USEDEFAULT;
    fi.cy=CW_USEDEFAULT;

    //If we can initialize pFR, start chugging messages
    if (pFR->Init(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

//CHAPTER21MOD
/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Patron Document object to call when it gets
 *  destroyed.  We destroy the main window if the proper conditions
 *  are met for shutdown.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;

    //No more objects, no locks, no user control, shut the app down.
    if (0==g_cObj && 0==g_cLock && IsWindow(g_hWnd) && !g_fUser)
        PostMessage(g_hWnd, WM_CLOSE, 0, 0L);

    return;
    }

//End CHAPTER21MOD

/*
 * CPatronFrame::CPatronFrame
 * CPatronFrame::~CPatronFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WInMain
 */

CPatronFrame::CPatronFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    : CFrame(hInst, hInstPrev, pszCmdLine, nCmdShow)
    {
    m_fInitialized=FALSE;
    m_pIClassDataTran=NULL;

    //CHAPTER21MOD
    m_pDocCreated=NULL;
    m_fEmbedding=FALSE;

    m_dwRegCO=0;
    m_pIClassFactory=NULL;
    //End CHAPTER21MOD

    return;
    }

CPatronFrame::~CPatronFrame(void)
    {
    //CHAPTER21MOD
    //Opposite of CoRegisterClassObject, takes class factory ref to 1
    if (0L!=m_dwRegCO)
        CoRevokeClassObject(m_dwRegCO);

    //This should be the last Release, which frees the class factory.
    ReleaseInterface(m_pIClassFactory);
    //End CHAPTER21MOD

    if (NULL!=m_pIClassDataTran)
        {
        m_pIClassDataTran->LockServer(FALSE);
        m_pIClassDataTran->Release();
        }

    OleFlushClipboard();

    if (m_fInitialized)
        OleUninitialize();

    return;
    }

/*
 * CPatronFrame::Init
 *
 * Purpose:
 *  Call OleInitialize then calling down into the base class
 *  initialization.
 *
 * Parameters:
 *  pFI             PFRAMEINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CPatronFrame::Init(PFRAMEINIT pFI)
    {
    HRESULT     hr;

    CHECKVER_OLE;

    if (FAILED(OleInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    //Lock the data transfer object factory as an optimization.
    hr=CoGetClassObject(CLSID_DataTransferObject
        , CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory
        , (PPVOID)&m_pIClassDataTran);

    if (SUCCEEDED(hr))
        m_pIClassDataTran->LockServer(TRUE);

    //CHAPTER21MOD
    //Check for command line flags
    ParseCommandLine();

    if (NULL!=m_ppszCmdArgs)
        {
        if(0==lstrcmpi(m_ppszCmdArgs[0], TEXT("-Embedding"))
           || 0==lstrcmpi(m_ppszCmdArgs[0], TEXT("/Embedding")))
            m_fEmbedding=TRUE;
        }

    g_fUser=!m_fEmbedding;

    if (m_fEmbedding)
        {
        HRESULT     hr;

        m_pIClassFactory=new CLinkClassFactory(this);

        if (NULL==m_pIClassFactory)
            return FALSE;

        //Since we hold on to this, we should AddRef it.
        m_pIClassFactory->AddRef();

        hr=CoRegisterClassObject(CLSID_PatronPages, m_pIClassFactory
            , CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE, &m_dwRegCO);

        if (FAILED(hr))
            return FALSE;
        }
    //End CHAPTER21MOD

    return CFrame::Init(pFI);
    }

/*
 * CPatronFrame::CreateCClient
 *
 * Purpose:
 *  Constructs a new client specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient        Pointer to the new client object.
 */

PCClient CPatronFrame::CreateCClient(void)
    {
    return (PCClient)(new CPatronClient(m_hInst, this));
    }

/*
 * CPatronFrame::RegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CPatronFrame::RegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //First let the standard frame do its thing
    if (!CFrame::RegisterAllClasses())
        return FALSE;

    //We need double-clicks now and for object activation.
    wc.style         = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
    wc.hInstance     = m_hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PagesWndProc;
    wc.cbWndExtra    = CBPAGESWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPAGES;

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

    return TRUE;
    }

//CHAPTER21MOD
/*
 * CPatronFrame::PreShowInit
 *
 * Purpose:
 *  Called from Init before intially showing the window.  We do
 *  whatever else we want here, modifying m_nCmdShow as necessary
 *  which affects ShowWindow in Init.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if this successful, FALSE otherwise.
 */

BOOL CPatronFrame::PreShowInit(void)
    {
    //Base class does nothing
    CFrame::PreShowInit();

    //Save the window handle for shutdown if necessary.
    g_hWnd=m_hWnd;

    //If we're -Embedding, don't show the window initially.
    if (m_fEmbedding)
        m_nCmdShow=SW_HIDE;

    return TRUE;
    }
//End CHAPTER21MOD

/*
 * CPatronFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the Patron frame window that processes
 *  extra File menu items as well as the Page menu.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CPatronFrame::OnCommand(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PCPatronDoc     pDoc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many toolbar notifications.
     */
    if (m_fInit)
        return 0L;

    pDoc=(PCPatronDoc)m_pCL->ActiveDocument();

    if (NULL!=pDoc && (IDM_VERBMIN <= wID) && (IDM_VERBMAX >= wID))
        {
        pDoc->ActivateObject(wID-IDM_VERBMIN);
        return 0L;
        }

    switch (wID)
        {
        case IDM_FILEPRINT:
            pDoc->Print(m_hWnd);
            return 0L;

        case IDM_FILEPRINTERSETUP:
            pDoc->PrinterSetup(m_hWnd, FALSE);
            return 0L;

        case IDM_EDITPASTESPECIAL:
            pDoc->PasteSpecial(m_hWnd);
            return 0L;

        case IDM_EDITDELETEOBJECT:
            pDoc->Delete();
            return 0L;

        case IDM_EDITINSERTOBJECT:
            pDoc->InsertObject(m_hWnd);
            return 0L;

        case IDM_EDITCONVERT:
            pDoc->ConvertObject(m_hWnd);
            return 0L;

        case IDM_EDITLINKS:
            pDoc->EditLinks(m_hWnd);
            return 0L;

        case IDM_PAGENEWPAGE:
            pDoc->NewPage();
            break;

        case IDM_PAGEDELETEPAGE:
            pDoc->DeletePage();
            break;

        case IDM_PAGENEXTPAGE:
            pDoc->NextPage();
            break;

        case IDM_PAGEPREVIOUSPAGE:
            pDoc->PreviousPage();
            break;

        case IDM_PAGEFIRSTPAGE:
            pDoc->FirstPage();
            break;

        case IDM_PAGELASTPAGE:
            pDoc->LastPage();
            break;

        case IDM_PAGESHOWOBJECTS:
            {
            BOOL    fTemp;

            //First get the current state, then toggle it.
            fTemp=pDoc->ShowOrQueryObjectTypes(TRUE, FALSE);
            pDoc->ShowOrQueryObjectTypes(FALSE, !fTemp);
            }
            break;

        default:
           return CFrame::OnCommand(hWnd, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPatronFrame::CreateToolbar
 *
 * Purpose:
 *  Procedure to create all the necessary toolbar buttons.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of tools added to the bar.
 */

UINT CPatronFrame::CreateToolbar(void)
    {
    UINT            iLast;
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;

    //Insert the standard ones.
    iLast=CFrame::CreateToolbar();

    //Remove Undo:  we don't use it.
    m_pTB->Remove(IDM_EDITUNDO);

    /*
     * Insert Print File Import in the 5th position and account
     * for it in iLast.
     */
    m_pTB->Add(utCmd, 4, IDM_FILEPRINT, m_dxB, m_dyB
        , NULL, NULL, 6, uState);

    iLast++;

    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //Add New Page, and Delete Page
    m_pTB->Add(utCmd, iLast++, IDM_PAGENEWPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 2, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGEDELETEPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 3, uState);

    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //First, Prev, Next, Last pages.
    m_pTB->Add(utCmd, iLast++, IDM_PAGEFIRSTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 4, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGEPREVIOUSPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 5, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGENEXTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 6, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGELASTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 7, uState);

    return iLast;
    }

/*
 * CPatronFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CPatronFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    PCPatronDoc     pDoc;
    BOOL            fOK=FALSE;
    BOOL            fCallDefault=TRUE;
    UINT            uTemp;
    UINT            uTempE;
    UINT            uTempD;

    pDoc=(PCPatronDoc)m_pCL->ActiveDocument();

    uTempE=MF_ENABLED | MF_BYCOMMAND;
    uTempD=MF_DISABLED | MF_GRAYED | MF_BYCOMMAND;
    uTemp=((NULL!=pDoc) ? uTempE : uTempD);

    if (m_phMenu[0]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_FILEPRINT, uTemp);

        if (NULL!=pDoc)
            fOK=pDoc->FQueryPrinterSetup();

        EnableMenuItem(hMenu, IDM_FILEPRINTERSETUP
            , (fOK) ? uTempE : uTempD);
        }

    if (m_phMenu[1]==hMenu)
        {
        if (NULL!=pDoc)
            fOK=pDoc->FQueryPaste();

        EnableMenuItem(hMenu, IDM_EDITPASTE
            , (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITPASTESPECIAL
            , (fOK) ? uTempE : uTempD);

        //Cut, Copy, Delete depends on there being a selection.
        if (NULL!=pDoc)
            fOK=pDoc->FQueryObjectSelected(hMenu);
        else
            fOK=FALSE;

        EnableMenuItem(hMenu, IDM_EDITCUT, (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITCOPY
            , (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITDELETEOBJECT
            , (fOK) ? uTempE : uTempD);

        EnableMenuItem(hMenu, IDM_EDITINSERTOBJECT, uTemp);

        if (NULL!=pDoc)
            fOK=pDoc->FQueryEnableEditLinks();
        else
            fOK=FALSE;

        EnableMenuItem(hMenu, IDM_EDITLINKS
            , (fOK) ? uTempE : uTempD);

        //We did the whole menu...
        fCallDefault=FALSE;
        }

    //Page menu
    if (m_phMenu[2]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_PAGENEWPAGE,      uTemp);
        EnableMenuItem(hMenu, IDM_PAGEDELETEPAGE,   uTemp);
        EnableMenuItem(hMenu, IDM_PAGENEXTPAGE,     uTemp);
        EnableMenuItem(hMenu, IDM_PAGEPREVIOUSPAGE, uTemp);
        EnableMenuItem(hMenu, IDM_PAGEFIRSTPAGE,    uTemp);
        EnableMenuItem(hMenu, IDM_PAGELASTPAGE,     uTemp);

        //Check the Show Objects command or not.
        if (NULL!=pDoc)
            fOK=pDoc->ShowOrQueryObjectTypes(TRUE, FALSE);
        else
            fOK=FALSE;

        CheckMenuItem(hMenu, IDM_PAGESHOWOBJECTS, MF_BYCOMMAND
            | ((fOK) ? MF_CHECKED : MF_UNCHECKED));
        EnableMenuItem(hMenu, IDM_PAGESHOWOBJECTS, uTemp);
        }

    if (fCallDefault)
        CFrame::UpdateMenus(hMenu, iMenu);

    return;
    }

/*
 * CPatronFrame::UpdateToolbar
 *
 * Purpose:
 *  Enables and disables tools depending on whether we have
 *  a document or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronFrame::UpdateToolbar(void)
    {
    PCDocument  pDoc;
    BOOL        fEnable;

    //Let the default hack on its tools.
    CFrame::UpdateToolbar();

    pDoc=m_pCL->ActiveDocument();
    fEnable=(NULL!=pDoc);

    //No document, disable just about everything
    m_pTB->Enable(IDM_FILEPRINT,        fEnable);
    m_pTB->Enable(IDM_FILEPRINTERSETUP, fEnable);

    m_pTB->Enable(IDM_PAGENEWPAGE,      fEnable);
    m_pTB->Enable(IDM_PAGEDELETEPAGE,   fEnable);
    m_pTB->Enable(IDM_PAGEFIRSTPAGE,    fEnable);
    m_pTB->Enable(IDM_PAGEPREVIOUSPAGE, fEnable);
    m_pTB->Enable(IDM_PAGENEXTPAGE,     fEnable);
    m_pTB->Enable(IDM_PAGELASTPAGE,     fEnable);

    return;
    }

/*
 * CPatronFrame::FMessageHook
 *
 * Purpose:
 *  Override of CFrame::FMessageHook so we can specifically trap
 *  WM_MENUSELECT messages for the Object verb menu to provide some
 *  meaningful information on the status strip.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return value
 *                  for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CPatronFrame::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    BOOL        fRet=FALSE;

    *pLRes=0;

    MENUSELECTPARAMS(wItem, wMenuFlags, hMenu);

    //CHAPTER21MOD
    /*
     * When closing, make sure any document create from the
     * class factory is saved since the object in it might
     * have been changed.  We want to save without showing
     * the user any message or asking if the user wants to save.
     */
    if (WM_CLOSE==iMsg)
        {
        if (NULL!=m_pDocCreated)
            {
            if (m_pDocCreated->FDirtyGet())
                {
                CHourglass  wait;
                m_pDocCreated->Save(0, NULL);
                }
            }

        return FALSE;
        }
    //End CHAPTER21MOD

    //If this is the wrong message, nothing to do.
    if (WM_MENUSELECT!=iMsg)
        return FALSE;

    //This happens when there's no menu selection.
    if (-1==wMenuFlags)
        return FALSE;

    if (MF_POPUP & wMenuFlags)
        {
        /*
         * If this is the cascade verb menu itself, display the same
         * message.  m_phMenu[1] contains the current edit menu
         * handle.
         */
        if (0!=wItem)
            {
            fRet=((HMENU)wItem==GetSubMenu(m_phMenu[1]
                , MENUPOS_OBJECT));
            }
        }
    else
        {
        /*
         * If the ID is in the verb range, use
         * IDS_ITEMMESSAGEEDITOBJECT message
         */
        fRet=(IDM_VERBMIN <= wItem && IDM_VERBMAX >= wItem);
        }

    if (fRet)
        m_pSL->MessageDisplay(IDM_EDITOBJECT);

    return fRet;
    }


PATRON.H

/*
 * PATRON.H
 * Patron Chapter 21
 *
 * Single include file that pulls in everything needed for other
 * source files in the application.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _PATRON_H_
#define _PATRON_H_

#define INC_CLASSLIB
#define INC_OLEUI
//CHAPTER21MOD
#define CHAPTER21
//End CHAPTER21MOD
#include <inole.h>
#include "resource.h"

//Get editor window information
#include "pages.h"

/*
 * UINT value such that adding one produces zero.  Portable to Win32.
 * This is used to represent a non-existent zero-based UINT value
 */
#define NOVALUE                     ((UINT)-1)

//PATRON.CPP:  Frame object that creates a main window

class CPatronFrame : public CFrame
    {
    //CHAPTER21MOD
    //For access to m_pCL for document creation
    friend class CLinkClassFactory;
    //End CHAPTER21MOD

    private:
        BOOL            m_fInitialized;     //OleInitialize worked
        LPCLASSFACTORY  m_pIClassDataTran;  //For locking.

        //CHAPTER21MOD
        PCDocument      m_pDocCreated;      //What class factory makes
        BOOL            m_fEmbedding;       //-Embedding on command line?
        DWORD           m_dwRegCO;          //From CoRegisterClassObject
        LPCLASSFACTORY  m_pIClassFactory;
        //End CHAPTER21MOD

    protected:
        //Overridable for creating a CPatronClient
        virtual PCClient    CreateCClient(void);

        virtual BOOL        FMessageHook(HWND, UINT, WPARAM, LPARAM
                                , LRESULT *);

        //CHAPTER21MOD
        virtual BOOL        PreShowInit(void);
        //End CHAPTER21MOD

        virtual BOOL        RegisterAllClasses(void);
        virtual UINT        CreateToolbar(void);
        virtual LRESULT     OnCommand(HWND, WPARAM, LPARAM);

    public:
        CPatronFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CPatronFrame(void);

        //Overrides
        virtual BOOL        Init(PFRAMEINIT);

        virtual void        UpdateMenus(HMENU, UINT);
        virtual void        UpdateToolbar(void);

    };

typedef CPatronFrame *PCPatronFrame;

//CHAPTER21MOD

//These are global for simplification of object implementation.
extern ULONG g_cObj;
extern ULONG g_cLock;
extern HWND  g_hWnd;

//Function for the object to notify on destruction.
void ObjectDestroyed(void);

//ICLASSF.CPP

class CLinkClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;
        PCPatronFrame   m_pFR;          //For document creation.

    public:
        CLinkClassFactory(PCPatronFrame);
        ~CLinkClassFactory(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP         CreateInstance(LPUNKNOWN, REFIID
                                 , PPVOID);
        STDMETHODIMP         LockServer(BOOL);
    };

typedef CLinkClassFactory *PCLinkClassFactory;

//End CHAPTER21MOD

//CLIENT.CPP

/*
 * The only reason we have a derived class here is to override
 * CreateCDocument so we can create our own type as well as
 * overriding NewDocument to perform one other piece of work once
 * the document's been created.
 */

class CPatronClient : public CClient
    {
    protected:
        //Overridable for creating a new CDocument
        virtual PCDocument CreateCDocument(void);

    public:
        CPatronClient(HINSTANCE, PCFrame);
        virtual ~CPatronClient(void);
    };

typedef CPatronClient *PCPatronClient;

//DOCUMENT.CPP

//Constant ID for the pages window that lives in a document window
#define ID_PAGES            723

//CHAPTER21MOD
class CImpIPersistFile;
typedef class CImpIPersistFile *PCImpIPersistFile;
class CImpIOleItemContainer;
typedef class CImpIOleItemContainer *PCImpIOleItemContainer;

/*
 * To support linking to embeddings, the document needs to have
 * IPersistFile and IOleItemContainer interfaces, so we multiply
 * inherit from IUnknown and provide two interface implementations.
 */

class CPatronDoc : public CDocument, public IUnknown
//End CHAPTER21MOD
    {
    //These need access to FQueryPasteFromData, PasteFromData
    friend class CDropTarget;
    friend class CDropSource;

    //CHAPTER21MOD
    friend class CImpIPersistFile;
    friend class CImpIOleItemContainer;
    //End CHAPTER21MOD

    protected:
        LONG            m_lVer;         //Loaded data version
        PCPages         m_pPG;          //Pages window in us
        LPSTORAGE       m_pIStorage;    //Root storage for document
        BOOL            m_fPrintSetup;

        class CDropTarget *m_pDropTarget;   //Registered target

        UINT            m_cfEmbeddedObject; //Clipboard formats
        UINT            m_cfObjectDescriptor;
        UINT            m_cfLinkSource;
        UINT            m_cfLinkSrcDescriptor;
        BOOL            m_fShowTypes;       //Show Objects active?

        //CHAPTER21MOD
        ULONG               m_cRef;
        BOOL                m_fRename;          //Rename on Save?
        DWORD               m_dwRegROT;
        DWORD               m_dwRegROTWild;

        PCImpIPersistFile       m_pImpIPersistFile;
        PCImpIOleItemContainer  m_pImpIOleItemContainer;
        //End CHAPTER21MOD

    protected:
        virtual BOOL    FMessageHook(HWND, UINT, WPARAM, LPARAM
            , LRESULT *);

        BOOL            FQueryPasteFromData(LPDATAOBJECT
                            , LPFORMATETC, PTENANTTYPE);
        BOOL            FQueryPasteLinkFromData(LPDATAOBJECT
                            , LPFORMATETC, PTENANTTYPE);
        BOOL            PasteFromData(LPDATAOBJECT, LPFORMATETC
                            , TENANTTYPE, PPATRONOBJECT, DWORD
                            , BOOL);

    public:
        CPatronDoc(HINSTANCE, PCFrame, PCDocumentAdviseSink);
        virtual ~CPatronDoc(void);

        //CHAPTER21MOD
        //We now need our ubiquitous set of IUnknown members.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
        //End CHAPTER21MOD

        virtual BOOL    Init(PDOCUMENTINIT);
        virtual void    Clear(void);

        //CHAPTER21MOD
        virtual BOOL    FDirtySet(BOOL);
        //End CHAPTER21MOD
        virtual BOOL    FDirtyGet(void);
        virtual void    Delete(void);
        virtual BOOL    FQueryPrinterSetup(void);
        virtual BOOL    FQueryObjectSelected(HMENU);

        virtual UINT    Load(BOOL, LPTSTR);
        virtual UINT    Save(UINT, LPTSTR);

        virtual BOOL    Print(HWND);
        virtual UINT    PrinterSetup(HWND, BOOL);

        virtual BOOL    Clip(HWND, BOOL);
        virtual BOOL    FQueryPaste(void);
        virtual BOOL    Paste(HWND);
        virtual BOOL    PasteSpecial(HWND);
        virtual BOOL    FQueryEnableEditLinks(void);
        virtual BOOL    EditLinks(HWND);
        virtual BOOL    ShowOrQueryObjectTypes(BOOL, BOOL);

        virtual UINT    NewPage(void);
        virtual UINT    DeletePage(void);
        virtual UINT    NextPage(void);
        virtual UINT    PreviousPage(void);
        virtual UINT    FirstPage(void);
        virtual UINT    LastPage(void);

        virtual void    Rename(LPTSTR);
        virtual BOOL    InsertObject(HWND);
        virtual void    ActivateObject(LONG);
        virtual BOOL    ConvertObject(HWND);
    };

typedef CPatronDoc *PCPatronDoc;

//Hook for Print Dialog to hide Setup... button
UINT CALLBACK PrintDlgHook(HWND, UINT, WPARAM, LPARAM);

//CHAPTER21MOD

//IPersistFile implementation for CPatronDoc

class CImpIPersistFile : public IPersistFile
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCPatronDoc         m_pDoc;      //Back pointer to the object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIPersistFile(PCPatronDoc, LPUNKNOWN);
        ~CImpIPersistFile(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP Load(LPCOLESTR, DWORD);
        STDMETHODIMP Save(LPCOLESTR, BOOL);
        STDMETHODIMP SaveCompleted(LPCOLESTR);
        STDMETHODIMP GetCurFile(LPOLESTR *);
    };

/*
 * IOleItemContainer implementation for both CPatronDoc and CPage,
 * therefore have two back pointers.
 */

class CImpIOleItemContainer : public IOleItemContainer
    {
    protected:
        ULONG               m_cRef;
        class CPage        *m_pPage;
        PCPatronDoc         m_pDoc;      //Convenient naming & types
        LPUNKNOWN           m_pUnkOuter;
        BOOL                m_fDoc;      //Document or page?

    private:
        BOOL         TenantFromName(LPOLESTR, PCTenant *);

    public:
        CImpIOleItemContainer(LPVOID, LPUNKNOWN, BOOL);
        ~CImpIOleItemContainer(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP ParseDisplayName(LPBC, LPOLESTR, ULONG *
            , LPMONIKER *);
        STDMETHODIMP EnumObjects(DWORD, LPENUMUNKNOWN *);
        STDMETHODIMP LockContainer(BOOL);
        STDMETHODIMP GetObject(LPOLESTR, DWORD, LPBINDCTX, REFIID
            , PPVOID);
        STDMETHODIMP GetObjectStorage(LPOLESTR, LPBINDCTX, REFIID
            , PPVOID);
        STDMETHODIMP IsRunning(LPOLESTR);
    };

//End CHAPTER21MOD

//Drag-drop objects we need in the document

class CDropTarget : public IDropTarget
    {
    protected:
        ULONG               m_cRef;
        PCPatronDoc         m_pDoc;

        LPDATAOBJECT        m_pIDataObject;  //From DragEnter
        BOOL                m_fPendingRepaint;
        POINTL              m_ptPick;        //Pick-up offsets
        POINTL              m_ptLast;        //Last drag point
        SIZEL               m_szl;           //Object size
        BOOL                m_fFeedback;     //Draw feedback?
        FORMATETC           m_fe;            //Real dropping format

    public:
        CDropTarget(PCPatronDoc);
        ~CDropTarget(void);

        //IDropTarget interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP DragEnter(LPDATAOBJECT, DWORD, POINTL,LPDWORD);
        STDMETHODIMP DragOver(DWORD, POINTL, LPDWORD);
        STDMETHODIMP DragLeave(void);
        STDMETHODIMP Drop(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
    };

typedef CDropTarget *PCDropTarget;

class CDropSource : public IDropSource
    {
    protected:
        ULONG               m_cRef;

    public:
        CDropSource(void);
        ~CDropSource(void);

        //IDropSource interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP QueryContinueDrag(BOOL, DWORD);
        STDMETHODIMP GiveFeedback(DWORD);
    };

typedef CDropSource *PCDropSource;

#endif //_PATRON_H_


PRINT.CPP

/*
 * PRINT.CPP
 * Patron Chapter 21
 *
 * Implementation of printing functions for both CPatronDoc
 * and CPages classes.  These functions are here to keep clutter
 * down in document.cpp and pages.cpp.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

static HWND g_hDlgPrint=NULL;
static BOOL g_fCancelPrint=FALSE;

/*
 * CPatronDoc::Print
 *
 * Purpose:
 *  Prints the current document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *
 * Return Value:
 *  BOOL            TRUE if printing happened, FALSE if it didn't
 *                  start or didn't complete.
 */

BOOL CPatronDoc::Print(HWND hWndFrame)
    {
    PRINTDLG        pd;
    BOOL            fSuccess;

    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);
    pd.hwndOwner  =hWndFrame;
    pd.nCopies    =1;
    pd.nFromPage  =(USHORT)-1;
    pd.nToPage    =(USHORT)-1;
    pd.nMinPage   =1;
    pd.nMaxPage   =m_pPG->NumPagesGet();

    pd.lpfnPrintHook=PrintDlgHook;

    //Get the current document printer settings
    pd.hDevMode=m_pPG->DevModeGet();

    pd.Flags=PD_RETURNDC | PD_ALLPAGES | PD_COLLATE
        | PD_HIDEPRINTTOFILE | PD_NOSELECTION | PD_ENABLEPRINTHOOK;

    if (!PrintDlg(&pd))
        return FALSE;

    if (NULL!=pd.hDevMode)
        GlobalFree(pd.hDevMode);

    if (NULL!=pd.hDevNames)
        GlobalFree(pd.hDevNames);

    //Go do the actual printing.
    fSuccess=m_pPG->Print(pd.hDC, PSZ(IDS_DOCUMENTNAME), pd.Flags
        , pd.nFromPage, pd.nToPage, pd.nCopies);

    if (!fSuccess)
        {
        MessageBox(m_hWnd, PSZ(IDS_PRINTERROR)
            , PSZ(IDS_DOCUMENTCAPTION), MB_OK);
        }

    return fSuccess;
    }

/*
 * CPatronDoc::PrinterSetup
 *
 * Purpose:
 *  Selects a new printer and options for this document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *  fDefault        BOOL to avoid any dialog and just use the
 *                  default.
 *
 * Return Value:
 *  UINT            Undefined
 */

UINT CPatronDoc::PrinterSetup(HWND hWndFrame, BOOL fDefault)
    {
    PRINTDLG        pd;

    //Attempt to get printer metrics for the default printer.
    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);

    if (fDefault)
        pd.Flags=PD_RETURNDEFAULT;
    else
        {
        pd.hwndOwner=hWndFrame;
        pd.Flags=PD_PRINTSETUP;

        //Get the current document printer settings
        pd.hDevMode=m_pPG->DevModeGet();
        }

    if (!PrintDlg(&pd))
        return FALSE;

    if (!m_pPG->DevModeSet(pd.hDevMode, pd.hDevNames))
        {
        GlobalFree(pd.hDevNames);
        GlobalFree(pd.hDevMode);
        return FALSE;
        }

    FDirtySet(TRUE);
    return 1;
    }

/*
 * PrintDlgHook
 *
 * Purpose:
 *  Callback hook for the Print Dialog so we can hide the Setup
 *  button.  Patron only allows Setup before anything exists on
 *  the page, and is not written to handle setup at Print time.
 */

UINT CALLBACK PrintDlgHook(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    if (WM_INITDIALOG==iMsg)
        {
        HWND        hWnd;

        hWnd=GetDlgItem(hDlg, psh1);
        ShowWindow(hWnd, SW_HIDE);
        return TRUE;
        }

    return FALSE;
    }

/*
 * CPatronDoc::FQueryPrinterSetup
 *
 * Purpose:
 *  Returns whether or not the Printer Setup menu item can be
 *  enabled.  Once you create a tenant in any page, Printer Setup
 *  is voided simply to keep this sample simple, that is, we don't
 *  have to worry about reorganizing potentially large amounts
 *  of layout after we start plopping down objects.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE to enable the menu, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPrinterSetup(void)
    {
    return m_fPrintSetup;
    }

/*
 * CPages::DevModeSet
 *
 * Purpose:
 *  Provides the Pages with the current printer information.
 *
 * Parameters:
 *  hDevMode        HGLOBAL to the memory containing the DEVMODE.
 *                  This function assumes responsibility for this
 *                  handle.
 *  hDevNames       HGLOBAL providing the driver name and device
 *                  name from which we can create a DC for
 *                  information.
 *
 * Return Value:
 *  BOOL            TRUE if we could accept this configuration,
 *                  FALSE otherwise.  If we return TRUE we also
 *                  delete the old memory we hold.
 */

BOOL CPages::DevModeSet(HGLOBAL hDevMode, HGLOBAL hDevNames)
    {
    LPDEVNAMES      pdn;
    LPTSTR          psz;
    HGLOBAL         hMem;
    PDEVICECONFIG   pdc;
    LPDEVMODE       pdm;
    LPSTREAM        pIStream;
    HRESULT         hr;
    ULONG           cbDevMode, cbWrite;
    BOOL            fRet=FALSE;

    if (NULL==hDevMode || NULL==hDevNames)
        return FALSE;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    /*
     * DEVMODE is variable length--total length in hDevMode, so the
     * amount to write is that plus string space.  We subtract
     * sizeof(DEVMODE) as that is already included from GlobalSize.
     */
    cbDevMode=GlobalSize(hDevMode);
    cbWrite=cbDevMode+sizeof(DEVICECONFIG)-sizeof(DEVMODE);

    hMem=GlobalAlloc(GHND, cbWrite);

    if (NULL==hMem)
        {
        pIStream->Release();
        return FALSE;
        }

    pdc=(PDEVICECONFIG)GlobalLock(hMem);    //This always works
    pdm=(LPDEVMODE)GlobalLock(hDevMode);    //This might not

    if (NULL!=pdm)
        {
        pdc->cb=cbWrite;
        pdc->cbDevMode=cbDevMode;
        memcpy(&pdc->dm, pdm, (int)cbDevMode);
        GlobalUnlock(hDevMode);

        psz=(LPTSTR)GlobalLock(hDevNames);

        if (NULL!=psz)
            {
            pdn=(LPDEVNAMES)psz;
            lstrcpy(pdc->szDriver, psz+pdn->wDriverOffset);
            lstrcpy(pdc->szDevice, psz+pdn->wDeviceOffset);
            lstrcpy(pdc->szPort,   psz+pdn->wOutputOffset);

            pIStream->Write(pdc, cbWrite, &cbWrite);
            GlobalUnlock(hDevNames);
            fRet=TRUE;
            }
        }

    GlobalUnlock(hMem);
    GlobalFree(hMem);

    pIStream->Release();

    if (!fRet)
        return FALSE;

    GlobalFree(hDevNames);
    GlobalFree(hDevMode);

    return ConfigureForDevice();
    }

/*
 * CPages::DevModeGet
 *
 * Purpose:
 *  Retrieves a copy of the current DEVMODE structure for this
 *  Pages window.  The caller is responsible for this memory.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HGLOBAL         Handle to the memory containing the DEVMODE
 *                  structure.
 */

HGLOBAL CPages::DevModeGet(void)
    {
    HGLOBAL         hMem;
    LPVOID          pv;
    ULONG           cbDevMode, cbRead;
    LARGE_INTEGER   li;
    LPSTREAM        pIStream;
    HRESULT         hr;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    //Read how much to allocate for the DEVMODE structure
    LISet32(li, CBSEEKOFFSETCBDEVMODE);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);
    pIStream->Read(&cbDevMode, sizeof(ULONG), &cbRead);

    hMem=GlobalAlloc(GHND, cbDevMode);

    if (NULL!=hMem)
        {
        pv=(LPVOID)GlobalLock(hMem);
        pIStream->Read(pv, cbDevMode, &cbRead);
        GlobalUnlock(hMem);
        }

    pIStream->Release();
    return hMem;
    }

/*
 * CPages::DevReadConfig
 *
 * Purpose:
 *  Public function to read the current device configuration and
 *  optionally return an information context for it.
 *
 *
 * Parameters:
 *  ppcd            PCOMBINEDEVICE * in which to return a pointer
 *                  to an allocated structure that has all the
 *                  device information we want.  Ignored if NULL.
 *                  This is allocated with the task allocator.
 *  phDC            HDC * in which to return the information
 *                  context.  If NULL, no IC is created.  Caller
 *                  becomes responsible for the returned IC.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::DevReadConfig(PCOMBINEDEVICE *ppcd, HDC *phDC)
    {
    HRESULT         hr;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    PCOMBINEDEVICE  pcd;
    ULONG           cb, cbRead;
    LARGE_INTEGER   li;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    /*
     * Allocate the COMBINEDEVICE structure including the variable
     * information past the DEVMODE part.
     */

    hr=CoGetMalloc(MEMCTX_TASK, &pIMalloc);

    if (FAILED(hr))
        {
        pIStream->Release();
        return FALSE;
        }

    /*
     * Read size of the DEVICECONFIG structure including variable
     * portion of DEVMODE.  We need to load all this information
     * for CreateIC.  To this size we'll add the size of
     * DVTARGETDEVICE in order to allocate a COMBINEDEVICE.
     */

    pIStream->Read(&cb, sizeof(DWORD), &cbRead);

    pcd=(PCOMBINEDEVICE)pIMalloc->Alloc(cb+sizeof(DVTARGETDEVICE));

    if (NULL==pcd)
        {
        pIMalloc->Release();
        pIStream->Release();
        return FALSE;
        }

    //Now get the real information.
    LISet32(li, 0);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);
    pIStream->Read(&(pcd->dc), cb, &cbRead);
    pIStream->Release();

    /*
     * If requested, complete the DVTARGETDEVICE structure in
     * pcd and store pcd in *ppcd for return.
     */

    if (NULL!=ppcd)
        {
        WORD    cb=sizeof(DVTARGETDEVICE);

        pcd->td.tdSize=cb;
        pcd->td.tdExtDevmodeOffset=cb;
        pcd->td.tdDriverNameOffset=cb+sizeof(DEVMODE);
        pcd->td.tdDeviceNameOffset=cb+sizeof(DEVMODE)
            +(CCHDEVICENAME*sizeof(TCHAR));
        pcd->td.tdPortNameOffset  =cb+sizeof(DEVMODE)
            +(CCHDEVICENAME*2*sizeof(TCHAR));

        *ppcd=pcd;
        }

    //Create an IC if requested.
    if (NULL!=phDC)
        {
        //Get the DC then configure
        *phDC=CreateIC(pcd->dc.szDriver, pcd->dc.szDevice
            , pcd->dc.szPort, &(pcd->dc.dm));

        if (NULL==*phDC)
            return FALSE;
        }

    //pcd is a temporary allocation in this case
    if (NULL==ppcd)
        pIMalloc->Free(pcd);

    pIMalloc->Release();
    return (NULL!=*phDC);
    }

/*
 * CPages::ConfigureForDevice
 *
 * Purpose:
 *  Recalculates our drawing configuration based on the contents of
 *  an hDC.  If no HDC is given we use the contents of our DevMode
 *  stream.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::ConfigureForDevice(void)
    {
    POINT           ptOffset, ptPaper;
    RECT            rc;
    HDC             hDC;
    CHourglass      hg;

    if (!DevReadConfig(NULL, &hDC))
        return FALSE;

    //Get usable page dimensions:  already sensitive to orientation
    m_cx=GetDeviceCaps(hDC, HORZSIZE)*10-16; //*10: mm to LOMETRIC
    m_cy=GetDeviceCaps(hDC, VERTSIZE)*10-16; //-16: for driver bugs.

    //Calculate the printer-limited margins on sides in LOMETRIC.
    Escape(hDC, GETPRINTINGOFFSET, NULL, NULL, &ptOffset);
    Escape(hDC, GETPHYSPAGESIZE,   NULL, NULL, &ptPaper);

    SetRect(&rc, ptOffset.x, ptOffset.y, ptPaper.x, ptPaper.y);
    SetMapMode(hDC, MM_LOMETRIC);
    RectConvertMappings(&rc, hDC, FALSE);

    //Left and top margins are the printing offset.
    m_xMarginLeft= rc.left+8;   //+8 to match -16 above
    m_yMarginTop =-rc.top+8;    //LOMETRIC makes this negative.

    //Right is (paper width)-(usable width)-(left margin)
    m_xMarginRight =rc.right-m_cx-m_xMarginLeft;

    //Bottom is (paper height)-(usable height)-(top margin)+1
    m_yMarginBottom=-rc.bottom-m_cy-m_yMarginTop+1;

    UpdateScrollRanges();

    DeleteDC(hDC);
    return TRUE;
    }

/*
 * CPages::Print
 *
 * Purpose:
 *  Prints a specified range of pages to a given hDC.  Repeats for
 *  a given number of copies.
 *
 * Parameters:
 *  hDC             HDC to which we print.
 *  pszDoc          LPTSTR providing the document name.
 *  dwFlags         DWORD flags from PrintDlg
 *  iPageStart      UINT starting page index (one based)
 *  iPageEnd        UINT ending page index (one based).  Includes
 *                  this page.
 *  cCopies         UINT number of copies to print.  If PD_COLLATE
 *                  in dwFlags is set, we print multiple copies of
 *                  each page as we cycle through.  Otherwise we
 *                  cycle multiple times.
 *
 * Return Value:
 *  None
 */

BOOL CPages::Print(HDC hDC, LPTSTR pszDoc, DWORD dwFlags
    , UINT iPageStart, UINT iPageEnd, UINT cCopies)
    {
    BOOL        fError=FALSE;
    int         iPage, iPageInc;
    int         iUserPage, cPages;
    UINT        iRepeat, cRepeat;
    UINT        iCycle, cCycles;
    UINT        iPageHold=m_iPageCur;
    HWND        hWndT, hWndTop=NULL;
    DOCINFO     di;
    PCDocument  pDoc;

    //Validate hDC and page ranges
    if (NULL==hDC)
        return FALSE;

    if ((PD_PAGENUMS & dwFlags))
        {
        if (-1==iPageStart)
            iPageStart=0;
        else
            iPageStart--;   //Switch to zero offset.

        if (-1==iPageEnd)
            iPageEnd=m_cPages-1;
        else
            iPageEnd--;     //Switch to zero offset.
        }
    else //Can't test PD_ALLPAGES with & since it's defined as 0L
        {
        iPageStart=0;
        iPageEnd=m_cPages-1;
        }

    //Arrange cycles and repeats depending on cCopies and collating
    if (PD_COLLATE & dwFlags)
        {
        cCycles=cCopies;
        cRepeat=1;
        }
    else
        {
        cCycles=1;
        cRepeat=cCopies;
        }

    //Disable the frame window to prevent reentrancy while printing.
    hWndT=GetParent(m_hWnd);
    pDoc=(PCDocument)SendMessage(hWndT, DOCM_PDOCUMENT, 0, 0L);

    if (NULL!=pDoc)
        {
        PCFrame pFR;

        pFR=pDoc->FrameGet();
        hWndTop=pFR->Window();
        EnableWindow(hWndTop, FALSE);
        }

    SetAbortProc(hDC, AbortProc);
    g_fCancelPrint=FALSE;

    //If these don't work then we'll just live without a dialog.
    g_hDlgPrint=CreateDialog(m_hInst, MAKEINTRESOURCE(IDD_PRINTING)
        , hWndTop, PrintDlgProc);

    //Increment for either direction.
    iPageInc=(iPageStart > iPageEnd) ? -1 : 1;

    //Initial entries in dialog box.
    cPages=1+((int)(iPageEnd-iPageStart)*iPageInc);

    SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE, 1, (LPARAM)cPages);
    SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, 1, (LPARAM)cRepeat);

    di.cbSize=sizeof(DOCINFO);
    di.lpszDocName=pszDoc;
    di.lpszOutput=NULL;

    if (StartDoc(hDC, &di) > 0)
        {
        /*
         * Iterate over the pages, repeating each page depending on
         * the copies we want and if we have collate enabled.
         */

        for (iCycle=1; iCycle <= cCycles; iCycle++)
            {
            if (PD_COLLATE & dwFlags)
                {
                SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, iCycle
                    , (LPARAM)cCycles);
                }

            //iPageInc controls direction
            for (iPage=iPageStart; ; iPage+=iPageInc)
                {
                iUserPage=1+((iPage-(int)iPageStart)*iPageInc);

                SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE
                    , iUserPage, (LPARAM)cPages);

                m_iPageCur=iPage;   //We restore this later.

                for (iRepeat=1; iRepeat <= cRepeat; iRepeat++)
                    {
                    if (!(PD_COLLATE & dwFlags))
                        {
                        SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE
                            , iRepeat, (LPARAM)cRepeat);
                        }

                    StartPage(hDC);
                    Draw(hDC, TRUE, TRUE);

                    if (EndPage(hDC) < 0)
                        fError=TRUE;

                    if (fError || g_fCancelPrint)
                        break;
                    }

                if (fError || g_fCancelPrint)
                    break;

                //If we just printed the last page, time to quit.
                if (iPage==(int)iPageEnd)
                    break;
                }

            if (fError || g_fCancelPrint)
                break;
            }

        if (!fError)
            EndDoc(hDC);
        else
            AbortDoc(hDC);
        }
    else
        fError=TRUE;

    //Set the page back to what it was before all this started.
    m_iPageCur=iPageHold;

    EnableWindow(hWndTop, TRUE);
    SetFocus(hWndTop);
    DestroyWindow(g_hDlgPrint);
    DeleteDC(hDC);
    return !fError;
    }

/*
 * AbortProc
 *
 * Purpose:
 *  Abort procedure for printing the pages.
 *
 * Parameters:
 *  hDC             HDC on which printing is happening.
 *  iErr            int error code.
 *
 * Return Value:
 *  BOOL            TRUE to continue the print job, FALSE otherwise.
 */

BOOL APIENTRY AbortProc(HDC hDC, int iErr)
    {
    MSG     msg;

    while (!g_fCancelPrint
        && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
        if (NULL==g_hDlgPrint
            || !IsDialogMessage(g_hDlgPrint, &msg))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    return !g_fCancelPrint;
    }

/*
 * PrintDlgProc
 *
 * Purpose:
 *  Modeless dialog procedure for the dialog displayed while Patron
 *  is printing pages.
 */

BOOL APIENTRY PrintDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    TCHAR           szFormat[40];
    TCHAR           szOutput[80];

    switch (iMsg)
        {
        case WM_INITDIALOG:
            EnableMenuItem(GetSystemMenu(hDlg, FALSE), SC_CLOSE
                , MF_GRAYED);
            return TRUE;

        case WM_COMMAND:
            //Cancel button was pressed.
            g_fCancelPrint=TRUE;
            ShowWindow(hDlg, SW_HIDE);
            return TRUE;

        case PRINTM_PAGEUPDATE:
            GetDlgItemText(hDlg, ID_PAGESTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTPAGE, szOutput);
            return TRUE;

        case PRINTM_COPYUPDATE:
            GetDlgItemText(hDlg, ID_COPYSTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTCOPY, szOutput);
            return TRUE;
        }

    return FALSE;
    }


RESOURCE.H

/*
 * RESOURCE.H
 * Patron Chapter 21
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Note that we augment classlib.h which we include before this.

//Position of Window menu.
#undef  WINDOW_MENU
#define WINDOW_MENU                 3

//Position of the "Object" item on the Edit menu.
#define MENUPOS_OBJECT              8
#define MENUPOS_OBJECTONPOPUP       5

//Popup menu
#define IDR_RIGHTPOPUPMENU          2

//Total number of menus
#undef  CMENUS
#ifdef MDI
#define CMENUS                      5
#else
#define CMENUS                      4
#endif

//Menu command identifiers.

#define IDM_FILEPRINT               (IDM_CUSTOMFILEMIN)
#define IDM_FILEPRINTERSETUP        (IDM_CUSTOMFILEMIN+1)

#define IDM_EDITPASTESPECIAL        (IDM_CUSTOMEDITMIN)
#define IDM_EDITDELETEOBJECT        (IDM_CUSTOMEDITMIN+1)
#define IDM_EDITINSERTOBJECT        (IDM_CUSTOMEDITMIN+2)
#define IDM_EDITOBJECT              (IDM_CUSTOMEDITMIN+3)
#define IDM_EDITCONVERT             (IDM_CUSTOMEDITMIN+4)
#define IDM_EDITLINKS               (IDM_CUSTOMEDITMIN+5)

#define IDM_PAGENEWPAGE             (IDM_CUSTOMMIN)
#define IDM_PAGEDELETEPAGE          (IDM_CUSTOMMIN+1)
#define IDM_PAGENEXTPAGE            (IDM_CUSTOMMIN+2)
#define IDM_PAGEPREVIOUSPAGE        (IDM_CUSTOMMIN+3)
#define IDM_PAGEFIRSTPAGE           (IDM_CUSTOMMIN+4)
#define IDM_PAGELASTPAGE            (IDM_CUSTOMMIN+5)
#define IDM_PAGESHOWOBJECTS         (IDM_CUSTOMMIN+6)

//This special item is for the dynamic verbs on the Edit/Object item.
#define IDM_VERBMIN                 (IDM_CUSTOMMIN+100)
#define IDM_VERBMAX                 (IDM_CUSTOMMIN+200)

/*
 * IDs for StatStrip handling of popup menus:  must be in order of menu
 * CLASSRES.H already defines ID_MENUFILE and ID_MENUEDIT for us.
 */

#undef ID_MENUWINDOW
#undef ID_MENUHELP

#define ID_MENUPAGE                 (ID_MENUCUSTOMMIN+0)
#define ID_MENUWINDOW               (ID_MENUCUSTOMMIN+1)
#define ID_MENUHELP                 (ID_MENUCUSTOMMIN+2)

//Stringtable IDs.  Keep sequential for each group.
#define IDS_FRAMEMIN                IDS_STANDARDFRAMEMIN
#define IDS_FRAMEMAX                IDS_STANDARDFRAMEMAX

#define IDS_DOCUMENTMIN             IDS_STANDARDDOCMIN
#define IDS_PRINTERROR              (IDS_CUSTOMDOCMIN+0)
#define IDS_DOCUMENTNAME            (IDS_CUSTOMDOCMIN+1)
#define IDS_PASTEASPATRON           (IDS_CUSTOMDOCMIN+2)
#define IDS_PASTEMETAFILE           (IDS_CUSTOMDOCMIN+3)
#define IDS_PASTEASMETAFILE         (IDS_CUSTOMDOCMIN+4)
#define IDS_PASTEDIB                (IDS_CUSTOMDOCMIN+5)
#define IDS_PASTEASDIB              (IDS_CUSTOMDOCMIN+6)
#define IDS_PASTEBITMAP             (IDS_CUSTOMDOCMIN+7)
#define IDS_PASTEASBITMAP           (IDS_CUSTOMDOCMIN+8)
#define IDS_PASTEOBJECT             (IDS_CUSTOMDOCMIN+9)
#define IDS_PASTEASOBJECT           (IDS_CUSTOMDOCMIN+10)
#define IDS_PASTELINK               (IDS_CUSTOMDOCMIN+11)
#define IDS_PASTEASLINK             (IDS_CUSTOMDOCMIN+12)
//CHAPTER21MOD
#define IDS_EXTENSION               (IDS_CUSTOMDOCMIN+13)
#define IDS_DOCUMENTMAX             (IDS_CUSTOMDOCMIN+13)
//End CHAPTER21MOD

//These are specifically for the StatStrip
#define IDS_STATMESSAGEMIN                  IDS_STANDARDSTATMESSAGEMIN

#define IDS_ITEMMESSAGEFILEPRINT            (IDS_CUSTOMSTATMESSAGEMIN+0)
#define IDS_ITEMMESSAGEFILEPRINTERSETUP     (IDS_CUSTOMSTATMESSAGEMIN+1)

#define IDS_MENUMESSAGEPAGE                 (IDS_CUSTOMSTATMESSAGEMIN+2)
#define IDS_ITEMMESSAGEPAGENEWPAGE          (IDS_CUSTOMSTATMESSAGEMIN+3)
#define IDS_ITEMMESSAGEPAGEDELETEPAGE       (IDS_CUSTOMSTATMESSAGEMIN+4)
#define IDS_ITEMMESSAGEPAGENEXTPAGE         (IDS_CUSTOMSTATMESSAGEMIN+5)
#define IDS_ITEMMESSAGEPAGEPREVIOUSPAGE     (IDS_CUSTOMSTATMESSAGEMIN+6)
#define IDS_ITEMMESSAGEPAGEFIRSTPAGE        (IDS_CUSTOMSTATMESSAGEMIN+7)
#define IDS_ITEMMESSAGEPAGELASTPAGE         (IDS_CUSTOMSTATMESSAGEMIN+8)
#define IDS_ITEMMESSAGEEDITPASTESPECIAL     (IDS_CUSTOMSTATMESSAGEMIN+9)
#define IDS_ITEMMESSAGEEDITDELETEOBJECT     (IDS_CUSTOMSTATMESSAGEMIN+10)
#define IDS_ITEMMESSAGEEDITINSERTOBJECT     (IDS_CUSTOMSTATMESSAGEMIN+11)
#define IDS_ITEMMESSAGEEDITOBJECT           (IDS_CUSTOMSTATMESSAGEMIN+12)
#define IDS_ITEMMESSAGEEDITCONVERT          (IDS_CUSTOMSTATMESSAGEMIN+13)
#define IDS_ITEMMESSAGEEDITLINKS            (IDS_CUSTOMSTATMESSAGEMIN+14)
#define IDS_ITEMMESSAGEPAGESHOWOBJECTS      (IDS_CUSTOMSTATMESSAGEMIN+15)
#define IDS_STATMESSAGEMAX                  (IDS_CUSTOMSTATMESSAGEMIN+15)

//Definitions for the printing dialog.

#define IDD_PRINTING                2

#define ID_CURRENTPAGE              101
#define ID_CURRENTCOPY              102
#define ID_COPYSTRING               103
#define ID_PAGESTRING               104

#define PRINTM_PAGEUPDATE           (WM_USER+100)
#define PRINTM_COPYUPDATE           (WM_USER+101)

#endif //_RESOURCE_H_


TENANT.CPP

/*
 * TENANT.CPP
 * Patron Chapter 21
 *
 * Implementation of the CTentant class which holds information
 * for a single object on a page.  It maintains position, references
 * to data, and a storage.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CTenant::CTenant
 * CTenant::~CTenant
 *
 * Constructor Parameters:
 *  dwID            DWORD identifier for this page.
 *  hWnd            HWND of the pages window.
 *  pPG             PCPages to the parent structure.
 */

CTenant::CTenant(DWORD dwID, HWND hWnd, PCPages pPG)
    {
    m_hWnd=hWnd;
    m_dwID=dwID;

    m_fInitialized=0;
    m_pIStorage=NULL;
    m_cOpens=0;

    m_pObj=NULL;
    m_pPG =pPG;
    m_clsID=CLSID_NULL;
    m_fSetExtent=FALSE;

    m_cRef=0;
    m_pIOleObject=NULL;
    m_pIViewObject2=NULL;
    m_grfMisc=0;

    m_pImpIOleClientSite=NULL;
    m_pImpIAdviseSink=NULL;

    m_fRepaintEnabled=TRUE;
    m_pmkFile=NULL;
    m_fLinkAvail=TRUE;          //Checked on Load

    //CHAPTER21MOD
    m_pmk=NULL;
    //End CHAPTER21MOD
    return;
    }

CTenant::~CTenant(void)
    {
    //CHAPTER21MOD
    ReleaseInterface(m_pmk);
    //End CHAPTER21MOD

    ReleaseInterface(m_pmkFile);

    //Object pointers cleaned up in Close.

    DeleteInterfaceImp(m_pImpIAdviseSink);
    DeleteInterfaceImp(m_pImpIOleClientSite);
    return;
    }

/*
 * CTenant::QueryInterface
 * CTenant::AddRef
 * CTenant::Release
 *
 * Purpose:
 *  IUnknown members for CTenant object.
 */

STDMETHODIMP CTenant::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IOleClientSite==riid)
        *ppv=m_pImpIOleClientSite;

    if (IID_IAdviseSink==riid)
        *ppv=m_pImpIAdviseSink;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CTenant::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CTenant::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CTenant::GetID
 *
 * Return Value:
 *  DWORD           dwID field in this tenant.
 */

DWORD CTenant::GetID(void)
    {
    return m_dwID;
    }

/*
 * CTenant::GetStorageName
 *
 * Parameters:
 *  pszName         LPOLESTR to a buffer in which to store the storage
 *                  name for this tenant.
 *
 * Return Value:
 *  UINT            Number of characters stored.
 */

UINT CTenant::GetStorageName(LPOLESTR pszName)
    {
   #ifdef WIN32ANSI
    char        szTemp[32];
    UINT        cch;

    cch=wsprintf(szTemp, "Tenant %lu", m_dwID);
    MultiByteToWideChar(CP_ACP, 0, szTemp, -1, pszName, 32);
    return cch;
   #else
    return wsprintf(pszName, TEXT("Tenant %lu"), m_dwID);
   #endif
    }

/*
 * CTenant::StorageGet
 *
 * Purpose:
 *  Returns the IStorage pointer maintained by this tenant,
 *  AddRef'd of course.
 *
 * Parameters:
 *  ppStg           LPSTORAGE * in which to return the pointer.
 *
 * Return Value:
 *  None
 */

void CTenant::StorageGet(LPSTORAGE *ppStg)
    {
    if (NULL==ppStg)
        return;

    *ppStg=m_pIStorage;

    if (NULL!=*ppStg)
        (*ppStg)->AddRef();

    return;
    }

/*
 * CTenant::Create
 *
 * Purpose:
 *  Creates a new tenant of the given CLSID, which can be either a
 *  static bitmap or metafile or any compound document object.
 *
 * Parameters:
 *  tType           TENANTTYPE to create, either a static metafile,
 *                  bitmap, or some kind of compound document object
 *                  This determines which OleCreate* call we use.
 *  pvType          LPVOID providing the relevant pointer from which
 *                  to create the tenant, depending on iType.
 *  pFE             LPFORMATETC specifying the type of renderings
 *                  to use.
 *  pptl            PPOINTL in which we store offset coordinates.
 *  pszl            LPSIZEL where this object should store its
 *                  lometric extents.
 *  pIStorage       LPSTORAGE of the page we live in.  We have to
 *                  create another storage in this for the tenant.
 *  ppo             PPATRONOBJECT containing placement data.
 *  dwData          DWORD with extra data, sensitive to iType.
 *
 * Return Value:
 *  UINT            A CREATE_* value depending on what we
 *                  actually do.
 */

UINT CTenant::Create(TENANTTYPE tType, LPVOID pvType
    , LPFORMATETC pFE, PPOINTL pptl, LPSIZEL pszl
    , LPSTORAGE pIStorage, PPATRONOBJECT ppo, DWORD dwData)
    {
    HRESULT             hr;
    LPUNKNOWN           pObj;
    UINT                uRet=CREATE_GRAPHICONLY;

    if (NULL==pvType || NULL==pIStorage)
        return CREATE_FAILED;

    //Fail if this is called for an already living tenant.
    if (m_fInitialized)
        return CREATE_FAILED;

    m_fInitialized=TRUE;

    //Create a new storage for this tenant.
    if (!Open(pIStorage))
        return CREATE_FAILED;

    /*
     * Get the placement info if it's here.  We either have a non-
     * NULL PPATRONOBJECT in ppo or we have to use default
     * placement and retrieve the size from the object itself.
     */
    pszl->cx=0;
    pszl->cy=0;

    if (NULL!=ppo)
        {
        *pFE=ppo->fe;
        *pptl=ppo->ptl;
        *pszl=ppo->szl;     //Could be 0,0 , so we ask object

        uRet=CREATE_PLACEDOBJECT;
        }

    hr=ResultFromScode(E_FAIL);

    //Now create an object based specifically for the type.
    switch (tType)
        {
        case TENANTTYPE_NULL:
            break;

        case TENANTTYPE_STATIC:
            /*
             * We could use OleCreateStaticFromData here which does
             * pretty much what we're doing below.  However, it does
             * not allow us to control whether we paste a bitmap or
             * a metafile--it uses metafile first, bitmap second.
             * For this reason we'll use code developed in Chapter
             * 11's FreeLoader to affect the paste.
             */
            hr=CreateStatic((LPDATAOBJECT)pvType, pFE, &pObj);
            break;

        case TENANTTYPE_EMBEDDEDOBJECT:
            hr=OleCreate(*((LPCLSID)pvType), IID_IUnknown
                , OLERENDER_DRAW, NULL, NULL, m_pIStorage
                , (PPVOID)&pObj);
            break;

        case TENANTTYPE_EMBEDDEDFILE:
            hr=OleCreateFromFile(CLSID_NULL, (LPTSTR)pvType
                , IID_IUnknown, OLERENDER_DRAW, NULL, NULL
                , m_pIStorage, (PPVOID)&pObj);
            break;

        case TENANTTYPE_EMBEDDEDOBJECTFROMDATA:
            hr=OleCreateFromData((LPDATAOBJECT)pvType, IID_IUnknown
                , OLERENDER_DRAW, NULL, NULL, m_pIStorage
                , (PPVOID)&pObj);
            break;

        case TENANTTYPE_LINKEDFILE:
            hr=OleCreateLinkToFile((LPTSTR)pvType, IID_IUnknown
                , OLERENDER_DRAW, NULL, NULL, m_pIStorage
                , (PPVOID)&pObj);
            break;

        case TENANTTYPE_LINKEDOBJECTFROMDATA:
            hr=OleCreateLinkFromData((LPDATAOBJECT)pvType
                , IID_IUnknown, OLERENDER_DRAW, NULL, NULL
                , m_pIStorage, (PPVOID)&pObj);
            break;

        default:
            break;
        }

    //If creation didn't work, get rid of the element Open created.
    if (FAILED(hr))
        {
        Destroy(pIStorage);
        return CREATE_FAILED;
        }

    //We don't get the size if PatronObject data was seen already.
    if (!ObjectInitialize(pObj, pFE, dwData))
        {
        Destroy(pIStorage);
        return CREATE_FAILED;
        }

    if (0==pszl->cx && 0==pszl->cy)
        {
        SIZEL   szl;

        //Try to get the real size of the object, default to 2"*2"
        SETSIZEL((*pszl), 2*LOMETRIC_PER_INCH, 2*LOMETRIC_PER_INCH);
        hr=ResultFromScode(E_FAIL);

        //Try IViewObject2 first, then IOleObject as a backup.
        if (NULL!=m_pIViewObject2)
            {
            hr=m_pIViewObject2->GetExtent(m_fe.dwAspect, -1, NULL
                , &szl);
            }
        else
            {
            if (NULL!=m_pIOleObject)
                hr=m_pIOleObject->GetExtent(m_fe.dwAspect, &szl);
            }

        if (SUCCEEDED(hr))
            {
            //Convert HIMETRIC to our LOMETRIC mapping
            SETSIZEL((*pszl), szl.cx/10, szl.cy/10);
            }
        }

    return uRet;
    }

/*
 * CTenant::Load
 *
 * Purpose:
 *  Recreates the object living in this tenant in place of calling
 *  FCreate.  This is used in loading as opposed to new creation.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE of the page we live in.
 *  pti             PTENTANTINFO containing persistent information.
 *                  The ID value in this structure is ignored.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CTenant::Load(LPSTORAGE pIStorage, PTENANTINFO pti)
    {
    HRESULT         hr;
    LPUNKNOWN       pObj;
    DWORD           dwState=TENANTSTATE_DEFAULT;

    if (NULL==pIStorage || NULL==pti)
        return FALSE;

    /*
     * If we already initialized once, clean up, releasing
     * everything before we attempt to reload.  This happens
     * when using the Convert Dialog.
     */
    if (m_fInitialized)
        {
        //Preserve all states except open
        dwState=(m_dwState & ~TENANTSTATE_OPEN);
        m_cRef++;   //Prevent accidental closure

        //This should release all holds on our IStorage as well.
        if (NULL!=m_pIViewObject2)
            {
            m_pIViewObject2->SetAdvise(m_fe.dwAspect, 0, NULL);
            ReleaseInterface(m_pIViewObject2);
            }

        ReleaseInterface(m_pIOleObject);
        ReleaseInterface(m_pObj);

        m_pIStorage=NULL;   //We'll have already released this.
        m_cRef--;           //Match safety increment above.
        }

    m_fInitialized=TRUE;

    //Open the storage for this tenant.
    if (!Open(pIStorage))
        return FALSE;

    /*
     * NOTE:  If you do not pass an IOleClientSite to OleLoad
     * it will not automatically reconnect a linked object to
     * the running source via IOleLink::BindIfRunning.  Since
     * we do not pass m_pImpIOleClientSite here, we'll call
     * BindIfRunning ourselves in ObjectInitialize.
     */
    hr=OleLoad(m_pIStorage, IID_IUnknown, NULL, (PPVOID)&pObj);

    if (FAILED(hr))
        {
        Destroy(pIStorage);
        return FALSE;
        }

    m_fSetExtent=pti->fSetExtent;
    ObjectInitialize(pObj, &pti->fe, NULL);

    //Restore the original state before reloading.
    m_dwState=dwState;

    RectSet(&pti->rcl, FALSE, FALSE);
    return TRUE;
    }

/*
 * CTenant::GetInfo
 *
 * Purpose:
 *  Retrieved a TENANTINFO structure for this tenant.
 *
 * Parameters:
 *  pti             PTENANTINFO structure to fill
 *
 * Return Value:
 *  None
 */

void CTenant::GetInfo(PTENANTINFO pti)
    {
    if (NULL!=pti)
        {
        pti->dwID=m_dwID;
        pti->rcl=m_rcl;
        pti->fe=m_fe;
        pti->fSetExtent=m_fSetExtent;
        }

    return;
    }

/*
 * CTenant::ObjectInitialize
 * (Protected)
 *
 * Purpose:
 *  Performs operations necessary after creating an object or
 *  reloading one from storage.
 *
 * Parameters:
 *  pObj            LPUNKNOWN of the object in this tenant.
 *  pFE             LPFORMATETC describing the graphic here.
 *  dwData          DWORD extra data.  If pFE->dwAspect==
 *                  DVASPECT_ICON then this is the iconic metafile.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CTenant::ObjectInitialize(LPUNKNOWN pObj, LPFORMATETC pFE
    , DWORD dwData)
    {
    HRESULT         hr;
    LPPERSIST       pIPersist=NULL;
    DWORD           dw;
    PCDocument      pDoc;
    TCHAR           szFile[CCHPATHMAX];
    LPOLELINK       pIOleLink=NULL;

    if (NULL==pObj || NULL==pFE)
        return FALSE;

    m_pObj=pObj;
    m_fe=*pFE;
    m_fe.ptd=NULL;
    m_dwState=TENANTSTATE_DEFAULT;

    /*
     * Determine the type:  Static or Embedded.  If Static,
     * this will have CLSID_Picture_Metafile or CLSID_Picture_Dib.
     * Otherwise it's embedded.  Later we'll add a case for links.
     */
    m_tType=TENANTTYPE_EMBEDDEDOBJECT;

    if (SUCCEEDED(pObj->QueryInterface(IID_IPersist
        , (PPVOID)&pIPersist)))
        {
        CLSID   clsid=CLSID_NULL;

        hr=pIPersist->GetClassID(&clsid);

        //If we don't have a CLSID, default to static
        if (FAILED(hr) || CLSID_Picture_Metafile==clsid
            || CLSID_Picture_Dib==clsid)
            m_tType=TENANTTYPE_STATIC;

        pIPersist->Release();
        }

    //Check if this is a linked object.
    if (SUCCEEDED(pObj->QueryInterface(IID_IOleLink
        , (PPVOID)&pIOleLink)))
        {
        LPMONIKER   pmk;

        hr=pIOleLink->GetSourceMoniker(&pmk);

        if (FAILED(hr) || NULL==pmk)
            m_tType=TENANTTYPE_STATIC;
        else
            {
            m_tType=TENANTTYPE_LINKEDOBJECT;
            pmk->Release();

            //Connect to the object if the source is running.
            pIOleLink->BindIfRunning();
            }

        pIOleLink->Release();
        }

    m_pIViewObject2=NULL;
    hr=pObj->QueryInterface(IID_IViewObject2
        , (PPVOID)&m_pIViewObject2);

    if (FAILED(hr))
        return FALSE;

    m_pIViewObject2->SetAdvise(m_fe.dwAspect, 0, m_pImpIAdviseSink);

    //We need an IOleObject most of the time, so get one here.
    m_pIOleObject=NULL;
    hr=pObj->QueryInterface(IID_IOleObject
         , (PPVOID)&m_pIOleObject);

    /*
     * Follow up object creation with advises and so forth.  If
     * we cannot get IOleObject here, then we know we can't do
     * any IOleObject actions from here on--object is static.
     */
    if (FAILED(hr))
        return TRUE;

    /*
     * Get the MiscStatus bits and check for OLEMISC_ONLYICONIC.
     * If set, force dwAspect in m_fe to DVASPECT_ICON so we
     * remember to draw it properly and do extents right.
     */
    m_pIOleObject->GetMiscStatus(m_fe.dwAspect, &m_grfMisc);

    if (OLEMISC_ONLYICONIC & m_grfMisc)
        m_fe.dwAspect=DVASPECT_ICON;

    /*
     * We could pass m_pImpIOleClientSite in an OleCreate* call, but
     * since this function could be called after OleLoad, we still
     * need to do this here, so it's always done here...
     */
    m_pIOleObject->SetClientSite(m_pImpIOleClientSite);
    m_pIOleObject->Advise(m_pImpIAdviseSink, &dw);

    OleSetContainedObject(m_pIOleObject, TRUE);

    /*
     * For IOleObject::SetHostNames we need the application name
     * and the document name (which is passed in the object
     * parameter).  The design of Patron doesn't give us nice
     * structured access to the name of the document we're in, so
     * I grab the parent of the Pages window (the document) and
     * send it DOCM_PDOCUMENT which returns us the pointer.
     * Roundabout, but it works.
     */

    pDoc=(PCDocument)SendMessage(GetParent(m_hWnd), DOCM_PDOCUMENT
        , 0, 0L);

    if (NULL!=pDoc)
        pDoc->FilenameGet(szFile, CCHPATHMAX);
    else
        szFile[0]=0;

    //CHAPTER21MOD
    NotifyOfRename(szFile, NULL, NULL);
    //End CHAPTER21MOD

    /*
     * If we're creating an iconic aspect object and we have
     * an object from the Insert Object dialog, then we need to
     * store that iconic presentation in the cache, handled
     * with the utility function INOLE_SwitchDisplayAspect.  In
     * this case dwData is a handle to the metafile containing
     * the icon.  If dwData is NULL then we depend on the
     * server to provide the aspect, in which case we need
     * a view advise.
     */

    if (DVASPECT_ICON & m_fe.dwAspect)
        {
        DWORD           dw=DVASPECT_CONTENT;
        IAdviseSink    *pSink;

        pSink=(NULL==dwData) ? NULL : m_pImpIAdviseSink;

        INOLE_SwitchDisplayAspect(m_pIOleObject, &dw
            , DVASPECT_ICON, (HGLOBAL)(UINT)dwData, FALSE
            , (NULL!=dwData), pSink, NULL);
        }

    return TRUE;
    }

/*
 * CTenant::Open
 *
 * Purpose:
 *  Retrieves the IStorage associated with this tenant.  The
 *  IStorage is owned by the tenant and thus the tenant always
 *  holds a reference count.
 *
 *  If the storage is already open for this tenant, then this
 *  function will AddRef it; therefore the caller must always
 *  match an Open with a Close.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE above this tenant (which has its
 *                  own storage).
 *
 * Return Value:
 *  BOOL            TRUE if opening succeeds, FALSE otherwise.
 */

BOOL CTenant::Open(LPSTORAGE pIStorage)
    {
    HRESULT     hr=NOERROR;
    DWORD       dwMode=STGM_TRANSACTED | STGM_READWRITE
                    | STGM_SHARE_EXCLUSIVE;
    OLECHAR     szTemp[32];

    if (NULL==m_pIStorage)
        {
        if (NULL==pIStorage)
            return FALSE;

        /*
         * Attempt to open the storage under this ID.  If there is
         * none, then create it.  In either case we end up with an
         * IStorage that we either save in pPage or release.
         */

        GetStorageName(szTemp);
        hr=pIStorage->OpenStorage(szTemp, NULL, dwMode, NULL, 0
            , &m_pIStorage);

        if (FAILED(hr))
            {
            hr=pIStorage->CreateStorage(szTemp, dwMode, 0, 0
                , &m_pIStorage);
            }
        }
    else
        m_pIStorage->AddRef();

    if (FAILED(hr))
        return FALSE;

    m_cOpens++;

    //Create these if we don't have them already.
    if (NULL==m_pImpIOleClientSite && NULL==m_pImpIAdviseSink)
        {
        m_pImpIOleClientSite=new CImpIOleClientSite(this, this);
        m_pImpIAdviseSink=new CImpIAdviseSink(this, this);

        if (NULL==m_pImpIOleClientSite || NULL==m_pImpIAdviseSink)
            return FALSE;
        }

    return TRUE;
    }

/*
 * CTenant::Close
 *
 * Purpose:
 *  Possibly commits the storage, then releases it reversing the
 *  reference count from Open.  If the reference on the storage
 *  goes to zero, the storage is forgotten.  However, the object we
 *  contain is still held and as long as it's active the storage
 *  remains alive.
 *
 * Parameters:
 *  fCommit         BOOL indicating if we're to commit.
 *
 * Return Value:
 *  None
 */

void CTenant::Close(BOOL fCommit)
    {
    if (fCommit)
        Update();

    if (NULL!=m_pIStorage)
        {
        m_pIStorage->Release();

        /*
         * We can't use a zero reference count to know when to NULL
         * this since other things might have AddRef'd the storage.
         */
        if (0==--m_cOpens)
            {
            m_pIStorage=NULL;

            //Close the object saving if necessary
            if (NULL!=m_pIOleObject)
                {
                m_pIOleObject->Close(OLECLOSE_SAVEIFDIRTY);
                ReleaseInterface(m_pIOleObject);
                }

            //Release all other held pointers
            if (NULL!=m_pIViewObject2)
                {
                m_pIViewObject2->SetAdvise(m_fe.dwAspect, 0, NULL);
                ReleaseInterface(m_pIViewObject2);
                }

            //We know we only hold one ref from Create or Load
            ReleaseInterface(m_pObj);
            }
        }

    return;
    }

/*
 * CTenant::Update
 *
 * Purpose:
 *  Forces a common on the page if it's open.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the object is open, FALSE otherwise.
 */

BOOL CTenant::Update(void)
    {
    LPPERSISTSTORAGE    pIPS;

    if (NULL!=m_pIStorage)
        {
        /*
         * We need to OleSave again because we might have changed
         * the size or position of this tenant.  We also need to
         * save the rectangle on the page, since that's not known
         * to OLE.
         */
        m_pObj->QueryInterface(IID_IPersistStorage, (PPVOID)&pIPS);

        //This fails for static objects...so we improvise if so
        if (FAILED(OleSave(pIPS, m_pIStorage, TRUE)))
            {
            //This is essentially what OleSave does.
            WriteClassStg(m_pIStorage, m_clsID);
            pIPS->Save(m_pIStorage, TRUE);
            }

        pIPS->SaveCompleted(NULL);
        pIPS->Release();

        m_pIStorage->Commit(STGC_DEFAULT);
        }

    return FALSE;
    }

/*
 * CTenant::Destroy
 *
 * Purpose:
 *  Removes this page from the given storage.  The caller should
 *  eventually delete this CTenant object to free the object herein.
 *  Nothing is committed when being destroyed.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE contianing this page on which to call
 *                  DestroyElement
 *
 * Return Value:
 *  None
 */

void CTenant::Destroy(LPSTORAGE pIStorage)
    {
    OLECHAR     szTemp[32];

    if (NULL!=pIStorage)
        {
        if (NULL!=m_pIOleObject)
            m_pIOleObject->Close(OLECLOSE_NOSAVE);

        if (NULL!=m_pIStorage)
            {
            //Remove all reference/open counts on this storage.
            while (0!=m_cOpens)
                {
                m_pIStorage->Release();
                m_cOpens--;
                }
            }

        GetStorageName(szTemp);
        pIStorage->DestroyElement(szTemp);

        m_pIStorage=NULL;
        }

    return;
    }

/*
 * CTenant::Select
 *
 * Purpose:
 *  Selects or deselects the tenant.
 *
 * Parameters:
 *  fSelect         BOOL indicating the new state of the tenant.
 *
 * Return Value:
 *  None
 */

void CTenant::Select(BOOL fSelect)
    {
    BOOL        fWasSelected;
    DWORD       dwState;
    RECT        rc;
    HDC         hDC;

    fWasSelected=(BOOL)(TENANTSTATE_SELECTED & m_dwState);

    //Nothing to do when there's no change.
    if (fWasSelected==fSelect)
        return;

    dwState=m_dwState & ~TENANTSTATE_SELECTED;
    m_dwState=dwState | ((fSelect) ? TENANTSTATE_SELECTED : 0);

    /*
     * Draw sizing handles to show the selection state.  We convert
     * things to MM_TEXT since that's what this function expects.
     */

    RECTFROMRECTL(rc, m_rcl);
    RectConvertMappings(&rc, NULL, TRUE);
    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

    hDC=GetDC(m_hWnd);

    UIDrawHandles(&rc, hDC, UI_HANDLES_INSIDE
        | UI_HANDLES_NOBORDER | UI_HANDLES_USEINVERSE
        , CXYHANDLE, !fWasSelected);

    ReleaseDC(m_hWnd, hDC);

    if (fSelect)
        m_pPG->m_fDirty=TRUE;

    return;
    }

/*
 * CTenant::ShowAsOpen
 *
 * Purpose:
 *  Draws or removes the hatch pattern over an object.
 *
 * Parameters:
 *  fOpen           BOOL indicating the open state of this tenant.
 *
 * Return Value:
 *  None
 */

void CTenant::ShowAsOpen(BOOL fOpen)
    {
    BOOL        fWasOpen;
    DWORD       dwState;
    RECT        rc;
    HDC         hDC;

    fWasOpen=(BOOL)(TENANTSTATE_OPEN & m_dwState);

    dwState=m_dwState & ~TENANTSTATE_OPEN;
    m_dwState=dwState | ((fOpen) ? TENANTSTATE_OPEN : 0);

    //If this was not open, then just hatch, otherwise repaint.
    if (!fWasOpen && fOpen)
        {
        RECTFROMRECTL(rc, m_rcl);
        RectConvertMappings(&rc, NULL, TRUE);
        OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

        hDC=GetDC(m_hWnd);
        UIDrawShading(&rc, hDC, UI_SHADE_FULLRECT, 0);
        ReleaseDC(m_hWnd, hDC);
        }

    if (fWasOpen && !fOpen)
        Repaint();

    return;
    }

/*
 * CTenant::ShowYourself
 *
 * Purpose:
 *  Function that really just implements IOleClientSite::ShowObject.
 *  Here we first check if the tenant is fully visible, and if so,
 *  then nothing needs to happen.  Otherwise, if the upper left
 *  corner of the tenant is in the upper left visible quadrant of
 *  the window, we'll also consider ourselves done.  Otherwise
 *  we'll put the upper left corner of the object at the upper left
 *  corner of the window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CTenant::ShowYourself(void)
    {
    RECTL       rcl;
    RECT        rc;
    POINT       pt1, pt2;

    //Scrolling deals in device units; get our rectangle in those.
    RectGet(&rcl, TRUE);

    //Get the window rectangle offset for the current scroll pos.
    GetClientRect(m_hWnd, &rc);
    OffsetRect(&rc, m_pPG->m_xPos, m_pPG->m_yPos);

    //Check if the object is already visible. (macro in bookguid.h)
    SETPOINT(pt1, (int)rcl.left,  (int)rcl.top);
    SETPOINT(pt2, (int)rcl.right, (int)rcl.bottom);

    if (PtInRect(&rc, pt1) && PtInRect(&rc, pt2))
        return;

    //Check if the upper left is within the upper left quadrant
    if (((int)rcl.left > rc.left
        && (int)rcl.left < ((rc.right+rc.left)/2))
        && ((int)rcl.top > rc.top
        && (int)rcl.top < ((rc.bottom+rc.top)/2)))
        return;

    //These are macros in INC\BOOK1632.H
    SendScrollPosition(m_hWnd, WM_HSCROLL, rcl.left-8);
    SendScrollPosition(m_hWnd, WM_VSCROLL, rcl.top-8);
    return;
    }

/*
 * CTenant::AddVerbMenu
 *
 * Purpose:
 *  Creates the variable verb menu item for the object in this
 *  tenant.
 *
 * Parmeters:
 *  hMenu           HMENU on which to add items.
 *  iPos            UINT position on that menu to add items.
 *
 * Return Value:
 *  None
 */

void CTenant::AddVerbMenu(HMENU hMenu, UINT iPos)
    {
    HMENU       hMenuTemp;
    LPOLEOBJECT pObj=m_pIOleObject;

    //If we're static, say we have no object.
    if (TENANTTYPE_STATIC==m_tType)
        pObj=NULL;

    OleUIAddVerbMenu(pObj, NULL, hMenu, iPos, IDM_VERBMIN
        , IDM_VERBMAX, TRUE, IDM_EDITCONVERT, &hMenuTemp);

    return;
    }

/*
 * CTenant::TypeGet
 *
 * Purpose:
 *  Returns the type of this tenant
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  TENANTTYPE      Type of the tenant.
 */

TENANTTYPE CTenant::TypeGet(void)
    {
    return m_tType;
    }

/*
 * CTenant::CopyEmbeddedObject
 *
 * Purpose:
 *  Copies an embedded object to the given data object (via SetData,
 *  assuming this is a data transfer object for clipboard/drag-drop)
 *  if that's what we're holding.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT in which to store the copy.
 *  pFE             LPFORMATETC into which to copy CFSTR_EMBEDDEDOBJECT
 *                  if we put that in the data object.
 *  pptl            PPOINTL to the pick point (NULL outside of
 *                  drag-drop);
 *
 * Return Value:
 *  None
 */

void CTenant::CopyEmbeddedObject(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, PPOINTL pptl)
    {
    LPPERSISTSTORAGE    pIPS;
    STGMEDIUM           stm;
    FORMATETC           fe;
    HRESULT             hr;
    UINT                cf;
    POINTL              ptl;
    SIZEL               szl;

    //Can only copy embeddings.
    if (TENANTTYPE_EMBEDDEDOBJECT!=m_tType || NULL==m_pIOleObject)
        return;

    if (NULL==pptl)
        {
        SETPOINTL(ptl, 0, 0);
        pptl=&ptl;
        }

    /*
     * Create CFSTR_EMBEDDEDOBJECT.  This is simply an IStorage with
     * a copy of the embedded object in it.  The not-so-simple part
     * is getting an IStorage to stuff it in.  For this operation

* we'll use a temporary compound file.
     */

    stm.pUnkForRelease=NULL;
    stm.tymed=TYMED_ISTORAGE;
    hr=StgCreateDocfile(NULL, STGM_TRANSACTED | STGM_READWRITE
        | STGM_CREATE| STGM_SHARE_EXCLUSIVE | STGM_DELETEONRELEASE
        , 0, &stm.pstg);

    if (FAILED(hr))
        return;

    m_pObj->QueryInterface(IID_IPersistStorage, (PPVOID)&pIPS);

    if (NOERROR==pIPS->IsDirty())
        {
        OleSave(pIPS, stm.pstg, FALSE);
        pIPS->SaveCompleted(NULL);
        }
    else
        m_pIStorage->CopyTo(0, NULL, NULL, stm.pstg);

    pIPS->Release();

    //stm.pstg now has a copy, so stuff it away.
    cf=RegisterClipboardFormat(CFSTR_EMBEDDEDOBJECT);
    SETDefFormatEtc(fe, cf, TYMED_ISTORAGE);

    if (SUCCEEDED(pIDataObject->SetData(&fe, &stm, TRUE)))
        *pFE=fe;
    else
        ReleaseStgMedium(&stm);

    stm.tymed=TYMED_HGLOBAL;

    /*
     * You want to make sure that if this object is iconic, that you
     * create the object descriptor with DVASPECT_ICON instead of
     * the more typical DVASPECT_CONTENT.  Also remember that
     * the pick point is in HIMETRIC.
     */
    XformSizeInPixelsToHimetric(NULL, (LPSIZEL)pptl, (LPSIZEL)&ptl);

    SETSIZEL(szl, (10*(m_rcl.right-m_rcl.left))
        , (10 * (m_rcl.bottom-m_rcl.top)));

    stm.hGlobal=INOLE_ObjectDescriptorFromOleObject
        (m_pIOleObject, m_fe.dwAspect, ptl, &szl);

    cf=RegisterClipboardFormat(CFSTR_OBJECTDESCRIPTOR);
    SETDefFormatEtc(fe, cf, TYMED_HGLOBAL);

    if (FAILED(pIDataObject->SetData(&fe, &stm, TRUE)))
        ReleaseStgMedium(&stm);

    return;
    }

//CHAPTER21MOD
/*
 * CTenant::CopyLinkedObject
 *
 * Purpose:
 *  Copies an linked object to the given data object (via SetData,
 *  assuming this is a data transfer object for clipboard/drag-drop)
 *  if that's what we're holding.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT in which to store the copy.
 *  pFE             LPFORMATETC into which to copy CF_LINKSOURCE
 *                  if we put that in the data object.
 *  pptl            PPOINTL to the pick point (NULL outside of
 *                  drag-drop);
 *
 * Return Value:
 *  None
 */

void CTenant::CopyLinkedObject(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, PPOINTL pptl)
    {
    STGMEDIUM           stm;
    FORMATETC           fe;
    HRESULT             hr;
    UINT                cf;
    POINTL              ptl;
    LPMONIKER           pmk;
    CLSID               clsID;
    LPTSTR              psz=NULL;
    SIZEL               szl;

    //Can only copy links to embeddings from here.
    if (TENANTTYPE_EMBEDDEDOBJECT!=m_tType || NULL==m_pIOleObject)
        return;

    //If we don't have a full moniker, no linking allowed
    if (NULL==m_pmk)
        return;

    //If the object doesn't support this, return.
    if (OLEMISC_CANTLINKINSIDE & m_grfMisc)
        return;

    if (NULL==pptl)
        {
        SETPOINTL(ptl, 0, 0);
        pptl=&ptl;
        }

    /*
     * We need to get CFSTR_LINKSOURCE, but the server may not be
     * running, in which case we just grab the moniker and CLSID
     * for this object and call INOLE_GetLinkSourceData.
     */

    m_pIOleObject->GetUserClassID(&clsID);
    hr=m_pIOleObject->GetMoniker(0, OLEWHICHMK_OBJFULL, &pmk);

    if (FAILED(hr))
        return;

    stm.pUnkForRelease=NULL;
    stm.tymed=TYMED_NULL;
    cf=RegisterClipboardFormat(CFSTR_LINKSOURCE);
    SETDefFormatEtc(fe, cf, TYMED_ISTREAM);
    hr=INOLE_GetLinkSourceData(pmk, &clsID, &fe, &stm);

    if (FAILED(hr))
        {
        pmk->Release();
        return;
        }

    //Send it to the data object for transfer
    if (SUCCEEDED(pIDataObject->SetData(&fe, &stm, TRUE)))
        *pFE=fe;
    else
        ReleaseStgMedium(&stm);

    XformSizeInPixelsToHimetric(NULL, (LPSIZEL)pptl, (LPSIZEL)&ptl);

    SETSIZEL(szl, (10*(m_rcl.right-m_rcl.left))
        , (10 * (m_rcl.bottom-m_rcl.top)));

    stm.hGlobal=INOLE_ObjectDescriptorFromOleObject
        (m_pIOleObject, m_fe.dwAspect, ptl, &szl);

    //Better set these properly or errors occur.
    stm.tymed=TYMED_HGLOBAL;
    stm.pUnkForRelease=NULL;

    cf=RegisterClipboardFormat(CFSTR_LINKSRCDESCRIPTOR);
    SETDefFormatEtc(fe, cf, TYMED_HGLOBAL);

    if (FAILED(pIDataObject->SetData(&fe, &stm, TRUE)))
        ReleaseStgMedium(&stm);

    return;
    }
//End CHAPTER21MOD

/*
 * CTenant::ShowObjectType
 *
 * Purpose:
 *  Tells the object to switch on or off an indication of whether
 *  it is linked or embedded.
 *
 * Parameters:
 *  fShow           BOOL indicating to show the type (TRUE) or
 *                  not (FALSE)
 *
 * Return Value:
 *  None
 */

void CTenant::ShowObjectType(BOOL fShow)
    {
    BOOL        fWasShow;
    DWORD       dwState;
    RECT        rc;
    HDC         hDC;

    fWasShow=(BOOL)(TENANTSTATE_SHOWTYPE & m_dwState);

    dwState=m_dwState & ~TENANTSTATE_SHOWTYPE;
    m_dwState=dwState | ((fShow) ? TENANTSTATE_SHOWTYPE : 0);

    /*
     * If this wasn't previously shown, just add the line,
     * otherwise repaint.
     */
    if (!fWasShow && fShow)
        {
        RECTFROMRECTL(rc, m_rcl);
        RectConvertMappings(&rc, NULL, TRUE);
        OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

        hDC=GetDC(m_hWnd);
        UIShowObject(&rc, hDC, (TENANTTYPE_LINKEDOBJECT==m_tType));
        ReleaseDC(m_hWnd, hDC);
        }

    if (fWasShow && !fShow)
        Repaint();

    return;
    }

/*
 * CTenant::NotifyOfRename
 *
 * Purpose:
 *  Instructs the tenant that the document was saved under a
 *  different name.  In order to keep the right compound document
 *  user interface, this tenant needs to tell its object through
 *  IOleObject::SetHostNames.
 *
 * Parameters:
 *  pszFile         LPTSTR of filename.
 *  pmkFile         LPMONIKER of the new filename.  If this and
 *                  pmkPage are NULL then nothing happens with
 *                  monikers.
 *  pmkPage         LPMONIKER of the page we're in.
 *
 * Return Value:
 *  None
 */

//CHAPTER21MOD
void CTenant::NotifyOfRename(LPTSTR pszFile, LPMONIKER pmkFile
    , LPMONIKER pmkPage)
//End CHAPTER21MOD
    {
    TCHAR       szObj[40];
    TCHAR       szApp[40];

    if (NULL==m_pIOleObject)
        return;

    if (TEXT('\0')==*pszFile)
        {
        LoadString(m_pPG->m_hInst, IDS_UNTITLED, szObj
            , sizeof(szObj));
        }
    else
        {
        GetFileTitle(pszFile, szObj, sizeof(szObj));

       #ifndef WIN32
        //Force filenames to uppercase in DOS versions.
        AnsiUpper(szObj);
       #endif
        }

    LoadString(m_pPG->m_hInst, IDS_CAPTION, szApp, sizeof(szApp));
   #ifdef WIN32ANSI
    OLECHAR     szObjW[40], szAppW[40];

    MultiByteToWideChar(CP_ACP, 0, szObj, -1, szObjW, 40);
    MultiByteToWideChar(CP_ACP, 0, szApp, -1, szAppW, 40);
    m_pIOleObject->SetHostNames(szAppW, szObjW);
   #else
    m_pIOleObject->SetHostNames(szApp, szObj);
   #endif

    //CHAPTER21MOD
    if (NULL!=pmkFile)
        {
        ReleaseInterface(m_pmkFile);
        m_pmkFile=pmkFile;
        m_pmkFile->AddRef();

        m_pIOleObject->SetMoniker(OLEWHICHMK_CONTAINER, pmkFile);
        }

    if (NULL!=pmkFile && NULL!=pmkPage)
        {
        LPMONIKER   pmkTenant=NULL;
        LPMONIKER   pmkRel=NULL;
        HRESULT     hr;

        //Create the moniker for this tenant.
       #ifdef WIN32ANSI
        GetStorageName(szObjW);
        WideCharToMultiByte(CP_ACP, 0, szObjW, -1, szObj, 40
           , NULL, NULL);
       #else
        GetStorageName(szObj);
       #endif
        hr=CreateItemMoniker(TEXT("!"), szObj, &pmkTenant);

        if (SUCCEEDED(hr))
            {
            //Create the relative moniker, i.e. no pathname.
            hr=pmkPage->ComposeWith(pmkTenant, FALSE, &pmkRel);
            pmkTenant->Release();

            //Hold on to the relative moniker
            ReleaseInterface(m_pmk);
            m_pmk=pmkRel;

            if (SUCCEEDED(hr))
                m_pIOleObject->SetMoniker(OLEWHICHMK_OBJREL, pmkRel);
            }
        }

    /*
     * Note that this tenant is already registered as running
     * because the page registered a wildcard.
     */
    //End CHAPTER21MOD

    return;
    }

/*
 * CTenant::Activate
 *
 * Purpose:
 *  Activates a verb on the object living in the tenant.  Does
 *  nothing for static objects.
 *
 * Parameters:
 *  iVerb           LONG of the verb to execute.
 *
 * Return Value:
 *  BOOL            TRUE if the object changed due to this verb
 *                  execution.
 */

BOOL CTenant::Activate(LONG iVerb)
    {
    RECT        rc, rcH;
    CHourglass *pHour;
    SIZEL       szl;

    //Can't activate statics.
    if (TENANTTYPE_STATIC==m_tType || NULL==m_pIOleObject)
        {
        MessageBeep(0);
        return FALSE;
        }

    RECTFROMRECTL(rc, m_rcl);
    RectConvertMappings(&rc, NULL, TRUE);
    XformRectInPixelsToHimetric(NULL, &rc, &rcH);

    pHour=new CHourglass;

    //Get the server running first, then do a SetExtent, then show it
    OleRun(m_pIOleObject);

    if (m_fSetExtent)
        {
        SETSIZEL(szl, rcH.right-rcH.left, rcH.top-rcH.bottom);
        m_pIOleObject->SetExtent(m_fe.dwAspect, &szl);
        m_fSetExtent=FALSE;
        }

    m_pIOleObject->DoVerb(iVerb, NULL, m_pImpIOleClientSite, 0
        , m_hWnd, &rcH);

    delete pHour;

    //If object changes, IAdviseSink::OnViewChange will see it.
    return FALSE;
    }

/*
 * CTenant::Draw
 *
 * Purpose:
 *  Draws the tenant in its rectangle on the given hDC.  We assume
 *  the DC is already set up for the mapping mode in which our
 *  rectangle is expressed, since the Page we're in tells us both
 *  the rect and the hDC.
 *
 * Parameters:
 *  hDC             HDC in which to draw.  Could be a metafile,
 *                  memory DC, screen, or printer.
 *  ptd             DVTARGETDEVICE * describing the device.
 *  hIC             HDC holding an information context (printing).
 *  xOff, yOff      int offsets for the page in lometric
 *  fNoColor        BOOL indicating if we should do B & W
 *  fPrinter        BOOL indicating if we should render for a
 *                  printer.
 *
 * Return Value:
 *  None
 */

void CTenant::Draw(HDC hDC, DVTARGETDEVICE *ptd, HDC hIC
    , int xOff, int yOff, BOOL fNoColor, BOOL fPrinter)
    {
    HRESULT         hr;
    RECT            rc;
    RECTL           rcl;
    UINT            uMM;

    RECTFROMRECTL(rc, m_rcl);
    OffsetRect(&rc, -xOff, -yOff);
    RECTLFROMRECT(rcl, rc);

    //Repaint erases the rectangle to insure full object cleanup
    if (!fNoColor && !fPrinter)
        {
        COLORREF    cr;
        cr=SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
        ExtTextOut(hDC, rc.left, rc.top, ETO_OPAQUE, &rc, NULL
            , 0, NULL);
        SetBkColor(hDC, cr);
        }

    //We have to use Draw since we have a target device and IC.
    hr=m_pIViewObject2->Draw(m_fe.dwAspect, -1, NULL, ptd, hIC, hDC
        , &rcl, NULL, NULL, 0);

    /*
     * If Draw failed, then perhaps it couldn't work for the device,
     * so try good old OleDraw as a last resort.  The code will
     * generally be OLE_E_BLANK.
     */
    if (FAILED(hr))
        OleDraw(m_pObj, m_fe.dwAspect, hDC, &rc);

    if (!fPrinter)
        {
        /*
         * Draw sizing handles to show the selection state.  We
         * convert things to MM_TEXT since that's what this
         * function expects.
         */
        RectConvertMappings(&rc, NULL, TRUE);
        uMM=SetMapMode(hDC, MM_TEXT);

        if (TENANTSTATE_SELECTED & m_dwState)
            {
            UIDrawHandles(&rc, hDC, UI_HANDLES_INSIDE
                | UI_HANDLES_NOBORDER | UI_HANDLES_USEINVERSE
                , CXYHANDLE, TRUE);
            }

        if (TENANTSTATE_OPEN & m_dwState)
            UIDrawShading(&rc, hDC, UI_SHADE_FULLRECT, 0);

        //Distinguish linked and embedded objects.
        if (TENANTSTATE_SHOWTYPE & m_dwState)
            {
            UIShowObject(&rc, hDC
                , (TENANTTYPE_LINKEDOBJECT==m_tType));
            }

        uMM=SetMapMode(hDC, uMM);
        }

    return;
    }

/*
 * CTenant::Repaint
 * CTenant::Invalidate
 *
 * Purpose:
 *  Repaints the tenant where it lies or invalidates its area
 *  for later repainting.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CTenant::Repaint(void)
    {
    RECT        rc;
    HDC         hDC;

    /*
     * We might be asked to repaint from
     * IOleClientSite::OnShowWindow after we've switched pages if
     * our server was running. This check on m_cOpens prevents that.
     */
    if (0==m_cOpens || !m_fRepaintEnabled)
        return;

    hDC=GetDC(m_hWnd);
    SetRect(&rc, m_pPG->m_xPos, m_pPG->m_yPos, 0, 0);
    RectConvertMappings(&rc, NULL, FALSE);

    SetMapMode(hDC, MM_LOMETRIC);
    Draw(hDC, NULL, NULL, rc.left, rc.top, FALSE, FALSE);

    ReleaseDC(m_hWnd, hDC);
    return;
    }

void CTenant::Invalidate(void)
    {
    RECTL       rcl;
    RECT        rc;

    RectGet(&rcl, TRUE);
    RECTFROMRECTL(rc, rcl);

    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);
    InvalidateRect(m_hWnd, &rc, FALSE);

    return;
    }

/*
 * CTenant::FIsSelected
 *
 * Purpose:
 *  Returns the selection state of this tenant.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if selected, FALSE otherwise.
 */

BOOL CTenant::FIsSelected(void)
    {
    return (BOOL)(m_dwState & TENANTSTATE_SELECTED);
    }

/*
 * CTenant::ConvertToStatic
 *
 * Purpose:
 *  Changes the object that lives in this tenant to a static one.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CTenant::ConvertToStatic(void)
    {
    /*
     * If you SetSourceMoniker in IOleLink to NULL, then the link is
     * gone as far as OLE is concerned.  You only need to make sure
     * the user doesn't have access to other functionality for this
     * object, which we insure by changing our internal type.  We
     * set this on loading if GetSourceMoniker returns NULL.
     */
    m_tType=TENANTTYPE_STATIC;
    return TRUE;
    }

/*
 * CTenant::ObjectClassFormatAndIcon
 *
 * Purpose:
 *  Helper function for CPage::ConvertObject to retrieve necessary
 *  information about the object.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store the CLSID.
 *  pwFormat        LPWORD in which to store the clipboard format
 *                  used.
 *  ppszType        LPTSTR * in which to return a pointer to a
 *                  type string.
 *  phMetaIcon      HGLOBAL * in which to return the metafile
 *                  icon currently in use.
 *
 * Return Value:
 *  None
 */

void CTenant::ObjectClassFormatAndIcon(LPCLSID pClsID
    , LPWORD pwFormat, LPTSTR *ppszType, HGLOBAL *phMetaIcon
    , LPTSTR *ppszLabel)
    {
    HRESULT         hr;
    TCHAR           szType[128];
    LPDATAOBJECT    pIDataObject;
    FORMATETC       fe;
    STGMEDIUM       stm;

    if (TENANTTYPE_EMBEDDEDOBJECT!=m_tType || NULL==m_pIOleObject)
        return;

    if (NULL==pClsID || NULL==pwFormat || NULL==ppszType
        || NULL==phMetaIcon)
        return;

    /*
     * For embedded objects get the real CLSID of the object and
     * its format string.  If this fails then we can try to ask
     * the object, or we can look in the registry.
     */

    hr=ReadClassStg(m_pIStorage, pClsID);

    if (FAILED(hr))
        {
        hr=m_pIOleObject->GetUserClassID(pClsID);

        if (FAILED(hr))
            *pClsID=CLSID_NULL;
        }

    hr=ReadFmtUserTypeStg(m_pIStorage, pwFormat, ppszType);

    if (FAILED(hr))
        {
        *pwFormat=0;
        *ppszType=NULL;

        if (INOLE_GetUserTypeOfClass(*pClsID, 0, szType
            , sizeof(szType)))
            {
            *ppszType=INOLE_CopyString(szType);
            }
        }

    /*
     * Try to get the AuxUserType from the registry, using
     * the short version (registered under AuxUserType\2).
     * If that fails, just copy *ppszType.
     */
    *ppszLabel=NULL;

    if (INOLE_GetUserTypeOfClass(*pClsID, 2, szType
        , sizeof(szType)))
        {
        *ppszLabel=INOLE_CopyString(szType);
        }
    else
        *ppszLabel=INOLE_CopyString(*ppszType);

    //Get the icon for this thing, if we're iconic.
    *phMetaIcon=NULL;

    hr=m_pObj->QueryInterface(IID_IDataObject
        , (PPVOID)&pIDataObject);

    if (SUCCEEDED(hr))
        {
        SETFormatEtc(fe, CF_METAFILEPICT, DVASPECT_ICON, NULL
            , TYMED_MFPICT, -1);
        hr=pIDataObject->GetData(&fe, &stm);
        pIDataObject->Release();

        if (SUCCEEDED(hr))
            *phMetaIcon=stm.hGlobal;
        else
            *phMetaIcon=OleGetIconOfClass(*pClsID, NULL, TRUE);
        }

    return;
    }

/*
 * CTenant::SwitchOrUpdateAspect
 *
 * Purpose:
 *  Switches between DVASPECT_CONTENT and DVASPECT_ICON
 *
 * Parameters:
 *  hMetaIcon       HGLOBAL to the new icon if we're changing the
 *                  icon or switching to DVASPECT_ICON.  NULL to
 *                  change back to content.
 *  fPreserve       BOOL indicating if we're to preserve the old
 *                  aspect after changing.
 *
 * Return Value:
 *  BOOL            TRUE if anything changed, FALSE otherwise.
 */

BOOL CTenant::SwitchOrUpdateAspect(HGLOBAL hMetaIcon
    , BOOL fPreserve)
    {
    HRESULT     hr;
    DWORD       dwAspect;
    BOOL        fUpdate=FALSE;

    //Nothing to do if we're content already and there's no icon.
    if (NULL==hMetaIcon && DVASPECT_CONTENT==m_fe.dwAspect)
        return FALSE;

    //If we're iconic already, just cache the new icon
    if (NULL!=hMetaIcon && DVASPECT_ICON==m_fe.dwAspect)
        hr=INOLE_SetIconInCache(m_pIOleObject, hMetaIcon);
    else
        {
        //Otherwise, switch between iconic and content.
        dwAspect=(NULL==hMetaIcon) ? DVASPECT_CONTENT : DVASPECT_ICON;

        /*
         * Switch between aspects, where dwAspect has the new one
         * and m_fe.dwAspect will be changed in the process.
         */
        hr=INOLE_SwitchDisplayAspect(m_pIOleObject
            , &m_fe.dwAspect, dwAspect, hMetaIcon, !fPreserve
            , TRUE, m_pImpIAdviseSink, &fUpdate);

        if (SUCCEEDED(hr))
            {
            //Update MiscStatus for the new aspect
            m_pIOleObject->GetMiscStatus(m_fe.dwAspect, &m_grfMisc);

            if (fUpdate)
                m_pIOleObject->Update();    //This repaints.
            }
        }

    //If we switched, update our extents.
    if (SUCCEEDED(hr))
        {
        SIZEL       szl;

        m_pIOleObject->GetExtent(m_fe.dwAspect, &szl);

        if (0 > szl.cy)
            szl.cy=-szl.cy;

        //Convert HIMETRIC absolute units to our LOMETRIC mapping
        if (0!=szl.cx && 0!=szl.cy)
            SETSIZEL(szl, szl.cx/10, -szl.cy/10);

        Invalidate();                   //Remove old aspect
        SizeSet(&szl, FALSE, FALSE);    //Change size
        Repaint();                      //Paint the new one
        }

    return SUCCEEDED(hr);
    }

/*
 * CTenant::EnableRepaint
 *
 * Purpose:
 *  Toggles whether the Repaint function does anything.  This
 *  is used during conversion/emulation of an object to disable
 *  repaints until the new object can be given the proper extents.
 *
 * Parameters:
 *  fEnable         TRUE to enable repaints, FALSE to disable.
 *
 * Return Value:
 *  None
 */

void CTenant::EnableRepaint(BOOL fEnable)
    {
    m_fRepaintEnabled=fEnable;
    return;
    }

/*
 * CTenant::ObjectGet
 *
 * Purpose:
 *  Retrieves the LPUNKNOWN of the object in use by this tenant
 *
 * Parameters:
 *  ppUnk           LPUNKNOWN * in which to return the object
 *                  pointer.
 *
 * Return Value:
 *  None
 */

void CTenant::ObjectGet(LPUNKNOWN *ppUnk)
    {
    if (NULL!=ppUnk)
        {
        *ppUnk=m_pObj;
        m_pObj->AddRef();
        }

    return;
    }

/*
 * CTenant::FormatEtcGet
 *
 * Purpose:
 *  Retrieves the FORMATETC in use by this tenant
 *
 * Parameters:
 *  pFE             LPFORMATETC in which to store the information.
 *  fPresentation   BOOL indicating if we want the real format or
 *                  that of the presentation.
 *
 * Return Value:
 *  None
 */

void CTenant::FormatEtcGet(LPFORMATETC pFE, BOOL fPresentation)
    {
    if (NULL!=pFE)
        {
        *pFE=m_fe;

        //If there is no format, use metafile (for embedded objects)
        if (fPresentation || 0==pFE->cfFormat)
            {
            //Don't mess with dwAspect; might be icon or content.
            pFE->cfFormat=CF_METAFILEPICT;
            pFE->tymed=TYMED_MFPICT;
            }
        }

    return;
    }

/*
 * CTenant::SizeGet
 * CTenant::SizeSet
 * CTenant::RectGet
 * CTenant::RectSet
 *
 * Purpose:
 *  Returns or sets the size/position of the object contained here.
 *
 * Parameters:
 *  pszl/prcl       LPSIZEL (Size) or LPRECTL (Rect) with the
 *                  extents of interest.  In Get situations,
 *                  this will receive the extents; in Set it
 *                  contains the extents.
 *  fDevice         BOOL indicating that pszl/prcl is expressed
 *                  in device units.  Otherwise it's LOMETRIC.
 *  fInformObj      (Set Only) BOOL indicating if we need to inform
 *                  the object all.
 *
 * Return Value:
 *  None
 */

void CTenant::SizeGet(LPSIZEL pszl, BOOL fDevice)
    {
    if (!fDevice)
        {
        pszl->cx=m_rcl.right-m_rcl.left;
        pszl->cy=m_rcl.bottom-m_rcl.top;
        }
    else
        {
        RECT        rc;

        SetRect(&rc, (int)(m_rcl.right-m_rcl.left)
            , (int)(m_rcl.bottom-m_rcl.top), 0, 0);

        RectConvertMappings(&rc, NULL, TRUE);

        pszl->cx=(long)rc.left;
        pszl->cy=(long)rc.top;
        }

    return;
    }

void CTenant::SizeSet(LPSIZEL pszl, BOOL fDevice, BOOL fInformObj)
    {
    SIZEL           szl;

    if (!fDevice)
        {
        szl=*pszl;
        m_rcl.right =pszl->cx+m_rcl.left;
        m_rcl.bottom=pszl->cy+m_rcl.top;
        }
    else
        {
        RECT        rc;

        SetRect(&rc, (int)pszl->cx, (int)pszl->cy, 0, 0);
        RectConvertMappings(&rc, NULL, FALSE);

        m_rcl.right =(long)rc.left+m_rcl.left;
        m_rcl.bottom=(long)rc.top+m_rcl.top;

        SETSIZEL(szl, (long)rc.left, (long)rc.top);
        }

    //Tell OLE that this object was resized.
    if (NULL!=m_pIOleObject && fInformObj)
        {
        HRESULT     hr;
        BOOL        fRun=FALSE;

        //Convert our LOMETRIC into HIMETRIC by *=10
        szl.cx*=10;
        szl.cy*=-10;    //Our size is stored negative.

        /*
         * If the MiscStatus bit of OLEMISC_RECOMPOSEONRESIZE
         * is set, then we need to run the object before calling
         * SetExtent to make sure it has a real chance to
         * re-render the object.  We have to update and close
         * the object as well after this happens.
         */

        if (OLEMISC_RECOMPOSEONRESIZE & m_grfMisc)
            {
            if (!OleIsRunning(m_pIOleObject))
                {
                OleRun(m_pIOleObject);
                fRun=TRUE;
                }
            }

        hr=m_pIOleObject->SetExtent(m_fe.dwAspect, &szl);

        /*
         * If the object is not running and it does not have
         * RECOMPOSEONRESIZE, then SetExtent fails.  Make
         * sure that we call SetExtent again (by just calling
         * SizeSet here again) when we next run the object.
         */
        if (SUCCEEDED(hr))
            {
            m_fSetExtent=FALSE;

            if (fRun)
                {
                m_pIOleObject->Update();
                m_pIOleObject->Close(OLECLOSE_SAVEIFDIRTY);
                }
            }
        else
            {
            if (OLE_E_NOTRUNNING==GetScode(hr))
                m_fSetExtent=TRUE;
            }
        }

    return;
    }

void CTenant::RectGet(LPRECTL prcl, BOOL fDevice)
    {
    if (!fDevice)
        *prcl=m_rcl;
    else
        {
        RECT        rc;

        RECTFROMRECTL(rc, m_rcl);
        RectConvertMappings(&rc, NULL, TRUE);
        RECTLFROMRECT(*prcl, rc);
        }

    return;
    }

void CTenant::RectSet(LPRECTL prcl, BOOL fDevice, BOOL fInformObj)
    {
    SIZEL   szl;
    LONG    cx, cy;

    cx=m_rcl.right-m_rcl.left;
    cy=m_rcl.bottom-m_rcl.top;

    if (!fDevice)
        m_rcl=*prcl;
    else
        {
        RECT        rc;

        RECTFROMRECTL(rc, *prcl);
        RectConvertMappings(&rc, NULL, FALSE);
        RECTLFROMRECT(m_rcl, rc);
        }

    /*
     * Tell ourselves that the size changed, if it did.  SizeSet
     * will call IOleObject::SetExtent for us.
     */
    if ((m_rcl.right-m_rcl.left)!=cx || (m_rcl.bottom-m_rcl.top)!=cy)
        {
        SETSIZEL(szl, m_rcl.right-m_rcl.left, m_rcl.bottom-m_rcl.top);
        SizeSet(&szl, FALSE, fInformObj);
        }

    return;
    }

/*
 * CTenant::CreateStatic
 * (Protected)
 *
 * Purpose:
 *  Creates a new static bitmap or metafile object for this tenant
 *  using a freeloading method allowing us to specify exactly which
 *  type of data we want to paste since OleCreateStaticFromData
 *  doesn't.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *  pFE             LPFORMATETC describing the format to paste.
 *  ppObj           LPUNKNOWN * into which we store the
 *                  object pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, error code otherwise.
 */

HRESULT CTenant::CreateStatic(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, LPUNKNOWN *ppObj)
    {
    HRESULT             hr;
    STGMEDIUM           stm;
    LPUNKNOWN           pIUnknown;
    LPOLECACHE          pIOleCache;
    LPPERSISTSTORAGE    pIPersistStorage;
    CLSID               clsID;

    *ppObj=NULL;

    //Try to get the data desired as specified in pFE->cfFormat
    hr=pIDataObject->GetData(pFE, &stm);

    if (FAILED(hr))
        return hr;

    //Create the object to handle this data.
    if (CF_METAFILEPICT==pFE->cfFormat)
        clsID=CLSID_Picture_Metafile;
    else
        clsID=CLSID_Picture_Dib;

    hr=CreateDataCache(NULL, clsID, IID_IUnknown
        , (PPVOID)&pIUnknown);

    if (FAILED(hr))
        {
        ReleaseStgMedium(&stm);
        return hr;
        }

    m_clsID=clsID;

    //Stuff the data into the object
    pIUnknown->QueryInterface(IID_IPersistStorage
        , (PPVOID)&pIPersistStorage);
    pIPersistStorage->InitNew(m_pIStorage);

    //Now that we have the cache object, shove the data into it.
    pIUnknown->QueryInterface(IID_IOleCache, (PPVOID)&pIOleCache);
    pIOleCache->Cache(pFE, ADVF_PRIMEFIRST, NULL);

    hr=pIOleCache->SetData(pFE, &stm, TRUE);
    pIOleCache->Release();

    //Insure there is a persistent copy on the disk
    WriteClassStg(m_pIStorage, m_clsID);
    pIPersistStorage->Save(m_pIStorage, TRUE);
    pIPersistStorage->SaveCompleted(NULL);
    pIPersistStorage->Release();

    //The cache owns this now.
    ReleaseStgMedium(&stm);

    if (FAILED(hr))
        pIUnknown->Release();
    else
        *ppObj=pIUnknown;

    return hr;
    }


TENANT.H

/*
 * TENANT.H
 * Patron Chapter 21
 *
 * Definitions and function prototypes for the CTenant class
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _TENANT_H_
#define _TENANT_H_

class CImpIOleClientSite : public IOleClientSite
    {
    protected:
        ULONG               m_cRef;
        class CTenant      *m_pTen;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIOleClientSite(class CTenant *, LPUNKNOWN);
        ~CImpIOleClientSite(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP SaveObject(void);
        STDMETHODIMP GetMoniker(DWORD, DWORD, LPMONIKER *);
        STDMETHODIMP GetContainer(LPOLECONTAINER *);
        STDMETHODIMP ShowObject(void);
        STDMETHODIMP OnShowWindow(BOOL);
        STDMETHODIMP RequestNewObjectLayout(void);
    };

typedef CImpIOleClientSite *PCImpIOleClientSite;

class CImpIAdviseSink : public IAdviseSink
    {
    protected:
        ULONG               m_cRef;
        class CTenant      *m_pTen;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIAdviseSink(class CTenant *, LPUNKNOWN);
        ~CImpIAdviseSink(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(void)  OnDataChange(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP_(void)  OnViewChange(DWORD, LONG);
        STDMETHODIMP_(void)  OnRename(LPMONIKER);
        STDMETHODIMP_(void)  OnSave(void);
        STDMETHODIMP_(void)  OnClose(void);
    };

typedef CImpIAdviseSink *PCImpIAdviseSink;

/*
 * Tenant class describing an individual piece of data in a page.
 * It knows where it sits, what object is inside of it, and what
 * its idenitifer is such that it can find it's storage within a
 * page.
 */

//Patron Objects clipboard format
typedef struct tagPATRONOBJECT
    {
    POINTL      ptl;        //Location of object
    POINTL      ptlPick;    //Pick point from drag-drop operation
    SIZEL       szl;        //Extents of object (absolute)
    FORMATETC   fe;         //Actual object format
    } PATRONOBJECT, *PPATRONOBJECT;

//Values for hit-testing, drawing, and resize-tracking tenants
#define CXYHANDLE       5

//Tenant types (not persistent, but determined at load time)
typedef enum
    {
    TENANTTYPE_NULL=0,
    TENANTTYPE_STATIC,
    TENANTTYPE_EMBEDDEDOBJECT,
    TENANTTYPE_EMBEDDEDFILE,
    TENANTTYPE_EMBEDDEDOBJECTFROMDATA,
    TENANTTYPE_LINKEDOBJECT,
    TENANTTYPE_LINKEDFILE,
    TENANTTYPE_LINKEDOBJECTFROMDATA
    } TENANTTYPE, *PTENANTTYPE;

//State flags
#define TENANTSTATE_DEFAULT      0x00000000
#define TENANTSTATE_SELECTED     0x00000001
#define TENANTSTATE_OPEN         0x00000002
#define TENANTSTATE_SHOWTYPE     0x00000004

/*
 * Persistent information we need to save for each tenant, which is
 * done in the tenant list instead of with each tenant.  Since this
 * is a small structure it's best not to blow another stream for it
 * (overhead).
 */
typedef struct tagTENANTINFO
    {
    DWORD       dwID;
    RECTL       rcl;
    FORMATETC   fe;             //Excludes ptd
    short       fSetExtent;     //Call IOleObject::SetExtent on Run
    } TENANTINFO, *PTENANTINFO;

class CTenant : public IUnknown
    {
    friend CImpIOleClientSite;
    friend CImpIAdviseSink;

    private:
        HWND            m_hWnd;         //Pages window
        DWORD           m_dwID;         //Persistent DWORD ID
        DWORD           m_cOpens;       //Count calls to Open

        BOOL            m_fInitialized; //Something here?
        LPUNKNOWN       m_pObj;         //The object here
        LPSTORAGE       m_pIStorage;    //Sub-storage for tenant

        FORMATETC       m_fe;           //Used to create the object
        DWORD           m_dwState;      //State flags
        RECTL           m_rcl;          //Space of this object
        CLSID           m_clsID;        //Object class (for statics)
        BOOL            m_fSetExtent;   //Call SetExtent on next run

        class CPages   *m_pPG;          //Pages window

        TENANTTYPE      m_tType;            //Type identifier
        ULONG           m_cRef;             //We're an object now
        LPOLEOBJECT     m_pIOleObject;      //IOleObject on m_pObj
        LPVIEWOBJECT2   m_pIViewObject2;    //IViewObject2 on m_pObj
        ULONG           m_grfMisc;          //OLEMISC flags
        BOOL            m_fRepaintEnabled;  //No redundant paints

        //Our interfaces
        PCImpIOleClientSite m_pImpIOleClientSite;
        PCImpIAdviseSink    m_pImpIAdviseSink;

        //CHAPTER21MOD
        LPMONIKER       m_pmk;          //Relative moniker
        LPMONIKER       m_pmkFile;      //Container document moniker
        //End CHAPTER21MOD

    /*
     * This flag is used exculsively by the implementation of
     * IOleUILinkContainer on the page we're in for the Links
     * Dialog. Since we never use it ourselves, public here
     * is no big deal.
     */
    public:
        BOOL            m_fLinkAvail;

    protected:
        BOOL    ObjectInitialize(LPUNKNOWN, LPFORMATETC, DWORD);
        HRESULT CreateStatic(LPDATAOBJECT, LPFORMATETC
            , LPUNKNOWN *);

    public:
        CTenant(DWORD, HWND, CPages *);
        ~CTenant(void);

        //Gotta have an IUnknown for delegation
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        DWORD       GetID(void);
        UINT        GetStorageName(LPOLESTR);
        void        StorageGet(LPSTORAGE *);
        UINT        Create(TENANTTYPE, LPVOID, LPFORMATETC, PPOINTL
                        , LPSIZEL, LPSTORAGE, PPATRONOBJECT, DWORD);
        BOOL        Load(LPSTORAGE, PTENANTINFO);
        void        GetInfo(PTENANTINFO);
        BOOL        Open(LPSTORAGE);
        void        Close(BOOL);
        BOOL        Update(void);
        void        Destroy(LPSTORAGE);

        void        Select(BOOL);
        void        ShowAsOpen(BOOL);
        void        ShowYourself(void);
        void        AddVerbMenu(HMENU, UINT);
        TENANTTYPE  TypeGet(void);
        void        CopyEmbeddedObject(LPDATAOBJECT, LPFORMATETC
                        , PPOINTL);
        void        ShowObjectType(BOOL);

        //CHAPTER21MOD
        void        CopyLinkedObject(LPDATAOBJECT, LPFORMATETC
                        , PPOINTL);
        void        NotifyOfRename(LPTSTR, LPMONIKER, LPMONIKER);
        //End CHAPTER21MOD

        BOOL        Activate(LONG);
        void        Draw(HDC, DVTARGETDEVICE *, HDC, int, int
                        , BOOL, BOOL);
        void        Repaint(void);
        void        Invalidate(void);

        void        ObjectClassFormatAndIcon(LPCLSID, LPWORD
                        , LPTSTR *, HGLOBAL *, LPTSTR *);
        BOOL        SwitchOrUpdateAspect(HGLOBAL, BOOL);
        void        EnableRepaint(BOOL);

        BOOL        FIsSelected(void);
        BOOL        ConvertToStatic(void);

        void        ObjectGet(LPUNKNOWN *);
        void        FormatEtcGet(LPFORMATETC, BOOL);
        void        SizeGet(LPSIZEL, BOOL);
        void        SizeSet(LPSIZEL, BOOL, BOOL);
        void        RectGet(LPRECTL, BOOL);
        void        RectSet(LPRECTL, BOOL, BOOL);
    };

typedef CTenant *PCTenant;

//Return codes for Create
#define CREATE_FAILED               0
#define CREATE_GRAPHICONLY          1
#define CREATE_PLACEDOBJECT         2

#endif //_TENANT_H_


DLLPOLY.CPP

/*
 * DLLPOLY.CPP
 * Polyline Component Chapter 21
 *
 * Polyline component object used in CoCosmo that supports a custom
 * interface IPolyline.  Contains DLL entry code and the component
 * object exports DllGetClassObject and DllCanUnloadNow and the
 * class factory object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "polyline.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

//DLL Instance handle
HINSTANCE   g_hInst=0;

/*
 * LibMain32 (Win32)
 *
 * Purpose:
 *  Entry point for Win32 DLLs that calls the Win16 LibMain.
 */

#ifdef WIN32
BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    if (DLL_PROCESS_DETACH==ulReason)
        {
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    return (0!=LibMain(hInstance, 0,  0, NULL));
    }
#endif

/*
 * LibMain (also called from Win32 LibMain32)
 *
 * Purpose:
 *  DLL-specific entry point called from LibEntry.
 */

int PASCAL LibMain(HINSTANCE hInst, WORD wDataSeg
    , WORD cbHeapSize, LPSTR lpCmdLine)
    {
    WNDCLASS    wc;

    if (GetClassInfo(hInst, SZCLASSPOLYLINE, &wc))
        return (int)hInst;

   #ifndef WIN32
    if (0!=cbHeapSize)
        UnlockData(0);
   #endif

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.hInstance     = hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PolylineWndProc;
    wc.cbWndExtra    = CBPOLYLINEWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_CROSS);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPOLYLINE;

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

    g_hInst=hInst;
    return (int)hInst;
    }

/*
 * DllGetClassObject
 *
 * Purpose:
 *  Provides an IClassFactory for a given CLSID that this DLL is
 *  registered to support.  This DLL is placed under the CLSID
 *  in the registration database as the InProcServer.
 *
 * Parameters:
 *  clsID           REFCLSID that identifies the class factory
 *                  desired.  Since this parameter is passed this
 *                  DLL can handle any number of objects simply
 *                  by returning different class factories here
 *                  for different CLSIDs.
 *
 *  riid            REFIID specifying the interface the caller wants
 *                  on the class object, usually IID_ClassFactory.
 *
 *  ppv             PPVOID in which to return the interface
 *                  pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, otherwise an error code.
 */

HRESULT APIENTRY DllGetClassObject(REFCLSID rclsid
    , REFIID riid, PPVOID ppv)
    {
    if (CLSID_Polyline19!=rclsid)
        return ResultFromScode(E_FAIL);

    //Check that we can provide the interface
    if (IID_IUnknown!=riid && IID_IClassFactory!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Return our IClassFactory for Polyline objects
    *ppv=new CPolylineClassFactory;

    if (NULL==*ppv)
        return ResultFromScode(E_OUTOFMEMORY);

    //AddRef the object through any interface we return
    ((LPUNKNOWN)*ppv)->AddRef();
    g_cObj++;
    return NOERROR;
    }

/*
 * DllCanUnloadNow
 *
 * Purpose:
 *  Answers if the DLL can be freed, that is, if there are no
 *  references to anything this DLL provides.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if nothing is using us, FALSE otherwise.
 */

STDAPI DllCanUnloadNow(void)
    {
    SCODE   sc;

    //Our answer is whether there are any object or locks
    sc=(0L==g_cObj && 0L==g_cLock) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Polyline object to call when it gets destroyed.
 *  Since we're in a DLL we only track the number of objects here
 *  letting DllCanUnloadNow take care of the rest.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;
    return;
    }

/*
 * CPolylineClassFactory::CPolylineClassFactory
 *
 * Purpose:
 *  Constructor for an object supporting an IClassFactory that
 *  instantiates Polyline objects.
 *
 * Parameters:
 *  None
 */

CPolylineClassFactory::CPolylineClassFactory(void)
    {
    m_cRef=0L;
    return;
    }

/*
 * CPolylineClassFactory::~CPolylineClassFactory
 *
 * Purpose:
 *  Destructor for a CPolylineClassFactory object.  This will be
 *  called when we Release the object to a zero reference count.
 */

CPolylineClassFactory::~CPolylineClassFactory(void)
    {
    return;
    }

/*
 * CPolylineClassFactory::QueryInterface
 * CPolylineClassFactory::AddRef
 * CPolylineClassFactory::Release
 */

STDMETHODIMP CPolylineClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CPolylineClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPolylineClassFactory::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    ObjectDestroyed();
    return 0L;
    }

/*
 * CPolylineClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a Polyline object.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we
 *                  are being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_NOINTERFACE
 *                  if we cannot support the requested interface.
 */

STDMETHODIMP CPolylineClassFactory::CreateInstance
    (LPUNKNOWN pUnkOuter, REFIID riid, PPVOID ppvObj)
    {
    PCPolyline          pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //Verify that a controlling unknown asks for IUnknown
    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Create the object.  This also creates a window.
    pObj=new CPolyline(pUnkOuter, ObjectDestroyed, g_hInst);

    if (NULL==pObj)
        return hr;

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    //Kill the object if initial creation or Init failed.
    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

/*
 * CPolylineClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the DLL.  If the lock
 *  count goes to zero and there are no objects, the DLL is allowed
 *  to unload.  See DllCanUnloadNow.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CPolylineClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        g_cLock--;

    return NOERROR;
    }


ICONNPT.CPP

/*
 * ICONNPT.CPP
 * Polyline Component Chapter 21
 *
 * Implementation of CImpIConnectionPoint for the Polyline object
 * as well as CConnectionPoint.
 *
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIConnPtCont:CImpIConnPtCont
 * CImpIConnPtCont::~CImpIConnPtCont
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIConnPtCont::CImpIConnPtCont(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIConnPtCont::~CImpIConnPtCont(void)
    {
    return;
    }

/*
 * CImpIConnPtCont::QueryInterface
 * CImpIConnPtCont::AddRef
 * CImpIConnPtCont::Release
 */

STDMETHODIMP CImpIConnPtCont::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIConnPtCont::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIConnPtCont::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIConnPtCont::EnumConnectionPoints
 *
 * Purpose:
 *  Not implemented.
 *
 * Return Value:
 *  HRESULT         E_NOTIMPL
 */

STDMETHODIMP CImpIConnPtCont::EnumConnectionPoints
    (LPENUMCONNECTIONPOINTS *ppEnum)
    {
    *ppEnum=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIConnPtCont::FindConnectionPoint
 *
 * Purpose:
 *  Returns a pointer to the IConnectionPoint for a given
 *  outgoing IID.
 *
 * Parameters:
 *  riid            REFIID of the outgoing interface for which
 *                  a connection point is desired.
 *  ppCP            IConnectionPoint ** in which to return
 *                  the pointer after calling AddRef.
 *
 * Return Value:
 *  HRESULT         NOERROR if the connection point is found,
 *                  E_NOINTERFACE if it's not supported.
 */

STDMETHODIMP CImpIConnPtCont::FindConnectionPoint(REFIID riid
    , IConnectionPoint **ppCP)
    {
    *ppCP=NULL;

    if (IID_IPolylineAdviseSink10==riid)
        {
        return m_pObj->m_pConnPt->QueryInterface
            (IID_IConnectionPoint, (PPVOID)ppCP);
        }

    return ResultFromScode(E_NOINTERFACE);
    }

//CConnectionPoint implementation

/*
 * CConnectionPoint::CConnectionPoint
 * CConnectionPoint::~CConnectionPoint
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.  We can
 *                  query this for the IConnectionPointContainer
 *                  interface we might need.
 */

CConnectionPoint::CConnectionPoint(PCPolyline pObj)
    {
    m_cRef=0;

    /*
     * Our lifetime is controlled by the connectable object itself,
     * although other external clients will call AddRef and Release.
     * Since we're nested in the connectable object's lifetime,
     * there's no need to call AddRef on pObj.
     */
    m_pObj=pObj;
    return;
    }

CConnectionPoint::~CConnectionPoint(void)
    {
    ReleaseInterface(m_pObj->m_pAdv);
    return;
    }

/*
 * CConnectionPoint::QueryInterface
 * CConnectionPoint::AddRef
 * CConnectionPoint::Release
 *
 * Purpose:
 *  Non-delegating IUnknown members for CConnectionPoint.
 */

STDMETHODIMP CConnectionPoint::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IConnectionPoint==riid)
        *ppv=(LPVOID)this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CConnectionPoint::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CConnectionPoint::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CConnectionPoint::GetConnectionInterface
 *
 * Purpose:
 *  Returns the IID of the outgoing interface supported through
 *  this connection point.
 *
 * Parameters:
 *  pIID            IID * in which to store the IID.
 */

STDMETHODIMP CConnectionPoint::GetConnectionInterface(IID *pIID)
    {
    if (NULL==pIID)
        return ResultFromScode(E_POINTER);

    *pIID=IID_IPolylineAdviseSink10;
    return NOERROR;
    }

/*
 * CConnectionPoint::GetConnectionPointContainer
 *
 * Purpose:
 *  Returns a pointer to the IConnectionPointContainer that
 *  is manageing this connection point.
 *
 * Parameters:
 *  ppCPC           IConnectionPointContainer ** in which to return
 *                  the pointer after calling AddRef.
 */

STDMETHODIMP CConnectionPoint::GetConnectionPointContainer
    (IConnectionPointContainer **ppCPC)
    {
    return m_pObj->QueryInterface(IID_IConnectionPointContainer
        , (void **)ppCPC);
    }

/*
 * CConnectionPoint::Advise
 *
 * Purpose:
 *  Provides this connection point with a notification sink to
 *  call whenever the appropriate outgoing function/event occurs.
 *
 * Parameters:
 *  pUnkSink        LPUNKNOWN to the sink to notify.  The connection
 *                  point must QueryInterface on this pointer to obtain
 *                  the proper interface to call.  The connection
 *                  point must also insure that any pointer held has
 *                  a reference count (QueryInterface will do it).
 *  pdwCookie       DWORD * in which to store the connection key for
 *                  later calls to Unadvise.
 */

STDMETHODIMP CConnectionPoint::Advise(LPUNKNOWN pUnkSink
    , DWORD *pdwCookie)
    {
    IPolylineAdviseSink10  *pSink;

    *pdwCookie=0;

    //Only allow one connection
    if (NULL!=m_pObj->m_pAdv)
        return ResultFromScode(CONNECT_E_ADVISELIMIT);

    //Check for the right interface on the sink.
    if (FAILED(pUnkSink->QueryInterface(IID_IPolylineAdviseSink10
        , (PPVOID)&pSink)))
        return ResultFromScode(CONNECT_E_CANNOTCONNECT);

    *pdwCookie=ADVISEKEY;
    m_pObj->m_pAdv=pSink;
    return NOERROR;
    }

/*
 * CConnectionPoint::Unadvise
 *
 * Purpose:
 *  Terminates the connection to the notification sink identified
 *  with dwCookie (that was returned from Advise).  The connection
 *  point has to Release any held pointers for that sink.
 *
 * Parameters:
 *  dwCookie        DWORD connection key from Advise.
 */

STDMETHODIMP CConnectionPoint::Unadvise(DWORD dwCookie)
    {
    if (0==dwCookie)
        return ResultFromScode(E_INVALIDARG);

    if (ADVISEKEY!=dwCookie)
        ResultFromScode(CONNECT_E_NOCONNECTION);

    ReleaseInterface(m_pObj->m_pAdv);
    return NOERROR;
    }

/*
 * CConnectionPoint::EnumConnections
 *
 * Purpose:
 *  Not implemented.
 */

STDMETHODIMP CConnectionPoint::EnumConnections
    (LPENUMCONNECTIONS *ppEnum)
    {
    *ppEnum=NULL;
    return ResultFromScode(E_NOTIMPL);
    }


IDATAOBJ.CPP

/*
 * IDATAOBJ.CPP
 * Polyline Component Chapter 21
 *
 * Implementation of the IDataObject interface.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIDataObject::CImpIDataObject
 * CImpIDataObject::~CImpIDataObject
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIDataObject::CImpIDataObject(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIDataObject::~CImpIDataObject(void)
    {
    return;
    }

/*
 * CImpIDataObject::QueryInterface
 * CImpIDataObject::AddRef
 * CImpIDataObject::Release
 */

STDMETHODIMP CImpIDataObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIDataObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIDataObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIDataObject::GetData
 *
 * Purpose:
 *  Retrieves data described by a specific FormatEtc into a StgMedium
 *  allocated by this function.  Used like GetClipboardData.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *  pSTM            LPSTGMEDIUM in which to return the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetData(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM)
    {
    UINT            cf=pFE->cfFormat;

    //Check the aspects we support.
    if (!(DVASPECT_CONTENT & pFE->dwAspect))
        return ResultFromScode(DATA_E_FORMATETC);

    pSTM->pUnkForRelease=NULL;

    //Run creates the window to use as a basis for extents
    m_pObj->m_pImpIRunnableObject->Run(NULL);

    //Go render the appropriate data for the format.
    switch (cf)
        {
        case CF_METAFILEPICT:
            pSTM->tymed=TYMED_MFPICT;
            return m_pObj->RenderMetafilePict(&pSTM->hGlobal);

        case CF_BITMAP:
            pSTM->tymed=TYMED_GDI;
            return m_pObj->RenderBitmap((HBITMAP *)&pSTM->hGlobal);

        default:
            if (cf==m_pObj->m_cf)
                {
                pSTM->tymed=TYMED_HGLOBAL;
                return m_pObj->RenderNative(&pSTM->hGlobal);
                }

            break;
        }

    return ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::GetDataHere
 *
 * Purpose:
 *  Renders the specific FormatEtc into caller-allocated medium
 *  provided in pSTM.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *  pSTM            LPSTGMEDIUM providing the medium into which
 *                  wer render the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetDataHere(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM)
    {
    UINT        cf;
    HRESULT     hr;

    /*
     * The only reasonable time this is called is for
     * CFSTR_EMBEDSOURCE and TYMED_ISTORAGE (and later for
     * CFSTR_LINKSOURCE).  This means the same as
     * IPersistStorage::Save.
     */

    cf=RegisterClipboardFormat(CFSTR_EMBEDSOURCE);

    //Aspect is unimportant to us here, as is lindex and ptd.
    if (cf==pFE->cfFormat && (TYMED_ISTORAGE & pFE->tymed))
        {
        //We have an IStorage we can write into.
        pSTM->tymed=TYMED_ISTORAGE;
        pSTM->pUnkForRelease=NULL;

        hr=m_pObj->m_pImpIPersistStorage->Save(pSTM->pstg, FALSE);
        m_pObj->m_pImpIPersistStorage->SaveCompleted(NULL);
        return hr;
        }

    return ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::QueryGetData
 *
 * Purpose:
 *  Tests if a call to GetData with this FormatEtc will provide
 *  any rendering; used like IsClipboardFormatAvailable.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::QueryGetData(LPFORMATETC pFE)
    {
    UINT            cf=pFE->cfFormat;
    BOOL            fRet=FALSE;

    //Check the aspects we support.
    if (!(DVASPECT_CONTENT & pFE->dwAspect))
        return ResultFromScode(DATA_E_FORMATETC);

    switch (cf)
        {
        case CF_METAFILEPICT:
            fRet=(BOOL)(pFE->tymed & TYMED_MFPICT);
            break;

        case CF_BITMAP:
            fRet=(BOOL)(pFE->tymed & TYMED_GDI);
            break;

        default:
            //Check our own format.
            fRet=((cf==m_pObj->m_cf)
                && (BOOL)(pFE->tymed & TYMED_HGLOBAL));
            break;
        }

    return fRet ? NOERROR : ResultFromScode(S_FALSE);
    }

/*
 * CImpIDataObject::GetCanonicalFormatEtc
 *
 * Purpose:
 *  Provides the caller with an equivalent FormatEtc to the one
 *  provided when different FormatEtcs will produce exactly the
 *  same renderings.
 *
 * Parameters:
 *  pFEIn            LPFORMATETC of the first description.
 *  pFEOut           LPFORMATETC of the equal description.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetCanonicalFormatEtc
    (LPFORMATETC pFEIn, LPFORMATETC pFEOut)
    {
    if (NULL==pFEOut)
        return ResultFromScode(E_INVALIDARG);

    pFEOut->ptd=NULL;
    return ResultFromScode(DATA_S_SAMEFORMATETC);
    }

/*
 * CImpIDataObject::SetData
 *
 * Purpose:
 *  Places data described by a FormatEtc and living in a StgMedium
 *  into the object.  The object may be responsible to clean up the
 *  StgMedium before exiting.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the data to set.
 *  pSTM            LPSTGMEDIUM containing the data.
 *  fRelease        BOOL indicating if this function is responsible
 *                  for freeing the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::SetData(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM, BOOL fRelease)
    {
    UINT            cf=pFE->cfFormat;
    BOOL            fRet=FALSE;
    PPOLYLINEDATA   ppl;

    //Check for our own clipboard format and DVASPECT_CONTENT
    if ((cf!=m_pObj->m_cf) || !(DVASPECT_CONTENT & pFE->dwAspect))
        return ResultFromScode(DATA_E_FORMATETC);

    /*
     * Data can only come from global memory containing a
     * POLYLINEDATA structure that we send to the Polyline's
     * DataSet, a now internal function used from here and
     * from IPersistStorage::Load.
     */

    if (TYMED_HGLOBAL!=pSTM->tymed)
        return ResultFromScode(DATA_E_FORMATETC);

    ppl=(PPOLYLINEDATA)GlobalLock(pSTM->hGlobal);

    if (NULL!=ppl)
        {
        m_pObj->DataSet(ppl, TRUE, TRUE);
        GlobalUnlock(pSTM->hGlobal);
        fRet=TRUE;
        }

    if (fRelease)
        ReleaseStgMedium(pSTM);

    return fRet ? NOERROR : ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::EnumFormatEtc
 *
 * Purpose:
 *  Returns an IEnumFORMATETC object through which the caller can
 *  iterate to learn about all the data formats this object can
 *  provide through either GetData[Here] or SetData.
 *
 * Parameters:
 *  dwDir           DWORD describing a data direction, either
 *                  DATADIR_SET or DATADIR_GET.
 *  ppEnum          LPENUMFORMATETC * in which to return the
 *                  pointer to the enumerator.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::EnumFormatEtc(DWORD dwDir
    , LPENUMFORMATETC *ppEnum)
    {
    return m_pObj->m_pDefIDataObject->EnumFormatEtc(dwDir, ppEnum);
    }

/*
 * CImpIDataObject::DAdvise
 * CImpIDataObject::DUnadvise
 * CImpIDataObject::EnumDAdvise
 */

STDMETHODIMP CImpIDataObject::DAdvise(LPFORMATETC pFE, DWORD dwFlags
    , LPADVISESINK pIAdviseSink, LPDWORD pdwConn)
    {
    HRESULT         hr;

    if (NULL==m_pObj->m_pIDataAdviseHolder)
        {
        hr=CreateDataAdviseHolder(&m_pObj->m_pIDataAdviseHolder);

        if (FAILED(hr))
            return ResultFromScode(E_OUTOFMEMORY);
        }

    hr=m_pObj->m_pIDataAdviseHolder->Advise(this, pFE
        , dwFlags, pIAdviseSink, pdwConn);

    return hr;
    }

STDMETHODIMP CImpIDataObject::DUnadvise(DWORD dwConn)
    {
    HRESULT         hr;

    if (NULL==m_pObj->m_pIDataAdviseHolder)
        return ResultFromScode(E_FAIL);

    hr=m_pObj->m_pIDataAdviseHolder->Unadvise(dwConn);

    return hr;
    }

STDMETHODIMP CImpIDataObject::EnumDAdvise(LPENUMSTATDATA *ppEnum)
    {
    HRESULT         hr;

    if (NULL==m_pObj->m_pIDataAdviseHolder)
        return ResultFromScode(E_FAIL);

    hr=m_pObj->m_pIDataAdviseHolder->EnumAdvise(ppEnum);
    return hr;
    }


IEXTCONN.CPP

/*
 * IEXTCONN.CPP
 * Polyline Component Chapter 21
 *
 * Implementation of IExternalConnection as required for an
 * in-process object that supports linking to embedding.
 * Specifically, this will call IOleObject::Close when there
 * are no more strong locks on the object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIExternalConnection::CImpIExternalConnection
 * CImpIExternalConnection::~CImpIExternalConnection
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIExternalConnection::CImpIExternalConnection(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    m_cLockStrong=0L;
    return;
    }

CImpIExternalConnection::~CImpIExternalConnection(void)
    {
    return;
    }

/*
 * CImpIExternalConnection::QueryInterface
 * CImpIExternalConnection::AddRef
 * CImpIExternalConnection::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIExternalConnection.
 */

STDMETHODIMP CImpIExternalConnection::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIExternalConnection::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIExternalConnection::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIExternalConnection::AddConnection
 *
 * Purpose:
 *  Informs the object that a strong connection has been made to it.
 *
 * Parameters:
 *  dwConn          DWORD identifying the type of connection, taken
 *                  from the EXTCONN enumeration.
 *  dwReserved      DWORD reserved.  This is used inside OLE and
 *                  should not be validated.
 *
 * Return Value:
 *  DWORD           The number of connection counts on the
 *                  object, used for debugging purposes only.
 */

STDMETHODIMP_(DWORD) CImpIExternalConnection::AddConnection
    (DWORD dwConn, DWORD dwReserved)
    {
    if (EXTCONN_STRONG & dwConn)
        return ++m_cLockStrong;

    return 0;
    }

/*
 * CImpIExternalConnection::ReleaseConnection
 *
 * Purpose:
 *  Informs an object that a connection has been taken away from
 *  it in which case the object may need to shut down.
 *
 * Parameters:
 *  dwConn              DWORD identifying the type of connection,
 *                      taken from the EXTCONN enumeration.
 *  dwReserved          DWORD reserved.  This is used inside OLE and
 *                      should not be validated.
 *  dwRerved            DWORD reserved
 *  fLastReleaseCloses  BOOL indicating if the last call to this
 *                      function should close the object.
 *
 * Return Value:
 *  DWORD           The number of remaining connection counts on
 *                  the object, used for debugging purposes only.
 */

STDMETHODIMP_(DWORD) CImpIExternalConnection::ReleaseConnection
    (DWORD dwConn, DWORD dwReserved, BOOL fLastReleaseCloses)
    {
    if (EXTCONN_STRONG==dwConn)
        {
        if (0==--m_cLockStrong && fLastReleaseCloses)
            m_pObj->m_pImpIOleObject->Close(OLECLOSE_SAVEIFDIRTY);

        return m_cLockStrong;
        }

    return 0L;
    }


IOLEOBJ.CPP

/*
 * IOLEOBJ.CPP
 * Polyline Component Chapter 21
 *
 * Implementation of the IOleObject interface for Polyline.  Some of
 * these just pass through to the default handler which does default
 * implementations.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIOleObject::CImpIOleObject
 * CImpIOleObject::~CImpIOleObject
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleObject::CImpIOleObject(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleObject::~CImpIOleObject(void)
    {
    return;
    }

/*
 * CImpIOleObject::QueryInterface
 * CImpIOleObject::AddRef
 * CImpIOleObject::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleObject object.
 */

STDMETHODIMP CImpIOleObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleObject::SetClientSite
 * CImpIOleObject::GetClientSite
 *
 * Purpose:
 *  Manages the IOleClientSite pointer of our container.
 */

STDMETHODIMP CImpIOleObject::SetClientSite
    (LPOLECLIENTSITE pIOleClientSite)
    {
    if (NULL!=m_pObj->m_pIOleClientSite)
        m_pObj->m_pIOleClientSite->Release();

    m_pObj->m_pIOleClientSite=pIOleClientSite;

    //CHAPTER21MOD
    if (NULL!=m_pObj->m_pIOleClientSite)
        {
        HRESULT         hr;
        LPMONIKER       pmk;
        LPOLECONTAINER  pIOleCont;

        m_pObj->m_pIOleClientSite->AddRef();

        /*
         * Within IRunnableObject::Run we're supposed to register
         * ourselves as running...however, the moniker has to come
         * from the container's IOleClientSite::GetMoniker.  But
         * Run is called before SetClientSite here, so we have to
         * register now that we do have the client site as well
         * as lock the container.
         */

        hr=m_pObj->m_pIOleClientSite->GetMoniker
            (OLEGETMONIKER_ONLYIFTHERE, OLEWHICHMK_OBJFULL, &pmk);

        if (SUCCEEDED(hr))
            {
            INOLE_RegisterAsRunning(this, pmk, 0
                , &m_pObj->m_dwRegROT);
            pmk->Release();
            }

        hr=m_pObj->m_pIOleClientSite->GetContainer(&pIOleCont);

        if (SUCCEEDED(hr))
            {
            m_pObj->m_fLockContainer=TRUE;
            pIOleCont->LockContainer(TRUE);
            pIOleCont->Release();
            }
        }
    //End CHAPTER21MOD

    return NOERROR;
    }

STDMETHODIMP CImpIOleObject::GetClientSite(LPOLECLIENTSITE *ppSite)
    {
    //Be sure to AddRef the new pointer you are giving away.
    *ppSite=m_pObj->m_pIOleClientSite;
    m_pObj->m_pIOleClientSite->AddRef();

    return NOERROR;
    }

/*
 * CImpIOleObject::SetHostNames
 *
 * Purpose:
 *  Provides the object with names of the container application and
 *  the object in the container to use in object user interface.
 *
 * Parameters:
 *  pszApp          LPCOLESTR of the container application.
 *  pszObj          LPCOLESTR of some name that is useful in window
 *                  titles.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleObject::SetHostNames(LPCOLESTR pszApp
    , LPCOLESTR pszObj)
    {
    if (NULL!=m_pObj->m_hDlg)
        {
        TCHAR       szTemp[128];

       #ifdef WIN32ANSI
        char        szObj[80];
        WideCharToMultiByte(CP_ACP, 0, pszObj, -1, szObj, 80
            , NULL, NULL);
        wsprintf(szTemp, SZPOLYFRAMETITLE, szObj);
       #else
        wsprintf(szTemp, SZPOLYFRAMETITLE, pszObj);
       #endif
        SetWindowText(m_pObj->m_hDlg, szTemp);
        }

    return NOERROR;
    }

/*
 * CImpIOleObject::Close
 *
 * Purpose:
 *  Forces the object to close down its user interface and unload.
 *
 * Parameters:
 *  dwSaveOption    DWORD describing the circumstances under which
 *                  the object is being saved and closed.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::Close(DWORD dwSaveOption)
    {
    HWND        hWnd;
    BOOL        fSave=FALSE;

    hWnd=m_pObj->m_hDlg;

    //If object is dirty and we're asked to save, save it and close.
    if (OLECLOSE_SAVEIFDIRTY==dwSaveOption && m_pObj->m_fDirty)
        fSave=TRUE;

    /*
     * If asked to prompt, only do so if dirty, then if we get a
     * YES, save as usual and close.  On NO, just close.  On
     * CANCEL return OLE_E_PROMPTSAVECANCELLED.
     */
    if (OLECLOSE_PROMPTSAVE==dwSaveOption && m_pObj->m_fDirty)
        {
        TCHAR       szTitle[20];
        TCHAR       szTemp[80];
        UINT        uRet;

        lstrcpy(szTitle, m_pObj->String(IDS_CLOSECAPTION));
        lstrcpy(szTemp, m_pObj->String(IDS_CLOSEPROMPT));

        uRet=MessageBox(hWnd, szTemp, szTitle, MB_YESNOCANCEL);

        if (IDCANCEL==uRet)
            return ResultFromScode(OLE_E_PROMPTSAVECANCELLED);

        if (IDYES==uRet)
            fSave=TRUE;
        }

    if (fSave)
        {
        m_pObj->SendAdvise(OBJECTCODE_SAVEOBJECT);
        m_pObj->SendAdvise(OBJECTCODE_SAVED);
        }

    //We get directly here on OLECLOSE_NOSAVE.
    //CHAPTER21MOD
    if (m_pObj->m_fLockContainer)
        {
        //Match LockContainer call from SetClientSite
        LPOLECONTAINER  pIOleCont;

        if (SUCCEEDED(m_pObj->m_pIOleClientSite
            ->GetContainer(&pIOleCont)))
            {
            pIOleCont->LockContainer(FALSE);
            pIOleCont->Release();
            m_pObj->m_fLockContainer=FALSE;
            }
        }
    //End CHAPTER21MOD

    if (NULL!=hWnd)
        {
        //This hides the window and sends the appropriate notify.
        DoVerb(OLEIVERB_HIDE, NULL, NULL, -1, NULL, NULL);

        m_pObj->SendAdvise(OBJECTCODE_CLOSED);
        PostMessage(hWnd, POLYM_CLOSE, 0, 0L);
        }

    return NOERROR;
    }

/*
 * CImpIOleObject::DoVerb
 *
 * Purpose:
 *  Executes an object-defined action.
 *
 * Parameters:
 *  iVerb           LONG index of the verb to execute.
 *  pMSG            LPMSG describing the event causing the
 *                  activation.
 *  pActiveSite     LPOLECLIENTSITE to the site involved.
 *  lIndex          LONG the piece on which execution is happening.
 *  hWndParent      HWND of the window in which the object can play
 *                  in-place.
 *  pRectPos        LPRECT of the object in hWndParent where the
 *                  object can play in-place if desired.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::DoVerb(LONG iVerb, LPMSG pMSG
    , LPOLECLIENTSITE pActiveSite, LONG lIndex, HWND hWndParent
    , LPCRECT pRectPos)
    {
    HRESULT     hr;

    switch (iVerb)
        {
        case OLEIVERB_HIDE:
            if (NULL!=m_pObj->m_hDlg)
                {
                ShowWindow(m_pObj->m_hDlg, SW_HIDE);
                m_pObj->SendAdvise(OBJECTCODE_HIDEWINDOW);
                }

            break;

        case OLEIVERB_PRIMARY:
        case OLEIVERB_OPEN:
        case OLEIVERB_SHOW:
            /*
             * If we're not running, make sure we are.  In any
             * case, make the dialog visible and insure it has
             * the right parent now.
             */
            hr=NOERROR;
            if (NULL==m_pObj->m_hDlg)
                hr=m_pObj->m_pImpIRunnableObject->Run(NULL);

            if (FAILED(hr) || NULL==m_pObj->m_hDlg)
                return ResultFromScode(E_OUTOFMEMORY);

            ShowWindow(m_pObj->m_hDlg, SW_SHOW);
            SetFocus(m_pObj->m_hDlg);
            m_pObj->SendAdvise(OBJECTCODE_SHOWOBJECT);
            m_pObj->SendAdvise(OBJECTCODE_SHOWWINDOW);

            break;

        default:
            return ResultFromScode(OLEOBJ_S_INVALIDVERB);
        }

    return NOERROR;
    }

/*
 * CImpIOleObject::GetUserClassID
 *
 * Purpose:
 *  Used for linked objects, this returns the class ID of what end
 *  users think they are editing.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store the CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetUserClassID(LPCLSID pClsID)
    {
    /*
     * If you are not registered to handle data other than yourself,
     * then you can just return your class ID here.  If you are
     * registered as usable from Treat-As dialogs, then you need to
     * return the CLSID of what you are really editing.
     */

    *pClsID=CLSID_Polyline19;
    return NOERROR;
    }

/*
 * CImpIOleObject::SetExtent
 *
 * Purpose:
 *  Sets the size of the object in HIMETRIC units.  Since we're in
 *  a dialog, the size of the object in us is fixed, so we ignore
 *  this call.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect affected.
 *  pszl            LPSIZEL containing the new size.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::SetExtent(DWORD dwAspect
    , LPSIZEL pszl)
    {
    //Ignored:  no size change in the dialog.
    return NOERROR;
    }

/*
 * CImpIOleObject::GetExtent
 *
 * Purpose:
 *  Retrieves the size of the object in HIMETRIC units.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect requested
 *  pszl            LPSIZEL into which to store the size.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetExtent(DWORD dwAspect, LPSIZEL pszl)
    {
    //Delegate directly to IViewObject2::GetExtent
    return m_pObj->m_pImpIViewObject->GetExtent(dwAspect, -1
        , NULL, pszl);
    }

/*
 * CImpIOleObject::Advise
 * CImpIOleObject::Unadvise
 * CImpIOleObject::EnumAdvise
 *
 * Purpose:
 *  Advisory connection functions.
 */

STDMETHODIMP CImpIOleObject::Advise(LPADVISESINK pIAdviseSink
    , LPDWORD pdwConn)
    {
    if (NULL==m_pObj->m_pIOleAdviseHolder)
        {
        HRESULT     hr;

        hr=CreateOleAdviseHolder(&m_pObj->m_pIOleAdviseHolder);

        if (FAILED(hr))
            return hr;
        }

    return m_pObj->m_pIOleAdviseHolder->Advise(pIAdviseSink
        , pdwConn);
    }

STDMETHODIMP CImpIOleObject::Unadvise(DWORD dwConn)
    {
    if (NULL!=m_pObj->m_pIOleAdviseHolder)
        return m_pObj->m_pIOleAdviseHolder->Unadvise(dwConn);

    return ResultFromScode(E_FAIL);
    }

STDMETHODIMP CImpIOleObject::EnumAdvise(LPENUMSTATDATA *ppEnum)
    {
    if (NULL!=m_pObj->m_pIOleAdviseHolder)
        return m_pObj->m_pIOleAdviseHolder->EnumAdvise(ppEnum);

    return ResultFromScode(E_FAIL);
    }

//CHAPTER21MOD
/*
 * CImpIOleObject::SetMoniker
 *
 * Purpose:
 *  Informs the object of its moniker or its container's moniker
 *  depending on dwWhich.
 *
 * Parameters:
 *  dwWhich         DWORD describing whether the moniker is the
 *                  object's or the container's.
 *  pmk             LPMONIKER with the name.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::SetMoniker(DWORD dwWhich
    , LPMONIKER pmk)
    {
    LPMONIKER       pmkFull;
    HRESULT         hr=ResultFromScode(E_FAIL);

    /*
     * For an embedded object we might be living in a container that
     * has given us away as a link.  This is our indication to
     * register the full moniker for this object that we obtain from
     * IOleClientSite::GetMoniker(OLEWHICHMK_FULL).
     */

    if (NULL!=m_pObj->m_pIOleClientSite)
        {
        hr=m_pObj->m_pIOleClientSite->GetMoniker
            (OLEGETMONIKER_ONLYIFTHERE, OLEWHICHMK_OBJFULL
            , &pmkFull);
        }

    if (SUCCEEDED(hr))
        {
        /*
         * If this moniker is already running then we don't
         * need to revoke and re-register the same thing again.
         */
        if (NOERROR==pmkFull->IsRunning(NULL, NULL, NULL))
            {
            pmkFull->Release();
            return NOERROR;
            }

        //This will revoke the old one if m_dwRegROT is nonzero.
        INOLE_RegisterAsRunning(m_pObj, pmkFull, 0
            , &m_pObj->m_dwRegROT);

        //Inform clients of the new moniker
        if (NULL!=m_pObj->m_pIOleAdviseHolder)
            m_pObj->m_pIOleAdviseHolder->SendOnRename(pmkFull);

        pmkFull->Release();
        }

    return hr;
    }

/*
 * CImpIOleObject::GetMoniker
 *
 * Purpose:
 *  Asks the object for a moniker that can later be used to
 *  reconnect to it.
 *
 * Parameters:
 *  dwAssign        DWORD determining how to assign the moniker to
 *                  to the object.
 *  dwWhich         DWORD describing which moniker the caller wants.
 *  ppmk            LPMONIKER * into which to store the moniker.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetMoniker(DWORD dwAssign
    , DWORD dwWhich, LPMONIKER *ppmk)
    {
    HRESULT         hr=ResultFromScode(E_FAIL);

    *ppmk=NULL;

    /*
     * Since we only support embedded objects, our moniker
     * is always the full moniker from the contianer.
     */

    if (NULL!=m_pObj->m_pIOleClientSite)
        {
        hr=m_pObj->m_pIOleClientSite->GetMoniker
            (OLEGETMONIKER_ONLYIFTHERE, OLEWHICHMK_OBJFULL, ppmk);
        }

    return (NULL!=*ppmk) ? NOERROR : hr;
    }

//Methods not implemented or trivial
STDMETHODIMP CImpIOleObject::InitFromData(LPDATAOBJECT pIDataObject
    , BOOL fCreation, DWORD dw)
    {
    return ResultFromScode(E_NOTIMPL);
    }

STDMETHODIMP CImpIOleObject::GetClipboardData(DWORD dwReserved
    , LPDATAOBJECT *ppIDataObj)
    {
    return ResultFromScode(E_NOTIMPL);
    }

STDMETHODIMP CImpIOleObject::Update(void)
    {
    return NOERROR;
    }

STDMETHODIMP CImpIOleObject::IsUpToDate(void)
    {
    return NOERROR;
    }

STDMETHODIMP CImpIOleObject::SetColorScheme(LPLOGPALETTE pLP)
    {
    return ResultFromScode(E_NOTIMPL);
    }
//End CHAPTER21MOD

//Methods implemented using registry helper functions in OLE.

STDMETHODIMP CImpIOleObject::EnumVerbs(LPENUMOLEVERB *ppEnum)
    {
    return OleRegEnumVerbs(m_pObj->m_clsID, ppEnum);
    }

STDMETHODIMP CImpIOleObject::GetUserType(DWORD dwForm
    , LPOLESTR *ppszType)
    {
    return OleRegGetUserType(m_pObj->m_clsID, dwForm, ppszType);
    }

STDMETHODIMP CImpIOleObject::GetMiscStatus(DWORD dwAspect
    , LPDWORD pdwStatus)
    {
    return OleRegGetMiscStatus(m_pObj->m_clsID, dwAspect
        , pdwStatus);
    }


IPERSTMI.CPP

/*
 * IPERSTMI.CPP
 * Polyline Component Chapter 21
 *
 * Implementation of the IPersistStreamInit interface exposed on the
 * Polyline object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIPersistStreamInit:CImpIPersistStreamInit
 * CImpIPersistStreamInit::~CImpIPersistStreamInit
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistStreamInit::CImpIPersistStreamInit(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPersistStreamInit::~CImpIPersistStreamInit(void)
    {
    return;
    }

/*
 * CImpIPersistStreamInit::QueryInterface
 * CImpIPersistStreamInit::AddRef
 * CImpIPersistStreamInit::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIPersistStreamInit.
 */

STDMETHODIMP CImpIPersistStreamInit::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistStreamInit::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistStreamInit::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistStreamInit::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the object represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 */

STDMETHODIMP CImpIPersistStreamInit::GetClassID(LPCLSID pClsID)
    {
    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this object since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 */

STDMETHODIMP CImpIPersistStreamInit::IsDirty(void)
    {
    return ResultFromScode(m_pObj->m_fDirty ? S_OK : S_FALSE);
    }

/*
 * CImpIPersistStreamInit::Load
 *
 * Purpose:
 *  Instructs the object to load itself from a previously saved
 *  IStreamInit that was handled by Save in another object lifetime.
 *  The seek pointer in this stream will be exactly the same as
 *  it was when Save was called, and this function must leave
 *  the seek pointer the same as it was on exit from Save, regardless
 *  of success or failure.  This function should not hold on to
 *  pIStream.
 *
 *  This function is called in lieu of IPersistStreamInit::InitNew
 *  when the object already has a persistent state.
 *
 * Parameters:
 *  pIStream        LPSTREAM from which to load.
 */

STDMETHODIMP CImpIPersistStreamInit::Load(LPSTREAM pIStream)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    HRESULT         hr;

    if (NULL==pIStream)
        return ResultFromScode(E_POINTER);

    //Read all the data into the POLYLINEDATA structure.
    hr=pIStream->Read(&pl, CBPOLYLINEDATA, &cb);

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        return hr;

    m_pObj->DataSet(&pl, TRUE, TRUE);
    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::Save
 *
 * Purpose:
 *  Saves the data for this object to an IStreamInit.  Be sure not
 *  to change the position of the seek pointer on entry to this
 *  function: the caller will assume that you write from the
 *  current offset.  Leave the stream's seek pointer at the end
 *  of the data written on exit.
 *
 * Parameters:
 *  pIStream        LPSTREAM in which to save our data.
 *  fClearDirty     BOOL indicating if this call should clear
 *                  the object's dirty flag (TRUE) or leave it
 *                  unchanged (FALSE).
 */

STDMETHODIMP CImpIPersistStreamInit::Save(LPSTREAM pIStream
    , BOOL fClearDirty)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    HRESULT         hr;

    if (NULL==pIStream)
        return ResultFromScode(E_POINTER);

    m_pObj->DataGet(&pl);

    hr=pIStream->Write(&pl, CBPOLYLINEDATA, &cb);

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        return ResultFromScode(STG_E_WRITEFAULT);

    if (fClearDirty)
        m_pObj->m_fDirty;

    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::GetSizeMax
 *
 * Purpose:
 *  Returns the size of the data we would write if Save was
 *  called right now.
 *
 * Parameters:
 *  pcbSize         ULARGE_INTEGER * in which to save the size
 *                  of the stream an immediate call to Save would
 *                  write.
 */

STDMETHODIMP CImpIPersistStreamInit::GetSizeMax(ULARGE_INTEGER
    *pcbSize)
    {
    if (NULL==pcbSize)
        return ResultFromScode(E_POINTER);

    ULISet32(*pcbSize, CBPOLYLINEDATA);
    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::InitNew
 *
 * Purpose:
 *  Informs the object that it is being created new instead of
 *  loaded from a persistent state.  This will be called in lieu
 *  of IPersistStreamInit::Load.
 *
 * Parameters:
 *  None
 */

STDMETHODIMP CImpIPersistStreamInit::InitNew(void)
    {
    //Nothing for us to do
    return NOERROR;
    }


IPERSTOR.CPP

/*
 * IPERSTOR.CPP
 * Polyline Component Chapter 21
 *
 * Implementation of the IPersistStorage interface exposed on the
 * Polyline object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIPersistStorage:CImpIPersistStorage
 * CImpIPersistStorage::~CImpIPersistStorage
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistStorage::CImpIPersistStorage(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    m_psState=PSSTATE_UNINIT;
    return;
    }

CImpIPersistStorage::~CImpIPersistStorage(void)
    {
    return;
    }

/*
 * CImpIPersistStorage::QueryInterface
 * CImpIPersistStorage::AddRef
 * CImpIPersistStorage::Release
 */

STDMETHODIMP CImpIPersistStorage::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistStorage::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistStorage::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistStorage::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the object represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, error code otherwise.
 */

STDMETHODIMP CImpIPersistStorage::GetClassID(LPCLSID pClsID)
    {
    if (PSSTATE_UNINIT==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this object since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 *
 */

STDMETHODIMP CImpIPersistStorage::IsDirty(void)
    {
    if (PSSTATE_UNINIT==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    return ResultFromScode(m_pObj->m_fDirty ? S_OK : S_FALSE);
    }

/*
 * CImpIPersistStorage::InitNew
 *
 * Purpose:
 *  Provides the object with the IStorage to hold on to while the
 *  object is running.  Here we initialize the structure of the
 *  storage and AddRef it for incremental access. This function will
 *  only be called once in the object's lifetime in lieu of Load.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE for the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::InitNew(LPSTORAGE pIStorage)
    {
    HRESULT     hr;

    if (PSSTATE_UNINIT!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    if (NULL==pIStorage)
        return ResultFromScode(E_POINTER);

    /*
     * The rules of IPersistStorage mean we hold onto the IStorage
     * and pre-create anything we'd need in Save(...,TRUE) for
     * low-memory situations.  For us this means creating our
     * "CONTENTS" stream and holding onto that IStream as
     * well as the IStorage here (requiring an AddRef call).
     */

    hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
        | STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE
        , 0, 0, &m_pObj->m_pIStream);

    if (FAILED(hr))
        return hr;

    //We expect that the client has called WriteClassStg
    WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
        , (*m_pObj->m_pST)[IDS_USERTYPE]);

    m_pObj->m_pIStorage=pIStorage;
    pIStorage->AddRef();

    m_psState=PSSTATE_SCRIBBLE;

    //Initialize the cache as needed.
    m_pObj->m_pDefIPersistStorage->InitNew(pIStorage);
    return NOERROR;
    }

/*
 * CImpIPersistStorage::Load
 *
 * Purpose:
 *  Instructs the object to load itself from a previously saved
 *  IStorage that was handled by Save in another object lifetime.
 *  This function will only be called once in the object's lifetime
 *  in lieu of InitNew. The object should hold on to pIStorage here
 *  for incremental access and low-memory saves in Save.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE from which to load.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::Load(LPSTORAGE pIStorage)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    LPSTREAM        pIStream;
    HRESULT         hr;

    if (PSSTATE_UNINIT!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    if (NULL==pIStorage)
        return ResultFromScode(E_POINTER);

    //We don't check CLSID to remain compatible with other chapters.

    hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
        | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return ResultFromScode(STG_E_READFAULT);

    //Read all the data into the POLYLINEDATA structure.
    hr=pIStream->Read(&pl, CBPOLYLINEDATA, &cb);

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        {
        pIStream->Release();
        return hr;
        }

    /*
     * We don't call pIStream->Release here because we may need
     * it for a low-memory save in Save.  We also need to
     * hold onto a copy of pIStorage, meaning AddRef.
     */
    m_pObj->m_pIStream=pIStream;

    m_pObj->m_pIStorage=pIStorage;
    pIStorage->AddRef();

    //DataSet now internal on CPolyline
    m_pObj->DataSet(&pl, TRUE, TRUE);

    m_psState=PSSTATE_SCRIBBLE;

    //We also need to tell the cache to load cached graphics
    m_pObj->m_pDefIPersistStorage->Load(pIStorage);
    return NOERROR;
    }

/*
 * CImpIPersistStorage::Save
 *
 * Purpose:
 *  Saves the data for this object to an IStorage which may
 *  or may not be the same as the one previously passed to
 *  Load, indicated with fSameAsLoad.  After this call we may
 *  not write into the storage again until SaveCompleted is
 *  called, although we may still read.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which to save our data.
 *  fSameAsLoad     BOOL indicating if this is the same pIStorage
 *                  that was passed to Load.  If TRUE, then the
 *                  object should write whatever it has *without
 *                  *using any extra memory* as this may be a low
 *                  memory save attempt.  That means that you must
 *                  not try to open or create streams.  If FALSE
 *                  you need to regenerate your whole storage
 *                  structure, being sure to also release any
 *                  pointers held from InitNew and Load.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::Save(LPSTORAGE pIStorage
    , BOOL fSameAsLoad)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    LPSTREAM        pIStream;
    HRESULT         hr;

    //Have to come here from scribble state.
    if (PSSTATE_SCRIBBLE!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    //Must have an IStorage if we're not in SameAsLoad
    if (NULL==pIStorage && !fSameAsLoad)
        return ResultFromScode(E_POINTER);

    /*
     * If we're saving to a new storage, create a new stream.
     * If fSameAsLoad it TRUE, then we write to the
     * stream we already allocated.  We should NOT depends on
     * pIStorage with fSameAsLoad is TRUE.
     */
    if (fSameAsLoad)
        {
        LARGE_INTEGER   li;

        /*
         * Use pre-allocated streams to avoid failures due
         * to low-memory conditions.  Be sure to reset the
         * stream pointer if you used this stream before!!
         */
        pIStream=m_pObj->m_pIStream;
        LISet32(li, 0);
        pIStream->Seek(li, STREAM_SEEK_SET, NULL);

        //This matches the Release below.
        pIStream->AddRef();
        }
    else
        {
        hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
            | STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE
            , 0, 0, &pIStream);

        if (FAILED(hr))
            return hr;

        //Only do this with new storages.
        WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
            , (*m_pObj->m_pST)[IDS_USERTYPE]);
        }

    //DataGet now internal on CPolyline
    m_pObj->DataGet(&pl);

    hr=pIStream->Write(&pl, CBPOLYLINEDATA, &cb);
    pIStream->Release();

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        return ResultFromScode(STG_E_WRITEFAULT);

    m_psState=PSSTATE_ZOMBIE;

    //We also need to tell the cache to save cached graphics
    m_pObj->m_pDefIPersistStorage->Save(pIStorage, fSameAsLoad);
    return NOERROR;
    }

/*
 * CImpIPersistStorage::SaveCompleted
 *
 * Purpose:
 *  Notifies the object that the storage in pIStorage has been
 *  completely saved now.  This is called when the user of this
 *  object wants to save us in a completely new storage, and if
 *  we normally hang on to the storage we have to reinitialize
 *  ourselves here for this new one that is now complete.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE of the new storage in which we live.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::SaveCompleted(LPSTORAGE pIStorage)
    {
    HRESULT     hr;
    LPSTREAM    pIStream;

    //Must be called in no-scribble or hands-off state
    if (!(PSSTATE_ZOMBIE==m_psState || PSSTATE_HANDSOFF==m_psState))
        return ResultFromScode(E_UNEXPECTED);

    //If we're coming from Hands-Off, we'd better get a storage
    if (NULL==pIStorage && PSSTATE_HANDSOFF==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    /*
     * If pIStorage is NULL, then we don't need to do anything
     * since we already have all the pointers we need for Save.
     * Otherwise we have to release any held pointers and
     * reinitialize them from pIStorage.
     */

    if (NULL!=pIStorage)
        {
        hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
            | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0
            , &pIStream);

        if (FAILED(hr))
            return hr;

        if (NULL!=m_pObj->m_pIStream)
            m_pObj->m_pIStream->Release();

        m_pObj->m_pIStream=pIStream;

        if (NULL!=m_pObj->m_pIStorage)
            m_pObj->m_pIStorage->Release();

        m_pObj->m_pIStorage=pIStorage;
        m_pObj->m_pIStorage->AddRef();
        }

    //Change state back to scribble.
    m_psState=PSSTATE_SCRIBBLE;

    m_pObj->m_pDefIPersistStorage->SaveCompleted(pIStorage);
    return NOERROR;
    }

/*
 * CImpIPersistStorage::HandsOffStorage
 *
 * Purpose:
 *  Instructs the object that another agent is interested in having
 *  total access to the storage we might be hanging on to from
 *  InitNew or SaveCompleted.  In this case we must release our hold
 *  and await another call to SaveCompleted before we have a hold
 *  again.  Therefore we cannot read or write after this call until
 *  SaveCompleted.
 *
 *  Situations where this might happen arise in compound document
 *  scenarios where this object might be in-place active but the
 *  application wants to rename and commit the root storage.
 *  Therefore we are asked to close our hold, let the container
 *  party on the storage, then call us again later to tell us the
 *  new storage we can hold.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::HandsOffStorage(void)
    {
    /*
     * Must come from scribble or no-scribble.  A repeated call
     * to HandsOffStorage is an unexpected error (bug in client).
     */
    if (PSSTATE_UNINIT==m_psState || PSSTATE_HANDSOFF==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    //Release held pointers
    if (NULL!=m_pObj->m_pIStream)
        {
        m_pObj->m_pIStream->Release();
        m_pObj->m_pIStream=NULL;
        }

    if (NULL!=m_pObj->m_pIStorage)
        {
        m_pObj->m_pIStorage->Release();
        m_pObj->m_pIStorage=NULL;
        }

    m_psState=PSSTATE_HANDSOFF;

    m_pObj->m_pDefIPersistStorage->HandsOffStorage();
    return NOERROR;
    }


IPOLYLIN.CPP

/*
 * IPOLYLIN.CPP
 * Polyline Component Chapter 21
 *
 * Implementation of the IPolyline10 interface that we expose on the
 * CPolyline object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIPolyline:CImpIPolyline
 * CImpIPolyline::~CImpIPolyline
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPolyline::CImpIPolyline(PCPolyline pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPolyline::~CImpIPolyline(void)
    {
    return;
    }

/*
 * CImpIPolyline::QueryInterface
 * CImpIPolyline::AddRef
 * CImpIPolyline::Release
 */

STDMETHODIMP CImpIPolyline::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPolyline::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPolyline::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPolyline::Init
 *
 * Purpose:
 *  Instantiates a polyline window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags
 *  uID             UINT ID to associate with this window
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_OUTOFMEMORY
 */

STDMETHODIMP CImpIPolyline::Init(HWND hWndParent, LPRECT pRect
    , DWORD dwStyle, UINT uID)
    {
    SCODE           sc;

    m_pObj->m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY
        , SZCLASSPOLYLINE, SZCLASSPOLYLINE, dwStyle, pRect->left
        , pRect->top, pRect->right-pRect->left
        , pRect->bottom-pRect->top, hWndParent, (HMENU)uID
        , m_pObj->m_hInst, m_pObj);

    sc=(NULL!=m_pObj->m_hWnd) ? S_OK : E_OUTOFMEMORY;
    return ResultFromScode(sc);
    }

/*
 * CImpIPolyline::New
 *
 * Purpose:
 *  Cleans out and reinitializes the data to defaults.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::New(void)
    {
    PPOLYLINEDATA   ppl=&m_pObj->m_pl;
    UINT            i;
    RECT            rc;

    ppl->wVerMaj=VERSIONMAJOR;
    ppl->wVerMin=VERSIONMINOR;

    //Our rectangle is the size of our window's client area.
    if (NULL!=m_pObj->m_hWnd)
        {
        GetClientRect(m_pObj->m_hWnd, &rc);
        RECTTORECTS(rc, ppl->rc);
        }
    else
        {
        SetRect(&rc, 0, 0, 100, 100);       //Something reasonable
        RECTTORECTS(rc, ppl->rc);
        }

    //Clean out the POLYLINEDATA structure and repaint the window.
    for (i=0; i< CPOLYLINEPOINTS; i++)
        {
        ppl->rgpt[i].x=0;
        ppl->rgpt[i].y=0;
        }

    ppl->cPoints      =0;
    ppl->rgbBackground=GetSysColor(COLOR_WINDOW);
    ppl->rgbLine      =GetSysColor(COLOR_WINDOWTEXT);
    ppl->iLineStyle   =PS_SOLID;

    //This is now conditional since we may not yet have a window.
    if (NULL!=m_pObj->m_hWnd)
        {
        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        UpdateWindow(m_pObj->m_hWnd);
        m_pObj->m_fDirty=TRUE;
        }

    m_pObj->SendAdvise(OBJECTCODE_DATACHANGED);
    return NOERROR;
    }

/*
 * CImpIPolyline::Undo
 *
 * Purpose:
 *  Reverses previous actions in a Polyline.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         S_OK if we can Undo more, S_FALSE otherwise.
 */

STDMETHODIMP CImpIPolyline::Undo(void)
    {
    SCODE           sc;

    //Decrement the number of active points and repaint.
    if (m_pObj->m_pl.cPoints > 0)
        {
        m_pObj->m_pl.cPoints--;
        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        UpdateWindow(m_pObj->m_hWnd);
        }

    if (NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnPointChange();
        }

    m_pObj->SendAdvise(OBJECTCODE_DATACHANGED);

    //Return if we can undo any more.
    sc=(0!=m_pObj->m_pl.cPoints) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * CImpIPolyline::Window
 *
 * Purpose:
 *  Returns the window handle associated with this polyline.
 *
 * Parameters:
 *  phWnd           HWND * in which to return the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CImpIPolyline::Window(HWND *phWnd)
    {
    *phWnd=m_pObj->m_hWnd;
    return NOERROR;
    }

/*
 * CImpIPolyline::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::RectGet(LPRECT pRect)
    {
    RECT            rc;
    POINT           pt;

    if (NULL==m_pObj->m_hWnd)
        {
        SetRect(pRect, 0, 0, 150, 150); //Reasonable default
        return NOERROR;
        }

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_pObj->m_hWnd, &rc);
    pt.x=rc.left;
    pt.y=rc.top;
    ScreenToClient(GetParent(m_pObj->m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return NOERROR;
    }

/*
 * CImpIPolyline::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return NOERROR;
    }

/*
 * CImpIPolyline::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT            cx, cy;
    RECT            rc;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetRect(&rc, 0, 0, cx, cy);
    RECTTORECTS(rc, m_pObj->m_pl.rc);

    if (NULL!=m_pObj->m_hWnd)
        {
        SetWindowPos(m_pObj->m_hWnd, NULL, pRect->left, pRect->top
            , cx, cy, SWP_NOZORDER);
        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        }

    if (fNotify && NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnSizeChange();
        }

    return NOERROR;
    }

/*
 * CImpIPolyline::SizeSet
 *
 * Purpose:
 *  Sets a new size for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT            cx, cy;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    if (NULL!=m_pObj->m_hWnd)
        {
        SetWindowPos(m_pObj->m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
            , SWP_NOMOVE | SWP_NOZORDER);
        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        }

    if (fNotify && NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnSizeChange();
        }

    return NOERROR;
    }

/*
 * CImpIPolyline::ColorSet
 *
 * Purpose:
 *  Changes for background or line color in the Polyline
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color to use.
 *  pcrPrev         COLORREF * in whch to store the
 *                  previous color.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::ColorSet(UINT iColor, COLORREF cr
    , COLORREF *pcrPrev)
    {
    COLORREF        crRet;

    if (NULL==pcrPrev)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    switch (iColor)
        {
        case POLYLINECOLOR_BACKGROUND:
            crRet=m_pObj->m_pl.rgbBackground;
            m_pObj->m_pl.rgbBackground=cr;
            break;

        case POLYLINECOLOR_LINE:
            crRet=m_pObj->m_pl.rgbLine;
            m_pObj->m_pl.rgbLine=cr;
            break;
        }

    //If the color changed, repaint
    if (crRet!=cr)
        {
        if (NULL!=m_pObj->m_pAdv)
            {
            m_pObj->m_fDirty=TRUE;
            m_pObj->m_pAdv->OnColorChange();
            }

        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        UpdateWindow(m_pObj->m_hWnd);

        m_pObj->SendAdvise(OBJECTCODE_DATACHANGED);
        }

    *pcrPrev=crRet;
    return NOERROR;
    }

/*
 * CImpIPolyline::ColorGet
 *
 * Purpose:
 *  Retrieves one of the colors currently in use by the Polyline.
 *
 * Parameters:
 *  iColor          UINT identifying the color of interest.
 *  pcr             COLORREF * in which to return the color.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::ColorGet(UINT iColor, COLORREF *pcr)
    {
    COLORREF        crRet;

    if (NULL==pcr)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    crRet=(POLYLINECOLOR_BACKGROUND==iColor)
        ? m_pObj->m_pl.rgbBackground : m_pObj->m_pl.rgbLine;

    *pcr=crRet;
    return NOERROR;
    }

/*
 * CImpIPolyline::LineStyleSet
 *
 * Purpose:
 *  Changes the line style in use by the Polyline
 *
 * Parameters:
 *  iStyle          UINT style of the line to use.
 *  piPrev          UINT * in which to store the previous style.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::LineStyleSet(UINT iStyle, UINT *piPrev)
    {
    UINT            uRet;

    uRet=(UINT)m_pObj->m_pl.iLineStyle;

    if (NULL==piPrev)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    //Validate the line style
    if (PS_SOLID==iStyle || PS_DASH==iStyle || PS_DOT==iStyle
        || PS_DASHDOT==iStyle || PS_DASHDOTDOT==iStyle)
        {
        m_pObj->m_pl.iLineStyle=iStyle;

        if (uRet!=(UINT)m_pObj->m_pl.iLineStyle)
            {
            if (NULL!=m_pObj->m_pAdv)
                {
                m_pObj->m_fDirty=TRUE;
                m_pObj->m_pAdv->OnLineStyleChange();
                }

            InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
            UpdateWindow(m_pObj->m_hWnd);

            m_pObj->SendAdvise(OBJECTCODE_DATACHANGED);
            }
        }

    *piPrev=uRet;
    return NOERROR;
    }

/*
 * CImpIPolyline::LineStyleGet
 *
 * Purpose:
 *  Retrieves the current line style in use in the Polyline
 *
 * Parameters:
 *  piStyle         UINT * in which to store the style.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::LineStyleGet(UINT *piStyle)
    {
    if (NULL==piStyle)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    *piStyle=m_pObj->m_pl.iLineStyle;
    return NOERROR;
    }


IRUNOBJ.CPP

/*
 * IRUNOBJ.CPP
 * Polyline Component Chapter 21
 *
 * Implementation of the IRunnableObject interface which allows
 * us to enter the "running" state which means Polyline's dialog
 * box is created, but not visible.  This is necessary so that
 * containers can ask for our extents before calling DoVerb.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIRunnableObject::CImpIRunnableObject
 * CImpIRunnableObject::~CImpIRunnableObject
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIRunnableObject::CImpIRunnableObject(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIRunnableObject::~CImpIRunnableObject(void)
    {
    return;
    }

/*
 * CImpIRunnableObject::QueryInterface
 * CImpIRunnableObject::AddRef
 * CImpIRunnableObject::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIRunnableObject.
 */

STDMETHODIMP CImpIRunnableObject::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIRunnableObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIRunnableObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIRunnableObject::GetRunningClass
 *
 * Purpose:
 *  Returns the CLSID of the object.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store the CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIRunnableObject::GetRunningClass(LPCLSID pClsID)
    {
    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

/*
 * CImpIRunnableObject::Run
 *
 * Purpose:
 *  Run an object in the given bind context, that is, put the object
 *  into the running state.
 *
 * Parameters:
 *  pBindCtx        LPBINDCTX of the bind context to use.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIRunnableObject::Run(LPBINDCTX pBindCtx)
    {
    /*
     * Create the dialog but not show it.  This function is called
     * from OleRun which is called from within OleCreate.  So when
     * a container creates this object, we'll create the dialog
     * which is a basis for the object's extents (as returned from
     * IViewObject2::GetExtent.  Our IOleObject::DoVerb will later
     * note that the dialog is already created and will just make
     * it visible and not create it anew.
     *
     * Note that the dialog is unowned--it acts like an overlapped
     * window.
     */

    if (NULL!=m_pObj->m_hDlg)
        return NOERROR;

    //This stores the dialog handle in m_pObj.
    CreateDialogParam(m_pObj->Instance()
        , MAKEINTRESOURCE(IDD_EDITDIALOG), NULL, PolyDlgProc
        , (LPARAM)m_pObj);

    if (NULL==m_pObj->m_hDlg)
        return ResultFromScode(E_OUTOFMEMORY);

    //CHAPTER21MOD
    /*
     * Registration of the object as running happens in
     * IOleObject::SetClientSite since we need a moniker from
     * the container and we don't have a client site pointer yet.
     */
    //End CHAPTER21MOD

    return NOERROR;
    }

/*
 * CImpIRunnableObject::IsRunning
 *
 * Purpose:
 *  Answers whether an object is currently in the running state.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            Indicates the running state of the object.
 */

STDMETHODIMP_(BOOL) CImpIRunnableObject::IsRunning(void)
    {
    return (NULL!=m_pObj->m_hDlg);
    }

/*
 * CImpIRunnableObject::LockRunning
 *
 * Purpose:
 *  Locks an already running object into the running state or unlocks
 *  it from such a state.
 *
 * Parameters:
 *  fLock               BOOL indicating lock (TRUE) or unlock
 *                      (FALSE)
 *  fLastUnlockCloses   BOOL indicating if the last call to this
 *                      function with fLock==FALSE closes the
 *                      object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIRunnableObject::LockRunning(BOOL fLock
    , BOOL fLastUnlockCloses)
    {
    //Calling CoLockObjectExternal is all we have to do here.
    return CoLockObjectExternal(this, fLock, fLastUnlockCloses);
    }

/*
 * CImpIRunnableObject::SetContainedObject
 *
 * Purpose:
 *  Informs the object (embedded object) that it is inside a
 *  compound document container.
 *
 * Parameters:
 *  fContained      BOOL indicating if the object is now contained.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIRunnableObject::SetContainedObject(BOOL fContained)
    {
    //We can ignore this.
    return NOERROR;
    }


IVIEWOBJ.CPP

/*
 * IVIEWOBJ.CPP
 * Polyline Component Chapter 21
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIViewObject::CImpIViewObject
 * CImpIViewObject::~CImpIViewObject
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIViewObject::CImpIViewObject(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIViewObject::~CImpIViewObject(void)
    {
    return;
    }

/*
 * CImpIViewObject::QueryInterface
 * CImpIViewObject::AddRef
 * CImpIViewObject::Release
 *
 * Purpose:
 *  IUnknown members for CImpIViewObject object.
 */

STDMETHODIMP CImpIViewObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIViewObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIViewObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIViewObject::Draw
 *
 * Purpose:
 *  Draws the object on the given hDC specifically for the requested
 *  aspect, device, and within the appropriate bounds.
 *
 * Parameters:
 *  dwAspect        DWORD aspect to draw.
 *  lindex          LONG index of the piece to draw.
 *  pvAspect        LPVOID for extra information, always NULL.
 *  ptd             DVTARGETDEVICE * containing device
 *                  information.
 *  hICDev          HDC containing the IC for the device.
 *  hDC             HDC on which to draw.
 *  pRectBounds     LPCRECTL describing the rectangle in which
 *                  to draw.
 *  pRectWBounds    LPCRECTL describing the placement rectangle
 *                  if part of what you draw is another metafile.
 *  pfnContinue     Function to call periodically during
 *                  long repaints.
 *  dwContinue      DWORD extra information to pass to the
 *                  pfnContinue.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::Draw(DWORD dwAspect, LONG lindex
    , LPVOID pvAspect, DVTARGETDEVICE *ptd, HDC hICDev
    , HDC hDC, LPCRECTL pRectBounds, LPCRECTL pRectWBounds
    , BOOL (CALLBACK *pfnContinue) (DWORD), DWORD dwContinue)
    {
    RECT            rc;
    POLYLINEDATA    pl;
    PPOLYLINEDATA   ppl=&m_pObj->m_pl;

    RECTFROMRECTL(rc, *pRectBounds);

    //Delegate iconic and printed representations.
    if (!((DVASPECT_CONTENT | DVASPECT_THUMBNAIL) & dwAspect))
        {
        return m_pObj->m_pDefIViewObject->Draw(dwAspect, lindex
            , pvAspect, ptd, hICDev, hDC, pRectBounds, pRectWBounds
            , pfnContinue, dwContinue);
        }

    /*
     * If we're asked to draw a frozen aspect, use the data from
     * a copy we made in IViewObject::Freeze.  Otherwise use the
     * current data.
     */
    if (dwAspect & m_pObj->m_dwFrozenAspects)
        {
        //Point to the data to actually use.
        if (DVASPECT_CONTENT==dwAspect)
            ppl=&m_pObj->m_plContent;
        else
            ppl=&m_pObj->m_plThumbnail;
        }

    //Make a copy so we can modify it (and not worry about threads)
    memcpy(&pl, ppl, CBPOLYLINEDATA);

    /*
     * If we're going to a printer, check if it's color capable.
     * if not, then use black on white for this figure.
     */
    if (NULL!=hICDev)
        {
        if (GetDeviceCaps(hICDev, NUMCOLORS) <= 2)
            {
            pl.rgbBackground=RGB(255, 255, 255);
            pl.rgbLine=RGB(0, 0, 0);
            }
        }

    m_pObj->Draw(hDC, FALSE, TRUE, &rc, &pl);
    return NOERROR;
    }

/*
 * CImpIViewObject::GetColorSet
 *
 * Purpose:
 *  Retrieves the color palette used by the object.
 *
 * Parameters:
 *  dwAspect        DWORD aspect of interest.
 *  lindex          LONG piece of interest.
 *  pvAspect        LPVOID with extra information, always NULL.
 *  ptd             DVTARGETDEVICE * containing device info.
 *  hICDev          HDC containing the IC for the device.
 *  ppColorSet      LPLOGPALETTE * into which to return the
 *                  pointer to the palette in this color set.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::GetColorSet(DWORD dwDrawAspect
    , LONG lindex, LPVOID pvAspect, DVTARGETDEVICE *ptd
    , HDC hICDev, LPLOGPALETTE *ppColorSet)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIViewObject::Freeze
 *
 * Purpose:
 *  Freezes the view of a particular aspect such that data
 *  changes do not affect the view.
 *
 * Parameters:
 *  dwAspect        DWORD aspect to freeze.
 *  lindex          LONG piece index under consideration.
 *  pvAspect        LPVOID for further information, always NULL.
 *  pdwFreeze       LPDWORD in which to return the key.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::Freeze(DWORD dwAspect, LONG lindex
    , LPVOID pvAspect, LPDWORD pdwFreeze)
    {
    //Delegate anything for ICON or DOCPRINT aspects
    if (!((DVASPECT_CONTENT | DVASPECT_THUMBNAIL) & dwAspect))
        {
        return m_pObj->m_pDefIViewObject->Freeze(dwAspect, lindex
            , pvAspect, pdwFreeze);
        }

    if (dwAspect & m_pObj->m_dwFrozenAspects)
        {
        *pdwFreeze=dwAspect + FREEZE_KEY_OFFSET;
        return ResultFromScode(VIEW_S_ALREADY_FROZEN);
        }

    m_pObj->m_dwFrozenAspects |= dwAspect;

    /*
     * For whatever aspects become frozen, make a copy of the data.
     * Later when drawing, if such a frozen aspect is requested,
     * we'll draw from this data rather than from our current data.
     */
    if (DVASPECT_CONTENT & dwAspect)
        {
        memcpy(&m_pObj->m_plContent, &m_pObj->m_pl
            , CBPOLYLINEDATA);
        }

    if (DVASPECT_THUMBNAIL & dwAspect)
        {
        memcpy(&m_pObj->m_plThumbnail, &m_pObj->m_pl
            , CBPOLYLINEDATA);
        }

    if (NULL!=pdwFreeze)
        *pdwFreeze=dwAspect + FREEZE_KEY_OFFSET;

    return NOERROR;
    }

/*
 * CImpIViewObject::Unfreeze
 *
 * Purpose:
 *  Thaws an aspect frozen in ::Freeze.  We expect that a container
 *  will redraw us after freezing if necessary, so we don't send
 *  any sort of notification here.
 *
 * Parameters:
 *  dwFreeze        DWORD key returned from ::Freeze.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::Unfreeze(DWORD dwFreeze)
    {
    DWORD       dwAspect=dwFreeze - FREEZE_KEY_OFFSET;

    //Delegate anything for ICON or DOCPRINT aspects
    if (!((DVASPECT_CONTENT | DVASPECT_THUMBNAIL) & dwAspect))
        m_pObj->m_pDefIViewObject->Unfreeze(dwFreeze);

    //The aspect to unfreeze is in the key.
    m_pObj->m_dwFrozenAspects &= ~(dwAspect);

    /*
     * Since we always kept our current data up to date, we don't
     * have to do anything thing here like requesting data again.
     * Because we removed dwAspect from m_dwFrozenAspects, Draw
     * will again use the current data.
     */

    return NOERROR;
    }

/*
 * CImpIViewObject::SetAdvise
 *
 * Purpose:
 *  Provides an advise sink to the view object enabling
 *  notifications for a specific aspect.
 *
 * Parameters:
 *  dwAspects       DWORD describing the aspects of interest.
 *  dwAdvf          DWORD containing advise flags.
 *  pIAdviseSink    LPADVISESINK to notify.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::SetAdvise(DWORD dwAspects, DWORD dwAdvf
    , LPADVISESINK pIAdviseSink)
    {
    //Pass anything with DVASPECT_ICON or _DOCPRINT to the handler.
    if (!((DVASPECT_CONTENT | DVASPECT_THUMBNAIL) & dwAspects))
        {
        m_pObj->m_pDefIViewObject->SetAdvise(dwAspects, dwAdvf
            , pIAdviseSink);
        }

    //We continue because dwAspects may have more than one in it.
    if (NULL!=m_pObj->m_pIAdviseSink)
        m_pObj->m_pIAdviseSink->Release();

    m_pObj->m_pIAdviseSink=pIAdviseSink;
    m_pObj->m_dwAdviseAspects=dwAspects;
    m_pObj->m_dwAdviseFlags=dwAdvf;

    if (NULL!=m_pObj->m_pIAdviseSink)
        m_pObj->m_pIAdviseSink->AddRef();

    return NOERROR;
    }

/*
 * CImpIViewObject::GetAdvise
 *
 * Purpose:
 *  Returns the last known IAdviseSink seen by ::SetAdvise.
 *
 * Parameters:
 *  pdwAspects      LPDWORD in which to store the last
 *                  requested aspects.
 *  pdwAdvf         LPDWORD in which to store the last
 *                  requested flags.
 *  ppIAdvSink      LPADVISESINK * in which to store the
 *                  IAdviseSink.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::GetAdvise(LPDWORD pdwAspects
    , LPDWORD pdwAdvf, LPADVISESINK *ppAdvSink)
    {
    if (NULL!=ppAdvSink)
        {
        *ppAdvSink=m_pObj->m_pIAdviseSink;
        m_pObj->m_pIAdviseSink->AddRef();
        }

    if (NULL!=pdwAspects)
        *pdwAspects=m_pObj->m_dwAdviseAspects;

    if (NULL!=pdwAdvf)
        *pdwAdvf=m_pObj->m_dwAdviseFlags;

    return NOERROR;
    }

/*
 * CImpIViewObject::GetExtent
 *
 * Purpose:
 *  Retrieves the extents of the object's display.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect of interest.
 *  lindex          LONG index of the piece of interest.
 *  ptd             DVTARGETDEVICE * with device information.
 *  pszl            LPSIZEL to the structure in which to return
 *                  the extents.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::GetExtent(DWORD dwAspect, LONG lindex
    , DVTARGETDEVICE *ptd, LPSIZEL pszl)
    {
    RECT            rc;

    if (!(DVASPECT_CONTENT & dwAspect))
        {
        return m_pObj->m_pDefIViewObject->GetExtent(dwAspect
            , lindex, ptd, pszl);
        }

    m_pObj->m_pImpIPolyline->RectGet(&rc);
    m_pObj->RectConvertMappings(&rc, FALSE);

    pszl->cx=rc.right-rc.left;
    pszl->cy=rc.bottom-rc.top;

    return NOERROR;
    }


POLYLINE.CPP

/*
 * POLYLINE.CPP
 * Polyline Component Chapter 21
 *
 * Implementation of the CPolyline class that we expose as a
 * component object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CPolyline:CPolyline
 * CPolyline::~CPolyline
 *
 * Constructor Parameters:
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 *  pfnDestroy      PFNDESTROYED to call when an object is
 *                  destroyed.
 *  hInst           HINSTANCE of the application we're in.
 */

CPolyline::CPolyline(LPUNKNOWN pUnkOuter, PFNDESTROYED pfnDestroy
    , HINSTANCE hInst)
    {
    m_hWnd=NULL;
    m_hInst=hInst;

    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pfnDestroy=pfnDestroy;
    m_fDirty=FALSE;

    m_pImpIPolyline=NULL;
    m_pImpIConnPtCont=NULL;

    m_pAdv=NULL;
    m_pConnPt=NULL;

    m_pST  =NULL;
    m_cf   =0;
    m_clsID=CLSID_Polyline19;

    m_pIStorage=NULL;
    m_pIStream =NULL;

    m_pImpIPersistStorage=NULL;
    m_pImpIPersistStreamInit=NULL;

    m_pImpIDataObject   =NULL;
    m_pIDataAdviseHolder=NULL;

    m_pDefIUnknown       =NULL;
    m_pDefIDataObject    =NULL;
    m_pDefIViewObject    =NULL;
    m_pDefIPersistStorage=NULL;

    m_pIOleAdviseHolder =NULL;
    m_pImpIOleObject    =NULL;
    m_pIOleClientSite   =NULL;
    m_pImpIViewObject   =NULL;
    m_pIAdviseSink      =NULL;

    m_dwFrozenAspects   =0;
    m_dwAdviseAspects   =0;
    m_dwAdviseFlags     =0;

    m_pImpIRunnableObject=NULL;
    m_hDlg=NULL;

    //CHAPTER21MOD
    m_pImpIExternalConnection=NULL;
    m_fLockContainer=FALSE;
    m_dwRegROT=0L;
    //End CHAPTER21MOD

    return;
    }

CPolyline::~CPolyline(void)
    {
    LPUNKNOWN       pIUnknown=this;

    if (NULL!=m_pUnkOuter)
        pIUnknown=m_pUnkOuter;

    if (NULL!=m_pST)
        delete m_pST;

    if (NULL!=m_hDlg)
        DestroyWindow(m_hDlg);

    /*
     * In aggregation, release cached pointers but
     * AddRef the controlling unknown first.  The
     * extra reference count protects from reentrancy.
     */

    m_cRef++;

    pIUnknown->AddRef();
    pIUnknown->AddRef();
    pIUnknown->AddRef();

    ReleaseInterface(m_pDefIViewObject);
    ReleaseInterface(m_pDefIDataObject);
    ReleaseInterface(m_pDefIPersistStorage);

    m_cRef--;

    //Cached pointer rules do not apply to IUnknown
    ReleaseInterface(m_pDefIUnknown);

    ReleaseInterface(m_pIAdviseSink);

    //CHAPTER21MOD
    if (m_fLockContainer)
        {
        LPOLECONTAINER  pIOleCont;

        if (SUCCEEDED(m_pIOleClientSite
            ->GetContainer(&pIOleCont)))
            {
            pIOleCont->LockContainer(FALSE);
            pIOleCont->Release();
            }
        }
    //End CHAPTER21MOD
    ReleaseInterface(m_pIOleClientSite);
    ReleaseInterface(m_pIOleAdviseHolder);

    DeleteInterfaceImp(m_pImpIOleObject);
    DeleteInterfaceImp(m_pImpIViewObject);
    DeleteInterfaceImp(m_pImpIRunnableObject);

    //CHAPTER21MOD
    //Anything we might have registered in IRunnableObject::Run
    INOLE_RevokeAsRunning(&m_dwRegROT);

    DeleteInterfaceImp(m_pImpIExternalConnection);
    //End CHAPTERw21MOD

    ReleaseInterface(m_pIDataAdviseHolder);
    DeleteInterfaceImp(m_pImpIDataObject);

    DeleteInterfaceImp(m_pImpIPersistStreamInit);
    DeleteInterfaceImp(m_pImpIPersistStorage);
    ReleaseInterface(m_pIStream);
    ReleaseInterface(m_pIStorage);

    DeleteInterfaceImp(m_pImpIConnPtCont);
    DeleteInterfaceImp(m_pImpIPolyline);

    ReleaseInterface(m_pAdv);
    ReleaseInterface(m_pConnPt);

    return;
    }

/*
 * CPolyline::Init
 *
 * Purpose:
 *  Performs any intiailization of a CPolyline that's prone to
 *  failure that we also use internally before exposing the
 *  object outside this DLL.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CPolyline::Init(void)
    {
    LPUNKNOWN       pIUnknown=this;
    HRESULT         hr;

    if (NULL!=m_pUnkOuter)
        pIUnknown=m_pUnkOuter;

    m_pST=new CStringTable(m_hInst);

    if (!m_pST->Init(IDS_POLYLINEMIN, IDS_POLYLINEMAX))
        return FALSE;

    m_cf=RegisterClipboardFormat(SZPOLYLINECLIPFORMAT);

    m_pImpIPersistStorage=new CImpIPersistStorage(this, pIUnknown);

    if (NULL==m_pImpIPersistStorage)
        return FALSE;

    m_pImpIPersistStreamInit=new CImpIPersistStreamInit(this
        , pIUnknown);

    if (NULL==m_pImpIPersistStreamInit)
        return FALSE;

    m_pImpIPolyline=new CImpIPolyline(this, pIUnknown);

    if (NULL==m_pImpIPolyline)
        return FALSE;

    m_pImpIConnPtCont=new CImpIConnPtCont(this, pIUnknown);

    if (NULL==m_pImpIConnPtCont)
        return FALSE;

    m_pConnPt=new CConnectionPoint(this);

    if (NULL==m_pConnPt)
        return FALSE;

    m_pConnPt->AddRef();    //Reversed in destructor

    m_pImpIDataObject=new CImpIDataObject(this, pIUnknown);

    if (NULL==m_pImpIDataObject)
        return FALSE;

    m_pImpIOleObject=new CImpIOleObject(this, pIUnknown);

    if (NULL==m_pImpIOleObject)
        return FALSE;

    m_pImpIViewObject=new CImpIViewObject(this, pIUnknown);

    if (NULL==m_pImpIViewObject)
        return FALSE;

    m_pImpIRunnableObject=new CImpIRunnableObject(this, pIUnknown);

    if (NULL==m_pImpIRunnableObject)
        return FALSE;

    //CHAPTER21MOD
    m_pImpIExternalConnection=new CImpIExternalConnection(this
        , pIUnknown);

    if (NULL==m_pImpIExternalConnection)
        return FALSE;
    //End CHAPTER21MOD

    /*
     * We're sitting at ref count 0 and the next call will AddRef a
     * few times and Release a few times.  This insures we don't
     * delete ourselves prematurely.
     */
    m_cRef++;

    //Aggregate OLE's cache for IOleCache* interfaces.
    hr=CreateDataCache(pIUnknown, CLSID_Polyline19
        , IID_IUnknown, (PPVOID)&m_pDefIUnknown);

    if (FAILED(hr))
        return FALSE;

    /*
     * NOTE:  The spec specifically states that any interfaces
     * besides IUnknown that we obtain on an aggregated object
     * should be Released immediately after we QueryInterface for
     * them because the QueryInterface will AddRef us, and since
     * we would not release these interfaces until we were
     * destroyed, we'd never go away because we'd never get a zero
     * ref count.
     */

    //Now try to get other interfaces to which we delegate
    hr=m_pDefIUnknown->QueryInterface(IID_IViewObject2
        , (PPVOID)&m_pDefIViewObject);

    if (FAILED(hr))
        return FALSE;

    pIUnknown->Release();

    hr=m_pDefIUnknown->QueryInterface(IID_IDataObject
        , (PPVOID)&m_pDefIDataObject);

    if (FAILED(hr))
        return FALSE;

    pIUnknown->Release();

    hr=m_pDefIUnknown->QueryInterface(IID_IPersistStorage
        , (PPVOID)&m_pDefIPersistStorage);

    if (FAILED(hr))
        return FALSE;

    pIUnknown->Release();

    m_cRef--;
    m_pImpIPolyline->New();

    return TRUE;
    }

/*
 * CPolyline::QueryInterface
 * CPolyline::AddRef
 * CPolyline::Release
 *
 * Purpose:
 *  IUnknown members for CPolyline object.
 */

STDMETHODIMP CPolyline::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IConnectionPointContainer==riid)
        *ppv=m_pImpIConnPtCont;

    if (IID_IPolyline10==riid)
        *ppv=m_pImpIPolyline;

    if (IID_IPersistStorage==riid)
        *ppv=m_pImpIPersistStorage;

    if (IID_IPersist==riid || IID_IPersistStream==riid
        || IID_IPersistStreamInit==riid)
        *ppv=m_pImpIPersistStreamInit;

    if (IID_IDataObject==riid)
        *ppv=m_pImpIDataObject;

    if (IID_IOleObject==riid)
        *ppv=m_pImpIOleObject;

    if (IID_IViewObject==riid || IID_IViewObject2==riid)
        *ppv=m_pImpIViewObject;

    if (IID_IRunnableObject==riid)
        *ppv=m_pImpIRunnableObject;

    //CHAPTER21MOD
    if (IID_IExternalConnection==riid)
        *ppv=m_pImpIExternalConnection;
    //End CHAPTER21MOD

    //Use the default handler's cache.
    if (IID_IOleCache==riid || IID_IOleCache2==riid)
        return m_pDefIUnknown->QueryInterface(riid, ppv);

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CPolyline::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPolyline::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0L;
    }

/*
 * CPolyline::RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device (MM_TEXT) or
 *  HIMETRIC to the other.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  fToDevice       BOOL TRUE to convert from HIMETRIC to device,
 *                  FALSE to convert device to HIMETRIC.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectConvertMappings(LPRECT pRect, BOOL fToDevice)
    {
    HDC      hDC;
    int      iLpx, iLpy;

    if (NULL==pRect)
        return;

    hDC=GetDC(NULL);
    iLpx=GetDeviceCaps(hDC, LOGPIXELSX);
    iLpy=GetDeviceCaps(hDC, LOGPIXELSY);
    ReleaseDC(NULL, hDC);

    if (fToDevice)
        {
        pRect->left=MulDiv(iLpx, pRect->left, HIMETRIC_PER_INCH);
        pRect->top =MulDiv(iLpy, pRect->top , HIMETRIC_PER_INCH);

        pRect->right =MulDiv(iLpx, pRect->right, HIMETRIC_PER_INCH);
        pRect->bottom=MulDiv(iLpy, pRect->bottom,HIMETRIC_PER_INCH);
        }
    else
        {
        pRect->left=MulDiv(pRect->left, HIMETRIC_PER_INCH, iLpx);
        pRect->top =MulDiv(pRect->top , HIMETRIC_PER_INCH, iLpy);

        pRect->right =MulDiv(pRect->right, HIMETRIC_PER_INCH, iLpx);
        pRect->bottom=MulDiv(pRect->bottom,HIMETRIC_PER_INCH, iLpy);
        }

    return;
    }

/*
 * CPolyline::DataSet
 *
 * Purpose:
 *  Sets the current data in this Polyline to a given structure.
 *
 * Parameters:
 *  pplIn           PPOLYLINEDATA to initialize to.
 *  fSizeToData     BOOL indicating if we're to size to the data
 *                  or scale it.
 *  fNotify         BOOL indicating if we're to send an advise
 *                  on this change.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::DataSet(PPOLYLINEDATA pplIn
    , BOOL fSizeToData, BOOL fNotify)
    {
    RECT            rc;

    /*
     * Copy the structure in pplIn and repaint to reflect the
     * new point set.  Note that unlike the RectSet message, we
     * do no scaling, assuming that the rect in the structure
     * is appropriate for the data.
     */

    if (NULL==pplIn)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    m_pl=*pplIn;
    m_fDirty=TRUE;

    /*
     * If we're scaling the window to fit the data, then use
     * RectSet passing our current rectangle as the new one.
     * That makes sure that the data won't change but that the
     * window is resized.
     */

    if (fSizeToData)
        {
        POINT       pt;

        /*
         * Get our offset in the parent window so we can RectSet
         * to the right place since RectSet expects rectangle in
         * parent coordinates and we get it in client coordinates.
         */
        if (NULL!=m_hWnd)
            {
            GetWindowRect(m_hWnd, &rc);
            pt.x=rc.left;
            pt.y=rc.top;
            ScreenToClient(GetParent(m_hWnd), &pt);
            RECTSTORECT(m_pl.rc, rc);
            OffsetRect(&rc, pt.x, pt.y);

            //This will also cause a repaint.
            m_pImpIPolyline->RectSet(&rc, fNotify);
            }
        }
    else
        {
        if (NULL!=m_hWnd)
            {
            //Make sure we're updated.
            InvalidateRect(m_hWnd, NULL, TRUE);
            UpdateWindow(m_hWnd);
            }
        }

    SendAdvise(OBJECTCODE_DATACHANGED);
    return NOERROR;
    }

/*
 * CPolyline::DataGet
 *
 * Purpose:
 *  Retrieves the Polyline's current data.
 *
 * Parameters:
 *  pplIn           PPOLYLINEDATA into which we copy the data.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::DataGet(PPOLYLINEDATA pplIn)
    {
    if (NULL==pplIn)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    *pplIn=m_pl;
    return NOERROR;
    }

/*
 * CPolyline::RenderNative
 *
 * Purpose:
 *  Retrieves the Polyline's data in a global memory handle.
 *
 * Parameters:
 *  phMem           HGLOBAL * in which to store the handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::RenderNative(HGLOBAL *phMem)
    {
    HGLOBAL         hMem;
    PPOLYLINEDATA   ppl;
    HRESULT         hr=ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    if (NULL==phMem)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, CBPOLYLINEDATA);

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        hr=DataGet(ppl);

        GlobalUnlock(hMem);

        if (FAILED(hr))
            {
            GlobalFree(hMem);
            hMem=NULL;
            }
        }

    *phMem=hMem;
    return hr;
    }

/*
 * CPolyline::RenderBitmap
 *
 * Purpose:
 *  Creates a bitmap image of the current Polyline.
 *
 * Parameters:
 *  phBmp           HBITMAP * in which to return the bitmap.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::RenderBitmap(HBITMAP *phBmp)
    {
    HDC             hDC;
    HDC             hMemDC;
    HBITMAP         hBmp;
    RECT            rc;
    HGDIOBJ         hObj;

    if (NULL==phBmp)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    //Render a bitmap the size of the current rectangle.
    hDC=GetDC(m_hWnd);
    hMemDC=CreateCompatibleDC(hDC);

    GetClientRect(m_hWnd, &rc);
    hBmp=CreateCompatibleBitmap(hDC, rc.right, rc.bottom);

    if (NULL!=hBmp)
        {
        //Draw the POLYLINEDATA into the bitmap.
        hObj=SelectObject(hMemDC, hBmp);
        Draw(hMemDC, FALSE, TRUE, &rc, NULL);
        SelectObject(hMemDC, hObj);
        }

    DeleteDC(hMemDC);
    ReleaseDC(m_hWnd, hDC);

    *phBmp=hBmp;
    return NOERROR;
    }

//RenderMetafile not necessary--now part of RenderMetafilePict.

/*
 * CPolyline::RenderMetafilePict
 *
 * Purpose:
 *  Renders the current Polyline into a METAFILEPICT structure in
 *  global memory.
 *
 * Parameters:
 *  phMem           HGLOBAL * in which to return the
 *                  METAFILEPICT.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::RenderMetafilePict(HGLOBAL *phMem)
    {
    HGLOBAL         hMem;
    HMETAFILE       hMF;
    LPMETAFILEPICT  pMF;
    RECT            rc;
    HDC             hDC;

    if (NULL==phMem)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    //Create a memory metafile and return its handle.
    hDC=(HDC)CreateMetaFile(NULL);

    if (NULL==hDC)
        return ResultFromScode(STG_E_MEDIUMFULL);

    SetMapMode(hDC, MM_ANISOTROPIC);
    GetClientRect(m_hWnd, &rc);
    SetWindowOrgEx(hDC, 0, 0, NULL);
    SetWindowExtEx(hDC, rc.right, rc.bottom, NULL);

    Draw(hDC, TRUE, TRUE, &rc, NULL);
    hMF=CloseMetaFile(hDC);

    if (NULL==hMF)
        return ResultFromScode(STG_E_MEDIUMFULL);

    //Allocate the METAFILEPICT structure.
    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
        , sizeof(METAFILEPICT));

    if (NULL==hMem)
        {
        DeleteMetaFile(hMF);
        return ResultFromScode(E_FAIL);
        }

    /*
     * Global lock only fails in PMODE if the selector is invalid
     * (like it was discarded) or references a 0 length segment,
     * neither of which can happen here.
     */
    pMF=(LPMETAFILEPICT)GlobalLock(hMem);

    pMF->hMF=hMF;
    pMF->mm=MM_ANISOTROPIC;

    //Insert the extents in MM_HIMETRIC units.
    GetClientRect(m_hWnd, &rc);
    RectConvertMappings(&rc, FALSE);
    pMF->xExt=rc.right;
    pMF->yExt=rc.bottom;

    GlobalUnlock(hMem);

    *phMem=hMem;
    return NOERROR;
    }

/*
 * CPolyline::Instance
 *
 * Purpose:
 *  Provides access to the module instance.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HINSTANCE       The instance handle of the module.
 */

HINSTANCE CPolyline::Instance(void)
    {
    return m_hInst;
    }

/*
 * CPolyline::String
 *
 * Purpose:
 *  Provides string lookup in the Polyline string table.
 *
 * Parameters:
 *  uID             UINT of the string to return.
 *
 * Return Value:
 *  LPTSTR          Pointer to the string.
 */

LPTSTR CPolyline::String(UINT uID)
    {
    return PSZ(uID);
    }

/*
 * CPolyline::SendAdvise
 *
 * Purpose:
 *  Calls the appropriate IOleClientSite or IAdviseSink member
 *  function for various events such as closure, renaming, etc.
 *
 * Parameters:
 *  uCode           UINT OBJECTCODE_* identifying the notification.
 *
 * Return Value:
 *  None
 */

void CPolyline::SendAdvise(UINT uCode)
    {
    DWORD       dwAspect=DVASPECT_CONTENT | DVASPECT_THUMBNAIL;

    switch (uCode)
        {
        case OBJECTCODE_SAVED:
            if (NULL!=m_pIOleAdviseHolder)
                m_pIOleAdviseHolder->SendOnSave();
            break;

        case OBJECTCODE_CLOSED:
            if (NULL!=m_pIOleAdviseHolder)
                m_pIOleAdviseHolder->SendOnClose();

            break;

        case OBJECTCODE_RENAMED:
            //Call IOleAdviseHolder::SendOnRename (later)
            break;

        case OBJECTCODE_SAVEOBJECT:
            if (m_fDirty && NULL!=m_pIOleClientSite)
                m_pIOleClientSite->SaveObject();

            m_fDirty=FALSE;
            break;

        case OBJECTCODE_DATACHANGED:
            m_fDirty=TRUE;

            //No flags are necessary here.
            if (NULL!=m_pIDataAdviseHolder)
                {
                m_pIDataAdviseHolder->SendOnDataChange
                    (m_pImpIDataObject, 0, 0);
                }

            if (NULL!=m_pIAdviseSink
                & (dwAspect & m_dwAdviseAspects))
                {
                m_pIAdviseSink->OnViewChange(dwAspect
                    & m_dwAdviseAspects, 0);
                }

            break;

        case OBJECTCODE_SHOWWINDOW:
            if (NULL!=m_pIOleClientSite)
                m_pIOleClientSite->OnShowWindow(TRUE);

            break;

        case OBJECTCODE_HIDEWINDOW:
            if (NULL!=m_pIOleClientSite)
                m_pIOleClientSite->OnShowWindow(FALSE);

            break;

        case OBJECTCODE_SHOWOBJECT:
            if (NULL!=m_pIOleClientSite)
                m_pIOleClientSite->ShowObject();

            break;
        }

    return;
    }


POLYLINE.H

/*
 * POLYLINE.H
 * Polyline Component Chapter 21
 *
 * Definitions and function prototypes
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _POLYLINE_H_
#define _POLYLINE_H_

#define INC_CLASSLIB
#define INC_CONTROLS
//CHAPTER21MOD
#define INC_OLEUI
#define CHAPTER21
//CHAPTER21MOD
#include <inole.h>
#include <ipoly10.h>
#include "resource.h"

//Classname
#define SZCLASSPOLYLINE             TEXT("polyline")

//Stream Name that holds the data
#define SZSTREAM                    OLETEXT("CONTENTS")

#define SZPOLYFRAMETITLE            TEXT("Polyline Figure in %s")

//Magic number to add to aspects returned from IViewObject::Freeze
#define FREEZE_KEY_OFFSET           0x0723

#define HIMETRIC_PER_INCH           2540
#define CCHPATHMAX                  256

//Window extra bytes and offsets
#define CBPOLYLINEWNDEXTRA          (sizeof(LONG))
#define PLWL_STRUCTURE              0

//DLLPOLY.CPP
int PASCAL LibMain(HINSTANCE, WORD, WORD, LPSTR);

//This class factory object creates Polyline objects.

class CPolylineClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;

    public:
        CPolylineClassFactory(void);
        ~CPolylineClassFactory(void);

        //IUnknown members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP         CreateInstance(LPUNKNOWN, REFIID
                                 , PPVOID);
        STDMETHODIMP         LockServer(BOOL);
    };

typedef CPolylineClassFactory *PCPolylineClassFactory;

//POLYWIN.CPP
LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM, LPARAM);

#ifdef WIN32
#define PROP_POINTER    TEXT("Pointer")
#else
#define PROP_SELECTOR   "Selector"
#define PROP_OFFSET     "Offset"
#endif

BOOL APIENTRY PolyDlgProc(HWND, UINT, WPARAM, LPARAM);

//Forward class references
class CImpIPolyline;
typedef class CImpIPolyline *PCImpIPolyline;

class CImpIConnPtCont;
typedef CImpIConnPtCont *PCImpIConnPtCont;

class CConnectionPoint;
typedef CConnectionPoint *PCConnectionPoint;

class CImpIPersistStorage;
typedef class CImpIPersistStorage *PCImpIPersistStorage;

class CImpIPersistStreamInit;
typedef class CImpIPersistStreamInit *PCImpIPersistStreamInit;

class CImpIDataObject;
typedef class CImpIDataObject *PCImpIDataObject;

class CImpIOleObject;
typedef class CImpIOleObject *PCImpIOleObject;

class CImpIViewObject;
typedef class CImpIViewObject *PCImpIViewObject;

class CImpIRunnableObject;
typedef class CImpIRunnableObject *PCImpIRunnableObject;

//CHAPTER21MOD
class CImpIExternalConnection;
typedef class CImpIExternalConnection *PCImpIExternalConnection;
//End CHAPTER21MOD

//POLYLINE.CPP
class CPolyline : public IUnknown
    {
    friend LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM
        , LPARAM);

    friend class CImpIPolyline;
    friend class CImpIConnPtCont;
    friend class CConnectionPoint;
    friend class CImpIPersistStorage;
    friend class CImpIPersistStreamInit;
    friend class CImpIDataObject;

    friend BOOL APIENTRY PolyDlgProc(HWND, UINT, WPARAM, LPARAM);
    friend class CImpIOleObject;
    friend class CImpIViewObject;
    friend class CImpIRunnableObject;

    //CHAPTER21MOD
    friend class CImpIExternalConnection;
    //End CHAPTER21MOD

    protected:
        HWND            m_hWnd;
        HINSTANCE       m_hInst;

        ULONG           m_cRef;         //Object reference count
        LPUNKNOWN       m_pUnkOuter;    //Controlling Unknown
        PFNDESTROYED    m_pfnDestroy;   //Function called on closure
        BOOL            m_fDirty;       //Have we changed?
        POLYLINEDATA    m_pl;           //Our actual data

        //Contained interfaces
        PCImpIPolyline          m_pImpIPolyline;
        PCImpIConnPtCont        m_pImpIConnPtCont;
        PCImpIPersistStorage    m_pImpIPersistStorage;
        PCImpIPersistStreamInit m_pImpIPersistStreamInit;
        PCImpIDataObject        m_pImpIDataObject;

        /*
         * Connected sink:  only one per object.  We get this
         * through our connection point.
         */
        IPolylineAdviseSink10  *m_pAdv;
        PCConnectionPoint       m_pConnPt;

        PCStringTable   m_pST;          //Object strings
        UINT            m_cf;           //Object clipboard format
        CLSID           m_clsID;        //Current CLSID

        //We have to hold these for IPersistStorage::Save
        LPSTORAGE       m_pIStorage;
        LPSTREAM        m_pIStream;

        LPDATAADVISEHOLDER  m_pIDataAdviseHolder;

        //These are default handler interfaces we use
        LPUNKNOWN           m_pDefIUnknown;
        LPVIEWOBJECT2       m_pDefIViewObject;
        LPPERSISTSTORAGE    m_pDefIPersistStorage;
        LPDATAOBJECT        m_pDefIDataObject;

        //Implemented and used interfaces
        PCImpIOleObject     m_pImpIOleObject;       //Implemented
        LPOLEADVISEHOLDER   m_pIOleAdviseHolder;    //Used

        LPOLECLIENTSITE     m_pIOleClientSite;      //Used

        PCImpIViewObject    m_pImpIViewObject;      //Implemented
        LPADVISESINK        m_pIAdviseSink;         //Used
        DWORD               m_dwFrozenAspects;      //Freeze
        DWORD               m_dwAdviseAspects;      //SetAdvise
        DWORD               m_dwAdviseFlags;        //SetAdvise

        POLYLINEDATA        m_plContent;            //For freezing
        POLYLINEDATA        m_plThumbnail;          //For freezing

        PCImpIRunnableObject m_pImpIRunnableObject; //Implemented
        HWND                m_hDlg;                 //Editing window

        //CHAPTER21MOD
        PCImpIExternalConnection m_pImpIExternalConnection; //Implemented
        BOOL                     m_fLockContainer;
        DWORD                    m_dwRegROT;
        //End CHAPTER21MOD

    protected:
        void      PointScale(LPRECT, LPPOINTS, BOOL);
        void      Draw(HDC, BOOL, BOOL, LPRECT, PPOLYLINEDATA);
        HINSTANCE Instance(void);
        LPTSTR    String(UINT);
        void      SendAdvise(UINT);
        void      RectConvertMappings(LPRECT, BOOL);

        /*
         * These members pulled from IPolyline now serve as a
         * central store for this functionality to be used from
         * other interfaces like IPersistStorage and IDataObject.
         * Other interfaces later may also use them.
         */
        STDMETHODIMP DataSet(PPOLYLINEDATA, BOOL, BOOL);
        STDMETHODIMP DataGet(PPOLYLINEDATA);

        STDMETHODIMP RenderNative(HGLOBAL *);
        STDMETHODIMP RenderBitmap(HBITMAP *);
        STDMETHODIMP RenderMetafilePict(HGLOBAL *);

    public:
        CPolyline(LPUNKNOWN, PFNDESTROYED, HINSTANCE);
        ~CPolyline(void);

        BOOL      Init(void);

        //Non-delegating object IUnknown
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
    };

typedef CPolyline *PCPolyline;

//Codes for CPolyline::SendAdvise
//......Code.....................Method called in CPolyline::SendAdvise
#define OBJECTCODE_SAVED       0 //IOleAdviseHolder::SendOnSave
#define OBJECTCODE_CLOSED      1 //IOleAdviseHolder::SendOnClose
#define OBJECTCODE_RENAMED     2 //IOleAdviseHolder::SendOnRename
#define OBJECTCODE_SAVEOBJECT  3 //IOleClientSite::SaveObject
#define OBJECTCODE_DATACHANGED 4 //IDataAdviseHolder::SendOnDataChange
#define OBJECTCODE_SHOWWINDOW  5 //IOleClientSite::OnShowWindow(TRUE)
#define OBJECTCODE_HIDEWINDOW  6 //IOleClientSite::OnShowWindow(FALSE)
#define OBJECTCODE_SHOWOBJECT  7 //IOleClientSite::ShowObject

//Interface implementation contained in the Polyline.

class CImpIPolyline : public IPolyline10
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIPolyline(PCPolyline, LPUNKNOWN);
        ~CImpIPolyline(void);

        //IUnknown members.
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //Manipulation members:
        STDMETHODIMP Init(HWND, LPRECT, DWORD, UINT);
        STDMETHODIMP New(void);
        STDMETHODIMP Undo(void);
        STDMETHODIMP Window(HWND *);

        STDMETHODIMP RectGet(LPRECT);
        STDMETHODIMP SizeGet(LPRECT);
        STDMETHODIMP RectSet(LPRECT, BOOL);
        STDMETHODIMP SizeSet(LPRECT, BOOL);

        STDMETHODIMP ColorSet(UINT, COLORREF, COLORREF *);
        STDMETHODIMP ColorGet(UINT, COLORREF *);

        STDMETHODIMP LineStyleSet(UINT, UINT *);
        STDMETHODIMP LineStyleGet(UINT *);
    };

class CImpIConnPtCont : public IConnectionPointContainer
    {
    private:
        ULONG               m_cRef;      //Interface ref count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIConnPtCont(PCPolyline, LPUNKNOWN);
        ~CImpIConnPtCont(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(DWORD) AddRef(void);
        STDMETHODIMP_(DWORD) Release(void);

        //IConnectionPointContainer members
        STDMETHODIMP EnumConnectionPoints(IEnumConnectionPoints **);
    STDMETHODIMP FindConnectionPoint(REFIID, IConnectionPoint **);
    };

/*
 * This connection point will only allow one connection,
 * so it always returns the same connection key.  It also
 * doesn't bother to implement enumerations.
 */

#define CCONNMAX    1
#define ADVISEKEY   72388       //Arbitrary

class CConnectionPoint : public IConnectionPoint
    {
    private:
        ULONG           m_cRef;     //Object reference count
        PCPolyline      m_pObj;     //Containing object.

    public:
        CConnectionPoint(PCPolyline);
        ~CConnectionPoint(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IConnectionPoint members
        STDMETHODIMP GetConnectionInterface(IID *);
        STDMETHODIMP GetConnectionPointContainer
            (IConnectionPointContainer **);
        STDMETHODIMP Advise(LPUNKNOWN, DWORD *);
        STDMETHODIMP Unadvise(DWORD);
        STDMETHODIMP EnumConnections(IEnumConnections **);
    };

class CImpIPersistStorage : public IPersistStorage
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown
        PSSTATE             m_psState;   //Storage state

    public:
        CImpIPersistStorage(PCPolyline, LPUNKNOWN);
        ~CImpIPersistStorage(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP InitNew(LPSTORAGE);
        STDMETHODIMP Load(LPSTORAGE);
        STDMETHODIMP Save(LPSTORAGE, BOOL);
        STDMETHODIMP SaveCompleted(LPSTORAGE);
        STDMETHODIMP HandsOffStorage(void);
    };

class CImpIPersistStreamInit : public IPersistStreamInit
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIPersistStreamInit(PCPolyline, LPUNKNOWN);
        ~CImpIPersistStreamInit(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP Load(LPSTREAM);
        STDMETHODIMP Save(LPSTREAM, BOOL);
        STDMETHODIMP GetSizeMax(ULARGE_INTEGER *);
        STDMETHODIMP InitNew(void);
    };

//IDATAOBJ.CPP
class CImpIDataObject : public IDataObject
    {
    private:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIDataObject(PCPolyline, LPUNKNOWN);
        ~CImpIDataObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IDataObject members
        STDMETHODIMP GetData(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP GetDataHere(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP QueryGetData(LPFORMATETC);
        STDMETHODIMP GetCanonicalFormatEtc(LPFORMATETC,LPFORMATETC);
        STDMETHODIMP SetData(LPFORMATETC, LPSTGMEDIUM, BOOL);
        STDMETHODIMP EnumFormatEtc(DWORD, LPENUMFORMATETC *);
        STDMETHODIMP DAdvise(LPFORMATETC, DWORD, LPADVISESINK
            , DWORD *);
        STDMETHODIMP DUnadvise(DWORD);
        STDMETHODIMP EnumDAdvise(LPENUMSTATDATA *);
    };

//IENUMFE.CPP
class CEnumFormatEtc : public IEnumFORMATETC
    {
    private:
        ULONG           m_cRef;
        LPUNKNOWN       m_pUnkRef;
        ULONG           m_iCur;
        ULONG           m_cfe;
        LPFORMATETC     m_prgfe;

    public:
        CEnumFormatEtc(LPUNKNOWN, ULONG, LPFORMATETC);
        ~CEnumFormatEtc(void);

        //IUnknown members that delegate to m_pUnkRef.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IEnumFORMATETC members
        STDMETHODIMP Next(ULONG, LPFORMATETC, ULONG *);
        STDMETHODIMP Skip(ULONG);
        STDMETHODIMP Reset(void);
        STDMETHODIMP Clone(IEnumFORMATETC **);
    };

typedef CEnumFormatEtc *PCEnumFormatEtc;

class CImpIOleObject : public IOleObject
    {
    private:
        ULONG           m_cRef;
        PCPolyline      m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIOleObject(PCPolyline, LPUNKNOWN);
        ~CImpIOleObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IOleObject members
        STDMETHODIMP SetClientSite(LPOLECLIENTSITE);
        STDMETHODIMP GetClientSite(LPOLECLIENTSITE *);
        STDMETHODIMP SetHostNames(LPCOLESTR, LPCOLESTR);
        STDMETHODIMP Close(DWORD);
        STDMETHODIMP SetMoniker(DWORD, LPMONIKER);
        STDMETHODIMP GetMoniker(DWORD, DWORD, LPMONIKER *);
        STDMETHODIMP InitFromData(LPDATAOBJECT, BOOL, DWORD);
        STDMETHODIMP GetClipboardData(DWORD, LPDATAOBJECT *);
        STDMETHODIMP DoVerb(LONG, LPMSG, LPOLECLIENTSITE, LONG
                         , HWND, LPCRECT);
        STDMETHODIMP EnumVerbs(LPENUMOLEVERB *);
        STDMETHODIMP Update(void);
        STDMETHODIMP IsUpToDate(void);
        STDMETHODIMP GetUserClassID(CLSID *);
        STDMETHODIMP GetUserType(DWORD, LPOLESTR *);
        STDMETHODIMP SetExtent(DWORD, LPSIZEL);
        STDMETHODIMP GetExtent(DWORD, LPSIZEL);
        STDMETHODIMP Advise(LPADVISESINK, DWORD *);
        STDMETHODIMP Unadvise(DWORD);
        STDMETHODIMP EnumAdvise(LPENUMSTATDATA *);
        STDMETHODIMP GetMiscStatus(DWORD, DWORD *);
        STDMETHODIMP SetColorScheme(LPLOGPALETTE);
    };

//IVIEWOBJ.CPP
class CImpIViewObject : public IViewObject2
    {
    private:
        ULONG           m_cRef;
        PCPolyline      m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIViewObject(PCPolyline, LPUNKNOWN);
        ~CImpIViewObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IViewObject members
        STDMETHODIMP Draw(DWORD, LONG, LPVOID
            , DVTARGETDEVICE *, HDC, HDC, LPCRECTL
            , LPCRECTL, BOOL (CALLBACK *)(DWORD), DWORD);
        STDMETHODIMP GetColorSet(DWORD, LONG, LPVOID
            , DVTARGETDEVICE *, HDC, LPLOGPALETTE *);
        STDMETHODIMP Freeze(DWORD, LONG, LPVOID, LPDWORD);
        STDMETHODIMP Unfreeze(DWORD);
        STDMETHODIMP SetAdvise(DWORD, DWORD, LPADVISESINK);
        STDMETHODIMP GetAdvise(LPDWORD, LPDWORD, LPADVISESINK *);
        STDMETHODIMP GetExtent(DWORD, LONG, DVTARGETDEVICE *
            , LPSIZEL);
    };

class CImpIRunnableObject : public IRunnableObject
    {
    protected:
        ULONG           m_cRef;
        PCPolyline      m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIRunnableObject(PCPolyline, LPUNKNOWN);
        ~CImpIRunnableObject(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetRunningClass(LPCLSID);
        STDMETHODIMP Run(LPBINDCTX);
        STDMETHODIMP_(BOOL) IsRunning(void);
        STDMETHODIMP LockRunning(BOOL, BOOL);
        STDMETHODIMP SetContainedObject(BOOL);
    };

//CHAPTER21MOD
class CImpIExternalConnection : public IExternalConnection
    {
    protected:
        ULONG           m_cRef;
        PCPolyline      m_pObj;
        LPUNKNOWN       m_pUnkOuter;
        DWORD           m_cLockStrong;

    public:
        CImpIExternalConnection(PCPolyline, LPUNKNOWN);
        ~CImpIExternalConnection(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(DWORD) AddConnection(DWORD, DWORD);
        STDMETHODIMP_(DWORD) ReleaseConnection(DWORD, DWORD, BOOL);
    };
//End CHAPTER21MOD

#endif  //_POLYLINE_H_


POLYWIN.CPP

/*
 * POLYWIN.CPP
 * Polyline Component Chapter 21
 *
 * Window procedure for the polyline drawing window and support
 * functions.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * PolylineWndProc
 *
 * Purpose:
 *  Window procedure for the polyline drawing window.
 */

LRESULT APIENTRY PolylineWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCPolyline      ppl;
    PAINTSTRUCT     ps;
    HDC             hDC;
    POINTS          pt;
    RECT            rc;

    ppl=(PCPolyline)GetWindowLong(hWnd, PLWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppl=(PCPolyline)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PLWL_STRUCTURE, (LONG)ppl);
            ppl->m_hWnd=hWnd;
            break;

        case WM_PAINT:
            hDC=BeginPaint(hWnd, &ps);
            GetClientRect(hWnd, &rc);
            ppl->Draw(hDC, FALSE, TRUE, &rc, NULL);
            EndPaint(hWnd, &ps);
            break;

        case WM_LBUTTONDOWN:
            //Stop if we are already at the limit.
            if (CPOLYLINEPOINTS==ppl->m_pl.cPoints)
                {
                MessageBeep(0);
                break;
                }

            //Convert the points into 0-32767 range
            GetClientRect(hWnd, &rc);
            pt=MAKEPOINTS(lParam);
            ppl->PointScale(&rc, &pt, FALSE);

            ppl->m_pl.rgpt[ppl->m_pl.cPoints++]=pt;

            //Draw the lines to this new point only.
            hDC=GetDC(hWnd);
            ppl->Draw(hDC, FALSE, FALSE, &rc, NULL);
            ReleaseDC(hWnd, hDC);

            if (NULL!=ppl->m_pAdv)
                ppl->m_pAdv->OnPointChange();

            ppl->SendAdvise(OBJECTCODE_DATACHANGED);
            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPolyline::Draw
 *
 * Purpose:
 *  Paints the current line in the polyline window.
 *
 * Parameters:
 *  hDC             HDC to draw on, a metafile or printer DC.
 *  fMetafile       BOOL indicating if hDC is a metafile or not,
 *                  so we can avoid operations that RIP.
 *  fEntire         BOOL indicating if we should draw the entire
 *                  figure or not.
 *  pRect           LPRECT defining the bounds in which to draw.
 *  ppl             PPOLYLINEDATA to draw.  If NULL, use current.
 *
 * Return Value:
 *  None
 */

void CPolyline::Draw(HDC hDC, BOOL fMetafile, BOOL fEntire
    , LPRECT pRect, PPOLYLINEDATA ppl)
    {
    HBRUSH          hBrush;
    HPEN            hPen;
    HGDIOBJ         hObj1, hObj2;
    UINT            i, j;
    int             nDC;
    POINTS          pt1,pt2;
    POINT           rgpt[CPOLYLINEPOINTS];

    if (NULL==ppl)
        ppl=&m_pl;

    nDC=SaveDC(hDC);

    for (i=0; i < ppl->cPoints; i++)
        {
        rgpt[i].x=ppl->rgpt[i].x;
        rgpt[i].y=ppl->rgpt[i].y;
        }

    //Printer and frozen differences handled in IViewObject::Draw

    hPen=CreatePen(ppl->iLineStyle, 1, ppl->rgbLine);
    hObj1=SelectObject(hDC, hPen);

    hBrush=CreateSolidBrush(ppl->rgbBackground);
    hObj2=SelectObject(hDC, hBrush);
    SetBkColor(hDC, ppl->rgbBackground);

    /*
     * Either draw the entire figure or just a single point.  The
     * entire figure also includes erasing the background completely,
     * since hDC may be a metafile DC.  Drawing a single point just
     * updates the figure for that new point.
     */
    if (fEntire || 0==ppl->cPoints)
        {
        //Erase the background for bitmaps and metafiles.
        SelectObject(hDC, GetStockObject(NULL_PEN));
        Rectangle(hDC, pRect->left, pRect->top, pRect->right+1
            , pRect->bottom+1);
        SelectObject(hDC, hPen);

        /*
         * If we are drawing the entire figure, then loop through
         * each point drawing a line to each successive point.
         */

        for (i=0; i < ppl->cPoints; i++)
            {
            for (j=i; j < ppl->cPoints; j++)
                {
                pt1.x=(short)rgpt[i].x;
                pt1.y=(short)rgpt[i].y;
                pt2.x=(short)rgpt[j].x;
                pt2.y=(short)rgpt[j].y;

                PointScale(pRect, &pt1, TRUE);
                PointScale(pRect, &pt2, TRUE);
                MoveToEx(hDC, pt1.x, pt1.y, NULL);
                LineTo(hDC, pt2.x, pt2.y);
                }
            }
        }
    else
        {
        /*
         * If we are only drawing the last point, just cycle once
         * through previous points.
         */

        //Get the last point entered in the array.
        j=ppl->cPoints-1;
        pt1.x=(short)rgpt[j].x;
        pt1.y=(short)rgpt[j].y;
        PointScale(pRect, &pt1, TRUE);

        for (i=0; i < j; i++)
            {
            pt2.x=(short)rgpt[i].x;
            pt2.y=(short)rgpt[i].y;
            PointScale(pRect, &pt2, TRUE);

            MoveToEx(hDC, pt1.x, pt1.y, NULL);
            LineTo(hDC, pt2.x, pt2.y);
            }
        }

    //If we have one point, draw a dot to indicate it's position.
    if (1==ppl->cPoints)
        {
        pt1.x=(short)rgpt[0].x;
        pt1.y=(short)rgpt[0].y;
        PointScale(pRect, &pt1, TRUE);
        SetPixel(hDC, pt1.x, pt1.y, m_pl.rgbLine);
        }

    SelectObject(hDC, hObj1);
    SelectObject(hDC, hObj2);
    DeleteObject(hBrush);
    DeleteObject(hPen);

    RestoreDC(hDC, nDC);
    return;
    }

/*
 * CPolyline::PointScale
 *
 * Purpose:
 *  Scales a point to or from a relative window coordinate to a
 *  0-32767 coordinate.
 *
 * Parameters:
 *  pRect           LPRECT of the window.
 *  ppt             LPPOINTS to convert
 *  fScaleToWindow  BOOL indicating direction of scaling.
 *
 * Return Value:
 *  None
 */

void CPolyline::PointScale(LPRECT pRect, LPPOINTS ppt
    , BOOL fScaleToWindow)
    {
    DWORD   cx, cy;

    //Window size
    cx=(DWORD)(pRect->right-pRect->left);
    cy=(DWORD)(pRect->bottom-pRect->top);

    //Prevent crashes
    if (0L==cx) cx=1;
    if (0L==cy) cy=1;

    //Must use DWORD to insure proper scaling.

    /*
     * As an in-proc server we may not have a rectangle where the
     * top left was (0,0) which was always true when we drew to
     * a Polyline window.  But this may be a container's hDC in
     * which case we'd better place the points in the container's
     * rectangle.  That is, we have to add/subtract pRect->left
     * and ->top in these calculations.
     */

    if (fScaleToWindow)
        {
        ppt->x=pRect->left+(UINT)(((DWORD)ppt->x*cx) >> 15);
        ppt->y=pRect->top+(UINT)(((DWORD)ppt->y*cy)  >> 15);
        }
    else
        {
        ppt->x=(UINT)(((DWORD)(ppt->x - pRect->left) << 15)/cx);
        ppt->y=(UINT)(((DWORD)(ppt->y - pRect->top)  << 15)/cy);
        }

    return;
    }

/*
 * PolyDlgProc
 *
 * Purpose:
 *  Dialog procedure for a window in which to display the Polyline
 *  for editing.  This pretty much handles all editing functionality
 *  for the embedded object.
 */

BOOL APIENTRY PolyDlgProc(HWND hDlg, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCPolyline      ppl=NULL;
    HWND            hWnd;
    RECT            rc;
    POINT           pt;
    UINT            uID, uTemp;
    UINT            cx, cy;

   #ifdef WIN32
    ppl=(PCPolyline)GetProp(hDlg, PROP_POINTER);
   #else
    WORD            w1, w2;

    w1=(WORD)GetProp(hDlg, PROP_SELECTOR);
    w2=(WORD)GetProp(hDlg, PROP_OFFSET);

    ppl=(PCPolyline)MAKELP(w1, w2);
   #endif

    switch (iMsg)
        {
        case WM_INITDIALOG:
            ppl=(PCPolyline)lParam;
            ppl->m_hDlg=hDlg;

           #ifdef WIN32
            //Properties are 32-bits in Win32
            SetProp(hDlg, PROP_POINTER, (HANDLE)ppl);
           #else
            SetProp(hDlg, PROP_SELECTOR, (HANDLE)SELECTOROF(ppl));
            SetProp(hDlg, PROP_OFFSET,   (HANDLE)OFFSETOF(ppl));
           #endif

            //Create the Polyline to exactly cover the static rect.
            hWnd=GetDlgItem(hDlg, ID_POLYLINERECT);
            GetWindowRect(hWnd, &rc);
            SETPOINT(pt, rc.left, rc.top);
            ScreenToClient(hDlg, &pt);

            //Set the polyline just within the black frame
            SetRect(&rc, pt.x, pt.y, pt.x+(rc.right-rc.left)
                , pt.y+(rc.bottom-rc.top));
            InflateRect(&rc, -1, -1);

            //Try to create the window.
            ppl->m_pImpIPolyline->Init(hDlg, &rc, WS_CHILD | WS_VISIBLE
                , ID_POLYLINE);

            //Set the initial line style radiobutton.
            ppl->m_pImpIPolyline->LineStyleGet(&uTemp);
            CheckRadioButton(hDlg, ID_LINESOLID, ID_LINEDASHDOTDOT
                , uTemp+ID_LINEMIN);

            //Don't set focus--it takes it away from the container.
            return FALSE;

        case WM_SHOWWINDOW:
            if (LOWORD(wParam))
                {
                //Center the dialog on the screen
                cx=GetSystemMetrics(SM_CXSCREEN);
                cy=GetSystemMetrics(SM_CYSCREEN);
                GetWindowRect(hDlg, &rc);
                SetWindowPos(hDlg, NULL, (cx-(rc.right-rc.left))/2
                    , (cy-(rc.bottom-rc.top))/2, 0, 0, SWP_NOZORDER
                    | SWP_NOSIZE);

                //We didn't SetFocus from WM_INITDIALOG.  Do it now.
                SetFocus(GetDlgItem(hDlg, IDOK));
                }

            break;

        case WM_COMMAND:
            uID=LOWORD(wParam);

            switch (uID)
                {
                case IDOK:
                    //Close the dialog, but save first.
                    if (NULL!=ppl)
                        {
                        //IOleObject::Close does what we want
                        ppl->m_pImpIOleObject->Close
                            (OLECLOSE_SAVEIFDIRTY);
                        }

                    break;

                case ID_UNDO:
                    if (NULL!=ppl)
                        ppl->m_pImpIPolyline->Undo();
                    break;

                case ID_COLORLINE:
                case ID_COLORBACK:
                    if (NULL!=ppl)
                        {
                        UINT            i;
                        COLORREF        rgColors[16];
                        CHOOSECOLOR     cc;

                        //Invoke the color chooser for either color
                        uTemp=(ID_COLORBACK==uID)
                            ? POLYLINECOLOR_BACKGROUND
                            : POLYLINECOLOR_LINE;

                        for (i=0; i<16; i++)
                            rgColors[i]=RGB(0, 0, i*16);

                        memset(&cc, 0, sizeof(CHOOSECOLOR));
                        cc.lStructSize=sizeof(CHOOSECOLOR);
                        cc.lpCustColors=rgColors;
                        cc.hwndOwner=hDlg;
                        cc.Flags=CC_RGBINIT;
                        ppl->m_pImpIPolyline->ColorGet(uTemp
                            , &cc.rgbResult);

                        if (ChooseColor(&cc))
                            {
                            //rgColor is just some COLORREF pointer
                            ppl->m_pImpIPolyline->ColorSet(uTemp
                                , cc.rgbResult, rgColors);
                            }
                        }
                    break;

                case ID_LINESOLID:
                case ID_LINEDASH:
                case ID_LINEDOT:
                case ID_LINEDASHDOT:
                case ID_LINEDASHDOTDOT:
                    if (NULL!=ppl)
                        {
                        ppl->m_pImpIPolyline
                            ->LineStyleSet(uID-ID_LINEMIN, &uTemp);
                        }

                    break;
                }
            break;

        case WM_DESTROY:
           #ifdef WIN32
            RemoveProp(hDlg, PROP_POINTER);
           #else
            RemoveProp(hDlg, PROP_SELECTOR);
            RemoveProp(hDlg, PROP_OFFSET);
           #endif
            break;

        case WM_CLOSE:
            //This will do the IDOK handling, then send POLYM_CLOSE
            SendCommand(hDlg, IDOK, 0, NULL);
            break;

        case POLYM_CLOSE:
            ShowWindow(hDlg, SW_HIDE);
            ppl->SendAdvise(OBJECTCODE_HIDEWINDOW);
            break;
        }

    return FALSE;
    }


RESOURCE.H

/*
 * RESOURCE.H
 * Polyline Component Chapter 21
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Object stringtable IDs
#define IDS_POLYLINEMIN     0
#define IDS_STORAGEFORMAT   0
#define IDS_USERTYPE        1

#define IDS_CLOSECAPTION    2
#define IDS_CLOSEPROMPT     3
#define IDS_POLYLINEMAX     3

#define IDR_ICON            1

#define IDD_EDITDIALOG      1

#define ID_UNDO             100
#define ID_COLORBACK        101
#define ID_COLORLINE        102
#define ID_GROUPCOLORS      103
#define ID_GROUPPREVIEW     104
#define ID_GROUPSTYLES      105
#define ID_GROUPFIGURE      106
#define ID_POLYLINERECT     107
#define ID_POLYLINE         108

#define ID_LINEMIN          200
#define ID_LINESOLID        200     //(ID_LINEMIN+PS_SOLID)
#define ID_LINEDASH         201     //(ID_LINEMIN+PS_DASH)
#define ID_LINEDOT          202     //(ID_LINEMIN+PS_DOT)
#define ID_LINEDASHDOT      203     //(ID_LINEMIN+PS_DASHDOT)
#define ID_LINEDASHDOTDOT   204     //(ID_LINEMIN+PS_DASHDOTDOT)

//Message to close the dialog
#define POLYM_CLOSE         (WM_USER+1000)

#endif //_RESOURCE_H_


CLIENT.CPP

/*
 * CLIENT.CPP
 * Patron Chapter 22
 *
 * Implementation of the CPatronClient class that just makes sure
 * we get a CPatronDoc on doc creation and that we initialize fully.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPatronClient::CPatronClient
 * CPatronClient::~CPatronClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame to the frame object.
 */

CPatronClient::CPatronClient(HINSTANCE hInst, PCFrame pFR)
    : CClient(hInst, pFR)
    {
    return;
    }

CPatronClient::~CPatronClient(void)
    {
    return;
    }

/*
 * CPatronClient::CreateCDocument
 *
 * Purpose:
 *  Constructs a new document specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CPatronClient::CreateCDocument(void)
    {
    return (PCDocument)(new CPatronDoc(m_hInst, m_pFR, m_pAdv));
    }

//CHAPTER22MOD
/*
 * CPatronClient::SetMenu
 *
 * Purpose:
 *  Changes the frame-level menu, isolating the rest of the
 *  application from MDI/SDI considerations.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame window.
 *  hMenu           HMENU to set in the frame for the current
 *                  document.
 *  hMenuWin        HMENU of the window menu.
 *
 * Return Value:
 *  None
 */

void CPatronClient::SetMenu(HWND hWndFrame, HMENU hMenu
    , HMENU hMenuWin)
    {
   #ifdef MDI
     MDISETMENU(m_hWnd, hMenu, hMenuWin);
     MDIREFRESHMENU(m_hWnd);
   #else
    if (NULL!=hMenu)
        ::SetMenu(hWndFrame, hMenu);
   #endif

    DrawMenuBar(hWndFrame);
    return;
    }

/*
 * CPatronClient::MoveWithoutFamily
 *
 * Purpose:
 *  This specific in-place activation function applies moves the
 *  client window but to leave all child windows within it exactly
 *  where they are to keep in-place objects in the same absolute
 *  screen position.
 *
 * Parameters:
 *  prc             LPRECT containing the new space for the client
 *  dx, dy          ints specifying how much to move the client
 *
 * Return Value:
 *  None
 */

void CPatronClient::MoveWithoutFamily(LPRECT prc, int dx, int dy)
    {
    RECT        rc;
    HWND        hWndFrame;
    HWND        hWnd;
    POINT       pt;

    hWndFrame=GetParent(m_hWnd);
    SendMessage(hWndFrame, WM_SETREDRAW, FALSE, 0L);

    ShowWindow(m_hWnd, SW_HIDE);
    SetWindowPos(m_hWnd, NULL, prc->left, prc->top
        , prc->right-prc->left, prc->bottom-prc->top
        , SWP_NOZORDER | SWP_NOACTIVATE);

    //Move all children of the client
    hWnd=GetWindow(m_hWnd, GW_CHILD);

    while (NULL!=hWnd)
        {
        GetWindowRect(hWnd, &rc);
        SETPOINT(pt, rc.left, rc.top);
        ScreenToClient(m_hWnd, &pt);

        if (pt.x!=dx && pt.y!=dy && !IsZoomed(hWnd))
            {
            //Move window in the opposite direction as the client
            SetWindowPos(hWnd, NULL, pt.x-dx, pt.y-dy
                , rc.right-rc.left, rc.bottom-rc.top
                , SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE);
            }

        hWnd=GetWindow(hWnd, GW_HWNDNEXT);
        }

    SendMessage(hWndFrame, WM_SETREDRAW, TRUE, 0L);
    ShowWindow(m_hWnd, SW_SHOW);

    return;
    }

/*
 * CPatronClient::CallContextHelpOnDocuments
 *
 * Purpose:
 *  Calls IOleInPlaceUIWindow->ContextSensitiveHelp for each
 *  document window as required in an MDI container.  This does
 *  nothing in SDI.
 *
 * Parameters:
 *  fEnterMode      BOOl to pass to the documents
 *
 * Return Value:
 *  None
 */

void CPatronClient::CallContextHelpOnDocuments(BOOL fEnterMode)
    {
    LPOLEINPLACEUIWINDOW    pUIWin;
    HWND                    hWndT;
    PCPatronDoc             pDoc;

    //Loop through the documents calling their functions.
    hWndT=GetWindow(m_hWnd, GW_CHILD);

    for ( ; hWndT; hWndT=GetWindow(hWndT, GW_HWNDNEXT))
        {
        //Skip if icon title window
        if (NULL!=GetWindow(hWndT, GW_OWNER))
            continue;

        pDoc=(PCPatronDoc)SendMessage(hWndT, DOCM_PDOCUMENT
            , 0, 0L);

        if (NULL==pDoc)
            continue;

        pDoc->QueryInterface(IID_IOleInPlaceUIWindow
            , (PPVOID)&pUIWin);

        if (NULL==pUIWin)
            continue;

        pUIWin->ContextSensitiveHelp(fEnterMode);
        pUIWin->Release();
        }

    return;
    }

//End CHAPTER22MOD


DOCUMENT.CPP

/*
 * DOCUMENT.CPP
 * Patron Chapter 22
 *
 * Implementation of the CPatronDoc derivation of CDocument that
 * manages pages for us.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"
#include <memory.h>
#include <dlgs.h>       //Pring Dlg button IDs

/*
 * CPatronDoc::CPatronDoc
 * CPatronDoc::~CPatronDoc
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame of the frame object.
 *  pAdv            PCDocumentAdviseSink to notify on events.
 */

CPatronDoc::CPatronDoc(HINSTANCE hInst, PCFrame pFR
    , PCDocumentAdviseSink pAdv)
    : CDocument(hInst, pFR, pAdv)
    {
    m_pPG=NULL;
    m_lVer=VERSIONCURRENT;
    m_pIStorage=NULL;
    m_fPrintSetup=TRUE;
    m_pDropTarget=NULL;

    //These CFSTR_* values are standard (defined in INC\CLASSLIB.H)
    m_cfEmbeddedObject=RegisterClipboardFormat
        (CFSTR_EMBEDDEDOBJECT);
    m_cfObjectDescriptor=RegisterClipboardFormat
        (CFSTR_OBJECTDESCRIPTOR);

    m_cfLinkSource=RegisterClipboardFormat(CFSTR_LINKSOURCE);
    m_cfLinkSrcDescriptor=RegisterClipboardFormat
        (CFSTR_LINKSRCDESCRIPTOR);

    m_fShowTypes=FALSE;
    m_cRef=1L;
    m_fRename=TRUE;
    m_dwRegROT=0L;
    m_dwRegROTWild=0L;
    m_pImpIPersistFile=NULL;
    m_pImpIOleItemContainer=NULL;

    //CHAPTER22MOD
    m_pImpIOleIPUIWindow=NULL;
    m_pIOleIPActiveObject=NULL;
    m_fNoObjectFrameTools=TRUE;
    //End CHAPTER22MOD

    return;
    }

CPatronDoc::~CPatronDoc(void)
    {
    INOLE_RevokeAsRunning(&m_dwRegROT);
    INOLE_RevokeAsRunning(&m_dwRegROTWild);

    DeleteInterfaceImp(m_pImpIPersistFile);
    DeleteInterfaceImp(m_pImpIOleItemContainer);

    if (NULL!=m_pPG)
        delete m_pPG;

    //CHAPTER22MOD
    DeleteInterfaceImp(m_pImpIOleIPUIWindow);
    //End CHAPTER22MOD

    ReleaseInterface(m_pIStorage);
    CoFreeUnusedLibraries();
    return;
    }

/*
 * CPatronDoc::QueryInterface
 * CPatronDoc::AddRef
 * CPatronDoc::Release
 */

STDMETHODIMP CPatronDoc::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    /*
     * WARNING:  Because the *ppv=this implicitly does
     * *ppv=(LPVOID)this, the this pointer is incorrectly cast
     * from a CPatronDoc pointer to LPVOID where we want an
     * IUnknown pointer instead.  CPatronDoc uses multiple
     * inheritance here which means that we have to explicitly
     * provide the typecast to select the base class like
     * IUnknown.  If you don't do this, calls to AddRef, for
     * example, might call your desctructor!
     */

    if (IID_IUnknown==riid)
        *ppv=(LPUNKNOWN)this;

    if (IID_IOleItemContainer==riid || IID_IOleContainer==riid
        || IID_IParseDisplayName==riid)
        *ppv=m_pImpIOleItemContainer;

    if (IID_IPersistFile==riid || IID_IPersist==riid)
        *ppv=m_pImpIPersistFile;

    //CHAPTER22MOD
    if (IID_IOleWindow==riid || IID_IOleInPlaceUIWindow==riid)
        *ppv=m_pImpIOleIPUIWindow;
    //End CHAPTER22MOD

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CPatronDoc::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPatronDoc::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    PostMessage(m_hWnd, WM_CLOSE, 0, 0);
    return 0;
    }

/*
 * CPatronDoc::Init
 *
 * Purpose:
 *  Initializes an already created document window.  The client
 *  actually creates the window for us, then passes that here for
 *  further initialization.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPatronDoc::Init(PDOCUMENTINIT pDI)
    {
    //Change the stringtable range to our customization.
    pDI->idsMin=IDS_DOCUMENTMIN;
    pDI->idsMax=IDS_DOCUMENTMAX;

    //Do default initialization
    if (!CDocument::Init(pDI))
        return FALSE;

    //Pages are created when we get a Load later.

    //Instantiate our interfaces
    m_pImpIPersistFile=new CImpIPersistFile(this, this);

    if (NULL==m_pImpIPersistFile)
        return FALSE;

    m_pImpIOleItemContainer=new CImpIOleItemContainer(this, this
        , TRUE);

    if (NULL==m_pImpIOleItemContainer)
        return FALSE;

    //CHAPTER22MOD
    m_pImpIOleIPUIWindow=new CImpIOleInPlaceUIWindow(this, this);

    if (NULL==m_pImpIOleIPUIWindow)
        return FALSE;
    //End CHAPTER22MOD

    return TRUE;
    }

/*
 * CPatronDoc::FMessageHook
 *
 * Purpose:
 *  Processes WM_SIZE for the document so we can resize the Pages
 *  window.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CPatronDoc::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    UINT        dx, dy;
    RECT        rc;

    *pLRes=0;

    //CHAPTER22MOD
    //CLASSLIB always sends MDIACTIVATE, regardless of MDI or SDI.
    if (WM_MDIACTIVATE==iMsg)
        {
        /*
         * Win32 has hWndActivate in lParam.  Win16 has it in
         * LOWORD(lParam).  (HWND)(UINT) handles both cases.
         */
        BOOL    fActivate=((HWND)(UINT)lParam==hWnd);

        if (NULL!=m_pIOleIPActiveObject)
            {
            //Hide our tools
            if (fActivate)
                g_pFR->ShowUIAndTools(m_fNoObjectFrameTools, FALSE);

            m_pIOleIPActiveObject->OnDocWindowActivate(fActivate);
            }
        else
            {
            //Restore frame-level UI if we're becoming active.
            if (fActivate)
                g_pFR->ReinstateUI();
            }
        }
    //End CHAPTER22MOD

    //Eat to prevent flickering
    if (WM_ERASEBKGND==iMsg)
        return TRUE;

    if (WM_SIZE==iMsg && NULL!=m_pPG)
        {
        dx=LOWORD(lParam);
        dy=HIWORD(lParam);

        if (SIZE_MINIMIZED!=wParam)
            {
            //Resize Pages window to fit the new document size.
            GetClientRect(hWnd, &rc);
            m_pPG->RectSet(&rc, FALSE);
            }

        //CHAPTER22MOD
        if (NULL!=m_pIOleIPActiveObject)
            {
            //Also tell in-place objects that document was resized.
            m_pIOleIPActiveObject->ResizeBorder(&rc
                , m_pImpIOleIPUIWindow, FALSE);
            }
        //End CHAPTER22MOD
        }

    if (WM_DESTROY==iMsg)
        {
        /*
         * We have to revoke the drop target here because the window
         * will be destroyed and the property forcefully removed
         * before we could do this in the destructor.
         */
        if (NULL!=m_pDropTarget)
            {
            RevokeDragDrop(m_hWnd);
            CoLockObjectExternal(m_pDropTarget, FALSE, TRUE);
            m_pDropTarget->Release();
            }

        return FALSE;
        }

    /*
     * We return FALSE even on WM_SIZE so we can let the default
     * procedure handle maximized MDI child windows appropriately.
     */
    return FALSE;
    }

/*
 * CPatronDoc::Clear
 *
 * Purpose:
 *  Sets all contents in the document back to defaults with no
 *  filename.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Clear(void)
    {
    //Completely reset the pages
    if (NULL!=m_pPG)
        m_pPG->StorageSet(NULL, FALSE, FALSE);

    CDocument::Clear();
    m_lVer=VERSIONCURRENT;
    return;
    }

/*
 * CPatronDoc::FDirtySet
 *
 * Purpose:
 *  Sets or clears the document 'dirty' flag returning the previous
 *  state of that same flag.  We override this to note change
 *  times in the running object table.
 *
 * Parameters:
 *  fDirty          BOOL indicating the new contents of the dirty
 *                  flag.
 *
 * Return Value:
 *  BOOL            Previous value of the dirty flag.
 */

BOOL CPatronDoc::FDirtySet(BOOL fDirty)
    {
    BOOL        fRet;

    fRet=CDocument::FDirtySet(fDirty);
    INOLE_NoteChangeTime(m_dwRegROT, NULL, NULL);
    INOLE_NoteChangeTime(m_dwRegROTWild, NULL, NULL);

    return fRet;
    }

/*
 * CPatronDoc::FDirtyGet
 *
 * Purpose:
 *  Returns the current dirty status of the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the file is clean, FALSE otherwise.
 */

BOOL CPatronDoc::FDirtyGet()
    {
    BOOL    fPageDirty;

    fPageDirty=m_pPG->FIsDirty();
    return m_fDirty | fPageDirty;
    }

/*
 * CPatronDoc::Delete
 *
 * Purpose:
 *  Removed the current object from the document.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Delete(void)
    {
    if (NULL!=m_pPG)
        m_pPG->TenantDestroy();

    CoFreeUnusedLibraries();
    return;
    }

/*
 * CPatronDoc::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected in this
 *  document for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU of the Edit menu.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryObjectSelected(HMENU hMenu)
    {
    return m_pPG->FQueryObjectSelected(hMenu);
    }

/*
 * CPatronDoc::Load
 *
 * Purpose:
 *  Loads a given document without any user interface overwriting
 *  the previous contents of the editor.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load.  Could be NULL
 *                  for an untitled document.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CPatronDoc::Load(BOOL fChangeFile, LPTSTR pszFile)
    {
    RECT        rc;
    LPSTORAGE   pIStorage;
    HRESULT     hr;
    CLSID       clsID;
    DWORD       dwMode=STGM_TRANSACTED | STGM_READWRITE
                    | STGM_SHARE_EXCLUSIVE;
    LPMONIKER   pmk;

    if (NULL==pszFile)
        {
        //Create a new temp file.
        hr=StgCreateDocfile(NULL, dwMode | STGM_CREATE
            | STGM_DELETEONRELEASE, 0, &pIStorage);

        //Mark this our class since we check with ReadClassStg.
        if (SUCCEEDED(hr))
            WriteClassStg(pIStorage, CLSID_PatronPages);
        }
    else
        {
        hr=StgOpenStorage(pszFile, NULL, dwMode, NULL, 0, &pIStorage);
        }

    if (FAILED(hr))
        return DOCERR_COULDNOTOPEN;

    //Check if this is our type of file and exit if not.
    hr=ReadClassStg(pIStorage, &clsID);

    if (FAILED(hr) || CLSID_PatronPages!=clsID)
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    //Attempt to create our contained Pages window.
    m_pPG=new CPages(m_hInst, m_cf);
    GetClientRect(m_hWnd, &rc);

    //CHAPTER22MOD
    //Always use WS_CLIPCHILDREN with in-place support.
    if (!m_pPG->Init(m_hWnd, &rc, WS_CLIPCHILDREN | WS_CHILD
        | WS_VISIBLE, ID_PAGES, NULL))
    //End CHAPTER22MOD
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    /*
     * This moniker registration insures that all the file opening
     * sequence will see this instance of the document as running
     * and be able to communicate with it.  This is so a link to
     * an embedding on the same page of this document will hook
     * up correctly.
     */
    if (NULL!=pszFile)
        {
        CreateFileMoniker(pszFile, &pmk);

        if (NULL!=pmk)
            {
            //This will be revoked and re-registered in Rename below
            INOLE_RegisterAsRunning(this, pmk, 0, &m_dwRegROT);
            pmk->Release();
            }
        }

    if (!m_pPG->StorageSet(pIStorage, FALSE, (NULL==pszFile)))
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    //Open the window up for drag-drop
    m_pDropTarget=new CDropTarget(this);

    if (NULL!=m_pDropTarget)
        {
        m_pDropTarget->AddRef();
        CoLockObjectExternal(m_pDropTarget, TRUE, FALSE);
        RegisterDragDrop(m_hWnd, m_pDropTarget);
        }

    m_pIStorage=pIStorage;
    Rename(pszFile);

    //Do initial setup if new file, otherwise Pages handles things.
    if (NULL==pszFile)
        {
        //Go initialize the Pages for the default printer.
        if (!PrinterSetup(NULL, TRUE))
            return DOCERR_COULDNOTOPEN;

        //Go create an initial page.
        m_pPG->PageInsert(0);
        }
    else
        {
        //Can't change an already saved configuration
        m_fPrintSetup=FALSE;
        }

    FDirtySet(FALSE);
    return DOCERR_NONE;
    }

/*
 * CPatronDoc::Save
 *
 * Purpose:
 *  Writes the file to a known filename, requiring that the user
 *  has previously used FileOpen or FileSaveAs in order to have
 *  a filename.
 *
 * Parameters:
 *  uType           UINT indicating the type of file the user
 *                  requested to save in the File Save As dialog.
 *  pszFile         LPTSTR under which to save.  If NULL, use the
 *                  current name.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CPatronDoc::Save(UINT uType, LPTSTR pszFile)
    {
    HRESULT     hr;
    LPSTORAGE   pIStorage;

    //Save or Save As with the same file is just a commit.
    if (NULL==pszFile
        || (NULL!=pszFile && 0==lstrcmpi(pszFile, m_szFile)))
        {
        WriteFmtUserTypeStg(m_pIStorage, m_cf
            , PSZ(IDS_CLIPBOARDFORMAT));

        //Insure pages are up to date.
        m_pPG->StorageUpdate(FALSE);

        //Commit everyting
        m_pIStorage->Commit(STGC_DEFAULT);

        FDirtySet(FALSE);
        return DOCERR_NONE;
        }

    /*
     * When we're given a name, open the storage, creating it new
     * if it does not exist or overwriting the old one.  Then CopyTo
     * from the current to the new, Commit the new, Release the old.
     */

    hr=StgCreateDocfile(pszFile, STGM_TRANSACTED | STGM_READWRITE
        | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

    if (FAILED(hr))
        return DOCERR_COULDNOTOPEN;

    WriteClassStg(pIStorage, CLSID_PatronPages);
    WriteFmtUserTypeStg(pIStorage, m_cf, PSZ(IDS_CLIPBOARDFORMAT));

    //Insure all pages are up-to-date.
    m_pPG->StorageUpdate(TRUE);

    //This also copies the CLSID we stuff in here on file creation.
    hr=m_pIStorage->CopyTo(NULL, NULL, NULL, pIStorage);

    if (FAILED(hr))
        {
        SCODE       sc;

        pIStorage->Release();
        sc=GetScode(hr);

        /*
         * If we failed because of low memory, use IRootStorage
         * to switch into the new file.
         */
        if (E_OUTOFMEMORY==sc)
            {
            LPROOTSTORAGE        pIRoot;

            //Delete file we already created
            DeleteFile(pszFile);

            if (FAILED(m_pIStorage->QueryInterface
                (IID_IRootStorage, (PPVOID)&pIRoot)))
                return DOCERR_WRITEFAILURE;

           #ifdef WIN32ANSI
            OLECHAR     szTemp[MAX_PATH];

            MultiByteToWideChar(CP_ACP, 0, pszFile, -1, szTemp, MAX_PATH);
            hr=pIRoot->SwitchToFile(szTemp);
           #else
            hr=pIRoot->SwitchToFile(pszFile);
           #endif
            pIRoot->Release();

            if (FAILED(hr))
                return DOCERR_WRITEFAILURE;

            //If successful, the Commit below finishes the save.
            pIStorage=m_pIStorage;
            m_pIStorage->AddRef();    //Matches Release below
            }
        }

    if (m_fRename)
        Rename(pszFile);    //Update caption bar.

    pIStorage->Commit(STGC_DEFAULT);

    /*
     * Revert changes on the original storage.  If this was a temp
     * file, it's deleted since we used STGM_DELETEONRELEASE.
     */
    m_pIStorage->Release();

    //Make this new storage current
    m_pIStorage=pIStorage;
    m_pPG->StorageSet(pIStorage, TRUE, FALSE);

    FDirtySet(FALSE);

    return DOCERR_NONE;
    }

/*
 * CPatronDoc::Clip
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard, optionally implementing Cut by deleting the
 *  data in the current window after rendering.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *  fCut            BOOL indicating cut (TRUE) or copy (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::Clip(HWND hWndFrame, BOOL fCut)
    {
    if (NULL==m_pPG)
        return FALSE;

    return m_pPG->TenantClip(fCut);
    }

/*
 * CPatronDoc::Paste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  to the current figure in the editor window.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::Paste(HWND hWndFrame)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet=FALSE;
    FORMATETC       fe;
    TENANTTYPE      tType;

    if (NULL==m_pPG)
        return FALSE;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    //Go get type and format we *can* paste, then actually paste it.
    if (FQueryPasteFromData(pIDataObject, &fe, &tType))
        {
        fRet=PasteFromData(pIDataObject, &fe, tType, NULL
            , 0L, TRUE);
        }

    pIDataObject->Release();
    return fRet;
    }

/*
 * CPatronDoc::FQueryPaste
 *
 * Purpose:
 *  Determines if we can paste data from the clipboard.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPaste(void)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    fRet=FQueryPasteFromData(pIDataObject, NULL, NULL);

    fRet |= FQueryPasteLinkFromData(pIDataObject, NULL, NULL);
    pIDataObject->Release();
    return fRet;
    }

/*
 * CPatronDoc::PasteSpecial
 *
 * Purpose:
 *  Retrieves a specific data format from the clipboard and sends
 *  it to the editor window appropriately.
 *
 *  Note that if this function is called, then the appropriate
 *  format is available because the Paste menu item is only
 *  enabled if the format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::PasteSpecial(HWND hWndFrame)
    {
    OLEUIPASTESPECIAL   ps;
    OLEUIPASTEENTRY     rgPaste[6];
    UINT                rgcf[1];        //For ps.m_arrLinkTypes
    DWORD               dwData=0;
    UINT                uTemp;
    BOOL                fRet=FALSE;

    if (NULL==m_pPG)
        return FALSE;

    memset(&ps, 0, sizeof(ps));

    if (FAILED(OleGetClipboard(&ps.lpSrcDataObj)))
        return FALSE;

    ps.cbStruct=sizeof(ps);
    ps.hWndOwner=hWndFrame;

    ps.dwFlags=PSF_SELECTPASTE;
    ps.arrPasteEntries=rgPaste;

    //Set up Paste Special descriptor arrays.
    SETDefFormatEtc(rgPaste[0].fmtetc, m_cf, TYMED_HGLOBAL);
    rgPaste[0].lpstrFormatName=PSZ(IDS_CLIPBOARDFORMAT);
    rgPaste[0].lpstrResultText=PSZ(IDS_PASTEASPATRON);
    rgPaste[0].dwFlags=OLEUIPASTE_PASTEONLY;

    //Embedded objects can be iconic displays if the user wants.
    SETDefFormatEtc(rgPaste[1].fmtetc, m_cfEmbeddedObject
        , TYMED_ISTORAGE);
    rgPaste[1].lpstrFormatName=PSZ(IDS_PASTEOBJECT);
    rgPaste[1].lpstrResultText=PSZ(IDS_PASTEASOBJECT);

    /*
     * CAUTION:  Use OLEUI_PASTE with embedded objects or else
     * this item will not show up in the dialog.  I learned this the
     * hard way (that is, after about 6 hours of pulling hair!).
     */
    rgPaste[1].dwFlags=OLEUIPASTE_PASTE | OLEUIPASTE_ENABLEICON;

    SETDefFormatEtc(rgPaste[2].fmtetc,CF_METAFILEPICT,TYMED_MFPICT);
    rgPaste[2].lpstrFormatName=PSZ(IDS_PASTEMETAFILE);
    rgPaste[2].lpstrResultText=PSZ(IDS_PASTEASMETAFILE);
    rgPaste[2].dwFlags=OLEUIPASTE_PASTEONLY;

    SETDefFormatEtc(rgPaste[3].fmtetc, CF_DIB, TYMED_HGLOBAL);
    rgPaste[3].lpstrFormatName=PSZ(IDS_PASTEDIB);
    rgPaste[3].lpstrResultText=PSZ(IDS_PASTEASDIB);
    rgPaste[3].dwFlags=OLEUIPASTE_PASTEONLY;

    SETDefFormatEtc(rgPaste[4].fmtetc, CF_BITMAP, TYMED_GDI);
    rgPaste[4].lpstrFormatName=PSZ(IDS_PASTEBITMAP);
    rgPaste[4].lpstrResultText=PSZ(IDS_PASTEASBITMAP);
    rgPaste[4].dwFlags=OLEUIPASTE_PASTEONLY;

    SETDefFormatEtc(rgPaste[5].fmtetc,m_cfLinkSource,TYMED_ISTREAM);
    rgPaste[5].lpstrFormatName=PSZ(IDS_PASTELINK);
    rgPaste[5].lpstrResultText=PSZ(IDS_PASTEASLINK);
    rgPaste[5].dwFlags=OLEUIPASTE_LINKTYPE1 | OLEUIPASTE_ENABLEICON;

    //Types we can Paste Link from the clipboard.
    rgcf[0]=m_cfLinkSource;
    ps.arrLinkTypes=rgcf;
    ps.cLinkTypes=1;
    ps.cPasteEntries=6;

    uTemp=OleUIPasteSpecial(&ps);

    if (OLEUI_OK==uTemp)
        {
        UINT        i=ps.nSelectedIndex;
        TENANTTYPE  tType;

        if (ps.fLink)
            tType=TENANTTYPE_LINKEDOBJECTFROMDATA;
        else
            {
            if (1==ps.nSelectedIndex)
                tType=TENANTTYPE_EMBEDDEDOBJECTFROMDATA;
            else
                tType=TENANTTYPE_STATIC;
            }

        //Handle iconic aspects...from links as well
        if ((1==i || ps.fLink) && (PSF_CHECKDISPLAYASICON
            & ps.dwFlags) && NULL!=ps.hMetaPict)
            {
            rgPaste[i].fmtetc.dwAspect=DVASPECT_ICON;
            dwData=(DWORD)(UINT)ps.hMetaPict;
            }

        fRet=PasteFromData(ps.lpSrcDataObj, &rgPaste[i].fmtetc
            , tType, NULL, dwData, FALSE);

        //Always free this regardless of what we do with it.
        INOLE_MetafilePictIconFree(ps.hMetaPict);
        }

    ps.lpSrcDataObj->Release();
    return fRet;
    }

/*
 * CPatronDoc::FQueryPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Determines if we can paste data from a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which we might want to paste.
 *  pFE             LPFORMATETC in which to return the first format
 *                  we can use.  Ignored if NULL.
 *  ptType          PTENANTTYPE in which to store the type of
 *                  object we can paste.  Ignored if NULL.
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPasteFromData(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, PTENANTTYPE ptType)
    {
    FORMATETC       fe;
    HRESULT         hr, hr2;

    if (NULL!=(LPVOID)ptType)
        *ptType=TENANTTYPE_STATIC;

    //Any of our specific data here?
    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    hr=pIDataObject->QueryGetData(&fe);

    //If embedded object data is available, set the appropriate type
    hr2=OleQueryCreateFromData(pIDataObject);

    if (NOERROR==hr2)
        {
        if (NULL!=pFE)
            {
            /*
             * Default to content.  Paste will use
             * CFSTR_OBJECTDESCRIPTOR to figure the actual aspect.
             */
            SETDefFormatEtc(*pFE, m_cfEmbeddedObject
                , TYMED_ISTORAGE);
            }

        if (NULL!=(LPVOID)ptType)
            *ptType=TENANTTYPE_EMBEDDEDOBJECTFROMDATA;

        /*
         * Return now if PatronObject wasn't available, otherwise
         * break out so that pFE gets PatronObject format.
         */
        if (NOERROR!=hr)
            return TRUE;
        }

    if (NOERROR!=hr && NOERROR!=hr2)
        {
        //Try metafile, DIB, then bitmap, setting fe each time
        SETDefFormatEtc(fe, CF_METAFILEPICT, TYMED_MFPICT);
        hr=pIDataObject->QueryGetData(&fe);

        if (NOERROR!=hr)
            {
            SETDefFormatEtc(fe, CF_DIB, TYMED_HGLOBAL);
            hr=pIDataObject->QueryGetData(&fe);

            if (NOERROR!=hr)
                {
                SETDefFormatEtc(fe, CF_BITMAP, TYMED_GDI);
                hr=pIDataObject->QueryGetData(&fe);
                }
            }
        }

    if (NOERROR==hr && NULL!=pFE)
        *pFE=fe;

    return (NOERROR==hr);
    }

/*
 * CPatronDoc::FQueryPasteLinkFromData
 * (Protected)
 *
 * Purpose:
 *  Determines if we can paste link from a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which we might want to paste.
 *  pFE             LPFORMATETC in which to return the first format
 *                  we can use.  Ignored if NULL.
 *  ptType          PTENANTTYPE in which to store the type of object
 *                  we can paste.  Ignored if NULL.
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPasteLinkFromData(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, PTENANTTYPE ptType)
    {
    HRESULT         hr;

    if (NULL==pIDataObject)
        return FALSE;

    hr=OleQueryLinkFromData(pIDataObject);

    if (NOERROR!=hr)
        return FALSE;

    if (NULL!=pFE)
        SETDefFormatEtc(*pFE, m_cfLinkSource, TYMED_ISTREAM);

    if (NULL!=(LPVOID)ptType)
        *ptType=TENANTTYPE_LINKEDOBJECTFROMDATA;

    return TRUE;
    }

/*
 * CPatronDoc::PasteFromData
 * (Protected)
 *
 * Purpose:
 *  Retrieves the private data format from a data object and sets
 *  it to the current figure in the editor window.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *  pFE             LPFORMATETC to use in the paste. Cannot be NULL.
 *  tType           TENANTTYPE to paste.
 *  ppo             PPATRONOBJECT containing placement data.
 *  dwData          DWORD extra data sensitive to tType
 *  fUseObjDesc     BOOL indicating to use CFSTR_OBJECTDESCRIPTOR
 *                  format for determining the aspect of the object
 *                  if the format is available.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::PasteFromData(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, TENANTTYPE tType, PPATRONOBJECT ppo
    , DWORD dwData, BOOL fUseObjDesc)
    {
    BOOL                fRet;
    HRESULT             hr;
    PATRONOBJECT        po;
    STGMEDIUM           stm;
    LPOBJECTDESCRIPTOR  pOD;
    FORMATETC           fe;
    BOOL                fRelease=FALSE;

    if (NULL==pFE)
        return FALSE;

    //If not given any placement data, see if we can retrieve it
    if (pFE->cfFormat==m_cf && NULL==ppo)
        {
        hr=pIDataObject->GetData(pFE, &stm);

        if (SUCCEEDED(hr))
            {
            ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);

            po=*ppo;
            ppo=&po;

            //If there's an object here, make sure type is right.
            if (ppo->fe.cfFormat==m_cfEmbeddedObject)
                tType=TENANTTYPE_EMBEDDEDOBJECTFROMDATA;

            if (ppo->fe.cfFormat==m_cfLinkSource)
                tType=TENANTTYPE_LINKEDOBJECTFROMDATA;

GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);
            }
        }

    /*
     * If we're told to look at CFSTR_OBJECTDESCRIPTOR, then try to get
     * the data and copy the aspect out of it.  We're not interested
     * in any other part of it, however.
     */
    if (fUseObjDesc)
        {
        SETDefFormatEtc(fe, m_cfObjectDescriptor, TYMED_HGLOBAL);

        hr=pIDataObject->GetData(&fe, &stm);

        if (FAILED(hr))
            {
            SETDefFormatEtc(fe, m_cfLinkSrcDescriptor
                , TYMED_HGLOBAL);
            hr=pIDataObject->GetData(&fe, &stm);
            }

        if (SUCCEEDED(hr))
            {
            pOD=(LPOBJECTDESCRIPTOR)GlobalLock(stm.hGlobal);
            pFE->dwAspect=pOD->dwDrawAspect;

            if (NULL!=ppo)
                ppo->fe.dwAspect=pFE->dwAspect;

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);

            /*
             * Furthermore, if pFE->dwAspect is DVASPECT_ICON, get
             * the metafile which will always be the icon
             * representation.
             */
            SETFormatEtc(fe, CF_METAFILEPICT, DVASPECT_ICON, NULL
                , TYMED_MFPICT, -1);

            if (SUCCEEDED(pIDataObject->GetData(&fe, &stm)))
                {
                dwData=(DWORD)(UINT)stm.hGlobal;
                fRelease=TRUE;
                }
            }
        }

    fRet=m_pPG->TenantCreate(tType, pIDataObject, pFE, ppo, dwData);

    //Release the stm from the last GetData
    if (fRelease)
        ReleaseStgMedium(&stm);

    if (fRet)
        {
        //Disable Printer Setup once we've created a tenant.
        m_fPrintSetup=FALSE;
        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CPatronDoc::FQueryEnableEditLinks
 *
 * Purpose:
 *  Determines whether or not the Edit Links menu item should be
 *  enabled on the frame window menu.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE to enable the item, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryEnableEditLinks(void)
    {
    return m_pPG->FQueryLinksInPage();
    }

/*
 * CPatronDoc::EditLinks
 *
 * Purpose:
 *  Invokes the links dialog in which the end-user can manipulate
 *  all the links in the current page.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame window to use as the dialog
 *                  parent.
 *
 * Return Value:
 *  BOOL            TRUE if the function was successful,
 *                  FALSE otherwise.
 */

BOOL CPatronDoc::EditLinks(HWND hWndFrame)
    {
    UINT                    uRet;
    OLEUIEDITLINKS          el;
    PCIOleUILinkContainer   pIUILinks;

    memset(&el, 0, sizeof(el));
    el.cbStruct=sizeof(el);
    el.hWndOwner=hWndFrame;

    if (!m_pPG->GetUILinkContainer(&pIUILinks))
        return FALSE;

    el.lpOleUILinkContainer=pIUILinks;
    uRet=OleUIEditLinks(&el);

    //Only the IOleUILinkContainer interface will know dirtying.
    m_fDirty=pIUILinks->m_fDirty;

    el.lpOleUILinkContainer->Release();
    return TRUE;
    }

/*
 * CPatronDoc::ShowOrQueryObjectTypes
 *
 * Purpose:
 *  If querying, we return whether or not ShowObjects is active
 *  in this document.  Otherwise pass-through to
 *  CPages::ShowObjectTypes.
 *
 * Parameters:
 *  fQuery          BOOL indicating query or set.
 *  fShow           BOOL indicating to show the type or not.
 *
 * Return Value:
 *  None
 */

BOOL CPatronDoc::ShowOrQueryObjectTypes(BOOL fQuery, BOOL fShow)
    {
    if (fQuery)
        return m_fShowTypes;

    m_fShowTypes=fShow;
    m_pPG->ShowObjectTypes(fShow);
    return TRUE;
    }

/*
 * CPatronDoc::NewPage
 *
 * Purpose:
 *  Creates a new page in the document's pages control after the
 *  current page.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::NewPage(void)
    {
    FDirtySet(TRUE);
    return m_pPG->PageInsert(0);
    }

/*
 * CPatronDoc::DeletePage
 *
 * Purpose:
 *  Deletes the current page from the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the now current page.
 */

UINT CPatronDoc::DeletePage(void)
    {
    FDirtySet(TRUE);
    return m_pPG->PageDelete(0);
    }

/*
 * CPatronDoc::NextPage
 *
 * Purpose:
 *  Shows the next page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::NextPage(void)
    {
    UINT        iPage;

    iPage=m_pPG->CurPageGet();
    return m_pPG->CurPageSet(++iPage);
    }

/*
 * CPatronDoc::PreviousPage
 *
 * Purpose:
 *  Shows the previous page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::PreviousPage(void)
    {
    UINT        iPage;

    //If iPage is zero, then we wrap around to the end.
    iPage=m_pPG->CurPageGet();
    return m_pPG->CurPageSet(--iPage);
    }

/*
 * CPatronDoc::FirstPage
 *
 * Purpose:
 *  Shows the first page page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::FirstPage(void)
    {
    return m_pPG->CurPageSet(0);
    }

/*
 * CPatronDoc::LastPage
 *
 * Purpose:
 *  Shows the last page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the last page.
 */

UINT CPatronDoc::LastPage(void)
    {
    return m_pPG->CurPageSet(NOVALUE);
    }

/*
 * CPatronDoc::Rename
 *
 * Purpose:
 *  Overrides the normal rename to include notification of tenants
 *  in this document of the new name.  This is so embedded objects
 *  are told the correct name through IOleObject::SetHostNames.
 *
 * Parameters:
 *  pszFile         LPTSTR to the new filename.
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Rename(LPTSTR pszFile)
    {
    LPMONIKER   pmk;

    //We don't need to change the base class, just augment...
    CDocument::Rename(pszFile);

    //Unregister the old moniker (m_dwRegROT set to zero).
    INOLE_RevokeAsRunning(&m_dwRegROT);

    if (NULL==pszFile)
        return;

    CreateFileMoniker(pszFile, &pmk);

    if (NULL!=pmk)
        {
        LPMONIKER   pmkAll;

        INOLE_RegisterAsRunning(this, pmk, 0, &m_dwRegROT);

        //Give a moniker to linked objects in tenants.
        m_pPG->NotifyTenantsOfRename(pszFile, pmk);

        //Register a File!"\" wildcard moniker as well
        CreateItemMoniker(TEXT("!"), TEXT("\\"), &pmkAll);

        if (NULL!=pmkAll)
            {
            LPMONIKER   pmkWild;

            INOLE_RevokeAsRunning(&m_dwRegROTWild);
            pmk->ComposeWith(pmkAll, FALSE, &pmkWild);

            if (NULL!=pmkWild)
                {
                INOLE_RegisterAsRunning(this, pmk, 0
                    , &m_dwRegROTWild);
                pmkWild->Release();
                }

            pmkAll->Release();
            }

        //No need for us to hold on to this.
        pmk->Release();
        }

    return;
    }

/*
 * CPatronDoc::InsertObject
 *
 * Purpose:
 *  Retrieves a CLSID or a filename from the Insert Object dialog
 *  box and creates an object using those identifiers.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::InsertObject(HWND hWndFrame)
    {
    OLEUIINSERTOBJECT   io;
    DWORD               dwData=0;
    TCHAR               szFile[CCHPATHMAX];
    UINT                uTemp;
    BOOL                fRet=FALSE;

    if (NULL==m_pPG)
        return FALSE;

    memset(&io, 0, sizeof(io));

    io.cbStruct=sizeof(io);
    io.hWndOwner=hWndFrame;

    szFile[0]=0;
    io.lpszFile=szFile;
    io.cchFile=CCHPATHMAX;

    //We now support linking, so remove the IOF_DISABLELINK flag.
    io.dwFlags=IOF_SELECTCREATENEW;

    uTemp=OleUIInsertObject(&io);

    if (OLEUI_OK==uTemp)
        {
        TENANTTYPE      tType;
        LPVOID          pv;
        FORMATETC       fe;

        SETDefFormatEtc(fe, 0, TYMED_NULL);

        if (io.dwFlags & IOF_SELECTCREATENEW)
            {
            tType=TENANTTYPE_EMBEDDEDOBJECT;
            pv=&io.clsid;
            }
        else
            {
            if (io.dwFlags & IOF_CHECKLINK)
                tType=TENANTTYPE_LINKEDFILE;
            else
                tType=TENANTTYPE_EMBEDDEDFILE;

            pv=szFile;
            }

        if ((io.dwFlags & IOF_CHECKDISPLAYASICON)
            && NULL!=io.hMetaPict)
            {
            fe.dwAspect=DVASPECT_ICON;
            dwData=(DWORD)(UINT)io.hMetaPict;
            }

        fRet=m_pPG->TenantCreate(tType, pv, &fe, NULL, dwData);

        //Free this regardless of what we do with it.
        INOLE_MetafilePictIconFree(io.hMetaPict);

        if (fRet)
            {
            //Disable Printer Setup once we've created a tenant.
            m_fPrintSetup=FALSE;
            FDirtySet(TRUE);
            }
        }

    return fRet;
    }

/*
 * CPatronDoc::ActivateObject
 *
 * Purpose:
 *  Executes a verb on the currently selected object.
 *
 * Parameters:
 *  iVerb           LONG of the selected verb.
 *  pMSG            LPMSG that caused the activation.
 *
 * Return Value:
 *  None
 */

//CHAPTER22MOD
void CPatronDoc::ActivateObject(LONG iVerb, LPMSG pMSG)
    {
    m_pPG->ActivateObject(iVerb, pMSG);
    return;
    }
//End CHAPTER22MOD

/*
 * CPatronDoc::ConvertObject
 *
 * Purpose:
 *  Invokes the Convert dialog on the current object.  Here it's
 *  just a pass-through to the pages.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame window.
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful, FALSE otherwise.
 */

BOOL CPatronDoc::ConvertObject(HWND hWndFrame)
    {
    return m_pPG->ConvertObject(hWndFrame);
    }

//CHAPTER22MOD
/*
 * CPatronDoc::NoObjectFrameTools
 *
 * Purpose:
 *  Saves/retrieves a flag with the document that says whether or
 *  not the object active in this document has *frame* tools.  We
 *  need this to tell the frame what to do when document activation
 *  switches.
 *
 * Parameters:
 *  fNoTools        BOOL TRUE to indicate the object has no tools.
 *                  FALSE says the object has tools.
 *  fSet            BOOL indicating if this is a set (TRUE) or get
 *                  (FALSE) operation.
 *
 * Return Value:
 *  BOOL            Value of flag for Get operations, previous value
 *                  for Set operations.
 */

BOOL CPatronDoc::NoObjectFrameTools(BOOL fNoTools, BOOL fSet)
    {
    BOOL        fLast=m_fNoObjectFrameTools;

    if (fSet)
        m_fNoObjectFrameTools=fNoTools;

    return fLast;
    }
//End CHAPTER22MOD


DRAGDROP.CPP

/*
 * DRAGDROP.CPP
 * Patron Chapter 22
 *
 * Member functions of the CPages class concerned with drag-drop
 * and other object-placement functionality.  Moved here to clean
 * up CPAGES.CPP somewhat.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPages::UTestDroppablePoint
 *
 * Purpose:
 *  Returns if the point in pptl is on the paper in the current page
 *  and alternately if it's within horizontal or vertical inset
 *  regions.
 *
 * Parameters:
 *  pptl            PPOINTL containing the point in screen
 *                  coordinates.
 *
 * Return Value:
 *  UINT            A UDROP_* value.
 */

UINT CPages::UTestDroppablePoint(PPOINTL pptl)
    {
    POINT       pt;
    RECT        rc, rcT, rcC;
    UINT        uRet;

    POINTFROMPOINTL(pt, *pptl);
    ScreenToClient(m_hWnd, &pt);

    CalcBoundingRect(&rc, FALSE);

    GetClientRect(m_hWnd, &rcC);
    IntersectRect(&rcT, &rc, &rcC);

    //Check for at least a client area hit.
    if (!PtInRect(&rcT, pt))
        return UDROP_NONE;

    uRet=UDROP_CLIENT;

    //Scroll checks happen on client area, not document area
    if (PtInRect(&rcC, pt))
        {
        //Check horizontal inset
        if (pt.x <= rcC.left+(int)m_uScrollInset)
            uRet |= UDROP_INSETLEFT;
        else if (pt.x >= rcC.right-(int)m_uScrollInset)
            uRet |= UDROP_INSETRIGHT;

        //Check vertical inset
        if (pt.y <= rcC.top+(int)m_uScrollInset)
            uRet |= UDROP_INSETTOP;
        else if (pt.y >= rcC.bottom-(int)m_uScrollInset)
            uRet |= UDROP_INSETBOTTOM;
        }

    return uRet;
    }

/*
 * CPages::DrawDropTargetRect
 *
 * Purpose:
 *  Draws a dotted rectangle on the Pages window to show where
 *  a drop might occur.  This is a toggle function.
 *
 * Parameters:
 *  pptl            PPOINTL containing the upper-left point
 *                  in screen coordinates.
 *  pszl            LPSIZEL containing the rect extents in device
 *                  units.
 *
 * Return Value:
 *  None
 */

void CPages::DrawDropTargetRect(PPOINTL pptl, LPSIZEL pszl)
    {
    POINT           pt;
    RECT            rc, rcT;
    HDC             hDC;

    if (NULL==pptl && NULL==pszl)
        {
        /*
         * This case is used from WM_PAINT in pagewin.cpp so we can
         * control the proper visibility of the drag rectangle when
         * we're scrolling.  If drag-drop happens between two apps,
         * then any ScrollWindow will cause a WM_PAINT after we
         * leave DragOver, so that paint would normally overwrite
         * the last drawn rectangle.  To alleviate that, WM_PAINT
         * will remove the last rect by sending us NULLs, paint,
         * then reinstate the rectangle.
         */
        pptl=&m_ptlRect;
        pszl=&m_szlRect;
        }
    else
        {
        m_ptlRect.x=pptl->x;
        m_ptlRect.y=pptl->y;
        m_szlRect.cx=pszl->cx;
        m_szlRect.cy=pszl->cy;

        //Flag is only affected from IDropTarget, not WM_PAINT calls
        m_fDragRectShown=!m_fDragRectShown;
        }

    POINTFROMPOINTL(pt, *pptl);
    ScreenToClient(m_hWnd, &pt);
    SetRect(&rc, pt.x, pt.y, pt.x+(int)pszl->cx
        , pt.y+(int)pszl->cy);

    CalcBoundingRect(&rcT, FALSE);
    IntersectRect(&rc, &rc, &rcT);

    if (!IsRectEmpty(&rc))
        {
        hDC=GetDC(m_hWnd);
        DrawFocusRect(hDC, &rc);
        ReleaseDC(m_hWnd, hDC);
        }

    return;
    }

/*
 * CPages::AdjustPosition
 *
 * Purpose:
 *  Adjusts a point for the scrolling offset and then converts it
 *  and a size from device into LOMETRIC units.
 *
 * Parameters:
 *  pptl            PPOINTL to adjust and convert
 *  pszl            LPSIZEL to convert
 *
 * Return Value:
 *  None
 */

void CPages::AdjustPosition(PPOINTL pptl, LPSIZEL pszl)
    {
    RECT        rc;

    SetRect(&rc, m_xPos+(int)pptl->x
        , m_yPos+(int)pptl->y, (int)pszl->cx, (int)pszl->cy);

    RectConvertMappings(&rc, NULL, FALSE);

    pptl->x=rc.left;
    pptl->y=rc.top;

    pszl->cx=rc.right;
    pszl->cy=rc.bottom;

    return;
    }


DROPSRC.CPP

/*
 * DROPSRC.CPP
 * Patron Chapter 22
 *
 * Implementation of a DropSource object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CDropSource::CDropSource
 * CDropSource::~CDropSource
 *
 * Constructor Parameters:
 *  None
 */

CDropSource::CDropSource(void)
    {
    m_cRef=0;
    return;
    }

CDropSource::~CDropSource(void)
    {
    return;
    }

/*
 * CDropSource::QueryInterface
 * CDropSource::AddRef
 * CDropSource::Release
 *
 * Purpose:
 *  IUnknown members for CDropSource object.
 */

STDMETHODIMP CDropSource::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDropSource==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDropSource::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropSource::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDropSource::QueryDragContinue
 *
 * Purpose:
 *  Determines whether to continue a drag operation or cancel it.
 *
 * Parameters:
 *  fEsc            BOOL indicating that the ESC key was pressed.
 *  grfKeyState     DWORD providing states of keys and mouse buttons
 *
 * Return Value:
 *  HRESULT         DRAGDROP_S_CANCEL to stop the drag,
 *                  DRAGDROP_S_DROP to drop the data where it is,
 *                  or NOERROR to continue.
 */

STDMETHODIMP CDropSource::QueryContinueDrag(BOOL fEsc, DWORD grfKeyState)
    {
    if (fEsc)
        return ResultFromScode(DRAGDROP_S_CANCEL);

    if (!(grfKeyState & MK_LBUTTON))
        return ResultFromScode(DRAGDROP_S_DROP);

    return NOERROR;
    }

/*
 * CDropSource::GiveFeedback
 *
 * Purpose:
 *  Provides cursor feedback to the user since the source task
 *  always has the mouse capture.  We can also provide any other
 *  type of feedback above cursors if we so desire.
 *
 * Parameters:
 *  dwEffect        DWORD effect flags returned from the last target
 *
 * Return Value:
 *  HRESULT         NOERROR if you set a cursor yourself or
 *                  DRAGDROP_S_USEDEFAULTCURSORS to let OLE do
 *                  the work.
 */

STDMETHODIMP CDropSource::GiveFeedback(DWORD dwEffect)
    {
    return ResultFromScode(DRAGDROP_S_USEDEFAULTCURSORS);
    }


DROPTGT.CPP

/*
 * DROPTGT.CPP
 * Patron Chapter 22
 *
 * Implementation of a DropTarget object
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CDropTarget::CDropTarget
 * CDropTarget::~CDropTarget
 *
 * Constructor Parameters:
 *  pDoc            PCPatronDoc of the window containing us.
 */

CDropTarget::CDropTarget(PCPatronDoc pDoc)
    {
    m_cRef=0;
    m_pDoc=pDoc;

    m_pIDataObject=NULL;
    return;
    }

CDropTarget::~CDropTarget(void)
    {
    return;
    }

/*
 * CDropTarget::QueryInterface
 * CDropTarget::AddRef
 * CDropTarget::Release
 *
 * Purpose:
 *  IUnknown members for CDropTarget object.
 */

STDMETHODIMP CDropTarget::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDropTarget==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDropTarget::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropTarget::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDropTarget::DragEnter
 *
 * Purpose:
 *  Indicates that data in a drag operation has been dragged over
 *  our window that's a potential target.  We are to decide if it's
 *  something in which we're interested.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT providing the source data.
 *  grfKeyState     DWORD flags: states of keys and mouse buttons.
 *  pt              POINTL coordinates in the client space of
 *                  the document.
 *  pdwEffect       LPDWORD into which we'll place the appropriate
 *                  effect flag for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragEnter(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    PCPages         ppg=m_pDoc->m_pPG;
    HWND            hWnd;
    FORMATETC       fe;
    STGMEDIUM       stm;
    UINT            uRet;

    m_fFeedback=FALSE;
    m_pIDataObject=NULL;

    if (!m_pDoc->FQueryPasteFromData(pIDataSource, &fe, NULL))
        {
        *pdwEffect=DROPEFFECT_NONE;
        return NOERROR;
        }

    //Check if we can link from this data object as well.
    ppg->m_fLinkAllowed
        =(NOERROR==OleQueryLinkFromData(pIDataSource));

    //We never allow it dragging in ourselves.
    ppg->m_fLinkAllowed &= !ppg->m_fDragSource;

    //Check if this is a valid drop point.
    uRet=ppg->UTestDroppablePoint(&pt);
    ppg->m_uLastTest=uRet;

    if (UDROP_NONE==uRet)
        *pdwEffect=DROPEFFECT_NONE;
    else
        {
        //Default is move if we can, in fact drop here.
        *pdwEffect=DROPEFFECT_MOVE;

        if (grfKeyState & MK_CONTROL)
            {
            if (ppg->m_fLinkAllowed && (grfKeyState & MK_SHIFT))
                *pdwEffect=DROPEFFECT_LINK;
            else
                *pdwEffect=DROPEFFECT_COPY;
            }
        }

    m_pIDataObject=pIDataSource;
    m_pIDataObject->AddRef();

    /*
     * Determine the size of the data, if we can.  The default is
     * a small rectangle since we can't easily tell what size
     * something will be if we're pulling in a metafile or bitmap.
     * It's not a good idea to render it here with GetData just to
     * find that out. We only know the size if it's our own object
     * in which case a GetData will be fast.
     */

    if (fe.cfFormat==m_pDoc->m_cf)
        {
        if (SUCCEEDED(pIDataSource->GetData(&fe, &stm)))
            {
            PPATRONOBJECT   ppo;
            RECT            rc;

            ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);

            SetRect(&rc, (int)ppo->szl.cx, -(int)ppo->szl.cy, 0, 0);
            RectConvertMappings(&rc, NULL, TRUE);
            SETSIZEL(m_szl, rc.left, rc.top);

            m_ptPick=ppo->ptlPick;
            m_fe=ppo->fe;

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);
            }
        }
    else
        {
        SETSIZEL(m_szl, 30, 30);
        m_ptPick.x=0;
        m_ptPick.y=0;
        m_fe.cfFormat=0;

        /*
         * Try to get CFSTR_OBJECTDESCRIPTOR which might have a size
         * and a pick point.  If it exists, then always use the
         * point but still default to a 30*30 size if the sizes
         * are zero.
         */
        uRet=RegisterClipboardFormat(CFSTR_OBJECTDESCRIPTOR);
        SETDefFormatEtc(fe, uRet, TYMED_HGLOBAL);

        if (SUCCEEDED(pIDataSource->GetData(&fe, &stm)))
            {
            LPOBJECTDESCRIPTOR  pOD;

            pOD=(LPOBJECTDESCRIPTOR)GlobalLock(stm.hGlobal);

            //Get the size, converting to LOMETRIC.
            if (0!=pOD->sizel.cx && 0!=pOD->sizel.cy)
                {
                XformSizeInHimetricToPixels(NULL, &pOD->sizel
                    , &m_szl);
                }

            //POINTL and SIZEL are interchangeable
            XformSizeInHimetricToPixels(NULL, (LPSIZEL)&pOD->pointl
                , (LPSIZEL)&m_ptPick);

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);
            }
        }

    //Bring the document window up front, show what a drop will do.
    hWnd=m_pDoc->Window();
    BringWindowToTop(hWnd);
    UpdateWindow(hWnd);

    ppg->m_uVScrollCode=NOVALUE;
    ppg->m_uHScrollCode=NOVALUE;
    m_fPendingRepaint=FALSE;

    pt.x-=m_ptPick.x;
    pt.y-=m_ptPick.y;

    m_ptLast=pt;
    m_fFeedback=TRUE;
    ppg->DrawDropTargetRect(&pt, &m_szl);

    return NOERROR;
    }

/*
 * CDropTarget::DragOver
 *
 * Purpose:
 *  Indicates that the mouse was moved inside the window represented
 *  by this drop target.  This happens on every WM_MOUSEMOVE, so
 *  this function should be very efficient.
 *
 * Parameters:
 *  grfKeyState     DWORD providing the current keyboard and
 *                  mouse states
 *  pt              POINTL where the mouse currently is.
 *  pdwEffect       LPDWORD in which to store the effect flag
 *                  for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragOver(DWORD grfKeyState, POINTL pt
    , LPDWORD pdwEffect)
    {
    PCPages     ppg=m_pDoc->m_pPG;
    UINT        uRet, uLast;
    UINT        xPos, yPos;

    *pdwEffect=DROPEFFECT_NONE;

    if (NULL==m_pIDataObject)
        return NOERROR;

    //Check if this is still a valid point.  uRet used below as well
    uRet=ppg->UTestDroppablePoint(&pt);

    if (UDROP_NONE==uRet)
        *pdwEffect=DROPEFFECT_NONE;
    else
        {
        //Store these before possibly ORing in DROPEFFECT_SCROLL
        *pdwEffect=DROPEFFECT_MOVE;

        if (grfKeyState & MK_CONTROL)
            {
            if (ppg->m_fLinkAllowed && (grfKeyState & MK_SHIFT))
                *pdwEffect=DROPEFFECT_LINK;
            else
                *pdwEffect=DROPEFFECT_COPY;
            }
        }

    //If we haven't moved and we are not scrolling, then we're done.
    if ((pt.x-m_ptPick.x==m_ptLast.x)
        && (pt.y-m_ptPick.y==m_ptLast.y)
        && !((UDROP_INSETHORZ|UDROP_INSETVERT) & ppg->m_uLastTest))
        {
        return NOERROR;
        }

    //Remove the last feedback rectangle.
    if (m_fFeedback)
        ppg->DrawDropTargetRect(&m_ptLast, &m_szl);

    uLast=ppg->m_uLastTest;
    ppg->m_uLastTest=uRet;

    if (UDROP_NONE==uRet)
        {
        //If we're now an invalid point, better repaint as necessary
        if (m_fPendingRepaint)
            {
            UpdateWindow(ppg->m_hWnd);
            m_fPendingRepaint=FALSE;
            }

        ppg->m_uVScrollCode=NOVALUE;
        ppg->m_uHScrollCode=NOVALUE;
        m_fFeedback=FALSE;
        return NOERROR;
        }

    /*
     * Scrolling is a little tricky:  We get a DragOver pulse even
     * if we didn't move.  First we have to delay scrolling for
     * ppg->m_uScrollDelay clock ticks which we can determine using
     * GetTickCount.  Timers do not work here since we may not be
     * yielding to our message loop.
     *
     * Once we know we are scrolling then we determine if we
     * scroll again or if we reset the scrolling state.
     */

    if ((UDROP_INSETHORZ & uLast) && !(UDROP_INSETHORZ & uRet))
        ppg->m_uHScrollCode=NOVALUE;

    if (!(UDROP_INSETHORZ & uLast) && (UDROP_INSETHORZ & uRet))
        {
        ppg->m_dwTimeLast=GetTickCount();
        ppg->m_uHScrollCode=(0!=(UDROP_INSETLEFT & uRet))
            ? SB_LINELEFT : SB_LINERIGHT; //Same as UP & DOWN codes.
        }

    if ((UDROP_INSETVERT & uLast) && !(UDROP_INSETVERT & uRet))
        ppg->m_uVScrollCode=NOVALUE;

    if (!(UDROP_INSETVERT & uLast) && (UDROP_INSETVERT & uRet))
        {
        ppg->m_dwTimeLast=GetTickCount();
        ppg->m_uVScrollCode=(0!=(UDROP_INSETTOP & uRet))
            ? SB_LINEUP : SB_LINEDOWN;
        }

    //Only change the last time if ALL scrolling stops.
    if (NOVALUE==ppg->m_uHScrollCode && NOVALUE==ppg->m_uVScrollCode)
        ppg->m_dwTimeLast=0L;

    //Set the scroll effect on any inset hit.
    if ((UDROP_INSETHORZ | UDROP_INSETVERT) & uRet)
        *pdwEffect |= DROPEFFECT_SCROLL;

    xPos=ppg->m_xPos;
    yPos=ppg->m_yPos;

    //Has the delay elapsed?  We can scroll if so
    if (ppg->m_dwTimeLast!=0
        && (GetTickCount()-ppg->m_dwTimeLast)
        > (DWORD)ppg->m_uScrollDelay)
        {
        if (NOVALUE!=ppg->m_uHScrollCode)
            {
            m_fPendingRepaint=TRUE;
            SendMessage(ppg->m_hWnd, WM_HSCROLL
                , ppg->m_uHScrollCode, 0L);
            }

        if (NOVALUE!=ppg->m_uVScrollCode)
            {
            m_fPendingRepaint=TRUE;
            SendMessage(ppg->m_hWnd, WM_VSCROLL
                , ppg->m_uVScrollCode, 0L);
            }
        }

    //If we didn't scroll but have a pending repaint, do it now.
    if (xPos==ppg->m_xPos && yPos==ppg->m_yPos && m_fPendingRepaint)
        {
        UpdateWindow(ppg->m_hWnd);
        m_fPendingRepaint=FALSE;
        }

    pt.x-=m_ptPick.x;
    pt.y-=m_ptPick.y;

    m_ptLast=pt;
    m_fFeedback=TRUE;
    ppg->DrawDropTargetRect(&pt, &m_szl);

    return NOERROR;
    }

/*
 * CDropTarget::DragLeave
 *
 * Purpose:
 *  Informs the drop target that the operation has left its window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragLeave(void)
    {
    PCPages         ppg=m_pDoc->m_pPG;

    if (NULL==m_pIDataObject)
        return NOERROR;

    //Stop scrolling
    ppg->m_uHScrollCode=NOVALUE;
    ppg->m_uVScrollCode=NOVALUE;

    if (m_fPendingRepaint)
        UpdateWindow(ppg->m_hWnd);

    //Remove the last feedback rectangle.
    if (m_fFeedback)
        ppg->DrawDropTargetRect(&m_ptLast, &m_szl);

    m_fFeedback=FALSE;
    m_pIDataObject->Release();
    return NOERROR;
    }

/*
 * CDropTarget::Drop
 *
 * Purpose:
 *  Instructs the drop target to paste the data that was just now
 *  dropped on it.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT from which we'll paste.
 *  grfKeyState     DWORD providing current keyboard/mouse state.
 *  pt              POINTL at which the drop occurred.
 *  pdwEffect       LPDWORD in which to store what you did.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::Drop(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    PCPages         ppg=m_pDoc->m_pPG;
    BOOL            fRet=TRUE;
    FORMATETC       fe;
    TENANTTYPE      tType;
    PATRONOBJECT    po;
    POINT           ptS;

    *pdwEffect=DROPEFFECT_NONE;

    if (NULL==m_pIDataObject)
        return ResultFromScode(E_FAIL);

    if (UDROP_NONE==ppg->UTestDroppablePoint(&pt))
        return ResultFromScode(E_FAIL);

    //Stop scrolling
    ppg->m_uHScrollCode=NOVALUE;
    ppg->m_uVScrollCode=NOVALUE;

    if (m_fPendingRepaint)
        UpdateWindow(ppg->m_hWnd);

    //2.  Remove the UI feedback
    if (m_fFeedback)
        ppg->DrawDropTargetRect(&m_ptLast, &m_szl);

    m_pIDataObject->Release();

    /*
     * Check if we can do the paste, and if so, tell our pasting
     * mechanism exactly where to place us.
     */
    pt.x-=m_ptPick.x;
    pt.y-=m_ptPick.y;

    POINTFROMPOINTL(ptS, pt);
    ScreenToClient(ppg->Window(), &ptS);
    POINTLFROMPOINT(po.ptl, ptS);

    //This is true if we didn't see placement data in DragEnter
    if (0!=m_fe.cfFormat)
        {
        po.szl.cx=m_szl.cx;         //We stored these positive
        po.szl.cy=-m_szl.cy;
        }
    else
        SETSIZEL(po.szl, 0, 0); //Ask object for its size.

    //Adjust for scrolling and mapping mode.
    ppg->AdjustPosition(&po.ptl, &po.szl);

    /*
     * If we're in the same document and moving, then we can just
     * stuff the Pages' m_ptDrop which will move us and return.
     */
    if (ppg->m_fDragSource && !(grfKeyState & MK_CONTROL))
        {
        *pdwEffect=DROPEFFECT_MOVE;
        ppg->m_fMoveInPage=TRUE;
        ppg->m_ptDrop=po.ptl;
        return NOERROR;
        }

    /*
     * Otherwise, paste either from another document or from
     * the same document which will always be a copy to the new
     * point.
     */

    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        {
        if (ppg->m_fLinkAllowed && (grfKeyState & MK_SHIFT))
            *pdwEffect=DROPEFFECT_LINK;
        else
            *pdwEffect=DROPEFFECT_COPY;
        }

    ppg->m_fMoveInPage=FALSE;

    /*
     * We know linking is desired if effect flag is set, so this
     * function will get the FORMATETC for linking.  Otherwise
     * FQueryPasteFromData will get the other FORMATETC to use.
     */
    if (DROPEFFECT_LINK==*pdwEffect)
        {
        fRet=m_pDoc->FQueryPasteLinkFromData(pIDataSource, &fe
            , &tType);
        }
    else
        fRet=m_pDoc->FQueryPasteFromData(pIDataSource, &fe, &tType);

    if (fRet)
        {
        //Copy the real format if we have placement data.
        po.fe=(m_pDoc->m_cf==fe.cfFormat) ? m_fe : fe;

        //Flag PasteFromData to use CFSTR_OBJECTDESCRIPTOR
        fRet=m_pDoc->PasteFromData(pIDataSource, &fe, tType
            , &po, 0, TRUE);
        }

    if (!fRet)
        return ResultFromScode(E_FAIL);

    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        *pdwEffect=DROPEFFECT_COPY;

    return NOERROR;
    }


IADVSINK.CPP

/*
 * IADVSINK.CPP
 * Patron Chapter 22
 *
 * Implementation of the IAdviseSink interface for Patron's tenants.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CImpIAdviseSink::CImpIAdviseSink
 * CImpIAdviseSink::~CImpIAdviseSink
 *
 * Parameters (Constructor):
 *  pTenant         PCTenant of the tenant we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIAdviseSink::CImpIAdviseSink(PCTenant pTenant
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pTen=pTenant;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIAdviseSink::~CImpIAdviseSink(void)
    {
    return;
    }

/*
 * CImpIAdviseSink::QueryInterface
 * CImpIAdviseSink::AddRef
 * CImpIAdviseSink::Release
 *
 * Purpose:
 *  IUnknown members for CImpIAdviseSink object.
 */

STDMETHODIMP CImpIAdviseSink::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIAdviseSink::OnDataChange
 *
 * Unused since we don't IDataObject::Advise.
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnDataChange(LPFORMATETC pFEIn
    , LPSTGMEDIUM pSTM)
    {
    return;
    }

/*
 * CImpIAdviseSink::OnViewChange
 *
 * Purpose:
 *  Notifes the advise sink that presentation data changed in the
 *  data object to which we're connected providing the right time
 *  to update displays using such presentations.
 *
 * Parameters:
 *  dwAspect        DWORD indicating which aspect has changed.
 *  lindex          LONG indicating the piece that changed.
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnViewChange(DWORD dwAspect
    , LONG lindex)
    {
    //Repaint only if this is the right aspect
    if (dwAspect==m_pTen->m_fe.dwAspect)
        {
        m_pTen->m_pPG->m_fDirty=TRUE;
        m_pTen->Repaint();
        }

    return;
    }

/*
 * CImpIAdviseSink::OnRename
 *
 * Purpose:
 *  Informs the advise sink that a linked object has been renamed.
 *  Generally only the OLE default handler cares about this.
 *
 * Parameters:
 *  pmk             LPMONIKER providing the new name of the object
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnRename(LPMONIKER pmk)
    {
    /*
     * As a container this is unimportant to us since it really
     * tells the handler's implementation of IOleLink that the
     * object's moniker has changed.  Since we get this call
     * from the handler, we don't have to do anything ourselves.
     */
    return;
    }

/*
 * CImpIAdviseSink::OnSave
 *
 * Purpose:
 *  Informs the advise sink that the OLE object has been saved
 *  persistently.  The primary purpose of this is for containers
 *  that want to make optimizations for objects that are not in a
 *  saved state, so on this you have to disable such optimizations.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnSave(void)
    {
    /*
     * A Container has nothing to do here as this notification is
     * only useful when we have an ADVFCACHE_ONSAVE advise set up,
     * which we don't.  So we ignore it.
     */
    return;
    }

/*
 * CImpIAdviseSink::OnClose
 *
 * Purpose:
 *  Informs the advise sink that the OLE object has closed and is
 *  no longer bound in any way.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnClose(void)
    {
    /*
     * This doesn't have much to do with us again as it's only
     * used to notify the handler's IOleLink implementation of the
     * change in the object.  We don't have to do anything since
     * we'll also get an IOleClientSite::OnShowWindow(FALSE) to
     * tell us to repaint.
     */

    /*
     * If we are dealing with an OLE 1.0 server it may not call
     * IOleClientSite::OnShowWindow(FALSE) properly, so to protect
     * ourselves we make sure the object is drawn as closed on
     * this notification.
     */
    m_pTen->ShowAsOpen(FALSE);
    return;
    }


ICLASSF.CPP

/*
 * ICLASSF.CPP
 * Patron Chapter 22
 *
 * Implementation of an IClassFactory interface for Patron to
 * enable linking to its embedded objects.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CLinkClassFactory::CLinkClassFactory
 * CLinkClassFactory::~CLinkClassFactory
 *
 * Constructor Parameters:
 *  pFR             PCLinkFrame that can create documents.
 */

CLinkClassFactory::CLinkClassFactory(PCPatronFrame pFR)
    {
    m_cRef=0L;
    m_pFR=pFR;
    return;
    }

CLinkClassFactory::~CLinkClassFactory(void)
    {
    return;
    }

/*
 * CLinkClassFactory::QueryInterface
 * CLinkClassFactory::AddRef
 * CLinkClassFactory::Release
 */

STDMETHODIMP CLinkClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CLinkClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CLinkClassFactory::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CLinkClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates an object to resolve a link to an embedding.
 */

STDMETHODIMP CLinkClassFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCPatronDoc         pDoc;
    HRESULT             hr;

    *ppvObj=NULL;

    hr=ResultFromScode(E_OUTOFMEMORY);

    //We don't support aggregation
    if (NULL!=pUnkOuter)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    //Try creating a new document, which creates the object.
    pDoc=(PCPatronDoc)m_pFR->m_pCL->NewDocument(TRUE);

    //ObjectDestroyed has already been called.
    if (NULL==pDoc)
        {
        g_cObj++;
        ObjectDestroyed();
        return hr;
        }

    /*
     * We don't want to do any file initialization here because we
     * want to wait for IPersistFile calls.  If we called Load(NULL)
     * we'd create storages and whatnot that we don't want in this
     * case.
     */

    hr=pDoc->QueryInterface(riid, ppvObj);

    if (FAILED(hr))
        {
        m_pFR->m_pCL->CloseDocument(pDoc);
        g_cObj++;
        ObjectDestroyed();
        return hr;
        }

    m_pFR->m_pDocCreated=pDoc;
    return NOERROR;
    }

/*
 * CLinkClassFactory::LockServer
 *
 * Purpose:
 *  Provides control of the application in memory.
 */

STDMETHODIMP CLinkClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        {
        g_cLock--;
        g_cObj++;
        ObjectDestroyed();
        }

    return NOERROR;
    }


ICLISITE.CPP

/*
 * ICLISITE.CPP
 * Patron Chapter 22
 *
 * Implementation of the IOleClientSite interface for
 * Patron's tenants.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CImpIOleClientSite::CImpIOleClientSite
 * CImpIOleClientSite::~CImpIOleClientSite
 *
 * Parameters (Constructor):
 *  pTenant         PCTenant of the tenant we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleClientSite::CImpIOleClientSite(PCTenant pTenant
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pTen=pTenant;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleClientSite::~CImpIOleClientSite(void)
    {
    return;
    }

/*
 * CImpIOleClientSite::QueryInterface
 * CImpIOleClientSite::AddRef
 * CImpIOleClientSite::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleClientSite object.
 */

STDMETHODIMP CImpIOleClientSite::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleClientSite::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleClientSite::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleClientSite::SaveObject
 *
 * Purpose:
 *  Requests that the container call OleSave for the object that
 *  lives here.  Typically this happens on server shutdown.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::SaveObject(void)
    {
    //We're already set up with the tenant to save; this is trivial.
    m_pTen->Update();
    return NOERROR;
    }

/*
 * CImpIOleClientSite::GetMoniker
 *
 * Purpose:
 *  Retrieves the moniker for the site in which this object lives,
 *  either the moniker relative to the container or the full
 *  moniker.
 *
 * Parameters:
 *  dwAssign        DWORD specifying that the object wants moniker
 *                  assignment.  Yeah.  Right.  Got any bridges to
 *                  sell?
 *  dwWhich         DWORD identifying which moniker the object
 *                  wants, either the container's moniker, the
 *                  moniker relative to this client site, or the
 *                  full moniker.
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::GetMoniker(DWORD dwAssign
    , DWORD dwWhich, LPMONIKER *ppmk)
    {
    *ppmk=NULL;

    switch (dwWhich)
        {
        case OLEWHICHMK_CONTAINER:
            //This is just the file we're living in.
            if (NULL!=m_pTen->m_pmkFile)
                *ppmk=m_pTen->m_pmkFile;

            break;

        case OLEWHICHMK_OBJREL:
            //This is everything but the filename.
            if (NULL!=m_pTen->m_pmk)
                *ppmk=m_pTen->m_pmk;

            break;

        case OLEWHICHMK_OBJFULL:
            //Concatenate file and relative monikers for this one.
            if (NULL!=m_pTen->m_pmkFile && NULL!=m_pTen->m_pmk)
                {
                return m_pTen->m_pmkFile->ComposeWith
                    (m_pTen->m_pmk, FALSE, ppmk);
                }

            break;
        }

    if (NULL==*ppmk)
        return ResultFromScode(E_FAIL);

    (*ppmk)->AddRef();
    return NOERROR;
    }

/*
 * CImpIOleClientSite::GetContainer
 *
 * Purpose:
 *  Returns a pointer to the document's IOleContainer interface.
 *
 * Parameters:
 *  ppContainer     LPOLECONTAINER * in which to return the
 *                  interface.
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::GetContainer(LPOLECONTAINER
    * ppContainer)
    {
    PCPage  pPage;

    *ppContainer=NULL;

    /*
     * Here we want to get the interface on the page we're in.
     * The function we call in CPages does not AddRef the page, but
     * QueryInterface does.
     */
    m_pTen->m_pPG->IPageGetFromID((DWORD)-1L, &pPage, FALSE);

    if (NULL!=pPage)
        {
        return pPage->QueryInterface(IID_IOleItemContainer
            , (PPVOID)ppContainer);
        }

    return ResultFromScode(E_FAIL);
    }

/*
 * CImpIOleClientSite::ShowObject
 *
 * Purpose:
 *  Tells the container to bring the object fully into view as much
 *  as possible, that is, scroll the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::ShowObject(void)
    {
    HWND        hWnd, hWndT;

    /*
     * We let the tenant do this, since it can access the current
     * scroll position as a friend of CPages whereas we cannot.
     */
    //CHAPTER22MOD
    //Don't try to show objects that are always active.
    if (!((OLEMISC_INSIDEOUT | OLEMISC_ACTIVATEWHENVISIBLE)
        & m_pTen->m_grfMisc))
        m_pTen->ShowYourself();
    //End CHAPTER22MOD

    //For linking to embedding, show the main window.
    hWndT=GetParent(m_pTen->m_hWnd);

    while (NULL!=hWndT)
        {
        hWnd=hWndT;
        hWndT=GetParent(hWnd);
        }

    ShowWindow(hWnd, SW_SHOWNOACTIVATE);

    //CHAPTER22MOD
    m_pTen->UpdateInPlaceObjectRects(NULL, TRUE);
    //End CHAPTER22MOD

    return NOERROR;
    }

/*
 * CImpIOleClientSite::OnShowWindow
 *
 * Purpose:
 *  Informs the container if the object is showing itself or
 *  hiding itself.  This is done only in the opening mode and allows
 *  the container to know when to shade or unshade the object.
 *
 * Parameters:
 *  fShow           BOOL indiciating that the object is being shown
 *                  (TRUE) or hidden (FALSE).
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::OnShowWindow(BOOL fShow)
    {
    //All we have to do is tell the tenant of the open state change.
    m_pTen->ShowAsOpen(fShow);
    return NOERROR;
    }

/*
 * CImpIOleClientSite::RequestNewObjectLayout
 *
 * Purpose:
 *  Called when the object needs more room in the container.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::RequestNewObjectLayout(void)
    {
    return ResultFromScode(E_NOTIMPL);
    }


IIPSITE.CPP

/*
 * IIPSITE.CPP
 * Patron Chapter 22
 *
 * IOleInPlaceSite interface implementation for Patron
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

BOOL        g_fSwitchingActive=FALSE;

/*
 * CImpIOleInPlaceSite::CImpIOleInPlaceSite
 * CImpIOleInPlaceSite::~CImpIOleInPlaceSite
 *
 * Parameters (Constructor):
 *  pTen            PCTenant of the tenant we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleInPlaceSite::CImpIOleInPlaceSite(PCTenant pTen
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pTen=pTen;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleInPlaceSite::~CImpIOleInPlaceSite(void)
    {
    return;
    }

/*
 * CImpIOleInPlaceSite::QueryInterface
 * CImpIOleInPlaceSite::AddRef
 * CImpIOleInPlaceSite::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleInPlaceSite object.
 */

STDMETHODIMP CImpIOleInPlaceSite::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceSite::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceSite::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleInPlaceActiveObject::GetWindow
 *
 * Purpose:
 *  Retrieves the handle of the window associated with the object
 *  on which this interface is implemented.
 *
 * Parameters:
 *  phWnd           HWND * in which to store the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_FAIL if there is no
 *                  window.
 */

STDMETHODIMP CImpIOleInPlaceSite::GetWindow(HWND *phWnd)
    {
    *phWnd=m_pTen->m_hWnd;
    return NOERROR;
    }

/*
 * CImpIOleInPlaceActiveObject::ContextSensitiveHelp
 *
 * Purpose:
 *  Instructs the object on which this interface is implemented to
 *  enter or leave a context-sensitive help mode.
 *
 * Parameters:
 *  fEnterMode      BOOL TRUE to enter the mode, FALSE otherwise.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceSite::ContextSensitiveHelp
    (BOOL fEnterMode)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::CanInPlaceActivate
 *
 * Purpose:
 *  Answers the server whether or not we can currently in-place
 *  activate its object.  By implementing this interface we say
 *  that we support in-place activation, but through this function
 *  we indicate whether the object can currently be activated
 *  in-place.  Iconic aspects, for example, cannot, meaning we
 *  return S_FALSE.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR if we can in-place activate the object
 *                  in this site, S_FALSE if not.
 */

STDMETHODIMP CImpIOleInPlaceSite::CanInPlaceActivate(void)
    {
    if (DVASPECT_CONTENT!=m_pTen->m_fe.dwAspect)
        return ResultFromScode(S_FALSE);

    if (TENANTTYPE_EMBEDDEDOBJECT!=m_pTen->m_tType)
        return ResultFromScode(S_FALSE);

    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::OnInPlaceActivate
 *
 * Purpose:
 *  Informs the container that an object is being activated in-place
 *  such that the container can prepare appropriately.  The
 *  container does not, however, make any user interface changes at
 *  this point.  See OnUIActivate.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an appropriate error code.
 */

STDMETHODIMP CImpIOleInPlaceSite::OnInPlaceActivate(void)
    {
    //m_pIOleIPObject is our in-place flag.
    m_pTen->m_pObj->QueryInterface(IID_IOleInPlaceObject
        , (PPVOID)&m_pTen->m_pIOleIPObject);
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::OnInPlaceDeactivate
 *
 * Purpose:
 *  Notifies the container that the object has deactivated itself
 *  from an in-place state.  Opposite of OnInPlaceActivate.  The
 *  container does not change any UI at this point.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an appropriate error code.
 */

STDMETHODIMP CImpIOleInPlaceSite::OnInPlaceDeactivate(void)
    {
    /*
     * Since we don't have an Undo command, we can tell the object
     * right away to discard its Undo state.
     */
    m_pTen->Activate(OLEIVERB_DISCARDUNDOSTATE, NULL);
    ReleaseInterface(m_pTen->m_pIOleIPObject);
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::OnUIActivate
 *
 * Purpose:
 *  Informs the container that the object is going to start munging
 *  around with user interface, like replacing the menu.  The
 *  container should remove any relevant UI in preparation.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an appropriate error code.
 */

STDMETHODIMP CImpIOleInPlaceSite::OnUIActivate(void)
    {
    PCPatronDoc     pDoc;

    m_pTen->m_pPG->m_fAddUI=FALSE;

    pDoc=(PCPatronDoc)SendMessage(GetParent(m_pTen->m_hWnd)
        , DOCM_PDOCUMENT, 0, 0L);

    /*
     * Change the currently selected tenant in the page.  This
     * will UIDeactivate the currently UI Active tenant.
     */
    g_fSwitchingActive=TRUE;
    m_pTen->m_pPG->m_pPageCur->SwitchActiveTenant(m_pTen);
    g_fSwitchingActive=FALSE;

    //Hide the frame tools if necessary.
    g_pFR->ShowUIAndTools(pDoc->NoObjectFrameTools(0, FALSE), FALSE);
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::OnUIDeactivate
 *
 * Purpose:
 *  Informs the container that the object is deactivating its
 *  in-place user interface at which time the container may
 *  reinstate its own.  Opposite of OnUIActivate.
 *
 * Parameters:
 *  fUndoable       BOOL indicating if the object will actually
 *                  perform an Undo if the container calls
 *                  ReactivateAndUndo.
 *
 * Return Value:
 *  HRESULT         NOERROR or an appropriate error code.
 */

STDMETHODIMP CImpIOleInPlaceSite::OnUIDeactivate(BOOL fUndoable)
    {
    PCDocument  pDoc;
    MSG         msg;

    /*
     * Ignore this notification if we're switching between
     * multiple active objects.
     */
    if (g_fSwitchingActive)
        return NOERROR;

    //If in shutdown (NULL storage), don't check messages.
    if (NULL==m_pTen->m_pIStorage)
        {
        g_pFR->ReinstateUI();
        return NOERROR;
        }

    pDoc=(PCDocument)SendMessage(GetParent(m_pTen->m_hWnd)
        , DOCM_PDOCUMENT, 0, 0L);

    //If there's a pending double-click, delay showing our UI
    if (!PeekMessage(&msg, pDoc->Window(), WM_LBUTTONDBLCLK
        , WM_LBUTTONDBLCLK, PM_NOREMOVE | PM_NOYIELD))
        {
        //Turn everything back on.
        g_pFR->ReinstateUI();
        }
    else
        m_pTen->m_pPG->m_fAddUI=TRUE;

    SetFocus(pDoc->Window());
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::DeactivateAndUndo
 *
 * Purpose:
 *  If immediately after activation the object does an Undo, the
 *  action being undone is the activation itself, and this call
 *  informs the container that this is, in fact, what happened.
 *  The container should call IOleInPlaceObject::UIDeactivate.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an appropriate error code.
 */

STDMETHODIMP CImpIOleInPlaceSite::DeactivateAndUndo(void)
    {
    m_pTen->m_pIOleIPObject->InPlaceDeactivate();
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::DiscardUndoState
 *
 * Purpose:
 *  Informs the container that something happened in the object
 *  that means the container should discard any undo information
 *  it currently maintains for the object.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an appropriate error code.
 */

STDMETHODIMP CImpIOleInPlaceSite::DiscardUndoState(void)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleInPlaceSite::GetWindowContext
 *
 * Purpose:
 *  Provides an in-place object with pointers to the frame and
 *  document level in-place interfaces (IOleInPlaceFrame and
 *  IOleInPlaceUIWindow) such that the object can do border
 *  negotiation and so forth.  Also requests the position and
 *  clipping rectangles of the object in the container and a
 *  pointer to an OLEINPLACEFRAME info structure which contains
 *  accelerator information.
 *
 *  Note that the two interfaces this call returns are not
 *  available through QueryInterface on IOleInPlaceSite since they
 *  live with the frame and document, but not the site.
 *
 * Parameters:
 *  ppIIPFrame      LPOLEINPLACEFRAME * in which to return the
 *                  AddRef'd pointer to the container's
 *                  IOleInPlaceFrame.
 *  ppIIPUIWindow   LPOLEINPLACEUIWINDOW * in which to return
 *                  the AddRef'd pointer to the container document's
 *                  IOleInPlaceUIWindow.
 *  prcPos          LPRECT in which to store the object's position.
 *  prcClip         LPRECT in which to store the object's visible
 *                  region.
 *  pFI             LPOLEINPLACEFRAMEINFO to fill with accelerator
 *                  stuff.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceSite::GetWindowContext
    (LPOLEINPLACEFRAME *ppIIPFrame, LPOLEINPLACEUIWINDOW
    *ppIIPUIWindow, LPRECT prcPos, LPRECT prcClip
    , LPOLEINPLACEFRAMEINFO pFI)
    {
    PCPatronDoc     pDoc;
    RECTL           rcl;

    *ppIIPUIWindow=NULL;

    *ppIIPFrame=(LPOLEINPLACEFRAME)g_pFR;
    g_pFR->AddRef();

    pDoc=(PCPatronDoc)SendMessage(GetParent(m_pTen->m_hWnd)
        , DOCM_PDOCUMENT, 0, 0L);

    if (NULL!=pDoc)
        {
        pDoc->QueryInterface(IID_IOleInPlaceUIWindow
            , (PPVOID)ppIIPUIWindow);
        }

    //Now get the rectangles and frame information.
    m_pTen->RectGet(&rcl, TRUE);
    RECTFROMRECTL(*prcPos, rcl);

    //Include scroll position here.
    OffsetRect(prcPos, -(int)m_pTen->m_pPG->m_xPos
        , -(int)m_pTen->m_pPG->m_yPos);

    SetRect(prcClip, 0, 0, 32767, 32767);

    pFI->cb=sizeof(OLEINPLACEFRAMEINFO);
   #ifdef MDI
    pFI->fMDIApp=TRUE;
   #else
    pFI->fMDIApp=FALSE;
   #endif

    pFI->hwndFrame=g_pFR->Window();

    pFI->haccel=g_pFR->m_hAccelIP;
    pFI->cAccelEntries=CINPLACEACCELERATORS;

    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::Scroll
 *
 * Purpose:
 *  Asks the container to scroll the document, and thus the object,
 *  by the given amounts in the sz parameter.
 *
 * Parameters:
 *  sz              SIZE containing signed horizontal and vertical
 *                  extents by which the container should scroll.
 *                  These are in device units.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceSite::Scroll(SIZE sz)
    {
    int         x, y;

    x=m_pTen->m_pPG->m_xPos+sz.cx;
    y=m_pTen->m_pPG->m_yPos+sz.cy;

    SendScrollPosition(m_pTen->m_hWnd, WM_HSCROLL, x);
    SendScrollPosition(m_pTen->m_hWnd, WM_VSCROLL, y);
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::OnPosRectChange
 *
 * Purpose:
 *  Informs the container that the in-place object was resized.
 *  The container must call IOleInPlaceObject::SetObjectRects.
 *  This does not change the site's rectangle in any case.
 *
 * Parameters:
 *  prcPos          LPCRECT containing the new size of the object.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceSite::OnPosRectChange(LPCRECT prcPos)
    {
    if (NULL!=prcPos)
        m_pTen->m_rcPos=*prcPos;

    m_pTen->UpdateInPlaceObjectRects(prcPos, FALSE);
    return NOERROR;
    }


IIPUIWIN.CPP

/*
 * IIPUIWIN.CPP
 * Patron Chapter 22
 *
 * IOleInPlaceUIWindow interface implementation for Patron's
 * documents.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CImpIOleInPlaceUIWindow::CImpIOleInPlaceUIWindow
 * CImpIOleInPlaceUIWindow::~CImpIOleInPlaceUIWindow
 *
 * Parameters (Constructor):
 *  pDoc            PCPatronDoc of the document we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleInPlaceUIWindow::CImpIOleInPlaceUIWindow(PCPatronDoc pDoc
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pDoc=pDoc;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleInPlaceUIWindow::~CImpIOleInPlaceUIWindow(void)
    {
    return;
    }

/*
 * CImpIOleInPlaceUIWindow::QueryInterface
 * CImpIOleInPlaceUIWindow::AddRef
 * CImpIOleInPlaceUIWindow::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleInPlaceUIWindow object.
 */

STDMETHODIMP CImpIOleInPlaceUIWindow::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceUIWindow::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceUIWindow::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleInPlaceUIWindow::GetWindow
 *
 * Purpose:
 *  Retrieves the handle of the window associated with the object
 *  on which this interface is implemented.
 *
 * Parameters:
 *  phWnd           HWND * in which to store the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_FAIL if there is no
 *                  window.
 */

STDMETHODIMP CImpIOleInPlaceUIWindow::GetWindow(HWND *phWnd)
    {
    *phWnd=m_pDoc->m_hWnd;
    return NOERROR;
    }

/*
 * CImpIOleInPlaceUIWindow::ContextSensitiveHelp
 *
 * Purpose:
 *  Instructs the object on which this interface is implemented to
 *  enter or leave a context-sensitive help mode.
 *
 * Parameters:
 *  fEnterMode      BOOL TRUE to enter the mode, FALSE otherwise.
 *
 * Return Value:
 *  HRESULT         NOERROR or an appropriate error code.
 */

STDMETHODIMP CImpIOleInPlaceUIWindow::ContextSensitiveHelp
    (BOOL fEnterMode)
    {
   #ifdef MDI
    //Only MDI documents care
    if (NULL!=m_pDoc->m_pIOleIPActiveObject)
        {
        m_pDoc->m_pIOleIPActiveObject->ContextSensitiveHelp
           (fEnterMode);
        }
   #endif
    return NOERROR;
    }

/*
 * CImpIOleInPlaceUIWindow::GetBorder
 *
 * Purpose:
 *  Returns the rectangle in which the container is willing to
 *  negotiate about an object's adornments.
 *
 * Parameters:
 *  prcBorder       LPRECT in which to store the rectangle.
 *
 * Return Value:
 *  HRESULT         NOERROR if all is well, INPLACE_E_NOTOOLSPACE if
 *                  there is no negotiable space.
 */

STDMETHODIMP CImpIOleInPlaceUIWindow::GetBorder(LPRECT prcBorder)
    {
    if (NULL==prcBorder)
        return ResultFromScode(E_INVALIDARG);

    /*
     * All of the document window space is available for negotiation
     * since we can resize the Pages window to anything we want.  So
     * we just return the entire client rectangle here.
     */
    GetClientRect(m_pDoc->m_hWnd, prcBorder);
    return NOERROR;
    }

/*
 * CImpIOleInPlaceUIWindow::RequestBorderSpace
 *
 * Purpose:
 *  Asks the container if it can surrender the amount of space
 *  in pBW that the object would like for it's adornments.  The
 *  container does nothing but validate the spaces on this call.
 *
 * Parameters:
 *  pBW             LPCBORDERWIDTHS containing the requested space.
 *                  The values are the amount of space requested
 *                  from each side of the relevant window.
 *
 * Return Value:
 *  HRESULT         NOERROR if we can give up space,
 *                  INPLACE_E_NOTOOLSPACE otherwise.
 */

STDMETHODIMP CImpIOleInPlaceUIWindow::RequestBorderSpace
    (LPCBORDERWIDTHS pBW)
    {
    //We don't have any restrictions, so we always return NOERROR.
    return NOERROR;
    }

/*
 * CImpIOleInPlaceUIWindow::SetBorderSpace
 *
 * Purpose:
 *  Called when the object now officially requests that the
 *  container surrender border space it previously allowed in
 *  RequestBorderSpace.  The container should resize windows
 *  appropriately to surrender this space.
 *
 * Parameters:
 *  pBW             LPCBORDERWIDTHS containing the amount of space
 *                  from each side of the relevant window that
 *                  the object is now reserving.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceUIWindow::SetBorderSpace
    (LPCBORDERWIDTHS pBW)
    {
    RECT        rc;
    HWND        hWndDoc;
    HWND        hWndPages;
    POINT       pt1, pt2;

    if (NULL==pBW)
        return NOERROR;

    /*
     * Now we just have to resize the Pages window to make room for
     * the object's tools or resize it to occupy the full document
     * if we're reverting to normal.
     */

    hWndDoc=m_pDoc->m_hWnd;
    hWndPages=m_pDoc->m_pPG->Window();

    //Get the current offset of the Pages
    GetWindowRect(hWndPages, &rc);
    SETPOINT(pt1, rc.left, rc.top);
    SETPOINT(pt2, rc.right, rc.bottom);
    ScreenToClient(hWndDoc, &pt1);
    ScreenToClient(hWndDoc, &pt2);

    GetClientRect(hWndDoc, &rc);

    if (NULL!=pBW)
        {
        rc.left+=pBW->left;
        rc.right-=pBW->right;
        rc.top+=pBW->top;
        rc.bottom-=pBW->bottom;
        }

    //Only move Pages if necessary
    if (!(pt1.x==rc.left && pt1.y==rc.top
        && pt2.x==rc.right && pt2.y==rc.bottom))
        {
        SendMessage(hWndDoc, WM_SETREDRAW, FALSE, 0L);
        ShowWindow(hWndPages, SW_HIDE);

        m_pDoc->m_pPG->RectSet(&rc, FALSE);

        /*
         * Scroll the Pages in the opposite direction to keep
         * objects steady.
         */

        SendScrollPosition(hWndPages, WM_HSCROLL, pBW->left-pt1.x);
        SendScrollPosition(hWndPages, WM_VSCROLL, pBW->top-pt1.y);

        SendMessage(hWndDoc, WM_SETREDRAW, TRUE, 0L);
        ShowWindow(hWndPages, SW_SHOW);
        }

    return NOERROR;
    }

/*
 * CImpIOleInPlaceUIWindow::SetActiveObject
 *
 * Purpose:
 *  Provides the container with the object's IOleInPlaceActiveObject
 *  pointer.
 *
 * Parameters:
 *  pIIPActiveObj   LPOLEINPLACEACTIVEOBJECT of interest.
 *  pszObj          LPCOLESTR naming the object.  Not used.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceUIWindow::SetActiveObject(
    LPOLEINPLACEACTIVEOBJECT pIIPActiveObj, LPCOLESTR pszObj)
    {
    if (NULL!=m_pDoc->m_pIOleIPActiveObject)
        m_pDoc->m_pIOleIPActiveObject->Release();

    //NULLs m_pIOleIPActiveObject if pIIPActiveObj is NULL
    m_pDoc->m_pIOleIPActiveObject=pIIPActiveObj;

    if (NULL!=m_pDoc->m_pIOleIPActiveObject)
        m_pDoc->m_pIOleIPActiveObject->AddRef();

    return NOERROR;
    }


IOLECONT.CPP

/*
 * IOLECONT.CPP
 * Patron Chapter 22
 *
 * Implementation of the IOleItemContainer interface for Patron's
 * CPage and CPatronDoc alike, using the constructor parameter fDoc
 * to differentiate.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include <stdlib.h>
#include "patron.h"

/*
 * CImpIOleItemContainer::CImpIOleItemContainer
 * CImpIOleItemContainer::~CImpIOleItemContainer
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the page or pages.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 *  fDoc            BOOL indicating if we're in CPatronDoc or CPage
 */

CImpIOleItemContainer::CImpIOleItemContainer(LPVOID pObj
    , LPUNKNOWN pUnkOuter, BOOL fDoc)
    {
    m_cRef=0;
    m_fDoc=fDoc;

    if (fDoc)
        {
        m_pDoc=(PCPatronDoc)pObj;
        m_pPage=NULL;
        }
    else
        {
        m_pDoc=NULL;
        m_pPage=(PCPage)pObj;
        }

    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleItemContainer::~CImpIOleItemContainer(void)
    {
    return;
    }

/*
 * CImpIOleItemContainer::QueryInterface
 * CImpIOleItemContainer::AddRef
 * CImpIOleItemContainer::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleItemContainer object.
 */

STDMETHODIMP CImpIOleItemContainer::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleItemContainer::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleItemContainer::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleItemContainer::ParseDisplayName
 *
 * Purpose:
 *  Inherited member of IParseDisplayName that takes a string name
 *  and turns out a moniker for it.
 *
 * Parameters:
 *  pbc             LPBC to the binding context
 *  pszName         LPOLESTR to the name to parse.
 *  pchEaten        ULONG * into which to store how many
 *                  characters we scanned in the display name.
 *  ppmk            LPMONIKER * in which to return the moniker.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::ParseDisplayName(LPBC pbc
    , LPOLESTR pszName, ULONG *pchEaten, LPMONIKER *ppmk)
    {
    OLECHAR     ch;
    ULONG       chEaten=0;
    TCHAR       szName[256];
    TCHAR       szComp[15];
    LPTSTR      psz;
    UINT        cch;

    *ppmk=NULL;
    *pchEaten=0;

    /*
     * All we have to look for is the string between the !
     * delimeters (or a null terminator).  pszName should be pointing
     * to a !, so skip it and scan the string for a ! or 0,
     * then pass the result to CreateItemMoniker.
     */

    psz=szName;

    ch=*pszName++;
    chEaten++;

    if ((OLECHAR)'!'!=ch)
        return ResultFromScode(MK_E_SYNTAX);

    ch=*pszName++;

    while ((OLECHAR)0!=ch && (OLECHAR)'!' !=ch)
        {
        *psz++=(TCHAR)ch;
        chEaten++;
        ch=*pszName++;
        }

    *psz=(TCHAR)0;

    /*
     * Syntax check.  If we're the DOcument object, check for
     * "Page n" at the beginning of the string.  Otherwise check
     * for "Tenant n".
     */
    lstrcpy(szComp, m_fDoc ? TEXT("Page ") : TEXT("Tenant "));

    //Does szName start with szComp?
    cch=lstrlen(szComp);

    if (0!=_tcsncicmp(szName, szComp, cch))
        {
        *pchEaten=1;    //Parsed ! at least
        return ResultFromScode(MK_E_SYNTAX);
        }

    //Check for a number in szName
    if ((TCHAR)'0' != szName[cch])
        {
        if (0==_ttoi(szName+cch))
            {
            *pchEaten=cch;  //Got past name
            return ResultFromScode(MK_E_SYNTAX);
            }
        }

    *pchEaten=chEaten;
   #ifdef WIN32ANSI
    //Use the ANSI version here since szName is ANSI
    return INOLE_CreateItemMoniker(TEXT("!"), szName, ppmk);
   #else
    return CreateItemMoniker(OLETEXT("!"), szName, ppmk);
   #endif
    }

/*
 * CImpIOleItemContainer::EnumObjects
 *
 * Purpose:
 *  Creates and returns an IEnumUnknown object that allows the
 *  caller to walk through the objects in this continer thing.
 *
 * Parameters:
 *  dwFlags         DWORD specifying what kind of objects to
 *                  enumerate.
 *  ppEnum          LPENUMUNKNOWN * into which to return the
 *                  enumerator
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::EnumObjects(DWORD dwFlags
    , LPENUMUNKNOWN *ppEnum)
    {
    *ppEnum=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleItemContainer::LockContainer
 *
 * Purpose:
 *  Establishes a lock on the container to prevent it from shutting
 *  down outside of user control.  This is used to control the
 *  lifetime of the container when it's used to update a link to an
 *  embedded object within it.  If we're unlock and the user has not
 *  taken control, we close.
 *
 * Parameters:
 *  fLock           BOOL indicating a lock or unlock.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::LockContainer(BOOL fLock)
    {
    /*
     * This is pretty much the same implementation as
     * IClassFactory::LockServer, and we can use the same lock
     * count to accomplish our goal.
     */

    if (fLock)
        g_cLock++;
    else
        {
        g_cLock--;
        g_cObj++;
        ObjectDestroyed();
        }

    return NOERROR;
    }

/*
 * CImpIOleItemContainer::GetObject
 *
 * Purpose:
 *  Returns the requested interface pointer on an object in this
 *  container.
 *
 * Parameters:
 *  pszItem         LPOLESTR to the item we must locate.
 *  dwSpeed         DWORD identifying how long the caller is willing
 *                  to wait.
 *  pcb             LPBINDCTX providing the binding context.
 *  riid            REFIID of the interface requested.
 *  ppv             PPVOID into which to return the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::GetObject(LPOLESTR pszItem
    , DWORD dwSpeed, LPBINDCTX pbc, REFIID riid, PPVOID ppv)
    {
    DWORD       dw;
    char        szTemp[40];     //ANSI for atol
    HRESULT     hr=ResultFromScode(E_FAIL);
    PCPage      pPage;
    PCTenant    pTenant;
    LPUNKNOWN   pObj;
    UINT        i, iCur;

    *ppv=NULL;

    if (m_fDoc)
        {
        /*
         * The item name should be "Page n", so we'll do it the
         * easy way:  call atol on pszItem+5 (we always know that
         * we'll have "Page " there since we put it there (see
         * CPage::GetStorageName).
         */

        UNICODETOANSI((pszItem+5), szTemp, sizeof(szTemp));
        dw=atol(szTemp);

        i=m_pDoc->m_pPG->IPageGetFromID(dw, &pPage, FALSE);

        if (NOVALUE==i)
            return hr;

        /*
         * If we're asked for immediate speed, only do this if the
         * page is already current, i.e. everything is loaded.
         */
        iCur=m_pDoc->m_pPG->CurPageGet();

        if (BINDSPEED_IMMEDIATE==dwSpeed && iCur!=i)
            return ResultFromScode(MK_E_EXCEEDEDDEADLINE);

        m_pDoc->m_pPG->CurPageSet(i);

        //This will have changed to be the current page now.
        if (NULL!=m_pDoc->m_pPG->m_pPageCur)
            hr=m_pDoc->m_pPG->m_pPageCur->QueryInterface(riid, ppv);
        }
    else
        {
        if (TenantFromName(pszItem, &pTenant))
            {
            pTenant->ObjectGet(&pObj);

            /*
             * If we're asked for immediate or moderate, only work
             * if the object is already running.
             */
            hr=IsRunning(pszItem);  //This is the function below

            if ((BINDSPEED_IMMEDIATE==dwSpeed
                || BINDSPEED_MODERATE==dwSpeed) && NOERROR!=hr)
                hr=ResultFromScode(MK_E_EXCEEDEDDEADLINE);
            else
                {
                //IMPORTANT:  Make sure this object is running first
                OleRun(pObj);
                hr=pObj->QueryInterface(riid, ppv);
                }

            pObj->Release();
            }
        else
            hr=ResultFromScode(MK_E_NOOBJECT);
        }

    return hr;
    }

/*
 * CImpIOleItemContainer::GetObjectStorage
 *
 * Purpose:
 *  Similar to get Object in that we have to locate the object
 *  described by a given name, but instead of returning any old
 *  interface we return a storage element.
 *
 * Parameters:
 *  pszItem         LPOLESTR to the item we must locate.
 *  pcb             LPBINDCTX providing the binding context.
 *  riid            REFIID of the interface requested.  Usually
 *                  IStorage or IStream.
 *  ppv             PPVOID into which to return the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::GetObjectStorage(LPOLESTR pszItem
    , LPBINDCTX pbc, REFIID riid, PPVOID ppv)
    {
    PCTenant    pTenant;

    *ppv=NULL;

    if (m_fDoc)
        return ResultFromScode(E_NOTIMPL);

    //Can only handle IStorage.
    if (IID_IStorage!=riid)
        return ResultFromScode(E_NOINTERFACE);

    if (TenantFromName(pszItem, &pTenant))
        pTenant->StorageGet((LPSTORAGE *)ppv);

    return (NULL!=*ppv) ? NOERROR : ResultFromScode(E_FAIL);
    }

/*
 * CImpIOleItemContainer::IsRunning
 *
 * Purpose:
 *  Answers if the object under the given name is currently running.
 *
 * Parameters:
 *  pszItem         LPOLESTR of the item to check
 *
 * Return Value:
 *  HRESULT         NOERROR if the object is running, S_FALSE
 *                  otherwise.  Possibly MK_E_NOOBJECT if the name
 *                  is bogus.
 */

STDMETHODIMP CImpIOleItemContainer::IsRunning(LPOLESTR pszItem)
    {
    HRESULT     hr;
    PCTenant    pTenant;
    LPUNKNOWN   pObj;
    LPOLEOBJECT pIOleObject;

    /*
     * If this is the document's container interface, the object
     * is a page and the page is always running.
     */
    if (m_fDoc)
        return NOERROR;
    else
        {
        if (TenantFromName(pszItem, &pTenant))
            {
            //Ask the actual object if its running.
            pTenant->ObjectGet(&pObj);
            hr=pObj->QueryInterface(IID_IOleObject
                , (PPVOID)&pIOleObject);
            pObj->Release();

            if (SUCCEEDED(hr))
                {
                hr=(OleIsRunning(pIOleObject))
                    ? NOERROR : ResultFromScode(S_FALSE);
                pIOleObject->Release();
                }
            }
        else
            hr=ResultFromScode(MK_E_NOOBJECT);
        }

    return hr;
    }

/*
 * CImpIOleItemContainer::TenantFromName
 * (Private)
 *
 * Purpose:
 *  This function which is NOT part of the interface retrieves
 *  a tenant pointer from a tenant name.
 *
 * Parameters:
 *  pszItem         LPOLESTR of the tenant to locate.
 *  ppTenant        PCTenant * in which to return the pointer.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CImpIOleItemContainer::TenantFromName(LPOLESTR pszItem
    , PCTenant *ppTenant)
    {
    DWORD       dw;
    char        szTemp[40];     //ANSI for atol

    if (m_fDoc)
        return FALSE;

    //The item name should be "Tenant xxxx", so use pszItem+7.
    UNICODETOANSI((pszItem+7), szTemp, sizeof(szTemp));
    dw=atol(szTemp);

    *ppTenant=NULL;

    return m_pPage->TenantGetFromID(dw, ppTenant, FALSE);
    }


IPERFILE.CPP

/*
 * IPERFILE.CPP
 * Patron Chapter 22
 *
 * Implementation of the IPersistFile interface for Patron's
 * documents.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CImpIPersistFile:CImpIPersistFile
 * CImpIPersistFile::~CImpIPersistFile
 *
 * Constructor Parameters:
 *  pDoc            PCPatronDoc associated with this interface
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistFile::CImpIPersistFile(PCPatronDoc pDoc
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pDoc=pDoc;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPersistFile::~CImpIPersistFile(void)
    {
    return;
    }

/*
 * CImpIPersistFile::QueryInterface
 * CImpIPersistFile::AddRef
 * CImpIPersistFile::Release
 *
 * Purpose:
 *  Standard set of IUnknown members for this interface
 */

STDMETHODIMP CImpIPersistFile::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistFile::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistFile::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistFile::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the file represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::GetClassID(LPCLSID pClsID)
    {
    *pClsID=CLSID_PatronPages;
    return NOERROR;
    }

/*
 * CImpIPersistFile::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this file since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 */

STDMETHODIMP CImpIPersistFile::IsDirty(void)
    {
    return ResultFromScode(m_pDoc->FDirtyGet() ? S_OK : S_FALSE);
    }

/*
 * IPersistFile::Load
 *
 * Purpose:
 *  Asks the server to load the document for the given filename.
 *
 * Parameters:
 *  pszFile         LPCOLESTR to the filename to load.
 *  grfMode         DWORD containing open flags requested from the
 *                  caller.  Currently these are safely ignored.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::Load(LPCOLESTR pszFile, DWORD grfMode)
    {
    UINT        uRet;

   #ifdef WIN32ANSI
    char        szTemp[CCHPATHMAX];

    WideCharToMultiByte(CP_ACP, 0, pszFile, -1, szTemp, CCHPATHMAX
       , NULL, NULL);
    uRet=m_pDoc->Load(TRUE, szTemp);
   #else
    uRet=m_pDoc->Load(TRUE, (LPTSTR)pszFile);
   #endif
    return (DOCERR_NONE==uRet) ? NOERROR
        : ResultFromScode(STG_E_READFAULT);
    }

/*
 * IPersistFile::Save
 *
 * Purpose:
 *  Instructs the server to write the current file into a new
 *  filename, possibly then using that filename as the current one.
 *
 * Parameters:
 *  pszFile         LPCOLESTR of the file into which we save.  If NULL,
 *                  this means save the current file.
 *  fRemember       BOOL indicating if we're to use this filename as
 *                  the current file now (Save As instead of Save
 *                  Copy As).
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::Save(LPCOLESTR pszFile, BOOL fRemember)
    {
    UINT        uRet;

    /*
     * Since we don't want to mess with changing Save (which would
     * require changes to CLASSLIB, urk) we instead save fRemember
     * in the document before calling Save which supresses the call
     * to CPatronDoc::Rename if FALSE.
     */

    m_pDoc->m_fRename=fRemember;
   #ifdef WIN32ANSI
    char        szTemp[CCHPATHMAX];

    WideCharToMultiByte(CP_ACP, 0, pszFile, -1, szTemp, CCHPATHMAX
       , NULL, NULL);
    uRet=m_pDoc->Save(0, szTemp);
   #else
    uRet=m_pDoc->Save(0, (LPTSTR)pszFile);
   #endif
    m_pDoc->m_fRename=TRUE;

    return (DOCERR_NONE==uRet) ? NOERROR
        : ResultFromScode(STG_E_WRITEFAULT);
    }

/*
 * IPersistFile::SaveCompleted
 *
 * Purpose:
 *  Informs us that the operation that called Save is now finished
 *  and we can access the file again.
 *
 * Parameters:
 *  pszFile         LPCOLESTR of the file in which we can start
 *                  writing again.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::SaveCompleted(LPCOLESTR pszFile)
    {
    return NOERROR;
    }

/*
 * IPersistFile::GetCurFile
 *
 * Purpose:
 *  Retrieves the name of the current file.
 *
 * Parameters:
 *  ppszFile        LPOLESTR * into which we store a pointer to
 *                  the filename that should be allocated with the
 *                  shared IMalloc.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::GetCurFile(LPOLESTR *ppszFile)
    {
    LPMALLOC    pIMalloc;
    LPOLESTR    psz;
    UINT        uRet;

    *ppszFile=NULL;

    if (FAILED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        return ResultFromScode(E_FAIL);

    psz=(LPOLESTR)pIMalloc->Alloc(CCHPATHMAX*sizeof(OLECHAR));
    pIMalloc->Release();

   #ifdef WIN32ANSI
    char    szTemp[CCHPATHMAX];

    uRet=m_pDoc->FilenameGet(szTemp, CCHPATHMAX);
    MultiByteToWideChar(CP_ACP, 0, szTemp, -1, psz, CCHPATHMAX);
   #else
    uRet=m_pDoc->FilenameGet(psz, CCHPATHMAX);
   #endif

    //If we have no filename, return the prompt for File Open/Save.
    if (0==uRet)
       #ifdef WIN32ANSI
        MultiByteToWideChar(CP_ACP, 0
            , (*m_pDoc->m_pST)[IDS_EXTENSION], -1, psz, CCHPATHMAX);
       #else
        lstrcpy(psz, (*m_pDoc->m_pST)[IDS_EXTENSION]);
       #endif

    *ppszFile=psz;
    return (0==uRet) ? ResultFromScode(S_FALSE) : NOERROR;
    }


IUILINK.CPP

/*
 * IUILINK.CPP
 * Patron Chapter 22
 *
 * Implementation of an object with the IOleUILinkContainer
 * interface necessary to use the standard Links Dialog.  This
 * is implemented as a stand-along object with access to the CPage
 * with which its associated, primiarily because it is only used
 * for the one dialog.  Therefore this object has it's own IUnknown.
 * In addition, we use the Links Assistant object developed in this
 * chapter to simplify our own code.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CIOleUILinkContainer::CIOleUILinkContainer
 * CIOleUILinkContainer::~CIOleUILinkContainer
 *
 * Parameters (Constructor):
 *  pPage           PCPage of the page we're in.
 */

CIOleUILinkContainer::CIOleUILinkContainer(PCPage pPage)
    {
    m_cRef=0;
    m_pPage=pPage;
    m_iTenant=0;
    m_pDelIUILinks=NULL;
    m_fDirty=FALSE;
    return;
    }

CIOleUILinkContainer::~CIOleUILinkContainer(void)
    {
    ReleaseInterface(m_pDelIUILinks);
    return;
    }

/*
 * CIOleUILinkContainer::Init
 *
 * Purpose:
 *  Performs initialization on the object that might fail.  In
 *  particular this creates an object of CLSID_LinksAssistant that
 *  helps in implementing this interface.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CIOleUILinkContainer::Init(void)
    {
    HRESULT     hr;

    hr=CoCreateInstance(CLSID_LinksAssistant, NULL
        , CLSCTX_INPROC_SERVER, IID_IOleUILinkContainer
        , (PPVOID)&m_pDelIUILinks);

    return SUCCEEDED(hr);
    }

/*
 * CIOleUILinkContainer::QueryInterface
 * CIOleUILinkContainer::AddRef
 * CIOleUILinkContainer::Release
 *
 * Purpose:
 *  IUnknown members for CIOleUILinkContainer object.
 */

STDMETHODIMP CIOleUILinkContainer::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IOleUILinkContainer==riid)
        {
        *ppv=this;
        AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CIOleUILinkContainer::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CIOleUILinkContainer::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CIOleUILinkContainer::GetNextLink
 *
 * Purpose:
 *  Called when the Links dialog is filling its listbox.  Here we
 *  need to return a key for the first link if dwLink is zero, then
 *  return the next links if it's non-zero.
 *
 * Parameters:
 *  dwLink          DWORD last returned from this function.  Zero if
 *                  this is the first call to this function.
 *
 * Return Value:
 *  DWORD           Some value that identifies this object.  Zero
 *                  stops the sequence such that this function is
 *                  no longer called.
 */

STDMETHODIMP_(DWORD) CIOleUILinkContainer::GetNextLink(DWORD dwLink)
    {
    PCTenant        pTenant;

    //If we're told to start the sequence, set index to zero.
    if (0L==dwLink)
        m_iTenant=0;

    /*
     * On each subsequent call, find the next linked object in
     * this document and return it.  Make sure the index is
     * incremented for the next time this function is called.
     */
    for ( ; m_iTenant < m_pPage->m_cTenants; m_iTenant++)
        {
        if (m_pPage->TenantGet(m_iTenant, &pTenant, FALSE))
            {
            if (TENANTTYPE_LINKEDOBJECT==pTenant->TypeGet())
                {
                m_iTenant++;
                return (DWORD)pTenant;
                }
            }
        }

    //If we hit the end of list, this tells the dialog to stop.
    return 0L;
    }

/*
 * CIOleUILinkContainer::SetLinkUpdateOptions
 *
 * Purpose:
 *  Informs the application to call IOleLink::SetUpdateOptions for
 *  the object identified by dwLink.
 *
 * Parameters:
 *  dwLink          DWORD object identifier as returned from
 *                  GetNextLink.
 *  dwOptions       DWORD containing the new options.
 *
 * Return Value:
 *  HRESULT         Return value of IOleLink::SetUpdateOptions.
 */

STDMETHODIMP CIOleUILinkContainer::SetLinkUpdateOptions(DWORD dwLink
    , DWORD dwOptions)
    {
    LPOLELINK       pIOleLink;
    HRESULT         hr;

    if (NULL==dwLink)
        return ResultFromScode(E_FAIL);

    /*
     * Your responsibility is to call the object's
     * IOleLink::SetUpdateOptions function with dwOptions.  Simple?
     *
     * For Patron we must first get the object pointer obtainable
     * from the tenant's ObjectGet function, then QI for IOleLink.
     */

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    hr=pIOleLink->SetUpdateOptions(dwOptions);
    pIOleLink->Release();

    m_fDirty=SUCCEEDED(hr);
    return hr;
    }

/*
 * CIOleUILinkContainer::GetLinkUpdateOptions
 *
 * Purpose:
 *  Requests the container to call IOleLink::GetUpdateOptions for
 *  the object identified by dwLink.
 *
 * Parameters:
 *  dwLink          DWORD identifying the object
 *  pdwOptions      LPDWORD in which to store the options.
 *
 * Return Value:
 *  HRESULT         Return value of IOleLink::GetUpdateOptions
 */

STDMETHODIMP CIOleUILinkContainer::GetLinkUpdateOptions(DWORD dwLink
    , LPDWORD pdwOptions)
    {
    LPOLELINK       pIOleLink;
    HRESULT         hr;

    if (NULL==dwLink)
        return ResultFromScode(E_FAIL);

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    hr=pIOleLink->GetUpdateOptions(pdwOptions);
    pIOleLink->Release();

    return hr;
    }

/*
 * CIOleUILinkContainer::SetLinkSource
 *
 * Purpose:
 *  Changes the moniker to which an object is linked.
 *
 * Parameters:
 *  dwLink          DWORD identifying the object in question.
 *  pszName         LPTSTR to the displayable name of the source.
 *  cchName         ULONG length of the file portaion of pszName
 *  pchEaten        ULONG * in which to return the number of
 *                  characters used in parsing pszDisplayName.
 *  fValidate       BOOL indicating if we're to validate that the
 *                  source exists first.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_FAIL otherwise.
 */

STDMETHODIMP CIOleUILinkContainer::SetLinkSource(DWORD dwLink
    , LPTSTR pszName, ULONG cchName, ULONG *pchEaten
    , BOOL fValidate)
    {
    PCTenant        pTenant=(PCTenant)dwLink;
    HRESULT         hr;
    LPOLELINK       pIOleLink;

    if (NULL==dwLink)
        return ResultFromScode(E_FAIL);

    //This is for use in GetLinkSource, below.
    pTenant->m_fLinkAvail=FALSE;

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    hr=m_pDelIUILinks->SetLinkSource((DWORD)pIOleLink, pszName
        , cchName, pchEaten, fValidate);
    pIOleLink->Release();

    if (FAILED(hr))
        return hr;

    //hr will be S_FALSE if link is unavailable.
    pTenant->Repaint();
    pTenant->m_fLinkAvail=(NOERROR==hr);
    m_fDirty=TRUE;
    return NOERROR;
    }

/*
 * CIOleUILinkContainer::GetLinkSource
 *
 * Purpose:
 *  Retrieves various strings and values for this link source.
 *
 * Parameters:
 *  dwLink          DWORD identifying the object affected.
 *  ppszName        LPTSTR * in which to return the new source
 *                  name
 *  pcchName        ULONG * in which to return the length of
 *                  pszName
 *  ppszFullLink    LPTSTR * in which to return the full name of
 *                  the class of linked object.
 *  ppszShortLink   LPTSTR * in which to return the short name of
 *                  the class of linked object.
 *  pfSourceAvail   BOOL * in which to return if this is an
 *                  available link source.
 *  pfSelected      BOOL * in which to return if this object is
 *                  currently selected in the document.  This
 *                  selects the item in the listbox for this object.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, error code otherwise.
 */

STDMETHODIMP CIOleUILinkContainer::GetLinkSource(DWORD dwLink
    , LPTSTR *ppszName, ULONG *pcchName, LPTSTR *ppszFullLink
    , LPTSTR *ppszShortLink, BOOL *pfSourceAvail, BOOL *pfSelected)
    {
    HRESULT         hr;
    PCTenant        pTenant=(PCTenant)dwLink;
    LPOLELINK       pIOleLink=NULL;
    LPOLEOBJECT     pIOleObject=NULL;
    LPMONIKER       pmk=NULL;
    LPMONIKER       pmkFirst=NULL;
    LPBC            pbc=NULL;

    if (NULL==dwLink)
        return ResultFromScode(E_FAIL);

    //We know what this is from SetLinkSource
    *pfSourceAvail=pTenant->m_fLinkAvail;

    if (pfSelected)
        *pfSelected=pTenant->FIsSelected();

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    hr=m_pDelIUILinks->GetLinkSource((DWORD)pIOleLink, ppszName
        , pcchName, ppszFullLink, ppszShortLink, pfSourceAvail
        , pfSelected);

    pIOleLink->Release();
    return hr;
    }

/*
 * CIOleUILinkContainer::OpenLinkSource
 *
 * Purpose:
 *  Asks the container to call DoVerb on this object with
 *  OLEIVERB_SHOW.
 *
 * Parameters:
 *  dwLink          DWORD identifying the linked object.
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CIOleUILinkContainer::OpenLinkSource(DWORD dwLink)
    {
    PCTenant        pTenant=(PCTenant)dwLink;

    //CHAPTER22MOD
    pTenant->Activate(OLEIVERB_OPEN, NULL);
    //End CHAPTER22MOD

    return NOERROR;
    }

/*
 * CIOleUILinkContainer::UpdateLink
 *
 * Purpose:
 *  Asks the container to update the link for this object.
 *
 * Parameters:
 *  dwLink          DWORD identifying the linked object.
 *  fErrorMessage   BOOL indicating if we can show errors.
 *  fErrorAction    BOOL making no sense whatsoever.
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CIOleUILinkContainer::UpdateLink(DWORD dwLink
    , BOOL fErrorMessage, BOOL fErrorAction)
    {
    PCTenant        pTenant=(PCTenant)dwLink;
    LPOLELINK       pIOleLink;
    HRESULT         hr;

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    hr=m_pDelIUILinks->UpdateLink((DWORD)pIOleLink, fErrorMessage
        , fErrorAction);

    pTenant->Repaint();
    pTenant->m_fLinkAvail=SUCCEEDED(hr);
    pIOleLink->Release();

    if (FAILED(hr))
        {
        if (fErrorMessage)
            {
            MessageBox(m_pPage->m_hWnd
                , TEXT("Could not update link."), TEXT("Patron")
                , MB_OK);
            }
        }
    else
        m_fDirty=TRUE;

    return hr;
    }

/*
 * CIOleUILinkContainer::CancelLink
 *
 * Purpose:
 *  Requests that the container turn this linked object into a
 *  static object.
 *
 * Parameters:
 *  dwLink          DWORD identifying the linked object.
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CIOleUILinkContainer::CancelLink(DWORD dwLink)
    {
    PCTenant        pTenant=(PCTenant)dwLink;
    LPOLELINK       pIOleLink;
    HRESULT         hr;

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    //This sets the source moniker to NULL.
    m_pDelIUILinks->CancelLink((DWORD)pIOleLink);
    pIOleLink->Release();

    //Go change this object over to a static one.
    pTenant->ConvertToStatic();

    m_fDirty=TRUE;
    return NOERROR;
    }

//PROTECTED FUNCTIONS INTERNAL TO CIOleUILinkContainer

/*
 * CIOleUILinkContainer::GetObjectInterface
 * (Protected)
 *
 * Purpose:
 *  Retrieves and interface pointer for the object identified by
 *  dwLink
 *
 * Parameters:
 *  dwLink          DWORD identifying the object
 *  riid            REFIID of the interface desired.
 *  ppv             PPVOID into which we return the pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, error code otherwise.
 */

STDMETHODIMP CIOleUILinkContainer::GetObjectInterface(DWORD dwLink
    , REFIID riid, PPVOID ppv)
    {
    PCTenant        pTenant=(PCTenant)dwLink;
    LPUNKNOWN       pIUnknown;
    HRESULT         hr;

    pTenant->ObjectGet(&pIUnknown);
    hr=pIUnknown->QueryInterface(riid, ppv);
    pIUnknown->Release();

    return hr;
    }


PAGE.CPP

/*
 * PAGE.CPP
 * Patron Chapter 22
 *
 * Implementation of parts of the CPage class; those member
 * functions dealing with mouse events are in PAGEMOUS.CPP.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPage::CPage
 * CPage::~CPage
 *
 * Constructor Parameters:
 *  dwID            DWORD identifier for this page.
 *  hWnd            HWND of the pages window (for repaints, etc).
 *  pPG             PCPages to the Pages window.
 */

CPage::CPage(DWORD dwID, HWND hWnd, PCPages pPG)
    {
    m_dwID     =dwID;
    m_pIStorage=NULL;

    m_cOpens=0;
    m_hWnd=hWnd;
    m_pPG=pPG;

    m_dwIDNext      =0;
    m_cTenants      =0;
    m_hWndTenantList=NULL;
    m_iTenantCur    =NOVALUE;   //Tenants are zero indexed.
    m_pTenantCur    =NULL;

    m_uHTCode=HTNOWHERE;
    m_uSizingFlags=0;
    m_fTracking=FALSE;
    m_hDC=NULL;

    m_fDragPending=FALSE;
    m_fSizePending=FALSE;
    m_fTimer=FALSE;

    //Get WIN.INI distance and delay values, with OLE defaults.
    m_cxyDist=GetProfileInt(TEXT("windows"), TEXT("DragMinDist")
        , DD_DEFDRAGMINDIST);
    m_cDelay=GetProfileInt(TEXT("windows"), TEXT("DragDelay")
        , DD_DEFDRAGDELAY);

    m_fReopen=FALSE;
    m_pmkFile=m_pPG->m_pmkFile;

    if (NULL!=m_pmkFile)
        m_pmkFile->AddRef();

    m_cRef=0L;
    m_dwRegROTWild=0L;
    m_pImpIOleItemContainer=NULL;

    //CHAPTER22MOD
    m_fFirstUIActivate=TRUE;  //We haven't UIActivated anyone yet
    //End CHAPTER22MOD
    return;
    }

CPage::~CPage(void)
    {
    INOLE_RevokeAsRunning(&m_dwRegROTWild);

    if (m_fTimer)
        KillTimer(m_hWnd, IDTIMER_DEBOUNCE);

    m_hWnd=NULL;
    Close(FALSE);
    return;
    }

/*
 * CPage::QueryInterface
 * CPage::AddRef
 * CPage::Release
 *
 * Purpose:
 *  IUnknown members for CPage object.
 */

STDMETHODIMP CPage::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IOleItemContainer==riid || IID_IOleContainer==riid
        || IID_IParseDisplayName==riid)
        *ppv=m_pImpIOleItemContainer;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CPage::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPage::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CPage::GetID
 *
 * Return Value:
 *  DWORD           dwID field in this page.
 */

DWORD CPage::GetID(void)
    {
    return m_dwID;
    }

/*
 * CPage::Open
 *
 * Purpose:
 *  Retrieves the IStorage associated with this page.  The IStorage
 *  is owned by the page and thus the page always holds a reference
 *  count.  The caller should call Close or delete this page to
 *  match this open.
 *
 *  This function may be called multiple times resulting in
 *  additional reference counts on the storage each of which must be
 *  matched with a call to Close.  The last Close can be done
 *  through delete.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which this page lives.
 *
 * Return Value:
 *  BOOL            TRUE if opening succeeds, FALSE otherwise.
 */

BOOL CPage::Open(LPSTORAGE pIStorage)
    {
    HRESULT                 hr=NOERROR;
    LPSTREAM                pIStream;
    DWORD                   dwMode;
    OLECHAR                 szTemp[32];
    TCHAR                   szCap[32];
    BOOL                    fNew;
    BOOL                    fCreated=FALSE;
    TENANTLIST              tl;
    PTENANTINFO             pti;
    ULONG                   cb;
    LPMALLOC                pIMalloc;
    UINT                    i;
    PCTenant                pTenant;
    UINT                    cLinks;
    LPOLELINK               pIOleLink;
    LPUNKNOWN               pIUnknown;
    UINT                    uRet;
    OLEUIEDITLINKS          el;
    PCIOleUILinkContainer   pIUILinks;
    HWND                    hWndDoc;

    fNew=(NULL==m_pIStorage);

    if (!fNew)
        {
        m_cOpens++;
        m_pIStorage->AddRef();
        return TRUE;
        }

    if (NULL==pIStorage)
        return FALSE;

    /*
     * Attempt to open the storage under this ID.  If none,
     * create one.  In either case, the IStorage is either
     * saved in pPage or released.
     */

    GetStorageName(szTemp);
    dwMode=STGM_TRANSACTED | STGM_READWRITE | STGM_SHARE_EXCLUSIVE;

    hr=pIStorage->OpenStorage(szTemp, NULL, dwMode, NULL, 0
        , &m_pIStorage);

    if (FAILED(hr))
        {
        hr=pIStorage->CreateStorage(szTemp, dwMode, 0, 0
            , &m_pIStorage);
        fCreated=TRUE;
        }

    if (FAILED(hr))
        return FALSE;

    m_cOpens++;

    if (NULL==m_hWndTenantList)
        {
        /*
         * The first time we open this page, create the hidden
         * listbox we'll use to track tenants.  We give it the
         * owner-draw style so we can just store pointers in it.
         */
        m_hWndTenantList=CreateWindow(TEXT("listbox")
            , TEXT("Tenant List"), WS_POPUP | LBS_OWNERDRAWFIXED
            , 0, 0, 100, 100, HWND_DESKTOP, NULL
            , m_pPG->m_hInst, NULL);

        if (NULL==m_hWndTenantList)
            return FALSE;
        }

    m_pImpIOleItemContainer=new CImpIOleItemContainer(this, this
        , FALSE);

    if (NULL==m_pImpIOleItemContainer)
        return FALSE;

    //If this is brand-new, we're done.
    if (fCreated)
        return TRUE;

    /*
     * Now open the stream we saved in Close and load all the
     * tenants listed in there.  If there are none, then we don't
     * have to load squat.
     */

    hr=m_pIStorage->OpenStream(SZSTREAMTENANTLIST, NULL, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pIStream->Read(&tl, sizeof(tl), NULL);
        m_cTenants=tl.cTenants;
        m_dwIDNext=tl.dwIDNext;
        m_iTenantCur=0;

        cb=tl.cTenants*sizeof(TENANTINFO);

        if (0!=cb)
            {
            pti=(PTENANTINFO)pIMalloc->Alloc(cb);

            if (NULL!=pti)
                {
                pIStream->Read(pti, cb, NULL);

                for (i=0; i < m_cTenants; i++)
                    {
                    if (TenantAdd(NOVALUE, (pti+i)->dwID, &pTenant))
                        {
                        pTenant->Load(m_pIStorage, (pti+i));

                        //Make sure it knows about the show state.
                        pTenant->ShowObjectType(m_pPG->m_fShowTypes);
                        }
                    }

                pIMalloc->Free(pti);
                }
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    //Get and select the first tenant
    //CHAPTER22MOD
    if (TenantGet(0, &m_pTenantCur, FALSE))
        m_pTenantCur->Select(TRUE, TRUE);
    //End CHAPTER22MOD

    //If we just saved and closed, don't bother with updating links
    if (m_fReopen)
        {
        m_fReopen=FALSE;
        return TRUE;
        }

    /*
     * Update all the links in this page, showing the progress
     * indicator as it's happening.  We use the same
     * IOlUILinkContainer implementation as we do for the links
     * dialog, passing it to OleUIUpdateLinks which does everything
     * for us.
     *
     * We might also optimize this to not do anything if there are
     * no automatic links, but it's not a big concern.
     */

    //First, count the number of automatic links.
    cLinks=0;

    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            {
            DWORD       dw;

            pTenant->ObjectGet(&pIUnknown);
            hr=pIUnknown->QueryInterface(IID_IOleLink
                , (PPVOID)&pIOleLink);
            pIUnknown->Release();

            if (FAILED(hr))
                continue;

            pIOleLink->GetUpdateOptions(&dw);
            pIOleLink->Release();

            if (OLEUPDATE_ALWAYS==dw)
                cLinks++;
            }
        }

    //If we have any automatic links, invoke the update dialog.
    if (0==cLinks)
        return TRUE;

    //Create an IOleUILinkContainer instantiation.
    if (!m_pPG->GetUILinkContainer(&pIUILinks))
        return TRUE;    //Guess we can't update, oh well.

    hWndDoc=GetParent(m_hWnd);
    LoadString(m_pPG->m_hInst, IDS_CAPTION, szCap, sizeof(szCap));

    if (!OleUIUpdateLinks(pIUILinks, hWndDoc, szCap, cLinks))
        {
        /*
         * If updating failed, ask to show the links dialog.  NOTE:
         * OleUIPromptUser has a variable wsprintf argument list
         * after the hWnd parameter!  Use appropriate typecasting!
         */
        uRet=OleUIPromptUser(IDD_CANNOTUPDATELINK, hWndDoc, szCap);

       #ifdef IDC_PU_LINKS
        if (IDC_PU_LINKS==uRet)
       #else
        if (ID_PU_LINKS==uRet)
       #endif
            {
            //Throw up the links dialog.
            memset(&el, 0, sizeof(el));
            el.cbStruct=sizeof(el);
            el.hWndOwner=hWndDoc;
            el.lpOleUILinkContainer=pIUILinks;
            OleUIEditLinks(&el);
            }
        }

    m_pPG->m_fDirty=pIUILinks->m_fDirty;
    pIUILinks->Release();
    return TRUE;
    }

/*
 * CPage::Close
 *
 * Purpose:
 *  Possibly commits the storage, then releases it reversing the
 *  reference count from Open.
 *
 * Parameters:
 *  fCommit         BOOL indicating if we're to commit.
 *
 * Return Value:
 *  None
 */

void CPage::Close(BOOL fCommit)
    {
    if (NULL==m_pIStorage)
        return;

    if (fCommit)
        Update();

    m_pIStorage->Release();

    //If this was the last close, make all tenants loaded->passive
    if (0==--m_cOpens)
        {
        UINT        i;
        PCTenant    pTenant;

        m_pIStorage=NULL;

        for (i=0; i < m_cTenants; i++)
            {
            if (TenantGet(i, &pTenant, FALSE))
                {
                if (NULL!=m_hWnd)
                    {
                    //Open may select again, so this repaints.
                    //CHAPTER22MOD
                    pTenant->Select(FALSE, TRUE);
                    //End CHAPTER22MOD
                    }

                pTenant->Close(FALSE);
                pTenant->Release();
                }
            }

        DestroyWindow(m_hWndTenantList);
        m_hWndTenantList=NULL;
        m_fReopen=TRUE;

        if (NULL!=m_pmkFile)
            m_pmkFile->Release();

        m_pmkFile=NULL;

        DeleteInterfaceImp(m_pImpIOleItemContainer);
        }

    return;
    }

/*
 * CPage::Update
 *
 * Purpose:
 *  Forces a common on the page if it's open.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if there are any open objects on this page,
 *                  that is, we should remain open.
 */

BOOL CPage::Update(void)
    {
    BOOL            fOpen=FALSE;
    UINT            i;
    PCTenant        pTenant;
    HRESULT         hr;
    LPSTREAM        pIStream;
    TENANTLIST      tl;
    PTENANTINFO     pti;
    ULONG           cb;
    LPMALLOC        pIMalloc;

    //Walk the list of objects and update them all as well.
    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            fOpen |= pTenant->Update();
        }

    //Now write our own stream containing the tenant list.
    hr=m_pIStorage->CreateStream(SZSTREAMTENANTLIST, STGM_CREATE
        | STGM_WRITE| STGM_DIRECT | STGM_SHARE_EXCLUSIVE, 0, 0
        , &pIStream);

    if (FAILED(hr))
        return fOpen;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        tl.cTenants=m_cTenants;
        tl.dwIDNext=m_dwIDNext;

        pIStream->Write(&tl, sizeof(TENANTLIST), &cb);

        cb=m_cTenants*sizeof(TENANTINFO);
        pti=(PTENANTINFO)pIMalloc->Alloc(cb);

        if (NULL!=pti)
            {
            for (i=0; i < m_cTenants; i++)
                {
                TenantGet(i, &pTenant, FALSE);
                pTenant->GetInfo((pti+i));
                }

            pIStream->Write(pti, cb, &cb);
            pIMalloc->Free(pti);
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    //Now commit the whole mess and we're done
    if (NULL!=m_pIStorage)
        m_pIStorage->Commit(STGC_DEFAULT);

    return fOpen;
    }

/*
 * CPage::Destroy
 *
 * Purpose:
 *  Removes this page from the given storage.  The caller should
 *  eventually delete this Page object to free the storage.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE contianing this page on which to call
 *                  DestroyElement
 *
 * Return Value:
 *  None
 */

void CPage::Destroy(LPSTORAGE pIStorage)
    {
    if (NULL!=pIStorage)
        {
        OLECHAR szTemp[32];

        Close(FALSE);
        GetStorageName(szTemp);
        pIStorage->DestroyElement(szTemp);
        }

    return;
    }

/*
 * CPage::GetStorageName
 *
 * Parameters:
 *  pszName         LPOLESTR to a buffer in which to store the
 *                  storage name for this page.
 *
 * Return Value:
 *  UINT            Number of characters stored.
 */

UINT CPage::GetStorageName(LPOLESTR pszName)
    {
   #ifdef WIN32ANSI
    char        szTemp[32];
    UINT        cch;

    cch=wsprintf(szTemp, "Page %lu", m_dwID);
    MultiByteToWideChar(CP_ACP, 0, szTemp, -1, pszName, 32);
    return cch;
   #else
    return wsprintf(pszName, TEXT("Page %lu"), m_dwID);
   #endif
    }

/*
 * CPage::Draw
 *
 * Purpose:
 *  Draws the objects on this page to the given hDC.
 *
 * Parameters:
 *  hDC             HDC on which to draw.
 *  xOff, yOff      int offsets for the page.
 *  fNoColor        BOOL indicating black & white screen rendering.
 *  fPrinter        BOOL indicating hDC is on the printer.
 *
 * Return Value:
 *  None
 */

void CPage::Draw(HDC hDC, int xOff, int yOff, BOOL fNoColor
    , BOOL fPrinter)
    {
    int                 i;
    PCTenant            pTenant;
    HDC                 hIC=NULL;
    PCOMBINEDEVICE      pcd=NULL;
    DVTARGETDEVICE     *ptd=NULL;

    /*
     * If printing, tell the tenant to forget the borders. Otherwise
     * we leave xOff and yOff the same to account for scrolling.
     */
    if (fPrinter)
        {
        xOff=LOMETRIC_BORDER+m_pPG->m_xMarginLeft;
        yOff=-LOMETRIC_BORDER-m_pPG->m_yMarginTop;

        /*
         * Get device information.  If this fails, ptd is
         * NULL which is acceptable.
         */
        if (m_pPG->DevReadConfig(&pcd, &hIC))
            ptd=&(pcd->td);
        }

    for (i=(int)m_cTenants-1; i >=0; i--)
        {
        if (TenantGet(i, &pTenant, FALSE))
            {
            RECT        rc, rcWin;
            RECTL       rcl;

            //Paint this tenant only if visible.
            pTenant->RectGet(&rcl, TRUE);
            RECTFROMRECTL(rc, rcl);
            OffsetRect(&rc, -(int)m_pPG->m_xPos
                , -(int)m_pPG->m_yPos);
            GetClientRect(m_hWnd, &rcWin);

            if (IntersectRect(&rc, &rc, &rcWin))
                {
                pTenant->Draw(hDC, ptd, hIC, xOff, yOff
                    , fNoColor, fPrinter);
                }
            }
        }

    //Free whatever CPages::DevReadConfig returned.
    if (NULL!=pcd)
        {
        LPMALLOC    pIMalloc;

        if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
            {
            pIMalloc->Free(pcd);
            pIMalloc->Release();
            }
        }

    if (NULL!=hIC)
        DeleteDC(hIC);

    return;
    }

/*
 * CPage::TenantCreate
 *
 * Purpose:
 *  Creates a new tenant of a specific type.
 *
 * Parameters:
 *  tType           TENANTTYPE to create.
 *  pv              LPVOID providing information for the new
 *                  object creation.
 *  pFE             LPFORMATETC describing how we want this
 *                  rendered.
 *  ppo             PPATRONOBJECT with placement data.
 *  dwData          DWORD extra data to pass to the tenant.
 *
 * Return Value:
 *  None
 */

BOOL CPage::TenantCreate(TENANTTYPE tType, LPVOID pv
    , LPFORMATETC pFE, PPATRONOBJECT ppo, DWORD dwData)
    {
    PCTenant    pTenant;
    UINT        uRet;
    int         x, y;
    int         h, v;
    POINTL      ptl;
    SIZEL       szl;
    RECTL       rcl;
    RECT        rc;

    //New tenants go at top of the pile; zero index to TenantAdd.
    if (!TenantAdd(0, m_dwIDNext, &pTenant))
        return FALSE;

    uRet=pTenant->Create(tType, pv, pFE, &ptl, &szl, m_pIStorage
        , ppo, dwData);

    if (CREATE_FAILED==uRet)
        {
        //Reverse Create AND TenantAdd
        SendMessage(m_hWndTenantList, LB_DELETESTRING, 0, 0L);
        pTenant->Destroy(m_pIStorage);

        pTenant->Release();
        return FALSE;
        }

    m_dwIDNext++;
    m_cTenants++;

    //CHAPTER22MOD
    if (NULL!=m_pTenantCur)
        m_pTenantCur->Select(FALSE, TRUE);
    //End CHAPTER22MOD

    m_iTenantCur=0;             //First one in the list now.
    m_pTenantCur=pTenant;

    //Tell the tenant where it lives, default is (0,0) in print area
    x=LOMETRIC_BORDER+m_pPG->m_xMarginLeft;
    y=-LOMETRIC_BORDER-m_pPG->m_yMarginTop;

    h=x;
    v=y;

    if (CREATE_PLACEDOBJECT==uRet)
        {
        SetRect(&rc, 3*CXYHANDLE, 3*CXYHANDLE, 0, 0);
        RectConvertMappings(&rc, NULL, FALSE);

        //Make sure place point is on page, otherwise go to (0,0)
        if (((int)ptl.x > x)
            && ((int)ptl.x < x+(int)m_pPG->m_cx-rc.left))
            x=(int)ptl.x;

        //m_pPG->m_cy is absolute
        if (((int)ptl.y < y)
            && ((int)ptl.y > y-(int)m_pPG->m_cy-rc.top))
            y=(int)ptl.y;
        }

    //Bounds check size of the object and fit to page as necessary.
    if (x+(int)szl.cx > (int)(h+m_pPG->m_cx))
        szl.cx=h+m_pPG->m_cx-x;

    //Remember that szl we get from Create is absolute
    if (y-(int)szl.cy < (int)(v-m_pPG->m_cy))
        szl.cy=-(int)(v-m_pPG->m_cy-y);

    SETRECTL(rcl, x, y, x+szl.cx, y-szl.cy);
    m_pTenantCur->RectSet(&rcl, FALSE, TRUE);

    //Force a repaint on this new guy
    m_pTenantCur->Invalidate();
    UpdateWindow(m_hWnd);

    //CHAPTER22MOD
    m_pTenantCur->Select(TRUE, TRUE);
    //End CHAPTER22MOD

    //Make sure this new tenant knows about showing it's type.
    m_pTenantCur->ShowObjectType(m_pPG->m_fShowTypes);

    //New tenants must know the available monikers
    if (NULL!=m_pmkFile)
        {
        LPBC        pbc;
        LPMONIKER   pmkPage;
        TCHAR       szTemp[32];
        LPTSTR      pszFile;

       #ifdef WIN32ANSI
        OLECHAR     szW[32];

        GetStorageName(szW);
        WideCharToMultiByte(CP_ACP, 0, szW, -1, szTemp, 32
           , NULL, NULL);
       #else
        GetStorageName(szTemp);
       #endif
        CreateItemMoniker(TEXT("!"), szTemp, &pmkPage);

        //We can get the filename from our file moniker
        CreateBindCtx(0, &pbc);
       #ifdef WIN32ANSI
        LPOLESTR    pszTemp;
        m_pmkFile->GetDisplayName(pbc, NULL, &pszTemp);
        pszFile=(LPTSTR)CoTaskMemAlloc(512);
        WideCharToMultiByte(CP_ACP, 0, pszTemp, -1, pszFile
           , 512, NULL, NULL);
        CoTaskMemFree((void *)pszTemp);
       #else
        m_pmkFile->GetDisplayName(pbc, NULL, &pszFile);
       #endif
        pbc->Release();

        pTenant->NotifyOfRename(pszFile, m_pmkFile, pmkPage);
        pmkPage->Release();
        CoTaskMemFree((void *)pszFile);
        }

    //Activate new objects immediately and force a save on them
    if (TENANTTYPE_EMBEDDEDOBJECT==tType)
        {
        //CHAPTER22MOD
        m_pTenantCur->Activate(OLEIVERB_SHOW, NULL);
        //End CHAPTER22MOD
        m_pTenantCur->Update();
        }

    return TRUE;
    }

/*
 * CPage::TenantDestroy
 *
 * Purpose:
 *  Destroys the currently selected tenant on this page.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPage::TenantDestroy(void)
    {
    if (NULL==m_pTenantCur)
        {
        MessageBeep(0);
        return FALSE;
        }

    SendMessage(m_hWndTenantList, LB_DELETESTRING
        , m_iTenantCur, 0L);

    m_pTenantCur->Invalidate();
    m_pTenantCur->Destroy(m_pIStorage);
    m_pTenantCur->Release();
    m_pTenantCur=NULL;

    //Update counts, etc., and select the next tenant in the list.
    if (m_iTenantCur==m_cTenants-1)
        m_iTenantCur--;

    if (0==--m_cTenants)
        m_pTenantCur=NULL;
    else
        {
        TenantGet(m_iTenantCur, &m_pTenantCur, TRUE);

        //CHAPTER22MOD
        m_pTenantCur->Select(TRUE, TRUE);
        //End CHAPTER22MOD
        }

    UpdateWindow(m_hWnd);
    return TRUE;
    }

/*
 * CPage::TenantClip
 *
 * Purpose:
 *  Copies or cuts the currently selected tenant to the clipoard.
 *
 * Parameters:
 *  fCut            BOOL TRUE to cut the object, FALSE to copy.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPage::TenantClip(BOOL fCut)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet=FALSE;

    if (NULL==m_pTenantCur)
        return FALSE;

    /*
     * To perform a data transfer operation, we need to create a
     * data object with the selected object's data inside. To do
     * this we CoCreateInstance on CLSID_DataTransferObject
     * (Also implemented in this chapter), retrieve data from the
     * object we have, stuff that data into the transfer object,
     * then stick that object on the clipboard.
     *
     * Since we'll want an identical object at other times, like for
     * drag-drop, we use a private function to actually create it.
     */

    pIDataObject=TransferObjectCreate(NULL);

    if (NULL!=pIDataObject)
        {
        if (SUCCEEDED(OleSetClipboard(pIDataObject)))
            {
            if (fCut)
                TenantDestroy();

            fRet=TRUE;
            }

        pIDataObject->Release();
        }

    return fRet;
    }

/*
 * CPage::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected on this
 *  page for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU of the Edit menu.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPage::FQueryObjectSelected(HMENU hMenu)
    {
    HMENU       hMenuTemp;

    /*
     * This will only be called on WM_INITMENUPOPUP, we'll also
     * use this function to create the Verb menu for this object.
     */
    if (NULL!=m_pTenantCur)
        {
        m_pTenantCur->AddVerbMenu(hMenu, MENUPOS_OBJECT);
        return TRUE;
        }

    OleUIAddVerbMenu(NULL, NULL, hMenu, MENUPOS_OBJECT
        , IDM_VERBMIN, IDM_VERBMAX, FALSE, 0, &hMenuTemp);

    return FALSE;
    }

//CHAPTER22MOD
/*
 * CPage::ActivateObject
 *
 * Purpose:
 *  Executes a verb on the currently selected object.
 *
 * Parameters:
 *  iVerb           LONG of the selected verb.
 *  pMSG            LPMSG that caused the invocation of the verb.
 *
 * Return Value:
 *  None
 */

void CPage::ActivateObject(LONG iVerb, LPMSG pMSG)
    {
    if (NULL==m_pTenantCur)
        return;

    m_pTenantCur->Activate(iVerb, pMSG);
    return;
    }
//End CHAPTER22MOD

/*
 * CPage::ShowObjectTypes
 *
 * Purpose:
 *  Loops through all the tenants and tells each one to turn on or
 *  off the Show Objects features.
 *
 * Parameters:
 *  fShow           BOOL indicating to show the type or not.
 *
 * Return Value:
 *  None
 */

void CPage::ShowObjectTypes(BOOL fShow)
    {
    PCTenant    pTenant;
    UINT        i;

    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            pTenant->ShowObjectType(fShow);
        }

    return;
    }

/*
 * CPage::NotifyTenantsOfRename
 *
 * Purpose:
 *  Loops through all the tenants and informs each of the new
 *  document name.
 *
 * Parameters:
 *  pszFile         LPTSTR of the new filename.
 *  pmk             LPMONKIER for the new filename.
 *
 * Return Value:
 *  None
 */

void CPage::NotifyTenantsOfRename(LPTSTR pszFile, LPMONIKER pmk)
    {
    PCTenant    pTenant;
    UINT        i;
    LPMONIKER   pmkPage;
    LPMONIKER   pmkAll;
    TCHAR       szTemp[32];

    //Save the file moniker
    ReleaseInterface(m_pmkFile);
    m_pmkFile=pmk;
    m_pmkFile->AddRef();

    //Create a page moniker to send to the tenants.
   #ifdef WIN32ANSI
    OLECHAR     szW[32];

    GetStorageName(szW);
    WideCharToMultiByte(CP_ACP, 0, szW, -1, szTemp, 32
       , NULL, NULL);
   #else
    GetStorageName(szTemp);
   #endif
    CreateItemMoniker(TEXT("!"), szTemp, &pmkPage);

    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            pTenant->NotifyOfRename(pszFile, pmk, pmkPage);
        }

    /*
     * Register a File!Page!"\" wildcard moniker as well.
     * Note that the page is already marked as running
     * with the document's wildcard moniker.
     */
    CreateItemMoniker(TEXT("!"), TEXT("\\"), &pmkAll);

    if (NULL!=pmkAll)
        {
        LPMONIKER   pmkWild=NULL;
        LPMONIKER   pmkTemp=NULL;

        INOLE_RevokeAsRunning(&m_dwRegROTWild);
        pmk->ComposeWith(pmkPage, FALSE, &pmkTemp);

        if (NULL!=pmkTemp)
            {
            pmkTemp->ComposeWith(pmkAll, FALSE, &pmkWild);
            pmkTemp->Release();
            }

        if (NULL!=pmkWild)
            {
            INOLE_RegisterAsRunning(this, pmk, 0
                , &m_dwRegROTWild);
            pmkWild->Release();
            }

        pmkAll->Release();
        }

    //If anything held onto this, they AddRef'd
    pmkPage->Release();
    return;
    }

/*
 * CPage::ConvertObject
 *
 * Purpose:
 *  Invokes and handles the results of the Convert dialog
 *
 * Parameters:
 *  hWndFrame       HWND to use as the parent of the dialog.
 *  fNoServer       BOOL indicating if this was called because
 *                  ActivateObject failed.
 *
 * Return Value:
 *  None
 */

BOOL CPage::ConvertObject(HWND hWndFrame, BOOL fNoServer)
    {
    HRESULT         hr;
    OLEUICONVERT    ct;
    TENANTTYPE      tType;
    TENANTINFO      ti;
    UINT            uRet;
    HCURSOR         hCur;
    BOOL            fActivate=fNoServer;
    SIZEL           szl;

    if (NULL==m_pTenantCur)
        return FALSE;

    tType=m_pTenantCur->TypeGet();

    if (TENANTTYPE_STATIC==tType)
        {
        MessageBeep(0);
        return FALSE;
        }

    //Get object information we may want.
    m_pTenantCur->GetInfo(&ti);

    //Fill the structure.
    memset(&ct, 0, sizeof(ct));
    ct.cbStruct=sizeof(OLEUICONVERT);
    ct.hWndOwner=hWndFrame;
    ct.fIsLinkedObject=(TENANTTYPE_LINKEDOBJECT==tType);
    ct.dvAspect=ti.fe.dwAspect;

    m_pTenantCur->ObjectClassFormatAndIcon(&ct.clsid, &ct.wFormat
        , &ct.lpszUserType, &ct.hMetaPict, &ct.lpszDefLabel);

    uRet=OleUIConvert(&ct);

    if (OLEUI_OK==uRet)
        {
        //Potentially a long operation.
        hCur=SetCursor(LoadCursor(NULL, IDC_WAIT));

        //Prevent multiple repaints.
        m_pTenantCur->EnableRepaint(FALSE);

        //First, let's bother with the iconic aspect switch.
        if ((DVASPECT_ICON==ct.dvAspect && ct.fObjectsIconChanged)
            || ct.dvAspect!=ti.fe.dwAspect)
            {
            HGLOBAL     hMem=NULL;

            //Only pass non-NULL handle for icon aspects.
            if (DVASPECT_ICON==ct.dvAspect)
                hMem=ct.hMetaPict;

            m_pPG->m_fDirty=m_pTenantCur->SwitchOrUpdateAspect(hMem
                , FALSE);
            }

        //Now change types around.
        if ((CF_SELECTCONVERTTO & ct.dwFlags)
            && ct.clsid!=ct.clsidNew)
            {
            LPSTORAGE   pIStorage;

            /*
             * User selected convert, so:
             *  1.  Unload the object (back to passive state)
             *  2.  Call INOLE_DoConvert, which calls WriteClassStg,
             *      WriteFmtUserTypeStg, and SetConvertStg.
             *  3.  Reload the object and force an update.
             */

            //This should be the only close necessary.
            m_pTenantCur->StorageGet(&pIStorage);
            m_pTenantCur->Close(TRUE);

            hr=INOLE_DoConvert(pIStorage, ct.clsidNew);

            //Need to commit the new type and format
            pIStorage->Commit(STGC_DEFAULT);
            pIStorage->Release();

            if (SUCCEEDED(hr))
                {
                LPUNKNOWN   pObj;
                LPOLEOBJECT pIOleObject;

                //Reload and update.
                m_pTenantCur->Load(m_pIStorage, &ti);

                m_pTenantCur->ObjectGet(&pObj);
                pObj->QueryInterface(IID_IOleObject
                    , (PPVOID)&pIOleObject);
                pIOleObject->Update();
                pIOleObject->Release();
                pObj->Release();
                }

            m_pPG->m_fDirty=TRUE;
            }

        if (CF_SELECTACTIVATEAS & ct.dwFlags)
            {
            /*
             * User selected Activate As, so:
             *  1.  Add the TreatAs entry in the registry
             *      through CoTreatAsClass
             *  2.  Unload all objects of the old CLSID that you
             *      have loaded.
             *  3.  Reload objects as desired
             *  4.  Activate the current object.
             */

            hr=CoTreatAsClass(ct.clsid, ct.clsidNew);

            if (SUCCEEDED(hr))
                {
                PCTenant    pTenant;
                UINT        i;

                for (i=0; i < m_cTenants; i++)
                    {
                    if (TenantGet(i, &pTenant, FALSE))
                        {
                        pTenant->GetInfo(&ti);
                        pTenant->Close(FALSE);
                        pTenant->Load(m_pIStorage, &ti);
                        }
                    }

                fActivate=TRUE;
                }
            }

        //These two steps insure the object knows of the size.
        m_pTenantCur->SizeGet(&szl, FALSE);
        m_pTenantCur->SizeSet(&szl, FALSE, TRUE);

        m_pTenantCur->EnableRepaint(TRUE);
        m_pTenantCur->Repaint();

        if (fActivate)
            //CHAPTER22MOD
            m_pTenantCur->Activate(OLEIVERB_SHOW, NULL);
            //End CHAPTER22MOD

        SetCursor(hCur);
        }

    CoTaskMemFree((void*)ct.lpszUserType);
    INOLE_MetafilePictIconFree(ct.hMetaPict);

    return TRUE;
    }

/*
 * CPages::FQueryLinksInPage
 *
 * Purpose:
 *  Pass through to current page to see if there are any linked
 *  objects.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPage::FQueryLinksInPage()
    {
    PCTenant    pTenant;
    UINT        i;
    BOOL        fRet=FALSE;

    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            fRet |= (pTenant->TypeGet()==TENANTTYPE_LINKEDOBJECT);
        }

    return fRet;
    }

//CHAPTER22MOD
/*
 * CPage::ScrolledWindow
 *
 * Purpose:
 *  Instructs the page to call CTenant::UpdateInPlaceObjectRects
 *  for the current tenant when the window is scrolled.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPage::ScrolledWindow(void)
    {
    UINT        i;
    PCTenant    pTenant;

    /*
     * Tell all tenants to update the position of in-place objects.
     * If you do not support inside-out, only the selected object
     * need be notified.
     */
    for (i=0; i < m_cTenants; i++)
        {
        if (!TenantGet(i, &pTenant, FALSE))
            continue;

        pTenant->UpdateInPlaceObjectRects(NULL, TRUE);
        }

    return;
    }

/*
 * CPage::SwitchActiveTenant
 *
 * Purpose:
 *  Changes the activation of in-place objects from the current
 *  one known to the page to the new one passed to this function.
 *  This is called from IOleInPlaceSite::OnUIDeactivate.
 *
 * Parameters:
 *  pTenant         PCTenant that is becoming active.
 *
 * Return Value:
 *  None
 */

void CPage::SwitchActiveTenant(PCTenant pTenant)
    {
    BOOL        fSelect;

    /*
     * If we're UI activating the selected tenant, don't
     * bother changing selection--just so activation.
     */
    fSelect=(pTenant!=m_pTenantCur);

    /*
     * The first time we UI Activate we're not switching
     * anything so avoid the whole sequence.
     */
    if (m_fFirstUIActivate)
        {
        m_fFirstUIActivate=FALSE;
        return;
        }

    if (NULL!=m_pTenantCur && fSelect)
        m_pTenantCur->Select(FALSE, TRUE);

    m_pTenantCur=pTenant;

    //Select the new tenant, but don't activate it again
    if (NULL!=m_pTenantCur)
        {
        UINT        i;

        if (fSelect)
            m_pTenantCur->Select(TRUE, FALSE);

        //Find the new tenant in our list and move to the top
        for (i=0; i < m_cTenants; i++)
            {
            PCTenant        pTenList;

            if (TenantGet(i, &pTenList, FALSE))
                {
                if (pTenList==m_pTenantCur)
                    {
                    HWND        hWndObj;

                    m_iTenantCur=0;

                    //Remove the tenant and add to the top again.
                    SendMessage(m_hWndTenantList, LB_DELETESTRING
                        , i, 0L);
                    SendMessage(m_hWndTenantList, LB_INSERTSTRING
                        , 0, (LONG)pTenant);

                    hWndObj=pTenant->ObjectWindow();

                    if (NULL!=hWndObj)
                        {
                        SetWindowPos(hWndObj, HWND_TOP, 0, 0, 0, 0
                            , SWP_NOMOVE | SWP_NOSIZE
                            | SWP_NOACTIVATE);
                        }
                    break;
                    }
                }
            }
        }

    return;
    }
//End CHAPTER22MOD

/*
 * CPage::TenantGet
 * (Protected)
 *
 * Purpose:
 *  Returns a tenant of a given index returning a BOOL so it's
 *  simple to use this function inside if statements.
 *
 * Parameters:
 *  iTenant         UINT tenant to retrieve 0 based.
 *  ppTenant        PCPage * in which to return the tenant
 *                  pointer
 *  fOpen           BOOL indicating if we should open this
 *                  tenant as well.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPage::TenantGet(UINT iTenant, PCTenant *ppTenant
    , BOOL fOpen)
    {
    if (NULL==ppTenant)
        return FALSE;

    if (LB_ERR!=SendMessage(m_hWndTenantList, LB_GETTEXT
        , iTenant, (LONG)ppTenant))
        {
        if (fOpen)
            (*ppTenant)->Open(m_pIStorage);

        return TRUE;
        }

    return FALSE;
    }

/*
 * CPage::TenantGetFromID
 * (Protected)
 *
 * Purpose:
 *  Finds a tenant pointer from an ID for use from
 *  IOleItemContainer::GetObject
 *
 * Parameters:
 *  dwID            DWORD identifier of the tenant to find.
 *  ppTenant        PCTenant * in which to return the tenant
 *                  pointer
 *  fOpen           BOOL indicating if we should open this tenant as
 *                  well.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPage::TenantGetFromID(DWORD dwID, PCTenant *ppTenant
    , BOOL fOpen)
    {
    UINT        i;
    PCTenant    pTenant;

    if (NULL==ppTenant)
        return FALSE;

    for (i=0; i < m_cTenants; i++)
        {
        if (!TenantGet(i, &pTenant, FALSE))
            continue;

        if (pTenant->GetID()==dwID)
            {
            if (fOpen)
                pTenant->Open(m_pIStorage);

            *ppTenant=pTenant;
            return TRUE;
            }
        }

    return FALSE;
    }

/*
 * CPage::TenantAdd
 * (Protected)
 *
 * Purpose:
 *  Creates a new tenant initialized to the given values.  The new
 *  tenants's storage is created if it does not already exist.  If
 *  fOpenStorage is set the the tenants's storage is opened and left
 *  opened.
 *
 * Parameters:
 *  iTenant         UINT Location at which to insert tenant; new
 *                  tenant is inserted after this position.  NOVALUE
 *                  for the end.
 *  dwID            DWORD ID for this tenant.
 *  ppNew           PCTenant * in which to store the new tenant.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPage::TenantAdd(UINT iTenant, DWORD dwID
    , PCTenant *ppNew)
    {
    PCTenant    pTenant;
    LRESULT     lr;

    if (NULL!=ppNew)
        *ppNew=NULL;

    pTenant=new CTenant(dwID, m_hWnd, m_pPG);

    if (NULL==pTenant)
        return FALSE;

    //The constructor doesn't AddRef, so we need to.
    pTenant->AddRef();

    //Now try to add to the listbox.
    lr=SendMessage(m_hWndTenantList, LB_INSERTSTRING, iTenant
        , (LONG)pTenant);

    if (lr < 0)
        {
        pTenant->Release();
        return FALSE;
        }

    *ppNew=pTenant;
    return TRUE;
    }

/*
 * CPage::TransferObjectCreate
 * (Protected)
 *
 * Purpose:
 *  Creates a DataTransferObject and stuff the current selection's
 *  data into it.
 *
 * Parameters:
 *  pptl            PPOINTL containing the pick point in device
 *                  units applicable only to drag-drop; since
 *                  drag-drop is inherently mouse oriented, we use
 *                  device units for the point.  Ignored if NULL.
 *
 * Return Value:
 *  LPDATAOBJECT    Pointer to the object created, NULL on failure
 */

LPDATAOBJECT CPage::TransferObjectCreate(PPOINTL pptl)
    {
    LPDATAOBJECT    pIDataObject;
    LPDATAOBJECT    pIDataT;
    PPATRONOBJECT   ppo;
    RECTL           rcl;
    LPUNKNOWN       pObj;
    FORMATETC       fe;
    STGMEDIUM       stm;
    HRESULT         hr;
    HGLOBAL         hMem;

    m_pTenantCur->ObjectGet(&pObj);

    hr=CoCreateInstance(CLSID_DataTransferObject, NULL
        , CLSCTX_INPROC_SERVER, IID_IDataObject
        , (PPVOID)&pIDataObject);

    if (FAILED(hr))
        return NULL;

    //Go get the data we should hold on to.
    hr=pObj->QueryInterface(IID_IDataObject, (PPVOID)&pIDataT);

    if (FAILED(hr))
        {
        pIDataObject->Release();
        pObj->Release();
        return NULL;
        }

    //Copy from known obj into transfer obj.  Ordering is important!

    //Generate placeable object structure
    stm.tymed=TYMED_HGLOBAL;
    stm.pUnkForRelease=NULL;
    stm.hGlobal=GlobalAlloc(GHND, sizeof(PATRONOBJECT));

    if (NULL==stm.hGlobal)
        {
        pIDataObject->Release();
        pObj->Release();
        return NULL;
        }

    ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);

    m_pTenantCur->SizeGet(&ppo->szl, FALSE);
    ppo->szl.cy=-ppo->szl.cy; //Negate to make absolute size

    m_pTenantCur->RectGet(&rcl, FALSE);
    ppo->ptl.x=rcl.left;
    ppo->ptl.y=rcl.top;

    if (NULL==pptl)
        {
        ppo->ptlPick.x=0;
        ppo->ptlPick.y=0;
        }
    else
        ppo->ptlPick=*pptl;

    m_pTenantCur->FormatEtcGet(&ppo->fe, FALSE);

    //If this is a linked object, just copy a presentation
    if (TENANTTYPE_LINKEDOBJECT==m_pTenantCur->TypeGet())
        m_pTenantCur->FormatEtcGet(&ppo->fe, TRUE);

    SETDefFormatEtc(fe, m_pPG->m_cf, TYMED_HGLOBAL);
    pIDataObject->SetData(&fe, &stm, TRUE);

    /*
     * Here now we have to include CFSTR_EMBEDDEDOBJECT and
     * CFSTR_OBJECTDESCRIPTOR when what we have selected is, in
     * fact, a compound document object.  We'll just ask the tenant
     * to set these in pIDataObject since it knows what the object.
     * If we copy embedded object data, make sure the PATRONOBJECT
     * structure has the right format in it as well.
     */
    m_pTenantCur->CopyEmbeddedObject(pIDataObject, &ppo->fe, pptl);
    hMem=stm.hGlobal;

    //Copy the actual presentation.
    m_pTenantCur->FormatEtcGet(&fe, TRUE);
    pIDataT->GetData(&fe, &stm);
    pIDataObject->SetData(&fe, &stm, TRUE);

    //Copy a link to this tenant if it's embedded
    m_pTenantCur->CopyLinkedObject(pIDataObject, &ppo->fe, pptl);
    GlobalUnlock(hMem); //ppo

    pIDataT->Release();
    pObj->Release();
    return pIDataObject;    //Caller now responsible
    }


PAGEMOUS.CPP

/*
 * PAGEMOUS.CPP
 * Patron Chapter 22
 *
 * Implementation of mouse-related member functions of CPage.
 * The remainder is in PAGE.CPP.  This separate file keeps this
 * grungy hit-testing/drawing code out of our way.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

//Lookups into the array using g_rgHTCode[x+y*3] in PAGEMOUS.CPP
#define YTOP            0
#define YMID            1
#define YBOT            2
#define XLEFT           0
#define XMID            1
#define XRIGHT          2

//Values to restrict sizing in CPage::OnMouseMove
#define SIZINGTOP       0x0001
#define SIZINGBOTTOM    0x0002
#define SIZINGLEFT      0x0004
#define SIZINGRIGHT     0x0008

//This array is for hit-testing lookups
static UINT g_rgHTCode[9]={HTTOPLEFT, HTTOP, HTTOPRIGHT
    , HTLEFT, HTCLIENT, HTRIGHT, HTBOTTOMLEFT, HTBOTTOM
    , HTBOTTOMRIGHT};

//This is for restricting tracking based on the hit-test
static UINT g_rguSizingFlags[9]={SIZINGTOP | SIZINGLEFT, SIZINGTOP
    , SIZINGTOP | SIZINGRIGHT, SIZINGLEFT, 0, SIZINGRIGHT
    , SIZINGBOTTOM | SIZINGLEFT, SIZINGBOTTOM
    , SIZINGBOTTOM | SIZINGRIGHT};

/*
 * CPage::OnRightDown
 *
 * Purpose:
 *  Called when the user clicks with the right button on this
 *  page.  If there is an object here, determined by the last
 *  hit-test code, the we'll make a popup-menu for it.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if the action changed the object.
 */

BOOL CPage::OnRightDown(UINT uKeys, UINT x, UINT y)
    {
    HMENU       hMenu;
    HMENU       hMenuRes;
    HINSTANCE   hInst;
    HWND        hWndFrame, hWndT;
    POINT       pt;
    UINT        i, cItems;

    //Select the tenant under the mouse, if there is one.
    if (!SelectTenantAtPoint(x, y))
        return FALSE;

    /*
     * Get the top-level window to which menu command will go.  This
     * will be whatever parent doesn't have a parent itself...
     */
    hWndT=GetParent(m_hWnd);

    while (NULL!=hWndT)
        {
        hWndFrame=hWndT;
        hWndT=GetParent(hWndT);
        }

    /*
     * Build a popup menu for this object with Cut, Copy, Delete,
     * and object verbs.
     */
    hInst=GETWINDOWINSTANCE(m_hWnd);    //Macro in BOOK1632.H
    hMenuRes=LoadMenu(hInst, MAKEINTRESOURCE(IDR_RIGHTPOPUPMENU));

    if (NULL==hMenuRes)
        return FALSE;

    hMenu=CreatePopupMenu();
    cItems=GetMenuItemCount(hMenuRes);

    for (i=0; i < cItems; i++)
        {
        TCHAR       szTemp[80];
        int         id, uFlags;

        GetMenuString(hMenuRes, i, szTemp, sizeof(szTemp)
            , MF_BYPOSITION);
        id=GetMenuItemID(hMenuRes, i);

        uFlags=(0==id) ? MF_SEPARATOR : MF_STRING | MF_ENABLED;
        AppendMenu(hMenu, uFlags, id, szTemp);
        }

    DestroyMenu(hMenuRes);

    //Munge the Object menu item
    m_pTenantCur->AddVerbMenu(hMenu, MENUPOS_OBJECTONPOPUP);

    //Enable or disable the Links item.
    i=FQueryLinksInPage() ? MF_ENABLED : MF_DISABLED | MF_GRAYED;
    EnableMenuItem(hMenu, IDM_EDITLINKS, i | MF_BYCOMMAND);

    SETPOINT(pt, x, y);
    ClientToScreen(m_hWnd, &pt);

    TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON
        , pt.x, pt.y, 0, hWndFrame, NULL);

    DestroyMenu(hMenu);
    return FALSE;
    }

/*
 * CPage::SelectTenantAtPoint
 *
 * Purpose:
 *  Selects whatever tenant is at the point (x,y) if there is one,
 *  deselecting the previously selected tenant.
 *
 * Parameters:
 *  x, y            UINT coordinates of the mouse.
 *
 * Return Value:
 *  BOOL            TRUE if there is a tenant here, FALSE otherwise.
 */

BOOL CPage::SelectTenantAtPoint(UINT x, UINT y)
    {
    UINT            iTenant;
    PCTenant        pTenant;
    PCDocument      pDoc;

    iTenant=TenantFromPoint(x, y, &pTenant);

    //Make the document window active in any case
    pDoc=(PCDocument)SendMessage(GetParent(m_hWnd), DOCM_PDOCUMENT
        , 0, 0L);

    if (NULL!=pDoc)
        BringWindowToTop(pDoc->Window());

    //CHAPTER22MOD
    if (NULL==pTenant)
        {
        //Deactivate any in-place object, if one exists.
        if (NULL!=m_pTenantCur)
            m_pTenantCur->DeactivateInPlaceObject(FALSE);

        return FALSE;
        }

    //The m_pTenantCur->Select(FALSE, TRUE) deactivates as well.
    //End CHAPTER22MOD

    //If this one is already current, we might be now sizing.
    if (pTenant==m_pTenantCur)
        return TRUE;

    //Deselect the current tenant
    if (NULL!=m_pTenantCur)
        //CHAPTER22MOD
        m_pTenantCur->Select(FALSE, TRUE);
        //End CHAPTER22MOD

    //Move this tenant to the top of the list
    m_iTenantCur=0;

    SendMessage(m_hWndTenantList, LB_DELETESTRING, iTenant, 0L);
    SendMessage(m_hWndTenantList, LB_INSERTSTRING, 0
        , (LONG)pTenant);

    //Select and repaint the new tenant to show it up front
    m_pTenantCur=pTenant;

    m_pTenantCur->Repaint();
    //CHAPTER22MOD
    m_pTenantCur->Select(TRUE, TRUE);
    //End CHAPTER22MOD

    return TRUE;
    }

/*
 * CPage::OnLeftDown
 *
 * Purpose:
 *  Called when the user clicks with the left button on this page.
 *  We find the object under that position that is visibly on top
 *  (always the first one under this location in the page list since
 *  we paint in reverse order) and select it.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if the action changed the object.
 */

BOOL CPage::OnLeftDown(UINT uKeys, UINT x, UINT y)
    {
    /*
     * If the mouse is in a position to start dragging,
     * start the timer as with sizing below.
     */
    if (HTCAPTION==m_uHTCode)
        {
        m_fDragPending=TRUE;

        //Save down point and start timer.
        m_ptDown.x=x;
        m_ptDown.y=y;

        m_uKeysDown=uKeys;

        m_fTimer=TRUE;
        SetTimer(m_hWnd, IDTIMER_DEBOUNCE, m_cDelay, NULL);
        return FALSE;
        }

    /*
     * If the mouse is in a position to start sizing, start
     * the debounce timer and note the condition.  The sizing
     * will start in OnTimer or OnMouseMove.  This will always
     * happen on the currently selected tenant, and m_uHTCode is
     * set in OnNCHitTest below.
     */
    if (HTNOWHERE!=m_uHTCode && HTCLIENT!=m_uHTCode)
        {
        m_fSizePending=TRUE;

        //Save down point and start timer.
        m_ptDown.x=x;
        m_ptDown.y=y;

        m_fTimer=TRUE;
        SetTimer(m_hWnd, IDTIMER_DEBOUNCE, m_cDelay, NULL);
        return FALSE;
        }

    SelectTenantAtPoint(x, y);
    return FALSE;
    }

/*
 * CPage::OnLeftUp
 *
 * Purpose:
 *  Called when the user clicks up with the left button on this
 *  page. We stop tracking on this message, if necessary, and
 *  resize the object.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if this action changed the object.
 */

BOOL CPage::OnLeftUp(UINT uKeys, UINT x, UINT y)
    {
    RECT    rc, rcT;

    if (m_fSizePending || m_fDragPending)
        {
        m_fSizePending=FALSE;
        m_fDragPending=FALSE;

        if (m_fTimer)
            {
            KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
            m_fTimer=FALSE;
            }

        return FALSE;
        }

    if (!m_fTracking)
        return FALSE;

    //Remove the dotted rectangle.
    RECTFROMRECTL(rc, m_rcl)
    DrawFocusRect(m_hDC, &rc);
    ReleaseDC(m_hWnd, m_hDC);

    ReleaseCapture();
    m_fTracking=FALSE;

    //If the original and new rects are the same, nothing happened.
    RECTFROMRECTL(rcT, m_rclOrg);

    if (EqualRect(&rc, &rcT))
        return FALSE;

    RECTFROMRECTL(rcT, m_rclOrg);
    InvalidateRect(m_hWnd, &rcT, TRUE);

    //Invalidate on the screen before accounting for scrolling
    InvalidateRect(m_hWnd, &rc, TRUE);

    //Factor in scrolling and tell the tenant where it now stands.
    OffsetRect(&rc, (int)m_pPG->m_xPos, (int)m_pPG->m_yPos);
    RECTLFROMRECT(m_rcl, rc);
    m_pTenantCur->RectSet(&m_rcl, TRUE, TRUE);

    UpdateWindow(m_hWnd);
    return TRUE;
    }

/*
 * CPage::OnLeftDoubleClick
 *
 * Purpose:
 *  Called when the user double-clicks with the left button on this
 *  page.  We find the object under that position that is visibly on
 *  top (always the first one under this location in the page list
 *  since we paint in reverse order) and activate it.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if the action changed the object.
 */

BOOL CPage::OnLeftDoubleClick(UINT uKeys, UINT x, UINT y)
    {
    /*
     * The current tenant is the only one that can be activated, so
     * we just have to make sure the mouse is there.  For that we
     * can use the last hit-test code we saw since it's updated on
     * every mouse move.
     */

    //CHAPTER22MOD
    if (HTNOWHERE!=m_uHTCode)
        {
        MSG     msg;
        DWORD   dw;

        //Include a message for in-place objects.
        msg.hwnd=NULL;
        msg.message=WM_LBUTTONDBLCLK;
        msg.wParam=(WPARAM)uKeys;
        msg.lParam=MAKELONG(x, y);
        msg.time=GetMessageTime();

        dw=GetMessagePos();
        SETPOINT(msg.pt, LOWORD(dw), HIWORD(dw));

        return m_pTenantCur->Activate(OLEIVERB_PRIMARY, &msg);
        }

    //If we've been waiting to add UI, now's the time to do all of it.
    if (m_pPG->m_fAddUI)
        {
        g_pFR->ShowUIAndTools(TRUE, TRUE);
        m_pPG->m_fAddUI=FALSE;
        }
    //End CHAPTER22MOD

    return FALSE;
    }

/*
 * CPage::OnMouseMove
 *
 * Purpose:
 *  Processes WM_MOUSEMOVE on a page so we can handle tracking
 *  resize of a tenant.
 *
 * Parameters:
 *  x, y            int device coordinates to check.
 *
 * Return Value:
 *  None
 */

void CPage::OnMouseMove(UINT uKeys, int x, int y)
    {
    RECT        rc, rcO, rcB;
    int         cxy;

    if (m_fSizePending || m_fDragPending)
        {
        int     dx, dy;

        dx=(x > m_ptDown.x) ? (x-m_ptDown.x) : (m_ptDown.x-x);
        dy=(y > m_ptDown.y) ? (y-m_ptDown.y) : (m_ptDown.y-y);

        /*
         * Has the mouse moved outside the debounce distance?  If
         * so, we can start sizing.  Note that this happens
         * regardless of the timer state.
         */
        if (dx > m_cxyDist || dy > m_cxyDist)
            {
            POINT       pt;
            BOOL        fSize=m_fSizePending;
            BOOL        fDrag=m_fDragPending;

            m_fSizePending=FALSE;
            m_fDragPending=FALSE;

            if (m_fTimer)
                {
                KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
                m_fTimer=FALSE;
                }

            if (fDrag)
                {
                //Set dirty flag if drag & drop changed things.
                m_pPG->m_fDirty |= DragDrop(m_uKeysDown, x, y);
                return;
                }

            if (fSize)
                StartSizeTracking();

            /*
             * Since we might have moved out of the sizing handle
             * in order to start the operation, we need to set the
             * m_uSizingFlags field based on the original down point
             * for subsequent mouse moves to function properly.
             * Note that OnNCHitTest expects screen coordinates.
             */
            SETPOINT(pt, m_ptDown.x, m_ptDown.y);
            ClientToScreen(m_hWnd, &pt);
            OnNCHitTest(pt.x, pt.y);
            OnSetCursor(m_uHTCode);
            return;
            }
        }

    if (!m_fTracking)
        return;

    //Get rid of the old rectangle.
    RECTFROMRECTL(rc, m_rcl)
    DrawFocusRect(m_hDC, &rc);

    /*
     * Calculate the new.  The flags in m_uSizingFlags tell us what
     * to change.  We limit the object by the page margins and a
     * minimum size of 3*CXYHANDLE in either dimension.
     */
    cxy=3*CXYHANDLE;

    RECTFROMRECTL(rcO, m_rclOrg);
    RECTFROMRECTL(rcB, m_rclBounds);

    if (m_uSizingFlags & SIZINGTOP)
        {
        if (y >= rcO.bottom-cxy)
            y=rcO.bottom-cxy;

        if (y <= rcB.top)           //Limit to top of page.
            y=rcB.top;

        m_rcl.top=y;
        }

    if (m_uSizingFlags & SIZINGBOTTOM)
        {
        if (y <= rcO.top+cxy)
            y=rcO.top+cxy;

        if (y >= rcB.bottom)         //Limit to bottom of page.
            y=rcB.bottom;

        m_rcl.bottom=y;
        }

    if (m_uSizingFlags & SIZINGLEFT)
        {
        if (x >= rcO.right-cxy)
            x=rcO.right-cxy;

        if (x <= rcB.left)           //Limit to left of page.
            x=rcB.left;

        m_rcl.left=x;
        }

    if (m_uSizingFlags & SIZINGRIGHT)
        {
        if (x <= rcO.left+cxy)
            x=rcO.left+cxy;

        if (x >= rcB.right)          //Limit to right of page.
            x=rcB.right;

        m_rcl.right=x;
        }

    //Draw the new
    RECTFROMRECTL(rc, m_rcl)
    DrawFocusRect(m_hDC, &rc);

    return;
    }

/*
 * CPage::OnTimer
 *
 * Purpose:
 *  Processes WM_TIMER messages to a page used to perform mouse
 *  debouncing.
 *
 * Parameters:
 *  uID             UINT timer ID.
 *
 * Return Value:
 *  None
 */

void CPage::OnTimer(UINT uID)
    {
    if (m_fSizePending || m_fDragPending)
        {
        BOOL        fSize=m_fSizePending;
        BOOL        fDrag=m_fDragPending;

        /*
         * Having this function called means the delay requirement
         * is satisfied.  Start tracking for sizing or dragging.
         */

        m_fSizePending=FALSE;
        m_fDragPending=FALSE;

        KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
        m_fTimer=FALSE;

        if (fDrag)
            {
            POINT       pt;

            GetCursorPos(&pt);
            m_pPG->m_fDirty |= DragDrop(m_uKeysDown
                , m_ptDown.x, m_ptDown.y);
            return;
            }

        if (fSize)
            StartSizeTracking();
        }

    return;
    }

/*
 * CPage::StartSizeTracking
 *
 * Purpose:
 *  Begins sizing of a tenant when mouse debounce conditions are
 *  met.
 *
 * Parameters:
 *  uID             UINT timer ID.
 *
 * Return Value:
 *  None
 */

void CPage::StartSizeTracking(void)
    {
    RECT        rc;

    m_pTenantCur->RectGet(&m_rcl, TRUE);
    SetCapture(m_hWnd);
    m_fTracking=TRUE;

    m_hDC=GetDC(m_hWnd);

    //Place the rectangle exactly where it is on the screen.
    RECTFROMRECTL(rc, m_rcl)
    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);
    RECTLFROMRECT(m_rcl, rc);
    m_rclOrg=m_rcl;

    DrawFocusRect(m_hDC, &rc);

    m_pPG->CalcBoundingRect(&rc, TRUE);
    RECTLFROMRECT(m_rclBounds, rc);
    return;
    }

/*
 * CPage::OnNCHitTest
 *
 * Purpose:
 *  Processes WM_NCHITTEST on a page so we can check for hits on the
 *  handles of the selected object for resizing.  We only save
 *  information for ourselves and do not interfere with normal
 *  hit-testing.
 *
 * Parameters:
 *  x, y            UINT device coordinates to check.
 *
 * Return Value:
 *  None
 */

void CPage::OnNCHitTest(UINT x, UINT y)
    {
    RECT        rc;
    RECTL       rcl;
    int         iMid1, iMid2;
    int         xHit, yHit;
    POINT       pt;
    int         x0, y0;

    /*
     * Ignore this message if it occurs during tracking to adjust
     * for the behavior of oddball mouse drivers.
     */
    if (m_fSizePending || m_fTracking)
        return;

    //Default: don't start sizing on a click, don't hit an object.
    m_uSizingFlags=0;
    m_uHTCode=HTNOWHERE;

    if (NULL==m_pTenantCur)
        return;

    //Convert device points to our coordinates
    m_pTenantCur->RectGet(&rcl, FALSE);
    RECTFROMRECTL(rc, rcl);
    RectConvertMappings(&rc, NULL, TRUE);
    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

    SETPOINT(pt, x, y);
    ScreenToClient(m_hWnd, &pt);
    x0=pt.x;
    y0=pt.y;

    if (x0 < rc.left || x0 > rc.right)
        return;

    if (y0 < rc.top || y0 > rc.bottom)
        return;

    //It's at least in the object.
    m_uHTCode=HTCLIENT;

    //Check for hits in horizontal regions
    xHit=NOVALUE;
    iMid1=rc.left+((rc.right-rc.left-CXYHANDLE) >> 1);
    iMid2=rc.left+((rc.right-rc.left+CXYHANDLE) >> 1);

    if (x0 >= rc.left && x0 <= rc.left+CXYHANDLE)
        xHit=XLEFT;
    else if (x0 >= iMid1 && x0 <= iMid2)
        xHit=XMID;
    else if (x0 >= rc.right-CXYHANDLE && x0 <= rc.right)
        xHit=XRIGHT;

    //Don't exit yet if we didn't hit a handle--might hit a y edge.

    //Check for hits in vertical regions
    yHit=NOVALUE;
    iMid1=rc.top+((rc.bottom-rc.top-CXYHANDLE) >> 1);
    iMid2=rc.top+((rc.bottom-rc.top+CXYHANDLE) >> 1);

    if (y0 >= rc.top && y0 <= rc.top+CXYHANDLE)
        yHit=YTOP;
    else if (y0 >= iMid1 && y0 <= iMid2)
        yHit=YMID;
    else if (y0 >= rc.bottom-CXYHANDLE && y0 <= rc.bottom)
        yHit=YBOT;

    /*
     * If we hit any edge, but didn't hit a handle, then one of xHit
     * and yHit will be NOVALUE and the other something else.  When
     * we hit an edge on the 'something else' then we're on a drag
     * point.
     */

    if ((NOVALUE==xHit && NOVALUE==yHit)
        || (XMID==xHit && YMID==yHit)
        || (NOVALUE==xHit && YMID==yHit)
        || (XMID==xHit && NOVALUE==yHit))
        return;

    if ((NOVALUE==xHit && (YTOP==yHit || YBOT==yHit))
        || ((XLEFT==xHit || XRIGHT==xHit) && NOVALUE==yHit))
        {
        m_uHTCode=HTCAPTION;
        return;
        }

    //We hit a handle, so save our HT code
    m_uSizingFlags=g_rguSizingFlags[xHit+(yHit*3)];
    m_uHTCode=g_rgHTCode[xHit+(yHit*3)];
    return;
    }

/*
 * CPage::SetCursor
 *
 * Purpose:
 *  Processes WM_SETCURSOR using the code from OnNCHitTest.
 *
 * Parameters:
 *  x, y            UINT device coordinates to check.
 *
 * Return Value:
 *  LRESULT         HT* code for Windows.
 */

BOOL CPage::OnSetCursor(UINT uHTCode)
    {
    HCURSOR     hCur;
    UINT        iCur;

    /*
     * We really just ignore uHTCode and use the one we saved
     * in OnNCHitTest.
     */

    switch (m_uHTCode)
        {
        case HTTOP:
        case HTBOTTOM:
            iCur=IDC_VARROWS;
            break;

        case HTLEFT:
        case HTRIGHT:
            iCur=IDC_HARROWS;
            break;

        case HTTOPLEFT:
        case HTBOTTOMRIGHT:
            iCur=IDC_NWSEARROWS;
            break;

        case HTTOPRIGHT:
        case HTBOTTOMLEFT:
            iCur=IDC_NESWARROWS;
            break;

        case HTCAPTION:
            iCur=IDC_SMALLARROWS;
            break;

        default:
            return FALSE;
        }

    hCur=UICursorLoad(iCur);
    SetCursor(hCur);

    return TRUE;
    }

/*
 * CPage::TenantFromPoint
 * (Protected)
 *
 * Purpose:
 *  Finds the tenant under the given device coordinates on this
 *  page.
 *
 * Parmeters:
 *  x, y            UINT coordinates.
 *  ppTenant        PCTenant * in which to return the pointer.
 *
 * Return Value:
 *  UINT            Index of the matched tenant, NOVALUE if not
 *                  found.
 */

UINT CPage::TenantFromPoint(UINT x, UINT y, PCTenant *ppTenant)
    {
    PCTenant    pTenant;
    RECTL       rcl;
    UINT        i;
    int         x0, y0;

    x0=x+m_pPG->m_xPos;
    y0=y+m_pPG->m_yPos;

    for (i=0; i < m_cTenants; i++)
        {
        if (!TenantGet(i, &pTenant, FALSE))
            continue;

        pTenant->RectGet(&rcl, TRUE);

        //Essentially Perform PointInRECTL
        if (x0 >= rcl.left && x0 <= rcl.right)
            {
            if (y0 <=rcl.bottom && y0 >=rcl.top)
                {
                *ppTenant=pTenant;
                return i;
                }
            }
        }

    *ppTenant=NULL;
    return NOVALUE;
    }

/*
 * CPage::DragDrop
 *
 * Purpose:
 *  Performs drag-drop operations from the page window
 *
 * Parmeters:
 *  uKeys           UINT state of the keyboard
 *  x, y            UINT mouse coordinates of the starting click.
 *
 * Return Value:
 *  BOOL            TRUE if we modified the page, FALSE otherwise.
 */

BOOL CPage::DragDrop(UINT uKeys, UINT x, UINT y)
    {
    LPDROPSOURCE    pIDropSource;
    LPDATAOBJECT    pIDataObject;
    HRESULT         hr;
    DWORD           dwEffect;
    POINTL          ptl;
    SIZEL           szl;
    RECTL           rcl;
    RECT            rc, rcT;

    pIDropSource=new CDropSource();

    if (NULL==pIDropSource)
        return FALSE;

    pIDropSource->AddRef();
    m_pPG->m_fDragSource=TRUE;

    /*
     * Store a pick point with the data indicating the offset from
     * the upper left of the rectangle where we grabbed it.  This is
     * so the UI feedback in IDropTarget lines up with this tenant.
     */

    m_pTenantCur->RectGet(&rcl, TRUE);
    ptl.x=x+m_pPG->m_xPos-rcl.left;
    ptl.y=y+m_pPG->m_yPos-rcl.top;
    pIDataObject=TransferObjectCreate(&ptl);

    if (NULL==pIDataObject)
        {
        pIDropSource->Release();
        return FALSE;
        }

    m_pPG->m_fMoveInPage=FALSE;

    dwEffect=DROPEFFECT_COPY | DROPEFFECT_MOVE;
    hr=DoDragDrop(pIDataObject, pIDropSource
        , DROPEFFECT_COPY | DROPEFFECT_MOVE, &dwEffect);

    pIDataObject->Release();
    pIDropSource->Release();

    m_pPG->m_fDragSource=FALSE;

    //No drop-no action.
    if (DRAGDROP_S_DROP!=GetScode(hr) || DROPEFFECT_NONE==dwEffect)
        return FALSE;

    /*
     * If m_pPG->m_fMoveInPage is set, then we just change the
     * coordinates on m_pTenantCur and we're done.
     */
    if (m_pPG->m_fMoveInPage)
        {
        m_pTenantCur->Invalidate();

        /*
         * Clip to page boundaries.  We know that ptDrop has to be
         * in the page somewhere or we would not have dropped
         * (effect was NONE).  So first make sure that ptDrop is
         * within 3*CXYHANDLE of the right or bottom, and if so,
         * pull it out to 3*CXYHANDLE.  Then we can just clip the
         * size to the page rectangle and we'll always be sure to
         * have at least a sizeable object.
         */
        m_pTenantCur->SizeGet(&szl, TRUE);
        SetRect(&rc, (int)m_pPG->m_ptDrop.x, (int)m_pPG->m_ptDrop.y
            , 0, 0);
        RectConvertMappings(&rc, NULL, TRUE);

        m_pPG->CalcBoundingRect(&rcT, FALSE);
        OffsetRect(&rcT, (int)m_pPG->m_xPos, (int)m_pPG->m_yPos);

        if (rc.left >= rcT.right-3*CXYHANDLE)
            rc.left=rcT.right-3*CXYHANDLE;

        if (rc.top >= rcT.bottom-3*CXYHANDLE)
            rc.top=rcT.bottom-3*CXYHANDLE;

        rc.right=rc.left+(int)szl.cx;
        rc.bottom=rc.top+(int)szl.cy;
        IntersectRect(&rc, &rc, &rcT);

        RECTLFROMRECT(rcl, rc);

        m_pTenantCur->RectSet(&rcl, TRUE, FALSE);
        m_pTenantCur->Repaint();
        return TRUE;
        }

    /*
     * Otherwise we may have to delete the old tenant if the effect
     * was move.  This will not happen in the move in page case.
     */

    if (DROPEFFECT_MOVE==dwEffect)
        {
        TenantDestroy();
        return TRUE;
        }

    //Copy is a clean operation
    return FALSE;
    }


PAGES.CPP

/*
 * PAGES.CPP
 * Patron Chapter 22
 *
 * Implementation of the CPages class.  See PAGEWIN.CPP and PRINT.CPP
 * for additional member functions.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPages:CPages
 * CPages::~CPages
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application we're in.
 *  cf              UINT application clipboard format.
 */

CPages::CPages(HINSTANCE hInst, UINT cf)
    : CWindow(hInst)
    {
    m_pPageCur=NULL;
    m_iPageCur=NOVALUE;  //Pages are 0 indexed, this is one before
    m_cPages=0;
    m_hWndPageList=NULL;
    m_hFont=NULL;
    m_fSystemFont=FALSE;

    //Initialize to 8.5*11 inch with .25 inch margins as a default.
    m_cx=(LOMETRIC_PER_INCH*17)/2;
    m_cy=LOMETRIC_PER_INCH*11;

    m_xMarginLeft=LOMETRIC_PER_INCH/4;
    m_xMarginRight=LOMETRIC_PER_INCH/4;
    m_yMarginTop=LOMETRIC_PER_INCH/4;
    m_yMarginBottom=LOMETRIC_PER_INCH/4;

    m_xPos=0L;
    m_yPos=0L;

    m_dwIDNext=0;
    m_pIStorage=NULL;

    m_fDirty=FALSE;
    m_cf=cf;

    m_fDragSource=FALSE;
    m_fMoveInPage=FALSE;
    m_fLinkAllowed=FALSE;

    m_fDragRectShown=FALSE;

    m_uScrollInset=GetProfileInt(TEXT("windows")
        , TEXT("DragScrollInset"), DD_DEFSCROLLINSET);

    m_uScrollDelay=GetProfileInt(TEXT("windows")
        , TEXT("DragScrollDelay"), DD_DEFSCROLLDELAY);

    m_uHScrollCode=0;
    m_uVScrollCode=0;
    m_fShowTypes=FALSE;
    m_pmkFile=NULL;

    //CHAPTER22MOD
    m_fAddUI=FALSE;
    //End CHAPTER22MOD
    return;
    }

CPages::~CPages(void)
    {
    //Ensure memory cleaned up in list; do final IStorage::Release
    StorageSet(NULL, FALSE, FALSE);

    ReleaseInterface(m_pmkFile);

    if (NULL!=m_hFont && !m_fSystemFont)
        DeleteObject(m_hFont);

    if (NULL!=m_hWndPageList)
        DestroyWindow(m_hWndPageList);

    //m_hWnd destroyed with the document.
    return;
    }

/*
 * CPages::FIsDirty
 *
 * Purpose:
 *  Tells the caller (document) if anything's happened to dirty us.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPages::FIsDirty(void)
    {
    return m_fDirty;
    }

/*
 * CPages::Init
 *
 * Purpose:
 *  Instantiates a pages window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags.
 *                  Should contain WS_CHILD | WS_VISIBLE in
 *                  typical circumstances.
 *  uID             UINT ID to associate with this window
 *  pv              LPVOID unused for now.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPages::Init(HWND hWndParent, LPRECT pRect, DWORD dwStyle
    , UINT uID, LPVOID pv)
    {
    int     cy;

    m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY, SZCLASSPAGES
        , SZCLASSPAGES, dwStyle, pRect->left, pRect->top
        , pRect->right-pRect->left, pRect->bottom-pRect->top
        , hWndParent, (HMENU)uID, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    /*
     * Create the hidden listbox we'll use to track pages.  We give
     * it the owner-draw style so we can just store pointers in it.
     */
    m_hWndPageList=CreateWindow(TEXT("listbox"), TEXT("Page List")
        , WS_POPUP | LBS_OWNERDRAWFIXED, 0, 0, 100, 100
        , HWND_DESKTOP, NULL, m_hInst, NULL);

    if (NULL==m_hWndPageList)
        return FALSE;

    //Create a 14 point Arial font, or use the system variable font.
    cy=MulDiv(-14, LOMETRIC_PER_INCH, 72);
    m_hFont=CreateFont(cy, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE
        , ANSI_CHARSET, OUT_TT_PRECIS, CLIP_TT_ALWAYS, PROOF_QUALITY
        , VARIABLE_PITCH | FF_SWISS, TEXT("Arial"));

    if (NULL==m_hFont)
        {
        m_hFont=(HFONT)GetStockObject(ANSI_VAR_FONT);
        m_fSystemFont=TRUE;
        }

    return TRUE;
    }

/*
 * CPages::StorageSet
 *
 * Purpose:
 *  Provides the document's IStorage to the pages for its own use.
 *  If this is a new storage, we initalize it with streams that we
 *  want to always exists.  If this is an open, then we create
 *  our page list from the PageList string we wrote before.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE to the new or opened storage.  If
 *                  NULL, we just clean up and exit.
 *  fChange         BOOL indicating is this was a Save As operation,
 *                  meaning that we have the structure already and
 *                  just need to change the value of m_pIStorage.
 *  fInitNew        BOOL indicating if this is a new storage or one
 *                  opened from a previous save.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::StorageSet(LPSTORAGE pIStorage, BOOL fChange
    , BOOL fInitNew)
    {
    DWORD           dwMode=STGM_DIRECT | STGM_READWRITE
                        | STGM_SHARE_EXCLUSIVE;
    HRESULT         hr;
    PCPage          pPage;
    BOOL            fRet=FALSE;
    ULONG           cbRead;
    PAGELIST        pgList;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    LPDWORD         pdwID;
    UINT            i;

    //If we're changing saved roots, simply open current page again
    if (fChange)
        {
        if (NULL==pIStorage)
            return FALSE;

        m_pIStorage->Release();
        m_pIStorage=pIStorage;
        m_pIStorage->AddRef();

        PageGet(m_iPageCur, &m_pPageCur, TRUE);
        m_fDirty=FALSE;
        return TRUE;
        }

    if (NULL!=m_hWndPageList)
        {
        //On new or open, clean out whatever it is we have.
        for (i=0; i < m_cPages; i++)
            {
            if (PageGet(i, &pPage, FALSE))
                pPage->Release();
            }

        SendMessage(m_hWndPageList, LB_RESETCONTENT, 0, 0L);
        }

    if (NULL!=m_pIStorage)
        m_pIStorage->Release();

    m_pIStorage=NULL;

    //If we're just cleaning up, then we're done.
    if (NULL==pIStorage)
        return TRUE;

    m_pIStorage=pIStorage;
    m_pIStorage->AddRef();

    //If this is a new storage, create the streams we require
    if (fInitNew)
        {
        //Page list header.
        hr=m_pIStorage->CreateStream(SZSTREAMPAGELIST, dwMode
            | STGM_CREATE, 0, 0, &pIStream);

        if (FAILED(hr))
            return FALSE;

        pIStream->Release();

        //Device Configuration
        hr=m_pIStorage->CreateStream(SZSTREAMDEVICECONFIG, dwMode
            | STGM_CREATE, 0, 0, &pIStream);

        if (FAILED(hr))
            return FALSE;

        pIStream->Release();
        return TRUE;
        }

    /*
     * We're opening an existing file:
     *  1)  Configure for the device we're on
     *  2)  Read the Page List and create page entries for each.
     */

    ConfigureForDevice();

    //Read the page list.
    hr=m_pIStorage->OpenStream(SZSTREAMPAGELIST, NULL, dwMode, 0
        , &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pIStream->Read(&pgList, sizeof(PAGELIST), &cbRead);
        m_cPages  =(UINT)pgList.cPages;
        m_iPageCur=(UINT)pgList.iPageCur;
        m_dwIDNext=pgList.dwIDNext;

        fRet=TRUE;
        cbRead=pgList.cPages*sizeof(DWORD);

        if (0!=cbRead)
            {
            pdwID=(LPDWORD)pIMalloc->Alloc(cbRead);

            if (NULL!=pdwID)
                {
                pIStream->Read(pdwID, cbRead, &cbRead);

                for (i=0; i < m_cPages; i++)
                    fRet &=PageAdd(NOVALUE, *(pdwID+i), FALSE);

                pIMalloc->Free(pdwID);
                }
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    if (!fRet)
        return FALSE;

    PageGet(m_iPageCur, &m_pPageCur, TRUE);
    m_fDirty=FALSE;

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);

    return TRUE;
    }

/*
 * CPages::StorageUpdate
 *
 * Purpose:
 *  Insures that all pages are committed before a root save.
 *
 * Parameters:
 *  fCloseAll       BOOL directing us to close all open storages
 *                  and streams.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::StorageUpdate(BOOL fCloseAll)
    {
    PCPage          pPage;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    LPDWORD         pdwID;
    ULONG           cb;
    HRESULT         hr;
    PAGELIST        pgList;
    BOOL            fRet=FALSE;
    UINT            i;

    //We only need to close the current page--nothing else is open.
    if (NULL!=m_pPageCur)
        {
        m_pPageCur->Update();

        if (fCloseAll)
            m_pPageCur->Close(FALSE);
        }

    //We don't hold anything else open, so write the page list.
    hr=m_pIStorage->OpenStream(SZSTREAMPAGELIST, NULL, STGM_DIRECT
        | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pgList.cPages=m_cPages;
        pgList.iPageCur=m_iPageCur;
        pgList.dwIDNext=m_dwIDNext;

        pIStream->Write(&pgList, sizeof(PAGELIST), &cb);

        cb=m_cPages*sizeof(DWORD);
        pdwID=(LPDWORD)pIMalloc->Alloc(cb);

        if (NULL!=pdwID)
            {
            for (i=0; i < m_cPages; i++)
                {
                PageGet(i, &pPage, FALSE);
                *(pdwID+i)=pPage->GetID();
                }

            pIStream->Write(pdwID, cb, &cb);
            pIMalloc->Free(pdwID);
            fRet=TRUE;
            }
        pIMalloc->Release();
        }

    pIStream->Release();

    //Clean up the dirty flag when we do an update.
    m_fDirty=!fRet;
    return fRet;
    }

/*
 * CPages::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Pages window in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  None
 */

void CPages::RectGet(LPRECT pRect)
    {
    RECT        rc;
    POINT       pt;

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_hWnd, &rc);
    SETPOINT(pt, rc.left, rc.top);
    ScreenToClient(GetParent(m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return;
    }

/*
 * CPages::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Pages window which sizes to fit.
 *  Coordinates are given in parent terms.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPages::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    if (NULL==pRect)
        return;

    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, pRect->left, pRect->top
        , (UINT)cx, (UINT)cy, SWP_NOZORDER);

    UpdateScrollRanges();
    return;
    }

/*
 * CPages::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the pages window in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  None
 */

void CPages::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return;
    }

/*
 * CPages::SizeSet
 *
 * Purpose:
 *  Sets a new size in parent coordinates for the Pages window.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPages::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    if (NULL==pRect)
        return;

    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
        , SWP_NOMOVE | SWP_NOZORDER);

    UpdateScrollRanges();
    return;
    }

/*
 * CPages::ActivePage
 *
 * Purpose:
 *  Returns a CPage pointer to the current page.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCPage          Pointer to the current page.
 */

PCPage CPages::ActivePage(void)
    {
    PCPage      pPage;
    BOOL        fRet;

    fRet=PageGet(m_iPageCur, &pPage, FALSE);
    return fRet ? pPage : NULL;
    }

/*
 * CPages::PageInsert
 *
 * Purpose:
 *  Creates a new page immediately after the current page.  If
 *  there are no pages then this creates page 1.
 *
 * Parameters:
 *  uReserved       UINT unused
 *
 * Return Value:
 *  UINT            Index of the new page, 0 on failure.
 */

UINT CPages::PageInsert(UINT uReserved)
    {
    if (0!=m_cPages && NULL!=m_pPageCur)
        {
        //Close the current page, committing changes.
        m_pPageCur->Close(TRUE);
        }

    //Create and open the new page.
    if (!PageAdd(m_iPageCur, m_dwIDNext, TRUE))
        return 0;

    m_dwIDNext++;
    m_iPageCur++;
    m_cPages++;

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);

    PageGet(m_iPageCur, &m_pPageCur, FALSE);
    return m_iPageCur;
    }

/*
 * CPages::PageDelete
 *
 * Removes the current page from the page list.
 *
 * Parameters:
 *  uReserved       UINT unused
 *
 * Return Value:
 *  UINT            Index to the now current page from the page
 *                  list, NOVALUE on error.
 */

UINT CPages::PageDelete(UINT uReserved)
    {
    PCPage      pPage;

    if (!PageGet(m_iPageCur, &pPage, FALSE))
        return NOVALUE;

    //Delete the page in both the storage and in memory.
    SendMessage(m_hWndPageList, LB_DELETESTRING, m_iPageCur, 0L);

    m_pPageCur->Destroy(m_pIStorage);

    m_pPageCur->Release();   //Does final pPage->Close
    m_pPageCur=NULL;

    /*
     * If this is the last page then the current is one less.  If
     * it's the only page the current is zero.  Otherwise the
     * current is the next page.
     */

    if (m_iPageCur==m_cPages-1)   //Covers last or only page.
        m_iPageCur--;

    m_cPages--;

    //Insure the new visible page is open.
    if (0!=m_cPages)
        {
        PageGet(m_iPageCur, &m_pPageCur, TRUE);
        InvalidateRect(m_hWnd, NULL, FALSE);
        }
    else
        InvalidateRect(m_hWnd, NULL, TRUE);

    UpdateWindow(m_hWnd);
    return m_iPageCur;
    }

/*
 * CPages::CurPageGet
 *
 * Purpose:
 *  Retrieves the index of the current page we're viewing.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the current page.
 */

UINT CPages::CurPageGet(void)
    {
    return m_iPageCur;
    }

/*
 * CPages::CurPageSet
 *
 * Purpose:
 *  Sets the index of the current page to view.
 *
 * Parameters:
 *  iPage           UINT index of the page to view. 0 means first
 *                  page, NOVALUE means last page.
 *
 * Return Value:
 *  UINT            Index of the previous current page, NOVALUE on
 *                  error.
 */

UINT CPages::CurPageSet(UINT iPage)
    {
    UINT    iPageNew;
    UINT    iPagePrev=m_iPageCur;
    PCPage  pPage;

    switch (iPage)
        {
        case 0:
            iPageNew=0;
            break;

        case NOVALUE:
            iPageNew=m_cPages-1;
            break;

        default:
            if (iPage >= m_cPages)
                iPage=0;

            iPageNew=iPage;
            break;
        }

    //No reason to switch to the same page.
    if (iPagePrev==iPageNew)
        return iPage;

    //Close the old page committing changes.
    if (!PageGet(iPagePrev, &pPage, FALSE))
        return NOVALUE;

    pPage->Close(TRUE);

    m_iPageCur=iPageNew;

    //Open the new page.
    PageGet(m_iPageCur, &m_pPageCur, TRUE);

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);
    return iPagePrev;
    }

/*
 * CPages::NumPagesGet
 *
 * Purpose:
 *  Returns the number of pages this object current contains.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of pages we contain.
 */

UINT CPages::NumPagesGet(void)
    {
    return m_cPages;
    }

/*
 * CPages::TenantCreate
 * CPages::TenantDestroy
 *
 * Purpose:
 *  Pass-throughs for CPage members on the current page.
 */

BOOL CPages::TenantCreate(TENANTTYPE tType, LPVOID pv
    , LPFORMATETC pFE, PPATRONOBJECT ppo, DWORD dwData)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantCreate(tType, pv, pFE, ppo, dwData);
    m_fDirty |= fRet;
    return fRet;
    }

BOOL CPages::TenantDestroy(void)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantDestroy();
    m_fDirty |= fRet;
    return fRet;
    }

/*
 * CPages::TenantClip
 *
 * Purpose:
 *  Copies or cuts the currently selected tenant to the clipoard.
 *
 * Parameters:
 *  fCut            BOOL TRUE to cut the object, FALSE to copy.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::TenantClip(BOOL fCut)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantClip(fCut);
    m_fDirty |= (fRet && fCut);
    return fRet;
    }

/*
 * CPages::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected on this
 *  page for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU on which object related items live.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPages::FQueryObjectSelected(HMENU hMenu)
    {
    if (NULL==m_pPageCur)
        return FALSE;

    return m_pPageCur->FQueryObjectSelected(hMenu);
    }

//CHAPTER22MOD
/*
 * CPages::ActivateObject
 *
 * Purpose:
 *  Executes a verb on the currently selected object.
 *
 * Parameters:
 *  iVerb           LONG of the selected verb.
 *  pMSG            LPMSG to whatever invoked the verb.
 *
 * Return Value:
 *  None
 */

void CPages::ActivateObject(LONG iVerb, LPMSG pMSG)
    {
    if (NULL==m_pPageCur)
        return;

    m_pPageCur->ActivateObject(iVerb, pMSG);
    return;
    }
//End CHAPTER22MOD

/*
 * CPages::ShowObjectTypes
 *
 * Purpose:
 *  Pass-through to CPage::ShowObjectTypes
 *
 * Parameters:
 *  fShow           BOOL indicating to show the type or not.
 *
 * Return Value:
 *  None
 */

void CPages::ShowObjectTypes(BOOL fShow)
    {
    if (NULL==m_pPageCur)
        return;

    m_fShowTypes=fShow;
    m_pPageCur->ShowObjectTypes(fShow);
    return;
    }

/*
 * CPages::NotifyTenantsOfRename
 *
 * Purpose:
 *  Pass-through to CPage to tell tenants that the document
 *  changes names.
 *
 * Parameters:
 *  pszFile         LPTSTR of the new filename.
 *  pmk             LPMONKIER to the new file moniker.
 *
 * Return Value:
 *  None
 */

void CPages::NotifyTenantsOfRename(LPTSTR pszFile, LPMONIKER pmk)
    {
    if (NULL==m_pPageCur)
        return;

    ReleaseInterface(m_pmkFile);
    m_pmkFile=pmk;
    m_pmkFile->AddRef();
    m_pPageCur->NotifyTenantsOfRename(pszFile, pmk);
    return;
    }

/*
 * CPages::FQueryLinksInPage
 *
 * Purpose:
 *  Pass through to current page to see if there are any
 *  linked objects
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPages::FQueryLinksInPage()
    {
    if (NULL==m_pPageCur)
        return FALSE;

    return m_pPageCur->FQueryLinksInPage();
    }

/*
 * CPages::GetUILinkContainer
 *
 * Purpose:
 *  Creates an object with the IOleUILinkContainer interface for
 *  the links dialog.  We know the current page which is what
 *  we need to create this thing, so we can create it here instead
 *  of bugging the page for it.
 *
 * Parameters:
 *  ppObj           PCIOleUILinkContainer * in which we return
 *                  the pointer.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::GetUILinkContainer(PCIOleUILinkContainer *ppObj)
    {
    PCIOleUILinkContainer   pObj;

    *ppObj=NULL;

    if (NULL==m_pPageCur)
        return FALSE;

    pObj=new CIOleUILinkContainer(m_pPageCur);

    if (NULL==pObj)
        return FALSE;

    if (!pObj->Init())
        {
        delete pObj;
        return FALSE;
        }

    pObj->AddRef();
    *ppObj=pObj;
    return TRUE;
    }

/*
 * CPages::ConvertObject
 *
 * Purpose:
 *  Pass-through to the current page.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame window.
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful, FALSE
 *                  otherwise.
 */

BOOL CPages::ConvertObject(HWND hWndFrame)
    {
    if (NULL==m_pPageCur)
        return FALSE;

    return m_pPageCur->ConvertObject(hWndFrame, FALSE);
    }

/*
 * CPages::CalcBoundingRect
 * (Protected)
 *
 * Purpose:
 *  Calculates a rectangle that bounds the printed page and the
 *  current scroll state of the window.
 *
 * Parameters:
 *  prc             LPRECT to fill with window (device) coordinates.
 *  fWindow         BOOL indicating to include the window in this
 *                  calculation or return only the printed page
 *                  coordinates.
 *
 * Return Value:
 *  None
 */

void CPages::CalcBoundingRect(LPRECT prc, BOOL fWindow)
    {
    RECT        rc, rcT;

    if (NULL==prc)
        return;

    //Calculate the boundaries for sizing: intersect page & screen
    rc.left=LOMETRIC_BORDER+m_xMarginLeft;
    rc.top =-LOMETRIC_BORDER-m_yMarginTop;
    rc.right =rc.left+(UINT)m_cx;
    rc.bottom=rc.top -(UINT)m_cy;
    RectConvertMappings(&rc, NULL, TRUE);
    OffsetRect(&rc, -(int)m_xPos, -(int)m_yPos);

    if (!fWindow)
        {
        *prc=rc;
        return;
        }

    //Intersect with window to make the size bounds.
    GetClientRect(m_hWnd, &rcT);
    IntersectRect(prc, &rc, &rcT);
    return;
    }

/*
 * CPages::PageGet
 * (Protected)
 *
 * Purpose:
 *  Returns a page of a given index returning a BOOL so it's simple
 *  to use this function inside if statements.
 *
 * Parameters:
 *  iPage           UINT page to retrieve 0 based.
 *  ppPage          PCPage * in which to return the page pointer
 *  fOpen           BOOL indicating if we should open this page as
 *                  well.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::PageGet(UINT iPage, PCPage *ppPage, BOOL fOpen)
    {
    if (NULL==ppPage)
        return FALSE;

    if (LB_ERR!=SendMessage(m_hWndPageList, LB_GETTEXT
        , iPage, (LONG)ppPage))
        {
        if (fOpen)
            (*ppPage)->Open(m_pIStorage);

        return TRUE;
        }

    return FALSE;
    }

/*
 * CPages::PageAdd
 * (Protected)
 *
 * Purpose:
 *  Creates a new page initialized to the given values.  The new
 *  page's storage is created if it does not already exist.  If
 *  fOpenStorage is set the page's storage is opened and left
 *  opened.
 *
 * Parameters:
 *  iPage           UINT Location at which to insert page; new page
 *                  is inserted after this position.  NOVALUE for
 *                  the end.
 *  dwID            DWORD ID for this page.
 *  fOpenStorage    BOOL indicating if we're to leave the storage
 *                  open.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPages::PageAdd(UINT iPage, DWORD dwID, BOOL fOpenStorage)
    {
    PCPage      pPage;
    LRESULT     lr;

    pPage=new CPage(dwID, m_hWnd, this);

    if (NULL==pPage)
        return FALSE;

    pPage->AddRef();

    if (fOpenStorage)
        pPage->Open(m_pIStorage);

    if (NOVALUE==iPage)
        iPage--;

    //Now try to add to the listbox.
    lr=SendMessage(m_hWndPageList, LB_INSERTSTRING, iPage+1
        , (LONG)pPage);

    if (LB_ERRSPACE==lr)
        {
        if (fOpenStorage)
            pPage->Close(FALSE);

        pPage->Release();
        return FALSE;
        }

    return TRUE;
    }

/*
 * CPages::IPageGetFromID
 * (Protected)
 *
 * Purpose:
 *  Returns a page of a given identifier
 *
 * Parameters:
 *  dwID            DWORD identifier of the page to retrieve.  If
 *                  NOVALUE, then we return the current page.
 *  ppPage          PCPage * in which to return the page
 *                  pointer
 *  fOpen           BOOL indicating if we should open this page as
 *                  well.
 *
 * Return Value:
 *  UINT            Index of the page if successful, NOVALUE
 *                  otherwise.
 */

UINT CPages::IPageGetFromID(DWORD dwID, PCPage *ppPage
    , BOOL fOpen)
    {
    UINT        i;
    PCPage      pPage;

    if (NULL==ppPage)
        return FALSE;

    if ((LONG)-1==(LONG)dwID)
        {
        *ppPage=m_pPageCur;
        return m_iPageCur;
        }

    *ppPage=NULL;

    for (i=0; i < m_cPages; i++)
        {
        if (!PageGet(i, &pPage, FALSE))
            continue;

        if (pPage->GetID()==dwID)
            {
            if (fOpen)
                pPage->Open(m_pIStorage);

            *ppPage=pPage;
            return i;
            }
        }

    return NOVALUE;
    }


PAGES.H

/*
 * PAGES.H
 * Patron Chapter 22
 *
 * Definitions and function prototypes for the Pages window control
 * as well as the CPage class.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _PAGES_H_
#define _PAGES_H_

#include <stdlib.h>     //For atol

//Versioning.
#define VERSIONMAJOR                2
#define VERSIONMINOR                0
#define VERSIONCURRENT              0x00020000

//Classname
#define SZCLASSPAGES                TEXT("pages")

#define HIMETRIC_PER_INCH           2540
#define LOMETRIC_PER_INCH           254
#define LOMETRIC_BORDER             60          //Border around page

//Window extra bytes and offsets
#define CBPAGESWNDEXTRA             (sizeof(LONG))
#define PAGEWL_STRUCTURE            0

#include "tenant.h"

typedef struct tagTENANTLIST
    {
    DWORD       cTenants;
    DWORD       dwIDNext;
    } TENANTLIST, *PTENANTLIST;

#define SZSTREAMTENANTLIST        OLETEXT("Tenant List")

//Delay timer used in mouse debouncing
#define IDTIMER_DEBOUNCE          120

/*
 * Page class describing an individual page and what things it
 * contains, managing an IStorage for us.
 *
 * A DWORD is used to identify this page as the name of the storage
 * is the string form of this ID.  If we added a page every second,
 * it would take 136 years to overrun this counter, so we can
 * get away with saving it persistently.  I hope this software is
 * obsolete by then.
 */

class CPage : public IUnknown
    {
    friend class CIOleUILinkContainer;
    friend class CImpIOleItemContainer;

    private:
        DWORD       m_dwID;             //Persistent identifier
        LPSTORAGE   m_pIStorage;        //Substorage for this page
        HWND        m_hWnd;             //Pages window
        DWORD       m_cOpens;           //Calls to Open

        class CPages *m_pPG;            //Pages window

        DWORD       m_dwIDNext;
        DWORD       m_cTenants;
        HWND        m_hWndTenantList;   //Listbox; our tenant list

        UINT        m_iTenantCur;
        PCTenant    m_pTenantCur;

        UINT        m_uHTCode;          //Last hit-test/mouse move
        UINT        m_uSizingFlags;     //Restrictions on sizing
        BOOL        m_fTracking;        //Tracking resize?
        RECTL       m_rclOrg;           //Original before tracking
        RECTL       m_rcl;              //Tracking rectangle
        RECTL       m_rclBounds;        //Boundaries f/size tracking
        HDC         m_hDC;              //Tracking hDC

        BOOL        m_fDragPending;     //Waiting for drag?
        BOOL        m_fSizePending;     //Waiting for debounce?
        int         m_cxyDist;          //Debounce distance
        UINT        m_cDelay;           //Debounce delay
        POINTS      m_ptDown;           //Point of click to debounce
        UINT        m_uKeysDown;        //Keys when click happens
        DWORD       m_fTimer;           //Timer active?
        BOOL        m_fReopen;          //Did we just close?

        LPMONIKER               m_pmkFile;  //Document name
        ULONG                   m_cRef;
        DWORD                   m_dwRegROTWild;

        class CImpIOleItemContainer *m_pImpIOleItemContainer;

        //CHAPTER22MOD
        BOOL                m_fFirstUIActivate;
        //End CHAPTER22MOD

    protected:
        BOOL         TenantGet(UINT, PCTenant *, BOOL);
        BOOL         TenantGetFromID(DWORD, PCTenant *, BOOL);
        BOOL         TenantAdd(UINT, DWORD, PCTenant *);
        LPDATAOBJECT TransferObjectCreate(PPOINTL);

        //PAGEMOUS.CPP
        BOOL         SelectTenantAtPoint(UINT, UINT);
        UINT         TenantFromPoint(UINT, UINT, PCTenant *);
        BOOL         DragDrop(UINT, UINT, UINT);

    public:
        CPage(DWORD, HWND, class CPages *);
        ~CPage(void);

        //IUnknown for delegation
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        DWORD       GetID(void);
        BOOL        Open(LPSTORAGE);
        void        Close(BOOL);
        BOOL        Update(void);
        void        Destroy(LPSTORAGE);
        UINT        GetStorageName(LPOLESTR);

        void        Draw(HDC, int, int, BOOL, BOOL);

        BOOL        TenantCreate(TENANTTYPE, LPVOID, LPFORMATETC
                        , PPATRONOBJECT, DWORD);
        BOOL        TenantDestroy(void);
        BOOL        TenantClip(BOOL);
        BOOL        FQueryObjectSelected(HMENU);

        //CHAPTER22MOD
        void        ActivateObject(LONG, LPMSG);
        //End CHAPTER22MOD

        void        ShowObjectTypes(BOOL);
        void        NotifyTenantsOfRename(LPTSTR, LPMONIKER);
        BOOL        FQueryLinksInPage(void);
        BOOL        ConvertObject(HWND, BOOL);

        //CHAPTER22MOD
        void        ScrolledWindow(void);
        void        SwitchActiveTenant(PCTenant);
        //End CHAPTER22MOD

        //PAGEMOUSE.CPP
        BOOL        OnRightDown(UINT, UINT, UINT);
        BOOL        OnLeftDown(UINT, UINT, UINT);
        BOOL        OnLeftDoubleClick(UINT, UINT, UINT);
        BOOL        OnLeftUp(UINT, UINT, UINT);
        void        OnMouseMove(UINT, int, int);
        void        OnTimer(UINT);
        void        StartSizeTracking(void);
        void        OnNCHitTest(UINT, UINT);
        BOOL        OnSetCursor(UINT);
    };

typedef CPage *PCPage;

/*
 * Structures to save with the document describing the device
 * configuration and pages that we have.  This is followed by
 * a list of DWORD IDs for the individual pages.
 */

typedef struct tagDEVICECONFIG
    {
    DWORD       cb;                         //Size of structure
    TCHAR       szDriver[CCHDEVICENAME];
    TCHAR       szDevice[CCHDEVICENAME];
    TCHAR       szPort[CCHDEVICENAME];
    DWORD       cbDevMode;                  //Size of actual DEVMODE
    DEVMODE     dm;                         //Variable
    } DEVICECONFIG, *PDEVICECONFIG;

//Offset to cbDevMode
#define CBSEEKOFFSETCBDEVMODE  (sizeof(DWORD)   \
                               +(3*CCHDEVICENAME*sizeof(TCHAR)))

//Combined OLE and Patron device structures.
typedef struct tagCOMBINEDEVICE
    {
    DVTARGETDEVICE  td;
    DEVICECONFIG    dc;
    } COMBINEBDEVICE, *PCOMBINEDEVICE;

typedef struct tagPAGELIST
    {
    DWORD       cPages;
    DWORD       iPageCur;
    DWORD       dwIDNext;
    } PAGELIST, *PPAGELIST;

//PRINT.CPP
BOOL    APIENTRY PrintDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL    APIENTRY AbortProc(HDC, int);

//PAGEWIN.CPP
LRESULT APIENTRY PagesWndProc(HWND, UINT, WPARAM, LPARAM);
void             RectConvertMappings(LPRECT, HDC, BOOL);

class CPages : public CWindow
    {
    friend LRESULT APIENTRY PagesWndProc(HWND, UINT, WPARAM, LPARAM);
    friend BOOL    APIENTRY PrintDlgProc(HWND, UINT, WPARAM, LPARAM);

    friend class CPage;
    friend class CTenant;
    friend class CDropTarget;
    friend class CImpIAdviseSink;
    friend class CImpIOleItemContainer;

    //CHAPTER22MOD
    friend class CImpIOleInPlaceSite;
    //End CHAPTER22MOD

    protected:
        PCPage      m_pPageCur;             //Current page
        UINT        m_iPageCur;             //Current page
        UINT        m_cPages;               //Number of pages

        HWND        m_hWndPageList;         //Listbox with page list
        HFONT       m_hFont;                //Page font
        BOOL        m_fSystemFont;          //m_hFont system object?

        UINT        m_cx;                   //Page size in LOMETRIC
        UINT        m_cy;

        UINT        m_xMarginLeft;          //Unusable margins,
        UINT        m_xMarginRight;         //in LOMETRIC
        UINT        m_yMarginTop;
        UINT        m_yMarginBottom;

        UINT        m_xPos;                 //Viewport scroll pos,
        UINT        m_yPos;                 //both in *PIXELS*

        DWORD       m_dwIDNext;             //Next ID for a page.
        LPSTORAGE   m_pIStorage;            //Root storage

        UINT        m_cf;                   //Clipboard format
        BOOL        m_fDirty;

        BOOL        m_fDragSource;          //Source==target?
        BOOL        m_fMoveInPage;          //Moving in same page
        BOOL        m_fLinkAllowed;         //Linking in drag-drop?
        POINTL      m_ptDrop;               //Where to move object

        BOOL        m_fDragRectShown;       //Is rect on the screen?
        UINT        m_uScrollInset;         //Hit-test for drag-drop
        UINT        m_uScrollDelay;         //Delay before repeat
        DWORD       m_dwTimeLast;           //Ticks on last DragOver
        UINT        m_uHScrollCode;         //L/R on scroll repeat?
        UINT        m_uVScrollCode;         //U/D on scroll repeat?
        UINT        m_uLastTest;            //Last test result
        POINTL      m_ptlRect;              //Last feedback rectangle
        SIZEL       m_szlRect;

        BOOL        m_fShowTypes;           //Show Object active?
        LPMONIKER   m_pmkFile;
        //CHAPTER22MOD
        BOOL        m_fAddUI;
        //End CHAPTER22MOD

    private:
        void        Draw(HDC, BOOL, BOOL);
        void        UpdateScrollRanges(void);
        BOOL        ConfigureForDevice(void);
        BOOL        PageGet(UINT, PCPage *, BOOL);
        BOOL        PageAdd(UINT, DWORD, BOOL);

        void        CalcBoundingRect(LPRECT, BOOL);

        //DRAGDROP.CPP
        UINT        UTestDroppablePoint(PPOINTL);
        void        DrawDropTargetRect(PPOINTL, LPSIZEL);
        void        AdjustPosition(PPOINTL, LPSIZEL);

    public:
        CPages(HINSTANCE, UINT);
        ~CPages(void);

        BOOL        Init(HWND, LPRECT, DWORD, UINT, LPVOID);

        BOOL        StorageSet(LPSTORAGE, BOOL, BOOL);
        BOOL        StorageUpdate(BOOL);

        BOOL        Print(HDC, LPTSTR, DWORD, UINT, UINT, UINT);

        void        RectGet(LPRECT);
        void        RectSet(LPRECT, BOOL);
        void        SizeGet(LPRECT);
        void        SizeSet(LPRECT, BOOL);

        PCPage      ActivePage(void);
        UINT        PageInsert(UINT);
        UINT        PageDelete(UINT);
        UINT        CurPageGet(void);
        UINT        CurPageSet(UINT);
        UINT        NumPagesGet(void);

        BOOL        DevModeSet(HGLOBAL, HGLOBAL);
        HGLOBAL     DevModeGet(void);

        BOOL        FIsDirty(void);
        BOOL        DevReadConfig(PCOMBINEDEVICE *, HDC *);
        BOOL        TenantCreate(TENANTTYPE, LPVOID, LPFORMATETC
                        , PPATRONOBJECT, DWORD);
        BOOL        TenantDestroy(void);
        BOOL        TenantClip(BOOL);
        BOOL        FQueryObjectSelected(HMENU);

        //CHAPTER22MOD
        void        ActivateObject(LONG, LPMSG);
        //End CHAPTER22MOD

        void        ShowObjectTypes(BOOL);
        void        NotifyTenantsOfRename(LPTSTR, LPMONIKER);
        BOOL        FQueryLinksInPage(void);
        BOOL        GetUILinkContainer(class CIOleUILinkContainer **);
        BOOL        ConvertObject(HWND);
        UINT        IPageGetFromID(DWORD, PCPage *, BOOL);
    };

typedef CPages *PCPages;

//Fixed names of streams in the Pages IStorage
#define SZSTREAMPAGELIST        OLETEXT("Page List")
#define SZSTREAMDEVICECONFIG    OLETEXT("Device Configuration")

//Return values for UTestDroppablePoint
#define UDROP_NONE              0x0000      //Exclusive
#define UDROP_CLIENT            0x0001      //Inclusive
#define UDROP_INSETLEFT         0x0002      //L/R are exclusive
#define UDROP_INSETRIGHT        0x0004
#define UDROP_INSETHORZ         (UDROP_INSETLEFT | UDROP_INSETRIGHT)

#define UDROP_INSETTOP          0x0008      //T/B are exclusive
#define UDROP_INSETBOTTOM       0x0010
#define UDROP_INSETVERT         (UDROP_INSETTOP | UDROP_INSETBOTTOM)

//Object used for the Links dialog

class CIOleUILinkContainer : public IOleUILinkContainer
    {
    private:
        ULONG                   m_cRef;
        PCPage                  m_pPage;
        UINT                    m_iTenant;
        LPOLEUILINKCONTAINER    m_pDelIUILinks;

    public:
        BOOL                    m_fDirty;   //No reason to hide it

    protected:
        STDMETHODIMP GetObjectInterface(DWORD, REFIID, PPVOID);

    public:
        CIOleUILinkContainer(PCPage);
        ~CIOleUILinkContainer(void);

        BOOL Init(void);
        BOOL IsDirty(void);

        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(DWORD) GetNextLink(DWORD);
        STDMETHODIMP         SetLinkUpdateOptions(DWORD, DWORD);
        STDMETHODIMP         GetLinkUpdateOptions(DWORD, LPDWORD);
        STDMETHODIMP         SetLinkSource(DWORD, LPTSTR, ULONG
                                 , ULONG *, BOOL);
        STDMETHODIMP         GetLinkSource(DWORD, LPTSTR *, ULONG *
                                 , LPTSTR *, LPTSTR *, BOOL *
                                 , BOOL *);
        STDMETHODIMP         OpenLinkSource(DWORD);
        STDMETHODIMP         UpdateLink(DWORD, BOOL, BOOL);
        STDMETHODIMP         CancelLink(DWORD);
    };

typedef CIOleUILinkContainer *PCIOleUILinkContainer;

#endif  //_PAGES_H_


PAGEWIN.CPP

/*
 * PAGEWIN.CPP
 * Patron Chapter 22
 *
 * Window procedure for the Pages window and support functions.
 * This window manages its own scrollbars and viewport and provides
 * printing capabilities as well.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * PagesWndProc
 *
 * Purpose:
 *  Window procedure for the Pages window.
 */

LRESULT APIENTRY PagesWndProc(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    PCPages         ppg;
    PAINTSTRUCT     ps;
    HDC             hDC;
    int             iPos, iTmp;
    int             iMin, iMax;
    UINT            idScroll;
    BOOL            fDirty=FALSE;

    ppg=(PCPages)GetWindowLong(hWnd, PAGEWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppg=(PCPages)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PAGEWL_STRUCTURE, (LONG)ppg);

            ppg->m_hWnd=hWnd;
            break;

        case WM_PAINT:
            /*
             * If there is currently a drag-rectangle showing, then
             * remove it before painting.  This insures that
             * painting doesn't blast part of that rectangle away so
             * that when we draw it next, garbage is left around.
             */
            if (ppg->m_fDragRectShown)
                ppg->DrawDropTargetRect(NULL, NULL);

            hDC=BeginPaint(hWnd, &ps);

            //Draw only if we have a page to show.
            if (0!=ppg->m_cPages)
                ppg->Draw(hDC, FALSE, FALSE);

            EndPaint(hWnd, &ps);

            //Turn the rectangle back on, if necessary.
            if (ppg->m_fDragRectShown)
                ppg->DrawDropTargetRect(NULL, NULL);
            break;

        case WM_HSCROLL:
        case WM_VSCROLL:
            idScroll=(WM_HSCROLL==iMsg) ? SB_HORZ : SB_VERT;

            iPos=GetScrollPos(hWnd, idScroll);
            iTmp=iPos;
            GetScrollRange(hWnd, idScroll, &iMin, &iMax);

            switch (LOWORD(wParam))
                {
                case SB_LINEUP:     iPos -= 20;  break;
                case SB_PAGEUP:     iPos -=100;  break;
                case SB_LINEDOWN:   iPos += 20;  break;
                case SB_PAGEDOWN:   iPos +=100;  break;

                case SB_THUMBPOSITION:
                    iPos=ScrollThumbPosition(wParam, lParam);
                    break;

                //We don't want scrolling on this message.
                case SB_THUMBTRACK:
                    return 0L;
                }

            iPos=max(iMin, min(iPos, iMax));

            if (iPos!=iTmp)
                {
                //Set the new position and scroll the window
                SetScrollPos(hWnd, idScroll, iPos, TRUE);

                if (SB_HORZ==idScroll)
                    {
                    ppg->m_xPos=iPos;
                    ScrollWindow(hWnd, iTmp-iPos, 0, NULL, NULL);
                    }
                else
                    {
                    ppg->m_yPos=iPos;
                    ScrollWindow(hWnd, 0, iTmp-iPos, NULL, NULL);
                    }
                }

            //CHAPTER22MOD
            /*
             * This tells the page to update the rects for the current
             * in-place object.
             */
            if (NULL!=ppg->m_pPageCur)
                ppg->m_pPageCur->ScrolledWindow();
            //End CHAPTER22MOD

            break;

        case WM_RBUTTONDOWN:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnRightDown(wParam
                , LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_LBUTTONDOWN:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftDown(wParam
                , LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_LBUTTONUP:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftUp(wParam
                , LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_LBUTTONDBLCLK:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftDoubleClick(wParam, LOWORD(lParam)
                , HIWORD(lParam));
            break;

        case WM_MOUSEMOVE:
            if (NULL==ppg->m_pPageCur)
                break;

            ppg->m_pPageCur->OnMouseMove(wParam, LOWORD(lParam)
                , HIWORD(lParam));
            break;

        case WM_TIMER:
            if (NULL==ppg->m_pPageCur)
                break;

            ppg->m_pPageCur->OnTimer(wParam);
            break;

        case WM_NCHITTEST:
            if (NULL!=ppg->m_pPageCur)
                {
                /*
                 * This just saves information in the page for
                 * OnSetCursor
                 */
                ppg->m_pPageCur->OnNCHitTest(LOWORD(lParam)
                    , HIWORD(lParam));
                }

            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        case WM_SETCURSOR:
            if (NULL!=ppg->m_pPageCur)
                {
                if (ppg->m_pPageCur->OnSetCursor(LOWORD(lParam)))
                    break;
                }

            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    ppg->m_fDirty |= fDirty;
    return 0L;
    }

/*
 * RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device to logical
 *  coordinates where the hDC defines the logical coordinates.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  hDC             HDC describing the logical coordinate system.
 *                  if NULL, uses a screen DC in MM_LOMETRIC.
 *  fToDevice       BOOL TRUE to convert from uConv to device,
 *                  FALSE to convert device to uConv.
 *
 * Return Value:
 *  None
 */

void RectConvertMappings(LPRECT pRect, HDC hDC, BOOL fToDevice)
    {
    POINT   rgpt[2];
    BOOL    fSysDC=FALSE;

    if (NULL==pRect)
        return;

    rgpt[0].x=pRect->left;
    rgpt[0].y=pRect->top;
    rgpt[1].x=pRect->right;
    rgpt[1].y=pRect->bottom;

    if (NULL==hDC)
        {
        hDC=GetDC(NULL);
        SetMapMode(hDC, MM_LOMETRIC);
        fSysDC=TRUE;
        }

    if (fToDevice)
        LPtoDP(hDC, rgpt, 2);
    else
        DPtoLP(hDC, rgpt, 2);

    if (fSysDC)
        ReleaseDC(NULL, hDC);

    pRect->left=rgpt[0].x;
    pRect->top=rgpt[0].y;
    pRect->right=rgpt[1].x;
    pRect->bottom=rgpt[1].y;

    return;
    }

/*
 * CPages::Draw
 *
 * Purpose:
 *  Paints the current page in the pages window.
 *
 * Parameters:
 *  hDC             HDC to draw on, could be a metafile or printer
 *                  DC or any other type of DC.
 *  fNoColor        BOOL indicating if we should use screen colors
 *                  or printer colors (B&W).  Objects are printed
 *                  as-is, however.  This is TRUE for printer DCs
 *                  or print preview.
 *  fPrinter        BOOL indicating if this is a printer DC in which
 *                  case we eliminate some of the fancy drawing,
 *                  like shadows on the page and so forth.
 *
 * Return Value:
 *  None
 */

void CPages::Draw(HDC hDC, BOOL fNoColor, BOOL fPrinter)
    {
    RECT            rc, rcT;
    UINT            uMM;
    HPEN            hPen;
    HBRUSH          hBrush;
    HGDIOBJ         hObj1, hObj2;
    COLORREF        cr;
    TCHAR           szTemp[20];
    UINT            cch;
    SIZE            sz;
    PCPage          pPage;
    RECT            rcPos;

    //Make sure the DC is in LOMETRIC
    uMM=SetMapMode(hDC, MM_LOMETRIC);

    if (!fPrinter)
        {
        /*
         * We maintain a 6mm border around the page on the screen
         * besides 12.7mm margins.  We also have to account for
         * the scroll position with m_*Pos which are in pixels so
         * we have to convert them.
         */

        SetRect(&rcPos, m_xPos, m_yPos, 0, 0);
        RectConvertMappings(&rcPos, hDC, FALSE);

        rc.left  = LOMETRIC_BORDER-rcPos.left;
        rc.top   =-LOMETRIC_BORDER-rcPos.top;
        }
    else
        {
        /*
         * We define the corner of the printed paper at a negative
         * offset so rc.right and rc.bottom come out right below.
         */
        SetRect(&rc, -(int)m_xMarginLeft, m_yMarginTop, 0, 0);
        }

    rc.right=rc.left+m_cx+(m_xMarginLeft+m_xMarginRight);
    rc.bottom=rc.top-m_cy-(m_yMarginTop+m_yMarginBottom);

    //Draw a rect filled with the window color to show the page.
    if (!fPrinter)
        {
        if (fNoColor)
            {
            //Black frame, white box for printed colors.
            hPen  =CreatePen(PS_SOLID, 0, RGB(0,0,0));
            hBrush=CreateSolidBrush(RGB(255, 255, 255));
            }
        else
            {
            //Normal colors on display
            hPen=CreatePen(PS_SOLID, 0
                , GetSysColor(COLOR_WINDOWFRAME));
            hBrush=CreateSolidBrush(GetSysColor(COLOR_WINDOW));
            }

        hObj1=SelectObject(hDC, hPen);
        hObj2=SelectObject(hDC, hBrush);

        //Paper boundary
        Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom+1);

        /*
         * Draw a shadow on the *visual* bottom and right edges
         * .5mm wide.  If the button shadow color and workspace
         * colors match, then use black.  We always use black
         * when printing as well.
         */
        if (fNoColor)
            cr=RGB(0,0,0);
        else
            {
            cr=GetSysColor(COLOR_BTNSHADOW);

            if (GetSysColor(COLOR_APPWORKSPACE)==cr)
                cr=RGB(0,0,0);
            }

        cr=SetBkColor(hDC, cr);
        SetRect(&rcT, rc.left+5, rc.bottom, rc.right+5,rc.bottom-5);
        ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rcT, NULL, 0, NULL);

        SetRect(&rcT, rc.right, rc.top-5, rc.right+5, rc.bottom-5);
        ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rcT, NULL, 0, NULL);
        SetBkColor(hDC, cr);

        SelectObject(hDC, hObj1);
        SelectObject(hDC, hObj2);
        DeleteObject(hBrush);
        DeleteObject(hPen);
        }

    //Write the page number in the lower left corner
    if (!fNoColor)
        {
        SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));
        SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
        }

    //Write the page number in our page font.
    cch=wsprintf(szTemp, TEXT("Page %d"), m_iPageCur+1);

    hObj1=SelectObject(hDC, m_hFont);
    GetTextExtentPoint(hDC, szTemp, cch, &sz);

    TextOut(hDC, rc.left+m_xMarginLeft
        , rc.bottom+m_yMarginBottom+sz.cy, szTemp, cch);

    SelectObject(hDC, hObj1);

    //Rectangle to show border.
    MoveToEx(hDC, rc.left+m_xMarginLeft, rc.top-m_yMarginTop, NULL);
    LineTo(hDC, rc.left+m_xMarginLeft,   rc.bottom+m_yMarginBottom);
    LineTo(hDC, rc.right-m_xMarginRight, rc.bottom+m_yMarginBottom);
    LineTo(hDC, rc.right-m_xMarginRight, rc.top-m_yMarginTop);
    LineTo(hDC, rc.left+m_xMarginLeft,   rc.top-m_yMarginTop);

    /*
     * Go draw the objects on this page.  If the page is not open,
     * we open it anyway.  If it is already open, then opening again
     * will bump it's reference count, so the Close in ineffectual.
     */
    if (PageGet(m_iPageCur, &pPage, TRUE))
        {
        if (!fPrinter)
            {
            pPage->Draw(hDC, rcPos.left, rcPos.top, fNoColor
                , fPrinter);
            }
        else
            pPage->Draw(hDC, 0, 0, fNoColor, fPrinter);

        pPage->Close(FALSE);
        }

    SetMapMode(hDC, uMM);
    return;
    }

/*
 * CPages::UpdateScrollRanges
 *
 * Purpose:
 *  Reset scrollbar ranges (horizontal and vertical) depending on
 *  the window size and the page size.  This function may remove
 *  the scrollbars altogether.
 *
 * Parameters:
 *  None, but set m_cx, m_cy and size m_hWnd before calling.
 *
 * Return Value:
 *  None
 */

void CPages::UpdateScrollRanges(void)
    {
    UINT        cxSB;   //Scrollbar width and height.
    UINT        cySB;
    UINT        cx, cy;
    UINT        dx, dy;
    UINT        u;
    int         iMin, iMax;
    RECT        rc;
    BOOL        fHScroll;
    BOOL        fVScroll;
    BOOL        fWasThere;

    GetClientRect(m_hWnd, &rc);

    cx=rc.right-rc.left;
    cy=rc.bottom-rc.top;

    //Convert dimensions of the image in LOMETRIC to pixels.
    SetRect(&rc, (m_cx+m_xMarginLeft+m_xMarginRight
        +LOMETRIC_BORDER*2), (m_cy+m_yMarginTop
        +m_yMarginBottom+LOMETRIC_BORDER*2), 0, 0);

    RectConvertMappings(&rc, NULL, TRUE);

    dx=rc.left;
    dy=-rc.top;

    //Assume that both scrollbars will be visible.
    fHScroll=TRUE;
    fVScroll=TRUE;

    /*
     * Determine:
     *  1)  Which scrollbars are needed.
     *  2)  How many divisions to give scrollbars so as to
     *      only scroll as little as necessary.
     */

    //Scrollbar dimensions in our units.
    cxSB=GetSystemMetrics(SM_CXVSCROLL);
    cySB=GetSystemMetrics(SM_CYHSCROLL);

    //Remove horizontal scroll if window >= cxPage+borders
    if (cx >= dx)
        fHScroll=FALSE;

    /*
     * If we still need a horizontal scroll, see if we need a
     * vertical taking the height of the horizontal scroll into
     * account.
     */

    u=fHScroll ? cySB : 0;

    if ((cy-u) >= dy)
        fVScroll=FALSE;

    //Check if adding vert scrollbar necessitates a horz now.
    u=fVScroll ? cxSB : 0;
    fHScroll=((cx-u) < dx);

    /*
     * Modify cx,cy to reflect the new client area before scaling
     * scrollbars.  We only affect the client size if there is a
     * *change* in scrollbar status:  if the scrollbar was there
     * but is no longer, then add to the client size; if it was
     * not there but now is, then subtract.
     */

    //Change cx depending on vertical scrollbar change
    GetScrollRange(m_hWnd, SB_VERT, &iMin, &iMax);
    fWasThere=(0!=iMin || 0!=iMax);

    if (fWasThere && !fVScroll)
        cx+=cxSB;

    if (!fWasThere && fVScroll)
        cx-=cxSB;

    //Change cy depending on horizontal scrollbar change
    GetScrollRange(m_hWnd, SB_HORZ, &iMin, &iMax);
    fWasThere=(0!=iMin || 0!=iMax);

    if (fWasThere && !fHScroll)
        cy+=cySB;

    if (!fWasThere && fHScroll)
        cy-=cySB;

    /*
     * Show/Hide the scrollbars if necessary and set the ranges.
     * The range is the number of units of the page we cannot see.
     */
    if (fHScroll)
        {
        //Convert current scroll position to new range.
        u=GetScrollPos(m_hWnd, SB_HORZ);

        if (0!=u)
            {
            GetScrollRange(m_hWnd, SB_HORZ, &iMin, &iMax);
            u=MulDiv(u, (dx-cx), (iMax-iMin));
            }

        SetScrollRange(m_hWnd, SB_HORZ, 0, dx-cx, FALSE);
        SetScrollPos(m_hWnd, SB_HORZ, u, TRUE);
        m_xPos=u;
        }
    else
        {
        SetScrollRange(m_hWnd, SB_HORZ, 0, 0, TRUE);
        m_xPos=0;
        }

    if (fVScroll)
        {
        //Convert current scroll position to new range.
        u=GetScrollPos(m_hWnd, SB_VERT);

        if (0!=u)
            {
            GetScrollRange(m_hWnd, SB_VERT, &iMin, &iMax);
            u=MulDiv(u, (dy-cy), (iMax-iMin));
            }

        SetScrollRange(m_hWnd, SB_VERT, 0, dy-cy, FALSE);
        SetScrollPos(m_hWnd, SB_VERT, u, TRUE);

        m_yPos=u;
        }
    else
        {
        SetScrollRange(m_hWnd, SB_VERT, 0, 0, TRUE);
        m_yPos=0;
        }

    //Repaint to insure that changes to m_x/yPos are reflected
    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }


PATRON.CPP

/*
 * PATRON.CPP
 * Patron Chapter 22
 *
 * WinMain which is all we need for the basic application.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "patron.h"

ULONG       g_cObj=0;
ULONG       g_cLock=0;
HWND        g_hWnd=NULL;
BOOL        g_fUser=TRUE;

//CHAPTER22MOD
/*
 * The in-place site needs to have access to the frame and its
 * IOleInPlaceFrame interface.  A global, pardon me, is the simplest
 * way to achieve this.
 */

PCPatronFrame   g_pFR;
//End CHAPTER22MOD

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCPatronFrame   pFR;
    FRAMEINIT       fi;
    WPARAM          wRet=0;

    SETMESSAGEQUEUE;

    //Attempt to allocate and initialize the application
    pFR=new CPatronFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return -1;

    //CHAPTER22MOD
    g_pFR=pFR;
    //End CHAPTER22MOD

    fi.idsMin=IDS_FRAMEMIN;
    fi.idsMax=IDS_FRAMEMAX;
    fi.idsStatMin=IDS_STATMESSAGEMIN;
    fi.idsStatMax=IDS_STATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=CW_USEDEFAULT;
    fi.cy=CW_USEDEFAULT;

    //If we can initialize pFR, start chugging messages
    if (pFR->Init(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Patron Document object to call when it gets
 *  destroyed.  We destroy the main window if the proper conditions
 *  are met for shutdown.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;

    //No more objects, no locks, no user control, shut the app down.
    if (0==g_cObj && 0==g_cLock && IsWindow(g_hWnd) && !g_fUser)
        PostMessage(g_hWnd, WM_CLOSE, 0, 0L);

    return;
    }

/*
 * CPatronFrame::CPatronFrame
 * CPatronFrame::~CPatronFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WInMain
 */

CPatronFrame::CPatronFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    : CFrame(hInst, hInstPrev, pszCmdLine, nCmdShow)
    {
    m_fInitialized=FALSE;
    m_pIClassDataTran=NULL;

    m_pDocCreated=NULL;
    m_fEmbedding=FALSE;

    m_dwRegCO=0;
    m_pIClassFactory=NULL;

    //CHAPTER22MOD
    m_cRef=0;
    m_hAccelIP=NULL;
    m_hWndObj=NULL;
    m_hMenuOrg=NULL;
    m_hMenuTop=NULL;
    m_fOurToolsShowing=TRUE;
    m_fInContextHelp=FALSE;
    m_pIOleIPActiveObject=NULL;
    //End CHAPTER22MOD

    return;
    }

CPatronFrame::~CPatronFrame(void)
    {
    //Opposite of CoRegisterClassObject, takes class factory ref to 1
    if (0L!=m_dwRegCO)
        CoRevokeClassObject(m_dwRegCO);

    //This should be the last Release, which frees the class factory.
    ReleaseInterface(m_pIClassFactory);

    if (NULL!=m_pIClassDataTran)
        {
        m_pIClassDataTran->LockServer(FALSE);
        m_pIClassDataTran->Release();
        }

    OleFlushClipboard();

    if (m_fInitialized)
        OleUninitialize();

    return;
    }

/*
 * CPatronFrame::Init
 *
 * Purpose:
 *  Call OleInitialize then calling down into the base class
 *  initialization.
 *
 * Parameters:
 *  pFI             PFRAMEINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CPatronFrame::Init(PFRAMEINIT pFI)
    {
    HRESULT     hr;
    BOOL        fRet;

    CHECKVER_OLE;

    if (FAILED(OleInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    //Lock the data transfer object factory as an optimization.
    hr=CoGetClassObject(CLSID_DataTransferObject
        , CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory
        , (PPVOID)&m_pIClassDataTran);

    if (SUCCEEDED(hr))
        m_pIClassDataTran->LockServer(TRUE);

    //Check for command line flags
    ParseCommandLine();

    if (NULL!=m_ppszCmdArgs)
        {
        if(0==lstrcmpi(m_ppszCmdArgs[0], TEXT("-Embedding"))
           || 0==lstrcmpi(m_ppszCmdArgs[0], TEXT("/Embedding")))
            m_fEmbedding=TRUE;
        }

    g_fUser=!m_fEmbedding;

    if (m_fEmbedding)
        {
        HRESULT     hr;

        m_pIClassFactory=new CLinkClassFactory(this);

        if (NULL==m_pIClassFactory)
            return FALSE;

        //Since we hold on to this, we should AddRef it.
        m_pIClassFactory->AddRef();

        hr=CoRegisterClassObject(CLSID_PatronPages, m_pIClassFactory
            , CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE, &m_dwRegCO);

        if (FAILED(hr))
            return FALSE;
        }

    //CHAPTER22MOD
    //Load in-place accelerators
    m_hAccelIP=LoadAccelerators(m_hInst
        , MAKEINTRESOURCE(IDR_INPLACEACCELERATORS));

    if (NULL==m_hAccelIP)
        return FALSE;

    fRet=CFrame::Init(pFI);

    m_hMenuOrg=GetMenu(m_hWnd);
    return fRet;
    //End CHAPTER22MOD
    }

/*
 * CPatronFrame::CreateCClient
 *
 * Purpose:
 *  Constructs a new client specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient        Pointer to the new client object.
 */

PCClient CPatronFrame::CreateCClient(void)
    {
    return (PCClient)(new CPatronClient(m_hInst, this));
    }

/*
 * CPatronFrame::RegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CPatronFrame::RegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //First let the standard frame do its thing
    if (!CFrame::RegisterAllClasses())
        return FALSE;

    //We need double-clicks now and for object activation.
    wc.style         = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
    wc.hInstance     = m_hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PagesWndProc;
    wc.cbWndExtra    = CBPAGESWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPAGES;

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

    return TRUE;
    }

/*
 * CPatronFrame::PreShowInit
 *
 * Purpose:
 *  Called from Init before intially showing the window.  We do
 *  whatever else we want here, modifying m_nCmdShow as necessary
 *  which affects ShowWindow in Init.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if this successful, FALSE otherwise.
 */

BOOL CPatronFrame::PreShowInit(void)
    {
    //Base class does nothing
    CFrame::PreShowInit();

    //Save the window handle for shutdown if necessary.
    g_hWnd=m_hWnd;

    //If we're -Embedding, don't show the window initially.
    if (m_fEmbedding)
        m_nCmdShow=SW_HIDE;

    return TRUE;
    }

/*
 * CPatronFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the Patron frame window that processes
 *  extra File menu items as well as the Page menu.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CPatronFrame::OnCommand(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PCPatronDoc     pDoc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many toolbar notifications.
     */
    if (m_fInit)
        return 0L;

    pDoc=(PCPatronDoc)m_pCL->ActiveDocument();

    //CHAPTER22MOD
    //Also check for the open command now too.
    if (NULL!=pDoc && ((IDM_VERBMIN <= wID) && (IDM_VERBMAX >= wID)
        || IDM_OPENOBJECT==wID))
        {
        MSG     msg;
        DWORD   dw;
        LONG    iVerb=(long)(wID-IDM_VERBMIN);

        //Include a message for in-place objects.
        msg.hwnd=NULL;
        msg.message=WM_COMMAND;
        msg.wParam=wParam;
        msg.lParam=lParam;
        msg.time=GetMessageTime();

        dw=GetMessagePos();
        SETPOINT(msg.pt, LOWORD(dw), HIWORD(dw));

        if (IDM_OPENOBJECT==wID)
            iVerb=OLEIVERB_OPEN;

        pDoc->ActivateObject(iVerb, &msg);
        return 0L;
        }
    //End CHAPTER22MOD

    switch (wID)
        {
        case IDM_FILEPRINT:
            pDoc->Print(m_hWnd);
            return 0L;

        case IDM_FILEPRINTERSETUP:
            pDoc->PrinterSetup(m_hWnd, FALSE);
            return 0L;

        case IDM_EDITPASTESPECIAL:
            pDoc->PasteSpecial(m_hWnd);
            return 0L;

        case IDM_EDITDELETEOBJECT:
            pDoc->Delete();
            return 0L;

        case IDM_EDITINSERTOBJECT:
            pDoc->InsertObject(m_hWnd);
            return 0L;

        case IDM_EDITCONVERT:
            pDoc->ConvertObject(m_hWnd);
            return 0L;

        case IDM_EDITLINKS:
            pDoc->EditLinks(m_hWnd);
            return 0L;

        case IDM_PAGENEWPAGE:
            pDoc->NewPage();
            break;

        case IDM_PAGEDELETEPAGE:
            pDoc->DeletePage();
            break;

        case IDM_PAGENEXTPAGE:
            pDoc->NextPage();
            break;

        case IDM_PAGEPREVIOUSPAGE:
            pDoc->PreviousPage();
            break;

        case IDM_PAGEFIRSTPAGE:
            pDoc->FirstPage();
            break;

        case IDM_PAGELASTPAGE:
            pDoc->LastPage();
            break;

        case IDM_PAGESHOWOBJECTS:
            {
            BOOL    fTemp;

            //First get the current state, then toggle it.
            fTemp=pDoc->ShowOrQueryObjectTypes(TRUE, FALSE);
            pDoc->ShowOrQueryObjectTypes(FALSE, !fTemp);
            }
            break;

        //CHAPTER22MOD
        case IDM_ENTERCONTEXTHELP:
        case IDM_ESCAPECONTEXTHELP:
            //Notify the object on entry and exit.
            ContextSensitiveHelp(IDM_ENTERCONTEXTHELP==wID);
            break;
        //End CHAPTER22MOD

        default:
           return CFrame::OnCommand(hWnd, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPatronFrame::CreateToolbar
 *
 * Purpose:
 *  Procedure to create all the necessary toolbar buttons.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of tools added to the bar.
 */

UINT CPatronFrame::CreateToolbar(void)
    {
    UINT            iLast;
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;

    //Insert the standard ones.
    iLast=CFrame::CreateToolbar();

    //Remove Undo:  we don't use it.
    m_pTB->Remove(IDM_EDITUNDO);

    /*
     * Insert Print File Import in the 5th position and account
     * for it in iLast.
     */
    m_pTB->Add(utCmd, 4, IDM_FILEPRINT, m_dxB, m_dyB
        , NULL, NULL, 6, uState);

    iLast++;

    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //Add New Page, and Delete Page
    m_pTB->Add(utCmd, iLast++, IDM_PAGENEWPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 2, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGEDELETEPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 3, uState);

    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //First, Prev, Next, Last pages.
    m_pTB->Add(utCmd, iLast++, IDM_PAGEFIRSTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 4, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGEPREVIOUSPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 5, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGENEXTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 6, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGELASTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 7, uState);

    return iLast;
    }

/*
 * CPatronFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CPatronFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    PCPatronDoc     pDoc;
    BOOL            fOK=FALSE;
    BOOL            fCallDefault=TRUE;
    UINT            uTemp;
    UINT            uTempE;
    UINT            uTempD;

    pDoc=(PCPatronDoc)m_pCL->ActiveDocument();

    uTempE=MF_ENABLED | MF_BYCOMMAND;
    uTempD=MF_DISABLED | MF_GRAYED | MF_BYCOMMAND;
    uTemp=((NULL!=pDoc) ? uTempE : uTempD);

    if (m_phMenu[0]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_FILEPRINT, uTemp);

        if (NULL!=pDoc)
            fOK=pDoc->FQueryPrinterSetup();

        EnableMenuItem(hMenu, IDM_FILEPRINTERSETUP
            , (fOK) ? uTempE : uTempD);
        }

    if (m_phMenu[1]==hMenu)
        {
        if (NULL!=pDoc)
            fOK=pDoc->FQueryPaste();

        EnableMenuItem(hMenu, IDM_EDITPASTE
            , (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITPASTESPECIAL
            , (fOK) ? uTempE : uTempD);

        //Cut, Copy, Delete depends on there being a selection.
        if (NULL!=pDoc)
            fOK=pDoc->FQueryObjectSelected(hMenu);
        else
            fOK=FALSE;

        EnableMenuItem(hMenu, IDM_EDITCUT, (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITCOPY
            , (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITDELETEOBJECT
            , (fOK) ? uTempE : uTempD);

        EnableMenuItem(hMenu, IDM_EDITINSERTOBJECT, uTemp);

        if (NULL!=pDoc)
            fOK=pDoc->FQueryEnableEditLinks();
        else
            fOK=FALSE;

        EnableMenuItem(hMenu, IDM_EDITLINKS
            , (fOK) ? uTempE : uTempD);

        //We did the whole menu...
        fCallDefault=FALSE;
        }

    //Page menu
    if (m_phMenu[2]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_PAGENEWPAGE,      uTemp);
        EnableMenuItem(hMenu, IDM_PAGEDELETEPAGE,   uTemp);
        EnableMenuItem(hMenu, IDM_PAGENEXTPAGE,     uTemp);
        EnableMenuItem(hMenu, IDM_PAGEPREVIOUSPAGE, uTemp);
        EnableMenuItem(hMenu, IDM_PAGEFIRSTPAGE,    uTemp);
        EnableMenuItem(hMenu, IDM_PAGELASTPAGE,     uTemp);

        //Check the Show Objects command or not.
        if (NULL!=pDoc)
            fOK=pDoc->ShowOrQueryObjectTypes(TRUE, FALSE);
        else
            fOK=FALSE;

        CheckMenuItem(hMenu, IDM_PAGESHOWOBJECTS, MF_BYCOMMAND
            | ((fOK) ? MF_CHECKED : MF_UNCHECKED));
        EnableMenuItem(hMenu, IDM_PAGESHOWOBJECTS, uTemp);
        }

    if (fCallDefault)
        CFrame::UpdateMenus(hMenu, iMenu);

    return;
    }

/*
 * CPatronFrame::UpdateToolbar
 *
 * Purpose:
 *  Enables and disables tools depending on whether we have
 *  a document or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronFrame::UpdateToolbar(void)
    {
    PCDocument  pDoc;
    BOOL        fEnable;

    //Let the default hack on its tools.
    CFrame::UpdateToolbar();

    pDoc=m_pCL->ActiveDocument();
    fEnable=(NULL!=pDoc);

    //No document, disable just about everything
    m_pTB->Enable(IDM_FILEPRINT,        fEnable);
    m_pTB->Enable(IDM_FILEPRINTERSETUP, fEnable);

    m_pTB->Enable(IDM_PAGENEWPAGE,      fEnable);
    m_pTB->Enable(IDM_PAGEDELETEPAGE,   fEnable);
    m_pTB->Enable(IDM_PAGEFIRSTPAGE,    fEnable);
    m_pTB->Enable(IDM_PAGEPREVIOUSPAGE, fEnable);
    m_pTB->Enable(IDM_PAGENEXTPAGE,     fEnable);
    m_pTB->Enable(IDM_PAGELASTPAGE,     fEnable);

    return;
    }

//CHAPTER22MOD
/*
 * CPatronFrame::MessageLoop
 *
 * Purpose:
 *  Override of standard message loop function in CLASSLIB to use
 *  in-place accelerators when necessary.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  WPARAM          Contents of msg.wParam from WM_QUIT.
 */

WPARAM CPatronFrame::MessageLoop(void)
    {
    MSG     msg;

    while (GetMessage(&msg, NULL, 0,0 ))
        {
        HACCEL      hAccel=m_hAccel;

        //Always give the object first crack at translation.
        if (NULL!=m_pIOleIPActiveObject)
            {
            HRESULT     hr;

            hAccel=m_hAccelIP;
            hr=m_pIOleIPActiveObject->TranslateAccelerator(&msg);

            //If the object translated the accelerator, we're done
            if (NOERROR==hr)
                continue;
            }

        if (!m_pCL->TranslateAccelerator(&msg))
            {
            //hAccel is either the normal ones or the in-place ones.

            if (!::TranslateAccelerator(m_hWnd, hAccel, &msg))
                {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
                }
            }
        }

    return msg.wParam;
    }
//End CHAPTER22MOD

/*
 * CPatronFrame::FMessageHook
 *
 * Purpose:
 *  Override of CFrame::FMessageHook so we can specifically trap
 *  WM_MENUSELECT messages for the Object verb menu to provide some
 *  meaningful information on the status strip.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return value
 *                  for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CPatronFrame::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    BOOL        fRet=FALSE;
    //CHAPTER22MOD
    int         dx, dy;
    //End CHAPTER22MOD
    MENUSELECTPARAMS(wItem, wMenuFlags, hMenu);

    *pLRes=0;

    //CHAPTER22MOD
    /*
     * We use a switch now because we previously only processed
     * WM_MENUSELECT which did not use a case statement.
     */

    switch (iMsg)
        {
        case WM_SIZE:
            if (NULL!=m_pIOleIPActiveObject)
                {
                RECT        rc;

                GetClientRect(m_hWnd, &rc);
                m_pIOleIPActiveObject->ResizeBorder(&rc
                    , this, TRUE);
                }

            /*
             * If we're not showing our tools, don't let CLASSLIB
             * resize the client, instead doing it ourselves.
             */
            if (m_fOurToolsShowing)
                return FALSE;

            m_fSizing=TRUE;
            dx=LOWORD(lParam);
            dy=HIWORD(lParam);

            //Change the Toolbar and StatStrip widths to match
            m_pTB->OnSize(hWnd);
            m_pSL->OnSize(hWnd);

            //Adjust the client properly, remembering the StatStrip.
            m_pCL->OnSize(m_cxLeft, m_cyTop, dx-m_cxLeft-m_cxRight
                , dy-m_cyTop-m_cyBottom-CYSTATSTRIP);

            m_fSizing=FALSE;
            return TRUE;

        case WM_ACTIVATEAPP:
            if (NULL!=m_pIOleIPActiveObject)
                {
                m_pIOleIPActiveObject->OnFrameWindowActivate
                    ((BOOL)wParam);
                }

            return FALSE;

        case WM_SETFOCUS:
            if (NULL!=m_pIOleIPActiveObject)
                {
                HWND    hWndObj;

                m_pIOleIPActiveObject->GetWindow(&hWndObj);
                SetFocus(hWndObj);
                }

            return TRUE;

        case WM_CLOSE:
            if (NULL!=m_pDocCreated)
                {
                if (m_pDocCreated->FDirtyGet())
                    {
                    CHourglass  wait;
                    m_pDocCreated->Save(0, NULL);
                    }
                }

            return FALSE;

        case WM_MENUSELECT:
            break;      //Continue processing below.

        default:
            return FALSE;
        }
    //End CHAPTER22MOD

    //This happens when there's no menu selection.
    if (-1==wMenuFlags)
        return FALSE;

    if (MF_POPUP & wMenuFlags)
        {
        /*
         * If this is the cascade verb menu itself, display the same
         * message.  m_phMenu[1] contains the current edit menu
         * handle.
         */
        if (0!=wItem)
            {
            fRet=((HMENU)wItem==GetSubMenu(m_phMenu[1]
                , MENUPOS_OBJECT));
            }
        }
    else
        {
        /*
         * If the ID is in the verb range, use
         * IDS_ITEMMESSAGEEDITOBJECT message
         */
        fRet=(IDM_VERBMIN <= wItem && IDM_VERBMAX >= wItem);
        }

    if (fRet)
        m_pSL->MessageDisplay(IDM_EDITOBJECT);

    return fRet;
    }

//CHAPTER22MOD

/*
 * CPatronFrame::ShowUIAndTools
 *
 * Purpose:
 *  Installs or removes the in-place user interface which includes
 *  the menus and the tools.  The tools may be affected outside of
 *  the menu.
 *
 * Parameters:
 *  fShow           BOOL indicating to show (TRUE) or hide (FALSE)
 *  fMenu           BOOl indicating if the menu is also affected.
 *
 * Return Value:
 *  None
 */

void CPatronFrame::ShowUIAndTools(BOOL fShow, BOOL fMenu)
    {
    HWND    hWndTB;

    //This is the only menu case...restore our original menu
    if (fMenu && fShow)
        SetMenu(NULL, NULL, NULL);

    /*
     * If we're trying to hide our tools but the object didn't
     * want any space, then just leave our tools up and active.
     */

    hWndTB=m_pTB->Window();
    ShowWindow(hWndTB, fShow ? SW_SHOW : SW_HIDE);

    if (fShow)
        {
        InvalidateRect(hWndTB, NULL, TRUE);
        UpdateWindow(hWndTB);
        }

    m_fOurToolsShowing=fShow;
    return;
    }

/*
 * CPatronFrame::ReinstateUI
 *
 * Purpose:
 *  Reinstalls the application's normal toolbar and menu and
 *  readjusts the position of the client window and the documents
 *  within it.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronFrame::ReinstateUI(void)
    {
    BORDERWIDTHS    bw;

    ShowUIAndTools(TRUE, TRUE);

    SetRect((LPRECT)&bw, 0, m_cyBar, 0, 0);
    SetBorderSpace(&bw);
    return;
    }

/*
 * CPatronFrame::QueryInterface
 * CPatronFrame::AddRef
 * CPatronFrame::Release
 */

STDMETHODIMP CPatronFrame::QueryInterface(REFIID riid, PPVOID ppv)
    {
    //We only know IUnknown and IOleInPlaceFrame
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IOleInPlaceUIWindow==riid
        || IID_IOleWindow==riid || IID_IOleInPlaceFrame==riid)
        *ppv=(LPOLEINPLACEFRAME)this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CPatronFrame::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPatronFrame::Release(void)
    {
    /*
     * We don't do anything with this since we're not controlled
     * by a reference count as far as in-place stuff is concerned.
     */
    return --m_cRef;
    }

/*
 * CPatronFrame::GetWindow
 *
 * Purpose:
 *  Retrieves the handle of the window associated with the object
 *  on which this interface is implemented.
 *
 * Parameters:
 *  phWnd           HWND * in which to store the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_FAIL if there is no
 *                  window.
 */

STDMETHODIMP CPatronFrame::GetWindow(HWND *phWnd)
    {
    *phWnd=m_hWnd;
    return NOERROR;
    }

/*
 * CPatronFrame::ContextSensitiveHelp
 *
 * Purpose:
 *  Instructs the object on which this interface is implemented to
 *  enter or leave a context-sensitive help mode.
 *
 * Parameters:
 *  fEnterMode      BOOL TRUE to enter the mode, FALSE otherwise.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CPatronFrame::ContextSensitiveHelp(BOOL fEnterMode)
    {
    /*
     * Don't bother if there is no active object since we don't do
     * context help on our own.
     */
    if (NULL==m_pIOleIPActiveObject)
        return NOERROR;

    /*
     * If the state changes, an MDI frame should call the same
     * function in all documents.  An SDI frame should just call
     * the active object, if it has one.
     */

    if (m_fInContextHelp!=fEnterMode)
        {
        m_fInContextHelp=fEnterMode;

       #ifdef MDI
        ((PCPatronClient)m_pCL)->CallContextHelpOnDocuments
           (fEnterMode);
       #else
        m_pIOleIPActiveObject->ContextSensitiveHelp(fEnterMode);
       #endif
        }

    return NOERROR;
    }

/*
 * CPatronFrame::GetBorder
 *
 * Purpose:
 *  Returns the rectangle in which the container is willing to
 *  negotiate about an object's adornments.
 *
 * Parameters:
 *  prcBorder       LPRECT in which to store the rectangle.
 *
 * Return Value:
 *  HRESULT         NOERROR if all is well, INPLACE_E_NOTOOLSPACE
 *                  if there is no negotiable space.
 */

STDMETHODIMP CPatronFrame::GetBorder(LPRECT prcBorder)
    {
    if (NULL==prcBorder)
        return ResultFromScode(E_INVALIDARG);

    /*
     * We return all the client area space sans the StatStrip,
     * which we control
     */
    GetClientRect(m_hWnd, prcBorder);
    prcBorder->bottom-=CYSTATSTRIP;

    return NOERROR;
    }

/*
 * CPatronFrame::RequestBorderSpace
 *
 * Purpose:
 *  Asks the container if it can surrender the amount of space
 *  in pBW that the object would like for it's adornments.  The
 *  container does nothing but validate the spaces on this call.
 *
 * Parameters:
 *  pBW             LPCBORDERWIDTHS containing the requested space.
 *                  The values are the amount of space requested
 *                  from each side of the relevant window.
 *
 * Return Value:
 *  HRESULT         NOERROR if we can give up space,
 *                  INPLACE_E_NOTOOLSPACE otherwise.
 */

STDMETHODIMP CPatronFrame::RequestBorderSpace(LPCBORDERWIDTHS pBW)
    {
    //Everything is fine with us, so always return an OK.
    return NOERROR;
    }

/*
 * CPatronFrame::SetBorderSpace
 *
 * Purpose:
 *  Called when the object now officially requests that the
 *  container surrender border space it previously allowed
 *  in RequestBorderSpace.  The container should resize windows
 *  appropriately to surrender this space.
 *
 * Parameters:
 *  pBW             LPCBORDERWIDTHS containing the amount of space
 *                  from each side of the relevant window that the
 *                  object is now reserving.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CPatronFrame::SetBorderSpace(LPCBORDERWIDTHS pBW)
    {
    RECT            rc;
    POINT           pt1, pt2;
    PCPatronDoc     pDoc;

    pDoc=(PCPatronDoc)m_pCL->ActiveDocument();

    /*
     * If pBW is NULL, the object is not interested in tools, so we
     * can just leave ours up if we want.
     */

    if (NULL==pBW)
        {

if (NULL!=pDoc)
            {
            pDoc->NoObjectFrameTools(TRUE, TRUE);

            /*
             * In some cases IOleInPlaceSite::OnUIActivate might
             * have been called before SetBorderSpace, so we might
             * have already hidden our tools (OnUIActivate calls
             * pDoc->NoObjectFrameTools before we set it to TRUE
             * here).  So we have to insure they are visible now
             * by a call to ShowUIAndTools.
             */
            if (!m_fOurToolsShowing)
                ShowUIAndTools(TRUE, FALSE);
            }

        return NOERROR;
        }

    if (NULL!=pDoc)
        pDoc->NoObjectFrameTools(FALSE, TRUE);

    /*
     * This tells CFrame::FMessageHook (WM_SIZE) how much space
     * to reserve off the sides when resizing the client when
     * the frame is resized.
     */

    m_cyTop   =pBW->top;
    m_cyBottom=pBW->bottom;
    m_cxLeft  =pBW->left;
    m_cxRight =pBW->right;

    //Get the current offset of the client
    GetWindowRect(m_pCL->Window(), &rc);
    SETPOINT(pt1, rc.left, rc.top);
    SETPOINT(pt2, rc.right, rc.bottom);
    ScreenToClient(m_hWnd, &pt1);
    ScreenToClient(m_hWnd, &pt2);

    /*
     * Now move the client, keeping documents steady. pBW->left-pt.x
     * and pBW->top-pt.y are the deltas for the documents.
     */

    GetClientRect(m_hWnd, &rc);
    rc.left+=pBW->left;
    rc.right-=pBW->right;
    rc.top+=pBW->top;
    rc.bottom-=pBW->bottom+CYSTATSTRIP; //Remember the status line

    //Only bother the client if necessary.
    if (!(pt1.x==rc.left && pt1.y==rc.top
        && pt2.x==rc.right && pt2.y==rc.bottom))
        {
        ((PCPatronClient)m_pCL)->MoveWithoutFamily(&rc
            , pBW->left-pt1.x, pBW->top-pt1.y);
        }

    return NOERROR;
    }

/*
 * CPatronFrame::SetActiveObject
 *
 * Purpose:
 *  Provides the container with the object's IOleInPlaceActiveObject
 *  pointer
 *
 * Parameters:
 *  pIIPActiveObj   LPOLEINPLACEACTIVEOBJECT of interest.
 *  pszObj          LPCOLESTR naming the object.  Not used.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CPatronFrame::SetActiveObject
    (LPOLEINPLACEACTIVEOBJECT pIIPActiveObj, LPCOLESTR pszObj)
    {
    if (NULL!=m_pIOleIPActiveObject)
        m_pIOleIPActiveObject->Release();

    //NULLs m_pIOleIPActiveObject if pIIPActiveObj is NULL
    m_pIOleIPActiveObject=pIIPActiveObj;

    if (NULL!=m_pIOleIPActiveObject)
        m_pIOleIPActiveObject->AddRef();

    return NOERROR;
    }

/*
 * CPatronFrame::InsertMenus
 *
 * Purpose:
 *  Instructs the container to place its in-place menu items where
 *  necessary in the given menu and to fill in elements 0, 2, and 4
 *  of the OLEMENUGROUPWIDTHS array to indicate how many top-level
 *  items are in each group.
 *
 * Parameters:
 *  hMenu           HMENU in which to add popups.
 *  pMGW            LPOLEMENUGROUPWIDTHS in which to store the
 *                  width of each container menu group.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CPatronFrame::InsertMenus(HMENU hMenu
    , LPOLEMENUGROUPWIDTHS pMGW)
    {
    /*
     * Here Patron needs to copy it's File, Page, and Window menu
     * items into the object-supplied menu, meaning that we have
     * three menu groups.  The actual handles of these popup menus
     * are already in CPatronFrame::m_phMenu where File is element
     * 0 and Page is element 2, and in m_hMenuWindow which is the
     * Window menu.  The latter we do only for MDI, of course.
     */

    InsertMenu(hMenu, 0, MF_BYPOSITION | MF_POPUP, (UINT)m_phMenu[0]
        , PSZ(IDS_FILEMENU));
    InsertMenu(hMenu, 1, MF_BYPOSITION | MF_POPUP, (UINT)m_phMenu[2]
        , PSZ(IDS_PAGEMENU));

    pMGW->width[0]=1;
    pMGW->width[2]=1;

   #ifdef MDI
    InsertMenu(hMenu, 2, MF_BYPOSITION | MF_POPUP
       , (UINT)m_hMenuWindow, PSZ(IDS_WINDOWMENU));

    pMGW->width[4]=1;
   #else
    pMGW->width[4]=0;
   #endif

    return NOERROR;
    }

/*
 * CPatronFrame::SetMenu
 *
 * Purpose:
 *  Instructs the container to replace whatever menu it's currently
 *  using with the given menu and to call OleSetMenuDescritor so OLE
 *  knows to whom to dispatch messages.
 *
 * Parameters:
 *  hMenu           HMENU to show.
 *  hOLEMenu        HOLEMENU to the menu descriptor.
 *  hWndObj         HWND of the active object to which messages are
 *                  dispatched.
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CPatronFrame::SetMenu(HMENU hMenu
    , HOLEMENU hOLEMenu, HWND hWndObj)
    {
    HRESULT         hr;
    PCPatronClient  pCL=(PCPatronClient)m_pCL;

    /*
     * Our responsibilities here are to put the menu on the frame
     * window and call OleSetMenuDescriptor.
     * CPatronClient::SetMenu which we call here takes care of
     * MDI/SDI differences.
     *
     * We also want to save the object's hWnd for use in WM_SETFOCUS
     * processing.
     */

    if (NULL==hMenu)
        {
        m_hWndObj=NULL;

        //Prevent redundant calls, or debug warnings on startup.
        if (NULL==m_hMenuTop)
            return NOERROR;

        hMenu=m_hMenuTop;
        m_hMenuTop=NULL;
        }
    else
        {
        m_hMenuTop=m_hMenuOrg;
        m_hWndObj=hWndObj;
        }

    pCL->SetMenu(m_hWnd, hMenu, m_hMenuWindow);
    hr=OleSetMenuDescriptor(hOLEMenu, m_hWnd, hWndObj, NULL, NULL);
    return hr;
    }

/*
 * CPatronFrame::RemoveMenus
 *
 * Purpose:
 *  Asks the container to remove any menus it put into hMenu in
 *  InsertMenus.
 *
 * Parameters:
 *  hMenu           HMENU from which to remove the container's
 *                  items.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CPatronFrame::RemoveMenus(HMENU hMenu)
    {
    int         cItems, i, j;
    HMENU       hMenuT;

    /*
     * To be defensive, loop through this menu removing anything
     * we recognize (that is, anything in m_phMenu) just in case
     * the server didn't clean it up right.  At least we can
     * give ourselves the prophylactic benefit.
     */

    if (NULL==hMenu)
        return NOERROR;

    cItems=GetMenuItemCount(hMenu);

    /*
     * Walk backwards down the menu.  For each popup, see if it
     * matches any other popup we know about, and if so, remove
     * it from the shared menu.
     */
    for (i=cItems; i >=0; i--)
        {
        hMenuT=GetSubMenu(hMenu, i);

        for (j=0; j <= CMENUS; j++)
            {
            if (hMenuT==m_phMenu[j])
                RemoveMenu(hMenu, i, MF_BYPOSITION);
            }
        }

    //The menu should now be empty.
    return NOERROR;
    }

/*
 * CPatronFrame::SetStatusText
 *
 * Purpose:
 *  Asks the container to place some text in a status line, if one
 *  exists.  If the container does not have a status line it
 *  should return E_FAIL here in which case the object could
 *  display its own.
 *
 * Parameters:
 *  pszText         LPCOLESTR to display.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_TRUNCATED if not all
 *                  of the text could be displayed, or E_FAIL if
 *                  the container has no status line.
 */

STDMETHODIMP CPatronFrame::SetStatusText(LPCOLESTR pszText)
    {
    /*
     * Just send this to the StatStrip.  Unfortunately it won't tell
     * us about truncation.  Oh well, we'll just act like it worked.
     */
   #ifdef WIN32ANSI
    TCHAR       szTemp[256];

    WideCharToMultiByte(CP_ACP, 0, pszText, -1, szTemp, 256
        , NULL, NULL);
    m_pSL->MessageSet(szTemp);
   #else
    m_pSL->MessageSet((LPTSTR)pszText);
   #endif

    return NOERROR;
    }

/*
 * CPatronFrame::EnableModeless
 *
 * Purpose:
 *  Instructs the container to show or hide any modeless popup
 *  windows that it may be using.
 *
 * Parameters:
 *  fEnable         BOOL indicating to enable/show the windows
 *                  (TRUE) or to hide them (FALSE).
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CPatronFrame::EnableModeless(BOOL fEnable)
    {
    return NOERROR;
    }

/*
 * CPatronFrame::TranslateAccelerator
 *
 * Purpose:
 *  When dealing with an in-place object from an EXE server, this
 *  is called to give the container a chance to process accelerators
 *  after the server has looked at the message.
 *
 * Parameters:
 *  pMSG            LPMSG for the container to examine.
 *  wID             WORD the identifier in the container's
 *                  accelerator table (from IOleInPlaceSite
 *                  ::GetWindowContext) for this message (OLE does
 *                  some translation before calling).
 *
 * Return Value:
 *  HRESULT         NOERROR if the keystroke was used,
 *                  S_FALSE otherwise.
 */

STDMETHODIMP CPatronFrame::TranslateAccelerator(LPMSG pMSG, WORD wID)
    {
    SCODE       sc;

    /*
     * wID already has anything translated from m_hAccelIP for us,
     * so we can just check for the commands we want and process
     * them instead of calling TranslateAccelerator which would be
     * redundant and which also has a possibility of dispatching to
     * the wrong window.
     */
    if ((IDM_PAGENEWPAGE <= wID && IDM_PAGELASTPAGE >= wID)
        || IDM_OPENOBJECT==wID || IDM_ENTERCONTEXTHELP==wID
        || IDM_ESCAPECONTEXTHELP==wID)
        {
        //wID properly expands to 32-bits
        OnCommand(m_hWnd, (WPARAM)wID, 0L);
        sc=S_OK;
        }
   #ifdef MDI
    else if (TranslateMDISysAccel(m_pCL->Window(), pMSG))
        sc=S_OK;
   #endif
    else
        sc=S_FALSE;

    return ResultFromScode(sc);
    }

//End CHAPTER22MOD


PATRON.H

/*
 * PATRON.H
 * Patron Chapter 22
 *
 * Single include file that pulls in everything needed for other
 * source files in the application.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _PATRON_H_
#define _PATRON_H_

#define INC_CLASSLIB
#define INC_OLEUI
//CHAPTER22MOD
#define CHAPTER22
//End CHAPTER22MOD
#include <inole.h>
#include "resource.h"

//Get editor window information
#include "pages.h"

/*
 * UINT value such that adding one produces zero.  Portable to Win32.
 * This is used to represent a non-existent zero-based UINT value
 */
#define NOVALUE                     ((UINT)-1)

//PATRON.CPP:  Frame object that creates a main window

//CHAPTER22MOD
class CPatronFrame : public CFrame, public IOleInPlaceFrame
//End CHAPTER22MOD
    {
    //For access to m_pCL for document creation
    friend class CLinkClassFactory;

    private:
        BOOL            m_fInitialized;     //OleInitialize worked
        LPCLASSFACTORY  m_pIClassDataTran;  //For locking.
        PCDocument      m_pDocCreated;      //What class factory makes
        BOOL            m_fEmbedding;       //-Embedding on command line?
        DWORD           m_dwRegCO;          //From CoRegisterClassObject
        LPCLASSFACTORY  m_pIClassFactory;

        //CHAPTER22MOD
        DWORD           m_cRef;
        HMENU           m_hMenuOrg;         //Original menu
        HMENU           m_hMenuTop;
        HWND            m_hWndObj;          //Object window
        BOOL            m_fOurToolsShowing; //Our tools up?

        int             m_cyTop;            //In-place tool allocations
        int             m_cyBottom;
        int             m_cxLeft;
        int             m_cxRight;

        BOOL            m_fInContextHelp;   //In context help mode?

        LPOLEINPLACEACTIVEOBJECT    m_pIOleIPActiveObject;
        //End CHAPTER22MOD

    //CHAPTER22MOD
    //This is for access from IOleInPlaceSite::GetWindowContext
    public:
        HACCEL          m_hAccelIP;         //Accelerators for in-place
    //End CHAPTER22MOD

    protected:
        //Overridable for creating a CPatronClient
        virtual PCClient    CreateCClient(void);

        virtual BOOL        FMessageHook(HWND, UINT, WPARAM, LPARAM
                                , LRESULT *);

        virtual BOOL        PreShowInit(void);
        virtual BOOL        RegisterAllClasses(void);
        virtual UINT        CreateToolbar(void);
        virtual LRESULT     OnCommand(HWND, WPARAM, LPARAM);

    public:
        CPatronFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CPatronFrame(void);

        //Overrides
        virtual BOOL        Init(PFRAMEINIT);

        //CHAPTER22MOD
        virtual WPARAM      MessageLoop(void);
        //End CHAPTER22MOD

        virtual void        UpdateMenus(HMENU, UINT);
        virtual void        UpdateToolbar(void);

        //CHAPTER22MOD
        void                ShowUIAndTools(BOOL, BOOL);
        void                ReinstateUI(void);

        //IOleInPlaceFrame implementation
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP         GetWindow(HWND *);
        STDMETHODIMP         ContextSensitiveHelp(BOOL);
        STDMETHODIMP         GetBorder(LPRECT);
        STDMETHODIMP         RequestBorderSpace(LPCBORDERWIDTHS);
        STDMETHODIMP         SetBorderSpace(LPCBORDERWIDTHS);
        STDMETHODIMP         SetActiveObject(LPOLEINPLACEACTIVEOBJECT
                                 , LPCOLESTR);
        STDMETHODIMP         InsertMenus(HMENU, LPOLEMENUGROUPWIDTHS);
        STDMETHODIMP         SetMenu(HMENU, HOLEMENU, HWND);
        STDMETHODIMP         RemoveMenus(HMENU);
        STDMETHODIMP         SetStatusText(LPCOLESTR);
        STDMETHODIMP         EnableModeless(BOOL);
        STDMETHODIMP         TranslateAccelerator(LPMSG, WORD);
        //End CHAPTER22MOD
    };

typedef CPatronFrame *PCPatronFrame;

//These are global for simplification of object implementation.
extern ULONG g_cObj;
extern ULONG g_cLock;
extern HWND  g_hWnd;

//CHAPTER22MOD
extern PCPatronFrame  g_pFR;
//End CHAPTER22MOD

//Function for the object to notify on destruction.
void ObjectDestroyed(void);

//ICLASSF.CPP

class CLinkClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;
        PCPatronFrame   m_pFR;          //For document creation.

    public:
        CLinkClassFactory(PCPatronFrame);
        ~CLinkClassFactory(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP         CreateInstance(LPUNKNOWN, REFIID
                                 , PPVOID);
        STDMETHODIMP         LockServer(BOOL);
    };

typedef CLinkClassFactory *PCLinkClassFactory;

//CLIENT.CPP

/*
 * The only reason we have a derived class here is to override
 * CreateCDocument so we can create our own type as well as
 * overriding NewDocument to perform one other piece of work once
 * the document's been created.
 */

class CPatronClient : public CClient
    {
    protected:
        //Overridable for creating a new CDocument
        virtual PCDocument CreateCDocument(void);

    public:
        CPatronClient(HINSTANCE, PCFrame);
        virtual ~CPatronClient(void);

        //CHAPTER22MOD
        //This is added to handle IOleInPlaceFrame::SetMenu
        void         SetMenu(HWND, HMENU, HMENU);
        void         MoveWithoutFamily(LPRECT, int, int);
        void         CallContextHelpOnDocuments(BOOL);
        //End CHAPTER22MOD
    };

typedef CPatronClient *PCPatronClient;

//DOCUMENT.CPP

//Constant ID for the pages window that lives in a document window
#define ID_PAGES            723

class CImpIPersistFile;
typedef class CImpIPersistFile *PCImpIPersistFile;
class CImpIOleItemContainer;
typedef class CImpIOleItemContainer *PCImpIOleItemContainer;
//CHAPTER22MOD
class CImpIOleInPlaceUIWindow;
typedef class CImpIOleInPlaceUIWindow *PCImpIOleInPlaceUIWindow;
//End CHAPTER22MOD

/*
 * To support linking to embeddings, the document needs to have
 * IPersistFile and IOleItemContainer interfaces, so we multiply
 * inherit from IUnknown and provide two interface implementations.
 */

class CPatronDoc : public CDocument, public IUnknown
    {
    //These need access to FQueryPasteFromData, PasteFromData
    friend class CDropTarget;
    friend class CDropSource;
    friend class CImpIPersistFile;
    friend class CImpIOleItemContainer;
    //CHAPTER22MOD
    friend class CImpIOleInPlaceUIWindow;
    //End CHAPTER22MOD

    protected:
        LONG            m_lVer;         //Loaded data version
        PCPages         m_pPG;          //Pages window in us
        LPSTORAGE       m_pIStorage;    //Root storage for document
        BOOL            m_fPrintSetup;

        class CDropTarget *m_pDropTarget;   //Registered target

        UINT            m_cfEmbeddedObject; //Clipboard formats
        UINT            m_cfObjectDescriptor;
        UINT            m_cfLinkSource;
        UINT            m_cfLinkSrcDescriptor;
        BOOL            m_fShowTypes;       //Show Objects active?

        ULONG               m_cRef;
        BOOL                m_fRename;          //Rename on Save?
        DWORD               m_dwRegROT;
        DWORD               m_dwRegROTWild;

        PCImpIPersistFile       m_pImpIPersistFile;
        PCImpIOleItemContainer  m_pImpIOleItemContainer;

        //CHAPTER22MOD
        PCImpIOleInPlaceUIWindow m_pImpIOleIPUIWindow;  //Ours
        LPOLEINPLACEACTIVEOBJECT m_pIOleIPActiveObject; //Object's
        BOOL                     m_fNoObjectFrameTools;
        //End CHAPTER22MOD

    protected:
        virtual BOOL    FMessageHook(HWND, UINT, WPARAM, LPARAM
            , LRESULT *);

        BOOL            FQueryPasteFromData(LPDATAOBJECT
                            , LPFORMATETC, PTENANTTYPE);
        BOOL            FQueryPasteLinkFromData(LPDATAOBJECT
                            , LPFORMATETC, PTENANTTYPE);
        BOOL            PasteFromData(LPDATAOBJECT, LPFORMATETC
                            , TENANTTYPE, PPATRONOBJECT, DWORD
                            , BOOL);

    public:
        CPatronDoc(HINSTANCE, PCFrame, PCDocumentAdviseSink);
        virtual ~CPatronDoc(void);

        //We now need our ubiquitous set of IUnknown members.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        virtual BOOL    Init(PDOCUMENTINIT);
        virtual void    Clear(void);

        virtual BOOL    FDirtySet(BOOL);
        virtual BOOL    FDirtyGet(void);
        virtual void    Delete(void);
        virtual BOOL    FQueryPrinterSetup(void);
        virtual BOOL    FQueryObjectSelected(HMENU);

        virtual UINT    Load(BOOL, LPTSTR);
        virtual UINT    Save(UINT, LPTSTR);

        virtual BOOL    Print(HWND);
        virtual UINT    PrinterSetup(HWND, BOOL);

        virtual BOOL    Clip(HWND, BOOL);
        virtual BOOL    FQueryPaste(void);
        virtual BOOL    Paste(HWND);
        virtual BOOL    PasteSpecial(HWND);
        virtual BOOL    FQueryEnableEditLinks(void);
        virtual BOOL    EditLinks(HWND);
        virtual BOOL    ShowOrQueryObjectTypes(BOOL, BOOL);

        virtual UINT    NewPage(void);
        virtual UINT    DeletePage(void);
        virtual UINT    NextPage(void);
        virtual UINT    PreviousPage(void);
        virtual UINT    FirstPage(void);
        virtual UINT    LastPage(void);

        virtual void    Rename(LPTSTR);
        virtual BOOL    InsertObject(HWND);
        //CHAPTER22MOD
        virtual void    ActivateObject(LONG, LPMSG);
        virtual BOOL    NoObjectFrameTools(BOOL, BOOL);
        //End CHAPTER22MOD
        virtual BOOL    ConvertObject(HWND);

    };

typedef CPatronDoc *PCPatronDoc;

//Hook for Print Dialog to hide Setup... button
UINT CALLBACK PrintDlgHook(HWND, UINT, WPARAM, LPARAM);

//IPersistFile implementation for CPatronDoc

class CImpIPersistFile : public IPersistFile
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCPatronDoc         m_pDoc;      //Back pointer to the object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIPersistFile(PCPatronDoc, LPUNKNOWN);
        ~CImpIPersistFile(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP Load(LPCOLESTR, DWORD);
        STDMETHODIMP Save(LPCOLESTR, BOOL);
        STDMETHODIMP SaveCompleted(LPCOLESTR);
        STDMETHODIMP GetCurFile(LPOLESTR *);
    };

/*
 * IOleItemContainer implementation for both CPatronDoc and CPage,
 * therefore have two back pointers.
 */

class CImpIOleItemContainer : public IOleItemContainer
    {
    protected:
        ULONG               m_cRef;
        class CPage        *m_pPage;
        PCPatronDoc         m_pDoc;      //Convenient naming & types
        LPUNKNOWN           m_pUnkOuter;
        BOOL                m_fDoc;      //Document or page?

    private:
        BOOL         TenantFromName(LPOLESTR, PCTenant *);

    public:
        CImpIOleItemContainer(LPVOID, LPUNKNOWN, BOOL);
        ~CImpIOleItemContainer(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP ParseDisplayName(LPBC, LPOLESTR, ULONG *
            , LPMONIKER *);
        STDMETHODIMP EnumObjects(DWORD, LPENUMUNKNOWN *);
        STDMETHODIMP LockContainer(BOOL);
        STDMETHODIMP GetObject(LPOLESTR, DWORD, LPBINDCTX, REFIID
            , PPVOID);
        STDMETHODIMP GetObjectStorage(LPOLESTR, LPBINDCTX, REFIID
            , PPVOID);
        STDMETHODIMP IsRunning(LPOLESTR);
    };

//CHAPTER22MOD
//This goes on the document.

class CImpIOleInPlaceUIWindow : public IOleInPlaceUIWindow
    {
    protected:
        ULONG               m_cRef;
        PCPatronDoc         m_pDoc;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIOleInPlaceUIWindow(PCPatronDoc, LPUNKNOWN);
        ~CImpIOleInPlaceUIWindow(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetWindow(HWND *);
        STDMETHODIMP ContextSensitiveHelp(BOOL);
        STDMETHODIMP GetBorder(LPRECT);
        STDMETHODIMP RequestBorderSpace(LPCBORDERWIDTHS);
        STDMETHODIMP SetBorderSpace(LPCBORDERWIDTHS);
        STDMETHODIMP SetActiveObject(LPOLEINPLACEACTIVEOBJECT
                         , LPCOLESTR);
    };
//End CHAPTER22MOD

//Drag-drop objects we need in the document

class CDropTarget : public IDropTarget
    {
    protected:
        ULONG               m_cRef;
        PCPatronDoc         m_pDoc;

        LPDATAOBJECT        m_pIDataObject;  //From DragEnter
        BOOL                m_fPendingRepaint;
        POINTL              m_ptPick;        //Pick-up offsets
        POINTL              m_ptLast;        //Last drag point
        SIZEL               m_szl;           //Object size
        BOOL                m_fFeedback;     //Draw feedback?
        FORMATETC           m_fe;            //Real dropping format

    public:
        CDropTarget(PCPatronDoc);
        ~CDropTarget(void);

        //IDropTarget interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP DragEnter(LPDATAOBJECT, DWORD, POINTL,LPDWORD);
        STDMETHODIMP DragOver(DWORD, POINTL, LPDWORD);
        STDMETHODIMP DragLeave(void);
        STDMETHODIMP Drop(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
    };

typedef CDropTarget *PCDropTarget;

class CDropSource : public IDropSource
    {
    protected:
        ULONG               m_cRef;

    public:
        CDropSource(void);
        ~CDropSource(void);

        //IDropSource interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP QueryContinueDrag(BOOL, DWORD);
        STDMETHODIMP GiveFeedback(DWORD);
    };

typedef CDropSource *PCDropSource;

#endif //_PATRON_H_


PRINT.CPP

/*
 * PRINT.CPP
 * Patron Chapter 22
 *
 * Implementation of printing functions for both CPatronDoc
 * and CPages classes.  These functions are here to keep clutter
 * down in document.cpp and pages.cpp.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

static HWND g_hDlgPrint=NULL;
static BOOL g_fCancelPrint=FALSE;

/*
 * CPatronDoc::Print
 *
 * Purpose:
 *  Prints the current document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *
 * Return Value:
 *  BOOL            TRUE if printing happened, FALSE if it didn't
 *                  start or didn't complete.
 */

BOOL CPatronDoc::Print(HWND hWndFrame)
    {
    PRINTDLG        pd;
    BOOL            fSuccess;

    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);
    pd.hwndOwner  =hWndFrame;
    pd.nCopies    =1;
    pd.nFromPage  =(USHORT)-1;
    pd.nToPage    =(USHORT)-1;
    pd.nMinPage   =1;
    pd.nMaxPage   =m_pPG->NumPagesGet();

    pd.lpfnPrintHook=PrintDlgHook;

    //Get the current document printer settings
    pd.hDevMode=m_pPG->DevModeGet();

    pd.Flags=PD_RETURNDC | PD_ALLPAGES | PD_COLLATE
        | PD_HIDEPRINTTOFILE | PD_NOSELECTION | PD_ENABLEPRINTHOOK;

    if (!PrintDlg(&pd))
        return FALSE;

    if (NULL!=pd.hDevMode)
        GlobalFree(pd.hDevMode);

    if (NULL!=pd.hDevNames)
        GlobalFree(pd.hDevNames);

    //Go do the actual printing.
    fSuccess=m_pPG->Print(pd.hDC, PSZ(IDS_DOCUMENTNAME), pd.Flags
        , pd.nFromPage, pd.nToPage, pd.nCopies);

    if (!fSuccess)
        {
        MessageBox(m_hWnd, PSZ(IDS_PRINTERROR)
            , PSZ(IDS_DOCUMENTCAPTION), MB_OK);
        }

    return fSuccess;
    }

/*
 * CPatronDoc::PrinterSetup
 *
 * Purpose:
 *  Selects a new printer and options for this document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *  fDefault        BOOL to avoid any dialog and just use the
 *                  default.
 *
 * Return Value:
 *  UINT            Undefined
 */

UINT CPatronDoc::PrinterSetup(HWND hWndFrame, BOOL fDefault)
    {
    PRINTDLG        pd;

    //Attempt to get printer metrics for the default printer.
    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);

    if (fDefault)
        pd.Flags=PD_RETURNDEFAULT;
    else
        {
        pd.hwndOwner=hWndFrame;
        pd.Flags=PD_PRINTSETUP;

        //Get the current document printer settings
        pd.hDevMode=m_pPG->DevModeGet();
        }

    if (!PrintDlg(&pd))
        return FALSE;

    if (!m_pPG->DevModeSet(pd.hDevMode, pd.hDevNames))
        {
        GlobalFree(pd.hDevNames);
        GlobalFree(pd.hDevMode);
        return FALSE;
        }

    FDirtySet(TRUE);
    return 1;
    }

/*
 * PrintDlgHook
 *
 * Purpose:
 *  Callback hook for the Print Dialog so we can hide the Setup
 *  button.  Patron only allows Setup before anything exists on
 *  the page, and is not written to handle setup at Print time.
 */

UINT CALLBACK PrintDlgHook(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    if (WM_INITDIALOG==iMsg)
        {
        HWND        hWnd;

        hWnd=GetDlgItem(hDlg, psh1);
        ShowWindow(hWnd, SW_HIDE);
        return TRUE;
        }

    return FALSE;
    }

/*
 * CPatronDoc::FQueryPrinterSetup
 *
 * Purpose:
 *  Returns whether or not the Printer Setup menu item can be
 *  enabled.  Once you create a tenant in any page, Printer Setup
 *  is voided simply to keep this sample simple, that is, we don't
 *  have to worry about reorganizing potentially large amounts
 *  of layout after we start plopping down objects.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE to enable the menu, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPrinterSetup(void)
    {
    return m_fPrintSetup;
    }

/*
 * CPages::DevModeSet
 *
 * Purpose:
 *  Provides the Pages with the current printer information.
 *
 * Parameters:
 *  hDevMode        HGLOBAL to the memory containing the DEVMODE.
 *                  This function assumes responsibility for this
 *                  handle.
 *  hDevNames       HGLOBAL providing the driver name and device
 *                  name from which we can create a DC for
 *                  information.
 *
 * Return Value:
 *  BOOL            TRUE if we could accept this configuration,
 *                  FALSE otherwise.  If we return TRUE we also
 *                  delete the old memory we hold.
 */

BOOL CPages::DevModeSet(HGLOBAL hDevMode, HGLOBAL hDevNames)
    {
    LPDEVNAMES      pdn;
    LPTSTR          psz;
    HGLOBAL         hMem;
    PDEVICECONFIG   pdc;
    LPDEVMODE       pdm;
    LPSTREAM        pIStream;
    HRESULT         hr;
    ULONG           cbDevMode, cbWrite;
    BOOL            fRet=FALSE;

    if (NULL==hDevMode || NULL==hDevNames)
        return FALSE;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    /*
     * DEVMODE is variable length--total length in hDevMode, so the
     * amount to write is that plus string space.  We subtract
     * sizeof(DEVMODE) as that is already included from GlobalSize.
     */
    cbDevMode=GlobalSize(hDevMode);
    cbWrite=cbDevMode+sizeof(DEVICECONFIG)-sizeof(DEVMODE);

    hMem=GlobalAlloc(GHND, cbWrite);

    if (NULL==hMem)
        {
        pIStream->Release();
        return FALSE;
        }

    pdc=(PDEVICECONFIG)GlobalLock(hMem);    //This always works
    pdm=(LPDEVMODE)GlobalLock(hDevMode);    //This might not

    if (NULL!=pdm)
        {
        pdc->cb=cbWrite;
        pdc->cbDevMode=cbDevMode;
        memcpy(&pdc->dm, pdm, (int)cbDevMode);
        GlobalUnlock(hDevMode);

        psz=(LPTSTR)GlobalLock(hDevNames);

        if (NULL!=psz)
            {
            pdn=(LPDEVNAMES)psz;
            lstrcpy(pdc->szDriver, psz+pdn->wDriverOffset);
            lstrcpy(pdc->szDevice, psz+pdn->wDeviceOffset);
            lstrcpy(pdc->szPort,   psz+pdn->wOutputOffset);

            pIStream->Write(pdc, cbWrite, &cbWrite);
            GlobalUnlock(hDevNames);
            fRet=TRUE;
            }
        }

    GlobalUnlock(hMem);
    GlobalFree(hMem);

    pIStream->Release();

    if (!fRet)
        return FALSE;

    GlobalFree(hDevNames);
    GlobalFree(hDevMode);

    return ConfigureForDevice();
    }

/*
 * CPages::DevModeGet
 *
 * Purpose:
 *  Retrieves a copy of the current DEVMODE structure for this
 *  Pages window.  The caller is responsible for this memory.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HGLOBAL         Handle to the memory containing the DEVMODE
 *                  structure.
 */

HGLOBAL CPages::DevModeGet(void)
    {
    HGLOBAL         hMem;
    LPVOID          pv;
    ULONG           cbDevMode, cbRead;
    LARGE_INTEGER   li;
    LPSTREAM        pIStream;
    HRESULT         hr;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    //Read how much to allocate for the DEVMODE structure
    LISet32(li, CBSEEKOFFSETCBDEVMODE);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);
    pIStream->Read(&cbDevMode, sizeof(ULONG), &cbRead);

    hMem=GlobalAlloc(GHND, cbDevMode);

    if (NULL!=hMem)
        {
        pv=(LPVOID)GlobalLock(hMem);
        pIStream->Read(pv, cbDevMode, &cbRead);
        GlobalUnlock(hMem);
        }

    pIStream->Release();
    return hMem;
    }

/*
 * CPages::DevReadConfig
 *
 * Purpose:
 *  Public function to read the current device configuration and
 *  optionally return an information context for it.
 *
 *
 * Parameters:
 *  ppcd            PCOMBINEDEVICE * in which to return a pointer
 *                  to an allocated structure that has all the
 *                  device information we want.  Ignored if NULL.
 *                  This is allocated with the task allocator.
 *  phDC            HDC * in which to return the information
 *                  context.  If NULL, no IC is created.  Caller
 *                  becomes responsible for the returned IC.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::DevReadConfig(PCOMBINEDEVICE *ppcd, HDC *phDC)
    {
    HRESULT         hr;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    PCOMBINEDEVICE  pcd;
    ULONG           cb, cbRead;
    LARGE_INTEGER   li;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    /*
     * Allocate the COMBINEDEVICE structure including the variable
     * information past the DEVMODE part.
     */

    hr=CoGetMalloc(MEMCTX_TASK, &pIMalloc);

    if (FAILED(hr))
        {
        pIStream->Release();
        return FALSE;
        }

    /*
     * Read size of the DEVICECONFIG structure including variable
     * portion of DEVMODE.  We need to load all this information
     * for CreateIC.  To this size we'll add the size of
     * DVTARGETDEVICE in order to allocate a COMBINEDEVICE.
     */

    pIStream->Read(&cb, sizeof(DWORD), &cbRead);

    pcd=(PCOMBINEDEVICE)pIMalloc->Alloc(cb+sizeof(DVTARGETDEVICE));

    if (NULL==pcd)
        {
        pIMalloc->Release();
        pIStream->Release();
        return FALSE;
        }

    //Now get the real information.
    LISet32(li, 0);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);
    pIStream->Read(&(pcd->dc), cb, &cbRead);
    pIStream->Release();

    /*
     * If requested, complete the DVTARGETDEVICE structure in
     * pcd and store pcd in *ppcd for return.
     */

    if (NULL!=ppcd)
        {
        WORD    cb=sizeof(DVTARGETDEVICE);

        pcd->td.tdSize=cb;
        pcd->td.tdExtDevmodeOffset=cb;
        pcd->td.tdDriverNameOffset=cb+sizeof(DEVMODE);
        pcd->td.tdDeviceNameOffset=cb+sizeof(DEVMODE)
            +(CCHDEVICENAME*sizeof(TCHAR));
        pcd->td.tdPortNameOffset  =cb+sizeof(DEVMODE)
            +(CCHDEVICENAME*2*sizeof(TCHAR));

        *ppcd=pcd;
        }

    //Create an IC if requested.
    if (NULL!=phDC)
        {
        //Get the DC then configure
        *phDC=CreateIC(pcd->dc.szDriver, pcd->dc.szDevice
            , pcd->dc.szPort, &(pcd->dc.dm));

        if (NULL==*phDC)
            return FALSE;
        }

    //pcd is a temporary allocation in this case
    if (NULL==ppcd)
        pIMalloc->Free(pcd);

    pIMalloc->Release();
    return (NULL!=*phDC);
    }

/*
 * CPages::ConfigureForDevice
 *
 * Purpose:
 *  Recalculates our drawing configuration based on the contents of
 *  an hDC.  If no HDC is given we use the contents of our DevMode
 *  stream.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::ConfigureForDevice(void)
    {
    POINT           ptOffset, ptPaper;
    RECT            rc;
    HDC             hDC;
    CHourglass      hg;

    if (!DevReadConfig(NULL, &hDC))
        return FALSE;

    //Get usable page dimensions:  already sensitive to orientation
    m_cx=GetDeviceCaps(hDC, HORZSIZE)*10-16; //*10: mm to LOMETRIC
    m_cy=GetDeviceCaps(hDC, VERTSIZE)*10-16; //-16: for driver bugs.

    //Calculate the printer-limited margins on sides in LOMETRIC.
    Escape(hDC, GETPRINTINGOFFSET, NULL, NULL, &ptOffset);
    Escape(hDC, GETPHYSPAGESIZE,   NULL, NULL, &ptPaper);

    SetRect(&rc, ptOffset.x, ptOffset.y, ptPaper.x, ptPaper.y);
    SetMapMode(hDC, MM_LOMETRIC);
    RectConvertMappings(&rc, hDC, FALSE);

    //Left and top margins are the printing offset.
    m_xMarginLeft= rc.left+8;   //+8 to match -16 above
    m_yMarginTop =-rc.top+8;    //LOMETRIC makes this negative.

    //Right is (paper width)-(usable width)-(left margin)
    m_xMarginRight =rc.right-m_cx-m_xMarginLeft;

    //Bottom is (paper height)-(usable height)-(top margin)+1
    m_yMarginBottom=-rc.bottom-m_cy-m_yMarginTop+1;

    UpdateScrollRanges();

    DeleteDC(hDC);
    return TRUE;
    }

/*
 * CPages::Print
 *
 * Purpose:
 *  Prints a specified range of pages to a given hDC.  Repeats for
 *  a given number of copies.
 *
 * Parameters:
 *  hDC             HDC to which we print.
 *  pszDoc          LPTSTR providing the document name.
 *  dwFlags         DWORD flags from PrintDlg
 *  iPageStart      UINT starting page index (one based)
 *  iPageEnd        UINT ending page index (one based).  Includes
 *                  this page.
 *  cCopies         UINT number of copies to print.  If PD_COLLATE
 *                  in dwFlags is set, we print multiple copies of
 *                  each page as we cycle through.  Otherwise we
 *                  cycle multiple times.
 *
 * Return Value:
 *  None
 */

BOOL CPages::Print(HDC hDC, LPTSTR pszDoc, DWORD dwFlags
    , UINT iPageStart, UINT iPageEnd, UINT cCopies)
    {
    BOOL        fError=FALSE;
    int         iPage, iPageInc;
    int         iUserPage, cPages;
    UINT        iRepeat, cRepeat;
    UINT        iCycle, cCycles;
    UINT        iPageHold=m_iPageCur;
    HWND        hWndT, hWndTop=NULL;
    DOCINFO     di;
    PCDocument  pDoc;

    //Validate hDC and page ranges
    if (NULL==hDC)
        return FALSE;

    if ((PD_PAGENUMS & dwFlags))
        {
        if (-1==iPageStart)
            iPageStart=0;
        else
            iPageStart--;   //Switch to zero offset.

        if (-1==iPageEnd)
            iPageEnd=m_cPages-1;
        else
            iPageEnd--;     //Switch to zero offset.
        }
    else //Can't test PD_ALLPAGES with & since it's defined as 0L
        {
        iPageStart=0;
        iPageEnd=m_cPages-1;
        }

    //Arrange cycles and repeats depending on cCopies and collating
    if (PD_COLLATE & dwFlags)
        {
        cCycles=cCopies;
        cRepeat=1;
        }
    else
        {
        cCycles=1;
        cRepeat=cCopies;
        }

    //Disable the frame window to prevent reentrancy while printing.
    hWndT=GetParent(m_hWnd);
    pDoc=(PCDocument)SendMessage(hWndT, DOCM_PDOCUMENT, 0, 0L);

    if (NULL!=pDoc)
        {
        PCFrame pFR;

        pFR=pDoc->FrameGet();
        hWndTop=pFR->Window();
        EnableWindow(hWndTop, FALSE);
        }

    SetAbortProc(hDC, AbortProc);
    g_fCancelPrint=FALSE;

    //If these don't work then we'll just live without a dialog.
    g_hDlgPrint=CreateDialog(m_hInst, MAKEINTRESOURCE(IDD_PRINTING)
        , hWndTop, PrintDlgProc);

    //Increment for either direction.
    iPageInc=(iPageStart > iPageEnd) ? -1 : 1;

    //Initial entries in dialog box.
    cPages=1+((int)(iPageEnd-iPageStart)*iPageInc);

    SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE, 1, (LPARAM)cPages);
    SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, 1, (LPARAM)cRepeat);

    di.cbSize=sizeof(DOCINFO);
    di.lpszDocName=pszDoc;
    di.lpszOutput=NULL;

    if (StartDoc(hDC, &di) > 0)
        {
        /*
         * Iterate over the pages, repeating each page depending on
         * the copies we want and if we have collate enabled.
         */

        for (iCycle=1; iCycle <= cCycles; iCycle++)
            {
            if (PD_COLLATE & dwFlags)
                {
                SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, iCycle
                    , (LPARAM)cCycles);
                }

            //iPageInc controls direction
            for (iPage=iPageStart; ; iPage+=iPageInc)
                {
                iUserPage=1+((iPage-(int)iPageStart)*iPageInc);

                SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE
                    , iUserPage, (LPARAM)cPages);

                m_iPageCur=iPage;   //We restore this later.

                for (iRepeat=1; iRepeat <= cRepeat; iRepeat++)
                    {
                    if (!(PD_COLLATE & dwFlags))
                        {
                        SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE
                            , iRepeat, (LPARAM)cRepeat);
                        }

                    StartPage(hDC);
                    Draw(hDC, TRUE, TRUE);

                    if (EndPage(hDC) < 0)
                        fError=TRUE;

                    if (fError || g_fCancelPrint)
                        break;
                    }

                if (fError || g_fCancelPrint)
                    break;

                //If we just printed the last page, time to quit.
                if (iPage==(int)iPageEnd)
                    break;
                }

            if (fError || g_fCancelPrint)
                break;
            }

        if (!fError)
            EndDoc(hDC);
        else
            AbortDoc(hDC);
        }
    else
        fError=TRUE;

    //Set the page back to what it was before all this started.
    m_iPageCur=iPageHold;

    EnableWindow(hWndTop, TRUE);
    SetFocus(hWndTop);
    DestroyWindow(g_hDlgPrint);
    DeleteDC(hDC);
    return !fError;
    }

/*
 * AbortProc
 *
 * Purpose:
 *  Abort procedure for printing the pages.
 *
 * Parameters:
 *  hDC             HDC on which printing is happening.
 *  iErr            int error code.
 *
 * Return Value:
 *  BOOL            TRUE to continue the print job, FALSE otherwise.
 */

BOOL APIENTRY AbortProc(HDC hDC, int iErr)
    {
    MSG     msg;

    while (!g_fCancelPrint
        && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
        if (NULL==g_hDlgPrint
            || !IsDialogMessage(g_hDlgPrint, &msg))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    return !g_fCancelPrint;
    }

/*
 * PrintDlgProc
 *
 * Purpose:
 *  Modeless dialog procedure for the dialog displayed while Patron
 *  is printing pages.
 */

BOOL APIENTRY PrintDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    TCHAR           szFormat[40];
    TCHAR           szOutput[80];

    switch (iMsg)
        {
        case WM_INITDIALOG:
            EnableMenuItem(GetSystemMenu(hDlg, FALSE), SC_CLOSE
                , MF_GRAYED);
            return TRUE;

        case WM_COMMAND:
            //Cancel button was pressed.
            g_fCancelPrint=TRUE;
            ShowWindow(hDlg, SW_HIDE);
            return TRUE;

        case PRINTM_PAGEUPDATE:
            GetDlgItemText(hDlg, ID_PAGESTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTPAGE, szOutput);
            return TRUE;

        case PRINTM_COPYUPDATE:
            GetDlgItemText(hDlg, ID_COPYSTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTCOPY, szOutput);
            return TRUE;
        }

    return FALSE;
    }


RESOURCE.H

/*
 * RESOURCE.H
 * Patron Chapter 22
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Note that we augment classlib.h which we include before this.

//Position of Window menu.
#undef  WINDOW_MENU
#define WINDOW_MENU                 3

//Position of the "Object" item on the Edit menu.
#define MENUPOS_OBJECT              8
#define MENUPOS_OBJECTONPOPUP       5

//Popup menu
#define IDR_RIGHTPOPUPMENU          2

//CHAPTER22MOD
//In-place accelerators
#define IDR_INPLACEACCELERATORS     2
#define CINPLACEACCELERATORS        11  //See patron.rc
//End CHAPTER22MOD

//Total number of menus
#undef  CMENUS
#ifdef MDI
#define CMENUS                      5
#else
#define CMENUS                      4
#endif

//Menu command identifiers.

#define IDM_FILEPRINT               (IDM_CUSTOMFILEMIN)
#define IDM_FILEPRINTERSETUP        (IDM_CUSTOMFILEMIN+1)

#define IDM_EDITPASTESPECIAL        (IDM_CUSTOMEDITMIN)
#define IDM_EDITDELETEOBJECT        (IDM_CUSTOMEDITMIN+1)
#define IDM_EDITINSERTOBJECT        (IDM_CUSTOMEDITMIN+2)
#define IDM_EDITOBJECT              (IDM_CUSTOMEDITMIN+3)
#define IDM_EDITCONVERT             (IDM_CUSTOMEDITMIN+4)
#define IDM_EDITLINKS               (IDM_CUSTOMEDITMIN+5)

#define IDM_PAGENEWPAGE             (IDM_CUSTOMMIN)
#define IDM_PAGEDELETEPAGE          (IDM_CUSTOMMIN+1)
#define IDM_PAGENEXTPAGE            (IDM_CUSTOMMIN+2)
#define IDM_PAGEPREVIOUSPAGE        (IDM_CUSTOMMIN+3)
#define IDM_PAGEFIRSTPAGE           (IDM_CUSTOMMIN+4)
#define IDM_PAGELASTPAGE            (IDM_CUSTOMMIN+5)
#define IDM_PAGESHOWOBJECTS         (IDM_CUSTOMMIN+6)

//This special item is for the dynamic verbs on the Edit/Object item.
#define IDM_VERBMIN                 (IDM_CUSTOMMIN+100)
#define IDM_VERBMAX                 (IDM_CUSTOMMIN+200)

//CHAPTER22MOD
#define IDM_OPENOBJECT              (IDM_CUSTOMMIN+201)
#define IDM_ENTERCONTEXTHELP        (IDM_CUSTOMMIN+202)
#define IDM_ESCAPECONTEXTHELP       (IDM_CUSTOMMIN+203)
//End CHAPTER22MOD

/*
 * IDs for StatStrip handling of popup menus:  must be in order of menu
 * CLASSRES.H already defines ID_MENUFILE and ID_MENUEDIT for us.
 */

#undef ID_MENUWINDOW
#undef ID_MENUHELP

#define ID_MENUPAGE                 (ID_MENUCUSTOMMIN+0)
#define ID_MENUWINDOW               (ID_MENUCUSTOMMIN+1)
#define ID_MENUHELP                 (ID_MENUCUSTOMMIN+2)

//Stringtable IDs.  Keep sequential for each group.
#define IDS_FRAMEMIN                IDS_STANDARDFRAMEMIN
//CHAPTER22MOD
#define IDS_FILEMENU                (IDS_CUSTOMFRAMEMIN+0)
#define IDS_PAGEMENU                (IDS_CUSTOMFRAMEMIN+1)
#define IDS_WINDOWMENU              (IDS_CUSTOMFRAMEMIN+2)
#define IDS_IPFRAMECAPTION          (IDS_CUSTOMFRAMEMIN+3)
#define IDS_FRAMEMAX                (IDS_CUSTOMFRAMEMIN+4)
//End CHAPTER22MOD

#define IDS_DOCUMENTMIN             IDS_STANDARDDOCMIN
#define IDS_PRINTERROR              (IDS_CUSTOMDOCMIN+0)
#define IDS_DOCUMENTNAME            (IDS_CUSTOMDOCMIN+1)
#define IDS_PASTEASPATRON           (IDS_CUSTOMDOCMIN+2)
#define IDS_PASTEMETAFILE           (IDS_CUSTOMDOCMIN+3)
#define IDS_PASTEASMETAFILE         (IDS_CUSTOMDOCMIN+4)
#define IDS_PASTEDIB                (IDS_CUSTOMDOCMIN+5)
#define IDS_PASTEASDIB              (IDS_CUSTOMDOCMIN+6)
#define IDS_PASTEBITMAP             (IDS_CUSTOMDOCMIN+7)
#define IDS_PASTEASBITMAP           (IDS_CUSTOMDOCMIN+8)
#define IDS_PASTEOBJECT             (IDS_CUSTOMDOCMIN+9)
#define IDS_PASTEASOBJECT           (IDS_CUSTOMDOCMIN+10)
#define IDS_PASTELINK               (IDS_CUSTOMDOCMIN+11)
#define IDS_PASTEASLINK             (IDS_CUSTOMDOCMIN+12)
#define IDS_EXTENSION               (IDS_CUSTOMDOCMIN+13)
//CHAPTER22MOD
#define IDS_UNTITLEDDOC             (IDS_CUSTOMDOCMIN+14)
#define IDS_IPDOCCAPTION            (IDS_CUSTOMDOCMIN+15)
#define IDS_DOCUMENTMAX             (IDS_CUSTOMDOCMIN+15)
//End CHAPTER22MOD

//These are specifically for the StatStrip
#define IDS_STATMESSAGEMIN                  IDS_STANDARDSTATMESSAGEMIN

#define IDS_ITEMMESSAGEFILEPRINT            (IDS_CUSTOMSTATMESSAGEMIN+0)
#define IDS_ITEMMESSAGEFILEPRINTERSETUP     (IDS_CUSTOMSTATMESSAGEMIN+1)

#define IDS_MENUMESSAGEPAGE                 (IDS_CUSTOMSTATMESSAGEMIN+2)
#define IDS_ITEMMESSAGEPAGENEWPAGE          (IDS_CUSTOMSTATMESSAGEMIN+3)
#define IDS_ITEMMESSAGEPAGEDELETEPAGE       (IDS_CUSTOMSTATMESSAGEMIN+4)
#define IDS_ITEMMESSAGEPAGENEXTPAGE         (IDS_CUSTOMSTATMESSAGEMIN+5)
#define IDS_ITEMMESSAGEPAGEPREVIOUSPAGE     (IDS_CUSTOMSTATMESSAGEMIN+6)
#define IDS_ITEMMESSAGEPAGEFIRSTPAGE        (IDS_CUSTOMSTATMESSAGEMIN+7)
#define IDS_ITEMMESSAGEPAGELASTPAGE         (IDS_CUSTOMSTATMESSAGEMIN+8)
#define IDS_ITEMMESSAGEEDITPASTESPECIAL     (IDS_CUSTOMSTATMESSAGEMIN+9)
#define IDS_ITEMMESSAGEEDITDELETEOBJECT     (IDS_CUSTOMSTATMESSAGEMIN+10)
#define IDS_ITEMMESSAGEEDITINSERTOBJECT     (IDS_CUSTOMSTATMESSAGEMIN+11)
#define IDS_ITEMMESSAGEEDITOBJECT           (IDS_CUSTOMSTATMESSAGEMIN+12)
#define IDS_ITEMMESSAGEEDITCONVERT          (IDS_CUSTOMSTATMESSAGEMIN+13)
#define IDS_ITEMMESSAGEEDITLINKS            (IDS_CUSTOMSTATMESSAGEMIN+14)
#define IDS_ITEMMESSAGEPAGESHOWOBJECTS      (IDS_CUSTOMSTATMESSAGEMIN+15)
#define IDS_STATMESSAGEMAX                  (IDS_CUSTOMSTATMESSAGEMIN+15)

//Definitions for the printing dialog.

#define IDD_PRINTING                2

#define ID_CURRENTPAGE              101
#define ID_CURRENTCOPY              102
#define ID_COPYSTRING               103
#define ID_PAGESTRING               104

#define PRINTM_PAGEUPDATE           (WM_USER+100)
#define PRINTM_COPYUPDATE           (WM_USER+101)

#endif //_RESOURCE_H_


TENANT.CPP

/*
 * TENANT.CPP
 * Patron Chapter 22
 *
 * Implementation of the CTentant class which holds information
 * for a single object on a page.  It maintains position, references
 * to data, and a storage.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CTenant::CTenant
 * CTenant::~CTenant
 *
 * Constructor Parameters:
 *  dwID            DWORD identifier for this page.
 *  hWnd            HWND of the pages window.
 *  pPG             PCPages to the parent structure.
 */

CTenant::CTenant(DWORD dwID, HWND hWnd, PCPages pPG)
    {
    m_hWnd=hWnd;
    m_dwID=dwID;

    m_fInitialized=0;
    m_pIStorage=NULL;
    m_cOpens=0;

    m_pObj=NULL;
    m_pPG =pPG;
    m_clsID=CLSID_NULL;
    m_fSetExtent=FALSE;

    m_cRef=0;
    m_pIOleObject=NULL;
    m_pIViewObject2=NULL;
    m_grfMisc=0;

    m_pImpIOleClientSite=NULL;
    m_pImpIAdviseSink=NULL;

    m_fRepaintEnabled=TRUE;
    m_pmkFile=NULL;
    m_fLinkAvail=TRUE;          //Checked on Load
    m_pmk=NULL;

    //CHAPTER22MOD
    m_pImpIOleIPSite=NULL;
    m_pIOleIPObject=NULL;
    m_rcPos.left=-1;
    m_fInRectSet=FALSE;
    //End CHAPTER22MOD
    return;
    }

CTenant::~CTenant(void)
    {
    ReleaseInterface(m_pmk);
    ReleaseInterface(m_pmkFile);

    //Object pointers cleaned up in Close.

    //We delete our own interfaces since we control them
    //CHAPTER22MOD
    DeleteInterfaceImp(m_pImpIOleIPSite);
    //End CHAPTER22MOD

    DeleteInterfaceImp(m_pImpIAdviseSink);
    DeleteInterfaceImp(m_pImpIOleClientSite);
    return;
    }

/*
 * CTenant::QueryInterface
 * CTenant::AddRef
 * CTenant::Release
 *
 * Purpose:
 *  IUnknown members for CTenant object.
 */

STDMETHODIMP CTenant::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IOleClientSite==riid)
        *ppv=m_pImpIOleClientSite;

    if (IID_IAdviseSink==riid)
        *ppv=m_pImpIAdviseSink;

    //CHAPTER22MOD
    if (IID_IOleWindow==riid || IID_IOleInPlaceSite==riid)
        *ppv=m_pImpIOleIPSite;
    //End CHAPTER22MOD

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CTenant::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CTenant::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CTenant::GetID
 *
 * Return Value:
 *  DWORD           dwID field in this tenant.
 */

DWORD CTenant::GetID(void)
    {
    return m_dwID;
    }

/*
 * CTenant::GetStorageName
 *
 * Parameters:
 *  pszName         LPOLESTR to a buffer in which to store the storage
 *                  name for this tenant.
 *
 * Return Value:
 *  UINT            Number of characters stored.
 */

UINT CTenant::GetStorageName(LPOLESTR pszName)
    {
   #ifdef WIN32ANSI
    char        szTemp[32];
    UINT        cch;

    cch=wsprintf(szTemp, "Tenant %lu", m_dwID);
    MultiByteToWideChar(CP_ACP, 0, szTemp, -1, pszName, 32);
    return cch;
   #else
    return wsprintf(pszName, TEXT("Tenant %lu"), m_dwID);
   #endif
    }

/*
 * CTenant::StorageGet
 *
 * Purpose:
 *  Returns the IStorage pointer maintained by this tenant,
 *  AddRef'd of course.
 *
 * Parameters:
 *  ppStg           LPSTORAGE * in which to return the pointer.
 *
 * Return Value:
 *  None
 */

void CTenant::StorageGet(LPSTORAGE *ppStg)
    {
    if (NULL==ppStg)
        return;

    *ppStg=m_pIStorage;

    if (NULL!=*ppStg)
        (*ppStg)->AddRef();

    return;
    }

/*
 * CTenant::Create
 *
 * Purpose:
 *  Creates a new tenant of the given CLSID, which can be either a
 *  static bitmap or metafile or any compound document object.
 *
 * Parameters:
 *  tType           TENANTTYPE to create, either a static metafile,
 *                  bitmap, or some kind of compound document object
 *                  This determines which OleCreate* call we use.
 *  pvType          LPVOID providing the relevant pointer from which
 *                  to create the tenant, depending on iType.
 *  pFE             LPFORMATETC specifying the type of renderings
 *                  to use.
 *  pptl            PPOINTL in which we store offset coordinates.
 *  pszl            LPSIZEL where this object should store its
 *                  lometric extents.
 *  pIStorage       LPSTORAGE of the page we live in.  We have to
 *                  create another storage in this for the tenant.
 *  ppo             PPATRONOBJECT containing placement data.
 *  dwData          DWORD with extra data, sensitive to iType.
 *
 * Return Value:
 *  UINT            A CREATE_* value depending on what we
 *                  actually do.
 */

UINT CTenant::Create(TENANTTYPE tType, LPVOID pvType
    , LPFORMATETC pFE, PPOINTL pptl, LPSIZEL pszl
    , LPSTORAGE pIStorage, PPATRONOBJECT ppo, DWORD dwData)
    {
    HRESULT             hr;
    LPUNKNOWN           pObj;
    UINT                uRet=CREATE_GRAPHICONLY;

    if (NULL==pvType || NULL==pIStorage)
        return CREATE_FAILED;

    //Fail if this is called for an already living tenant.
    if (m_fInitialized)
        return CREATE_FAILED;

    m_fInitialized=TRUE;

    //Create a new storage for this tenant.
    if (!Open(pIStorage))
        return CREATE_FAILED;

    /*
     * Get the placement info if it's here.  We either have a non-
     * NULL PPATRONOBJECT in ppo or we have to use default
     * placement and retrieve the size from the object itself.
     */
    pszl->cx=0;
    pszl->cy=0;

    if (NULL!=ppo)
        {
        *pFE=ppo->fe;
        *pptl=ppo->ptl;
        *pszl=ppo->szl;     //Could be 0,0 , so we ask object

        uRet=CREATE_PLACEDOBJECT;
        }

    hr=ResultFromScode(E_FAIL);

    //Now create an object based specifically for the type.
    switch (tType)
        {
        case TENANTTYPE_NULL:
            break;

        case TENANTTYPE_STATIC:
            /*
             * We could use OleCreateStaticFromData here which does
             * pretty much what we're doing below.  However, it does
             * not allow us to control whether we paste a bitmap or
             * a metafile--it uses metafile first, bitmap second.
             * For this reason we'll use code developed in Chapter
             * 11's FreeLoader to affect the paste.
             */
            hr=CreateStatic((LPDATAOBJECT)pvType, pFE, &pObj);
            break;

        case TENANTTYPE_EMBEDDEDOBJECT:
            hr=OleCreate(*((LPCLSID)pvType), IID_IUnknown
                , OLERENDER_DRAW, NULL, NULL, m_pIStorage
                , (PPVOID)&pObj);
            break;

        case TENANTTYPE_EMBEDDEDFILE:
            hr=OleCreateFromFile(CLSID_NULL, (LPTSTR)pvType
                , IID_IUnknown, OLERENDER_DRAW, NULL, NULL
                , m_pIStorage, (PPVOID)&pObj);
            break;

        case TENANTTYPE_EMBEDDEDOBJECTFROMDATA:
            hr=OleCreateFromData((LPDATAOBJECT)pvType, IID_IUnknown
                , OLERENDER_DRAW, NULL, NULL, m_pIStorage
                , (PPVOID)&pObj);
            break;

        case TENANTTYPE_LINKEDFILE:
            hr=OleCreateLinkToFile((LPTSTR)pvType, IID_IUnknown
                , OLERENDER_DRAW, NULL, NULL, m_pIStorage
                , (PPVOID)&pObj);
            break;

        case TENANTTYPE_LINKEDOBJECTFROMDATA:
            hr=OleCreateLinkFromData((LPDATAOBJECT)pvType
                , IID_IUnknown, OLERENDER_DRAW, NULL, NULL
                , m_pIStorage, (PPVOID)&pObj);
            break;

        default:
            break;
        }

    //If creation didn't work, get rid of the element Open created.
    if (FAILED(hr))
        {
        Destroy(pIStorage);
        return CREATE_FAILED;
        }

    //We don't get the size if PatronObject data was seen already.
    if (!ObjectInitialize(pObj, pFE, dwData))
        {
        Destroy(pIStorage);
        return CREATE_FAILED;
        }

    if (0==pszl->cx && 0==pszl->cy)
        {
        SIZEL   szl;

        //Try to get the real size of the object, default to 2"*2"
        SETSIZEL((*pszl), 2*LOMETRIC_PER_INCH, 2*LOMETRIC_PER_INCH);
        hr=ResultFromScode(E_FAIL);

        //Try IViewObject2 first, then IOleObject as a backup.
        if (NULL!=m_pIViewObject2)
            {
            hr=m_pIViewObject2->GetExtent(m_fe.dwAspect, -1, NULL
                , &szl);
            }
        else
            {
            if (NULL!=m_pIOleObject)
                hr=m_pIOleObject->GetExtent(m_fe.dwAspect, &szl);
            }

        if (SUCCEEDED(hr))
            {
            //Convert HIMETRIC to our LOMETRIC mapping
            SETSIZEL((*pszl), szl.cx/10, szl.cy/10);
            }
        }

    return uRet;
    }

/*
 * CTenant::Load
 *
 * Purpose:
 *  Recreates the object living in this tenant in place of calling
 *  FCreate.  This is used in loading as opposed to new creation.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE of the page we live in.
 *  pti             PTENTANTINFO containing persistent information.
 *                  The ID value in this structure is ignored.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CTenant::Load(LPSTORAGE pIStorage, PTENANTINFO pti)
    {
    HRESULT         hr;
    LPUNKNOWN       pObj;
    DWORD           dwState=TENANTSTATE_DEFAULT;

    if (NULL==pIStorage || NULL==pti)
        return FALSE;

    /*
     * If we already initialized once, clean up, releasing
     * everything before we attempt to reload.  This happens
     * when using the Convert Dialog.
     */
    if (m_fInitialized)
        {
        //Preserve all states except open
        dwState=(m_dwState & ~TENANTSTATE_OPEN);
        m_cRef++;   //Prevent accidental closure

        //This should release all holds on our IStorage as well.
        if (NULL!=m_pIViewObject2)
            {
            m_pIViewObject2->SetAdvise(m_fe.dwAspect, 0, NULL);
            ReleaseInterface(m_pIViewObject2);
            }

        ReleaseInterface(m_pIOleObject);
        ReleaseInterface(m_pObj);

        m_pIStorage=NULL;   //We'll have already released this.
        m_cRef--;           //Match safety increment above.
        }

    m_fInitialized=TRUE;

    //Open the storage for this tenant.
    if (!Open(pIStorage))
        return FALSE;

    /*
     * NOTE:  If you do not pass an IOleClientSite to OleLoad
     * it will not automatically reconnect a linked object to
     * the running source via IOleLink::BindIfRunning.  Since
     * we do not pass m_pImpIOleClientSite here, we'll call
     * BindIfRunning ourselves in ObjectInitialize.
     */
    hr=OleLoad(m_pIStorage, IID_IUnknown, NULL, (PPVOID)&pObj);

    if (FAILED(hr))
        {
        Destroy(pIStorage);
        return FALSE;
        }

    m_fSetExtent=pti->fSetExtent;
    ObjectInitialize(pObj, &pti->fe, NULL);

    //Restore the original state before reloading.
    m_dwState=dwState;

    RectSet(&pti->rcl, FALSE, FALSE);

    //CHAPTER22MOD
    /*
     * If the object is ActiveWhenVisible, send it the
     * OLEIVERB_INPLACEACTIVATE verb now.
     */
    if ((m_grfMisc & OLEMISC_ACTIVATEWHENVISIBLE))
        Activate(OLEIVERB_INPLACEACTIVATE, NULL);
    //End CHAPTER22MOD

    return TRUE;
    }

/*
 * CTenant::GetInfo
 *
 * Purpose:
 *  Retrieved a TENANTINFO structure for this tenant.
 *
 * Parameters:
 *  pti             PTENANTINFO structure to fill
 *
 * Return Value:
 *  None
 */

void CTenant::GetInfo(PTENANTINFO pti)
    {
    if (NULL!=pti)
        {
        pti->dwID=m_dwID;
        pti->rcl=m_rcl;
        pti->fe=m_fe;
        pti->fSetExtent=m_fSetExtent;
        }

    return;
    }

/*
 * CTenant::ObjectInitialize
 * (Protected)
 *
 * Purpose:
 *  Performs operations necessary after creating an object or
 *  reloading one from storage.
 *
 * Parameters:
 *  pObj            LPUNKNOWN of the object in this tenant.
 *  pFE             LPFORMATETC describing the graphic here.
 *  dwData          DWORD extra data.  If pFE->dwAspect==
 *                  DVASPECT_ICON then this is the iconic metafile.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CTenant::ObjectInitialize(LPUNKNOWN pObj, LPFORMATETC pFE
    , DWORD dwData)
    {
    HRESULT         hr;
    LPPERSIST       pIPersist=NULL;
    DWORD           dw;
    PCDocument      pDoc;
    TCHAR           szFile[CCHPATHMAX];
    LPOLELINK       pIOleLink=NULL;

    if (NULL==pObj || NULL==pFE)
        return FALSE;

    m_pObj=pObj;
    m_fe=*pFE;
    m_fe.ptd=NULL;
    m_dwState=TENANTSTATE_DEFAULT;

    /*
     * Determine the type:  Static or Embedded.  If Static,
     * this will have CLSID_Picture_Metafile or CLSID_Picture_Dib.
     * Otherwise it's embedded.  Later we'll add a case for links.
     */
    m_tType=TENANTTYPE_EMBEDDEDOBJECT;

    if (SUCCEEDED(pObj->QueryInterface(IID_IPersist
        , (PPVOID)&pIPersist)))
        {
        CLSID   clsid=CLSID_NULL;

        hr=pIPersist->GetClassID(&clsid);

        //If we don't have a CLSID, default to static
        if (FAILED(hr) || CLSID_Picture_Metafile==clsid
            || CLSID_Picture_Dib==clsid)
            m_tType=TENANTTYPE_STATIC;

        pIPersist->Release();
        }

    //Check if this is a linked object.
    if (SUCCEEDED(pObj->QueryInterface(IID_IOleLink
        , (PPVOID)&pIOleLink)))
        {
        LPMONIKER   pmk;

        hr=pIOleLink->GetSourceMoniker(&pmk);

        if (FAILED(hr) || NULL==pmk)
            m_tType=TENANTTYPE_STATIC;
        else
            {
            m_tType=TENANTTYPE_LINKEDOBJECT;
            pmk->Release();

            //Connect to the object if the source is running.
            pIOleLink->BindIfRunning();
            }

        pIOleLink->Release();
        }

    m_pIViewObject2=NULL;
    hr=pObj->QueryInterface(IID_IViewObject2
        , (PPVOID)&m_pIViewObject2);

    if (FAILED(hr))
        return FALSE;

    m_pIViewObject2->SetAdvise(m_fe.dwAspect, 0, m_pImpIAdviseSink);

    //We need an IOleObject most of the time, so get one here.
    m_pIOleObject=NULL;
    hr=pObj->QueryInterface(IID_IOleObject
         , (PPVOID)&m_pIOleObject);

    /*
     * Follow up object creation with advises and so forth.  If
     * we cannot get IOleObject here, then we know we can't do
     * any IOleObject actions from here on--object is static.
     */
    if (FAILED(hr))
        return TRUE;

    /*
     * Get the MiscStatus bits and check for OLEMISC_ONLYICONIC.
     * If set, force dwAspect in m_fe to DVASPECT_ICON so we
     * remember to draw it properly and do extents right.
     */
    m_pIOleObject->GetMiscStatus(m_fe.dwAspect, &m_grfMisc);

    if (OLEMISC_ONLYICONIC & m_grfMisc)
        m_fe.dwAspect=DVASPECT_ICON;

    /*
     * We could pass m_pImpIOleClientSite in an OleCreate* call, but
     * since this function could be called after OleLoad, we still
     * need to do this here, so it's always done here...
     */
    m_pIOleObject->SetClientSite(m_pImpIOleClientSite);
    m_pIOleObject->Advise(m_pImpIAdviseSink, &dw);

    OleSetContainedObject(m_pIOleObject, TRUE);

    /*
     * For IOleObject::SetHostNames we need the application name
     * and the document name (which is passed in the object
     * parameter).  The design of Patron doesn't give us nice
     * structured access to the name of the document we're in, so
     * I grab the parent of the Pages window (the document) and
     * send it DOCM_PDOCUMENT which returns us the pointer.
     * Roundabout, but it works.
     */

    pDoc=(PCDocument)SendMessage(GetParent(m_hWnd), DOCM_PDOCUMENT
        , 0, 0L);

    if (NULL!=pDoc)
        pDoc->FilenameGet(szFile, CCHPATHMAX);
    else
        szFile[0]=0;

    NotifyOfRename(szFile, NULL, NULL);

    /*
     * If we're creating an iconic aspect object and we have
     * an object from the Insert Object dialog, then we need to
     * store that iconic presentation in the cache, handled
     * with the utility function INOLE_SwitchDisplayAspect.  In
     * this case dwData is a handle to the metafile containing
     * the icon.  If dwData is NULL then we depend on the
     * server to provide the aspect, in which case we need
     * a view advise.
     */

    if (DVASPECT_ICON & m_fe.dwAspect)
        {
        DWORD           dw=DVASPECT_CONTENT;
        IAdviseSink    *pSink;

        pSink=(NULL==dwData) ? NULL : m_pImpIAdviseSink;

        INOLE_SwitchDisplayAspect(m_pIOleObject, &dw
            , DVASPECT_ICON, (HGLOBAL)(UINT)dwData, FALSE
            , (NULL!=dwData), pSink, NULL);
        }

    return TRUE;
    }

/*
 * CTenant::Open
 *
 * Purpose:
 *  Retrieves the IStorage associated with this tenant.  The
 *  IStorage is owned by the tenant and thus the tenant always
 *  holds a reference count.
 *
 *  If the storage is already open for this tenant, then this
 *  function will AddRef it; therefore the caller must always
 *  match an Open with a Close.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE above this tenant (which has its
 *                  own storage).
 *
 * Return Value:
 *  BOOL            TRUE if opening succeeds, FALSE otherwise.
 */

BOOL CTenant::Open(LPSTORAGE pIStorage)
    {
    HRESULT     hr=NOERROR;
    DWORD       dwMode=STGM_TRANSACTED | STGM_READWRITE
                    | STGM_SHARE_EXCLUSIVE;
    OLECHAR     szTemp[32];

    if (NULL==m_pIStorage)
        {
        if (NULL==pIStorage)
            return FALSE;

        /*
         * Attempt to open the storage under this ID.  If there is
         * none, then create it.  In either case we end up with an
         * IStorage that we either save in pPage or release.
         */

        GetStorageName(szTemp);
        hr=pIStorage->OpenStorage(szTemp, NULL, dwMode, NULL, 0
            , &m_pIStorage);

        if (FAILED(hr))
            {
            hr=pIStorage->CreateStorage(szTemp, dwMode, 0, 0
                , &m_pIStorage);
            }
        }
    else
        m_pIStorage->AddRef();

    if (FAILED(hr))
        return FALSE;

    m_cOpens++;

    //Create these if we don't have them already.
    if (NULL==m_pImpIOleClientSite && NULL==m_pImpIAdviseSink)
        {
        m_pImpIOleClientSite=new CImpIOleClientSite(this, this);
        m_pImpIAdviseSink=new CImpIAdviseSink(this, this);

        //CHAPTER22MOD
        m_pImpIOleIPSite=new CImpIOleInPlaceSite(this, this);

        if (NULL==m_pImpIOleClientSite || NULL==m_pImpIAdviseSink
            || NULL==m_pImpIOleIPSite)
            return FALSE;
        //End CHAPTER22MOD
        }

    return TRUE;
    }

/*
 * CTenant::Close
 *
 * Purpose:
 *  Possibly commits the storage, then releases it reversing the
 *  reference count from Open.  If the reference on the storage
 *  goes to zero, the storage is forgotten.  However, the object we
 *  contain is still held and as long as it's active the storage
 *  remains alive.
 *
 * Parameters:
 *  fCommit         BOOL indicating if we're to commit.
 *
 * Return Value:
 *  None
 */

void CTenant::Close(BOOL fCommit)
    {
    if (fCommit)
        Update();

    if (NULL!=m_pIStorage)
        {
        m_pIStorage->Release();

        /*
         * We can't use a zero reference count to know when to NULL
         * this since other things might have AddRef'd the storage.
         */
        if (0==--m_cOpens)
            {
            m_pIStorage=NULL;

            //CHAPTER22MOD
            //OnInPlaceDeactivate releases this pointer.
            if (NULL!=m_pIOleIPObject)
                m_pIOleIPObject->InPlaceDeactivate();
            //End CHAPTER22MOD

            //Close the object saving if necessary
            if (NULL!=m_pIOleObject)
                {
                m_pIOleObject->Close(OLECLOSE_SAVEIFDIRTY);
                ReleaseInterface(m_pIOleObject);
                }

            //Release all other held pointers
            if (NULL!=m_pIViewObject2)
                {
                m_pIViewObject2->SetAdvise(m_fe.dwAspect, 0, NULL);
                ReleaseInterface(m_pIViewObject2);
                }

            //We know we only hold one ref from Create or Load
            ReleaseInterface(m_pObj);
            }
        }

    return;
    }

/*
 * CTenant::Update
 *
 * Purpose:
 *  Forces a common on the page if it's open.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the object is open, FALSE otherwise.
 */

BOOL CTenant::Update(void)
    {
    LPPERSISTSTORAGE    pIPS;

    if (NULL!=m_pIStorage)
        {
        /*
         * We need to OleSave again because we might have changed
         * the size or position of this tenant.  We also need to
         * save the rectangle on the page, since that's not known
         * to OLE.
         */
        m_pObj->QueryInterface(IID_IPersistStorage, (PPVOID)&pIPS);

        //This fails for static objects...so we improvise if so
        if (FAILED(OleSave(pIPS, m_pIStorage, TRUE)))
            {
            //This is essentially what OleSave does.
            WriteClassStg(m_pIStorage, m_clsID);
            pIPS->Save(m_pIStorage, TRUE);
            }

        pIPS->SaveCompleted(NULL);
        pIPS->Release();

        m_pIStorage->Commit(STGC_DEFAULT);
        }

    return FALSE;
    }

/*
 * CTenant::Destroy
 *
 * Purpose:
 *  Removes this page from the given storage.  The caller should
 *  eventually delete this CTenant object to free the object herein.
 *  Nothing is committed when being destroyed.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE contianing this page on which to call
 *                  DestroyElement
 *
 * Return Value:
 *  None
 */

void CTenant::Destroy(LPSTORAGE pIStorage)
    {
    OLECHAR     szTemp[32];

    if (NULL!=pIStorage)
        {
        if (NULL!=m_pIOleObject)
            {
            //CHAPTER22MOD
            DeactivateInPlaceObject(TRUE);
            //End CHAPTER22MOD

            m_pIOleObject->Close(OLECLOSE_NOSAVE);
            }

        if (NULL!=m_pIStorage)
            {
            //Remove all reference/open counts on this storage.
            while (0!=m_cOpens)
                {
                m_pIStorage->Release();
                m_cOpens--;
                }
            }

        GetStorageName(szTemp);
        pIStorage->DestroyElement(szTemp);

        m_pIStorage=NULL;
        }

    return;
    }

/*
 * CTenant::Select
 *
 * Purpose:
 *  Selects or deselects the tenant.
 *
 * Parameters:
 *  fSelect         BOOL indicating the new state of the tenant.
 *  fActivate       BOOL indicating whether to activate or
 *                  deactivate an in-place object.  If TRUE, then
 *                  activation/deactivation will happen.  If
 *                  FALSE, no change in activation takes place.
 *
 * Return Value:
 *  None
 */

//CHAPTER22MOD
void CTenant::Select(BOOL fSelect, BOOL fActivate)
//End CHAPTER22MOD
    {
    BOOL        fWasSelected;
    DWORD       dwState;
    RECT        rc;
    HDC         hDC;

    fWasSelected=(BOOL)(TENANTSTATE_SELECTED & m_dwState);

    //Nothing to do when there's no change.
    if (fWasSelected==fSelect)
        return;

    dwState=m_dwState & ~TENANTSTATE_SELECTED;
    m_dwState=dwState | ((fSelect) ? TENANTSTATE_SELECTED : 0);

    /*
     * Draw sizing handles to show the selection state.  We convert
     * things to MM_TEXT since that's what this function expects.
     */

    RECTFROMRECTL(rc, m_rcl);
    RectConvertMappings(&rc, NULL, TRUE);
    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

    hDC=GetDC(m_hWnd);

    UIDrawHandles(&rc, hDC, UI_HANDLES_INSIDE
        | UI_HANDLES_NOBORDER | UI_HANDLES_USEINVERSE
        , CXYHANDLE, !fWasSelected);

    ReleaseDC(m_hWnd, hDC);

    if (fSelect)
        m_pPG->m_fDirty=TRUE;

    //CHAPTER22MOD
    if (fActivate)
        {
        if (!fSelect)
            DeactivateInPlaceObject(FALSE);
        else
            {
            if (m_grfMisc & OLEMISC_INSIDEOUT)
                {
                MSG     msg;
                DWORD   dw;

                //Include a message for in-place objects.
                msg.hwnd=NULL;
                msg.message=WM_LBUTTONDOWN;
                msg.wParam=0;
                msg.time=GetMessageTime();

                dw=GetMessagePos();
                msg.lParam=dw;
                SETPOINT(msg.pt, LOWORD(dw), HIWORD(dw));

                Activate(OLEIVERB_UIACTIVATE, &msg);
                }
            }
        }
    //End CHAPTER22MOD

    return;
    }

/*
 * CTenant::ShowAsOpen
 *
 * Purpose:
 *  Draws or removes the hatch pattern over an object.
 *
 * Parameters:
 *  fOpen           BOOL indicating the open state of this tenant.
 *
 * Return Value:
 *  None
 */

void CTenant::ShowAsOpen(BOOL fOpen)
    {
    BOOL        fWasOpen;
    DWORD       dwState;
    RECT        rc;
    HDC         hDC;

    fWasOpen=(BOOL)(TENANTSTATE_OPEN & m_dwState);

    dwState=m_dwState & ~TENANTSTATE_OPEN;
    m_dwState=dwState | ((fOpen) ? TENANTSTATE_OPEN : 0);

    //If this was not open, then just hatch, otherwise repaint.
    if (!fWasOpen && fOpen)
        {
        RECTFROMRECTL(rc, m_rcl);
        RectConvertMappings(&rc, NULL, TRUE);
        OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

        hDC=GetDC(m_hWnd);
        UIDrawShading(&rc, hDC, UI_SHADE_FULLRECT, 0);
        ReleaseDC(m_hWnd, hDC);
        }

    if (fWasOpen && !fOpen)
        Repaint();

    return;
    }

/*
 * CTenant::ShowYourself
 *
 * Purpose:
 *  Function that really just implements IOleClientSite::ShowObject.
 *  Here we first check if the tenant is fully visible, and if so,
 *  then nothing needs to happen.  Otherwise, if the upper left
 *  corner of the tenant is in the upper left visible quadrant of
 *  the window, we'll also consider ourselves done.  Otherwise
 *  we'll put the upper left corner of the object at the upper left
 *  corner of the window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CTenant::ShowYourself(void)
    {
    RECTL       rcl;
    RECT        rc;
    POINT       pt1, pt2;

    //Scrolling deals in device units; get our rectangle in those.
    RectGet(&rcl, TRUE);

    //Get the window rectangle offset for the current scroll pos.
    GetClientRect(m_hWnd, &rc);
    OffsetRect(&rc, m_pPG->m_xPos, m_pPG->m_yPos);

    //Check if the object is already visible. (macro in bookguid.h)
    SETPOINT(pt1, (int)rcl.left,  (int)rcl.top);
    SETPOINT(pt2, (int)rcl.right, (int)rcl.bottom);

    //CHAPTER22MOD
    /*
     * If we're doing in-place, don't move anything if the
     * object is visible AT ALL.  IOleInPlaceSite::OnInPlaceActivate
     * will have been called by now--the object will always
     * make that call before showing itself.
     */
    if (NULL!=m_pIOleIPObject && PtInRect(&rc, pt1))
        return;
    //End CHAPTER22MOD

    if (PtInRect(&rc, pt1) && PtInRect(&rc, pt2))
        return;

    //Check if the upper left is within the upper left quadrant
    if (((int)rcl.left > rc.left
        && (int)rcl.left < ((rc.right+rc.left)/2))
        && ((int)rcl.top > rc.top
        && (int)rcl.top < ((rc.bottom+rc.top)/2)))
        return;

    //These are macros in INC\BOOK1632.H

SendScrollPosition(m_hWnd, WM_HSCROLL, rcl.left-8);
    SendScrollPosition(m_hWnd, WM_VSCROLL, rcl.top-8);
    return;
    }

/*
 * CTenant::AddVerbMenu
 *
 * Purpose:
 *  Creates the variable verb menu item for the object in this
 *  tenant.
 *
 * Parmeters:
 *  hMenu           HMENU on which to add items.
 *  iPos            UINT position on that menu to add items.
 *
 * Return Value:
 *  None
 */

void CTenant::AddVerbMenu(HMENU hMenu, UINT iPos)
    {
    HMENU       hMenuTemp;
    LPOLEOBJECT pObj=m_pIOleObject;

    //If we're static, say we have no object.
    if (TENANTTYPE_STATIC==m_tType)
        pObj=NULL;

    OleUIAddVerbMenu(pObj, NULL, hMenu, iPos, IDM_VERBMIN
        , IDM_VERBMAX, TRUE, IDM_EDITCONVERT, &hMenuTemp);

    return;
    }

/*
 * CTenant::TypeGet
 *
 * Purpose:
 *  Returns the type of this tenant
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  TENANTTYPE      Type of the tenant.
 */

TENANTTYPE CTenant::TypeGet(void)
    {
    return m_tType;
    }

/*
 * CTenant::CopyEmbeddedObject
 *
 * Purpose:
 *  Copies an embedded object to the given data object (via SetData,
 *  assuming this is a data transfer object for clipboard/drag-drop)
 *  if that's what we're holding.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT in which to store the copy.
 *  pFE             LPFORMATETC into which to copy CFSTR_EMBEDDEDOBJECT
 *                  if we put that in the data object.
 *  pptl            PPOINTL to the pick point (NULL outside of
 *                  drag-drop);
 *
 * Return Value:
 *  None
 */

void CTenant::CopyEmbeddedObject(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, PPOINTL pptl)
    {
    LPPERSISTSTORAGE    pIPS;
    STGMEDIUM           stm;
    FORMATETC           fe;
    HRESULT             hr;
    UINT                cf;
    POINTL              ptl;
    SIZEL               szl;

    //Can only copy embeddings.
    if (TENANTTYPE_EMBEDDEDOBJECT!=m_tType || NULL==m_pIOleObject)
        return;

    if (NULL==pptl)
        {
        SETPOINTL(ptl, 0, 0);
        pptl=&ptl;
        }

    /*
     * Create CFSTR_EMBEDDEDOBJECT.  This is simply an IStorage with
     * a copy of the embedded object in it.  The not-so-simple part
     * is getting an IStorage to stuff it in.  For this operation
     * we'll use a temporary compound file.
     */

    stm.pUnkForRelease=NULL;
    stm.tymed=TYMED_ISTORAGE;
    hr=StgCreateDocfile(NULL, STGM_TRANSACTED | STGM_READWRITE
        | STGM_CREATE| STGM_SHARE_EXCLUSIVE | STGM_DELETEONRELEASE
        , 0, &stm.pstg);

    if (FAILED(hr))
        return;

    m_pObj->QueryInterface(IID_IPersistStorage, (PPVOID)&pIPS);

    if (NOERROR==pIPS->IsDirty())
        {
        OleSave(pIPS, stm.pstg, FALSE);
        pIPS->SaveCompleted(NULL);
        }
    else
        m_pIStorage->CopyTo(0, NULL, NULL, stm.pstg);

    pIPS->Release();

    //stm.pstg now has a copy, so stuff it away.
    cf=RegisterClipboardFormat(CFSTR_EMBEDDEDOBJECT);
    SETDefFormatEtc(fe, cf, TYMED_ISTORAGE);

    if (SUCCEEDED(pIDataObject->SetData(&fe, &stm, TRUE)))
        *pFE=fe;
    else
        ReleaseStgMedium(&stm);

    stm.tymed=TYMED_HGLOBAL;

    /*
     * You want to make sure that if this object is iconic, that you
     * create the object descriptor with DVASPECT_ICON instead of
     * the more typical DVASPECT_CONTENT.  Also remember that
     * the pick point is in HIMETRIC.
     */
    XformSizeInPixelsToHimetric(NULL, (LPSIZEL)pptl, (LPSIZEL)&ptl);

    SETSIZEL(szl, (10*(m_rcl.right-m_rcl.left))
        , (10 * (m_rcl.bottom-m_rcl.top)));

    stm.hGlobal=INOLE_ObjectDescriptorFromOleObject
        (m_pIOleObject, m_fe.dwAspect, ptl, &szl);

    cf=RegisterClipboardFormat(CFSTR_OBJECTDESCRIPTOR);
    SETDefFormatEtc(fe, cf, TYMED_HGLOBAL);

    if (FAILED(pIDataObject->SetData(&fe, &stm, TRUE)))
        ReleaseStgMedium(&stm);

    return;
    }

/*
 * CTenant::CopyLinkedObject
 *
 * Purpose:
 *  Copies an linked object to the given data object (via SetData,
 *  assuming this is a data transfer object for clipboard/drag-drop)
 *  if that's what we're holding.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT in which to store the copy.
 *  pFE             LPFORMATETC into which to copy CF_LINKSOURCE
 *                  if we put that in the data object.
 *  pptl            PPOINTL to the pick point (NULL outside of
 *                  drag-drop);
 *
 * Return Value:
 *  None
 */

void CTenant::CopyLinkedObject(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, PPOINTL pptl)
    {
    STGMEDIUM           stm;
    FORMATETC           fe;
    HRESULT             hr;
    UINT                cf;
    POINTL              ptl;
    LPMONIKER           pmk;
    CLSID               clsID;
    LPTSTR              psz=NULL;
    SIZEL               szl;

    //Can only copy links to embeddings from here.
    if (TENANTTYPE_EMBEDDEDOBJECT!=m_tType || NULL==m_pIOleObject)
        return;

    //If we don't have a full moniker, no linking allowed
    if (NULL==m_pmk)
        return;

    //If the object doesn't support this, return.
    if (OLEMISC_CANTLINKINSIDE & m_grfMisc)
        return;

    if (NULL==pptl)
        {
        SETPOINTL(ptl, 0, 0);
        pptl=&ptl;
        }

    /*
     * We need to get CFSTR_LINKSOURCE, but the server may not be
     * running, in which case we just grab the moniker and CLSID
     * for this object and call INOLE_GetLinkSourceData.
     */

    m_pIOleObject->GetUserClassID(&clsID);
    hr=m_pIOleObject->GetMoniker(0, OLEWHICHMK_OBJFULL, &pmk);

    if (FAILED(hr))
        return;

    stm.pUnkForRelease=NULL;
    stm.tymed=TYMED_NULL;
    cf=RegisterClipboardFormat(CFSTR_LINKSOURCE);
    SETDefFormatEtc(fe, cf, TYMED_ISTREAM);
    hr=INOLE_GetLinkSourceData(pmk, &clsID, &fe, &stm);

    if (FAILED(hr))
        {
        pmk->Release();
        return;
        }

    //Send it to the data object for transfer
    if (SUCCEEDED(pIDataObject->SetData(&fe, &stm, TRUE)))
        *pFE=fe;
    else
        ReleaseStgMedium(&stm);

    XformSizeInPixelsToHimetric(NULL, (LPSIZEL)pptl, (LPSIZEL)&ptl);

    SETSIZEL(szl, (10*(m_rcl.right-m_rcl.left))
        , (10 * (m_rcl.bottom-m_rcl.top)));

    stm.hGlobal=INOLE_ObjectDescriptorFromOleObject
        (m_pIOleObject, m_fe.dwAspect, ptl, &szl);

    //Better set these properly or errors occur.
    stm.tymed=TYMED_HGLOBAL;
    stm.pUnkForRelease=NULL;

    cf=RegisterClipboardFormat(CFSTR_LINKSRCDESCRIPTOR);
    SETDefFormatEtc(fe, cf, TYMED_HGLOBAL);

    if (FAILED(pIDataObject->SetData(&fe, &stm, TRUE)))
        ReleaseStgMedium(&stm);

    return;
    }

/*
 * CTenant::ShowObjectType
 *
 * Purpose:
 *  Tells the object to switch on or off an indication of whether
 *  it is linked or embedded.
 *
 * Parameters:
 *  fShow           BOOL indicating to show the type (TRUE) or
 *                  not (FALSE)
 *
 * Return Value:
 *  None
 */

void CTenant::ShowObjectType(BOOL fShow)
    {
    BOOL        fWasShow;
    DWORD       dwState;
    RECT        rc;
    HDC         hDC;

    fWasShow=(BOOL)(TENANTSTATE_SHOWTYPE & m_dwState);

    dwState=m_dwState & ~TENANTSTATE_SHOWTYPE;
    m_dwState=dwState | ((fShow) ? TENANTSTATE_SHOWTYPE : 0);

    /*
     * If this wasn't previously shown, just add the line,
     * otherwise repaint.
     */
    if (!fWasShow && fShow)
        {
        RECTFROMRECTL(rc, m_rcl);
        RectConvertMappings(&rc, NULL, TRUE);
        OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

        hDC=GetDC(m_hWnd);
        UIShowObject(&rc, hDC, (TENANTTYPE_LINKEDOBJECT==m_tType));
        ReleaseDC(m_hWnd, hDC);
        }

    if (fWasShow && !fShow)
        Repaint();

    return;
    }

/*
 * CTenant::NotifyOfRename
 *
 * Purpose:
 *  Instructs the tenant that the document was saved under a
 *  different name.  In order to keep the right compound document
 *  user interface, this tenant needs to tell its object through
 *  IOleObject::SetHostNames.
 *
 * Parameters:
 *  pszFile         LPTSTR of filename.
 *  pmkFile         LPMONIKER of the new filename.  If this and
 *                  pmkPage are NULL then nothing happens with
 *                  monikers.
 *  pmkPage         LPMONIKER of the page we're in.
 *
 * Return Value:
 *  None
 */

void CTenant::NotifyOfRename(LPTSTR pszFile, LPMONIKER pmkFile
    , LPMONIKER pmkPage)
    {
    TCHAR       szObj[40];
    TCHAR       szApp[40];

    if (NULL==m_pIOleObject)
        return;

    if (TEXT('\0')==*pszFile)
        {
        LoadString(m_pPG->m_hInst, IDS_UNTITLED, szObj
            , sizeof(szObj));
        }
    else
        {
        GetFileTitle(pszFile, szObj, sizeof(szObj));

       #ifndef WIN32
        //Force filenames to uppercase in DOS versions.
        AnsiUpper(szObj);
       #endif
        }

    LoadString(m_pPG->m_hInst, IDS_CAPTION, szApp, sizeof(szApp));
   #ifdef WIN32ANSI
    OLECHAR     szObjW[40], szAppW[40];

    MultiByteToWideChar(CP_ACP, 0, szObj, -1, szObjW, 40);
    MultiByteToWideChar(CP_ACP, 0, szApp, -1, szAppW, 40);
    m_pIOleObject->SetHostNames(szAppW, szObjW);
   #else
    m_pIOleObject->SetHostNames(szApp, szObj);
   #endif

    if (NULL!=pmkFile)
        {
        ReleaseInterface(m_pmkFile);
        m_pmkFile=pmkFile;
        m_pmkFile->AddRef();

        m_pIOleObject->SetMoniker(OLEWHICHMK_CONTAINER, pmkFile);
        }

    if (NULL!=pmkFile && NULL!=pmkPage)
        {
        LPMONIKER   pmkTenant=NULL;
        LPMONIKER   pmkRel=NULL;
        HRESULT     hr;

        //Create the moniker for this tenant.
       #ifdef WIN32ANSI
        GetStorageName(szObjW);
        WideCharToMultiByte(CP_ACP, 0, szObjW, -1, szObj, 40
           , NULL, NULL);
       #else
        GetStorageName(szObj);
       #endif
        hr=CreateItemMoniker(TEXT("!"), szObj, &pmkTenant);

        if (SUCCEEDED(hr))
            {
            //Create the relative moniker, i.e. no pathname.
            hr=pmkPage->ComposeWith(pmkTenant, FALSE, &pmkRel);
            pmkTenant->Release();

            //Hold on to the relative moniker
            ReleaseInterface(m_pmk);
            m_pmk=pmkRel;

            if (SUCCEEDED(hr))
                m_pIOleObject->SetMoniker(OLEWHICHMK_OBJREL, pmkRel);
            }
        }

    return;
    }

/*
 * CTenant::Activate
 *
 * Purpose:
 *  Activates a verb on the object living in the tenant.  Does
 *  nothing for static objects.
 *
 * Parameters:
 *  iVerb           LONG of the verb to execute.
 *  pMSG            LPMSG to the message causing the invocation.
 *
 * Return Value:
 *  BOOL            TRUE if the object changed due to this verb
 *                  execution.
 */

//CHAPTER22MOD
BOOL CTenant::Activate(LONG iVerb, LPMSG pMSG)
//End CHAPTER22MOD
    {
    RECT        rc, rcH;
    CHourglass *pHour;
    SIZEL       szl;

    //Can't activate statics.
    if (TENANTTYPE_STATIC==m_tType || NULL==m_pIOleObject)
        {
        MessageBeep(0);
        return FALSE;
        }

    RECTFROMRECTL(rc, m_rcl);
    RectConvertMappings(&rc, NULL, TRUE);
    XformRectInPixelsToHimetric(NULL, &rc, &rcH);

    pHour=new CHourglass;

    //Get the server running first, then do a SetExtent, then show it
    OleRun(m_pIOleObject);

    if (m_fSetExtent)
        {
        SETSIZEL(szl, rcH.right-rcH.left, rcH.top-rcH.bottom);
        m_pIOleObject->SetExtent(m_fe.dwAspect, &szl);
        m_fSetExtent=FALSE;
        }

    //CHAPTER22MOD
    m_pIOleObject->DoVerb(iVerb, pMSG, m_pImpIOleClientSite, 0
        , m_hWnd, &rcH);
    //End CHAPTER22MOD

    delete pHour;

    //If object changes, IAdviseSink::OnViewChange will see it.
    return FALSE;
    }

/*
 * CTenant::Draw
 *
 * Purpose:
 *  Draws the tenant in its rectangle on the given hDC.  We assume
 *  the DC is already set up for the mapping mode in which our
 *  rectangle is expressed, since the Page we're in tells us both
 *  the rect and the hDC.
 *
 * Parameters:
 *  hDC             HDC in which to draw.  Could be a metafile,
 *                  memory DC, screen, or printer.
 *  ptd             DVTARGETDEVICE * describing the device.
 *  hIC             HDC holding an information context (printing).
 *  xOff, yOff      int offsets for the page in lometric
 *  fNoColor        BOOL indicating if we should do B & W
 *  fPrinter        BOOL indicating if we should render for a
 *                  printer.
 *
 * Return Value:
 *  None
 */

void CTenant::Draw(HDC hDC, DVTARGETDEVICE *ptd, HDC hIC
    , int xOff, int yOff, BOOL fNoColor, BOOL fPrinter)
    {
    HRESULT         hr;
    RECT            rc;
    RECTL           rcl;
    UINT            uMM;

    //CHAPTER22MOD
    /*
     * Don't bother drawing anything but shading if we're doing in-place.
     * It would just cause a lot of flicker.
     */
    if (NULL!=m_pIOleIPObject)
        return;
    //End CHAPTER22MOD

    RECTFROMRECTL(rc, m_rcl);
    OffsetRect(&rc, -xOff, -yOff);
    RECTLFROMRECT(rcl, rc);

    //Repaint erases the rectangle to insure full object cleanup
    if (!fNoColor && !fPrinter)
        {
        COLORREF    cr;
        cr=SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
        ExtTextOut(hDC, rc.left, rc.top, ETO_OPAQUE, &rc, NULL
            , 0, NULL);
        SetBkColor(hDC, cr);
        }

    //We have to use Draw since we have a target device and IC.
    hr=m_pIViewObject2->Draw(m_fe.dwAspect, -1, NULL, ptd, hIC, hDC
        , &rcl, NULL, NULL, 0);

    /*
     * If Draw failed, then perhaps it couldn't work for the device,
     * so try good old OleDraw as a last resort.  The code will
     * generally be OLE_E_BLANK.
     */
    if (FAILED(hr))
        OleDraw(m_pObj, m_fe.dwAspect, hDC, &rc);

    if (!fPrinter)
        {
        /*
         * Draw sizing handles to show the selection state.  We
         * convert things to MM_TEXT since that's what this
         * function expects.
         */
        RectConvertMappings(&rc, NULL, TRUE);
        uMM=SetMapMode(hDC, MM_TEXT);

        if (TENANTSTATE_SELECTED & m_dwState)
            {
            UIDrawHandles(&rc, hDC, UI_HANDLES_INSIDE
                | UI_HANDLES_NOBORDER | UI_HANDLES_USEINVERSE
                , CXYHANDLE, TRUE);
            }

        if (TENANTSTATE_OPEN & m_dwState)
            UIDrawShading(&rc, hDC, UI_SHADE_FULLRECT, 0);

        //Distinguish linked and embedded objects.
        if (TENANTSTATE_SHOWTYPE & m_dwState)
            {
            UIShowObject(&rc, hDC
                , (TENANTTYPE_LINKEDOBJECT==m_tType));
            }

        uMM=SetMapMode(hDC, uMM);
        }

    return;
    }

/*
 * CTenant::Repaint
 * CTenant::Invalidate
 *
 * Purpose:
 *  Repaints the tenant where it lies or invalidates its area
 *  for later repainting.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CTenant::Repaint(void)
    {
    RECT        rc;
    HDC         hDC;

    /*
     * We might be asked to repaint from
     * IOleClientSite::OnShowWindow after we've switched pages if
     * our server was running. This check on m_cOpens prevents that.
     */
    if (0==m_cOpens || !m_fRepaintEnabled)
        return;

    hDC=GetDC(m_hWnd);
    SetRect(&rc, m_pPG->m_xPos, m_pPG->m_yPos, 0, 0);
    RectConvertMappings(&rc, NULL, FALSE);

    SetMapMode(hDC, MM_LOMETRIC);
    Draw(hDC, NULL, NULL, rc.left, rc.top, FALSE, FALSE);

    ReleaseDC(m_hWnd, hDC);
    return;
    }

void CTenant::Invalidate(void)
    {
    RECTL       rcl;
    RECT        rc;

    RectGet(&rcl, TRUE);
    RECTFROMRECTL(rc, rcl);

    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);
    InvalidateRect(m_hWnd, &rc, FALSE);

    return;
    }

/*
 * CTenant::FIsSelected
 *
 * Purpose:
 *  Returns the selection state of this tenant.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if selected, FALSE otherwise.
 */

BOOL CTenant::FIsSelected(void)
    {
    return (BOOL)(m_dwState & TENANTSTATE_SELECTED);
    }

/*
 * CTenant::ConvertToStatic
 *
 * Purpose:
 *  Changes the object that lives in this tenant to a static one.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CTenant::ConvertToStatic(void)
    {
    /*
     * If you SetSourceMoniker in IOleLink to NULL, then the link is
     * gone as far as OLE is concerned.  You only need to make sure
     * the user doesn't have access to other functionality for this
     * object, which we insure by changing our internal type.  We
     * set this on loading if GetSourceMoniker returns NULL.
     */
    m_tType=TENANTTYPE_STATIC;
    return TRUE;
    }

/*
 * CTenant::ObjectClassFormatAndIcon
 *
 * Purpose:
 *  Helper function for CPage::ConvertObject to retrieve necessary
 *  information about the object.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store the CLSID.
 *  pwFormat        LPWORD in which to store the clipboard format
 *                  used.
 *  ppszType        LPTSTR * in which to return a pointer to a
 *                  type string.
 *  phMetaIcon      HGLOBAL * in which to return the metafile
 *                  icon currently in use.
 *
 * Return Value:
 *  None
 */

void CTenant::ObjectClassFormatAndIcon(LPCLSID pClsID
    , LPWORD pwFormat, LPTSTR *ppszType, HGLOBAL *phMetaIcon
    , LPTSTR *ppszLabel)
    {
    HRESULT         hr;
    TCHAR           szType[128];
    LPDATAOBJECT    pIDataObject;
    FORMATETC       fe;
    STGMEDIUM       stm;

    if (TENANTTYPE_EMBEDDEDOBJECT!=m_tType || NULL==m_pIOleObject)
        return;

    if (NULL==pClsID || NULL==pwFormat || NULL==ppszType
        || NULL==phMetaIcon)
        return;

    /*
     * For embedded objects get the real CLSID of the object and
     * its format string.  If this fails then we can try to ask
     * the object, or we can look in the registry.
     */

    hr=ReadClassStg(m_pIStorage, pClsID);

    if (FAILED(hr))
        {
        hr=m_pIOleObject->GetUserClassID(pClsID);

        if (FAILED(hr))
            *pClsID=CLSID_NULL;
        }

    hr=ReadFmtUserTypeStg(m_pIStorage, pwFormat, ppszType);

    if (FAILED(hr))
        {
        *pwFormat=0;
        *ppszType=NULL;

        if (INOLE_GetUserTypeOfClass(*pClsID, 0, szType
            , sizeof(szType)))
            {
            *ppszType=INOLE_CopyString(szType);
            }
        }

    /*
     * Try to get the AuxUserType from the registry, using
     * the short version (registered under AuxUserType\2).
     * If that fails, just copy *ppszType.
     */
    *ppszLabel=NULL;

    if (INOLE_GetUserTypeOfClass(*pClsID, 2, szType
        , sizeof(szType)))
        {
        *ppszLabel=INOLE_CopyString(szType);
        }
    else
        *ppszLabel=INOLE_CopyString(*ppszType);

    //Get the icon for this thing, if we're iconic.
    *phMetaIcon=NULL;

    hr=m_pObj->QueryInterface(IID_IDataObject
        , (PPVOID)&pIDataObject);

    if (SUCCEEDED(hr))
        {
        SETFormatEtc(fe, CF_METAFILEPICT, DVASPECT_ICON, NULL
            , TYMED_MFPICT, -1);
        hr=pIDataObject->GetData(&fe, &stm);
        pIDataObject->Release();

        if (SUCCEEDED(hr))
            *phMetaIcon=stm.hGlobal;
        else
            *phMetaIcon=OleGetIconOfClass(*pClsID, NULL, TRUE);
        }

    return;
    }

/*
 * CTenant::SwitchOrUpdateAspect
 *
 * Purpose:
 *  Switches between DVASPECT_CONTENT and DVASPECT_ICON
 *
 * Parameters:
 *  hMetaIcon       HGLOBAL to the new icon if we're changing the
 *                  icon or switching to DVASPECT_ICON.  NULL to
 *                  change back to content.
 *  fPreserve       BOOL indicating if we're to preserve the old
 *                  aspect after changing.
 *
 * Return Value:
 *  BOOL            TRUE if anything changed, FALSE otherwise.
 */

BOOL CTenant::SwitchOrUpdateAspect(HGLOBAL hMetaIcon
    , BOOL fPreserve)
    {
    HRESULT     hr;
    DWORD       dwAspect;
    BOOL        fUpdate=FALSE;

    //Nothing to do if we're content already and there's no icon.
    if (NULL==hMetaIcon && DVASPECT_CONTENT==m_fe.dwAspect)
        return FALSE;

    //If we're iconic already, just cache the new icon
    if (NULL!=hMetaIcon && DVASPECT_ICON==m_fe.dwAspect)
        hr=INOLE_SetIconInCache(m_pIOleObject, hMetaIcon);
    else
        {
        //Otherwise, switch between iconic and content.
        dwAspect=(NULL==hMetaIcon) ? DVASPECT_CONTENT : DVASPECT_ICON;

        /*
         * Switch between aspects, where dwAspect has the new one
         * and m_fe.dwAspect will be changed in the process.
         */
        hr=INOLE_SwitchDisplayAspect(m_pIOleObject
            , &m_fe.dwAspect, dwAspect, hMetaIcon, !fPreserve
            , TRUE, m_pImpIAdviseSink, &fUpdate);

        if (SUCCEEDED(hr))
            {
            //Update MiscStatus for the new aspect
            m_pIOleObject->GetMiscStatus(m_fe.dwAspect, &m_grfMisc);

            if (fUpdate)
                m_pIOleObject->Update();    //This repaints.
            }
        }

    //If we switched, update our extents.
    if (SUCCEEDED(hr))
        {
        SIZEL       szl;

        m_pIOleObject->GetExtent(m_fe.dwAspect, &szl);

        if (0 > szl.cy)
            szl.cy=-szl.cy;

        //Convert HIMETRIC absolute units to our LOMETRIC mapping
        if (0!=szl.cx && 0!=szl.cy)
            SETSIZEL(szl, szl.cx/10, -szl.cy/10);

        Invalidate();                   //Remove old aspect
        SizeSet(&szl, FALSE, FALSE);    //Change size
        Repaint();                      //Paint the new one
        }

    return SUCCEEDED(hr);
    }

/*
 * CTenant::EnableRepaint
 *
 * Purpose:
 *  Toggles whether the Repaint function does anything.  This
 *  is used during conversion/emulation of an object to disable
 *  repaints until the new object can be given the proper extents.
 *
 * Parameters:
 *  fEnable         TRUE to enable repaints, FALSE to disable.
 *
 * Return Value:
 *  None
 */

void CTenant::EnableRepaint(BOOL fEnable)
    {
    m_fRepaintEnabled=fEnable;
    return;
    }

/*
 * CTenant::ObjectGet
 *
 * Purpose:
 *  Retrieves the LPUNKNOWN of the object in use by this tenant
 *
 * Parameters:
 *  ppUnk           LPUNKNOWN * in which to return the object
 *                  pointer.
 *
 * Return Value:
 *  None
 */

void CTenant::ObjectGet(LPUNKNOWN *ppUnk)
    {
    if (NULL!=ppUnk)
        {
        *ppUnk=m_pObj;
        m_pObj->AddRef();
        }

    return;
    }

/*
 * CTenant::FormatEtcGet
 *
 * Purpose:
 *  Retrieves the FORMATETC in use by this tenant
 *
 * Parameters:
 *  pFE             LPFORMATETC in which to store the information.
 *  fPresentation   BOOL indicating if we want the real format or
 *                  that of the presentation.
 *
 * Return Value:
 *  None
 */

void CTenant::FormatEtcGet(LPFORMATETC pFE, BOOL fPresentation)
    {
    if (NULL!=pFE)
        {
        *pFE=m_fe;

        //If there is no format, use metafile (for embedded objects)
        if (fPresentation || 0==pFE->cfFormat)
            {
            //Don't mess with dwAspect; might be icon or content.
            pFE->cfFormat=CF_METAFILEPICT;
            pFE->tymed=TYMED_MFPICT;
            }
        }

    return;
    }

/*
 * CTenant::SizeGet
 * CTenant::SizeSet
 * CTenant::RectGet
 * CTenant::RectSet
 *
 * Purpose:
 *  Returns or sets the size/position of the object contained here.
 *
 * Parameters:
 *  pszl/prcl       LPSIZEL (Size) or LPRECTL (Rect) with the
 *                  extents of interest.  In Get situations,
 *                  this will receive the extents; in Set it
 *                  contains the extents.
 *  fDevice         BOOL indicating that pszl/prcl is expressed
 *                  in device units.  Otherwise it's LOMETRIC.
 *  fInformObj      (Set Only) BOOL indicating if we need to inform
 *                  the object all.
 *
 * Return Value:
 *  None
 */

void CTenant::SizeGet(LPSIZEL pszl, BOOL fDevice)
    {
    if (!fDevice)
        {
        pszl->cx=m_rcl.right-m_rcl.left;
        pszl->cy=m_rcl.bottom-m_rcl.top;
        }
    else
        {
        RECT        rc;

        SetRect(&rc, (int)(m_rcl.right-m_rcl.left)
            , (int)(m_rcl.bottom-m_rcl.top), 0, 0);

        RectConvertMappings(&rc, NULL, TRUE);

        pszl->cx=(long)rc.left;
        pszl->cy=(long)rc.top;
        }

    return;
    }

void CTenant::SizeSet(LPSIZEL pszl, BOOL fDevice, BOOL fInformObj)
    {
    SIZEL           szl;

    if (!fDevice)
        {
        szl=*pszl;
        m_rcl.right =pszl->cx+m_rcl.left;
        m_rcl.bottom=pszl->cy+m_rcl.top;
        }
    else
        {
        RECT        rc;

        SetRect(&rc, (int)pszl->cx, (int)pszl->cy, 0, 0);
        RectConvertMappings(&rc, NULL, FALSE);

        m_rcl.right =(long)rc.left+m_rcl.left;
        m_rcl.bottom=(long)rc.top+m_rcl.top;

        SETSIZEL(szl, (long)rc.left, (long)rc.top);
        }

    //Tell OLE that this object was resized.
    if (NULL!=m_pIOleObject && fInformObj)
        {
        HRESULT     hr;
        BOOL        fRun=FALSE;

        //Convert our LOMETRIC into HIMETRIC by *=10
        szl.cx*=10;
        szl.cy*=-10;    //Our size is stored negative.

        /*
         * If the MiscStatus bit of OLEMISC_RECOMPOSEONRESIZE
         * is set, then we need to run the object before calling
         * SetExtent to make sure it has a real chance to
         * re-render the object.  We have to update and close
         * the object as well after this happens.
         */

        if (OLEMISC_RECOMPOSEONRESIZE & m_grfMisc)
            {
            if (!OleIsRunning(m_pIOleObject))
                {
                OleRun(m_pIOleObject);
                fRun=TRUE;
                }
            }

        hr=m_pIOleObject->SetExtent(m_fe.dwAspect, &szl);

        /*
         * If the object is not running and it does not have
         * RECOMPOSEONRESIZE, then SetExtent fails.  Make
         * sure that we call SetExtent again (by just calling
         * SizeSet here again) when we next run the object.
         */
        if (SUCCEEDED(hr))
            {
            m_fSetExtent=FALSE;

            if (fRun)
                {
                m_pIOleObject->Update();
                m_pIOleObject->Close(OLECLOSE_SAVEIFDIRTY);
                }
            }
        else
            {
            if (OLE_E_NOTRUNNING==GetScode(hr))
                m_fSetExtent=TRUE;
            }
        }

    return;
    }

void CTenant::RectGet(LPRECTL prcl, BOOL fDevice)
    {
    if (!fDevice)
        *prcl=m_rcl;
    else
        {
        RECT        rc;

        RECTFROMRECTL(rc, m_rcl);
        RectConvertMappings(&rc, NULL, TRUE);
        RECTLFROMRECT(*prcl, rc);
        }

    return;
    }

void CTenant::RectSet(LPRECTL prcl, BOOL fDevice, BOOL fInformObj)
    {
    SIZEL   szl;
    LONG    cx, cy;

    //CHAPTER22MOD
    /*
     * Prevent reentrant calls that may come from calling
     * UpdateInPlaceObjectRects here and elsewhere.
     */
    if (m_fInRectSet)
        return;

    m_fInRectSet=TRUE;
    //End CHAPTER22MOD

    cx=m_rcl.right-m_rcl.left;
    cy=m_rcl.bottom-m_rcl.top;

    if (!fDevice)
        m_rcl=*prcl;
    else
        {
        RECT        rc;

        RECTFROMRECTL(rc, *prcl);

RectConvertMappings(&rc, NULL, FALSE);
        RECTLFROMRECT(m_rcl, rc);
        }

    /*
     * Tell ourselves that the size changed, if it did.  SizeSet
     * will call IOleObject::SetExtent for us.
     */
    if ((m_rcl.right-m_rcl.left)!=cx || (m_rcl.bottom-m_rcl.top)!=cy)
        {
        SETSIZEL(szl, m_rcl.right-m_rcl.left, m_rcl.bottom-m_rcl.top);
        SizeSet(&szl, FALSE, fInformObj);
        }

    //CHAPTER22MOD
    //Tell an in-place active object it moved too
    UpdateInPlaceObjectRects(NULL, TRUE);
    m_fInRectSet=FALSE;
    //End CHAPTER22MOD

    return;
    }

/*
 * CTenant::CreateStatic
 * (Protected)
 *
 * Purpose:
 *  Creates a new static bitmap or metafile object for this tenant
 *  using a freeloading method allowing us to specify exactly which
 *  type of data we want to paste since OleCreateStaticFromData
 *  doesn't.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *  pFE             LPFORMATETC describing the format to paste.
 *  ppObj           LPUNKNOWN * into which we store the
 *                  object pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, error code otherwise.
 */

HRESULT CTenant::CreateStatic(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, LPUNKNOWN *ppObj)
    {
    HRESULT             hr;
    STGMEDIUM           stm;
    LPUNKNOWN           pIUnknown;
    LPOLECACHE          pIOleCache;
    LPPERSISTSTORAGE    pIPersistStorage;
    CLSID               clsID;

    *ppObj=NULL;

    //Try to get the data desired as specified in pFE->cfFormat
    hr=pIDataObject->GetData(pFE, &stm);

    if (FAILED(hr))
        return hr;

    //Create the object to handle this data.
    if (CF_METAFILEPICT==pFE->cfFormat)
        clsID=CLSID_Picture_Metafile;
    else
        clsID=CLSID_Picture_Dib;

    hr=CreateDataCache(NULL, clsID, IID_IUnknown
        , (PPVOID)&pIUnknown);

    if (FAILED(hr))
        {
        ReleaseStgMedium(&stm);
        return hr;
        }

    m_clsID=clsID;

    //Stuff the data into the object
    pIUnknown->QueryInterface(IID_IPersistStorage
        , (PPVOID)&pIPersistStorage);
    pIPersistStorage->InitNew(m_pIStorage);

    //Now that we have the cache object, shove the data into it.
    pIUnknown->QueryInterface(IID_IOleCache, (PPVOID)&pIOleCache);
    pIOleCache->Cache(pFE, ADVF_PRIMEFIRST, NULL);

    hr=pIOleCache->SetData(pFE, &stm, TRUE);
    pIOleCache->Release();

    //Insure there is a persistent copy on the disk
    WriteClassStg(m_pIStorage, m_clsID);
    pIPersistStorage->Save(m_pIStorage, TRUE);
    pIPersistStorage->SaveCompleted(NULL);
    pIPersistStorage->Release();

    //The cache owns this now.
    ReleaseStgMedium(&stm);

    if (FAILED(hr))
        pIUnknown->Release();
    else
        *ppObj=pIUnknown;

    return hr;
    }

//CHAPTER22MOD
/*
 * CTenant::DeactivateInPlaceObject
 *
 * Purpose:
 *  Deactivates an in-place object if there is one in this tenant.
 *
 * Parameters:
 *  fFull           BOOL indicating full deactivation of UI
 *                  deactivate only.
 *
 * Return Value:
 *  None
 */

void CTenant::DeactivateInPlaceObject(BOOL fFull)
    {
    if (NULL!=m_pIOleIPObject)
        {
        /*
         * Activate-when-visible objects only UI deactivate
         * unless we're fully deactivating on purpose.
         */
        if ((OLEMISC_ACTIVATEWHENVISIBLE & m_grfMisc) && !fFull)
            m_pIOleIPObject->UIDeactivate();
        else
            m_pIOleIPObject->InPlaceDeactivate();
        }

    return;
    }

/*
 * CTenant::UpdateInPlaceObjectRects
 *
 * Purpose:
 *  Generates a call to IOleInPlaceObject::SetObjectRects to allow
 *  it to show it's shading and its object adornments properly.
 *  This function deals in HIMETRIC units.
 *
 * Parameters:
 *  prcPos          LPCRECT to the size the object wants.  Ignored
 *                  if NULL in which case we use the size of the
 *                  tenant.  This rect is in client coordinates of
 *                  the pages window.
 *  fUseTenantRect  BOOL indicating if we need to use the tenant
 *                  rectangle offset for scrolling regardless.
 *
 * Return Value:
 *  None
 */

void CTenant::UpdateInPlaceObjectRects(LPCRECT prcPos
    , BOOL fUseTenantRect)
    {
    RECTL       rcl;
    RECT        rc;
    RECT        rcClip;
    BOOL        fResizeTenant=TRUE;

    /*
     * Note that if the object here is activate-when-visible
     * we'll always have this pointer.
     */
    if (NULL!=m_pIOleIPObject)
        {
        /*
         * This uses the last position rectangle from
         * IOleInPlaceSite::OnPosRectChange if it's been
         * initialized
         */
        if (NULL==prcPos && -1!=m_rcPos.left && !fUseTenantRect)
            prcPos=&m_rcPos;

        //This code is normally called from OnPosRectChange direct.
        if (NULL!=prcPos && !fUseTenantRect)
            {
            rc=*prcPos;

            //Calculate the boundaries of the full page
            m_pPG->CalcBoundingRect(&rcClip, FALSE);

            //Make sure we limit object to page boundaries.
            IntersectRect(&rc, &rc, &rcClip);
            }
        else
            {
            /*
             * We have no rectangle of the object on which to
             * base the position, so just use the tenant rectangle.
             * This code is also used when scrolling objects.
             */
            RectGet(&rcl, TRUE);
            RECTFROMRECTL(rc, rcl);

            //Account for scrolling
            OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);
            fResizeTenant=FALSE;
            }

        //We don't clip special anywhere in our window.
        SetRect(&rcClip, 0, 0, 32767, 32767);

        /*
         * NOTE:  The rectangles to SetObjectRects is in client
         * coordinates of the pages window.
         */
        if (NULL!=m_pIOleIPObject)
            m_pIOleIPObject->SetObjectRects(&rc, &rcClip);

        if (fResizeTenant)
            {
            //Need to tell the tenant to change position too
            RECTLFROMRECT(rcl, rc);
            RectSet(&rcl, TRUE, FALSE);
            }
        }

    return;
    }

/*
 * CTenant::ObjectWindow
 *
 * Purpose:
 *  Returns the window handle of the in-place object.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HWND            Handle of the object window.
 */

HWND CTenant::ObjectWindow(void)
    {
    HWND    hWnd=NULL;

    if (NULL!=m_pIOleIPObject)
        m_pIOleIPObject->GetWindow(&hWnd);

    return hWnd;
    }
//End CHAPTER22MOD


TENANT.H

/*
 * TENANT.H
 * Patron Chapter 22
 *
 * Definitions and function prototypes for the CTenant class
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _TENANT_H_
#define _TENANT_H_

class CImpIOleClientSite : public IOleClientSite
    {
    protected:
        ULONG               m_cRef;
        class CTenant      *m_pTen;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIOleClientSite(class CTenant *, LPUNKNOWN);
        ~CImpIOleClientSite(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP SaveObject(void);
        STDMETHODIMP GetMoniker(DWORD, DWORD, LPMONIKER *);
        STDMETHODIMP GetContainer(LPOLECONTAINER *);
        STDMETHODIMP ShowObject(void);
        STDMETHODIMP OnShowWindow(BOOL);
        STDMETHODIMP RequestNewObjectLayout(void);
    };

typedef CImpIOleClientSite *PCImpIOleClientSite;

class CImpIAdviseSink : public IAdviseSink
    {
    protected:
        ULONG               m_cRef;
        class CTenant      *m_pTen;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIAdviseSink(class CTenant *, LPUNKNOWN);
        ~CImpIAdviseSink(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(void)  OnDataChange(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP_(void)  OnViewChange(DWORD, LONG);
        STDMETHODIMP_(void)  OnRename(LPMONIKER);
        STDMETHODIMP_(void)  OnSave(void);
        STDMETHODIMP_(void)  OnClose(void);
    };

typedef CImpIAdviseSink *PCImpIAdviseSink;

//CHAPTER22MOD
class CImpIOleInPlaceSite : public IOleInPlaceSite
    {
    protected:
        ULONG               m_cRef;
        class CTenant      *m_pTen;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIOleInPlaceSite(class CTenant *, LPUNKNOWN);
        ~CImpIOleInPlaceSite(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetWindow(HWND *);
        STDMETHODIMP ContextSensitiveHelp(BOOL);
        STDMETHODIMP CanInPlaceActivate(void);
        STDMETHODIMP OnInPlaceActivate(void);
        STDMETHODIMP OnUIActivate(void);
        STDMETHODIMP GetWindowContext(LPOLEINPLACEFRAME *
                        , LPOLEINPLACEUIWINDOW *, LPRECT, LPRECT
                        , LPOLEINPLACEFRAMEINFO);
        STDMETHODIMP Scroll(SIZE);
        STDMETHODIMP OnUIDeactivate(BOOL);
        STDMETHODIMP OnInPlaceDeactivate(void);
        STDMETHODIMP DiscardUndoState(void);
        STDMETHODIMP DeactivateAndUndo(void);
        STDMETHODIMP OnPosRectChange(LPCRECT);
    };

typedef CImpIOleInPlaceSite *PCImpIOleInPlaceSite;
//End CHAPTER22MOD

/*
 * Tenant class describing an individual piece of data in a page.
 * It knows where it sits, what object is inside of it, and what
 * its idenitifer is such that it can find it's storage within a
 * page.
 */

//Patron Objects clipboard format
typedef struct tagPATRONOBJECT
    {
    POINTL      ptl;        //Location of object
    POINTL      ptlPick;    //Pick point from drag-drop operation
    SIZEL       szl;        //Extents of object (absolute)
    FORMATETC   fe;         //Actual object format
    } PATRONOBJECT, *PPATRONOBJECT;

//Values for hit-testing, drawing, and resize-tracking tenants
#define CXYHANDLE       5

//Tenant types (not persistent, but determined at load time)
typedef enum
    {
    TENANTTYPE_NULL=0,
    TENANTTYPE_STATIC,
    TENANTTYPE_EMBEDDEDOBJECT,
    TENANTTYPE_EMBEDDEDFILE,
    TENANTTYPE_EMBEDDEDOBJECTFROMDATA,
    TENANTTYPE_LINKEDOBJECT,
    TENANTTYPE_LINKEDFILE,
    TENANTTYPE_LINKEDOBJECTFROMDATA
    } TENANTTYPE, *PTENANTTYPE;

//State flags
#define TENANTSTATE_DEFAULT      0x00000000
#define TENANTSTATE_SELECTED     0x00000001
#define TENANTSTATE_OPEN         0x00000002
#define TENANTSTATE_SHOWTYPE     0x00000004

/*
 * Persistent information we need to save for each tenant, which is
 * done in the tenant list instead of with each tenant.  Since this
 * is a small structure it's best not to blow another stream for it
 * (overhead).
 */
typedef struct tagTENANTINFO
    {
    DWORD       dwID;
    RECTL       rcl;
    FORMATETC   fe;             //Excludes ptd
    short       fSetExtent;     //Call IOleObject::SetExtent on Run
    } TENANTINFO, *PTENANTINFO;

class CTenant : public IUnknown
    {
    friend CImpIOleClientSite;
    friend CImpIAdviseSink;
    //CHAPTER22MOD
    friend CImpIOleInPlaceSite;
    //End CHAPTER22MOD

    private:
        HWND            m_hWnd;         //Pages window
        DWORD           m_dwID;         //Persistent DWORD ID
        DWORD           m_cOpens;       //Count calls to Open

        BOOL            m_fInitialized; //Something here?
        LPUNKNOWN       m_pObj;         //The object here
        LPSTORAGE       m_pIStorage;    //Sub-storage for tenant

        FORMATETC       m_fe;           //Used to create the object
        DWORD           m_dwState;      //State flags
        RECTL           m_rcl;          //Space of this object
        CLSID           m_clsID;        //Object class (for statics)
        BOOL            m_fSetExtent;   //Call SetExtent on next run

        class CPages   *m_pPG;          //Pages window

        TENANTTYPE      m_tType;            //Type identifier
        ULONG           m_cRef;             //We're an object now
        LPOLEOBJECT     m_pIOleObject;      //IOleObject on m_pObj
        LPVIEWOBJECT2   m_pIViewObject2;    //IViewObject2 on m_pObj
        ULONG           m_grfMisc;          //OLEMISC flags
        BOOL            m_fRepaintEnabled;  //No redundant paints

        //Our interfaces
        PCImpIOleClientSite m_pImpIOleClientSite;
        PCImpIAdviseSink    m_pImpIAdviseSink;

        LPMONIKER       m_pmk;          //Relative moniker
        LPMONIKER       m_pmkFile;      //Container document moniker

        //CHAPTER22MOD
        PCImpIOleInPlaceSite m_pImpIOleIPSite; //Our interface
        LPOLEINPLACEOBJECT   m_pIOleIPObject;  //The object
        RECT                 m_rcPos;          //Last object rect
        BOOL                 m_fInRectSet;     //Prevent reentrancy
        //End CHAPTER22MOD

    /*
     * This flag is used exculsively by the implementation of
     * IOleUILinkContainer on the page we're in for the Links
     * Dialog. Since we never use it ourselves, public here
     * is no big deal.
     */
    public:
        BOOL            m_fLinkAvail;

    protected:
        BOOL    ObjectInitialize(LPUNKNOWN, LPFORMATETC, DWORD);
        HRESULT CreateStatic(LPDATAOBJECT, LPFORMATETC
            , LPUNKNOWN *);

    public:
        CTenant(DWORD, HWND, CPages *);
        ~CTenant(void);

        //Gotta have an IUnknown for delegation
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        DWORD       GetID(void);
        UINT        GetStorageName(LPOLESTR);
        void        StorageGet(LPSTORAGE *);
        UINT        Create(TENANTTYPE, LPVOID, LPFORMATETC, PPOINTL
                        , LPSIZEL, LPSTORAGE, PPATRONOBJECT, DWORD);
        BOOL        Load(LPSTORAGE, PTENANTINFO);
        void        GetInfo(PTENANTINFO);
        BOOL        Open(LPSTORAGE);
        void        Close(BOOL);
        BOOL        Update(void);
        void        Destroy(LPSTORAGE);

        //CHAPTER22MOD
        void        Select(BOOL, BOOL);
        //End CHAPTER22MOD
        void        ShowAsOpen(BOOL);
        void        ShowYourself(void);
        void        AddVerbMenu(HMENU, UINT);
        TENANTTYPE  TypeGet(void);
        void        CopyEmbeddedObject(LPDATAOBJECT, LPFORMATETC
                        , PPOINTL);
        void        ShowObjectType(BOOL);
        void        CopyLinkedObject(LPDATAOBJECT, LPFORMATETC
                        , PPOINTL);
        void        NotifyOfRename(LPTSTR, LPMONIKER, LPMONIKER);

        //CHAPTER22MOD
        BOOL        Activate(LONG, LPMSG);
        //End CHAPTER22MOD

        void        Draw(HDC, DVTARGETDEVICE *, HDC, int, int
                        , BOOL, BOOL);
        void        Repaint(void);
        void        Invalidate(void);

        void        ObjectClassFormatAndIcon(LPCLSID, LPWORD
                        , LPTSTR *, HGLOBAL *, LPTSTR *);
        BOOL        SwitchOrUpdateAspect(HGLOBAL, BOOL);
        void        EnableRepaint(BOOL);

        BOOL        FIsSelected(void);
        BOOL        ConvertToStatic(void);

        void        ObjectGet(LPUNKNOWN *);
        void        FormatEtcGet(LPFORMATETC, BOOL);
        void        SizeGet(LPSIZEL, BOOL);
        void        SizeSet(LPSIZEL, BOOL, BOOL);
        void        RectGet(LPRECTL, BOOL);
        void        RectSet(LPRECTL, BOOL, BOOL);

        //CHAPTER22MOD
        void        DeactivateInPlaceObject(BOOL);
        void        UpdateInPlaceObjectRects(LPCRECT, BOOL);
        HWND        ObjectWindow(void);
        //End CHAPTER22MOD
    };

typedef CTenant *PCTenant;

//Return codes for Create
#define CREATE_FAILED               0
#define CREATE_GRAPHICONLY          1
#define CREATE_PLACEDOBJECT         2

#endif //_TENANT_H_


README.TXT

To build a version of Component Cosmo that uses POLY23.DLL, copy
the sources from CHAP13\COCOSMO and modify the function
CCosmoDoc::FInit in DOCUMENT.CPP to use CLSID_Polyline23 instead
of CLSID_Polyline10 in the CoCreateInstance call:

Nothing else in CoCosmo needs to change to use the revised Polyline.
Note that CoCosmo still uses the IPolyline10 interface on the Polyline
object.  For all intents and purposes, nothing should change at all
in the execution of CoCosmo, demonstrating how interfaces do not
interfere with each other:  CoCosmo ignores the compound document
interfaces.

Just note how all the in-place activation support in Polyline does
not preclude Component Cosmo from using like a non-compound document
embedded object!


CLIENT.CPP

/*
 * CLIENT.CPP
 * Cosmo Chapter 23
 *
 * Implementation of the CCosmoClient class that just makes sure
 * we get a CCosmoDoc on doc creation and that we initialize fully.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CCosmoClient::CCosmoClient
 * CCosmoClient::~CCosmoClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame to the frame object.
 */

CCosmoClient::CCosmoClient(HINSTANCE hInst, PCFrame pFR)
    : CClient(hInst, pFR)
    {
    return;
    }

CCosmoClient::~CCosmoClient(void)
    {
    return;
    }

/*
 * CCosmoClient::CreateCDocument
 *
 * Purpose:
 *  Constructs a new document specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::CreateCDocument(void)
    {
    return (PCDocument)(new CCosmoDoc(m_hInst, m_pFR, m_pAdv));
    }

/*
 * CCosmoClient::NewDocument
 *
 * Purpose:
 *  Small override of the CClient::NewDocument that we have just
 *  to check the initial line selection on new document creation.
 *
 * Parameters:
 *  fVisible        BOOL indicating if the document is to be
 *                  visible or not.
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CCosmoClient::NewDocument(BOOL fVisible)
    {
    PCDocument  pDoc;

    //Perform default NewDocument first
    pDoc=CClient::NewDocument(fVisible);

    //We know that m_pFR is actually a CCosmoFrame, so type is safe.
    ((PCCosmoFrame)m_pFR)->CheckLineSelection(IDM_LINESOLID);

    return pDoc;
    }


COSMO.CPP

/*
 * COSMO.CPP
 * Cosmo Chapter 23
 *
 * WinMain and CCosmoFrame implementations.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "cosmo.h"

/*
 * These are for proper implementation of the class factory,
 * OLE Documents support, and shutdown conditions.
 */
ULONG g_cObj=0;
ULONG g_cLock=0;
HWND  g_hWnd=NULL;
BOOL  g_fUser=FALSE;

//CHAPTER23MOD
//This is filled by CFigure on in-place activation
PCToolBar  g_pInPlaceTB=NULL;
//End CHAPTER23MOD

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCCosmoFrame    pFR;
    FRAMEINIT       fi;
    WPARAM          wRet;

    SETMESSAGEQUEUE;

    //Attempt to allocate and initialize the application
    pFR=new CCosmoFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return -1;

    fi.idsMin=IDS_FRAMEMIN;
    fi.idsMax=IDS_FRAMEMAX;
    fi.idsStatMin=IDS_STATMESSAGEMIN;
    fi.idsStatMax=IDS_STATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=440;
    fi.cy=460;

    //If we can initialize pFR, start chugging messages
    if (pFR->Init(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Cosmo Figure object to call when it gets
 *  destroyed.  We destroy the main window if the proper conditions
 *  are met for shutdown.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;

    //No more objects, no locks, no user control, shut the app down.
    if (0L==g_cObj && 0L==g_cLock && IsWindow(g_hWnd) && !g_fUser)
        PostMessage(g_hWnd, WM_CLOSE, 0, 0L);

    return;
    }

/*
 * CCosmoFrame::CCosmoFrame
 * CCosmoFrame::~CCosmoFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WInMain
 */

CCosmoFrame::CCosmoFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    : CFrame(hInst, hInstPrev, pszCmdLine, nCmdShow)
    {
    UINT        i;

    for (i=0; i<5; i++)
        m_hBmpLines[i]=NULL;

    m_uIDCurLine=0;
    m_fInitialized=FALSE;
    m_pIClassDataTran=NULL;

    m_fEmbedding=FALSE;
    m_dwRegCO=0;
    m_pIClassFactory=NULL;

    //CHAPTER23MOD
    m_pIOleIPFrame=NULL;    //Flag for in-place or not.
    //End CHAPTER23MOD

    return;
    }

CCosmoFrame::~CCosmoFrame(void)
    {
    UINT        i;

    //Reverse CoRegisterClassObject, takes class factory ref to 1
    if (0L!=m_dwRegCO)
        CoRevokeClassObject(m_dwRegCO);

    ReleaseInterface(m_pIClassFactory);

    for (i=0; i<5; i++)
        {
        if (NULL!=m_hBmpLines[i])
            DeleteObject(m_hBmpLines[i]);
        }

    if (NULL!=m_pIClassDataTran)
        {
        m_pIClassDataTran->LockServer(FALSE);
        m_pIClassDataTran->Release();
        }

    OleFlushClipboard();

    if (m_fInitialized)
        OleUninitialize();

    return;
    }

/*
 * CCosmoFrame::Init
 *
 * Purpose:
 *  Call CoInitialize then calling down into the base class
 *  initialization.
 *
 * Parameters:
 *  pFI             PFRAMEINIT containing initialization parameters.
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded, FALSE otherwise.
 */

BOOL CCosmoFrame::Init(PFRAMEINIT pFI)
    {
    HRESULT     hr;

    CHECKVER_OLE;

    if (FAILED(OleInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    hr=CoGetClassObject(CLSID_DataTransferObject
        , CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory
        , (PPVOID)&m_pIClassDataTran);

    if (SUCCEEDED(hr))
        m_pIClassDataTran->LockServer(TRUE);

    //Check for command line flags
    ParseCommandLine();

    if (NULL!=m_ppszCmdArgs)
        {
        if(0==lstrcmpi(m_ppszCmdArgs[0], TEXT("-Embedding"))
           || 0==lstrcmpi(m_ppszCmdArgs[0], TEXT("/Embedding")))
            m_fEmbedding=TRUE;
        }

    g_fUser=!m_fEmbedding;

    /*
     * Create our class factory and register it for this application
     * using CoRegisterClassObject.  The REGCLS_*USE flags have to
     * do with servicable object from this instance, not with MDI or
     * SDI.  Since it's most convenient to be single use, we'll do
     * this in either version.
     *
     * In addition, it only makes sense to do any of this if we're
     * being launched to be a server.
     */
    if (m_fEmbedding)
        {
        m_pIClassFactory=new CFigureClassFactory(this);

        if (NULL==m_pIClassFactory)
            return FALSE;

        //Since we hold on to this, we should AddRef it.
        m_pIClassFactory->AddRef();

        hr=CoRegisterClassObject(CLSID_CosmoFigure, m_pIClassFactory
            , CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE, &m_dwRegCO);

        if (FAILED(hr))
            return FALSE;
        }

    return CFrame::Init(pFI);
    }

//CHAPTER23MOD

/*
 * CCosmoFrame::MessageLoop
 *
 * Purpose:
 *  Override of standard message loop function in CLASSLIB to use
 *  in-place accelerators when necessary.  We only need to make
 *  sure we call OleTranslateAccelerator as appropriate.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  WPARAM          Contents of msg.wParam from WM_QUIT.
 */

WPARAM CCosmoFrame::MessageLoop(void)
    {
    MSG     msg;

    while (GetMessage(&msg, NULL, 0,0 ))
        {
        //If we're in-place, don't bother with MDI accelerators
        if (NULL==m_pIOleIPFrame)
            {
            if (m_pCL->TranslateAccelerator(&msg))
                continue;
            }

        //Translate our accelerators.
        if (TranslateAccelerator(m_hWnd, m_hAccel, &msg))
            continue;

        /*
         * Let the container have a crack during in-place.  Note
         * that if we were an MDI multiple-use server, which we
         * are not, then we'd have to make sure that the document
         * with the in-place object was active, and we would
         * have to make sure that we use the right IOleInPlaceFrame
         * and frame info variables.  So what's shown here is a
         * somewhat simplified case.
         */
        if (NULL!=m_pIOleIPFrame)
            {
            if (NOERROR==OleTranslateAccelerator(m_pIOleIPFrame
                , &m_frameInfo, &msg))
                continue;
            }

        TranslateMessage(&msg);
        DispatchMessage(&msg);
        }

    return msg.wParam;
    }

/*
 * CCosmoFrame::FMessageHook
 *
 * Purpose:
 *  Override of CFrame::FMessageHook so we can specifically trap
 *  WM_MENUSELECT messages in order to call
 *  IOleInPlaceFrame::SetStatusText.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CCosmoFrame::FMessageHook(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam, LRESULT *pLRes)
    {
    TCHAR           szText[128];

    if (WM_MENUSELECT!=iMsg)
        return FALSE;

    if (NULL==m_pIOleIPFrame)
        return FALSE;

    /*
     * We have to now get the message that the StatStrip would
     * display for this menu over to the container.  We do this
     * by letting the StatStrip do its thing first on our currently
     * hidden window (beware of this technique if you are multiple
     * use and might be visible!) then we ask the StatStrip for its
     * current text which we send to the container via
     * IOleInPlaceFrame::SetStatusText.
     */

    m_pSL->MenuSelect(wParam, lParam);
    m_pSL->MessageGet(szText, sizeof(szText));
   #ifdef WIN32ANSI
    OLECHAR     szTemp[128];

    MultiByteToWideChar(CP_ACP, 0, szText, -1, szTemp, 128);
    m_pIOleIPFrame->SetStatusText(szTemp);
   #else
    m_pIOleIPFrame->SetStatusText(szText);
   #endif

    *pLRes=0L;
    return TRUE;
    }

//End CHAPTER23MOD

/*
 * CCosmoFrame::CreateCClient
 *
 * Purpose:
 *  Constructs a new client specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient        Pointer to the new client object.
 */

PCClient CCosmoFrame::CreateCClient(void)
    {
    return (PCClient)(new CCosmoClient(m_hInst, this));
    }

/*
 * CCosmoFrame::RegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CCosmoFrame::RegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //First let the standard frame do its thing
    if (!CFrame::RegisterAllClasses())
        return FALSE;

    /*
     * We want a different background color for the document
     * because the Polyline we put in the document will paint
     * with COLOR_WINDOW which by default which is CLASSLIB's
     * default document color.
     */

    GetClassInfo(m_hInst, SZCLASSDOCUMENT, &wc);
    UnregisterClass(SZCLASSDOCUMENT, m_hInst);

    wc.hbrBackground=(HBRUSH)(COLOR_APPWORKSPACE+1);

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

    //Register the Polyline window.
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.hInstance     = m_hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PolylineWndProc;
    wc.cbWndExtra    = CBPOLYLINEWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_CROSS);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPOLYLINE;

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

    //CHAPTER23MOD
    if (!HatchWindowRegister(m_hInst))
        return FALSE;
    //End CHAPTER23MOD

    return TRUE;
    }

/*
 * CCosmoFrame::PreShowInit
 *
 * Purpose:
 *  Called from Init before intially showing the window.  We do
 *  whatever else we want here, modifying nCmdShow as necessary
 *  which affects ShowWindow in Init.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if this initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CCosmoFrame::PreShowInit(void)
    {
    CreateLineMenu();
    CheckLineSelection(IDM_LINESOLID);

    //Save the window handle for shutdown if necessary.
    g_hWnd=m_hWnd;

    //If we're under OLE control, don't show the main window.
    if (m_fEmbedding)
        m_nCmdShow=SW_HIDE;

    return TRUE;
    }

/*
 * CCosmoFrame::CreateLineMenu
 *
 * Purpose:
 *  Initializes the bitmaps used to create the Line menu and
 *  replaces the text items defined in the application resources
 *  with these bitmaps.  Note that the contents of m_hBmpLines
 *  must be cleaned up when the application terminates.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CreateLineMenu(void)
    {
    HMENU       hMenu;
    HDC         hDC, hMemDC;
    HPEN        hPen;
    HGDIOBJ     hObj;
    TEXTMETRIC  tm;
    UINT        i, cx, cy;

    hMenu=GetSubMenu(GetMenu(m_hWnd), 3);   //Line menu.
    hDC=GetDC(m_hWnd);

    //Create each line in a menu item 8 chars wide, one char high.
    GetTextMetrics(hDC, &tm);
    cx=tm.tmAveCharWidth*8;
    cy=tm.tmHeight;

    /*
     * Create a memory DC in which to draw lines, and bitmaps
     * for each line.
     */
    hMemDC=CreateCompatibleDC(hDC);
    ReleaseDC(m_hWnd, hDC);

    for (i=0; i<5; i++)
        {
        m_hBmpLines[i]=CreateCompatibleBitmap(hMemDC, cx, cy);
        SelectObject(hMemDC, m_hBmpLines[i]);

        PatBlt(hMemDC, 0, 0, cx, cy, WHITENESS);

        hPen=CreatePen(i, 1, 0L);       //i=line style like PS_SOLID
        hObj=SelectObject(hMemDC, hPen);

        MoveToEx(hMemDC, 0, cy/2, NULL);
        LineTo(hMemDC, cx, cy/2);

        ModifyMenu(hMenu, IDM_LINEMIN+i, MF_BYCOMMAND | MF_BITMAP
            , IDM_LINEMIN+i, (LPTSTR)(LONG)(UINT)m_hBmpLines[i]);

        SelectObject(hMemDC, hObj);
        DeleteObject(hPen);
        }

    CheckMenuItem(hMenu, IDM_LINESOLID, MF_CHECKED);
    DeleteDC(hMemDC);

    return;
    }

/*
 * CCosmoFrame::CreateToolbar
 *
 * Purpose:
 *  Procedure to create all the necessary toolbar buttons.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of tools added to the bar.
 */

UINT CCosmoFrame::CreateToolbar(void)
    {
    UINT            iLast;
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;
    UINT            utEx  =GIZMOTYPE_BUTTONATTRIBUTEEX;

    //Insert the standard ones.
    iLast=CFrame::CreateToolbar();

    /*
     * Insert File Import in the 5th position and account for
     * it in iLast.
     */
    m_pTB->Add(utCmd, 4, IDM_FILEIMPORT, m_dxB, m_dyB
        , NULL, m_hBmp, 2, uState);
    iLast++;

    //Separator
    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    /*
     * For the Background bitmap, preserve our use of black
     * (part of the image)
     */
    m_pTB->Add(utCmd, iLast++, IDM_COLORBACKGROUND, m_dxB, m_dyB
        , NULL, m_hBmp, 3, GIZMO_NORMAL | PRESERVE_BLACK);

    m_pTB->Add(utCmd, iLast++, IDM_COLORLINE, m_dxB, m_dyB
        , NULL, m_hBmp, 4, uState);

    //Separator
    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //Line styles.
    m_pTB->Add(utEx, iLast++, IDM_LINESOLID, m_dxB, m_dyB
        , NULL, m_hBmp, 5, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASH, m_dxB, m_dyB
        , NULL, m_hBmp, 6, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 7, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASHDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 8, uState);
    m_pTB->Add(utEx, iLast++, IDM_LINEDASHDOTDOT, m_dxB, m_dyB
        , NULL, m_hBmp, 9, uState);

    return iLast;
    }

/*
 * CCosmoFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the Cosmo frame window that just
 *  processes the line menu and the color menu leaving the
 *  CFrame to do everything else.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CCosmoFrame::OnCommand(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PCCosmoDoc      pDoc;
    TCHAR           szFile[CCHPATHMAX];
    BOOL            fOK;
    UINT            i, uTemp;
    COLORREF        rgColors[16];
    CHOOSECOLOR     cc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many toolbar notifications.
     */
    if (m_fInit)
        return 0L;

    pDoc=(PCCosmoDoc)m_pCL->ActiveDocument();

    /*
     * Check for the line style commands which are
     * IDM_LINEMIN+<style>.  We handle this by changing the menu
     * and toolbar, then we pass it to the document for real
     * processing.
     */
    if (NULL!=pDoc && IDM_LINEMIN <= wID && IDM_LINEMAX >=wID)
        {
        CheckLineSelection(wID);
        pDoc->LineStyleSet(wID-IDM_LINEMIN);
        return 0L;
        }

    switch (wID)
        {
        case IDM_FILEIMPORT:
            szFile[0]=0;
            fOK=SaveOpenDialog(szFile, CCHPATHMAX, IDS_FILEIMPORT
                , TRUE, &i);

            if (fOK)
                {
                uTemp=pDoc->Load(FALSE, szFile);
                pDoc->ErrorMessage(uTemp);
                }

            return (LRESULT)fOK;

        case IDM_COLORBACKGROUND:
        case IDM_COLORLINE:
            //Invoke the color chooser for either color
            uTemp=(IDM_COLORBACKGROUND==wID)
                ? DOCCOLOR_BACKGROUND : DOCCOLOR_LINE;

            for (i=0; i<16; i++)
                rgColors[i]=RGB(0, 0, i*16);

            memset(&cc, 0, sizeof(CHOOSECOLOR));
            cc.lStructSize=sizeof(CHOOSECOLOR);
            cc.lpCustColors=rgColors;

            //CHAPTER23MOD
            /*
             * If we're in-place active, use the container's frame
             * window instead of our own as the parent of the dialog.
             */
            if (NULL!=m_pIOleIPFrame)
                m_pIOleIPFrame->GetWindow(&cc.hwndOwner);
            else
                cc.hwndOwner=hWnd;
            //End CHAPTER23MOD

            cc.Flags=CC_RGBINIT;
            cc.rgbResult=pDoc->ColorGet(uTemp);

            if (ChooseColor(&cc))
                pDoc->ColorSet(uTemp, cc.rgbResult);

            break;

        //CHAPTER23MOD
        case ID_HATCHWINDOW:
            //Open the in-place object on hatch border double-click.
            if (HWN_BORDERDOUBLECLICKED==wCode)
                pDoc->OpenInPlaceObject();

            break;

        case IDM_EDITOPEN:
            //Invoke the OPEN verb on the current object.
            pDoc->OpenInPlaceObject();
            break;
        //End CHAPTER23MOD

        default:
           CFrame::OnCommand(hWnd, wParam, lParam);
        }

    return 0L;
    }

/*
 * CCosmoFrame::OnDocumentDataChange
 *
 * Purpose:
 *  Update the Line menu and toolbar if the style in the data
 *  changes.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentDataChange(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::OnDocumentActivate
 *
 * Purpose:
 *  Informs us that document activation changed, so update the UI
 *  for that new document.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::OnDocumentActivate(PCDocument pDoc)
    {
    CheckLineSelection(IDM_LINEMIN
        +((PCCosmoDoc)pDoc)->LineStyleGet());
    return;
    }

/*
 * CCosmoFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    PCDocument  pDoc;
    BOOL        fOK=FALSE;
    BOOL        fCallDefault=TRUE;
    UINT        i;
    UINT        uTemp;
    UINT        uTempE;
    UINT        uTempD;

    pDoc=m_pCL->ActiveDocument();

    uTempE=MF_ENABLED | MF_BYCOMMAND;
    uTempD=MF_DISABLED | MF_GRAYED | MF_BYCOMMAND;
    uTemp=((NULL!=pDoc) ? uTempE : uTempD);

    //File menu:  If there is document window, disable Import.
    if (m_phMenu[0]==hMenu)
        EnableMenuItem(hMenu, IDM_FILEIMPORT, uTemp);

    //Color menu:  no document, no commands
    if (m_phMenu[2]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_COLORBACKGROUND, uTemp);
        EnableMenuItem(hMenu, IDM_COLORLINE,       uTemp);
        fCallDefault=FALSE;
        }

    //Line menu:  no document, no commands
    if (m_phMenu[3]==hMenu)
        {
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            EnableMenuItem(hMenu, i, uTemp);

        fCallDefault=FALSE;
        }

    if (fCallDefault)
        CFrame::UpdateMenus(hMenu, iMenu);

    return;
    }

/*
 * CCosmoFrame::UpdateToolbar
 *
 * Purpose:
 *  Enables and disables tools depending on whether we have
 *  a document or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateToolbar(void)
    {
    BOOL        fLast;
    UINT        i;

    //Save the last enabled state before CFrame changes it
    fLast=m_fLastEnable;

    //Let the default hack on its tools
    CFrame::UpdateToolbar();

    /*
     * If CFrame::UpdateToolbar changed anything, then we need
     * to change as well--if nothing changes, nothing to do.
     */
    if (fLast!=m_fLastEnable)
        {
        m_pTB->Enable(IDM_FILEIMPORT, m_fLastEnable);

        m_pTB->Enable(IDM_COLORBACKGROUND, m_fLastEnable);
        m_pTB->Enable(IDM_COLORLINE,       m_fLastEnable);

        for (i=IDM_LINEMIN; i <= IDM_LINEMAX; i++)
            m_pTB->Enable(i, m_fLastEnable);
        }

    return;
    }

/*
 * CCosmoFrame::CheckLineSelection
 *
 * Purpose:
 *  Maintains the bitmap menu and the tools for the line selection.
 *  Both are mutially exclusive option lists where a selection in
 *  one has to affect the other.
 *
 * Parameters:
 *  uID             UINT ID of the item to be selected
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::CheckLineSelection(UINT uID)
    {
    UINT        i;
    HMENU       hMenu;

    //Update menus and tools if the selection changed.
    if (uID!=m_uIDCurLine)
        {
        m_uIDCurLine=uID;

        //CHAPTER23MOD
        /*
         * Should only check on the popup, not on the main menu since it
         * could be a shared menu at the moment.
         */
        hMenu=m_phMenu[3];
        //End CHAPTER23MOD

        //Uncheck all lines initially.
        for (i=IDM_LINEMIN; i<=IDM_LINEMAX; i++)
            CheckMenuItem(hMenu, i, MF_UNCHECKED | MF_BYCOMMAND);

        CheckMenuItem(hMenu, uID, MF_CHECKED | MF_BYCOMMAND);
        m_pTB->Check(uID, TRUE);

        //CHAPTER23MOD
        //Affect in-place toolbar as well
        if (NULL!=g_pInPlaceTB)
            g_pInPlaceTB->Check(uID, TRUE);
        //End CHAPTER23MOD
        }

    return;
    }

/*
 * CCosmoFrame::UpdateEmbeddingUI
 *
 * Purpose:
 *  Puts the application into the user interface for editing an
 *  embedded object, manipulating menus and title bars.
 *
 * Parameters:
 *  fEmbedding      BOOL TRUE to go in the mode, FALSE to leave it.
 *  pszApp          LPCTSTR name of the container application as
 *                  received in IOleObject::SetHostNames.
 *  pszObj          LPCTSTR name of the object in the container as
 *                  received in IOleObject::SetHostNames.
 *
 * Return Value:
 *  None
 */

void CCosmoFrame::UpdateEmbeddingUI(BOOL fEmbedding
    , PCDocument pDoc, LPCTSTR pszApp, LPCTSTR pszObj)
    {
    HMENU           hMenu;
    TCHAR           szTemp[256];

    //First let's play with the File menu.
    hMenu=m_phMenu[0];

    //Remove or add the File New, Open, and Save items
    if (fEmbedding)
        {
        DeleteMenu(m_phMenu[0], IDM_FILENEW,   MF_BYCOMMAND);
        DeleteMenu(m_phMenu[0], IDM_FILEOPEN,  MF_BYCOMMAND);
        DeleteMenu(m_phMenu[0], IDM_FILECLOSE, MF_BYCOMMAND);
        DeleteMenu(m_phMenu[0], IDM_FILESAVE,  MF_BYCOMMAND);

        //Save As->Save Copy As
        ModifyMenu(m_phMenu[0], IDM_FILESAVEAS, MF_BYCOMMAND
            , IDM_FILESAVEAS, PSZ(IDS_SAVECOPYAS));
        }
    else
        {
        InsertMenu(m_phMenu[0], 0, MF_BYPOSITION, IDM_FILENEW
            , PSZ(IDS_NEW));
        InsertMenu(m_phMenu[0], 1, MF_BYPOSITION, IDM_FILEOPEN
            , PSZ(IDS_OPEN));
        InsertMenu(m_phMenu[0], 2, MF_BYPOSITION, IDM_FILESAVE
            , PSZ(IDS_SAVE));
        InsertMenu(m_phMenu[0], 3, MF_BYPOSITION, IDM_FILECLOSE
            , PSZ(IDS_SAVE));

        //Save Copy As->Save As
        ModifyMenu(m_phMenu[0], IDM_FILESAVEAS, MF_BYCOMMAND
            , IDM_FILESAVEAS, PSZ(IDS_SAVEAS));
        }

    //Change "Exit" to "Exit & Return to xx" or vice-versa for SDI
    if (fEmbedding)
        wsprintf(szTemp, PSZ(IDS_EXITANDRETURN), (LPSTR)pszObj);
    else
        lstrcpy(szTemp, PSZ(IDS_EXIT));

    ModifyMenu(m_phMenu[0], IDM_FILEEXIT, MF_STRING, IDM_FILEEXIT
        , szTemp);
    DrawMenuBar(m_hWnd);

    //Now let's play with the toolbar.
    m_pTB->Show(IDM_FILENEW,   !fEmbedding);
    m_pTB->Show(IDM_FILEOPEN,  !fEmbedding);
    m_pTB->Show(IDM_FILECLOSE, !fEmbedding);
    m_pTB->Show(IDM_FILESAVE,  !fEmbedding);

    //Enable what's left appropriately.
    UpdateToolbar();

    //Now play with the title bar.

    //IDS_EMBEDDINGCAPTION is MDI/SDI sensitive in COSMO.RC.
    wsprintf(szTemp, PSZ(IDS_EMBEDDINGCAPTION), pszObj);

    /*
     * Remember that in MDI situations that Windows takes care of
     * the frame window caption bar when the document is maximized.
     */
   #ifdef MDI
    SetWindowText(pDoc->Window(), szTemp);
   #else
    SetWindowText(m_hWnd, szTemp);
   #endif

    return;
    }


COSMO.H

/*
 * COSMO.H
 * Cosmo Chapter 23
 *
 * Single include file that pulls in everything needed for other
 * source files in the Cosmo application.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _COSMO_H_
#define _COSMO_H_

#define INC_CLASSLIB
#define INC_OLE2UI
//CHAPTER23MOD
#define CHAPTER23
//End CHAPTER23MOD
#include <inole.h>
#include "resource.h"

//Get the editor window information.
#include "polyline.h"

//COSMO.CPP:  Frame object that creates a main window

class CCosmoFrame : public CFrame
    {
    friend class CFigureClassFactory;
    friend class CFigure;   //For UI purposes.

    private:
        HBITMAP         m_hBmpLines[5];     //Menu item bitmaps
        UINT            m_uIDCurLine;       //Current line selection
        BOOL            m_fInitialized;     //Did OleInitalize work?
        LPCLASSFACTORY  m_pIClassDataTran;  //For locking

        BOOL            m_fEmbedding;       //-Embedding found?
        DWORD           m_dwRegCO;          //Registration key
        LPCLASSFACTORY  m_pIClassFactory;

        //CHAPTER23MOD
        OLEINPLACEFRAMEINFO     m_frameInfo;    //Container's stuff.
        LPOLEINPLACEFRAME       m_pIOleIPFrame; //Copy of CFigure's
        //End CHAPTER23MOD

    protected:
        //CHAPTER23MOD
        BOOL      FMessageHook(HWND, UINT, WPARAM, LPARAM
                      , LRESULT *);
        //End CHAPTER23MOD

        //Overridable for creating a CClient for this frame
        virtual PCClient  CreateCClient(void);

        virtual BOOL      RegisterAllClasses(void);
        virtual BOOL      PreShowInit(void);
        virtual UINT      CreateToolbar(void);

        virtual LRESULT   OnCommand(HWND, WPARAM, LPARAM);
        virtual void      OnDocumentDataChange(PCDocument);
        virtual void      OnDocumentActivate(PCDocument);

        //New for this class
        virtual void      CreateLineMenu(void);

    public:
        CCosmoFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CCosmoFrame(void);

        //Overrides
        virtual BOOL      Init(PFRAMEINIT);
        //CHAPTER23MOD
        virtual  WPARAM   MessageLoop(void);
        //End CHAPTER23MOD

        virtual void      UpdateMenus(HMENU, UINT);
        virtual void      UpdateToolbar(void);

        //New for this class
        virtual void      CheckLineSelection(UINT);
        virtual void      UpdateEmbeddingUI(BOOL, PCDocument
                              , LPCTSTR, LPCTSTR);
    };

typedef CCosmoFrame *PCCosmoFrame;

//CLIENT.CPP

/*
 * The only reason we have a derived class here is to override
 * CreateCDocument so we can create our own type as well as
 * overriding NewDocument to perform one other piece of work once
 * the document's been created.
 */

class CCosmoClient : public CClient
    {
    protected:
        //Overridable for creating a new CDocument
        virtual PCDocument CreateCDocument(void);

    public:
        CCosmoClient(HINSTANCE, PCFrame);
        virtual ~CCosmoClient(void);

        virtual PCDocument NewDocument(BOOL);
    };

typedef CCosmoClient *PCCosmoClient;

//DOCUMENT.CPP

//Constant ID for the window polyline that lives in a document
#define ID_POLYLINE         10

class CCosmoDoc : public CDocument
    {
    friend class CPolylineAdviseSink;

    //These need access to FQueryPasteFromData, PasteFromData
    friend class CDropTarget;
    friend class CDropSource;

    friend class CFigureClassFactory;
    friend class CFigure;

    protected:
        UINT                    m_uPrevSize;    //Last WM_SIZE wParam
        LONG                    m_lVer;         //Loaded Polyline ver

        PCPolyline              m_pPL;          //Polyline window here
        PCPolylineAdviseSink    m_pPLAdv;       //Advises from Polyline

        class CDropTarget      *m_pDropTarget;  //Registered target
        BOOL                    m_fDragSource;  //Source==target?

        UINT                    m_cfEmbedSource;
        UINT                    m_cfObjectDescriptor;

        class CFigure          *m_pFigure;      //The object in us.
        LPMONIKER               m_pMoniker;     //Our file
        DWORD                   m_dwRegROT;     //From IROT::Register

        UINT                    m_cfLinkSource;
        UINT                    m_cfLinkSrcDescriptor;

    protected:
        virtual BOOL     FMessageHook(HWND, UINT, WPARAM, LPARAM
            , LRESULT *);

        void             DropSelectTargetWindow(void);

    public:
        CCosmoDoc(HINSTANCE, PCFrame, PCDocumentAdviseSink);
        virtual ~CCosmoDoc(void);

        virtual BOOL     Init(PDOCUMENTINIT);

        virtual void     Clear(void);

        virtual BOOL     FDirtySet(BOOL);
        virtual BOOL     FDirtyGet(void);

        virtual UINT     Load(BOOL, LPTSTR);
        virtual UINT     Save(UINT, LPTSTR);
        virtual void     Rename(LPTSTR);

        virtual void     Undo(void);
        virtual BOOL     Clip(HWND, BOOL);
        virtual HGLOBAL  RenderFormat(UINT);
        virtual BOOL     RenderMedium(UINT, LPSTGMEDIUM);
        virtual BOOL     FQueryPaste(void);
        virtual BOOL     Paste(HWND);

        //These were protected.  Now for IOleObject, should be public.
        virtual BOOL     FQueryPasteFromData(LPDATAOBJECT);
        virtual BOOL     PasteFromData(LPDATAOBJECT);
        LPDATAOBJECT     TransferObjectCreate(BOOL);

        //CHAPTER23MOD
        void             OpenInPlaceObject(void);
        //End CHAPTER23MOD

        virtual COLORREF ColorSet(UINT, COLORREF);
        virtual COLORREF ColorGet(UINT);

        virtual UINT     LineStyleSet(UINT);
        virtual UINT     LineStyleGet(void);
    };

typedef CCosmoDoc *PCCosmoDoc;

//These color indices wrap the polyline definitions
#define DOCCOLOR_BACKGROUND             POLYLINECOLOR_BACKGROUND
#define DOCCOLOR_LINE                   POLYLINECOLOR_LINE

//Drag-drop interfaces we need in the document
class CDropTarget : public IDropTarget
    {
    protected:
        ULONG               m_cRef;
        PCCosmoDoc          m_pDoc;

        LPDATAOBJECT        m_pIDataObject;     //From DragEnter

    public:
        CDropTarget(PCCosmoDoc);
        ~CDropTarget(void);

        //IDropTarget interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP DragEnter(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
        STDMETHODIMP DragOver(DWORD, POINTL, LPDWORD);
        STDMETHODIMP DragLeave(void);
        STDMETHODIMP Drop(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
    };

typedef CDropTarget *PCDropTarget;

class CDropSource : public IDropSource
    {
    protected:
        ULONG               m_cRef;
        PCCosmoDoc          m_pDoc;

    public:
        CDropSource(PCCosmoDoc);
        ~CDropSource(void);

        //IDropSource interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP QueryContinueDrag(BOOL, DWORD);
        STDMETHODIMP GiveFeedback(DWORD);
    };

typedef CDropSource *PCDropSource;

//Include classes necessary to become an OLE Document server.
#include "cosmole.h"

#endif //_COSMO_H_


COSMOLE.H

/*
 * COSMOLE.H
 * Cosmo Chapter 23
 *
 * Include file containing all compound document related
 * definitions.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _COSMOLE_H_
#define _COSMOLE_H_

//These are global for simplification of object implementation.
extern ULONG g_cObj;
extern ULONG g_cLock;
extern HWND  g_hWnd;

//CHAPTER23MOD
//This is for simplification of CCosmoFrame::CheckLineSelection
extern PCToolBar    g_pInPlaceTB;

#define ID_HATCHWINDOW  2000
//End CHAPTER23MOD

//Function for the object to notify on destruction.
void ObjectDestroyed(void);

//ICLASSF.CPP
class CFigureClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;
        PCCosmoFrame    m_pFR;
        BOOL            m_fCreated;     //Created one object yet?

    public:
        CFigureClassFactory(PCCosmoFrame);
        ~CFigureClassFactory(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP         CreateInstance(LPUNKNOWN, REFIID
                                 , PPVOID);
        STDMETHODIMP         LockServer(BOOL);
    };

typedef CFigureClassFactory *PCFigureClassFactory;

//FIGURE.CPP
//This is what the class factory creates

#define CFORMATETCGET   5

class CImpIPersistStorage;
typedef class CImpIPersistStorage *PCImpIPersistStorage;
class CImpIDataObject;
typedef class CImpIDataObject *PCImpIDataObject;
class CImpIOleObject;
typedef class CImpIOleObject *PCImpIOleObject;
class CImpIPersistFile;
typedef class CImpIPersistFile *PCImpIPersistFile;
//CHAPTER23MOD
class CImpIOleInPlaceObject;
typedef class CImpIOleInPlaceObject *PCImpIOleInPlaceObject;
class CImpIOleInPlaceActiveObject;
typedef class CImpIOleInPlaceActiveObject *PCImpIOleInPlaceActiveObject;
//End CHAPTER23MOD

class CFigure : public IUnknown
    {
    friend class CImpIPersistStorage;
    friend class CImpIDataObject;
    friend class CImpIOleObject;
    friend class CImpIPersistFile;
    //CHAPTER23MOD
    friend class CImpIOleInPlaceObject;
    friend class CImpIOleInPlaceActiveObject;
    //End CHAPTER23MOD

    protected:
        ULONG               m_cRef;
        PCCosmoFrame        m_pFR;      //Frame (for UI changes)
        PCCosmoDoc          m_pDoc;     //What holds real polyline
        PCPolyline          m_pPL;      //Copy of m_pDoc->m_pPL

        BOOL                m_fEmbedded;
        PFNDESTROYED        m_pfnDestroy;

        PCStringTable       m_pST;      //Object strings

        //Things for IPersistStorage
        PCImpIPersistStorage m_pImpIPersistStorage;
        LPSTORAGE           m_pIStorage;
        LPSTREAM            m_pIStream;
        CLSID               m_clsID;

        //Things for IDataObject
        PCImpIDataObject    m_pImpIDataObject;      //Implemented
        LPDATAADVISEHOLDER  m_pIDataAdviseHolder;   //Used

        UINT                m_cf;                   //pDoc->m_cf
        ULONG               m_cfeGet;
        FORMATETC           m_rgfeGet[CFORMATETCGET];

        //Things for IOleObject
        PCImpIOleObject     m_pImpIOleObject;       //Implemented
        LPOLEADVISEHOLDER   m_pIOleAdviseHolder;    //Used
        LPOLECLIENTSITE     m_pIOleClientSite;      //Used

        LPMONIKER           m_pMoniker;
        DWORD               m_dwRegROT;
        PCImpIPersistFile   m_pImpIPersistFile;     //Implemented

        //CHAPTER23MOD
        LPOLEINPLACESITE            m_pIOleIPSite;
        LPOLEINPLACEFRAME           m_pIOleIPFrame;
        LPOLEINPLACEUIWINDOW        m_pIOleIPUIWindow;

        PCImpIOleInPlaceObject       m_pImpIOleIPObject;
        PCImpIOleInPlaceActiveObject m_pImpIOleIPActiveObject;

        HMENU                       m_hMenuShared;
        HOLEMENU                    m_hOLEMenu;
        HACCEL                      m_hAccel;

        PCHatchWin                  m_pHW;      //Hatch window
        PCToolBar                   m_pTB;      //In-place toolbar
        UINT                        m_cyBar;    //Copy of m_pFR->m_cyBar
        BOOL                        m_fUndoDeactivates;
        BOOL                        m_fAllowInPlace;
        BOOL                        m_fForceSave;
        //End CHAPTER23MOD

    public:
        CFigure(PFNDESTROYED, PCCosmoDoc);
        ~CFigure(void);

        //Non-delegating IUnknown:  we don't support aggregation
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        BOOL Init(void);
        void FrameSet(PCCosmoFrame);
        BOOL FIsDirty(void);
        BOOL FIsEmbedded(void);
        void SendAdvise(UINT);

        //CHAPTER23MOD
        HRESULT  InPlaceActivate(LPOLECLIENTSITE, BOOL);
        void     InPlaceDeactivate(void);
        HRESULT  UIActivate(void);
        void     UIDeactivate(void);
        BOOL     InPlaceMenuCreate(void);
        BOOL     InPlaceMenuDestroy(void);
        BOOL     InPlaceToolsCreate(void);
        BOOL     InPlaceToolsDestroy(void);
        BOOL     InPlaceToolsRenegotiate(void);

        void     OpenIntoWindow(void);
        BOOL     Undo(void);
        //End CHAPTER23MOD
    };

typedef CFigure *PCFigure;

//Codes for CFigure::SendAdvise
//......Code......................Method called in CFigureSendAdvise...
#define OBJECTCODE_SAVED       0  //IOleAdviseHolder::SendOnSave
#define OBJECTCODE_CLOSED      1  //IOleAdviseHolder::SendOnClose
#define OBJECTCODE_RENAMED     2  //IOleAdviseHolder::SendOnRename
#define OBJECTCODE_SAVEOBJECT  3  //IOleClientSite::SaveObject
#define OBJECTCODE_DATACHANGED 4  //IDataAdviseHolder::SendOnDataChange
#define OBJECTCODE_SHOWWINDOW  5  //IOleClientSite::OnShowWindow(TRUE)
#define OBJECTCODE_HIDEWINDOW  6  //IOleClientSite::OnShowWindow(FALSE)
#define OBJECTCODE_SHOWOBJECT  7  //IOleClientSite::ShowObject

//IPERSTOR.CPP

class CImpIPersistStorage : public IPersistStorage
    {
    protected:
        ULONG               m_cRef;
        PCFigure            m_pObj;
        LPUNKNOWN           m_pUnkOuter;
        PSSTATE             m_psState;      //Storage state
        BOOL                m_fConvert;     //Are we Converting?

    public:
        CImpIPersistStorage(PCFigure, LPUNKNOWN);
        ~CImpIPersistStorage(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP InitNew(LPSTORAGE);
        STDMETHODIMP Load(LPSTORAGE);
        STDMETHODIMP Save(LPSTORAGE, BOOL);
        STDMETHODIMP SaveCompleted(LPSTORAGE);
        STDMETHODIMP HandsOffStorage(void);
    };

//IDATAOBJ.CPP
class CImpIDataObject : public IDataObject
    {
    private:
        ULONG           m_cRef;
        PCFigure        m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIDataObject(PCFigure, LPUNKNOWN);
        ~CImpIDataObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IDataObject members
        STDMETHODIMP GetData(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP GetDataHere(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP QueryGetData(LPFORMATETC);
        STDMETHODIMP GetCanonicalFormatEtc(LPFORMATETC, LPFORMATETC);
        STDMETHODIMP SetData(LPFORMATETC, LPSTGMEDIUM, BOOL);
        STDMETHODIMP EnumFormatEtc(DWORD, LPENUMFORMATETC *);
        STDMETHODIMP DAdvise(LPFORMATETC, DWORD,  LPADVISESINK
                         , DWORD *);
        STDMETHODIMP DUnadvise(DWORD);
        STDMETHODIMP EnumDAdvise(LPENUMSTATDATA *);
    };

//IOLEOBJ.CPP
class CImpIOleObject : public IOleObject
    {
    private:
        ULONG           m_cRef;
        PCFigure        m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIOleObject(PCFigure, LPUNKNOWN);
        ~CImpIOleObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IOleObject members
        STDMETHODIMP SetClientSite(LPOLECLIENTSITE);
        STDMETHODIMP GetClientSite(LPOLECLIENTSITE *);
        STDMETHODIMP SetHostNames(LPCOLESTR, LPCOLESTR);
        STDMETHODIMP Close(DWORD);
        STDMETHODIMP SetMoniker(DWORD, LPMONIKER);
        STDMETHODIMP GetMoniker(DWORD, DWORD, LPMONIKER *);
        STDMETHODIMP InitFromData(LPDATAOBJECT, BOOL, DWORD);
        STDMETHODIMP GetClipboardData(DWORD, LPDATAOBJECT *);
        STDMETHODIMP DoVerb(LONG, LPMSG, LPOLECLIENTSITE, LONG, HWND
                         , LPCRECT);
        STDMETHODIMP EnumVerbs(LPENUMOLEVERB *);
        STDMETHODIMP Update(void);
        STDMETHODIMP IsUpToDate(void);
        STDMETHODIMP GetUserClassID(CLSID *);
        STDMETHODIMP GetUserType(DWORD, LPOLESTR *);
        STDMETHODIMP SetExtent(DWORD, LPSIZEL);
        STDMETHODIMP GetExtent(DWORD, LPSIZEL);
        STDMETHODIMP Advise(LPADVISESINK, DWORD *);
        STDMETHODIMP Unadvise(DWORD);
        STDMETHODIMP EnumAdvise(LPENUMSTATDATA *);
        STDMETHODIMP GetMiscStatus(DWORD, DWORD *);
        STDMETHODIMP SetColorScheme(LPLOGPALETTE);
    };

class CImpIPersistFile : public IPersistFile
    {
    protected:
        ULONG               m_cRef;
        PCFigure            m_pObj;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIPersistFile(PCFigure, LPUNKNOWN);
        ~CImpIPersistFile(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP Load(LPCOLESTR, DWORD);
        STDMETHODIMP Save(LPCOLESTR, BOOL);
        STDMETHODIMP SaveCompleted(LPCOLESTR);
        STDMETHODIMP GetCurFile(LPOLESTR *);
    };

//CHAPTER23MOD
//IIPOBJ.CPP
class CImpIOleInPlaceObject : public IOleInPlaceObject
    {
    protected:
        ULONG               m_cRef;
        PCFigure            m_pObj;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIOleInPlaceObject(PCFigure, LPUNKNOWN);
        ~CImpIOleInPlaceObject(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetWindow(HWND *);
        STDMETHODIMP ContextSensitiveHelp(BOOL);
        STDMETHODIMP InPlaceDeactivate(void);
        STDMETHODIMP UIDeactivate(void);
        STDMETHODIMP SetObjectRects(LPCRECT, LPCRECT);
        STDMETHODIMP ReactivateAndUndo(void);
    };

//IIPAOBJ.CPP
class CImpIOleInPlaceActiveObject
    : public IOleInPlaceActiveObject
    {
    protected:
        ULONG               m_cRef;
        PCFigure            m_pObj;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIOleInPlaceActiveObject(PCFigure, LPUNKNOWN);
        ~CImpIOleInPlaceActiveObject(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetWindow(HWND *);
        STDMETHODIMP ContextSensitiveHelp(BOOL);
        STDMETHODIMP TranslateAccelerator(LPMSG);
        STDMETHODIMP OnFrameWindowActivate(BOOL);
        STDMETHODIMP OnDocWindowActivate(BOOL);
        STDMETHODIMP ResizeBorder(LPCRECT, LPOLEINPLACEUIWINDOW
                         , BOOL);
        STDMETHODIMP EnableModeless(BOOL);
    };

//End CHAPTER23MOD

#endif _COSMOLE_H_


DOCUMENT.CPP

/*
 * DOCUMENT.CPP
 * Cosmo Chapter 23
 *
 * Implementation of the CCosmoDoc derivation of CDocument as
 * well as an implementation of CPolylineAdviseSink.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CCosmoDoc::CCosmoDoc
 * CCosmoDoc::~CCosmoDoc
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame of the frame object.
 *  pAdv            PCDocumentAdviseSink to notify on events
 */

CCosmoDoc::CCosmoDoc(HINSTANCE hInst, PCFrame pFR
    , PCDocumentAdviseSink pAdv)
    : CDocument(hInst, pFR, pAdv)
    {
    m_pPL=NULL;
    m_pPLAdv=NULL;
    m_uPrevSize=SIZE_RESTORED;

    m_pDropTarget=NULL;
    m_fDragSource=FALSE;

    m_cfEmbedSource=RegisterClipboardFormat(CFSTR_EMBEDSOURCE);
    m_cfObjectDescriptor=RegisterClipboardFormat
        (CFSTR_OBJECTDESCRIPTOR);

    m_pFigure=NULL;
    m_pMoniker=NULL;
    m_dwRegROT=0L;

    m_cfLinkSource=RegisterClipboardFormat(CFSTR_LINKSOURCE);
    m_cfLinkSrcDescriptor=RegisterClipboardFormat
        (CFSTR_LINKSRCDESCRIPTOR);

    return;
    }

CCosmoDoc::~CCosmoDoc(void)
    {
    //Make sure the object is saved
    m_pFigure->SendAdvise(OBJECTCODE_SAVEOBJECT);
    m_pFigure->SendAdvise(OBJECTCODE_HIDEWINDOW);
    m_pFigure->SendAdvise(OBJECTCODE_CLOSED);

    //If the document is reg'd as running, revoke and free the moniker
    INOLE_RevokeAsRunning(&m_dwRegROT);

    if (NULL!=m_pMoniker)
        m_pMoniker->Release();

    if (NULL!=m_pFigure)
        {
        m_pFigure->AddRef();
        CoDisconnectObject(m_pFigure, 0L);
        m_pFigure->Release();   //Starts shutdown if necessary.
        delete m_pFigure;
        }

    //Clean up the allocations we did in Init
    if (NULL!=m_pPL)
        delete m_pPL;

    if (NULL!=m_pPLAdv)
        delete m_pPLAdv;

    return;
    }

/*
 * CCosmoDoc::Init
 *
 * Purpose:
 *  Initializes an already created document window.  The client
 *  actually creates the window for us, then passes that here for
 *  further initialization.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CCosmoDoc::Init(PDOCUMENTINIT pDI)
    {
    RECT        rc;

    //Change the stringtable range to our customization.
    pDI->idsMin=IDS_DOCUMENTMIN;
    pDI->idsMax=IDS_DOCUMENTMAX;

    //Do default initialization
    if (!CDocument::Init(pDI))
        return FALSE;

    //Add the Polyline stuff we need.
    m_pPLAdv=new CPolylineAdviseSink(this);
    m_pPL   =new CPolyline(m_hInst);

    //Attempt to create our contained Polyline.
    GetClientRect(m_hWnd, &rc);
    InflateRect(&rc, -8, -8);

    if (!m_pPL->Init(m_hWnd, &rc, WS_CHILD | WS_VISIBLE
        , ID_POLYLINE, m_pPLAdv))
        return FALSE;

    m_pDropTarget=new CDropTarget(this);

    if (NULL!=m_pDropTarget)
        {
        m_pDropTarget->AddRef();
        CoLockObjectExternal(m_pDropTarget, TRUE, FALSE);
        RegisterDragDrop(m_hWnd, m_pDropTarget);
        }

    m_pFigure=new CFigure(ObjectDestroyed, this);

    if (NULL==m_pFigure)
        return FALSE;

    //We created an object, so count it.
    g_cObj++;

    if (!m_pFigure->Init())
        return FALSE;

    return TRUE;
    }

/*
 * CCosmoDoc::FMessageHook
 *
 * Purpose:
 *  Processes WM_SIZE for the document so we can resize
 *  the Polyline.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CCosmoDoc::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    UINT        dx, dy;
    RECT        rc;

    *pLRes=0;

    if (WM_SIZE==iMsg)
        {
        //Don't effect the Polyline size to or from minimized state.
        if (SIZE_MINIMIZED!=wParam && SIZE_MINIMIZED !=m_uPrevSize)
            {
            //When we change size, resize any Polyline we hold.
            dx=LOWORD(lParam);
            dy=HIWORD(lParam);

            /*
             * If we are getting WM_SIZE in response to a Polyline
             * notification, then don't resize the Polyline window
             * again.
             */
            if (!m_fNoSize && NULL!=m_pPL)
                {
                //Resize the polyline to fit the new client
                SetRect(&rc, 8, 8, dx-8, dy-8);
                m_pPL->RectSet(&rc, FALSE);

                /*
                 * We consider sizing something that makes the file
                 * dirty, but not until we've finished the create
                 * process, which is why we set fNoDirty to FALSE
                 * in WM_CREATE since we get a WM_SIZE on the first
                 * creation.
                 */
                if (!m_fNoDirty)
                    FDirtySet(TRUE);

                SetRect(&rc, 0, 0, dx, dy);

                if (NULL!=m_pAdv)
                    m_pAdv->OnSizeChange(this, &rc);

                m_fNoDirty=FALSE;
                }
            }

        m_uPrevSize=wParam;
        }

    if (WM_LBUTTONDOWN==iMsg)
        {
        LPDROPSOURCE    pIDropSource;
        LPDATAOBJECT    pIDataObject;
        HRESULT         hr;
        SCODE           sc;
        DWORD           dwEffect;

        /*
         * The document has an 8 pixel border around the polyline
         * window where we'll see mouse clicks.  A left mouse button
         * click here means the start of a drag-drop operation.
         *
         * Since this is a modal operation, this IDropSource
         * is entirely local.
         */

        pIDropSource=new CDropSource(this);

        if (NULL==pIDropSource)
            return FALSE;

        pIDropSource->AddRef();
        m_fDragSource=TRUE;

        //Go get the data and start the ball rolling.
        pIDataObject=TransferObjectCreate(FALSE);

        if (NULL!=pIDataObject)
            {
            hr=DoDragDrop(pIDataObject, pIDropSource
                , DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK
                , &dwEffect);

            pIDataObject->Release();
            sc=GetScode(hr);
            }
        else
            sc=E_FAIL;

        /*
         * When we return from DoDragDrop, either cancel or drop.
         * First toss the IDropSource we have here, then bail out
         * on cancel, and possibly clear our data on a move drop.
         */

        pIDropSource->Release();

        /*
         * If dropped on the same document (determined using
         * this flag, then dwEffect will be DROPEFFECT_NONE (see
         * IDropTarget::Drop in DROPTGT.CPP).  In any case,
         * reset this since the operation is done.
         */

        m_fDragSource=FALSE;

        if (DRAGDROP_S_DROP==sc && DROPEFFECT_MOVE==dwEffect)
            {
            m_pPL->New();
            FDirtySet(TRUE);
            }

        //On a canceled drop or a copy we don't do anything else
        return TRUE;
        }

    if (WM_DESTROY==iMsg)
        {
        /*
         * We have to revoke the drop target here because the window
         * will be destroyed and the property forcefully removed
         * before we could do this in the destructor.
         */
        if (NULL!=m_pDropTarget)
            {
            RevokeDragDrop(m_hWnd);
            CoLockObjectExternal(m_pDropTarget, FALSE, TRUE);
            ReleaseInterface(m_pDropTarget);
            }

        return FALSE;
        }

    /*
     * We return FALSE even on WM_SIZE so we can let the default
     * procedure handle maximized MDI child windows appropriately.
     */
    return FALSE;
    }

/*
 * CCosmoDoc::Clear
 *
 * Purpose:
 *  Sets all contents in the document back to defaults with
 *  no filename.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Clear(void)
    {
    //Completely reset the polyline
    m_pPL->New();

    CDocument::Clear();
    m_lVer=0;
    return;
    }

/*
 * CCosmoDoc::FDirtySet
 *
 * Purpose:
 *  Sets or clears the document 'dirty' flag returning the previous
 *  state of that same flag.  We override this in Cosmo server to
 *  send the OnDataChange notification as necessary.
 *
 * Parameters:
 *  fDirty          BOOL indicating the new contents of the dirty
 *                  flag.
 *
 * Return Value:
 *  BOOL            Previous value of the dirty flag.
 */

BOOL CCosmoDoc::FDirtySet(BOOL fDirty)
    {
    BOOL        fRet;

    fRet=CDocument::FDirtySet(fDirty);
    m_pFigure->SendAdvise(OBJECTCODE_DATACHANGED);

    return fRet;
    }

/*
 * CCosmoDoc::FDirtyGet
 *
 * Purpose:
 *  Override of the normal FDirtyGet such that we never return dirty
 *  for an embedded object we're serving since updates constantly
 *  happen and since the object will be saved on closure.  This then
 *  prevents any message boxes asking the user to save.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the document is dirty, FALSE otherwise.
 */

BOOL CCosmoDoc::FDirtyGet(void)
    {
    if (m_pFigure->FIsEmbedded())
        return FALSE;

    return m_fDirty;
    }

/*
 * CCosmoDoc::Load
 *
 * Purpose:
 *  Loads a given document without any user interface overwriting
 *  the previous contents of the Polyline window.  We do this by
 *  opening the file and telling the Polyline to load itself from
 *  that file.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load, NULL if the file
 *                  is new and untitled.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CCosmoDoc::Load(BOOL fChangeFile, LPTSTR pszFile)
    {
    HRESULT         hr;
    LPSTORAGE       pIStorage;

    if (NULL==pszFile)
        {
        //For a new untitled document, just rename ourselves.
        Rename(NULL);
        m_lVer=VERSIONCURRENT;
        return DOCERR_NONE;
        }

    /*
     * If not a Compound File, open the file using STGM_CONVERT in
     * transacted mode to see old files as a storage with one stream
     * called "CONTENTS" (which is conveniently the name we use
     * in the new files).  We must use STGM_TRANSACTED here or else
     * the old file will be immediately converted on disk:  we only
     * want a converted image in memory from which to read.  In
     * addition, note that we need STGM_READWRITE as well since
     * conversion is inherently a write operation.
     */

    pIStorage=NULL;

    if (NOERROR!=StgIsStorageFile(pszFile))
        {
        hr=StgCreateDocfile(pszFile,STGM_TRANSACTED | STGM_READWRITE
            | STGM_CONVERT | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

        if (FAILED(hr))
            {
            //If denied write access, try to load the old way
            if (STG_E_ACCESSDENIED==GetScode(hr))
                m_lVer=m_pPL->ReadFromFile(pszFile);
            else
                return DOCERR_COULDNOTOPEN;
            }
        }
    else
        {
        hr=StgOpenStorage(pszFile, NULL, STGM_DIRECT | STGM_READ
            | STGM_SHARE_EXCLUSIVE, NULL, 0, &pIStorage);

        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;
        }

    if (NULL!=pIStorage)
        {
        m_lVer=m_pPL->ReadFromStorage(pIStorage);
        pIStorage->Release();
        }

    if (POLYLINE_E_READFAILURE==m_lVer)
        return DOCERR_READFAILURE;

    if (POLYLINE_E_UNSUPPORTEDVERSION==m_lVer)
        return DOCERR_UNSUPPORTEDVERSION;

    if (fChangeFile)
        Rename(pszFile);

    //Importing a file makes things dirty
    FDirtySet(!fChangeFile);

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::Save
 *
 * Purpose:
 *  Writes the file to a known filename, requiring that the user has
 *  previously used FileOpen or FileSaveAs to provide a filename.
 *
 * Parameters:
 *  uType           UINT indicating the type of file the user
 *                  requested to save in the File Save As dialog.
 *  pszFile         LPTSTR under which to save.  If NULL, use the
 *                  current name.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CCosmoDoc::Save(UINT uType, LPTSTR pszFile)
    {
    LONG        lVer, lRet;
    UINT        uTemp;
    BOOL        fRename=TRUE;
    HRESULT     hr;
    LPSTORAGE   pIStorage;
    BOOL        fEmbedding;

    fEmbedding=m_pFigure->FIsEmbedded();

    if (NULL==pszFile)
        {
        fRename=FALSE;
        pszFile=m_szFile;
        }

    /*
     * Type 1 is the current version, type 2 is version 1.0 of the
     * Polyline so we use this to send the right version to
     * CPolyline::WriteToFile/WriteToStorage.
     */

    switch (uType)
        {
        case 0:         //From Save, use loaded version.
            lVer=m_lVer;
            break;

        case 1:
            lVer=VERSIONCURRENT;
            break;

        case 2:
            lVer=MAKELONG(0, 1);    //1.0
            break;

        default:
            return DOCERR_UNSUPPORTEDVERSION;
        }

    /*
     * If the version the user wants to save is different from the
     * version that we loaded and m_lVer is not zero (new doc),
     * then inform the user of the version change and verify.
     */

    //For embedding, this is Save Copy As, so don't ask about versions.
    if (0!=m_lVer && m_lVer!=lVer && !fEmbedding)
        {
        TCHAR       szMsg[128];

        wsprintf(szMsg, PSZ(IDS_VERSIONCHANGE)
            , (UINT)HIWORD(m_lVer), (UINT)LOWORD(m_lVer)
            , (UINT)HIWORD(lVer), (UINT)LOWORD(lVer));

        uTemp=MessageBox(m_hWnd, szMsg, PSZ(IDS_DOCUMENTCAPTION)
            , MB_YESNOCANCEL);

        if (IDCANCEL==uTemp)
            return DOCERR_CANCELLED;

        //If the user won't upgrade, revert to loaded version.
        if (IDNO==uTemp)
            lVer=m_lVer;
        }

    /*
     * For 1.0 files, still use the old code.  For new files, use
     * storages instead
     */
    if (lVer==MAKELONG(0, 1))
        lRet=m_pPL->WriteToFile(pszFile, lVer);
    else
        {
        hr=StgCreateDocfile(pszFile, STGM_DIRECT | STGM_READWRITE
            | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

        if (FAILED(hr))
            return DOCERR_COULDNOTOPEN;

        //Mark this as one of our class
        WriteClassStg(pIStorage, CLSID_CosmoFigure);

        //Write user-readable class information
        WriteFmtUserTypeStg(pIStorage, m_cf
            , PSZ(IDS_CLIPBOARDFORMAT));

        lRet=m_pPL->WriteToStorage(pIStorage, lVer);
        pIStorage->Release();
        }

    if (POLYLINE_E_NONE!=lRet)
        return DOCERR_WRITEFAILURE;

    //Saving makes us clean, but this doesn't apply to embedding.
    if (!fEmbedding)
        FDirtySet(FALSE);

    //Update the known version of this document.
    m_lVer=lVer;

    /*
     * If we're embedding, this is Save Copy As, so no rename.
     * Note that we also don't care about having been set to clean
     * since we're always 'clean' as an embedded object from
     * the user's perspective.
     */
    if (fRename && !fEmbedding)
        Rename(pszFile);

    return DOCERR_NONE;
    }

/*
 * CCosmoDoc::Rename
 *
 * Purpose:
 *  Overrides the normal rename to manage a moniker for the open
 *  file.  We maintain no moniker for untitled documents, and
 *  therefore do not allow linking to such documents.
 *
 * Parameters:
 *  pszFile         LPTSTR to the new filename.
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Rename(LPTSTR pszFile)
    {
    LPMONIKER   pmk;

    //We don't need to change the base class, just augment...
    CDocument::Rename(pszFile);

    //Unregister the old moniker (m_dwRegROT set to zero).
    INOLE_RevokeAsRunning(&m_dwRegROT);

    ReleaseInterface(m_pMoniker);

    if (NULL!=pszFile)
        {
        CreateFileMoniker(pszFile, &pmk);

        if (NULL!=pmk)
            {
            m_pMoniker=pmk;     //pmk AddRef'd in CreateFileMoniker
            INOLE_RegisterAsRunning(m_pFigure, m_pMoniker
                , 0, &m_dwRegROT);

            m_pFigure->SendAdvise(OBJECTCODE_RENAMED);
            }
        }

    return;
    }

/*
 * CCosmoDoc::Undo
 *
 * Purpose:
 *  Reverses a previous action.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::Undo(void)
    {
    //CHAPTER23MOD
    //Give the in-place object a chance for DeactivateAndUndo.
    if (m_pFigure->Undo())
        return;
    //End CHAPTER23MOD

    m_pPL->Undo();
    return;
    }

/*
 * CCosmoDoc::Clip
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard, optionally implementing Cut by deleting the
 *  data in the current window after rendering.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *  fCut            BOOL indicating cut (TRUE) or copy (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::Clip(HWND hWndFrame, BOOL fCut)
    {
    BOOL            fRet=TRUE;
    LPDATAOBJECT    pIDataObject;

    pIDataObject=TransferObjectCreate(fCut);

    if (NULL==pIDataObject)
        return FALSE;

    fRet=SUCCEEDED(OleSetClipboard(pIDataObject));
    pIDataObject->Release();

    //Delete our current data if "cut" succeeded.
    if (fRet && fCut)
        {
        m_pPL->New();
        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CCosmoDoc::RenderFormat
 *
 * Purpose:
 *  Renders a specific clipboard format into global memory.
 *
 * Parameters:
 *  cf              UINT format to render.
 *
 * Return Value:
 *  HGLOBAL         Global memory handle containing the data.
 */

HGLOBAL CCosmoDoc::RenderFormat(UINT cf)
    {
    HGLOBAL     hMem;

    if (cf==m_cf)
        {
        m_pPL->DataGetMem(VERSIONCURRENT, &hMem);
        return hMem;
        }

    switch (cf)
        {
        case CF_METAFILEPICT:
            return m_pPL->RenderMetafilePict();

        case CF_BITMAP:
            return (HGLOBAL)m_pPL->RenderBitmap();
        }

    return NULL;
    }

/*
 * CCosmoDoc::RenderMedium
 *
 * Purpose:
 *  Like RenderFormat, this function creates a specific data format
 *  based on the cf parameter.  Unlike RenderFormat, we store the
 *  result in a STGMEDIUM in case it has a medium other than
 *  TYMED_HGLOBAL.  For conveniece we'll centralize all compound
 *  document formats here, hGlobal or not.
 *
 * Parameters:
 *  cf              UINT clipboard format of interest.
 *  pSTM            LSTGMEDIUM to fill.  We only fill the union
 *                  and tymed.
 *
 * Return Value:
 *  BOOL            TRUE if we could render the format,
 *                  FALSE otherwise.
 */

BOOL CCosmoDoc::RenderMedium(UINT cf, LPSTGMEDIUM pSTM)
    {
    if (NULL==pSTM)
        return FALSE;

    if (cf==m_cfEmbedSource)
        {
        /*
         * Embed Source data is an IStorage containing the native
         * data (same as Embedded Object).  Since our data is small,
         * it makes the most sense to create an IStorage in memory
         * and put transfer that instead of a disk-based IStorage.
         */

        pSTM->pstg=INOLE_CreateStorageOnHGlobal(STGM_DIRECT
            | STGM_READWRITE | STGM_SHARE_EXCLUSIVE);

        if (NULL==pSTM->pstg)
            return FALSE;

        //Now save the data to the storage.
        WriteClassStg(pSTM->pstg, CLSID_CosmoFigure);
        WriteFmtUserTypeStg(pSTM->pstg, m_cf
            , PSZ(IDS_CLIPBOARDFORMAT));

        if (POLYLINE_E_NONE!=m_pPL->WriteToStorage(pSTM->pstg
            , VERSIONCURRENT))
            {
            /*
             * When someone releases the IStorage, STORAGE.DLL will
             * release the ILockBytes which, having fDeleteOnRelease
             * TRUE (second parameter) will release the memory.
             * That's why we don't have STGM_DELETEONRELEASE on the
             * IStorage.
             */
            pSTM->pstg->Release();
            return FALSE;
            }

        pSTM->tymed=TYMED_ISTORAGE;
        return TRUE;
        }

    /*
     * CFSTR_OBJECTDESCRIPTOR and CFSTR_LINKSRCDESCRIPTOR are the
     * same formats, but only copy link source if we have a moniker.
     */
    if (cf==m_cfLinkSrcDescriptor && NULL==m_pMoniker)
        return FALSE;

    if (cf==m_cfObjectDescriptor || cf==m_cfLinkSrcDescriptor)
        {
        SIZEL   szl, szlT;
        POINTL  ptl;
        RECT    rc;
        LPTSTR  psz=NULL;

        m_pPL->SizeGet(&rc);
        SETSIZEL(szlT, rc.right, rc.bottom);
        XformSizeInPixelsToHimetric(NULL, &szlT, &szl);

        SETPOINTL(ptl, 0, 0);

        //Include the moniker display name now, if we have one.
        if (m_pMoniker)
            {
            LPBC    pbc;

            CreateBindCtx(0, &pbc);
           #ifdef WIN32ANSI
            LPOLESTR    pszW;

            m_pMoniker->GetDisplayName(pbc, NULL, &pszW);
            psz=(LPTSTR)CoTaskMemAlloc(512);
            WideCharToMultiByte(CP_ACP, 0, pszW, -1, psz, 512
                , NULL, NULL);
            CoTaskMemFree((void *)pszW);
           #else
            m_pMoniker->GetDisplayName(pbc, NULL, &psz);
           #endif
            pbc->Release();
            }

        pSTM->hGlobal=INOLE_AllocObjectDescriptor
            (CLSID_CosmoFigure, DVASPECT_CONTENT, szl, ptl
            , OLEMISC_RECOMPOSEONRESIZE, PSZ(IDS_OBJECTDESCRIPTION)
            , psz);

        CoTaskMemFree((void *)psz);
        pSTM->tymed=TYMED_HGLOBAL;
        return (NULL!=pSTM->hGlobal);
        }

    if (cf==m_cfLinkSource)
        {
        if (NULL!=m_pMoniker)
            {
            FORMATETC   fe;
            HRESULT     hr;

            pSTM->tymed=TYMED_NULL;
            SETDefFormatEtc(fe, cf, TYMED_ISTREAM);
            hr=INOLE_GetLinkSourceData(m_pMoniker
                , (LPCLSID)&CLSID_CosmoFigure, &fe, pSTM);

            return SUCCEEDED(hr);
            }
        }

    return FALSE;
    }

/*
 * CCosmoDoc::FQueryPaste
 *
 * Purpose:
 *  Determines if we can paste data from the clipboard.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CCosmoDoc::FQueryPaste(void)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    fRet=FQueryPasteFromData(pIDataObject);
    pIDataObject->Release();
    return fRet;
    }

/*
 * CCosmoDoc::FQueryPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Determines if we can paste data from a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which we might want to paste.
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CCosmoDoc::FQueryPasteFromData(LPDATAOBJECT pIDataObject)
    {
    FORMATETC       fe;

    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    return (NOERROR==pIDataObject->QueryGetData(&fe));
    }

/*
 * CCosmoDoc::Paste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  to the current figure in the editor window.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::Paste(HWND hWndFrame)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    fRet=PasteFromData(pIDataObject);
    pIDataObject->Release();

    return fRet;
    }

/*
 * CCosmoDoc::PasteFromData
 * (Protected)
 *
 * Purpose:
 *  Retrieves the private data format from a data object and sets
 *  it to the current figure in the editor window.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CCosmoDoc::PasteFromData(LPDATAOBJECT pIDataObject)
    {
    FORMATETC       fe;
    STGMEDIUM       stm;
    BOOL            fRet;

    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    fRet=SUCCEEDED(pIDataObject->GetData(&fe, &stm));

    if (fRet && NULL!=stm.hGlobal)
        {
        m_pPL->DataSetMem(stm.hGlobal, FALSE, FALSE, TRUE);
        ReleaseStgMedium(&stm);
        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CCosmoDoc::TransferObjectCreate
 * (Protected)
 *
 * Purpose:
 *  Creates a DataTransferObject and stuffs the current Polyline
 *  data into it, used for both clipboard and drag-drop operations.
 *
 * Parameters:
 *  fCut            BOOL TRUE if we're cutting, FALSE if we're
 *                  copying.
 *
 * Return Value:
 *  LPDATAOBJECT    Pointer to the object created, NULL on failure
 */

LPDATAOBJECT CCosmoDoc::TransferObjectCreate(BOOL fCut)
    {
    UINT            i;
    HRESULT         hr;
    STGMEDIUM       stm;
    FORMATETC       fe;
    LPDATAOBJECT    pIDataObject=NULL;
    const UINT      cFormats=7;
    static UINT     rgcf[7]={0, 0, 0, CF_METAFILEPICT, CF_BITMAP, 0, 0};
    static DWORD    rgtm[7]={TYMED_HGLOBAL, TYMED_ISTORAGE, TYMED_HGLOBAL
        , TYMED_MFPICT, TYMED_GDI, TYMED_ISTREAM, TYMED_HGLOBAL};

    hr=CoCreateInstance(CLSID_DataTransferObject, NULL
        , CLSCTX_INPROC_SERVER, IID_IDataObject
        , (PPVOID)&pIDataObject);

    if (FAILED(hr))
        return NULL;

    rgcf[0]=m_cf;
    rgcf[1]=m_cfEmbedSource;
    rgcf[2]=m_cfObjectDescriptor;

    //Don't include link stuff for cutting (0 format always fails)
    if (!fCut)
        {
        rgcf[5]=m_cfLinkSource;
        rgcf[6]=m_cfLinkSrcDescriptor;
        }

    for (i=0; i < cFormats; i++)
        {
        /*
         * RenderFormat handles memory handles, but for compound doc
         * formats we need something more.  So if RenderFormat fails
         * (which it will for i=1, try our latest addition which
         * writes to a different field in the STGMEDIUM.
         */
        stm.hGlobal=RenderFormat(rgcf[i]);

        if (NULL==stm.hGlobal)
            {
            if (!RenderMedium(rgcf[i], &stm))
                continue;
            }

        stm.tymed=rgtm[i];
        stm.pUnkForRelease=NULL;
        SETDefFormatEtc(fe, rgcf[i], rgtm[i]);
        pIDataObject->SetData(&fe, &stm, TRUE);
        }

    return pIDataObject;    //Caller now responsible
    }

/*
 * CCosmoDoc::DropSelectTargetWindow
 * (Protected)
 *
 * Purpose:
 *  Creates a thin inverted frame around a window that we use to
 *  show the window as a drop target.  This is a toggle function:
 *  It uses XOR to create the effect so it must be called twice to
 *  leave the window as it was.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::DropSelectTargetWindow(void)
    {
    HDC         hDC;
    RECT        rc;

UINT        dd=3;
    HWND        hWnd;

    hWnd=m_pPL->Window();
    hDC=GetWindowDC(hWnd);
    GetClientRect(hWnd, &rc);

    //Frame this window with inverted pixels

    //Top
    PatBlt(hDC, rc.left, rc.top, rc.right-rc.left, dd, DSTINVERT);

    //Bottom
    PatBlt(hDC, rc.left, rc.bottom-dd, rc.right-rc.left, dd
        , DSTINVERT);

    //Left excluding regions already affected by top and bottom
    PatBlt(hDC, rc.left, rc.top+dd, dd, rc.bottom-rc.top-(2*dd)
        , DSTINVERT);

    //Right excluding regions already affected by top and bottom
    PatBlt(hDC, rc.right-dd, rc.top+dd, dd, rc.bottom-rc.top-(2*dd)
        , DSTINVERT);

    ReleaseDC(hWnd, hDC);
    return;
    }

//CHAPTER23MOD
/*
 * CCosmoDoc::OpenInPlaceObject
 *
 * Purpose:
 *  Instructs the current figure object to open into a window from
 *  in-place active state, if applicable.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CCosmoDoc::OpenInPlaceObject(void)
    {
    if (NULL!=m_pFigure)
        m_pFigure->OpenIntoWindow();

    return;
    }

//End CHAPTER23MOD

/*
 * CCosmoDoc::ColorSet
 *
 * Purpose:
 *  Changes a color used in our contained Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color.
 *
 * Return Value:
 *  COLORREF        Previous color for the given index.
 */

COLORREF CCosmoDoc::ColorSet(UINT iColor, COLORREF cr)
    {
    return m_pPL->ColorSet(iColor, cr);
    }

/*
 * CCosmoDoc::ColorGet
 *
 * Purpose:
 *  Retrieves a color currently in use in the Polyline.
 *
 * Parameters:
 *  iColor          UINT index of the color to retrieve.
 *
 * Return Value:
 *  COLORREF        Current color for the given index.
 */

COLORREF CCosmoDoc::ColorGet(UINT iColor)
    {
    return m_pPL->ColorGet(iColor);
    }

/*
 * CCosmoDoc::LineStyleSet
 *
 * Purpose:
 *  Changes the line style currently used in the Polyline
 *
 * Parameters:
 *  iStyle          UINT index of the new line style to use.
 *
 * Return Value:
 *  UINT            Previous line style.
 */

UINT CCosmoDoc::LineStyleSet(UINT iStyle)
    {
    return m_pPL->LineStyleSet(iStyle);
    }

/*
 * CCosmoDoc::LineStyleGet
 *
 * Purpose:
 *  Retrieves the line style currently used in the Polyline
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Current line style.
 */

UINT CCosmoDoc::LineStyleGet(void)
    {
    if (NULL==m_pPL)    //m_pPL might not be valid yet
        return 0L;

    return m_pPL->LineStyleGet();
    }

/*
 * CPolylineAdviseSink::CPolylineAdviseSink
 * CPolylineAdviseSink::~CPolylineAdviseSink
 *
 * Constructor Parameters:
 *  pv              LPVOID to store in this object
 */

CPolylineAdviseSink::CPolylineAdviseSink(LPVOID pv)
    {
    m_pv=pv;
    return;
    }

CPolylineAdviseSink::~CPolylineAdviseSink(void)
    {
    return;
    }

/*
 * CPolylineAdviseSink::OnPointChange
 *
 * Purpose:
 *  Informs the document that the polyline added or removed a point.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnPointChange(void)
    {
    PCDocument      pDoc=(PCDocument)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnSizeChange
 *
 * Purpose:
 *  Informs the document that the polyline changed size.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnSizeChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;
    RECT            rc;
    DWORD           dwStyle;

    /*
     * Polyline window is informing us that it changed size in
     * response to setting it's data.  Therefore we have to
     * size ourselves accordingly but without moving the screen
     * position of the polyline window.
     */

    pDoc->m_fNoSize=TRUE;

    //Set the document window size.
    GetWindowRect(pDoc->m_pPL->Window(), &rc);
    InflateRect(&rc, 8, 8);

    //Adjust for a window sans menu
    dwStyle=GetWindowLong(pDoc->m_hWnd, GWL_STYLE);
    AdjustWindowRect(&rc, dwStyle, FALSE);

    SetWindowPos(pDoc->m_hWnd, NULL, 0, 0, rc.right-rc.left
        , rc.bottom-rc.top, SWP_NOMOVE | SWP_NOZORDER);

    if (NULL!=pDoc->m_pAdv)
        pDoc->m_pAdv->OnSizeChange(pDoc, &rc);

    pDoc->m_fNoSize=FALSE;
    pDoc->FDirtySet(TRUE);

    return;
    }

/*
 * CPolylineAdviseSink::OnDataChange
 *
 * Purpose:
 *  Informs the document that the polyline data changed.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnDataChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    if (NULL!=pDoc->m_pAdv)
        pDoc->m_pAdv->OnDataChange(pDoc);

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnColorChange
 *
 * Purpose:
 *  Informs the document that the polyline data changed a color.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnColorChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }

/*
 * CPolylineAdviseSink::OnLineStyleChange
 *
 * Purpose:
 *  Informs the document that the polyline changed its line style.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolylineAdviseSink::OnLineStyleChange(void)
    {
    PCCosmoDoc      pDoc=(PCCosmoDoc)m_pv;

    pDoc->FDirtySet(TRUE);
    return;
    }


DROPSRC.CPP

/*
 * DROPSRC.CPP
 * Cosmo Chapter 23
 *
 * Implementation of a DropSource object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CDropSource::CDropSource
 * CDropSource::~CDropSource
 *
 * Constructor Parameters:
 *  pDoc            PCosmoDoc containing this interface.
 */

CDropSource::CDropSource(PCCosmoDoc pDoc)
    {
    m_cRef=0;
    m_pDoc=pDoc;
    return;
    }

CDropSource::~CDropSource(void)
    {
    return;
    }

/*
 * CDropSource::QueryInterface
 * CDropSource::AddRef
 * CDropSource::Release
 *
 * Purpose:
 *  IUnknown members for CDropSource object.
 */

STDMETHODIMP CDropSource::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDropSource==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDropSource::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropSource::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDropSource::QueryDragContinue
 *
 * Purpose:
 *  Determines whether to continue a drag operation or cancel it.
 *
 * Parameters:
 *  fEsc            BOOL indicating that the ESC key was pressed.
 *  grfKeyState     DWORD providing states of keys and mouse buttons
 *
 * Return Value:
 *  HRESULT         DRAGDROP_S_CANCEL to stop the drag,
 *                  DRAGDROP_S_DROP to drop the data where it is,
 *                  or NOERROR to continue.
 */

STDMETHODIMP CDropSource::QueryContinueDrag(BOOL fEsc
    , DWORD grfKeyState)
    {
    if (fEsc)
        return ResultFromScode(DRAGDROP_S_CANCEL);

    if (!(grfKeyState & MK_LBUTTON))
        return ResultFromScode(DRAGDROP_S_DROP);

    return NOERROR;
    }

/*
 * CDropSource::GiveFeedback
 *
 * Purpose:
 *  Provides cursor feedback to the user since the source task
 *  always has the mouse capture.  We can also provide any other
 *  type of feedback above cursors if we so desire.
 *
 * Parameters:
 *  dwEffect        DWORD effect flags returned from the last target
 *
 * Return Value:
 *  HRESULT         NOERROR if you set a cursor yourself or
 *                  DRAGDROP_S_USEDEFAULTCURSORS to let OLE do
 *                  the work.
 */

STDMETHODIMP CDropSource::GiveFeedback(DWORD dwEffect)
    {
    return ResultFromScode(DRAGDROP_S_USEDEFAULTCURSORS);
    }


DROPTGT.CPP

/*
 * DROPTGT.CPP
 * Cosmo Chapter 23
 *
 * Implementation of the IDropTarget interface.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CDropTarget::CDropTarget
 * CDropTarget::~CDropTarget
 *
 * Constructor Parameters:
 *  pDoc            PCCosmoDoc of the document containing us.
 */

CDropTarget::CDropTarget(PCCosmoDoc pDoc)
    {
    m_cRef=0;
    m_pDoc=pDoc;

    m_pIDataObject=NULL;
    return;
    }

CDropTarget::~CDropTarget(void)
    {
    return;
    }

/*
 * CDropTarget::QueryInterface
 * CDropTarget::AddRef
 * CDropTarget::Release
 *
 * Purpose:
 *  IUnknown members for CDropTarget object.
 */

STDMETHODIMP CDropTarget::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDropTarget==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDropTarget::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropTarget::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDropTarget::DragEnter
 *
 * Purpose:
 *  Indicates that data in a drag operation has been dragged over
 *  our window that's a potential target.  We are to decide if it's
 *  something we're interested in or not.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT providing the source data.
 *  grfKeyState     DWORD flags: states of keys and mouse buttons.
 *  pt              POINTL coordinates in the document client space.
 *  pdwEffect       LPDWORD into which we'll place the
 *                  appropriate effect flag for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragEnter(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    HWND        hWnd;

    /*
     * 1.  Check if we can paste from the pIDataSource we're
     *     provided. We already made this nice useful function
     *     to test.
     */

    m_pIDataObject=NULL;

    if (!m_pDoc->FQueryPasteFromData(pIDataSource))
        {
        *pdwEffect=DROPEFFECT_NONE;
        return NOERROR;
        }

    /*
     * 2.  We can always drop anywhere in our document,
     *     so pt is uninteresting.
     */

    /*
     * 3. We return either a COPY or MOVE effect flag, depending on
     *    the state of the grfKeyState flags.  We MOVE on no key or
     *    Shift key, COPY on Ctrl key.
     */

    //Default is move
    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        *pdwEffect=DROPEFFECT_COPY;

    /*
     * 4. We really don't need to keep the IDataObject around since
     *    we're not interested in it in DragOver.  However, we'll
     *    save it just for demonstration.
     */
    m_pIDataObject=pIDataSource;
    m_pIDataObject->AddRef();

    /*
     * 5.  We always accept drops of our data on us, so we only need
     *     to provide some UI feedback here which we do by inverting
     *     the edge of the polyline window in this document.  We'll
     *     remove this in DragLeave and in Drop.  DragOver won't
     *     effect it since we can always drop.
     *
     *     Since we're inverting a border, insure that this window is
     *     on top.
     */

    hWnd=m_pDoc->Window();
    BringWindowToTop(hWnd);
    UpdateWindow(hWnd);
    m_pDoc->DropSelectTargetWindow();

    return NOERROR;
    }

/*
 * CDropTarget::DragOver
 *
 * Purpose:
 *  Indicates that the mouse was moved inside the window represented
 *  by this drop target. This happens on every WM_MOUSEMOVE, so this
 *  function should be very efficient.
 *
 * Parameters:
 *  grfKeyState     DWORD providing current keyboard/mouse states
 *  pt              POINTL where the mouse currently is.
 *  pdwEffect       LPDWORD to store the effect flag for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragOver(DWORD grfKeyState, POINTL pt
    , LPDWORD pdwEffect)
    {
    if (NULL==m_pIDataObject)
        {
        *pdwEffect=DROPEFFECT_NONE;
        return NOERROR;
        }

    //We can always drop; return effect flags based on keys.
    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        *pdwEffect=DROPEFFECT_COPY;

    return NOERROR;
    }

/*
 * CDropTarget::DragLeave
 *
 * Purpose:
 *  Informs the drop target that the operation has left its window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragLeave(void)
    {
    //1.  Remove the UI feedback
   m_pDoc->DropSelectTargetWindow();

   //2.  Release the held IDataObject
   ReleaseInterface(m_pIDataObject);

    return NOERROR;
    }

/*
 * CDropTarget::Drop
 *
 * Purpose:
 *  Instructs the drop target to paste data that was just now
 *  dropped on it.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT from which we'll paste.
 *  grfKeyState     DWORD providing current keyboard/mouse state.
 *  pt              POINTL at which the drop occurred.
 *  pdwEffect       LPDWORD to store what you do with the data.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::Drop(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    BOOL        fRet=TRUE;

    *pdwEffect=DROPEFFECT_NONE;

    if (NULL==m_pIDataObject)
        return ResultFromScode(E_FAIL);

    //1.  Remove the UI feedback, release pointer
    DragLeave();

    //No point in drag-drop to ourselves (for Cosmo, at least)
    if (m_pDoc->m_fDragSource)
        return ResultFromScode(E_FAIL);

    //2.  Try a paste
    fRet=m_pDoc->PasteFromData(pIDataSource);

    //3.  Store the effect
    if (!fRet)
        return ResultFromScode(E_FAIL);

    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        *pdwEffect=DROPEFFECT_COPY;

    return NOERROR;
    }


FIGURE.CPP

/*
 * FIGURE.CPP
 * Cosmo Chapter 23
 *
 * Implementation of the CFigure object for Cosmo.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CFigure::CFigure
 * CFigure::~CFigure
 *
 * Parameters (Constructor):
 *  pfnDestroy      PFNDESTROYED to call when object is destroyed.
 *  pDoc            PCCosmoDoc we're associated with.
 */

CFigure::CFigure(PFNDESTROYED pfnDestroy, PCCosmoDoc pDoc)
    {
    m_cRef=0;
    m_pfnDestroy=pfnDestroy;

    m_pFR=NULL;     //We get this later through FrameSet.
    m_pDoc=pDoc;
    m_pPL=pDoc->m_pPL;

    m_fEmbedded=FALSE;

    //NULL any contained interfaces initially.
    m_pImpIPersistStorage=NULL;
    m_pIStorage=NULL;
    m_pIStream=NULL;
    m_pImpIDataObject=NULL;
    m_pIDataAdviseHolder=NULL;
    m_pImpIOleObject=NULL;
    m_pIOleAdviseHolder=NULL;
    m_pIOleClientSite=NULL;

    m_clsID=CLSID_CosmoFigure;
    m_cf=pDoc->m_cf;

    //These are for IDataObject::QueryGetData
    m_cfeGet=CFORMATETCGET;

    SETDefFormatEtc(m_rgfeGet[0], pDoc->m_cf, TYMED_HGLOBAL);
    SETDefFormatEtc(m_rgfeGet[1], pDoc->m_cfEmbedSource
        , TYMED_ISTORAGE);
    SETDefFormatEtc(m_rgfeGet[2], pDoc->m_cfObjectDescriptor
        , TYMED_HGLOBAL);
    SETDefFormatEtc(m_rgfeGet[3], CF_METAFILEPICT, TYMED_MFPICT);
    SETDefFormatEtc(m_rgfeGet[4], CF_BITMAP, TYMED_GDI);

    m_pST=NULL;

    m_pImpIPersistFile=NULL;

    //We live in the document's lifetime, so no need to AddRef here.
    m_pMoniker=m_pDoc->m_pMoniker;
    m_dwRegROT=0L;

    //CHAPTER23MOD
    m_pIOleIPSite=NULL;
    m_pIOleIPFrame=NULL;
    m_pIOleIPUIWindow=NULL;
    m_pImpIOleIPObject=NULL;
    m_pImpIOleIPActiveObject=NULL;
    m_hMenuShared=NULL;
    m_hOLEMenu=NULL;
    m_hAccel=NULL;
    m_pHW=NULL;
    m_pTB=NULL;
    m_cyBar=0;
    m_fUndoDeactivates=FALSE;
    m_fAllowInPlace=TRUE;
    m_fForceSave=FALSE;
    //End CHAPTER23MOD

    return;
    }

CFigure::~CFigure(void)
    {
    ReleaseInterface(m_pIOleClientSite);
    ReleaseInterface(m_pIDataAdviseHolder);
    ReleaseInterface(m_pIOleAdviseHolder);

    //CHAPTER23MOD
    if (NULL!=m_pHW)
        delete m_pHW;

    if (NULL!=m_pST)
        delete m_pST;

    /*
     * Free contained interfaces.
     * Container in-place interfaces released during deactivation.
     */

    if (NULL!=m_pTB)    //Safety-net
        delete m_pTB;

    DeleteInterfaceImp(m_pImpIOleIPObject);
    DeleteInterfaceImp(m_pImpIOleIPActiveObject);
    //End CHAPTER23MOD

    //Make sure no one thinks we're still running
    if (0L!=m_dwRegROT)
        INOLE_RevokeAsRunning(&m_dwRegROT);

    ReleaseInterface(m_pIStorage)
    ReleaseInterface(m_pIStream)

    DeleteInterfaceImp(m_pImpIPersistFile);
    DeleteInterfaceImp(m_pImpIOleObject)
    DeleteInterfaceImp(m_pImpIDataObject)
    DeleteInterfaceImp(m_pImpIPersistStorage);

    //Free strings.
    if (NULL!=m_pST)
        delete m_pST;

    return;
    }

/*
 * CFigure::QueryInterface
 * CFigure::AddRef
 * CFigure::Release
 *
 * Purpose:
 *  IUnknown members for CFigure object.
 */

STDMETHODIMP CFigure::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IPersist==riid || IID_IPersistStorage==riid)
        *ppv=m_pImpIPersistStorage;

    if (IID_IDataObject==riid)
        *ppv=m_pImpIDataObject;

    if (IID_IOleObject==riid)
        *ppv=m_pImpIOleObject;

    if (IID_IPersistFile==riid)
        *ppv=m_pImpIPersistFile;

    //CHAPTER23MOD
    if (IID_IOleWindow==riid || IID_IOleInPlaceObject==riid)
        *ppv=m_pImpIOleIPObject;
    //End CHAPTER23MOD

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CFigure::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CFigure::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    //Document deletes us
    return 0;
    }

/*
 * CFigure::Init
 *
 * Purpose:
 *  Performs any initialization of a CFigure that's prone to failure
 *  that we also use internally before exposing the object outside.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CFigure::Init(void)
    {
    m_pST=new CStringTable(m_pDoc->m_hInst);

    if (NULL==m_pST)
        return FALSE;

    if (!m_pST->Init(IDS_FIGUREMIN, IDS_FIGUREMAX))
        return FALSE;

    //Allocate contained interfaces.
    m_pImpIPersistStorage=new CImpIPersistStorage(this, this);

    if (NULL==m_pImpIPersistStorage)
        return FALSE;

    m_pImpIDataObject=new CImpIDataObject(this, this);

    if (NULL==m_pImpIDataObject)
        return FALSE;

    m_pImpIOleObject=new CImpIOleObject(this, this);

    if (NULL==m_pImpIOleObject)
        return FALSE;

    m_pImpIPersistFile=new CImpIPersistFile(this, this);

    if (NULL==m_pImpIPersistFile)
        return FALSE;

    //CHAPTER23MOD
    m_pImpIOleIPObject=new CImpIOleInPlaceObject(this, this);

    if (NULL==m_pImpIOleIPObject)
        return FALSE;

    m_pImpIOleIPActiveObject=new CImpIOleInPlaceActiveObject(this
        , this);

    if (NULL==m_pImpIOleIPActiveObject)
        return FALSE;

    m_pHW=new CHatchWin(m_pDoc->m_hInst);

    if (NULL==m_pHW)
        return FALSE;

    //We don't have m_pFR yet from which to get the frame window.
    if (!m_pHW->Init(m_pDoc->Window(), ID_HATCHWINDOW, NULL))
        return FALSE;
    //End CHAPTER23MOD

    return TRUE;
    }

/*
 * CFigure::FrameSet
 *
 * Purpose:
 *  Provides the compound document object with access to the frame
 *  of this application for UI purposes.
 *
 * Parameters:
 *  pFR             PCCosmoFrame of the frame window.
 *
 * Return Value:
 *  None
 */

void CFigure::FrameSet(PCCosmoFrame pFR)
    {
    m_pFR=pFR;

    //CHAPTER23MOD
    //We need this in IOleInPlaceActiveObject::ResizeBorder
    m_cyBar=m_pFR->m_cyBar;

    //We need this in IOleInPlaceActiveObject::TranslateAccelerator
    m_hAccel=m_pFR->m_hAccel;
    //End CHAPTER23MOD
    return;
    }

/*
 * CFigure::FIsDirty
 *
 * Purpose:
 *  Checks if the document is dirty.  This can be called from
 *  IPersistStorage::IsDirty which doesn't have access to CCosmoDoc.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if dirty, FALSE if clean.
 */

BOOL CFigure::FIsDirty(void)
    {
    //CHAPTER23MOD
    //Force a save if we opened after being in-place.
    return m_pDoc->m_fDirty || m_fForceSave;
    //End CHAPTER23MOD
    }

/*
 * CFigure::FIsEmbedded
 *
 * Purpose:
 *  Answers if the object is embedded or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the object is embedded, FALSE otherwise.
 */

BOOL CFigure::FIsEmbedded(void)
    {
    return m_fEmbedded;
    }

/*
 * CFigure::SendAdvise
 *
 * Purpose:
 *  Calls the appropriate IOleClientSite or IAdviseSink member
 *  function for various events such as closure, saving, etc.
 *
 * Parameters:
 *  uCode           UINT OBJECTCODE_* identifying the notification.
 *
 * Return Value:
 *  None
 */

void CFigure::SendAdvise(UINT uCode)
    {
    switch (uCode)
        {
        case OBJECTCODE_SAVED:
            if (NULL!=m_pIOleAdviseHolder)
                m_pIOleAdviseHolder->SendOnSave();

break;

        case OBJECTCODE_CLOSED:
            if (NULL!=m_pIOleAdviseHolder)
                m_pIOleAdviseHolder->SendOnClose();

break;

        case OBJECTCODE_RENAMED:
            m_pMoniker=m_pDoc->m_pMoniker;  //For IOleObject::GetMoniker
            m_dwRegROT=m_pDoc->m_dwRegROT;

            if (NULL!=m_pIOleAdviseHolder)
                m_pIOleAdviseHolder->SendOnRename(m_pMoniker);

            break;

        case OBJECTCODE_SAVEOBJECT:
            if (FIsDirty() && NULL!=m_pIOleClientSite)
                m_pIOleClientSite->SaveObject();

            break;

        case OBJECTCODE_DATACHANGED:
            //No flags are necessary here.
            if (NULL!=m_pIDataAdviseHolder)
                {
                m_pIDataAdviseHolder->SendOnDataChange
                    (m_pImpIDataObject, 0, 0);
                }

            //Tell the running object table of the change
            if (0!=m_dwRegROT)
                INOLE_NoteChangeTime(m_dwRegROT, NULL, NULL);

            //CHAPTER23MOD
            /*
             * If this is the first change after activation, tell the
             * container to free any undo information it's holding.
             */
            if (NULL!=m_pIOleIPSite && m_fUndoDeactivates)
                m_pIOleIPSite->DiscardUndoState();

            m_fUndoDeactivates=FALSE;
            //End CHAPTER23MOD

            break;

        case OBJECTCODE_SHOWWINDOW:
            if (NULL!=m_pIOleClientSite)
                m_pIOleClientSite->OnShowWindow(TRUE);

            break;

        case OBJECTCODE_HIDEWINDOW:
            if (NULL!=m_pIOleClientSite)
                m_pIOleClientSite->OnShowWindow(FALSE);

            break;

        case OBJECTCODE_SHOWOBJECT:
            if (NULL!=m_pIOleClientSite)
                m_pIOleClientSite->ShowObject();

            break;
        }

    return;
    }

//CHAPTER23MOD
/*
 * CFigure::InPlaceActivate
 *
 * Purpose:
 *  Goes through all the steps of activating the Figure as an
 *  in-place object.
 *
 * Parameters:
 *  pActiveSite     LPOLECLIENTSITE of the active site we show in.
 *  fIncludeUI      BOOL indicating if we should do UI as well.
 *
 * Return Value:
 *  HRESULT         Whatever error code is appropriate.
 */

HRESULT CFigure::InPlaceActivate(LPOLECLIENTSITE pActiveSite
    , BOOL fIncludeUI)
    {
    HRESULT                 hr;
    HWND                    hWnd, hWndHW;
    RECT                    rcPos;
    RECT                    rcClip;

    if (NULL==pActiveSite)
        return ResultFromScode(E_INVALIDARG);

    //If we're already active, do the UI and we're done.
    if (NULL!=m_pIOleIPSite)
        {
        if (fIncludeUI)
            UIActivate();

        return NOERROR;
        }

    /*
     * 1.  Initialization, obtaining interfaces, calling
     *     OnInPlaceActivate.
     */
    hr=pActiveSite->QueryInterface(IID_IOleInPlaceSite
        , (PPVOID)&m_pIOleIPSite);

    if (FAILED(hr))
        return hr;

    hr=m_pIOleIPSite->CanInPlaceActivate();

    if (NOERROR!=hr)
        {
        m_pIOleIPSite->Release();
        m_pIOleIPSite=NULL;
        return ResultFromScode(E_FAIL);
        }

    m_pIOleIPSite->OnInPlaceActivate();
    m_fUndoDeactivates=TRUE;

    /*
     * 2.  Get the window context and create a window or change the
     *     parent and position of an existing window.  Servers are
     *     required to full cb in the OLEINPLACEFRAMEINFO structure.
     */
    m_pIOleIPSite->GetWindow(&hWnd);
    m_pFR->m_frameInfo.cb=sizeof(OLEINPLACEFRAMEINFO);

    m_pIOleIPSite->GetWindowContext(&m_pIOleIPFrame
        , &m_pIOleIPUIWindow, &rcPos, &rcClip
        , &m_pFR->m_frameInfo);

    /*
     * Copy container frame pointer to CCosmoFrame for accelerators.
     * No AddRef because frame never messes with it.  Note also that
     * we don't do anything special with our own accelerators here
     * because we just use the same ones as always.
     */
    m_pFR->m_pIOleIPFrame=m_pIOleIPFrame;

    /*
     * We'll use a hatch window as the child of the container and the
     * editing window as a child of the hatch window.  We already
     * created the hatch window, so now all we have to do is put it
     * in the right place and stick the Polyline in it.
     */

    m_pHW->HwndAssociateSet(m_pFR->Window());

    m_pHW->ChildSet(m_pPL->Window());   //Calls SetParent
    m_pHW->RectsSet(&rcPos, &rcClip);   //Positions polyline

    hWndHW=m_pHW->Window();
    SetParent(hWndHW, hWnd);            //Move the hatch window
    ShowWindow(hWndHW, SW_SHOW);        //Make us visible.
    SendAdvise(OBJECTCODE_SHOWOBJECT);

    if (fIncludeUI)
        return UIActivate();

    return NOERROR;
    }

/*
 * CFigure::InPlaceDeactivate
 *
 * Purpose:
 *  Reverses all the activation steps from InPlaceActivate.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CFigure::InPlaceDeactivate(void)
    {
    RECT        rc;

    UIDeactivate();

    /*
     * When setting the parent back to the normal document,
     * reposition the Polyline to be at (8,8) instead of at wherever
     * it was in the container's window or our hatch window.  This
     * is so if we are deactivating to open in our own window the
     * Polyline appears in the proper place in the document window.
     */

    SetParent(m_pPL->Window(), m_pDoc->m_hWnd);
    m_pHW->ChildSet(NULL);

    //Make sure the hatch window is invisible and owned by Cosmo
    ShowWindow(m_pHW->Window(), SW_HIDE);
    SetParent(m_pHW->Window(), m_pDoc->m_hWnd);

    GetClientRect(m_pDoc->m_hWnd, &rc);
    InflateRect(&rc, -8, -8);

    SetWindowPos(m_pPL->Window(), NULL, rc.left, rc.top
        , rc.right-rc.left, rc.bottom-rc.top
        , SWP_NOZORDER | SWP_NOACTIVATE);

    if (NULL!=m_pIOleIPSite)
        m_pIOleIPSite->OnInPlaceDeactivate();

    m_pFR->m_pIOleIPFrame=NULL;
    ReleaseInterface(m_pIOleIPFrame);
    ReleaseInterface(m_pIOleIPUIWindow);
    ReleaseInterface(m_pIOleIPSite);

    return;
    }

/*
 * CFigure::UIActivate
 *
 * Purpose:
 *  Goes through all the steps of activating the user interface of
 *  the Figure as an in-place object.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or error code.
 */

HRESULT CFigure::UIActivate(void)
    {
    //1.  Call IOleInPlaceSite::UIActivate
    if (NULL!=m_pIOleIPSite)
        m_pIOleIPSite->OnUIActivate();

    //2.  Critical for accelerators to work initially.
    SetFocus(m_pHW->Window());

    //3.  Set the active object

   #ifdef WIN32ANSI
    OLECHAR     szTemp[40];

    MultiByteToWideChar(CP_ACP, 0, PSZ(IDS_INPLACETITLE)
        , -1, szTemp, 40);
   #endif

    if (NULL!=m_pIOleIPFrame)
        {
        m_pIOleIPFrame->SetActiveObject(m_pImpIOleIPActiveObject
           #ifdef WIN32ANSI
            , szTemp);
           #else
            , PSZ(IDS_INPLACETITLE));
           #endif
        }

    if (NULL!=m_pIOleIPUIWindow)
        {
        m_pIOleIPUIWindow->SetActiveObject(m_pImpIOleIPActiveObject
           #ifdef WIN32ANSI
            , szTemp);
           #else
            , PSZ(IDS_INPLACETITLE));
           #endif
        }

    //4.  Create frame tools
    InPlaceToolsCreate();

    //5.  Create the shared menu.
    InPlaceMenuCreate();

    return NOERROR;
    }

/*
 * CFigure::UIDeactivate
 *
 * Purpose:
 *  Reverses all the user interface activation steps from
 *  UIActivate.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CFigure::UIDeactivate(void)
    {
    //1.  Remove the in-place tools
    InPlaceToolsDestroy();

    //2.  Remove the shared menu.
    InPlaceMenuDestroy();

    //3.  Set active obejcts to NULL
    if (NULL!=m_pIOleIPFrame)
        m_pIOleIPFrame->SetActiveObject(NULL, NULL);

    if (NULL!=m_pIOleIPUIWindow)
        m_pIOleIPUIWindow->SetActiveObject(NULL, NULL);

    //3.  Call IOleInPlaceSite::OnUIDeactivate
    if (NULL!=m_pIOleIPSite)
        m_pIOleIPSite->OnUIDeactivate(FALSE);

    return;
    }

/*
 * CFigure::InPlaceMenuCreate
 *
 * Purpose:
 *  Creates and sets a menu for an in-place embedded object.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if everything is well, FALSE on error.
 */

BOOL CFigure::InPlaceMenuCreate(void)
    {
    HMENU               hMenu, hMenuT;
    UINT                uTemp=MF_BYPOSITION | MF_POPUP;
    UINT                i;
    OLEMENUGROUPWIDTHS  mgw;

    for (i=0; i<6; i++)
        mgw.width[i]=0;

    //We already have popup menu handles in m_pFR->m_phMenu[]

    //Create the new shared menu and let container do its thing
    hMenu=CreateMenu();
    m_pIOleIPFrame->InsertMenus(hMenu, &mgw);

    /*
     * Add our menus remembering that the container has added its
     * already.
     */
    InsertMenu(hMenu, (UINT)mgw.width[0]
       , uTemp, (UINT)m_pFR->m_phMenu[1], PSZ(IDS_MENUEDIT));

    /*
     * Add the Open item to the edit menu.
     * NOTE:  If you are a multiple-use server, this sort of code
     * will also modify the menu on the server window as well as
     * this shared menu in which case you need a separate popup menu
     * altogether.
     */
    AppendMenu(m_pFR->m_phMenu[1], MF_SEPARATOR, 0, NULL);
    AppendMenu(m_pFR->m_phMenu[1], MF_STRING, IDM_EDITOPEN
        , PSZ(IDS_MENUOPEN));

    InsertMenu(hMenu, (UINT)mgw.width[0]+1+(UINT)mgw.width[2]
       , uTemp, (UINT)m_pFR->m_phMenu[2], PSZ(IDS_MENUCOLOR));

    InsertMenu(hMenu, (UINT)mgw.width[0]+1+(UINT)mgw.width[2]+1
       , uTemp, (UINT)m_pFR->m_phMenu[3], PSZ(IDS_MENULINE));

    //Window menu position changes between MDI and SDI
   #ifdef MDI
    hMenuT=m_pFR->m_phMenu[5];
   #else
    hMenuT=m_pFR->m_phMenu[4];
   #endif

    InsertMenu(hMenu, (UINT)mgw.width[0]+1+(UINT)mgw.width[2]+2
        + (UINT)mgw.width[4], uTemp, (UINT)hMenuT
        , PSZ(IDS_MENUHELP));

    //Tell OLE how many items in each group are ours.
    mgw.width[1]=1;
    mgw.width[3]=2;
    mgw.width[5]=1;

    m_hMenuShared=hMenu;
    m_hOLEMenu=OleCreateMenuDescriptor(m_hMenuShared, &mgw);

    m_pIOleIPFrame->SetMenu(m_hMenuShared, m_hOLEMenu
        , m_pFR->Window());
    return TRUE;
    }

/*
 * CFigure::InPlaceMenuDestroy
 *
 * Purpose:
 *  Performs opposite actions from InPlaceMenuCreate
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if all is well, FALSE otherwise.
 */

BOOL CFigure::InPlaceMenuDestroy(void)
    {
    int         cItems, i, j;
    HMENU       hMenuT;

    //If we don't have a shared menu, nothing to do.
    if (NULL==m_hMenuShared)
        return TRUE;

    //Stop the container frame from using this menu.
    m_pIOleIPFrame->SetMenu(NULL, NULL, NULL);

    //Clean up what we got from OleCreateMenuDescriptor.
    OleDestroyMenuDescriptor(m_hOLEMenu);
    m_hOLEMenu=NULL;

    cItems=GetMenuItemCount(m_hMenuShared);

    /*
     * Walk backwards down the menu.  For each popup, see if it
     * matches any other popup we know about, and if so, remove
     * it from the shared menu.
     */
    for (i=cItems; i >=0; i--)
        {
        hMenuT=GetSubMenu(m_hMenuShared, i);

        for (j=0; j <= CMENUS; j++)
            {
            /*
             * If the submenu matches any we have, remove, don't
             * delete. Since we're walking backwards this only
             * affects the positions of those menus after us so the
             * GetSubMenu call above is not affected.
             */
            if (hMenuT==m_pFR->m_phMenu[j])
                RemoveMenu(m_hMenuShared, i, MF_BYPOSITION);
            }
        }

    /*
     * Remove the Open item and separator from the Edit menu.
     * NOTE:  If you are a multiple-user server, this affects the
     * menu on the server window as well as the shared menu in which
     * case you need to use a separate popup menu altogether.
     */
    RemoveMenu(m_pFR->m_phMenu[1], MPOS_OPEN, MF_BYPOSITION);
    RemoveMenu(m_pFR->m_phMenu[1], MPOS_SEP,  MF_BYPOSITION);

    if (NULL!=m_pIOleIPFrame)
        m_pIOleIPFrame->RemoveMenus(m_hMenuShared);

    DestroyMenu(m_hMenuShared);
    m_hMenuShared=NULL;
    return TRUE;
    }

/*
 * CFigure::InPlaceToolsCreate
 *
 * Purpose:
 *  Creates a toolbar for in-place activation and negotiates the
 *  border space for the toolbar.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if we could allocate the space and create
 *                  the tools.  FALSE if we fail, meaning that we
 *                  just do in-place with menus only, since we can
 *                  do without the tools.  We could fail and attempt
 *                  to reverse what we might have done already, but
 *                  since we don't really *need* the toolbar, we
 *                  keep going.
 */

BOOL CFigure::InPlaceToolsCreate(void)
    {
    BORDERWIDTHS    bw;
    HWND            hWnd;
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;
    UINT            utEx  =GIZMOTYPE_BUTTONATTRIBUTEEX;
    UINT            i;
    HBITMAP         hBmp;
    RECT            rc;
    UINT            dxB, dyB;

    //We don't need anything on the document, so send zeros.
    SetRectEmpty((LPRECT)&bw);

    if (NULL!=m_pIOleIPUIWindow)
        m_pIOleIPUIWindow->SetBorderSpace(&bw);

    if (NULL==m_pIOleIPFrame)
        return FALSE;

    /*
     * 1.  Make sure we can reserve space for what we need.  If this
     *     fails then we just do without because the menu has
     *     everything we really need.  A more willing server could
     *     put tools in popup windows as well.
     */

    if (!InPlaceToolsRenegotiate())
        {
        //If the container doesn't allow us any, don't ask for any.
        m_pIOleIPFrame->SetBorderSpace(&bw);
        return FALSE;
        }

    /*
     * 2.  Create the toolbar window using the container window as
     *     the parent.  In order to get messages from it, we use
     *     it's AssociateSet ability directly after creation and
     *     before we add any tools to the bar.
     */

    m_pIOleIPFrame->GetWindow(&hWnd);

    //If we already have a toolbar, just show it again.
    if (NULL!=m_pTB)
        {
        ShowWindow(m_pTB->Window(), SW_SHOW);
        return TRUE;
        }

    m_pTB=new CToolBar(m_pFR->m_hInst);

    if (NULL==m_pTB)
        {
        SetRectEmpty((LPRECT)&bw);
        m_pIOleIPFrame->SetBorderSpace(&bw);
        return FALSE;
        }

    m_pTB->Init(hWnd, ID_GIZMOBAR, m_cyBar);
    g_pInPlaceTB=m_pTB;

    //Insure the tools are initially invisible
    ShowWindow(m_pTB->Window(), SW_HIDE);

    //Tell the toolbar who to send messages to.
    m_pTB->HwndAssociateSet(m_pFR->m_hWnd);

    /*
     * Add tools to the bar, setting CFrame::fInit to avoid
     * command processing
     */
    m_pFR->m_fInit=TRUE;

    hBmp=m_pFR->m_hBmp;
    dxB=m_pFR->m_dxB;
    dyB=m_pFR->m_dyB;

    //Edit Undo, Cut, Copy, Paste
    m_pTB->Add(utCmd, 1, IDM_EDITUNDO,  dxB, dyB, NULL, hBmp, 1, uState);
    m_pTB->Add(utCmd, 2, IDM_EDITCUT,   dxB, dyB, NULL, NULL, 0, uState);
    m_pTB->Add(utCmd, 3, IDM_EDITCOPY,  dxB, dyB, NULL, NULL, 1, uState);
    m_pTB->Add(utCmd, 4, IDM_EDITPASTE, dxB, dyB, NULL, NULL, 2, uState);

    //Separator
    m_pTB->Add(GIZMOTYPE_SEPARATOR, 5, 0, 6, dyB, NULL, NULL, 0, uState);

    //Color Background and Color Line
    m_pTB->Add(utCmd, 6, IDM_COLORBACKGROUND, dxB, dyB, NULL, hBmp, 3
               , GIZMO_NORMAL | PRESERVE_BLACK);

    m_pTB->Add(utCmd, 7, IDM_COLORLINE, dxB, dyB, NULL, hBmp, 4, uState);

    //Separator
    m_pTB->Add(GIZMOTYPE_SEPARATOR, 8, 0, 6, dyB, NULL, NULL, 0, uState);

    //Line styles.
    m_pTB->Add(utEx, 19, IDM_LINESOLID, dxB, dyB, NULL, hBmp, 5, uState);
    m_pTB->Add(utEx, 10, IDM_LINEDASH, dxB, dyB, NULL, hBmp, 6, uState);
    m_pTB->Add(utEx, 11, IDM_LINEDOT, dxB, dyB, NULL, hBmp, 7, uState);
    m_pTB->Add(utEx, 12, IDM_LINEDASHDOT, dxB, dyB, NULL, hBmp, 8
        , uState);
    m_pTB->Add(utEx, 13, IDM_LINEDASHDOTDOT, dxB, dyB, NULL, hBmp, 9
        , uState);

    //Check the current line style.
    i=m_pPL->LineStyleGet()+IDM_LINEMIN;
    m_pTB->Check(i, TRUE);

    m_pFR->m_fInit=FALSE;

    /*
     * Before making the toolbar visible, resize it to the
     * container's GetBorder rectangle.  By default the toolbar
     * sizes itself to the client area of the parent, but we can't
     * assume that's the same as GetBorder returns, so we do the
     * extra work here.
     */

    m_pIOleIPFrame->GetBorder(&rc);
    SetWindowPos(m_pTB->Window(), NULL, rc.left, rc.top
        , rc.right-rc.left, rc.top+m_cyBar, SWP_NOZORDER);

    //3.  Make the tools visible.
    ShowWindow(m_pTB->Window(), SW_SHOW);

    return TRUE;
    }

/*
 * CFigure::InPlaceToolsDestroy
 *
 * Purpose:
 *  Reverses the process of InPlaceToolsCreate
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CFigure::InPlaceToolsDestroy(void)
    {
    //Nothing to do if we never created anything.
    if (NULL==m_pTB)
        return TRUE;

    /*
     * No reason to call SetBorderSpace with an empty rectangle
     * since you call IOleInPlaceSite::OnUIDeactivate.  The
     * container will restore its own tools appropriately.
     */

    //Destroy the toolbar.
    if (NULL!=m_pTB)
        {
        delete m_pTB;
        m_pTB=NULL;
        g_pInPlaceTB=NULL;
        }

    return TRUE;
    }

/*
 * CFigure::InPlaceToolsRenegotiate
 *
 * Purpose:
 *  Calls IOleInPlaceFrame::RequestBorderSpace and SetBorderSpace
 *  to reserve space for our toolbar.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if all is well, FALSE otherwise.
 */

BOOL CFigure::InPlaceToolsRenegotiate(void)
    {
    HRESULT         hr;
    BORDERWIDTHS    bw;

    SetRect((LPRECT)&bw, 0, m_pFR->m_cyBar, 0, 0);

    hr=m_pIOleIPFrame->RequestBorderSpace(&bw);

    if (NOERROR!=hr)
        return FALSE;

    //Safety net:  RequestBorderSpace may modify values in bw
    SetRect((LPRECT)&bw, 0, m_pFR->m_cyBar, 0, 0);
    m_pIOleIPFrame->SetBorderSpace(&bw);
    return TRUE;
    }

/*
 * CFigure::OpenIntoWindow
 *
 * Purpose:
 *  If we're current open in-place, send ourselves the OPEN verb to
 *  show into a full window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CFigure::OpenIntoWindow(void)
    {
    if (NULL!=m_pIOleIPSite)
        {
        //Make sure we don't try to do this.
        m_fUndoDeactivates=FALSE;

        /*
         * We can get away with passing a lot of NULLs since we know
         * how we implemented DoVerb.
         */
        m_pImpIOleObject->DoVerb(OLEIVERB_OPEN, NULL
            , m_pIOleClientSite, -1, NULL, NULL);

        //This makes sure we save ourselves when closing.
        m_fForceSave=TRUE;

        //Repaint the container immediately
        SendAdvise(OBJECTCODE_DATACHANGED);
        }

    return;
    }

/*
 * Undo
 *
 * Purpose:
 *  If we have not done anything else in this object then call
 *  IOleInPlaceSite::DeactivateAndUndo and return TRUE, otherwise
 *  just return FALSE.  Note that the m_fUndoDeactivates is set
 *  to FALSE in CFigure::SendAdvise for OBJECTCODE_DATACHANGED.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if we deactivated, FALSE otherwise.
 */

BOOL CFigure::Undo(void)
    {
    if (!m_fUndoDeactivates)
        return FALSE;

    m_fUndoDeactivates=FALSE;
    m_pIOleIPSite->DeactivateAndUndo();
    return TRUE;
    }

//End CHAPTER23MOD


ICLASSF.CPP

/*
 * ICLASSF.CPP
 * Cosmo Chapter 23
 *
 * Implementation of Cosmo's class factory.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CFigureClassFactory::CFigureClassFactory
 * CFigureClassFactory::~CFigureClassFactory
 *
 * Constructor Parameters:
 *  pFR             PCCosmoFrame that can create documents.
 */

CFigureClassFactory::CFigureClassFactory(PCCosmoFrame pFR)
    {
    m_cRef=0L;
    m_pFR=pFR;
    m_fCreated=FALSE;
    return;
    }

CFigureClassFactory::~CFigureClassFactory(void)
    {
    return;
    }

/*
 * CFigureClassFactory::QueryInterface
 * CFigureClassFactory::AddRef
 * CFigureClassFactory::Release
 */

STDMETHODIMP CFigureClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CFigureClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CFigureClassFactory::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CFigureClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a Figure object that supports embedding.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we are
 *                  being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise contains
 *                  E_NOINTERFACE if we cannot support the requested
 *                  interface.
 */

STDMETHODIMP CFigureClassFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCCosmoDoc          pDoc;
    HRESULT             hr;

    *ppvObj=NULL;

    //Great idea to protect yourself from multiple creates here.
    if (m_fCreated)
        return ResultFromScode(E_UNEXPECTED);

    m_fCreated=TRUE;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //We don't support aggregation
    if (NULL!=pUnkOuter)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    //Try creating a new document, which creates the object.
    pDoc=(PCCosmoDoc)m_pFR->m_pCL->NewDocument(TRUE);

    if (NULL==pDoc)
        {
        //This will cause shutdown; object count will go to zero.
        g_cObj++;
        ObjectDestroyed();
        return hr;
        }

    //Insure the document is untitled; get the requested interface.
    pDoc->Load(TRUE, NULL);
    pDoc->m_pFigure->FrameSet(m_pFR);
    hr=pDoc->m_pFigure->QueryInterface(riid, ppvObj);

    //Closing the document will destroy the objec, cause shutdown.
    if (FAILED(hr))
        {
        m_pFR->m_pCL->CloseDocument(pDoc);
        return hr;
        }

    return NOERROR;
    }

/*
 * CFigureClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the serving
 *  IClassFactory object.  When the number of locks goes to
 *  zero and the number of objects is zero, we shut down the
 *  application.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CFigureClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        {
        g_cLock--;
        g_cObj++;
        ObjectDestroyed();
        }

    return NOERROR;
    }


IDATAOBJ.CPP

/*
 * IDATAOBJ.CPP
 * Cosmo Chapter 23
 *
 * Implementation of the IDataObject interface.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CImpIDataObject::CImpIDataObject
 * CImpIDataObject::~CImpIDataObject
 *
 * Parameters (Constructor):
 *  pObj            PCFigure of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIDataObject::CImpIDataObject(PCFigure pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIDataObject::~CImpIDataObject(void)
    {
    return;
    }

/*
 * CImpIDataObject::QueryInterface
 * CImpIDataObject::AddRef
 * CImpIDataObject::Release
 */

STDMETHODIMP CImpIDataObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIDataObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIDataObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIDataObject::GetData
 *
 * Purpose:
 *  Retrieves data described by a specific FormatEtc into a StgMedium
 *  allocated by this function.  Used like GetClipboardData.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *  pSTM            LPSTGMEDIUM in which to return the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetData(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM)
    {
    UINT            cf=pFE->cfFormat;
    BOOL            fRet=FALSE;

    //Another part of us already knows if the format is good.
    if (NOERROR!=QueryGetData(pFE))
        return ResultFromScode(DATA_E_FORMATETC);

    if (CF_METAFILEPICT==cf || CF_BITMAP==cf || m_pObj->m_cf==cf)
        {
        if (CF_METAFILEPICT==cf)
            {
            pSTM->tymed=TYMED_MFPICT;
            }
        else
            pSTM->tymed=TYMED_HGLOBAL;

        pSTM->pUnkForRelease=NULL;
        pSTM->hGlobal=m_pObj->m_pDoc->RenderFormat(cf);
        fRet=(NULL!=pSTM->hGlobal);
        }
    else
        fRet=m_pObj->m_pDoc->RenderMedium(cf, pSTM);

    return fRet ? NOERROR : ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::GetDataHere
 *
 * Purpose:
 *  Renders the specific FormatEtc into caller-allocated medium
 *  provided in pSTM.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *  pSTM            LPSTGMEDIUM providing the medium into which
 *                  wer render the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetDataHere(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM)
    {
    UINT        cf;
    LONG        lRet;

    /*
     * The only reasonable time this is called is for
     * CFSTR_EMBEDSOURCE and TYMED_ISTORAGE (and later for
     * CFSTR_LINKSOURCE).  This means the same as
     * IPersistStorage::Save.
     */

    cf=RegisterClipboardFormat(CFSTR_EMBEDSOURCE);

    //Aspect is unimportant to us here, as is lindex and ptd.
    if (cf==pFE->cfFormat && (TYMED_ISTORAGE & pFE->tymed))
        {
        //We have an IStorage we can write into.
        pSTM->tymed=TYMED_ISTORAGE;
        pSTM->pUnkForRelease=NULL;
        lRet=m_pObj->m_pPL->WriteToStorage(pSTM->pstg
            , VERSIONCURRENT);

        //These are for creating an embedded object from a file
        WriteClassStg(pSTM->pstg, CLSID_CosmoFigure);
        WriteFmtUserTypeStg(pSTM->pstg, m_pObj->m_cf
            , (*m_pObj->m_pST)[IDS_USERTYPE]);

        if (lRet >= 0)
            return NOERROR;

        return ResultFromScode(STG_E_WRITEFAULT);
        }

    return ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::QueryGetData
 *
 * Purpose:
 *  Tests if a call to GetData with this FormatEtc will provide
 *  any rendering; used like IsClipboardFormatAvailable.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::QueryGetData(LPFORMATETC pFE)
    {
    UINT            cf=pFE->cfFormat;
    UINT            i;

    //Check the aspects we support.
    if (!(DVASPECT_CONTENT & pFE->dwAspect))
        return ResultFromScode(S_FALSE);

    for (i=0; i < m_pObj->m_cfeGet; i++)
        {
        if (pFE->cfFormat==m_pObj->m_rgfeGet[i].cfFormat
            && pFE->tymed & m_pObj->m_rgfeGet[i].tymed)
            {
            return NOERROR;
            }
        }

    return ResultFromScode(S_FALSE);
    }

/*
 * CImpIDataObject::GetCanonicalFormatEtc
 *
 * Purpose:
 *  Provides the caller with an equivalent FormatEtc to the one
 *  provided when different FormatEtcs will produce exactly the
 *  same renderings.
 *
 * Parameters:
 *  pFEIn            LPFORMATETC of the first description.
 *  pFEOut           LPFORMATETC of the equal description.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetCanonicalFormatEtc
    (LPFORMATETC pFEIn, LPFORMATETC pFEOut)
    {
    if (NULL==pFEOut)
        return ResultFromScode(E_INVALIDARG);

    pFEOut->ptd=NULL;
    return ResultFromScode(DATA_S_SAMEFORMATETC);
    }

/*
 * CImpIDataObject::SetData
 *
 * Purpose:
 *  Places data described by a FormatEtc and living in a StgMedium
 *  into the object.  The object may be responsible to clean up the
 *  StgMedium before exiting.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the data to set.
 *  pSTM            LPSTGMEDIUM containing the data.
 *  fRelease        BOOL indicating if this function is responsible
 *                  for freeing the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::SetData(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM, BOOL fRelease)
    {
    LONG            lRet;

    /*
     * Data can only come from global memory containing a
     * POLYLINEDATA structure that we send to the Polyline's
     * DataSetMem.
     */
    if ((pFE->cfFormat!=m_pObj->m_cf)
        || !(DVASPECT_CONTENT & pFE->dwAspect)
        || (TYMED_HGLOBAL!=pSTM->tymed))
        return ResultFromScode(DATA_E_FORMATETC);

    lRet=m_pObj->m_pPL->DataSetMem(pSTM->hGlobal, FALSE, TRUE
        , TRUE);

    if (fRelease)
        ReleaseStgMedium(pSTM);

    return (POLYLINE_E_NONE==lRet) ?
        NOERROR : ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::EnumFormatEtc
 *
 * Purpose:
 *  Returns an IEnumFORMATETC object through which the caller can
 *  iterate to learn about all the data formats this object can
 *  provide through either GetData[Here] or SetData.
 *
 * Parameters:
 *  dwDir           DWORD describing a data direction, either
 *                  DATADIR_SET or DATADIR_GET.
 *  ppEnum          LPENUMFORMATETC * in which to return the
 *                  pointer to the enumerator.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::EnumFormatEtc(DWORD dwDir
    , LPENUMFORMATETC *ppEnum)
    {
    return ResultFromScode(OLE_S_USEREG);
    }

/*
 * CImpIDataObject::DAdvise
 * CImpIDataObject::DUnadvise
 * CImpIDataObject::EnumDAdvise
 *
 * Pass-throughs to IDataAdviseHolder.
 */

STDMETHODIMP CImpIDataObject::DAdvise(LPFORMATETC pFE, DWORD dwFlags
    , LPADVISESINK pIAdviseSink, LPDWORD pdwConn)
    {
    HRESULT         hr;

    if (NULL==m_pObj->m_pIDataAdviseHolder)
        {
        hr=CreateDataAdviseHolder(&m_pObj->m_pIDataAdviseHolder);

        if (FAILED(hr))
            return ResultFromScode(E_OUTOFMEMORY);
        }

    hr=m_pObj->m_pIDataAdviseHolder->Advise(this, pFE
        , dwFlags, pIAdviseSink, pdwConn);

    return hr;
    }

STDMETHODIMP CImpIDataObject::DUnadvise(DWORD dwConn)
    {
    if (NULL==m_pObj->m_pIDataAdviseHolder)
        return ResultFromScode(E_FAIL);

    return m_pObj->m_pIDataAdviseHolder->Unadvise(dwConn);
    }

STDMETHODIMP CImpIDataObject::EnumDAdvise(LPENUMSTATDATA *ppEnum)
    {
    if (NULL==m_pObj->m_pIDataAdviseHolder)
        return ResultFromScode(E_FAIL);

    return m_pObj->m_pIDataAdviseHolder->EnumAdvise(ppEnum);
    }


IIPAOBJ.CPP

/*
 * IIPAOBJ.CPP
 * Cosmo Chapter 23
 *
 * IOleInPlaceActiveObject interface implementation for Cosmo
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CImpIOleInPlaceActiveObject::CImpIOleInPlaceActiveObject
 * CImpIOleInPlaceActiveObject::~CImpIOleInPlaceActiveObject
 *
 * Parameters (Constructor):
 *  pObj            PCFigure of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleInPlaceActiveObject::CImpIOleInPlaceActiveObject
    (PCFigure pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleInPlaceActiveObject::~CImpIOleInPlaceActiveObject(void)
    {
    return;
    }

/*
 * CImpIOleInPlaceActiveObject::QueryInterface
 * CImpIOleInPlaceActiveObject::AddRef
 * CImpIOleInPlaceActiveObject::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleInPlaceActiveObject object.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    /*
     * This interface should be stand-alone on an object such that
     * a container cannot QueryInterface for it through any other
     * object interface, relying instead of calls to SetActiveObject
     * for it. By implementing QueryInterface here ourselves, we
     * prevent such abuses.  Note that reference counting still
     * uses CFigure.
     */

    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IOleWindow==riid
        || IID_IOleInPlaceActiveObject==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceActiveObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceActiveObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleInPlaceActiveObject::GetWindow
 *
 * Purpose:
 *  Retrieves the handle of the window associated with the object
 *  on which this interface is implemented.
 *
 * Parameters:
 *  phWnd           HWND * in which to store the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_FAIL if there is no
 *                  window.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::GetWindow(HWND * phWnd)
    {
    *phWnd=m_pObj->m_pHW->Window();
    return NOERROR;
    }

/*
 * CImpIOleInPlaceActiveObject::ContextSensitiveHelp
 *
 * Purpose:
 *  Instructs the object on which this interface is implemented to
 *  enter or leave a context-sensitive help mode.
 *
 * Parameters:
 *  fEnterMode      BOOL TRUE to enter the mode, FALSE otherwise.
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::ContextSensitiveHelp
    (BOOL fEnterMode)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleInPlaceActiveObject::TranslateAccelerator
 *
 * Purpose:
 *  Requests that the active in-place object translate the message
 *  given in pMSG if appropriate.
 *
 * Parameters:
 *  pMSG            LPMSG to the message to translate.
 *
 * Return Value:
 *  HRESULT         NOERROR if translates, S_FALSE if not.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::TranslateAccelerator
    (LPMSG pMSG)
    {
    //This is only used for DLL objects.
    return ResultFromScode(S_FALSE);
    }

/*
 * CImpIOleInPlaceActiveObject::OnFrameWindowActivate
 *
 * Purpose:
 *  Informs the in-place object that the container's frame window
 *  was either activated or deactivated.  When the frame is
 *  activated, the object can change its toolbar interface to
 *  reflect a non-activated state as desired.  Usually you don't
 *  have to do anything.
 *
 * Parameters:
 *  fActivate       BOOL TRUE if the frame is active, FALSE
 *                  otherwise
 * Return Value:
 *  HRESULT         NOERROR or an error code
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::OnFrameWindowActivate
    (BOOL fActivate)
    {
    //Nothing for us to do.
    return NOERROR;
    }

/*
 * CImpIOleInPlaceActiveObject::OnDocWindowActivate
 *
 * Purpose:
 *  Informs the in-place object that the document window in the
 *  container is either becoming active or deactive.  On this call
 *  the object must either add or remove frame-level tools,
 *  including the mixed menu, depending on fActivate.
 *
 * Parameters:
 *  fActivate       BOOL TRUE if the document is active, FALSE
 *                  otherwise
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::OnDocWindowActivate
    (BOOL fActivate)
    {
    HWND        hWndTB;

    if (NULL==m_pObj->m_pIOleIPFrame)
        return NOERROR;

    hWndTB=m_pObj->m_pTB->Window();

    if (fActivate)
        {
       #ifdef WIN32ANSI
        OLECHAR     szTemp[40];

        MultiByteToWideChar(CP_ACP, 0
            , (*m_pObj->m_pST)[IDS_INPLACETITLE], -1, szTemp, 40);
        m_pObj->m_pIOleIPFrame->SetActiveObject(this, szTemp);
       #else
        m_pObj->m_pIOleIPFrame->SetActiveObject(this
            , (*m_pObj->m_pST)[IDS_INPLACETITLE]);
       #endif

        m_pObj->m_pIOleIPFrame->SetMenu(m_pObj->m_hMenuShared
            , m_pObj->m_hOLEMenu, m_pObj->m_pFR->Window());

        if (m_pObj->InPlaceToolsRenegotiate())
            {
            RECT    rc;

            m_pObj->m_pIOleIPFrame->GetBorder(&rc);
            SetWindowPos(hWndTB, NULL, rc.left, rc.top
                , rc.right-rc.left, rc.top+m_pObj->m_cyBar
                , SWP_NOZORDER);

            ShowWindow(hWndTB, SW_SHOW);
            }
        }
    else
        {
        m_pObj->m_pIOleIPFrame->SetActiveObject(NULL, NULL);

        //Hide our tools, but do not call SetMenu
        ShowWindow(hWndTB, SW_HIDE);
        }

    return NOERROR;
    }

/*
 * CImpIOleInPlaceActiveObject::ResizeBorder
 *
 * Purpose:
 *  Informs the object that the frame or document size changed in
 *  which case the object may need to resize any of its frame or
 *  document-level tools to match.
 *
 * Parameters:
 *  pRect           LPCRECT indicating the new size of the window
 *                  of interest.
 *  pIUIWindow      LPOLEINPLACEUIWINDOW pointing to an
 *                  IOleInPlaceUIWindow interface on the container
 *                  object of interest.  We use this to do border-
 *                  space negotiation.
 *  fFrame          BOOL indicating if the frame was resized (TRUE)
 *                  or the document (FALSE)
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::ResizeBorder(LPCRECT pRect
    , LPOLEINPLACEUIWINDOW pIUIWindow, BOOL fFrame)
    {
    //The document case is uninteresting for us.
    if (!fFrame)
        return NOERROR;

    if (!m_pObj->InPlaceToolsRenegotiate())
        return ResultFromScode(INPLACE_E_NOTOOLSPACE);

    SetWindowPos(m_pObj->m_pTB->Window(), NULL, pRect->left
        , pRect->top, pRect->right-pRect->left, m_pObj->m_cyBar
        , SWP_NOZORDER);

    return NOERROR;
    }

/*
 * CImpIOleInPlaceActiveObject::EnableModeless
 *
 * Purpose:
 *  Instructs the object to show or hide any modeless popup windows
 *  that it may be using when activated in-place.
 *
 * Parameters:
 *  fEnable         BOOL indicating to enable/show the windows
 *                  (TRUE) or to hide them (FALSE).
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::EnableModeless
    (BOOL fActivate)
    {
    return ResultFromScode(E_NOTIMPL);
    }


IIPOBJ.CPP

/*
 * IIPOBJ.CPP
 * Cosmo Chapter 23
 *
 * IOleInPlaceObject interface implementation for Cosmo
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CImpIOleInPlaceObject::CImpIOleInPlaceObject
 * CImpIOleInPlaceObject::~CImpIOleInPlaceObject
 *
 * Parameters (Constructor):
 *  pObj            PCFigure of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleInPlaceObject::CImpIOleInPlaceObject(PCFigure pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleInPlaceObject::~CImpIOleInPlaceObject(void)
    {
    return;
    }

/*
 * CImpIOleInPlaceObject::QueryInterface
 * CImpIOleInPlaceObject::AddRef
 * CImpIOleInPlaceObject::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleInPlaceObject object.
 */

STDMETHODIMP CImpIOleInPlaceObject::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleInPlaceObject::GetWindow
 *
 * Purpose:
 *  Retrieves the handle of the window associated with the object
 *  on which this interface is implemented.
 *
 * Parameters:
 *  phWnd           HWND * in which to store the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_FAIL if there is no
 *                  window.
 */

STDMETHODIMP CImpIOleInPlaceObject::GetWindow(HWND *phWnd)
    {
    *phWnd=m_pObj->m_pHW->Window();
    return NOERROR;
    }

/*
 * CImpIOleInPlaceObject::ContextSensitiveHelp
 *
 * Purpose:
 *  Instructs the object on which this interface is implemented to
 *  enter or leave a context-sensitive help mode.
 *
 * Parameters:
 *  fEnterMode      BOOL TRUE to enter the mode, FALSE otherwise.
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code
 */

STDMETHODIMP CImpIOleInPlaceObject::ContextSensitiveHelp
    (BOOL fEnterMode)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleInPlaceObject::InPlaceDeactivate
 *
 * Purpose:
 *  Instructs the object to deactivate itself from an in-place state
 *  and to discard any Undo state.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code
 */

STDMETHODIMP CImpIOleInPlaceObject::InPlaceDeactivate(void)
    {
    m_pObj->InPlaceDeactivate();
    return NOERROR;
    }

/*
 * CImpIOleInPlaceObject::UIDeactivate
 *
 * Purpose:
 *  Instructs the object to just remove any in-place user interface
 *  but to do no other deactivation.  The object should just hide
 *  the UI components but not destroy them until InPlaceDeactivate
 *  is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code
 */

STDMETHODIMP CImpIOleInPlaceObject::UIDeactivate(void)
    {
    m_pObj->UIDeactivate();
    return NOERROR;
    }

/*
 * CImpIOleInPlaceObject::SetObjectRects
 *
 * Purpose:
 *  Provides the object with rectangles describing the position of
 *  the object in the container window as well as its visible area.
 *
 * Parameters:
 *  prcPos          LPCRECT providing the object's full rectangle
 *                  relative to the container document.  The object
 *                  should scale to this rectangle.
 *  prcClip         LPCRECT describing the visible area of the object
 *                  which should not draw outside these areas.
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code
 */

STDMETHODIMP CImpIOleInPlaceObject::SetObjectRects(LPCRECT prcPos
    , LPCRECT prcClip)
    {
    /*
     * The idea here is to resize the polyline (editor) window to
     * the position rectangle as that defines the scaling.  We then
     * resize the hatch window, the parent of the editor, to the
     * intersection of the clip rectangle and the position rectangle.
     * Since the polyline is the child of the hatch window, the
     * polyline, while still scaled to the position rectangle, is
     * also clipped to the clip rectangle, providing only a partial
     * view of the correctly scaled presentation.
     *
     * The Hatch window takes care of this.
     */

    m_pObj->m_pHW->RectsSet((LPRECT)prcPos, (LPRECT)prcClip);
    return NOERROR;
    }

/*
 * CImpIOleInPlaceObject::ReactivateAndUndo
 *
 * Purpose:
 *  Instructs the object to reactivate itself in-place and perform
 *  whatever Undo means for it.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code
 */

STDMETHODIMP CImpIOleInPlaceObject::ReactivateAndUndo(void)
    {
    return m_pObj->InPlaceActivate(m_pObj->m_pIOleClientSite, TRUE);
    }


IOLEOBJ.CPP

/*
 * IOLEOBJ.CPP
 * Cosmo Chapter 23
 *
 * Implementation of the IOleObject interface for Polyline.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CImpIOleObject::CImpIOleObject
 * CImpIOleObject::~CImpIOleObject
 *
 * Parameters (Constructor):
 *  pObj            PCFigure of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleObject::CImpIOleObject(PCFigure pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleObject::~CImpIOleObject(void)
    {
    return;
    }

/*
 * CImpIOleObject::QueryInterface
 * CImpIOleObject::AddRef
 * CImpIOleObject::Release
 */

STDMETHODIMP CImpIOleObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleObject::SetClientSite
 *
 * Purpose:
 *  Provides the object with a pointer to the IOleClient site
 *  representing the container in which this object resides.
 *
 * Parameters:
 *  pIOleClientSite LPOLECLIENTSITE to the container's interface.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleObject::SetClientSite
    (LPOLECLIENTSITE pIOleClientSite)
    {
    if (NULL!=m_pObj->m_pIOleClientSite)
        m_pObj->m_pIOleClientSite->Release();

    m_pObj->m_pIOleClientSite=pIOleClientSite;
    m_pObj->m_pIOleClientSite->AddRef();
    return NOERROR;
    }

/*
 * CImpIOleObject::GetClientSite
 *
 * Purpose:
 *  Asks the object for the client site provided in SetClientSite.
 *  If you have not seen SetClientSite yet, return a NULL in
 *  ppIOleClientSite.
 *
 * Parameters:
 *  ppSite          LPOLECLIENTSITE * in which to store the
 *                  pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleObject::GetClientSite(LPOLECLIENTSITE
    *ppSite)
    {
    //Be sure to AddRef the new pointer you are giving away.
    *ppSite=m_pObj->m_pIOleClientSite;
    m_pObj->m_pIOleClientSite->AddRef();

    return NOERROR;
    }

/*
 * CImpIOleObject::SetHostNames
 *
 * Purpose:
 *  Provides the object with names of the container application and
 *  the object in the container to use in object user interface.
 *
 * Parameters:
 *  pszApp          LPCOLESTR of the container application.
 *  pszObj          LPCOLESTR of some name useful in window titles.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleObject::SetHostNames(LPCOLESTR pszApp
    , LPCOLESTR pszObj)
    {
    m_pObj->m_fEmbedded=TRUE;
   #ifdef WIN32ANSI
    char        szApp[80], szObj[80];

    szApp[0]=0;
    szObj[0]=0;

    if (NULL!=pszApp)
        {
        WideCharToMultiByte(CP_ACP, 0, pszApp, -1, szApp, 80
            , NULL, NULL);
        }

    if (NULL!=pszObj)
        {
        WideCharToMultiByte(CP_ACP, 0, pszObj, -1, szObj, 80
            , NULL, NULL);
        }

    m_pObj->m_pFR->UpdateEmbeddingUI(TRUE, m_pObj->m_pDoc
        , szApp, szObj);
   #else
    m_pObj->m_pFR->UpdateEmbeddingUI(TRUE, m_pObj->m_pDoc
        , pszApp, pszObj);
   #endif
    return NOERROR;
    }

/*
 * CImpIOleObject::Close
 *
 * Purpose:
 *  Forces the object to close down its user interface and unload.
 *
 * Parameters:
 *  dwSaveOption    DWORD describing the circumstances under which
 *                  the object is being saved and closed.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::Close(DWORD dwSaveOption)
    {
    HWND        hWnd;
    BOOL        fSave=FALSE;

    hWnd=m_pObj->m_pDoc->Window();

    //If object is dirty and we're asked to save, save it and close.
    if (OLECLOSE_SAVEIFDIRTY==dwSaveOption && m_pObj->FIsDirty())
        fSave=TRUE;

    /*
     * If asked to prompt, only do so if dirty, then if we get a
     * YES, save as usual and close.  On NO, just close.  On
     * CANCEL return OLE_E_PROMPTSAVECANCELLED.
     */
    if (OLECLOSE_PROMPTSAVE==dwSaveOption && m_pObj->FIsDirty())
        {
        UINT        uRet;

        uRet=MessageBox(hWnd, (*m_pObj->m_pST)[IDS_CLOSECAPTION]
            , (*m_pObj->m_pST)[IDS_CLOSEPROMPT], MB_YESNOCANCEL);

        if (IDCANCEL==uRet)
            return ResultFromScode(OLE_E_PROMPTSAVECANCELLED);

        if (IDYES==uRet)
            fSave=TRUE;
        }

    if (fSave)
        {
        m_pObj->SendAdvise(OBJECTCODE_SAVEOBJECT);
        m_pObj->SendAdvise(OBJECTCODE_SAVED);
        }

    //We get directly here on OLECLOSE_NOSAVE.
    PostMessage(hWnd, WM_CLOSE, 0, 0L);
    return NOERROR;
    }

/*
 * CImpIOleObject::SetMoniker
 *
 * Purpose:
 *  Informs the object of its moniker or its container's moniker
 *  depending on dwWhich.
 *
 * Parameters:
 *  dwWhich         DWORD describing whether the moniker is the
 *                  object's or the container's.
 *  pmk             LPMONIKER with the name.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::SetMoniker(DWORD dwWhich
    , LPMONIKER pmk)
    {
    LPMONIKER       pmkFull;
    HRESULT         hr=ResultFromScode(E_FAIL);

    /*
     * For an embedded object we might be living in a container that
     * has given us away as a link.  This is our indication to
     * register the full moniker for this object that we obtain from
     * IOleClientSite::GetMoniker(OLEWHICHMK_FULL).
     */

    if (NULL!=m_pObj->m_pIOleClientSite)
        {
        hr=m_pObj->m_pIOleClientSite->GetMoniker
            (OLEGETMONIKER_ONLYIFTHERE, OLEWHICHMK_OBJFULL
            , &pmkFull);
        }

    if (SUCCEEDED(hr))
        {
        /*
         * If this moniker is already running then we don't
         * need to revoke and re-register the same thing again.
         */
        if (NOERROR==pmkFull->IsRunning(NULL, NULL, NULL))
            {
            pmkFull->Release();
            return NOERROR;
            }

        //This will revoke the old one if m_dwRegROT is nonzero.
        INOLE_RegisterAsRunning(m_pObj, pmkFull
            , 0, &m_pObj->m_dwRegROT);

        //Inform clients of the new moniker
        if (NULL!=m_pObj->m_pIOleAdviseHolder)
            m_pObj->m_pIOleAdviseHolder->SendOnRename(pmkFull);

        pmkFull->Release();
        }

    return hr;
    }

/*
 * CImpIOleObject::GetMoniker
 *
 * Purpose:
 *  Asks the object for a moniker that can later be used to
 *  reconnect to it.
 *
 * Parameters:
 *  dwAssign        DWORD determining how to assign the moniker to
 *                  to the object.
 *  dwWhich         DWORD describing which moniker the caller wants.
 *  ppmk            LPMONIKER * into which to store the moniker.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetMoniker(DWORD dwAssign
    , DWORD dwWhich, LPMONIKER * ppmk)
    {
    HRESULT         hr=ResultFromScode(E_FAIL);

    *ppmk=NULL;

    /*
     * When we support linking we either return our file moniker if
     * we're linked, or out full moniker from the container if we're
     * embedded.
     */

    if (NULL!=m_pObj->m_pMoniker)
        {
        *ppmk=m_pObj->m_pMoniker;       //Document file moniker
        m_pObj->m_pMoniker->AddRef();
        }
    else
        {
        //Get the full container:object moniker if we're embedded
        if (NULL!=m_pObj->m_pIOleClientSite)
            {
            hr=m_pObj->m_pIOleClientSite->GetMoniker
                (OLEGETMONIKER_ONLYIFTHERE, OLEWHICHMK_OBJFULL
                , ppmk);
            }
        }

    return (NULL!=*ppmk) ? NOERROR : hr;
    }

/*
 * CImpIOleObject::InitFromData
 *
 * Purpose:
 *  Initializes the object from the contents of a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT containing the data.
 *  fCreation       BOOL indicating if this is part of a new
 *                  creation. If FALSE, the container is trying
 *                  to paste here.
 *  dwReserved      DWORD reserved.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::InitFromData(LPDATAOBJECT pIDataObject
    , BOOL fCreation, DWORD dwReserved)
    {
    BOOL    fRet;

    /*
     * If we get a data object here, try to paste from it.  If
     * you've written clipboard code already, this is a snap.
     * We don't really care about fCreation or not since pasting
     * in us blasts away whatever is already here.
     */
    fRet=m_pObj->m_pDoc->PasteFromData(pIDataObject);
    return fRet ? NOERROR : ResultFromScode(E_FAIL);
    }

/*
 * CImpIOleObject::GetClipboardData
 *
 * Purpose:
 *  Returns an IDataObject pointer to the caller representing what
 *  would be on the clipboard if the server did an Edit/Copy using
 *  OleSetClipboard.
 *
 * Parameters:
 *  dwReserved      DWORD reserved.
 *  ppIDataObj      LPDATAOBJECT * into which to store the
 *                  pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetClipboardData(DWORD dwReserved
    , LPDATAOBJECT *ppIDataObj)
    {
    /*
     * Again, if you have a function to create a data object for the
     * clipboard, this is a simple implementation.  The one we have
     * does all the compound document formats already.
     */
    *ppIDataObj=m_pObj->m_pDoc->TransferObjectCreate(FALSE);
    return (NULL!=*ppIDataObj) ? NOERROR : ResultFromScode(E_FAIL);
    }

/*
 * CImpIOleObject::DoVerb
 *
 * Purpose:
 *  Executes an object-defined action.
 *
 * Parameters:
 *  iVerb           LONG index of the verb to execute.
 *  pMSG            LPMSG describing the event causing the
 *                  activation.
 *  pActiveSite     LPOLECLIENTSITE to the site involved.
 *  lIndex          LONG the piece on which execution is happening.
 *  hWndParent      HWND of window in which the object can play
 *                  in-place.
 *  pRectPos        LPRECT of the object in hWndParent where the
 *                  object can play in-place if desired.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::DoVerb(LONG iVerb, LPMSG pMSG
    , LPOLECLIENTSITE pActiveSite, LONG lIndex, HWND hWndParent
    , LPCRECT pRectPos)
    {
    HWND            hWnd, hWndT;

    //CHAPTER23MOD
    /*
     * Find the upper most window.  Note that for in-place activation
     * we must start at the document to find our uppermost frame because
     * the object editing window may be a child of the container now.
     */
    //End CHAPTER23MOD
    hWndT=GetParent(m_pObj->m_pDoc->Window());

    while (NULL!=hWndT)
        {
        hWnd=hWndT;
        hWndT=GetParent(hWndT);
        }

    switch (iVerb)
        {
        //CHAPTER23MOD
        case OLEIVERB_HIDE:
            if (NULL!=m_pObj->m_pIOleIPSite)
                m_pObj->InPlaceDeactivate();
            else
                {
                ShowWindow(hWnd, SW_HIDE);
                m_pObj->SendAdvise(OBJECTCODE_HIDEWINDOW);
                }
            break;

        case OLEIVERB_PRIMARY:
        case OLEIVERB_SHOW:
            //If already IP active, nothing much to do here.
            if (NULL!=m_pObj->m_pIOleIPSite)
                return NOERROR;

            if (m_pObj->m_fAllowInPlace)
                {
                if (SUCCEEDED(m_pObj->InPlaceActivate(pActiveSite
                    , TRUE)))
                    {
                    return NOERROR;
                    }
                }

            //FALL-THROUGH

        case OLEIVERB_OPEN:
            /*
             * If we're already in-place active, deactivate and
             * prevent later reactivation.
             */
            if (NULL!=m_pObj->m_pIOleIPSite)
                {
                m_pObj->InPlaceDeactivate();
                m_pObj->m_fAllowInPlace=FALSE;
                }

            /*
             * With all the in-place stuff gone, we can go back to
             * our normal open state.
             */

            ShowWindow(hWnd, SW_SHOW);
            SetForegroundWindow(hWnd);
            SetFocus(hWnd);

            m_pObj->SendAdvise(OBJECTCODE_SHOWOBJECT);
            m_pObj->SendAdvise(OBJECTCODE_SHOWWINDOW);
            break;

        case OLEIVERB_INPLACEACTIVATE:
            //This means activate in-place but no UI.
            return m_pObj->InPlaceActivate(pActiveSite, FALSE);

        case OLEIVERB_UIACTIVATE:
            //This means fully activate in-place with full UI
            return m_pObj->InPlaceActivate(pActiveSite, TRUE);

        case OLEIVERB_DISCARDUNDOSTATE:
            //We don't hold a state, but if you do, free it here.
            break;
        //End CHAPTER23MOD

        default:
            return ResultFromScode(OLEOBJ_S_INVALIDVERB);
        }

    return NOERROR;
    }

/*
 * CImpIOleObject::EnumVerbs
 *
 * Purpose:
 *  Creates an enumerator that knows the object's verbs.  If you
 *  need to change the verb list dynamically, then you'll need to
 *  implement this, otherwise you can return OLE_S_USEREG.
 *
 * Parameters:
 *  ppEnum          LPENUMOLEVERB * into which to return the
 *                  enum.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::EnumVerbs(LPENUMOLEVERB *ppEnum)
    {
    //Trivial implementation if you fill the regDB.
    return ResultFromScode(OLE_S_USEREG);
    }

/*
 * CImpIOleObject::Update
 *
 * Purpose:
 *  Insures that the object is up to date.  This is mostly used for
 *  caching but you must make sure that you recursively call all
 *  nested objects you contain as well.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::Update(void)
    {
    //We're always updated since we don't contain.
    return NOERROR;
    }

/*
 * CImpIOleObject::IsUpToDate
 *
 * Purpose:
 *  Returns if the object is currently up to date, which involves
 *  asking all contained object inside this object if they are up
 *  to date as well.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE if dirty.
 */

STDMETHODIMP CImpIOleObject::IsUpToDate(void)
    {
    //We're always updated since we don't contain.
    return NOERROR;
    }

/*
 * CImpIOleObject::GetUserClassID
 *
 * Purpose:
 *  Used for linked objects, this returns the class ID of what end
 *  users think they are editing.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store the CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetUserClassID(LPCLSID pClsID)
    {
    /*
     * If you are not registered to handle data other than yourself,
     * then you can just return your class ID here.  If you are
     * registered as usable from Treat-As dialogs, then you need
     * to return the CLSID of what you are really editing.
     */

    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

/*
 * CImpIOleObject::GetUserType
 *
 * Purpose:
 *  Determines the user-presentable name of the object.
 *
 * Parameters:
 *  dwForm          DWORD describing which form of the string is
 *                  desired.
 *  pszType         LPOLESTR * into which to return the pointer to
 *                  the type string.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetUserType(DWORD dwForm
    , LPOLESTR *ppszType)
    {
    return ResultFromScode(OLE_S_USEREG);
    }

/*
 * CImpIOleObject::SetExtent
 *
 * Purpose:
 *  Sets the size of the object in HIMETRIC units.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect affected.
 *  pszl            LPSIZEL containing the new size.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::SetExtent(DWORD dwAspect, LPSIZEL pszl)
    {
    RECT            rc;
    SIZEL           szl;

    if (!(DVASPECT_CONTENT & dwAspect))
        return ResultFromScode(E_FAIL);

    XformSizeInHimetricToPixels(NULL, pszl, &szl);

    //This resizes the window to match the container's size.
    SetRect(&rc, 0, 0, (int)szl.cx, (int)szl.cy);
    m_pObj->m_pPL->SizeSet(&rc, TRUE);

    return NOERROR;
    }

/*
 * CImpIOleObject::GetExtent
 *
 * Purpose:
 *  Retrieves the size of the object in HIMETRIC units.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect requested
 *  pszl            LPSIZEL into which to store the size.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetExtent(DWORD dwAspect, LPSIZEL pszl)
    {
    RECT            rc;
    SIZEL           szl;

    if (!(DVASPECT_CONTENT & dwAspect))
        return ResultFromScode(E_FAIL);

    m_pObj->m_pPL->RectGet(&rc);
    szl.cx=rc.right-rc.left;
    szl.cy=rc.bottom-rc.top;

    XformSizeInPixelsToHimetric(NULL, &szl, pszl);
    return NOERROR;
    }

/*
 * CImpIOleObject::Advise
 *
 * Purpose:
 *  Provides an IAdviseSink to the object for notifications.
 *
 * Parameters:
 *  pIAdviseSink    LPADVISESINK to notify.
 *  pdwConn         LPDWORD into which to store a connection key.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::Advise(LPADVISESINK pIAdviseSink
    , LPDWORD pdwConn)
    {
    if (NULL==m_pObj->m_pIOleAdviseHolder)
        {
        HRESULT     hr;

        hr=CreateOleAdviseHolder(&m_pObj->m_pIOleAdviseHolder);

        if (FAILED(hr))
            return hr;
        }

    return m_pObj->m_pIOleAdviseHolder->Advise(pIAdviseSink
        , pdwConn);
    }

/*
 * CImpIOleObject::Unadvise
 *
 * Purpose:
 *  Terminates a previous advise connection from Advise.
 *
 * Parameters:
 *  dwConn          DWORD connection key from Advise.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::Unadvise(DWORD dwConn)
    {
    if (NULL!=m_pObj->m_pIOleAdviseHolder)
        return m_pObj->m_pIOleAdviseHolder->Unadvise(dwConn);

    return ResultFromScode(E_FAIL);
    }

/*
 * CImpIOleObject::EnumAdvise
 *
 * Purpose:
 *  Creates and returns a enumeration of the advises on this object.
 *
 * Parameters:
 *  ppEnum          LPENUMSTATDATA * in which to return the
 *                  enumerator.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::EnumAdvise(LPENUMSTATDATA *ppEnum)
    {
    if (NULL!=m_pObj->m_pIOleAdviseHolder)
        return m_pObj->m_pIOleAdviseHolder->EnumAdvise(ppEnum);

    return ResultFromScode(E_FAIL);
    }

/*
 * CImpIOleObject::GetMiscStatus
 *
 * Purpose:
 *  Returns a set of miscellaneous status flags for the object.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect in question.
 *  pdwStatus       LPDWORD in which to store the flags.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetMiscStatus(DWORD dwAspect
    , LPDWORD pdwStatus)
    {
    return ResultFromScode(OLE_S_USEREG);
    }

/*
 * CImpIOleObject::SetColorScheme
 *
 * Purpose:
 *  Provides the object with the color palette as recommended by
 *  the container application that also knows the palettes of other
 *  objects.  The object here is not required to use these colors.
 *
 * Parameters:
 *  pLP             LPLOGPALETTE providing the colors.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::SetColorScheme(LPLOGPALETTE pLP)
    {
    return ResultFromScode(E_NOTIMPL);
    }


IPERFILE.CPP

/*
 * IPERFILE.CPP
 * Cosmo Chapter 23
 *
 * Implementation of the IPersistFile interface for Cosmo
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CImpIPersistFile:CImpIPersistFile
 * CImpIPersistFile::~CImpIPersistFile
 *
 * Constructor Parameters:
 *  pObj            PCFigure associated with this object.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistFile::CImpIPersistFile(PCFigure pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPersistFile::~CImpIPersistFile(void)
    {
    return;
    }

/*
 * CImpIPersistFile::QueryInterface
 * CImpIPersistFile::AddRef
 * CImpIPersistFile::Release
 */

STDMETHODIMP CImpIPersistFile::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistFile::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistFile::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistFile::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the file represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::GetClassID(LPCLSID pClsID)
    {
    *pClsID=CLSID_CosmoFigure;
    return NOERROR;
    }

/*
 * CImpIPersistFile::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this file since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 */

STDMETHODIMP CImpIPersistFile::IsDirty(void)
    {
    return ResultFromScode(m_pObj->FIsDirty() ? S_OK : S_FALSE);
    }

/*
 * IPersistFile::Load
 *
 * Purpose:
 *  Asks the server to load the document for the given filename.
 *
 * Parameters:
 *  pszFile         LPCOLESTR to the filename to load.
 *  grfMode         DWORD containing open flags requested from the
 *                  caller.  Currently these are safely ignored.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::Load(LPCOLESTR pszFile, DWORD grfMode)
    {
    UINT        uRet;

   #ifdef WIN32ANSI
    TCHAR       szTemp[CCHPATHMAX];
    WideCharToMultiByte(CP_ACP, 0, pszFile, -1, szTemp, CCHPATHMAX
        , NULL, NULL);
    uRet=m_pObj->m_pDoc->Load(TRUE, (LPTSTR)szTemp);
   #else
    uRet=m_pObj->m_pDoc->Load(TRUE, (LPTSTR)pszFile);
   #endif
    return (DOCERR_NONE==uRet) ? NOERROR
        : ResultFromScode(STG_E_READFAULT);
    }

/*
 * IPersistFile::Save
 *
 * Purpose:
 * Purpose:
 *  Instructs the server to write the current file into a new
 *  filename, possibly then using that filename as the current one.
 *
 * Parameters:
 *  pszFile         LPCOLESTR of the file into which we save.  If NULL,
 *                  this means save the current file.
 *  fRemember       BOOL indicating if we're to use this filename as
 *                  the current file now (Save As instead of Save
 *                  Copy As).
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::Save(LPCOLESTR pszFile, BOOL fRemember)
    {
    UINT        uRet;
    BOOL        fTemp;

    /*
     * We set CFigure::m_fEmbedding here to TRUE if we don't want to
     * remember this file, as that cons Save into ignoring this
     * filename.  This is not something you have to do in your own
     * code the same way, but be sure to pay attention to fRemember.
     */
    fTemp=m_pObj->m_fEmbedded;
    m_pObj->m_fEmbedded=!fRemember;
   #ifdef WIN32ANSI
    char        szTemp[CCHPATHMAX];

    WideCharToMultiByte(CP_ACP, 0, pszFile, -1, szTemp, CCHPATHMAX
       , NULL, NULL);
    uRet=m_pObj->m_pDoc->Save(0, szTemp);
   #else
    uRet=m_pObj->m_pDoc->Save(0, (LPTSTR)pszFile);
   #endif

    m_pObj->m_fEmbedded=fTemp;

    return (DOCERR_NONE==uRet) ? NOERROR
        : ResultFromScode(STG_E_WRITEFAULT);
    }

/*
 * IPersistFile::SaveCompleted
 *
 * Purpose:
 *  Informs us that the operation that called Save is now finished
 *  and we can access the file again.
 *
 * Parameters:
 *  pszFile         LPCOLESTR of the file in which we can start
 *                  writing again.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::SaveCompleted(LPCOLESTR pszFile)
    {
    return NOERROR;
    }

/*
 * IPersistFile::GetCurFile
 *
 * Purpose:
 *  Retrieves the name of the current file.
 *
 * Parameters:
 *  ppszFile        LPOLESTR * into which we store a pointer to
 *                  the filename that should be allocated with the
 *                  shared IMalloc.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::GetCurFile(LPOLESTR *ppszFile)
    {
    LPMALLOC    pIMalloc;
    LPOLESTR    psz;
    UINT        uRet;

    *ppszFile=NULL;

    if (FAILED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        return ResultFromScode(E_FAIL);

    psz=(LPOLESTR)pIMalloc->Alloc(CCHPATHMAX*sizeof(OLECHAR));
    pIMalloc->Release();

   #ifdef WIN32ANSI
    char    szTemp[CCHPATHMAX];

    uRet=m_pObj->m_pDoc->FilenameGet(szTemp, CCHPATHMAX);
    MultiByteToWideChar(CP_ACP, 0, szTemp, -1, psz, CCHPATHMAX);
   #else
    uRet=m_pObj->m_pDoc->FilenameGet(psz, CCHPATHMAX);
   #endif

    //If we have no filename, return the prompt for File Open/Save.
    if (0==uRet)
       #ifdef WIN32ANSI
        MultiByteToWideChar(CP_ACP, 0
            , (*m_pObj->m_pST)[IDS_EXTENSION], -1, psz, CCHPATHMAX);
       #else
        lstrcpy(psz, (*m_pObj->m_pST)[IDS_EXTENSION]);
       #endif

    *ppszFile=psz;
    return (0==uRet) ? ResultFromScode(S_FALSE) : NOERROR;
    }


IPERSTOR.CPP

/*
 * IPERSTOR.CPP
 * Cosmo Chapter 23
 *
 * Implementation of the IPersistStorage interface that we expose on
 * the CFigure compound document object.  This ties into the
 * functionality of CPolyline.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CImpIPersistStorage:CImpIPersistStorage
 * CImpIPersistStorage::~CImpIPersistStorage
 *
 * Constructor Parameters:
 *  pObj            PCFigure associated with this object.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistStorage::CImpIPersistStorage(PCFigure pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    m_psState=PSSTATE_UNINIT;

    m_fConvert=FALSE;
    return;
    }

CImpIPersistStorage::~CImpIPersistStorage(void)
    {
    return;
    }

/*
 * CImpIPersistStorage::QueryInterface
 * CImpIPersistStorage::AddRef
 * CImpIPersistStorage::Release
 */

STDMETHODIMP CImpIPersistStorage::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistStorage::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistStorage::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistStorage::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the object represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::GetClassID(LPCLSID pClsID)
    {
    if (PSSTATE_UNINIT==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this object since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 */

STDMETHODIMP CImpIPersistStorage::IsDirty(void)
    {
    if (PSSTATE_UNINIT==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    //CFigure::FIsDirty returns the document's dirty flag.
    return ResultFromScode(m_pObj->FIsDirty() ? S_OK : S_FALSE);
    }

/*
 * CImpIPersistStorage::InitNew
 *
 * Purpose:
 *  Provides the object with the IStorage to hold on to while the
 *  object is running.  Here we initialize the structure of the
 *  storage and AddRef it for incremental access. This function will
 *  only be called once in the object's lifetime in lieu of Load.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE for the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::InitNew(LPSTORAGE pIStorage)
    {
    HRESULT     hr;

    if (PSSTATE_UNINIT!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    if (NULL==pIStorage)
        return ResultFromScode(E_POINTER);

    /*
     * The rules of IPersistStorage mean we hold onto the IStorage
     * and pre-create anything we'd need in Save(...,TRUE) for
     * low-memory situations.  For us this means creating our
     * "CONTENTS" stream and holding onto that IStream as
     * well as the IStorage here (requiring an AddRef call).
     */

    hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
        | STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE
        , 0, 0, &m_pObj->m_pIStream);

    if (FAILED(hr))
        return hr;

    //We expect that the client has called WriteClassStg
    WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
        , (*m_pObj->m_pST)[IDS_USERTYPE]);

    pIStorage->AddRef();
    m_pObj->m_pIStorage=pIStorage;

    m_psState=PSSTATE_SCRIBBLE;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::Load
 *
 * Purpose:
 *  Instructs the object to load itself from a previously saved
 *  IStorage that was handled by Save in another object lifetime.
 *  This function will only be called once in the object's lifetime
 *  in lieu of InitNew. The object should hold on to pIStorage here
 *  for incremental access and low-memory saves in Save.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE from which to load.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::Load(LPSTORAGE pIStorage)
    {
    HRESULT     hr;
    LONG        lRet;
    LPSTREAM    pIStream;

    if (PSSTATE_UNINIT!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    if (NULL==pIStorage)
        return ResultFromScode(E_POINTER);

    //This tells us if we're coming from another class storage.
    m_fConvert=(NOERROR==GetConvertStg(pIStorage));

    //This is the type of storage we're really messing with in Treat As
    ReadClassStg(pIStorage, &m_pObj->m_clsID);

    hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
        | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    //We might be looking for OLE 1 streams as well.
    if (FAILED(hr))
        {
        hr=pIStorage->OpenStream(SZOLE1STREAM, 0, STGM_DIRECT
            | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

        if (FAILED(hr))
            return ResultFromScode(STG_E_READFAULT);

        m_pObj->m_pPL->m_fReadFromOLE10=TRUE;
        }

    if (FAILED(hr))
        return ResultFromScode(STG_E_READFAULT);

    lRet=m_pObj->m_pPL->ReadFromStream(pIStream);

    if (lRet < 0)
        return ResultFromScode(STG_E_READFAULT);

    /*
     * We don't call pIStream->Release here because we may need
     * it for a low-memory save in Save.  We also need to
     * hold onto a copy of pIStorage, meaning AddRef.
     */
    m_pObj->m_pIStream=pIStream;

    pIStorage->AddRef();
    m_pObj->m_pIStorage=pIStorage;

    m_psState=PSSTATE_SCRIBBLE;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::Save
 *
 * Purpose:
 * Purpose:
 *  Saves the data for this object to an IStorage which may
 *  or may not be the same as the one previously passed to
 *  Load, indicated with fSameAsLoad.  After this call we may
 *  not write into the storage again until SaveCompleted is
 *  called, although we may still read.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which to save our data.
 *  fSameAsLoad     BOOL indicating if this is the same pIStorage
 *                  that was passed to Load.  If TRUE, then the
 *                  object should write whatever it has *without
 *                  *using any extra memory* as this may be a low
 *                  memory save attempt.  That means that you must
 *                  not try to open or create streams.  If FALSE
 *                  you need to regenerate your whole storage
 *                  structure, being sure to also release any
 *                  pointers held from InitNew and Load.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::Save(LPSTORAGE pIStorage
    , BOOL fSameAsLoad)
    {
    LONG        lRet;
    HRESULT     hr;
    LPSTREAM    pIStream;
    LONG        lVer=VERSIONCURRENT;

    //Have to come here from scribble state.
    if (PSSTATE_SCRIBBLE!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    //Must have an IStorage if we're not in SameAsLoad
    if (NULL==pIStorage && !fSameAsLoad)
        return ResultFromScode(E_POINTER);

    /*
     * If this was read from an OLE 1.0 storage, but there is no
     * convert bit, then we have to save the 1.0 format to the
     * "\1Ole10Native" stream.  Otherwise if we were converting
     * from OLE 1.0, we should nuke the stream since it's no longer
     * useful.  To handle this, we call WriteToStorage with
     * VERSIONCURRENT in any convert case.  WriteToStorage will
     * remove the OLE 1.0 stream if it previously read from one, or
     * it will just save normally.
     *
     * The Polyine allows us to look at it's m_fReadFromOLE10 which
     * tells us to pass it 0x00010000 if we're not converting, that
     * is, we're doing Treat As on the OLE 1.0 object and so we
     * need to write the new data in the Ole10Native stream.
     */

    if (!m_fConvert && m_pObj->m_pPL->m_fReadFromOLE10)
        lVer=0x00010000;

    /*
     * If we're saving to a new storage, create a new stream.
     * If fSameAsLoad it TRUE, then we write to the
     * stream we already allocated.  We should NOT depends on
     * pIStorage with fSameAsLoad is TRUE.
     *
     * If we're converting an OLE 1 storage to an OLE 2 storage,
     * then we have to create a new stream (conversion is not
     * guaranteed to succeed in low memory) and delete the old
     * one, so we ignore fSameAsLoad if we're converting.
     */

    if (fSameAsLoad
        && !(m_pObj->m_pPL->m_fReadFromOLE10 && m_fConvert))
        {
        LARGE_INTEGER   li;

        /*
         * Use pre-allocated streams to avoid failures due
         * to low-memory conditions.  Be sure to reset the
         * stream pointer if you used this stream before!!
         */
        pIStream=m_pObj->m_pIStream;
        LISet32(li, 0);
        pIStream->Seek(li, STREAM_SEEK_SET, NULL);

        //This matches the Release below.
        pIStream->AddRef();
        }
    else
        {
        hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
            | STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE
            , 0, 0, &pIStream);

        if (FAILED(hr))
            return hr;

        WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
            , (*m_pObj->m_pST)[IDS_USERTYPE]);
        }

    lRet=m_pObj->m_pPL->WriteToStream(pIStream, lVer);
    pIStream->Release();

    /*
     * If we are overwriting an OLE 1 storage, delete the old
     * Ole10Native stream if writing our CONTENTS worked.
     */
    if (m_pObj->m_pPL->m_fReadFromOLE10 && m_fConvert && (lRet >= 0))
        pIStorage->DestroyElement(SZOLE1STREAM);

    //Clear the convert bit if it was set
    if (m_fConvert)
        {
        UINT        cf;

        cf=RegisterClipboardFormat((*m_pObj->m_pST)[IDS_FORMAT]);
        WriteFmtUserTypeStg(pIStorage, cf
            , (*m_pObj->m_pST)[IDS_USERTYPE]);

        SetConvertStg(pIStorage, FALSE);
        m_fConvert=FALSE;
        }

    if (lRet >= 0)
        {
        m_psState=PSSTATE_ZOMBIE;
        return NOERROR;
        }

    return ResultFromScode(STG_E_WRITEFAULT);
    }

/*
 * CImpIPersistStorage::SaveCompleted
 *
 * Purpose:
 *  Notifies the object that the storage in pIStorage has been
 *  completely saved now.  This is called when the user of this
 *  object wants to save us in a completely new storage, and if
 *  we normally hang on to the storage we have to reinitialize
 *  ourselves here for this new one that is now complete.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE of the new storage in which we live.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::SaveCompleted(LPSTORAGE pIStorage)
    {
    HRESULT     hr;
    LPSTREAM    pIStream;

    //Must be called in no-scribble or hands-off state
    if (!(PSSTATE_ZOMBIE==m_psState || PSSTATE_HANDSOFF==m_psState))
        return ResultFromScode(E_UNEXPECTED);

    //If we're coming from Hands-Off, we'd better get a storage
    if (NULL==pIStorage && PSSTATE_HANDSOFF==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    /*
     * If pIStorage is NULL, then we don't need to do anything
     * since we already have all the pointers we need for Save.
     * Otherwise we have to release any held pointers and
     * reinitialize them from pIStorage.
     */

    if (NULL!=pIStorage)
        {
        hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
            | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0
            , &pIStream);

        if (FAILED(hr))
            return hr;

        if (NULL!=m_pObj->m_pIStream)
            m_pObj->m_pIStream->Release();

        m_pObj->m_pIStream=pIStream;

        if (NULL!=m_pObj->m_pIStorage)
            m_pObj->m_pIStorage->Release();

        m_pObj->m_pIStorage=pIStorage;
        m_pObj->m_pIStorage->AddRef();
        }

    m_pObj->SendAdvise(OBJECTCODE_SAVED);
    m_psState=PSSTATE_SCRIBBLE;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::HandsOffStorage
 *
 * Purpose:
 * Purpose:
 *  Instructs the object that another agent is interested in having
 *  total access to the storage we might be hanging on to from
 *  InitNew or SaveCompleted.  In this case we must release our hold
 *  and await another call to SaveCompleted before we have a hold
 *  again.  Therefore we cannot read or write after this call until
 *  SaveCompleted.
 *
 *  Situations where this might happen arise in compound document
 *  scenarios where this object might be in-place active but the
 *  application wants to rename and commit the root storage.
 *  Therefore we are asked to close our hold, let the container
 *  party on the storage, then call us again later to tell us the
 *  new storage we can hold.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::HandsOffStorage(void)
    {
    /*
     * Must come from scribble or no-scribble.  A repeated call
     * to HandsOffStorage is an unexpected error (bug in client).
     */
    if (PSSTATE_UNINIT==m_psState || PSSTATE_HANDSOFF==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    //Release held pointers
    if (NULL!=m_pObj->m_pIStream)
        {
        m_pObj->m_pIStream->Release();
        m_pObj->m_pIStream=NULL;
        }

    if (NULL!=m_pObj->m_pIStorage)
        {
        m_pObj->m_pIStorage->Release();
        m_pObj->m_pIStorage=NULL;
        }

    m_psState=PSSTATE_HANDSOFF;
    return NOERROR;
    }


POLYLINE.CPP

/*
 * POLYLINE.CPP
 * Cosmo Chapter 23
 *
 * Implementation of the CPolyline class.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * CPolyline:CPolyline
 * CPolyline::~CPolyline
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application we're in.
 */

CPolyline::CPolyline(HINSTANCE hInst)
    : CWindow(hInst)
    {
    m_pAdv=NULL;
    m_hWnd=NULL;
    m_fReadFromOLE10=FALSE;
    return;
    }

CPolyline::~CPolyline(void)
    {
    return;
    }

/*
 * CPolyline::Init
 *
 * Purpose:
 *  Instantiates a polyline window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags
 *  uID             UINT ID to associate with this window
 *  pAdv            PCPolylineAdviseSink of the sink wanting our
 *                  notifications.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPolyline::Init(HWND hWndParent, LPRECT pRect, DWORD dwStyle
    , UINT uID, PCPolylineAdviseSink pAdv)
    {
    m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY, SZCLASSPOLYLINE
        , SZCLASSPOLYLINE, dwStyle, pRect->left, pRect->top
        , pRect->right-pRect->left, pRect->bottom-pRect->top
        , hWndParent, (HMENU)uID, m_hInst, this);

    if (NULL!=m_hWnd)
        m_pAdv=pAdv;

    return (NULL!=m_hWnd);
    }

/*
 * CPolyline::New
 *
 * Purpose:
 *  Cleans out and reinitializes the data to defaults.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolyline::New(void)
    {
    UINT        i;
    RECT        rc;

    m_pl.wVerMaj=VERSIONMAJOR;
    m_pl.wVerMin=VERSIONMINOR;

    //Our rectangle is the size of our window's client area.
    GetClientRect(m_hWnd, &rc);
    RECTTORECTS(rc, m_pl.rc);

    //Clean out the POLYLINE structure and repaint the window.
    for (i=0; i< CPOLYLINEPOINTS; i++)
        {
        m_pl.rgpt[i].x=0;
        m_pl.rgpt[i].y=0;
        }

    m_pl.cPoints      =0;
    m_pl.rgbBackground=GetSysColor(COLOR_WINDOW);
    m_pl.rgbLine      =GetSysColor(COLOR_WINDOWTEXT);
    m_pl.iLineStyle   =PS_SOLID;

    InvalidateRect(m_hWnd, NULL, TRUE);
    UpdateWindow(m_hWnd);

    //Inform the advise sink of this data change.
    if (NULL!=m_pAdv)
        m_pAdv->OnDataChange();

    return;
    }

/*
 * CPolyline::Undo
 *
 * Purpose:
 *  Reverses previous actions in a Polyline.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if we can Undo more, FALSE otherwise.
 */

BOOL CPolyline::Undo(void)
    {
    //Decrement the number of active points and repaint.
    if (m_pl.cPoints > 0)
        {
        m_pl.cPoints--;
        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    if (NULL!=m_pAdv)
        m_pAdv->OnPointChange();

    //Return if we can undo any more.
    return (0!=m_pl.cPoints);
    }

/*
 * CPolyline::ReadFromStorage
 *
 * Purpose:
 *  Loads our data (any known version) from an IStorage returning
 *  the version number of the data or an error value.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE from which we'll read.
 *
 * Return Value:
 *  LONG            Version number or negative POLYLINE_E_* value.
 */

LONG CPolyline::ReadFromStorage(LPSTORAGE pIStorage)
    {
    LONG            lRet;
    LPSTREAM        pIStream;
    HRESULT         hr;

    if (NULL==pIStorage)
        return POLYLINE_E_READFAILURE;

    //Open the CONTENTS stream
    hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT | STGM_READ
        | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return POLYLINE_E_READFAILURE;

    /*
     * Since we had to create ReadFromStream for IPersistStorage,
     * use it ourselves.
     */
    lRet=ReadFromStream(pIStream);
    pIStream->Release();

    return lRet;
    }

/*
 * CPolyline::WriteToStorage
 *
 * Purpose:
 *  Ignorantly writes our current data into a storage in a
 *  particular version.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which to save.
 *  lVer            LONG providing version number Major (HI) and
 *                  Minor (Low)
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::WriteToStorage(LPSTORAGE pIStorage, LONG lVer)
    {
    LONG            lRet;
    LPSTREAM        pIStream;
    HRESULT         hr;

    if (NULL==pIStorage)
        return POLYLINE_E_READFAILURE;

    hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT | STGM_CREATE
        | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, 0, &pIStream);

    if (FAILED(hr))
        return POLYLINE_E_WRITEFAILURE;

    /*
     * Use our function that will save to a stream now that
     * we needed it for IPersistStorage.
     */

    lRet=WriteToStream(pIStream, lVer);
    pIStream->Release();

    return lRet;
    }

/*
 * CPolyline::ReadFromStream
 *
 * Purpose:
 *  Loads our data (any known version) from an IStream that we
 *  do not open nor maintain.  This is for the compound document
 *  object's implementation of IPersistStorage.
 *
 * Parameters:
 *  pIStream        LPSTREAM from which we'll read.
 *
 * Return Value:
 *  LONG            Version number or negative POLYLINE_E_* value.
 */

LONG CPolyline::ReadFromStream(LPSTREAM pIStream)
    {
    POLYLINEDATA    pl;
    ULONG           cb=(ULONG)-1;
    ULONG           cbExpect=0;
    HRESULT         hr;
    LARGE_INTEGER   li;

    if (NULL==pIStream)
        return POLYLINE_E_READFAILURE;

    /*
     * If IPersistStorage::Load failed to find CONTENTS but did
     * open "\1Ole10Native" then m_fReadFromOLE10 is set.  So
     * we have to start reading past the leading DWORD.
     */

    if (m_fReadFromOLE10)
        {
        DWORD       dw;

        /*
         * Skip the DWORD length at the beginning of the
         * Ole10Native stream.
         */
        pIStream->Read(&dw, sizeof(DWORD), &cb);
        }

    //Read version numbers and seek back to file beginning.
    hr=pIStream->Read(&pl, 2*sizeof(WORD), &cb);

    //If we read OLE 1, seek back but skip the size header.
    LISet32(li, m_fReadFromOLE10 ? sizeof(DWORD) : 0);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);

    if (FAILED(hr) || 2*sizeof(WORD)!=cb)
        {
        pIStream->Release();
        return POLYLINE_E_READFAILURE;
        }

    /*
     * For version 2.0, read the entire file.  For version 1.0 read
     * the file up to CBPOLYLINEDATAVER10.  For anything else, give
     * an error.
     */

    switch (pl.wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (pl.wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (pl.wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    hr=pIStream->Read(&pl, cbExpect, &cb);

    if (cbExpect!=cb)
        return POLYLINE_E_READFAILURE;

    /*
     * If we loaded successfully, make the data current.  By using
     * DataSet we centralize our version upgrading.  We size the
     * polyline window to the data AND notify the document so it
     * sizes to the polyline.
     */
    DataSet(&pl, TRUE, TRUE);

    //Return what version we just loaded.
    return MAKELONG(pl.wVerMin, pl.wVerMaj);
    }

/*
 * CPolyline::WriteToStream
 *
 * Purpose:
 *  Ignorantly writes our current data into a stream in a
 *  particular version.
 *
 * Parameters:
 *  pIStream        LPSTREAM in which to save.
 *  lVer            LONG providing version number Major (HI) and
 *                  Minor (Low)
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::WriteToStream(LPSTREAM pIStream, LONG lVer)
    {
    ULONG           cb;
    ULONG           cbExpect=0;
    WORD            wVerMaj=HIWORD(lVer);
    WORD            wVerMin=LOWORD(lVer);
    POLYLINEDATA    pl;
    HRESULT         hr;

    if (NULL==pIStream)
        return POLYLINE_E_READFAILURE;

    //Get a copy of our data in the version we're going to save.
    DataGet(&pl, lVer);

    switch (wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    /*
     * If writing to an OLE 1 storage, include the length of
     * data as the first DWORD in the stream.
     */

     if (m_fReadFromOLE10 && CBPOLYLINEDATA10==cbExpect)
        {
        hr=pIStream->Write(&cbExpect, sizeof(DWORD), &cb);

        if (FAILED(hr) || sizeof(DWORD)!=cb)
            return POLYLINE_E_WRITEFAILURE;
        }

    hr=pIStream->Write(&pl, cbExpect, &cb);

    if (FAILED(hr) || cbExpect!=cb)
        return POLYLINE_E_WRITEFAILURE;

    return POLYLINE_E_NONE;
    }

/*
 * CPolyline::ReadFromFile
 *
 * Purpose:
 *  Loads our data (any known version) from a file handle returning
 *  the version number of the data or an error value.
 *
 * Parameters:
 *  pszFile         LPTSTR of the file to open.
 *
 * Return Value:
 *  LONG            Version number or negative POLYLINE_E_* value.
 */

LONG CPolyline::ReadFromFile(LPTSTR pszFile)
    {
    OFSTRUCT        of;
    HFILE           hFile;
    POLYLINEDATA    pl;
    UINT            cb=(UINT)-1;
    UINT            cbExpect=0;

    if (NULL==pszFile)
        return POLYLINE_E_READFAILURE;

    //OpenFileW is a member of CPolyline.
    hFile=OpenFileW(pszFile, &of, OF_READ);

    if (HFILE_ERROR==hFile)
        return POLYLINE_E_READFAILURE;

    //Read version numbers and seek back to file beginning.
    cb=_lread(hFile, &pl, 2*sizeof(WORD));
    _llseek(hFile, 0L, 0);

    if (2*sizeof(WORD)!=cb)
        {
        _lclose(hFile);
        return POLYLINE_E_READFAILURE;
        }

    /*
     * For version 2.0, read the entire file.  For version 1.0 read
     * the file up to CBPOLYLINEDATAVER10.  For anything else, give
     * an error.
     */

    switch (pl.wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (pl.wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (pl.wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        {
        _lclose(hFile);
        return POLYLINE_E_UNSUPPORTEDVERSION;
        }

    cb=_lread(hFile, &pl, cbExpect);
    _lclose(hFile);

    if (cbExpect!=cb)
        return POLYLINE_E_READFAILURE;

    /*
     * If we loaded successfully, make the data current.  By using
     * DataSet we centralize our version upgrading.  We size the
     * polyline window to the data AND notify the document so it
     * sizes to the polyline.
     */
    DataSet(&pl, TRUE, TRUE);

    //Return what version we just loaded.
    return MAKELONG(pl.wVerMin, pl.wVerMaj);
    }

/*
 * CPolyline::WriteToFile
 *
 * Purpose:
 *  Ignorantly writes our current data into an opened file in a
 *  particular version.
 *
 * Parameters:
 *  pszFile         LPTSTR filename in which to store the data.
 *  lVer            LONG providing version number Major (HI)
 *                  and Minor (Low)
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::WriteToFile(LPTSTR pszFile, LONG lVer)
    {
    OFSTRUCT        of;
    HFILE           hFile;
    UINT            cb;
    UINT            cbExpect=0;
    WORD            wVerMaj=HIWORD(lVer);
    WORD            wVerMin=LOWORD(lVer);
    POLYLINEDATA    pl;

    if (NULL==pszFile)
        return POLYLINE_E_READFAILURE;

    //Get a copy of our data in the version we're going to save.
    DataGet(&pl, lVer);

    switch (wVerMaj)
        {
        case VERSIONMAJOR:  //2.x
            switch (wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    cbExpect=CBPOLYLINEDATA;
                    break;

                default:
                    break;
                }
            break;

        case 1: //1.x
            switch (wVerMin)
                {
                case 0:  //1.0
                    cbExpect=CBPOLYLINEDATA10;
                    break;

                default:
                    break;
                }
            break;

        default:
            break;
        }

    if (0==cbExpect)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    hFile=OpenFileW(pszFile, &of, OF_CREATE | OF_WRITE);

    if (HFILE_ERROR==hFile)
        return DOCERR_COULDNOTOPEN;

    cb=_lwrite(hFile, (LPCSTR)&pl, cbExpect);
    _lclose(hFile);

    return (cbExpect==cb) ? POLYLINE_E_NONE
        : POLYLINE_E_WRITEFAILURE;
    }

/*
 * CPolyline::OpenFileW (Private)
 *
 * Purpose:
 *  Under Win32, OpenFile does not take Unicode strings.  This
 *  function converts a Unicode string into an ANSI string and
 *  calls OpenFile.  This just maps to OpenFile without Unicode.
 *
 * Parameters, Return Value:
 *  Same as OpenFile.
 */

HFILE CPolyline::OpenFileW(LPTSTR pszFile, LPOFSTRUCT pof
    , UINT uFlags)
    {
   #ifdef UNICODE
    CHAR        szTemp[CCHPATHMAX];

    UNICODETOANSI(pszFile, szTemp, CCHPATHMAX);
    return OpenFile(szTemp, pof, uFlags);
   #else
    return OpenFile(pszFile, pof, uFlags);
   #endif
    }

/*
 * CPolyline::DataSet
 *
 * Purpose:
 *  Sets the current data in this Polyline to a given structure.
 *
 * Parameters:
 *  ppl             PPOLYLINEDATA to initialize to.
 *  fSizeToData     BOOL indicating if we're to size to the data or
 *                  scale it.
 *  fNotify         BOOL indicating if we're to send an advise on
 *                  this change.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataSet(PPOLYLINEDATA ppl, BOOL fSizeToData
    , BOOL fNotify)
    {
    RECTS       rcs;
    RECT        rc;
    UINT        i;

    /*
     * Copy the structure in ppl and repaint to reflect the new
     * point set.  Note that unlike the RectSet message, we do no
     * scaling, assuming that the rect in the structure is
     * appropriate for the data.
     */

    if (NULL==ppl)
        return POLYLINE_E_INVALIDPOINTER;

    //Preserve the old rectangle
    rcs=m_pl.rc;

    /*
     * For version 2.0 we perform a straight copy.  For version
     * 1.0 we copy the 1.0 structure and fill in defaults for
     * the 2.0 additions.
     */

    switch (ppl->wVerMaj)
        {
        case VERSIONMAJOR:          //2.x
            switch (ppl->wVerMin)
                {
                case VERSIONMINOR:  //2.0
                    m_pl=*ppl;
                    break;

                default:
                    return POLYLINE_E_UNSUPPORTEDVERSION;
                }
            break;

        case 1:                     //1.x
            switch (ppl->wVerMin)
                {
                case 0:             //1.0
                    *((PPOLYLINEDATA10)&m_pl)=
                        *((PPOLYLINEDATA10)ppl);

                    /*
                     * Update this structure to 2.0.  Note that we
                     * assume whoever loaded us to save the loaded
                     * version so it can later ask what version
                     * the user wants to save.
                     */
                    m_pl.wVerMaj=VERSIONMAJOR;
                    m_pl.wVerMin=VERSIONMINOR;

                    /*
                     * Version 1.0 stored rc in parent coordinates.
                     * We need those now in our client coodinates.
                     */
                    RECTSTORECT(m_pl.rc, rc);
                    OffsetRect(&rc, -m_pl.rc.left, -m_pl.rc.top);

                    /*
                     * 1.0 data had points relative to size of the
                     * rectangle.  We need to scale these to 0-32767
                     * independent of the rectangle for the version
                     * upgrade.
                     */
                    for (i=0; i < m_pl.cPoints; i++)
                        PointScale(&rc, &m_pl.rgpt[i], FALSE);

                    RECTTORECTS(rc, m_pl.rc);

                    //New 2.0 features
                    m_pl.rgbBackground=GetSysColor(COLOR_WINDOW);
                    m_pl.rgbLine=GetSysColor(COLOR_WINDOWTEXT);
                    m_pl.iLineStyle=PS_SOLID;
                    break;

                default:
                    return POLYLINE_E_UNSUPPORTEDVERSION;
                }
            break;

        default:
            return POLYLINE_E_UNSUPPORTEDVERSION;
        }

    //Inform our parent of the data change
    if (NULL!=m_pAdv)
        m_pAdv->OnDataChange();

    /*
     * If we're scaling the window to fit the data, then use
     * RectSet passing our current rectangle as the new one.
     * That makes sure that the data won't change but that the
     * window is resized.
     */

    if (fSizeToData)
        {
        POINT       pt;

        /*
         * Get our offset in the parent window so we can RectSet
         * to the right place since RectSet expects rectangle in
         * parent coordinates and we get it in client coordinates.
         */
        GetWindowRect(m_hWnd, &rc);
        pt.x=rc.left;
        pt.y=rc.top;
        ScreenToClient(GetParent(m_hWnd), &pt);
        RECTSTORECT(m_pl.rc, rc);
        OffsetRect(&rc, pt.x, pt.y);

        //This will also cause a repaint.
        RectSet(&rc, fNotify);
        }
    else
        {
        //Make sure we're updated.
        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    return POLYLINE_E_NONE;
    }

/*
 * CPolyline::DataGet
 *
 * Purpose:
 *  Retrieves the Polyline's current data.
 *
 * Parameters:
 *  ppl             PPOLYLINEDATA into which we copy the data.
 *  lVer            LONG version of the data to retrieve.  Use
 *                  VERSIONCURRENT to retrieve the most current.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value
 */

LONG CPolyline::DataGet(PPOLYLINEDATA ppl, LONG lVer)
    {
    UINT        i;
    RECT        rc;

    //Retieve the current version
    if (lVer==MAKELONG(VERSIONMINOR, VERSIONMAJOR)
        || VERSIONCURRENT==lVer)
        {
        *ppl=m_pl;
        return POLYLINE_E_NONE;
        }

    //Check for versions we support, 1.x
    if (HIWORD(lVer)!=1)
        return POLYLINE_E_UNSUPPORTEDVERSION;

    if (LOWORD(lVer)==0)    //Check for 1.0
        {
        //Do 2.0 to 1.0 conversion
        *((PPOLYLINEDATA10)ppl)=*((PPOLYLINEDATA10)&m_pl);

        RECTSTORECT(ppl->rc, rc);

        //Get the parent coordinates of our rectangle
        if (!IsWindow(m_hWnd))
            OffsetRect(&rc, 8, 8); //This is always the offset.
        else
            RectGet(&rc);

        /*
         * 1.0 data has points relative to size of the rectangle.
         * We need to scale these from 0-32767 so we have the
         * right values.
         */
        for (i=0; i < ppl->cPoints; i++)
            PointScale(&rc, &ppl->rgpt[i], TRUE);

        RECTTORECTS(rc, ppl->rc);

        //Insure old version numbers.
        ppl->wVerMaj=1;
        ppl->wVerMin=0;

        ((PPOLYLINEDATA10)ppl)->fDrawEntire=TRUE;
        return POLYLINE_E_NONE;
        }

    return POLYLINE_E_UNSUPPORTEDVERSION;
    }

/*
 * CPolyline::DataSetMem
 *
 * Purpose:
 *  Sets the Polyline's data using a global memory handle
 *  instead of a pointer.
 *
 * Parameters:
 *  hMem            HGLOBAL containing the data.
 *  fFree           BOOL indicating if we're to free the data.
 *                  The memory will be freed regardless of any
 *                  error returned from here.
 *  fSizeToData     BOOL indicating if we're to size to the data
 *                  or scale it.
 *  fNotify         BOOL indicating if we're to send an advise
 *                  on this change.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataSetMem(HGLOBAL hMem, BOOL fFree
    , BOOL fSizeToData, BOOL fNotify)
    {
    PPOLYLINEDATA   ppl;
    LONG            lRet=POLYLINE_E_INVALIDPOINTER;

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        lRet=DataSet(ppl, fSizeToData, fNotify);

        GlobalUnlock(hMem);

        if (fFree)
            GlobalFree(hMem);
        }

    return lRet;
    }

/*
 * CPolyline::DataGetMem
 *
 * Purpose:
 *  Retrieves the Polyline's data in a global memory handle.
 *
 * Parameters:
 *  lVer            LONG version of data to retrieve.
 *  phMem           HGLOBAL * in which to store the handle.
 *
 * Return Value:
 *  LONG            A POLYLINE_E_* value.
 */

LONG CPolyline::DataGetMem(LONG lVer, HGLOBAL *phMem)
    {
    HGLOBAL         hMem;
    PPOLYLINEDATA   ppl;
    LONG            lRet;

    if (NULL==phMem)
        return POLYLINE_E_INVALIDPOINTER;

    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
        , CBPOLYLINEDATA);

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        lRet=DataGet(ppl, lVer);
        GlobalUnlock(hMem);

        if (POLYLINE_E_NONE!=lRet)
            {
            GlobalFree(hMem);
            hMem=NULL;
            }
        }

    *phMem=hMem;
    return lRet;
    }

/*
 * CPolyline::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectGet(LPRECT pRect)
    {
    RECT        rc;
    POINT       pt;

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_hWnd, &rc);
    pt.x=rc.left;
    pt.y=rc.top;
    ScreenToClient(GetParent(m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return;
    }

/*
 * CPolyline::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  None
 */

void CPolyline::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return;
    }

/*
 * CPolyline::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;
    RECT        rc;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, pRect->left, pRect->top
        , cx, cy, SWP_NOZORDER);

    SetRect(&rc, 0, 0, cx, cy);
    RECTTORECTS(rc, m_pl.rc);

    if (fNotify && NULL!=m_pAdv)
        m_pAdv->OnSizeChange();

    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }

/*
 * CPolyline::SizeSet
 *
 * Purpose:
 *  Sets a new size for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPolyline::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
        , SWP_NOMOVE | SWP_NOZORDER);

    if (fNotify && NULL!=m_pAdv)
        m_pAdv->OnSizeChange();

    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }

/*
 * CPolyline::ColorSet
 *
 * Purpose:
 *  Changes for background or line color in the Polyline
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color to use.
 *
 * Return Value:
 *  COLORREF        Previous color for the index iColor.
 */

COLORREF CPolyline::ColorSet(UINT iColor, COLORREF cr)
    {
    COLORREF    crRet;

    switch (iColor)
        {
        case POLYLINECOLOR_BACKGROUND:
            crRet=m_pl.rgbBackground;
            m_pl.rgbBackground=cr;
            break;

        case POLYLINECOLOR_LINE:
            crRet=m_pl.rgbLine;
            m_pl.rgbLine=cr;
            break;
        }

    //If the color changed, repaint
    if (crRet!=cr)
        {
        if (NULL!=m_pAdv)
            m_pAdv->OnColorChange();

        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    return crRet;
    }

/*
 * CPolyline::ColorGet
 *
 * Purpose:
 *  Retrieves one of the colors currently in use by the Polyline.
 *
 * Parameters:
 *  iColor          UINT identifying the color of interest.
 *
 * Return Value:
 *  COLORREF        Current color for iColor.
 */

COLORREF CPolyline::ColorGet(UINT iColor)
    {
    COLORREF    crRet;

    crRet=(POLYLINECOLOR_BACKGROUND==iColor)
        ? m_pl.rgbBackground : m_pl.rgbLine;

    return crRet;
    }

/*
 * CPolyline::LineStyleSet
 *
 * Purpose:
 *  Changes the line style in use by the Polyline
 *
 * Parameters:
 *  iStyle          UINT style of the line to use.
 *
 * Return Value:
 *  UINT            Previous style.
 */

UINT CPolyline::LineStyleSet(UINT iStyle)
    {
    UINT        uRet=(UINT)m_pl.iLineStyle;

    //Validate the line style

if (PS_SOLID==iStyle || PS_DASH==iStyle || PS_DOT==iStyle
        || PS_DASHDOT==iStyle || PS_DASHDOTDOT==iStyle)
        {
        m_pl.iLineStyle=iStyle;

        if (uRet!=(UINT)m_pl.iLineStyle)
            {
            if (NULL!=m_pAdv)
                m_pAdv->OnLineStyleChange();

            InvalidateRect(m_hWnd, NULL, TRUE);
            UpdateWindow(m_hWnd);
            }
        }

    return uRet;
    }

/*
 * CPolyline::LineStyleGet
 *
 * Purpose:
 *  Retrieves the current line style in use in the Polyline
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Current line style.
 */

UINT CPolyline::LineStyleGet(void)
    {
    return m_pl.iLineStyle;
    }

/*
 * CPolyline::RenderBitmap
 *
 * Purpose:
 *  Creates a bitmap image of the current Polyline.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HBITMAP         Handle to the newly rendered bitmap.
 */

HBITMAP CPolyline::RenderBitmap(void)
    {
    HDC         hDC;
    HDC         hMemDC;
    HBITMAP     hBmp;
    RECT        rc;
    HGDIOBJ     hObj;

    //Render a bitmap the size of the current rectangle.
    hDC=GetDC(m_hWnd);
    hMemDC=CreateCompatibleDC(hDC);

    GetClientRect(m_hWnd, &rc);
    hBmp=CreateCompatibleBitmap(hDC, rc.right, rc.bottom);

    if (NULL!=hBmp)
        {
        //Draw the POLYLINEDATA into the bitmap.
        hObj=SelectObject(hMemDC, hBmp);
        Draw(hMemDC, FALSE, TRUE);
        SelectObject(hMemDC, hObj);
        }

    DeleteDC(hMemDC);
    ReleaseDC(m_hWnd, hDC);

    return hBmp;
    }

/*
 * CPolyline::RenderMetafile
 *
 * Purpose:
 *  Renders the current image of the Polyline into a metafile.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HMETAFILE       Handle to the newly created metafile.
 */

HMETAFILE CPolyline::RenderMetafile(void)
    {
    HDC         hDC;
    HMETAFILE   hMF;
    RECT        rc;

    //Create a memory metafile and return its handle.
    hDC=(HDC)CreateMetaFile(NULL);
    hMF=NULL;

    if (NULL!=hDC)
        {
        /*
         * This is absolutely essential to the metafile so it
         * can be scaled in the clipboard and any destination
         * application.
         */
        SetMapMode(hDC, MM_ANISOTROPIC);
        GetClientRect(m_hWnd, &rc);
        SetWindowOrgEx(hDC, 0, 0, NULL);
        SetWindowExtEx(hDC, rc.right, rc.bottom, NULL);

        Draw(hDC, TRUE, TRUE);
        hMF=CloseMetaFile(hDC);
        }

    return hMF;
    }

/*
 * CPolyline::RenderMetafilePict
 *
 * Purpose:
 *  Renders the current Polyline into a METAFILEPICT structure in
 *  global memory.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HGLOBAL         Memory containing the METAFILEPICT structure.
 */

HGLOBAL CPolyline::RenderMetafilePict(void)
    {
    HGLOBAL         hMem;
    HMETAFILE       hMF;
    LPMETAFILEPICT  pMF;
    RECT            rc;

    //Get the metafile
    hMF=RenderMetafile();

    if (NULL==hMF)
        return NULL;

    //Allocate the METAFILEPICT structure.
    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
        , sizeof(METAFILEPICT));

    if (NULL==hMem)
        {
        DeleteMetaFile(hMF);
        return NULL;
        }

    /*
     * Global lock only fails in PMODE if the selector is invalid
     * (like it was discarded) or references a 0 length segment,
     * neither of which can happen here.
     */
    pMF=(LPMETAFILEPICT)GlobalLock(hMem);

    pMF->hMF=hMF;
    pMF->mm=MM_ANISOTROPIC;

    //Insert the extents in MM_HIMETRIC units.
    GetClientRect(m_hWnd, &rc);
    RectConvertMappings(&rc, FALSE);
    pMF->xExt=rc.right;
    pMF->yExt=rc.bottom;

    GlobalUnlock(hMem);
    return hMem;
    }

/*
 * CPolyline::RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device (MM_TEXT) or
 *  HIMETRIC to the other.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  fToDevice       BOOL TRUE to convert from HIMETRIC to device,
 *                  FALSE to convert device to HIMETRIC.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectConvertMappings(LPRECT pRect, BOOL fToDevice)
    {
    HDC      hDC;
    int      iLpx, iLpy;

    if (NULL==pRect)
        return;

    hDC=GetDC(NULL);
    iLpx=GetDeviceCaps(hDC, LOGPIXELSX);
    iLpy=GetDeviceCaps(hDC, LOGPIXELSY);
    ReleaseDC(NULL, hDC);

    if (fToDevice)
        {
        pRect->left=MulDiv(iLpx, pRect->left, HIMETRIC_PER_INCH);
        pRect->top =MulDiv(iLpy, pRect->top , HIMETRIC_PER_INCH);

        pRect->right=MulDiv(iLpx, pRect->right,  HIMETRIC_PER_INCH);
        pRect->bottom=MulDiv(iLpy, pRect->bottom,HIMETRIC_PER_INCH);

       #ifdef NEVER
        /*
         * In this conversion we may get situations where the top
         * coordinate is larger than the bottom, which messes us up.
         */
        if (pRect->bottom < pRect->top)
            {
            iLpy=pRect->top;
            pRect->top=pRect->bottom;
            pRect->bottom=iLpy;
            }
       #endif
        }
    else
        {
        pRect->left=MulDiv(pRect->left, HIMETRIC_PER_INCH, iLpx);
        pRect->top =MulDiv(pRect->top , HIMETRIC_PER_INCH, iLpy);

        pRect->right =MulDiv(pRect->right, HIMETRIC_PER_INCH, iLpx);
        pRect->bottom=MulDiv(pRect->bottom,HIMETRIC_PER_INCH, iLpy);
        }

    return;
    }


POLYLINE.H

/*
 * POLYLINE.H
 * Cosmo Chapter 23
 *
 * Definitions and function prototypes for the PolyLine window
 * class that can be treated like its own control.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _POLYLINE_H_
#define _POLYLINE_H_

//Versioning.
#define VERSIONMAJOR                2
#define VERSIONMINOR                0
#define VERSIONCURRENT              0x00020000

//Classname
#define SZCLASSPOLYLINE             TEXT("polyline")

//Stream Name that holds the data
#define SZSTREAM                    OLETEXT("CONTENTS")
#define SZOLE1STREAM                OLETEXT("\1Ole10Native")

#define HIMETRIC_PER_INCH           2540
#define CPOLYLINEPOINTS             20

//Window extra bytes and offsets
#define CBPOLYLINEWNDEXTRA          (sizeof(LONG))
#define PLWL_STRUCTURE              0

//Version 2.0 Polyline Structure
typedef struct tagPOLYLINEDATA
    {
    WORD        wVerMaj;                //Major version number.
    WORD        wVerMin;                //Minor version number.
    WORD        cPoints;                //Number of points.
    short       fReserved;              //Obsolete from v1.0
    RECTS       rc;                     //Rectangle of this figure
    POINTS      rgpt[CPOLYLINEPOINTS];  //Points (0-32767 grid)

    //Version 2.0 additions
    COLORREF    rgbBackground;          //Background color
    COLORREF    rgbLine;                //Line color
    short       iLineStyle;             //Line style
    } POLYLINEDATA, *PPOLYLINEDATA;

#define CBPOLYLINEDATA   sizeof(POLYLINEDATA)
#define CBPOLYLINEDATA20 sizeof(POLYLINEDATA)

//Version 1.0 Polyline Structure
typedef struct tagPOLYLINEDATA10
    {
    WORD        wVerMaj;                //Major version number.
    WORD        wVerMin;                //Minor version number.
    WORD        cPoints;                //Number of points.
    short       fDrawEntire;            //Flag to draw entire figure
    RECTS       rc;                     //Rectangle of this figure
    POINTS      rgpt[CPOLYLINEPOINTS];  //Points (scaled to rc)
    } POLYLINEDATA10, *PPOLYLINEDATA10;

#define CBPOLYLINEDATA10 sizeof(POLYLINEDATA10)

//POLYWIN.CPP
LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM, LPARAM);

class CPolyline : public CWindow
    {
    friend LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM
        , LPARAM);

    private:
        POLYLINEDATA   m_pl;

        class CPolylineAdviseSink * m_pAdv;

    public:
        //This is for CFigure to look at when writing its storage.
        BOOL            m_fReadFromOLE10;

    private:
        HFILE     OpenFileW(LPTSTR, LPOFSTRUCT, UINT);
        void      PointScale(LPRECT, LPPOINTS, BOOL);
        void      Draw(HDC, BOOL, BOOL);
        void      RectConvertMappings(LPRECT, BOOL);

    public:
        CPolyline(HINSTANCE);
        ~CPolyline(void);

        BOOL      Init(HWND, LPRECT, DWORD, UINT
            , class CPolylineAdviseSink *);

        void      New(void);
        BOOL      Undo(void);

        //File functions
        LONG      ReadFromStorage(LPSTORAGE);
        LONG      WriteToStorage(LPSTORAGE, LONG);

        LONG      ReadFromStream(LPSTREAM);
        LONG      WriteToStream(LPSTREAM, LONG);

        LONG      ReadFromFile(LPTSTR);
        LONG      WriteToFile(LPTSTR, LONG);

        //Data transfer functions
        LONG      DataSet(PPOLYLINEDATA, BOOL, BOOL);
        LONG      DataGet(PPOLYLINEDATA, LONG);
        LONG      DataSetMem(HGLOBAL, BOOL, BOOL, BOOL);
        LONG      DataGetMem(LONG, HGLOBAL *);
        HBITMAP   RenderBitmap(void);
        HMETAFILE RenderMetafile(void);
        HGLOBAL   RenderMetafilePict(void);

        void      RectGet(LPRECT);
        void      SizeGet(LPRECT);
        void      RectSet(LPRECT, BOOL);
        void      SizeSet(LPRECT, BOOL);
        COLORREF  ColorSet(UINT, COLORREF);
        COLORREF  ColorGet(UINT);
        UINT      LineStyleSet(UINT);
        UINT      LineStyleGet(void);
    };

typedef CPolyline *PCPolyline;

//Error values for data transfer functions
#define POLYLINE_E_NONE                    0
#define POLYLINE_E_UNSUPPORTEDVERSION      -1
#define POLYLINE_E_INVALIDPOINTER          -2
#define POLYLINE_E_READFAILURE             -3
#define POLYLINE_E_WRITEFAILURE            -4

class CPolylineAdviseSink
    {
    private:
        LPVOID      m_pv;           //Customizable structure

    public:
        CPolylineAdviseSink(LPVOID);
        ~CPolylineAdviseSink(void);

        void OnPointChange(void);
        void OnSizeChange(void);
        void OnDataChange(void);
        void OnColorChange(void);
        void OnLineStyleChange(void);
    };

typedef CPolylineAdviseSink *PCPolylineAdviseSink;

//Color indices for color messages
#define POLYLINECOLOR_BACKGROUND    0
#define POLYLINECOLOR_LINE          1

#endif  //_POLYLINE_H_


POLYWIN.CPP

/*
 * POLYWIN.CPP
 * Cosmo Chapter 23
 *
 * Window procedure for the polyline drawing window and support
 * functions.  This window is not complicated.  On creation it
 * allocates a block of memory for a POLYLINEDATA structure that
 * contains 20 POINTs.  We do not attempt to reallocate this
 * array at all just to maintain simplicity.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "cosmo.h"

/*
 * PolylineWndProc
 *
 * Purpose:
 *  Window procedure for the polyline drawing window.
 */

LRESULT APIENTRY PolylineWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCPolyline      ppl;
    PAINTSTRUCT     ps;
    HDC             hDC;
    POINTS          pt;
    RECT            rc;

    ppl=(PCPolyline)GetWindowLong(hWnd, PLWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppl=(PCPolyline)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PLWL_STRUCTURE, (LONG)ppl);
            ppl->m_hWnd=hWnd;
            ppl->New();
            break;

        case WM_PAINT:
            hDC=BeginPaint(hWnd, &ps);
            ppl->Draw(hDC, FALSE, TRUE);
            EndPaint(hWnd, &ps);
            break;

        case WM_LBUTTONDOWN:
            //Stop if we are already at the limit.
            if (CPOLYLINEPOINTS==ppl->m_pl.cPoints)
                {
                MessageBeep(0);
                break;
                }

            //Convert the points into 0-32767 range
            GetClientRect(hWnd, &rc);
            pt=MAKEPOINTS(lParam);
            ppl->PointScale(&rc, &pt, FALSE);

            ppl->m_pl.rgpt[ppl->m_pl.cPoints++]=pt;

            //Draw the lines to this new point only.
            hDC=GetDC(hWnd);
            ppl->Draw(hDC, FALSE, FALSE);
            ReleaseDC(hWnd, hDC);

            if (NULL!=ppl->m_pAdv)
                ppl->m_pAdv->OnPointChange();

            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPolyline::Draw
 *
 * Purpose:
 *  Paints the current line in the polyline window.
 *
 * Parameters:
 *  hDC             HDC to draw on, could be metafile or printer DC.
 *  fMetafile       BOOL indicating if hDC is a metafile or not, so
 *                  we can avoid operations that RIP.
 *  fEntire         BOOL indicating if we should draw the entire
 *                  figure or not.
 *
 * Return Value:
 *  None
 */

void CPolyline::Draw(HDC hDC, BOOL fMetafile, BOOL fEntire)
    {
    HBRUSH          hBrush;
    HPEN            hPen;
    HGDIOBJ         hObj1, hObj2;
    UINT            i, j;
    UINT            uMM;
    POINTS          pt1, pt2;
    POINT           rgpt[CPOLYLINEPOINTS];
    RECT            rc;

    GetClientRect(m_hWnd, &rc);

    /*
     * Make a 32-bit copy of the points in the POLYLINE itself
     * so we can use DPtoLP and LPtoDP.
     */
    for (i=0; i < m_pl.cPoints; i++)
        {
        rgpt[i].x=m_pl.rgpt[i].x;
        rgpt[i].y=m_pl.rgpt[i].y;
        }

    /*
     * If the mapping mode is not MM_TEXT, convert the points to
     * whatever mapping mode in in effect before drawing.
     * This specifically supports metafiles in MM_ANISOTROPIC.
     */
    uMM=fMetafile ? MM_TEXT : GetMapMode(hDC);

    if (MM_TEXT!=uMM)
        DPtoLP(hDC, rgpt, m_pl.cPoints);

    hPen=CreatePen(m_pl.iLineStyle, 1, m_pl.rgbLine);
    hObj1=SelectObject(hDC, hPen);

    hBrush=CreateSolidBrush(m_pl.rgbBackground);
    hObj2=SelectObject(hDC, hBrush);
    SetBkColor(hDC, m_pl.rgbBackground);

    /*
     * Either draw the entire figure or just a single point.  The
     * entire figure also includes erasing the background
     * completely,since hDC may be a metafile DC.  Drawing a
     * single point just updates the figure for that new point.
     */
    if (fEntire || 0==m_pl.cPoints)
        {
        //Erase the background for bitmaps and metafiles.
        SelectObject(hDC, GetStockObject(NULL_PEN));
        Rectangle(hDC, rc.left, rc.top, rc.right+1, rc.bottom+1);
        SelectObject(hDC, hPen);

        /*
         * If we are drawing the entire figure, then loop through
         * each point drawing a line to each successive point.
         */

        for (i=0; i < m_pl.cPoints; i++)
            {
            for (j=i; j < m_pl.cPoints; j++)
                {
                /*
                 * Not using POINTTOPOINTS since it unfortunately
                 * returns a LONG which doesn't equate to a POINTS.
                 */
                pt1.x=(short)rgpt[i].x;
                pt1.y=(short)rgpt[i].y;
                pt2.x=(short)rgpt[j].x;
                pt2.y=(short)rgpt[j].y;

                PointScale(&rc, &pt1, TRUE);
                PointScale(&rc, &pt2, TRUE);
                MoveToEx(hDC, pt1.x, pt1.y, NULL);
                LineTo(hDC, pt2.x, pt2.y);
                }
            }
        }
    else
        {
        /*
         * If we are only drawing the last point, just cycle once
         * through previous points.
         */

        //Get the last point entered in the array.
        j=m_pl.cPoints-1;
        pt1.x=(short)rgpt[j].x;
        pt1.y=(short)rgpt[j].y;
        PointScale(&rc, &pt1, TRUE);

        for (i=0; i < j; i++)
            {
            pt2.x=(short)rgpt[i].x;
            pt2.y=(short)rgpt[i].y;
            PointScale(&rc, &pt2, TRUE);

            MoveToEx(hDC, pt1.x, pt1.y, NULL);
            LineTo(hDC, pt2.x, pt2.y);
            }
        }

    //If we have one point, draw a dot to indicate it's position.
    if (1==m_pl.cPoints)
        {
        pt1.x=(short)rgpt[0].x;
        pt1.y=(short)rgpt[0].y;
        PointScale(&rc, &pt1, TRUE);
        SetPixel(hDC, pt1.x, pt1.y, m_pl.rgbLine);
        }

    //Note:  m_pl.rgpt is unmodified in this code.

    SelectObject(hDC, hObj1);
    SelectObject(hDC, hObj2);
    DeleteObject(hBrush);
    DeleteObject(hPen);
    return;
    }

/*
 * CPolyline::PointScale
 *
 * Purpose:
 *  Scales a point to or from a relative window coordinate to a
 *  0-32767 coordinate.
 *
 * Parameters:
 *  pRect           LPRECT of the window.
 *  ppt             LPPOINTS to convert
 *  fScaleToWindow  BOOL indicating direction of scaling.
 *
 * Return Value:
 *  None
 */

void CPolyline::PointScale(LPRECT pRect, LPPOINTS ppt
    , BOOL fScaleToWindow)
    {
    DWORD   cx, cy;

    //Window size
    cx=(DWORD)(pRect->right-pRect->left);
    cy=(DWORD)(pRect->bottom-pRect->top);

    //Prevent crashes
    if (0L==cx) cx=1;
    if (0L==cy) cy=1;

    //Must use DWORD to insure proper scaling.
    if (fScaleToWindow)
        {
        ppt->x=(UINT)(((DWORD)ppt->x*cx) >> 15);
        ppt->y=(UINT)(((DWORD)ppt->y*cy) >> 15);
        }
    else
        {
        ppt->x=(UINT)(((DWORD)ppt->x << 15)/cx);
        ppt->y=(UINT)(((DWORD)ppt->y << 15)/cy);
        }

    return;
    }


RESOURCE.H

/*
 * RESOURCE.H
 * Cosmo Chapter 23
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Note that we augment classlib.h which we include before this.

//Position of Window menu.
#undef  WINDOW_MENU
#define WINDOW_MENU             4

//Total number of menus
#undef  CMENUS
#ifdef MDI
#define CMENUS                  6
#else
#define CMENUS                  5
#endif

//CHAPTER23MOD
#define MPOS_SEP                5
#define MPOS_OPEN               6
//End CHAPTER23MOD

//Menu command identifiers.

#define IDM_FILEIMPORT          (IDM_CUSTOMFILEMIN)

#define IDM_COLORBACKGROUND     (IDM_CUSTOMMIN)
#define IDM_COLORLINE           (IDM_CUSTOMMIN+1)

//CHAPTER23MOD
#define IDM_EDITOPEN            (IDM_CUSTOMEDITMIN)
//End CHAPTER23MOD

#define IDM_LINEMIN             (IDM_CUSTOMMIN+100)
#define IDM_LINESOLID           (IDM_LINEMIN+0)         //PS_SOLID       0
#define IDM_LINEDASH            (IDM_LINEMIN+1)         //PS_DASH        1
#define IDM_LINEDOT             (IDM_LINEMIN+2)         //PS_DOT         2
#define IDM_LINEDASHDOT         (IDM_LINEMIN+3)         //PS_DASHDOT     3
#define IDM_LINEDASHDOTDOT      (IDM_LINEMIN+4)         //PS_DASHDOTDOT  4
#define IDM_LINEMAX             IDM_LINEDASHDOTDOT

/*
 * IDs for StatStrip handling of popup menus:  must be in order of menu
 * CLASSRES.H already defines ID_MENUFILE and ID_MENUEDIT for us.
 */

#undef  ID_MENUWINDOW
#undef  ID_MENUHELP

#define ID_MENUCOLOR            (ID_MENUCUSTOMMIN+0)
#define ID_MENULINE             (ID_MENUCUSTOMMIN+1)
#define ID_MENUWINDOW           (ID_MENUCUSTOMMIN+2)
#define ID_MENUHELP             (ID_MENUCUSTOMMIN+3)

//Stringtable IDs.  Keep sequential for each group.
#define IDS_FRAMEMIN            IDS_STANDARDFRAMEMIN
#define IDS_FILEIMPORT          (IDS_CUSTOMFRAMEMIN+0)
#define IDS_FIGUREINCONTAINER   (IDS_CUSTOMFRAMEMIN+1)
#define IDS_EXITANDRETURN       (IDS_CUSTOMFRAMEMIN+2)
#define IDS_EMBEDDINGCAPTION    (IDS_CUSTOMFRAMEMIN+3)
#define IDS_SAVECOPYAS          (IDS_CUSTOMFRAMEMIN+4)
#define IDS_NEW                 (IDS_CUSTOMFRAMEMIN+5)
#define IDS_OPEN                (IDS_CUSTOMFRAMEMIN+6)
#define IDS_CLOSE2              (IDS_CUSTOMFRAMEMIN+7)
#define IDS_SAVE                (IDS_CUSTOMFRAMEMIN+8)
#define IDS_SAVEAS              (IDS_CUSTOMFRAMEMIN+9)
#define IDS_EXIT                (IDS_CUSTOMFRAMEMIN+10)
#define IDS_FRAMEMAX            (IDS_CUSTOMFRAMEMIN+11)

#define IDS_DOCUMENTMIN         IDS_STANDARDDOCMIN
#define IDS_VERSIONCHANGE       (IDS_CUSTOMDOCMIN+0)
#define IDS_OBJECTDESCRIPTION   (IDS_CUSTOMDOCMIN+1)
#define IDS_DOCUMENTMAX         (IDS_CUSTOMDOCMIN+1)

//Strings for the CFigure object implementation.
#define IDS_FIGUREMIN           256
#define IDS_CLOSECAPTION        (IDS_FIGUREMIN+1)
#define IDS_CLOSEPROMPT         (IDS_FIGUREMIN+2)
#define IDS_USERTYPE            (IDS_FIGUREMIN+3)
#define IDS_FORMAT              (IDS_FIGUREMIN+4)
#define IDS_EXTENSION           (IDS_FIGUREMIN+5)
//CHAPTER23MOD
#define IDS_INPLACETITLE        (IDS_FIGUREMIN+6)
#define IDS_MENUEDIT            (IDS_FIGUREMIN+7)
#define IDS_MENUOPEN            (IDS_FIGUREMIN+8)
#define IDS_MENUCOLOR           (IDS_FIGUREMIN+9)
#define IDS_MENULINE            (IDS_FIGUREMIN+10)
#define IDS_MENUHELP            (IDS_FIGUREMIN+11)
#define IDS_FIGUREMAX           (IDS_FIGUREMIN+11)
//End CHAPTER23MOD

//These are specifically for the StatStrip
#define IDS_STATMESSAGEMIN                  IDS_STANDARDSTATMESSAGEMIN

#define IDS_ITEMMESSAGEFILEIMPORT           (IDS_CUSTOMSTATMESSAGEMIN+0)

#define IDS_MENUMESSAGECOLOR                (IDS_CUSTOMSTATMESSAGEMIN+1)
#define IDS_ITEMMESSAGECOLORBACKGROUND      (IDS_CUSTOMSTATMESSAGEMIN+2)
#define IDS_ITEMMESSAGECOLORLINE            (IDS_CUSTOMSTATMESSAGEMIN+3)

#define IDS_MENUMESSAGELINE                 (IDS_CUSTOMSTATMESSAGEMIN+4)
#define IDS_ITEMMESSAGELINESOLID            (IDS_CUSTOMSTATMESSAGEMIN+5)
#define IDS_ITEMMESSAGELINEDASH             (IDS_CUSTOMSTATMESSAGEMIN+6)
#define IDS_ITEMMESSAGELINEDOT              (IDS_CUSTOMSTATMESSAGEMIN+7)
#define IDS_ITEMMESSAGELINEDASHDOT          (IDS_CUSTOMSTATMESSAGEMIN+8)
#define IDS_ITEMMESSAGELINEDASHDOTDOT       (IDS_CUSTOMSTATMESSAGEMIN+9)

//CHAPTER23MOD
#define IDS_ITEMMESSAGEEDITOPEN             (IDS_CUSTOMSTATMESSAGEMIN+10)
#define IDS_STATMESSAGEMAX                  (IDS_CUSTOMSTATMESSAGEMIN+10)
//End CHAPTER23MOD

#endif //_RESOURCE_H_


DLLPOLY.CPP

/*
 * DLLPOLY.CPP
 * Polyline Component Chapter 23
 *
 * Polyline component object used in CoCosmo that supports a custom
 * interface IPolyline.  Contains DLL entry code and the component
 * object exports DllGetClassObject and DllCanUnloadNow and the
 * class factory object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "polyline.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

//DLL Instance handle
HINSTANCE   g_hInst=0;

/*
 * LibMain32 (Win32)
 *
 * Purpose:
 *  Entry point for Win32 DLLs that calls the Win16 LibMain.
 */

#ifdef WIN32
BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    if (DLL_PROCESS_DETACH==ulReason)
        {
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    return (0!=LibMain(hInstance, 0,  0, NULL));
    }
#endif

/*
 * LibMain (also called from Win32 LibMain32)
 *
 * Purpose:
 *  DLL-specific entry point called from LibEntry.
 */

int PASCAL LibMain(HINSTANCE hInst, WORD wDataSeg
    , WORD cbHeapSize, LPSTR lpCmdLine)
    {
    WNDCLASS    wc;

    if (GetClassInfo(hInst, SZCLASSPOLYLINE, &wc))
        return (int)hInst;

   #ifndef WIN32
    if (0!=cbHeapSize)
        UnlockData(0);
   #endif

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.hInstance     = hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PolylineWndProc;
    wc.cbWndExtra    = CBPOLYLINEWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_CROSS);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPOLYLINE;

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

    //CHAPTER23MOD
    if (!HatchWindowRegister(hInst))
        return 0;
    //End CHAPTER23MOD

    g_hInst=hInst;
    return (int)hInst;
    }

/*
 * DllGetClassObject
 *
 * Purpose:
 *  Provides an IClassFactory for a given CLSID that this DLL is
 *  registered to support.  This DLL is placed under the CLSID
 *  in the registration database as the InProcServer.
 *
 * Parameters:
 *  clsID           REFCLSID that identifies the class factory
 *                  desired.  Since this parameter is passed this
 *                  DLL can handle any number of objects simply
 *                  by returning different class factories here
 *                  for different CLSIDs.
 *
 *  riid            REFIID specifying the interface the caller wants
 *                  on the class object, usually IID_ClassFactory.
 *
 *  ppv             PPVOID in which to return the interface
 *                  pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, otherwise an error code.
 */

HRESULT APIENTRY DllGetClassObject(REFCLSID rclsid
    , REFIID riid, PPVOID ppv)
    {
    if (CLSID_Polyline19!=rclsid)
        return ResultFromScode(E_FAIL);

    //Check that we can provide the interface
    if (IID_IUnknown!=riid && IID_IClassFactory!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Return our IClassFactory for Polyline objects
    *ppv=new CPolylineClassFactory;

    if (NULL==*ppv)
        return ResultFromScode(E_OUTOFMEMORY);

    //AddRef the object through any interface we return
    ((LPUNKNOWN)*ppv)->AddRef();
    g_cObj++;
    return NOERROR;
    }

/*
 * DllCanUnloadNow
 *
 * Purpose:
 *  Answers if the DLL can be freed, that is, if there are no
 *  references to anything this DLL provides.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if nothing is using us, FALSE otherwise.
 */

STDAPI DllCanUnloadNow(void)
    {
    SCODE   sc;

    //Our answer is whether there are any object or locks
    sc=(0L==g_cObj && 0L==g_cLock) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Polyline object to call when it gets destroyed.
 *  Since we're in a DLL we only track the number of objects here
 *  letting DllCanUnloadNow take care of the rest.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;
    return;
    }

/*
 * CPolylineClassFactory::CPolylineClassFactory
 *
 * Purpose:
 *  Constructor for an object supporting an IClassFactory that
 *  instantiates Polyline objects.
 *
 * Parameters:
 *  None
 */

CPolylineClassFactory::CPolylineClassFactory(void)
    {
    m_cRef=0L;
    return;
    }

/*
 * CPolylineClassFactory::~CPolylineClassFactory
 *
 * Purpose:
 *  Destructor for a CPolylineClassFactory object.  This will be
 *  called when we Release the object to a zero reference count.
 */

CPolylineClassFactory::~CPolylineClassFactory(void)
    {
    return;
    }

/*
 * CPolylineClassFactory::QueryInterface
 * CPolylineClassFactory::AddRef
 * CPolylineClassFactory::Release
 */

STDMETHODIMP CPolylineClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CPolylineClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPolylineClassFactory::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    ObjectDestroyed();
    return 0L;
    }

/*
 * CPolylineClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a Polyline object.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we
 *                  are being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_NOINTERFACE
 *                  if we cannot support the requested interface.
 */

STDMETHODIMP CPolylineClassFactory::CreateInstance
    (LPUNKNOWN pUnkOuter, REFIID riid, PPVOID ppvObj)
    {
    PCPolyline          pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //Verify that a controlling unknown asks for IUnknown
    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Create the object.  This also creates a window.
    pObj=new CPolyline(pUnkOuter, ObjectDestroyed, g_hInst);

    if (NULL==pObj)
        return hr;

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    //Kill the object if initial creation or Init failed.
    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

/*
 * CPolylineClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the DLL.  If the lock
 *  count goes to zero and there are no objects, the DLL is allowed
 *  to unload.  See DllCanUnloadNow.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CPolylineClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        g_cLock--;

    return NOERROR;
    }


ICONNPT.CPP

/*
 * ICONNPT.CPP
 * Polyline Component Chapter 23
 *
 * Implementation of CImpIConnectionPoint for the Polyline object
 * as well as CConnectionPoint.
 *
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIConnPtCont:CImpIConnPtCont
 * CImpIConnPtCont::~CImpIConnPtCont
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIConnPtCont::CImpIConnPtCont(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIConnPtCont::~CImpIConnPtCont(void)
    {
    return;
    }

/*
 * CImpIConnPtCont::QueryInterface
 * CImpIConnPtCont::AddRef
 * CImpIConnPtCont::Release
 */

STDMETHODIMP CImpIConnPtCont::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIConnPtCont::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIConnPtCont::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIConnPtCont::EnumConnectionPoints
 *
 * Purpose:
 *  Not implemented.
 *
 * Return Value:
 *  HRESULT         E_NOTIMPL
 */

STDMETHODIMP CImpIConnPtCont::EnumConnectionPoints
    (LPENUMCONNECTIONPOINTS *ppEnum)
    {
    *ppEnum=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIConnPtCont::FindConnectionPoint
 *
 * Purpose:
 *  Returns a pointer to the IConnectionPoint for a given
 *  outgoing IID.
 *
 * Parameters:
 *  riid            REFIID of the outgoing interface for which
 *                  a connection point is desired.
 *  ppCP            IConnectionPoint ** in which to return
 *                  the pointer after calling AddRef.
 *
 * Return Value:
 *  HRESULT         NOERROR if the connection point is found,
 *                  E_NOINTERFACE if it's not supported.
 */

STDMETHODIMP CImpIConnPtCont::FindConnectionPoint(REFIID riid
    , IConnectionPoint **ppCP)
    {
    *ppCP=NULL;

    if (IID_IPolylineAdviseSink10==riid)
        {
        return m_pObj->m_pConnPt->QueryInterface
            (IID_IConnectionPoint, (PPVOID)ppCP);
        }

    return ResultFromScode(E_NOINTERFACE);
    }

//CConnectionPoint implementation

/*
 * CConnectionPoint::CConnectionPoint
 * CConnectionPoint::~CConnectionPoint
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.  We can
 *                  query this for the IConnectionPointContainer
 *                  interface we might need.
 */

CConnectionPoint::CConnectionPoint(PCPolyline pObj)
    {
    m_cRef=0;

    /*
     * Our lifetime is controlled by the connectable object itself,
     * although other external clients will call AddRef and Release.
     * Since we're nested in the connectable object's lifetime,
     * there's no need to call AddRef on pObj.
     */
    m_pObj=pObj;
    return;
    }

CConnectionPoint::~CConnectionPoint(void)
    {
    ReleaseInterface(m_pObj->m_pAdv);
    return;
    }

/*
 * CConnectionPoint::QueryInterface
 * CConnectionPoint::AddRef
 * CConnectionPoint::Release
 *
 * Purpose:
 *  Non-delegating IUnknown members for CConnectionPoint.
 */

STDMETHODIMP CConnectionPoint::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IConnectionPoint==riid)
        *ppv=(LPVOID)this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CConnectionPoint::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CConnectionPoint::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CConnectionPoint::GetConnectionInterface
 *
 * Purpose:
 *  Returns the IID of the outgoing interface supported through
 *  this connection point.
 *
 * Parameters:
 *  pIID            IID * in which to store the IID.
 */

STDMETHODIMP CConnectionPoint::GetConnectionInterface(IID *pIID)
    {
    if (NULL==pIID)
        return ResultFromScode(E_POINTER);

    *pIID=IID_IPolylineAdviseSink10;
    return NOERROR;
    }

/*
 * CConnectionPoint::GetConnectionPointContainer
 *
 * Purpose:
 *  Returns a pointer to the IConnectionPointContainer that
 *  is manageing this connection point.
 *
 * Parameters:
 *  ppCPC           IConnectionPointContainer ** in which to return
 *                  the pointer after calling AddRef.
 */

STDMETHODIMP CConnectionPoint::GetConnectionPointContainer
    (IConnectionPointContainer **ppCPC)
    {
    return m_pObj->QueryInterface(IID_IConnectionPointContainer
        , (void **)ppCPC);
    }

/*
 * CConnectionPoint::Advise
 *
 * Purpose:
 *  Provides this connection point with a notification sink to
 *  call whenever the appropriate outgoing function/event occurs.
 *
 * Parameters:
 *  pUnkSink        LPUNKNOWN to the sink to notify.  The connection
 *                  point must QueryInterface on this pointer to obtain
 *                  the proper interface to call.  The connection
 *                  point must also insure that any pointer held has
 *                  a reference count (QueryInterface will do it).
 *  pdwCookie       DWORD * in which to store the connection key for
 *                  later calls to Unadvise.
 */

STDMETHODIMP CConnectionPoint::Advise(LPUNKNOWN pUnkSink
    , DWORD *pdwCookie)
    {
    IPolylineAdviseSink10  *pSink;

    *pdwCookie=0;

    //Only allow one connection
    if (NULL!=m_pObj->m_pAdv)
        return ResultFromScode(CONNECT_E_ADVISELIMIT);

    //Check for the right interface on the sink.
    if (FAILED(pUnkSink->QueryInterface(IID_IPolylineAdviseSink10
        , (PPVOID)&pSink)))
        return ResultFromScode(CONNECT_E_CANNOTCONNECT);

    *pdwCookie=ADVISEKEY;
    m_pObj->m_pAdv=pSink;
    return NOERROR;
    }

/*
 * CConnectionPoint::Unadvise
 *
 * Purpose:
 *  Terminates the connection to the notification sink identified
 *  with dwCookie (that was returned from Advise).  The connection
 *  point has to Release any held pointers for that sink.
 *
 * Parameters:
 *  dwCookie        DWORD connection key from Advise.
 */

STDMETHODIMP CConnectionPoint::Unadvise(DWORD dwCookie)
    {
    if (0==dwCookie)
        return ResultFromScode(E_INVALIDARG);

    if (ADVISEKEY!=dwCookie)
        ResultFromScode(CONNECT_E_NOCONNECTION);

    ReleaseInterface(m_pObj->m_pAdv);
    return NOERROR;
    }

/*
 * CConnectionPoint::EnumConnections
 *
 * Purpose:
 *  Not implemented.
 */

STDMETHODIMP CConnectionPoint::EnumConnections
    (LPENUMCONNECTIONS *ppEnum)
    {
    *ppEnum=NULL;
    return ResultFromScode(E_NOTIMPL);
    }


IDATAOBJ.CPP

/*
 * IDATAOBJ.CPP
 * Polyline Component Chapter 23
 *
 * Implementation of the IDataObject interface.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIDataObject::CImpIDataObject
 * CImpIDataObject::~CImpIDataObject
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIDataObject::CImpIDataObject(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIDataObject::~CImpIDataObject(void)
    {
    return;
    }

/*
 * CImpIDataObject::QueryInterface
 * CImpIDataObject::AddRef
 * CImpIDataObject::Release
 */

STDMETHODIMP CImpIDataObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIDataObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIDataObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIDataObject::GetData
 *
 * Purpose:
 *  Retrieves data described by a specific FormatEtc into a StgMedium
 *  allocated by this function.  Used like GetClipboardData.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *  pSTM            LPSTGMEDIUM in which to return the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetData(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM)
    {
    UINT            cf=pFE->cfFormat;

    //Check the aspects we support.
    if (!(DVASPECT_CONTENT & pFE->dwAspect))
        return ResultFromScode(DATA_E_FORMATETC);

    pSTM->pUnkForRelease=NULL;

    //Run creates the window to use as a basis for extents
    m_pObj->m_pImpIRunnableObject->Run(NULL);

    //Go render the appropriate data for the format.
    switch (cf)
        {
        case CF_METAFILEPICT:
            pSTM->tymed=TYMED_MFPICT;
            return m_pObj->RenderMetafilePict(&pSTM->hGlobal);

        case CF_BITMAP:
            pSTM->tymed=TYMED_GDI;
            return m_pObj->RenderBitmap((HBITMAP *)&pSTM->hGlobal);

        default:
            if (cf==m_pObj->m_cf)
                {
                pSTM->tymed=TYMED_HGLOBAL;
                return m_pObj->RenderNative(&pSTM->hGlobal);
                }

            break;
        }

    return ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::GetDataHere
 *
 * Purpose:
 *  Renders the specific FormatEtc into caller-allocated medium
 *  provided in pSTM.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *  pSTM            LPSTGMEDIUM providing the medium into which
 *                  wer render the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetDataHere(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM)
    {
    UINT        cf;
    HRESULT     hr;

    /*
     * The only reasonable time this is called is for
     * CFSTR_EMBEDSOURCE and TYMED_ISTORAGE (and later for
     * CFSTR_LINKSOURCE).  This means the same as
     * IPersistStorage::Save.
     */

    cf=RegisterClipboardFormat(CFSTR_EMBEDSOURCE);

    //Aspect is unimportant to us here, as is lindex and ptd.
    if (cf==pFE->cfFormat && (TYMED_ISTORAGE & pFE->tymed))
        {
        //We have an IStorage we can write into.
        pSTM->tymed=TYMED_ISTORAGE;
        pSTM->pUnkForRelease=NULL;

        hr=m_pObj->m_pImpIPersistStorage->Save(pSTM->pstg, FALSE);
        m_pObj->m_pImpIPersistStorage->SaveCompleted(NULL);
        return hr;
        }

    return ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::QueryGetData
 *
 * Purpose:
 *  Tests if a call to GetData with this FormatEtc will provide
 *  any rendering; used like IsClipboardFormatAvailable.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::QueryGetData(LPFORMATETC pFE)
    {
    UINT            cf=pFE->cfFormat;
    BOOL            fRet=FALSE;

    //Check the aspects we support.
    if (!(DVASPECT_CONTENT & pFE->dwAspect))
        return ResultFromScode(DATA_E_FORMATETC);

    switch (cf)
        {
        case CF_METAFILEPICT:
            fRet=(BOOL)(pFE->tymed & TYMED_MFPICT);
            break;

        case CF_BITMAP:
            fRet=(BOOL)(pFE->tymed & TYMED_GDI);
            break;

        default:
            //Check our own format.
            fRet=((cf==m_pObj->m_cf)
                && (BOOL)(pFE->tymed & TYMED_HGLOBAL));
            break;
        }

    return fRet ? NOERROR : ResultFromScode(S_FALSE);
    }

/*
 * CImpIDataObject::GetCanonicalFormatEtc
 *
 * Purpose:
 *  Provides the caller with an equivalent FormatEtc to the one
 *  provided when different FormatEtcs will produce exactly the
 *  same renderings.
 *
 * Parameters:
 *  pFEIn            LPFORMATETC of the first description.
 *  pFEOut           LPFORMATETC of the equal description.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetCanonicalFormatEtc
    (LPFORMATETC pFEIn, LPFORMATETC pFEOut)
    {
    if (NULL==pFEOut)
        return ResultFromScode(E_INVALIDARG);

    pFEOut->ptd=NULL;
    return ResultFromScode(DATA_S_SAMEFORMATETC);
    }

/*
 * CImpIDataObject::SetData
 *
 * Purpose:
 *  Places data described by a FormatEtc and living in a StgMedium
 *  into the object.  The object may be responsible to clean up the
 *  StgMedium before exiting.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the data to set.
 *  pSTM            LPSTGMEDIUM containing the data.
 *  fRelease        BOOL indicating if this function is responsible
 *                  for freeing the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::SetData(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM, BOOL fRelease)
    {
    UINT            cf=pFE->cfFormat;
    BOOL            fRet=FALSE;
    PPOLYLINEDATA   ppl;

    //Check for our own clipboard format and DVASPECT_CONTENT
    if ((cf!=m_pObj->m_cf) || !(DVASPECT_CONTENT & pFE->dwAspect))
        return ResultFromScode(DATA_E_FORMATETC);

    /*
     * Data can only come from global memory containing a
     * POLYLINEDATA structure that we send to the Polyline's
     * DataSet, a now internal function used from here and
     * from IPersistStorage::Load.
     */

    if (TYMED_HGLOBAL!=pSTM->tymed)
        return ResultFromScode(DATA_E_FORMATETC);

    ppl=(PPOLYLINEDATA)GlobalLock(pSTM->hGlobal);

    if (NULL!=ppl)
        {
        m_pObj->DataSet(ppl, TRUE, TRUE);
        GlobalUnlock(pSTM->hGlobal);
        fRet=TRUE;
        }

    if (fRelease)
        ReleaseStgMedium(pSTM);

    return fRet ? NOERROR : ResultFromScode(DATA_E_FORMATETC);
    }

/*
 * CImpIDataObject::EnumFormatEtc
 *
 * Purpose:
 *  Returns an IEnumFORMATETC object through which the caller can
 *  iterate to learn about all the data formats this object can
 *  provide through either GetData[Here] or SetData.
 *
 * Parameters:
 *  dwDir           DWORD describing a data direction, either
 *                  DATADIR_SET or DATADIR_GET.
 *  ppEnum          LPENUMFORMATETC * in which to return the
 *                  pointer to the enumerator.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::EnumFormatEtc(DWORD dwDir
    , LPENUMFORMATETC *ppEnum)
    {
    return m_pObj->m_pDefIDataObject->EnumFormatEtc(dwDir, ppEnum);
    }

/*
 * CImpIDataObject::DAdvise
 * CImpIDataObject::DUnadvise
 * CImpIDataObject::EnumDAdvise
 */

STDMETHODIMP CImpIDataObject::DAdvise(LPFORMATETC pFE, DWORD dwFlags
    , LPADVISESINK pIAdviseSink, LPDWORD pdwConn)
    {
    HRESULT         hr;

    if (NULL==m_pObj->m_pIDataAdviseHolder)
        {
        hr=CreateDataAdviseHolder(&m_pObj->m_pIDataAdviseHolder);

        if (FAILED(hr))
            return ResultFromScode(E_OUTOFMEMORY);
        }

    hr=m_pObj->m_pIDataAdviseHolder->Advise(this, pFE
        , dwFlags, pIAdviseSink, pdwConn);

    return hr;
    }

STDMETHODIMP CImpIDataObject::DUnadvise(DWORD dwConn)
    {
    HRESULT         hr;

    if (NULL==m_pObj->m_pIDataAdviseHolder)
        return ResultFromScode(E_FAIL);

    hr=m_pObj->m_pIDataAdviseHolder->Unadvise(dwConn);

    return hr;
    }

STDMETHODIMP CImpIDataObject::EnumDAdvise(LPENUMSTATDATA *ppEnum)
    {
    HRESULT         hr;

    if (NULL==m_pObj->m_pIDataAdviseHolder)
        return ResultFromScode(E_FAIL);

    hr=m_pObj->m_pIDataAdviseHolder->EnumAdvise(ppEnum);
    return hr;
    }


IEXTCONN.CPP

/*
 * IEXTCONN.CPP
 * Polyline Component Chapter 23
 *
 * Implementation of IExternalConnection as required for an
 * in-process object that supports linking to embedding.
 * Specifically, this will call IOleObject::Close when there
 * are no more strong locks on the object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIExternalConnection::CImpIExternalConnection
 * CImpIExternalConnection::~CImpIExternalConnection
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIExternalConnection::CImpIExternalConnection(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    m_cLockStrong=0L;
    return;
    }

CImpIExternalConnection::~CImpIExternalConnection(void)
    {
    return;
    }

/*
 * CImpIExternalConnection::QueryInterface
 * CImpIExternalConnection::AddRef
 * CImpIExternalConnection::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIExternalConnection.
 */

STDMETHODIMP CImpIExternalConnection::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIExternalConnection::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIExternalConnection::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIExternalConnection::AddConnection
 *
 * Purpose:
 *  Informs the object that a strong connection has been made to it.
 *
 * Parameters:
 *  dwConn          DWORD identifying the type of connection, taken
 *                  from the EXTCONN enumeration.
 *  dwReserved      DWORD reserved.  This is used inside OLE and
 *                  should not be validated.
 *
 * Return Value:
 *  DWORD           The number of connection counts on the
 *                  object, used for debugging purposes only.
 */

STDMETHODIMP_(DWORD) CImpIExternalConnection::AddConnection
    (DWORD dwConn, DWORD dwReserved)
    {
    if (EXTCONN_STRONG & dwConn)
        return ++m_cLockStrong;

    return 0;
    }

/*
 * CImpIExternalConnection::ReleaseConnection
 *
 * Purpose:
 *  Informs an object that a connection has been taken away from
 *  it in which case the object may need to shut down.
 *
 * Parameters:
 *  dwConn              DWORD identifying the type of connection,
 *                      taken from the EXTCONN enumeration.
 *  dwReserved          DWORD reserved.  This is used inside OLE and
 *                      should not be validated.
 *  dwRerved            DWORD reserved
 *  fLastReleaseCloses  BOOL indicating if the last call to this
 *                      function should close the object.
 *
 * Return Value:
 *  DWORD           The number of remaining connection counts on
 *                  the object, used for debugging purposes only.
 */

STDMETHODIMP_(DWORD) CImpIExternalConnection::ReleaseConnection
    (DWORD dwConn, DWORD dwReserved, BOOL fLastReleaseCloses)
    {
    if (EXTCONN_STRONG==dwConn)
        {
        if (0==--m_cLockStrong && fLastReleaseCloses)
            m_pObj->m_pImpIOleObject->Close(OLECLOSE_SAVEIFDIRTY);

        return m_cLockStrong;
        }

    return 0L;
    }


IIPAOBJ.CPP

/*
 * IIPAOBJ.CPP
 * Polyline Component Chapter 23
 *
 * IOleInPlaceActiveObject interface implementation for Polyline
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIOleInPlaceActiveObject::CImpIOleInPlaceActiveObject
 * CImpIOleInPlaceActiveObject::~CImpIOleInPlaceActiveObject
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleInPlaceActiveObject::CImpIOleInPlaceActiveObject
    (PCPolyline pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleInPlaceActiveObject::~CImpIOleInPlaceActiveObject(void)
    {
    return;
    }

/*
 * CImpIOleInPlaceActiveObject::QueryInterface
 * CImpIOleInPlaceActiveObject::AddRef
 * CImpIOleInPlaceActiveObject::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleInPlaceActiveObject object.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    /*
     * This interface should be stand-alone on an object such that a
     * container cannot QueryInterface for it through any other
     * object interface, relying instead of calls to SetActiveObject
     * for it.  By implementing QueryInterface here ourselves, we
     * prevent such abuses.  Note that reference counting still uses
     * CFigure.
     */

    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IOleWindow==riid
        || IID_IOleInPlaceActiveObject==riid)
        *ppv=this;

    //AddRef any interface we'll return.
    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceActiveObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceActiveObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleInPlaceActiveObject::GetWindow
 *
 * Purpose:
 *  Retrieves the handle of the window associated with the object on
 *  which this interface is implemented.
 *
 * Parameters:
 *  phWnd           HWND * in which to store the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_FAIL if there is no
 *                  window.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::GetWindow(HWND *phWnd)
    {
    *phWnd=m_pObj->m_pHW->Window();;
    return NOERROR;
    }

/*
 * CImpIOleInPlaceActiveObject::ContextSensitiveHelp
 *
 * Purpose:
 *  Instructs the object on which this interface is implemented to
 *  enter or leave a context-sensitive help mode.
 *
 * Parameters:
 *  fEnterMode      BOOL TRUE to enter the mode, FALSE otherwise.
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::ContextSensitiveHelp
    (BOOL fEnterMode)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleInPlaceActiveObject::TranslateAccelerator
 *
 * Purpose:
 *  Requests that the active in-place object translate the message
 *  given in pMSG if appropriate.  This is only called for DLL
 *  servers where the container's message loop is running.  EXE
 *  servers have control of the message loop so this will not be
 *  called in such cases.
 *
 * Parameters:
 *  pMSG            LPMSG to the message to translate.
 *
 * Return Value:
 *  HRESULT         NOERROR if translates, S_FALSE if not.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::TranslateAccelerator
    (LPMSG pMSG)
    {
    //We have no accelerators, but this is called anyway
    return ResultFromScode(S_FALSE);
    }

/*
 * CImpIOleInPlaceActiveObject::OnFrameWindowActivate
 *
 * Purpose:
 *  Informs the in-place object that the container's frame window
 *  was either activated or deactivated.  Not currently used.
 *
 * Parameters:
 *  fActivate       BOOL TRUE if the frame is active,
 *                  FALSE otherwise
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::OnFrameWindowActivate
    (BOOL fActivate)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleInPlaceActiveObject::OnDocWindowActivate
 *
 * Purpose:
 *  Informs the in-place object that the document window in the
 *  container is either becoming active or deactive.  On this call
 *  the object must either add or remove frame-level tools,
 *  including the mixed menu, depending on fActivate.
 *
 * Parameters:
 *  fActivate       BOOL TRUE if the document is active,
 *                  FALSE otherwise
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::OnDocWindowActivate
    (BOOL fActivate)
    {
    if (NULL==m_pObj->m_pIOleIPFrame)
        return NOERROR;

    if (fActivate)
        {
       #ifdef WIN32ANSI
        OLECHAR     szTemp[40];

        MultiByteToWideChar(CP_ACP, 0
            , (*m_pObj->m_pST)[IDS_USERTYPE], -1, szTemp, 40);
        m_pObj->m_pIOleIPFrame->SetActiveObject(this
            , szTemp);
       #else
        m_pObj->m_pIOleIPFrame->SetActiveObject(this
            , (*m_pObj->m_pST)[IDS_USERTYPE]);
       #endif

        m_pObj->m_pIOleIPFrame->SetMenu(m_pObj->m_hMenuShared
            , m_pObj->m_hOLEMenu, m_pObj->m_hWnd);

        //Nothing to do with tools.
        }
    else
        {
        m_pObj->m_pIOleIPFrame->SetActiveObject(NULL, NULL);
        }

    return NOERROR;
    }

/*
 * CImpIOleInPlaceActiveObject::ResizeBorder
 *
 * Purpose:
 *  Informs the object that the frame or document size changed in
 *  which case the object may need to resize any of its frame or
 *  document-level tools to match.
 *
 * Parameters:
 *  pRect           LPCRECT indicating the new size of the window
 *                  of interest.
 *  pIUIWindow      LPOLEINPLACEUIWINDOW pointing to an
 *                  IOleInPlaceUIWindow interface on the container
 *                  object of interest.  We use this to do
 *                  border-space negotiation.
 *
 *  fFrame          BOOL indicating if the frame was resized (TRUE)
 *                  or the document (FALSE)
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::ResizeBorder(LPCRECT pRect
    , LPOLEINPLACEUIWINDOW pIUIWindow, BOOL fFrame)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleInPlaceActiveObject::EnableModeless
 *
 * Purpose:
 *  Instructs the object to show or hide any modeless popup windows
 *  that it may be using when activated in-place.
 *
 * Parameters:
 *  fEnable         BOOL indicating to enable/show the windows
 *                  (TRUE) or to hide them (FALSE).
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::EnableModeless
    (BOOL fActivate)
    {
    return ResultFromScode(E_NOTIMPL);
    }


IIPOBJ.CPP

/*
 * IIPOBJ.CPP
 * Polyline Component Chapter 23
 *
 * IOleInPlaceObject interface implementation for Polyline
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIOleInPlaceObject::CImpIOleInPlaceObject
 * CImpIOleInPlaceObject::~CImpIOleInPlaceObject
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleInPlaceObject::CImpIOleInPlaceObject(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleInPlaceObject::~CImpIOleInPlaceObject(void)
    {
    return;
    }

/*
 * CImpIOleInPlaceObject::QueryInterface
 * CImpIOleInPlaceObject::AddRef
 * CImpIOleInPlaceObject::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleInPlaceObject object.
 */

STDMETHODIMP CImpIOleInPlaceObject::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleInPlaceObject::GetWindow
 *
 * Purpose:
 *  Retrieves the handle of the window associated with the object
 *  on which this interface is implemented.
 *
 * Parameters:
 *  phWnd           HWND * in which to store the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_FAIL if there is no
 *                  window.
 */

STDMETHODIMP CImpIOleInPlaceObject::GetWindow(HWND *phWnd)
    {
    if (NULL!=m_pObj->m_pHW)
        *phWnd=m_pObj->m_pHW->Window();
    else
        *phWnd=m_pObj->m_hWnd;

    return NOERROR;
    }

/*
 * CImpIOleInPlaceObject::ContextSensitiveHelp
 *
 * Purpose:
 *  Instructs the object on which this interface is implemented to
 *  enter or leave a context-sensitive help mode.
 *
 * Parameters:
 *  fEnterMode      BOOL TRUE to enter the mode, FALSE otherwise.
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code
 */

STDMETHODIMP CImpIOleInPlaceObject::ContextSensitiveHelp
    (BOOL fEnterMode)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleInPlaceObject::InPlaceDeactivate
 *
 * Purpose:
 *  Instructs the object to deactivate itself from an in-place state
 *  and to discard any Undo state.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code
 */

STDMETHODIMP CImpIOleInPlaceObject::InPlaceDeactivate(void)
    {
    m_pObj->InPlaceDeactivate();
    return NOERROR;
    }

/*
 * CImpIOleInPlaceObject::UIDeactivate
 *
 * Purpose:
 *  Instructs the object to just remove any in-place user interface
 *  but to do no other deactivation.  The object should just hide
 *  the UI components but not destroy them until InPlaceDeactivate
 *  is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code
 */

STDMETHODIMP CImpIOleInPlaceObject::UIDeactivate(void)
    {
    m_pObj->UIDeactivate();
    return NOERROR;
    }

/*
 * CImpIOleInPlaceObject::SetObjectRects
 *
 * Purpose:
 *  Provides the object with rectangles describing the position of
 *  the object in the container window as well as its visible area.
 *
 * Parameters:
 *  prcPos          LPCRECT providing the object's full rectangle
 *                  relative to the continer's document.  The object
 *                  should scale to this rectangle.
 *  prcClip         LPCRECT describing the visible area of the object
 *                  which should not draw outside these areas.
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code
 */

STDMETHODIMP CImpIOleInPlaceObject::SetObjectRects(LPCRECT prcPos
    , LPCRECT prcClip)
    {
    if (NULL!=m_pObj->m_pHW)
        m_pObj->m_pHW->RectsSet((LPRECT)prcPos, (LPRECT)prcClip);

    return NOERROR;
    }

/*
 * CImpIOleInPlaceObject::ReactivateAndUndo
 *
 * Purpose:
 *  Instructs the object to reactivate itself in-place and perform
 *  whatever Undo means for it.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code
 */

STDMETHODIMP CImpIOleInPlaceObject::ReactivateAndUndo(void)
    {
    return m_pObj->InPlaceActivate(m_pObj->m_pIOleClientSite, TRUE);
    }


IOLEOBJ.CPP

/*
 * IOLEOBJ.CPP
 * Polyline Component Chapter 23
 *
 * Implementation of the IOleObject interface for Polyline.  Some of
 * these just pass through to the default handler which does default
 * implementations.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIOleObject::CImpIOleObject
 * CImpIOleObject::~CImpIOleObject
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleObject::CImpIOleObject(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleObject::~CImpIOleObject(void)
    {
    return;
    }

/*
 * CImpIOleObject::QueryInterface
 * CImpIOleObject::AddRef
 * CImpIOleObject::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleObject object.
 */

STDMETHODIMP CImpIOleObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleObject::SetClientSite
 * CImpIOleObject::GetClientSite
 *
 * Purpose:
 *  Manages the IOleClientSite pointer of our container.
 */

STDMETHODIMP CImpIOleObject::SetClientSite
    (LPOLECLIENTSITE pIOleClientSite)
    {
    if (NULL!=m_pObj->m_pIOleClientSite)
        m_pObj->m_pIOleClientSite->Release();

    m_pObj->m_pIOleClientSite=pIOleClientSite;

    if (NULL!=m_pObj->m_pIOleClientSite)
        {
        HRESULT         hr;
        LPMONIKER       pmk;
        LPOLECONTAINER  pIOleCont;

        m_pObj->m_pIOleClientSite->AddRef();

        /*
         * Within IRunnableObject::Run we're supposed to register
         * ourselves as running...however, the moniker has to come
         * from the container's IOleClientSite::GetMoniker.  But
         * Run is called before SetClientSite here, so we have to
         * register now that we do have the client site as well
         * as lock the container.
         */

        hr=m_pObj->m_pIOleClientSite->GetMoniker
            (OLEGETMONIKER_ONLYIFTHERE, OLEWHICHMK_OBJFULL, &pmk);

        if (SUCCEEDED(hr))
            {
            INOLE_RegisterAsRunning(this, pmk, 0
                , &m_pObj->m_dwRegROT);
            pmk->Release();
            }

        hr=m_pObj->m_pIOleClientSite->GetContainer(&pIOleCont);

        if (SUCCEEDED(hr))
            {
            m_pObj->m_fLockContainer=TRUE;
            pIOleCont->LockContainer(TRUE);
            pIOleCont->Release();
            }
        }

    return NOERROR;
    }

STDMETHODIMP CImpIOleObject::GetClientSite(LPOLECLIENTSITE *ppSite)
    {
    //Be sure to AddRef the new pointer you are giving away.
    *ppSite=m_pObj->m_pIOleClientSite;
    m_pObj->m_pIOleClientSite->AddRef();

    return NOERROR;
    }

/*
 * CImpIOleObject::SetHostNames
 *
 * Purpose:
 *  Provides the object with names of the container application and
 *  the object in the container to use in object user interface.
 *
 * Parameters:
 *  pszApp          LPCOLESTR of the container application.
 *  pszObj          LPCOLESTR of some name that is useful in window
 *                  titles.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleObject::SetHostNames(LPCOLESTR pszApp
    , LPCOLESTR pszObj)
    {
    if (NULL!=m_pObj->m_hDlg)
        {
        TCHAR       szTemp[128];

       #ifdef WIN32ANSI
        char        szObj[80];
        WideCharToMultiByte(CP_ACP, 0, pszObj, -1, szObj, 80
            , NULL, NULL);
        wsprintf(szTemp, SZPOLYFRAMETITLE, szObj);
       #else
        wsprintf(szTemp, SZPOLYFRAMETITLE, pszObj);
       #endif
        SetWindowText(m_pObj->m_hDlg, szTemp);
        }

    return NOERROR;
    }

/*
 * CImpIOleObject::Close
 *
 * Purpose:
 *  Forces the object to close down its user interface and unload.
 *
 * Parameters:
 *  dwSaveOption    DWORD describing the circumstances under which
 *                  the object is being saved and closed.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::Close(DWORD dwSaveOption)
    {
    HWND        hWnd;
    BOOL        fSave=FALSE;

    hWnd=m_pObj->m_hDlg;

    //If object is dirty and we're asked to save, save it and close.
    if (OLECLOSE_SAVEIFDIRTY==dwSaveOption && m_pObj->m_fDirty)
        fSave=TRUE;

    /*
     * If asked to prompt, only do so if dirty, then if we get a
     * YES, save as usual and close.  On NO, just close.  On
     * CANCEL return OLE_E_PROMPTSAVECANCELLED.
     */
    if (OLECLOSE_PROMPTSAVE==dwSaveOption && m_pObj->m_fDirty)
        {
        TCHAR       szTitle[20];
        TCHAR       szTemp[80];
        UINT        uRet;

        lstrcpy(szTitle, m_pObj->String(IDS_CLOSECAPTION));
        lstrcpy(szTemp, m_pObj->String(IDS_CLOSEPROMPT));

        uRet=MessageBox(hWnd, szTemp, szTitle, MB_YESNOCANCEL);

        if (IDCANCEL==uRet)
            return ResultFromScode(OLE_E_PROMPTSAVECANCELLED);

        if (IDYES==uRet)
            fSave=TRUE;
        }

    if (fSave)
        {
        m_pObj->SendAdvise(OBJECTCODE_SAVEOBJECT);
        m_pObj->SendAdvise(OBJECTCODE_SAVED);
        }

    //We get directly here on OLECLOSE_NOSAVE.
    if (m_pObj->m_fLockContainer)
        {
        //Match LockContainer call from SetClientSite
        LPOLECONTAINER  pIOleCont;

        if (SUCCEEDED(m_pObj->m_pIOleClientSite
            ->GetContainer(&pIOleCont)))
            {
            pIOleCont->LockContainer(FALSE);
            pIOleCont->Release();
            }
        }

    if (NULL!=hWnd)
        {
        //This hides the window and sends the appropriate notify.
        DoVerb(OLEIVERB_HIDE, NULL, NULL, -1, NULL, NULL);

        m_pObj->SendAdvise(OBJECTCODE_CLOSED);
        PostMessage(hWnd, POLYM_CLOSE, 0, 0L);
        }

    return NOERROR;
    }

/*
 * CImpIOleObject::DoVerb
 *
 * Purpose:
 *  Executes an object-defined action.
 *
 * Parameters:
 *  iVerb           LONG index of the verb to execute.
 *  pMSG            LPMSG describing the event causing the
 *                  activation.
 *  pActiveSite     LPOLECLIENTSITE to the site involved.
 *  lIndex          LONG the piece on which execution is happening.
 *  hWndParent      HWND of the window in which the object can play
 *                  in-place.
 *  pRectPos        LPRECT of the object in hWndParent where the
 *                  object can play in-place if desired.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::DoVerb(LONG iVerb, LPMSG pMSG
    , LPOLECLIENTSITE pActiveSite, LONG lIndex, HWND hWndParent
    , LPCRECT pRectPos)
    {
    HRESULT     hr;

    switch (iVerb)
        {
        case OLEIVERB_HIDE:
            //CHAPTER23MOD
            if (NULL!=m_pObj->m_pIOleIPSite)
                {
                if (NULL!=m_pObj->m_pHW)
                    ShowWindow(m_pObj->m_pHW->Window(), SW_HIDE);
                }
            else
                {
                if (NULL!=m_pObj->m_hDlg)
                    ShowWindow(m_pObj->m_hDlg, SW_HIDE);
                }

            m_pObj->SendAdvise(OBJECTCODE_HIDEWINDOW);
            m_pObj->m_fAllowInPlace=TRUE;
            //End CHAPTER23MOD

            break;

        //CHAPTER23MOD
        case OLEIVERB_PRIMARY:
        case OLEIVERB_SHOW:
            if (NULL!=m_pObj->m_pIOleIPSite)
                {
                if (NULL!=m_pObj->m_pHW)
                    ShowWindow(m_pObj->m_pHW->Window(), SW_HIDE);

                return NOERROR; //Already active
                }

            if (m_pObj->m_fAllowInPlace)
                {
                if (SUCCEEDED(m_pObj->InPlaceActivate(pActiveSite
                    , TRUE)))
                    return NOERROR;
                }
            //FALL THROUGH
        //End CHAPTER23MOD

        case OLEIVERB_OPEN:
            //CHAPTER23MOD
            if (NULL!=m_pObj->m_pIOleIPSite)
                {
                m_pObj->InPlaceDeactivate();
                m_pObj->m_fAllowInPlace=FALSE;
                }
            //End CHAPTER23MOD

            /*
             * If we're not running, make sure we are.  In any
             * case, make the dialog visible and insure it has
             * the right parent now.
             */
            hr=NOERROR;
            if (NULL==m_pObj->m_hDlg)
                hr=m_pObj->m_pImpIRunnableObject->Run(NULL);

            if (FAILED(hr) || NULL==m_pObj->m_hDlg)
                return ResultFromScode(E_OUTOFMEMORY);

            ShowWindow(m_pObj->m_hDlg, SW_SHOW);
            SetFocus(m_pObj->m_hDlg);
            m_pObj->SendAdvise(OBJECTCODE_SHOWOBJECT);
            m_pObj->SendAdvise(OBJECTCODE_SHOWWINDOW);

            break;

        //CHAPTER23MOD
        case OLEIVERB_INPLACEACTIVATE:
            if (NULL!=m_pObj->m_pHW)
                {
                HWND    hWndHW=m_pObj->m_pHW->Window();

                ShowWindow(hWndHW, SW_SHOW);
                SetFocus(hWndHW);

                return NOERROR;
                }

            /*
             * Only inside-out supporting containers will use
             * this verb.
             */
            m_pObj->m_fContainerKnowsInsideOut=TRUE;
            m_pObj->InPlaceActivate(pActiveSite, FALSE);
            break;

        case OLEIVERB_UIACTIVATE:
            m_pObj->InPlaceActivate(pActiveSite, TRUE);
            break;
        //End CHAPTER23MOD

        default:
            return ResultFromScode(OLEOBJ_S_INVALIDVERB);
        }

    return NOERROR;
    }

/*
 * CImpIOleObject::GetUserClassID
 *
 * Purpose:
 *  Used for linked objects, this returns the class ID of what end
 *  users think they are editing.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store the CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetUserClassID(LPCLSID pClsID)
    {
    /*
     * If you are not registered to handle data other than yourself,
     * then you can just return your class ID here.  If you are
     * registered as usable from Treat-As dialogs, then you need to
     * return the CLSID of what you are really editing.
     */

    *pClsID=CLSID_Polyline19;
    return NOERROR;
    }

/*
 * CImpIOleObject::SetExtent
 *
 * Purpose:
 *  Sets the size of the object in HIMETRIC units.  Since we're in
 *  a dialog, the size of the object in us is fixed, so we ignore
 *  this call.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect affected.
 *  pszl            LPSIZEL containing the new size.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::SetExtent(DWORD dwAspect
    , LPSIZEL pszl)
    {
    //Ignored:  no size change in the dialog.
    return NOERROR;
    }

/*
 * CImpIOleObject::GetExtent
 *
 * Purpose:
 *  Retrieves the size of the object in HIMETRIC units.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect requested
 *  pszl            LPSIZEL into which to store the size.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetExtent(DWORD dwAspect, LPSIZEL pszl)
    {
    //Delegate directly to IViewObject2::GetExtent
    return m_pObj->m_pImpIViewObject->GetExtent(dwAspect, -1
        , NULL, pszl);
    }

/*
 * CImpIOleObject::Advise
 * CImpIOleObject::Unadvise
 * CImpIOleObject::EnumAdvise
 *
 * Purpose:
 *  Advisory connection functions.
 */

STDMETHODIMP CImpIOleObject::Advise(LPADVISESINK pIAdviseSink
    , LPDWORD pdwConn)
    {
    if (NULL==m_pObj->m_pIOleAdviseHolder)
        {
        HRESULT     hr;

        hr=CreateOleAdviseHolder(&m_pObj->m_pIOleAdviseHolder);

        if (FAILED(hr))
            return hr;
        }

    return m_pObj->m_pIOleAdviseHolder->Advise(pIAdviseSink
        , pdwConn);
    }

STDMETHODIMP CImpIOleObject::Unadvise(DWORD dwConn)
    {
    if (NULL!=m_pObj->m_pIOleAdviseHolder)
        return m_pObj->m_pIOleAdviseHolder->Unadvise(dwConn);

    return ResultFromScode(E_FAIL);
    }

STDMETHODIMP CImpIOleObject::EnumAdvise(LPENUMSTATDATA *ppEnum)
    {
    if (NULL!=m_pObj->m_pIOleAdviseHolder)
        return m_pObj->m_pIOleAdviseHolder->EnumAdvise(ppEnum);

    return ResultFromScode(E_FAIL);
    }

/*
 * CImpIOleObject::SetMoniker
 *
 * Purpose:
 *  Informs the object of its moniker or its container's moniker
 *  depending on dwWhich.
 *
 * Parameters:
 *  dwWhich         DWORD describing whether the moniker is the
 *                  object's or the container's.
 *  pmk             LPMONIKER with the name.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::SetMoniker(DWORD dwWhich
    , LPMONIKER pmk)
    {
    LPMONIKER       pmkFull;
    HRESULT         hr=ResultFromScode(E_FAIL);

    if (NULL!=m_pObj->m_pIOleClientSite)
        {
        hr=m_pObj->m_pIOleClientSite->GetMoniker
            (OLEGETMONIKER_ONLYIFTHERE, OLEWHICHMK_OBJFULL
            , &pmkFull);
        }

    if (SUCCEEDED(hr))
        {
        if (NOERROR==pmkFull->IsRunning(NULL, NULL, NULL))
            {
            pmkFull->Release();
            return NOERROR;
            }

        //This will revoke the old one if m_dwRegROT is nonzero.
        INOLE_RegisterAsRunning(m_pObj, pmkFull, 0
            , &m_pObj->m_dwRegROT);

        //Inform clients of the new moniker
        if (NULL!=m_pObj->m_pIOleAdviseHolder)
            m_pObj->m_pIOleAdviseHolder->SendOnRename(pmkFull);

        pmkFull->Release();
        }

    return hr;
    }

/*
 * CImpIOleObject::GetMoniker
 *
 * Purpose:
 *  Asks the object for a moniker that can later be used to
 *  reconnect to it.
 *
 * Parameters:
 *  dwAssign        DWORD determining how to assign the moniker to
 *                  to the object.
 *  dwWhich         DWORD describing which moniker the caller wants.
 *  ppmk            LPMONIKER * into which to store the moniker.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetMoniker(DWORD dwAssign
    , DWORD dwWhich, LPMONIKER *ppmk)
    {
    HRESULT         hr=ResultFromScode(E_FAIL);

    *ppmk=NULL;

    /*
     * Since we only support embedded objects, our moniker
     * is always the full moniker from the contianer.
     */

    if (NULL!=m_pObj->m_pIOleClientSite)
        {
        hr=m_pObj->m_pIOleClientSite->GetMoniker
            (OLEGETMONIKER_ONLYIFTHERE, OLEWHICHMK_OBJFULL, ppmk);
        }

    return (NULL!=*ppmk) ? NOERROR : hr;
    }

//Methods not implemented or trivial
STDMETHODIMP CImpIOleObject::InitFromData(LPDATAOBJECT pIDataObject
    , BOOL fCreation, DWORD dw)
    {
    return ResultFromScode(E_NOTIMPL);
    }

STDMETHODIMP CImpIOleObject::GetClipboardData(DWORD dwReserved
    , LPDATAOBJECT *ppIDataObj)
    {
    return ResultFromScode(E_NOTIMPL);
    }

STDMETHODIMP CImpIOleObject::Update(void)
    {
    return NOERROR;
    }

STDMETHODIMP CImpIOleObject::IsUpToDate(void)
    {
    return NOERROR;
    }

STDMETHODIMP CImpIOleObject::SetColorScheme(LPLOGPALETTE pLP)
    {
    return ResultFromScode(E_NOTIMPL);
    }

//Methods implemented using registry helper functions in OLE.

STDMETHODIMP CImpIOleObject::EnumVerbs(LPENUMOLEVERB *ppEnum)
    {
    return OleRegEnumVerbs(m_pObj->m_clsID, ppEnum);
    }

STDMETHODIMP CImpIOleObject::GetUserType(DWORD dwForm
    , LPOLESTR *ppszType)
    {
    return OleRegGetUserType(m_pObj->m_clsID, dwForm, ppszType);
    }

STDMETHODIMP CImpIOleObject::GetMiscStatus(DWORD dwAspect
    , LPDWORD pdwStatus)
    {
    return OleRegGetMiscStatus(m_pObj->m_clsID, dwAspect
        , pdwStatus);
    }


IPERSTMI.CPP

/*
 * IPERSTMI.CPP
 * Polyline Component Chapter 23
 *
 * Implementation of the IPersistStreamInit interface exposed on the
 * Polyline object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIPersistStreamInit:CImpIPersistStreamInit
 * CImpIPersistStreamInit::~CImpIPersistStreamInit
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistStreamInit::CImpIPersistStreamInit(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPersistStreamInit::~CImpIPersistStreamInit(void)
    {
    return;
    }

/*
 * CImpIPersistStreamInit::QueryInterface
 * CImpIPersistStreamInit::AddRef
 * CImpIPersistStreamInit::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIPersistStreamInit.
 */

STDMETHODIMP CImpIPersistStreamInit::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistStreamInit::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistStreamInit::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistStreamInit::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the object represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 */

STDMETHODIMP CImpIPersistStreamInit::GetClassID(LPCLSID pClsID)
    {
    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this object since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 */

STDMETHODIMP CImpIPersistStreamInit::IsDirty(void)
    {
    return ResultFromScode(m_pObj->m_fDirty ? S_OK : S_FALSE);
    }

/*
 * CImpIPersistStreamInit::Load
 *
 * Purpose:
 *  Instructs the object to load itself from a previously saved
 *  IStreamInit that was handled by Save in another object lifetime.
 *  The seek pointer in this stream will be exactly the same as
 *  it was when Save was called, and this function must leave
 *  the seek pointer the same as it was on exit from Save, regardless
 *  of success or failure.  This function should not hold on to
 *  pIStream.
 *
 *  This function is called in lieu of IPersistStreamInit::InitNew
 *  when the object already has a persistent state.
 *
 * Parameters:
 *  pIStream        LPSTREAM from which to load.
 */

STDMETHODIMP CImpIPersistStreamInit::Load(LPSTREAM pIStream)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    HRESULT         hr;

    if (NULL==pIStream)
        return ResultFromScode(E_POINTER);

    //Read all the data into the POLYLINEDATA structure.
    hr=pIStream->Read(&pl, CBPOLYLINEDATA, &cb);

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        return hr;

    m_pObj->DataSet(&pl, TRUE, TRUE);
    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::Save
 *
 * Purpose:
 *  Saves the data for this object to an IStreamInit.  Be sure not
 *  to change the position of the seek pointer on entry to this
 *  function: the caller will assume that you write from the
 *  current offset.  Leave the stream's seek pointer at the end
 *  of the data written on exit.
 *
 * Parameters:
 *  pIStream        LPSTREAM in which to save our data.
 *  fClearDirty     BOOL indicating if this call should clear
 *                  the object's dirty flag (TRUE) or leave it
 *                  unchanged (FALSE).
 */

STDMETHODIMP CImpIPersistStreamInit::Save(LPSTREAM pIStream
    , BOOL fClearDirty)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    HRESULT         hr;

    if (NULL==pIStream)
        return ResultFromScode(E_POINTER);

    m_pObj->DataGet(&pl);

    hr=pIStream->Write(&pl, CBPOLYLINEDATA, &cb);

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        return ResultFromScode(STG_E_WRITEFAULT);

    if (fClearDirty)
        m_pObj->m_fDirty;

    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::GetSizeMax
 *
 * Purpose:
 *  Returns the size of the data we would write if Save was
 *  called right now.
 *
 * Parameters:
 *  pcbSize         ULARGE_INTEGER * in which to save the size
 *                  of the stream an immediate call to Save would
 *                  write.
 */

STDMETHODIMP CImpIPersistStreamInit::GetSizeMax(ULARGE_INTEGER
    *pcbSize)
    {
    if (NULL==pcbSize)
        return ResultFromScode(E_POINTER);

    ULISet32(*pcbSize, CBPOLYLINEDATA);
    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::InitNew
 *
 * Purpose:
 *  Informs the object that it is being created new instead of
 *  loaded from a persistent state.  This will be called in lieu
 *  of IPersistStreamInit::Load.
 *
 * Parameters:
 *  None
 */

STDMETHODIMP CImpIPersistStreamInit::InitNew(void)
    {
    //Nothing for us to do
    return NOERROR;
    }


IPERSTOR.CPP

/*
 * IPERSTOR.CPP
 * Polyline Component Chapter 23
 *
 * Implementation of the IPersistStorage interface exposed on the
 * Polyline object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIPersistStorage:CImpIPersistStorage
 * CImpIPersistStorage::~CImpIPersistStorage
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistStorage::CImpIPersistStorage(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    m_psState=PSSTATE_UNINIT;
    return;
    }

CImpIPersistStorage::~CImpIPersistStorage(void)
    {
    return;
    }

/*
 * CImpIPersistStorage::QueryInterface
 * CImpIPersistStorage::AddRef
 * CImpIPersistStorage::Release
 */

STDMETHODIMP CImpIPersistStorage::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistStorage::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistStorage::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistStorage::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the object represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, error code otherwise.
 */

STDMETHODIMP CImpIPersistStorage::GetClassID(LPCLSID pClsID)
    {
    if (PSSTATE_UNINIT==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this object since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 *
 */

STDMETHODIMP CImpIPersistStorage::IsDirty(void)
    {
    if (PSSTATE_UNINIT==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    return ResultFromScode(m_pObj->m_fDirty ? S_OK : S_FALSE);
    }

/*
 * CImpIPersistStorage::InitNew
 *
 * Purpose:
 *  Provides the object with the IStorage to hold on to while the
 *  object is running.  Here we initialize the structure of the
 *  storage and AddRef it for incremental access. This function will
 *  only be called once in the object's lifetime in lieu of Load.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE for the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::InitNew(LPSTORAGE pIStorage)
    {
    HRESULT     hr;

    if (PSSTATE_UNINIT!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    if (NULL==pIStorage)
        return ResultFromScode(E_POINTER);

    /*
     * The rules of IPersistStorage mean we hold onto the IStorage
     * and pre-create anything we'd need in Save(...,TRUE) for
     * low-memory situations.  For us this means creating our
     * "CONTENTS" stream and holding onto that IStream as
     * well as the IStorage here (requiring an AddRef call).
     */

    hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
        | STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE
        , 0, 0, &m_pObj->m_pIStream);

    if (FAILED(hr))
        return hr;

    //We expect that the client has called WriteClassStg
    WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
        , (*m_pObj->m_pST)[IDS_USERTYPE]);

    m_pObj->m_pIStorage=pIStorage;
    pIStorage->AddRef();

    m_psState=PSSTATE_SCRIBBLE;

    //Initialize the cache as needed.
    m_pObj->m_pDefIPersistStorage->InitNew(pIStorage);
    return NOERROR;
    }

/*
 * CImpIPersistStorage::Load
 *
 * Purpose:
 *  Instructs the object to load itself from a previously saved
 *  IStorage that was handled by Save in another object lifetime.
 *  This function will only be called once in the object's lifetime
 *  in lieu of InitNew. The object should hold on to pIStorage here
 *  for incremental access and low-memory saves in Save.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE from which to load.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::Load(LPSTORAGE pIStorage)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    LPSTREAM        pIStream;
    HRESULT         hr;

    if (PSSTATE_UNINIT!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    if (NULL==pIStorage)
        return ResultFromScode(E_POINTER);

    //We don't check CLSID to remain compatible with other chapters.

    hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
        | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return ResultFromScode(STG_E_READFAULT);

    //Read all the data into the POLYLINEDATA structure.
    hr=pIStream->Read(&pl, CBPOLYLINEDATA, &cb);

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        {
        pIStream->Release();
        return hr;
        }

    /*
     * We don't call pIStream->Release here because we may need
     * it for a low-memory save in Save.  We also need to
     * hold onto a copy of pIStorage, meaning AddRef.
     */
    m_pObj->m_pIStream=pIStream;

    m_pObj->m_pIStorage=pIStorage;
    pIStorage->AddRef();

    //DataSet now internal on CPolyline
    m_pObj->DataSet(&pl, TRUE, TRUE);

    m_psState=PSSTATE_SCRIBBLE;

    //We also need to tell the cache to load cached graphics
    m_pObj->m_pDefIPersistStorage->Load(pIStorage);
    return NOERROR;
    }

/*
 * CImpIPersistStorage::Save
 *
 * Purpose:
 *  Saves the data for this object to an IStorage which may
 *  or may not be the same as the one previously passed to
 *  Load, indicated with fSameAsLoad.  After this call we may
 *  not write into the storage again until SaveCompleted is
 *  called, although we may still read.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which to save our data.
 *  fSameAsLoad     BOOL indicating if this is the same pIStorage
 *                  that was passed to Load.  If TRUE, then the
 *                  object should write whatever it has *without
 *                  *using any extra memory* as this may be a low
 *                  memory save attempt.  That means that you must
 *                  not try to open or create streams.  If FALSE
 *                  you need to regenerate your whole storage
 *                  structure, being sure to also release any
 *                  pointers held from InitNew and Load.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::Save(LPSTORAGE pIStorage
    , BOOL fSameAsLoad)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    LPSTREAM        pIStream;
    HRESULT         hr;

    //Have to come here from scribble state.
    if (PSSTATE_SCRIBBLE!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    //Must have an IStorage if we're not in SameAsLoad
    if (NULL==pIStorage && !fSameAsLoad)
        return ResultFromScode(E_POINTER);

    /*
     * If we're saving to a new storage, create a new stream.
     * If fSameAsLoad it TRUE, then we write to the
     * stream we already allocated.  We should NOT depends on
     * pIStorage with fSameAsLoad is TRUE.
     */
    if (fSameAsLoad)
        {
        LARGE_INTEGER   li;

        /*
         * Use pre-allocated streams to avoid failures due
         * to low-memory conditions.  Be sure to reset the
         * stream pointer if you used this stream before!!
         */
        pIStream=m_pObj->m_pIStream;
        LISet32(li, 0);
        pIStream->Seek(li, STREAM_SEEK_SET, NULL);

        //This matches the Release below.
        pIStream->AddRef();
        }
    else
        {
        hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
            | STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE
            , 0, 0, &pIStream);

        if (FAILED(hr))
            return hr;

        //Only do this with new storages.
        WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
            , (*m_pObj->m_pST)[IDS_USERTYPE]);
        }

    //DataGet now internal on CPolyline
    m_pObj->DataGet(&pl);

    hr=pIStream->Write(&pl, CBPOLYLINEDATA, &cb);
    pIStream->Release();

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        return ResultFromScode(STG_E_WRITEFAULT);

    m_psState=PSSTATE_ZOMBIE;

    //We also need to tell the cache to save cached graphics
    m_pObj->m_pDefIPersistStorage->Save(pIStorage, fSameAsLoad);
    return NOERROR;
    }

/*
 * CImpIPersistStorage::SaveCompleted
 *
 * Purpose:
 *  Notifies the object that the storage in pIStorage has been
 *  completely saved now.  This is called when the user of this
 *  object wants to save us in a completely new storage, and if
 *  we normally hang on to the storage we have to reinitialize
 *  ourselves here for this new one that is now complete.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE of the new storage in which we live.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::SaveCompleted(LPSTORAGE pIStorage)
    {
    HRESULT     hr;
    LPSTREAM    pIStream;

    //Must be called in no-scribble or hands-off state
    if (!(PSSTATE_ZOMBIE==m_psState || PSSTATE_HANDSOFF==m_psState))
        return ResultFromScode(E_UNEXPECTED);

    //If we're coming from Hands-Off, we'd better get a storage
    if (NULL==pIStorage && PSSTATE_HANDSOFF==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    /*
     * If pIStorage is NULL, then we don't need to do anything
     * since we already have all the pointers we need for Save.
     * Otherwise we have to release any held pointers and
     * reinitialize them from pIStorage.
     */

    if (NULL!=pIStorage)
        {
        hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
            | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0
            , &pIStream);

        if (FAILED(hr))
            return hr;

        if (NULL!=m_pObj->m_pIStream)
            m_pObj->m_pIStream->Release();

        m_pObj->m_pIStream=pIStream;

        if (NULL!=m_pObj->m_pIStorage)
            m_pObj->m_pIStorage->Release();

        m_pObj->m_pIStorage=pIStorage;
        m_pObj->m_pIStorage->AddRef();
        }

    //Change state back to scribble.
    m_psState=PSSTATE_SCRIBBLE;

    m_pObj->m_pDefIPersistStorage->SaveCompleted(pIStorage);
    return NOERROR;
    }

/*
 * CImpIPersistStorage::HandsOffStorage
 *
 * Purpose:
 *  Instructs the object that another agent is interested in having
 *  total access to the storage we might be hanging on to from
 *  InitNew or SaveCompleted.  In this case we must release our hold
 *  and await another call to SaveCompleted before we have a hold
 *  again.  Therefore we cannot read or write after this call until
 *  SaveCompleted.
 *
 *  Situations where this might happen arise in compound document
 *  scenarios where this object might be in-place active but the
 *  application wants to rename and commit the root storage.
 *  Therefore we are asked to close our hold, let the container
 *  party on the storage, then call us again later to tell us the
 *  new storage we can hold.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::HandsOffStorage(void)
    {
    /*
     * Must come from scribble or no-scribble.  A repeated call
     * to HandsOffStorage is an unexpected error (bug in client).
     */
    if (PSSTATE_UNINIT==m_psState || PSSTATE_HANDSOFF==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    //Release held pointers
    if (NULL!=m_pObj->m_pIStream)
        {
        m_pObj->m_pIStream->Release();
        m_pObj->m_pIStream=NULL;
        }

    if (NULL!=m_pObj->m_pIStorage)
        {
        m_pObj->m_pIStorage->Release();
        m_pObj->m_pIStorage=NULL;
        }

    m_psState=PSSTATE_HANDSOFF;

    m_pObj->m_pDefIPersistStorage->HandsOffStorage();
    return NOERROR;
    }


IPOLYLIN.CPP

/*
 * IPOLYLIN.CPP
 * Polyline Component Chapter 23
 *
 * Implementation of the IPolyline10 interface that we expose on the
 * CPolyline object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIPolyline:CImpIPolyline
 * CImpIPolyline::~CImpIPolyline
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPolyline::CImpIPolyline(PCPolyline pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPolyline::~CImpIPolyline(void)
    {
    return;
    }

/*
 * CImpIPolyline::QueryInterface
 * CImpIPolyline::AddRef
 * CImpIPolyline::Release
 */

STDMETHODIMP CImpIPolyline::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPolyline::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPolyline::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPolyline::Init
 *
 * Purpose:
 *  Instantiates a polyline window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags
 *  uID             UINT ID to associate with this window
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_OUTOFMEMORY
 */

STDMETHODIMP CImpIPolyline::Init(HWND hWndParent, LPRECT pRect
    , DWORD dwStyle, UINT uID)
    {
    SCODE           sc;

    m_pObj->m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY
        , SZCLASSPOLYLINE, SZCLASSPOLYLINE, dwStyle, pRect->left
        , pRect->top, pRect->right-pRect->left
        , pRect->bottom-pRect->top, hWndParent, (HMENU)uID
        , m_pObj->m_hInst, m_pObj);

    sc=(NULL!=m_pObj->m_hWnd) ? S_OK : E_OUTOFMEMORY;
    return ResultFromScode(sc);
    }

/*
 * CImpIPolyline::New
 *
 * Purpose:
 *  Cleans out and reinitializes the data to defaults.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::New(void)
    {
    PPOLYLINEDATA   ppl=&m_pObj->m_pl;
    UINT            i;
    RECT            rc;

    ppl->wVerMaj=VERSIONMAJOR;
    ppl->wVerMin=VERSIONMINOR;

    //Our rectangle is the size of our window's client area.
    if (NULL!=m_pObj->m_hWnd)
        {
        GetClientRect(m_pObj->m_hWnd, &rc);
        RECTTORECTS(rc, ppl->rc);
        }
    else
        {
        SetRect(&rc, 0, 0, 100, 100);       //Something reasonable
        RECTTORECTS(rc, ppl->rc);
        }

    //Clean out the POLYLINEDATA structure and repaint the window.
    for (i=0; i< CPOLYLINEPOINTS; i++)
        {
        ppl->rgpt[i].x=0;
        ppl->rgpt[i].y=0;
        }

    ppl->cPoints      =0;
    ppl->rgbBackground=GetSysColor(COLOR_WINDOW);
    ppl->rgbLine      =GetSysColor(COLOR_WINDOWTEXT);
    ppl->iLineStyle   =PS_SOLID;

    //This is now conditional since we may not yet have a window.
    if (NULL!=m_pObj->m_hWnd)
        {
        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        UpdateWindow(m_pObj->m_hWnd);
        m_pObj->m_fDirty=TRUE;
        }

    m_pObj->SendAdvise(OBJECTCODE_DATACHANGED);
    return NOERROR;
    }

/*
 * CImpIPolyline::Undo
 *
 * Purpose:
 *  Reverses previous actions in a Polyline.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         S_OK if we can Undo more, S_FALSE otherwise.
 */

STDMETHODIMP CImpIPolyline::Undo(void)
    {
    SCODE           sc;

    //Decrement the number of active points and repaint.
    if (m_pObj->m_pl.cPoints > 0)
        {
        m_pObj->m_pl.cPoints--;
        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        UpdateWindow(m_pObj->m_hWnd);
        }

    if (NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnPointChange();
        }

    m_pObj->SendAdvise(OBJECTCODE_DATACHANGED);

    //Return if we can undo any more.
    sc=(0!=m_pObj->m_pl.cPoints) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * CImpIPolyline::Window
 *
 * Purpose:
 *  Returns the window handle associated with this polyline.
 *
 * Parameters:
 *  phWnd           HWND * in which to return the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CImpIPolyline::Window(HWND *phWnd)
    {
    *phWnd=m_pObj->m_hWnd;
    return NOERROR;
    }

/*
 * CImpIPolyline::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::RectGet(LPRECT pRect)
    {
    RECT            rc;
    POINT           pt;

    if (NULL==m_pObj->m_hWnd)
        {
        SetRect(pRect, 0, 0, 150, 150); //Reasonable default
        return NOERROR;
        }

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_pObj->m_hWnd, &rc);
    pt.x=rc.left;
    pt.y=rc.top;
    ScreenToClient(GetParent(m_pObj->m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return NOERROR;
    }

/*
 * CImpIPolyline::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return NOERROR;
    }

/*
 * CImpIPolyline::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT            cx, cy;
    RECT            rc;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetRect(&rc, 0, 0, cx, cy);
    RECTTORECTS(rc, m_pObj->m_pl.rc);

    if (NULL!=m_pObj->m_hWnd)
        {
        SetWindowPos(m_pObj->m_hWnd, NULL, pRect->left, pRect->top
            , cx, cy, SWP_NOZORDER);
        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        }

    if (fNotify && NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnSizeChange();
        }

    return NOERROR;
    }

/*
 * CImpIPolyline::SizeSet
 *
 * Purpose:
 *  Sets a new size for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT            cx, cy;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    if (NULL!=m_pObj->m_hWnd)
        {
        SetWindowPos(m_pObj->m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
            , SWP_NOMOVE | SWP_NOZORDER);
        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        }

    if (fNotify && NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnSizeChange();
        }

    return NOERROR;
    }

/*
 * CImpIPolyline::ColorSet
 *
 * Purpose:
 *  Changes for background or line color in the Polyline
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color to use.
 *  pcrPrev         COLORREF * in whch to store the
 *                  previous color.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::ColorSet(UINT iColor, COLORREF cr
    , COLORREF *pcrPrev)
    {
    COLORREF        crRet;

    if (NULL==pcrPrev)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    switch (iColor)
        {
        case POLYLINECOLOR_BACKGROUND:
            crRet=m_pObj->m_pl.rgbBackground;
            m_pObj->m_pl.rgbBackground=cr;
            break;

        case POLYLINECOLOR_LINE:
            crRet=m_pObj->m_pl.rgbLine;
            m_pObj->m_pl.rgbLine=cr;
            break;
        }

    //If the color changed, repaint
    if (crRet!=cr)
        {
        if (NULL!=m_pObj->m_pAdv)
            {
            m_pObj->m_fDirty=TRUE;
            m_pObj->m_pAdv->OnColorChange();
            }

        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        UpdateWindow(m_pObj->m_hWnd);

        m_pObj->SendAdvise(OBJECTCODE_DATACHANGED);
        }

    *pcrPrev=crRet;
    return NOERROR;
    }

/*
 * CImpIPolyline::ColorGet
 *
 * Purpose:
 *  Retrieves one of the colors currently in use by the Polyline.
 *
 * Parameters:
 *  iColor          UINT identifying the color of interest.
 *  pcr             COLORREF * in which to return the color.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::ColorGet(UINT iColor, COLORREF *pcr)
    {
    COLORREF        crRet;

    if (NULL==pcr)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    crRet=(POLYLINECOLOR_BACKGROUND==iColor)
        ? m_pObj->m_pl.rgbBackground : m_pObj->m_pl.rgbLine;

    *pcr=crRet;
    return NOERROR;
    }

/*
 * CImpIPolyline::LineStyleSet
 *
 * Purpose:
 *  Changes the line style in use by the Polyline
 *
 * Parameters:
 *  iStyle          UINT style of the line to use.
 *  piPrev          UINT * in which to store the previous style.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::LineStyleSet(UINT iStyle, UINT *piPrev)
    {
    UINT            uRet;

    uRet=(UINT)m_pObj->m_pl.iLineStyle;

    if (NULL==piPrev)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    //Validate the line style
    if (PS_SOLID==iStyle || PS_DASH==iStyle || PS_DOT==iStyle
        || PS_DASHDOT==iStyle || PS_DASHDOTDOT==iStyle)
        {
        m_pObj->m_pl.iLineStyle=iStyle;

        if (uRet!=(UINT)m_pObj->m_pl.iLineStyle)
            {
            if (NULL!=m_pObj->m_pAdv)
                {
                m_pObj->m_fDirty=TRUE;
                m_pObj->m_pAdv->OnLineStyleChange();
                }

            InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
            UpdateWindow(m_pObj->m_hWnd);

            m_pObj->SendAdvise(OBJECTCODE_DATACHANGED);
            }
        }

    *piPrev=uRet;
    return NOERROR;
    }

/*
 * CImpIPolyline::LineStyleGet
 *
 * Purpose:
 *  Retrieves the current line style in use in the Polyline
 *
 * Parameters:
 *  piStyle         UINT * in which to store the style.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::LineStyleGet(UINT *piStyle)
    {
    if (NULL==piStyle)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    *piStyle=m_pObj->m_pl.iLineStyle;
    return NOERROR;
    }


IRUNOBJ.CPP

/*
 * IRUNOBJ.CPP
 * Polyline Component Chapter 23
 *
 * Implementation of the IRunnableObject interface which allows
 * us to enter the "running" state which means Polyline's dialog
 * box is created, but not visible.  This is necessary so that
 * containers can ask for our extents before calling DoVerb.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIRunnableObject::CImpIRunnableObject
 * CImpIRunnableObject::~CImpIRunnableObject
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIRunnableObject::CImpIRunnableObject(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIRunnableObject::~CImpIRunnableObject(void)
    {
    return;
    }

/*
 * CImpIRunnableObject::QueryInterface
 * CImpIRunnableObject::AddRef
 * CImpIRunnableObject::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIRunnableObject.
 */

STDMETHODIMP CImpIRunnableObject::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIRunnableObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIRunnableObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIRunnableObject::GetRunningClass
 *
 * Purpose:
 *  Returns the CLSID of the object.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store the CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIRunnableObject::GetRunningClass(LPCLSID pClsID)
    {
    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

/*
 * CImpIRunnableObject::Run
 *
 * Purpose:
 *  Run an object in the given bind context, that is, put the object
 *  into the running state.
 *
 * Parameters:
 *  pBindCtx        LPBINDCTX of the bind context to use.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIRunnableObject::Run(LPBINDCTX pBindCtx)
    {
    /*
     * Create the dialog but not show it.  This function is called
     * from OleRun which is called from within OleCreate.  So when
     * a container creates this object, we'll create the dialog
     * which is a basis for the object's extents (as returned from
     * IViewObject2::GetExtent.  Our IOleObject::DoVerb will later
     * note that the dialog is already created and will just make
     * it visible and not create it anew.
     *
     * Note that the dialog is unowned--it acts like an overlapped
     * window.
     */

    if (NULL!=m_pObj->m_hDlg)
        return NOERROR;

    //This stores the dialog handle in m_pObj.
    CreateDialogParam(m_pObj->Instance()
        , MAKEINTRESOURCE(IDD_EDITDIALOG), NULL, PolyDlgProc
        , (LPARAM)m_pObj);

    if (NULL==m_pObj->m_hDlg)
        return ResultFromScode(E_OUTOFMEMORY);

    /*
     * Registration of the object as running happens in
     * IOleObject::SetClientSite since we need a moniker from
     * the container and we don't have a client site pointer yet.
     */
    return NOERROR;
    }

/*
 * CImpIRunnableObject::IsRunning
 *
 * Purpose:
 *  Answers whether an object is currently in the running state.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            Indicates the running state of the object.
 */

STDMETHODIMP_(BOOL) CImpIRunnableObject::IsRunning(void)
    {
    return (NULL!=m_pObj->m_hDlg);
    }

/*
 * CImpIRunnableObject::LockRunning
 *
 * Purpose:
 *  Locks an already running object into the running state or unlocks
 *  it from such a state.
 *
 * Parameters:
 *  fLock               BOOL indicating lock (TRUE) or unlock
 *                      (FALSE)
 *  fLastUnlockCloses   BOOL indicating if the last call to this
 *                      function with fLock==FALSE closes the
 *                      object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIRunnableObject::LockRunning(BOOL fLock
    , BOOL fLastUnlockCloses)
    {
    //Calling CoLockObjectExternal is all we have to do here.
    return CoLockObjectExternal(this, fLock, fLastUnlockCloses);
    }

/*
 * CImpIRunnableObject::SetContainedObject
 *
 * Purpose:
 *  Informs the object (embedded object) that it is inside a
 *  compound document container.
 *
 * Parameters:
 *  fContained      BOOL indicating if the object is now contained.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIRunnableObject::SetContainedObject(BOOL fContained)
    {
    //We can ignore this.
    return NOERROR;
    }


IVIEWOBJ.CPP

/*
 * IVIEWOBJ.CPP
 * Polyline Component Chapter 23
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIViewObject::CImpIViewObject
 * CImpIViewObject::~CImpIViewObject
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIViewObject::CImpIViewObject(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIViewObject::~CImpIViewObject(void)
    {
    return;
    }

/*
 * CImpIViewObject::QueryInterface
 * CImpIViewObject::AddRef
 * CImpIViewObject::Release
 *
 * Purpose:
 *  IUnknown members for CImpIViewObject object.
 */

STDMETHODIMP CImpIViewObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIViewObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIViewObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIViewObject::Draw
 *
 * Purpose:
 *  Draws the object on the given hDC specifically for the requested
 *  aspect, device, and within the appropriate bounds.
 *
 * Parameters:
 *  dwAspect        DWORD aspect to draw.
 *  lindex          LONG index of the piece to draw.
 *  pvAspect        LPVOID for extra information, always NULL.
 *  ptd             DVTARGETDEVICE * containing device
 *                  information.
 *  hICDev          HDC containing the IC for the device.
 *  hDC             HDC on which to draw.
 *  pRectBounds     LPCRECTL describing the rectangle in which
 *                  to draw.
 *  pRectWBounds    LPCRECTL describing the placement rectangle
 *                  if part of what you draw is another metafile.
 *  pfnContinue     Function to call periodically during
 *                  long repaints.
 *  dwContinue      DWORD extra information to pass to the
 *                  pfnContinue.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::Draw(DWORD dwAspect, LONG lindex
    , LPVOID pvAspect, DVTARGETDEVICE *ptd, HDC hICDev
    , HDC hDC, LPCRECTL pRectBounds, LPCRECTL pRectWBounds
    , BOOL (CALLBACK *pfnContinue) (DWORD), DWORD dwContinue)
    {
    RECT            rc;
    POLYLINEDATA    pl;
    PPOLYLINEDATA   ppl=&m_pObj->m_pl;

    RECTFROMRECTL(rc, *pRectBounds);

    //Delegate iconic and printed representations.
    if (!((DVASPECT_CONTENT | DVASPECT_THUMBNAIL) & dwAspect))
        {
        return m_pObj->m_pDefIViewObject->Draw(dwAspect, lindex
            , pvAspect, ptd, hICDev, hDC, pRectBounds, pRectWBounds
            , pfnContinue, dwContinue);
        }

    /*
     * If we're asked to draw a frozen aspect, use the data from
     * a copy we made in IViewObject::Freeze.  Otherwise use the
     * current data.
     */
    if (dwAspect & m_pObj->m_dwFrozenAspects)
        {
        //Point to the data to actually use.
        if (DVASPECT_CONTENT==dwAspect)
            ppl=&m_pObj->m_plContent;
        else
            ppl=&m_pObj->m_plThumbnail;
        }

    //Make a copy so we can modify it (and not worry about threads)
    memcpy(&pl, ppl, CBPOLYLINEDATA);

    /*
     * If we're going to a printer, check if it's color capable.
     * if not, then use black on white for this figure.
     */
    if (NULL!=hICDev)
        {
        if (GetDeviceCaps(hICDev, NUMCOLORS) <= 2)
            {
            pl.rgbBackground=RGB(255, 255, 255);
            pl.rgbLine=RGB(0, 0, 0);
            }
        }

    m_pObj->Draw(hDC, FALSE, TRUE, &rc, &pl);
    return NOERROR;
    }

/*
 * CImpIViewObject::GetColorSet
 *
 * Purpose:
 *  Retrieves the color palette used by the object.
 *
 * Parameters:
 *  dwAspect        DWORD aspect of interest.
 *  lindex          LONG piece of interest.
 *  pvAspect        LPVOID with extra information, always NULL.
 *  ptd             DVTARGETDEVICE * containing device info.
 *  hICDev          HDC containing the IC for the device.
 *  ppColorSet      LPLOGPALETTE * into which to return the
 *                  pointer to the palette in this color set.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::GetColorSet(DWORD dwDrawAspect
    , LONG lindex, LPVOID pvAspect, DVTARGETDEVICE *ptd
    , HDC hICDev, LPLOGPALETTE *ppColorSet)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIViewObject::Freeze
 *
 * Purpose:
 *  Freezes the view of a particular aspect such that data
 *  changes do not affect the view.
 *
 * Parameters:
 *  dwAspect        DWORD aspect to freeze.
 *  lindex          LONG piece index under consideration.
 *  pvAspect        LPVOID for further information, always NULL.
 *  pdwFreeze       LPDWORD in which to return the key.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::Freeze(DWORD dwAspect, LONG lindex
    , LPVOID pvAspect, LPDWORD pdwFreeze)
    {
    //Delegate anything for ICON or DOCPRINT aspects
    if (!((DVASPECT_CONTENT | DVASPECT_THUMBNAIL) & dwAspect))
        {
        return m_pObj->m_pDefIViewObject->Freeze(dwAspect, lindex
            , pvAspect, pdwFreeze);
        }

    if (dwAspect & m_pObj->m_dwFrozenAspects)
        {
        *pdwFreeze=dwAspect + FREEZE_KEY_OFFSET;
        return ResultFromScode(VIEW_S_ALREADY_FROZEN);
        }

    m_pObj->m_dwFrozenAspects |= dwAspect;

    /*
     * For whatever aspects become frozen, make a copy of the data.
     * Later when drawing, if such a frozen aspect is requested,
     * we'll draw from this data rather than from our current data.
     */
    if (DVASPECT_CONTENT & dwAspect)
        {
        memcpy(&m_pObj->m_plContent, &m_pObj->m_pl
            , CBPOLYLINEDATA);
        }

    if (DVASPECT_THUMBNAIL & dwAspect)
        {
        memcpy(&m_pObj->m_plThumbnail, &m_pObj->m_pl
            , CBPOLYLINEDATA);
        }

    if (NULL!=pdwFreeze)
        *pdwFreeze=dwAspect + FREEZE_KEY_OFFSET;

    return NOERROR;
    }

/*
 * CImpIViewObject::Unfreeze
 *
 * Purpose:
 *  Thaws an aspect frozen in ::Freeze.  We expect that a container
 *  will redraw us after freezing if necessary, so we don't send
 *  any sort of notification here.
 *
 * Parameters:
 *  dwFreeze        DWORD key returned from ::Freeze.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::Unfreeze(DWORD dwFreeze)
    {
    DWORD       dwAspect=dwFreeze - FREEZE_KEY_OFFSET;

    //Delegate anything for ICON or DOCPRINT aspects
    if (!((DVASPECT_CONTENT | DVASPECT_THUMBNAIL) & dwAspect))
        m_pObj->m_pDefIViewObject->Unfreeze(dwFreeze);

    //The aspect to unfreeze is in the key.
    m_pObj->m_dwFrozenAspects &= ~(dwAspect);

    /*
     * Since we always kept our current data up to date, we don't
     * have to do anything thing here like requesting data again.
     * Because we removed dwAspect from m_dwFrozenAspects, Draw
     * will again use the current data.
     */

    return NOERROR;
    }

/*
 * CImpIViewObject::SetAdvise
 *
 * Purpose:
 *  Provides an advise sink to the view object enabling
 *  notifications for a specific aspect.
 *
 * Parameters:
 *  dwAspects       DWORD describing the aspects of interest.
 *  dwAdvf          DWORD containing advise flags.
 *  pIAdviseSink    LPADVISESINK to notify.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::SetAdvise(DWORD dwAspects, DWORD dwAdvf
    , LPADVISESINK pIAdviseSink)
    {
    //Pass anything with DVASPECT_ICON or _DOCPRINT to the handler.
    if (!((DVASPECT_CONTENT | DVASPECT_THUMBNAIL) & dwAspects))
        {
        m_pObj->m_pDefIViewObject->SetAdvise(dwAspects, dwAdvf
            , pIAdviseSink);
        }

    //We continue because dwAspects may have more than one in it.
    if (NULL!=m_pObj->m_pIAdviseSink)
        m_pObj->m_pIAdviseSink->Release();

    m_pObj->m_pIAdviseSink=pIAdviseSink;
    m_pObj->m_dwAdviseAspects=dwAspects;
    m_pObj->m_dwAdviseFlags=dwAdvf;

    if (NULL!=m_pObj->m_pIAdviseSink)
        m_pObj->m_pIAdviseSink->AddRef();

    return NOERROR;
    }

/*
 * CImpIViewObject::GetAdvise
 *
 * Purpose:
 *  Returns the last known IAdviseSink seen by ::SetAdvise.
 *
 * Parameters:
 *  pdwAspects      LPDWORD in which to store the last
 *                  requested aspects.
 *  pdwAdvf         LPDWORD in which to store the last
 *                  requested flags.
 *  ppIAdvSink      LPADVISESINK * in which to store the
 *                  IAdviseSink.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::GetAdvise(LPDWORD pdwAspects
    , LPDWORD pdwAdvf, LPADVISESINK *ppAdvSink)
    {
    if (NULL!=ppAdvSink)
        {
        *ppAdvSink=m_pObj->m_pIAdviseSink;
        m_pObj->m_pIAdviseSink->AddRef();
        }

    if (NULL!=pdwAspects)
        *pdwAspects=m_pObj->m_dwAdviseAspects;

    if (NULL!=pdwAdvf)
        *pdwAdvf=m_pObj->m_dwAdviseFlags;

    return NOERROR;
    }

/*
 * CImpIViewObject::GetExtent
 *
 * Purpose:
 *  Retrieves the extents of the object's display.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect of interest.
 *  lindex          LONG index of the piece of interest.
 *  ptd             DVTARGETDEVICE * with device information.
 *  pszl            LPSIZEL to the structure in which to return
 *                  the extents.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::GetExtent(DWORD dwAspect, LONG lindex
    , DVTARGETDEVICE *ptd, LPSIZEL pszl)
    {
    RECT            rc;

    if (!(DVASPECT_CONTENT & dwAspect))
        {
        return m_pObj->m_pDefIViewObject->GetExtent(dwAspect
            , lindex, ptd, pszl);
        }

    m_pObj->m_pImpIPolyline->RectGet(&rc);
    m_pObj->RectConvertMappings(&rc, FALSE);

    pszl->cx=rc.right-rc.left;
    pszl->cy=rc.bottom-rc.top;

    return NOERROR;
    }


POLYLINE.CPP

/*
 * POLYLINE.CPP
 * Polyline Component Chapter 23
 *
 * Implementation of the CPolyline class that we expose as a
 * component object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CPolyline:CPolyline
 * CPolyline::~CPolyline
 *
 * Constructor Parameters:
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 *  pfnDestroy      PFNDESTROYED to call when an object is
 *                  destroyed.
 *  hInst           HINSTANCE of the application we're in.
 */

CPolyline::CPolyline(LPUNKNOWN pUnkOuter, PFNDESTROYED pfnDestroy
    , HINSTANCE hInst)
    {
    m_hWnd=NULL;
    m_hInst=hInst;

    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pfnDestroy=pfnDestroy;
    m_fDirty=FALSE;

    m_pImpIPolyline=NULL;
    m_pImpIConnPtCont=NULL;

    m_pAdv=NULL;
    m_pConnPt=NULL;

    m_pST  =NULL;
    m_cf   =0;
    m_clsID=CLSID_Polyline19;

    m_pIStorage=NULL;
    m_pIStream =NULL;

    m_pImpIPersistStorage=NULL;
    m_pImpIPersistStreamInit=NULL;

    m_pImpIDataObject   =NULL;
    m_pIDataAdviseHolder=NULL;

    m_pDefIUnknown       =NULL;
    m_pDefIDataObject    =NULL;
    m_pDefIViewObject    =NULL;
    m_pDefIPersistStorage=NULL;

    m_pIOleAdviseHolder =NULL;
    m_pImpIOleObject    =NULL;
    m_pIOleClientSite   =NULL;
    m_pImpIViewObject   =NULL;
    m_pIAdviseSink      =NULL;

    m_dwFrozenAspects   =0;
    m_dwAdviseAspects   =0;
    m_dwAdviseFlags     =0;

    m_pImpIRunnableObject=NULL;
    m_hDlg=NULL;

    m_pImpIExternalConnection=NULL;
    m_fLockContainer=FALSE;
    m_dwRegROT=0L;

    //CHAPTER23MOD
    m_pIOleIPSite=NULL;
    m_pIOleIPFrame=NULL;
    m_pIOleIPUIWindow=NULL;
    m_pImpIOleIPObject=NULL;
    m_pImpIOleIPActiveObject=NULL;
    m_hMenuShared=NULL;
    m_hOLEMenu=NULL;
    m_pHW=NULL;
    m_fAllowInPlace=TRUE;
    m_fUIActive=FALSE;

    m_fContainerKnowsInsideOut=FALSE;
    //End CHAPTER23MOD

    return;
    }

CPolyline::~CPolyline(void)
    {
    LPUNKNOWN       pIUnknown=this;

    if (NULL!=m_pUnkOuter)
        pIUnknown=m_pUnkOuter;

    if (NULL!=m_pST)
        delete m_pST;

    if (NULL!=m_hDlg)
        DestroyWindow(m_hDlg);

    /*
     * In aggregation, release cached pointers but
     * AddRef the controlling unknown first.  The
     * extra reference count protects from reentrancy.
     */

    m_cRef++;

    pIUnknown->AddRef();
    pIUnknown->AddRef();
    pIUnknown->AddRef();

    ReleaseInterface(m_pDefIViewObject);
    ReleaseInterface(m_pDefIDataObject);
    ReleaseInterface(m_pDefIPersistStorage);

    m_cRef--;

    //Cached pointer rules do not apply to IUnknown
    ReleaseInterface(m_pDefIUnknown);

    ReleaseInterface(m_pIAdviseSink);
    ReleaseInterface(m_pIOleClientSite);
    ReleaseInterface(m_pIOleAdviseHolder);

    DeleteInterfaceImp(m_pImpIOleObject);
    DeleteInterfaceImp(m_pImpIViewObject);
    DeleteInterfaceImp(m_pImpIRunnableObject);

    //CHAPTER23MOD
    //Other in-place interfaces released in deactivation.
    DeleteInterfaceImp(m_pImpIOleIPObject);
    DeleteInterfaceImp(m_pImpIOleIPActiveObject);
    //End CHAPTER23MOD

    //Anything we might have registered in IRunnableObject::Run
    INOLE_RevokeAsRunning(&m_dwRegROT);

    DeleteInterfaceImp(m_pImpIExternalConnection);
    ReleaseInterface(m_pIDataAdviseHolder);
    DeleteInterfaceImp(m_pImpIDataObject);

    DeleteInterfaceImp(m_pImpIPersistStreamInit);
    DeleteInterfaceImp(m_pImpIPersistStorage);
    ReleaseInterface(m_pIStream);
    ReleaseInterface(m_pIStorage);

    DeleteInterfaceImp(m_pImpIConnPtCont);
    DeleteInterfaceImp(m_pImpIPolyline);

    ReleaseInterface(m_pAdv);
    ReleaseInterface(m_pConnPt);

    return;
    }

/*
 * CPolyline::Init
 *
 * Purpose:
 *  Performs any intiailization of a CPolyline that's prone to
 *  failure that we also use internally before exposing the
 *  object outside this DLL.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CPolyline::Init(void)
    {
    LPUNKNOWN       pIUnknown=this;
    HRESULT         hr;

    if (NULL!=m_pUnkOuter)
        pIUnknown=m_pUnkOuter;

    m_pST=new CStringTable(m_hInst);

    if (!m_pST->Init(IDS_POLYLINEMIN, IDS_POLYLINEMAX))
        return FALSE;

    m_cf=RegisterClipboardFormat(SZPOLYLINECLIPFORMAT);

    m_pImpIPersistStorage=new CImpIPersistStorage(this, pIUnknown);

    if (NULL==m_pImpIPersistStorage)
        return FALSE;

    m_pImpIPersistStreamInit=new CImpIPersistStreamInit(this
        , pIUnknown);

    if (NULL==m_pImpIPersistStreamInit)
        return FALSE;

    m_pImpIPolyline=new CImpIPolyline(this, pIUnknown);

    if (NULL==m_pImpIPolyline)
        return FALSE;

    m_pImpIConnPtCont=new CImpIConnPtCont(this, pIUnknown);

    if (NULL==m_pImpIConnPtCont)
        return FALSE;

    m_pConnPt=new CConnectionPoint(this);

    if (NULL==m_pConnPt)
        return FALSE;

    m_pConnPt->AddRef();    //Reversed in destructor

    m_pImpIDataObject=new CImpIDataObject(this, pIUnknown);

    if (NULL==m_pImpIDataObject)
        return FALSE;

    m_pImpIOleObject=new CImpIOleObject(this, pIUnknown);

    if (NULL==m_pImpIOleObject)
        return FALSE;

    m_pImpIViewObject=new CImpIViewObject(this, pIUnknown);

    if (NULL==m_pImpIViewObject)
        return FALSE;

    m_pImpIRunnableObject=new CImpIRunnableObject(this, pIUnknown);

    if (NULL==m_pImpIRunnableObject)
        return FALSE;

    m_pImpIExternalConnection=new CImpIExternalConnection(this
        , pIUnknown);

    if (NULL==m_pImpIExternalConnection)
        return FALSE;

    //CHAPTER23MOD
    m_pImpIOleIPObject=new CImpIOleInPlaceObject(this, pIUnknown);

    if (NULL==m_pImpIOleIPObject)
        return FALSE;

    m_pImpIOleIPActiveObject=new CImpIOleInPlaceActiveObject(this
        , pIUnknown);

    if (NULL==m_pImpIOleIPActiveObject)
        return FALSE;
    //End CHAPTER23MOD

    /*
     * We're sitting at ref count 0 and the next call will AddRef a
     * few times and Release a few times.  This insures we don't
     * delete ourselves prematurely.
     */
    m_cRef++;

    //Aggregate OLE's cache for IOleCache* interfaces.
    hr=CreateDataCache(pIUnknown, CLSID_Polyline19
        , IID_IUnknown, (PPVOID)&m_pDefIUnknown);

    if (FAILED(hr))
        return FALSE;

    /*
     * NOTE:  The spec specifically states that any interfaces
     * besides IUnknown that we obtain on an aggregated object
     * should be Released immediately after we QueryInterface for
     * them because the QueryInterface will AddRef us, and since
     * we would not release these interfaces until we were
     * destroyed, we'd never go away because we'd never get a zero
     * ref count.
     */

    //Now try to get other interfaces to which we delegate
    hr=m_pDefIUnknown->QueryInterface(IID_IViewObject2
        , (PPVOID)&m_pDefIViewObject);

    if (FAILED(hr))
        return FALSE;

    pIUnknown->Release();

    hr=m_pDefIUnknown->QueryInterface(IID_IDataObject
        , (PPVOID)&m_pDefIDataObject);

    if (FAILED(hr))
        return FALSE;

    pIUnknown->Release();

    hr=m_pDefIUnknown->QueryInterface(IID_IPersistStorage
        , (PPVOID)&m_pDefIPersistStorage);

    if (FAILED(hr))
        return FALSE;

    pIUnknown->Release();

    m_cRef--;
    m_pImpIPolyline->New();

    return TRUE;
    }

/*
 * CPolyline::QueryInterface
 * CPolyline::AddRef
 * CPolyline::Release
 *
 * Purpose:
 *  IUnknown members for CPolyline object.
 */

STDMETHODIMP CPolyline::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IConnectionPointContainer==riid)
        *ppv=m_pImpIConnPtCont;

    if (IID_IPolyline10==riid)
        *ppv=m_pImpIPolyline;

    if (IID_IPersistStorage==riid)
        *ppv=m_pImpIPersistStorage;

    if (IID_IPersist==riid || IID_IPersistStream==riid
        || IID_IPersistStreamInit==riid)
        *ppv=m_pImpIPersistStreamInit;

    if (IID_IDataObject==riid)
        *ppv=m_pImpIDataObject;

    if (IID_IOleObject==riid)
        *ppv=m_pImpIOleObject;

    if (IID_IViewObject==riid || IID_IViewObject2==riid)
        *ppv=m_pImpIViewObject;

    if (IID_IRunnableObject==riid)
        *ppv=m_pImpIRunnableObject;

    if (IID_IExternalConnection==riid)
        *ppv=m_pImpIExternalConnection;

    //CHAPTER23MOD
    //IOleWindow will be the InPlaceObject
    if (IID_IOleWindow==riid || IID_IOleInPlaceObject==riid)
        *ppv=m_pImpIOleIPObject;
    //End CHAPTER23MOD

    //Use the default handler's cache.
    if (IID_IOleCache==riid || IID_IOleCache2==riid)
        return m_pDefIUnknown->QueryInterface(riid, ppv);

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CPolyline::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPolyline::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0L;
    }

/*
 * CPolyline::RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device (MM_TEXT) or
 *  HIMETRIC to the other.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  fToDevice       BOOL TRUE to convert from HIMETRIC to device,
 *                  FALSE to convert device to HIMETRIC.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectConvertMappings(LPRECT pRect, BOOL fToDevice)
    {
    HDC      hDC;
    int      iLpx, iLpy;

    if (NULL==pRect)
        return;

    hDC=GetDC(NULL);
    iLpx=GetDeviceCaps(hDC, LOGPIXELSX);
    iLpy=GetDeviceCaps(hDC, LOGPIXELSY);
    ReleaseDC(NULL, hDC);

    if (fToDevice)
        {
        pRect->left=MulDiv(iLpx, pRect->left, HIMETRIC_PER_INCH);
        pRect->top =MulDiv(iLpy, pRect->top , HIMETRIC_PER_INCH);

        pRect->right =MulDiv(iLpx, pRect->right, HIMETRIC_PER_INCH);
        pRect->bottom=MulDiv(iLpy, pRect->bottom,HIMETRIC_PER_INCH);
        }
    else
        {
        pRect->left=MulDiv(pRect->left, HIMETRIC_PER_INCH, iLpx);
        pRect->top =MulDiv(pRect->top , HIMETRIC_PER_INCH, iLpy);

        pRect->right =MulDiv(pRect->right, HIMETRIC_PER_INCH, iLpx);
        pRect->bottom=MulDiv(pRect->bottom,HIMETRIC_PER_INCH, iLpy);
        }

    return;
    }

/*
 * CPolyline::DataSet
 *
 * Purpose:
 *  Sets the current data in this Polyline to a given structure.
 *
 * Parameters:
 *  pplIn           PPOLYLINEDATA to initialize to.
 *  fSizeToData     BOOL indicating if we're to size to the data
 *                  or scale it.
 *  fNotify         BOOL indicating if we're to send an advise
 *                  on this change.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::DataSet(PPOLYLINEDATA pplIn
    , BOOL fSizeToData, BOOL fNotify)
    {
    RECT            rc;

    /*
     * Copy the structure in pplIn and repaint to reflect the
     * new point set.  Note that unlike the RectSet message, we
     * do no scaling, assuming that the rect in the structure
     * is appropriate for the data.
     */

    if (NULL==pplIn)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    m_pl=*pplIn;
    m_fDirty=TRUE;

    /*
     * If we're scaling the window to fit the data, then use
     * RectSet passing our current rectangle as the new one.
     * That makes sure that the data won't change but that the
     * window is resized.
     */

    if (fSizeToData)
        {
        POINT       pt;

        /*
         * Get our offset in the parent window so we can RectSet
         * to the right place since RectSet expects rectangle in
         * parent coordinates and we get it in client coordinates.
         */
        if (NULL!=m_hWnd)
            {
            GetWindowRect(m_hWnd, &rc);
            pt.x=rc.left;
            pt.y=rc.top;
            ScreenToClient(GetParent(m_hWnd), &pt);
            RECTSTORECT(m_pl.rc, rc);
            OffsetRect(&rc, pt.x, pt.y);

            //This will also cause a repaint.
            m_pImpIPolyline->RectSet(&rc, fNotify);
            }
        }
    else
        {
        if (NULL!=m_hWnd)
            {
            //Make sure we're updated.
            InvalidateRect(m_hWnd, NULL, TRUE);
            UpdateWindow(m_hWnd);
            }
        }

    SendAdvise(OBJECTCODE_DATACHANGED);
    return NOERROR;
    }

/*
 * CPolyline::DataGet
 *
 * Purpose:
 *  Retrieves the Polyline's current data.
 *
 * Parameters:
 *  pplIn           PPOLYLINEDATA into which we copy the data.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::DataGet(PPOLYLINEDATA pplIn)
    {
    if (NULL==pplIn)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    *pplIn=m_pl;
    return NOERROR;
    }

/*
 * CPolyline::RenderNative
 *
 * Purpose:
 *  Retrieves the Polyline's data in a global memory handle.
 *
 * Parameters:
 *  phMem           HGLOBAL * in which to store the handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::RenderNative(HGLOBAL *phMem)
    {
    HGLOBAL         hMem;
    PPOLYLINEDATA   ppl;
    HRESULT         hr=ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    if (NULL==phMem)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, CBPOLYLINEDATA);

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        hr=DataGet(ppl);

        GlobalUnlock(hMem);

        if (FAILED(hr))
            {
            GlobalFree(hMem);
            hMem=NULL;
            }
        }

    *phMem=hMem;
    return hr;
    }

/*
 * CPolyline::RenderBitmap
 *
 * Purpose:
 *  Creates a bitmap image of the current Polyline.
 *
 * Parameters:
 *  phBmp           HBITMAP * in which to return the bitmap.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::RenderBitmap(HBITMAP *phBmp)
    {
    HDC             hDC;
    HDC             hMemDC;
    HBITMAP         hBmp;
    RECT            rc;
    HGDIOBJ         hObj;

    if (NULL==phBmp)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    //Render a bitmap the size of the current rectangle.
    hDC=GetDC(m_hWnd);
    hMemDC=CreateCompatibleDC(hDC);

    GetClientRect(m_hWnd, &rc);
    hBmp=CreateCompatibleBitmap(hDC, rc.right, rc.bottom);

    if (NULL!=hBmp)
        {
        //Draw the POLYLINEDATA into the bitmap.
        hObj=SelectObject(hMemDC, hBmp);
        Draw(hMemDC, FALSE, TRUE, &rc, NULL);
        SelectObject(hMemDC, hObj);
        }

    DeleteDC(hMemDC);
    ReleaseDC(m_hWnd, hDC);

    *phBmp=hBmp;
    return NOERROR;
    }

//RenderMetafile not necessary--now part of RenderMetafilePict.

/*
 * CPolyline::RenderMetafilePict
 *
 * Purpose:
 *  Renders the current Polyline into a METAFILEPICT structure in
 *  global memory.
 *
 * Parameters:
 *  phMem           HGLOBAL * in which to return the
 *                  METAFILEPICT.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::RenderMetafilePict(HGLOBAL *phMem)
    {
    HGLOBAL         hMem;
    HMETAFILE       hMF;
    LPMETAFILEPICT  pMF;
    RECT            rc;
    HDC             hDC;

    if (NULL==phMem)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    //Create a memory metafile and return its handle.
    hDC=(HDC)CreateMetaFile(NULL);

    if (NULL==hDC)
        return ResultFromScode(STG_E_MEDIUMFULL);

    SetMapMode(hDC, MM_ANISOTROPIC);
    GetClientRect(m_hWnd, &rc);
    SetWindowOrgEx(hDC, 0, 0, NULL);
    SetWindowExtEx(hDC, rc.right, rc.bottom, NULL);

    Draw(hDC, TRUE, TRUE, &rc, NULL);
    hMF=CloseMetaFile(hDC);

    if (NULL==hMF)
        return ResultFromScode(STG_E_MEDIUMFULL);

    //Allocate the METAFILEPICT structure.
    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
        , sizeof(METAFILEPICT));

    if (NULL==hMem)
        {
        DeleteMetaFile(hMF);
        return ResultFromScode(E_FAIL);
        }

    /*
     * Global lock only fails in PMODE if the selector is invalid
     * (like it was discarded) or references a 0 length segment,
     * neither of which can happen here.
     */
    pMF=(LPMETAFILEPICT)GlobalLock(hMem);

    pMF->hMF=hMF;
    pMF->mm=MM_ANISOTROPIC;

    //Insert the extents in MM_HIMETRIC units.
    GetClientRect(m_hWnd, &rc);
    RectConvertMappings(&rc, FALSE);
    pMF->xExt=rc.right;
    pMF->yExt=rc.bottom;

    GlobalUnlock(hMem);

    *phMem=hMem;
    return NOERROR;
    }

/*
 * CPolyline::Instance
 *
 * Purpose:
 *  Provides access to the module instance.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HINSTANCE       The instance handle of the module.
 */

HINSTANCE CPolyline::Instance(void)
    {
    return m_hInst;
    }

/*
 * CPolyline::String
 *
 * Purpose:
 *  Provides string lookup in the Polyline string table.
 *
 * Parameters:
 *  uID             UINT of the string to return.
 *
 * Return Value:
 *  LPTSTR          Pointer to the string.
 */

LPTSTR CPolyline::String(UINT uID)
    {
    return PSZ(uID);
    }

/*
 * CPolyline::SendAdvise
 *
 * Purpose:
 *  Calls the appropriate IOleClientSite or IAdviseSink member
 *  function for various events such as closure, renaming, etc.
 *
 * Parameters:
 *  uCode           UINT OBJECTCODE_* identifying the notification.
 *
 * Return Value:
 *  None
 */

void CPolyline::SendAdvise(UINT uCode)
    {
    DWORD       dwAspect=DVASPECT_CONTENT | DVASPECT_THUMBNAIL;

    switch (uCode)
        {
        case OBJECTCODE_SAVED:
            if (NULL!=m_pIOleAdviseHolder)
                m_pIOleAdviseHolder->SendOnSave();
            break;

        case OBJECTCODE_CLOSED:
            if (NULL!=m_pIOleAdviseHolder)
                m_pIOleAdviseHolder->SendOnClose();

            break;

        case OBJECTCODE_RENAMED:
            //Call IOleAdviseHolder::SendOnRename (later)
            break;

        case OBJECTCODE_SAVEOBJECT:
            if (m_fDirty && NULL!=m_pIOleClientSite)
                m_pIOleClientSite->SaveObject();

            m_fDirty=FALSE;
            break;

        case OBJECTCODE_DATACHANGED:
            m_fDirty=TRUE;

            //No flags are necessary here.
            if (NULL!=m_pIDataAdviseHolder)
                {
                m_pIDataAdviseHolder->SendOnDataChange
                    (m_pImpIDataObject, 0, 0);
                }

            if (NULL!=m_pIAdviseSink
                & (dwAspect & m_dwAdviseAspects))
                {
                m_pIAdviseSink->OnViewChange(dwAspect
                    & m_dwAdviseAspects, 0);
                }

            break;

        case OBJECTCODE_SHOWWINDOW:
            if (NULL!=m_pIOleClientSite)
                m_pIOleClientSite->OnShowWindow(TRUE);

            break;

        case OBJECTCODE_HIDEWINDOW:
            if (NULL!=m_pIOleClientSite)
                m_pIOleClientSite->OnShowWindow(FALSE);

            break;

        case OBJECTCODE_SHOWOBJECT:
            if (NULL!=m_pIOleClientSite)
                m_pIOleClientSite->ShowObject();

            break;
        }

    return;
    }

//CHAPTER23MOD

/*
 * CPolyline::InPlaceActivate
 *
 * Purpose:
 *  Goes through all the steps of activating the Polyline as an
 *  in-place object.
 *
 * Parameters:
 *  pActiveSite     LPOLECLIENTSITE of the active site we show in.
 *  fIncludeUI      BOOL controls whether we call UIActivate too.
 *
 * Return Value:
 *  HRESULT         Whatever error code is appropriate.
 */

HRESULT CPolyline::InPlaceActivate(LPOLECLIENTSITE pActiveSite
    , BOOL fIncludeUI)
    {
    HRESULT                 hr;
    HWND                    hWnd;
    HWND                    hWndHW;
    RECT                    rcPos;
    RECT                    rcClip;
    OLEINPLACEFRAMEINFO     frameInfo;

    if (NULL==pActiveSite)
        return ResultFromScode(E_INVALIDARG);

    if (NULL!=m_pIOleIPSite)
        {
        if (fIncludeUI)
            UIActivate();

        return NOERROR;
        }

    //1.  Initialization, obtaining interfaces, OnInPlaceActivate.
    hr=pActiveSite->QueryInterface(IID_IOleInPlaceSite
        , (PPVOID)&m_pIOleIPSite);

    if (FAILED(hr))
        return hr;

    hr=m_pIOleIPSite->CanInPlaceActivate();

    if (NOERROR!=hr)
        {
        m_pIOleIPSite->Release();
        m_pIOleIPSite=NULL;
        return ResultFromScode(E_FAIL);
        }

    m_pIOleIPSite->OnInPlaceActivate();

    //2.  Get the window context and create a window.
    m_pIOleIPSite->GetWindow(&hWnd);
    frameInfo.cb=sizeof(OLEINPLACEFRAMEINFO);

    m_pIOleIPSite->GetWindowContext(&m_pIOleIPFrame
        , &m_pIOleIPUIWindow, &rcPos, &rcClip, &frameInfo);

    /*
     * Create the hatch window after we get a parent window.  We
     * could not create the hatch window sooner because had nothing
     * to use for the parent.
     */
    m_pHW=new CHatchWin(m_hInst);

    if (NULL==m_pHW)
        {
        InPlaceDeactivate();
        return ResultFromScode(E_OUTOFMEMORY);
        }

    if (!m_pHW->Init(hWnd, ID_HATCHWINDOW, NULL))
        {
        InPlaceDeactivate();
        return ResultFromScode(E_OUTOFMEMORY);
        }

    //Make sure dialog is hidden
    if (NULL!=m_hDlg)
        {
        ShowWindow(m_hDlg, SW_HIDE);
        SendAdvise(OBJECTCODE_HIDEWINDOW);
        }

    //Move the hatch window to the container window.
    hWndHW=m_pHW->Window();
    SetParent(hWndHW, hWnd);

    //Move the Polyline window from the hidden dialog to hatch window
    m_pHW->HwndAssociateSet(m_hWnd);
    m_pHW->ChildSet(m_hWnd);
    m_pHW->RectsSet(&rcPos, &rcClip);   //Positions polyline

    ShowWindow(hWndHW, SW_SHOW);
    SendAdvise(OBJECTCODE_SHOWOBJECT);

    //3, 4, 5.  Do UI things: active object, shared menu, tools
    hr=NOERROR;

    if (fIncludeUI)
        hr=UIActivate();

    /*
     * Since we don't have an Undo while in-place, tell the continer
     * to free it's undo state immediately.
     */
    m_pIOleIPSite->DiscardUndoState();

    return hr;
    }

/*
 * CPolyline::InPlaceDeactivate
 *
 * Purpose:
 *  Reverses all the activation steps from InPlaceActivate.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolyline::InPlaceDeactivate(void)
    {
    UIDeactivate();

    if (NULL!=m_pHW)
        {
        RECT        rc;

        ShowWindow(m_pHW->Window(), SW_HIDE);

        //Reposition the polyline window in the dialog box.
        SetParent(m_hWnd, m_hDlg);
        CalcPolyRectInDialog(m_hDlg, &rc);

        SetWindowPos(m_hWnd, NULL, rc.left, rc.top
            , rc.right-rc.left, rc.bottom-rc.top, SWP_NOZORDER);

        m_pHW->ChildSet(NULL);

        delete m_pHW;
        m_pHW=NULL;
        }

    ReleaseInterface(m_pIOleIPFrame);
    ReleaseInterface(m_pIOleIPUIWindow)

    if (NULL!=m_pIOleIPSite)
        {
        m_pIOleIPSite->OnInPlaceDeactivate();
        ReleaseInterface(m_pIOleIPSite);
        }

    return;
    }

/*
 * CPolyline::UIActivate
 *
 * Purpose:
 *  Goes through all the steps of activating the user interface of
 *  Polyline as an in-place object.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or error code.
 */

HRESULT CPolyline::UIActivate(void)
    {
    //1.  Call IOleInPlaceSite::UIActivate
    if (NULL!=m_pIOleIPSite)
        m_pIOleIPSite->OnUIActivate();

    //2.  Set the active object
   #ifdef WIN32ANSI
    OLECHAR     szTemp[40];

    MultiByteToWideChar(CP_ACP, 0, PSZ(IDS_USERTYPE)
        , -1, szTemp, 40);
   #endif

    if (NULL!=m_pIOleIPFrame)
        {
        m_pIOleIPFrame->SetActiveObject(m_pImpIOleIPActiveObject
           #ifdef WIN32ANSI
            , szTemp);
           #else
            , PSZ(IDS_USERTYPE));
           #endif
        }

    if (NULL!=m_pIOleIPUIWindow)
        {
        m_pIOleIPUIWindow->SetActiveObject(m_pImpIOleIPActiveObject
           #ifdef WIN32ANSI
            , szTemp);
           #else
            , PSZ(IDS_USERTYPE));
           #endif
        }

    //3.  Critical for accelerators to work initially.
    SetFocus(m_pHW->Window());

    //4.  Negotiate border space.  Polyline doesn't need any.
    if (NULL!=m_pIOleIPFrame)
        m_pIOleIPFrame->SetBorderSpace(NULL);

    if (NULL!=m_pIOleIPUIWindow)
        m_pIOleIPUIWindow->SetBorderSpace(NULL);

    /*
     * 5.  Create the shared menu.  We don't have any, so tell
     *     the container to use its own menu.
     */

    if (NULL!=m_pIOleIPFrame)
        m_pIOleIPFrame->SetMenu(NULL, NULL, m_hWnd);

    m_fUIActive=TRUE;
    return NOERROR;
    }

/*
 * CPolyline::UIDeactivate
 *
 * Purpose:
 *  Reverses all the user interface activation steps from
 *  UIActivate.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolyline::UIDeactivate(void)
    {
    m_fUIActive=FALSE;

    //We don't have any shared menu or tools to clean up.

    //Clear out the active objects
    if (NULL!=m_pIOleIPFrame)
        m_pIOleIPFrame->SetActiveObject(NULL, NULL);

    if (NULL!=m_pIOleIPUIWindow)
        m_pIOleIPUIWindow->SetActiveObject(NULL, NULL);

    if (NULL!=m_pIOleIPSite)
        m_pIOleIPSite->OnUIDeactivate(FALSE);

    return;
    }
//End CHAPTER23MOD


POLYLINE.H

/*
 * POLYLINE.H
 * Polyline Component Chapter 23
 *
 * Definitions and function prototypes
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _POLYLINE_H_
#define _POLYLINE_H_

#define INC_CLASSLIB
#define INC_CONTROLS
//CHAPTER23MOD
#define INC_OLEUI
#define CHAPTER23
//CHAPTER23MOD
#include <inole.h>
#include <ipoly10.h>
#include "resource.h"

//Classname
#define SZCLASSPOLYLINE             TEXT("polyline")

//Stream Name that holds the data
#define SZSTREAM                    OLETEXT("CONTENTS")

#define SZPOLYFRAMETITLE            TEXT("Polyline Figure in %s")

//Magic number to add to aspects returned from IViewObject::Freeze
#define FREEZE_KEY_OFFSET           0x0723

#define HIMETRIC_PER_INCH           2540
#define CCHPATHMAX                  256

//Window extra bytes and offsets
#define CBPOLYLINEWNDEXTRA          (sizeof(LONG))
#define PLWL_STRUCTURE              0

//CHAPTER23MOD
#define ID_HATCHWINDOW              2000
//End CHAPTER23MOD

//DLLPOLY.CPP
int PASCAL LibMain(HINSTANCE, WORD, WORD, LPSTR);

//This class factory object creates Polyline objects.

class CPolylineClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;

    public:
        CPolylineClassFactory(void);
        ~CPolylineClassFactory(void);

        //IUnknown members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP         CreateInstance(LPUNKNOWN, REFIID
                                 , PPVOID);
        STDMETHODIMP         LockServer(BOOL);
    };

typedef CPolylineClassFactory *PCPolylineClassFactory;

//POLYWIN.CPP
LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM, LPARAM);

#ifdef WIN32
#define PROP_POINTER    TEXT("Pointer")
#else
#define PROP_SELECTOR   "Selector"
#define PROP_OFFSET     "Offset"
#endif

BOOL APIENTRY PolyDlgProc(HWND, UINT, WPARAM, LPARAM);

//CHAPTER23MOD
void CalcPolyRectInDialog(HWND, LPRECT);
//End CHAPTER23MOD

//Forward class references
class CImpIPolyline;
typedef class CImpIPolyline *PCImpIPolyline;

class CImpIConnPtCont;
typedef CImpIConnPtCont *PCImpIConnPtCont;

class CConnectionPoint;
typedef CConnectionPoint *PCConnectionPoint;

class CImpIPersistStorage;
typedef class CImpIPersistStorage *PCImpIPersistStorage;

class CImpIPersistStreamInit;
typedef class CImpIPersistStreamInit *PCImpIPersistStreamInit;

class CImpIDataObject;
typedef class CImpIDataObject *PCImpIDataObject;

class CImpIOleObject;
typedef class CImpIOleObject *PCImpIOleObject;

class CImpIViewObject;
typedef class CImpIViewObject *PCImpIViewObject;

class CImpIRunnableObject;
typedef class CImpIRunnableObject *PCImpIRunnableObject;

class CImpIExternalConnection;
typedef class CImpIExternalConnection *PCImpIExternalConnection;

//CHAPTER23MOD
class CImpIOleInPlaceObject;
typedef class CImpIOleInPlaceObject *PCImpIOleInPlaceObject;
class CImpIOleInPlaceActiveObject;
typedef class CImpIOleInPlaceActiveObject *PCImpIOleInPlaceActiveObject;
//End CHAPTER23MOD

//POLYLINE.CPP
class CPolyline : public IUnknown
    {
    friend LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM
        , LPARAM);

    friend class CImpIPolyline;
    friend class CImpIConnPtCont;
    friend class CConnectionPoint;
    friend class CImpIPersistStorage;
    friend class CImpIPersistStreamInit;
    friend class CImpIDataObject;

    friend BOOL APIENTRY PolyDlgProc(HWND, UINT, WPARAM, LPARAM);
    friend class CImpIOleObject;
    friend class CImpIViewObject;
    friend class CImpIRunnableObject;
    friend class CImpIExternalConnection;

    //CHAPTER23MOD
    friend class CImpIOleInPlaceObject;
    friend class CImpIOleInPlaceActiveObject;
    //End CHAPTER23MOD

    protected:
        HWND            m_hWnd;
        HINSTANCE       m_hInst;

        ULONG           m_cRef;         //Object reference count
        LPUNKNOWN       m_pUnkOuter;    //Controlling Unknown
        PFNDESTROYED    m_pfnDestroy;   //Function called on closure
        BOOL            m_fDirty;       //Have we changed?
        POLYLINEDATA    m_pl;           //Our actual data

        //Contained interfaces
        PCImpIPolyline          m_pImpIPolyline;
        PCImpIConnPtCont        m_pImpIConnPtCont;
        PCImpIPersistStorage    m_pImpIPersistStorage;
        PCImpIPersistStreamInit m_pImpIPersistStreamInit;
        PCImpIDataObject        m_pImpIDataObject;

        /*
         * Connected sink:  only one per object.  We get this
         * through our connection point.
         */
        IPolylineAdviseSink10  *m_pAdv;
        PCConnectionPoint       m_pConnPt;

        PCStringTable   m_pST;          //Object strings
        UINT            m_cf;           //Object clipboard format
        CLSID           m_clsID;        //Current CLSID

        //We have to hold these for IPersistStorage::Save
        LPSTORAGE       m_pIStorage;
        LPSTREAM        m_pIStream;

        LPDATAADVISEHOLDER  m_pIDataAdviseHolder;

        //These are default handler interfaces we use
        LPUNKNOWN           m_pDefIUnknown;
        LPVIEWOBJECT2       m_pDefIViewObject;
        LPPERSISTSTORAGE    m_pDefIPersistStorage;
        LPDATAOBJECT        m_pDefIDataObject;

        //Implemented and used interfaces
        PCImpIOleObject     m_pImpIOleObject;       //Implemented
        LPOLEADVISEHOLDER   m_pIOleAdviseHolder;    //Used

        LPOLECLIENTSITE     m_pIOleClientSite;      //Used

        PCImpIViewObject    m_pImpIViewObject;      //Implemented
        LPADVISESINK        m_pIAdviseSink;         //Used
        DWORD               m_dwFrozenAspects;      //Freeze
        DWORD               m_dwAdviseAspects;      //SetAdvise
        DWORD               m_dwAdviseFlags;        //SetAdvise

        POLYLINEDATA        m_plContent;            //For freezing
        POLYLINEDATA        m_plThumbnail;          //For freezing

        PCImpIRunnableObject m_pImpIRunnableObject; //Implemented
        HWND                m_hDlg;                 //Editing window

        PCImpIExternalConnection m_pImpIExternalConnection; //Implemented
        BOOL                     m_fLockContainer;
        DWORD                    m_dwRegROT;

        //CHAPTER23MOD
        LPOLEINPLACESITE            m_pIOleIPSite;
        LPOLEINPLACEFRAME           m_pIOleIPFrame;
        LPOLEINPLACEUIWINDOW        m_pIOleIPUIWindow;

        PCImpIOleInPlaceObject       m_pImpIOleIPObject;
        PCImpIOleInPlaceActiveObject m_pImpIOleIPActiveObject;

        HMENU                       m_hMenuShared;
        HOLEMENU                    m_hOLEMenu;

        PCHatchWin                  m_pHW;
        BOOL                        m_fAllowInPlace;
        BOOL                        m_fUIActive;
        BOOL                        m_fContainerKnowsInsideOut;
        //End CHAPTER23MOD

    protected:
        void      PointScale(LPRECT, LPPOINTS, BOOL);
        void      Draw(HDC, BOOL, BOOL, LPRECT, PPOLYLINEDATA);
        HINSTANCE Instance(void);
        LPTSTR    String(UINT);
        void      SendAdvise(UINT);
        void      RectConvertMappings(LPRECT, BOOL);

        /*
         * These members pulled from IPolyline now serve as a
         * central store for this functionality to be used from
         * other interfaces like IPersistStorage and IDataObject.
         * Other interfaces later may also use them.
         */
        STDMETHODIMP DataSet(PPOLYLINEDATA, BOOL, BOOL);
        STDMETHODIMP DataGet(PPOLYLINEDATA);

        STDMETHODIMP RenderNative(HGLOBAL *);
        STDMETHODIMP RenderBitmap(HBITMAP *);
        STDMETHODIMP RenderMetafilePict(HGLOBAL *);

    public:
        CPolyline(LPUNKNOWN, PFNDESTROYED, HINSTANCE);
        ~CPolyline(void);

        BOOL      Init(void);

        //Non-delegating object IUnknown
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //CHAPTER23MOD
        HRESULT  InPlaceActivate(LPOLECLIENTSITE, BOOL);
        void     InPlaceDeactivate(void);
        HRESULT  UIActivate(void);
        void     UIDeactivate(void);
        //End CHAPTER23MOD
    };

typedef CPolyline *PCPolyline;

//Codes for CPolyline::SendAdvise
//......Code.....................Method called in CPolyline::SendAdvise
#define OBJECTCODE_SAVED       0 //IOleAdviseHolder::SendOnSave
#define OBJECTCODE_CLOSED      1 //IOleAdviseHolder::SendOnClose
#define OBJECTCODE_RENAMED     2 //IOleAdviseHolder::SendOnRename
#define OBJECTCODE_SAVEOBJECT  3 //IOleClientSite::SaveObject
#define OBJECTCODE_DATACHANGED 4 //IDataAdviseHolder::SendOnDataChange
#define OBJECTCODE_SHOWWINDOW  5 //IOleClientSite::OnShowWindow(TRUE)
#define OBJECTCODE_HIDEWINDOW  6 //IOleClientSite::OnShowWindow(FALSE)
#define OBJECTCODE_SHOWOBJECT  7 //IOleClientSite::ShowObject

//Interface implementation contained in the Polyline.

class CImpIPolyline : public IPolyline10
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIPolyline(PCPolyline, LPUNKNOWN);
        ~CImpIPolyline(void);

        //IUnknown members.
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //Manipulation members:
        STDMETHODIMP Init(HWND, LPRECT, DWORD, UINT);
        STDMETHODIMP New(void);
        STDMETHODIMP Undo(void);
        STDMETHODIMP Window(HWND *);

        STDMETHODIMP RectGet(LPRECT);
        STDMETHODIMP SizeGet(LPRECT);
        STDMETHODIMP RectSet(LPRECT, BOOL);
        STDMETHODIMP SizeSet(LPRECT, BOOL);

        STDMETHODIMP ColorSet(UINT, COLORREF, COLORREF *);
        STDMETHODIMP ColorGet(UINT, COLORREF *);

        STDMETHODIMP LineStyleSet(UINT, UINT *);
        STDMETHODIMP LineStyleGet(UINT *);
    };

class CImpIConnPtCont : public IConnectionPointContainer
    {
    private:
        ULONG               m_cRef;      //Interface ref count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIConnPtCont(PCPolyline, LPUNKNOWN);
        ~CImpIConnPtCont(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(DWORD) AddRef(void);
        STDMETHODIMP_(DWORD) Release(void);

        //IConnectionPointContainer members
        STDMETHODIMP EnumConnectionPoints(IEnumConnectionPoints **);
    STDMETHODIMP FindConnectionPoint(REFIID, IConnectionPoint **);
    };

/*
 * This connection point will only allow one connection,
 * so it always returns the same connection key.  It also
 * doesn't bother to implement enumerations.
 */

#define CCONNMAX    1
#define ADVISEKEY   72388       //Arbitrary

class CConnectionPoint : public IConnectionPoint
    {
    private:
        ULONG           m_cRef;     //Object reference count
        PCPolyline      m_pObj;     //Containing object.

    public:
        CConnectionPoint(PCPolyline);
        ~CConnectionPoint(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IConnectionPoint members
        STDMETHODIMP GetConnectionInterface(IID *);
        STDMETHODIMP GetConnectionPointContainer
            (IConnectionPointContainer **);
        STDMETHODIMP Advise(LPUNKNOWN, DWORD *);
        STDMETHODIMP Unadvise(DWORD);
        STDMETHODIMP EnumConnections(IEnumConnections **);
    };

class CImpIPersistStorage : public IPersistStorage
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown
        PSSTATE             m_psState;   //Storage state

    public:
        CImpIPersistStorage(PCPolyline, LPUNKNOWN);
        ~CImpIPersistStorage(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP InitNew(LPSTORAGE);
        STDMETHODIMP Load(LPSTORAGE);
        STDMETHODIMP Save(LPSTORAGE, BOOL);
        STDMETHODIMP SaveCompleted(LPSTORAGE);
        STDMETHODIMP HandsOffStorage(void);
    };

class CImpIPersistStreamInit : public IPersistStreamInit
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIPersistStreamInit(PCPolyline, LPUNKNOWN);
        ~CImpIPersistStreamInit(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP Load(LPSTREAM);
        STDMETHODIMP Save(LPSTREAM, BOOL);
        STDMETHODIMP GetSizeMax(ULARGE_INTEGER *);
        STDMETHODIMP InitNew(void);
    };

//IDATAOBJ.CPP
class CImpIDataObject : public IDataObject
    {
    private:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIDataObject(PCPolyline, LPUNKNOWN);
        ~CImpIDataObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IDataObject members
        STDMETHODIMP GetData(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP GetDataHere(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP QueryGetData(LPFORMATETC);
        STDMETHODIMP GetCanonicalFormatEtc(LPFORMATETC,LPFORMATETC);
        STDMETHODIMP SetData(LPFORMATETC, LPSTGMEDIUM, BOOL);
        STDMETHODIMP EnumFormatEtc(DWORD, LPENUMFORMATETC *);
        STDMETHODIMP DAdvise(LPFORMATETC, DWORD, LPADVISESINK
            , DWORD *);
        STDMETHODIMP DUnadvise(DWORD);
        STDMETHODIMP EnumDAdvise(LPENUMSTATDATA *);
    };

//IENUMFE.CPP
class CEnumFormatEtc : public IEnumFORMATETC
    {
    private:
        ULONG           m_cRef;
        LPUNKNOWN       m_pUnkRef;
        ULONG           m_iCur;
        ULONG           m_cfe;
        LPFORMATETC     m_prgfe;

    public:
        CEnumFormatEtc(LPUNKNOWN, ULONG, LPFORMATETC);
        ~CEnumFormatEtc(void);

        //IUnknown members that delegate to m_pUnkRef.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IEnumFORMATETC members
        STDMETHODIMP Next(ULONG, LPFORMATETC, ULONG *);
        STDMETHODIMP Skip(ULONG);
        STDMETHODIMP Reset(void);
        STDMETHODIMP Clone(IEnumFORMATETC **);
    };

typedef CEnumFormatEtc *PCEnumFormatEtc;

class CImpIOleObject : public IOleObject
    {
    private:
        ULONG           m_cRef;
        PCPolyline      m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIOleObject(PCPolyline, LPUNKNOWN);
        ~CImpIOleObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IOleObject members
        STDMETHODIMP SetClientSite(LPOLECLIENTSITE);
        STDMETHODIMP GetClientSite(LPOLECLIENTSITE *);
        STDMETHODIMP SetHostNames(LPCOLESTR, LPCOLESTR);
        STDMETHODIMP Close(DWORD);
        STDMETHODIMP SetMoniker(DWORD, LPMONIKER);
        STDMETHODIMP GetMoniker(DWORD, DWORD, LPMONIKER *);
        STDMETHODIMP InitFromData(LPDATAOBJECT, BOOL, DWORD);
        STDMETHODIMP GetClipboardData(DWORD, LPDATAOBJECT *);
        STDMETHODIMP DoVerb(LONG, LPMSG, LPOLECLIENTSITE, LONG
                         , HWND, LPCRECT);
        STDMETHODIMP EnumVerbs(LPENUMOLEVERB *);
        STDMETHODIMP Update(void);
        STDMETHODIMP IsUpToDate(void);
        STDMETHODIMP GetUserClassID(CLSID *);
        STDMETHODIMP GetUserType(DWORD, LPOLESTR *);
        STDMETHODIMP SetExtent(DWORD, LPSIZEL);
        STDMETHODIMP GetExtent(DWORD, LPSIZEL);
        STDMETHODIMP Advise(LPADVISESINK, DWORD *);
        STDMETHODIMP Unadvise(DWORD);
        STDMETHODIMP EnumAdvise(LPENUMSTATDATA *);
        STDMETHODIMP GetMiscStatus(DWORD, DWORD *);
        STDMETHODIMP SetColorScheme(LPLOGPALETTE);
    };

//IVIEWOBJ.CPP
class CImpIViewObject : public IViewObject2
    {
    private:
        ULONG           m_cRef;
        PCPolyline      m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIViewObject(PCPolyline, LPUNKNOWN);
        ~CImpIViewObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IViewObject members
        STDMETHODIMP Draw(DWORD, LONG, LPVOID
            , DVTARGETDEVICE *, HDC, HDC, LPCRECTL
            , LPCRECTL, BOOL (CALLBACK *)(DWORD), DWORD);
        STDMETHODIMP GetColorSet(DWORD, LONG, LPVOID
            , DVTARGETDEVICE *, HDC, LPLOGPALETTE *);
        STDMETHODIMP Freeze(DWORD, LONG, LPVOID, LPDWORD);
        STDMETHODIMP Unfreeze(DWORD);
        STDMETHODIMP SetAdvise(DWORD, DWORD, LPADVISESINK);
        STDMETHODIMP GetAdvise(LPDWORD, LPDWORD, LPADVISESINK *);
        STDMETHODIMP GetExtent(DWORD, LONG, DVTARGETDEVICE *
            , LPSIZEL);
    };

class CImpIRunnableObject : public IRunnableObject
    {
    protected:
        ULONG           m_cRef;
        PCPolyline      m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIRunnableObject(PCPolyline, LPUNKNOWN);
        ~CImpIRunnableObject(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetRunningClass(LPCLSID);
        STDMETHODIMP Run(LPBINDCTX);
        STDMETHODIMP_(BOOL) IsRunning(void);
        STDMETHODIMP LockRunning(BOOL, BOOL);
        STDMETHODIMP SetContainedObject(BOOL);
    };

class CImpIExternalConnection : public IExternalConnection
    {
    protected:
        ULONG           m_cRef;
        PCPolyline      m_pObj;
        LPUNKNOWN       m_pUnkOuter;
        DWORD           m_cLockStrong;

    public:
        CImpIExternalConnection(PCPolyline, LPUNKNOWN);
        ~CImpIExternalConnection(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(DWORD) AddConnection(DWORD, DWORD);
        STDMETHODIMP_(DWORD) ReleaseConnection(DWORD, DWORD, BOOL);
    };

//CHAPTER23MOD
class CImpIOleInPlaceObject : public IOleInPlaceObject
    {
    protected:
        ULONG               m_cRef;
        PCPolyline          m_pObj;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIOleInPlaceObject(PCPolyline, LPUNKNOWN);
        ~CImpIOleInPlaceObject(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetWindow(HWND *);
        STDMETHODIMP ContextSensitiveHelp(BOOL);
        STDMETHODIMP InPlaceDeactivate(void);
        STDMETHODIMP UIDeactivate(void);
        STDMETHODIMP SetObjectRects(LPCRECT, LPCRECT);
        STDMETHODIMP ReactivateAndUndo(void);
    };

class CImpIOleInPlaceActiveObject
    : public IOleInPlaceActiveObject
    {
    protected:
        ULONG               m_cRef;
        PCPolyline          m_pObj;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIOleInPlaceActiveObject(PCPolyline, LPUNKNOWN);
        ~CImpIOleInPlaceActiveObject(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetWindow(HWND *);
        STDMETHODIMP ContextSensitiveHelp(BOOL);
        STDMETHODIMP TranslateAccelerator(LPMSG);
        STDMETHODIMP OnFrameWindowActivate(BOOL);
        STDMETHODIMP OnDocWindowActivate(BOOL);
        STDMETHODIMP ResizeBorder(LPCRECT, LPOLEINPLACEUIWINDOW
                         , BOOL);
        STDMETHODIMP EnableModeless(BOOL);
    };

//End CHAPTER23MOD

#endif  //_POLYLINE_H_


POLYWIN.CPP

/*
 * POLYWIN.CPP
 * Polyline Component Chapter 23
 *
 * Window procedure for the polyline drawing window and support
 * functions.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * PolylineWndProc
 *
 * Purpose:
 *  Window procedure for the polyline drawing window.
 */

LRESULT APIENTRY PolylineWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCPolyline      ppl;
    PAINTSTRUCT     ps;
    HDC             hDC;
    POINTS          pt;
    RECT            rc;

    //CHAPTER23MOD
    COMMANDPARAMS(wID, wCode, hWndMsg);
    //End CHAPTER23MOD

    ppl=(PCPolyline)GetWindowLong(hWnd, PLWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppl=(PCPolyline)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PLWL_STRUCTURE, (LONG)ppl);
            ppl->m_hWnd=hWnd;
            break;

        case WM_PAINT:
            hDC=BeginPaint(hWnd, &ps);
            GetClientRect(hWnd, &rc);
            ppl->Draw(hDC, FALSE, TRUE, &rc, NULL);
            EndPaint(hWnd, &ps);
            break;

        case WM_LBUTTONDOWN:
            //CHAPTER23MOD
            //We become UI Active with mouse action
            if (!ppl->m_fUIActive && NULL!=ppl->m_pIOleIPSite)
                ppl->UIActivate();
            //End CHAPTER23MOD

            //Stop if we are already at the limit.
            if (CPOLYLINEPOINTS==ppl->m_pl.cPoints)
                {
                MessageBeep(0);
                break;
                }

            //Convert the points into 0-32767 range
            GetClientRect(hWnd, &rc);
            pt=MAKEPOINTS(lParam);
            ppl->PointScale(&rc, &pt, FALSE);

            ppl->m_pl.rgpt[ppl->m_pl.cPoints++]=pt;

            //Draw the lines to this new point only.
            hDC=GetDC(hWnd);
            ppl->Draw(hDC, FALSE, FALSE, &rc, NULL);
            ReleaseDC(hWnd, hDC);

            if (NULL!=ppl->m_pAdv)
                ppl->m_pAdv->OnPointChange();

            ppl->SendAdvise(OBJECTCODE_DATACHANGED);
            break;

        //CHAPTER23MOD
        case WM_COMMAND:
            if (ID_HATCHWINDOW==wID
                && HWN_BORDERDOUBLECLICKED==wCode)
                {
                ppl->m_pImpIOleObject->DoVerb(OLEIVERB_OPEN, NULL
                    , ppl->m_pIOleClientSite, -1, NULL, NULL);
                }
            break;
        //End CHAPTER23MOD

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPolyline::Draw
 *
 * Purpose:
 *  Paints the current line in the polyline window.
 *
 * Parameters:
 *  hDC             HDC to draw on, a metafile or printer DC.
 *  fMetafile       BOOL indicating if hDC is a metafile or not,
 *                  so we can avoid operations that RIP.
 *  fEntire         BOOL indicating if we should draw the entire
 *                  figure or not.
 *  pRect           LPRECT defining the bounds in which to draw.
 *  ppl             PPOLYLINEDATA to draw.  If NULL, use current.
 *
 * Return Value:
 *  None
 */

void CPolyline::Draw(HDC hDC, BOOL fMetafile, BOOL fEntire
    , LPRECT pRect, PPOLYLINEDATA ppl)
    {
    HBRUSH          hBrush;
    HPEN            hPen;
    HGDIOBJ         hObj1, hObj2;
    UINT            i, j;
    int             nDC;
    POINTS          pt1,pt2;
    POINT           rgpt[CPOLYLINEPOINTS];

    if (NULL==ppl)
        ppl=&m_pl;

    nDC=SaveDC(hDC);

    for (i=0; i < ppl->cPoints; i++)
        {
        rgpt[i].x=ppl->rgpt[i].x;
        rgpt[i].y=ppl->rgpt[i].y;
        }

    //Printer and frozen differences handled in IViewObject::Draw

    hPen=CreatePen(ppl->iLineStyle, 1, ppl->rgbLine);
    hObj1=SelectObject(hDC, hPen);

    hBrush=CreateSolidBrush(ppl->rgbBackground);
    hObj2=SelectObject(hDC, hBrush);
    SetBkColor(hDC, ppl->rgbBackground);

    /*
     * Either draw the entire figure or just a single point.  The
     * entire figure also includes erasing the background completely,
     * since hDC may be a metafile DC.  Drawing a single point just
     * updates the figure for that new point.
     */
    if (fEntire || 0==ppl->cPoints)
        {
        //Erase the background for bitmaps and metafiles.
        SelectObject(hDC, GetStockObject(NULL_PEN));
        Rectangle(hDC, pRect->left, pRect->top, pRect->right+1
            , pRect->bottom+1);
        SelectObject(hDC, hPen);

        /*
         * If we are drawing the entire figure, then loop through
         * each point drawing a line to each successive point.
         */

        for (i=0; i < ppl->cPoints; i++)
            {
            for (j=i; j < ppl->cPoints; j++)
                {
                pt1.x=(short)rgpt[i].x;
                pt1.y=(short)rgpt[i].y;
                pt2.x=(short)rgpt[j].x;
                pt2.y=(short)rgpt[j].y;

                PointScale(pRect, &pt1, TRUE);
                PointScale(pRect, &pt2, TRUE);
                MoveToEx(hDC, pt1.x, pt1.y, NULL);
                LineTo(hDC, pt2.x, pt2.y);
                }
            }
        }
    else
        {
        /*
         * If we are only drawing the last point, just cycle once
         * through previous points.
         */

        //Get the last point entered in the array.
        j=ppl->cPoints-1;
        pt1.x=(short)rgpt[j].x;
        pt1.y=(short)rgpt[j].y;
        PointScale(pRect, &pt1, TRUE);

        for (i=0; i < j; i++)
            {
            pt2.x=(short)rgpt[i].x;
            pt2.y=(short)rgpt[i].y;
            PointScale(pRect, &pt2, TRUE);

            MoveToEx(hDC, pt1.x, pt1.y, NULL);
            LineTo(hDC, pt2.x, pt2.y);
            }
        }

    //If we have one point, draw a dot to indicate it's position.
    if (1==ppl->cPoints)
        {
        pt1.x=(short)rgpt[0].x;
        pt1.y=(short)rgpt[0].y;
        PointScale(pRect, &pt1, TRUE);
        SetPixel(hDC, pt1.x, pt1.y, m_pl.rgbLine);
        }

    SelectObject(hDC, hObj1);
    SelectObject(hDC, hObj2);
    DeleteObject(hBrush);
    DeleteObject(hPen);

    RestoreDC(hDC, nDC);
    return;
    }

/*
 * CPolyline::PointScale
 *
 * Purpose:
 *  Scales a point to or from a relative window coordinate to a
 *  0-32767 coordinate.
 *
 * Parameters:
 *  pRect           LPRECT of the window.
 *  ppt             LPPOINTS to convert
 *  fScaleToWindow  BOOL indicating direction of scaling.
 *
 * Return Value:
 *  None
 */

void CPolyline::PointScale(LPRECT pRect, LPPOINTS ppt
    , BOOL fScaleToWindow)
    {
    DWORD   cx, cy;

    //Window size
    cx=(DWORD)(pRect->right-pRect->left);
    cy=(DWORD)(pRect->bottom-pRect->top);

    //Prevent crashes
    if (0L==cx) cx=1;
    if (0L==cy) cy=1;

    //Must use DWORD to insure proper scaling.

    /*
     * As an in-proc server we may not have a rectangle where the
     * top left was (0,0) which was always true when we drew to
     * a Polyline window.  But this may be a container's hDC in
     * which case we'd better place the points in the container's
     * rectangle.  That is, we have to add/subtract pRect->left
     * and ->top in these calculations.
     */

    if (fScaleToWindow)
        {
        ppt->x=pRect->left+(UINT)(((DWORD)ppt->x*cx) >> 15);
        ppt->y=pRect->top+(UINT)(((DWORD)ppt->y*cy)  >> 15);
        }
    else
        {
        ppt->x=(UINT)(((DWORD)(ppt->x - pRect->left) << 15)/cx);
        ppt->y=(UINT)(((DWORD)(ppt->y - pRect->top)  << 15)/cy);
        }

    return;
    }

/*
 * PolyDlgProc
 *
 * Purpose:
 *  Dialog procedure for a window in which to display the Polyline
 *  for editing.  This pretty much handles all editing functionality
 *  for the embedded object.
 */

BOOL APIENTRY PolyDlgProc(HWND hDlg, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCPolyline      ppl=NULL;
    RECT            rc;
    UINT            uID, uTemp;
    UINT            cx, cy;
    //CHAPTER23MOD
    //hWnd and pt variables moved to CalcPolyRectInDialog.
    //End CHAPTER23MOD

   #ifdef WIN32
    ppl=(PCPolyline)GetProp(hDlg, PROP_POINTER);
   #else
    WORD            w1, w2;

    w1=(WORD)GetProp(hDlg, PROP_SELECTOR);
    w2=(WORD)GetProp(hDlg, PROP_OFFSET);

    ppl=(PCPolyline)MAKELP(w1, w2);
   #endif

    switch (iMsg)
        {
        case WM_INITDIALOG:
            ppl=(PCPolyline)lParam;
            ppl->m_hDlg=hDlg;

           #ifdef WIN32
            //Properties are 32-bits in Win32
            SetProp(hDlg, PROP_POINTER, (HANDLE)ppl);
           #else
            SetProp(hDlg, PROP_SELECTOR, (HANDLE)SELECTOROF(ppl));
            SetProp(hDlg, PROP_OFFSET,   (HANDLE)OFFSETOF(ppl));
           #endif

            //CHAPTER23MOD
            //Get the rectangle that Polyline occupies
            CalcPolyRectInDialog(hDlg, &rc);
            //End CHAPTER23MOD

            //Try to create the window.
            ppl->m_pImpIPolyline->Init(hDlg, &rc, WS_CHILD | WS_VISIBLE
                , ID_POLYLINE);

            //Set the initial line style radiobutton.
            ppl->m_pImpIPolyline->LineStyleGet(&uTemp);
            CheckRadioButton(hDlg, ID_LINESOLID, ID_LINEDASHDOTDOT
                , uTemp+ID_LINEMIN);

            return FALSE;

        case WM_SHOWWINDOW:
            if (LOWORD(wParam))
                {
                //Center the dialog on the screen
                cx=GetSystemMetrics(SM_CXSCREEN);
                cy=GetSystemMetrics(SM_CYSCREEN);
                GetWindowRect(hDlg, &rc);
                SetWindowPos(hDlg, NULL, (cx-(rc.right-rc.left))/2
                    , (cy-(rc.bottom-rc.top))/2, 0, 0, SWP_NOZORDER
                    | SWP_NOSIZE);

                //We didn't SetFocus from WM_INITDIALOG.  Do it now.
                SetFocus(GetDlgItem(hDlg, IDOK));
                }

            break;

        case WM_COMMAND:
            uID=LOWORD(wParam);

            switch (uID)
                {
                case IDOK:
                    //Close the dialog, but save first.
                    if (NULL!=ppl)
                        {
                        //CHAPTER23MOD
                        HRESULT     hr;

                        /*
                         * Instead of just closing, see if we can
                         * activate in-place again--we know we're an
                         * inside out object and want to be activated
                         * when we can be.  If we can't activate
                         * in-place, then close like we normally
                         * would.  Note that this makes sense only
                         * with an inside-out supporting container,
                         * which we determine from whether it ever
                         * used OLEIVERB_INPLACEACTIVATE.
                         */

                        hr=ResultFromScode(E_FAIL);

                        if (ppl->m_fContainerKnowsInsideOut)
                            {
                            hr=ppl->InPlaceActivate
                                (ppl->m_pIOleClientSite, FALSE);
                            }

                        if (FAILED(hr))
                            {
                            ppl->m_pImpIOleObject->Close
                                (OLECLOSE_SAVEIFDIRTY);
                            }

                        //End CHAPTER23MOD
                        }

                    break;

                case ID_UNDO:
                    if (NULL!=ppl)
                        ppl->m_pImpIPolyline->Undo();
                    break;

                case ID_COLORLINE:
                case ID_COLORBACK:
                    if (NULL!=ppl)
                        {
                        UINT            i;
                        COLORREF        rgColors[16];
                        CHOOSECOLOR     cc;

                        //Invoke the color chooser for either color
                        uTemp=(ID_COLORBACK==uID)
                            ? POLYLINECOLOR_BACKGROUND
                            : POLYLINECOLOR_LINE;

                        for (i=0; i<16; i++)
                            rgColors[i]=RGB(0, 0, i*16);

                        memset(&cc, 0, sizeof(CHOOSECOLOR));
                        cc.lStructSize=sizeof(CHOOSECOLOR);
                        cc.lpCustColors=rgColors;
                        cc.hwndOwner=hDlg;
                        cc.Flags=CC_RGBINIT;
                        ppl->m_pImpIPolyline->ColorGet(uTemp
                            , &cc.rgbResult);

                        if (ChooseColor(&cc))
                            {
                            //rgColor is just some COLORREF pointer
                            ppl->m_pImpIPolyline->ColorSet(uTemp
                                , cc.rgbResult, rgColors);
                            }
                        }
                    break;

                case ID_LINESOLID:
                case ID_LINEDASH:
                case ID_LINEDOT:
                case ID_LINEDASHDOT:
                case ID_LINEDASHDOTDOT:
                    if (NULL!=ppl)
                        {
                        ppl->m_pImpIPolyline
                            ->LineStyleSet(uID-ID_LINEMIN, &uTemp);
                        }

                    break;
                }
            break;

        case WM_DESTROY:
           #ifdef WIN32
            RemoveProp(hDlg, PROP_POINTER);
           #else
            RemoveProp(hDlg, PROP_SELECTOR);
            RemoveProp(hDlg, PROP_OFFSET);
           #endif
            break;

        case WM_CLOSE:
            //This will do the IDOK handling, then send POLYM_CLOSE
            SendCommand(hDlg, IDOK, 0, NULL);
            break;

        case POLYM_CLOSE:
            ShowWindow(hDlg, SW_HIDE);
            ppl->SendAdvise(OBJECTCODE_HIDEWINDOW);
            break;
        }

    return FALSE;
    }

//CHAPTER23MOD
/*
 * CalcPolyRectInDialog
 *
 * Purpose:
 *  Calculates the rectangle that the Polyline should occupy in the
 *  dialog box.  This is separated for in-place handling because
 *  when we open the dialog from an in-place active situation we
 *  have to move the Polyline window from the container into the
 *  dialog and position it properly.
 *
 * Parameters:
 *  hDlg            HWND of the dialog.
 *  prc             LPRECT in which to store the rectangle.
 *
 * Return Value:
 *  None
 */

void CalcPolyRectInDialog(HWND hDlg, LPRECT prc)
    {
    HWND        hWnd;
    POINT       pt;

    hWnd=GetDlgItem(hDlg, ID_POLYLINERECT);
    GetWindowRect(hWnd, prc);
    SETPOINT(pt, prc->left, prc->top);
    ScreenToClient(hDlg, &pt);

    //Set the polyline just within the black frame
    SetRect(prc, pt.x, pt.y, pt.x+(prc->right-prc->left)
        , pt.y+(prc->bottom-prc->top));
    InflateRect(prc, -1, -1);
    return;
    }
//End CHAPTER23MOD


RESOURCE.H

/*
 * RESOURCE.H
 * Polyline Component Chapter 23
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Object stringtable IDs
#define IDS_POLYLINEMIN     0
#define IDS_STORAGEFORMAT   0
#define IDS_USERTYPE        1

#define IDS_CLOSECAPTION    2
#define IDS_CLOSEPROMPT     3
#define IDS_POLYLINEMAX     3

#define IDR_ICON            1

#define IDD_EDITDIALOG      1

#define ID_UNDO             100
#define ID_COLORBACK        101
#define ID_COLORLINE        102
#define ID_GROUPCOLORS      103
#define ID_GROUPPREVIEW     104
#define ID_GROUPSTYLES      105
#define ID_GROUPFIGURE      106
#define ID_POLYLINERECT     107
#define ID_POLYLINE         108

#define ID_LINEMIN          200
#define ID_LINESOLID        200     //(ID_LINEMIN+PS_SOLID)
#define ID_LINEDASH         201     //(ID_LINEMIN+PS_DASH)
#define ID_LINEDOT          202     //(ID_LINEMIN+PS_DOT)
#define ID_LINEDASHDOT      203     //(ID_LINEMIN+PS_DASHDOT)
#define ID_LINEDASHDOTDOT   204     //(ID_LINEMIN+PS_DASHDOTDOT)

//Message to close the dialog
#define POLYM_CLOSE         (WM_USER+1000)

#endif //_RESOURCE_H_


README.TXT

To build a version of Component Cosmo that uses POLY24.DLL, copy
the sources from CHAP13\COCOSMO and modify the function
CCosmoDoc::FInit in DOCUMENT.CPP to use CLSID_Polyline24 instead
of CLSID_Polyline10 in the CoCreateInstance call:

Nothing else in CoCosmo needs to change to use the revised Polyline.
Note that CoCosmo still uses the IPolyline10 interface on the Polyline
object.  For all intents and purposes, nothing should change at all
in the execution of CoCosmo, demonstrating how interfaces do not
interfere with each other:  CoCosmo ignores the compound document
interfaces.

Just note how all the in-place activation and OLE Control support
in Polyline does not preclude Component Cosmo from using like a
non-compound document embedded object or a non-control object!


AMBIENTS.CPP

/*
 * AMBIENTS.CPP
 * Patron Chapter 24
 *
 * Implementation of the ambient properties IDispatch on a
 * tenant control site.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CImpIDispatch::CImpIDispatch
 * CImpIDispatch::~CImpIDispatch
 *
 * Parameters (Constructor):
 *  pTen            PCTenant of the tenant we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIDispatch::CImpIDispatch(PCTenant pTen, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pTen=pTen;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIDispatch::~CImpIDispatch(void)
    {
    return;
    }

/*
 * CImpIDispatch::QueryInterface
 * CImpIDispatch::AddRef
 * CImpIDispatch::Release
 */

STDMETHODIMP CImpIDispatch::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIDispatch::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIDispatch::Release(void)
    {
    m_cRef--;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIDispatch::GetTypeInfoCount
 * CImpIDispatch::GetTypeInfo
 * CImpIDispatch::GetIDsOfNames
 *
 * Unimplemented members, not needed for ambient properties.
 */

STDMETHODIMP CImpIDispatch::GetTypeInfoCount(UINT *pctInfo)
    {
    *pctInfo=0;
    return NOERROR;
    }

STDMETHODIMP CImpIDispatch::GetTypeInfo(UINT itinfo
    , LCID lcid, ITypeInfo **pptInfo)
    {
    *pptInfo=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

STDMETHODIMP CImpIDispatch::GetIDsOfNames(REFIID riid
    , OLECHAR **rgszNames, UINT cNames, LCID lcid, DISPID *rgDispID)
    {
    *rgszNames=NULL;
    *rgDispID=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIDispatch::Invoke
 *
 * Purpose:
 *  Calls a method in the dispatch interface or manipulates a
 *  property.
 *
 * Parameters:
 *  dispIDMember    DISPID of the method or property of interest.
 *  riid            REFIID reserved, must be NULL.
 *  lcid            LCID of the locale.
 *  wFlags          USHORT describing the context of the invocation.
 *  pDispParams     DISPPARAMS * to the array of arguments.
 *  pVarResult      VARIANT * in which to store the result.  Is
 *                  NULL if the caller is not interested.
 *  pExcepInfo      EXCEPINFO * to exception information.
 *  puArgErr        UINT * in which to store the index of an
 *                  invalid parameter if DISP_E_TYPEMISMATCH
 *                  is returned.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CImpIDispatch::Invoke(DISPID dispIDMember, REFIID riid
    , LCID lcid, unsigned short wFlags, DISPPARAMS *pDispParams
    , VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
    {
    HRESULT     hr;
    VARIANT     varResult;

    if (IID_NULL!=riid)
        return ResultFromScode(E_INVALIDARG);

    /*
     * We ignore lcid in this function.  A multilingual application
     * might use it to determine the meaning of certain parameters
     * or perhaps as an indication of how to format data like
     * time, date, and currency or any other language or locale-
     * sensitive data.
     */

    /*
     * Variable handling:  since ambient properties is just a
     * collection of singular read-only values, we don't have to
     * worry about input parameters.
     */

    /*
     * If the caller is not interested in the return value, then
     * pVarResult is NULL.  But since we're dealing with ambient
     * properties, there should always be an interest.  In any case,
     * if we're given a NULL, we'll point it to a dummy structure so
     * the rest of the code can assume that pVarResult is non-NULL.
     */
    if(NULL==pVarResult)
      pVarResult=&varResult;

    VariantInit(pVarResult);

    //The most common case is boolean, use as an initial type
    V_VT(pVarResult)=VT_BOOL;

    /*
     * Process the requested ambient property.  Anything but a
     * request for a property is invalid, so we can check that
     * before looking at the specific ID.  We can only get away
     * with this because all properties are read-only.
     */

    if (!(DISPATCH_PROPERTYGET & wFlags))
        return ResultFromScode(DISP_E_MEMBERNOTFOUND);

    hr=NOERROR;

    switch (dispIDMember)
        {
        case DISPID_AMBIENT_BACKCOLOR:
            V_I4(pVarResult)=m_pTen->m_clrBack;
            V_VT(pVarResult)=VT_I4;
            break;

        case DISPID_AMBIENT_FORECOLOR:
            V_I4(pVarResult)=m_pTen->m_clrFore;
            V_VT(pVarResult)=VT_I4;
            break;

        case DISPID_AMBIENT_FONT:
            /*
             * If we failed to create the font, act like we
             * don't support it.
             */
            if (NULL==m_pTen->m_pIFont)
                return ResultFromScode(DISP_E_MEMBERNOTFOUND);

            //The correct type is an IFontDisp pointer
            V_DISPATCH(pVarResult)=m_pTen->m_pIFont;
            m_pTen->m_pIFont->AddRef();
            V_VT(pVarResult)=VT_FONT;
            break;

        case DISPID_AMBIENT_LOCALEID:
            V_I4(pVarResult)=m_pTen->m_lcid;
            V_VT(pVarResult)=VT_I4;
            break;

        case DISPID_AMBIENT_USERMODE:
            V_BOOL(pVarResult)=!m_pTen->m_fDesignMode;
            break;

        case DISPID_AMBIENT_UIDEAD:
            V_BOOL(pVarResult)=m_pTen->m_fUIDead;
            break;

        case DISPID_AMBIENT_SUPPORTSMNEMONICS:
            V_BOOL(pVarResult)=TRUE;
            break;

        case DISPID_AMBIENT_SHOWGRABHANDLES:
            V_BOOL(pVarResult)=m_pTen->m_fHatchHandles;
            break;

        case DISPID_AMBIENT_SHOWHATCHING:
            V_BOOL(pVarResult)=m_pTen->m_fHatchHandles;
            break;

        default:
            hr=ResultFromScode(DISP_E_MEMBERNOTFOUND);
            break;
        }

    return hr;
    }


CLIENT.CPP

/*
 * CLIENT.CPP
 * Patron Chapter 24
 *
 * Implementation of the CPatronClient class that just makes sure
 * we get a CPatronDoc on doc creation and that we initialize fully.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPatronClient::CPatronClient
 * CPatronClient::~CPatronClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame to the frame object.
 */

CPatronClient::CPatronClient(HINSTANCE hInst, PCFrame pFR)
    : CClient(hInst, pFR)
    {
    return;
    }

CPatronClient::~CPatronClient(void)
    {
    return;
    }

/*
 * CPatronClient::CreateCDocument
 *
 * Purpose:
 *  Constructs a new document specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CPatronClient::CreateCDocument(void)
    {
    return (PCDocument)(new CPatronDoc(m_hInst, m_pFR, m_pAdv));
    }

/*
 * CPatronClient::SetMenu
 *
 * Purpose:
 *  Changes the frame-level menu, isolating the rest of the
 *  application from MDI/SDI considerations.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame window.
 *  hMenu           HMENU to set in the frame for the current
 *                  document.
 *  hMenuWin        HMENU of the window menu.
 *
 * Return Value:
 *  None
 */

void CPatronClient::SetMenu(HWND hWndFrame, HMENU hMenu
    , HMENU hMenuWin)
    {
   #ifdef MDI
     MDISETMENU(m_hWnd, hMenu, hMenuWin);
     MDIREFRESHMENU(m_hWnd);
   #else
    if (NULL!=hMenu)
        ::SetMenu(hWndFrame, hMenu);
   #endif

    DrawMenuBar(hWndFrame);
    return;
    }

/*
 * CPatronClient::MoveWithoutFamily
 *
 * Purpose:
 *  This specific in-place activation function applies moves the
 *  client window but to leave all child windows within it exactly
 *  where they are to keep in-place objects in the same absolute
 *  screen position.
 *
 * Parameters:
 *  prc             LPRECT containing the new space for the client
 *  dx, dy          ints specifying how much to move the client
 *
 * Return Value:
 *  None
 */

void CPatronClient::MoveWithoutFamily(LPRECT prc, int dx, int dy)
    {
    RECT        rc;
    HWND        hWndFrame;
    HWND        hWnd;
    POINT       pt;

    hWndFrame=GetParent(m_hWnd);
    SendMessage(hWndFrame, WM_SETREDRAW, FALSE, 0L);

    ShowWindow(m_hWnd, SW_HIDE);
    SetWindowPos(m_hWnd, NULL, prc->left, prc->top
        , prc->right-prc->left, prc->bottom-prc->top
        , SWP_NOZORDER | SWP_NOACTIVATE);

    //Move all children of the client
    hWnd=GetWindow(m_hWnd, GW_CHILD);

    while (NULL!=hWnd)
        {
        GetWindowRect(hWnd, &rc);
        SETPOINT(pt, rc.left, rc.top);
        ScreenToClient(m_hWnd, &pt);

        if (pt.x!=dx && pt.y!=dy && !IsZoomed(hWnd))
            {
            //Move window in the opposite direction as the client
            SetWindowPos(hWnd, NULL, pt.x-dx, pt.y-dy
                , rc.right-rc.left, rc.bottom-rc.top
                , SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOSIZE);
            }

        hWnd=GetWindow(hWnd, GW_HWNDNEXT);
        }

    SendMessage(hWndFrame, WM_SETREDRAW, TRUE, 0L);
    ShowWindow(m_hWnd, SW_SHOW);

    return;
    }

/*
 * CPatronClient::CallContextHelpOnDocuments
 *
 * Purpose:
 *  Calls IOleInPlaceUIWindow->ContextSensitiveHelp for each
 *  document window as required in an MDI container.  This does
 *  nothing in SDI.
 *
 * Parameters:
 *  fEnterMode      BOOl to pass to the documents
 *
 * Return Value:
 *  None
 */

void CPatronClient::CallContextHelpOnDocuments(BOOL fEnterMode)
    {
    LPOLEINPLACEUIWINDOW    pUIWin;
    HWND                    hWndT;
    PCPatronDoc             pDoc;

    //Loop through the documents calling their functions.
    hWndT=GetWindow(m_hWnd, GW_CHILD);

    for ( ; hWndT; hWndT=GetWindow(hWndT, GW_HWNDNEXT))
        {
        //Skip if icon title window
        if (NULL!=GetWindow(hWndT, GW_OWNER))
            continue;

        pDoc=(PCPatronDoc)SendMessage(hWndT, DOCM_PDOCUMENT
            , 0, 0L);

        if (NULL==pDoc)
            continue;

        pDoc->QueryInterface(IID_IOleInPlaceUIWindow
            , (PPVOID)&pUIWin);

        if (NULL==pUIWin)
            continue;

        pUIWin->ContextSensitiveHelp(fEnterMode);
        pUIWin->Release();
        }

    return;
    }


CONNECT.CPP

/*
 * CONNECT.CPP
 * Patron Chapter 24
 *
 * Helper functions for working with connection points on objects
 * and the event sets of those objects.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * InterfaceConnect
 *
 * Purpose:
 *  Connects some sink interface of a given IID to an object.
 *
 * Parameters:
 *  pObj            LPUNKNOWN of the object to which we connect
 *  riid            REFIID of the interface of the sink
 *  pIUnknownSink   LPUNKNOWN of the caller's sink interface that
 *                  is actually of the type matching riid
 *  pdwConn         LPDWORD in which to return the connection key
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL InterfaceConnect(LPUNKNOWN pObj, REFIID riid
    , LPUNKNOWN pIUnknownSink, LPDWORD pdwConn)
    {
    HRESULT                     hr;
    LPCONNECTIONPOINTCONTAINER  pCPC;
    LPCONNECTIONPOINT           pCP;

    if (NULL==pObj || NULL==pIUnknownSink || NULL==pdwConn)
        return FALSE;

    hr=pObj->QueryInterface(IID_IConnectionPointContainer
        , (PPVOID)&pCPC);

    if (FAILED(hr))
        return FALSE;

    hr=pCPC->FindConnectionPoint(riid, &pCP);

    if (SUCCEEDED(hr))
        {
        hr=pCP->Advise(pIUnknownSink, pdwConn);
        pCP->Release();
        }

    pCPC->Release();
    return SUCCEEDED(hr);
    }

/*
 * InterfaceDisconnect
 *
 * Purpose:
 *  Disconnects a prior connection to an object.
 *
 * Parameters:
 *  pObj            LPUNKNOWN of the object from which to disconnect
 *  riid            REFIID of the interface of the sink
 *  pdwConn         LPDWORD containing the key returned by
 *                  InterfaceConnect.  This function will zero the
 *                  key on diconnect.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL InterfaceDisconnect(LPUNKNOWN pObj, REFIID riid
    , LPDWORD pdwConn)
    {
    HRESULT                     hr;
    LPCONNECTIONPOINTCONTAINER  pCPC;
    LPCONNECTIONPOINT           pCP;

    if (NULL==pObj || NULL==pdwConn)
        return FALSE;

    if (0==*pdwConn)
        return FALSE;

    hr=pObj->QueryInterface(IID_IConnectionPointContainer
        , (PPVOID)&pCPC);

    if (FAILED(hr))
        return FALSE;

    hr=pCPC->FindConnectionPoint(riid, &pCP);

    if (SUCCEEDED(hr))
        {
        hr=pCP->Unadvise(*pdwConn);

        if (SUCCEEDED(hr))
            *pdwConn=0L;

        pCP->Release();
        }

    pCPC->Release();
    return SUCCEEDED(hr);
    }

/*
 * ObjectTypeInfo
 *
 * Purpose:
 *  Retrieves the ITypeInfo for the entire object from which
 *  one can learn the IID of the event set and navigate to the
 *  ITypeInfo for events, among other things.
 *
 * Parameters:
 *  pObj            LPUNKNOWN of the object
 *  ppITypeInfo     LPTYPEINFO * in which to return the ITypeInfo
 *                  interface for the object's events.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL ObjectTypeInfo(LPUNKNOWN pObj, LPTYPEINFO *ppITypeInfo)
    {
    HRESULT             hr;
    LPPROVIDECLASSINFO  pIProvideClassInfo;

    if (NULL==pObj || NULL==ppITypeInfo)
        return FALSE;

    *ppITypeInfo=NULL;

    /*
     * Get the object's IProvideClassInfo and call the GetClassInfo
     * method therein.  This will give us back the ITypeInfo for
     * the entire object.
     */

    hr=pObj->QueryInterface(IID_IProvideClassInfo
        , (PPVOID)&pIProvideClassInfo);

    if (FAILED(hr))
        return FALSE;

    hr=pIProvideClassInfo->GetClassInfo(ppITypeInfo);
    pIProvideClassInfo->Release();

    return SUCCEEDED(hr);
    }

/*
 * ObjectTypeInfoEvents
 *
 * Purpose:
 *  Retrieves the events type information from an object.  This is
 *  defined to be the "default source" interface in the object's
 *  type library.
 *
 * Parameters:
 *  pObj            LPUNKNOWN of the object
 *  ppITypeInfo     LPTYPEINFO * in which to return the ITypeInfo
 *                  interface for the object's events.
 *
 * Return Value:
 *  BOOL            TRUE if the event type lib exists, FALSE
 *                  if not or on any other error.
 */

BOOL ObjectTypeInfoEvents(LPUNKNOWN pObj, LPTYPEINFO *ppITypeInfo)
    {
    HRESULT             hr;
    LPTYPEINFO          pITypeInfoAll;
    LPTYPEATTR          pTA;

    if (NULL==pObj || NULL==ppITypeInfo)
        return FALSE;

    if (!ObjectTypeInfo(pObj, &pITypeInfoAll))
        return FALSE;

    /*
     * We have the object's overall ITypeInfo in pITypeInfoAll.
     * Now get the type attributes which will tell us the number of
     * individual interfaces in this type library.  We then loop
     * through the "implementation types" for all those interfaces
     * calling GetImplTypeFlags, looking for the default source.
     */

    *ppITypeInfo=NULL;  //Use this to determine success

    if (SUCCEEDED(pITypeInfoAll->GetTypeAttr(&pTA)))
        {
        UINT        i;
        int         iFlags;

        for (i=0; i < pTA->cImplTypes; i++)
            {
            //Get the implementation type for this interface
            hr=pITypeInfoAll->GetImplTypeFlags(i, &iFlags);

            if (FAILED(hr))
                continue;

            if ((iFlags & IMPLTYPEFLAG_FDEFAULT)
                && (iFlags & IMPLTYPEFLAG_FSOURCE))
                {
                HREFTYPE    hRefType=NULL;

                /*
                 * This is the interface we want.  Get a handle to
                 * the type description from which we can then get
                 * the ITypeInfo.
                 */
                pITypeInfoAll->GetRefTypeOfImplType(i, &hRefType);
                hr=pITypeInfoAll->GetRefTypeInfo(hRefType
                    , ppITypeInfo);

                break;
                }
            }

        pITypeInfoAll->ReleaseTypeAttr(pTA);
        }

    pITypeInfoAll->Release();
    return (NULL!=*ppITypeInfo);
    }

/*
 * ObjectEventsIID
 *
 * Purpose:
 *  Determines the IID of the object's events interface so we
 *  can ask for the right IConnectionPoint for events.
 *
 * Parameters:
 *  pObj            LPUNKNOWN of the object
 *  piid            IID * in which to return the IID for events
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL ObjectEventsIID(LPUNKNOWN pObj, IID *piid)
    {
    HRESULT             hr;
    LPTYPEINFO          pITypeInfo;
    LPTYPEATTR          pTA;

    *piid=CLSID_NULL;

    if (!ObjectTypeInfoEvents(pObj, &pITypeInfo))
        return FALSE;

    hr=pITypeInfo->GetTypeAttr(&pTA);

    if (SUCCEEDED(hr))
        {
        *piid=pTA->guid;
        pITypeInfo->ReleaseTypeAttr(pTA);
        }

    pITypeInfo->Release();
    return SUCCEEDED(hr);
    }


DOCUMENT.CPP

/*
 * DOCUMENT.CPP
 * Patron Chapter 24
 *
 * Implementation of the CPatronDoc derivation of CDocument that
 * manages pages for us.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"
#include <memory.h>
#include <dlgs.h>       //Pring Dlg button IDs

/*
 * CPatronDoc::CPatronDoc
 * CPatronDoc::~CPatronDoc
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame of the frame object.
 *  pAdv            PCDocumentAdviseSink to notify on events.
 */

CPatronDoc::CPatronDoc(HINSTANCE hInst, PCFrame pFR
    , PCDocumentAdviseSink pAdv)
    : CDocument(hInst, pFR, pAdv)
    {
    m_pPG=NULL;
    m_lVer=VERSIONCURRENT;
    m_pIStorage=NULL;
    m_fPrintSetup=TRUE;
    m_pDropTarget=NULL;

    //These CFSTR_* values are standard (defined in INC\CLASSLIB.H)
    m_cfEmbeddedObject=RegisterClipboardFormat
        (CFSTR_EMBEDDEDOBJECT);
    m_cfObjectDescriptor=RegisterClipboardFormat
        (CFSTR_OBJECTDESCRIPTOR);

    m_cfLinkSource=RegisterClipboardFormat(CFSTR_LINKSOURCE);
    m_cfLinkSrcDescriptor=RegisterClipboardFormat
        (CFSTR_LINKSRCDESCRIPTOR);

    m_fShowTypes=FALSE;
    m_cRef=1L;
    m_fRename=TRUE;
    m_dwRegROT=0L;
    m_dwRegROTWild=0L;
    m_pImpIPersistFile=NULL;
    m_pImpIOleItemContainer=NULL;

    m_pImpIOleIPUIWindow=NULL;
    m_pIOleIPActiveObject=NULL;
    m_fNoObjectFrameTools=TRUE;

    //CHAPTER24MOD
    m_fDesignMode=FALSE;
    m_fUIDead=FALSE;
    m_fHatchHandles=TRUE;
    //End CHAPTER24MOD

    return;
    }

CPatronDoc::~CPatronDoc(void)
    {
    INOLE_RevokeAsRunning(&m_dwRegROT);
    INOLE_RevokeAsRunning(&m_dwRegROTWild);

    DeleteInterfaceImp(m_pImpIPersistFile);
    DeleteInterfaceImp(m_pImpIOleItemContainer);

    if (NULL!=m_pPG)
        delete m_pPG;

    DeleteInterfaceImp(m_pImpIOleIPUIWindow);
    ReleaseInterface(m_pIStorage);
    CoFreeUnusedLibraries();
    return;
    }

/*
 * CPatronDoc::QueryInterface
 * CPatronDoc::AddRef
 * CPatronDoc::Release
 */

STDMETHODIMP CPatronDoc::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    /*
     * WARNING:  Because the *ppv=this implicitly does
     * *ppv=(LPVOID)this, the this pointer is incorrectly cast
     * from a CPatronDoc pointer to LPVOID where we want an
     * IUnknown pointer instead.  CPatronDoc uses multiple
     * inheritance here which means that we have to explicitly
     * provide the typecast to select the base class like
     * IUnknown.  If you don't do this, calls to AddRef, for
     * example, might call your desctructor!
     */

    if (IID_IUnknown==riid)
        *ppv=(LPUNKNOWN)this;

    if (IID_IOleItemContainer==riid || IID_IOleContainer==riid
        || IID_IParseDisplayName==riid)
        *ppv=m_pImpIOleItemContainer;

    if (IID_IPersistFile==riid || IID_IPersist==riid)
        *ppv=m_pImpIPersistFile;

    if (IID_IOleWindow==riid || IID_IOleInPlaceUIWindow==riid)
        *ppv=m_pImpIOleIPUIWindow;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CPatronDoc::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPatronDoc::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    PostMessage(m_hWnd, WM_CLOSE, 0, 0);
    return 0;
    }

/*
 * CPatronDoc::Init
 *
 * Purpose:
 *  Initializes an already created document window.  The client
 *  actually creates the window for us, then passes that here for
 *  further initialization.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPatronDoc::Init(PDOCUMENTINIT pDI)
    {
    //Change the stringtable range to our customization.
    pDI->idsMin=IDS_DOCUMENTMIN;
    pDI->idsMax=IDS_DOCUMENTMAX;

    //Do default initialization
    if (!CDocument::Init(pDI))
        return FALSE;

    //Pages are created when we get a Load later.

    //Instantiate our interfaces
    m_pImpIPersistFile=new CImpIPersistFile(this, this);

    if (NULL==m_pImpIPersistFile)
        return FALSE;

    m_pImpIOleItemContainer=new CImpIOleItemContainer(this, this
        , TRUE);

    if (NULL==m_pImpIOleItemContainer)
        return FALSE;

    m_pImpIOleIPUIWindow=new CImpIOleInPlaceUIWindow(this, this);

    if (NULL==m_pImpIOleIPUIWindow)
        return FALSE;

    return TRUE;
    }

/*
 * CPatronDoc::FMessageHook
 *
 * Purpose:
 *  Processes WM_SIZE for the document so we can resize the Pages
 *  window.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return
 *                  value for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CPatronDoc::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    UINT        dx, dy;
    RECT        rc;

    *pLRes=0;

    //CLASSLIB always sends MDIACTIVATE, regardless of MDI or SDI.
    if (WM_MDIACTIVATE==iMsg)
        {
        /*
         * Win32 has hWndActivate in lParam.  Win16 has it in
         * LOWORD(lParam).  (HWND)(UINT) handles both cases.
         */
        BOOL    fActivate=((HWND)(UINT)lParam==hWnd);

        if (NULL!=m_pIOleIPActiveObject)
            {
            //Hide our tools
            if (fActivate)
                g_pFR->ShowUIAndTools(m_fNoObjectFrameTools, FALSE);

            m_pIOleIPActiveObject->OnDocWindowActivate(fActivate);
            }
        else
            {
            //Restore frame-level UI if we're becoming active.
            if (fActivate)
                g_pFR->ReinstateUI();
            }
        }

    //Eat to prevent flickering
    if (WM_ERASEBKGND==iMsg)
        return TRUE;

    if (WM_SIZE==iMsg && NULL!=m_pPG)
        {
        dx=LOWORD(lParam);
        dy=HIWORD(lParam);

        if (SIZE_MINIMIZED!=wParam)
            {
            //Resize Pages window to fit the new document size.
            GetClientRect(hWnd, &rc);
            m_pPG->RectSet(&rc, FALSE);
            }

        if (NULL!=m_pIOleIPActiveObject)
            {
            //Also tell in-place objects that document was resized.
            m_pIOleIPActiveObject->ResizeBorder(&rc
                , m_pImpIOleIPUIWindow, FALSE);
            }
        }

    if (WM_DESTROY==iMsg)
        {
        /*
         * We have to revoke the drop target here because the window
         * will be destroyed and the property forcefully removed
         * before we could do this in the destructor.
         */
        if (NULL!=m_pDropTarget)
            {
            RevokeDragDrop(m_hWnd);
            CoLockObjectExternal(m_pDropTarget, FALSE, TRUE);
            m_pDropTarget->Release();
            }

        return FALSE;
        }

    /*
     * We return FALSE even on WM_SIZE so we can let the default
     * procedure handle maximized MDI child windows appropriately.
     */
    return FALSE;
    }

/*
 * CPatronDoc::Clear
 *
 * Purpose:
 *  Sets all contents in the document back to defaults with no
 *  filename.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Clear(void)
    {
    //Completely reset the pages
    if (NULL!=m_pPG)
        m_pPG->StorageSet(NULL, FALSE, FALSE);

    CDocument::Clear();
    m_lVer=VERSIONCURRENT;
    return;
    }

/*
 * CPatronDoc::FDirtySet
 *
 * Purpose:
 *  Sets or clears the document 'dirty' flag returning the previous
 *  state of that same flag.  We override this to note change
 *  times in the running object table.
 *
 * Parameters:
 *  fDirty          BOOL indicating the new contents of the dirty
 *                  flag.
 *
 * Return Value:
 *  BOOL            Previous value of the dirty flag.
 */

BOOL CPatronDoc::FDirtySet(BOOL fDirty)
    {
    BOOL        fRet;

    fRet=CDocument::FDirtySet(fDirty);
    INOLE_NoteChangeTime(m_dwRegROT, NULL, NULL);
    INOLE_NoteChangeTime(m_dwRegROTWild, NULL, NULL);

    return fRet;
    }

/*
 * CPatronDoc::FDirtyGet
 *
 * Purpose:
 *  Returns the current dirty status of the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the file is clean, FALSE otherwise.
 */

BOOL CPatronDoc::FDirtyGet()
    {
    BOOL    fPageDirty;

    fPageDirty=m_pPG->FIsDirty();
    return m_fDirty | fPageDirty;
    }

/*
 * CPatronDoc::Delete
 *
 * Purpose:
 *  Removed the current object from the document.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Delete(void)
    {
    if (NULL!=m_pPG)
        m_pPG->TenantDestroy();

    CoFreeUnusedLibraries();
    return;
    }

/*
 * CPatronDoc::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected in this
 *  document for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU of the Edit menu.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryObjectSelected(HMENU hMenu)
    {
    return m_pPG->FQueryObjectSelected(hMenu);
    }

/*
 * CPatronDoc::Load
 *
 * Purpose:
 *  Loads a given document without any user interface overwriting
 *  the previous contents of the editor.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load.  Could be NULL
 *                  for an untitled document.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CPatronDoc::Load(BOOL fChangeFile, LPTSTR pszFile)
    {
    RECT        rc;
    LPSTORAGE   pIStorage;
    HRESULT     hr;
    CLSID       clsID;
    DWORD       dwMode=STGM_TRANSACTED | STGM_READWRITE
                    | STGM_SHARE_EXCLUSIVE;
    LPMONIKER   pmk;

    if (NULL==pszFile)
        {
        //Create a new temp file.
        hr=StgCreateDocfile(NULL, dwMode | STGM_CREATE
            | STGM_DELETEONRELEASE, 0, &pIStorage);

        //Mark this our class since we check with ReadClassStg.
        if (SUCCEEDED(hr))
            WriteClassStg(pIStorage, CLSID_PatronPages);
        }
    else
        {
        hr=StgOpenStorage(pszFile, NULL, dwMode, NULL, 0, &pIStorage);
        }

    if (FAILED(hr))
        return DOCERR_COULDNOTOPEN;

    //Check if this is our type of file and exit if not.
    hr=ReadClassStg(pIStorage, &clsID);

    if (FAILED(hr) || CLSID_PatronPages!=clsID)
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    //Attempt to create our contained Pages window.
    m_pPG=new CPages(m_hInst, m_cf);
    GetClientRect(m_hWnd, &rc);

    //Always use WS_CLIPCHILDREN with in-place support.
    if (!m_pPG->Init(m_hWnd, &rc, WS_CLIPCHILDREN | WS_CHILD
        | WS_VISIBLE, ID_PAGES, NULL))
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    /*
     * This moniker registration insures that all the file opening
     * sequence will see this instance of the document as running
     * and be able to communicate with it.  This is so a link to
     * an embedding on the same page of this document will hook
     * up correctly.
     */
    if (NULL!=pszFile)
        {
        CreateFileMoniker(pszFile, &pmk);

        if (NULL!=pmk)
            {
            //This will be revoked and re-registered in Rename below
            INOLE_RegisterAsRunning(this, pmk, 0, &m_dwRegROT);
            pmk->Release();
            }
        }

    if (!m_pPG->StorageSet(pIStorage, FALSE, (NULL==pszFile)))
        {
        pIStorage->Release();
        return DOCERR_READFAILURE;
        }

    //Open the window up for drag-drop
    m_pDropTarget=new CDropTarget(this);

    if (NULL!=m_pDropTarget)
        {
        m_pDropTarget->AddRef();
        CoLockObjectExternal(m_pDropTarget, TRUE, FALSE);
        RegisterDragDrop(m_hWnd, m_pDropTarget);
        }

    m_pIStorage=pIStorage;
    Rename(pszFile);

    //Do initial setup if new file, otherwise Pages handles things.
    if (NULL==pszFile)
        {
        //Go initialize the Pages for the default printer.
        if (!PrinterSetup(NULL, TRUE))
            return DOCERR_COULDNOTOPEN;

        //Go create an initial page.
        m_pPG->PageInsert(0);
        }
    else
        {
        //Can't change an already saved configuration
        m_fPrintSetup=FALSE;
        }

    FDirtySet(FALSE);
    return DOCERR_NONE;
    }

/*
 * CPatronDoc::Save
 *
 * Purpose:
 *  Writes the file to a known filename, requiring that the user
 *  has previously used FileOpen or FileSaveAs in order to have
 *  a filename.
 *
 * Parameters:
 *  uType           UINT indicating the type of file the user
 *                  requested to save in the File Save As dialog.
 *  pszFile         LPTSTR under which to save.  If NULL, use the
 *                  current name.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_*
 */

UINT CPatronDoc::Save(UINT uType, LPTSTR pszFile)
    {
    HRESULT     hr;
    LPSTORAGE   pIStorage;

    //Save or Save As with the same file is just a commit.
    if (NULL==pszFile
        || (NULL!=pszFile && 0==lstrcmpi(pszFile, m_szFile)))
        {
        WriteFmtUserTypeStg(m_pIStorage, m_cf
            , PSZ(IDS_CLIPBOARDFORMAT));

        //Insure pages are up to date.
        m_pPG->StorageUpdate(FALSE);

        //Commit everyting
        m_pIStorage->Commit(STGC_DEFAULT);

        FDirtySet(FALSE);
        return DOCERR_NONE;
        }

    /*
     * When we're given a name, open the storage, creating it new
     * if it does not exist or overwriting the old one.  Then CopyTo
     * from the current to the new, Commit the new, Release the old.
     */

    hr=StgCreateDocfile(pszFile, STGM_TRANSACTED | STGM_READWRITE
        | STGM_CREATE | STGM_SHARE_EXCLUSIVE, 0, &pIStorage);

    if (FAILED(hr))
        return DOCERR_COULDNOTOPEN;

    WriteClassStg(pIStorage, CLSID_PatronPages);
    WriteFmtUserTypeStg(pIStorage, m_cf, PSZ(IDS_CLIPBOARDFORMAT));

    //Insure all pages are up-to-date.
    m_pPG->StorageUpdate(TRUE);

    //This also copies the CLSID we stuff in here on file creation.
    hr=m_pIStorage->CopyTo(NULL, NULL, NULL, pIStorage);

    if (FAILED(hr))
        {
        SCODE       sc;

        pIStorage->Release();
        sc=GetScode(hr);

        /*
         * If we failed because of low memory, use IRootStorage
         * to switch into the new file.
         */
        if (E_OUTOFMEMORY==sc)
            {
            LPROOTSTORAGE        pIRoot;

            //Delete file we already created
            DeleteFile(pszFile);

            if (FAILED(m_pIStorage->QueryInterface
                (IID_IRootStorage, (PPVOID)&pIRoot)))
                return DOCERR_WRITEFAILURE;

           #ifdef WIN32ANSI
            OLECHAR     szTemp[MAX_PATH];

            MultiByteToWideChar(CP_ACP, 0, pszFile, -1, szTemp, MAX_PATH);
            hr=pIRoot->SwitchToFile(szTemp);
           #else
            hr=pIRoot->SwitchToFile(pszFile);
           #endif
            pIRoot->Release();

            if (FAILED(hr))
                return DOCERR_WRITEFAILURE;

            //If successful, the Commit below finishes the save.
            pIStorage=m_pIStorage;
            m_pIStorage->AddRef();    //Matches Release below
            }
        }

    if (m_fRename)
        Rename(pszFile);    //Update caption bar.

    pIStorage->Commit(STGC_DEFAULT);

    /*
     * Revert changes on the original storage.  If this was a temp
     * file, it's deleted since we used STGM_DELETEONRELEASE.
     */
    m_pIStorage->Release();

    //Make this new storage current
    m_pIStorage=pIStorage;
    m_pPG->StorageSet(pIStorage, TRUE, FALSE);

    FDirtySet(FALSE);

    return DOCERR_NONE;
    }

/*
 * CPatronDoc::Clip
 *
 * Purpose:
 *  Places a private format, a metafile, and a bitmap of the display
 *  on the clipboard, optionally implementing Cut by deleting the
 *  data in the current window after rendering.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *  fCut            BOOL indicating cut (TRUE) or copy (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::Clip(HWND hWndFrame, BOOL fCut)
    {
    if (NULL==m_pPG)
        return FALSE;

    return m_pPG->TenantClip(fCut);
    }

/*
 * CPatronDoc::Paste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  to the current figure in the editor window.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::Paste(HWND hWndFrame)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet=FALSE;
    FORMATETC       fe;
    TENANTTYPE      tType;

    if (NULL==m_pPG)
        return FALSE;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    //Go get type and format we *can* paste, then actually paste it.
    if (FQueryPasteFromData(pIDataObject, &fe, &tType))
        {
        fRet=PasteFromData(pIDataObject, &fe, tType, NULL
            , 0L, TRUE);
        }

    pIDataObject->Release();
    return fRet;
    }

/*
 * CPatronDoc::FQueryPaste
 *
 * Purpose:
 *  Determines if we can paste data from the clipboard.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPaste(void)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet;

    if (FAILED(OleGetClipboard(&pIDataObject)))
        return FALSE;

    fRet=FQueryPasteFromData(pIDataObject, NULL, NULL);

    fRet |= FQueryPasteLinkFromData(pIDataObject, NULL, NULL);
    pIDataObject->Release();
    return fRet;
    }

/*
 * CPatronDoc::PasteSpecial
 *
 * Purpose:
 *  Retrieves a specific data format from the clipboard and sends
 *  it to the editor window appropriately.
 *
 *  Note that if this function is called, then the appropriate
 *  format is available because the Paste menu item is only
 *  enabled if the format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::PasteSpecial(HWND hWndFrame)
    {
    OLEUIPASTESPECIAL   ps;
    OLEUIPASTEENTRY     rgPaste[6];
    UINT                rgcf[1];        //For ps.m_arrLinkTypes
    DWORD               dwData=0;
    UINT                uTemp;
    BOOL                fRet=FALSE;

    if (NULL==m_pPG)
        return FALSE;

    memset(&ps, 0, sizeof(ps));

    if (FAILED(OleGetClipboard(&ps.lpSrcDataObj)))
        return FALSE;

    ps.cbStruct=sizeof(ps);
    ps.hWndOwner=hWndFrame;

    ps.dwFlags=PSF_SELECTPASTE;
    ps.arrPasteEntries=rgPaste;

    //Set up Paste Special descriptor arrays.
    SETDefFormatEtc(rgPaste[0].fmtetc, m_cf, TYMED_HGLOBAL);
    rgPaste[0].lpstrFormatName=PSZ(IDS_CLIPBOARDFORMAT);
    rgPaste[0].lpstrResultText=PSZ(IDS_PASTEASPATRON);
    rgPaste[0].dwFlags=OLEUIPASTE_PASTEONLY;

    //Embedded objects can be iconic displays if the user wants.
    SETDefFormatEtc(rgPaste[1].fmtetc, m_cfEmbeddedObject
        , TYMED_ISTORAGE);
    rgPaste[1].lpstrFormatName=PSZ(IDS_PASTEOBJECT);
    rgPaste[1].lpstrResultText=PSZ(IDS_PASTEASOBJECT);

    /*
     * CAUTION:  Use OLEUI_PASTE with embedded objects or else
     * this item will not show up in the dialog.  I learned this the
     * hard way (that is, after about 6 hours of pulling hair!).
     */
    rgPaste[1].dwFlags=OLEUIPASTE_PASTE | OLEUIPASTE_ENABLEICON;

    SETDefFormatEtc(rgPaste[2].fmtetc,CF_METAFILEPICT,TYMED_MFPICT);
    rgPaste[2].lpstrFormatName=PSZ(IDS_PASTEMETAFILE);
    rgPaste[2].lpstrResultText=PSZ(IDS_PASTEASMETAFILE);
    rgPaste[2].dwFlags=OLEUIPASTE_PASTEONLY;

    SETDefFormatEtc(rgPaste[3].fmtetc, CF_DIB, TYMED_HGLOBAL);
    rgPaste[3].lpstrFormatName=PSZ(IDS_PASTEDIB);
    rgPaste[3].lpstrResultText=PSZ(IDS_PASTEASDIB);
    rgPaste[3].dwFlags=OLEUIPASTE_PASTEONLY;

    SETDefFormatEtc(rgPaste[4].fmtetc, CF_BITMAP, TYMED_GDI);
    rgPaste[4].lpstrFormatName=PSZ(IDS_PASTEBITMAP);
    rgPaste[4].lpstrResultText=PSZ(IDS_PASTEASBITMAP);
    rgPaste[4].dwFlags=OLEUIPASTE_PASTEONLY;

    SETDefFormatEtc(rgPaste[5].fmtetc,m_cfLinkSource,TYMED_ISTREAM);
    rgPaste[5].lpstrFormatName=PSZ(IDS_PASTELINK);
    rgPaste[5].lpstrResultText=PSZ(IDS_PASTEASLINK);
    rgPaste[5].dwFlags=OLEUIPASTE_LINKTYPE1 | OLEUIPASTE_ENABLEICON;

    //Types we can Paste Link from the clipboard.
    rgcf[0]=m_cfLinkSource;
    ps.arrLinkTypes=rgcf;
    ps.cLinkTypes=1;
    ps.cPasteEntries=6;

    uTemp=OleUIPasteSpecial(&ps);

    if (OLEUI_OK==uTemp)
        {
        UINT        i=ps.nSelectedIndex;
        TENANTTYPE  tType;

        if (ps.fLink)
            tType=TENANTTYPE_LINKEDOBJECTFROMDATA;
        else
            {
            if (1==ps.nSelectedIndex)
                tType=TENANTTYPE_EMBEDDEDOBJECTFROMDATA;
            else
                tType=TENANTTYPE_STATIC;
            }

        //Handle iconic aspects...from links as well
        if ((1==i || ps.fLink) && (PSF_CHECKDISPLAYASICON
            & ps.dwFlags) && NULL!=ps.hMetaPict)
            {
            rgPaste[i].fmtetc.dwAspect=DVASPECT_ICON;
            dwData=(DWORD)(UINT)ps.hMetaPict;
            }

        fRet=PasteFromData(ps.lpSrcDataObj, &rgPaste[i].fmtetc
            , tType, NULL, dwData, FALSE);

        //Always free this regardless of what we do with it.
        INOLE_MetafilePictIconFree(ps.hMetaPict);
        }

    ps.lpSrcDataObj->Release();
    return fRet;
    }

/*
 * CPatronDoc::FQueryPasteFromData
 * (Protected)
 *
 * Purpose:
 *  Determines if we can paste data from a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which we might want to paste.
 *  pFE             LPFORMATETC in which to return the first format
 *                  we can use.  Ignored if NULL.
 *  ptType          PTENANTTYPE in which to store the type of
 *                  object we can paste.  Ignored if NULL.
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPasteFromData(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, PTENANTTYPE ptType)
    {
    FORMATETC       fe;
    HRESULT         hr, hr2;

    if (NULL!=(LPVOID)ptType)
        *ptType=TENANTTYPE_STATIC;

    //Any of our specific data here?
    SETDefFormatEtc(fe, m_cf, TYMED_HGLOBAL);
    hr=pIDataObject->QueryGetData(&fe);

    //If embedded object data is available, set the appropriate type
    hr2=OleQueryCreateFromData(pIDataObject);

    if (NOERROR==hr2)
        {
        if (NULL!=pFE)
            {
            /*
             * Default to content.  Paste will use
             * CFSTR_OBJECTDESCRIPTOR to figure the actual aspect.
             */
            SETDefFormatEtc(*pFE, m_cfEmbeddedObject
                , TYMED_ISTORAGE);
            }

        if (NULL!=(LPVOID)ptType)
            *ptType=TENANTTYPE_EMBEDDEDOBJECTFROMDATA;

        /*
         * Return now if PatronObject wasn't available, otherwise
         * break out so that pFE gets PatronObject format.
         */
        if (NOERROR!=hr)
            return TRUE;
        }

    if (NOERROR!=hr && NOERROR!=hr2)
        {
        //Try metafile, DIB, then bitmap, setting fe each time
        SETDefFormatEtc(fe, CF_METAFILEPICT, TYMED_MFPICT);
        hr=pIDataObject->QueryGetData(&fe);

        if (NOERROR!=hr)
            {
            SETDefFormatEtc(fe, CF_DIB, TYMED_HGLOBAL);
            hr=pIDataObject->QueryGetData(&fe);

            if (NOERROR!=hr)
                {
                SETDefFormatEtc(fe, CF_BITMAP, TYMED_GDI);
                hr=pIDataObject->QueryGetData(&fe);
                }
            }
        }

    if (NOERROR==hr && NULL!=pFE)
        *pFE=fe;

    return (NOERROR==hr);
    }

/*
 * CPatronDoc::FQueryPasteLinkFromData
 * (Protected)
 *
 * Purpose:
 *  Determines if we can paste link from a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which we might want to paste.
 *  pFE             LPFORMATETC in which to return the first format
 *                  we can use.  Ignored if NULL.
 *  ptType          PTENANTTYPE in which to store the type of object
 *                  we can paste.  Ignored if NULL.
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPasteLinkFromData(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, PTENANTTYPE ptType)
    {
    HRESULT         hr;

    if (NULL==pIDataObject)
        return FALSE;

    hr=OleQueryLinkFromData(pIDataObject);

    if (NOERROR!=hr)
        return FALSE;

    if (NULL!=pFE)
        SETDefFormatEtc(*pFE, m_cfLinkSource, TYMED_ISTREAM);

    if (NULL!=(LPVOID)ptType)
        *ptType=TENANTTYPE_LINKEDOBJECTFROMDATA;

    return TRUE;
    }

/*
 * CPatronDoc::PasteFromData
 * (Protected)
 *
 * Purpose:
 *  Retrieves the private data format from a data object and sets
 *  it to the current figure in the editor window.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *  pFE             LPFORMATETC to use in the paste. Cannot be NULL.
 *  tType           TENANTTYPE to paste.
 *  ppo             PPATRONOBJECT containing placement data.
 *  dwData          DWORD extra data sensitive to tType
 *  fUseObjDesc     BOOL indicating to use CFSTR_OBJECTDESCRIPTOR
 *                  format for determining the aspect of the object
 *                  if the format is available.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::PasteFromData(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, TENANTTYPE tType, PPATRONOBJECT ppo
    , DWORD dwData, BOOL fUseObjDesc)
    {
    BOOL                fRet;
    HRESULT             hr;
    PATRONOBJECT        po;
    STGMEDIUM           stm;
    LPOBJECTDESCRIPTOR  pOD;
    FORMATETC           fe;
    BOOL                fRelease=FALSE;

    if (NULL==pFE)
        return FALSE;

    //If not given any placement data, see if we can retrieve it
    if (pFE->cfFormat==m_cf && NULL==ppo)
        {
        hr=pIDataObject->GetData(pFE, &stm);

        if (SUCCEEDED(hr))
            {
            ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);

            po=*ppo;
            ppo=&po;

            //If there's an object here, make sure type is right.
            if (ppo->fe.cfFormat==m_cfEmbeddedObject)
                tType=TENANTTYPE_EMBEDDEDOBJECTFROMDATA;

            if (ppo->fe.cfFormat==m_cfLinkSource)
                tType=TENANTTYPE_LINKEDOBJECTFROMDATA;

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);
            }
        }

    /*
     * If we're told to look at CFSTR_OBJECTDESCRIPTOR, then try to get
     * the data and copy the aspect out of it.  We're not interested

* in any other part of it, however.
     */
    if (fUseObjDesc)
        {
        SETDefFormatEtc(fe, m_cfObjectDescriptor, TYMED_HGLOBAL);

        hr=pIDataObject->GetData(&fe, &stm);

        if (FAILED(hr))
            {
            SETDefFormatEtc(fe, m_cfLinkSrcDescriptor
                , TYMED_HGLOBAL);
            hr=pIDataObject->GetData(&fe, &stm);
            }

        if (SUCCEEDED(hr))
            {
            pOD=(LPOBJECTDESCRIPTOR)GlobalLock(stm.hGlobal);
            pFE->dwAspect=pOD->dwDrawAspect;

            if (NULL!=ppo)
                ppo->fe.dwAspect=pFE->dwAspect;

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);

            /*
             * Furthermore, if pFE->dwAspect is DVASPECT_ICON, get
             * the metafile which will always be the icon
             * representation.
             */
            SETFormatEtc(fe, CF_METAFILEPICT, DVASPECT_ICON, NULL
                , TYMED_MFPICT, -1);

            if (SUCCEEDED(pIDataObject->GetData(&fe, &stm)))
                {
                dwData=(DWORD)(UINT)stm.hGlobal;
                fRelease=TRUE;
                }
            }
        }

    fRet=m_pPG->TenantCreate(tType, pIDataObject, pFE, ppo, dwData);

    //Release the stm from the last GetData
    if (fRelease)
        ReleaseStgMedium(&stm);

    if (fRet)
        {
        //Disable Printer Setup once we've created a tenant.
        m_fPrintSetup=FALSE;
        FDirtySet(TRUE);
        }

    return fRet;
    }

/*
 * CPatronDoc::FQueryEnableEditLinks
 *
 * Purpose:
 *  Determines whether or not the Edit Links menu item should be
 *  enabled on the frame window menu.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE to enable the item, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryEnableEditLinks(void)
    {
    return m_pPG->FQueryLinksInPage();
    }

/*
 * CPatronDoc::EditLinks
 *
 * Purpose:
 *  Invokes the links dialog in which the end-user can manipulate
 *  all the links in the current page.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame window to use as the dialog
 *                  parent.
 *
 * Return Value:
 *  BOOL            TRUE if the function was successful,
 *                  FALSE otherwise.
 */

BOOL CPatronDoc::EditLinks(HWND hWndFrame)
    {
    UINT                    uRet;
    OLEUIEDITLINKS          el;
    PCIOleUILinkContainer   pIUILinks;

    memset(&el, 0, sizeof(el));
    el.cbStruct=sizeof(el);
    el.hWndOwner=hWndFrame;

    if (!m_pPG->GetUILinkContainer(&pIUILinks))
        return FALSE;

    el.lpOleUILinkContainer=pIUILinks;
    uRet=OleUIEditLinks(&el);

    //Only the IOleUILinkContainer interface will know dirtying.
    m_fDirty=pIUILinks->m_fDirty;

    el.lpOleUILinkContainer->Release();
    return TRUE;
    }

/*
 * CPatronDoc::ShowOrQueryObjectTypes
 *
 * Purpose:
 *  If querying, we return whether or not ShowObjects is active
 *  in this document.  Otherwise pass-through to
 *  CPages::ShowObjectTypes.
 *
 * Parameters:
 *  fQuery          BOOL indicating query or set.
 *  fShow           BOOL indicating to show the type or not.
 *
 * Return Value:
 *  None
 */

BOOL CPatronDoc::ShowOrQueryObjectTypes(BOOL fQuery, BOOL fShow)
    {
    if (fQuery)
        return m_fShowTypes;

    m_fShowTypes=fShow;
    m_pPG->ShowObjectTypes(fShow);
    return TRUE;
    }

/*
 * CPatronDoc::NewPage
 *
 * Purpose:
 *  Creates a new page in the document's pages control after the
 *  current page.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::NewPage(void)
    {
    FDirtySet(TRUE);
    return m_pPG->PageInsert(0);
    }

/*
 * CPatronDoc::DeletePage
 *
 * Purpose:
 *  Deletes the current page from the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the now current page.
 */

UINT CPatronDoc::DeletePage(void)
    {
    FDirtySet(TRUE);
    return m_pPG->PageDelete(0);
    }

/*
 * CPatronDoc::NextPage
 *
 * Purpose:
 *  Shows the next page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::NextPage(void)
    {
    UINT        iPage;

    iPage=m_pPG->CurPageGet();
    return m_pPG->CurPageSet(++iPage);
    }

/*
 * CPatronDoc::PreviousPage
 *
 * Purpose:
 *  Shows the previous page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::PreviousPage(void)
    {
    UINT        iPage;

    //If iPage is zero, then we wrap around to the end.
    iPage=m_pPG->CurPageGet();
    return m_pPG->CurPageSet(--iPage);
    }

/*
 * CPatronDoc::FirstPage
 *
 * Purpose:
 *  Shows the first page page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the new page.
 */

UINT CPatronDoc::FirstPage(void)
    {
    return m_pPG->CurPageSet(0);
    }

/*
 * CPatronDoc::LastPage
 *
 * Purpose:
 *  Shows the last page in the pages window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the last page.
 */

UINT CPatronDoc::LastPage(void)
    {
    return m_pPG->CurPageSet(NOVALUE);
    }

/*
 * CPatronDoc::Rename
 *
 * Purpose:
 *  Overrides the normal rename to include notification of tenants
 *  in this document of the new name.  This is so embedded objects
 *  are told the correct name through IOleObject::SetHostNames.
 *
 * Parameters:
 *  pszFile         LPTSTR to the new filename.
 *
 * Return Value:
 *  None
 */

void CPatronDoc::Rename(LPTSTR pszFile)
    {
    LPMONIKER   pmk;

    //We don't need to change the base class, just augment...
    CDocument::Rename(pszFile);

    //Unregister the old moniker (m_dwRegROT set to zero).
    INOLE_RevokeAsRunning(&m_dwRegROT);

    if (NULL==pszFile)
        return;

    CreateFileMoniker(pszFile, &pmk);

    if (NULL!=pmk)
        {
        LPMONIKER   pmkAll;

        INOLE_RegisterAsRunning(this, pmk, 0, &m_dwRegROT);

        //Give a moniker to linked objects in tenants.
        m_pPG->NotifyTenantsOfRename(pszFile, pmk);

        //Register a File!"\" wildcard moniker as well
        CreateItemMoniker(TEXT("!"), TEXT("\\"), &pmkAll);

        if (NULL!=pmkAll)
            {
            LPMONIKER   pmkWild;

            INOLE_RevokeAsRunning(&m_dwRegROTWild);
            pmk->ComposeWith(pmkAll, FALSE, &pmkWild);

            if (NULL!=pmkWild)
                {
                INOLE_RegisterAsRunning(this, pmk, 0
                    , &m_dwRegROTWild);
                pmkWild->Release();
                }

            pmkAll->Release();
            }

        //No need for us to hold on to this.
        pmk->Release();
        }

    return;
    }

/*
 * CPatronDoc::InsertObject
 *
 * Purpose:
 *  Retrieves a CLSID or a filename from the Insert Object dialog
 *  box and creates an object using those identifiers.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPatronDoc::InsertObject(HWND hWndFrame)
    {
    OLEUIINSERTOBJECT   io;
    DWORD               dwData=0;
    TCHAR               szFile[CCHPATHMAX];
    UINT                uTemp;
    BOOL                fRet=FALSE;

    if (NULL==m_pPG)
        return FALSE;

    memset(&io, 0, sizeof(io));

    io.cbStruct=sizeof(io);
    io.hWndOwner=hWndFrame;

    szFile[0]=0;
    io.lpszFile=szFile;
    io.cchFile=CCHPATHMAX;

    //We now support linking, so remove the IOF_DISABLELINK flag.
    io.dwFlags=IOF_SHOWINSERTCONTROL | IOF_SELECTCREATENEW;

    uTemp=OleUIInsertObject(&io);

    if (OLEUI_OK==uTemp)
        {
        TENANTTYPE      tType;
        LPVOID          pv;
        FORMATETC       fe;

        SETDefFormatEtc(fe, 0, TYMED_NULL);

        if (io.dwFlags & IOF_SELECTCREATENEW)
            {
            tType=TENANTTYPE_EMBEDDEDOBJECT;
            pv=&io.clsid;
            }
        else
            {
            if (io.dwFlags & IOF_CHECKLINK)
                tType=TENANTTYPE_LINKEDFILE;
            else
                tType=TENANTTYPE_EMBEDDEDFILE;

            pv=szFile;
            }

        if ((io.dwFlags & IOF_CHECKDISPLAYASICON)
            && NULL!=io.hMetaPict)
            {
            fe.dwAspect=DVASPECT_ICON;
            dwData=(DWORD)(UINT)io.hMetaPict;
            }

        fRet=m_pPG->TenantCreate(tType, pv, &fe, NULL, dwData);

        //Free this regardless of what we do with it.
        INOLE_MetafilePictIconFree(io.hMetaPict);

        if (fRet)
            {
            //Disable Printer Setup once we've created a tenant.
            m_fPrintSetup=FALSE;
            FDirtySet(TRUE);
            }
        }

    return fRet;
    }

/*
 * CPatronDoc::ActivateObject
 *
 * Purpose:
 *  Executes a verb on the currently selected object.
 *
 * Parameters:
 *  iVerb           LONG of the selected verb.
 *  pMSG            LPMSG that caused the activation.
 *
 * Return Value:
 *  None
 */

void CPatronDoc::ActivateObject(LONG iVerb, LPMSG pMSG)
    {
    m_pPG->ActivateObject(iVerb, pMSG);
    return;
    }

/*
 * CPatronDoc::ConvertObject
 *
 * Purpose:
 *  Invokes the Convert dialog on the current object.  Here it's
 *  just a pass-through to the pages.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame window.
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful, FALSE otherwise.
 */

BOOL CPatronDoc::ConvertObject(HWND hWndFrame)
    {
    return m_pPG->ConvertObject(hWndFrame);
    }

/*
 * CPatronDoc::NoObjectFrameTools
 *
 * Purpose:
 *  Saves/retrieves a flag with the document that says whether or
 *  not the object active in this document has *frame* tools.  We
 *  need this to tell the frame what to do when document activation
 *  switches.
 *
 * Parameters:
 *  fNoTools        BOOL TRUE to indicate the object has no tools.
 *                  FALSE says the object has tools.
 *  fSet            BOOL indicating if this is a set (TRUE) or get
 *                  (FALSE) operation.
 *
 * Return Value:
 *  BOOL            Value of flag for Get operations, previous value
 *                  for Set operations.
 */

BOOL CPatronDoc::NoObjectFrameTools(BOOL fNoTools, BOOL fSet)
    {
    BOOL        fLast=m_fNoObjectFrameTools;

    if (fSet)
        m_fNoObjectFrameTools=fNoTools;

    return fLast;
    }

//CHAPTER24MOD
/*
 * CPatronDoc::FToggleOrQueryDesignMode
 * CPatronDoc::FToggleOrQueryUIDead
 * CPatronDoc::FToggleOrQueryHatchHandles
 *
 * Purpose:
 *  If querying, we return whether or not a mode is active
 *  in this document.  Otherwise pass-through to CPages.
 *
 * Parameters:
 *  fQuery          BOOL indicating query or set.
 *  <fMode>         BOOL indicating we're in the mode state
 *
 * Return Value:
 *  BOOL            State of the mode if fQuery is TRUE.
 */

BOOL CPatronDoc::FToggleOrQueryDesignMode(BOOL fQuery, BOOL fDesign)
    {
    if (fQuery)
        return m_fDesignMode;

    m_fDesignMode=fDesign;
    m_pPG->ToggleDesignMode(fDesign);
    return TRUE;
    }

BOOL CPatronDoc::FToggleOrQueryUIDead(BOOL fQuery, BOOL fUIDead)
    {
    if (fQuery)
        return m_fUIDead;

    m_fUIDead=fUIDead;
    m_pPG->ToggleUIDead(fUIDead);
    return TRUE;
    }

BOOL CPatronDoc::FToggleOrQueryHatchHandles(BOOL fQuery
    , BOOL fHatchHandles)
    {
    if (fQuery)
        return m_fHatchHandles;

    m_fHatchHandles=fHatchHandles;
    m_pPG->ToggleHatchHandles(fHatchHandles);
    return TRUE;
    }

/*
 * CPatronDoc::FQueryEnableEvents
 * CPatronDoc::AssignEvents
 * CPatronDoc::TryMnemonic
 * CPatronDoc::FIsButton
 * CPatronDoc::MarkButton
 *
 * Purpose:
 *  Pass-throughs
 */

BOOL CPatronDoc::FQueryEnableEvents(void)
    {
    return m_pPG->FQueryEnableEvents();
    }

void CPatronDoc::AssignEvents(HWND hWndFrame)
    {
    m_pPG->AssignEvents(hWndFrame);
    return;
    }

BOOL CPatronDoc::TryMnemonic(LPMSG pMsg)
    {
    return m_pPG->TryMnemonic(pMsg);
    }
//End CHAPTER24MOD


DRAGDROP.CPP

/*
 * DRAGDROP.CPP
 * Patron Chapter 24
 *
 * Member functions of the CPages class concerned with drag-drop
 * and other object-placement functionality.  Moved here to clean
 * up CPAGES.CPP somewhat.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPages::UTestDroppablePoint
 *
 * Purpose:
 *  Returns if the point in pptl is on the paper in the current page
 *  and alternately if it's within horizontal or vertical inset
 *  regions.
 *
 * Parameters:
 *  pptl            PPOINTL containing the point in screen
 *                  coordinates.
 *
 * Return Value:
 *  UINT            A UDROP_* value.
 */

UINT CPages::UTestDroppablePoint(PPOINTL pptl)
    {
    POINT       pt;
    RECT        rc, rcT, rcC;
    UINT        uRet;

    POINTFROMPOINTL(pt, *pptl);
    ScreenToClient(m_hWnd, &pt);

    CalcBoundingRect(&rc, FALSE);

    GetClientRect(m_hWnd, &rcC);
    IntersectRect(&rcT, &rc, &rcC);

    //Check for at least a client area hit.
    if (!PtInRect(&rcT, pt))
        return UDROP_NONE;

    uRet=UDROP_CLIENT;

    //Scroll checks happen on client area, not document area
    if (PtInRect(&rcC, pt))
        {
        //Check horizontal inset
        if (pt.x <= rcC.left+(int)m_uScrollInset)
            uRet |= UDROP_INSETLEFT;
        else if (pt.x >= rcC.right-(int)m_uScrollInset)
            uRet |= UDROP_INSETRIGHT;

        //Check vertical inset
        if (pt.y <= rcC.top+(int)m_uScrollInset)
            uRet |= UDROP_INSETTOP;
        else if (pt.y >= rcC.bottom-(int)m_uScrollInset)
            uRet |= UDROP_INSETBOTTOM;
        }

    return uRet;
    }

/*
 * CPages::DrawDropTargetRect
 *
 * Purpose:
 *  Draws a dotted rectangle on the Pages window to show where
 *  a drop might occur.  This is a toggle function.
 *
 * Parameters:
 *  pptl            PPOINTL containing the upper-left point
 *                  in screen coordinates.
 *  pszl            LPSIZEL containing the rect extents in device
 *                  units.
 *
 * Return Value:
 *  None
 */

void CPages::DrawDropTargetRect(PPOINTL pptl, LPSIZEL pszl)
    {
    POINT           pt;
    RECT            rc, rcT;
    HDC             hDC;

    if (NULL==pptl && NULL==pszl)
        {
        /*
         * This case is used from WM_PAINT in pagewin.cpp so we can
         * control the proper visibility of the drag rectangle when
         * we're scrolling.  If drag-drop happens between two apps,
         * then any ScrollWindow will cause a WM_PAINT after we
         * leave DragOver, so that paint would normally overwrite
         * the last drawn rectangle.  To alleviate that, WM_PAINT
         * will remove the last rect by sending us NULLs, paint,
         * then reinstate the rectangle.
         */
        pptl=&m_ptlRect;
        pszl=&m_szlRect;
        }
    else
        {
        m_ptlRect.x=pptl->x;
        m_ptlRect.y=pptl->y;
        m_szlRect.cx=pszl->cx;
        m_szlRect.cy=pszl->cy;

        //Flag is only affected from IDropTarget, not WM_PAINT calls
        m_fDragRectShown=!m_fDragRectShown;
        }

    POINTFROMPOINTL(pt, *pptl);
    ScreenToClient(m_hWnd, &pt);
    SetRect(&rc, pt.x, pt.y, pt.x+(int)pszl->cx
        , pt.y+(int)pszl->cy);

    CalcBoundingRect(&rcT, FALSE);
    IntersectRect(&rc, &rc, &rcT);

    if (!IsRectEmpty(&rc))
        {
        hDC=GetDC(m_hWnd);
        DrawFocusRect(hDC, &rc);
        ReleaseDC(m_hWnd, hDC);
        }

    return;
    }

/*
 * CPages::AdjustPosition
 *
 * Purpose:
 *  Adjusts a point for the scrolling offset and then converts it
 *  and a size from device into LOMETRIC units.
 *
 * Parameters:
 *  pptl            PPOINTL to adjust and convert
 *  pszl            LPSIZEL to convert
 *
 * Return Value:
 *  None
 */

void CPages::AdjustPosition(PPOINTL pptl, LPSIZEL pszl)
    {
    RECT        rc;

    SetRect(&rc, m_xPos+(int)pptl->x
        , m_yPos+(int)pptl->y, (int)pszl->cx, (int)pszl->cy);

    RectConvertMappings(&rc, NULL, FALSE);

    pptl->x=rc.left;
    pptl->y=rc.top;

    pszl->cx=rc.right;
    pszl->cy=rc.bottom;

    return;
    }


DROPSRC.CPP

/*
 * DROPSRC.CPP
 * Patron Chapter 24
 *
 * Implementation of a DropSource object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CDropSource::CDropSource
 * CDropSource::~CDropSource
 *
 * Constructor Parameters:
 *  None
 */

CDropSource::CDropSource(void)
    {
    m_cRef=0;
    return;
    }

CDropSource::~CDropSource(void)
    {
    return;
    }

/*
 * CDropSource::QueryInterface
 * CDropSource::AddRef
 * CDropSource::Release
 *
 * Purpose:
 *  IUnknown members for CDropSource object.
 */

STDMETHODIMP CDropSource::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDropSource==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDropSource::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropSource::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDropSource::QueryDragContinue
 *
 * Purpose:
 *  Determines whether to continue a drag operation or cancel it.
 *
 * Parameters:
 *  fEsc            BOOL indicating that the ESC key was pressed.
 *  grfKeyState     DWORD providing states of keys and mouse buttons
 *
 * Return Value:
 *  HRESULT         DRAGDROP_S_CANCEL to stop the drag,
 *                  DRAGDROP_S_DROP to drop the data where it is,
 *                  or NOERROR to continue.
 */

STDMETHODIMP CDropSource::QueryContinueDrag(BOOL fEsc, DWORD grfKeyState)
    {
    if (fEsc)
        return ResultFromScode(DRAGDROP_S_CANCEL);

    if (!(grfKeyState & MK_LBUTTON))
        return ResultFromScode(DRAGDROP_S_DROP);

    return NOERROR;
    }

/*
 * CDropSource::GiveFeedback
 *
 * Purpose:
 *  Provides cursor feedback to the user since the source task
 *  always has the mouse capture.  We can also provide any other
 *  type of feedback above cursors if we so desire.
 *
 * Parameters:
 *  dwEffect        DWORD effect flags returned from the last target
 *
 * Return Value:
 *  HRESULT         NOERROR if you set a cursor yourself or
 *                  DRAGDROP_S_USEDEFAULTCURSORS to let OLE do
 *                  the work.
 */

STDMETHODIMP CDropSource::GiveFeedback(DWORD dwEffect)
    {
    return ResultFromScode(DRAGDROP_S_USEDEFAULTCURSORS);
    }


DROPTGT.CPP

/*
 * DROPTGT.CPP
 * Patron Chapter 24
 *
 * Implementation of a DropTarget object
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CDropTarget::CDropTarget
 * CDropTarget::~CDropTarget
 *
 * Constructor Parameters:
 *  pDoc            PCPatronDoc of the window containing us.
 */

CDropTarget::CDropTarget(PCPatronDoc pDoc)
    {
    m_cRef=0;
    m_pDoc=pDoc;

    m_pIDataObject=NULL;
    return;
    }

CDropTarget::~CDropTarget(void)
    {
    return;
    }

/*
 * CDropTarget::QueryInterface
 * CDropTarget::AddRef
 * CDropTarget::Release
 *
 * Purpose:
 *  IUnknown members for CDropTarget object.
 */

STDMETHODIMP CDropTarget::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDropTarget==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDropTarget::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropTarget::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDropTarget::DragEnter
 *
 * Purpose:
 *  Indicates that data in a drag operation has been dragged over
 *  our window that's a potential target.  We are to decide if it's
 *  something in which we're interested.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT providing the source data.
 *  grfKeyState     DWORD flags: states of keys and mouse buttons.
 *  pt              POINTL coordinates in the client space of
 *                  the document.
 *  pdwEffect       LPDWORD into which we'll place the appropriate
 *                  effect flag for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragEnter(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    PCPages         ppg=m_pDoc->m_pPG;
    HWND            hWnd;
    FORMATETC       fe;
    STGMEDIUM       stm;
    UINT            uRet;

    m_fFeedback=FALSE;
    m_pIDataObject=NULL;

    if (!m_pDoc->FQueryPasteFromData(pIDataSource, &fe, NULL))
        {
        *pdwEffect=DROPEFFECT_NONE;
        return NOERROR;
        }

    //Check if we can link from this data object as well.
    ppg->m_fLinkAllowed
        =(NOERROR==OleQueryLinkFromData(pIDataSource));

    //We never allow it dragging in ourselves.
    ppg->m_fLinkAllowed &= !ppg->m_fDragSource;

    //Check if this is a valid drop point.
    uRet=ppg->UTestDroppablePoint(&pt);
    ppg->m_uLastTest=uRet;

    if (UDROP_NONE==uRet)
        *pdwEffect=DROPEFFECT_NONE;
    else
        {
        //Default is move if we can, in fact drop here.
        *pdwEffect=DROPEFFECT_MOVE;

        if (grfKeyState & MK_CONTROL)
            {
            if (ppg->m_fLinkAllowed && (grfKeyState & MK_SHIFT))
                *pdwEffect=DROPEFFECT_LINK;
            else
                *pdwEffect=DROPEFFECT_COPY;
            }
        }

    m_pIDataObject=pIDataSource;
    m_pIDataObject->AddRef();

    /*
     * Determine the size of the data, if we can.  The default is
     * a small rectangle since we can't easily tell what size
     * something will be if we're pulling in a metafile or bitmap.
     * It's not a good idea to render it here with GetData just to
     * find that out. We only know the size if it's our own object
     * in which case a GetData will be fast.
     */

    if (fe.cfFormat==m_pDoc->m_cf)
        {
        if (SUCCEEDED(pIDataSource->GetData(&fe, &stm)))
            {
            PPATRONOBJECT   ppo;
            RECT            rc;

            ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);

            SetRect(&rc, (int)ppo->szl.cx, -(int)ppo->szl.cy, 0, 0);
            RectConvertMappings(&rc, NULL, TRUE);
            SETSIZEL(m_szl, rc.left, rc.top);

            m_ptPick=ppo->ptlPick;
            m_fe=ppo->fe;

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);
            }
        }
    else
        {
        SETSIZEL(m_szl, 30, 30);
        m_ptPick.x=0;
        m_ptPick.y=0;
        m_fe.cfFormat=0;

        /*
         * Try to get CFSTR_OBJECTDESCRIPTOR which might have a size
         * and a pick point.  If it exists, then always use the
         * point but still default to a 30*30 size if the sizes
         * are zero.
         */
        uRet=RegisterClipboardFormat(CFSTR_OBJECTDESCRIPTOR);
        SETDefFormatEtc(fe, uRet, TYMED_HGLOBAL);

        if (SUCCEEDED(pIDataSource->GetData(&fe, &stm)))
            {
            LPOBJECTDESCRIPTOR  pOD;

            pOD=(LPOBJECTDESCRIPTOR)GlobalLock(stm.hGlobal);

            //Get the size, converting to LOMETRIC.
            if (0!=pOD->sizel.cx && 0!=pOD->sizel.cy)
                {
                XformSizeInHimetricToPixels(NULL, &pOD->sizel
                    , &m_szl);
                }

            //POINTL and SIZEL are interchangeable
            XformSizeInHimetricToPixels(NULL, (LPSIZEL)&pOD->pointl
                , (LPSIZEL)&m_ptPick);

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);
            }
        }

    //Bring the document window up front, show what a drop will do.
    hWnd=m_pDoc->Window();
    BringWindowToTop(hWnd);
    UpdateWindow(hWnd);

    ppg->m_uVScrollCode=NOVALUE;
    ppg->m_uHScrollCode=NOVALUE;
    m_fPendingRepaint=FALSE;

    pt.x-=m_ptPick.x;
    pt.y-=m_ptPick.y;

    m_ptLast=pt;
    m_fFeedback=TRUE;
    ppg->DrawDropTargetRect(&pt, &m_szl);

    return NOERROR;
    }

/*
 * CDropTarget::DragOver
 *
 * Purpose:
 *  Indicates that the mouse was moved inside the window represented
 *  by this drop target.  This happens on every WM_MOUSEMOVE, so
 *  this function should be very efficient.
 *
 * Parameters:
 *  grfKeyState     DWORD providing the current keyboard and
 *                  mouse states
 *  pt              POINTL where the mouse currently is.
 *  pdwEffect       LPDWORD in which to store the effect flag
 *                  for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragOver(DWORD grfKeyState, POINTL pt
    , LPDWORD pdwEffect)
    {
    PCPages     ppg=m_pDoc->m_pPG;
    UINT        uRet, uLast;
    UINT        xPos, yPos;

    *pdwEffect=DROPEFFECT_NONE;

    if (NULL==m_pIDataObject)
        return NOERROR;

    //Check if this is still a valid point.  uRet used below as well
    uRet=ppg->UTestDroppablePoint(&pt);

    if (UDROP_NONE==uRet)
        *pdwEffect=DROPEFFECT_NONE;
    else
        {
        //Store these before possibly ORing in DROPEFFECT_SCROLL
        *pdwEffect=DROPEFFECT_MOVE;

        if (grfKeyState & MK_CONTROL)
            {
            if (ppg->m_fLinkAllowed && (grfKeyState & MK_SHIFT))
                *pdwEffect=DROPEFFECT_LINK;
            else
                *pdwEffect=DROPEFFECT_COPY;
            }
        }

    //If we haven't moved and we are not scrolling, then we're done.
    if ((pt.x-m_ptPick.x==m_ptLast.x)
        && (pt.y-m_ptPick.y==m_ptLast.y)
        && !((UDROP_INSETHORZ|UDROP_INSETVERT) & ppg->m_uLastTest))
        {
        return NOERROR;
        }

    //Remove the last feedback rectangle.
    if (m_fFeedback)
        ppg->DrawDropTargetRect(&m_ptLast, &m_szl);

    uLast=ppg->m_uLastTest;
    ppg->m_uLastTest=uRet;

    if (UDROP_NONE==uRet)
        {
        //If we're now an invalid point, better repaint as necessary
        if (m_fPendingRepaint)
            {
            UpdateWindow(ppg->m_hWnd);
            m_fPendingRepaint=FALSE;
            }

        ppg->m_uVScrollCode=NOVALUE;
        ppg->m_uHScrollCode=NOVALUE;
        m_fFeedback=FALSE;
        return NOERROR;
        }

    /*
     * Scrolling is a little tricky:  We get a DragOver pulse even
     * if we didn't move.  First we have to delay scrolling for
     * ppg->m_uScrollDelay clock ticks which we can determine using
     * GetTickCount.  Timers do not work here since we may not be
     * yielding to our message loop.
     *
     * Once we know we are scrolling then we determine if we
     * scroll again or if we reset the scrolling state.
     */

    if ((UDROP_INSETHORZ & uLast) && !(UDROP_INSETHORZ & uRet))
        ppg->m_uHScrollCode=NOVALUE;

    if (!(UDROP_INSETHORZ & uLast) && (UDROP_INSETHORZ & uRet))
        {
        ppg->m_dwTimeLast=GetTickCount();
        ppg->m_uHScrollCode=(0!=(UDROP_INSETLEFT & uRet))
            ? SB_LINELEFT : SB_LINERIGHT; //Same as UP & DOWN codes.
        }

    if ((UDROP_INSETVERT & uLast) && !(UDROP_INSETVERT & uRet))
        ppg->m_uVScrollCode=NOVALUE;

    if (!(UDROP_INSETVERT & uLast) && (UDROP_INSETVERT & uRet))
        {
        ppg->m_dwTimeLast=GetTickCount();
        ppg->m_uVScrollCode=(0!=(UDROP_INSETTOP & uRet))
            ? SB_LINEUP : SB_LINEDOWN;
        }

    //Only change the last time if ALL scrolling stops.
    if (NOVALUE==ppg->m_uHScrollCode && NOVALUE==ppg->m_uVScrollCode)
        ppg->m_dwTimeLast=0L;

    //Set the scroll effect on any inset hit.
    if ((UDROP_INSETHORZ | UDROP_INSETVERT) & uRet)
        *pdwEffect |= DROPEFFECT_SCROLL;

    xPos=ppg->m_xPos;
    yPos=ppg->m_yPos;

    //Has the delay elapsed?  We can scroll if so
    if (ppg->m_dwTimeLast!=0
        && (GetTickCount()-ppg->m_dwTimeLast)
        > (DWORD)ppg->m_uScrollDelay)
        {
        if (NOVALUE!=ppg->m_uHScrollCode)
            {
            m_fPendingRepaint=TRUE;
            SendMessage(ppg->m_hWnd, WM_HSCROLL
                , ppg->m_uHScrollCode, 0L);
            }

        if (NOVALUE!=ppg->m_uVScrollCode)
            {
            m_fPendingRepaint=TRUE;
            SendMessage(ppg->m_hWnd, WM_VSCROLL
                , ppg->m_uVScrollCode, 0L);
            }
        }

    //If we didn't scroll but have a pending repaint, do it now.
    if (xPos==ppg->m_xPos && yPos==ppg->m_yPos && m_fPendingRepaint)
        {
        UpdateWindow(ppg->m_hWnd);
        m_fPendingRepaint=FALSE;
        }

    pt.x-=m_ptPick.x;
    pt.y-=m_ptPick.y;

    m_ptLast=pt;
    m_fFeedback=TRUE;
    ppg->DrawDropTargetRect(&pt, &m_szl);

    return NOERROR;
    }

/*
 * CDropTarget::DragLeave
 *
 * Purpose:
 *  Informs the drop target that the operation has left its window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragLeave(void)
    {
    PCPages         ppg=m_pDoc->m_pPG;

    if (NULL==m_pIDataObject)
        return NOERROR;

    //Stop scrolling
    ppg->m_uHScrollCode=NOVALUE;
    ppg->m_uVScrollCode=NOVALUE;

    if (m_fPendingRepaint)
        UpdateWindow(ppg->m_hWnd);

    //Remove the last feedback rectangle.
    if (m_fFeedback)
        ppg->DrawDropTargetRect(&m_ptLast, &m_szl);

    m_fFeedback=FALSE;
    m_pIDataObject->Release();
    return NOERROR;
    }

/*
 * CDropTarget::Drop
 *
 * Purpose:
 *  Instructs the drop target to paste the data that was just now
 *  dropped on it.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT from which we'll paste.
 *  grfKeyState     DWORD providing current keyboard/mouse state.
 *  pt              POINTL at which the drop occurred.
 *  pdwEffect       LPDWORD in which to store what you did.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::Drop(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    PCPages         ppg=m_pDoc->m_pPG;
    BOOL            fRet=TRUE;
    FORMATETC       fe;
    TENANTTYPE      tType;
    PATRONOBJECT    po;
    POINT           ptS;

    *pdwEffect=DROPEFFECT_NONE;

    if (NULL==m_pIDataObject)
        return ResultFromScode(E_FAIL);

    if (UDROP_NONE==ppg->UTestDroppablePoint(&pt))
        return ResultFromScode(E_FAIL);

    //Stop scrolling
    ppg->m_uHScrollCode=NOVALUE;
    ppg->m_uVScrollCode=NOVALUE;

    if (m_fPendingRepaint)
        UpdateWindow(ppg->m_hWnd);

    //2.  Remove the UI feedback
    if (m_fFeedback)
        ppg->DrawDropTargetRect(&m_ptLast, &m_szl);

    m_pIDataObject->Release();

    /*
     * Check if we can do the paste, and if so, tell our pasting
     * mechanism exactly where to place us.
     */
    pt.x-=m_ptPick.x;
    pt.y-=m_ptPick.y;

    POINTFROMPOINTL(ptS, pt);
    ScreenToClient(ppg->Window(), &ptS);
    POINTLFROMPOINT(po.ptl, ptS);

    //This is true if we didn't see placement data in DragEnter
    if (0!=m_fe.cfFormat)
        {
        po.szl.cx=m_szl.cx;         //We stored these positive
        po.szl.cy=-m_szl.cy;
        }
    else
        SETSIZEL(po.szl, 0, 0); //Ask object for its size.

    //Adjust for scrolling and mapping mode.
    ppg->AdjustPosition(&po.ptl, &po.szl);

    /*
     * If we're in the same document and moving, then we can just
     * stuff the Pages' m_ptDrop which will move us and return.
     */
    if (ppg->m_fDragSource && !(grfKeyState & MK_CONTROL))
        {
        *pdwEffect=DROPEFFECT_MOVE;
        ppg->m_fMoveInPage=TRUE;
        ppg->m_ptDrop=po.ptl;
        return NOERROR;
        }

    /*
     * Otherwise, paste either from another document or from
     * the same document which will always be a copy to the new
     * point.
     */

    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        {
        if (ppg->m_fLinkAllowed && (grfKeyState & MK_SHIFT))
            *pdwEffect=DROPEFFECT_LINK;
        else
            *pdwEffect=DROPEFFECT_COPY;
        }

    ppg->m_fMoveInPage=FALSE;

    /*
     * We know linking is desired if effect flag is set, so this
     * function will get the FORMATETC for linking.  Otherwise
     * FQueryPasteFromData will get the other FORMATETC to use.
     */
    if (DROPEFFECT_LINK==*pdwEffect)
        {
        fRet=m_pDoc->FQueryPasteLinkFromData(pIDataSource, &fe
            , &tType);
        }
    else
        fRet=m_pDoc->FQueryPasteFromData(pIDataSource, &fe, &tType);

    if (fRet)
        {
        //Copy the real format if we have placement data.
        po.fe=(m_pDoc->m_cf==fe.cfFormat) ? m_fe : fe;

        //Flag PasteFromData to use CFSTR_OBJECTDESCRIPTOR
        fRet=m_pDoc->PasteFromData(pIDataSource, &fe, tType
            , &po, 0, TRUE);
        }

    if (!fRet)
        return ResultFromScode(E_FAIL);

    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        *pdwEffect=DROPEFFECT_COPY;

    return NOERROR;
    }


EVENTS.CPP

/*
 * EVENTS.CPP
 * Patron Chapter 24
 *
 * Implementation of the Events dialog box, the CEventMap class,
 * and the events IDispatch on a tenant control site.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * EventsDlgProc
 *
 * Purpose:
 *  Dialog procedure for the dialog in which the user can assign
 *  actions to events.
 */

BOOL APIENTRY EventsDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    PCEventMap      pEM=NULL;
    HWND            hList;
    UINT            i, iEvent;

    COMMANDPARAMS(wID, wCode, hWndMsg);

   #ifdef WIN32
    pEM=(PCEventMap)GetProp(hDlg, PROP_POINTER);
   #else
    WORD            w1, w2;

    w1=(WORD)GetProp(hDlg, PROP_SELECTOR);
    w2=(WORD)GetProp(hDlg, PROP_OFFSET);

    pEM=(PCEventMap)MAKELP(w1, w2);
   #endif

    switch (iMsg)
        {
        case WM_INITDIALOG:
            pEM=(PCEventMap)lParam;

           #ifdef WIN32
            SetProp(hDlg, PROP_POINTER, (HANDLE)pEM);
           #else
            SetProp(hDlg, PROP_SELECTOR, (HANDLE)SELECTOROF(pEM));
            SetProp(hDlg, PROP_OFFSET,   (HANDLE)OFFSETOF(pEM));
           #endif

            /*
             * Fill the listbox with events and select the first
             * one.  The selection will cause an LBN_SELCHANGE
             * notification which will set the appropriate
             * radiobutton for the action.
             */

            hList=GetDlgItem(hDlg, IDC_EVENTLIST);

            for (i=0; i < pEM->m_cEvents; i++)
                {
                //Add the name of the event to the list
               #ifdef WIN32ANSI
                char        szTemp[40];

                WideCharToMultiByte(CP_ACP, 0
                    , pEM->m_pEventMap[i].bstrName, -1, szTemp
                    , 40, NULL, NULL);
                iEvent=(UINT)SendMessage(hList, LB_ADDSTRING, 0
                    , (LONG)(LPSTR)szTemp);
               #else
                iEvent=(UINT)SendMessage(hList, LB_ADDSTRING, 0
                    , (LONG)(LPSTR)pEM->m_pEventMap[i].bstrName);
               #endif

                if (LB_ERR!=iEvent)
                    {
                    //Give that item a pointer to the map data
                    SendMessage(hList, LB_SETITEMDATA, iEvent
                        , (LONG)&pEM->m_pEventMap[i]);
                    }
                }

            //Set the initial action for the first item
            SendMessage(hList, LB_SETCURSEL, 0, 0L);
            CheckAction(hDlg, hList);
            return TRUE;

        case WM_COMMAND:
            hList=GetDlgItem(hDlg, IDC_EVENTLIST);

            switch (wID)
                {
                case IDOK:
                    EndDialog(hDlg, 1);
                    break;

                case ID_TEST:
                    TestSelection(hList);
                    break;

                case IDC_EVENTLIST:
                    //Update the radiobuttons
                    if (LBN_SELCHANGE==wCode)
                        CheckAction(hDlg, hWndMsg);

                    //Double-click, same as hitting Test button
                    if (LBN_DBLCLK==wCode)
                        TestSelection(GetDlgItem(hDlg, IDC_EVENTLIST));
                    break;

                case IDC_BEEPNONE:
                case IDC_BEEPDEFAULT:
                case IDC_BEEPEXCLAMATION:
                case IDC_BEEPASTERISK:
                case IDC_BEEPHAND:
                case IDC_BEEPQUESTION:
                    UpdateAction(hList, wID);
                    break;
                }
            return TRUE;
        }

    return FALSE;
    }

/*
 * CheckAction
 *
 * Purpose:
 *  Sets the appropriate radiobutton for the current listbox
 *  selection
 *
 * Parameters:
 *  hDlg            HWND of the dialog.
 *  hList           HWND of the event list.
 *
 * Return Value:
 *  None
 */

void CheckAction(HWND hDlg, HWND hList)
    {
    UINT        i, idControl;
    PEVENTMAP   pMap;

    i=(UINT)SendMessage(hList, LB_GETCURSEL, 0, 0L);
    pMap=(PEVENTMAP)SendMessage(hList, LB_GETITEMDATA, i, 0L);

    if (LB_ERR==(LONG)pMap)
        return;

    //Map the action to the button
    switch (pMap->iAction)
        {
        case ACTION_NONE:            idControl=IDC_BEEPNONE; break;
        case ACTION_BEEPDEFAULT:     idControl=IDC_BEEPDEFAULT; break;
        case ACTION_BEEPASTERISK:    idControl=IDC_BEEPASTERISK; break;
        case ACTION_BEEPEXCLAMATION: idControl=IDC_BEEPEXCLAMATION; break;
        case ACTION_BEEPHAND:        idControl=IDC_BEEPHAND; break;
        case ACTION_BEEPQUESTION:    idControl=IDC_BEEPQUESTION; break;
        default:                     idControl=IDC_BEEPNONE; break;
        }

    CheckRadioButton(hDlg, IDC_BEEPNONE, IDC_BEEPQUESTION, idControl);
    return;
    }

/*
 * UpdateAction
 *
 * Purpose:
 *  Sets the appropriate action in the event map for the
 *  selected radiobutton.
 *
 * Parameters:
 *  hList           HWND of the event list.
 *  idControl       UINT identifier of the selected action control
 *
 * Return Value:
 *  None
 */

void UpdateAction(HWND hList, UINT idControl)
    {
    UINT        i;
    PEVENTMAP   pMap;

    i=(UINT)SendMessage(hList, LB_GETCURSEL, 0, 0L);
    pMap=(PEVENTMAP)SendMessage(hList, LB_GETITEMDATA, i, 0L);

    if (LB_ERR==(LONG)pMap)
        return;

    //Map the button to the action
    switch (idControl)
        {
        case IDC_BEEPNONE:        pMap->iAction=ACTION_NONE; break;
        case IDC_BEEPDEFAULT:     pMap->iAction=ACTION_BEEPDEFAULT; break;
        case IDC_BEEPASTERISK:    pMap->iAction=ACTION_BEEPASTERISK; break;
        case IDC_BEEPEXCLAMATION: pMap->iAction=ACTION_BEEPEXCLAMATION; break;
        case IDC_BEEPHAND:        pMap->iAction=ACTION_BEEPHAND; break;
        case IDC_BEEPQUESTION:    pMap->iAction=ACTION_BEEPQUESTION; break;
        default:                  pMap->iAction=ACTION_NONE; break;
        }

    return;
    }

/*
 * TestSelection
 *
 * Purpose:
 *  Executes the action associated with the currently selected
 *  event.
 *
 * Parameters:
 *  hList           HWND of the event list.
 *
 * Return Value:
 *  None
 */

void TestSelection(HWND hList)
    {
    UINT        i;
    PEVENTMAP   pMap;

    i=(UINT)SendMessage(hList, LB_GETCURSEL, 0, 0L);
    pMap=(PEVENTMAP)SendMessage(hList, LB_GETITEMDATA, i, 0L);

    //Event values corresond to MessageBeep values.
    if (LB_ERR!=(LONG)pMap)
        MessageBeep(pMap->iAction);

    return;
    }

//CEventMap implementations

/*
 * CEventMap::CEventMap
 * CEventMap::~CEventMap
 *
 * Parameters (Constructor):
 *  pITypeInfo      LPTYPEINFO from which to read event names.
 */

CEventMap::CEventMap(LPTYPEINFO pITypeInfo)
    {
    m_cEvents=0;
    m_pITypeInfo=pITypeInfo;

    if (NULL!=m_pITypeInfo)
        m_pITypeInfo->AddRef();

    m_pEventMap=NULL;
    return;
    }

CEventMap::~CEventMap(void)
    {
    if (NULL!=m_pITypeInfo)
        m_pITypeInfo->Release();

    if (NULL!=m_pEventMap)
        {
        UINT        i;

        //Be sure to clean up allocated BSTRs
        for (i=0; i < m_cEvents; i++)
            SysFreeString(m_pEventMap[i].bstrName);

        delete [] m_pEventMap;
        }

    return;
    }

/*
 * CEventMap::Init
 *
 * Purpose:
 *  Initializes the event map with any operation prone to failure.
 *  If this function fails, the caller should delete this object
 *  immediately as it is unusable.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded, FALSE otherwise
 */

BOOL CEventMap::Init(void)
    {
    LPTYPEATTR      pTA;
    UINT            i;

    if (NULL==m_pITypeInfo)
        return FALSE;

    if (FAILED(m_pITypeInfo->GetTypeAttr(&pTA)))
        return FALSE;

    m_cEvents=pTA->cFuncs;
    m_pITypeInfo->ReleaseTypeAttr(pTA);

    m_pEventMap=new EVENTMAP[m_cEvents];

    if (NULL==m_pEventMap)
        {
        m_cEvents=0;
        return FALSE;
        }

    for (i=0; i < m_cEvents; i++)
        {
        LPFUNCDESC     pFD;

        m_pEventMap[i].id=0;
        m_pEventMap[i].bstrName=NULL;
        m_pEventMap[i].iAction=ACTION_NONE;

        /*
         * The only piece of information we want from for each
         * event is the function name using ITypeInfo::GetNames.
         *
         * A more sophisticated container will probably save
         * more information about each event here (such as
         * parameter names and so forth) or access it dynamically
         * when the end user wants to write code for events.
         */

        if (SUCCEEDED(m_pITypeInfo->GetFuncDesc(i, &pFD)))
            {
            UINT        cNames;
            HRESULT     hr;

            /*
             * Since we only want the function name, we ask
             * ITypeInfo::GetNames for only one function and pass
             * the address of our one BSTR to it.  If we wanted all
             * the names from ITypeInfo, then we'd allocate an
             * array of BSTRs with "new BSTR[pFD->cParams+1]"
             * and pass pFD->cParams+1 to GetNames below instead
             * of just 1.  In either case, GetNames allocates
             * the string and stores the pointer to it in our
             * variable.
             */

            m_pEventMap[i].id=pFD->memid;

            hr=m_pITypeInfo->GetNames(pFD->memid
                , &m_pEventMap[i].bstrName, 1, &cNames);

            m_pITypeInfo->ReleaseFuncDesc(pFD);
            }
        }

    return TRUE;
    }

/*
 * CEventMap::Set
 *
 * Purpose:
 *  Sets the event mapping of a specific ID to a given action.
 *  To clear an event, call this function with the ID and
 *  ACTION_NONE.
 *
 * Parameters:
 *  id              DISPID of the event ID.
 *  iAction         EVENTACTION to assign.
 *
 * Return Value:
 *  BOOL            TRUE if the assignment happened, FALSE otherwise.
 */

BOOL CEventMap::Set(DISPID id, EVENTACTION iAction)
    {
    BOOL        fRet=FALSE;

    if (NULL!=m_pEventMap)
        {
        UINT        i;

        for (i=0; i < m_cEvents; i++)
            {
            if (m_pEventMap[i].id==id)
                {
                m_pEventMap[i].iAction=iAction;
                fRet=TRUE;
                }
            }
        }

    return fRet;
    }

/*
 * CEventMap::Get
 *
 * Purpose:
 *  Retrieves the event assignment for a given ID.
 *
 * Parameters:
 *  id              DISPID of the event ID.
 *
 * Return Value:
 *  EVENTACTION     The action assigned to this ID.  ACTION_NONE
 *                  if the ID is invalid.
 */

EVENTACTION CEventMap::Get(DISPID id)
    {
    EVENTACTION iAction=ACTION_NONE;

    if (NULL!=m_pEventMap)
        {
        UINT        i;

        //Scan the list looking for the event
        for (i=0; i < m_cEvents; i++)
            {
            if (m_pEventMap[i].id==id)
                {
                iAction=m_pEventMap[i].iAction;
                break;
                }
            }
        }

    return iAction;
    }

/*
 * CEventMap::Serialize
 * CEventMap::Deserialize
 *
 * Purpose:
 *  Writes or reads the mappings from DISPID to actions
 *  into or from a stream.
 *
 * Parameters:
 *  pIStream        LPSTREAM into which to write or from which to
 *                  read.
 *
 * Return Value:
 *  None
 */

void CEventMap::Serialize(LPSTREAM pIStream)
    {
    EVENTMAP        emTemp;
    ULONG           cbWrite=sizeof(DISPID)+sizeof(EVENTACTION);

    if (NULL==pIStream)
        return;

    /*
     * Loop through all the IDs and write the ID and the action
     * mapping only.  We don't need the event name because that
     * will be retrieved when the control is again loaded.
     *
     * Writing these pieces of info means writing the first
     * so many bytes of each EVENTMAP structure, ignoring the
     * BSTR of the name.
     */

    if (NULL!=m_pEventMap)
        {
        UINT        i;

        for (i=0; i < m_cEvents; i++)
            pIStream->Write(&m_pEventMap[i], cbWrite, NULL);
        }

    /*
     * Finish off by writing a terminating EVENTMAP structure
     * where the action is ACTION_TAILING which only have
     * meaning here.  The ID is ignored in this tail.
     */

    emTemp.id=0;
    emTemp.iAction=ACTION_TAILING;
    pIStream->Write(&emTemp, cbWrite, NULL);

    return;
    }

void CEventMap::Deserialize(LPSTREAM pIStream)
    {
    if (NULL==pIStream)
        return;

    /*
     * When reading back the event mappings we have to be
     * careful:  the control's event set might have changed
     * in the meantime so some events may no longer exist and
     * there may be new events.  Therefore we read each mapping
     * one at a time (until we hit the tailing map) and find
     * the ID in the current memory event map.  When we find
     * a match we update the action in memory.
     */

    if (NULL==m_pEventMap)
        return;

    while (TRUE)
        {
        ULONG       cbRead=sizeof(DISPID)+sizeof(EVENTACTION);
        HRESULT     hr;
        EVENTMAP    em;

        hr=pIStream->Read(&em, cbRead, NULL);

        //Failure to read means a stream problem, to abort
        if (FAILED(hr))
            break;

        //If we hit the tail, we're done
        if (ACTION_TAILING==em.iAction)
            break;

        //Assign the action to the ID, if it exists
        Set(em.id, em.iAction);
        }

    return;
    }

//Events IDispatch

/*
 * CDispatchEvents::CDispatchEvents
 * CDispatchEvents::~CDispatchEvents
 *
 * Parameters (Constructor):
 *  pTen            PCTenant of the tenant we're in.
 */

CDispatchEvents::CDispatchEvents(PCTenant pTen)
    {
    m_cRef=0;
    m_pTen=pTen;
    return;
    }

CDispatchEvents::~CDispatchEvents(void)
    {
    return;
    }

/*
 * CDispatchEvents::QueryInterface
 * CDispatchEvents::AddRef
 * CDispatchEvents::Release
 *
 * Purpose:
 *  IUnknown members for CDispatchEvents object.
 */

STDMETHODIMP CDispatchEvents::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDispatch==riid
        || m_pTen->m_iidEvents==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CDispatchEvents::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDispatchEvents::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CDispatchEvents::GetTypeInfoCount
 * CDispatchEvents::GetTypeInfo
 * CDispatchEvents::GetIDsOfNames
 *
 * Purpose:
 *  These type-information functions are not implemented.  The
 *  only caller of this interface is a control which is the source
 *  of the type information itself.  A control will not have a
 *  need to call these functions.
 *
 * Return Value:
 *  HRESULT         E_NOTIMPL in all cases.
 */

STDMETHODIMP CDispatchEvents::GetTypeInfoCount(UINT *pctInfo)
    {
    *pctInfo=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

STDMETHODIMP CDispatchEvents::GetTypeInfo(UINT itinfo
    , LCID lcid, ITypeInfo **pptInfo)
    {
    *pptInfo=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

STDMETHODIMP CDispatchEvents::GetIDsOfNames(REFIID riid
    , OLECHAR **rgszNames, UINT cNames, LCID lcid, DISPID *rgDispID)
    {
    *rgszNames=NULL;
    *rgDispID=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CDispatchEvents::Invoke
 *
 * Purpose:
 *  Notifies the container of the event in the control.  In this
 *  container we look in the event mapping for this particular
 *  site and execute the appropriate action recorded in that
 *  mapping.  If there is no event handler set up, then nothing
 *  happens.
 *
 * Parameters:
 *  dispIDMember    DISPID of the method or property of interest.
 *  riid            REFIID reserved, must be NULL.
 *  lcid            LCID of the locale.
 *  wFlags          USHORT describing the context of the invocation.
 *  pDispParams     DISPPARAMS * to the array of arguments.
 *  pVarResult      VARIANT * in which to store the result.  Is
 *                  NULL if the caller is not interested.
 *  pExcepInfo      EXCEPINFO * to exception information.
 *  puArgErr        UINT * in which to store the index of an
 *                  invalid parameter if DISP_E_TYPEMISMATCH
 *                  is returned.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CDispatchEvents::Invoke(DISPID dispIDMember, REFIID riid
    , LCID lcid, unsigned short wFlags, DISPPARAMS * pDispParams
    , VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
    {
    HRESULT     hr;
    VARIANT     varResult;
    EVENTACTION iAction;
    UINT        i;
    PEVENTMAP  pEM;

    ODSlu("Events IDispatch called with ID=%lu", dispIDMember);

    if (IID_NULL!=riid)
        return ResultFromScode(E_INVALIDARG);

    /*
     * We ignore lcid in this function.  A multilingual application
     * might use it to determine the meaning of certain parameters
     * or perhaps as an indication of how to format data like
     * time, date, and currency or any other language or locale-
     * sensitive data.
     */

    /*
     * Variable handling:  we don't actually do anything with any
     * of the variables from the control's events, so we don't have
     * any VARIANTARG variables to initialize.
     */

    /*
     * We don't handle the return value of any events if
     * events have them.  We should, however, initialize an
     * empty return value just so it's not garbage.
     */
    if(NULL==pVarResult)
      pVarResult=&varResult;

    VariantInit(pVarResult);
    V_VT(pVarResult)=VT_EMPTY;

    //Only method calls are valid.
    if (!(DISPATCH_METHOD & wFlags))
        return ResultFromScode(DISP_E_MEMBERNOTFOUND);

    /*
     * Process the event by looking for dispIDMember in the
     * list maintained in the tenant that maps event IDs to
     * actions.  If we find the ID, then we execute the action,
     * otherwise we do nothing.
     *
     * Control containers that allow more sophisticated programming
     * for events would do something on the same order but process
     * parameters and call user-implemented functions instead of
     * something simple like MessageBeep.
     */

    iAction=ACTION_NONE;
    pEM=m_pTen->m_pEventMap->m_pEventMap;

    for (i=0; i < m_pTen->m_pEventMap->m_cEvents; i++)
        {
        if (dispIDMember==pEM[i].id)
            {
            iAction=pEM[i].iAction;
            break;
            }
        }

    if (ACTION_NONE==iAction)
        hr=ResultFromScode(DISP_E_MEMBERNOTFOUND);
    else
        {
        MessageBeep((UINT)iAction);
        hr=NOERROR;
        }

    return hr;
    }


IADVSINK.CPP

/*
 * IADVSINK.CPP
 * Patron Chapter 24
 *
 * Implementation of the IAdviseSink interface for Patron's tenants.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CImpIAdviseSink::CImpIAdviseSink
 * CImpIAdviseSink::~CImpIAdviseSink
 *
 * Parameters (Constructor):
 *  pTenant         PCTenant of the tenant we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIAdviseSink::CImpIAdviseSink(PCTenant pTenant
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pTen=pTenant;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIAdviseSink::~CImpIAdviseSink(void)
    {
    return;
    }

/*
 * CImpIAdviseSink::QueryInterface
 * CImpIAdviseSink::AddRef
 * CImpIAdviseSink::Release
 *
 * Purpose:
 *  IUnknown members for CImpIAdviseSink object.
 */

STDMETHODIMP CImpIAdviseSink::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIAdviseSink::OnDataChange
 *
 * Unused since we don't IDataObject::Advise.
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnDataChange(LPFORMATETC pFEIn
    , LPSTGMEDIUM pSTM)
    {
    return;
    }

/*
 * CImpIAdviseSink::OnViewChange
 *
 * Purpose:
 *  Notifes the advise sink that presentation data changed in the
 *  data object to which we're connected providing the right time
 *  to update displays using such presentations.
 *
 * Parameters:
 *  dwAspect        DWORD indicating which aspect has changed.
 *  lindex          LONG indicating the piece that changed.
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnViewChange(DWORD dwAspect
    , LONG lindex)
    {
    //Repaint only if this is the right aspect
    if (dwAspect==m_pTen->m_fe.dwAspect)
        {
        m_pTen->m_pPG->m_fDirty=TRUE;
        m_pTen->Repaint();
        }

    return;
    }

/*
 * CImpIAdviseSink::OnRename
 *
 * Purpose:
 *  Informs the advise sink that a linked object has been renamed.
 *  Generally only the OLE default handler cares about this.
 *
 * Parameters:
 *  pmk             LPMONIKER providing the new name of the object
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnRename(LPMONIKER pmk)
    {
    /*
     * As a container this is unimportant to us since it really
     * tells the handler's implementation of IOleLink that the
     * object's moniker has changed.  Since we get this call
     * from the handler, we don't have to do anything ourselves.
     */
    return;
    }

/*
 * CImpIAdviseSink::OnSave
 *
 * Purpose:
 *  Informs the advise sink that the OLE object has been saved
 *  persistently.  The primary purpose of this is for containers
 *  that want to make optimizations for objects that are not in a
 *  saved state, so on this you have to disable such optimizations.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnSave(void)
    {
    /*
     * A Container has nothing to do here as this notification is
     * only useful when we have an ADVFCACHE_ONSAVE advise set up,
     * which we don't.  So we ignore it.
     */
    return;
    }

/*
 * CImpIAdviseSink::OnClose
 *
 * Purpose:
 *  Informs the advise sink that the OLE object has closed and is
 *  no longer bound in any way.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnClose(void)
    {
    /*
     * This doesn't have much to do with us again as it's only
     * used to notify the handler's IOleLink implementation of the
     * change in the object.  We don't have to do anything since
     * we'll also get an IOleClientSite::OnShowWindow(FALSE) to
     * tell us to repaint.
     */

    /*
     * If we are dealing with an OLE 1.0 server it may not call
     * IOleClientSite::OnShowWindow(FALSE) properly, so to protect
     * ourselves we make sure the object is drawn as closed on
     * this notification.
     */
    m_pTen->ShowAsOpen(FALSE);
    return;
    }


ICLASSF.CPP

/*
 * ICLASSF.CPP
 * Patron Chapter 24
 *
 * Implementation of an IClassFactory interface for Patron to
 * enable linking to its embedded objects.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CLinkClassFactory::CLinkClassFactory
 * CLinkClassFactory::~CLinkClassFactory
 *
 * Constructor Parameters:
 *  pFR             PCLinkFrame that can create documents.
 */

CLinkClassFactory::CLinkClassFactory(PCPatronFrame pFR)
    {
    m_cRef=0L;
    m_pFR=pFR;
    return;
    }

CLinkClassFactory::~CLinkClassFactory(void)
    {
    return;
    }

/*
 * CLinkClassFactory::QueryInterface
 * CLinkClassFactory::AddRef
 * CLinkClassFactory::Release
 */

STDMETHODIMP CLinkClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CLinkClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CLinkClassFactory::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CLinkClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates an object to resolve a link to an embedding.
 */

STDMETHODIMP CLinkClassFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCPatronDoc         pDoc;
    HRESULT             hr;

    *ppvObj=NULL;

    hr=ResultFromScode(E_OUTOFMEMORY);

    //We don't support aggregation
    if (NULL!=pUnkOuter)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    //Try creating a new document, which creates the object.
    pDoc=(PCPatronDoc)m_pFR->m_pCL->NewDocument(TRUE);

    //ObjectDestroyed has already been called.
    if (NULL==pDoc)
        {
        g_cObj++;
        ObjectDestroyed();
        return hr;
        }

    /*
     * We don't want to do any file initialization here because we
     * want to wait for IPersistFile calls.  If we called Load(NULL)
     * we'd create storages and whatnot that we don't want in this
     * case.
     */

    hr=pDoc->QueryInterface(riid, ppvObj);

    if (FAILED(hr))
        {
        m_pFR->m_pCL->CloseDocument(pDoc);
        g_cObj++;
        ObjectDestroyed();
        return hr;
        }

    m_pFR->m_pDocCreated=pDoc;
    return NOERROR;
    }

/*
 * CLinkClassFactory::LockServer
 *
 * Purpose:
 *  Provides control of the application in memory.
 */

STDMETHODIMP CLinkClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        {
        g_cLock--;
        g_cObj++;
        ObjectDestroyed();
        }

    return NOERROR;
    }


ICLISITE.CPP

/*
 * ICLISITE.CPP
 * Patron Chapter 24
 *
 * Implementation of the IOleClientSite interface for
 * Patron's tenants.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CImpIOleClientSite::CImpIOleClientSite
 * CImpIOleClientSite::~CImpIOleClientSite
 *
 * Parameters (Constructor):
 *  pTenant         PCTenant of the tenant we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleClientSite::CImpIOleClientSite(PCTenant pTenant
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pTen=pTenant;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleClientSite::~CImpIOleClientSite(void)
    {
    return;
    }

/*
 * CImpIOleClientSite::QueryInterface
 * CImpIOleClientSite::AddRef
 * CImpIOleClientSite::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleClientSite object.
 */

STDMETHODIMP CImpIOleClientSite::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleClientSite::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleClientSite::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleClientSite::SaveObject
 *
 * Purpose:
 *  Requests that the container call OleSave for the object that
 *  lives here.  Typically this happens on server shutdown.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::SaveObject(void)
    {
    //We're already set up with the tenant to save; this is trivial.
    m_pTen->Update();
    return NOERROR;
    }

/*
 * CImpIOleClientSite::GetMoniker
 *
 * Purpose:
 *  Retrieves the moniker for the site in which this object lives,
 *  either the moniker relative to the container or the full
 *  moniker.
 *
 * Parameters:
 *  dwAssign        DWORD specifying that the object wants moniker
 *                  assignment.  Yeah.  Right.  Got any bridges to
 *                  sell?
 *  dwWhich         DWORD identifying which moniker the object
 *                  wants, either the container's moniker, the
 *                  moniker relative to this client site, or the
 *                  full moniker.
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::GetMoniker(DWORD dwAssign
    , DWORD dwWhich, LPMONIKER *ppmk)
    {
    *ppmk=NULL;

    switch (dwWhich)
        {
        case OLEWHICHMK_CONTAINER:
            //This is just the file we're living in.
            if (NULL!=m_pTen->m_pmkFile)
                *ppmk=m_pTen->m_pmkFile;

            break;

        case OLEWHICHMK_OBJREL:
            //This is everything but the filename.
            if (NULL!=m_pTen->m_pmk)
                *ppmk=m_pTen->m_pmk;

            break;

        case OLEWHICHMK_OBJFULL:
            //Concatenate file and relative monikers for this one.
            if (NULL!=m_pTen->m_pmkFile && NULL!=m_pTen->m_pmk)
                {
                return m_pTen->m_pmkFile->ComposeWith
                    (m_pTen->m_pmk, FALSE, ppmk);
                }

            break;
        }

    if (NULL==*ppmk)
        return ResultFromScode(E_FAIL);

    (*ppmk)->AddRef();
    return NOERROR;
    }

/*
 * CImpIOleClientSite::GetContainer
 *
 * Purpose:
 *  Returns a pointer to the document's IOleContainer interface.
 *
 * Parameters:
 *  ppContainer     LPOLECONTAINER * in which to return the
 *                  interface.
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::GetContainer(LPOLECONTAINER
    * ppContainer)
    {
    PCPage  pPage;

    *ppContainer=NULL;

    /*
     * Here we want to get the interface on the page we're in.
     * The function we call in CPages does not AddRef the page, but
     * QueryInterface does.
     */
    m_pTen->m_pPG->IPageGetFromID((DWORD)-1L, &pPage, FALSE);

    if (NULL!=pPage)
        {
        return pPage->QueryInterface(IID_IOleItemContainer
            , (PPVOID)ppContainer);
        }

    return ResultFromScode(E_FAIL);
    }

/*
 * CImpIOleClientSite::ShowObject
 *
 * Purpose:
 *  Tells the container to bring the object fully into view as much
 *  as possible, that is, scroll the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::ShowObject(void)
    {
    HWND        hWnd, hWndT;

    /*
     * We let the tenant do this, since it can access the current
     * scroll position as a friend of CPages whereas we cannot.
     */

    //Don't try to show objects that are always active.
    if (!((OLEMISC_INSIDEOUT | OLEMISC_ACTIVATEWHENVISIBLE)
        & m_pTen->m_grfMisc))
        m_pTen->ShowYourself();

    //For linking to embedding, show the main window.
    hWndT=GetParent(m_pTen->m_hWnd);

    while (NULL!=hWndT)
        {
        hWnd=hWndT;
        hWndT=GetParent(hWnd);
        }

    ShowWindow(hWnd, SW_SHOWNOACTIVATE);
    m_pTen->UpdateInPlaceObjectRects(NULL, TRUE);
    return NOERROR;
    }

/*
 * CImpIOleClientSite::OnShowWindow
 *
 * Purpose:
 *  Informs the container if the object is showing itself or
 *  hiding itself.  This is done only in the opening mode and allows
 *  the container to know when to shade or unshade the object.
 *
 * Parameters:
 *  fShow           BOOL indiciating that the object is being shown
 *                  (TRUE) or hidden (FALSE).
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::OnShowWindow(BOOL fShow)
    {
    //All we have to do is tell the tenant of the open state change.
    m_pTen->ShowAsOpen(fShow);
    return NOERROR;
    }

//CHAPTER24MOD
/*
 * CImpIOleClientSite::RequestNewObjectLayout
 *
 * Purpose:
 *  Called when the object would like to have its layout
 *  reinitialized.  This is used by OLE Controls.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CImpIOleClientSite::RequestNewObjectLayout(void)
    {
    RECT    rc, rcT;
    SIZEL   szl;
    HRESULT hr;

    /*
     * This function is called by a control that is not in-place
     * active or UI active and therefore doesn't have our
     * IOleInPlaceSite interface in which to call OnPosRectChange.
     * Therefore we do pretty much the same thing we do in that
     * function although we ask the control for the size it wants.
     */

    //Get the size from the control
    if (NULL!=m_pTen->m_pIViewObject2)
        {
        hr=m_pTen->m_pIViewObject2->GetExtent(m_pTen->m_fe.dwAspect
            , -1, NULL, &szl);
        }

    if (FAILED(hr))
        return hr;

    //Add these extents to the existing tenant position.
    SetRect(&rcT, 0, 0, szl.cx*10, -szl.cy*10);
    RectConvertMappings(&rcT, NULL, TRUE);

    rc=m_pTen->m_rcPos;
    rc.right=rc.left+rcT.right;
    rc.bottom=rc.top+rcT.bottom;

    m_pTen->UpdateInPlaceObjectRects(&rc, FALSE);
    return NOERROR;
    }
//End CHAPTER24MOD


ICONSITE.CPP

/*
 * ICONSITE.CPP
 * Patron Chapter 24
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CImpIOleControlSite::CImpIOleControlSite
 * CImpIOleControlSite::~CImpIOleControlSite
 *
 * Parameters (Constructor):
 *  pTen            PCTenant of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleControlSite::CImpIOleControlSite(PCTenant pTen
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pTen=pTen;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleControlSite::~CImpIOleControlSite(void)
    {
    return;
    }

/*
 * CImpIOleControlSite::QueryInterface
 * CImpIOleControlSite::AddRef
 * CImpIOleControlSite::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIOleControlSite.
 */

STDMETHODIMP CImpIOleControlSite::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleControlSite::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleControlSite::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleControlSite::OnControlInfoChanged
 *
 * Purpose:
 *  Informs the site that the CONTROLINFO for the control has
 *  changed and we thus need to reload the data.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleControlSite::OnControlInfoChanged(void)
    {
    //We also update our "have info" flag here.
    m_pTen->m_fHaveControlInfo=SUCCEEDED(m_pTen->m_pIOleControl
        ->GetControlInfo(&m_pTen->m_ctrlInfo));

    return NOERROR;
    }

/*
 * CImpIOleControlSite::LockInPlaceActive
 *
 * Purpose:
 *  Forces the container to keep this control in-place active
 *  (but not UI active) regardless of other considerations, or
 *  removes this lock.
 *
 * Parameters:
 *  fLock           BOOL indicating to lock (TRUE) or unlock (FALSE)
 *                  in-place activation.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleControlSite::LockInPlaceActive(BOOL fLock)
    {
    if (fLock)
        m_pTen->m_cLockInPlace++;
    else
        {
        if (0==--m_pTen->m_cLockInPlace)
            {
            //If there's a pending deactivate, do it now.
            if (m_pTen->m_fPendingDeactivate)
                m_pTen->DeactivateInPlaceObject(TRUE);
            }
        }

    return NOERROR;
    }

/*
 * CImpIOleControlSite::GetExtendedControl
 *
 * Purpose:
 *  Returns a pointer to the container's extended control that wraps
 *  the actual control in this site, if one exists.
 *
 * Parameters:
 *  ppDispatch      LPDISPATCH * in which to return the pointer
 *                  to the extended control's IDispatch interface.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleControlSite::GetExtendedControl(LPDISPATCH
    * ppDispatch)
    {
    *ppDispatch=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleControlSite::TransformCoords
 *
 * Purpose:
 *  Converts coordinates in HIMETRIC units into those used by the
 *  container.
 *
 * Parameters:
 *  pptlHiMet       POINTL * containing either the coordinates to
 *                  transform to container or where to store the
 *                  transformed container coordinates.
 *  pptlCont        POINTF * containing the container coordinates.
 *  dwFlags         DWORD containing instructional flags.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleControlSite::TransformCoords(POINTL *pptlHiMet
    , POINTF *pptlCont, DWORD dwFlags)
    {
    if (NULL==pptlHiMet || NULL==pptlCont)
        return ResultFromScode(E_POINTER);

    /*
     * Convert coordinates.  We use MM_LOMETRIC which means that
     * to convert from HIMETRIC we divide by 10 and negate the y
     * coordinate.  Conversion to HIMETRIC means negate the y
     * and multiply by 10.  Note that size and position are
     * considered the same thing, that is, we don't differentiate
     * the two.
     */

    if (XFORMCOORDS_HIMETRICTOCONTAINER & dwFlags)
        {
        pptlCont->x=(float)(pptlHiMet->x/10);
        pptlCont->y=(float)-(pptlHiMet->y/10);
        }
    else
        {
        pptlHiMet->x=(long)(pptlCont->x*10);
        pptlHiMet->y=(long)-(pptlCont->y*10);
        }

    return NOERROR;
    }

/*
 * CImpIOleControlSite::TranslateAccelerator
 *
 * Purpose:
 *  Instructs the container to translate a keyboard accelerator
 *  message that the control has picked up instead.
 *
 * Parameters:
 *  pMsg            LPMSG to the message to translate.
 *  grfModifiers    DWORD flags with additional instructions.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleControlSite::TranslateAccelerator(LPMSG pMsg
    , DWORD grfModifiers)
    {
    /*
     * The control has picked up a keystroke through its own
     * TranslateAccelerator and is now giving us the change to
     * play with it.  Currently there are no flags for
     * grfModifiers, so we ignore them.  Especially since
     * we have nothing to do here ourselves anyway.
     */
    return ResultFromScode(S_FALSE);
    }

/*
 * CImpIOleControlSite::OnFocus
 *
 * Purpose:
 *  Informs the container that focus has either been lost or
 *  gained in the control.
 *
 * Parameters:
 *  fGotFocus       BOOL indicating that the control gained (TRUE)
 *                  or lost (FALSE) focus.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleControlSite::OnFocus(BOOL fGotFocus)
    {
    /*
     * Patron doesn't do this, but to handle the default
     * and cancel buttons properly, we normally process RETURN
     * and ESC accelerators to press the right button.
     * This behavior must be disabled when the control with
     * the focus has either CTRLINFO_EATS_RETURN or
     * CTRLINFO_EATS_ESCAPE set.  We tell the frame as
     * we need to when a new control gets the focus.  We
     * do nothing when a control loses the focus.
     */
    return NOERROR;
    }

/*
 * CImpIOleControlSite::ShowPropertyFrame
 *
 * Purpose:
 *  Instructs the container to show the property frame if
 *  this is an extended object and requires its own property
 *  pages.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleControlSite::ShowPropertyFrame(void)
    {
    /*
     * Returning an error here means that the container has
     * no property pages itself for the control, so the
     * control should display its own.
     */
    return ResultFromScode(E_NOTIMPL);
    }


IIPSITE.CPP

/*
 * IIPSITE.CPP
 * Patron Chapter 24
 *
 * IOleInPlaceSite interface implementation for Patron
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

BOOL        g_fSwitchingActive=FALSE;

/*
 * CImpIOleInPlaceSite::CImpIOleInPlaceSite
 * CImpIOleInPlaceSite::~CImpIOleInPlaceSite
 *
 * Parameters (Constructor):
 *  pTen            PCTenant of the tenant we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleInPlaceSite::CImpIOleInPlaceSite(PCTenant pTen
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pTen=pTen;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleInPlaceSite::~CImpIOleInPlaceSite(void)
    {
    return;
    }

/*
 * CImpIOleInPlaceSite::QueryInterface
 * CImpIOleInPlaceSite::AddRef
 * CImpIOleInPlaceSite::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleInPlaceSite object.
 */

STDMETHODIMP CImpIOleInPlaceSite::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceSite::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceSite::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleInPlaceActiveObject::GetWindow
 *
 * Purpose:
 *  Retrieves the handle of the window associated with the object
 *  on which this interface is implemented.
 *
 * Parameters:
 *  phWnd           HWND * in which to store the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_FAIL if there is no
 *                  window.
 */

STDMETHODIMP CImpIOleInPlaceSite::GetWindow(HWND *phWnd)
    {
    *phWnd=m_pTen->m_hWnd;
    return NOERROR;
    }

/*
 * CImpIOleInPlaceActiveObject::ContextSensitiveHelp
 *
 * Purpose:
 *  Instructs the object on which this interface is implemented to
 *  enter or leave a context-sensitive help mode.
 *
 * Parameters:
 *  fEnterMode      BOOL TRUE to enter the mode, FALSE otherwise.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceSite::ContextSensitiveHelp
    (BOOL fEnterMode)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::CanInPlaceActivate
 *
 * Purpose:
 *  Answers the server whether or not we can currently in-place
 *  activate its object.  By implementing this interface we say
 *  that we support in-place activation, but through this function
 *  we indicate whether the object can currently be activated
 *  in-place.  Iconic aspects, for example, cannot, meaning we
 *  return S_FALSE.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR if we can in-place activate the object
 *                  in this site, S_FALSE if not.
 */

STDMETHODIMP CImpIOleInPlaceSite::CanInPlaceActivate(void)
    {
    if (DVASPECT_CONTENT!=m_pTen->m_fe.dwAspect)
        return ResultFromScode(S_FALSE);

    if (TENANTTYPE_EMBEDDEDOBJECT!=m_pTen->m_tType)
        return ResultFromScode(S_FALSE);

    //CHAPTER24MOD
    if (m_pTen->m_pPG->m_fDesignMode)
        return ResultFromScode(S_FALSE);
    //End CHAPTER24MOD

    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::OnInPlaceActivate
 *
 * Purpose:
 *  Informs the container that an object is being activated in-place
 *  such that the container can prepare appropriately.  The
 *  container does not, however, make any user interface changes at
 *  this point.  See OnUIActivate.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an appropriate error code.
 */

STDMETHODIMP CImpIOleInPlaceSite::OnInPlaceActivate(void)
    {
    //CHAPTER24MOD
    m_pTen->m_fPendingDeactivate=FALSE;
    //End CHAPTER24MOD

    //m_pIOleIPObject is our in-place flag.
    m_pTen->m_pObj->QueryInterface(IID_IOleInPlaceObject
        , (PPVOID)&m_pTen->m_pIOleIPObject);

    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::OnInPlaceDeactivate
 *
 * Purpose:
 *  Notifies the container that the object has deactivated itself
 *  from an in-place state.  Opposite of OnInPlaceActivate.  The
 *  container does not change any UI at this point.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an appropriate error code.
 */

STDMETHODIMP CImpIOleInPlaceSite::OnInPlaceDeactivate(void)
    {
    /*
     * Since we don't have an Undo command, we can tell the object
     * right away to discard its Undo state.
     */
    m_pTen->Activate(OLEIVERB_DISCARDUNDOSTATE, NULL);
    ReleaseInterface(m_pTen->m_pIOleIPObject);
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::OnUIActivate
 *
 * Purpose:
 *  Informs the container that the object is going to start munging
 *  around with user interface, like replacing the menu.  The
 *  container should remove any relevant UI in preparation.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an appropriate error code.
 */

STDMETHODIMP CImpIOleInPlaceSite::OnUIActivate(void)
    {
    PCPatronDoc     pDoc;

    m_pTen->m_pPG->m_fAddUI=FALSE;

    //CHAPTER24MOD
    m_pTen->m_fPendingDeactivate=FALSE;
    //End CHAPTER24MOD

    pDoc=(PCPatronDoc)SendMessage(GetParent(m_pTen->m_hWnd)
        , DOCM_PDOCUMENT, 0, 0L);

    /*
     * Change the currently selected tenant in the page.  This
     * will UIDeactivate the currently UI Active tenant.
     */
    g_fSwitchingActive=TRUE;
    m_pTen->m_pPG->m_pPageCur->SwitchActiveTenant(m_pTen);
    g_fSwitchingActive=FALSE;

    //Hide the frame tools if necessary.
    g_pFR->ShowUIAndTools(pDoc->NoObjectFrameTools(0, FALSE), FALSE);
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::OnUIDeactivate
 *
 * Purpose:
 *  Informs the container that the object is deactivating its
 *  in-place user interface at which time the container may
 *  reinstate its own.  Opposite of OnUIActivate.
 *
 * Parameters:
 *  fUndoable       BOOL indicating if the object will actually
 *                  perform an Undo if the container calls
 *                  ReactivateAndUndo.
 *
 * Return Value:
 *  HRESULT         NOERROR or an appropriate error code.
 */

STDMETHODIMP CImpIOleInPlaceSite::OnUIDeactivate(BOOL fUndoable)
    {
    PCDocument  pDoc;
    MSG         msg;

    /*
     * Ignore this notification if we're switching between
     * multiple active objects.
     */
    if (g_fSwitchingActive)
        return NOERROR;

    //If in shutdown (NULL storage), don't check messages.
    if (NULL==m_pTen->m_pIStorage)
        {
        g_pFR->ReinstateUI();
        return NOERROR;
        }

    pDoc=(PCDocument)SendMessage(GetParent(m_pTen->m_hWnd)
        , DOCM_PDOCUMENT, 0, 0L);

    //If there's a pending double-click, delay showing our UI
    if (!PeekMessage(&msg, pDoc->Window(), WM_LBUTTONDBLCLK
        , WM_LBUTTONDBLCLK, PM_NOREMOVE | PM_NOYIELD))
        {
        //Turn everything back on.
        g_pFR->ReinstateUI();
        }
    else
        m_pTen->m_pPG->m_fAddUI=TRUE;

    SetFocus(pDoc->Window());
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::DeactivateAndUndo
 *
 * Purpose:
 *  If immediately after activation the object does an Undo, the
 *  action being undone is the activation itself, and this call
 *  informs the container that this is, in fact, what happened.
 *  The container should call IOleInPlaceObject::UIDeactivate.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an appropriate error code.
 */

STDMETHODIMP CImpIOleInPlaceSite::DeactivateAndUndo(void)
    {
    //CHAPTER24MOD
    /*
     * Note that we don't pay attention to the locking
     * from IOleControlSite::LockInPlaceActive since only
     * the object calls this function and should know
     * that it's going to be deactivated.
     */
    //End CHAPTER24MOD

    m_pTen->m_pIOleIPObject->InPlaceDeactivate();
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::DiscardUndoState
 *
 * Purpose:
 *  Informs the container that something happened in the object
 *  that means the container should discard any undo information
 *  it currently maintains for the object.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an appropriate error code.
 */

STDMETHODIMP CImpIOleInPlaceSite::DiscardUndoState(void)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleInPlaceSite::GetWindowContext
 *
 * Purpose:
 *  Provides an in-place object with pointers to the frame and
 *  document level in-place interfaces (IOleInPlaceFrame and
 *  IOleInPlaceUIWindow) such that the object can do border
 *  negotiation and so forth.  Also requests the position and
 *  clipping rectangles of the object in the container and a
 *  pointer to an OLEINPLACEFRAME info structure which contains
 *  accelerator information.
 *
 *  Note that the two interfaces this call returns are not
 *  available through QueryInterface on IOleInPlaceSite since they
 *  live with the frame and document, but not the site.
 *
 * Parameters:
 *  ppIIPFrame      LPOLEINPLACEFRAME * in which to return the
 *                  AddRef'd pointer to the container's
 *                  IOleInPlaceFrame.
 *  ppIIPUIWindow   LPOLEINPLACEUIWINDOW * in which to return
 *                  the AddRef'd pointer to the container document's
 *                  IOleInPlaceUIWindow.
 *  prcPos          LPRECT in which to store the object's position.
 *  prcClip         LPRECT in which to store the object's visible
 *                  region.
 *  pFI             LPOLEINPLACEFRAMEINFO to fill with accelerator
 *                  stuff.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceSite::GetWindowContext
    (LPOLEINPLACEFRAME *ppIIPFrame, LPOLEINPLACEUIWINDOW
    *ppIIPUIWindow, LPRECT prcPos, LPRECT prcClip
    , LPOLEINPLACEFRAMEINFO pFI)
    {
    PCPatronDoc     pDoc;
    RECTL           rcl;

    *ppIIPUIWindow=NULL;

    *ppIIPFrame=(LPOLEINPLACEFRAME)g_pFR;
    g_pFR->AddRef();

    pDoc=(PCPatronDoc)SendMessage(GetParent(m_pTen->m_hWnd)
        , DOCM_PDOCUMENT, 0, 0L);

    if (NULL!=pDoc)
        {
        pDoc->QueryInterface(IID_IOleInPlaceUIWindow
            , (PPVOID)ppIIPUIWindow);
        }

    //Now get the rectangles and frame information.
    m_pTen->RectGet(&rcl, TRUE);
    RECTFROMRECTL(*prcPos, rcl);

    //Include scroll position here.
    OffsetRect(prcPos, -(int)m_pTen->m_pPG->m_xPos
        , -(int)m_pTen->m_pPG->m_yPos);

    SetRect(prcClip, 0, 0, 32767, 32767);

    pFI->cb=sizeof(OLEINPLACEFRAMEINFO);
   #ifdef MDI
    pFI->fMDIApp=TRUE;
   #else
    pFI->fMDIApp=FALSE;
   #endif

    pFI->hwndFrame=g_pFR->Window();

    pFI->haccel=g_pFR->m_hAccelIP;
    pFI->cAccelEntries=CINPLACEACCELERATORS;

    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::Scroll
 *
 * Purpose:
 *  Asks the container to scroll the document, and thus the object,
 *  by the given amounts in the sz parameter.
 *
 * Parameters:
 *  sz              SIZE containing signed horizontal and vertical
 *                  extents by which the container should scroll.
 *                  These are in device units.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceSite::Scroll(SIZE sz)
    {
    int         x, y;

    x=m_pTen->m_pPG->m_xPos+sz.cx;
    y=m_pTen->m_pPG->m_yPos+sz.cy;

    SendScrollPosition(m_pTen->m_hWnd, WM_HSCROLL, x);
    SendScrollPosition(m_pTen->m_hWnd, WM_VSCROLL, y);
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::OnPosRectChange
 *
 * Purpose:
 *  Informs the container that the in-place object was resized.
 *  The container must call IOleInPlaceObject::SetObjectRects.
 *  This does not change the site's rectangle in any case.
 *
 * Parameters:
 *  prcPos          LPCRECT containing the new size of the object.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceSite::OnPosRectChange(LPCRECT prcPos)
    {
    if (NULL!=prcPos)
        m_pTen->m_rcPos=*prcPos;

    m_pTen->UpdateInPlaceObjectRects(prcPos, FALSE);
    return NOERROR;
    }


IIPUIWIN.CPP

/*
 * IIPUIWIN.CPP
 * Patron Chapter 24
 *
 * IOleInPlaceUIWindow interface implementation for Patron's
 * documents.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CImpIOleInPlaceUIWindow::CImpIOleInPlaceUIWindow
 * CImpIOleInPlaceUIWindow::~CImpIOleInPlaceUIWindow
 *
 * Parameters (Constructor):
 *  pDoc            PCPatronDoc of the document we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleInPlaceUIWindow::CImpIOleInPlaceUIWindow(PCPatronDoc pDoc
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pDoc=pDoc;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleInPlaceUIWindow::~CImpIOleInPlaceUIWindow(void)
    {
    return;
    }

/*
 * CImpIOleInPlaceUIWindow::QueryInterface
 * CImpIOleInPlaceUIWindow::AddRef
 * CImpIOleInPlaceUIWindow::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleInPlaceUIWindow object.
 */

STDMETHODIMP CImpIOleInPlaceUIWindow::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceUIWindow::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceUIWindow::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleInPlaceUIWindow::GetWindow
 *
 * Purpose:
 *  Retrieves the handle of the window associated with the object
 *  on which this interface is implemented.
 *
 * Parameters:
 *  phWnd           HWND * in which to store the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_FAIL if there is no
 *                  window.
 */

STDMETHODIMP CImpIOleInPlaceUIWindow::GetWindow(HWND *phWnd)
    {
    *phWnd=m_pDoc->m_hWnd;
    return NOERROR;
    }

/*
 * CImpIOleInPlaceUIWindow::ContextSensitiveHelp
 *
 * Purpose:
 *  Instructs the object on which this interface is implemented to
 *  enter or leave a context-sensitive help mode.
 *
 * Parameters:
 *  fEnterMode      BOOL TRUE to enter the mode, FALSE otherwise.
 *
 * Return Value:
 *  HRESULT         NOERROR or an appropriate error code.
 */

STDMETHODIMP CImpIOleInPlaceUIWindow::ContextSensitiveHelp
    (BOOL fEnterMode)
    {
   #ifdef MDI
    //Only MDI documents care
    if (NULL!=m_pDoc->m_pIOleIPActiveObject)
        {
        m_pDoc->m_pIOleIPActiveObject->ContextSensitiveHelp
           (fEnterMode);
        }
   #endif
    return NOERROR;
    }

/*
 * CImpIOleInPlaceUIWindow::GetBorder
 *
 * Purpose:
 *  Returns the rectangle in which the container is willing to
 *  negotiate about an object's adornments.
 *
 * Parameters:
 *  prcBorder       LPRECT in which to store the rectangle.
 *
 * Return Value:
 *  HRESULT         NOERROR if all is well, INPLACE_E_NOTOOLSPACE if
 *                  there is no negotiable space.
 */

STDMETHODIMP CImpIOleInPlaceUIWindow::GetBorder(LPRECT prcBorder)
    {
    if (NULL==prcBorder)
        return ResultFromScode(E_INVALIDARG);

    /*
     * All of the document window space is available for negotiation
     * since we can resize the Pages window to anything we want.  So
     * we just return the entire client rectangle here.
     */
    GetClientRect(m_pDoc->m_hWnd, prcBorder);
    return NOERROR;
    }

/*
 * CImpIOleInPlaceUIWindow::RequestBorderSpace
 *
 * Purpose:
 *  Asks the container if it can surrender the amount of space
 *  in pBW that the object would like for it's adornments.  The
 *  container does nothing but validate the spaces on this call.
 *
 * Parameters:
 *  pBW             LPCBORDERWIDTHS containing the requested space.
 *                  The values are the amount of space requested
 *                  from each side of the relevant window.
 *
 * Return Value:
 *  HRESULT         NOERROR if we can give up space,
 *                  INPLACE_E_NOTOOLSPACE otherwise.
 */

STDMETHODIMP CImpIOleInPlaceUIWindow::RequestBorderSpace
    (LPCBORDERWIDTHS pBW)
    {
    //We don't have any restrictions, so we always return NOERROR.
    return NOERROR;
    }

/*
 * CImpIOleInPlaceUIWindow::SetBorderSpace
 *
 * Purpose:
 *  Called when the object now officially requests that the
 *  container surrender border space it previously allowed in
 *  RequestBorderSpace.  The container should resize windows
 *  appropriately to surrender this space.
 *
 * Parameters:
 *  pBW             LPCBORDERWIDTHS containing the amount of space
 *                  from each side of the relevant window that
 *                  the object is now reserving.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceUIWindow::SetBorderSpace
    (LPCBORDERWIDTHS pBW)
    {
    RECT        rc;
    HWND        hWndDoc;
    HWND        hWndPages;
    POINT       pt1, pt2;

    if (NULL==pBW)
        return NOERROR;

    /*
     * Now we just have to resize the Pages window to make room for
     * the object's tools or resize it to occupy the full document
     * if we're reverting to normal.
     */

    hWndDoc=m_pDoc->m_hWnd;
    hWndPages=m_pDoc->m_pPG->Window();

    //Get the current offset of the Pages
    GetWindowRect(hWndPages, &rc);
    SETPOINT(pt1, rc.left, rc.top);
    SETPOINT(pt2, rc.right, rc.bottom);
    ScreenToClient(hWndDoc, &pt1);
    ScreenToClient(hWndDoc, &pt2);

    GetClientRect(hWndDoc, &rc);

    if (NULL!=pBW)
        {
        rc.left+=pBW->left;
        rc.right-=pBW->right;
        rc.top+=pBW->top;
        rc.bottom-=pBW->bottom;
        }

    //Only move Pages if necessary
    if (!(pt1.x==rc.left && pt1.y==rc.top
        && pt2.x==rc.right && pt2.y==rc.bottom))
        {
        SendMessage(hWndDoc, WM_SETREDRAW, FALSE, 0L);
        ShowWindow(hWndPages, SW_HIDE);

        m_pDoc->m_pPG->RectSet(&rc, FALSE);

        /*
         * Scroll the Pages in the opposite direction to keep
         * objects steady.
         */

        SendScrollPosition(hWndPages, WM_HSCROLL, pBW->left-pt1.x);
        SendScrollPosition(hWndPages, WM_VSCROLL, pBW->top-pt1.y);

        SendMessage(hWndDoc, WM_SETREDRAW, TRUE, 0L);
        ShowWindow(hWndPages, SW_SHOW);
        }

    return NOERROR;
    }

/*
 * CImpIOleInPlaceUIWindow::SetActiveObject
 *
 * Purpose:
 *  Provides the container with the object's IOleInPlaceActiveObject
 *  pointer.
 *
 * Parameters:
 *  pIIPActiveObj   LPOLEINPLACEACTIVEOBJECT of interest.
 *  pszObj          LPCOLESTR naming the object.  Not used.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceUIWindow::SetActiveObject(
    LPOLEINPLACEACTIVEOBJECT pIIPActiveObj, LPCOLESTR pszObj)
    {
    if (NULL!=m_pDoc->m_pIOleIPActiveObject)
        m_pDoc->m_pIOleIPActiveObject->Release();

    //NULLs m_pIOleIPActiveObject if pIIPActiveObj is NULL
    m_pDoc->m_pIOleIPActiveObject=pIIPActiveObj;

    if (NULL!=m_pDoc->m_pIOleIPActiveObject)
        m_pDoc->m_pIOleIPActiveObject->AddRef();

    return NOERROR;
    }


IOLECONT.CPP

/*
 * IOLECONT.CPP
 * Patron Chapter 24
 *
 * Implementation of the IOleItemContainer interface for Patron's
 * CPage and CPatronDoc alike, using the constructor parameter fDoc
 * to differentiate.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include <stdlib.h>
#include "patron.h"

/*
 * CImpIOleItemContainer::CImpIOleItemContainer
 * CImpIOleItemContainer::~CImpIOleItemContainer
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the page or pages.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 *  fDoc            BOOL indicating if we're in CPatronDoc or CPage
 */

CImpIOleItemContainer::CImpIOleItemContainer(LPVOID pObj
    , LPUNKNOWN pUnkOuter, BOOL fDoc)
    {
    m_cRef=0;
    m_fDoc=fDoc;

    if (fDoc)
        {
        m_pDoc=(PCPatronDoc)pObj;
        m_pPage=NULL;
        }
    else
        {
        m_pDoc=NULL;
        m_pPage=(PCPage)pObj;
        }

    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleItemContainer::~CImpIOleItemContainer(void)
    {
    return;
    }

/*
 * CImpIOleItemContainer::QueryInterface
 * CImpIOleItemContainer::AddRef
 * CImpIOleItemContainer::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleItemContainer object.
 */

STDMETHODIMP CImpIOleItemContainer::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleItemContainer::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleItemContainer::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleItemContainer::ParseDisplayName
 *
 * Purpose:
 *  Inherited member of IParseDisplayName that takes a string name
 *  and turns out a moniker for it.
 *
 * Parameters:
 *  pbc             LPBC to the binding context
 *  pszName         LPOLESTR to the name to parse.
 *  pchEaten        ULONG * into which to store how many
 *                  characters we scanned in the display name.
 *  ppmk            LPMONIKER * in which to return the moniker.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::ParseDisplayName(LPBC pbc
    , LPOLESTR pszName, ULONG *pchEaten, LPMONIKER *ppmk)
    {
    OLECHAR     ch;
    ULONG       chEaten=0;
    TCHAR       szName[256];
    TCHAR       szComp[15];
    LPTSTR      psz;
    UINT        cch;

    *ppmk=NULL;
    *pchEaten=0;

    /*
     * All we have to look for is the string between the !
     * delimeters (or a null terminator).  pszName should be pointing
     * to a !, so skip it and scan the string for a ! or 0,
     * then pass the result to CreateItemMoniker.
     */

    psz=szName;

    ch=*pszName++;
    chEaten++;

    if ((OLECHAR)'!'!=ch)
        return ResultFromScode(MK_E_SYNTAX);

    ch=*pszName++;

    while ((OLECHAR)0!=ch && (OLECHAR)'!' !=ch)
        {
        *psz++=(TCHAR)ch;
        chEaten++;
        ch=*pszName++;
        }

    *psz=(TCHAR)0;

    /*
     * Syntax check.  If we're the DOcument object, check for
     * "Page n" at the beginning of the string.  Otherwise check
     * for "Tenant n".
     */
    lstrcpy(szComp, m_fDoc ? TEXT("Page ") : TEXT("Tenant "));

    //Does szName start with szComp?
    cch=lstrlen(szComp);

    if (0!=_tcsncicmp(szName, szComp, cch))
        {
        *pchEaten=1;    //Parsed ! at least
        return ResultFromScode(MK_E_SYNTAX);
        }

    //Check for a number in szName
    if ((TCHAR)'0' != szName[cch])
        {
        if (0==_ttoi(szName+cch))
            {
            *pchEaten=cch;  //Got past name
            return ResultFromScode(MK_E_SYNTAX);
            }
        }

    *pchEaten=chEaten;
   #ifdef WIN32ANSI
    //Use the ANSI version here since szName is ANSI
    return INOLE_CreateItemMoniker(TEXT("!"), szName, ppmk);
   #else
    return CreateItemMoniker(OLETEXT("!"), szName, ppmk);
   #endif
    }

/*
 * CImpIOleItemContainer::EnumObjects
 *
 * Purpose:
 *  Creates and returns an IEnumUnknown object that allows the
 *  caller to walk through the objects in this continer thing.
 *
 * Parameters:
 *  dwFlags         DWORD specifying what kind of objects to
 *                  enumerate.
 *  ppEnum          LPENUMUNKNOWN * into which to return the
 *                  enumerator
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::EnumObjects(DWORD dwFlags
    , LPENUMUNKNOWN *ppEnum)
    {
    *ppEnum=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleItemContainer::LockContainer
 *
 * Purpose:
 *  Establishes a lock on the container to prevent it from shutting
 *  down outside of user control.  This is used to control the
 *  lifetime of the container when it's used to update a link to an
 *  embedded object within it.  If we're unlock and the user has not
 *  taken control, we close.
 *
 * Parameters:
 *  fLock           BOOL indicating a lock or unlock.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::LockContainer(BOOL fLock)
    {
    /*
     * This is pretty much the same implementation as
     * IClassFactory::LockServer, and we can use the same lock
     * count to accomplish our goal.
     */

    if (fLock)
        g_cLock++;
    else
        {
        g_cLock--;
        g_cObj++;
        ObjectDestroyed();
        }

    return NOERROR;
    }

/*
 * CImpIOleItemContainer::GetObject
 *
 * Purpose:
 *  Returns the requested interface pointer on an object in this
 *  container.
 *
 * Parameters:
 *  pszItem         LPOLESTR to the item we must locate.
 *  dwSpeed         DWORD identifying how long the caller is willing
 *                  to wait.
 *  pcb             LPBINDCTX providing the binding context.
 *  riid            REFIID of the interface requested.
 *  ppv             PPVOID into which to return the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::GetObject(LPOLESTR pszItem
    , DWORD dwSpeed, LPBINDCTX pbc, REFIID riid, PPVOID ppv)
    {
    DWORD       dw;
    char        szTemp[40];     //ANSI for atol
    HRESULT     hr=ResultFromScode(E_FAIL);
    PCPage      pPage;
    PCTenant    pTenant;
    LPUNKNOWN   pObj;
    UINT        i, iCur;

    *ppv=NULL;

    if (m_fDoc)
        {
        /*
         * The item name should be "Page n", so we'll do it the
         * easy way:  call atol on pszItem+5 (we always know that
         * we'll have "Page " there since we put it there (see
         * CPage::GetStorageName).
         */

        UNICODETOANSI((pszItem+5), szTemp, sizeof(szTemp));
        dw=atol(szTemp);

        i=m_pDoc->m_pPG->IPageGetFromID(dw, &pPage, FALSE);

        if (NOVALUE==i)
            return hr;

        /*
         * If we're asked for immediate speed, only do this if the
         * page is already current, i.e. everything is loaded.
         */
        iCur=m_pDoc->m_pPG->CurPageGet();

        if (BINDSPEED_IMMEDIATE==dwSpeed && iCur!=i)
            return ResultFromScode(MK_E_EXCEEDEDDEADLINE);

        m_pDoc->m_pPG->CurPageSet(i);

        //This will have changed to be the current page now.
        if (NULL!=m_pDoc->m_pPG->m_pPageCur)
            hr=m_pDoc->m_pPG->m_pPageCur->QueryInterface(riid, ppv);
        }
    else
        {
        if (TenantFromName(pszItem, &pTenant))
            {
            pTenant->ObjectGet(&pObj);

            /*
             * If we're asked for immediate or moderate, only work
             * if the object is already running.
             */
            hr=IsRunning(pszItem);  //This is the function below

            if ((BINDSPEED_IMMEDIATE==dwSpeed
                || BINDSPEED_MODERATE==dwSpeed) && NOERROR!=hr)
                hr=ResultFromScode(MK_E_EXCEEDEDDEADLINE);
            else
                {
                //IMPORTANT:  Make sure this object is running first
                OleRun(pObj);
                hr=pObj->QueryInterface(riid, ppv);
                }

            pObj->Release();
            }
        else
            hr=ResultFromScode(MK_E_NOOBJECT);
        }

    return hr;
    }

/*
 * CImpIOleItemContainer::GetObjectStorage
 *
 * Purpose:
 *  Similar to get Object in that we have to locate the object
 *  described by a given name, but instead of returning any old
 *  interface we return a storage element.
 *
 * Parameters:
 *  pszItem         LPOLESTR to the item we must locate.
 *  pcb             LPBINDCTX providing the binding context.
 *  riid            REFIID of the interface requested.  Usually
 *                  IStorage or IStream.
 *  ppv             PPVOID into which to return the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::GetObjectStorage(LPOLESTR pszItem
    , LPBINDCTX pbc, REFIID riid, PPVOID ppv)
    {
    PCTenant    pTenant;

    *ppv=NULL;

    if (m_fDoc)
        return ResultFromScode(E_NOTIMPL);

    //Can only handle IStorage.
    if (IID_IStorage!=riid)
        return ResultFromScode(E_NOINTERFACE);

    if (TenantFromName(pszItem, &pTenant))
        pTenant->StorageGet((LPSTORAGE *)ppv);

    return (NULL!=*ppv) ? NOERROR : ResultFromScode(E_FAIL);
    }

/*
 * CImpIOleItemContainer::IsRunning
 *
 * Purpose:
 *  Answers if the object under the given name is currently running.
 *
 * Parameters:
 *  pszItem         LPOLESTR of the item to check
 *
 * Return Value:
 *  HRESULT         NOERROR if the object is running, S_FALSE
 *                  otherwise.  Possibly MK_E_NOOBJECT if the name
 *                  is bogus.
 */

STDMETHODIMP CImpIOleItemContainer::IsRunning(LPOLESTR pszItem)
    {
    HRESULT     hr;
    PCTenant    pTenant;
    LPUNKNOWN   pObj;
    LPOLEOBJECT pIOleObject;

    /*
     * If this is the document's container interface, the object
     * is a page and the page is always running.
     */
    if (m_fDoc)
        return NOERROR;
    else
        {
        if (TenantFromName(pszItem, &pTenant))
            {
            //Ask the actual object if its running.
            pTenant->ObjectGet(&pObj);
            hr=pObj->QueryInterface(IID_IOleObject
                , (PPVOID)&pIOleObject);
            pObj->Release();

            if (SUCCEEDED(hr))
                {
                hr=(OleIsRunning(pIOleObject))
                    ? NOERROR : ResultFromScode(S_FALSE);
                pIOleObject->Release();
                }
            }
        else
            hr=ResultFromScode(MK_E_NOOBJECT);
        }

    return hr;
    }

/*
 * CImpIOleItemContainer::TenantFromName
 * (Private)
 *
 * Purpose:
 *  This function which is NOT part of the interface retrieves
 *  a tenant pointer from a tenant name.
 *
 * Parameters:
 *  pszItem         LPOLESTR of the tenant to locate.
 *  ppTenant        PCTenant * in which to return the pointer.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CImpIOleItemContainer::TenantFromName(LPOLESTR pszItem
    , PCTenant *ppTenant)
    {
    DWORD       dw;
    char        szTemp[40];     //ANSI for atol

    if (m_fDoc)
        return FALSE;

    //The item name should be "Tenant xxxx", so use pszItem+7.
    UNICODETOANSI((pszItem+7), szTemp, sizeof(szTemp));
    dw=atol(szTemp);

    *ppTenant=NULL;

    return m_pPage->TenantGetFromID(dw, ppTenant, FALSE);
    }


IPERFILE.CPP

/*
 * IPERFILE.CPP
 * Patron Chapter 24
 *
 * Implementation of the IPersistFile interface for Patron's
 * documents.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CImpIPersistFile:CImpIPersistFile
 * CImpIPersistFile::~CImpIPersistFile
 *
 * Constructor Parameters:
 *  pDoc            PCPatronDoc associated with this interface
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistFile::CImpIPersistFile(PCPatronDoc pDoc
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pDoc=pDoc;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPersistFile::~CImpIPersistFile(void)
    {
    return;
    }

/*
 * CImpIPersistFile::QueryInterface
 * CImpIPersistFile::AddRef
 * CImpIPersistFile::Release
 *
 * Purpose:
 *  Standard set of IUnknown members for this interface
 */

STDMETHODIMP CImpIPersistFile::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistFile::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistFile::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistFile::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the file represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::GetClassID(LPCLSID pClsID)
    {
    *pClsID=CLSID_PatronPages;
    return NOERROR;
    }

/*
 * CImpIPersistFile::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this file since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 */

STDMETHODIMP CImpIPersistFile::IsDirty(void)
    {
    return ResultFromScode(m_pDoc->FDirtyGet() ? S_OK : S_FALSE);
    }

/*
 * IPersistFile::Load
 *
 * Purpose:
 *  Asks the server to load the document for the given filename.
 *
 * Parameters:
 *  pszFile         LPCOLESTR to the filename to load.
 *  grfMode         DWORD containing open flags requested from the
 *                  caller.  Currently these are safely ignored.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::Load(LPCOLESTR pszFile, DWORD grfMode)
    {
    UINT        uRet;

   #ifdef WIN32ANSI
    char        szTemp[CCHPATHMAX];

    WideCharToMultiByte(CP_ACP, 0, pszFile, -1, szTemp, CCHPATHMAX
       , NULL, NULL);
    uRet=m_pDoc->Load(TRUE, szTemp);
   #else
    uRet=m_pDoc->Load(TRUE, (LPTSTR)pszFile);
   #endif
    return (DOCERR_NONE==uRet) ? NOERROR
        : ResultFromScode(STG_E_READFAULT);
    }

/*
 * IPersistFile::Save
 *
 * Purpose:
 *  Instructs the server to write the current file into a new
 *  filename, possibly then using that filename as the current one.
 *
 * Parameters:
 *  pszFile         LPCOLESTR of the file into which we save.  If NULL,
 *                  this means save the current file.
 *  fRemember       BOOL indicating if we're to use this filename as
 *                  the current file now (Save As instead of Save
 *                  Copy As).
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::Save(LPCOLESTR pszFile, BOOL fRemember)
    {
    UINT        uRet;

    /*
     * Since we don't want to mess with changing Save (which would
     * require changes to CLASSLIB, urk) we instead save fRemember
     * in the document before calling Save which supresses the call
     * to CPatronDoc::Rename if FALSE.
     */

    m_pDoc->m_fRename=fRemember;
   #ifdef WIN32ANSI
    char        szTemp[CCHPATHMAX];

    WideCharToMultiByte(CP_ACP, 0, pszFile, -1, szTemp, CCHPATHMAX
       , NULL, NULL);
    uRet=m_pDoc->Save(0, szTemp);
   #else
    uRet=m_pDoc->Save(0, (LPTSTR)pszFile);
   #endif
    m_pDoc->m_fRename=TRUE;

    return (DOCERR_NONE==uRet) ? NOERROR
        : ResultFromScode(STG_E_WRITEFAULT);
    }

/*
 * IPersistFile::SaveCompleted
 *
 * Purpose:
 *  Informs us that the operation that called Save is now finished
 *  and we can access the file again.
 *
 * Parameters:
 *  pszFile         LPCOLESTR of the file in which we can start
 *                  writing again.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::SaveCompleted(LPCOLESTR pszFile)
    {
    return NOERROR;
    }

/*
 * IPersistFile::GetCurFile
 *
 * Purpose:
 *  Retrieves the name of the current file.
 *
 * Parameters:
 *  ppszFile        LPOLESTR * into which we store a pointer to
 *                  the filename that should be allocated with the
 *                  shared IMalloc.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::GetCurFile(LPOLESTR *ppszFile)
    {
    LPMALLOC    pIMalloc;
    LPOLESTR    psz;
    UINT        uRet;

    *ppszFile=NULL;

    if (FAILED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        return ResultFromScode(E_FAIL);

    psz=(LPOLESTR)pIMalloc->Alloc(CCHPATHMAX*sizeof(OLECHAR));
    pIMalloc->Release();

   #ifdef WIN32ANSI
    char    szTemp[CCHPATHMAX];

    uRet=m_pDoc->FilenameGet(szTemp, CCHPATHMAX);
    MultiByteToWideChar(CP_ACP, 0, szTemp, -1, psz, CCHPATHMAX);
   #else
    uRet=m_pDoc->FilenameGet(psz, CCHPATHMAX);
   #endif

    //If we have no filename, return the prompt for File Open/Save.
    if (0==uRet)
       #ifdef WIN32ANSI
        MultiByteToWideChar(CP_ACP, 0
            , (*m_pDoc->m_pST)[IDS_EXTENSION], -1, psz, CCHPATHMAX);
       #else
        lstrcpy(psz, (*m_pDoc->m_pST)[IDS_EXTENSION]);
       #endif

    *ppszFile=psz;
    return (0==uRet) ? ResultFromScode(S_FALSE) : NOERROR;
    }


IUILINK.CPP

/*
 * IUILINK.CPP
 * Patron Chapter 24
 *
 * Implementation of an object with the IOleUILinkContainer
 * interface necessary to use the standard Links Dialog.  This
 * is implemented as a stand-along object with access to the CPage
 * with which its associated, primiarily because it is only used
 * for the one dialog.  Therefore this object has it's own IUnknown.
 * In addition, we use the Links Assistant object developed in this
 * chapter to simplify our own code.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CIOleUILinkContainer::CIOleUILinkContainer
 * CIOleUILinkContainer::~CIOleUILinkContainer
 *
 * Parameters (Constructor):
 *  pPage           PCPage of the page we're in.
 */

CIOleUILinkContainer::CIOleUILinkContainer(PCPage pPage)
    {
    m_cRef=0;
    m_pPage=pPage;
    m_iTenant=0;
    m_pDelIUILinks=NULL;
    m_fDirty=FALSE;
    return;
    }

CIOleUILinkContainer::~CIOleUILinkContainer(void)
    {
    ReleaseInterface(m_pDelIUILinks);
    return;
    }

/*
 * CIOleUILinkContainer::Init
 *
 * Purpose:
 *  Performs initialization on the object that might fail.  In
 *  particular this creates an object of CLSID_LinksAssistant that
 *  helps in implementing this interface.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CIOleUILinkContainer::Init(void)
    {
    HRESULT     hr;

    hr=CoCreateInstance(CLSID_LinksAssistant, NULL
        , CLSCTX_INPROC_SERVER, IID_IOleUILinkContainer
        , (PPVOID)&m_pDelIUILinks);

    return SUCCEEDED(hr);
    }

/*
 * CIOleUILinkContainer::QueryInterface
 * CIOleUILinkContainer::AddRef
 * CIOleUILinkContainer::Release
 *
 * Purpose:
 *  IUnknown members for CIOleUILinkContainer object.
 */

STDMETHODIMP CIOleUILinkContainer::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IOleUILinkContainer==riid)
        {
        *ppv=this;
        AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CIOleUILinkContainer::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CIOleUILinkContainer::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CIOleUILinkContainer::GetNextLink
 *
 * Purpose:
 *  Called when the Links dialog is filling its listbox.  Here we
 *  need to return a key for the first link if dwLink is zero, then
 *  return the next links if it's non-zero.
 *
 * Parameters:
 *  dwLink          DWORD last returned from this function.  Zero if
 *                  this is the first call to this function.
 *
 * Return Value:
 *  DWORD           Some value that identifies this object.  Zero
 *                  stops the sequence such that this function is
 *                  no longer called.
 */

STDMETHODIMP_(DWORD) CIOleUILinkContainer::GetNextLink(DWORD dwLink)
    {
    PCTenant        pTenant;

    //If we're told to start the sequence, set index to zero.
    if (0L==dwLink)
        m_iTenant=0;

    /*
     * On each subsequent call, find the next linked object in
     * this document and return it.  Make sure the index is
     * incremented for the next time this function is called.
     */
    for ( ; m_iTenant < m_pPage->m_cTenants; m_iTenant++)
        {
        if (m_pPage->TenantGet(m_iTenant, &pTenant, FALSE))
            {
            if (TENANTTYPE_LINKEDOBJECT==pTenant->TypeGet())
                {
                m_iTenant++;
                return (DWORD)pTenant;
                }
            }
        }

    //If we hit the end of list, this tells the dialog to stop.
    return 0L;
    }

/*
 * CIOleUILinkContainer::SetLinkUpdateOptions
 *
 * Purpose:
 *  Informs the application to call IOleLink::SetUpdateOptions for
 *  the object identified by dwLink.
 *
 * Parameters:
 *  dwLink          DWORD object identifier as returned from
 *                  GetNextLink.
 *  dwOptions       DWORD containing the new options.
 *
 * Return Value:
 *  HRESULT         Return value of IOleLink::SetUpdateOptions.
 */

STDMETHODIMP CIOleUILinkContainer::SetLinkUpdateOptions(DWORD dwLink
    , DWORD dwOptions)
    {
    LPOLELINK       pIOleLink;
    HRESULT         hr;

    if (NULL==dwLink)
        return ResultFromScode(E_FAIL);

    /*
     * Your responsibility is to call the object's
     * IOleLink::SetUpdateOptions function with dwOptions.  Simple?
     *
     * For Patron we must first get the object pointer obtainable
     * from the tenant's ObjectGet function, then QI for IOleLink.
     */

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    hr=pIOleLink->SetUpdateOptions(dwOptions);
    pIOleLink->Release();

    m_fDirty=SUCCEEDED(hr);
    return hr;
    }

/*
 * CIOleUILinkContainer::GetLinkUpdateOptions
 *
 * Purpose:
 *  Requests the container to call IOleLink::GetUpdateOptions for
 *  the object identified by dwLink.
 *
 * Parameters:
 *  dwLink          DWORD identifying the object
 *  pdwOptions      LPDWORD in which to store the options.
 *
 * Return Value:
 *  HRESULT         Return value of IOleLink::GetUpdateOptions
 */

STDMETHODIMP CIOleUILinkContainer::GetLinkUpdateOptions(DWORD dwLink
    , LPDWORD pdwOptions)
    {
    LPOLELINK       pIOleLink;
    HRESULT         hr;

    if (NULL==dwLink)
        return ResultFromScode(E_FAIL);

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    hr=pIOleLink->GetUpdateOptions(pdwOptions);
    pIOleLink->Release();

    return hr;
    }

/*
 * CIOleUILinkContainer::SetLinkSource
 *
 * Purpose:
 *  Changes the moniker to which an object is linked.
 *
 * Parameters:
 *  dwLink          DWORD identifying the object in question.
 *  pszName         LPTSTR to the displayable name of the source.
 *  cchName         ULONG length of the file portaion of pszName
 *  pchEaten        ULONG * in which to return the number of
 *                  characters used in parsing pszDisplayName.
 *  fValidate       BOOL indicating if we're to validate that the
 *                  source exists first.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_FAIL otherwise.
 */

STDMETHODIMP CIOleUILinkContainer::SetLinkSource(DWORD dwLink
    , LPTSTR pszName, ULONG cchName, ULONG *pchEaten
    , BOOL fValidate)
    {
    PCTenant        pTenant=(PCTenant)dwLink;
    HRESULT         hr;
    LPOLELINK       pIOleLink;

    if (NULL==dwLink)
        return ResultFromScode(E_FAIL);

    //This is for use in GetLinkSource, below.
    pTenant->m_fLinkAvail=FALSE;

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    hr=m_pDelIUILinks->SetLinkSource((DWORD)pIOleLink, pszName
        , cchName, pchEaten, fValidate);
    pIOleLink->Release();

    if (FAILED(hr))
        return hr;

    //hr will be S_FALSE if link is unavailable.
    pTenant->Repaint();
    pTenant->m_fLinkAvail=(NOERROR==hr);
    m_fDirty=TRUE;
    return NOERROR;
    }

/*
 * CIOleUILinkContainer::GetLinkSource
 *
 * Purpose:
 *  Retrieves various strings and values for this link source.
 *
 * Parameters:
 *  dwLink          DWORD identifying the object affected.
 *  ppszName        LPTSTR * in which to return the new source
 *                  name
 *  pcchName        ULONG * in which to return the length of
 *                  pszName
 *  ppszFullLink    LPTSTR * in which to return the full name of
 *                  the class of linked object.
 *  ppszShortLink   LPTSTR * in which to return the short name of
 *                  the class of linked object.
 *  pfSourceAvail   BOOL * in which to return if this is an
 *                  available link source.
 *  pfSelected      BOOL * in which to return if this object is
 *                  currently selected in the document.  This
 *                  selects the item in the listbox for this object.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, error code otherwise.
 */

STDMETHODIMP CIOleUILinkContainer::GetLinkSource(DWORD dwLink
    , LPTSTR *ppszName, ULONG *pcchName, LPTSTR *ppszFullLink
    , LPTSTR *ppszShortLink, BOOL *pfSourceAvail, BOOL *pfSelected)
    {
    HRESULT         hr;
    PCTenant        pTenant=(PCTenant)dwLink;
    LPOLELINK       pIOleLink=NULL;
    LPOLEOBJECT     pIOleObject=NULL;
    LPMONIKER       pmk=NULL;
    LPMONIKER       pmkFirst=NULL;
    LPBC            pbc=NULL;

    if (NULL==dwLink)
        return ResultFromScode(E_FAIL);

    //We know what this is from SetLinkSource
    *pfSourceAvail=pTenant->m_fLinkAvail;

    if (pfSelected)
        *pfSelected=pTenant->FIsSelected();

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    hr=m_pDelIUILinks->GetLinkSource((DWORD)pIOleLink, ppszName
        , pcchName, ppszFullLink, ppszShortLink, pfSourceAvail
        , pfSelected);

    pIOleLink->Release();
    return hr;
    }

/*
 * CIOleUILinkContainer::OpenLinkSource
 *
 * Purpose:
 *  Asks the container to call DoVerb on this object with
 *  OLEIVERB_SHOW.
 *
 * Parameters:
 *  dwLink          DWORD identifying the linked object.
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CIOleUILinkContainer::OpenLinkSource(DWORD dwLink)
    {
    PCTenant        pTenant=(PCTenant)dwLink;

    pTenant->Activate(OLEIVERB_OPEN, NULL);
    return NOERROR;
    }

/*
 * CIOleUILinkContainer::UpdateLink
 *
 * Purpose:
 *  Asks the container to update the link for this object.
 *
 * Parameters:
 *  dwLink          DWORD identifying the linked object.
 *  fErrorMessage   BOOL indicating if we can show errors.
 *  fErrorAction    BOOL making no sense whatsoever.
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CIOleUILinkContainer::UpdateLink(DWORD dwLink
    , BOOL fErrorMessage, BOOL fErrorAction)
    {
    PCTenant        pTenant=(PCTenant)dwLink;
    LPOLELINK       pIOleLink;
    HRESULT         hr;

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    hr=m_pDelIUILinks->UpdateLink((DWORD)pIOleLink, fErrorMessage
        , fErrorAction);

    pTenant->Repaint();
    pTenant->m_fLinkAvail=SUCCEEDED(hr);
    pIOleLink->Release();

    if (FAILED(hr))
        {
        if (fErrorMessage)
            {
            MessageBox(m_pPage->m_hWnd
                , TEXT("Could not update link."), TEXT("Patron")
                , MB_OK);
            }
        }
    else
        m_fDirty=TRUE;

    return hr;
    }

/*
 * CIOleUILinkContainer::CancelLink
 *
 * Purpose:
 *  Requests that the container turn this linked object into a
 *  static object.
 *
 * Parameters:
 *  dwLink          DWORD identifying the linked object.
 *
 * Return Value:
 *  HRESULT         Standard.
 */

STDMETHODIMP CIOleUILinkContainer::CancelLink(DWORD dwLink)
    {
    PCTenant        pTenant=(PCTenant)dwLink;
    LPOLELINK       pIOleLink;
    HRESULT         hr;

    hr=GetObjectInterface(dwLink, IID_IOleLink, (PPVOID)&pIOleLink);

    if (FAILED(hr))
        return hr;

    //This sets the source moniker to NULL.
    m_pDelIUILinks->CancelLink((DWORD)pIOleLink);
    pIOleLink->Release();

    //Go change this object over to a static one.
    pTenant->ConvertToStatic();

    m_fDirty=TRUE;
    return NOERROR;
    }

//PROTECTED FUNCTIONS INTERNAL TO CIOleUILinkContainer

/*
 * CIOleUILinkContainer::GetObjectInterface
 * (Protected)
 *
 * Purpose:
 *  Retrieves and interface pointer for the object identified by
 *  dwLink
 *
 * Parameters:
 *  dwLink          DWORD identifying the object
 *  riid            REFIID of the interface desired.
 *  ppv             PPVOID into which we return the pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, error code otherwise.
 */

STDMETHODIMP CIOleUILinkContainer::GetObjectInterface(DWORD dwLink
    , REFIID riid, PPVOID ppv)
    {
    PCTenant        pTenant=(PCTenant)dwLink;
    LPUNKNOWN       pIUnknown;
    HRESULT         hr;

    pTenant->ObjectGet(&pIUnknown);
    hr=pIUnknown->QueryInterface(riid, ppv);
    pIUnknown->Release();

    return hr;
    }


PAGE.CPP

/*
 * PAGE.CPP
 * Patron Chapter 24
 *
 * Implementation of parts of the CPage class; those member
 * functions dealing with mouse events are in PAGEMOUS.CPP.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPage::CPage
 * CPage::~CPage
 *
 * Constructor Parameters:
 *  dwID            DWORD identifier for this page.
 *  hWnd            HWND of the pages window (for repaints, etc).
 *  pPG             PCPages to the Pages window.
 */

CPage::CPage(DWORD dwID, HWND hWnd, PCPages pPG)
    {
    m_dwID     =dwID;
    m_pIStorage=NULL;

    m_cOpens=0;
    m_hWnd=hWnd;
    m_pPG=pPG;

    m_dwIDNext      =0;
    m_cTenants      =0;
    m_hWndTenantList=NULL;
    m_iTenantCur    =NOVALUE;   //Tenants are zero indexed.
    m_pTenantCur    =NULL;

    m_uHTCode=HTNOWHERE;
    m_uSizingFlags=0;
    m_fTracking=FALSE;
    m_hDC=NULL;

    m_fDragPending=FALSE;
    m_fSizePending=FALSE;
    m_fTimer=FALSE;

    //Get WIN.INI distance and delay values, with OLE defaults.
    m_cxyDist=GetProfileInt(TEXT("windows"), TEXT("DragMinDist")
        , DD_DEFDRAGMINDIST);
    m_cDelay=GetProfileInt(TEXT("windows"), TEXT("DragDelay")
        , DD_DEFDRAGDELAY);

    m_fReopen=FALSE;
    m_pmkFile=m_pPG->m_pmkFile;

    if (NULL!=m_pmkFile)
        m_pmkFile->AddRef();

    m_cRef=0L;
    m_dwRegROTWild=0L;
    m_pImpIOleItemContainer=NULL;

    m_fFirstUIActivate=TRUE;  //We haven't UIActivated anyone yet
    return;
    }

CPage::~CPage(void)
    {
    INOLE_RevokeAsRunning(&m_dwRegROTWild);

    if (m_fTimer)
        KillTimer(m_hWnd, IDTIMER_DEBOUNCE);

    m_hWnd=NULL;
    Close(FALSE);
    return;
    }

/*
 * CPage::QueryInterface
 * CPage::AddRef
 * CPage::Release
 *
 * Purpose:
 *  IUnknown members for CPage object.
 */

STDMETHODIMP CPage::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IOleItemContainer==riid || IID_IOleContainer==riid
        || IID_IParseDisplayName==riid)
        *ppv=m_pImpIOleItemContainer;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CPage::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPage::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CPage::GetID
 *
 * Return Value:
 *  DWORD           dwID field in this page.
 */

DWORD CPage::GetID(void)
    {
    return m_dwID;
    }

/*
 * CPage::Open
 *
 * Purpose:
 *  Retrieves the IStorage associated with this page.  The IStorage
 *  is owned by the page and thus the page always holds a reference
 *  count.  The caller should call Close or delete this page to
 *  match this open.
 *
 *  This function may be called multiple times resulting in
 *  additional reference counts on the storage each of which must be
 *  matched with a call to Close.  The last Close can be done
 *  through delete.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which this page lives.
 *
 * Return Value:
 *  BOOL            TRUE if opening succeeds, FALSE otherwise.
 */

BOOL CPage::Open(LPSTORAGE pIStorage)
    {
    HRESULT                 hr=NOERROR;
    LPSTREAM                pIStream;
    DWORD                   dwMode;
    OLECHAR                 szTemp[32];
    TCHAR                   szCap[32];
    BOOL                    fNew;
    BOOL                    fCreated=FALSE;
    TENANTLIST              tl;
    PTENANTINFO             pti;
    ULONG                   cb;
    LPMALLOC                pIMalloc;
    UINT                    i;
    PCTenant                pTenant;
    UINT                    cLinks;
    LPOLELINK               pIOleLink;
    LPUNKNOWN               pIUnknown;
    UINT                    uRet;
    OLEUIEDITLINKS          el;
    PCIOleUILinkContainer   pIUILinks;
    HWND                    hWndDoc;

    fNew=(NULL==m_pIStorage);

    if (!fNew)
        {
        m_cOpens++;
        m_pIStorage->AddRef();
        return TRUE;
        }

    if (NULL==pIStorage)
        return FALSE;

    /*
     * Attempt to open the storage under this ID.  If none,
     * create one.  In either case, the IStorage is either
     * saved in pPage or released.
     */

    GetStorageName(szTemp);
    dwMode=STGM_TRANSACTED | STGM_READWRITE | STGM_SHARE_EXCLUSIVE;

    hr=pIStorage->OpenStorage(szTemp, NULL, dwMode, NULL, 0
        , &m_pIStorage);

    if (FAILED(hr))
        {
        hr=pIStorage->CreateStorage(szTemp, dwMode, 0, 0
            , &m_pIStorage);
        fCreated=TRUE;
        }

    if (FAILED(hr))
        return FALSE;

    m_cOpens++;

    if (NULL==m_hWndTenantList)
        {
        /*
         * The first time we open this page, create the hidden
         * listbox we'll use to track tenants.  We give it the
         * owner-draw style so we can just store pointers in it.
         */
        m_hWndTenantList=CreateWindow(TEXT("listbox")
            , TEXT("Tenant List"), WS_POPUP | LBS_OWNERDRAWFIXED
            , 0, 0, 100, 100, HWND_DESKTOP, NULL
            , m_pPG->m_hInst, NULL);

        if (NULL==m_hWndTenantList)
            return FALSE;
        }

    m_pImpIOleItemContainer=new CImpIOleItemContainer(this, this
        , FALSE);

    if (NULL==m_pImpIOleItemContainer)
        return FALSE;

    //If this is brand-new, we're done.
    if (fCreated)
        return TRUE;

    /*
     * Now open the stream we saved in Close and load all the
     * tenants listed in there.  If there are none, then we don't
     * have to load squat.
     */

    hr=m_pIStorage->OpenStream(SZSTREAMTENANTLIST, NULL, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pIStream->Read(&tl, sizeof(tl), NULL);
        m_cTenants=tl.cTenants;
        m_dwIDNext=tl.dwIDNext;
        m_iTenantCur=0;

        cb=tl.cTenants*sizeof(TENANTINFO);

        if (0!=cb)
            {
            pti=(PTENANTINFO)pIMalloc->Alloc(cb);

            if (NULL!=pti)
                {
                pIStream->Read(pti, cb, NULL);

                for (i=0; i < m_cTenants; i++)
                    {
                    if (TenantAdd(NOVALUE, (pti+i)->dwID, &pTenant))
                        {
                        pTenant->Load(m_pIStorage, (pti+i));

                        //Make sure it knows about the show state.
                        pTenant->ShowObjectType(m_pPG->m_fShowTypes);
                        }
                    }

                pIMalloc->Free(pti);
                }
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    //Get and select the first tenant
    if (TenantGet(0, &m_pTenantCur, FALSE))
        m_pTenantCur->Select(TRUE, TRUE);

    //If we just saved and closed, don't bother with updating links
    if (m_fReopen)
        {
        m_fReopen=FALSE;
        return TRUE;
        }

    /*
     * Update all the links in this page, showing the progress
     * indicator as it's happening.  We use the same
     * IOlUILinkContainer implementation as we do for the links
     * dialog, passing it to OleUIUpdateLinks which does everything
     * for us.
     *
     * We might also optimize this to not do anything if there are
     * no automatic links, but it's not a big concern.
     */

    //First, count the number of automatic links.
    cLinks=0;

    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            {
            DWORD       dw;

            pTenant->ObjectGet(&pIUnknown);
            hr=pIUnknown->QueryInterface(IID_IOleLink
                , (PPVOID)&pIOleLink);
            pIUnknown->Release();

            if (FAILED(hr))
                continue;

            pIOleLink->GetUpdateOptions(&dw);
            pIOleLink->Release();

            if (OLEUPDATE_ALWAYS==dw)
                cLinks++;
            }
        }

    //If we have any automatic links, invoke the update dialog.
    if (0==cLinks)
        return TRUE;

    //Create an IOleUILinkContainer instantiation.
    if (!m_pPG->GetUILinkContainer(&pIUILinks))
        return TRUE;    //Guess we can't update, oh well.

    hWndDoc=GetParent(m_hWnd);
    LoadString(m_pPG->m_hInst, IDS_CAPTION, szCap, sizeof(szCap));

    if (!OleUIUpdateLinks(pIUILinks, hWndDoc, szCap, cLinks))
        {
        /*
         * If updating failed, ask to show the links dialog.  NOTE:
         * OleUIPromptUser has a variable wsprintf argument list
         * after the hWnd parameter!  Use appropriate typecasting!
         */
        uRet=OleUIPromptUser(IDD_CANNOTUPDATELINK, hWndDoc, szCap);

       #ifdef IDC_PU_LINKS
        if (IDC_PU_LINKS==uRet)
       #else
        if (ID_PU_LINKS==uRet)
       #endif
            {
            //Throw up the links dialog.
            memset(&el, 0, sizeof(el));
            el.cbStruct=sizeof(el);
            el.hWndOwner=hWndDoc;
            el.lpOleUILinkContainer=pIUILinks;
            OleUIEditLinks(&el);
            }
        }

    m_pPG->m_fDirty=pIUILinks->m_fDirty;
    pIUILinks->Release();
    return TRUE;
    }

/*
 * CPage::Close
 *
 * Purpose:
 *  Possibly commits the storage, then releases it reversing the
 *  reference count from Open.
 *
 * Parameters:
 *  fCommit         BOOL indicating if we're to commit.
 *
 * Return Value:
 *  None
 */

void CPage::Close(BOOL fCommit)
    {
    if (NULL==m_pIStorage)
        return;

    if (fCommit)
        Update();

    m_pIStorage->Release();

    //If this was the last close, make all tenants loaded->passive
    if (0==--m_cOpens)
        {
        UINT        i;
        PCTenant    pTenant;

        m_pIStorage=NULL;

        for (i=0; i < m_cTenants; i++)
            {
            if (TenantGet(i, &pTenant, FALSE))
                {
                if (NULL!=m_hWnd)
                    {
                    //Open may select again, so this repaints.
                    pTenant->Select(FALSE, TRUE);
                    }

                pTenant->Close(FALSE);
                pTenant->Release();
                }
            }

        DestroyWindow(m_hWndTenantList);
        m_hWndTenantList=NULL;
        m_fReopen=TRUE;

        if (NULL!=m_pmkFile)
            m_pmkFile->Release();

        m_pmkFile=NULL;

        DeleteInterfaceImp(m_pImpIOleItemContainer);
        }

    return;
    }

/*
 * CPage::Update
 *
 * Purpose:
 *  Forces a common on the page if it's open.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if there are any open objects on this page,
 *                  that is, we should remain open.
 */

BOOL CPage::Update(void)
    {
    BOOL            fOpen=FALSE;
    UINT            i;
    PCTenant        pTenant;
    HRESULT         hr;
    LPSTREAM        pIStream;
    TENANTLIST      tl;
    PTENANTINFO     pti;
    ULONG           cb;
    LPMALLOC        pIMalloc;

    //Walk the list of objects and update them all as well.
    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            fOpen |= pTenant->Update();
        }

    //Now write our own stream containing the tenant list.
    hr=m_pIStorage->CreateStream(SZSTREAMTENANTLIST, STGM_CREATE
        | STGM_WRITE| STGM_DIRECT | STGM_SHARE_EXCLUSIVE, 0, 0
        , &pIStream);

    if (FAILED(hr))
        return fOpen;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        tl.cTenants=m_cTenants;
        tl.dwIDNext=m_dwIDNext;

        pIStream->Write(&tl, sizeof(TENANTLIST), &cb);

        cb=m_cTenants*sizeof(TENANTINFO);
        pti=(PTENANTINFO)pIMalloc->Alloc(cb);

        if (NULL!=pti)
            {
            for (i=0; i < m_cTenants; i++)
                {
                TenantGet(i, &pTenant, FALSE);
                pTenant->GetInfo((pti+i));
                }

            pIStream->Write(pti, cb, &cb);
            pIMalloc->Free(pti);
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    //Now commit the whole mess and we're done
    if (NULL!=m_pIStorage)
        m_pIStorage->Commit(STGC_DEFAULT);

    return fOpen;
    }

/*
 * CPage::Destroy
 *
 * Purpose:
 *  Removes this page from the given storage.  The caller should
 *  eventually delete this Page object to free the storage.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE contianing this page on which to call
 *                  DestroyElement
 *
 * Return Value:
 *  None
 */

void CPage::Destroy(LPSTORAGE pIStorage)
    {
    if (NULL!=pIStorage)
        {
        OLECHAR szTemp[32];

        Close(FALSE);
        GetStorageName(szTemp);
        pIStorage->DestroyElement(szTemp);
        }

    return;
    }

/*
 * CPage::GetStorageName
 *
 * Parameters:
 *  pszName         LPOLESTR to a buffer in which to store the
 *                  storage name for this page.
 *
 * Return Value:
 *  UINT            Number of characters stored.
 */

UINT CPage::GetStorageName(LPOLESTR pszName)
    {
   #ifdef WIN32ANSI
    char        szTemp[32];
    UINT        cch;

    cch=wsprintf(szTemp, "Page %lu", m_dwID);
    MultiByteToWideChar(CP_ACP, 0, szTemp, -1, pszName, 32);
    return cch;
   #else
    return wsprintf(pszName, TEXT("Page %lu"), m_dwID);
   #endif
    }

/*
 * CPage::Draw
 *
 * Purpose:
 *  Draws the objects on this page to the given hDC.
 *
 * Parameters:
 *  hDC             HDC on which to draw.
 *  xOff, yOff      int offsets for the page.
 *  fNoColor        BOOL indicating black & white screen rendering.
 *  fPrinter        BOOL indicating hDC is on the printer.
 *
 * Return Value:
 *  None
 */

void CPage::Draw(HDC hDC, int xOff, int yOff, BOOL fNoColor
    , BOOL fPrinter)
    {
    int                 i;
    PCTenant            pTenant;
    HDC                 hIC=NULL;
    PCOMBINEDEVICE      pcd=NULL;
    DVTARGETDEVICE     *ptd=NULL;

    /*
     * If printing, tell the tenant to forget the borders. Otherwise
     * we leave xOff and yOff the same to account for scrolling.
     */
    if (fPrinter)
        {
        xOff=LOMETRIC_BORDER+m_pPG->m_xMarginLeft;
        yOff=-LOMETRIC_BORDER-m_pPG->m_yMarginTop;

        /*
         * Get device information.  If this fails, ptd is
         * NULL which is acceptable.
         */
        if (m_pPG->DevReadConfig(&pcd, &hIC))
            ptd=&(pcd->td);
        }

    for (i=(int)m_cTenants-1; i >=0; i--)
        {
        if (TenantGet(i, &pTenant, FALSE))
            {
            RECT        rc, rcWin;
            RECTL       rcl;

            //Paint this tenant only if visible.
            pTenant->RectGet(&rcl, TRUE);
            RECTFROMRECTL(rc, rcl);
            OffsetRect(&rc, -(int)m_pPG->m_xPos
                , -(int)m_pPG->m_yPos);
            GetClientRect(m_hWnd, &rcWin);

            if (IntersectRect(&rc, &rc, &rcWin))
                {
                pTenant->Draw(hDC, ptd, hIC, xOff, yOff
                    , fNoColor, fPrinter);
                }
            }
        }

    //Free whatever CPages::DevReadConfig returned.
    if (NULL!=pcd)
        {
        LPMALLOC    pIMalloc;

        if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
            {
            pIMalloc->Free(pcd);
            pIMalloc->Release();
            }
        }

    if (NULL!=hIC)
        DeleteDC(hIC);

    return;
    }

/*
 * CPage::TenantCreate
 *
 * Purpose:
 *  Creates a new tenant of a specific type.
 *
 * Parameters:
 *  tType           TENANTTYPE to create.
 *  pv              LPVOID providing information for the new
 *                  object creation.
 *  pFE             LPFORMATETC describing how we want this
 *                  rendered.
 *  ppo             PPATRONOBJECT with placement data.
 *  dwData          DWORD extra data to pass to the tenant.
 *
 * Return Value:
 *  None
 */

BOOL CPage::TenantCreate(TENANTTYPE tType, LPVOID pv
    , LPFORMATETC pFE, PPATRONOBJECT ppo, DWORD dwData)
    {
    PCTenant    pTenant;
    UINT        uRet;
    int         x, y;
    int         h, v;
    POINTL      ptl;
    SIZEL       szl;
    RECTL       rcl;
    RECT        rc;

    //New tenants go at top of the pile; zero index to TenantAdd.
    if (!TenantAdd(0, m_dwIDNext, &pTenant))
        return FALSE;

    uRet=pTenant->Create(tType, pv, pFE, &ptl, &szl, m_pIStorage
        , ppo, dwData);

    if (CREATE_FAILED==uRet)
        {
        //Reverse Create AND TenantAdd
        SendMessage(m_hWndTenantList, LB_DELETESTRING, 0, 0L);
        pTenant->Destroy(m_pIStorage);

        pTenant->Release();
        return FALSE;
        }

    m_dwIDNext++;
    m_cTenants++;

    if (NULL!=m_pTenantCur)
        m_pTenantCur->Select(FALSE, TRUE);

    m_iTenantCur=0;             //First one in the list now.
    m_pTenantCur=pTenant;

    //Tell the tenant where it lives, default is (0,0) in print area
    x=LOMETRIC_BORDER+m_pPG->m_xMarginLeft;
    y=-LOMETRIC_BORDER-m_pPG->m_yMarginTop;

    h=x;
    v=y;

    if (CREATE_PLACEDOBJECT==uRet)
        {
        SetRect(&rc, 3*CXYHANDLE, 3*CXYHANDLE, 0, 0);
        RectConvertMappings(&rc, NULL, FALSE);

        //Make sure place point is on page, otherwise go to (0,0)
        if (((int)ptl.x > x)
            && ((int)ptl.x < x+(int)m_pPG->m_cx-rc.left))
            x=(int)ptl.x;

        //m_pPG->m_cy is absolute
        if (((int)ptl.y < y)
            && ((int)ptl.y > y-(int)m_pPG->m_cy-rc.top))
            y=(int)ptl.y;
        }

    //Bounds check size of the object and fit to page as necessary.
    if (x+(int)szl.cx > (int)(h+m_pPG->m_cx))
        szl.cx=h+m_pPG->m_cx-x;

    //Remember that szl we get from Create is absolute
    if (y-(int)szl.cy < (int)(v-m_pPG->m_cy))
        szl.cy=-(int)(v-m_pPG->m_cy-y);

    SETRECTL(rcl, x, y, x+szl.cx, y-szl.cy);
    m_pTenantCur->RectSet(&rcl, FALSE, TRUE);

    //Force a repaint on this new guy
    m_pTenantCur->Invalidate();
    UpdateWindow(m_hWnd);

    m_pTenantCur->Select(TRUE, TRUE);

    //Make sure this new tenant knows about showing it's type.
    m_pTenantCur->ShowObjectType(m_pPG->m_fShowTypes);

    //New tenants must know the available monikers
    if (NULL!=m_pmkFile)
        {
        LPBC        pbc;
        LPMONIKER   pmkPage;
        TCHAR       szTemp[32];
        LPTSTR      pszFile;

       #ifdef WIN32ANSI
        OLECHAR     szW[32];

        GetStorageName(szW);
        WideCharToMultiByte(CP_ACP, 0, szW, -1, szTemp, 32
           , NULL, NULL);
       #else
        GetStorageName(szTemp);
       #endif
        CreateItemMoniker(TEXT("!"), szTemp, &pmkPage);

        //We can get the filename from our file moniker
        CreateBindCtx(0, &pbc);
       #ifdef WIN32ANSI
        LPOLESTR    pszTemp;
        m_pmkFile->GetDisplayName(pbc, NULL, &pszTemp);
        pszFile=(LPTSTR)CoTaskMemAlloc(512);
        WideCharToMultiByte(CP_ACP, 0, pszTemp, -1, pszFile
           , 512, NULL, NULL);
        CoTaskMemFree((void *)pszTemp);
       #else
        m_pmkFile->GetDisplayName(pbc, NULL, &pszFile);
       #endif
        pbc->Release();

        pTenant->NotifyOfRename(pszFile, m_pmkFile, pmkPage);
        pmkPage->Release();
        CoTaskMemFree((void *)pszFile);
        }

    //Activate new objects immediately and force a save on them
    if (TENANTTYPE_EMBEDDEDOBJECT==tType)
        {
        //CHAPTER24MOD
        DWORD       dwFlags;

        /*
         * A control, by virtue of being a control, will already
         * be in-place active except in design mode.  In any case,
         * the only thing a control can do is draw on our page,
         * so there's little point in activating a control here.
         */
        dwFlags=m_pTenantCur->GetControlFlags();

        if (!(TENANTSTATE_CONTROL & dwFlags))
            {
            m_pTenantCur->Activate(OLEIVERB_SHOW, NULL);
            m_pTenantCur->Update();
            }
        //End CHAPTER24MOD
        }

    return TRUE;
    }

/*
 * CPage::TenantDestroy
 *
 * Purpose:
 *  Destroys the currently selected tenant on this page.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPage::TenantDestroy(void)
    {
    if (NULL==m_pTenantCur)
        {
        MessageBeep(0);
        return FALSE;
        }

    SendMessage(m_hWndTenantList, LB_DELETESTRING
        , m_iTenantCur, 0L);

    m_pTenantCur->Invalidate();
    m_pTenantCur->Destroy(m_pIStorage);
    m_pTenantCur->Release();
    m_pTenantCur=NULL;

    //Update counts, etc., and select the next tenant in the list.
    if (m_iTenantCur==m_cTenants-1)
        m_iTenantCur--;

    if (0==--m_cTenants)
        m_pTenantCur=NULL;
    else
        {
        TenantGet(m_iTenantCur, &m_pTenantCur, TRUE);
        m_pTenantCur->Select(TRUE, TRUE);
        }

    UpdateWindow(m_hWnd);
    return TRUE;
    }

/*
 * CPage::TenantClip
 *
 * Purpose:
 *  Copies or cuts the currently selected tenant to the clipoard.
 *
 * Parameters:
 *  fCut            BOOL TRUE to cut the object, FALSE to copy.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPage::TenantClip(BOOL fCut)
    {
    LPDATAOBJECT    pIDataObject;
    BOOL            fRet=FALSE;

    if (NULL==m_pTenantCur)
        return FALSE;

    /*
     * To perform a data transfer operation, we need to create a
     * data object with the selected object's data inside. To do
     * this we CoCreateInstance on CLSID_DataTransferObject
     * (Also implemented in this chapter), retrieve data from the
     * object we have, stuff that data into the transfer object,
     * then stick that object on the clipboard.
     *
     * Since we'll want an identical object at other times, like for
     * drag-drop, we use a private function to actually create it.
     */

    pIDataObject=TransferObjectCreate(NULL);

    if (NULL!=pIDataObject)
        {
        if (SUCCEEDED(OleSetClipboard(pIDataObject)))
            {
            if (fCut)
                TenantDestroy();

            fRet=TRUE;
            }

        pIDataObject->Release();
        }

    return fRet;
    }

/*
 * CPage::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected on this
 *  page for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU of the Edit menu.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPage::FQueryObjectSelected(HMENU hMenu)
    {
    HMENU       hMenuTemp;

    /*
     * This will only be called on WM_INITMENUPOPUP, we'll also
     * use this function to create the Verb menu for this object.
     */
    if (NULL!=m_pTenantCur)
        {
        m_pTenantCur->AddVerbMenu(hMenu, MENUPOS_OBJECT);
        return TRUE;
        }

    OleUIAddVerbMenu(NULL, NULL, hMenu, MENUPOS_OBJECT
        , IDM_VERBMIN, IDM_VERBMAX, FALSE, 0, &hMenuTemp);

    return FALSE;
    }

/*
 * CPage::ActivateObject
 *
 * Purpose:
 *  Executes a verb on the currently selected object.
 *
 * Parameters:
 *  iVerb           LONG of the selected verb.
 *  pMSG            LPMSG that caused the invocation of the verb.
 *
 * Return Value:
 *  None
 */

void CPage::ActivateObject(LONG iVerb, LPMSG pMSG)
    {
    if (NULL==m_pTenantCur)
        return;

    m_pTenantCur->Activate(iVerb, pMSG);
    return;
    }

/*
 * CPage::ShowObjectTypes
 *
 * Purpose:
 *  Loops through all the tenants and tells each one to turn on or
 *  off the Show Objects features.
 *
 * Parameters:
 *  fShow           BOOL indicating to show the type or not.
 *
 * Return Value:
 *  None
 */

void CPage::ShowObjectTypes(BOOL fShow)
    {
    PCTenant    pTenant;
    UINT        i;

    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            pTenant->ShowObjectType(fShow);
        }

    return;
    }

/*
 * CPage::NotifyTenantsOfRename
 *
 * Purpose:
 *  Loops through all the tenants and informs each of the new
 *  document name.
 *
 * Parameters:
 *  pszFile         LPTSTR of the new filename.
 *  pmk             LPMONKIER for the new filename.
 *
 * Return Value:
 *  None
 */

void CPage::NotifyTenantsOfRename(LPTSTR pszFile, LPMONIKER pmk)
    {
    PCTenant    pTenant;
    UINT        i;
    LPMONIKER   pmkPage;
    LPMONIKER   pmkAll;
    TCHAR       szTemp[32];

    //Save the file moniker
    ReleaseInterface(m_pmkFile);
    m_pmkFile=pmk;
    m_pmkFile->AddRef();

    //Create a page moniker to send to the tenants.
   #ifdef WIN32ANSI
    OLECHAR     szW[32];

    GetStorageName(szW);
    WideCharToMultiByte(CP_ACP, 0, szW, -1, szTemp, 32
       , NULL, NULL);
   #else
    GetStorageName(szTemp);
   #endif
    CreateItemMoniker(TEXT("!"), szTemp, &pmkPage);

    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            pTenant->NotifyOfRename(pszFile, pmk, pmkPage);
        }

    /*
     * Register a File!Page!"\" wildcard moniker as well.
     * Note that the page is already marked as running
     * with the document's wildcard moniker.
     */
    CreateItemMoniker(TEXT("!"), TEXT("\\"), &pmkAll);

    if (NULL!=pmkAll)
        {
        LPMONIKER   pmkWild=NULL;
        LPMONIKER   pmkTemp=NULL;

        INOLE_RevokeAsRunning(&m_dwRegROTWild);
        pmk->ComposeWith(pmkPage, FALSE, &pmkTemp);

        if (NULL!=pmkTemp)
            {
            pmkTemp->ComposeWith(pmkAll, FALSE, &pmkWild);
            pmkTemp->Release();
            }

        if (NULL!=pmkWild)
            {
            INOLE_RegisterAsRunning(this, pmk, 0
                , &m_dwRegROTWild);
            pmkWild->Release();
            }

        pmkAll->Release();
        }

    //If anything held onto this, they AddRef'd
    pmkPage->Release();
    return;
    }

/*
 * CPage::ConvertObject
 *
 * Purpose:
 *  Invokes and handles the results of the Convert dialog
 *
 * Parameters:
 *  hWndFrame       HWND to use as the parent of the dialog.
 *  fNoServer       BOOL indicating if this was called because
 *                  ActivateObject failed.
 *
 * Return Value:
 *  None
 */

BOOL CPage::ConvertObject(HWND hWndFrame, BOOL fNoServer)
    {
    HRESULT         hr;
    OLEUICONVERT    ct;
    TENANTTYPE      tType;
    TENANTINFO      ti;
    UINT            uRet;
    HCURSOR         hCur;
    BOOL            fActivate=fNoServer;
    SIZEL           szl;

    if (NULL==m_pTenantCur)
        return FALSE;

    tType=m_pTenantCur->TypeGet();

    if (TENANTTYPE_STATIC==tType)
        {
        MessageBeep(0);
        return FALSE;
        }

    //Get object information we may want.
    m_pTenantCur->GetInfo(&ti);

    //Fill the structure.
    memset(&ct, 0, sizeof(ct));
    ct.cbStruct=sizeof(OLEUICONVERT);
    ct.hWndOwner=hWndFrame;
    ct.fIsLinkedObject=(TENANTTYPE_LINKEDOBJECT==tType);
    ct.dvAspect=ti.fe.dwAspect;

    m_pTenantCur->ObjectClassFormatAndIcon(&ct.clsid, &ct.wFormat

, &ct.lpszUserType, &ct.hMetaPict, &ct.lpszDefLabel);

    uRet=OleUIConvert(&ct);

    if (OLEUI_OK==uRet)
        {
        //Potentially a long operation.
        hCur=SetCursor(LoadCursor(NULL, IDC_WAIT));

        //Prevent multiple repaints.
        m_pTenantCur->EnableRepaint(FALSE);

        //First, let's bother with the iconic aspect switch.
        if ((DVASPECT_ICON==ct.dvAspect && ct.fObjectsIconChanged)
            || ct.dvAspect!=ti.fe.dwAspect)
            {
            HGLOBAL     hMem=NULL;

            //Only pass non-NULL handle for icon aspects.
            if (DVASPECT_ICON==ct.dvAspect)
                hMem=ct.hMetaPict;

            m_pPG->m_fDirty=m_pTenantCur->SwitchOrUpdateAspect(hMem
                , FALSE);
            }

        //Now change types around.
        if ((CF_SELECTCONVERTTO & ct.dwFlags)
            && ct.clsid!=ct.clsidNew)
            {
            LPSTORAGE   pIStorage;

            /*
             * User selected convert, so:
             *  1.  Unload the object (back to passive state)
             *  2.  Call INOLE_DoConvert, which calls WriteClassStg,
             *      WriteFmtUserTypeStg, and SetConvertStg.
             *  3.  Reload the object and force an update.
             */

            //This should be the only close necessary.
            m_pTenantCur->StorageGet(&pIStorage);
            m_pTenantCur->Close(TRUE);

            hr=INOLE_DoConvert(pIStorage, ct.clsidNew);

            //Need to commit the new type and format
            pIStorage->Commit(STGC_DEFAULT);
            pIStorage->Release();

            if (SUCCEEDED(hr))
                {
                LPUNKNOWN   pObj;
                LPOLEOBJECT pIOleObject;

                //Reload and update.
                m_pTenantCur->Load(m_pIStorage, &ti);

                m_pTenantCur->ObjectGet(&pObj);
                pObj->QueryInterface(IID_IOleObject
                    , (PPVOID)&pIOleObject);
                pIOleObject->Update();
                pIOleObject->Release();
                pObj->Release();
                }

            m_pPG->m_fDirty=TRUE;
            }

        if (CF_SELECTACTIVATEAS & ct.dwFlags)
            {
            /*
             * User selected Activate As, so:
             *  1.  Add the TreatAs entry in the registry
             *      through CoTreatAsClass
             *  2.  Unload all objects of the old CLSID that you
             *      have loaded.
             *  3.  Reload objects as desired
             *  4.  Activate the current object.
             */

            hr=CoTreatAsClass(ct.clsid, ct.clsidNew);

            if (SUCCEEDED(hr))
                {
                PCTenant    pTenant;
                UINT        i;

                for (i=0; i < m_cTenants; i++)
                    {
                    if (TenantGet(i, &pTenant, FALSE))
                        {
                        pTenant->GetInfo(&ti);
                        pTenant->Close(FALSE);
                        pTenant->Load(m_pIStorage, &ti);
                        }
                    }

                fActivate=TRUE;
                }
            }

        //These two steps insure the object knows of the size.
        m_pTenantCur->SizeGet(&szl, FALSE);
        m_pTenantCur->SizeSet(&szl, FALSE, TRUE);

        m_pTenantCur->EnableRepaint(TRUE);
        m_pTenantCur->Repaint();

        if (fActivate)
            m_pTenantCur->Activate(OLEIVERB_SHOW, NULL);

        SetCursor(hCur);
        }

    CoTaskMemFree((void*)ct.lpszUserType);
    INOLE_MetafilePictIconFree(ct.hMetaPict);

    return TRUE;
    }

/*
 * CPages::FQueryLinksInPage
 *
 * Purpose:
 *  Pass through to current page to see if there are any linked
 *  objects.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPage::FQueryLinksInPage()
    {
    PCTenant    pTenant;
    UINT        i;
    BOOL        fRet=FALSE;

    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            fRet |= (pTenant->TypeGet()==TENANTTYPE_LINKEDOBJECT);
        }

    return fRet;
    }

/*
 * CPage::ScrolledWindow
 *
 * Purpose:
 *  Instructs the page to call CTenant::UpdateInPlaceObjectRects
 *  for the current tenant when the window is scrolled.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPage::ScrolledWindow(void)
    {
    UINT        i;
    PCTenant    pTenant;

    /*
     * Tell all tenants to update the position of in-place objects.
     * If you do not support inside-out, only the selected object
     * need be notified.
     */
    for (i=0; i < m_cTenants; i++)
        {
        if (!TenantGet(i, &pTenant, FALSE))
            continue;

        pTenant->UpdateInPlaceObjectRects(NULL, TRUE);
        }

    return;
    }

/*
 * CPage::SwitchActiveTenant
 *
 * Purpose:
 *  Changes the activation of in-place objects from the current
 *  one known to the page to the new one passed to this function.
 *  This is called from IOleInPlaceSite::OnUIDeactivate.
 *
 * Parameters:
 *  pTenant         PCTenant that is becoming active.
 *
 * Return Value:
 *  None
 */

void CPage::SwitchActiveTenant(PCTenant pTenant)
    {
    BOOL        fSelect;

    /*
     * If we're UI activating the selected tenant, don't
     * bother changing selection--just so activation.
     */
    fSelect=(pTenant!=m_pTenantCur);

    /*
     * The first time we UI Activate we're not switching
     * anything so avoid the whole sequence.
     */
    if (m_fFirstUIActivate)
        {
        m_fFirstUIActivate=FALSE;
        return;
        }

    if (NULL!=m_pTenantCur && fSelect)
        m_pTenantCur->Select(FALSE, TRUE);

    m_pTenantCur=pTenant;

    //Select the new tenant, but don't activate it again
    if (NULL!=m_pTenantCur)
        {
        UINT        i;

        if (fSelect)
            m_pTenantCur->Select(TRUE, FALSE);

        //Find the new tenant in our list and move to the top
        for (i=0; i < m_cTenants; i++)
            {
            PCTenant        pTenList;

            if (TenantGet(i, &pTenList, FALSE))
                {
                if (pTenList==m_pTenantCur)
                    {
                    HWND        hWndObj;

                    m_iTenantCur=0;

                    //Remove the tenant and add to the top again.
                    SendMessage(m_hWndTenantList, LB_DELETESTRING
                        , i, 0L);
                    SendMessage(m_hWndTenantList, LB_INSERTSTRING
                        , 0, (LONG)pTenant);

                    hWndObj=pTenant->ObjectWindow();

                    if (NULL!=hWndObj)
                        {
                        SetWindowPos(hWndObj, HWND_TOP, 0, 0, 0, 0
                            , SWP_NOMOVE | SWP_NOSIZE
                            | SWP_NOACTIVATE);
                        }
                    break;
                    }
                }
            }
        }

    return;
    }

//CHAPTER24MOD
/*
 * CPage::ToggleDesignMode
 * CPage::ToggleUIDead
 * CPage::ToggleHatchHandles
 *
 * Purpose:
 *  Loops through all the tenants and toggles a state.
 */

void CPage::ToggleDesignMode(BOOL fDesign)
    {
    PCTenant    pTenant;
    int         i;

    /*
     * We do this in reverse order such that when we reactivate
     * objects coming out of design mode we reactivate the
     * visible "top" tenant last, therefore it's in-place window
     * will be top of the z-order in the container and will overlap
     * other objects correctly.
     */
    for (i=(int)m_cTenants-1; i >=0; i--)
        {
        if (TenantGet(i, &pTenant, FALSE))
            pTenant->ToggleDesignMode(fDesign);
        }

    return;
    }

void CPage::ToggleUIDead(BOOL fUIDead)
    {
    PCTenant    pTenant;
    UINT        i;

    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            pTenant->ToggleUIDead(fUIDead);
        }

    return;
    }

void CPage::ToggleHatchHandles(BOOL fHatchHandles)
    {
    PCTenant    pTenant;
    UINT        i;

    for (i=0; i < m_cTenants; i++)
        {
        if (TenantGet(i, &pTenant, FALSE))
            pTenant->ToggleHatchHandles(fHatchHandles);
        }

    return;
    }

/*
 * CPage::FQueryEnableEvents
 *
 * Purpose:
 *  Returns whether to enable events for the current tenant.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE to enable the item, FALSE otherwise.
 */

BOOL CPage::FQueryEnableEvents(void)
    {
    DWORD   dwFlags;

    if (NULL==m_pTenantCur)
        return FALSE;

    dwFlags=m_pTenantCur->GetControlFlags();
    return 0!=(TENANTSTATE_EVENTS & dwFlags);
    }

/*
 * CPage::AssignEvents
 *
 * Purpose:
 *  Invokes a dialog in which the user can assign actions to
 *  control events.
 *
 * Parameters:
 *  hWndParent      HWND to use as the dialog parent.
 *
 * Return Value:
 *  None
 */

void CPage::AssignEvents(HWND hWndParent)
    {
    DialogBoxParam(m_pPG->m_hInst, MAKEINTRESOURCE(IDD_EVENTS)
        , hWndParent, EventsDlgProc
        , (LONG)m_pTenantCur->EventMap());

    return;
    }

/*
 * CPage::TryMnemonic
 *
 * Purpose:
 *  Loops through all the tenants checking each for a match
 *  with a keybord mnemonic.
 *
 * Parameters:
 *  pMsg            LPMSG containing the message to check.
 *
 * Return Value:
 *  BOOL            TRUE if the mnemonic was a match with any
 *                  tenant, FALSE otherwise.
 */

BOOL CPage::TryMnemonic(LPMSG pMsg)
    {
    BOOL    fRet=FALSE;
    UINT    i;

    for (i=0; i < m_cTenants; i++)
        {
        PCTenant    pTenant;

        if (TenantGet(i, &pTenant, FALSE))
            {
            fRet=pTenant->TryMnemonic(pMsg);

            if (fRet)
                break;
            }
        else
            break;
        }

    return fRet;
    }
//End CHAPTER24MOD

/*
 * CPage::TenantGet
 * (Protected)
 *
 * Purpose:
 *  Returns a tenant of a given index returning a BOOL so it's
 *  simple to use this function inside if statements.
 *
 * Parameters:
 *  iTenant         UINT tenant to retrieve 0 based.
 *  ppTenant        PCPage * in which to return the tenant
 *                  pointer
 *  fOpen           BOOL indicating if we should open this
 *                  tenant as well.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPage::TenantGet(UINT iTenant, PCTenant *ppTenant
    , BOOL fOpen)
    {
    if (NULL==ppTenant)
        return FALSE;

    if (LB_ERR!=SendMessage(m_hWndTenantList, LB_GETTEXT
        , iTenant, (LONG)ppTenant))
        {
        if (fOpen)
            (*ppTenant)->Open(m_pIStorage);

        return TRUE;
        }

    return FALSE;
    }

/*
 * CPage::TenantGetFromID
 * (Protected)
 *
 * Purpose:
 *  Finds a tenant pointer from an ID for use from
 *  IOleItemContainer::GetObject
 *
 * Parameters:
 *  dwID            DWORD identifier of the tenant to find.
 *  ppTenant        PCTenant * in which to return the tenant
 *                  pointer
 *  fOpen           BOOL indicating if we should open this tenant as
 *                  well.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPage::TenantGetFromID(DWORD dwID, PCTenant *ppTenant
    , BOOL fOpen)
    {
    UINT        i;
    PCTenant    pTenant;

    if (NULL==ppTenant)
        return FALSE;

    for (i=0; i < m_cTenants; i++)
        {
        if (!TenantGet(i, &pTenant, FALSE))
            continue;

        if (pTenant->GetID()==dwID)
            {
            if (fOpen)
                pTenant->Open(m_pIStorage);

            *ppTenant=pTenant;
            return TRUE;
            }
        }

    return FALSE;
    }

/*
 * CPage::TenantAdd
 * (Protected)
 *
 * Purpose:
 *  Creates a new tenant initialized to the given values.  The new
 *  tenants's storage is created if it does not already exist.  If
 *  fOpenStorage is set the the tenants's storage is opened and left
 *  opened.
 *
 * Parameters:
 *  iTenant         UINT Location at which to insert tenant; new
 *                  tenant is inserted after this position.  NOVALUE
 *                  for the end.
 *  dwID            DWORD ID for this tenant.
 *  ppNew           PCTenant * in which to store the new tenant.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPage::TenantAdd(UINT iTenant, DWORD dwID
    , PCTenant *ppNew)
    {
    PCTenant    pTenant;
    LRESULT     lr;

    if (NULL!=ppNew)
        *ppNew=NULL;

    pTenant=new CTenant(dwID, m_hWnd, m_pPG);

    if (NULL==pTenant)
        return FALSE;

    //The constructor doesn't AddRef, so we need to.
    pTenant->AddRef();

    //Now try to add to the listbox.
    lr=SendMessage(m_hWndTenantList, LB_INSERTSTRING, iTenant
        , (LONG)pTenant);

    if (lr < 0)
        {
        pTenant->Release();
        return FALSE;
        }

    *ppNew=pTenant;
    return TRUE;
    }

/*
 * CPage::TransferObjectCreate
 * (Protected)
 *
 * Purpose:
 *  Creates a DataTransferObject and stuff the current selection's
 *  data into it.
 *
 * Parameters:
 *  pptl            PPOINTL containing the pick point in device
 *                  units applicable only to drag-drop; since
 *                  drag-drop is inherently mouse oriented, we use
 *                  device units for the point.  Ignored if NULL.
 *
 * Return Value:
 *  LPDATAOBJECT    Pointer to the object created, NULL on failure
 */

LPDATAOBJECT CPage::TransferObjectCreate(PPOINTL pptl)
    {
    LPDATAOBJECT    pIDataObject;
    LPDATAOBJECT    pIDataT;
    PPATRONOBJECT   ppo;
    RECTL           rcl;
    LPUNKNOWN       pObj;
    FORMATETC       fe;
    STGMEDIUM       stm;
    HRESULT         hr;
    HGLOBAL         hMem;

    m_pTenantCur->ObjectGet(&pObj);

    hr=CoCreateInstance(CLSID_DataTransferObject, NULL
        , CLSCTX_INPROC_SERVER, IID_IDataObject
        , (PPVOID)&pIDataObject);

    if (FAILED(hr))
        return NULL;

    //Go get the data we should hold on to.
    hr=pObj->QueryInterface(IID_IDataObject, (PPVOID)&pIDataT);

    if (FAILED(hr))
        {
        pIDataObject->Release();
        pObj->Release();
        return NULL;
        }

    //Copy from known obj into transfer obj.  Ordering is important!

    //Generate placeable object structure
    stm.tymed=TYMED_HGLOBAL;
    stm.pUnkForRelease=NULL;
    stm.hGlobal=GlobalAlloc(GHND, sizeof(PATRONOBJECT));

    if (NULL==stm.hGlobal)
        {
        pIDataObject->Release();
        pObj->Release();
        return NULL;
        }

    ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);

    m_pTenantCur->SizeGet(&ppo->szl, FALSE);
    ppo->szl.cy=-ppo->szl.cy; //Negate to make absolute size

    m_pTenantCur->RectGet(&rcl, FALSE);
    ppo->ptl.x=rcl.left;
    ppo->ptl.y=rcl.top;

    if (NULL==pptl)
        {
        ppo->ptlPick.x=0;
        ppo->ptlPick.y=0;
        }
    else
        ppo->ptlPick=*pptl;

    m_pTenantCur->FormatEtcGet(&ppo->fe, FALSE);

    //If this is a linked object, just copy a presentation
    if (TENANTTYPE_LINKEDOBJECT==m_pTenantCur->TypeGet())
        m_pTenantCur->FormatEtcGet(&ppo->fe, TRUE);

    SETDefFormatEtc(fe, m_pPG->m_cf, TYMED_HGLOBAL);
    pIDataObject->SetData(&fe, &stm, TRUE);

    /*
     * Here now we have to include CFSTR_EMBEDDEDOBJECT and
     * CFSTR_OBJECTDESCRIPTOR when what we have selected is, in
     * fact, a compound document object.  We'll just ask the tenant
     * to set these in pIDataObject since it knows what the object.
     * If we copy embedded object data, make sure the PATRONOBJECT
     * structure has the right format in it as well.
     */
    m_pTenantCur->CopyEmbeddedObject(pIDataObject, &ppo->fe, pptl);
    hMem=stm.hGlobal;

    //Copy the actual presentation.
    m_pTenantCur->FormatEtcGet(&fe, TRUE);
    pIDataT->GetData(&fe, &stm);
    pIDataObject->SetData(&fe, &stm, TRUE);

    //Copy a link to this tenant if it's embedded
    m_pTenantCur->CopyLinkedObject(pIDataObject, &ppo->fe, pptl);
    GlobalUnlock(hMem); //ppo

    pIDataT->Release();
    pObj->Release();
    return pIDataObject;    //Caller now responsible
    }


PAGEMOUS.CPP

/*
 * PAGEMOUS.CPP
 * Patron Chapter 24
 *
 * Implementation of mouse-related member functions of CPage.
 * The remainder is in PAGE.CPP.  This separate file keeps this
 * grungy hit-testing/drawing code out of our way.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

//Lookups into the array using g_rgHTCode[x+y*3] in PAGEMOUS.CPP
#define YTOP            0
#define YMID            1
#define YBOT            2
#define XLEFT           0
#define XMID            1
#define XRIGHT          2

//Values to restrict sizing in CPage::OnMouseMove
#define SIZINGTOP       0x0001
#define SIZINGBOTTOM    0x0002
#define SIZINGLEFT      0x0004
#define SIZINGRIGHT     0x0008

//This array is for hit-testing lookups
static UINT g_rgHTCode[9]={HTTOPLEFT, HTTOP, HTTOPRIGHT
    , HTLEFT, HTCLIENT, HTRIGHT, HTBOTTOMLEFT, HTBOTTOM
    , HTBOTTOMRIGHT};

//This is for restricting tracking based on the hit-test
static UINT g_rguSizingFlags[9]={SIZINGTOP | SIZINGLEFT, SIZINGTOP
    , SIZINGTOP | SIZINGRIGHT, SIZINGLEFT, 0, SIZINGRIGHT
    , SIZINGBOTTOM | SIZINGLEFT, SIZINGBOTTOM
    , SIZINGBOTTOM | SIZINGRIGHT};

/*
 * CPage::OnRightDown
 *
 * Purpose:
 *  Called when the user clicks with the right button on this
 *  page.  If there is an object here, determined by the last
 *  hit-test code, the we'll make a popup-menu for it.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if the action changed the object.
 */

BOOL CPage::OnRightDown(UINT uKeys, UINT x, UINT y)
    {
    HMENU       hMenu;
    HMENU       hMenuRes;
    HINSTANCE   hInst;
    HWND        hWndFrame, hWndT;
    POINT       pt;
    UINT        i, cItems;

    //Select the tenant under the mouse, if there is one.
    if (!SelectTenantAtPoint(x, y))
        return FALSE;

    /*
     * Get the top-level window to which menu command will go.  This
     * will be whatever parent doesn't have a parent itself...
     */
    hWndT=GetParent(m_hWnd);

    while (NULL!=hWndT)
        {
        hWndFrame=hWndT;
        hWndT=GetParent(hWndT);
        }

    /*
     * Build a popup menu for this object with Cut, Copy, Delete,
     * and object verbs.
     */
    hInst=GETWINDOWINSTANCE(m_hWnd);    //Macro in BOOK1632.H
    hMenuRes=LoadMenu(hInst, MAKEINTRESOURCE(IDR_RIGHTPOPUPMENU));

    if (NULL==hMenuRes)
        return FALSE;

    hMenu=CreatePopupMenu();
    cItems=GetMenuItemCount(hMenuRes);

    for (i=0; i < cItems; i++)
        {
        TCHAR       szTemp[80];
        int         id, uFlags;

        GetMenuString(hMenuRes, i, szTemp, sizeof(szTemp)
            , MF_BYPOSITION);
        id=GetMenuItemID(hMenuRes, i);

        uFlags=(0==id) ? MF_SEPARATOR : MF_STRING | MF_ENABLED;
        AppendMenu(hMenu, uFlags, id, szTemp);
        }

    DestroyMenu(hMenuRes);

    //Munge the Object menu item
    m_pTenantCur->AddVerbMenu(hMenu, MENUPOS_OBJECTONPOPUP);

    //Enable or disable the Links item.
    i=FQueryLinksInPage() ? MF_ENABLED : MF_DISABLED | MF_GRAYED;
    EnableMenuItem(hMenu, IDM_EDITLINKS, i | MF_BYCOMMAND);

    //CHAPTER24MOD
    i=FQueryEnableEvents() ? MF_ENABLED : MF_DISABLED | MF_GRAYED;
    EnableMenuItem(hMenu, IDM_EDITOBJECTEVENTS, i | MF_BYCOMMAND);
    //End CHAPTER24MOD

    SETPOINT(pt, x, y);
    ClientToScreen(m_hWnd, &pt);

    TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON
        , pt.x, pt.y, 0, hWndFrame, NULL);

    DestroyMenu(hMenu);
    return FALSE;
    }

/*
 * CPage::SelectTenantAtPoint
 *
 * Purpose:
 *  Selects whatever tenant is at the point (x,y) if there is one,
 *  deselecting the previously selected tenant.
 *
 * Parameters:
 *  x, y            UINT coordinates of the mouse.
 *
 * Return Value:
 *  BOOL            TRUE if there is a tenant here, FALSE otherwise.
 */

BOOL CPage::SelectTenantAtPoint(UINT x, UINT y)
    {
    UINT            iTenant;
    PCTenant        pTenant;
    PCDocument      pDoc;

    iTenant=TenantFromPoint(x, y, &pTenant);

    //Make the document window active in any case
    pDoc=(PCDocument)SendMessage(GetParent(m_hWnd), DOCM_PDOCUMENT
        , 0, 0L);

    if (NULL!=pDoc)
        BringWindowToTop(pDoc->Window());

    if (NULL==pTenant)
        {
        //Deactivate any in-place object, if one exists.
        if (NULL!=m_pTenantCur)
            m_pTenantCur->DeactivateInPlaceObject(FALSE);

        return FALSE;
        }

    //The m_pTenantCur->Select(FALSE, TRUE) deactivates as well.

    //If this one is already current, we might be now sizing.
    if (pTenant==m_pTenantCur)
        return TRUE;

    //Deselect the current tenant
    if (NULL!=m_pTenantCur)
        //CHAPTER24MOD
        m_pTenantCur->Select(FALSE, !m_pPG->m_fDesignMode);
        //End CHAPTER24MOD

    //Move this tenant to the top of the list
    m_iTenantCur=0;

    SendMessage(m_hWndTenantList, LB_DELETESTRING, iTenant, 0L);
    SendMessage(m_hWndTenantList, LB_INSERTSTRING, 0
        , (LONG)pTenant);

    //Select and repaint the new tenant to show it up front
    m_pTenantCur=pTenant;

    m_pTenantCur->Repaint();
    //CHAPTER24MOD
    m_pTenantCur->Select(TRUE, !m_pPG->m_fDesignMode);
    //End CHAPTER24MOD

    return TRUE;
    }

/*
 * CPage::OnLeftDown
 *
 * Purpose:
 *  Called when the user clicks with the left button on this page.
 *  We find the object under that position that is visibly on top
 *  (always the first one under this location in the page list since
 *  we paint in reverse order) and select it.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if the action changed the object.
 */

BOOL CPage::OnLeftDown(UINT uKeys, UINT x, UINT y)
    {
    /*
     * If the mouse is in a position to start dragging,
     * start the timer as with sizing below.
     */
    if (HTCAPTION==m_uHTCode)
        {
        m_fDragPending=TRUE;

        //Save down point and start timer.
        m_ptDown.x=x;
        m_ptDown.y=y;

        m_uKeysDown=uKeys;

        m_fTimer=TRUE;
        SetTimer(m_hWnd, IDTIMER_DEBOUNCE, m_cDelay, NULL);
        return FALSE;
        }

    /*
     * If the mouse is in a position to start sizing, start
     * the debounce timer and note the condition.  The sizing
     * will start in OnTimer or OnMouseMove.  This will always
     * happen on the currently selected tenant, and m_uHTCode is
     * set in OnNCHitTest below.
     */
    if (HTNOWHERE!=m_uHTCode && HTCLIENT!=m_uHTCode)
        {
        m_fSizePending=TRUE;

        //Save down point and start timer.
        m_ptDown.x=x;
        m_ptDown.y=y;

        m_fTimer=TRUE;
        SetTimer(m_hWnd, IDTIMER_DEBOUNCE, m_cDelay, NULL);
        return FALSE;
        }

    SelectTenantAtPoint(x, y);
    return FALSE;
    }

/*
 * CPage::OnLeftUp
 *
 * Purpose:
 *  Called when the user clicks up with the left button on this
 *  page. We stop tracking on this message, if necessary, and
 *  resize the object.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if this action changed the object.
 */

BOOL CPage::OnLeftUp(UINT uKeys, UINT x, UINT y)
    {
    RECT    rc, rcT;

    if (m_fSizePending || m_fDragPending)
        {
        m_fSizePending=FALSE;
        m_fDragPending=FALSE;

        if (m_fTimer)
            {
            KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
            m_fTimer=FALSE;
            }

        return FALSE;
        }

    if (!m_fTracking)
        return FALSE;

    //Remove the dotted rectangle.
    RECTFROMRECTL(rc, m_rcl)
    DrawFocusRect(m_hDC, &rc);
    ReleaseDC(m_hWnd, m_hDC);

    ReleaseCapture();
    m_fTracking=FALSE;

    //If the original and new rects are the same, nothing happened.
    RECTFROMRECTL(rcT, m_rclOrg);

    if (EqualRect(&rc, &rcT))
        return FALSE;

    RECTFROMRECTL(rcT, m_rclOrg);
    InvalidateRect(m_hWnd, &rcT, TRUE);

    //Invalidate on the screen before accounting for scrolling
    InvalidateRect(m_hWnd, &rc, TRUE);

    //Factor in scrolling and tell the tenant where it now stands.
    OffsetRect(&rc, (int)m_pPG->m_xPos, (int)m_pPG->m_yPos);
    RECTLFROMRECT(m_rcl, rc);
    m_pTenantCur->RectSet(&m_rcl, TRUE, TRUE);

    UpdateWindow(m_hWnd);
    return TRUE;
    }

/*
 * CPage::OnLeftDoubleClick
 *
 * Purpose:
 *  Called when the user double-clicks with the left button on this
 *  page.  We find the object under that position that is visibly on
 *  top (always the first one under this location in the page list
 *  since we paint in reverse order) and activate it.
 *
 * Parameters:
 *  uKeys           UINT carrying the key state.
 *  x, y            UINT coordinates of the click in device units.
 *
 * Return Value:
 *  BOOL            Indicates if the action changed the object.
 */

BOOL CPage::OnLeftDoubleClick(UINT uKeys, UINT x, UINT y)
    {
    /*
     * The current tenant is the only one that can be activated, so
     * we just have to make sure the mouse is there.  For that we
     * can use the last hit-test code we saw since it's updated on
     * every mouse move.
     */

    if (HTNOWHERE!=m_uHTCode)
        {
        MSG     msg;
        DWORD   dw;

        //Include a message for in-place objects.
        msg.hwnd=NULL;
        msg.message=WM_LBUTTONDBLCLK;
        msg.wParam=(WPARAM)uKeys;
        msg.lParam=MAKELONG(x, y);
        msg.time=GetMessageTime();

        dw=GetMessagePos();
        SETPOINT(msg.pt, LOWORD(dw), HIWORD(dw));

        return m_pTenantCur->Activate(OLEIVERB_PRIMARY, &msg);
        }

    //If we've been waiting to add UI, now's the time to do all of it.
    if (m_pPG->m_fAddUI)
        {
        g_pFR->ShowUIAndTools(TRUE, TRUE);
        m_pPG->m_fAddUI=FALSE;
        }

    return FALSE;
    }

/*
 * CPage::OnMouseMove
 *
 * Purpose:
 *  Processes WM_MOUSEMOVE on a page so we can handle tracking
 *  resize of a tenant.
 *
 * Parameters:
 *  x, y            int device coordinates to check.
 *
 * Return Value:
 *  None
 */

void CPage::OnMouseMove(UINT uKeys, int x, int y)
    {
    RECT        rc, rcO, rcB;
    int         cxy;

    if (m_fSizePending || m_fDragPending)
        {
        int     dx, dy;

        dx=(x > m_ptDown.x) ? (x-m_ptDown.x) : (m_ptDown.x-x);
        dy=(y > m_ptDown.y) ? (y-m_ptDown.y) : (m_ptDown.y-y);

        /*
         * Has the mouse moved outside the debounce distance?  If
         * so, we can start sizing.  Note that this happens
         * regardless of the timer state.
         */
        if (dx > m_cxyDist || dy > m_cxyDist)
            {
            POINT       pt;
            BOOL        fSize=m_fSizePending;
            BOOL        fDrag=m_fDragPending;

            m_fSizePending=FALSE;
            m_fDragPending=FALSE;

            if (m_fTimer)
                {
                KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
                m_fTimer=FALSE;
                }

            if (fDrag)
                {
                //Set dirty flag if drag & drop changed things.
                m_pPG->m_fDirty |= DragDrop(m_uKeysDown, x, y);
                return;
                }

            if (fSize)
                StartSizeTracking();

            /*
             * Since we might have moved out of the sizing handle
             * in order to start the operation, we need to set the
             * m_uSizingFlags field based on the original down point
             * for subsequent mouse moves to function properly.
             * Note that OnNCHitTest expects screen coordinates.
             */
            SETPOINT(pt, m_ptDown.x, m_ptDown.y);
            ClientToScreen(m_hWnd, &pt);
            OnNCHitTest(pt.x, pt.y);
            OnSetCursor(m_uHTCode);
            return;
            }
        }

    if (!m_fTracking)
        return;

    //Get rid of the old rectangle.
    RECTFROMRECTL(rc, m_rcl)
    DrawFocusRect(m_hDC, &rc);

    /*
     * Calculate the new.  The flags in m_uSizingFlags tell us what
     * to change.  We limit the object by the page margins and a
     * minimum size of 3*CXYHANDLE in either dimension.
     */
    cxy=3*CXYHANDLE;

    RECTFROMRECTL(rcO, m_rclOrg);
    RECTFROMRECTL(rcB, m_rclBounds);

    if (m_uSizingFlags & SIZINGTOP)
        {
        if (y >= rcO.bottom-cxy)
            y=rcO.bottom-cxy;

        if (y <= rcB.top)           //Limit to top of page.
            y=rcB.top;

        m_rcl.top=y;
        }

    if (m_uSizingFlags & SIZINGBOTTOM)
        {
        if (y <= rcO.top+cxy)
            y=rcO.top+cxy;

        if (y >= rcB.bottom)         //Limit to bottom of page.
            y=rcB.bottom;

        m_rcl.bottom=y;
        }

    if (m_uSizingFlags & SIZINGLEFT)
        {
        if (x >= rcO.right-cxy)
            x=rcO.right-cxy;

        if (x <= rcB.left)           //Limit to left of page.
            x=rcB.left;

        m_rcl.left=x;
        }

    if (m_uSizingFlags & SIZINGRIGHT)
        {
        if (x <= rcO.left+cxy)
            x=rcO.left+cxy;

        if (x >= rcB.right)          //Limit to right of page.
            x=rcB.right;

        m_rcl.right=x;
        }

    //Draw the new
    RECTFROMRECTL(rc, m_rcl)
    DrawFocusRect(m_hDC, &rc);

    return;
    }

/*
 * CPage::OnTimer
 *
 * Purpose:
 *  Processes WM_TIMER messages to a page used to perform mouse
 *  debouncing.
 *
 * Parameters:
 *  uID             UINT timer ID.
 *
 * Return Value:
 *  None
 */

void CPage::OnTimer(UINT uID)
    {
    if (m_fSizePending || m_fDragPending)
        {
        BOOL        fSize=m_fSizePending;
        BOOL        fDrag=m_fDragPending;

        /*
         * Having this function called means the delay requirement
         * is satisfied.  Start tracking for sizing or dragging.
         */

        m_fSizePending=FALSE;
        m_fDragPending=FALSE;

        KillTimer(m_hWnd, IDTIMER_DEBOUNCE);
        m_fTimer=FALSE;

        if (fDrag)
            {
            POINT       pt;

            GetCursorPos(&pt);
            m_pPG->m_fDirty |= DragDrop(m_uKeysDown
                , m_ptDown.x, m_ptDown.y);
            return;
            }

        if (fSize)
            StartSizeTracking();
        }

    return;
    }

/*
 * CPage::StartSizeTracking
 *
 * Purpose:
 *  Begins sizing of a tenant when mouse debounce conditions are
 *  met.
 *
 * Parameters:
 *  uID             UINT timer ID.
 *
 * Return Value:
 *  None
 */

void CPage::StartSizeTracking(void)
    {
    RECT        rc;

    m_pTenantCur->RectGet(&m_rcl, TRUE);
    SetCapture(m_hWnd);
    m_fTracking=TRUE;

    m_hDC=GetDC(m_hWnd);

    //Place the rectangle exactly where it is on the screen.
    RECTFROMRECTL(rc, m_rcl)
    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);
    RECTLFROMRECT(m_rcl, rc);
    m_rclOrg=m_rcl;

    DrawFocusRect(m_hDC, &rc);

    m_pPG->CalcBoundingRect(&rc, TRUE);
    RECTLFROMRECT(m_rclBounds, rc);
    return;
    }

/*
 * CPage::OnNCHitTest
 *
 * Purpose:
 *  Processes WM_NCHITTEST on a page so we can check for hits on the
 *  handles of the selected object for resizing.  We only save
 *  information for ourselves and do not interfere with normal
 *  hit-testing.
 *
 * Parameters:
 *  x, y            UINT device coordinates to check.
 *
 * Return Value:
 *  None
 */

void CPage::OnNCHitTest(UINT x, UINT y)
    {
    RECT        rc;
    RECTL       rcl;
    int         iMid1, iMid2;
    int         xHit, yHit;
    POINT       pt;
    int         x0, y0;

    /*
     * Ignore this message if it occurs during tracking to adjust
     * for the behavior of oddball mouse drivers.
     */
    if (m_fSizePending || m_fTracking)
        return;

    //Default: don't start sizing on a click, don't hit an object.
    m_uSizingFlags=0;
    m_uHTCode=HTNOWHERE;

    if (NULL==m_pTenantCur)
        return;

    //Convert device points to our coordinates
    m_pTenantCur->RectGet(&rcl, FALSE);
    RECTFROMRECTL(rc, rcl);
    RectConvertMappings(&rc, NULL, TRUE);
    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

    SETPOINT(pt, x, y);
    ScreenToClient(m_hWnd, &pt);
    x0=pt.x;
    y0=pt.y;

    if (x0 < rc.left || x0 > rc.right)
        return;

    if (y0 < rc.top || y0 > rc.bottom)
        return;

    //It's at least in the object.
    m_uHTCode=HTCLIENT;

    //Check for hits in horizontal regions
    xHit=NOVALUE;
    iMid1=rc.left+((rc.right-rc.left-CXYHANDLE) >> 1);
    iMid2=rc.left+((rc.right-rc.left+CXYHANDLE) >> 1);

    if (x0 >= rc.left && x0 <= rc.left+CXYHANDLE)
        xHit=XLEFT;
    else if (x0 >= iMid1 && x0 <= iMid2)
        xHit=XMID;
    else if (x0 >= rc.right-CXYHANDLE && x0 <= rc.right)
        xHit=XRIGHT;

    //Don't exit yet if we didn't hit a handle--might hit a y edge.

    //Check for hits in vertical regions
    yHit=NOVALUE;
    iMid1=rc.top+((rc.bottom-rc.top-CXYHANDLE) >> 1);
    iMid2=rc.top+((rc.bottom-rc.top+CXYHANDLE) >> 1);

    if (y0 >= rc.top && y0 <= rc.top+CXYHANDLE)
        yHit=YTOP;
    else if (y0 >= iMid1 && y0 <= iMid2)
        yHit=YMID;
    else if (y0 >= rc.bottom-CXYHANDLE && y0 <= rc.bottom)
        yHit=YBOT;

    /*
     * If we hit any edge, but didn't hit a handle, then one of xHit
     * and yHit will be NOVALUE and the other something else.  When
     * we hit an edge on the 'something else' then we're on a drag
     * point.
     */

    if ((NOVALUE==xHit && NOVALUE==yHit)
        || (XMID==xHit && YMID==yHit)
        || (NOVALUE==xHit && YMID==yHit)
        || (XMID==xHit && NOVALUE==yHit))
        return;

    if ((NOVALUE==xHit && (YTOP==yHit || YBOT==yHit))
        || ((XLEFT==xHit || XRIGHT==xHit) && NOVALUE==yHit))
        {
        m_uHTCode=HTCAPTION;
        return;
        }

    //We hit a handle, so save our HT code
    m_uSizingFlags=g_rguSizingFlags[xHit+(yHit*3)];
    m_uHTCode=g_rgHTCode[xHit+(yHit*3)];
    return;
    }

/*
 * CPage::SetCursor
 *
 * Purpose:
 *  Processes WM_SETCURSOR using the code from OnNCHitTest.
 *
 * Parameters:
 *  x, y            UINT device coordinates to check.
 *
 * Return Value:
 *  LRESULT         HT* code for Windows.
 */

BOOL CPage::OnSetCursor(UINT uHTCode)
    {
    HCURSOR     hCur;
    UINT        iCur;

    /*
     * We really just ignore uHTCode and use the one we saved
     * in OnNCHitTest.
     */

    switch (m_uHTCode)
        {
        case HTTOP:
        case HTBOTTOM:
            iCur=IDC_VARROWS;
            break;

        case HTLEFT:
        case HTRIGHT:
            iCur=IDC_HARROWS;
            break;

        case HTTOPLEFT:
        case HTBOTTOMRIGHT:
            iCur=IDC_NWSEARROWS;
            break;

        case HTTOPRIGHT:
        case HTBOTTOMLEFT:
            iCur=IDC_NESWARROWS;
            break;

        case HTCAPTION:
            iCur=IDC_SMALLARROWS;
            break;

        default:
            return FALSE;
        }

    hCur=UICursorLoad(iCur);
    SetCursor(hCur);

    return TRUE;
    }

/*
 * CPage::TenantFromPoint
 * (Protected)
 *
 * Purpose:
 *  Finds the tenant under the given device coordinates on this
 *  page.
 *
 * Parmeters:
 *  x, y            UINT coordinates.
 *  ppTenant        PCTenant * in which to return the pointer.
 *
 * Return Value:
 *  UINT            Index of the matched tenant, NOVALUE if not
 *                  found.
 */

UINT CPage::TenantFromPoint(UINT x, UINT y, PCTenant *ppTenant)
    {
    PCTenant    pTenant;
    RECTL       rcl;
    UINT        i;
    int         x0, y0;

    x0=x+m_pPG->m_xPos;
    y0=y+m_pPG->m_yPos;

    for (i=0; i < m_cTenants; i++)
        {
        if (!TenantGet(i, &pTenant, FALSE))
            continue;

        pTenant->RectGet(&rcl, TRUE);

        //Essentially Perform PointInRECTL
        if (x0 >= rcl.left && x0 <= rcl.right)
            {
            if (y0 <=rcl.bottom && y0 >=rcl.top)
                {
                *ppTenant=pTenant;
                return i;
                }
            }
        }

    *ppTenant=NULL;
    return NOVALUE;
    }

/*
 * CPage::DragDrop
 *
 * Purpose:
 *  Performs drag-drop operations from the page window
 *
 * Parmeters:
 *  uKeys           UINT state of the keyboard
 *  x, y            UINT mouse coordinates of the starting click.
 *
 * Return Value:
 *  BOOL            TRUE if we modified the page, FALSE otherwise.
 */

BOOL CPage::DragDrop(UINT uKeys, UINT x, UINT y)
    {
    LPDROPSOURCE    pIDropSource;
    LPDATAOBJECT    pIDataObject;
    HRESULT         hr;
    DWORD           dwEffect;
    POINTL          ptl;
    SIZEL           szl;
    RECTL           rcl;
    RECT            rc, rcT;

    pIDropSource=new CDropSource();

    if (NULL==pIDropSource)
        return FALSE;

    pIDropSource->AddRef();
    m_pPG->m_fDragSource=TRUE;

    /*
     * Store a pick point with the data indicating the offset from
     * the upper left of the rectangle where we grabbed it.  This is
     * so the UI feedback in IDropTarget lines up with this tenant.
     */

    m_pTenantCur->RectGet(&rcl, TRUE);
    ptl.x=x+m_pPG->m_xPos-rcl.left;
    ptl.y=y+m_pPG->m_yPos-rcl.top;
    pIDataObject=TransferObjectCreate(&ptl);

    if (NULL==pIDataObject)
        {
        pIDropSource->Release();
        return FALSE;
        }

    m_pPG->m_fMoveInPage=FALSE;

    dwEffect=DROPEFFECT_COPY | DROPEFFECT_MOVE;
    hr=DoDragDrop(pIDataObject, pIDropSource
        , DROPEFFECT_COPY | DROPEFFECT_MOVE, &dwEffect);

    pIDataObject->Release();
    pIDropSource->Release();

    m_pPG->m_fDragSource=FALSE;

    //No drop-no action.
    if (DRAGDROP_S_DROP!=GetScode(hr) || DROPEFFECT_NONE==dwEffect)
        return FALSE;

    /*
     * If m_pPG->m_fMoveInPage is set, then we just change the
     * coordinates on m_pTenantCur and we're done.
     */
    if (m_pPG->m_fMoveInPage)
        {
        m_pTenantCur->Invalidate();

        /*
         * Clip to page boundaries.  We know that ptDrop has to be
         * in the page somewhere or we would not have dropped
         * (effect was NONE).  So first make sure that ptDrop is
         * within 3*CXYHANDLE of the right or bottom, and if so,
         * pull it out to 3*CXYHANDLE.  Then we can just clip the
         * size to the page rectangle and we'll always be sure to
         * have at least a sizeable object.
         */
        m_pTenantCur->SizeGet(&szl, TRUE);
        SetRect(&rc, (int)m_pPG->m_ptDrop.x, (int)m_pPG->m_ptDrop.y
            , 0, 0);
        RectConvertMappings(&rc, NULL, TRUE);

        m_pPG->CalcBoundingRect(&rcT, FALSE);
        OffsetRect(&rcT, (int)m_pPG->m_xPos, (int)m_pPG->m_yPos);

        if (rc.left >= rcT.right-3*CXYHANDLE)
            rc.left=rcT.right-3*CXYHANDLE;

        if (rc.top >= rcT.bottom-3*CXYHANDLE)
            rc.top=rcT.bottom-3*CXYHANDLE;

        rc.right=rc.left+(int)szl.cx;
        rc.bottom=rc.top+(int)szl.cy;
        IntersectRect(&rc, &rc, &rcT);

        RECTLFROMRECT(rcl, rc);

        m_pTenantCur->RectSet(&rcl, TRUE, FALSE);
        m_pTenantCur->Repaint();
        return TRUE;
        }

    /*
     * Otherwise we may have to delete the old tenant if the effect
     * was move.  This will not happen in the move in page case.
     */

    if (DROPEFFECT_MOVE==dwEffect)
        {
        TenantDestroy();
        return TRUE;
        }

    //Copy is a clean operation
    return FALSE;
    }


PAGES.CPP

/*
 * PAGES.CPP
 * Patron Chapter 24
 *
 * Implementation of the CPages class.  See PAGEWIN.CPP and PRINT.CPP
 * for additional member functions.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CPages:CPages
 * CPages::~CPages
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application we're in.
 *  cf              UINT application clipboard format.
 */

CPages::CPages(HINSTANCE hInst, UINT cf)
    : CWindow(hInst)
    {
    m_pPageCur=NULL;
    m_iPageCur=NOVALUE;  //Pages are 0 indexed, this is one before
    m_cPages=0;
    m_hWndPageList=NULL;
    m_hFont=NULL;
    m_fSystemFont=FALSE;

    //Initialize to 8.5*11 inch with .25 inch margins as a default.
    m_cx=(LOMETRIC_PER_INCH*17)/2;
    m_cy=LOMETRIC_PER_INCH*11;

    m_xMarginLeft=LOMETRIC_PER_INCH/4;
    m_xMarginRight=LOMETRIC_PER_INCH/4;
    m_yMarginTop=LOMETRIC_PER_INCH/4;
    m_yMarginBottom=LOMETRIC_PER_INCH/4;

    m_xPos=0L;
    m_yPos=0L;

    m_dwIDNext=0;
    m_pIStorage=NULL;

    m_fDirty=FALSE;
    m_cf=cf;

    m_fDragSource=FALSE;
    m_fMoveInPage=FALSE;
    m_fLinkAllowed=FALSE;

    m_fDragRectShown=FALSE;

    m_uScrollInset=GetProfileInt(TEXT("windows")
        , TEXT("DragScrollInset"), DD_DEFSCROLLINSET);

    m_uScrollDelay=GetProfileInt(TEXT("windows")
        , TEXT("DragScrollDelay"), DD_DEFSCROLLDELAY);

    m_uHScrollCode=0;
    m_uVScrollCode=0;
    m_fShowTypes=FALSE;
    m_pmkFile=NULL;

    m_fAddUI=FALSE;
    //CHAPTER24MOD
    m_fDesignMode=FALSE;
    m_fUIDead=FALSE;
    m_fHatchHandles=TRUE;
    m_pIFont=NULL;
    //End CHAPTER24MOD
    return;
    }

CPages::~CPages(void)
    {
    //Ensure memory cleaned up in list; do final IStorage::Release
    StorageSet(NULL, FALSE, FALSE);

    ReleaseInterface(m_pmkFile);

    //CHAPTER24MOD
    ReleaseInterface(m_pIFont);
    //End CHAPTER24MOD

    if (NULL!=m_hFont && !m_fSystemFont)
        DeleteObject(m_hFont);

    if (NULL!=m_hWndPageList)
        DestroyWindow(m_hWndPageList);

    //m_hWnd destroyed with the document.
    return;
    }

/*
 * CPages::FIsDirty
 *
 * Purpose:
 *  Tells the caller (document) if anything's happened to dirty us.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPages::FIsDirty(void)
    {
    return m_fDirty;
    }

/*
 * CPages::Init
 *
 * Purpose:
 *  Instantiates a pages window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags.
 *                  Should contain WS_CHILD | WS_VISIBLE in
 *                  typical circumstances.
 *  uID             UINT ID to associate with this window
 *  pv              LPVOID unused for now.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPages::Init(HWND hWndParent, LPRECT pRect, DWORD dwStyle
    , UINT uID, LPVOID pv)
    {
    int         cy;
    //CHAPTER24MOD
    FONTDESC    fd={sizeof(FONTDESC), OLETEXT("Arial")
                    , FONTSIZE(14), FW_NORMAL, DEFAULT_CHARSET
                    , FALSE, FALSE, FALSE};
    //End CHAPTER24MOD

    m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY, SZCLASSPAGES
        , SZCLASSPAGES, dwStyle, pRect->left, pRect->top
        , pRect->right-pRect->left, pRect->bottom-pRect->top
        , hWndParent, (HMENU)uID, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    /*
     * Create the hidden listbox we'll use to track pages.  We give
     * it the owner-draw style so we can just store pointers in it.
     */
    m_hWndPageList=CreateWindow(TEXT("listbox"), TEXT("Page List")
        , WS_POPUP | LBS_OWNERDRAWFIXED, 0, 0, 100, 100
        , HWND_DESKTOP, NULL, m_hInst, NULL);

    if (NULL==m_hWndPageList)
        return FALSE;

    cy=MulDiv(-14, LOMETRIC_PER_INCH, 72);
    m_hFont=CreateFont(cy, 0, 0, 0, FW_NORMAL, FALSE, FALSE, FALSE
        , ANSI_CHARSET, OUT_TT_PRECIS, CLIP_TT_ALWAYS, PROOF_QUALITY
        , VARIABLE_PITCH | FF_SWISS, TEXT("Arial"));

    if (NULL==m_hFont)
        {
        m_hFont=(HFONT)GetStockObject(ANSI_VAR_FONT);
        m_fSystemFont=TRUE;
        }

    //CHAPTER24MOD
    //Need a standard font object for ambient properties
    OleCreateFontIndirect(&fd, IID_IFontDisp, (void **)&m_pIFont);
    //End CHAPTER24MOD

    return TRUE;
    }

/*
 * CPages::StorageSet
 *
 * Purpose:
 *  Provides the document's IStorage to the pages for its own use.
 *  If this is a new storage, we initalize it with streams that we
 *  want to always exists.  If this is an open, then we create
 *  our page list from the PageList string we wrote before.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE to the new or opened storage.  If
 *                  NULL, we just clean up and exit.
 *  fChange         BOOL indicating is this was a Save As operation,
 *                  meaning that we have the structure already and
 *                  just need to change the value of m_pIStorage.
 *  fInitNew        BOOL indicating if this is a new storage or one
 *                  opened from a previous save.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::StorageSet(LPSTORAGE pIStorage, BOOL fChange
    , BOOL fInitNew)
    {
    DWORD           dwMode=STGM_DIRECT | STGM_READWRITE
                        | STGM_SHARE_EXCLUSIVE;
    HRESULT         hr;
    PCPage          pPage;
    BOOL            fRet=FALSE;
    ULONG           cbRead;
    PAGELIST        pgList;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    LPDWORD         pdwID;
    UINT            i;

    //If we're changing saved roots, simply open current page again
    if (fChange)
        {
        if (NULL==pIStorage)
            return FALSE;

        m_pIStorage->Release();
        m_pIStorage=pIStorage;
        m_pIStorage->AddRef();

        PageGet(m_iPageCur, &m_pPageCur, TRUE);
        m_fDirty=FALSE;
        return TRUE;
        }

    if (NULL!=m_hWndPageList)
        {
        //On new or open, clean out whatever it is we have.
        for (i=0; i < m_cPages; i++)
            {
            if (PageGet(i, &pPage, FALSE))
                pPage->Release();
            }

        SendMessage(m_hWndPageList, LB_RESETCONTENT, 0, 0L);
        }

    if (NULL!=m_pIStorage)
        m_pIStorage->Release();

    m_pIStorage=NULL;

    //If we're just cleaning up, then we're done.
    if (NULL==pIStorage)
        return TRUE;

    m_pIStorage=pIStorage;
    m_pIStorage->AddRef();

    //If this is a new storage, create the streams we require
    if (fInitNew)
        {
        //Page list header.
        hr=m_pIStorage->CreateStream(SZSTREAMPAGELIST, dwMode
            | STGM_CREATE, 0, 0, &pIStream);

        if (FAILED(hr))
            return FALSE;

        pIStream->Release();

        //Device Configuration
        hr=m_pIStorage->CreateStream(SZSTREAMDEVICECONFIG, dwMode
            | STGM_CREATE, 0, 0, &pIStream);

        if (FAILED(hr))
            return FALSE;

        pIStream->Release();
        return TRUE;
        }

    /*
     * We're opening an existing file:
     *  1)  Configure for the device we're on
     *  2)  Read the Page List and create page entries for each.
     */

    ConfigureForDevice();

    //Read the page list.
    hr=m_pIStorage->OpenStream(SZSTREAMPAGELIST, NULL, dwMode, 0
        , &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pIStream->Read(&pgList, sizeof(PAGELIST), &cbRead);
        m_cPages  =(UINT)pgList.cPages;
        m_iPageCur=(UINT)pgList.iPageCur;
        m_dwIDNext=pgList.dwIDNext;

        fRet=TRUE;
        cbRead=pgList.cPages*sizeof(DWORD);

        if (0!=cbRead)
            {
            pdwID=(LPDWORD)pIMalloc->Alloc(cbRead);

            if (NULL!=pdwID)
                {
                pIStream->Read(pdwID, cbRead, &cbRead);

                for (i=0; i < m_cPages; i++)
                    fRet &=PageAdd(NOVALUE, *(pdwID+i), FALSE);

                pIMalloc->Free(pdwID);
                }
            }

        pIMalloc->Release();
        }

    pIStream->Release();

    if (!fRet)
        return FALSE;

    PageGet(m_iPageCur, &m_pPageCur, TRUE);
    m_fDirty=FALSE;

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);

    return TRUE;
    }

/*
 * CPages::StorageUpdate
 *
 * Purpose:
 *  Insures that all pages are committed before a root save.
 *
 * Parameters:
 *  fCloseAll       BOOL directing us to close all open storages
 *                  and streams.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::StorageUpdate(BOOL fCloseAll)
    {
    PCPage          pPage;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    LPDWORD         pdwID;
    ULONG           cb;
    HRESULT         hr;
    PAGELIST        pgList;
    BOOL            fRet=FALSE;
    UINT            i;

    //We only need to close the current page--nothing else is open.
    if (NULL!=m_pPageCur)
        {
        m_pPageCur->Update();

        if (fCloseAll)
            m_pPageCur->Close(FALSE);
        }

    //We don't hold anything else open, so write the page list.
    hr=m_pIStorage->OpenStream(SZSTREAMPAGELIST, NULL, STGM_DIRECT
        | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    if (SUCCEEDED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        {
        pgList.cPages=m_cPages;
        pgList.iPageCur=m_iPageCur;
        pgList.dwIDNext=m_dwIDNext;

        pIStream->Write(&pgList, sizeof(PAGELIST), &cb);

        cb=m_cPages*sizeof(DWORD);
        pdwID=(LPDWORD)pIMalloc->Alloc(cb);

        if (NULL!=pdwID)
            {
            for (i=0; i < m_cPages; i++)
                {
                PageGet(i, &pPage, FALSE);
                *(pdwID+i)=pPage->GetID();
                }

            pIStream->Write(pdwID, cb, &cb);
            pIMalloc->Free(pdwID);
            fRet=TRUE;
            }
        pIMalloc->Release();
        }

    pIStream->Release();

    //Clean up the dirty flag when we do an update.
    m_fDirty=!fRet;
    return fRet;
    }

/*
 * CPages::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Pages window in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  None
 */

void CPages::RectGet(LPRECT pRect)
    {
    RECT        rc;
    POINT       pt;

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_hWnd, &rc);
    SETPOINT(pt, rc.left, rc.top);
    ScreenToClient(GetParent(m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return;
    }

/*
 * CPages::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Pages window which sizes to fit.
 *  Coordinates are given in parent terms.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPages::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    if (NULL==pRect)
        return;

    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, pRect->left, pRect->top
        , (UINT)cx, (UINT)cy, SWP_NOZORDER);

    UpdateScrollRanges();
    return;
    }

/*
 * CPages::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the pages window in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  None
 */

void CPages::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return;
    }

/*
 * CPages::SizeSet
 *
 * Purpose:
 *  Sets a new size in parent coordinates for the Pages window.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  None
 */

void CPages::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT        cx, cy;

    if (NULL==pRect)
        return;

    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetWindowPos(m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
        , SWP_NOMOVE | SWP_NOZORDER);

    UpdateScrollRanges();
    return;
    }

/*
 * CPages::ActivePage
 *
 * Purpose:
 *  Returns a CPage pointer to the current page.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCPage          Pointer to the current page.
 */

PCPage CPages::ActivePage(void)
    {
    PCPage      pPage;
    BOOL        fRet;

    fRet=PageGet(m_iPageCur, &pPage, FALSE);
    return fRet ? pPage : NULL;
    }

/*
 * CPages::PageInsert
 *
 * Purpose:
 *  Creates a new page immediately after the current page.  If
 *  there are no pages then this creates page 1.
 *
 * Parameters:
 *  uReserved       UINT unused
 *
 * Return Value:
 *  UINT            Index of the new page, 0 on failure.
 */

UINT CPages::PageInsert(UINT uReserved)
    {
    if (0!=m_cPages && NULL!=m_pPageCur)
        {
        //Close the current page, committing changes.
        m_pPageCur->Close(TRUE);
        }

    //Create and open the new page.
    if (!PageAdd(m_iPageCur, m_dwIDNext, TRUE))
        return 0;

    m_dwIDNext++;
    m_iPageCur++;
    m_cPages++;

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);

    PageGet(m_iPageCur, &m_pPageCur, FALSE);
    return m_iPageCur;
    }

/*
 * CPages::PageDelete
 *
 * Removes the current page from the page list.
 *
 * Parameters:
 *  uReserved       UINT unused
 *
 * Return Value:
 *  UINT            Index to the now current page from the page
 *                  list, NOVALUE on error.
 */

UINT CPages::PageDelete(UINT uReserved)
    {
    PCPage      pPage;

    if (!PageGet(m_iPageCur, &pPage, FALSE))
        return NOVALUE;

    //Delete the page in both the storage and in memory.
    SendMessage(m_hWndPageList, LB_DELETESTRING, m_iPageCur, 0L);

    m_pPageCur->Destroy(m_pIStorage);

    m_pPageCur->Release();   //Does final pPage->Close
    m_pPageCur=NULL;

    /*
     * If this is the last page then the current is one less.  If
     * it's the only page the current is zero.  Otherwise the
     * current is the next page.
     */

    if (m_iPageCur==m_cPages-1)   //Covers last or only page.
        m_iPageCur--;

    m_cPages--;

    //Insure the new visible page is open.
    if (0!=m_cPages)
        {
        PageGet(m_iPageCur, &m_pPageCur, TRUE);
        InvalidateRect(m_hWnd, NULL, FALSE);
        }
    else
        InvalidateRect(m_hWnd, NULL, TRUE);

    UpdateWindow(m_hWnd);
    return m_iPageCur;
    }

/*
 * CPages::CurPageGet
 *
 * Purpose:
 *  Retrieves the index of the current page we're viewing.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Index of the current page.
 */

UINT CPages::CurPageGet(void)
    {
    return m_iPageCur;
    }

/*
 * CPages::CurPageSet
 *
 * Purpose:
 *  Sets the index of the current page to view.
 *
 * Parameters:
 *  iPage           UINT index of the page to view. 0 means first
 *                  page, NOVALUE means last page.
 *
 * Return Value:
 *  UINT            Index of the previous current page, NOVALUE on
 *                  error.
 */

UINT CPages::CurPageSet(UINT iPage)
    {
    UINT    iPageNew;
    UINT    iPagePrev=m_iPageCur;
    PCPage  pPage;

    switch (iPage)
        {
        case 0:
            iPageNew=0;
            break;

        case NOVALUE:
            iPageNew=m_cPages-1;
            break;

        default:
            if (iPage >= m_cPages)
                iPage=0;

            iPageNew=iPage;
            break;
        }

    //No reason to switch to the same page.
    if (iPagePrev==iPageNew)
        return iPage;

    //Close the old page committing changes.
    if (!PageGet(iPagePrev, &pPage, FALSE))
        return NOVALUE;

    pPage->Close(TRUE);

    m_iPageCur=iPageNew;

    //Open the new page.
    PageGet(m_iPageCur, &m_pPageCur, TRUE);

    InvalidateRect(m_hWnd, NULL, FALSE);
    UpdateWindow(m_hWnd);
    return iPagePrev;
    }

/*
 * CPages::NumPagesGet
 *
 * Purpose:
 *  Returns the number of pages this object current contains.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of pages we contain.
 */

UINT CPages::NumPagesGet(void)
    {
    return m_cPages;
    }

/*
 * CPages::TenantCreate
 * CPages::TenantDestroy
 *
 * Purpose:
 *  Pass-throughs for CPage members on the current page.
 */

BOOL CPages::TenantCreate(TENANTTYPE tType, LPVOID pv
    , LPFORMATETC pFE, PPATRONOBJECT ppo, DWORD dwData)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantCreate(tType, pv, pFE, ppo, dwData);
    m_fDirty |= fRet;
    return fRet;
    }

BOOL CPages::TenantDestroy(void)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantDestroy();
    m_fDirty |= fRet;
    return fRet;
    }

/*
 * CPages::TenantClip
 *
 * Purpose:
 *  Copies or cuts the currently selected tenant to the clipoard.
 *
 * Parameters:
 *  fCut            BOOL TRUE to cut the object, FALSE to copy.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::TenantClip(BOOL fCut)
    {
    BOOL    fRet;

    if (NULL==m_pPageCur)
        return FALSE;

    fRet=m_pPageCur->TenantClip(fCut);
    m_fDirty |= (fRet && fCut);
    return fRet;
    }

/*
 * CPages::FQueryObjectSelected
 *
 * Purpose:
 *  Returns whether or not there is an object selected on this
 *  page for Cut, Copy, Delete functions.
 *
 * Parameters:
 *  hMenu           HMENU on which object related items live.
 *
 * Return Value:
 *  BOOL            TRUE if we have an object, FALSE otherwise.
 */

BOOL CPages::FQueryObjectSelected(HMENU hMenu)
    {
    if (NULL==m_pPageCur)
        return FALSE;

    return m_pPageCur->FQueryObjectSelected(hMenu);
    }

/*
 * CPages::ActivateObject
 *
 * Purpose:
 *  Executes a verb on the currently selected object.
 *
 * Parameters:
 *  iVerb           LONG of the selected verb.
 *  pMSG            LPMSG to whatever invoked the verb.
 *
 * Return Value:
 *  None
 */

void CPages::ActivateObject(LONG iVerb, LPMSG pMSG)
    {
    if (NULL==m_pPageCur)
        return;

    m_pPageCur->ActivateObject(iVerb, pMSG);
    return;
    }

//CHAPTER24MOD
/*
 * CPages::ToggleDesignMode
 * CPages::ToggleUIDead
 * CPages::ToggleHatchHandles
 *
 * Purpose:
 *  Pass-through to CPage toggles.
 */

void CPages::ToggleDesignMode(BOOL fDesign)
    {
    if (NULL==m_pPageCur)
        return;

    m_fDesignMode=fDesign;
    m_pPageCur->ToggleDesignMode(fDesign);
    return;
    }

void CPages::ToggleUIDead(BOOL fUIDead)
    {
    if (NULL==m_pPageCur)
        return;

    m_fUIDead=fUIDead;
    m_pPageCur->ToggleUIDead(fUIDead);
    return;
    }

void CPages::ToggleHatchHandles(BOOL fHatchHandles)
    {
    if (NULL==m_pPageCur)
        return;

    m_fHatchHandles=fHatchHandles;
    m_pPageCur->ToggleHatchHandles(fHatchHandles);
    return;
    }

/*
 * CPages::FQueryEnableEvents
 * CPages::AssignEvents
 * CPages::TryMnemonic
 * CPages::FIsButton
 * CPages::MarkButton
 *
 * Purpose:
 *  Pass-throughs
 */

BOOL CPages::FQueryEnableEvents(void)
    {
    if (NULL==m_pPageCur)
        return FALSE;

    return m_pPageCur->FQueryEnableEvents();
    }

void CPages::AssignEvents(HWND hWndParent)
    {
    if (NULL!=m_pPageCur)
        m_pPageCur->AssignEvents(hWndParent);

    return;
    }

BOOL CPages::TryMnemonic(LPMSG pMsg)
    {
    if (NULL!=m_pPageCur)
        return m_pPageCur->TryMnemonic(pMsg);

    return FALSE;
    }
//End CHAPTER24MOD

/*
 * CPages::ShowObjectTypes
 *
 * Purpose:
 *  Pass-through to CPage::ShowObjectTypes
 *
 * Parameters:
 *  fShow           BOOL indicating to show the type or not.
 *
 * Return Value:
 *  None
 */

void CPages::ShowObjectTypes(BOOL fShow)
    {
    if (NULL==m_pPageCur)
        return;

    m_fShowTypes=fShow;
    m_pPageCur->ShowObjectTypes(fShow);
    return;
    }

/*
 * CPages::NotifyTenantsOfRename
 *
 * Purpose:
 *  Pass-through to CPage to tell tenants that the document
 *  changes names.
 *
 * Parameters:
 *  pszFile         LPTSTR of the new filename.
 *  pmk             LPMONKIER to the new file moniker.
 *
 * Return Value:
 *  None
 */

void CPages::NotifyTenantsOfRename(LPTSTR pszFile, LPMONIKER pmk)
    {
    if (NULL==m_pPageCur)
        return;

    ReleaseInterface(m_pmkFile);
    m_pmkFile=pmk;
    m_pmkFile->AddRef();
    m_pPageCur->NotifyTenantsOfRename(pszFile, pmk);
    return;
    }

/*
 * CPages::FQueryLinksInPage
 *
 * Purpose:
 *  Pass through to current page to see if there are any
 *  linked objects
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

BOOL CPages::FQueryLinksInPage()
    {
    if (NULL==m_pPageCur)
        return FALSE;

    return m_pPageCur->FQueryLinksInPage();
    }

/*
 * CPages::GetUILinkContainer
 *
 * Purpose:
 *  Creates an object with the IOleUILinkContainer interface for
 *  the links dialog.  We know the current page which is what
 *  we need to create this thing, so we can create it here instead
 *  of bugging the page for it.
 *
 * Parameters:
 *  ppObj           PCIOleUILinkContainer * in which we return
 *                  the pointer.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::GetUILinkContainer(PCIOleUILinkContainer *ppObj)
    {
    PCIOleUILinkContainer   pObj;

    *ppObj=NULL;

    if (NULL==m_pPageCur)
        return FALSE;

    pObj=new CIOleUILinkContainer(m_pPageCur);

    if (NULL==pObj)
        return FALSE;

    if (!pObj->Init())
        {
        delete pObj;
        return FALSE;
        }

    pObj->AddRef();
    *ppObj=pObj;
    return TRUE;
    }

/*
 * CPages::ConvertObject
 *
 * Purpose:
 *  Pass-through to the current page.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame window.
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful, FALSE
 *                  otherwise.
 */

BOOL CPages::ConvertObject(HWND hWndFrame)
    {
    if (NULL==m_pPageCur)
        return FALSE;

    return m_pPageCur->ConvertObject(hWndFrame, FALSE);
    }

/*
 * CPages::CalcBoundingRect
 * (Protected)
 *
 * Purpose:
 *  Calculates a rectangle that bounds the printed page and the
 *  current scroll state of the window.
 *
 * Parameters:
 *  prc             LPRECT to fill with window (device) coordinates.
 *  fWindow         BOOL indicating to include the window in this
 *                  calculation or return only the printed page
 *                  coordinates.
 *
 * Return Value:
 *  None
 */

void CPages::CalcBoundingRect(LPRECT prc, BOOL fWindow)
    {
    RECT        rc, rcT;

    if (NULL==prc)
        return;

    //Calculate the boundaries for sizing: intersect page & screen
    rc.left=LOMETRIC_BORDER+m_xMarginLeft;
    rc.top =-LOMETRIC_BORDER-m_yMarginTop;
    rc.right =rc.left+(UINT)m_cx;
    rc.bottom=rc.top -(UINT)m_cy;
    RectConvertMappings(&rc, NULL, TRUE);
    OffsetRect(&rc, -(int)m_xPos, -(int)m_yPos);

    if (!fWindow)
        {
        *prc=rc;
        return;
        }

    //Intersect with window to make the size bounds.
    GetClientRect(m_hWnd, &rcT);
    IntersectRect(prc, &rc, &rcT);
    return;
    }

/*
 * CPages::PageGet
 * (Protected)
 *
 * Purpose:
 *  Returns a page of a given index returning a BOOL so it's simple
 *  to use this function inside if statements.
 *
 * Parameters:
 *  iPage           UINT page to retrieve 0 based.
 *  ppPage          PCPage * in which to return the page pointer
 *  fOpen           BOOL indicating if we should open this page as
 *                  well.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::PageGet(UINT iPage, PCPage *ppPage, BOOL fOpen)
    {
    if (NULL==ppPage)
        return FALSE;

    if (LB_ERR!=SendMessage(m_hWndPageList, LB_GETTEXT
        , iPage, (LONG)ppPage))
        {
        if (fOpen)
            (*ppPage)->Open(m_pIStorage);

        return TRUE;
        }

    return FALSE;
    }

/*
 * CPages::PageAdd
 * (Protected)
 *
 * Purpose:
 *  Creates a new page initialized to the given values.  The new
 *  page's storage is created if it does not already exist.  If
 *  fOpenStorage is set the page's storage is opened and left
 *  opened.
 *
 * Parameters:
 *  iPage           UINT Location at which to insert page; new page
 *                  is inserted after this position.  NOVALUE for
 *                  the end.
 *  dwID            DWORD ID for this page.
 *  fOpenStorage    BOOL indicating if we're to leave the storage
 *                  open.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CPages::PageAdd(UINT iPage, DWORD dwID, BOOL fOpenStorage)
    {
    PCPage      pPage;
    LRESULT     lr;

    pPage=new CPage(dwID, m_hWnd, this);

    if (NULL==pPage)
        return FALSE;

    pPage->AddRef();

    if (fOpenStorage)
        pPage->Open(m_pIStorage);

    if (NOVALUE==iPage)
        iPage--;

    //Now try to add to the listbox.
    lr=SendMessage(m_hWndPageList, LB_INSERTSTRING, iPage+1
        , (LONG)pPage);

    if (LB_ERRSPACE==lr)
        {
        if (fOpenStorage)
            pPage->Close(FALSE);

        pPage->Release();
        return FALSE;
        }

    return TRUE;
    }

/*
 * CPages::IPageGetFromID
 * (Protected)
 *
 * Purpose:
 *  Returns a page of a given identifier
 *
 * Parameters:
 *  dwID            DWORD identifier of the page to retrieve.  If
 *                  NOVALUE, then we return the current page.
 *  ppPage          PCPage * in which to return the page
 *                  pointer
 *  fOpen           BOOL indicating if we should open this page as
 *                  well.
 *
 * Return Value:
 *  UINT            Index of the page if successful, NOVALUE
 *                  otherwise.
 */

UINT CPages::IPageGetFromID(DWORD dwID, PCPage *ppPage

, BOOL fOpen)
    {
    UINT        i;
    PCPage      pPage;

    if (NULL==ppPage)
        return FALSE;

    if ((LONG)-1==(LONG)dwID)
        {
        *ppPage=m_pPageCur;
        return m_iPageCur;
        }

    *ppPage=NULL;

    for (i=0; i < m_cPages; i++)
        {
        if (!PageGet(i, &pPage, FALSE))
            continue;

        if (pPage->GetID()==dwID)
            {
            if (fOpen)
                pPage->Open(m_pIStorage);

            *ppPage=pPage;
            return i;
            }
        }

    return NOVALUE;
    }


PAGES.H

/*
 * PAGES.H
 * Patron Chapter 24
 *
 * Definitions and function prototypes for the Pages window control
 * as well as the CPage class.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _PAGES_H_
#define _PAGES_H_

#include <stdlib.h>     //For atol

//Versioning.
#define VERSIONMAJOR                2
#define VERSIONMINOR                0
#define VERSIONCURRENT              0x00020000

//Classname
#define SZCLASSPAGES                TEXT("pages")

#define HIMETRIC_PER_INCH           2540
#define LOMETRIC_PER_INCH           254
#define LOMETRIC_BORDER             60          //Border around page

//Window extra bytes and offsets
#define CBPAGESWNDEXTRA             (sizeof(LONG))
#define PAGEWL_STRUCTURE            0

#include "tenant.h"

typedef struct tagTENANTLIST
    {
    DWORD       cTenants;
    DWORD       dwIDNext;
    } TENANTLIST, *PTENANTLIST;

#define SZSTREAMTENANTLIST        OLETEXT("Tenant List")

//Delay timer used in mouse debouncing
#define IDTIMER_DEBOUNCE          120

/*
 * Page class describing an individual page and what things it
 * contains, managing an IStorage for us.
 *
 * A DWORD is used to identify this page as the name of the storage
 * is the string form of this ID.  If we added a page every second,
 * it would take 136 years to overrun this counter, so we can
 * get away with saving it persistently.  I hope this software is
 * obsolete by then.
 */

class CPage : public IUnknown
    {
    friend class CIOleUILinkContainer;
    friend class CImpIOleItemContainer;

    private:
        DWORD       m_dwID;             //Persistent identifier
        LPSTORAGE   m_pIStorage;        //Substorage for this page
        HWND        m_hWnd;             //Pages window
        DWORD       m_cOpens;           //Calls to Open

        class CPages *m_pPG;            //Pages window

        DWORD       m_dwIDNext;
        DWORD       m_cTenants;
        HWND        m_hWndTenantList;   //Listbox; our tenant list

        UINT        m_iTenantCur;
        PCTenant    m_pTenantCur;

        UINT        m_uHTCode;          //Last hit-test/mouse move
        UINT        m_uSizingFlags;     //Restrictions on sizing
        BOOL        m_fTracking;        //Tracking resize?
        RECTL       m_rclOrg;           //Original before tracking
        RECTL       m_rcl;              //Tracking rectangle
        RECTL       m_rclBounds;        //Boundaries f/size tracking
        HDC         m_hDC;              //Tracking hDC

        BOOL        m_fDragPending;     //Waiting for drag?
        BOOL        m_fSizePending;     //Waiting for debounce?
        int         m_cxyDist;          //Debounce distance
        UINT        m_cDelay;           //Debounce delay
        POINTS      m_ptDown;           //Point of click to debounce
        UINT        m_uKeysDown;        //Keys when click happens
        DWORD       m_fTimer;           //Timer active?
        BOOL        m_fReopen;          //Did we just close?

        LPMONIKER               m_pmkFile;  //Document name
        ULONG                   m_cRef;
        DWORD                   m_dwRegROTWild;

        class CImpIOleItemContainer *m_pImpIOleItemContainer;

        BOOL                m_fFirstUIActivate;

    protected:
        BOOL         TenantGet(UINT, PCTenant *, BOOL);
        BOOL         TenantGetFromID(DWORD, PCTenant *, BOOL);
        BOOL         TenantAdd(UINT, DWORD, PCTenant *);
        LPDATAOBJECT TransferObjectCreate(PPOINTL);

        //PAGEMOUS.CPP
        BOOL         SelectTenantAtPoint(UINT, UINT);
        UINT         TenantFromPoint(UINT, UINT, PCTenant *);
        BOOL         DragDrop(UINT, UINT, UINT);

    public:
        CPage(DWORD, HWND, class CPages *);
        ~CPage(void);

        //IUnknown for delegation
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        DWORD       GetID(void);
        BOOL        Open(LPSTORAGE);
        void        Close(BOOL);
        BOOL        Update(void);
        void        Destroy(LPSTORAGE);
        UINT        GetStorageName(LPOLESTR);

        void        Draw(HDC, int, int, BOOL, BOOL);

        BOOL        TenantCreate(TENANTTYPE, LPVOID, LPFORMATETC
                        , PPATRONOBJECT, DWORD);
        BOOL        TenantDestroy(void);
        BOOL        TenantClip(BOOL);
        BOOL        FQueryObjectSelected(HMENU);
        void        ActivateObject(LONG, LPMSG);

        void        ShowObjectTypes(BOOL);
        void        NotifyTenantsOfRename(LPTSTR, LPMONIKER);
        BOOL        FQueryLinksInPage(void);
        BOOL        ConvertObject(HWND, BOOL);
        void        ScrolledWindow(void);
        void        SwitchActiveTenant(PCTenant);
        //CHAPTER24MOD
        void        ToggleDesignMode(BOOL);
        void        ToggleUIDead(BOOL);
        void        ToggleHatchHandles(BOOL);
        BOOL        FQueryEnableEvents(void);
        void        AssignEvents(HWND);
        BOOL        TryMnemonic(LPMSG);
        //End CHAPTER24MOD

        //PAGEMOUSE.CPP
        BOOL        OnRightDown(UINT, UINT, UINT);
        BOOL        OnLeftDown(UINT, UINT, UINT);
        BOOL        OnLeftDoubleClick(UINT, UINT, UINT);
        BOOL        OnLeftUp(UINT, UINT, UINT);
        void        OnMouseMove(UINT, int, int);
        void        OnTimer(UINT);
        void        StartSizeTracking(void);
        void        OnNCHitTest(UINT, UINT);
        BOOL        OnSetCursor(UINT);
    };

typedef CPage *PCPage;

/*
 * Structures to save with the document describing the device
 * configuration and pages that we have.  This is followed by
 * a list of DWORD IDs for the individual pages.
 */

typedef struct tagDEVICECONFIG
    {
    DWORD       cb;                         //Size of structure
    TCHAR       szDriver[CCHDEVICENAME];
    TCHAR       szDevice[CCHDEVICENAME];
    TCHAR       szPort[CCHDEVICENAME];
    DWORD       cbDevMode;                  //Size of actual DEVMODE
    DEVMODE     dm;                         //Variable
    } DEVICECONFIG, *PDEVICECONFIG;

//Offset to cbDevMode
#define CBSEEKOFFSETCBDEVMODE  (sizeof(DWORD)   \
                               +(3*CCHDEVICENAME*sizeof(TCHAR)))

//Combined OLE and Patron device structures.
typedef struct tagCOMBINEDEVICE
    {
    DVTARGETDEVICE  td;
    DEVICECONFIG    dc;
    } COMBINEBDEVICE, *PCOMBINEDEVICE;

typedef struct tagPAGELIST
    {
    DWORD       cPages;
    DWORD       iPageCur;
    DWORD       dwIDNext;
    } PAGELIST, *PPAGELIST;

//PRINT.CPP
BOOL    APIENTRY PrintDlgProc(HWND, UINT, WPARAM, LPARAM);
BOOL    APIENTRY AbortProc(HDC, int);

//PAGEWIN.CPP
LRESULT APIENTRY PagesWndProc(HWND, UINT, WPARAM, LPARAM);
void             RectConvertMappings(LPRECT, HDC, BOOL);

//CHAPTER24MOD
//EVENTS.CPP
BOOL    APIENTRY EventsDlgProc(HWND, UINT, WPARAM, LPARAM);

//Helpers for EventsDlgProc
void CheckAction(HWND, HWND);
void UpdateAction(HWND, UINT);
void TestSelection(HWND);

#ifdef WIN32
#define PROP_POINTER    TEXT("Pointer")
#else
#define PROP_SELECTOR   "Selector"
#define PROP_OFFSET     "Offset"
#endif

//End CHAPTER24MOD

class CPages : public CWindow
    {
    friend LRESULT APIENTRY PagesWndProc(HWND, UINT, WPARAM, LPARAM);
    friend BOOL    APIENTRY PrintDlgProc(HWND, UINT, WPARAM, LPARAM);

    friend class CPage;
    friend class CTenant;
    friend class CDropTarget;
    friend class CImpIAdviseSink;
    friend class CImpIOleItemContainer;
    friend class CImpIOleInPlaceSite;

    protected:
        PCPage      m_pPageCur;             //Current page
        UINT        m_iPageCur;             //Current page
        UINT        m_cPages;               //Number of pages

        HWND        m_hWndPageList;         //Listbox with page list
        HFONT       m_hFont;                //Page font
        BOOL        m_fSystemFont;          //m_hFont system object?

        UINT        m_cx;                   //Page size in LOMETRIC
        UINT        m_cy;

        UINT        m_xMarginLeft;          //Unusable margins,
        UINT        m_xMarginRight;         //in LOMETRIC
        UINT        m_yMarginTop;
        UINT        m_yMarginBottom;

        UINT        m_xPos;                 //Viewport scroll pos,
        UINT        m_yPos;                 //both in *PIXELS*

        DWORD       m_dwIDNext;             //Next ID for a page.
        LPSTORAGE   m_pIStorage;            //Root storage

        UINT        m_cf;                   //Clipboard format
        BOOL        m_fDirty;

        BOOL        m_fDragSource;          //Source==target?
        BOOL        m_fMoveInPage;          //Moving in same page
        BOOL        m_fLinkAllowed;         //Linking in drag-drop?
        POINTL      m_ptDrop;               //Where to move object

        BOOL        m_fDragRectShown;       //Is rect on the screen?
        UINT        m_uScrollInset;         //Hit-test for drag-drop
        UINT        m_uScrollDelay;         //Delay before repeat
        DWORD       m_dwTimeLast;           //Ticks on last DragOver
        UINT        m_uHScrollCode;         //L/R on scroll repeat?
        UINT        m_uVScrollCode;         //U/D on scroll repeat?
        UINT        m_uLastTest;            //Last test result
        POINTL      m_ptlRect;              //Last feedback rectangle
        SIZEL       m_szlRect;

        BOOL        m_fShowTypes;           //Show Object active?
        LPMONIKER   m_pmkFile;
        BOOL        m_fAddUI;
        //CHAPTER24MOD
        BOOL        m_fDesignMode;
        BOOL        m_fUIDead;
        BOOL        m_fHatchHandles;
        IFontDisp  *m_pIFont;               //Ambient font
        //End CHAPTER24MOD

    private:
        void        Draw(HDC, BOOL, BOOL);
        void        UpdateScrollRanges(void);
        BOOL        ConfigureForDevice(void);
        BOOL        PageGet(UINT, PCPage *, BOOL);
        BOOL        PageAdd(UINT, DWORD, BOOL);

        void        CalcBoundingRect(LPRECT, BOOL);

        //DRAGDROP.CPP
        UINT        UTestDroppablePoint(PPOINTL);
        void        DrawDropTargetRect(PPOINTL, LPSIZEL);
        void        AdjustPosition(PPOINTL, LPSIZEL);

    public:
        CPages(HINSTANCE, UINT);
        ~CPages(void);

        BOOL        Init(HWND, LPRECT, DWORD, UINT, LPVOID);

        BOOL        StorageSet(LPSTORAGE, BOOL, BOOL);
        BOOL        StorageUpdate(BOOL);

        BOOL        Print(HDC, LPTSTR, DWORD, UINT, UINT, UINT);

        void        RectGet(LPRECT);
        void        RectSet(LPRECT, BOOL);
        void        SizeGet(LPRECT);
        void        SizeSet(LPRECT, BOOL);

        PCPage      ActivePage(void);
        UINT        PageInsert(UINT);
        UINT        PageDelete(UINT);
        UINT        CurPageGet(void);
        UINT        CurPageSet(UINT);
        UINT        NumPagesGet(void);

        BOOL        DevModeSet(HGLOBAL, HGLOBAL);
        HGLOBAL     DevModeGet(void);

        BOOL        FIsDirty(void);
        BOOL        DevReadConfig(PCOMBINEDEVICE *, HDC *);
        BOOL        TenantCreate(TENANTTYPE, LPVOID, LPFORMATETC
                        , PPATRONOBJECT, DWORD);
        BOOL        TenantDestroy(void);
        BOOL        TenantClip(BOOL);
        BOOL        FQueryObjectSelected(HMENU);
        void        ActivateObject(LONG, LPMSG);
        //CHAPTER24MOD
        void        ToggleDesignMode(BOOL);
        void        ToggleUIDead(BOOL);
        void        ToggleHatchHandles(BOOL);
        BOOL        FQueryEnableEvents(void);
        void        AssignEvents(HWND);
        BOOL        TryMnemonic(LPMSG);
        //End CHAPTER24MOD

        void        ShowObjectTypes(BOOL);
        void        NotifyTenantsOfRename(LPTSTR, LPMONIKER);
        BOOL        FQueryLinksInPage(void);
        BOOL        GetUILinkContainer(class CIOleUILinkContainer **);
        BOOL        ConvertObject(HWND);
        UINT        IPageGetFromID(DWORD, PCPage *, BOOL);
    };

typedef CPages *PCPages;

//Fixed names of streams in the Pages IStorage
#define SZSTREAMPAGELIST        OLETEXT("Page List")
#define SZSTREAMDEVICECONFIG    OLETEXT("Device Configuration")

//Return values for UTestDroppablePoint
#define UDROP_NONE              0x0000      //Exclusive
#define UDROP_CLIENT            0x0001      //Inclusive
#define UDROP_INSETLEFT         0x0002      //L/R are exclusive
#define UDROP_INSETRIGHT        0x0004
#define UDROP_INSETHORZ         (UDROP_INSETLEFT | UDROP_INSETRIGHT)

#define UDROP_INSETTOP          0x0008      //T/B are exclusive
#define UDROP_INSETBOTTOM       0x0010
#define UDROP_INSETVERT         (UDROP_INSETTOP | UDROP_INSETBOTTOM)

//Object used for the Links dialog

class CIOleUILinkContainer : public IOleUILinkContainer
    {
    private:
        ULONG                   m_cRef;
        PCPage                  m_pPage;
        UINT                    m_iTenant;
        LPOLEUILINKCONTAINER    m_pDelIUILinks;

    public:
        BOOL                    m_fDirty;   //No reason to hide it

    protected:
        STDMETHODIMP GetObjectInterface(DWORD, REFIID, PPVOID);

    public:
        CIOleUILinkContainer(PCPage);
        ~CIOleUILinkContainer(void);

        BOOL Init(void);
        BOOL IsDirty(void);

        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(DWORD) GetNextLink(DWORD);
        STDMETHODIMP         SetLinkUpdateOptions(DWORD, DWORD);
        STDMETHODIMP         GetLinkUpdateOptions(DWORD, LPDWORD);
        STDMETHODIMP         SetLinkSource(DWORD, LPTSTR, ULONG
                                 , ULONG *, BOOL);
        STDMETHODIMP         GetLinkSource(DWORD, LPTSTR *, ULONG *
                                 , LPTSTR *, LPTSTR *, BOOL *
                                 , BOOL *);
        STDMETHODIMP         OpenLinkSource(DWORD);
        STDMETHODIMP         UpdateLink(DWORD, BOOL, BOOL);
        STDMETHODIMP         CancelLink(DWORD);
    };

typedef CIOleUILinkContainer *PCIOleUILinkContainer;

#endif  //_PAGES_H_


PAGEWIN.CPP

/*
 * PAGEWIN.CPP
 * Patron Chapter 24
 *
 * Window procedure for the Pages window and support functions.
 * This window manages its own scrollbars and viewport and provides
 * printing capabilities as well.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * PagesWndProc
 *
 * Purpose:
 *  Window procedure for the Pages window.
 */

LRESULT APIENTRY PagesWndProc(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    PCPages         ppg;
    PAINTSTRUCT     ps;
    HDC             hDC;
    int             iPos, iTmp;
    int             iMin, iMax;
    UINT            idScroll;
    BOOL            fDirty=FALSE;

    ppg=(PCPages)GetWindowLong(hWnd, PAGEWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppg=(PCPages)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PAGEWL_STRUCTURE, (LONG)ppg);

            ppg->m_hWnd=hWnd;
            break;

        case WM_PAINT:
            /*
             * If there is currently a drag-rectangle showing, then
             * remove it before painting.  This insures that
             * painting doesn't blast part of that rectangle away so
             * that when we draw it next, garbage is left around.
             */
            if (ppg->m_fDragRectShown)
                ppg->DrawDropTargetRect(NULL, NULL);

            hDC=BeginPaint(hWnd, &ps);

            //Draw only if we have a page to show.
            if (0!=ppg->m_cPages)
                ppg->Draw(hDC, FALSE, FALSE);

            EndPaint(hWnd, &ps);

            //Turn the rectangle back on, if necessary.
            if (ppg->m_fDragRectShown)
                ppg->DrawDropTargetRect(NULL, NULL);
            break;

        case WM_HSCROLL:
        case WM_VSCROLL:
            idScroll=(WM_HSCROLL==iMsg) ? SB_HORZ : SB_VERT;

            iPos=GetScrollPos(hWnd, idScroll);
            iTmp=iPos;
            GetScrollRange(hWnd, idScroll, &iMin, &iMax);

            switch (LOWORD(wParam))
                {
                case SB_LINEUP:     iPos -= 20;  break;
                case SB_PAGEUP:     iPos -=100;  break;
                case SB_LINEDOWN:   iPos += 20;  break;
                case SB_PAGEDOWN:   iPos +=100;  break;

                case SB_THUMBPOSITION:
                    iPos=ScrollThumbPosition(wParam, lParam);
                    break;

                //We don't want scrolling on this message.
                case SB_THUMBTRACK:
                    return 0L;
                }

            iPos=max(iMin, min(iPos, iMax));

            if (iPos!=iTmp)
                {
                //Set the new position and scroll the window
                SetScrollPos(hWnd, idScroll, iPos, TRUE);

                if (SB_HORZ==idScroll)
                    {
                    ppg->m_xPos=iPos;
                    ScrollWindow(hWnd, iTmp-iPos, 0, NULL, NULL);
                    }
                else
                    {
                    ppg->m_yPos=iPos;
                    ScrollWindow(hWnd, 0, iTmp-iPos, NULL, NULL);
                    }
                }

            /*
             * This tells the page to update the rects for the current
             * in-place object.
             */
            if (NULL!=ppg->m_pPageCur)
                ppg->m_pPageCur->ScrolledWindow();

            break;

        case WM_RBUTTONDOWN:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnRightDown(wParam
                , LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_LBUTTONDOWN:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftDown(wParam
                , LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_LBUTTONUP:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftUp(wParam
                , LOWORD(lParam), HIWORD(lParam));
            break;

        case WM_LBUTTONDBLCLK:
            if (NULL==ppg->m_pPageCur)
                break;

            fDirty=ppg->m_pPageCur->OnLeftDoubleClick(wParam, LOWORD(lParam)
                , HIWORD(lParam));
            break;

        case WM_MOUSEMOVE:
            if (NULL==ppg->m_pPageCur)
                break;

            ppg->m_pPageCur->OnMouseMove(wParam, LOWORD(lParam)
                , HIWORD(lParam));
            break;

        case WM_TIMER:
            if (NULL==ppg->m_pPageCur)
                break;

            ppg->m_pPageCur->OnTimer(wParam);
            break;

        case WM_NCHITTEST:
            if (NULL!=ppg->m_pPageCur)
                {
                /*
                 * This just saves information in the page for
                 * OnSetCursor
                 */
                ppg->m_pPageCur->OnNCHitTest(LOWORD(lParam)
                    , HIWORD(lParam));
                }

            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        case WM_SETCURSOR:
            if (NULL!=ppg->m_pPageCur)
                {
                if (ppg->m_pPageCur->OnSetCursor(LOWORD(lParam)))
                    break;
                }

            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    ppg->m_fDirty |= fDirty;
    return 0L;
    }

/*
 * RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device to logical
 *  coordinates where the hDC defines the logical coordinates.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  hDC             HDC describing the logical coordinate system.
 *                  if NULL, uses a screen DC in MM_LOMETRIC.
 *  fToDevice       BOOL TRUE to convert from uConv to device,
 *                  FALSE to convert device to uConv.
 *
 * Return Value:
 *  None
 */

void RectConvertMappings(LPRECT pRect, HDC hDC, BOOL fToDevice)
    {
    POINT   rgpt[2];
    BOOL    fSysDC=FALSE;

    if (NULL==pRect)
        return;

    rgpt[0].x=pRect->left;
    rgpt[0].y=pRect->top;
    rgpt[1].x=pRect->right;
    rgpt[1].y=pRect->bottom;

    if (NULL==hDC)
        {
        hDC=GetDC(NULL);
        SetMapMode(hDC, MM_LOMETRIC);
        fSysDC=TRUE;
        }

    if (fToDevice)
        LPtoDP(hDC, rgpt, 2);
    else
        DPtoLP(hDC, rgpt, 2);

    if (fSysDC)
        ReleaseDC(NULL, hDC);

    pRect->left=rgpt[0].x;
    pRect->top=rgpt[0].y;
    pRect->right=rgpt[1].x;
    pRect->bottom=rgpt[1].y;

    return;
    }

/*
 * CPages::Draw
 *
 * Purpose:
 *  Paints the current page in the pages window.
 *
 * Parameters:
 *  hDC             HDC to draw on, could be a metafile or printer
 *                  DC or any other type of DC.
 *  fNoColor        BOOL indicating if we should use screen colors
 *                  or printer colors (B&W).  Objects are printed
 *                  as-is, however.  This is TRUE for printer DCs
 *                  or print preview.
 *  fPrinter        BOOL indicating if this is a printer DC in which
 *                  case we eliminate some of the fancy drawing,
 *                  like shadows on the page and so forth.
 *
 * Return Value:
 *  None
 */

void CPages::Draw(HDC hDC, BOOL fNoColor, BOOL fPrinter)
    {
    RECT            rc, rcT;
    UINT            uMM;
    HPEN            hPen;
    HBRUSH          hBrush;
    HGDIOBJ         hObj1, hObj2;
    COLORREF        cr;
    TCHAR           szTemp[20];
    UINT            cch;
    SIZE            sz;
    PCPage          pPage;
    RECT            rcPos;

    //Make sure the DC is in LOMETRIC
    uMM=SetMapMode(hDC, MM_LOMETRIC);

    if (!fPrinter)
        {
        /*
         * We maintain a 6mm border around the page on the screen
         * besides 12.7mm margins.  We also have to account for
         * the scroll position with m_*Pos which are in pixels so
         * we have to convert them.
         */

        SetRect(&rcPos, m_xPos, m_yPos, 0, 0);
        RectConvertMappings(&rcPos, hDC, FALSE);

        rc.left  = LOMETRIC_BORDER-rcPos.left;
        rc.top   =-LOMETRIC_BORDER-rcPos.top;
        }
    else
        {
        /*
         * We define the corner of the printed paper at a negative
         * offset so rc.right and rc.bottom come out right below.
         */
        SetRect(&rc, -(int)m_xMarginLeft, m_yMarginTop, 0, 0);
        }

    rc.right=rc.left+m_cx+(m_xMarginLeft+m_xMarginRight);
    rc.bottom=rc.top-m_cy-(m_yMarginTop+m_yMarginBottom);

    //Draw a rect filled with the window color to show the page.
    if (!fPrinter)
        {
        if (fNoColor)
            {
            //Black frame, white box for printed colors.
            hPen  =CreatePen(PS_SOLID, 0, RGB(0,0,0));
            hBrush=CreateSolidBrush(RGB(255, 255, 255));
            }
        else
            {
            //Normal colors on display
            hPen=CreatePen(PS_SOLID, 0
                , GetSysColor(COLOR_WINDOWFRAME));
            hBrush=CreateSolidBrush(GetSysColor(COLOR_WINDOW));
            }

        hObj1=SelectObject(hDC, hPen);
        hObj2=SelectObject(hDC, hBrush);

        //Paper boundary
        Rectangle(hDC, rc.left, rc.top, rc.right, rc.bottom+1);

        /*
         * Draw a shadow on the *visual* bottom and right edges
         * .5mm wide.  If the button shadow color and workspace
         * colors match, then use black.  We always use black
         * when printing as well.
         */
        if (fNoColor)
            cr=RGB(0,0,0);
        else
            {
            cr=GetSysColor(COLOR_BTNSHADOW);

            if (GetSysColor(COLOR_APPWORKSPACE)==cr)
                cr=RGB(0,0,0);
            }

        cr=SetBkColor(hDC, cr);
        SetRect(&rcT, rc.left+5, rc.bottom, rc.right+5,rc.bottom-5);
        ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rcT, NULL, 0, NULL);

        SetRect(&rcT, rc.right, rc.top-5, rc.right+5, rc.bottom-5);
        ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rcT, NULL, 0, NULL);
        SetBkColor(hDC, cr);

        SelectObject(hDC, hObj1);
        SelectObject(hDC, hObj2);
        DeleteObject(hBrush);
        DeleteObject(hPen);
        }

    //Write the page number in the lower left corner
    if (!fNoColor)
        {
        SetTextColor(hDC, GetSysColor(COLOR_WINDOWTEXT));
        SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
        }

    //Write the page number in our page font.
    cch=wsprintf(szTemp, TEXT("Page %d"), m_iPageCur+1);

    hObj1=SelectObject(hDC, m_hFont);
    GetTextExtentPoint(hDC, szTemp, cch, &sz);

    TextOut(hDC, rc.left+m_xMarginLeft
        , rc.bottom+m_yMarginBottom+sz.cy, szTemp, cch);

    SelectObject(hDC, hObj1);

    //Rectangle to show border.
    MoveToEx(hDC, rc.left+m_xMarginLeft, rc.top-m_yMarginTop, NULL);
    LineTo(hDC, rc.left+m_xMarginLeft,   rc.bottom+m_yMarginBottom);
    LineTo(hDC, rc.right-m_xMarginRight, rc.bottom+m_yMarginBottom);
    LineTo(hDC, rc.right-m_xMarginRight, rc.top-m_yMarginTop);
    LineTo(hDC, rc.left+m_xMarginLeft,   rc.top-m_yMarginTop);

    /*
     * Go draw the objects on this page.  If the page is not open,
     * we open it anyway.  If it is already open, then opening again
     * will bump it's reference count, so the Close in ineffectual.
     */
    if (PageGet(m_iPageCur, &pPage, TRUE))
        {
        if (!fPrinter)
            {
            pPage->Draw(hDC, rcPos.left, rcPos.top, fNoColor
                , fPrinter);
            }
        else
            pPage->Draw(hDC, 0, 0, fNoColor, fPrinter);

        pPage->Close(FALSE);
        }

    SetMapMode(hDC, uMM);
    return;
    }

/*
 * CPages::UpdateScrollRanges
 *
 * Purpose:
 *  Reset scrollbar ranges (horizontal and vertical) depending on
 *  the window size and the page size.  This function may remove
 *  the scrollbars altogether.
 *
 * Parameters:
 *  None, but set m_cx, m_cy and size m_hWnd before calling.
 *
 * Return Value:
 *  None
 */

void CPages::UpdateScrollRanges(void)
    {
    UINT        cxSB;   //Scrollbar width and height.
    UINT        cySB;
    UINT        cx, cy;
    UINT        dx, dy;
    UINT        u;
    int         iMin, iMax;
    RECT        rc;
    BOOL        fHScroll;
    BOOL        fVScroll;
    BOOL        fWasThere;

    GetClientRect(m_hWnd, &rc);

    cx=rc.right-rc.left;
    cy=rc.bottom-rc.top;

    //Convert dimensions of the image in LOMETRIC to pixels.
    SetRect(&rc, (m_cx+m_xMarginLeft+m_xMarginRight
        +LOMETRIC_BORDER*2), (m_cy+m_yMarginTop
        +m_yMarginBottom+LOMETRIC_BORDER*2), 0, 0);

    RectConvertMappings(&rc, NULL, TRUE);

    dx=rc.left;
    dy=-rc.top;

    //Assume that both scrollbars will be visible.
    fHScroll=TRUE;
    fVScroll=TRUE;

    /*
     * Determine:
     *  1)  Which scrollbars are needed.
     *  2)  How many divisions to give scrollbars so as to
     *      only scroll as little as necessary.
     */

    //Scrollbar dimensions in our units.
    cxSB=GetSystemMetrics(SM_CXVSCROLL);
    cySB=GetSystemMetrics(SM_CYHSCROLL);

    //Remove horizontal scroll if window >= cxPage+borders
    if (cx >= dx)
        fHScroll=FALSE;

    /*
     * If we still need a horizontal scroll, see if we need a
     * vertical taking the height of the horizontal scroll into
     * account.
     */

    u=fHScroll ? cySB : 0;

    if ((cy-u) >= dy)
        fVScroll=FALSE;

    //Check if adding vert scrollbar necessitates a horz now.
    u=fVScroll ? cxSB : 0;
    fHScroll=((cx-u) < dx);

    /*
     * Modify cx,cy to reflect the new client area before scaling
     * scrollbars.  We only affect the client size if there is a
     * *change* in scrollbar status:  if the scrollbar was there
     * but is no longer, then add to the client size; if it was
     * not there but now is, then subtract.
     */

    //Change cx depending on vertical scrollbar change
    GetScrollRange(m_hWnd, SB_VERT, &iMin, &iMax);
    fWasThere=(0!=iMin || 0!=iMax);

    if (fWasThere && !fVScroll)
        cx+=cxSB;

    if (!fWasThere && fVScroll)
        cx-=cxSB;

    //Change cy depending on horizontal scrollbar change
    GetScrollRange(m_hWnd, SB_HORZ, &iMin, &iMax);
    fWasThere=(0!=iMin || 0!=iMax);

    if (fWasThere && !fHScroll)
        cy+=cySB;

    if (!fWasThere && fHScroll)
        cy-=cySB;

    /*
     * Show/Hide the scrollbars if necessary and set the ranges.
     * The range is the number of units of the page we cannot see.
     */
    if (fHScroll)
        {
        //Convert current scroll position to new range.
        u=GetScrollPos(m_hWnd, SB_HORZ);

        if (0!=u)
            {
            GetScrollRange(m_hWnd, SB_HORZ, &iMin, &iMax);
            u=MulDiv(u, (dx-cx), (iMax-iMin));
            }

        SetScrollRange(m_hWnd, SB_HORZ, 0, dx-cx, FALSE);
        SetScrollPos(m_hWnd, SB_HORZ, u, TRUE);
        m_xPos=u;
        }
    else
        {
        SetScrollRange(m_hWnd, SB_HORZ, 0, 0, TRUE);
        m_xPos=0;
        }

    if (fVScroll)
        {
        //Convert current scroll position to new range.
        u=GetScrollPos(m_hWnd, SB_VERT);

        if (0!=u)
            {
            GetScrollRange(m_hWnd, SB_VERT, &iMin, &iMax);
            u=MulDiv(u, (dy-cy), (iMax-iMin));
            }

        SetScrollRange(m_hWnd, SB_VERT, 0, dy-cy, FALSE);
        SetScrollPos(m_hWnd, SB_VERT, u, TRUE);

        m_yPos=u;
        }
    else
        {
        SetScrollRange(m_hWnd, SB_VERT, 0, 0, TRUE);
        m_yPos=0;
        }

    //Repaint to insure that changes to m_x/yPos are reflected
    InvalidateRect(m_hWnd, NULL, TRUE);

    return;
    }


PATRON.CPP

/*
 * PATRON.CPP
 * Patron Chapter 24
 *
 * WinMain which is all we need for the basic application.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "patron.h"

ULONG       g_cObj=0;
ULONG       g_cLock=0;
HWND        g_hWnd=NULL;
BOOL        g_fUser=TRUE;

/*
 * The in-place site needs to have access to the frame and its
 * IOleInPlaceFrame interface.  A global, pardon me, is the simplest
 * way to achieve this.
 */

PCPatronFrame   g_pFR;

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCPatronFrame   pFR;
    FRAMEINIT       fi;
    WPARAM          wRet=0;

    SETMESSAGEQUEUE;

    //Attempt to allocate and initialize the application
    pFR=new CPatronFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return -1;

    g_pFR=pFR;

    fi.idsMin=IDS_FRAMEMIN;
    fi.idsMax=IDS_FRAMEMAX;
    fi.idsStatMin=IDS_STATMESSAGEMIN;
    fi.idsStatMax=IDS_STATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=CW_USEDEFAULT;
    fi.cy=CW_USEDEFAULT;

    //If we can initialize pFR, start chugging messages
    if (pFR->Init(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Patron Document object to call when it gets
 *  destroyed.  We destroy the main window if the proper conditions
 *  are met for shutdown.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;

    //No more objects, no locks, no user control, shut the app down.
    if (0==g_cObj && 0==g_cLock && IsWindow(g_hWnd) && !g_fUser)
        PostMessage(g_hWnd, WM_CLOSE, 0, 0L);

    return;
    }

/*
 * CPatronFrame::CPatronFrame
 * CPatronFrame::~CPatronFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WInMain
 */

CPatronFrame::CPatronFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    : CFrame(hInst, hInstPrev, pszCmdLine, nCmdShow)
    {
    m_fInitialized=FALSE;
    m_pIClassDataTran=NULL;

    m_pDocCreated=NULL;
    m_fEmbedding=FALSE;

    m_dwRegCO=0;
    m_pIClassFactory=NULL;

    m_cRef=0;
    m_hAccelIP=NULL;
    m_hWndObj=NULL;
    m_hMenuOrg=NULL;
    m_hMenuTop=NULL;
    m_fOurToolsShowing=TRUE;
    m_fInContextHelp=FALSE;
    m_pIOleIPActiveObject=NULL;

    return;
    }

CPatronFrame::~CPatronFrame(void)
    {
    //Opposite of CoRegisterClassObject, takes class factory ref to 1
    if (0L!=m_dwRegCO)
        CoRevokeClassObject(m_dwRegCO);

    //This should be the last Release, which frees the class factory.
    ReleaseInterface(m_pIClassFactory);

    if (NULL!=m_pIClassDataTran)
        {
        m_pIClassDataTran->LockServer(FALSE);
        m_pIClassDataTran->Release();
        }

    OleFlushClipboard();

    if (m_fInitialized)
        OleUninitialize();

    return;
    }

/*
 * CPatronFrame::Init
 *
 * Purpose:
 *  Call OleInitialize then calling down into the base class
 *  initialization.
 *
 * Parameters:
 *  pFI             PFRAMEINIT containing initialization
 *                  parameters.
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded,
 *                  FALSE otherwise.
 */

BOOL CPatronFrame::Init(PFRAMEINIT pFI)
    {
    HRESULT     hr;
    BOOL        fRet;

    CHECKVER_OLE;

    if (FAILED(OleInitialize(NULL)))
        return FALSE;

    m_fInitialized=TRUE;

    //Lock the data transfer object factory as an optimization.
    hr=CoGetClassObject(CLSID_DataTransferObject
        , CLSCTX_INPROC_SERVER, NULL, IID_IClassFactory
        , (PPVOID)&m_pIClassDataTran);

    if (SUCCEEDED(hr))
        m_pIClassDataTran->LockServer(TRUE);

    //Check for command line flags
    ParseCommandLine();

    if (NULL!=m_ppszCmdArgs)
        {
        if(0==lstrcmpi(m_ppszCmdArgs[0], TEXT("-Embedding"))
           || 0==lstrcmpi(m_ppszCmdArgs[0], TEXT("/Embedding")))
            m_fEmbedding=TRUE;
        }

    g_fUser=!m_fEmbedding;

    if (m_fEmbedding)
        {
        HRESULT     hr;

        m_pIClassFactory=new CLinkClassFactory(this);

        if (NULL==m_pIClassFactory)
            return FALSE;

        //Since we hold on to this, we should AddRef it.
        m_pIClassFactory->AddRef();

        hr=CoRegisterClassObject(CLSID_PatronPages, m_pIClassFactory
            , CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE, &m_dwRegCO);

        if (FAILED(hr))
            return FALSE;
        }

    //Load in-place accelerators
    m_hAccelIP=LoadAccelerators(m_hInst
        , MAKEINTRESOURCE(IDR_INPLACEACCELERATORS));

    if (NULL==m_hAccelIP)
        return FALSE;

    fRet=CFrame::Init(pFI);

    m_hMenuOrg=GetMenu(m_hWnd);
    return fRet;
    }

/*
 * CPatronFrame::CreateCClient
 *
 * Purpose:
 *  Constructs a new client specific to the application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient        Pointer to the new client object.
 */

PCClient CPatronFrame::CreateCClient(void)
    {
    return (PCClient)(new CPatronClient(m_hInst, this));
    }

/*
 * CPatronFrame::RegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CPatronFrame::RegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //First let the standard frame do its thing
    if (!CFrame::RegisterAllClasses())
        return FALSE;

    //We need double-clicks now and for object activation.
    wc.style         = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
    wc.hInstance     = m_hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PagesWndProc;
    wc.cbWndExtra    = CBPAGESWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPAGES;

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

    return TRUE;
    }

/*
 * CPatronFrame::PreShowInit
 *
 * Purpose:
 *  Called from Init before intially showing the window.  We do
 *  whatever else we want here, modifying m_nCmdShow as necessary
 *  which affects ShowWindow in Init.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if this successful, FALSE otherwise.
 */

BOOL CPatronFrame::PreShowInit(void)
    {
    //Base class does nothing
    CFrame::PreShowInit();

    //Save the window handle for shutdown if necessary.
    g_hWnd=m_hWnd;

    //If we're -Embedding, don't show the window initially.
    if (m_fEmbedding)
        m_nCmdShow=SW_HIDE;

    return TRUE;
    }

/*
 * CPatronFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the Patron frame window that processes
 *  extra File menu items as well as the Page menu.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CPatronFrame::OnCommand(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PCPatronDoc     pDoc;
    //CHAPTER24MOD
    BOOL            fTemp;
    //End CHAPTER24MOD

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many toolbar notifications.
     */
    if (m_fInit)
        return 0L;

    pDoc=(PCPatronDoc)m_pCL->ActiveDocument();

    //Also check for the open command now too.
    if (NULL!=pDoc && ((IDM_VERBMIN <= wID) && (IDM_VERBMAX >= wID)
        || IDM_OPENOBJECT==wID))
        {
        MSG     msg;
        DWORD   dw;
        LONG    iVerb=(long)(wID-IDM_VERBMIN);

        //Include a message for in-place objects.
        msg.hwnd=NULL;
        msg.message=WM_COMMAND;
        msg.wParam=wParam;
        msg.lParam=lParam;
        msg.time=GetMessageTime();

        dw=GetMessagePos();
        SETPOINT(msg.pt, LOWORD(dw), HIWORD(dw));

        if (IDM_OPENOBJECT==wID)
            iVerb=OLEIVERB_OPEN;

        pDoc->ActivateObject(iVerb, &msg);
        return 0L;
        }

    switch (wID)
        {
        case IDM_FILEPRINT:
            pDoc->Print(m_hWnd);
            return 0L;

        case IDM_FILEPRINTERSETUP:
            pDoc->PrinterSetup(m_hWnd, FALSE);
            return 0L;

        case IDM_EDITPASTESPECIAL:
            pDoc->PasteSpecial(m_hWnd);
            return 0L;

        case IDM_EDITDELETEOBJECT:
            pDoc->Delete();
            return 0L;

        case IDM_EDITINSERTOBJECT:
            pDoc->InsertObject(m_hWnd);
            return 0L;

        case IDM_EDITCONVERT:
            pDoc->ConvertObject(m_hWnd);
            return 0L;

        case IDM_EDITLINKS:
            pDoc->EditLinks(m_hWnd);
            return 0L;

        //CHAPTER24MOD
        case IDM_EDITOBJECTEVENTS:
            pDoc->AssignEvents(m_hWnd);
            break;
        //End CHAPTER24MOD

        case IDM_PAGENEWPAGE:
            pDoc->NewPage();
            break;

        case IDM_PAGEDELETEPAGE:
            pDoc->DeletePage();
            break;

        case IDM_PAGENEXTPAGE:
            pDoc->NextPage();
            break;

        case IDM_PAGEPREVIOUSPAGE:
            pDoc->PreviousPage();
            break;

        case IDM_PAGEFIRSTPAGE:
            pDoc->FirstPage();
            break;

        case IDM_PAGELASTPAGE:
            pDoc->LastPage();
            break;

        case IDM_PAGESHOWOBJECTS:
            {
            BOOL    fTemp;

            //First get the current state, then toggle it.
            fTemp=pDoc->ShowOrQueryObjectTypes(TRUE, FALSE);
            pDoc->ShowOrQueryObjectTypes(FALSE, !fTemp);
            }
            break;

        //CHAPTER24MOD
        case IDM_PAGEDESIGNMODE:
            fTemp=pDoc->FToggleOrQueryDesignMode(TRUE, FALSE);
            pDoc->FToggleOrQueryDesignMode(FALSE, !fTemp);
            break;

        case IDM_PAGEUIDISABLED:
            fTemp=pDoc->FToggleOrQueryUIDead(TRUE, FALSE);
            pDoc->FToggleOrQueryUIDead(FALSE, !fTemp);
            break;

         case IDM_PAGESHOWHATCHHANDLES:
            fTemp=pDoc->FToggleOrQueryHatchHandles(TRUE, FALSE);
            pDoc->FToggleOrQueryHatchHandles(FALSE, !fTemp);
            break;
        //End CHAPTER24MOD

        case IDM_ENTERCONTEXTHELP:
        case IDM_ESCAPECONTEXTHELP:
            //Notify the object on entry and exit.
            ContextSensitiveHelp(IDM_ENTERCONTEXTHELP==wID);
            break;

        default:
           return CFrame::OnCommand(hWnd, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPatronFrame::CreateToolbar
 *
 * Purpose:
 *  Procedure to create all the necessary toolbar buttons.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of tools added to the bar.
 */

UINT CPatronFrame::CreateToolbar(void)
    {
    UINT            iLast;
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;

    //Insert the standard ones.
    iLast=CFrame::CreateToolbar();

    //Remove Undo:  we don't use it.
    m_pTB->Remove(IDM_EDITUNDO);

    /*
     * Insert Print File Import in the 5th position and account
     * for it in iLast.
     */
    m_pTB->Add(utCmd, 4, IDM_FILEPRINT, m_dxB, m_dyB
        , NULL, NULL, 6, uState);

    iLast++;

    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //Add New Page, and Delete Page
    m_pTB->Add(utCmd, iLast++, IDM_PAGENEWPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 2, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGEDELETEPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 3, uState);

    m_pTB->Add(GIZMOTYPE_SEPARATOR, iLast++, 0, 6, m_dyB
        , NULL, NULL, 0, uState);

    //First, Prev, Next, Last pages.
    m_pTB->Add(utCmd, iLast++, IDM_PAGEFIRSTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 4, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGEPREVIOUSPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 5, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGENEXTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 6, uState);
    m_pTB->Add(utCmd, iLast++, IDM_PAGELASTPAGE, m_dxB, m_dyB
        , NULL, m_hBmp, 7, uState);

    return iLast;
    }

/*
 * CPatronFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CPatronFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    PCPatronDoc     pDoc;
    BOOL            fOK=FALSE;
    BOOL            fCallDefault=TRUE;
    UINT            uTemp;
    UINT            uTempE;
    UINT            uTempD;

    pDoc=(PCPatronDoc)m_pCL->ActiveDocument();

    uTempE=MF_ENABLED | MF_BYCOMMAND;
    uTempD=MF_DISABLED | MF_GRAYED | MF_BYCOMMAND;
    uTemp=((NULL!=pDoc) ? uTempE : uTempD);

    if (m_phMenu[0]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_FILEPRINT, uTemp);

        if (NULL!=pDoc)
            fOK=pDoc->FQueryPrinterSetup();

        EnableMenuItem(hMenu, IDM_FILEPRINTERSETUP
            , (fOK) ? uTempE : uTempD);
        }

    if (m_phMenu[1]==hMenu)
        {
        if (NULL!=pDoc)
            fOK=pDoc->FQueryPaste();

        EnableMenuItem(hMenu, IDM_EDITPASTE
            , (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITPASTESPECIAL
            , (fOK) ? uTempE : uTempD);

        //Cut, Copy, Delete depends on there being a selection.
        if (NULL!=pDoc)
            fOK=pDoc->FQueryObjectSelected(hMenu);
        else
            fOK=FALSE;

        EnableMenuItem(hMenu, IDM_EDITCUT, (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITCOPY
            , (fOK) ? uTempE : uTempD);
        EnableMenuItem(hMenu, IDM_EDITDELETEOBJECT
            , (fOK) ? uTempE : uTempD);

        EnableMenuItem(hMenu, IDM_EDITINSERTOBJECT, uTemp);

        if (NULL!=pDoc)
            fOK=pDoc->FQueryEnableEditLinks();
        else
            fOK=FALSE;

        EnableMenuItem(hMenu, IDM_EDITLINKS
            , (fOK) ? uTempE : uTempD);

        //CHAPTER24MOD
        //Enable Events... if we have a control
        fOK=FALSE;

        if (NULL!=pDoc)
            fOK=pDoc->FQueryEnableEvents();

        EnableMenuItem(hMenu, IDM_EDITOBJECTEVENTS
            , (fOK) ? uTempE : uTempD);
        //End CHAPTER24MOD

        //We did the whole menu...
        fCallDefault=FALSE;
        }

    //Page menu
    if (m_phMenu[2]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_PAGENEWPAGE,      uTemp);
        EnableMenuItem(hMenu, IDM_PAGEDELETEPAGE,   uTemp);
        EnableMenuItem(hMenu, IDM_PAGENEXTPAGE,     uTemp);
        EnableMenuItem(hMenu, IDM_PAGEPREVIOUSPAGE, uTemp);
        EnableMenuItem(hMenu, IDM_PAGEFIRSTPAGE,    uTemp);
        EnableMenuItem(hMenu, IDM_PAGELASTPAGE,     uTemp);

        //Check the Show Objects command or not.
        if (NULL!=pDoc)
            fOK=pDoc->ShowOrQueryObjectTypes(TRUE, FALSE);
        else
            fOK=FALSE;

        CheckMenuItem(hMenu, IDM_PAGESHOWOBJECTS, MF_BYCOMMAND
            | ((fOK) ? MF_CHECKED : MF_UNCHECKED));
        EnableMenuItem(hMenu, IDM_PAGESHOWOBJECTS, uTemp);

        //CHAPTER24MOD
        //Check the Design Mode command or not.
        if (NULL!=pDoc)
            fOK=pDoc->FToggleOrQueryDesignMode(TRUE, FALSE);
        else
            fOK=FALSE;

        CheckMenuItem(hMenu, IDM_PAGEDESIGNMODE, MF_BYCOMMAND
            | ((fOK) ? MF_CHECKED : MF_UNCHECKED));
        EnableMenuItem(hMenu, IDM_PAGEDESIGNMODE, uTemp);

        //Check the UI Disabled command or not.
        if (NULL!=pDoc)
            fOK=pDoc->FToggleOrQueryUIDead(TRUE, FALSE);
        else
            fOK=FALSE;

        CheckMenuItem(hMenu, IDM_PAGEUIDISABLED, MF_BYCOMMAND
            | ((fOK) ? MF_CHECKED : MF_UNCHECKED));
        EnableMenuItem(hMenu, IDM_PAGEUIDISABLED, uTemp);

        //Check the Show Hatch and Handles command or not.
        if (NULL!=pDoc)
            fOK=pDoc->FToggleOrQueryHatchHandles(TRUE, FALSE);
        else
            fOK=FALSE;

        CheckMenuItem(hMenu, IDM_PAGESHOWHATCHHANDLES, MF_BYCOMMAND
            | ((fOK) ? MF_CHECKED : MF_UNCHECKED));
        EnableMenuItem(hMenu, IDM_PAGESHOWHATCHHANDLES, uTemp);
        //End CHAPTER24MOD
        }

    if (fCallDefault)
        CFrame::UpdateMenus(hMenu, iMenu);

    return;
    }

/*
 * CPatronFrame::UpdateToolbar
 *
 * Purpose:
 *  Enables and disables tools depending on whether we have
 *  a document or not.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronFrame::UpdateToolbar(void)
    {
    PCDocument  pDoc;
    BOOL        fEnable;

    //Let the default hack on its tools.
    CFrame::UpdateToolbar();

    pDoc=m_pCL->ActiveDocument();
    fEnable=(NULL!=pDoc);

    //No document, disable just about everything
    m_pTB->Enable(IDM_FILEPRINT,        fEnable);
    m_pTB->Enable(IDM_FILEPRINTERSETUP, fEnable);

    m_pTB->Enable(IDM_PAGENEWPAGE,      fEnable);
    m_pTB->Enable(IDM_PAGEDELETEPAGE,   fEnable);
    m_pTB->Enable(IDM_PAGEFIRSTPAGE,    fEnable);
    m_pTB->Enable(IDM_PAGEPREVIOUSPAGE, fEnable);
    m_pTB->Enable(IDM_PAGENEXTPAGE,     fEnable);
    m_pTB->Enable(IDM_PAGELASTPAGE,     fEnable);

    return;
    }

/*
 * CPatronFrame::MessageLoop
 *
 * Purpose:
 *  Override of standard message loop function in CLASSLIB to use
 *  in-place accelerators when necessary.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  WPARAM          Contents of msg.wParam from WM_QUIT.
 */

WPARAM CPatronFrame::MessageLoop(void)
    {
    MSG     msg;

    while (GetMessage(&msg, NULL, 0,0 ))
        {
        HACCEL      hAccel=m_hAccel;

        //Always give the object first crack at translation.
        if (NULL!=m_pIOleIPActiveObject)
            {
            HRESULT     hr;

            hAccel=m_hAccelIP;
            hr=m_pIOleIPActiveObject->TranslateAccelerator(&msg);

            //If the object translated the accelerator, we're done
            if (NOERROR==hr)
                continue;
            }

        if (!m_pCL->TranslateAccelerator(&msg))
            {
            //hAccel is either the normal ones or the in-place ones.

            if (!::TranslateAccelerator(m_hWnd, hAccel, &msg))
                {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
                }
            }
        }

    return msg.wParam;
    }

/*
 * CPatronFrame::FMessageHook
 *
 * Purpose:
 *  Override of CFrame::FMessageHook so we can specifically trap
 *  WM_MENUSELECT messages for the Object verb menu to provide some
 *  meaningful information on the status strip.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return value
 *                  for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing,
 *                  FALSE otherwise.
 */

BOOL CPatronFrame::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    BOOL        fRet=FALSE;
    int         dx, dy;
    //CHAPTER24MOD
    PCPatronDoc pDoc;
    MSG         msg;
    DWORD       dw;
    //End CHAPTER24MOD

    MENUSELECTPARAMS(wItem, wMenuFlags, hMenu);

    *pLRes=0;

    /*
     * We use a switch now because we previously only processed
     * WM_MENUSELECT which did not use a case statement.
     */

    switch (iMsg)
        {
        case WM_SIZE:
            if (NULL!=m_pIOleIPActiveObject)
                {
                RECT        rc;

                GetClientRect(m_hWnd, &rc);
                m_pIOleIPActiveObject->ResizeBorder(&rc
                    , this, TRUE);
                }

            /*
             * If we're not showing our tools, don't let CLASSLIB
             * resize the client, instead doing it ourselves.
             */
            if (m_fOurToolsShowing)
                return FALSE;

            m_fSizing=TRUE;
            dx=LOWORD(lParam);
            dy=HIWORD(lParam);

            //Change the Toolbar and StatStrip widths to match
            m_pTB->OnSize(hWnd);
            m_pSL->OnSize(hWnd);

            //Adjust the client properly, remembering the StatStrip.
            m_pCL->OnSize(m_cxLeft, m_cyTop, dx-m_cxLeft-m_cxRight
                , dy-m_cyTop-m_cyBottom-CYSTATSTRIP);

            m_fSizing=FALSE;
            return TRUE;

        case WM_ACTIVATEAPP:
            if (NULL!=m_pIOleIPActiveObject)
                {
                m_pIOleIPActiveObject->OnFrameWindowActivate
                    ((BOOL)wParam);
                }

            return FALSE;

        case WM_SETFOCUS:
            if (NULL!=m_pIOleIPActiveObject)
                {
                HWND    hWndObj;

                m_pIOleIPActiveObject->GetWindow(&hWndObj);
                SetFocus(hWndObj);
                }

            return TRUE;

        case WM_CLOSE:
            if (NULL!=m_pDocCreated)
                {
                if (m_pDocCreated->FDirtyGet())
                    {
                    CHourglass  wait;
                    m_pDocCreated->Save(0, NULL);
                    }
                }

            return FALSE;

        case WM_MENUSELECT:
            break;      //Continue processing below.

        //CHAPTER24MOD
        /*
         * Keyboard mnemonics can be Control- or Alt-keys with
         * any other modifier.  That means WM_KEYDOWN with
         * Control on or WM_SYSKEYDOWN by itself.
         */
        case WM_KEYDOWN:
            //Filter out things without Control
            if (!(0x8000 & GetKeyState(VK_CONTROL)))
                return FALSE;

            //FALL-THROUGH

        case WM_SYSKEYDOWN:
            pDoc=(PCPatronDoc)m_pCL->ActiveDocument();

            if (NULL==pDoc)
                return FALSE;

            msg.hwnd=hWnd;
            msg.message=iMsg;
            msg.wParam=wParam;
            msg.lParam=lParam;
            msg.time=GetMessageTime();
            dw=GetMessagePos();
            msg.pt.x=LOWORD(dw);
            msg.pt.y=HIWORD(dw);
            return pDoc->TryMnemonic(&msg);
        //End CHAPTER24MOD

        default:
            return FALSE;
        }

    //This happens when there's no menu selection.
    if (-1==wMenuFlags)
        return FALSE;

    if (MF_POPUP & wMenuFlags)
        {
        /*
         * If this is the cascade verb menu itself, display the same
         * message.  m_phMenu[1] contains the current edit menu
         * handle.
         */
        if (0!=wItem)
            {
            fRet=((HMENU)wItem==GetSubMenu(m_phMenu[1]
                , MENUPOS_OBJECT));
            }
        }
    else
        {
        /*
         * If the ID is in the verb range, use
         * IDS_ITEMMESSAGEEDITOBJECT message
         */
        fRet=(IDM_VERBMIN <= wItem && IDM_VERBMAX >= wItem);
        }

    if (fRet)
        m_pSL->MessageDisplay(IDM_EDITOBJECT);

    return fRet;
    }

/*
 * CPatronFrame::ShowUIAndTools
 *
 * Purpose:
 *  Installs or removes the in-place user interface which includes
 *  the menus and the tools.  The tools may be affected outside of
 *  the menu.
 *
 * Parameters:
 *  fShow           BOOL indicating to show (TRUE) or hide (FALSE)
 *  fMenu           BOOl indicating if the menu is also affected.
 *
 * Return Value:
 *  None
 */

void CPatronFrame::ShowUIAndTools(BOOL fShow, BOOL fMenu)
    {
    HWND    hWndTB;

    //This is the only menu case...restore our original menu
    if (fMenu && fShow)
        SetMenu(NULL, NULL, NULL);

    /*
     * If we're trying to hide our tools but the object didn't
     * want any space, then just leave our tools up and active.
     */

    hWndTB=m_pTB->Window();
    ShowWindow(hWndTB, fShow ? SW_SHOW : SW_HIDE);

    if (fShow)
        {
        InvalidateRect(hWndTB, NULL, TRUE);
        UpdateWindow(hWndTB);
        }

    m_fOurToolsShowing=fShow;
    return;
    }

/*
 * CPatronFrame::ReinstateUI
 *
 * Purpose:
 *  Reinstalls the application's normal toolbar and menu and
 *  readjusts the position of the client window and the documents
 *  within it.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPatronFrame::ReinstateUI(void)
    {
    BORDERWIDTHS    bw;

    ShowUIAndTools(TRUE, TRUE);

    SetRect((LPRECT)&bw, 0, m_cyBar, 0, 0);
    SetBorderSpace(&bw);
    return;
    }

/*
 * CPatronFrame::QueryInterface
 * CPatronFrame::AddRef
 * CPatronFrame::Release
 */

STDMETHODIMP CPatronFrame::QueryInterface(REFIID riid, PPVOID ppv)
    {
    //We only know IUnknown and IOleInPlaceFrame
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IOleInPlaceUIWindow==riid
        || IID_IOleWindow==riid || IID_IOleInPlaceFrame==riid)
        *ppv=(LPOLEINPLACEFRAME)this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CPatronFrame::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPatronFrame::Release(void)
    {
    /*
     * We don't do anything with this since we're not controlled
     * by a reference count as far as in-place stuff is concerned.
     */
    return --m_cRef;
    }

/*
 * CPatronFrame::GetWindow
 *
 * Purpose:
 *  Retrieves the handle of the window associated with the object
 *  on which this interface is implemented.
 *
 * Parameters:
 *  phWnd           HWND * in which to store the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_FAIL if there is no
 *                  window.
 */

STDMETHODIMP CPatronFrame::GetWindow(HWND *phWnd)
    {
    *phWnd=m_hWnd;
    return NOERROR;
    }

/*
 * CPatronFrame::ContextSensitiveHelp
 *
 * Purpose:
 *  Instructs the object on which this interface is implemented to
 *  enter or leave a context-sensitive help mode.
 *
 * Parameters:
 *  fEnterMode      BOOL TRUE to enter the mode, FALSE otherwise.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CPatronFrame::ContextSensitiveHelp(BOOL fEnterMode)
    {
    /*
     * Don't bother if there is no active object since we don't do
     * context help on our own.
     */
    if (NULL==m_pIOleIPActiveObject)
        return NOERROR;

    /*
     * If the state changes, an MDI frame should call the same
     * function in all documents.  An SDI frame should just call
     * the active object, if it has one.
     */

    if (m_fInContextHelp!=fEnterMode)

{
        m_fInContextHelp=fEnterMode;

       #ifdef MDI
        ((PCPatronClient)m_pCL)->CallContextHelpOnDocuments
           (fEnterMode);
       #else
        m_pIOleIPActiveObject->ContextSensitiveHelp(fEnterMode);
       #endif
        }

    return NOERROR;
    }

/*
 * CPatronFrame::GetBorder
 *
 * Purpose:
 *  Returns the rectangle in which the container is willing to
 *  negotiate about an object's adornments.
 *
 * Parameters:
 *  prcBorder       LPRECT in which to store the rectangle.
 *
 * Return Value:
 *  HRESULT         NOERROR if all is well, INPLACE_E_NOTOOLSPACE
 *                  if there is no negotiable space.
 */

STDMETHODIMP CPatronFrame::GetBorder(LPRECT prcBorder)
    {
    if (NULL==prcBorder)
        return ResultFromScode(E_INVALIDARG);

    /*
     * We return all the client area space sans the StatStrip,
     * which we control
     */
    GetClientRect(m_hWnd, prcBorder);
    prcBorder->bottom-=CYSTATSTRIP;

    return NOERROR;
    }

/*
 * CPatronFrame::RequestBorderSpace
 *
 * Purpose:
 *  Asks the container if it can surrender the amount of space
 *  in pBW that the object would like for it's adornments.  The
 *  container does nothing but validate the spaces on this call.
 *
 * Parameters:
 *  pBW             LPCBORDERWIDTHS containing the requested space.
 *                  The values are the amount of space requested
 *                  from each side of the relevant window.
 *
 * Return Value:
 *  HRESULT         NOERROR if we can give up space,
 *                  INPLACE_E_NOTOOLSPACE otherwise.
 */

STDMETHODIMP CPatronFrame::RequestBorderSpace(LPCBORDERWIDTHS pBW)
    {
    //Everything is fine with us, so always return an OK.
    return NOERROR;
    }

/*
 * CPatronFrame::SetBorderSpace
 *
 * Purpose:
 *  Called when the object now officially requests that the
 *  container surrender border space it previously allowed
 *  in RequestBorderSpace.  The container should resize windows
 *  appropriately to surrender this space.
 *
 * Parameters:
 *  pBW             LPCBORDERWIDTHS containing the amount of space
 *                  from each side of the relevant window that the
 *                  object is now reserving.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CPatronFrame::SetBorderSpace(LPCBORDERWIDTHS pBW)
    {
    RECT            rc;
    POINT           pt1, pt2;
    PCPatronDoc     pDoc;

    pDoc=(PCPatronDoc)m_pCL->ActiveDocument();

    /*
     * If pBW is NULL, the object is not interested in tools, so we
     * can just leave ours up if we want.
     */

    if (NULL==pBW)
        {
        if (NULL!=pDoc)
            {
            pDoc->NoObjectFrameTools(TRUE, TRUE);

            /*
             * In some cases IOleInPlaceSite::OnUIActivate might
             * have been called before SetBorderSpace, so we might
             * have already hidden our tools (OnUIActivate calls
             * pDoc->NoObjectFrameTools before we set it to TRUE
             * here).  So we have to insure they are visible now
             * by a call to ShowUIAndTools.
             */
            if (!m_fOurToolsShowing)
                ShowUIAndTools(TRUE, FALSE);
            }

        return NOERROR;
        }

    if (NULL!=pDoc)
        pDoc->NoObjectFrameTools(FALSE, TRUE);

    /*
     * This tells CFrame::FMessageHook (WM_SIZE) how much space
     * to reserve off the sides when resizing the client when
     * the frame is resized.
     */

    m_cyTop   =pBW->top;
    m_cyBottom=pBW->bottom;
    m_cxLeft  =pBW->left;
    m_cxRight =pBW->right;

    //Get the current offset of the client
    GetWindowRect(m_pCL->Window(), &rc);
    SETPOINT(pt1, rc.left, rc.top);
    SETPOINT(pt2, rc.right, rc.bottom);
    ScreenToClient(m_hWnd, &pt1);
    ScreenToClient(m_hWnd, &pt2);

    /*
     * Now move the client, keeping documents steady. pBW->left-pt.x
     * and pBW->top-pt.y are the deltas for the documents.
     */

    GetClientRect(m_hWnd, &rc);
    rc.left+=pBW->left;
    rc.right-=pBW->right;
    rc.top+=pBW->top;
    rc.bottom-=pBW->bottom+CYSTATSTRIP; //Remember the status line

    //Only bother the client if necessary.
    if (!(pt1.x==rc.left && pt1.y==rc.top
        && pt2.x==rc.right && pt2.y==rc.bottom))
        {
        ((PCPatronClient)m_pCL)->MoveWithoutFamily(&rc
            , pBW->left-pt1.x, pBW->top-pt1.y);
        }

    return NOERROR;
    }

/*
 * CPatronFrame::SetActiveObject
 *
 * Purpose:
 *  Provides the container with the object's IOleInPlaceActiveObject
 *  pointer
 *
 * Parameters:
 *  pIIPActiveObj   LPOLEINPLACEACTIVEOBJECT of interest.
 *  pszObj          LPCOLESTR naming the object.  Not used.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CPatronFrame::SetActiveObject
    (LPOLEINPLACEACTIVEOBJECT pIIPActiveObj, LPCOLESTR pszObj)
    {
    if (NULL!=m_pIOleIPActiveObject)
        m_pIOleIPActiveObject->Release();

    //NULLs m_pIOleIPActiveObject if pIIPActiveObj is NULL
    m_pIOleIPActiveObject=pIIPActiveObj;

    if (NULL!=m_pIOleIPActiveObject)
        m_pIOleIPActiveObject->AddRef();

    return NOERROR;
    }

/*
 * CPatronFrame::InsertMenus
 *
 * Purpose:
 *  Instructs the container to place its in-place menu items where
 *  necessary in the given menu and to fill in elements 0, 2, and 4
 *  of the OLEMENUGROUPWIDTHS array to indicate how many top-level
 *  items are in each group.
 *
 * Parameters:
 *  hMenu           HMENU in which to add popups.
 *  pMGW            LPOLEMENUGROUPWIDTHS in which to store the
 *                  width of each container menu group.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CPatronFrame::InsertMenus(HMENU hMenu
    , LPOLEMENUGROUPWIDTHS pMGW)
    {
    /*
     * Here Patron needs to copy it's File, Page, and Window menu
     * items into the object-supplied menu, meaning that we have
     * three menu groups.  The actual handles of these popup menus
     * are already in CPatronFrame::m_phMenu where File is element
     * 0 and Page is element 2, and in m_hMenuWindow which is the
     * Window menu.  The latter we do only for MDI, of course.
     */

    InsertMenu(hMenu, 0, MF_BYPOSITION | MF_POPUP, (UINT)m_phMenu[0]
        , PSZ(IDS_FILEMENU));
    InsertMenu(hMenu, 1, MF_BYPOSITION | MF_POPUP, (UINT)m_phMenu[2]
        , PSZ(IDS_PAGEMENU));

    pMGW->width[0]=1;
    pMGW->width[2]=1;

   #ifdef MDI
    InsertMenu(hMenu, 2, MF_BYPOSITION | MF_POPUP
       , (UINT)m_hMenuWindow, PSZ(IDS_WINDOWMENU));

    pMGW->width[4]=1;
   #else
    pMGW->width[4]=0;
   #endif

    return NOERROR;
    }

/*
 * CPatronFrame::SetMenu
 *
 * Purpose:
 *  Instructs the container to replace whatever menu it's currently
 *  using with the given menu and to call OleSetMenuDescritor so OLE
 *  knows to whom to dispatch messages.
 *
 * Parameters:
 *  hMenu           HMENU to show.
 *  hOLEMenu        HOLEMENU to the menu descriptor.
 *  hWndObj         HWND of the active object to which messages are
 *                  dispatched.
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CPatronFrame::SetMenu(HMENU hMenu
    , HOLEMENU hOLEMenu, HWND hWndObj)
    {
    HRESULT         hr;
    PCPatronClient  pCL=(PCPatronClient)m_pCL;

    /*
     * Our responsibilities here are to put the menu on the frame
     * window and call OleSetMenuDescriptor.
     * CPatronClient::SetMenu which we call here takes care of
     * MDI/SDI differences.
     *
     * We also want to save the object's hWnd for use in WM_SETFOCUS
     * processing.
     */

    if (NULL==hMenu)
        {
        m_hWndObj=NULL;

        //Prevent redundant calls, or debug warnings on startup.
        if (NULL==m_hMenuTop)
            return NOERROR;

        hMenu=m_hMenuTop;
        m_hMenuTop=NULL;
        }
    else
        {
        m_hMenuTop=m_hMenuOrg;
        m_hWndObj=hWndObj;
        }

    pCL->SetMenu(m_hWnd, hMenu, m_hMenuWindow);
    hr=OleSetMenuDescriptor(hOLEMenu, m_hWnd, hWndObj, NULL, NULL);
    return hr;
    }

/*
 * CPatronFrame::RemoveMenus
 *
 * Purpose:
 *  Asks the container to remove any menus it put into hMenu in
 *  InsertMenus.
 *
 * Parameters:
 *  hMenu           HMENU from which to remove the container's
 *                  items.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CPatronFrame::RemoveMenus(HMENU hMenu)
    {
    int         cItems, i, j;
    HMENU       hMenuT;

    /*
     * To be defensive, loop through this menu removing anything
     * we recognize (that is, anything in m_phMenu) just in case
     * the server didn't clean it up right.  At least we can
     * give ourselves the prophylactic benefit.
     */

    if (NULL==hMenu)
        return NOERROR;

    cItems=GetMenuItemCount(hMenu);

    /*
     * Walk backwards down the menu.  For each popup, see if it
     * matches any other popup we know about, and if so, remove
     * it from the shared menu.
     */
    for (i=cItems; i >=0; i--)
        {
        hMenuT=GetSubMenu(hMenu, i);

        for (j=0; j <= CMENUS; j++)
            {
            if (hMenuT==m_phMenu[j])
                RemoveMenu(hMenu, i, MF_BYPOSITION);
            }
        }

    //The menu should now be empty.
    return NOERROR;
    }

/*
 * CPatronFrame::SetStatusText
 *
 * Purpose:
 *  Asks the container to place some text in a status line, if one
 *  exists.  If the container does not have a status line it
 *  should return E_FAIL here in which case the object could
 *  display its own.
 *
 * Parameters:
 *  pszText         LPCOLESTR to display.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_TRUNCATED if not all
 *                  of the text could be displayed, or E_FAIL if
 *                  the container has no status line.
 */

STDMETHODIMP CPatronFrame::SetStatusText(LPCOLESTR pszText)
    {
    /*
     * Just send this to the StatStrip.  Unfortunately it won't tell
     * us about truncation.  Oh well, we'll just act like it worked.
     */
   #ifdef WIN32ANSI
    TCHAR       szTemp[256];

    WideCharToMultiByte(CP_ACP, 0, pszText, -1, szTemp, 256
        , NULL, NULL);
    m_pSL->MessageSet(szTemp);
   #else
    m_pSL->MessageSet((LPTSTR)pszText);
   #endif

    return NOERROR;
    }

/*
 * CPatronFrame::EnableModeless
 *
 * Purpose:
 *  Instructs the container to show or hide any modeless popup
 *  windows that it may be using.
 *
 * Parameters:
 *  fEnable         BOOL indicating to enable/show the windows
 *                  (TRUE) or to hide them (FALSE).
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CPatronFrame::EnableModeless(BOOL fEnable)
    {
    return NOERROR;
    }

/*
 * CPatronFrame::TranslateAccelerator
 *
 * Purpose:
 *  When dealing with an in-place object from an EXE server, this
 *  is called to give the container a chance to process accelerators
 *  after the server has looked at the message.
 *
 * Parameters:
 *  pMSG            LPMSG for the container to examine.
 *  wID             WORD the identifier in the container's
 *                  accelerator table (from IOleInPlaceSite
 *                  ::GetWindowContext) for this message (OLE does
 *                  some translation before calling).
 *
 * Return Value:
 *  HRESULT         NOERROR if the keystroke was used,
 *                  S_FALSE otherwise.
 */

STDMETHODIMP CPatronFrame::TranslateAccelerator(LPMSG pMSG, WORD wID)
    {
    SCODE       sc;

    /*
     * wID already has anything translated from m_hAccelIP for us,
     * so we can just check for the commands we want and process
     * them instead of calling TranslateAccelerator which would be
     * redundant and which also has a possibility of dispatching to
     * the wrong window.
     */
    if ((IDM_PAGENEWPAGE <= wID && IDM_PAGELASTPAGE >= wID)
        || IDM_OPENOBJECT==wID || IDM_ENTERCONTEXTHELP==wID
        || IDM_ESCAPECONTEXTHELP==wID)
        {
        //wID properly expands to 32-bits
        OnCommand(m_hWnd, (WPARAM)wID, 0L);
        sc=S_OK;
        }
   #ifdef MDI
    else if (TranslateMDISysAccel(m_pCL->Window(), pMSG))
        sc=S_OK;
   #endif
    else
        sc=S_FALSE;

    return ResultFromScode(sc);
    }


PATRON.H

/*
 * PATRON.H
 * Patron Chapter 24
 *
 * Single include file that pulls in everything needed for other
 * source files in the application.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _PATRON_H_
#define _PATRON_H_

#define INC_CLASSLIB
#define INC_OLEUI
//CHAPTER24MOD
#define INC_AUTOMATION
#define INC_CONTROLS
#define CHAPTER24
//End CHAPTER24MOD
#include <inole.h>
#include "resource.h"

//Get editor window information
#include "pages.h"

/*
 * UINT value such that adding one produces zero.  Portable to Win32.
 * This is used to represent a non-existent zero-based UINT value
 */
#define NOVALUE                     ((UINT)-1)

//PATRON.CPP:  Frame object that creates a main window

class CPatronFrame : public CFrame, public IOleInPlaceFrame
    {
    //For access to m_pCL for document creation
    friend class CLinkClassFactory;

    private:
        BOOL            m_fInitialized;     //OleInitialize worked
        LPCLASSFACTORY  m_pIClassDataTran;  //For locking.
        PCDocument      m_pDocCreated;      //What class factory makes
        BOOL            m_fEmbedding;       //-Embedding on command line?
        DWORD           m_dwRegCO;          //From CoRegisterClassObject
        LPCLASSFACTORY  m_pIClassFactory;

        DWORD           m_cRef;
        HMENU           m_hMenuOrg;         //Original menu
        HMENU           m_hMenuTop;
        HWND            m_hWndObj;          //Object window
        BOOL            m_fOurToolsShowing; //Our tools up?

        int             m_cyTop;            //In-place tool allocations
        int             m_cyBottom;
        int             m_cxLeft;
        int             m_cxRight;

        BOOL            m_fInContextHelp;   //In context help mode?

        LPOLEINPLACEACTIVEOBJECT    m_pIOleIPActiveObject;

    //This is for access from IOleInPlaceSite::GetWindowContext
    public:
        HACCEL          m_hAccelIP;         //Accelerators for in-place

    protected:
        //Overridable for creating a CPatronClient
        virtual PCClient    CreateCClient(void);

        virtual BOOL        FMessageHook(HWND, UINT, WPARAM, LPARAM
                                , LRESULT *);

        virtual BOOL        PreShowInit(void);
        virtual BOOL        RegisterAllClasses(void);
        virtual UINT        CreateToolbar(void);
        virtual LRESULT     OnCommand(HWND, WPARAM, LPARAM);

    public:
        CPatronFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CPatronFrame(void);

        //Overrides
        virtual BOOL        Init(PFRAMEINIT);
        virtual WPARAM      MessageLoop(void);
        virtual void        UpdateMenus(HMENU, UINT);
        virtual void        UpdateToolbar(void);

        void                ShowUIAndTools(BOOL, BOOL);
        void                ReinstateUI(void);

        //IOleInPlaceFrame implementation
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP         GetWindow(HWND *);
        STDMETHODIMP         ContextSensitiveHelp(BOOL);
        STDMETHODIMP         GetBorder(LPRECT);
        STDMETHODIMP         RequestBorderSpace(LPCBORDERWIDTHS);
        STDMETHODIMP         SetBorderSpace(LPCBORDERWIDTHS);
        STDMETHODIMP         SetActiveObject(LPOLEINPLACEACTIVEOBJECT
                                 , LPCOLESTR);
        STDMETHODIMP         InsertMenus(HMENU, LPOLEMENUGROUPWIDTHS);
        STDMETHODIMP         SetMenu(HMENU, HOLEMENU, HWND);
        STDMETHODIMP         RemoveMenus(HMENU);
        STDMETHODIMP         SetStatusText(LPCOLESTR);
        STDMETHODIMP         EnableModeless(BOOL);
        STDMETHODIMP         TranslateAccelerator(LPMSG, WORD);
    };

typedef CPatronFrame *PCPatronFrame;

//These are global for simplification of object implementation.
extern ULONG g_cObj;
extern ULONG g_cLock;
extern HWND  g_hWnd;

extern PCPatronFrame  g_pFR;

//Function for the object to notify on destruction.
void ObjectDestroyed(void);

//ICLASSF.CPP

class CLinkClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;
        PCPatronFrame   m_pFR;          //For document creation.

    public:
        CLinkClassFactory(PCPatronFrame);
        ~CLinkClassFactory(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP         CreateInstance(LPUNKNOWN, REFIID
                                 , PPVOID);
        STDMETHODIMP         LockServer(BOOL);
    };

typedef CLinkClassFactory *PCLinkClassFactory;

//CLIENT.CPP

/*
 * The only reason we have a derived class here is to override
 * CreateCDocument so we can create our own type as well as
 * overriding NewDocument to perform one other piece of work once
 * the document's been created.
 */

class CPatronClient : public CClient
    {
    protected:
        //Overridable for creating a new CDocument
        virtual PCDocument CreateCDocument(void);

    public:
        CPatronClient(HINSTANCE, PCFrame);
        virtual ~CPatronClient(void);

        //This is added to handle IOleInPlaceFrame::SetMenu
        void         SetMenu(HWND, HMENU, HMENU);
        void         MoveWithoutFamily(LPRECT, int, int);
        void         CallContextHelpOnDocuments(BOOL);
    };

typedef CPatronClient *PCPatronClient;

//DOCUMENT.CPP

//Constant ID for the pages window that lives in a document window
#define ID_PAGES            723

class CImpIPersistFile;
typedef class CImpIPersistFile *PCImpIPersistFile;
class CImpIOleItemContainer;
typedef class CImpIOleItemContainer *PCImpIOleItemContainer;
class CImpIOleInPlaceUIWindow;
typedef class CImpIOleInPlaceUIWindow *PCImpIOleInPlaceUIWindow;

/*
 * To support linking to embeddings, the document needs to have
 * IPersistFile and IOleItemContainer interfaces, so we multiply
 * inherit from IUnknown and provide two interface implementations.
 */

class CPatronDoc : public CDocument, public IUnknown
    {
    //These need access to FQueryPasteFromData, PasteFromData
    friend class CDropTarget;
    friend class CDropSource;
    friend class CImpIPersistFile;
    friend class CImpIOleItemContainer;
    friend class CImpIOleInPlaceUIWindow;

    protected:
        LONG            m_lVer;         //Loaded data version
        PCPages         m_pPG;          //Pages window in us
        LPSTORAGE       m_pIStorage;    //Root storage for document
        BOOL            m_fPrintSetup;

        class CDropTarget *m_pDropTarget;   //Registered target

        UINT            m_cfEmbeddedObject; //Clipboard formats
        UINT            m_cfObjectDescriptor;
        UINT            m_cfLinkSource;
        UINT            m_cfLinkSrcDescriptor;
        BOOL            m_fShowTypes;       //Show Objects active?

        ULONG               m_cRef;
        BOOL                m_fRename;          //Rename on Save?
        DWORD               m_dwRegROT;
        DWORD               m_dwRegROTWild;

        PCImpIPersistFile       m_pImpIPersistFile;
        PCImpIOleItemContainer  m_pImpIOleItemContainer;

        PCImpIOleInPlaceUIWindow m_pImpIOleIPUIWindow;  //Ours
        LPOLEINPLACEACTIVEOBJECT m_pIOleIPActiveObject; //Object's
        BOOL                     m_fNoObjectFrameTools;

        //CHAPTER24MOD
        BOOL                     m_fDesignMode;
        BOOL                     m_fUIDead;
        BOOL                     m_fHatchHandles;
        //End CHAPTER24MOD

    protected:
        virtual BOOL    FMessageHook(HWND, UINT, WPARAM, LPARAM
            , LRESULT *);

        BOOL            FQueryPasteFromData(LPDATAOBJECT
                            , LPFORMATETC, PTENANTTYPE);
        BOOL            FQueryPasteLinkFromData(LPDATAOBJECT
                            , LPFORMATETC, PTENANTTYPE);
        BOOL            PasteFromData(LPDATAOBJECT, LPFORMATETC
                            , TENANTTYPE, PPATRONOBJECT, DWORD
                            , BOOL);

    public:
        CPatronDoc(HINSTANCE, PCFrame, PCDocumentAdviseSink);
        virtual ~CPatronDoc(void);

        //We now need our ubiquitous set of IUnknown members.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        virtual BOOL    Init(PDOCUMENTINIT);
        virtual void    Clear(void);

        virtual BOOL    FDirtySet(BOOL);
        virtual BOOL    FDirtyGet(void);
        virtual void    Delete(void);
        virtual BOOL    FQueryPrinterSetup(void);
        virtual BOOL    FQueryObjectSelected(HMENU);

        virtual UINT    Load(BOOL, LPTSTR);
        virtual UINT    Save(UINT, LPTSTR);

        virtual BOOL    Print(HWND);
        virtual UINT    PrinterSetup(HWND, BOOL);

        virtual BOOL    Clip(HWND, BOOL);
        virtual BOOL    FQueryPaste(void);
        virtual BOOL    Paste(HWND);
        virtual BOOL    PasteSpecial(HWND);
        virtual BOOL    FQueryEnableEditLinks(void);
        virtual BOOL    EditLinks(HWND);
        virtual BOOL    ShowOrQueryObjectTypes(BOOL, BOOL);

        virtual UINT    NewPage(void);
        virtual UINT    DeletePage(void);
        virtual UINT    NextPage(void);
        virtual UINT    PreviousPage(void);
        virtual UINT    FirstPage(void);
        virtual UINT    LastPage(void);

        virtual void    Rename(LPTSTR);
        virtual BOOL    InsertObject(HWND);
        virtual void    ActivateObject(LONG, LPMSG);
        virtual BOOL    NoObjectFrameTools(BOOL, BOOL);
        virtual BOOL    ConvertObject(HWND);

        //CHAPTER24MOD
        virtual BOOL    FToggleOrQueryDesignMode(BOOL, BOOL);
        virtual BOOL    FToggleOrQueryUIDead(BOOL, BOOL);
        virtual BOOL    FToggleOrQueryHatchHandles(BOOL, BOOL);
        virtual BOOL    FQueryEnableEvents(void);
        virtual void    AssignEvents(HWND);
        virtual BOOL    TryMnemonic(LPMSG);
        //End CHAPTER24MOD
    };

typedef CPatronDoc *PCPatronDoc;

//Hook for Print Dialog to hide Setup... button
UINT CALLBACK PrintDlgHook(HWND, UINT, WPARAM, LPARAM);

//IPersistFile implementation for CPatronDoc

class CImpIPersistFile : public IPersistFile
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCPatronDoc         m_pDoc;      //Back pointer to the object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIPersistFile(PCPatronDoc, LPUNKNOWN);
        ~CImpIPersistFile(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP Load(LPCOLESTR, DWORD);
        STDMETHODIMP Save(LPCOLESTR, BOOL);
        STDMETHODIMP SaveCompleted(LPCOLESTR);
        STDMETHODIMP GetCurFile(LPOLESTR *);
    };

/*
 * IOleItemContainer implementation for both CPatronDoc and CPage,
 * therefore have two back pointers.
 */

class CImpIOleItemContainer : public IOleItemContainer
    {
    protected:
        ULONG               m_cRef;
        class CPage        *m_pPage;
        PCPatronDoc         m_pDoc;      //Convenient naming & types
        LPUNKNOWN           m_pUnkOuter;
        BOOL                m_fDoc;      //Document or page?

    private:
        BOOL         TenantFromName(LPOLESTR, PCTenant *);

    public:
        CImpIOleItemContainer(LPVOID, LPUNKNOWN, BOOL);
        ~CImpIOleItemContainer(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP ParseDisplayName(LPBC, LPOLESTR, ULONG *
            , LPMONIKER *);
        STDMETHODIMP EnumObjects(DWORD, LPENUMUNKNOWN *);
        STDMETHODIMP LockContainer(BOOL);
        STDMETHODIMP GetObject(LPOLESTR, DWORD, LPBINDCTX, REFIID
            , PPVOID);
        STDMETHODIMP GetObjectStorage(LPOLESTR, LPBINDCTX, REFIID
            , PPVOID);
        STDMETHODIMP IsRunning(LPOLESTR);
    };

//This goes on the document.

class CImpIOleInPlaceUIWindow : public IOleInPlaceUIWindow
    {
    protected:
        ULONG               m_cRef;
        PCPatronDoc         m_pDoc;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIOleInPlaceUIWindow(PCPatronDoc, LPUNKNOWN);
        ~CImpIOleInPlaceUIWindow(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetWindow(HWND *);
        STDMETHODIMP ContextSensitiveHelp(BOOL);
        STDMETHODIMP GetBorder(LPRECT);
        STDMETHODIMP RequestBorderSpace(LPCBORDERWIDTHS);
        STDMETHODIMP SetBorderSpace(LPCBORDERWIDTHS);
        STDMETHODIMP SetActiveObject(LPOLEINPLACEACTIVEOBJECT
                         , LPCOLESTR);
    };

//Drag-drop objects we need in the document

class CDropTarget : public IDropTarget
    {
    protected:
        ULONG               m_cRef;
        PCPatronDoc         m_pDoc;

        LPDATAOBJECT        m_pIDataObject;  //From DragEnter
        BOOL                m_fPendingRepaint;
        POINTL              m_ptPick;        //Pick-up offsets
        POINTL              m_ptLast;        //Last drag point
        SIZEL               m_szl;           //Object size
        BOOL                m_fFeedback;     //Draw feedback?
        FORMATETC           m_fe;            //Real dropping format

    public:
        CDropTarget(PCPatronDoc);
        ~CDropTarget(void);

        //IDropTarget interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP DragEnter(LPDATAOBJECT, DWORD, POINTL,LPDWORD);
        STDMETHODIMP DragOver(DWORD, POINTL, LPDWORD);
        STDMETHODIMP DragLeave(void);
        STDMETHODIMP Drop(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
    };

typedef CDropTarget *PCDropTarget;

class CDropSource : public IDropSource
    {
    protected:
        ULONG               m_cRef;

    public:
        CDropSource(void);
        ~CDropSource(void);

        //IDropSource interface members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP QueryContinueDrag(BOOL, DWORD);
        STDMETHODIMP GiveFeedback(DWORD);
    };

typedef CDropSource *PCDropSource;

#endif //_PATRON_H_


PRINT.CPP

/*
 * PRINT.CPP
 * Patron Chapter 24
 *
 * Implementation of printing functions for both CPatronDoc
 * and CPages classes.  These functions are here to keep clutter
 * down in document.cpp and pages.cpp.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

static HWND g_hDlgPrint=NULL;
static BOOL g_fCancelPrint=FALSE;

/*
 * CPatronDoc::Print
 *
 * Purpose:
 *  Prints the current document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *
 * Return Value:
 *  BOOL            TRUE if printing happened, FALSE if it didn't
 *                  start or didn't complete.
 */

BOOL CPatronDoc::Print(HWND hWndFrame)
    {
    PRINTDLG        pd;
    BOOL            fSuccess;

    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);
    pd.hwndOwner  =hWndFrame;
    pd.nCopies    =1;
    pd.nFromPage  =(USHORT)-1;
    pd.nToPage    =(USHORT)-1;
    pd.nMinPage   =1;
    pd.nMaxPage   =m_pPG->NumPagesGet();

    pd.lpfnPrintHook=PrintDlgHook;

    //Get the current document printer settings
    pd.hDevMode=m_pPG->DevModeGet();

    pd.Flags=PD_RETURNDC | PD_ALLPAGES | PD_COLLATE
        | PD_HIDEPRINTTOFILE | PD_NOSELECTION | PD_ENABLEPRINTHOOK;

    if (!PrintDlg(&pd))
        return FALSE;

    if (NULL!=pd.hDevMode)
        GlobalFree(pd.hDevMode);

    if (NULL!=pd.hDevNames)
        GlobalFree(pd.hDevNames);

    //Go do the actual printing.
    fSuccess=m_pPG->Print(pd.hDC, PSZ(IDS_DOCUMENTNAME), pd.Flags
        , pd.nFromPage, pd.nToPage, pd.nCopies);

    if (!fSuccess)
        {
        MessageBox(m_hWnd, PSZ(IDS_PRINTERROR)
            , PSZ(IDS_DOCUMENTCAPTION), MB_OK);
        }

    return fSuccess;
    }

/*
 * CPatronDoc::PrinterSetup
 *
 * Purpose:
 *  Selects a new printer and options for this document.
 *
 * Parameters:
 *  hWndFrame       HWND of the frame to use for dialog parents.
 *  fDefault        BOOL to avoid any dialog and just use the
 *                  default.
 *
 * Return Value:
 *  UINT            Undefined
 */

UINT CPatronDoc::PrinterSetup(HWND hWndFrame, BOOL fDefault)
    {
    PRINTDLG        pd;

    //Attempt to get printer metrics for the default printer.
    memset(&pd, 0, sizeof(PRINTDLG));
    pd.lStructSize=sizeof(PRINTDLG);

    if (fDefault)
        pd.Flags=PD_RETURNDEFAULT;
    else
        {
        pd.hwndOwner=hWndFrame;
        pd.Flags=PD_PRINTSETUP;

        //Get the current document printer settings
        pd.hDevMode=m_pPG->DevModeGet();
        }

    if (!PrintDlg(&pd))
        return FALSE;

    if (!m_pPG->DevModeSet(pd.hDevMode, pd.hDevNames))
        {
        GlobalFree(pd.hDevNames);
        GlobalFree(pd.hDevMode);
        return FALSE;
        }

    FDirtySet(TRUE);
    return 1;
    }

/*
 * PrintDlgHook
 *
 * Purpose:
 *  Callback hook for the Print Dialog so we can hide the Setup
 *  button.  Patron only allows Setup before anything exists on
 *  the page, and is not written to handle setup at Print time.
 */

UINT CALLBACK PrintDlgHook(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    if (WM_INITDIALOG==iMsg)
        {
        HWND        hWnd;

        hWnd=GetDlgItem(hDlg, psh1);
        ShowWindow(hWnd, SW_HIDE);
        return TRUE;
        }

    return FALSE;
    }

/*
 * CPatronDoc::FQueryPrinterSetup
 *
 * Purpose:
 *  Returns whether or not the Printer Setup menu item can be
 *  enabled.  Once you create a tenant in any page, Printer Setup
 *  is voided simply to keep this sample simple, that is, we don't
 *  have to worry about reorganizing potentially large amounts
 *  of layout after we start plopping down objects.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE to enable the menu, FALSE otherwise.
 */

BOOL CPatronDoc::FQueryPrinterSetup(void)
    {
    return m_fPrintSetup;
    }

/*
 * CPages::DevModeSet
 *
 * Purpose:
 *  Provides the Pages with the current printer information.
 *
 * Parameters:
 *  hDevMode        HGLOBAL to the memory containing the DEVMODE.
 *                  This function assumes responsibility for this
 *                  handle.
 *  hDevNames       HGLOBAL providing the driver name and device
 *                  name from which we can create a DC for
 *                  information.
 *
 * Return Value:
 *  BOOL            TRUE if we could accept this configuration,
 *                  FALSE otherwise.  If we return TRUE we also
 *                  delete the old memory we hold.
 */

BOOL CPages::DevModeSet(HGLOBAL hDevMode, HGLOBAL hDevNames)
    {
    LPDEVNAMES      pdn;
    LPTSTR          psz;
    HGLOBAL         hMem;
    PDEVICECONFIG   pdc;
    LPDEVMODE       pdm;
    LPSTREAM        pIStream;
    HRESULT         hr;
    ULONG           cbDevMode, cbWrite;
    BOOL            fRet=FALSE;

    if (NULL==hDevMode || NULL==hDevNames)
        return FALSE;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_WRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    /*
     * DEVMODE is variable length--total length in hDevMode, so the
     * amount to write is that plus string space.  We subtract
     * sizeof(DEVMODE) as that is already included from GlobalSize.
     */
    cbDevMode=GlobalSize(hDevMode);
    cbWrite=cbDevMode+sizeof(DEVICECONFIG)-sizeof(DEVMODE);

    hMem=GlobalAlloc(GHND, cbWrite);

    if (NULL==hMem)
        {
        pIStream->Release();
        return FALSE;
        }

    pdc=(PDEVICECONFIG)GlobalLock(hMem);    //This always works
    pdm=(LPDEVMODE)GlobalLock(hDevMode);    //This might not

    if (NULL!=pdm)
        {
        pdc->cb=cbWrite;
        pdc->cbDevMode=cbDevMode;
        memcpy(&pdc->dm, pdm, (int)cbDevMode);
        GlobalUnlock(hDevMode);

        psz=(LPTSTR)GlobalLock(hDevNames);

        if (NULL!=psz)
            {
            pdn=(LPDEVNAMES)psz;
            lstrcpy(pdc->szDriver, psz+pdn->wDriverOffset);
            lstrcpy(pdc->szDevice, psz+pdn->wDeviceOffset);
            lstrcpy(pdc->szPort,   psz+pdn->wOutputOffset);

            pIStream->Write(pdc, cbWrite, &cbWrite);
            GlobalUnlock(hDevNames);
            fRet=TRUE;
            }
        }

    GlobalUnlock(hMem);
    GlobalFree(hMem);

    pIStream->Release();

    if (!fRet)
        return FALSE;

    GlobalFree(hDevNames);
    GlobalFree(hDevMode);

    return ConfigureForDevice();
    }

/*
 * CPages::DevModeGet
 *
 * Purpose:
 *  Retrieves a copy of the current DEVMODE structure for this
 *  Pages window.  The caller is responsible for this memory.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HGLOBAL         Handle to the memory containing the DEVMODE
 *                  structure.
 */

HGLOBAL CPages::DevModeGet(void)
    {
    HGLOBAL         hMem;
    LPVOID          pv;
    ULONG           cbDevMode, cbRead;
    LARGE_INTEGER   li;
    LPSTREAM        pIStream;
    HRESULT         hr;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    //Read how much to allocate for the DEVMODE structure
    LISet32(li, CBSEEKOFFSETCBDEVMODE);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);
    pIStream->Read(&cbDevMode, sizeof(ULONG), &cbRead);

    hMem=GlobalAlloc(GHND, cbDevMode);

    if (NULL!=hMem)
        {
        pv=(LPVOID)GlobalLock(hMem);
        pIStream->Read(pv, cbDevMode, &cbRead);
        GlobalUnlock(hMem);
        }

    pIStream->Release();
    return hMem;
    }

/*
 * CPages::DevReadConfig
 *
 * Purpose:
 *  Public function to read the current device configuration and
 *  optionally return an information context for it.
 *
 *
 * Parameters:
 *  ppcd            PCOMBINEDEVICE * in which to return a pointer
 *                  to an allocated structure that has all the
 *                  device information we want.  Ignored if NULL.
 *                  This is allocated with the task allocator.
 *  phDC            HDC * in which to return the information
 *                  context.  If NULL, no IC is created.  Caller
 *                  becomes responsible for the returned IC.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::DevReadConfig(PCOMBINEDEVICE *ppcd, HDC *phDC)
    {
    HRESULT         hr;
    LPSTREAM        pIStream;
    LPMALLOC        pIMalloc;
    PCOMBINEDEVICE  pcd;
    ULONG           cb, cbRead;
    LARGE_INTEGER   li;

    hr=m_pIStorage->OpenStream(SZSTREAMDEVICECONFIG, 0, STGM_DIRECT
        | STGM_READ | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return FALSE;

    /*
     * Allocate the COMBINEDEVICE structure including the variable
     * information past the DEVMODE part.
     */

    hr=CoGetMalloc(MEMCTX_TASK, &pIMalloc);

    if (FAILED(hr))
        {
        pIStream->Release();
        return FALSE;
        }

    /*
     * Read size of the DEVICECONFIG structure including variable
     * portion of DEVMODE.  We need to load all this information
     * for CreateIC.  To this size we'll add the size of
     * DVTARGETDEVICE in order to allocate a COMBINEDEVICE.
     */

    pIStream->Read(&cb, sizeof(DWORD), &cbRead);

    pcd=(PCOMBINEDEVICE)pIMalloc->Alloc(cb+sizeof(DVTARGETDEVICE));

    if (NULL==pcd)
        {
        pIMalloc->Release();
        pIStream->Release();
        return FALSE;
        }

    //Now get the real information.
    LISet32(li, 0);
    pIStream->Seek(li, STREAM_SEEK_SET, NULL);
    pIStream->Read(&(pcd->dc), cb, &cbRead);
    pIStream->Release();

    /*
     * If requested, complete the DVTARGETDEVICE structure in
     * pcd and store pcd in *ppcd for return.
     */

    if (NULL!=ppcd)
        {
        WORD    cb=sizeof(DVTARGETDEVICE);

        pcd->td.tdSize=cb;
        pcd->td.tdExtDevmodeOffset=cb;
        pcd->td.tdDriverNameOffset=cb+sizeof(DEVMODE);
        pcd->td.tdDeviceNameOffset=cb+sizeof(DEVMODE)
            +(CCHDEVICENAME*sizeof(TCHAR));
        pcd->td.tdPortNameOffset  =cb+sizeof(DEVMODE)
            +(CCHDEVICENAME*2*sizeof(TCHAR));

        *ppcd=pcd;
        }

    //Create an IC if requested.
    if (NULL!=phDC)
        {
        //Get the DC then configure
        *phDC=CreateIC(pcd->dc.szDriver, pcd->dc.szDevice
            , pcd->dc.szPort, &(pcd->dc.dm));

        if (NULL==*phDC)
            return FALSE;
        }

    //pcd is a temporary allocation in this case
    if (NULL==ppcd)
        pIMalloc->Free(pcd);

    pIMalloc->Release();
    return (NULL!=*phDC);
    }

/*
 * CPages::ConfigureForDevice
 *
 * Purpose:
 *  Recalculates our drawing configuration based on the contents of
 *  an hDC.  If no HDC is given we use the contents of our DevMode
 *  stream.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CPages::ConfigureForDevice(void)
    {
    POINT           ptOffset, ptPaper;
    RECT            rc;
    HDC             hDC;
    CHourglass      hg;

    if (!DevReadConfig(NULL, &hDC))
        return FALSE;

    //Get usable page dimensions:  already sensitive to orientation
    m_cx=GetDeviceCaps(hDC, HORZSIZE)*10-16; //*10: mm to LOMETRIC
    m_cy=GetDeviceCaps(hDC, VERTSIZE)*10-16; //-16: for driver bugs.

    //Calculate the printer-limited margins on sides in LOMETRIC.
    Escape(hDC, GETPRINTINGOFFSET, NULL, NULL, &ptOffset);
    Escape(hDC, GETPHYSPAGESIZE,   NULL, NULL, &ptPaper);

    SetRect(&rc, ptOffset.x, ptOffset.y, ptPaper.x, ptPaper.y);
    SetMapMode(hDC, MM_LOMETRIC);
    RectConvertMappings(&rc, hDC, FALSE);

    //Left and top margins are the printing offset.
    m_xMarginLeft= rc.left+8;   //+8 to match -16 above
    m_yMarginTop =-rc.top+8;    //LOMETRIC makes this negative.

    //Right is (paper width)-(usable width)-(left margin)
    m_xMarginRight =rc.right-m_cx-m_xMarginLeft;

    //Bottom is (paper height)-(usable height)-(top margin)+1
    m_yMarginBottom=-rc.bottom-m_cy-m_yMarginTop+1;

    UpdateScrollRanges();

    DeleteDC(hDC);
    return TRUE;
    }

/*
 * CPages::Print
 *
 * Purpose:
 *  Prints a specified range of pages to a given hDC.  Repeats for
 *  a given number of copies.
 *
 * Parameters:
 *  hDC             HDC to which we print.
 *  pszDoc          LPTSTR providing the document name.
 *  dwFlags         DWORD flags from PrintDlg
 *  iPageStart      UINT starting page index (one based)
 *  iPageEnd        UINT ending page index (one based).  Includes
 *                  this page.
 *  cCopies         UINT number of copies to print.  If PD_COLLATE
 *                  in dwFlags is set, we print multiple copies of
 *                  each page as we cycle through.  Otherwise we
 *                  cycle multiple times.
 *
 * Return Value:
 *  None
 */

BOOL CPages::Print(HDC hDC, LPTSTR pszDoc, DWORD dwFlags
    , UINT iPageStart, UINT iPageEnd, UINT cCopies)
    {
    BOOL        fError=FALSE;
    int         iPage, iPageInc;
    int         iUserPage, cPages;
    UINT        iRepeat, cRepeat;
    UINT        iCycle, cCycles;
    UINT        iPageHold=m_iPageCur;
    HWND        hWndT, hWndTop=NULL;
    DOCINFO     di;
    PCDocument  pDoc;

    //Validate hDC and page ranges
    if (NULL==hDC)
        return FALSE;

    if ((PD_PAGENUMS & dwFlags))
        {
        if (-1==iPageStart)
            iPageStart=0;
        else
            iPageStart--;   //Switch to zero offset.

        if (-1==iPageEnd)
            iPageEnd=m_cPages-1;
        else
            iPageEnd--;     //Switch to zero offset.
        }
    else //Can't test PD_ALLPAGES with & since it's defined as 0L
        {
        iPageStart=0;
        iPageEnd=m_cPages-1;
        }

    //Arrange cycles and repeats depending on cCopies and collating
    if (PD_COLLATE & dwFlags)
        {
        cCycles=cCopies;
        cRepeat=1;
        }
    else
        {
        cCycles=1;
        cRepeat=cCopies;
        }

    //Disable the frame window to prevent reentrancy while printing.
    hWndT=GetParent(m_hWnd);
    pDoc=(PCDocument)SendMessage(hWndT, DOCM_PDOCUMENT, 0, 0L);

    if (NULL!=pDoc)
        {
        PCFrame pFR;

        pFR=pDoc->FrameGet();
        hWndTop=pFR->Window();
        EnableWindow(hWndTop, FALSE);
        }

    SetAbortProc(hDC, AbortProc);
    g_fCancelPrint=FALSE;

    //If these don't work then we'll just live without a dialog.
    g_hDlgPrint=CreateDialog(m_hInst, MAKEINTRESOURCE(IDD_PRINTING)
        , hWndTop, PrintDlgProc);

    //Increment for either direction.
    iPageInc=(iPageStart > iPageEnd) ? -1 : 1;

    //Initial entries in dialog box.
    cPages=1+((int)(iPageEnd-iPageStart)*iPageInc);

    SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE, 1, (LPARAM)cPages);
    SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, 1, (LPARAM)cRepeat);

    di.cbSize=sizeof(DOCINFO);
    di.lpszDocName=pszDoc;
    di.lpszOutput=NULL;

    if (StartDoc(hDC, &di) > 0)
        {
        /*
         * Iterate over the pages, repeating each page depending on
         * the copies we want and if we have collate enabled.
         */

        for (iCycle=1; iCycle <= cCycles; iCycle++)
            {
            if (PD_COLLATE & dwFlags)
                {
                SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE, iCycle
                    , (LPARAM)cCycles);
                }

            //iPageInc controls direction
            for (iPage=iPageStart; ; iPage+=iPageInc)
                {
                iUserPage=1+((iPage-(int)iPageStart)*iPageInc);

                SendMessage(g_hDlgPrint, PRINTM_PAGEUPDATE
                    , iUserPage, (LPARAM)cPages);

                m_iPageCur=iPage;   //We restore this later.

                for (iRepeat=1; iRepeat <= cRepeat; iRepeat++)
                    {
                    if (!(PD_COLLATE & dwFlags))
                        {
                        SendMessage(g_hDlgPrint, PRINTM_COPYUPDATE
                            , iRepeat, (LPARAM)cRepeat);
                        }

                    StartPage(hDC);
                    Draw(hDC, TRUE, TRUE);

                    if (EndPage(hDC) < 0)
                        fError=TRUE;

                    if (fError || g_fCancelPrint)
                        break;
                    }

                if (fError || g_fCancelPrint)
                    break;

                //If we just printed the last page, time to quit.
                if (iPage==(int)iPageEnd)
                    break;
                }

            if (fError || g_fCancelPrint)
                break;
            }

        if (!fError)
            EndDoc(hDC);
        else
            AbortDoc(hDC);
        }
    else
        fError=TRUE;

    //Set the page back to what it was before all this started.
    m_iPageCur=iPageHold;

    EnableWindow(hWndTop, TRUE);
    SetFocus(hWndTop);
    DestroyWindow(g_hDlgPrint);
    DeleteDC(hDC);
    return !fError;
    }

/*
 * AbortProc
 *
 * Purpose:
 *  Abort procedure for printing the pages.
 *
 * Parameters:
 *  hDC             HDC on which printing is happening.
 *  iErr            int error code.
 *
 * Return Value:
 *  BOOL            TRUE to continue the print job, FALSE otherwise.
 */

BOOL APIENTRY AbortProc(HDC hDC, int iErr)
    {
    MSG     msg;

    while (!g_fCancelPrint
        && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
        if (NULL==g_hDlgPrint
            || !IsDialogMessage(g_hDlgPrint, &msg))
            {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            }
        }

    return !g_fCancelPrint;
    }

/*
 * PrintDlgProc
 *
 * Purpose:
 *  Modeless dialog procedure for the dialog displayed while Patron
 *  is printing pages.
 */

BOOL APIENTRY PrintDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    TCHAR           szFormat[40];
    TCHAR           szOutput[80];

    switch (iMsg)
        {
        case WM_INITDIALOG:
            EnableMenuItem(GetSystemMenu(hDlg, FALSE), SC_CLOSE
                , MF_GRAYED);
            return TRUE;

        case WM_COMMAND:
            //Cancel button was pressed.
            g_fCancelPrint=TRUE;
            ShowWindow(hDlg, SW_HIDE);
            return TRUE;

        case PRINTM_PAGEUPDATE:
            GetDlgItemText(hDlg, ID_PAGESTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTPAGE, szOutput);
            return TRUE;

        case PRINTM_COPYUPDATE:
            GetDlgItemText(hDlg, ID_COPYSTRING, szFormat
                , sizeof(szFormat));
            wsprintf(szOutput, szFormat, wParam, (UINT)lParam);
            SetDlgItemText(hDlg, ID_CURRENTCOPY, szOutput);
            return TRUE;
        }

    return FALSE;
    }


RESOURCE.H

/*
 * RESOURCE.H
 * Patron Chapter 24
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Note that we augment classlib.h which we include before this.

//Position of Window menu.
#undef  WINDOW_MENU
#define WINDOW_MENU                 3

//Position of the "Object" item on the Edit menu.
#define MENUPOS_OBJECT              8
#define MENUPOS_OBJECTONPOPUP       5

//Popup menu
#define IDR_RIGHTPOPUPMENU          2

//In-place accelerators
#define IDR_INPLACEACCELERATORS     2
#define CINPLACEACCELERATORS        11  //See patron.rc

//Total number of menus
#undef  CMENUS
#ifdef MDI
#define CMENUS                      5
#else
#define CMENUS                      4
#endif

//Menu command identifiers.

#define IDM_FILEPRINT               (IDM_CUSTOMFILEMIN)
#define IDM_FILEPRINTERSETUP        (IDM_CUSTOMFILEMIN+1)

#define IDM_EDITPASTESPECIAL        (IDM_CUSTOMEDITMIN)
#define IDM_EDITDELETEOBJECT        (IDM_CUSTOMEDITMIN+1)
#define IDM_EDITINSERTOBJECT        (IDM_CUSTOMEDITMIN+2)
#define IDM_EDITOBJECT              (IDM_CUSTOMEDITMIN+3)
#define IDM_EDITCONVERT             (IDM_CUSTOMEDITMIN+4)
#define IDM_EDITLINKS               (IDM_CUSTOMEDITMIN+5)
//CHAPTER24MOD
#define IDM_EDITOBJECTEVENTS        (IDM_CUSTOMEDITMIN+6)
//End CHAPTER24MOD

#define IDM_PAGENEWPAGE             (IDM_CUSTOMMIN)
#define IDM_PAGEDELETEPAGE          (IDM_CUSTOMMIN+1)
#define IDM_PAGENEXTPAGE            (IDM_CUSTOMMIN+2)
#define IDM_PAGEPREVIOUSPAGE        (IDM_CUSTOMMIN+3)
#define IDM_PAGEFIRSTPAGE           (IDM_CUSTOMMIN+4)
#define IDM_PAGELASTPAGE            (IDM_CUSTOMMIN+5)
#define IDM_PAGESHOWOBJECTS         (IDM_CUSTOMMIN+6)
//CHAPTER24MOD
#define IDM_PAGEDESIGNMODE          (IDM_CUSTOMMIN+7)
#define IDM_PAGEUIDISABLED          (IDM_CUSTOMMIN+8)
#define IDM_PAGESHOWHATCHHANDLES    (IDM_CUSTOMMIN+9)
//End CHAPTER24MOD

//This special item is for the dynamic verbs on the Edit/Object item.
#define IDM_VERBMIN                 (IDM_CUSTOMMIN+100)
#define IDM_VERBMAX                 (IDM_CUSTOMMIN+200)

#define IDM_OPENOBJECT              (IDM_CUSTOMMIN+201)
#define IDM_ENTERCONTEXTHELP        (IDM_CUSTOMMIN+202)
#define IDM_ESCAPECONTEXTHELP       (IDM_CUSTOMMIN+203)

/*
 * IDs for StatStrip handling of popup menus:  must be in order of menu
 * CLASSRES.H already defines ID_MENUFILE and ID_MENUEDIT for us.
 */

#undef ID_MENUWINDOW
#undef ID_MENUHELP

#define ID_MENUPAGE                 (ID_MENUCUSTOMMIN+0)
#define ID_MENUWINDOW               (ID_MENUCUSTOMMIN+1)
#define ID_MENUHELP                 (ID_MENUCUSTOMMIN+2)

//Stringtable IDs.  Keep sequential for each group.
#define IDS_FRAMEMIN                IDS_STANDARDFRAMEMIN
#define IDS_FILEMENU                (IDS_CUSTOMFRAMEMIN+0)
#define IDS_PAGEMENU                (IDS_CUSTOMFRAMEMIN+1)
#define IDS_WINDOWMENU              (IDS_CUSTOMFRAMEMIN+2)
#define IDS_IPFRAMECAPTION          (IDS_CUSTOMFRAMEMIN+3)
#define IDS_FRAMEMAX                (IDS_CUSTOMFRAMEMIN+4)

#define IDS_DOCUMENTMIN             IDS_STANDARDDOCMIN
#define IDS_PRINTERROR              (IDS_CUSTOMDOCMIN+0)
#define IDS_DOCUMENTNAME            (IDS_CUSTOMDOCMIN+1)
#define IDS_PASTEASPATRON           (IDS_CUSTOMDOCMIN+2)
#define IDS_PASTEMETAFILE           (IDS_CUSTOMDOCMIN+3)
#define IDS_PASTEASMETAFILE         (IDS_CUSTOMDOCMIN+4)
#define IDS_PASTEDIB                (IDS_CUSTOMDOCMIN+5)
#define IDS_PASTEASDIB              (IDS_CUSTOMDOCMIN+6)
#define IDS_PASTEBITMAP             (IDS_CUSTOMDOCMIN+7)
#define IDS_PASTEASBITMAP           (IDS_CUSTOMDOCMIN+8)
#define IDS_PASTEOBJECT             (IDS_CUSTOMDOCMIN+9)
#define IDS_PASTEASOBJECT           (IDS_CUSTOMDOCMIN+10)
#define IDS_PASTELINK               (IDS_CUSTOMDOCMIN+11)
#define IDS_PASTEASLINK             (IDS_CUSTOMDOCMIN+12)
#define IDS_EXTENSION               (IDS_CUSTOMDOCMIN+13)
#define IDS_UNTITLEDDOC             (IDS_CUSTOMDOCMIN+14)
#define IDS_IPDOCCAPTION            (IDS_CUSTOMDOCMIN+15)
#define IDS_DOCUMENTMAX             (IDS_CUSTOMDOCMIN+15)

//These are specifically for the StatStrip
#define IDS_STATMESSAGEMIN                  IDS_STANDARDSTATMESSAGEMIN

#define IDS_ITEMMESSAGEFILEPRINT            (IDS_CUSTOMSTATMESSAGEMIN+0)
#define IDS_ITEMMESSAGEFILEPRINTERSETUP     (IDS_CUSTOMSTATMESSAGEMIN+1)

#define IDS_MENUMESSAGEPAGE                 (IDS_CUSTOMSTATMESSAGEMIN+2)
#define IDS_ITEMMESSAGEPAGENEWPAGE          (IDS_CUSTOMSTATMESSAGEMIN+3)
#define IDS_ITEMMESSAGEPAGEDELETEPAGE       (IDS_CUSTOMSTATMESSAGEMIN+4)
#define IDS_ITEMMESSAGEPAGENEXTPAGE         (IDS_CUSTOMSTATMESSAGEMIN+5)
#define IDS_ITEMMESSAGEPAGEPREVIOUSPAGE     (IDS_CUSTOMSTATMESSAGEMIN+6)
#define IDS_ITEMMESSAGEPAGEFIRSTPAGE        (IDS_CUSTOMSTATMESSAGEMIN+7)
#define IDS_ITEMMESSAGEPAGELASTPAGE         (IDS_CUSTOMSTATMESSAGEMIN+8)
#define IDS_ITEMMESSAGEEDITPASTESPECIAL     (IDS_CUSTOMSTATMESSAGEMIN+9)
#define IDS_ITEMMESSAGEEDITDELETEOBJECT     (IDS_CUSTOMSTATMESSAGEMIN+10)
#define IDS_ITEMMESSAGEEDITINSERTOBJECT     (IDS_CUSTOMSTATMESSAGEMIN+11)
#define IDS_ITEMMESSAGEEDITOBJECT           (IDS_CUSTOMSTATMESSAGEMIN+12)
#define IDS_ITEMMESSAGEEDITCONVERT          (IDS_CUSTOMSTATMESSAGEMIN+13)
#define IDS_ITEMMESSAGEEDITLINKS            (IDS_CUSTOMSTATMESSAGEMIN+14)
#define IDS_ITEMMESSAGEPAGESHOWOBJECTS      (IDS_CUSTOMSTATMESSAGEMIN+15)
//CHAPTER24MOD
#define IDS_ITEMMESSAGEEDITOBJECTEVENTS     (IDS_CUSTOMSTATMESSAGEMIN+16)
#define IDS_ITEMMESSAGEPAGEDESIGNMODE       (IDS_CUSTOMSTATMESSAGEMIN+17)
#define IDS_ITEMMESSAGEPAGEUIDISABLED       (IDS_CUSTOMSTATMESSAGEMIN+18)
#define IDS_ITEMMESSAGEPAGESHOWHATCHHANDLES (IDS_CUSTOMSTATMESSAGEMIN+19)
#define IDS_STATMESSAGEMAX                  (IDS_CUSTOMSTATMESSAGEMIN+19)
//End CHAPTER24MOD

//Definitions for the printing dialog.

#define IDD_PRINTING                2

#define ID_CURRENTPAGE              101
#define ID_CURRENTCOPY              102
#define ID_COPYSTRING               103
#define ID_PAGESTRING               104

#define PRINTM_PAGEUPDATE           (WM_USER+100)
#define PRINTM_COPYUPDATE           (WM_USER+101)

//CHAPTER24MOD
//Definitions for the Control Events dialog

#define IDD_EVENTS                      101

#define ID_TEST                         2
#define IDC_EVENTLIST                   1000
#define IDC_ACTIONSSFRAME               1001
#define IDC_BEEPNONE                    1002
#define IDC_BEEPDEFAULT                 1003
#define IDC_BEEPEXCLAMATION             1004
#define IDC_BEEPASTERISK                1005
#define IDC_BEEPHAND                    1006
#define IDC_BEEPQUESTION                1007
#define IDC_EVENTSFRAME                 -1

//End CHAPTER24MOD

#endif //_RESOURCE_H_


TENANT.CPP

/*
 * TENANT.CPP
 * Patron Chapter 24
 *
 * Implementation of the CTentant class which holds information
 * for a single object on a page.  It maintains position, references
 * to data, and a storage.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "patron.h"

/*
 * CTenant::CTenant
 * CTenant::~CTenant
 *
 * Constructor Parameters:
 *  dwID            DWORD identifier for this page.
 *  hWnd            HWND of the pages window.
 *  pPG             PCPages to the parent structure.
 */

CTenant::CTenant(DWORD dwID, HWND hWnd, PCPages pPG)
    {
    m_hWnd=hWnd;
    m_dwID=dwID;

    m_fInitialized=0;
    m_pIStorage=NULL;
    m_cOpens=0;

    m_pObj=NULL;
    m_pPG =pPG;
    m_clsID=CLSID_NULL;
    m_fSetExtent=FALSE;

    m_cRef=0;
    m_pIOleObject=NULL;
    m_pIViewObject2=NULL;
    m_grfMisc=0;

    m_pImpIOleClientSite=NULL;
    m_pImpIAdviseSink=NULL;

    m_fRepaintEnabled=TRUE;
    m_pmkFile=NULL;
    m_fLinkAvail=TRUE;          //Checked on Load
    m_pmk=NULL;

    m_pImpIOleIPSite=NULL;
    m_pIOleIPObject=NULL;
    m_rcPos.left=-1;
    m_fInRectSet=FALSE;

    //CHAPTER24MOD
    m_pImpIOleControlSite=NULL;
    m_pImpIDispatch=NULL;

    m_pDispEvents=NULL;
    m_dwConnEvents=0L;
    m_iidEvents=GUID_NULL;
    m_pEventMap=NULL;

    m_pIOleControl=NULL;
    m_pIDispatchControl=NULL;

    //0x80000000 in OLE_COLOR indicates low byte is color index.
    m_clrBack=0x80000000+COLOR_WINDOW;
    m_clrFore=0x80000000+COLOR_WINDOWTEXT;

    m_pIFont=m_pPG->m_pIFont;
    m_lcid=LOCALE_USER_DEFAULT;
    m_fDesignMode=m_pPG->m_fDesignMode;
    m_fUIDead=m_pPG->m_fUIDead;
    m_fHatchHandles=m_pPG->m_fHatchHandles;

    m_fHaveControlInfo=FALSE;
    m_cLockInPlace=0;
    m_fPendingDeactivate=FALSE;
    //End CHAPTER24MOD

    return;
    }

CTenant::~CTenant(void)
    {
    ReleaseInterface(m_pmk);
    ReleaseInterface(m_pmkFile);

    //Object pointers cleaned up in Close.

    //CHAPTER24MOD
    if (NULL!=m_pEventMap)
        delete m_pEventMap;

    DeleteInterfaceImp(m_pImpIOleControlSite);
    DeleteInterfaceImp(m_pImpIDispatch);

    if (NULL!=m_pDispEvents)
        delete m_pDispEvents;
    //End CHAPTER24MOD

    DeleteInterfaceImp(m_pImpIOleIPSite);
    DeleteInterfaceImp(m_pImpIAdviseSink);
    DeleteInterfaceImp(m_pImpIOleClientSite);
    return;
    }

/*
 * CTenant::QueryInterface
 * CTenant::AddRef
 * CTenant::Release
 *
 * Purpose:
 *  IUnknown members for CTenant object.
 */

STDMETHODIMP CTenant::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IOleClientSite==riid)
        *ppv=m_pImpIOleClientSite;

    if (IID_IAdviseSink==riid)
        *ppv=m_pImpIAdviseSink;

    if (IID_IOleWindow==riid || IID_IOleInPlaceSite==riid)
        *ppv=m_pImpIOleIPSite;

    //CHAPTER24MOD
    if (IID_IOleControlSite==riid)
        *ppv=m_pImpIOleControlSite;

    //Queries for IDispatch return the ambient properties interface
    if (IID_IDispatch==riid)
        *ppv=m_pImpIDispatch;
    //End CHAPTER24MOD

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CTenant::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CTenant::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CTenant::GetID
 *
 * Return Value:
 *  DWORD           dwID field in this tenant.
 */

DWORD CTenant::GetID(void)
    {
    return m_dwID;
    }

/*
 * CTenant::GetStorageName
 *
 * Parameters:
 *  pszName         LPOLESTR to a buffer in which to store the storage
 *                  name for this tenant.
 *
 * Return Value:
 *  UINT            Number of characters stored.
 */

UINT CTenant::GetStorageName(LPOLESTR pszName)
    {
   #ifdef WIN32ANSI
    char        szTemp[32];
    UINT        cch;

    cch=wsprintf(szTemp, "Tenant %lu", m_dwID);
    MultiByteToWideChar(CP_ACP, 0, szTemp, -1, pszName, 32);
    return cch;
   #else
    return wsprintf(pszName, TEXT("Tenant %lu"), m_dwID);
   #endif
    }

/*
 * CTenant::StorageGet
 *
 * Purpose:
 *  Returns the IStorage pointer maintained by this tenant,
 *  AddRef'd of course.
 *
 * Parameters:
 *  ppStg           LPSTORAGE * in which to return the pointer.
 *
 * Return Value:
 *  None
 */

void CTenant::StorageGet(LPSTORAGE *ppStg)
    {
    if (NULL==ppStg)
        return;

    *ppStg=m_pIStorage;

    if (NULL!=*ppStg)
        (*ppStg)->AddRef();

    return;
    }

/*
 * CTenant::Create
 *
 * Purpose:
 *  Creates a new tenant of the given CLSID, which can be either a
 *  static bitmap or metafile or any compound document object.
 *
 * Parameters:
 *  tType           TENANTTYPE to create, either a static metafile,
 *                  bitmap, or some kind of compound document object
 *                  This determines which OleCreate* call we use.
 *  pvType          LPVOID providing the relevant pointer from which
 *                  to create the tenant, depending on iType.
 *  pFE             LPFORMATETC specifying the type of renderings
 *                  to use.
 *  pptl            PPOINTL in which we store offset coordinates.
 *  pszl            LPSIZEL where this object should store its
 *                  lometric extents.
 *  pIStorage       LPSTORAGE of the page we live in.  We have to
 *                  create another storage in this for the tenant.
 *  ppo             PPATRONOBJECT containing placement data.
 *  dwData          DWORD with extra data, sensitive to iType.
 *
 * Return Value:
 *  UINT            A CREATE_* value depending on what we
 *                  actually do.
 */

UINT CTenant::Create(TENANTTYPE tType, LPVOID pvType
    , LPFORMATETC pFE, PPOINTL pptl, LPSIZEL pszl
    , LPSTORAGE pIStorage, PPATRONOBJECT ppo, DWORD dwData)
    {
    HRESULT             hr;
    LPUNKNOWN           pObj;
    UINT                uRet=CREATE_GRAPHICONLY;

    if (NULL==pvType || NULL==pIStorage)
        return CREATE_FAILED;

    //Fail if this is called for an already living tenant.
    if (m_fInitialized)
        return CREATE_FAILED;

    m_fInitialized=TRUE;

    //Create a new storage for this tenant.
    if (!Open(pIStorage))
        return CREATE_FAILED;

    /*
     * Get the placement info if it's here.  We either have a non-
     * NULL PPATRONOBJECT in ppo or we have to use default
     * placement and retrieve the size from the object itself.
     */
    pszl->cx=0;
    pszl->cy=0;

    if (NULL!=ppo)
        {
        *pFE=ppo->fe;
        *pptl=ppo->ptl;
        *pszl=ppo->szl;     //Could be 0,0 , so we ask object

        uRet=CREATE_PLACEDOBJECT;
        }

    hr=ResultFromScode(E_FAIL);

    //Now create an object based specifically for the type.
    switch (tType)
        {
        case TENANTTYPE_NULL:
            break;

        case TENANTTYPE_STATIC:
            /*
             * We could use OleCreateStaticFromData here which does
             * pretty much what we're doing below.  However, it does
             * not allow us to control whether we paste a bitmap or
             * a metafile--it uses metafile first, bitmap second.
             * For this reason we'll use code developed in Chapter
             * 11's FreeLoader to affect the paste.
             */
            hr=CreateStatic((LPDATAOBJECT)pvType, pFE, &pObj);
            break;

        case TENANTTYPE_EMBEDDEDOBJECT:
            //CHAPTER24MOD
            /*
             * The OLE Control specifications mention that a
             * a control might implement IPersistStream[Init]
             * instead of IPersistStorage.  In that case you
             * cannot use OleCreate on a control but must rather
             * use CoCreateInstance since OleCreate assumes
             * that IPersistStorage is available.  With a control,
             * you would have to create the object first, then
             * check if OLEMISC_SETCLIENTSITEFIRST is set, then
             * send it your IOleClientSite first.  Then you check
             * for IPersistStorage and failing that, try
             * IPersistStream[Init].
             *
             * For simplicity we'll assume storage-based
             * controls in this sample.
             */
            //End CHAPTER24MOD

            hr=OleCreate(*((LPCLSID)pvType), IID_IUnknown
                , OLERENDER_DRAW, NULL, NULL, m_pIStorage
                , (PPVOID)&pObj);
            break;

        case TENANTTYPE_EMBEDDEDFILE:
            hr=OleCreateFromFile(CLSID_NULL, (LPTSTR)pvType
                , IID_IUnknown, OLERENDER_DRAW, NULL, NULL
                , m_pIStorage, (PPVOID)&pObj);
            break;

        case TENANTTYPE_EMBEDDEDOBJECTFROMDATA:
            hr=OleCreateFromData((LPDATAOBJECT)pvType, IID_IUnknown
                , OLERENDER_DRAW, NULL, NULL, m_pIStorage
                , (PPVOID)&pObj);
            break;

        case TENANTTYPE_LINKEDFILE:
            hr=OleCreateLinkToFile((LPTSTR)pvType, IID_IUnknown
                , OLERENDER_DRAW, NULL, NULL, m_pIStorage
                , (PPVOID)&pObj);
            break;

        case TENANTTYPE_LINKEDOBJECTFROMDATA:
            hr=OleCreateLinkFromData((LPDATAOBJECT)pvType
                , IID_IUnknown, OLERENDER_DRAW, NULL, NULL
                , m_pIStorage, (PPVOID)&pObj);
            break;

        default:
            break;
        }

    //If creation didn't work, get rid of the element Open created.
    if (FAILED(hr))
        {
        Destroy(pIStorage);
        return CREATE_FAILED;
        }

    //We don't get the size if PatronObject data was seen already.
    if (!ObjectInitialize(pObj, pFE, dwData))
        {
        Destroy(pIStorage);
        return CREATE_FAILED;
        }

    if (0==pszl->cx && 0==pszl->cy)
        {
        SIZEL   szl;

        //Try to get the real size of the object, default to 2"*2"
        SETSIZEL((*pszl), 2*LOMETRIC_PER_INCH, 2*LOMETRIC_PER_INCH);
        hr=ResultFromScode(E_FAIL);

        //Try IViewObject2 first, then IOleObject as a backup.
        if (NULL!=m_pIViewObject2)
            {
            hr=m_pIViewObject2->GetExtent(m_fe.dwAspect, -1, NULL
                , &szl);
            }
        else
            {
            if (NULL!=m_pIOleObject)
                hr=m_pIOleObject->GetExtent(m_fe.dwAspect, &szl);
            }

        if (SUCCEEDED(hr))
            {
            //Convert HIMETRIC to our LOMETRIC mapping
            SETSIZEL((*pszl), szl.cx/10, szl.cy/10);
            }
        }

    //CHAPTER24MOD
    //Make sure this happens
    if ((OLEMISC_ACTIVATEWHENVISIBLE & m_grfMisc) && !m_fDesignMode)
        Activate(OLEIVERB_INPLACEACTIVATE, NULL);
    //End CHAPTER24MOD

    return uRet;
    }

/*
 * CTenant::Load
 *
 * Purpose:
 *  Recreates the object living in this tenant in place of calling
 *  FCreate.  This is used in loading as opposed to new creation.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE of the page we live in.
 *  pti             PTENTANTINFO containing persistent information.
 *                  The ID value in this structure is ignored.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CTenant::Load(LPSTORAGE pIStorage, PTENANTINFO pti)
    {
    HRESULT         hr;
    LPUNKNOWN       pObj;
    DWORD           dwState=TENANTSTATE_DEFAULT;

    if (NULL==pIStorage || NULL==pti)
        return FALSE;

    /*
     * If we already initialized once, clean up, releasing
     * everything before we attempt to reload.  This happens
     * when using the Convert Dialog.
     */

    if (m_fInitialized)
        {
        //Preserve all states except open
        dwState=(m_dwState & ~TENANTSTATE_OPEN);
        m_cRef++;   //Prevent accidental closure

        //This should release all holds on our IStorage as well.
        if (NULL!=m_pIViewObject2)
            {
            m_pIViewObject2->SetAdvise(m_fe.dwAspect, 0, NULL);
            ReleaseInterface(m_pIViewObject2);
            }

        ReleaseInterface(m_pIOleObject);
        ReleaseInterface(m_pObj);

        m_pIStorage=NULL;   //We'll have already released this.
        m_cRef--;           //Match safety increment above.
        }

    m_fInitialized=TRUE;

    //Open the storage for this tenant.
    if (!Open(pIStorage))
        return FALSE;

    /*
     * NOTE:  If you do not pass an IOleClientSite to OleLoad
     * it will not automatically reconnect a linked object to
     * the running source via IOleLink::BindIfRunning.  Since
     * we do not pass m_pImpIOleClientSite here, we'll call
     * BindIfRunning ourselves in ObjectInitialize.
     */

    //CHAPTER24MOD
    /*
     * As with creation, OleLoad doesn't work with anything
     * other than storage-based objects, and doesn't pay
     * attention to OLEMISC_SETCLIENTSITEFIRST.  For
     * simplicity, this sample only works with storage-based
     * controls.
     */
    //End CHAPTER24MOD

    hr=OleLoad(m_pIStorage, IID_IUnknown, NULL, (PPVOID)&pObj);

    if (FAILED(hr))
        {
        Destroy(pIStorage);
        return FALSE;
        }

    m_fSetExtent=pti->fSetExtent;
    ObjectInitialize(pObj, &pti->fe, NULL);

    //Restore the original state before reloading.
    //CHAPTER24MOD
    m_dwState=(m_dwState & STATEMASK_CONTROLS) | dwState;
    //End CHAPTER24MOD

    RectSet(&pti->rcl, FALSE, FALSE);

    /*
     * If the object is ActiveWhenVisible, send it the
     * OLEIVERB_INPLACEACTIVATE verb now unless we're
     * in design mode, then don't send at all.
     */

    //CHAPTER24MOD
    if ((OLEMISC_ACTIVATEWHENVISIBLE & m_grfMisc) && !m_fDesignMode)
        Activate(OLEIVERB_INPLACEACTIVATE, NULL);
    //End CHAPTER24MOD

    return TRUE;
    }

/*
 * CTenant::GetInfo
 *
 * Purpose:
 *  Retrieved a TENANTINFO structure for this tenant.
 *
 * Parameters:
 *  pti             PTENANTINFO structure to fill
 *
 * Return Value:
 *  None
 */

void CTenant::GetInfo(PTENANTINFO pti)
    {
    if (NULL!=pti)
        {
        pti->dwID=m_dwID;
        pti->rcl=m_rcl;
        pti->fe=m_fe;
        pti->fSetExtent=m_fSetExtent;
        }

    return;
    }

/*
 * CTenant::ObjectInitialize
 * (Protected)
 *
 * Purpose:
 *  Performs operations necessary after creating an object or
 *  reloading one from storage.
 *
 * Parameters:
 *  pObj            LPUNKNOWN of the object in this tenant.
 *  pFE             LPFORMATETC describing the graphic here.
 *  dwData          DWORD extra data.  If pFE->dwAspect==
 *                  DVASPECT_ICON then this is the iconic metafile.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CTenant::ObjectInitialize(LPUNKNOWN pObj, LPFORMATETC pFE
    , DWORD dwData)
    {
    HRESULT         hr;
    LPPERSIST       pIPersist=NULL;
    DWORD           dw;
    PCDocument      pDoc;
    TCHAR           szFile[CCHPATHMAX];
    LPOLELINK       pIOleLink=NULL;

    if (NULL==pObj || NULL==pFE)
        return FALSE;

    m_pObj=pObj;
    m_fe=*pFE;
    m_fe.ptd=NULL;
    m_dwState=TENANTSTATE_DEFAULT;

    /*
     * Determine the type:  Static or Embedded.  If Static,
     * this will have CLSID_Picture_Metafile or CLSID_Picture_Dib.
     * Otherwise it's embedded.  Later we'll add a case for links.
     */
    m_tType=TENANTTYPE_EMBEDDEDOBJECT;

    if (SUCCEEDED(pObj->QueryInterface(IID_IPersist
        , (PPVOID)&pIPersist)))
        {
        CLSID   clsid=CLSID_NULL;

        hr=pIPersist->GetClassID(&clsid);

        //If we don't have a CLSID, default to static
        if (FAILED(hr) || CLSID_Picture_Metafile==clsid
            || CLSID_Picture_Dib==clsid)
            m_tType=TENANTTYPE_STATIC;

        pIPersist->Release();
        }

    //Check if this is a linked object.
    if (SUCCEEDED(pObj->QueryInterface(IID_IOleLink
        , (PPVOID)&pIOleLink)))
        {
        LPMONIKER   pmk;

        hr=pIOleLink->GetSourceMoniker(&pmk);

        if (FAILED(hr) || NULL==pmk)
            m_tType=TENANTTYPE_STATIC;
        else
            {
            m_tType=TENANTTYPE_LINKEDOBJECT;
            pmk->Release();

            //Connect to the object if the source is running.
            pIOleLink->BindIfRunning();
            }

        pIOleLink->Release();
        }

    m_pIViewObject2=NULL;
    hr=pObj->QueryInterface(IID_IViewObject2
        , (PPVOID)&m_pIViewObject2);

    if (FAILED(hr))
        return FALSE;

    m_pIViewObject2->SetAdvise(m_fe.dwAspect, 0, m_pImpIAdviseSink);

    //We need an IOleObject most of the time, so get one here.
    m_pIOleObject=NULL;
    hr=pObj->QueryInterface(IID_IOleObject
         , (PPVOID)&m_pIOleObject);

    /*
     * Follow up object creation with advises and so forth.  If
     * we cannot get IOleObject here, then we know we can't do
     * any IOleObject actions from here on--object is static.
     */
    if (FAILED(hr))
        return TRUE;

    /*
     * Get the MiscStatus bits and check for OLEMISC_ONLYICONIC.
     * If set, force dwAspect in m_fe to DVASPECT_ICON so we
     * remember to draw it properly and do extents right.
     */
    m_pIOleObject->GetMiscStatus(m_fe.dwAspect, &m_grfMisc);

    //CHAPTER24MOD
    //Run the object if it says to do so
    if (OLEMISC_ALWAYSRUN & m_grfMisc)
        OleRun(pObj);
    //End CHAPTER24MOD

    if (OLEMISC_ONLYICONIC & m_grfMisc)
        m_fe.dwAspect=DVASPECT_ICON;

    /*
     * We could pass m_pImpIOleClientSite in an OleCreate* call, but
     * since this function could be called after OleLoad, we still
     * need to do this here, so it's always done here...
     */
    m_pIOleObject->SetClientSite(m_pImpIOleClientSite);
    m_pIOleObject->Advise(m_pImpIAdviseSink, &dw);

    OleSetContainedObject(m_pIOleObject, TRUE);

    /*
     * For IOleObject::SetHostNames we need the application name
     * and the document name (which is passed in the object
     * parameter).  The design of Patron doesn't give us nice
     * structured access to the name of the document we're in, so
     * I grab the parent of the Pages window (the document) and
     * send it DOCM_PDOCUMENT which returns us the pointer.
     * Roundabout, but it works.
     */

    pDoc=(PCDocument)SendMessage(GetParent(m_hWnd), DOCM_PDOCUMENT
        , 0, 0L);

    if (NULL!=pDoc)
        pDoc->FilenameGet(szFile, CCHPATHMAX);
    else
        szFile[0]=0;

    NotifyOfRename(szFile, NULL, NULL);

    /*
     * If we're creating an iconic aspect object and we have
     * an object from the Insert Object dialog, then we need to
     * store that iconic presentation in the cache, handled
     * with the utility function INOLE_SwitchDisplayAspect.  In
     * this case dwData is a handle to the metafile containing
     * the icon.  If dwData is NULL then we depend on the
     * server to provide the aspect, in which case we need
     * a view advise.
     */

    if (DVASPECT_ICON & m_fe.dwAspect)
        {
        DWORD           dw=DVASPECT_CONTENT;
        IAdviseSink    *pSink;

        pSink=(NULL==dwData) ? NULL : m_pImpIAdviseSink;

        INOLE_SwitchDisplayAspect(m_pIOleObject, &dw
            , DVASPECT_ICON, (HGLOBAL)(UINT)dwData, FALSE
            , (NULL!=dwData), pSink, NULL);
        }

    //CHAPTER24MOD
    //Go try initializing control-related things.
    ControlInitialize();
    //End CHAPTER24MOD

    return TRUE;
    }

/*
 * CTenant::Open
 *
 * Purpose:
 *  Retrieves the IStorage associated with this tenant.  The
 *  IStorage is owned by the tenant and thus the tenant always
 *  holds a reference count.
 *
 *  If the storage is already open for this tenant, then this
 *  function will AddRef it; therefore the caller must always
 *  match an Open with a Close.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE above this tenant (which has its
 *                  own storage).
 *
 * Return Value:
 *  BOOL            TRUE if opening succeeds, FALSE otherwise.
 */

BOOL CTenant::Open(LPSTORAGE pIStorage)
    {
    HRESULT     hr=NOERROR;
    DWORD       dwMode=STGM_TRANSACTED | STGM_READWRITE
                    | STGM_SHARE_EXCLUSIVE;
    OLECHAR     szTemp[32];

    if (NULL==m_pIStorage)
        {
        if (NULL==pIStorage)
            return FALSE;

        /*
         * Attempt to open the storage under this ID.  If there is
         * none, then create it.  In either case we end up with an
         * IStorage that we either save in pPage or release.
         */

        GetStorageName(szTemp);
        hr=pIStorage->OpenStorage(szTemp, NULL, dwMode, NULL, 0
            , &m_pIStorage);

        if (FAILED(hr))
            {
            hr=pIStorage->CreateStorage(szTemp, dwMode, 0, 0
                , &m_pIStorage);
            }
        }
    else
        m_pIStorage->AddRef();

    if (FAILED(hr))
        return FALSE;

    m_cOpens++;

    //Create these if we don't have them already.
    if (NULL==m_pImpIOleClientSite && NULL==m_pImpIAdviseSink)
        {
        m_pImpIOleClientSite=new CImpIOleClientSite(this, this);
        m_pImpIAdviseSink=new CImpIAdviseSink(this, this);
        m_pImpIOleIPSite=new CImpIOleInPlaceSite(this, this);

        //CHAPTER24MOD
        m_pImpIOleControlSite=new CImpIOleControlSite(this, this);
        m_pImpIDispatch=new CImpIDispatch(this, this);

        if (NULL==m_pImpIOleClientSite || NULL==m_pImpIAdviseSink
            || NULL==m_pImpIOleIPSite || NULL==m_pImpIOleControlSite
            || NULL==m_pImpIDispatch)
            return FALSE;
        //End CHAPTER24MOD
        }

    return TRUE;
    }

/*
 * CTenant::Close
 *
 * Purpose:
 *  Possibly commits the storage, then releases it reversing the
 *  reference count from Open.  If the reference on the storage
 *  goes to zero, the storage is forgotten.  However, the object we
 *  contain is still held and as long as it's active the storage
 *  remains alive.
 *
 * Parameters:
 *  fCommit         BOOL indicating if we're to commit.
 *
 * Return Value:
 *  None
 */

void CTenant::Close(BOOL fCommit)
    {
    if (fCommit)
        Update();

    if (NULL!=m_pIStorage)
        {
        m_pIStorage->Release();

        /*
         * We can't use a zero reference count to know when to NULL
         * this since other things might have AddRef'd the storage.
         */
        if (0==--m_cOpens)
            {
            m_pIStorage=NULL;

            //OnInPlaceDeactivate releases this pointer.
            if (NULL!=m_pIOleIPObject)
                m_pIOleIPObject->InPlaceDeactivate();

            //Close the object saving if necessary
            if (NULL!=m_pIOleObject)
                {
                m_pIOleObject->Close(OLECLOSE_SAVEIFDIRTY);
                ReleaseInterface(m_pIOleObject);
                }

            //Release all other held pointers
            //CHAPTER24MOD
            ReleaseInterface(m_pIOleControl);
            ReleaseInterface(m_pIDispatchControl);

            if (0!=m_dwConnEvents)
                {
                InterfaceDisconnect(m_pObj, m_iidEvents
                    , &m_dwConnEvents);
                }
            //End CHAPTER24MOD

            //Release all other held pointers
            if (NULL!=m_pIViewObject2)
                {
                m_pIViewObject2->SetAdvise(m_fe.dwAspect, 0, NULL);
                ReleaseInterface(m_pIViewObject2);
                }

            //We know we only hold one ref from Create or Load
            ReleaseInterface(m_pObj);
            }
        }

    return;
    }

/*
 * CTenant::Update
 *
 * Purpose:
 *  Forces a common on the page if it's open.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the object is open, FALSE otherwise.
 */

BOOL CTenant::Update(void)
    {
    LPPERSISTSTORAGE    pIPS;

    if (NULL!=m_pIStorage)
        {
        /*
         * We need to OleSave again because we might have changed
         * the size or position of this tenant.  We also need to
         * save the rectangle on the page, since that's not known
         * to OLE.
         */
        m_pObj->QueryInterface(IID_IPersistStorage, (PPVOID)&pIPS);

        //CHAPTER24MOD
        /*
         * Some controls may not implement IPersistStorage, using
         * IPersistStream[Init] instead.  In that case, the
         * QueryInterface above will fail, and we then have to
         * create a stream ourselves in m_pIStorage in which to
         * serialize the object through IPersistStream.  This
         * sample doesn't support stream-only objects at this time.
         */
        //End CHAPTER24MOD

        //This fails for static objects...so we improvise if so
        if (FAILED(OleSave(pIPS, m_pIStorage, TRUE)))
            {
            //This is essentially what OleSave does.
            WriteClassStg(m_pIStorage, m_clsID);
            pIPS->Save(m_pIStorage, TRUE);
            }

        //CHAPTER24MOD
        /*
         * If this is a control, then we also need to serialize
         * our events list.  We do this into a stream called
         * "\x03Events Mapping" the presence of which is used in
         * ControlInitialize to initialize the events map.  Note
         * that since we're writing a container stream into the
         * object's IStorage then we have to use the ASCII 3 prefix.
         */
        if (TENANTSTATE_EVENTS & m_dwState)
            {
            LPSTREAM    pIStream;
            HRESULT     hr;

            hr=m_pIStorage->CreateStream(SZEVENTSSTREAM
                , STGM_CREATE | STGM_DIRECT | STGM_READWRITE
                | STGM_SHARE_EXCLUSIVE, 0, 0, &pIStream);

            if (SUCCEEDED(hr));
                {
                m_pEventMap->Serialize(pIStream);
                pIStream->Release();
                }
            }
        //End CHAPTER24MOD

        pIPS->SaveCompleted(NULL);
        pIPS->Release();

        m_pIStorage->Commit(STGC_DEFAULT);
        }

    return FALSE;
    }

/*
 * CTenant::Destroy
 *
 * Purpose:
 *  Removes this page from the given storage.  The caller should
 *  eventually delete this CTenant object to free the object herein.
 *  Nothing is committed when being destroyed.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE contianing this page on which to call
 *                  DestroyElement
 *
 * Return Value:
 *  None
 */

void CTenant::Destroy(LPSTORAGE pIStorage)
    {
    OLECHAR     szTemp[32];

    if (NULL!=pIStorage)
        {
        if (NULL!=m_pIOleObject)
            {
            DeactivateInPlaceObject(TRUE);
            m_pIOleObject->Close(OLECLOSE_NOSAVE);
            }

        if (NULL!=m_pIStorage)
            {
            //Remove all reference/open counts on this storage.
            while (0!=m_cOpens)
                {
                m_pIStorage->Release();
                m_cOpens--;
                }
            }

        GetStorageName(szTemp);
        pIStorage->DestroyElement(szTemp);

        m_pIStorage=NULL;
        }

    return;
    }

/*
 * CTenant::Select
 *
 * Purpose:
 *  Selects or deselects the tenant.
 *
 * Parameters:
 *  fSelect         BOOL indicating the new state of the tenant.
 *  fActivate       BOOL indicating whether to activate or
 *                  deactivate an in-place object.  If TRUE, then
 *                  activation/deactivation will happen.  If
 *                  FALSE, no change in activation takes place.
 *
 * Return Value:
 *  None

*/

void CTenant::Select(BOOL fSelect, BOOL fActivate)
    {
    BOOL        fWasSelected;
    DWORD       dwState;
    RECT        rc;
    HDC         hDC;

    fWasSelected=(BOOL)(TENANTSTATE_SELECTED & m_dwState);

    //Nothing to do when there's no change.
    if (fWasSelected==fSelect)
        return;

    dwState=m_dwState & ~TENANTSTATE_SELECTED;
    m_dwState=dwState | ((fSelect) ? TENANTSTATE_SELECTED : 0);

    /*
     * Draw sizing handles to show the selection state.  We convert
     * things to MM_TEXT since that's what this function expects.
     */

    //CHAPTER24MOD
    /*
     * Suppress drawing any handles for invisible controls
     * unless we're in design mode.
     */
    if (!(OLEMISC_INVISIBLEATRUNTIME & m_grfMisc)
        || m_fDesignMode)
        {
        RECTFROMRECTL(rc, m_rcl);
        RectConvertMappings(&rc, NULL, TRUE);
        OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

        hDC=GetDC(m_hWnd);

        UIDrawHandles(&rc, hDC, UI_HANDLES_INSIDE
            | UI_HANDLES_NOBORDER | UI_HANDLES_USEINVERSE
            , CXYHANDLE, !fWasSelected);

        ReleaseDC(m_hWnd, hDC);
        }

    //Selecting only dirties in design mode
    if (fSelect && m_fDesignMode)
        m_pPG->m_fDirty=TRUE;
    //End CHAPTER24MOD

    if (fActivate)
        {
        if (!fSelect)
            DeactivateInPlaceObject(FALSE);
        else
            {
            //CHAPTER24MOD
            /*
             * Normally we'll send OLEIVERB_UIACTIVATE to any
             * inside-out object.  A control might be marked
             * with OLEMISC_NOUIACTIVATE in which case we skip
             * this step entirely.
             */
            if ((m_grfMisc & OLEMISC_INSIDEOUT) &&
                ! (m_grfMisc & OLEMISC_NOUIACTIVATE))
            //End CHAPTER24MOD
                {
                MSG     msg;
                DWORD   dw;

                //Include a message for in-place objects.
                msg.hwnd=NULL;
                msg.message=WM_LBUTTONDOWN;
                msg.wParam=0;
                msg.time=GetMessageTime();

                dw=GetMessagePos();
                msg.lParam=dw;
                SETPOINT(msg.pt, LOWORD(dw), HIWORD(dw));

                Activate(OLEIVERB_UIACTIVATE, &msg);
                }
            }
        }

    return;
    }

/*
 * CTenant::ShowAsOpen
 *
 * Purpose:
 *  Draws or removes the hatch pattern over an object.
 *
 * Parameters:
 *  fOpen           BOOL indicating the open state of this tenant.
 *
 * Return Value:
 *  None
 */

void CTenant::ShowAsOpen(BOOL fOpen)
    {
    BOOL        fWasOpen;
    DWORD       dwState;
    RECT        rc;
    HDC         hDC;

    fWasOpen=(BOOL)(TENANTSTATE_OPEN & m_dwState);

    dwState=m_dwState & ~TENANTSTATE_OPEN;
    m_dwState=dwState | ((fOpen) ? TENANTSTATE_OPEN : 0);

    //CHAPTER24MOD
    /*
     * Suppress drawing shading in any case if this object is
     * invisible at run time and we're not in design mode.
     */
    if ((OLEMISC_INVISIBLEATRUNTIME & m_grfMisc)
        && !m_fDesignMode)
        {
        return;
        }
    //End CHAPTER24MOD

    //If this was not open, then just hatch, otherwise repaint.
    if (!fWasOpen && fOpen)
        {
        RECTFROMRECTL(rc, m_rcl);
        RectConvertMappings(&rc, NULL, TRUE);
        OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

        hDC=GetDC(m_hWnd);
        UIDrawShading(&rc, hDC, UI_SHADE_FULLRECT, 0);
        ReleaseDC(m_hWnd, hDC);
        }

    if (fWasOpen && !fOpen)
        Repaint();

    return;
    }

/*
 * CTenant::ShowYourself
 *
 * Purpose:
 *  Function that really just implements IOleClientSite::ShowObject.
 *  Here we first check if the tenant is fully visible, and if so,
 *  then nothing needs to happen.  Otherwise, if the upper left
 *  corner of the tenant is in the upper left visible quadrant of
 *  the window, we'll also consider ourselves done.  Otherwise
 *  we'll put the upper left corner of the object at the upper left
 *  corner of the window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CTenant::ShowYourself(void)
    {
    RECTL       rcl;
    RECT        rc;
    POINT       pt1, pt2;

    //Scrolling deals in device units; get our rectangle in those.
    RectGet(&rcl, TRUE);

    //Get the window rectangle offset for the current scroll pos.
    GetClientRect(m_hWnd, &rc);
    OffsetRect(&rc, m_pPG->m_xPos, m_pPG->m_yPos);

    //Check if the object is already visible. (macro in bookguid.h)
    SETPOINT(pt1, (int)rcl.left,  (int)rcl.top);
    SETPOINT(pt2, (int)rcl.right, (int)rcl.bottom);

    /*
     * If we're doing in-place, don't move anything if the
     * object is visible AT ALL.  IOleInPlaceSite::OnInPlaceActivate
     * will have been called by now--the object will always
     * make that call before showing itself.
     */
    if (NULL!=m_pIOleIPObject && PtInRect(&rc, pt1))
        return;

    if (PtInRect(&rc, pt1) && PtInRect(&rc, pt2))
        return;

    //Check if the upper left is within the upper left quadrant
    if (((int)rcl.left > rc.left
        && (int)rcl.left < ((rc.right+rc.left)/2))
        && ((int)rcl.top > rc.top
        && (int)rcl.top < ((rc.bottom+rc.top)/2)))
        return;

    //These are macros in INC\BOOK1632.H
    SendScrollPosition(m_hWnd, WM_HSCROLL, rcl.left-8);
    SendScrollPosition(m_hWnd, WM_VSCROLL, rcl.top-8);
    return;
    }

/*
 * CTenant::AddVerbMenu
 *
 * Purpose:
 *  Creates the variable verb menu item for the object in this
 *  tenant.
 *
 * Parmeters:
 *  hMenu           HMENU on which to add items.
 *  iPos            UINT position on that menu to add items.
 *
 * Return Value:
 *  None
 */

void CTenant::AddVerbMenu(HMENU hMenu, UINT iPos)
    {
    HMENU       hMenuTemp;
    LPOLEOBJECT pObj=m_pIOleObject;

    //If we're static, say we have no object.
    if (TENANTTYPE_STATIC==m_tType)
        pObj=NULL;

    OleUIAddVerbMenu(pObj, NULL, hMenu, iPos, IDM_VERBMIN
        , IDM_VERBMAX, TRUE, IDM_EDITCONVERT, &hMenuTemp);

    return;
    }

/*
 * CTenant::TypeGet
 *
 * Purpose:
 *  Returns the type of this tenant
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  TENANTTYPE      Type of the tenant.
 */

TENANTTYPE CTenant::TypeGet(void)
    {
    return m_tType;
    }

/*
 * CTenant::CopyEmbeddedObject
 *
 * Purpose:
 *  Copies an embedded object to the given data object (via SetData,
 *  assuming this is a data transfer object for clipboard/drag-drop)
 *  if that's what we're holding.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT in which to store the copy.
 *  pFE             LPFORMATETC into which to copy CFSTR_EMBEDDEDOBJECT
 *                  if we put that in the data object.
 *  pptl            PPOINTL to the pick point (NULL outside of
 *                  drag-drop);
 *
 * Return Value:
 *  None
 */

void CTenant::CopyEmbeddedObject(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, PPOINTL pptl)
    {
    LPPERSISTSTORAGE    pIPS;
    STGMEDIUM           stm;
    FORMATETC           fe;
    HRESULT             hr;
    UINT                cf;
    POINTL              ptl;
    SIZEL               szl;

    //Can only copy embeddings.
    if (TENANTTYPE_EMBEDDEDOBJECT!=m_tType || NULL==m_pIOleObject)
        return;

    if (NULL==pptl)
        {
        SETPOINTL(ptl, 0, 0);
        pptl=&ptl;
        }

    /*
     * Create CFSTR_EMBEDDEDOBJECT.  This is simply an IStorage with
     * a copy of the embedded object in it.  The not-so-simple part
     * is getting an IStorage to stuff it in.  For this operation
     * we'll use a temporary compound file.
     */

    stm.pUnkForRelease=NULL;
    stm.tymed=TYMED_ISTORAGE;
    hr=StgCreateDocfile(NULL, STGM_TRANSACTED | STGM_READWRITE
        | STGM_CREATE| STGM_SHARE_EXCLUSIVE | STGM_DELETEONRELEASE
        , 0, &stm.pstg);

    if (FAILED(hr))
        return;

    m_pObj->QueryInterface(IID_IPersistStorage, (PPVOID)&pIPS);

    if (NOERROR==pIPS->IsDirty())
        {
        OleSave(pIPS, stm.pstg, FALSE);
        pIPS->SaveCompleted(NULL);
        }
    else
        m_pIStorage->CopyTo(0, NULL, NULL, stm.pstg);

    pIPS->Release();

    //stm.pstg now has a copy, so stuff it away.
    cf=RegisterClipboardFormat(CFSTR_EMBEDDEDOBJECT);
    SETDefFormatEtc(fe, cf, TYMED_ISTORAGE);

    if (SUCCEEDED(pIDataObject->SetData(&fe, &stm, TRUE)))
        *pFE=fe;
    else
        ReleaseStgMedium(&stm);

    stm.tymed=TYMED_HGLOBAL;

    /*
     * You want to make sure that if this object is iconic, that you
     * create the object descriptor with DVASPECT_ICON instead of
     * the more typical DVASPECT_CONTENT.  Also remember that
     * the pick point is in HIMETRIC.
     */
    XformSizeInPixelsToHimetric(NULL, (LPSIZEL)pptl, (LPSIZEL)&ptl);

    SETSIZEL(szl, (10*(m_rcl.right-m_rcl.left))
        , (10 * (m_rcl.bottom-m_rcl.top)));

    stm.hGlobal=INOLE_ObjectDescriptorFromOleObject
        (m_pIOleObject, m_fe.dwAspect, ptl, &szl);

    cf=RegisterClipboardFormat(CFSTR_OBJECTDESCRIPTOR);
    SETDefFormatEtc(fe, cf, TYMED_HGLOBAL);

    if (FAILED(pIDataObject->SetData(&fe, &stm, TRUE)))
        ReleaseStgMedium(&stm);

    return;
    }

/*
 * CTenant::CopyLinkedObject
 *
 * Purpose:
 *  Copies an linked object to the given data object (via SetData,
 *  assuming this is a data transfer object for clipboard/drag-drop)
 *  if that's what we're holding.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT in which to store the copy.
 *  pFE             LPFORMATETC into which to copy CF_LINKSOURCE
 *                  if we put that in the data object.
 *  pptl            PPOINTL to the pick point (NULL outside of
 *                  drag-drop);
 *
 * Return Value:
 *  None
 */

void CTenant::CopyLinkedObject(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, PPOINTL pptl)
    {
    STGMEDIUM           stm;
    FORMATETC           fe;
    HRESULT             hr;
    UINT                cf;
    POINTL              ptl;
    LPMONIKER           pmk;
    CLSID               clsID;
    LPTSTR              psz=NULL;
    SIZEL               szl;

    //Can only copy links to embeddings from here.
    if (TENANTTYPE_EMBEDDEDOBJECT!=m_tType || NULL==m_pIOleObject)
        return;

    //If we don't have a full moniker, no linking allowed
    if (NULL==m_pmk)
        return;

    //If the object doesn't support this, return.
    if (OLEMISC_CANTLINKINSIDE & m_grfMisc)
        return;

    if (NULL==pptl)
        {
        SETPOINTL(ptl, 0, 0);
        pptl=&ptl;
        }

    /*
     * We need to get CFSTR_LINKSOURCE, but the server may not be
     * running, in which case we just grab the moniker and CLSID
     * for this object and call INOLE_GetLinkSourceData.
     */

    m_pIOleObject->GetUserClassID(&clsID);
    hr=m_pIOleObject->GetMoniker(0, OLEWHICHMK_OBJFULL, &pmk);

    if (FAILED(hr))
        return;

    stm.pUnkForRelease=NULL;
    stm.tymed=TYMED_NULL;
    cf=RegisterClipboardFormat(CFSTR_LINKSOURCE);
    SETDefFormatEtc(fe, cf, TYMED_ISTREAM);
    hr=INOLE_GetLinkSourceData(pmk, &clsID, &fe, &stm);

    if (FAILED(hr))
        {
        pmk->Release();
        return;
        }

    //Send it to the data object for transfer
    if (SUCCEEDED(pIDataObject->SetData(&fe, &stm, TRUE)))
        *pFE=fe;
    else
        ReleaseStgMedium(&stm);

    XformSizeInPixelsToHimetric(NULL, (LPSIZEL)pptl, (LPSIZEL)&ptl);

    SETSIZEL(szl, (10*(m_rcl.right-m_rcl.left))
        , (10 * (m_rcl.bottom-m_rcl.top)));

    stm.hGlobal=INOLE_ObjectDescriptorFromOleObject
        (m_pIOleObject, m_fe.dwAspect, ptl, &szl);

    //Better set these properly or errors occur.
    stm.tymed=TYMED_HGLOBAL;
    stm.pUnkForRelease=NULL;

    cf=RegisterClipboardFormat(CFSTR_LINKSRCDESCRIPTOR);
    SETDefFormatEtc(fe, cf, TYMED_HGLOBAL);

    if (FAILED(pIDataObject->SetData(&fe, &stm, TRUE)))
        ReleaseStgMedium(&stm);

    return;
    }

/*
 * CTenant::ShowObjectType
 *
 * Purpose:
 *  Tells the object to switch on or off an indication of whether
 *  it is linked or embedded.
 *
 * Parameters:
 *  fShow           BOOL indicating to show the type (TRUE) or
 *                  not (FALSE)
 *
 * Return Value:
 *  None
 */

void CTenant::ShowObjectType(BOOL fShow)
    {
    BOOL        fWasShow;
    DWORD       dwState;
    RECT        rc;
    HDC         hDC;

    fWasShow=(BOOL)(TENANTSTATE_SHOWTYPE & m_dwState);

    dwState=m_dwState & ~TENANTSTATE_SHOWTYPE;
    m_dwState=dwState | ((fShow) ? TENANTSTATE_SHOWTYPE : 0);

    /*
     * If this wasn't previously shown, just add the line,
     * otherwise repaint.
     */
    if (!fWasShow && fShow)
        {
        RECTFROMRECTL(rc, m_rcl);
        RectConvertMappings(&rc, NULL, TRUE);
        OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);

        hDC=GetDC(m_hWnd);
        UIShowObject(&rc, hDC, (TENANTTYPE_LINKEDOBJECT==m_tType));
        ReleaseDC(m_hWnd, hDC);
        }

    if (fWasShow && !fShow)
        Repaint();

    return;
    }

/*
 * CTenant::NotifyOfRename
 *
 * Purpose:
 *  Instructs the tenant that the document was saved under a
 *  different name.  In order to keep the right compound document
 *  user interface, this tenant needs to tell its object through
 *  IOleObject::SetHostNames.
 *
 * Parameters:
 *  pszFile         LPTSTR of filename.
 *  pmkFile         LPMONIKER of the new filename.  If this and
 *                  pmkPage are NULL then nothing happens with
 *                  monikers.
 *  pmkPage         LPMONIKER of the page we're in.
 *
 * Return Value:
 *  None
 */

void CTenant::NotifyOfRename(LPTSTR pszFile, LPMONIKER pmkFile
    , LPMONIKER pmkPage)
    {
    TCHAR       szObj[40];
    TCHAR       szApp[40];

    if (NULL==m_pIOleObject)
        return;

    if (TEXT('\0')==*pszFile)
        {
        LoadString(m_pPG->m_hInst, IDS_UNTITLED, szObj
            , sizeof(szObj));
        }
    else
        {
        GetFileTitle(pszFile, szObj, sizeof(szObj));

       #ifndef WIN32
        //Force filenames to uppercase in DOS versions.
        AnsiUpper(szObj);
       #endif
        }

    LoadString(m_pPG->m_hInst, IDS_CAPTION, szApp, sizeof(szApp));
   #ifdef WIN32ANSI
    OLECHAR     szObjW[40], szAppW[40];

    MultiByteToWideChar(CP_ACP, 0, szObj, -1, szObjW, 40);
    MultiByteToWideChar(CP_ACP, 0, szApp, -1, szAppW, 40);
    m_pIOleObject->SetHostNames(szAppW, szObjW);
   #else
    m_pIOleObject->SetHostNames(szApp, szObj);
   #endif

    if (NULL!=pmkFile)
        {
        ReleaseInterface(m_pmkFile);
        m_pmkFile=pmkFile;
        m_pmkFile->AddRef();

        m_pIOleObject->SetMoniker(OLEWHICHMK_CONTAINER, pmkFile);
        }

    if (NULL!=pmkFile && NULL!=pmkPage)
        {
        LPMONIKER   pmkTenant=NULL;
        LPMONIKER   pmkRel=NULL;
        HRESULT     hr;

        //Create the moniker for this tenant.
       #ifdef WIN32ANSI
        GetStorageName(szObjW);
        WideCharToMultiByte(CP_ACP, 0, szObjW, -1, szObj, 40
           , NULL, NULL);
       #else
        GetStorageName(szObj);
       #endif
        hr=CreateItemMoniker(TEXT("!"), szObj, &pmkTenant);

        if (SUCCEEDED(hr))
            {
            //Create the relative moniker, i.e. no pathname.
            hr=pmkPage->ComposeWith(pmkTenant, FALSE, &pmkRel);
            pmkTenant->Release();

            //Hold on to the relative moniker
            ReleaseInterface(m_pmk);
            m_pmk=pmkRel;

            if (SUCCEEDED(hr))
                m_pIOleObject->SetMoniker(OLEWHICHMK_OBJREL, pmkRel);
            }
        }

    return;
    }

/*
 * CTenant::Activate
 *
 * Purpose:
 *  Activates a verb on the object living in the tenant.  Does
 *  nothing for static objects.
 *
 * Parameters:
 *  iVerb           LONG of the verb to execute.
 *  pMSG            LPMSG to the message causing the invocation.
 *
 * Return Value:
 *  BOOL            TRUE if the object changed due to this verb
 *                  execution.
 */

BOOL CTenant::Activate(LONG iVerb, LPMSG pMSG)
    {
    RECT        rc, rcH;
    CHourglass *pHour;
    SIZEL       szl;

    //Can't activate statics.
    if (TENANTTYPE_STATIC==m_tType || NULL==m_pIOleObject)
        {
        MessageBeep(0);
        return FALSE;
        }

    RECTFROMRECTL(rc, m_rcl);
    RectConvertMappings(&rc, NULL, TRUE);
    XformRectInPixelsToHimetric(NULL, &rc, &rcH);

    pHour=new CHourglass;

    //Get the server running first, then do a SetExtent, then show it
    OleRun(m_pIOleObject);

    if (m_fSetExtent)
        {
        SETSIZEL(szl, rcH.right-rcH.left, rcH.top-rcH.bottom);
        m_pIOleObject->SetExtent(m_fe.dwAspect, &szl);
        m_fSetExtent=FALSE;
        }

    //CHAPTER24MOD
    /*
     * If we have a pending deactivation, but we're activating
     * again, clear the pending flag.
     */
    if (OLEIVERB_UIACTIVATE==iVerb
        || OLEIVERB_INPLACEACTIVATE==iVerb)
        m_fPendingDeactivate=FALSE;
    //End CHAPTER24MOD

    m_pIOleObject->DoVerb(iVerb, pMSG, m_pImpIOleClientSite, 0
        , m_hWnd, &rcH);

    delete pHour;

    //If object changes, IAdviseSink::OnViewChange will see it.
    return FALSE;
    }

/*
 * CTenant::Draw
 *
 * Purpose:
 *  Draws the tenant in its rectangle on the given hDC.  We assume
 *  the DC is already set up for the mapping mode in which our
 *  rectangle is expressed, since the Page we're in tells us both
 *  the rect and the hDC.
 *
 * Parameters:
 *  hDC             HDC in which to draw.  Could be a metafile,
 *                  memory DC, screen, or printer.
 *  ptd             DVTARGETDEVICE * describing the device.
 *  hIC             HDC holding an information context (printing).
 *  xOff, yOff      int offsets for the page in lometric
 *  fNoColor        BOOL indicating if we should do B & W
 *  fPrinter        BOOL indicating if we should render for a
 *                  printer.
 *
 * Return Value:
 *  None
 */

void CTenant::Draw(HDC hDC, DVTARGETDEVICE *ptd, HDC hIC
    , int xOff, int yOff, BOOL fNoColor, BOOL fPrinter)
    {
    HRESULT         hr;
    RECT            rc;
    RECTL           rcl;
    UINT            uMM;

    /*
     * Don't bother drawing anything but shading if we're doing in-place.
     * It would just cause a lot of flicker.
     */
    if (NULL!=m_pIOleIPObject)
        return;

    //CHAPTER24MOD
    /*
     * If the control is marked OLEMISC_INVISIBLEATRUNTIME and we're
     * not in design mode, then we don't want to draw anything.
     * Usually we'll not draw anything anyway because of the check
     * against m_pIOleIPObject above, but we do this to make sure.
     */
    if ((OLEMISC_INVISIBLEATRUNTIME & m_grfMisc) && !m_fDesignMode)
        return;
    //End CHAPTER24MOD

    RECTFROMRECTL(rc, m_rcl);
    OffsetRect(&rc, -xOff, -yOff);
    RECTLFROMRECT(rcl, rc);

    //Repaint erases the rectangle to insure full object cleanup
    if (!fNoColor && !fPrinter)
        {
        COLORREF    cr;
        cr=SetBkColor(hDC, GetSysColor(COLOR_WINDOW));
        ExtTextOut(hDC, rc.left, rc.top, ETO_OPAQUE, &rc, NULL
            , 0, NULL);
        SetBkColor(hDC, cr);
        }

    //We have to use Draw since we have a target device and IC.
    hr=m_pIViewObject2->Draw(m_fe.dwAspect, -1, NULL, ptd, hIC, hDC
        , &rcl, NULL, NULL, 0);

    /*
     * If Draw failed, then perhaps it couldn't work for the device,
     * so try good old OleDraw as a last resort.  The code will
     * generally be OLE_E_BLANK.
     */
    if (FAILED(hr))
        OleDraw(m_pObj, m_fe.dwAspect, hDC, &rc);

    if (!fPrinter)
        {
        /*
         * Draw sizing handles to show the selection state.  We
         * convert things to MM_TEXT since that's what this
         * function expects.
         */
        RectConvertMappings(&rc, NULL, TRUE);
        uMM=SetMapMode(hDC, MM_TEXT);

        if (TENANTSTATE_SELECTED & m_dwState)
            {
            UIDrawHandles(&rc, hDC, UI_HANDLES_INSIDE
                | UI_HANDLES_NOBORDER | UI_HANDLES_USEINVERSE
                , CXYHANDLE, TRUE);
            }

        if (TENANTSTATE_OPEN & m_dwState)
            UIDrawShading(&rc, hDC, UI_SHADE_FULLRECT, 0);

        //Distinguish linked and embedded objects.
        if (TENANTSTATE_SHOWTYPE & m_dwState)
            {
            UIShowObject(&rc, hDC
                , (TENANTTYPE_LINKEDOBJECT==m_tType));
            }

        uMM=SetMapMode(hDC, uMM);
        }

    return;
    }

/*
 * CTenant::Repaint
 * CTenant::Invalidate
 *
 * Purpose:
 *  Repaints the tenant where it lies or invalidates its area
 *  for later repainting.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CTenant::Repaint(void)
    {
    RECT        rc;
    HDC         hDC;

    /*
     * We might be asked to repaint from
     * IOleClientSite::OnShowWindow after we've switched pages if
     * our server was running. This check on m_cOpens prevents that.
     */
    if (0==m_cOpens || !m_fRepaintEnabled)
        return;

    hDC=GetDC(m_hWnd);
    SetRect(&rc, m_pPG->m_xPos, m_pPG->m_yPos, 0, 0);
    RectConvertMappings(&rc, NULL, FALSE);

    SetMapMode(hDC, MM_LOMETRIC);
    Draw(hDC, NULL, NULL, rc.left, rc.top, FALSE, FALSE);

    ReleaseDC(m_hWnd, hDC);
    return;
    }

void CTenant::Invalidate(void)
    {
    RECTL       rcl;
    RECT        rc;

    RectGet(&rcl, TRUE);
    RECTFROMRECTL(rc, rcl);

    OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);
    InvalidateRect(m_hWnd, &rc, FALSE);

    return;
    }

/*
 * CTenant::FIsSelected
 *
 * Purpose:
 *  Returns the selection state of this tenant.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if selected, FALSE otherwise.
 */

BOOL CTenant::FIsSelected(void)
    {
    return (BOOL)(m_dwState & TENANTSTATE_SELECTED);
    }

/*
 * CTenant::ConvertToStatic
 *
 * Purpose:
 *  Changes the object that lives in this tenant to a static one.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CTenant::ConvertToStatic(void)
    {
    /*
     * If you SetSourceMoniker in IOleLink to NULL, then the link is
     * gone as far as OLE is concerned.  You only need to make sure
     * the user doesn't have access to other functionality for this
     * object, which we insure by changing our internal type.  We
     * set this on loading if GetSourceMoniker returns NULL.
     */
    m_tType=TENANTTYPE_STATIC;
    return TRUE;
    }

/*
 * CTenant::ObjectClassFormatAndIcon
 *
 * Purpose:
 *  Helper function for CPage::ConvertObject to retrieve necessary
 *  information about the object.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store the CLSID.
 *  pwFormat        LPWORD in which to store the clipboard format
 *                  used.
 *  ppszType        LPTSTR * in which to return a pointer to a
 *                  type string.
 *  phMetaIcon      HGLOBAL * in which to return the metafile
 *                  icon currently in use.
 *
 * Return Value:
 *  None
 */

void CTenant::ObjectClassFormatAndIcon(LPCLSID pClsID
    , LPWORD pwFormat, LPTSTR *ppszType, HGLOBAL *phMetaIcon
    , LPTSTR *ppszLabel)
    {
    HRESULT         hr;
    TCHAR           szType[128];
    LPDATAOBJECT    pIDataObject;
    FORMATETC       fe;
    STGMEDIUM       stm;

    if (TENANTTYPE_EMBEDDEDOBJECT!=m_tType || NULL==m_pIOleObject)
        return;

    if (NULL==pClsID || NULL==pwFormat || NULL==ppszType
        || NULL==phMetaIcon)
        return;

    /*
     * For embedded objects get the real CLSID of the object and
     * its format string.  If this fails then we can try to ask
     * the object, or we can look in the registry.
     */

    hr=ReadClassStg(m_pIStorage, pClsID);

    if (FAILED(hr))
        {
        hr=m_pIOleObject->GetUserClassID(pClsID);

        if (FAILED(hr))
            *pClsID=CLSID_NULL;
        }

    hr=ReadFmtUserTypeStg(m_pIStorage, pwFormat, ppszType);

    if (FAILED(hr))
        {
        *pwFormat=0;
        *ppszType=NULL;

        if (INOLE_GetUserTypeOfClass(*pClsID, 0, szType
            , sizeof(szType)))
            {
            *ppszType=INOLE_CopyString(szType);
            }
        }

    /*
     * Try to get the AuxUserType from the registry, using
     * the short version (registered under AuxUserType\2).
     * If that fails, just copy *ppszType.
     */
    *ppszLabel=NULL;

    if (INOLE_GetUserTypeOfClass(*pClsID, 2, szType
        , sizeof(szType)))
        {
        *ppszLabel=INOLE_CopyString(szType);
        }
    else
        *ppszLabel=INOLE_CopyString(*ppszType);

    //Get the icon for this thing, if we're iconic.
    *phMetaIcon=NULL;

    hr=m_pObj->QueryInterface(IID_IDataObject
        , (PPVOID)&pIDataObject);

    if (SUCCEEDED(hr))
        {
        SETFormatEtc(fe, CF_METAFILEPICT, DVASPECT_ICON, NULL
            , TYMED_MFPICT, -1);
        hr=pIDataObject->GetData(&fe, &stm);
        pIDataObject->Release();

        if (SUCCEEDED(hr))
            *phMetaIcon=stm.hGlobal;
        else
            *phMetaIcon=OleGetIconOfClass(*pClsID, NULL, TRUE);
        }

    return;
    }

/*
 * CTenant::SwitchOrUpdateAspect
 *
 * Purpose:
 *  Switches between DVASPECT_CONTENT and DVASPECT_ICON
 *
 * Parameters:
 *  hMetaIcon       HGLOBAL to the new icon if we're changing the
 *                  icon or switching to DVASPECT_ICON.  NULL to
 *                  change back to content.
 *  fPreserve       BOOL indicating if we're to preserve the old
 *                  aspect after changing.
 *
 * Return Value:
 *  BOOL            TRUE if anything changed, FALSE otherwise.
 */

BOOL CTenant::SwitchOrUpdateAspect(HGLOBAL hMetaIcon
    , BOOL fPreserve)
    {
    HRESULT     hr;
    DWORD       dwAspect;
    BOOL        fUpdate=FALSE;

    //Nothing to do if we're content already and there's no icon.
    if (NULL==hMetaIcon && DVASPECT_CONTENT==m_fe.dwAspect)
        return FALSE;

    //If we're iconic already, just cache the new icon
    if (NULL!=hMetaIcon && DVASPECT_ICON==m_fe.dwAspect)
        hr=INOLE_SetIconInCache(m_pIOleObject, hMetaIcon);
    else
        {
        //Otherwise, switch between iconic and content.
        dwAspect=(NULL==hMetaIcon) ? DVASPECT_CONTENT : DVASPECT_ICON;

        /*
         * Switch between aspects, where dwAspect has the new one
         * and m_fe.dwAspect will be changed in the process.
         */
        hr=INOLE_SwitchDisplayAspect(m_pIOleObject
            , &m_fe.dwAspect, dwAspect, hMetaIcon, !fPreserve
            , TRUE, m_pImpIAdviseSink, &fUpdate);

        if (SUCCEEDED(hr))
            {
            //Update MiscStatus for the new aspect
            m_pIOleObject->GetMiscStatus(m_fe.dwAspect, &m_grfMisc);

            if (fUpdate)
                m_pIOleObject->Update();    //This repaints.
            }
        }

    //If we switched, update our extents.
    if (SUCCEEDED(hr))
        {
        SIZEL       szl;

        m_pIOleObject->GetExtent(m_fe.dwAspect, &szl);

        if (0 > szl.cy)
            szl.cy=-szl.cy;

        //Convert HIMETRIC absolute units to our LOMETRIC mapping
        if (0!=szl.cx && 0!=szl.cy)
            SETSIZEL(szl, szl.cx/10, -szl.cy/10);

        Invalidate();                   //Remove old aspect
        SizeSet(&szl, FALSE, FALSE);    //Change size
        Repaint();                      //Paint the new one
        }

    return SUCCEEDED(hr);
    }

/*
 * CTenant::EnableRepaint
 *
 * Purpose:
 *  Toggles whether the Repaint function does anything.  This
 *  is used during conversion/emulation of an object to disable
 *  repaints until the new object can be given the proper extents.
 *
 * Parameters:
 *  fEnable         TRUE to enable repaints, FALSE to disable.
 *
 * Return Value:
 *  None
 */

void CTenant::EnableRepaint(BOOL fEnable)
    {
    m_fRepaintEnabled=fEnable;
    return;
    }

/*
 * CTenant::ObjectGet
 *
 * Purpose:
 *  Retrieves the LPUNKNOWN of the object in use by this tenant
 *
 * Parameters:
 *  ppUnk           LPUNKNOWN * in which to return the object
 *                  pointer.
 *
 * Return Value:
 *  None
 */

void CTenant::ObjectGet(LPUNKNOWN *ppUnk)
    {
    if (NULL!=ppUnk)
        {
        *ppUnk=m_pObj;
        m_pObj->AddRef();
        }

    return;
    }

/*
 * CTenant::FormatEtcGet
 *
 * Purpose:
 *  Retrieves the FORMATETC in use by this tenant
 *
 * Parameters:
 *  pFE             LPFORMATETC in which to store the information.
 *  fPresentation   BOOL indicating if we want the real format or
 *                  that of the presentation.
 *
 * Return Value:
 *  None
 */

void CTenant::FormatEtcGet(LPFORMATETC pFE, BOOL fPresentation)
    {
    if (NULL!=pFE)
        {
        *pFE=m_fe;

        //If there is no format, use metafile (for embedded objects)
        if (fPresentation || 0==pFE->cfFormat)
            {
            //Don't mess with dwAspect; might be icon or content.
            pFE->cfFormat=CF_METAFILEPICT;
            pFE->tymed=TYMED_MFPICT;
            }
        }

    return;
    }

/*
 * CTenant::SizeGet
 * CTenant::SizeSet
 * CTenant::RectGet
 * CTenant::RectSet
 *
 * Purpose:
 *  Returns or sets the size/position of the object contained here.
 *
 * Parameters:
 *  pszl/prcl       LPSIZEL (Size) or LPRECTL (Rect) with the
 *                  extents of interest.  In Get situations,
 *                  this will receive the extents; in Set it
 *                  contains the extents.
 *  fDevice         BOOL indicating that pszl/prcl is expressed
 *                  in device units.  Otherwise it's LOMETRIC.
 *  fInformObj      (Set Only) BOOL indicating if we need to inform
 *                  the object all.
 *
 * Return Value:
 *  None
 */

void CTenant::SizeGet(LPSIZEL pszl, BOOL fDevice)
    {
    if (!fDevice)
        {
        pszl->cx=m_rcl.right-m_rcl.left;
        pszl->cy=m_rcl.bottom-m_rcl.top;
        }
    else
        {
        RECT        rc;

        SetRect(&rc, (int)(m_rcl.right-m_rcl.left)
            , (int)(m_rcl.bottom-m_rcl.top), 0, 0);

        RectConvertMappings(&rc, NULL, TRUE);

        pszl->cx=(long)rc.left;
        pszl->cy=(long)rc.top;
        }

    return;
    }

void CTenant::SizeSet(LPSIZEL pszl, BOOL fDevice, BOOL fInformObj)
    {
    SIZEL           szl;

    if (!fDevice)
        {
        szl=*pszl;
        m_rcl.right =pszl->cx+m_rcl.left;
        m_rcl.bottom=pszl->cy+m_rcl.top;
        }
    else
        {
        RECT        rc;

        SetRect(&rc, (int)pszl->cx, (int)pszl->cy, 0, 0);
        RectConvertMappings(&rc, NULL, FALSE);

        m_rcl.right =(long)rc.left+m_rcl.left;
        m_rcl.bottom=(long)rc.top+m_rcl.top;

        SETSIZEL(szl, (long)rc.left, (long)rc.top);
        }

    //Tell OLE that this object was resized.
    if (NULL!=m_pIOleObject && fInformObj)
        {
        HRESULT     hr;
        BOOL        fRun=FALSE;

        //Convert our LOMETRIC into HIMETRIC by *=10
        szl.cx*=10;
        szl.cy*=-10;    //Our size is stored negative.

        /*
         * If the MiscStatus bit of OLEMISC_RECOMPOSEONRESIZE
         * is set, then we need to run the object before calling
         * SetExtent to make sure it has a real chance to
         * re-render the object.  We have to update and close
         * the object as well after this happens.
         */

        if (OLEMISC_RECOMPOSEONRESIZE & m_grfMisc)
            {
            if (!OleIsRunning(m_pIOleObject))
                {
                OleRun(m_pIOleObject);
                fRun=TRUE;
                }
            }

        hr=m_pIOleObject->SetExtent(m_fe.dwAspect, &szl);

        /*
         * If the object is not running and it does not have
         * RECOMPOSEONRESIZE, then SetExtent fails.  Make
         * sure that we call SetExtent again (by just calling
         * SizeSet here again) when we next run the object.
         */
        if (SUCCEEDED(hr))
            {
            m_fSetExtent=FALSE;

            if (fRun)
                {
                m_pIOleObject->Update();
                m_pIOleObject->Close(OLECLOSE_SAVEIFDIRTY);
                }
            }
        else
            {
            if (OLE_E_NOTRUNNING==GetScode(hr))
                m_fSetExtent=TRUE;
            }
        }

    return;
    }

void CTenant::RectGet(LPRECTL prcl, BOOL fDevice)
    {
    if (!fDevice)
        *prcl=m_rcl;
    else
        {
        RECT        rc;

        RECTFROMRECTL(rc, m_rcl);
        RectConvertMappings(&rc, NULL, TRUE);
        RECTLFROMRECT(*prcl, rc);
        }

    return;
    }

void CTenant::RectSet(LPRECTL prcl, BOOL fDevice, BOOL fInformObj)
    {
    SIZEL   szl;
    LONG    cx, cy;

    /*
     * Prevent reentrant calls that may come from calling
     * UpdateInPlaceObjectRects here and elsewhere.
     */
    if (m_fInRectSet)
        return;

    m_fInRectSet=TRUE;

    cx=m_rcl.right-m_rcl.left;
    cy=m_rcl.bottom-m_rcl.top;

    if (!fDevice)
        m_rcl=*prcl;
    else
        {
        RECT        rc;

        RECTFROMRECTL(rc, *prcl);
        RectConvertMappings(&rc, NULL, FALSE);
        RECTLFROMRECT(m_rcl, rc);
        }

    /*
     * Tell ourselves that the size changed, if it did.  SizeSet
     * will call IOleObject::SetExtent for us.
     */
    if ((m_rcl.right-m_rcl.left)!=cx || (m_rcl.bottom-m_rcl.top)!=cy)
        {
        SETSIZEL(szl, m_rcl.right-m_rcl.left, m_rcl.bottom-m_rcl.top);
        SizeSet(&szl, FALSE, fInformObj);
        }

    //Tell an in-place active object it moved too
    UpdateInPlaceObjectRects(NULL, TRUE);
    m_fInRectSet=FALSE;
    return;
    }

/*
 * CTenant::CreateStatic
 * (Protected)
 *
 * Purpose:
 *  Creates a new static bitmap or metafile object for this tenant
 *  using a freeloading method allowing us to specify exactly which
 *  type of data we want to paste since OleCreateStaticFromData
 *  doesn't.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT from which to paste.
 *  pFE             LPFORMATETC describing the format to paste.
 *  ppObj           LPUNKNOWN * into which we store the
 *                  object pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, error code otherwise.
 */

HRESULT CTenant::CreateStatic(LPDATAOBJECT pIDataObject
    , LPFORMATETC pFE, LPUNKNOWN *ppObj)
    {
    HRESULT             hr;
    STGMEDIUM           stm;
    LPUNKNOWN           pIUnknown;
    LPOLECACHE          pIOleCache;
    LPPERSISTSTORAGE    pIPersistStorage;
    CLSID               clsID;

    *ppObj=NULL;

    //Try to get the data desired as specified in pFE->cfFormat
    hr=pIDataObject->GetData(pFE, &stm);

    if (FAILED(hr))
        return hr;

    //Create the object to handle this data.
    if (CF_METAFILEPICT==pFE->cfFormat)
        clsID=CLSID_Picture_Metafile;
    else
        clsID=CLSID_Picture_Dib;

    hr=CreateDataCache(NULL, clsID, IID_IUnknown
        , (PPVOID)&pIUnknown);

    if (FAILED(hr))
        {
        ReleaseStgMedium(&stm);
        return hr;
        }

    m_clsID=clsID;

    //Stuff the data into the object
    pIUnknown->QueryInterface(IID_IPersistStorage
        , (PPVOID)&pIPersistStorage);
    pIPersistStorage->InitNew(m_pIStorage);

    //Now that we have the cache object, shove the data into it.
    pIUnknown->QueryInterface(IID_IOleCache, (PPVOID)&pIOleCache);
    pIOleCache->Cache(pFE, ADVF_PRIMEFIRST, NULL);

    hr=pIOleCache->SetData(pFE, &stm, TRUE);
    pIOleCache->Release();

    //Insure there is a persistent copy on the disk
    WriteClassStg(m_pIStorage, m_clsID);
    pIPersistStorage->Save(m_pIStorage, TRUE);
    pIPersistStorage->SaveCompleted(NULL);
    pIPersistStorage->Release();

    //The cache owns this now.
    ReleaseStgMedium(&stm);

    if (FAILED(hr))
        pIUnknown->Release();
    else
        *ppObj=pIUnknown;

    return hr;
    }

/*
 * CTenant::DeactivateInPlaceObject
 *
 * Purpose:
 *  Deactivates an in-place object if there is one in this tenant.
 *
 * Parameters:
 *  fFull           BOOL indicating full deactivation of UI
 *                  deactivate only.
 *
 * Return Value:
 *  None
 */

void CTenant::DeactivateInPlaceObject(BOOL fFull)
    {
    if (NULL!=m_pIOleIPObject)
        {
        /*
         * Activate-when-visible objects only UI deactivate
         * unless we're fully deactivating on purpose.
         */
        if ((OLEMISC_ACTIVATEWHENVISIBLE & m_grfMisc) && !fFull)
            m_pIOleIPObject->UIDeactivate();
        else
            {
            //CHAPTER24MOD
            /*
             * Only deactivate when there's no locks.  If there
             * is a lock, then remember that we need to deactivate
             * when all the locks go away.
             */
            if (0==m_cLockInPlace)
                m_pIOleIPObject->InPlaceDeactivate();
            else
                m_fPendingDeactivate=TRUE;
            //End CHAPTER24MOD
            }
        }

    return;
    }

/*
 * CTenant::UpdateInPlaceObjectRects
 *
 * Purpose:
 *  Generates a call to IOleInPlaceObject::SetObjectRects to allow
 *  it to show it's shading and its object adornments properly.
 *  This function deals in HIMETRIC units.
 *
 * Parameters:
 *  prcPos          LPCRECT to the size the object wants.  Ignored
 *                  if NULL in which case we use the size of the
 *                  tenant.  This rect is in client coordinates of
 *                  the pages window.
 *  fUseTenantRect  BOOL indicating if we need to use the tenant
 *                  rectangle offset for scrolling regardless.
 *
 * Return Value:
 *  None
 */

void CTenant::UpdateInPlaceObjectRects(LPCRECT prcPos
    , BOOL fUseTenantRect)
    {
    RECTL       rcl;
    RECT        rc;
    RECT        rcClip;
    BOOL        fResizeTenant=TRUE;

    /*
     * Note that if the object here is activate-when-visible
     * we'll always have this pointer.
     */
    if (NULL!=m_pIOleIPObject)
        {
        /*
         * This uses the last position rectangle from
         * IOleInPlaceSite::OnPosRectChange if it's been
         * initialized
         */
        if (NULL==prcPos && -1!=m_rcPos.left && !fUseTenantRect)
            prcPos=&m_rcPos;

        //This code is normally called from OnPosRectChange direct.
        if (NULL!=prcPos && !fUseTenantRect)
            {
            rc=*prcPos;

            //Calculate the boundaries of the full page
            m_pPG->CalcBoundingRect(&rcClip, FALSE);

            //Make sure we limit object to page boundaries.
            IntersectRect(&rc, &rc, &rcClip);
            }
        else
            {
            /*
             * We have no rectangle of the object on which to
             * base the position, so just use the tenant rectangle.
             * This code is also used when scrolling objects.
             */
            RectGet(&rcl, TRUE);
            RECTFROMRECTL(rc, rcl);

            //Account for scrolling
            OffsetRect(&rc, -(int)m_pPG->m_xPos, -(int)m_pPG->m_yPos);
            fResizeTenant=FALSE;
            }

        //We don't clip special anywhere in our window.
        SetRect(&rcClip, 0, 0, 32767, 32767);

        /*
         * NOTE:  The rectangles to SetObjectRects is in client
         * coordinates of the pages window.
         */
        if (NULL!=m_pIOleIPObject)
            m_pIOleIPObject->SetObjectRects(&rc, &rcClip);

        if (fResizeTenant)
            {
            //Need to tell the tenant to change position too
            RECTLFROMRECT(rcl, rc);
            RectSet(&rcl, TRUE, FALSE);
            }
        }

    return;
    }

/*
 * CTenant::ObjectWindow
 *
 * Purpose:
 *  Returns the window handle of the in-place object.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HWND            Handle of the object window.
 */

HWND CTenant::ObjectWindow(void)
    {
    HWND    hWnd=NULL;

    if (NULL!=m_pIOleIPObject)
        m_pIOleIPObject->GetWindow(&hWnd);

    return hWnd;
    }

//CHAPTER24MOD
/*
 * CTenant::ToggleDesignMode
 *
 * Purpose:
 *  Switches between design mode and run mode for this tenant.
 *  Going to design mode deactivates the tenant.  Otherwise it's
 *  activated in-place.  If this is the selected tenant, then
 *  it is also UI activated
 *
 * Parameters:
 *  fDesign         BOOL indicating to deactivate or activate.
 *
 * Return Value:
 *  None
 */

void CTenant::ToggleDesignMode(BOOL fDesign)
    {
    BOOL    fChange=!(m_fDesignMode & fDesign);

    if (fDesign==m_fDesignMode)
        return;

    m_fDesignMode=fDesign;

    /*
     * Inform the control of ambient property changed.  A change
     * in design mode changes UserMode, ShowGrabHandles, and
     * ShowHatching (see AMBIENTS.CPP)
     */

    AmbientChange(DISPID_AMBIENT_USERMODE);

    if (m_fDesignMode)
        {
        //This even deactivates activate-when-visible objects
        DeactivateInPlaceObject(TRUE);
        Invalidate();
        }
    else
        {
        //First hide whatever windows might be open
        if (TENANTSTATE_OPEN & m_dwState)
            Activate(OLEIVERB_HIDE, NULL);

        //Activate all tenants, UI activate the selected one
        Activate(OLEIVERB_INPLACEACTIVATE, NULL);

        if (TENANTSTATE_SELECTED & m_dwState)
            Activate(OLEIVERB_UIACTIVATE, NULL);
        }

    return;
    }

/*
 * CTenant::ToggleUIDead
 * CTenant::ToggleHatchHandles
 *
 * Purpose:
 *  Toggles UI Dead or ShowHatching/ShowGrabHandles states
 */

void CTenant::ToggleUIDead(BOOL fUIDead)
    {
    BOOL    fChange=!(m_fUIDead & fUIDead);

    if (fUIDead==m_fUIDead)
        return;

    m_fUIDead=fUIDead;
    AmbientChange(DISPID_AMBIENT_UIDEAD);
    return;
    }

void CTenant::ToggleHatchHandles(BOOL fHatchHandles)
    {
    BOOL    fChange=!(m_fHatchHandles & fHatchHandles);

    if (fHatchHandles==m_fHatchHandles)
        return;

    m_fHatchHandles=fHatchHandles;
    AmbientChange(DISPID_AMBIENT_SHOWHATCHING);
    AmbientChange(DISPID_AMBIENT_SHOWGRABHANDLES);
    return;
    }

/*
 * CTenant::ControlInitialize
 *
 * Purpose:
 *  Initializes the control if that's the type of object we have
 *  in the site.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if initialization worked, FALSE otherwise.
 */

BOOL CTenant::ControlInitialize(void)
    {
    HRESULT         hr;
    BOOL            fEvents;

    if (NULL==m_pObj)
        return FALSE;

    hr=m_pObj->QueryInterface(IID_IOleControl
        , (PPVOID)&m_pIOleControl);

    //Failure means not a control.
    if (FAILED(hr))
        return FALSE;

    m_dwState |= TENANTSTATE_CONTROL;

    if (OLEMISC_ACTSLIKEBUTTON & m_grfMisc)
        m_dwState |= TENANTSTATE_BUTTON;

    //We don't use this, but might as well store it.
    if (OLEMISC_ACTSLIKELABEL & m_grfMisc)
        m_dwState |= TENANTSTATE_LABEL;

    /*
     * Call IOleControl::GetControlInfo to retrieve the keyboard
     * information for this control.  We have to reload this
     * information in IOleControlSite::OnControlInfoChanged.
     */
    m_fHaveControlInfo=SUCCEEDED(m_pIOleControl
        ->GetControlInfo(&m_ctrlInfo));

    /*
     * Get the properties and methods IDispatch (NOTE:  we don't
     * ever call this in this container, but this is where you'd
     * probably ask for it.  Failure is tolerable--it just means
     * we could not set properties.
     */

    m_pObj->QueryInterface(IID_IDispatch
        , (PPVOID)&m_pIDispatchControl);

    /*
     * Connect our events IDispatch to the object.  The function
     * EventIIDFromObject (see connect.cpp) retrieves the events
     * IID for us.  If InterfaceConnect (also in connect.cpp) fails,
     * then we'll just do without events.  If we do connect to the
     * events, then we need to initialize event information.
     */
    ObjectEventsIID(m_pObj, &m_iidEvents);

    m_pDispEvents=new CDispatchEvents(this);
    fEvents=InterfaceConnect(m_pObj, m_iidEvents, m_pDispEvents
        , &m_dwConnEvents);

    /*
     * If we successfully connected our IDispatch to the control
     * for events, then get all the pertinent event information
     * we need for allowing the user to assign code/actions to
     * each event.  In the case of this container, all we need
     * are the names of the events so we can show them in a listbox.
     * Real containers, like Visual Basic or a database, for example,
     * would also want the types and names of event parameters
     * and so forth.
     *
     * So in any case, we'll need an ITypeInfo interface for the
     * event set of the control.  The function ObjectEventsTypeInfo
     * (connect.cpp) gets the right ITypeInfo for us.
     */

    if (fEvents)
        {
        LPTYPEINFO      pITypeInfo;
        LPSTREAM        pIStream;

        m_dwState |= TENANTSTATE_EVENTS;

        //Get the ITypeInfo specifically for events (connect.cpp)
        fEvents=ObjectTypeInfoEvents(m_pObj, &pITypeInfo);

        if (fEvents)
            {
            fEvents=FALSE;

            //CEventMap implemented in events.cpp
            m_pEventMap=new CEventMap(pITypeInfo);

            if (NULL!=m_pEventMap)
                {
                fEvents=m_pEventMap->Init();

                //Check if there's mappings already and load them.
                if (fEvents)
                    {
                    if (SUCCEEDED(m_pIStorage->OpenStream
                        (SZEVENTSSTREAM, NULL, STGM_DIRECT
                        | STGM_READWRITE | STGM_SHARE_EXCLUSIVE
                        , 0, &pIStream)))
                        {
                        m_pEventMap->Deserialize(pIStream);
                        pIStream->Release();
                        }
                    }
                }

            pITypeInfo->Release();
            }

        if (!fEvents)
            {
            /*
             * Event mapping failed, disconnect and clean up--we just
             * won't handle any events for this control.  We need to
             * clean up and NULL m_pDispEvents which is our
             * flag that we can do events.
             */
            InterfaceDisconnect(m_pObj, m_iidEvents
                , &m_dwConnEvents);
            ReleaseInterface(m_pDispEvents);
            delete m_pEventMap;
            m_pEventMap=NULL;
            }
        }

    /*
     * If you wanted to receive IPropertyNotifySink notifications
     * for a control, establish that advisory connection here
     * through the object's IConnectionPointContainer and
     * IConnectionPoint.
     */

    return TRUE;
    }

/*
 * CTenant::EventMap
 *
 * Purpose:
 *  Retrieve the current event map from the tenant.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCEventMap      Current map for the tenant.
 */

PCEventMap CTenant::EventMap(void)
    {
    return m_pEventMap;
    }

/*
 * CTenant::GetControlFlags
 *
 * Purpose:
 *  Requests flags describing the control inside this tenant
 *  if there is, in fact a control.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  DWORD           Flags describing the control from the
 *                  TENANT
 */

DWORD CTenant::GetControlFlags(void)
    {
    return m_dwState & STATEMASK_CONTROLS;
    }

/*
 * CTenant::TryMnemonic
 *
 * Purpose:
 *  Asks the tenant to check the given keyboard message against
 *  one that its control might want, passing it to
 *  IOleControl::OnMnemonic if there is a match.
 *
 * Parameters:
 *  pMsg            LPMSG containing the message to check.
 *
 * Return Value:
 *  BOOL            TRUE if the mnemonic was a match,
 *                  FALSE otherwise.
 */

BOOL CTenant::TryMnemonic(LPMSG pMsg)
    {
    UINT        i;
    BOOL        fRet=FALSE;
    LPACCEL     pACC;
    BYTE        fVirt=FVIRTKEY;

    if (!m_fHaveControlInfo)    //False for non-controls
        return FALSE;

    if (0==m_ctrlInfo.cAccel)
        return FALSE;

    pACC=(LPACCEL)GlobalLock(m_ctrlInfo.hAccel);

    if (NULL==pACC)
        return FALSE;

    /*
     * We'll come here on WM_KEYDOWN messages and WM_SYSKEYDOWN
     * messages.  The control's accelerator table will contain
     * entries that each have the desired key and the various
     * modifier flags.  We the create the current modifier flags
     * then look for entries that match those flags and the key
     * that is in the message itself.
     */

    fVirt |= (WM_SYSKEYDOWN==pMsg->message) ? FALT : 0;

    //GetKeyState works on the last message
    fVirt |= (0x8000 & GetKeyState(VK_CONTROL)) ? FCONTROL : 0;
    fVirt |= (0x8000 & GetKeyState(VK_SHIFT)) ? FSHIFT : 0;

    for (i=0; i < m_ctrlInfo.cAccel; i++)
        {
        if (pACC[i].key==pMsg->wParam && pACC[i].fVirt==fVirt)
            {
            m_pIOleControl->OnMnemonic(pMsg);
            fRet=TRUE;
            break;
            }
        }

    GlobalUnlock(m_ctrlInfo.hAccel);
    return fRet;
    }

/*
 * CTenant::AmbientChange
 *
 * Purpose:
 *  Notifes a control that an ambient property has changed in
 *  the control site.
 *
 * Parameters:
 *  dispID          DISPID of the property that changed.
 *
 * Return Value:
 *  None
 */

void CTenant::AmbientChange(DISPID dispID)
    {
    if (NULL!=m_pIOleControl)
        m_pIOleControl->OnAmbientPropertyChange(dispID);

    return;
    }

//End CHAPTER24MOD


TENANT.H

/*
 * TENANT.H
 * Patron Chapter 24
 *
 * Definitions and function prototypes for the CTenant class
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _TENANT_H_
#define _TENANT_H_

class CImpIOleClientSite : public IOleClientSite
    {
    protected:
        ULONG               m_cRef;
        class CTenant      *m_pTen;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIOleClientSite(class CTenant *, LPUNKNOWN);
        ~CImpIOleClientSite(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP SaveObject(void);
        STDMETHODIMP GetMoniker(DWORD, DWORD, LPMONIKER *);
        STDMETHODIMP GetContainer(LPOLECONTAINER *);
        STDMETHODIMP ShowObject(void);
        STDMETHODIMP OnShowWindow(BOOL);
        STDMETHODIMP RequestNewObjectLayout(void);
    };

typedef CImpIOleClientSite *PCImpIOleClientSite;

class CImpIAdviseSink : public IAdviseSink
    {
    protected:
        ULONG               m_cRef;
        class CTenant      *m_pTen;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIAdviseSink(class CTenant *, LPUNKNOWN);
        ~CImpIAdviseSink(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(void)  OnDataChange(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP_(void)  OnViewChange(DWORD, LONG);
        STDMETHODIMP_(void)  OnRename(LPMONIKER);
        STDMETHODIMP_(void)  OnSave(void);
        STDMETHODIMP_(void)  OnClose(void);
    };

typedef CImpIAdviseSink *PCImpIAdviseSink;

class CImpIOleInPlaceSite : public IOleInPlaceSite
    {
    protected:
        ULONG               m_cRef;
        class CTenant      *m_pTen;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIOleInPlaceSite(class CTenant *, LPUNKNOWN);
        ~CImpIOleInPlaceSite(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetWindow(HWND *);
        STDMETHODIMP ContextSensitiveHelp(BOOL);
        STDMETHODIMP CanInPlaceActivate(void);
        STDMETHODIMP OnInPlaceActivate(void);
        STDMETHODIMP OnUIActivate(void);
        STDMETHODIMP GetWindowContext(LPOLEINPLACEFRAME *
                        , LPOLEINPLACEUIWINDOW *, LPRECT, LPRECT
                        , LPOLEINPLACEFRAMEINFO);
        STDMETHODIMP Scroll(SIZE);
        STDMETHODIMP OnUIDeactivate(BOOL);
        STDMETHODIMP OnInPlaceDeactivate(void);
        STDMETHODIMP DiscardUndoState(void);
        STDMETHODIMP DeactivateAndUndo(void);
        STDMETHODIMP OnPosRectChange(LPCRECT);
    };

typedef CImpIOleInPlaceSite *PCImpIOleInPlaceSite;

//CHAPTER24MOD
class CImpIOleControlSite : public IOleControlSite
    {
    protected:
        ULONG           m_cRef;
        class CTenant  *m_pTen;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIOleControlSite(class CTenant *, LPUNKNOWN);
        ~CImpIOleControlSite(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP OnControlInfoChanged(void);
        STDMETHODIMP LockInPlaceActive(BOOL);
        STDMETHODIMP GetExtendedControl(LPDISPATCH *);
        STDMETHODIMP TransformCoords(POINTL *, POINTF *, DWORD);
        STDMETHODIMP TranslateAccelerator(LPMSG, DWORD);
        STDMETHODIMP OnFocus(BOOL);
        STDMETHODIMP ShowPropertyFrame(void);
    };

typedef class CImpIOleControlSite *PCImpIOleControlSite;

//This IDispatch implements Ambient properties
class CImpIDispatch : public IDispatch
    {
    protected:
        ULONG           m_cRef;
        class CTenant  *m_pTen;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIDispatch(class CTenant *, LPUNKNOWN);
        ~CImpIDispatch(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetTypeInfoCount(UINT *);
        STDMETHODIMP GetTypeInfo(UINT, LCID, ITypeInfo **);
        STDMETHODIMP GetIDsOfNames(REFIID, OLECHAR **, UINT
            , LCID, DISPID *);
        STDMETHODIMP Invoke(DISPID, REFIID, LCID, USHORT
            , DISPPARAMS *, VARIANT *, EXCEPINFO *, UINT *);
    };

typedef class CImpIDispatch *PCImpIDispatch;

//Events IDispatch created once for each outgoing event set.
class CDispatchEvents : public IDispatch
    {
    protected:
        ULONG           m_cRef;
        class CTenant  *m_pTen;

    public:
        CDispatchEvents(class CTenant *);
        ~CDispatchEvents(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetTypeInfoCount(UINT *);
        STDMETHODIMP GetTypeInfo(UINT, LCID, ITypeInfo **);
        STDMETHODIMP GetIDsOfNames(REFIID, OLECHAR **, UINT
            , LCID, DISPID *);
        STDMETHODIMP Invoke(DISPID, REFIID, LCID, USHORT
            , DISPPARAMS *, VARIANT *, EXCEPINFO *, UINT *);
    };

typedef class CDispatchEvents *PCDispatchEvents;

//End CHAPTER24MOD

/*
 * Tenant class describing an individual piece of data in a page.
 * It knows where it sits, what object is inside of it, and what
 * its idenitifer is such that it can find it's storage within a
 * page.
 */

//Patron Objects clipboard format
typedef struct tagPATRONOBJECT
    {
    POINTL      ptl;        //Location of object
    POINTL      ptlPick;    //Pick point from drag-drop operation
    SIZEL       szl;        //Extents of object (absolute)
    FORMATETC   fe;         //Actual object format
    } PATRONOBJECT, *PPATRONOBJECT;

//Values for hit-testing, drawing, and resize-tracking tenants
#define CXYHANDLE       5

//Tenant types (not persistent, but determined at load time)
typedef enum
    {
    TENANTTYPE_NULL=0,
    TENANTTYPE_STATIC,
    TENANTTYPE_EMBEDDEDOBJECT,
    TENANTTYPE_EMBEDDEDFILE,
    TENANTTYPE_EMBEDDEDOBJECTFROMDATA,
    TENANTTYPE_LINKEDOBJECT,
    TENANTTYPE_LINKEDFILE,
    TENANTTYPE_LINKEDOBJECTFROMDATA
    } TENANTTYPE, *PTENANTTYPE;

//State flags
//CHAPTER24MOD
//Switched #defines to an enum
enum
    {
    TENANTSTATE_DEFAULT =0x00000000,
    TENANTSTATE_SELECTED=0x00000001,
    TENANTSTATE_OPEN    =0x00000002,
    TENANTSTATE_SHOWTYPE=0x00000004,

    //New ones for controls.
    TENANTSTATE_CONTROL     =0x00000100,
    TENANTSTATE_EVENTS      =0x00000200,
    TENANTSTATE_BUTTON      =0x00000400,
    TENANTSTATE_LABEL       =0x00000800,
    TENANTSTATE_DEFBUTTON   =0x00001000,
    TENANTSTATE_CANCELBUTTON=0x00002000
    };

//To mask off non-control flags
#define STATEMASK_CONTROLS 0xFFFFFF00

//End CHAPTER24MOD

/*
 * Persistent information we need to save for each tenant, which is
 * done in the tenant list instead of with each tenant.  Since this
 * is a small structure it's best not to blow another stream for it
 * (overhead).
 */
typedef struct tagTENANTINFO
    {
    DWORD       dwID;
    RECTL       rcl;
    FORMATETC   fe;             //Excludes ptd
    short       fSetExtent;     //Call IOleObject::SetExtent on Run
    } TENANTINFO, *PTENANTINFO;

//CHAPTER24MOD
/*
 * Event handling constructs:  EVENTACTION is the type of
 * action to execute on an event.  EVENTMAP is an element of
 * an array to hold event mappings.
 */

typedef enum
    {
    ACTION_NONE=-1,
    ACTION_BEEPDEFAULT=MB_OK,
    ACTION_BEEPASTERISK=MB_ICONASTERISK,
    ACTION_BEEPEXCLAMATION=MB_ICONEXCLAMATION,
    ACTION_BEEPHAND=MB_ICONHAND,
    ACTION_BEEPQUESTION=MB_ICONQUESTION,
    ACTION_TAILING=-2
    } EVENTACTION;

typedef struct tagEVENTMAP
    {
    DISPID      id;             //Event ID
    EVENTACTION iAction;        //Action to take
    BSTR        bstrName;       //Event name (function only)
    } EVENTMAP, *PEVENTMAP;

class CEventMap
    {
    public:
        UINT            m_cEvents;
        LPTYPEINFO      m_pITypeInfo;
        PEVENTMAP       m_pEventMap;

    public:
        CEventMap(LPTYPEINFO);
        ~CEventMap(void);

        BOOL            Init(void);
        BOOL            Set(DISPID, EVENTACTION);
        EVENTACTION     Get(DISPID);
        void            Serialize(LPSTREAM);
        void            Deserialize(LPSTREAM);
    };

typedef CEventMap *PCEventMap;

//Events stream in the object storage
#define SZEVENTSSTREAM  OLETEXT("\003Event Mappings")

//End CHAPTER24MOD

class CTenant : public IUnknown
    {
    friend CImpIOleClientSite;
    friend CImpIAdviseSink;
    friend CImpIOleInPlaceSite;

    //CHAPTER24MOD
    friend CImpIOleControlSite;
    friend CImpIDispatch;

    friend CDispatchEvents;
    //End CHAPTER24MOD

    private:
        HWND            m_hWnd;         //Pages window
        DWORD           m_dwID;         //Persistent DWORD ID
        DWORD           m_cOpens;       //Count calls to Open

        BOOL            m_fInitialized; //Something here?
        LPUNKNOWN       m_pObj;         //The object here
        LPSTORAGE       m_pIStorage;    //Sub-storage for tenant

        FORMATETC       m_fe;           //Used to create the object
        DWORD           m_dwState;      //State flags
        RECTL           m_rcl;          //Space of this object
        CLSID           m_clsID;        //Object class (for statics)
        BOOL            m_fSetExtent;   //Call SetExtent on next run

        class CPages   *m_pPG;          //Pages window

        TENANTTYPE      m_tType;            //Type identifier
        ULONG           m_cRef;             //We're an object now
        LPOLEOBJECT     m_pIOleObject;      //IOleObject on m_pObj
        LPVIEWOBJECT2   m_pIViewObject2;    //IViewObject2 on m_pObj
        ULONG           m_grfMisc;          //OLEMISC flags
        BOOL            m_fRepaintEnabled;  //No redundant paints

        //Our interfaces
        PCImpIOleClientSite m_pImpIOleClientSite;
        PCImpIAdviseSink    m_pImpIAdviseSink;

        LPMONIKER       m_pmk;          //Relative moniker
        LPMONIKER       m_pmkFile;      //Container document moniker

        PCImpIOleInPlaceSite m_pImpIOleIPSite; //Our interface
        LPOLEINPLACEOBJECT   m_pIOleIPObject;  //The object
        RECT                 m_rcPos;          //Last object rect
        BOOL                 m_fInRectSet;     //Prevent reentrancy

        //CHAPTER24MOD
        PCImpIOleControlSite    m_pImpIOleControlSite; //Implemented
        PCImpIDispatch          m_pImpIDispatch;       //Implemented

        PCDispatchEvents        m_pDispEvents;      //Event Sink
        DWORD                   m_dwConnEvents;
        IID                     m_iidEvents;
        PCEventMap              m_pEventMap;

        //Control interfaces
        LPOLECONTROL        m_pIOleControl;      //Used
        LPDISPATCH          m_pIDispatchControl; //Used

        //Ambient properties
        OLE_COLOR           m_clrBack;
        OLE_COLOR           m_clrFore;
        IFontDisp          *m_pIFont;       //Copy from CPages
        LCID                m_lcid;
        BOOL                m_fDesignMode;
        BOOL                m_fUIDead;
        BOOL                m_fHatchHandles;

        //Other state information
        BOOL                m_fHaveControlInfo;
        CONTROLINFO         m_ctrlInfo;
        ULONG               m_cLockInPlace;
        BOOL                m_fPendingDeactivate;
        //End CHAPTER24MOD

    /*
     * This flag is used exculsively by the implementation of
     * IOleUILinkContainer on the page we're in for the Links
     * Dialog. Since we never use it ourselves, public here
     * is no big deal.
     */
    public:
        BOOL            m_fLinkAvail;

    protected:
        BOOL    ObjectInitialize(LPUNKNOWN, LPFORMATETC, DWORD);
        HRESULT CreateStatic(LPDATAOBJECT, LPFORMATETC
            , LPUNKNOWN *);

        //CHAPTER24MOD
        BOOL    ControlInitialize(void);
        //End CHAPTER24MOD

    public:
        CTenant(DWORD, HWND, CPages *);
        ~CTenant(void);

        //Gotta have an IUnknown for delegation
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        DWORD       GetID(void);
        UINT        GetStorageName(LPOLESTR);
        void        StorageGet(LPSTORAGE *);
        UINT        Create(TENANTTYPE, LPVOID, LPFORMATETC, PPOINTL
                        , LPSIZEL, LPSTORAGE, PPATRONOBJECT, DWORD);
        BOOL        Load(LPSTORAGE, PTENANTINFO);
        void        GetInfo(PTENANTINFO);
        BOOL        Open(LPSTORAGE);
        void        Close(BOOL);
        BOOL        Update(void);
        void        Destroy(LPSTORAGE);

        void        Select(BOOL, BOOL);
        void        ShowAsOpen(BOOL);
        void        ShowYourself(void);
        void        AddVerbMenu(HMENU, UINT);
        TENANTTYPE  TypeGet(void);
        void        CopyEmbeddedObject(LPDATAOBJECT, LPFORMATETC
                        , PPOINTL);
        void        ShowObjectType(BOOL);
        void        CopyLinkedObject(LPDATAOBJECT, LPFORMATETC
                        , PPOINTL);
        void        NotifyOfRename(LPTSTR, LPMONIKER, LPMONIKER);
        BOOL        Activate(LONG, LPMSG);

        void        Draw(HDC, DVTARGETDEVICE *, HDC, int, int
                        , BOOL, BOOL);
        void        Repaint(void);
        void        Invalidate(void);

        void        ObjectClassFormatAndIcon(LPCLSID, LPWORD
                        , LPTSTR *, HGLOBAL *, LPTSTR *);
        BOOL        SwitchOrUpdateAspect(HGLOBAL, BOOL);
        void        EnableRepaint(BOOL);

        BOOL        FIsSelected(void);
        BOOL        ConvertToStatic(void);

        void        ObjectGet(LPUNKNOWN *);
        void        FormatEtcGet(LPFORMATETC, BOOL);
        void        SizeGet(LPSIZEL, BOOL);
        void        SizeSet(LPSIZEL, BOOL, BOOL);
        void        RectGet(LPRECTL, BOOL);
        void        RectSet(LPRECTL, BOOL, BOOL);

        void        DeactivateInPlaceObject(BOOL);
        void        UpdateInPlaceObjectRects(LPCRECT, BOOL);
        HWND        ObjectWindow(void);

        //CHAPTER24MOD
        void        ToggleDesignMode(BOOL);
        void        ToggleUIDead(BOOL);
        void        ToggleHatchHandles(BOOL);
        PCEventMap  EventMap(void);
        DWORD       GetControlFlags();
        BOOL        TryMnemonic(LPMSG);
        void        AmbientChange(DISPID);
        //End CHAPTER24MOD
    };

typedef CTenant *PCTenant;

//Return codes for Create
#define CREATE_FAILED               0
#define CREATE_GRAPHICONLY          1
#define CREATE_PLACEDOBJECT         2

//CHAPTER24MOD
//Helper functions in CONNECT.CPP

BOOL InterfaceConnect(LPUNKNOWN, REFIID, LPUNKNOWN, LPDWORD);
BOOL InterfaceDisconnect(LPUNKNOWN, REFIID, LPDWORD);
BOOL ObjectTypeInfo(LPUNKNOWN, LPTYPEINFO *);
BOOL ObjectTypeInfoEvents(LPUNKNOWN, LPTYPEINFO *);
BOOL ObjectEventsIID(LPUNKNOWN, IID *);

//End CHAPTER24MOD

#endif //_TENANT_H_


CONTROL.CPP

/*
 * CONTROL.CPP
 * Polyline Chapter 24
 *
 * Additional interfaces above the Chapter 23 version of Polyline
 * to make it an OLE Control, specifically ISpecifyPropertyPages,
 * IProvideClassInfo, IOleControl, and IDispatch, none of which
 * are terribly complex.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

//ISpecifyPropertyPages implementation
/*
 * CImpISpecifyPP::CImpISpecifyPP
 * CImpISpecifyPP::~CImpISpecifyPP
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpISpecifyPP::CImpISpecifyPP(PCPolyline pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpISpecifyPP::~CImpISpecifyPP(void)
    {
    return;
    }

/*
 * CImpISpecifyPP::QueryInterface
 * CImpISpecifyPP::AddRef
 * CImpISpecifyPP::Release
 */

STDMETHODIMP CImpISpecifyPP::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpISpecifyPP::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpISpecifyPP::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpISpecifyPP::GetPages
 *
 * Purpose:
 *  Returns an array of GUIDs identifying the indivudual property
 *  pages used for this object.
 *
 * Parameters:
 *  pPages          CAUUID * pointing to a counted array of GUIDs.
 *                  This function allocates the array elements
 *                  and stores them in this structure.
 */

STDMETHODIMP CImpISpecifyPP::GetPages(CAUUID *pPages)
    {
    IMalloc *pIMalloc;
    GUID    *pGUID;

    pPages->cElems=0;
    pPages->pElems=NULL;

    if (FAILED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        return ResultFromScode(E_OUTOFMEMORY);

    pGUID=(GUID *)pIMalloc->Alloc(CPROPPAGES * sizeof(GUID));

    if (NULL!=pGUID)
        {
        //We use our own page plus the standard color page.
        pGUID[0]=CLSID_PolylinePropPage;

        //Fill the structure and return.
        pPages->cElems=CPROPPAGES;
        pPages->pElems=pGUID;
        }

    pIMalloc->Release();
    return (NULL!=pGUID) ? NOERROR
        : ResultFromScode(E_OUTOFMEMORY);
    }

//IProvideClassInfo interface implementation
/*
 * CImpIProvideClassInfo::CImpIProvideClassInfo
 * CImpIProvideClassInfo::~CImpIProvideClassInfo
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIProvideClassInfo::CImpIProvideClassInfo(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIProvideClassInfo::~CImpIProvideClassInfo(void)
    {
    return;
    }

/*
 * CImpIProvideClassInfo::QueryInterface
 * CImpIProvideClassInfo::AddRef
 * CImpIProvideClassInfo::Release
 */

STDMETHODIMP CImpIProvideClassInfo::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIProvideClassInfo::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIProvideClassInfo::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIProvideClassInfo::GetClassInfo
 *
 * Purpose:
 *  Returns the ITypeInfo through which the caller can retrieve
 *  information about the entire object.
 *
 * Parameters:
 *  ppTI            LPTYPEINFO * in which to store the ITypeInfo
 *                  pointer.
 */

STDMETHODIMP CImpIProvideClassInfo::GetClassInfo(LPTYPEINFO *ppTI)
    {
    if (NULL==ppTI)
        return ResultFromScode(E_POINTER);

    *ppTI=NULL;

    return m_pObj->m_pITypeLib->GetTypeInfoOfGuid(CLSID_Polyline19
        , ppTI);
    }

//IDispatch interface implementation

/*
 * CImpIDispatch::CImpIDispatch
 * CImpIDispatch::~CImpIDispatch
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIDispatch::CImpIDispatch(PCPolyline pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIDispatch::~CImpIDispatch(void)
    {
    return;
    }

/*
 * CImpIDispatch::QueryInterface
 * CImpIDispatch::AddRef
 * CImpIDispatch::Release
 */

STDMETHODIMP CImpIDispatch::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIDispatch::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIDispatch::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIDispatch::GetTypeInfoCount
 * CImpIDispatch::GetTypeInfo
 * CImpIDispatch::GetIDsOfNames
 *
 * The usual
 */

STDMETHODIMP CImpIDispatch::GetTypeInfoCount(UINT *pctInfo)
    {
    //We implement GetTypeInfo so return 1
    *pctInfo=1;
    return NOERROR;
    }

STDMETHODIMP CImpIDispatch::GetTypeInfo(UINT itInfo, LCID lcid
    , ITypeInfo **ppITypeInfo)
    {
    if (0!=itInfo)
        return ResultFromScode(TYPE_E_ELEMENTNOTFOUND);

    if (NULL==ppITypeInfo)
        return ResultFromScode(E_POINTER);

    *ppITypeInfo=NULL;

    //We ignore the LCID
    return m_pObj->m_pITypeLib->GetTypeInfoOfGuid
        (DIID_DIPolylineControl, ppITypeInfo);
    }

STDMETHODIMP CImpIDispatch::GetIDsOfNames(REFIID riid
    , OLECHAR **rgszNames, UINT cNames, LCID lcid, DISPID *rgDispID)
    {
    HRESULT     hr;
    ITypeInfo  *pTI;

    if (IID_NULL!=riid)
        return ResultFromScode(DISP_E_UNKNOWNINTERFACE);

    hr=GetTypeInfo(0, lcid, &pTI);

    if (SUCCEEDED(hr))
        {
        hr=DispGetIDsOfNames(pTI, rgszNames, cNames, rgDispID);
        pTI->Release();
        }

    return hr;
    }

/*
 * CImpIDispatch::Invoke
 *
 * Purpose:
 *  Calls a method in the dispatch interface or manipulates a
 *  property.
 *
 * Parameters:
 *  dispID          DISPID of the method or property of interest.
 *  riid            REFIID reserved, must be IID_NULL.
 *  lcid            LCID of the locale.
 *  wFlags          USHORT describing the context of the invocation.
 *  pDispParams     DISPPARAMS * to the array of arguments.
 *  pVarResult      VARIANT * in which to store the result.  Is
 *                  NULL if the caller is not interested.
 *  pExcepInfo      EXCEPINFO * to exception information.
 *  puArgErr        UINT * in which to store the index of an
 *                  invalid parameter if DISP_E_TYPEMISMATCH
 *                  is returned.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error code.
 */

STDMETHODIMP CImpIDispatch::Invoke(DISPID dispID, REFIID riid
    , LCID lcid, unsigned short wFlags, DISPPARAMS *pDispParams
    , VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr)
    {
    HRESULT     hr;
    ITypeInfo  *pTI;

    //riid is supposed to be IID_NULL always
    if (IID_NULL!=riid)
        return ResultFromScode(DISP_E_UNKNOWNINTERFACE);

    //Get the ITypeInfo for lcid
    hr=GetTypeInfo(0, lcid, &pTI);

    if (FAILED(hr))
        return hr;

    hr=pTI->Invoke(m_pObj->m_pImpIPolylineControl, dispID, wFlags
        , pDispParams, pVarResult, pExcepInfo, puArgErr);

    pTI->Release();
    return hr;
    }

//IPolylineControl interface implementation

/*
 * CImpIPolylineControl::CImpIPolylineControl
 * CImpIPolylineControl::~CImpIPolylineControl
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIPolylineControl::CImpIPolylineControl(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPolylineControl::~CImpIPolylineControl(void)
    {
    return;
    }

/*
 * CImpIPolylineControl::QueryInterface
 * CImpIPolylineControl::AddRef
 * CImpIPolylineControl::Release
 */

STDMETHODIMP CImpIPolylineControl::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPolylineControl::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPolylineControl::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPolylineControl::BackColor
 * CImpIPolylineControl::LineColor
 * CImpIPolylineControl::LineStyle
 * CImpIPolylineControl::Clear
 * CImpIPolylineControl::RemoveLastPoint
 *
 * Purpose:
 *  Dispatch interface entry points that map to IPolyline
 *  members.
 */

STDMETHODIMP_(void) CImpIPolylineControl::put_BackColor
    (OLE_COLOR cr)
    {
    COLORREF        crOld;

    m_pObj->m_pImpIPolyline->ColorSet(POLYLINECOLOR_BACKGROUND
        , cr, &crOld);
    return;
    }

STDMETHODIMP_(OLE_COLOR) CImpIPolylineControl::get_BackColor(void)
    {
    COLORREF        cr;

    m_pObj->m_pImpIPolyline->ColorGet(POLYLINECOLOR_BACKGROUND
        , &cr);

    return cr;
    }

STDMETHODIMP_(void) CImpIPolylineControl::put_LineColor
    (OLE_COLOR cr)
    {
    COLORREF        crOld;

    m_pObj->m_pImpIPolyline->ColorSet(POLYLINECOLOR_LINE
        , cr, &crOld);

    return;
    }

STDMETHODIMP_(OLE_COLOR) CImpIPolylineControl::get_LineColor(void)
    {
    COLORREF        cr;

    m_pObj->m_pImpIPolyline->ColorGet(POLYLINECOLOR_LINE, &cr);
    return cr;
    }

STDMETHODIMP_(void) CImpIPolylineControl::put_LineStyle
    (short iStyle)
    {
    UINT    i;

    m_pObj->m_pImpIPolyline->LineStyleSet(iStyle, &i);
    return;
    }

STDMETHODIMP_(short) CImpIPolylineControl::get_LineStyle(void)
    {
    UINT    iStyle;

    m_pObj->m_pImpIPolyline->LineStyleGet(&iStyle);
    return (short)iStyle;
    }

STDMETHODIMP CImpIPolylineControl::Clear(void)
    {
    return m_pObj->m_pImpIPolyline->New();
    }

STDMETHODIMP CImpIPolylineControl::RemoveLastPoint(void)
    {
    return m_pObj->m_pImpIPolyline->Undo();
    }

//IOleControl interface implementation

/*
 * CImpIOleControl::CImpIOleControl
 * CImpIOleControl::~CImpIOleControl
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleControl::CImpIOleControl(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleControl::~CImpIOleControl(void)
    {
    return;
    }

/*
 * CImpIOleControl::QueryInterface
 * CImpIOleControl::AddRef
 * CImpIOleControl::Release
 */

STDMETHODIMP CImpIOleControl::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleControl::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleControl::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleControl::GetControlInfo
 *
 * Purpose:
 *  Fills a CONTROLINFO structure containing information about
 *  the controls mnemonics and other behavioral aspects.
 *
 * Parameters:
 *  pCI             LPCONTROLINFO to the structure to fill
 */

STDMETHODIMP CImpIOleControl::GetControlInfo(LPCONTROLINFO pCI)
    {
    if (NULL==pCI)
        return ResultFromScode(E_INVALIDARG);

    *pCI=m_pObj->m_ctrlInfo;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleControl::OnMnemonic
 *
 * Purpose:
 *  Notifies the control that a mnemonic was activated.
 *
 * Parameters:
 *  pMsg            LPMSG containing the message that matches one of
 *                  the control's mnemonics.  The control uses this
 *                  to distinguish which mnemonic was pressed.
 */

STDMETHODIMP CImpIOleControl::OnMnemonic(LPMSG pMsg)
    {
    //No mnemonics
    return NOERROR;
    }

/*
 * CImpIOleControl::OnAmbientPropertyChange
 *
 * Purpose:
 *  Notifies the control that one or more of the container's ambient
 *  properties changed.
 *
 * Parameters:
 *  dispID          DISPID identifying the property, which can
 *                  be DISPID_UNKNOWN indicating that more than
 *                  one changed.
 */

STDMETHODIMP CImpIOleControl::OnAmbientPropertyChange(DISPID dispID)
    {
    /*
     * We detect any change in UIDead or ShowHatching.  Changes
     * in container colors do not affect us as we only use those
     * for initial values.
     */

    switch (dispID)
        {
        case DISPID_UNKNOWN:
            m_pObj->AmbientsInitialize(INITAMBIENT_SHOWHATCHING
                | INITAMBIENT_UIDEAD);
            break;

        case DISPID_AMBIENT_SHOWHATCHING:
            m_pObj->AmbientsInitialize(INITAMBIENT_SHOWHATCHING);
            break;

        case DISPID_AMBIENT_UIDEAD:
            m_pObj->AmbientsInitialize(INITAMBIENT_UIDEAD);
            break;
        }

    return NOERROR;
    }

/*
 * CImpIOleControl::FreezeEvents
 *
 * Purpose:
 *  Instructs the control to stop firing events or to continue
 *  firing them.
 *
 * Parameters:
 *  fFreeze         BOOL indicating to freeze (TRUE) or thaw (FALSE)
 *                  events from this control.
 */

STDMETHODIMP CImpIOleControl::FreezeEvents(BOOL fFreeze)
    {
    m_pObj->m_fFreezeEvents=fFreeze;
    return NOERROR;
    }

//CAdviseRouter class implementation
/*
 * CAdviseRouter::CAdviseRouter
 * CAdviseRouter::~CAdviseRouter
 *
 * Constructor Parameters:
 *  pIDispatch      IDispatch * to which we route notifications.
 *  pObj            PCPolyline to the control itself.
 */

CAdviseRouter::CAdviseRouter(IDispatch *pIDispatch
    , PCPolyline pObj)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pIDispatch=pIDispatch;
    return;
    }

CAdviseRouter::~CAdviseRouter(void)
    {
    ReleaseInterface(m_pIDispatch);
    return;
    }

/*
 * CAdviseRouter::QueryInterface
 * CAdviseRouter::AddRef
 * CAdviseRouter::Release
 *
 * Purpose:
 *  IUnknown members for this IPolylineAdviseSink implementations.
 */

STDMETHODIMP CAdviseRouter::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IPolylineAdviseSink10==riid)
        {
        *ppv=this;
        AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CAdviseRouter::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CAdviseRouter::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CAdviseRouter::OnPointChange
 * CAdviseRouter::OnSizeChange
 * CAdviseRouter::OnColorChange
 * CAdviseRouter::OnLineStyleChange
 *
 * Purpose:
 *  Invokes the same member in the IDispatch pointer we hold.
 */

STDMETHODIMP_(void) CAdviseRouter::OnPointChange(void)
    {
    Invoke(EVENT_ONPOINTCHANGE);
    return;
    }

STDMETHODIMP_(void) CAdviseRouter::OnSizeChange(void)
    {
    Invoke(EVENT_ONSIZECHANGE);
    return;
    }

STDMETHODIMP_(void) CAdviseRouter::OnColorChange(void)
    {
    Invoke(EVENT_ONCOLORCHANGE);
    return;
    }

STDMETHODIMP_(void) CAdviseRouter::OnLineStyleChange(void)
    {
    Invoke(EVENT_ONLINESTYLECHANGE);
    return;
    }

/*
 * CAdviseRouter::Invoke
 *
 * Purpose:
 *  Calls IDispatch::Invoke for any of the events we handle.
 *  None of these have any arguments.
 *
 * Parameters:
 *  dispID          DISPID of the event to fire.
 *
 * Return Value:
 *  None
 */

void CAdviseRouter::Invoke(DISPID dispID)
    {
    DISPPARAMS  dp;
    VARIANT     va;

    if (m_pObj->m_fFreezeEvents)
        return;

    m_pIDispatch->Invoke(dispID, IID_NULL
        , LOCALE_USER_DEFAULT, DISPATCH_METHOD, &dp
        , &va, NULL, NULL);

    return;
    }


DLLPOLY.CPP

/*
 * DLLPOLY.CPP
 * Polyline Component Chapter 24
 *
 * Polyline component object used in CoCosmo that supports a custom
 * interface IPolyline.  Contains DLL entry code and the component
 * object exports DllGetClassObject and DllCanUnloadNow and the
 * class factory object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "polyline.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

//DLL Instance handle
HINSTANCE   g_hInst=0;

/*
 * LibMain32 (Win32)
 *
 * Purpose:
 *  Entry point for Win32 DLLs that calls the Win16 LibMain.
 */

#ifdef WIN32
BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    if (DLL_PROCESS_DETACH==ulReason)
        {
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    return (0!=LibMain(hInstance, 0,  0, NULL));
    }
#endif

/*
 * LibMain (also called from Win32 LibMain32)
 *
 * Purpose:
 *  DLL-specific entry point called from LibEntry.
 */

int PASCAL LibMain(HINSTANCE hInst, WORD wDataSeg
    , WORD cbHeapSize, LPSTR lpCmdLine)
    {
    WNDCLASS    wc;

    if (GetClassInfo(hInst, SZCLASSPOLYLINE, &wc))
        return (int)hInst;

   #ifndef WIN32
    if (0!=cbHeapSize)
        UnlockData(0);
   #endif

    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.hInstance     = hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = PolylineWndProc;
    wc.cbWndExtra    = CBPOLYLINEWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_CROSS);
    wc.hbrBackground = NULL;
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSPOLYLINE;

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

    if (!HatchWindowRegister(hInst))
        return 0;

    g_hInst=hInst;
    return (int)hInst;
    }

/*
 * DllGetClassObject
 *
 * Purpose:
 *  Provides an IClassFactory for a given CLSID that this DLL is
 *  registered to support.  This DLL is placed under the CLSID
 *  in the registration database as the InProcServer.
 *
 * Parameters:
 *  clsID           REFCLSID that identifies the class factory
 *                  desired.  Since this parameter is passed this
 *                  DLL can handle any number of objects simply
 *                  by returning different class factories here
 *                  for different CLSIDs.
 *
 *  riid            REFIID specifying the interface the caller wants
 *                  on the class object, usually IID_ClassFactory.
 *
 *  ppv             PPVOID in which to return the interface
 *                  pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, otherwise an error code.
 */

HRESULT APIENTRY DllGetClassObject(REFCLSID rclsid
    , REFIID riid, PPVOID ppv)
    {
    if (CLSID_Polyline19!=rclsid)
        return ResultFromScode(E_FAIL);

    //Check that we can provide the interface
    if (IID_IUnknown!=riid && IID_IClassFactory!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Return our IClassFactory for Polyline objects
    *ppv=new CPolylineClassFactory;

    if (NULL==*ppv)
        return ResultFromScode(E_OUTOFMEMORY);

    //AddRef the object through any interface we return
    ((LPUNKNOWN)*ppv)->AddRef();
    g_cObj++;
    return NOERROR;
    }

/*
 * DllCanUnloadNow
 *
 * Purpose:
 *  Answers if the DLL can be freed, that is, if there are no
 *  references to anything this DLL provides.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if nothing is using us, FALSE otherwise.
 */

STDAPI DllCanUnloadNow(void)
    {
    SCODE   sc;

    //Our answer is whether there are any object or locks
    sc=(0L==g_cObj && 0L==g_cLock) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * ObjectDestroyed
 *
 * Purpose:
 *  Function for the Polyline object to call when it gets destroyed.
 *  Since we're in a DLL we only track the number of objects here
 *  letting DllCanUnloadNow take care of the rest.
 */

void ObjectDestroyed(void)
    {
    g_cObj--;
    return;
    }

/*
 * CPolylineClassFactory::CPolylineClassFactory
 *
 * Purpose:
 *  Constructor for an object supporting an IClassFactory that
 *  instantiates Polyline objects.
 *
 * Parameters:
 *  None
 */

CPolylineClassFactory::CPolylineClassFactory(void)
    {
    m_cRef=0L;
    return;
    }

/*
 * CPolylineClassFactory::~CPolylineClassFactory
 *
 * Purpose:
 *  Destructor for a CPolylineClassFactory object.  This will be
 *  called when we Release the object to a zero reference count.
 */

CPolylineClassFactory::~CPolylineClassFactory(void)
    {
    return;
    }

/*
 * CPolylineClassFactory::QueryInterface
 * CPolylineClassFactory::AddRef
 * CPolylineClassFactory::Release
 */

STDMETHODIMP CPolylineClassFactory::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CPolylineClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPolylineClassFactory::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    ObjectDestroyed();
    return 0L;
    }

/*
 * CPolylineClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates a Polyline object.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we
 *                  are being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          PPVOID in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_NOINTERFACE
 *                  if we cannot support the requested interface.
 */

STDMETHODIMP CPolylineClassFactory::CreateInstance
    (LPUNKNOWN pUnkOuter, REFIID riid, PPVOID ppvObj)
    {
    PCPolyline          pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //Verify that a controlling unknown asks for IUnknown
    if (NULL!=pUnkOuter && IID_IUnknown!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Create the object.  This also creates a window.
    pObj=new CPolyline(pUnkOuter, ObjectDestroyed, g_hInst);

    if (NULL==pObj)
        return hr;

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    //Kill the object if initial creation or Init failed.
    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

/*
 * CPolylineClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the DLL.  If the lock
 *  count goes to zero and there are no objects, the DLL is allowed
 *  to unload.  See DllCanUnloadNow.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CPolylineClassFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        g_cLock--;

    return NOERROR;
    }


ICONNPT.CPP

/*
 * ICONNPT.CPP
 * Polyline Component Chapter 23
 *
 * Implementation of CImpIConnectionPoint for the Polyline object
 * as well as CConnectionPoint.
 *
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIConnPtCont:CImpIConnPtCont
 * CImpIConnPtCont::~CImpIConnPtCont
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIConnPtCont::CImpIConnPtCont(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIConnPtCont::~CImpIConnPtCont(void)
    {
    return;
    }

/*
 * CImpIConnPtCont::QueryInterface
 * CImpIConnPtCont::AddRef
 * CImpIConnPtCont::Release
 */

STDMETHODIMP CImpIConnPtCont::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIConnPtCont::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIConnPtCont::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIConnPtCont::EnumConnectionPoints
 *
 * Purpose:
 *  Not implemented.
 *
 * Return Value:
 *  HRESULT         E_NOTIMPL
 */

STDMETHODIMP CImpIConnPtCont::EnumConnectionPoints
    (LPENUMCONNECTIONPOINTS *ppEnum)
    {
    *ppEnum=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIConnPtCont::FindConnectionPoint
 *
 * Purpose:
 *  Returns a pointer to the IConnectionPoint for a given
 *  outgoing IID.
 *
 * Parameters:
 *  riid            REFIID of the outgoing interface for which
 *                  a connection point is desired.
 *  ppCP            IConnectionPoint ** in which to return
 *                  the pointer after calling AddRef.
 *
 * Return Value:
 *  HRESULT         NOERROR if the connection point is found,
 *                  E_NOINTERFACE if it's not supported.
 */

STDMETHODIMP CImpIConnPtCont::FindConnectionPoint(REFIID riid
    , IConnectionPoint **ppCP)
    {
    *ppCP=NULL;

    //CHAPTER24MOD
    if (IID_IPolylineAdviseSink10==riid
        || DIID_DIPolylineAdviseSink10==riid)
        {
        /*
         * This tells the connection point which one we're
         * using.  Note that Polyline only has one
         * connection point and can handle only one sink.
         */
        m_pObj->m_pConnPt->SetIID(riid);

        return m_pObj->m_pConnPt->QueryInterface
            (IID_IConnectionPoint, (PPVOID)ppCP);
        }
    //End CHAPTER24MOD

    return ResultFromScode(E_NOINTERFACE);
    }

//CConnectionPoint implementation

/*
 * CConnectionPoint::CConnectionPoint
 * CConnectionPoint::~CConnectionPoint
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.  We can
 *                  query this for the IConnectionPointContainer
 *                  interface we might need.
 */

CConnectionPoint::CConnectionPoint(PCPolyline pObj)
    {
    m_cRef=0;

    /*
     * Our lifetime is controlled by the connectable object itself,
     * although other external clients will call AddRef and Release.
     * Since we're nested in the connectable object's lifetime,
     * there's no need to call AddRef on pObj.
     */
    m_pObj=pObj;
    return;
    }

CConnectionPoint::~CConnectionPoint(void)
    {
    ReleaseInterface(m_pObj->m_pAdv);
    return;
    }

//CHAPTER24MOD

/*
 * CConnectionPoint::SetIID
 *
 * Purpose:
 *  Informs the connection point which outgoing interface
 *  it should use (vtable or dispatch interface).
 *
 * Parameters:
 *  riid            REFIID of the interface we're using.
 */

void CConnectionPoint::SetIID(REFIID riid)
    {
    m_iid=riid;
    return;
    }

//End CHAPTER24MOD

/*
 * CConnectionPoint::QueryInterface
 * CConnectionPoint::AddRef
 * CConnectionPoint::Release
 *
 * Purpose:
 *  Non-delegating IUnknown members for CConnectionPoint.
 */

STDMETHODIMP CConnectionPoint::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IConnectionPoint==riid)
        *ppv=(LPVOID)this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CConnectionPoint::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CConnectionPoint::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CConnectionPoint::GetConnectionInterface
 *
 * Purpose:
 *  Returns the IID of the outgoing interface supported through
 *  this connection point.
 *
 * Parameters:
 *  pIID            IID * in which to store the IID.
 */

STDMETHODIMP CConnectionPoint::GetConnectionInterface(IID *pIID)
    {
    if (NULL==pIID)
        return ResultFromScode(E_POINTER);

    //CHPATER24MOD
    *pIID=m_iid;
    //End CHAPTER24MOD
    return NOERROR;
    }

/*
 * CConnectionPoint::GetConnectionPointContainer
 *
 * Purpose:
 *  Returns a pointer to the IConnectionPointContainer that
 *  is manageing this connection point.
 *
 * Parameters:
 *  ppCPC           IConnectionPointContainer ** in which to return
 *                  the pointer after calling AddRef.
 */

STDMETHODIMP CConnectionPoint::GetConnectionPointContainer
    (IConnectionPointContainer **ppCPC)
    {
    return m_pObj->QueryInterface(IID_IConnectionPointContainer
        , (void **)ppCPC);
    }

/*
 * CConnectionPoint::Advise
 *
 * Purpose:
 *  Provides this connection point with a notification sink to
 *  call whenever the appropriate outgoing function/event occurs.
 *
 * Parameters:
 *  pUnkSink        LPUNKNOWN to the sink to notify.  The connection
 *                  point must QueryInterface on this pointer to obtain
 *                  the proper interface to call.  The connection
 *                  point must also insure that any pointer held has
 *                  a reference count (QueryInterface will do it).
 *  pdwCookie       DWORD * in which to store the connection key for
 *                  later calls to Unadvise.
 */

STDMETHODIMP CConnectionPoint::Advise(LPUNKNOWN pUnkSink
    , DWORD *pdwCookie)
    {
    IPolylineAdviseSink10  *pSink;

    *pdwCookie=0;

    //Only allow one connection
    if (NULL!=m_pObj->m_pAdv)
        return ResultFromScode(CONNECT_E_ADVISELIMIT);

    //CHAPTER24MOD
    /*
     * Polyline code is set to handle calls to
     * IPolylineAdviseSink10 everywhere else around the code.
     * To handle calling a control container's dispinterface
     * implementation of the dispatch version of this interface,
     * we'll create an instance of our own CAdviseRouter
     * and get an IPolylineAdviseSink10 from it.  That router
     * will then convert vtable calls into IDispatch calls.
     */
    if (DIID_DIPolylineAdviseSink10==m_iid)
        {
        IDispatch  *pIDisp;

        if (FAILED(pUnkSink->QueryInterface(m_iid
            , (PPVOID)&pIDisp)))
            {
            return ResultFromScode(CONNECT_E_CANNOTCONNECT);
            }

        /*
         * We have IDispatch, create a wrapper for it,
         * which takes ownership of the pointer.
         */
        pSink=new CAdviseRouter(pIDisp, m_pObj);
        pSink->AddRef();
        }

    //Otherwise, do what we always did before
    if (IID_IPolylineAdviseSink10==m_iid)
        {
        //Check for the right interface on the sink.
        if (FAILED(pUnkSink->QueryInterface(m_iid
            , (PPVOID)&pSink)))
            {
            return ResultFromScode(CONNECT_E_CANNOTCONNECT);
            }
        }
    //End CHAPTER24MOD

    *pdwCookie=ADVISEKEY;
    m_pObj->m_pAdv=pSink;
    return NOERROR;
    }

/*
 * CConnectionPoint::Unadvise
 *
 * Purpose:
 *  Terminates the connection to the notification sink identified
 *  with dwCookie (that was returned from Advise).  The connection
 *  point has to Release any held pointers for that sink.
 *
 * Parameters:
 *  dwCookie        DWORD connection key from Advise.
 */

STDMETHODIMP CConnectionPoint::Unadvise(DWORD dwCookie)
    {
    if (0==dwCookie)
        return ResultFromScode(E_INVALIDARG);

    if (ADVISEKEY!=dwCookie)
        ResultFromScode(CONNECT_E_NOCONNECTION);

    ReleaseInterface(m_pObj->m_pAdv);
    return NOERROR;
    }

/*
 * CConnectionPoint::EnumConnections
 *
 * Purpose:
 *  Not implemented.
 */

STDMETHODIMP CConnectionPoint::EnumConnections
    (LPENUMCONNECTIONS *ppEnum)
    {
    *ppEnum=NULL;
    return ResultFromScode(E_NOTIMPL);
    }


IIPAOBJ.CPP

/*
 * IIPAOBJ.CPP
 * Polyline Component Chapter 24
 *
 * IOleInPlaceActiveObject interface implementation for Polyline
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIOleInPlaceActiveObject::CImpIOleInPlaceActiveObject
 * CImpIOleInPlaceActiveObject::~CImpIOleInPlaceActiveObject
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleInPlaceActiveObject::CImpIOleInPlaceActiveObject
    (PCPolyline pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleInPlaceActiveObject::~CImpIOleInPlaceActiveObject(void)
    {
    return;
    }

/*
 * CImpIOleInPlaceActiveObject::QueryInterface
 * CImpIOleInPlaceActiveObject::AddRef
 * CImpIOleInPlaceActiveObject::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleInPlaceActiveObject object.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    /*
     * This interface should be stand-alone on an object such that a
     * container cannot QueryInterface for it through any other
     * object interface, relying instead of calls to SetActiveObject
     * for it.  By implementing QueryInterface here ourselves, we
     * prevent such abuses.  Note that reference counting still uses
     * CFigure.
     */

    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IOleWindow==riid
        || IID_IOleInPlaceActiveObject==riid)
        *ppv=this;

    //AddRef any interface we'll return.
    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceActiveObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceActiveObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleInPlaceActiveObject::GetWindow
 *
 * Purpose:
 *  Retrieves the handle of the window associated with the object on
 *  which this interface is implemented.
 *
 * Parameters:
 *  phWnd           HWND * in which to store the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_FAIL if there is no
 *                  window.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::GetWindow(HWND *phWnd)
    {
    *phWnd=m_pObj->m_pHW->Window();;
    return NOERROR;
    }

/*
 * CImpIOleInPlaceActiveObject::ContextSensitiveHelp
 *
 * Purpose:
 *  Instructs the object on which this interface is implemented to
 *  enter or leave a context-sensitive help mode.
 *
 * Parameters:
 *  fEnterMode      BOOL TRUE to enter the mode, FALSE otherwise.
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::ContextSensitiveHelp
    (BOOL fEnterMode)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleInPlaceActiveObject::TranslateAccelerator
 *
 * Purpose:
 *  Requests that the active in-place object translate the message
 *  given in pMSG if appropriate.  This is only called for DLL
 *  servers where the container's message loop is running.  EXE
 *  servers have control of the message loop so this will not be
 *  called in such cases.
 *
 * Parameters:
 *  pMSG            LPMSG to the message to translate.
 *
 * Return Value:
 *  HRESULT         NOERROR if translates, S_FALSE if not.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::TranslateAccelerator
    (LPMSG pMSG)
    {
    //We have no accelerators, but this is called anyway
    return ResultFromScode(S_FALSE);
    }

/*
 * CImpIOleInPlaceActiveObject::OnFrameWindowActivate
 *
 * Purpose:
 *  Informs the in-place object that the container's frame window
 *  was either activated or deactivated.  Not currently used.
 *
 * Parameters:
 *  fActivate       BOOL TRUE if the frame is active,
 *                  FALSE otherwise
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::OnFrameWindowActivate
    (BOOL fActivate)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleInPlaceActiveObject::OnDocWindowActivate
 *
 * Purpose:
 *  Informs the in-place object that the document window in the
 *  container is either becoming active or deactive.  On this call
 *  the object must either add or remove frame-level tools,
 *  including the mixed menu, depending on fActivate.
 *
 * Parameters:
 *  fActivate       BOOL TRUE if the document is active,
 *                  FALSE otherwise
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::OnDocWindowActivate
    (BOOL fActivate)
    {
    if (NULL==m_pObj->m_pIOleIPFrame)
        return NOERROR;

    if (fActivate)
        {
       #ifdef WIN32ANSI
        OLECHAR     szTemp[40];

        MultiByteToWideChar(CP_ACP, 0
            , (*m_pObj->m_pST)[IDS_USERTYPE], -1, szTemp, 40);
        m_pObj->m_pIOleIPFrame->SetActiveObject(this
            , szTemp);
       #else
        m_pObj->m_pIOleIPFrame->SetActiveObject(this
            , (*m_pObj->m_pST)[IDS_USERTYPE]);
       #endif

        m_pObj->m_pIOleIPFrame->SetMenu(m_pObj->m_hMenuShared
            , m_pObj->m_hOLEMenu, m_pObj->m_hWnd);

        //Nothing to do with tools.
        }
    else
        {
        m_pObj->m_pIOleIPFrame->SetActiveObject(NULL, NULL);
        }

    return NOERROR;
    }

/*
 * CImpIOleInPlaceActiveObject::ResizeBorder
 *
 * Purpose:
 *  Informs the object that the frame or document size changed in
 *  which case the object may need to resize any of its frame or
 *  document-level tools to match.
 *
 * Parameters:
 *  pRect           LPCRECT indicating the new size of the window
 *                  of interest.
 *  pIUIWindow      LPOLEINPLACEUIWINDOW pointing to an
 *                  IOleInPlaceUIWindow interface on the container
 *                  object of interest.  We use this to do
 *                  border-space negotiation.
 *
 *  fFrame          BOOL indicating if the frame was resized (TRUE)
 *                  or the document (FALSE)
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::ResizeBorder(LPCRECT pRect
    , LPOLEINPLACEUIWINDOW pIUIWindow, BOOL fFrame)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleInPlaceActiveObject::EnableModeless
 *
 * Purpose:
 *  Instructs the object to show or hide any modeless popup windows
 *  that it may be using when activated in-place.
 *
 * Parameters:
 *  fEnable         BOOL indicating to enable/show the windows
 *                  (TRUE) or to hide them (FALSE).
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::EnableModeless
    (BOOL fActivate)
    {
    return ResultFromScode(E_NOTIMPL);
    }


IIPOBJ.CPP

/*
 * IIPOBJ.CPP
 * Polyline Component Chapter 24
 *
 * IOleInPlaceObject interface implementation for Polyline
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIOleInPlaceObject::CImpIOleInPlaceObject
 * CImpIOleInPlaceObject::~CImpIOleInPlaceObject
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleInPlaceObject::CImpIOleInPlaceObject(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleInPlaceObject::~CImpIOleInPlaceObject(void)
    {
    return;
    }

/*
 * CImpIOleInPlaceObject::QueryInterface
 * CImpIOleInPlaceObject::AddRef
 * CImpIOleInPlaceObject::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleInPlaceObject object.
 */

STDMETHODIMP CImpIOleInPlaceObject::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleInPlaceObject::GetWindow
 *
 * Purpose:
 *  Retrieves the handle of the window associated with the object
 *  on which this interface is implemented.
 *
 * Parameters:
 *  phWnd           HWND * in which to store the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_FAIL if there is no
 *                  window.
 */

STDMETHODIMP CImpIOleInPlaceObject::GetWindow(HWND *phWnd)
    {
    if (NULL!=m_pObj->m_pHW)
        *phWnd=m_pObj->m_pHW->Window();
    else
        *phWnd=m_pObj->m_hWnd;

    return NOERROR;
    }

/*
 * CImpIOleInPlaceObject::ContextSensitiveHelp
 *
 * Purpose:
 *  Instructs the object on which this interface is implemented to
 *  enter or leave a context-sensitive help mode.
 *
 * Parameters:
 *  fEnterMode      BOOL TRUE to enter the mode, FALSE otherwise.
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code
 */

STDMETHODIMP CImpIOleInPlaceObject::ContextSensitiveHelp
    (BOOL fEnterMode)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleInPlaceObject::InPlaceDeactivate
 *
 * Purpose:
 *  Instructs the object to deactivate itself from an in-place state
 *  and to discard any Undo state.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code
 */

STDMETHODIMP CImpIOleInPlaceObject::InPlaceDeactivate(void)
    {
    m_pObj->InPlaceDeactivate();
    return NOERROR;
    }

/*
 * CImpIOleInPlaceObject::UIDeactivate
 *
 * Purpose:
 *  Instructs the object to just remove any in-place user interface
 *  but to do no other deactivation.  The object should just hide
 *  the UI components but not destroy them until InPlaceDeactivate
 *  is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code
 */

STDMETHODIMP CImpIOleInPlaceObject::UIDeactivate(void)
    {
    m_pObj->UIDeactivate();
    return NOERROR;
    }

/*
 * CImpIOleInPlaceObject::SetObjectRects
 *
 * Purpose:
 *  Provides the object with rectangles describing the position of
 *  the object in the container window as well as its visible area.
 *
 * Parameters:
 *  prcPos          LPCRECT providing the object's full rectangle
 *                  relative to the continer's document.  The object
 *                  should scale to this rectangle.
 *  prcClip         LPCRECT describing the visible area of the object
 *                  which should not draw outside these areas.
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code
 */

STDMETHODIMP CImpIOleInPlaceObject::SetObjectRects(LPCRECT prcPos
    , LPCRECT prcClip)
    {
    if (NULL!=m_pObj->m_pHW)
        m_pObj->m_pHW->RectsSet((LPRECT)prcPos, (LPRECT)prcClip);

    return NOERROR;
    }

/*
 * CImpIOleInPlaceObject::ReactivateAndUndo
 *
 * Purpose:
 *  Instructs the object to reactivate itself in-place and perform
 *  whatever Undo means for it.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code
 */

STDMETHODIMP CImpIOleInPlaceObject::ReactivateAndUndo(void)
    {
    return m_pObj->InPlaceActivate(m_pObj->m_pIOleClientSite, TRUE);
    }


IOLEOBJ.CPP

/*
 * IOLEOBJ.CPP
 * Polyline Component Chapter 24
 *
 * Implementation of the IOleObject interface for Polyline.  Some of
 * these just pass through to the default handler which does default
 * implementations.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIOleObject::CImpIOleObject
 * CImpIOleObject::~CImpIOleObject
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleObject::CImpIOleObject(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleObject::~CImpIOleObject(void)
    {
    return;
    }

/*
 * CImpIOleObject::QueryInterface
 * CImpIOleObject::AddRef
 * CImpIOleObject::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleObject object.
 */

STDMETHODIMP CImpIOleObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleObject::SetClientSite
 * CImpIOleObject::GetClientSite
 *
 * Purpose:
 *  Manages the IOleClientSite pointer of our container.
 */

STDMETHODIMP CImpIOleObject::SetClientSite
    (LPOLECLIENTSITE pIOleClientSite)
    {
    if (NULL!=m_pObj->m_pIOleClientSite)
        m_pObj->m_pIOleClientSite->Release();

    m_pObj->m_pIOleClientSite=pIOleClientSite;

    if (NULL!=m_pObj->m_pIOleClientSite)
        {
        HRESULT         hr;
        LPMONIKER       pmk;
        LPOLECONTAINER  pIOleCont;

        m_pObj->m_pIOleClientSite->AddRef();

        /*
         * Within IRunnableObject::Run we're supposed to register
         * ourselves as running...however, the moniker has to come
         * from the container's IOleClientSite::GetMoniker.  But
         * Run is called before SetClientSite here, so we have to
         * register now that we do have the client site as well
         * as lock the container.
         */

        hr=m_pObj->m_pIOleClientSite->GetMoniker
            (OLEGETMONIKER_ONLYIFTHERE, OLEWHICHMK_OBJFULL, &pmk);

        if (SUCCEEDED(hr))
            {
            INOLE_RegisterAsRunning(this, pmk, 0
                , &m_pObj->m_dwRegROT);
            pmk->Release();
            }

        hr=m_pObj->m_pIOleClientSite->GetContainer(&pIOleCont);

        if (SUCCEEDED(hr))
            {
            m_pObj->m_fLockContainer=TRUE;
            pIOleCont->LockContainer(TRUE);
            pIOleCont->Release();
            }

        //CHAPTER24MOD
        /*
         * Go get the container's IDispatch for ambient
         * properties if it has one, and initilize ourself
         * with those properties.
         */
        hr=m_pObj->m_pIOleClientSite->QueryInterface(IID_IDispatch
            , (void **)&m_pObj->m_pIDispatchAmbients);

        if (SUCCEEDED(hr))
            m_pObj->AmbientsInitialize(INITAMBIENT_ALL);
        //End CHAPTER24MOD
        }

    return NOERROR;
    }

STDMETHODIMP CImpIOleObject::GetClientSite(LPOLECLIENTSITE *ppSite)
    {
    //Be sure to AddRef the new pointer you are giving away.
    *ppSite=m_pObj->m_pIOleClientSite;
    m_pObj->m_pIOleClientSite->AddRef();

    return NOERROR;
    }

/*
 * CImpIOleObject::SetHostNames
 *
 * Purpose:
 *  Provides the object with names of the container application and
 *  the object in the container to use in object user interface.
 *
 * Parameters:
 *  pszApp          LPCOLESTR of the container application.
 *  pszObj          LPCOLESTR of some name that is useful in window
 *                  titles.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleObject::SetHostNames(LPCOLESTR pszApp
    , LPCOLESTR pszObj)
    {
    if (NULL!=m_pObj->m_hDlg)
        {
        TCHAR       szTemp[128];

       #ifdef WIN32ANSI
        char        szObj[80];
        WideCharToMultiByte(CP_ACP, 0, pszObj, -1, szObj, 80
            , NULL, NULL);
        wsprintf(szTemp, SZPOLYFRAMETITLE, szObj);
       #else
        wsprintf(szTemp, SZPOLYFRAMETITLE, pszObj);
       #endif
        SetWindowText(m_pObj->m_hDlg, szTemp);
        }

    return NOERROR;
    }

/*
 * CImpIOleObject::Close
 *
 * Purpose:
 *  Forces the object to close down its user interface and unload.
 *
 * Parameters:
 *  dwSaveOption    DWORD describing the circumstances under which
 *                  the object is being saved and closed.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::Close(DWORD dwSaveOption)
    {
    HWND        hWnd;
    BOOL        fSave=FALSE;

    hWnd=m_pObj->m_hDlg;

    //If object is dirty and we're asked to save, save it and close.
    if (OLECLOSE_SAVEIFDIRTY==dwSaveOption && m_pObj->m_fDirty)
        fSave=TRUE;

    /*
     * If asked to prompt, only do so if dirty, then if we get a
     * YES, save as usual and close.  On NO, just close.  On
     * CANCEL return OLE_E_PROMPTSAVECANCELLED.
     */
    if (OLECLOSE_PROMPTSAVE==dwSaveOption && m_pObj->m_fDirty)
        {
        TCHAR       szTitle[20];
        TCHAR       szTemp[80];
        UINT        uRet;

        lstrcpy(szTitle, m_pObj->String(IDS_CLOSECAPTION));
        lstrcpy(szTemp, m_pObj->String(IDS_CLOSEPROMPT));

        uRet=MessageBox(hWnd, szTemp, szTitle, MB_YESNOCANCEL);

        if (IDCANCEL==uRet)
            return ResultFromScode(OLE_E_PROMPTSAVECANCELLED);

        if (IDYES==uRet)
            fSave=TRUE;
        }

    if (fSave)
        {
        m_pObj->SendAdvise(OBJECTCODE_SAVEOBJECT);
        m_pObj->SendAdvise(OBJECTCODE_SAVED);
        }

    //We get directly here on OLECLOSE_NOSAVE.
    if (m_pObj->m_fLockContainer)
        {
        //Match LockContainer call from SetClientSite
        LPOLECONTAINER  pIOleCont;

        if (SUCCEEDED(m_pObj->m_pIOleClientSite
            ->GetContainer(&pIOleCont)))
            {
            pIOleCont->LockContainer(FALSE);
            pIOleCont->Release();
            }
        }

    if (NULL!=hWnd)
        {
        //This hides the window and sends the appropriate notify.
        DoVerb(OLEIVERB_HIDE, NULL, NULL, -1, NULL, NULL);

        m_pObj->SendAdvise(OBJECTCODE_CLOSED);
        PostMessage(hWnd, POLYM_CLOSE, 0, 0L);
        }

    return NOERROR;
    }

/*
 * CImpIOleObject::DoVerb
 *
 * Purpose:
 *  Executes an object-defined action.
 *
 * Parameters:
 *  iVerb           LONG index of the verb to execute.
 *  pMSG            LPMSG describing the event causing the
 *                  activation.
 *  pActiveSite     LPOLECLIENTSITE to the site involved.
 *  lIndex          LONG the piece on which execution is happening.
 *  hWndParent      HWND of the window in which the object can play
 *                  in-place.
 *  pRectPos        LPRECT of the object in hWndParent where the
 *                  object can play in-place if desired.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::DoVerb(LONG iVerb, LPMSG pMSG
    , LPOLECLIENTSITE pActiveSite, LONG lIndex, HWND hWndParent
    , LPCRECT pRectPos)
    {
    HRESULT     hr;
    //CHAPTER24MOD
    CAUUID      caGUID;
    //End CHAPTER24MOD

    switch (iVerb)
        {
        case OLEIVERB_HIDE:
            if (NULL!=m_pObj->m_pIOleIPSite)
                {
                if (NULL!=m_pObj->m_pHW)
                    ShowWindow(m_pObj->m_pHW->Window(), SW_HIDE);
                }
            else
                {
                if (NULL!=m_pObj->m_hDlg)
                    ShowWindow(m_pObj->m_hDlg, SW_HIDE);
                }

            m_pObj->SendAdvise(OBJECTCODE_HIDEWINDOW);
            m_pObj->m_fAllowInPlace=TRUE;
            break;

        case OLEIVERB_PRIMARY:
        case OLEIVERB_SHOW:
            if (NULL!=m_pObj->m_pIOleIPSite)
                {
                if (NULL!=m_pObj->m_pHW)
                    ShowWindow(m_pObj->m_pHW->Window(), SW_HIDE);

                return NOERROR; //Already active
                }

            if (m_pObj->m_fAllowInPlace)
                {
                if (SUCCEEDED(m_pObj->InPlaceActivate(pActiveSite
                    , TRUE)))
                    return NOERROR;
                }
            //FALL THROUGH

        case OLEIVERB_OPEN:
            if (NULL!=m_pObj->m_pIOleIPSite)
                {
                m_pObj->InPlaceDeactivate();
                m_pObj->m_fAllowInPlace=FALSE;
                }

            /*
             * If we're not running, make sure we are.  In any
             * case, make the dialog visible and insure it has
             * the right parent now.
             */
            hr=NOERROR;
            if (NULL==m_pObj->m_hDlg)
                hr=m_pObj->m_pImpIRunnableObject->Run(NULL);

            if (FAILED(hr) || NULL==m_pObj->m_hDlg)
                return ResultFromScode(E_OUTOFMEMORY);

            ShowWindow(m_pObj->m_hDlg, SW_SHOW);
            SetFocus(m_pObj->m_hDlg);
            m_pObj->SendAdvise(OBJECTCODE_SHOWOBJECT);
            m_pObj->SendAdvise(OBJECTCODE_SHOWWINDOW);

            break;

        case OLEIVERB_INPLACEACTIVATE:
            if (NULL!=m_pObj->m_pHW)
                {
                HWND    hWndHW=m_pObj->m_pHW->Window();

                ShowWindow(hWndHW, SW_SHOW);
                SetFocus(hWndHW);

                return NOERROR;
                }

            /*
             * Only inside-out supporting containers will use
             * this verb.
             */
            m_pObj->m_fContainerKnowsInsideOut=TRUE;
            m_pObj->InPlaceActivate(pActiveSite, FALSE);
            break;

        case OLEIVERB_UIACTIVATE:
            m_pObj->InPlaceActivate(pActiveSite, TRUE);
            break;

        //CHAPTER24MOD
        case OLEIVERB_PROPERTIES:
        case POLYLINEVERB_PROPERTIES:
            /*
             * Let the container try first if there are
             * extended controls.  Otherwise we'll display
             * our own pages.
             */
            if (NULL!=m_pObj->m_pIOleControlSite)
                {
                hr=m_pObj->m_pIOleControlSite->ShowPropertyFrame();

                if (NOERROR==hr)
                    break;      //All done
                }

            //Put up our property pages.
            hr=m_pObj->m_pImpISpecifyPP->GetPages(&caGUID);

            if (FAILED(hr))
                return FALSE;

            hr=OleCreatePropertyFrame(m_pObj->m_hWnd, 10, 10
                , OLETEXT("Polyline"), 1, (IUnknown **)&m_pObj
                , caGUID.cElems, caGUID.pElems
                , LANG_NEUTRAL, 0L, NULL);

            //Free the GUIDs
            CoTaskMemFree((void *)caGUID.pElems);
            break;
            //End CHAPTER24MOD

        default:
            return ResultFromScode(OLEOBJ_S_INVALIDVERB);
        }

    return NOERROR;
    }

/*
 * CImpIOleObject::GetUserClassID
 *
 * Purpose:
 *  Used for linked objects, this returns the class ID of what end
 *  users think they are editing.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store the CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetUserClassID(LPCLSID pClsID)
    {
    /*
     * If you are not registered to handle data other than yourself,
     * then you can just return your class ID here.  If you are
     * registered as usable from Treat-As dialogs, then you need to
     * return the CLSID of what you are really editing.
     */

    *pClsID=CLSID_Polyline19;
    return NOERROR;
    }

/*
 * CImpIOleObject::SetExtent
 *
 * Purpose:
 *  Sets the size of the object in HIMETRIC units.  Since we're in
 *  a dialog, the size of the object in us is fixed, so we ignore
 *  this call.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect affected.
 *  pszl            LPSIZEL containing the new size.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::SetExtent(DWORD dwAspect
    , LPSIZEL pszl)
    {
    //Ignored:  no size change in the dialog.
    return NOERROR;
    }

/*
 * CImpIOleObject::GetExtent
 *
 * Purpose:
 *  Retrieves the size of the object in HIMETRIC units.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect requested
 *  pszl            LPSIZEL into which to store the size.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetExtent(DWORD dwAspect, LPSIZEL pszl)
    {
    //Delegate directly to IViewObject2::GetExtent
    return m_pObj->m_pImpIViewObject->GetExtent(dwAspect, -1
        , NULL, pszl);
    }

/*
 * CImpIOleObject::Advise
 * CImpIOleObject::Unadvise
 * CImpIOleObject::EnumAdvise
 *
 * Purpose:
 *  Advisory connection functions.
 */

STDMETHODIMP CImpIOleObject::Advise(LPADVISESINK pIAdviseSink
    , LPDWORD pdwConn)
    {
    if (NULL==m_pObj->m_pIOleAdviseHolder)
        {
        HRESULT     hr;

        hr=CreateOleAdviseHolder(&m_pObj->m_pIOleAdviseHolder);

        if (FAILED(hr))
            return hr;
        }

    return m_pObj->m_pIOleAdviseHolder->Advise(pIAdviseSink
        , pdwConn);
    }

STDMETHODIMP CImpIOleObject::Unadvise(DWORD dwConn)
    {
    if (NULL!=m_pObj->m_pIOleAdviseHolder)
        return m_pObj->m_pIOleAdviseHolder->Unadvise(dwConn);

    return ResultFromScode(E_FAIL);
    }

STDMETHODIMP CImpIOleObject::EnumAdvise(LPENUMSTATDATA *ppEnum)
    {
    if (NULL!=m_pObj->m_pIOleAdviseHolder)
        return m_pObj->m_pIOleAdviseHolder->EnumAdvise(ppEnum);

    return ResultFromScode(E_FAIL);
    }

/*
 * CImpIOleObject::SetMoniker
 *
 * Purpose:
 *  Informs the object of its moniker or its container's moniker
 *  depending on dwWhich.
 *
 * Parameters:
 *  dwWhich         DWORD describing whether the moniker is the
 *                  object's or the container's.
 *  pmk             LPMONIKER with the name.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::SetMoniker(DWORD dwWhich
    , LPMONIKER pmk)
    {
    LPMONIKER       pmkFull;
    HRESULT         hr=ResultFromScode(E_FAIL);

    if (NULL!=m_pObj->m_pIOleClientSite)
        {
        hr=m_pObj->m_pIOleClientSite->GetMoniker
            (OLEGETMONIKER_ONLYIFTHERE, OLEWHICHMK_OBJFULL
            , &pmkFull);
        }

    if (SUCCEEDED(hr))
        {
        if (NOERROR==pmkFull->IsRunning(NULL, NULL, NULL))
            {
            pmkFull->Release();
            return NOERROR;
            }

        //This will revoke the old one if m_dwRegROT is nonzero.
        INOLE_RegisterAsRunning(m_pObj, pmkFull, 0
            , &m_pObj->m_dwRegROT);

        //Inform clients of the new moniker
        if (NULL!=m_pObj->m_pIOleAdviseHolder)
            m_pObj->m_pIOleAdviseHolder->SendOnRename(pmkFull);

        pmkFull->Release();
        }

    return hr;
    }

/*
 * CImpIOleObject::GetMoniker
 *
 * Purpose:
 *  Asks the object for a moniker that can later be used to
 *  reconnect to it.
 *
 * Parameters:
 *  dwAssign        DWORD determining how to assign the moniker to
 *                  to the object.
 *  dwWhich         DWORD describing which moniker the caller wants.
 *  ppmk            LPMONIKER * into which to store the moniker.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetMoniker(DWORD dwAssign
    , DWORD dwWhich, LPMONIKER *ppmk)
    {
    HRESULT         hr=ResultFromScode(E_FAIL);

    *ppmk=NULL;

    /*
     * Since we only support embedded objects, our moniker
     * is always the full moniker from the contianer.
     */

    if (NULL!=m_pObj->m_pIOleClientSite)
        {
        hr=m_pObj->m_pIOleClientSite->GetMoniker
            (OLEGETMONIKER_ONLYIFTHERE, OLEWHICHMK_OBJFULL, ppmk);
        }

    return (NULL!=*ppmk) ? NOERROR : hr;
    }

//Methods not implemented or trivial
STDMETHODIMP CImpIOleObject::InitFromData(LPDATAOBJECT pIDataObject
    , BOOL fCreation, DWORD dw)
    {
    return ResultFromScode(E_NOTIMPL);
    }

STDMETHODIMP CImpIOleObject::GetClipboardData(DWORD dwReserved
    , LPDATAOBJECT *ppIDataObj)
    {
    return ResultFromScode(E_NOTIMPL);
    }

STDMETHODIMP CImpIOleObject::Update(void)
    {
    return NOERROR;
    }

STDMETHODIMP CImpIOleObject::IsUpToDate(void)
    {
    return NOERROR;
    }

STDMETHODIMP CImpIOleObject::SetColorScheme(LPLOGPALETTE pLP)
    {
    return ResultFromScode(E_NOTIMPL);
    }

//Methods implemented using registry helper functions in OLE.

STDMETHODIMP CImpIOleObject::EnumVerbs(LPENUMOLEVERB *ppEnum)
    {
    return OleRegEnumVerbs(m_pObj->m_clsID, ppEnum);
    }

STDMETHODIMP CImpIOleObject::GetUserType(DWORD dwForm
    , LPOLESTR *ppszType)
    {
    return OleRegGetUserType(m_pObj->m_clsID, dwForm, ppszType);
    }

STDMETHODIMP CImpIOleObject::GetMiscStatus(DWORD dwAspect
    , LPDWORD pdwStatus)
    {
    return OleRegGetMiscStatus(m_pObj->m_clsID, dwAspect
        , pdwStatus);
    }


IPERSTMI.CPP

/*
 * IPERSTMI.CPP
 * Polyline Component Chapter 24
 *
 * Implementation of the IPersistStreamInit interface exposed on the
 * Polyline object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIPersistStreamInit:CImpIPersistStreamInit
 * CImpIPersistStreamInit::~CImpIPersistStreamInit
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistStreamInit::CImpIPersistStreamInit(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPersistStreamInit::~CImpIPersistStreamInit(void)
    {
    return;
    }

/*
 * CImpIPersistStreamInit::QueryInterface
 * CImpIPersistStreamInit::AddRef
 * CImpIPersistStreamInit::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIPersistStreamInit.
 */

STDMETHODIMP CImpIPersistStreamInit::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistStreamInit::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistStreamInit::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistStreamInit::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the object represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 */

STDMETHODIMP CImpIPersistStreamInit::GetClassID(LPCLSID pClsID)
    {
    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this object since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 */

STDMETHODIMP CImpIPersistStreamInit::IsDirty(void)
    {
    return ResultFromScode(m_pObj->m_fDirty ? S_OK : S_FALSE);
    }

/*
 * CImpIPersistStreamInit::Load
 *
 * Purpose:
 *  Instructs the object to load itself from a previously saved
 *  IStreamInit that was handled by Save in another object lifetime.
 *  The seek pointer in this stream will be exactly the same as
 *  it was when Save was called, and this function must leave
 *  the seek pointer the same as it was on exit from Save, regardless
 *  of success or failure.  This function should not hold on to
 *  pIStream.
 *
 *  This function is called in lieu of IPersistStreamInit::InitNew
 *  when the object already has a persistent state.
 *
 * Parameters:
 *  pIStream        LPSTREAM from which to load.
 */

STDMETHODIMP CImpIPersistStreamInit::Load(LPSTREAM pIStream)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    HRESULT         hr;

    if (NULL==pIStream)
        return ResultFromScode(E_POINTER);

    //Read all the data into the POLYLINEDATA structure.
    hr=pIStream->Read(&pl, CBPOLYLINEDATA, &cb);

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        return hr;

    m_pObj->DataSet(&pl, TRUE, TRUE);
    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::Save
 *
 * Purpose:
 *  Saves the data for this object to an IStreamInit.  Be sure not
 *  to change the position of the seek pointer on entry to this
 *  function: the caller will assume that you write from the
 *  current offset.  Leave the stream's seek pointer at the end
 *  of the data written on exit.
 *
 * Parameters:
 *  pIStream        LPSTREAM in which to save our data.
 *  fClearDirty     BOOL indicating if this call should clear
 *                  the object's dirty flag (TRUE) or leave it
 *                  unchanged (FALSE).
 */

STDMETHODIMP CImpIPersistStreamInit::Save(LPSTREAM pIStream
    , BOOL fClearDirty)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    HRESULT         hr;

    if (NULL==pIStream)
        return ResultFromScode(E_POINTER);

    m_pObj->DataGet(&pl);

    hr=pIStream->Write(&pl, CBPOLYLINEDATA, &cb);

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        return ResultFromScode(STG_E_WRITEFAULT);

    if (fClearDirty)
        m_pObj->m_fDirty;

    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::GetSizeMax
 *
 * Purpose:
 *  Returns the size of the data we would write if Save was
 *  called right now.
 *
 * Parameters:
 *  pcbSize         ULARGE_INTEGER * in which to save the size
 *                  of the stream an immediate call to Save would
 *                  write.
 */

STDMETHODIMP CImpIPersistStreamInit::GetSizeMax(ULARGE_INTEGER
    *pcbSize)
    {
    if (NULL==pcbSize)
        return ResultFromScode(E_POINTER);

    ULISet32(*pcbSize, CBPOLYLINEDATA);
    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::InitNew
 *
 * Purpose:
 *  Informs the object that it is being created new instead of
 *  loaded from a persistent state.  This will be called in lieu
 *  of IPersistStreamInit::Load.
 *
 * Parameters:
 *  None
 */

STDMETHODIMP CImpIPersistStreamInit::InitNew(void)
    {
    //Nothing for us to do
    return NOERROR;
    }


IPERSTOR.CPP

/*
 * IPERSTOR.CPP
 * Polyline Component Chapter 24
 *
 * Implementation of the IPersistStorage interface exposed on the
 * Polyline object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIPersistStorage:CImpIPersistStorage
 * CImpIPersistStorage::~CImpIPersistStorage
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistStorage::CImpIPersistStorage(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    m_psState=PSSTATE_UNINIT;
    return;
    }

CImpIPersistStorage::~CImpIPersistStorage(void)
    {
    return;
    }

/*
 * CImpIPersistStorage::QueryInterface
 * CImpIPersistStorage::AddRef
 * CImpIPersistStorage::Release
 */

STDMETHODIMP CImpIPersistStorage::QueryInterface(REFIID riid
    , PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistStorage::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistStorage::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistStorage::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the object represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR on success, error code otherwise.
 */

STDMETHODIMP CImpIPersistStorage::GetClassID(LPCLSID pClsID)
    {
    if (PSSTATE_UNINIT==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this object since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 *
 */

STDMETHODIMP CImpIPersistStorage::IsDirty(void)
    {
    if (PSSTATE_UNINIT==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    return ResultFromScode(m_pObj->m_fDirty ? S_OK : S_FALSE);
    }

/*
 * CImpIPersistStorage::InitNew
 *
 * Purpose:
 *  Provides the object with the IStorage to hold on to while the
 *  object is running.  Here we initialize the structure of the
 *  storage and AddRef it for incremental access. This function will
 *  only be called once in the object's lifetime in lieu of Load.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE for the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::InitNew(LPSTORAGE pIStorage)
    {
    HRESULT     hr;

    if (PSSTATE_UNINIT!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    if (NULL==pIStorage)
        return ResultFromScode(E_POINTER);

    /*
     * The rules of IPersistStorage mean we hold onto the IStorage
     * and pre-create anything we'd need in Save(...,TRUE) for
     * low-memory situations.  For us this means creating our
     * "CONTENTS" stream and holding onto that IStream as
     * well as the IStorage here (requiring an AddRef call).
     */

    hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
        | STGM_CREATE | STGM_READWRITE | STGM_SHARE_EXCLUSIVE
        , 0, 0, &m_pObj->m_pIStream);

    if (FAILED(hr))
        return hr;

    //We expect that the client has called WriteClassStg
    WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
        , (*m_pObj->m_pST)[IDS_USERTYPE]);

    m_pObj->m_pIStorage=pIStorage;
    pIStorage->AddRef();

    m_psState=PSSTATE_SCRIBBLE;

    //Initialize the cache as needed.
    m_pObj->m_pDefIPersistStorage->InitNew(pIStorage);
    return NOERROR;
    }

/*
 * CImpIPersistStorage::Load
 *
 * Purpose:
 *  Instructs the object to load itself from a previously saved
 *  IStorage that was handled by Save in another object lifetime.
 *  This function will only be called once in the object's lifetime
 *  in lieu of InitNew. The object should hold on to pIStorage here
 *  for incremental access and low-memory saves in Save.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE from which to load.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::Load(LPSTORAGE pIStorage)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    LPSTREAM        pIStream;
    HRESULT         hr;

    if (PSSTATE_UNINIT!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    if (NULL==pIStorage)
        return ResultFromScode(E_POINTER);

    //We don't check CLSID to remain compatible with other chapters.

    hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
        | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0, &pIStream);

    if (FAILED(hr))
        return ResultFromScode(STG_E_READFAULT);

    //Read all the data into the POLYLINEDATA structure.
    hr=pIStream->Read(&pl, CBPOLYLINEDATA, &cb);

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        {
        pIStream->Release();
        return hr;
        }

    /*
     * We don't call pIStream->Release here because we may need
     * it for a low-memory save in Save.  We also need to
     * hold onto a copy of pIStorage, meaning AddRef.
     */
    m_pObj->m_pIStream=pIStream;

    m_pObj->m_pIStorage=pIStorage;
    pIStorage->AddRef();

    //DataSet now internal on CPolyline
    m_pObj->DataSet(&pl, TRUE, TRUE);

    m_psState=PSSTATE_SCRIBBLE;

    //We also need to tell the cache to load cached graphics
    m_pObj->m_pDefIPersistStorage->Load(pIStorage);
    return NOERROR;
    }

/*
 * CImpIPersistStorage::Save
 *
 * Purpose:
 *  Saves the data for this object to an IStorage which may
 *  or may not be the same as the one previously passed to
 *  Load, indicated with fSameAsLoad.  After this call we may
 *  not write into the storage again until SaveCompleted is
 *  called, although we may still read.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which to save our data.
 *  fSameAsLoad     BOOL indicating if this is the same pIStorage
 *                  that was passed to Load.  If TRUE, then the
 *                  object should write whatever it has *without
 *                  *using any extra memory* as this may be a low
 *                  memory save attempt.  That means that you must
 *                  not try to open or create streams.  If FALSE
 *                  you need to regenerate your whole storage
 *                  structure, being sure to also release any
 *                  pointers held from InitNew and Load.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::Save(LPSTORAGE pIStorage
    , BOOL fSameAsLoad)
    {
    POLYLINEDATA    pl;
    ULONG           cb;
    LPSTREAM        pIStream;
    HRESULT         hr;

    //Have to come here from scribble state.
    if (PSSTATE_SCRIBBLE!=m_psState)
        return ResultFromScode(E_UNEXPECTED);

    //Must have an IStorage if we're not in SameAsLoad
    if (NULL==pIStorage && !fSameAsLoad)
        return ResultFromScode(E_POINTER);

    /*
     * If we're saving to a new storage, create a new stream.
     * If fSameAsLoad it TRUE, then we write to the
     * stream we already allocated.  We should NOT depends on
     * pIStorage with fSameAsLoad is TRUE.
     */
    if (fSameAsLoad)
        {
        LARGE_INTEGER   li;

        /*
         * Use pre-allocated streams to avoid failures due
         * to low-memory conditions.  Be sure to reset the
         * stream pointer if you used this stream before!!
         */
        pIStream=m_pObj->m_pIStream;
        LISet32(li, 0);
        pIStream->Seek(li, STREAM_SEEK_SET, NULL);

        //This matches the Release below.
        pIStream->AddRef();
        }
    else
        {
        hr=pIStorage->CreateStream(SZSTREAM, STGM_DIRECT
            | STGM_CREATE | STGM_WRITE | STGM_SHARE_EXCLUSIVE
            , 0, 0, &pIStream);

        if (FAILED(hr))
            return hr;

        //Only do this with new storages.
        WriteFmtUserTypeStg(pIStorage, m_pObj->m_cf
            , (*m_pObj->m_pST)[IDS_USERTYPE]);
        }

    //DataGet now internal on CPolyline
    m_pObj->DataGet(&pl);

    hr=pIStream->Write(&pl, CBPOLYLINEDATA, &cb);
    pIStream->Release();

    if (FAILED(hr) || CBPOLYLINEDATA!=cb)
        return ResultFromScode(STG_E_WRITEFAULT);

    m_psState=PSSTATE_ZOMBIE;

    //We also need to tell the cache to save cached graphics
    m_pObj->m_pDefIPersistStorage->Save(pIStorage, fSameAsLoad);
    return NOERROR;
    }

/*
 * CImpIPersistStorage::SaveCompleted
 *
 * Purpose:
 *  Notifies the object that the storage in pIStorage has been
 *  completely saved now.  This is called when the user of this
 *  object wants to save us in a completely new storage, and if
 *  we normally hang on to the storage we have to reinitialize
 *  ourselves here for this new one that is now complete.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE of the new storage in which we live.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::SaveCompleted(LPSTORAGE pIStorage)
    {
    HRESULT     hr;
    LPSTREAM    pIStream;

    //Must be called in no-scribble or hands-off state
    if (!(PSSTATE_ZOMBIE==m_psState || PSSTATE_HANDSOFF==m_psState))
        return ResultFromScode(E_UNEXPECTED);

    //If we're coming from Hands-Off, we'd better get a storage
    if (NULL==pIStorage && PSSTATE_HANDSOFF==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    /*
     * If pIStorage is NULL, then we don't need to do anything
     * since we already have all the pointers we need for Save.
     * Otherwise we have to release any held pointers and
     * reinitialize them from pIStorage.
     */

    if (NULL!=pIStorage)
        {
        hr=pIStorage->OpenStream(SZSTREAM, 0, STGM_DIRECT
            | STGM_READWRITE | STGM_SHARE_EXCLUSIVE, 0
            , &pIStream);

        if (FAILED(hr))
            return hr;

        if (NULL!=m_pObj->m_pIStream)
            m_pObj->m_pIStream->Release();

        m_pObj->m_pIStream=pIStream;

        if (NULL!=m_pObj->m_pIStorage)
            m_pObj->m_pIStorage->Release();

        m_pObj->m_pIStorage=pIStorage;
        m_pObj->m_pIStorage->AddRef();
        }

    //Change state back to scribble.
    m_psState=PSSTATE_SCRIBBLE;

    m_pObj->m_pDefIPersistStorage->SaveCompleted(pIStorage);
    return NOERROR;
    }

/*
 * CImpIPersistStorage::HandsOffStorage
 *
 * Purpose:
 *  Instructs the object that another agent is interested in having
 *  total access to the storage we might be hanging on to from
 *  InitNew or SaveCompleted.  In this case we must release our hold
 *  and await another call to SaveCompleted before we have a hold
 *  again.  Therefore we cannot read or write after this call until
 *  SaveCompleted.
 *
 *  Situations where this might happen arise in compound document
 *  scenarios where this object might be in-place active but the
 *  application wants to rename and commit the root storage.
 *  Therefore we are asked to close our hold, let the container
 *  party on the storage, then call us again later to tell us the
 *  new storage we can hold.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::HandsOffStorage(void)
    {
    /*
     * Must come from scribble or no-scribble.  A repeated call
     * to HandsOffStorage is an unexpected error (bug in client).
     */
    if (PSSTATE_UNINIT==m_psState || PSSTATE_HANDSOFF==m_psState)
        return ResultFromScode(E_UNEXPECTED);

    //Release held pointers
    if (NULL!=m_pObj->m_pIStream)
        {
        m_pObj->m_pIStream->Release();
        m_pObj->m_pIStream=NULL;
        }

    if (NULL!=m_pObj->m_pIStorage)
        {
        m_pObj->m_pIStorage->Release();
        m_pObj->m_pIStorage=NULL;
        }

    m_psState=PSSTATE_HANDSOFF;

    m_pObj->m_pDefIPersistStorage->HandsOffStorage();
    return NOERROR;
    }


IPOLY.H

/* This header file machine-generated by mktyplib.exe */
/* Interface to type library: PolylineTypeLibrary */

#ifndef _PolylineTypeLibrary_H_
#define _PolylineTypeLibrary_H_

DEFINE_GUID(LIBID_PolylineTypeLibrary,0x00021173,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

DEFINE_GUID(IID_IPolylineControl,0x00021171,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

/* Definition of interface: IPolylineControl */
DECLARE_INTERFACE_(IPolylineControl, IUnknown)
{
#ifndef NO_BASEINTERFACE_FUNCS

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;
#endif

    /* IPolylineControl methods */
    STDMETHOD_(void, put_BackColor)(THIS_ OLE_COLOR cr) PURE;
    STDMETHOD_(OLE_COLOR, get_BackColor)(THIS) PURE;
    STDMETHOD_(void, put_LineColor)(THIS_ OLE_COLOR cr) PURE;
    STDMETHOD_(OLE_COLOR, get_LineColor)(THIS) PURE;
    STDMETHOD_(void, put_LineStyle)(THIS_ short iStyle) PURE;
    STDMETHOD_(short, get_LineStyle)(THIS) PURE;
    STDMETHOD(Clear)(THIS) PURE;
    STDMETHOD(RemoveLastPoint)(THIS) PURE;
};

DEFINE_GUID(DIID_DIPolylineControl,0x00021172,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

/* Definition of dispatch interface: DIPolylineControl */
DECLARE_INTERFACE_(DIPolylineControl, IDispatch)
{
#ifndef NO_BASEINTERFACE_FUNCS

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(THIS_ UINT FAR* pctinfo) PURE;

    STDMETHOD(GetTypeInfo)(
      THIS_
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo) PURE;

    STDMETHOD(GetIDsOfNames)(
      THIS_
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid) PURE;

    STDMETHOD(Invoke)(
      THIS_
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr) PURE;
#endif

/* Capable of dispatching all the methods of interface IPolylineControl */
};

DEFINE_GUID(IID_IPolylineAdviseSink10,0x00021159,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

/* Definition of interface: IPolylineAdviseSink10 */
DECLARE_INTERFACE_(IPolylineAdviseSink10, IUnknown)
{
#ifndef NO_BASEINTERFACE_FUNCS

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;
#endif

    /* IPolylineAdviseSink10 methods */
    STDMETHOD_(void, OnPointChange)(THIS) PURE;
    STDMETHOD_(void, OnSizeChange)(THIS) PURE;
    STDMETHOD_(void, OnColorChange)(THIS) PURE;
    STDMETHOD_(void, OnLineStyleChange)(THIS) PURE;
};

DEFINE_GUID(DIID_DIPolylineAdviseSink10,0x00021170,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

/* Definition of dispatch interface: DIPolylineAdviseSink10 */
DECLARE_INTERFACE_(DIPolylineAdviseSink10, IDispatch)
{
#ifndef NO_BASEINTERFACE_FUNCS

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(THIS_ UINT FAR* pctinfo) PURE;

    STDMETHOD(GetTypeInfo)(
      THIS_
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo) PURE;

    STDMETHOD(GetIDsOfNames)(
      THIS_
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid) PURE;

    STDMETHOD(Invoke)(
      THIS_
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr) PURE;
#endif

    /* DIPolylineAdviseSink10 methods:
    void OnPointChange(void);
    void OnSizeChange(void);
    void OnColorChange(void);
    void OnLineStyleChange(void);
    */
};

DEFINE_GUID(CLSID_Polyline19,0x0002116D,0x0000,0x0000,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x46);

class Polyline19;

#endif


IPOLYLIN.CPP

/*
 * IPOLYLIN.CPP
 * Polyline Component Chapter 24
 *
 * Implementation of the IPolyline10 interface that we expose on the
 * CPolyline object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIPolyline:CImpIPolyline
 * CImpIPolyline::~CImpIPolyline
 *
 * Constructor Parameters:
 *  pObj            PCPolyline pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPolyline::CImpIPolyline(PCPolyline pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPolyline::~CImpIPolyline(void)
    {
    return;
    }

/*
 * CImpIPolyline::QueryInterface
 * CImpIPolyline::AddRef
 * CImpIPolyline::Release
 */

STDMETHODIMP CImpIPolyline::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPolyline::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPolyline::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPolyline::Init
 *
 * Purpose:
 *  Instantiates a polyline window within a given parent.  The
 *  parent may be a main application window, could be an MDI child
 *  window. We really do not care.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  pRect           LPRECT that this window should occupy
 *  dwStyle         DWORD containing the window's style flags
 *  uID             UINT ID to associate with this window
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_OUTOFMEMORY
 */

STDMETHODIMP CImpIPolyline::Init(HWND hWndParent, LPRECT pRect
    , DWORD dwStyle, UINT uID)
    {
    SCODE           sc;

    m_pObj->m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY
        , SZCLASSPOLYLINE, SZCLASSPOLYLINE, dwStyle, pRect->left
        , pRect->top, pRect->right-pRect->left
        , pRect->bottom-pRect->top, hWndParent, (HMENU)uID
        , m_pObj->m_hInst, m_pObj);

    sc=(NULL!=m_pObj->m_hWnd) ? S_OK : E_OUTOFMEMORY;
    return ResultFromScode(sc);
    }

/*
 * CImpIPolyline::New
 *
 * Purpose:
 *  Cleans out and reinitializes the data to defaults.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::New(void)
    {
    PPOLYLINEDATA   ppl=&m_pObj->m_pl;
    UINT            i;
    RECT            rc;

    ppl->wVerMaj=VERSIONMAJOR;
    ppl->wVerMin=VERSIONMINOR;

    //Our rectangle is the size of our window's client area.
    if (NULL!=m_pObj->m_hWnd)
        {
        GetClientRect(m_pObj->m_hWnd, &rc);
        RECTTORECTS(rc, ppl->rc);
        }
    else
        {
        SetRect(&rc, 0, 0, 100, 100);       //Something reasonable
        RECTTORECTS(rc, ppl->rc);
        }

    //Clean out the POLYLINEDATA structure and repaint the window.
    for (i=0; i< CPOLYLINEPOINTS; i++)
        {
        ppl->rgpt[i].x=0;
        ppl->rgpt[i].y=0;
        }

    ppl->cPoints      =0;
    ppl->rgbBackground=GetSysColor(COLOR_WINDOW);
    ppl->rgbLine      =GetSysColor(COLOR_WINDOWTEXT);
    ppl->iLineStyle   =PS_SOLID;

    //This is now conditional since we may not yet have a window.
    if (NULL!=m_pObj->m_hWnd)
        {
        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        UpdateWindow(m_pObj->m_hWnd);
        m_pObj->m_fDirty=TRUE;
        }

    m_pObj->SendAdvise(OBJECTCODE_DATACHANGED);
    return NOERROR;
    }

/*
 * CImpIPolyline::Undo
 *
 * Purpose:
 *  Reverses previous actions in a Polyline.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         S_OK if we can Undo more, S_FALSE otherwise.
 */

STDMETHODIMP CImpIPolyline::Undo(void)
    {
    SCODE           sc;

    //Decrement the number of active points and repaint.
    if (m_pObj->m_pl.cPoints > 0)
        {
        m_pObj->m_pl.cPoints--;
        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        UpdateWindow(m_pObj->m_hWnd);
        }

    if (NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnPointChange();
        }

    m_pObj->SendAdvise(OBJECTCODE_DATACHANGED);

    //Return if we can undo any more.
    sc=(0!=m_pObj->m_pl.cPoints) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * CImpIPolyline::Window
 *
 * Purpose:
 *  Returns the window handle associated with this polyline.
 *
 * Parameters:
 *  phWnd           HWND * in which to return the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CImpIPolyline::Window(HWND *phWnd)
    {
    *phWnd=m_pObj->m_hWnd;
    return NOERROR;
    }

/*
 * CImpIPolyline::RectGet
 *
 * Purpose:
 *  Returns the rectangle of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the rectangle.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::RectGet(LPRECT pRect)
    {
    RECT            rc;
    POINT           pt;

    if (NULL==m_pObj->m_hWnd)
        {
        SetRect(pRect, 0, 0, 150, 150); //Reasonable default
        return NOERROR;
        }

    //Retrieve the size of our rectangle in parent coordinates.
    GetWindowRect(m_pObj->m_hWnd, &rc);
    pt.x=rc.left;
    pt.y=rc.top;
    ScreenToClient(GetParent(m_pObj->m_hWnd), &pt);

    SetRect(pRect, pt.x, pt.y, pt.x+(rc.right-rc.left)
        , pt.y+(rc.bottom-rc.top));

    return NOERROR;
    }

/*
 * CImpIPolyline::SizeGet
 *
 * Purpose:
 *  Retrieves the size of the Polyline in parent coordinates.
 *
 * Parameters:
 *  pRect           LPRECT in which to return the size.  The right
 *                  and bottom fields will contain the dimensions.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::SizeGet(LPRECT pRect)
    {
    RectGet(pRect);
    return NOERROR;
    }

/*
 * CImpIPolyline::RectSet
 *
 * Purpose:
 *  Sets a new rectangle for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::RectSet(LPRECT pRect, BOOL fNotify)
    {
    UINT            cx, cy;
    RECT            rc;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    SetRect(&rc, 0, 0, cx, cy);
    RECTTORECTS(rc, m_pObj->m_pl.rc);

    if (NULL!=m_pObj->m_hWnd)
        {
        SetWindowPos(m_pObj->m_hWnd, NULL, pRect->left, pRect->top
            , cx, cy, SWP_NOZORDER);
        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        }

    if (fNotify && NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnSizeChange();
        }

    return NOERROR;
    }

/*
 * CImpIPolyline::SizeSet
 *
 * Purpose:
 *  Sets a new size for the Polyline which sizes to fit.
 *
 * Parameters:
 *  pRect           LPRECT containing the new rectangle.
 *  fNotify         BOOL indicating if we're to notify anyone of
 *                  the change.
 *
 * Return Value:
 *  HRESULT         NOERROR always
 */

STDMETHODIMP CImpIPolyline::SizeSet(LPRECT pRect, BOOL fNotify)
    {
    UINT            cx, cy;

    //Scale the points from our current size to the new size
    cx=pRect->right-pRect->left;
    cy=pRect->bottom-pRect->top;

    if (NULL!=m_pObj->m_hWnd)
        {
        SetWindowPos(m_pObj->m_hWnd, NULL, 0, 0, (UINT)cx, (UINT)cy
            , SWP_NOMOVE | SWP_NOZORDER);
        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        }

    if (fNotify && NULL!=m_pObj->m_pAdv)
        {
        m_pObj->m_fDirty=TRUE;
        m_pObj->m_pAdv->OnSizeChange();
        }

    return NOERROR;
    }

/*
 * CImpIPolyline::ColorSet
 *
 * Purpose:
 *  Changes for background or line color in the Polyline
 *
 * Parameters:
 *  iColor          UINT index of the color to change.
 *  cr              COLORREF new color to use.
 *  pcrPrev         COLORREF * in whch to store the
 *                  previous color.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::ColorSet(UINT iColor, COLORREF cr
    , COLORREF *pcrPrev)
    {
    COLORREF        crRet;

    if (NULL==pcrPrev)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    switch (iColor)
        {
        case POLYLINECOLOR_BACKGROUND:
            crRet=m_pObj->m_pl.rgbBackground;
            m_pObj->m_pl.rgbBackground=cr;
            break;

        case POLYLINECOLOR_LINE:
            crRet=m_pObj->m_pl.rgbLine;
            m_pObj->m_pl.rgbLine=cr;
            break;
        }

    //If the color changed, repaint
    if (crRet!=cr)
        {
        if (NULL!=m_pObj->m_pAdv)
            {
            m_pObj->m_fDirty=TRUE;
            m_pObj->m_pAdv->OnColorChange();
            }

        InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
        UpdateWindow(m_pObj->m_hWnd);

        m_pObj->SendAdvise(OBJECTCODE_DATACHANGED);
        }

    *pcrPrev=crRet;
    return NOERROR;
    }

/*
 * CImpIPolyline::ColorGet
 *
 * Purpose:
 *  Retrieves one of the colors currently in use by the Polyline.
 *
 * Parameters:
 *  iColor          UINT identifying the color of interest.
 *  pcr             COLORREF * in which to return the color.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::ColorGet(UINT iColor, COLORREF *pcr)
    {
    COLORREF        crRet;

    if (NULL==pcr)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    crRet=(POLYLINECOLOR_BACKGROUND==iColor)
        ? m_pObj->m_pl.rgbBackground : m_pObj->m_pl.rgbLine;

    *pcr=crRet;
    return NOERROR;
    }

/*
 * CImpIPolyline::LineStyleSet
 *
 * Purpose:
 *  Changes the line style in use by the Polyline
 *
 * Parameters:
 *  iStyle          UINT style of the line to use.
 *  piPrev          UINT * in which to store the previous style.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::LineStyleSet(UINT iStyle, UINT *piPrev)
    {
    UINT            uRet;

    uRet=(UINT)m_pObj->m_pl.iLineStyle;

    if (NULL==piPrev)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    //Validate the line style
    if (PS_SOLID==iStyle || PS_DASH==iStyle || PS_DOT==iStyle
        || PS_DASHDOT==iStyle || PS_DASHDOTDOT==iStyle)
        {
        m_pObj->m_pl.iLineStyle=iStyle;

        if (uRet!=(UINT)m_pObj->m_pl.iLineStyle)
            {
            if (NULL!=m_pObj->m_pAdv)
                {
                m_pObj->m_fDirty=TRUE;
                m_pObj->m_pAdv->OnLineStyleChange();
                }

            InvalidateRect(m_pObj->m_hWnd, NULL, TRUE);
            UpdateWindow(m_pObj->m_hWnd);

            m_pObj->SendAdvise(OBJECTCODE_DATACHANGED);
            }
        }

    *piPrev=uRet;
    return NOERROR;
    }

/*
 * CImpIPolyline::LineStyleGet
 *
 * Purpose:
 *  Retrieves the current line style in use in the Polyline
 *
 * Parameters:
 *  piStyle         UINT * in which to store the style.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CImpIPolyline::LineStyleGet(UINT *piStyle)
    {
    if (NULL==piStyle)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    *piStyle=m_pObj->m_pl.iLineStyle;
    return NOERROR;
    }


IRUNOBJ.CPP

/*
 * IRUNOBJ.CPP
 * Polyline Component Chapter 24
 *
 * Implementation of the IRunnableObject interface which allows
 * us to enter the "running" state which means Polyline's dialog
 * box is created, but not visible.  This is necessary so that
 * containers can ask for our extents before calling DoVerb.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIRunnableObject::CImpIRunnableObject
 * CImpIRunnableObject::~CImpIRunnableObject
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIRunnableObject::CImpIRunnableObject(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIRunnableObject::~CImpIRunnableObject(void)
    {
    return;
    }

/*
 * CImpIRunnableObject::QueryInterface
 * CImpIRunnableObject::AddRef
 * CImpIRunnableObject::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIRunnableObject.
 */

STDMETHODIMP CImpIRunnableObject::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIRunnableObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIRunnableObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIRunnableObject::GetRunningClass
 *
 * Purpose:
 *  Returns the CLSID of the object.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store the CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIRunnableObject::GetRunningClass(LPCLSID pClsID)
    {
    *pClsID=m_pObj->m_clsID;
    return NOERROR;
    }

/*
 * CImpIRunnableObject::Run
 *
 * Purpose:
 *  Run an object in the given bind context, that is, put the object
 *  into the running state.
 *
 * Parameters:
 *  pBindCtx        LPBINDCTX of the bind context to use.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIRunnableObject::Run(LPBINDCTX pBindCtx)
    {
    /*
     * Create the dialog but not show it.  This function is called
     * from OleRun which is called from within OleCreate.  So when
     * a container creates this object, we'll create the dialog
     * which is a basis for the object's extents (as returned from
     * IViewObject2::GetExtent.  Our IOleObject::DoVerb will later
     * note that the dialog is already created and will just make
     * it visible and not create it anew.
     *
     * Note that the dialog is unowned--it acts like an overlapped
     * window.
     */

    if (NULL!=m_pObj->m_hDlg)
        return NOERROR;

    //This stores the dialog handle in m_pObj.
    CreateDialogParam(m_pObj->Instance()
        , MAKEINTRESOURCE(IDD_EDITDIALOG), NULL, PolyDlgProc
        , (LPARAM)m_pObj);

    if (NULL==m_pObj->m_hDlg)
        return ResultFromScode(E_OUTOFMEMORY);

    /*
     * Registration of the object as running happens in
     * IOleObject::SetClientSite since we need a moniker from
     * the container and we don't have a client site pointer yet.
     */
    return NOERROR;
    }

/*
 * CImpIRunnableObject::IsRunning
 *
 * Purpose:
 *  Answers whether an object is currently in the running state.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            Indicates the running state of the object.
 */

STDMETHODIMP_(BOOL) CImpIRunnableObject::IsRunning(void)
    {
    return (NULL!=m_pObj->m_hDlg);
    }

/*
 * CImpIRunnableObject::LockRunning
 *
 * Purpose:
 *  Locks an already running object into the running state or unlocks
 *  it from such a state.
 *
 * Parameters:
 *  fLock               BOOL indicating lock (TRUE) or unlock
 *                      (FALSE)
 *  fLastUnlockCloses   BOOL indicating if the last call to this
 *                      function with fLock==FALSE closes the
 *                      object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIRunnableObject::LockRunning(BOOL fLock
    , BOOL fLastUnlockCloses)
    {
    //Calling CoLockObjectExternal is all we have to do here.
    return CoLockObjectExternal(this, fLock, fLastUnlockCloses);
    }

/*
 * CImpIRunnableObject::SetContainedObject
 *
 * Purpose:
 *  Informs the object (embedded object) that it is inside a
 *  compound document container.
 *
 * Parameters:
 *  fContained      BOOL indicating if the object is now contained.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIRunnableObject::SetContainedObject(BOOL fContained)
    {
    //We can ignore this.
    return NOERROR;
    }


IVIEWOBJ.CPP

/*
 * IVIEWOBJ.CPP
 * Polyline Component Chapter 24
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CImpIViewObject::CImpIViewObject
 * CImpIViewObject::~CImpIViewObject
 *
 * Parameters (Constructor):
 *  pObj            PCPolyline of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIViewObject::CImpIViewObject(PCPolyline pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIViewObject::~CImpIViewObject(void)
    {
    return;
    }

/*
 * CImpIViewObject::QueryInterface
 * CImpIViewObject::AddRef
 * CImpIViewObject::Release
 *
 * Purpose:
 *  IUnknown members for CImpIViewObject object.
 */

STDMETHODIMP CImpIViewObject::QueryInterface(REFIID riid, PPVOID ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIViewObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIViewObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIViewObject::Draw
 *
 * Purpose:
 *  Draws the object on the given hDC specifically for the requested
 *  aspect, device, and within the appropriate bounds.
 *
 * Parameters:
 *  dwAspect        DWORD aspect to draw.
 *  lindex          LONG index of the piece to draw.
 *  pvAspect        LPVOID for extra information, always NULL.
 *  ptd             DVTARGETDEVICE * containing device
 *                  information.
 *  hICDev          HDC containing the IC for the device.
 *  hDC             HDC on which to draw.
 *  pRectBounds     LPCRECTL describing the rectangle in which
 *                  to draw.
 *  pRectWBounds    LPCRECTL describing the placement rectangle
 *                  if part of what you draw is another metafile.
 *  pfnContinue     Function to call periodically during
 *                  long repaints.
 *  dwContinue      DWORD extra information to pass to the
 *                  pfnContinue.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::Draw(DWORD dwAspect, LONG lindex
    , LPVOID pvAspect, DVTARGETDEVICE *ptd, HDC hICDev
    , HDC hDC, LPCRECTL pRectBounds, LPCRECTL pRectWBounds
    , BOOL (CALLBACK *pfnContinue) (DWORD), DWORD dwContinue)
    {
    RECT            rc;
    POLYLINEDATA    pl;
    PPOLYLINEDATA   ppl=&m_pObj->m_pl;

    RECTFROMRECTL(rc, *pRectBounds);

    //Delegate iconic and printed representations.
    if (!((DVASPECT_CONTENT | DVASPECT_THUMBNAIL) & dwAspect))
        {
        return m_pObj->m_pDefIViewObject->Draw(dwAspect, lindex
            , pvAspect, ptd, hICDev, hDC, pRectBounds, pRectWBounds
            , pfnContinue, dwContinue);
        }

    /*
     * If we're asked to draw a frozen aspect, use the data from
     * a copy we made in IViewObject::Freeze.  Otherwise use the
     * current data.
     */
    if (dwAspect & m_pObj->m_dwFrozenAspects)
        {
        //Point to the data to actually use.
        if (DVASPECT_CONTENT==dwAspect)
            ppl=&m_pObj->m_plContent;
        else
            ppl=&m_pObj->m_plThumbnail;
        }

    //Make a copy so we can modify it (and not worry about threads)
    memcpy(&pl, ppl, CBPOLYLINEDATA);

    /*
     * If we're going to a printer, check if it's color capable.
     * if not, then use black on white for this figure.
     */
    if (NULL!=hICDev)
        {
        if (GetDeviceCaps(hICDev, NUMCOLORS) <= 2)
            {
            pl.rgbBackground=RGB(255, 255, 255);
            pl.rgbLine=RGB(0, 0, 0);
            }
        }

    m_pObj->Draw(hDC, FALSE, TRUE, &rc, &pl);
    return NOERROR;
    }

/*
 * CImpIViewObject::GetColorSet
 *
 * Purpose:
 *  Retrieves the color palette used by the object.
 *
 * Parameters:
 *  dwAspect        DWORD aspect of interest.
 *  lindex          LONG piece of interest.
 *  pvAspect        LPVOID with extra information, always NULL.
 *  ptd             DVTARGETDEVICE * containing device info.
 *  hICDev          HDC containing the IC for the device.
 *  ppColorSet      LPLOGPALETTE * into which to return the
 *                  pointer to the palette in this color set.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::GetColorSet(DWORD dwDrawAspect
    , LONG lindex, LPVOID pvAspect, DVTARGETDEVICE *ptd
    , HDC hICDev, LPLOGPALETTE *ppColorSet)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIViewObject::Freeze
 *
 * Purpose:
 *  Freezes the view of a particular aspect such that data
 *  changes do not affect the view.
 *
 * Parameters:
 *  dwAspect        DWORD aspect to freeze.
 *  lindex          LONG piece index under consideration.
 *  pvAspect        LPVOID for further information, always NULL.
 *  pdwFreeze       LPDWORD in which to return the key.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::Freeze(DWORD dwAspect, LONG lindex
    , LPVOID pvAspect, LPDWORD pdwFreeze)
    {
    //Delegate anything for ICON or DOCPRINT aspects
    if (!((DVASPECT_CONTENT | DVASPECT_THUMBNAIL) & dwAspect))
        {
        return m_pObj->m_pDefIViewObject->Freeze(dwAspect, lindex
            , pvAspect, pdwFreeze);
        }

    if (dwAspect & m_pObj->m_dwFrozenAspects)
        {
        *pdwFreeze=dwAspect + FREEZE_KEY_OFFSET;
        return ResultFromScode(VIEW_S_ALREADY_FROZEN);
        }

    m_pObj->m_dwFrozenAspects |= dwAspect;

    /*
     * For whatever aspects become frozen, make a copy of the data.
     * Later when drawing, if such a frozen aspect is requested,
     * we'll draw from this data rather than from our current data.
     */
    if (DVASPECT_CONTENT & dwAspect)
        {
        memcpy(&m_pObj->m_plContent, &m_pObj->m_pl
            , CBPOLYLINEDATA);
        }

    if (DVASPECT_THUMBNAIL & dwAspect)
        {
        memcpy(&m_pObj->m_plThumbnail, &m_pObj->m_pl
            , CBPOLYLINEDATA);
        }

    if (NULL!=pdwFreeze)
        *pdwFreeze=dwAspect + FREEZE_KEY_OFFSET;

    return NOERROR;
    }

/*
 * CImpIViewObject::Unfreeze
 *
 * Purpose:
 *  Thaws an aspect frozen in ::Freeze.  We expect that a container
 *  will redraw us after freezing if necessary, so we don't send
 *  any sort of notification here.
 *
 * Parameters:
 *  dwFreeze        DWORD key returned from ::Freeze.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::Unfreeze(DWORD dwFreeze)
    {
    DWORD       dwAspect=dwFreeze - FREEZE_KEY_OFFSET;

    //Delegate anything for ICON or DOCPRINT aspects
    if (!((DVASPECT_CONTENT | DVASPECT_THUMBNAIL) & dwAspect))
        m_pObj->m_pDefIViewObject->Unfreeze(dwFreeze);

    //The aspect to unfreeze is in the key.
    m_pObj->m_dwFrozenAspects &= ~(dwAspect);

    /*
     * Since we always kept our current data up to date, we don't
     * have to do anything thing here like requesting data again.
     * Because we removed dwAspect from m_dwFrozenAspects, Draw
     * will again use the current data.
     */

    return NOERROR;
    }

/*
 * CImpIViewObject::SetAdvise
 *
 * Purpose:
 *  Provides an advise sink to the view object enabling
 *  notifications for a specific aspect.
 *
 * Parameters:
 *  dwAspects       DWORD describing the aspects of interest.
 *  dwAdvf          DWORD containing advise flags.
 *  pIAdviseSink    LPADVISESINK to notify.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::SetAdvise(DWORD dwAspects, DWORD dwAdvf
    , LPADVISESINK pIAdviseSink)
    {
    //Pass anything with DVASPECT_ICON or _DOCPRINT to the handler.
    if (!((DVASPECT_CONTENT | DVASPECT_THUMBNAIL) & dwAspects))
        {
        m_pObj->m_pDefIViewObject->SetAdvise(dwAspects, dwAdvf
            , pIAdviseSink);
        }

    //We continue because dwAspects may have more than one in it.
    if (NULL!=m_pObj->m_pIAdviseSink)
        m_pObj->m_pIAdviseSink->Release();

    m_pObj->m_pIAdviseSink=pIAdviseSink;
    m_pObj->m_dwAdviseAspects=dwAspects;
    m_pObj->m_dwAdviseFlags=dwAdvf;

    if (NULL!=m_pObj->m_pIAdviseSink)
        m_pObj->m_pIAdviseSink->AddRef();

    return NOERROR;
    }

/*
 * CImpIViewObject::GetAdvise
 *
 * Purpose:
 *  Returns the last known IAdviseSink seen by ::SetAdvise.
 *
 * Parameters:
 *  pdwAspects      LPDWORD in which to store the last
 *                  requested aspects.
 *  pdwAdvf         LPDWORD in which to store the last
 *                  requested flags.
 *  ppIAdvSink      LPADVISESINK * in which to store the
 *                  IAdviseSink.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::GetAdvise(LPDWORD pdwAspects
    , LPDWORD pdwAdvf, LPADVISESINK *ppAdvSink)
    {
    if (NULL!=ppAdvSink)
        {
        *ppAdvSink=m_pObj->m_pIAdviseSink;
        m_pObj->m_pIAdviseSink->AddRef();
        }

    if (NULL!=pdwAspects)
        *pdwAspects=m_pObj->m_dwAdviseAspects;

    if (NULL!=pdwAdvf)
        *pdwAdvf=m_pObj->m_dwAdviseFlags;

    return NOERROR;
    }

/*
 * CImpIViewObject::GetExtent
 *
 * Purpose:
 *  Retrieves the extents of the object's display.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect of interest.
 *  lindex          LONG index of the piece of interest.
 *  ptd             DVTARGETDEVICE * with device information.
 *  pszl            LPSIZEL to the structure in which to return
 *                  the extents.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject::GetExtent(DWORD dwAspect, LONG lindex
    , DVTARGETDEVICE *ptd, LPSIZEL pszl)
    {
    RECT            rc;

    if (!(DVASPECT_CONTENT & dwAspect))
        {
        return m_pObj->m_pDefIViewObject->GetExtent(dwAspect
            , lindex, ptd, pszl);
        }

    m_pObj->m_pImpIPolyline->RectGet(&rc);
    m_pObj->RectConvertMappings(&rc, FALSE);

    pszl->cx=rc.right-rc.left;
    pszl->cy=rc.bottom-rc.top;

    return NOERROR;
    }


POLYLINE.CPP

/*
 * POLYLINE.CPP
 * Polyline Component Chapter 24
 *
 * Implementation of the CPolyline class that we expose as a
 * component object.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * CPolyline:CPolyline
 * CPolyline::~CPolyline
 *
 * Constructor Parameters:
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 *  pfnDestroy      PFNDESTROYED to call when an object is
 *                  destroyed.
 *  hInst           HINSTANCE of the application we're in.
 */

CPolyline::CPolyline(LPUNKNOWN pUnkOuter, PFNDESTROYED pfnDestroy
    , HINSTANCE hInst)
    {
    m_hWnd=NULL;
    m_hInst=hInst;

    m_cRef=0;
    m_pUnkOuter=pUnkOuter;
    m_pfnDestroy=pfnDestroy;
    m_fDirty=FALSE;

    m_pImpIPolyline=NULL;
    m_pImpIConnPtCont=NULL;

    m_pAdv=NULL;
    m_pConnPt=NULL;

    m_pST  =NULL;
    m_cf   =0;
    m_clsID=CLSID_Polyline19;

    m_pIStorage=NULL;
    m_pIStream =NULL;

    m_pImpIPersistStorage=NULL;
    m_pImpIPersistStreamInit=NULL;

    m_pImpIDataObject   =NULL;
    m_pIDataAdviseHolder=NULL;

    m_pDefIUnknown       =NULL;
    m_pDefIDataObject    =NULL;
    m_pDefIViewObject    =NULL;
    m_pDefIPersistStorage=NULL;

    m_pIOleAdviseHolder =NULL;
    m_pImpIOleObject    =NULL;
    m_pIOleClientSite   =NULL;
    m_pImpIViewObject   =NULL;
    m_pIAdviseSink      =NULL;

    m_dwFrozenAspects   =0;
    m_dwAdviseAspects   =0;
    m_dwAdviseFlags     =0;

    m_pImpIRunnableObject=NULL;
    m_hDlg=NULL;

    m_pImpIExternalConnection=NULL;
    m_fLockContainer=FALSE;
    m_dwRegROT=0L;

    m_pIOleIPSite=NULL;
    m_pIOleIPFrame=NULL;
    m_pIOleIPUIWindow=NULL;
    m_pImpIOleIPObject=NULL;
    m_pImpIOleIPActiveObject=NULL;
    m_hMenuShared=NULL;
    m_hOLEMenu=NULL;
    m_pHW=NULL;
    m_fAllowInPlace=TRUE;
    m_fUIActive=FALSE;

    m_fContainerKnowsInsideOut=FALSE;

    //CHAPTER24MOD
    m_pImpISpecifyPP=NULL;
    m_pImpIProvideClassInfo=NULL;
    m_pImpIDispatch=NULL;
    m_pImpIPolylineControl=NULL;
    m_pImpIOleControl=NULL;

    m_pITypeLib=NULL;
    m_pIOleControlSite=NULL;
    m_pIDispatchAmbients=NULL;
    m_fFreezeEvents=FALSE;

    m_fHatch=TRUE;
    m_fUIDead=FALSE;
    //End CHAPTER24MOD

    return;
    }

CPolyline::~CPolyline(void)
    {
    LPUNKNOWN       pIUnknown=this;

    if (NULL!=m_pUnkOuter)
        pIUnknown=m_pUnkOuter;

    if (NULL!=m_pST)
        delete m_pST;

    if (NULL!=m_hDlg)
        DestroyWindow(m_hDlg);

    /*
     * In aggregation, release cached pointers but
     * AddRef the controlling unknown first.  The
     * extra reference count protects from reentrancy.
     */

    m_cRef++;

    pIUnknown->AddRef();
    pIUnknown->AddRef();
    pIUnknown->AddRef();

    ReleaseInterface(m_pDefIViewObject);
    ReleaseInterface(m_pDefIDataObject);
    ReleaseInterface(m_pDefIPersistStorage);

    m_cRef--;

    //Cached pointer rules do not apply to IUnknown
    ReleaseInterface(m_pDefIUnknown);

    ReleaseInterface(m_pIAdviseSink);
    ReleaseInterface(m_pIOleClientSite);
    ReleaseInterface(m_pIOleAdviseHolder);

    DeleteInterfaceImp(m_pImpIOleObject);
    DeleteInterfaceImp(m_pImpIViewObject);
    DeleteInterfaceImp(m_pImpIRunnableObject);

    //Other in-place interfaces released in deactivation.
    DeleteInterfaceImp(m_pImpIOleIPObject);
    DeleteInterfaceImp(m_pImpIOleIPActiveObject);

    //CHAPTER24MOD
    ReleaseInterface(m_pIDispatchAmbients);
    ReleaseInterface(m_pIOleControlSite);
    ReleaseInterface(m_pITypeLib);

    DeleteInterfaceImp(m_pImpISpecifyPP);
    DeleteInterfaceImp(m_pImpIProvideClassInfo);
    DeleteInterfaceImp(m_pImpIDispatch);
    DeleteInterfaceImp(m_pImpIPolylineControl);
    DeleteInterfaceImp(m_pImpIOleControl);
    //End CHAPTER24MOD

    //Anything we might have registered in IRunnableObject::Run
    INOLE_RevokeAsRunning(&m_dwRegROT);

    DeleteInterfaceImp(m_pImpIExternalConnection);
    ReleaseInterface(m_pIDataAdviseHolder);
    DeleteInterfaceImp(m_pImpIDataObject);

    DeleteInterfaceImp(m_pImpIPersistStreamInit);
    DeleteInterfaceImp(m_pImpIPersistStorage);
    ReleaseInterface(m_pIStream);
    ReleaseInterface(m_pIStorage);

    DeleteInterfaceImp(m_pImpIConnPtCont);
    DeleteInterfaceImp(m_pImpIPolyline);

    ReleaseInterface(m_pAdv);
    ReleaseInterface(m_pConnPt);

    return;
    }

/*
 * CPolyline::Init
 *
 * Purpose:
 *  Performs any intiailization of a CPolyline that's prone to
 *  failure that we also use internally before exposing the
 *  object outside this DLL.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful,
 *                  FALSE otherwise.
 */

BOOL CPolyline::Init(void)
    {
    LPUNKNOWN       pIUnknown=this;
    HRESULT         hr;

    if (NULL!=m_pUnkOuter)
        pIUnknown=m_pUnkOuter;

    m_pST=new CStringTable(m_hInst);

    if (!m_pST->Init(IDS_POLYLINEMIN, IDS_POLYLINEMAX))
        return FALSE;

    m_cf=RegisterClipboardFormat(SZPOLYLINECLIPFORMAT);

    m_pImpIPersistStorage=new CImpIPersistStorage(this, pIUnknown);

    if (NULL==m_pImpIPersistStorage)
        return FALSE;

    m_pImpIPersistStreamInit=new CImpIPersistStreamInit(this
        , pIUnknown);

    if (NULL==m_pImpIPersistStreamInit)
        return FALSE;

    m_pImpIPolyline=new CImpIPolyline(this, pIUnknown);

    if (NULL==m_pImpIPolyline)
        return FALSE;

    m_pImpIConnPtCont=new CImpIConnPtCont(this, pIUnknown);

    if (NULL==m_pImpIConnPtCont)
        return FALSE;

    m_pConnPt=new CConnectionPoint(this);

    if (NULL==m_pConnPt)
        return FALSE;

    m_pConnPt->AddRef();    //Reversed in destructor

    m_pImpIDataObject=new CImpIDataObject(this, pIUnknown);

    if (NULL==m_pImpIDataObject)
        return FALSE;

    m_pImpIOleObject=new CImpIOleObject(this, pIUnknown);

    if (NULL==m_pImpIOleObject)
        return FALSE;

    m_pImpIViewObject=new CImpIViewObject(this, pIUnknown);

    if (NULL==m_pImpIViewObject)
        return FALSE;

    m_pImpIRunnableObject=new CImpIRunnableObject(this, pIUnknown);

    if (NULL==m_pImpIRunnableObject)
        return FALSE;

    m_pImpIExternalConnection=new CImpIExternalConnection(this
        , pIUnknown);

    if (NULL==m_pImpIExternalConnection)
        return FALSE;

    m_pImpIOleIPObject=new CImpIOleInPlaceObject(this, pIUnknown);

    if (NULL==m_pImpIOleIPObject)
        return FALSE;

    m_pImpIOleIPActiveObject=new CImpIOleInPlaceActiveObject(this
        , pIUnknown);

    if (NULL==m_pImpIOleIPActiveObject)
        return FALSE;

    //CHAPTER24MOD
    m_pImpISpecifyPP=new CImpISpecifyPP(this, pIUnknown);

    if (NULL==m_pImpISpecifyPP)
        return FALSE;

    m_pImpIProvideClassInfo=new CImpIProvideClassInfo(this, pIUnknown);

    if (NULL==m_pImpIProvideClassInfo)
        return FALSE;

    m_pImpIDispatch=new CImpIDispatch(this, pIUnknown);

    if (NULL==m_pImpIDispatch)
        return FALSE;

    m_pImpIPolylineControl=new CImpIPolylineControl(this, pIUnknown);

    if (NULL==m_pImpIPolylineControl)
        return FALSE;

    m_pImpIOleControl=new CImpIOleControl(this, pIUnknown);

    if (NULL==m_pImpIOleControl)
        return FALSE;
    //End CHAPTER24MOD

    /*
     * We're sitting at ref count 0 and the next call will AddRef a
     * few times and Release a few times.  This insures we don't
     * delete ourselves prematurely.
     */
    m_cRef++;

    //Aggregate OLE's cache for IOleCache* interfaces.
    hr=CreateDataCache(pIUnknown, CLSID_Polyline19
        , IID_IUnknown, (PPVOID)&m_pDefIUnknown);

    if (FAILED(hr))
        return FALSE;

    /*
     * NOTE:  The spec specifically states that any interfaces
     * besides IUnknown that we obtain on an aggregated object
     * should be Released immediately after we QueryInterface for
     * them because the QueryInterface will AddRef us, and since
     * we would not release these interfaces until we were
     * destroyed, we'd never go away because we'd never get a zero
     * ref count.
     */

    //Now try to get other interfaces to which we delegate
    hr=m_pDefIUnknown->QueryInterface(IID_IViewObject2
        , (PPVOID)&m_pDefIViewObject);

    if (FAILED(hr))
        return FALSE;

    pIUnknown->Release();

    hr=m_pDefIUnknown->QueryInterface(IID_IDataObject
        , (PPVOID)&m_pDefIDataObject);

    if (FAILED(hr))
        return FALSE;

    pIUnknown->Release();

    hr=m_pDefIUnknown->QueryInterface(IID_IPersistStorage
        , (PPVOID)&m_pDefIPersistStorage);

    if (FAILED(hr))
        return FALSE;

    pIUnknown->Release();

    m_cRef--;
    m_pImpIPolyline->New();

    //CHAPTER24MOD
    /*
     * Go load our own type information and save its ITypeLib
     * pointer that will be used be CImpIDispatch and
     * CImpIProvideClassInfo.
     */

    hr=LoadRegTypeLib(LIBID_PolylineTypeLibrary, 1, 0
        , LANG_NEUTRAL, &m_pITypeLib);

    if (FAILED(hr))
        hr=LoadTypeLib(OLETEXT("POLYLINE.TLB"), &m_pITypeLib);

    if (FAILED(hr))
        return FALSE;

    //Set up our CONTROLINFO structure (we have two mnemonics)
    m_ctrlInfo.cb=sizeof(CONTROLINFO);
    m_ctrlInfo.dwFlags=0;
    m_ctrlInfo.hAccel=NULL;
    m_ctrlInfo.cAccel=0;

    /*
     * Note:  we cannot initialize ambients until we get
     * a container interface pointer in IOleObject::SetClientSite.
     */
    //End CHAPTER24MOD

    return TRUE;
    }

/*
 * CPolyline::QueryInterface
 * CPolyline::AddRef
 * CPolyline::Release
 *
 * Purpose:
 *  IUnknown members for CPolyline object.
 */

STDMETHODIMP CPolyline::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid)
        *ppv=this;

    if (IID_IConnectionPointContainer==riid)
        *ppv=m_pImpIConnPtCont;

    if (IID_IPolyline10==riid)
        *ppv=m_pImpIPolyline;

    if (IID_IPersistStorage==riid)
        *ppv=m_pImpIPersistStorage;

    if (IID_IPersist==riid || IID_IPersistStream==riid
        || IID_IPersistStreamInit==riid)
        *ppv=m_pImpIPersistStreamInit;

    if (IID_IDataObject==riid)
        *ppv=m_pImpIDataObject;

    if (IID_IOleObject==riid)
        *ppv=m_pImpIOleObject;

    if (IID_IViewObject==riid || IID_IViewObject2==riid)
        *ppv=m_pImpIViewObject;

    if (IID_IRunnableObject==riid)
        *ppv=m_pImpIRunnableObject;

    if (IID_IExternalConnection==riid)
        *ppv=m_pImpIExternalConnection;

    //IOleWindow will be the InPlaceObject
    if (IID_IOleWindow==riid || IID_IOleInPlaceObject==riid)
        *ppv=m_pImpIOleIPObject;

    //CHAPTER24MOD
    if (IID_ISpecifyPropertyPages==riid)
        *ppv=m_pImpISpecifyPP;

    if (IID_IProvideClassInfo==riid)
        *ppv=m_pImpIProvideClassInfo;

    if (IID_IDispatch==riid || DIID_DIPolylineControl==riid)
        *ppv=m_pImpIDispatch;

    if (IID_IPolylineControl==riid)
        *ppv=m_pImpIPolylineControl;

    if (IID_IOleControl==riid)
        *ppv=m_pImpIOleControl;
    //End CHAPTER24MOD

    //Use the default handler's cache.
    if (IID_IOleCache==riid || IID_IOleCache2==riid)
        return m_pDefIUnknown->QueryInterface(riid, ppv);

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CPolyline::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPolyline::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    if (NULL!=m_pfnDestroy)
        (*m_pfnDestroy)();

    delete this;
    return 0L;
    }

/*
 * CPolyline::RectConvertMappings
 *
 * Purpose:
 *  Converts the contents of a rectangle from device (MM_TEXT) or
 *  HIMETRIC to the other.
 *
 * Parameters:
 *  pRect           LPRECT containing the rectangle to convert.
 *  fToDevice       BOOL TRUE to convert from HIMETRIC to device,
 *                  FALSE to convert device to HIMETRIC.
 *
 * Return Value:
 *  None
 */

void CPolyline::RectConvertMappings(LPRECT pRect, BOOL fToDevice)
    {
    HDC      hDC;
    int      iLpx, iLpy;

    if (NULL==pRect)
        return;

    hDC=GetDC(NULL);
    iLpx=GetDeviceCaps(hDC, LOGPIXELSX);
    iLpy=GetDeviceCaps(hDC, LOGPIXELSY);
    ReleaseDC(NULL, hDC);

    if (fToDevice)
        {
        pRect->left=MulDiv(iLpx, pRect->left, HIMETRIC_PER_INCH);
        pRect->top =MulDiv(iLpy, pRect->top , HIMETRIC_PER_INCH);

        pRect->right =MulDiv(iLpx, pRect->right, HIMETRIC_PER_INCH);
        pRect->bottom=MulDiv(iLpy, pRect->bottom,HIMETRIC_PER_INCH);
        }
    else
        {
        pRect->left=MulDiv(pRect->left, HIMETRIC_PER_INCH, iLpx);
        pRect->top =MulDiv(pRect->top , HIMETRIC_PER_INCH, iLpy);

        pRect->right =MulDiv(pRect->right, HIMETRIC_PER_INCH, iLpx);
        pRect->bottom=MulDiv(pRect->bottom,HIMETRIC_PER_INCH, iLpy);
        }

    return;
    }

/*
 * CPolyline::DataSet
 *
 * Purpose:
 *  Sets the current data in this Polyline to a given structure.
 *
 * Parameters:
 *  pplIn           PPOLYLINEDATA to initialize to.
 *  fSizeToData     BOOL indicating if we're to size to the data
 *                  or scale it.
 *  fNotify         BOOL indicating if we're to send an advise
 *                  on this change.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::DataSet(PPOLYLINEDATA pplIn
    , BOOL fSizeToData, BOOL fNotify)
    {
    RECT            rc;

    /*
     * Copy the structure in pplIn and repaint to reflect the
     * new point set.  Note that unlike the RectSet message, we
     * do no scaling, assuming that the rect in the structure
     * is appropriate for the data.
     */

    if (NULL==pplIn)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    m_pl=*pplIn;
    m_fDirty=TRUE;

    /*
     * If we're scaling the window to fit the data, then use
     * RectSet passing our current rectangle as the new one.
     * That makes sure that the data won't change but that the
     * window is resized.
     */

    if (fSizeToData)
        {
        POINT       pt;

        /*
         * Get our offset in the parent window so we can RectSet
         * to the right place since RectSet expects rectangle in
         * parent coordinates and we get it in client coordinates.
         */
        if (NULL!=m_hWnd)
            {
            GetWindowRect(m_hWnd, &rc);
            pt.x=rc.left;
            pt.y=rc.top;
            ScreenToClient(GetParent(m_hWnd), &pt);
            RECTSTORECT(m_pl.rc, rc);
            OffsetRect(&rc, pt.x, pt.y);

            //This will also cause a repaint.
            m_pImpIPolyline->RectSet(&rc, fNotify);
            }
        }
    else
        {
        if (NULL!=m_hWnd)
            {
            //Make sure we're updated.
            InvalidateRect(m_hWnd, NULL, TRUE);
            UpdateWindow(m_hWnd);
            }
        }

    SendAdvise(OBJECTCODE_DATACHANGED);
    return NOERROR;
    }

/*
 * CPolyline::DataGet
 *
 * Purpose:
 *  Retrieves the Polyline's current data.
 *
 * Parameters:
 *  pplIn           PPOLYLINEDATA into which we copy the data.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::DataGet(PPOLYLINEDATA pplIn)
    {
    if (NULL==pplIn)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    *pplIn=m_pl;
    return NOERROR;
    }

/*
 * CPolyline::RenderNative
 *
 * Purpose:
 *  Retrieves the Polyline's data in a global memory handle.
 *
 * Parameters:
 *  phMem           HGLOBAL * in which to store the handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::RenderNative(HGLOBAL *phMem)
    {
    HGLOBAL         hMem;
    PPOLYLINEDATA   ppl;
    HRESULT         hr=ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    if (NULL==phMem)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE, CBPOLYLINEDATA);

    if (NULL!=hMem)
        {
        ppl=(PPOLYLINEDATA)GlobalLock(hMem);
        hr=DataGet(ppl);

        GlobalUnlock(hMem);

        if (FAILED(hr))
            {
            GlobalFree(hMem);
            hMem=NULL;
            }
        }

    *phMem=hMem;
    return hr;
    }

/*
 * CPolyline::RenderBitmap
 *
 * Purpose:
 *  Creates a bitmap image of the current Polyline.
 *
 * Parameters:
 *  phBmp           HBITMAP * in which to return the bitmap.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::RenderBitmap(HBITMAP *phBmp)
    {
    HDC             hDC;
    HDC             hMemDC;
    HBITMAP         hBmp;
    RECT            rc;
    HGDIOBJ         hObj;

    if (NULL==phBmp)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    //Render a bitmap the size of the current rectangle.
    hDC=GetDC(m_hWnd);
    hMemDC=CreateCompatibleDC(hDC);

    GetClientRect(m_hWnd, &rc);
    hBmp=CreateCompatibleBitmap(hDC, rc.right, rc.bottom);

    if (NULL!=hBmp)
        {
        //Draw the POLYLINEDATA into the bitmap.
        hObj=SelectObject(hMemDC, hBmp);
        Draw(hMemDC, FALSE, TRUE, &rc, NULL);
        SelectObject(hMemDC, hObj);
        }

    DeleteDC(hMemDC);
    ReleaseDC(m_hWnd, hDC);

    *phBmp=hBmp;
    return NOERROR;
    }

//RenderMetafile not necessary--now part of RenderMetafilePict.

/*
 * CPolyline::RenderMetafilePict
 *
 * Purpose:
 *  Renders the current Polyline into a METAFILEPICT structure in
 *  global memory.
 *
 * Parameters:
 *  phMem           HGLOBAL * in which to return the
 *                  METAFILEPICT.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise a
 *                  POLYLINE_E_ value.
 */

STDMETHODIMP CPolyline::RenderMetafilePict(HGLOBAL *phMem)
    {
    HGLOBAL         hMem;
    HMETAFILE       hMF;
    LPMETAFILEPICT  pMF;
    RECT            rc;
    HDC             hDC;

    if (NULL==phMem)
        return ResultFromScode(POLYLINE_E_INVALIDPOINTER);

    //Create a memory metafile and return its handle.
    hDC=(HDC)CreateMetaFile(NULL);

    if (NULL==hDC)
        return ResultFromScode(STG_E_MEDIUMFULL);

    SetMapMode(hDC, MM_ANISOTROPIC);
    GetClientRect(m_hWnd, &rc);
    SetWindowOrgEx(hDC, 0, 0, NULL);
    SetWindowExtEx(hDC, rc.right, rc.bottom, NULL);

    Draw(hDC, TRUE, TRUE, &rc, NULL);
    hMF=CloseMetaFile(hDC);

    if (NULL==hMF)
        return ResultFromScode(STG_E_MEDIUMFULL);

    //Allocate the METAFILEPICT structure.
    hMem=GlobalAlloc(GMEM_DDESHARE | GMEM_MOVEABLE
        , sizeof(METAFILEPICT));

    if (NULL==hMem)
        {
        DeleteMetaFile(hMF);
        return ResultFromScode(E_FAIL);
        }

    /*
     * Global lock only fails in PMODE if the selector is invalid
     * (like it was discarded) or references a 0 length segment,
     * neither of which can happen here.
     */
    pMF=(LPMETAFILEPICT)GlobalLock(hMem);

    pMF->hMF=hMF;
    pMF->mm=MM_ANISOTROPIC;

    //Insert the extents in MM_HIMETRIC units.
    GetClientRect(m_hWnd, &rc);
    RectConvertMappings(&rc, FALSE);
    pMF->xExt=rc.right;
    pMF->yExt=rc.bottom;

    GlobalUnlock(hMem);

    *phMem=hMem;
    return NOERROR;
    }

/*
 * CPolyline::Instance
 *
 * Purpose:
 *  Provides access to the module instance.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HINSTANCE       The instance handle of the module.
 */

HINSTANCE CPolyline::Instance(void)
    {
    return m_hInst;
    }

/*
 * CPolyline::String
 *
 * Purpose:
 *  Provides string lookup in the Polyline string table.
 *
 * Parameters:
 *  uID             UINT of the string to return.
 *
 * Return Value:
 *  LPTSTR          Pointer to the string.
 */

LPTSTR CPolyline::String(UINT uID)
    {
    return PSZ(uID);
    }

/*
 * CPolyline::SendAdvise
 *
 * Purpose:
 *  Calls the appropriate IOleClientSite or IAdviseSink member
 *  function for various events such as closure, renaming, etc.
 *
 * Parameters:
 *  uCode           UINT OBJECTCODE_* identifying the notification.
 *
 * Return Value:
 *  None
 */

void CPolyline::SendAdvise(UINT uCode)
    {
    DWORD       dwAspect=DVASPECT_CONTENT | DVASPECT_THUMBNAIL;

    switch (uCode)
        {
        case OBJECTCODE_SAVED:
            if (NULL!=m_pIOleAdviseHolder)
                m_pIOleAdviseHolder->SendOnSave();
            break;

        case OBJECTCODE_CLOSED:
            if (NULL!=m_pIOleAdviseHolder)
                m_pIOleAdviseHolder->SendOnClose();

            break;

        case OBJECTCODE_RENAMED:
            //Call IOleAdviseHolder::SendOnRename (later)
            break;

        case OBJECTCODE_SAVEOBJECT:
            if (m_fDirty && NULL!=m_pIOleClientSite)
                m_pIOleClientSite->SaveObject();

            m_fDirty=FALSE;
            break;

        case OBJECTCODE_DATACHANGED:
            m_fDirty=TRUE;

            //No flags are necessary here.
            if (NULL!=m_pIDataAdviseHolder)
                {
                m_pIDataAdviseHolder->SendOnDataChange
                    (m_pImpIDataObject, 0, 0);
                }

            if (NULL!=m_pIAdviseSink
                & (dwAspect & m_dwAdviseAspects))
                {
                m_pIAdviseSink->OnViewChange(dwAspect
                    & m_dwAdviseAspects, 0);
                }

            break;

        case OBJECTCODE_SHOWWINDOW:
            if (NULL!=m_pIOleClientSite)
                m_pIOleClientSite->OnShowWindow(TRUE);

            break;

        case OBJECTCODE_HIDEWINDOW:
            if (NULL!=m_pIOleClientSite)
                m_pIOleClientSite->OnShowWindow(FALSE);

            break;

        case OBJECTCODE_SHOWOBJECT:
            if (NULL!=m_pIOleClientSite)
                m_pIOleClientSite->ShowObject();

            break;
        }

    return;
    }

/*
 * CPolyline::InPlaceActivate
 *
 * Purpose:
 *  Goes through all the steps of activating the Polyline as an
 *  in-place object.
 *
 * Parameters:
 *  pActiveSite     LPOLECLIENTSITE of the active site we show in.
 *  fIncludeUI      BOOL controls whether we call UIActivate too.
 *
 * Return Value:
 *  HRESULT         Whatever error code is appropriate.
 */

HRESULT CPolyline::InPlaceActivate(LPOLECLIENTSITE pActiveSite
    , BOOL fIncludeUI)
    {
    HRESULT                 hr;
    HWND                    hWnd;
    HWND                    hWndHW;
    RECT                    rcPos;
    RECT                    rcClip;
    OLEINPLACEFRAMEINFO     frameInfo;

    if (NULL==pActiveSite)
        return ResultFromScode(E_INVALIDARG);

    if (NULL!=m_pIOleIPSite)
        {
        if (fIncludeUI)
            UIActivate();

        return NOERROR;
        }

    //1.  Initialization, obtaining interfaces, OnInPlaceActivate.
    hr=pActiveSite->QueryInterface(IID_IOleInPlaceSite
        , (PPVOID)&m_pIOleIPSite);

    if (FAILED(hr))
        return hr;

    hr=m_pIOleIPSite->CanInPlaceActivate();

    if (NOERROR!=hr)
        {
        m_pIOleIPSite->Release();
        m_pIOleIPSite=NULL;
        return ResultFromScode(E_FAIL);
        }

    m_pIOleIPSite->OnInPlaceActivate();

    //2.  Get the window context and create a window.
    m_pIOleIPSite->GetWindow(&hWnd);
    frameInfo.cb=sizeof(OLEINPLACEFRAMEINFO);

    m_pIOleIPSite->GetWindowContext(&m_pIOleIPFrame
        , &m_pIOleIPUIWindow, &rcPos, &rcClip, &frameInfo);

    /*
     * Create the hatch window after we get a parent window.  We
     * could not create the hatch window sooner because had nothing
     * to use for the parent.
     */
    m_pHW=new CHatchWin(m_hInst);

    if (NULL==m_pHW)
        {
        InPlaceDeactivate();
        return ResultFromScode(E_OUTOFMEMORY);
        }

    if (!m_pHW->Init(hWnd, ID_HATCHWINDOW, NULL))
        {
        InPlaceDeactivate();
        return ResultFromScode(E_OUTOFMEMORY);
        }

    //Make sure dialog is hidden
    if (NULL!=m_hDlg)
        {
        ShowWindow(m_hDlg, SW_HIDE);
        SendAdvise(OBJECTCODE_HIDEWINDOW);
        }

    //Move the hatch window to the container window.
    hWndHW=m_pHW->Window();
    SetParent(hWndHW, hWnd);

    //Move the Polyline window from the hidden dialog to hatch window
    m_pHW->HwndAssociateSet(m_hWnd);
    m_pHW->ChildSet(m_hWnd);
    m_pHW->RectsSet(&rcPos, &rcClip);   //Positions polyline

    ///CHAPTER24MOD
    //This might be off when we get to here.
    if (!m_fHatch)
        m_pHW->ShowHatch(FALSE);
    //End CHAPTER24MOD

    ShowWindow(hWndHW, SW_SHOW);
    SendAdvise(OBJECTCODE_SHOWOBJECT);

    //Critical for accelerators to work initially.
    SetFocus(hWndHW);

    //3, 4, 5.  Do UI things: active object, shared menu, tools
    hr=NOERROR;

    if (fIncludeUI)
        hr=UIActivate();

    /*
     * Since we don't have an Undo while in-place, tell the continer
     * to free it's undo state immediately.
     */
    m_pIOleIPSite->DiscardUndoState();

    return hr;
    }

/*
 * CPolyline::InPlaceDeactivate
 *
 * Purpose:
 *  Reverses all the activation steps from InPlaceActivate.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolyline::InPlaceDeactivate(void)
    {
    UIDeactivate();

    if (NULL!=m_pHW)
        {
        RECT        rc;

        ShowWindow(m_pHW->Window(), SW_HIDE);

        //Reposition the polyline window in the dialog box.
        SetParent(m_hWnd, m_hDlg);
        CalcPolyRectInDialog(m_hDlg, &rc);

        SetWindowPos(m_hWnd, NULL, rc.left, rc.top
            , rc.right-rc.left, rc.bottom-rc.top, SWP_NOZORDER);

        m_pHW->ChildSet(NULL);

        delete m_pHW;
        m_pHW=NULL;
        }

    ReleaseInterface(m_pIOleIPFrame);
    ReleaseInterface(m_pIOleIPUIWindow)

    if (NULL!=m_pIOleIPSite)
        {
        m_pIOleIPSite->OnInPlaceDeactivate();
        ReleaseInterface(m_pIOleIPSite);
        }

    return;
    }

/*
 * CPolyline::UIActivate
 *
 * Purpose:
 *  Goes through all the steps of activating the user interface of
 *  Polyline as an in-place object.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or error code.
 */

HRESULT CPolyline::UIActivate(void)
    {
    //1.  Call IOleInPlaceSite::UIActivate
    if (NULL!=m_pIOleIPSite)
        m_pIOleIPSite->OnUIActivate();

    //2.  Set the active object
   #ifdef WIN32ANSI
    OLECHAR     szTemp[40];

    MultiByteToWideChar(CP_ACP, 0, PSZ(IDS_USERTYPE)
        , -1, szTemp, 40);
   #endif

    if (NULL!=m_pIOleIPFrame)
        {
        m_pIOleIPFrame->SetActiveObject(m_pImpIOleIPActiveObject
           #ifdef WIN32ANSI
            , szTemp);
           #else
            , PSZ(IDS_USERTYPE));
           #endif
        }

    if (NULL!=m_pIOleIPUIWindow)
        {
        m_pIOleIPUIWindow->SetActiveObject(m_pImpIOleIPActiveObject
           #ifdef WIN32ANSI
            , szTemp);
           #else
            , PSZ(IDS_USERTYPE));

#endif
        }

    //3.  Critical for accelerators to work initially.
    SetFocus(m_pHW->Window());

    //4.  Negotiate border space.  Polyline doesn't need any.
    if (NULL!=m_pIOleIPFrame)
        m_pIOleIPFrame->SetBorderSpace(NULL);

    if (NULL!=m_pIOleIPUIWindow)
        m_pIOleIPUIWindow->SetBorderSpace(NULL);

    /*
     * 5.  Create the shared menu.  We don't have any, so tell
     *     the container to use its own menu.
     */

    if (NULL!=m_pIOleIPFrame)
        m_pIOleIPFrame->SetMenu(NULL, NULL, m_hWnd);

    m_fUIActive=TRUE;
    return NOERROR;
    }

/*
 * CPolyline::UIDeactivate
 *
 * Purpose:
 *  Reverses all the user interface activation steps from
 *  UIActivate.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CPolyline::UIDeactivate(void)
    {
    m_fUIActive=FALSE;

    //We don't have any shared menu or tools to clean up.

    //Clear out the active objects
    if (NULL!=m_pIOleIPFrame)
        m_pIOleIPFrame->SetActiveObject(NULL, NULL);

    if (NULL!=m_pIOleIPUIWindow)
        m_pIOleIPUIWindow->SetActiveObject(NULL, NULL);

    if (NULL!=m_pIOleIPSite)
        m_pIOleIPSite->OnUIDeactivate(FALSE);

    return;
    }

//CHAPTER24MOD

/*
 * AmbientGet
 *
 * Purpose:
 *  Retrieves a specific ambient property into a VARIANT.
 *
 * Parameters:
 *  dispID          DISPID of the property to retrieve.
 *  pva             VARIANT * to fill with the new value.
 *
 * Return value
 *  BOOL            TRUE if the ambient was retrieved, FALSE
 *                  otherwise.
 */

BOOL CPolyline::AmbientGet(DISPID dispID, VARIANT *pva)
    {
    HRESULT         hr;
    DISPPARAMS      dp;

    if (NULL==pva)
        return FALSE;

    if (NULL==m_pIDispatchAmbients)
        return FALSE;

    SETNOPARAMS(dp);
    hr=m_pIDispatchAmbients->Invoke(dispID, IID_NULL
        , LOCALE_USER_DEFAULT, DISPATCH_PROPERTYGET
        , &dp, pva, NULL, NULL);

    return SUCCEEDED(hr);
    }

/*
 * AmbientsInitialize
 *
 * Purpose:
 *  Attempts to retrieve the container's ambient properties
 *  and initialize (or reinitialize) Polyline accordingly.
 *
 * Parameters:
 *  dwWhich         DWORD containing INITAMBIENT_... flags
 *                  describing which ambients to initialize.
 *                  This can be any combination.
 *
 * Return Value:
 *  None
 */

void CPolyline::AmbientsInitialize(DWORD dwWhich)
    {
    VARIANT     va;
    COLORREF    cr;

    if (NULL==m_pIDispatchAmbients)
        return;

    /*
     * We need to retrieve these ambients into these variables:
     *
     *  Ambient Property:               Variable:
     *  -----------------------------------------------
     *  DISPID_AMBIENT_SHOWHATCHING     m_fHatch
     *  DISPID_AMBIENT_UIDEAD           m_fUIDead
     *  DISPID_AMBIENT_BACKCOLOR        m_pl.rgbBackground
     *  DISPID_AMBIENT_FORECOLOR        m_pl.rgbLine
     */

    if ((INITAMBIENT_SHOWHATCHING & dwWhich)
        &&AmbientGet(DISPID_AMBIENT_SHOWHATCHING, &va))
        {
        m_fHatch=V_BOOL(&va);

        if (NULL!=m_pHW)
            m_pHW->ShowHatch(m_fHatch);
        }

    if ((INITAMBIENT_UIDEAD & dwWhich)
        && AmbientGet(DISPID_AMBIENT_UIDEAD, &va))
        {
        //This affects our detection of mouse clicks
        m_fUIDead=V_BOOL(&va);
        }

    if ((INITAMBIENT_BACKCOLOR & dwWhich)
        && AmbientGet(DISPID_AMBIENT_BACKCOLOR, &va))
        {
        cr=V_I4(&va);

        if (0x80000000 & cr)
            cr=GetSysColor(cr & 0x7FFFFFFF);

        m_pl.rgbBackground=cr;
        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    if ((INITAMBIENT_FORECOLOR & dwWhich)
        && AmbientGet(DISPID_AMBIENT_FORECOLOR, &va))
        {
        cr=V_I4(&va);

        if (0x80000000 & cr)
            cr=GetSysColor(cr & 0x7FFFFFFF);

        m_pl.rgbLine=cr;
        InvalidateRect(m_hWnd, NULL, TRUE);
        UpdateWindow(m_hWnd);
        }

    return;
    }

//End CHAPTER24MOD


POLYLINE.H

/*
 * POLYLINE.H
 * Polyline Component Chapter 24
 *
 * Definitions and function prototypes
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _POLYLINE_H_
#define _POLYLINE_H_

#define INC_CLASSLIB
#define INC_CONTROLS
//CHAPTER24MOD
#define GUIDS_FROM_TYPELIB
#define INC_AUTOMATION
#define INC_OLEUI
#define CHAPTER24
//End CHAPTER24MOD
#include <inole.h>
//CHAPTER24MOD
#include "ipoly.h"  //From MKTYPLIB
//Prevent duplicate definition of IPolylineAdviseSink10 in ipoly10.h
#define OMIT_POLYLINESINK
//End CHAPTER24MOD
#include <ipoly10.h>
#include "resource.h"

//Classname
#define SZCLASSPOLYLINE             TEXT("polyline")

//Stream Name that holds the data
#define SZSTREAM                    OLETEXT("CONTENTS")

#define SZPOLYFRAMETITLE            TEXT("Polyline Figure in %s")

//Magic number to add to aspects returned from IViewObject::Freeze
#define FREEZE_KEY_OFFSET           0x0723

#define HIMETRIC_PER_INCH           2540
#define CCHPATHMAX                  256

//Window extra bytes and offsets
#define CBPOLYLINEWNDEXTRA          (sizeof(LONG))
#define PLWL_STRUCTURE              0

#define ID_HATCHWINDOW              2000

//DLLPOLY.CPP
int PASCAL LibMain(HINSTANCE, WORD, WORD, LPSTR);

//This class factory object creates Polyline objects.

class CPolylineClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;

    public:
        CPolylineClassFactory(void);
        ~CPolylineClassFactory(void);

        //IUnknown members
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP         CreateInstance(LPUNKNOWN, REFIID
                                 , PPVOID);
        STDMETHODIMP         LockServer(BOOL);
    };

typedef CPolylineClassFactory *PCPolylineClassFactory;

//POLYWIN.CPP
LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM, LPARAM);

#ifdef WIN32
#define PROP_POINTER    TEXT("Pointer")
#else
#define PROP_SELECTOR   "Selector"
#define PROP_OFFSET     "Offset"
#endif

BOOL APIENTRY PolyDlgProc(HWND, UINT, WPARAM, LPARAM);
void CalcPolyRectInDialog(HWND, LPRECT);

//Forward class references
class CImpIPolyline;
typedef class CImpIPolyline *PCImpIPolyline;

class CImpIConnPtCont;
typedef CImpIConnPtCont *PCImpIConnPtCont;

class CConnectionPoint;
typedef CConnectionPoint *PCConnectionPoint;

class CImpIPersistStorage;
typedef class CImpIPersistStorage *PCImpIPersistStorage;

class CImpIPersistStreamInit;
typedef class CImpIPersistStreamInit *PCImpIPersistStreamInit;

class CImpIDataObject;
typedef class CImpIDataObject *PCImpIDataObject;

class CImpIOleObject;
typedef class CImpIOleObject *PCImpIOleObject;

class CImpIViewObject;
typedef class CImpIViewObject *PCImpIViewObject;

class CImpIRunnableObject;
typedef class CImpIRunnableObject *PCImpIRunnableObject;

class CImpIExternalConnection;
typedef class CImpIExternalConnection *PCImpIExternalConnection;

class CImpIOleInPlaceObject;
typedef class CImpIOleInPlaceObject *PCImpIOleInPlaceObject;

class CImpIOleInPlaceActiveObject;
typedef class CImpIOleInPlaceActiveObject *PCImpIOleInPlaceActiveObject;

//CHAPTER24MOD
class CImpISpecifyPP;
typedef CImpISpecifyPP *PCImpISpecifyPP;

class CImpIProvideClassInfo;
typedef CImpIProvideClassInfo *PCImpIProvideClassInfo;

class CImpIDispatch;
typedef CImpIDispatch *PCImpIDispatch;

class CImpIPolylineControl;
typedef CImpIPolylineControl *PCImpIPolylineControl;

class CImpIOleControl;
typedef CImpIOleControl *PCImpIOleControl;

class CAdviseRouter;
typedef CAdviseRouter *PCAdviseRouter;
//End CHAPTER24MOD

//POLYLINE.CPP
class CPolyline : public IUnknown
    {
    friend LRESULT APIENTRY PolylineWndProc(HWND, UINT, WPARAM
        , LPARAM);

    friend class CImpIPolyline;
    friend class CImpIConnPtCont;
    friend class CConnectionPoint;
    friend class CImpIPersistStorage;
    friend class CImpIPersistStreamInit;
    friend class CImpIDataObject;

    friend BOOL APIENTRY PolyDlgProc(HWND, UINT, WPARAM, LPARAM);
    friend class CImpIOleObject;
    friend class CImpIViewObject;
    friend class CImpIRunnableObject;
    friend class CImpIExternalConnection;
    friend class CImpIOleInPlaceObject;
    friend class CImpIOleInPlaceActiveObject;

    //CHAPTER24MOD
    friend class CImpISpecifyPP;
    friend class CImpIProvideClassInfo;
    friend class CImpIDispatch;
    friend class CImpIPolylineControl;
    friend class CImpIOleControl;

    friend class CAdviseRouter;
    //End CHAPTER24MOD

    protected:
        HWND            m_hWnd;
        HINSTANCE       m_hInst;

        ULONG           m_cRef;         //Object reference count
        LPUNKNOWN       m_pUnkOuter;    //Controlling Unknown
        PFNDESTROYED    m_pfnDestroy;   //Function called on closure
        BOOL            m_fDirty;       //Have we changed?
        POLYLINEDATA    m_pl;           //Our actual data

        //Contained interfaces
        PCImpIPolyline          m_pImpIPolyline;
        PCImpIConnPtCont        m_pImpIConnPtCont;
        PCImpIPersistStorage    m_pImpIPersistStorage;
        PCImpIPersistStreamInit m_pImpIPersistStreamInit;
        PCImpIDataObject        m_pImpIDataObject;

        /*
         * Connected sink:  only one per object.  We get this
         * through our connection point.
         */
        IPolylineAdviseSink10  *m_pAdv;
        PCConnectionPoint       m_pConnPt;

        PCStringTable   m_pST;          //Object strings
        UINT            m_cf;           //Object clipboard format
        CLSID           m_clsID;        //Current CLSID

        //We have to hold these for IPersistStorage::Save
        LPSTORAGE       m_pIStorage;
        LPSTREAM        m_pIStream;

        LPDATAADVISEHOLDER  m_pIDataAdviseHolder;

        //These are default handler interfaces we use
        LPUNKNOWN           m_pDefIUnknown;
        LPVIEWOBJECT2       m_pDefIViewObject;
        LPPERSISTSTORAGE    m_pDefIPersistStorage;
        LPDATAOBJECT        m_pDefIDataObject;

        //Implemented and used interfaces
        PCImpIOleObject     m_pImpIOleObject;       //Implemented
        LPOLEADVISEHOLDER   m_pIOleAdviseHolder;    //Used

        LPOLECLIENTSITE     m_pIOleClientSite;      //Used

        PCImpIViewObject    m_pImpIViewObject;      //Implemented
        LPADVISESINK        m_pIAdviseSink;         //Used
        DWORD               m_dwFrozenAspects;      //Freeze
        DWORD               m_dwAdviseAspects;      //SetAdvise
        DWORD               m_dwAdviseFlags;        //SetAdvise

        POLYLINEDATA        m_plContent;            //For freezing
        POLYLINEDATA        m_plThumbnail;          //For freezing

        PCImpIRunnableObject m_pImpIRunnableObject; //Implemented
        HWND                m_hDlg;                 //Editing window

        PCImpIExternalConnection m_pImpIExternalConnection; //Implemented
        BOOL                     m_fLockContainer;
        DWORD                    m_dwRegROT;

        LPOLEINPLACESITE            m_pIOleIPSite;
        LPOLEINPLACEFRAME           m_pIOleIPFrame;
        LPOLEINPLACEUIWINDOW        m_pIOleIPUIWindow;

        PCImpIOleInPlaceObject       m_pImpIOleIPObject;
        PCImpIOleInPlaceActiveObject m_pImpIOleIPActiveObject;

        HMENU                       m_hMenuShared;
        HOLEMENU                    m_hOLEMenu;

        PCHatchWin                  m_pHW;
        BOOL                        m_fAllowInPlace;
        BOOL                        m_fUIActive;
        BOOL                        m_fContainerKnowsInsideOut;

        //CHAPTER24MOD
        PCImpISpecifyPP             m_pImpISpecifyPP;
        PCImpIProvideClassInfo      m_pImpIProvideClassInfo;
        PCImpIDispatch              m_pImpIDispatch;
        PCImpIPolylineControl       m_pImpIPolylineControl;
        PCImpIOleControl            m_pImpIOleControl;

        //Our own type lib for the object
        ITypeLib                   *m_pITypeLib;

        //From the container;
        IOleControlSite            *m_pIOleControlSite;
        IDispatch                  *m_pIDispatchAmbients;
        BOOL                        m_fFreezeEvents;
        CONTROLINFO                 m_ctrlInfo;

        //Other ambients
        BOOL                        m_fHatch;
        BOOL                        m_fUIDead;
        //End CHAPTER24MOD

    protected:
        void      PointScale(LPRECT, LPPOINTS, BOOL);
        void      Draw(HDC, BOOL, BOOL, LPRECT, PPOLYLINEDATA);
        HINSTANCE Instance(void);
        LPTSTR    String(UINT);
        void      SendAdvise(UINT);
        void      RectConvertMappings(LPRECT, BOOL);

        /*
         * These members pulled from IPolyline now serve as a
         * central store for this functionality to be used from
         * other interfaces like IPersistStorage and IDataObject.
         * Other interfaces later may also use them.
         */
        STDMETHODIMP DataSet(PPOLYLINEDATA, BOOL, BOOL);
        STDMETHODIMP DataGet(PPOLYLINEDATA);

        STDMETHODIMP RenderNative(HGLOBAL *);
        STDMETHODIMP RenderBitmap(HBITMAP *);
        STDMETHODIMP RenderMetafilePict(HGLOBAL *);

    public:
        CPolyline(LPUNKNOWN, PFNDESTROYED, HINSTANCE);
        ~CPolyline(void);

        BOOL      Init(void);

        //Non-delegating object IUnknown
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        HRESULT  InPlaceActivate(LPOLECLIENTSITE, BOOL);
        void     InPlaceDeactivate(void);
        HRESULT  UIActivate(void);
        void     UIDeactivate(void);

        //CHAPTER24MOD
        BOOL     AmbientGet(DISPID, VARIANT *);
        void     AmbientsInitialize(DWORD);
        //End CHAPTER24MOD
    };

typedef CPolyline *PCPolyline;

//Codes for CPolyline::SendAdvise
//......Code.....................Method called in CPolyline::SendAdvise
#define OBJECTCODE_SAVED       0 //IOleAdviseHolder::SendOnSave
#define OBJECTCODE_CLOSED      1 //IOleAdviseHolder::SendOnClose
#define OBJECTCODE_RENAMED     2 //IOleAdviseHolder::SendOnRename
#define OBJECTCODE_SAVEOBJECT  3 //IOleClientSite::SaveObject
#define OBJECTCODE_DATACHANGED 4 //IDataAdviseHolder::SendOnDataChange
#define OBJECTCODE_SHOWWINDOW  5 //IOleClientSite::OnShowWindow(TRUE)
#define OBJECTCODE_HIDEWINDOW  6 //IOleClientSite::OnShowWindow(FALSE)
#define OBJECTCODE_SHOWOBJECT  7 //IOleClientSite::ShowObject

//CHAPTER24MOD
//Flags for AmbientsInitialize
enum
    {
    INITAMBIENT_SHOWHATCHING = 0x00000001,
    INITAMBIENT_UIDEAD       = 0x00000002,
    INITAMBIENT_BACKCOLOR    = 0x00000004,
    INITAMBIENT_FORECOLOR    = 0x00000008,
    INITAMBIENT_ALL          = 0xFFFFFFFF
    };
//End CHAPTER24MOD

//Interface implementation contained in the Polyline.

class CImpIPolyline : public IPolyline10
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIPolyline(PCPolyline, LPUNKNOWN);
        ~CImpIPolyline(void);

        //IUnknown members.
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //Manipulation members:
        STDMETHODIMP Init(HWND, LPRECT, DWORD, UINT);
        STDMETHODIMP New(void);
        STDMETHODIMP Undo(void);
        STDMETHODIMP Window(HWND *);

        STDMETHODIMP RectGet(LPRECT);
        STDMETHODIMP SizeGet(LPRECT);
        STDMETHODIMP RectSet(LPRECT, BOOL);
        STDMETHODIMP SizeSet(LPRECT, BOOL);

        STDMETHODIMP ColorSet(UINT, COLORREF, COLORREF *);
        STDMETHODIMP ColorGet(UINT, COLORREF *);

        STDMETHODIMP LineStyleSet(UINT, UINT *);
        STDMETHODIMP LineStyleGet(UINT *);
    };

class CImpIConnPtCont : public IConnectionPointContainer
    {
    private:
        ULONG               m_cRef;      //Interface ref count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIConnPtCont(PCPolyline, LPUNKNOWN);
        ~CImpIConnPtCont(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(DWORD) AddRef(void);
        STDMETHODIMP_(DWORD) Release(void);

        //IConnectionPointContainer members
        STDMETHODIMP EnumConnectionPoints(IEnumConnectionPoints **);
    STDMETHODIMP FindConnectionPoint(REFIID, IConnectionPoint **);
    };

/*
 * This connection point will only allow one connection,
 * so it always returns the same connection key.  It also
 * doesn't bother to implement enumerations.
 */

#define CCONNMAX    1
#define ADVISEKEY   72388       //Arbitrary

class CConnectionPoint : public IConnectionPoint
    {
    private:
        ULONG           m_cRef;     //Object reference count
        PCPolyline      m_pObj;     //Containing object.
        //CHAPTER24MOD
        IID             m_iid;      //Outgoing interface
        //End CHAPTER24MOD

    public:
        CConnectionPoint(PCPolyline);
        ~CConnectionPoint(void);

        //CHAPTER24MOD
        void SetIID(REFIID);
        //End CHAPTER24MOD

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IConnectionPoint members
        STDMETHODIMP GetConnectionInterface(IID *);
        STDMETHODIMP GetConnectionPointContainer
            (IConnectionPointContainer **);
        STDMETHODIMP Advise(LPUNKNOWN, DWORD *);
        STDMETHODIMP Unadvise(DWORD);
        STDMETHODIMP EnumConnections(IEnumConnections **);
    };

class CImpIPersistStorage : public IPersistStorage
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown
        PSSTATE             m_psState;   //Storage state

    public:
        CImpIPersistStorage(PCPolyline, LPUNKNOWN);
        ~CImpIPersistStorage(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP InitNew(LPSTORAGE);
        STDMETHODIMP Load(LPSTORAGE);
        STDMETHODIMP Save(LPSTORAGE, BOOL);
        STDMETHODIMP SaveCompleted(LPSTORAGE);
        STDMETHODIMP HandsOffStorage(void);
    };

class CImpIPersistStreamInit : public IPersistStreamInit
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIPersistStreamInit(PCPolyline, LPUNKNOWN);
        ~CImpIPersistStreamInit(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP Load(LPSTREAM);
        STDMETHODIMP Save(LPSTREAM, BOOL);
        STDMETHODIMP GetSizeMax(ULARGE_INTEGER *);
        STDMETHODIMP InitNew(void);
    };

//IDATAOBJ.CPP
class CImpIDataObject : public IDataObject
    {
    private:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIDataObject(PCPolyline, LPUNKNOWN);
        ~CImpIDataObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IDataObject members
        STDMETHODIMP GetData(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP GetDataHere(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP QueryGetData(LPFORMATETC);
        STDMETHODIMP GetCanonicalFormatEtc(LPFORMATETC,LPFORMATETC);
        STDMETHODIMP SetData(LPFORMATETC, LPSTGMEDIUM, BOOL);
        STDMETHODIMP EnumFormatEtc(DWORD, LPENUMFORMATETC *);
        STDMETHODIMP DAdvise(LPFORMATETC, DWORD, LPADVISESINK
            , DWORD *);
        STDMETHODIMP DUnadvise(DWORD);
        STDMETHODIMP EnumDAdvise(LPENUMSTATDATA *);
    };

//IENUMFE.CPP
class CEnumFormatEtc : public IEnumFORMATETC
    {
    private:
        ULONG           m_cRef;
        LPUNKNOWN       m_pUnkRef;
        ULONG           m_iCur;
        ULONG           m_cfe;
        LPFORMATETC     m_prgfe;

    public:
        CEnumFormatEtc(LPUNKNOWN, ULONG, LPFORMATETC);
        ~CEnumFormatEtc(void);

        //IUnknown members that delegate to m_pUnkRef.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IEnumFORMATETC members
        STDMETHODIMP Next(ULONG, LPFORMATETC, ULONG *);
        STDMETHODIMP Skip(ULONG);
        STDMETHODIMP Reset(void);
        STDMETHODIMP Clone(IEnumFORMATETC **);
    };

typedef CEnumFormatEtc *PCEnumFormatEtc;

//CHAPTER24MOD
//Our own properties verb
#define POLYLINEVERB_PROPERTIES     1
//End CHAPTER24MOD

class CImpIOleObject : public IOleObject
    {
    private:
        ULONG           m_cRef;
        PCPolyline      m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIOleObject(PCPolyline, LPUNKNOWN);
        ~CImpIOleObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IOleObject members
        STDMETHODIMP SetClientSite(LPOLECLIENTSITE);
        STDMETHODIMP GetClientSite(LPOLECLIENTSITE *);
        STDMETHODIMP SetHostNames(LPCOLESTR, LPCOLESTR);
        STDMETHODIMP Close(DWORD);
        STDMETHODIMP SetMoniker(DWORD, LPMONIKER);
        STDMETHODIMP GetMoniker(DWORD, DWORD, LPMONIKER *);
        STDMETHODIMP InitFromData(LPDATAOBJECT, BOOL, DWORD);
        STDMETHODIMP GetClipboardData(DWORD, LPDATAOBJECT *);
        STDMETHODIMP DoVerb(LONG, LPMSG, LPOLECLIENTSITE, LONG
                         , HWND, LPCRECT);
        STDMETHODIMP EnumVerbs(LPENUMOLEVERB *);
        STDMETHODIMP Update(void);
        STDMETHODIMP IsUpToDate(void);
        STDMETHODIMP GetUserClassID(CLSID *);
        STDMETHODIMP GetUserType(DWORD, LPOLESTR *);
        STDMETHODIMP SetExtent(DWORD, LPSIZEL);
        STDMETHODIMP GetExtent(DWORD, LPSIZEL);
        STDMETHODIMP Advise(LPADVISESINK, DWORD *);
        STDMETHODIMP Unadvise(DWORD);
        STDMETHODIMP EnumAdvise(LPENUMSTATDATA *);
        STDMETHODIMP GetMiscStatus(DWORD, DWORD *);
        STDMETHODIMP SetColorScheme(LPLOGPALETTE);
    };

//IVIEWOBJ.CPP
class CImpIViewObject : public IViewObject2
    {
    private:
        ULONG           m_cRef;
        PCPolyline      m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIViewObject(PCPolyline, LPUNKNOWN);
        ~CImpIViewObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IViewObject members
        STDMETHODIMP Draw(DWORD, LONG, LPVOID
            , DVTARGETDEVICE *, HDC, HDC, LPCRECTL
            , LPCRECTL, BOOL (CALLBACK *)(DWORD), DWORD);
        STDMETHODIMP GetColorSet(DWORD, LONG, LPVOID
            , DVTARGETDEVICE *, HDC, LPLOGPALETTE *);
        STDMETHODIMP Freeze(DWORD, LONG, LPVOID, LPDWORD);
        STDMETHODIMP Unfreeze(DWORD);
        STDMETHODIMP SetAdvise(DWORD, DWORD, LPADVISESINK);
        STDMETHODIMP GetAdvise(LPDWORD, LPDWORD, LPADVISESINK *);
        STDMETHODIMP GetExtent(DWORD, LONG, DVTARGETDEVICE *
            , LPSIZEL);
    };

class CImpIRunnableObject : public IRunnableObject
    {
    protected:
        ULONG           m_cRef;
        PCPolyline      m_pObj;
        LPUNKNOWN       m_pUnkOuter;

    public:
        CImpIRunnableObject(PCPolyline, LPUNKNOWN);
        ~CImpIRunnableObject(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetRunningClass(LPCLSID);
        STDMETHODIMP Run(LPBINDCTX);
        STDMETHODIMP_(BOOL) IsRunning(void);
        STDMETHODIMP LockRunning(BOOL, BOOL);
        STDMETHODIMP SetContainedObject(BOOL);
    };

class CImpIExternalConnection : public IExternalConnection
    {
    protected:
        ULONG           m_cRef;
        PCPolyline      m_pObj;
        LPUNKNOWN       m_pUnkOuter;
        DWORD           m_cLockStrong;

    public:
        CImpIExternalConnection(PCPolyline, LPUNKNOWN);
        ~CImpIExternalConnection(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(DWORD) AddConnection(DWORD, DWORD);
        STDMETHODIMP_(DWORD) ReleaseConnection(DWORD, DWORD, BOOL);
    };

class CImpIOleInPlaceObject : public IOleInPlaceObject
    {
    protected:
        ULONG               m_cRef;
        PCPolyline          m_pObj;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIOleInPlaceObject(PCPolyline, LPUNKNOWN);
        ~CImpIOleInPlaceObject(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetWindow(HWND *);
        STDMETHODIMP ContextSensitiveHelp(BOOL);
        STDMETHODIMP InPlaceDeactivate(void);
        STDMETHODIMP UIDeactivate(void);
        STDMETHODIMP SetObjectRects(LPCRECT, LPCRECT);
        STDMETHODIMP ReactivateAndUndo(void);
    };

class CImpIOleInPlaceActiveObject
    : public IOleInPlaceActiveObject
    {
    protected:
        ULONG               m_cRef;
        PCPolyline          m_pObj;
        LPUNKNOWN           m_pUnkOuter;

    public:
        CImpIOleInPlaceActiveObject(PCPolyline, LPUNKNOWN);
        ~CImpIOleInPlaceActiveObject(void);

        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetWindow(HWND *);
        STDMETHODIMP ContextSensitiveHelp(BOOL);
        STDMETHODIMP TranslateAccelerator(LPMSG);
        STDMETHODIMP OnFrameWindowActivate(BOOL);
        STDMETHODIMP OnDocWindowActivate(BOOL);
        STDMETHODIMP ResizeBorder(LPCRECT, LPOLEINPLACEUIWINDOW
                         , BOOL);
        STDMETHODIMP EnableModeless(BOOL);
    };

//CHAPTER24MOD

#define CPROPPAGES   1   //Total property page count.

class CImpISpecifyPP : public ISpecifyPropertyPages
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        PCPolyline      m_pObj;      //Backpointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpISpecifyPP(PCPolyline, LPUNKNOWN);
        ~CImpISpecifyPP(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetPages(CAUUID *);
    };

class CImpIProvideClassInfo : public IProvideClassInfo
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        PCPolyline      m_pObj;      //Backpointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIProvideClassInfo(PCPolyline, LPUNKNOWN);
        ~CImpIProvideClassInfo(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassInfo(LPTYPEINFO *);
    };

class CImpIDispatch : public IDispatch
    {
    public:

    private:
        ULONG           m_cRef;     //For debugging
        PCPolyline      m_pObj;
        LPUNKNOWN       m_pUnkOuter;

        ITypeInfo      *m_pITI;     //Type information

    public:
        CImpIDispatch(PCPolyline, LPUNKNOWN);
        ~CImpIDispatch(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IDispatch members
        STDMETHODIMP GetTypeInfoCount(UINT *);
        STDMETHODIMP GetTypeInfo(UINT, LCID, ITypeInfo **);
        STDMETHODIMP GetIDsOfNames(REFIID, OLECHAR **, UINT, LCID
            , DISPID *);
        STDMETHODIMP Invoke(DISPID, REFIID, LCID, WORD
            , DISPPARAMS *, VARIANT *, EXCEPINFO *, UINT *);
    };

class CImpIPolylineControl : public IPolylineControl
    {
    protected:
        ULONG               m_cRef;      //Interface reference count
        PCPolyline          m_pObj;      //Back pointer to object
        LPUNKNOWN           m_pUnkOuter; //Controlling unknown

    public:
        CImpIPolylineControl(PCPolyline, LPUNKNOWN);
        ~CImpIPolylineControl(void);

        //IUnknown members.
        STDMETHODIMP QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //Manipulation members:
        STDMETHODIMP_(void)      put_BackColor(OLE_COLOR);
        STDMETHODIMP_(OLE_COLOR) get_BackColor(void);
        STDMETHODIMP_(void)      put_LineColor(OLE_COLOR);
        STDMETHODIMP_(OLE_COLOR) get_LineColor(void);
        STDMETHODIMP_(void)      put_LineStyle(short);
        STDMETHODIMP_(short)     get_LineStyle(void);
        STDMETHODIMP             Clear(void);
        STDMETHODIMP             RemoveLastPoint(void);
    };

class CImpIOleControl : public IOleControl
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        PCPolyline      m_pObj;      //Backpointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIOleControl(PCPolyline, LPUNKNOWN);
        ~CImpIOleControl(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetControlInfo(LPCONTROLINFO);
        STDMETHODIMP OnMnemonic(LPMSG);
        STDMETHODIMP OnAmbientPropertyChange(DISPID);
        STDMETHODIMP FreezeEvents(BOOL);
    };

class CAdviseRouter : public IPolylineAdviseSink10
    {
    private:
        ULONG       m_cRef;
        PCPolyline  m_pObj;
        IDispatch  *m_pIDispatch;

    public:
        CAdviseRouter(IDispatch *, PCPolyline);
        ~CAdviseRouter(void);

        void Invoke(DISPID);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //Advise members.
        STDMETHODIMP_(void) OnPointChange(void);
        STDMETHODIMP_(void) OnSizeChange(void);
        STDMETHODIMP_(void) OnColorChange(void);
        STDMETHODIMP_(void) OnLineStyleChange(void);
    };

//These values match the ID's in polyline.odl
enum
    {
    EVENT_ONPOINTCHANGE=0,
    EVENT_ONSIZECHANGE=1,
    EVENT_ONCOLORCHANGE=2,
    EVENT_ONLINESTYLECHANGE=3
    };

//End CHAPTER24MOD

#endif  //_POLYLINE_H_


POLYWIN.CPP

/*
 * POLYWIN.CPP
 * Polyline Component Chapter 24
 *
 * Window procedure for the polyline drawing window and support
 * functions.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "polyline.h"

/*
 * PolylineWndProc
 *
 * Purpose:
 *  Window procedure for the polyline drawing window.
 */

LRESULT APIENTRY PolylineWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCPolyline      ppl;
    PAINTSTRUCT     ps;
    HDC             hDC;
    POINTS          pt;
    RECT            rc;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    ppl=(PCPolyline)GetWindowLong(hWnd, PLWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            ppl=(PCPolyline)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, PLWL_STRUCTURE, (LONG)ppl);
            ppl->m_hWnd=hWnd;
            break;

        case WM_PAINT:
            hDC=BeginPaint(hWnd, &ps);
            GetClientRect(hWnd, &rc);
            ppl->Draw(hDC, FALSE, TRUE, &rc, NULL);
            EndPaint(hWnd, &ps);
            break;

        case WM_LBUTTONDOWN:
            //CHAPTER24MOD
            //If the UI is dead, we ignore all mouse clicks.
            if (ppl->m_fUIDead)
                break;
            //End CHAPTER24MOD

            //We become UI Active with mouse action
            if (!ppl->m_fUIActive && NULL!=ppl->m_pIOleIPSite)
                ppl->UIActivate();

            //Stop if we are already at the limit.
            if (CPOLYLINEPOINTS==ppl->m_pl.cPoints)
                {
                MessageBeep(0);
                break;
                }

            //Convert the points into 0-32767 range
            GetClientRect(hWnd, &rc);
            pt=MAKEPOINTS(lParam);
            ppl->PointScale(&rc, &pt, FALSE);

            ppl->m_pl.rgpt[ppl->m_pl.cPoints++]=pt;

            //Draw the lines to this new point only.
            hDC=GetDC(hWnd);
            ppl->Draw(hDC, FALSE, FALSE, &rc, NULL);
            ReleaseDC(hWnd, hDC);

            if (NULL!=ppl->m_pAdv)
                ppl->m_pAdv->OnPointChange();

            ppl->SendAdvise(OBJECTCODE_DATACHANGED);
            break;

        case WM_COMMAND:
            //CHAPTER24MOD
            //Ignore Open commands when we're UIDead
            if (ppl->m_fUIDead)
                break;
            //End CHAPTER24MOD

            if (ID_HATCHWINDOW==wID
                && HWN_BORDERDOUBLECLICKED==wCode)
                {
                ppl->m_pImpIOleObject->DoVerb(OLEIVERB_OPEN, NULL
                    , ppl->m_pIOleClientSite, -1, NULL, NULL);
                }
            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }

/*
 * CPolyline::Draw
 *
 * Purpose:
 *  Paints the current line in the polyline window.
 *
 * Parameters:
 *  hDC             HDC to draw on, a metafile or printer DC.
 *  fMetafile       BOOL indicating if hDC is a metafile or not,
 *                  so we can avoid operations that RIP.
 *  fEntire         BOOL indicating if we should draw the entire
 *                  figure or not.
 *  pRect           LPRECT defining the bounds in which to draw.
 *  ppl             PPOLYLINEDATA to draw.  If NULL, use current.
 *
 * Return Value:
 *  None
 */

void CPolyline::Draw(HDC hDC, BOOL fMetafile, BOOL fEntire
    , LPRECT pRect, PPOLYLINEDATA ppl)
    {
    HBRUSH          hBrush;
    HPEN            hPen;
    HGDIOBJ         hObj1, hObj2;
    UINT            i, j;
    int             nDC;
    POINTS          pt1,pt2;
    POINT           rgpt[CPOLYLINEPOINTS];

    if (NULL==ppl)
        ppl=&m_pl;

    nDC=SaveDC(hDC);

    for (i=0; i < ppl->cPoints; i++)
        {
        rgpt[i].x=ppl->rgpt[i].x;
        rgpt[i].y=ppl->rgpt[i].y;
        }

    //Printer and frozen differences handled in IViewObject::Draw

    hPen=CreatePen(ppl->iLineStyle, 1, ppl->rgbLine);
    hObj1=SelectObject(hDC, hPen);

    hBrush=CreateSolidBrush(ppl->rgbBackground);
    hObj2=SelectObject(hDC, hBrush);
    SetBkColor(hDC, ppl->rgbBackground);

    /*
     * Either draw the entire figure or just a single point.  The
     * entire figure also includes erasing the background completely,
     * since hDC may be a metafile DC.  Drawing a single point just
     * updates the figure for that new point.
     */
    if (fEntire || 0==ppl->cPoints)
        {
        //Erase the background for bitmaps and metafiles.
        SelectObject(hDC, GetStockObject(NULL_PEN));
        Rectangle(hDC, pRect->left, pRect->top, pRect->right+1
            , pRect->bottom+1);
        SelectObject(hDC, hPen);

        /*
         * If we are drawing the entire figure, then loop through
         * each point drawing a line to each successive point.
         */

        for (i=0; i < ppl->cPoints; i++)
            {
            for (j=i; j < ppl->cPoints; j++)
                {
                pt1.x=(short)rgpt[i].x;
                pt1.y=(short)rgpt[i].y;
                pt2.x=(short)rgpt[j].x;
                pt2.y=(short)rgpt[j].y;

                PointScale(pRect, &pt1, TRUE);
                PointScale(pRect, &pt2, TRUE);
                MoveToEx(hDC, pt1.x, pt1.y, NULL);
                LineTo(hDC, pt2.x, pt2.y);
                }
            }
        }
    else
        {
        /*
         * If we are only drawing the last point, just cycle once
         * through previous points.
         */

        //Get the last point entered in the array.
        j=ppl->cPoints-1;
        pt1.x=(short)rgpt[j].x;
        pt1.y=(short)rgpt[j].y;
        PointScale(pRect, &pt1, TRUE);

        for (i=0; i < j; i++)
            {
            pt2.x=(short)rgpt[i].x;
            pt2.y=(short)rgpt[i].y;
            PointScale(pRect, &pt2, TRUE);

            MoveToEx(hDC, pt1.x, pt1.y, NULL);
            LineTo(hDC, pt2.x, pt2.y);
            }
        }

    //If we have one point, draw a dot to indicate it's position.
    if (1==ppl->cPoints)
        {
        pt1.x=(short)rgpt[0].x;
        pt1.y=(short)rgpt[0].y;
        PointScale(pRect, &pt1, TRUE);
        SetPixel(hDC, pt1.x, pt1.y, m_pl.rgbLine);
        }

    SelectObject(hDC, hObj1);
    SelectObject(hDC, hObj2);
    DeleteObject(hBrush);
    DeleteObject(hPen);

    RestoreDC(hDC, nDC);
    return;
    }

/*
 * CPolyline::PointScale
 *
 * Purpose:
 *  Scales a point to or from a relative window coordinate to a
 *  0-32767 coordinate.
 *
 * Parameters:
 *  pRect           LPRECT of the window.
 *  ppt             LPPOINTS to convert
 *  fScaleToWindow  BOOL indicating direction of scaling.
 *
 * Return Value:
 *  None
 */

void CPolyline::PointScale(LPRECT pRect, LPPOINTS ppt
    , BOOL fScaleToWindow)
    {
    DWORD   cx, cy;

    //Window size
    cx=(DWORD)(pRect->right-pRect->left);
    cy=(DWORD)(pRect->bottom-pRect->top);

    //Prevent crashes
    if (0L==cx) cx=1;
    if (0L==cy) cy=1;

    //Must use DWORD to insure proper scaling.

    /*
     * As an in-proc server we may not have a rectangle where the
     * top left was (0,0) which was always true when we drew to
     * a Polyline window.  But this may be a container's hDC in
     * which case we'd better place the points in the container's
     * rectangle.  That is, we have to add/subtract pRect->left
     * and ->top in these calculations.
     */

    if (fScaleToWindow)
        {
        ppt->x=pRect->left+(UINT)(((DWORD)ppt->x*cx) >> 15);
        ppt->y=pRect->top+(UINT)(((DWORD)ppt->y*cy)  >> 15);
        }
    else
        {
        ppt->x=(UINT)(((DWORD)(ppt->x - pRect->left) << 15)/cx);
        ppt->y=(UINT)(((DWORD)(ppt->y - pRect->top)  << 15)/cy);
        }

    return;
    }

/*
 * PolyDlgProc
 *
 * Purpose:
 *  Dialog procedure for a window in which to display the Polyline
 *  for editing.  This pretty much handles all editing functionality
 *  for the embedded object.
 */

BOOL APIENTRY PolyDlgProc(HWND hDlg, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCPolyline      ppl=NULL;
    RECT            rc;
    UINT            uID, uTemp;
    UINT            cx, cy;

   #ifdef WIN32
    ppl=(PCPolyline)GetProp(hDlg, PROP_POINTER);
   #else
    WORD            w1, w2;

    w1=(WORD)GetProp(hDlg, PROP_SELECTOR);
    w2=(WORD)GetProp(hDlg, PROP_OFFSET);

    ppl=(PCPolyline)MAKELP(w1, w2);
   #endif

    switch (iMsg)
        {
        case WM_INITDIALOG:
            ppl=(PCPolyline)lParam;
            ppl->m_hDlg=hDlg;

           #ifdef WIN32
            //Properties are 32-bits in Win32
            SetProp(hDlg, PROP_POINTER, (HANDLE)ppl);
           #else
            SetProp(hDlg, PROP_SELECTOR, (HANDLE)SELECTOROF(ppl));
            SetProp(hDlg, PROP_OFFSET,   (HANDLE)OFFSETOF(ppl));
           #endif

            //Get the rectangle that Polyline occupies
            CalcPolyRectInDialog(hDlg, &rc);

            //Try to create the window.
            ppl->m_pImpIPolyline->Init(hDlg, &rc, WS_CHILD | WS_VISIBLE
                , ID_POLYLINE);

            //Set the initial line style radiobutton.
            ppl->m_pImpIPolyline->LineStyleGet(&uTemp);
            CheckRadioButton(hDlg, ID_LINESOLID, ID_LINEDASHDOTDOT
                , uTemp+ID_LINEMIN);

            return FALSE;

        case WM_SHOWWINDOW:
            if (LOWORD(wParam))
                {
                //Center the dialog on the screen
                cx=GetSystemMetrics(SM_CXSCREEN);
                cy=GetSystemMetrics(SM_CYSCREEN);
                GetWindowRect(hDlg, &rc);
                SetWindowPos(hDlg, NULL, (cx-(rc.right-rc.left))/2
                    , (cy-(rc.bottom-rc.top))/2, 0, 0, SWP_NOZORDER
                    | SWP_NOSIZE);

                //We didn't SetFocus from WM_INITDIALOG.  Do it now.
                SetFocus(GetDlgItem(hDlg, IDOK));
                }

            break;

        case WM_COMMAND:
            uID=LOWORD(wParam);

            switch (uID)
                {
                case IDOK:
                    //Close the dialog, but save first.
                    if (NULL!=ppl)
                        {
                        HRESULT     hr;

                        /*
                         * Instead of just closing, see if we can
                         * activate in-place again--we know we're an
                         * inside out object and want to be activated
                         * when we can be.  If we can't activate
                         * in-place, then close like we normally
                         * would.  Note that this makes sense only
                         * with an inside-out supporting container,
                         * which we determine from whether it ever
                         * used OLEIVERB_INPLACEACTIVATE.
                         */

                        hr=ResultFromScode(E_FAIL);

                        if (ppl->m_fContainerKnowsInsideOut)
                            {
                            hr=ppl->InPlaceActivate
                                (ppl->m_pIOleClientSite, FALSE);
                            }

                        if (FAILED(hr))
                            {
                            ppl->m_pImpIOleObject->Close
                                (OLECLOSE_SAVEIFDIRTY);
                            }
                        }

                    break;

                case ID_UNDO:
                    if (NULL!=ppl)
                        ppl->m_pImpIPolyline->Undo();
                    break;

                case ID_COLORLINE:
                case ID_COLORBACK:
                    if (NULL!=ppl)
                        {
                        UINT            i;
                        COLORREF        rgColors[16];
                        CHOOSECOLOR     cc;

                        //Invoke the color chooser for either color
                        uTemp=(ID_COLORBACK==uID)
                            ? POLYLINECOLOR_BACKGROUND
                            : POLYLINECOLOR_LINE;

                        for (i=0; i<16; i++)
                            rgColors[i]=RGB(0, 0, i*16);

                        memset(&cc, 0, sizeof(CHOOSECOLOR));
                        cc.lStructSize=sizeof(CHOOSECOLOR);
                        cc.lpCustColors=rgColors;
                        cc.hwndOwner=hDlg;
                        cc.Flags=CC_RGBINIT;
                        ppl->m_pImpIPolyline->ColorGet(uTemp
                            , &cc.rgbResult);

                        if (ChooseColor(&cc))
                            {
                            //rgColor is just some COLORREF pointer
                            ppl->m_pImpIPolyline->ColorSet(uTemp
                                , cc.rgbResult, rgColors);
                            }
                        }
                    break;

                case ID_LINESOLID:
                case ID_LINEDASH:
                case ID_LINEDOT:
                case ID_LINEDASHDOT:
                case ID_LINEDASHDOTDOT:
                    if (NULL!=ppl)
                        {
                        ppl->m_pImpIPolyline
                            ->LineStyleSet(uID-ID_LINEMIN, &uTemp);
                        }

                    break;
                }
            break;

        case WM_DESTROY:
           #ifdef WIN32
            RemoveProp(hDlg, PROP_POINTER);
           #else
            RemoveProp(hDlg, PROP_SELECTOR);
            RemoveProp(hDlg, PROP_OFFSET);
           #endif
            break;

        case WM_CLOSE:
            //This will do the IDOK handling, then send POLYM_CLOSE
            SendCommand(hDlg, IDOK, 0, NULL);
            break;

        case POLYM_CLOSE:
            ShowWindow(hDlg, SW_HIDE);
            ppl->SendAdvise(OBJECTCODE_HIDEWINDOW);
            break;
        }

    return FALSE;
    }

/*
 * CalcPolyRectInDialog
 *
 * Purpose:
 *  Calculates the rectangle that the Polyline should occupy in the
 *  dialog box.  This is separated for in-place handling because
 *  when we open the dialog from an in-place active situation we
 *  have to move the Polyline window from the container into the
 *  dialog and position it properly.
 *
 * Parameters:
 *  hDlg            HWND of the dialog.
 *  prc             LPRECT in which to store the rectangle.
 *
 * Return Value:
 *  None
 */

void CalcPolyRectInDialog(HWND hDlg, LPRECT prc)
    {
    HWND        hWnd;
    POINT       pt;

    hWnd=GetDlgItem(hDlg, ID_POLYLINERECT);
    GetWindowRect(hWnd, prc);
    SETPOINT(pt, prc->left, prc->top);
    ScreenToClient(hDlg, &pt);

    //Set the polyline just within the black frame
    SetRect(prc, pt.x, pt.y, pt.x+(prc->right-prc->left)
        , pt.y+(prc->bottom-prc->top));
    InflateRect(prc, -1, -1);
    return;
    }


RESOURCE.H

/*
 * RESOURCE.H
 * Polyline Component Chapter 24
 *
 * Definitions specifically pertaining to resources.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _RESOURCE_H_
#define _RESOURCE_H_

//Object stringtable IDs
#define IDS_POLYLINEMIN     0
#define IDS_STORAGEFORMAT   0
#define IDS_USERTYPE        1

#define IDS_CLOSECAPTION    2
#define IDS_CLOSEPROMPT     3
#define IDS_POLYLINEMAX     3

#define IDR_ICON            1

#define IDD_EDITDIALOG      1

#define ID_UNDO             100
#define ID_COLORBACK        101
#define ID_COLORLINE        102
#define ID_GROUPCOLORS      103
#define ID_GROUPPREVIEW     104
#define ID_GROUPSTYLES      105
#define ID_GROUPFIGURE      106
#define ID_POLYLINERECT     107
#define ID_POLYLINE         108

#define ID_LINEMIN          200
#define ID_LINESOLID        200     //(ID_LINEMIN+PS_SOLID)
#define ID_LINEDASH         201     //(ID_LINEMIN+PS_DASH)
#define ID_LINEDOT          202     //(ID_LINEMIN+PS_DOT)
#define ID_LINEDASHDOT      203     //(ID_LINEMIN+PS_DASHDOT)
#define ID_LINEDASHDOTDOT   204     //(ID_LINEMIN+PS_DASHDOTDOT)

//Message to close the dialog
#define POLYM_CLOSE         (WM_USER+1000)

#endif //_RESOURCE_H_


POLYPROP.CPP

/*
 * POLYPROP.CPP
 * Polyline Property Page Chapter 24
 *
 * Server module code and class code CPolyPropertyPage class.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INITGUIDS
#include "polyprop.h"

//Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;
HINSTANCE   g_hInst=NULL;   //For resources

#ifdef WIN32
TCHAR       g_szObj[]=TEXT("Object");
#else
TCHAR       g_szObjHi[]=TEXT("ObjectHi");
TCHAR       g_szObjLo[]=TEXT("ObjectLo");
#endif

/*
 * LibMain(32)
 *
 * Purpose:
 *  Entry point conditionally compiled for Win32 and Windows
 *  3.1.  Provides the proper structure for each environment.
 */

#ifdef WIN32
BOOL WINAPI LibMain32(HINSTANCE hInstance, ULONG ulReason
    , LPVOID pvReserved)
    {
    g_hInst=hInstance;

    if (DLL_PROCESS_DETACH==ulReason)
        {
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    return TRUE;
    }
#else
int PASCAL LibMain(HINSTANCE hInstance, WORD wDataSeg
    , WORD cbHeapSize, LPSTR lpCmdLine)
    {
    if (0!=cbHeapSize)
        UnlockData(0);

    g_hInst=hInstance;
    return (int)hInstance;
    }
#endif

/*
 * DllGetClassObject
 * DllCanUnloadNow
 * Standard COM exports for DLL servers.
 */

HRESULT APIENTRY DllGetClassObject(REFCLSID rclsid, REFIID riid
    , PPVOID ppv)
    {
    CPolyPPFactory *pPF;
    HRESULT         hr;

    if (CLSID_PolylinePropPage!=rclsid)
        return ResultFromScode(E_FAIL);

    //Check that we can provide the interface
    if (IID_IUnknown!=riid && IID_IClassFactory!=riid)
        return ResultFromScode(E_NOINTERFACE);

    //Return our factory's IClassFactory
    pPF=new CPolyPPFactory();

    if (NULL==pPF)
        return ResultFromScode(E_OUTOFMEMORY);

    //If the factory hasn't the interface, delete it
    hr=pPF->QueryInterface(riid, ppv);

    if (FAILED(hr))
        delete pPF;
    else
        g_cObj++;

    return hr;
    }

STDAPI DllCanUnloadNow(void)
    {
    SCODE   sc;

    //Our answer is whether there are any object or locks
    sc=(0L==g_cObj && 0L==g_cLock) ? S_OK : S_FALSE;
    return ResultFromScode(sc);
    }

/*
 * CPolyPPFactory::CPolyPPFactory
 * CPolyPPFactory::~CPolyPPFactory
 * CPolyPPFactory::QueryInterface
 * CPolyPPFactory::AddRef
 * CPolyPPFactory::Release
 */

CPolyPPFactory::CPolyPPFactory(void)
    {
    m_cRef=0L;
    return;
    }

CPolyPPFactory::~CPolyPPFactory(void)
    {
    return;
    }

STDMETHODIMP CPolyPPFactory::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CPolyPPFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPolyPPFactory::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    g_cObj--;
    return 0;
    }

/*
 * CPolyPPFactory::CreateInstance
 * CPolyPPFactory::LockServer
 */

STDMETHODIMP CPolyPPFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, PPVOID ppvObj)
    {
    PCPolyPropPage      pObj;
    HRESULT             hr;

    *ppvObj=NULL;
    hr=ResultFromScode(E_OUTOFMEMORY);

    //No aggregation supported
    if (NULL!=pUnkOuter)
        return ResultFromScode(CLASS_E_NOAGGREGATION);

    //Create the object passing function to notify on destruction.
    pObj=new CPolyPropPage(g_hInst);

    if (NULL==pObj)
        return hr;

    if (pObj->Init())
        hr=pObj->QueryInterface(riid, ppvObj);

    //Kill the object if initial creation or Init failed.
    if (FAILED(hr))
        delete pObj;
    else
        g_cObj++;

    return hr;
    }

STDMETHODIMP CPolyPPFactory::LockServer(BOOL fLock)
    {
    if (fLock)
        g_cLock++;
    else
        g_cLock--;

    return NOERROR;
    }

/***
 *** CPolyPropPage implementation
 ***/

/*
 * CPolyPropPage::CPolyPropPage
 * CPolyPropPage::~CPolyPropPage
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the module
 */

CPolyPropPage::CPolyPropPage(HINSTANCE hInst)
    {
    m_cRef=0L;

    m_hInst=hInst;
    m_hDlg=NULL;

    //Default sizes
    m_cx=300;
    m_cy=100;

    m_pIPropertyPageSite=NULL;
    m_ppObj=NULL;
    m_cObjects=0;

    m_uIDLastLine=ID_LINESOLID;
    m_crLastBack=0;
    m_crLastLine=0;

    m_fDirty=FALSE;
    return;
    }

CPolyPropPage::~CPolyPropPage(void)
    {
    if (NULL!=m_hDlg)
        DestroyWindow(m_hDlg);

    FreeAllObjects();
    ReleaseInterface(m_pIPropertyPageSite);
    return;
    }

/*
 * CPolyPropPage::QueryInterface
 * CPolyPropPage::AddRef
 * CPolyPropPage::Release
 */

STDMETHODIMP CPolyPropPage::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IPropertyPage==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CPolyPropPage::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CPolyPropPage::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    g_cObj--;
    delete this;
    return 0;
    }

/*
 * CPolyPropPage::Init
 *
 * Purpose:
 *  Performs initialization operations that might fail.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if initialization successful, FALSE
 *                  otherwise.
 */

BOOL CPolyPropPage::Init(void)
    {
    //Nothing to do
    return TRUE;
    }

/*
 * CPolyPropPage::FreeAllObjects
 *
 * Purpose:
 *  Releases all the objects from IPropertyPage::SetObjects
 *
 * Parameters:
 *  None
 */

void CPolyPropPage::FreeAllObjects(void)
    {
    UINT        i;

    if (NULL==m_ppObj)
        return;

    for (i=0; i < m_cObjects; i++)
        ReleaseInterface(m_ppObj[i]);

    delete [] m_ppObj;
    m_ppObj=NULL;
    m_cObjects=0;
    return;
    }

/*
 * CPolyPropPage::SetPageSite
 *
 * Purpose:
 *  Provides the property page with the IPropertyPageSite
 *  that contains it.  SetPageSite(NULL) will be called as
 *  part of the close sequence.
 *
 * Parameters:
 *  pPageSite       LPPROPERTYPAGESITE pointer to the site.
 */

STDMETHODIMP CPolyPropPage::SetPageSite
    (LPPROPERTYPAGESITE pPageSite)
    {
    if (NULL==pPageSite)
        ReleaseInterface(m_pIPropertyPageSite)
    else
        {
        HWND        hDlg;
        RECT        rc;

        m_pIPropertyPageSite=pPageSite;
        m_pIPropertyPageSite->AddRef();

        //We ignore locales

        /*
         * Load the dialog and determine the size it will be to
         * return through GetPageSize.  We just create the dialog
         * here and destroy it again to retrieve the size,
         * leaving Activate to create it for real.
         */
        hDlg=CreateDialogParam(m_hInst
            , MAKEINTRESOURCE(IDD_POLYLINEPROPS), GetDesktopWindow()
            , (DLGPROC)PolyPropPageProc, 0L);

        //If creation fails, use default values set in constructor
        if (NULL!=hDlg)
            {
            GetWindowRect(hDlg, &rc);
            m_cx=rc.right-rc.left;
            m_cy=rc.bottom-rc.top;

            DestroyWindow(hDlg);
            }
        }

    return NOERROR;
    }

/*
 * CPolyPropPage::Activate
 *
 * Purpose:
 *  Instructs the property page to create a window in which to
 *  display its contents, using the given parent window and
 *  rectangle.  The window should be initially visible.
 *
 * Parameters:
 *  hWndParent      HWND of the parent window.
 *  prc             LPCRECT of the rectangle to use.
 *  fModal          BOOL indicating whether the frame is modal.
 */

STDMETHODIMP CPolyPropPage::Activate(HWND hWndParent
    , LPCRECT prc, BOOL fModal)
    {
    if (NULL!=m_hDlg)
        return ResultFromScode(E_UNEXPECTED);

    m_hDlg=CreateDialogParam(m_hInst
        , MAKEINTRESOURCE(IDD_POLYLINEPROPS)
        , hWndParent, PolyPropPageProc, (LPARAM)this);

    if (NULL==m_hDlg)
        return ResultFromScode(E_OUTOFMEMORY);

    //Move the page into position and show it.
    SetWindowPos(m_hDlg, NULL, prc->left, prc->top
        , prc->right-prc->left, prc->bottom-prc->top, 0);

return NOERROR;
    }

/*
 * CPolyPropPage::Deactivate
 *
 * Purpose:
 *  Instructs the property page to destroy its window that was
 *  created in Activate.
 *
 * Parameters:
 *  None
 */

STDMETHODIMP CPolyPropPage::Deactivate(void)
    {
    if (NULL==m_hDlg)
        return ResultFromScode(E_UNEXPECTED);

    DestroyWindow(m_hDlg);
    m_hDlg=NULL;
    return NOERROR;
    }

/*
 * CPolyPropPage::GetPageInfo
 *
 * Purpose:
 *  Fills a PROPPAGEINFO structure describing the page's size,
 *  contents, and help information.
 *
 * Parameters:
 *  pPageInfo       LPPROPPAGEINFO to the structure to fill.
 */

STDMETHODIMP CPolyPropPage::GetPageInfo(LPPROPPAGEINFO pPageInfo)
    {
    IMalloc     *pIMalloc;

    if (FAILED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        return ResultFromScode(E_FAIL);

    pPageInfo->pszTitle=(LPOLESTR)pIMalloc->Alloc(80*sizeof(TCHAR));

    if (NULL!=pPageInfo->pszTitle)
       #ifdef WIN32ANSI
        wcscpy(pPageInfo->pszTitle, OLETEXT("General"));
       #else
        lstrcpy(pPageInfo->pszTitle, TEXT("General"));
       #endif

    pIMalloc->Release();

pPageInfo->size.cx      = m_cx;
    pPageInfo->size.cy      = m_cy;
pPageInfo->pszDocString = NULL;
pPageInfo->pszHelpFile  = NULL;
pPageInfo->dwHelpContext= 0;
    return NOERROR;
    }

/*
 * CPolyPropPage::SetObjects
 *
 * Purpose:
 *  Identifies the objects that are being affected by this property
 *  page (and all other pages in the frame).  These are the object
 *  to which to send new property values in the Apply member.
 *
 * Parameters:
 *  cObjects        ULONG number of objects
 *  ppUnk           IUnknown ** to the array of objects being
 *                  passed to the page.
 */

STDMETHODIMP CPolyPropPage::SetObjects(ULONG cObjects
    , IUnknown **ppUnk)
    {
    BOOL        fRet=TRUE;

    FreeAllObjects();

    if (0!=cObjects)
        {
        UINT        i;
        HRESULT     hr;

        m_ppObj=new IPolylineControl * [(UINT)cObjects];

    for (i=0; i < cObjects; i++)
        {
            hr=ppUnk[i]->QueryInterface(IID_IPolylineControl
                , (void **)&m_ppObj[i]);

            if (FAILED(hr))
                fRet=FALSE;
        }
    }

    //If we didn't get one of our objects, fail this call.
    if (!fRet)
        return ResultFromScode(E_FAIL);

    m_cObjects=cObjects;
    return NOERROR;
    }

/*
 * CPolyPropPage::Show
 *
 * Purpose:
 *  Instructs the page to show or hide its window created in
 *  Activate.
 *
 * Parameters:
 *  nCmdShow        UINT to pass to ShowWindow.
 */

STDMETHODIMP CPolyPropPage::Show(UINT nCmdShow)
    {
    if (NULL==m_hDlg)
        ResultFromScode(E_UNEXPECTED);

    ShowWindow(m_hDlg, nCmdShow);

    //Take the focus
    if (SW_SHOWNORMAL==nCmdShow || SW_SHOW==nCmdShow)
SetFocus(m_hDlg);

    return NOERROR;
    }

/*
 * CPolyPropPage::Move
 *
 * Purpose:
 *  Instructs the property page to change its position.
 *
 * Parameters:
 *  prc             LPCRECT containing the new position.
 */

STDMETHODIMP CPolyPropPage::Move(LPCRECT prc)
    {
    SetWindowPos(m_hDlg, NULL, prc->left, prc->top
        , prc->right-prc->left, prc->bottom-prc->top, 0);

    return NOERROR;
    }

/*
 * CPolyPropPage::IsPageDirty
 *
 * Purpose:
 *  Asks the page if anything's changed in it, that is, if the
 *  property values in the page are out of sync with the objects
 *  under consideration.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR if dirty, S_FALSE if not.
 */

STDMETHODIMP CPolyPropPage::IsPageDirty(void)
    {
    return ResultFromScode(m_fDirty ? S_OK : S_FALSE);
    }

/*
 * CPolyPropPage::Apply
 *
 * Purpose:
 *  Instructs the page to send changes in its page to whatever
 *  objects it knows about through SetObjects.  This is the only
 *  time the page should change the objects' properties, and not
 *  when the value is changed on the page.
 *
 * Parameters:
 *  None
 */

STDMETHODIMP CPolyPropPage::Apply(void)
    {
    UINT        i;
    UINT        iLine, iLineNew;
    COLORREF    crNew;
    BOOL        fChanged;

    if (0==m_cObjects)
        return NOERROR;

    /*
     * The dialog's last known line selection is in m_uIDLastLine,
     * colors in m_crLastBack and m_crLastLine, so we can just
     * send these to IPolylineControl member functions.
     * We the test if the change took place to determine whether
     * we set our dirty flag.
     */

    iLine=m_uIDLastLine-ID_LINEMIN;
    fChanged=FALSE;

    for (i=0; i < m_cObjects; i++)
        {
        m_ppObj[i]->put_LineStyle(iLine);
        iLineNew=m_ppObj[i]->get_LineStyle();

        fChanged |= (iLine!=iLineNew);

        m_ppObj[i]->put_BackColor(m_crLastBack);
        crNew=m_ppObj[i]->get_BackColor();

        fChanged |= (m_crLastBack!=crNew);

        m_ppObj[i]->put_LineColor(m_crLastLine);
        crNew=m_ppObj[i]->get_LineColor();

        fChanged |= (m_crLastLine!=crNew);
        }

    m_fDirty=!fChanged;
    return NOERROR;
    }

/*
 * CPolyPropPage::Help
 *
 * Purpose:
 *  Invokes help for this property page when the user presses
 *  the Help button.  If you return NULLs for the help file
 *  in GetPageInfo, the button will be grayed.  Otherwise the
 *  page can perform its own help here.
 *
 * Parameters:
 *  pszHelpDir      LPCOLESTR identifying the default location of
 *                  the help information
 *
 * Return Value:
 *  HRESULT         NOERROR to tell the frame that we've done our
 *                  own help.  Returning an error code or S_FALSE
 *                  causes the frame to use any help information
 *                  in PROPPAGEINFO.
 */

STDMETHODIMP CPolyPropPage::Help(LPCOLESTR pszHelpDir)
    {
    /*
     * We can either provide help ourselves, or rely on the
     * information in PROPPAGEINFO.
     */
    return ResultFromScode(S_FALSE);
    }

/*
 * CPolyPropPage::TranslateAccelerator
 *
 * Purpose:
 *  Provides the page with the messages that occur in the frame.
 *  This gives the page to do whatever it wants with the message,
 *  such as handle keyboard mnemonics.
 *
 * Parameters:
 *  pMsg            LPMSG containing the keyboard message.
 */

STDMETHODIMP CPolyPropPage::TranslateAccelerator(LPMSG lpMsg)
    {
    //No keyboard interface supported here
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * PolyPropPageProc
 *
 * Purpose:
 *  Dialog procedure for the Polyline Property Page.
 */

BOOL APIENTRY PolyPropPageProc(HWND hDlg, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCPolyPropPage      pObj;
    WORD                wID;
    UINT                i;
    COLORREF            rgColors[16];
    CHOOSECOLOR         cc;

   #ifdef WIN32
    pObj=(PCPolyPropPage)(ULONG)GetProp(hDlg, g_szObj);
   #else
    pObj=(PCPolyPropPage)MAKELONG((WORD)GetProp(hDlg, g_szObjLo)
       , (WORD)GetProp(hDlg, g_szObjHi));
   #endif

    switch (iMsg)
        {
        case WM_INITDIALOG:
            pObj=(PCPolyPropPage)(ULONG)lParam;
           #ifdef WIN32
            SetProp(hDlg, g_szObj, (HANDLE)lParam);
           #else
            SetProp(hDlg, g_szObjHi, (HANDLE)HIWORD(lParam));
            SetProp(hDlg, g_szObjLo, (HANDLE)LOWORD(lParam));
           #endif

            if (NULL==pObj)
                return TRUE;

            /*
             * If we have one object then we can try to set the
             * right field in the dialog box.  Otherwise we could
             * ask for the value from all of the objects and see
             * if they all match.
             */
            if (1==pObj->m_cObjects)
                {
                UINT        iLine;

                iLine=pObj->m_ppObj[0]->get_LineStyle();

                CheckRadioButton(hDlg, ID_LINESOLID
                    , ID_LINEDASHDOTDOT, iLine+ID_LINEMIN);

                SetFocus(GetDlgItem(hDlg, iLine+ID_LINEMIN));

                pObj->m_uIDLastLine=iLine;

                pObj->m_crLastBack=pObj->m_ppObj[0]->get_BackColor();
                pObj->m_crLastLine=pObj->m_ppObj[0]->get_LineColor();
                }

            return FALSE;

        case WM_DESTROY:
           #ifdef WIN32
            RemoveProp(hDlg, g_szObj);
           #else
            RemoveProp(hDlg, g_szObjHi);
            RemoveProp(hDlg, g_szObjLo);
           #endif
            return FALSE;

        case WM_COMMAND:
            wID=LOWORD(wParam);

            switch (wID)
                {
                case ID_LINESOLID:
                case ID_LINEDASH:
                case ID_LINEDOT:
                case ID_LINEDASHDOT:
                case ID_LINEDASHDOTDOT:
                    if (NULL==pObj)
                        break;

                    //Selecting the same one doesn't dirty
                    if (pObj->m_uIDLastLine==wID)
                        break;

                    //Save the most recently selected
                    pObj->m_uIDLastLine=LOWORD(wParam);
                    pObj->m_fDirty=TRUE;

                    if (NULL!=pObj->m_pIPropertyPageSite)
                        {
                        pObj->m_pIPropertyPageSite
                            ->OnStatusChange(PROPPAGESTATUS_DIRTY);
                        }

                    break;

                case ID_COLORLINE:
                case ID_COLORBACK:
                    if (NULL==pObj)
                        break;

                    for (i=0; i<16; i++)
                        rgColors[i]=RGB(0, 0, i*16);

                    memset(&cc, 0, sizeof(CHOOSECOLOR));
                    cc.lStructSize=sizeof(CHOOSECOLOR);
                    cc.lpCustColors=rgColors;
                    cc.hwndOwner=GetParent(hDlg);
                    cc.Flags=CC_RGBINIT;
                    cc.rgbResult=(ID_COLORBACK==wID)
                        ? pObj->m_crLastBack
                        : pObj->m_crLastLine;

                    if (ChooseColor(&cc))
                        {
                        COLORREF    cr;
                        ;
                        if (ID_COLORBACK==wID)
                            {
                            cr=pObj->m_crLastBack;
                            pObj->m_crLastBack=cc.rgbResult;
                            }
                        else
                            {
                            cr=pObj->m_crLastLine;
                            pObj->m_crLastLine=cc.rgbResult;
                            }

                        pObj->m_fDirty=(cr!=cc.rgbResult);

                        if (pObj->m_fDirty
                            && NULL!=pObj->m_pIPropertyPageSite)
                            {
                            pObj->m_pIPropertyPageSite
                                ->OnStatusChange(PROPPAGESTATUS_DIRTY);
                            }
                        }
                    break;

                }
            break;
        }
    return FALSE;
    }


POLYPROP.H

/*
 * POLYPROP.H
 * Polyline Property Page, Chapter 24
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _POLYPROP_H_
#define _POLYPROP_H_

#define INC_AUTOMATION
#define INC_CONTROLS
#define INC_CLASSLIB
#define GUIDS_FROM_TYPELIB
#define CHAPTER24
#include <inole.h>

//Get IPolylineControl definitions for the object we work with
#include "..\polyline\ipoly.h"

//Dialog template
#define IDD_POLYLINEPROPS                 100

#define ID_GROUPSTYLE       199
#define ID_LINEMIN          200
#define ID_LINESOLID        200     //(ID_LINEMIN+PS_SOLID)
#define ID_LINEDASH         201     //(ID_LINEMIN+PS_DASH)
#define ID_LINEDOT          202     //(ID_LINEMIN+PS_DOT)
#define ID_LINEDASHDOT      203     //(ID_LINEMIN+PS_DASHDOT)
#define ID_LINEDASHDOTDOT   204     //(ID_LINEMIN+PS_DASHDOTDOT)

#define ID_GROUPCOLORS      300
#define ID_COLORBACK        301
#define ID_COLORLINE        302

//This class factory object creates other objects

class CPolyPPFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;

    public:
        CPolyPPFactory(void);
        ~CPolyPPFactory(void);

        //IUnknown members
        STDMETHODIMP         QueryInterface(REFIID, PPVOID);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassPPFactory members
        STDMETHODIMP     CreateInstance(LPUNKNOWN, REFIID, PPVOID);
        STDMETHODIMP     LockServer(BOOL);
    };

typedef CPolyPPFactory *PCPolyPPFactory;

//Our property page class

BOOL APIENTRY PolyPropPageProc(HWND, UINT, WPARAM, LPARAM);

class CPolyPropPage : public IPropertyPage
    {
    friend BOOL APIENTRY PolyPropPageProc(HWND, UINT, WPARAM, LPARAM);

    protected:
        ULONG           m_cRef;         //Reference count
        HINSTANCE       m_hInst;        //Module instance
        HWND            m_hDlg;         //Dialog handle

        ULONG           m_cx;           //Dialog size
        ULONG           m_cy;
        UINT            m_cObjects;     //Number of objects
        UINT            m_uIDLastLine;  //Last selected in page
        COLORREF        m_crLastBack;   //Last background color
        COLORREF        m_crLastLine;   //Last line color
        BOOL            m_fDirty;       //Page dirty?

        IPolylineControl **m_ppObj;

        IPropertyPageSite *m_pIPropertyPageSite;    //Frame's

    public:
        CPolyPropPage(HINSTANCE);
        ~CPolyPropPage(void);

        BOOL Init(void);
        void FreeAllObjects(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

    STDMETHODIMP SetPageSite(LPPROPERTYPAGESITE);
    STDMETHODIMP Activate(HWND, LPCRECT, BOOL);
    STDMETHODIMP Deactivate(void);
    STDMETHODIMP GetPageInfo(LPPROPPAGEINFO);
    STDMETHODIMP SetObjects(ULONG, LPUNKNOWN *);
    STDMETHODIMP Show(UINT);
    STDMETHODIMP Move(LPCRECT);
    STDMETHODIMP IsPageDirty(void);
    STDMETHODIMP Apply(void);
    STDMETHODIMP Help(LPCOLESTR);
    STDMETHODIMP TranslateAccelerator(LPMSG);
    };

typedef CPolyPropPage *PCPolyPropPage;

#endif //_POLYPROP_H_


README.TXT

There are no samples for Chapter 25.


CCLIENT.CPP

/*
 * CCLIENT.CPP
 * Sample Code Class Libraries
 *
 * Implementation of the CClient class that handles an SDI or MDI
 * client area window.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include <windows.h>
#include "classlib.h"
#include <dbgout.h>

/*
 * CClient::CClient
 * CClient::~CClient
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 */

CClient::CClient(HINSTANCE hInst)
    : CWindow(hInst)
    {
    m_pFR=NULL;
    m_cDoc=0;
    m_pDocLast=NULL;

    return;
    }

CClient::~CClient(void)
    {
    return;
    }

/*
 * CClient::FInit
 *
 * Purpose:
 *  Creates a client area window sensitive to SDI or MDI
 *  (compile-time).
 *
 * Parameters:
 *  hMenuWindow     HWND of the Window menu on the frame.
 *  pRect           LPRECT containing the desired window rectangle.
 *  pFR             PCFrame to which this client belongs.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CClient::FInit(HMENU hMenuWindow, LPRECT pRect, PCFrame pFR)
    {
    HWND                hWndFrame;
   #ifdef MDI
    CLIENTCREATESTRUCT  ccs;
   #endif

    m_pFR=pFR;

    hWndFrame=m_pFR->Window();

   #ifdef MDI

    ccs.hWindowMenu =hMenuWindow;
    ccs.idFirstChild=ID_MDICHILDMIN;

    //Create the MDI client filling the client area
    m_hWnd=CreateWindow(TEXT("mdiclient"), TEXT("mdiclient")
        , MDIS_ALLCHILDSTYLES | WS_CHILD | WS_CLIPCHILDREN
        | WS_VISIBLE | WS_CLIPSIBLINGS
        , pRect->left, pRect->top, pRect->right-pRect->left
        , pRect->bottom-pRect->top, hWndFrame, NULL, m_hInst
        , &ccs);

   #else

    //SDI case, create out own SDI Client window
    m_hWnd=CreateWindow(SZCLASSSDICLIENT, SZCLASSSDICLIENT
        , WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | WS_CLIPSIBLINGS
        , pRect->left, pRect->top,  pRect->right-pRect->left
        , pRect->bottom-pRect->top, hWndFrame, NULL, m_hInst, 0L);

   #endif

    return (NULL!=m_hWnd);
    }

/*
 * CClient::CreateDoc (Internal)
 * CClient::CreateCDocument (Virtual)
 *
 * Purpose:
 *  Constructs a new document.  This function is overridable so an
 *  app can use the default CClient but create a derived document
 *  class.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CClient::CreateDoc(void)
    {
    PCDocument  pDoc;

    pDoc=CreateCDocument();
    m_pDocLast=pDoc;

    return pDoc;
    }

PCDocument CClient::CreateCDocument(void)
    {
    return new CDocument(m_hInst, m_pFR);
    }

/*
 * CClient::TranslateAccelerator
 *
 * Purpose:
 *  Provides an isolated system accelerator translation as necessary
 *  for the client window.  In MDI this calls TranslateMDISysAccel
 *  but is a NOP in SDI.
 *
 * Parameters:
 *  pMsg            LPMSG to the message from GetMessage
 *
 * Return Value:
 *  BOOL            TRUE if the accelerator was translated and
 *                  processed, FALSE otherwise.
 */

BOOL CClient::TranslateAccelerator(LPMSG pMsg)
    {
   #ifdef MDI
    return TranslateMDISysAccel(m_hWnd, pMsg);
   #else
    return FALSE;
   #endif
    }

/*
 * CClient::DefFrameProc
 *
 * Purpose:
 *  Encapsulates which default message procedure to call for a frame
 *  window: SDI (DefWindowProc) or MDI (DefFrameProc).
 *
 * Parameters:
 *  hWnd            HWND of the frame
 *  iMsg            UINT of the message
 *  wParam          WPARAM of the message
 *  lParam          LPARAM of the message
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CClient::DefaultFrameProc(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
   #ifdef MDI
    return (DefFrameProc(hWnd, m_hWnd, iMsg, wParam, lParam));
   #else
    return (DefWindowProc(hWnd, iMsg, wParam, lParam));
   #endif
    }

/*
 * CClient::OnWindowCommand
 *
 * Purpose:
 *  Handles Window menu commands for MDI situations.  This is a NOP
 *  in SDI.
 *
 * Parameters:
 *  uCommand        UINT command to execute:
 *                      WM_MDICASCADE
 *                      WM_MDITILE
 *                      WM_MDIICONARRANGE
 *
 *  uOption         UINT optional parameter for WM_MDITILE, either
 *                  MDITILE_HORIZONTAL or MDITILE_VERTICAL.
 *
 * Return Value:
 *  None
 */

void CClient::OnWindowCommand(UINT uCommand, UINT uOption)
    {
    SendMessage(m_hWnd, uCommand, (WPARAM)uOption, 0L);
    return;
    }

/*
 * CClient::OnSize
 *
 * Purpose:
 *  Handles resizing the client window when the frame is resized.
 *
 * Parameters:
 *  x, y            UINT new location of the window.
 *  cx, cy          UINT new dimensions of the window.
 *
 * Return Value:
 *  None
 */

void CClient::OnSize(UINT x, UINT y, UINT cx, UINT cy)
    {
    SetWindowPos(m_hWnd, NULL, x, y, cx, cy
        , SWP_NOZORDER | SWP_NOACTIVATE);
    return;
    }

/*
 * CClient::NewDocument
 *
 * Purpose:
 *  Creates a new blank document in the client space.  See
 *  CloseDocument for the opposite effect.
 *
 * Parameters:
 *  fVisible        BOOL indicating if the document is to be visible
 *                  or not.
 *  pAdv            PCDocumentAdviseSink to set with the new document
 *                  for notifications.  Can be NULL.
 *
 * Return Value:
 *  PCDocument      Pointer to the new document object.
 */

PCDocument CClient::NewDocument(BOOL fVisible
    , PCDocumentAdviseSink pAdv)
    {
    MDICREATESTRUCT mcs;
    HWND            hWndDoc;
    PCDocument      pDoc;
    DOCUMENTINIT    di;
    BOOL            fCreate=TRUE;
   #ifdef MDI
    DWORD           dw;
   #endif

   #ifdef MDI
    //In MDI we create a new CDocument
    pDoc=CreateDoc();  //This could create a derived class...
   #else
    //In SDI we close the one we have and create a new one.
    pDoc=ActiveDocument();

    if (NULL!=pDoc)
        CloseDocument(pDoc);

    pDoc=CreateDoc();
   #endif

    if (fCreate)
        {
        /*
         * We implement this by having the client window actually
         * create the windows instead of using the CDocument
         * initializer.  Reason being is that we ask the client to
         * actually create the window using WM_MDICREATE (which works
         * for our SDI client as well. Since we need to conditionally
         * compile for MDI or SDI here, we create a window before
         * calling the document constructor.
         */

        mcs.szTitle=TEXT("");
        mcs.szClass=SZCLASSDOCUMENT;
        mcs.hOwner =m_hInst;
        mcs.lParam =(LPARAM)pDoc;

        mcs.x =CW_USEDEFAULT;
        mcs.cx=CW_USEDEFAULT;
        mcs.y =CW_USEDEFAULT;
        mcs.cy=CW_USEDEFAULT;

        /*
         * Set the style of the window, controlling visiblity.
         * WS_CLIPCHILDREN is important to prevent unnecessary
         * flashing of document contents that we'll usually fill
         * with some editor window.
         */
       #ifdef MDI
        mcs.style=WS_CHILD | WS_SYSMENU | WS_CAPTION
            | WS_CLIPSIBLINGS| WS_THICKFRAME | WS_MINIMIZEBOX
            | WS_MAXIMIZEBOX| WS_CLIPCHILDREN
            | ((fVisible) ? WS_VISIBLE : 0L);

        //If the current document is maxmized, maximize this one
       #ifdef WIN32
        dw=GetWindowLong(m_hWnd, GWL_STYLE);
        mcs.style |= (dw & WS_MAXIMIZE);
       #else
        dw=SendMessage(m_hWnd, WM_MDIGETACTIVE, 0, 0L);

        if (HIWORD(dw))
            mcs.style |= WS_MAXIMIZE;
       #endif //WIN32

       #else
        mcs.style=WS_CHILD | WS_CLIPCHILDREN
            | ((fVisible) ? WS_VISIBLE : 0L);
       #endif //MDI

        //Tell the Client window to create the child
        hWndDoc=(HWND)(UINT)SendMessage(m_hWnd, WM_MDICREATE, 0
            , (LONG)&mcs);

        di.idsMin=IDS_STANDARDDOCMIN;
        di.idsMax=IDS_STANDARDDOCMAX;
        di.hWndDoc=hWndDoc;
        di.pAdv=pAdv;

        if (!pDoc->FInit(&di))
            {
            SendMessage(m_hWnd, WM_MDIDESTROY, (WPARAM)hWndDoc, 0L);
            delete pDoc;
            return NULL;
            }
        }

    m_cDoc++;

    //Update menus and gizmos for the new document if visible
    if (fVisible)
        m_pFR->UpdateGizmos();

    return pDoc;
    }

/*
 * CClient::ActiveDocument
 *
 * Purpose:
 *  Returns the active document window (encapsulates WM_MDIGETACTIVE)
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCDocument      Pointer to the active document object.
 */

PCDocument CClient::ActiveDocument(void)
    {
    PCDocument  pDoc=NULL;
    HWND        hWnd;

    hWnd=(HWND)(UINT)SendMessage(m_hWnd, WM_MDIGETACTIVE, 0, 0L);

    if (NULL!=hWnd)
        pDoc=(PCDocument)SendMessage(hWnd, DOCM_PDOCUMENT, 0, 0L);
    else
        pDoc=m_pDocLast;

    return pDoc;
    }

/*
 * CClient::ShowDocument
 *
 * Purpose:
 *  Shows or hides a document.
 *
 * Parameters:
 *  pDoc            PCDocument to show or hide.
 *  fShow           BOOL indicating whether to show or hide the
 *                  document.
 *
 * Return Value:
 *  BOOL            Previous shown state of the document.
 */

BOOL CClient::ShowDocument(PCDocument pDoc, BOOL fShow)
    {
    BOOL        fRet;

    if (NULL==pDoc)
        return FALSE;

    fRet=IsWindowVisible(pDoc->Window());

    ShowWindow(pDoc->Window(), fShow ? SW_SHOW : SW_HIDE);

    MDIREFRESHMENU(m_hWnd);     //Macro in book1632.h
    DrawMenuBar(m_pFR->Window());

    SendMessage(m_hWnd, WM_MDIACTIVATE, (WPARAM)pDoc->Window(), 0L);

    //Update gizmos if we're *changing* a document's visibility
    if (fShow != fRet)
        m_pFR->UpdateGizmos();

    return fRet;
    }

/*
 * CClient::SDIVerify
 *
 * Purpose:
 *  In MDI, this is a NOP, but in SDI checks if the user has dirtied
 *  and wants to save the current document before blasting it away.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the operation calling us can proceed,
 *                  FALSE to abort the operation.
 */

BOOL CClient::SDIVerify(void)
    {
   #ifdef MDI
    return TRUE;
   #else
    PCDocument      pDoc;

    pDoc=ActiveDocument();

    /*
     * In SDI, we'll erase the current, so verify if we have one
     * before continuing.  If we don't have any document, then we
     * don't have a problem.
     */

    if (NULL==pDoc)
        return TRUE;

    return FCleanVerify(pDoc);
   #endif
    }

/*
 * CClient::CloseDocument
 *
 * Purpose:
 *  Closes a document created with NewDocument.
 *
 * Parameters:
 *  pDoc            PCDocument of the document to close.
 *
 * Return Value:
 *  UINT            New count of open documents.
 */

UINT CClient::CloseDocument(PCDocument pDoc)
    {
    HWND        hWndT;

    if (NULL==pDoc)
        return m_cDoc;

    /*
     * Again, since the client window controlled document creation
     * we destroy the document window here instead of asking the
     * document to do it for us.  Once we're rid of the window then
     * we can use the destructor.
     */

    hWndT=pDoc->Window();

    //Don't delete unowned windows.
    if (GetParent(hWndT)!=m_hWnd)
        return m_cDoc;

    m_cDoc--;

    //Update window text.  Maxed MDI windows handled automatically.
   #ifdef MDI
    m_pFR->WindowTitleSet(NULL, TRUE);
   #else
    m_pFR->WindowTitleSet(NULL, FALSE);
   #endif

    if (m_pDocLast==pDoc)
        m_pDocLast=NULL;

    //Let the document clean up first, then we can nuke the window.
    SendMessage(m_hWnd, WM_MDIDESTROY, (WPARAM)hWndT, 0L);
    delete pDoc;

    return m_cDoc;
    }

/*
 * CClient::QueryCloseAllDocuments
 *
 * Purpose:
 *  Ask every document window we have in us if we can close them
 *  down.  We use this when exiting the entire application.
 *
 * Parameters:
 *  fClose          BOOL indicating if we should query close on the
 *                  documents or acutally close them.
 *
 * Return Value:
 *  BOOL            TRUE if we can close, FALSE otherwise.
 */

BOOL CClient::QueryCloseAllDocuments(BOOL fClose)
    {
    PCDocument   pDoc;
    HWND         hWndT;
    HWND         hPrevClose=NULL;

    hWndT=GetWindow(m_hWnd, GW_CHILD);

    while (NULL!=hWndT)
        {
        //Cache these: hWndT might be destroyed in closing hPrevClose
        HWND    hWndOwner=GetWindow(hWndT, GW_OWNER);
        HWND    hWndNext =GetWindow(hWndT, GW_HWNDNEXT);

        if (NULL!=hPrevClose)
            {
            pDoc=(PCDocument)SendMessage(hPrevClose, DOCM_PDOCUMENT
                , 0, 0L);
            CloseDocument(pDoc);
            hPrevClose=NULL;
            }

        //Skip icon title windows when saving hPrevClose.
        if (NULL==hWndOwner)
            {
            pDoc=(PCDocument)SendMessage(hWndT, DOCM_PDOCUMENT
                , 0, 0L);

            if (fClose)
                {
                if (!FCleanVerify(pDoc))
                    return FALSE;

                //Delay closing this.
                hPrevClose=hWndT;
                }
            else
                {
                if (0==SendMessage(hWndT, WM_QUERYENDSESSION, 0, 0L))
                    return FALSE;
                }
            }

        hWndT=hWndNext;
        }

    //Close the last window as necessary.
    if (fClose && IsWindow(hPrevClose))
        {
        pDoc=(PCDocument)SendMessage(hPrevClose, DOCM_PDOCUMENT
            , 0, 0L);
        CloseDocument(pDoc);
        hPrevClose=NULL;
        }

    return TRUE;
    }

/*
 * CClient::FCleanVerify
 *
 * Purpose:
 *  Checks if the document under scrutiny is dirty or not.  If so,
 *  then we ask the user if they want to save it.  If not, then we
 *  just return TRUE.
 *
 * Parameters:
 *  pDoc            PCDocument under consideration.
 *
 * Return Value:
 *  BOOL            TRUE if the document is clean, saved, or the user
 *                  doesn't want to save it.  FALSE if the user
 *                  presses Cancel or the document was not saved.
 */

BOOL CClient::FCleanVerify(PCDocument pDoc)
    {
    TCHAR           szFile[CCHPATHMAX];
    LPTSTR          psz=szFile;

    if (NULL==pDoc)
        return TRUE;

    //Nothing to do if we're clean.
    if (!pDoc->FDirtyGet())
        return TRUE;

    //Get the filename and send to the frame for asking the question.
    *psz=0;
    pDoc->FilenameGet(psz, CCHPATHMAX);

    return m_pFR->FAskAndSave(psz);
    }

/*
 * SDIClientWndProc
 *
 * Purpose:
 *  SDI client window class that will create one document for us,
 *  emulating the functions of an MDI client but with only one doc.
 */

LRESULT APIENTRY SDIClientWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    HWND                hWndDoc;
    LPMDICREATESTRUCT   pMCS;
    RECT                rc;

    hWndDoc=(HWND)GetWindowLong(hWnd, CLIENTWL_HWNDDOC);

    switch (iMsg)
        {
        case WM_CREATE:
            SetWindowLong(hWnd, CLIENTWL_HWNDDOC, 0L);
            break;

        case WM_SIZE:
            if (NULL!=hWndDoc)
                {
                //Change the document window to match
                SetWindowPos(hWndDoc, NULL, 0,  0, LOWORD(lParam)
                    , HIWORD(lParam), SWP_NOMOVE | SWP_NOZORDER
                    | SWP_NOACTIVATE);
                }
            break;

        case WM_MDICREATE:
            pMCS=(LPMDICREATESTRUCT)lParam;

            //We only ate one *visible* document in SDI cases.
            if (NULL!=hWndDoc && (WS_VISIBLE & pMCS->style))
                return (LONG)(UINT)hWndDoc;

            /*
             * For our one visible window, we set this as the active
             * window.  For hidden windows, we return their window
             * handle but don't change the 'active' window we store.
             *
             * Note that we force SDI documents to fill the client
             * area.
             */

            GetClientRect(hWnd, &rc);

            hWndDoc=CreateWindowEx(WS_EX_NOPARENTNOTIFY
                , pMCS->szClass, pMCS->szTitle, pMCS->style
                , rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top
                , hWnd, (HMENU)ID_DOCUMENT, (HINSTANCE)pMCS->hOwner
                , pMCS);

            if (WS_VISIBLE & pMCS->style)
                {
                ShowWindow(hWndDoc, SW_SHOW);
                SetWindowLong(hWnd, CLIENTWL_HWNDDOC
                    , (LONG)(UINT)hWndDoc);
                }

            return (LONG)(UINT)hWndDoc;

        case WM_MDIACTIVATE:
            /*
             * Make the new window the active one.  The NEWMDIACTIVE
             * macro is wParam in Win16, lParam in Win32.
             */
            SetWindowLong(hWnd, CLIENTWL_HWNDDOC,(LONG)NEWMDIACTIVE);
            break;

        case WM_MDIGETACTIVE:
            return (LONG)(UINT)hWndDoc;

        case WM_MDIDESTROY:
            //The only windows we should destroy are children of us.
            if (GetParent((HWND)wParam)==hWnd)
                {
                DestroyWindow((HWND)wParam);

                /*
                 * If this is the visible window, clear out the
                 * window word.
                 */
                if ((HWND)wParam==hWndDoc)
                    SetWindowLong(hWnd, CLIENTWL_HWNDDOC, 0L);
                }
            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }


CDOCUMNT.CPP

/*
 * CDOCUMNT.CPP
 * Sample Code Class Libraries
 *
 * Implementation of the CDocument class.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include <windows.h>
#include <string.h>
#include "classlib.h"

/*
 * CDocument::CDocument
 * CDocument::~CDocument
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application.
 *  pFR             PCFrame, a back pointer to the frame window.
 */

CDocument::CDocument(HINSTANCE hInst, PCFrame pFR)
    : CWindow(hInst)
    {
    m_pFR=pFR;
    m_pST=NULL;
    m_pAdv=NULL;
    return;
    }

CDocument::~CDocument(void)
    {
    if (NULL!=m_pST)
        delete m_pST;

    return;
    }

/*
 * CDocument::FInit
 *
 * Purpose:
 *  Initializes an already created document window.  The client
 *  actually creates the window then passes that here for further
 *  initialization.
 *
 * Parameters:
 *  pDI             PDOCUMENTINIT containing initialization params
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CDocument::FInit(PDOCUMENTINIT pDI)
    {
    if (NULL==pDI)
        return FALSE;

    if (NULL==pDI->hWndDoc)
        return FALSE;

    //Create our stringtable
    m_pST=new CStringTable(m_hInst);

    if (!m_pST->FInit(pDI->idsMin, pDI->idsMax))
        return FALSE;

    m_hWnd=pDI->hWndDoc;
    m_pAdv=pDI->pAdv;
    m_cf  =RegisterClipboardFormat(PSZ(IDS_CLIPBOARDFORMAT));

    m_fDirty=FALSE;
    m_fNoDirty=FALSE;
    m_fNoSize=FALSE;

    m_fFileKnown=FALSE;
    m_szFile[0]=0;

    return TRUE;
    }

/*
 * CDocument::FMessageHook
 *
 * Purpose:
 *  Provides a derivation of the base CDocument class to hook all
 *  messages to the window procedure for special processing,
 *  including WM_COMMAND since that is much less common for a
 *  document than for a frame (see CFrame::OnCommand).
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return value
 *                  for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing, FALSE
 *                  otherwise.
 */

BOOL CDocument::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    *pLRes=0;
    return FALSE;
    }

/*
 * CDocument::FDirtySet
 *
 * Purpose:
 *  Sets or clears the document 'dirty' flag returning the previous
 *  state of that same flag.
 *
 * Parameters:
 *  fDirty          BOOL indicating the new contents of the dirty
 *                  flag.
 *
 * Return Value:
 *  BOOL            Previous value of the dirty flag.
 */

BOOL CDocument::FDirtySet(BOOL fDirty)
    {
    BOOL        fPrevious;

    /*
     * If we are a hidden window, then there's nothing that could
     * make us dirty since there cannot be any user interaction here.
     */
    if (!IsWindowVisible(m_hWnd))
        return m_fDirty;

    //Ignore the initial WM_SIZE on creation.
    if (m_fNoDirty)
        return m_fDirty;

    fPrevious=m_fDirty;
    m_fDirty=fDirty;

    return fPrevious;
    }

/*
 * CDocument::FDirtyGet
 *
 * Purpose:
 *  Returns the current dirty status of the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if the file is clean, FALSE otherwise.
 */

BOOL CDocument::FDirtyGet()
    {
    return m_fDirty;
    }

/*
 * CDocument::Clear
 *
 * Purpose:
 *  Sets all contents in the document back to defaults with no
 *  filename.
 *
 * Paramters:
 *  None
 *
 * Return Value:
 *  None
 */

void CDocument::Clear()
    {
    FDirtySet(FALSE);
    Rename(NULL);
    return;
    }

/*
 * CDocument::ULoad
 *
 * Purpose:
 *  Function that derived classes override.  Does nothing in base
 *  class.
 *
 * Parameters:
 *  fChangeFile     BOOL indicating if we're to update the window
 *                  title and the filename from using this file.
 *  pszFile         LPTSTR to the filename to load.  If NULL then this
 *                  is a new, untitled file, so perform any
 *                  initialization for such a case.
 *
 * Return Value:
 *  UINT            An error value from DOCERR_.  Always
 *                  DOCERR_NOFILE here.
 */

UINT CDocument::ULoad(BOOL fChangeFile, LPTSTR pszFile)
    {
    Rename(NULL);
    return DOCERR_NOFILE;
    }

/*
 * CDocument::USave
 *
 * Purpose:
 *  Function that derived classes override.  Does nothing in base
 *  class.
 *
 * Parameters:
 *  uType           UINT type of file to save (from Save As dialog).
 *  pszFile         LPTSTR providing the name under which we should
 *                  save.  If NULL, then use the current name.  If
 *                  non-NULL, then call Rename if save is successful
 *                  and the name has changed.
 * Return Value:
 *  UINT            An error value from DOCERR_.  Always
 *                  DOCERR_NOFILE here.
 */

UINT CDocument::USave(UINT uType, LPTSTR pszFile)
    {
    return DOCERR_NOFILE;
    }

/*
 * CDocument::ErrorMessage
 *
 * Purpose:
 *  Displays an error message to the user appropriate for a given
 *  error code.  If the code is DOCERR_NONE then this is a NOP.
 *
 * Parameters:
 *  uErr            UINT identifying the error code.
 *
 * Return Value:
 *  None
 */

void CDocument::ErrorMessage(UINT uErr)
    {
    LPTSTR      psz;

    switch (uErr)
        {
        case DOCERR_NONE:
            psz=NULL;
            break;

        case DOCERR_NOFILE:
            psz=PSZ(IDS_FILEDOESNOTEXIST);
            break;

        case DOCERR_COULDNOTOPEN:
            psz=PSZ(IDS_FILEOPENERROR);
            break;

        case DOCERR_READFAILURE:
            psz=PSZ(IDS_FILELOADERROR);
            break;

        case DOCERR_UNSUPPORTEDVERSION:
            psz=PSZ(IDS_VERSIONMISMATCH);
            break;

        case DOCERR_WRITEFAILURE:
            psz=PSZ(IDS_FILESAVEERROR);
            break;

        case DOCERR_CANCELLED:
            //No message on this.
            return;

        default:
            psz=PSZ(IDS_UNKNOWNERROR);
            break;
        }

    if (NULL!=psz)
        MessageBox(m_hWnd, psz, PSZ(IDS_DOCUMENTCAPTION), MB_OK);

    return;
    }

/*
 * CDocument::FClip
 *
 * Purpose:
 *  Places document data on the clipboard, optionally implementing
 *  Cut by deleting the data in the current window after rendering.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window.
 *  fCut            BOOL indicating cut (TRUE) or copy (FALSE).
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CDocument::FClip(HWND hWndFrame, BOOL fCut)
    {
    return FALSE;
    }

/*
 * CDocument::RenderFormat
 *
 * Purpose:
 *  Returns a global memory handle containing a specific clipboard
 *  format.
 *
 * Parameters:
 *  cf              UINT format to render
 *
 * Return Value:
 *  HGLOBAL         Memory handle of the rendering.
 */

HGLOBAL CDocument::RenderFormat(UINT cf)
    {
    return NULL;
    }

/*
 * CDocument::FQueryPaste
 *
 * Purpose:
 *  Determines if we can paste data from the clipboard.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if data is available, FALSE otherwise.
 */

BOOL CDocument::FQueryPaste(void)
    {
    return IsClipboardFormatAvailable(m_cf);
    }

/*
 * CDocument::FPaste
 *
 * Purpose:
 *  Retrieves the private data format from the clipboard and sets it
 *  as the current data.
 *
 *  Note that if this function is called, then the clipboard format
 *  is available because the Paste menu item is only enabled if the
 *  format is present.
 *
 * Parameters:
 *  hWndFrame       HWND of the main window
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL CDocument::FPaste(HWND hWndFrame)
    {
    return FALSE;
    }

/*
 * CDocument::Undo
 *
 * Purpose:
 *  Reverses a previous action.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CDocument::Undo(void)
    {
    return;
    }

/*
 * CDocument::FQuerySave
 *
 * Purpose:
 *  Returns whether or not a call to FSave will work.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if we have a known file, FALSE otherwise.
 */

BOOL CDocument::FQuerySave(void)
    {
    return m_fFileKnown;
    }

/*
 * CDocument::Rename
 *
 * Purpose:
 *  Changes the name of the current file we're dealing with but
 *  does no disk action.  FSave should be used after this
 *  call to implement a Save As operation.
 *
 * Parameters:
 *  pszFile         LPTSTR to the filename to save ourselves under.
 *
 * Return Value:
 *  None
 */

void CDocument::Rename(LPTSTR pszFile)
    {
    //Clear out the filename on pszFile==NULL.
    if (NULL==pszFile)
        {
        m_szFile[0]=0;
        m_fFileKnown=FALSE;

        //Tell the associate window to change captions.
        if (NULL!=m_pAdv)
            m_pAdv->OnCaptionChange(this);
        }

    //First, check if anything changed
    if (NULL!=pszFile && 0!=lstrcmpi(m_szFile, pszFile))
        {
        /*
         * Copy the new filename to the document structure and inform
         * associate
         */
        lstrcpyn(m_szFile, pszFile, CCHPATHMAX);

        if (NULL!=m_pAdv)
            m_pAdv->OnCaptionChange(this);

        m_fFileKnown=TRUE;
        }

    return;
    }

/*
 * CDocument::FilenameGet
 *
 * Purpose:
 *  Retrieves the current filename used in the document.
 *
 * Parameters:
 *  psz             LPTSTR in which to store the filename.
 *  cchMax          UINT maximum number of characters to copy.
 *
 * Return Value:
 *  UINT            Number of characters copied.
 */

UINT CDocument::FilenameGet(LPTSTR psz, UINT cchMax)
    {
    UINT        uRet=0;

    if (NULL!=psz)
        {
        lstrcpyn(psz, m_szFile, cchMax);
        uRet=lstrlen(m_szFile);
        }

    return uRet;
    }

/*
 * CDocument::FrameGet
 *
 * Purpose:
 *  Returns the CFrame pointer of the application frame.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCFrame         Pointer to the frame object.
 */

PCFrame CDocument::FrameGet(void)
    {
    return m_pFR;
    }


CFRAME.CPP

/*
 * CFRAME.CPP
 * Sample Code Class Libraries
 *
 * Generic CFrame class that manages either SDI or MDI clients as
 * well as typical File, Edit, Window, and Help commands.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include <windows.h>
#include <memory.h>

extern "C"
    {
    #include <commdlg.h>
    }

#include "classlib.h"

/*
 * CFrame::CFrame
 * CFrame::~CFrame
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE from WinMain
 *  hInstPrev       HINSTANCE from WinMain
 *  pszCmdLine      LPSTR from WinMain
 *  nCmdShow        int from WinMain
 */

CFrame::CFrame(HINSTANCE hInst, HINSTANCE hInstPrev
    , LPSTR pszCmdLine, int nCmdShow)
    : CWindow(hInst)
    {
    m_hInstPrev =hInstPrev;
    m_nCmdShow  =nCmdShow;

   #ifdef WIN32
    //This gives us the Unicode version if necessary
    m_pszCmdLine=GetCommandLine();
   #else
    m_pszCmdLine=pszCmdLine;
   #endif

    m_fLastEnable=(BOOL)-1; //Uninitialized
    m_fLastPaste =(BOOL)-1; //Uninitialized

    m_phMenu=NULL;
    m_hBmp  =NULL;
    m_pST   =NULL;

    m_pGB   =NULL;
    m_pSS   =NULL;
    m_pCL   =NULL;
    m_pAdv  =NULL;
    return;
    }

CFrame::~CFrame(void)
    {
    m_fClosing=TRUE;

    if (NULL!=m_pAdv)
        delete m_pAdv;

    //Accelerators freed automatically.

    //Free the GizmoBar bitmaps
    if (NULL!=m_hBmp)
        DeleteObject(m_hBmp);

    if (NULL!=m_pCL)
        delete m_pCL;

    if (NULL!=m_pSS)
        delete m_pSS;

    if (NULL!=m_pGB)
        delete m_pGB;

    //Free the menu handle array
    if (NULL!=m_phMenu)
        delete []((UINT *)m_phMenu);

    //Free the stringtable.
    if (NULL!=m_pST)
        delete m_pST;

    m_fClosing=FALSE;
    return;
    }

/*
 * CFrame::FInit
 *
 * Purpose:
 *  Initializer for a CFrame object containing anything prone to
 *  failure.
 *
 * Parameters:
 *  pFI             PFRAMEINIT containing initialization parameters.
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded, FALSE
 *                  otherwise. If FALSE is returned, the caller must
 *                  guarantee that the destructor is called promptly
 *                  to insure cleanup.
 */

BOOL CFrame::FInit(PFRAMEINIT pFI)
    {
    RECT                rc;
    HMENU               hMenu;
    UINT                uTemp;
    TOOLDISPLAYDATA     tdd;

    m_fInit=TRUE;

    //1.  Create our stringtable
    m_pST=new CStringTable(m_hInst);

    if (!m_pST->FInit(pFI->idsMin, pFI->idsMax))
        return FALSE;

    /*
     * 2.  Register the classes we need for this application.
     *     We have our main (frame) window, document windows (for
     *     either MDI or SDI, and Polyline windows which are the
     *     editing controls.  This separate virtual function allows
     *     applications to add additional classes.
     */
    if (NULL==m_hInstPrev)
        {
        if (!FRegisterAllClasses())
            return FALSE;
        }

    /*
     * 3.  Create the main window, the gizmobar, the StatStrip,
     *     and a client that owns documents.
     */
    m_pCL=NULL;

    m_hWnd=CreateWindow(SZCLASSFRAME, PSZ(IDS_CAPTION)
        , WS_MINIMIZEBOX | WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN
        , pFI->x, pFI->y, pFI->cx, pFI->cy
        , NULL, NULL, m_hInst, this);

    if (NULL==m_hWnd)
        return FALSE;

    GetClientRect(m_hWnd, &rc);

    UIToolConfigureForDisplay(&tdd);
    m_dxB=tdd.cxButton;
    m_dyB=tdd.cyButton;
    m_cyBar=tdd.cyBar;

    m_pGB=new CGizmoBar(m_hInst);

    if (!m_pGB->FInit(m_hWnd, ID_GIZMOBAR, m_cyBar))
        return FALSE;

    m_pSS=new CStatStrip(m_hInst);

    if (!m_pSS->FInit(m_hWnd, ID_STATSTRIP, CYSTATSTRIP))
        return FALSE;

    //Initialize the StatStrip for automated WM_MENUSELECT processing
    if (!m_pSS->MessageMap(m_hWnd, m_hInst, IDR_STATMESSAGEMAP
        , pFI->idsStatMin, pFI->idsStatMax, CCHMESSAGEMAX
        , pFI->idStatMenuMin, pFI->idStatMenuMax, ID_MESSAGEREADY
        , ID_MESSAGEEMPTY, ID_MENUSYS))
        return FALSE;

    rc.top+=m_cyBar;

    m_pCL=CreateCClient();

    /*
     * 4.  Allocate space for the menu handle array and store the
     *     popup handles.  Get the menu handle of the Window menu
     *     specifically for later processing.
     */

    hMenu=GetMenu(m_hWnd);
    m_phMenu=new HMENU[pFI->cMenus];

    for (uTemp=0; uTemp < pFI->cMenus; uTemp++)
        m_phMenu[uTemp]=GetSubMenu(hMenu, uTemp);

   #ifdef MDI
    //Save this for UpdateMenus.  Stays NULL in SDI
    m_hMenuWindow=GetSubMenu(hMenu, pFI->iPosWindowMenu);
   #endif

    if (!m_pCL->FInit(m_hMenuWindow, &rc, this))
        return FALSE;

    /*
     * 5.  Initialize fancy things like the gizmobar.  If a derived
     *     class wants more gizmo images, they can copy the first
     *     two in the standard image set and this code will still
     *     load it.  This code just won't reference it.
     */

    m_hBmp=LoadBitmap(m_hInst, MAKEINTRESOURCE(tdd.uIDImages));

    if (NULL==m_hBmp)
        return FALSE;

    //Create all the gizmos on the gizmobar
    CreateGizmos();
    UpdateGizmos();

    m_hAccel=LoadAccelerators(m_hInst
        , MAKEINTRESOURCE(IDR_ACCELERATORS));

    if (NULL==m_hAccel)
        return FALSE;

    /*
     * 6.  Before we possibly create any new documents, create an
     *     AdviseSink for document notifications.
     */

    m_pAdv=new CDocumentAdviseSink(this);

    /*
     * 7.  In the default implementation FPreShowInit does not do
     *     anything, but is called here to allow derivations to
     *     hook the function and modify m_nCmdShow before we
     *     call ShowWindow.  This is one such place where OLE affects
     *     a derivation, because servers will change m_nCmdShow to
     *     SW_HIDE if started with -Embedding.
     */
    if (!FPreShowInit())
        return FALSE;

    /*
     * 8.  Handle window visibility appropriately after giving
     *     FPreShowInit a chance to modify it.
     */
    ShowWindow(m_hWnd, m_nCmdShow);
    UpdateWindow(m_hWnd);

    /*
     * 9.  Parse the command line and take appropriate action. Again,
     *     this virtual function is here for apps to override.
     */
    ParseCommandLine();

    m_fInit=FALSE;
    return TRUE;
    }

/*
 * CFrame::CreateCClient
 *
 * Purpose:
 *  Creates a CClient object for use in this frame.  This function
 *  is overrided by derived classes to create a different type of
 *  CClient.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  PCClient         Pointer to the new CClient object.
 */

PCClient CFrame::CreateCClient()
    {
    return new CClient(m_hInst);
    }

/*
 * CFrame::FRegisterAllClasses
 *
 * Purpose:
 *  Registers all classes used in this application.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if registration succeeded, FALSE otherwise.
 */

BOOL CFrame::FRegisterAllClasses(void)
    {
    WNDCLASS        wc;

    //Field that are the same for all windows.
    wc.style         = CS_HREDRAW | CS_VREDRAW;
    wc.hInstance     = m_hInst;
    wc.cbClsExtra    = 0;

    //Register the Frame window
    wc.lpfnWndProc   = FrameWndProc;
    wc.cbWndExtra    = CBFRAMEWNDEXTRA;
    wc.hIcon         = LoadIcon(m_hInst, TEXT("Icon"));
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE+1);
    wc.lpszMenuName  = MAKEINTRESOURCE(IDR_MENU);
    wc.lpszClassName = SZCLASSFRAME;

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

   #ifndef MDI
    wc.lpfnWndProc   = SDIClientWndProc;
    wc.cbWndExtra    = CBCLIENTWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSSDICLIENT;

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

    wc.lpfnWndProc   = DocumentWndProc;
    wc.cbWndExtra    = CBDOCUMENTWNDEXTRA;
    wc.hIcon         = LoadIcon(m_hInst
                           , MAKEINTRESOURCE(IDR_DOCUMENTICON));
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSDOCUMENT;

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

    return TRUE;
    }

/*
 * CFrame::FPreShowInit
 *
 * Purpose:
 *  Called from FInit before intially showing the window.  We do
 *  whatever else we want here, modifying nCmdShow as necessary
 *  which affects ShowWindow in FInit.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if this initialization succeeded, FALSE
 *                  otherwise.
 */

BOOL CFrame::FPreShowInit(void)
    {
    return TRUE;
    }

/*
 * CFrame::ParseCommandLine
 *
 * Purpose:
 *  Allows the application to parse the command line and take action
 *  after the window has possibly been shown.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            TRUE if this function succeeded, FALSE otherwise.
 */

void CFrame::ParseCommandLine(void)
    {
    LPTSTR      psz;
    UINT        uTemp;
    PCDocument  pDoc;

    psz=PszWhiteSpaceScan(m_pszCmdLine, TRUE);

   #ifdef WIN32
    //Skip the first argument which is the EXE name.
    psz=PszWhiteSpaceScan(psz, FALSE);
    psz=PszWhiteSpaceScan(psz, TRUE);
   #endif

    if ((TCHAR)0!=*psz)
        {
        pDoc=m_pCL->NewDocument(TRUE, m_pAdv);

        //If loading fails, we'll just sit around with a new document
        uTemp=pDoc->ULoad(TRUE, psz);
        pDoc->ErrorMessage(uTemp);
        }
   #ifndef MDI
    else
        {
        /*
         * In SDI we always want to have an initial document visible,
         * so if there was no command line we'll create a new
         * untitled document here.
         */
        pDoc=m_pCL->NewDocument(TRUE, m_pAdv);

        if (NULL!=pDoc)
            pDoc->ULoad(TRUE, NULL);
        }
   #endif

    return;
    }

/*
 * CFrame::CreateGizmos
 *
 * Purpose:
 *  Procedure to create all the necessary gizmobar buttons.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of gizmos added to the bar.
 */

UINT CFrame::CreateGizmos(void)
    {
    UINT            uState=GIZMO_NORMAL;
    UINT            utCmd =GIZMOTYPE_BUTTONCOMMAND;
    UINT            utEx  =GIZMOTYPE_BUTTONATTRIBUTEEX;

    //File New, Open, Close, Save, Import
    m_pGB->Add(utCmd, 0, IDM_FILENEW,   m_dxB, m_dyB, NULL, NULL
        , 3, uState);
    m_pGB->Add(utCmd, 1, IDM_FILEOPEN,  m_dxB, m_dyB, NULL, NULL
        , 4, uState);
    m_pGB->Add(utCmd, 2, IDM_FILECLOSE, m_dxB, m_dyB, NULL, m_hBmp
        , 0, uState);
    m_pGB->Add(utCmd, 3, IDM_FILESAVE,  m_dxB, m_dyB, NULL, NULL
        , 5, uState);

    //Separator
    m_pGB->Add(GIZMOTYPE_SEPARATOR,   4, 0, 6, m_dyB, NULL, NULL
        , 0, uState);

    //Edit Undo, Cut, Copy, Paste
    m_pGB->Add(utCmd, 5, IDM_EDITUNDO,  m_dxB, m_dyB, NULL, m_hBmp
        , 1, uState);
    m_pGB->Add(utCmd, 6, IDM_EDITCUT,   m_dxB, m_dyB, NULL, NULL
        , 0, uState);
    m_pGB->Add(utCmd, 7, IDM_EDITCOPY,  m_dxB, m_dyB, NULL, NULL
        , 1, uState);
    m_pGB->Add(utCmd, 8, IDM_EDITPASTE, m_dxB, m_dyB, NULL, NULL
        , 2, uState);

    return 9;
    }

/*
 * CFrame::MessageLoop
 *
 * Purpose:
 *  Spins in a standard message loop (with accelerators) until
 *  WM_QUIT is found after which it returns.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  WPARAM          Contents of msg.wParam from WM_QUIT.
 */

WPARAM CFrame::MessageLoop(void)
    {
    MSG     msg;

    while (GetMessage(&msg, NULL, 0,0 ))
        {
        if (!m_pCL->TranslateAccelerator(&msg))
            {
            if (!TranslateAccelerator(m_hWnd, m_hAccel, &msg))
                {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
                }
            }
        }

    return msg.wParam;
    }

/*
 * CFrame::FMessageHook
 *
 * Purpose:
 *  Provides a derivation of the base CFrame class to hook all
 *  messages to the window procedure for special processing.
 *  WM_COMMAND is NOT sent here as that goes through OnCommand
 *  instead.
 *
 * Parameters:
 *  <WndProc Parameters>
 *  pLRes           LRESULT * in which to store the return value
 *                  for the message.
 *
 * Return Value:
 *  BOOL            TRUE to prevent further processing, FALSE
 *                  otherwise.
 */

BOOL CFrame::FMessageHook(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, LRESULT *pLRes)
    {
    *pLRes=0;
    return FALSE;
    }

/*
 * CFrame::OnCommand
 *
 * Purpose:
 *  WM_COMMAND handler for the frame window so derivations can
 *  process their messages and then pass the standard commands (like
 *  file open and save) on to the base class.
 *
 * Parameters:
 *  hWnd            HWND of the frame window.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value for the message.
 */

LRESULT CFrame::OnCommand(HWND hWnd, WPARAM wParam, LPARAM lParam)
    {
    PCDocument      pDoc;
    TCHAR           szFile[CCHPATHMAX];
    BOOL            fOK;
    UINT            uTemp;
    UINT            uType;
    PCHourglass     pHour;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    /*
     * Don't bother with anything during first initialization,
     * skipping many GizmoBar notifications.
     */
    if (m_fInit)
        return 0L;

    pDoc=m_pCL->ActiveDocument();

    switch (wID)
        {
        case IDM_FILENEW:
            if (!m_pCL->SDIVerify())
                break;

            pHour=new CHourglass;
            pDoc=m_pCL->NewDocument(TRUE, m_pAdv);

            //Insure the document is untitled.
            if (NULL!=pDoc)
                pDoc->ULoad(TRUE, NULL);

            delete pHour;
            break;

        case IDM_FILEOPEN:
            if (!m_pCL->SDIVerify())
                break;

            szFile[0]=0;
            fOK=FSaveOpenDialog(szFile, CCHPATHMAX, IDS_FILEOPEN
                , TRUE, &uType);

            if (!fOK)
                return 0L;

            pDoc=m_pCL->NewDocument(FALSE, m_pAdv);

            if (NULL==pDoc)
                return 0L;

            pHour=new CHourglass;
            uTemp=pDoc->ULoad(TRUE, szFile);
            delete pHour;

            pDoc->ErrorMessage(uTemp);

            //Close the new doc on failure, show on success.
            if (DOCERR_NONE!=uTemp)
                m_pCL->CloseDocument(pDoc);
            else
                m_pCL->ShowDocument(pDoc, TRUE);

            return (DOCERR_NONE==uTemp);

        case IDM_FILECLOSE:
            //Ask if you want to save.
            if (!m_pCL->FCleanVerify(pDoc))
                return 0L;

            m_pCL->CloseDocument(pDoc);
            UpdateGizmos();
            break;

        case IDM_FILESAVE:
            //If we get this from the gizmobar, we may need Save As
            fOK=pDoc->FQuerySave();

            if (fOK)
                {
                //Save using current document name and version
                pHour=new CHourglass;
                uTemp=pDoc->USave(0, NULL);
                delete pHour;

                pDoc->ErrorMessage(uTemp);

                //This return code is for CFrame::FAskAndSave
                return (DOCERR_NONE==uTemp);
                }

            //FALL through to File/Save As for no name documents.

        case IDM_FILESAVEAS:
            //Go get a filename, then save it.
            pDoc->FilenameGet(szFile, CCHPATHMAX);

            fOK=FSaveOpenDialog(szFile, CCHPATHMAX, IDS_FILESAVEAS
                , FALSE, &uType);

            if (!fOK)
                return 0L;

            uTemp=pDoc->USave(uType, szFile);
            pDoc->ErrorMessage(uTemp);

            return (DOCERR_NONE==uTemp);

        case IDM_FILEEXIT:
            PostMessage(hWnd, WM_CLOSE, 0, 0L);
            break;

        case IDM_EDITCUT:
        case IDM_EDITCOPY:
            pDoc->FClip(hWnd, (IDM_EDITCUT==wID));

            //Update the gizmobar as appropriate.
            m_pGB->Enable(IDM_EDITPASTE, pDoc->FQueryPaste());
            break;

        case IDM_EDITUNDO:
            pDoc->Undo();
            break;

        case IDM_EDITPASTE:
            pDoc->FPaste(hWnd);
            break;

        //These commands don't happen in SDI builds; not on the menu
        case IDM_WINDOWCASCADE:
            m_pCL->OnWindowCommand(WM_MDICASCADE, 0);
            break;

        case IDM_WINDOWTILEHORZ:
            m_pCL->OnWindowCommand(WM_MDITILE, MDITILE_HORIZONTAL);
            break;

        case IDM_WINDOWTILEVERT:
            m_pCL->OnWindowCommand(WM_MDITILE, MDITILE_VERTICAL);
            break;

        case IDM_WINDOWICONS:
            m_pCL->OnWindowCommand(WM_MDIICONARRANGE, 0);
            break;

        case IDM_HELPABOUT:
            DialogBox(m_hInst, MAKEINTRESOURCE(IDD_ABOUT)
                , m_hWnd, AboutProc);
            break;

        default:
           return m_pCL->DefaultFrameProc(hWnd, WM_COMMAND, wParam
               , lParam);
        }

    return 0L;
    }

/*
 * CFrame::OnDocumentDataChange
 *
 * Purpose:
 *  Most of the CDocumentAdviseSink notifications we get are fairly
 *  standard implementations.  When data in the document changes,
 *  however, there's probably more special things we can do, so we
 *  keep this simple case simple with this hook, not forcing a
 *  derived class to reimplement CDocumentAdviseSink.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CFrame::OnDocumentDataChange(PCDocument pDoc)
    {
    return;
    }

/*
 * CFrame::OnDocumentActivate
 *
 * Purpose:
 *  Most of the CDocumentAdviseSink notifications we get are fairly
 *  standard implementations.  When the current document changes,
 *  however, there's probably more UI that frame derivations need
 *  to do, so we allow the hook here.
 *
 * Parameters:
 *  pDoc            PCDocument notifying the sink.
 *
 * Return Value:
 *  None
 */

void CFrame::OnDocumentActivate(PCDocument pDoc)
    {
    return;
    }

/*
 * CFrame::FSaveOpenDialog
 *
 * Purpose:
 *  Invokes the COMMDLG.DLL GetOpenFileName dialog and retrieves
 *  a filename for saving or opening.
 *
 * Parameters:
 *  pszFile         LPTSTR buffer to receive the entered filename.
 *  cchFile         UINT length of pszFile
 *  idsCaption      UINT of  string to use in the caption bar.
 *  fOpen           BOOL indicating if we want file open or save.
 *  puType          UINT * in which we store the selected type.
 *                  Can be NULL.
 *
 * Return Value:
 *  BOOL            TRUE if the function retrieved a filename,
 *                  FALSE if the user pressed CANCEL.
 */

BOOL CFrame::FSaveOpenDialog(LPTSTR pszFile, UINT cchFile
    , UINT idsCaption, BOOL fOpen, UINT *puType)
    {
    OPENFILENAME        ofn;
    TCHAR               szFilter[80];
    UINT                cch;
    BOOL                fRet;
   #ifdef DEBUG
    DWORD               dwErr;
   #endif

    if (NULL==pszFile)
        return FALSE;

    memset(&ofn, 0, sizeof(OPENFILENAME));
    ofn.lStructSize      =sizeof(OPENFILENAME);
    ofn.hwndOwner        =m_hWnd;

    if (fOpen)
        lstrcpy(szFilter, PSZ(IDS_FILEOPENFILTER));
    else
        lstrcpy(szFilter, PSZ(IDS_FILESAVEFILTER));

    cch=lstrlen(szFilter);
    ReplaceCharWithNull(szFilter, szFilter[cch-1]);

    ofn.lpstrFilter      =szFilter;
    ofn.nFilterIndex     =1L;

    ofn.lpstrTitle       =PSZ(idsCaption);
    ofn.lpstrFile        =pszFile;
    ofn.nMaxFile         =cchFile;

    ofn.lpstrDefExt      =PSZ(IDS_DEFEXT);

    if (fOpen)
        {
        ofn.Flags=OFN_HIDEREADONLY | OFN_FILEMUSTEXIST;
        fRet=GetOpenFileName(&ofn);
        }
    else
        {
        ofn.Flags=OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT;
        fRet=GetSaveFileName(&ofn);
        }

    if (fRet || NULL!=puType)
        *puType=(UINT)ofn.nFilterIndex;

   #ifdef DEBUG
    dwErr=CommDlgExtendedError();
   #endif
    return fRet;
    }

/*
 * CFrame::ReplaceCharWithNull
 *
 * Purpose:
 *  Walks a null-terminated string and replaces a given character
 *  with a zero for use with the FSaveOpenDialog member.
 *
 * Parameters:
 *  psz             LPTSTR to the string to process.
 *  ch              int character to replace.
 *
 * Return Value:
 *  int             Number of characters replaced.  -1 if psz is NULL
 */

UINT CFrame::ReplaceCharWithNull(LPTSTR psz, int ch)
    {
    UINT            cChanged=0;

    if (NULL==psz)
        return 0;

    while ((TCHAR)0!=*psz)
        {
        if (ch==*psz)
            {
            *psz=0;
            cChanged++;
            }

        psz++;
        }

    return cChanged;
    }

/*
 * CFrame::PszWhiteSpaceScan
 *
 * Purpose:
 *  Skips characters in a string until a whitespace or non-whitespace
 *  character is seen.  Whitespace is defined as \n, \r, \t, or ' '.
 *
 * Parameters:
 *  psz             LPTSTR to string to manipulate
 *  fSkip           BOOL  TRUE if we want to skip whitespace.
 *                  FALSE if we want to skip anything but whitespace.
 *
 * Return Value:
 *  LPTSTR          Pointer to first character in the string that
 *                  either non-whitespace (fSkip=TRUE) or
 *                  whitespace (fSkip=FALSE), which may be the
 *                  null terminator.
 */

LPTSTR CFrame::PszWhiteSpaceScan(LPTSTR psz, BOOL fSkip)
    {
    TCHAR       ch;
    BOOL        fWhite;

    while (ch=*psz)
        {
        //Not too sure how this localizes...
        fWhite=(TEXT('\n')==ch || TEXT('\r')==ch
            || TEXT('\t')==ch || TEXT(' ')==ch);

        //Too bad C doesn't have a logical XOR (^^) operator.
        if ((fSkip && !fWhite) || (!fSkip && fWhite))
            break;

        psz++;
        }

    return psz;
    }

//PUBLIC FUNCTIONS

/*
 * CFrame::FAskAndSave
 *
 * Purpose:
 *  If a document is closed and is dirty the client window we own
 *  will get the document's filename and call us here.  We will
 *  ask the user if they want to save that file and if so, send a
 *  message to our frame window to execute the Save command.
 *
 * Parameters:
 *  pszDoc          LPTSTR name of the document.  If the first
 *                  character is 0 then we use (Untitled).
 *
 * Return Value:
 *  BOOL            TRUE if the file was saved, FALSE otherwise.
 */

BOOL CFrame::FAskAndSave(LPTSTR pszDoc)
    {
    BOOL            fRet=TRUE;
    UINT            uRet;
    TCHAR           szTemp[CCHFILENAMEMAX+100];

    if (NULL==pszDoc)
        return FALSE;

    if ((TCHAR)0==*pszDoc)
        pszDoc=PSZ(IDS_UNTITLED);
    else
        {
        GetFileTitle(pszDoc, szTemp, CCHFILENAMEMAX);
        lstrcpy(pszDoc, szTemp);
        }

    wsprintf(szTemp, PSZ(IDS_FILEDIRTY), pszDoc);

    uRet=MessageBox(m_hWnd, szTemp, PSZ(IDS_CAPTION)
        , MB_YESNOCANCEL | MB_ICONEXCLAMATION);

    /*
     * If the user wants to save, tell the window to execute the
     * command.
     */
    if (IDYES==uRet)
        return (BOOL)SendCommand(m_hWnd, IDM_FILESAVE, 0, 0);

    //TRUE for No, False for Cancel
    return (IDCANCEL!=uRet);
    }

/*
 * CFrame::UpdateMenus
 *
 * Purpose:
 *  Handles the WM_INITMENU message for the frame window.  Depending
 *  on the existence of an active window, menu items are selectively
 *  enabled and disabled.
 *
 * Parameters:
 *  hMenu           HMENU of the menu to intialize
 *  iMenu           UINT position of the menu.
 *
 * Return Value:
 *  None
 */

void CFrame::UpdateMenus(HMENU hMenu, UINT iMenu)
    {
    PCDocument  pDoc;
    BOOL        fOK=FALSE;
    UINT        uTemp;
    UINT        uTempE;
    UINT        uTempD;

    pDoc=m_pCL->ActiveDocument();

    uTempE=MF_ENABLED | MF_BYCOMMAND;
    uTempD=MF_DISABLED | MF_GRAYED | MF_BYCOMMAND;
    uTemp=((NULL!=pDoc) ? uTempE : uTempD);

    /*
     * File menu:  If there is no current document window, disable
     * Close, Save, and Save As.  If there is a document but
     * it doesn't have a filename, disable save.
     */
    if (m_phMenu[0]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_FILECLOSE,  uTemp);
        EnableMenuItem(hMenu, IDM_FILESAVE,   uTemp);
        EnableMenuItem(hMenu, IDM_FILESAVEAS, uTemp);

        if (NULL!=pDoc)
            fOK=pDoc->FQuerySave();

        EnableMenuItem(hMenu, IDM_FILESAVE, (fOK) ? uTempE : uTempD);
        }

    /*
     * Edit menus:  If there's no document, disable all of it.
     * If there's a document but no clipboard format available,
     * disable paste only.
     */
    if (m_phMenu[1]==hMenu)
        {
        EnableMenuItem(hMenu, IDM_EDITUNDO,  uTemp);
        EnableMenuItem(hMenu, IDM_EDITCOPY,  uTemp);
        EnableMenuItem(hMenu, IDM_EDITCUT,   uTemp);

        /*
         * Paste has two dependencies; format available and an open
         * document
         */
        if (NULL!=pDoc)
            fOK=pDoc->FQueryPaste();

        fOK &=(uTemp==uTempE);
        EnableMenuItem(hMenu,IDM_EDITPASTE, (fOK) ? uTempE : uTempD);
        }

   #ifdef MDI
    //Window menu:  no document, no commands
    if (m_hMenuWindow==hMenu)
        {
        EnableMenuItem(hMenu, IDM_WINDOWCASCADE,  uTemp);
        EnableMenuItem(hMenu, IDM_WINDOWTILEHORZ, uTemp);
        EnableMenuItem(hMenu, IDM_WINDOWTILEVERT, uTemp);
        EnableMenuItem(hMenu, IDM_WINDOWICONS,    uTemp);
        }
   #endif

    return;
    }

/*
 * CFrame::UpdateGizmos
 *
 * Purpose:
 *  Enables and disables gizmos depending on whether we have
 *  a document or not and depending on clipboard format availability.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CFrame::UpdateGizmos(void)
    {
    PCDocument  pDoc;
    BOOL        fEnable;

    pDoc=m_pCL->ActiveDocument();
    fEnable=(NULL!=pDoc);

    if (m_fLastEnable!=fEnable)
        {
        m_fLastEnable=fEnable;

        //No document, disable just about everything
        m_pGB->Enable(IDM_FILECLOSE,  fEnable);
        m_pGB->Enable(IDM_FILESAVE,   fEnable);

        m_pGB->Enable(IDM_EDITUNDO,  fEnable);
        m_pGB->Enable(IDM_EDITCUT,   fEnable);
        m_pGB->Enable(IDM_EDITCOPY,  fEnable);
        }

    //Special handling:  clipboard format available, enable paste
    if (NULL!=pDoc)
        fEnable &= pDoc->FQueryPaste();

    //Paste is not enabled unless there's a document too
    if (m_fLastPaste!=fEnable)
        {
        m_fLastPaste=fEnable;
        m_pGB->Enable(IDM_EDITPASTE, fEnable);
        }

    return;
    }

/*
 * CFrame::WindowTitleSet
 *
 * Purpose:
 *  Handles changing the caption bar of a document window.
 *
 * Parameters:
 *  pDoc            PCDocument of the document affected.  If NULL,
 *                  then we remove titles.

*  fDocTitle       BOOL indicating to set the document or the main
 *                  window captions.  In MDI we may do either, this
 *                  is ignored in SDI.
 *
 * Return Value:
 *  None
 */

void CFrame::WindowTitleSet(PCDocument pDoc, BOOL fDocTitle)
    {
    TCHAR       szTitle[CCHPATHMAX];
    TCHAR       szFile[CCHPATHMAX];

    /*
     * Go grab the filename.  If we get an emtpy file back, then
     * we'll use (Untitled).  If we have a NULL document, then we'll
     * just set the title of the app back to having no filename.
     */
    if (NULL!=pDoc)
        {
        pDoc->FilenameGet(szTitle, CCHPATHMAX);

        if ((TCHAR)0==szTitle[0])
            lstrcpy(szFile, PSZ(IDS_UNTITLED));
        else
            {
            GetFileTitle(szTitle, szFile, sizeof(szFile));
           #ifndef WIN32
            AnsiUpper(szFile);
           #endif
            }
        }
    else
        szFile[0]=0;

   #ifndef MDI
    //SDI always titles the application window
    fDocTitle=FALSE;
   #endif

    if (fDocTitle)
        {
        if (NULL!=pDoc)
            SetWindowText(pDoc->Window(), szFile);
        }
    else
        {
        if ((TCHAR)0!=szFile[0])
            {
            wsprintf(szTitle, TEXT("%s - %s"), PSZ(IDS_CAPTION)
                , (LPTSTR)szFile);
            SetWindowText(m_hWnd, szTitle);
            }
        else
            SetWindowText(m_hWnd, PSZ(IDS_CAPTION));
        }

    return;
    }

/*
 * CFrame::GetStatusControl (inline)
 *
 * Purpose:
 *  Returns the StatStrip object pointer to the caller so they can
 *  display status messages.
 *
 * Parameters:
 *  None
 *
 *
 * Return Value:
 *  PCStatStrip     Pointer to the status strip object.
 */

PCStatStrip inline CFrame::GetStatusControl(void)
    {
    return m_pSS;
    }


CHATCH.CPP

/*
 * CHATCH.CPP
 *
 * Implementation of a class to manage a hatch border around an
 * in-place editing window.  This is a modification of the OLESTD
 * hatch window because we want to use this window to forward
 * messages.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define INC_OLE2
#include <windows.h>
#include <ole2.h>

#define SHADE_FULLRECT        1
#define SHADE_BORDERIN        2
#define SHADE_BORDEROUT       3

/*
 * DrawShading
 *
 * Purpose:
 *  Shade the object when it is in in-place editing. Borders are drawn
 *  on the Object rectangle. The right and bottom edge of the rectangle
 *  are excluded in the drawing.
 *
 * Parameters:
 *  lpRect      Dimensions of Container Object
 *  hdc         HDC for drawing
 *  dwFlags-
 *      Exclusive flags
 *          SHADE_FULLRECT    Shade the whole rectangle
 *          SHADE_BORDERIN    Shade cWidth pixels inside rect
 *          SHADE_BORDEROUT   Shade cWidth pixels outside rect
 *      Optional flags
 *          SHADE_USEINVERSE
 *              use PATINVERT instead of the hex value
 *  cWidth      width of border in pixel
 *
 * Return Value: null
 *
 * Copied from OBJFDBK.C
 *
 */
void DrawShading(LPRECT lpRect, HDC hdc, DWORD dwFlags, UINT cWidth)
{
        HBRUSH  hbr;
        HBRUSH  hbrOld;
        HBITMAP hbm;
        RECT    rc;
        WORD    wHatchBmp[] = {0x11, 0x22, 0x44, 0x88, 0x11, 0x22, 0x44, 0x88};
        COLORREF cvText;
        COLORREF cvBk;

        hbm = CreateBitmap(8, 8, 1, 1, wHatchBmp);
        hbr = CreatePatternBrush(hbm);
        hbrOld = (HBRUSH) SelectObject(hdc, hbr);

        rc = *lpRect;

        if (dwFlags == SHADE_FULLRECT) {
                cvText = SetTextColor(hdc, RGB(255, 255, 255));
                cvBk = SetBkColor(hdc, RGB(0, 0, 0));
                PatBlt(hdc, rc.left, rc.top, rc.right-rc.left, rc.bottom-rc.top,
                        0x00A000C9L /* DPa */ );

        } else {    // either inside or outside rect

                if (dwFlags == SHADE_BORDEROUT)
                        InflateRect((LPRECT)&rc, cWidth - 1, cWidth - 1);

                cvText = SetTextColor(hdc, RGB(255, 255, 255));
                cvBk = SetBkColor(hdc, RGB(0, 0, 0));
                PatBlt(hdc, rc.left, rc.top, rc.right - rc.left,
                        cWidth, 0x00A000C9L /* DPa */);
                PatBlt(hdc, rc.left, rc.top, cWidth, rc.bottom - rc.top,
                        0x00A000C9L /* DPa */);
                PatBlt(hdc, rc.right - cWidth, rc.top, cWidth,
                        rc.bottom - rc.top, 0x00A000C9L /* DPa */);
                PatBlt(hdc, rc.left, rc.bottom - cWidth, rc.right-rc.left,
                        cWidth, 0x00A000C9L /* DPa */);
        }

        SetTextColor(hdc, cvText);
        SetBkColor(hdc, cvBk);
        SelectObject(hdc, hbrOld);
        DeleteObject(hbr);
        DeleteObject(hbm);
}

#include "classlib.h"

/*
 * FHatchWindowRegister
 *
 * Purpose:
 *  Registers the hatch window class for use with CHatchWin.
 *
 * Parameters:
 *  hInst           HINSTANCE under which to register.
 *
 * Return Value:
 *  BOOL            TRUE if successful, FALSE otherwise.
 */

BOOL FHatchWindowRegister(HINSTANCE hInst)
    {
    WNDCLASS    wc;

    //Must have CS_DBLCLKS for border!
    wc.style         = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
    wc.hInstance     = hInst;
    wc.cbClsExtra    = 0;
    wc.lpfnWndProc   = HatchWndProc;
    wc.cbWndExtra    = CBHATCHWNDEXTRA;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = SZCLASSHATCHWIN;

    return RegisterClass(&wc);
        return FALSE;
    }

/*
 * CHatchWin:CHatchWin
 * CHatchWin::~CHatchWin
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the application we're in.
 */

CHatchWin::CHatchWin(HINSTANCE hInst)
    : CWindow(hInst)
    {
    m_hWnd=NULL;
    m_hWndKid=NULL;
    m_hWndAssociate=NULL;
    m_uID=0;

    m_dBorder=GetProfileInt(TEXT("windows")
        , TEXT("OleInPlaceBorderWidth"), HATCHWIN_BORDERWIDTHDEFAULT);

    return;
    }

CHatchWin::~CHatchWin(void)
    {
    /*
     * Chances are this was already destroyed when a document
     * was destroyed.
     */
    if (NULL!=m_hWnd && IsWindow(m_hWnd))
        DestroyWindow(m_hWnd);

    return;
    }

/*
 * CHatchWin::FInit
 *
 * Purpose:
 *  Instantiates a hatch window within a given parent with a
 *  default rectangle.  This is not initially visible.
 *
 * Parameters:
 *  hWndParent      HWND of the parent of this window
 *  uID             UINT identifier for this window (send in
 *                  notifications to associate window).
 *  hWndAssoc       HWND of the initial associate.
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CHatchWin::FInit(HWND hWndParent, UINT uID, HWND hWndAssoc)
    {
    m_hWnd=CreateWindowEx(WS_EX_NOPARENTNOTIFY, SZCLASSHATCHWIN
        , SZCLASSHATCHWIN, WS_CHILD | WS_CLIPSIBLINGS
        | WS_CLIPCHILDREN, 0, 0, 100, 100, hWndParent, (HMENU)uID
        , m_hInst, this);

    m_uID=uID;
    m_hWndAssociate=hWndAssoc;

    return (NULL!=m_hWnd);
    }

/*
 * CHatchWin::HwndAssociateSet
 * CHatchWin::HwndAssociateGet
 *
 * Purpose:
 *  Sets (Set) or retrieves (Get) the associate window of the
 *  hatch window.
 *
 * Parameters: (Set only)
 *  hWndAssoc       HWND to set as the associate.
 *
 * Return Value:
 *  HWND            Previous (Set) or current (Get) associate
 *                  window.
 */

HWND CHatchWin::HwndAssociateSet(HWND hWndAssoc)
    {
    HWND    hWndT=m_hWndAssociate;

    m_hWndAssociate=hWndAssoc;
    return hWndT;
    }

HWND CHatchWin::HwndAssociateGet(void)
    {
    return m_hWndAssociate;
    }

/*
 * CHatchWin::RectsSet
 *
 * Purpose:
 *  Changes the size and position of the hatch window and the child
 *  window within it using a position rectangle for the child and
 *  a clipping rectangle for the hatch window and child.  The hatch
 *  window occupies prcPos expanded by the hatch border and clipped
 *  by prcClip.  The child window is fit to prcPos to give the
 *  proper scaling, but it clipped to the hatch window which
 *  therefore clips it to prcClip without affecting the scaling.
 *
 * Parameters:
 *  prcPos          LPRECT providing the position rectangle.
 *  prcClip         LPRECT providing the clipping rectangle.
 *
 * Return Value:
 *  None
 */

void CHatchWin::RectsSet(LPRECT prcPos, LPRECT prcClip)
    {
    RECT    rc;
    RECT    rcPos;

    //Calculate the rectangle for the hatch window, then clip it.
    rcPos=*prcPos;
    InflateRect(&rcPos, m_dBorder, m_dBorder);
    IntersectRect(&rc, &rcPos, prcClip);

    SetWindowPos(m_hWnd, NULL, rc.left, rc.top, rc.right-rc.left
        , rc.bottom-rc.top, SWP_NOZORDER | SWP_NOACTIVATE);

    /*
     * Set the rectangle of the child window to be at m_dBorder
     * from the top and left but with the same size as prcPos
     * contains.  The hatch window will clip it.
     */

    SetWindowPos(m_hWndKid, NULL, rcPos.left-rc.left+m_dBorder
        , rcPos.top-rc.top+m_dBorder, prcPos->right-prcPos->left
        , prcPos->bottom-prcPos->top, SWP_NOZORDER | SWP_NOACTIVATE);

    return;
    }

/*
 * CHatchWin::ChildSet
 *
 * Purpose:
 *  Assigns a child window to this hatch window.
 *
 * Parameters:
 *  hWndKid         HWND of the child window.
 *
 * Return Value:
 *  None
 */

void CHatchWin::ChildSet(HWND hWndKid)
    {
    m_hWndKid=hWndKid;

    if (NULL!=hWndKid)
        {
        SetParent(hWndKid, m_hWnd);

        //Insure this is visible when the hatch window becomes visible.
        ShowWindow(hWndKid, SW_SHOW);
        }

    return;
    }

/*
 * HatchWndProc
 *
 * Purpose:
 *  Standard window procedure for the Hatch Window
 */

LRESULT APIENTRY HatchWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCHatchWin  phw;
    int         d;
    HDC         hDC;
    PAINTSTRUCT ps;
    RECT        rc;

    phw=(PCHatchWin)GetWindowLong(hWnd, HWWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_CREATE:
            phw=(PCHatchWin)((LPCREATESTRUCT)lParam)->lpCreateParams;
            SetWindowLong(hWnd, HWWL_STRUCTURE, (LONG)phw);
            break;

        case WM_PAINT:
            hDC=BeginPaint(hWnd, &ps);
            GetClientRect(hWnd, &rc);

            d=phw->m_dBorder;

            //Always draw the hatching.
            DrawShading(&rc, hDC, SHADE_BORDERIN, d);

            EndPaint(hWnd, &ps);
            break;

        case WM_SETFOCUS:
            //We need this since the container will SetFocus to us.
            if (NULL!=phw->m_hWndKid)
                SetFocus(phw->m_hWndKid);

            break;

        case WM_LBUTTONDBLCLK:
            /*
             * If the double click was within m_dBorder of an
             * edge, send the HWN_BORDERDOUBLECLICKED notification.
             *
             * Because we're always sized just larger than our child
             * window by the border width, we can only *get* this
             * message when the mouse is on the border.  So we can
             * just send the notification.
             */

            if (NULL!=phw->m_hWndAssociate)
                {
                SendCommand(phw->m_hWndAssociate, phw->m_uID
                    , HWN_BORDERDOUBLECLICKED, hWnd);
                }

            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }


CHOURGLS.CPP

/*
 * CHOURGLS.CPP
 * Sample Code Class Libraries
 *
 * Implementation of the CHourglass class.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

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

/*
 * CHourglass::CHourglass
 * CHourglass::~CHourglass
 *
 * Constructor Parameters (optional):
 *  hWnd            HWND of a window that wants capture.  Can
 *                  be NULL in which case it is ignored.
 */

CHourglass::CHourglass(void)
    {
    m_hCur=SetCursor(LoadCursor(NULL, IDC_WAIT));
    m_hWndCapture=NULL;
    return;
    }

CHourglass::CHourglass(HWND hWnd)
    {
    m_hCur=SetCursor(LoadCursor(NULL, IDC_WAIT));

    if (NULL!=hWnd)
        {
        m_hWndCapture=hWnd;
        SetCapture(hWnd);
        }

    return;
    }

CHourglass::~CHourglass(void)
    {
    if (NULL!=m_hWndCapture)
        ReleaseCapture();

    SetCursor(m_hCur);
    return;
    }


CLASSLIB.H

/*
 * CLASSLIB.H
 * Sample Code Class Libraries
 *
 * Master include file for our own Class Library containing:
 *  CStringTable    Class that manages a stringtable.
 *  CHourglass      Class to manage the hourglass.
 *
 *  CWindow         Basic window class for most other classes in here
 *  CGizmoBar       Wrapper for GizmoBar control
 *  CStatStrip      Wrapper for StatStrip control
 *
 *  CFrame          Frame window
 *  CClient         Client window with a uniform interface for
 *                  MDI and SDI frames.
 *  CDocument       Document window, also with a uniform interface
 *                  for MDI and SDI.
 *
 *  CDocumentAdviseSink Object implemented in the frame that the
 *                      document notifies of important events.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _CLASSLIB_H_
#define _CLASSLIB_H_

#include <book1632.h>

//Get default resource definitions
#include "classres.h"

class CStringTable;
class CHourglass;
class CGizmoBar;
class CStatStrip;
class CWindow;
class CFrame;
class CClient;
class CDocument;
class CDocumentAdviseSink;
class CHatchWin;

/**
 ** CStringTable providing string table management for anyone.
 **/

class CStringTable
    {
    protected:
        HINSTANCE       m_hInst;
        UINT            m_idsMin;
        UINT            m_idsMax;
        USHORT          m_cStrings;
        LPTSTR          m_pszStrings;
        LPTSTR         *m_ppszTable;

    public:
        CStringTable(HINSTANCE);
        ~CStringTable(void);

        BOOL FInit(UINT, UINT);

        //Function to resolve an ID into a string pointer.
        const LPTSTR operator [](const UINT) const;
    };

typedef CStringTable *PCStringTable;

/*
 * Typical classes that initialize a stringtable should have an m_pST
 * member, then they can use this macro conveniently.
 */

#define PSZ(i) ((*m_pST)[i])
#define CCHSTRINGMAX            256

/**
 ** CHourglass:  Manages the hourglass and mouse capture
 **/

class CHourglass
    {
    protected:
        HWND            m_hWndCapture;      //Window with capture
        HCURSOR         m_hCur;             //Cursor held.

    public:
        CHourglass(void);
        CHourglass(HWND);
        ~CHourglass(void);
    };

typedef CHourglass *PCHourglass;

/**
 ** CWindow:  Our basic window class.
 **/

class CWindow
    {
    protected:
        HINSTANCE   m_hInst;            //Task instance
        HWND        m_hWnd;             //Window handle of the window

    public:
        //Standard Class Functions
        CWindow(HINSTANCE);
        ~CWindow(void);

        //Just returns m_hWnd.  No need to modify
        HWND Window(void);
    };

typedef CWindow * PCWindow;

/**
 ** CGizmoBar encapculates a GizmoBar control
 **/

#include <bttncur.h>
#include <gizmobar.h>

class CGizmoBar : public CWindow
    {
    protected:
        UINT            m_cyBar;

    public:
        CGizmoBar(HINSTANCE);
        ~CGizmoBar(void);

        BOOL FInit(HWND, UINT, UINT);

        //Window message wrappers
        void   OnSize(HWND);
        void   FontSet(HFONT, BOOL);
        HFONT  FontGet(void);
        void   Enable(BOOL);

        //GizmoBar function wrappers
        HWND   HwndAssociateSet(HWND);
        HWND   HwndAssociateGet(void);

        BOOL   Add(UINT, UINT, UINT, UINT, UINT, LPTSTR, HBITMAP
                   , UINT, UINT);
        BOOL   Remove(UINT);

        LONG   SendMessage(UINT, UINT, WPARAM, LPARAM);

        BOOL   Show(UINT, BOOL);
        BOOL   Enable(UINT, BOOL);
        BOOL   Check(UINT, BOOL);
        UINT   FocusSet(UINT);
        BOOL   Exist(UINT);

        int    TypeGet(UINT);

        DWORD  DataSet(UINT, DWORD);
        DWORD  DataGet(UINT);
        BOOL   NotifySet(UINT, BOOL);
        BOOL   NotifyGet(UINT);

        int    TextGet(UINT, LPTSTR, UINT);
        void   TextSet(UINT, LPTSTR);
        UINT   IntGet(UINT, BOOL *, BOOL);
        void   IntSet(UINT, int, BOOL);
    };

typedef CGizmoBar *PCGizmoBar;

/**
 ** CStatStrip encapsulates a StatStrip control.
 **/

#include <stastrip.h>

class CStatStrip : public CWindow
    {
    protected:
        UINT        m_cy;           //Control height

    public:
        CStatStrip(HINSTANCE);
        ~CStatStrip(void);

        BOOL FInit(HWND, UINT, UINT);

        void  OnSize(HWND);
        void  MessageSet(LPTSTR);
        UINT  MessageGet(LPTSTR, UINT);
        UINT  MessageGetLength(void);
        void  FontSet(HFONT, BOOL);
        HFONT FontGet(void);
        void  Enable(BOOL);

        BOOL  MessageMap(HWND, HINSTANCE, UINT, UINT, UINT, UINT
                  , UINT, UINT, UINT, UINT, UINT);
        void  MenuSelect(WPARAM, LPARAM);
        void  MessageDisplay(UINT);
    };

typedef CStatStrip *PCStatStrip;

/**
 ** CFrame encapsulates a main application window
 **/

//FRAMEWIN.CPP:  Standard window procedure and AboutProc
LRESULT APIENTRY FrameWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL    APIENTRY AboutProc(HWND, UINT, WPARAM, LPARAM);

#define CBFRAMEWNDEXTRA     sizeof(LONG)
#define FRAMEWL_STRUCTURE   0

/*
 * Structure containing resource ranges for initialization of
 * a CFrame object through its FInit member.
 */

typedef struct tagFRAMEINIT
    {
    UINT    idsMin;         //Stringtable start and end
    UINT    idsMax;

    UINT    idsStatMin;     //StatStrip stringtable start and end
    UINT    idsStatMax;

    UINT    idStatMenuMin;  //Start and end IDs for StatStrip
    UINT    idStatMenuMax;  //popup menu IDs

    UINT    iPosWindowMenu; //Position of the Window menu (MDI init)
    UINT    cMenus;         //Number of popup menus we have.

    int     x;              //Window positioning.
    int     y;
    int     cx;
    int     cy;
    } FRAMEINIT, *PFRAMEINIT;

//FRAME.CPP:  Frame object that creates a main window

class CFrame : public CWindow
    {
    //Let our window procedure look in our private variables.
    friend LRESULT APIENTRY FrameWndProc(HWND, UINT, WPARAM, LPARAM);
    friend class CDocumentAdviseSink;

    protected:
        HINSTANCE       m_hInstPrev;        //WinMain parameters
        LPTSTR          m_pszCmdLine;
        int             m_nCmdShow;

        BOOL            m_fInit;            //Initializing or closing
        BOOL            m_fSizing;          //Inside WM_SIZE
        BOOL            m_fClosing;         //In WM_CLOSE

        BOOL            m_fLastEnable;      //Toolbar enable state
        BOOL            m_fLastPaste;

        HMENU          *m_phMenu;           //Popup menu handles
        HMENU           m_hMenuWindow;      //"Window" menu

        HACCEL          m_hAccel;           //Accelerators
        HBITMAP         m_hBmp;             //Gizmobar images
        UINT            m_cyBar;            //Gizmobar height
        UINT            m_dxB;              //Gizmobar button sizes
        UINT            m_dyB;

        PCGizmoBar      m_pGB;              //GizmoBar
        PCStatStrip     m_pSS;              //StatStrip

        PCStringTable   m_pST;              //Stringtable.
        CClient        *m_pCL;              //SDI/MDI client window

        CDocumentAdviseSink *m_pAdv;    //Sink for document codes

    protected:
        virtual CClient *CreateCClient(void);

        virtual BOOL     FRegisterAllClasses(void);
        virtual BOOL     FPreShowInit(void);
        virtual void     ParseCommandLine(void);
        virtual UINT     CreateGizmos(void);

        virtual BOOL     FMessageHook(HWND, UINT, WPARAM, LPARAM
                             , LRESULT *);
        virtual LRESULT  OnCommand(HWND, WPARAM, LPARAM);
        virtual void     OnDocumentDataChange(CDocument *);
        virtual void     OnDocumentActivate(CDocument *);

        virtual BOOL     FSaveOpenDialog(LPTSTR, UINT, UINT, BOOL
                             , UINT *);
        virtual UINT     ReplaceCharWithNull(LPTSTR, int);
        virtual LPTSTR   PszWhiteSpaceScan(LPTSTR, BOOL);

    public:
        CFrame(HINSTANCE, HINSTANCE, LPSTR, int);
        virtual ~CFrame(void);

        virtual BOOL     FInit(PFRAMEINIT);

        virtual WPARAM   MessageLoop(void);
        virtual BOOL     FAskAndSave(LPTSTR);
        virtual void     UpdateMenus(HMENU, UINT);
        virtual void     UpdateGizmos(void);
        virtual void     WindowTitleSet(CDocument *, BOOL);

        virtual PCStatStrip inline GetStatusControl(void);
    };

typedef CFrame *PCFrame;

//Other Miscellaneous CFrame definitions

//GizmoBar and StatStrip IDs used in this frame
#define ID_GIZMOBAR         11
#define ID_STATSTRIP        12

//Classname
#define SZCLASSFRAME        TEXT("Frame")

/**
 ** CClient encapsulates an MDI or SDI client window
 **/

//CLIENT.CPP
LRESULT APIENTRY SDIClientWndProc(HWND, UINT, WPARAM, LPARAM);

#define CBCLIENTWNDEXTRA    sizeof(LONG)
#define CLIENTWL_HWNDDOC    0

class CClient : public CWindow
    {
    friend LRESULT APIENTRY SDIClientWndProc(HWND, UINT, WPARAM
        , LPARAM);

    protected:
        PCFrame         m_pFR;          //Frame window information
        UINT            m_cDoc;         //Count of open documents
        CDocument *     m_pDocLast;     //Last created document

    private:
        //This non-overridable sets m_pDocLast
        CDocument         * CreateDoc(void);

    protected:
        //Overridable for creating a new CDocument
        virtual CDocument * CreateCDocument(void);

    public:
        CClient(HINSTANCE);
        virtual ~CClient(void);

        virtual BOOL            FInit(HMENU, LPRECT, PCFrame);

        virtual BOOL            TranslateAccelerator(LPMSG);
        virtual LRESULT         DefaultFrameProc(HWND, UINT, WPARAM
                                    , LPARAM);
        virtual void            OnWindowCommand(UINT, UINT);
        virtual void            OnSize(UINT, UINT, UINT, UINT);

        virtual CDocument *     NewDocument(BOOL
                                    , CDocumentAdviseSink *);
        virtual CDocument *     ActiveDocument(void);
        virtual BOOL            ShowDocument(CDocument *, BOOL);
        virtual BOOL            SDIVerify(void);
        virtual UINT            CloseDocument(CDocument *);
        virtual BOOL            QueryCloseAllDocuments(BOOL);
        virtual BOOL            FCleanVerify(CDocument *);
    };

typedef CClient *PCClient;

//Other Miscellaneous CFrame definitions

//Document window identifiers
#define ID_DOCUMENT         999     //Internal ID for all documents
#define ID_MDICHILDMIN      1000    //Starting point for MDI doc IDs

#define SZCLASSSDICLIENT    TEXT("SDIClient")

/**
 ** CDocument encapsulates an MDI or SDI child window
 **/

//DOCWIN.CPP
LRESULT APIENTRY DocumentWndProc(HWND, UINT, WPARAM, LPARAM);

//DOCUMENT.CPP
//File-related string lengths.
#define CCHPATHMAX          256
#define CCHFILENAMEMAX      15

//Window extra bytes and offsets
#define CBDOCUMENTWNDEXTRA  sizeof(LONG)
#define DOCWL_STRUCTURE     0

//Error codes from Load and Save functions
#define DOCERR_NONE                     0
#define DOCERR_NOFILE                   1
#define DOCERR_COULDNOTOPEN             2
#define DOCERR_READFAILURE              3
#define DOCERR_UNSUPPORTEDVERSION       4
#define DOCERR_WRITEFAILURE             5
#define DOCERR_CANCELLED                6
#define DOCERR_STDMAX                   6

/*
 * Structure containing resource ranges and other data for
 * initialization of a CDocument object through its FInit member.
 */

typedef struct tagDOCUMENTINIT
    {
    UINT        idsMin;    //Stringtable start and end
    UINT        idsMax;
    HWND        hWndDoc;   //HWND of this document, created in client

    CDocumentAdviseSink * pAdv;
    } DOCUMENTINIT, *PDOCUMENTINIT;

class CDocument : public CWindow
    {
    friend LRESULT APIENTRY DocumentWndProc(HWND, UINT, WPARAM
        , LPARAM);

    protected:
        PCFrame         m_pFR;                //Back pointer
        UINT            m_cf;                 //Clipboard format
        BOOL            m_fDirty;             //Is file dirty?
        BOOL            m_fNoDirty;           //Don't touch dirty flag
        BOOL            m_fNoSize;            //Prevent sizing
        BOOL            m_fFileKnown;         //File/Save allowed?
        TCHAR           m_szFile[CCHPATHMAX]; //Filename for Save

        PCStringTable   m_pST;                //Document strings

        /*
         * If someone above us wants information, they'll
         * give us this object.
         */
        class CDocumentAdviseSink * m_pAdv;

    protected:
        virtual BOOL     FMessageHook(HWND, UINT, WPARAM, LPARAM
                             , LRESULT *);

    public:
        CDocument(HINSTANCE, PCFrame);
        virtual ~CDocument(void);

        virtual BOOL     FInit(PDOCUMENTINIT);

        virtual BOOL     FDirtySet(BOOL);
        virtual BOOL     FDirtyGet(void);
        virtual void     Clear(void);

        virtual UINT     ULoad(BOOL, LPTSTR);
        virtual UINT     USave(UINT, LPTSTR);
        virtual void     ErrorMessage(UINT);

        virtual BOOL     FClip(HWND, BOOL);
        virtual HGLOBAL  RenderFormat(UINT);
        virtual BOOL     FQueryPaste(void);
        virtual BOOL     FPaste(HWND);
        virtual void     Undo(void);

        virtual BOOL     FQuerySave(void);
        virtual void     Rename(LPTSTR);
        virtual UINT     FilenameGet(LPTSTR, UINT);
        virtual PCFrame  FrameGet(void);
    };

typedef CDocument *PCDocument;

//Classname for documents, not localized
#define SZCLASSDOCUMENT     TEXT("document")

//Macro that isolates us from MDI or SDI Def procs
#ifdef MDI
#define DEFDOCUMENTPROC     DefMDIChildProc
#else
#define DEFDOCUMENTPROC     DefWindowProc
#endif

//Message to get PCDocument from hWnd
#define DOCM_PDOCUMENT      (WM_USER+0)

/**
 ** CDocumentAdviseSink through which a document notifies the
 ** frame or important events.
 **/

class CDocumentAdviseSink
    {
    protected:
        LPVOID      m_pv;               //Customizable structure

    public:
        CDocumentAdviseSink(LPVOID);

        virtual void OnDataChange(PCDocument);
        virtual void OnCloseRequest(PCDocument);
        virtual void OnSizeChange(PCDocument, LPRECT);
        virtual void OnCaptionChange(PCDocument);
        virtual void OnActivate(PCDocument);
    };

typedef CDocumentAdviseSink *PCDocumentAdviseSink;

/**
 ** CHatchWin when used as a parent window creates a thin
 ** hatch border around the child window.
 **/

BOOL FHatchWindowRegister(HINSTANCE);

//Classname
#define SZCLASSHATCHWIN                 TEXT("hatchwin")

//Window extra bytes and offsets
#define CBHATCHWNDEXTRA                 (sizeof(LONG))
#define HWWL_STRUCTURE                  0

//Notification codes for WM_COMMAND messages
#define HWN_BORDERDOUBLECLICKED         1

//Width of the border
#define HATCHWIN_BORDERWIDTHDEFAULT     4

class CHatchWin : public CWindow
    {
    friend LRESULT APIENTRY HatchWndProc(HWND, UINT, WPARAM, LPARAM);

    public:
        int         m_dBorder;
        UINT        m_uID;
        HWND        m_hWndKid;
        HWND        m_hWndAssociate;

    public:
        CHatchWin(HINSTANCE);
        ~CHatchWin(void);

        BOOL      FInit(HWND, UINT, HWND);

        HWND      HwndAssociateSet(HWND);
        HWND      HwndAssociateGet(void);

        void      RectsSet(LPRECT, LPRECT);
        void      ChildSet(HWND);
    };

typedef CHatchWin *PCHatchWin;

#endif //_CLASSLIB_H_


CLASSRES.H

/*
 * CLASSRES.H
 * Sample Code Class Libraries
 *
 * Definitions specifically pertaining to resources for the default
 * class libraries.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _CLASSRES_H_
#define _CLASSRES_H_

#include <bttncur.h>

//Position of Window menu and number of menus.
#define WINDOW_MENU                         2
#define CMENUS                              3

//Resource identifiers.
#define IDR_MENU                            1
#define IDR_ACCELERATORS                    1
#define IDR_DOCUMENTICON                    1
#define IDD_ABOUT                           1

#define IDR_STATMESSAGEMAP                  1

//Dialog control identifiers
#define ID_NULL                             99

//Image identifiers in the standard bmp file.
#define ID_STDGIZMOFILECLOSE                0
#define ID_STDGIZMOEDITUNDO                 1

//Menu command identifiers.
#define IDM_FILENEW                         100
#define IDM_FILEOPEN                        101
#define IDM_FILECLOSE                       102
#define IDM_FILESAVE                        103
#define IDM_FILESAVEAS                      104
#define IDM_FILEEXIT                        105
#define IDM_CUSTOMFILEMIN                   106

#define IDM_EDITUNDO                        200
#define IDM_EDITCUT                         201
#define IDM_EDITCOPY                        202
#define IDM_EDITPASTE                       203
#define IDM_CUSTOMEDITMIN                   204

#define IDM_WINDOWCASCADE                   300
#define IDM_WINDOWTILEHORZ                  301
#define IDM_WINDOWTILEVERT                  302
#define IDM_WINDOWICONS                     303
#define IDM_CUSTOMWINDOWMIN                 304

#define IDM_HELPABOUT                       400
#define IDM_CUSTOMHELPMIN                   401

//Where derivations can start using IDs.
#define IDM_CUSTOMMIN                       500

//IDs for StatStrip handling of popup menus:  must be in order of menu
#define ID_MENUSYS                          1000
#define ID_MESSAGEEMPTY                     1001
#define ID_MESSAGEREADY                     1002
#define ID_STANDARDMAX                      1002

#define ID_MENUFILE                         (ID_STANDARDMAX+1)
#define ID_MENUEDIT                         (ID_STANDARDMAX+2)
#define ID_MENUWINDOW                       (ID_STANDARDMAX+3)
#define ID_MENUHELP                         (ID_STANDARDMAX+4)
#define ID_MENUCUSTOMMIN                    (ID_STANDARDMAX+5)

/*
 * Stringtable IDs.  Keep sequential for each group.  Strings
 * are limited by CCHSTRINGMAX (see classlib.h).
 */

//Frame window junk
#define IDS_STANDARDFRAMEMIN                0
#define IDS_CAPTION                         0
#define IDS_UNTITLED                        1
#define IDS_FILEDIRTY                       2
#define IDS_DEFEXT                          3
#define IDS_DOTEXT                          4
#define IDS_FILEOPENFILTER                  5
#define IDS_FILESAVEFILTER                  6
#define IDS_FILEOPEN                        7
#define IDS_FILESAVEAS                      8
#define IDS_STANDARDFRAMEMAX                8
#define IDS_CUSTOMFRAMEMIN                  9

//Document window junk
#define IDS_STANDARDDOCMIN                  128
#define IDS_CLIPBOARDFORMAT                 128
#define IDS_DOCUMENTCAPTION                 129
#define IDS_VERSIONMISMATCH                 130
#define IDS_FILELOADERROR                   131
#define IDS_FILESAVEERROR                   132
#define IDS_FILEDOESNOTEXIST                133
#define IDS_FILEOPENERROR                   134
#define IDS_UNKNOWNERROR                    135
#define IDS_STANDARDDOCMAX                  135
#define IDS_CUSTOMDOCMIN                    136

//These are specifically for the StatStrip
#define IDS_STANDARDSTATMESSAGEMIN          1024

#define IDS_EMPTYMESSAGE                    1024
#define IDS_READYMESSAGE                    1025

#define IDS_MENUMESSAGESYSTEM               1026
#define IDS_SYSMESSAGESIZE                  1027
#define IDS_SYSMESSAGEMOVE                  1028
#define IDS_SYSMESSAGEMINIMIZE              1029
#define IDS_SYSMESSAGEMAXIMIZE              1030
#define IDS_SYSMESSAGENEXTWINDOW            1031
#define IDS_SYSMESSAGEPREVWINDOW            1032
#define IDS_SYSMESSAGECLOSE                 1033
#define IDS_SYSMESSAGERESTORE               1034
#define IDS_SYSMESSAGETASKLIST              1035

#define IDS_MENUMESSAGEFILE                 1036
#define IDS_ITEMMESSAGEFILENEW              1037
#define IDS_ITEMMESSAGEFILEOPEN             1038
#define IDS_ITEMMESSAGEFILECLOSE            1039
#define IDS_ITEMMESSAGEFILESAVE             1040
#define IDS_ITEMMESSAGEFILESAVEAS           1041
#define IDS_ITEMMESSAGEFILEEXIT             1042

#define IDS_MENUMESSAGEEDIT                 1043
#define IDS_ITEMMESSAGEEDITUNDO             1044
#define IDS_ITEMMESSAGEEDITCUT              1045
#define IDS_ITEMMESSAGEEDITCOPY             1046
#define IDS_ITEMMESSAGEEDITPASTE            1047

#define IDS_MENUMESSAGEWINDOW               1048
#define IDS_ITEMMESSAGEWINDOWCASCADE        1049
#define IDS_ITEMMESSAGEWINDOWTILEHORZ       1050
#define IDS_ITEMMESSAGEWINDOWTILEVERT       1051
#define IDS_ITEMMESSAGEWINDOWICONS          1052

#define IDS_MENUMESSAGEHELP                 1053
#define IDS_ITEMMESSAGEHELPABOUT            1054

#define IDS_STANDARDSTATMESSAGEMAX          1054
#define IDS_CUSTOMSTATMESSAGEMIN            1055

#define CCHMESSAGEMAX                       128

#endif //_CLASSRES_H_


CSTATLIN.CPP

/*
 * CSTATLIN.C
 * Sample Code Class Libraries
 *
 * C++ wrapper class for a status line control
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

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

/*
 * CStatusLine::CStatusLine
 * CStatusLine::~CStatusLine
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the module we're loaded in.
 */

CStatusLine::CStatusLine(HINSTANCE hInst)
    : CWindow(hInst)
    {
    return;
    }

CStatusLine::~CStatusLine(void)
    {
    return;
    }

/*
 * CStatusLine::Init
 *
 * Purpose:
 *  Initializes a status line object by creating the control that it
 *  owns.
 *
 * Parameters:
 *  hWndParent      HWND of the parent window.  The status line is
 *                  created up from the bottom of this window,
 *                  spanning the entire width of the window.
 *  uID             UINT id of the control.
 *  cy              UINT height to create the control
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CStatusLine::Init(HWND hWndParent, UINT uID, UINT cy)
    {
    RECT        rc;

    GetClientRect(hWndParent, &rc);

    m_cy=cy;
    m_hWnd=CreateWindow(CLASS_STATSTRIP, TEXT("Ready")
        , WS_CHILD | WS_VISIBLE, rc.left, rc.top, rc.right-rc.left
        , cy , hWndParent, (HMENU)uID, m_hInst, 0L);

    return (NULL!=m_hWnd);
    }

/*
 * CStatusLine::OnSize
 *
 * Purpose:
 *  Handles parent resizing.  The owner of this window is responsible
 *  to call this function when it wants the status line to resize.  The
 *  status line will automatically occupy a lower strip of CYSTATSTRIP
 *  high on the bottom of the window.
 *
 * Parameters:
 *  hWndParent      HWND of the parent window to which we're resizing
 *
 * Return Value:
 *  None
 */

void CStatusLine::OnSize(HWND hWndParent)
    {
    RECT        rc;

    GetClientRect(hWndParent, &rc);

    SetWindowPos(m_hWnd, NULL, rc.left, rc.bottom-rc.top-CYSTATSTRIP
        , rc.right-rc.left, m_cy, SWP_NOZORDER);

    return;
    }

/*
 * CStatusLine::MessageSet
 *
 * Purpose:
 *  Sets a message in the status line explicitly with a string.
 *
 * Parameters:
 *  pszMsg          LPTSTR to the string to display.
 *
 * Return Value:
 *  None
 */

void CStatusLine::MessageSet(LPTSTR psz)
    {
    SetWindowText(m_hWnd, psz);
    return;
    }

/*
 * CStatusLine::MessageGet
 *
 * Purpose:
 *  Retrieves the current message from the status line
 *
 * Parameters:
 *  psz             LPTSTR in which to return the message.
 *  cchMax          UINT maximum number of characters to copy.
 *
 * Return Value:
 *  UINT            Number of characters copied.
 */

UINT CStatusLine::MessageGet(LPTSTR psz, UINT cchMax)
    {
    return GetWindowText(m_hWnd, psz, cchMax);
    }

/*
 * CStatusLine::MessageGetLength
 *
 * Purpose:
 *  Returns the length of the current message in the status line.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  UINT            Number of characters in the current message
 */

UINT CStatusLine::MessageGetLength(void)
    {
    return GetWindowTextLength(m_hWnd);
    }

/*
 * CStatusLine::FontSet
 *
 * Purpose:
 *  Changes the font in the status line.
 *
 * Parameters:
 *  hFont           HFONT of the font to use in the control.
 *  fRedraw         BOOL indicating if the control is to repaint or
 *                  not.
 *
 * Return Value:
 *  None
 */

void CStatusLine::FontSet(HFONT hFont, BOOL fRedraw)
    {
    SendMessage(m_hWnd, WM_SETFONT, (WPARAM)hFont, fRedraw);
    return;
    }

/*
 * CStatusLine::FontGet
 *
 * Purpose:
 *  Retrieves the handle of the current font used in the control.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HFONT           Handle to the current font.
 */

HFONT CStatusLine::FontGet(void)
    {
    return (HFONT)(UINT)SendMessage(m_hWnd, WM_GETFONT, 0, 0L);
    }

/*
 * CStatusLine::Enable
 *
 * Purpose:
 *  Enables or disables the status line window, graying the text if
 *  the control is disabled.
 *
 * Parameters:
 *  fEnable         BOOL specifying to enable (TRUE) or disable
 *
 * Return Value:
 *  None
 */

void CStatusLine::Enable(BOOL fEnable)
    {
    EnableWindow(m_hWnd, fEnable);
    return;
    }

/*
 * CStatusLine::MessageMap
 *
 * Purpose:
 *  Initializes a CStatusLine for automated processing of
 *  WM_MENUSELECT messages as well as setting up a list of messages
 *  that we can display using identifiers instead of string
 *  pointers.  See MenuSelect and MessageDisplay members.
 *
 * Parameters:
 *  hWnd            HWND of the window owning menus.
 *  hInst           HINSTANCE of the app from which to load resources
 *  uIDRMap         UINT identifying a resource mapping ID values to
 *                  string ID values.
 *  idsMin          UINT specifying the lowest string ID to load.
 *  idsMax          UINT specifying the hightest string ID to load.
 *  cchMax          UINT maximum string length.
 *  uIDPopupMin     UINT lowest ID to assign to popup menus.
 *  uIDPopupMax     UINT highest ID to assign to popup menus.
 *  uIDStatic       UINT ID for the quiescent message.
 *  uIDBlank        UINT ID for a blank message.
 *  uIDSysMenu      UINT ID for the system menu.
 *
 * Return Value:
 *  BOOL            TRUE if the function was successful, FALSE
 *                  otherwise.
 */

BOOL CStatusLine::MessageMap(HWND hWnd, HINSTANCE hInst, UINT uIDRMap
    , UINT idsMin, UINT idsMax, UINT cchMax, UINT uIDPopupMin
    , UINT uIDPopupMax, UINT uIDStatic, UINT uIDBlank
    , UINT uIDSysMenu)
    {
    //Initialize all the messages in the status line
    return StatStripMessageMap(m_hWnd, hWnd, hInst, uIDRMap, idsMin
        , idsMax, cchMax, uIDPopupMin, uIDPopupMax, uIDStatic
        , uIDBlank, uIDSysMenu);
    }

/*
 * CStatusLine::MenuSelect
 *
 * Purpose:
 *  Displays the appropriate message for whatever is in the
 *  parameters of a WM_MENUSELECT message.  This can only be called
 *  if StatStripMessageMap has already been called and must be used
 *  with the same menu the owner window had at the time of that call.
 *
 * Parameters:
 *  wParam          WPARAM of the WM_MENUSELECT message.
 *  lParam          LPARAM of the WM_MENUSELECT message.
 *
 * Return Value:
 *  None
 */

void CStatusLine::MenuSelect(WPARAM wParam, LPARAM lParam)
    {
    StatStripMenuSelect(m_hWnd, wParam, lParam);
    return;
    }

/*
 * CStatusLine::MessageDisplay
 *
 * Purpose:
 *  Displays the appropriate message for a given ID value.   This can
 *  only be called if StatStripMessageMap has already been called.
 *
 * Parameters:
 *  uID             UINT of the message to display.
 *
 * Return Value:
 *  None
 */

void CStatusLine::MessageDisplay(UINT uID)
    {
    StatStripMessageDisplay(m_hWnd, uID);
    return;
    }


CSTRTABL.CPP

/*
 * CSTRTABL.CPP
 * Sample Code Class Libraries
 *
 * Implementation of a string table handler.  The CStringTable
 * class hides details of storage from the user.  The strings might
 * be cached, or they might be loaded as necessary.  In either case,
 * we must know the number of strings so we know whether or not to
 * reload strings.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include <windows.h>
#include <malloc.h>
#include "classlib.h"

/*
 * CStringTable::CStringTable
 * CStringTable::~CStringTable
 *
 * Constructor Parameters:
 *  hInst           HANDLE to the application instance from which we
 *                  load strings.
 */

CStringTable::CStringTable(HINSTANCE hInst)
    {
    m_hInst=hInst;
    m_pszStrings=NULL;
    m_ppszTable=NULL;
    return;
    }

CStringTable::~CStringTable(void)
    {
    if (NULL!=m_pszStrings)
        free(m_pszStrings);

    if (NULL!=m_ppszTable)
        free(m_ppszTable);

    return;
    }

/*
 * CStringTable::FInit
 *
 * Purpose:
 *  Initialization function for a StringTable that is prone to
 *  failure.  If this fails then the caller is responsible for
 *  guaranteeing that the destructor is called quickly.
 *
 * Parameters:
 *  idsMin          UINT first identifier in the stringtable
 *  idsMax          UINT last identifier in the stringtable.
 *
 * Return Value:
 *  BOOL            TRUE if the function is successful, FALSE
 *                  otherwise.
 */

BOOL CStringTable::FInit(UINT idsMin, UINT idsMax)
    {
    UINT        i;
    UINT        cch;
    UINT        cchUsed=0;
    LPTSTR      psz;

    m_idsMin=idsMin;
    m_idsMax=idsMax;
    m_cStrings=(idsMax-idsMin+1);

    //Allocate space for the pointer table.
    m_ppszTable=(LPTSTR *)malloc(sizeof(LPTSTR)*m_cStrings);

    if (NULL==m_ppszTable)
        return FALSE;

    //Allocate enough memory for cStrings of CCHSTRINGMAX.
    m_pszStrings=(LPTSTR)malloc(m_cStrings * CCHSTRINGMAX * sizeof(TCHAR));

    if (NULL==m_pszStrings)
        {
        free(m_ppszTable);
        m_ppszTable=NULL;
        return FALSE;
        }

    /*
     * Load the strings:  we load each string in turn into psz,
     * store the string pointer into the table and increment psz
     * to the next positions.
     */

    psz=m_pszStrings;

    for (i=idsMin; i <= idsMax; i++)
        {
        m_ppszTable[i-idsMin]=psz;
        cch=LoadString(m_hInst, i, psz, CCHSTRINGMAX-1);

        //Account for a null terminator
        psz+=cch+1;
        cchUsed+=cch;
        }

    return TRUE;
    }

/*
 * CStringTable::operator[]
 *
 * Purpose:
 *  Returns a pointer to the requested string in the stringtable or
 *  NULL if the specified string does not exist.
 */

const LPTSTR CStringTable::operator[](const UINT uID) const
    {
    if (uID < m_idsMin || uID > m_idsMax)
        return NULL;

    return (const LPTSTR)m_ppszTable[uID-m_idsMin];
    }


CTOOLBAR.CPP

/*
 * CTOOLBAR.CPP
 * Sample Code Class Libraries
 *
 * Implementation of the CToolBar class
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

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

/*
 * CToolBar::CToolBar
 * CToolBar::~CToolBar
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the module we're loaded in.
 */

CToolBar::CToolBar(HINSTANCE hInst)
    : CWindow(hInst)
    {
    return;
    }

CToolBar::~CToolBar(void)
    {
    return;
    }

/*
 * CToolBar::Init
 *
 * Purpose:
 *  Initializes a toolbar object by creating the control that it
 *  owns.
 *
 * Parameters:
 *  hWndParent      HWND of the parent window.  The toolbar is
 *                  created up from the bottom of this window,
 *                  spanning the entire width of the window.
 *  uID             UINT id of the control.
 *  cy              UINT height to create the control
 *
 * Return Value:
 *  BOOL            TRUE if the function succeeded, FALSE otherwise.
 */

BOOL CToolBar::Init(HWND hWndParent, UINT uID, UINT cy)
    {
    RECT            rc;

    /*
     * Note that the class is already registered since we live in a
     * DLL and that DLL will be loaded if anyone is using this class
     * library.
     */

    GetClientRect(hWndParent, &rc);
    m_cyBar=cy;

    m_hWnd=CreateWindow(CLASS_GIZMOBAR, TEXT("Wooley")
        , WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS, rc.left, rc.top
        , rc.right-rc.left, m_cyBar, hWndParent, (HMENU)uID, m_hInst
        , 0L);

    return (NULL!=m_hWnd);
    }

/*
 * CToolBar::OnSize
 *
 * Purpose:
 *  Handles parent resizing.  The owner of this window is responsible
 *  to call this function when it wants the toolbar to resize.  The
 *  toolbar will automatically occupy a strip of the appropriate
 *  height along the top of the window.
 *
 * Parameters:
 *  hWndParent      HWND of the parent window to which we're resizing
 *
 * Return Value:
 *  None
 */

void CToolBar::OnSize(HWND hWndParent)
    {
    RECT        rc;

    GetClientRect(hWndParent, &rc);

    SetWindowPos(m_hWnd, NULL, rc.left, rc.top, rc.right-rc.left
        , m_cyBar, SWP_NOZORDER);

    return;
    }

/*
 * CToolBar::FontSet
 *
 * Purpose:
 *  Changes the font in the StatStrip.
 *
 * Parameters:
 *  hFont           HFONT of the font to use in the control.
 *  fRedraw         BOOL indicating if the control is to repaint
 *
 * Return Value:
 *  None
 */

void CToolBar::FontSet(HFONT hFont, BOOL fRedraw)
    {
    SendMessage((UINT)m_hWnd, WM_SETFONT, (WPARAM)hFont, fRedraw);
    return;
    }

/*
 * CToolBar::FontGet
 *
 * Purpose:
 *  Retrieves the handle of the current font used in the control.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HFONT           Handle to the current font.
 */

HFONT CToolBar::FontGet(void)
    {
    return (HFONT)(UINT)SendMessage((UINT)m_hWnd, WM_GETFONT, 0, 0L);
    }

/*
 * CToolBar::Enable
 *
 * Purpose:
 *  Enables or disables the StatStrip window, graying the text if
 *  the control is disabled.
 *
 * Parameters:
 *  fEnable         BOOL specifying to enable (TRUE) or disable
 *
 * Return Value:
 *  None
 */

void CToolBar::Enable(BOOL fEnable)
    {
    EnableWindow(m_hWnd, fEnable);
    return;
    }

/*
 * CToolBar::HwndAssociateSet
 *
 * Purpose:
 *  Changes the associate window of a toolbar.
 *
 * Parameters:
 *  hWndNew         HWND of new associate.
 *
 * Return Value:
 *  HWND            Handle of previous associate.
 */

HWND CToolBar::HwndAssociateSet(HWND hWndNew)
    {
    return GBHwndAssociateSet(m_hWnd, hWndNew);
    }

/*
 * CToolBar::HwndAssociateGet
 *
 * Purpose:
 *  Retrieves the associate window of a toolbar
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HWND            Handle of current associate.
 */

HWND CToolBar::HwndAssociateGet(void)
    {
    return GBHwndAssociateGet(m_hWnd);
    }

/*
 * CToolBar::Add
 *
 * Purpose:
 *  Creates a new tool on the toolbar.  Subsequent operations
 *  should be done using the identifier, uID, for this tool.
 *
 * Parameters:
 *  iType           UINT type of the tool to create.
 *  iTool           UINT position (zero-based) for the tool.
 *  uID             UINT identifier for WM_COMMAND from this tool.
 *  dx, dy          UINT dimensions of the tool.
 *  pszText         LPTSTR initial text for edit, list, combo, text
 *  hBitmap         HBITMAP for tools of the button types (COMMAND
 *                  or ATTRIBUTE) specifies a source bitmap from
 *                  which the button image is taken.
 *  iImage          UINT index into hBitmap for the button image
 *  uState          UINT initial state of the tool.
 *
 * Return Value:
 *  BOOL            TRUE if creation succeeded, FALSE otherwise.
 */

BOOL CToolBar::Add(UINT iType, UINT iTool, UINT uID, UINT dx
    , UINT dy, LPTSTR pszText, HBITMAP hBmp, UINT iImage, UINT uState)
    {
    return GBGizmoAdd(m_hWnd, iType, iTool, uID, dx, dy
        , pszText, hBmp, iImage, uState);
    }

/*
 * CToolBar::Remove
 *
 * Purpose:
 *  Removes an existing tool from the toolbar.
 *
 * Parameters:
 *  uID             UINT identifier for this tool.
 *
 * Return Value:
 *  BOOL            TRUE if deletion succeeded, FALSE otherwise.
 */

BOOL CToolBar::Remove(UINT uID)
    {
    return GBGizmoRemove(m_hWnd, uID);
    }

/*
 * CToolBar::SendMessage
 *
 * Purpose:
 *  Implements the equivalent of SendMessage to a tool in the
 *  toolbar.  Separators, command buttons, and attribute buttons
 *  do not accept messages.
 *
 * Parameters:
 *  uID             UINT identifier of the tool to affect.
 *  iMsg            UINT message to send.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LONG            Return value from the message.  0L if the
 *                  tool does not accept messages.
 */

LONG CToolBar::SendMessage(UINT uID, UINT iMsg, WPARAM wParam
    , LPARAM lParam)
    {
    return GBGizmoSendMessage(m_hWnd, uID, iMsg, wParam, lParam);
    }

/*
 * CToolBar::Show
 *
 * Purpose:
 *  Shows or hides a control, adjusting the positions of all others
 *  to make room for or reuse the space for this control.
 *
 * Parameters:
 *  uID             UINT identifier of the tool to affect.
 *  fShow           BOOL TRUE to show the tool, FALSE to hide it.
 *
 * Return Value:
 *  BOOL            TRUE if the function was successful, FALSE
 *                  otherwise.
 */

BOOL CToolBar::Show(UINT uID, BOOL fShow)
    {
    return GBGizmoShow(m_hWnd, uID, fShow);
    }

/*
 * CToolBar::Enable
 *
 * Purpose:
 *  Enables or disables a control on the toolbar.
 *
 * Parameters:
 *  uID             UINT identifier of the tool to affect.
 *  fEnable         BOOL TRUE to enable the tool, FALSE otherwise.
 *
 * Return Value:
 *  BOOL            TRUE if the tool was previously disabled, FALSE
 *                  otherwise.
 */

BOOL CToolBar::Enable(UINT uID, BOOL fEnable)
    {
    return GBGizmoEnable(m_hWnd, uID, fEnable);
    }

/*
 * CToolBar::Check
 *
 * Purpose:
 *  Checks or unchecks an attribute button in the toolbar.  If the
 *  tool is part of a group of mutually exclusive attributes, then
 *  other tools are unchecked when this one is checked.  If this is
 *  the only one checked in these circumstances, this function is
 *  a NOP.
 *
 * Parameters:
 *  uID             UINT identifier of the tool to affect.
 *  fCheck          BOOL TRUE to check this tool, FALSE to uncheck.
 *
 * Return Value:
 *  BOOL            TRUE if the change took place.  FALSE otherwise.
 */

BOOL CToolBar::Check(UINT uID, BOOL fCheck)
    {
    return GBGizmoCheck(m_hWnd, uID, fCheck);
    }

/*
 * CToolBar::FocusSet
 *
 * Purpose:
 *  Sets the focus to a partuclar tool in the toolbar if that tool
 *  can accept the focus.  Separators, attribute buttons, text,
 *  and command buttons cannot have the focus.
 *
 * Parameters:
 *  uID             UINT identifier of the tool to affect.
 *
 * Return Value:
 *  BOOL            TRUE if the focus was set.  FALSE otherwise,
 *                  such as when uID identifies a control that
 *                  cannot have focus.
 *
 */

UINT CToolBar::FocusSet(UINT uID)
    {
    return GBGizmoFocusSet(m_hWnd, uID);
    }

/*
 * CToolBar::Exist
 *
 * Purpose:
 *  Determines if a tool of a given identifier exists.
 *
 * Parameters:
 *  uID             UINT identifier to verify.
 *
 * Return Value:
 *  BOOL            TRUE if the tool exists, FALSE otherwise.
 */

BOOL CToolBar::Exist(UINT uID)
    {
    return GBGizmoExist(m_hWnd, uID);
    }

/*
 * CToolBar::TypeGet
 *
 * Purpose:
 *  Returns the type of the tool specified by the given identifer.
 *
 * Parameters:
 *  uID             UINT identifier to find.
 *
 * Return Value:
 *  int             A GIZMOTYPE_* value if the function is
 *                  successful, otherwise -1.
 */

int CToolBar::TypeGet(UINT uID)
    {
    return GBGizmoTypeGet(m_hWnd, uID);
    }

/*
 * CToolBar::DataSet
 * CToolBar::DataGet
 *
 * Purpose:
 *  Sets or retrieves an extra DWORD value associated with the given
 *  tool.  Applications can store any information here they please.
 *
 * Parameters:
 *  uID             UINT identifier of the tool.
 *  dwData          (Set only) DWORD data to store with the tool.
 *
 * Return Value:
 *  DWORD           Set:  Previous value
 *                  Get:  Current value
 */

DWORD CToolBar::DataSet(UINT uID, DWORD dwData)
    {
    return GBGizmoDataSet(m_hWnd, uID, dwData);
    }

DWORD CToolBar::DataGet(UINT uID)
    {
    return GBGizmoDataGet(m_hWnd, uID);
    }

/*
 * CToolBar::NotifySet
 * CToolBar::NotifyGet
 *
 * Purpose:
 *  Sets or retrieves the notify status of a tool.  If notify is
 *  FALSE, the no WM_COMMAND messages are sent from the toolbar
 *  to the parent window when this tool is used.
 *
 * Parameters:
 *  uID             UINT identifier of the tool.
 *  fNotify         (Set only) BOOL new notify status to set.
 *
 * Return Value:
 *  BOOL            Set:  Previous value of the notify flag.
 *                  Get:  Current value of the notify flag.
 */

BOOL CToolBar::NotifySet(UINT uID, BOOL fNotify)
    {
    return GBGizmoNotifySet(m_hWnd, uID, fNotify);
    }

BOOL CToolBar::NotifyGet(UINT uID)
    {
    return GBGizmoNotifyGet(m_hWnd, uID);
    }

/*
 * CToolBar::TextSet
 * CToolBar::TextGet
 *
 * Purpose:
 *  Retrieves or sets text in a toolbar control.  Separators,
 *  command buttons, and attribute buttons are not affected by
 *  this call.
 *
 * Parameters:
 *  uID             UINT identifying the tool.
 *  psz             LPTSTR pointing to a buffer to receive the text.
 *  cch             (Get only) UINT maximum number of chars to copy
 *                  to psz.
 *
 * Return Value:
 *  int             Number of characters copied to psz.
 */

void CToolBar::TextSet(UINT uID, LPTSTR psz)
    {
    GBGizmoTextSet(m_hWnd, uID, psz);
    return;
    }

int CToolBar::TextGet(UINT uID, LPTSTR psz, UINT cch)
    {
    return GBGizmoTextGet(m_hWnd, uID, psz, cch);
    }

/*
 * CToolBar::IntSet
 * CToolBar::IntGet
 *
 * Purpose:
 *  Retrieves or sets an integer in a toolbar control.  Separators,
 *  command buttons, and attribute buttons are not affected by this
 *  call.
 *
 * Parameters:
 *  uID             UINT identifying the tool.
 *
 *  Get Parameters:
 *  pfTrans         BOOL * in which the success of the function is
 *                  returned.
 *  fSigned         BOOL TRUE to indicate if the value is signed.
 *
 *  Set Parameters:
 *  u               UINT value to set in the tool.
 *  fSigned         BOOL TRUE to indicate if the value is signed.
 *
 * Return Value:
 *  UINT            Integer translation of the tool's text.
 */

void CToolBar::IntSet(UINT uID, int i, BOOL fSigned)
    {
    GBGizmoIntSet(m_hWnd, uID, i, fSigned);
    return;
    }

UINT CToolBar::IntGet(UINT uID, BOOL *pfTrans, BOOL fSigned)
    {
    return GBGizmoIntGet(m_hWnd, uID, pfTrans, fSigned);
    }


CWINDOW.CPP

/*
 * CWINDOW.CPP
 * Sample Code Class Libraries
 *
 * Implementation of a simple CWindow class.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

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

/*
 * CWindow::CWindow
 * CWindow::~CWindow
 *
 * Constructor Parameters:
 *  hInst           HINSTANCE of the task owning us.
 */

CWindow::CWindow(HINSTANCE hInst)
    {
    m_hInst=hInst;
    m_hWnd =NULL;
    return;
    }

CWindow::~CWindow(void)
    {
    if (IsWindow(m_hWnd))
        DestroyWindow(m_hWnd);

    return;
    }

/*
 * CWindow::Window
 *
 * Purpose:
 *  Returns the window handle associated with this object.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HWND            Window handle for this object
 */

HWND CWindow::Window(void)
    {
    return m_hWnd;
    }


DOCWIN.CPP

/*
 * DOCWIN.CPP
 * Sample Code Class Libraries
 *
 * Window procedure for document windows.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

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

/*
 * DocumentWndProc
 *
 * Purpose:
 *  Document window class that contains a polyline but does not own
 *  things like the gizmobar.
 *
 *  We handle all commands from menus and gizmobars as well as
 *  notifications from the polyline itself.  The frame window
 *  just makes sure that commands and such are dispatched here
 *  as necessary, especially in an MDI case.
 */

LRESULT APIENTRY DocumentWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCDocument      pDoc;
    BOOL            fOK=FALSE;
    LPARAM          lTemp;
    LRESULT         lRet;

    pDoc=(PCDocument)GetWindowLong(hWnd, DOCWL_STRUCTURE);

    if (NULL!=pDoc)
        {
        //Call the hook and return its value if it tells us to.
        if (pDoc->FMessageHook(hWnd, iMsg, wParam, lParam, &lRet))
            return lRet;
        }

    switch (iMsg)
        {
        case WM_CREATE:
            /*
             * Save our object pointer with this window.  We have to
             * do this inside this message since we don't get the
             * MDICREATESTRUCT anywhere else.
             */

            lTemp=(LPARAM)((LPCREATESTRUCT)lParam)->lpCreateParams;
            pDoc=(PCDocument)(((LPMDICREATESTRUCT)lTemp)->lParam);
            SetWindowLong(hWnd, DOCWL_STRUCTURE, (LONG)pDoc);
            break;

        case WM_CLOSE:
            //Tell our main window to close us
            if (NULL!=pDoc->m_pAdv)
                pDoc->m_pAdv->OnCloseRequest(pDoc);

            break;

        case WM_QUERYENDSESSION:
            return TRUE;    //Right now we can always close.

        case DOCM_PDOCUMENT:
            //Return our object pointer
            return (LONG)pDoc;

        case WM_MDIACTIVATE:
            /*
             * NEWMDIACTIVE isolates wParam/lParam differences
             * between Win16 and Win32.
             */
            if (0!=NEWMDIACTIVE && NULL!=pDoc->m_pAdv)
                pDoc->m_pAdv->OnActivate(pDoc);

            break;

        case WM_MENUSELECT:
            {
            PCStatStrip     pSS;

            pSS=pDoc->m_pFR->GetStatusControl();

            if (NULL!=pSS)
                pSS->MenuSelect(wParam, lParam);

            }
            break;

        default:
            return DEFDOCUMENTPROC(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }


FRAMEWIN.CPP

/*
 * FRAMEWIN.CPP
 * Sample Code Class Libraries
 *
 * Frame window procedure used with the CFrame class as well as
 * the implementation of CDocumentAdviseSink that the frame is
 * interested in.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

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

/*
 * FrameWndProc
 *
 * Purpose:
 *  Frame window class procedure that allows a derivation of these
 *  classes to hook and process any messages desired.  Otherwise this
 *  handles standard commands as well as the status line and menus.
 */

LRESULT APIENTRY FrameWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PCFrame         pFR;
    PCClient        pCL=NULL;
    WORD            dx, dy;
    LRESULT         lRet;

    pFR=(PCFrame)GetWindowLong(hWnd, FRAMEWL_STRUCTURE);

    if (NULL!=pFR)
        {
        //Call the hook and return its value if it tells us to.
        if (pFR->FMessageHook(hWnd, iMsg, wParam, lParam, &lRet))
            return lRet;

        //Otherwise copy the CClient pointer and continue.
        pCL=pFR->m_pCL;
        }

    switch (iMsg)
        {
        case WM_NCCREATE:
            pFR=(PCFrame)((LPCREATESTRUCT)lParam)->lpCreateParams;

            SetWindowLong(hWnd, FRAMEWL_STRUCTURE, (LONG)pFR);
            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        case WM_DESTROY:
            PostQuitMessage(0);
            break;

        case WM_CLOSE:
            //Don't close if any children cancel the operation.
            pFR->m_fClosing=TRUE;

            if (!pCL->QueryCloseAllDocuments(TRUE))
                {
                pFR->m_fClosing=FALSE;
                break;
                }

            //Destroy everything.
            DestroyWindow(hWnd);
            break;

        case WM_QUERYENDSESSION:
            return pCL->QueryCloseAllDocuments(TRUE);

        case WM_ERASEBKGND:
            //Our client area is never visible, so don't flash
            return TRUE;

        case WM_SIZE:
            pFR->m_fSizing=TRUE;
            dx=LOWORD(lParam);
            dy=HIWORD(lParam);

            //Change the gizmobar and StatStrip widths to match
            pFR->m_pGB->OnSize(hWnd);
            pFR->m_pSS->OnSize(hWnd);

            pCL->OnSize(0, pFR->m_cyBar, dx
                , dy-pFR->m_cyBar-CYSTATSTRIP);

            pFR->m_fSizing=FALSE;
            break;

        case WM_INITMENUPOPUP:
            //Skip the system menu
            if (TRUE==(BOOL)HIWORD(lParam))
                break;

            //Go handle graying menu items appropriately.
            pFR->UpdateMenus((HMENU)wParam, (UINT)LOWORD(lParam));
            break;

        case WM_MENUSELECT:
            pFR->m_pSS->MenuSelect(wParam, lParam);
            break;

        case WM_DESTROYCLIPBOARD:
            //When we empty contents, disable the paste button
            pFR->m_pGB->Enable(IDM_EDITPASTE, FALSE);
            break;

        case WM_COMMAND:
            return pFR->OnCommand(hWnd, wParam, lParam);

        default:
            //Don't try using CClient unless we have one.
            if (NULL!=pCL)
                {
                return pCL->DefaultFrameProc(hWnd, iMsg, wParam
                    , lParam);
                }
            else
                return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }

/*
 * AboutProc
 *
 * Purpose:
 *  Dialog procedure for the omnipresent About box.
 *
 * Parameters:
 *  The standard.
 *
 * Return Value:
 *  The value to be returned through the DialogBox call that
 *  created the dialog.
 *
 */

BOOL APIENTRY AboutProc(HWND hDlg, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    switch (iMsg)
        {
        case WM_INITDIALOG:
            return TRUE;

        case WM_COMMAND:
            switch (LOWORD(wParam))
                {
                case IDOK:
                    EndDialog(hDlg, TRUE);
                }
            break;
        }
    return FALSE;
    }

//CDocumentAdviseSink implementation for the frame window.

/*
 * CDocumentAdviseSink::CDocumentAdviseSink
 *
 * Purpose:
 *  Constructs an advise object storing a pointer for the caller's
 *  use.
 *
 * Parameters:
 *  pv              LPVOID to store in this object
 */

CDocumentAdviseSink::CDocumentAdviseSink(LPVOID pv)
    {
    m_pv=pv;
    return;
    }

/*
 * CDocumentAdviseSink::OnDataChange
 *
 * Purpose:
 *  Informs the frame that document data changed so we may need to
 *  update some of our UI.
 *
 * Parameters:
 *  pDoc            PCDocument notifying us.
 *
 * Return Value:
 *  None
 */

void CDocumentAdviseSink::OnDataChange(PCDocument pDoc)
    {
    PCFrame     pFR=(PCFrame)m_pv;

    pFR->OnDocumentDataChange(pDoc);
    return;
    }

/*
 * CDocumentAdviseSink::OnCloseRequest
 *
 * Purpose:
 *  A document was closed by the user so we have to effect it here.
 *
 * Parameters:
 *  pDoc            PCDocument notifying us.
 *
 * Return Value:
 *  None
 */

void CDocumentAdviseSink::OnCloseRequest(PCDocument pDoc)
    {
    PCFrame     pFR=(PCFrame)m_pv;

    //Ask if you want to save.
    if (!pFR->m_pCL->FCleanVerify(pDoc))
        return;

    pFR->m_pCL->CloseDocument(pDoc);
    pFR->UpdateGizmos();

    return;
    }

/*
 * CDocumentAdviseSink::OnSizeChange
 *
 * Purpose:
 *  Notifies us that the document was resized.
 *
 * Parameters:
 *  pDoc            PCDocument notifying us.
 *  pRect           LPRECT of the new document window size.
 *
 * Return Value:
 *  None
 */

void CDocumentAdviseSink::OnSizeChange(PCDocument pDoc, LPRECT pRect)
    {
    PCFrame     pFR=(PCFrame)m_pv;
   #ifndef MDI
    RECT        rc;
    DWORD       dwStyle;
   #endif

    //MDI, do nothing.  In SDI, resize the frame to match
   #ifdef MDI
    return;
   #else
    //If we caused this from our own WM_SIZE, skip out.
    if (pFR->m_fSizing)
        return;

    //Get the document's screen rectangle.
    GetWindowRect(pDoc->Window(), &rc);

    //Adjust for GizmoBar
    rc.top-=pFR->m_cyBar;

    //Adjust for StatStrip
    rc.bottom+=CYSTATSTRIP;

    //Adjust for our window
    dwStyle=GetWindowLong(pFR->m_hWnd, GWL_STYLE);
    AdjustWindowRect(&rc, dwStyle, TRUE);

    //This causes sizing of all other tools.
    SetWindowPos(pFR->m_hWnd, NULL, rc.left, rc.top, rc.right-rc.left
        , rc.bottom-rc.top, SWP_NOZORDER | SWP_NOACTIVATE);

    return;
   #endif
    }

/*
 * CDocumentAdviseSink::OnCaptionChange
 *
 * Purpose:
 *  Notifies us that we should update the caption bars used on this
 *  document.
 *
 * Parameters:
 *  pDoc            PCDocument notifying us.
 *
 * Return Value:
 *  None
 */

void CDocumentAdviseSink::OnCaptionChange(PCDocument pDoc)
    {
    PCFrame     pFR=(PCFrame)m_pv;

   #ifdef MDI
    pFR->WindowTitleSet(pDoc, TRUE);
   #else
    pFR->WindowTitleSet(pDoc, FALSE);
   #endif

    return;
    }

/*
 * CDocumentAdviseSink::OnActivate
 *
 * Purpose:
 *  Notifies us that we should update any user interface that
 *  depends on the active document.
 *
 * Parameters:
 *  pDoc            PCDocument notifying us.
 *
 * Return Value:
 *  None
 */

void CDocumentAdviseSink::OnActivate(PCDocument pDoc)
    {
    PCFrame     pFR=(PCFrame)m_pv;

    pFR->OnDocumentActivate(pDoc);
    return;
    }


SKEL.CPP

/*
 * SKEL.CPP
 * CLASSLIB Skeleton
 *
 * Skeleton application using CLASSLIB which only needs a WinMain
 * and a number of standard resources.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include <windows.h>
#include <classlib.h>

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.  Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 */

int PASCAL WinMain (HINSTANCE hInst, HINSTANCE hPrev
    , LPSTR pszCmdLine, int nCmdShow)
    {
    PCFrame         pFR;
    FRAMEINIT       fi;
    WPARAM          wRet;

    //Attempt to allocate and initialize the application
    pFR=new CFrame(hInst, hPrev, pszCmdLine, nCmdShow);

    if (NULL==pFR)
        return 0;

    fi.idsMin=IDS_STANDARDFRAMEMIN;
    fi.idsMax=IDS_STANDARDFRAMEMAX;
    fi.idsStatMin=IDS_STANDARDSTATMESSAGEMIN;
    fi.idsStatMax=IDS_STANDARDSTATMESSAGEMAX;
    fi.idStatMenuMin=ID_MENUFILE;
    fi.idStatMenuMax=ID_MENUHELP;
    fi.iPosWindowMenu=WINDOW_MENU;
    fi.cMenus=CMENUS;

    fi.x=CW_USEDEFAULT;
    fi.y=CW_USEDEFAULT;
    fi.cx=CW_USEDEFAULT;
    fi.cy=CW_USEDEFAULT;

    //If we can initialize pFR, start chugging messages
    if (pFR->Init(&fi))
        wRet=pFR->MessageLoop();

    delete pFR;
    return wRet;
    }


BOOK1632.H

/*
 * BOOK1632.H
 *
 * Macros and other definitions that assist in porting between Win16
 * and Win32 applications.  Defines WIN32 to enable 32-bit versions.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  INTERNET>kraigb@microsoft.com
 */

#ifndef _BOOK1632_H_
#define _BOOK1632_H_

#ifdef WIN32

#ifndef COMMANDPARAMS
#define COMMANDPARAMS(wID, wCode, hWndMsg)                          \
    WORD        wID     = LOWORD(wParam);                           \
    WORD        wCode   = HIWORD(wParam);                           \
    HWND        hWndMsg = (HWND)(UINT)lParam;
#endif  //COMMANDPARAMS

#ifndef SendCommand
#define SendCommand(hWnd, wID, wCode, hControl)                     \
            SendMessage(hWnd, WM_COMMAND, MAKEWPARAM(wID, wCode)    \
                        , (LPARAM)hControl)
#endif  //SendCommand

#ifndef MENUSELECTPARAMS
#define MENUSELECTPARAMS(wID, wFlags, hMenu)                        \
    WORD        wID     = LOWORD(wParam);                           \
    WORD        wFlags  = HIWORD(wParam);                           \
    HMENU       hMenu   = (HMENU)lParam;
#endif  //MENUSELECTPARAMS

#ifndef SendMenuSelect
#define SendMenuSelect(hWnd, wID, wFlags, hMenu)                    \
            SendMessage(hWnd, WM_MENUSELECT                         \
                , MAKEWPARAM((WORD)wID, (WORD)wFlags), (LPARAM)hMenu)

#endif  //SendMenuSelect

#ifndef SendScrollPosition
#define SendScrollPosition(hWnd, iMsg, iPos)                        \
            SendMessage(hWnd, iMsg, MAKEWPARAM(SB_THUMBPOSITION, iPos), 0)
#endif //SendScrollPosition

#ifndef ScrollThumbPosition
#define ScrollThumbPosition(w, l) HIWORD(w)
#endif //ScrollThumbPosition

#ifndef GETWINDOWINSTANCE
#define GETWINDOWINSTANCE(h) (HINSTANCE)GetWindowLong(h, GWL_HINSTANCE)
#endif  //GETWINDOWINSTANCE

#ifndef GETWINDOWID
#define GETWINDOWID(h) (UINT)GetWindowLong(h, GWW_ID)
#endif  //GETWINDOWID

#ifndef POINTFROMLPARAM
#define POINTFROMLPARAM(p, l) {p.x=(LONG)(SHORT)LOWORD(l); \
                               p.y=(LONG)(SHORT)HIWORD(l);}
#endif  //POINTEFROMLPARAM

#ifndef MDIREFRESHMENU
#define MDIREFRESHMENU(h) SendMessage(h, WM_MDIREFRESHMENU, 0, 0L)
#endif  //MDIREFRESHMENU

//Activated child window in WM_MDIACTIVATE
#ifndef NEWMDIACTIVE
#define NEWMDIACTIVE lParam
#endif  //NEWMDIACTIVE

#ifndef UNICODETOANSI
#ifdef UNICODE
#define UNICODETOANSI(s, d, cch) WideCharToMultiByte(CP_ACP \
            , 0, s, -1, d, cch, NULL, NULL)
#else  //ANSI
#define UNICODETOANSI(s, d, cch) lstrcpy(d, (LPTSTR)s)
#endif
#endif //UNICODETOANSI

//****END WIN32

#else

//****START !WIN32

#ifndef POINTS
typedef POINT POINTS;
#endif  //POINTS

#ifndef LPPOINTS
typedef POINTS FAR *LPPOINTS;
#endif  //LPPOINTS

#ifndef MAKEPOINTS
#define MAKEPOINTS MAKEPOINT
#endif  //MAKEPOINTS

#ifndef COMMANDPARAMS
#define COMMANDPARAMS(wID, wCode, hWndMsg)                          \
    WORD        wID     = LOWORD(wParam);                           \
    WORD        wCode   = HIWORD(lParam);                           \
    HWND        hWndMsg = (HWND)(UINT)lParam;
#endif  //COMMANDPARAMS

#ifndef SendCommand
#define SendCommand(hWnd, wID, wCode, hControl)                     \
            SendMessage(hWnd, WM_COMMAND, wID                       \
                        , MAKELONG(hControl, wCode))
#endif  //SendCommand

#ifndef MENUSELECTPARAMS
#define MENUSELECTPARAMS(wID, wFlags, hMenu)                        \
    WORD        wID     = LOWORD(wParam);                           \
    WORD        wFlags  = LOWORD(lParam);                           \
    HMENU       hMenu   = (HMENU)HIWORD(lParam);
#endif  //MENUSELECTPARAMS

#ifndef SendMenuSelect
#define SendMenuSelect(hWnd, wID, wFlags, hMenu)                    \
            SendMessage(hWnd, WM_MENUSELECT, wID                    \
                        , MAKELONG((WORD)wFlags, (WORD)hMenu))
#endif  //SendMenuSelect

#ifndef SendScrollPosition
#define SendScrollPosition(hWnd, iMsg, iPos)                        \
            SendMessage(hWnd, iMsg, SB_THUMBPOSITION, MAKELONG(iPos, 0))
#endif //Send ScrollPosition

#ifndef ScrollThumbPosition
#define ScrollThumbPosition(w, l) LOWORD(l)
#endif //ScrollThumbPosition

#ifndef GETWINDOWINSTANCE
#define GETWINDOWINSTANCE(h) (HINSTANCE)GetWindowWord(h, GWW_HINSTANCE)
#endif  //GETWINDOWINSTANCE

#ifndef GETWINDOWID
#define GETWINDOWID(h) (UINT)GetWindowWord(h, GWW_ID)
#endif  //GETWINDOWID

#ifndef POINTFROMLPARAM
#define POINTFROMLPARAM(p, l) {p.x=LOWORD(l); p.y=HIWORD(l);}
#endif  //POINTEFROMLPARAM

#ifndef MDIREFRESHMENU
#define MDIREFRESHMENU(h) SendMessage(h, WM_MDISETMENU, TRUE, 0L)
#endif  //MDIREFRESHMENU

//Activated child window in WM_MDIACTIVATE
#ifndef NEWMDIACTIVE
#define NEWMDIACTIVE wParam
#endif  //NEWMDIACTIVE

//Things not present in Win3.1 SDK but present in Win32 SDK.

#ifndef APIENTRY
#define APIENTRY __export FAR PASCAL
#endif  //APIENTRY

#ifndef USHORT
typedef unsigned short USHORT;
#endif  //USHORT

//These are so we can write ANSI/UNICODE portable code.
#ifndef TCHAR
typedef char TCHAR;
#endif  //TCHAR

#ifndef LPTSTR
typedef TCHAR *LPTSTR;
#endif  //LPTSTR

#ifndef LPCTSTR
typedef const TCHAR *LPCTSTR;
#endif  //LPCTSTR

#ifndef _tcsncpy
#define _tcsncpy strncpy
#endif  //_tcsncpy

#ifndef lstrcpyA
#define lstrcpyA lstrcpy
#endif  //lstrcpyA

#ifndef lstrcmpiA
#define lstrcmpiA lstrcmpi
#endif  //lstrcmpiA

#ifndef TEXT
#define TEXT(a) a
#endif  //TEXT

#ifndef DeleteFile
#define DeleteFile(f)                       \
            {                               \
            OFSTRUCT    of;                 \
            OpenFile(f, &of, OF_DELETE);    \
            }
#endif  //DeleteFile

#ifndef UNICODETOANSI
#define UNICODETOANSI(s, d, cch) lstrcpy(d, (LPTSTR)s)
#endif  //UNICODETOANSI

#ifndef SetForegroundWindow
#define SetForegroundWindow(w) SetActiveWindow(w)
#endif  //SetForegroundWindow

#endif  //!WIN32

//These definitions we need regardless of Win16 or Win32

typedef struct
    {
    short   left;
    short   top;
    short   right;
    short   bottom;
    } RECTS, FAR *LPRECTS;

#define RECTSTORECT(rs, r) {(r).left=(rs).left;(r).top=(rs).top; \
    (r).right=(rs).right;(r).bottom=(rs).bottom;};

#define RECTTORECTS(r, rs) {(rs).left=(short)(r).left;   \
    (rs).top=(short)(r).top;(rs).right=(short)(r).right; \
    (rs).bottom=(short)(r).bottom;};

#endif  //_BOOK1632_H_


BOOKGUID.H

/*
 * BOOKGUID.H
 *
 * CLSIDs and IIDs defined for the Windows Object book.  This file
 * centralizes them all so we can keep track of which ones are used.
 * The file also includes a number of useful macros that all samples
 * might be interested in as well as including BOOK1632.H and DEBUG.H.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _BOOKGUID_H_
#define _BOOKGUID_H_

#include <book1632.h>
#include <dbgout.h>

#ifdef INITGUIDS
#include <initguid.h>
#endif //INITGUIDS

//Types that OLE2.H et. al. leave out
#ifndef LPLPVOID
typedef LPVOID FAR * LPLPVOID;
#endif  //LPLPVOID

#ifndef PPVOID  //Large model version
typedef LPVOID * PPVOID;
#endif  //PPVOID

#ifdef _OLE2_H_   //May not include ole2.h at all times.
#ifndef LPPOINTL
typedef POINTL FAR * LPPOINTL;
#endif  //LPPOINTL

#ifndef PPOINTL //Large model version
typedef POINTL * PPOINTL;
#endif  //PPOINTL

//Useful macros.
#define SETFormatEtc(fe, cf, asp, td, med, li)   \
    {\
    (fe).cfFormat=cf;\
    (fe).dwAspect=asp;\
    (fe).ptd=td;\
    (fe).tymed=med;\
    (fe).lindex=li;\
    };

#define SETDefFormatEtc(fe, cf, med)   \
    {\
    (fe).cfFormat=cf;\
    (fe).dwAspect=DVASPECT_CONTENT;\
    (fe).ptd=NULL;\
    (fe).tymed=med;\
    (fe).lindex=-1;\
    };

#define SETRECTL(rcl, l, t, r, b) \
    {\
    (rcl).left=l;\
    (rcl).top=t;\
    (rcl).right=r;\
    (rcl).bottom=b;\
    }

#define SETSIZEL(szl, h, v) \
    {\
    (szl).cx=h;\
    (szl).cy=v;\
    }

#define RECTLFROMRECT(rcl, rc)\
    {\
    (rcl).left=(long)(rc).left;\
    (rcl).top=(long)(rc).top;\
    (rcl).right=(long)(rc).right;\
    (rcl).bottom=(long)(rc).bottom;\
    }

#define RECTFROMRECTL(rc, rcl)\
    {\
    (rc).left=(int)(rcl).left;\
    (rc).top=(int)(rcl).top;\
    (rc).right=(int)(rcl).right;\
    (rc).bottom=(int)(rcl).bottom;\
    }

#define POINTLFROMPOINT(ptl, pt) \
    { \
    (ptl).x=(long)(pt).x; \
    (ptl).y=(long)(pt).y; \
    }

#define POINTFROMPOINTL(pt, ptl) \
    { \
    (pt).x=(int)(ptl).x; \
    (pt).y=(int)(ptl).y; \
    }

//Here's one that should be in windows.h
#define SETPOINT(pt, h, v) \
    {\
    (pt).x=h;\
    (pt).y=v;\
    }

#define SETPOINTL(ptl, h, v) \
    {\
    (ptl).x=h;\
    (ptl).y=v;\
    }

#endif  //_OLE2_H_

/*
 * State flags for IPersistStorage implementations.  These
 * are kept here to avoid repeating the code in all samples.
 */

//State Flags
typedef enum
    {
    PSSTATE_UNINIT,     //Uninitialized
    PSSTATE_SCRIBBLE,   //Scribble
    PSSTATE_ZOMBIE,     //No scribble
    PSSTATE_HANDSOFF    //Hand-off
    } PSSTATE;

/*
 * GUIDs
 *
 * For this book the author has been allocated the GUIDs in the range:
 *     00021100-0000-0000-C000-000000000046
 *     000211FF-0000-0000-C000-000000000046
 *
 * Gaps in the sequences are GUIDS used by the author for purposes other
 * than this book, e.g. magazine articles.
 */

//GUIDs for Chapter 4

//Koala
DEFINE_GUID(CLSID_Koala, 0x00021102, 0, 0, 0xC0,0,0,0,0,0,0,0x46);

//Polyine
DEFINE_GUID(CLSID_Polyline4,          0x00021103, 0, 0, 0xC0,0,0,0,0,0,0,0x46);
DEFINE_GUID(IID_IPolyline4,           0x00021104, 0, 0, 0xC0,0,0,0,0,0,0,0x46);
DEFINE_GUID(IID_IPolylineAdviseSink4, 0x00021105, 0, 0, 0xC0,0,0,0,0,0,0,0x46);

//GUIDs for Chapter 5

//Patron Files for Chapter 5 and beyond:  becomes Patron's App ID
DEFINE_GUID(CLSID_PatronPages, 0x00021106, 0, 0, 0xC0,0,0,0,0,0,0,0x46);

//Cosmo Files for Chapter 5 and beyond:  becomes Cosmo's App ID
DEFINE_GUID(CLSID_Cosmo2Figure, 0x00021107, 0, 0, 0xC0,0,0,0,0,0,0,0x46);

//Polyine
DEFINE_GUID(CLSID_Polyline5,          0x00021108, 0, 0, 0xC0,0,0,0,0,0,0,0x46);
DEFINE_GUID(IID_IPolyline5,           0x00021109, 0, 0, 0xC0,0,0,0,0,0,0,0x46);
DEFINE_GUID(IID_IPolylineAdviseSink5, 0x0002110A, 0, 0, 0xC0,0,0,0,0,0,0,0x46);

//GUIDs for Chapter 6

//DataObject variations for the data set.  Same server supplies them all.
DEFINE_GUID(CLSID_DataObjectSmall,  0x0002110B, 0, 0, 0xC0,0,0,0,0,0,0,0x46);
DEFINE_GUID(CLSID_DataObjectMedium, 0x0002110C, 0, 0, 0xC0,0,0,0,0,0,0,0x46);
DEFINE_GUID(CLSID_DataObjectLarge,  0x0002110D, 0, 0, 0xC0,0,0,0,0,0,0,0x46);

//Polyine
DEFINE_GUID(CLSID_Polyline6,          0x0002110E, 0, 0, 0xC0,0,0,0,0,0,0,0x46);
DEFINE_GUID(IID_IPolyline6,           0x0002110F, 0, 0, 0xC0,0,0,0,0,0,0,0x46);
DEFINE_GUID(IID_IPolylineAdviseSink6, 0x00021110, 0, 0, 0xC0,0,0,0,0,0,0,0x46);

//GUIDS for Chapter 7

//Data Transfer Component Object
DEFINE_GUID(CLSID_DataTransferObject, 0x00021111, 0, 0, 0xC0,0,0,0,0,0,0,0x46);

#ifndef WIN32
//OLE32 has these defined, OLE16 doesn't
#undef CLSID_Picture_Metafile
#undef CLSID_Picture_Dib
DEFINE_OLEGUID(CLSID_Picture_Metafile,        0x00000315, 0, 0);
DEFINE_OLEGUID(CLSID_Picture_Dib,             0x00000316, 0, 0);
#endif

//Cosmo in chapter 10 uses CLSID_Cosmo2Figure above.

//GUIDs for Chapter 11

//HCosmo is registered under CLSID_Cosmo2Figure

//Polyine
DEFINE_GUID(CLSID_Polyline11, 0x00021112, 0, 0, 0xC0,0,0,0,0,0,0,0x46);

//GUIDs for Chapter 12

//LinksAssistant
DEFINE_GUID(CLSID_LinksAssistant, 0x00021113, 0, 0, 0xC0,0,0,0,0,0,0,0x46);

//OLE 2.0 SDK forgot this one.
DEFINE_GUID(IID_IOleUILinkContainer, 0x00021114, 0, 0, 0xC0,0,0,0,0,0,0,0x46);

//GUIDs for Chapter 16

/*
 * Cosmo in chapter 16 uses CLSID_Cosmo2Figure above.
 * Polyine in Chapter 16 uses CLSID_Polyline11.
 *
 * This is so we don't make repeat entries in the
 * registration database.
 */

#endif //_BOOKGUID_H_


BTTNCUR.H

/*
 * BTTNCUR.H
 * Buttons & Cursors
 *
 * Public include file for the Button Images and Cursor DLL,
 * including structures, definitions, and function prototypes.
 *
 * Copyright (c)1992-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _BTTNCUR_H_
#define _BTTNCUR_H_

#ifdef __cplusplus
extern "C"
    {
#endif

//Standard image bitmap

//WARNING:  Obsolete.  Use the return from UIToolDisplayData
#define IDB_STANDARDIMAGES              400

//New values for display types
#define IDB_STANDARDIMAGESMIN           400
#define IDB_STANDARDIMAGES96            400
#define IDB_STANDARDIMAGES72            401
#define IDB_STANDARDIMAGES120           402

//Image indices inside the standard bitmap.
#define TOOLIMAGE_MIN                   0
#define TOOLIMAGE_EDITCUT               0
#define TOOLIMAGE_EDITCOPY              1
#define TOOLIMAGE_EDITPASTE             2
#define TOOLIMAGE_FILENEW               3
#define TOOLIMAGE_FILEOPEN              4
#define TOOLIMAGE_FILESAVE              5
#define TOOLIMAGE_FILEPRINT             6
#define TOOLIMAGE_HELP                  7
#define TOOLIMAGE_HELPCONTEXT           8
#define TOOLIMAGE_MAX                   8

//Additional Standard Cursors as defined in the UI Design Guide.
#define IDC_NEWUICURSORMIN              500
#define IDC_RIGHTARROW                  500
#define IDC_CONTEXTHELP                 501
#define IDC_MAGNIFY                     502
#define IDC_NODROP                      503
#define IDC_TABLETOP                    504
#define IDC_HSIZEBAR                    505
#define IDC_VSIZEBAR                    506
#define IDC_HSPLITBAR                   507
#define IDC_VSPLITBAR                   508
#define IDC_SMALLARROWS                 509
#define IDC_LARGEARROWS                 510
#define IDC_HARROWS                     511
#define IDC_VARROWS                     512
#define IDC_NESWARROWS                  513
#define IDC_NWSEARROWS                  514
#define IDC_NEWUICURSORMAX              514

//Standard sizes for toolbar buttons and bitmaps on display types

//WARNING:  These are obsolete for version 1.0 compatibility/
#define TOOLBUTTON_STDWIDTH             24
#define TOOLBUTTON_STDHEIGHT            22
#define TOOLBUTTON_STDIMAGEWIDTH        16
#define TOOLBUTTON_STDIMAGEHEIGHT       15

/*
 * Applications can call UIToolDisplayData to get the particular
 * values to use for the current display instead of using these
 * values directly.  However, if the application has the aspect
 * ratio already then these are available for them.
 */

//Sizes for 72 DPI (EGA)
#define TOOLBUTTON_STD72WIDTH           24
#define TOOLBUTTON_STD72HEIGHT          16
#define TOOLBUTTON_STD72IMAGEWIDTH      16
#define TOOLBUTTON_STD72IMAGEHEIGHT     11

//Sizes for 96 DPI (VGA)
#define TOOLBUTTON_STD96WIDTH           24
#define TOOLBUTTON_STD96HEIGHT          22
#define TOOLBUTTON_STD96IMAGEWIDTH      16
#define TOOLBUTTON_STD96IMAGEHEIGHT     15

//Sizes for 120 DPI (8514/a)
#define TOOLBUTTON_STD120WIDTH          32
#define TOOLBUTTON_STD120HEIGHT         31
#define TOOLBUTTON_STD120IMAGEWIDTH     24
#define TOOLBUTTON_STD120IMAGEHEIGHT    23

//Sizes of a standard button bar depending on the display
#define CYBUTTONBAR72                   23
#define CYBUTTONBAR96                   29
#define CYBUTTONBAR120                  38

/*
 * The low-word of the state contains the display state where each
 * value is mutually exclusive and contains one or more grouping
 * bits.  Each group represents buttons that share some sub-state
 * in common.
 *
 * The high-order byte controls which colors in the source bitmap,
 * black, white, gray, and dark gray, are to be converted into the
 * system colors COLOR_BTNTEXT, COLOR_HILIGHT, COLOR_BTNFACE, and
 * COLOR_BTNSHADOW.  Any or all of these bits may be set to allow
 * the application control over specific colors.
 *
 * The actual state values are split into a command group and an
 * attribute group.  Up, mouse down, and disabled states are
 * identical, but only attributes can have down, down disabled,
 * and indeterminate states.
 *
 * BUTTONGROUP_BLANK is defined so an application can draw only the
 * button without an image in the up, down, mouse down, or
 * indeterminate state, that is, BUTTONGROUP_BLANK is inclusive
 * with BUTTONGROUP_DOWN and BUTTONGROUP_LIGHTFACE.
 */

#define BUTTONGROUP_DOWN                0x0001
#define BUTTONGROUP_ACTIVE              0x0002
#define BUTTONGROUP_DISABLED            0x0004
#define BUTTONGROUP_LIGHTFACE           0x0008
#define BUTTONGROUP_BLANK               0x0010

//Command buttons only
#define COMMANDBUTTON_UP                (BUTTONGROUP_ACTIVE)
#define COMMANDBUTTON_MOUSEDOWN         (BUTTONGROUP_ACTIVE     \
                                        | BUTTONGROUP_DOWN)
#define COMMANDBUTTON_DISABLED          (BUTTONGROUP_DISABLED)

//Attribute buttons only
#define ATTRIBUTEBUTTON_UP              (BUTTONGROUP_ACTIVE)
#define ATTRIBUTEBUTTON_MOUSEDOWN       (BUTTONGROUP_ACTIVE     \
                                        | BUTTONGROUP_DOWN)
#define ATTRIBUTEBUTTON_DISABLED        (BUTTONGROUP_DISABLED)
#define ATTRIBUTEBUTTON_DOWN            (BUTTONGROUP_ACTIVE     \
                                        | BUTTONGROUP_DOWN      \
                                        | BUTTONGROUP_LIGHTFACE)

#define ATTRIBUTEBUTTON_INDETERMINATE   (BUTTONGROUP_ACTIVE     \
                                        | BUTTONGROUP_LIGHTFACE)
#define ATTRIBUTEBUTTON_DOWNDISABLED    (BUTTONGROUP_DISABLED   \
                                        | BUTTONGROUP_DOWN      \
                                        | BUTTONGROUP_LIGHTFACE)

//Blank buttons only
#define BLANKBUTTON_UP                  (BUTTONGROUP_ACTIVE \
                                        | BUTTONGROUP_BLANK)

#define BLANKBUTTON_DOWN                (BUTTONGROUP_ACTIVE \
                                        | BUTTONGROUP_BLANK \
                                        | BUTTONGROUP_DOWN  \
                                        | BUTTONGROUP_LIGHTFACE)

#define BLANKBUTTON_MOUSEDOWN           (BUTTONGROUP_ACTIVE \
                                        | BUTTONGROUP_BLANK \
                                        | BUTTONGROUP_DOWN)

#define BLANKBUTTON_INDETERMINATE       (BUTTONGROUP_ACTIVE \
                                        | BUTTONGROUP_BLANK \
                                        | BUTTONGROUP_LIGHTFACE)

/*
 * Specific bits to prevent conversions of specific colors to
 * system colors.  If an application uses this newer library
 * and never specified any bits, then they benefit from color
 * conversion automatically.
 */
#define PRESERVE_BLACK                  0x0100
#define PRESERVE_DKGRAY                 0x0200
#define PRESERVE_LTGRAY                 0x0400
#define PRESERVE_WHITE                  0x0800

#define PRESERVE_ALL                    (PRESERVE_BLACK     \
                                        | PRESERVE_DKGRAY   \
                                        | PRESERVE_LTGRAY   \
                                        | PRESERVE_WHITE)

#define PRESERVE_NONE                   0   //Backwards compatible

//Structure for UIToolConfigureForDisplay
typedef struct tagTOOLDISPLAYDATA
    {
    UINT        uDPI;       //Display driver DPI
    UINT        cyBar;      //Vertical size for a bar w/ buttons.
    UINT        cxButton;   //Dimensions of a button.
    UINT        cyButton;
    UINT        cxImage;    //Dimensions of bitmap image
    UINT        cyImage;
    UINT        uIDImages;  //Standard resource ID for images
    } TOOLDISPLAYDATA, FAR *LPTOOLDISPLAYDATA;

//Public functions
HCURSOR WINAPI UICursorLoad(UINT);
BOOL    WINAPI UIToolConfigureForDisplay(LPTOOLDISPLAYDATA);
BOOL    WINAPI UIToolButtonDraw(HDC, int, int, int, int, HBITMAP
            , int, int, int, UINT, LPTOOLDISPLAYDATA);

#ifdef __cplusplus
    }
#endif

#endif //_BTTNCUR_H_


DBGOUT.H

/*
 * DBGOUT.H
 *
 * Useful debugging output macros that compile to nothing and
 * eliminate ugly #ifdef DEBUGs from source code.  Note that
 * string literals do not need TEXT() around them as this file
 * includes them.
 *
 * Copyright (c)1994-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  INTERNET>kraigb@microsoft.com
 */

#ifndef _DBGOUT_H
#define _DBGOUT_H

#ifdef DEBUG

//Basic debug macros
#define D(x)        {x;}

#define ODS(x)  \
    {\
    TCHAR        szDebug[128];\
    OutputDebugString(TEXT(x));\
    wsprintf(szDebug, TEXT(" [%s, %u]\r\n"), (LPTSTR)__FILE__, __LINE__);\
    OutputDebugString(szDebug);\
    }

#define ODSsz(f, s) \
    {\
    TCHAR       szDebug[128];\
    wsprintf(szDebug, TEXT(f), (LPTSTR)s);\
    OutputDebugString(szDebug);\
    wsprintf(szDebug, TEXT(" [%s, %u]\r\n"), (LPTSTR)__FILE__, __LINE__);\
    OutputDebugString(szDebug);\
    }

#define ODSu(f, u)  \
    {\
    TCHAR       szDebug[128];\
    wsprintf(szDebug, TEXT(f), (UINT)u);\
    OutputDebugString(szDebug);\
    wsprintf(szDebug, TEXT(" [%s, %u]\r\n"), (LPTSTR)__FILE__, __LINE__);\
    OutputDebugString(szDebug);\
    }

#define ODSlu(f, lu) \
    {\
    TCHAR       szDebug[128];\
    wsprintf(szDebug, TEXT(f), (DWORD)lu);\
    OutputDebugString(szDebug);\
    wsprintf(szDebug, TEXT(" [%s, %u]\r\n"), (LPTSTR)__FILE__, __LINE__);\
    OutputDebugString(szDebug);\
    }

#define ODSlulu(f, lu1, lu2) \
    {\
    TCHAR       szDebug[128];\
    wsprintf(szDebug, TEXT(f), (DWORD)lu1, (DWORD)lu1);\
    OutputDebugString(szDebug);\
    wsprintf(szDebug, TEXT(" [%s, %u]\r\n"), (LPTSTR)__FILE__, __LINE__);\
    OutputDebugString(szDebug);\
    }

#define ODSszu(f, s, u)     \
    {\
    TCHAR       szDebug[128];\
    wsprintf(szDebug, TEXT(f), (LPTSTR)s, (UINT)u);\
    OutputDebugString(szDebug);\
    wsprintf(szDebug, TEXT(" [%s, %u]\r\n"), (LPTSTR)__FILE__, __LINE__);\
    OutputDebugString(szDebug);\
    }

#define ODSszlu(f, s, lu)   \
    {\
    TCHAR       szDebug[128];\
    wsprintf(szDebug, TEXT(f), (LPTSTR)s, (DWORD)lu);\
    OutputDebugString(szDebug);\
    wsprintf(szDebug, TEXT(" [%s, %u]\r\n"), (LPTSTR)__FILE__, __LINE__);\
    OutputDebugString(szDebug);\
    }

#else   //NO DEBUG

#define D(x)
#define ODS(x)

#define ODSsz(f, s)
#define ODSu(f, u)
#define ODSlu(f, lu)
#define ODSszu(f, s, u)
#define ODSszlu(f, s, lu)

#endif //DEBUG

#endif //_DBGOUT_H


GIZMOBAR.H

/*
 * GIZMOBAR.H
 * GizmoBar portion of INOLE.DLL
 *
 * Public definitions for application that use the GizmoBar such as
 * messages, prototypes for API functions, notification codes, and
 * control styles.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _GIZMOBAR_H_
#define _GIZMOBAR_H_

#include <bttncur.h>
#include <book1632.h>

#ifdef __cplusplus
extern "C"
    {
#endif

//Global classnames
#define CLASS_GIZMOBAR  TEXT("gizmobar")

//Message API Functions
HWND    WINAPI GBHwndAssociateSet(HWND, HWND);
HWND    WINAPI GBHwndAssociateGet(HWND);

BOOL    WINAPI GBGizmoAdd(HWND, UINT, UINT, UINT, UINT, UINT, LPTSTR
            , HBITMAP, UINT, UINT);
BOOL    WINAPI GBGizmoRemove(HWND, UINT);

LRESULT WINAPI GBGizmoSendMessage(HWND, UINT, UINT, WPARAM, LPARAM);

BOOL    WINAPI GBGizmoShow(HWND, UINT, BOOL);
BOOL    WINAPI GBGizmoEnable(HWND, UINT, BOOL);
BOOL    WINAPI GBGizmoCheck(HWND, UINT, BOOL);
UINT    WINAPI GBGizmoFocusSet(HWND, UINT);
BOOL    WINAPI GBGizmoExist(HWND, UINT);

int     WINAPI GBGizmoTypeGet(HWND, UINT);

DWORD   WINAPI GBGizmoDataSet(HWND, UINT, DWORD);
DWORD   WINAPI GBGizmoDataGet(HWND, UINT);
BOOL    WINAPI GBGizmoNotifySet(HWND, UINT, BOOL);
BOOL    WINAPI GBGizmoNotifyGet(HWND, UINT);

int     WINAPI GBGizmoTextGet(HWND, UINT, LPTSTR, UINT);
void    WINAPI GBGizmoTextSet(HWND, UINT, LPTSTR);
UINT    WINAPI GBGizmoIntGet(HWND, UINT, BOOL FAR *, BOOL);
void    WINAPI GBGizmoIntSet(HWND, UINT, UINT, BOOL);

//Notification codes sent via WM_COMMAND from GBHwndAssociateSet
#define GBN_ASSOCIATEGAIN               1
#define GBN_ASSOCIATELOSS               2
#define GBN_GIZMOADDED                  3
#define GBN_GIZMOREMOVED                4

//Message equivalents for functions.
#define GBM_HWNDASSOCIATESET            (WM_USER+0)
#define GBM_HWNDASSOCIATEGET            (WM_USER+1)
#define GBM_GIZMOADD                    (WM_USER+2)
#define GBM_GIZMOREMOVE                 (WM_USER+3)
#define GBM_GIZMOSENDMESSAGE            (WM_USER+4)
#define GBM_GIZMOSHOW                   (WM_USER+5)
#define GBM_GIZMOENABLE                 (WM_USER+6)
#define GBM_GIZMOCHECK                  (WM_USER+7)
#define GBM_GIZMOFOCUSSET               (WM_USER+8)
#define GBM_GIZMOEXIST                  (WM_USER+9)
#define GBM_GIZMOTYPEGET                (WM_USER+10)
#define GBM_GIZMODATASET                (WM_USER+11)
#define GBM_GIZMODATAGET                (WM_USER+12)
#define GBM_GIZMONOTIFYSET              (WM_USER+13)
#define GBM_GIZMONOTIFYGET              (WM_USER+14)
#define GBM_GIZMOTEXTGET                (WM_USER+15)
#define GBM_GIZMOTEXTSET                (WM_USER+16)
#define GBM_GIZMOINTGET                 (WM_USER+17)
#define GBM_GIZMOINTSET                 (WM_USER+18)

/*
 * Structure passed in lParam of GBM_GIZMOADD that mirrors the
 * parameters to GBGizmoAdd.
 */

typedef struct
    {
    HWND        hWndParent;         //Parent window
    UINT        iType;              //Type of gizmo
    UINT        iGizmo;             //Position to create gizmo
    UINT        uID;                //Identifier of gizmo
    UINT        dx;                 //Dimensions of gizmo
    UINT        dy;
    LPTSTR      pszText;            //Gizmo text
    HBITMAP     hBmp;               //Source of gizmo button image.
    UINT        iImage;             //Index of image from hBmp
    UINT        uState;             //Initial state of the gizmo.
    } CREATEGIZMO, FAR *LPCREATEGIZMO;

#define CBCREATEGIZMO sizeof(CREATEGIZMO)

//For GBM_GIZMOSENDMESSAGE
typedef struct
    {
    UINT        iMsg;
    WPARAM      wParam;
    LPARAM      lParam;
    } GBMSG, FAR * LPGBMSG;

#define CBGBMSG sizeof(GBMSG);

//For GBM_GIZMOGETTEXT
typedef struct
    {
    LPTSTR      psz;
    UINT        cch;
    } GBGETTEXT, FAR * LPGBGETTEXT;

#define CBGBGETTEXT sizeof(GBGETTEXT);

//For GBM_GIZMOGETINT
typedef struct
    {
    BOOL        fSigned;
    BOOL        fSuccess;
    } GBGETINT, FAR * LPGBGETINT;

#define CBGBGETINT sizeof(GBGETINT);

//For GBM_GIZMOSETINT
typedef struct
    {
    UINT        uValue;
    BOOL        fSigned;
    } GBSETINT, FAR * LPGBSETINT;

#define CBGBSETINT sizeof(GBSETINT);

//Gizmo control types.  DO NOT CHANGE THESE!
#define GIZMOTYPE_EDIT                  0x0001
#define GIZMOTYPE_LISTBOX               0x0002
#define GIZMOTYPE_COMBOBOX              0x0004
#define GIZMOTYPE_BUTTONNORMAL          0x0008
#define GIZMOTYPE_TEXT                  0x0010
#define GIZMOTYPE_SEPARATOR             0x0020
#define GIZMOTYPE_BUTTONATTRIBUTEIN     0x0040
#define GIZMOTYPE_BUTTONATTRIBUTEEX     0x0080
#define GIZMOTYPE_BUTTONCOMMAND         0x0100

//Generic state flags for non-buttons based on BTTNCUR.H's groups.
#define GIZMO_NORMAL                    (BUTTONGROUP_ACTIVE)
#define GIZMO_DISABLED                  (BUTTONGROUP_DISABLED)

#ifdef __cplusplus
    }   //Match with extern "C" above.
#endif

#endif //_GIZMOBAR_H_


IANIM16.H

/*
 * NOTE:  This file is a 16-bit modification of ianimal.h.
 * This is not generated by MIDL directly.
 */

#ifndef __ianim16_h__
#define __ianim16_h__

DECLARE_INTERFACE_(IAnimal, IUnknown)
    {
    //IUnknown members
    STDMETHOD(QueryInterface) (THIS_ REFIID, PPVOID) PURE;
    STDMETHOD_(ULONG,AddRef)  (THIS) PURE;
    STDMETHOD_(ULONG,Release) (THIS) PURE;

    STDMETHOD(Eat)(THIS_ LPTSTR, LPTSTR, short) PURE;
    STDMETHOD(Sleep)(THIS_ short FAR *) PURE;
    STDMETHOD(Procreate)(short FAR *) PURE;
    STDMETHOD(WhatKindOfAnimal)(IID FAR *) PURE;
    };

typedef IAnimal *PANIMAL, FAR *LPANIMAL;

#endif //__ianim16_h__


IANIMAL.H

#include "rpc.h"
#include "rpcndr.h"

#ifndef __ianimal_h__
#define __ianimal_h__

#ifdef __cplusplus
extern "C"{
#endif

/* Forward Declarations */

#ifndef __IAnimal_FWD_DEFINED__
#define __IAnimal_FWD_DEFINED__
typedef interface IAnimal IAnimal;
#endif /* __IAnimal_FWD_DEFINED__ */

#include "unknwn.h"
void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t);
void __RPC_USER MIDL_user_free( void __RPC_FAR * );

#ifndef __IAnimal_INTERFACE_DEFINED__
#define __IAnimal_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IAnimal
 * at Thu Apr 20 09:35:58 1995
 * using MIDL 2.00.72
 ****************************************/
/* [object][uuid] */

EXTERN_C const IID IID_IAnimal;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface IAnimal : public IUnknown
    {
    public:
        virtual HRESULT __stdcall Eat(
            /* [in] */ LPTSTR pszFoodRecommended,
            /* [out][in] */ LPTSTR pszFoodEaten,
            /* [in] */ short cchEaten) = 0;

        virtual HRESULT __stdcall Sleep(
            /* [out][in] */ short __RPC_FAR *pcMinutes) = 0;

        virtual HRESULT __stdcall Procreate(
            /* [out] */ short __RPC_FAR *pcOffspring) = 0;

        virtual HRESULT __stdcall WhatKindOfAnimal(
            /* [out] */ IID __RPC_FAR *pIID) = 0;

    };

#else /* C style interface */

    typedef struct IAnimalVtbl
    {

        HRESULT ( __stdcall __RPC_FAR *QueryInterface )(
            IAnimal __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( __stdcall __RPC_FAR *AddRef )(
            IAnimal __RPC_FAR * This);

        ULONG ( __stdcall __RPC_FAR *Release )(
            IAnimal __RPC_FAR * This);

        HRESULT ( __stdcall __RPC_FAR *Eat )(
            IAnimal __RPC_FAR * This,
            /* [in] */ LPTSTR pszFoodRecommended,
            /* [out][in] */ LPTSTR pszFoodEaten,
            /* [in] */ short cchEaten);

        HRESULT ( __stdcall __RPC_FAR *Sleep )(
            IAnimal __RPC_FAR * This,
            /* [out][in] */ short __RPC_FAR *pcMinutes);

        HRESULT ( __stdcall __RPC_FAR *Procreate )(
            IAnimal __RPC_FAR * This,
            /* [out] */ short __RPC_FAR *pcOffspring);

        HRESULT ( __stdcall __RPC_FAR *WhatKindOfAnimal )(
            IAnimal __RPC_FAR * This,
            /* [out] */ IID __RPC_FAR *pIID);

    } IAnimalVtbl;

    interface IAnimal
    {
        CONST_VTBL struct IAnimalVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IAnimal_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IAnimal_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IAnimal_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IAnimal_Eat(This,pszFoodRecommended,pszFoodEaten,cchEaten)\
    (This)->lpVtbl -> Eat(This,pszFoodRecommended,pszFoodEaten,cchEaten)

#define IAnimal_Sleep(This,pcMinutes)\
    (This)->lpVtbl -> Sleep(This,pcMinutes)

#define IAnimal_Procreate(This,pcOffspring)\
    (This)->lpVtbl -> Procreate(This,pcOffspring)

#define IAnimal_WhatKindOfAnimal(This,pIID)\
    (This)->lpVtbl -> WhatKindOfAnimal(This,pIID)

#endif /* COBJMACROS */

#endif /* C style interface */

HRESULT __stdcall IAnimal_Eat_Proxy(
    IAnimal __RPC_FAR * This,
    /* [in] */ LPTSTR pszFoodRecommended,
    /* [out][in] */ LPTSTR pszFoodEaten,
    /* [in] */ short cchEaten);

void __RPC_STUB IAnimal_Eat_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT __stdcall IAnimal_Sleep_Proxy(
    IAnimal __RPC_FAR * This,
    /* [out][in] */ short __RPC_FAR *pcMinutes);

void __RPC_STUB IAnimal_Sleep_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT __stdcall IAnimal_Procreate_Proxy(
    IAnimal __RPC_FAR * This,
    /* [out] */ short __RPC_FAR *pcOffspring);

void __RPC_STUB IAnimal_Procreate_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT __stdcall IAnimal_WhatKindOfAnimal_Proxy(
    IAnimal __RPC_FAR * This,
    /* [out] */ IID __RPC_FAR *pIID);

void __RPC_STUB IAnimal_WhatKindOfAnimal_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IAnimal_INTERFACE_DEFINED__ */

/* Additional Prototypes for ALL interfaces */

/* end of Additional Prototypes */

#ifdef __cplusplus
}
#endif

#endif


IDESCRIP.H

/* this ALWAYS GENERATED file contains the definitions for the interfaces */

/* File created by MIDL compiler version 2.00.0102 */
/* at Tue May 21 15:05:31 1996
 */
//@@MIDL_FILE_HEADING(  )
#include "rpc.h"
#include "rpcndr.h"
#ifndef COM_NO_WINDOWS_H
#include "windows.h"
#include "ole2.h"
#endif /*COM_NO_WINDOWS_H*/

#ifndef __idescrip_h__
#define __idescrip_h__

#ifdef __cplusplus
extern "C"{
#endif

/* Forward Declarations */

#ifndef __IDescription_FWD_DEFINED__
#define __IDescription_FWD_DEFINED__
typedef interface IDescription IDescription;
#endif /* __IDescription_FWD_DEFINED__ */

/* header files for imported files */
#include "unknwn.h"

void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t);
void __RPC_USER MIDL_user_free( void __RPC_FAR * );

#ifndef __IDescription_INTERFACE_DEFINED__
#define __IDescription_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IDescription
 * at Tue May 21 15:05:31 1996
 * using MIDL 2.00.0102
 ****************************************/
/* [unique][object][uuid] */

EXTERN_C const IID IID_IDescription;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface IDescription : public IUnknown
    {
    public:
        virtual HRESULT __stdcall GetText(
            /* [size_is][out][in] */ LPOLESTR pszText,
            /* [in] */ ULONG cch) = 0;

    };

#else /* C style interface */

    typedef struct IDescriptionVtbl
    {

        HRESULT ( __stdcall __RPC_FAR *QueryInterface )(
            IDescription __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( __stdcall __RPC_FAR *AddRef )(
            IDescription __RPC_FAR * This);

        ULONG ( __stdcall __RPC_FAR *Release )(
            IDescription __RPC_FAR * This);

        HRESULT ( __stdcall __RPC_FAR *GetText )(
            IDescription __RPC_FAR * This,
            /* [size_is][out][in] */ LPOLESTR pszText,
            /* [in] */ ULONG cch);

    } IDescriptionVtbl;

    interface IDescription
    {
        CONST_VTBL struct IDescriptionVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IDescription_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IDescription_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IDescription_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IDescription_GetText(This,pszText,cch)\
    (This)->lpVtbl -> GetText(This,pszText,cch)

#endif /* COBJMACROS */

#endif /* C style interface */

HRESULT __stdcall IDescription_GetText_Proxy(
    IDescription __RPC_FAR * This,
    /* [size_is][out][in] */ LPOLESTR pszText,
    /* [in] */ ULONG cch);

void __RPC_STUB IDescription_GetText_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IDescription_INTERFACE_DEFINED__ */

/* Additional Prototypes for ALL interfaces */

/* end of Additional Prototypes */

#ifdef __cplusplus
}
#endif

#endif


IKOALA.H

#include "rpc.h"
#include "rpcndr.h"

#ifndef __ikoala_h__
#define __ikoala_h__

#ifdef __cplusplus
extern "C"{
#endif

/* Forward Declarations */

#ifndef __IKoala_FWD_DEFINED__
#define __IKoala_FWD_DEFINED__
typedef interface IKoala IKoala;
#endif /* __IKoala_FWD_DEFINED__ */

#include "unknwn.h"
void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t);
void __RPC_USER MIDL_user_free( void __RPC_FAR * );

#ifndef __IKoala_INTERFACE_DEFINED__
#define __IKoala_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IKoala
 * at Mon May 01 08:28:33 1995
 * using MIDL 2.00.72
 ****************************************/
/* [object][uuid] */

EXTERN_C const IID IID_IKoala;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface IKoala : public IUnknown
    {
    public:
        virtual HRESULT __stdcall ClimbEucalyptusTree(
            /* [in] */ short iTree) = 0;

        virtual HRESULT __stdcall PouchOpensDown( void) = 0;

        virtual HRESULT __stdcall SleepAfterEating(
            /* [in] */ short cMinutes) = 0;

    };

#else /* C style interface */

    typedef struct IKoalaVtbl
    {

        HRESULT ( __stdcall __RPC_FAR *QueryInterface )(
            IKoala __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( __stdcall __RPC_FAR *AddRef )(
            IKoala __RPC_FAR * This);

        ULONG ( __stdcall __RPC_FAR *Release )(
            IKoala __RPC_FAR * This);

        HRESULT ( __stdcall __RPC_FAR *ClimbEucalyptusTree )(
            IKoala __RPC_FAR * This,
            /* [in] */ short iTree);

        HRESULT ( __stdcall __RPC_FAR *PouchOpensDown )(
            IKoala __RPC_FAR * This);

        HRESULT ( __stdcall __RPC_FAR *SleepAfterEating )(
            IKoala __RPC_FAR * This,
            /* [in] */ short cMinutes);

    } IKoalaVtbl;

    interface IKoala
    {
        CONST_VTBL struct IKoalaVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IKoala_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IKoala_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IKoala_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IKoala_ClimbEucalyptusTree(This,iTree)\
    (This)->lpVtbl -> ClimbEucalyptusTree(This,iTree)

#define IKoala_PouchOpensDown(This)\
    (This)->lpVtbl -> PouchOpensDown(This)

#define IKoala_SleepAfterEating(This,cMinutes)\
    (This)->lpVtbl -> SleepAfterEating(This,cMinutes)

#endif /* COBJMACROS */

#endif /* C style interface */

HRESULT __stdcall IKoala_ClimbEucalyptusTree_Proxy(
    IKoala __RPC_FAR * This,
    /* [in] */ short iTree);

void __RPC_STUB IKoala_ClimbEucalyptusTree_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT __stdcall IKoala_PouchOpensDown_Proxy(
    IKoala __RPC_FAR * This);

void __RPC_STUB IKoala_PouchOpensDown_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT __stdcall IKoala_SleepAfterEating_Proxy(
    IKoala __RPC_FAR * This,
    /* [in] */ short cMinutes);

void __RPC_STUB IKoala_SleepAfterEating_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IKoala_INTERFACE_DEFINED__ */

/* Additional Prototypes for ALL interfaces */

/* end of Additional Prototypes */

#ifdef __cplusplus
}
#endif

#endif


IKOALA16.H

/*
 * NOTE:  This file is a 16-bit modification of ikoala.h.
 * This is not generated by MIDL directly.
 */

#ifndef __ikoala_h__
#define __ikoala_h__

DECLARE_INTERFACE_(IKoala, IUnknown)
    {
    //IUnknown members
    STDMETHOD(QueryInterface) (THIS_ REFIID, PPVOID) PURE;
    STDMETHOD_(ULONG,AddRef)  (THIS) PURE;
    STDMETHOD_(ULONG,Release) (THIS) PURE;

    STDMETHOD(ClimbEucalyptusTree)(THIS_ short) PURE;
    STDMETHOD(PouchOpensDown)(THIS) PURE;
    STDMETHOD(SleepAfterEating)(THIS_ short) PURE;
    };

typedef IKoala *PKOALA, FAR *LPKOALA;

#endif //__ikoala_h__


INOLE.H

/*
 * INOLE.H
 *
 * Master header file for all Inside OLE samples.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _INOLE_H_
#define _INOLE_H_

#define INC_OLE2
#include <windows.h>
#include <ole2.h>
#include <ole2ver.h>

#ifdef INC_OLEUI
#ifdef WIN32
/*
 * Comment the next line and remove the comment from the
 * line after it to compile for Windows NT 3.5.
 */
#include <oledlg.h>
//#include <ole2ui.h>
#else
#include <ole2ui.h>
#endif
#endif

#include <bookguid.h>

#ifdef INC_CONTROLS
#define INC_AUTOMATION
#endif

#ifdef INC_AUTOMATION
#ifndef WIN32
#include <dispatch.h>
#include <olenls.h>
#else
#include <oleauto.h>
#endif
#endif

#ifdef INC_CONTROLS
#include <olectl.h>
#ifndef INITGUIDS
#include <olectlid.h>
#endif
#endif

#ifdef WIN32
#include <tchar.h>
#ifdef UNICODE
#include <wchar.h>
#endif
#endif

#ifndef WIN32
#include <shellapi.h>
#include <malloc.h>
#endif

#include <book1632.h>
#include <dbgout.h>

#ifdef INC_CLASSLIB
extern "C"
    {
    #include <commdlg.h>
   #ifndef WIN32
    #include <print.h>
    #include <dlgs.h>
   #endif
    }

#include <classlib.h>
#endif

//Types that OLE2.H et. al. leave out

#ifndef PPVOID
typedef LPVOID * PPVOID;
#endif  //PPVOID

#ifdef _OLE2_H_   //May not include ole2.h at all times.

#ifndef PPOINTL
typedef POINTL * PPOINTL;
#endif  //PPOINTL

#ifndef _WIN32
#ifndef OLECHAR
typedef char OLECHAR;
typedef OLECHAR FAR* LPOLESTR;
typedef const OLECHAR FAR* LPCOLESTR;
#endif //OLECHAR
#endif //_WIN32

//Useful macros.
#define SETFormatEtc(fe, cf, asp, td, med, li)   \
    {\
    (fe).cfFormat=cf;\
    (fe).dwAspect=asp;\
    (fe).ptd=td;\
    (fe).tymed=med;\
    (fe).lindex=li;\
    }

#define SETDefFormatEtc(fe, cf, med)   \
    {\
    (fe).cfFormat=cf;\
    (fe).dwAspect=DVASPECT_CONTENT;\
    (fe).ptd=NULL;\
    (fe).tymed=med;\
    (fe).lindex=-1;\
    }

#define SETRECTL(rcl, l, t, r, b) \
    {\
    (rcl).left=l;\
    (rcl).top=t;\
    (rcl).right=r;\
    (rcl).bottom=b;\
    }

#define SETSIZEL(szl, h, v) \
    {\
    (szl).cx=h;\
    (szl).cy=v;\
    }

#define RECTLFROMRECT(rcl, rc)\
    {\
    (rcl).left=(long)(rc).left;\
    (rcl).top=(long)(rc).top;\
    (rcl).right=(long)(rc).right;\
    (rcl).bottom=(long)(rc).bottom;\
    }

#define RECTFROMRECTL(rc, rcl)\
    {\
    (rc).left=(int)(rcl).left;\
    (rc).top=(int)(rcl).top;\
    (rc).right=(int)(rcl).right;\
    (rc).bottom=(int)(rcl).bottom;\
    }

#define POINTLFROMPOINT(ptl, pt) \
    { \
    (ptl).x=(long)(pt).x; \
    (ptl).y=(long)(pt).y; \
    }

#define POINTFROMPOINTL(pt, ptl) \
    { \
    (pt).x=(int)(ptl).x; \
    (pt).y=(int)(ptl).y; \
    }

//Here's one that should be in windows.h
#define SETPOINT(pt, h, v) \
    {\
    (pt).x=h;\
    (pt).y=v;\
    }

#define SETPOINTL(ptl, h, v) \
    {\
    (ptl).x=h;\
    (ptl).y=v;\
    }

#endif  //_OLE2_H_

#ifdef INC_AUTOMATION

//Macros for setting DISPPARAMS structures
#define SETDISPPARAMS(dp, numArgs, pvArgs, numNamed, pNamed) \
    {\
    (dp).cArgs=numArgs;\
    (dp).rgvarg=pvArgs;\
    (dp).cNamedArgs=numNamed;\
    (dp).rgdispidNamedArgs=pNamed;\
    }

#define SETNOPARAMS(dp) SETDISPPARAMS(dp, 0, NULL, 0, NULL)

//Macros for setting EXCEPINFO structures
#define SETEXCEPINFO(ei, excode, src, desc, file, ctx, func, scd) \
    {\
    (ei).wCode=excode;\
    (ei).wReserved=0;\
    (ei).bstrSource=src;\
    (ei).bstrDescription=desc;\
    (ei).bstrHelpFile=file;\
    (ei).dwHelpContext=ctx;\
    (ei).pvReserved=NULL;\
    (ei).pfnDeferredFillIn=func;\
    (ei).scode=scd;\
    }

#define INITEXCEPINFO(ei) \
        SETEXCEPINFO(ei,0,NULL,NULL,NULL,0L,NULL,S_OK)

#endif

/*
 * State flags for IPersistStorage implementations.  These
 * are kept here to avoid repeating the code in all samples.
 */

typedef enum
    {
    PSSTATE_UNINIT,     //Uninitialized
    PSSTATE_SCRIBBLE,   //Scribble
    PSSTATE_ZOMBIE,     //No scribble
    PSSTATE_HANDSOFF    //Hand-off
    } PSSTATE;

/*
 * Identifers to describe which persistence model an object
 * is using, along with a union type that holds on the the
 * appropriate pointers that a client may need.
 */
typedef enum
    {
    PERSIST_UNKNOWN=0,
    PERSIST_STORAGE,
    PERSIST_STREAM,
    PERSIST_STREAMINIT,
    PERSIST_FILE
    } PERSIST_MODEL;

typedef struct
    {
    PERSIST_MODEL   psModel;
    union
        {
        IPersistStorage    *pIPersistStorage;
        IPersistStream     *pIPersistStream;
       #ifdef INC_CONTROLS
        IPersistStreamInit *pIPersistStreamInit;
       #endif
        IPersistFile       *pIPersistFile;
        } pIP;

    } PERSISTPOINTER, *PPERSISTPOINTER;

//To identify a storage in which to save, load, or create.
typedef struct
    {
    PERSIST_MODEL   psModel;
    union
        {
        IStorage    *pIStorage;
        IStream     *pIStream;
        } pIS;

    } STGPOINTER, *PSTGPOINTER;

//Type for an object-destroyed callback
typedef void (*PFNDESTROYED)(void);

//DeleteInterfaceImp calls 'delete' and NULLs the pointer
#define DeleteInterfaceImp(p)\
            {\
            if (NULL!=p)\
                {\
                delete p;\
                p=NULL;\
                }\
            }

//ReleaseInterface calls 'Release' and NULLs the pointer
#define ReleaseInterface(p)\
            {\
            IUnknown *pt=(IUnknown *)p;\
            p=NULL;\
            if (NULL!=pt)\
                pt->Release();\
            }

//OLE Documents Clipboard Formats

#define CFSTR_EMBEDSOURCE       TEXT("Embed Source")
#define CFSTR_EMBEDDEDOBJECT    TEXT("Embedded Object")
#define CFSTR_LINKSOURCE        TEXT("Link Source")
#define CFSTR_CUSTOMLINKSOURCE  TEXT("Custom Link Source")
#define CFSTR_OBJECTDESCRIPTOR  TEXT("Object Descriptor")
#define CFSTR_LINKSRCDESCRIPTOR TEXT("Link Source Descriptor")

//Functions in the helper DLL, INOLE.DLL

//UI Effects
STDAPI_(void) UIDrawHandles(LPRECT, HDC, DWORD, UINT, BOOL);
STDAPI_(void) UIDrawShading(LPRECT, HDC, DWORD, UINT);
STDAPI_(void) UIShowObject(LPCRECT, HDC, BOOL);

//For UIDrawHandles
#define UI_HANDLES_USEINVERSE    0x00000001L
#define UI_HANDLES_NOBORDER      0x00000002L
#define UI_HANDLES_INSIDE        0x00000004L
#define UI_HANDLES_OUTSIDE       0x00000008L

//For UIDrawShading
#define UI_SHADE_FULLRECT        1
#define UI_SHADE_BORDERIN        2
#define UI_SHADE_BORDEROUT       3

//Coordinate Munging
STDAPI_(int)  XformWidthInHimetricToPixels(HDC,  int);
STDAPI_(int)  XformWidthInPixelsToHimetric(HDC,  int);
STDAPI_(int)  XformHeightInHimetricToPixels(HDC, int);
STDAPI_(int)  XformHeightInPixelsToHimetric(HDC, int);

STDAPI_(void) XformRectInPixelsToHimetric(HDC, LPRECT,  LPRECT);
STDAPI_(void) XformRectInHimetricToPixels(HDC, LPRECT,  LPRECT);
STDAPI_(void) XformSizeInPixelsToHimetric(HDC, LPSIZEL, LPSIZEL);
STDAPI_(void) XformSizeInHimetricToPixels(HDC, LPSIZEL, LPSIZEL);

//Helpers
STDAPI_(void)     INOLE_MetafilePictIconFree(HGLOBAL);
STDAPI            INOLE_SwitchDisplayAspect(IUnknown *, LPDWORD, DWORD
                      , HGLOBAL, BOOL, BOOL, IAdviseSink *, BOOL *);
STDAPI            INOLE_SetIconInCache(IUnknown *, HGLOBAL);
STDAPI_(UINT)     INOLE_GetUserTypeOfClass(REFCLSID, UINT, LPTSTR
                      , UINT);
STDAPI            INOLE_DoConvert(IStorage *, REFCLSID);
STDAPI_(LPTSTR)   INOLE_CopyString(LPTSTR);
STDAPI_(HGLOBAL)  INOLE_ObjectDescriptorFromOleObject(IOleObject *
                      , DWORD, POINTL, LPSIZEL);
STDAPI_(HGLOBAL)  INOLE_AllocObjectDescriptor(CLSID, DWORD
                      , SIZEL, POINTL, DWORD, LPTSTR, LPTSTR);
STDAPI_(IStorage *) INOLE_CreateStorageOnHGlobal(DWORD);
STDAPI            INOLE_GetLinkSourceData(IMoniker *, LPCLSID
                      , LPFORMATETC, LPSTGMEDIUM);
STDAPI_(void)     INOLE_RegisterAsRunning(IUnknown *, IMoniker *
                      , DWORD, LPDWORD);
STDAPI_(void)     INOLE_RevokeAsRunning(LPDWORD);
STDAPI_(void)     INOLE_NoteChangeTime(DWORD, FILETIME *, LPTSTR);

/*
 * These are for ANSI compilations on Win32.  Source code assumes
 * a Win32 Unicode environment (or Win16 Ansi).  These macros
 * route Win32 ANSI compiled functions to wrappers which do the
 * proper Unicode conversion.
 */

#ifdef WIN32ANSI
STDAPI INOLE_ConvertStringToANSI(LPCWSTR, LPSTR *);
STDAPI INOLE_StringFromCLSID(REFCLSID, LPSTR *);
STDAPI INOLE_StringFromGUID2(REFGUID, LPSTR, int);
STDAPI INOLE_ProgIDFromCLSID(REFCLSID, LPSTR *);

STDAPI INOLE_ReadFmtUserTypeStg(IStorage *, CLIPFORMAT *, LPSTR *);
STDAPI INOLE_WriteFmtUserTypeStg(IStorage *, CLIPFORMAT, LPSTR);

STDAPI INOLE_StgIsStorageFile(LPCSTR);
STDAPI INOLE_StgCreateDocfile(LPCSTR, DWORD, DWORD, IStorage **);
STDAPI INOLE_StgOpenStorage(LPCSTR, IStorage *, DWORD, SNB
           , DWORD, IStorage **);

STDAPI INOLE_CreateFileMoniker(LPCSTR, LPMONIKER *);
STDAPI INOLE_CreateItemMoniker(LPCSTR, LPCSTR, LPMONIKER *);
STDAPI INOLE_MkParseDisplayName(LPBC, LPCSTR, ULONG *, LPMONIKER *);

STDAPI INOLE_OleCreateLinkToFile(LPCSTR, REFIID, DWORD, LPFORMATETC
           , LPOLECLIENTSITE, LPSTORAGE, LPVOID *);
STDAPI INOLE_OleCreateFromFile(REFCLSID, LPCSTR, REFIID
           , DWORD, LPFORMATETC, LPOLECLIENTSITE, LPSTORAGE, LPVOID *);

#ifndef NOMACROREDIRECT
#undef StringFromCLSID
#define StringFromCLSID(c, pp) INOLE_StringFromCLSID(c, pp)

#undef StringFromGUID2
#define StringFromGUID2(r, p, i) INOLE_StringFromGUID2(r, p, i)

#undef ProgIDFromCLSID
#define ProgIDFromCLSID(c, pp) INOLE_ProgIDFromCLSID(c, pp)

#undef ReadFmtUserTypeStg
#define ReadFmtUserTypeStg(p, c, s) INOLE_ReadFmtUserTypeStg(p, c, s)

#undef WriteFmtUserTypeStg
#define WriteFmtUserTypeStg(p, c, s) INOLE_WriteFmtUserTypeStg(p, c, s)

#undef StgIsStorageFile
#define StgIsStorageFile(s) INOLE_StgIsStorageFile(s)

#undef StgCreateDocfile
#define StgCreateDocfile(a, b, c, d) INOLE_StgCreateDocfile(a, b, c, d)

#undef StgOpenStorage
#define StgOpenStorage(a,b,c,d,e,f) INOLE_StgOpenStorage(a,b,c,d,e,f)

#undef CreateFileMoniker
#define CreateFileMoniker(p, i) INOLE_CreateFileMoniker(p, i)

#undef CreateItemMoniker
#define CreateItemMoniker(p1, p2, i) INOLE_CreateItemMoniker(p1, p2, i)

#undef MkParseDisplayName
#define MkParseDisplayName(b, p, u, i) INOLE_MkParseDisplayName(b, p, u, i)

#undef OleCreateLinkToFile
#define OleCreateLinkToFile(s, i, d, fe, cs, st, pv) INOLE_OleCreateLinkToFile(s, i, d, fe, cs, st, pv)

#undef OleCreateFromFile
#define OleCreateFromFile(c, s, i, d, fe, cs, st, pv) INOLE_OleCreateFromFile(c, s, i, d, fe, cs, st, pv)

#endif

#endif

#endif //_INOLE_H_


IPOLY10.H

/*
 * IPOLY10.H
 * Polyline Object Chapter 10
 *
 * Definition of an IPolyline interface for a Polyline object.
 * This custom interface and is only supported from DLL-based
 * objects.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IPOLY10_H_
#define _IPOLY10_H_

//Versioning.
#define VERSIONMAJOR                2
#define VERSIONMINOR                0
#define VERSIONCURRENT              0x00020000

#define CPOLYLINEPOINTS             20

//Version 2.0 Polyline Structure
typedef struct tagPOLYLINEDATA
    {
    WORD        wVerMaj;                //Major version number
    WORD        wVerMin;                //Minor version number
    WORD        cPoints;                //Number of points
    short       fReserved;              //Previously fDrawEntire
    RECTS       rc;                     //Rectangle of figure
    POINTS      rgpt[CPOLYLINEPOINTS];  //Points on 0-32767 grid

    //Version 2.0 additions
    COLORREF    rgbBackground;          //Background color
    COLORREF    rgbLine;                //Line color
    short       iLineStyle;             //Line style
    } POLYLINEDATA, *PPOLYLINEDATA;

#define CBPOLYLINEDATA   sizeof(POLYLINEDATA)

/*
 * !!Addition:  Clipboard format shared with using applications
 * This name matches those in the stringtable of all Cosmo and CoCosmo
 * versions.  All data is interchangable.
 */
#define SZPOLYLINECLIPFORMAT        TEXT("Polyline Figure")

#ifndef OMIT_POLYLINESINK

#undef  INTERFACE
#define INTERFACE IPolylineAdviseSink10

/*
 * When someone initializes a polyline and is interested in receiving
 * notifications on events, then they provide one of these objects.
 */

DECLARE_INTERFACE_(IPolylineAdviseSink10, IUnknown)
    {
    //IUnknown members
    STDMETHOD(QueryInterface) (THIS_ REFIID, PPVOID) PURE;
    STDMETHOD_(ULONG,AddRef)  (THIS) PURE;
    STDMETHOD_(ULONG,Release) (THIS) PURE;

    //Advise members.
    STDMETHOD_(void,OnPointChange)     (THIS) PURE;
    STDMETHOD_(void,OnSizeChange)      (THIS) PURE;
    STDMETHOD_(void,OnColorChange)     (THIS) PURE;
    STDMETHOD_(void,OnLineStyleChange) (THIS) PURE;
    //OnDataChange replaced with IAdviseSink
    };

typedef IPolylineAdviseSink10 *PPOLYLINEADVISESINK;

#endif //OMIT_POLYLINESINK

#undef  INTERFACE
#define INTERFACE IPolyline10

DECLARE_INTERFACE_(IPolyline10, IUnknown)
    {
    //IUnknown members
    STDMETHOD(QueryInterface) (THIS_ REFIID, PPVOID) PURE;
    STDMETHOD_(ULONG,AddRef)  (THIS) PURE;
    STDMETHOD_(ULONG,Release) (THIS) PURE;

    //IPolyline members

    //File-related members use IPersistStorage, IPersistStreamInit
    //Data transfer members use IDataObject

    //Manipulation members:
    STDMETHOD(Init)   (THIS_ HWND, LPRECT, DWORD, UINT) PURE;
    STDMETHOD(New)    (THIS) PURE;
    STDMETHOD(Undo)   (THIS) PURE;
    STDMETHOD(Window) (THIS_ HWND *) PURE;

    STDMETHOD(RectGet) (THIS_ LPRECT) PURE;
    STDMETHOD(SizeGet) (THIS_ LPRECT) PURE;
    STDMETHOD(RectSet) (THIS_ LPRECT, BOOL) PURE;
    STDMETHOD(SizeSet) (THIS_ LPRECT, BOOL) PURE;

    STDMETHOD(ColorSet) (THIS_ UINT, COLORREF, COLORREF *) PURE;
    STDMETHOD(ColorGet) (THIS_ UINT, COLORREF *) PURE;

    STDMETHOD(LineStyleSet) (THIS_ UINT, UINT *) PURE;
    STDMETHOD(LineStyleGet) (THIS_ UINT *) PURE;
    };

typedef IPolyline10 *PPOLYLINE;

//Error values for data transfer functions
#define POLYLINE_E_INVALIDPOINTER   \
    MAKE_SCODE(SEVERITY_ERROR, FACILITY_ITF, 1)
#define POLYLINE_E_READFAILURE      \
    MAKE_SCODE(SEVERITY_ERROR, FACILITY_ITF, 2)
#define POLYLINE_E_WRITEFAILURE     \
    MAKE_SCODE(SEVERITY_ERROR, FACILITY_ITF, 3)

//Color indices for color member functions
#define POLYLINECOLOR_BACKGROUND    0
#define POLYLINECOLOR_LINE          1

#endif //_IPOLY10_H_


IPOLY5.H

/*
 * IPOLY5.H
 * Polyline Object Chapter 5
 *
 * Definition of an IPolyline interface for a Polyline object.
 * This custom interface and is only supported from DLL-based
 * objects.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IPOLY5_H_
#define _IPOLY5_H_

//Versioning.
#define VERSIONMAJOR                2
#define VERSIONMINOR                0
#define VERSIONCURRENT              0x00020000

#define CPOLYLINEPOINTS             20

//Version 2.0 Polyline Structure
typedef struct tagPOLYLINEDATA
    {
    WORD        wVerMaj;                //Major version number
    WORD        wVerMin;                //Minor version number
    WORD        cPoints;                //Number of points
    short       fReserved;              //Previously fDrawEntire
    RECTS       rc;                     //Rectangle of figure
    POINTS      rgpt[CPOLYLINEPOINTS];  //Points on 0-32767 grid

    //Version 2.0 additions
    COLORREF    rgbBackground;          //Background color
    COLORREF    rgbLine;                //Line color
    short       iLineStyle;             //Line style
    } POLYLINEDATA, *PPOLYLINEDATA;

#define CBPOLYLINEDATA   sizeof(POLYLINEDATA)

//We use the OLE 2.0 macro to define a new interface
#undef  INTERFACE
#define INTERFACE IPolylineAdviseSink5

/*
 * When someone initializes a polyline and is interested in receiving
 * notifications on events, then they provide one of these objects.
 */

DECLARE_INTERFACE_(IPolylineAdviseSink5, IUnknown)
    {
    //IUnknown members
    STDMETHOD(QueryInterface) (THIS_ REFIID, PPVOID) PURE;
    STDMETHOD_(ULONG,AddRef)  (THIS) PURE;
    STDMETHOD_(ULONG,Release) (THIS) PURE;

    //Advise members.
    STDMETHOD_(void,OnPointChange)     (THIS) PURE;
    STDMETHOD_(void,OnSizeChange)      (THIS) PURE;
    STDMETHOD_(void,OnDataChange)      (THIS) PURE;
    STDMETHOD_(void,OnColorChange)     (THIS) PURE;
    STDMETHOD_(void,OnLineStyleChange) (THIS) PURE;
    };

typedef IPolylineAdviseSink5 *PPOLYLINEADVISESINK;

#undef  INTERFACE
#define INTERFACE IPolyline5

DECLARE_INTERFACE_(IPolyline5, IUnknown)
    {
    //IUnknown members
    STDMETHOD(QueryInterface) (THIS_ REFIID, PPVOID) PURE;
    STDMETHOD_(ULONG,AddRef)  (THIS) PURE;
    STDMETHOD_(ULONG,Release) (THIS) PURE;

    //IPolyline members

    //File-related members use IPersistStorage

    //Data transfer members:
    STDMETHOD(DataSet)      (THIS_ PPOLYLINEDATA, BOOL, BOOL) PURE;
    STDMETHOD(DataGet)      (THIS_ PPOLYLINEDATA) PURE;
    STDMETHOD(DataSetMem)   (THIS_ HGLOBAL, BOOL, BOOL, BOOL) PURE;
    STDMETHOD(DataGetMem)   (THIS_ HGLOBAL *) PURE;
    STDMETHOD(RenderBitmap) (THIS_ HBITMAP *) PURE;
    STDMETHOD(RenderMetafile) (THIS_ HMETAFILE *) PURE;
    STDMETHOD(RenderMetafilePict) (THIS_ HGLOBAL *) PURE;

    //Manipulation members:
    STDMETHOD(Init)   (THIS_ HWND, LPRECT, DWORD, UINT) PURE;
    STDMETHOD(New)    (THIS) PURE;
    STDMETHOD(Undo)   (THIS) PURE;
    STDMETHOD(Window) (THIS_ HWND *) PURE;

    STDMETHOD(SetAdvise) (THIS_ PPOLYLINEADVISESINK) PURE;
    STDMETHOD(GetAdvise) (THIS_ PPOLYLINEADVISESINK *) PURE;

    STDMETHOD(RectGet) (THIS_ LPRECT) PURE;
    STDMETHOD(SizeGet) (THIS_ LPRECT) PURE;
    STDMETHOD(RectSet) (THIS_ LPRECT, BOOL) PURE;
    STDMETHOD(SizeSet) (THIS_ LPRECT, BOOL) PURE;

    STDMETHOD(ColorSet) (THIS_ UINT, COLORREF, COLORREF *) PURE;
    STDMETHOD(ColorGet) (THIS_ UINT, COLORREF *) PURE;

    STDMETHOD(LineStyleSet) (THIS_ UINT, UINT *) PURE;
    STDMETHOD(LineStyleGet) (THIS_ UINT *) PURE;
    };

typedef IPolyline5 *PPOLYLINE;

//Error values for data transfer functions
#define POLYLINE_E_INVALIDPOINTER   \
    MAKE_SCODE(SEVERITY_ERROR, FACILITY_ITF, 1)

//Color indices for color member functions
#define POLYLINECOLOR_BACKGROUND    0
#define POLYLINECOLOR_LINE          1

#endif //_IPOLY5_H_


IPOLY8.H

/*
 * IPOLY8.H
 * Polyline Object Chapter 8
 *
 * Definition of an IPolyline interface for a Polyline object.
 * This custom interface and is only supported from DLL-based
 * objects.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IPOLY8_H_
#define _IPOLY8_H_

//Versioning.
#define VERSIONMAJOR                2
#define VERSIONMINOR                0
#define VERSIONCURRENT              0x00020000

#define CPOLYLINEPOINTS             20

//Version 2.0 Polyline Structure
typedef struct tagPOLYLINEDATA
    {
    WORD        wVerMaj;                //Major version number
    WORD        wVerMin;                //Minor version number
    WORD        cPoints;                //Number of points
    short       fReserved;              //Previously fDrawEntire
    RECTS       rc;                     //Rectangle of figure
    POINTS      rgpt[CPOLYLINEPOINTS];  //Points on 0-32767 grid

    //Version 2.0 additions
    COLORREF    rgbBackground;          //Background color
    COLORREF    rgbLine;                //Line color
    short       iLineStyle;             //Line style
    } POLYLINEDATA, *PPOLYLINEDATA;

#define CBPOLYLINEDATA   sizeof(POLYLINEDATA)

#undef  INTERFACE
#define INTERFACE IPolylineAdviseSink8

/*
 * When someone initializes a polyline and is interested in receiving
 * notifications on events, then they provide one of these objects.
 */

DECLARE_INTERFACE_(IPolylineAdviseSink8, IUnknown)
    {
    //IUnknown members
    STDMETHOD(QueryInterface) (THIS_ REFIID, PPVOID) PURE;
    STDMETHOD_(ULONG,AddRef)  (THIS) PURE;
    STDMETHOD_(ULONG,Release) (THIS) PURE;

    //Advise members.
    STDMETHOD_(void,OnPointChange)     (THIS) PURE;
    STDMETHOD_(void,OnSizeChange)      (THIS) PURE;
    STDMETHOD_(void,OnDataChange)      (THIS) PURE;
    STDMETHOD_(void,OnColorChange)     (THIS) PURE;
    STDMETHOD_(void,OnLineStyleChange) (THIS) PURE;
    };

typedef IPolylineAdviseSink8 *PPOLYLINEADVISESINK8;

#undef  INTERFACE
#define INTERFACE IPolyline8

DECLARE_INTERFACE_(IPolyline8, IUnknown)
    {
    //IUnknown members
    STDMETHOD(QueryInterface) (THIS_ REFIID, PPVOID) PURE;
    STDMETHOD_(ULONG,AddRef)  (THIS) PURE;
    STDMETHOD_(ULONG,Release) (THIS) PURE;

    //IPolyline members

    //File-related members use IPersistStorage, IPersistStreamInit

    //Data transfer members:
    STDMETHOD(DataSet)      (THIS_ PPOLYLINEDATA, BOOL, BOOL) PURE;
    STDMETHOD(DataGet)      (THIS_ PPOLYLINEDATA) PURE;
    STDMETHOD(DataSetMem)   (THIS_ HGLOBAL, BOOL, BOOL, BOOL) PURE;
    STDMETHOD(DataGetMem)   (THIS_ HGLOBAL *) PURE;
    STDMETHOD(RenderBitmap) (THIS_ HBITMAP *) PURE;
    STDMETHOD(RenderMetafile) (THIS_ HMETAFILE *) PURE;
    STDMETHOD(RenderMetafilePict) (THIS_ HGLOBAL *) PURE;

    //Manipulation members:
    STDMETHOD(Init)   (THIS_ HWND, LPRECT, DWORD, UINT) PURE;
    STDMETHOD(New)    (THIS) PURE;
    STDMETHOD(Undo)   (THIS) PURE;
    STDMETHOD(Window) (THIS_ HWND *) PURE;

    STDMETHOD(RectGet) (THIS_ LPRECT) PURE;
    STDMETHOD(SizeGet) (THIS_ LPRECT) PURE;
    STDMETHOD(RectSet) (THIS_ LPRECT, BOOL) PURE;
    STDMETHOD(SizeSet) (THIS_ LPRECT, BOOL) PURE;

    STDMETHOD(ColorSet) (THIS_ UINT, COLORREF, COLORREF *) PURE;
    STDMETHOD(ColorGet) (THIS_ UINT, COLORREF *) PURE;

    STDMETHOD(LineStyleSet) (THIS_ UINT, UINT *) PURE;
    STDMETHOD(LineStyleGet) (THIS_ UINT *) PURE;
    };

typedef IPolyline8 *PPOLYLINE8;

//Error values for data transfer functions
#define POLYLINE_E_INVALIDPOINTER   \
    MAKE_SCODE(SEVERITY_ERROR, FACILITY_ITF, 1)
#define POLYLINE_E_READFAILURE      \
    MAKE_SCODE(SEVERITY_ERROR, FACILITY_ITF, 2)
#define POLYLINE_E_WRITEFAILURE     \
    MAKE_SCODE(SEVERITY_ERROR, FACILITY_ITF, 3)

//Color indices for color member functions
#define POLYLINECOLOR_BACKGROUND    0
#define POLYLINECOLOR_LINE          1

#endif //_IPOLY8_H_


STASTRIP.H

/*
 * STASTRIP.H
 * StatStrip Version 1.00
 *
 * Public class and definitions for the StatStrip control.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _STASTRIP_H_
#define _STASTRIP_H_

#include <book1632.h>

#ifdef __cplusplus
extern "C"
    {
#endif //__cplusplus

#define CYSTATSTRIP     23
#define CLASS_STATSTRIP TEXT("StatStrip")

//External APIs.
BOOL WINAPI StatStripMessageMap(HWND, HWND, HINSTANCE, UINT, UINT
    , UINT, UINT, USHORT, USHORT, USHORT, USHORT, USHORT);
void WINAPI StatStripMenuSelect(HWND, WPARAM, LPARAM);
void WINAPI StatStripMessageDisplay(HWND, USHORT);

//Message equivalents
#define STATM_MESSAGEMAP        (WM_USER+0)
#define STATM_MENUSELECT        (WM_USER+1)
#define STATM_MESSAGEDISPLAY    (WM_USER+2)

//Structure for STATM_MESSAGEMAP
typedef struct tagSTATMESSAGEMAPINIT
    {
    HWND        hWndOwner;
    HINSTANCE   hInst;
    UINT        uIDRMap;
    UINT        idsMin;
    UINT        idsMax;
    UINT        cchMax;
    USHORT      uIDPopupMin;
    USHORT      uIDPopupMax;
    USHORT      uIDStatic;
    USHORT      uIDBlank;
    USHORT      uIDSysMenu;
    } STATMESSAGEMAPINIT, FAR *LPSTATMESSAGEMAPINIT;

#ifdef __cplusplus
    }
#endif //__cplusplus

#endif //_STASTRIP_H_


ANSI.CPP

/*
 * ANSI.CPP
 *
 * Utility functions for dealing with OLE APIs that need
 * Unicode strings.  These are only defined when UNICODE
 * is not defined, otherwise INOLE.H simply redefines them
 * as macro pass-throughs to the original OLE functions.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#define NOMACROREDIRECT
#include "inoledll.h"

#ifdef WIN32ANSI

/*
 * ConvertStringAlloc
 *
 * Purpose:
 *  Allocates memory using the task allocator.  Used internally
 *  in this file.
 */

HRESULT ConvertStringAlloc(ULONG ulSize, void **ppv)
    {
    HRESULT     hr;
    IMalloc    *pIMalloc;

    if (NULL==ppv)
        return ResultFromScode(E_INVALIDARG);

    hr=CoGetMalloc(MEMCTX_TASK, &pIMalloc);

    if (FAILED(hr))
        return hr;

    *ppv=pIMalloc->Alloc(ulSize);
    pIMalloc->Release();

    return (NULL==*ppv) ? ResultFromScode(E_OUTOFMEMORY) : NOERROR;
    }

/*
 * INOLE_ConvertStringToANSI
 *
 * Purpose:
 *  Converts the string in pszW to ANSI, allocating the
 *  output buffer pointed to by *ppszA on output.  The
 *  string is allocated with COM's task allocator.
 *
 */

STDAPI INOLE_ConvertStringToANSI(LPCWSTR pszW, LPSTR *ppszA)
    {
    ULONG   cch;
    HRESULT hr;

    //If input is NULL then just return the same
    if (NULL==pszW)
        {
        *ppszA=NULL;
        return NOERROR;
        }

    //This calculates the number of MBCS characters we'll need
    cch=1+WideCharToMultiByte(CP_ACP, 0, pszW, -1, NULL, 0, NULL, NULL);

    hr=ConvertStringAlloc(cch, (void **)ppszA);

    if (FAILED(hr))
        return hr;

    WideCharToMultiByte(CP_ACP, 0, pszW, -1, *ppszA, cch, NULL, NULL);
    return NOERROR;
    }

/*
 * INOLE_StringFromCLSID
 */

STDAPI INOLE_StringFromCLSID(REFCLSID clsID, LPSTR *ppszCLSID)
    {
    LPWSTR  psz;
    HRESULT hr;

    if (NULL==ppszCLSID)
        return ResultFromScode(E_INVALIDARG);

    hr=StringFromCLSID(clsID, &psz);

    if (FAILED(hr))
        return hr;

    hr=INOLE_ConvertStringToANSI(psz, ppszCLSID);
    CoTaskMemFree((void *)psz);
    return hr;
    }

/*
 * INOLE_StringFromGUID2
 */

STDAPI INOLE_StringFromGUID2(REFGUID guid, LPSTR pszGUID, int cch)
    {
    LPSTR   psz;
    HRESULT hr;

    if (NULL==pszGUID)
        return ResultFromScode(E_INVALIDARG);

    hr=INOLE_StringFromCLSID(guid, &psz);

    if (FAILED(hr))
        return hr;

    lstrcpyn(pszGUID, psz, cch);
    CoTaskMemFree((void *)psz);
    return hr;
    }

/*
 * INOLE_ProgIDFromCLSID
 */

STDAPI INOLE_ProgIDFromCLSID(REFCLSID clsID, LPSTR *ppszProgID)
    {
    LPWSTR  psz;
    HRESULT hr;

    if (NULL==ppszProgID)
return ResultFromScode(E_INVALIDARG);

    hr=ProgIDFromCLSID(clsID, &psz);

    if (FAILED(hr))
        return hr;

    hr=INOLE_ConvertStringToANSI(psz, ppszProgID);
    CoTaskMemFree(psz);
    return hr;
    }

/*
 * INOLE_ReadFmtUserTypeStg
 * INOLE_WriteFmtUserTypeStg
 */

STDAPI INOLE_ReadFmtUserTypeStg(IStorage *pIStorage
    , CLIPFORMAT *pcf, LPSTR *ppszUserType)
    {
    HRESULT     hr;
    LPOLESTR    pszUserType;

    if (NULL==ppszUserType)
        return ResultFromScode(E_INVALIDARG);

    hr=ReadFmtUserTypeStg(pIStorage, pcf, &pszUserType);

    if (FAILED(hr))
        return hr;

    if (ppszUserType)
        {
        hr=INOLE_ConvertStringToANSI(pszUserType, ppszUserType);
        CoTaskMemFree(pszUserType);
        }

    return hr;
    }

STDAPI INOLE_WriteFmtUserTypeStg(IStorage *pIStorage, CLIPFORMAT cf
    , LPSTR pszUserType)
    {
    OLECHAR     szType[512];
    HRESULT     hr;

    if (NULL==pszUserType)
        return ResultFromScode(E_INVALIDARG);

    MultiByteToWideChar(CP_ACP, 0, pszUserType, -1, szType, 512);
    hr=WriteFmtUserTypeStg(pIStorage, cf, szType);

    return hr;
    }

/*
 * INOLE_StgIsStorageFile
 * INOLE_StgCreateDocfile
 * INOLE_StgOpenStorage
 */

STDAPI INOLE_StgIsStorageFile(LPCSTR pszName)
    {
    OLECHAR     szTemp[MAX_PATH];

    MultiByteToWideChar(CP_ACP, 0, pszName, -1
        , szTemp, MAX_PATH);
    return StgIsStorageFile(szTemp);
    }

STDAPI INOLE_StgCreateDocfile(LPCSTR pszNameA, DWORD grfMode
    , DWORD reserved, IStorage **ppIStorage)
    {
    OLECHAR     szTemp[MAX_PATH];
    LPOLESTR    pszName;

    *ppIStorage=NULL;

    if (NULL!=pszNameA)
        {
        MultiByteToWideChar(CP_ACP, 0, pszNameA, -1
            , szTemp, MAX_PATH);
        pszName=szTemp;
        }
else
        pszName=NULL;

    return StgCreateDocfile(pszName, grfMode, reserved
        , ppIStorage);
    }

STDAPI INOLE_StgOpenStorage(LPCSTR pszNameA, IStorage *pStgPri
    , DWORD grfMode, SNB snbEx, DWORD reserved
    , IStorage * *ppIStorage)
    {
    OLECHAR   szTemp[MAX_PATH];
    LPOLESTR  pszName;

    *ppIStorage=NULL;

    if (NULL!=pszNameA)
        {
        MultiByteToWideChar(CP_ACP, 0, pszNameA, -1
            , szTemp, MAX_PATH);
        pszName= szTemp;
        }
else
        pszName=NULL;

    return StgOpenStorage(pszName, pStgPri, grfMode, snbEx
        , reserved, ppIStorage);
    }

/*
 * INOLE_CreateFileMoniker
 * INOLE_CreateItemMoniker
 * INOLE_MkParseDisplayName
 */

STDAPI INOLE_CreateFileMoniker(LPCSTR pszPathNameA, LPMONIKER *ppmk)
    {
    OLECHAR     szTemp[MAX_PATH];

    if (NULL==ppmk)
        return ResultFromScode(E_INVALIDARG);

    *ppmk=NULL;

    MultiByteToWideChar(CP_ACP, 0, pszPathNameA, -1
        , szTemp, MAX_PATH);

    return CreateFileMoniker(szTemp, ppmk);
    }

STDAPI INOLE_CreateItemMoniker(LPCSTR pszDelimA, LPCSTR pszItemA
    , LPMONIKER *ppmk)
    {
    OLECHAR     szItem[MAX_PATH];   //Some assumptions about string length
    OLECHAR     szDelim[20];

    if (NULL==ppmk)
        return ResultFromScode(E_INVALIDARG);

    *ppmk=NULL;

    MultiByteToWideChar(CP_ACP, 0, pszItemA, -1
        , szItem, MAX_PATH);
    MultiByteToWideChar(CP_ACP, 0, pszDelimA, -1
        , szDelim, 20);

    return CreateItemMoniker(szDelim, szItem, ppmk);
    }

STDAPI INOLE_MkParseDisplayName(LPBC pbc, LPCSTR pszUserNameA
    , ULONG *pchEaten, LPMONIKER *ppmk)
    {
    OLECHAR     szTemp[512];    //Assumption on string length

    if (NULL==ppmk)
        return ResultFromScode(E_INVALIDARG);

    *ppmk=NULL;

    MultiByteToWideChar(CP_ACP, 0, pszUserNameA, -1
        , szTemp, 512);

    return MkParseDisplayName(pbc, szTemp, pchEaten, ppmk);
    }

/*
 * INOLE_OleCreateLinkToFile
 * INOLE_OleCreateFromFile
 */

STDAPI INOLE_OleCreateLinkToFile(LPCSTR lpszFileName, REFIID riid
    , DWORD renderopt, LPFORMATETC lpFormatEtc
    , LPOLECLIENTSITE pClientSite, LPSTORAGE pStg, LPVOID *ppvObj)
    {
    OLECHAR     szFile[512];    //Assumption on string length

    MultiByteToWideChar(CP_ACP, 0, lpszFileName, -1, szFile, 512);
    return OleCreateLinkToFile(szFile, riid, renderopt, lpFormatEtc
        , pClientSite, pStg, ppvObj);
    }

STDAPI INOLE_OleCreateFromFile(REFCLSID rclsid, LPCSTR lpszFileName
    , REFIID riid, DWORD renderopt, LPFORMATETC lpFormatEtc
    , LPOLECLIENTSITE pClientSite, LPSTORAGE pStg, LPVOID *ppvObj)
    {
    OLECHAR     szFile[512];    //Assumption on string length

    MultiByteToWideChar(CP_ACP, 0, lpszFileName, -1, szFile, 512);
    return OleCreateFromFile(rclsid, szFile, riid, renderopt, lpFormatEtc
        , pClientSite, pStg, ppvObj);
    }

#else

//Do-nothing functions so we can at least export them.

#ifndef WIN32
STDAPI INOLE_ConvertStringToANSI(LPCSTR a, LPSTR *b) {return NOERROR;}
#else
STDAPI INOLE_ConvertStringToANSI(LPCWSTR a, LPSTR *b) {return NOERROR;}
#endif
STDAPI INOLE_StringFromCLSID(REFCLSID a, LPSTR *b) {return NOERROR;}
STDAPI INOLE_StringFromGUID2(REFGUID a, LPSTR b, int c) {return NOERROR;}
STDAPI INOLE_ProgIDFromCLSID(REFCLSID a, LPSTR *b) {return NOERROR;}

STDAPI INOLE_ReadFmtUserTypeStg(IStorage *a, CLIPFORMAT *b, LPSTR *c) {return NOERROR;}
STDAPI INOLE_WriteFmtUserTypeStg(IStorage *a, CLIPFORMAT b, LPSTR c) {return NOERROR;}

STDAPI INOLE_StgIsStorageFile(LPCSTR a) {return NOERROR;}
STDAPI INOLE_StgCreateDocfile(LPCSTR a, DWORD b, DWORD c, IStorage ** d) {return NOERROR;}
STDAPI INOLE_StgOpenStorage(LPCSTR a, IStorage *b, DWORD c, SNB d
           , DWORD e, IStorage **f) {return NOERROR;}

STDAPI INOLE_CreateFileMoniker(LPCSTR, LPMONIKER *) {return NOERROR;}
STDAPI INOLE_CreateItemMoniker(LPCSTR, LPCSTR, LPMONIKER *) {return NOERROR;}
STDAPI INOLE_MkParseDisplayName(LPBC, LPCSTR, ULONG *, LPMONIKER *) {return NOERROR;}

STDAPI INOLE_OleCreateLinkToFile(LPCOLESTR, REFIID, DWORD, LPFORMATETC
    , LPOLECLIENTSITE, LPSTORAGE, LPVOID *) {return NOERROR;}

STDAPI INOLE_OleCreateFromFile(REFCLSID, LPCOLESTR, REFIID
    , DWORD, LPFORMATETC, LPOLECLIENTSITE, LPSTORAGE, LPVOID *) {return NOERROR;}

#endif


BTTNCUR.C

/*
 * BTTNCUR.C
 * Buttons & Cursors
 *
 * Public functions to generate different states of toolbar buttons
 * from a single bitmap.  States are normal, pressed, checked, and
 * disabled.
 *
 * Copyright (c)1992-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "inoledll.h"

//Cache GDI objects to speed drawing.
HDC     g_hDCGlyphs    = NULL;
HDC     g_hDCMono      = NULL;
HBRUSH  g_hBrushDither = NULL;

//Standard images to use in case caller doesn't provide them
HBITMAP g_rghBmpStandardImages[3];

//Standard button colors.
const COLORREF g_crStandard[4]={ RGB(0, 0, 0)      //STDCOLOR_BLACK
                   , RGB(128, 128, 128)            //STDCOLOR_DKGRAY
                   , RGB(192, 192, 192)            //STDCOLOR_LTGRAY
                   , RGB(255, 255, 255)};          //STDCOLOR_WHITE

COLORREF g_crSys[5];

/*
 * Mapping from image identifier to button type (command/attribute).
 * Version 1.00 of this DLL has no attribute images defined, so
 * the code will only support three states for each command
 * button.  Any state is, however, valid for an application
 * defined image.
 */

UINT mpButtonType[TOOLIMAGE_MAX-TOOLIMAGE_MIN+1]=
        {
        BUTTONTYPE_COMMAND, BUTTONTYPE_COMMAND, BUTTONTYPE_COMMAND,
        BUTTONTYPE_COMMAND, BUTTONTYPE_COMMAND, BUTTONTYPE_COMMAND,
        BUTTONTYPE_COMMAND, BUTTONTYPE_COMMAND, BUTTONTYPE_COMMAND
        };

/*
 * UIToolConfigureForDisplay
 * Public API
 *
 * Purpose:
 *  Initializes the library to scale button images for the display
 *  type.  Without calling this function the library defaults to 96
 *  DPI (VGA).  By calling this function an application acknowledges
 *  that it must use the data returned from this function to
 *  configure itself for the display.
 *
 * Parameters:
 *  lpDD            LPTOOLDISPLAYDATA to fill with the display-
 *                  sensitive size values.
 *
 * Return Value:
 *  BOOL            TRUE if the sizes were obtained, FALSE otherwise.
 */

BOOL WINAPI UIToolConfigureForDisplay(LPTOOLDISPLAYDATA lpDD)
    {
    int         cy;
    HDC         hDC;

    if (NULL==lpDD || IsBadWritePtr(lpDD, sizeof(TOOLDISPLAYDATA)))
        return FALSE;

    /*
     * Determine the aspect ratio of the display we're currently
     * running on and calculate the necessary information.
     *
     * By retrieving the logical Y extent of the display driver, you
     * only have limited possibilities:
     *      LOGPIXELSY      Display
     *      ----------------------------------------
     *         48             CGA    (unsupported)
     *         72             EGA
     *         96             VGA
     *        120             8514/a (i.e. HiRes VGA)
     */

    hDC=GetDC(NULL);

    if (NULL==hDC)
        return FALSE;

    cy=GetDeviceCaps(hDC, LOGPIXELSY);
    ReleaseDC(NULL, hDC);

    /*
     * Instead of single comparisons, check ranges instead, so in
     * case we get something funky, we'll act reasonable.
     */
    if (72 >=cy)
        {
        lpDD->uDPI     =72;
        lpDD->cyBar    =CYBUTTONBAR72;
        lpDD->cxButton =TOOLBUTTON_STD72WIDTH;
        lpDD->cyButton =TOOLBUTTON_STD72HEIGHT;
        lpDD->cxImage  =TOOLBUTTON_STD72IMAGEWIDTH;
        lpDD->cyImage  =TOOLBUTTON_STD72IMAGEHEIGHT;
        lpDD->uIDImages=IDB_STANDARDIMAGES72;
        }
    else
        {
        if (72 < cy && 120 > cy)
            {
            lpDD->uDPI     =96;
            lpDD->cyBar    =CYBUTTONBAR96;
            lpDD->cxButton =TOOLBUTTON_STD96WIDTH;
            lpDD->cyButton =TOOLBUTTON_STD96HEIGHT;
            lpDD->cxImage  =TOOLBUTTON_STD96IMAGEWIDTH;
            lpDD->cyImage  =TOOLBUTTON_STD96IMAGEHEIGHT;
            lpDD->uIDImages=IDB_STANDARDIMAGES96;
            }
        else
            {
            lpDD->uDPI     =120;
            lpDD->cyBar    =CYBUTTONBAR120;
            lpDD->cxButton =TOOLBUTTON_STD120WIDTH;
            lpDD->cyButton =TOOLBUTTON_STD120HEIGHT;
            lpDD->cxImage  =TOOLBUTTON_STD120IMAGEWIDTH;
            lpDD->cyImage  =TOOLBUTTON_STD120IMAGEHEIGHT;
            lpDD->uIDImages=IDB_STANDARDIMAGES120;
            }
        }

    return TRUE;
    }

/*
 * ToolButtonInit
 * Internal
 *
 * Purpose:
 *  Initializes GDI objects for drawing images through
 *  UIToolButtonDraw. If the function fails, the function has
 *  already performed proper cleanup.
 *
 * Parameters:
 *  hInst           HINSTANCE of the DLL
 *
 * Return Value:
 *  BOOL            TRUE if initialization succeeded.  FALSE
 *                  otherwise.
 */

BOOL ToolButtonInit(HINSTANCE hInst)
    {
    UINT        i;

    //DC for BitBltting the image (the glyph)
    g_hDCGlyphs=CreateCompatibleDC(NULL);

    //Create a monochrome DC and a brush for doing pattern dithering.
    g_hDCMono=CreateCompatibleDC(NULL);

    g_hBrushDither=HBrushDitherCreate(GetSysColor(COLOR_BTNFACE)
        , GetSysColor(COLOR_BTNHIGHLIGHT));

    for (i=0; i < 3; i++)
        {
        g_rghBmpStandardImages[i]=LoadBitmap(hInst
            , MAKEINTRESOURCE(IDB_STANDARDIMAGESMIN+i));
        }

    if (NULL==g_hDCGlyphs || NULL==g_hDCMono
        || NULL==g_hBrushDither || NULL==g_rghBmpStandardImages[0])
        {
        //On failure, cleanup whatever might have been allocated.
        ToolButtonFree();
        return FALSE;
        }

    return TRUE;
    }

/*
 * ToolButtonFree
 * Internal
 *
 * Purpose:
 *  Free all GDI allocations made during initialization.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void ToolButtonFree(void)
    {
    UINT        i;

    if (NULL!=g_hDCMono)
        DeleteDC(g_hDCMono);

    g_hDCMono=NULL;

    if (NULL!=g_hDCGlyphs)
        DeleteDC(g_hDCGlyphs);

    g_hDCGlyphs=NULL;

    if (NULL!=g_hBrushDither)
        DeleteObject(g_hBrushDither);

    g_hBrushDither=NULL;

    for (i=0; i < 3; i++)
        {
        if (NULL!=g_rghBmpStandardImages[i])
            DeleteObject(g_rghBmpStandardImages[i]);

        g_rghBmpStandardImages[i]=NULL;
        }

    return;
    }

/*
 * HBrushDitherCreate
 * Internal
 *
 * Purpose:
 *  Creates and returns a handle to a pattern brush created from
 *  an 8*8 monochrome pattern bitmap.  We use the button face and
 *  highlight colors to indicate the resulting colors of a PatBlt
 *  using this brush.
 *
 * Parameters:
 *  rgbFace         COLORREF of the button face color.
 *  rgbHilight      COLORREF of the button highlight color.
 *
 * Return Value:
 *  HBITMAP         Handle to the dither bitmap.
 */

HBRUSH HBrushDitherCreate(COLORREF rgbFace, COLORREF rgbHilight)
    {
    struct  //BITMAPINFO with 16 colors
        {
        BITMAPINFOHEADER    bmiHeader;
        RGBQUAD             bmiColors[16];
        } bmi;

    HBRUSH          hBrush=NULL;
    DWORD           patGray[8];
    HDC             hDC;
    HBITMAP         hBmp;
    static COLORREF rgbFaceOld   =0xFFFFFFFF;  //Initially impossible
    static COLORREF rgbHilightOld=0xFFFFFFFF;  //Initially impossible

    /*
     * If the colors haven't changed from last time, just return the
     * existing brush.
     */
    if (rgbFace==rgbFaceOld && rgbHilight==rgbHilightOld)
        return g_hBrushDither;

    rgbFaceOld=rgbFace;
    rgbHilightOld=rgbHilight;

    /*
     * We're going to create an 8*8 brush for PatBlt using the
     * button face color and button highlight color.  We use this
     * brush to affect the pressed state and the disabled state.
     */
    bmi.bmiHeader.biSize         = sizeof(BITMAPINFOHEADER);
    bmi.bmiHeader.biWidth        = 8;
    bmi.bmiHeader.biHeight       = 8;
    bmi.bmiHeader.biPlanes       = 1;
    bmi.bmiHeader.biBitCount     = 1;
    bmi.bmiHeader.biCompression  = BI_RGB;
    bmi.bmiHeader.biSizeImage    = 0;
    bmi.bmiHeader.biXPelsPerMeter= 0;
    bmi.bmiHeader.biYPelsPerMeter= 0;
    bmi.bmiHeader.biClrUsed      = 0;
    bmi.bmiHeader.biClrImportant = 0;

    bmi.bmiColors[0].rgbBlue     = GetBValue(rgbFace);
    bmi.bmiColors[0].rgbGreen    = GetGValue(rgbFace);
    bmi.bmiColors[0].rgbRed      = GetRValue(rgbFace);
    bmi.bmiColors[0].rgbReserved = 0;

    bmi.bmiColors[1].rgbBlue     = GetBValue(rgbHilight);
    bmi.bmiColors[1].rgbGreen    = GetGValue(rgbHilight);
    bmi.bmiColors[1].rgbRed      = GetRValue(rgbHilight);
    bmi.bmiColors[1].rgbReserved = 0;

    //Create the byte array for CreateDIBitmap.
    patGray[6]=patGray[4]=patGray[2]=patGray[0]=0x5555AAAAL;
    patGray[7]=patGray[5]=patGray[3]=patGray[1]=0xAAAA5555L;

    //Create the bitmap
    hDC=GetDC(NULL);
    hBmp=CreateDIBitmap(hDC, &bmi.bmiHeader, CBM_INIT, patGray
                        , (LPBITMAPINFO)&bmi, DIB_RGB_COLORS);
    ReleaseDC(NULL, hDC);

    //Create the brush from the bitmap
    if (NULL!=hBmp)
        {
        hBrush=CreatePatternBrush(hBmp);
        DeleteObject(hBmp);
        }

    /*
     * If we could recreate a brush, clean up and make it the current
     * pattern.  Otherwise the best we can do it return the old one,
     * which will be colored wrong, but at least it works.
     */
    if (NULL!=hBrush)
        {
        if (NULL!=g_hBrushDither)
            DeleteObject(g_hBrushDither);

        g_hBrushDither=hBrush;
        }

    return g_hBrushDither;
    }

/*
 * UIToolButtonDraw
 * Public API
 *
 * Purpose:
 *  Draws the complete image of a toolbar-style button with a given
 *  image in the center and in a specific state.  The button is drawn
 *  on a specified hDC at a given location, so this function is
 *  useful on standard owner-draw buttons as well as on toolbar
 *  controls that have only one window but show images of multiple
 *  buttons.
 *
 * Parameters:
 *  hDC             HDC on which to draw.
 *  x, y            int coordinates at which to draw.
 *  dx, dy          int dimensions of the *button*, not the image.
 *  hBmp            HBITMAP from which to draw the image.
 *  bmx, bmy        int dimensions of each bitmap in hBmp.  If hBmp
 *                  is NULL then these are forced to the standard
 *                  sizes.
 *  iImage          int index to the image to draw in the button
 *  uStateIn        UINT containing the state index for the button
 *                  and the color control bits.
 *  pTDD            LPTOOLDISPLAYDATA containing display
 *                  configuration. Can be NULL if hBmp is non-NULL.
 *
 * Return Value:
 *  BOOL            TRUE if drawing succeeded, FALSE otherwise
 *                  meaning that hDC is NULL or hBmp is NULL and
 *                  iImage is not a valid index for a standard image.
 */

BOOL WINAPI UIToolButtonDraw(HDC hDC, int x, int y, int dx, int dy
    , HBITMAP hBmp, int bmx, int bmy, int iImage, UINT uStateIn
    , LPTOOLDISPLAYDATA pTDD)
    {
    UINT            uState=(UINT)LOBYTE((WORD)uStateIn);
    UINT            uColors=(UINT)HIBYTE((WORD)uStateIn
                        & PRESERVE_ALL);
    int             xOffsetGlyph, yOffsetGlyph;
    int             i, iSaveDC;
    HDC             hMemDC;
    HGDIOBJ         hObj;
    HBRUSH          hBR;
    HBITMAP         hBmpT;
    HBITMAP         hBmpMono;
    HBITMAP         hBmpMonoOrg;
    HBITMAP         hBmpSave=NULL;
    TOOLDISPLAYDATA tdd;

    if (NULL==hDC)
        return FALSE;

    if (NULL==pTDD)
        {
        pTDD=&tdd;
        UIToolConfigureForDisplay(pTDD);
        }

    /*
     * If we're given no image bitmap, then use the standard and
     * validate the image index.  We also enforce the standard
     * bitmap size and the size of the button (as requested by
     * User Interface designers).
     */
    if (NULL==hBmp && !(uState & BUTTONGROUP_BLANK))
        {
        i=pTDD->uIDImages-IDB_STANDARDIMAGESMIN;
        hBmp=g_rghBmpStandardImages[i];

        bmx=pTDD->cxImage;            //Force bitmap dimensions
        bmy=pTDD->cyImage;

        dx=pTDD->cxButton;            //Force button dimensions
        dy=pTDD->cyButton;

        if (iImage > TOOLIMAGE_MAX)
            return FALSE;

        /*
         * If we are using a standard command button, verify that
         * the state does not contain the LIGHTFACE group which
         * only applies to attribute buttons.
         */
        if (BUTTONTYPE_COMMAND==mpButtonType[iImage]
            && (uState & BUTTONGROUP_LIGHTFACE))
            return FALSE;
        }

    //Create a dithered bitmap.
    hBmpMono=CreateBitmap(dx-2, dy-2, 1, 1, NULL);

    if (NULL==hBmpMono)
        return FALSE;

    hBmpMonoOrg=(HBITMAP)SelectObject(g_hDCMono,  hBmpMono);

    //Save the DC state before we munge on it.
    iSaveDC=SaveDC(hDC);

    /*
     * Draw a button sans image.  This also fills g_crSys with the
     * system colors for us which has space for five colors.
     * We don't use the fifth, the frame color, in this function.
     */
    DrawBlankButton(hDC, x, y, dx, dy
        , (BOOL)(uState & BUTTONGROUP_DOWN), g_crSys);

    //Shift coordinates to account for the button's border
    x++;
    y++;
    dx-=2;
    dy-=2;

    /*
     * Determine the offset necessary to center the image but also
     * reflect the pushed-in state, which means just adding 1 to
     * the up state.
     */
    i=(uState & BUTTONGROUP_DOWN) ? 1 : 0;
    xOffsetGlyph=((dx-bmx) >> 1)+i;
    yOffsetGlyph=((dy-bmy) >> 1)+i;

    //Select given image into the glyph DC before calling MaskCreate
    if (NULL!=hBmp)
        hBmpSave=(HBITMAP)SelectObject(g_hDCGlyphs, hBmp);

    /*
     * Draw the face on the button.  If we have an up or [mouse]down
     * button then we can just draw it as-is.  For indeterminate,
     * disabled, or down disabled we have to gray the image and
     * possibly add a white shadow to it (disabled/down disabled).
     *
     * Also note that for the intermediate state we first draw the
     * normal up state, then proceed to add disabling looking
     * highlights.
     */

    //Up, mouse down, down, indeterminate
    if ((uState & BUTTONGROUP_ACTIVE)
        && !(uState & BUTTONGROUP_BLANK))
        {
        BOOL            fColorsSame=TRUE;

        /*
         * In here we pay close attention to the system colors.
         * Where the source image is black, we paint COLOR_BTNTEXT.
         * Where light gray, we paint COLOR_BTNFACE.  Where dark gray
         * we paint COLOR_BTNSHADOW, and where white we paint
         * COLOR_BTNHILIGHT.
         *
         * The uColors variable contains flags to prevent color
         * conversion.  To do a little optimization, we just do a
         * single BitBlt if we're preserving all colors or if no
         * colors are different than the standards, which is by far
         * the most common case.  Otherwise, cycle through the four
         * colors we can convert and do a BitBlt that converts it to
         * the system color.
         */

        //See what colors are different.
        for (i=STDCOLOR_BLACK; i<=STDCOLOR_WHITE; i++)
            fColorsSame &= (g_crSys[i]==g_crStandard[i]);

        if (PRESERVE_ALL==uColors || fColorsSame)
            {
            BitBlt(hDC, x+xOffsetGlyph, y+yOffsetGlyph, bmx, bmy
                , g_hDCGlyphs, iImage*bmx, 0, SRCCOPY);
            }
        else
            {
            /*
             * Cycle through hard-coded colors and create a mask that
             * has all regions of that color in white and all other
             * regions black.  Then we select a pattern brush of the
             * color to convert to: if we aren't converting the color
             * then we use a brush of the standard hard-coded color,
             * otherwise we use the actual system color.  The
             * ROP_DSPDxax means that anything that's 1's in the mask
             * get the pattern, anything that's 0 is unchanged in
             * the destination.
             *
             * To prevent too many Blts to the screen, we use an
             * intermediate bitmap and DC.
             */

            hMemDC=CreateCompatibleDC(hDC);

            //Make sure conversion of monochrome to color stays B&W
            SetTextColor(hMemDC, 0L);                 //mono 0's->0
            SetBkColor(hMemDC, (COLORREF)0x00FFFFFF); //mono 1's->1

            hBmpT=CreateCompatibleBitmap(hDC, bmx, bmy);
            SelectObject(hMemDC, hBmpT);

            //Copy the unmodified bitmap to the temporary bitmap
            BitBlt(hMemDC, 0, 0, bmx, bmy, g_hDCGlyphs, iImage*bmx
                , 0, SRCCOPY);

            for (i=STDCOLOR_BLACK; i<=STDCOLOR_WHITE; i++)
                {
                //Convert pixels of convert color to 1's in the mask
                SetBkColor(g_hDCGlyphs, g_crStandard[i]);
                BitBlt(g_hDCMono, 0, 0, bmx, bmy, g_hDCGlyphs, iImage*bmx
                    , 0, SRCCOPY);

                //Preserve or modify the color depending on the flag.
                hBR=CreateSolidBrush((uColors & (1 << i))
                    ? g_crStandard[i] : g_crSys[i]);

                if (NULL!=hBR)
                    {
                    hObj=SelectObject(hMemDC, hBR);

                    if (NULL!=hObj)
                        {
                        BitBlt(hMemDC, 0, 0, dx-1, dy-1, g_hDCMono, 0
                            , 0, ROP_DSPDxax);
                        SelectObject(hMemDC, hObj);
                        }

                    DeleteObject(hBR);
                    }
                }

            //Now put the final version on the display and clean up
            BitBlt(hDC, x+xOffsetGlyph, y+yOffsetGlyph, dx-1, dy-1
                   , hMemDC, 0, 0, SRCCOPY);

            DeleteDC(hMemDC);
            DeleteObject(hBmpT);

            }
        }

    //Disabled and indeterminate states (unless we're blank)
    if ((uState & BUTTONGROUP_DISABLED
        || ATTRIBUTEBUTTON_INDETERMINATE==uState)
        && !(uState & BUTTONGROUP_BLANK))
        {
        //Grayed state (up or down, no difference)
        MaskCreate(iImage, dx, dy, bmx, bmy, xOffsetGlyph
            , yOffsetGlyph, 0);

        //Make sure conversion of monochrome to color stays B&W
        SetTextColor(hDC, 0L);                     //0's in mono -> 0
        SetBkColor(hDC, (COLORREF)0x00FFFFFF);     //1's in mono -> 1

        //If we're disabled, up or down, draw the highlighted shadow.
        if (uState & BUTTONGROUP_DISABLED)
            {
            hBR=CreateSolidBrush(g_crSys[SYSCOLOR_HILIGHT]);

            if (NULL!=hBR)
                {
                hObj=SelectObject(hDC, hBR);

                if (NULL!=hObj)
                    {
                    //Draw hilight color where we have 0's in mask
                    BitBlt(hDC, x+1, y+1, dx-2, dy-2, g_hDCMono, 0, 0
                        , ROP_PSDPxax);
                    SelectObject(hDC, hObj);
                    }
                DeleteObject(hBR);
                }
            }

        //Draw the gray image.
        hBR=CreateSolidBrush(g_crSys[SYSCOLOR_SHADOW]);

        if (NULL!=hBR)
            {
            hObj=SelectObject(hDC, hBR);

            if (NULL!=hObj)
                {
                //Draw the shadow color where we have 0's in the mask
                BitBlt(hDC, x, y, dx-2, dy-2, g_hDCMono, 0, 0
                    , ROP_PSDPxax);
                SelectObject(hDC, hObj);
                }

            DeleteObject(hBR);
            }
        }

    //If the button is selected do the dither brush avoiding glyph
    if (uState & BUTTONGROUP_LIGHTFACE)
        {
        HBRUSH      hBRDither;

        /*
         * Get the dither brush.  This function will recreate it if
         * necessary or return the global one if the colors already
         * match.
         */
        hBRDither=HBrushDitherCreate(g_crSys[SYSCOLOR_FACE]
            , g_crSys[SYSCOLOR_HILIGHT]);
        hObj=SelectObject(hDC, hBRDither);

        if (NULL!=hObj)
            {
            /*
             * The mask we create now determines where the dithering
             * ends up.  In the down disabled state, we have to
             * preserve the highlighted shadow, so the mask we create
             * must have two masks of the original glyph, one of them
             * offset by one pixel in both x & y.  For the
             * indeterminate state, we have to mask all highlighted
             * areas.  The state passed to MaskCreate matters here
             * (we've used zero before).
             */
            MaskCreate(iImage, dx, dy, bmx, bmy
                       , xOffsetGlyph-1, yOffsetGlyph-1, uState);

            //Convert monochrome masks to B&W color bitmap in BitBlt.
            SetTextColor(hDC, 0L);
            SetBkColor(hDC, (COLORREF)0x00FFFFFF);

            /*
             * Only draw the dither brush where the mask is 1's.  For
             * the indeterminate state we have to not overdraw the
             * shadow highlight so we use dx-3, dy-3 instead of dx-1
             * and dy-1.  We do this whether or not we're blank.
             */
            i=(ATTRIBUTEBUTTON_INDETERMINATE==uState
               || BLANKBUTTON_INDETERMINATE==uState) ? 3 : 1;

            BitBlt(hDC, x+1, y+1, dx-i, dy-i, g_hDCMono, 0, 0
                , ROP_DSPDxax);
            SelectObject(hDC, hObj);
            }

        //DO NOT delete hBRDither!  It's a shared global.
        }

    //Cleanup g_hDCGlyphs:  Must do AFTER calling MaskCreate
    if (NULL!=hBmpSave)
        SelectObject(g_hDCGlyphs, hBmpSave);

    SelectObject(g_hDCMono,   hBmpMonoOrg);
    DeleteObject(hBmpMono);

    //Restore everything in the DC.
    RestoreDC(hDC, iSaveDC);
    return TRUE;
    }

/*
 * DrawBlankButton
 *
 * Purpose:
 *  Draws a button with no face using the current system colors in
 *  either an up or down state.
 *
 * Parameters:
 *  hDC             HDC on which to draw
 *  x, y            int coordinates where we start drawing
 *  dx,dy           int size of the button
 *  fDown           BOOL indicating the up or down state of the
 *                  button
 *  pcr             COLORREF * to five colors in which we store text,
 *                  shadow, face, highlight, and frame colors.  This
 *                  is a matter of convenience for the caller, since
 *                  we have to load these colors anyway we might as
 *                  well send them back.
 *
 * Return Value:
 *  None
 */

void DrawBlankButton(HDC hDC, int x, int y, int dx, int dy
    , BOOL fDown, COLORREF *pcr)
    {
    //Get the current system colors for buttons.
    pcr[0]=GetSysColor(COLOR_BTNTEXT);
    pcr[1]=GetSysColor(COLOR_BTNSHADOW);
    pcr[2]=GetSysColor(COLOR_BTNFACE);
    pcr[3]=GetSysColor(COLOR_BTNHIGHLIGHT);
    pcr[4]=GetSysColor(COLOR_WINDOWFRAME);

    //Draw the border around the button.
    PatB(hDC, x+1,    y,      dx-2, 1,    pcr[4]);
    PatB(hDC, x+1,    y+dy-1, dx-2, 1,    pcr[4]);
    PatB(hDC, x,      y+1,    1,    dy-2, pcr[4]);
    PatB(hDC, x+dx-1, y+1,    1,    dy-2, pcr[4]);

    //Shift coordinates to account for the border we just drew
    x++;
    y++;
    dx-=2;
    dy-=2;

    //Paint the interior grey as a default.
    PatB(hDC, x, y, dx, dy, pcr[2]);

    /*
     * Draw shadows and highlights.  The DOWN grouping that contains
     * down, mouse down, and down disabled are drawn depressed.  Up,
     * indeterminate, and disabled are drawn up.
     */

    if (fDown)
        {
        PatB(hDC, x, y, 1,  dy, pcr[1]);
        PatB(hDC, x, y, dx, 1,  pcr[1]);
        }
    else
        {
        //Normal button look.
        PatB(hDC, x, y, 1,    dy-1, pcr[3]);
        PatB(hDC, x, y, dx-1, 1,    pcr[3]);

        PatB(hDC, x+dx-1, y,      1,  dy, pcr[1]);
        PatB(hDC, x,      y+dy-1, dx, 1,  pcr[1]);

        PatB(hDC, x+1+dx-3, y+1,    1,    dy-2, pcr[1]);
        PatB(hDC, x+1,      y+dy-2, dx-2, 1,    pcr[1]);
        }

    return;
    }

/*
 * PatB
 * Internal
 *
 * Purpose:
 *  A more convenient PatBlt operation for drawing button borders and
 *  highlights.
 *
 * Parameters:
 *  hDC             HDC on which to paint.
 *  x, y            int coordinates at which to paint.
 *  dx, dy          int dimensions of rectangle to paint.
 *  rgb             COLORREF to use as the background color.
 *
 * Return Value:
 *  None
 */

void PatB(HDC hDC, int x, int y, int dx, int dy, COLORREF rgb)
    {
    RECT        rc;

    SetBkColor(hDC, rgb);
    SetRect(&rc, x, y, x+dx, y+dy);
    ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &rc, NULL, 0, NULL);
    }

/*
 * MaskCreate
 * Internal
 *
 * Purpose:
 *  Creates a monochrome mask bitmap of the given image at the given
 *  offset in the global g_hDCMono.  Anywhere in the image that you
 *  have the light gray (STDCOLOR_LTGRAY) or the white highlight
 *  (STDCOLOR_WHITE) you get get 1's.  All other pixels are 0's
 *
 * Parameters:
 *  iImage          UINT index of the image for which to create a
 *                  mask.
 *  dx, dy          int dimensions of the button.
 *  bmx, bmy        int dimensions of the bitmap to use.
 *  xOffset         int offset for x inside g_hDCMono where we paint.
 *  yOffset         int offset for y inside g_hDCMono where we paint.
 *  uState          UINT state of the image.  Special cases are made
 *                  for ATTRIBUTEBUTTON_DOWNDISABLED and
 *                  ATTRIBUTEBUTTON_INDETERMINATE.  In any case
 *                  where you do not want a special case, pass zero
 *                  here, regardless of the true button state.
 *
 * Return Value:
 *  None
 */

void MaskCreate(UINT iImage, int dx, int dy, int bmx, int bmy
    ,int xOffset, int yOffset, UINT uState)
    {
    //Initalize whole area with zeros
    PatBlt(g_hDCMono, 0, 0, dx, dy, WHITENESS);

    if (uState & BUTTONGROUP_BLANK)
        return;

    //Convert face colored pixels to 1's. all others to black.
    SetBkColor(g_hDCGlyphs, g_crStandard[STDCOLOR_LTGRAY]);
    BitBlt(g_hDCMono, xOffset, yOffset, bmx, bmy, g_hDCGlyphs
        , iImage*bmx, 0, SRCCOPY);

    //Indeterminate state, don't turn highlight's to 1's--leave black
    if (ATTRIBUTEBUTTON_INDETERMINATE!=uState)
        {
        //Convert highlight colored pixels to 1's, OR with previous.
        SetBkColor(g_hDCGlyphs, g_crStandard[STDCOLOR_WHITE]);
        BitBlt(g_hDCMono, xOffset, yOffset, bmx, bmy, g_hDCGlyphs
            , iImage*bmx, 0, SRCPAINT);
        }

    /*
     * For the down disabled state, AND this same mask with itself at
     * an offset of 1, which accounts for the highlight shadow.
     */
    if (ATTRIBUTEBUTTON_DOWNDISABLED==uState)
        BitBlt(g_hDCMono, 1, 1, dx-1, dy-1, g_hDCMono,  0, 0, SRCAND);

    return;
    }


BTTNCURI.H

/*
 * BTTNCURI.H
 * Buttons & Cursors
 *
 * Private include file for the Button Images and Cursors DLL.
 *
 * Copyright (c)1992-1995 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _BTTNCURI_H_
#define _BTTNCURI_H_

#ifdef __cplusplus
extern "C"
    {
#endif

//Function prototypes.

//BTTNCUR.C
BOOL     ToolButtonInit(HINSTANCE);
void     ToolButtonFree(void);
HBRUSH   HBrushDitherCreate(COLORREF, COLORREF);
void     DrawBlankButton(HDC, int, int, int, int, BOOL, COLORREF *);

void     PatB(HDC, int, int, int, int, COLORREF);
void     MaskCreate(UINT, int, int, int, int, int, int, UINT);

//CURSORS.C
void        CursorsCache(HINSTANCE);
void        CursorsFree(void);

#define ROP_DSPDxax  0x00E20746
#define ROP_PSDPxax  0x00B8074A

/*
 * Color indices into an array of standard hard-coded black, white,
 * and gray colors.
 */

#define STDCOLOR_BLACK      0
#define STDCOLOR_DKGRAY     1
#define STDCOLOR_LTGRAY     2
#define STDCOLOR_WHITE      3

/*
 * Color indices into an array of system colors, matching those in
 * the hard-coded array for the colors they replace.
 */

#define SYSCOLOR_TEXT       0
#define SYSCOLOR_SHADOW     1
#define SYSCOLOR_FACE       2
#define SYSCOLOR_HILIGHT    3

/*
 * Button types, used internally to distinguish command buttons from
 * attribute buttons to enforce three-state or six-state
 * possibilities.  Command buttons can only have three states (up,
 * mouse down, disabled) while attribute buttons add (down, down
 * disabled, and indeterminate).
 */

#define BUTTONTYPE_COMMAND      0
#define BUTTONTYPE_ATTRIBUTE    1

#ifdef __cplusplus
    }
#endif

#endif //_BTTNCURI_H_


CURSORS.C

/*
 * CURSORS.C
 * Buttons & Cursors
 *
 * Public functions to retrieve cursors.
 *
 * Copyright (c)1992-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "inoledll.h"

/*
 * The +1 is because MAX is the highest allowable number and MIN is
 * not necessarily zero.
 */
HCURSOR rgHCursors[IDC_NEWUICURSORMAX-IDC_NEWUICURSORMIN+1];

/*
 * CursorsCache
 * Internal
 *
 * Purpose:
 *  Loads all the cursors available through NewUICursorLoad into
 *  a global array.  This way we can clean up all the cursors without
 *  placing the burden on the application.
 *
 * Parameters:
 *  hInst           HANDLE of the DLL instance.
 *
 * Return Value:
 *  None. If any of the LoadCursor calls fail, then the corresponding
 *  array entry is NULL and NewUICursorLoad will fail. Better to fail
 *  an app getting a cursor than failing to load the app just for
 *  that reason; and app can attempt to load the cursor on startup if
 *  it's that important, and fail itself.
 */

void CursorsCache(HINSTANCE hInst)
    {
    UINT            i;

    for (i=IDC_NEWUICURSORMIN; i<=IDC_NEWUICURSORMAX; i++)
        {
        rgHCursors[i-IDC_NEWUICURSORMIN]=LoadCursor(hInst
            , MAKEINTRESOURCE(i));
        }
    return;
    }

/*
 * CursorsFree
 * Internal
 *
 * Purpose:
 *  Frees all the cursors previously loaded through CursorsCache.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

void CursorsFree(void)
    {
    /*
     * Note that since cursors are discardable resources and should
     * not be used with DestroyCursor, there's nothing to do here.
     * We still provide this API for compatibility and to maintain
     * symmetry.
     */
    return;
    }

/*
 * UICursorLoad
 * Public API
 *
 * Purpose:
 *  Loads and returns a handle to one of the new standard UI cursors
 *  contained in UITOOLS.DLL.  The application must not call
 *  DestroyCursor on this cursor as it is managed by the DLL.
 *
 * Parameters:
 *  iCursor         UINT index to the cursor to load which must be one
 *                  of the following values:
 *
 *      IDC_RIGHTARROW    Right pointing standard arrow
 *      IDC_CONTEXTHELP   Arrow with a ? (context help)
 *      IDC_MAGNIFY       Magnifying glass for zooming
 *      IDC_NODROP        Circle with a slash
 *      IDC_TABLETOP      Small arrow pointing down
 *
 *      IDC_SMALLARROWS   Thin four-headed arrow
 *      IDC_LARGEARROWS   Wide four-headed arrow
 *      IDC_HARROWS       Horizontal two-headed arrow
 *      IDC_VARROWS       Vertical two-headed arrow
 *      IDC_NESWARROWS    Two-headed arrow pointing NE<->SW
 *      IDC_NWSEHARROWS   Two-headed arrow pointing NW<->SE
 *
 *      IDC_HSIZEBAR      Horizontal two-headed arrow with
 *                        a single vertical bar down the
 *                        middle
 *
 *      IDC_VSIZEBAR      Vertical two-headed arrow with a
 *                        single horizontal bar down the
 *                        middle
 *
 *      IDC_HSPLITBAR     Horizontal two-headed arrow with
 *                        split double vertical bars down the
 *                        middle
 *
 *      IDC_VSPLITBAR     Vertical two-headed arrow with split
 *                        double horizontal bars down the
 *                        middle
 *
 * Return Value:
 *  HCURSOR         Handle to the loaded cursor if successful, NULL
 *                  if iCursor is out of range or the function could
 *                  not load the cursor.
 */

HCURSOR WINAPI UICursorLoad(UINT iCursor)
    {
    HCURSOR     hCur=NULL;

    if ((iCursor >= IDC_NEWUICURSORMIN)
        && (iCursor <= IDC_NEWUICURSORMAX))
        hCur=rgHCursors[iCursor-IDC_NEWUICURSORMIN];

    return hCur;
    }


GIZMO.C

/*
 * GIZMO.C
 *
 * Allocate, free, find, and enumerate functions for the GIZMO
 * structure and a generic subclass procedure to handle tabbing
 * between gizmos.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "inoledll.h"

/*
 * In order to control tabbing in the gizmos, we need to subclass
 * real pushbuttons, edit controls, listboxes, and comboboxes.  So
 * we keep an array of the four original procs for such controls.
 */
WNDPROC     pfnOrg[CSUBGIZMOS]={NULL, NULL, NULL, NULL};

TCHAR szStatic[]=TEXT("static");
TCHAR szEdit[]=TEXT("edit");
TCHAR szCombobox[]=TEXT("combobox");
TCHAR szListbox[]=TEXT("listbox");
TCHAR szButton[]=TEXT("button");

//Here so GIZMOBAR.C can get at it.
TOOLDISPLAYDATA tdd;

/*
 * GizmoPAllocate
 *
 * Purpose:
 *  Allocates and initializes a GIZMO data structure.
 *
 * Parameters:
 *  pfSuccess       int * flag indicating success of failure.
 *  ppFirst         PPGIZMO providing the first gizmo in this list.
 *  hWndParent      HWND of the parent of this gizmo.  Can be NULL
 *                  for iType==GIZMOTYPE_BUTTON* or
 *                  GIZMOTYPE_SEPARATOR.
 *  iType           UINT gizmo control type.
 *  iGizmo          UINT index of this gizmo in the GizmoBar.
 *  uID             UINT identifier to send with WM_COMMAND for this
 *                  control.
 *  dx, dy          UINT width and height of the gizmo.
 *  pszText         LPTSTR to the text for edits, listboxes, combobox,
 *                  and text.
 *  dwStyle         DWORD style for edits, lists, and combos, and
 *                  texts.
 *  hBmp            HBITMAP for button gizmos, is applicable.
 *  iImage          UINT index into hBmp for the button image, if
 *                  applicable.
 *  uState          UINT initial state of the control.
 *
 * Return Value:
 *  PGIZMO          If NULL returned then GizmoPAllocate could not
 *                  allocate memory.  If a non-NULL pointer is
 *                  returned with *pfSuccess, then call GizmoPFree
 *                  immediately.  If you get a non-NULL pointer and
 *                  *pfSuccess==TRUE then the function succeeded.
 */

PGIZMO GizmoPAllocate(int *pfSuccess, PPGIZMO ppFirst
    , HWND hWndParent, UINT iType, UINT iGizmo, UINT uID, UINT dx
    , UINT dy, LPTSTR pszText, HBITMAP hBmp, UINT iImage, UINT uState)
    {
    PGIZMO          pGizmo;
    PGIZMO          pCur, pPrev;
    LPTSTR          pszClass;
    HINSTANCE       hInst;
    UINT            i;
    DWORD           dwStyle;
    HWND            hWndE;

    if (NULL==pfSuccess)
        return NULL;

    //Make sure we know of this gizmo type.
    if (GIZMOTYPE_MIN > iType || GIZMOTYPE_MAX < iType)
        return NULL;

    *pfSuccess=FALSE;

    //Allocate the structure
    pGizmo=(PGIZMO)(TCHAR *)LocalAlloc(LPTR, CBGIZMO);

    if (NULL==pGizmo)
        return NULL;

    //Store the necessary information for this gizmo.
    pGizmo->iType   =iType;
    pGizmo->uID     =uID;
    pGizmo->hBmp    =hBmp;
    pGizmo->iBmp    =iImage;
    pGizmo->uState  =uState;
    pGizmo->fNotify =TRUE;

    /*
     * Insert this structure into our gizmo list.  Each time we scan
     * we increment the index counter (starting at zero) comparing it
     * to the desired index of insertion.  We then know exactly where
     * to insert this new gizmo.  Note that we insert the new gizmo
     * in the list appropriately for the given owner, so enumerations
     * will come out ordered in the same way for that owner.
     */

    i=0;
    pCur=*ppFirst;
    pPrev=NULL;

    while (NULL!=pCur && i++ < iGizmo)
        {
        pPrev=pCur;
        pCur =pCur->pNext;
        }

    //Point to our neighbors
    pGizmo->pPrev=pPrev;
    pGizmo->pNext=pCur;

    //Point out neighbors to us.
    if (NULL==pPrev)
        *ppFirst=pGizmo;
    else
        pPrev->pNext=pGizmo;

    if (NULL!=pCur)
        pCur->pPrev=pGizmo;

    //Our x-coordinate is the x of the previous gizmo plus its width.
    if (NULL!=pPrev)
        pGizmo->x=pGizmo->pPrev->x+pGizmo->pPrev->dx;
    else
        pGizmo->x=4;    //First gizmo is at x=4

    //If we're a separator or image button, force standards on dx.
    UIToolConfigureForDisplay(&tdd);
    pGizmo->cxImage=tdd.cxImage;
    pGizmo->cyImage=tdd.cyImage;

    if ((GIZMOTYPE_DRAWN & iType) && NULL==hBmp)
        dx=tdd.cxButton;

    if (GIZMOTYPE_SEPARATOR==iType)
        dx=6;

    /*
     * Now create windows for edits, texts, lists, and comboboxes.
     * First calculate the most often defaults used in the switch.
     */
    pGizmo->dx=dx+6;
    pGizmo->dy=min(dy, tdd.cyButton);
    pGizmo->y=2;
    pszClass=NULL;

    //If this is new gizmo is a window, create it.
    switch (iType)
        {
        case GIZMOTYPE_TEXT:
            pGizmo->dx=dx;

            //Center vertically.
            pGizmo->y=(tdd.cyBar-1-pGizmo->dy) >> 1;
            pszClass=szStatic;
            dwStyle=SS_LEFT;
            break;

        case GIZMOTYPE_EDIT:
            //Center vertically.
            pGizmo->y=(tdd.cyBar-1-pGizmo->dy) >> 1;
            pszClass=szEdit;
            dwStyle=ES_LEFT | WS_BORDER | WS_TABSTOP;
            break;

        case GIZMOTYPE_LISTBOX:
            pGizmo->dy=dy;
            pszClass=szCombobox;
            dwStyle=CBS_DROPDOWNLIST | WS_TABSTOP;
            break;

        case GIZMOTYPE_COMBOBOX:
            pGizmo->dy=dy;
            pszClass=szCombobox;
            dwStyle=CBS_DROPDOWN | WS_TABSTOP;
            break;

        case GIZMOTYPE_BUTTONNORMAL:
            pGizmo->dy=dy;
            pszClass=szButton;
            dwStyle=BS_PUSHBUTTON | WS_TABSTOP;
            break;

        case GIZMOTYPE_SEPARATOR:
            pGizmo->dx=dx;
            pGizmo->y=3;
            break;

        case GIZMOTYPE_BUTTONATTRIBUTEIN:
        case GIZMOTYPE_BUTTONATTRIBUTEEX:
        case GIZMOTYPE_BUTTONCOMMAND:
            pGizmo->dx=dx;
            pGizmo->y=3;
            break;
        }

    //If we matched a classname, create a window.
    if (GIZMOTYPE_WINDOWS & iType)
        {
        if (!IsWindow(hWndParent))
            return pGizmo;

        hInst=GETWINDOWINSTANCE(hWndParent);    //Macro in book1632.h

        pGizmo->hWnd=CreateWindow(pszClass, pszText
            , dwStyle | WS_CHILD | WS_VISIBLE, pGizmo->x, pGizmo->y
            , dx, pGizmo->dy, hWndParent, (HMENU)uID, hInst, NULL);

        if (NULL==pGizmo->hWnd)
            return pGizmo;

        /*
         * Subclass comboboxes, listboxes, edits, and windowed
         * buttons.  We use iType to index the original proc array
         * so we can use a single subclass procedure for all
         * controls.  If you mess with the gizmo type definitions,
         * this is going to break.
         */

        if (GIZMOTYPE_WINDOWS & iType && GIZMOTYPE_TEXT!=iType)
            {
            //Give the window its type.
            BITPOSITION(iType, i);
            SetProp(pGizmo->hWnd, SZTYPEPROP, (HANDLE)i);

            if (NULL==pfnOrg[i])
                {
                pfnOrg[i]=(WNDPROC)GetWindowLong(pGizmo->hWnd
                    , GWL_WNDPROC);
                }

            SetWindowLong(pGizmo->hWnd, GWL_WNDPROC
                , (LONG)GenericSubProc);

            //If we're a combobox, subclass edit control
            if (GIZMOTYPE_COMBOBOX==iType)
                {
                hWndE=GetDlgItem(pGizmo->hWnd, ID_COMBOEDIT);
                SetProp(hWndE, SZTYPEPROP, (HANDLE)-1);

                if (NULL==pfnOrg[0])
                    {
                    pfnOrg[0]=(WNDPROC)GetWindowLong(pGizmo->hWnd
                        , GWL_WNDPROC);
                    }

                SetWindowLong(hWndE, GWL_WNDPROC
                    , (LONG)GenericSubProc);
                }
            }
        }

    //Finally, move all our neighbors to the right to accomodate us.
    GizmosExpand(pGizmo);

    *pfSuccess=TRUE;
    return pGizmo;
    }

/*
 * GizmoPFree
 *
 * Purpose:
 *  Reverses all initialization done by GizmoPAllocate, cleaning up
 *  any allocations including the application structure itself.
 *
 * Parameters:
 *  ppFirst         PPGIZMO providing the first gizmo in this list.
 *  pGizmo          PGIZMO to the structure
 *
 * Return Value:
 *  PGIZMO          NULL if successful, pGizmo if not, meaning we
 *                  couldn't free something.
 */

PGIZMO GizmoPFree(PPGIZMO ppFirst, PGIZMO pGizmo)
    {
    int     i;

    if (NULL==pGizmo)
        return NULL;

    //Move other gizmos to fill in this gap.
    GizmosCompact(pGizmo);

    //Unsubclass
    if (GIZMOTYPE_WINDOWS & pGizmo->iType
        && GIZMOTYPE_TEXT!=pGizmo->iType)
        {
        i=(int)GetProp(pGizmo->hWnd, SZTYPEPROP);
        RemoveProp(pGizmo->hWnd, SZTYPEPROP);

        if (GIZMOTYPE_COMBOBOX==pGizmo->iType)
            {
            HWND        hWndE;

            hWndE=GetDlgItem(pGizmo->hWnd, ID_COMBOEDIT);
            RemoveProp(hWndE, SZTYPEPROP);
            }

        SetWindowLong(pGizmo->hWnd, GWL_WNDPROC, (LONG)pfnOrg[i]);
        }

    //If this was a window gizmo, destroy the window.
    if (NULL!=pGizmo->hWnd && IsWindow(pGizmo->hWnd))
        DestroyWindow(pGizmo->hWnd);

    //Unlink ourselves.
    if (NULL!=pGizmo->pNext)
        pGizmo->pNext->pPrev=pGizmo->pPrev;

    if (NULL!=pGizmo->pPrev)
        pGizmo->pPrev->pNext=pGizmo->pNext;
    else
        *ppFirst=pGizmo->pNext;

    return (PGIZMO)LocalFree((HLOCAL)(UINT)(LONG)pGizmo);
    }

/*
 * GizmosExpand
 *
 * Purpose:
 *  Given a starting gizmo and a width, moves it and all gizmos to
 *  its right to the right by the width to make space for showing
 *  or creating a new gizmo.
 *
 * Parameters:
 *  pGizmo          PGIZMO specifying the gizmo that was inserted.
 *
 * Return Value:
 *  None
 */

void GizmosExpand(PGIZMO pGizmo)
    {
    int         cx;

    cx=(int)pGizmo->dx;

    /*
     * If we and the next control are buttons, use our width-1 to
     * expand so we overlap borders with our neighboring button.
     */

    if (NULL!=pGizmo->pNext)
        {
        if ((GIZMOTYPE_BUTTONS & pGizmo->pNext->iType)
            && (GIZMOTYPE_BUTTONS & pGizmo->iType))
            cx-=1;
        }

    //Walk the gizmo list moving them right by our width.
    pGizmo=pGizmo->pNext;

    while (NULL!=pGizmo)
        {
        pGizmo->x+=cx;

        //hWnd is NULL for buttons and separators.
        if (NULL!=pGizmo->hWnd)
            {
            SetWindowPos(pGizmo->hWnd, NULL, pGizmo->x, pGizmo->y
                , 0, 0, SWP_NOZORDER | SWP_NOSIZE);
            }

        pGizmo=pGizmo->pNext;
        }

    return;
    }

/*
 * GizmosCompact
 *
 * Purpose:
 *  Given a gizmo, moves all other gizmos to the right of it to the
 *  left by its width on the GizmoBar.  Used when removing or hiding
 *  the gizmo.
 *
 * Parameters:
 *  pGizmo          PGIZMO that is going away, visibly or physically.
 *
 * Return Value:
 *  None
 */

void GizmosCompact(PGIZMO pGizmo)
    {
    UINT        cx;
    PGIZMO      pCur;

    //Move all the gizmos beyond us back by our width.
    if (NULL!=pGizmo->pNext)
        {
        cx=pGizmo->pNext->x - pGizmo->x;
        pCur=pGizmo->pNext;

        while (NULL!=pCur)
            {
            pCur->x-=cx;

            if (NULL!=pCur->hWnd)
                {
                SetWindowPos(pCur->hWnd, NULL, pCur->x, pCur->y
                             , 0, 0, SWP_NOZORDER | SWP_NOSIZE);
                }

            pCur=pCur->pNext;
            }
        }

    return;
    }

/*
 * GizmoPFind
 *
 * Purpose:
 *  Given a GIZMO identifier, locates and returns a pointer to the
 *  structure for that position.
 *
 * Parameters:
 *  ppFirst         PPGIZMO providing the first gizmo in this list.
 *  uID             UINT identifier to find.
 *
 * Return Value:
 *  PGIZMO          A pointer to a GIZMO structure allocated through
 *                  GizmoPAllocate, NULL if iGizmo is out of range.
 */

PGIZMO GizmoPFind(PPGIZMO ppFirst, UINT uID)
    {
    PGIZMO       pGizmo;

    pGizmo=*ppFirst;

    /*
     * Yep, linear search, but a better search algorithm won't
     * improve things appreciably.  The better thing to optimize
     * is what the caller passes as ppFirst.
     */
    while (NULL!=pGizmo && uID!=pGizmo->uID)
        pGizmo=pGizmo->pNext;

    return pGizmo;
    }

/*
 * GizmoFEnum
 *
 * Purpose:
 *  Enumerates the list of GIZMO structures, passing each one to
 *  an application-defined callback.
 *
 * Parameters:
 *  ppFirst         PPGIZMO providing the first gizmo in this list.
 *  pfnEnum         PFNGIZMOENUM to call for each enumerated
 *                  structure.
 *  dw              DWORD extra data to pass to the enumeration
 *                  function.
 *
 * Return Value:
 *  PGIZMO          NULL if the enumeration completed.  Otherwise a
 *                  pointer to the gizmo that enumeration stopped on.
 */

PGIZMO GizmoPEnum(PPGIZMO ppFirst, PFNGIZMOENUM pfnEnum, DWORD dw)
    {
    PGIZMO  pGizmo;
    UINT    i=0;

    pGizmo=*ppFirst;

    while (NULL!=pGizmo)
        {
        if (!(*pfnEnum)(pGizmo, i++, dw))
            break;

        pGizmo=pGizmo->pNext;
        }

    return pGizmo;
    }

/*
 * GizmoPStateSet
 *
 * Purpose:
 *  State maniuplation functions.  Set and Clear also invalidate
 *  this gizmo's rectangle on the given window and forces a repaint.
 *
 * Parameters:
 *  hWnd            HWND of the window to repaint.
 *  pGizmo          PGIZMO affected.
 *  dwNew           DWORD new state flags.
 *
 * Return Value:
 *  UINT            Previous state.
 */

UINT  GizmoPStateSet(HWND hWnd, PGIZMO pGizmo, UINT uNew)
    {
    UINT        uRet;
    RECT        rc;

    if (GIZMOTYPE_SEPARATOR==pGizmo->iType)
        return pGizmo->uState;

    //Preserve the color conversion flags across this state change.
    uRet=pGizmo->uState;
    pGizmo->uState=(uNew & 0x00FF) | (uRet & 0xFF00);

    //Adjust the rectangle by  one to avoid repainting  borders.
    SetRect(&rc, pGizmo->x+1, pGizmo->y+1, pGizmo->x+pGizmo->dx-1
        , pGizmo->y+pGizmo->dy-1);
    InvalidateRect(hWnd, &rc, FALSE);
    UpdateWindow(hWnd);

    return uRet;
    }

/*
 * GizmoPCheck
 *
 * Purpose:
 *  Handles checking a single button in a group of attribute buttons.
 *  If the gizmo belongs to a group of mutually exclusive buttons
 *  then the others surrounding it are unchecked appropriately.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  pGizmo          PGIZMO of the gizmo affected.
 *  fCheck          BOOL TRUE to check the button, FALSE to uncheck.
 *
 * Return Value:
 *  BOOL            TRUE if the gizmo was previously checked, FALSE
 *                  otherwise.
 */

BOOL GizmoPCheck(HWND hWnd, PGIZMO pGizmo, BOOL fCheck)
    {
    BOOL        fPrevCheck;
    PGIZMO      pCur;

    //Ignore command buttons.
    if (GIZMOTYPE_BUTTONCOMMAND==pGizmo->iType)
        return FALSE;

    //Get the previous state
    fPrevCheck=(BOOL)(BUTTONGROUP_DOWN & pGizmo->uState);

    //Simply set the state for inclusive attribute buttons.
    if (GIZMOTYPE_BUTTONATTRIBUTEIN==pGizmo->iType)
        {
        if (pGizmo->fDisabled)
            {
            GizmoPStateSet(hWnd, pGizmo
                , fCheck ? ATTRIBUTEBUTTON_DOWNDISABLED
                : ATTRIBUTEBUTTON_DISABLED);
            }
        else
            {
            GizmoPStateSet(hWnd, pGizmo, fCheck
                ? ATTRIBUTEBUTTON_DOWN : ATTRIBUTEBUTTON_UP);
            }
        }

    if (GIZMOTYPE_BUTTONATTRIBUTEEX==pGizmo->iType)
        {
        //We cannot uncheck an exclusive attribute
        if (!fCheck)
            return fPrevCheck;

        /*
         * For exclusive buttons we have to do more work.  First, if
         * we're already checked (incliding DOWN and MOUSEDOWN) then
         * we set DOWN and exit.  If we're not already checked, then
         * we look for the gizmo around us, backwards and forwards,
         * that is checked and uncheck him.
         */

        //Search  backwards.
        pCur=pGizmo->pPrev;

        while (NULL!=pCur)
            {
            //Stop at any non-exclusive attribute.
            if (GIZMOTYPE_BUTTONATTRIBUTEEX!=pCur->iType)
                {
                pCur=NULL;
                break;
                }

            //If it's down, set it up and we've finished.
            if (BUTTONGROUP_DOWN & pCur->uState)
                break;

            pCur=pCur->pPrev;
            }

        //If we didn't find a previous one, pCur is NULL, look ahead.
        if (NULL==pCur)
            {
            pCur=pGizmo->pNext;

            while (NULL!=pCur)
                {
                //Stop at any non-exclusive attribute.
                if (GIZMOTYPE_BUTTONATTRIBUTEEX!=pCur->iType)
                    {
                    pCur=NULL;
                    break;
                    }

                //If it's down, set it up and we've finished.
                if (BUTTONGROUP_DOWN & pCur->uState)
                    break;

                pCur=pCur->pNext;
                }
            }

        //If pCur is non-NULL, we found a neighbor, so uncheck it
        if (NULL!=pCur)
            {
            GizmoPStateSet(hWnd, pCur, (pGizmo->fDisabled)
                ? ATTRIBUTEBUTTON_DISABLED : ATTRIBUTEBUTTON_UP);
            }

        //Always set ourselves down
        GizmoPStateSet(hWnd, pGizmo, (pGizmo->fDisabled)
            ? ATTRIBUTEBUTTON_DOWNDISABLED : ATTRIBUTEBUTTON_DOWN);
        }

    return fPrevCheck;
    }

/*
 * GenericSubProc
 *
 * Purpose:
 *  Subclasses window controls in Gizmos so we can trap the tab key
 *  and tab to the next control.  We can have one shared generic
 *  subclass procedure because we save the type index for this
 *  control in the property "iType."  This allows us to look up the
 *  original procedure in the pfnOrg array.
 *
 * Parameters:
 *  Standard
 *
 * Return Value:
 *  Standard
 */

LRESULT APIENTRY GenericSubProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    LONG        lRet;
    RECT        rc;
    RECT        rcE;
    HWND        hWndE;
    HBRUSH      hBr;
    HDC         hDC;
    UINT        dx;
    UINT        iType, i;

    i=(int)GetProp(hWnd, SZTYPEPROP);
    iType=POSITIONBIT(i);

    //Special:  paint the gap in drop-down comboboxes.
    if (GIZMOTYPE_COMBOBOX==iType && WM_PAINT==iMsg)
        {
        //Do default painting.
        lRet=(*pfnOrg[i])(hWnd, iMsg, wParam, lParam);

        hWndE=GetDlgItem(hWnd, ID_COMBOEDIT);

        GetClientRect(hWnd, &rc);
        GetClientRect(hWndE, &rcE);

        //The width of the button is the scroll bar width.
        dx=GetSystemMetrics(SM_CXVSCROLL);

        //Calculate the rectangle
        rc.right -=dx;
        rc.left   =rcE.right;
        rc.bottom+=1;

        //Paint the gap
        hDC=GetDC(hWnd);   //Already did BeginPaint and EndPaint

        hBr=CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
        FillRect(hDC, &rc, hBr);
        DeleteObject(hBr);

        ReleaseDC(hWnd, hDC);
        return lRet;
        }

    //Control tabbing to the next or previous control
    if (WM_KEYDOWN==iMsg && VK_TAB==wParam)
        {
        hWndE=hWnd;

        if (-1==i)
            hWndE=GetParent(hWnd);

        hWndE=GetNextDlgTabItem(GetParent(hWndE), hWnd
            , (BOOL)(GetKeyState(VK_SHIFT)));
        SetFocus(hWndE);
        return 0L;
        }

    if (-1==i) i=0;

    //Eat tab chars in edit controls to prevent beeping.
    if (0==i && WM_CHAR==iMsg && VK_TAB==wParam)
        return 0L;

    //Do this or edit controls bomb big-time.
    return CallWindowProc(pfnOrg[i], hWnd, iMsg, wParam, lParam);
    }


GIZMOAPI.C

/*
 * GIZMOAPI.C
 *
 * API functions affecting a GizmoBar and a message processing
 * function to handle the equivalent called through messages.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "inoledll.h"

/*
 * GBMessageHandler
 *
 * Purpose:
 *  Processes control messages that are equivalents of available
 *  control API.  The data passed with these messages is simply
 *  extracted from structures and passed as parameters to their
 *  equivalent function.
 *
 * Parameters:
 *  <Standard Message Parameters> plus
 *  pGB             PGIZMOBAR providing control-specific data.
 *
 * Return Value:
 *  LRESULT         Return value from equivalent API function.
 */

LRESULT GBMessageHandler(HWND hWnd, UINT iMsg, WPARAM wParam
    , LPARAM lParam, PGIZMOBAR pGB)
    {
    LRESULT         lRet=0L;
    LPCREATEGIZMO   pCG;
    LPGBMSG         pMsg;
    LPGBGETTEXT     pGT;
    LPGBGETINT      pGI;
    LPGBSETINT      pSI;

    if (NULL==pGB)
        return 0L;

    switch (iMsg)
        {
        case GBM_HWNDASSOCIATESET:
            lRet=(LRESULT)(UINT)GBHwndAssociateSet(hWnd
                , (HWND)wParam);
            break;

        case GBM_HWNDASSOCIATEGET:
            lRet=(LRESULT)(UINT)GBHwndAssociateGet(hWnd);
            break;

        case GBM_GIZMOADD:
            pCG=(LPCREATEGIZMO)lParam;
            lRet=(LRESULT)GBGizmoAdd(pCG->hWndParent, pCG->iType
                , pCG->iGizmo, pCG->uID, pCG->dx, pCG->dy
                , pCG->pszText, pCG->hBmp, pCG->iImage, pCG->uState);
            break;

        case GBM_GIZMOREMOVE:
            lRet=(LRESULT)GBGizmoRemove(hWnd, wParam);
            break;

        case GBM_GIZMOSENDMESSAGE:
            pMsg=(LPGBMSG)lParam;
            lRet=GBGizmoSendMessage(hWnd, wParam, pMsg->iMsg
                , pMsg->wParam, pMsg->lParam);
            break;

        case GBM_GIZMOSHOW:
            lRet=(LRESULT)GBGizmoShow(hWnd, wParam
                , (BOOL)LOWORD(lParam));
            break;

        case GBM_GIZMOENABLE:
            lRet=(LRESULT)GBGizmoEnable(hWnd, wParam
                , (BOOL)LOWORD(lParam));
            break;

        case GBM_GIZMOCHECK:
            lRet=(LRESULT)GBGizmoCheck(hWnd, wParam
                , (BOOL)LOWORD(lParam));
            break;

        case GBM_GIZMOFOCUSSET:
            lRet=(LRESULT)GBGizmoFocusSet(hWnd, wParam);
            break;

        case GBM_GIZMOEXIST:
            lRet=(LRESULT)GBGizmoExist(hWnd, wParam);
            break;

        case GBM_GIZMOTYPEGET:
            lRet=(LRESULT)GBGizmoTypeGet(hWnd, wParam);
            break;

        case GBM_GIZMODATASET:
            lRet=(LRESULT)GBGizmoDataSet(hWnd, wParam
                , (DWORD)lParam);
            break;

        case GBM_GIZMODATAGET:
            lRet=(LRESULT)GBGizmoDataGet(hWnd, wParam);
            break;

        case GBM_GIZMONOTIFYSET:
            lRet=(LRESULT)GBGizmoNotifySet(hWnd, wParam
                , (BOOL)LOWORD(lParam));
            break;

        case GBM_GIZMONOTIFYGET:
            lRet=(LRESULT)GBGizmoNotifyGet(hWnd, wParam);
            break;

        case GBM_GIZMOTEXTGET:
            pGT=(LPGBGETTEXT)lParam;
            lRet=(LRESULT)GBGizmoTextGet(hWnd, wParam, pGT->psz
                , pGT->cch);
            break;

        case GBM_GIZMOTEXTSET:
            GBGizmoTextSet(hWnd, wParam, (LPTSTR)lParam);
            break;

        case GBM_GIZMOINTGET:
            pGI=(LPGBGETINT)lParam;
            lRet=(LRESULT)GBGizmoIntGet(hWnd, wParam, &pGI->fSuccess
                , pGI->fSigned);
            break;

        case GBM_GIZMOINTSET:
            pSI=(LPGBSETINT)lParam;
            GBGizmoIntSet(hWnd, wParam, pSI->uValue, pSI->fSigned);
            break;

        default:
            break;
        }

    return lRet;
    }

/*
 * PGizmoFromHwndID
 *
 * Purpose:
 *  Retrieves the pGizmo for the given GizmoBar and the gizmo ID.
 *
 * Parameters:
 *  hWnd            HWND of a GizmoBar.
 *  uID             UINT gizmo identifier.
 *
 * Return Value:
 *  PGIZMO          NULL if the gizmo does not exist or hWnd is
 *                  invalid.  Non-NULL PGIZMO otherwise.
 */

PGIZMO PGizmoFromHwndID(HWND hWnd, UINT uID)
    {
    PGIZMOBAR     pGB;

    if (!IsWindow(hWnd))
        return FALSE;

    pGB=(PGIZMOBAR)GetWindowLong(hWnd, GBWL_STRUCTURE);

    if (NULL==pGB)
        return FALSE;

    return GizmoPFind(&pGB->pGizmos, uID);
    }

/*
 * GBHwndAssociateSet
 *
 * Purpose:
 *  Changes the associate window of a GizmoBar.
 *
 * Parameters:
 *  hWnd            HWND of the control window.
 *
 * Set Parameters:
 *  hWndAssociate   HWND of new associate.
 *
 * Return Value:
 *  HWND            Handle of previous associate.
 */

HWND WINAPI GBHwndAssociateSet(HWND hWnd, HWND hWndNew)
    {
    HWND        hWndOld=NULL;
    PGIZMOBAR   pGB;

    pGB=(PGIZMOBAR)GetWindowLong(hWnd, GBWL_STRUCTURE);

    if (NULL!=pGB)
        {
        hWndOld=pGB->hWndAssociate;
        pGB->hWndAssociate=hWndNew;

        if (NULL!=hWndOld)
            SendCommand(hWndOld, pGB->uID, GBN_ASSOCIATELOSS, hWnd);

        if (NULL!=hWndNew)
            SendCommand(hWndNew, pGB->uID, GBN_ASSOCIATEGAIN, hWnd);
        }

    return hWndOld;
    }

/*
 * GBHwndAssociateGet
 *
 * Purpose:
 *  Retrieves the associate window of a GizmoBar
 *
 * Parameters:
 *  hWnd            HWND of the control window.
 *
 * Set Parameters:
 *  hWndAssociate   HWND of new associate.
 *
 * Return Value:
 *  HWND            Handle of current associate.
 */

HWND WINAPI GBHwndAssociateGet(HWND hWnd)
    {
    HWND        hWndOld=NULL;
    PGIZMOBAR   pGB;

    pGB=(PGIZMOBAR)GetWindowLong(hWnd, GBWL_STRUCTURE);

    if (NULL!=pGB)
        hWndOld=pGB->hWndAssociate;

    return hWndOld;
    }

/*
 * GBGizmoAdd
 *
 * Purpose:
 *  Creates a new gizmo on the GizmoBar.  Subsequent operations
 *  should be done using the identifier, uID, for this gizmo.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  iType           UINT type of the gizmo to create.
 *  iGizmo          UINT position (zero-based) at which to place the
 *                  gizmo.
 *  uID             UINT identifier for WM_COMMAND from this gizmo.
 *  dx, dy          UINT dimensions of the gizmo.
 *  pszText         LPTSTR initial text for edit, list, combo, and
 *                  text gizmos.
 *  hBitmap         HBITMAP for gizmos of the button types (COMMAND
 *                  or ATTRIBUTE) specifies a source bitmap from
 *                  which the button image is taken.
 *  iImage          UINT index into hBitmap for the image for this
 *                  button.
 *  uState          UINT initial state of the gizmo.
 *
 * Return Value:
 *  BOOL            TRUE if creation succeeded, FALSE otherwise.
 */

BOOL WINAPI GBGizmoAdd(HWND hWnd, UINT iType, UINT iGizmo, UINT uID
    , UINT dx, UINT dy, LPTSTR pszText, HBITMAP hBmp, UINT iImage
    , UINT uState)
    {
    BOOL        fSuccess;
    PGIZMOBAR   pGB;
    PGIZMO      pGizmo;

    if (!IsWindow(hWnd))
        return FALSE;

    pGB=(PGIZMOBAR)GetWindowLong(hWnd, GBWL_STRUCTURE);

    if (NULL==pGB)
        return FALSE;

    /*
     * This automatically creates the windows, allocates structures,
     * includes the gizmo in pGB->pGizmos, and so forth.
     */
    pGizmo=GizmoPAllocate(&fSuccess, &pGB->pGizmos, hWnd, iType
        , iGizmo, uID, dx, dy, pszText, hBmp, iImage, uState);

    if (fSuccess)
        {
        if (NULL!=pGB->hWndAssociate)
            {
            SendCommand(pGB->hWndAssociate,GBN_GIZMOADDED, pGB->uID
                , hWnd);
            }

        InvalidateRect(hWnd, NULL, TRUE);
        UpdateWindow(hWnd);
        }
    else
        GizmoPFree(&pGB->pGizmos, pGizmo);

    return fSuccess;
    }

/*
 * GBGizmoRemove
 *
 * Purpose:
 *  Removes an existing gizmo from the GizmoBar.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifier for this gizmo.
 *
 * Return Value:
 *  BOOL            TRUE if deletion succeeded, FALSE otherwise.
 */

BOOL WINAPI GBGizmoRemove(HWND hWnd, UINT uID)
    {
    PGIZMOBAR   pGB;
    PGIZMO      pGizmo;

    if (!IsWindow(hWnd))
        return FALSE;

    pGB=(PGIZMOBAR)GetWindowLong(hWnd, GBWL_STRUCTURE);

    if (NULL==pGB)
        return FALSE;

    pGizmo=GizmoPFind(&pGB->pGizmos, uID);

    if (NULL==pGizmo)
        return FALSE;

    GizmoPFree(&pGB->pGizmos, pGizmo);

    if (NULL!=pGB->hWndAssociate)
        {
        SendCommand(pGB->hWndAssociate, GBN_GIZMOREMOVED, pGB->uID
            , hWnd);
        }

    InvalidateRect(hWnd, NULL, TRUE);
    UpdateWindow(hWnd);
    return TRUE;
    }

/*
 * GBGizmoSendMessage
 *
 * Purpose:
 *  Implements the equivalent of SendMessage to a gizmo in the
 *  GizmoBar.  Separators, command buttons, and attribute buttons
 *  do not accept messages.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifier of the gizmo to affect.
 *  iMsg            UINT message to send.
 *  wParam          WPARAM of the message.
 *  lParam          LPARAM of the message.
 *
 * Return Value:
 *  LRESULT         Return value from the message.  0L if the
 *                  gizmo does not accept messages.
 */

LRESULT WINAPI GBGizmoSendMessage(HWND hWnd, UINT uID, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PGIZMO      pGizmo;
    LONG        lRet=0L;

    pGizmo=PGizmoFromHwndID(hWnd, uID);

    if (NULL!=pGizmo && NULL!=pGizmo->hWnd)
        lRet=SendMessage(pGizmo->hWnd, iMsg, wParam, lParam);

    return lRet;
    }

/*
 * GBGizmoShow
 *
 * Purpose:
 *  Shows or hides a control, adjusting the positions of all others
 *  to make room for or reuse the space for this control.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifier of the gizmo to affect.
 *  fShow           BOOL TRUE to show the gizmo, FALSE to hide it.
 *
 * Return Value:
 *  BOOL            TRUE if the function was successful, FALSE
 *                  otherwise.
 */

BOOL WINAPI GBGizmoShow(HWND hWnd, UINT uID, BOOL fShow)
    {
    BOOL        fRet=FALSE;
    PGIZMO      pGizmo;

    pGizmo=PGizmoFromHwndID(hWnd, uID);

    if (NULL!=pGizmo)
        {
        if (fShow && pGizmo->fHidden)
            {
            if (NULL!=pGizmo->hWnd)
                ShowWindow(pGizmo->hWnd, SW_SHOWNORMAL);

            GizmosExpand(pGizmo);
            }

        if (!fShow && !pGizmo->fHidden)
            {
            if (NULL!=pGizmo->hWnd)
                ShowWindow(pGizmo->hWnd, SW_HIDE);

            GizmosCompact(pGizmo);
            }

        //This will be right even if we didn't change anything.
        pGizmo->fHidden=!fShow;
        }

    InvalidateRect(hWnd, NULL, TRUE);
    UpdateWindow(hWnd);
    return fRet;
    }

/*
 * GBGizmoEnable
 *
 * Purpose:
 *  Enables or disables a control on the GizmoBar.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifier of the gizmo to affect.
 *  fEnable         BOOL TRUE to enable the gizmo, FALSE otherwise.
 *
 * Return Value:
 *  BOOL            TRUE if the gizmo was previously disabled, FALSE
 *                  otherwise.
 */

BOOL WINAPI GBGizmoEnable(HWND hWnd, UINT uID, BOOL fEnable)
    {
    PGIZMO      pGizmo;
    BOOL        fRet=FALSE;

    pGizmo=PGizmoFromHwndID(hWnd, uID);

    if (NULL==pGizmo)
        return FALSE;

    fRet=(BOOL)(BUTTONGROUP_DISABLED & pGizmo->uState);

    //Use windows to enable or disable window gizmos
    if (NULL!=pGizmo->hWnd)
        EnableWindow(pGizmo->hWnd, fEnable);
    else
        {
        /*
         * If we're not down, command and attribute buttons act
         * the same.
         */
        if (!(BUTTONGROUP_DOWN & pGizmo->uState))
            {
            GizmoPStateSet(hWnd, pGizmo, fEnable
                ? COMMANDBUTTON_UP : COMMANDBUTTON_DISABLED);
            }
        else
            {
            /*
             * Attribute buttons are a little more sensitive with
             * DOWNDISABLED
             */
            GizmoPStateSet(hWnd, pGizmo, fEnable
                ? ATTRIBUTEBUTTON_DOWN
                : ATTRIBUTEBUTTON_DOWNDISABLED);
            }
        }

    return fRet;
    }

/*
 * GBGizmoCheck
 *
 * Purpose:
 *  Checks or unchecks an attribute button in the GizmoBar.  If the
 *  gizmo is part of a group of mutually exclusive attributes, then
 *  other gizmos are unchecked when this one is checked.  If this is
 *  the only one checked in these circumstances, this function is a
 *  NOP.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifier of the gizmo to affect.
 *  fCheck          BOOL TRUE to check this gizmo, FALSE to uncheck.
 *
 * Return Value:
 *  BOOL            TRUE if the change took place.  FALSE otherwise.
 */

BOOL WINAPI GBGizmoCheck(HWND hWnd, UINT uID, BOOL fCheck)
    {
    PGIZMOBAR   pGB;
    PGIZMO      pGizmo;

    if (!IsWindow(hWnd))
        return FALSE;

    pGB=(PGIZMOBAR)GetWindowLong(hWnd, GBWL_STRUCTURE);

    if (NULL==pGB)
        return FALSE;

    pGizmo=GizmoPFind(&pGB->pGizmos, uID);

    if (NULL!=pGizmo)
        GizmoPCheck(hWnd, pGizmo, fCheck);

    return TRUE;
    }

/*
 * GBGizmoFocusSet
 *
 * Purpose:
 *  Sets the focus to a partuclar gizmo in the gizmo if that gizmo
 *  can accept the focus.  Separators, attribute buttons, text,
 *  and command buttons cannot have the focus.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifier of the gizmo to affect.
 *
 * Return Value:
 *  BOOL            TRUE if the focus was set.  FALSE otherwise,
 *                  such as when uID identifies a control that cannot
 *                  have focus.
 */

UINT WINAPI GBGizmoFocusSet(HWND hWnd, UINT uID)
    {
    PGIZMO      pGizmo;
    BOOL        fRet=FALSE;

    pGizmo=PGizmoFromHwndID(hWnd, uID);

    if (NULL!=pGizmo && NULL!=pGizmo->hWnd)
        {
        fRet=TRUE;
        SetFocus(pGizmo->hWnd);
        }

    return fRet;
    }

/*
 * GBGizmoExist
 *
 * Purpose:
 *  Determines if a gizmo of a given identifier exists in the
 *  GizmoBar.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifier to verify.
 *
 * Return Value:
 *  BOOL            TRUE if the gizmo exists, FALSE otherwise.
 */

BOOL WINAPI GBGizmoExist(HWND hWnd, UINT uID)
    {
    return (NULL!=PGizmoFromHwndID(hWnd, uID));
    }

/*
 * GBGizmoTypeGet
 *
 * Purpose:
 *  Returns the type of the gizmo specified by the given identifer.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifier to find.
 *
 * Return Value:
 *  int             A GIZMOTYPE_* value if the function is
 *                  successful, otherwise -1.
 */

int WINAPI GBGizmoTypeGet(HWND hWnd, UINT uID)
    {
    int         iRet=-1;
    PGIZMO      pGizmo;

    pGizmo=PGizmoFromHwndID(hWnd, uID);

    if (NULL!=pGizmo)
        iRet=pGizmo->iType;

    return iRet;
    }

/*
 * GBGizmoDataSet
 * GBGizmoDataGet
 *
 * Purpose:
 *  Sets or retrieves an extra DWORD value associated with the given
 *  gizmo.  Applications can store any information here they please.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifier of the gizmo.
 *  dwData          (Set only) DWORD data to store with the gizmo.
 *
 * Return Value:
 *  DWORD           Set:  Previous value
 *                  Get:  Current value
 */

DWORD WINAPI GBGizmoDataSet(HWND hWnd, UINT uID, DWORD dwData)
    {
    PGIZMO      pGizmo;
    DWORD       dw=0L;

    pGizmo=PGizmoFromHwndID(hWnd, uID);

    if (NULL!=pGizmo)
        {
        dw=pGizmo->dwData;
        pGizmo->dwData=dwData;
        }

    return dw;
    }

DWORD WINAPI GBGizmoDataGet(HWND hWnd, UINT uID)
    {
    PGIZMO      pGizmo;
    DWORD       dw=0L;

    pGizmo=PGizmoFromHwndID(hWnd, uID);

    if (NULL!=pGizmo)
        dw=pGizmo->dwData;

    return dw;
    }

/*
 * GBGizmoNotifySet
 * GBGizmoNotifyGet
 *
 * Purpose:
 *  Sets or retrieves the notify status of a gizmo.  If notify is
 *  FALSE, the no WM_COMMAND messages are sent from the GizmoBar to
 *  the parent window when this gizmo is used.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifier of the gizmo.
 *  fNotify         (Set only) BOOL new notify status to set.
 *
 * Return Value:
 *  BOOL            Set:  Previous value of the notify flag.
 *                  Get:  Current value of the notify flag.
 */

BOOL WINAPI GBGizmoNotifySet(HWND hWnd, UINT uID, BOOL fNotify)
    {
    PGIZMO      pGizmo;
    BOOL        fRet=FALSE;

    pGizmo=PGizmoFromHwndID(hWnd, uID);

    if (NULL!=pGizmo)
        {
        fRet=pGizmo->fNotify;
        pGizmo->fNotify=fNotify;
        }

    return fRet;
    }

BOOL WINAPI GBGizmoNotifyGet(HWND hWnd, UINT uID)
    {
    PGIZMO      pGizmo;
    BOOL        fRet=FALSE;

    pGizmo=PGizmoFromHwndID(hWnd, uID);

    if (NULL!=pGizmo)
        fRet=pGizmo->fNotify;

    return fRet;
    }

/*
 * GBGizmoTextSet
 * GBGizmoTextGet
 *
 * Purpose:
 *  Retrieves or sets text in a GizmoBar gizmo.  Separators, command
 *  buttons, and attribute buttons are not affected by this call.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifying the gizmo.
 *  psz             LPTSTR (Set) providing the text to show in the
 *                  window or (Get) pointing to a buffer to receive
 *                  the text.
 *  cch             (Get only) UINT maximum number of chars to copy
 *                  to psz.
 *
 * Return Value:
 *  int             Number of characters copied to psz.
 */

void WINAPI GBGizmoTextSet(HWND hWnd, UINT uID, LPTSTR psz)
    {
    //This fails on non-windowed gizmos anyway, so we don't check.
    SetDlgItemText(hWnd, uID, psz);
    return;
    }

int WINAPI GBGizmoTextGet(HWND hWnd, UINT uID, LPTSTR psz, UINT cch)
    {
    //This fails on non-windowed gizmos anyway, so we don't check.
    return GetDlgItemText(hWnd, uID, psz, cch);
    }

/*
 * GBGizmoIntSet
 * GBGizmoIntGet
 *
 * Purpose:
 *  Retrieves or sets an integer in a GizmoBar gizmo.  Separators,
 *  command buttons, and attribute buttons are not affected by this
 *  call.
 *
 * Parameters:
 *  hWnd            HWND of the GizmoBar.
 *  uID             UINT identifying the gizmo.
 *
 *  (Set only)
 *  u               UINT value to set in the gizmo.
 *  fSigned         BOOL TRUE to indicate if the value is signed.
 *
 *  (Get only)
 *  pfTrans         BOOL FAR * in which the success of the function
 *                  is returned.
 *  fSigned         BOOL TRUE to indicate if the value is signed.
 *
 * Return Value:
 *  (Set): None
 *  (Get): UINT     Integer translation of the gizmo's text.
 */

void WINAPI GBGizmoIntSet(HWND hWnd, UINT uID, UINT u, BOOL fSigned)
    {
    //This fails on non-windowed gizmos anyway, so we don't check.
    SetDlgItemInt(hWnd, uID, u, fSigned);
    return;
    }

UINT WINAPI GBGizmoIntGet(HWND hWnd, UINT uID, BOOL FAR *pfTrans
    , BOOL fSigned)
    {
    //This fails on non-windowed gizmos anyway, so we don't check.
    return GetDlgItemInt(hWnd, uID, pfTrans, fSigned);
    }


GIZMOBAR.C

/*
 * GIZMOBAR.C
 *
 * Contains the main window procedure of the GizmoBar control
 * that handles mouse logic and Windows messages.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "inoledll.h"

//In GIZMO.C
extern TOOLDISPLAYDATA g_tdd;

/*
 * GizmoBarPAllocate
 *
 * Purpose:
 *  Allocates and initializes the control's primary data structure
 *  for each window that gets created.
 *
 * Parameters:
 *  pfSuccess       int * indicating success of the function.
 *  hWnd            HWND that is tied to this structure.
 *  hInst           HINSTANCE of the DLL.
 *  hWndAssociate   HWND to which we send messages.
 *  dwStyle         DWORD initial style.
 *  uState          UINT initial state.
 *  uID             UINT identifier for this window.
 *
 * Return Value:
 *  PGIZMOBAR       If NULL returned then GizmoBarPAllocate could not
 *                  allocate memory.  If a non-NULL pointer is
 *                  returned with *pfSuccess, then call GizmoBarPFree
 *                  immediately.  If you get a non-NULL pointer and
 *                  *pfSuccess==TRUE then the function succeeded.
 */

PGIZMOBAR GizmoBarPAllocate(int *pfSuccess, HWND hWnd
    , HINSTANCE hInst, HWND hWndAssociate, DWORD dwStyle
    , UINT uState , UINT uID)
    {
    PGIZMOBAR     pGB;

    if (NULL==pfSuccess)
        return NULL;

    *pfSuccess=FALSE;

    //Allocate the structure
    pGB=(PGIZMOBAR)(void *)LocalAlloc(LPTR, CBGIZMOBAR);

    if (NULL==pGB)
        return NULL;

    //Initialize LibMain parameter holders.
    pGB->hWnd         =hWnd;
    pGB->hInst        =hInst;
    pGB->hWndAssociate=hWndAssociate;
    pGB->dwStyle      =dwStyle;
    pGB->uState       =uState;
    pGB->uID          =uID;
    pGB->fEnabled     =TRUE;

    pGB->crFace=GetSysColor(COLOR_BTNFACE);
    pGB->hBrFace=CreateSolidBrush(pGB->crFace);

    if (NULL==pGB->hBrFace)
        return pGB;

    pGB->hFont=GetStockObject(SYSTEM_FONT);

    *pfSuccess=TRUE;
    return pGB;
    }

/*
 * GizmoBarPFree
 *
 * Purpose:
 *  Reverses all initialization done by GizmoBarPAllocate, cleaning
 *  up any allocations including the application structure itself.
 *
 * Parameters:
 *  pGB             PGIZMOBAR to the control's structure
 *
 * Return Value:
 *  PGIZMOBAR       NULL if successful, pGB if not, meaning we
 *                  couldn't free some allocation.
 */

PGIZMOBAR GizmoBarPFree(PGIZMOBAR pGB)
    {
    if (NULL==pGB)
        return NULL;

    /*
     * Free all the gizmos we own.  When we call GizmoPFree we always
     * free the first one in the list which updates pGB->pGizmos for
     * us, so we just have to keep going until pGizmos is NULL,
     * meaning we're at the end of the list.
     */
    while (NULL!=pGB->pGizmos)
        GizmoPFree(&pGB->pGizmos, pGB->pGizmos);

    if (NULL!=pGB->hBrFace)
        DeleteObject(pGB->hBrFace);

    /*
     * Notice that since we never create a font, we aren't
     * responsible for our hFont member.
     */

    return (PGIZMOBAR)(void *)LocalFree((HLOCAL)(UINT)(LONG)pGB);
    }

/*
 * GizmoBarWndProc
 *
 * Purpose:
 *  Window Procedure for the GizmoBar custom control.  Handles all
 *  messages like WM_PAINT just as a normal application window would.
 *  Any message not processed here should go to DefWindowProc.
 *
 * Parameters:
 *  Standard
 *
 * Return Value:
 *  Standard
 */

LRESULT WINAPI GizmoBarWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    BOOL            fSuccess;
    BOOL            fTemp;
    LPCREATESTRUCT  pCreate;
    PGIZMOBAR       pGB;
    PGIZMO          pGizmo;
    RECT            rc;
    POINT           pt;
    short           x, y;
    COLORREF        cr;

    COMMANDPARAMS(wID, wCode, hWndMsg);

    pGB=(PGIZMOBAR)GetWindowLong(hWnd, GBWL_STRUCTURE);

    //Pass control messages onto another function for processing.
    if (iMsg >= WM_USER)
        return GBMessageHandler(hWnd, iMsg, wParam, lParam, pGB);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pCreate=(LPCREATESTRUCT)lParam;

            pGB=GizmoBarPAllocate(&fSuccess, hWnd
                , pCreate->hInstance, pCreate->hwndParent
                , pCreate->style, 0, (UINT)pCreate->hMenu);

            if (!fSuccess)
                {
                GizmoBarPFree(pGB);
                return -1L;
                }
            else
                SetWindowLong(hWnd, GBWL_STRUCTURE, (LONG)pGB);

            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        case WM_DESTROY:
            /*
             * We want to clean up before DestroyWindow nukes all the
             * children, so WM_DESTROY is a better to do it than
             * WM_NCDESTROY.
             */
            GizmoBarPFree(pGB);
            break;

        case WM_ERASEBKGND:
            /*
             * Eat this message to avoid erasing portions that
             * we are going to repaint in WM_PAINT.  Part of a
             * change-state-and-repaint strategy is to rely on
             * WM_PAINT to do anything visual, which includes
             * erasing invalid portions.  Letting WM_ERASEBKGND
             * erase the background is redundant.
             */
            return TRUE;

       #ifdef WIN32
        case WM_CTLCOLORBTN:
        case WM_CTLCOLORSTATIC:
            fTemp=TRUE;
       #else
        case WM_CTLCOLOR:
            //Change the color of static text on the GizmoBar.
            fTemp=(HIWORD(lParam)==CTLCOLOR_STATIC
                || HIWORD(lParam)==CTLCOLOR_BTN);
       #endif

            if (fTemp)
                {
                cr=GetSysColor(COLOR_BTNFACE);
                SetTextColor((HDC)wParam
                    , GetSysColor(COLOR_BTNTEXT));
                SetBkColor((HDC)wParam, cr);

                /*
                 * If the system colors have changed, then crFace
                 * will not be equal to COLOR_BTNFACE, so we
                 * reinitialize the background brush.  This scheme
                 * handles system color changes appropriately
                 * without processing WM_WININICHANGE and without
                 * blindly creating a new brush on every WM_CTLCOLOR
                 * message.
                 */
                if (cr!=pGB->crFace)
                    {
                    pGB->crFace=cr;

                    if (NULL!=pGB->hBrFace)
                        DeleteObject(pGB->hBrFace);

                    pGB->hBrFace=CreateSolidBrush(pGB->crFace);
                    }

                return (LONG)(UINT)pGB->hBrFace;
                }

            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        case WM_PAINT:
            GizmoBarPaint(hWnd, pGB);
            break;

        case WM_SETFONT:
            /*
             * wParam has the new font that we now send to all other
             * windows controls in us.  We control repaints here to
             * prevent a lot of repainting for each control.
             */
            DefWindowProc(hWnd, WM_SETREDRAW, FALSE, 0L);

            if ((WPARAM)NULL!=wParam)
                {
                pGB->hFont=(HFONT)wParam;
                GizmoPEnum(&pGB->pGizmos, FEnumChangeFont
                    , (DWORD)(LPTSTR)pGB);

                DefWindowProc(hWnd, WM_SETREDRAW, TRUE, 0L);
                InvalidateRect(hWnd, NULL, FALSE);
                UpdateWindow(hWnd);
                }

            break;

        case WM_GETFONT:
            return (LRESULT)(UINT)pGB->hFont;

        case WM_ENABLE:
            /*
             * wParam has the new enable flag that we use to enable
             * or disable ALL controls in us at one time.  We also
             * turn the redraw off to prevent a lot of flicker.
             */
            DefWindowProc(hWnd, WM_SETREDRAW, FALSE, 0L);

            pGB->fEnabled=(BOOL)wParam;
            GizmoPEnum(&pGB->pGizmos, FEnumEnable
                , (DWORD)(LPTSTR)pGB);

            DefWindowProc(hWnd, WM_SETREDRAW, TRUE, 0L);
            InvalidateRect(hWnd, NULL, FALSE);
            UpdateWindow(hWnd);
            break;

        case WM_CANCELMODE:
            pGizmo=pGB->pGizmoTrack;

            pGB->fTracking=FALSE;
            pGB->fMouseOut=FALSE;

            if (NULL!=pGizmo)
                GizmoPStateSet(hWnd, pGizmo, COMMANDBUTTON_UP);

            ReleaseCapture();
            break;

        case WM_LBUTTONDBLCLK:
        case WM_LBUTTONDOWN:
            //Get the mouse coordinates.
            x=LOWORD(lParam);
            y=HIWORD(lParam);

            /*
             * See if we hit a command or attribute gizmo or not.
             * Anything else that is a control will get the message
             * instead of us anyway, so we don't have to check.
             * FEnumHitTest also validates drawn gizmos, enabled, and
             * visible, so we don't.
             */
            pGizmo=GizmoPEnum(&pGB->pGizmos, FEnumHitTest, lParam);

            if (NULL==pGizmo)
                break;          //Didn't hit one matching our needs.

            /*
             * Inform the associate that a command was hit like a
             * menu item.
             */
            if (NULL!=pGB->hWndAssociate)
                {
                if (pGizmo->fNotify)
                    {
                    SendMenuSelect(pGB->hWndAssociate, pGizmo->uID
                        , 0, 0);
                    }
                }

            /*
             * We hit a button.  If it's a command or attribute,
             * then change the state and start tracking.
             */
            pGB->fTracking=TRUE;
            pGB->pGizmoTrack=pGizmo;
            pGB->fMouseOut=FALSE;
            SetCapture(hWnd);

            pGizmo->uStateOrg=pGizmo->uState;
            GizmoPStateSet(hWnd, pGizmo, ATTRIBUTEBUTTON_MOUSEDOWN);

            break;

        case WM_MOUSEMOVE:
            POINTFROMLPARAM(pt, lParam);

            if (!pGB->fTracking)
                break;

            pGizmo=pGB->pGizmoTrack;
            SetRect(&rc, pGizmo->x, pGizmo->y, pGizmo->x+pGizmo->dx
                , pGizmo->y+pGizmo->dy);

            fTemp=pGB->fMouseOut;
            pGB->fMouseOut=!PtInRect(&rc, pt);

            //If the mouse went out, change state to the original.
            if (!fTemp && pGB->fMouseOut)
                {
                GizmoPStateSet(hWnd, pGizmo, pGizmo->uStateOrg);

                if (NULL!=pGB->hWndAssociate)
                    {
                    //Notify that we left the button
                    if (pGizmo->fNotify)
                        SendMenuSelect(pGB->hWndAssociate, 0, ~0, 0);
                    }
                }

            if (fTemp && !pGB->fMouseOut)
                {
                GizmoPStateSet(hWnd, pGizmo
                    , ATTRIBUTEBUTTON_MOUSEDOWN);

                if (NULL!=pGB->hWndAssociate)
                    {
                    //Notify that we pressed down again
                    if (pGizmo->fNotify)
                        {
                        SendMenuSelect(pGB->hWndAssociate
                            , pGizmo->uID , 0, 0);
                        }
                    }
                }

            break;

        case WM_LBUTTONUP:
            if (!pGB->fTracking)
                break;

            pGB->fTracking=FALSE;
            pGizmo=pGB->pGizmoTrack;
            ReleaseCapture();

            /*
             * Repaint if we were actually below the mouse when this
             * occurred.  For command buttons, pop the button up.
             * For attributes, either toggle the state (inclusive
             * buttons) or check the selected one (exclusive buttons)
             */

            if (!pGB->fMouseOut)
                {
                //Command buttons always come up.
                if (GIZMOTYPE_BUTTONCOMMAND==pGizmo->iType)
                    GizmoPStateSet(hWnd, pGizmo, COMMANDBUTTON_UP);

                //Attribute inclusive buttons toggle
                if (GIZMOTYPE_BUTTONATTRIBUTEIN==pGizmo->iType)
                    {
                    GizmoPCheck(hWnd, pGizmo, !(BUTTONGROUP_DOWN
                        & pGizmo->uStateOrg));
                    }

                //Attribure exclusive buttons are always checked.
                if (GIZMOTYPE_BUTTONATTRIBUTEEX==pGizmo->iType)
                    GizmoPCheck(hWnd, pGizmo, TRUE);

                //Only send messages if notify is ON.
                if (NULL!=pGB->hWndAssociate && pGizmo->fNotify)
                    {
                    SendMenuSelect(pGB->hWndAssociate, 0, ~0, 0);
                    SendCommand(pGB->hWndAssociate, pGizmo->uID
                        , BN_CLICKED, hWnd);
                    }
                }

            break;

        case WM_COMMAND:
            //Pass control messages on if the gizmo's notify is ON.
            if (NULL!=pGB->hWndAssociate)
                {
                pGizmo=PGizmoFromHwndID(hWnd, wID);

                if (NULL!=pGizmo)
                    {
                    if (pGizmo->fNotify)
                        {
                        SendMessage(pGB->hWndAssociate, iMsg, wParam
                            , lParam);
                        }
                    }
                }
            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }

/*
 * GizmoBarPaint
 *
 * Purpose:
 *  Handles all WM_PAINT messages for the control and paints either
 *  the entire thing or just one GizmoBar button if pGB->pGizmoPaint
 *  is non-NULL.
 *
 * Parameters:
 *  hWnd            HWND Handle to the control.
 *  pGB             PGIZMOBAR control data pointer.
 *
 * Return Value:
 *  None
 */

void GizmoBarPaint(HWND hWnd, PGIZMOBAR pGB)
    {
    RECT            rc;
    HDC             hDC;
    HBRUSH          hBr=NULL;
    HPEN            hPen=NULL;
    PAINTGIZMO      pg;

    hDC=BeginPaint(hWnd, &pg.ps);
    GetClientRect(hWnd, &rc);

    /*
     * The only part of the frame we need to draw is the bottom line,
     * so we inflate the rectangle such that all other parts are
     * outside the visible region.
     */
    hBr=CreateSolidBrush(GetSysColor(COLOR_BTNFACE));

    if (NULL!=hBr)
        SelectObject(hDC, hBr);

    hPen=CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));

    if (NULL!=hPen)
        SelectObject(hDC, hPen);

    Rectangle(hDC, rc.left-1, rc.top-1, rc.right+1, rc.bottom);

    /*
     * All that we have to do to draw the controls is start through
     * the list, ignoring anything but buttons, and calling BTTNCUR's
     * UIToolButtonDraw for buttons.  Since we don't even have to
     * track positions of things, we can just use an enum.
     */

    UIToolConfigureForDisplay(&pg.tdd);
    GizmoPEnum(&pGB->pGizmos, FEnumPaintGizmos, (DWORD)(LPTSTR)&pg);

    //Clean up
    EndPaint(hWnd, &pg.ps);

    if (NULL!=hBr)
        DeleteObject(hBr);

    if (NULL!=hPen)
        DeleteObject(hPen);

    return;
    }

/*
 * FEnumPaintGizmos
 *
 * Purpose:
 *  Enumeration callback for all the gizmos we know about in order to
 *  draw them.
 *
 * Parameters:
 *  pGizmo          PGIZMO to draw.
 *  iGizmo          UINT index on the GizmoBar of this gizmo.
 *  dw              DWORD extra data passed to GizmoPEnum, in our
 *                  case a pointer to a PAINTGIZMO structure.
 *
 * Return Value:
 *  BOOL            TRUE to continue the enumeration, FALSE
 *                  otherwise.
 */

BOOL WINAPI FEnumPaintGizmos(PGIZMO pGizmo, UINT iGizmo, DWORD dw)
    {
    PPAINTGIZMO     ppg=(PPAINTGIZMO)dw;
    RECT            rc, rcI;

    //Only draw those marked for repaint.
    if ((GIZMOTYPE_DRAWN & pGizmo->iType))
        {
        SetRect(&rc, pGizmo->x, pGizmo->y
            , pGizmo->x+pGizmo->dx, pGizmo->y+pGizmo->dy);

        //Only draw gizmos in the repaint area
        if (IntersectRect(&rcI, &rc, &ppg->ps.rcPaint))
            {
            UIToolButtonDraw(ppg->ps.hdc, pGizmo->x, pGizmo->y
                , pGizmo->dx, pGizmo->dy, pGizmo->hBmp
                , pGizmo->cxImage, pGizmo->cyImage, pGizmo->iBmp
                , (UINT)pGizmo->uState, &ppg->tdd);
            }
        }

    return TRUE;
    }

/*
 * FEnumChangeFont
 *
 * Purpose:
 *  Enumeration callback for all the gizmos we know about in order to
 *  send a new font to them that's stored in PGIZMOBAR in dw.
 *
 * Parameters:
 *  pGizmo          PGIZMO to draw.
 *  iGizmo          UINT index on the GizmoBar of this gizmo.
 *  dw              DWORD extra data passed to GizmoPEnum, in our
 *                  case the GizmoBar's pGB.
 *
 * Return Value:
 *  BOOL            TRUE to continue the enumeration, FALSE otherwise.
 */

BOOL WINAPI FEnumChangeFont(PGIZMO pGizmo, UINT iGizmo, DWORD dw)
    {
    PGIZMOBAR   pGB=(PGIZMOBAR)dw;

    //Only need to change fonts in windowed controls using WM_SETFONT
    if (NULL!=pGizmo->hWnd)
        {
        SendMessage(pGizmo->hWnd, WM_SETFONT
            , (WPARAM)pGB->hFont, 1L);
        }

    return TRUE;
    }

/*
 * FEnumEnable
 *
 * Purpose:
 *  Enumeration callback for all the gizmos we know about in order to
 *  enable or disable them from the WM_ENABLE message.
 *
 * Parameters:
 *  pGizmo          PGIZMO to draw.
 *  iGizmo          UINT index on the GizmoBar of this gizmo.
 *  dw              DWORD extra data passed to GizmoPEnum, in our
 *                  case the GizmoBar's pGB.
 *
 * Return Value:
 *  BOOL            TRUE to continue the enumeration, FALSE
 *                  otherwise.
 */

BOOL WINAPI FEnumEnable(PGIZMO pGizmo, UINT iGizmo, DWORD dw)
    {
    PGIZMOBAR   pGB=(PGIZMOBAR)dw;
    BOOL        fEnable=pGB->fEnabled;

    //NOTE:  This code is duplicated in GBGizmoEnable in API.C
    if (NULL!=pGizmo->hWnd)
        EnableWindow(pGizmo->hWnd, fEnable);
    else
        {
        //If we're not down, command and attribute buttons act same.
        if (!(BUTTONGROUP_DOWN & pGizmo->uState))
            {
            GizmoPStateSet(pGB->hWnd, pGizmo, fEnable
                ? COMMANDBUTTON_UP : COMMANDBUTTON_DISABLED);
            }
        else
            {
            /*
             * Attribute buttons are a little more sensitive
             * with DOWNDISABLED
             */
            GizmoPStateSet(pGB->hWnd, pGizmo, fEnable
                ? ATTRIBUTEBUTTON_DOWN
                : ATTRIBUTEBUTTON_DOWNDISABLED);
            }
        }

    return TRUE;
    }

/*
 * FEnumHitTest
 *
 * Purpose:
 *  Enumeration callback for all the gizmos we know about in order to
 *  hit-test them.
 *
 * Parameters:
 *  pGizmo          PGIZMO to draw.
 *  iGizmo          UINT index on the GizmoBar of this gizmo.
 *  dw              DWORD extra data passed to GizmoPEnum, in our
 *                  case the hDC on which to draw.
 *
 * Return Value:
 *  BOOL            TRUE to continue the enumeration, FALSE
 *                  otherwise.
 */

BOOL WINAPI FEnumHitTest(PGIZMO pGizmo, UINT iGizmo, DWORD dw)
    {
    RECT  rc;
    POINT pt;

    POINTFROMLPARAM(pt, dw);

    /*
     * Hit tests have to happen on visible, enabled, and drawn
     * controls only.
     */
    if (GIZMOTYPE_DRAWN & pGizmo->iType && !pGizmo->fHidden
        && !(BUTTONGROUP_DISABLED & pGizmo->uState))
        {
        SetRect(&rc, pGizmo->x, pGizmo->y
            , pGizmo->x+pGizmo->dx, pGizmo->y+pGizmo->dy);

        //Stop enumeration if we have a hit.
        return !PtInRect(&rc, pt);
        }

    return TRUE;
    }


GIZMOINT.H

/*
 * GIZMOINT.H
 * Gizmobar Internals
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _GIZMOINT_H_
#define _GIZMOINT_H_

#ifdef __cplusplus
extern "C"
    {
#endif

typedef struct tagGIZMO
    {
    struct tagGIZMO     *pPrev;
    struct tagGIZMO     *pNext;
    UINT                 iType;
    HWND                 hWnd;       //Texts, edits, lists, combos
    UINT                 uID;
    UINT                 x, y;
    UINT                 dx, dy;
    UINT                 cxImage;    //From UIToolConfigureForDisplay
    UINT                 cyImage;
    HBITMAP              hBmp;       //Buttons only.
    UINT                 iBmp;
    BOOL                 fNotify;    //Send WM_COMMANDs?
    BOOL                 fHidden;    //Independent of state
    BOOL                 fDisabled;
    UINT                 uState;
    UINT                 uStateOrg;
    DWORD                dwData;     //Application-supplied data.
    } GIZMO, * PGIZMO;

typedef PGIZMO *PPGIZMO;
#define CBGIZMO sizeof(GIZMO)

//Property name we attach to controls in a gizmo to identify type
#define SZTYPEPROP      TEXT("iType")

//Number of controls we subclass
#define CSUBGIZMOS       4

//ID of edit controls in comboboxes
#define ID_COMBOEDIT     1001

/*
 * Conversion of iType (a positioned bit) into its position.
 * The BITPOSITION macro does not need to be fast because we only
 * use it once when creating a gizmo.  POSITIONBIT does, however,
 * since we use it in subclass procedures.
 */
#define BITPOSITION(i, j)  {int k=i; for (j=0; k>>=1; j++);}
#define POSITIONBIT(i)     (1 << i)

//Control classifications.  GIZMOBAR.H must be included first.
#define GIZMOTYPE_WINDOWS   (GIZMOTYPE_TEXT | GIZMOTYPE_EDIT \
                            | GIZMOTYPE_LISTBOX              \
                            | GIZMOTYPE_COMBOBOX             \
                            | GIZMOTYPE_BUTTONNORMAL)

#define GIZMOTYPE_BUTTONS   (GIZMOTYPE_BUTTONATTRIBUTEIN     \
                            | GIZMOTYPE_BUTTONATTRIBUTEEX    \
                            | GIZMOTYPE_BUTTONCOMMAND        \
                            | GIZMOTYPE_BUTTONNORMAL)

#define GIZMOTYPE_DRAWN     (GIZMOTYPE_BUTTONATTRIBUTEIN     \
                            | GIZMOTYPE_BUTTONATTRIBUTEEX    \
                            | GIZMOTYPE_BUTTONCOMMAND)

//These must stay in sync with GIZMOBAR.H
#define GIZMOTYPE_MIN               GIZMOTYPE_EDIT
#define GIZMOTYPE_MAX               GIZMOTYPE_BUTTONCOMMAND

//Enumeration callback
typedef BOOL (CALLBACK *PFNGIZMOENUM)(PGIZMO, UINT, DWORD);

//GIZMO.C
PGIZMO   GizmoPAllocate(int *, PPGIZMO, HWND, UINT, UINT, UINT
             , UINT, UINT, LPTSTR, HBITMAP, UINT, UINT);
void     GizmosExpand(PGIZMO);
PGIZMO   GizmoPFree(PPGIZMO, PGIZMO);
void     GizmosCompact(PGIZMO);
PGIZMO   GizmoPFind(PPGIZMO, UINT);
PGIZMO   GizmoPEnum(PPGIZMO, PFNGIZMOENUM, DWORD);
UINT     GizmoPStateSet(HWND, PGIZMO, UINT);
BOOL     GizmoPCheck(HWND, PGIZMO, BOOL);

LRESULT APIENTRY GenericSubProc(HWND, UINT, WPARAM, LPARAM);

/*
 * The main gizmobar structure itself.  There's only one of these,
 * but it references the first GIZMO in the list.
 */

typedef struct tagGIZMOBAR
    {
    PGIZMO      pGizmos;            //List of gizmos we own.
    HWND        hWnd;               //Window handle of ourselves.
    HINSTANCE   hInst;
    HWND        hWndAssociate;      //Associate who gets messages.
    DWORD       dwStyle;            //Copy of GWL_STYLE
    UINT        uState;             //State flags
    UINT        uID;                //Control ID.

    HBRUSH      hBrFace;            //Static control background color
    COLORREF    crFace;             //Color of hBrFace
    HFONT       hFont;              //Font in use
    BOOL        fEnabled;           //Are we enabled?

    PGIZMO      pGizmoTrack;        //Current pressed button.
    BOOL        fTracking;
    BOOL        fMouseOut;
    } GIZMOBAR, * PGIZMOBAR;

#define CBGIZMOBAR sizeof(GIZMOBAR)

//Extra bytes for the window if the size of a local handle.
#define CBEXTRAGIZMOBAR     sizeof(PGIZMOBAR)

#define GBWL_STRUCTURE      0

//Structure for passing paint info to a gizmo enumeration callback.
typedef struct
    {
    PAINTSTRUCT         ps;
    TOOLDISPLAYDATA     tdd;
    } PAINTGIZMO, *PPAINTGIZMO;

//GIZMOBAR.C
PGIZMOBAR         GizmoBarPAllocate(int *, HWND, HINSTANCE, HWND
                      , DWORD, UINT, UINT);
PGIZMOBAR         GizmoBarPFree(PGIZMOBAR);
LRESULT WINAPI    GizmoBarWndProc(HWND, UINT, WPARAM, LPARAM);
BOOL    WINAPI    FEnumChangeFont(PGIZMO, UINT, DWORD);
BOOL    WINAPI    FEnumEnable(PGIZMO, UINT, DWORD);
BOOL    WINAPI    FEnumHitTest(PGIZMO, UINT, DWORD);
void              GizmoBarPaint(HWND, PGIZMOBAR);
BOOL WINAPI       FEnumPaintGizmos(PGIZMO, UINT, DWORD);

//GIZMOAPI.C  See GIZMOBAR.H for others
LRESULT    GBMessageHandler(HWND, UINT, WPARAM, LPARAM, PGIZMOBAR);
PGIZMO     PGizmoFromHwndID(HWND, UINT);

#ifdef __cplusplus
    }
#endif

#endif //_GIZMOINT_H_


HELPERS.CPP

/*
 * HELPERS.CPP
 *
 * Utility functions for drawing various graphical effects
 * as well as coordinate conversion functions.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "inoledll.h"

/*
 * INOLE_MetafilePictIconFree
 *
 * Purpose:
 *  Deletes the metafile contained in a METAFILEPICT structure and
 *  frees the memory for the structure itself.
 *
 * Parameters:
 *  hMetaPict       HGLOBAL metafilepict structure created in
 *                  OleMetafilePictFromIconAndLabel
 *
 * Return Value:
 *  None
 */

STDAPI_(void) INOLE_MetafilePictIconFree(HGLOBAL hMetaPict)
   {
   LPMETAFILEPICT      pMF;

   if (NULL==hMetaPict)
      return;

   pMF=(LPMETAFILEPICT)GlobalLock(hMetaPict);

   if (NULL!=pMF)
      {
      if (NULL!=pMF->hMF)
         DeleteMetaFile(pMF->hMF);
      }

   GlobalUnlock(hMetaPict);
   GlobalFree(hMetaPict);
   return;
   }

/*
 * INOLE_SwitchDisplayAspect
 *
 * Purpose:
 *  Switch the currently cached display aspect between DVASPECT_ICON
 *  and DVASPECT_CONTENT.  When setting up icon aspect, any currently
 *  cached content cache is discarded and any advise connections for
 *  content aspect are broken.
 *
 * Parameters:
 *  pObj            IUnknown * to the object in question
 *  pdwCurAspect    DWORD * containing the current aspect which
 *                  will contain the new aspect on output.
 *  dwNewAspect     DWORD with the aspect to switch to.
 *  hMetaPict       HGLOBAL containing the CF_METAFILEPICT with
 *                  the icon.
 *  fDeleteOld      BOOL indicating if we're to delete the old
 *                  aspect from the cache.
 *  fViewAdvise     BOOL indicating if we're to establish an
 *                  advise with the object for this new aspect.
 *  pSink           IAdviseSink * to the notification sink.
 *  pfMustUpdate    BOOL * in which to return whether or not
 *                  an update from a running server is necessary.
 *
 * Return Value:
 *  HRESULT         NOERROR or an error code in which case the cache
 *                  remains unchanged.
 */

STDAPI INOLE_SwitchDisplayAspect(IUnknown *pObj, LPDWORD pdwCurAspect
    , DWORD dwNewAspect, HGLOBAL hMetaPict, BOOL fDeleteOld
    , BOOL fViewAdvise, IAdviseSink *pSink, BOOL *pfMustUpdate)
    {
    IOleCache      *pCache=NULL;
    FORMATETC       fe;
    STGMEDIUM       stm;
    DWORD           dwAdvf;
    DWORD           dwNewConnection;
    DWORD           dwOldAspect=*pdwCurAspect;
    HRESULT         hr;

    if (pfMustUpdate)
       *pfMustUpdate=FALSE;

    hr=pObj->QueryInterface(IID_IOleCache, (void **)&pCache);

    if (FAILED(hr))
       return ResultFromScode(E_FAIL);

    //Establish new cache with the new aspect
    SETFormatEtc(fe, 0, dwNewAspect, NULL, TYMED_NULL, -1);

    /*
     * If we are using a custom icon for the display aspect then
     * we won't want to establish notifications with the data
     * source, that is, we don't want to change the icon.  Otherwise
     * we link up the given advise sink.
     */
    if (DVASPECT_ICON==dwNewAspect && NULL!=hMetaPict)
       dwAdvf=ADVF_NODATA;
    else
       dwAdvf=ADVF_PRIMEFIRST;

    hr=pCache->Cache(&fe, dwAdvf, &dwNewConnection);

    if (FAILED(hr))
        {
        pCache->Release();
        return hr;
        }

    *pdwCurAspect=dwNewAspect;

    /*
     * Stuff the custom icon into the cache, or force an update
     * from the server.
     */
    if (DVASPECT_ICON==dwNewAspect && NULL!=hMetaPict)
        {
        SETFormatEtc(fe, CF_METAFILEPICT, DVASPECT_ICON, NULL
            , TYMED_MFPICT, -1);

        stm.tymed=TYMED_MFPICT;
        stm.hGlobal=hMetaPict;
        stm.pUnkForRelease=NULL;

        hr=pCache->SetData(&fe, &stm, FALSE);
        }
    else
        {
        if (pfMustUpdate)
           *pfMustUpdate=TRUE;
        }

    if (fViewAdvise && pSink)
        {
        IViewObject    *pView=NULL;

        hr=pObj->QueryInterface(IID_IViewObject, (void **)&pView);

        if (SUCCEEDED(hr))
            {
            pView->SetAdvise(dwNewAspect, 0, pSink);
            pView->Release();
            }
        }

    /*
     * Remove existing caches for the old display aspect to cut
     * down on needless storage overhead.  If you want to switch
     * frequently between icon and content aspects, then it is
     * best to actually keep both presentations in the cache.
     */
    if (fDeleteOld)
        {
        IEnumSTATDATA  *pEnum=NULL;
        STATDATA        sd;

        hr=pCache->EnumCache(&pEnum);

        while(NOERROR==hr)
            {
            hr=pEnum->Next(1, &sd, NULL);

            if (NOERROR==hr)
                {
                //Remove old aspect caches
                if (sd.formatetc.dwAspect==dwOldAspect)
                    pCache->Uncache(sd.dwConnection);
                }
            }

        if (NULL!=pEnum)
            pEnum->Release();
        }

    pCache->Release();
    return NOERROR;
    }

/*
 * INOLE_SetIconInCache
 *
 * Purpose:
 *  Stores an iconic presentation metafile in the cache.
 *
 * Parameters:
 *  pObj            IUnknown * of the object.
 *  hMetaPict       HGLOBAL containing the presentation.
 *
 * Return Value:
 *  HRESULT         From IOleCache::SetData.
 */

STDAPI INOLE_SetIconInCache(IUnknown *pObj, HGLOBAL hMetaPict)
    {
    IOleCache      *pCache;
    FORMATETC       fe;
    STGMEDIUM       stm;
    HRESULT         hr;

    if (NULL==hMetaPict)
        return ResultFromScode(E_INVALIDARG);

    if (FAILED(pObj->QueryInterface(IID_IOleCache, (void **)&pCache)))
        return ResultFromScode(E_NOINTERFACE);

    SETFormatEtc(fe, CF_METAFILEPICT, DVASPECT_ICON, NULL
        , TYMED_MFPICT, -1);

    stm.tymed=TYMED_MFPICT;
    stm.hGlobal=hMetaPict;
    stm.pUnkForRelease=NULL;

    hr=pCache->SetData(&fe, &stm, FALSE);
    pCache->Release();

    return hr;
    }

/*
 * INOLE_GetUserTypeOfClass
 *
 * Purpose:
 *  Returns the user type (human readable class name) of the
 *  specified class as stored in the registry.
 *
 * Parameters:
 *  clsID           CLSID in question
 *  iName           UINT index to the name to retrieve, where
 *                  zero is the name found as the value of
 *                  of the CLSID, anything else tries
 *                  AuxUserType\iName.
 *  pszUserType     LPTSTR in which to return the type
 *  cch             UINT length of pszUserType
 *
 * Return Value:
 *  UINT            Number of characters in returned string.
 *                  0 on error.
 */

STDAPI_(UINT) INOLE_GetUserTypeOfClass(REFCLSID clsID, UINT iName
    , LPTSTR pszUserType, UINT cch)
    {
    LPTSTR      pszCLSID;
    LPTSTR      pszProgID;
    TCHAR       szKey[300];
    LONG        dw;
    LONG        lRet;

    if (!pszUserType)
        return 0;

    *pszUserType='\0';

    //Get a string containing the class name
    StringFromCLSID(clsID, &pszCLSID);

    if (0==iName)
        wsprintf(szKey, TEXT("CLSID\\%s"), pszCLSID);
    else
        {
        wsprintf(szKey, TEXT("CLSID\\%s\\AuxUserType\\%u")
            , pszCLSID, iName);
        }

    CoTaskMemFree(pszCLSID);

    dw=cch;
    lRet=RegQueryValue(HKEY_CLASSES_ROOT, szKey, pszUserType, &dw);

    if (ERROR_SUCCESS!=lRet)
        {
        lstrcpyn(pszUserType, TEXT("Unknown"), cch);

        if (CoIsOle1Class(clsID))
            {
            //Try to get ProgID value for OLE 1 class
            ProgIDFromCLSID(clsID, &pszProgID);

            dw=cch;
            lRet=RegQueryValue(HKEY_CLASSES_ROOT, pszProgID
                , pszUserType, &dw);

            CoTaskMemFree(pszProgID);

            if (ERROR_SUCCESS!=lRet)
                dw=0;
            }
        }

    return (UINT)dw;
    }

/*
 * INOLE_DoConvert
 *
 * Purpose:
 *  Convert an embedded or linked object to another type, working
 *  in conjunection with OleUIConvert.
 *
 * Parameters:
 *  pIStorage       IStorage * to the object's data.
 *  clsID           CLSID to which we convert the object.
 *
 * Return Value:
 *  HRESULT         The usual.
 */

STDAPI INOLE_DoConvert(IStorage *pIStorage, REFCLSID clsID)
    {
    HRESULT     hr;
    CLSID       clsIDOrg;
    CLIPFORMAT  cfOrg;
    LPTSTR      pszOrg=NULL;
    TCHAR       szNew[256];

    if (FAILED(ReadClassStg(pIStorage, &clsIDOrg)))
        return ResultFromScode(E_FAIL);

    //Read original format/user type
    hr=ReadFmtUserTypeStg(pIStorage, &cfOrg, &pszOrg);

    //Get new user type
    if (0==INOLE_GetUserTypeOfClass(clsID, 0, szNew, 256))
        lstrcpy(szNew, TEXT(""));

    //Write new class into the storage
    if (SUCCEEDED(WriteClassStg(pIStorage, clsID)))
        {
        if (SUCCEEDED(WriteFmtUserTypeStg(pIStorage, cfOrg
            , szNew)))
            {
            SetConvertStg(pIStorage, TRUE);
            CoTaskMemFree((void *)pszOrg);
            return NOERROR;
            }

        //Failed to write new type, restore the old class
        WriteClassStg(pIStorage, clsIDOrg);
        }

    CoTaskMemFree((void *)pszOrg);
    return ResultFromScode(E_FAIL);
    }

/*
 * INOLE_CopyString
 *
 * Purpose:
 *  Copies a string allocated with CoTaskMemAlloc.
 *
 * Parameters:
 *  pszSrc          LPTSTR to the string to copy.
 *
 * Return Value:
 *  LPTSTR          New string or a NULL.
 */

STDAPI_(LPTSTR) INOLE_CopyString(LPTSTR pszSrc)
    {
    IMalloc    *pIMalloc;
    LPTSTR      pszDst;
    UINT        cch;

    cch=lstrlen(pszSrc);

    if (FAILED(CoGetMalloc(MEMCTX_TASK, &pIMalloc)))
        return NULL;

    pszDst=(LPTSTR)pIMalloc->Alloc((cch+1)*sizeof(TCHAR));

    if (NULL!=pszDst)
       lstrcpy(pszDst, pszSrc);

    pIMalloc->Release();
    return pszDst;
    }

/*
 * INOLE_ObjectDescriptorFromOleObject
 *
 * Purpose:
 *  Fills and returns an OBJECTDESCRIPTOR structure. Information
 *  for the structure is obtained from an IOleObject instance.
 *
 * Parameters:
 *  pObj            IOleObject * from which to retrieve information.
 *  dwAspect        DWORD with the display aspect
 *  ptl             POINTL from upper-left corner of object where
 *                  mouse went down for use with Drag & Drop.
 *  pszl            LPSIZEL (optional) if the object is being scaled in
 *                  its container, then the container should pass the
 *                  extents that it is using to display the object.
 *
 * Return Value:
 *  HBGLOBAL         Handle to OBJECTDESCRIPTOR structure.
 */

STDAPI_(HGLOBAL) INOLE_ObjectDescriptorFromOleObject
    (IOleObject *pObj, DWORD dwAspect, POINTL ptl, LPSIZEL pszl)
    {
    CLSID           clsID;
    LPTSTR          pszName=NULL;
    LPTSTR          pszSrc=NULL;
    BOOL            fLink=FALSE;
    IOleLink       *pLink;
    TCHAR           szName[512];
    DWORD           dwMisc=0;
    SIZEL           szl;
    HGLOBAL         hMem;
    HRESULT         hr;
   #ifdef WIN32ANSI
   //#ifndef UNICODE
    LPWSTRpszw;
    char            szTemp[512];
   #endif

    if (SUCCEEDED(pObj->QueryInterface(IID_IOleLink
        , (void **)&pLink)))
        fLink=TRUE;

    if (FAILED(pObj->GetUserClassID(&clsID)))
        clsID=CLSID_NULL;

    //Get user string, expand to "Linked %s" if this is link
   #ifndef WIN32ANSI
   //#ifdef UNICODE
    pObj->GetUserType(USERCLASSTYPE_FULL, &pszName);
   #else
    pObj->GetUserType(USERCLASSTYPE_FULL, &pszw);
    WideCharToMultiByte(CP_ACP, 0, pszw, -1, szTemp
        , sizeof(szTemp), NULL, NULL);
pszName=szTemp;
   #endif

    if (fLink && NULL!=pszName)
        wsprintf(szName, TEXT("Linked %s"), pszName);
    else
        lstrcpy(szName, pszName);

   #ifndef WIN32ANSI
   //#ifdef UNICODE
    CoTaskMemFree(pszName);
   #else
CoTaskMemFree(pszw);
    pszw=NULL;
   #endif

    /*
     * Get the source name of this object using either the
     * link display name (for link) or a moniker display
     * name.
     */

    if (fLink)
{
       #ifndef WIN32ANSI
       //#ifdef UNICODE
        hr=pLink->GetSourceDisplayName(&pszSrc);
   #else
        hr=pLink->GetSourceDisplayName(&pszw);
        WideCharToMultiByte(CP_ACP, 0, pszw, -1, szTemp
            , sizeof(szTemp), NULL, NULL);
    pszSrc=szTemp;
   #endif
}
    else
        {
        IMoniker   *pmk;

        hr=pObj->GetMoniker(OLEGETMONIKER_TEMPFORUSER
            , OLEWHICHMK_OBJFULL, &pmk);

        if (SUCCEEDED(hr))
            {
            IBindCtx  *pbc;
            CreateBindCtx(0, &pbc);

           #ifndef WIN32ANSI
           //#ifdef UNICODE
            pmk->GetDisplayName(pbc, NULL, &pszSrc);
    #else
            pmk->GetDisplayName(pbc, NULL, &pszw);
            WideCharToMultiByte(CP_ACP, 0, pszw, -1, szTemp
                , sizeof(szTemp), NULL, NULL);
        pszSrc=szTemp;
   #endif

            pbc->Release();

            pmk->Release();
            }
        }

    if (fLink)
        pLink->Release();

    //Get MiscStatus bits
    hr=pObj->GetMiscStatus(dwAspect, &dwMisc);

    //Get OBJECTDESCRIPTOR
    hMem=INOLE_AllocObjectDescriptor(clsID, dwAspect, szl, ptl
        , dwMisc, szName, pszSrc);

   #ifndef WIN32ANSI
   //#ifdef UNICODE
    CoTaskMemFree(pszSrc);
   #else
    if (NULL!=pszw)
    CoTaskMemFree(pszw);
   #endif

    return hMem;
    }

/*
 * INOLE_AllocObjectDescriptor
 *
 * Purpose:
 *  Allocated and fills an OBJECTDESCRIPTOR structure.
 *
 * Parameters:
 *  clsID           CLSID to store.
 *  dwAspect        DWORD with the display aspect
 *  pszl            LPSIZEL (optional) if the object is being scaled in
 *                  its container, then the container should pass the
 *                  extents that it is using to display the object.
 *  ptl             POINTL from upper-left corner of object where
 *                  mouse went down for use with Drag & Drop.
 *  dwMisc          DWORD containing MiscStatus flags
 *  pszName         LPTSTR naming the object to copy
 *  pszSrc          LPTSTR identifying the source of the object.
 *
 * Return Value:
 *  HBGLOBAL         Handle to OBJECTDESCRIPTOR structure.
 */

STDAPI_(HGLOBAL) INOLE_AllocObjectDescriptor(CLSID clsID
    , DWORD dwAspect, SIZEL szl, POINTL ptl, DWORD dwMisc
    , LPTSTR pszName, LPTSTR pszSrc)
    {
    HGLOBAL              hMem=NULL;
    LPOBJECTDESCRIPTOR   pOD;
    DWORD                cb, cbStruct;
    DWORD                cchName, cchSrc;

    cchName=lstrlen(pszName)+1;

    if (NULL!=pszSrc)
        cchSrc=lstrlen(pszSrc)+1;
    else
        {
        cchSrc=cchName;
        pszSrc=pszName;
        }

    /*
     * Note:  CFSTR_OBJECTDESCRIPTOR is an ANSI structure.
     * That means strings in it must be ANSI.  OLE will do
     * internal conversions back to Unicode as necessary,
     * but we have to put ANSI strings in it ourselves.
     */
    cbStruct=sizeof(OBJECTDESCRIPTOR);
    cb=cbStruct+(sizeof(WCHAR)*(cchName+cchSrc));   //HACK

    hMem=GlobalAlloc(GHND, cb);

    if (NULL==hMem)
        return NULL;

    pOD=(LPOBJECTDESCRIPTOR)GlobalLock(hMem);

    pOD->cbSize=cb;
    pOD->clsid=clsID;
    pOD->dwDrawAspect=dwAspect;
    pOD->sizel=szl;
    pOD->pointl=ptl;
    pOD->dwStatus=dwMisc;

    if (pszName)
        {
        pOD->dwFullUserTypeName=cbStruct;
       #ifdef WIN32ANSI
        MultiByteToWideChar(CP_ACP, 0, pszName, -1
            , (LPWSTR)((LPBYTE)pOD+pOD->dwFullUserTypeName), cchName);
       #else
        lstrcpy((LPTSTR)((LPBYTE)pOD+pOD->dwFullUserTypeName)
            , pszName);
       #endif
        }
    else
        pOD->dwFullUserTypeName=0;  //No string

    if (pszSrc)
        {
        pOD->dwSrcOfCopy=cbStruct+(cchName*sizeof(WCHAR));

       #ifdef WIN32ANSI
        MultiByteToWideChar(CP_ACP, 0, pszSrc, -1
            , (LPWSTR)((LPBYTE)pOD+pOD->dwSrcOfCopy), cchSrc);
       #else
        lstrcpy((LPTSTR)((LPBYTE)pOD+pOD->dwSrcOfCopy), pszSrc);
       #endif
        }
    else
        pOD->dwSrcOfCopy=0;  //No string

    GlobalUnlock(hMem);
    return hMem;
    }

/*
 * INOLE_CreateStorageOnHGlobal
 *
 * Purpose:
 *  Create a memory based storage object on an memory lockbytes.
 *
 * Parameters:
 *  grfMode --  flags passed to StgCreateDocfileOnILockBytes
 *
 *  NOTE: if hGlobal is NULL, then a new IStorage is created and
 *              STGM_CREATE flag is passed to StgCreateDocfileOnILockBytes.
 *        if hGlobal is non-NULL, then it is assumed that the hGlobal already
 *              has an IStorage inside it and STGM_CONVERT flag is passed
 *              to StgCreateDocfileOnILockBytes.
 *
 * Return Value:
 *  IStorage *      Pointer to the storage we create.
 */

STDAPI_(IStorage *) INOLE_CreateStorageOnHGlobal(DWORD grfMode)
    {
    DWORD           grfCreate=grfMode | STGM_CREATE;
    HRESULT         hr;
    ILockBytes     *pLB=NULL;
    IStorage       *pIStorage=NULL;

    hr=CreateILockBytesOnHGlobal(NULL, TRUE, &pLB);

    if (FAILED(hr))
        return NULL;

    hr=StgCreateDocfileOnILockBytes(pLB, grfCreate, 0, &pIStorage);

    pLB->Release();

    return SUCCEEDED(hr) ? pIStorage : NULL;
    }

/*
 * INOLE_GetLinkSourceData
 *
 * Purpose:
 *  Creates a CFSTR_LINKSOURCE data format, that is, a serialized
 *  moniker in a stream.
 *
 * Parameters:
 *  pmk             IMoniker * of the link.
 *  pClsID          CLSID * to write into the stream.
 *  pFE             FORMATETC * describing the data which
 *                  should have TYMED_ISTREAM in it.
 *  pSTM            STGMEDIUM in which to return the data.
 *                  If this has TYMED_NULL, then this function
 *                  allocates the stream, otherwise it writes
 *                  into the stream in pSTM->pstm.
 */

STDAPI INOLE_GetLinkSourceData(IMoniker *pmk, LPCLSID pClsID
    , LPFORMATETC pFE, LPSTGMEDIUM pSTM)
    {
    LPSTREAM    pIStream = NULL;
    DWORD       reserved = 0;
    HRESULT     hr;

    pSTM->pUnkForRelease=NULL;

    if (TYMED_NULL==pSTM->tymed)
        {
        if (pFE->tymed & TYMED_ISTREAM)
            {
            hr=CreateStreamOnHGlobal(NULL, TRUE, &pIStream);

            if (FAILED(hr))
                return ResultFromScode(E_OUTOFMEMORY);

            pSTM->pstm=pIStream;
            pSTM->tymed=TYMED_ISTREAM;
            }
        else
            return ResultFromScode(DATA_E_FORMATETC);
        }
    else
        {
        if (TYMED_ISTREAM==pSTM->tymed)
            {
            pSTM->tymed=TYMED_ISTREAM;
            pSTM->pstm=pSTM->pstm;
            }
        else
            return ResultFromScode(DATA_E_FORMATETC);
       }

    hr=OleSaveToStream(pmk, pSTM->pstm);

    if (FAILED(hr))
        return hr;

    return WriteClassStm(pSTM->pstm, *pClsID);
    }

/*
 * INOLE_RegisterAsRunning
 *
 * Purpose:
 *  Registers a moniker as running in the running object
 *  table, revoking any existing registration.
 *
 * Parameters:
 *  pUnk            IUnknown * of the object.
 *  pmk             IMoniker * naming the object.
 *  dwFlags         DWORD flags to pass to IROT::Register
 *  pdwReg          LPDWORD in which to store the registration key.
 *
 * Return Value:
 *  None
 */

STDAPI_(void) INOLE_RegisterAsRunning(IUnknown *pUnk
    , IMoniker *pmk, DWORD dwFlags, LPDWORD pdwReg)
    {
    IRunningObjectTable    *pROT;
    HRESULT                 hr;
    DWORD                   dwReg=*pdwReg;

    if (NULL==pmk || NULL==pUnk || NULL==pdwReg)
        return;

    dwReg=*pdwReg;

    if (FAILED(GetRunningObjectTable(0, &pROT)))
        return;

    hr=pROT->Register(dwFlags, pUnk, pmk, pdwReg);

    if (MK_S_MONIKERALREADYREGISTERED==GetScode(hr))
        {
        if (0!=dwReg)
            pROT->Revoke(dwReg);
        }

    pROT->Release();
    return;
    }

/*
 * INOLE_RevokeAsRunning
 *
 * Purpose:
 *  Wrapper for IRunningObjectTable::Revoke
 *
 * Parameters:
 *  pdwReg          LPDWORD containing the key on input, zeroed
 *                  on output.
 *
 * Return Value:
 *  None
 */

STDAPI_(void) INOLE_RevokeAsRunning(LPDWORD pdwReg)
    {
    IRunningObjectTable    *pROT;

    if (0==*pdwReg)
        return;

    if (FAILED(GetRunningObjectTable(0, &pROT)))
        return;

    pROT->Revoke(*pdwReg);
    pROT->Release();

    *pdwReg=0;
    return;
    }

/*
 * INOLE_NoteChangeTime
 *
 * Purpose:
 *  Wrapper for IRunningObjectTable::NoteChangeTime
 *
 * Parameters:
 *  dwReg           DWORD identifying the running object to note.
 *  pft             FILETIME * containing the new time.  If NULL,
 *                  then we'll get it from CoFileTimeNow.
 *  pszFile         LPTSTR to a filename.  If pft is NULL, we'll
 *                  retrieve times from this file.
 *
 * Return Value:
 *  None
 */

STDAPI_(void) INOLE_NoteChangeTime(DWORD dwReg, FILETIME *pft
    , LPTSTR pszFile)
    {
    IRunningObjectTable    *pROT;
    FILETIME                ft;

    if (NULL==pft)
        {
        CoFileTimeNow(&ft);
        pft=&ft;
        }

    if (NULL!=pszFile)
        GetFileTimes(pszFile, pft);

    if (FAILED(GetRunningObjectTable(0, &pROT)))
        return;

    pROT->NoteChangeTime(dwReg, pft);
    pROT->Release();
    return;
    }

/*
 * GetFileTimes
 * (Internal)
 *
 * Purpose:
 *  Retrieve the FILETIME structure for a given file.
 */

static BOOL GetFileTimes(LPTSTR pszFileName, FILETIME *pft)
    {
   #ifdef WIN32
    WIN32_FIND_DATA fd;
    HANDLE          hFind;

    hFind=FindFirstFile(pszFileName, &fd);

    if (NULL==hFind || INVALID_HANDLE_VALUE==hFind)
        return FALSE;

    FindClose(hFind);
    *pft=fd.ftLastWriteTime;
    return TRUE;
   #else
    static char           sz[256];
    static struct _find_t fileinfo;

    lstrcpyn((LPSTR)sz, pszFileName, sizeof(sz)-1);
    sz[sizeof(sz)-1]= '\0';
    AnsiToOem(sz, sz);

    return (_dos_findfirst(sz,_A_NORMAL|_A_HIDDEN|_A_SUBDIR|_A_SYSTEM
        , (struct _find_t *)&fileinfo) == 0
        && CoDosDateTimeToFileTime(fileinfo.wr_date, fileinfo.wr_time
        , pft));
   #endif
    }


INOLE.CPP

/*
 * INOLE.CPP
 * Inside OLE Utilities DLL
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "inoledll.h"

/*
 * LibMain(32)
 *
 * Purpose:
 *  Entry point for the DLL, conditionally compiled.
 */

#ifdef WIN32

BOOL __stdcall LibMain32(HINSTANCE hInstance, ULONG ulReason
    , PCONTEXT pContext)
    {
    UNREFERENCED_PARAMETER(pContext);

    if (DLL_PROCESS_DETACH==ulReason)
        {
        return TRUE;
        }
    else
        {
        if (DLL_PROCESS_ATTACH!=ulReason)
            return TRUE;
        }

    return Initialize(hInstance);
    }

#else

int WINAPI LibMain(HANDLE hInstance, WORD wDataSeg
    , WORD cbHeapSize, LPTSTR lpCmdLine)
    {
     //Perform global initialization.
    if (Initialize((HINSTANCE)hInstance))
        {
        if (0!=cbHeapSize)
            UnlockData(0);
        }

    return (int)hInstance;
    }

#endif

/*
 * Initialize
 *
 * Purpose:
 *  Registers the control classes contained in this library
 *  and performs other initialization.
 *
 * Parameters:
 *  hInst           HINSTANCE of the DLL that will own this class.
 *
 * Return Value:
 *  BOOL            TRUE all if successful, FALSE otherwise.
 */

BOOL Initialize(HINSTANCE hInst)
    {
    static BOOL     fInitialized=FALSE;
    WNDCLASS        wc;

    if (fInitialized)
        return TRUE;

    wc.cbClsExtra    =0;
    wc.hInstance     =hInst;
    wc.hIcon         =NULL;
    wc.hCursor       =LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground =(HBRUSH)(COLOR_BTNFACE+1);
    wc.lpszMenuName  =NULL;
    wc.style         =CS_DBLCLKS | CS_GLOBALCLASS
                      | CS_VREDRAW | CS_HREDRAW;

    wc.lpfnWndProc   =GizmoBarWndProc;
    wc.cbWndExtra    =CBEXTRAGIZMOBAR;
    wc.lpszClassName =CLASS_GIZMOBAR;

    RegisterClass(&wc);

    wc.lpfnWndProc   =StatStripWndProc;
    wc.cbWndExtra    =CBEXTRASTATSTRIP;
    wc.lpszClassName =CLASS_STATSTRIP;

    RegisterClass(&wc);

    ToolButtonInit(hInst);
    CursorsCache(hInst);

    fInitialized=TRUE;
    return TRUE;
    }


INOLEDLL.H

/*
 * INOLEDLL.H
 * Inside OLE Utilities DLL
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _INOLEDLL_H_
#define _INOLEDLL_H_

#include <inole.h>
#include <bttncur.h>
#include <gizmobar.h>
#include <stastrip.h>

#ifndef WIN32
#include <shellapi.h>
#ifndef RC_INVOKED
#include <dos.h>
#endif
#endif

//Private definitions
#include "bttncuri.h"
#include "gizmoint.h"
#include "statint.h"

BOOL Initialize(HINSTANCE);
void DrawHandle(HDC, int, int, UINT, BOOL, BOOL);
BOOL GetFileTimes(LPTSTR, FILETIME *);

HRESULT ConvertStringAlloc(ULONG, void **);

#endif //_INOLEDLL_H_


STASTRIP.C

/*
 * STASTRIP.C
 * StatStrip Control
 *
 * Window procedure and other functions that are frequently used in
 * the life of a StatStrip.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "inoledll.h"

/*
 * StatStripWndProc
 *
 * Purpose:
 *  Window procedure for the StatStrip control.
 */

LRESULT APIENTRY StatStripWndProc(HWND hWnd, UINT iMsg
    , WPARAM wParam, LPARAM lParam)
    {
    PSTATSTRIP      pST;
    int             cyFont;
    HDC             hDC;
    LRESULT         lRet;

    pST=(PSTATSTRIP)GetWindowLong(hWnd, STATWL_STRUCTURE);

    switch (iMsg)
        {
        case WM_NCCREATE:
            pST=(PSTATSTRIP)(void *)LocalAlloc(LPTR, CBSTATSTRIP);

            if (NULL==pST)
                return -1L;

            //Calc size of 0 point font, which we'll use as default.
            hDC=GetDC(NULL);
            cyFont=-MulDiv(10, GetDeviceCaps(hDC, LOGPIXELSY), 72);
            ReleaseDC(NULL, hDC);

            pST->hFont=CreateFont(cyFont, 0, 0, 0, FW_NORMAL, FALSE
                , FALSE, FALSE, ANSI_CHARSET, OUT_DEFAULT_PRECIS
                , CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY
                , VARIABLE_PITCH | FF_SWISS, TEXT("MS Sans Serif"));

            /*
             * If we could create the font, remember that we own it.
             * If font creation failed, then we'll just do with the
             * system font.
             */
            pST->fMyFont=(NULL!=pST->hFont);

            SetWindowLong(hWnd, STATWL_STRUCTURE, (LONG)pST);
            return DefWindowProc(hWnd, iMsg, wParam, lParam);

        case WM_DESTROY:
            StatStripClean(pST, TRUE);
            LocalFree((HLOCAL)(UINT)(LONG)pST);
            break;

        case WM_ERASEBKGND:
            /*
             * Eat this message to avoid erasing portions that we are
             * going to repaint in WM_PAINT.  Part of a change-state-
             * and-repaint strategy is to rely on WM_PAINT to do
             * anything visual, which includes erasing invalid
             * portions.  Letting WM_ERASEBKGND erase the background
             * is redundant.
             */
            return TRUE;

        case WM_PAINT:
            StatStripPaint(hWnd, pST);
            break;

        case WM_SETFONT:
            if (!IsWindowEnabled(hWnd))
                break;

            /*
             * wParam has the new font.  Always repaint immediately.
             * First, delete the old font only if we own it.
             */
            if (NULL!=pST->hFont && pST->fMyFont)
                DeleteObject(pST->hFont);

            //Save the new font but mark that we don't own it.
            pST->hFont=(HFONT)wParam;
            pST->fMyFont=FALSE;

            InvalidateRect(hWnd, NULL, FALSE);
            UpdateWindow(hWnd);
            break;

        case WM_GETFONT:
            return (LRESULT)(UINT)pST->hFont;

        case WM_SETTEXT:
            if (!IsWindowEnabled(hWnd))
                break;

            //This saves the text for us, so we only have to repaint.
            lRet=DefWindowProc(hWnd, iMsg, wParam, lParam);

            InvalidateRect(hWnd, NULL, FALSE);
            UpdateWindow(hWnd);
            break;

        case WM_ENABLE:
            //Repaint on enabling or disabling either way.
            InvalidateRect(hWnd, NULL, FALSE);
            UpdateWindow(hWnd);
            break;

        //Control-specific messages
        case STATM_MESSAGEMAP:
            //lParam is an LPSTATMESSAGEMAPINIT
            if (0L!=lParam)
                {
                LPSTATMESSAGEMAPINIT  pMI=(LPSTATMESSAGEMAPINIT)lParam;

                return (LRESULT)StatStripMessageMap(hWnd
                    , pMI->hWndOwner, pMI->hInst, pMI->uIDRMap
                    , pMI->idsMin, pMI->idsMax, pMI->cchMax
                    , pMI->uIDPopupMin, pMI->uIDPopupMax
                    , pMI->uIDStatic, pMI->uIDBlank
                    , pMI->uIDSysMenu);
                }
            break;

        case STATM_MENUSELECT:
            //wParam and lParam from caller's WM_MENUSELECT message
            StatStripMenuSelect(hWnd, wParam, lParam);
            break;

        case STATM_MESSAGEDISPLAY:
            //wParam is ID to display
            StatStripMessageDisplay(hWnd, (USHORT)wParam);
            break;

        default:
            return DefWindowProc(hWnd, iMsg, wParam, lParam);
        }

    return 0L;
    }

/*
 * StatStripPaint
 * Internal
 *
 * Purpose:
 *  Provides drawing the StatStrip window with the 3-D effect and the
 *  current message and the current font.
 *
 * Parameters:
 *  hWnd            HWND of the window;
 *  pST             PSTATSTRIP containing control information.
 *
 * Return Value:
 *  None
 */

void StatStripPaint(HWND hWnd, PSTATSTRIP pST)
    {
    int             y;
    HDC             hDC;
    RECT            rc;
    UINT            cch;
    TCHAR           szMsg[512];
    HPEN            hPenFrame, hPenHigh;
    HFONT           hFontT;
    HBRUSH          hBr;
    COLORREF        crHighlight;
    TEXTMETRIC      tm;
    PAINTSTRUCT     ps;

    hDC=BeginPaint(hWnd, &ps);
    GetClientRect(hWnd, &rc);

    //Draw the top line using the frame color
    hPenFrame=CreatePen(PS_SOLID, 1, GetSysColor(COLOR_WINDOWFRAME));

    if (NULL!=hPenFrame)
        {
        SelectObject(hDC, hPenFrame);
        MoveToEx(hDC, rc.left, rc.top, NULL);
        LineTo(hDC, rc.right, rc.top);
        }

    //Draw the two highlight lines
    crHighlight=GetSysColor(COLOR_BTNHIGHLIGHT);

    hPenHigh=CreatePen(PS_SOLID, 1, crHighlight);

    if (NULL!=hPenHigh)
        {
        SelectObject(hDC, hPenHigh);
        MoveToEx(hDC, rc.left, rc.bottom, NULL);
        LineTo(hDC, rc.left, rc.top+1);
        LineTo(hDC, rc.right, rc.top+1);
        }

    //Draw the face color avoiding the frame and highlight
    rc.top +=2;
    rc.left+=1;

    hBr=CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
    FillRect(hDC, &rc, hBr);
    DeleteObject(hBr);

    //Now write the text, color sensitive to the enabled state
    SetBkMode(hDC, TRANSPARENT);
    SetTextColor(hDC, GetSysColor(IsWindowEnabled(hWnd)
        ? COLOR_BTNTEXT : COLOR_BTNSHADOW));

    //Center the font vertically, accounting for borders on the rect.
    hFontT=SelectObject(hDC, pST->hFont);

    GetTextMetrics(hDC, &tm);
    y=rc.top+(((rc.bottom-rc.top)-tm.tmHeight) >> 1);
    y=max(y, rc.top);

    cch=GetWindowText(hWnd, szMsg, sizeof(szMsg));
    TextOut(hDC, 4, y, szMsg, cch);
    SelectObject(hDC, hFontT);

    //Clean up
    EndPaint(hWnd, &ps);

    DeleteObject(hPenHigh);
    DeleteObject(hPenFrame);
    return;
    }

/*
 * StatStripMenuSelect
 * External
 *
 * Purpose:
 *  Displays the appropriate message for whatever is in the
 *  parameters of a WM_MENUSELECT message.  This can only be called
 *  if StatStripMessageMap has already been called and must be used
 *  with the same menu the owner window had at the time of that call.
 *
 *  Since we're unpacking an application's messages for it, the app
 *  and this DLL must match 16-16 and 32-32 bits.
 *
 * Parameters:
 *  hWnd            HWND of the StatStrip
 *  wParam          WPARAM of the WM_MENUSELECT message.
 *  lParam          LPARAM of the WM_MENUSELECT message.
 *
 * Return Value:
 *  None
 */

void WINAPI StatStripMenuSelect(HWND hWnd, WPARAM wParam
    , LPARAM lParam)
    {
    PSTATSTRIP      pST;
    USHORT          uID;

    MENUSELECTPARAMS(wItem, wMenuFlags, hMenu);

    if (!IsWindow(hWnd) || !IsWindowEnabled(hWnd))
        return;

    pST=(PSTATSTRIP)GetWindowLong(hWnd, STATWL_STRUCTURE);

    //Case 1:  Menu was cancelled, display static string
    if (0==wItem && 0xFFFF==wMenuFlags)
        uID=pST->uIDStatic;
    else
        //Case 2:  System menu selected by itself.
        if (MF_POPUP & wMenuFlags && MF_SYSMENU & wMenuFlags)
            uID=pST->uIDSysMenu;
        else
            /*
             * Case 3:  A popup menu was chosen:  Find the ID for
             * hMenu (in wItem)
             */
            if (MF_POPUP & wMenuFlags)
                uID=IDFromHMenu(pST, (HMENU)wItem);
            else
                //Case 4:  A menu item is selected
                if (0!=wItem)
                    uID=(UINT)wItem;
                else
                    //Case 5:  Nothing is selected (like a separator)
                    uID=pST->uIDBlank;

    /*
     * Go display the message.  Since all this function does extra
     * that we may not have to do here is a GetWindowLong, which
     * is quick, so by doing this we keep the code in one place and
     * only have to test this API to test the other.
     */
    StatStripMessageDisplay(hWnd, uID);
    return;
    }

/*
 * StatStripMessageDisplay
 * External
 *
 * Purpose:
 *  Displays the appropriate message for a given ID value.   This can
 *  only be called if StatStripMessageMap has already been called.
 *
 * Parameters:
 *  hWnd            HWND of the StatStrip
 *  uID             USHORT of the message to display.
 *
 * Return Value:
 *  None
 */

void WINAPI StatStripMessageDisplay(HWND hWnd, USHORT uID)
    {
    PSTATSTRIP      pST;
    UINT            idsMsg;

    if (!IsWindow(hWnd) || !IsWindowEnabled(hWnd))
        return;

    pST=(PSTATSTRIP)GetWindowLong(hWnd, STATWL_STRUCTURE);

    //Go look up the string ID to display.
    idsMsg=IStringFromID(pST->pSMM, pST->cMessages, uID);
    SetWindowText(hWnd, pST->ppsz[idsMsg-pST->idsMin]);
    return;
    }

/*
 * IDFromHMenu
 *
 * Purpose:
 *  Given a specific menu handle, searches through pST->pPMM for a
 *  match and returns the ID associated with that menu.
 *
 * Parameters:
 *  pST             PSTATSTRIP of the control
 *  hMenu           HMENU to search for
 *
 * Return Value:
 *  USHORT          ID associated with the menu handle.
 */

USHORT IDFromHMenu(PSTATSTRIP pST, HMENU hMenu)
    {
    USHORT      uID=pST->uIDBlank;      //Default is empty
    UINT        i;

   #ifdef WIN32
    /*
     * Under Win32 the hMenu passed here from the WM_MENUSELECT
     * message will only be the index to the actual menu.  We
     * have to use GetSubMenu from the top-level menu using this
     * index to actually get the menu handle.
     */

    hMenu=GetSubMenu(GetMenu(pST->hWndOwner), (UINT)hMenu);
   #endif

    for (i=0; i < pST->cPopups; i++)
        {
        if (pST->pPMM[i].hMenu==hMenu)
            {
            uID=pST->pPMM[i].uID;
            break;
            }
        }

    return uID;
    }

/*
 * IStringFromID
 *
 * Purpose:
 *  Performs a binary search in a STATMESSAGEMAP array looking for
 *  a specific item ID returning the string ID for that item.
 *
 * Parameters:
 *  pSMM            PSTATMESSAGEMAP to search
 *  cItems          USHORT size of the map in elements
 *  uID             USHORT item ID to locate.
 *
 * Return Value:
 *  UINT            String ID associated with wItem.
 */

UINT IStringFromID(PSTATMESSAGEMAP pSMM, USHORT cItems, USHORT uID)
    {
    UINT        iLow =0;
    UINT        iHigh=cItems-1;
    UINT        iMid;

    while (TRUE)
        {
        iMid=(iLow+iHigh) >> 1;

        if (uID < pSMM[iMid].uID)
            iHigh=iMid-1;
        else
            {
            if (uID > pSMM[iMid].uID)
                iLow=iMid+1;
            else
                break;    //Equality
            }

        if (iHigh < iLow)
            break;
        }

    return pSMM[iMid].idsMsg;
    }

/*
 * StatStripMessageMap
 *
 * Purpose:
 *  Initializes the message mappings in the StatStrip for subsequent
 *  use with StatMessageMenuSelect.  If this function is called more
 *  than once then any previous initialization is cleaned up so
 *  previous message IDs will then be invalid.
 *
 *  The total number of messages is inferred by (idsMax-idsMin+1).
 *  The maximum number of popup menu items we'll hold is inferred by
 *  (uIDPopupMax-uIDPopupMin+1).
 *
 * Parameters:
 *  hWnd            HWND of the StatStrip control.
 *  hWndOwner       HWND of the window owning menus.
 *  hInst           HINSTANCE of the app from which to load resources
 *  uIDRMap         UINT identifying a resource mapping ID values
 *                  to string ID values.
 *  idsMin          UINT specifying the lowest string ID to load.
 *  idsMax          UINT specifying the hightest string ID to load.
 *  cchMax          UINT maximum string length.
 *  uIDPopupMin     USHORT lowest ID to assign to popup menus.
 *  uIDPopupMax     USHORT highest ID to assign to popup menus.
 *  uIDStatic       USHORT ID for the quiescent message.
 *  uIDBlank        USHORT ID for a blank message.
 *  uIDSysMenu      USHORT ID for the system menu.
 *
 * Return Value:
 *  BOOL            TRUE if the function was successful, FALSE
 *                  otherwise.
 */

BOOL WINAPI StatStripMessageMap(HWND hWnd, HWND hWndOwner
    , HINSTANCE hInst , UINT uIDRMap, UINT idsMin, UINT idsMax
    , UINT cchMax, USHORT uIDPopupMin, USHORT uIDPopupMax
    , USHORT uIDStatic, USHORT uIDBlank, USHORT uIDSysMenu)
    {
    PSTATSTRIP      pST;
    HMENU           hMenu;
    HRSRC           hRes;
    UINT            i;
    USHORT          uID;

   #ifdef WIN32
    DWORD           cbRes;
    DWORD           dwPrevProt;
   #endif

    if (!IsWindow(hWnd))
        return FALSE;

    pST=(PSTATSTRIP)GetWindowLong(hWnd, STATWL_STRUCTURE);

    if (NULL==pST)
        return FALSE;

    //Parameter validation
    if (NULL==hInst || idsMax < idsMin || uIDPopupMax < uIDPopupMin)
        return FALSE;

    //Clean ourselves out if we've already initialized.
    if (pST->fMapped)
        StatStripClean(pST, FALSE);

    pST->hWndOwner  =hWndOwner;

    pST->idsMin     =idsMin;
    pST->idsMax     =idsMax;
    pST->cMessages  =(USHORT)(idsMax-idsMin+1);

    pST->uIDPopupMin=uIDPopupMin;
    pST->uIDPopupMax=uIDPopupMax;
    pST->cPopups    =(USHORT)(uIDPopupMax-uIDPopupMin+1);

    pST->uIDStatic  =uIDStatic;
    pST->uIDBlank   =uIDBlank;
    pST->uIDSysMenu =uIDSysMenu;

    //Load the STATMESSAGEMAP array from our resources.
    hRes=FindResource(hInst, MAKEINTRESOURCE(uIDRMap), RT_RCDATA);

    if (NULL==hRes)
        return FALSE;

    pST->hMemSMM=LoadResource(hInst, hRes);

    if (NULL==pST->hMemSMM)
        return FALSE;

    pST->pSMM=(PSTATMESSAGEMAP)LockResource(pST->hMemSMM);

    if (NULL==pST->pSMM)
        {
        StatStripClean(pST, FALSE);
        return FALSE;
        }

   #ifdef WIN32
    /*
     * In Win32 resource pages are read-only when loaded.
     * Change to read-write for the purposes of sorting
     * initially.
     */
    cbRes=SizeofResource(hInst, hRes);
    VirtualProtect(pST->pSMM, cbRes, PAGE_READWRITE, &dwPrevProt);
   #endif

    //Sort these for binary search lookup.
    StatMessageMapSort(pST->pSMM, pST->cMessages);

   #ifdef WIN32
    VirtualProtect(pST->pSMM, cbRes, dwPrevProt, &dwPrevProt);
   #endif

    //Allocate space for string pointers
    pST->ppsz=(LPTSTR *)LocalAlloc(LPTR
        , sizeof(LPTSTR)*pST->cMessages);

    if (NULL==pST->ppsz)
        {
        StatStripClean(pST, FALSE);
        return FALSE;
        }

    //Load the stringtable for messages.
    pST->hMemSzStat=HStringCache(hInst, idsMin, idsMax, cchMax
        , pST->ppsz);

    if (NULL==pST->hMemSzStat)
        {
        StatStripClean(pST, FALSE);
        return FALSE;
        }

    //Allocate an array of POPUPMENUMAP structures
    pST->pPMM=(PPOPUPMENUMAP)(void *)LocalAlloc(LPTR
        , sizeof(POPUPMENUMAP)*pST->cPopups);

    if (NULL==pST->pPMM)
        {
        StatStripClean(pST, FALSE);
        return FALSE;
        }

    //Initialize the array mapping popup menus to specific IDs.
    uID=uIDPopupMin;
    hMenu=GetMenu(hWndOwner);

    for (i=0; i < pST->cPopups; i++)
        {
        pST->pPMM[i].hMenu=GetSubMenu(hMenu, i);
        pST->pPMM[i].uID  =uID++;
        }

    pST->fMapped=TRUE;
    return TRUE;
    }

/*
 * StatStripClean
 *
 * Purpose:
 *  Cleans out any allocations in a STATSTRIP.
 *
 * Parameters:
 *  pST             PSTATSTRIP to clean
 *  fIncludeFont    BOOL indicates if we're to also clean the font.
 *                  This is FALSE from StatMessageMap, TRUE from
 *                  WM_DESTROY.
 *
 * Return Value:
 *  None
 */

void StatStripClean(PSTATSTRIP pST, BOOL fIncludeFont)
    {
    //Free up anything from StatMessageMap
    if (NULL!=pST->pPMM)
        {
        LocalFree((HLOCAL)(UINT)(LONG)pST->pPMM);
        pST->pPMM=NULL;
        }

    if (NULL!=pST->ppsz)
        {
        LocalFree((HLOCAL)(UINT)(LONG)pST->ppsz);
        pST->ppsz=NULL;
        }

    if (NULL!=pST->hMemSzStat)
        {
        HStringCacheFree(pST->hMemSzStat);
        pST->hMemSzStat=NULL;
        }

    if (NULL!=pST->pSMM)
        {
       #ifndef WIN32
        UnlockResource(pST->hMemSMM);
       #endif
        pST->pSMM=NULL;
        }

    if (NULL!=pST->hMemSMM)
        {
        FreeResource(pST->hMemSMM);
        pST->hMemSMM=NULL;
        }

    //Delete the old font only if we own it.
    if (fIncludeFont)
        {
        if (NULL!=pST->hFont && pST->fMyFont)
            {
            DeleteObject(pST->hFont);
            pST->hFont=NULL;
            }
        }

    return;
    }

/*
 * HStringCache
 *
 * Purpose:
 *  Allocates memory and reads a stringtable into that memory.
 *  Pointers to the strings in the memory are then stored at ppsz
 *  which is assumed to be (idsMax-idsMin+1) strings long.
 *
 * Parameters:
 *  hInst           HINSTANCE of the application
 *  idsMin          UINT string index to start loading
 *  idsMax          UINT maximum string index in this table.
 *  cchMax          UINT length of the longest string in TCHARs.
 *  ppsz            LPTSTR * to an array in which to store pointers.
 *
 * Return Value:
 *  HGLOBAL         Handle to the memory.  NULL if memory could
 *                  not be allocated.
 */

HGLOBAL HStringCache(HINSTANCE hInst, UINT idsMin, UINT idsMax
    , UINT cchMax, LPTSTR *ppsz)
    {
    HANDLE      hMem;
    LPTSTR      pch;
    UINT        cchUsed=0;
    UINT        cch;
    UINT        i, cStrings;

    cStrings=idsMax-idsMin+1;
    hMem=GlobalAlloc(GHND, cStrings * cchMax * sizeof(TCHAR));

    if (NULL!=hMem)
        {
        pch=GlobalLock(hMem);

        /*
         * Load the strings into the memory and retain the specific
         * pointer to that string.
         */
        for (i=0; i < cStrings; i++)
            {
            cch=LoadString(hInst, i+idsMin, (LPTSTR)(pch+cchUsed)
                , cchMax-1);
            ppsz[i]=(LPTSTR)(pch+cchUsed);

            /*
             * Add one char to cch to include a NULL.  The memory
             * was ZEROINITed on allocation so by skipping a TCHAR
             * we get the NULL.
             */
            cchUsed+=cch+sizeof(TCHAR);
            }

        /*
         * Memory is locked for the duration of the app.  Don't
         * bother reallocating since we might have to recalc
         * all the pointers to the strings again.
         */
        }

    return hMem;
    }

/*
 * HStringCacheFree
 *
 * Purpose:
 *  Frees up any memory associated with the string cache from
 *  HStringCache.
 *
 * Parameters:
 *  hMem            HGLOBAL to the memory containing the cahce.
 *
 * Return Value:
 *  None
 */

void HStringCacheFree(HGLOBAL hMem)
    {
    if (NULL!=hMem)
        {
        GlobalUnlock(hMem);
        GlobalFree(hMem);
        }
    return;
    }

/*
 * StatMessageMapSort
 *
 * Purpose:
 *  Performs a selection sort on the STATMESSAGEMAP array that we
 *  load from our resource.  Since we expect that the data is
 *  partially sorted (we tend to place things in resources in groups
 *  of seqential values), since the number of messages is usually
 *  < 200, and since we're in startup (which takes a long time
 *  anyway), selection sort is a better choice to implement over
 *  qsort saving much code.
 *
 * Parameters:
 *  pSMM            PSTATMESSAGEMAP to sort
 *  n               USHORT number of elements in the array.
 *
 * Return Value:
 *  None
 */

void StatMessageMapSort(PSTATMESSAGEMAP pSMM, USHORT n)
    {
    USHORT          i, j, k;
    STATMESSAGEMAP  smm;

    for (j=0; j < (UINT)(n-1); j++)
        {
        k=j;
        smm.uID   =pSMM[j].uID;
        smm.idsMsg=pSMM[j].idsMsg;

        for (i=j+1; i < (UINT)n; i++)
            {
            if (pSMM[i].uID < smm.uID)
                {
                smm.uID   =pSMM[i].uID;
                smm.idsMsg=pSMM[i].idsMsg;
                k=i;
                }
            }

        smm.uID       =pSMM[j].uID;
        smm.idsMsg    =pSMM[j].idsMsg;
        pSMM[j].uID   =pSMM[k].uID;
        pSMM[j].idsMsg=pSMM[k].idsMsg;
        pSMM[k].uID   =smm.uID;
        pSMM[k].idsMsg=smm.idsMsg;
        }

    return;
    }


STATINT.H

/*
 * STATINT.H
 *
 * Internal definitions and function prototypes for the StatStrip
 * control.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _STATINT_H_
#define _STATINT_H_

#ifdef __cplusplus
extern "C"
    {
#endif

//For loading the RCDATA mapping menu item IDs to string IDs
typedef struct tagSTATMESSAGEMAP
    {
    USHORT      uID;
    USHORT      idsMsg;
    } STATMESSAGEMAP, *PSTATMESSAGEMAP;

//Array mapping menu handles to menu item IDs
typedef struct tagPOPUPMENUMAP
    {
    HMENU       hMenu;
    USHORT      uID;
    } POPUPMENUMAP, * PPOPUPMENUMAP;

typedef struct tagSTATSTRIP
    {
    HFONT               hFont;      //Current control font
    BOOL                fMyFont;

    BOOL                fMapped;    //StatStripMessageMap called?
    HWND                hWndOwner;

    USHORT              cMessages;  //Total number of messages
    UINT                idsMin;     //Starting string ID
    UINT                idsMax;

    USHORT              uIDStatic;  //Quiescent message ID
    USHORT              uIDBlank;   //Blank message ID
    USHORT              uIDSysMenu; //System menu message ID

    HGLOBAL             hMemSMM;    //Handle to STATMESSAGEMAP data
    PSTATMESSAGEMAP     pSMM;       //Memory holding STATMESSAGEMAP

    HGLOBAL             hMemSzStat; //Memory for Stat strings
    LPTSTR *            ppsz;       //Stat string pointers

    USHORT              cPopups;
    USHORT              uIDPopupMin;
    USHORT              uIDPopupMax;
    PPOPUPMENUMAP       pPMM;
    } STATSTRIP, *PSTATSTRIP;

#define CBSTATSTRIP             sizeof(STATSTRIP)

#define CBEXTRASTATSTRIP        sizeof(PSTATSTRIP)
#define STATWL_STRUCTURE        0

//STASTRIP.C
LRESULT APIENTRY StatStripWndProc(HWND, UINT, WPARAM, LPARAM);
void             StatStripPaint(HWND, PSTATSTRIP);
USHORT           IDFromHMenu(PSTATSTRIP, HMENU);
UINT             IStringFromID(PSTATMESSAGEMAP, USHORT, USHORT);

void             StatStripClean(PSTATSTRIP, BOOL);
HGLOBAL          HStringCache(HINSTANCE, UINT, UINT, UINT, LPTSTR *);
void             HStringCacheFree(HGLOBAL);
void             StatMessageMapSort(PSTATMESSAGEMAP, USHORT);

#ifdef __cplusplus
    }
#endif

#endif //_STATINT_H_


UIEFFECT.CPP

/*
 * UIEFFECT.CPP
 *
 * Utility functions for drawing various graphical effects.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "inoledll.h"

//Hatch pattern brush bits
static WORD g_wHatchBmp[]={0x11, 0x22, 0x44, 0x88, 0x11, 0x22, 0x44, 0x88};

/*
 * UIDrawHandles
 *
 * Purpose:
 *  Draw handles or/and boundary around Container Object when selected
 *
 * Parameters:
 *  pRect           Dimensions of Container Object
 *  hDC             HDC of the site
 *  dwFlags         DWORD specifying how to draw the handles.
 *  cSize           UINT size of handle box
 *  fDraw           BOOL draw if TRUE, erase if FALSE
 *
 * Return Value:
 *  None
 */

STDAPI_(void) UIDrawHandles(LPRECT pRect, HDC hDC, DWORD dwFlags, UINT cSize
    , BOOL fDraw)
    {
HBRUSH  hBR;
RECT    rc;
int     bkmodeOld;
BOOL    fInvert=(BOOL)(dwFlags & UI_HANDLES_USEINVERSE);

CopyRect((LPRECT)&rc, pRect);

bkmodeOld=SetBkMode(hDC, TRANSPARENT);

if (dwFlags & UI_HANDLES_OUTSIDE)
InflateRect((LPRECT)&rc, cSize - 1, cSize - 1);

//Draw the handles inside the rectangle boundary
DrawHandle(hDC, rc.left, rc.top, cSize, fInvert, fDraw);
DrawHandle(hDC, rc.left, rc.top+(rc.bottom-rc.top-cSize)/2, cSize, fInvert, fDraw);
DrawHandle(hDC, rc.left, rc.bottom-cSize, cSize, fInvert, fDraw);
DrawHandle(hDC, rc.left+(rc.right-rc.left-cSize)/2, rc.top, cSize, fInvert, fDraw);
DrawHandle(hDC, rc.left+(rc.right-rc.left-cSize)/2, rc.bottom-cSize, cSize, fInvert, fDraw);
DrawHandle(hDC, rc.right-cSize, rc.top, cSize, fInvert, fDraw);
DrawHandle(hDC, rc.right-cSize, rc.top+(rc.bottom-rc.top-cSize)/2, cSize, fInvert, fDraw);
DrawHandle(hDC, rc.right-cSize, rc.bottom-cSize, cSize, fInvert, fDraw);

if (!(dwFlags & UI_HANDLES_NOBORDER))
        {
if (fDraw)
hBR=(HBRUSH)GetStockObject(BLACK_BRUSH);
else
hBR=(HBRUSH)GetStockObject(WHITE_BRUSH);

FrameRect(hDC, pRect, hBR);
    }

SetBkMode(hDC, bkmodeOld);
    }

/*
 * DrawHandle
 *
 * Purpose:
 *  Draw a handle box at the specified coordinate
 *
 * Parameters:
 *  hDC             HDC to be drawn into
 *  x, y            int upper left corner coordinate of the handle box
 *  cSize           UINT size of handle box
 *  fInvert         BOOL indicating to invert the rect or fill it
 *  fDraw           BOOL draw if TRUE, erase if FALSE,
 *                  ignored if fInvert is TRUE
 *
 * Return Value:
 *  None
 */

void DrawHandle(HDC hDC, int x, int y, UINT cSize, BOOL fInvert
    , BOOL fDraw)
    {
HBRUSH  hBR;
HBRUSH  hBROld;
HPEN    hPen;
HPEN    hPenOld;
RECT    rc;

if (!fInvert)
        {
if (fDraw)
            {
hPen=(HPEN)GetStockObject(BLACK_PEN);
hBR=(HBRUSH)GetStockObject(BLACK_BRUSH);
    }
        else
            {
hPen=(HPEN)GetStockObject(WHITE_PEN);
hBR=(HBRUSH)GetStockObject(WHITE_PEN);
    }

hPenOld=(HPEN)SelectObject(hDC, hPen);
hBROld=(HBRUSH)SelectObject(hDC, hBR);
Rectangle(hDC, x, y, x+cSize, y+cSize);
SelectObject(hDC, hPenOld);
SelectObject(hDC, hBROld);
    }
else
        {
rc.left=x;
rc.top=y;
rc.right=x + cSize;
rc.bottom=y + cSize;
InvertRect(hDC, (LPRECT)&rc);
    }

    return;
    }

/*
 * UIDrawShading
 *
 * Purpose:
 *  Draw a hatching across a rectangle, inside a rectangle, or
 *  outside a rectangle.
 *
 * Parameters:
 *  prc             LPRECT containing the rectangle.
 *  hDC             HDC on which to draw.
 *  dwFlags         DWORD specifying how to draw the shading.
 *  cWidth          UINT width of the border to draw.  Ignored
 *                  if dwFlags has UI_SHADE_FULLRECT.
 *
 * Return Value:
 *  None
 */

STDAPI_(void) UIDrawShading(LPRECT prc, HDC hDC, DWORD dwFlags, UINT cWidth)
    {
    HBRUSH      hBR;
    HBRUSH      hBROld;
    HBITMAP     hBM;
    RECT        rc;
    UINT        cx, cy;
    COLORREF    crText;
    COLORREF    crBk;
    const DWORD dwROP=0x00A000C9L;  //DPa

    if (NULL==prc || NULL==hDC)
return;

    hBM=CreateBitmap(8, 8, 1, 1, g_wHatchBmp);
    hBR=CreatePatternBrush(hBM);
    hBROld=(HBRUSH)SelectObject(hDC, hBR);

    rc=*prc;
    cx=rc.right-rc.left;
    cy=rc.bottom-rc.top;

    if (UI_SHADE_FULLRECT==dwFlags)
        {
        crText=SetTextColor(hDC, RGB(255, 255, 255));
        crBk=SetBkColor(hDC, RGB(0, 0, 0));
        PatBlt(hDC, rc.left, rc.top, cx, cy, dwROP);
        }
    else
        {
        if (UI_SHADE_BORDEROUT==dwFlags)
            InflateRect((LPRECT)&rc, cWidth-1, cWidth-1);

        crText=SetTextColor(hDC, RGB(255, 255, 255));
        crBk=SetBkColor(hDC, RGB(0, 0, 0));
        PatBlt(hDC, rc.left, rc.top, cx, cWidth, dwROP);
        PatBlt(hDC, rc.left, rc.top, cWidth, cy, dwROP);
        PatBlt(hDC, rc.right-cWidth, rc.top, cWidth, cy, dwROP);
        PatBlt(hDC, rc.left, rc.bottom-cWidth, cx, cWidth, dwROP);
        }

    SetTextColor(hDC, crText);
    SetBkColor(hDC, crBk);
    SelectObject(hDC, hBROld);
    DeleteObject(hBR);
    DeleteObject(hBM);

    return;
    }

/*
 * UIShowObject
 *
 * Purpose:
 *  Draw the ShowObject effect around an embedded or linked
 *  object.
 *
 * Parameters:
 *  prc             LPRECT to draw around.
 *  hDC             HDC on which to draw.
 *  fLink           BOOL specifying a linked (TRUE) or
 *                  embedded (FALSE) object
 *
 * Return Value:
 *  None
 */

STDAPI_(void) UIShowObject(LPCRECT prc, HDC hDC, BOOL fLink)
    {
HPEN    hPen;
HPEN    hPenOld;
HBRUSH  hBROld;

if (NULL==prc || NULL==hDC)
return;

hPen=fLink ? CreatePen(PS_DASH, 1, RGB(0,0,0))
        : (HPEN)GetStockObject(BLACK_PEN);

if (!hPen)
return;

hPenOld=(HPEN)SelectObject(hDC, hPen);
hBROld=(HBRUSH)SelectObject(hDC, GetStockObject(NULL_BRUSH));

Rectangle(hDC, prc->left, prc->top, prc->right, prc->bottom);

SelectObject(hDC, hPenOld);
SelectObject(hDC, hBROld);

if (fLink)
DeleteObject(hPen);

    return;
    }


XFORM.CPP

/*
 * XFORM.CPP
 *
 * Utility functions for coordinate conversion taken from the
 * original OLE UI Library.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "inoledll.h"

#define HIMETRIC_PER_INCH   2540    //Number HIMETRIC units per inch
#define PTS_PER_INCH        72      //Number points (font size) per inch

#define MAP_PIX_TO_LOGHIM(x,ppli)   MulDiv(HIMETRIC_PER_INCH, (x), (ppli))
#define MAP_LOGHIM_TO_PIX(x,ppli)   MulDiv((ppli), (x), HIMETRIC_PER_INCH)

/*
 * XformWidthInPixelsToHimetric
 * XformWidthInHimetricToPixels
 * XformHeightInPixelsToHimetric
 * XformHeightInHimetricToPixels
 *
 * Functions to convert an int between a device coordinate system and
 * logical HiMetric units.
 *
 * Parameters:
 *  hDC             HDC providing reference to the pixel mapping.  If
 *                  NULL, a screen DC is used.
 *  (others)        Values to convert
 *
 * NOTE:
 *  When displaying on the screen, Window apps display everything enlarged
 *  from its actual size so that it is easier to read. For example, if an
 *  app wants to display a 1in. horizontal line, that when printed is
 *  actually a 1in. line on the printed page, then it will display the line
 *  on the screen physically larger than 1in. This is described as a line
 *  that is "logically" 1in. along the display width. Windows maintains as
 *  part of the device-specific information about a given display device:
 *      LOGPIXELSX -- no. of pixels per logical in along the display width
 *      LOGPIXELSY -- no. of pixels per logical in along the display height
 *
 *  The following formula converts a distance in pixels into its equivalent
 *  logical HIMETRIC units:
 *
 *      DistInHiMetric = (HIMETRIC_PER_INCH * DistInPix)
 *                       -------------------------------
 *                           PIXELS_PER_LOGICAL_IN
 *
 */

STDAPI_(int) XformWidthInPixelsToHimetric(HDC hDC, int iWidthInPix)
{
int     iXppli;     //Pixels per logical inch along width
int     iWidthInHiMetric;
BOOL    fSystemDC=FALSE;

if (NULL==hDC)
{
hDC=GetDC(NULL);
fSystemDC=TRUE;
}

iXppli=GetDeviceCaps (hDC, LOGPIXELSX);

//We got pixel units, convert them to logical HIMETRIC along the display
iWidthInHiMetric=MAP_PIX_TO_LOGHIM(iWidthInPix, iXppli);

if (fSystemDC)
ReleaseDC(NULL, hDC);

return iWidthInHiMetric;
}

STDAPI_(int) XformWidthInHimetricToPixels(HDC hDC, int iWidthInHiMetric)
{
int     iXppli;     //Pixels per logical inch along width
int     iWidthInPix;
BOOL    fSystemDC=FALSE;

if (NULL==hDC)
{
hDC=GetDC(NULL);
fSystemDC=TRUE;
}

iXppli=GetDeviceCaps (hDC, LOGPIXELSX);

//We got logical HIMETRIC along the display, convert them to pixel units
iWidthInPix=MAP_LOGHIM_TO_PIX(iWidthInHiMetric, iXppli);

if (fSystemDC)
ReleaseDC(NULL, hDC);

return iWidthInPix;
}

STDAPI_(int) XformHeightInPixelsToHimetric(HDC hDC, int iHeightInPix)
{
int     iYppli;     //Pixels per logical inch along height
int     iHeightInHiMetric;
BOOL    fSystemDC=FALSE;

if (NULL==hDC)
{
hDC=GetDC(NULL);
fSystemDC=TRUE;
}

iYppli=GetDeviceCaps (hDC, LOGPIXELSY);

//We got pixel units, convert them to logical HIMETRIC along the display
iHeightInHiMetric=MAP_PIX_TO_LOGHIM(iHeightInPix, iYppli);

if (fSystemDC)
ReleaseDC(NULL, hDC);

return iHeightInHiMetric;
}

STDAPI_(int) XformHeightInHimetricToPixels(HDC hDC, int iHeightInHiMetric)
{
int     iYppli;     //Pixels per logical inch along height
int     iHeightInPix;
BOOL    fSystemDC=FALSE;

if (NULL==hDC)
{
hDC=GetDC(NULL);
fSystemDC=TRUE;
}

iYppli=GetDeviceCaps (hDC, LOGPIXELSY);

//We got logical HIMETRIC along the display, convert them to pixel units
iHeightInPix=MAP_LOGHIM_TO_PIX(iHeightInHiMetric, iYppli);

if (fSystemDC)
ReleaseDC(NULL, hDC);

return iHeightInPix;
}

/*
 * XformRectInPixelsToHimetric
 * XformRectInHimetricToPixels
 *
 * Purpose:
 *  Convert a rectangle between pixels of a given hDC and HIMETRIC units
 *  as manipulated in OLE.  If the hDC is NULL, then a screen DC is used
 *  and assumes the MM_TEXT mapping mode.
 *
 * Parameters:
 *  hDC             HDC providing reference to the pixel mapping.  If
 *                  NULL, a screen DC is used.
 *  prcPix          LPRECT containng the rectangles to convert.
 *  prcHiMetric
 *
 * Return Value:
 *  None
 *
 * NOTE:
 *  When displaying on the screen, Window apps display everything enlarged
 *  from its actual size so that it is easier to read. For example, if an
 *  app wants to display a 1in. horizontal line, that when printed is
 *  actually a 1in. line on the printed page, then it will display the line
 *  on the screen physically larger than 1in. This is described as a line
 *  that is "logically" 1in. along the display width. Windows maintains as
 *  part of the device-specific information about a given display device:
 *      LOGPIXELSX -- no. of pixels per logical in along the display width
 *      LOGPIXELSY -- no. of pixels per logical in along the display height
 *
 *  The following formula converts a distance in pixels into its equivalent
 *  logical HIMETRIC units:
 *
 *      DistInHiMetric=(HIMETRIC_PER_INCH * DistInPix)
 *                      -------------------------------
 *                            PIXELS_PER_LOGICAL_IN
 *
 * Rect in Pixels (MM_TEXT):
 *
 *              0---------- X
 *              |
 *              |       1) ------------------ ( 2   P1=(rc.left, rc.top)
 *              |       |                     |     P2=(rc.right, rc.top)
 *              |       |                     |     P3=(rc.left, rc.bottom)
 *              |       |                     |     P4=(rc.right, rc.bottom)
 *                      |                     |
 *              Y       |                     |
 *                      3) ------------------ ( 4
 *
 *              NOTE:   Origin  =(P1x, P1y)
 *                      X extent=P4x - P1x
 *                      Y extent=P4y - P1y
 *
 *
 * Rect in Himetric (MM_HIMETRIC):
 *
 *
 *                      1) ------------------ ( 2   P1=(rc.left, rc.top)
 *              Y       |                     |     P2=(rc.right, rc.top)
 *                      |                     |     P3=(rc.left, rc.bottom)
 *              |       |                     |     P4=(rc.right, rc.bottom)
 *              |       |                     |
 *              |       |                     |
 *              |       3) ------------------ ( 4
 *              |
 *              0---------- X
 *
 *              NOTE:   Origin  =(P3x, P3y)
 *                      X extent=P2x - P3x
 *                      Y extent=P2y - P3y
 *
 *
 */

STDAPI_(void) XformRectInPixelsToHimetric(HDC hDC, LPRECT prcPix
, LPRECT prcHiMetric)
{
int     iXppli;     //Pixels per logical inch along width
int     iYppli;     //Pixels per logical inch along height
int     iXextInPix=(prcPix->right-prcPix->left);
int     iYextInPix=(prcPix->bottom-prcPix->top);
BOOL    fSystemDC=FALSE;

if (NULL==hDC || GetDeviceCaps(hDC, LOGPIXELSX) == 0)
{
hDC=GetDC(NULL);
fSystemDC=TRUE;
}

iXppli=GetDeviceCaps (hDC, LOGPIXELSX);
iYppli=GetDeviceCaps (hDC, LOGPIXELSY);

//We got pixel units, convert them to logical HIMETRIC along the display
prcHiMetric->right=MAP_PIX_TO_LOGHIM(iXextInPix, iXppli);
prcHiMetric->top  =MAP_PIX_TO_LOGHIM(iYextInPix, iYppli);

prcHiMetric->left   =0;
prcHiMetric->bottom =0;

if (fSystemDC)
ReleaseDC(NULL, hDC);

return;
}

STDAPI_(void) XformRectInHimetricToPixels(HDC hDC, LPRECT prcHiMetric
, LPRECT prcPix)
{
int     iXppli;     //Pixels per logical inch along width
int     iYppli;     //Pixels per logical inch along height
int     iXextInHiMetric=(prcHiMetric->right-prcHiMetric->left);
int     iYextInHiMetric=(prcHiMetric->bottom-prcHiMetric->top);
BOOL    fSystemDC=FALSE;

if (NULL==hDC || GetDeviceCaps(hDC, LOGPIXELSX) == 0)
{
hDC=GetDC(NULL);
fSystemDC=TRUE;
}

iXppli=GetDeviceCaps (hDC, LOGPIXELSX);
iYppli=GetDeviceCaps (hDC, LOGPIXELSY);

//We got pixel units, convert them to logical HIMETRIC along the display
prcPix->right=MAP_LOGHIM_TO_PIX(iXextInHiMetric, iXppli);
prcPix->top  =MAP_LOGHIM_TO_PIX(iYextInHiMetric, iYppli);

prcPix->left =0;
prcPix->bottom= 0;

if (fSystemDC)
ReleaseDC(NULL, hDC);

return;
}

/*
 * XformSizeInPixelsToHimetric
 * XformSizeInHimetricToPixels
 *
 * Functions to convert a SIZEL structure (Size functions) or
 * an int (Width functions) between a device coordinate system and
 * logical HiMetric units.
 *
 * Parameters:
 *  hDC             HDC providing reference to the pixel mapping.  If
 *                  NULL, a screen DC is used.
 *  pSizeInPix      LPSIZEL containing the size to convert.
 *  pSizeInHiMetric
 *
 * NOTE:
 *  When displaying on the screen, Window apps display everything enlarged
 *  from its actual size so that it is easier to read. For example, if an
 *  app wants to display a 1in. horizontal line, that when printed is
 *  actually a 1in. line on the printed page, then it will display the line
 *  on the screen physically larger than 1in. This is described as a line
 *  that is "logically" 1in. along the display width. Windows maintains as
 *  part of the device-specific information about a given display device:
 *      LOGPIXELSX -- no. of pixels per logical in along the display width
 *      LOGPIXELSY -- no. of pixels per logical in along the display height
 *
 *  The following formula converts a distance in pixels into its equivalent
 *  logical HIMETRIC units:
 *
 *      DistInHiMetric=(HIMETRIC_PER_INCH * DistInPix)
 *                       -------------------------------
 *                           PIXELS_PER_LOGICAL_IN
 *
 */

STDAPI_(void) XformSizeInPixelsToHimetric(HDC hDC, LPSIZEL pSizeInPix
, LPSIZEL pSizeInHiMetric)
{
int     iXppli;     //Pixels per logical inch along width
int     iYppli;     //Pixels per logical inch along height
BOOL    fSystemDC=FALSE;

if (NULL==hDC || GetDeviceCaps(hDC, LOGPIXELSX) == 0)
{
hDC=GetDC(NULL);
fSystemDC=TRUE;
}

iXppli=GetDeviceCaps (hDC, LOGPIXELSX);
iYppli=GetDeviceCaps (hDC, LOGPIXELSY);

//We got pixel units, convert them to logical HIMETRIC along the display
pSizeInHiMetric->cx=(long)MAP_PIX_TO_LOGHIM((int)pSizeInPix->cx, iXppli);
pSizeInHiMetric->cy=(long)MAP_PIX_TO_LOGHIM((int)pSizeInPix->cy, iYppli);

if (fSystemDC)
ReleaseDC(NULL, hDC);

return;
}

STDAPI_(void) XformSizeInHimetricToPixels(HDC hDC, LPSIZEL pSizeInHiMetric
, LPSIZEL pSizeInPix)
{
int     iXppli;     //Pixels per logical inch along width
int     iYppli;     //Pixels per logical inch along height
BOOL    fSystemDC=FALSE;

if (NULL==hDC || GetDeviceCaps(hDC, LOGPIXELSX) == 0)
{
hDC=GetDC(NULL);
fSystemDC=TRUE;
}

iXppli=GetDeviceCaps (hDC, LOGPIXELSX);
iYppli=GetDeviceCaps (hDC, LOGPIXELSY);

//We got logical HIMETRIC along the display, convert them to pixel units
pSizeInPix->cx=(long)MAP_LOGHIM_TO_PIX((int)pSizeInHiMetric->cx, iXppli);
pSizeInPix->cy=(long)MAP_LOGHIM_TO_PIX((int)pSizeInHiMetric->cy, iYppli);

if (fSystemDC)
ReleaseDC(NULL, hDC);

return;
}


IADVSINK.CPP

/*
 * IADVSINK.CPP
 *
 * Template IAdviseSink interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iadvsink.h"

/*
 * CImpIAdviseSink::CImpIAdviseSink
 * CImpIAdviseSink::~CImpIAdviseSink
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIAdviseSink::CImpIAdviseSink(LPVOID pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIAdviseSink::~CImpIAdviseSink(void)
    {
    return;
    }

/*
 * CImpIAdviseSink::QueryInterface
 * CImpIAdviseSink::AddRef
 * CImpIAdviseSink::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIAdviseSink.
 */

STDMETHODIMP CImpIAdviseSink::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIAdviseSink::OnDataChange
 *
 * Purpose:
 *  Notifes the advise sink that data changed in a data object.
 *  On this message you may request a new data rendering and update
 *  your displays as necessary.
 *
 * Parameters:
 *  pFEIn           LPFORMATETC describing format that changed
 *  pSTM            LPSTGMEDIUM providing the medium in which the
 *                  data is provided.
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnDataChange(LPFORMATETC pFEIn
    , LPSTGMEDIUM pSTM)
    {
    return;
    }

/*
 * CImpIAdviseSink::OnViewChange
 *
 * Purpose:
 *  Notifes the advise sink that presentation data changed in the
 *  data object to which we're connected providing the right time
 *  to update displays using such presentations.
 *
 * Parameters:
 *  dwAspect        DWORD indicating which aspect has changed.
 *  lindex          LONG indicating the piece that changed.
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnViewChange(DWORD dwAspect
    , LONG lindex)
    {
    return;
    }

/*
 * CImpIAdviseSink::OnRename
 *
 * Purpose:
 *  Informs the advise sink that a linked object has been renamed.
 *  Generally only the OLE 2.0 default handler cares about this.
 *
 * Parameters:
 *  pmk             LPMONIKER providing the new name of the object
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnRename(LPMONIKER pmk)
    {
    return;
    }

/*
 * CImpIAdviseSink::OnSave
 *
 * Purpose:
 *  Informs the advise sink that the OLE object has been saved
 *  persistently.  The primary purpose of this is for containers
 *  that want to make optimizations for objects that are not in a
 *  saved state, so on this you have to disable such optimizations.
 *  Generally only the OLE 2.0 default handler cares about this.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnSave(void)
    {
    return;
    }

/*
 * CImpIAdviseSink::OnClose
 *
 * Purpose:
 *  Informs the advise sink that the OLE object has closed and is
 *  no longer bound in any way.  Generally only of interest to the
 *  OLE 2.0 default handler.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink::OnClose(void)
    {
    return;
    }


IADVSINK.H

/*
 * IADVSINK.H
 *
 * Definitions of a temmplate IAdviseSink interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IADVSINK_H_
#define _IADVSINK_H_

class CImpIAdviseSink;
typedef class CImpIAdviseSink *PIMPIADVISESINK;

class CImpIAdviseSink : public IAdviseSink
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIAdviseSink(LPVOID, LPUNKNOWN);
        ~CImpIAdviseSink(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(void)  OnDataChange(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP_(void)  OnViewChange(DWORD, LONG);
        STDMETHODIMP_(void)  OnRename(LPMONIKER);
        STDMETHODIMP_(void)  OnSave(void);
        STDMETHODIMP_(void)  OnClose(void);
    };

#endif //_IADVSINK_H_


IADVSNK2.CPP

/*
 * IADVSNK2.CPP
 *
 * Template IAdviseSink2 interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iadvsink.h"

/*
 * CImpIAdviseSink2::CImpIAdviseSink2
 * CImpIAdviseSink2::~CImpIAdviseSink2
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIAdviseSink2::CImpIAdviseSink2(LPVOID pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIAdviseSink2::~CImpIAdviseSink2(void)
    {
    return;
    }

/*
 * CImpIAdviseSink2::QueryInterface
 * CImpIAdviseSink2::AddRef
 * CImpIAdviseSink2::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIAdviseSink2.
 */

STDMETHODIMP CImpIAdviseSink2::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink2::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIAdviseSink2::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIAdviseSink2::OnDataChange
 *
 * Purpose:
 *  Notifes the advise sink that data changed in a data object.
 *  On this message you may request a new data rendering and update
 *  your displays as necessary.
 *
 * Parameters:
 *  pFEIn           LPFORMATETC describing format that changed
 *  pSTM            LPSTGMEDIUM providing the medium in which the
 *                  data is provided.
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink2::OnDataChange(LPFORMATETC pFEIn
    , LPSTGMEDIUM pSTM)
    {
    return;
    }

/*
 * CImpIAdviseSink2::OnViewChange
 *
 * Purpose:
 *  Notifes the advise sink that presentation data changed in the
 *  data object to which we're connected providing the right time
 *  to update displays using such presentations.
 *
 * Parameters:
 *  dwAspect        DWORD indicating which aspect has changed.
 *  lindex          LONG indicating the piece that changed.
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink2::OnViewChange(DWORD dwAspect
    , LONG lindex)
    {
    return;
    }

/*
 * CImpIAdviseSink2::OnRename
 *
 * Purpose:
 *  Informs the advise sink that a linked compound document object
 *  has been renamed.  Generally only the OLE default handler cares
 *  about this.
 *
 * Parameters:
 *  pmk             LPMONIKER providing the new name of the object
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink2::OnRename(LPMONIKER pmk)
    {
    return;
    }

/*
 * CImpIAdviseSink2::OnSave
 *
 * Purpose:
 *  Informs the advise sink that the compound document object has
 *  been saved persistently.  The primary purpose of this is for
 *  containers that want to make optimizations for objects that are
 *  not in a saved state, so on this you have to disable such
 *  optimizations.  Generally only the OLE default handler
 *  cares about this.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink2::OnSave(void)
    {
    return;
    }

/*
 * CImpIAdviseSink2::OnClose
 *
 * Purpose:
 *  Informs the advise sink that the compound document object has
 *  closed and is no longer bound in any way.  Generally only of
 *  interest to the OLE default handler.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink2::OnClose(void)
    {
    return;
    }

/*
 * CImpIAdviseSink2::OnLinkSrcChange
 *
 * Purpose:
 *  Informs the advise sink that a linked compound document object
 *  has changed its link source to the object identified by the
 *  given moniker.  This is generally only of interest to the OLE
 *  default handler's implementation of linked objects.
 *
 * Parameters:
 *  pmk             LPMONIKER specifying the new link source.
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIAdviseSink2::OnLinkSrcChange(LPMONIKER pmk)
    {
    return;
    }


IADVSNK2.H

/*
 * IADVSNK2.H
 *
 * Definitions of a template IAdviseSink2 interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IADVSNK2_H_
#define _IADVSNK2_H_

class CImpIAdviseSink2;
typedef class CImpIAdviseSink2 *PIMPIADVISESNK2;

class CImpIAdviseSink2 : public IAdviseSink2
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIAdviseSink2(LPVOID, LPUNKNOWN);
        ~CImpIAdviseSink2(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IAdviseSink
        STDMETHODIMP_(void)  OnDataChange(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP_(void)  OnViewChange(DWORD, LONG);
        STDMETHODIMP_(void)  OnRename(LPMONIKER);
        STDMETHODIMP_(void)  OnSave(void);
        STDMETHODIMP_(void)  OnClose(void);

        //IAdviseSink2
        STDMETHODIMP_(void)  OnLinkSrcChange(LPMONIKER);
    };

#endif //_IADVSNK2_H_


ICLASSF.CPP

/*
 * ICLASSF.CPP
 *
 * Template implementation of a Class Factory object.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iclassf.h"

/*
 * CClassFactory::CClassFactory
 * CClassFactory::~CClassFactory
 *
 * Constructor Parameters:
 *  None
 */

CClassFactory::CClassFactory(void)
    {
    m_cRef=0L;
    return;
    }

CClassFactory::~CClassFactory(void)
    {
    return;
    }

/*
 * CClassFactory::QueryInterface
 * CClassFactory::AddRef
 * CClassFactory::Release
 *
 * Purpose:
 *  Non-delegating IUnknown members for CClassFactory.
 */

STDMETHODIMP CClassFactory::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    //Any interface on this object is the object pointer.
    if (IsEqualIID(riid, IID_IUnknown)
        || IsEqualIID(riid, IID_IClassFactory))
        *ppv=(LPVOID)this;

    /*
     * If we actually assign an interface to ppv we need to AddRef
     * it since we're returning a new pointer.
     */
    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return E_NOINTERFACE;
    }

STDMETHODIMP_(ULONG) CClassFactory::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CClassFactory::Release(void)
    {
    ULONG           cRefT;

    cRefT=--m_cRef;

    /*
     * Free the object if the reference and lock counts are zero.
     * This is most useful in a DLL where you can use zero reference
     * counts to free the object.  In an executable you will never
     * have this at zero until you CoRevokeClassObject, then you
     * have to worry about locks.  CHAP04\EKOALA has an example
     * application object implementation for this.
     */
    if (0L==m_cRef)
        delete this;

    return cRefT;
    }

/*
 * CClassFactory::CreateInstance
 *
 * Purpose:
 *  Instantiates an object supported by this class factory.  That
 *  object must at least support IUnknown.
 *
 *  Derived classes should override this function.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we are
 *                  being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          LPVOID * in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_NOINTERFACE
 *                  if we cannot support the requested interface.
 */

STDMETHODIMP CClassFactory::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, LPVOID *ppvObj)
    {
    *ppvObj=NULL;
    return E_OUTOFMEMORY;
    }

/*
 * CClassFactory::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the serving
 *  IClassFactory object.  When the lock count is zero and the
 *  reference count is zero we get rid of this object.
 *
 *  DLL objects should override this to affect their DLL ref count.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CClassFactory::LockServer(BOOL fLock)
    {
    return NOERROR;
    }


ICLASSF.H

/*
 * ICLASSF.H
 *
 * Definitions for a template Class Factory object with the
 * IClassFactory interface.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _ICLASSF_H_
#define _ICLASSF_H_

class CClassFactory;
typedef class CClassFactory *PCClassFactory;

class CClassFactory : public IClassFactory
    {
    protected:
        ULONG           m_cRef;         //Reference count on object

    public:
        CClassFactory(void);
        ~CClassFactory(void);

        //IUnknown interface members
        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP CreateInstance(LPUNKNOWN, REFIID, LPVOID *);
        STDMETHODIMP LockServer(BOOL);
    };

#endif //_ICLASSF_H_


ICLASSF2.CPP

/*
 * ICLASSF2.CPP
 *
 * Template implementation of a Class Factory object that
 * supports licensing.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iclassf2.h"

/*
 * CClassFactory2::CClassFactory2
 * CClassFactory2::~CClassFactory2
 *
 * Constructor Parameters:
 *  None
 */

CClassFactory2::CClassFactory2(void)
    {
    m_cRef=0L;
    return;
    }

CClassFactory2::~CClassFactory2(void)
    {
    return;
    }

/*
 * CClassFactory2::QueryInterface
 * CClassFactory2::AddRef
 * CClassFactory2::Release
 *
 * Purpose:
 *  Non-delegating IUnknown members for CClassFactory2.
 */

STDMETHODIMP CClassFactory2::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IClassFactory2==riid
        || IID_IClassFactory2==riid)
        *ppv=(LPVOID)this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CClassFactory2::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CClassFactory2::Release(void)
    {
    if (0L!=--m_cRef)
        return m_cRef;

    //Free the object if the reference and lock counts are zero.
    delete this;
    return 0;
    }

/*
 * CClassFactory2::CreateInstance
 *
 * Purpose:
 *  Instantiates an object supported by this class factory.  That
 *  object must at least support IUnknown.
 *
 *  Derived classes should override this function.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we are
 *                  being used in an aggregation.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  ppvObj          LPVOID * in which to store the desired
 *                  interface pointer for the new object.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise E_NOINTERFACE
 *                  if we cannot support the requested interface.
 */

STDMETHODIMP CClassFactory2::CreateInstance(LPUNKNOWN pUnkOuter
    , REFIID riid, LPVOID *ppvObj)
    {
    *ppvObj=NULL;
    return ResultFromScode(E_OUTOFMEMORY);
    }

/*
 * CClassFactory2::LockServer
 *
 * Purpose:
 *  Increments or decrements the lock count of the serving
 *  IClassFactory2 object.  When the lock count is zero and the
 *  reference count is zero we get rid of this object.
 *
 *  DLL objects should override this to affect their DLL ref count.
 *
 * Parameters:
 *  fLock           BOOL specifying whether to increment or
 *                  decrement the lock count.
 */

STDMETHODIMP CClassFactory2::LockServer(BOOL fLock)
    {
    return NOERROR;
    }

/*
 * CClassFactory2::GetLicInfo
 *
 * Purpose:
 *  Fills a LICINFO structure with license information for
 *  this class factory.
 *
 * Parameters:
 *  pLicInfo        LPLICINFO to the structure to fill
 */

STDMETHODIMP CClassFactory2::GetLicInfo(LPLICINFO pLicInfo)
    {
    if (NULL==pLicInfo)
        return ResultFromScode(E_POINTER);

    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CClassFactory2::RequestLicKey
 *
 * Purpose:
 *  Retrieves a license key from this class factory for use with
 *  CreateInstanceLic.
 *
 * Parameters:
 *  dwReserved      DWORD reserved
 *  pbstrKey        BSTR * in which to return the key.
 */

STDMETHODIMP CClassFactory2::RequestLicKey(DWORD dwReserved
    , BSTR *pbstrKey)
    {
    *pbstrKey=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CClassFactory2::CreateInstanceLic
 *
 * Purpose:
 *  Creates and instance of the object given a license key.
 *  Same as CreateInstance, and implementations of this function
 *  will typically just validate the key and call CreateInstance.
 *
 * Parameters:
 *  pUnkOuter       LPUNKNOWN to the controlling IUnknown if we are
 *                  being used in an aggregation.
 *  pUnkReserved    LPUNKNOWN reserved.
 *  riid            REFIID identifying the interface the caller
 *                  desires to have for the new object.
 *  bstrKey         BSTR key used to validate creation.
 *  ppvObj          LPVOID * in which to store the desired
 *                  interface pointer for the new object.
 */

STDMETHODIMP CClassFactory2::CreateInstanceLic(LPUNKNOWN pUnkOuter
    , LPUNKNOWN pUnkReserved, REFIID riid, BSTR bstrKey
    , LPVOID *ppvObj)
    {
    *ppvObj=NULL;

    //Validate bstrKey

    //Create an instance once the key is validated.
    return CreateInstance(pUnkOuter, riid, ppvObj);
    }


ICLASSF2.H

/*
 * ICLASSF2.H
 *
 * Definitions for a template Class Factory object with the
 * IClassFactory2 interface.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _ICLASSF2_H_
#define _ICLASSF2_H_

#define INC_CONTROLS
#include <inole.h>

class CClassFactory2;
typedef class CClassFactory2 *PCClassFactory2;

class CClassFactory2 : public IClassFactory2
    {
    protected:
        ULONG           m_cRef;         //Reference count on object

    public:
        CClassFactory2(void);
        ~CClassFactory2(void);

        //IUnknown interface members
        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IClassFactory members
        STDMETHODIMP CreateInstance(LPUNKNOWN, REFIID, LPVOID *);
        STDMETHODIMP LockServer(BOOL);

        //IClassFactory2 members
        STDMETHODIMP GetLicInfo(LPLICINFO);
    STDMETHODIMP RequestLicKey(DWORD, BSTR *);
    STDMETHODIMP CreateInstanceLic(LPUNKNOWN, LPUNKNOWN, REFIID
    , BSTR, LPVOID *);
    };

#endif //_ICLASSF2_H_


ICLISITE.CPP

/*
 * ICLISITE.CPP
 *
 * Template IOleClientSite interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iclisite.h"

/*
 * CImpIOleClientSite::CImpIOleClientSite
 * CImpIOleClientSite::~CImpIOleClientSite
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleClientSite::CImpIOleClientSite(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleClientSite::~CImpIOleClientSite(void)
    {
    return;
    }

/*
 * CImpIOleClientSite::QueryInterface
 * CImpIOleClientSite::AddRef
 * CImpIOleClientSite::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIOleClientSite.
 */

STDMETHODIMP CImpIOleClientSite::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleClientSite::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleClientSite::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleClientSite::SaveObject
 *
 * Purpose:
 *  Requests that the container call OleSave for the object that
 *  lives here.  Typically this happens on server shutdown.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleClientSite::SaveObject(void)
    {
    return NOERROR;
    }

/*
 * CImpIOleClientSite::GetMoniker
 *
 * Purpose:
 *  Retrieves the moniker for the site in which this object lives,
 *  either the moniker relative to the container or the full
 *  moniker.
 *
 * Parameters:
 *  dwAssign        DWORD specifying that the object wants moniker
 *                  assignment.
 *  dwWhich         DWORD identifying which moniker the object wants,
 *                  either the container's moniker, the moniker
 *                  relative to this client site, or the full moniker.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleClientSite::GetMoniker(DWORD dwAssign
    , DWORD dwWhich, LPMONIKER *ppmk)
    {
    //Only necessary if you allow linking to embeddings
    *ppmk=NULL;
    return E_NOTIMPL;
    }

/*
 * CImpIOleClientSite::GetContainer
 *
 * Purpose:
 *  Returns a pointer to the document's IOleContainer interface.
 *
 * Parameters:
 *  ppContainer     LPOLECONTAINER * in which to return the
 *                  interface.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleClientSite::GetContainer(LPOLECONTAINER
    * ppContainer)
    {
    //Only necessary if you allow linking to embeddings
    *ppContainer=NULL;
    return E_NOTIMPL;
    }

/*
 * CImpIOleClientSite::ShowObject
 *
 * Purpose:
 *  Tells the container to bring the object fully into view as
 *  much as possible, that is, scroll the document.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleClientSite::ShowObject(void)
    {
    return NOERROR;
    }

/*
 * CImpIOleClientSite::OnShowWindow
 *
 * Purpose:
 *  Informs the container if the object is showing itself or
 *  hiding itself.  This is done only in the opening mode and allows
 *  the container to know when to shade or unshade the object.
 *
 * Parameters:
 *  fShow           BOOL indiciating that the object is being shown
 *                  (TRUE) or hidden (FALSE).
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleClientSite::OnShowWindow(BOOL fShow)
    {
    return NOERROR;
    }

/*
 * CImpIOleClientSite::RequestNewObjectLayout
 *
 * Purpose:
 *  Called when the object needs more room in the container.
 *  This is not used in OLE 2.0
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleClientSite::RequestNewObjectLayout(void)
    {
    return E_NOTIMPL;
    }


ICLISITE.H

/*
 * ICLISITE.H
 *
 * Definitions of a template IOleClientSite interface
 * implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _ICLISITE_H_
#define _ICLISITE_H_

class CImpIOleClientSite;
typedef class CImpIOleClientSite *PIMPIOLECLIENTSITE;

class CImpIOleClientSite : public IOleClientSite
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIOleClientSite(LPVOID, LPUNKNOWN);
        ~CImpIOleClientSite(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP SaveObject(void);
        STDMETHODIMP GetMoniker(DWORD, DWORD, LPMONIKER *);
        STDMETHODIMP GetContainer(LPOLECONTAINER *);
        STDMETHODIMP ShowObject(void);
        STDMETHODIMP OnShowWindow(BOOL);
        STDMETHODIMP RequestNewObjectLayout(void);
    };

#endif //_ICLISITE_H_


ICONNPT.CPP

/*
 * ICONNPT.CPP
 *
 * Template implemenation of a connection point object.  Usually
 * these objects will be stand-alone objects contained within
 * a connection point container.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iconnpt.h"

/*
 * CConnectionPoint::CConnectionPoint
 * CConnectionPoint::~CConnectionPoint
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 */

CConnectionPoint::CConnectionPoint(LPVOID pObj)
    {
    m_cRef=0;
    m_pObj=pObj;
    return;
    }

CConnectionPoint::~CConnectionPoint(void)
    {
    return;
    }

/*
 * CConnectionPoint::QueryInterface
 * CConnectionPoint::AddRef
 * CConnectionPoint::Release
 *
 * Purpose:
 *  Non-delegating IUnknown members for CConnectionPoint.
 */

STDMETHODIMP CConnectionPoint::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IConnectionPoint==riid)
        *ppv=(LPVOID)this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CConnectionPoint::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CConnectionPoint::Release(void)
    {
    if (0!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CConnectionPoint::GetConnectionInterface
 *
 * Purpose:
 *  Returns the IID of the outgoing interface supported through
 *  this connection point.
 *
 * Parameters:
 *  pIID            IID * in which to store the IID.
 */

STDMETHODIMP CConnectionPoint::GetConnectionInterface(IID *pIID)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CConnectionPoint::GetConnectionPointContainer
 *
 * Purpose:
 *  Returns a pointer to the IConnectionPointContainer that
 *  is manageing this connection point.
 *
 * Parameters:
 *  ppCPC           IConnectionPointContainer ** in which to return
 *                  the pointer after calling AddRef.
 */

STDMETHODIMP CConnectionPoint::GetConnectionPointContainer
    (IConnectionPointContainer **ppCPC)
    {
    *ppCPC=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CConnectionPoint::Advise
 *
 * Purpose:
 *  Provides this connection point with a notification sink to
 *  call whenever the appropriate outgoing function/event occurs.
 *
 * Parameters:
 *  pUnkSink        LPUNKNOWN to the sink to notify.  The connection
 *                  point must QueryInterface on this pointer to obtain
 *                  the proper interface to call.  The connection
 *                  point must also insure that any pointer held has
 *                  a reference count (QueryInterface will do it).
 *  pdwCookie       DWORD * in which to store the connection key for
 *                  later calls to Unadvise.
 */

STDMETHODIMP CConnectionPoint::Advise(LPUNKNOWN pUnkSink
    , DWORD *pdwCookie)
    {
    *pdwCookie=0;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CConnectionPoint::Unadvise
 *
 * Purpose:
 *  Terminates the connection to the notification sink identified
 *  with dwCookie (that was returned from Advise).  The connection
 *  point has to Release any held pointers for that sink.
 *
 * Parameters:
 *  dwCookie        DWORD connection key from Advise.
 */

STDMETHODIMP CConnectionPoint::Unadvise(DWORD dwCookie)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CConnectionPoint::EnumConnections
 *
 * Purpose:
 *  Creates and returns an enumerator object with the
 *  IEnumConnections interface that will enumerate the IUnknown
 *  pointers of each connected sink.
 *
 * Parameters:
 *  ppEnum          LPENUMCONNECTIONS in which to store the
 *                  IEnumConnections pointer.
 */

STDMETHODIMP CConnectionPoint::EnumConnections
    (LPENUMCONNECTIONS *ppEnum)
    {
    *ppEnum=NULL;
    return ResultFromScode(E_NOTIMPL);
    }


ICONNPT.H

/*
 * ICONNPT.H
 *
 * Header for template IConnectionPoint implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _ICONNPT_H_
#define _ICONNPT_H_

#define INC_CONTROLS
#include <inole.h>

class CConnectionPoint;
typedef class CConnectionPoint *PCConnectionPoint;

class CConnectionPoint : public IConnectionPoint
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Backpointer to the object

    public:
        CConnectionPoint(LPVOID);
        ~CConnectionPoint(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetConnectionInterface(IID *);
        STDMETHODIMP GetConnectionPointContainer
            (IConnectionPointContainer **);
        STDMETHODIMP Advise(LPUNKNOWN, DWORD *);
        STDMETHODIMP Unadvise(DWORD);
        STDMETHODIMP EnumConnections(LPENUMCONNECTIONS *);
    };

#endif //_ICONNPT_H_


ICONNPTC.CPP

/*
 * ICONNPTC.CPP
 *
 * Template implemenation of IConnectionPointContainer.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iconnptc.h"

/*
 * CImpIConnectionPointContainer::CImpIConnectionPointContainer
 * CImpIConnectionPointContainer::~CImpIConnectionPointContainer
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIConnectionPointContainer::CImpIConnectionPointContainer
    (LPVOID pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIConnectionPointContainer::~CImpIConnectionPointContainer(void)
    {
    return;
    }

/*
 * CImpIConnectionPointContainer::QueryInterface
 * CImpIConnectionPointContainer::AddRef
 * CImpIConnectionPointContainer::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIConnectionPointContainer.
 */

STDMETHODIMP CImpIConnectionPointContainer::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIConnectionPointContainer::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIConnectionPointContainer::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIConnectionPointContainer::EnumConnectionPoints
 *
 * Purpose:
 *  Creates and returns an enumerator object with the
 *  IEnumConnectionPoints interface that will enumerate the
 *  individual connection points supported in this object.
 *
 * Parameters:
 *  ppEnum          LPENUMCONNECTIONPOINTS in which to store the
 *                  IEnumConnectionPoints pointer.
 */

STDMETHODIMP CImpIConnectionPointContainer::EnumConnectionPoints
    (LPENUMCONNECTIONPOINTS *ppEnum)
    {
    *ppEnum=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIConnectionPointContainer::FindConnectionPoint
 *
 * Purpose:
 *  Returns a pointer to the IConnectionPoint for a given
 *  outgoing IID.
 *
 * Parameters:
 *  riid            REFIID of the outgoing interface for which
 *                  a connection point is desired.
 *  ppCP            IConnectionPoint ** in which to return
 *                  the pointer after calling AddRef.
 */

STDMETHODIMP CImpIConnectionPointContainer::FindConnectionPoint
    (REFIID riid, IConnectionPoint **ppCP)
    {
    *ppCP=NULL;
    return ResultFromScode(E_NOTIMPL);
    }


ICONNPTC.H

/*
 * ICONNPTC.H
 *
 * Header for template IConnectionPointContainer interface
 * implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _ICONNPTC_H_
#define _ICONNPTC_H_

#define INC_CONTROLS
#include <inole.h>

class CImpIConnectionPointContainer;
typedef class CImpIConnectionPointContainer
    *PCImpIConnectionPointContainer;

class CImpIConnectionPointContainer : public IConnectionPointContainer
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Backpointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIConnectionPointContainer(LPVOID, LPUNKNOWN);
        ~CImpIConnectionPointContainer(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

    STDMETHODIMP EnumConnectionPoints(LPENUMCONNECTIONPOINTS *);
    STDMETHODIMP FindConnectionPoint(REFIID, LPCONNECTIONPOINT *);
    };

#endif //_ICONNPTC_H_


ICONSITE.CPP

/*
 * ICONSITE.CPP
 *
 * Template IOleControlSite interface implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iconsite.h"

/*
 * CImpIOleControlSite::CImpIOleControlSite
 * CImpIOleControlSite::~CImpIOleControlSite
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleControlSite::CImpIOleControlSite(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleControlSite::~CImpIOleControlSite(void)
    {
    return;
    }

/*
 * CImpIOleControlSite::QueryInterface
 * CImpIOleControlSite::AddRef
 * CImpIOleControlSite::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIOleControlSite.
 */

STDMETHODIMP CImpIOleControlSite::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleControlSite::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleControlSite::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleControlSite::OnControlInfoChanged
 *
 * Purpose:
 *  Informs the site that the CONTROLINFO for the control has
 *  changed and we thus need to reload the data.
 *
 * Parameters:
 *  None
 */

STDMETHODIMP CImpIOleControlSite::OnControlInfoChanged(void)
    {
    return NOERROR;
    }

/*
 * CImpIOleControlSite::LockInPlaceActive
 *
 * Purpose:
 *  Forces the container to keep this control in-place active
 *  (but not UI active) regardless of other considerations, or
 *  removes this lock.
 *
 * Parameters:
 *  fLock           BOOL indicating to lock (TRUE) or unlock (FALSE)
 *                  in-place activation.
 */

STDMETHODIMP CImpIOleControlSite::LockInPlaceActive(BOOL fLock)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleControlSite::GetExtendedControl
 *
 * Purpose:
 *  Returns a pointer to the container's extended control that wraps
 *  the actual control in this site, if one exists.
 *
 * Parameters:
 *  ppDispatch      LPDISPATCH * in which to return the pointer
 *                  to the extended control's IDispatch interface.
 */

STDMETHODIMP CImpIOleControlSite::GetExtendedControl(LPDISPATCH
    *ppDispatch)
    {
    *ppDispatch=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleControlSite::TransformCoords
 *
 * Purpose:
 *  Converts coordinates in HIMETRIC units into those used by the
 *  container.
 *
 * Parameters:
 *  pptlHiMet       POINTL * containing the coordinates to transform
 *  pptlCont        POINTF * in which to return the new coordinates
 *  dwFlags         DWORD containing instructional flags.
 */

STDMETHODIMP CImpIOleControlSite::TransformCoords(POINTL *pptlHiMet
    , POINTF *pptlCont, DWORD dwFlags)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleControlSite::TranslateAccelerator
 *
 * Purpose:
 *  Instructs the object to translate a keyboard accelerator
 *  message that the control has picked up instead.
 *
 * Parameters:
 *  pMsg            LPMSG to the message to translate.
 *  grfModifiers    DWORD flags with additional instructions.
 */

STDMETHODIMP CImpIOleControlSite::TranslateAccelerator(LPMSG pMsg
    , DWORD grfModifiers)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleControlSite::OnFocus
 *
 * Purpose:
 *  Informs the container that focus has either been lost or
 *  gained in the control.
 *
 * Parameters:
 *  fGotFocus       BOOL indicating that the control gained (TRUE)
 *                  or lost (FALSE) focus.
 */

STDMETHODIMP CImpIOleControlSite::OnFocus(BOOL fGotFocus)
    {
    return NOERROR;
    }

/*
 * CImpIOleControlSite::ShowPropertyFrame
 *
 * Purpose:
 *  Instructs the container to show the property frame if
 *  this is an extended object and requires its own property
 *  pages.
 *
 * Parameters:
 *  None
 */

STDMETHODIMP CImpIOleControlSite::ShowPropertyFrame(void)
    {
    return ResultFromScode(E_NOTIMPL);
    }


ICONSITE.H

/*
 * ICONSITE.H
 *
 * Definitions of a template IOleControlSite interface
 * implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _ICONSITE_H_
#define _ICONSITE_H_

#define INC_CONTROLS
#include <inole.h>

class CImpIOleControlSite;
typedef class CImpIOleControlSite *PCImpIOleControlSite;

class CImpIOleControlSite : public IOleControlSite
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Backpointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIOleControlSite(LPVOID, LPUNKNOWN);
        ~CImpIOleControlSite(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP OnControlInfoChanged(void);
        STDMETHODIMP LockInPlaceActive(BOOL);
        STDMETHODIMP GetExtendedControl(LPDISPATCH *);
        STDMETHODIMP TransformCoords(POINTL *, POINTF *, DWORD);
        STDMETHODIMP TranslateAccelerator(LPMSG, DWORD);
        STDMETHODIMP OnFocus(BOOL);
        STDMETHODIMP ShowPropertyFrame(void);
    };

#endif //_ICONSITE_H_


ICONTROL.CPP

/*
 * ICONTROL.CPP
 *
 * Template IOleControl interface implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "icontrol.h"

/*
 * CImpIOleControl::CImpIOleControl
 * CImpIOleControl::~CImpIOleControl
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleControl::CImpIOleControl(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleControl::~CImpIOleControl(void)
    {
    return;
    }

/*
 * CImpIOleControl::QueryInterface
 * CImpIOleControl::AddRef
 * CImpIOleControl::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIOleControl.
 */

STDMETHODIMP CImpIOleControl::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleControl::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleControl::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleControl::GetControlInfo
 *
 * Purpose:
 *  Fills a CONTROLINFO structure containing information about
 *  the controls mnemonics and other behavioral aspects.
 *
 * Parameters:
 *  pCI             LPCONTROLINFO to the structure to fill
 */

STDMETHODIMP CImpIOleControl::GetControlInfo(LPCONTROLINFO pCI)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleControl::OnMnemonic
 *
 * Purpose:
 *  Notifies the control that a mnemonic was activated.
 *
 * Parameters:
 *  pMsg            LPMSG containing the message that matches one of
 *                  the control's mnemonics.  The control uses this
 *                  to distinguish which mnemonic was pressed.
 */

STDMETHODIMP CImpIOleControl::OnMnemonic(LPMSG pMsg)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIOleControl::OnAmbientPropertyChange
 *
 * Purpose:
 *  Notifies the control that one or more of the container's ambient
 *  properties changed.
 *
 * Parameters:
 *  dispID          DISPID identifying the property, which can
 *                  be DISPID_UNKNOWN indicating that more than
 *                  one changed.
 */

STDMETHODIMP CImpIOleControl::OnAmbientPropertyChange(DISPID dispid)
    {
    return NOERROR;
    }

/*
 * CImpIOleControl::FreezeEvents
 *
 * Purpose:
 *  Instructs the control to stop firing events or to continue
 *  firing them.
 *
 * Parameters:
 *  fFreeze         BOOL indicating to freeze (TRUE) or thaw (FALSE)
 *                  events from this control.
 */

STDMETHODIMP CImpIOleControl::FreezeEvents(BOOL fFreeze)
    {
    return ResultFromScode(E_NOTIMPL);
    }


ICONTROL.H

/*
 * ICONTROL.H
 *
 * Definitions of a template IOleControl interface implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _ICONTROL_H_
#define _ICONTROL_H_

#define INC_CONTROLS
#include <inole.h>

class CImpIOleControl;
typedef class CImpIOleControl *PCImpIOleControl;

class CImpIOleControl : public IOleControl
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Backpointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIOleControl(LPVOID, LPUNKNOWN);
        ~CImpIOleControl(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetControlInfo(LPCONTROLINFO);
        STDMETHODIMP OnMnemonic(LPMSG);
        STDMETHODIMP OnAmbientPropertyChange(DISPID);
        STDMETHODIMP FreezeEvents(BOOL);
    };

#endif //_ICONTROL_H_


IDATAOBJ.CPP

/*
 * IDATAOBJ.CPP
 *
 * Template IDataObject interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "idataobj.h"

/*
 * CImpIDataObject::CImpIDataObject
 * CImpIDataObject::~CImpIDataObject
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIDataObject::CImpIDataObject(LPVOID pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIDataObject::~CImpIDataObject(void)
    {
    return;
    }

/*
 * CImpIDataObject::QueryInterface
 * CImpIDataObject::AddRef
 * CImpIDataObject::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIDataObject.
 */

STDMETHODIMP CImpIDataObject::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIDataObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIDataObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIDataObject::GetData
 *
 * Purpose:
 *  Retrieves data described by a specific FormatEtc into a StgMedium
 *  allocated by this function.  Used like GetClipboardData.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *  pSTM            LPSTGMEDIUM in which to return the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetData(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM)
    {
    return DATA_E_FORMATETC;
    }

/*
 * CImpIDataObject::GetDataHere
 *
 * Purpose:
 *  Renders the specific FormatEtc into caller-allocated medium
 *  provided in pSTM.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *  pSTM            LPSTGMEDIUM providing the medium into which
 *                  wer render the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetDataHere(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM)
    {
    return DATA_E_FORMATETC;
    }

/*
 * CImpIDataObject::QueryGetData
 *
 * Purpose:
 *  Tests if a call to GetData with this FormatEtc will provide
 *  any rendering; used like IsClipboardFormatAvailable.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the desired data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::QueryGetData(LPFORMATETC pFE)
    {
    return S_FALSE;
    }

/*
 * CImpIDataObject::GetCanonicalFormatEtc
 *
 * Purpose:
 *  Provides the caller with an equivalent FormatEtc to the one
 *  provided when different FormatEtcs will produce exactly the
 *  same renderings.
 *
 * Parameters:
 *  pFEIn            LPFORMATETC of the first description.
 *  pFEOut           LPFORMATETC of the equal description.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::GetCanonicalFormatEtc(LPFORMATETC pFEIn
    , LPFORMATETC pFEOut)
    {
    return DATA_S_SAMEFORMATETC;
    }

/*
 * CImpIDataObject::SetData
 *
 * Purpose:
 *  Places data described by a FormatEtc and living in a StgMedium
 *  into the object.  The object may be responsible to clean up the
 *  StgMedium before exiting.
 *
 * Parameters:
 *  pFE             LPFORMATETC describing the data to set.
 *  pSTM            LPSTGMEDIUM containing the data.
 *  fRelease        BOOL indicating if this function is responsible
 *                  for freeing the data.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::SetData(LPFORMATETC pFE
    , LPSTGMEDIUM pSTM, BOOL fRelease)
    {
    return DATA_E_FORMATETC;
    }

/*
 * CImpIDataObject::EnumFormatEtc
 *
 * Purpose:
 *  Returns an IEnumFORMATETC object through which the caller can
 *  iterate to learn about all the data formats this object can
 *  provide through either GetData[Here] or SetData.
 *
 * Parameters:
 *  dwDir           DWORD describing a data direction, either
 *                  DATADIR_SET or DATADIR_GET.
 *  ppEnum          LPENUMFORMATETC * in which to return the
 *                  pointer to the enumerator.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::EnumFormatEtc(DWORD dwDir
    , LPENUMFORMATETC *ppEnum)
    {
    return E_FAIL;
    }

/*
 * CImpIDataObject::DAdvise
 *
 * Purpose:
 *  Provides the data object with an IAdviseSink object that we are
 *  responsible to notify when the data changes.
 *
 * Parameters:
 *  ppFE            LPFORMATETC
 *  dwFlags         DWORD carrying flags indicating how the advise
 *                  sink wants to be treated.
 *  pIAdviseSink    LPADVISESINK to the object to notify
 *  pdwConn         LPDWORD into which we store a DWORD key
 *                  identifying the advise connection.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::DAdvise(LPFORMATETC pFE, DWORD dwFlags
    , LPADVISESINK pIAdviseSink, LPDWORD pdwConn)
    {
    return E_OUTOFMEMORY;
    }

/*
 * CImpIDataObject::DUnadvise
 *
 * Purpose:
 *  Turns off advising previously set up with Advise.
 *
 * Parameters:
 *  dwConn          DWORD connection key returned from Advise.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::DUnadvise(DWORD dwConn)
    {
    return E_FAIL;
    }

/*
 * CImpIDataObject::EnumDAdvise
 *
 * Purpose:
 *  Returns an enumerator object through which the caller can find
 *  all the agents currently receiving advises on this data object.
 *
 * Parameters:
 *  ppEnum          LPENUMSTATDATA * in which to return the
 *                  enumerator.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIDataObject::EnumDAdvise(LPENUMSTATDATA *ppEnum)
    {
    return E_FAIL;
    }


IDATAOBJ.H

/*
 * IDATAOBJ.H
 *
 * Definitions of a template IDataObject interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IDATAOBJ_H_
#define _IDATAOBJ_H_

class CImpIDataObject;
typedef class CImpIDataObject *PIMPIDATAOBJECT;

class CImpIDataObject : public IDataObject
    {
    private:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIDataObject(LPVOID, LPUNKNOWN);
        ~CImpIDataObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IDataObject members
        STDMETHODIMP GetData(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP GetDataHere(LPFORMATETC, LPSTGMEDIUM);
        STDMETHODIMP QueryGetData(LPFORMATETC);
        STDMETHODIMP GetCanonicalFormatEtc(LPFORMATETC
            , LPFORMATETC);
        STDMETHODIMP SetData(LPFORMATETC, LPSTGMEDIUM, BOOL);
        STDMETHODIMP EnumFormatEtc(DWORD, LPENUMFORMATETC *);
        STDMETHODIMP DAdvise(LPFORMATETC, DWORD
            ,  LPADVISESINK, DWORD *);
        STDMETHODIMP DUnadvise(DWORD);
        STDMETHODIMP EnumDAdvise(LPENUMSTATDATA *);
    };

#endif //_IDATAOBJ_H_


IDISPAT.CPP

/*
 * IDISPAT.CPP
 *
 * Template IDispatch interface implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "idispat.h"

/*
 * CImpIDispatch::CImpIDispatch
 * CImpIDispatch::~CImpIDispatch
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIDispatch::CImpIDispatch(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIDispatch::~CImpIDispatch(void)
    {
    return;
    }

/*
 * CImpIDispatch::QueryInterface
 * CImpIDispatch::AddRef
 * CImpIDispatch::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIDispatch.
 */

STDMETHODIMP CImpIDispatch::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIDispatch::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIDispatch::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIDispatch::GetTypeInfoCount
 *
 * Purpose:
 *  Returns the number of tyep information (ITypeInfo) interfaces
 *  that the object provides (0 or 1).
 *
 * Parameters:
 *  pctInfo         UINT * to the location to receive
 *                  the count of interfaces.
 */

STDMETHODIMP CImpIDispatch::GetTypeInfoCount(UINT *pctInfo)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIDispatch::GetTypeInfo
 *
 * Purpose:
 *  Retrieves type information for the automation interface.
 *
 * Parameters:
 *  itinfo          UINT reserved.  Must be zero.
 *  lcid            LCID providing the locale for the type
 *                  information.  If the object does not support
 *                  localization, this is ignored.
 *  pptinfo         ITypeInfo ** in which to store the ITypeInfo
 *                  interface for the object.
 */

STDMETHODIMP CImpIDispatch::GetTypeInfo(UINT itinfo
    , LCID lcid, ITypeInfo **pptInfo)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIDispatch::GetIDsOfNames
 *
 * Purpose:
 *
 * Parameters:
 *  riid            REFIID reserved.  Must be NULL.
 *  rgszNames       OLECHAR ** pointing to the array of names to b
 *                  mapped.
 *  cNames          UINT number of names to be mapped.
 *  lcid            LCID of the locale.
 *  rgDispID        DISPID * caller allocated array containing IDs
 *                  corresponginf to those names in rgszNames.
 */

STDMETHODIMP CImpIDispatch::GetIDsOfNames(REFIID riid
    , OLECHAR **rgszNames, UINT cNames, LCID lcid
    , DISPID *rgDispID)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIDispatch::Invoke
 *
 * Purpose:
 *
 * Parameters:
 *  dispIDMember    DISPID of the method to invoke.
 *  riid            REFIID reserved, must be NULL.
 *  lcid            LCID of the locale.
 *  wFlags          USHORT describing the context of the invocation.
 *  pDispParams     DISPPARAMS * to the array of arguments.
 *  pVarResult      VARIANT * in which to store the result.  Is
 *                  NULL if the caller is not interested.
 *  pExcepInfo      EXCEPINFO * to exception information.
 *  puArgErr        UINT * in which to store the index of an
 *                  invalid parameter if DISP_E_TYPEMISMATCH
 *                  is returned.
 */

STDMETHODIMP CImpIDispatch::Invoke(DISPID disIDMember, REFIID riid
    , LCID lcid, unsigned short wFlags, DISPPARAMS *pDispParams
    , VARIANT *pVarResult, EXCEPINFO *pExcepInfo
    , UINT *puArgErr)
    {
    return ResultFromScode(E_NOTIMPL);
    }


IDISPAT.H

/*
 * IDISPAT.H
 *
 * Definitions of a template IDispatch interface implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IDISPAT_H_
#define _IDISPAT_H_

#include <inole.h>

class CImpIDispatch;
typedef class CImpIDispatch *PCImpIDispatch;

class CImpIDispatch : public IDispatch
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Backpointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIDispatch(LPVOID, LPUNKNOWN);
        ~CImpIDispatch(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetTypeInfoCount(UINT *);
        STDMETHODIMP GetTypeInfo(UINT, LCID, ITypeInfo **);
        STDMETHODIMP GetIDsOfNames(REFIID, OLECHAR **, UINT
            , LCID, DISPID *);
        STDMETHODIMP Invoke(DISPID, REFIID, LCID, USHORT
            , DISPPARAMS *, VARIANT *, EXCEPINFO *, UINT *);
    };

#endif //_IDISPAT_H_


IDROPSRC.CPP

/*
 * IDROPSRC.CPP
 *
 * Template implementation of a DropSource object.  There is
 * actually a full standard implementation of this interface here.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "idropsrc.h"

/*
 * CDropSource::CDropSource
 * CDropSource::~CDropSource
 *
 * Constructor Parameters:
 *  pObj            LPVOID back pointer to whatever we live with.
 */

CDropSource::CDropSource(LPVOID pBack)
    {
    m_cRef=0;
    m_pBack=pBack;
    return;
    }

CDropSource::~CDropSource(void)
    {
    return;
    }

/*
 * CDropSource::QueryInterface
 * CDropSource::AddRef
 * CDropSource::Release
 *
 * Purpose:
 *  Non-delegating IUnknown members for CDropSource.
 */

STDMETHODIMP CDropSource::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    //Any interface on this object is the object pointer.
    if (IsEqualIID(riid, IID_IUnknown)
        || IsEqualIID(riid, IID_IDropSource))
        *ppv=(LPVOID)this;

    /*
     * If we actually assign an interface to ppv we need to AddRef
     * it since we're returning a new pointer.
     */
    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return E_NOINTERFACE;
    }

STDMETHODIMP_(ULONG) CDropSource::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropSource::Release(void)
    {
    ULONG   cRefT;

    cRefT=--m_cRef;

    if (0L==m_cRef)
        delete this;

    return cRefT;
    }

/*
 * CDropSource::QueryDragContinue
 *
 * Purpose:
 *  Determines whether to continue a drag operation or cancel it.
 *
 * Parameters:
 *  fEsc            BOOL indicating that the ESC key was pressed.
 *  grfKeyState     DWORD providing states of keys and mouse buttons
 *
 * Return Value:
 *  HRESULT         DRAGDROP_S_CANCEL to stop the drag,
 *                  DRAGDROP_S_DROP to drop the data where it is,
 *                  or NOERROR to continue.
 */

STDMETHODIMP CDropSource::QueryContinueDrag(BOOL fEsc
    , DWORD grfKeyState)
    {
    if (fEsc)
        return DRAGDROP_S_CANCEL;

    if (!(grfKeyState & MK_LBUTTON))
        return DRAGDROP_S_DROP;

    return NOERROR;
    }

/*
 * CDropSource::GiveFeedback
 *
 * Purpose:
 *  Provides cursor feedback to the user since the source task
 *  always has the mouse capture.  We can also provide any other
 *  type of feedback above cursors if we so desire.
 *
 * Parameters:
 *  dwEffect        DWORD effect flags returned from the last target
 *
 * Return Value:
 *  HRESULT         NOERROR if you set a cursor yourself or
 *                  DRAGDROP_S_USEDEFAULTCURSORS to let OLE do
 *                  the work.
 */

STDMETHODIMP CDropSource::GiveFeedback(DWORD dwEffect)
    {
    return DRAGDROP_S_USEDEFAULTCURSORS;
    }


IDROPSRC.H

/*
 * IDROPSRC.H
 *
 * Definitions and function prototypes for a template
 * DropSrouce object.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IDROPSRC_H_
#define _IDROPSRC_H_

class CDropSource;
typedef class CDropSource *PCDropSource;

class CDropSource : public IDropSource
    {
    protected:
        ULONG           m_cRef;     //Interface reference count
        LPVOID          m_pBack;    //Back pointer

    public:
        CDropSource(LPVOID);
        ~CDropSource(void);

        //IDropSource interface members
        STDMETHODIMP QueryInterface(REFIID, LPVOID );
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP QueryContinueDrag(BOOL, DWORD);
        STDMETHODIMP GiveFeedback(DWORD);
    };

#endif //_IDROPSRC_H_


IDROPTGT.CPP

/*
 * IDROPTGT.CPP
 *
 * Template implementation of a DropTarget object.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "idroptgt.h"

/*
 * CDropTarget::CDropTarget
 * CDropTarget::~CDropTarget
 *
 * Constructor Parameters:
 *  pBack           LPVOID back pointer to whatever we're related to.
 */

CDropTarget::CDropTarget(LPVOID pBack)
    {
    m_cRef=0;
    m_pBack=pBack;
    return;
    }

CDropTarget::~CDropTarget(void)
    {
    return;
    }

/*
 * CDropTarget::QueryInterface
 * CDropTarget::AddRef
 * CDropTarget::Release
 *
 * Purpose:
 *  Non-delegating IUnknown members for CDropTarget.
 */

STDMETHODIMP CDropTarget::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    //Any interface on this object is the object pointer.
    if (IsEqualIID(riid, IID_IUnknown)
        || IsEqualIID(riid, IID_IDropTarget))
        *ppv=(LPVOID)this;

    /*
     * If we actually assign an interface to ppv we need to AddRef
     * it since we're returning a new pointer.
     */
    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return E_NOINTERFACE;
    }

STDMETHODIMP_(ULONG) CDropTarget::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropTarget::Release(void)
    {
    ULONG   cRefT;

    cRefT=--m_cRef;

    if (0L==m_cRef)
        delete this;

    return cRefT;
    }

/*
 * CDropTarget::DragEnter
 *
 * Purpose:
 *  Indicates that data in a drag operation has been dragged over
 *  our window that's a potential target.  We are to decide if it's
 *  something in which we're interested.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT providing the source data.
 *  grfKeyState     DWORD flags: states of keys and mouse buttons.
 *  pt              POINTL coordinates in the client space of
 *                  the document.
 *  pdwEffect       LPDWORD into which we'll place the appropriate
 *                  effect flag for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragEnter(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    *pdwEffect=DROPEFFECT_NONE;
    return NOERROR;
    }

/*
 * CDropTarget::DragOver
 *
 * Purpose:
 *  Indicates that the mouse was moved inside the window represented
 *  by this drop target.  This happens on every WM_MOUSEMOVE, so
 *  this function should be very efficient.
 *
 * Parameters:
 *  grfKeyState     DWORD providing the current keyboard and
 *                  mouse states
 *  pt              POINTL where the mouse currently is.
 *  pdwEffect       LPDWORD in which to store the effect flag
 *                  for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragOver(DWORD grfKeyState, POINTL pt
    , LPDWORD pdwEffect)
    {
    //Since we can always drop, we just return effect flags
    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        *pdwEffect=DROPEFFECT_COPY;

    return NOERROR;
    }

/*
 * CDropTarget::DragLeave
 *
 * Purpose:
 *  Informs the drop target that the operation has left its window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragLeave(void)
    {
    return NOERROR;
    }

/*
 * CDropTarget::Drop
 *
 * Purpose:
 *  Instructs the drop target to paste the data that was just now
 *  dropped on it.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT from which we'll paste.
 *  grfKeyState     DWORD providing current keyboard/mouse state.
 *  pt              POINTL at which the drop occurred.
 *  pdwEffect       LPDWORD in which to store what you did.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::Drop(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    return NOERROR;
    }


IDROPTGT.H

/*
 * IDROPTGT.H
 *
 * Definitions and function prototypes for a template
 * DropTarget object.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IDROPTGT_H_
#define _IDROPTGT_H_

class CDropTarget;
typedef class CDropTarget *PCDropTarget;

class CDropTarget : public IDropTarget
    {
    protected:
        ULONG           m_cRef;      //Interface reference count.
        LPVOID          m_pBack;     //Back pointer

    public:
        CDropTarget(LPVOID);
        ~CDropTarget(void);

        //IDropTarget interface members
        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP DragEnter(LPDATAOBJECT, DWORD, POINTL
            , LPDWORD);
        STDMETHODIMP DragOver(DWORD, POINTL, LPDWORD);
        STDMETHODIMP DragLeave(void);
        STDMETHODIMP Drop(LPDATAOBJECT, DWORD, POINTL, LPDWORD);
    };

#endif //_IDROPTGT_H_


IENUMCON.CPP

/*
 * IENUMCON.CPP
 *
 * Standard implementation of an enumerator with the
 * IEnumConnections interface that will generally not need
 * modification.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "ienumcon.h"

/*
 * CEnumConnections::CEnumConnections
 * CEnumConnections::~CEnumConnections
 *
 * Parameters (Constructor):
 *  pUnkRef         LPUNKNOWN to use for reference counting.
 *  cConn           ULONG number of connections in prgpConn
 *  prgConnData     LPCONNECTDATA to the array to enumerate.
 */

CEnumConnections::CEnumConnections(LPUNKNOWN pUnkRef, ULONG cConn
    , LPCONNECTDATA prgConnData)
    {
    UINT        i;

    m_cRef=0;
    m_pUnkRef=pUnkRef;

    m_iCur=0;
    m_cConn=cConn;
    m_rgConnData=new CONNECTDATA[(UINT)cConn];

    if (NULL!=m_rgConnData)
        {
        for (i=0; i < cConn; i++)
            m_rgConnData[i]=prgConnData[i];
        }

    return;
    }

CEnumConnections::~CEnumConnections(void)
    {
    if (NULL!=m_rgConnData)
        delete [] m_rgConnData;

    return;
    }

/*
 * CEnumConnections::QueryInterface
 * CEnumConnections::AddRef
 * CEnumConnections::Release
 *
 * Purpose:
 *  IUnknown members for CEnumConnections object.
 */

STDMETHODIMP CEnumConnections::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    /*
     * Enumerators are separate objects, so we only need to support
     * ou IUnknown and IEnumConnections interfaces here with no
     * concern for aggregation.
     */
    if (IID_IUnknown==riid || IID_IEnumConnections==riid)
        *ppv=(LPVOID)this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CEnumConnections::AddRef(void)
    {
    ++m_cRef;
    m_pUnkRef->AddRef();
    return m_cRef;
    }

STDMETHODIMP_(ULONG) CEnumConnections::Release(void)
    {
    m_pUnkRef->Release();

    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CEnumConnections::Next
 *
 * Purpose:
 *  Returns the next element in the enumeration.
 *
 * Parameters:
 *  cConn           ULONG number of connections to return.
 *  pConnData       LPCONNECTDATA in which to store the returned
 *                  structures.
 *  pulEnum         ULONG * in which to return how many we
 *                  enumerated.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE otherwise,
 */

STDMETHODIMP CEnumConnections::Next(ULONG cConn
    , LPCONNECTDATA pConnData, ULONG *pulEnum)
    {
    ULONG               cReturn=0L;

    if (NULL==m_rgConnData)
        return ResultFromScode(S_FALSE);

    if (NULL==pulEnum)
        {
        if (1L!=cConn)
            return ResultFromScode(E_POINTER);
        }
    else
        *pulEnum=0L;

    if (NULL==pConnData || m_iCur >= m_cConn)
        return ResultFromScode(S_FALSE);

    while (m_iCur < m_cConn && cConn > 0)
        {
        *pConnData++=m_rgConnData[m_iCur];
        m_rgConnData[m_iCur++].pUnk->AddRef;
        cReturn++;
        cConn--;
        }

    if (NULL!=pulEnum)
        *pulEnum=cReturn;

    return NOERROR;
    }

/*
 * CEnumConnections::Skip
 *
 * Purpose:
 *  Skips the next n elements in the enumeration.
 *
 * Parameters:
 *  cSkip           ULONG number of elements to skip.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE if we could not
 *                  skip the requested number.
 */

STDMETHODIMP CEnumConnections::Skip(ULONG cSkip)
    {
    if (((m_iCur+cSkip) >= m_cConn) || NULL==m_rgConnData)
        return ResultFromScode(S_FALSE);

    m_iCur+=cSkip;
    return NOERROR;
    }

/*
 * CEnumConnections::Reset
 *
 * Purpose:
 *  Resets the current element index in the enumeration to zero.
 *
 * Parameters:
 *  None
 */

STDMETHODIMP CEnumConnections::Reset(void)
    {
    m_iCur=0;
    return NOERROR;
    }

/*
 * CEnumConnections::Clone
 *
 * Purpose:
 *  Returns another IEnumConnections with the same state as ourselves.
 *
 * Parameters:
 *  ppEnum          LPENUMCONNECTIONPOINTS * in which to return the
 *                  new object.
 */

STDMETHODIMP CEnumConnections::Clone(LPENUMCONNECTIONS *ppEnum)
    {
    PCEnumConnections   pNew;

    *ppEnum=NULL;

    //Create the clone
    pNew=new CEnumConnections(m_pUnkRef, m_cConn, m_rgConnData);

    if (NULL==pNew)
        return ResultFromScode(E_OUTOFMEMORY);

    pNew->AddRef();
    pNew->m_iCur=m_iCur;

    *ppEnum=pNew;
    return NOERROR;
    }


IENUMCON.H

/*
 * IENUMCON.H
 *
 * Definitions of a template IEnumConnections enumerator.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IENUMCON_H_
#define _IENUMCON_H_

#define INC_CONTROLS
#include <inole.h>

class CEnumConnections;
typedef class CEnumConnections *PCEnumConnections;

class CEnumConnections : public IEnumConnections
    {
    private:
        ULONG           m_cRef;       //Object reference count
        LPUNKNOWN       m_pUnkRef;    //IUnknown for ref counting
        ULONG           m_iCur;       //Current element
        ULONG           m_cConn;      //Number of connections
        LPCONNECTDATA   m_rgConnData; //Source of connections

    public:
        CEnumConnections(LPUNKNOWN, ULONG, LPCONNECTDATA);
        ~CEnumConnections(void);

        //IUnknown members that delegate to m_pUnkRef.
        STDMETHODIMP         QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IEnumConnections members
        STDMETHODIMP Next(ULONG, LPCONNECTDATA, ULONG *);
        STDMETHODIMP Skip(ULONG);
        STDMETHODIMP Reset(void);
        STDMETHODIMP Clone(IEnumConnections **);
    };

#endif //_IENUMCON_H_


IENUMCP.CPP

/*
 * IENUMCP.CPP
 *
 * Standard implementation of an IConnectionPoint enumerator with the
 * IEnumConnectionPoints interface that will generally not need
 * modification.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "ienumcp.h"

/*
 * CEnumConnectionPoints::CEnumConnectionPoints
 * CEnumConnectionPoints::~CEnumConnectionPoints
 *
 * Parameters (Constructor):
 *  pUnkRef         LPUNKNOWN to use for reference counting.
 *  cPoints         ULONG number of connection points in rgpCP
 *  rgpCP           LPCONNECTIONPOINT * to the array to enumerate.
 */

CEnumConnectionPoints::CEnumConnectionPoints(LPUNKNOWN pUnkRef
    , ULONG cPoints, LPCONNECTIONPOINT *rgpCP)
    {
    UINT        i;

    m_cRef=0;
    m_pUnkRef=pUnkRef;

    m_iCur=0;
    m_cPoints=cPoints;
    m_rgpCP=new LPCONNECTIONPOINT[(UINT)cPoints];

    if (NULL!=m_rgpCP)
        {
        for (i=0; i < cPoints; i++)
            m_rgpCP[i]=rgpCP[i];
        }

    return;
    }

CEnumConnectionPoints::~CEnumConnectionPoints(void)
    {
    if (NULL!=m_rgpCP)
        delete [] m_rgpCP;

    return;
    }

/*
 * CEnumConnectionPoints::QueryInterface
 * CEnumConnectionPoints::AddRef
 * CEnumConnectionPoints::Release
 *
 * Purpose:
 *  IUnknown members for CEnumConnectionPoints object.
 */

STDMETHODIMP CEnumConnectionPoints::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    /*
     * Enumerators are separate objects, not the data object, so
     * we only need to support out IUnknown and IEnumConnectionPoints
     * interfaces here with no concern for aggregation.
     */
    if (IID_IUnknown==riid || IID_IEnumConnectionPoints==riid)
        *ppv=(LPVOID)this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }

STDMETHODIMP_(ULONG) CEnumConnectionPoints::AddRef(void)
    {
    ++m_cRef;
    m_pUnkRef->AddRef();
    return m_cRef;
    }

STDMETHODIMP_(ULONG) CEnumConnectionPoints::Release(void)
    {
    m_pUnkRef->Release();

    if (0L!=--m_cRef)
        return m_cRef;

    delete this;
    return 0;
    }

/*
 * CEnumConnectionPoints::Next
 *
 * Purpose:
 *  Returns the next element in the enumeration.
 *
 * Parameters:
 *  cPoints         ULONG number of connection points to return.
 *  pCP             LPCONNECTIONPOINT in which to store the returned
 *                  pointers.
 *  pulEnum         ULONG * in which to return how many we
 *                  enumerated.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE otherwise,
 */

STDMETHODIMP CEnumConnectionPoints::Next(ULONG cPoints
    , LPCONNECTIONPOINT *pCP, ULONG *pulEnum)
    {
    ULONG               cReturn=0L;

    if (NULL==m_rgpCP)
        return ResultFromScode(S_FALSE);

    if (NULL==pulEnum)
        {
        if (1L!=cPoints)
            return ResultFromScode(E_POINTER);
        }
    else
        *pulEnum=0L;

    if (NULL==pCP || m_iCur >= m_cPoints)
        return ResultFromScode(S_FALSE);

    while (m_iCur < m_cPoints && cPoints > 0)
        {
        *pCP=m_rgpCP[m_iCur++];

        if (NULL!=*pCP)
            (*pCP)->AddRef();

        pCP++;
        cReturn++;
        cPoints--;
        }

    if (NULL!=pulEnum)
        *pulEnum=cReturn;

    return NOERROR;
    }

/*
 * CEnumConnectionPoints::Skip
 *
 * Purpose:
 *  Skips the next n elements in the enumeration.
 *
 * Parameters:
 *  cSkip           ULONG number of elements to skip.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE if we could not
 *                  skip the requested number.
 */

STDMETHODIMP CEnumConnectionPoints::Skip(ULONG cSkip)
    {
    if (((m_iCur+cSkip) >= m_cPoints) || NULL==m_rgpCP)
        return ResultFromScode(S_FALSE);

    m_iCur+=cSkip;
    return NOERROR;
    }

/*
 * CEnumConnectionPoints::Reset
 *
 * Purpose:
 *  Resets the current element index in the enumeration to zero.
 *
 * Parameters:
 *  None
 */

STDMETHODIMP CEnumConnectionPoints::Reset(void)
    {
    m_iCur=0;
    return NOERROR;
    }

/*
 * CEnumConnectionPoints::Clone
 *
 * Purpose:
 *  Returns another IEnumConnectionPoints with the same state as ourselves.
 *
 * Parameters:
 *  ppEnum          LPENUMCONNECTIONPOINTS * in which to return the
 *                  new object.
 */

STDMETHODIMP CEnumConnectionPoints::Clone
    (LPENUMCONNECTIONPOINTS *ppEnum)
    {
    PCEnumConnectionPoints   pNew;

    *ppEnum=NULL;

    //Create the clone
    pNew=new CEnumConnectionPoints(m_pUnkRef, m_cPoints, m_rgpCP);

    if (NULL==pNew)
        return ResultFromScode(E_OUTOFMEMORY);

    pNew->AddRef();
    pNew->m_iCur=m_iCur;

    *ppEnum=pNew;
    return NOERROR;
    }


IENUMCP.H

/*
 * IENUMCP.H
 *
 * Definitions of a template IEnumConnectionPoints enumerator.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IENUMCP_H_
#define _IENUMCP_H_

#define INC_CONTROLS
#include <inole.h>

class CEnumConnectionPoints;
typedef class CEnumConnectionPoints *PCEnumConnectionPoints;

class CEnumConnectionPoints : public IEnumConnectionPoints
    {
    private:
        ULONG           m_cRef;     //Object reference count
        LPUNKNOWN       m_pUnkRef;  //IUnknown for ref counting
        ULONG           m_iCur;     //Current element
        ULONG           m_cPoints;  //Number of conn points
        LPCONNECTIONPOINT *m_rgpCP; //Source of conn points

    public:
        CEnumConnectionPoints(LPUNKNOWN, ULONG, LPCONNECTIONPOINT *);
        ~CEnumConnectionPoints(void);

        //IUnknown members that delegate to m_pUnkRef.
        STDMETHODIMP         QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IEnumConnectionPoints members
        STDMETHODIMP Next(ULONG, LPCONNECTIONPOINT *, ULONG *);
        STDMETHODIMP Skip(ULONG);
        STDMETHODIMP Reset(void);
        STDMETHODIMP Clone(IEnumConnectionPoints **);
    };

#endif //_IENUMCP_H_


IENUMFE.CPP

/*
 * IENUMFE.CPP
 *
 * Standard implementation of a FORMATETC enumerator with the
 * IEnumFORMATETC interface that will generally not need
 * modification.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "ienumfe.h"

/*
 * CEnumFormatEtc::CEnumFormatEtc
 * CEnumFormatEtc::~CEnumFormatEtc
 *
 * Parameters (Constructor):
 *  pUnkRef         LPUNKNOWN to use for reference counting.
 *  cFE             ULONG number of FORMATETCs in pFE
 *  prgFE           LPFORMATETC to the array to enumerate.
 */

CEnumFormatEtc::CEnumFormatEtc(LPUNKNOWN pUnkRef, ULONG cFE
    , LPFORMATETC prgFE)
    {
    UINT        i;

    m_cRef=0;
    m_pUnkRef=pUnkRef;

    m_iCur=0;
    m_cfe=cFE;
    m_prgfe=new FORMATETC[(UINT)cFE];

    if (NULL!=m_prgfe)
        {
        for (i=0; i < cFE; i++)
            m_prgfe[i]=prgFE[i];
        }

    return;
    }

CEnumFormatEtc::~CEnumFormatEtc(void)
    {
    if (NULL!=m_prgfe)
        delete [] m_prgfe;

    return;
    }

/*
 * CEnumFormatEtc::QueryInterface
 * CEnumFormatEtc::AddRef
 * CEnumFormatEtc::Release
 *
 * Purpose:
 *  IUnknown members for CEnumFormatEtc object.  For QueryInterface
 *  we only return out own interfaces and not those of the data
 *  object.  However, since enumerating formats only makes sense
 *  when the data object is around, we insure that it stays as
 *  long as we stay by calling an outer IUnknown for AddRef
 *  and Release.  But since we are not controlled by the lifetime
 *  of the outer object, we still keep our own reference count in
 *  order to free ourselves.
 */

STDMETHODIMP CEnumFormatEtc::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    /*
     * Enumerators are separate objects, not the data object, so
     * we only need to support out IUnknown and IEnumFORMATETC
     * interfaces here with no concern for aggregation.
     */
    if (IsEqualIID(riid, IID_IUnknown)
        || IsEqualIID(riid, IID_IEnumFORMATETC))
        *ppv=(LPVOID)this;

    //AddRef any interface we'll return.
    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return E_NOINTERFACE;
    }

STDMETHODIMP_(ULONG) CEnumFormatEtc::AddRef(void)
    {
    ++m_cRef;
    m_pUnkRef->AddRef();
    return m_cRef;
    }

STDMETHODIMP_(ULONG) CEnumFormatEtc::Release(void)
    {
    ULONG       cRefT;

    cRefT=--m_cRef;

    m_pUnkRef->Release();

    if (0L==m_cRef)
        delete this;

    return cRefT;
    }

/*
 * CEnumFormatEtc::Next
 *
 * Purpose:
 *  Returns the next element in the enumeration.
 *
 * Parameters:
 *  cFE             ULONG number of FORMATETCs to return.
 *  pFE             LPFORMATETC in which to store the returned
 *                  structures.
 *  pulFE           ULONG * in which to return how many we
 *                  enumerated.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE otherwise,
 */

STDMETHODIMP CEnumFormatEtc::Next(ULONG cFE, LPFORMATETC pFE
    , ULONG * pulFE)
    {
    ULONG               cReturn=0L;

    if (NULL==m_prgfe)
        return S_FALSE;

    if (NULL==pulFE)
        {
        if (1L!=cFE)
            return E_POINTER;
        }
    else
        *pulFE=0L;

    if (NULL==pFE || m_iCur >= m_cfe)
        return S_FALSE;

    while (m_iCur < m_cfe && cFE > 0)
        {
        *pFE++=m_prgfe[m_iCur++];
        cReturn++;
        cFE--;
        }

    if (NULL!=pulFE)
        *pulFE=cReturn;

    return NOERROR;
    }

/*
 * CEnumFormatEtc::Skip
 *
 * Purpose:
 *  Skips the next n elements in the enumeration.
 *
 * Parameters:
 *  cSkip           ULONG number of elements to skip.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE if we could not
 *                  skip the requested number.
 */

STDMETHODIMP CEnumFormatEtc::Skip(ULONG cSkip)
    {
    if (((m_iCur+cSkip) >= m_cfe) || NULL==m_prgfe)
        return S_FALSE;

    m_iCur+=cSkip;
    return NOERROR;
    }

/*
 * CEnumFormatEtc::Reset
 *
 * Purpose:
 *  Resets the current element index in the enumeration to zero.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CEnumFormatEtc::Reset(void)
    {
    m_iCur=0;
    return NOERROR;
    }

/*
 * CEnumFormatEtc::Clone
 *
 * Purpose:
 *  Returns another IEnumFORMATETC with the same state as ourselves.
 *
 * Parameters:
 *  ppEnum          LPENUMFORMATETC * in which to return the
 *                  new object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CEnumFormatEtc::Clone(LPENUMFORMATETC *ppEnum)
    {
    PCEnumFormatEtc pNew;

    *ppEnum=NULL;

    //Create the clone
    pNew=new CEnumFormatEtc(m_pUnkRef, m_cfe, m_prgfe);

    if (NULL==pNew)
        return E_OUTOFMEMORY;

    pNew->AddRef();
    pNew->m_iCur=m_iCur;

    *ppEnum=pNew;
    return NOERROR;
    }


IENUMFE.H

/*
 * IENUMFE.H
 *
 * Definitions of a FORMATETC enumerator.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IENUMFE_H_
#define _IENUMFE_H_

/*
 * IEnumFORMATETC object that is created from
 * IDataObject::EnumFormatEtc.  This object lives on its own,
 * that is, QueryInterface only knows IUnknown and IEnumFormatEtc,
 * nothing more.  We still use an outer unknown for reference
 * counting, because as long as this enumerator lives, the data
 * object should live, thereby keeping the application up.
 */

class CEnumFormatEtc;
typedef class CEnumFormatEtc *PCEnumFormatEtc;

class CEnumFormatEtc : public IEnumFORMATETC
    {
    private:
        ULONG           m_cRef;     //Object reference count
        LPUNKNOWN       m_pUnkRef;  //IUnknown for ref counting
        ULONG           m_iCur;     //Current element
        ULONG           m_cfe;      //Number of FORMATETCs in us
        LPFORMATETC     m_prgfe;    //Source of FORMATETCs

    public:
        CEnumFormatEtc(LPUNKNOWN, ULONG, LPFORMATETC);
        ~CEnumFormatEtc(void);

        //IUnknown members that delegate to m_pUnkRef.
        STDMETHODIMP         QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IEnumFORMATETC members
        STDMETHODIMP Next(ULONG, LPFORMATETC, ULONG *);
        STDMETHODIMP Skip(ULONG);
        STDMETHODIMP Reset(void);
        STDMETHODIMP Clone(IEnumFORMATETC **);
    };

#endif //_IENUMFE_H_


IENUMSTR.CPP

/*
 * IENUMSTR.CPP
 *
 * Standard implementation of a string enumerator with the
 * IEnumString interface that will generally not need
 * modification.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "ienumstr.h"

/*
 * CEnumString::CEnumString
 * CEnumString::~CEnumString
 *
 * Parameters (Constructor):
 *  pUnkRef         LPUNKNOWN to use for reference counting.
 *  cstr            ULONG number of LPTSTRs in pstr
 *  prgstr          LPTSTR to the array to enumerate.
 */

CEnumString::CEnumString(LPUNKNOWN pUnkRef, ULONG cstr
    , LPTSTR prgstr)
    {
    UINT        i;

    m_cRef=0;
    m_pUnkRef=pUnkRef;

    m_iCur=0;
    m_cstr=cFE;
    m_prgstr=new LPTSTR[(UINT)cFE];

    if (NULL!=m_prgstr)
        {
        for (i=0; i < cFE; i++)
            m_prgstr[i]=prgFE[i];
        }

    return;
    }

CEnumString::~CEnumString(void)
    {
    if (NULL!=m_prgstr)
        delete [] m_prgstr;

    return;
    }

/*
 * CEnumString::QueryInterface
 * CEnumString::AddRef
 * CEnumString::Release
 *
 * Purpose:
 *  IUnknown members for CEnumString object.
 */

STDMETHODIMP CEnumString::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    if (IsEqualIID(riid, IID_IUnknown)
        || IsEqualIID(riid, IID_IEnumString))
        *ppv=(LPVOID)this;

    //AddRef any interface we'll return.
    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return E_NOINTERFACE;
    }

STDMETHODIMP_(ULONG) CEnumString::AddRef(void)
    {
    ++m_cRef;
    m_pUnkRef->AddRef();
    return m_cRef;
    }

STDMETHODIMP_(ULONG) CEnumString::Release(void)
    {
    ULONG       cRefT;

    cRefT=--m_cRef;

    m_pUnkRef->Release();

    if (0L==m_cRef)
        delete this;

    return cRefT;
    }

/*
 * CEnumString::Next
 *
 * Purpose:
 *  Returns the next element in the enumeration.
 *
 * Parameters:
 *  cstr            ULONG number of LPTSTRs to return.
 *  pstr            LPTSTR in which to store the returned
 *                  structures.
 *  pulstr          ULONG * in which to return how many we
 *                  enumerated.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE otherwise,
 */

STDMETHODIMP CEnumString::Next(ULONG cstr, LPTSTR pstr
    , ULONG * pulstr)
    {
    ULONG               cReturn=0L;

    if (NULL==m_prgstr)
        return S_FALSE;

    if (NULL==pulstr)
        {
        if (1L!=cstr)
            return E_POINTER;
        }
    else
        *pulstr=0L;

    if (NULL==pstr || m_iCur >= m_cstr)
        return S_FALSE;

    while (m_iCur < m_cstr && cstr > 0)
        {
        *pstr++=m_prgstr[m_iCur++];
        cReturn++;
        cstr--;
        }

    if (NULL!=pulstr)
        *pulstr=cReturn;

    return NOERROR;
    }

/*
 * CEnumString::Skip
 *
 * Purpose:
 *  Skips the next n elements in the enumeration.
 *
 * Parameters:
 *  cSkip           ULONG number of elements to skip.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE if we could not
 *                  skip the requested number.
 */

STDMETHODIMP CEnumString::Skip(ULONG cSkip)
    {
    if (((m_iCur+cSkip) >= m_cstr) || NULL==m_prgstr)
        return S_FALSE;

    m_iCur+=cSkip;
    return NOERROR;
    }

/*
 * CEnumString::Reset
 *
 * Purpose:
 *  Resets the current element index in the enumeration to zero.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CEnumString::Reset(void)
    {
    m_iCur=0;
    return NOERROR;
    }

/*
 * CEnumString::Clone
 *
 * Purpose:
 *  Returns another IEnumString with the same state as ourselves.
 *
 * Parameters:
 *  ppEnum          LPENUMSTRING * in which to return the
 *                  new object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CEnumString::Clone(LPENUMSTRING *ppEnum)
    {
    PCEnumString    pNew;

    *ppEnum=NULL;

    //Create the clone
    pNew=new CEnumString(m_pUnkRef, m_cstr, m_prgstr);

    if (NULL==pNew)
        return E_OUTOFMEMORY;

    pNew->AddRef();
    pNew->m_iCur=m_iCur;

    *ppEnum=pNew;
    return NOERROR;
    }


IENUMSTR.H

/*
 * IENUMSTR.H
 *
 * Definitions of an IEnumString object.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IENUMSTR_H_
#define _IENUMSTR_H_

class CEnumString;
typedef class CEnumString *PCEnumString;

class CEnumString : public IEnumString
    {
    private:
        ULONG           m_cRef;     //Object reference count
        LPUNKNOWN       m_pUnkRef;  //IUnknown for ref counting
        ULONG           m_iCur;     //Current element
        ULONG           m_cstr;     //Number of strings in us
        LPTSTR          m_prgstr;   //Source of strings

    public:
        CEnumString(LPUNKNOWN, ULONG, LPTSTR);
        ~CEnumString(void);

        //IUnknown members that delegate to m_pUnkRef.
        STDMETHODIMP         QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IEnumString members
        STDMETHODIMP Next(ULONG, LPTSTR *, ULONG *);
        STDMETHODIMP Skip(ULONG);
        STDMETHODIMP Reset(void);
        STDMETHODIMP Clone(IEnumString **);
    };

#endif //_IENUMSTR_H_


IENUMUNK.CPP

/*
 * IENUMUNK.CPP
 *
 * Standard implementation of am IUnknown enumerator with the
 * IEnumUnknown interface that will generally not need
 * modification.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "ienumunk.h"

/*
 * CEnumUnknown::CEnumUnknown
 * CEnumUnknown::~CEnumUnknown
 *
 * Parameters (Constructor):
 *  pUnkRef         LPUNKNOWN to use for reference counting.
 *  cUnk            ULONG number of LPUNKNOWNs in prgUnk
 *  prgUnk          LPUNKNOWN to the array to enumerate.
 */

CEnumUnknown::CEnumUnknown(LPUNKNOWN pUnkRef, ULONG cUnk
    , LPUNKNOWN prgUnk)
    {
    UINT        i;

    m_cRef=0;
    m_pUnkRef=pUnkRef;

    m_iCur=0;
    m_cUnk=cUnk;
    m_prgUnk=new LPUNKNOWN[(UINT)cUnk];

    if (NULL!=m_prgUnk)
        {
        for (i=0; i < cUnk; i++)
            m_prgUnk[i]=prgUnk[i];
        }

    return;
    }

CEnumUnknown::~CEnumUnknown(void)
    {
    if (NULL!=m_prgUnk)
        delete [] m_prgUnk;

    return;
    }

/*
 * CEnumUnknown::QueryInterface
 * CEnumUnknown::AddRef
 * CEnumUnknown::Release
 *
 * Purpose:
 *  IUnknown members for CEnumUnknown object.
 */

STDMETHODIMP CEnumUnknown::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    /*
     * Enumerators are separate objects, not the data object, so
     * we only need to support out IUnknown and IEnumUnknown
     * interfaces here with no concern for aggregation.
     */
    if (IsEqualIID(riid, IID_IUnknown)
        || IsEqualIID(riid, IID_IEnumUnknown))
        *ppv=(LPVOID)this;

    //AddRef any interface we'll return.
    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return E_NOINTERFACE;
    }

STDMETHODIMP_(ULONG) CEnumUnknown::AddRef(void)
    {
    ++m_cRef;
    m_pUnkRef->AddRef();
    return m_cRef;
    }

STDMETHODIMP_(ULONG) CEnumUnknown::Release(void)
    {
    ULONG       cRefT;

    cRefT=--m_cRef;

    m_pUnkRef->Release();

    if (0L==m_cRef)
        delete this;

    return cRefT;
    }

/*
 * CEnumUnknown::Next
 *
 * Purpose:
 *  Returns the next element in the enumeration.
 *
 * Parameters:
 *  cUnk            ULONG number of LPUNKNOWNs to return.
 *  pUnk            LPUNKNOWN in which to store the returned
 *                  structures.
 *  pulUnk          ULONG * in which to return how many we
 *                  enumerated.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE otherwise,
 */

STDMETHODIMP CEnumUnknown::Next(ULONG cUnk, LPUNKNOWN pUnk
    , ULONG * pulUnk)
    {
    ULONG               cReturn=0L;

    if (NULL==m_prgUnk)
        return S_FALSE;

    if (NULL==pulUnk)
        {
        if (1L!=cUnk)
            return E_POINTER;
        }
    else
        *pulUnk=0L;

    if (NULL==pUnk || m_iCur >= m_cUnk)
        return S_FALSE;

    while (m_iCur < m_cUnk && cUnk > 0)
        {
        *pUnk++=m_prgUnk[m_iCur++];
        cReturn++;
        cUnk--;
        }

    if (NULL!=pulUnk)
        *pulUnk=cReturn;

    return NOERROR;
    }

/*
 * CEnumUnknown::Skip
 *
 * Purpose:
 *  Skips the next n elements in the enumeration.
 *
 * Parameters:
 *  cSkip           ULONG number of elements to skip.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE if we could not
 *                  skip the requested number.
 */

STDMETHODIMP CEnumUnknown::Skip(ULONG cSkip)
    {
    if (((m_iCur+cSkip) >= m_cUnk) || NULL==m_prgUnk)
        return S_FALSE;

    m_iCur+=cSkip;
    return NOERROR;
    }

/*
 * CEnumUnknown::Reset
 *
 * Purpose:
 *  Resets the current element index in the enumeration to zero.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CEnumUnknown::Reset(void)
    {
    m_iCur=0;
    return NOERROR;
    }

/*
 * CEnumUnknown::Clone
 *
 * Purpose:
 *  Returns another IEnumUnknown with the same state as ourselves.
 *
 * Parameters:
 *  ppEnum          LPENUMUNKNOWN * in which to return the
 *                  new object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CEnumUnknown::Clone(LPENUMUNKNOWN *ppEnum)
    {
    PCEnumUnknown   pNew;

    *ppEnum=NULL;

    //Create the clone
    pNew=new CEnumUnknown(m_pUnkRef, m_cUnk, m_prgUnk);

    if (NULL==pNew)
        return E_OUTOFMEMORY;

    pNew->AddRef();
    pNew->m_iCur=m_iCur;

    *ppEnum=pNew;
    return NOERROR;
    }


IENUMUNK.H

/*
 * IENUMUNK.H
 *
 * Definitions of a template IEnumUnknown enumerator.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IENUMUNK_H_
#define _IENUMUNK_H_

class CEnumUnknown;
typedef class CEnumUnknown *PCEnumUnknown;

class CEnumUnknown : public IEnumUnknown
    {
    private:
        ULONG           m_cRef;     //Object reference count
        LPUNKNOWN       m_pUnkRef;  //IUnknown for ref counting
        ULONG           m_iCur;     //Current element
        ULONG           m_cunk;     //Number of unknowns in us
        LPUNKNOWN       m_prgunk;   //Source of unknowns

    public:
        CEnumUnknown(LPUNKNOWN, ULONG, LPUNKNOWN);
        ~CEnumUnknown(void);

        //IUnknown members that delegate to m_pUnkRef.
        STDMETHODIMP         QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IEnumUnknown members
        STDMETHODIMP Next(ULONG, LPUNKNOWN *, ULONG *);
        STDMETHODIMP Skip(ULONG);
        STDMETHODIMP Reset(void);
        STDMETHODIMP Clone(IEnumUnknown **);
    };

#endif //_IENUMUNK_H_


IENUMVRB.CPP

/*
 * IENUMVRB.CPP
 *
 * Standard implementation of am OLEVERB enumerator with the
 * IEnumOLEVERB interface that will generally not need
 * modification.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "ienumfe.h"

/*
 * CEnumOLEVERB::CEnumOLEVERB
 * CEnumOLEVERB::~CEnumOLEVERB
 *
 * Parameters (Constructor):
 *  pUnkRef         LPUNKNOWN to use for reference counting.
 *  cVerb           ULONG number of OLEVERBs in prgVerb
 *  prgVerb         LPOLEVERB to the array to enumerate.
 */

CEnumOLEVERB::CEnumOLEVERB(LPUNKNOWN pUnkRef, ULONG cVerb
    , LPOLEVERB prgVerb)
    {
    UINT        i;

    m_cRef=0;
    m_pUnkRef=pUnkRef;

    m_iCur=0;
    m_cVerb=cVerb;
    m_prgVerb=new OLEVERB[(UINT)cVerb];

    if (NULL!=m_prgVerb)
        {
        for (i=0; i < cVerb; i++)
            m_prgVerb[i]=prgVerb[i];
        }

    return;
    }

CEnumOLEVERB::~CEnumOLEVERB(void)
    {
    if (NULL!=m_prgVerb)
        delete [] m_prgVerb;

    return;
    }

/*
 * CEnumOLEVERB::QueryInterface
 * CEnumOLEVERB::AddRef
 * CEnumOLEVERB::Release
 *
 * Purpose:
 *  IUnknown members for CEnumOLEVERB object.
 */

STDMETHODIMP CEnumOLEVERB::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    *ppv=NULL;

    if (IsEqualIID(riid, IID_IUnknown)
        || IsEqualIID(riid, IID_IEnumOLEVERB))
        *ppv=(LPVOID)this;

    //AddRef any interface we'll return.
    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return E_NOINTERFACE;
    }

STDMETHODIMP_(ULONG) CEnumOLEVERB::AddRef(void)
    {
    ++m_cRef;
    m_pUnkRef->AddRef();
    return m_cRef;
    }

STDMETHODIMP_(ULONG) CEnumOLEVERB::Release(void)
    {
    ULONG       cRefT;

    cRefT=--m_cRef;

    m_pUnkRef->Release();

    if (0L==m_cRef)
        delete this;

    return cRefT;
    }

/*
 * CEnumOLEVERB::Next
 *
 * Purpose:
 *  Returns the next element in the enumeration.
 *
 * Parameters:
 *  cVerb           ULONG number of OLEVERBs to return.
 *  pVerb           LPOLEVERB in which to store the returned
 *                  structures.
 *  pulVerb         ULONG * in which to return how many we
 *                  enumerated.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE otherwise,
 */

STDMETHODIMP CEnumOLEVERB::Next(ULONG cVerb, LPOLEVERB pVerb
    , ULONG * pulVerb)
    {
    ULONG               cReturn=0L;

    if (NULL==m_prgVerb)
        return S_FALSE;

    if (NULL==pulVerb)
        {
        if (1L!=cVerb)
            return E_POINTER;
        }
    else
        *pulVerb=0L;

    if (NULL==pVerb || m_iCur >= m_cVerb)
        return S_FALSE;

    while (m_iCur < m_cVerb && cVerb > 0)
        {
        *pVerb++=m_prgVerb[m_iCur++];
        cReturn++;
        cVerb--;
        }

    if (NULL!=pulVerb)
        *pulVerb=cReturn;

    return NOERROR;
    }

/*
 * CEnumOLEVERB::Skip
 *
 * Purpose:
 *  Skips the next n elements in the enumeration.
 *
 * Parameters:
 *  cSkip           ULONG number of elements to skip.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE if we could not
 *                  skip the requested number.
 */

STDMETHODIMP CEnumOLEVERB::Skip(ULONG cSkip)
    {
    if (((m_iCur+cSkip) >= m_cVerb) || NULL==m_prgVerb)
        return S_FALSE;

    m_iCur+=cSkip;
    return NOERROR;
    }

/*
 * CEnumOLEVERB::Reset
 *
 * Purpose:
 *  Resets the current element index in the enumeration to zero.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CEnumOLEVERB::Reset(void)
    {
    m_iCur=0;
    return NOERROR;
    }

/*
 * CEnumOLEVERB::Clone
 *
 * Purpose:
 *  Returns another IEnumOLEVERB with the same state as ourselves.
 *
 * Parameters:
 *  ppEnum          LPENUMOLEVERB * in which to return the
 *                  new object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CEnumOLEVERB::Clone(LPENUMOLEVERB *ppEnum)
    {
    PCEnumOLEVERB   pNew;

    *ppEnum=NULL;

    //Create the clone
    pNew=new CEnumOLEVERB(m_pUnkRef, m_cVerb, m_prgVerb);

    if (NULL==pNew)
        return E_OUTOFMEMORY;

    pNew->AddRef();
    pNew->m_iCur=m_iCur;

    *ppEnum=pNew;
    return NOERROR;
    }


IENUMVRB.H

/*
 * IENUMVRB.H
 *
 * Definitions of an IEnumOLEVERB object.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IENUMVRB_H_
#define _IENUMVRB_H_

class CEnumOLEVERB;
typedef class CEnumOLEVERB *PCEnumOLEVERB;

class CEnumOLEVERB : public IEnumOLEVERB
    {
    private:
        ULONG           m_cRef;     //Object reference count
        LPVARIANT       m_pUnkRef;  //IUnknown for ref counting
        ULONG           m_iCur;     //Current element
        ULONG           m_cVerb;    //Number of verbs in us
        LPOLEVERB       m_prgVerb;  //Source of verbs

    public:
        CEnumOLEVERB(LPUNKNOWN, ULONG, LPOLEVERB);
        ~CEnumOLEVERB(void);

        //IUnknown members that delegate to m_pUnkRef.
        STDMETHODIMP         QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IEnumOLEVERB members
        STDMETHODIMP Next(ULONG, LPOLEVERB *, ULONG *);
        STDMETHODIMP Skip(ULONG);
        STDMETHODIMP Reset(void);
        STDMETHODIMP Clone(IEnumOLEVERB **);
    };

#endif //_IENUMVRB_H_


IEXTCONN.CPP

/*
 * IEXTCONN.CPP
 *
 * Template IExternalConnection interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iextconn.h"

/*
 * CImpIExternalConnection::CImpIExternalConnection
 * CImpIExternalConnection::~CImpIExternalConnection
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIExternalConnection::CImpIExternalConnection(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIExternalConnection::~CImpIExternalConnection(void)
    {
    return;
    }

/*
 * CImpIExternalConnection::QueryInterface
 * CImpIExternalConnection::AddRef
 * CImpIExternalConnection::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIExternalConnection.
 */

STDMETHODIMP CImpIExternalConnection::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIExternalConnection::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIExternalConnection::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIExternalConnection::AddConnection
 *
 * Purpose:
 *  Informs the object that a strong connection has been made to it.
 *
 * Parameters:
 *  dwConn          DWORD identifying the type of connection, taken
 *                  from the EXTCONN enumeration.
 *  dwReserved      DWORD reserved.  This is used inside OLE and
 *                  should not be validated.
 *
 * Return Value:
 *  DWORD           The number of connection counts on the
 *                  object, used for debugging purposes only.
 */

STDMETHODIMP_(DWORD) CImpIExternalConnection::AddConnection
    (DWORD dwConn, DWORD dwReserved)
    {
    return 0L;
    }

/*
 * CImpIExternalConnection::ReleaseConnection
 *
 * Purpose:
 *  Informs an object that a connection has been taken away from
 *  it in which case the object may need to shut down.
 *
 * Parameters:
 *  dwConn              DWORD identifying the type of connection,
 *                      taken from the EXTCONN enumeration.
 *  dwReserved          DWORD reserved.  This is used inside OLE and
 *                      should not be validated.
 *  dwRerved            DWORD reserved
 *  fLastReleaseCloses  BOOL indicating if the last call to this
 *                      function should close the object.
 *
 * Return Value:
 *  DWORD           The number of remaining connection counts on
 *                  the object, used for debugging purposes only.
 */

STDMETHODIMP_(DWORD) CImpIExternalConnection::ReleaseConnection
    (DWORD dwConn, DWORD dwReserved, BOOL fLastReleaseCloses)
    {
    return 0L;
    }


IEXTCONN.H

/*
 * IEXTCONN.H
 *
 * Definitions of a template IExternalConnection interface
 * implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IEXTCONN_H_
#define _IEXTCONN_H_

class CImpIExternalConnection;
typedef class CImpIExternalConnection *PIMPIEXTERNALCONNECTION;

class CImpIExternalConnection : public IExternalConnection
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIExternalConnection(LPVOID, LPUNKNOWN);
        ~CImpIExternalConnection(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(DWORD) AddConnection(DWORD, DWORD);
        STDMETHODIMP_(DWORD) ReleaseConnection(DWORD, DWORD, BOOL);
    };

#endif //_IEXTCONN_H_


IIPAOBJ.CPP

/*
 * IIPAOBJ.CPP
 *
 * Template IOleInPlaceActiveObject interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iipaobj.h"

/*
 * CImpIOleInPlaceActiveObject::CImpIOleInPlaceActiveObject
 * CImpIOleInPlaceActiveObject::~CImpIOleInPlaceActiveObject
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleInPlaceActiveObject::CImpIOleInPlaceActiveObject(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleInPlaceActiveObject::~CImpIOleInPlaceActiveObject(void)
    {
    return;
    }

/*
 * CImpIOleInPlaceActiveObject::QueryInterface
 * CImpIOleInPlaceActiveObject::AddRef
 * CImpIOleInPlaceActiveObject::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIOleInPlaceActiveObject.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceActiveObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceActiveObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleInPlaceActiveObject::GetWindow
 *
 * Purpose:
 *  Retrieves the handle of the window associated with the object on
 *  which this interface is implemented.
 *
 * Parameters:
 *  phWnd           HWND * in which to store the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_FAIL if no window.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::GetWindow(HWND * phWnd)
    {
    return E_FAIL;
    }

/*
 * CImpIOleInPlaceActiveObject::ContextSensitiveHelp
 *
 * Purpose:
 *  Instructs the object on which this interface is implemented to
 *  enter or leave a context-sensitive help mode.
 *
 * Parameters:
 *  fEnterMode      BOOL TRUE to enter the mode, FALSE otherwise.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::ContextSensitiveHelp(
    BOOL fEnterMode)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleInPlaceActiveObject::TranslateAccelerator
 *
 * Purpose:
 *  Requests that the active in-place object translate the message
 *  given in pMSG if appropriate.  This is only called for DLL
 *  servers where the container's message loop is running.  EXE servers
 *  have control of the message loop so this will not be called in
 *  such cases.
 *
 * Parameters:
 *  pMSG            LPMSG to the message to translate.
 *
 * Return Value:
 *  HRESULT         NOERROR if translates, S_FALSE if not.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::TranslateAccelerator(
    LPMSG pMSG)
    {
    return S_FALSE;
    }

/*
 * CImpIOleInPlaceActiveObject::OnFrameWindowActivate
 *
 * Purpose:
 *  Informs the in-place object that the container's frame window
 *  was either activated or deactivated.  The object doesn't need
 *  to do anything in OLE 2.0.
 *
 * Parameters:
 *  fActivate       BOOL TRUE if frame is active, FALSE otherwise.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::OnFrameWindowActivate(
    BOOL fActivate)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleInPlaceActiveObject::OnDocWindowActivate
 *
 * Purpose:
 *  Informs the in-place object that the document window in the
 *  container is either becoming active or deactive.  On this call
 *  the object must either add or remove frame-level tools,
 *  including the mixed menu, depending on fActivate.
 *
 * Parameters:
 *  fActivate       BOOL TRUE if document is active, FALSE otherwise
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::OnDocWindowActivate(
    BOOL fActivate)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleInPlaceActiveObject::ResizeBorder
 *
 * Purpose:
 *  Informs the object that the frame or document size changed in
 *  which case the object may need to resize any of its frame or
 *  document-level tools to match.
 *
 * Parameters:
 *  pRect           LPCRECT indicating the new size of the window
 *                  of interest.
 *
 *  pIUIWindow      LPOLEINPLACEUIWINDOW pointing to an
 *                  IOleInPlaceUIWindow interface on the container
 *                  object of interest.  We use this to do
 *                  border-space negotiation.
 *
 *  fFrame          BOOL indicating if the frame was resized
 *                  (TRUE) or the document (FALSE)
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::ResizeBorder(LPCRECT pRect
    , LPOLEINPLACEUIWINDOW pIUIWindow, BOOL fFrame)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleInPlaceActiveObject::EnableModeless
 *
 * Purpose:
 *  Instructs the object to show or hide any modeless popup windows
 *  that it may be using when activated in-place.
 *
 * Parameters:
 *  fEnable         BOOL indicating to enable/show the windows
 *                  (TRUE) or to hide them (FALSE).
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleInPlaceActiveObject::EnableModeless(
    BOOL fActivate)
    {
    return E_NOTIMPL;
    }


IIPAOBJ.H

/*
 * IIPAOBJ.H
 *
 * Definitions of a template IOleInPlaceActiveObject interface
 * implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IIPAOBJ_H_
#define _IIPAOBJ_H_

class CImpIOleInPlaceActiveObject;
typedef CImpIOleInPlaceActiveObject *PIMPIOLEINPLACEACTIVEOBJECT;

class CImpIOleInPlaceActiveObject
    : public IOleInPlaceActiveObject
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIOleInPlaceActiveObject(LPVOID, LPUNKNOWN);
        ~CImpIOleInPlaceActiveObject(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetWindow(HWND *);
        STDMETHODIMP ContextSensitiveHelp(BOOL);
        STDMETHODIMP TranslateAccelerator(LPMSG);
        STDMETHODIMP OnFrameWindowActivate(BOOL);
        STDMETHODIMP OnDocWindowActivate(BOOL);
        STDMETHODIMP ResizeBorder(LPCRECT, LPOLEINPLACEUIWINDOW
            , BOOL);
        STDMETHODIMP EnableModeless(BOOL);
    };

#endif //_IIPAOBJ_H_


IIPFRAME.CPP

/*
 * IIPFRAME.CPP
 *
 * Template IOleInPlaceFrame interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iipframe.h"

/*
 * CImpIOleInPlaceFrame::CImpIOleInPlaceFrame
 * CImpIOleInPlaceFrame::~CImpIOleInPlaceFrame
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleInPlaceFrame::CImpIOleInPlaceFrame(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleInPlaceFrame::~CImpIOleInPlaceFrame(void)
    {
    return;
    }

/*
 * CImpIOleInPlaceFrame::QueryInterface
 * CImpIOleInPlaceFrame::AddRef
 * CImpIOleInPlaceFrame::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIOleInPlaceFrame.
 */

STDMETHODIMP CImpIOleInPlaceFrame::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceFrame::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceFrame::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleInPlaceFrame::GetWindow
 *
 * Purpose:
 *  Retrieves the handle of the window associated with the object
 *  on which this interface is implemented.
 *
 * Parameters:
 *  phWnd           HWND * in which to store the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_FAIL if no window.
 */

STDMETHODIMP CImpIOleInPlaceFrame::GetWindow(HWND * phWnd)
    {
    return E_FAIL;
    }

/*
 * CImpIOleInPlaceFrame::ContextSensitiveHelp
 *
 * Purpose:
 *  Instructs the object on which this interface is implemented to
 *  enter or leave a context-sensitive help mode.
 *
 * Parameters:
 *  fEnterMode      BOOL TRUE to enter the mode, FALSE otherwise.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceFrame::ContextSensitiveHelp(
    BOOL fEnterMode)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceFrame::GetBorder
 *
 * Purpose:
 *  Returns the rectangle in which the container is willing to
 *  negotiate about an object's adornments.
 *
 * Parameters:
 *  prcBorder       LPRECT in which to store the rectangle.
 *
 * Return Value:
 *  HRESULT         NOERROR if all is well, INPLACE_E_NOTOOLSPACE
 *                  if there is no negotiable space.
 */

STDMETHODIMP CImpIOleInPlaceFrame::GetBorder(LPRECT prcBorder)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceFrame::RequestBorderSpace
 *
 * Purpose:
 *  Asks the container if it can surrender the amount of space
 *  in pBW that the object would like for it's adornments.  The
 *  container does nothing but validate the spaces on this call.
 *
 * Parameters:
 *  pBW             LPCBORDERWIDTHS containing the requested space.
 *                  The values are the amount of space requested
 *                  from each side of the relevant window.
 *
 * Return Value:
 *  HRESULT         NOERROR if we can give up space,
 *                  INPLACE_E_NOTOOLSPACE otherwise.
 */

STDMETHODIMP CImpIOleInPlaceFrame::RequestBorderSpace(
    LPCBORDERWIDTHS pBW)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceFrame::SetBorderSpace
 *
 * Purpose:
 *  Called when the object now officially requests that the
 *  container surrender border space it previously allowed
 *  in RequestBorderSpace.  The container should resize windows
 *  appropriately to surrender this space.
 *
 * Parameters:
 *  pBW             LPCBORDERWIDTHS containing the amount of space
 *                  from each side of the relevant window that the
 *                  object is now reserving.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceFrame::SetBorderSpace(
    LPCBORDERWIDTHS pBW)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceFrame::SetActiveObject
 *
 * Purpose:
 *  Provides the container with the object's IOleInPlaceActiveObject
 *  pointer and a name of the object to show in the container's
 *  caption.
 *
 * Parameters:
 *  pIIPActiveObj   LPOLEINPLACEACTIVEOBJECT of interest.
 *  pszObj          LPCSTR to use in the container's caption bar.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceFrame::SetActiveObject(
    LPOLEINPLACEACTIVEOBJECT pIIPActiveObj, LPCSTR pszObj)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceFrame::InsertMenus
 *
 * Purpose:
 *  Instructs the container to place its in-place menu items where
 *  necessary in the given menu and to fill in elements 0, 2, and 4
 *  of the OLEMENUGROUPWIDTHS array to indicate how many top-level
 *  items are in each group.
 *
 * Parameters:
 *  hMenu           HMENU in which to add popups.
 *  pMGW            LPOLEMENUGROUPWIDTHS in which to store the
 *                  width of each container menu group.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceFrame::InsertMenus(HMENU hMenu
    , LPOLEMENUGROUPWIDTHS pMGW)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceFrame::SetMenu
 *
 * Purpose:
 *  Instructs the container to replace whatever menu it's currently
 *  using with the given menu and to call OleSetMenuDescritor so
 *  OLE knows to whom to dispatch messages.
 *
 * Parameters:
 *  hMenu           HMENU to show.
 *  hOLEMenu        HOLEMENU to the menu descriptor.
 *  hWndObj         HWND of the active object to which messages
 *                  are dispatched.  Usually a frame window.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceFrame::SetMenu(HMENU hMenu
    , HOLEMENU hOLEMenu, HWND hWndObj)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceFrame::RemoveMenus
 *
 * Purpose:
 *  Asks the container to remove any menus it put into hMenu in
 *  InsertMenus.
 *
 * Parameters:
 *  hMenu           HMENU from which to remove the container's
 *                  items.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceFrame::RemoveMenus(HMENU hMenu)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceFrame::SetStatusText
 *
 * Purpose:
 *  Asks the container to place some text in a status line, if
 *  one exists.  If the container does not have a status line it
 *  should return E_FAIL here in which case the object could
 *  display its own.
 *
 * Parameters:
 *  pszText         LPCSTR to display.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_TRUNCATED if not all
 *                  of the text could be displayed, or E_FAIL if
 *                  the container has no status line.
 */

STDMETHODIMP CImpIOleInPlaceFrame::SetStatusText(LPCSTR pszText)
    {
    return E_FAIL;
    }

/*
 * CImpIOleInPlaceFrame::EnableModeless
 *
 * Purpose:
 *  Instructs the container to show or hide any modeless popup
 *  windows that it may be using.
 *
 * Parameters:
 *  fEnable         BOOL indicating to enable/show the windows
 *                  (TRUE) or to hide them (FALSE).
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceFrame::EnableModeless(BOOL fEnable)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceFrame::TranslateAccelerator
 *
 * Purpose:
 *  When dealing with an in-place object from an EXE server, this
 *  is called to give the container a chance to process accelerators
 *  after the server has looked at the message.
 *
 * Parameters:
 *  pMSG            LPMSG for the container to examine.
 *  wID             WORD identifier in the container's accelerator
 *                  table (from IOleInPlaceSite::GetWindowContext)
 *                  for this message (OLE does some translation
 *                  before calling).
 *
 * Return Value:
 *  HRESULT         NOERROR if the keystroke was used,
 *                  S_FALSE otherwise.
 */

STDMETHODIMP CImpIOleInPlaceFrame::TranslateAccelerator(LPMSG pMSG
    , WORD wID)
    {
    return S_FALSE;
    }


IIPFRAME.H

/*
 * IIPFRAME.H
 *
 * Definitions of a template IOleInPlaceFrame interface
 * implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IIPFRAME_H_
#define _IIPFRAME_H_

class CImpIOleInPlaceFrame;
typedef class CImpIOleInPlaceFrame *PIMPIOLEINPLACEFRAME;

class CImpIOleInPlaceFrame : public IOleInPlaceFrame
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIOleInPlaceFrame(LPVOID, LPUNKNOWN);
        ~CImpIOleInPlaceFrame(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetWindow(HWND *);
        STDMETHODIMP ContextSensitiveHelp(BOOL);
        STDMETHODIMP GetBorder(LPRECT);
        STDMETHODIMP RequestBorderSpace(LPCBORDERWIDTHS);
        STDMETHODIMP SetBorderSpace(LPCBORDERWIDTHS);
        STDMETHODIMP SetActiveObject(LPOLEINPLACEACTIVEOBJECT
            , LPCSTR);
        STDMETHODIMP InsertMenus(HMENU, LPOLEMENUGROUPWIDTHS);
        STDMETHODIMP SetMenu(HMENU, HOLEMENU, HWND);
        STDMETHODIMP RemoveMenus(HMENU);
        STDMETHODIMP SetStatusText(LPCSTR);
        STDMETHODIMP EnableModeless(BOOL);
        STDMETHODIMP TranslateAccelerator(LPMSG, WORD);
    };

#endif //_IIPFRAME_H_


IIPOBJ.CPP

/*
 * IIPOBJ.CPP
 *
 * Template IOleInPlaceObject interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iipobj.h"

/*
 * CImpIOleInPlaceObject::CImpIOleInPlaceObject
 * CImpIOleInPlaceObject::~CImpIOleInPlaceObject
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleInPlaceObject::CImpIOleInPlaceObject(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleInPlaceObject::~CImpIOleInPlaceObject(void)
    {
    return;
    }

/*
 * CImpIOleInPlaceObject::QueryInterface
 * CImpIOleInPlaceObject::AddRef
 * CImpIOleInPlaceObject::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIOleInPlaceObject.
 */

STDMETHODIMP CImpIOleInPlaceObject::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleInPlaceObject::GetWindow
 *
 * Purpose:
 *  Retrieves the handle of the window associated with the object on
 *  which this interface is implemented.
 *
 * Parameters:
 *  phWnd           HWND * in which to store the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_FAIL if no window.
 */

STDMETHODIMP CImpIOleInPlaceObject::GetWindow(HWND * phWnd)
    {
    return E_FAIL;
    }

/*
 * CImpIOleInPlaceObject::ContextSensitiveHelp
 *
 * Purpose:
 *  Instructs the object on which this interface is implemented to
 *  enter or leave a context-sensitive help mode.
 *
 * Parameters:
 *  fEnterMode      BOOL TRUE to enter the mode, FALSE otherwise.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleInPlaceObject::ContextSensitiveHelp(
    BOOL fEnterMode)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleInPlaceObject::InPlaceDeactivate
 *
 * Purpose:
 *  Instructs the object to deactivate itself from an in-place
 *  state and to discard any Undo state.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleInPlaceObject::InPlaceDeactivate(void)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleInPlaceObject::UIDeactivate
 *
 * Purpose:
 *  Instructs the object to just remove any in-place user
 *  interface but to do no other deactivation.  The object
 *  should just hide the UI components but not destroy them
 *  until InPlaceDeactivate is called.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleInPlaceObject::UIDeactivate(void)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleInPlaceObject::SetObjectRects
 *
 * Purpose:
 *  Provides the object with rectangles describing the position
 *  of the object in the container window as well as its visible
 *  area.  The object must reposition according to these rectangles.
 *
 * Parameters:
 *  prcPos          LPCRECT providing the object's full rectangle
 *                  relative to the continer's document.  The object
 *                  should scale to this rectangle.
 *  prcClip         LPCRECT describing the visible area of the object
 *                  which should not draw outside these areas.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleInPlaceObject::SetObjectRects(LPCRECT prcPos
    , LPCRECT prcClip)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleInPlaceObject::ReactivateAndUndo
 *
 * Purpose:
 *  Instructs the object to reactivate itself in-place and perform
 *  whatever Undo means for it.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleInPlaceObject::ReactivateAndUndo(void)
    {
    return E_NOTIMPL;
    }


IIPOBJ.H

/*
 * IIPOBJ.H
 *
 * Definitions of a template IOleInPlaceObject interface
 * implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IIPOBJ_H_
#define _IIPOBJ_H_

class CImpIOleInPlaceObject;
typedef class CImpIOleInPlaceObject *PIMPIOLEINPLACEOBJECT;

class CImpIOleInPlaceObject : public IOleInPlaceObject
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIOleInPlaceObject(LPVOID, LPUNKNOWN);
        ~CImpIOleInPlaceObject(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetWindow(HWND *);
        STDMETHODIMP ContextSensitiveHelp(BOOL);
        STDMETHODIMP InPlaceDeactivate(void);
        STDMETHODIMP UIDeactivate(void);
        STDMETHODIMP SetObjectRects(LPCRECT, LPCRECT);
        STDMETHODIMP ReactivateAndUndo(void);
    };

#endif //_IIPOBJ_H_


IIPSITE.CPP

/*
 * IIPSITE.CPP
 *
 * Template IOleInPlaceSite interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iipsite.h"

/*
 * CImpIOleInPlaceSite::CImpIOleInPlaceSite
 * CImpIOleInPlaceSite::~CImpIOleInPlaceSite
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleInPlaceSite::CImpIOleInPlaceSite(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleInPlaceSite::~CImpIOleInPlaceSite(void)
    {
    return;
    }

/*
 * CImpIOleInPlaceSite::QueryInterface
 * CImpIOleInPlaceSite::AddRef
 * CImpIOleInPlaceSite::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleInPlaceSite object.
 */

STDMETHODIMP CImpIOleInPlaceSite::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceSite::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceSite::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleInPlaceSite::GetWindow
 *
 * Purpose:
 *  Retrieves the handle of the window associated with the object
 *  on which this interface is implemented.
 *
 * Parameters:
 *  phWnd           HWND * in which to store the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_FAIL if no window.
 */

STDMETHODIMP CImpIOleInPlaceSite::GetWindow(HWND * phWnd)
    {
    return E_FAIL;
    }

/*
 * CImpIOleInPlaceSite::ContextSensitiveHelp
 *
 * Purpose:
 *  Instructs the object on which this interface is implemented to
 *  enter or leave a context-sensitive help mode.
 *
 * Parameters:
 *  fEnterMode      BOOL TRUE to enter the mode, FALSE otherwise.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceSite::ContextSensitiveHelp(
    BOOL fEnterMode)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::CanInPlaceActivate
 *
 * Purpose:
 *  Answers the server whether or not we can currently in-place
 *  activate its object.  By implementing this interface we say
 *  that we support in-place activation, but through this function
 *  we indicate whether the object can currently be activated
 *  in-place.  Linked objects, for example, cannot, meaning we
 *  return S_FALSE.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR if we can in-place activate the
 *                  object in this site, S_FALSE if not.
 */

STDMETHODIMP CImpIOleInPlaceSite::CanInPlaceActivate(void)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::OnInPlaceActivate
 *
 * Purpose:
 *  Informs the container that an object is being activated
 *  in-place such that the container can prepare appropriately.
 *  The container does not, however, make any user interface
 *  changes at this point.  See OnUIActivate.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceSite::OnInPlaceActivate(void)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::OnInPlaceDeactivate
 *
 * Purpose:
 *  Notifies the container that the object has deactivated itself
 *  from an in-place state.  Opposite of OnInPlaceActivate.  The
 *  container does not change any UI at this point.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceSite::OnInPlaceDeactivate(void)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::OnUIActivate
 *
 * Purpose:
 *  Informs the container that the object is going to start
 *  munging around with user interface, like replacing the menu.
 *  The container should remove any relevant UI in preparation.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceSite::OnUIActivate(void)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::OnUIDeactivate
 *
 * Purpose:
 *  Informs the container that the object is deactivating its
 *  in-place user interface at which time the container may
 *  reinstate its own.  Opposite of OnUIActivate.
 *
 * Parameters:
 *  fUndoable       BOOL indicating if the object will actually
 *                  perform an Undo if the container calls
 *                  ReactivateAndUndo.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceSite::OnUIDeactivate(BOOL fUndoable)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::DeactivateAndUndo
 *
 * Purpose:
 *  If immediately after activation the object does an Undo, the
 *  action being undone is the activation itself, and this call
 *  informs the container that this is, in fact, what happened.
 *  The container should call IOleInPlaceObject::UIDeactivate.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceSite::DeactivateAndUndo(void)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::DiscardUndoState
 *
 * Purpose:
 *  Informs the container that something happened in the object
 *  that means the container should discard any undo information
 *  it currently maintains for the object.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceSite::DiscardUndoState(void)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::GetWindowContext
 *
 * Purpose:
 *  Provides an in-place object with pointers to the frame and
 *  document level in-place interfaces (IOleInPlaceFrame and
 *  IOleInPlaceUIWindow) such that the object can do border
 *  negotiation and so forth.  Also requests the position and
 *  clipping rectangles of the object in the container and a
 *  pointer to an OLEINPLACEFRAME info structure which contains
 *  accelerator information.
 *
 *  Note that the two interfaces this call returns are not
 *  available through QueryInterface on IOleInPlaceSite since they
 *  live with the frame and document, but not the site.
 *
 * Parameters:
 *  ppIIPFrame      LPOLEINPLACEFRAME * in which to return
 *                  the AddRef'd pointer to the IOleInPlaceFrame.
 *  ppIIPUIWindow   LPOLEINPLACEUIWINDOW * in which to return
 *                  the AddRef'd pointer to the document's
 *                  IOleInPlaceUIWindow.
 *  prcPos          LPRECT in which to store the object's position.
 *  prcClip         LPRECT in which to store the object's
 *                  visible region.
 *  pFI             LPOLEINPLACEFRAMEINFO for accelerator info.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceSite::GetWindowContext(
    LPOLEINPLACEFRAME * ppIIPFrame, LPOLEINPLACEUIWINDOW
    * ppIIPUIWindow, LPRECT prcPos, LPRECT prcClip
    , LPOLEINPLACEFRAMEINFO pFI)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::Scroll
 *
 * Purpose:
 *  Asks the container to scroll the document, and thus the
 *  object, by the given amounts in the sz parameter.
 *
 * Parameters:
 *  sz              SIZE containing signed horizontal and vertical
 *                  extents by which the container should scroll.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceSite::Scroll(SIZE sz)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceSite::OnPosRectChange
 *
 * Purpose:
 *  Informs the container that the in-place object was resized.
 *  The container must call IOleInPlaceObject::SetObjectRects.
 *
 * Parameters:
 *  prcPos          LPCRECT containing the new size of the object.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceSite::OnPosRectChange(LPCRECT prcPos)
    {
    return NOERROR;
    }


IIPSITE.H

/*
 * IIPSITE.H
 *
 * Definitions of a template IOleInPlaceSite interface
 * implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IIPSITE_H_
#define _IIPSITE_H_

class CImpIOleInPlaceSite;
typedef class CImpIOleInPlaceSite *PIMPIOLEINPLACESITE;

class CImpIOleInPlaceSite : public IOleInPlaceSite
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIOleInPlaceSite(LPVOID, LPUNKNOWN);
        ~CImpIOleInPlaceSite(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetWindow(HWND *);
        STDMETHODIMP ContextSensitiveHelp(BOOL);
        STDMETHODIMP CanInPlaceActivate(void);
        STDMETHODIMP OnInPlaceActivate(void);
        STDMETHODIMP OnUIActivate(void);
        STDMETHODIMP GetWindowContext(LPOLEINPLACEFRAME *
            , LPOLEINPLACEUIWINDOW *, LPRECT, LPRECT
            , LPOLEINPLACEFRAMEINFO);
        STDMETHODIMP Scroll(SIZE);
        STDMETHODIMP OnUIDeactivate(BOOL);
        STDMETHODIMP OnInPlaceDeactivate(void);
        STDMETHODIMP DiscardUndoState(void);
        STDMETHODIMP DeactivateAndUndo(void);
        STDMETHODIMP OnPosRectChange(LPCRECT);
    };

#endif //_IIPSITE_H_


IIPUIWIN.CPP

/*
 * IIPUIWIN.CPP
 *
 * Template IOleInPlaceUIWIndow interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iipuiwin.h"

/*
 * CImpIOleInPlaceUIWindow::CImpIOleInPlaceUIWindow
 * CImpIOleInPlaceUIWindow::~CImpIOleInPlaceUIWindow
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleInPlaceUIWindow::CImpIOleInPlaceUIWindow(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleInPlaceUIWindow::~CImpIOleInPlaceUIWindow(void)
    {
    return;
    }

/*
 * CImpIOleInPlaceUIWindow::QueryInterface
 * CImpIOleInPlaceUIWindow::AddRef
 * CImpIOleInPlaceUIWindow::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIOleInPlaceUIWindow.
 */

STDMETHODIMP CImpIOleInPlaceUIWindow::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceUIWindow::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleInPlaceUIWindow::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleInPlaceUIWindow::GetWindow
 *
 * Purpose:
 *  Retrieves the handle of the window associated with the object
 *  on which this interface is implemented.
 *
 * Parameters:
 *  phWnd           HWND * in which to store the window handle.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, E_FAIL if no window.
 */

STDMETHODIMP CImpIOleInPlaceUIWindow::GetWindow(HWND * phWnd)
    {
    return E_FAIL;
    }

/*
 * CImpIOleInPlaceUIWindow::ContextSensitiveHelp
 *
 * Purpose:
 *  Instructs the object on which this interface is implemented to
 *  enter or leave a context-sensitive help mode.
 *
 * Parameters:
 *  fEnterMode      BOOL TRUE to enter the mode, FALSE otherwise.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceUIWindow::ContextSensitiveHelp(
    BOOL fEnterMode)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceUIWindow::GetBorder
 *
 * Purpose:
 *  Returns the rectangle in which the container is willing to
 *  negotiate about an object's adornments.
 *
 * Parameters:
 *  prcBorder       LPRECT in which to store the rectangle.
 *
 * Return Value:
 *  HRESULT         NOERROR if all is well, INPLACE_E_NOTOOLSPACE
 *                  if there is no negotiable space.
 */

STDMETHODIMP CImpIOleInPlaceUIWindow::GetBorder(LPRECT prcBorder)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceUIWindow::RequestBorderSpace
 *
 * Purpose:
 *  Asks the container if it can surrender the amount of space
 *  in pBW that the object would like for it's adornments.  The
 *  container does nothing but validate the spaces on this call.
 *
 * Parameters:
 *  pBW             LPCBORDERWIDTHS containing the requested space.
 *                  The values are the amount of space requested
 *                  from each side of the relevant window.
 *
 * Return Value:
 *  HRESULT         NOERROR if we can give up space,
 *                  INPLACE_E_NOTOOLSPACE otherwise.
 */

STDMETHODIMP CImpIOleInPlaceUIWindow::RequestBorderSpace(
    LPCBORDERWIDTHS pBW)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceUIWindow::SetBorderSpace
 *
 * Purpose:
 *  Called when the object now officially requests that the
 *  container surrender border space it previously allowed in
 *  RequestBorderSpace.  The container should resize windows
 *  appropriately to surrender this space.
 *
 * Parameters:
 *  pBW             LPCBORDERWIDTHS containing the amount of space
 *                  from each side of the relevant window that the
 *                  object is now reserving.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceUIWindow::SetBorderSpace(
    LPCBORDERWIDTHS pBW)
    {
    return NOERROR;
    }

/*
 * CImpIOleInPlaceUIWindow::SetActiveObject
 *
 * Purpose:
 *  Provides the container with the object's IOleInPlaceActiveObject
 *  pointer and a name of the object to show in the container's
 *  caption.
 *
 * Parameters:
 *  pIIPActiveObj   LPOLEINPLACEACTIVEOBJECT of interest.
 *  pszObj          LPCSTR to use in the container's caption bar.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleInPlaceUIWindow::SetActiveObject(
    LPOLEINPLACEACTIVEOBJECT pIIPActiveObj, LPCSTR pszObj)
    {
    return NOERROR;
    }


IIPUIWIN.H

/*
 * IIPUIWIN.H
 *
 * Definitions of a template IOleInPlaceUIWindow interface
 * implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IIPUIWIN_H_
#define _IIPUIWIN_H_

class CImpIOleInPlaceUIWindow;
typedef class CImpIOleInPlaceUIWindow *PIMPIOLEINPLACEUIWINDOW;

class CImpIOleInPlaceUIWindow : public IOleInPlaceUIWindow
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //Fr delegation

    public:
        CImpIOleInPlaceUIWindow(LPVOID, LPUNKNOWN);
        ~CImpIOleInPlaceUIWindow(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetWindow(HWND *);
        STDMETHODIMP ContextSensitiveHelp(BOOL);
        STDMETHODIMP GetBorder(LPRECT);
        STDMETHODIMP RequestBorderSpace(LPCBORDERWIDTHS);
        STDMETHODIMP SetBorderSpace(LPCBORDERWIDTHS);
        STDMETHODIMP SetActiveObject(LPOLEINPLACEACTIVEOBJECT
            , LPCSTR);
    };

#endif //_IIPUIWIN_H_


ILOCKBYT.CPP

/*
 * ILOCKBYT.H
 *
 * Template ILockBytes interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "ilockbyt.h"

/*
 * CImpILockBytes::CImpILockBytes
 * CImpILockBytes::~CImpILockBytes
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpILockBytes::CImpILockBytes(LPVOID pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpILockBytes::~CImpILockBytes(void)
    {
    return;
    }

/*
 * CImpILockBytes::QueryInterface
 * CImpILockBytes::AddRef
 * CImpILockBytes::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpILockBytes.
 */

STDMETHODIMP CImpILockBytes::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpILockBytes::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpILockBytes::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpILockBytes::ReadAt
 *
 * Purpose:
 *  Reads bytes from the byte array starting at a specific position.
 *
 * Parameters:
 *  ulOffset        ULARGE_INTEGER specifying the offset at which
 *                  to read.
 *  pv              void HUGEP * into which to read the information.
 *  cb              ULONG number of bytes to read
 *  pcbRead         ULONG * in which to store the number of bytes
 *                  read
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP ReadAt(ULARGE_INTEGER ulOffset, VOID HUGEP *pv
    , ULONG cb, ULONG *pcbRead)
    {
    return E_NOTIMPL;
    }

/*
 * CImpILockBytes::WriteAt
 *
 * Purpose:
 *  Writes bytes into the byte array starting at a specific position.
 *
 * Parameters:
 *  ulOffset        ULARGE_INTEGER specifying the offset at which
 *                  to write.
 *  pv              void HUGEP * to the information to write.
 *  cb              ULONG number of bytes to write
 *  pcbWritten      ULONG * in which to store the number of bytes
 *                  written.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP WriteAt(ULARGE_INTEGER ulOffset, VOID HUGEP *pv
    , ULONG cb, ULONG *pcbWritten)
    {
    return E_NOTIMPL;
    }

/*
 * CImpILockBytes::Flush
 *
 * Purpose:
 *  Insures that any internal buffers are written to the underlying
 *  storage.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP Flush(void)
    {
    return E_NOTIMPL;
    }

/*
 * CImpILockBytes::SetSize
 *
 * Purpose:
 *  Changes the size of the byte array.
 *
 * Parameters:
 *  cb              ULARGE_INTEGER specifying the size to set.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP SetSize(ULARGE_INTEGER cb)
    {
    return E_NOTIMPL;
    }

/*
 * CImpILockBytes::LockRegion
 *
 * Purpose:
 *  Locks a range of bytes in the byte array.
 *
 * Parameters:
 *  libOffset       ULARGE_INTEGER at which to start the lock.
 *  cb              ULARGE_INTEGER number of bytes to lock.
 *  dwLockType      DWORD the type of lock access.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP LockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb
    , DWORD dwLockType)
    {
    return E_NOTIMPL;
    }

/*
 * CImpILockBytes::UnlockRegion
 *
 * Purpose:
 *  Unlocks a range of bytes in the byte array.
 *
 * Parameters:
 *  libOffset       ULARGE_INTEGER at which to start the unlock.
 *  cb              ULARGE_INTEGER number of bytes to unlock.
 *  dwLockType      DWORD the type of lock access.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP UnlockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb
    , DWORD dwLockType)
    {
    return E_NOTIMPL;
    }

/*
 * CImpILockBytes::Stat
 *
 * Purpose:
 *  Returns relevant statistics about the byte array.
 *
 * Parameters:
 *  pstatstg        LPSTATSTG in which to store the statistics.
 *  grfStatFlag     DWORD specifying the level of information
 *                  requested.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP Stat(STATSTG *pstatstg, DWORD grfStatFlag)
    {
    return E_NOTIMPL;
    }


ILOCKBYT.H

/*
 * ILOCKBYT.H
 *
 * Definitions of a template ILockBytes interface
 * implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _ILOCKBYT_H_
#define _ILOCKBYT_H_

class CImpILockBytes;
typedef class CImpILockBytes *PIMPILOCKBYTES;

class CImpILockBytes : public ILockBytes
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //Fr delegation

    public:
        CImpILockBytes(LPVOID, LPUNKNOWN);
        ~CImpILockBytes(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP ReadAt(ULARGE_INTEGER, VOID HUGEP *, ULONG
            , ULONG *);
        STDMETHODIMP WriteAt(ULARGE_INTEGER, VOID const HUGEP *
            , ULONG, ULONG *);
        STDMETHODIMP Flush(void);
        STDMETHODIMP SetSize(ULARGE_INTEGER);
        STDMETHODIMP LockRegion(ULARGE_INTEGER, ULARGE_INTEGER
            , DWORD);
        STDMETHODIMP UnlockRegion(ULARGE_INTEGER, ULARGE_INTEGER
            , DWORD);
        STDMETHODIMP Stat(LPSTATSTG, DWORD);
    };

#endif //_ILOCKBYT_H_


IMALLOC.CPP

/*
 * IMALLOC.CPP
 *
 * Template IMalloc interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "imalloc.h"

/*
 * CImpIMalloc::CImpIMalloc
 * CImpIMalloc::~CImpIMalloc
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIMalloc::CImpIMalloc(LPVOID pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIMalloc::~CImpIMalloc(void)
    {
    return;
    }

/*
 * CImpIMalloc::QueryInterface
 * CImpIMalloc::AddRef
 * CImpIMalloc::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIMalloc.
 */

STDMETHODIMP CImpIMalloc::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIMalloc::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIMalloc::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIMalloc::Alloc
 *
 * Purpose:
 *  Allocates memory according to the rules of the allocator
 *  object with this interface.
 *
 * Parameters:
 *  cb              ULONG number of bytes to allocate.
 *
 * Return Value:
 *  void *          Pointer to the reallocated memory
 */

STDMETHODIMP_(void *) CImpIMalloc::Alloc(ULONG cb)
    {
    return NULL;
    }

/*
 * CImpIMalloc::Realloc
 *
 * Purpose:
 *  Reallocates a previous allocation to a different size.
 *
 * Parameters:
 *  pv              void * to the previously allocated memory.
 *  cb              ULONG with the new size.
 *
 * Return Value:
 *  void *          Pointer to the reallocated memory
 */

STDMETHODIMP_(void *) CImpIMalloc::Realloc(void *pv, ULONG cb)
    {
    return NULL;
    }

/*
 * CImpIMalloc::Free
 *
 * Purpose:
 *  Frees and allocation from Alloc.
 *
 * Parameters:
 *  pv              void * of the allocation to free.
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIMalloc::Free(void *pv)
    {
    return;
    }

/*
 * CImpIMalloc::GetSize
 *
 * Purpose:
 *  Return the size of an allocation.
 *
 * Parameters:
 *  pv              void * of the allocation in question.
 *
 * Return Value:
 *  ULONG           The number of bytes in the allocation.
 */

STDMETHODIMP_(ULONG) CImpIMalloc::GetSize(void *pv)
    {
    }

/*
 * CImpIMalloc::DidAlloc
 *
 * Purpose:
 *  Answers if this allocator allocated the given block of memory.
 *
 * Parameters:
 *  pv              void * of the allocation in question.
 *
 * Return Value:
 *  int             1 if the allocation was made by this allocator,
 *                  0 if not, -1 if the answer is indeterminate.
 */

STDMETHODIMP_(int) CImpIMalloc::DidAlloc(void *pv)
    {
    return -1;
    }

/*
 * CImpIMalloc::HeapMinimize
 *
 * Purpose:
 *  Releases unused memory to the operating system, minimizing the
 *  heap.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  None
 */

STDMETHODIMP_(void) CImpIMalloc::HeapMinimize(void)
    {
    return;
    }


IMALLOC.H

/*
 * IMALLOC.H
 *
 * Definitions of a template IMalloc interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IMALLOC_H_
#define _IMALLOC_H_

class CImpIMalloc;
typedef class CImpIMalloc *PIMPIMALLOC;

class CImpIMalloc : public IMalloc
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIMalloc(LPVOID, LPUNKNOWN);
        ~CImpIMalloc(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(void *) Alloc(ULONG);
        STDMETHODIMP_(void *) Realloc(void *, ULONG);
        STDMETHODIMP_(void)   Free(void *);
        STDMETHODIMP_(ULONG)  GetSize(void *);
        STDMETHODIMP_(int)    DidAlloc(void *);
        STDMETHODIMP_(void)   HeapMinimize(void);
    };

#endif //_IMALLOC_H_


IMARSHAL.CPP

/*
 * IMARSHAL.CPP
 *
 * Template IMarshal interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "imarshal.h"

/*
 * CImpIMarshal::CImpIMarshal
 * CImpIMarshal::~CImpIMarshal
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIMarshal::CImpIMarshal(LPVOID pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIMarshal::~CImpIMarshal(void)
    {
    return;
    }

/*
 * CImpIMarshal::QueryInterface
 * CImpIMarshal::AddRef
 * CImpIMarshal::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIMarshal.
 */

STDMETHODIMP CImpIMarshal::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIMarshal::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIMarshal::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIMarshal::GetUnmarshalClass
 *
 * Purpose:
 *  Determines the class of object to be used to create an
 *  uninitalized proxy in the unmarshaling process.
 *
 * Parameters:
 *  riid            REFIID of the interface to be marshaled.
 *  pv              LPVOID to the interface to be marshaled.
 *  dwCtx           DWORD specifying the relation of the processes
 *                  between which the marshaling is occuring, from the
 *                  MSHCTX enumeration.
 *  pvCtx           LPVOID Reserved for future MSHCTX values.
 *  dwFlags         DWORD specifying why marshaling is taking place.
 *  pClsID          LPCLSID in which to store the proxy CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIMarshal::GetUnmarshalClass(REFIID riid
    , LPVOID pv, DWORD dwCtx, LPVOID pvCtx, DWORD dwFlags
    , LPCLSID pClsID)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMarshal::GetMarshalSizeMax
 *
 * Purpose:
 *  Returns the upper memory bound needed to write data into a stream
 *  for IMarshal::MarshalInterface.
 *
 * Parameters:
 *  riid            REFIID of the interface to be marshaled.
 *  pv              LPVOID of the interface to be marshaled.
 *  dwDestCtx       DWORD with the destination context from MSHCTX.
 *  pvDestCtx       LPVOID reserved for future MSHCTX flags.
 *  dwFlags         DWORD specifying why marshaling is taking place.
 *  pdwSize         LPDWORD in which the size is returned.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIMarshal::GetMarshalSizeMax(REFIID riid, LPVOID pv
    , DWORD dwDestCtx, LPVOID pvDestCtx, DWORD dwFlags
    , LPDWORD pdwSize)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMarshal::MarshalInterface
 *
 * Purpose:
 *  Marshals an interface ID into a stream.
 *
 * Parameters:
 *  pStm            LPSTREAM into which to marshal the interface.
 *  riid            REFIID of the interface to be marshaled.
 *  pv              LPVOID of the interface to be marshaled.
 *  dwDestCtx       DWORD with the destination context from MSHCTX.
 *  pvDestCtx       LPVOID reserved for future MSHCTX flags.
 *  dwFlags         DWORD specifying why marshaling is taking place.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIMarshal::MarshalInterface(LPSTREAM pstm
    , REFIID riid, LPVOID pv, DWORD dwDestCtx, LPVOID pvDestCtx
    , DWORD dwFlags)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMarshal::UnmarshalInterface
 *
 * Purpose:
 *  Initializes a newly created proxy from a stream stored in
 *  MarshalInterface.
 *
 * Parameters:
 *  pStm            LPSTREAM to the stream containing marshal
 *                  data.
 *  riid            REFIID of the interface to be marshaled.
 *  pv              LPVOID of the interface to be marshaled.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIMarshal::UnmarshalInterface(LPSTREAM pstm
    , REFIID riid, LPVOID *pv)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMarshal::ReleaseMarshalData
 *
 * Purpose:
 *  Destroy a marshaled data packet.
 *
 * Parameters:
 *  pStm            LPSTREAM containing the data to release.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIMarshal::ReleaseMarshalData(LPSTREAM pstm)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMarshal::Disconnect
 *
 * Purpose:
 *  Instructs an object with custom marshaling that it's being
 *  disconnected.
 *
 * Parameters:
 *  dwReserved      DWORD reserved.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIMarshal::DisconnectObject(DWORD dwReserved)
    {
    return E_NOTIMPL;
    }


IMARSHAL.H

/*
 * IMARSHAL.H
 *
 * Definitions of a template IMarshal interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IMARSHAL_H_
#define _IMARSHAL_H_

class CImpIMarshal;
typedef class CImpIMarshal *PIMPIMARSHAL;

class CImpIMarshal : public IMarshal
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIMarshal(LPVOID, LPUNKNOWN);
        ~CImpIMarshal(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetUnmarshalClass(REFIID, LPVOID, DWORD
            , LPVOID, DWORD, LPCLSID);
        STDMETHODIMP GetMarshalSizeMax(REFIID, LPVOID, DWORD
            , LPVOID, DWORD, LPDWORD);
        STDMETHODIMP MarshalInterface(LPSTREAM, REFIID, LPVOID
            , DWORD, LPVOID, DWORD);
        STDMETHODIMP UnmarshalInterface(LPSTREAM, REFIID, LPVOID *);
        STDMETHODIMP ReleaseMarshalData(LPSTREAM);
        STDMETHODIMP DisconnectObject(DWORD);
    };

#endif //_IMARSHAL_H_


IMONIKER.CPP

/*
 * IMONIKER.CPP
 *
 * Template IMoniker interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "imoniker.h"

/*
 * CImpIMoniker::CImpIMoniker
 * CImpIMoniker::~CImpIMoniker
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIMoniker::CImpIMoniker(LPVOID pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIMoniker::~CImpIMoniker(void)
    {
    return;
    }

/*
 * CImpIMoniker::QueryInterface
 * CImpIMoniker::AddRef
 * CImpIMoniker::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIMoniker.
 */

STDMETHODIMP CImpIMoniker::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIMoniker::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIMoniker::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIMoniker::GetClassID
 *
 * Purpose:
 *  Return the CLSID of the moniker.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store the CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIMoniker::GetClassID(LPCLSID pClsID)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMoniker::IsDirty
 *
 * Purpose:
 *  Answers whether the state of the moniker itself is dirty.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         S_TRUE if the moniker is dirty, S_FALSE if not.
 *                  Possibly a failure code.
 */

STDMETHODIMP CImpIMoniker::IsDirty(void)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMoniker::Load
 *
 * Purpose:
 *  Initializes the moniker from the information in a stream.
 *
 * Parameters:
 *  pstm            LPSTREAM containing the moniker's information.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIMoniker::Load(LPTSTREAM pstm)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMoniker::Save
 *
 * Purpose:
 *  Instructs the moniker to save its state to a stream.
 *
 * Parameters:
 *  pstm            LPSTREAM in which to save the moniker.
 *  fClearDirty     BOOL indicating if the object is to clear its
 *                  dirty flag after the save.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIMoniker::Save(LPTSTREAM pstm, BOOL fClearDirty)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMoniker::GetSizeMax
 *
 * Purpose:
 *  Returns the size of a stream the moniker need to store its
 *  persistent state.
 *
 * Parameters:
 *  pcbSize         ULARGE_INTEGER * in which to store the size.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIMoniker::GetSizeMax(ULARGE_INTEGER *pcbSize)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMoniker::BindToObject
 *
 * Purpose:
 *  Instructs a moniker to locate and load the object it refereces.
 *
 * Parameters:
 *  pBindCtx        LPBC to the bind context.
 *  pmkLeft         LPMONIKER to the "left" of this moniker.
 *  riid            REFIID of the interface desired on the object
 *  ppvObj          LPVOID * into which to store the bound object's
 *                  interface pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIMoniker::BindToObject(LPBC pBindCtx
    , LPMONIKER pmkLeft, REFIID riid, LPVOID *ppvObj)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMoniker::BindToStorage
 *
 * Purpose:
 *  Locates and returns an interface pointer to the storage of
 *  the object referenced by this moniker.
 *
 * Parameters:
 *  pBindCtx        LPBC to the bind context.
 *  pmkLeft         LPMONIKER to the "left" of this moniker.
 *  riid            REFIID of the interface desired on the object
 *  ppvObj          LPVOID * into which to store the bound object's
 *                  interface pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIMoniker::BindToStorage(LPBC pBindCtx
    , LPMONIKER pmkLeft, REFIID riid, LPVOID *ppvObj)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMoniker::Reduce
 *
 * Purpose:
 *  Returns a more or equally efficient moniker equivalent to
 *  this moniker.
 *
 * Parameters:
 *  pBindCtx        LPBC to the bind context.
 *  dwHowFar        DWORD indicating how far the reduction should go.
 *  ppmkLeft        LPMONIKER * (in-out) contains initial left moniker
 *                  on input and a new prefix on output (if necessary).
 *  ppmkReduced     LPMONIKER * into which to store the reduced moniker.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIMoniker::Reduce(LPBC pBindCtx, DWORD dwHowFar
    , LPMONIKER *ppmkLeft, LPMONIKER *ppmkReduced)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMoniker::ComposeWith
 *
 * Purpose:
 *  Returns a new composite moniker composed with this moniker on
 *  the left and ppmkRight on the right.
 *
 * Parameters:
 *  pmkRight        LPMONIKER of the moniker to store on the right.
 *  fOnlyIfNotGen   BOOL that contrls what should be done if the
 *                  result is not a generic composite.
 *  ppmkComposite   LPMONIKER * in which to store the resulting
 *                  composite.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIMoniker::ComposeWith(LPMONIKER pmkRight
    , BOOL fOnlyIfNotGen, LPMONIKER *ppmkComposite)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMoniker::Enum
 *
 * Purpose:
 *  Enumerates the monikers within this moniker if we are a
 *  composite.  Need not be implemented for simple monikers.
 *
 * Parameters:
 *  fForward        Specifies the direction of enumeration, TRUE
 *                  for forward, FALSE for backwards.
 *  ppEnum          LPENUMMONIKER * in which to store the
 *                  enumerator's IEnumMoniker interface.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIMoniker::Enum(BOOL fForward, LPENUMMONIKER *ppEnum)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMoniker::IsEqual
 *
 * Purpose:
 *  Compares this moniker to another for equivalence.
 *
 * Parameters:
 *  pmk             LPMONKIER to the other moniker to compare.
 *
 * Return Value:
 *  HRESULT         S_OK or S_FALSE depending on the result of
 *                  the comparison.
 */

STDMETHODIMP CImpIMoniker::IsEqual(LPMONIKER pmk)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMoniker::Hash
 *
 * Purpose:
 *  Returns a 32-bit integer associated with this moniker that
 *  can be used in creating moniker tables.  This should only rely
 *  on the internal state of the moniker and not any memory
 *  addresses.
 *
 * Parameters:
 *  pdwHash         LPDWORD in which to store the hash number.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIMoniker::Hash(LPDWORD pdwHash)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMoniker::IsRunning
 *
 * Purpose:
 *  Tests if the object associated with this moniker is already
 *  running.
 *
 * Parameters:
 *  pBindCtx        LPBINDCTX to the bind context in use.
 *  pmkLeft         LPMONIKER to the left.
 *  pmkNewlyRunning LPMONIKER most recently added to the running
 *                  object table (can be NULL).  If equivalent to
 *                  this moniker then the object is already running.
 *
 * Return Value:
 *  HRESULT         S_OK or S_FALSE (or an error code)
 */

STDMETHODIMP CImpIMoniker::IsRunning(LPBC pBindCtx
    , LPMONIKER pmkLeft, LPMONIKER pmkNewlyRunning)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMoniker::GetTimeOfLastChange
 *
 * Purpose:
 *  Reports the time that the object referenced by this moniker,
 *  such as a file, changed.
 *
 * Parameters:
 *  pBindCtx        LPBINDCTX to the bind context in use.
 *  pmkLeft         LPMONIKER to the left.
 *  pTime           FILETIME * in which to store the last change
 *                  time and date information.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIMoniker::GetTimeOfLastChange(LPBC pBindCtx
    , LPMONIKER pmkLeft, FILETIME *pTime)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMoniker::Inverse
 *
 * Purpose:
 *  Creates an inverse of this moniker such that a composite
 *  containing this moniker and its inverse to the right would
 *  annihilate each other.
 *
 * Parameters:
 *  ppmkInverse     LPMONIKER * in which to store the inverse.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIMoniker::Inverse(LPMONIKER *)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMoniker::CommonPrefixWith
 *
 * Purpose:
 *  Determines if this moniker and another have a common prefix
 *  (left elements) that can be separated out into another
 *  moniker.
 *
 * Parameters:
 *  pmk             LPMONIKER to the other moniker.
 *  ppmkPrefix      LPMONIKER * in which to store the prefix moniker
 *                  if there is one, otherwise receives NULL.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIMoniker::CommonPrefixWith(LPMONIKER, LPMONIKER *)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMoniker::RelativePathTo
 *
 * Purpose:
 *  Returns another moniker that contains the relative path between
 *  this moniker and another.
 *
 * Parameters:
 *  pmk             LPMONIKER of the other moniker.
 *  ppmkRelative    LPMONIKER * in which to store a moniker that
 *                  contains the relative path.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIMoniker::RelativePathTo(LPMONIKER, LPMONIKER *)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMoniker::GetDisplayName
 *
 * Purpose:
 *  Returns a human-readable name suitable for display of this
 *  moniker.
 *
 * Parameters:
 *  pBindCtx        LPBC to the current bind context.
 *  pmkLeft         LPMONIKER to the left.
 *  ppszName        LPTSTR * in which to store the display name.
 *                  Output can be NULL if there is no name.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIMoniker::GetDisplayName(LPBC, LPMONIKER, LPTSTR *)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMoniker::ParseDisplayName
 *
 * Purpose:
 *  Pparses the moniker's remaining display name. The pszDisplayName
 *  parameter is the yet-to-be-parsed tail of the display name.  This
 *  function parses as much of the remaining tail as is appropriate
 *  for a display name within the object identified by
 *  (pmkToLeft * (the receiver)) and returns the corresponding
 *  moniker.
 *
 * Parameters:
 *  pBindCtx        LPBC to the current bind context.
 *  pmkLeft         LPMONIKER to the left.
 *  pszDisplayName  LPTSTR to the display name to be parsed.
 *  pchEaten        ULONG * with the number of characters of the
 *                  input name this parse consumed.
 *  ppmkOut         LPMONIKER in whic to store the resulting
 *                  moniker.
 *
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIMoniker::ParseDisplayName(LPBC pBindCtx
    , LPMONIKER pmkLeft, LPTSTR pszDisplayName, ULONG *pchEaten
    , LPMONIKER *ppmkOut)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIMoniker::IsSystemMoniker
 *
 * Purpose:
 *  Returns whether or not this is a system-implemented moniker
 *  whose implemented semantics are important for the binding
 *  process.
 *
 * Parameters:
 *  pdwType         LPDWORD in which to return the system moniker
 *                  type, from the MKSYS enumeration if S_OK is
 *                  returned.
 *
 * Return Value:
 *  HRESULT         S_OK or S_FALSE.
 */

STDMETHODIMP CImpIMoniker::IsSystemMoniker(LPDWORD)
    {
    return E_NOTIMPL;
    }


IMONIKER.H

/*
 * IMONIKER.H
 *
 * Definitions of a template IMoniker interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IMONIKER_H_
#define _IMONIKER_H_

class CImpIMoniker;
typedef class CImpIMoniker *PIMPIMONIKER;

class CImpIMoniker : public IMoniker
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIMoniker(LPVOID, LPUNKNOWN);
        ~CImpIMoniker(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP Load(LPTSTREAM);
        STDMETHODIMP Save(LPTSTREAM, BOOL);
        STDMETHODIMP GetSizeMax(ULARGE_INTEGER *);

        STDMETHODIMP BindToObject(LPBC, LPMONIKER
            , REFIID, LPVOID *);
        STDMETHODIMP BindToStorage(LPBC, LPMONIKER
            , REFIID, LPVOID *);
        STDMETHODIMP Reduce(LPBC, DWORD, LPMONIKER *, LPMONIKER *);
        STDMETHODIMP ComposeWith(LPMONIKER, BOOL, LPMONIKER *);
        STDMETHODIMP Enum(BOOL, LPENUMMONIKER *);
        STDMETHODIMP IsEqual(LPMONIKER);
        STDMETHODIMP Hash(LPDWORD);
        STDMETHODIMP IsRunning(LPBC, LPMONIKER, LPMONIKER);
        STDMETHODIMP GetTimeOfLastChange(LPBC, LPMONIKER
            , FILETIME *);
        STDMETHODIMP Inverse(LPMONIKER *);
        STDMETHODIMP CommonPrefixWith(LPMONIKER, LPMONIKER *);
        STDMETHODIMP RelativePathTo(LPMONIKER, LPMONIKER *);
        STDMETHODIMP GetDisplayName(LPBC, LPMONIKER, LPTSTR *);
        STDMETHODIMP ParseDisplayName(LPBC, LPMONIKER, LPTSTR
            , ULONG *, LPMONIKER *);
        STDMETHODIMP IsSystemMoniker(LPDWORD);
    };

#endif //_IMONIKER_H_


IMSGFILT.CPP

/*
 * IMSGFILT.CPP
 *
 * Template IMessageFilter interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "imsgfilt.h"

/*
 * CImpIMessageFilter::CImpIMessageFilter
 * CImpIMessageFilter::~CImpIMessageFilter
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIMessageFilter::CImpIMessageFilter(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIMessageFilter::~CImpIMessageFilter(void)
    {
    return;
    }

/*
 * CImpIMessageFilter::QueryInterface
 * CImpIMessageFilter::AddRef
 * CImpIMessageFilter::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIMessageFilter.
 */

STDMETHODIMP CImpIMessageFilter::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIMessageFilter::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIMessageFilter::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIMessageFilter::HandleIncomingCall
 *
 * Purpose:
 *  Requests that the container call OleSave for the object that
 *  lives here.  Typically this happens on server shutdown.
 *
 * Parameters:
 *  dwCallType      DWORD indicating the type of call received, from
 *                  the CALLTYPE enumeration
 *  hTaskCaller     HTASK of the caller
 *  dwTickCount     DWORD elapsed tick count since the outgoing call
 *                  was made if dwCallType is not CALLTYPE_TOPLEVEL.
 *                  Ignored for other call types.
 *  pInterfaceInfo  LPINTERFACEINFO providing information about the
 *                  call.  Can be NULL.
 *
 * Return Value:
 *  DWORD           One of SERVERCALL_ISHANDLED (if the call might
 *                  be handled), SERVERCALL_REJECTED (call cannot
 *                  be handled), or SERVERCALL_RETRYLATER (try
 *                  again sometime).
 */

STDMETHOD_(DWORD) CImpIMessageFilter::HandleInComingCall
    (DWORD dwCallType, HTASK htaskCaller, DWORD dwTickCount
    , LPINTERFACEINFO pInterfaceInfo)
    {
    return SERVERCALL_ISHANDLED;
    }

/*
 * CImpIMessageFilter::RetryRejectedCall
 *
 * Purpose:
 *
 * Parameters:
 *  hTaskCallee     HTASK of the caller
 *  dwTickCount     DWORD elapsed tick count since the call was made
 *  dwRejectType    DWORD either SERVERCALL_REJECTED or
 *                  SERVERCALL_RETRYLATER as returned by
 *                  HandleIncomingCall.
 *
 * Return Value:
 *  DWORD           (DWORD)-1 to cancel the call, any number between
 *                  0 and 100 to try the call again immediately, or
 *                  a value over 100 (but not (DWORD)-1) to instruct
 *                  COM to wait that many milliseconds before trying
 *                  again.
 */

STDMETHOD_(DWORD) CImpIMessageFilter::RetryRejectedCall
    (HTASK htaskCallee, DWORD dwTickCount, DWORD dwRejectType)
    {
    return 0;
    }

/*
 * CImpIMessageFilter::MessagePending
 *
 * Purpose:
 *
 * Parameters:
 *  hTaskCallee     HTASK of the caller
 *  dwTickCount     DWORD elapsed tick count since the call was made
 *  dwPendingType   DWORD with the type of call made from the
 *                  PENDINGTYPE enumeration.
 *
 * Return Value:
 *  DWORD           One of PENDINGMSG_CANCELCALL (cancels the call
 *                  under extreme conditions), PENDINGMSG_WAITNO-
 *                  PROCESS (continue waiting), or PENDINGMSG_WAIT-
 *                  DEFPROCESS (invoke default handling).
 */

STDMETHOD_(DWORD) CImpIMessageFilter::MessagePending
    (HTASK htaskCallee, DWORD dwTickCount, DWORD dwPendingType)
    {
    return PENDINGMSG_WAITDEFPROCESS;
    }


IMSGFILT.H

/*
 * IMSGFILT.H
 *
 * Definitions of a template IMessageFilter interface
 * implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IMSGFILT_H_
#define _IMSGFILT_H_

class CImpIMessageFilter;
typedef class CImpIMessageFilter *PIMPIMESSAGEFILTER;

class CImpIMessageFilter : public IMessageFilter
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIMessageFilter(LPVOID, LPUNKNOWN);
        ~CImpIMessageFilter(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP_(DWORD) HandleInComingCall(DWORD, HTASK
            , DWORD, LPINTERFACEINFO);
        STDMETHODIMP_(DWORD) RetryRejectedCall(HTASK, DWORD, DWORD);
        STDMETHODIMP_(DWORD) MessagePending(HTASK, DWORD, DWORD);
    };

#endif //_IMSGFILT_H_


IOLECONT.CPP

/*
 * IOLECONT.CPP
 *
 * Templace implementation of the IOleItemContainer interface.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iolecont.h"

/*
 * CImpIOleItemContainer::CImpIOleItemContainer
 * CImpIOleItemContainer::~CImpIOleItemContainer
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object controlling this interface.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleItemContainer::CImpIOleItemContainer(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleItemContainer::~CImpIOleItemContainer(void)
    {
    return;
    }

/*
 * CImpIOleItemContainer::QueryInterface
 * CImpIOleItemContainer::AddRef
 * CImpIOleItemContainer::Release
 *
 * Purpose:
 *  IUnknown members for CImpIOleItemContainer object.
 */

STDMETHODIMP CImpIOleItemContainer::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleItemContainer::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleItemContainer::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleItemContainer::ParseDisplayName
 *
 * Purpose:
 *  Inherited member of IParseDisplayName that takes a string name
 *  and turns out a moniker for it.
 *
 * Parameters:
 *  pbc             LPBC to the binding context
 *  pszName         LPTSTR to the name to parse.
 *  pchEaten        ULONG * into which to store how many
 *                  characters we scanned in the display name.
 *  ppmk            LPMONIKER * in which to return the moniker.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::ParseDisplayName(LPBC pbc
    , LPTSTR pszName, ULONG * pchEaten, LPMONIKER *ppmk)
    {
    *ppmk=NULL;
    return E_NOTIMPL;
    }

/*
 * CImpIOleItemContainer::EnumObjects
 *
 * Purpose:
 *  Creates and returns an IEnumUnknown object that allows the
 *  caller to walk through the objects in this continer thing.
 *
 * Parameters:
 *  dwFlags         DWORD specifying what kind of objects to
 *                  enumerate.
 *  ppEnum          LPENUMUNKNOWN * into which to return the
 *                  enumerator
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::EnumObjects(DWORD dwFlags
    , LPENUMUNKNOWN *ppEnum)
    {
    *ppEnum=NULL;
    return E_NOTIMPL;
    }

/*
 * CImpIOleItemContainer::LockContainer
 *
 * Purpose:
 *  Establishes a lock on the container to prevent it from shutting
 *  down outside of user control.  This is used to control the
 *  lifetime of the container when it's used to update a link to an
 *  embedded object within it.  If we're unlock and the user has not
 *  taken control, we close.
 *
 * Parameters:
 *  fLock           BOOL indicating a lock or unlock.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::LockContainer(BOOL fLock)
    {
    /*
     * This is pretty much the same implementation as
     * IClassFactory::LockServer, and we can use the same lock
     * count to accomplish our goal.
     */

    return E_NOTIMPL;
    }

/*
 * CImpIOleItemContainer::GetObject
 *
 * Purpose:
 *  Returns the requested interface pointer on an object in this
 *  container.
 *
 * Parameters:
 *  pszItem         LPTSTR to the item we must locate.
 *  dwSpeed         DWORD identifying how long the caller is willing
 *                  to wait.
 *  pcb             LPBINDCTX providing the binding context.
 *  riid            REFIID of the interface requested.
 *  ppv             LPVOID * into which to return the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::GetObject(LPTSTR pszItem
    , DWORD dwSpeed, LPBINDCTX pbc, REFIID riid, LPVOID *ppv)
    {
    *ppv=NULL;
    return E_NOTIMPL;
    }

/*
 * CImpIOleItemContainer::GetObjectStorage
 *
 * Purpose:
 *  Similar to get Object in that we have to locate the object
 *  described by a given name, but instead of returning any old
 *  interface we return a storage element.
 *
 * Parameters:
 *  pszItem         LPTSTR to the item we must locate.
 *  pcb             LPBINDCTX providing the binding context.
 *  riid            REFIID of the interface requested.  Usually
 *                  IStorage or IStream.
 *  ppv             LPVOID into which to return the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleItemContainer::GetObjectStorage(LPTSTR pszItem
    , LPBINDCTX pbc, REFIID riid, LPLPVOID *ppv)
    {
    *ppv=NULL;
    return E_NOTIMPL;
    }

/*
 * CImpIOleItemContainer::IsRunning
 *
 * Purpose:
 *  Answers if the object under the given name is currently running.
 *
 * Parameters:
 *  pszItem         LPTSTR of the item to check
 *
 * Return Value:
 *  HRESULT         NOERROR if the object is running, S_FALSE
 *                  otherwise.  Possibly MK_E_NOOBJECT if the name
 *                  is bogus.
 */

STDMETHODIMP CImpIOleItemContainer::IsRunning(LPTSTR pszItem)
    {
    return E_NOTIMPL;
    }


IOLECONT.H

/*
 * IOLECONT.H
 *
 * Definitions of a template IOleItemContainer interface
 * implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IOLECONT_H_
#define _IOLECONT_H_

class CImpIOleItemContainer;
typedef class CImpIOleItemContainer *PIMPIOLEITEMCONTAINER;

class CImpIOleItemContainer : public IOleItemContainer
    {
    protected:
        ULONG               m_cRef;
        LPVOID              m_pObj;
        LPUNKNOWN           m_punkOuter;

    public:
        CImpIOleItemContainer(LPVOID, LPUNKNOWN);
        ~CImpIOleItemContainer(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP ParseDisplayName(LPBC, LPTSTR, ULONG *
                         , LPMONIKER *);
        STDMETHODIMP EnumObjects(DWORD, LPENUMUNKNOWN *);
        STDMETHODIMP LockContainer(BOOL);
        STDMETHODIMP GetObject(LPTSTR, DWORD, LPBINDCTX, REFIID
                         , LPVOID *);
        STDMETHODIMP GetObjectStorage(LPTSTR, LPBINDCTX, REFIID
                         , LPVOID *);
        STDMETHODIMP IsRunning(LPTSTR);
    };

#endif  //_IOLECONT_H_


IOLEOBJ.CPP

/*
 * IOLEOBJ.CPP
 *
 * Template IOleObject interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "ioleobj.h"

/*
 * CImpIOleObject::CImpIOleObject
 * CImpIOleObject::~CImpIOleObject
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIOleObject::CImpIOleObject(LPVOID pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIOleObject::~CImpIOleObject(void)
    {
    return;
    }

/*
 * CImpIOleObject::QueryInterface
 * CImpIOleObject::AddRef
 * CImpIOleObject::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIOleObject.
 */

STDMETHODIMP CImpIOleObject::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIOleObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIOleObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIOleObject::SetClientSite
 *
 * Purpose:
 *  Provides the object with a pointer to the IOleClient site
 *  representing the container in which this object resides.
 *
 * Parameters:
 *  pIOleClientSite LPOLECLIENTSITE to the container's interface.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleObject::SetClientSite
    (LPOLECLIENTSITE pIOleClientSite)
    {
    return NOERROR;
    }

/*
 * CImpIOleObject::GetClientSite
 *
 * Purpose:
 *  Asks the object for the client site provided in SetClientSite.
 *  If you have not seen SetClientSite yet, return a NULL in
 *  ppIOleClientSite.
 *
 * Parameters:
 *  ppIOleClientSite    LPOLECLIENTSITE * in which to store
 *                      the pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleObject::GetClientSite(LPOLECLIENTSITE
    * ppIOleClientSite)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleObject::SetHostNames
 *
 * Purpose:
 *  Provides the object with names of the container application and
 *  the object in the container to use in object user interface.
 *
 * Parameters:
 *  pszApp          LPCSTR of the container application.
 *  pszObj          LPCSTR of some name useful in window titles.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CImpIOleObject::SetHostNames(LPCSTR pszApp
    , LPCSTR pszObj)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleObject::Close
 *
 * Purpose:
 *  Forces the object to close down its user interface and unload.
 *
 * Parameters:
 *  dwSaveOption    DWORD describing the circumstances under which
 *                  the object is being saved and closed.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::Close(DWORD dwSaveOption)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleObject::SetMoniker
 *
 * Purpose:
 *  Informs the object of its moniker or its container's moniker
 *  depending on dwWhich.
 *
 * Parameters:
 *  dwWhich         DWORD describing whether the moniker is the
 *                  object's or the container's.
 *  pmk             LPMONIKER with the name.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::SetMoniker(DWORD dwWhich
    , LPMONIKER pmk)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleObject::GetMoniker
 *
 * Purpose:
 *  Asks the object for a moniker that can later be used to
 *  reconnect to it.
 *
 * Parameters:
 *  dwAssign        DWORD determining how to assign the moniker to
 *                  to the object.
 *  dwWhich         DWORD describing which moniker the caller wants.
 *  ppmk            LPMONIKER * into which to store the moniker.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetMoniker(DWORD dwAssign
    , DWORD dwWhichMoniker, LPMONIKER * ppmk)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleObject::InitFromData
 *
 * Purpose:
 *  Initilizes the object from the contents of a data object.
 *
 * Parameters:
 *  pIDataObject    LPDATAOBJECT containing the data.
 *  fCreation       BOOL indicating if this is part of a new
 *                  creation.  If FALSE, the container is trying
 *                  to paste here.
 *  dwReserved      DWORD reserved.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::InitFromData(LPDATAOBJECT pIDataObject
    , BOOL fCreation, DWORD dwReserved)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleObject::GetClipboardData
 *
 * Purpose:
 *  Returns an IDataObject pointer to the caller representing what
 *  would be on the clipboard if the server did an Edit/Copy using
 *  OleSetClipboard.
 *
 * Parameters:
 *  dwReserved      DWORD reserved.
 *  ppIDataObj      LPDATAOBJECT * into which to store the
 *                  pointer.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetClipboardData(DWORD dwReserved
    , LPDATAOBJECT * ppIDataObj)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleObject::DoVerb
 *
 * Purpose:
 *  Executes an object-defined action.
 *
 * Parameters:
 *  iVerb           LONG index of the verb to execute.
 *  pMSG            LPMSG describing the event causing the
 *                  activation.
 *  pIOleClientSite LPOLECLIENTSITE to the site involved.
 *  lIndex          LONG the piece on which execution is happening.
 *  hWndParent      HWND of the window in which the object can play
 *                  in-place.
 *  pRectPos        LPRECT of the object in hWndParent where the
 *                  object can play in-place if desired.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::DoVerb(LONG iVerb, LPMSG PMSG
    , LPOLECLIENTSITE pIOleClientSite, LONG lIndex
    , HWND hWndParent, LPCRECT pRectPos)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleObject::EnumVerbs
 *
 * Purpose:
 *  Creates an enumerator that knows the object's verbs.  If you
 *  need to change the verb list dynamically, then you'll need to
 *  implement this, otherwise you can return OLE_S_USEREG.
 *
 * Parameters:
 *  ppEnum          LPENUMOLEVERB * into which to return the
 *                  enum.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::EnumVerbs(LPENUMOLEVERB * ppEnum)
    {
    //Trivial implementation if you fill the regDB.
    return OLE_S_USEREG;
    }

/*
 * CImpIOleObject::Update
 *
 * Purpose:
 *  Insures that the object is up to date.  This is mostly used for
 *  caching but you must make sure that you recursively call all
 *  nested objects you contain as well.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::Update(void)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleObject::IsUpToDate
 *
 * Purpose:
 *  Returns if the object is currently up to date, which involves
 *  asking all contained object inside this object if they are up
 *  to date as well.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, S_FALSE if dirty.
 */

STDMETHODIMP CImpIOleObject::IsUpToDate(void)
    {
    return NOERROR;
    }

/*
 * CImpIOleObject::GetUserClassID
 *
 * Purpose:
 *  Used for linked objects, this returns the class ID of what end
 *  users think they are editing.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store the CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetUserClassID(LPCLSID pClsID)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleObject::GetUserType
 *
 * Purpose:
 *  Determines the user-presentable name of the object.
 *
 * Parameters:
 *  dwForm          DWORD describing which form of the string
 *                  is desired.
 *  pszType         LPTSTR * into which to return the pointer to
 *                  the type string.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetUserType(DWORD dwForm
    , LPTSTR * ppszType)
    {
    return OLE_S_USEREG;
    }

/*
 * CImpIOleObject::SetExtent
 *
 * Purpose:
 *  Sets the size of the object in HIMETRIC units.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect affected.
 *  pszl            LPSIZEL containing the new size.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::SetExtent(DWORD dwAspect, LPSIZEL pszl)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleObject::GetExtent
 *
 * Purpose:
 *  Retrieves the size of the object in HIMETRIC units.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect requested
 *  pszl            LPSIZEL into which to store the size.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetExtent(DWORD dwAspect, LPSIZEL pszl)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleObject::Advise
 *
 * Purpose:
 *  Provides an IAdviseSink to the object for notifications.
 *
 * Parameters:
 *  pIAdviseSink    LPADVISESINK to notify.
 *  pdwConn         LPDWORD into which to store a connection key.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::Advise(LPADVISESINK pIAdviseSink
    , LPDWORD pdwConn)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleObject::Unadvise
 *
 * Purpose:
 *  Terminates a previous advise connection from Advise.
 *
 * Parameters:
 *  dwConn          DWORD connection key from Advise.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::Unadvise(DWORD dwConn)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleObject::EnumAdvise
 *
 * Purpose:
 *  Creates and returns a enumeration of the advises on this object.
 *
 * Parameters:
 *  ppEnum          LPENUMSTATDATA * in which to return the
 *                  enumerator.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::EnumAdvise(LPENUMSTATDATA *ppEnum)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIOleObject::GetMiscStatus
 *
 * Purpose:
 *  Returns a set of miscellaneous status flags for the object.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect in question.
 *  pdwStatus       LPDWORD in which to store the flags.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::GetMiscStatus(DWORD dwAspect
    , LPDWORD pdwStatus)
    {
    return OLE_S_USEREG;
    }

/*
 * CImpIOleObject::SetColorScheme
 *
 * Purpose:
 *  Provides the object with the color palette as recommended by
 *  the container application that also knows the palettes of other
 *  objects.  The object here is not required to use these colors.
 *
 * Parameters:
 *  pLP             LPLOGPALETTE providing the colors.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIOleObject::SetColorScheme(LPLOGPALETTE pLP)
    {
    return E_NOTIMPL;
    }


IOLEOBJ.H

/*
 * IOLEOBJ.H
 *
 * Definitions of a template IOleObject interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IOLEOBJ_H_
#define _IOLEOBJ_H_

class CImpIOleObject;
typedef class CImpIOleObject *PIMPIOLEOBJECT;

class CImpIOleObject : public IOleObject
    {
    private:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIOleObject(LPVOID, LPUNKNOWN);
        ~CImpIOleObject(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IOleObject members
        STDMETHODIMP SetClientSite(LPOLECLIENTSITE);
        STDMETHODIMP GetClientSite(LPOLECLIENTSITE *);
        STDMETHODIMP SetHostNames(LPCSTR, LPCSTR);
        STDMETHODIMP Close(DWORD);
        STDMETHODIMP SetMoniker(DWORD, LPMONIKER);
        STDMETHODIMP GetMoniker(DWORD, DWORD, LPMONIKER *);
        STDMETHODIMP InitFromData(LPDATAOBJECT, BOOL, DWORD);
        STDMETHODIMP GetClipboardData(DWORD, LPDATAOBJECT *);
        STDMETHODIMP DoVerb(LONG, LPMSG, LPOLECLIENTSITE, LONG
            , HWND, LPCRECT);
        STDMETHODIMP EnumVerbs(LPENUMOLEVERB *);
        STDMETHODIMP Update(void);
        STDMETHODIMP IsUpToDate(void);
        STDMETHODIMP GetUserClassID(CLSID *);
        STDMETHODIMP GetUserType(DWORD, LPTSTR *);
        STDMETHODIMP SetExtent(DWORD, LPSIZEL);
        STDMETHODIMP GetExtent(DWORD, LPSIZEL);
        STDMETHODIMP Advise(LPADVISESINK, DWORD *);
        STDMETHODIMP Unadvise(DWORD);
        STDMETHODIMP EnumAdvise(LPENUMSTATDATA *);
        STDMETHODIMP GetMiscStatus(DWORD, DWORD *);
        STDMETHODIMP SetColorScheme(LPLOGPALETTE);
    };

#endif //_IOLEOBJ_H_


IPARSEDN.CPP

/*
 * IPARSEDN.H
 *
 * Template IParseDisplayName interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iparsedn.h"

/*
 * CImpIParseDisplayName::CImpIParseDisplayName
 * CImpIParseDisplayName::~CImpIParseDisplayName
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIParseDisplayName::CImpIParseDisplayName(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIParseDisplayName::~CImpIParseDisplayName(void)
    {
    return;
    }

/*
 * CImpIParseDisplayName::QueryInterface
 * CImpIParseDisplayName::AddRef
 * CImpIParseDisplayName::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIParseDisplayName.
 */

STDMETHODIMP CImpIParseDisplayName::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIParseDisplayName::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIParseDisplayName::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIParseDisplayName::ParseDisplayName
 *
 * Purpose:
 *  Parses an object's display name into a moniker to the object.
 *
 * Parameters:
 *  pBindCtx        LPBC to the bind context in use.
 *  pszName         LPTSTR to the display name to parse.
 *  pchEaten        ULONG * in which to store the number of
 *                  characters parsed.
 *  ppmk            LPMONIKER * in which to store the moniker.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIParseDisplayName::ParseDisplayName(LPBC pBindCtx
    , LPTSTR, ULONG *, LPMONIKER *);
    {
    return E_NOTIMPL;
    }


IPARSEDN.H

/*
 * IPARSEDN.H
 *
 * Definitions of a template IParseDisplayName interface
 * implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IPARSEDN_H_
#define _IPARSEDN_H_

class CImpIParseDisplayName;
typedef class CImpIParseDisplayName *PIMPIPARSEDISPLAYNAME;

class CImpIParseDisplayName : public IParseDisplayName
    {
    private:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIParseDisplayName(LPVOID, LPUNKNOWN);
        ~CImpIParseDisplayName(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IParseDisplayName members
        STDMETHODIMP ParseDisplayName(LPBC, LPTSTR
            , ULONG *, LPMONIKER *);
    };

#endif //_IPARSEDN_H_


IPERFILE.CPP

/*
 * IPERFILE.CPP
 *
 * Template IPersistFile interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iperfile.h"

/*
 * CImpIPersistFile:CImpIPersistFile
 * CImpIPersistFile::~CImpIPersistFile
 *
 * Constructor Parameters:
 *  pObj            LPVOID pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistFile::CImpIPersistFile(LPVOID pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPersistFile::~CImpIPersistFile(void)
    {
    return;
    }

/*
 * CImpIPersistFile::QueryInterface
 * CImpIPersistFile::AddRef
 * CImpIPersistFile::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIPersistFile.
 */

STDMETHODIMP CImpIPersistFile::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistFile::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistFile::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistFile::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the file represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::GetClassID(LPCLSID pClsID)
    {
    return NOERROR;
    }

/*
 * CImpIPersistFile::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this object since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 */

STDMETHODIMP CImpIPersistFile::IsDirty(void)
    {
    return S_FALSE;
    }

/*
 * CImpIPersistFile::Load
 *
 * Purpose:
 *  Asks the server to load the document for the given filename.
 *
 * Parameters:
 *  pszFile         LPCSTR of the filename to load.
 *  grfMode         DWORD flags to use when opening the file.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::Load(LPCSTR pszFile, DWORD grfMode)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIPersistFile::Save
 *
 * Purpose:
 *  Instructs the server to write the current file into a new
 *  filename, possibly then using that filename as the current one.
 *
 * Parameters:
 *  pszFile         LPCSTR of the file into which we save.  If NULL,
 *                  this means save the current file.
 *  fRemember       BOOL indicating if we're to use this filename as
 *                  the current file now (Save As instead of Save
 *                  Copy As).
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::Save(LPCSTR pszFile, BOOL fRemember)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIPersistFile::SaveCompleted
 *
 * Purpose:
 *  Informs us that the operation that called Save is now finished
 *  and we can access the file again.
 *
 * Parameters:
 *  pszFile         LPCSTR of the file in which we can start
 *                  writing again.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::SaveCompleted(LPCSTR pszFile)
    {
    return NOERROR;
    }

/*
 * CImpIPersistFile::GetCurFile
 *
 * Purpose:
 *  Returns the current filename.
 *
 * Parameters:
 *  ppszFile        LPTSTR * into which we store a pointer to
 *                  the filename that should be allocated with the
 *                  shared IMalloc.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistFile::GetCurFile(LPTSTR *ppszFile)
    {
    return E_NOTIMPL;
    }


IPERFILE.H

/*
 * IPERFILE.H
 *
 * Definitions of a template IPersistFile interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IPERFILE_H_
#define _IPERFILE_H_

class CImpIPersistFile;
typedef class CImpIPersistFile *PIMPIPERSISTFILE;

class CImpIPersistFile : public IPersistFile
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIPersistFile(LPVOID, LPUNKNOWN);
        ~CImpIPersistFile(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP Load(LPCSTR, DWORD);
        STDMETHODIMP Save(LPCSTR, BOOL);
        STDMETHODIMP SaveCompleted(LPCSTR);
        STDMETHODIMP GetCurFile(LPTSTR *);
    };

#endif  //_IPERFILE_H_


IPERPBRO.CPP

/*
 * IPERPBRO.CPP
 *
 * Template IPerPropertyBrowsing interface implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iperpbro.h"

/*
 * CImpIPerPropertyBrowsing::CImpIPerPropertyBrowsing
 * CImpIPerPropertyBrowsing::~CImpIPerPropertyBrowsing
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIPerPropertyBrowsing::CImpIPerPropertyBrowsing(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPerPropertyBrowsing::~CImpIPerPropertyBrowsing(void)
    {
    return;
    }

/*
 * CImpIPerPropertyBrowsing::QueryInterface
 * CImpIPerPropertyBrowsing::AddRef
 * CImpIPerPropertyBrowsing::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIPerPropertyBrowsing.
 */

STDMETHODIMP CImpIPerPropertyBrowsing::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPerPropertyBrowsing::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPerPropertyBrowsing::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPerPropertyBrowsing::OnStatusChange
 *
 * Purpose:
 *  Informs the page site that the status of its page has changed
 *  according to the flags in dwFlags:
 *      PROPPAGESTATUS_DIRTY        A value was changed.
 *      PROPPAGESTATUS_VALIDATE     It's an appropriate time to
 *                                  validate and apply changes.
 *
 * Parameters:
 *  dwFlags         DWORD identifying the changes.
 */

STDMETHODIMP CImpIPerPropertyBrowsing::OnStatusChange(DWORD dwFlags)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPerPropertyBrowsing::GetDisplayString
 *
 * Purpose:
 *  Retrieves a description string for a property.
 *
 * Parameters:
 *  dispID          DISPID of the property in question.
 *  pbstr           BSTR * in which to return the string that
 *                  describes the property.  This can be NULL
 *                  indicating that the caller only wants to
 *                  know about availability of the string, not
 *                  the string itself.
 *
 * Return Value:
 *  HRESULT         NOERROR if a string is give, S_FALSE if
 *                  a default string is acceptable.
 */

STDMETHODIMP CImpIPerPropertyBrowsing::GetDisplayString
    (DISPID dispid, BSTR *pbstr)
    {
    if (NULL!=pbstr)
        *pbstr=NULL;

    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPerPropertyBrowsing::MapPropertyToPage
 *
 * Purpose:
 *  Maps a property with a given dispID to a property page CLSID
 *  that can be used to edit this property.
 *
 * Parameters:
 *  dispID          DISPID of the property in question
 *  pCLSID          CLSID * in which to return the CLSID of the
 *                  associated property page.
 *
 * Return Value:
 *  HRESULT         NOERROR if a page is available but not necessarily
 *                  needed to do editing.  S_FALSE if the page must be
 *                  used for editing, CTL_E_NOPAGEAVAILABLE otherwise.
 */

STDMETHODIMP CImpIPerPropertyBrowsing::MapPropertyToPage
    (DISPID dispid, LPCLSID pCLSID)
    {
    *pCLSID=CLSID_NULL;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPerPropertyBrowsing::GetPredefinedStrings
 *
 * Purpose:
 *  Allows the property to define known legal strings that a browser
 *  might display in a listbox.
 *
 * Parameters:
 *  dispID          DISPID identifying the property.
 *  pcaStringsOut   CALPOLESTR * in which to store the strings.
 *  pcaCookiesOut   CADWORD * providing identifiers for each string.
 */

STDMETHODIMP CImpIPerPropertyBrowsing::GetPredefinedStrings
    (DISPID dispid, CALPOLESTR *pcaStringsOut
    , CADWORD *pcaCookiesOut)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPerPropertyBrowsing::GetPredefinedValue
 *
 * Purpose:
 *  Allows the property to define a known legal value that a browser
 *  might display as a default or use in valudation.
 *
 * Parameters:
 *  dispID          DISPID identifying the property.
 *  dwCookie        DWORD identifying the value from
 *                  GetPredefinedStrings.
 *  pVarOut         VARIANT * in which to return the value.
 *
 */

STDMETHODIMP CImpIPerPropertyBrowsing::GetPredefinedValue
    (DISPID dispid, DWORD dwCookie, VARIANT *pVarOut)
    {
    return ResultFromScode(E_NOTIMPL);
    }


IPERPBRO.H

/*
 * IPERPBRO.H
 *
 * Header for template IPerPropertyBrowsing interface implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IPERPBRO_H_
#define _IPERPBRO_H_

#define INC_CONTROLS
#include <inole.h>

class CImpIPerPropertyBrowsing;
typedef class CImpIPerPropertyBrowsing *PCImpIPerPropertyBrowsing;

class CImpIPerPropertyBrowsing : public IPerPropertyBrowsing
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Backpointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIPerPropertyBrowsing(LPVOID, LPUNKNOWN);
        ~CImpIPerPropertyBrowsing(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP OnStatusChange(DWORD);
        STDMETHODIMP GetDisplayString(DISPID, BSTR *);
        STDMETHODIMP MapPropertyToPage(DISPID, LPCLSID);
        STDMETHODIMP GetPredefinedStrings(DISPID, CALPOLESTR *
        , CADWORD *);
        STDMETHODIMP GetPredefinedValue(DISPID, DWORD, VARIANT *);
    };

#endif //_IPERPBRO_H_


IPERSTMI.CPP

/*
 * IPERSTMI.CPP
 *
 * Template IPersistStreamInitInit interface implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iperstmi.h"

/*
 * CImpIPersistStreamInit:CImpIPersistStreamInit
 * CImpIPersistStreamInit::~CImpIPersistStreamInit
 *
 * Constructor Parameters:
 *  pObj            LPVOID pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistStreamInit::CImpIPersistStreamInit(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPersistStreamInit::~CImpIPersistStreamInit(void)
    {
    return;
    }

/*
 * CImpIPersistStreamInit::QueryInterface
 * CImpIPersistStreamInit::AddRef
 * CImpIPersistStreamInit::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIPersistStreamInit.
 */

STDMETHODIMP CImpIPersistStreamInit::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistStreamInit::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistStreamInit::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistStreamInit::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the object represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 */

STDMETHODIMP CImpIPersistStreamInit::GetClassID(LPCLSID pClsID)
    {
    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this object since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 */

STDMETHODIMP CImpIPersistStreamInit::IsDirty(void)
    {
    return ResultFromScode(S_FALSE);
    }

/*
 * CImpIPersistStreamInit::Load
 *
 * Purpose:
 *  Instructs the object to load itself from a previously saved
 *  IStreamInit that was handled by Save in another object lifetime.
 *  The seek pointer in this stream will be exactly the same as
 *  it was when Save was called, and this function must leave
 *  the seek pointer the same as it was on exit from Save, regardless
 *  of success or failure.  This function should not hold on to
 *  pIStream.
 *
 *  This function is called in lieu of IPersistStreamInit::InitNew
 *  when the object already has a persistent state.
 *
 * Parameters:
 *  pIStream        LPSTREAM from which to load.
 */

STDMETHODIMP CImpIPersistStreamInit::Load(LPSTREAM pIStream)
    {
    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::Save
 *
 * Purpose:
 *  Saves the data for this object to an IStreamInit.  Be sure not
 *  to change the position of the seek pointer on entry to this
 *  function: the caller will assume that you write from the
 *  current offset.  Leave the stream's seek pointer at the end
 *  of the data written on exit.
 *
 * Parameters:
 *  pIStream        LPSTREAM in which to save our data.
 *  fClearDirty     BOOL indicating if this call should clear
 *                  the object's dirty flag (TRUE) or leave it
 *                  unchanged (FALSE).
 */

STDMETHODIMP CImpIPersistStreamInit::Save(LPSTREAM pIStream
    , BOOL fClearDirty)
    {
    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::GetSizeMax
 *
 * Purpose:
 *  Returns the size of the data we would write if Save was
 *  called right now.
 *
 * Parameters:
 *  pcbSize         ULARGE_INTEGER * in which to save the size
 *                  of the stream an immediate call to Save would
 *                  write.
 */

STDMETHODIMP CImpIPersistStreamInit::GetSizeMax(ULARGE_INTEGER
    *pcbSize)
    {
    return NOERROR;
    }

/*
 * CImpIPersistStreamInit::InitNew
 *
 * Purpose:
 *  Informs the object that it is being created new instead of
 *  loaded from a persistent state.  This will be called in lieu
 *  of IPersistStreamInit::Load.
 *
 * Parameters:
 *  None
 */

STDMETHODIMP CImpIPersistStreamInit::InitNew(void)
    {
    return NOERROR;
    }


IPERSTMI.H

/*
 * IPERSTMI.H
 *
 * Definitions of a template IPersistStreamInit interface
 * implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IPERSTMI_H_
#define _IPERSTMI_H_

#define INC_CONTROLS
#include <inole.h>

class CImpIPersistStreamInit;
typedef class CImpIPersistStreamInit *PCImpIPersistStreamInit;

class CImpIPersistStreamInit : public IPersistStreamInit
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Backpointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIPersistStreamInit(LPVOID, LPUNKNOWN);
        ~CImpIPersistStreamInit(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP Load(LPSTREAM);
        STDMETHODIMP Save(LPSTREAM, BOOL);
        STDMETHODIMP GetSizeMax(ULARGE_INTEGER *);

        STDMETHODIMP InitNew(void);
    };

#endif  //_IPERSTMI_H_


IPERSTOR.CPP

/*
 * IPERSTOR.CPP
 *
 * Template IPersistStorage interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iperstor.h"

/*
 * CImpIPersistStorage:CImpIPersistStorage
 * CImpIPersistStorage::~CImpIPersistStorage
 *
 * Constructor Parameters:
 *  pObj            LPVOID pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistStorage::CImpIPersistStorage(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    m_psState=PSSTATE_UNINIT;
    return;
    }

CImpIPersistStorage::~CImpIPersistStorage(void)
    {
    return;
    }

/*
 * CImpIPersistStorage::QueryInterface
 * CImpIPersistStorage::AddRef
 * CImpIPersistStorage::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIPersistStorage.
 */

STDMETHODIMP CImpIPersistStorage::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistStorage::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistStorage::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistStorage::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the object represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::GetClassID(LPCLSID pClsID)
    {
    if (PSSTATE_UNINIT==m_psState)
        return E_UNEXPECTED;

    return NOERROR;
    }

/*
 * CImpIPersistStorage::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this object since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 */

STDMETHODIMP CImpIPersistStorage::IsDirty(void)
    {
    if (PSSTATE_UNINIT==m_psState)
        return E_UNEXPECTED;

    return S_FALSE;
    }

/*
 * CImpIPersistStorage::InitNew
 *
 * Purpose:
 *  Provides the object with the IStorage to hold on to while the
 *  object is running.  Here we initialize the structure of the
 *  storage and AddRef it for incremental access. This function will
 *  only be called once in the object's lifetime in lieu of Load.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE for the object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::InitNew(LPSTORAGE pIStorage)
    {
    if (PSSTATE_UNINIT!=m_psState)
        return E_UNEXPECTED;

    m_psState=PSSTATE_SCRIBBLE;

    //Hold on to pIStorage for Save(..., TRUE)
    return NOERROR;
    }

/*
 * CImpIPersistStorage::Load
 *
 * Purpose:
 *  Instructs the object to load itself from a previously saved
 *  IStorage that was handled by Save in another object lifetime.
 *  This function will only be called once in the object's lifetime
 *  in lieu of InitNew. The object should hold on to pIStorage here
 *  for incremental access and low-memory saves in Save.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE from which to load.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::Load(LPSTORAGE pIStorage)
    {
    if (PSSTATE_UNINIT!=m_psState)
        return E_UNEXPECTED;

    m_psState=PSSTATE_SCRIBBLE;

    //Hold on to pIStorage for Save(..., TRUE)
    return NOERROR;
    }

/*
 * CImpIPersistStorage::Save
 *
 * Purpose:
 *  Saves the data for this object to an IStorage which may
 *  or may not be the same as the one previously passed to
 *  Load, indicated with fSameAsLoad.  After this call we may
 *  not write into the storage again until SaveCompleted is
 *  called, although we may still read.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE in which to save our data.
 *  fSameAsLoad     BOOL indicating if this is the same pIStorage
 *                  that was passed to Load.  If TRUE, then the
 *                  object should write whatever it has *without
 *                  *using any extra memory* as this may be a low
 *                  memory save attempt.  That means that you must
 *                  not try to open or create streams.  If FALSE
 *                  you need to regenerate your whole storage
 *                  structure, being sure to also release any
 *                  pointers held from InitNew and Load.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::Save(LPSTORAGE pIStorage
    , BOOL fSameAsLoad)
    {
    //Have to come here from scribble state.
    if (PSSTATE_SCRIBBLE!=m_psState)
        return E_UNEXPECTED;

    /*
     * Save your data to your open storages and streams if
     * fSameAsLoad is FALSE, otherwise save to the new pIStorage
     * but don't change your held pointers--you only do that in
     * SaveCompleted if given a different pIStorage.
     *
     * Also, do not call pIStorage->Commit.  That's the caller's
     * responsibility.
     */

    m_psState=PSSTATE_ZOMBIE;

    //Clear your dirty flag.
    return NOERROR;
    }

/*
 * CImpIPersistStorage::SaveCompleted
 *
 * Purpose:
 *  Notifies the object that the storage in pIStorage has been
 *  completely saved now.  This is called when the user of this
 *  object wants to save us in a completely new storage, and if
 *  we normally hang on to the storage we have to reinitialize
 *  ourselves here for this new one that is now complete.
 *
 * Parameters:
 *  pIStorage       LPSTORAGE of the new storage in which we live.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::SaveCompleted(LPSTORAGE pIStorage)
    {
    //Must be called in no-scribble or hands-off state
    if (PSSTATE_ZOMBIE!=m_psState || PSSTATE_HANDSOFF!=m_psState)
        return E_UNEXPECTED;

    /If we're coming from Hands-Off, we'd better get a storage
    if (NULL==pIStorage && PSSTATE_HANDSOFF==m_psState)
        return E_UNEXPECTED;

    /*
     * If in Hands-Off state and pIStorage is non-NULL, then
     * reopen storages and streams and hold the pointers,
     * returning to scribble state.
     *
     * If in no-scribble state and pIStorage is non-NULL, then
     * release currently held pointers and reopen pointers
     * in pIStorage.
     *
     * If in no-scribble state and pIStorage is NULL, do not
     * change your held pointers.
     */

    //Return to scribble if there are no errors
    m_psState=PSSTATE_SCRIBBLE;
    return NOERROR;
    }

/*
 * CImpIPersistStorage::HandsOffStorage
 *
 * Purpose:
 *  Instructs the object that another agent is interested in having
 *  total access to the storage we might be hanging on to from
 *  InitNew or SaveCompleted.  In this case we must release our hold
 *  and await another call to SaveCompleted before we have a hold
 *  again.  Therefore we cannot read or write after this call until
 *  SaveCompleted.
 *
 *  Situations where this might happen arise in compound document
 *  scenarios where this object might be in-place active but the
 *  application wants to rename and commit the root storage.
 *  Therefore we are asked to close our hold, let the container
 *  party on the storage, then call us again later to tell us the
 *  new storage we can hold.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStorage::HandsOffStorage(void)
    {
    /*
     * Must come from scribble or no-scribble.  A repeated call
     * to HandsOffStorage is an unexpected error (bug in client).
     */
    if (PSSTATE_UNINIT==m_psState || PSSTATE_HANDSOFF==m_psState)
        return E_UNEXPECTED;

    /*
     * Release all held pointers regardless.
     */

    m_psState=PSSTATE_HANDSOFF;
    return NOERROR;
    }


IPERSTOR.H

/*
 * IPERSTOR.H
 *
 * Definitions of a template IPersistStorage interface
 * implementation.  Note that this template includes
 * state flags used in the implementation to track the
 * scribble, no scribble, and hands-off states.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IPERSTOR_H_
#define _IPERSTOR_H_

//State Flags
typedef enum
    {
    PSSTATE_UNINIT,     //Uninitialized
    PSSTATE_SCRIBBLE,   //Scribble
    PSSTATE_ZOMBIE,     //No scribble
    PSSTATE_HANDSOFF    //Hand-off
    } PSSTATE;

class CImpIPersistStorage;
typedef class CImpIPersistStorage *PIMPIPERSISTSTORAGE;

class CImpIPersistStorage : public IPersistStorage
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation
        PSSTATE         psState;     //Current State

    public:
        CImpIPersistStorage(LPVOID, LPUNKNOWN);
        ~CImpIPersistStorage(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP InitNew(LPSTORAGE);
        STDMETHODIMP Load(LPSTORAGE);
        STDMETHODIMP Save(LPSTORAGE, BOOL);
        STDMETHODIMP SaveCompleted(LPSTORAGE);
        STDMETHODIMP HandsOffStorage(void);
    };

#endif  //_IPERSTOR_H_


IPERSTRM.CPP

/*
 * IPERSTRM.CPP
 *
 * Template IPersistStream interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iperstrm.h"

/*
 * CImpIPersistStream:CImpIPersistStream
 * CImpIPersistStream::~CImpIPersistStream
 *
 * Constructor Parameters:
 *  pObj            LPVOID pointing to the object we live in.
 *  pUnkOuter       LPUNKNOWN of the controlling unknown.
 */

CImpIPersistStream::CImpIPersistStream(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPersistStream::~CImpIPersistStream(void)
    {
    return;
    }

/*
 * CImpIPersistStream::QueryInterface
 * CImpIPersistStream::AddRef
 * CImpIPersistStream::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIPersistStream.
 */

STDMETHODIMP CImpIPersistStream::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPersistStream::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPersistStream::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPersistStream::GetClassID
 *
 * Purpose:
 *  Returns the CLSID of the object represented by this interface.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store our CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStream::GetClassID(LPCLSID pClsID)
    {
    return NOERROR;
    }

/*
 * CImpIPersistStream::IsDirty
 *
 * Purpose:
 *  Tells the caller if we have made changes to this object since
 *  it was loaded or initialized new.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         Contains S_OK if we ARE dirty, S_FALSE if
 *                  NOT dirty.
 */

STDMETHODIMP CImpIPersistStream::IsDirty(void)
    {
    return S_FALSE;
    }

/*
 * CImpIPersistStream::Load
 *
 * Purpose:
 *  Instructs the object to load itself from a previously saved
 *  IStream that was handled by Save in another object lifetime.
 *  The seek pointer in this stream will be exactly the same as
 *  it was when Save was called, and this function must leave
 *  the seek pointer the same as it was on exit from Save, regardless
 *  of success or failure.  This function should not hold on to
 *  pIStream.
 *
 * Parameters:
 *  pIStream        LPSTREAM from which to load.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStream::Load(LPSTREAM pIStream)
    {
    return NOERROR;
    }

/*
 * CImpIPersistStream::Save
 *
 * Purpose:
 *  Saves the data for this object to an IStream.  Be sure not
 *  to change the position of the seek pointer on entry to this
 *  function: the caller will assume that you write from the
 *  current offset.  Leave the stream's seek pointer at the end
 *  of the data written on exit.
 *
 * Parameters:
 *  pIStream        LPSTREAM in which to save our data.
 *  fClearDirty     BOOL indicating if this call should clear
 *                  the object's dirty flag (TRUE) or leave it
 *                  unchanged (FALSE).
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStream::Save(LPSTREAM pIStream
    , BOOL fClearDirty)
    {
    return NOERROR;
    }

/*
 * CImpIPersistStream::GetSizeMax
 *
 * Purpose:
 *  Returns the size of the data we would write if Save was
 *  called right now.
 *
 * Parameters:
 *  pcbSize         ULARGE_INTEGER * in which to save the size
 *                  of the stream an immediate call to Save would
 *                  write.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIPersistStream::GetSizeMax(ULARGE_INTEGER
    * pcbSize)
    {
    return NOERROR;
    }


IPERSTRM.H

/*
 * IPERSTRM.H
 *
 * Definitions of a template IPersistStream interface
 * implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IPERSTRM_H_
#define _IPERSTRM_H_

class CImpIPersistStream;
typedef class CImpIPersistStream *PIMPIPERSISTSTREAM;

class CImpIPersistStream : public IPersistStream
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIPersistStream(LPVOID, LPUNKNOWN);
        ~CImpIPersistStream(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassID(LPCLSID);

        STDMETHODIMP IsDirty(void);
        STDMETHODIMP Load(LPSTREAM);
        STDMETHODIMP Save(LPSTREAM, BOOL);
        STDMETHODIMP GetSizeMax(ULARGE_INTEGER *);
    };

#endif  //_IPERSTRM_H_


IPROPNOT.CPP

/*
 * IPROPNOT.CPP
 *
 * Template implementation of IPropertyNotifySink
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "ipropnot.h"

/*
 * CImpIPropertyNotifySink::CImpIPropertyNotifySink
 * CImpIPropertyNotifySink::~CImpIPropertyNotifySink
 *
 * Constructor Parameters:
 *  pObj            LPVOID of the object containing us.
 *  pUnkOuter       LPUNKNOWN to which we blindly delegate
 *                  all IUnknown calls.
 */

CImpIPropertyNotifySink::CImpIPropertyNotifySink(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPropertyNotifySink::~CImpIPropertyNotifySink(void)
    {
    return;
    }

/*
 * CImpIPropertyNotifySink::QueryInterface
 * CImpIPropertyNotifySink::AddRef
 * CImpIPropertyNotifySink::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIPropertyNotifySink.
 */

STDMETHODIMP CImpIPropertyNotifySink::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPropertyNotifySink::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPropertyNotifySink::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPropertyNotifySink::OnChanged
 *
 * Purpose:
 *  Notifies this sink that the property identified with dispID
 *  has changed in whatever object this sink is connected to.
 *
 * Parameters:
 *  dispID          DISPID of the property that changed,
 *                  which can be DISPID_UNKNOWN for unspecified
 *                  changes to multiple properties.
 *
 * Return Value:
 *  HRESULT         NOERROR always.
 */

STDMETHODIMP CImpIPropertyNotifySink::OnChanged(DISPID dispID)
    {
    return NOERROR;
    }

/*
 * CImpIPropertyNotifySink::OnRequestEdit
 *
 * Purpose:
 *  Notifies this sink that the property identified with dispID
 *  is about to change and that the sink can prevent the change
 *  if desired.  This can be used to enforce read-only states or
 *  to save prior states before the change occurs.
 *
 * Parameters:
 *  dispID          DISPID of the property that is changing
 *                  which can be DISPID_UNKNOWN for multiple
 *                  properties.
 *
 * Return Value:
 *  HRESULT         NOERROR if the property can change, S_FALSE
 *                  if it cannot change, error code otherwise.
 */

STDMETHODIMP CImpIPropertyNotifySink::OnRequestEdit(DISPID dispID)
    {
    return NOERROR;
    }


IPROPNOT.H

/*
 * IPROPNOT.H
 *
 * Header for template IPropertyNotifySink interface implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IPROPNOT_H_
#define _IPROPNOT_H_

#define INC_CONTROLS
#include <inole.h>

class CImpIPropertyNotifySink;
typedef class CImpIPropertyNotifySink *PCImpIPropertyNotifySink;

class CImpIPropertyNotifySink : public IPropertyNotifySink
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Backpointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIPropertyNotifySink(LPVOID, LPUNKNOWN);
        ~CImpIPropertyNotifySink(void);

        //IPropertyNotifySink interface members
        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP OnChanged(DISPID);
        STDMETHODIMP OnRequestEdit(DISPID);
    };

#endif //_IPROPNOT_H_


IPROPPG.CPP

/*
 * IPROPPG.CPP
 *
 * Template IPropertyPage interface implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iproppg.h"

/*
 * CImpIPropertyPage::CImpIPropertyPage
 * CImpIPropertyPage::~CImpIPropertyPage
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIPropertyPage::CImpIPropertyPage(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPropertyPage::~CImpIPropertyPage(void)
    {
    return;
    }

/*
 * CImpIPropertyPage::QueryInterface
 * CImpIPropertyPage::AddRef
 * CImpIPropertyPage::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIPropertyPage.
 */

STDMETHODIMP CImpIPropertyPage::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPropertyPage::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPropertyPage::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPropertyPage::SetPageSite
 *
 * Purpose:
 *  Provides the property page with the IPropertyPageSite
 *  that contains it.  SetPageSite(NULL) will be called as
 *  part of the close sequence.
 *
 * Parameters:
 *  pPageSite       LPPROPERTYPAGESITE pointer to the site.
 */

STDMETHODIMP CImpIPropertyPage::SetPageSite
    (LPPROPERTYPAGESITE pPageSite)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPage::Activate
 *
 * Purpose:
 *  Instructs the property page to create a window in which to
 *  display its contents, using the given parent window and
 *  rectangle.  The window should be initially visible.
 *
 * Parameters:
 *  hWndParent      HWND of the parent window.
 *  prc             LPCRECT of the rectangle to use.
 *  fModal          BOOL indicating whether the frame is modal.
 */

STDMETHODIMP CImpIPropertyPage::Activate(HWND hWndParent
    , LPCRECT prc, BOOL fModal)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPage::Deactivate
 *
 * Purpose:
 *  Instructs the property page to destroy its window that was
 *  created in Activate.
 *
 * Parameters:
 *  None
 */

STDMETHODIMP CImpIPropertyPage::Deactivate(void)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPage::GetPageInfo
 *
 * Purpose:
 *  Fills a PROPPAGEINFO structure describing the page's size,
 *  contents, and help information.
 *
 * Parameters:
 *  pPageInfo       LPPROPPAGEINFO to the structure to fill.
 */

STDMETHODIMP CImpIPropertyPage::GetPageInfo(LPPROPPAGEINFO pPageInfo)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPage::SetObjects
 *
 * Purpose:
 *  Identifies the objects that are being affected by this property
 *  page (and all other pages in the frame).  These are the object
 *  to which to send new property values in the Apply member.
 *
 * Parameters:
 *  cObjects        ULONG number of objects
 *  ppUnk           IUnknown ** to the array of objects being
 *                  passed to the page.
 */

STDMETHODIMP CImpIPropertyPage::SetObjects(ULONG cObjects
    , IUnknown **ppunk)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPage::Show
 *
 * Purpose:
 *  Instructs the page to show or hide its window created in
 *  Activate.
 *
 * Parameters:
 *  nCmdShow        UINT to pass to ShowWindow.
 */

STDMETHODIMP CImpIPropertyPage::Show(UINT nCmdShow)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPage::Move
 *
 * Purpose:
 *  Instructs the property page to change its position.
 *
 * Parameters:
 *  prc             LPCRECT containing the new position.
 */

STDMETHODIMP CImpIPropertyPage::Move(LPCRECT prc)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPage::IsPageDirty
 *
 * Purpose:
 *  Asks the page if anything's changed in it, that is, if the
 *  property values in the page are out of sync with the objects
 *  under consideration.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR if dirty, S_FALSE if not.
 */

STDMETHODIMP CImpIPropertyPage::IsPageDirty(void)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPage::Apply
 *
 * Purpose:
 *  Instructs the page to send changes in its page to whatever
 *  objects it knows about through SetObjects.  This is the only
 *  time the page should change the objects' properties, and not
 *  when the value is changed on the page.
 *
 * Parameters:
 *  None
 */

STDMETHODIMP CImpIPropertyPage::Apply(void)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPage::Help
 *
 * Purpose:
 *  Invokes help for this property page.
 *
 * Parameters:
 *  pszHelpDir      LPCOLESTR identifying the default location of
 *                  the help information
 */

STDMETHODIMP CImpIPropertyPage::Help(LPCOLESTR pszHelpDir)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPage::TranslateAccelerator
 *
 * Purpose:
 *  Provides the page with the messages that occur in the frame.
 *  This gives the page to do whatever it wants with the message,
 *  such as handle keyboard mnemonics.
 *
 * Parameters:
 *  pMsg            LPMSG containing the message of the accelerator.
 */

STDMETHODIMP CImpIPropertyPage::TranslateAccelerator(LPMSG lpMsg)
    {
    return ResultFromScode(E_NOTIMPL);
    }


IPROPPG.H

/*
 * IPROPPG.H
 *
 * Header for template IPropertyPage interface implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IPROPPG_H_
#define _IPROPPG_H_

#define INC_CONTROLS
#include <inole.h>

class CImpIPropertyPage;
typedef class CImpIPropertyPage *PCImpIPropertyPage;

class CImpIPropertyPage : public IPropertyPage
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIPropertyPage(LPVOID, LPUNKNOWN);
        ~CImpIPropertyPage(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

    STDMETHODIMP SetPageSite(LPPROPERTYPAGESITE);
    STDMETHODIMP Activate(HWND, LPCRECT, BOOL);
    STDMETHODIMP Deactivate(void);
    STDMETHODIMP GetPageInfo(LPPROPPAGEINFO);
    STDMETHODIMP SetObjects(ULONG, LPUNKNOWN *);
    STDMETHODIMP Show(UINT);
    STDMETHODIMP Move(LPCRECT);
    STDMETHODIMP IsPageDirty(void);
    STDMETHODIMP Apply(void);
    STDMETHODIMP Help(LPCOLESTR);
    STDMETHODIMP TranslateAccelerator(LPMSG);
    };

#endif //_IPROPPG_H_


IPROPPG2.CPP

/*
 * IPROPPG2.CPP
 *
 * Template IPropertyPage2 interface implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iproppg2.h"

/*
 * CImpIPropertyPage2::CImpIPropertyPage2
 * CImpIPropertyPage2::~CImpIPropertyPage2
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIPropertyPage2::CImpIPropertyPage2(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPropertyPage2::~CImpIPropertyPage2(void)
    {
    return;
    }

/*
 * CImpIPropertyPage2::QueryInterface
 * CImpIPropertyPage2::AddRef
 * CImpIPropertyPage2::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIPropertyPage2.
 */

STDMETHODIMP CImpIPropertyPage2::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPropertyPage2::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPropertyPage2::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPropertyPage2::SetPageSite
 *
 * Purpose:
 *  Provides the property page with the IPropertyPageSite
 *  that contains it.  SetPageSite(NULL) will be called as
 *  part of the close sequence.
 *
 * Parameters:
 *  pPageSite       LPPROPERTYPAGESITE pointer to the site.
 */

STDMETHODIMP CImpIPropertyPage2::SetPageSite
    (LPPROPERTYPAGESITE pPageSite)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPage2::Activate
 *
 * Purpose:
 *  Instructs the property page to create a window in which to
 *  display its contents, using the given parent window and
 *  rectangle.  The window should be initially visible.
 *
 * Parameters:
 *  hWndParent      HWND of the parent window.
 *  prc             LPCRECT of the rectangle to use.
 *  fModal          BOOL indicating whether the frame is modal.
 */

STDMETHODIMP CImpIPropertyPage2::Activate(HWND hWndParent
    , LPCRECT prc, BOOL fModal)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPage2::Deactivate
 *
 * Purpose:
 *  Instructs the property page to destroy its window that was
 *  created in Activate.
 *
 * Parameters:
 *  None
 */

STDMETHODIMP CImpIPropertyPage2::Deactivate(void)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPage2::GetPageInfo
 *
 * Purpose:
 *  Fills a PROPPAGEINFO structure describing the page's size,
 *  contents, and help information.
 *
 * Parameters:
 *  pPageInfo       LPPROPPAGEINFO to the structure to fill.
 */

STDMETHODIMP CImpIPropertyPage2::GetPageInfo(LPPROPPAGEINFO pPageInfo)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPage2::SetObjects
 *
 * Purpose:
 *  Identifies the objects that are being affected by this property
 *  page (and all other pages in the frame).  These are the object
 *  to which to send new property values in the Apply member.
 *
 * Parameters:
 *  cObjects        ULONG number of objects
 *  ppUnk           IUnknown ** to the array of objects being
 *                  passed to the page.
 */

STDMETHODIMP CImpIPropertyPage2::SetObjects(ULONG cObjects
    , IUnknown **ppunk)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPage2::Show
 *
 * Purpose:
 *  Instructs the page to show or hide its window created in
 *  Activate.
 *
 * Parameters:
 *  nCmdShow        UINT to pass to ShowWindow.
 */

STDMETHODIMP CImpIPropertyPage2::Show(UINT nCmdShow)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPage2::Move
 *
 * Purpose:
 *  Instructs the property page to change its position.
 *
 * Parameters:
 *  prc             LPCRECT containing the new position.
 */

STDMETHODIMP CImpIPropertyPage2::Move(LPCRECT prc)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPage2::IsPageDirty
 *
 * Purpose:
 *  Asks the page if anything's changed in it, that is, if the
 *  property values in the page are out of sync with the objects
 *  under consideration.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR if dirty, S_FALSE if not.
 */

STDMETHODIMP CImpIPropertyPage2::IsPageDirty(void)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPage2::Apply
 *
 * Purpose:
 *  Instructs the page to send changes in its page to whatever
 *  objects it knows about through SetObjects.  This is the only
 *  time the page should change the objects' properties, and not
 *  when the value is changed on the page.
 *
 * Parameters:
 *  None
 */

STDMETHODIMP CImpIPropertyPage2::Apply(void)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPage2::Help
 *
 * Purpose:
 *  Invokes help for this property page.
 *
 * Parameters:
 *  pszHelpDir      LPCOLESTR identifying the default location of
 *                  the help information
 */

STDMETHODIMP CImpIPropertyPage2::Help(LPCOLESTR pszHelpDir)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPage2::TranslateAccelerator
 *
 * Purpose:
 *  Provides the page with the messages that occur in the frame.
 *  This gives the page to do whatever it wants with the message,
 *  such as handle keyboard mnemonics.
 *
 * Parameters:
 *  pMsg            LPMSG containing the message of the accelerator.
 */

STDMETHODIMP CImpIPropertyPage2::TranslateAccelerator(LPMSG lpMsg)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPage2::EditProperty
 *
 * Purpose:
 *  Informs the property page to speifically set focus to a
 *  particular property identified with dispID.  Support for this
 *  method is optional.
 *
 * Parameters:
 *  dispID          DISPID of the property to highlight.
 */

STDMETHODIMP CImpIPropertyPage2::EditProperty(DISPID dispid)
    {
    return ResultFromScode(E_NOTIMPL);
    }


IPROPPG2.H

/*
 * IPROPPG2.H
 *
 * Header for template IPropertyPage2 interface implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IPROPPG2_H_
#define _IPROPPG2_H_

#define INC_CONTROLS
#include <inole.h>

class CImpIPropertyPage2;
typedef class CImpIPropertyPage2 *PCImpIPropertyPage2;

class CImpIPropertyPage2 : public IPropertyPage2
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Backpointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIPropertyPage2(LPVOID, LPUNKNOWN);
        ~CImpIPropertyPage2(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

    STDMETHODIMP SetPageSite(LPPROPERTYPAGESITE);
    STDMETHODIMP Activate(HWND, LPCRECT, BOOL);
    STDMETHODIMP Deactivate(void);
    STDMETHODIMP GetPageInfo(LPPROPPAGEINFO);
    STDMETHODIMP SetObjects(ULONG, LPUNKNOWN *);
    STDMETHODIMP Show(UINT);
    STDMETHODIMP Move(LPCRECT);
    STDMETHODIMP IsPageDirty(void);
    STDMETHODIMP Apply(void);
    STDMETHODIMP Help(LPCOLESTR);
    STDMETHODIMP TranslateAccelerator(LPMSG);

        STDMETHODIMP EditProperty(DISPID dispid);
    };

#endif //_IPROPPG2_H_


IPROPPSI.CPP

/*
 * IPROPPSI.CPP
 *
 * Template IPropertyPageSite interface implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iproppsi.h"

/*
 * CImpIPropertyPageSite::CImpIPropertyPageSite
 * CImpIPropertyPageSite::~CImpIPropertyPageSite
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIPropertyPageSite::CImpIPropertyPageSite(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIPropertyPageSite::~CImpIPropertyPageSite(void)
    {
    return;
    }

/*
 * CImpIPropertyPageSite::QueryInterface
 * CImpIPropertyPageSite::AddRef
 * CImpIPropertyPageSite::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIPropertyPageSite.
 */

STDMETHODIMP CImpIPropertyPageSite::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIPropertyPageSite::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIPropertyPageSite::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIPropertyPageSite::OnStatusChange
 *
 * Purpose:
 *  Informs the page site that the status of its page has changed
 *  according to the flags in dwFlags:
 *      PROPPAGESTATUS_DIRTY        A value was changed.
 *      PROPPAGESTATUS_VALIDATE     It's an appropriate time to
 *                                  validate and apply changes.
 *
 * Parameters:
 *  dwFlags         DWORD identifying the changes.
 */

STDMETHODIMP CImpIPropertyPageSite::OnStatusChange(DWORD dwFlags)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPageSite::GetLocaleID
 *
 * Purpose:
 *  Retrieves the LCID for the page frame, if the page wants to
 *  localize to this locale.
 *
 * Parameters:
 *  plcid           LCID * in which to store the LCID.
 */

STDMETHODIMP CImpIPropertyPageSite::GetLocaleID(LCID *plcid)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPageSite::GetPageContainer
 *
 * Purpose:
 *  Retrieves a pointer to the property frame.
 *
 * Parameters:
 *  ppUnk           IUnknown ** in which to return the frame's
 *                  IUnknown pointer.
 */

STDMETHODIMP CImpIPropertyPageSite::GetPageContainer(IUnknown **ppUnk)
    {
    *ppUnk=NULL;
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpIPropertyPageSite::TranslateAccelerator
 *
 * Purpose:
 *  Provides the page site with the oportunity to process messages.
 *
 * Parameters:
 *  pMsg            LPMSG containing the message of the accelerator.
 */

STDMETHODIMP CImpIPropertyPageSite::TranslateAccelerator(LPMSG lpMsg)
    {
    return ResultFromScode(E_NOTIMPL);
    }


IPROPPSI.H

/*
 * IPROPPSI.H
 *
 * Header for template IPropertyPageSite interface implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IPROPPSI_H_
#define _IPROPPSI_H_

#define INC_CONTROLS
#include <inole.h>

class CImpIPropertyPageSite;
typedef class CImpIPropertyPageSite *PCImpIPropertyPageSite;

class CImpIPropertyPageSite : public IPropertyPageSite
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Backpointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIPropertyPageSite(LPVOID, LPUNKNOWN);
        ~CImpIPropertyPageSite(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

    STDMETHODIMP OnStatusChange(DWORD);
    STDMETHODIMP GetLocaleID(LCID *);
    STDMETHODIMP GetPageContainer(LPUNKNOWN *);
    STDMETHODIMP TranslateAccelerator(LPMSG);
    };

#endif //_IPROPPSI_H_


IPROVINF.CPP

/*
 * IPROVINF.CPP
 *
 * Template IProvideClassInfo interface implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iprovinf.h"

/*
 * CImpIProvideClassInfo::CImpIProvideClassInfo
 * CImpIProvideClassInfo::~CImpIProvideClassInfo
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIProvideClassInfo::CImpIProvideClassInfo(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIProvideClassInfo::~CImpIProvideClassInfo(void)
    {
    return;
    }

/*
 * CImpIProvideClassInfo::QueryInterface
 * CImpIProvideClassInfo::AddRef
 * CImpIProvideClassInfo::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIProvideClassInfo.
 */

STDMETHODIMP CImpIProvideClassInfo::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIProvideClassInfo::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIProvideClassInfo::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIProvideClassInfo::GetClassInfo
 *
 * Purpose:
 *  Returns the ITypeInfo through which the caller can retrieve
 *  information about the entire object.
 *
 * Parameters:
 *  ppTI            LPTYPEINFO * in which to store the ITypeInfo
 *                  pointer.
 */

STDMETHODIMP CImpIProvideClassInfo::GetClassInfo(LPTYPEINFO *ppTI)
    {
    return ResultFromScode(E_NOTIMPL);
    }


IPROVINF.H

/*
 * IPROVINF.H
 *
 * Header for template IProvideClassInfo interface implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IPROVINF_H_
#define _IPROVINF_H_

#define INC_CONTROLS
#include <inole.h>

class CImpIProvideClassInfo;
typedef class CImpIProvideClassInfo *PCImpIProvideClassInfo;

class CImpIProvideClassInfo : public IProvideClassInfo
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Backpointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIProvideClassInfo(LPVOID, LPUNKNOWN);
        ~CImpIProvideClassInfo(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetClassInfo(LPTYPEINFO *);
    };

#endif //_IPROVINF_H_


IRUNOBJ.CPP

/*
 * IRUNOBJ.CPP
 *
 * Definitions of a template IRunnableObject interface
 * implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "irunobj.h"

/*
 * CImpIRunnableObject::CImpIRunnableObject
 * CImpIRunnableObject::~CImpIRunnableObject
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIRunnableObject::CImpIRunnableObject(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIRunnableObject::~CImpIRunnableObject(void)
    {
    return;
    }

/*
 * CImpIRunnableObject::QueryInterface
 * CImpIRunnableObject::AddRef
 * CImpIRunnableObject::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIRunnableObject.
 */

STDMETHODIMP CImpIRunnableObject::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIRunnableObject::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIRunnableObject::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIRunnableObject::GetRunningClass
 *
 * Purpose:
 *  Returns the CLSID of the object.
 *
 * Parameters:
 *  pClsID          LPCLSID in which to store the CLSID.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIRunnableObject::GetRunningClass(LPCLSID pClsID)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIRunnableObject::Run
 *
 * Purpose:
 *  Run an object in the given bind context, that is, put the object
 *  into the running state.
 *
 * Parameters:
 *  pBindCtx        LPBINDCTX of the bind context to use.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIRunnableObject::Run(LPBINDCTX pBindCtx)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIRunnableObject::IsRunning
 *
 * Purpose:
 *  Answers whether an object is currently in the running state.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  BOOL            Indicates the running state of the object.
 */

STDMETHODIMP_(BOOL) CImpIRunnableObject::IsRunning(void)
    {
    return FALSE;
    }

/*
 * CImpIRunnableObject::LockRunning
 *
 * Purpose:
 *  Locks an already running object into the running state or unlocks
 *  it from such a state.
 *
 * Parameters:
 *  fLock               BOOL indicating lock (TRUE) or unlock
 *                      (FALSE)
 *  fLastUnlockCloses   BOOL indicating if the last call to this
 *                      function with fLock==FALSE closes the
 *                      object.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIRunnableObject::LockRunning(BOOL fLock
    , BOOL fLastUnlockCloses)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIRunnableObject::SetContainedObject
 *
 * Purpose:
 *  Informs the object (embedded object) that it is inside a
 *  compound document container.
 *
 * Parameters:
 *  fContained      BOOL indicating if the object is now contained.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIRunnableObject::SetContainedObject(BOOL fContained)
    {
    return E_NOTIMPL;
    }


IRUNOBJ.H

/*
 * IRUNOBJ.H
 *
 * Definitions of a template IRunnableObject interface
 * implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IRUNOBJ_H_
#define _IRUNOBJ_H_

class CImpIRunnableObject;
typedef class CImpIRunnableObject *PIMPIRUNNABLEOBJECT;

class CImpIRunnableObject : public IRunnableObject
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpIRunnableObject(LPVOID, LPUNKNOWN);
        ~CImpIRunnableObject(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetRunningClass(LPCLSID);
        STDMETHODIMP Run(LPBINDCTX);
        STDMETHODIMP_(BOOL) IsRunning(void);
        STDMETHODIMP LockRunning(BOOL, BOOL);
        STDMETHODIMP SetContainedObject(BOOL);
    };

#endif //_IRUNOBJ_H_


ISIMPSIT.CPP

/*
 * ISIMPSIT.CPP
 *
 * Template ISimpleFrameSite interface implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "isimpsit.h"

/*
 * CImpISimpleFrameSite::CImpISimpleFrameSite
 * CImpISimpleFrameSite::~CImpISimpleFrameSite
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpISimpleFrameSite::CImpISimpleFrameSite(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpISimpleFrameSite::~CImpISimpleFrameSite(void)
    {
    return;
    }

/*
 * CImpISimpleFrameSite::QueryInterface
 * CImpISimpleFrameSite::AddRef
 * CImpISimpleFrameSite::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpISimpleFrameSite.
 */

STDMETHODIMP CImpISimpleFrameSite::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpISimpleFrameSite::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpISimpleFrameSite::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpISimpleFrameSite::PreMessageFilter
 *
 * Purpose:
 *  Allows the simple frame to pre-process messages that are
 *  going to controls within it.
 *
 * Parameters:
 *  Same as to a window procedure, plus:
 *  plResult        LRESULT * in which to store the return value
 *                  for the message.
 *  pdwCookie       DWORD * in which to store extra information to
 *                  pass to PostMessageFilter
 */

STDMETHODIMP CImpISimpleFrameSite::PreMessageFilter(HWND hWnd
    , UINT iMsg, WPARAM wParam, LPARAM lParam, LRESULT *plResult
    , DWORD *pdwCookie)
    {
    return ResultFromScode(E_NOTIMPL);
    }

/*
 * CImpISimpleFrameSite::PostMessageFilter
 *
 * Purpose:
 *  Allows the simple frame to post-process messages that are
 *  going to controls within it.
 *
 * Parameters:
 *  Same as to a window procedure, plus:
 *  plResult        LRESULT * in which to store the return value
 *                  for the message.
 *  dwCookie        DWORD from PreMessageFilter
 */

STDMETHODIMP CImpISimpleFrameSite::PostMessageFilter(HWND hWnd
    , UINT iMsg, WPARAM wParam, LPARAM lParam, LRESULT *plResult
    , DWORD dwCookie)
    {
    return ResultFromScode(E_NOTIMPL);
    }


ISIMPSIT.H

/*
 * ISIMPSIT.H
 *
 * Header for template ISimpleFrameSite interface implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _ISIMPSIT_H_
#define _ISIMPSIT_H_

#define INC_CONTROLS
#include <inole.h>

class CImpISimpleFrameSite;
typedef class CImpISimpleFrameSite *PCImpISimpleFrameSite;

class CImpISimpleFrameSite : public ISimpleFrameSite
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Backpointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpISimpleFrameSite(LPVOID, LPUNKNOWN);
        ~CImpISimpleFrameSite(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP CImpISimpleFrameSite::PreMessageFilter(HWND
            , UINT, WPARAM, LPARAM, LRESULT *, DWORD *);
        STDMETHODIMP CImpISimpleFrameSite::PostMessageFilter(HWND
            , UINT, WPARAM, LPARAM, LRESULT *, DWORD);
    };

#endif //_ISIMPSIT_H_


ISPPROPP.CPP

/*
 * ISPPROPP.CPP
 *
 * Template ISpecifyPropertyPages interface implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "isppropp.h"

/*
 * CImpISpecifyPropertyPages::CImpISpecifyPropertyPages
 * CImpISpecifyPropertyPages::~CImpISpecifyPropertyPages
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpISpecifyPropertyPages::CImpISpecifyPropertyPages(LPVOID pObj
    , LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpISpecifyPropertyPages::~CImpISpecifyPropertyPages(void)
    {
    return;
    }

/*
 * CImpISpecifyPropertyPages::QueryInterface
 * CImpISpecifyPropertyPages::AddRef
 * CImpISpecifyPropertyPages::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpISpecifyPropertyPages.
 */

STDMETHODIMP CImpISpecifyPropertyPages::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpISpecifyPropertyPages::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpISpecifyPropertyPages::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpISpecifyPropertyPages::GetPages
 *
 * Purpose:
 *  Returns an array of GUIDs identifying the indivudual property
 *  pages used for this object.
 *
 * Parameters:
 *  pPages          CAUUID * pointing to a counted array of GUIDs.
 *                  This function allocates the array elements
 *                  and stores them in this structure.
 */

STDMETHODIMP CImpISpecifyPropertyPages::GetPages(CAUUID *pPages)
    {
    return ResultFromScode(E_NOTIMPL);
    }


ISPPROPP.H

/*
 * ISPPROPP.H
 *
 * Header for template ISpecifyPropertyPages interface implementation.
 *
 * Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Microsoft
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _ISPPROPP_H_
#define _ISPPROPP_H_

#define INC_CONTROLS
#include <inole.h>

class CImpISpecifyPropertyPages;
typedef class CImpISpecifyPropertyPages *PCImpISpecifyPropertyPages;

class CImpISpecifyPropertyPages : public ISpecifyPropertyPages
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Backpointer to the object
        LPUNKNOWN       m_pUnkOuter; //For delegation

    public:
        CImpISpecifyPropertyPages(LPVOID, LPUNKNOWN);
        ~CImpISpecifyPropertyPages(void);

        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        STDMETHODIMP GetPages(CAUUID *);
    };

#endif //_ISPPROPP_H_


IUNKNOWN.CPP

/*
 * IUNKNOWN.CPP
 *
 * Template implementation of a delegating IUnknown.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iunknown.h"

/*
 * CImpIUnknown::CImpIUnknown
 * CImpIUnknown::~CImpIUnknown
 *
 * Constructor Parameters:
 *  pObj            LPVOID of the object containing us.
 *  pUnkOuter       LPUNKNOWN to which we blindly delegate
 *                  all IUnknown calls.
 */

CImpIUnknown::CImpIUnknown(LPVOID pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;

    /*
     * We don't need to AddRef pUnkOuter since it defines
     * our lifetime.
     */
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIUnknown::~CImpIUnknown(void)
    {
    return;
    }

/*
 * CImpIUnknown::QueryInterface
 *
 * Purpose:
 *  Delegate to whoever is providing IUnknown implementations
 *  for this interface.  May be our containing object, may be
 *  a controlling unknown.  We don't care.
 *
 * Parameters:
 *  riid            REFIID being asked for.
 *  ppv             LPVOID * in which to return the interface.
 *
 * Return Value:
 *  HRESULT         NOERROR if successful, otherwise contains
 *                  E_NOINTERFACE.
 */

STDMETHODIMP CImpIUnknown::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

/*
 * CImpIUnknown::AddRef
 *
 * Purpose:
 *  Increments the interface reference count (for debugging) and
 *  delegates to whoever is controlling this interface.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  ULONG           New reference count of the object.
 */

STDMETHODIMP_(ULONG) CImpIUnknown::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

/*
 * CImpIUnknown::Release
 *
 * Purpose:
 *  Decrements the interface reference count (for debugging) and
 *  delegates to whoever is controlling this interface.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  ULONG           Current reference count after decrement.  If
 *                  this returns zero then the interface is no
 *                  longer valid because the object is dead.
 */

STDMETHODIMP_(ULONG) CImpIUnknown::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }


IUNKNOWN.H

/*
 * IUNKNOWN.H
 *
 * Definitions and function prototypes for a template IUnknown
 * interface implementation that delegates all IUnknown calls
 * to some object controller, ignorant of aggregation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IUNKNOWN_H_
#define _IUNKNOWN_H_

class CImpIUnknown;
typedef class CImpIUnknown *PIMPIUNKNOWN;

class CImpIUnknown : public IUnknown
    {
    protected:
        ULONG           m_cRef;      //Interface reference count
        LPVOID          m_pObj;      //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter; //Fr delegation

    public:
        CImpIUnknown(LPVOID, LPUNKNOWN);
        ~CImpIUnknown(void);

        //IUnknown interface members
        STDMETHODIMP QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);
    };

#endif //_IUNKNOWN_H_


IVIEWOB2.CPP

/*
 * IVIEWOB2.CPP
 *
 * Template IViewObject2 interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iviewobj.h"

/*
 * CImpIViewObject2::CImpIViewObject2
 * CImpIViewObject2::~CImpIViewObject2
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIViewObject2::CImpIViewObject2(LPVOID pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIViewObject2::~CImpIViewObject2(void)
    {
    return;
    }

/*
 * CImpIViewObject2::QueryInterface
 * CImpIViewObject2::AddRef
 * CImpIViewObject2::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIViewObject2.
 */

STDMETHODIMP CImpIViewObject2::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIViewObject2::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIViewObject2::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIViewObject2::Draw
 *
 * Purpose:
 *  Draws the object on the given hDC specifically for the requested
 *  aspect, device, and within the appropriate bounds.
 *
 * Parameters:
 *  dwAspect        DWORD aspect to draw.
 *  lindex          LONG index of the piece to draw.
 *  pvAspect        LPVOID for extra information, always NULL.
 *  ptd             DVTARGETDEVICE * containing device
 *                  information.
 *  hICDev          HDC containing the IC for the device.
 *  hDC             HDC on which to draw.
 *  pRectBounds     LPCRECTL describing the rectangle in which
 *                  to draw.
 *  pRectWBounds    LPCRECTL describing the placement rectangle
 *                  if part of what you draw is another metafile.
 *  pfnContinue     Function to call periodically during
 *                  long repaints.
 *  dwContinue      DWORD extra information to pass to the
 *                  pfnContinue.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject2::Draw(DWORD dwAspect, LONG lindex
    , void * pvAspect, DVTARGETDEVICE * ptd, HDC hICDev
    , HDC hDC, LPCRECTL pRectBounds, LPCRECTL pRectWBounds
    , BOOL (CALLBACK * pfnContinue) (DWORD), DWORD dwContinue)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIViewObject2::GetColorSet
 *
 * Purpose:
 *  Retrieves the color palette used by the object.
 *
 * Parameters:
 *  dwAspect        DWORD aspect of interest.
 *  lindex          LONG piece of interest.
 *  pvAspect        LPVOID with extra information, always NULL.
 *  ptd             DVTARGETDEVICE * containing device info.
 *  hICDev          HDC containing the IC for the device.
 *  ppColorSet      LPLOGPALETTE * into which to return the
 *                  pointer to the palette in this color set.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject2::GetColorSet(DWORD dwDrawAspect
    , LONG lindex, LPVOID pvAspect, DVTARGETDEVICE * ptd
    , HDC hICDev, LPLOGPALETTE * ppColorSet)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIViewObject2::Freeze
 *
 * Purpose:
 *  Freezes the view of a particular aspect such that data
 *  changes do not affect the view.
 *
 * Parameters:
 *  dwAspect        DWORD aspect to freeze.
 *  lindex          LONG piece index under consideration.
 *  pvAspect        LPVOID for further information, always NULL.
 *  pdwFreeze       LPDWORD in which to return the key.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject2::Freeze(DWORD dwAspect, LONG lindex
    , LPVOID pvAspect, LPDWORD pdwFreeze)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIViewObject2::Unfreeze
 *
 * Purpose:
 *  Thaws an aspect frozen in Freeze.
 *
 * Parameters:
 *  dwFreeze        DWORD key returned from Freeze.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject2::Unfreeze(DWORD dwFreeze)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIViewObject2::SetAdvise
 *
 * Purpose:
 *  Provides an advise sink to the view object enabling
 *  notifications for a specific aspect.
 *
 * Parameters:
 *  dwAspects       DWORD describing the aspects of interest.
 *  dwAdvf          DWORD containing advise flags.
 *  pIAdviseSink    LPADVISESINK to notify.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject2::SetAdvise(DWORD dwAspects
    , DWORD dwAdvf, LPADVISESINK pIAdviseSink)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIViewObject2::GetAdvise
 *
 * Purpose:
 *  Returns the last known IAdviseSink seen by SetAdvise.
 *
 * Parameters:
 *  pdwAspects      LPDWORD in which to store the last
 *                  requested aspects.
 *  pdwAdvf         LPDWORD in which to store the last
 *                  requested flags.
 *  ppIAdvSink      LPADVISESINK * in which to store the
 *                  IAdviseSink.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject2::GetAdvise(DWORD *pAspects
    , DWORD *pAdvf, LPADVISESINK *ppAdvSink)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIViewObject2::GetExtent
 *
 * Purpose:
 *  Retrieves the extents of the object's display.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect of interest.
 *  lindex          LONG index of the piece of interest.
 *  ptd             DVTARGETDEVICE * with device information.
 *  pszl            LPSIZEL to the structure in which to return
 *                  the extents.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject2::GetExtent(DWORD dwAspect, LONG lindex
    , DVTARGETDEVICE *ptd, LPSIZEL pszl)
    {
    return E_NOTIMPL;
    }


IVIEWOB2.H

/*
 * IVIEWOB2.H
 *
 * Definitions of a template IViewObject2 interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IVIEWOBJ_H_
#define _IVIEWOBJ_H_

class CImpIViewObject2;
typedef class CImpIViewObject2 *PIMPIVIEWOBJECT2;

class CImpIViewObject2 : public IViewObject
    {
    private:
        ULONG           m_cRef;         //Interface reference count
        LPVOID          m_pObj;         //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter;    //For delegation

    public:
        CImpIViewObject2(LPVOID, LPUNKNOWN);
        ~CImpIViewObject2(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IViewObject members
        STDMETHODIMP Draw(DWORD, LONG, LPVOID, DVTARGETDEVICE *
            , HDC, HDC, LPCRECTL, LPCRECTL, BOOL (CALLBACK *)(DWORD)
            , DWORD);

        STDMETHODIMP GetColorSet(DWORD, LONG, LPVOID
            , DVTARGETDEVICE *, HDC, LPLOGPALETTE *);

        STDMETHODIMP Freeze(DWORD, LONG, LPVOID, LPDWORD);
        STDMETHODIMP Unfreeze(DWORD);
        STDMETHODIMP SetAdvise(DWORD, DWORD, LPADVISESINK);
        STDMETHODIMP GetAdvise(LPDWORD, LPDWORD, LPADVISESINK *);

        //IViewObject2 members
        STDMETHODIMP GetExtent(DWORD, LONG, DVTARGETDEVICE *
            , LPSIZEL);
    };

#endif //_IVIEWOBJ_H_


IVIEWOBJ.CPP

/*
 * IVIEWOBJ.CPP
 *
 * Template IViewObject2 interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#include "iviewobj.h"

/*
 * CImpIViewObject2::CImpIViewObject2
 * CImpIViewObject2::~CImpIViewObject2
 *
 * Parameters (Constructor):
 *  pObj            LPVOID of the object we're in.
 *  pUnkOuter       LPUNKNOWN to which we delegate.
 */

CImpIViewObject2::CImpIViewObject2(LPVOID pObj, LPUNKNOWN pUnkOuter)
    {
    m_cRef=0;
    m_pObj=pObj;
    m_pUnkOuter=pUnkOuter;
    return;
    }

CImpIViewObject2::~CImpIViewObject2(void)
    {
    return;
    }

/*
 * CImpIViewObject2::QueryInterface
 * CImpIViewObject2::AddRef
 * CImpIViewObject2::Release
 *
 * Purpose:
 *  Delegating IUnknown members for CImpIViewObject2.
 */

STDMETHODIMP CImpIViewObject2::QueryInterface(REFIID riid
    , LPVOID *ppv)
    {
    return m_pUnkOuter->QueryInterface(riid, ppv);
    }

STDMETHODIMP_(ULONG) CImpIViewObject2::AddRef(void)
    {
    ++m_cRef;
    return m_pUnkOuter->AddRef();
    }

STDMETHODIMP_(ULONG) CImpIViewObject2::Release(void)
    {
    --m_cRef;
    return m_pUnkOuter->Release();
    }

/*
 * CImpIViewObject2::Draw
 *
 * Purpose:
 *  Draws the object on the given hDC specifically for the requested
 *  aspect, device, and within the appropriate bounds.
 *
 * Parameters:
 *  dwAspect        DWORD aspect to draw.
 *  lindex          LONG index of the piece to draw.
 *  pvAspect        LPVOID for extra information, always NULL.
 *  ptd             DVTARGETDEVICE * containing device
 *                  information.
 *  hICDev          HDC containing the IC for the device.
 *  hDC             HDC on which to draw.
 *  pRectBounds     LPCRECTL describing the rectangle in which
 *                  to draw.
 *  pRectWBounds    LPCRECTL describing the placement rectangle
 *                  if part of what you draw is another metafile.
 *  pfnContinue     Function to call periodically during
 *                  long repaints.
 *  dwContinue      DWORD extra information to pass to the
 *                  pfnContinue.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject2::Draw(DWORD dwAspect, LONG lindex
    , void * pvAspect, DVTARGETDEVICE * ptd, HDC hICDev
    , HDC hDC, LPCRECTL pRectBounds, LPCRECTL pRectWBounds
    , BOOL (CALLBACK * pfnContinue) (DWORD), DWORD dwContinue)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIViewObject2::GetColorSet
 *
 * Purpose:
 *  Retrieves the color palette used by the object.
 *
 * Parameters:
 *  dwAspect        DWORD aspect of interest.
 *  lindex          LONG piece of interest.
 *  pvAspect        LPVOID with extra information, always NULL.
 *  ptd             DVTARGETDEVICE * containing device info.
 *  hICDev          HDC containing the IC for the device.
 *  ppColorSet      LPLOGPALETTE * into which to return the
 *                  pointer to the palette in this color set.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject2::GetColorSet(DWORD dwDrawAspect
    , LONG lindex, LPVOID pvAspect, DVTARGETDEVICE * ptd
    , HDC hICDev, LPLOGPALETTE * ppColorSet)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIViewObject2::Freeze
 *
 * Purpose:
 *  Freezes the view of a particular aspect such that data
 *  changes do not affect the view.
 *
 * Parameters:
 *  dwAspect        DWORD aspect to freeze.
 *  lindex          LONG piece index under consideration.
 *  pvAspect        LPVOID for further information, always NULL.
 *  pdwFreeze       LPDWORD in which to return the key.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject2::Freeze(DWORD dwAspect, LONG lindex
    , LPVOID pvAspect, LPDWORD pdwFreeze)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIViewObject2::Unfreeze
 *
 * Purpose:
 *  Thaws an aspect frozen in Freeze.
 *
 * Parameters:
 *  dwFreeze        DWORD key returned from Freeze.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject2::Unfreeze(DWORD dwFreeze)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIViewObject2::SetAdvise
 *
 * Purpose:
 *  Provides an advise sink to the view object enabling
 *  notifications for a specific aspect.
 *
 * Parameters:
 *  dwAspects       DWORD describing the aspects of interest.
 *  dwAdvf          DWORD containing advise flags.
 *  pIAdviseSink    LPADVISESINK to notify.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject2::SetAdvise(DWORD dwAspects
    , DWORD dwAdvf, LPADVISESINK pIAdviseSink)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIViewObject2::GetAdvise
 *
 * Purpose:
 *  Returns the last known IAdviseSink seen by SetAdvise.
 *
 * Parameters:
 *  pdwAspects      LPDWORD in which to store the last
 *                  requested aspects.
 *  pdwAdvf         LPDWORD in which to store the last
 *                  requested flags.
 *  ppIAdvSink      LPADVISESINK * in which to store the
 *                  IAdviseSink.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject2::GetAdvise(DWORD *pAspects
    , DWORD *pAdvf, LPADVISESINK *ppAdvSink)
    {
    return E_NOTIMPL;
    }

/*
 * CImpIViewObject2::GetExtent
 *
 * Purpose:
 *  Retrieves the extents of the object's display.
 *
 * Parameters:
 *  dwAspect        DWORD of the aspect of interest.
 *  lindex          LONG index of the piece of interest.
 *  ptd             DVTARGETDEVICE * with device information.
 *  pszl            LPSIZEL to the structure in which to return
 *                  the extents.
 *
 * Return Value:
 *  HRESULT         NOERROR or a general error value.
 */

STDMETHODIMP CImpIViewObject2::GetExtent(DWORD dwAspect, LONG lindex
    , DVTARGETDEVICE *ptd, LPSIZEL pszl)
    {
    return E_NOTIMPL;
    }


IVIEWOBJ.H

/*
 * IVIEWOBJ.H
 *
 * Definitions of a template IViewObject2 interface implementation.
 *
 * Copyright (c)1993-1996 Microsoft Corporation, All Right Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */

#ifndef _IVIEWOBJ_H_
#define _IVIEWOBJ_H_

class CImpIViewObject2;
typedef class CImpIViewObject2 *PIMPIVIEWOBJECT;

class CImpIViewObject2 : public IViewObject
    {
    private:
        ULONG           m_cRef;         //Interface reference count
        LPVOID          m_pObj;         //Back pointer to the object
        LPUNKNOWN       m_pUnkOuter;    //For delegation

    public:
        CImpIViewObject2(LPVOID, LPUNKNOWN);
        ~CImpIViewObject2(void);

        //IUnknown members that delegate to m_pUnkOuter.
        STDMETHODIMP         QueryInterface(REFIID, LPVOID *);
        STDMETHODIMP_(ULONG) AddRef(void);
        STDMETHODIMP_(ULONG) Release(void);

        //IViewObject members
        STDMETHODIMP Draw(DWORD, LONG, LPVOID, DVTARGETDEVICE *
            , HDC, HDC, LPCRECTL, LPCRECTL, BOOL (CALLBACK *)(DWORD)
            , DWORD);

        STDMETHODIMP GetColorSet(DWORD, LONG, LPVOID
            , DVTARGETDEVICE *, HDC, LPLOGPALETTE *);

        STDMETHODIMP Freeze(DWORD, LONG, LPVOID, LPDWORD);
        STDMETHODIMP Unfreeze(DWORD);
        STDMETHODIMP SetAdvise(DWORD, DWORD, LPADVISESINK);
        STDMETHODIMP GetAdvise(LPDWORD, LPDWORD, LPADVISESINK *);

        //IViewObject2 members
        STDMETHODIMP GetExtent(DWORD, LONG, DVTARGETDEVICE *
            , LPSIZEL);
    };

#endif //_IVIEWOBJ_H_


ACCOUNT.CPP

// Filename: Account.cpp
//
// Description: Implementation of DLL Exports.
//
// This file is provided as part of the Microsoft Transaction Server Samples
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
// WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
// PURPOSE.
//
// Copyright (C) 1997 Microsoft Corporation, All rights reserved
//
//
// You will need the NT SUR Beta 2 SDK or VC 4.2 in order to build this
// project.  This is because you will need MIDL 3.00.15 or higher and new
// headers and libs.  If you have VC 4.2 installed, then everything should
// already be configured correctly.

// Note: Proxy/Stub Information
//To merge the proxy/stub code into the object DLL, add the file
//dlldatax.c to the project.  Make sure precompiled headers
//are turned off for this file, and add _MERGE_PROXYSTUB to the
//defines for the project.
//
//Modify the custom build rule for Account.idl by adding the following
//files to the Outputs.  You can select all of the .IDL files by
//expanding each project and holding Ctrl while clicking on each of them.
//Account_p.c
//dlldata.c
//To build a separate proxy/stub DLL,
//run nmake -f Accountps.mak in the project directory.

#include "stdafx.h"
#include "resource.h"
#include "initguid.h"
#include "Account.h"
#include "CAccount.h"
#include "MoveMoney.h"
//
// to merge proxy/stub, uncomment this line
// #include "dlldatax.h"
//
#define IID_DEFINED
#include "Account_i.c"
#include "GetReceipt.h"
#include "UpdateReceipt.h"

#ifdef _MERGE_PROXYSTUB
extern "C" HINSTANCE hProxyDll;
#endif

CComModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_CUpdateReceipt, CUpdateReceipt)
OBJECT_ENTRY(CLSID_CGetReceipt, CGetReceipt)
OBJECT_ENTRY(CLSID_CAccount, CAccount)
OBJECT_ENTRY(CLSID_CMoveMoney, CMoveMoney)
END_OBJECT_MAP()

/////////////////////////////////////////////////////////////////////////////
// DLL Entry Point

extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
lpReserved;
#ifdef _MERGE_PROXYSTUB
if (!PrxDllMain(hInstance, dwReason, lpReserved))
return FALSE;
#endif
if (dwReason == DLL_PROCESS_ATTACH)
{
_Module.Init(ObjectMap, hInstance);
DisableThreadLibraryCalls(hInstance);
}
else if (dwReason == DLL_PROCESS_DETACH)
_Module.Term();
return TRUE;    // ok
}

/////////////////////////////////////////////////////////////////////////////
// Used to determine whether the DLL can be unloaded by OLE

STDAPI DllCanUnloadNow(void)
{
#ifdef _MERGE_PROXYSTUB
if (PrxDllCanUnloadNow() != S_OK)
return S_FALSE;
#endif
return (_Module.GetLockCount()==0) ? S_OK : S_FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// Returns a class factory to create an object of the requested type

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
#ifdef _MERGE_PROXYSTUB
if (PrxDllGetClassObject(rclsid, riid, ppv) == S_OK)
return S_OK;
#endif
return _Module.GetClassObject(rclsid, riid, ppv);
}

/////////////////////////////////////////////////////////////////////////////
// DllRegisterServer - Adds entries to the system registry

STDAPI DllRegisterServer(void)
{
#ifdef _MERGE_PROXYSTUB
HRESULT hRes = PrxDllRegisterServer();
if (FAILED(hRes))
return hRes;
#endif
// registers object, typelib and all interfaces in typelib
return _Module.RegisterServer(TRUE);
}

/////////////////////////////////////////////////////////////////////////////
// DllUnregisterServer - Removes entries from the system registry

STDAPI DllUnregisterServer(void)
{
#ifdef _MERGE_PROXYSTUB
PrxDllUnregisterServer();
#endif
_Module.UnregisterServer();
return S_OK;
}


ACCOUNT.IDL

// Filename: Account.idl
//
// Description: IDL source for Account.dll
// This file will be processed by the MIDL tool to
// produce the type library (Account.tlb) and marshalling code
//
// This file is provided as part of the Microsoft Transaction Server Samples
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
// WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
// PURPOSE.
//
// Copyright (C) 1997 Microsoft Corporation, All rights reserved

#include <mtxattr.h>

[
object,
uuid(04CF0B72-1989-11D0-B917-0080C7394688),
dual,
helpstring("IAccount Interface"),
pointer_default(unique)

]
interface IAccount : IDispatch
{
import "oaidl.idl";
HRESULT Post(
[in] long lAccount,
[in] long lAmount,
[out,retval] BSTR* pbstrResult);
};

[
object,
uuid(04CF0B77-1989-11D0-B917-0080C7394688),
dual,
helpstring("IMoveMoney Interface"),
pointer_default(unique)

]
interface IMoveMoney : IDispatch
{
import "oaidl.idl";
HRESULT Perform(
[in] long lPrimeAccount,
[in] long lSecondAccount,
[in] long lAmount,
[in] long lTranType,
[out, retval] BSTR* pbstrResult);
};

[
object,
uuid(A81260B1-DDC8-11D0-B5A0-00C04FB957D8),
dual,
helpstring("IGetReceipt Interface"),
pointer_default(unique)

]
interface IGetReceipt : IDispatch
{
import "oaidl.idl";
HRESULT GetNextReceipt (
[out,retval] long* plReceiptNo);
};

[
object,
uuid(A81260B7-DDC8-11D0-B5A0-00C04FB957D8),
dual,
helpstring("IUpdateReceipt Interface"),
pointer_default(unique)

]
interface IUpdateReceipt : IDispatch
{
import "oaidl.idl";
HRESULT Update (
[out,retval] long* plReceiptNo);
};

[
uuid(04CF0B70-1989-11D0-B917-0080C7394688),
version(1.0),
helpstring("Sample Bank Account (VC version)")
]
library ACCOUNT
{
importlib("stdole2.tlb");

[
uuid(04CF0B76-1989-11D0-B917-0080C7394688),
helpstring("Account Class"),
TRANSACTION_REQUIRED
]
coclass CAccount
{
[default] interface IAccount;
};

[
uuid(04CF0B7B-1989-11D0-B917-0080C7394688),
helpstring("MoveMoney Class"),
TRANSACTION_REQUIRED
]
coclass CMoveMoney
{
[default] interface IMoveMoney;
};

[
uuid(A81260B2-DDC8-11D0-B5A0-00C04FB957D8),
helpstring("GetReceipt Class"),
TRANSACTION_SUPPORTED
]
coclass CGetReceipt
{
[default] interface IGetReceipt;
};

[
uuid(A81260B8-DDC8-11D0-B5A0-00C04FB957D8),
helpstring("UpdateReceipt Class"),
TRANSACTION_REQUIRES_NEW
]
coclass CUpdateReceipt
{
[default] interface IUpdateReceipt;
};

};


CACCOUNT.CPP

// Filename: CAccount.cpp
//
// Description: Implementation of CAccount
//
// This file is provided as part of the Microsoft Transaction Server
// Software Development Kit
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
// WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
// PURPOSE.
//
// Copyright (C) 1997 Microsoft Corporation, All rights reserved
#include "stdafx.h"
#include "Account.h"
#include "CAccount.h"
#include <stdio.h>

#include <mtx.h>

#include <adoid.h>
#include <adoint.h>

/////////////////////////////////////////////////////////////////////////////
//

STDMETHODIMP CAccount::InterfaceSupportsErrorInfo(REFIID riid)
{
static const IID* arr[] =
{
&IID_IAccount,
};

for (int i=0;i<sizeof(arr)/sizeof(arr[0]);i++)
{
if (InlineIsEqualGUID(*arr[i],riid))
return S_OK;
}
return S_FALSE;
}

//Post: modifies the specified account by the specified amount
//
//pbstrResult: a BSTR giving information.  NOTE: this is set to NULL when an error occurs
//
//returns:  S_OK or E_FAIL

STDMETHODIMP CAccount::Post (IN long lAccount, IN long lAmount, OUT BSTR* pbstrResult) {

HRESULT hr = S_OK;

IObjectContext* pObjectContext = NULL;

ADOConnection* adoCoConnection = NULL;
ADORecordset* adoRsBalance = NULL;
ADOFields* pFields = NULL;
ADOField* pField = NULL;

*pbstrResult = NULL;

long lErrFlag = 0;
TCHAR* pErrMsg = NULL;

try {

// Get our object context
THROW_ERR (GetObjectContext(&pObjectContext), "GetObjectContext" );

// Check security for large transfers
if (lAmount > 500 || lAmount < -500) {
BOOL bInRole;
BSTR bstrRole;

bstrRole = ::SysAllocString(L"Managers");
hr = pObjectContext->IsCallerInRole (bstrRole, &bInRole);
::SysFreeString(bstrRole);

if (!SUCCEEDED ( hr )) {
THROW_STR ( _T("IsCallerInRole() call failed!  Please add the 'Managers' Roll to the package."));
}

if (!bInRole) {
THROW_STR ( _T("Need 'Managers' role for amounts over $500") );
}
}

// Create ADOConnection object and initialize the connection
THROW_ERR ( CoCreateInstance (CLSID_CADOConnection, NULL, CLSCTX_INPROC_SERVER, IID_IADOConnection,
(LPVOID *) &adoCoConnection), "CoCreateInstance(CADOConnection)" );

BSTR bstrDSN = ::SysAllocString (L"FILEDSN=MTSSamples");
hr = adoCoConnection->Open (bstrDSN, NULL, NULL, adCmdUnspecified);
::SysFreeString (bstrDSN);
RETHROW_ERR(hr);

// Update the account balance and
// then check to see if account would be overdrawn
TCHAR szBuffer [512];
wsprintf (szBuffer, _T("UPDATE Account SET Balance = Balance + %li WHERE AccountNo = %li"),
lAmount, lAccount);
BSTR bstrSQL = TCHAR2BSTR (szBuffer);
CComVariant vRecordCount;
vRecordCount.Clear();
hr = adoCoConnection->Execute(bstrSQL, &vRecordCount, -1, NULL);
::SysFreeString (bstrSQL);
RETHROW_ERR(hr);

// If no records got updated, the account doesn't exist
if (vRecordCount.lVal == 0) {
TCHAR szBuffer1 [512];
wsprintf (szBuffer1, _T("Error. Account %ld not on file."), lAccount);
THROW_STR ( szBuffer1 );
}

wsprintf (szBuffer, _T("SELECT Balance FROM Account WHERE AccountNo = %ld"), lAccount);
bstrSQL = TCHAR2BSTR (szBuffer);
hr = adoCoConnection->Execute (bstrSQL, &vRecordCount, -1, &adoRsBalance);
::SysFreeString (bstrSQL);
RETHROW_ERR(hr);
// Get the appropriate fields
hr =  adoRsBalance->get_Fields(&pFields);
RETHROW_ERR(hr);

// Get the appropriate field
CComVariant vField = L"Balance";
CComVariant vBalance;
hr = pFields->get_Item (vField, &pField) ;
RETHROW_ERR(hr);
hr = pField->get_Value (&vBalance) ;
RETHROW_ERR(hr);

// Check for overdraw, then prepare return string
if (vBalance.lVal < 0) {

TCHAR szBuffer2 [512];
wsprintf (szBuffer2, _T("Error. Account %ld would be overdrawn by %ld.  Balance is still %ld"),
lAccount, vBalance.lVal, vBalance.lVal - lAmount);
THROW_STR ( szBuffer2 );
}

TCHAR szBuffer3 [512];
wsprintf (szBuffer3, _T("%s account %ld, balance is $%ld. (VC++)"),
((lAmount >= 0) ? _T("Credit to") : _T("Debit from")), lAccount, vBalance.lVal);
*pbstrResult = TCHAR2BSTR (szBuffer3);

// Resource cleanup
if (pField) pField->Release();
if (pFields) pFields->Release();
if (adoRsBalance) adoRsBalance->Release();
if (adoCoConnection) adoCoConnection->Release();

// We are finished and happy
pObjectContext->SetComplete();

} catch (HRESULT hr) {

//
//ErrorInfo is saved here because the following ADO cleanup code
//may clear it.
//
IErrorInfo * pErrorInfo = NULL;
GetErrorInfo(NULL, &pErrorInfo);

if (pField)pField->Release();
if (pFields) pFields->Release();
if (adoRsBalance) adoRsBalance->Release();
if (adoCoConnection) adoCoConnection->Release();

// Fill in error information
switch (lErrFlag) {

// Unknown error occurred in this object
case (0):
TCHAR szErr [512];
wsprintf (szErr, _T("Error 0x%x from CAccount calling %s."), hr, pErrMsg);
pErrMsg = szErr;
// Fall through

// An application error occurred in this object
case (1):
//
//we are going to put our own error in TLS, so if there is one there, clear it
//
if (pErrorInfo)
pErrorInfo -> Release();

AtlReportError( CLSID_CAccount, pErrMsg, IID_IAccount, hr);
break;
case (2): // ADO error
{
//
//put the error back in TLS
//
SetErrorInfo(NULL, pErrorInfo);

}
break;
// Will never reach here
default:
break;
}

// Indicate our unhappiness
if (pObjectContext)
pObjectContext->SetAbort();

}

if (pObjectContext)pObjectContext->Release();

return hr;
}


CACCOUNT.H

// Filename: CAccount.h
//
// Description: Declaration of CAccount
//
// This file is provided as part of the Microsoft Transaction Server Samples
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
// WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
// PURPOSE.
//
// Copyright (C) 1997 Microsoft Corporation, All rights reserved

#include "resource.h"       // main symbols

/////////////////////////////////////////////////////////////////////////////
// Account

class CAccount :
public CComDualImpl<IAccount, &IID_IAccount, &LIBID_ACCOUNT>,
public ISupportErrorInfo,
public CComObjectRoot,
public CComCoClass<CAccount,&CLSID_CAccount>
{
public:
CAccount() {}
BEGIN_COM_MAP(CAccount)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IAccount)
COM_INTERFACE_ENTRY(ISupportErrorInfo)
END_COM_MAP()
//DECLARE_NOT_AGGREGATABLE(CAccount)
// Remove the comment from the line above if you don't want your object to
// support aggregation.  The default is to support it

DECLARE_REGISTRY_RESOURCEID(IDR_Account)
// ISupportsErrorInfo
STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);

// IAccount
public:
STDMETHOD(Post)(
IN long lAccount,
IN long lAmount,
OUT BSTR* pbstrResult);
};


GETRECEIPT.CPP

// Filename: GetReceipt.cpp
//
// Description: Implementation of CGetReceipt
//
// This file is provided as part of the Microsoft Transaction Server Samples
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
// WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
// PURPOSE.
//
// Copyright (C) 1997 Microsoft Corporation, All rights reserved

#include "stdafx.h"
#include "Account.h"
#include "GetReceipt.h"
#include "UpdateReceipt.h"

#include <mtx.h>
#include <mtxspm.h>

/////////////////////////////////////////////////////////////////////////////
//

STDMETHODIMP CGetReceipt::InterfaceSupportsErrorInfo(REFIID riid)
{
static const IID* arr[] =
{
&IID_IGetReceipt,
};

for (int i=0;i<sizeof(arr)/sizeof(arr[0]);i++)
{
if (InlineIsEqualGUID(*arr[i],riid))
return S_OK;
}
return S_FALSE;
}

//GetNextReceipt: return the next receipt number
//
//returns: S_OK or E_FAIL

STDMETHODIMP CGetReceipt::GetNextReceipt (OUT long* plReceiptNo) {

HRESULT hr = S_OK;

IObjectContext* pObjectContext = NULL;

ISharedPropertyGroupManager* spmMgr = NULL;
ISharedPropertyGroup* spmGroup = NULL;
ISharedProperty* spmPropNextReceipt = NULL;
ISharedProperty* spmPropMaxNum = NULL;

IUpdateReceipt* pObjUpdateReceipt = NULL;

CComVariant vNextReceipt;

long lErrFlag = 0;
TCHAR* pErrMsg = NULL;

try {

// First of all, get the object context
THROW_ERR ( GetObjectContext(&pObjectContext), "GetObjectContext" );

// Create the SharedPropertyGroupManager
THROW_ERR ( pObjectContext->CreateInstance (CLSID_SharedPropertyGroupManager,
IID_ISharedPropertyGroupManager, (void**)&spmMgr), "CreateInstance(CLSID_SharedPropertyGroupManager)" );

// Create the SharedPropertyGroup
LONG lIsolationMode = LockMethod;
LONG lReleaseMode = Process;
VARIANT_BOOL bExists = VARIANT_FALSE;
THROW_ERR ( spmMgr->CreatePropertyGroup (L"Receipt", &lIsolationMode, &lReleaseMode, &bExists, &spmGroup),
"CreatePropertyGroup" );

// Create the two shared properties
THROW_ERR ( spmGroup->CreateProperty (L"Next", &bExists, &spmPropNextReceipt), "CreateProperty(Next)" );
THROW_ERR ( spmGroup->CreateProperty (L"MaxNum", &bExists, &spmPropMaxNum), "CreateProperty(MaxNum)" );

// Obtain their current values
CComVariant vMaxNum;
THROW_ERR ( spmPropNextReceipt->get_Value (&vNextReceipt), "spmPropNextReceipt->get_Value" );
THROW_ERR ( spmPropMaxNum->get_Value (&vMaxNum), "spmPropMaxNum->get_Value" );

// Does MaxNum require an update?
if (vNextReceipt.lVal >= vMaxNum.lVal) {

// Create an UpdateReceipt object
THROW_ERR( pObjectContext->CreateInstance (CLSID_CUpdateReceipt, IID_IUpdateReceipt,
(void**)&pObjUpdateReceipt), "CreateInstance(CLSID_CUpdateReceipt)" );

// Update NextReceipt
RETHROW_ERR ( pObjUpdateReceipt->Update (&vNextReceipt.lVal) );

// Update NextReceipt shared property
THROW_ERR ( spmPropNextReceipt->put_Value (vNextReceipt), "spmPropNextReceipt->put_Value" );

// Update MaxNum shared property
vMaxNum.lVal = vNextReceipt.lVal + 100;
THROW_ERR ( spmPropMaxNum->put_Value (vMaxNum), "spmPropMaxNum->put_Value" );
}

// Increment NextReceipt shared property
vNextReceipt.lVal ++;
THROW_ERR ( spmPropNextReceipt->put_Value (vNextReceipt), "spmPropNextReceipt->put_Value" );
*plReceiptNo = vNextReceipt.lVal;
hr = S_OK;

// We're finished and happy
pObjectContext->SetComplete();

} catch (HRESULT hr) {

//
//ErrorInfo is saved here because the following ADO cleanup code
//may clear it.
//
IErrorInfo * pErrorInfo = NULL;
GetErrorInfo(NULL, &pErrorInfo);

// Fill in error information
switch (lErrFlag) {

// Unknown error occurred in this object
case (0):
TCHAR szErr [512];
wsprintf (szErr, _T("Error 0x%x from CGetReceipt calling %s."), hr, pErrMsg);
pErrMsg = szErr;
// Fall through

// An application error occurred in this object
case (1):
//
//we are going to put our own error in TLS, so if there is one there, clear it
//
if (pErrorInfo)
pErrorInfo -> Release();

AtlReportError( CLSID_CGetReceipt, pErrMsg, IID_IGetReceipt, hr);
break;

case (2):// An error occurred in a called object
//
//put the error back in TLS
//
SetErrorInfo(NULL, pErrorInfo);
break;

// Will never reach here
default:
break;
}

// Indicate our unhappiness
if (pObjectContext)
pObjectContext->SetAbort();
}

// Resource cleanup
if (pObjectContext)
pObjectContext->Release();

if (spmMgr)
spmMgr->Release();

if (spmGroup)
spmGroup->Release();

if (spmPropNextReceipt)
spmPropNextReceipt->Release();

if (spmPropMaxNum)
spmPropMaxNum->Release();

if (pObjUpdateReceipt)
pObjUpdateReceipt->Release();

return hr;
}


GETRECEIPT.H

// Filename: GetReceipt.h
//
// Description: Declaration of GetReceipt
//
// This file is provided as part of the Microsoft Transaction Server Samples
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
// WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
// PURPOSE.
//
// Copyright (C) 1997 Microsoft Corporation, All rights reserved

#if !defined(AFX_GETRECEIPT_H__A81260B3_DDC8_11D0_B5A0_00C04FB957D8__INCLUDED_)
#define AFX_GETRECEIPT_H__A81260B3_DDC8_11D0_B5A0_00C04FB957D8__INCLUDED_

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

#include "resource.h"       // main symbols

/////////////////////////////////////////////////////////////////////////////
// CGetReceipt

class CGetReceipt :
public CComDualImpl<IGetReceipt, &IID_IGetReceipt, &LIBID_ACCOUNT>,
public ISupportErrorInfo,
public CComObjectRoot,
public CComCoClass<CGetReceipt,&CLSID_CGetReceipt>
{
public:
CGetReceipt() {}
BEGIN_COM_MAP(CGetReceipt)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IGetReceipt)
COM_INTERFACE_ENTRY(ISupportErrorInfo)
END_COM_MAP()
//DECLARE_NOT_AGGREGATABLE(CGetReceipt)
// Remove the comment from the line above if you don't want your object to
// support aggregation.

DECLARE_REGISTRY_RESOURCEID(IDR_GetReceipt)
// ISupportsErrorInfo
STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);

// IGetReceipt
public:
STDMETHOD (GetNextReceipt) (OUT long* plReceiptNo);
};

#endif // !defined(AFX_GETRECEIPT_H__A81260B3_DDC8_11D0_B5A0_00C04FB957D8__INCLUDED_)


MOVEMONEY.CPP

// Filename: MoveMoney.cpp
//
// Description: Implementation of CMoveMoney
//
// This file is provided as part of the Microsoft Transaction Server
// Software Development Kit
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
// WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
// PURPOSE.
//
// Copyright (C) 1997 Microsoft Corporation, All rights reserved

#include "stdafx.h"
#include "Account.h"
#include "MoveMoney.h"
#include "Account.h"
#include "GetReceipt.h"

#include <mtx.h>
#include <comdef.h>

/////////////////////////////////////////////////////////////////////////////
//

STDMETHODIMP CMoveMoney::InterfaceSupportsErrorInfo(REFIID riid)
{
static const IID* arr[] =
{
&IID_IMoveMoney,
};

for (int i=0;i<sizeof(arr)/sizeof(arr[0]);i++)
{
if (InlineIsEqualGUID(*arr[i],riid))
return S_OK;
}
return S_FALSE;
}

//Perform: modifies the specified account by the specified amount
//
//pvResult: a BSTR giving information.  NOTE: this is set to NULL when an error occurs
//
//returns:  S_OK or E_FAIL

STDMETHODIMP CMoveMoney::Perform (IN long lPrimeAccount, IN long lSecondAccount, IN long lAmount,
  IN long lTranType, OUT BSTR* pbstrResult) {
USES_CONVERSION;

HRESULT hr = S_OK;

IObjectContext* pObjectContext = NULL;

IAccount* pObjAccount = NULL;
IGetReceipt* pObjGetReceipt = NULL;

long lErrFlag = 0;
TCHAR* pErrMsg = NULL;

BSTR bstrResult2 = NULL;
BSTR bstrCall1 = NULL;
BSTR bstrCall2 = NULL;

*pbstrResult = NULL;

try {

// Get the object context
THROW_ERR ( GetObjectContext (&pObjectContext), "GetObjectContext" );

// Check security for large transfers
if (lAmount > 500 || lAmount < -500) {

BOOL bInRole;
BSTR bstrRole = ::SysAllocString (L"Managers");
hr = pObjectContext->IsCallerInRole (bstrRole, &bInRole);
::SysFreeString(bstrRole);

if (!SUCCEEDED ( hr )) {

THROW_STR ( _T("IsCallerInRole() call failed!  Please add the 'Managers' Roll to the package."));
}

if (!bInRole) {
THROW_STR ( _T("Need 'Managers' role for amounts over $500") );
}
}

// Create the account object using our context
THROW_ERR ( pObjectContext->CreateInstance(CLSID_CAccount, IID_IAccount, (void**)&pObjAccount),
"CreateInstance(CLSID_CAccount)" );

// Call the post function based on the transaction type
switch ( lTranType )
{
case (1):// debit
RETHROW_ERR ( pObjAccount->Post (lPrimeAccount, 0 - lAmount, &bstrResult2) );
break;

case (2):// credit
RETHROW_ERR ( pObjAccount->Post (lPrimeAccount, lAmount, &bstrResult2) );
break;

case (3):// transfer
// Do the credit
RETHROW_ERR ( pObjAccount->Post (lSecondAccount, lAmount, &bstrCall1) );

// Then do the debit
RETHROW_ERR ( pObjAccount->Post (lPrimeAccount, 0 - lAmount, &bstrCall2) );

// Prepare return string
TCHAR szBuf [512];
_tcscpy (szBuf, W2T( bstrCall1 ));
_tcscat (szBuf, _T("; "));
_tcscat (szBuf, W2T( bstrCall2 ));
bstrResult2 = TCHAR2BSTR (szBuf);
break;

default:
THROW_STR ( _T("Invalid Transaction Type") );
}

// Get Receipt Number for the transaction
THROW_ERR ( pObjectContext->CreateInstance (CLSID_CGetReceipt, IID_IGetReceipt, (void**)&pObjGetReceipt),
"CreateInstance(CLSID_CGetReceipt)" );
long lngReceiptNo;
RETHROW_ERR ( pObjGetReceipt->GetNextReceipt(&lngReceiptNo) );

// Tag receipt information onto return string

TCHAR szBuffer [512];
wsprintf (szBuffer, _T("; Receipt No:  %li"), lngReceiptNo);
BSTR bstrTemp = TCHAR2BSTR (szBuffer);

TCHAR szBuffer2 [512];
_tcscpy (szBuffer2, W2T ( bstrResult2 ));
_tcscat (szBuffer2, W2T ( bstrTemp ));
::SysFreeString (bstrTemp);

*pbstrResult = TCHAR2BSTR (szBuffer2);

 // We are finished and happy
pObjectContext->SetComplete();
hr = S_OK;

} catch (HRESULT hr) {

//
//store error info locally
//

IErrorInfo * pErrorInfo = NULL;
GetErrorInfo(NULL, &pErrorInfo);

// Fill in error information
switch (lErrFlag) {

// Unknown error occurred in this object
case (0):
TCHAR szErr [512];
wsprintf (szErr, _T("Error 0x%x from CMoveMoney calling %s."), hr, pErrMsg);
pErrMsg = szErr;
// Fall through

// An application error occurred in this object
case (1):
//
//we are going to put our own error in TLS, so if there is one there, clear it
//
if (pErrorInfo)
pErrorInfo -> Release();

AtlReportError( CLSID_CMoveMoney, pErrMsg, IID_IMoveMoney, hr);
break;

case (2):// An error occurred in a called object
//
//put the error back in TLS
//
SetErrorInfo(NULL, pErrorInfo);
break;

// Will never reach here
default:
break;
}

// Indicate our unhappiness
if (pObjectContext)
pObjectContext->SetAbort();
}

// Release resources
if (pObjAccount)
pObjAccount->Release();

if (pObjectContext)
pObjectContext->Release();

if (pObjGetReceipt)
pObjGetReceipt->Release();

if (bstrResult2)
::SysFreeString (bstrResult2);

if (bstrCall1)
::SysFreeString (bstrCall1);

if (bstrCall2)
::SysFreeString (bstrCall2);

return hr;
}


MOVEMONEY.H

// Filename: MoveMoney.h
//
// Description: Declaration of MoveMoney
//
// This file is provided as part of the Microsoft Transaction Server Samples
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
// WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
// PURPOSE.
//
// Copyright (C) 1997 Microsoft Corporation, All rights reserved

#include "resource.h"       // main symbols

/////////////////////////////////////////////////////////////////////////////
// Account

class CMoveMoney :
public CComDualImpl<IMoveMoney, &IID_IMoveMoney, &LIBID_ACCOUNT>,
public ISupportErrorInfo,
public CComObjectRoot,
public CComCoClass<CMoveMoney,&CLSID_CMoveMoney>
{
public:
CMoveMoney() {}
BEGIN_COM_MAP(CMoveMoney)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IMoveMoney)
COM_INTERFACE_ENTRY(ISupportErrorInfo)
END_COM_MAP()
//DECLARE_NOT_AGGREGATABLE(CMoveMoney)
// Remove the comment from the line above if you don't want your object to
// support aggregation.  The default is to support it

DECLARE_REGISTRY_RESOURCEID(IDR_MoveMoney)
// ISupportsErrorInfo
STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);

// IMoveMoney
public:
STDMETHOD(Perform)(
IN long lPrimeAccount,
IN long lSecondAccount,
IN long lAmount,
IN long lTranType,
OUT BSTR* pbstrResult);
};


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by Account.rc
//
#define IDS_ACCOUNT_DESC                1
#define IDR_Account                     1
#define IDS_MOVEMONEY_DESC              2
#define IDR_MoveMoney                   2
#define IDS_UPDATERECEIPT_DESC          114
#define IDR_UpdateReceipt               115
#define IDS_GETRECEIPT_DESC             116
#define IDR_GetReceipt                  117
#define IDS_UPDATERECEIPT_DESC1         118
#define IDR_UpdateReceipt1              119

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        201
#define _APS_NEXT_COMMAND_VALUE         32768
#define _APS_NEXT_CONTROL_VALUE         201
#define _APS_NEXT_SYMED_VALUE           120
#endif
#endif


STDAFX.CPP

// stdafx.cpp : source file that includes just the standard includes
//  stdafx.pch will be the pre-compiled header
//  stdafx.obj will contain the pre-compiled type information

#include "stdafx.h"

#include <initguid.h>
#include <adoid.h>
#include <adoint.h>

#ifdef _ATL_STATIC_REGISTRY
#include <statreg.h>
#include <statreg.cpp>
#endif

#include <atlimpl.cpp>


STDAFX.H

// Filename: stdafx.h
//
// Description: include file for standard system include files,
//or project specific include files that are used frequently,
//but are changed infrequently
//
// This file is provided as part of the Microsoft Transaction Server
// Software Development Kit
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
// WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
// PURPOSE.
//
// Copyright (C) 1997 Microsoft Corporation, All rights reserved

#define _ATL_STATIC_REGISTRY

#include <atlbase.h>
//You may derive a class from CComModule and use it if you want to override
//something, but do not change the name of _Module
extern CComModule _Module;
#include <atlcom.h>

#define THROW_ERR(exp,str) {if (FAILED (hr = (exp))) {lErrFlag = 0; pErrMsg = _T(str); throw hr;}}
#define THROW_STR(str) { hr = APP_ERROR; lErrFlag = 1; pErrMsg = str; throw hr;}
#define RETHROW_ERR(exp) { if (FAILED(hr = (exp))) {lErrFlag = 2; throw hr;}}

#define APP_ERROR 0x80004100

inline BSTR TCHAR2BSTR (TCHAR* szBuf) {

WCHAR* wszBuf;

#ifndef UNICODE
wszBuf = new WCHAR [512];
MultiByteToWideChar( CP_ACP, 0, szBuf, -1, wszBuf, 512 );
#else
wszBuf = szBuf;
#endif

BSTR bstrNew = ::SysAllocString (wszBuf);

#ifndef UNICODE
delete [] wszBuf;
#endif

return bstrNew;
}


UPDATERECEIPT.CPP

// Filename: UpdateReceipt.cpp
//
// Description: Implementation of CUpdateReceipt
//
// This file is provided as part of the Microsoft Transaction Server Samples
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
// WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
// PURPOSE.
//
// Copyright (C) 1997 Microsoft Corporation, All rights reserved

#include "stdafx.h"
#include "Account.h"
#include "UpdateReceipt.h"

#include <mtx.h>
#include <adoid.h>
#include <adoint.h>

/////////////////////////////////////////////////////////////////////////////
//

STDMETHODIMP CUpdateReceipt::InterfaceSupportsErrorInfo(REFIID riid)
{
static const IID* arr[] =
{
&IID_IUpdateReceipt,
};

for (int i=0;i<sizeof(arr)/sizeof(arr[0]);i++)
{
if (InlineIsEqualGUID(*arr[i],riid))
return S_OK;
}
return S_FALSE;
}

//Update: update the database next receipt number when 100 receipts have been issued by GetReceipt
//
//returns: S_OK or E_FAIL

STDMETHODIMP CUpdateReceipt::Update (OUT long* plReceiptNo) {

HRESULT hr = S_OK;

IObjectContext* pObjectContext = NULL;

ADOConnection* adoCoConnection = NULL;
ADORecordset* adoRsReceipt = NULL;
ADOFields* pFields = NULL;
ADOField* pField = NULL;

long lErrFlag = 0;
TCHAR* pErrMsg = NULL;

*plReceiptNo = 0;

try {

// First of all, get the object context
THROW_ERR ( GetObjectContext(&pObjectContext), "GetObjectContext" );

// Create ADOConnection object and initialize the connection
THROW_ERR ( CoCreateInstance (CLSID_CADOConnection, NULL, CLSCTX_INPROC_SERVER,
IID_IADOConnection, (LPVOID *) &adoCoConnection), "CoCreateInstance(CLSID_CADOConnection)" );

BSTR bstrDSN = ::SysAllocString (L"FILEDSN=MTSSamples");
hr = adoCoConnection->Open(bstrDSN, NULL, NULL, adCmdUnspecified);
::SysFreeString (bstrDSN);
RETHROW_ERR ( hr );

// Update the next receipt record
CComVariant vAdoNull;
TCHAR szBuffer [512];
wsprintf (szBuffer, _T("Update Receipt set NextReceipt = NextReceipt + 100"));
BSTR bstrSQL = TCHAR2BSTR (szBuffer);
hr = adoCoConnection->Execute(bstrSQL, &vAdoNull, -1, NULL);
::SysFreeString (bstrSQL);
RETHROW_ERR ( hr );

// Obtain the desired recordset with an SQL query
bstrSQL = ::SysAllocString (L"SELECT NextReceipt FROM Receipt");
hr = adoCoConnection->Execute (bstrSQL, &vAdoNull, -1, &adoRsReceipt);
::SysFreeString (bstrSQL);
RETHROW_ERR ( hr );

// Get the appropriate fields
RETHROW_ERR( adoRsReceipt->get_Fields(&pFields) );

// Get the appropriate field
CComVariant vField = L"NextReceipt";
RETHROW_ERR ( pFields->get_Item (vField, &pField) );
CComVariant vNextReceipt;
RETHROW_ERR ( pField->get_Value (&vNextReceipt) );

// Cleanup resources
if (pField) pField->Release();
if (pFields) pFields->Release();
if (adoRsReceipt) adoRsReceipt->Release();
if (adoCoConnection) adoCoConnection->Release();

// We are finished and happy
pObjectContext->SetComplete();

// Prepare return value
*plReceiptNo = vNextReceipt.lVal;
hr = S_OK;

} catch (HRESULT hr) {

//
//ErrorInfo is saved here because the following ADO cleanup code
//may clear it.
//
IErrorInfo * pErrorInfo = NULL;
GetErrorInfo(NULL, &pErrorInfo);

if (pField)pField->Release();
if (pFields) pFields->Release();
if (adoRsReceipt) adoRsReceipt->Release();
if (adoCoConnection) adoCoConnection->Release();

// Fill in error information
switch (lErrFlag) {

// Unknown error occurred in this object
case (0):
TCHAR szErr [512];
wsprintf (szErr, _T("Error 0x%x from CUpdateReceipt calling %s."), hr, pErrMsg);
pErrMsg = szErr;
// Fall through

// An application error occurred in this object
case (1):
//
//we are going to put our own error in TLS, so if there is one there, clear it
//
if (pErrorInfo)
pErrorInfo -> Release();

AtlReportError( CLSID_CUpdateReceipt, pErrMsg, IID_IUpdateReceipt, hr);
break;

case (2): // An error occurred in a called object
//
//put the error back in TLS
//
SetErrorInfo(NULL, pErrorInfo);
break;

// Will never reach here
default:
break;
}

// Indicate our unhappiness
if (pObjectContext)
pObjectContext->SetAbort();
}

// Resource cleanup
if (pObjectContext)pObjectContext->Release();

return hr;
}


UPDATERECEIPT.H

// Filename: UpdateReceipt.h
//
// Description: Declaration of UpdateReceipt
//
// This file is provided as part of the Microsoft Transaction Server Samples
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
// WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
// PURPOSE.
//
// Copyright (C) 1997 Microsoft Corporation, All rights reserved

#if !defined(AFX_UPDATERECEIPT_H__A81260B9_DDC8_11D0_B5A0_00C04FB957D8__INCLUDED_)
#define AFX_UPDATERECEIPT_H__A81260B9_DDC8_11D0_B5A0_00C04FB957D8__INCLUDED_

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

#include "resource.h"       // main symbols

/////////////////////////////////////////////////////////////////////////////
// CUpdateReceipt

class CUpdateReceipt :
public CComDualImpl<IUpdateReceipt, &IID_IUpdateReceipt, &LIBID_ACCOUNT>,
public ISupportErrorInfo,
public CComObjectRoot,
public CComCoClass<CUpdateReceipt,&CLSID_CUpdateReceipt>
{
public:
CUpdateReceipt() {}
BEGIN_COM_MAP(CUpdateReceipt)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IUpdateReceipt)
COM_INTERFACE_ENTRY(ISupportErrorInfo)
END_COM_MAP()
//DECLARE_NOT_AGGREGATABLE(CUpdateReceipt)
// Remove the comment from the line above if you don't want your object to
// support aggregation.

DECLARE_REGISTRY_RESOURCEID(IDR_UpdateReceipt)
// ISupportsErrorInfo
STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);

// IUpdateReceipt
public:
STDMETHOD (Update) (OUT long* plReceiptNo);
};

#endif // !defined(AFX_UPDATERECEIPT_H__A81260B9_DDC8_11D0_B5A0_00C04FB957D8__INCLUDED_)


ACCOUNTLIB.IDL

// Filename: Account.idl
//
// Description: IDL source for Account.dll
// This file will be processed by the MIDL tool to
// produce the type library (Account.tlb) and marshalling code
//
// This file is provided as part of the Microsoft Transaction Server Samples
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
// WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
// PURPOSE.
//
// Copyright (C) 1997 Microsoft Corporation, All rights reserved

#include <mtxattr.h>
#include <javaattr.h>

[
   uuid(c6bb360d-289a-11d1-9dc5-00c04fc2fba2),
   helpstring("AccountLib Type Library"),
   version(1.0)
]
library AccountLib
{
   importlib("stdole32.tlb");

   [
      uuid(a9f34d04-28a2-11d1-9dc5-00c04fc2fba2),
      helpstring("IAccount Interface")
   ]
   dispinterface IAccount
   {
      properties:
      methods:
      [ helpstring("Post Method"), id(1) ]
      BSTR Post([in] long p1, [in] long p2);

   }

   [
     JAVACLASS("Account.Account"),
  PROGID("Bank.Account.VJ"),
  TRANSACTION_REQUIRED,
      uuid(9faf8612-e902-11d0-b5be-00c04fb957d8),
      helpstring("CAccount Object")
   ]
   coclass CAccount
   {
      [ default ]
      dispinterface IAccount;
   };

   [
      uuid(c20d92dc-28a2-11d1-9dc5-00c04fc2fba2),
      helpstring("IGetReceipt Interface")
   ]
   dispinterface IGetReceipt
   {
      properties:
      methods:
      [ helpstring("GetNextReceipt Method"), id(1) ]
      long GetNextReceipt();

   }

   [
  JAVACLASS("Account.GetReceipt"),
  PROGID("Bank.GetReceipt.VJ"),
  TRANSACTION_SUPPORTED,
      uuid(ab077646-e902-11d0-b5be-00c04fb957d8),
      helpstring("CGetReceipt Object")
   ]
   coclass CGetReceipt
   {
      [ default ]
      dispinterface IGetReceipt;
   };

   [
      uuid(ca6cf4ea-28a2-11d1-9dc5-00c04fc2fba2),
      helpstring("IMoveMoney Interface")
   ]
   dispinterface IMoveMoney
   {
      properties:
      methods:
      [ helpstring("Perform Method"), id(1) ]
      BSTR Perform([in] long p1, [in] long p2, [in] long p3, [in] long p4);

   }

   [
     JAVACLASS("Account.MoveMoney"),
  PROGID("Bank.MoveMoney.VJ"),
  TRANSACTION_REQUIRED,
      uuid(b790937a-e902-11d0-b5be-00c04fb957d8),
      helpstring("CMoveMoney Object")
   ]
   coclass CMoveMoney
   {
      [ default ]
      dispinterface IMoveMoney;
   };

   [
      uuid(d1bc8ae4-28a2-11d1-9dc5-00c04fc2fba2),
      helpstring("IUpdateReceipt Interface")
   ]
   dispinterface IUpdateReceipt
   {
      properties:
      methods:
      [ helpstring("Update Method"), id(1) ]
      long Update();

   }

   [
     JAVACLASS("Account.UpdateReceipt"),
  PROGID("Bank.UpdateReceipt.VJ"),
  TRANSACTION_REQUIRES_NEW,
      uuid(c25e3b4a-e902-11d0-b5be-00c04fb957d8),
      helpstring("CUpdateReceipt Object")
   ]
   coclass CUpdateReceipt
   {
      [ default ]
      dispinterface IUpdateReceipt;
   };

};


VBBANK.FRM

VERSION 5.00
Begin VB.Form Form1
   AutoRedraw      =   -1  'True
   BorderStyle     =   1  'Fixed Single
   Caption         =   "Sample Bank - Visual Basic"
   ClientHeight    =   4320
   ClientLeft      =   5040
   ClientTop       =   5325
   ClientWidth     =   8205
   LinkTopic       =   "Form1"
   MaxButton       =   0   'False
   MinButton       =   0   'False
   PaletteMode     =   1  'UseZOrder
   ScaleHeight     =   4320
   ScaleWidth      =   8205
   Begin VB.Frame Frame7
      Caption         =   "Language"
      Height          =   735
      Left            =   5760
      TabIndex        =   32
      Top             =   2280
      Width           =   2415
      Begin VB.OptionButton VB
         Caption         =   "VB"
         Height          =   255
         Left            =   600
         TabIndex        =   35
         Top             =   360
         Width           =   255
      End
      Begin VB.OptionButton VC
         Caption         =   "Option2"
         Height          =   255
         Left            =   1320
         TabIndex        =   34
         Top             =   360
         Width           =   255
      End
      Begin VB.OptionButton VJ
         Caption         =   "Option3"
         Height          =   375
         Left            =   2040
         TabIndex        =   33
         Top             =   240
         Width           =   255
      End
      Begin VB.Label Label12
         Alignment       =   2  'Center
         Caption         =   "Visual Basic"
         Height          =   375
         Left            =   120
         TabIndex        =   38
         Top             =   240
         Width           =   495
      End
      Begin VB.Label Label11
         Alignment       =   2  'Center
         Caption         =   "Visual C++"
         Height          =   375
         Left            =   840
         TabIndex        =   37
         Top             =   240
         Width           =   495
      End
      Begin VB.Label Label10
         Alignment       =   2  'Center
         Caption         =   "Visual J++"
         Height          =   375
         Left            =   1560
         TabIndex        =   36
         Top             =   240
         Width           =   495
      End
   End
   Begin VB.Frame Frame6
      Caption         =   "Component"
      Height          =   975
      Left            =   5760
      TabIndex        =   27
      Top             =   1320
      Width           =   2415
      Begin VB.OptionButton StatefulMoveMoney
         Caption         =   "Option2"
         Height          =   255
         Left            =   2040
         TabIndex        =   40
         Top             =   600
         Width           =   255
      End
      Begin VB.OptionButton MoveMoney
         Caption         =   "Option2"
         Height          =   255
         Left            =   2040
         TabIndex        =   31
         Top             =   240
         Width           =   255
      End
      Begin VB.OptionButton Account
         Caption         =   "VB"
         Height          =   255
         Left            =   840
         TabIndex        =   29
         Top             =   240
         Width           =   255
      End
      Begin VB.Label Label6
         Alignment       =   2  'Center
         Caption         =   "Stateful MoveMoney"
         Height          =   255
         Index           =   1
         Left            =   480
         TabIndex        =   39
         Top             =   600
         Width           =   1575
      End
      Begin VB.Label Label6
         Alignment       =   2  'Center
         Caption         =   "MoveMoney"
         Height          =   255
         Index           =   0
         Left            =   1080
         TabIndex        =   30
         Top             =   240
         Width           =   975
      End
      Begin VB.Label Label5
         Alignment       =   2  'Center
         Caption         =   "Account"
         Height          =   255
         Left            =   120
         TabIndex        =   28
         Top             =   240
         Width           =   735
      End
   End
   Begin VB.Frame Frame5
      Caption         =   "Response (sec)"
      Height          =   975
      Left            =   0
      TabIndex        =   17
      Top             =   2040
      Width           =   5655
      Begin VB.TextBox AvgResp
         Enabled         =   0   'False
         Height          =   285
         Left            =   4680
         TabIndex        =   22
         Top             =   360
         Width           =   615
      End
      Begin VB.TextBox MaxResp
         Enabled         =   0   'False
         Height          =   285
         Left            =   2880
         TabIndex        =   20
         Top             =   360
         Width           =   615
      End
      Begin VB.TextBox MinResp
         Enabled         =   0   'False
         Height          =   285
         Left            =   960
         TabIndex        =   18
         Top             =   360
         Width           =   615
      End
      Begin VB.Label AvgRespCap
         Caption         =   "Average"
         Height          =   255
         Left            =   3840
         TabIndex        =   23
         Top             =   360
         Width           =   735
      End
      Begin VB.Label Label4
         Caption         =   "Maximum"
         Height          =   255
         Left            =   2160
         TabIndex        =   21
         Top             =   360
         Width           =   735
      End
      Begin VB.Label Label7
         Caption         =   "Minimum"
         Height          =   255
         Left            =   240
         TabIndex        =   19
         Top             =   360
         Width           =   735
      End
   End
   Begin VB.Frame Frame3
      Caption         =   "Result"
      Height          =   1095
      Left            =   0
      TabIndex        =   15
      Top             =   3120
      Width           =   8175
      Begin VB.TextBox Result
         Height          =   735
         Left            =   120
         MultiLine       =   -1  'True
         TabIndex        =   16
         Top             =   240
         Width           =   7935
      End
   End
   Begin VB.Frame Frame2
      Caption         =   "Iterations"
      Height          =   615
      Left            =   2760
      TabIndex        =   12
      Top             =   1320
      Width           =   2895
      Begin VB.CheckBox Step
         Caption         =   "Check1"
         Height          =   255
         Left            =   2520
         TabIndex        =   41
         Top             =   240
         Width           =   255
      End
      Begin VB.TextBox nCurTrans
         Enabled         =   0   'False
         Height          =   285
         Left            =   240
         TabIndex        =   26
         Text            =   "11111"
         Top             =   240
         Width           =   615
      End
      Begin VB.TextBox nTrans
         Height          =   285
         Left            =   1200
         TabIndex        =   7
         Top             =   240
         Width           =   615
      End
      Begin VB.Label Label13
         Caption         =   "&Step"
         Height          =   255
         Left            =   2040
         TabIndex        =   43
         Top             =   240
         Width           =   375
      End
      Begin VB.Label Label9
         Caption         =   "Label9"
         Height          =   15
         Left            =   2040
         TabIndex        =   42
         Top             =   360
         Width           =   15
      End
      Begin VB.Label Label8
         Caption         =   "&of"
         Height          =   255
         Left            =   960
         TabIndex        =   13
         Top             =   240
         Width           =   135
      End
   End
   Begin VB.CommandButton Close
      Cancel          =   -1  'True
      Caption         =   "&Close"
      Height          =   375
      Left            =   5760
      TabIndex        =   9
      Top             =   720
      Width           =   2415
   End
   Begin VB.CommandButton Command1
      Caption         =   "&Submit"
      Default         =   -1  'True
      Height          =   375
      Index           =   0
      Left            =   5760
      TabIndex        =   8
      Top             =   240
      Width           =   2415
   End
   Begin VB.Frame Frame4
      Caption         =   "Transaction Type"
      Height          =   615
      Left            =   0
      TabIndex        =   3
      Top             =   1320
      Width           =   2655
      Begin VB.OptionButton Transfer
         Caption         =   "Trans&fer"
         Height          =   315
         Left            =   1680
         TabIndex        =   6
         Top             =   240
         Width           =   915
      End
      Begin VB.OptionButton Credit
         Caption         =   "&Credit"
         Height          =   315
         Left            =   240
         TabIndex        =   4
         Top             =   240
         Value           =   -1  'True
         Width           =   735
      End
      Begin VB.OptionButton Debit
         Caption         =   "&Debit"
         Height          =   315
         Left            =   960
         TabIndex        =   5
         Top             =   240
         Width           =   735
      End
   End
   Begin VB.Frame Frame1
      Caption         =   "Your Account Information"
      Height          =   1095
      Left            =   0
      TabIndex        =   0
      Top             =   120
      Width           =   5655
      Begin VB.TextBox PrimeAcct
         Height          =   285
         Left            =   1860
         TabIndex        =   1
         Top             =   360
         Width           =   1215
      End
      Begin VB.TextBox SecondAcct
         Enabled         =   0   'False
         Height          =   285
         Left            =   1860
         TabIndex        =   14
         Top             =   720
         Width           =   1215
      End
      Begin VB.TextBox Amount
         Height          =   285
         Left            =   4200
         TabIndex        =   2
         Top             =   360
         Width           =   1215
      End
      Begin VB.Label promptSecondAcct
         Alignment       =   1  'Right Justify
         Caption         =   "&Transfer to Account "
         Enabled         =   0   'False
         Height          =   315
         Left            =   240
         TabIndex        =   25
         Top             =   720
         Width           =   1515
      End
      Begin VB.Label Label1
         Alignment       =   1  'Right Justify
         Caption         =   "&Account Number"
         Height          =   315
         Left            =   240
         TabIndex        =   24
         Top             =   360
         Width           =   1515
      End
      Begin VB.Label Label3
         Caption         =   "Label3"
         Height          =   15
         Left            =   300
         TabIndex        =   11
         Top             =   2100
         Width           =   615
      End
      Begin VB.Label Label2
         Alignment       =   1  'Right Justify
         Caption         =   "A&mount"
         Height          =   315
         Left            =   3240
         TabIndex        =   10
         Top             =   360
         Width           =   915
      End
   End
End
Attribute VB_Name = "Form1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
' Filename: Bank.vbp
'
' Description: Sample bank client
'
' This file is provided as part of the Microsoft Transaction Server Samples
'
' THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
' WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
' INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
' OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
' PURPOSE.
'
' Copyright (C) 1997 Microsoft Corporation, All rights reserved

Option Explicit
Private Const APP_ERROR = -2147467008

' Timer Variables
Private Declare Function GetTickCount Lib "kernel32" () As Long
Private Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
Private StartTime As Long
Private Elapsed As Long
Private AvgResponseTime As Long
Private minRespTime As Long
Private maxRespTime As Long
Private lRet As Long
Private Res As Variant

Private Sub Account_Click()
    VC.Enabled = True
    VJ.Enabled = True
    Transfer.Enabled = False
End Sub

Private Sub Close_Click()
    End
End Sub

Private Sub Command1_Click(Index As Integer)
    Dim lSecondAcct As Long
    Dim TranType As String

    Screen.MousePointer = 11
    On Error GoTo ErrorHandler

    ' Decide which component to use
    Dim ProgID As String
    If Account = True Then
        ProgID = "Bank.Account"
    Else
        ProgID = "Bank.MoveMoney"
    End If

    ' Decide which language to use
    If VC = True Then
        ProgID = ProgID + ".VC"
    ElseIf VJ = True Then
        ProgID = ProgID + ".VJ"
    End If

    ' Decide transaction type
    Result = ""
    lSecondAcct = 0
    Dim Mult As Long
    If Debit = True Then
        TranType = 1
        Mult = -1
    ElseIf Credit = True Then
        TranType = 2
        Mult = 1
    Else
        If ProgID = "Bank.Account" Then
            MsgBox "Error. Use MoveMoney component to transfer."
            MoveMoney.SetFocus
            Screen.MousePointer = 0
            Exit Sub
        End If
        If SecondAcct = "" Then
            MsgBox "Error. Transfer to Account must be filled in."
            SecondAcct.SetFocus
            Screen.MousePointer = 0
            Exit Sub
        Else
            TranType = 3
            lSecondAcct = CLng(SecondAcct)
        End If
    End If

    ' Create the appropriate MoveMoney object
    On Error GoTo objError
    Dim obj As Object
    Set obj = CreateObject(ProgID)
    On Error GoTo ErrorHandler

    If obj Is Nothing Then
        Screen.MousePointer = 0
        MsgBox "Create object " + ProgID + "failed."
        Exit Sub
    End If

    InitTimer

    ' Call the object
    Dim i As Long
    For i = 1 To nTrans
        StartTimer
        Res = ""
        If Account = True Then
            Res = obj.Post(CLng(PrimeAcct), CLng(Amount * Mult))
        ElseIf MoveMoney = True Then
            Res = obj.Perform(CLng(PrimeAcct), lSecondAcct, CLng(Amount), TranType)
        Else
            obj.PrimeAccount = PrimeAcct
            obj.SecondAccount = lSecondAcct
            Res = obj.StatefulPerform(CLng(Amount), TranType)
        End If
        EndTimer
        nCurTrans = i
        Result = Res
        If lRet <> 0 Then
            Exit For
        End If
        If Step = 1 And i <> nTrans Then
            MsgBox "Step mode, OK to continue"
        End If
    Next i

    FinishTimer

    Set obj = Nothing

    Result = Res
    Screen.MousePointer = 0
    Exit Sub

objError:
    MsgBox "Error " & Err.Number & ": Make sure the Sample Bank package has been correctly installed in MTS."
    Screen.MousePointer = 0
    Err.Clear
    Exit Sub

ErrorHandler:
    Screen.MousePointer = 0
    MsgBox Err.Number & "(" & Err.Source & ") :" & Err.Description
    Err.Clear

End Sub

Private Sub Credit_Click()
    SecondAcct.Enabled = False
    promptSecondAcct.Enabled = False
    SecondAcct = ""
    Account.Enabled = True
End Sub
Private Sub Debit_Click()
    SecondAcct.Enabled = False
    promptSecondAcct.Enabled = False
    SecondAcct = ""
    Account.Enabled = True
End Sub
Private Sub Form_Load()
    PrimeAcct = 1
    SecondAcct = ""
    Amount = 1
    Result = ""
    Debit = 0
    Credit = 1
    Transfer = 0
    MoveMoney = 1
    VB = 1
    nCurTrans = 0
    nTrans = 1
    Step = 0
    MinResp = 0
    MaxResp = 0
    AvgResp = 0
End Sub

Private Sub MoveMoney_Click()
    VC.Enabled = True
    VJ.Enabled = True
    Transfer.Enabled = True
End Sub

Private Sub transfer_Click()
    SecondAcct.Enabled = True
    promptSecondAcct.Enabled = True
    Account.Enabled = False
End Sub

Public Sub StartTimer()
    StartTime = GetTickCount()
End Sub

Public Sub EndTimer()
    Elapsed = GetTickCount - StartTime
    If Elapsed < minRespTime Then minRespTime = Elapsed
    If Elapsed > maxRespTime Then maxRespTime = Elapsed
    AvgResponseTime = AvgResponseTime + Elapsed
End Sub

Public Sub FinishTimer()
    AvgResponseTime = AvgResponseTime / nTrans
    AvgResp = Format(AvgResponseTime / 1000, "#0.###")
    MinResp = Format(minRespTime / 1000, "#0.###")
    MaxResp = Format(maxRespTime / 1000, "#0.###")
    Form1.Refresh
End Sub
Public Sub InitTimer()
    minRespTime = 99999999
    maxRespTime = 0
    AvgResponseTime = 0
End Sub

Private Sub VC_Click()
    StatefulMoveMoney.Enabled = False
End Sub

Private Sub VJ_Click()
    StatefulMoveMoney.Enabled = False
End Sub

Private Sub VB_Click()
    StatefulMoveMoney.Enabled = True
End Sub
Private Sub StatefulMoveMoney_Click()
    VC.Enabled = False
    VJ.Enabled = False
    Transfer.Enabled = True
End Sub


ASSOCIATEROLE.FRM

VERSION 5.00
Begin VB.Form AssociateRole
   Caption         =   "Associate Role"
   ClientHeight    =   3195
   ClientLeft      =   60
   ClientTop       =   345
   ClientWidth     =   4680
   LinkTopic       =   "Form1"
   ScaleHeight     =   3195
   ScaleWidth      =   4680
   StartUpPosition =   3  'Windows Default
   Begin VB.CommandButton Cancel
      Cancel          =   -1  'True
      Caption         =   "Cancel"
      Height          =   495
      Left            =   2880
      TabIndex        =   3
      Top             =   2520
      Width           =   1335
   End
   Begin VB.CommandButton OK
      Caption         =   "OK"
      Default         =   -1  'True
      Height          =   495
      Left            =   360
      TabIndex        =   2
      Top             =   2520
      Width           =   1455
   End
   Begin VB.TextBox RoleName
      Height          =   285
      Left            =   480
      TabIndex        =   1
      Top             =   1080
      Width           =   3375
   End
   Begin VB.Label Label1
      Caption         =   "Role Name:"
      Height          =   255
      Left            =   480
      TabIndex        =   0
      Top             =   600
      Width           =   1815
   End
End
Attribute VB_Name = "AssociateRole"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
'/******************************************************************************\
'********************************************************************************
' Explore Sample
'
'* This file is provided as part of the Microsoft Transaction Server
'* Software Development Kit
'*
'*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
'*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
'*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
'*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
'*PURPOSE.
'*
'* Copyright (C) 1997 Microsoft Corporation, All rights reserved
'********************************************************************************
'\******************************************************************************/

Private Sub Cancel_Click()
    Hide
End Sub

Private Sub OK_Click()
    Hide
    On Error GoTo err
    Explorer.MousePointer = 11

    Dim util As Object
    Set util = Explorer.currentColl.GetUtilInterface
    util.AssociateRoleByName RoleName.Text

    Explorer.MousePointer = 0
    Exit Sub
err:
    MsgBox "Role associate failed with error code " + Explorer.mapError(err.Number)
    Resume Next
End Sub


COMPIMPORT.FRM

VERSION 5.00
Begin VB.Form CompImport
   Caption         =   "Import Component"
   ClientHeight    =   3195
   ClientLeft      =   60
   ClientTop       =   345
   ClientWidth     =   4680
   LinkTopic       =   "Form1"
   ScaleHeight     =   3195
   ScaleWidth      =   4680
   StartUpPosition =   3  'Windows Default
   Begin VB.CommandButton Cancel
      Cancel          =   -1  'True
      Caption         =   "Cancel"
      Height          =   495
      Left            =   2760
      TabIndex        =   3
      Top             =   2280
      Width           =   1215
   End
   Begin VB.CommandButton OK
      Caption         =   "OK"
      Default         =   -1  'True
      Height          =   495
      Left            =   480
      TabIndex        =   2
      Top             =   2280
      Width           =   1335
   End
   Begin VB.TextBox ProgID
      Height          =   375
      Left            =   240
      TabIndex        =   0
      Top             =   840
      Width           =   3975
   End
   Begin VB.Label Label1
      Caption         =   "ProgID:"
      Height          =   255
      Left            =   240
      TabIndex        =   1
      Top             =   360
      Width           =   1815
   End
End
Attribute VB_Name = "CompImport"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
'/******************************************************************************\
'********************************************************************************
' Explore Sample
'
'* This file is provided as part of the Microsoft Transaction Server
'* Software Development Kit
'*
'*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
'*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
'*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
'*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
'*PURPOSE.
'*
'* Copyright (C) 1997 Microsoft Corporation, All rights reserved
'********************************************************************************
'\******************************************************************************/

Private Sub Cancel_Click()
    Hide
End Sub

Private Sub OK_Click()
    Hide
    Explorer.MousePointer = 11
    On Error GoTo err

    Dim util As Object
    Set util = Explorer.currentColl.GetUtilInterface
    util.ImportComponentByName progID.Text

    Explorer.MousePointer = 0
    Exit Sub
err:
    MsgBox "Component import failed with error code " + Explorer.mapError(err.Number)
    Explorer.MousePointer = 0

End Sub


COMPINSTALL.FRM

VERSION 5.00
Object = "{F9043C88-F6F2-101A-A3C9-08002B2F49FB}#1.1#0"; "COMDLG32.OCX"
Begin VB.Form CompInstall
   Caption         =   "Install Component"
   ClientHeight    =   3195
   ClientLeft      =   60
   ClientTop       =   345
   ClientWidth     =   5835
   LinkTopic       =   "Form1"
   ScaleHeight     =   3195
   ScaleWidth      =   5835
   StartUpPosition =   3  'Windows Default
   Begin MSComDlg.CommonDialog CommonDialog1
      Left            =   4680
      Top             =   1440
      _ExtentX        =   847
      _ExtentY        =   847
      _Version        =   327680
   End
   Begin VB.CommandButton Cancel
      Cancel          =   -1  'True
      Caption         =   "Cancel"
      Height          =   495
      Left            =   3120
      TabIndex        =   4
      Top             =   2520
      Width           =   1455
   End
   Begin VB.CommandButton OK
      Caption         =   "OK"
      Default         =   -1  'True
      Height          =   495
      Left            =   840
      TabIndex        =   3
      Top             =   2520
      Width           =   1215
   End
   Begin VB.CommandButton BrowseBtn
      Caption         =   "Browse..."
      Height          =   375
      Left            =   3960
      TabIndex        =   2
      Top             =   120
      Width           =   1335
   End
   Begin VB.TextBox DLLFile
      Height          =   375
      Left            =   360
      TabIndex        =   0
      Top             =   600
      Width           =   4935
   End
   Begin VB.Label Label1
      Caption         =   "DLL:"
      Height          =   255
      Left            =   360
      TabIndex        =   1
      Top             =   240
      Width           =   1455
   End
End
Attribute VB_Name = "CompInstall"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
'/******************************************************************************\
'********************************************************************************
' Explore Sample
'
'* This file is provided as part of the Microsoft Transaction Server
'* Software Development Kit
'*
'*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
'*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
'*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
'*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
'*PURPOSE.
'*
'* Copyright (C) 1997 Microsoft Corporation, All rights reserved
'********************************************************************************
'\******************************************************************************/

Private Sub BrowseBtn_Click()
    ' Set CancelErrPor is True
    CommonDialog1.CancelError = True
    On Error GoTo ErrHandler
    ' Set flags
    CommonDialog1.Flags = cdlOFNHideReadOnly
    ' Set filters
    CommonDialog1.Filter = "All Files (*.*)|*.*|DLLs" & _
    "(*.dll)|*.dll"
    ' Specify default filter
    CommonDialog1.FilterIndex = 1
    ' Display the Open dialog box
    CommonDialog1.ShowOpen
    ' Display name of selected file
    DLLFile.Text = CommonDialog1.filename
    Exit Sub

ErrHandler:
    'User pressed the Cancel button
    Exit Sub

End Sub

Private Sub Cancel_Click()
    Hide
End Sub

Private Sub OK_Click()
    Hide
    Explorer.MousePointer = 11
    On Error GoTo err

    Dim util As Object
    Set util = Explorer.currentColl.GetUtilInterface
    util.InstallComponent DLLFile.Text, "", ""
    Explorer.MousePointer = 0
    Exit Sub
err:
    MsgBox "Component install failed with error code " + Explorer.mapError(err.Number)
    Explorer.MousePointer = 0

End Sub


FORM1.FRM

VERSION 5.00
Begin VB.Form Explorer
   Caption         =   "MTS Admin SDK Sample"
   ClientHeight    =   6360
   ClientLeft      =   60
   ClientTop       =   345
   ClientWidth     =   7950
   LinkTopic       =   "Form1"
   ScaleHeight     =   6360
   ScaleWidth      =   7950
   StartUpPosition =   3  'Windows Default
   Begin VB.CommandButton Connect
      Caption         =   "Connect..."
      Height          =   375
      Left            =   5520
      TabIndex        =   18
      Top             =   240
      Width           =   1575
   End
   Begin VB.CommandButton ExportBtn
      Caption         =   "Export..."
      Height          =   375
      Left            =   1440
      TabIndex        =   17
      Top             =   2640
      Width           =   1455
   End
   Begin VB.CommandButton Install
      Caption         =   "Install..."
      Height          =   375
      Left            =   120
      TabIndex        =   16
      Top             =   2640
      Width           =   1095
   End
   Begin VB.ListBox CollList
      Height          =   1230
      Left            =   120
      TabIndex        =   10
      Top             =   3840
      Width           =   2295
   End
   Begin VB.ListBox ObjList
      Height          =   2985
      Left            =   3480
      TabIndex        =   9
      Top             =   1320
      Width           =   2055
   End
   Begin VB.CommandButton Back
      Caption         =   "Back"
      Height          =   495
      Left            =   120
      TabIndex        =   8
      Top             =   5160
      Width           =   855
   End
   Begin VB.ListBox PropList
      Height          =   2985
      Left            =   5880
      TabIndex        =   7
      Top             =   1320
      Width           =   1935
   End
   Begin VB.TextBox Value
      Height          =   375
      Left            =   3480
      TabIndex        =   6
      Text            =   "Text1"
      Top             =   5160
      Width           =   3495
   End
   Begin VB.CommandButton SetBtn
      Caption         =   "Set"
      Height          =   375
      Left            =   7080
      TabIndex        =   5
      Top             =   5160
      Width           =   735
   End
   Begin VB.CommandButton SaveBtn
      Caption         =   "SaveChanges"
      Height          =   495
      Left            =   1440
      TabIndex        =   4
      Top             =   1560
      Width           =   1455
   End
   Begin VB.CommandButton AddBtn
      Caption         =   "Add"
      Height          =   375
      Left            =   120
      TabIndex        =   3
      Top             =   2160
      Width           =   1095
   End
   Begin VB.CommandButton RemoveBtn
      Caption         =   "Remove"
      Height          =   375
      Left            =   1440
      TabIndex        =   2
      Top             =   2160
      Width           =   1455
   End
   Begin VB.CommandButton Populate
      Caption         =   "Populate"
      Height          =   495
      Left            =   120
      TabIndex        =   1
      Top             =   1560
      Width           =   1095
   End
   Begin VB.CommandButton GoToBtn
      Caption         =   "GetCollection"
      Height          =   495
      Left            =   1200
      TabIndex        =   0
      Top             =   5160
      Width           =   1695
   End
   Begin VB.Frame Frame1
      Caption         =   "Collection"
      Height          =   4935
      Left            =   0
      TabIndex        =   19
      Top             =   840
      Width           =   3015
      Begin VB.CommandButton Associate
         Caption         =   "Associate Role..."
         Height          =   375
         Left            =   1440
         TabIndex        =   28
         Top             =   2280
         Width           =   1455
      End
      Begin VB.Label Label7
         Caption         =   "Related collections:"
         Height          =   255
         Left            =   120
         TabIndex        =   22
         Top             =   2760
         Width           =   2295
      End
      Begin VB.Label CurCollName
         BorderStyle     =   1  'Fixed Single
         BeginProperty Font
            Name            =   "MS Sans Serif"
            Size            =   8.25
            Charset         =   0
            Weight          =   700
            Underline       =   0   'False
            Italic          =   0   'False
            Strikethrough   =   0   'False
         EndProperty
         Height          =   255
         Left            =   120
         TabIndex        =   20
         Top             =   360
         Width           =   2775
      End
   End
   Begin VB.Frame Frame2
      Caption         =   "Objects"
      Height          =   4935
      Left            =   3240
      TabIndex        =   21
      Top             =   840
      Width           =   4695
      Begin VB.Label Label9
         Caption         =   "Property value:"
         Height          =   255
         Left            =   240
         TabIndex        =   24
         Top             =   3960
         Width           =   2295
      End
      Begin VB.Label Label8
         Caption         =   "Propeties:"
         Height          =   255
         Left            =   2640
         TabIndex        =   23
         Top             =   240
         Width           =   1335
      End
   End
   Begin VB.Frame Frame3
      Caption         =   "Catalog"
      Height          =   735
      Left            =   0
      TabIndex        =   25
      Top             =   0
      Width           =   7935
      Begin VB.Label Label6
         Caption         =   "Computer:"
         Height          =   255
         Left            =   240
         TabIndex        =   27
         Top             =   360
         Width           =   975
      End
      Begin VB.Label ServerName
         BorderStyle     =   1  'Fixed Single
         Height          =   255
         Left            =   1200
         TabIndex        =   26
         Top             =   360
         Width           =   3975
      End
   End
   Begin VB.Label Label2
      Caption         =   "Related collections:"
      Height          =   255
      Left            =   120
      TabIndex        =   15
      Top             =   3360
      Width           =   1935
   End
   Begin VB.Label Label3
      Caption         =   "Objects:"
      Height          =   255
      Left            =   3480
      TabIndex        =   14
      Top             =   960
      Width           =   1095
   End
   Begin VB.Label Label4
      Caption         =   "Properties"
      Height          =   255
      Left            =   6000
      TabIndex        =   13
      Top             =   960
      Width           =   1215
   End
   Begin VB.Label Label5
      Caption         =   "Property Value:"
      Height          =   255
      Left            =   3480
      TabIndex        =   12
      Top             =   4920
      Width           =   1095
   End
   Begin VB.Label status
      BorderStyle     =   1  'Fixed Single
      Height          =   255
      Left            =   0
      TabIndex        =   11
      Top             =   5880
      Width           =   7935
   End
End
Attribute VB_Name = "Explorer"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
'/******************************************************************************\
'********************************************************************************
' Explore Sample
'
'* This file is provided as part of the Microsoft Transaction Server
'* Software Development Kit
'*
'*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
'*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
'*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
'*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
'*PURPOSE.
'*
'* Copyright (C) 1997 Microsoft Corporation, All rights reserved
'********************************************************************************
'\******************************************************************************/

Public catalog As Object
Public currentColl As Object
Public related As Object
Public propNames As Object
Private stack(10) As Object
Private stackTop As Integer
Public Server As String

Private Sub AddBtn_Click()
    currentColl.Add
    displayObjects currentColl
End Sub

Private Sub Associate_Click()
    AssociateRole.Show 1, Me

End Sub

Private Sub CollList_Click()
    If CollList.ListIndex = -1 Then
        GoToBtn.Enabled = False
    Else
        GoToBtn.Enabled = True
    End If

End Sub

Private Sub Connect_Click()
    ServerDlg.Show 1, Me

End Sub

Private Sub ExportBtn_Click()
    PackageExport.Show 1, Me

End Sub

Private Sub GoToBtn_Click()
    status.Caption = ""
    nColl = CollList.ListIndex
    nObj = ObjList.ListIndex
    If nColl > -1 Then

        MousePointer = 11

        ' if navigating from the root then apply the
        ' current server to the catalog and re-get the
        ' root
        If currentColl.Name = "Root" Then
            Set currentColl = catalog.GetCollection("Root")
        End If
        Dim key As String
        key = ""
        If nObj > -1 Then
            key = currentColl.Item(nObj).key
        End If
        Dim coll As Object
        Set coll = currentColl.GetCollection(CollList.List(nColl), (key))
        push currentColl
        Set currentColl = coll
        navigateTo currentColl
        MousePointer = 0

    End If

End Sub

Private Sub Form_Load()
    Set catalog = CreateObject("MTSAdmin.Catalog.1")
    stackTop = -1

    status.Caption = ""
    Set currentColl = catalog.GetCollection("Root")
    push currentColl
    navigateTo currentColl
    ServerName.Caption = "local"

End Sub

Public Sub navigateTo(coll As Object)

    MousePointer = 11
    CurCollName.Caption = coll.Name
    ' populate related collections list
    CollList.Clear
    PropList.Clear
    GoToBtn.Enabled = False

    Set related = coll.GetCollection("RelatedCollectionInfo", "")
    related.Populate
    Dim relColl As Object

    For Each relColl In related
        CollList.AddItem (relColl.Name)
    Next

    ' populate property names
    Set propNames = coll.GetCollection("PropertyInfo", "")
    Dim propName As Object
    propNames.Populate
    For Each propName In propNames
       PropList.AddItem (propName.Name)
    Next

    ' populate objects
    On Error GoTo err
    coll.Populate
    On Error GoTo 0
    displayObjects coll

    MousePointer = 0
    AddBtn.Enabled = coll.AddEnabled
    RemoveBtn.Enabled = coll.RemoveEnabled
    SaveBtn.Enabled = False
    If coll.Name = "Root" Then
        Back.Enabled = False
    Else
        Back.Enabled = True
    End If
    If coll.Name = "Packages" Or coll.Name = "ComponentsInPackage" Or coll.Name = "RemoteComponents" Then
        Install.Enabled = True
    Else
        Install.Enabled = False
    End If
    If coll.Name = "Packages" Then
        ExportBtn.Enabled = True
    Else
        ExportBtn.Enabled = False
    End If
    If coll.Name = "RolesForPackageComponent" Or coll.Name = "RolesForPackageComponentInterface" Then
        Associate.Enabled = True
    Else
        Associate.Enabled = False
    End If
    Exit Sub
err:
    MsgBox "Collection populate failed with error code " + mapError(err.Number)
    Resume Next

End Sub

Private Sub displayObjects(coll As Object)
    Dim obj As Object
    ObjList.Clear
    For Each obj In coll
        ObjList.AddItem (obj.Name)
    Next
    Value.Text = ""

    ' display property value if possible
    If PropList.ListCount > 0 Then
        PropList.Selected(0) = True
    End If
    If ObjList.ListCount > 0 Then
        ObjList.Selected(0) = True
        PropList_Click
    End If

End Sub

Private Sub Install_Click()
    If currentColl.Name = "Packages" Then
        PackInstall.Show 1, Me
    End If
    If currentColl.Name = "ComponentsInPackage" Then
        MainCompInstall.Show 1, Me
    End If
    If currentColl.Name = "RemoteComponents" Then
        RemoteComp.Show 1, Me
    End If

End Sub

Private Sub ObjList_Click()
    status.Caption = ""
    DisplayValue
End Sub

Private Sub Populate_Click()
    MousePointer = 11
    status.Caption = ""
    navigateTo currentColl
    MousePointer = 0
End Sub

Private Sub DisplayValue()
    ' display property value and disable control if readOnly
    Dim val As Variant
    nObj = ObjList.ListIndex
    If nObj > -1 Then
        nProp = PropList.ListIndex
        If nProp = -1 Then
            Value.Text = ""
            Value.Locked = True
        Else
            ' check for write-only property
            If currentColl.Item(nObj).IsPropertyWriteOnly(PropList.List(nProp)) Then
                Value.Text = ""
            Else
                s = PropList.List(nProp)
                ' get property value
                val = currentColl.Item(nObj).Value(PropList.List(nProp))
                ' if this is an error code then map to a string form
                If currentColl.Name = "ErrorInfo" And nProp = 1 Then
                    Value.Text = Explorer.mapError((val))
                Else
                    Value.Text = val
                End If
            End If

            If currentColl.Item(nObj).IsPropertyReadOnly(PropList.List(nProp)) Then
                Value.Locked = True
            Else
                Value.Locked = False
            End If
        End If
        SetBtn.Enabled = Not Value.Locked

    End If

End Sub

Private Sub PropList_Click()
    status.Caption = ""
    DisplayValue
End Sub

Private Sub RemoveBtn_Click()
    nObj = ObjList.ListIndex
    If nObj > -1 Then
        currentColl.Remove nObj
        displayObjects currentColl
        SaveBtn.Enabled = True

    End If

End Sub

Private Sub Back_Click()
    status.Caption = ""
    MousePointer = 11
    Set currentColl = pop

    navigateTo currentColl
    If currentColl.Name = "Root" Then
        Connect.Enabled = True
    End If
    MousePointer = 0
End Sub

Private Sub SaveBtn_Click()
    status.Caption = ""
    MousePointer = 11
    On Error GoTo err
    currentColl.SaveChanges
    MousePointer = 0
    Exit Sub
err:
    MsgBox "Save changes failed with error code " + mapError(err.Number)
    MousePointer = 0
End Sub

Private Sub SetBtn_Click()
    status.Caption = ""
    On Error GoTo errorHandler
    nObj = ObjList.ListIndex
    If nObj > -1 Then
        nProp = PropList.ListIndex
        If nProp > -1 Then
            currentColl.Item(nObj).Value(PropList.List(nProp)) = Value.Text
            SaveBtn.Enabled = True
        End If
    End If
    Exit Sub
errorHandler:
    Beep
    status.Caption = "Invalid value"
    ' reset the value box contents
    DisplayValue

End Sub

Private Sub push(obj As Object)
    ' live on the edge - no overflow check
    stackTop = stackTop + 1
    Set stack(stackTop) = obj
End Sub

Function pop() As Object
    Set pop = Nothing
    If stackTop < 0 Then Return
    Set pop = stack(stackTop)
    Set stack(stackTop) = Nothing
    stackTop = stackTop - 1
End Function

Public Function mapError(errorCode As Long) As String
    Select Case errorCode

    Case mtsErrObjectErrors
        mapError = "mtsErrObjectErrors - see ErrorInfo collection"

    Case mtsErrObjectErrors
        mapError = "mtsErrObjectErrors"

    Case mtsErrObjectInvalid
        mapError = "mtsErrObjectInvalid"

    Case mtsErrKeyMissing
        mapError = "mtsErrKeyMissing"

    Case mtsErrAlreadyInstalled
        mapError = "mtsErrAlreadyInstalled"

    Case mtsErrCoReqCompInstalled
        mapError = "mtsErrCoReqCompInstalled"

    Case mtsErrBadPath
        mapError = "mtsErrBadPath"

    Case mtsErrPackageExists
        mapError = "mtsErrPackageExists"

    Case mtsErrRemoteInterface
        mapError = "mtsErrRemoteInterface"

    Case mtsErrCantCopyFile
        mapError = "mtsErrCantCopyFile"

    Case mtsErrNoTypeLib
        mapError = "mtsErrNoTypeLib"

    Case mtsErrNoUser
        mapError = "mtsErrNoUser"

    Case mtsErrInvalidUserids
        mapError = "mtsErrInvalidUserids"

    Case mtsErrUserPasswdNotValid
        mapError = "mtsErrUserPasswdNotValid"

    Case mtsErrNoServerShare
        mapError = "mtsErrNoServerShare"

    Case mtsErrPackDirNotFound
        mapError = "mtsErrPackDirNotFound"

    Case mtsErrCompFileNotInstallable
        mapError = "mtsCompFileNotInstallable"

    Case mtsErrCompFileNoRegistrar
        mapError = "mtsErrCompFileNoRegistrar"

    Case Else
        mapError = errorCode
    End Select

End Function


MAINCOMPINSTALL.FRM

VERSION 5.00
Begin VB.Form MainCompInstall
   Caption         =   "Install"
   ClientHeight    =   3195
   ClientLeft      =   60
   ClientTop       =   345
   ClientWidth     =   4680
   LinkTopic       =   "Form2"
   ScaleHeight     =   3195
   ScaleWidth      =   4680
   StartUpPosition =   3  'Windows Default
   Begin VB.CommandButton ImportComponentsBtn
      Caption         =   "Import Components..."
      Height          =   495
      Left            =   600
      TabIndex        =   1
      Top             =   1800
      Width           =   2895
   End
   Begin VB.CommandButton InstallComponentsBtn
      Caption         =   "Install Components..."
      Height          =   495
      Left            =   600
      TabIndex        =   0
      Top             =   480
      Width           =   2895
   End
End
Attribute VB_Name = "MainCompInstall"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
'/******************************************************************************\
'********************************************************************************
' Explore Sample
'
'* This file is provided as part of the Microsoft Transaction Server
'* Software Development Kit
'*
'*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
'*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
'*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
'*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
'*PURPOSE.
'*
'* Copyright (C) 1997 Microsoft Corporation, All rights reserved
'********************************************************************************
'\******************************************************************************/

Private Sub ImportComponentsBtn_Click()
    MousePointer = 11
    CompImport.Show 1, Me
    MousePointer = 0
    Hide
End Sub

Private Sub InstallComponentsBtn_Click()
    MousePointer = 11
    CompInstall.Show 1, Me
    MousePointer = 0
    Hide
End Sub


PACKAGEEXPORT.FRM

VERSION 5.00
Object = "{F9043C88-F6F2-101A-A3C9-08002B2F49FB}#1.1#0"; "COMDLG32.OCX"
Begin VB.Form PackageExport
   Caption         =   "Export Package"
   ClientHeight    =   3765
   ClientLeft      =   60
   ClientTop       =   345
   ClientWidth     =   5355
   LinkTopic       =   "Form1"
   ScaleHeight     =   3765
   ScaleWidth      =   5355
   StartUpPosition =   3  'Windows Default
   Begin MSComDlg.CommonDialog CommonDialog1
      Left            =   2520
      Top             =   3240
      _ExtentX        =   847
      _ExtentY        =   847
      _Version        =   327680
   End
   Begin VB.CommandButton Cancel
      Cancel          =   -1  'True
      Caption         =   "Cancel"
      Height          =   375
      Left            =   3480
      TabIndex        =   9
      Top             =   3360
      Width           =   1455
   End
   Begin VB.CommandButton OK
      Caption         =   "OK"
      Default         =   -1  'True
      Height          =   375
      Left            =   480
      TabIndex        =   8
      Top             =   3360
      Width           =   1455
   End
   Begin VB.Frame Frame2
      Caption         =   "Options"
      Height          =   855
      Left            =   120
      TabIndex        =   6
      Top             =   2280
      Width           =   5055
      Begin VB.CheckBox ExportUsersChk
         Caption         =   "Export users in roles"
         Height          =   255
         Left            =   240
         TabIndex        =   7
         Top             =   360
         Value           =   1  'Checked
         Width           =   3375
      End
   End
   Begin VB.CommandButton BrowseBtn
      Caption         =   "Browse..."
      Height          =   375
      Left            =   3720
      TabIndex        =   4
      Top             =   960
      Width           =   1335
   End
   Begin VB.TextBox PackageFile
      Height          =   375
      Left            =   240
      TabIndex        =   3
      Top             =   1440
      Width           =   4815
   End
   Begin VB.Frame Frame1
      Caption         =   "To File"
      Height          =   1335
      Left            =   120
      TabIndex        =   5
      Top             =   720
      Width           =   5055
   End
   Begin VB.Label Label3
      Caption         =   "To file:"
      Height          =   255
      Left            =   120
      TabIndex        =   2
      Top             =   960
      Width           =   1335
   End
   Begin VB.Label PackageName
      BorderStyle     =   1  'Fixed Single
      Height          =   255
      Left            =   1680
      TabIndex        =   1
      Top             =   240
      Width           =   3375
   End
   Begin VB.Label Label1
      Caption         =   "Export package:"
      Height          =   255
      Left            =   120
      TabIndex        =   0
      Top             =   240
      Width           =   1335
   End
End
Attribute VB_Name = "PackageExport"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
'/******************************************************************************\
'********************************************************************************
' Explore Sample
'
'* This file is provided as part of the Microsoft Transaction Server
'* Software Development Kit
'*
'*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
'*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
'*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
'*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
'*PURPOSE.
'*
'* Copyright (C) 1997 Microsoft Corporation, All rights reserved
'********************************************************************************
'\******************************************************************************/

Private Sub BrowseBtn_Click()
    ' Set CancelError is True
    CommonDialog1.CancelError = True
    On Error GoTo ErrHandler
    ' Set flags
    CommonDialog1.Flags = cdlOFNHideReadOnly
    ' Set filters
    CommonDialog1.Filter = "All Files (*.*)|*.*|Package Files" & _
    "(*.pak)|*.pak"
    ' Specify default filter
    CommonDialog1.FilterIndex = 1
    ' Display the Open dialog box
    CommonDialog1.ShowOpen
    ' Display name of selected file
    packageFile.Text = CommonDialog1.filename
    Exit Sub

ErrHandler:
    'User pressed the Cancel button
    Exit Sub

End Sub

Private Sub Cancel_Click()
    Hide
End Sub

Private Sub Form_Load()
    PackageName.Caption = Explorer.currentColl.Item(Explorer.ObjList.ListIndex).Name

End Sub

Private Sub OK_Click()
    Hide
    Explorer.MousePointer = 11
    On Error GoTo err
    Dim exportOptions As Integer
    exportOptions = 0
    If ExportUsersChk.Value = 1 Then
        exportOptions = exportOptions + mtsExportUsers
    End If

    Dim packageID As String
    packageID = Explorer.currentColl.Item(Explorer.ObjList.ListIndex).key

    Dim util As Object
    Set util = Explorer.currentColl.GetUtilInterface
    util.ExportPackage packageID, packageFile.Text, exportOptions

    MsgBox ("Package successfully exported.")

    Explorer.MousePointer = 0

    Exit Sub
err:
    MsgBox "Package export failed with error code " + Explorer.mapError(err.Number)
    Explorer.MousePointer = 0
End Sub


PACKINSTALL.FRM

VERSION 5.00
Object = "{F9043C88-F6F2-101A-A3C9-08002B2F49FB}#1.1#0"; "COMDLG32.OCX"
Begin VB.Form PackInstall
   Caption         =   "Install Package"
   ClientHeight    =   3885
   ClientLeft      =   60
   ClientTop       =   345
   ClientWidth     =   5940
   LinkTopic       =   "Form2"
   MinButton       =   0   'False
   ScaleHeight     =   3885
   ScaleWidth      =   5940
   StartUpPosition =   3  'Windows Default
   Begin MSComDlg.CommonDialog CommonDialog1
      Left            =   5400
      Top             =   3360
      _ExtentX        =   847
      _ExtentY        =   847
      _Version        =   327680
   End
   Begin VB.CommandButton BrowseInstall
      Caption         =   "Browse..."
      Height          =   375
      Left            =   4680
      TabIndex        =   8
      Top             =   1440
      Width           =   1095
   End
   Begin VB.CommandButton BrowsePDF
      Caption         =   "Browse..."
      Height          =   375
      Left            =   4680
      TabIndex        =   7
      Top             =   120
      Width           =   1095
   End
   Begin VB.CommandButton Cancel
      Cancel          =   -1  'True
      Caption         =   "Cancel"
      Height          =   375
      Left            =   3480
      TabIndex        =   6
      Top             =   3480
      Width           =   1335
   End
   Begin VB.CommandButton OK
      Caption         =   "OK"
      Default         =   -1  'True
      Height          =   375
      Left            =   1200
      TabIndex        =   5
      Top             =   3480
      Width           =   1215
   End
   Begin VB.CheckBox AddUsersChk
      Caption         =   "Add users to roles"
      Height          =   375
      Left            =   240
      TabIndex        =   4
      Top             =   2640
      Width           =   2895
   End
   Begin VB.TextBox InstallDir
      Height          =   375
      Left            =   240
      TabIndex        =   2
      Top             =   1920
      Width           =   5535
   End
   Begin VB.TextBox packageFile
      Height          =   375
      Left            =   240
      TabIndex        =   0
      Top             =   600
      Width           =   5535
   End
   Begin VB.Label Label2
      Caption         =   "Install Dir"
      Height          =   255
      Left            =   240
      TabIndex        =   3
      Top             =   1440
      Width           =   1455
   End
   Begin VB.Label Label1
      Caption         =   "Package File:"
      Height          =   255
      Left            =   240
      TabIndex        =   1
      Top             =   240
      Width           =   1815
   End
End
Attribute VB_Name = "PackInstall"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
'/******************************************************************************\
'********************************************************************************
' Explore Sample
'
'* This file is provided as part of the Microsoft Transaction Server
'* Software Development Kit
'*
'*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
'*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
'*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
'*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
'*PURPOSE.
'*
'* Copyright (C) 1997 Microsoft Corporation, All rights reserved
'********************************************************************************
'\******************************************************************************/

Private Sub BrowsePDF_Click()
    ' Set CancelError is True
    CommonDialog1.CancelError = True
    On Error GoTo ErrHandler
    ' Set flags
    CommonDialog1.Flags = cdlOFNHideReadOnly
    ' Set filters
    CommonDialog1.Filter = "All Files (*.*)|*.*|Package Files" & _
    "(*.pak)|*.pak"
    ' Specify default filter
    CommonDialog1.FilterIndex = 1
    ' Display the Open dialog box
    CommonDialog1.ShowOpen
    ' Display name of selected file
    packageFile.Text = CommonDialog1.filename
    Exit Sub

ErrHandler:
    'User pressed the Cancel button
    Exit Sub

End Sub

Private Sub Cancel_Click()
    Hide
End Sub

Private Sub OK_Click()
    Hide
    Explorer.MousePointer = 11
    On Error GoTo err
    Dim installOptions As Integer
    installOptions = 0
    If AddUsersChk.Value = 1 Then
        installOptions = installOptions + mtsInstallUsers
    End If

    Dim util As Object
    Set util = Explorer.currentColl.GetUtilInterface
    util.InstallPackage packageFile.Text, InstallDir.Text, installOptions

    Explorer.MousePointer = 0
    Exit Sub
err:
    MsgBox "Package install failed with error code " + Explorer.mapError(err.Number)

    Explorer.MousePointer = 0

End Sub


REMOTECOMP.FRM

VERSION 5.00
Begin VB.Form RemoteComp
   Caption         =   "Pulll Remote Component"
   ClientHeight    =   3810
   ClientLeft      =   60
   ClientTop       =   345
   ClientWidth     =   5910
   LinkTopic       =   "Form1"
   ScaleHeight     =   3810
   ScaleWidth      =   5910
   StartUpPosition =   3  'Windows Default
   Begin VB.CommandButton Cancel
      Cancel          =   -1  'True
      Caption         =   "Cancel"
      Height          =   495
      Left            =   3480
      TabIndex        =   7
      Top             =   3240
      Width           =   1335
   End
   Begin VB.CommandButton OK
      Caption         =   "OK"
      Default         =   -1  'True
      Height          =   495
      Left            =   480
      TabIndex        =   6
      Top             =   3240
      Width           =   1335
   End
   Begin VB.TextBox progID
      Height          =   285
      Left            =   1800
      TabIndex        =   5
      Top             =   2520
      Width           =   3375
   End
   Begin VB.TextBox Package
      Height          =   285
      Left            =   1800
      TabIndex        =   3
      Top             =   1320
      Width           =   3255
   End
   Begin VB.TextBox Server
      Height          =   285
      Left            =   1800
      TabIndex        =   1
      Top             =   240
      Width           =   3255
   End
   Begin VB.Label Label3
      Caption         =   "Component progID:"
      Height          =   255
      Left            =   240
      TabIndex        =   4
      Top             =   2520
      Width           =   1455
   End
   Begin VB.Label Label2
      Caption         =   "Package name:"
      Height          =   375
      Left            =   240
      TabIndex        =   2
      Top             =   1320
      Width           =   1335
   End
   Begin VB.Label Label1
      Caption         =   "From Server:"
      Height          =   255
      Left            =   240
      TabIndex        =   0
      Top             =   240
      Width           =   1215
   End
End
Attribute VB_Name = "RemoteComp"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
'/******************************************************************************\
'********************************************************************************
' Explore Sample
'
'* This file is provided as part of the Microsoft Transaction Server
'* Software Development Kit
'*
'*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
'*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
'*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
'*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
'*PURPOSE.
'*
'* Copyright (C) 1997 Microsoft Corporation, All rights reserved
'********************************************************************************
'\******************************************************************************/

Private Sub Cancel_Click()
    Hide
End Sub

Private Sub OK_Click()
    Hide
    Explorer.MousePointer = 11
    On Error GoTo err

    Dim util As Object
    Set util = Explorer.currentColl.GetUtilInterface
    util.InstallRemoteComponentByName Server.Text, Package.Text, progID.Text

    Explorer.MousePointer = 0
    Exit Sub

err:
    MsgBox "Remote component install failed with error code " + Explorer.mapError(err.Number)
    Explorer.MousePointer = 0

End Sub


SERVERDLG.FRM

VERSION 5.00
Begin VB.Form ServerDlg
   Caption         =   "Connect"
   ClientHeight    =   3735
   ClientLeft      =   60
   ClientTop       =   345
   ClientWidth     =   4680
   LinkTopic       =   "Form1"
   ScaleHeight     =   3735
   ScaleWidth      =   4680
   StartUpPosition =   3  'Windows Default
   Begin VB.CommandButton Cancel
      Cancel          =   -1  'True
      Caption         =   "Cancel"
      Height          =   375
      Left            =   2640
      TabIndex        =   6
      Top             =   3240
      Width           =   1455
   End
   Begin VB.CommandButton OK
      Caption         =   "OK"
      Default         =   -1  'True
      Height          =   375
      Left            =   480
      TabIndex        =   5
      Top             =   3240
      Width           =   1455
   End
   Begin VB.Frame Frame1
      Caption         =   "Computer"
      Height          =   1935
      Left            =   360
      TabIndex        =   1
      Top             =   840
      Width           =   3855
      Begin VB.TextBox ServerName
         Height          =   375
         Left            =   600
         TabIndex        =   4
         Top             =   1440
         Width           =   2775
      End
      Begin VB.OptionButton RemoteBtn
         Caption         =   "A remote computer:"
         Height          =   375
         Left            =   360
         TabIndex        =   3
         Top             =   960
         Width           =   2655
      End
      Begin VB.OptionButton LocalBtn
         Caption         =   "This computer"
         Height          =   255
         Left            =   360
         TabIndex        =   2
         Top             =   480
         Width           =   2655
      End
   End
   Begin VB.Label Label1
      Caption         =   "What computer do you want to configure? "
      Height          =   375
      Left            =   480
      TabIndex        =   0
      Top             =   360
      Width           =   3255
   End
End
Attribute VB_Name = "ServerDlg"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
'/******************************************************************************\
'********************************************************************************
' Explore Sample
'
'* This file is provided as part of the Microsoft Transaction Server
'* Software Development Kit
'*
'*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
'*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
'*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
'*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
'*PURPOSE.
'*
'* Copyright (C) 1997 Microsoft Corporation, All rights reserved
'********************************************************************************
'\******************************************************************************/

Private Sub Cancel_Click()
    Hide
End Sub

Private Sub LocalBtn_Click()
    EnableControls
End Sub

Private Sub OK_Click()
    If LocalBtn.Value = True Then
        Server = ""
        ServerDesc = "local"
    Else
        Server = ServerName.Text
        ServerDesc = Server
    End If
    Set Explorer.currentColl = Explorer.catalog.Connect(Server)
    Explorer.navigateTo Explorer.currentColl
    Explorer.ServerName.Caption = ServerDesc

    Hide
End Sub

Private Sub RemoteBtn_Click()
    EnableControls
End Sub
Private Sub EnableControls()
    If LocalBtn.Value = True Then
        ServerName.Enabled = False
    Else
        ServerName.Enabled = True
    End If

End Sub


FORM1.FRM

VERSION 5.00
Begin VB.Form Form1
   Caption         =   "SDK Component Install Sample"
   ClientHeight    =   3195
   ClientLeft      =   60
   ClientTop       =   345
   ClientWidth     =   4680
   LinkTopic       =   "Form1"
   ScaleHeight     =   3195
   ScaleWidth      =   4680
   StartUpPosition =   3  'Windows Default
   Begin VB.CommandButton Command2
      Caption         =   "Exit"
      Height          =   495
      Left            =   2760
      TabIndex        =   2
      Top             =   2640
      Width           =   1455
   End
   Begin VB.CommandButton Command1
      Caption         =   "Run Demo"
      Height          =   495
      Left            =   360
      TabIndex        =   0
      Top             =   2640
      Width           =   1455
   End
   Begin VB.Label Label1
      Caption         =   $"Form1.frx":0000
      Height          =   2055
      Left            =   240
      TabIndex        =   1
      Top             =   240
      Width           =   4335
   End
End
Attribute VB_Name = "Form1"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
'/******************************************************************************\
'********************************************************************************
' Install Sample
'
'* This file is provided as part of the Microsoft Transaction Server
'* Software Development Kit
'*
'*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
'*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
'*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
'*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
'*PURPOSE.
'*
'* Copyright (C) 1997 Microsoft Corporation, All rights reserved
'********************************************************************************
'\******************************************************************************/

Private Sub Command1_Click()
' create the catalog object
Dim catalog As Object
Set catalog = CreateObject("MTSAdmin.Catalog.1")

' get the packages collection - you can only get the top level collections
' from the catalog object - the packages collection is returned without retrieving any
' data from the catalog data store so the in-memory collection will be empty upon return
' from the GetCollection method
Dim packages As Object
Set packages = catalog.GetCollection("Packages")

' first I want to delete any packages called "SDK Demo" to clean up from previous
' runs - populate the packages collection to read in all packages and search for
' packages named "SDK Demo" - note that I iterate thru the collection starting at the
' highest index so I can call Remove within the loop -
' Remove will release the object, remove it from the in-memory collection and move objects up so that
' object(n+1) becomes object(n) for all n greater then or equal to the index being removed
' the effect of Remove on the in memory collection is immediate so Item and Count methods
' called any time after the Remove will reflect the change - the Remove action is not
' applied to the catalog data store until you call SaveChanges on the collection
packages.Populate
Dim pack As Object
n = packages.Count
For i = n - 1 To 0 Step -1
    If packages.Item(i).Value("Name") = "SDK Demo" Then
        packages.Remove (i)
    End If
Next
' apply the changes to the data store
packages.SaveChanges

' now lets add a new package - Add will assign a package guid for us
Dim newPack As Object
Dim newPackID As Variant
Set newPack = packages.Add
' make note of the ID assigned
newPackID = newPack.Value("ID")
' override a couple properties
newPack.Value("Name") = "SDK Demo"
newPack.Value("SecurityEnabled") = "N"

' now we need to commit the changes so the new package is saved
' the return value is the number of objects changed/added/deleted
' this will be 0 if no changes were pending
n = packages.SaveChanges

' lets go get all the packages so we can see the one we just created
packages.Populate

n = packages.Count

' iterate over the packages until I find the one I just created and
' when I find it I'm going to get the components collection for that
' new package
Dim package As Object
Dim v As Variant
Dim components As Object
For Each package In packages
    v = package.Key
    If v = newPackID Then
        ' found it
        ' lets update some package properties while I'm here
        package.Value("SecurityEnabled") = "Y"
        ' get the components collection for this package - I need to pass in the key
        ' of the package object that I am interested in as parm 2
        Set components = packages.GetCollection("ComponentsInPackage", package.Key)
    End If

    ' print the names of all the packages
    Debug.Print package.Value("Name")
Next

' commit the updates I made above
packages.SaveChanges

' now lets install a component - I specify the name of the DLL as parm 1
' if there is an external TLB file or a P/S DLL I can specify them in parms 2 and 3 - otherwise
' pass in empty strings for parms 2 and 3 - there is no need to call SaveChanges after a componentInstall
Dim util As Object
Set util = components.GetUtilInterface
On Error GoTo installFailed

Form2.Show 1
Dim thePath As String
thePath = Form2.MTSPath + "\samples\packages\vbacct.dll"
util.InstallComponent thePath, "", ""
On Error GoTo 0

' go get the data in the components collection so I can see the
' component(s) I just installed - all components in the DLL will
' be installed
components.Populate

' iterate thru the components and change transaction attributes
' - use the item and count methods instead of the for each this time
' - find the Bank.CreatTable component so I can delete it by index - note that I
' must iterate thru the collection backwards since I may call Remove during the loop
' (see comments above about removing packages)
Dim component As Object
n = components.Count
For i = n - 1 To 0 Step -1
    Set component = components.Item(i)
    component.Value("Transaction") = "Required"
    If component.Value("ProgID") = "Bank.CreateTable" Then
        components.Remove (i)
    End If
Next

' get new count and iterate through collection again - Bank.CreateTable will be gone but will
' not be deleted from the data store until I call SaveChanges
n = components.Count
For i = 0 To n - 1
    Set component = components.Item(i)
    Debug.Print component.Value("ProgID")
    Debug.Print component.Value("DLL")
Next

n = components.SaveChanges

MsgBox "SDK Demo package installed and configured."
Exit Sub

installFailed:
    MsgBox "Error code " + Str$(Err.Number) + " installing " + thePath + "  Make sure the MTS path you entered is correct and that vbacct.dll is not already installed."

End Sub

Private Sub Command2_Click()
    End
End Sub


FORM2.FRM

VERSION 5.00
Begin VB.Form Form2
   Caption         =   "MTS Install Path"
   ClientHeight    =   2340
   ClientLeft      =   60
   ClientTop       =   345
   ClientWidth     =   5745
   LinkTopic       =   "Form2"
   ScaleHeight     =   2340
   ScaleWidth      =   5745
   StartUpPosition =   3  'Windows Default
   Begin VB.CommandButton Ok
      Caption         =   "OK"
      Height          =   375
      Left            =   2040
      TabIndex        =   2
      Top             =   1800
      Width           =   1335
   End
   Begin VB.TextBox MTSPath
      Height          =   375
      Left            =   120
      TabIndex        =   1
      Top             =   1080
      Width           =   5295
   End
   Begin VB.Label Enter
      Caption         =   "Enter your MTS install path (EG: C:\MTS)"
      Height          =   615
      Left            =   240
      TabIndex        =   0
      Top             =   240
      Width           =   4095
   End
End
Attribute VB_Name = "Form2"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
'/******************************************************************************\
'********************************************************************************
' Install Sample
'
'* This file is provided as part of the Microsoft Transaction Server
'* Software Development Kit
'*
'*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
'*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
'*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
'*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
'*PURPOSE.
'*
'* Copyright (C) 1997 Microsoft Corporation, All rights reserved
'********************************************************************************
'\******************************************************************************/

Private Sub Ok_Click()
    Hide
End Sub

Private Sub Text1_Change()

End Sub


INSTALL.FRM

VERSION 5.00
Object = "{F9043C88-F6F2-101A-A3C9-08002B2F49FB}#1.1#0"; "comdlg32.ocx"
Begin VB.Form frmInstall
   Caption         =   "SDK Component Install Sample"
   ClientHeight    =   3195
   ClientLeft      =   60
   ClientTop       =   345
   ClientWidth     =   4680
   LinkTopic       =   "Form1"
   ScaleHeight     =   3195
   ScaleWidth      =   4680
   StartUpPosition =   3  'Windows Default
   Begin MSComDlg.CommonDialog cdlg
      Left            =   2040
      Top             =   2640
      _ExtentX        =   847
      _ExtentY        =   847
      _Version        =   327681
      DialogTitle     =   "Select the Component you wish to install"
      FileName        =   "*.dll"
      Filter          =   "*.dll"
   End
   Begin VB.CommandButton Command2
      Cancel          =   -1  'True
      Caption         =   "&Exit"
      Height          =   495
      Left            =   2760
      TabIndex        =   2
      Top             =   2640
      Width           =   1455
   End
   Begin VB.CommandButton Command1
      Caption         =   "&Run Demo"
      Default         =   -1  'True
      Height          =   495
      Left            =   360
      TabIndex        =   0
      Top             =   2640
      Width           =   1455
   End
   Begin VB.Label Label1
      BeginProperty Font
         Name            =   "MS Sans Serif"
         Size            =   12
         Charset         =   0
         Weight          =   700
         Underline       =   0   'False
         Italic          =   0   'False
         Strikethrough   =   0   'False
      EndProperty
      Height          =   2055
      Left            =   240
      TabIndex        =   1
      Top             =   240
      Width           =   4335
   End
End
Attribute VB_Name = "frmInstall"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
'/******************************************************************************\
'********************************************************************************
' Install Sample
'
'* This file is provided as part of the Microsoft Transaction Server
'* Software Development Kit
'*
'*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
'*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
'*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
'*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
'*PURPOSE.
'*
'* Copyright (C) 1997 Microsoft Corporation, All rights reserved
'********************************************************************************
'\******************************************************************************/

Private Sub Command1_Click()
' create the catalog object
Dim catalog As Object
Set catalog = CreateObject("MTSAdmin.Catalog.1")

' get the packages collection - you can only get the top level collections
' from the catalog object - the packages collection is returned without retrieving any
' data from the catalog data store so the in-memory collection will be empty upon return
' from the GetCollection method
Dim packages As Object
Set packages = catalog.GetCollection("Packages")

' first I want to delete any packages called "SDK Demo" to clean up from previous
' runs - populate the packages collection to read in all packages and search for
' packages named "SDK Demo" - note that I iterate thru the collection starting at the
' highest index so I can call Remove within the loop -
' Remove will release the object, remove it from the in-memory collection and move objects up so that
' object(n+1) becomes object(n) for all n greater then or equal to the index being removed
' the effect of Remove on the in memory collection is immediate so Item and Count methods
' called any time after the Remove will reflect the change - the Remove action is not
' applied to the catalog data store until you call SaveChanges on the collection
packages.Populate
Dim pack As Object
n = packages.Count
For i = n - 1 To 0 Step -1
    If packages.Item(i).Value("Name") = "SDK Demo" Then
        packages.Remove (i)
    End If
Next
' apply the changes to the data store
packages.SaveChanges

' now lets add a new package - Add will assign a package guid for us
Dim newPack As Object
Dim newPackID As Variant
Set newPack = packages.Add
' make note of the ID assigned
newPackID = newPack.Value("ID")
' override a couple properties
newPack.Value("Name") = "SDK Demo"
newPack.Value("SecurityEnabled") = "N"

' now we need to commit the changes so the new package is saved
' the return value is the number of objects changed/added/deleted
' this will be 0 if no changes were pending
n = packages.SaveChanges

' lets go get all the packages so we can see the one we just created
packages.Populate

n = packages.Count

' iterate over the packages until I find the one I just created and
' when I find it I'm going to get the components collection for that
' new package
Dim package As Object
Dim v As Variant
Dim components As Object
For Each package In packages
    v = package.Key
    If v = newPackID Then
        ' found it
        ' lets update some package properties while I'm here
        package.Value("SecurityEnabled") = "Y"
        ' get the components collection for this package - I need to pass in the key
        ' of the package object that I am interested in as parm 2
        Set components = packages.GetCollection("ComponentsInPackage", package.Key)
    End If

    ' print the names of all the packages
    Debug.Print package.Value("Name")
Next

' commit the updates I made above
packages.SaveChanges

' now lets install a component - I specify the name of the DLL as parm 1
' if there is an external TLB file or a P/S DLL I can specify them in parms 2 and 3 - otherwise
' pass in empty strings for parms 2 and 3 - there is no need to call SaveChanges after a componentInstall
Dim util As Object
Set util = components.GetUtilInterface
On Error GoTo installFailed
cdlg.filename = ""
cdlg.Filter = "Component (*.Dll)|*.Dll|All Files (*.*)|*.*"
cdlg.FilterIndex = 0
cdlg.ShowOpen

Dim thePath As String
thePath = cdlg.filename
util.InstallComponent thePath, "", ""
On Error GoTo 0

' go get the data in the components collection so I can see the
' component(s) I just installed - all components in the DLL will
' be installed
components.Populate

' iterate thru the components and change transaction attributes
' - use the item and count methods instead of the for each this time
' - find the Bank.CreatTable component so I can delete it by index - note that I
' must iterate thru the collection backwards since I may call Remove during the loop
' (see comments above about removing packages)
Dim component As Object
n = components.Count
For i = n - 1 To 0 Step -1
    Set component = components.Item(i)
    component.Value("Transaction") = "Required"
Next

' get new count and iterate through collection again - Bank.CreateTable will be gone but will
' not be deleted from the data store until I call SaveChanges
n = components.Count
For i = 0 To n - 1
    Set component = components.Item(i)
    Debug.Print component.Value("ProgID")
    Debug.Print component.Value("DLL")
Next

n = components.SaveChanges

MsgBox "SDK Demo package installed and configured."
Exit Sub

installFailed:
    MsgBox "Error code " + Str$(Err.Number) + " installing " & thePath + vbCrLf & _
    "Make sure that " & cdlg.filename _
    & vbCrLf & " is not already installed."

End Sub

Private Sub Command2_Click()
    Unload Me
End Sub

Private Sub Form_Load()
Dim sLabel1 As String

    sLabel1 = "Click the 'Run Demo' button below, " & vbCrLf & "then select the component to install." & vbCrLf & " the component will be installed into the SDK Demo Package"
    Label1.Caption = sLabel1
End Sub


TASKS.FRM

VERSION 5.00
Begin VB.Form Tasks
   Caption         =   "Task Samples"
   ClientHeight    =   8175
   ClientLeft      =   60
   ClientTop       =   345
   ClientWidth     =   4980
   LinkTopic       =   "Form1"
   ScaleHeight     =   8175
   ScaleWidth      =   4980
   StartUpPosition =   3  'Windows Default
   Begin VB.CommandButton RemoteComp
      Caption         =   "Configure Client to Use Remote Components"
      Height          =   495
      Left            =   360
      TabIndex        =   10
      Top             =   6360
      Width           =   4215
   End
   Begin VB.CommandButton Export
      Caption         =   "Export Package "
      Height          =   495
      Left            =   360
      TabIndex        =   9
      Top             =   5760
      Width           =   4215
   End
   Begin VB.CommandButton Command1
      Caption         =   "Configuring Security Role "
      Height          =   495
      Left            =   360
      TabIndex        =   8
      Top             =   5160
      Width           =   4215
   End
   Begin VB.CommandButton RemoteUpdate
      Caption         =   "Update Package on Remote Server"
      Height          =   495
      Left            =   360
      TabIndex        =   7
      Top             =   4560
      Width           =   4215
   End
   Begin VB.CommandButton PropInfo
      Caption         =   "Access Property Info"
      Height          =   495
      Left            =   360
      TabIndex        =   6
      Top             =   3960
      Width           =   4215
   End
   Begin VB.CommandButton Related
      Caption         =   "Access Related Collection Names"
      Height          =   495
      Left            =   360
      TabIndex        =   5
      Top             =   3360
      Width           =   4215
   End
   Begin VB.CommandButton PackDelete
      Caption         =   "Browse for installed package and Delete"
      Height          =   495
      Left            =   360
      TabIndex        =   4
      Top             =   2760
      Width           =   4215
   End
   Begin VB.CommandButton BrowseUpdate
      Caption         =   "Browse for installed Package and Update Properties"
      Height          =   495
      Left            =   360
      TabIndex        =   3
      Top             =   1560
      Width           =   4215
   End
   Begin VB.CommandButton BrowseDelete
      Caption         =   "Browse for installed component and Delete"
      Height          =   495
      Left            =   360
      TabIndex        =   2
      Top             =   2160
      Width           =   4215
   End
   Begin VB.CommandButton CreatePackage
      Caption         =   "Create New Package and Install Components"
      Height          =   495
      Left            =   360
      TabIndex        =   1
      Top             =   960
      Width           =   4215
   End
   Begin VB.CommandButton InstallPackage
      Caption         =   "Install Pre-built Package"
      Height          =   495
      Left            =   360
      TabIndex        =   0
      Top             =   360
      Width           =   4215
   End
End
Attribute VB_Name = "Tasks"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False

Private Sub BrowseDelete_Click()
    Dim catalog As Object
    Dim packages As Object
    Dim pack As Object
    Dim componentsInPack As Object
    Dim util As Object

    On Error GoTo failed
    Set catalog = CreateObject("MTSAdmin.CCatalog.1")
    Set packages = catalog.GetCollection("Packages")
    packages.populate
    For Each pack In packages
        If pack.Name = "My Package" Then
            Set componentsInPack = packages.GetCollection("ComponentsInPackage", pack.Key)
            componentsInPack.populate
            For i = 0 To componentsInPack.Count
                Set comp = componentsInPack.Item(i)
                If comp.Name = "Bank.Account" Then
                    componentsInPack.Remove (i)
                    componentsInPack.savechanges
                    Exit For
                End If
            Next
            Exit For
        End If
    Next

    Exit Sub

failed:
    MsgBox "Failure code " + Str$(Err.Number)

End Sub

Private Sub BrowseUpdate_Click()
    Dim catalog As Object
    Dim packages As Object
    Dim pack As Object
    Dim componentsInNewPack As Object
    Dim util As Object

    On Error GoTo failed
    Set catalog = CreateObject("MTSAdmin.CCatalog.1")
    Set packages = catalog.GetCollection("Packages")
    packages.populate
    For Each pack In packages
        If pack.Name = "My Package" Then
            pack.Value("SecurityEnabled") = "Y"
            Exit For
        End If
    Next
    packages.savechanges

    Exit Sub

failed:
    MsgBox "Failure code " + Str$(Err.Number)

End Sub

Private Sub Command1_Click()
    Dim catalog As Object
    Dim packages As Object
    Dim pack As Object
    Dim comp As Object
    Dim newUser As Object
    Dim newRole As Object
    Dim componentsInPack As Object
    Dim rolesInPack As Object
    Dim usersInRole As Object
    Dim rolesForComponent As Object
    Dim util As Object

    On Error GoTo failed
    Set catalog = CreateObject("MTSAdmin.CCatalog.1")
    Set packages = catalog.GetCollection("Packages")
    packages.populate
    For Each pack In packages
        If pack.Name = "My Package" Then
            ' create a new role in the package
            Set rolesInPack = packages.GetCollection("RolesInPackage", pack.Key)
            Set newRole = rolesInPack.Add
            newRole.Value("Name") = "R1"
            rolesInPack.savechanges

            ' associate the role with a user
            Set usersInRole = rolesInPack.GetCollection("UsersInRole", newRole.Key)
            Set newUser = usersInRole.Add
            newUser.Value("User") = "administrator"
            usersInRole.savechanges

            'associate the role with a component
            Set componentsInPack = packages.GetCollection("ComponentsInPackage", pack.Key)
            componentsInPack.populate
            For Each comp In componentsInPack
                If comp.Name = "Bank.Account" Then
                    Set rolesForComponent = componentsInPack.GetCollection("RolesForPackageComponent", comp.Key)
                    Set util = rolesForComponent.getUtilInterface
                    util.associateRole (newRole.Key)
                    Exit For
                End If
            Next
            Exit For
        End If
    Next

    Exit Sub

failed:
    MsgBox "Failure code " + Str$(Err.Number)

End Sub

Private Sub CreatePackage_Click()
    Dim catalog As Object
    Dim packages As Object
    Dim newPack As Object
    Dim componentsInNewPack As Object
    Dim util As Object

    On Error GoTo failed
    Set catalog = CreateObject("MTSAdmin.CCatalog.1")
    Set packages = catalog.GetCollection("Packages")
    Set newPack = packages.Add
    Dim newPackID As String

    newPackID = newPack.Key
    newPack.Value("Name") = "My Package"
    packages.savechanges

    Set componentsInNewPack = packages.GetCollection("ComponentsInPackage", newPackID)
    Set util = componentsInNewPack.getUtilInterface
    util.InstallComponent "d:\viper96\bin\x86\rel\samples\packages\vbacct.dll", "", ""
    Exit Sub

failed:
    MsgBox "Failure code " + Str$(Err.Number)

End Sub

Private Sub Export_Click()
    Dim catalog As Object
    Dim packages As Object
    Dim util As Object

    On Error GoTo failed
    Set catalog = CreateObject("MTSAdmin.CCatalog.1")
    Set packages = catalog.GetCollection("Packages")
    packages.populate
    For Each pack In packages
        If pack.Name = "My Package" Then
            Set util = packages.getUtilInterface
            util.ExportPackage pack.Key, "c:\test.pak", 0
            Exit For
        End If
    Next

    Exit Sub

failed:
    MsgBox "Failure code " + Str$(Err.Number)

End Sub

Private Sub InstallPackage_Click()
    Dim catalog As Object
    Dim packages As Object
    Dim util As Object

    On Error GoTo failed
    Set catalog = CreateObject("MTSAdmin.CCatalog.1")
    Set packages = catalog.GetCollection("Packages")
    Set util = packages.getUtilInterface
    util.InstallPackage "c:\test.pak", "", 0
    Exit Sub

failed:
    MsgBox "Failure code " + Str$(Err.Number)
End Sub

Private Sub PackDelete_Click()
    Dim catalog As Object
    Dim packages As Object
    Dim pack As Object
    Dim componentsInPack As Object
    Dim util As Object

    On Error GoTo failed
    Set catalog = CreateObject("MTSAdmin.CCatalog.1")
    Set packages = catalog.GetCollection("Packages")
    packages.populate
    For i = 0 To packages.Count
        Set pack = packages.Item(i)
        If pack.Name = "My Package" Then
            packages.Remove (i)
            packages.savechanges
            Exit For
        End If
    Next

    Exit Sub

failed:
    MsgBox "Failure code " + Str$(Err.Number)

End Sub

Private Sub PropInfo_Click()
    Dim catalog As Object
    Dim packages As Object
    Dim propertyInfo As Object
    Dim property As Object

    On Error GoTo failed
    Set catalog = CreateObject("MTSAdmin.CCatalog.1")
    Set packages = catalog.GetCollection("Packages")
    Set propertyInfo = packages.GetCollection("PropertyInfo", "")
    propertyInfo.populate
    For Each property In propertyInfo
        Debug.Print property.Name
    Next

    Exit Sub

failed:
    MsgBox "Failure code " + Str$(Err.Number)

End Sub

Private Sub Related_Click()
    Dim catalog As Object
    Dim packages As Object
    Dim relatedCollectionNames As Object
    Dim collName As Object

    On Error GoTo failed
    Set catalog = CreateObject("MTSAdmin.CCatalog.1")
    Set packages = catalog.GetCollection("Packages")
    Set relatedCollectionNames = packages.GetCollection("RelatedCollectionInfo", "")
    relatedCollectionNames.populate
    For Each collName In relatedCollectionNames
        Debug.Print collName.Name
    Next

    Exit Sub

failed:
    MsgBox "Failure code " + Str$(Err.Number)

End Sub

Private Sub RemoteUpdate_Click()
    Dim catalog As Object
    Dim packages As Object
    Dim pack As Object
    Dim root As Object

    On Error GoTo failed
    Set catalog = CreateObject("MTSAdmin.CCatalog.1")
    Set root = catalog.Connect("billdev9")
    Set packages = root.GetCollection("Packages", "")
    packages.populate
    For Each pack In packages
        If pack.Name = "My Package" Then
            pack.Value("SecurityEnabled") = "Y"
            Exit For
        End If
    Next
    packages.savechanges

    Exit Sub

failed:
    MsgBox "Failure code " + Str$(Err.Number)

End Sub

Private Sub RemoteComp_Click()
    Dim catalog As Object
    Dim remoteComps As Object
    Dim util As Object

    On Error GoTo failed
    Set catalog = CreateObject("MTSAdmin.CCatalog.1")
    Set remoteComps = catalog.GetCollection("RemoteComponents")
    Set util = remoteComps.getUtilInterface
    util.InstallRemoteComponentByName "billdev9", "New", "Bank.CreateTable"

    Exit Sub

failed:
    MsgBox "Failure code " + Str$(Err.Number)

End Sub

Private Sub RoleConfig_Click()

End Sub


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by DemoVC5.rc
//
#define IDD_DIALOG1                     101
#define IDD_DIALOG2                     102
#define IDD_MAINDIALOG                  103
#define IDC_INSTALLDLL                  1000
#define IDC_STATUSMSGBOX                1002
#define IDC_LIST1                       1003

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        104
#define _APS_NEXT_COMMAND_VALUE         40001
#define _APS_NEXT_CONTROL_VALUE         1004
#define _APS_NEXT_SYMED_VALUE           101
#endif
#endif


WMAIN.CPP

/******************************************************************************\
********************************************************************************
* Filename: wmain.cpp
*
* Description: Contains implementation of WinMain and other stuff.
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <tchar.h>
#include <stdio.h>
#include <comdef.h>
#include "resource.h"

// Use the VC5 import feature to pull in the typelib; note that we could also use
// the supplied header (mtxadmin.h, mtxadmin_i.c) but this way we get smart COM pointers.
#import "..\..\..\..\..\mtxadmin.dll"
using namespace MTSAdmin;

// Globals
HINSTANCE g_hInstance;
HWND g_hDlg;
HWND g_hwndStatusMsgEB;
_bstr_t  g_bstrDllPath;
const DWORD MAX_STRING_LEN = 255;

///////////////////////////////////////////////////////////////////
//   Appends a string to the bottom of the display buffer.
void Print(LPTSTR tszMsg)
{
TCHAR tszBuffer[MAX_STRING_LEN];
_stprintf(tszBuffer, _T("%s\r\n"), tszMsg);
SendMessage(g_hwndStatusMsgEB, EM_SETSEL,(WPARAM) -1, (LPARAM) -1) ;
SendMessage(g_hwndStatusMsgEB, EM_REPLACESEL, (WPARAM) FALSE, (LPARAM) tszBuffer) ;
}

///////////////////////////////////////////////////////////////////
//  Clears the contents of the display buffer
void ClearDisplay()
{
SendMessage(g_hwndStatusMsgEB, EM_SETSEL,(WPARAM) 0, (LPARAM) -1) ;
SendMessage(g_hwndStatusMsgEB, EM_REPLACESEL, (WPARAM) FALSE, (LPARAM) _T("")) ;
}

///////////////////////////////////////////////////////////////////
//  Prompts the user for a dll to install.
BOOL GetDllPathFromUser()
{
OPENFILENAME ofn;       // common dialog box structure
TCHAR tszFile[260];       // buffer for filename

_tcscpy(tszFile, _T(""));

// Initialize OPENFILENAME
ZeroMemory(&ofn, sizeof(OPENFILENAME));
ofn.lStructSize = sizeof(OPENFILENAME);
ofn.hwndOwner = g_hDlg;
ofn.lpstrFile = tszFile;
ofn.nMaxFile = sizeof(tszFile);
ofn.lpstrFilter = _T("All\0*.*");
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = _T("\\");
ofn.lpstrTitle = _T("Choose a component dll to install");
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY | OFN_NOCHANGEDIR;

// Display the Open dialog box and save filename if they press okay
if (!GetOpenFileName(&ofn))
return FALSE;

g_bstrDllPath = tszFile;
return TRUE;
}

///////////////////////////////////////////////////////////////////
//  Installs the file pointed to by g_bstrDllPath into the SDKDemo
//    package, which we assume already exists.
BOOL InstallFileIntoPackage()
{
_bstr_t bstrT, bstrT2, bstrT3;
long lPkgCount, lCompCount, i;
_variant_t varT;
TCHAR tszBuf[MAX_STRING_LEN];
try
{
ICatalogCollectionPtr pPkgCollection(NULL);
ICatalogCollectionPtr pCompCollection(NULL);
IComponentUtilPtr pCompUtil(NULL);
ICatalogObjectPtr pCatObject(NULL);

// Create instance of the catalog object
ICatalogPtr pCatalog(__uuidof(Catalog));

bstrT = (LPTSTR)NULL;
pCatalog->Connect(bstrT);

// Get the Packages collection
bstrT = "Packages";
pPkgCollection = pCatalog->GetCollection(bstrT);

// Populate it
pPkgCollection->Populate();

// Get the "SDK Demo" package
Print(_T("Getting the ""SDK Demo"" package...."));
lPkgCount = pPkgCollection->GetCount();
bstrT2 = "SDK Demo";
for (i = (lPkgCount-1); i >= 0 ; i--)
{
pCatObject = pPkgCollection->GetItem(i);

bstrT = pCatObject->GetName();
if (bstrT == bstrT2)
{
// Found it
break;
}
}

// Install the specified file name
bstrT = "ComponentsInPackage";
bstrT2 = "";
bstrT3 = "";

varT = pCatObject->GetKey();

// Get the "ComponentsInPackage" collection.
pCompCollection = pPkgCollection->GetCollection(bstrT, varT);
pCompCollection->Populate();
pCompUtil = pCompCollection->GetUtilInterface();

Print(_T("Installing your specified component dll..."));
pCompUtil->InstallComponent(g_bstrDllPath, bstrT2, bstrT3);

// Now save, then repopulate the component collection with the just added components
pCompCollection->SaveChanges();
pCompCollection->Populate();

// Now we are going to set the transaction property to "Required" for each component
// in the  'SDK Demo' package.
Print(_T("About to set the transaction property for each component..."));
lCompCount = pCompCollection->GetCount();
for (i = 0; i < lCompCount; i++)
{
pCatObject = pCompCollection->GetItem(i);
bstrT = "Transaction";
bstrT2 = "Required";
varT = bstrT2;
pCatObject->PutValue(bstrT, varT);
}
pCompCollection->SaveChanges();
pCompCollection->Populate();

Print(_T("Enumerating each of your components"));
bstrT = "ProgID";
for (i = 0; i < lCompCount; i++)
{
pCatObject = pCompCollection->GetItem(i);
varT = pCatObject->GetValue(bstrT);
bstrT2 = varT;
_stprintf(tszBuf, _T("  %s"), (char*)bstrT2);
Print(tszBuf);
}
}
catch (...)
{
// pop an error message and return
return FALSE;
}
return TRUE;
} // InstallFileIntoPackage

///////////////////////////////////////////////////////////////////
//  Deletes any and all local packages named "SDK Demo".
BOOL DeleteSDKDemoPackage()
{
_bstr_t bstrT, bstrT2;
long    lPkgCount;
long   i;

try
{
ICatalogCollectionPtr pPkgCollection(NULL);
ICatalogPtr pCatalog(__uuidof(Catalog));
ICatalogObjectPtr pCatObject(NULL);

bstrT = (LPTSTR)NULL;
pCatalog->Connect(bstrT);

// Get the Packages collection
Print(_T("Getting the Packages collection"));
bstrT = "Packages";
pPkgCollection = pCatalog->GetCollection(bstrT);

// Populate it
pPkgCollection->Populate();

// Loop through all packages in the collection; if we find an
//   "SDK Demo" package, remove it.
lPkgCount = pPkgCollection->GetCount();
bstrT2 = "SDK Demo";

Print(_T("Searching for the SDK Demo package, if it exists"));
for (i = (lPkgCount-1); i >= 0 ; i--)
{
pCatObject = pPkgCollection->GetItem(i);

bstrT = pCatObject->GetName();
if (bstrT == bstrT2)
{
Print(_T("Deleting the existing ""SDK Demo"" package now"));
pPkgCollection->Remove(i);
}
}

// Save any changes we made
pPkgCollection->SaveChanges();
}
catch (...)
{
return FALSE;
}

return TRUE;
}

///////////////////////////////////////////////////////////////////
//  Creates a local package named "SDKDemo"; assumes that a package
//    of this name does not already exist.
BOOL CreateSDKDemoPackage()
{
_bstr_t bstrT, bstrT2;
_variant_t varT;

try
{
ICatalogCollectionPtr pPkgCollection(NULL);
ICatalogPtr pCatalog(__uuidof(Catalog));
ICatalogObjectPtr pCatObject(NULL);

bstrT = (LPTSTR)NULL;
pCatalog->Connect(bstrT);

// Get the Packages collection
Print(_T("Getting the Packages collection"));
bstrT = "Packages";
pPkgCollection = pCatalog->GetCollection(bstrT);

// Populate it
pPkgCollection->Populate();

// Create a new package object
Print(_T("Adding a new package object"));
pCatObject = pPkgCollection->Add();

// Set its name property to "SDK Demo"
Print(_T("Changing its name to ""SDK Demo"""));
bstrT = "Name";
bstrT2 = "SDK Demo";
varT = bstrT2;
pCatObject->PutValue(bstrT, varT);

// Set its security property to off
Print(_T("Changing its security setting to off"));
bstrT = "SecurityEnabled";
bstrT2 = "N";
varT = bstrT2;
pCatObject->PutValue(bstrT, varT);

// Persist our changes
pPkgCollection->SaveChanges();
}
catch (...)
{
return FALSE;
}

return TRUE;
}

///////////////////////////////////////////////////////////////////
//
BOOL InstallDllIntoSDKDemoPackage()
{
BOOL bResult;

// Delete the SDKDemo package if it exists
if (!DeleteSDKDemoPackage())
return FALSE;

if (!CreateSDKDemoPackage())
return FALSE;

bResult = InstallFileIntoPackage();
if (bResult)
{
Print(_T("Installation of your components was successful"));
}
else
{
Print(_T("Installation of your components failed"));
}
return bResult;
} // InstallDllIntoSDKDemoPackage

///////////////////////////////////////////////////////////////////
//   Command handler from the wndproc.
void InstallDll()
{
ClearDisplay();

// First get component dll to install
if (!GetDllPathFromUser())
return;

// Install dll
InstallDllIntoSDKDemoPackage();

// Done
return;
} // InstallDLL

///////////////////////////////////////////////////////////////////
//
BOOL Dlg_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam)
{
g_hwndStatusMsgEB = GetDlgItem(hwnd,IDC_STATUSMSGBOX);
g_hDlg = hwnd;
return TRUE;
}  // Dlg_OnInitDialog

///////////////////////////////////////////////////////////////////
//
BOOL Dlg_OnDestroy(HWND hwnd)
{
return TRUE;
}  // Dlg_OnDestroy

///////////////////////////////////////////////////////////////////
//
void Dlg_OnClose(HWND hwnd)
{
EndDialog(hwnd, 1);
}  // Dlg_OnClose

///////////////////////////////////////////////////////////////////
//
void Dlg_OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
{
switch(id)
{
case IDOK:
break;
case IDCANCEL:
break;
case IDC_INSTALLDLL:
InstallDll();
break;
default:
break;
}
return;
}  // Dlg_OnCommand

///////////////////////////////////////////////////////////////////
//
BOOL CALLBACK Dlg_Proc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
BOOL fProcessed = TRUE;

switch(uMsg)
{
HANDLE_MSG(hDlg, WM_INITDIALOG, Dlg_OnInitDialog);
HANDLE_MSG(hDlg, WM_CLOSE, Dlg_OnClose);
HANDLE_MSG(hDlg, WM_DESTROY, Dlg_OnDestroy);
HANDLE_MSG(hDlg, WM_COMMAND, Dlg_OnCommand);
default:
fProcessed=FALSE;
break;
}
return fProcessed;
}  // Dlg_Proc

///////////////////////////////////////////////////////////////////
//   Beginning of program execution
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
HRESULT hr;
int iResult;

InitCommonControls();
g_hInstance = hInstance;

hr = CoInitialize(NULL);
if (FAILED(hr))
return -1;

iResult = DialogBox(hInstance, MAKEINTRESOURCE(IDD_MAINDIALOG), NULL, Dlg_Proc);

CoUninitialize();
return iResult;
}  // WinMain


COPYFILERM.CPP

/******************************************************************************\
********************************************************************************
* Filename: CopyFileRm.cpp
*
* Description: Implementation of WinMain
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#include "stdafx.h"
#include "resource.h"
#include "initguid.h"
#include "txdtc.h"
#define INITGUID
#include "txcoord.h"
#include "xolehlp.h"

#include "CopyFileRm.h"
#define IID_DEFINED
#include "CopyFileRm_i.c"
#include "SimpleLog.h"
#include "simplelog_i.c"
#include "rmworker.h"
#include "rmoptions.h"
#include "FileRm.h"
#include "rmdlg.h"

LONG CExeModule::Lock()
{
LONG l = CComModule::Lock();
if (_Module.m_dlg)
_Module.m_dlg->SetLockCount(l);

return l;
}

LONG CExeModule::Unlock()
{
LONG l = CComModule::Unlock();
if (_Module.m_dlg)
_Module.m_dlg->SetLockCount(l);
if (l == 0)
{
#if _WIN32_WINNT >= 0x0400
if (CoSuspendClassObjects() == S_OK)
PostThreadMessage(dwThreadID, WM_QUIT, 0, 0);
#else
PostThreadMessage(dwThreadID, WM_QUIT, 0, 0);
#endif
}
return l;
}

CExeModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_CoFileRm, CFileRm)
END_OBJECT_MAP()

LPCTSTR FindOneOf(LPCTSTR p1, LPCTSTR p2)
{
while (*p1 != NULL)
{
LPCTSTR p = p2;
while (*p != NULL)
{
if (*p1 == *p++)
return p1+1;
}
p1++;
}
return NULL;
}

/////////////////////////////////////////////////////////////////////////////
//
extern "C" int WINAPI _tWinMain(HINSTANCE hInstance,
HINSTANCE /*hPrevInstance*/, LPTSTR lpCmdLine, int /*nShowCmd*/)
{

lpCmdLine = GetCommandLine(); //this line necessary for _ATL_MIN_CRT
HRESULT hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED);
_ASSERTE(SUCCEEDED(hRes));
_Module.Init(ObjectMap, hInstance);
_Module.dwThreadID = GetCurrentThreadId();
TCHAR szTokens[] = _T("-/");

int nRet = 0;
BOOL bRun = TRUE;
LPCTSTR lpszToken = FindOneOf(lpCmdLine, szTokens);
while (lpszToken != NULL)
{
if (lstrcmpi(lpszToken, _T("UnregServer"))==0)
{
_Module.UpdateRegistryFromResource(IDR_CopyFileRm, FALSE);
nRet = _Module.UnregisterServer();
bRun = FALSE;
break;
}
if (lstrcmpi(lpszToken, _T("RegServer"))==0)
{
_Module.UpdateRegistryFromResource(IDR_CopyFileRm, TRUE);
nRet = _Module.RegisterServer(TRUE);
bRun = FALSE;
break;
}
lpszToken = FindOneOf(lpszToken, szTokens);
}

if (bRun)
{
//
//to make it easier to demo, I am always going to register the server when
//it is launched
//

_Module.UpdateRegistryFromResource(IDR_CopyFileRm, TRUE);
nRet = _Module.RegisterServer(TRUE);
_ASSERTE(SUCCEEDED(nRet ));

hRes = _Module.RegisterClassObjects(CLSCTX_LOCAL_SERVER, REGCLS_MULTIPLEUSE);
_ASSERTE(SUCCEEDED(hRes));

if (SUCCEEDED(hRes))
{

if (_Module.m_dlg)
_Module.m_dlg -> SetUsageCount(0);
MSG msg;
while (GetMessage(&msg, 0, 0, 0))
DispatchMessage(&msg);

_Module.RevokeClassObjects();
}

}

CoUninitialize();
return nRet;
}


CPCOPYFILERM.H

/******************************************************************************\
********************************************************************************
* Filename: CPCopyFileRm.h
*
* Description:
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

//////////////////////////////////////////////////////////////////////////////
// CProxyIFileRm
template <class T>
class CProxyIFileRm : public IConnectionPointImpl<T, &IID_IFileRm, CComDynamicUnkArray>
{
public:

//IFileRm : IUnknown
public:
HRESULT Fire_CopyListTx(
long lConHandle)
{
T* pT = (T*)this;
pT->Lock();
HRESULT ret;
IUnknown** pp = m_vec.begin();
while (pp < m_vec.end())
{
if (*pp != NULL)
{
IFileRm* pIFileRm = reinterpret_cast<IFileRm*>(*pp);
ret = pIFileRm->CopyListTx(lConHandle);
}
pp++;
}
pT->Unlock();
return ret;
}
HRESULT Fire_Disconnect(
long ulConnHandle)
{
T* pT = (T*)this;
pT->Lock();
HRESULT ret;
IUnknown** pp = m_vec.begin();
while (pp < m_vec.end())
{
if (*pp != NULL)
{
IFileRm* pIFileRm = reinterpret_cast<IFileRm*>(*pp);
ret = pIFileRm->Disconnect(ulConnHandle);
}
pp++;
}
pT->Unlock();
return ret;
}
HRESULT Fire_Connect(
BSTR sAppName,
long * ulConnHandle)
{
T* pT = (T*)this;
pT->Lock();
HRESULT ret;
IUnknown** pp = m_vec.begin();
while (pp < m_vec.end())
{
if (*pp != NULL)
{
IFileRm* pIFileRm = reinterpret_cast<IFileRm*>(*pp);
ret = pIFileRm->Connect(sAppName, ulConnHandle);
}
pp++;
}
pT->Unlock();
return ret;
}
HRESULT Fire_FileCopyTx(
long lConHandle,
BSTR sSource,
BSTR sDestination,
long bFailIfExists)
{
T* pT = (T*)this;
pT->Lock();
HRESULT ret;
IUnknown** pp = m_vec.begin();
while (pp < m_vec.end())
{
if (*pp != NULL)
{
IFileRm* pIFileRm = reinterpret_cast<IFileRm*>(*pp);
ret = pIFileRm->FileCopyTx(lConHandle, sSource, sDestination, bFailIfExists);
}
pp++;
}
pT->Unlock();
return ret;
}
HRESULT Fire_FileRenameTx(
long lConHandle,
BSTR sSource,
BSTR sDestination)
{
T* pT = (T*)this;
pT->Lock();
HRESULT ret;
IUnknown** pp = m_vec.begin();
while (pp < m_vec.end())
{
if (*pp != NULL)
{
IFileRm* pIFileRm = reinterpret_cast<IFileRm*>(*pp);
ret = pIFileRm->FileRenameTx(lConHandle, sSource, sDestination);
}
pp++;
}
pT->Unlock();
return ret;
}
HRESULT Fire_AddFile(
long lConHandle,
BSTR sSource,
BSTR sDestination)
{
T* pT = (T*)this;
pT->Lock();
HRESULT ret;
IUnknown** pp = m_vec.begin();
while (pp < m_vec.end())
{
if (*pp != NULL)
{
IFileRm* pIFileRm = reinterpret_cast<IFileRm*>(*pp);
ret = pIFileRm->AddFile(lConHandle, sSource, sDestination);
}
pp++;
}
pT->Unlock();
return ret;
}
HRESULT Fire_CopyDirTx(
long lConHandle,
BSTR sSource,
BSTR sDestination)
{
T* pT = (T*)this;
pT->Lock();
HRESULT ret;
IUnknown** pp = m_vec.begin();
while (pp < m_vec.end())
{
if (*pp != NULL)
{
IFileRm* pIFileRm = reinterpret_cast<IFileRm*>(*pp);
ret = pIFileRm->CopyDirTx(lConHandle, sSource, sDestination);
}
pp++;
}
pT->Unlock();
return ret;
}
HRESULT Fire_FileMoveTxt(
long lConHandle,
BSTR sSource,
BSTR sDestination)
{
T* pT = (T*)this;
pT->Lock();
HRESULT ret;
IUnknown** pp = m_vec.begin();
while (pp < m_vec.end())
{
if (*pp != NULL)
{
IFileRm* pIFileRm = reinterpret_cast<IFileRm*>(*pp);
ret = pIFileRm->FileMoveTxt(lConHandle, sSource, sDestination);
}
pp++;
}
pT->Unlock();
return ret;
}
HRESULT Fire_FileDeleteTx(
long lConHandle,
BSTR sFileName)
{
T* pT = (T*)this;
pT->Lock();
HRESULT ret;
IUnknown** pp = m_vec.begin();
while (pp < m_vec.end())
{
if (*pp != NULL)
{
IFileRm* pIFileRm = reinterpret_cast<IFileRm*>(*pp);
ret = pIFileRm->FileDeleteTx(lConHandle, sFileName);
}
pp++;
}
pT->Unlock();
return ret;
}
HRESULT Fire_GetTmWhereabouts(
long lConHandle,
unsigned char * * rgbTmAddr,
unsigned long * pcbTmAddr)
{
T* pT = (T*)this;
pT->Lock();
HRESULT ret;
IUnknown** pp = m_vec.begin();
while (pp < m_vec.end())
{
if (*pp != NULL)
{
IFileRm* pIFileRm = reinterpret_cast<IFileRm*>(*pp);
ret = pIFileRm->GetTmWhereabouts(lConHandle, rgbTmAddr, pcbTmAddr);
}
pp++;
}
pT->Unlock();
return ret;
}
HRESULT Fire_ExportTx(
long lConHandle,
unsigned long cbTranCookie,
unsigned char * rgbTranCookie)
{
T* pT = (T*)this;
pT->Lock();
HRESULT ret;
IUnknown** pp = m_vec.begin();
while (pp < m_vec.end())
{
if (*pp != NULL)
{
IFileRm* pIFileRm = reinterpret_cast<IFileRm*>(*pp);
ret = pIFileRm->ExportTx(lConHandle, cbTranCookie, rgbTranCookie);
}
pp++;
}
pT->Unlock();
return ret;
}
HRESULT Fire_SetVisible(
int bVisible)
{
T* pT = (T*)this;
pT->Lock();
HRESULT ret;
IUnknown** pp = m_vec.begin();
while (pp < m_vec.end())
{
if (*pp != NULL)
{
IFileRm* pIFileRm = reinterpret_cast<IFileRm*>(*pp);
ret = pIFileRm->SetVisible(bVisible);
}
pp++;
}
pT->Unlock();
return ret;
}
HRESULT Fire_ResetConnection(
long lConHandle)
{
T* pT = (T*)this;
pT->Lock();
HRESULT ret;
IUnknown** pp = m_vec.begin();
while (pp < m_vec.end())
{
if (*pp != NULL)
{
IFileRm* pIFileRm = reinterpret_cast<IFileRm*>(*pp);
ret = pIFileRm->ResetConnection(lConHandle);
}
pp++;
}
pT->Unlock();
return ret;
}
HRESULT Fire_MarshalPointer(
IStream * * ppStream)
{
T* pT = (T*)this;
pT->Lock();
HRESULT ret;
IUnknown** pp = m_vec.begin();
while (pp < m_vec.end())
{
if (*pp != NULL)
{
IFileRm* pIFileRm = reinterpret_cast<IFileRm*>(*pp);
ret = pIFileRm->MarshalPointer(ppStream);
}
pp++;
}
pT->Unlock();
return ret;
}
};


FILERM.CPP

/******************************************************************************\
********************************************************************************
* Filename: FileRm.cpp
*
* Description: Implementation of CFileRm
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#include "stdafx.h"
#include "txdtc.h"
#include "txcoord.h"
#include "xolehlp.h"
#include "CopyFileRm.h"
#include "rmworker.h"
#include "simplelog.h"
#include "rmoptions.h"
#include "FileRm.h"
#include "rmdlg.h"
#define IID_DEFINED
#include "rmworker_i.c"

#include "rmsink.h"
#include "RmOptionsDlg.h"

#define CHECK_TM_DOWN() {if (IsTMDown()){if (InitializeRm() == S_OK)TmUp();else returnXACT_E_TMNOTAVAILABLE; }}

//
//a worker thread function to check if the TM comes back up
//

DWORD WINAPI TMDownWorkerThread( LPVOID pData);

//
//Normally if you want to have multiple instances of an RM, you would log a guid
//for each RM instance and reuse it for recovery
//

static GUID CopyFileRmGuid =
{ 0xf3ffa31a, 0x860a, 0x11d0, { 0xb1, 0x71, 0x0, 0xaa, 0x0, 0xba, 0x32, 0x58 } };

long glWorkers = 0;

STDMETHODIMP CFileRm::Connect(BSTR sAppName, long * ulConnHandle)
{

CHECK_TM_DOWN();

HRESULT hr;
IRmWorker * pWorker = NULL;
hr = CoCreateInstance(CLSID_CRmWorkerObj, NULL, CLSCTX_SERVER, IID_IRmWorker , (void **)&pWorker);
if (FAILED(hr))
{
_ASSERTE(0);
return hr;
}
hr = pWorker -> Init((IUnknown *)m_pLog, (IUnknown *)m_pIResMgr, (IUnknown *)m_punkDTC, m_options.GetRecDir());
if (FAILED(hr))
{
_ASSERTE(0);
pWorker -> Release();
return hr;
}

InterlockedIncrement(&glWorkers);
if (_Module.m_dlg)
_Module.m_dlg->SetUsageCount(glWorkers);

*ulConnHandle = (long) pWorker;
m_setWorkers.insert(*ulConnHandle);
return S_OK;
}
//
//Disconnect() works even if the TM is down, so we
//don't use the GetWorker() method
//
STDMETHODIMP CFileRm::Disconnect(long ulConnHandle)
{

IRmWorker * pWorker  = NULL;
pWorker = (IRmWorker *)(*m_setWorkers.find(ulConnHandle));
if (!pWorker)
{
return E_FAIL;
}
ULONG lRef = pWorker -> Release();
pWorker = NULL;
InterlockedDecrement(&glWorkers);
if (_Module.m_dlg)
_Module.m_dlg->SetUsageCount(glWorkers);

int nEraseCount = m_setWorkers.erase(ulConnHandle);
_ASSERTE(nEraseCount == 1);
return S_OK;
}
//
//the whereabouts is the same for every worker, and is fixed for every instance
//of the RM
//
STDMETHODIMP CFileRm::GetTmWhereabouts(long lConHandle, BYTE **rgbTmAddr, ULONG *pcbTmAddr)
{

//
//NOTE:  this is handle independent!
//

CHECK_TM_DOWN();
HRESULT hr = S_OK;
if (!m_cbTmAddr)
{

hr = GetMyTmWhereAbouts();
if (FAILED(hr))
return hr;
}

*pcbTmAddr = m_cbTmAddr;
*rgbTmAddr = (BYTE *) CoTaskMemAlloc (m_cbTmAddr);
memcpy(*rgbTmAddr, m_rgbTmAddr, m_cbTmAddr);
return hr;
}
STDMETHODIMP CFileRm::ExportTx(long lConHandle, ULONG cbTranCookie, BYTE *rgbTranCookie)
{

CHECK_TM_DOWN();
IRmWorker * pWorker = GetWorker(lConHandle);
if (!pWorker)
{
return E_INVALIDARG;
}
HRESULT hr = pWorker ->ExportTx(cbTranCookie, rgbTranCookie);
return  hr;
}

STDMETHODIMP CFileRm::FileCopyTx(long lConHandle, BSTR sSource, BSTR sDestination, BOOL bFailIfExists)
{
CHECK_TM_DOWN();
IRmWorker * pWorker = GetWorker(lConHandle);
if (!pWorker)
{
return E_INVALIDARG;
}

HRESULT hr =pWorker -> FileCopyTx(sSource, sDestination, bFailIfExists);
return hr;
}

STDMETHODIMP CFileRm::FileRenameTx(long lConHandle, BSTR sSource, BSTR sDestination)
{

CHECK_TM_DOWN();
IRmWorker * pWorker = GetWorker(lConHandle);
if (!pWorker)
{
return E_INVALIDARG;
}

HRESULT hr =pWorker -> FileRenameTx(sSource, sDestination);
return hr;
}

STDMETHODIMP CFileRm::AddFile(long lConHandle, BSTR sSource, BSTR sDestination)
{

CHECK_TM_DOWN();
IRmWorker * pWorker = GetWorker(lConHandle);
if (!pWorker)
{
return E_INVALIDARG;
}

HRESULT hr =pWorker -> AddFile(sSource, sDestination);
return  hr;
}

STDMETHODIMP CFileRm::CopyListTx(long lConHandle)
{

CHECK_TM_DOWN();
IRmWorker * pWorker = GetWorker(lConHandle);
if (!pWorker)
{
return E_INVALIDARG;
}

HRESULT hr =  pWorker -> CopyListTx();
return  hr;
}

STDMETHODIMP CFileRm::FileMoveTxt(long lConHandle, BSTR sSource, BSTR sDestination)
{

CHECK_TM_DOWN();
IRmWorker * pWorker = GetWorker(lConHandle);
if (!pWorker)
{
return E_INVALIDARG;
}

HRESULT hr =  pWorker -> FileMoveTxt(sSource, sDestination);
return  hr;
}

STDMETHODIMP CFileRm::FileDeleteTx(long lConHandle, BSTR sFileName)
{

CHECK_TM_DOWN();
IRmWorker * pWorker = GetWorker(lConHandle);
if (!pWorker)
{
return E_INVALIDARG;
}

HRESULT hr =  pWorker -> FileDeleteTx(sFileName);
return  hr;

}

STDMETHODIMP CFileRm::ResetConnection(long lConHandle)
{

CHECK_TM_DOWN();
IRmWorker * pWorker = GetWorker(lConHandle);
if (!pWorker)
{
return E_INVALIDARG;
}

return pWorker -> Reset();

}
//
//FinalConstruct()
//
//
HRESULT CFileRm::FinalConstruct()
{
HRESULT hRes;
hRes = OpenLog();
if (hRes == S_OK)
{
hRes = InitializeRm();
{
if (hRes == S_OK)
hRes = Recover();
}
}
return hRes;
}

//
// Perform any required recovery.
//
HRESULT CFileRm::Recover()
{
Lock();
IStorage * pStorage = NULL;
long lCount = 0;
HRESULT hr;
hr = m_pLog -> RecoveryInfo(&pStorage);
if (FAILED(hr))
{
Unlock();
return hr;
}

IRmWorker * pWorker = NULL;
hr = CoCreateInstance(CLSID_CRmWorkerObj, NULL, CLSCTX_SERVER, IID_IRmWorker , (void **)&pWorker);
if (FAILED(hr))
{
Unlock();
return hr;
}

BSTR sRecDir = m_options.GetRecDir();
_ASSERTE(sRecDir);
hr = pWorker -> Init(m_pLog, m_pIResMgr, m_punkDTC, sRecDir);
if (FAILED(hr))
{
Unlock();
return hr;
}
hr = pWorker -> Recover(pStorage);
Unlock();
SafeRelease(pWorker);
return hr;
}

HRESULT CFileRm::InitializeRm()
{
HRESULThr = E_FAIL;
IResourceManagerFactory*pIRmFactory= NULL;

//
// we make have a call to this originating from the Resource Dispenser
// and while we are in the call, the worker thread wakes up and calls it again
//

Lock();

if (m_punkDTC)
{
Unlock();
return S_OK;// while we were Locked, somebody successfully finished
}

//
// Establish contact with the MS DTC transaction manager.
//

hr = DtcGetTransactionManager (NULL,
NULL,
IID_IUnknown,
0,
0,
0,
(LPVOID *) &m_punkDTC);
if (S_OK != hr)
{

Unlock();
return hr;

}

//
//if I wan't caching the IUnknown * of DTC, I could ask for this
//interface directly from the DtcGetTransactionManager API, and
//avoid the QI
//
hr = m_punkDTC->QueryInterface(IID_IResourceManagerFactory,
    (LPVOID *) &pIRmFactory);

_ASSERTE(hr == S_OK); // this can never fail

CComObject<CResourceManagerSink> * pSink = NULL;
hr = CComObject<CResourceManagerSink>::CreateInstance(&pSink);

pSink -> SetRm((IFileRm *)this);

IResourceManagerSink * pISink = NULL;
pSink -> QueryInterface(IID_IResourceManagerSink, (void **)&pISink);
_ASSERTE(pISink);

// Create instance of the resource manager interface.
hr = pIRmFactory->Create (&CopyFileRmGuid,
   "CopyFileRm",
   pISink,
   &m_pIResMgr );
//
//DTC owns the reference (and lifetime) to the IResourceManagerSink interface
//

SafeRelease(pSink);

if (FAILED(hr))
{
SafeRelease(m_punkDTC);

}

SafeRelease(pIRmFactory);

Unlock();
return hr;

}

HRESULT CFileRm::OpenLog()
{

_Module.m_dlg = new CRmDlg(&m_options);
_ASSERTE(_Module.m_dlg);
HWND hWnd = _Module.m_dlg -> Create(NULL);
_Module.m_dlg->ShowWindow(SW_SHOWNORMAL);
_Module.m_dlg->SetUsageCount(1);
_ASSERTE(hWnd);

m_pLog = NULL;
HRESULT hr;
hr = CoCreateInstance(CLSID_CoSimpleLog, NULL, CLSCTX_SERVER, IID_ISimpleLog , (void **)&m_pLog);
if (FAILED(hr))
{
return hr;
}

CComBSTR sFileName = m_options.GetLogDir();
_ASSERTE(sFileName.m_str);
sFileName += L"FileRmLog";
hr = m_pLog -> Init(sFileName);
if (FAILED(hr))
return hr;

return hr;
}

HRESULT CFileRm::FinalRelease()
{
SafeRelease(m_punkDTC);
SafeRelease(m_pIResMgr);
return S_OK;
}
//
//the resource Manager's TM goes down
//
//we need to
//1. set a global variarable so we won't except any new connections
//2. free all RmWorkers (make sure they have a TMDown() event already
//3. start a worker thread and ping DTC until it comes up
//4. when DTC comes back up, do recovery and then accept new connections
//
//NOTE:  You should NEVER block a DTC thread!
//
STDMETHODIMP CFileRm::TMDown()
{

m_bTMDown = TRUE;
WorkerSet::iterator item = m_setWorkers.begin();
IRmWorker * pWorker;
while (item != m_setWorkers.end())
{
pWorker = (IRmWorker *)(*item); // get the value
//
//if the Worker object was enlisted in DTC, then we
//are GUARANTEED that they will have gotten the TMDown()
//before the RM gets the TMDown(), but I want to make sure
//that all Workers get the TMDown, not just the enlisted ones.
//my implementation of TMDown() doesn't care if it is called
//twice, so rather than check the state and call TMDown on the
//ones that haven't gotten it yet, I'll call it for all workers
//

if (pWorker)
pWorker -> TMDown();
else
m_setWorkers.erase(item++);

item++;
}
if (m_cbTmAddr)
{
delete m_rgbTmAddr;
m_cbTmAddr = 0;
}

SafeRelease(m_punkDTC);
SafeRelease(m_pIResMgr);
SafeRelease(m_pLog);

DWORD dwThreadID;
HANDLE hThread;
hThread = CreateThread(NULL, 0, TMDownWorkerThread, (LPVOID)this, NULL, &dwThreadID);
return S_OK;
}

//
//Worker Thread that waits for the RM to come back up
//
DWORD WINAPI TMDownWorkerThread( LPVOID pData)
{

CFileRm * pRm = (CFileRm *)pData;
BOOL bGo= TRUE;
DWORD dwSleepTime = 1000 * 10;

while (bGo)
{

if (pRm -> InitializeRm() == S_OK)
bGo = FALSE;

Sleep(dwSleepTime);

}

HRESULT hRes;
hRes = pRm -> TmUp();
return hRes;

}
//
//called after the TM goes down -- resets everything to a good state
//
//
HRESULT CFileRm::TmUp()
{

HRESULT hRes;
hRes = OpenLog();
if (SUCCEEDED(hRes))
{
hRes = Recover();
}
if (SUCCEEDED(hRes))
{
_ASSERTE(GetLog());
_ASSERTE(GetIResourceManager());
_ASSERTE(GetDTCUnknown());

SetTMDown(FALSE);
WorkerSet::iterator item = m_setWorkers.begin();
IRmWorker * pWorker;
while (item != m_setWorkers.end())
{
pWorker = (IRmWorker *)(*item); // get the value
hRes = pWorker -> Init((IUnknown *)GetLog(), (IUnknown *)GetIResourceManager(), GetDTCUnknown(), m_options.GetRecDir());
_ASSERTE(hRes == S_OK);//this really can't fail...
item++;
}

}

hRes = GetMyTmWhereAbouts();

return hRes;

}

HRESULT CFileRm::GetMyTmWhereAbouts()
{

_ASSERTE(GetLog());
_ASSERTE(GetIResourceManager());
_ASSERTE(GetDTCUnknown());
ULONGcbUsed = 0;

ITransactionImportWhereabouts*pITxWhere = 0;

// Ensure that the resource manager is initializing

// Get a pointer to the ITransactionWhereabouts interface.
HRESULT hRc = m_punkDTC->QueryInterface (IID_ITransactionImportWhereabouts,
 (LPVOID *) &pITxWhere);
_ASSERTE (S_OK == hRc);
if (S_OK != hRc)
{
pITxWhere = 0;
return hRc;
} // if

// Get the size of the whereabouts blob for the TM
pITxWhere->GetWhereaboutsSize (&m_cbTmAddr);
m_rgbTmAddr = new BYTE[m_cbTmAddr];
// Get the whereabouts blob
hRc = pITxWhere->GetWhereabouts (m_cbTmAddr, m_rgbTmAddr, &cbUsed);
SafeRelease(pITxWhere);
return hRc;
}


FILERM.H

/******************************************************************************\
********************************************************************************
* Filename: FileRm.h
*
* Description: Declaration of the CFileRm
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#ifndef __FILERM_H_
#define __FILERM_H_

#include "resource.h"       // main symbols
#include <set>
interface IResourceManager;

extern long glWorkers;  // a global count of the number of workers connected to...
using namespace std;

typedef set<long> WorkerSet;

/////////////////////////////////////////////////////////////////////////////
// CFileRm
class ATL_NO_VTABLE CFileRm :
public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<CFileRm, &CLSID_CoFileRm>,
public IFileRm
{

private:

BYTE *m_rgbTmAddr;
ULONG m_cbTmAddr;

IResourceManager*m_pIResMgr;// Resource manager interface.
ISimpleLog*m_pLog;// log interface
IUnknown*m_punkDTC;// DTC

BOOL m_bTMDown;
WorkerSet m_setWorkers;
CRmOptions m_options;

public:
BOOL IsTMDown(){return m_bTMDown;}
void SetTMDown(BOOL bDown){m_bTMDown = bDown;}

HRESULT TmUp();
HRESULT GetMyTmWhereAbouts();
ISimpleLog * GetLog(BOOL bAddRef = FALSE)
{
if (m_pLog)
{
if (bAddRef) m_pLog -> AddRef();
}

return m_pLog;

}
IResourceManager * GetIResourceManager(BOOL bAddRef = FALSE)
{
if (m_pIResMgr)
{
if (bAddRef) m_pIResMgr -> AddRef();
}
return m_pIResMgr;

}
IUnknown * GetDTCUnknown(BOOL bAddRef = FALSE)
{
if (m_punkDTC)
{
if (bAddRef) m_punkDTC -> AddRef();
}
return m_punkDTC;
}

HRESULT OpenLog();
HRESULT Recover();
HRESULT InitializeRm();

CFileRm()
{
m_cbTmAddr = 0;
m_rgbTmAddr = NULL;
m_punkDTC = NULL;
m_pIResMgr = NULL;
m_bTMDown = FALSE;

}

IRmWorker * GetWorker(long lHandle)
{
_ASSERTE(lHandle);
IRmWorker * pWorker  = NULL;
pWorker = (IRmWorker *)(*m_setWorkers.find(lHandle));
return pWorker;

//return (IRmWorker *)lHandle;
}

~CFileRm()
{
WorkerSet::iterator item = m_setWorkers.begin();
IUnknown * pUnk;
while (item != m_setWorkers.end())
{
pUnk = (IUnknown *)(*item); // get the value
SafeRelease(pUnk);
item++;
}
m_setWorkers.clear();

if (m_cbTmAddr)
delete m_rgbTmAddr;
}

DECLARE_CLASSFACTORY_SINGLETON(CFileRm);
DECLARE_PROTECT_FINAL_CONSTRUCT();
DECLARE_REGISTRY_RESOURCEID(IDR_FILERM)
DECLARE_NOT_AGGREGATABLE(CFileRm)

BEGIN_COM_MAP(CFileRm)
COM_INTERFACE_ENTRY(IFileRm)
END_COM_MAP()

HRESULT FinalConstruct();
// IFileRm

STDMETHOD(GetTmWhereabouts)(long lConHandle, BYTE **rgbTmAddr, ULONG *pcbTmAddr);
STDMETHOD(ExportTx)(long lConHandle, ULONG cbTranCookie, BYTE *rgbTranCookie);
STDMETHOD(Connect)(BSTR sAppName, long * ulConnHandle);
STDMETHOD(Disconnect)(long ulConnHandle);
STDMETHOD(FileCopyTx)(long lConHandle, BSTR sSource, BSTR sDestination, BOOL bFailIfExists);
STDMETHOD(FileRenameTx)(long lConHandle, BSTR sSource, BSTR sDestination);
STDMETHOD(AddFile)(long lConHandle, BSTR sSource, BSTR sDestination);
STDMETHOD(CopyListTx)(long lConHandle);
STDMETHOD(FileMoveTxt)(long lConHandle, BSTR sSource, BSTR sDestination);
STDMETHOD(FileDeleteTx)(long lConHandle, BSTR sFileName);
STDMETHOD(ResetConnection)(long lConHandle);

// IFileRm
public:
STDMETHOD(TMDown)();
HRESULT FinalRelease();

};

#endif //__FILERM_H_


RESOURCE.H

/******************************************************************************\
********************************************************************************
* Filename: resource.h
*
* Description: Resouce includes
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by CopyFileRm.rc
//
#define IDS_PROJNAME                    100
#define IDR_CopyFileRm                  100
#define IDR_FILERM                      101
#define IDD_RMDLG                       102
#define IDD_RMOPTIONSDLG                103
#define IDR_MENU1                       202
#define IDC_LOG                         202
#define IDC_RECOVER                     203
#define IDC_LOGDIR                      203
#define IDC_RECOVERDIR                  204
#define IDC_USAGE                       212
#define IDC_LOCKS                       213
#define ID_FILE_EXIT                    32768
#define ID_OPTIONS                      32769

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        201
#define _APS_NEXT_COMMAND_VALUE         32768
#define _APS_NEXT_CONTROL_VALUE         205
#define _APS_NEXT_SYMED_VALUE           104
#endif
#endif


RMDLG.CPP

/******************************************************************************\
********************************************************************************
* Filename: RmDlg.cpp
*
* Description: Implementation of CRmDlg
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
#include "stdafx.h"
#include "RmDlg.h"
#include "rmoptions.h"
#include "RmOptionsDlg.h"

/////////////////////////////////////////////////////////////////////////////
// CRmDlg

CRmDlg::CRmDlg()
{
m_pOptions = NULL;
}

CRmDlg::~CRmDlg()
{
}

LRESULT CRmDlg::OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{

_ASSERTE(m_pOptions);
SetDlgItemText(IDC_LOGDIR, m_pOptions -> GetLogDir());
SetDlgItemText(IDC_RECOVERDIR, m_pOptions -> GetRecDir());

return 1;  // Let the system set the focus
}

LRESULT CRmDlg::OnOK(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
return OnExit(wNotifyCode, wID, hWndCtl, bHandled);

}

LRESULT CRmDlg::OnCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
return OnExit(wNotifyCode, wID, hWndCtl, bHandled);
}

void CRmDlg::SetUsageCount(long lCount)
{
if (!m_hWnd)
return;

TCHAR sz[16];
wsprintf((TCHAR *)sz,L"%ld", lCount);
SetDlgItemText(IDC_USAGE, sz);
UpdateWindow();
}

LRESULT CRmDlg::OnExit(WORD wNotify, WORD wID, HWND hWnd, BOOL& bHandled)
{

long lLockCount = _Module.GetLockCount();
if (lLockCount > 1)
{

TCHAR sz[128];
wsprintf((TCHAR *)sz,L"There are outstanding references on this object!\n\nLockCount is: %ld\nClosing it will probably cause a GP Fault!\nDo you wish to close the RM?", lLockCount - 1);
if (::MessageBox(NULL, sz,L"File Resource Manager", MB_ICONSTOP|MB_YESNO) == IDNO)
return 0;

try
{
while (_Module.Unlock() > 1)
;
}
catch(...)
{

}

}
else
{

if (::MessageBox(NULL, L"Do you wish to close the RM?",L"File Resource Manager", MB_ICONQUESTION|MB_YESNO) == IDNO)
return 0;

}

bHandled = TRUE;
DWORD dwThreadID = GetCurrentThreadId();
PostThreadMessage(dwThreadID, WM_QUIT, 0, 0);
return 0;
}

void CRmDlg::SetLockCount(long lCount)
{

if (!m_hWnd)
return;

TCHAR sz[16];
wsprintf((TCHAR *)sz,L"%ld", lCount);
SetDlgItemText(IDC_LOCKS, sz);
UpdateWindow();
}
LRESULT CRmDlg::OnOptions(WORD wNotify, WORD wID, HWND hWnd, BOOL& bHandled)
{
_ASSERTE(m_pOptions);
CRmOptionsDlg dlg(m_pOptions);
if (dlg.DoModal() == IDOK)
{
SetDlgItemText(IDC_LOGDIR, dlg.GetLogDir());
SetDlgItemText(IDC_RECOVERDIR, dlg.GetRecoverDir());

}
return 1;  // Let the system set the focus
}


RMDLG.H

/******************************************************************************\
********************************************************************************
* Filename: RmDlg.h
*
* Description:: Declaration of the CRmDlg
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#ifndef __RMDLG_H_
#define __RMDLG_H_

#include "resource.h"       // main symbols
class CRmOptions;
/////////////////////////////////////////////////////////////////////////////
// CRmDlg
class CRmDlg :
public CDialogImpl<CRmDlg>
{

private:

CComBSTR m_sRecoverDir;
CComBSTR m_sLogDir;
CRmOptions * m_pOptions;

public:
void SetLockCount(long lCount);
CRmDlg();
~CRmDlg();

CRmDlg(CRmOptions * pOpt)
{
m_pOptions = pOpt;
}

void SetOptions(CRmOptions * pOpt)
{
m_pOptions = pOpt;
}

CComBSTR &GetLogDir(){return m_sLogDir;}
CComBSTR &GetRecoverDir(){return m_sRecoverDir;}

enum { IDD = IDD_RMDLG };

BEGIN_MSG_MAP(CRmDlg)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
COMMAND_ID_HANDLER(IDOK, OnOK)
COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
COMMAND_ID_HANDLER(ID_FILE_EXIT, OnExit)
COMMAND_ID_HANDLER(WM_CLOSE, OnExit)
COMMAND_ID_HANDLER(ID_OPTIONS, OnOptions)

END_MSG_MAP()

LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnOK(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnExit(WORD wNotify, WORD wID, HWND hWnd, BOOL& bHandled);
LRESULT OnOptions(WORD wNotify, WORD wID, HWND hWnd, BOOL& bHandled);

void SetUsageCount(long lCount);
};

#endif //__RMDLG_H_


RMOPTIONS.H

/******************************************************************************\
********************************************************************************
* Filename: rmoptions.h
*
* Description:

Contains a class that encapsulates all options for the CopyFileRm
Automatically persists/loads itself to/from the registry

*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
class CRmOptions
{
private:
HKEY m_hKey;
CComBSTR m_sLogDir;
CComBSTR m_sRecDir;
public:

CComBSTR & GetLogDir(){return m_sLogDir;}
CComBSTR & GetRecDir(){return m_sRecDir;}

BOOL SetLogDir(BSTR sLogDir)
{

m_sLogDir = sLogDir;
_ASSERTE(m_hKey);
if (!m_sLogDir.m_str)
{
_ASSERTE(0);
return FALSE;
}

LPTSTR lpszValueName = _T("LogDir");
if (RegSetValueEx(m_hKey, lpszValueName, NULL, REG_SZ, (BYTE *)m_sLogDir.m_str, m_sLogDir.Length() * 2) == 0)
return TRUE;
return FALSE;

}

BOOL SetRecDir(BSTR sRecDir)
{

m_sRecDir = sRecDir;
_ASSERTE(m_hKey);
if (!m_sRecDir.m_str)
{
_ASSERTE(0);
return FALSE;
}

LPTSTR lpszValueName = _T("RecoverDir");
if (RegSetValueEx(m_hKey, lpszValueName, NULL, REG_SZ, (BYTE *)m_sRecDir.m_str, m_sRecDir.Length() * 2) == 0)
return TRUE;

return FALSE;

}

CRmOptions()
{
LPTSTR sKey= L"Software\\Microsoft\\CopyFileRm";
DWORD dwDisp;
LONG lRes = RegCreateKeyEx( HKEY_CURRENT_USER, sKey, NULL, NULL, REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS,NULL, &m_hKey, &dwDisp);
_ASSERTE(m_hKey);
TCHAR szDir[MAX_PATH];
DWORD dw = 0;
DWORD dwType = 0;
TCHAR* lpszValueName = L"LogDir";
lRes = RegQueryValueEx(m_hKey, (TCHAR*)lpszValueName, NULL, &dwType,
NULL, &dw);
if (lRes == ERROR_SUCCESS)
{
_ASSERTE(dwType == REG_SZ);
TCHAR* lpsz = szDir;
lRes = RegQueryValueEx(m_hKey, (TCHAR*)lpszValueName, NULL, &dwType, (BYTE*)lpsz, &dw);
_ASSERTE(lRes == ERROR_SUCCESS);
}
else
{
GetTempPath(MAX_PATH, szDir); // if it is not in the registery, put it in the temp directory
}

m_sLogDir = szDir;
dw = 0;
dwType = REG_SZ;
lpszValueName = L"RecoverDir";
lRes = RegQueryValueEx(m_hKey, (TCHAR*)lpszValueName, NULL, &dwType, NULL, &dw);
if (lRes == ERROR_SUCCESS)
{
_ASSERTE(dwType == REG_SZ);
TCHAR* lpsz = szDir;
lRes = RegQueryValueEx(m_hKey, (TCHAR*)lpszValueName, NULL, &dwType, (BYTE*)lpsz, &dw);
_ASSERTE(lRes == ERROR_SUCCESS);
}
else
{
GetTempPath(MAX_PATH, szDir); // if it is not in the registery, put it in the temp directory

}

m_sRecDir = szDir;
}

~CRmOptions()
{
RegCloseKey(m_hKey);
}

};


RMOPTIONSDLG.CPP

/******************************************************************************\
********************************************************************************
* Filename: RmOptionsDlg.cpp
*
* Description: Implementation of CRmOptionsDlg
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#include "stdafx.h"
#include "rmoptions.h"
#include "RmOptionsDlg.h"
#include "direct.h"

/////////////////////////////////////////////////////////////////////////////
// CRmOptionsDlg

CRmOptionsDlg::CRmOptionsDlg()
{
m_pOptions = new CRmOptions;
m_bCreatedOptions = TRUE;

}
CRmOptionsDlg::~CRmOptionsDlg()
{
if(m_bCreatedOptions)
delete m_pOptions;

}

LRESULT CRmOptionsDlg::OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
SetDlgItemText(IDC_LOG, m_pOptions -> GetLogDir());
SetDlgItemText(IDC_RECOVER, m_pOptions -> GetRecDir());
return 1;  // Let the system set the focus
}

LRESULT CRmOptionsDlg::OnOK(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{

TCHAR sPath[MAX_PATH];
GetDlgItemText(IDC_LOG, sPath, MAX_PATH);

LPTSTR p = sPath + lstrlen(sPath) - 1;
if (*p != '\\')
{
p++;
*p = '\\';
p++;
*p = NULL;
}

if (!CheckDirectory(sPath))
return 0;

m_pOptions -> SetLogDir(sPath);
GetDlgItemText(IDC_RECOVER, sPath, MAX_PATH);
p = sPath + lstrlen(sPath) - 1;
if (*p != '\\')
{
p++;
*p = '\\';
p++;
*p = NULL;
}

if (!CheckDirectory(sPath))
return 0;

m_pOptions -> SetRecDir(sPath);
EndDialog(wID);
return 0;
}

LRESULT CRmOptionsDlg::OnCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
EndDialog(wID);
return 0;
}

void CRmOptionsDlg::ShowMessage()
{
LPTSTR lpMsgBuf = NULL;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &lpMsgBuf,
0,
NULL
);

if (lpMsgBuf)
{
// Display the string.
MessageBox(lpMsgBuf, L"GetLastError", MB_OK|MB_ICONINFORMATION );

// Free the buffer.
LocalFree( lpMsgBuf );
}
}

BOOL CRmOptionsDlg::CheckDirectory(BSTR sDir)
{

// make sure dirs exist
TCHAR sCurWD[MAX_PATH];

 /* Get the current working directory: */
if( _tgetcwd( sCurWD, MAX_PATH ) == NULL )
{
_ASSERTE(0); // why would this fail?
return FALSE;
}

// try to set the currect directory to the sDir
if (_tchdir(sDir) == -1)
{
TCHAR sMsg[1024];
wsprintf(sMsg, L"The directory %s does not exist.\nDo you wish to create it?", sDir);
if (MessageBox(sMsg, L"CopyFileRm", MB_ICONQUESTION|MB_YESNO) == IDNO)
return FALSE;
else
{
if (_tmkdir( sDir ) != 0)
{
wsprintf(sMsg, L"Unable to create directory '%s'", sDir);
MessageBox(sMsg, L"CopyFileRm", MB_ICONSTOP);
return FALSE;
}

}

}

_tchdir(sCurWD);
return TRUE;
}


RMOPTIONSDLG.H

/******************************************************************************\
********************************************************************************
* Filename: RmOptionsDlg.h
*
* Description: Declaration of the CRmOptionsDlg -- used for modifying the options
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#ifndef __RMOPTIONSDLG_H_
#define __RMOPTIONSDLG_H_

#include "resource.h"       // main symbols

/////////////////////////////////////////////////////////////////////////////
// CRmOptionsDlg
class CRmOptionsDlg :
public CDialogImpl<CRmOptionsDlg>
{
private:

CRmOptions  * m_pOptions;
BOOL m_bCreatedOptions;

public:
BOOL CheckDirectory(BSTR sDir);
void ShowMessage();
CComBSTR &GetLogDir(){return m_pOptions -> GetLogDir();}
CComBSTR &GetRecoverDir(){return m_pOptions -> GetRecDir();}

CRmOptionsDlg();
CRmOptionsDlg(CRmOptions * pOptions){_ASSERTE(pOptions); m_pOptions = pOptions;m_bCreatedOptions = FALSE;}
~CRmOptionsDlg();

enum { IDD = IDD_RMOPTIONSDLG };

BEGIN_MSG_MAP(CRmOptionsDlg)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
COMMAND_ID_HANDLER(IDOK, OnOK)
COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
END_MSG_MAP()

LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnOK(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);

};

#endif //__RMOPTIONSDLG_H_


RMSINK.H

/******************************************************************************\
********************************************************************************
* Filename: RmSink.h
*
* Description: implements the DTC interface IResourceManagerSink
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
#ifndef __RMSINK_H__
#define __RMSINK_H__

interface IFileRm;
class ATL_NO_VTABLE CResourceManagerSink :
public CComObjectRootEx<CComMultiThreadModel>,
public IResourceManagerSink

{

public:

BEGIN_COM_MAP(CResourceManagerSink)
COM_INTERFACE_ENTRY(IResourceManagerSink)
END_COM_MAP()

IFileRm * m_pRm;
CResourceManagerSink()
{
m_pRm = NULL;
}

void SetRm(IFileRm * pRm)
{
m_pRm = pRm; // NOTE:  no reference added
}

//
//delegate to the RM
//
STDMETHODIMP TMDown()
{
_ASSERTE(m_pRm);
if (m_pRm)
return m_pRm -> TMDown();

return E_UNEXPECTED;
}

};

#endif


STDAFX.CPP

/******************************************************************************\
********************************************************************************
* Filename: StdAfx.cpp
*
* Description:

  stdafx.cpp : source file that includes just the standard includes
  stdafx.pch will be the pre-compiled header
  stdafx.obj will contain the pre-compiled type information

*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#include "stdafx.h"

#ifdef _ATL_STATIC_REGISTRY
#include <statreg.h>
#include <statreg.cpp>
#endif

#include <atlimpl.cpp>
#include <atlwin.cpp>


STDAFX.H

/******************************************************************************\
********************************************************************************
* Filename: StdAfx.h
*
* Description:

  stdafx.h : include file for standard system include files,
      or project specific include files that are used frequently,
      but are changed infrequently

*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#if !defined(AFX_STDAFX_H__BACEDF7E_74AB_11D0_B162_00AA00BA3258__INCLUDED_)
#define AFX_STDAFX_H__BACEDF7E_74AB_11D0_B162_00AA00BA3258__INCLUDED_

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

#define STRICT

#define _WIN32_WINNT 0x0400
#define _ATL_FREE_THREADED

#define SafeRelease(pUnk) {if (pUnk){pUnk -> Release();pUnk = NULL; }}

#include <atlbase.h>
#pragma warning( disable : 4786)
class CRmDlg;
interface ISimpleLog;

interface IResourceManager;
class CExeModule : public CComModule
{
public:

CRmDlg * m_dlg;
LONG Unlock();
DWORD dwThreadID;
LONG Lock( );

};
extern CExeModule _Module;
#include <atlcom.h>
#include <atlwin.h>

//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_STDAFX_H__BACEDF7E_74AB_11D0_B162_00AA00BA3258__INCLUDED)


CPYIMAGE.CPP

/******************************************************************************\
********************************************************************************
* Filename: CpyImage.cpp
*
* Description: implementation of WinMain
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
#include "stdafx.h"
#include "resource.h"
#include "CpyImage.h"
//#include "dlldatax.h"
#include "mtsevents.h"
#include "CpyImage_i.c"
#include "sqltypes.h" // for definition of HSTMT
#include "CpyImageObj.h"

#ifdef _MERGE_PROXYSTUB
extern "C" HINSTANCE hProxyDll;
#endif

CComModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_CpyImage, CCpyImageObj)
END_OBJECT_MAP()

/////////////////////////////////////////////////////////////////////////////
// DLL Entry Point

extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
lpReserved;
#ifdef _MERGE_PROXYSTUB
if (!PrxDllMain(hInstance, dwReason, lpReserved))
return FALSE;
#endif
if (dwReason == DLL_PROCESS_ATTACH)
{
_Module.Init(ObjectMap, hInstance);
DisableThreadLibraryCalls(hInstance);
}
else if (dwReason == DLL_PROCESS_DETACH)
_Module.Term();
return TRUE;    // ok
}

/////////////////////////////////////////////////////////////////////////////
// Used to determine whether the DLL can be unloaded by OLE

STDAPI DllCanUnloadNow(void)
{
#ifdef _MERGE_PROXYSTUB
if (PrxDllCanUnloadNow() != S_OK)
return S_FALSE;
#endif
return (_Module.GetLockCount()==0) ? S_OK : S_FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// Returns a class factory to create an object of the requested type

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
#ifdef _MERGE_PROXYSTUB
if (PrxDllGetClassObject(rclsid, riid, ppv) == S_OK)
return S_OK;
#endif
return _Module.GetClassObject(rclsid, riid, ppv);
}

/////////////////////////////////////////////////////////////////////////////
// DllRegisterServer - Adds entries to the system registry

STDAPI DllRegisterServer(void)
{
#ifdef _MERGE_PROXYSTUB
HRESULT hRes = PrxDllRegisterServer();
if (FAILED(hRes))
return hRes;
#endif

if (FAILED(_Module.RegisterTypeLib()))
_ASSERTE(0);
// registers object, typelib and all interfaces in typelib
return _Module.RegisterServer(TRUE);
}

/////////////////////////////////////////////////////////////////////////////
// DllUnregisterServer - Removes entries from the system registry

STDAPI DllUnregisterServer(void)
{
#ifdef _MERGE_PROXYSTUB
PrxDllUnregisterServer();
#endif
_Module.UnregisterServer();
#if _WIN32_WINNT >= 0x0400
UnRegisterTypeLib(LIBID_CPYIMAGELib, 1, 0, LOCALE_USER_DEFAULT, SYS_WIN32);
#endif
return S_OK;
}


CPYIMAGEOBJ.CPP

/******************************************************************************\
********************************************************************************
* Filename: CpyImageObj.cpp
*
* Description: Implementation of CCpyImageObj
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
#include "stdafx.h"
#include "CpyImage.h"
#include "mtsevents.h"

#ifndef DBNTWIN32
#define DBNTWIN32

#include <SQL.h>
#include <SQLEXT.h>
#include <ODBCINST.h>
#include "mtx.h"
#include "CpyImageObj.h"

#endif /* DBNTWIN32 */

#include "txfiledisp.h"
#include "txfiledisp_i.c"
#include "mtsusereventobj.h"
#include "mtsusereventobj_i.c"

#include "mtsevents_i.c"

/////////////////////////////////////////////////////////////////////////////
// CCpyImageObj
void GetError(HENV henv, HDBC hdbc, HSTMT hstmt, BSTR * msg);

STDMETHODIMP CCpyImageObj::InterfaceSupportsErrorInfo(REFIID riid)
{
static const IID* arr[] =
{
&IID_ICpyImage,
};
for (int i=0;i<sizeof(arr)/sizeof(arr[0]);i++)
{
if (InlineIsEqualGUID(*arr[i],riid))
return S_OK;
}
return S_FALSE;
}
//
//Inserts data into the database, and copies a file to the specified directory
//
//NOTE:  this is designed as a sample application and as such does not call
//SetComplete or SetAbort until the client application calls another method
//
//the [out] parameter is here so that the client can display a message box on an error
//in order to propagate the message to the client, the function returns S_FALSE for all
//errors
//
STDMETHODIMP CCpyImageObj::InsertData(BSTR sFromFile, BSTR sToFile,
   BSTR sDescription, BSTR sMainColor,
   BSTR sTheme, BSTR * msg)
{

RETCODE                  rc = 0;
    RETCODE                  rc1 = 0;
    HENV                     henv = NULL;
    HDBC                     hdbc = NULL;
    HSTMT                    hstmt = NULL;

long hPxyCon = 0;
    TCHAR sSQL[2048];;

HRESULT hr;

if (m_pEvents)
{

CComBSTR sEvent = L"Source: ";
sEvent += sFromFile;
sEvent += L" Destination: ";
sEvent += sToFile;

MtsTrace(sEvent);
IMtsUserEventData * pData = NULL;
CoCreateInstance(CLSID_MtsUserEventData, NULL, CLSCTX_ALL, IID_IMtsUserEventData, (void **)&pData);
_ASSERTE(pData);
pData->put_DisplayName(CComBSTR("CopyImage::InsertData"));
CComVariant v;
v = sFromFile;
pData -> put_Value(CComBSTR("From File"), &v);

v = sToFile;
pData -> put_Value(CComBSTR("To File"), &v);

v = sDescription;
pData -> put_Value(CComBSTR("Description"), &v);

v = sMainColor;
pData -> put_Value(CComBSTR("Main Color"), &v);

v = sTheme;
pData -> put_Value(CComBSTR("Theme"), &v);
v = (IUnknown *) pData;

m_pEvents -> PostEvent(&v);
pData -> Release();
MtsTraceObject(L"CopyFile Event", sEvent);

}

// create the Proxy
   IFileRmPxy * pPxy = NULL;

hr = CoCreateInstance(CLSID_FileRmPxy, NULL, CLSCTX_ALL, IID_IFileRmPxy, (void **)&pPxy);
if (FAILED(hr))
{
        *msg = ::SysAllocString(L"Failed to create the CopyFileRm Resource Dispenser!\r\nMake sure that txfiledisp.dll is properly registered.");
goto Exit;
}

    /* Allocate the Environment handle */
    rc = SQLAllocEnv( &henv );
    if( rc )
    {
        *msg = ::SysAllocString(L"SQLAllocEnv failed!");
        goto Exit;
    }

    /* Allocate the Connection handle */
    rc = SQLAllocConnect( henv, &hdbc );
    if( rc )
    {

        *msg = ::SysAllocString(L"SQLAllocConnect failed!");
goto Exit;
    }

SQLSetConnectOption(hdbc, SQL_LOGIN_TIMEOUT, 5);

    /* Establish the database connection */
    rc = SQLConnect( hdbc, L"MTXSamples", SQL_NTS,
L"sa", SQL_NTS,
L"", SQL_NTS );

    if( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO )
    {
     *msg = ::SysAllocString(L"SQLConnect() failed.\r\nMake sure that the ODBC DSN 'MTXSamples' is created, and\r\nthat sa has a NULL password (or change the source code)");
        goto Exit;
    }

    /* Allocate the statement handle */
    rc = SQLAllocStmt( hdbc, &hstmt );
    if( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO )
    {
        GetError( henv, hdbc, hstmt, msg);
        goto Exit;
    }

// done connecting to SQL Server, now Connect to the FileRm

hr = pPxy -> Connect(&hPxyCon);
_ASSERTE(hr == S_OK);

// SQL Do Work
     wsprintf(sSQL, L"insert into image values ('%s', '%s', '%s', '%s')",
sDescription, sMainColor, sTheme, sToFile);

//
//insert the row into the database
//
//NOTE: due to SQL server page locking, this will block all subsequent calls (until it returns)
//  it would be fairly easy to work around this, but I haven't done if for the demo.
//

    rc = SQLExecDirect( hstmt, sSQL, SQL_NTS );

    if( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO )
    {
//GetError( henv, hdbc, hstmt, msg);
BOOL bRet = CreateTable(hstmt, msg);
if (!bRet)
goto Exit;
else
{
// insert the row into the database
    rc = SQLExecDirect( hstmt, sSQL, SQL_NTS );
if( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO )
{
GetError( henv, hdbc, hstmt, msg);
goto Exit;
}
}
}

// FileRM DoWork

hr = pPxy -> CopyFileTx(hPxyCon, sFromFile, sToFile, FALSE);

if (FAILED(hr))
{
*msg = ::SysAllocString(L"CopyFileTx Failed\r\nMake sure that your destination directory exists\r\nand that CPYIMAGE.DLL is installed in a package and\r\nmarked as Requires Transaction");
goto Exit;
}

// SQL Free Resources

    /* Free the statement handle */
    rc = SQLFreeStmt( hstmt, SQL_DROP );
    if( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO )
    {
GetError( henv, hdbc, hstmt, msg);
    }

    Exit:

// FileRm Free Resources
HRESULT hrExit; // we don't want to touch the HRESULT obove the Exit: identifier
if (pPxy)
{
if (hPxyCon) // if we have a connection
{
hrExit = pPxy -> Disconnect(hPxyCon);
_ASSERTE(hrExit == S_OK);
}

    pPxy -> Release();
}

    if (hdbc)
{
   rc1 = SQLDisconnect( hdbc );
   SQLFreeConnect( hdbc );
       SQLFreeEnv( henv );

}

    if( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO ) // ODBC error
 hr = S_FALSE;

if (FAILED(hr))
hr = S_FALSE;

return hr;
}

void GetError(HENV henv, HDBC hdbc, HSTMT hstmt, BSTR *msg)
{
WCHAR szSqlState[64];
SDWORD NativeError;
WCHAR szErr[512];
SWORD cbErrorMsg;
SQLError(henv, hdbc, hstmt, szSqlState, &NativeError, szErr, 511, &cbErrorMsg);
*msg = SysAllocString(szErr);
}

STDMETHODIMP CCpyImageObj::SetComplete()
{
HRESULT hr = S_OK;
IObjectContext * pInstanceContext;
HRESULT hRes = GetObjectContext (&pInstanceContext);
_ASSERTE(hr==S_OK);

if (pInstanceContext)
{
pInstanceContext -> SetComplete();
pInstanceContext -> Release();
}

return hr;
}
STDMETHODIMP CCpyImageObj::SetAbort()
{
HRESULT hr = S_OK;
IObjectContext * pInstanceContext;
HRESULT hRes = GetObjectContext (&pInstanceContext);
_ASSERTE(hr==S_OK);

if (pInstanceContext)
{
pInstanceContext -> SetAbort();
pInstanceContext -> Release();
}
return hr;
}

STDMETHODIMP CCpyImageObj::Test(BSTR * msg)
{
*msg = ::SysAllocString(L"This is only a test!");
return S_FALSE;

}
//
//Post a message to the MTS Spy message Q
//
void CCpyImageObj::MtsTrace(BSTR sMsg)
{

CComVariant var = sMsg;
m_pEvents -> PostEvent(&var);
}

void CCpyImageObj::MtsTraceObject(BSTR sDisplayName, BSTR sVal)
{

}

BOOL CCpyImageObj::CreateTable(HSTMT hstmt, BSTR * msg)
{

RETCODE rc;
LPTSTR sSQL = _T("CREATE TABLE dbo.Image (ctr int IDENTITY (1, 1) NOT NULL,Description varchar (255) NULL , MainColor varchar (255) NULL ,Theme varchar (255) NULL ,FileName varchar (120) NOT NULL)");
rc = SQLExecDirect( hstmt, sSQL, SQL_NTS );
if( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO )
    {
*msg = ::SysAllocString(L"Failed to create the Image table.");
return FALSE;
}
return TRUE;
}


CPYIMAGEOBJ.H

/******************************************************************************\
********************************************************************************
* Filename: CpyImageObj.h
*
* Description: Declaration of the CCpyImageObj
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#include "resource.h"       // main symbols

/////////////////////////////////////////////////////////////////////////////
// CCpyImageObj
class CCpyImageObj :
public CComObjectRoot,
public CComCoClass<CCpyImageObj, &CLSID_CpyImage>,
public ISupportErrorInfo,
public IDispatchImpl<ICpyImage, &IID_ICpyImage, &LIBID_CPYIMAGELib>
{

private:

IMtsEvents * m_pEvents; // for tracing

public:

BEGIN_COM_MAP(CCpyImageObj)
COM_INTERFACE_ENTRY(ICpyImage)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()

CCpyImageObj()
{

HRESULT hr;
m_pEvents = NULL;

IMTSLocater * pLocator = NULL;
hr = CoCreateInstance(CLSID_CoMTSLocater, NULL, CLSCTX_INPROC, IID_IMTSLocater, (void **)&pLocator);
if (hr == S_OK)
{
IUnknown * pUnk = NULL;

pLocator -> GetEventDispatcher(&pUnk);

if (pUnk)
{
pUnk -> QueryInterface(IID_IMtsEvents, (void **)&m_pEvents);
pUnk -> Release();
}
pLocator -> Release();
}
}
~CCpyImageObj()
{

if (m_pEvents) m_pEvents -> Release();
}

DECLARE_REGISTRY_RESOURCEID(IDR_CpyImage)

// ISupportsErrorInfo
STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);

// ICpyImage
public:
BOOL CreateTable(HSTMT hstmt, BSTR * msg);
void MtsTrace(BSTR sMsg);
void MtsTraceObject(BSTR sDisplayName, BSTR sVal);

STDMETHOD(InsertData)(BSTR sFromFile, BSTR sToFile,
   BSTR sDescription, BSTR sMainColor,
   BSTR sTheme, BSTR * msg);

STDMETHOD(SetComplete)();
STDMETHOD(SetAbort)();

STDMETHOD(Test)(BSTR * msg);
};


RESOURCE.H

/******************************************************************************\
********************************************************************************
* Filename: Resource.h
*
* Description:
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by CpyImage.rc
//
#define IDS_PROJNAME100
#define IDR_CpyImage101

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        201
#define _APS_NEXT_COMMAND_VALUE         32768
#define _APS_NEXT_CONTROL_VALUE         201
#define _APS_NEXT_SYMED_VALUE           102
#endif
#endif


STDAFX.CPP

/******************************************************************************\
********************************************************************************
* Filename: StdAfx.cpp
*
* Description:

stdafx.cpp : source file that includes just the standard includes
stdafx.pch will be the pre-compiled header
stdafx.obj will contain the pre-compiled type information

*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#include "stdafx.h"

#ifdef _ATL_STATIC_REGISTRY
#include <statreg.h>
#include <statreg.cpp>
#endif

#include <atlimpl.cpp>


STDAFX.H

/******************************************************************************\
********************************************************************************
* Filename: StdAfx.h
*
* Description:

include file for standard system include files,
or project specific include files that are used frequently,
but are changed infrequently

*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
#define STRICT 1
#define _WIN32_WINNT 0x0400
#define _ATL_APARTMENT_THREADED

#include <atlbase.h>
//You may derive a class from CComModule and use it if you want to override
//something, but do not change the name of _Module
extern CComModule _Module;
#include <atlcom.h>


CPICTURECTL1.CPP

/******************************************************************************\
********************************************************************************
* Filename: cpicturectl1.cpp
*
* Description:

Machine generated IDispatch wrapper class(es) created by Microsoft Visual C++
NOTE: Do not modify the contents of this file.  If this class is regenerated by
Microsoft Visual C++, your modifications will be overwritten.

*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

throw

#include "stdafx.h"
#include "cpicturectl1.h"

/////////////////////////////////////////////////////////////////////////////
// CNewPictureCtl

IMPLEMENT_DYNCREATE(CNewPictureCtl, CWnd)

/////////////////////////////////////////////////////////////////////////////
// CNewPictureCtl properties

/////////////////////////////////////////////////////////////////////////////
// CNewPictureCtl operations

unsigned long CNewPictureCtl::AddRef()
{
unsigned long result;
InvokeHelper(0x60000001, DISPATCH_METHOD, VT_I4, (void*)&result, NULL);
return result;
}

unsigned long CNewPictureCtl::Release()
{
unsigned long result;
InvokeHelper(0x60000002, DISPATCH_METHOD, VT_I4, (void*)&result, NULL);
return result;
}

CString CNewPictureCtl::GetPictureFileName()
{
CString result;
InvokeHelper(0x68030000, DISPATCH_PROPERTYGET, VT_BSTR, (void*)&result, NULL);
return result;
}

void CNewPictureCtl::SetPictureFileName(LPCTSTR lpszNewValue)
{
static BYTE parms[] =
VTS_BSTR;
InvokeHelper(0x68030000, DISPATCH_PROPERTYPUT, VT_EMPTY, NULL, parms,
 lpszNewValue);
}


CPICTURECTL1.H

/******************************************************************************\
********************************************************************************
* Filename: cpicturectl1.h
*
* Description:

 Machine generated IDispatch wrapper class(es) created by Microsoft Visual C++
 NOTE: Do not modify the contents of this file.  If this class is regenerated by
 Microsoft Visual C++, your modifications will be overwritten.

*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
#ifndef __CPICTURECTL1_H__
#define __CPICTURECTL1_H__

/////////////////////////////////////////////////////////////////////////////
// CNewPictureCtl wrapper class

class CNewPictureCtl : public CWnd
{
protected:
DECLARE_DYNCREATE(CNewPictureCtl)
public:
CLSID const& GetClsid()
{

//
//vb will change this CLSID every time it recompiles, so
//we need to get it from the registry
//
static CLSID clsid;
HRESULT hr;
hr = CLSIDFromProgID(L"Picture.PictureCtl", &clsid);
if (FAILED(hr))
{
AfxMessageBox(_T("PictureCtl.OCX is not properly registerd!"), MB_ICONSTOP);
}
return clsid;
}
virtual BOOL Create(LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
const RECT& rect,
CWnd* pParentWnd, UINT nID,
CCreateContext* pContext = NULL)
{ return CreateControl(GetClsid(), lpszWindowName, dwStyle, rect, pParentWnd, nID); }

    BOOL Create(LPCTSTR lpszWindowName, DWORD dwStyle,
const RECT& rect, CWnd* pParentWnd, UINT nID,
CFile* pPersist = NULL, BOOL bStorage = FALSE,
BSTR bstrLicKey = NULL)
{ return CreateControl(GetClsid(), lpszWindowName, dwStyle, rect, pParentWnd, nID,
pPersist, bStorage, bstrLicKey); }

// Attributes
public:

// Operations
public:
// method 'QueryInterface' not emitted because of invalid return type or parameter type
unsigned long AddRef();
unsigned long Release();
// method 'GetTypeInfoCount' not emitted because of invalid return type or parameter type
// method 'GetTypeInfo' not emitted because of invalid return type or parameter type
// method 'GetIDsOfNames' not emitted because of invalid return type or parameter type
// method 'Invoke' not emitted because of invalid return type or parameter type
CString GetPictureFileName();
void SetPictureFileName(LPCTSTR lpszNewValue);
};

#endif // __CPICTURECTL1_H__


FILERMCLIENT.CPP

/******************************************************************************\
********************************************************************************
* Filename: FileRmClient.cpp
*
* Description: Defines the class behaviors for the application.
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
#include "stdafx.h"
#include "FileRmClient.h"
#include "FileRmClientDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CFileRmClientApp

BEGIN_MESSAGE_MAP(CFileRmClientApp, CWinApp)
//{{AFX_MSG_MAP(CFileRmClientApp)
// NOTE - the ClassWizard will add and remove mapping macros here.
//    DO NOT EDIT what you see in these blocks of generated code!
//}}AFX_MSG
ON_COMMAND(ID_HELP, CWinApp::OnHelp)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CFileRmClientApp construction

CFileRmClientApp::CFileRmClientApp()
{
// TODO: add construction code here,
// Place all significant initialization in InitInstance
}

/////////////////////////////////////////////////////////////////////////////
// The one and only CFileRmClientApp object

CFileRmClientApp theApp;

/////////////////////////////////////////////////////////////////////////////
// CFileRmClientApp initialization

BOOL CFileRmClientApp::InitInstance()
{
// Initialize OLE libraries
if (!AfxOleInit())
{
AfxMessageBox(IDP_OLE_INIT_FAILED);
return FALSE;
}

AfxEnableControlContainer();

// Standard initialization
// If you are not using these features and wish to reduce the size
//  of your final executable, you should remove from the following
//  the specific initialization routines you do not need.

#ifdef _AFXDLL
Enable3dControls();// Call this when using MFC in a shared DLL
#else
Enable3dControlsStatic();// Call this when linking to MFC statically
#endif

// Parse the command line to see if launched as OLE server
if (RunEmbedded() || RunAutomated())
{
// Register all OLE server (factories) as running.  This enables the
//  OLE libraries to create objects from other applications.
COleTemplateServer::RegisterAll();

// Application was run with /Embedding or /Automation.  Don't show the
//  main window in this case.
return TRUE;
}

// When a server application is launched stand-alone, it is a good idea
//  to update the system registry in case it has been damaged.
COleObjectFactory::UpdateRegistryAll();

CFileRmClientDlg dlg;
m_pMainWnd = &dlg;
int nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: Place code here to handle when the dialog is
//  dismissed with OK
}
else if (nResponse == IDCANCEL)
{
// TODO: Place code here to handle when the dialog is
//  dismissed with Cancel
}

// Since the dialog has been closed, return FALSE so that we exit the
//  application, rather than start the application's message pump.
return FALSE;
}


FILERMCLIENT.H

/******************************************************************************\
********************************************************************************
* Filename: FileRmClient.h
*
* Description: main header file for the FILERMCLIENT application
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#ifndef __AFXWIN_H__
#error include 'stdafx.h' before including this file for PCH
#endif

#include "resource.h"// main symbols

/////////////////////////////////////////////////////////////////////////////
// CFileRmClientApp:
// See FileRmClient.cpp for the implementation of this class
//

class CFileRmClientApp : public CWinApp
{
public:
CFileRmClientApp();

// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CFileRmClientApp)
public:
virtual BOOL InitInstance();
//}}AFX_VIRTUAL

// Implementation

//{{AFX_MSG(CFileRmClientApp)
// NOTE - the ClassWizard will add and remove member functions here.
//    DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

/////////////////////////////////////////////////////////////////////////////


FILERMCLIENTDLG.CPP

/******************************************************************************\
********************************************************************************
* Filename: FileRmClientDlg.cpp
*
* Description:  Main UI for test app
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#include "stdafx.h"
#include "FileRmClient.h"
#include "FileRmClientDlg.h"
#define IID_DEFINED
#include "cpyimage.h"
#include "cpyimage_i.c"
#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#include "afxpriv.h"

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
CAboutDlg();

// Dialog Data
//{{AFX_DATA(CAboutDlg)
enum { IDD = IDD_ABOUTBOX };
//}}AFX_DATA

// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CAboutDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
//}}AFX_VIRTUAL

// Implementation
protected:
//{{AFX_MSG(CAboutDlg)
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
//{{AFX_DATA_INIT(CAboutDlg)
//}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CAboutDlg)
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
//{{AFX_MSG_MAP(CAboutDlg)
// No message handlers
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CFileRmClientDlg dialog

CFileRmClientDlg::CFileRmClientDlg(CWnd* pParent /*=NULL*/)
: CDialog(CFileRmClientDlg::IDD, pParent)
{
//{{AFX_DATA_INIT(CFileRmClientDlg)
m_sSource = _T("c:\\test");
m_sImgFileName = _T("");
m_sMainColor = _T("<main color>");
m_sTheme = _T("<theme>");
m_sDescription = _T("<description>");
m_sMsg = _T("");
m_sDest = _T("c:\\images");
//}}AFX_DATA_INIT
// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
;
}

void CFileRmClientDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CFileRmClientDlg)
DDX_Text(pDX, IDC_IMG_FILENAME, m_sImgFileName);
DDV_MaxChars(pDX, m_sImgFileName, 118);
DDX_Text(pDX, IDC_MAINCOLOR, m_sMainColor);
DDV_MaxChars(pDX, m_sMainColor, 255);
DDX_Text(pDX, IDC_THEME, m_sTheme);
DDV_MaxChars(pDX, m_sTheme, 255);
DDX_Text(pDX, IDC_DESCRIPTION, m_sDescription);
DDV_MaxChars(pDX, m_sDescription, 255);
DDX_Text(pDX, IDC_MSG, m_sMsg);
DDX_Text(pDX, IDC_DESTINATION, m_sDest);
//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CFileRmClientDlg, CDialog)
//{{AFX_MSG_MAP(CFileRmClientDlg)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_COPY, OnCopy)
ON_BN_CLICKED(IDC_COPY_DIRECTORY, OnCopyDirectory)
ON_BN_CLICKED(IDC_INSERT_DATA, OnInsertData)
ON_BN_CLICKED(IDC_SELECT_PICTURE, OnSelectPicture)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CFileRmClientDlg message handlers

BOOL CFileRmClientDlg::OnInitDialog()
{
CDialog::OnInitDialog();

// Add "About..." menu item to system menu.

// IDM_ABOUTBOX must be in the system command range.
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);

CMenu* pSysMenu = GetSystemMenu(FALSE);
CString strAboutMenu;
strAboutMenu.LoadString(IDS_ABOUTBOX);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}

// Set the icon for this dialog.  The framework does this automatically
//  when the application's main window is not a dialog
SetIcon(m_hIcon, TRUE);// Set big icon
SetIcon(m_hIcon, FALSE);// Set small icon

CRect rect;

CWnd * pWnd = GetDlgItem(IDC_PICT_SPOT);
pWnd -> GetWindowRect(&rect);
ScreenToClient(rect);
pWnd -> ShowWindow(SW_HIDE);
BOOL bRet = m_ctlPict.Create("", WS_VISIBLE|WS_CHILD, rect, this, IDC_PICTURECTL);
if (!bRet)
AfxMessageBox("Failed to create PictureCtl!", MB_ICONSTOP);
return TRUE;  // return TRUE  unless you set the focus to a control
}

void CFileRmClientDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialog::OnSysCommand(nID, lParam);
}
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CFileRmClientDlg::OnPaint()
{
if (IsIconic())
{
CPaintDC dc(this); // device context for painting

SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

// Center icon in client rectangle
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;

// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialog::OnPaint();
}
}

// The system calls this to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CFileRmClientDlg::OnQueryDragIcon()
{
return (HCURSOR) m_hIcon;
}

void CFileRmClientDlg::OnCopy()
{
#if 0
CWaitCursor wait;
ITransactionDispenser*pTransactionDispenser;
ITransaction*pTransaction;
HRESULThr = S_OK ;
BOOLtf = 0 ;

// Obtain the ITransactionDispenser Interface pointer
// by calling DtcGetTransactionManager()
hr = DtcGetTransactionManager(
NULL,// LPTSTR pszHost,
NULL,// LPTSTR pszTmName,
IID_ITransactionDispenser,// /* in  */ REFIID rid,
0,// /* in  */ DWORDdwReserved1,
0, // /* in  */ WORDwcbReserved2,
NULL,// /* in  */ void FAR * pvReserved2,
(void **)&pTransactionDispenser // /* out */ void** ppvObject
);

if (FAILED (hr))
{
CString s;
s.Format("DtcGetTransactionManager failed: %x\n", hr);
AfxMessageBox(s);
return;
}

hr = pTransactionDispenser->BeginTransaction(
NULL,///* [in]  */ IUnknown __RPC_FAR *punkOuter,
ISOLATIONLEVEL_ISOLATED,///* [in]  */ ISOLEVEL isoLevel,
ISOFLAG_RETAIN_DONTCARE,// /* [in]  */ ULONG isoFlags,
NULL,///* [in]  */ ITransactionOptions *pOptions
&pTransaction///* [out] */ ITransaction **ppTransaction
) ;

if (FAILED (hr))
{
pTransactionDispenser-> Release();

CString s;
s.Format("BeginTransaction failed: %x\n",hr);
AfxMessageBox(s);
return;
}

// create our filerm proxy

IFileRmPxy * pPxy = NULL;

hr = CoCreateInstance(CLSID_CoFileRmPxy, NULL, CLSCTX_ALL, IID_IFileRmPxy, (void **)&pPxy);
ASSERT(hr==S_OK);

BSTR sFileName = ::SysAllocString(L"FileRmClient");
ULONG ul = 0;

hr = pPxy -> Init(sFileName, &ul);
::SysFreeString(sFileName);
if (FAILED(hr))
{
pTransactionDispenser-> Release();
AfxMessageBox("Failed to initialize the Resource manager");
return;
}

//IUnknown * pUnk = NULL;
//hr = pTransaction -> QueryInterface(IID_IUnknown, (void **)&pUnk);
//ASSERT(hr == S_OK);
hr = pPxy -> EnlistTx((IUnknown *)pTransaction);
//pUnk -> Release();
ASSERT(hr == S_OK);
if (SUCCEEDED(hr))
{
// copy the file
UpdateData(TRUE);

BSTR sSource = m_sSource.AllocSysString();
BSTR sDest = m_sDest.AllocSysString();

hr = pPxy -> FileCopyTx(sSource, sDest);

::SysFreeString(sSource);
::SysFreeString(sDest);

if (FAILED(hr))
{
pTransaction -> Abort(NULL, FALSE, FALSE);
}
else
{
pTransaction -> Commit(0,0,0);
}

}

// Commit the transaction
pPxy -> Release();
pTransactionDispenser-> Release();
#endif
}

void CFileRmClientDlg::OnCopyDirectory()
{
#if 0
CWaitCursor wait;
IFileRmPxy * pPxy = NULL;
HRESULT hr;
hr = CoCreateInstance(CLSID_CoFileRmPxy, NULL, CLSCTX_ALL, IID_IFileRmPxy, (void **)&pPxy);
ASSERT(hr==S_OK);
if (SUCCEEDED(hr))
{
// copy the file
UpdateData(TRUE);

//BSTR sSource = m_sSource.AllocSysString();
//BSTR sDest = m_sDest.AllocSysString();
pPxy -> ConfirmCommit(TRUE, (long)GetSafeHwnd());
hr = pPxy -> CopyDirTx(sSource, sDest);

::SysFreeString(sSource);
::SysFreeString(sDest);
pPxy -> Release();
}
#endif
}

void CFileRmClientDlg::OnInsertData()
{
CWaitCursor wait;
UpdateData(TRUE);
m_sMsg.Empty();
UpdateData(FALSE);
USES_CONVERSION;
ICpyImage * pCpyImage;
CString sDestination = m_sDest;
//
//make sure the last char in the destination is a "\"
//

if (sDestination.Right(1) != "\\")
sDestination += "\\";

HRESULT hr;
hr = CoCreateInstance(CLSID_CpyImage, NULL, CLSCTX_ALL, IID_ICpyImage, (void **)&pCpyImage);
if (FAILED(hr))
{
AfxMessageBox("Failed to Create the CopyImage component (cpyimage.dll).\nMake sure that it is built and properly registered.", MB_ICONSTOP);
return;
}

CString sSourceFile;
sSourceFile = m_ctlPict.GetPictureFileName();
LPSTR p;
LPSTR q;
p = sSourceFile.GetBuffer(118);
q = p+lstrlen(p) - 1;
while (q != p) // scan backwards
{
q--;
if (*q == '\\')
{
q++;
break;
}
}

// q points to the file name

sDestination += q;
sSourceFile.ReleaseBuffer();

BSTR sSource = sSourceFile.AllocSysString();
BSTR sDest = sDestination.AllocSysString();
BSTR sDescr = m_sDescription.AllocSysString();
BSTR sMain = m_sMainColor.AllocSysString();
BSTR sThem = m_sTheme.AllocSysString();
BSTR msg;

// have the middle tier object do some work for us
hr = pCpyImage -> InsertData(sSource,sDest ,sDescr ,sMain , sThem, &msg);

::SysFreeString(sSource);
::SysFreeString(sDest);
::SysFreeString(sDescr);
::SysFreeString(sMain);
::SysFreeString(sThem);

if (hr == S_OK)
{
SetDlgItemTextW(GetSafeHwnd(), IDC_MSG, msg);

if (::MessageBox(GetSafeHwnd(), "Do you wish to commit the tx?", "FileRmClient", MB_YESNO|MB_ICONQUESTION) == IDYES)
pCpyImage -> SetComplete();
else
pCpyImage -> SetAbort();
}
else
{
USES_CONVERSION;
m_sMsg.Format("Tx Aborted!\r\nCause: %s", OLE2T(msg));
UpdateData(FALSE);
int nLen = SysStringLen(msg);
if (nLen != 0)
{
::SysFreeString(msg);
}

pCpyImage -> SetAbort();
}

pCpyImage -> Release();

}

BEGIN_EVENTSINK_MAP(CFileRmClientDlg, CDialog)
    //{{AFX_EVENTSINK_MAP(CFileRmClientDlg)
ON_EVENT(CFileRmClientDlg, IDC_PICTURECTL, 1 /* PictureChanged */, OnPictureChanged, VTS_PBSTR)
//}}AFX_EVENTSINK_MAP
END_EVENTSINK_MAP()

void CFileRmClientDlg::OnPictureChanged(BSTR FAR* PictureFileName)
{
m_sImgFileName = *PictureFileName;
UpdateData(FALSE);

}

void CFileRmClientDlg::OnSelectPicture()
{
static char BASED_CODE szFilter[] = "All image files (*.bmp, *.wmf; *.gif)|*.bmp;*.gif;*.wmf|Bitmap Files (*.bmp)|*.bmp|GIF Files (*.gif)|*.giv|Windows Meta files (*.wmf)|*.wmf|All Files (*.*)|*.*||";

CFileDialog dlg(TRUE, NULL, NULL, OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT, szFilter, this);
if (dlg.DoModal() == IDCANCEL)
return;

m_sImgFileName = dlg.GetPathName( );
m_ctlPict.SetPictureFileName(m_sImgFileName);
UpdateData(FALSE);

}

#if 0
void ShowError(HENV henv, HDBC hdbc, HSTMT hstmt)
{
UCHAR szSqlState[64];
SDWORD NativeError;
UCHAR szErr[512];
SWORD cbErrorMsg;
SQLError(henv, hdbc, hstmt, szSqlState, &NativeError, szErr, 511, &cbErrorMsg);
AfxMessageBox((LPCSTR)szErr);
}

{
RETCODE                  rc = 0;
    RETCODE                  rc1 = 0;
    HENV                     henv = NULL;
    HDBC                     hdbc = NULL;
    HSTMT                    hstmt = NULL;
long hPxyCon = 0;
    CString sSQL;
CString sDestination = "c:\\images\\";
CString sSourceFile;
LPSTR p, q;

BSTR sSource;
BSTR sDest;

CWaitCursor wait;

    HRESULT                  hr;
    ITransactionDispenser   *pITransDisp = NULL;
    ITransaction            *pITransaction = NULL;
USES_CONVERSION;
UpdateData(TRUE);

sSourceFile = m_ctlPict.GetPictureFileName();
p = sSourceFile.GetBuffer(118);
q = p+lstrlen(p) - 1;
while (q != p) // scan backwards
{
q--;
if (*q == '\\')
{
q++;
break;
}
}

// q points to the file name

sDestination += q;
sSourceFile.ReleaseBuffer();

// create the Proxy
   IFileRmPxy * pPxy = NULL;

hr = CoCreateInstance(CLSID_FileRmPxy, NULL, CLSCTX_ALL, IID_IFileRmPxy, (void **)&pPxy);
ASSERT(hr==S_OK);

    //---------------------------------------------------------------
    //Obtain the transaction dispenser from DTC Proxy
    hr = DtcGetTransactionManager( 0x0,
                                   0x0,
                                   IID_ITransactionDispenser,
                                   0x0,
                                   0x0,
                                   0x0,
                                   (void **) &pITransDisp );

    if (hr != S_OK)
    {

        AfxMessageBox("Couldn't get the Tx Dispenser!  Make sure DTC is running!");
goto Exit;
    }

    //---------------------------------------------------------------
    //Obtain a new transaction from the dispenser
    hr = pITransDisp->BeginTransaction( NULL,
                                        ISOLATIONLEVEL_ISOLATED,
                                        0x0,
                                        NULL,
                                        &pITransaction );

pITransDisp -> Release();
pITransDisp = NULL;
    if (hr != S_OK)
    {

         goto Exit;
    }

    /* Allocate the Environment handle */
    rc = SQLAllocEnv( &henv );
    if( rc )
    {

        goto Exit;
    }

    /* Allocate the Connection handle */
    rc = SQLAllocConnect( henv, &hdbc );
    if( rc )
    {

        goto Exit;
    }

SQLSetConnectOption(hdbc, SQL_LOGIN_TIMEOUT, 5);

    /* Establish the database connection */
    rc = SQLConnect( hdbc, (UCHAR *)"FileRmTest", SQL_NTS,
(UCHAR *)"sa", SQL_NTS,
(UCHAR *)"", SQL_NTS );

    if( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO )
    {
        //printError( henv, hdbc, hstmt, SqlState );
        goto Exit;
    }
#if 0
rc = SQLSetConnectOption( hdbc, SQL_TXN_ISOLATION_OPTION, (DWORD )SQL_TXN_READ_UNCOMMITTED);
if( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO )
    {

UCHAR szSqlState[64];
SDWORD NativeError;
UCHAR szErr[512];
SWORD cbErrorMsg;
SQLError(henv, hdbc, hstmt, szSqlState, &NativeError, szErr, 511, &cbErrorMsg);

/* note: use SQLError from here on */
        //printError( henv, hdbc, hstmt, SqlState );
        goto Exit;
    }
#endif
rc = SQLSetConnectOption( hdbc, SQL_COPT_SS_ENLIST_IN_DTC, (DWORD )pITransaction );
if( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO )
    {

ShowError(henv, hdbc, hstmt);
goto Exit;
    }

rc = SQLSetConnectOption( hdbc, SQL_AUTOCOMMIT, SQL_AUTOCOMMIT_OFF );
    if( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO )
    {
        ShowError(henv, hdbc, hstmt);
goto Exit;
    }

hr = pPxy -> Connect(&hPxyCon);
_ASSERTE(hr == S_OK);
hr = pPxy -> Enlist(hPxyCon, (IUnknown *)pITransaction);
_ASSERTE(hr == S_OK);

    /* Allocate the statement handle */
    rc = SQLAllocStmt( hdbc, &hstmt );
    if( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO )
    {
        ShowError(henv, hdbc, hstmt);
        goto Exit;
    }

     /* Execute to Insert */

sSQL.Format("insert into image values ('%s', '%s', '%s', '%s')",
m_sDescription, m_sMainColor, m_sTheme, sDestination);

// insert the row into the database
    rc = SQLExecDirect( hstmt, ( UCHAR * )(LPCSTR)sSQL, SQL_NTS );

    if( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO )
    {
ShowError(henv, hdbc, hstmt);
pITransaction -> Abort(NULL, FALSE, FALSE);
        goto Exit;
    }

sSource = ::SysAllocString(T2OLE(sSourceFile));
sDest = ::SysAllocString(T2OLE(sDestination));

// copy the image to the images directory

hr = pPxy -> CopyFileTx(hPxyCon, sSource, sDest, TRUE);
::SysFreeString(sSource);
::SysFreeString(sDest);
if (FAILED(hr))
{
pITransaction -> Abort(NULL, FALSE, FALSE);
goto Exit;
}

/* Commit the DTC transaction */
if (::MessageBox(GetSafeHwnd(), "Commit Transaction?", "File Resource Manager", MB_YESNO) == IDYES)
        hr = pITransaction->Commit( FALSE, XACTTC_SYNC_PHASEONE, 0x0 );
else
hr = pITransaction->Abort( 0,0,0);

    if (hr != S_OK)
    {

        goto Exit;
    }

    /* Free the statement handle */
    rc = SQLFreeStmt( hstmt, SQL_DROP );
    if( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO )
    {
        goto Exit;
    }

    Exit:
if (pITransaction)
   pITransaction -> Release();

if (pPxy)
{
hr = pPxy -> Disconnect(hPxyCon);
    pPxy -> Release();
}

    if (hdbc)
{
rc1 = SQLDisconnect( hdbc );
   SQLFreeConnect( hdbc );
       SQLFreeEnv( henv );
}

if( rc1 != SQL_SUCCESS && rc1 != SQL_SUCCESS_WITH_INFO )
       {
// print message
   }

       if( rc == 0 )
       {
   // success
       }
       else
       {
   // failed
   }

}
#endif


FILERMCLIENTDLG.H

/******************************************************************************\
********************************************************************************
* Filename: FileRmClientDlg.h
*
* Description: header file
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

/////////////////////////////////////////////////////////////////////////////
// CFileRmClientDlg dialog
//{{AFX_INCLUDES()
#include "cpicturectl1.h"
//}}AFX_INCLUDES

class CFileRmClientDlg : public CDialog
{
// Construction
public:
CFileRmClientDlg(CWnd* pParent = NULL);// standard constructor
// Dialog Data
//{{AFX_DATA(CFileRmClientDlg)
enum { IDD = IDD_FILERMCLIENT_DIALOG };
CStringm_sSource;
CStringm_sImgFileName;
CStringm_sMainColor;
CStringm_sTheme;
CStringm_sDescription;
CStringm_sMsg;
CStringm_sDest;
//}}AFX_DATA

// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CFileRmClientDlg)
protected:
virtual void DoDataExchange(CDataExchange* pDX);// DDX/DDV support
//}}AFX_VIRTUAL

// Implementation
protected:
HICON m_hIcon;
CNewPictureCtl m_ctlPict;
// Generated message map functions
//{{AFX_MSG(CFileRmClientDlg)
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
afx_msg void OnCopy();
afx_msg void OnCopyDirectory();
afx_msg void OnInsertData();
afx_msg void OnPictureChanged(BSTR FAR* PictureFileName);
afx_msg void OnSelectPicture();
DECLARE_EVENTSINK_MAP()
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};


PICTURE1.CPP

/******************************************************************************\
********************************************************************************
* Filename: picture1.cpp
*
* Description:

// Machine generated IDispatch wrapper class(es) created by Microsoft Visual C++
// NOTE: Do not modify the contents of this file.  If this class is regenerated by
// Microsoft Visual C++, your modifications will be overwritten.

*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#include "stdafx.h"
#include "picture1.h"

/////////////////////////////////////////////////////////////////////////////
// CPicture properties

long CPicture::GetHandle()
{
long result;
GetProperty(0x0, VT_I4, (void*)&result);
return result;
}

long CPicture::GetHPal()
{
long result;
GetProperty(0x2, VT_I4, (void*)&result);
return result;
}

void CPicture::SetHPal(long propVal)
{
SetProperty(0x2, VT_I4, propVal);
}

short CPicture::GetType()
{
short result;
GetProperty(0x3, VT_I2, (void*)&result);
return result;
}

long CPicture::GetWidth()
{
long result;
GetProperty(0x4, VT_I4, (void*)&result);
return result;
}

long CPicture::GetHeight()
{
long result;
GetProperty(0x5, VT_I4, (void*)&result);
return result;
}

/////////////////////////////////////////////////////////////////////////////
// CPicture operations


PICTURE1.H

/******************************************************************************\
********************************************************************************
* Filename: picture1.h
*
* Description:

Machine generated IDispatch wrapper class(es) created by Microsoft Visual C++
NOTE: Do not modify the contents of this file.  If this class is regenerated by
Microsoft Visual C++, your modifications will be overwritten.

*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
#ifndef __PICTURE1_H__
#define __PICTURE1_H__

/////////////////////////////////////////////////////////////////////////////
// CPicture wrapper class

class CPicture : public COleDispatchDriver
{
public:
CPicture() {}// Calls COleDispatchDriver default constructor
CPicture(LPDISPATCH pDispatch) : COleDispatchDriver(pDispatch) {}
CPicture(const CPicture& dispatchSrc) : COleDispatchDriver(dispatchSrc) {}

// Attributes
public:
long GetHandle();
long GetHPal();
void SetHPal(long);
short GetType();
long GetWidth();
long GetHeight();

// Operations
public:
// method 'Render' not emitted because of invalid return type or parameter type
};

#endif // __PICTURE1_H__


RESOURCE.H

/******************************************************************************\
********************************************************************************
* Filename: resource.h
*
* Description:
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by FileRmClient.rc
//
#define IDM_ABOUTBOX                    0x0010
#define IDD_ABOUTBOX                    100
#define IDP_OLE_INIT_FAILED             100
#define IDS_ABOUTBOX                    101
#define IDD_FILERMCLIENT_DIALOG         102
#define IDR_MAINFRAME                   128
#define IDC_PICTURECTL                  207
#define IDC_SOURCE                      1000
#define IDC_DESTINATION                 1001
#define IDC_COPY                        1002
#define IDC_COPY_DIRECTORY              1003
#define IDC_CHECK1                      1004
#define IDC_IMG_FILENAME                1005
#define IDC_MAINCOLOR                   1006
#define IDC_DESCRIPTION                 1007
#define IDC_THEME                       1008
#define IDC_SELECT_PICTURE              1010
#define IDC_INSERT_DATA                 1011
#define IDC_PICT_SPOT                   1019
#define IDC_MSG                         1025

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        135
#define _APS_NEXT_COMMAND_VALUE         32771
#define _APS_NEXT_CONTROL_VALUE         1026
#define _APS_NEXT_SYMED_VALUE           102
#endif
#endif


STDAFX.CPP

/******************************************************************************\
********************************************************************************
* Filename: StdAfx.cpp
*
* Description:

stdafx.cpp : source file that includes just the standard includes
FileRmClient.pch will be the pre-compiled header
stdafx.obj will contain the pre-compiled type information

*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#include "stdafx.h"


STDAFX.H

/******************************************************************************\
********************************************************************************
* Filename: StdAfx.h
*
* Description:
stdafx.h : include file for standard system include files,
or project specific include files that are used frequently, but
are changed infrequently

*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
#define VC_EXTRALEAN// Exclude rarely-used stuff from Windows headers

#include <afxwin.h>         // MFC core and standard components
#include <afxext.h>         // MFC extensions
#include <afxdisp.h>        // MFC OLE automation classes
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h>// MFC support for Windows Common Controls
#endif // _AFX_NO_AFXCMN_SUPPORT


CPYIMAGE.IDL

/******************************************************************************\
********************************************************************************
* Filename: CpyImage.idl
*
* Description: IDL source for CpyImage.dll

 This file will be processed by the MIDL tool to
 produce the type library (CpyImage.tlb) and marshalling code.

*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

import "oaidl.idl";

[

object,

uuid(F2C67D15-5447-11D0-B152-00AA00BA3258),

dual,

helpstring("ICpyImage Interface"),
pointer_default(unique)
]
interface ICpyImage : IDispatch
{

[helpstring("Copies a file and inserts the tag data in SQL Server")]
HRESULT InsertData([in] BSTR sFromFile, [in] BSTR sToFile,
   [in] BSTR sDescription, [in] BSTR sMainColor,
   [in] BSTR sTheme, [out] BSTR * msg);

[helpstring("Commits the Tx")]
HRESULT SetComplete();

[helpstring("Aborts the Tx")]
HRESULT SetAbort();

HRESULT Test([out] BSTR * msg);

};
[
uuid(F2C67D13-5447-11D0-B152-00AA00BA3258),
version(1.0),
helpstring("CpyImage 1.0 Type Library")
]
library CPYIMAGELib
{
importlib("stdole2.tlb");

[
uuid(F2C67D17-5447-11D0-B152-00AA00BA3258),
helpstring("CpyImage Class")
]
coclass CpyImage
{
[default] interface ICpyImage;
};
};


EVENTCPTS.H

/* this ALWAYS GENERATED file contains the definitions for the interfaces */

/* File created by MIDL compiler version 3.01.75 */
/* at Fri Nov 14 05:07:29 1997
 */
/* Compiler settings for eventcpts.idl:
    Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext
    error checks: none
*/
//@@MIDL_FILE_HEADING(  )
#include "rpc.h"
#include "rpcndr.h"
#ifndef COM_NO_WINDOWS_H
#include "windows.h"
#include "ole2.h"
#endif /*COM_NO_WINDOWS_H*/

#ifndef __eventcpts_h__
#define __eventcpts_h__

#ifdef __cplusplus
extern "C"{
#endif

/* Forward Declarations */

#ifndef __IMtsUserEvent_FWD_DEFINED__
#define __IMtsUserEvent_FWD_DEFINED__
typedef interface IMtsUserEvent IMtsUserEvent;
#endif /* __IMtsUserEvent_FWD_DEFINED__ */

#ifndef __IMtsThreadEvents_FWD_DEFINED__
#define __IMtsThreadEvents_FWD_DEFINED__
typedef interface IMtsThreadEvents IMtsThreadEvents;
#endif /* __IMtsThreadEvents_FWD_DEFINED__ */

#ifndef __IMtsPackageEvents_FWD_DEFINED__
#define __IMtsPackageEvents_FWD_DEFINED__
typedef interface IMtsPackageEvents IMtsPackageEvents;
#endif /* __IMtsPackageEvents_FWD_DEFINED__ */

#ifndef __IMtsInstanceEvents_FWD_DEFINED__
#define __IMtsInstanceEvents_FWD_DEFINED__
typedef interface IMtsInstanceEvents IMtsInstanceEvents;
#endif /* __IMtsInstanceEvents_FWD_DEFINED__ */

#ifndef __IMtsTransactionEvents_FWD_DEFINED__
#define __IMtsTransactionEvents_FWD_DEFINED__
typedef interface IMtsTransactionEvents IMtsTransactionEvents;
#endif /* __IMtsTransactionEvents_FWD_DEFINED__ */

#ifndef __IMtsMethodEvents_FWD_DEFINED__
#define __IMtsMethodEvents_FWD_DEFINED__
typedef interface IMtsMethodEvents IMtsMethodEvents;
#endif /* __IMtsMethodEvents_FWD_DEFINED__ */

#ifndef __IMtsObjectEvents_FWD_DEFINED__
#define __IMtsObjectEvents_FWD_DEFINED__
typedef interface IMtsObjectEvents IMtsObjectEvents;
#endif /* __IMtsObjectEvents_FWD_DEFINED__ */

#ifndef __IMtsResourceEvents_FWD_DEFINED__
#define __IMtsResourceEvents_FWD_DEFINED__
typedef interface IMtsResourceEvents IMtsResourceEvents;
#endif /* __IMtsResourceEvents_FWD_DEFINED__ */

#ifndef __IMtsSecurityEvents_FWD_DEFINED__
#define __IMtsSecurityEvents_FWD_DEFINED__
typedef interface IMtsSecurityEvents IMtsSecurityEvents;
#endif /* __IMtsSecurityEvents_FWD_DEFINED__ */

/* header files for imported files */
#include "oaidl.h"

void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t);
void __RPC_USER MIDL_user_free( void __RPC_FAR * );

/****************************************
 * Generated header for interface: __MIDL_itf_eventcpts_0000
 * at Fri Nov 14 05:07:29 1997
 * using MIDL 3.01.75
 ****************************************/
/* [local] */

typedef ULONG MTS_OBJID;

typedef ULONG MTS_RESID;

extern RPC_IF_HANDLE __MIDL_itf_eventcpts_0000_v0_0_c_ifspec;
extern RPC_IF_HANDLE __MIDL_itf_eventcpts_0000_v0_0_s_ifspec;

#ifndef __IMtsUserEvent_INTERFACE_DEFINED__
#define __IMtsUserEvent_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IMtsUserEvent
 * at Fri Nov 14 05:07:29 1997
 * using MIDL 3.01.75
 ****************************************/
/* [uuid][object] */

EXTERN_C const IID IID_IMtsUserEvent;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("D56C3DC0-8482-11d0-B170-00AA00BA3258")
    IMtsUserEvent : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE OnUserEvent(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ VARIANT __RPC_FAR *pvarEvent) = 0;

    };

#else /* C style interface */

    typedef struct IMtsUserEventVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            IMtsUserEvent __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            IMtsUserEvent __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            IMtsUserEvent __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnUserEvent )(
            IMtsUserEvent __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ VARIANT __RPC_FAR *pvarEvent);

        END_INTERFACE
    } IMtsUserEventVtbl;

    interface IMtsUserEvent
    {
        CONST_VTBL struct IMtsUserEventVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IMtsUserEvent_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IMtsUserEvent_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IMtsUserEvent_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IMtsUserEvent_OnUserEvent(This,perfCount,pvarEvent)\
    (This)->lpVtbl -> OnUserEvent(This,perfCount,pvarEvent)

#endif /* COBJMACROS */

#endif /* C style interface */

HRESULT STDMETHODCALLTYPE IMtsUserEvent_OnUserEvent_Proxy(
    IMtsUserEvent __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ VARIANT __RPC_FAR *pvarEvent);

void __RPC_STUB IMtsUserEvent_OnUserEvent_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IMtsUserEvent_INTERFACE_DEFINED__ */

#ifndef __IMtsThreadEvents_INTERFACE_DEFINED__
#define __IMtsThreadEvents_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IMtsThreadEvents
 * at Fri Nov 14 05:07:29 1997
 * using MIDL 3.01.75
 ****************************************/
/* [uuid][object] */

EXTERN_C const IID IID_IMtsThreadEvents;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("BACEDF4F-74AB-11D0-B162-00AA00BA3258")
    IMtsThreadEvents : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE OnThreadStart(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ ULONG ThreadID) = 0;

        virtual HRESULT STDMETHODCALLTYPE OnThreadTerminate(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ ULONG ThreadID) = 0;

        virtual HRESULT STDMETHODCALLTYPE OnThreadAssignToActivity(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ REFGUID guidActivity,
            /* [in] */ ULONG ThreadID) = 0;

        virtual HRESULT STDMETHODCALLTYPE OnThreadUnassignFromActivity(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ REFGUID guidActivity,
            /* [in] */ ULONG ThreadID) = 0;

    };

#else /* C style interface */

    typedef struct IMtsThreadEventsVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            IMtsThreadEvents __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            IMtsThreadEvents __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            IMtsThreadEvents __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnThreadStart )(
            IMtsThreadEvents __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ ULONG ThreadID);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnThreadTerminate )(
            IMtsThreadEvents __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ ULONG ThreadID);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnThreadAssignToActivity )(
            IMtsThreadEvents __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ REFGUID guidActivity,
            /* [in] */ ULONG ThreadID);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnThreadUnassignFromActivity )(
            IMtsThreadEvents __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ REFGUID guidActivity,
            /* [in] */ ULONG ThreadID);

        END_INTERFACE
    } IMtsThreadEventsVtbl;

    interface IMtsThreadEvents
    {
        CONST_VTBL struct IMtsThreadEventsVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IMtsThreadEvents_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IMtsThreadEvents_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IMtsThreadEvents_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IMtsThreadEvents_OnThreadStart(This,perfCount,ThreadID)\
    (This)->lpVtbl -> OnThreadStart(This,perfCount,ThreadID)

#define IMtsThreadEvents_OnThreadTerminate(This,perfCount,ThreadID)\
    (This)->lpVtbl -> OnThreadTerminate(This,perfCount,ThreadID)

#define IMtsThreadEvents_OnThreadAssignToActivity(This,perfCount,guidActivity,ThreadID)\
    (This)->lpVtbl -> OnThreadAssignToActivity(This,perfCount,guidActivity,ThreadID)

#define IMtsThreadEvents_OnThreadUnassignFromActivity(This,perfCount,guidActivity,ThreadID)\
    (This)->lpVtbl -> OnThreadUnassignFromActivity(This,perfCount,guidActivity,ThreadID)

#endif /* COBJMACROS */

#endif /* C style interface */

HRESULT STDMETHODCALLTYPE IMtsThreadEvents_OnThreadStart_Proxy(
    IMtsThreadEvents __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ ULONG ThreadID);

void __RPC_STUB IMtsThreadEvents_OnThreadStart_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IMtsThreadEvents_OnThreadTerminate_Proxy(
    IMtsThreadEvents __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ ULONG ThreadID);

void __RPC_STUB IMtsThreadEvents_OnThreadTerminate_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IMtsThreadEvents_OnThreadAssignToActivity_Proxy(
    IMtsThreadEvents __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ REFGUID guidActivity,
    /* [in] */ ULONG ThreadID);

void __RPC_STUB IMtsThreadEvents_OnThreadAssignToActivity_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IMtsThreadEvents_OnThreadUnassignFromActivity_Proxy(
    IMtsThreadEvents __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ REFGUID guidActivity,
    /* [in] */ ULONG ThreadID);

void __RPC_STUB IMtsThreadEvents_OnThreadUnassignFromActivity_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IMtsThreadEvents_INTERFACE_DEFINED__ */

#ifndef __IMtsPackageEvents_INTERFACE_DEFINED__
#define __IMtsPackageEvents_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IMtsPackageEvents
 * at Fri Nov 14 05:07:29 1997
 * using MIDL 3.01.75
 ****************************************/
/* [uuid][object] */

EXTERN_C const IID IID_IMtsPackageEvents;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("BACEDF50-74AB-11D0-B162-00AA00BA3258")
    IMtsPackageEvents : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE OnPackageActivation(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ REFGUID guidPackage,
            /* [in] */ LPCOLESTR sPackageName) = 0;

        virtual HRESULT STDMETHODCALLTYPE OnPackageShutdown(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ REFGUID guidPackage,
            /* [in] */ LPCOLESTR sPackageName) = 0;

    };

#else /* C style interface */

    typedef struct IMtsPackageEventsVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            IMtsPackageEvents __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            IMtsPackageEvents __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            IMtsPackageEvents __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnPackageActivation )(
            IMtsPackageEvents __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ REFGUID guidPackage,
            /* [in] */ LPCOLESTR sPackageName);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnPackageShutdown )(
            IMtsPackageEvents __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ REFGUID guidPackage,
            /* [in] */ LPCOLESTR sPackageName);

        END_INTERFACE
    } IMtsPackageEventsVtbl;

    interface IMtsPackageEvents
    {
        CONST_VTBL struct IMtsPackageEventsVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IMtsPackageEvents_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IMtsPackageEvents_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IMtsPackageEvents_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IMtsPackageEvents_OnPackageActivation(This,perfCount,guidPackage,sPackageName)\
    (This)->lpVtbl -> OnPackageActivation(This,perfCount,guidPackage,sPackageName)

#define IMtsPackageEvents_OnPackageShutdown(This,perfCount,guidPackage,sPackageName)\
    (This)->lpVtbl -> OnPackageShutdown(This,perfCount,guidPackage,sPackageName)

#endif /* COBJMACROS */

#endif /* C style interface */

HRESULT STDMETHODCALLTYPE IMtsPackageEvents_OnPackageActivation_Proxy(
    IMtsPackageEvents __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ REFGUID guidPackage,
    /* [in] */ LPCOLESTR sPackageName);

void __RPC_STUB IMtsPackageEvents_OnPackageActivation_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IMtsPackageEvents_OnPackageShutdown_Proxy(
    IMtsPackageEvents __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ REFGUID guidPackage,
    /* [in] */ LPCOLESTR sPackageName);

void __RPC_STUB IMtsPackageEvents_OnPackageShutdown_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IMtsPackageEvents_INTERFACE_DEFINED__ */

#ifndef __IMtsInstanceEvents_INTERFACE_DEFINED__
#define __IMtsInstanceEvents_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IMtsInstanceEvents
 * at Fri Nov 14 05:07:29 1997
 * using MIDL 3.01.75
 ****************************************/
/* [uuid][object] */

EXTERN_C const IID IID_IMtsInstanceEvents;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("BACEDF51-74AB-11D0-B162-00AA00BA3258")
    IMtsInstanceEvents : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE OnObjectCreate(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ REFGUID guidActivity,
            /* [in] */ REFCLSID clsid,
            /* [in] */ REFGUID tsid,
            /* [in] */ MTS_OBJID ObjectID) = 0;

        virtual HRESULT STDMETHODCALLTYPE OnObjectRelease(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ MTS_OBJID ObjectID) = 0;

    };

#else /* C style interface */

    typedef struct IMtsInstanceEventsVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            IMtsInstanceEvents __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            IMtsInstanceEvents __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            IMtsInstanceEvents __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnObjectCreate )(
            IMtsInstanceEvents __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ REFGUID guidActivity,
            /* [in] */ REFCLSID clsid,
            /* [in] */ REFGUID tsid,
            /* [in] */ MTS_OBJID ObjectID);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnObjectRelease )(
            IMtsInstanceEvents __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ MTS_OBJID ObjectID);

        END_INTERFACE
    } IMtsInstanceEventsVtbl;

    interface IMtsInstanceEvents
    {
        CONST_VTBL struct IMtsInstanceEventsVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IMtsInstanceEvents_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IMtsInstanceEvents_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IMtsInstanceEvents_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IMtsInstanceEvents_OnObjectCreate(This,perfCount,guidActivity,clsid,tsid,ObjectID)\
    (This)->lpVtbl -> OnObjectCreate(This,perfCount,guidActivity,clsid,tsid,ObjectID)

#define IMtsInstanceEvents_OnObjectRelease(This,perfCount,ObjectID)\
    (This)->lpVtbl -> OnObjectRelease(This,perfCount,ObjectID)

#endif /* COBJMACROS */

#endif /* C style interface */

HRESULT STDMETHODCALLTYPE IMtsInstanceEvents_OnObjectCreate_Proxy(
    IMtsInstanceEvents __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ REFGUID guidActivity,
    /* [in] */ REFCLSID clsid,
    /* [in] */ REFGUID tsid,
    /* [in] */ MTS_OBJID ObjectID);

void __RPC_STUB IMtsInstanceEvents_OnObjectCreate_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IMtsInstanceEvents_OnObjectRelease_Proxy(
    IMtsInstanceEvents __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ MTS_OBJID ObjectID);

void __RPC_STUB IMtsInstanceEvents_OnObjectRelease_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IMtsInstanceEvents_INTERFACE_DEFINED__ */

#ifndef __IMtsTransactionEvents_INTERFACE_DEFINED__
#define __IMtsTransactionEvents_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IMtsTransactionEvents
 * at Fri Nov 14 05:07:29 1997
 * using MIDL 3.01.75
 ****************************************/
/* [uuid][object] */

EXTERN_C const IID IID_IMtsTransactionEvents;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("BACEDF52-74AB-11D0-B162-00AA00BA3258")
    IMtsTransactionEvents : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE OnTransactionStart(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ REFGUID guidTx,
            /* [in] */ REFGUID tsid,
            /* [in] */ BOOL fRoot) = 0;

        virtual HRESULT STDMETHODCALLTYPE OnTransactionPrepared(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ REFGUID guidTx,
            /* [in] */ BOOL fVoteYes) = 0;

        virtual HRESULT STDMETHODCALLTYPE OnTransactionAborted(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ REFGUID guidTx) = 0;

    };

#else /* C style interface */

    typedef struct IMtsTransactionEventsVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            IMtsTransactionEvents __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            IMtsTransactionEvents __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            IMtsTransactionEvents __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnTransactionStart )(
            IMtsTransactionEvents __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ REFGUID guidTx,
            /* [in] */ REFGUID tsid,
            /* [in] */ BOOL fRoot);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnTransactionPrepared )(
            IMtsTransactionEvents __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ REFGUID guidTx,
            /* [in] */ BOOL fVoteYes);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnTransactionAborted )(
            IMtsTransactionEvents __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ REFGUID guidTx);

        END_INTERFACE
    } IMtsTransactionEventsVtbl;

    interface IMtsTransactionEvents
    {
        CONST_VTBL struct IMtsTransactionEventsVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IMtsTransactionEvents_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IMtsTransactionEvents_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IMtsTransactionEvents_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IMtsTransactionEvents_OnTransactionStart(This,perfCount,guidTx,tsid,fRoot)\
    (This)->lpVtbl -> OnTransactionStart(This,perfCount,guidTx,tsid,fRoot)

#define IMtsTransactionEvents_OnTransactionPrepared(This,perfCount,guidTx,fVoteYes)\
    (This)->lpVtbl -> OnTransactionPrepared(This,perfCount,guidTx,fVoteYes)

#define IMtsTransactionEvents_OnTransactionAborted(This,perfCount,guidTx)\
    (This)->lpVtbl -> OnTransactionAborted(This,perfCount,guidTx)

#endif /* COBJMACROS */

#endif /* C style interface */

HRESULT STDMETHODCALLTYPE IMtsTransactionEvents_OnTransactionStart_Proxy(
    IMtsTransactionEvents __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ REFGUID guidTx,
    /* [in] */ REFGUID tsid,
    /* [in] */ BOOL fRoot);

void __RPC_STUB IMtsTransactionEvents_OnTransactionStart_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IMtsTransactionEvents_OnTransactionPrepared_Proxy(
    IMtsTransactionEvents __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ REFGUID guidTx,
    /* [in] */ BOOL fVoteYes);

void __RPC_STUB IMtsTransactionEvents_OnTransactionPrepared_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IMtsTransactionEvents_OnTransactionAborted_Proxy(
    IMtsTransactionEvents __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ REFGUID guidTx);

void __RPC_STUB IMtsTransactionEvents_OnTransactionAborted_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IMtsTransactionEvents_INTERFACE_DEFINED__ */

#ifndef __IMtsMethodEvents_INTERFACE_DEFINED__
#define __IMtsMethodEvents_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IMtsMethodEvents
 * at Fri Nov 14 05:07:29 1997
 * using MIDL 3.01.75
 ****************************************/
/* [uuid][object] */

EXTERN_C const IID IID_IMtsMethodEvents;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("BACEDF53-74AB-11D0-B162-00AA00BA3258")
    IMtsMethodEvents : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE OnMethodCall(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ MTS_OBJID oid,
            /* [in] */ REFCLSID guidCid,
            /* [in] */ REFIID guidRid,
            /* [in] */ ULONG iMeth) = 0;

        virtual HRESULT STDMETHODCALLTYPE OnMethodReturn(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ MTS_OBJID oid,
            /* [in] */ REFCLSID guidCid,
            /* [in] */ REFIID guidRid,
            /* [in] */ ULONG iMeth,
            /* [in] */ HRESULT hresult) = 0;

        virtual HRESULT STDMETHODCALLTYPE OnMethodException(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ MTS_OBJID oid,
            /* [in] */ REFCLSID guidCid,
            /* [in] */ REFIID guidRid,
            /* [in] */ ULONG iMeth) = 0;

    };

#else /* C style interface */

    typedef struct IMtsMethodEventsVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            IMtsMethodEvents __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            IMtsMethodEvents __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            IMtsMethodEvents __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnMethodCall )(
            IMtsMethodEvents __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ MTS_OBJID oid,
            /* [in] */ REFCLSID guidCid,
            /* [in] */ REFIID guidRid,
            /* [in] */ ULONG iMeth);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnMethodReturn )(
            IMtsMethodEvents __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ MTS_OBJID oid,
            /* [in] */ REFCLSID guidCid,
            /* [in] */ REFIID guidRid,
            /* [in] */ ULONG iMeth,
            /* [in] */ HRESULT hresult);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnMethodException )(
            IMtsMethodEvents __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ MTS_OBJID oid,
            /* [in] */ REFCLSID guidCid,
            /* [in] */ REFIID guidRid,
            /* [in] */ ULONG iMeth);

        END_INTERFACE
    } IMtsMethodEventsVtbl;

    interface IMtsMethodEvents
    {
        CONST_VTBL struct IMtsMethodEventsVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IMtsMethodEvents_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IMtsMethodEvents_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IMtsMethodEvents_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IMtsMethodEvents_OnMethodCall(This,perfCount,oid,guidCid,guidRid,iMeth)\
    (This)->lpVtbl -> OnMethodCall(This,perfCount,oid,guidCid,guidRid,iMeth)

#define IMtsMethodEvents_OnMethodReturn(This,perfCount,oid,guidCid,guidRid,iMeth,hresult)\
    (This)->lpVtbl -> OnMethodReturn(This,perfCount,oid,guidCid,guidRid,iMeth,hresult)

#define IMtsMethodEvents_OnMethodException(This,perfCount,oid,guidCid,guidRid,iMeth)\
    (This)->lpVtbl -> OnMethodException(This,perfCount,oid,guidCid,guidRid,iMeth)

#endif /* COBJMACROS */

#endif /* C style interface */

HRESULT STDMETHODCALLTYPE IMtsMethodEvents_OnMethodCall_Proxy(
    IMtsMethodEvents __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ MTS_OBJID oid,
    /* [in] */ REFCLSID guidCid,
    /* [in] */ REFIID guidRid,
    /* [in] */ ULONG iMeth);

void __RPC_STUB IMtsMethodEvents_OnMethodCall_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IMtsMethodEvents_OnMethodReturn_Proxy(
    IMtsMethodEvents __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ MTS_OBJID oid,
    /* [in] */ REFCLSID guidCid,
    /* [in] */ REFIID guidRid,
    /* [in] */ ULONG iMeth,
    /* [in] */ HRESULT hresult);

void __RPC_STUB IMtsMethodEvents_OnMethodReturn_Stub(

IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IMtsMethodEvents_OnMethodException_Proxy(
    IMtsMethodEvents __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ MTS_OBJID oid,
    /* [in] */ REFCLSID guidCid,
    /* [in] */ REFIID guidRid,
    /* [in] */ ULONG iMeth);

void __RPC_STUB IMtsMethodEvents_OnMethodException_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IMtsMethodEvents_INTERFACE_DEFINED__ */

#ifndef __IMtsObjectEvents_INTERFACE_DEFINED__
#define __IMtsObjectEvents_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IMtsObjectEvents
 * at Fri Nov 14 05:07:29 1997
 * using MIDL 3.01.75
 ****************************************/
/* [uuid][object] */

EXTERN_C const IID IID_IMtsObjectEvents;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("BACEDF54-74AB-11D0-B162-00AA00BA3258")
    IMtsObjectEvents : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE OnObjectActivate(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ MTS_OBJID ObjectID) = 0;

        virtual HRESULT STDMETHODCALLTYPE OnObjectDeactivate(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ MTS_OBJID ObjectID) = 0;

        virtual HRESULT STDMETHODCALLTYPE OnDisableCommit(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ MTS_OBJID ObjectID) = 0;

        virtual HRESULT STDMETHODCALLTYPE OnEnableCommit(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ MTS_OBJID ObjectID) = 0;

        virtual HRESULT STDMETHODCALLTYPE OnSetComplete(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ MTS_OBJID ObjectID) = 0;

    };

#else /* C style interface */

    typedef struct IMtsObjectEventsVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            IMtsObjectEvents __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            IMtsObjectEvents __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            IMtsObjectEvents __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnObjectActivate )(
            IMtsObjectEvents __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ MTS_OBJID ObjectID);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnObjectDeactivate )(
            IMtsObjectEvents __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ MTS_OBJID ObjectID);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnDisableCommit )(
            IMtsObjectEvents __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ MTS_OBJID ObjectID);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnEnableCommit )(
            IMtsObjectEvents __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ MTS_OBJID ObjectID);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnSetComplete )(
            IMtsObjectEvents __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ MTS_OBJID ObjectID);

        END_INTERFACE
    } IMtsObjectEventsVtbl;

    interface IMtsObjectEvents
    {
        CONST_VTBL struct IMtsObjectEventsVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IMtsObjectEvents_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IMtsObjectEvents_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IMtsObjectEvents_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IMtsObjectEvents_OnObjectActivate(This,perfCount,ObjectID)\
    (This)->lpVtbl -> OnObjectActivate(This,perfCount,ObjectID)

#define IMtsObjectEvents_OnObjectDeactivate(This,perfCount,ObjectID)\
    (This)->lpVtbl -> OnObjectDeactivate(This,perfCount,ObjectID)

#define IMtsObjectEvents_OnDisableCommit(This,perfCount,ObjectID)\
    (This)->lpVtbl -> OnDisableCommit(This,perfCount,ObjectID)

#define IMtsObjectEvents_OnEnableCommit(This,perfCount,ObjectID)\
    (This)->lpVtbl -> OnEnableCommit(This,perfCount,ObjectID)

#define IMtsObjectEvents_OnSetComplete(This,perfCount,ObjectID)\
    (This)->lpVtbl -> OnSetComplete(This,perfCount,ObjectID)

#endif /* COBJMACROS */

#endif /* C style interface */

HRESULT STDMETHODCALLTYPE IMtsObjectEvents_OnObjectActivate_Proxy(
    IMtsObjectEvents __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ MTS_OBJID ObjectID);

void __RPC_STUB IMtsObjectEvents_OnObjectActivate_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IMtsObjectEvents_OnObjectDeactivate_Proxy(
    IMtsObjectEvents __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ MTS_OBJID ObjectID);

void __RPC_STUB IMtsObjectEvents_OnObjectDeactivate_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IMtsObjectEvents_OnDisableCommit_Proxy(
    IMtsObjectEvents __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ MTS_OBJID ObjectID);

void __RPC_STUB IMtsObjectEvents_OnDisableCommit_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IMtsObjectEvents_OnEnableCommit_Proxy(
    IMtsObjectEvents __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ MTS_OBJID ObjectID);

void __RPC_STUB IMtsObjectEvents_OnEnableCommit_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IMtsObjectEvents_OnSetComplete_Proxy(
    IMtsObjectEvents __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ MTS_OBJID ObjectID);

void __RPC_STUB IMtsObjectEvents_OnSetComplete_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IMtsObjectEvents_INTERFACE_DEFINED__ */

#ifndef __IMtsResourceEvents_INTERFACE_DEFINED__
#define __IMtsResourceEvents_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IMtsResourceEvents
 * at Fri Nov 14 05:07:29 1997
 * using MIDL 3.01.75
 ****************************************/
/* [uuid][object] */

EXTERN_C const IID IID_IMtsResourceEvents;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("BACEDF55-74AB-11D0-B162-00AA00BA3258")
    IMtsResourceEvents : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE OnResourceCreate(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ REFGUID guidActivity,
            /* [in] */ MTS_OBJID ObjectID,
            /* [in] */ LPCOLESTR pszType,
            /* [in] */ MTS_RESID resId) = 0;

        virtual HRESULT STDMETHODCALLTYPE OnResourceAllocate(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ REFGUID guidActivity,
            /* [in] */ MTS_OBJID ObjectID,
            /* [in] */ LPCOLESTR pszType,
            /* [in] */ MTS_RESID resId) = 0;

        virtual HRESULT STDMETHODCALLTYPE OnResourceRecycle(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ MTS_OBJID ObjectID,
            /* [in] */ LPCOLESTR pszType,
            /* [in] */ MTS_RESID resId) = 0;

        virtual HRESULT STDMETHODCALLTYPE OnResourceDestroy(
            /* [in] */ LONGLONG perfCount,
            /* [in] */ MTS_OBJID ObjectID,
            /* [in] */ LPCOLESTR pszType,
            /* [in] */ MTS_RESID resId) = 0;

    };

#else /* C style interface */

    typedef struct IMtsResourceEventsVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            IMtsResourceEvents __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            IMtsResourceEvents __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            IMtsResourceEvents __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnResourceCreate )(
            IMtsResourceEvents __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ REFGUID guidActivity,
            /* [in] */ MTS_OBJID ObjectID,
            /* [in] */ LPCOLESTR pszType,
            /* [in] */ MTS_RESID resId);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnResourceAllocate )(
            IMtsResourceEvents __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ REFGUID guidActivity,
            /* [in] */ MTS_OBJID ObjectID,
            /* [in] */ LPCOLESTR pszType,
            /* [in] */ MTS_RESID resId);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnResourceRecycle )(
            IMtsResourceEvents __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ MTS_OBJID ObjectID,
            /* [in] */ LPCOLESTR pszType,
            /* [in] */ MTS_RESID resId);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnResourceDestroy )(
            IMtsResourceEvents __RPC_FAR * This,
            /* [in] */ LONGLONG perfCount,
            /* [in] */ MTS_OBJID ObjectID,
            /* [in] */ LPCOLESTR pszType,
            /* [in] */ MTS_RESID resId);

        END_INTERFACE
    } IMtsResourceEventsVtbl;

    interface IMtsResourceEvents
    {
        CONST_VTBL struct IMtsResourceEventsVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IMtsResourceEvents_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IMtsResourceEvents_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IMtsResourceEvents_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IMtsResourceEvents_OnResourceCreate(This,perfCount,guidActivity,ObjectID,pszType,resId)\
    (This)->lpVtbl -> OnResourceCreate(This,perfCount,guidActivity,ObjectID,pszType,resId)

#define IMtsResourceEvents_OnResourceAllocate(This,perfCount,guidActivity,ObjectID,pszType,resId)\
    (This)->lpVtbl -> OnResourceAllocate(This,perfCount,guidActivity,ObjectID,pszType,resId)

#define IMtsResourceEvents_OnResourceRecycle(This,perfCount,ObjectID,pszType,resId)\
    (This)->lpVtbl -> OnResourceRecycle(This,perfCount,ObjectID,pszType,resId)

#define IMtsResourceEvents_OnResourceDestroy(This,perfCount,ObjectID,pszType,resId)\
    (This)->lpVtbl -> OnResourceDestroy(This,perfCount,ObjectID,pszType,resId)

#endif /* COBJMACROS */

#endif /* C style interface */

HRESULT STDMETHODCALLTYPE IMtsResourceEvents_OnResourceCreate_Proxy(
    IMtsResourceEvents __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ REFGUID guidActivity,
    /* [in] */ MTS_OBJID ObjectID,
    /* [in] */ LPCOLESTR pszType,
    /* [in] */ MTS_RESID resId);

void __RPC_STUB IMtsResourceEvents_OnResourceCreate_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IMtsResourceEvents_OnResourceAllocate_Proxy(
    IMtsResourceEvents __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ REFGUID guidActivity,
    /* [in] */ MTS_OBJID ObjectID,
    /* [in] */ LPCOLESTR pszType,
    /* [in] */ MTS_RESID resId);

void __RPC_STUB IMtsResourceEvents_OnResourceAllocate_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IMtsResourceEvents_OnResourceRecycle_Proxy(
    IMtsResourceEvents __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ MTS_OBJID ObjectID,
    /* [in] */ LPCOLESTR pszType,
    /* [in] */ MTS_RESID resId);

void __RPC_STUB IMtsResourceEvents_OnResourceRecycle_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IMtsResourceEvents_OnResourceDestroy_Proxy(
    IMtsResourceEvents __RPC_FAR * This,
    /* [in] */ LONGLONG perfCount,
    /* [in] */ MTS_OBJID ObjectID,
    /* [in] */ LPCOLESTR pszType,
    /* [in] */ MTS_RESID resId);

void __RPC_STUB IMtsResourceEvents_OnResourceDestroy_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IMtsResourceEvents_INTERFACE_DEFINED__ */

#ifndef __IMtsSecurityEvents_INTERFACE_DEFINED__
#define __IMtsSecurityEvents_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IMtsSecurityEvents
 * at Fri Nov 14 05:07:29 1997
 * using MIDL 3.01.75
 ****************************************/
/* [uuid][object] */

EXTERN_C const IID IID_IMtsSecurityEvents;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("55EB3AFB-0A11-11d1-B1B4-00AA00BA3258")
    IMtsSecurityEvents : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE OnAuthenticate(
            LONGLONG perfCount,
            REFGUID guidActivity,
            ULONG cbByteOrig,
            /* [size_is][in] */ BYTE __RPC_FAR *pSidOriginalUser,
            ULONG cbByteCur,
            /* [size_is][in] */ BYTE __RPC_FAR *pSidCurrentUser,
            BOOL bCurrentUserInpersonatingInProc) = 0;

    };

#else /* C style interface */

    typedef struct IMtsSecurityEventsVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            IMtsSecurityEvents __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            IMtsSecurityEvents __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            IMtsSecurityEvents __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *OnAuthenticate )(
            IMtsSecurityEvents __RPC_FAR * This,
            LONGLONG perfCount,
            REFGUID guidActivity,
            ULONG cbByteOrig,
            /* [size_is][in] */ BYTE __RPC_FAR *pSidOriginalUser,
            ULONG cbByteCur,
            /* [size_is][in] */ BYTE __RPC_FAR *pSidCurrentUser,
            BOOL bCurrentUserInpersonatingInProc);

        END_INTERFACE
    } IMtsSecurityEventsVtbl;

    interface IMtsSecurityEvents
    {
        CONST_VTBL struct IMtsSecurityEventsVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IMtsSecurityEvents_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IMtsSecurityEvents_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IMtsSecurityEvents_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IMtsSecurityEvents_OnAuthenticate(This,perfCount,guidActivity,cbByteOrig,pSidOriginalUser,cbByteCur,pSidCurrentUser,bCurrentUserInpersonatingInProc)\
    (This)->lpVtbl -> OnAuthenticate(This,perfCount,guidActivity,cbByteOrig,pSidOriginalUser,cbByteCur,pSidCurrentUser,bCurrentUserInpersonatingInProc)

#endif /* COBJMACROS */

#endif /* C style interface */

HRESULT STDMETHODCALLTYPE IMtsSecurityEvents_OnAuthenticate_Proxy(
    IMtsSecurityEvents __RPC_FAR * This,
    LONGLONG perfCount,
    REFGUID guidActivity,
    ULONG cbByteOrig,
    /* [size_is][in] */ BYTE __RPC_FAR *pSidOriginalUser,
    ULONG cbByteCur,
    /* [size_is][in] */ BYTE __RPC_FAR *pSidCurrentUser,
    BOOL bCurrentUserInpersonatingInProc);

void __RPC_STUB IMtsSecurityEvents_OnAuthenticate_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IMtsSecurityEvents_INTERFACE_DEFINED__ */

/* Additional Prototypes for ALL interfaces */

unsigned long             __RPC_USER  VARIANT_UserSize(     unsigned long __RPC_FAR *, unsigned long            , VARIANT __RPC_FAR * );
unsigned char __RPC_FAR * __RPC_USER  VARIANT_UserMarshal(  unsigned long __RPC_FAR *, unsigned char __RPC_FAR *, VARIANT __RPC_FAR * );
unsigned char __RPC_FAR * __RPC_USER  VARIANT_UserUnmarshal(unsigned long __RPC_FAR *, unsigned char __RPC_FAR *, VARIANT __RPC_FAR * );
void                      __RPC_USER  VARIANT_UserFree(     unsigned long __RPC_FAR *, VARIANT __RPC_FAR * );

/* end of Additional Prototypes */

#ifdef __cplusplus
}
#endif

#endif


EVENTCPTS_I.C

/* this file contains the actual definitions of */
/* the IIDs and CLSIDs */

/* link this file in with the server and any clients */

/* File created by MIDL compiler version 3.01.75 */
/* at Fri Nov 14 05:07:29 1997
 */
/* Compiler settings for eventcpts.idl:
    Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext
    error checks: none
*/
//@@MIDL_FILE_HEADING(  )
#ifdef __cplusplus
extern "C"{
#endif

#ifndef __IID_DEFINED__
#define __IID_DEFINED__

typedef struct _IID
{
    unsigned long x;
    unsigned short s1;
    unsigned short s2;
    unsigned char  c[8];
} IID;

#endif // __IID_DEFINED__

#ifndef CLSID_DEFINED
#define CLSID_DEFINED
typedef IID CLSID;
#endif // CLSID_DEFINED

const IID IID_IMtsUserEvent = {0xD56C3DC0,0x8482,0x11d0,{0xB1,0x70,0x00,0xAA,0x00,0xBA,0x32,0x58}};

const IID IID_IMtsThreadEvents = {0xBACEDF4F,0x74AB,0x11D0,{0xB1,0x62,0x00,0xAA,0x00,0xBA,0x32,0x58}};

const IID IID_IMtsPackageEvents = {0xBACEDF50,0x74AB,0x11D0,{0xB1,0x62,0x00,0xAA,0x00,0xBA,0x32,0x58}};

const IID IID_IMtsInstanceEvents = {0xBACEDF51,0x74AB,0x11D0,{0xB1,0x62,0x00,0xAA,0x00,0xBA,0x32,0x58}};

const IID IID_IMtsTransactionEvents = {0xBACEDF52,0x74AB,0x11D0,{0xB1,0x62,0x00,0xAA,0x00,0xBA,0x32,0x58}};

const IID IID_IMtsMethodEvents = {0xBACEDF53,0x74AB,0x11D0,{0xB1,0x62,0x00,0xAA,0x00,0xBA,0x32,0x58}};

const IID IID_IMtsObjectEvents = {0xBACEDF54,0x74AB,0x11D0,{0xB1,0x62,0x00,0xAA,0x00,0xBA,0x32,0x58}};

const IID IID_IMtsResourceEvents = {0xBACEDF55,0x74AB,0x11D0,{0xB1,0x62,0x00,0xAA,0x00,0xBA,0x32,0x58}};

const IID IID_IMtsSecurityEvents = {0x55EB3AFB,0x0A11,0x11d1,{0xB1,0xB4,0x00,0xAA,0x00,0xBA,0x32,0x58}};

#ifdef __cplusplus
}
#endif


MTSAUDIT.IDL

// MtsAudit.idl : IDL source for MtsAudit.dll
//

// This file will be processed by the MIDL tool to
// produce the type library (MtsAudit.tlb) and marshalling code.

import "oaidl.idl";
import "ocidl.idl";

typedef ULONG MTS_OBJID;
typedef ULONG MTS_RESID;

[

uuid(AFCF0622-1429-11D1-B1B8-00AA00BA3258),

helpstring("IMtsSqlAudit Interface"),
pointer_default(unique)
]
interface IMtsSqlAudit : IUnknown
{
HRESULT OnUserEvent(
                [in] LONGLONG perfCount,
                [in] VARIANT * pvarEvent
    );
HRESULT OnThreadStart(
[in] LONGLONG perfCount,
[in] ULONG ThreadID
);

HRESULT OnThreadTerminate(
[in] LONGLONG perfCount,
[in] ULONG ThreadID
);

HRESULT OnThreadAssignToActivity(
[in] LONGLONG perfCount,
[in] LPCOLESTR guidActivity,
[in] ULONG ThreadID
);

HRESULT OnThreadUnassignFromActivity(
[in] LONGLONG perfCount,
[in] LPCOLESTR guidActivity,
[in] ULONG ThreadID
);
HRESULT OnPackageActivation(
[in] LONGLONG perfCount,
[in] LPCOLESTR guidPackage,
[in] LPCOLESTR sPackageName
);

HRESULT OnPackageShutdown(
[in] LONGLONG perfCount,
[in] LPCOLESTR guidPackage,
[in] LPCOLESTR sPackageName
);
HRESULT OnObjectCreate(
[in] LONGLONG perfCount,
[in] LPCOLESTR guidActivity,
[in] LPCOLESTR clsid,
[in] LPCOLESTR tsid,
[in] MTS_OBJID ObjectID
);

HRESULT OnObjectRelease(
[in] LONGLONG perfCount,
[in] MTS_OBJID ObjectID
);

HRESULT OnTransactionStart(
[in] LONGLONG perfCount,
[in] LPCOLESTR guidTx,
[in] LPCOLESTR tsid,
[in] BOOL fRoot
);

HRESULT OnTransactionPrepared(
[in] LONGLONG perfCount,
[in] LPCOLESTR guidTx,
[in] BOOL fVoteYes
);

HRESULT OnTransactionAborted(
[in] LONGLONG perfCount,
[in] LPCOLESTR guidTx
);

HRESULT OnMethodCall(
[in] LONGLONG perfCount,
[in] MTS_OBJID oid,
[in] LPCOLESTR guidCid,
[in] LPCOLESTR guidRid,
[in] LPCOLESTR sMethod
);

HRESULT OnMethodReturn(
[in] LONGLONG perfCount,
[in] MTS_OBJID oid,
[in] LPCOLESTR guidCid,
[in] LPCOLESTR guidRid,
[in] LPCOLESTR sMethod,
[in] HRESULT hresult
);

HRESULT OnMethodException(
[in] LONGLONG perfCount,
[in] MTS_OBJID oid,
[in] LPCOLESTR guidCid,
[in] LPCOLESTR guidRid,
[in] LPCOLESTR sMethod
);
HRESULT OnObjectActivate(
[in] LONGLONG perfCount,
[in] MTS_OBJID ObjectID
);

HRESULT OnObjectDeactivate(
[in] LONGLONG perfCount,
[in] MTS_OBJID ObjectID
);

HRESULT OnDisableCommit(
[in] LONGLONG perfCount,
[in] MTS_OBJID ObjectID
);

HRESULT OnEnableCommit(
[in] LONGLONG perfCount,
[in] MTS_OBJID ObjectID
);

HRESULT OnSetComplete(
[in] LONGLONG perfCount,
[in] MTS_OBJID ObjectID
);
   HRESULT OnResourceCreate(
[in] LONGLONG perfCount,
[in] LPCOLESTR guidActivity,
[in] MTS_OBJID ObjectID,
[in] LPCOLESTR pszType,
[in] MTS_RESID resId
);

HRESULT OnResourceAllocate(
[in] LONGLONG perfCount,
[in] LPCOLESTR guidActivity,
[in] MTS_OBJID ObjectID,
[in] LPCOLESTR pszType,
[in] MTS_RESID resId
);

HRESULT OnResourceRecycle(
[in] LONGLONG perfCount,
[in] MTS_OBJID ObjectID,
[in] LPCOLESTR pszType,
[in] MTS_RESID resId
);

HRESULT OnResourceDestroy(
[in] LONGLONG perfCount,
[in] MTS_OBJID ObjectID,
[in] LPCOLESTR pszType,
[in] MTS_RESID resId
);
HRESULT OnAuthenticate(
  LONGLONG perfCount,
  LPCOLESTR guidActivity,
  LPCOLESTR sOriginalUser,
  LPCOLESTR sDirectUser,
  BOOL bCurrentUserInpersonatingInProc
);

HRESULT Init(LPCOLESTR sDSN, LPCOLESTR sUser, LPCOLESTR sPw);

};
[
uuid(AFCF0615-1429-11D1-B1B8-00AA00BA3258),
version(1.0),
helpstring("MtsAudit 1.0 Type Library")
]
library MTSAUDITLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");

[
uuid(AFCF0623-1429-11D1-B1B8-00AA00BA3258),
helpstring("MtsSqlAudit Class")
]
coclass MtsSqlAudit
{
[default] interface IMtsSqlAudit;
};
};


MTSEVENTS.H

/* this ALWAYS GENERATED file contains the definitions for the interfaces */

/* File created by MIDL compiler version 3.01.75 */
/* at Fri Nov 14 05:07:24 1997
 */
/* Compiler settings for mtsevents.idl:
    Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext
    error checks: none
*/
//@@MIDL_FILE_HEADING(  )
#include "rpc.h"
#include "rpcndr.h"
#ifndef COM_NO_WINDOWS_H
#include "windows.h"
#include "ole2.h"
#endif /*COM_NO_WINDOWS_H*/

#ifndef __mtsevents_h__
#define __mtsevents_h__

#ifdef __cplusplus
extern "C"{
#endif

/* Forward Declarations */

#ifndef __IMtsEvents_FWD_DEFINED__
#define __IMtsEvents_FWD_DEFINED__
typedef interface IMtsEvents IMtsEvents;
#endif /* __IMtsEvents_FWD_DEFINED__ */

#ifndef __IMtsEventInfo_FWD_DEFINED__
#define __IMtsEventInfo_FWD_DEFINED__
typedef interface IMtsEventInfo IMtsEventInfo;
#endif /* __IMtsEventInfo_FWD_DEFINED__ */

#ifndef __IEventMoniker_FWD_DEFINED__
#define __IEventMoniker_FWD_DEFINED__
typedef interface IEventMoniker IEventMoniker;
#endif /* __IEventMoniker_FWD_DEFINED__ */

#ifndef __IMTSLocater_FWD_DEFINED__
#define __IMTSLocater_FWD_DEFINED__
typedef interface IMTSLocater IMTSLocater;
#endif /* __IMTSLocater_FWD_DEFINED__ */

#ifndef __CoMTSEvents_FWD_DEFINED__
#define __CoMTSEvents_FWD_DEFINED__

#ifdef __cplusplus
typedef class CoMTSEvents CoMTSEvents;
#else
typedef struct CoMTSEvents CoMTSEvents;
#endif /* __cplusplus */

#endif /* __CoMTSEvents_FWD_DEFINED__ */

#ifndef __EventMoniker_FWD_DEFINED__
#define __EventMoniker_FWD_DEFINED__

#ifdef __cplusplus
typedef class EventMoniker EventMoniker;
#else
typedef struct EventMoniker EventMoniker;
#endif /* __cplusplus */

#endif /* __EventMoniker_FWD_DEFINED__ */

#ifndef __CoMTSLocater_FWD_DEFINED__
#define __CoMTSLocater_FWD_DEFINED__

#ifdef __cplusplus
typedef class CoMTSLocater CoMTSLocater;
#else
typedef struct CoMTSLocater CoMTSLocater;
#endif /* __cplusplus */

#endif /* __CoMTSLocater_FWD_DEFINED__ */

/* header files for imported files */
#include "oaidl.h"
#include "eventcpts.h"

void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t);
void __RPC_USER MIDL_user_free( void __RPC_FAR * );

#ifndef __IMtsEvents_INTERFACE_DEFINED__
#define __IMtsEvents_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IMtsEvents
 * at Fri Nov 14 05:07:24 1997
 * using MIDL 3.01.75
 ****************************************/
/* [unique][helpstring][dual][uuid][object] */

EXTERN_C const IID IID_IMtsEvents;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("BACEDF4D-74AB-11D0-B162-00AA00BA3258")
    IMtsEvents : public IDispatch
    {
    public:
        virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_PackageName(
            /* [retval][out] */ BSTR __RPC_FAR *pVal) = 0;

        virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_PackageGuid(
            /* [retval][out] */ BSTR __RPC_FAR *pVal) = 0;

        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE PostEvent(
            /* [in] */ VARIANT __RPC_FAR *vEvent) = 0;

        virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_FireEvents(
            /* [retval][out] */ VARIANT_BOOL __RPC_FAR *pVal) = 0;

        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE GetProcessID(
            /* [retval][out] */ long __RPC_FAR *id) = 0;

    };

#else /* C style interface */

    typedef struct IMtsEventsVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            IMtsEvents __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            IMtsEvents __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            IMtsEvents __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfoCount )(
            IMtsEvents __RPC_FAR * This,
            /* [out] */ UINT __RPC_FAR *pctinfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfo )(
            IMtsEvents __RPC_FAR * This,
            /* [in] */ UINT iTInfo,
            /* [in] */ LCID lcid,
            /* [out] */ ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetIDsOfNames )(
            IMtsEvents __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [size_is][in] */ LPOLESTR __RPC_FAR *rgszNames,
            /* [in] */ UINT cNames,
            /* [in] */ LCID lcid,
            /* [size_is][out] */ DISPID __RPC_FAR *rgDispId);

        /* [local] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Invoke )(
            IMtsEvents __RPC_FAR * This,
            /* [in] */ DISPID dispIdMember,
            /* [in] */ REFIID riid,
            /* [in] */ LCID lcid,
            /* [in] */ WORD wFlags,
            /* [out][in] */ DISPPARAMS __RPC_FAR *pDispParams,
            /* [out] */ VARIANT __RPC_FAR *pVarResult,
            /* [out] */ EXCEPINFO __RPC_FAR *pExcepInfo,
            /* [out] */ UINT __RPC_FAR *puArgErr);

        /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_PackageName )(
            IMtsEvents __RPC_FAR * This,
            /* [retval][out] */ BSTR __RPC_FAR *pVal);

        /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_PackageGuid )(
            IMtsEvents __RPC_FAR * This,
            /* [retval][out] */ BSTR __RPC_FAR *pVal);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *PostEvent )(
            IMtsEvents __RPC_FAR * This,
            /* [in] */ VARIANT __RPC_FAR *vEvent);

        /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_FireEvents )(
            IMtsEvents __RPC_FAR * This,
            /* [retval][out] */ VARIANT_BOOL __RPC_FAR *pVal);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetProcessID )(
            IMtsEvents __RPC_FAR * This,
            /* [retval][out] */ long __RPC_FAR *id);

        END_INTERFACE
    } IMtsEventsVtbl;

    interface IMtsEvents
    {
        CONST_VTBL struct IMtsEventsVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IMtsEvents_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IMtsEvents_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IMtsEvents_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IMtsEvents_GetTypeInfoCount(This,pctinfo)\
    (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo)

#define IMtsEvents_GetTypeInfo(This,iTInfo,lcid,ppTInfo)\
    (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo)

#define IMtsEvents_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)\
    (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)

#define IMtsEvents_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)\
    (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)

#define IMtsEvents_get_PackageName(This,pVal)\
    (This)->lpVtbl -> get_PackageName(This,pVal)

#define IMtsEvents_get_PackageGuid(This,pVal)\
    (This)->lpVtbl -> get_PackageGuid(This,pVal)

#define IMtsEvents_PostEvent(This,vEvent)\
    (This)->lpVtbl -> PostEvent(This,vEvent)

#define IMtsEvents_get_FireEvents(This,pVal)\
    (This)->lpVtbl -> get_FireEvents(This,pVal)

#define IMtsEvents_GetProcessID(This,id)\
    (This)->lpVtbl -> GetProcessID(This,id)

#endif /* COBJMACROS */

#endif /* C style interface */

/* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE IMtsEvents_get_PackageName_Proxy(
    IMtsEvents __RPC_FAR * This,
    /* [retval][out] */ BSTR __RPC_FAR *pVal);

void __RPC_STUB IMtsEvents_get_PackageName_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE IMtsEvents_get_PackageGuid_Proxy(
    IMtsEvents __RPC_FAR * This,
    /* [retval][out] */ BSTR __RPC_FAR *pVal);

void __RPC_STUB IMtsEvents_get_PackageGuid_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE IMtsEvents_PostEvent_Proxy(
    IMtsEvents __RPC_FAR * This,
    /* [in] */ VARIANT __RPC_FAR *vEvent);

void __RPC_STUB IMtsEvents_PostEvent_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE IMtsEvents_get_FireEvents_Proxy(
    IMtsEvents __RPC_FAR * This,
    /* [retval][out] */ VARIANT_BOOL __RPC_FAR *pVal);

void __RPC_STUB IMtsEvents_get_FireEvents_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE IMtsEvents_GetProcessID_Proxy(
    IMtsEvents __RPC_FAR * This,
    /* [retval][out] */ long __RPC_FAR *id);

void __RPC_STUB IMtsEvents_GetProcessID_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IMtsEvents_INTERFACE_DEFINED__ */

#ifndef __IMtsEventInfo_INTERFACE_DEFINED__
#define __IMtsEventInfo_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IMtsEventInfo
 * at Fri Nov 14 05:07:24 1997
 * using MIDL 3.01.75
 ****************************************/
/* [unique][helpstring][dual][uuid][object] */

EXTERN_C const IID IID_IMtsEventInfo;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("D56C3DC1-8482-11d0-B170-00AA00BA3258")
    IMtsEventInfo : public IDispatch
    {
    public:
        virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Names(
            /* [retval][out] */ IUnknown __RPC_FAR *__RPC_FAR *pUnk) = 0;

        virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_DisplayName(
            /* [retval][out] */ BSTR __RPC_FAR *sDisplayName) = 0;

        virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_EventID(
            /* [retval][out] */ BSTR __RPC_FAR *sGuidEventID) = 0;

        virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Count(
            /* [retval][out] */ long __RPC_FAR *lCount) = 0;

        virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Value(
            /* [in] */ BSTR sKey,
            /* [retval][out] */ VARIANT __RPC_FAR *pVal) = 0;

    };

#else /* C style interface */

    typedef struct IMtsEventInfoVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            IMtsEventInfo __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            IMtsEventInfo __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            IMtsEventInfo __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfoCount )(
            IMtsEventInfo __RPC_FAR * This,
            /* [out] */ UINT __RPC_FAR *pctinfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfo )(
            IMtsEventInfo __RPC_FAR * This,
            /* [in] */ UINT iTInfo,
            /* [in] */ LCID lcid,
            /* [out] */ ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetIDsOfNames )(
            IMtsEventInfo __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [size_is][in] */ LPOLESTR __RPC_FAR *rgszNames,
            /* [in] */ UINT cNames,
            /* [in] */ LCID lcid,
            /* [size_is][out] */ DISPID __RPC_FAR *rgDispId);

        /* [local] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Invoke )(
            IMtsEventInfo __RPC_FAR * This,
            /* [in] */ DISPID dispIdMember,
            /* [in] */ REFIID riid,
            /* [in] */ LCID lcid,
            /* [in] */ WORD wFlags,
            /* [out][in] */ DISPPARAMS __RPC_FAR *pDispParams,
            /* [out] */ VARIANT __RPC_FAR *pVarResult,
            /* [out] */ EXCEPINFO __RPC_FAR *pExcepInfo,
            /* [out] */ UINT __RPC_FAR *puArgErr);

        /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_Names )(
            IMtsEventInfo __RPC_FAR * This,
            /* [retval][out] */ IUnknown __RPC_FAR *__RPC_FAR *pUnk);

        /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_DisplayName )(
            IMtsEventInfo __RPC_FAR * This,
            /* [retval][out] */ BSTR __RPC_FAR *sDisplayName);

        /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_EventID )(
            IMtsEventInfo __RPC_FAR * This,
            /* [retval][out] */ BSTR __RPC_FAR *sGuidEventID);

        /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_Count )(
            IMtsEventInfo __RPC_FAR * This,
            /* [retval][out] */ long __RPC_FAR *lCount);

        /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_Value )(
            IMtsEventInfo __RPC_FAR * This,
            /* [in] */ BSTR sKey,
            /* [retval][out] */ VARIANT __RPC_FAR *pVal);

        END_INTERFACE
    } IMtsEventInfoVtbl;

    interface IMtsEventInfo
    {
        CONST_VTBL struct IMtsEventInfoVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IMtsEventInfo_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IMtsEventInfo_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IMtsEventInfo_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IMtsEventInfo_GetTypeInfoCount(This,pctinfo)\
    (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo)

#define IMtsEventInfo_GetTypeInfo(This,iTInfo,lcid,ppTInfo)\
    (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo)

#define IMtsEventInfo_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)\
    (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)

#define IMtsEventInfo_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)\
    (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)

#define IMtsEventInfo_get_Names(This,pUnk)\
    (This)->lpVtbl -> get_Names(This,pUnk)

#define IMtsEventInfo_get_DisplayName(This,sDisplayName)\
    (This)->lpVtbl -> get_DisplayName(This,sDisplayName)

#define IMtsEventInfo_get_EventID(This,sGuidEventID)\
    (This)->lpVtbl -> get_EventID(This,sGuidEventID)

#define IMtsEventInfo_get_Count(This,lCount)\
    (This)->lpVtbl -> get_Count(This,lCount)

#define IMtsEventInfo_get_Value(This,sKey,pVal)\
    (This)->lpVtbl -> get_Value(This,sKey,pVal)

#endif /* COBJMACROS */

#endif /* C style interface */

/* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IMtsEventInfo_get_Names_Proxy(
    IMtsEventInfo __RPC_FAR * This,
    /* [retval][out] */ IUnknown __RPC_FAR *__RPC_FAR *pUnk);

void __RPC_STUB IMtsEventInfo_get_Names_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE IMtsEventInfo_get_DisplayName_Proxy(
    IMtsEventInfo __RPC_FAR * This,
    /* [retval][out] */ BSTR __RPC_FAR *sDisplayName);

void __RPC_STUB IMtsEventInfo_get_DisplayName_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IMtsEventInfo_get_EventID_Proxy(
    IMtsEventInfo __RPC_FAR * This,
    /* [retval][out] */ BSTR __RPC_FAR *sGuidEventID);

void __RPC_STUB IMtsEventInfo_get_EventID_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IMtsEventInfo_get_Count_Proxy(
    IMtsEventInfo __RPC_FAR * This,
    /* [retval][out] */ long __RPC_FAR *lCount);

void __RPC_STUB IMtsEventInfo_get_Count_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IMtsEventInfo_get_Value_Proxy(
    IMtsEventInfo __RPC_FAR * This,
    /* [in] */ BSTR sKey,
    /* [retval][out] */ VARIANT __RPC_FAR *pVal);

void __RPC_STUB IMtsEventInfo_get_Value_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IMtsEventInfo_INTERFACE_DEFINED__ */

#ifndef __IEventMoniker_INTERFACE_DEFINED__
#define __IEventMoniker_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IEventMoniker
 * at Fri Nov 14 05:07:24 1997
 * using MIDL 3.01.75
 ****************************************/
/* [object][unique][helpstring][hidden][uuid] */

EXTERN_C const IID IID_IEventMoniker;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("C8CF32B3-7885-11D0-B169-00AA00BA3258")
    IEventMoniker : public IUnknown
    {
    public:
        virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Name(
            /* [retval][out] */ BSTR __RPC_FAR *pVal) = 0;

        virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_Name(
            /* [in] */ BSTR newVal) = 0;

        virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_ID(
            /* [retval][out] */ long __RPC_FAR *pVal) = 0;

        virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_ID(
            /* [in] */ long newVal) = 0;

        virtual /* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE put_EventObj(
            /* [in] */ IUnknown __RPC_FAR *newVal) = 0;

    };

#else /* C style interface */

    typedef struct IEventMonikerVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            IEventMoniker __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            IEventMoniker __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            IEventMoniker __RPC_FAR * This);

        /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_Name )(
            IEventMoniker __RPC_FAR * This,
            /* [retval][out] */ BSTR __RPC_FAR *pVal);

        /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *put_Name )(
            IEventMoniker __RPC_FAR * This,
            /* [in] */ BSTR newVal);

        /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_ID )(
            IEventMoniker __RPC_FAR * This,
            /* [retval][out] */ long __RPC_FAR *pVal);

        /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *put_ID )(
            IEventMoniker __RPC_FAR * This,
            /* [in] */ long newVal);

        /* [helpstring][propput] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *put_EventObj )(
            IEventMoniker __RPC_FAR * This,
            /* [in] */ IUnknown __RPC_FAR *newVal);

        END_INTERFACE
    } IEventMonikerVtbl;

    interface IEventMoniker
    {
        CONST_VTBL struct IEventMonikerVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IEventMoniker_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IEventMoniker_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IEventMoniker_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IEventMoniker_get_Name(This,pVal)\
    (This)->lpVtbl -> get_Name(This,pVal)

#define IEventMoniker_put_Name(This,newVal)\
    (This)->lpVtbl -> put_Name(This,newVal)

#define IEventMoniker_get_ID(This,pVal)\
    (This)->lpVtbl -> get_ID(This,pVal)

#define IEventMoniker_put_ID(This,newVal)\
    (This)->lpVtbl -> put_ID(This,newVal)

#define IEventMoniker_put_EventObj(This,newVal)\
    (This)->lpVtbl -> put_EventObj(This,newVal)

#endif /* COBJMACROS */

#endif /* C style interface */

/* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IEventMoniker_get_Name_Proxy(
    IEventMoniker __RPC_FAR * This,
    /* [retval][out] */ BSTR __RPC_FAR *pVal);

void __RPC_STUB IEventMoniker_get_Name_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IEventMoniker_put_Name_Proxy(
    IEventMoniker __RPC_FAR * This,
    /* [in] */ BSTR newVal);

void __RPC_STUB IEventMoniker_put_Name_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE IEventMoniker_get_ID_Proxy(
    IEventMoniker __RPC_FAR * This,
    /* [retval][out] */ long __RPC_FAR *pVal);

void __RPC_STUB IEventMoniker_get_ID_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IEventMoniker_put_ID_Proxy(
    IEventMoniker __RPC_FAR * This,
    /* [in] */ long newVal);

void __RPC_STUB IEventMoniker_put_ID_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][propput] */ HRESULT STDMETHODCALLTYPE IEventMoniker_put_EventObj_Proxy(
    IEventMoniker __RPC_FAR * This,
    /* [in] */ IUnknown __RPC_FAR *newVal);

void __RPC_STUB IEventMoniker_put_EventObj_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IEventMoniker_INTERFACE_DEFINED__ */

#ifndef __IMTSLocater_INTERFACE_DEFINED__
#define __IMTSLocater_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IMTSLocater
 * at Fri Nov 14 05:07:24 1997
 * using MIDL 3.01.75
 ****************************************/
/* [unique][helpstring][dual][uuid][object] */

EXTERN_C const IID IID_IMTSLocater;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("D19B8BFD-7F88-11D0-B16E-00AA00BA3258")
    IMTSLocater : public IDispatch
    {
    public:
        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE GetEventDispatcher(
            /* [retval][out] */ IUnknown __RPC_FAR *__RPC_FAR *pUnk) = 0;

    };

#else /* C style interface */

    typedef struct IMTSLocaterVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            IMTSLocater __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            IMTSLocater __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            IMTSLocater __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfoCount )(
            IMTSLocater __RPC_FAR * This,
            /* [out] */ UINT __RPC_FAR *pctinfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfo )(
            IMTSLocater __RPC_FAR * This,
            /* [in] */ UINT iTInfo,
            /* [in] */ LCID lcid,
            /* [out] */ ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetIDsOfNames )(
            IMTSLocater __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [size_is][in] */ LPOLESTR __RPC_FAR *rgszNames,
            /* [in] */ UINT cNames,
            /* [in] */ LCID lcid,
            /* [size_is][out] */ DISPID __RPC_FAR *rgDispId);

        /* [local] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Invoke )(
            IMTSLocater __RPC_FAR * This,
            /* [in] */ DISPID dispIdMember,
            /* [in] */ REFIID riid,
            /* [in] */ LCID lcid,
            /* [in] */ WORD wFlags,
            /* [out][in] */ DISPPARAMS __RPC_FAR *pDispParams,
            /* [out] */ VARIANT __RPC_FAR *pVarResult,
            /* [out] */ EXCEPINFO __RPC_FAR *pExcepInfo,
            /* [out] */ UINT __RPC_FAR *puArgErr);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetEventDispatcher )(
            IMTSLocater __RPC_FAR * This,
            /* [retval][out] */ IUnknown __RPC_FAR *__RPC_FAR *pUnk);

        END_INTERFACE
    } IMTSLocaterVtbl;

    interface IMTSLocater
    {
        CONST_VTBL struct IMTSLocaterVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IMTSLocater_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IMTSLocater_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IMTSLocater_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IMTSLocater_GetTypeInfoCount(This,pctinfo)\
    (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo)

#define IMTSLocater_GetTypeInfo(This,iTInfo,lcid,ppTInfo)\
    (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo)

#define IMTSLocater_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)\
    (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)

#define IMTSLocater_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)\
    (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)

#define IMTSLocater_GetEventDispatcher(This,pUnk)\
    (This)->lpVtbl -> GetEventDispatcher(This,pUnk)

#endif /* COBJMACROS */

#endif /* C style interface */

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE IMTSLocater_GetEventDispatcher_Proxy(
    IMTSLocater __RPC_FAR * This,
    /* [retval][out] */ IUnknown __RPC_FAR *__RPC_FAR *pUnk);

void __RPC_STUB IMTSLocater_GetEventDispatcher_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IMTSLocater_INTERFACE_DEFINED__ */

#ifndef __MTSEVENTSLib_LIBRARY_DEFINED__
#define __MTSEVENTSLib_LIBRARY_DEFINED__

/****************************************
 * Generated header for library: MTSEVENTSLib
 * at Fri Nov 14 05:07:24 1997
 * using MIDL 3.01.75
 ****************************************/
/* [helpstring][version][uuid] */

EXTERN_C const IID LIBID_MTSEVENTSLib;

#ifdef __cplusplus
EXTERN_C const CLSID CLSID_CoMTSEvents;

class DECLSPEC_UUID("BACEDF4E-74AB-11D0-B162-00AA00BA3258")
CoMTSEvents;
#endif

#ifdef __cplusplus
EXTERN_C const CLSID CLSID_EventMoniker;

class DECLSPEC_UUID("C8CF32B4-7885-11D0-B169-00AA00BA3258")
EventMoniker;
#endif

#ifdef __cplusplus
EXTERN_C const CLSID CLSID_CoMTSLocater;

class DECLSPEC_UUID("D19B8BFE-7F88-11D0-B16E-00AA00BA3258")
CoMTSLocater;
#endif
#endif /* __MTSEVENTSLib_LIBRARY_DEFINED__ */

/* Additional Prototypes for ALL interfaces */

unsigned long             __RPC_USER  BSTR_UserSize(     unsigned long __RPC_FAR *, unsigned long            , BSTR __RPC_FAR * );
unsigned char __RPC_FAR * __RPC_USER  BSTR_UserMarshal(  unsigned long __RPC_FAR *, unsigned char __RPC_FAR *, BSTR __RPC_FAR * );

unsigned char __RPC_FAR * __RPC_USER  BSTR_UserUnmarshal(unsigned long __RPC_FAR *, unsigned char __RPC_FAR *, BSTR __RPC_FAR * );
void                      __RPC_USER  BSTR_UserFree(     unsigned long __RPC_FAR *, BSTR __RPC_FAR * );

unsigned long             __RPC_USER  VARIANT_UserSize(     unsigned long __RPC_FAR *, unsigned long            , VARIANT __RPC_FAR * );
unsigned char __RPC_FAR * __RPC_USER  VARIANT_UserMarshal(  unsigned long __RPC_FAR *, unsigned char __RPC_FAR *, VARIANT __RPC_FAR * );
unsigned char __RPC_FAR * __RPC_USER  VARIANT_UserUnmarshal(unsigned long __RPC_FAR *, unsigned char __RPC_FAR *, VARIANT __RPC_FAR * );
void                      __RPC_USER  VARIANT_UserFree(     unsigned long __RPC_FAR *, VARIANT __RPC_FAR * );

/* end of Additional Prototypes */

#ifdef __cplusplus
}
#endif

#endif


MTSEVENTS_I.C

/* this file contains the actual definitions of */
/* the IIDs and CLSIDs */

/* link this file in with the server and any clients */

/* File created by MIDL compiler version 3.01.75 */
/* at Fri Nov 14 05:07:24 1997
 */
/* Compiler settings for mtsevents.idl:
    Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext
    error checks: none
*/
//@@MIDL_FILE_HEADING(  )
#ifdef __cplusplus
extern "C"{
#endif

#ifndef __IID_DEFINED__
#define __IID_DEFINED__

typedef struct _IID
{
    unsigned long x;
    unsigned short s1;
    unsigned short s2;
    unsigned char  c[8];
} IID;

#endif // __IID_DEFINED__

#ifndef CLSID_DEFINED
#define CLSID_DEFINED
typedef IID CLSID;
#endif // CLSID_DEFINED

const IID IID_IMtsEvents = {0xBACEDF4D,0x74AB,0x11D0,{0xB1,0x62,0x00,0xAA,0x00,0xBA,0x32,0x58}};

const IID IID_IMtsEventInfo = {0xD56C3DC1,0x8482,0x11d0,{0xB1,0x70,0x00,0xAA,0x00,0xBA,0x32,0x58}};

const IID IID_IEventMoniker = {0xC8CF32B3,0x7885,0x11D0,{0xB1,0x69,0x00,0xAA,0x00,0xBA,0x32,0x58}};

const IID IID_IMTSLocater = {0xD19B8BFD,0x7F88,0x11D0,{0xB1,0x6E,0x00,0xAA,0x00,0xBA,0x32,0x58}};

const IID LIBID_MTSEVENTSLib = {0xBACEDF3E,0x74AB,0x11D0,{0xB1,0x62,0x00,0xAA,0x00,0xBA,0x32,0x58}};

const CLSID CLSID_CoMTSEvents = {0xBACEDF4E,0x74AB,0x11D0,{0xB1,0x62,0x00,0xAA,0x00,0xBA,0x32,0x58}};

const CLSID CLSID_EventMoniker = {0xC8CF32B4,0x7885,0x11D0,{0xB1,0x69,0x00,0xAA,0x00,0xBA,0x32,0x58}};

const CLSID CLSID_CoMTSLocater = {0xD19B8BFE,0x7F88,0x11D0,{0xB1,0x6E,0x00,0xAA,0x00,0xBA,0x32,0x58}};

#ifdef __cplusplus
}
#endif


MTSGRP.H

/* this ALWAYS GENERATED file contains the definitions for the interfaces */

/* File created by MIDL compiler version 3.01.75 */
/* at Fri Nov 14 05:07:23 1997
 */
/* Compiler settings for mtsgrp.idl:
    Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext
    error checks: none
*/
//@@MIDL_FILE_HEADING(  )
#include "rpc.h"
#include "rpcndr.h"
#ifndef COM_NO_WINDOWS_H
#include "windows.h"
#include "ole2.h"
#endif /*COM_NO_WINDOWS_H*/

#ifndef __mtsgrp_h__
#define __mtsgrp_h__

#ifdef __cplusplus
extern "C"{
#endif

/* Forward Declarations */

#ifndef __IMtsGrp_FWD_DEFINED__
#define __IMtsGrp_FWD_DEFINED__
typedef interface IMtsGrp IMtsGrp;
#endif /* __IMtsGrp_FWD_DEFINED__ */

#ifndef __MtsGrp_FWD_DEFINED__
#define __MtsGrp_FWD_DEFINED__

#ifdef __cplusplus
typedef class MtsGrp MtsGrp;
#else
typedef struct MtsGrp MtsGrp;
#endif /* __cplusplus */

#endif /* __MtsGrp_FWD_DEFINED__ */

/* header files for imported files */
#include "oaidl.h"
#include "ocidl.h"

void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t);
void __RPC_USER MIDL_user_free( void __RPC_FAR * );

#ifndef __IMtsGrp_INTERFACE_DEFINED__
#define __IMtsGrp_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IMtsGrp
 * at Fri Nov 14 05:07:23 1997
 * using MIDL 3.01.75
 ****************************************/
/* [unique][helpstring][dual][uuid][object] */

EXTERN_C const IID IID_IMtsGrp;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("4B2E958C-0393-11D1-B1AB-00AA00BA3258")
    IMtsGrp : public IDispatch
    {
    public:
        virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_Count(
            /* [retval][out] */ long __RPC_FAR *pVal) = 0;

        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE Item(
            /* [in] */ long lIndex,
            /* [out] */ IUnknown __RPC_FAR *__RPC_FAR *ppUnkDispatcher) = 0;

        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE Refresh( void) = 0;

    };

#else /* C style interface */

    typedef struct IMtsGrpVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            IMtsGrp __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            IMtsGrp __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            IMtsGrp __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfoCount )(
            IMtsGrp __RPC_FAR * This,
            /* [out] */ UINT __RPC_FAR *pctinfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfo )(
            IMtsGrp __RPC_FAR * This,
            /* [in] */ UINT iTInfo,
            /* [in] */ LCID lcid,
            /* [out] */ ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetIDsOfNames )(
            IMtsGrp __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [size_is][in] */ LPOLESTR __RPC_FAR *rgszNames,
            /* [in] */ UINT cNames,
            /* [in] */ LCID lcid,
            /* [size_is][out] */ DISPID __RPC_FAR *rgDispId);

        /* [local] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Invoke )(
            IMtsGrp __RPC_FAR * This,
            /* [in] */ DISPID dispIdMember,
            /* [in] */ REFIID riid,
            /* [in] */ LCID lcid,
            /* [in] */ WORD wFlags,
            /* [out][in] */ DISPPARAMS __RPC_FAR *pDispParams,
            /* [out] */ VARIANT __RPC_FAR *pVarResult,
            /* [out] */ EXCEPINFO __RPC_FAR *pExcepInfo,
            /* [out] */ UINT __RPC_FAR *puArgErr);

        /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_Count )(
            IMtsGrp __RPC_FAR * This,
            /* [retval][out] */ long __RPC_FAR *pVal);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Item )(
            IMtsGrp __RPC_FAR * This,
            /* [in] */ long lIndex,
            /* [out] */ IUnknown __RPC_FAR *__RPC_FAR *ppUnkDispatcher);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Refresh )(
            IMtsGrp __RPC_FAR * This);

        END_INTERFACE
    } IMtsGrpVtbl;

    interface IMtsGrp
    {
        CONST_VTBL struct IMtsGrpVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IMtsGrp_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IMtsGrp_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IMtsGrp_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IMtsGrp_GetTypeInfoCount(This,pctinfo)\
    (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo)

#define IMtsGrp_GetTypeInfo(This,iTInfo,lcid,ppTInfo)\
    (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo)

#define IMtsGrp_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)\
    (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)

#define IMtsGrp_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)\
    (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)

#define IMtsGrp_get_Count(This,pVal)\
    (This)->lpVtbl -> get_Count(This,pVal)

#define IMtsGrp_Item(This,lIndex,ppUnkDispatcher)\
    (This)->lpVtbl -> Item(This,lIndex,ppUnkDispatcher)

#define IMtsGrp_Refresh(This)\
    (This)->lpVtbl -> Refresh(This)

#endif /* COBJMACROS */

#endif /* C style interface */

/* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE IMtsGrp_get_Count_Proxy(
    IMtsGrp __RPC_FAR * This,
    /* [retval][out] */ long __RPC_FAR *pVal);

void __RPC_STUB IMtsGrp_get_Count_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE IMtsGrp_Item_Proxy(
    IMtsGrp __RPC_FAR * This,
    /* [in] */ long lIndex,
    /* [out] */ IUnknown __RPC_FAR *__RPC_FAR *ppUnkDispatcher);

void __RPC_STUB IMtsGrp_Item_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE IMtsGrp_Refresh_Proxy(
    IMtsGrp __RPC_FAR * This);

void __RPC_STUB IMtsGrp_Refresh_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IMtsGrp_INTERFACE_DEFINED__ */

#ifndef __MTSGRPLib_LIBRARY_DEFINED__
#define __MTSGRPLib_LIBRARY_DEFINED__

/****************************************
 * Generated header for library: MTSGRPLib
 * at Fri Nov 14 05:07:23 1997
 * using MIDL 3.01.75
 ****************************************/
/* [helpstring][version][uuid] */

EXTERN_C const IID LIBID_MTSGRPLib;

#ifdef __cplusplus
EXTERN_C const CLSID CLSID_MtsGrp;

class DECLSPEC_UUID("4B2E958D-0393-11D1-B1AB-00AA00BA3258")
MtsGrp;
#endif
#endif /* __MTSGRPLib_LIBRARY_DEFINED__ */

/* Additional Prototypes for ALL interfaces */

/* end of Additional Prototypes */

#ifdef __cplusplus
}
#endif

#endif


MTSGRP_I.C

/* this file contains the actual definitions of */
/* the IIDs and CLSIDs */

/* link this file in with the server and any clients */

/* File created by MIDL compiler version 3.01.75 */
/* at Fri Nov 14 05:07:23 1997
 */
/* Compiler settings for mtsgrp.idl:
    Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext
    error checks: none
*/
//@@MIDL_FILE_HEADING(  )
#ifdef __cplusplus
extern "C"{
#endif

#ifndef __IID_DEFINED__
#define __IID_DEFINED__

typedef struct _IID
{
    unsigned long x;
    unsigned short s1;
    unsigned short s2;
    unsigned char  c[8];
} IID;

#endif // __IID_DEFINED__

#ifndef CLSID_DEFINED
#define CLSID_DEFINED
typedef IID CLSID;
#endif // CLSID_DEFINED

const IID IID_IMtsGrp = {0x4B2E958C,0x0393,0x11D1,{0xB1,0xAB,0x00,0xAA,0x00,0xBA,0x32,0x58}};

const IID LIBID_MTSGRPLib = {0x4B2E957D,0x0393,0x11D1,{0xB1,0xAB,0x00,0xAA,0x00,0xBA,0x32,0x58}};

const CLSID CLSID_MtsGrp = {0x4B2E958D,0x0393,0x11D1,{0xB1,0xAB,0x00,0xAA,0x00,0xBA,0x32,0x58}};

#ifdef __cplusplus
}
#endif


MTSSPYCTL.IDL

#include <olectl.h>
// MtsSpyCtl.idl : IDL source for MtsSpyCtl.dll
//

// This file will be processed by the MIDL tool to
// produce the type library (MtsSpyCtl.tlb) and marshalling code.
cpp_quote ("#define DISPID_LOGFILE5")
cpp_quote ("#define DISPID_GRIDLINES6")
cpp_quote ("#define DISPID_COLWIDTH8")
cpp_quote ("#define DISPID_AUDIT17")

typedef enum {Package = 0,Thread,Instance,Transaction,Method,Object,Resource,User, Security} EventEnum;

import "oaidl.idl";
import "ocidl.idl";

[
object,
uuid(A9B39080-AAD0-11D0-B183-00AA00BA3258),
dual,
helpstring("IMtsSpy Interface"),
pointer_default(unique)
]
interface IMtsSpy : IDispatch
{
[propget, id(DISPID_HWND)]
HRESULT Window([out, retval]long* phwnd);
[propputref, id(DISPID_FONT)]
HRESULT Font([in]IFontDisp* pFont);
[propput, id(DISPID_FONT)]
HRESULT Font([in]IFontDisp* pFont);
[propget, id(DISPID_FONT)]
HRESULT Font([out, retval]IFontDisp** ppFont);
[propget, id(5), helpstring("property LogFile")] HRESULT LogFile([out, retval] BSTR *pVal);
[propput, id(5), helpstring("property LogFile")] HRESULT LogFile([in] BSTR newVal);
[propget, id(6), helpstring("property ShowGridLines")] HRESULT ShowGridLines([out, retval] BOOL *pVal);
[propput, id(6), helpstring("property ShowGridLines")] HRESULT ShowGridLines([in] BOOL newVal);
[propget, id(8), helpstring("property ColWidth")] HRESULT ColWidth(short nColumn, [out, retval] long *pVal);
[propput, id(8), helpstring("property ColWidth")] HRESULT ColWidth(short nColumn, [in] long newVal);
[id(9), helpstring("method SelectPackages")] HRESULT SelectPackages();
[id(10), helpstring("method SaveToFile")] HRESULT SaveToFile();
[id(11), helpstring("method ClearAllEvents")] HRESULT ClearAllEvents();
[id(12), helpstring("method About")] HRESULT About();
[propget, id(13), helpstring("property LogToFile")] HRESULT LogToFile([out, retval] BOOL *pVal);
[propput, id(13), helpstring("property LogToFile")] HRESULT LogToFile([in] BOOL newVal);
[id(14), helpstring("method ChooseFont")] HRESULT ChooseFont();
[propget, id(15), helpstring("property ShowOnScreen")] HRESULT ShowOnScreen([out, retval] BOOL *pVal);
[propput, id(15), helpstring("property ShowOnScreen")] HRESULT ShowOnScreen([in] BOOL newVal);
[id(16), helpstring("method ChooseLogFile")] HRESULT ChooseLogFile([out] BSTR * sLogFileName, [out] BOOL * bCanceled);
[propget, id(17), helpstring("property Audit")] HRESULT Audit([out, retval] BOOL *pVal);
[propput, id(17), helpstring("property Audit")] HRESULT Audit([in] BOOL newVal);

};
[

uuid(32E1C9D5-7A1E-11D0-B16D-00AA00BA3258),

helpstring("IMtsSink Interface"),
pointer_default(unique)
]
interface IMtsSink : IUnknown
{
[helpstring("Init")] HRESULT Init(IConnectionPointContainer * pConPt, LPWSTR sDisplayName);
[helpstring("method Advise")] HRESULT Advise();
[helpstring("method DAdvise")] HRESULT DAdvise();
[helpstring("method GetSinkType")] HRESULT GetSinkType([out] EventEnum * e);
[propget, helpstring("property DeleteFlag")] HRESULT DeleteFlag([out, retval] BOOL *pVal);
[propput, helpstring("property DeleteFlag")] HRESULT DeleteFlag([in] BOOL newVal);

};
[
uuid(A9B39071-AAD0-11D0-B183-00AA00BA3258),
version(1.0),
helpstring("MtsSpyCtl 1.0 Type Library")
]
library MTSSPYCTLLib
{
importlib("stdole2.tlb");

[
uuid(A9B39081-AAD0-11D0-B183-00AA00BA3258),
helpstring("MtsSpy ActiveX Control")
]
coclass MtsSpy
{
[default] interface IMtsSpy;
};

[
uuid(AAF6270D-AC5E-11D0-B185-00AA00BA3258),
helpstring("MtsSpyPropPage Class")
]
coclass MtsSpyPropPage
{
interface IUnknown;
};
};


MTSUSEREVENTOBJ.IDL

/******************************************************************************\
********************************************************************************
* Filename: MtsUserEventObj.idl
*
* Description:
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
// MtsUserEventObj.idl : IDL source for MtsUserEventObj.dll
//

// This file will be processed by the MIDL tool to
// produce the type library (MtsUserEventObj.tlb) and marshalling code.

import "oaidl.idl";
import "ocidl.idl";

[
object,
uuid(F523E424-8485-11D0-B170-00AA00BA3258),
dual,
helpstring("IMtsUserEventData Interface"),
pointer_default(unique)
]
interface IMtsUserEventData : IDispatch
{
[propput, id(1), helpstring("property DisplayName")] HRESULT DisplayName([in] BSTR newVal);
[propput, id(2), helpstring("property GuidEventId")] HRESULT GuidEventId([in] BSTR newVal);
[propput, id(3), helpstring("property Value")] HRESULT Value(BSTR sKey, [in] VARIANT * newVal);
};
[
uuid(F523E415-8485-11D0-B170-00AA00BA3258),
version(1.0),
helpstring("MtsUserEventObj 1.0 Type Library")
]
library MTSUSEREVENTOBJLib
{
importlib("stdole2.tlb");

[
uuid(F523E425-8485-11D0-B170-00AA00BA3258),
helpstring("MtsUserEventData Class")
]
coclass MtsUserEventData
{
[default] interface IMtsUserEventData;
};
};


MTXADMIN.H

/* this ALWAYS GENERATED file contains the definitions for the interfaces */

/* File created by MIDL compiler version 3.01.75 */
/* at Fri Nov 14 05:07:21 1997
 */
/* Compiler settings for mtxadmin.idl:
    Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext
    error checks: none
*/
//@@MIDL_FILE_HEADING(  )
#include "rpc.h"
#include "rpcndr.h"
#ifndef COM_NO_WINDOWS_H
#include "windows.h"
#include "ole2.h"
#endif /*COM_NO_WINDOWS_H*/

#ifndef __mtxadmin_h__
#define __mtxadmin_h__

#ifdef __cplusplus
extern "C"{
#endif

/* Forward Declarations */

#ifndef __ICatalog_FWD_DEFINED__
#define __ICatalog_FWD_DEFINED__
typedef interface ICatalog ICatalog;
#endif /* __ICatalog_FWD_DEFINED__ */

#ifndef __ICatalogObject_FWD_DEFINED__
#define __ICatalogObject_FWD_DEFINED__
typedef interface ICatalogObject ICatalogObject;
#endif /* __ICatalogObject_FWD_DEFINED__ */

#ifndef __ICatalogCollection_FWD_DEFINED__
#define __ICatalogCollection_FWD_DEFINED__
typedef interface ICatalogCollection ICatalogCollection;
#endif /* __ICatalogCollection_FWD_DEFINED__ */

#ifndef __IComponentUtil_FWD_DEFINED__
#define __IComponentUtil_FWD_DEFINED__
typedef interface IComponentUtil IComponentUtil;
#endif /* __IComponentUtil_FWD_DEFINED__ */

#ifndef __IPackageUtil_FWD_DEFINED__
#define __IPackageUtil_FWD_DEFINED__
typedef interface IPackageUtil IPackageUtil;
#endif /* __IPackageUtil_FWD_DEFINED__ */

#ifndef __IRemoteComponentUtil_FWD_DEFINED__
#define __IRemoteComponentUtil_FWD_DEFINED__
typedef interface IRemoteComponentUtil IRemoteComponentUtil;
#endif /* __IRemoteComponentUtil_FWD_DEFINED__ */

#ifndef __IRoleAssociationUtil_FWD_DEFINED__
#define __IRoleAssociationUtil_FWD_DEFINED__
typedef interface IRoleAssociationUtil IRoleAssociationUtil;
#endif /* __IRoleAssociationUtil_FWD_DEFINED__ */

#ifndef __Catalog_FWD_DEFINED__
#define __Catalog_FWD_DEFINED__

#ifdef __cplusplus
typedef class Catalog Catalog;
#else
typedef struct Catalog Catalog;
#endif /* __cplusplus */

#endif /* __Catalog_FWD_DEFINED__ */

#ifndef __CatalogObject_FWD_DEFINED__
#define __CatalogObject_FWD_DEFINED__

#ifdef __cplusplus
typedef class CatalogObject CatalogObject;
#else
typedef struct CatalogObject CatalogObject;
#endif /* __cplusplus */

#endif /* __CatalogObject_FWD_DEFINED__ */

#ifndef __CatalogCollection_FWD_DEFINED__
#define __CatalogCollection_FWD_DEFINED__

#ifdef __cplusplus
typedef class CatalogCollection CatalogCollection;
#else
typedef struct CatalogCollection CatalogCollection;
#endif /* __cplusplus */

#endif /* __CatalogCollection_FWD_DEFINED__ */

#ifndef __ComponentUtil_FWD_DEFINED__
#define __ComponentUtil_FWD_DEFINED__

#ifdef __cplusplus
typedef class ComponentUtil ComponentUtil;
#else
typedef struct ComponentUtil ComponentUtil;
#endif /* __cplusplus */

#endif /* __ComponentUtil_FWD_DEFINED__ */

#ifndef __PackageUtil_FWD_DEFINED__
#define __PackageUtil_FWD_DEFINED__

#ifdef __cplusplus
typedef class PackageUtil PackageUtil;
#else
typedef struct PackageUtil PackageUtil;
#endif /* __cplusplus */

#endif /* __PackageUtil_FWD_DEFINED__ */

#ifndef __RemoteComponentUtil_FWD_DEFINED__
#define __RemoteComponentUtil_FWD_DEFINED__

#ifdef __cplusplus
typedef class RemoteComponentUtil RemoteComponentUtil;
#else
typedef struct RemoteComponentUtil RemoteComponentUtil;
#endif /* __cplusplus */

#endif /* __RemoteComponentUtil_FWD_DEFINED__ */

#ifndef __RoleAssociationUtil_FWD_DEFINED__
#define __RoleAssociationUtil_FWD_DEFINED__

#ifdef __cplusplus
typedef class RoleAssociationUtil RoleAssociationUtil;
#else
typedef struct RoleAssociationUtil RoleAssociationUtil;
#endif /* __cplusplus */

#endif /* __RoleAssociationUtil_FWD_DEFINED__ */

/* header files for imported files */
#include "unknwn.h"
#include "oaidl.h"

void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t);
void __RPC_USER MIDL_user_free( void __RPC_FAR * );

/****************************************
 * Generated header for interface: __MIDL_itf_mtxadmin_0000
 * at Fri Nov 14 05:07:21 1997
 * using MIDL 3.01.75
 ****************************************/
/* [local] */

// -----------------------------------------------------------------------
// mtxadmin.h  -- Microsoft Transaction Server Programming Interfaces
//
// This file provides the prototypes for the APIs and COM interfaces
// used by Microsoft Transaction Server applications.
//
// Microsoft Transaction Server SDK
// Copyright (c) 1997 Microsoft Corporation.  All Rights Reserved.
// -----------------------------------------------------------------------
#include <objbase.h>
#ifndef DECLSPEC_UUID
#if _MSC_VER >= 1100
#define DECLSPEC_UUID(x)    __declspec(uuid(x))
#else
#define DECLSPEC_UUID(x)
#endif
#endif

extern RPC_IF_HANDLE __MIDL_itf_mtxadmin_0000_v0_0_c_ifspec;
extern RPC_IF_HANDLE __MIDL_itf_mtxadmin_0000_v0_0_s_ifspec;

#ifndef __ICatalog_INTERFACE_DEFINED__
#define __ICatalog_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: ICatalog
 * at Fri Nov 14 05:07:21 1997
 * using MIDL 3.01.75
 ****************************************/
/* [unique][helpstring][dual][uuid][object] */

EXTERN_C const IID IID_ICatalog;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("6eb22870-8a19-11d0-81b6-00a0c9231c29")
    ICatalog : public IDispatch
    {
    public:
        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE GetCollection(
            /* [in] */ BSTR bstrCollName,
            /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppCatalogCollection) = 0;

        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE Connect(
            /* [in] */ BSTR bstrConnectString,
            /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppCatalogCollection) = 0;

        virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_MajorVersion(
            /* [retval][out] */ long __RPC_FAR *retval) = 0;

        virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_MinorVersion(
            /* [retval][out] */ long __RPC_FAR *retval) = 0;

    };

#else /* C style interface */

    typedef struct ICatalogVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            ICatalog __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            ICatalog __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            ICatalog __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfoCount )(
            ICatalog __RPC_FAR * This,
            /* [out] */ UINT __RPC_FAR *pctinfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfo )(
            ICatalog __RPC_FAR * This,
            /* [in] */ UINT iTInfo,
            /* [in] */ LCID lcid,
            /* [out] */ ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetIDsOfNames )(
            ICatalog __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [size_is][in] */ LPOLESTR __RPC_FAR *rgszNames,
            /* [in] */ UINT cNames,
            /* [in] */ LCID lcid,
            /* [size_is][out] */ DISPID __RPC_FAR *rgDispId);

        /* [local] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Invoke )(
            ICatalog __RPC_FAR * This,
            /* [in] */ DISPID dispIdMember,
            /* [in] */ REFIID riid,
            /* [in] */ LCID lcid,
            /* [in] */ WORD wFlags,
            /* [out][in] */ DISPPARAMS __RPC_FAR *pDispParams,
            /* [out] */ VARIANT __RPC_FAR *pVarResult,
            /* [out] */ EXCEPINFO __RPC_FAR *pExcepInfo,
            /* [out] */ UINT __RPC_FAR *puArgErr);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetCollection )(
            ICatalog __RPC_FAR * This,
            /* [in] */ BSTR bstrCollName,
            /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppCatalogCollection);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Connect )(
            ICatalog __RPC_FAR * This,
            /* [in] */ BSTR bstrConnectString,
            /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppCatalogCollection);

        /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_MajorVersion )(
            ICatalog __RPC_FAR * This,
            /* [retval][out] */ long __RPC_FAR *retval);

        /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_MinorVersion )(
            ICatalog __RPC_FAR * This,
            /* [retval][out] */ long __RPC_FAR *retval);

        END_INTERFACE
    } ICatalogVtbl;

    interface ICatalog
    {
        CONST_VTBL struct ICatalogVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define ICatalog_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define ICatalog_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define ICatalog_Release(This)\
    (This)->lpVtbl -> Release(This)

#define ICatalog_GetTypeInfoCount(This,pctinfo)\
    (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo)

#define ICatalog_GetTypeInfo(This,iTInfo,lcid,ppTInfo)\
    (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo)

#define ICatalog_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)\
    (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)

#define ICatalog_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)\
    (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)

#define ICatalog_GetCollection(This,bstrCollName,ppCatalogCollection)\
    (This)->lpVtbl -> GetCollection(This,bstrCollName,ppCatalogCollection)

#define ICatalog_Connect(This,bstrConnectString,ppCatalogCollection)\
    (This)->lpVtbl -> Connect(This,bstrConnectString,ppCatalogCollection)

#define ICatalog_get_MajorVersion(This,retval)\
    (This)->lpVtbl -> get_MajorVersion(This,retval)

#define ICatalog_get_MinorVersion(This,retval)\
    (This)->lpVtbl -> get_MinorVersion(This,retval)

#endif /* COBJMACROS */

#endif /* C style interface */

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE ICatalog_GetCollection_Proxy(
    ICatalog __RPC_FAR * This,
    /* [in] */ BSTR bstrCollName,
    /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppCatalogCollection);

void __RPC_STUB ICatalog_GetCollection_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE ICatalog_Connect_Proxy(
    ICatalog __RPC_FAR * This,
    /* [in] */ BSTR bstrConnectString,
    /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppCatalogCollection);

void __RPC_STUB ICatalog_Connect_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE ICatalog_get_MajorVersion_Proxy(
    ICatalog __RPC_FAR * This,
    /* [retval][out] */ long __RPC_FAR *retval);

void __RPC_STUB ICatalog_get_MajorVersion_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE ICatalog_get_MinorVersion_Proxy(
    ICatalog __RPC_FAR * This,
    /* [retval][out] */ long __RPC_FAR *retval);

void __RPC_STUB ICatalog_get_MinorVersion_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __ICatalog_INTERFACE_DEFINED__ */

#ifndef __ICatalogObject_INTERFACE_DEFINED__
#define __ICatalogObject_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: ICatalogObject
 * at Fri Nov 14 05:07:21 1997
 * using MIDL 3.01.75
 ****************************************/
/* [unique][helpstring][dual][uuid][object] */

EXTERN_C const IID IID_ICatalogObject;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("6eb22871-8a19-11d0-81b6-00a0c9231c29")
    ICatalogObject : public IDispatch
    {
    public:
        virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_Value(
            /* [in] */ BSTR bstrPropName,
            /* [retval][out] */ VARIANT __RPC_FAR *retval) = 0;

        virtual /* [helpstring][id][propput] */ HRESULT STDMETHODCALLTYPE put_Value(
            /* [in] */ BSTR bstrPropName,
            /* [in] */ VARIANT val) = 0;

        virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_Key(
            /* [retval][out] */ VARIANT __RPC_FAR *retval) = 0;

        virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_Name(
            /* [retval][out] */ VARIANT __RPC_FAR *retval) = 0;

        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE IsPropertyReadOnly(
            /* [in] */ BSTR bstrPropName,
            /* [retval][out] */ VARIANT_BOOL __RPC_FAR *retval) = 0;

        virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_Valid(
            /* [retval][out] */ VARIANT_BOOL __RPC_FAR *retval) = 0;

        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE IsPropertyWriteOnly(
            /* [in] */ BSTR bstrPropName,
            /* [retval][out] */ VARIANT_BOOL __RPC_FAR *retval) = 0;

    };

#else /* C style interface */

    typedef struct ICatalogObjectVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            ICatalogObject __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            ICatalogObject __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            ICatalogObject __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfoCount )(
            ICatalogObject __RPC_FAR * This,
            /* [out] */ UINT __RPC_FAR *pctinfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfo )(
            ICatalogObject __RPC_FAR * This,
            /* [in] */ UINT iTInfo,
            /* [in] */ LCID lcid,
            /* [out] */ ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetIDsOfNames )(
            ICatalogObject __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [size_is][in] */ LPOLESTR __RPC_FAR *rgszNames,
            /* [in] */ UINT cNames,
            /* [in] */ LCID lcid,
            /* [size_is][out] */ DISPID __RPC_FAR *rgDispId);

        /* [local] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Invoke )(
            ICatalogObject __RPC_FAR * This,
            /* [in] */ DISPID dispIdMember,
            /* [in] */ REFIID riid,
            /* [in] */ LCID lcid,
            /* [in] */ WORD wFlags,
            /* [out][in] */ DISPPARAMS __RPC_FAR *pDispParams,
            /* [out] */ VARIANT __RPC_FAR *pVarResult,
            /* [out] */ EXCEPINFO __RPC_FAR *pExcepInfo,
            /* [out] */ UINT __RPC_FAR *puArgErr);

        /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_Value )(
            ICatalogObject __RPC_FAR * This,
            /* [in] */ BSTR bstrPropName,
            /* [retval][out] */ VARIANT __RPC_FAR *retval);

        /* [helpstring][id][propput] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *put_Value )(
            ICatalogObject __RPC_FAR * This,
            /* [in] */ BSTR bstrPropName,
            /* [in] */ VARIANT val);

        /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_Key )(
            ICatalogObject __RPC_FAR * This,
            /* [retval][out] */ VARIANT __RPC_FAR *retval);

        /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_Name )(
            ICatalogObject __RPC_FAR * This,
            /* [retval][out] */ VARIANT __RPC_FAR *retval);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *IsPropertyReadOnly )(
            ICatalogObject __RPC_FAR * This,
            /* [in] */ BSTR bstrPropName,
            /* [retval][out] */ VARIANT_BOOL __RPC_FAR *retval);

        /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_Valid )(
            ICatalogObject __RPC_FAR * This,
            /* [retval][out] */ VARIANT_BOOL __RPC_FAR *retval);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *IsPropertyWriteOnly )(
            ICatalogObject __RPC_FAR * This,
            /* [in] */ BSTR bstrPropName,
            /* [retval][out] */ VARIANT_BOOL __RPC_FAR *retval);

        END_INTERFACE
    } ICatalogObjectVtbl;

    interface ICatalogObject
    {
        CONST_VTBL struct ICatalogObjectVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define ICatalogObject_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define ICatalogObject_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define ICatalogObject_Release(This)\
    (This)->lpVtbl -> Release(This)

#define ICatalogObject_GetTypeInfoCount(This,pctinfo)\
    (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo)

#define ICatalogObject_GetTypeInfo(This,iTInfo,lcid,ppTInfo)\
    (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo)

#define ICatalogObject_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)\
    (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)

#define ICatalogObject_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)\
    (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)

#define ICatalogObject_get_Value(This,bstrPropName,retval)\
    (This)->lpVtbl -> get_Value(This,bstrPropName,retval)

#define ICatalogObject_put_Value(This,bstrPropName,val)\
    (This)->lpVtbl -> put_Value(This,bstrPropName,val)

#define ICatalogObject_get_Key(This,retval)\
    (This)->lpVtbl -> get_Key(This,retval)

#define ICatalogObject_get_Name(This,retval)\
    (This)->lpVtbl -> get_Name(This,retval)

#define ICatalogObject_IsPropertyReadOnly(This,bstrPropName,retval)\
    (This)->lpVtbl -> IsPropertyReadOnly(This,bstrPropName,retval)

#define ICatalogObject_get_Valid(This,retval)\
    (This)->lpVtbl -> get_Valid(This,retval)

#define ICatalogObject_IsPropertyWriteOnly(This,bstrPropName,retval)\
    (This)->lpVtbl -> IsPropertyWriteOnly(This,bstrPropName,retval)

#endif /* COBJMACROS */

#endif /* C style interface */

/* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE ICatalogObject_get_Value_Proxy(
    ICatalogObject __RPC_FAR * This,
    /* [in] */ BSTR bstrPropName,
    /* [retval][out] */ VARIANT __RPC_FAR *retval);

void __RPC_STUB ICatalogObject_get_Value_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id][propput] */ HRESULT STDMETHODCALLTYPE ICatalogObject_put_Value_Proxy(
    ICatalogObject __RPC_FAR * This,
    /* [in] */ BSTR bstrPropName,
    /* [in] */ VARIANT val);

void __RPC_STUB ICatalogObject_put_Value_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE ICatalogObject_get_Key_Proxy(
    ICatalogObject __RPC_FAR * This,
    /* [retval][out] */ VARIANT __RPC_FAR *retval);

void __RPC_STUB ICatalogObject_get_Key_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE ICatalogObject_get_Name_Proxy(
    ICatalogObject __RPC_FAR * This,
    /* [retval][out] */ VARIANT __RPC_FAR *retval);

void __RPC_STUB ICatalogObject_get_Name_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE ICatalogObject_IsPropertyReadOnly_Proxy(
    ICatalogObject __RPC_FAR * This,
    /* [in] */ BSTR bstrPropName,
    /* [retval][out] */ VARIANT_BOOL __RPC_FAR *retval);

void __RPC_STUB ICatalogObject_IsPropertyReadOnly_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE ICatalogObject_get_Valid_Proxy(
    ICatalogObject __RPC_FAR * This,
    /* [retval][out] */ VARIANT_BOOL __RPC_FAR *retval);

void __RPC_STUB ICatalogObject_get_Valid_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE ICatalogObject_IsPropertyWriteOnly_Proxy(
    ICatalogObject __RPC_FAR * This,
    /* [in] */ BSTR bstrPropName,
    /* [retval][out] */ VARIANT_BOOL __RPC_FAR *retval);

void __RPC_STUB ICatalogObject_IsPropertyWriteOnly_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __ICatalogObject_INTERFACE_DEFINED__ */

#ifndef __ICatalogCollection_INTERFACE_DEFINED__
#define __ICatalogCollection_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: ICatalogCollection
 * at Fri Nov 14 05:07:21 1997
 * using MIDL 3.01.75
 ****************************************/
/* [unique][helpstring][dual][uuid][object] */

EXTERN_C const IID IID_ICatalogCollection;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("6eb22872-8a19-11d0-81b6-00a0c9231c29")
    ICatalogCollection : public IDispatch
    {
    public:
        virtual /* [id][restricted][propget] */ HRESULT STDMETHODCALLTYPE get__NewEnum(
            /* [retval][out] */ IUnknown __RPC_FAR *__RPC_FAR *ppEnumVariant) = 0;

        virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_Item(
            /* [in] */ long lIndex,
            /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppCatalogObject) = 0;

        virtual /* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE get_Count(
            /* [retval][out] */ long __RPC_FAR *retval) = 0;

        virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Remove(
            /* [in] */ long lIndex) = 0;

        virtual /* [helpstring] */ HRESULT STDMETHODCALLTYPE Add(
            /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppCatalogObject) = 0;

        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE Populate( void) = 0;

        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE SaveChanges(
            /* [retval][out] */ long __RPC_FAR *retval) = 0;

        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE GetCollection(
            /* [in] */ BSTR bstrCollName,
            /* [in] */ VARIANT varObjectKey,
            /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppCatalogCollection) = 0;

        virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_Name(
            /* [retval][out] */ VARIANT __RPC_FAR *retval) = 0;

        virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_AddEnabled(
            /* [retval][out] */ VARIANT_BOOL __RPC_FAR *retval) = 0;

        virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_RemoveEnabled(
            /* [retval][out] */ VARIANT_BOOL __RPC_FAR *retval) = 0;

        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE GetUtilInterface(
            /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppUtil) = 0;

        virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_DataStoreMajorVersion(
            /* [retval][out] */ long __RPC_FAR *retval) = 0;

        virtual /* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE get_DataStoreMinorVersion(
            /* [retval][out] */ long __RPC_FAR *retval) = 0;

        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE PopulateByKey(
            /* [in] */ SAFEARRAY __RPC_FAR * aKeys) = 0;

        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE PopulateByQuery(
            /* [in] */ BSTR bstrQueryString,
            /* [in] */ long lQueryType) = 0;

    };

#else /* C style interface */

    typedef struct ICatalogCollectionVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            ICatalogCollection __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            ICatalogCollection __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            ICatalogCollection __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfoCount )(
            ICatalogCollection __RPC_FAR * This,
            /* [out] */ UINT __RPC_FAR *pctinfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfo )(
            ICatalogCollection __RPC_FAR * This,
            /* [in] */ UINT iTInfo,
            /* [in] */ LCID lcid,
            /* [out] */ ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetIDsOfNames )(
            ICatalogCollection __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [size_is][in] */ LPOLESTR __RPC_FAR *rgszNames,
            /* [in] */ UINT cNames,
            /* [in] */ LCID lcid,
            /* [size_is][out] */ DISPID __RPC_FAR *rgDispId);

        /* [local] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Invoke )(
            ICatalogCollection __RPC_FAR * This,
            /* [in] */ DISPID dispIdMember,
            /* [in] */ REFIID riid,
            /* [in] */ LCID lcid,
            /* [in] */ WORD wFlags,
            /* [out][in] */ DISPPARAMS __RPC_FAR *pDispParams,
            /* [out] */ VARIANT __RPC_FAR *pVarResult,
            /* [out] */ EXCEPINFO __RPC_FAR *pExcepInfo,
            /* [out] */ UINT __RPC_FAR *puArgErr);

        /* [id][restricted][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get__NewEnum )(
            ICatalogCollection __RPC_FAR * This,
            /* [retval][out] */ IUnknown __RPC_FAR *__RPC_FAR *ppEnumVariant);

        /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_Item )(
            ICatalogCollection __RPC_FAR * This,
            /* [in] */ long lIndex,
            /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppCatalogObject);

        /* [helpstring][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_Count )(
            ICatalogCollection __RPC_FAR * This,
            /* [retval][out] */ long __RPC_FAR *retval);

        /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Remove )(
            ICatalogCollection __RPC_FAR * This,
            /* [in] */ long lIndex);

        /* [helpstring] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Add )(
            ICatalogCollection __RPC_FAR * This,
            /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppCatalogObject);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Populate )(
            ICatalogCollection __RPC_FAR * This);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *SaveChanges )(
            ICatalogCollection __RPC_FAR * This,
            /* [retval][out] */ long __RPC_FAR *retval);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetCollection )(
            ICatalogCollection __RPC_FAR * This,
            /* [in] */ BSTR bstrCollName,

/* [in] */ VARIANT varObjectKey,
            /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppCatalogCollection);

        /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_Name )(
            ICatalogCollection __RPC_FAR * This,
            /* [retval][out] */ VARIANT __RPC_FAR *retval);

        /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_AddEnabled )(
            ICatalogCollection __RPC_FAR * This,
            /* [retval][out] */ VARIANT_BOOL __RPC_FAR *retval);

        /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_RemoveEnabled )(
            ICatalogCollection __RPC_FAR * This,
            /* [retval][out] */ VARIANT_BOOL __RPC_FAR *retval);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetUtilInterface )(
            ICatalogCollection __RPC_FAR * This,
            /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppUtil);

        /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_DataStoreMajorVersion )(
            ICatalogCollection __RPC_FAR * This,
            /* [retval][out] */ long __RPC_FAR *retval);

        /* [helpstring][id][propget] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *get_DataStoreMinorVersion )(
            ICatalogCollection __RPC_FAR * This,
            /* [retval][out] */ long __RPC_FAR *retval);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *PopulateByKey )(
            ICatalogCollection __RPC_FAR * This,
            /* [in] */ SAFEARRAY __RPC_FAR * aKeys);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *PopulateByQuery )(
            ICatalogCollection __RPC_FAR * This,
            /* [in] */ BSTR bstrQueryString,
            /* [in] */ long lQueryType);

        END_INTERFACE
    } ICatalogCollectionVtbl;

    interface ICatalogCollection
    {
        CONST_VTBL struct ICatalogCollectionVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define ICatalogCollection_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define ICatalogCollection_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define ICatalogCollection_Release(This)\
    (This)->lpVtbl -> Release(This)

#define ICatalogCollection_GetTypeInfoCount(This,pctinfo)\
    (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo)

#define ICatalogCollection_GetTypeInfo(This,iTInfo,lcid,ppTInfo)\
    (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo)

#define ICatalogCollection_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)\
    (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)

#define ICatalogCollection_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)\
    (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)

#define ICatalogCollection_get__NewEnum(This,ppEnumVariant)\
    (This)->lpVtbl -> get__NewEnum(This,ppEnumVariant)

#define ICatalogCollection_get_Item(This,lIndex,ppCatalogObject)\
    (This)->lpVtbl -> get_Item(This,lIndex,ppCatalogObject)

#define ICatalogCollection_get_Count(This,retval)\
    (This)->lpVtbl -> get_Count(This,retval)

#define ICatalogCollection_Remove(This,lIndex)\
    (This)->lpVtbl -> Remove(This,lIndex)

#define ICatalogCollection_Add(This,ppCatalogObject)\
    (This)->lpVtbl -> Add(This,ppCatalogObject)

#define ICatalogCollection_Populate(This)\
    (This)->lpVtbl -> Populate(This)

#define ICatalogCollection_SaveChanges(This,retval)\
    (This)->lpVtbl -> SaveChanges(This,retval)

#define ICatalogCollection_GetCollection(This,bstrCollName,varObjectKey,ppCatalogCollection)\
    (This)->lpVtbl -> GetCollection(This,bstrCollName,varObjectKey,ppCatalogCollection)

#define ICatalogCollection_get_Name(This,retval)\
    (This)->lpVtbl -> get_Name(This,retval)

#define ICatalogCollection_get_AddEnabled(This,retval)\
    (This)->lpVtbl -> get_AddEnabled(This,retval)

#define ICatalogCollection_get_RemoveEnabled(This,retval)\
    (This)->lpVtbl -> get_RemoveEnabled(This,retval)

#define ICatalogCollection_GetUtilInterface(This,ppUtil)\
    (This)->lpVtbl -> GetUtilInterface(This,ppUtil)

#define ICatalogCollection_get_DataStoreMajorVersion(This,retval)\
    (This)->lpVtbl -> get_DataStoreMajorVersion(This,retval)

#define ICatalogCollection_get_DataStoreMinorVersion(This,retval)\
    (This)->lpVtbl -> get_DataStoreMinorVersion(This,retval)

#define ICatalogCollection_PopulateByKey(This,aKeys)\
    (This)->lpVtbl -> PopulateByKey(This,aKeys)

#define ICatalogCollection_PopulateByQuery(This,bstrQueryString,lQueryType)\
    (This)->lpVtbl -> PopulateByQuery(This,bstrQueryString,lQueryType)

#endif /* COBJMACROS */

#endif /* C style interface */

/* [id][restricted][propget] */ HRESULT STDMETHODCALLTYPE ICatalogCollection_get__NewEnum_Proxy(
    ICatalogCollection __RPC_FAR * This,
    /* [retval][out] */ IUnknown __RPC_FAR *__RPC_FAR *ppEnumVariant);

void __RPC_STUB ICatalogCollection_get__NewEnum_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE ICatalogCollection_get_Item_Proxy(
    ICatalogCollection __RPC_FAR * This,
    /* [in] */ long lIndex,
    /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppCatalogObject);

void __RPC_STUB ICatalogCollection_get_Item_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][propget] */ HRESULT STDMETHODCALLTYPE ICatalogCollection_get_Count_Proxy(
    ICatalogCollection __RPC_FAR * This,
    /* [retval][out] */ long __RPC_FAR *retval);

void __RPC_STUB ICatalogCollection_get_Count_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring] */ HRESULT STDMETHODCALLTYPE ICatalogCollection_Remove_Proxy(
    ICatalogCollection __RPC_FAR * This,
    /* [in] */ long lIndex);

void __RPC_STUB ICatalogCollection_Remove_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring] */ HRESULT STDMETHODCALLTYPE ICatalogCollection_Add_Proxy(
    ICatalogCollection __RPC_FAR * This,
    /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppCatalogObject);

void __RPC_STUB ICatalogCollection_Add_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE ICatalogCollection_Populate_Proxy(
    ICatalogCollection __RPC_FAR * This);

void __RPC_STUB ICatalogCollection_Populate_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE ICatalogCollection_SaveChanges_Proxy(
    ICatalogCollection __RPC_FAR * This,
    /* [retval][out] */ long __RPC_FAR *retval);

void __RPC_STUB ICatalogCollection_SaveChanges_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE ICatalogCollection_GetCollection_Proxy(
    ICatalogCollection __RPC_FAR * This,
    /* [in] */ BSTR bstrCollName,
    /* [in] */ VARIANT varObjectKey,
    /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppCatalogCollection);

void __RPC_STUB ICatalogCollection_GetCollection_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE ICatalogCollection_get_Name_Proxy(
    ICatalogCollection __RPC_FAR * This,
    /* [retval][out] */ VARIANT __RPC_FAR *retval);

void __RPC_STUB ICatalogCollection_get_Name_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE ICatalogCollection_get_AddEnabled_Proxy(
    ICatalogCollection __RPC_FAR * This,
    /* [retval][out] */ VARIANT_BOOL __RPC_FAR *retval);

void __RPC_STUB ICatalogCollection_get_AddEnabled_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE ICatalogCollection_get_RemoveEnabled_Proxy(
    ICatalogCollection __RPC_FAR * This,
    /* [retval][out] */ VARIANT_BOOL __RPC_FAR *retval);

void __RPC_STUB ICatalogCollection_get_RemoveEnabled_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE ICatalogCollection_GetUtilInterface_Proxy(
    ICatalogCollection __RPC_FAR * This,
    /* [retval][out] */ IDispatch __RPC_FAR *__RPC_FAR *ppUtil);

void __RPC_STUB ICatalogCollection_GetUtilInterface_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE ICatalogCollection_get_DataStoreMajorVersion_Proxy(
    ICatalogCollection __RPC_FAR * This,
    /* [retval][out] */ long __RPC_FAR *retval);

void __RPC_STUB ICatalogCollection_get_DataStoreMajorVersion_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id][propget] */ HRESULT STDMETHODCALLTYPE ICatalogCollection_get_DataStoreMinorVersion_Proxy(
    ICatalogCollection __RPC_FAR * This,
    /* [retval][out] */ long __RPC_FAR *retval);

void __RPC_STUB ICatalogCollection_get_DataStoreMinorVersion_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE ICatalogCollection_PopulateByKey_Proxy(
    ICatalogCollection __RPC_FAR * This,
    /* [in] */ SAFEARRAY __RPC_FAR * aKeys);

void __RPC_STUB ICatalogCollection_PopulateByKey_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE ICatalogCollection_PopulateByQuery_Proxy(
    ICatalogCollection __RPC_FAR * This,
    /* [in] */ BSTR bstrQueryString,
    /* [in] */ long lQueryType);

void __RPC_STUB ICatalogCollection_PopulateByQuery_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __ICatalogCollection_INTERFACE_DEFINED__ */

#ifndef __IComponentUtil_INTERFACE_DEFINED__
#define __IComponentUtil_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IComponentUtil
 * at Fri Nov 14 05:07:21 1997
 * using MIDL 3.01.75
 ****************************************/
/* [unique][helpstring][dual][uuid][object] */

EXTERN_C const IID IID_IComponentUtil;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("6eb22873-8a19-11d0-81b6-00a0c9231c29")
    IComponentUtil : public IDispatch
    {
    public:
        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE InstallComponent(
            /* [in] */ BSTR bstrDLLFile,
            /* [in] */ BSTR bstrTypelibFile,
            /* [in] */ BSTR bstrProxyStubDLLFile) = 0;

        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE ImportComponent(
            /* [in] */ BSTR bstrCLSID) = 0;

        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE ImportComponentByName(
            /* [in] */ BSTR bstrProgID) = 0;

        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE GetCLSIDs(
            /* [in] */ BSTR bstrDLLFile,
            /* [in] */ BSTR bstrTypelibFile,
            /* [out] */ SAFEARRAY __RPC_FAR * __RPC_FAR *aCLSIDs) = 0;

    };

#else /* C style interface */

    typedef struct IComponentUtilVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            IComponentUtil __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            IComponentUtil __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            IComponentUtil __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfoCount )(
            IComponentUtil __RPC_FAR * This,
            /* [out] */ UINT __RPC_FAR *pctinfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfo )(
            IComponentUtil __RPC_FAR * This,
            /* [in] */ UINT iTInfo,
            /* [in] */ LCID lcid,
            /* [out] */ ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetIDsOfNames )(
            IComponentUtil __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [size_is][in] */ LPOLESTR __RPC_FAR *rgszNames,
            /* [in] */ UINT cNames,
            /* [in] */ LCID lcid,
            /* [size_is][out] */ DISPID __RPC_FAR *rgDispId);

        /* [local] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Invoke )(
            IComponentUtil __RPC_FAR * This,
            /* [in] */ DISPID dispIdMember,
            /* [in] */ REFIID riid,
            /* [in] */ LCID lcid,
            /* [in] */ WORD wFlags,
            /* [out][in] */ DISPPARAMS __RPC_FAR *pDispParams,
            /* [out] */ VARIANT __RPC_FAR *pVarResult,
            /* [out] */ EXCEPINFO __RPC_FAR *pExcepInfo,
            /* [out] */ UINT __RPC_FAR *puArgErr);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *InstallComponent )(
            IComponentUtil __RPC_FAR * This,
            /* [in] */ BSTR bstrDLLFile,
            /* [in] */ BSTR bstrTypelibFile,
            /* [in] */ BSTR bstrProxyStubDLLFile);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *ImportComponent )(
            IComponentUtil __RPC_FAR * This,
            /* [in] */ BSTR bstrCLSID);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *ImportComponentByName )(
            IComponentUtil __RPC_FAR * This,
            /* [in] */ BSTR bstrProgID);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetCLSIDs )(
            IComponentUtil __RPC_FAR * This,
            /* [in] */ BSTR bstrDLLFile,
            /* [in] */ BSTR bstrTypelibFile,
            /* [out] */ SAFEARRAY __RPC_FAR * __RPC_FAR *aCLSIDs);

        END_INTERFACE
    } IComponentUtilVtbl;

    interface IComponentUtil
    {
        CONST_VTBL struct IComponentUtilVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IComponentUtil_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IComponentUtil_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IComponentUtil_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IComponentUtil_GetTypeInfoCount(This,pctinfo)\
    (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo)

#define IComponentUtil_GetTypeInfo(This,iTInfo,lcid,ppTInfo)\
    (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo)

#define IComponentUtil_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)\
    (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)

#define IComponentUtil_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)\
    (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)

#define IComponentUtil_InstallComponent(This,bstrDLLFile,bstrTypelibFile,bstrProxyStubDLLFile)\
    (This)->lpVtbl -> InstallComponent(This,bstrDLLFile,bstrTypelibFile,bstrProxyStubDLLFile)

#define IComponentUtil_ImportComponent(This,bstrCLSID)\
    (This)->lpVtbl -> ImportComponent(This,bstrCLSID)

#define IComponentUtil_ImportComponentByName(This,bstrProgID)\
    (This)->lpVtbl -> ImportComponentByName(This,bstrProgID)

#define IComponentUtil_GetCLSIDs(This,bstrDLLFile,bstrTypelibFile,aCLSIDs)\
    (This)->lpVtbl -> GetCLSIDs(This,bstrDLLFile,bstrTypelibFile,aCLSIDs)

#endif /* COBJMACROS */

#endif /* C style interface */

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE IComponentUtil_InstallComponent_Proxy(
    IComponentUtil __RPC_FAR * This,
    /* [in] */ BSTR bstrDLLFile,
    /* [in] */ BSTR bstrTypelibFile,
    /* [in] */ BSTR bstrProxyStubDLLFile);

void __RPC_STUB IComponentUtil_InstallComponent_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE IComponentUtil_ImportComponent_Proxy(
    IComponentUtil __RPC_FAR * This,
    /* [in] */ BSTR bstrCLSID);

void __RPC_STUB IComponentUtil_ImportComponent_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE IComponentUtil_ImportComponentByName_Proxy(
    IComponentUtil __RPC_FAR * This,
    /* [in] */ BSTR bstrProgID);

void __RPC_STUB IComponentUtil_ImportComponentByName_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE IComponentUtil_GetCLSIDs_Proxy(
    IComponentUtil __RPC_FAR * This,
    /* [in] */ BSTR bstrDLLFile,
    /* [in] */ BSTR bstrTypelibFile,
    /* [out] */ SAFEARRAY __RPC_FAR * __RPC_FAR *aCLSIDs);

void __RPC_STUB IComponentUtil_GetCLSIDs_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IComponentUtil_INTERFACE_DEFINED__ */

#ifndef __IPackageUtil_INTERFACE_DEFINED__
#define __IPackageUtil_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IPackageUtil
 * at Fri Nov 14 05:07:21 1997
 * using MIDL 3.01.75
 ****************************************/
/* [unique][helpstring][dual][uuid][object] */

EXTERN_C const IID IID_IPackageUtil;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("6eb22874-8a19-11d0-81b6-00a0c9231c29")
    IPackageUtil : public IDispatch
    {
    public:
        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE InstallPackage(
            /* [in] */ BSTR bstrPackageFile,
            /* [in] */ BSTR bstrInstallPath,
            /* [in] */ long lOptions) = 0;

        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE ExportPackage(
            /* [in] */ BSTR bstrPackageID,
            /* [in] */ BSTR bstrPackageFile,
            /* [in] */ long lOptions) = 0;

        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE ShutdownPackage(
            /* [in] */ BSTR bstrPackageID) = 0;

    };

#else /* C style interface */

    typedef struct IPackageUtilVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            IPackageUtil __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            IPackageUtil __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            IPackageUtil __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfoCount )(
            IPackageUtil __RPC_FAR * This,
            /* [out] */ UINT __RPC_FAR *pctinfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfo )(
            IPackageUtil __RPC_FAR * This,
            /* [in] */ UINT iTInfo,
            /* [in] */ LCID lcid,
            /* [out] */ ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetIDsOfNames )(
            IPackageUtil __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [size_is][in] */ LPOLESTR __RPC_FAR *rgszNames,
            /* [in] */ UINT cNames,
            /* [in] */ LCID lcid,
            /* [size_is][out] */ DISPID __RPC_FAR *rgDispId);

        /* [local] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Invoke )(
            IPackageUtil __RPC_FAR * This,
            /* [in] */ DISPID dispIdMember,
            /* [in] */ REFIID riid,
            /* [in] */ LCID lcid,
            /* [in] */ WORD wFlags,
            /* [out][in] */ DISPPARAMS __RPC_FAR *pDispParams,
            /* [out] */ VARIANT __RPC_FAR *pVarResult,
            /* [out] */ EXCEPINFO __RPC_FAR *pExcepInfo,
            /* [out] */ UINT __RPC_FAR *puArgErr);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *InstallPackage )(
            IPackageUtil __RPC_FAR * This,
            /* [in] */ BSTR bstrPackageFile,
            /* [in] */ BSTR bstrInstallPath,
            /* [in] */ long lOptions);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *ExportPackage )(
            IPackageUtil __RPC_FAR * This,
            /* [in] */ BSTR bstrPackageID,
            /* [in] */ BSTR bstrPackageFile,
            /* [in] */ long lOptions);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *ShutdownPackage )(
            IPackageUtil __RPC_FAR * This,
            /* [in] */ BSTR bstrPackageID);

        END_INTERFACE
    } IPackageUtilVtbl;

    interface IPackageUtil
    {
        CONST_VTBL struct IPackageUtilVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IPackageUtil_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IPackageUtil_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IPackageUtil_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IPackageUtil_GetTypeInfoCount(This,pctinfo)\
    (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo)

#define IPackageUtil_GetTypeInfo(This,iTInfo,lcid,ppTInfo)\
    (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo)

#define IPackageUtil_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)\
    (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)

#define IPackageUtil_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)\
    (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)

#define IPackageUtil_InstallPackage(This,bstrPackageFile,bstrInstallPath,lOptions)\
    (This)->lpVtbl -> InstallPackage(This,bstrPackageFile,bstrInstallPath,lOptions)

#define IPackageUtil_ExportPackage(This,bstrPackageID,bstrPackageFile,lOptions)\
    (This)->lpVtbl -> ExportPackage(This,bstrPackageID,bstrPackageFile,lOptions)

#define IPackageUtil_ShutdownPackage(This,bstrPackageID)\
    (This)->lpVtbl -> ShutdownPackage(This,bstrPackageID)

#endif /* COBJMACROS */

#endif /* C style interface */

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE IPackageUtil_InstallPackage_Proxy(
    IPackageUtil __RPC_FAR * This,
    /* [in] */ BSTR bstrPackageFile,
    /* [in] */ BSTR bstrInstallPath,
    /* [in] */ long lOptions);

void __RPC_STUB IPackageUtil_InstallPackage_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE IPackageUtil_ExportPackage_Proxy(
    IPackageUtil __RPC_FAR * This,
    /* [in] */ BSTR bstrPackageID,
    /* [in] */ BSTR bstrPackageFile,
    /* [in] */ long lOptions);

void __RPC_STUB IPackageUtil_ExportPackage_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE IPackageUtil_ShutdownPackage_Proxy(
    IPackageUtil __RPC_FAR * This,
    /* [in] */ BSTR bstrPackageID);

void __RPC_STUB IPackageUtil_ShutdownPackage_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IPackageUtil_INTERFACE_DEFINED__ */

#ifndef __IRemoteComponentUtil_INTERFACE_DEFINED__
#define __IRemoteComponentUtil_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IRemoteComponentUtil
 * at Fri Nov 14 05:07:21 1997
 * using MIDL 3.01.75
 ****************************************/
/* [unique][helpstring][dual][uuid][object] */

EXTERN_C const IID IID_IRemoteComponentUtil;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("6eb22875-8a19-11d0-81b6-00a0c9231c29")
    IRemoteComponentUtil : public IDispatch
    {
    public:
        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE InstallRemoteComponent(
            /* [in] */ BSTR bstrServer,
            /* [in] */ BSTR bstrPackageID,
            /* [in] */ BSTR bstrCLSID) = 0;

        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE InstallRemoteComponentByName(
            /* [in] */ BSTR bstrServer,
            /* [in] */ BSTR bstrPackageName,
            /* [in] */ BSTR bstrProgID) = 0;

    };

#else /* C style interface */

    typedef struct IRemoteComponentUtilVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            IRemoteComponentUtil __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            IRemoteComponentUtil __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            IRemoteComponentUtil __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfoCount )(
            IRemoteComponentUtil __RPC_FAR * This,
            /* [out] */ UINT __RPC_FAR *pctinfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfo )(
            IRemoteComponentUtil __RPC_FAR * This,
            /* [in] */ UINT iTInfo,
            /* [in] */ LCID lcid,
            /* [out] */ ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetIDsOfNames )(
            IRemoteComponentUtil __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [size_is][in] */ LPOLESTR __RPC_FAR *rgszNames,
            /* [in] */ UINT cNames,
            /* [in] */ LCID lcid,
            /* [size_is][out] */ DISPID __RPC_FAR *rgDispId);

        /* [local] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Invoke )(
            IRemoteComponentUtil __RPC_FAR * This,
            /* [in] */ DISPID dispIdMember,
            /* [in] */ REFIID riid,
            /* [in] */ LCID lcid,
            /* [in] */ WORD wFlags,
            /* [out][in] */ DISPPARAMS __RPC_FAR *pDispParams,
            /* [out] */ VARIANT __RPC_FAR *pVarResult,
            /* [out] */ EXCEPINFO __RPC_FAR *pExcepInfo,
            /* [out] */ UINT __RPC_FAR *puArgErr);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *InstallRemoteComponent )(
            IRemoteComponentUtil __RPC_FAR * This,
            /* [in] */ BSTR bstrServer,
            /* [in] */ BSTR bstrPackageID,
            /* [in] */ BSTR bstrCLSID);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *InstallRemoteComponentByName )(
            IRemoteComponentUtil __RPC_FAR * This,
            /* [in] */ BSTR bstrServer,
            /* [in] */ BSTR bstrPackageName,
            /* [in] */ BSTR bstrProgID);

        END_INTERFACE
    } IRemoteComponentUtilVtbl;

    interface IRemoteComponentUtil
    {
        CONST_VTBL struct IRemoteComponentUtilVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IRemoteComponentUtil_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IRemoteComponentUtil_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IRemoteComponentUtil_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IRemoteComponentUtil_GetTypeInfoCount(This,pctinfo)\
    (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo)

#define IRemoteComponentUtil_GetTypeInfo(This,iTInfo,lcid,ppTInfo)\
    (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo)

#define IRemoteComponentUtil_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)\
    (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)

#define IRemoteComponentUtil_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)\
    (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)

#define IRemoteComponentUtil_InstallRemoteComponent(This,bstrServer,bstrPackageID,bstrCLSID)\
    (This)->lpVtbl -> InstallRemoteComponent(This,bstrServer,bstrPackageID,bstrCLSID)

#define IRemoteComponentUtil_InstallRemoteComponentByName(This,bstrServer,bstrPackageName,bstrProgID)\
    (This)->lpVtbl -> InstallRemoteComponentByName(This,bstrServer,bstrPackageName,bstrProgID)

#endif /* COBJMACROS */

#endif /* C style interface */

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE IRemoteComponentUtil_InstallRemoteComponent_Proxy(
    IRemoteComponentUtil __RPC_FAR * This,
    /* [in] */ BSTR bstrServer,
    /* [in] */ BSTR bstrPackageID,
    /* [in] */ BSTR bstrCLSID);

void __RPC_STUB IRemoteComponentUtil_InstallRemoteComponent_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE IRemoteComponentUtil_InstallRemoteComponentByName_Proxy(
    IRemoteComponentUtil __RPC_FAR * This,
    /* [in] */ BSTR bstrServer,
    /* [in] */ BSTR bstrPackageName,
    /* [in] */ BSTR bstrProgID);

void __RPC_STUB IRemoteComponentUtil_InstallRemoteComponentByName_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IRemoteComponentUtil_INTERFACE_DEFINED__ */

#ifndef __IRoleAssociationUtil_INTERFACE_DEFINED__
#define __IRoleAssociationUtil_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IRoleAssociationUtil
 * at Fri Nov 14 05:07:21 1997
 * using MIDL 3.01.75
 ****************************************/
/* [unique][helpstring][dual][uuid][object] */

EXTERN_C const IID IID_IRoleAssociationUtil;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("6eb22876-8a19-11d0-81b6-00a0c9231c29")
    IRoleAssociationUtil : public IDispatch
    {
    public:
        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE AssociateRole(
            /* [in] */ BSTR bstrRoleID) = 0;

        virtual /* [helpstring][id] */ HRESULT STDMETHODCALLTYPE AssociateRoleByName(
            /* [in] */ BSTR bstrRoleName) = 0;

    };

#else /* C style interface */

    typedef struct IRoleAssociationUtilVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            IRoleAssociationUtil __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            IRoleAssociationUtil __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            IRoleAssociationUtil __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfoCount )(
            IRoleAssociationUtil __RPC_FAR * This,
            /* [out] */ UINT __RPC_FAR *pctinfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfo )(
            IRoleAssociationUtil __RPC_FAR * This,
            /* [in] */ UINT iTInfo,
            /* [in] */ LCID lcid,
            /* [out] */ ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetIDsOfNames )(
            IRoleAssociationUtil __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [size_is][in] */ LPOLESTR __RPC_FAR *rgszNames,
            /* [in] */ UINT cNames,
            /* [in] */ LCID lcid,
            /* [size_is][out] */ DISPID __RPC_FAR *rgDispId);

        /* [local] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Invoke )(
            IRoleAssociationUtil __RPC_FAR * This,
            /* [in] */ DISPID dispIdMember,
            /* [in] */ REFIID riid,
            /* [in] */ LCID lcid,
            /* [in] */ WORD wFlags,
            /* [out][in] */ DISPPARAMS __RPC_FAR *pDispParams,
            /* [out] */ VARIANT __RPC_FAR *pVarResult,
            /* [out] */ EXCEPINFO __RPC_FAR *pExcepInfo,
            /* [out] */ UINT __RPC_FAR *puArgErr);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *AssociateRole )(
            IRoleAssociationUtil __RPC_FAR * This,
            /* [in] */ BSTR bstrRoleID);

        /* [helpstring][id] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *AssociateRoleByName )(
            IRoleAssociationUtil __RPC_FAR * This,
            /* [in] */ BSTR bstrRoleName);

        END_INTERFACE
    } IRoleAssociationUtilVtbl;

    interface IRoleAssociationUtil
    {
        CONST_VTBL struct IRoleAssociationUtilVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IRoleAssociationUtil_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IRoleAssociationUtil_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IRoleAssociationUtil_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IRoleAssociationUtil_GetTypeInfoCount(This,pctinfo)\
    (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo)

#define IRoleAssociationUtil_GetTypeInfo(This,iTInfo,lcid,ppTInfo)\
    (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo)

#define IRoleAssociationUtil_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)\
    (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)

#define IRoleAssociationUtil_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)\
    (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)

#define IRoleAssociationUtil_AssociateRole(This,bstrRoleID)\
    (This)->lpVtbl -> AssociateRole(This,bstrRoleID)

#define IRoleAssociationUtil_AssociateRoleByName(This,bstrRoleName)\
    (This)->lpVtbl -> AssociateRoleByName(This,bstrRoleName)

#endif /* COBJMACROS */

#endif /* C style interface */

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE IRoleAssociationUtil_AssociateRole_Proxy(
    IRoleAssociationUtil __RPC_FAR * This,
    /* [in] */ BSTR bstrRoleID);

void __RPC_STUB IRoleAssociationUtil_AssociateRole_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id] */ HRESULT STDMETHODCALLTYPE IRoleAssociationUtil_AssociateRoleByName_Proxy(
    IRoleAssociationUtil __RPC_FAR * This,
    /* [in] */ BSTR bstrRoleName);

void __RPC_STUB IRoleAssociationUtil_AssociateRoleByName_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IRoleAssociationUtil_INTERFACE_DEFINED__ */

#ifndef __MTSAdmin_LIBRARY_DEFINED__
#define __MTSAdmin_LIBRARY_DEFINED__

/****************************************
 * Generated header for library: MTSAdmin
 * at Fri Nov 14 05:07:21 1997
 * using MIDL 3.01.75
 ****************************************/
/* [helpstring][version][uuid] */

typedef /* [public][helpstring] */
enum __MIDL___MIDL_itf_mtxadmin_0107_0001
    {mtsInstallUsers= 1
    }MTSPackageInstallOptions;

typedef /* [public][helpstring] */
enum __MIDL___MIDL_itf_mtxadmin_0107_0002
    {mtsExportUsers= 1
    }MTSPackageExportOptions;

typedef /* [public][helpstring] */
enum __MIDL___MIDL_itf_mtxadmin_0107_0003
    {mtsErrObjectErrors= 0x80110401,
mtsErrObjectInvalid= 0x80110402,
mtsErrKeyMissing= 0x80110403,
mtsErrAlreadyInstalled= 0x80110404,
mtsErrDownloadFailed= 0x80110405,
mtsErrPDFWriteFail= 0x80110407,
mtsErrPDFReadFail= 0x80110408,
mtsErrPDFVersion= 0x80110409,
mtsErrCoReqCompInstalled= 0x80110410,
mtsErrBadPath= 0x8011040a,
mtsErrPackageExists= 0x8011040b,
mtsErrRoleExists= 0x8011040c,
mtsErrCantCopyFile= 0x8011040d,
mtsErrNoTypeLib= 0x8011040e,
mtsErrNoUser= 0x8011040f,
mtsErrInvalidUserids= 0x80110410,
mtsErrNoRegistryCLSID= 0x80110411,
mtsErrBadRegistryProgID= 0x80110412,
mtsErrAuthenticationLevel= 0x80110413,
mtsErrUserPasswdNotValid= 0x80110414,
mtsErrNoRegistryRead= 0x80110415,
mtsErrNoRegistryWrite= 0x80110416,
mtsErrNoRegistryRepair= 0x80110417,
mtsErrCLSIDOrIIDMismatch= 0x80110418,
mtsErrRemoteInterface= 0x80110419,
mtsErrDllRegisterServer= 0x8011041a,
mtsErrNoServerShare= 0x8011041b,
mtsErrNoAccessToUNC= 0x8011041c,
mtsErrDllLoadFailed= 0x8011041d,
mtsErrBadRegistryLibID= 0x8011041e,
mtsErrPackDirNotFound= 0x8011041f,
mtsErrTreatAs= 0x80110420,
mtsErrBadForward= 0x80110421,
mtsErrBadIID= 0x80110422,
mtsErrRegistrarFailed= 0x80110423,
mtsErrCompFileDoesNotExist= 0x80110424,
mtsErrCompFileLoadDLLFail= 0x80110425,
mtsErrCompFileGetClassObj= 0x80110426,
mtsErrCompFileClassNotAvail= 0x80110427,
mtsErrCompFileBadTLB= 0x80110428,
mtsErrCompFileNotInstallable= 0x80110429,
mtsErrNotChangeable= 0x8011042a,
mtsErrNotDeletable= 0x8011042b,
mtsErrSession= 0x8011042c,
mtsErrCompFileNoRegistrar= 0x80110434
    }MTSAdminErrorCodes;

#define E_MTS_OBJECTERRORS mtsErrObjectErrors
#define E_MTS_OBJECTINVALIDmtsErrObjectInvalid
#define E_MTS_KEYMISSINGmtsErrKeyMissing
#define E_MTS_ALREADYINSTALLEDmtsErrAlreadyInstalled
#define E_MTS_DOWNLOADFAILEDmtsErrDownloadFailed
#define E_MTS_PDFWRITEFAILmtsErrPDFWriteFail
#define E_MTS_PDFREADFAILmtsErrPDFReadFail
#define E_MTS_PDFVERSIONmtsErrPDFVersion
#define E_MTS_COREQCOMPINSTALLEDmtsErrCoReqCompInstalled
#define E_MTS_BADPATHmtsErrBadPath
#define E_MTS_PACKAGEEXISTSmtsErrPackageExists
#define E_MTS_ROLEEXISTSmtsErrRoleExists
#define E_MTS_CANTCOPYFILEmtsErrCantCopyFile
#define E_MTS_NOTYPELIBmtsErrNoTypeLib
#define E_MTS_NOUSERmtsErrNoUser
#define E_MTS_INVALIDUSERIDSmtsErrInvalidUserids
#define E_MTS_NOREGISTRYCLSIDmtsErrNoRegistryCLSID
#define E_MTS_BADREGISTRYPROGIDmtsErrBadRegistryProgID
#define E_MTS_AUTHENTICATIONLEVELmtsErrAuthenticationLevel
#define E_MTS_USERPASSWDNOTVALIDmtsErrUserPasswdNotValid
#define E_MTS_NOREGISTRYREADmtsErrNoRegistryRead
#define E_MTS_NOREGISTRYWRITEmtsErrNoRegistryWrite
#define E_MTS_NOREGISTRYREPAIRmtsErrNoRegistryRepair
#define E_MTS_CLSIDORIIDMISMATCHmtsErrCLSIDOrIIDMismatch
#define E_MTS_REMOTEINTERFACEmtsErrRemoteInterface
#define E_MTS_DLLREGISTERSERVERmtsErrDllRegisterServer
#define E_MTS_NOSERVERSHAREmtsErrNoServerShare
#define E_MTS_NOACCESSTOUNCmtsErrNoAccessToUNC
#define E_MTS_DLLLOADFAILEDmtsErrDllLoadFailed
#define E_MTS_BADREGISTRYLIBIDmtsErrBadRegistryLibID
#define E_MTS_PACKDIRNOTFOUNDmtsErrPackDirNotFound
#define E_MTS_TREATASmtsErrTreatAs
#define E_MTS_BADFORWARDmtsErrBadForward
#define E_MTS_BADIIDmtsErrBadIID
#define E_MTS_REGISTRARFAILEDmtsErrRegistrarFailed
#define E_MTS_COMPFILE_DOESNOTEXISTmtsErrCompFileDoesNotExist
#define E_MTS_COMPFILE_LOADDLLFAILmtsErrCompFileLoadDLLFail
#define E_MTS_COMPFILE_GETCLASSOBJmtsErrCompFileGetClassObj
#define E_MTS_COMPFILE_CLASSNOTAVAILmtsErrCompFileClassNotAvail
#define E_MTS_COMPFILE_BADTLBmtsErrCompFileBadTLB
#define E_MTS_COMPFILE_NOTINSTALLABLEmtsErrCompFileNotInstallable
#define E_MTS_NOTCHANGEABLEmtsErrNotChangeable
#define E_MTS_NOTDELETEABLEmtsErrNotDeleteable
#define E_MTS_SESSIONmtsErrSession
#define E_MTS_COMPFILE_NOREGISTRARmtsErrCompFileNoRegistrar

EXTERN_C const IID LIBID_MTSAdmin;

#ifdef __cplusplus
EXTERN_C const CLSID CLSID_Catalog;

class DECLSPEC_UUID("6eb22881-8a19-11d0-81b6-00a0c9231c29")
Catalog;
#endif

#ifdef __cplusplus
EXTERN_C const CLSID CLSID_CatalogObject;

class DECLSPEC_UUID("6eb22882-8a19-11d0-81b6-00a0c9231c29")
CatalogObject;
#endif

#ifdef __cplusplus
EXTERN_C const CLSID CLSID_CatalogCollection;

class DECLSPEC_UUID("6eb22883-8a19-11d0-81b6-00a0c9231c29")
CatalogCollection;
#endif

#ifdef __cplusplus
EXTERN_C const CLSID CLSID_ComponentUtil;

class DECLSPEC_UUID("6eb22884-8a19-11d0-81b6-00a0c9231c29")
ComponentUtil;
#endif

#ifdef __cplusplus
EXTERN_C const CLSID CLSID_PackageUtil;

class DECLSPEC_UUID("6eb22885-8a19-11d0-81b6-00a0c9231c29")
PackageUtil;
#endif

#ifdef __cplusplus
EXTERN_C const CLSID CLSID_RemoteComponentUtil;

class DECLSPEC_UUID("6eb22886-8a19-11d0-81b6-00a0c9231c29")
RemoteComponentUtil;
#endif

#ifdef __cplusplus
EXTERN_C const CLSID CLSID_RoleAssociationUtil;

class DECLSPEC_UUID("6eb22887-8a19-11d0-81b6-00a0c9231c29")
RoleAssociationUtil;
#endif
#endif /* __MTSAdmin_LIBRARY_DEFINED__ */

/* Additional Prototypes for ALL interfaces */

unsigned long             __RPC_USER  BSTR_UserSize(     unsigned long __RPC_FAR *, unsigned long            , BSTR __RPC_FAR * );
unsigned char __RPC_FAR * __RPC_USER  BSTR_UserMarshal(  unsigned long __RPC_FAR *, unsigned char __RPC_FAR *, BSTR __RPC_FAR * );
unsigned char __RPC_FAR * __RPC_USER  BSTR_UserUnmarshal(unsigned long __RPC_FAR *, unsigned char __RPC_FAR *, BSTR __RPC_FAR * );
void                      __RPC_USER  BSTR_UserFree(     unsigned long __RPC_FAR *, BSTR __RPC_FAR * );

unsigned long             __RPC_USER  LPSAFEARRAY_UserSize(     unsigned long __RPC_FAR *, unsigned long            , LPSAFEARRAY __RPC_FAR * );
unsigned char __RPC_FAR * __RPC_USER  LPSAFEARRAY_UserMarshal(  unsigned long __RPC_FAR *, unsigned char __RPC_FAR *, LPSAFEARRAY __RPC_FAR * );
unsigned char __RPC_FAR * __RPC_USER  LPSAFEARRAY_UserUnmarshal(unsigned long __RPC_FAR *, unsigned char __RPC_FAR *, LPSAFEARRAY __RPC_FAR * );
void                      __RPC_USER  LPSAFEARRAY_UserFree(     unsigned long __RPC_FAR *, LPSAFEARRAY __RPC_FAR * );

unsigned long             __RPC_USER  VARIANT_UserSize(     unsigned long __RPC_FAR *, unsigned long            , VARIANT __RPC_FAR * );
unsigned char __RPC_FAR * __RPC_USER  VARIANT_UserMarshal(  unsigned long __RPC_FAR *, unsigned char __RPC_FAR *, VARIANT __RPC_FAR * );
unsigned char __RPC_FAR * __RPC_USER  VARIANT_UserUnmarshal(unsigned long __RPC_FAR *, unsigned char __RPC_FAR *, VARIANT __RPC_FAR * );
void                      __RPC_USER  VARIANT_UserFree(     unsigned long __RPC_FAR *, VARIANT __RPC_FAR * );

/* end of Additional Prototypes */

#ifdef __cplusplus
}
#endif

#endif


MTXADMIN_I.C

/* this file contains the actual definitions of */
/* the IIDs and CLSIDs */

/* link this file in with the server and any clients */

/* File created by MIDL compiler version 3.01.75 */
/* at Fri Nov 14 05:07:21 1997
 */
/* Compiler settings for mtxadmin.idl:
    Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext
    error checks: none
*/
//@@MIDL_FILE_HEADING(  )
#ifdef __cplusplus
extern "C"{
#endif

#ifndef __IID_DEFINED__
#define __IID_DEFINED__

typedef struct _IID
{
    unsigned long x;
    unsigned short s1;
    unsigned short s2;
    unsigned char  c[8];
} IID;

#endif // __IID_DEFINED__

#ifndef CLSID_DEFINED
#define CLSID_DEFINED
typedef IID CLSID;
#endif // CLSID_DEFINED

const IID IID_ICatalog = {0x6eb22870,0x8a19,0x11d0,{0x81,0xb6,0x00,0xa0,0xc9,0x23,0x1c,0x29}};

const IID IID_ICatalogObject = {0x6eb22871,0x8a19,0x11d0,{0x81,0xb6,0x00,0xa0,0xc9,0x23,0x1c,0x29}};

const IID IID_ICatalogCollection = {0x6eb22872,0x8a19,0x11d0,{0x81,0xb6,0x00,0xa0,0xc9,0x23,0x1c,0x29}};

const IID IID_IComponentUtil = {0x6eb22873,0x8a19,0x11d0,{0x81,0xb6,0x00,0xa0,0xc9,0x23,0x1c,0x29}};

const IID IID_IPackageUtil = {0x6eb22874,0x8a19,0x11d0,{0x81,0xb6,0x00,0xa0,0xc9,0x23,0x1c,0x29}};

const IID IID_IRemoteComponentUtil = {0x6eb22875,0x8a19,0x11d0,{0x81,0xb6,0x00,0xa0,0xc9,0x23,0x1c,0x29}};

const IID IID_IRoleAssociationUtil = {0x6eb22876,0x8a19,0x11d0,{0x81,0xb6,0x00,0xa0,0xc9,0x23,0x1c,0x29}};

const IID LIBID_MTSAdmin = {0x6eb22880,0x8a19,0x11d0,{0x81,0xb6,0x00,0xa0,0xc9,0x23,0x1c,0x29}};

const CLSID CLSID_Catalog = {0x6eb22881,0x8a19,0x11d0,{0x81,0xb6,0x00,0xa0,0xc9,0x23,0x1c,0x29}};

const CLSID CLSID_CatalogObject = {0x6eb22882,0x8a19,0x11d0,{0x81,0xb6,0x00,0xa0,0xc9,0x23,0x1c,0x29}};

const CLSID CLSID_CatalogCollection = {0x6eb22883,0x8a19,0x11d0,{0x81,0xb6,0x00,0xa0,0xc9,0x23,0x1c,0x29}};

const CLSID CLSID_ComponentUtil = {0x6eb22884,0x8a19,0x11d0,{0x81,0xb6,0x00,0xa0,0xc9,0x23,0x1c,0x29}};

const CLSID CLSID_PackageUtil = {0x6eb22885,0x8a19,0x11d0,{0x81,0xb6,0x00,0xa0,0xc9,0x23,0x1c,0x29}};

const CLSID CLSID_RemoteComponentUtil = {0x6eb22886,0x8a19,0x11d0,{0x81,0xb6,0x00,0xa0,0xc9,0x23,0x1c,0x29}};

const CLSID CLSID_RoleAssociationUtil = {0x6eb22887,0x8a19,0x11d0,{0x81,0xb6,0x00,0xa0,0xc9,0x23,0x1c,0x29}};

#ifdef __cplusplus
}
#endif


MTXDM.H

/* this ALWAYS GENERATED file contains the definitions for the interfaces */

/* File created by MIDL compiler version 3.01.75 */
/* at Fri Nov 14 05:07:23 1997
 */
/* Compiler settings for mtxdm.idl:
    Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext
    error checks: none
*/
//@@MIDL_FILE_HEADING(  )
#include "rpc.h"
#include "rpcndr.h"
#ifndef COM_NO_WINDOWS_H
#include "windows.h"
#include "ole2.h"
#endif /*COM_NO_WINDOWS_H*/

#ifndef __mtxdm_h__
#define __mtxdm_h__

#ifdef __cplusplus
extern "C"{
#endif

/* Forward Declarations */

#ifndef __IDispenserManager_FWD_DEFINED__
#define __IDispenserManager_FWD_DEFINED__
typedef interface IDispenserManager IDispenserManager;
#endif /* __IDispenserManager_FWD_DEFINED__ */

#ifndef __IHolder_FWD_DEFINED__
#define __IHolder_FWD_DEFINED__
typedef interface IHolder IHolder;
#endif /* __IHolder_FWD_DEFINED__ */

#ifndef __IDispenserDriver_FWD_DEFINED__
#define __IDispenserDriver_FWD_DEFINED__
typedef interface IDispenserDriver IDispenserDriver;
#endif /* __IDispenserDriver_FWD_DEFINED__ */

/* header files for imported files */
#include "oaidl.h"

void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t);
void __RPC_USER MIDL_user_free( void __RPC_FAR * );

/****************************************
 * Generated header for interface: __MIDL_itf_mtxdm_0000
 * at Fri Nov 14 05:07:23 1997
 * using MIDL 3.01.75
 ****************************************/
/* [local] */

// -----------------------------------------------------------------------
// mtxdm.h  -- Microsoft Transaction Server Resource Dispenser Interfaces
//
// This file provides the prototypes for the APIs and COM interfaces
// used by Microsoft Transaction Server Resource Dispensers.
//
// Microsoft Transaction Server 2.0
// Copyright (c) 1996-1997 Microsoft Corporation.  All Rights Reserved.
// -----------------------------------------------------------------------

#ifndef DECLSPEC_UUID
#if _MSC_VER >= 1100
#define DECLSPEC_UUID(x)    __declspec(uuid(x))
#else
#define DECLSPEC_UUID(x)
#endif
#endif
typedef DWORD RESTYPID;

typedef DWORD RESID;

typedef LPOLESTR SRESID;

typedef LPCOLESTR constSRESID;

typedef DWORD RESOURCERATING;

typedef long TIMEINSECS;

typedef DWORD INSTID;

typedef DWORD TRANSID;

//
// Error Codes
//
#define MTXDM_E_ENLISTRESOURCEFAILED 0x8004E100 // return from EnlistResource, is then returned by AllocResource

//
// GetDispenserManager
// A Dispenser calls this API to get a reference to DispenserManager.
//
__declspec(dllimport) HRESULT __cdecl GetDispenserManager(IDispenserManager**);

//
// IDispenserManager
// Implemented by Dispenser Manager, called by all Dispensers.
//

extern RPC_IF_HANDLE __MIDL_itf_mtxdm_0000_v0_0_c_ifspec;
extern RPC_IF_HANDLE __MIDL_itf_mtxdm_0000_v0_0_s_ifspec;

#ifndef __IDispenserManager_INTERFACE_DEFINED__
#define __IDispenserManager_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IDispenserManager
 * at Fri Nov 14 05:07:23 1997
 * using MIDL 3.01.75
 ****************************************/
/* [unique][helpstring][uuid][object] */

EXTERN_C const IID IID_IDispenserManager;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("5cb31e10-2b5f-11cf-be10-00aa00a2fa25")
    IDispenserManager : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE RegisterDispenser(
            /* [in] */ IDispenserDriver __RPC_FAR *__MIDL_0015,
            /* [in] */ LPCOLESTR szDispenserName,
            /* [out] */ IHolder __RPC_FAR *__RPC_FAR *__MIDL_0016) = 0;

        virtual HRESULT STDMETHODCALLTYPE GetContext(
            /* [out] */ INSTID __RPC_FAR *__MIDL_0017,
            /* [out] */ TRANSID __RPC_FAR *__MIDL_0018) = 0;

    };

#else /* C style interface */

    typedef struct IDispenserManagerVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            IDispenserManager __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            IDispenserManager __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            IDispenserManager __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *RegisterDispenser )(
            IDispenserManager __RPC_FAR * This,
            /* [in] */ IDispenserDriver __RPC_FAR *__MIDL_0015,
            /* [in] */ LPCOLESTR szDispenserName,
            /* [out] */ IHolder __RPC_FAR *__RPC_FAR *__MIDL_0016);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetContext )(
            IDispenserManager __RPC_FAR * This,
            /* [out] */ INSTID __RPC_FAR *__MIDL_0017,
            /* [out] */ TRANSID __RPC_FAR *__MIDL_0018);

        END_INTERFACE
    } IDispenserManagerVtbl;

    interface IDispenserManager
    {
        CONST_VTBL struct IDispenserManagerVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IDispenserManager_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IDispenserManager_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IDispenserManager_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IDispenserManager_RegisterDispenser(This,__MIDL_0015,szDispenserName,__MIDL_0016)\
    (This)->lpVtbl -> RegisterDispenser(This,__MIDL_0015,szDispenserName,__MIDL_0016)

#define IDispenserManager_GetContext(This,__MIDL_0017,__MIDL_0018)\
    (This)->lpVtbl -> GetContext(This,__MIDL_0017,__MIDL_0018)

#endif /* COBJMACROS */

#endif /* C style interface */

HRESULT STDMETHODCALLTYPE IDispenserManager_RegisterDispenser_Proxy(
    IDispenserManager __RPC_FAR * This,
    /* [in] */ IDispenserDriver __RPC_FAR *__MIDL_0015,
    /* [in] */ LPCOLESTR szDispenserName,
    /* [out] */ IHolder __RPC_FAR *__RPC_FAR *__MIDL_0016);

void __RPC_STUB IDispenserManager_RegisterDispenser_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IDispenserManager_GetContext_Proxy(
    IDispenserManager __RPC_FAR * This,
    /* [out] */ INSTID __RPC_FAR *__MIDL_0017,
    /* [out] */ TRANSID __RPC_FAR *__MIDL_0018);

void __RPC_STUB IDispenserManager_GetContext_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IDispenserManager_INTERFACE_DEFINED__ */

/****************************************
 * Generated header for interface: __MIDL_itf_mtxdm_0094
 * at Fri Nov 14 05:07:23 1997
 * using MIDL 3.01.75
 ****************************************/
/* [local] */

//
// IHolder
// Implemented by Dispenser Manager, called by one Dispenser.
//

extern RPC_IF_HANDLE __MIDL_itf_mtxdm_0094_v0_0_c_ifspec;
extern RPC_IF_HANDLE __MIDL_itf_mtxdm_0094_v0_0_s_ifspec;

#ifndef __IHolder_INTERFACE_DEFINED__
#define __IHolder_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IHolder
 * at Fri Nov 14 05:07:23 1997
 * using MIDL 3.01.75
 ****************************************/
/* [unique][helpstring][uuid][object] */

EXTERN_C const IID IID_IHolder;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("bf6a1850-2b45-11cf-be10-00aa00a2fa25")
    IHolder : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE AllocResource(
            /* [in] */ const RESTYPID __MIDL_0019,
            /* [out] */ RESID __RPC_FAR *__MIDL_0020) = 0;

        virtual HRESULT STDMETHODCALLTYPE FreeResource(
            /* [in] */ const RESID __MIDL_0021) = 0;

        virtual HRESULT STDMETHODCALLTYPE TrackResource(
            /* [in] */ const RESID __MIDL_0022) = 0;

        virtual HRESULT STDMETHODCALLTYPE TrackResourceS(
            /* [in] */ constSRESID __MIDL_0023) = 0;

        virtual HRESULT STDMETHODCALLTYPE UntrackResource(
            /* [in] */ const RESID __MIDL_0024,
            /* [in] */ const BOOL __MIDL_0025) = 0;

        virtual HRESULT STDMETHODCALLTYPE UntrackResourceS(
            /* [in] */ constSRESID __MIDL_0026,
            /* [in] */ const BOOL __MIDL_0027) = 0;

        virtual HRESULT STDMETHODCALLTYPE Close( void) = 0;

        virtual HRESULT STDMETHODCALLTYPE RequestDestroyResource(
            /* [in] */ const RESID __MIDL_0028) = 0;

    };

#else /* C style interface */

    typedef struct IHolderVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            IHolder __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            IHolder __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            IHolder __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *AllocResource )(
            IHolder __RPC_FAR * This,
            /* [in] */ const RESTYPID __MIDL_0019,
            /* [out] */ RESID __RPC_FAR *__MIDL_0020);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *FreeResource )(
            IHolder __RPC_FAR * This,
            /* [in] */ const RESID __MIDL_0021);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *TrackResource )(
            IHolder __RPC_FAR * This,
            /* [in] */ const RESID __MIDL_0022);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *TrackResourceS )(
            IHolder __RPC_FAR * This,
            /* [in] */ constSRESID __MIDL_0023);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *UntrackResource )(
            IHolder __RPC_FAR * This,
            /* [in] */ const RESID __MIDL_0024,
            /* [in] */ const BOOL __MIDL_0025);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *UntrackResourceS )(
            IHolder __RPC_FAR * This,
            /* [in] */ constSRESID __MIDL_0026,
            /* [in] */ const BOOL __MIDL_0027);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Close )(
            IHolder __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *RequestDestroyResource )(
            IHolder __RPC_FAR * This,
            /* [in] */ const RESID __MIDL_0028);

        END_INTERFACE
    } IHolderVtbl;

    interface IHolder
    {
        CONST_VTBL struct IHolderVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IHolder_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IHolder_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IHolder_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IHolder_AllocResource(This,__MIDL_0019,__MIDL_0020)\
    (This)->lpVtbl -> AllocResource(This,__MIDL_0019,__MIDL_0020)

#define IHolder_FreeResource(This,__MIDL_0021)\
    (This)->lpVtbl -> FreeResource(This,__MIDL_0021)

#define IHolder_TrackResource(This,__MIDL_0022)\
    (This)->lpVtbl -> TrackResource(This,__MIDL_0022)

#define IHolder_TrackResourceS(This,__MIDL_0023)\
    (This)->lpVtbl -> TrackResourceS(This,__MIDL_0023)

#define IHolder_UntrackResource(This,__MIDL_0024,__MIDL_0025)\
    (This)->lpVtbl -> UntrackResource(This,__MIDL_0024,__MIDL_0025)

#define IHolder_UntrackResourceS(This,__MIDL_0026,__MIDL_0027)\
    (This)->lpVtbl -> UntrackResourceS(This,__MIDL_0026,__MIDL_0027)

#define IHolder_Close(This)\
    (This)->lpVtbl -> Close(This)

#define IHolder_RequestDestroyResource(This,__MIDL_0028)\
    (This)->lpVtbl -> RequestDestroyResource(This,__MIDL_0028)

#endif /* COBJMACROS */

#endif /* C style interface */

HRESULT STDMETHODCALLTYPE IHolder_AllocResource_Proxy(
    IHolder __RPC_FAR * This,
    /* [in] */ const RESTYPID __MIDL_0019,
    /* [out] */ RESID __RPC_FAR *__MIDL_0020);

void __RPC_STUB IHolder_AllocResource_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IHolder_FreeResource_Proxy(
    IHolder __RPC_FAR * This,
    /* [in] */ const RESID __MIDL_0021);

void __RPC_STUB IHolder_FreeResource_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IHolder_TrackResource_Proxy(
    IHolder __RPC_FAR * This,
    /* [in] */ const RESID __MIDL_0022);

void __RPC_STUB IHolder_TrackResource_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IHolder_TrackResourceS_Proxy(
    IHolder __RPC_FAR * This,
    /* [in] */ constSRESID __MIDL_0023);

void __RPC_STUB IHolder_TrackResourceS_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IHolder_UntrackResource_Proxy(
    IHolder __RPC_FAR * This,
    /* [in] */ const RESID __MIDL_0024,
    /* [in] */ const BOOL __MIDL_0025);

void __RPC_STUB IHolder_UntrackResource_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IHolder_UntrackResourceS_Proxy(
    IHolder __RPC_FAR * This,
    /* [in] */ constSRESID __MIDL_0026,
    /* [in] */ const BOOL __MIDL_0027);

void __RPC_STUB IHolder_UntrackResourceS_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IHolder_Close_Proxy(
    IHolder __RPC_FAR * This);

void __RPC_STUB IHolder_Close_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IHolder_RequestDestroyResource_Proxy(
    IHolder __RPC_FAR * This,
    /* [in] */ const RESID __MIDL_0028);

void __RPC_STUB IHolder_RequestDestroyResource_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IHolder_INTERFACE_DEFINED__ */

/****************************************
 * Generated header for interface: __MIDL_itf_mtxdm_0095
 * at Fri Nov 14 05:07:23 1997
 * using MIDL 3.01.75
 ****************************************/
/* [local] */

//
// IDispenserDriver
// Implemented by a Dispenser, called by Dispenser Manager.
//

extern RPC_IF_HANDLE __MIDL_itf_mtxdm_0095_v0_0_c_ifspec;
extern RPC_IF_HANDLE __MIDL_itf_mtxdm_0095_v0_0_s_ifspec;

#ifndef __IDispenserDriver_INTERFACE_DEFINED__
#define __IDispenserDriver_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IDispenserDriver
 * at Fri Nov 14 05:07:23 1997
 * using MIDL 3.01.75
 ****************************************/
/* [unique][helpstring][uuid][object] */

EXTERN_C const IID IID_IDispenserDriver;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("208b3651-2b48-11cf-be10-00aa00a2fa25")
    IDispenserDriver : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE CreateResource(
            /* [in] */ const RESTYPID ResTypId,
            /* [out] */ RESID __RPC_FAR *pResId,
            /* [out] */ TIMEINSECS __RPC_FAR *pSecsFreeBeforeDestroy) = 0;

        virtual HRESULT STDMETHODCALLTYPE RateResource(
            /* [in] */ const RESTYPID ResTypId,
            /* [in] */ const RESID ResId,
            /* [in] */ const BOOL fRequiresTransactionEnlistment,
            /* [out] */ RESOURCERATING __RPC_FAR *pRating) = 0;

        virtual HRESULT STDMETHODCALLTYPE EnlistResource(
            /* [in] */ const RESID ResId,
            /* [in] */ const TRANSID TransId) = 0;

        virtual HRESULT STDMETHODCALLTYPE ResetResource(
            /* [in] */ const RESID ResId) = 0;

        virtual HRESULT STDMETHODCALLTYPE DestroyResource(
            /* [in] */ const RESID ResId) = 0;

        virtual HRESULT STDMETHODCALLTYPE DestroyResourceS(
            /* [in] */ constSRESID ResId) = 0;

    };

#else /* C style interface */

    typedef struct IDispenserDriverVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            IDispenserDriver __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            IDispenserDriver __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            IDispenserDriver __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *CreateResource )(
            IDispenserDriver __RPC_FAR * This,
            /* [in] */ const RESTYPID ResTypId,
            /* [out] */ RESID __RPC_FAR *pResId,
            /* [out] */ TIMEINSECS __RPC_FAR *pSecsFreeBeforeDestroy);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *RateResource )(
            IDispenserDriver __RPC_FAR * This,
            /* [in] */ const RESTYPID ResTypId,
            /* [in] */ const RESID ResId,
            /* [in] */ const BOOL fRequiresTransactionEnlistment,
            /* [out] */ RESOURCERATING __RPC_FAR *pRating);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *EnlistResource )(
            IDispenserDriver __RPC_FAR * This,
            /* [in] */ const RESID ResId,
            /* [in] */ const TRANSID TransId);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *ResetResource )(
            IDispenserDriver __RPC_FAR * This,
            /* [in] */ const RESID ResId);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *DestroyResource )(
            IDispenserDriver __RPC_FAR * This,
            /* [in] */ const RESID ResId);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *DestroyResourceS )(
            IDispenserDriver __RPC_FAR * This,
            /* [in] */ constSRESID ResId);

        END_INTERFACE
    } IDispenserDriverVtbl;

    interface IDispenserDriver
    {
        CONST_VTBL struct IDispenserDriverVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IDispenserDriver_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IDispenserDriver_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IDispenserDriver_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IDispenserDriver_CreateResource(This,ResTypId,pResId,pSecsFreeBeforeDestroy)\
    (This)->lpVtbl -> CreateResource(This,ResTypId,pResId,pSecsFreeBeforeDestroy)

#define IDispenserDriver_RateResource(This,ResTypId,ResId,fRequiresTransactionEnlistment,pRating)\
    (This)->lpVtbl -> RateResource(This,ResTypId,ResId,fRequiresTransactionEnlistment,pRating)

#define IDispenserDriver_EnlistResource(This,ResId,TransId)\
    (This)->lpVtbl -> EnlistResource(This,ResId,TransId)

#define IDispenserDriver_ResetResource(This,ResId)\
    (This)->lpVtbl -> ResetResource(This,ResId)

#define IDispenserDriver_DestroyResource(This,ResId)\
    (This)->lpVtbl -> DestroyResource(This,ResId)

#define IDispenserDriver_DestroyResourceS(This,ResId)\
    (This)->lpVtbl -> DestroyResourceS(This,ResId)

#endif /* COBJMACROS */

#endif /* C style interface */

HRESULT STDMETHODCALLTYPE IDispenserDriver_CreateResource_Proxy(
    IDispenserDriver __RPC_FAR * This,
    /* [in] */ const RESTYPID ResTypId,
    /* [out] */ RESID __RPC_FAR *pResId,
    /* [out] */ TIMEINSECS __RPC_FAR *pSecsFreeBeforeDestroy);

void __RPC_STUB IDispenserDriver_CreateResource_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IDispenserDriver_RateResource_Proxy(
    IDispenserDriver __RPC_FAR * This,
    /* [in] */ const RESTYPID ResTypId,
    /* [in] */ const RESID ResId,
    /* [in] */ const BOOL fRequiresTransactionEnlistment,
    /* [out] */ RESOURCERATING __RPC_FAR *pRating);

void __RPC_STUB IDispenserDriver_RateResource_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IDispenserDriver_EnlistResource_Proxy(
    IDispenserDriver __RPC_FAR * This,
    /* [in] */ const RESID ResId,
    /* [in] */ const TRANSID TransId);

void __RPC_STUB IDispenserDriver_EnlistResource_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IDispenserDriver_ResetResource_Proxy(
    IDispenserDriver __RPC_FAR * This,
    /* [in] */ const RESID ResId);

void __RPC_STUB IDispenserDriver_ResetResource_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IDispenserDriver_DestroyResource_Proxy(
    IDispenserDriver __RPC_FAR * This,
    /* [in] */ const RESID ResId);

void __RPC_STUB IDispenserDriver_DestroyResource_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

HRESULT STDMETHODCALLTYPE IDispenserDriver_DestroyResourceS_Proxy(
    IDispenserDriver __RPC_FAR * This,
    /* [in] */ constSRESID ResId);

void __RPC_STUB IDispenserDriver_DestroyResourceS_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IDispenserDriver_INTERFACE_DEFINED__ */

/****************************************
 * Generated header for interface: __MIDL_itf_mtxdm_0096
 * at Fri Nov 14 05:07:23 1997
 * using MIDL 3.01.75
 ****************************************/
/* [local] */

#ifdef USE_UUIDOF_FOR_IID_
#define  IID_IHolder             __uuidof(IIHolder)
#define  IID_IDispenserManager   __uuidof(IDispenserManager)
#define  IID_IDispenserDriver    __uuidof(IDispenserDriver)
#endif

extern RPC_IF_HANDLE __MIDL_itf_mtxdm_0096_v0_0_c_ifspec;
extern RPC_IF_HANDLE __MIDL_itf_mtxdm_0096_v0_0_s_ifspec;

/* Additional Prototypes for ALL interfaces */

/* end of Additional Prototypes */

#ifdef __cplusplus
}
#endif

#endif


MTXDM_I.C

/* this file contains the actual definitions of */
/* the IIDs and CLSIDs */

/* link this file in with the server and any clients */

/* File created by MIDL compiler version 3.01.75 */
/* at Fri Nov 14 05:07:23 1997
 */
/* Compiler settings for mtxdm.idl:
    Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext
    error checks: none
*/
//@@MIDL_FILE_HEADING(  )
#ifdef __cplusplus
extern "C"{
#endif

#ifndef __IID_DEFINED__
#define __IID_DEFINED__

typedef struct _IID
{
    unsigned long x;
    unsigned short s1;
    unsigned short s2;
    unsigned char  c[8];
} IID;

#endif // __IID_DEFINED__

#ifndef CLSID_DEFINED
#define CLSID_DEFINED
typedef IID CLSID;
#endif // CLSID_DEFINED

const IID IID_IDispenserManager = {0x5cb31e10,0x2b5f,0x11cf,{0xbe,0x10,0x00,0xaa,0x00,0xa2,0xfa,0x25}};

const IID IID_IHolder = {0xbf6a1850,0x2b45,0x11cf,{0xbe,0x10,0x00,0xaa,0x00,0xa2,0xfa,0x25}};

const IID IID_IDispenserDriver = {0x208b3651,0x2b48,0x11cf,{0xbe,0x10,0x00,0xaa,0x00,0xa2,0xfa,0x25}};

#ifdef __cplusplus
}
#endif


RMWORKER.IDL

/******************************************************************************\
********************************************************************************
* Filename: RmWorker.idl
*
* Description: IDL source for RmWorker.dll
*
* This file will be processed by the MIDL tool to
* produce the type library (RmWorker.tlb) and marshalling code.
*
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

import "oaidl.idl";

//---------------------------------------------------------------------
//  RMRC:
//  Definition of the resource manager return code type.
//---------------------------------------------------------------------

typedef enum  tagRMSTATUS
{
RM_STATUS_SUCCESS,
RM_STATUS_FAILURE,
RM_STATUS_UNDO_ERROR,
RM_STATUS_LOG_ERROR,
RM_STATUS_COPY_FAILURE,
RM_STATUS_DELETE_FAILURE,
RM_STATUS_RENAME_FAILURE,
RM_STATUS_MOVE_FAILURE,
RM_STATUS_IN_MEMORY_LIST,
RM_STATUS_OVERWRITE_EXISTING,
RM_STATUS_FILE_NOT_FOUND,
RM_STATUS_INTERFACE,
RM_STATUS_NO_MEMORY,
RM_STATUS_INVALID_PARM,
RM_STATUS_IMPORT_ERROR,
RM_STATUS_ENLIST_ERROR,
RM_STATUS_INVALID_STATE,
RM_STATUS_TM_DOWN,
RM_STATUS_RECOVERY_FAILURE
} RMSTATUS;

//---------------------------------------------------------------------
// RECOVERYRC:
// Return codes for recovery functions.
//---------------------------------------------------------------------

typedef enum tagRECOVERYRC
{
REC_S_SUCCESS,
REC_E_NO_MEMORY,
REC_E_ORPHANED_TRANSACTION,
REC_E_INVALID_STATE,
REC_E_PREPARE_SEQUENCE,
REC_E_UNDO_SEQUENCE,
REC_E_END_TRANSACTION_SEQUENCE,
REC_E_FORGET_SEQUENCE,
REC_E_BEGINUNDO_SEQUENCE,
REC_E_ENDUNDO_SEQUENCE,
REC_E_COMMIT_SEQUENCE,
REC_E_TRANSACTION_DUPLICATED,
REC_E_INVALID_LOG_RECORD,
REC_E_LOG_FAILURE,
REC_E_STATE_UNKNOWN,
REC_E_UNDO_INCOMPLETE,
REC_E_INDOUBT_INVALID,
REC_E_REENLIST,
REC_E_TM_DOWN,
REC_E_OUTCOMEACTION
} RECOVERYRC;

//---------------------------------------------------------------------
// RECOVERY_STATE:
// State of a recovering transaction.
//---------------------------------------------------------------------

typedef enum tagRECOVERY_STATE
{
REC_STATE_ACTIVE,
REC_STATE_COMPLETED,
REC_STATE_DORMANT,
REC_STATE_INDOUBT,
REC_STATE_ABORTING
} RECOVERY_STATE;

//---------------------------------------------------------------------
// TXSTATE:
// Definition of Resource Manager states.
// NB:  The Resource Manager only processes one transaction at a time.
//      After initialization the Resource Manager states reflect the
//      transaction states.
//

typedef enum
{
TX_UNINITIALIZED,
TX_INITIALIZING,
TX_INITIALIZED,
TX_ENLISTING,
TX_ENLISTED,
TX_PREPARING,
TX_PREPARED,
TX_COMMITTING,
TX_COMMITTED,
TX_ABORTING,
TX_ABORTED,
TX_DONE,
TX_TMDOWN,
TX_INVALID_STATE
} TXSTATE;

typedef enum
{
RM_COPY,
RM_DELETE,
RM_MOVE,
RM_RENAME,
RM_UNDO,
RM_NONE,
RM_FORGET,
RM_UNDO_BEGIN,
RM_UNDO_END

} FILE_OPERATION;

[

object,
uuid(FC005CE6-3F0B-11D0-9693-00AA00C149A6),
helpstring("IRmWorker Interface"),
pointer_default(unique)
]
interface IRmWorker : IUnknown
{

[helpstring("copy a file")]
HRESULT FileCopyTx([in] BSTR sSource, [in] BSTR sDestination, BOOL bFailIfExists);

[helpstring("rename a file")]
HRESULT FileRenameTx([in] BSTR sSource, [in] BSTR sDestination);

[helpstring("add a file to copy")]
HRESULT AddFile([in] BSTR sSource, [in] BSTR sDestination);

[helpstring("copy the list of files")]
HRESULT CopyListTx();

[helpstring("Move a file")]
HRESULT FileMoveTxt([in] BSTR sSource, [in] BSTR sDestination);

[helpstring("delete a file")]
HRESULT FileDeleteTx([in] BSTR sFileName);

[helpstring("Exports the Tx")]
HRESULT ExportTx([in] ULONG cbTranCookie, [out]BYTE *rgbTranCookie);

[helpstring("Initializes the Worker")]
HRESULT Init([in] IUnknown * pUnkLog, [in] IUnknown * pUnkDtcResMgr, [in]IUnknown *pUnkDtc, [in] BSTR sRecDir);

[helpstring("Returns the Tx ID as a GUID converted to a string")]
HRESULT GetTransactionId([out] BSTR * guidTx);

[helpstring("Recovers any transactions that need it")]
HRESULT Recover(IStorage * pStorage);

[helpstring("Resets a completed transaction to a resusable state")]
HRESULT Reset();

[helpstring("called by CopyFileRm when the TM goes down and the worker was not enlisted")]
HRESULT TMDown();

};

[

object,
uuid(FC005CE2-3F0B-11D0-9693-00AA00C149A6),
helpstring("IRmWorkerPriv Interface"),
pointer_default(unique)
]
interface IRmWorkerPriv : IUnknown
{
import "oaidl.idl";
HRESULTPrepareTx (void);
HRESULTCommitTx (void);
HRESULTAbortTx (void);
HRESULT GetState([out] TXSTATE *eState);
HRESULTSetState([in]  TXSTATE eState);
HRESULTCleanupTx (void);
HRESULT GetDTCUnknown([out] IUnknown ** pUnk);
};

[
uuid(FC005CE3-3F0B-11D0-9693-00AA00C149A6),
version(1.0),
helpstring("RmWorker 1.0 Type Library")
]
library RMWORKERLib
{
importlib("stdole2.tlb");

[
uuid(FC005CE5-3F0B-11D0-9693-00AA00C149A6),
helpstring("RmWorker Class")
]
coclass CRmWorkerObj
{
[default] interface IRmWorker;
interface IRmWorkerPriv;
};
};


TXFILEDISP.IDL

/******************************************************************************\
********************************************************************************
* Filename: TxFileDisp.idl
*
* Description: IDL source for TxFileDisp.dll
*
*   This file will be processed by the MIDL tool to
*   produce the type library (TxFileDisp.tlb) and marshalling code.
*
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

import "oaidl.idl";

[

object,
uuid(8A7339E3-5397-11D0-B151-00AA00BA3258),
dual,
helpstring("IFileRmPxy Interface"),
pointer_default(unique)
]
interface IFileRmPxy : IDispatch
{

[helpstring("add a file to copy")]
HRESULT AddFile([in] long hConnection, [in] BSTR sSource, [in] BSTR sDestination);

[helpstring("copy the list of files")]
HRESULT CopyFilesInCopyList([in] long hConnection);

[helpstring("copies a directory")]
HRESULT CopyDirTx([in] long hConnection,[in] BSTR sSource,[in] BSTR sDestination);

[helpstring("connection based file copy")]
HRESULT CopyFileTx([in] long hConnection,[in] BSTR sSource,[in] BSTR sDestination, BOOL bFailIfExists);

[helpstring("connection based file move")]
HRESULT MoveFileTx([in] long hConnection,[in] BSTR sSource,[in] BSTR sDestination);

[helpstring("connection based file delete")]
HRESULT DeleteFileTx([in] long hConnection,[in] BSTR sFileName);

/* Connection Management */
[helpstring("connect to the rm")]
HRESULT Connect([out] long *hConnection);
[helpstring("disconnect from the rm")]
HRESULT Disconnect([in] long hConnection);
};

[
uuid(8A7339E1-5397-11D0-B151-00AA00BA3258),
version(1.0),
helpstring("TxFileDisp 1.0 Type Library")
]
library TXFILEDISPLib
{
importlib("stdole2.tlb");

[
uuid(8A7339E4-5397-11D0-B151-00AA00BA3258),
helpstring("FileRmPxy Class")
]
coclass FileRmPxy
{
[default] interface IFileRmPxy;
};

};


EVENTS.CPP

// MtsSqlAudit.cpp : Implementatioof CMtsSqlAudit
#include "stdafx.h"
#include "MtsAudit.h"
#ifndef DBNTWIN32
#define DBNTWIN32
#include <SQL.h>
#include <SQLEXT.h>
#include <ODBCINST.h>
#endif /* DBNTWIN32 */
#include <ODBCINST.h>
#include "MtsSqlAudit.h"

#define SQL_EXEC(x)\
{\
RETCODE rc;\
rc = SQLExecDirect(m_hstmt, x, lstrlen(x));\
    if( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO )\
{\
CComBSTR msg;\
GetError( m_henv, m_hdbc, m_hstmt, &msg.m_str);\
ATLTRACE(L"Warning! SQlExecDirec failed! Msg: ");\
ATLTRACE(msg.m_str);\
ATLTRACE(L"\n");\
}\
}

/////////////////////////////////////////////////////////////////////////////
// CMtsSqlAudit

STDMETHODIMP CMtsSqlAudit::OnUserEvent(
                LONGLONG perfCount,
                VARIANT * pvarEvent
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
        TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);

return S_OK;
}
STDMETHODIMP CMtsSqlAudit::OnThreadStart(
                LONGLONG perfCount,
                ULONG ThreadID
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
        TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);

ZeroMemory(m_sSQL, SQL_BUF_SIZE * sizeof(WCHAR));
wsprintf(m_sSQL,
 L"insert into ThreadEvents (tick, threadid, event) values (%d, %d, '%s')",
 TickCount,
 ThreadID,
 L"OnThreadStart");
SQL_EXEC(m_sSQL);
return S_OK;
}

STDMETHODIMP CMtsSqlAudit::OnThreadTerminate(
                LONGLONG perfCount,
                ULONG ThreadID
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
        TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);

ZeroMemory(m_sSQL, SQL_BUF_SIZE * sizeof(WCHAR));
wsprintf(m_sSQL,
 L"insert into ThreadEvents (tick, threadid, event) values (%d, %d, '%s')",
 TickCount,
 ThreadID,
 L"OnThreadTerminate");

SQL_EXEC(m_sSQL);
return S_OK;
}

STDMETHODIMP CMtsSqlAudit::OnThreadAssignToActivity(
                LONGLONG perfCount,
                LPCOLESTR guidActivity,
                ULONG ThreadID
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
        TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);
ZeroMemory(m_sSQL, SQL_BUF_SIZE * sizeof(WCHAR));
wsprintf(m_sSQL,
 L"insert into ThreadEvents(tick, threadid, guidActivity, event)\
values (%d, %d, '%s', '%s')",
 TickCount,
 ThreadID,
 guidActivity,
 L"OnThreadAssignToActivity");
SQL_EXEC(m_sSQL);
return S_OK;
}

STDMETHODIMP CMtsSqlAudit::OnThreadUnassignFromActivity(
                LONGLONG perfCount,
                LPCOLESTR guidActivity,
                ULONG ThreadID
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
        TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);

ZeroMemory(m_sSQL, SQL_BUF_SIZE * sizeof(WCHAR));
wsprintf(m_sSQL,
 L"insert into ThreadEvents(tick, threadid, guidActivity, event)\
values (%d, %d, '%s', '%s')",
 TickCount,
 ThreadID,
 guidActivity,
 L"OnThreadUnassignFromActivity");

SQL_EXEC(m_sSQL);
return S_OK;
}
STDMETHODIMP CMtsSqlAudit::OnPackageActivation(
                LONGLONG perfCount,
                LPCOLESTR guidPackage,
                LPCOLESTR sPackageName
            )
{
   // this should never be called...
_ASSERTE(0);
return S_OK;
}

STDMETHODIMP CMtsSqlAudit::OnPackageShutdown(
                LONGLONG perfCount,
                LPCOLESTR guidPackage,
                LPCOLESTR sPackageName
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
        TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);

ZeroMemory(m_sSQL, SQL_BUF_SIZE * sizeof(WCHAR));
wsprintf(m_sSQL,
 L"insert into PackageEvents values (%d, '%s', '%s')",
 TickCount,
 guidPackage,
 sPackageName);

SQL_EXEC(m_sSQL);
return S_OK;
}

STDMETHODIMP CMtsSqlAudit::OnTransactionStart(
                LONGLONG perfCount,
                LPCOLESTR guidTx,
                LPCOLESTR tsid,
                BOOL fRoot
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
        TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);
ZeroMemory(m_sSQL, SQL_BUF_SIZE * sizeof(WCHAR));
wsprintf(m_sSQL,
 L"insert into TransactionEvents (tick, guidTx, tsid, root, event) \
values (%d, '%s', '%s', %d, '%s')",
 TickCount,
 guidTx,
 tsid,
 fRoot ? 1 : 0,
 L"OnTransactionStart");

SQL_EXEC(m_sSQL);
return S_OK;
}

STDMETHODIMP CMtsSqlAudit::OnTransactionPrepared(
                LONGLONG perfCount,
                LPCOLESTR guidTx,
                BOOL fVoteYes
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
        TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);

ZeroMemory(m_sSQL, SQL_BUF_SIZE * sizeof(WCHAR));
wsprintf(m_sSQL,
 L"insert into TransactionEvents (tick, guidTx, voteYes, event) \
values (%d, '%s', %d, '%s')",
 TickCount,
 guidTx,
 fVoteYes ? 1 : 0,
 L"OnTransactionPrepared");

SQL_EXEC(m_sSQL);
return S_OK;
}

STDMETHODIMP CMtsSqlAudit::OnTransactionAborted(
                LONGLONG perfCount,
                LPCOLESTR guidTx
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
        TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);
ZeroMemory(m_sSQL, SQL_BUF_SIZE * sizeof(WCHAR));
wsprintf(m_sSQL,
 L"insert into TransactionEvents (tick, guidTx, event) \
values (%d, '%s', '%s')",
 TickCount,
 guidTx,
 L"OnTransactionAborted");

SQL_EXEC(m_sSQL);
return S_OK;
}

STDMETHODIMP CMtsSqlAudit::OnMethodCall(
                LONGLONG perfCount,
                MTS_OBJID oid,
                LPCOLESTR guidCid,
                LPCOLESTR guidRid,
                LPCOLESTR sMethod
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
        TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);

ZeroMemory(m_sSQL, SQL_BUF_SIZE * sizeof(WCHAR));
wsprintf(m_sSQL,
 L"insert into MethodEvents (tick, oid, cid, rid, Method, event) \
values (%d, %d, '%s', '%s', '%s', '%s')",
 TickCount,
 oid,
 guidCid,
 guidRid,
 sMethod,
 L"OnMethodCall");

SQL_EXEC(m_sSQL);
return S_OK;
}

STDMETHODIMP CMtsSqlAudit::OnMethodReturn(
                LONGLONG perfCount,
                MTS_OBJID oid,
                LPCOLESTR guidCid,
                LPCOLESTR guidRid,
                LPCOLESTR sMethod,
                HRESULT hresult
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
        TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);
ZeroMemory(m_sSQL, SQL_BUF_SIZE * sizeof(WCHAR));
wsprintf(m_sSQL,
 L"insert into MethodEvents (tick, oid, cid, rid, method, hresult, event) \
values (%d, %d, '%s', '%s', '%s', %d, '%s')",
 TickCount,
 oid,
 guidCid,
 guidRid,
 sMethod,
 hresult,
 L"OnMethodReturn");

SQL_EXEC(m_sSQL);
return S_OK;
}

STDMETHODIMP CMtsSqlAudit::OnMethodException(
                LONGLONG perfCount,
                MTS_OBJID oid,
                LPCOLESTR guidCid,
                LPCOLESTR guidRid,
                LPCOLESTR sMethod
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
    TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);
ZeroMemory(m_sSQL, SQL_BUF_SIZE * sizeof(WCHAR));
wsprintf(m_sSQL,
 L"insert into MethodEvents (tick, oid, cid, rid, Method, event) \
values (%d, %d, '%s', '%s', '%s', '%s')",
 TickCount,
 oid,
 guidCid,
 guidRid,
 sMethod,
 L"OnMethodException");

SQL_EXEC(m_sSQL);
return S_OK;
}
STDMETHODIMP CMtsSqlAudit::OnObjectCreate(
                LONGLONG perfCount,
                LPCOLESTR guidActivity,
                LPCOLESTR clsid,
                LPCOLESTR tsid,
                MTS_OBJID ObjectID
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
    TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);
TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);
ZeroMemory(m_sSQL, SQL_BUF_SIZE * sizeof(WCHAR));
wsprintf(m_sSQL,
 L"insert into ObjectEvents (tick, activity, clsid, tsid, ObjectId, event) \
values (%d, '%s', '%s', '%s', %d, '%s')",
 TickCount,
 guidActivity,
 clsid,
 tsid,
 ObjectID,
 L"OnObjectCreate");

SQL_EXEC(m_sSQL);
return S_OK;
}

STDMETHODIMP CMtsSqlAudit::OnObjectRelease(
                LONGLONG perfCount,
                MTS_OBJID ObjectID
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
        TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);
ZeroMemory(m_sSQL, SQL_BUF_SIZE * sizeof(WCHAR));
wsprintf(m_sSQL,
 L"insert into ObjectEvents (tick, ObjectId, event) \
values (%d,  %d, '%s')",
 TickCount,
 ObjectID,
 L"OnObjectRelease");

SQL_EXEC(m_sSQL);

return S_OK;
}
STDMETHODIMP CMtsSqlAudit::OnObjectActivate(
                LONGLONG perfCount,
                MTS_OBJID ObjectID
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
        TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);
ZeroMemory(m_sSQL, SQL_BUF_SIZE * sizeof(WCHAR));
wsprintf(m_sSQL,
 L"insert into ObjectEvents (tick, ObjectId, event) \
values (%d,  %d, '%s')",
 TickCount,
 ObjectID,
 L"OnObjectActivate");

SQL_EXEC(m_sSQL);
return S_OK;
}

STDMETHODIMP CMtsSqlAudit::OnObjectDeactivate(
                LONGLONG perfCount,
                MTS_OBJID ObjectID
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
        TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);
ZeroMemory(m_sSQL, SQL_BUF_SIZE * sizeof(WCHAR));
wsprintf(m_sSQL,
 L"insert into ObjectEvents (tick, ObjectId, event) \
values (%d,  %d, '%s')",
 TickCount,
 ObjectID,
 L"OnObjectDeactivate");

SQL_EXEC(m_sSQL);

return S_OK;
}

STDMETHODIMP CMtsSqlAudit::OnDisableCommit(
                LONGLONG perfCount,
                MTS_OBJID ObjectID
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
        TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);
ZeroMemory(m_sSQL, SQL_BUF_SIZE * sizeof(WCHAR));
wsprintf(m_sSQL,
 L"insert into ObjectEvents (tick, ObjectId, event) \
values (%d,  %d, '%s')",
 TickCount,
 ObjectID,
 L"OnDisableCommit");

SQL_EXEC(m_sSQL);

return S_OK;
}

STDMETHODIMP CMtsSqlAudit::OnEnableCommit(
                LONGLONG perfCount,
                MTS_OBJID ObjectID
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
        TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);
ZeroMemory(m_sSQL, SQL_BUF_SIZE * sizeof(WCHAR));
wsprintf(m_sSQL,
 L"insert into ObjectEvents (tick, ObjectId, event) \
values (%d,  %d, '%s')",
 TickCount,
 ObjectID,
 L"OnEnableCommit");

SQL_EXEC(m_sSQL);

return S_OK;
}

STDMETHODIMP CMtsSqlAudit::OnSetComplete(
                LONGLONG perfCount,
                MTS_OBJID ObjectID
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
        TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);
ZeroMemory(m_sSQL, SQL_BUF_SIZE * sizeof(WCHAR));
wsprintf(m_sSQL,
 L"insert into ObjectEvents (tick, ObjectId, event) \
values (%d,  %d, '%s')",
 TickCount,
 ObjectID,
 L"OnSetComplete");

SQL_EXEC(m_sSQL);

return S_OK;
}

STDMETHODIMP CMtsSqlAudit::OnResourceCreate(
                LONGLONG perfCount,
LPCOLESTR guidActivity,
                MTS_OBJID ObjectID,
                LPCOLESTR pszType,
                MTS_RESID resId
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
        TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);

ZeroMemory(m_sSQL, SQL_BUF_SIZE * sizeof(WCHAR));
wsprintf(m_sSQL,
 L"insert into ResourceEvents (tick, ActivityGuid, ObjectID, type, resid, event) \
values (%d, '%s', %d, '%s', %d, '%s')",
 TickCount,
 guidActivity,
 ObjectID,
 pszType,
 resId,
 L"OnResourceCreate");

SQL_EXEC(m_sSQL);

return S_OK;
}

STDMETHODIMP CMtsSqlAudit::OnResourceAllocate(
                LONGLONG perfCount,
LPCOLESTR guidActivity,
                MTS_OBJID ObjectID,
                LPCOLESTR pszType,
                MTS_RESID resId
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
        TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);

ZeroMemory(m_sSQL, SQL_BUF_SIZE * sizeof(WCHAR));
wsprintf(m_sSQL,
 L"insert into ResourceEvents (tick, ActivityGuid, ObjectID, type, resid, event) \
values (%d, '%s', %d, '%s', %d, '%s')",
 guidActivity,
 TickCount,
 ObjectID,
 pszType,
 resId,
 L"OnResourceAllocate");

SQL_EXEC(m_sSQL);

return S_OK;
}

STDMETHODIMP CMtsSqlAudit::OnResourceRecycle(
                LONGLONG perfCount,
                MTS_OBJID ObjectID,
                LPCOLESTR pszType,
                MTS_RESID resId
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
        TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);
ZeroMemory(m_sSQL, SQL_BUF_SIZE * sizeof(WCHAR));

wsprintf(m_sSQL,
 L"insert into ResourceEvents (tick, ObjectID, type, resid, event) \
values (%d, %d, '%s', %d, '%s')",
 TickCount,
 ObjectID,
 pszType,
 resId,
 L"OnResourceRecycle");

SQL_EXEC(m_sSQL);

return S_OK;
}

STDMETHODIMP CMtsSqlAudit::OnResourceDestroy(
                LONGLONG perfCount,
                MTS_OBJID ObjectID,
                LPCOLESTR pszType,
                MTS_RESID resId
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
        TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);
ZeroMemory(m_sSQL, SQL_BUF_SIZE * sizeof(WCHAR));
wsprintf(m_sSQL,
 L"insert into ResourceEvents (tick, ObjectID, type, resid, event) \
values (%d, %d, '%s', %d, '%s')",
 TickCount,
 ObjectID,
 pszType,
 resId,
 L"OnResourceDestroy");

SQL_EXEC(m_sSQL);

return S_OK;
}
STDMETHODIMP CMtsSqlAudit::OnAuthenticate(
          LONGLONG perfCount,
  LPCOLESTR guidActivity,
  LPCOLESTR sOriginalUser,
  LPCOLESTR sDirectUser,
  BOOL bCurrentUserInpersonatingInProc
            )
{
    long TickCount = 0;
    if (m_PerformanceFrequency != 0)
        TickCount = (long)((1000 * perfCount) / m_PerformanceFrequency);

ZeroMemory(m_sSQL, SQL_BUF_SIZE * sizeof(WCHAR));
wsprintf(m_sSQL,
 L"insert into Authenticate values (%d, '%s', '%s', '%s', %d)",
 TickCount,
 guidActivity,
 sOriginalUser,
 sDirectUser,
 bCurrentUserInpersonatingInProc);

SQL_EXEC(m_sSQL);
return S_OK;
}


MTSAUDIT.CPP

// MtsAudit.cpp : Implementation of DLL Exports.

// Note: Proxy/Stub Information
//To build a separate proxy/stub DLL,
//run nmake -f MtsAuditps.mk in the project directory.

#include "stdafx.h"
#include "resource.h"
#include "initguid.h"
#include <SQL.h>
#include <SQLEXT.h>
#include <ODBCINST.h>
#include "MtsAudit.h"

#include "MtsAudit_i.c"
#include "MtsSqlAudit.h"

CComModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_MtsSqlAudit, CMtsSqlAudit)
END_OBJECT_MAP()

/////////////////////////////////////////////////////////////////////////////
// DLL Entry Point

extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /*lpReserved*/)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
_Module.Init(ObjectMap, hInstance);
DisableThreadLibraryCalls(hInstance);
}
else if (dwReason == DLL_PROCESS_DETACH)
_Module.Term();
return TRUE;    // ok
}

/////////////////////////////////////////////////////////////////////////////
// Used to determine whether the DLL can be unloaded by OLE

STDAPI DllCanUnloadNow(void)
{
return (_Module.GetLockCount()==0) ? S_OK : S_FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// Returns a class factory to create an object of the requested type

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
return _Module.GetClassObject(rclsid, riid, ppv);
}

/////////////////////////////////////////////////////////////////////////////
// DllRegisterServer - Adds entries to the system registry

STDAPI DllRegisterServer(void)
{
// registers object, typelib and all interfaces in typelib
return _Module.RegisterServer(TRUE);
}

/////////////////////////////////////////////////////////////////////////////
// DllUnregisterServer - Removes entries from the system registry

STDAPI DllUnregisterServer(void)
{
_Module.UnregisterServer();
return S_OK;
}


MTSSQLAUDIT.CPP

#include "stdafx.h"
#include "MtsAudit.h"
#ifndef DBNTWIN32
#define DBNTWIN32
#include <SQL.h>
#include <SQLEXT.h>
#include <ODBCINST.h>
#endif /* DBNTWIN32 */
#include <ODBCINST.h>
#include "MtsSqlAudit.h"

LPTSTR sTables[] = {
_T("Authenticate"),
_T("MethodEvents"),
_T("ObjectEvents"),
_T("PackageEvents"),
_T("ResourceEvents"),
_T("ThreadEvents"),
_T("TransactionEvents")
};

LPTSTR sCreateTable =
_T("CREATE TABLE dbo.%s (\
ctr int IDENTITY (1, 1) NOT NULL ,\
tick int NOT NULL ,\
guidActivity varchar (40) NOT NULL ,\
OriginalUser varchar (255) NULL ,\
DirectUser varchar (255) NULL ,\
fImpersonation int NOT NULL\
)");

STDMETHODIMP CMtsSqlAudit::Init(LPCOLESTR sDSN, LPCOLESTR sUser, LPCOLESTR sPw)
{
CComBSTR msg;
RETCODE rc;

if (m_henv || m_hstmt || m_hdbc)
return E_UNEXPECTED; //did you call Init() twice?

rc = SQLAllocEnv( &m_henv );
    if( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO)
    {
        msg = L"CSqlLogObj::SetupEnv() - SQLAllocEnv failed!\n";
goto Exit;
    }

    /* Allocate the Connection handle */
    rc = SQLAllocConnect( m_henv, &m_hdbc );

    if( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO )
    {
        msg = L"CSqlLogObj::SetupEnv() - SQLAllocConnect failed!\n";
goto Exit;
    }

SQLSetConnectOption(m_hdbc, SQL_LOGIN_TIMEOUT, 5);

    rc = SQLConnect( m_hdbc, (SQLWCHAR *)sDSN,  SQL_NTS,
 (SQLWCHAR *)sUser, SQL_NTS,
 (SQLWCHAR *)sPw,   SQL_NTS );

    if( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO )
    {
        msg = L"CSqlLogObj::SetupEnv() - SQLConnect failed!\n";
goto Exit;
    }

    /* Allocate the statement handle */
    rc = SQLAllocStmt( m_hdbc, &m_hstmt);
if( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO )
    {
        msg = L"CSqlLogObj::SetupEnv() - SQLConnect failed!\n";
goto Exit;
    }
//
//verify the table existence
//
{
BOOL bOk = TRUE;
TCHAR sSQL[1024];
for (int i=0; i<sizeof(sTables)/sizeof(sTables[0]); i++)
{
ZeroMemory(sSQL, 1024 * sizeof(TCHAR));
wsprintf(sSQL, sCreateTable, sTables[i]);
rc = SQLExecDirect(m_hstmt, sSQL, lstrlen(sSQL));
if (rc == SQL_SUCCESS || rc == SQL_SUCCESS_WITH_INFO)
{
//
// this means the db isn't set up
//

ZeroMemory(sSQL, 1024 * sizeof(TCHAR));
wsprintf(sSQL, _T("drop table %s"), sTables[i]);
rc = SQLExecDirect(m_hstmt, sSQL, lstrlen(sSQL));
msg = L"Database not setup correctly";
bOk = FALSE;
break;
}

}

if (bOk) rc = 0;
}

Exit:
if( rc != SQL_SUCCESS && rc != SQL_SUCCESS_WITH_INFO )
    {

ATLTRACE(msg);
ATLTRACE(_T("\n"));

if (m_hstmt)
{
SQLFreeStmt( m_hstmt, SQL_DROP );
}

if (m_hdbc)
{
   rc = SQLDisconnect( m_hdbc);
   SQLFreeConnect( m_hdbc);
   if (m_henv)
 SQLFreeEnv( m_henv );
}

m_hdbc = m_hstmt  = m_henv = NULL;

return E_FAIL;
}

return S_OK;
}

void CMtsSqlAudit::GetError(HENV henv, HDBC hdbc, HSTMT hstmt, BSTR *msg)
{
WCHAR szSqlState[64];
SDWORD NativeError;
WCHAR szErr[512];
SWORD cbErrorMsg;
SQLError(henv, hdbc, hstmt, szSqlState, &NativeError, szErr, 511, &cbErrorMsg);
*msg = SysAllocString(szErr);
}


MTSSQLAUDIT.H

// MtsSqlAudit.h : Declaration of the CMtsSqlAudit

#ifndef __MTSSQLAUDIT_H_
#define __MTSSQLAUDIT_H_

#include "resource.h"       // main symbols
#define SQL_BUF_SIZE1024

/////////////////////////////////////////////////////////////////////////////
// CMtsSqlAudit
class ATL_NO_VTABLE CMtsSqlAudit :
public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<CMtsSqlAudit, &CLSID_MtsSqlAudit>,
public IMtsSqlAudit

{

private:
HENV m_henv;
HDBC m_hdbc;
HSTMT m_hstmt;
LONGLONG m_PerformanceFrequency;
WCHAR m_sSQL[SQL_BUF_SIZE];

public:
CMtsSqlAudit()
{
m_hdbc = m_henv = m_hstmt = NULL;
m_PerformanceFrequency = 0;
m_pUnkMarshaler = NULL;
}

void GetError(HENV henv, HDBC hdbc, HSTMT hstmt, BSTR *msg);

DECLARE_REGISTRY_RESOURCEID(IDR_MTSSQLAUDIT)
DECLARE_NOT_AGGREGATABLE(CMtsSqlAudit)
DECLARE_GET_CONTROLLING_UNKNOWN()

BEGIN_COM_MAP(CMtsSqlAudit)
COM_INTERFACE_ENTRY(IMtsSqlAudit)
COM_INTERFACE_ENTRY_AGGREGATE(IID_IMarshal, m_pUnkMarshaler.p)
END_COM_MAP()

HRESULT FinalConstruct()
{

QueryPerformanceFrequency((LARGE_INTEGER *)&m_PerformanceFrequency);
return CoCreateFreeThreadedMarshaler(
GetControllingUnknown(), &m_pUnkMarshaler.p);
}

void FinalRelease()
{
m_pUnkMarshaler.Release();
RETCODE rc;
if (m_hstmt)
{
SQLFreeStmt( m_hstmt, SQL_DROP );
}
if (m_hdbc)
{
   rc = SQLDisconnect( m_hdbc);
   SQLFreeConnect( m_hdbc);
   SQLFreeEnv( m_henv );
}
}

CComPtr<IUnknown> m_pUnkMarshaler;

// IMtsSqlAudit
public:
STDMETHOD(Init)(LPCOLESTR sDSN, LPCOLESTR sUser, LPCOLESTR sPw);

    STDMETHOD(OnUserEvent)(
LONGLONG perfCount,
                VARIANT * pvarEvent
            );
    STDMETHOD(OnThreadStart)(
                LONGLONG perfCount,
                ULONG ThreadID
            );

    STDMETHOD(OnThreadTerminate)(
                LONGLONG perfCount,
                ULONG ThreadID
            );

    STDMETHOD(OnThreadAssignToActivity)(
                LONGLONG perfCount,
                LPCOLESTR guidActivity,
                ULONG ThreadID
            );

    STDMETHOD(OnThreadUnassignFromActivity)(
                LONGLONG perfCount,
                LPCOLESTR guidActivity,
                ULONG ThreadID
            );
    STDMETHOD(OnPackageActivation)(
                LONGLONG perfCount,
                LPCOLESTR guidPackage,
                LPCOLESTR sPackageName
            );

    STDMETHOD(OnPackageShutdown)(
                LONGLONG perfCount,
                LPCOLESTR guidPackage,
                LPCOLESTR sPackageName
            );
    STDMETHOD(OnObjectCreate)(
                LONGLONG perfCount,
                LPCOLESTR guidActivity,
                LPCOLESTR clsid,
                LPCOLESTR tsid,
                MTS_OBJID ObjectID
            );

    STDMETHOD(OnObjectRelease)(
                LONGLONG perfCount,
                MTS_OBJID ObjectID
            );

    STDMETHOD(OnTransactionStart)(
                LONGLONG perfCount,
                LPCOLESTR guidTx,
                LPCOLESTR tsid,
                BOOL fRoot
            );

    STDMETHOD(OnTransactionPrepared)(
                LONGLONG perfCount,
                LPCOLESTR guidTx,
                BOOL fVoteYes
            );

    STDMETHOD(OnTransactionAborted)(
                LONGLONG perfCount,
                LPCOLESTR guidTx
            );

    STDMETHOD(OnMethodCall)(
                LONGLONG perfCount,
                MTS_OBJID oid,
                LPCOLESTR guidCid,
                LPCOLESTR guidRid,
                LPCOLESTR sMethod
            );

    STDMETHOD(OnMethodReturn)(
                LONGLONG perfCount,
                MTS_OBJID oid,
                LPCOLESTR guidCid,
                LPCOLESTR guidRid,
                LPCOLESTR sMethod,
                HRESULT hresult
            );

    STDMETHOD(OnMethodException)(
                LONGLONG perfCount,
                MTS_OBJID oid,
                LPCOLESTR guidCid,
                LPCOLESTR guidRid,
                LPCOLESTR sMethod
            );
    STDMETHOD(OnObjectActivate)(
                LONGLONG perfCount,
                MTS_OBJID ObjectID
            );

    STDMETHOD(OnObjectDeactivate)(
                LONGLONG perfCount,
                MTS_OBJID ObjectID
            );

    STDMETHOD(OnDisableCommit)(
                LONGLONG perfCount,
                MTS_OBJID ObjectID
            );

    STDMETHOD(OnEnableCommit)(
                LONGLONG perfCount,
                MTS_OBJID ObjectID
            );

    STDMETHOD(OnSetComplete)(
                LONGLONG perfCount,
                MTS_OBJID ObjectID
            );
   STDMETHOD(OnResourceCreate)(
LONGLONG perfCount,
LPCOLESTR guidActivity,
                MTS_OBJID ObjectID,
                LPCOLESTR pszType,
                MTS_RESID resId
            );

    STDMETHOD(OnResourceAllocate)(
                LONGLONG perfCount,
LPCOLESTR guidActivity,
                MTS_OBJID ObjectID,
                LPCOLESTR pszType,
                MTS_RESID resId
            );

    STDMETHOD(OnResourceRecycle)(
                LONGLONG perfCount,
                MTS_OBJID ObjectID,
                LPCOLESTR pszType,
                MTS_RESID resId
            );

    STDMETHOD(OnResourceDestroy)(
                LONGLONG perfCount,
                MTS_OBJID ObjectID,
                LPCOLESTR pszType,
                MTS_RESID resId
            );
    STDMETHOD(OnAuthenticate)(
          LONGLONG perfCount,
  LPCOLESTR guidActivity,
  LPCOLESTR sOriginalUser,
  LPCOLESTR sDirectUser,
  BOOL bCurrentUserInpersonatingInProc
            );

};

#endif //__MTSSQLAUDIT_H_


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by MtsAudit.rc
//
#define IDS_PROJNAME                    100
#define IDR_MTSSQLAUDIT                 101

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        201
#define _APS_NEXT_COMMAND_VALUE         32768
#define _APS_NEXT_CONTROL_VALUE         201
#define _APS_NEXT_SYMED_VALUE           103
#endif
#endif


STDAFX.CPP

// stdafx.cpp : source file that includes just the standard includes
//  stdafx.pch will be the pre-compiled header
//  stdafx.obj will contain the pre-compiled type information

#include "stdafx.h"

#ifdef _ATL_STATIC_REGISTRY
#include <statreg.h>
#include <statreg.cpp>
#endif

#include <atlimpl.cpp>


STDAFX.H

// stdafx.h : include file for standard system include files,
//      or project specific include files that are used frequently,
//      but are changed infrequently

#if !defined(AFX_STDAFX_H__AFCF0619_1429_11D1_B1B8_00AA00BA3258__INCLUDED_)
#define AFX_STDAFX_H__AFCF0619_1429_11D1_B1B8_00AA00BA3258__INCLUDED_

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

#define STRICT

#define _WIN32_WINNT 0x0400
#define _ATL_APARTMENT_THREADED

#include <atlbase.h>
//You may derive a class from CComModule and use it if you want to override
//something, but do not change the name of _Module
extern CComModule _Module;
#include <atlcom.h>

//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_STDAFX_H__AFCF0619_1429_11D1_B1B8_00AA00BA3258__INCLUDED)


MTSSPY.CPP

// MtsSpy.cpp : Implementation of WinMain

// Note: Proxy/Stub Information
//To build a separate proxy/stub DLL,
//run nmake -f MtsSpyps.mk in the project directory.

#include "stdafx.h"
#include "resource.h"
#include "initguid.h"
#include "MtsSpy.h"
#include "MtsSpyCtl.h"
#include "MtsSpyCtl_i.c"
#include "MtsSpy_i.c"
#include "SpyCon.h"

LONG CExeModule::Unlock()
{
LONG l = CComModule::Unlock();
if (l == 0)
{
#if _WIN32_WINNT >= 0x0400
if (CoSuspendClassObjects() == S_OK)
PostThreadMessage(dwThreadID, WM_QUIT, 0, 0);
#else
PostThreadMessage(dwThreadID, WM_QUIT, 0, 0);
#endif
}
return l;
}

CExeModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_SpyCon, CSpyCon)
END_OBJECT_MAP()

LPCTSTR FindOneOf(LPCTSTR p1, LPCTSTR p2)
{
while (*p1 != NULL)
{
LPCTSTR p = p2;
while (*p != NULL)
{
if (*p1 == *p++)
return p1+1;
}
p1++;
}
return NULL;
}

/////////////////////////////////////////////////////////////////////////////
//
extern "C" int WINAPI _tWinMain(HINSTANCE hInstance,
HINSTANCE /*hPrevInstance*/, LPTSTR lpCmdLine, int /*nShowCmd*/)
{
lpCmdLine = GetCommandLine(); //this line necessary for _ATL_MIN_CRT
HRESULT hRes = CoInitialize(NULL);
//  If you are running on NT 4.0 or higher you can use the following call
//instead to make the EXE free threaded.
//  This means that calls come in on a random RPC thread
//HRESULT hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED);
_ASSERTE(SUCCEEDED(hRes));
_Module.Init(ObjectMap, hInstance);
_Module.dwThreadID = GetCurrentThreadId();
TCHAR szTokens[] = _T("-/");

int nRet = 0;
BOOL bRun = TRUE;
LPCTSTR lpszToken = FindOneOf(lpCmdLine, szTokens);
while (lpszToken != NULL)
{
if (lstrcmpi(lpszToken, _T("UnregServer"))==0)
{
_Module.UpdateRegistryFromResource(IDR_MtsSpy, FALSE);
nRet = _Module.UnregisterServer();
bRun = FALSE;
break;
}
if (lstrcmpi(lpszToken, _T("RegServer"))==0)
{
_Module.UpdateRegistryFromResource(IDR_MtsSpy, TRUE);
nRet = _Module.RegisterServer(TRUE);
bRun = FALSE;
break;
}
lpszToken = FindOneOf(lpszToken, szTokens);
}

if (bRun)
{
hRes = _Module.RegisterClassObjects(CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE);
_ASSERTE(SUCCEEDED(hRes));

ISpyCon * pApp;
CoCreateInstance(CLSID_SpyCon, NULL, CLSCTX_INPROC_SERVER, IID_ISpyCon, (void**)&pApp);
if (pApp)
{
pApp->Run();
MSG msg;
while (GetMessage(&msg, 0, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

ULONG ul = pApp -> Release();
_ASSERTE(ul==0);
}
else
_ASSERTE(0);

_Module.RevokeClassObjects();
}

CoUninitialize();
return nRet;
}


MTSSPY.IDL

// MtsSpy.idl : IDL source for MtsSpy.dll
//

// This file will be processed by the MIDL tool to
// produce the type library (MtsSpy.tlb) and marshalling code.

import "oaidl.idl";
import "ocidl.idl";

[
object,
uuid(27EF6FD1-AF60-11D0-B188-00AA00BA3258),
helpstring("ISpyCon Interface"),
pointer_default(unique)
]
interface ISpyCon : IUnknown
{
[id(1), helpstring("method Run")] HRESULT Run();
};
[
uuid(CBFE2577-B030-11D0-B188-00AA00BA3258),
version(1.0),
helpstring("MtsSpy 1.0 Type Library")
]
library MTSSPYLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");

[
uuid(27EF6FD2-AF60-11D0-B188-00AA00BA3258),
helpstring("SpyCon Class")
]
coclass SpyCon
{
[default] interface ISpyCon;
};
};


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by MtsSpy.rc
//
#define IDS_PROJNAME                    100
#define IDR_MtsSpy                      100
#define IDR_SPYCON                      101
#define IDR_MENU1                       201
#define IDI_MAIN                        203
#define ID_LOG                          32768
#define IDM_SELECT_PACKAGES             32769
#define IDM_EXIT                        32770
#define ID_SAVE                         32771
#define ID_CHOOSE_LOG_FILE_NAME         32772
#define ID_CLEAR                        32773
#define ID_OPTIONS_GRID_LINES           32774
#define ID_CHOOSEFONT                   32775
#define IDM_ON_TOP                      32776
#define IDM_ABOUT                       32777
#define ID_SHOW_ON_SCREEN               32778
#define ID_SAVE_ON_EXIT                 32779
#define ID_SAVE_NOW                     32780

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        203
#define _APS_NEXT_COMMAND_VALUE         32781
#define _APS_NEXT_CONTROL_VALUE         201
#define _APS_NEXT_SYMED_VALUE           102
#endif
#endif


SPYCON.CPP

// SpyCon.cpp : Implementation of CSpyCon
#include "stdafx.h"
#include "MtsSpy.h"
#include "MtsSpyCtl.h"
#include "SpyCon.h"

static LPTSTR gsSubKey = _T("Software\\Microsoft\\Transaction Server\\MtsSpy");
/////////////////////////////////////////////////////////////////////////////
// CSpyCon

STDMETHODIMP CSpyCon::Run()
{
RECT rcPos = { CW_USEDEFAULT, 0, 0, 0 };
HMENU hMenu = LoadMenu(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDR_MENU1));
Create(GetDesktopWindow(), rcPos, _T("MTS Spy"), WS_VISIBLE | WS_OVERLAPPEDWINDOW, 0, (UINT)hMenu);
//
//Set the right icon
//
HICON hIcon = LoadIcon(_Module.GetModuleInstance(), MAKEINTRESOURCE(IDI_MAIN));
if (hIcon)
SetIcon(hIcon, TRUE);

// create the spy control
CoCreateInstance(CLSID_MtsSpy, NULL, CLSCTX_INPROC, IID_IOleObject, (void**)&m_pOleObject);
if (!m_pOleObject)
{
MessageBox(_T("CoCreateInstance failed"));
return 0;
}
m_pOleObject->SetClientSite(this);
m_pOleObject -> QueryInterface(IID_IMtsSpy, (void **)&m_pSpy);
_ASSERTE(m_pSpy);

// get our window state from the registry
HKEY hKey = NULL;
DWORD dwDisp;
LONG lRes = RegCreateKeyEx(HKEY_LOCAL_MACHINE, gsSubKey, NULL, NULL, REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,  NULL,  &hKey, &dwDisp);
if (hKey)
{

DWORD dwType = REG_DWORD;
DWORD dwSize = sizeof(m_wp.rcNormalPosition.left);
lRes = RegQueryValueEx(hKey, _T("Left"), NULL, &dwType, (BYTE *)&m_wp.rcNormalPosition.left, &dwSize);
lRes = RegQueryValueEx(hKey, _T("Top"), NULL, &dwType, (BYTE *)&m_wp.rcNormalPosition.top, &dwSize);
lRes = RegQueryValueEx(hKey, _T("Right"), NULL, &dwType, (BYTE *)&m_wp.rcNormalPosition.right, &dwSize);
lRes = RegQueryValueEx(hKey, _T("Bottom"), NULL, &dwType, (BYTE *)&m_wp.rcNormalPosition.bottom, &dwSize);
lRes = RegQueryValueEx(hKey, _T("ShowWindow"), NULL, &dwType, (BYTE *)&m_wp.showCmd, &dwSize);
//always on top option
lRes = RegQueryValueEx(hKey, _T("AlwaysOnTop"), NULL, &dwType, (BYTE *)&m_bTopmost, &dwSize);
m_bTopmost = 1 - m_bTopmost;
BOOL bHandled;
OnAlwaysOnTop(0,0,0,bHandled);

// save on exit
lRes = RegQueryValueEx(hKey, _T("SaveOnExit"), NULL, &dwType, (BYTE *)&m_bSaveOnExit, &dwSize);

// get the stream from the registry
dwType = REG_BINARY;
lRes = RegQueryValueEx(hKey, _T("ControlProperties"), NULL, &dwType, NULL, &dwSize);
if (lRes == ERROR_SUCCESS)
{
HGLOBAL hGlobal = GlobalAlloc(GPTR, dwSize);
BYTE * pByte = (BYTE *)GlobalLock(hGlobal);
lRes = RegQueryValueEx(hKey, _T("ControlProperties"), NULL, &dwType, (BYTE *)pByte, &dwSize);
GlobalUnlock(hGlobal);
IStream * pStream;
CreateStreamOnHGlobal(hGlobal, TRUE, &pStream);

CComQIPtr<IPersistStreamInit, &IID_IPersistStreamInit> pPersist(m_pSpy);
_ASSERTE(pPersist.p);

if (pPersist != NULL)
{
pPersist -> Load(pStream);

}
pStream -> Release();
}
}

if (!( ( m_wp.rcNormalPosition.right - m_wp.rcNormalPosition.left < 10) && (m_wp.rcNormalPosition.bottom - m_wp.rcNormalPosition.top < 10)))
SetWindowPlacement(&m_wp);
else
ShowWindow(SW_SHOWNORMAL);

GetClientRect(&rcPos);
MSG msg;
m_pOleObject->DoVerb(OLEIVERB_INPLACEACTIVATE, &msg, this, 0, m_hWnd, &rcPos);
return S_OK;
}

//
//show the select packages dialog
//
LRESULT CSpyCon::OnSelectPackages(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
_ASSERTE(m_pSpy);
m_pSpy -> SelectPackages();
return 0;
}
//
//the user clicks on the system menu
//
LRESULT CSpyCon::OnSysCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{

bHandled = FALSE; // always do default processing

if (wParam == SC_CLOSE)
OnExit(NULL, NULL, NULL, bHandled);

return 0;
}
//
//sets a glog to log to file
//if the user hasn't chosen a log file, show the common dialog to allow them to select one
//
LRESULT CSpyCon::OnLogToFile(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{

_ASSERTE(m_pSpy);
BOOL bVal;
m_pSpy -> get_LogToFile(&bVal);
bVal = 1 - bVal;
m_pSpy -> put_LogToFile(bVal);
return 0;
}
//
//User wants to choose a log file -- show the common dialog
//
LRESULT CSpyCon::OnChooseLogFile(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
CComBSTR sFileName;
BOOL bCanceled;
m_pSpy -> ChooseLogFile(&sFileName, &bCanceled);
return 0;

}
//
//toggle the save on exit flag
//
LRESULT CSpyCon::OnToggleSaveOnExit(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)

{
m_bSaveOnExit = 1 - m_bSaveOnExit;
//
//the UI semantics are odd here...if you have this selected, and deselect it
//  the fact you deselected it won't be saved (you have to click on "Save Now"
//so I'm going to always write what you choose to the registry if you deselect it

if (FALSE == m_bSaveOnExit)
{
HKEY hKey = NULL;
DWORD dwDisp;
LONG lRes = RegCreateKeyEx(HKEY_LOCAL_MACHINE, gsSubKey, NULL, NULL, REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,  NULL,  &hKey, &dwDisp);
if (hKey)
{
lRes = RegSetValueEx(hKey, _T("SaveOnExit"), NULL, REG_DWORD, (BYTE *)&m_bSaveOnExit, sizeof(BOOL));
RegCloseKey(hKey);
}
}

return 0;

}
//
//toggle the show on screen flag
//
LRESULT CSpyCon::OnToggleShowOnScreen(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
_ASSERTE(m_pSpy);
BOOL bVal;
m_pSpy -> get_ShowOnScreen(&bVal);
bVal = 1 - bVal;
m_pSpy -> put_ShowOnScreen(bVal);
return 0;

}
//
//update the UI in menus
//
LRESULT CSpyCon::OnInitMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{

HMENU hMenu = (HMENU)wParam;
CComBSTR temp = "&Log to File ";
CComBSTR sFileName;
m_pSpy -> get_LogFile(&sFileName);
if (sFileName.Length() > 5)
temp += sFileName;
else
temp = "&Log To file...";

USES_CONVERSION;
ModifyMenu(hMenu, ID_LOG, MF_BYCOMMAND|MF_STRING, ID_LOG, OLE2T(temp));
BOOL bVal;
m_pSpy -> get_LogToFile(&bVal);
CheckMenuItem(hMenu, ID_LOG,   bVal ? MF_BYCOMMAND|MF_CHECKED : MF_BYCOMMAND|MF_UNCHECKED);
m_pSpy -> get_ShowGridLines(&bVal);
CheckMenuItem(hMenu, ID_OPTIONS_GRID_LINES,  bVal ? MF_BYCOMMAND|MF_CHECKED : MF_BYCOMMAND|MF_UNCHECKED);
m_pSpy -> get_ShowOnScreen(&bVal);
CheckMenuItem(hMenu, ID_SHOW_ON_SCREEN,  bVal ? MF_BYCOMMAND|MF_CHECKED : MF_BYCOMMAND|MF_UNCHECKED);
CheckMenuItem(hMenu, IDM_ON_TOP,  m_bTopmost ? MF_BYCOMMAND|MF_CHECKED : MF_BYCOMMAND|MF_UNCHECKED);
CheckMenuItem(hMenu, ID_SAVE_ON_EXIT,  m_bSaveOnExit ? MF_BYCOMMAND|MF_CHECKED : MF_BYCOMMAND|MF_UNCHECKED);

return 0;
}

//
//show/hide gridlines in the list
//
//NOTE:  This will only work if the user has installed IE 3.0 or greater
//
LRESULT CSpyCon::OnToggleGridLines(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{

_ASSERTE(m_pSpy);
BOOL bVal;
m_pSpy -> get_ShowGridLines(&bVal);
bVal = 1 - bVal;
m_pSpy -> put_ShowGridLines(bVal);
return 0;
}
//
//Toggle the Always on top option
//
LRESULT CSpyCon::OnAlwaysOnTop(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{

m_bTopmost = 1 - m_bTopmost;
if (m_bTopmost)
SetWindowPos(HWND_TOPMOST, 0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);
else
SetWindowPos(HWND_NOTOPMOST, 0,0,0,0,SWP_NOMOVE|SWP_NOSIZE);
return 0;
}
//
//show the standard Shell About box
//
LRESULT CSpyCon::OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
m_pSpy -> About();
return 0;
}
//
//pick the font to use in the list
//
LRESULT CSpyCon::OnChooseFont(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
m_pSpy -> ChooseFont();
return 0;
}
//
//called on a WM_CLOSE & WM_SYSCOMMAND (SC_CLOSE) message.  If appropriate, save settings
//and then destroy the window
//
LRESULT CSpyCon::OnExit(WORD /*wNotifyCode*/, WORD /* wID */, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
if (m_bSaveOnExit)
SaveSettings();
m_pOleObject->Close(OLECLOSE_NOSAVE);
SafeRelease(m_pOleObject);
SafeRelease(m_pSpy);
DestroyWindow();
return 0;
}

//
//save current settings to the registyr
//
LRESULT CSpyCon::OnSaveNow(WORD /*wNotifyCode*/, WORD /* wID */, HWND /*hWndCtl*/, BOOL& /*bHandled*/)
{
SaveSettings();
return 0;
}

//
//clear the list box
//
LRESULT CSpyCon::OnClear(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
m_pSpy -> ClearAllEvents();
return 0;
}

//
//save the items in the list box to the log file
//
LRESULT CSpyCon::OnSave(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
m_pSpy -> SaveToFile();
return 0;

}
//
//saves settings.  asks the control fro a stream to save its state
//
HRESULT CSpyCon::SaveSettings()
{
_ASSERTE(m_pSpy);
if(!m_pSpy)
return E_UNEXPECTED;
HKEY hKey = NULL;
DWORD dwDisp;
LONG lRes = RegCreateKeyEx(HKEY_LOCAL_MACHINE, gsSubKey, NULL, NULL, REG_OPTION_NON_VOLATILE,KEY_ALL_ACCESS,  NULL,  &hKey, &dwDisp);
if (hKey)
{
lRes = RegSetValueEx(hKey, _T("Left"), NULL, REG_DWORD, (BYTE *)&m_wp.rcNormalPosition.left, sizeof(m_wp.rcNormalPosition.left));
lRes = RegSetValueEx(hKey, _T("Top"), NULL, REG_DWORD, (BYTE *)&m_wp.rcNormalPosition.top, sizeof(m_wp.rcNormalPosition.left));
lRes = RegSetValueEx(hKey, _T("Right"), NULL, REG_DWORD, (BYTE *)&m_wp.rcNormalPosition.right, sizeof(m_wp.rcNormalPosition.left));
lRes = RegSetValueEx(hKey, _T("Bottom"), NULL, REG_DWORD, (BYTE *)&m_wp.rcNormalPosition.bottom, sizeof(m_wp.rcNormalPosition.left));
lRes = RegSetValueEx(hKey, _T("ShowWindow"), NULL, REG_DWORD, (BYTE *)&m_wp.showCmd, sizeof(BOOL));
lRes = RegSetValueEx(hKey, _T("AlwaysOnTop"), NULL, REG_DWORD, (BYTE *)&m_bTopmost, sizeof(BOOL));
lRes = RegSetValueEx(hKey, _T("SaveOnExit"), NULL, REG_DWORD, (BYTE *)&m_bSaveOnExit, sizeof(BOOL));

CComQIPtr<IPersistStreamInit, &IID_IPersistStreamInit> pPersist(m_pSpy);
_ASSERTE(pPersist.p);
IStream * pStream;
CreateStreamOnHGlobal(NULL, TRUE, &pStream);
if (pPersist != NULL)
{
pPersist -> Save(pStream, TRUE);
HGLOBAL hGlobal;
GetHGlobalFromStream(pStream, &hGlobal);
BYTE * pStByte = (BYTE *)GlobalLock(hGlobal);
DWORD dwSize = GlobalSize(hGlobal);
lRes = RegSetValueEx(hKey, _T("ControlProperties"), NULL, REG_BINARY, (BYTE *)pStByte, dwSize);
GlobalUnlock(hGlobal);

}
pStream -> Release();
RegCloseKey(hKey);
}
return S_OK;
}


SPYCON.H

// SpyCon.h : Declaration of the CSpyCon

#ifndef __SPYCON_H_
#define __SPYCON_H_

#include "resource.h"       // main symbols

/////////////////////////////////////////////////////////////////////////////
// CSpyCon
class ATL_NO_VTABLE CSpyCon :
public CComObjectRoot,
public CComCoClass<CSpyCon, &CLSID_SpyCon>,
public ISpyCon,
public CWindowImpl<CSpyCon>,
public IOleClientSite,
public IOleInPlaceSite

{
private:
IOleObject * m_pOleObject;
BOOL m_bInPlaceActive;
IMtsSpy * m_pSpy;
public:

BOOL m_bTopmost;
WINDOWPLACEMENT m_wp;
BOOL m_bSaveOnExit;

~CSpyCon()
{
SafeRelease(m_pSpy);
SafeRelease(m_pOleObject);
}
CSpyCon()
{
memset(&m_wp, NULL, sizeof(m_wp));
m_wp.length = sizeof(m_wp);
m_bTopmost = FALSE;
m_bInPlaceActive = FALSE;
m_bSaveOnExit = TRUE;
m_pSpy = NULL;
m_pOleObject = NULL;
}

void FinalRelease()
{
SafeRelease(m_pSpy);
SafeRelease(m_pOleObject);
}
DECLARE_REGISTRY_RESOURCEID(IDR_SPYCON)
DECLARE_NOT_AGGREGATABLE(CSpyCon)

BEGIN_COM_MAP(CSpyCon)
COM_INTERFACE_ENTRY(ISpyCon)
COM_INTERFACE_ENTRY(IOleClientSite)
COM_INTERFACE_ENTRY(IOleWindow)
COM_INTERFACE_ENTRY(IOleInPlaceSite)
END_COM_MAP()

BEGIN_MSG_MAP(CSpyCon)
MESSAGE_HANDLER(WM_NCDESTROY, OnNCDestroy)
MESSAGE_HANDLER(WM_SIZE, OnSize)
MESSAGE_HANDLER(WM_ERASEBKGND, OnErase)
MESSAGE_HANDLER(WM_INITMENU, OnInitMenu)
COMMAND_ID_HANDLER(IDM_EXIT, OnExit)
COMMAND_ID_HANDLER(WM_CLOSE, OnExit)
COMMAND_ID_HANDLER(IDM_SELECT_PACKAGES, OnSelectPackages)
MESSAGE_HANDLER(WM_SYSCOMMAND, OnSysCommand)
COMMAND_ID_HANDLER(ID_LOG, OnLogToFile)
COMMAND_ID_HANDLER(ID_CHOOSE_LOG_FILE_NAME, OnChooseLogFile)
COMMAND_ID_HANDLER(ID_SAVE, OnSave)
COMMAND_ID_HANDLER(ID_CLEAR, OnClear)
COMMAND_ID_HANDLER(ID_OPTIONS_GRID_LINES, OnToggleGridLines)
COMMAND_ID_HANDLER(IDM_ON_TOP, OnAlwaysOnTop)
COMMAND_ID_HANDLER(IDM_ABOUT, OnAbout)
COMMAND_ID_HANDLER(ID_CHOOSEFONT, OnChooseFont)
COMMAND_ID_HANDLER(ID_SHOW_ON_SCREEN, OnToggleShowOnScreen)
COMMAND_ID_HANDLER(ID_SAVE_ON_EXIT, OnToggleSaveOnExit)
COMMAND_ID_HANDLER(ID_SAVE_NOW, OnSaveNow)

END_MSG_MAP()

LRESULT OnSaveNow(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnToggleSaveOnExit(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnToggleShowOnScreen(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnSelectPackages(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnSysCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnLogToFile(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnChooseLogFile(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnInitMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnSave(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnClear(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnToggleGridLines(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnAlwaysOnTop(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnChooseFont(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnClose(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnExit(WORD /*wNotifyCode*/, WORD /* wID */, HWND /*hWndCtl*/, BOOL& /*bHandled*/);

LRESULT OnNCDestroy(UINT /* uMsg */, WPARAM /* wParam */, LPARAM /* lParam */, BOOL& /* lResult */)
{
PostQuitMessage(0);
return 0;
}

LRESULT OnErase(UINT /* uMsg */, WPARAM /* wParam */, LPARAM /* lParam */, BOOL& bHandled)
{
if (m_bInPlaceActive)
return 0;
bHandled = FALSE;
return 1;
}

LRESULT OnSize(UINT /* uMsg */, WPARAM /* wParam */, LPARAM /* lParam */, BOOL& /* lResult */)
{
if (m_pOleObject && m_bInPlaceActive)
{
CComQIPtr<IOleInPlaceObject, &IID_IOleInPlaceObject> pInPlaceObject(m_pOleObject);
RECT rcClient;
GetClientRect(&rcClient);
pInPlaceObject->SetObjectRects(&rcClient, &rcClient);
GetWindowPlacement(&m_wp); // for storing in the registry
}
return 0;
}
// ISpyCon
public:
STDMETHOD(Run)();

// IOleClientSite
public:
    STDMETHOD(SaveObject)(void)
{
ATLTRACENOTIMPL(_T("IOleClientSite::SaveObject"));
}
    STDMETHOD(GetMoniker)(DWORD dwAssign, DWORD dwWhichMoniker, IMoniker **ppmk)
{
ATLTRACENOTIMPL(_T("IOleClientSite::GetMoniker"));
}
    STDMETHOD(GetContainer)(IOleContainer **ppContainer)
{
ATLTRACENOTIMPL(_T("IOleClientSite::GetContainer"));
}
    STDMETHOD(ShowObject)(void)
{
ATLTRACENOTIMPL(_T("IOleClientSite::ShowObject"));
}
    STDMETHOD(OnShowWindow)(BOOL fShow)
{
ATLTRACENOTIMPL(_T("IOleClientSite::OnShowWindow"));
}
    STDMETHOD(RequestNewObjectLayout)(void)
{
ATLTRACENOTIMPL(_T("IOleClientSite::RequestNewObjectLayout"));
}

// IOleWindow
public:
    STDMETHOD(GetWindow)(HWND *phwnd)
{
*phwnd = m_hWnd;
return S_OK;
}
    STDMETHOD(ContextSensitiveHelp)(BOOL fEnterMode)
{
ATLTRACENOTIMPL(_T("IOleWindow::CanInPlaceActivate"));
}

// IOleInPlaceSite
public:
HRESULT SaveSettings();
    STDMETHOD(CanInPlaceActivate)(void)
{
return S_OK;
}
    STDMETHOD(OnInPlaceActivate)(void)
{
m_bInPlaceActive = TRUE;
return S_OK;
}
    STDMETHOD(OnUIActivate)(void)
{
ATLTRACENOTIMPL(_T("IOleInPlaceSite::OnUIActivate"));
}
    STDMETHOD(GetWindowContext)(IOleInPlaceFrame **ppFrame, IOleInPlaceUIWindow **ppDoc,
LPRECT lprcPosRect, LPRECT lprcClipRect, LPOLEINPLACEFRAMEINFO lpFrameInfo)
{
GetClientRect(lprcPosRect);
GetClientRect(lprcClipRect);
return S_OK;
}
    STDMETHOD(Scroll)(SIZE scrollExtant)
{
ATLTRACENOTIMPL(_T("IOleInPlaceSite::Scroll"));
}
    STDMETHOD(OnUIDeactivate)(BOOL fUndoable)
{
ATLTRACENOTIMPL(_T("IOleInPlaceSite::OnUIDeactivate"));
}
    STDMETHOD(OnInPlaceDeactivate)( void)
{
m_bInPlaceActive = FALSE;
return S_OK;
}
    STDMETHOD(DiscardUndoState)( void)
{
ATLTRACENOTIMPL(_T("IOleInPlaceSite::DiscardUndoState"));
}
    STDMETHOD(DeactivateAndUndo)( void)
{
ATLTRACENOTIMPL(_T("IOleInPlaceSite::DeactivateAndUndo"));
}
    STDMETHOD(OnPosRectChange)(LPCRECT lprcPosRect)
{
ATLTRACENOTIMPL(_T("IOleInPlaceSite::OnPosRectChange"));
}

};

#endif //__SPYCON_H_


STDAFX.CPP

// stdafx.cpp : source file that includes just the standard includes
//  stdafx.pch will be the pre-compiled header
//  stdafx.obj will contain the pre-compiled type information

#include "stdafx.h"

#ifdef _ATL_STATIC_REGISTRY
#include <statreg.h>
#include <statreg.cpp>
#endif

#include <atlimpl.cpp>
#include <atlctl.cpp>
#include <atlwin.cpp>


STDAFX.H

// stdafx.h : include file for standard system include files,
//      or project specific include files that are used frequently,
//      but are changed infrequently

#if !defined(AFX_STDAFX_H__CBFE257B_B030_11D0_B188_00AA00BA3258__INCLUDED_)
#define AFX_STDAFX_H__CBFE257B_B030_11D0_B188_00AA00BA3258__INCLUDED_

#if _MSC_VER >= 1000
// stdafx.h : include file for standard system include files,
//      or project specific include files that are used frequently,
//      but are changed infrequently
#define STRICT

#define _WIN32_WINNT 0x0400
#define _ATL_APARTMENT_THREADED

#include <atlbase.h>
//You may derive a class from CComModule and use it if you want to override
//something, but do not change the name of _Module
class CExeModule : public CComModule
{
public:
LONG Unlock();
DWORD dwThreadID;
};
extern CExeModule _Module;
#include <atlcom.h>
#include <atlctl.h>
#include <atlwin.h>
#define SafeRelease(pUnk) {if (pUnk){pUnk -> Release();pUnk = NULL; }}

#endif // _MSC_VER >= 1000
//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_STDAFX_H__CBFE257B_B030_11D0_B188_00AA00BA3258__INCLUDED)


INSTANCESINK.H

/******************************************************************************\
********************************************************************************
* Filename: InstanceSink.h
*
* Description:  Sink for Instance Events
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#ifndef __INSTANCESINK_H__
#define __INSTANCESINK_H__

#include "resource.h"       // main symbols

CComBSTR GuidToBstr(REFGUID guid);

class CMtsSpy;

/////////////////////////////////////////////////////////////////////////////
// CInstanceSink
class ATL_NO_VTABLE CInstanceSink :
public CMtsSink,
public IMtsInstanceEvents
{
public:
CInstanceSink(CMtsSpy * pSpy)
{
m_pSpy = pSpy;
}

CInstanceSink()
{
m_pSpy = NULL;
}

DECLARE_NOT_AGGREGATABLE(CInstanceSink)
DECLARE_GET_CONTROLLING_UNKNOWN()

BEGIN_COM_MAP(CInstanceSink)
COM_INTERFACE_ENTRY(IMtsInstanceEvents)
COM_INTERFACE_ENTRY_CHAIN(CMtsSink)
END_COM_MAP()

virtual REFIID GetIID(){return IID_IMtsInstanceEvents;}
STDMETHODIMP GetSinkType(EventEnum * e){*e = Instance;return S_OK;}

STDMETHODIMP OnObjectCreate(LONGLONG perfCount, REFGUID guidActivity, REFCLSID clsid, REFGUID tsid, MTS_OBJID ObjectID)
{
m_pSpy -> AddEventToList(perfCount, _TEXT("OnObjectCreate"), m_sPackageName);
WCHAR id[16];
wsprintfW(id,L"0x%08X", ObjectID);

    CComBSTR sGuidActivity = GuidToBstr(guidActivity);
    CComBSTR sClsid = GuidToBstr(clsid);
    CComBSTR sTsid = GuidToBstr(tsid);

m_pSpy -> AddParamValueToList(L"ObjectID", id);
m_pSpy -> AddParamValueToList(L"guidActivity", sGuidActivity);
m_pSpy -> AddParamValueToList(L"clsid", sClsid);
m_pSpy -> AddParamValueToList(L"tsid", sTsid);
IF_AUDIT_DO(OnObjectCreate)(perfCount,
sGuidActivity,
sClsid,
sTsid,
ObjectID);

return S_OK;
}

STDMETHODIMP OnObjectRelease(LONGLONG perfCount, MTS_OBJID ObjectID)
{
WCHAR p1[32];
wsprintfW(p1, L"ObjectID: %ld", ObjectID);
m_pSpy -> AddEventToList(perfCount, _TEXT("OnObjectRelease"), m_sPackageName);
WCHAR id[16];
wsprintfW(id,L"0x%08X", ObjectID);
m_pSpy -> AddParamValueToList(L"ObjectID", id);
IF_AUDIT_DO(OnObjectRelease)(perfCount,
 ObjectID);

return S_OK;
}

};

#endif //__INSTANCESINK_H__


METHODSINK.CPP

/******************************************************************************\
********************************************************************************
* Filename: MethodSink.cpp
*
* Description:  Sink for Method events
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
#include "stdafx.h"
#include "mtsspyctl.h"
#include "mtsevents.h"
#include "MtsSink.h"
#include "MtsAudit.h"
#include "MTSSpy.h"
#include "packagesink.h"
#include "PackageInfo.h"
#include "methodsink.h"

extern LONGLONG PerformanceFrequency;

STDMETHODIMP  CMethodSink::OnMethodCall(LONGLONG perfCount, MTS_OBJID oid,REFCLSID guidCid, REFIID guidRid, ULONG iMeth)
{
TimeStack * pStack = m_map[oid];
if (!pStack)
{
pStack = new TimeStack;
m_map[oid] = pStack;
}
_ASSERTE(pStack);
pStack -> push_front(perfCount);

m_pSpy -> AddEventToList(perfCount, _TEXT("OnMethodCall"), m_sPackageName);
WCHAR id[16];
wsprintfW(id,L"0x%08X", oid);
m_pSpy -> AddParamValueToList(L"ObjectID", id);

    CComBSTR sCid = GuidToBstr(guidCid);
    CComBSTR sIid = GuidToBstr(guidRid);

m_pSpy -> AddParamValueToList(L"CLSID", sCid);
m_pSpy -> AddParamValueToList(L"riid", sIid);

TCHAR * pszGuidName = NULL;
HRESULT hr;
hr = GetMethodName(guidRid, iMeth, &pszGuidName) ;
    if(hr == S_OK)
{
m_pSpy -> AddParamValueToList(L"Method Name", pszGuidName);
}
else
{
pszGuidName = new _TCHAR[8] ;
wsprintf(pszGuidName, _T("%X"), iMeth) ;
m_pSpy -> AddParamValueToList(L"v-table index", pszGuidName);
}
   IF_AUDIT_DO(OnMethodCall)(perfCount,
  oid,
  sCid,
  sIid,
  pszGuidName);

    delete pszGuidName ;
    pszGuidName = NULL ;
return S_OK;
}

STDMETHODIMP  CMethodSink::OnMethodReturn(LONGLONG perfCount,
  MTS_OBJID oid,
  REFCLSID guidCid,
  REFIID guidRid,
  ULONG iMeth,
  HRESULT hrRet)
{
m_pSpy -> AddEventToList(perfCount, _TEXT("OnMethodReturn"), m_sPackageName);
WCHAR id[16];
wsprintfW(id,L"0x%08X", oid);
m_pSpy -> AddParamValueToList(L"ObjectID", id);

    CComBSTR sCid = GuidToBstr(guidCid);
    CComBSTR sIid = GuidToBstr(guidRid);

m_pSpy -> AddParamValueToList(L"CLSID", sCid);
m_pSpy -> AddParamValueToList(L"riid", sIid);

TCHAR      * pszGuidName   =   NULL ;
HRESULT hr;
hr = GetMethodName(guidRid, iMeth, &pszGuidName) ;
    if(hr != S_OK)
{
pszGuidName = new _TCHAR[8] ;
wsprintf(pszGuidName, _T("%X"), iMeth) ;
m_pSpy -> AddParamValueToList(L"v-table index", pszGuidName);
}
else
{
m_pSpy -> AddParamValueToList(L"Method Name", pszGuidName);
}

   IF_AUDIT_DO(OnMethodReturn)(perfCount,
  oid,
  sCid,
  sIid,
  pszGuidName,
  hrRet);

    delete pszGuidName ;
    pszGuidName = NULL ;

wsprintf(id, L"0x%08X", hrRet);
    m_pSpy -> AddParamValueToList(L"Return Value", id);

TCHAR sTime[16];
TimeStack * pStack = m_map[oid];
LONGLONG oldTime  = pStack -> front();
pStack -> pop_front();
_ASSERTE(oldTime);

    DWORD timeDiff = (DWORD)((1000*(perfCount-oldTime))/PerformanceFrequency);
wsprintf(sTime, _T("%d"), timeDiff);

m_pSpy -> AddParamValueToList(L"Call time (ms)", sTime);
if (pStack -> empty())
{
delete pStack;
m_map.erase(oid);
}
return S_OK;
}

STDMETHODIMP  CMethodSink::OnMethodException(LONGLONG perfCount, MTS_OBJID oid,REFCLSID guidCid, REFIID guidRid, ULONG iMeth)
{
m_pSpy -> AddEventToList(perfCount, _TEXT("OnInstanceException"), m_sPackageName);

WCHAR id[16];
wsprintfW(id,L"0x%08X", oid);
m_pSpy -> AddParamValueToList(L"ObjectID", id);

    CComBSTR sCid = GuidToBstr(guidCid);
    CComBSTR sIid = GuidToBstr(guidRid);

m_pSpy -> AddParamValueToList(L"CLSID", sCid);
m_pSpy -> AddParamValueToList(L"riid", sIid);

TCHAR      * pszGuidName   =   NULL ;
HRESULT hr;
hr = GetMethodName(guidRid, iMeth, &pszGuidName) ;
    if(hr != S_OK) { pszGuidName = new _TCHAR[64] ; lstrcpy(pszGuidName, L"Unknown (no TypeLibrary)") ; }
    m_pSpy -> AddParamValueToList(L"Method Name", pszGuidName);

   IF_AUDIT_DO(OnMethodException)(perfCount,
  oid,
  sCid,
  sIid,
  pszGuidName);

    delete pszGuidName ;
    pszGuidName = NULL ;
return S_OK;
}

STDMETHODIMP  CMethodSink::Advise()
{
if (m_dwAdviseCookie)
return E_FAIL;

_ASSERTE(m_pCPC);
HRESULT hr;
hr = m_pCPC -> FindConnectionPoint(GetIID(), &m_pConPt);
if (hr == S_OK)
{
hr = m_pConPt -> Advise((IUnknown *)(IMtsMethodEvents *)this, &m_dwAdviseCookie);
_ASSERTE(hr == S_OK);
}

return hr;
}

HRESULT CMethodSink::GetClsidOfTypeLib2 (IID * piid, UUID * puuidClsid)
{
WCHAR*pszBuffIID= 0x0;
HRESULThr= S_OK;
WCHARrgBufferIid [40];
WCHARszBufferClsid[40];
RPC_STATUSRpcStatus;
WCHARrgBufferKey [256];
longlRetVal;
HKEYhkey;
longlcbszClsidBuff= 40 * sizeof(WCHAR) ;

//-------------------------------------------------------------------------
//Convert the iid to it's string form
RpcStatus = UuidToString ( piid, &pszBuffIID);

lstrcpy (rgBufferIid, pszBuffIID);
RpcStringFree (&pszBuffIID);

//-------------------------------------------------------------------------
//Create the Registry key to do the lookup with
lstrcpy (rgBufferKey, L"Interface\\{");
lstrcat (rgBufferKey, rgBufferIid);
lstrcat (rgBufferKey, L"}\\TypeLib");

//UNDONE -- gaganc Perhaps should handle the \interface\<iid1>\forward\<iid2|
//case also.

//-------------------------------------------------------------------------
//Open the appropriate registry key
//HKEY_CLASSES_ROOT\Interface\{<...interface iid ...>}\Typelib

lRetVal = RegOpenKey(
HKEY_CLASSES_ROOT,
rgBufferKey,
&hkey
);

if (lRetVal != ERROR_SUCCESS)
{
return E_FAIL;
}

//-------------------------------------------------------------------------
//Obain the clsid from the open key
lRetVal = RegQueryValueEx(
hkey,
NULL,
NULL,
NULL,
(unsigned char *)szBufferClsid,
(unsigned long *)&lcbszClsidBuff
);

    if(lRetVal != ERROR_SUCCESS)
    {
        RegCloseKey(hkey) ;
        return E_FAIL ;
    }

//-------------------------------------------------------------------------
//Get rid of the braces in the begining and the end.

//Remove the trailing brace
szBufferClsid[37] = '\0';

//convert the string form of the clsid to a binary form. Plus 1 will
//remove the first brace.
lRetVal = UuidFromString ((szBufferClsid + 1), puuidClsid);

    if(lRetVal != ERROR_SUCCESS)
    {
        RegCloseKey(hkey) ;
        return E_UNEXPECTED ;
    }

//-------------------------------------------------------------------------
//Prepare to return

lRetVal = RegCloseKey (hkey);

return hr;
} //end GetClsidOfTypeLib

//----------------------------------------------------------------------------
// Function:GetMethodName (INTERNAL FUNCTION)
// Description:Takes an IID and a method index returns the corresponding
//              method name.
//
// Exceptions:n/a
// Process:
// Notes:ppszMethodName should be freed by the client using standard
//C++ delete.  If hr != S_OK, ppszMethodName will be set to
//NULL.
//----------------------------------------------------------------------------
HRESULT CMethodSink::GetMethodName (REFIID riid, int iMeth, _TCHAR** ppszMethodName)
{
BOOLfRet= FALSE;
HRESULThr;
UUIDuuidClsid;
LCIDlcid= 0x0;
ITypeLib*pITypeLib= 0x0;
ITypeInfo*pITypeInfo= 0x0;
    FUNCDESC    *   pFuncDesc   = 0x0;
    _TCHAR      *   pszMethod   = 0x0;
    BSTR            bstrName ;
    UINT            lcNames     = 0 ;

    IID * piid = (IID *) &riid;

//Obtain the clsid of the typelib for this interface
hr = GetClsidOfTypeLib2 (piid, &uuidClsid);

if (hr != S_OK) goto errExit ;

//-------------------------------------------------------------------------
//Obtain the ITypeLib interface on the typelib
hr = LoadRegTypeLib (
uuidClsid,//CLSID
1,//Major version number
0,//Minor version number
0,//the locale id
&pITypeLib
);

// if the default system lcid (0) doesn't work, we try
// LCID 9 (standard english) and if that doesn't work, we failfast
if(FAILED(hr))
{
hr = LoadRegTypeLib (uuidClsid,1,0,9,&pITypeLib);
}

    if(FAILED(hr)) goto errExit ;

hr = pITypeLib->GetTypeInfoOfGuid (*piid, &pITypeInfo);

    if(FAILED(hr)) goto errExit ;

hr = pITypeInfo->GetFuncDesc(iMeth, &pFuncDesc) ;

    if(FAILED(hr)) goto errExit ;

    hr = pITypeInfo->GetNames(pFuncDesc->memid, &bstrName, 1, &lcNames) ;

    if(FAILED(hr)) goto errExit ;

    pszMethod = new _TCHAR[lstrlen(bstrName)+1] ;

    if(! pszMethod)
    {
        hr = E_OUTOFMEMORY ;
        goto errExit ;
    }

    lstrcpy(pszMethod, bstrName) ;

    SysFreeString(bstrName) ;

errExit:

    if (pFuncDesc)
{
pITypeInfo->ReleaseFuncDesc(pFuncDesc) ;
}

if (pITypeInfo)
{
pITypeInfo->Release();
}

    if (pITypeLib)
    {
        pITypeLib->Release() ;
    }

    *ppszMethodName = pszMethod ;

    return hr ;
}

/* ----------------------------------------------------------------------------
 @func Description

 Usage:<nl>
----------------------------------------------------------------------------- */


METHODSINK.H

/******************************************************************************\
********************************************************************************
* Filename: MethodSink.h
*
* Description: Declration of Method Sink
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#ifndef __METHODSINK_H__
#define __METHODSINK_H__

#include "resource.h"       // main symbols

typedef list<LONGLONG> TimeStack;
typedef map<MTS_OBJID, TimeStack *> TimeMap;
class CMtsSpy;

/////////////////////////////////////////////////////////////////////////////
// CMethodSink
class ATL_NO_VTABLE CMethodSink :
public CMtsSink,
public IMtsMethodEvents
{
public:

TimeMap m_map;
CMethodSink(CMtsSpy * pSpy)
{
m_pSpy = pSpy;
}
CMethodSink()
{
m_pSpy = NULL;
}

DECLARE_NOT_AGGREGATABLE(CMethodSink)
DECLARE_GET_CONTROLLING_UNKNOWN()

BEGIN_COM_MAP(CMethodSink)
COM_INTERFACE_ENTRY(IMtsMethodEvents)
COM_INTERFACE_ENTRY_CHAIN(CMtsSink)
END_COM_MAP()

virtual REFIID GetIID(){return IID_IMtsMethodEvents;}
STDMETHODIMP GetSinkType(EventEnum * e){*e = Method;return S_OK;}

STDMETHOD(OnMethodCall)(LONGLONG perfCount, MTS_OBJID oid, REFCLSID cid, REFIID rid, ULONG iMeth);
STDMETHOD(OnMethodReturn)(LONGLONG perfCount, MTS_OBJID oid, REFCLSID cid, REFIID rid, ULONG iMeth, HRESULT hr);
STDMETHOD(OnMethodException)(LONGLONG perfCount, MTS_OBJID oid, REFCLSID cid, REFIID rid, ULONG iMeth);
STDMETHOD(Advise)();

HRESULT GetClsidOfTypeLib2 (IID * piid, UUID * puuidClsid);
HRESULT GetMethodName (REFIID riid, int iMeth, _TCHAR** ppszMethodName);

};
#endif //__METHODSINK_H__


MTSSINK.CPP

/******************************************************************************\
********************************************************************************
* Filename: MtsSink.cpp
*
* Description: implementation of CMtsSink
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
#include "stdafx.h"
#include "mtsspyctl.h"
#include "mtsevents.h"
#include "MtsAudit.h"
#include "MTSSpy.h"
#include "MtsSink.h"
#include "packagesink.h"
#include "PackageInfo.h"

/////////////////////////////////////////////////////////////////////////////
// CMtsSink

STDMETHODIMP CMtsSink::Init(IConnectionPointContainer * pConPt, LPWSTR sPackageName)
{

m_sPackageName = sPackageName;
m_pCPC = pConPt;
m_pCPC -> AddRef();
return S_OK;
}

STDMETHODIMP CMtsSink::Advise()
{
if (m_dwAdviseCookie)
return E_FAIL;

_ASSERTE(m_pCPC);
HRESULT hr;
hr = m_pCPC -> FindConnectionPoint(GetIID(), &m_pConPt);
if (hr == S_OK)
{
hr = m_pConPt -> Advise(GetControllingUnknown(), &m_dwAdviseCookie);
_ASSERTE(hr == S_OK);
}

return hr;
}

STDMETHODIMP CMtsSink::DAdvise()
{

if (!m_pConPt)
return E_FAIL;

m_pConPt -> Unadvise(m_dwAdviseCookie);
m_dwAdviseCookie = 0;
return S_OK;
}

STDMETHODIMP CMtsSink::get_DeleteFlag(BOOL * pVal)
{

*pVal = m_bReadyForDelete;
return S_OK;
}

STDMETHODIMP CMtsSink::put_DeleteFlag(BOOL newVal)
{
m_bReadyForDelete = newVal;
return S_OK;
}


MTSSINK.H

/******************************************************************************\
********************************************************************************
* Filename: MtsSink.h
*
* Description: Root object declaration for all sinks.  Does the Advise/Unadvise
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#ifndef __MTSSINK_H_
#define __MTSSINK_H_

#include "resource.h"       // main symbols

class CMtsSpy;

/////////////////////////////////////////////////////////////////////////////
// CMtsSink
class ATL_NO_VTABLE CMtsSink :
public CComObjectRootEx<CComSingleThreadModel>,
public IMtsSink
{
protected:
IConnectionPointContainer * m_pCPC;
DWORD m_dwAdviseCookie;
CComBSTR m_sPackageName;
IConnectionPoint * m_pConPt;
BOOL m_bReadyForDelete;

public:

CMtsSpy * m_pSpy;
void SetSpyObj(CMtsSpy * pSpy)
{
m_pSpy = pSpy;
}

CMtsSink()
{
m_dwAdviseCookie = 0;
m_pConPt = NULL;
m_pCPC = NULL;
m_bReadyForDelete = FALSE;
}
~CMtsSink()
{
if (m_dwAdviseCookie)
{
DAdvise();
m_dwAdviseCookie = NULL;
}

SafeRelease(m_pConPt);
SafeRelease(m_pCPC);
}

DECLARE_REGISTRY_RESOURCEID(IDR_MTSSINK)
DECLARE_GET_CONTROLLING_UNKNOWN()

BEGIN_COM_MAP(CMtsSink)
COM_INTERFACE_ENTRY(IMtsSink)
END_COM_MAP()

virtual REFIID GetIID() = 0;
// IMtsSink
public:
STDMETHOD(get_DeleteFlag)(/*[out, retval]*/ BOOL *pVal);
STDMETHOD(put_DeleteFlag)(/*[in]*/ BOOL newVal);
STDMETHOD(GetSinkType)(EventEnum * e) = 0; // must override
STDMETHOD(DAdvise)();
STDMETHOD(Advise)();
STDMETHOD(Init)(IConnectionPointContainer * pConPt, LPWSTR sPackageName);
};

#endif //__MTSSINK_H_


MTSSPY.CPP

// MtsSpy.cpp : Implementation of CMtsSpy
#include "stdafx.h"
#include "MtsSpyCtl.h"
#include "MtsAudit.h"
#include "MtsSpy.h"
#include "mtsevents.h"
#include "MtsSink.h"
#include "objsink.h"
#include "PackageSink.h"
#include "PackageInfo.h"
#include "TxSink.h"
#include "resourcesink.h"
#include "selecteventsdlg.h"
#include "commdlg.h"
#include <time.h>
#include "shellapi.h"
#include "MtsGrp.h"

LONGLONG PerformanceFrequency = 0;

//
//for some reason this define isn't being included
//

const GUID CLSID_StockFontPage = {0x7ebdaae0, 0x8120, 0x11cf, { 0x89, 0x9f, 0x0, 0xaa, 0x0, 0x68, 0x8b, 0x10}};

//
//  given a GUID, return its string representation
//
CComBSTR GuidToBstr(REFGUID guid)
{
    CComBSTR b(40, L"");
    int nRet = StringFromGUID2(guid, b.m_str, 40);
    _ASSERTE(nRet);
    return b;
}

/////////////////////////////////////////////////////////////////////////////
// CMtsSpy

#define MAX_MSG_LENGTH (1024*sizeof(TCHAR))
static LPTSTR sColNames[] = {_TEXT("Count"),_TEXT("Event"),_TEXT("Tick Count"),
_TEXT("Package"),_TEXT("Parameter"),TEXT("Value")};

HRESULT CMtsSpy::OnDrawAdvanced(ATL_DRAWINFO& di)
{
return S_OK;
}
LRESULT CMtsSpy::OnRButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
HMENU hMenu = LoadMenu(_Module.GetResourceInstance(), MAKEINTRESOURCE(IDR_SPYMENU));
if (!hMenu)
{
_ASSERTE(0);
return 0;
}
POINT local;
local.x = LOWORD(lParam);
local.y = HIWORD(lParam);
    ClientToScreen(&local);
HMENU hPopup = GetSubMenu(hMenu, 0);
    _ASSERTE(hPopup != NULL);
TrackPopupMenu(hPopup, TPM_LEFTALIGN | TPM_RIGHTBUTTON, local.x, local.y, NULL, m_hWndList, NULL);

return 0;
}

LRESULT CMtsSpy::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
RECT rc;
GetWindowRect(&rc);
rc.right -= rc.left;
rc.bottom -= rc.top;
rc.top = rc.left = 0;
DWORD dwStyle = WS_VISIBLE|WS_CHILD|LVS_REPORT|LVS_SINGLESEL|WS_BORDER;
if (!m_hWndList)
m_hWndList = m_ctlSysListView32.Create(m_hWnd, rc, _T(""), dwStyle);

_ASSERTE(m_hWndList);
put_ShowGridLines(m_bShowGridLines);

//
//we always want the list to be Full Row Select --
//note: this only works if the user has IE 3.0 installed
//
dwStyle = ListView_GetExtendedListViewStyle(m_hWndList);
dwStyle |= LVS_EX_FULLROWSELECT;
ListView_SetExtendedListViewStyle(m_hWndList, dwStyle);

//
//create the columns in our list
//
LV_COLUMN col;
memset(&col,NULL, sizeof(col));
col.mask = LVCF_TEXT|LVCF_WIDTH;
col.fmt = LVCFMT_LEFT;
int i;
for (i=0;i<NUMBER_COLUMNS;i++)
{
col.cx = m_nWidth[i];
col.pszText = sColNames[i];
col.cchTextMax  = lstrlen(col.pszText);
ListView_InsertColumn(m_hWndList, i, &col);
}

    QueryPerformanceFrequency((LARGE_INTEGER *)&PerformanceFrequency);

if (m_hFont)
{
// this is set if the container app has set our properties using the property bag
::SendMessage(m_hWndList, WM_SETFONT, (WPARAM)m_hFont, MAKELPARAM(TRUE,0));
}

return 0;
}

//
//update the UI in menus
//
LRESULT CMtsSpy::OnInitMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
HMENU hMenu = (HMENU)wParam;
CComBSTR temp = "Log to File ";
if (m_sLogFile.Length() > 5)
temp += m_sLogFile;
else
temp += "...";

USES_CONVERSION;
ModifyMenu(hMenu, ID_LOG, MF_BYCOMMAND|MF_STRING, ID_LOG, OLE2T(temp));
CheckMenuItem(hMenu, ID_LOG,  m_bLogToFile ? MF_BYCOMMAND|MF_CHECKED : MF_BYCOMMAND|MF_UNCHECKED);
CheckMenuItem(hMenu, ID_OPTIONS_GRID_LINES,  m_bShowGridLines ? MF_BYCOMMAND|MF_CHECKED : MF_BYCOMMAND|MF_UNCHECKED);
CheckMenuItem(hMenu, ID_SHOW_ON_SCREEN,  m_bShowOnScreen ? MF_BYCOMMAND|MF_CHECKED : MF_BYCOMMAND|MF_UNCHECKED);
CheckMenuItem(hMenu, ID_AUDIT,  m_bAudit ? MF_BYCOMMAND|MF_CHECKED : MF_BYCOMMAND|MF_UNCHECKED);

m_hMenuDebug = GetSubMenu(hMenu, 5);
_ASSERTE(m_hMenuDebug);
AddRunningAspsToDebugMenu(m_hMenuDebug);

return 0;
}

//
//sets a glog to log to file
//if the user hasn't chosen a log file, show the common dialog to allow them to select one
//
LRESULT CMtsSpy::OnLogToFile(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{

m_bLogToFile = 1 - m_bLogToFile;
if (m_bLogToFile)
{
if (m_sLogFile.Length() == 0)
{
BOOL bHandled = TRUE;
OnChooseLogFile(0,0,0,bHandled);
if (m_sLogFile.Length() == 0)
return 0;
}
m_hFile = CreateFile(m_sLogFile, GENERIC_WRITE,
0, (LPSECURITY_ATTRIBUTES) NULL,
OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);

if (!m_hFile)
{
m_bLogToFile = FALSE;
return 0;
}

}
else
{
if (m_hFile)
{
CloseHandle(m_hFile);
m_hFile = NULL;
}

}

return 0;
}
//
//User wants to choose a log file -- show the common dialog
//
LRESULT CMtsSpy::OnChooseLogFile(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{

USES_CONVERSION;
OPENFILENAME OpenFileName;
TCHAR         szFile[MAX_PATH];
memset(szFile, NULL, MAX_PATH * sizeof(TCHAR));
if (m_sLogFile.Length() > 2)
lstrcpy(szFile, OLE2T(m_sLogFile.m_str));
else
lstrcpy(szFile, _T("MTSSpy.Log"));

    // Fill in the OPENFILENAME structure to support a template and hook.
OpenFileName.lStructSize       = sizeof(OPENFILENAME);
    OpenFileName.hwndOwner         = m_hWndList;
    OpenFileName.hInstance         = _Module.GetModuleInstance( );
    OpenFileName.lpstrFilter       = _T("TAB seperated Files (*.log)\0*.log\0CSV Files (*.csv)\0*.csv\0");
    OpenFileName.lpstrCustomFilter = NULL;
    OpenFileName.nMaxCustFilter    = 0;
    OpenFileName.nFilterIndex      = 0;
    OpenFileName.lpstrFile          = szFile;
    OpenFileName.nMaxFile          = sizeof(szFile);
    OpenFileName.lpstrFileTitle    = NULL;
    OpenFileName.nMaxFileTitle     = 0;
    OpenFileName.lpstrInitialDir   = NULL;
    OpenFileName.lpstrTitle        = _T("Choose Log File");
    OpenFileName.nFileOffset       = 0;
    OpenFileName.nFileExtension    = 0;
    OpenFileName.lpstrDefExt       = NULL;
    OpenFileName.lCustData         = 0;
OpenFileName.lpfnHook    = NULL;
OpenFileName.lpTemplateName    = NULL;
    OpenFileName.Flags             = OFN_EXPLORER | OFN_HIDEREADONLY |OFN_NOREADONLYRETURN |OFN_CREATEPROMPT;

// Call the common dialog function.
    if (GetSaveFileName(&OpenFileName))
    {
m_sLogFile = OpenFileName.lpstrFile;
if (lstrcmpi(OpenFileName.lpstrFile + OpenFileName.nFileExtension, _T("csv")) == 0)
m_bCSV = TRUE;

}

return 0;

}

//
//show/hide gridlines in the list
//
//NOTE:  This will only work if the user has installed IE 3.0 or greater
//
LRESULT CMtsSpy::OnToggleGridLines(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{

m_bShowGridLines = 1 - m_bShowGridLines;

DWORD dwStyle = ListView_GetExtendedListViewStyle(m_hWndList);
(m_bShowGridLines) ? dwStyle |= LVS_EX_GRIDLINES :
 dwStyle &= ~LVS_EX_GRIDLINES;

ListView_SetExtendedListViewStyle(m_hWndList, dwStyle);

return 0;
}

//
//enable/disable auditing
//
//
LRESULT CMtsSpy::OnToggleAudit(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{

m_bAudit = 1 - m_bAudit;
HRESULT hr = EnableAudit(m_bAudit);
if (hr != S_OK)
m_bAudit = FALSE;
return 0;
}

LRESULT CMtsSpy::OnToggleShowOnScreen(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
m_bShowOnScreen = 1-m_bShowOnScreen;
return 0;

}

//
//show the standard Shell About box
//
LRESULT CMtsSpy::OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
HICON hIcon =  LoadIcon(_Module.GetModuleInstance(), MAKEINTRESOURCE(IDI_MAIN));

ShellAbout(m_hWnd, _T("MTS Spy"), _T("Version 1.00"), hIcon);
return 0;
}

//
//clear the list box
//
LRESULT CMtsSpy::OnClear(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{

ListView_DeleteAllItems(m_hWndList);
m_cEvents = 0;
return 0;
}

//
//save the items in the list box to the log file
//
LRESULT CMtsSpy::OnSave(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
USES_CONVERSION;
OPENFILENAME OpenFileName;
TCHAR         szFile[MAX_PATH];
memset(szFile, NULL, MAX_PATH * sizeof(TCHAR));
if (m_sLogFile.Length() > 2)
lstrcpy(szFile, OLE2T(m_sLogFile.m_str));
else
lstrcpy(szFile, _T("MTSSpy.Log"));

OpenFileName.lStructSize       = sizeof(OPENFILENAME);
    OpenFileName.hwndOwner         = m_hWndList;
    OpenFileName.hInstance         = _Module.GetModuleInstance( );
    OpenFileName.lpstrFilter       = NULL;
    OpenFileName.lpstrCustomFilter = NULL;
    OpenFileName.nMaxCustFilter    = 0;
    OpenFileName.nFilterIndex      = 0;
    OpenFileName.lpstrFile          = szFile;
    OpenFileName.nMaxFile          = sizeof(szFile);
    OpenFileName.lpstrFileTitle    = NULL;
    OpenFileName.nMaxFileTitle     = 0;
    OpenFileName.lpstrInitialDir   = NULL;
    OpenFileName.lpstrTitle        = _T("Log ToFile");
    OpenFileName.nFileOffset       = 0;
    OpenFileName.nFileExtension    = 0;
    OpenFileName.lpstrDefExt       = NULL;
    OpenFileName.lCustData         = 0;
OpenFileName.lpfnHook    = NULL;
OpenFileName.lpTemplateName    = NULL;
    OpenFileName.Flags             = OFN_EXPLORER | OFN_HIDEREADONLY |OFN_NOREADONLYRETURN;

// Call the common dialog function.
    if (GetSaveFileName(&OpenFileName))
    {

FILE* f = _tfopen(OpenFileName.lpstrFile, _T("a+"));
if (f)
{
int nCount = ListView_GetItemCount(m_hWndList);
int i, j;
TCHAR buf[MAX_MSG_LENGTH];
for (i=0; i<nCount; i++)
{
for (j=0; j < 4; j++)
{
ListView_GetItemText(m_hWndList, i, j, buf, MAX_MSG_LENGTH);
_ftprintf(f, _T("%s\t"),buf);
}
while ( i < nCount - 1)
{
i++; // move to next column

// now get the name/value pairs
ListView_GetItemText(m_hWndList, i, 4, buf, MAX_MSG_LENGTH);
if (*buf)
{

_ftprintf(f, _T("\n\t\t\t\t%s\t"), buf);
ListView_GetItemText(m_hWndList, i, 5, buf, MAX_MSG_LENGTH);
_ftprintf(f, _T("%s"),buf);
}
else
{
i--;
_ftprintf(f, _T("\n"), NULL);
break;
}
}
}

fclose(f);
}

}

return 0;

}

//
//given the index to the list, we return TRUE if the index
//should scroll -- this occurs when the user is looking
//at the last element in the list.  If they have scrolled
//up to look at a previous element in the list, then we
//don't scroll when an event is added
//
BOOL CMtsSpy::ShouldScroll(int nIndex)
{
BOOL bRet = FALSE;
int nCountPage = ListView_GetCountPerPage(m_hWndList);
int nTopIndex = ListView_GetTopIndex(m_hWndList);
if (nTopIndex + nCountPage>= nIndex)
bRet = TRUE;
return bRet;
}

//
//disconnect & destroy all sinks associated with the given package
//
HRESULT CMtsSpy::ShutdownPackage(LPCOLESTR sPackageName)
{

USES_CONVERSION;
CPackageInfo * pInfo = m_map[W2A(sPackageName)];
if (!pInfo)
{
_ASSERTE(0); // we should never get this unless we have a sink for (at least) the Package events
return E_FAIL;
}

int nElementsRemoved = m_map.erase(W2A(sPackageName));
_ASSERTE(nElementsRemoved == 1);
delete pInfo;
return S_OK;
}

//
//this method is called by all of the sinks to add an item to the list box
//
BOOL CMtsSpy::AddEventToList(LONGLONG perfCount, LPCOLESTR sEvent, LPCOLESTR sPackage)
{
    long TickCount = 0;
    if (PerformanceFrequency != 0)
        TickCount = (long)((1000 * perfCount) / PerformanceFrequency);

BOOL bRet;
if (m_bLogToFile)
{
TCHAR sSep[2];
ZeroMemory(sSep,2 * sizeof(TCHAR));
(m_bCSV) ? lstrcat(sSep, _T(",")) : lstrcat(sSep, _T("\t"));
sSep[1] = NULL;
_ASSERTE(m_hFile);
TCHAR sBuf[MAX_MSG_LENGTH];
ZeroMemory(sBuf, MAX_MSG_LENGTH*sizeof(TCHAR));

wsprintf(sBuf, _T("%s%s%d%s%s\n"), sEvent, sSep, TickCount, sSep, sPackage);

if (m_hFile)
{

DWORD cbWritten;
bRet = WriteFile(m_hFile, sBuf, lstrlen(sBuf) * sizeof(TCHAR), &cbWritten, NULL);

}
}
if (m_bShowOnScreen)
{
int nCount = ListView_GetItemCount(m_hWndList);
LV_ITEM item;
TCHAR sz[8];
wsprintf(sz,_TEXT("%ld"), m_cEvents++);
memset(&item, NULL, sizeof(item));
item.mask = LVIF_TEXT;
item.iItem = nCount;
item.pszText = sz;
item.cchTextMax = lstrlen(sz);

ListView_InsertItem(m_hWndList, &item);
ListView_SetItemText(m_hWndList, nCount, 1, (LPWSTR)sEvent);

TCHAR szTick[16];
wsprintf(szTick, _T("%ld"), TickCount);
ListView_SetItemText(m_hWndList, nCount, 2, szTick);
ListView_SetItemText(m_hWndList, nCount, 3, (LPWSTR)sPackage);
if (ShouldScroll(nCount))
ListView_EnsureVisible(m_hWndList, nCount, FALSE);
}
return TRUE;
}
//
//this method is called by all the sinks to add a Named/Value pair to the
//list
//
BOOL CMtsSpy::AddParamValueToList(LPCOLESTR sParamName, LPCOLESTR sValue)
{
BOOL bRet;
if (m_bLogToFile)
{
TCHAR sSep[2];
ZeroMemory(sSep,2 * sizeof(TCHAR));
(m_bCSV) ? lstrcat(sSep, _T(",")) : lstrcat(sSep, _T("\t"));
sSep[1] = NULL;
_ASSERTE(m_hFile);
TCHAR sBuf[MAX_MSG_LENGTH];
ZeroMemory(sBuf, MAX_MSG_LENGTH*sizeof(TCHAR));
wsprintf(sBuf, _T("%s%s%s%s%s%s\n"), sSep, sSep, sSep, sParamName,sSep, sValue);
if (m_hFile)
{

DWORD cbWritten;
bRet = WriteFile(m_hFile, sBuf, lstrlen(sBuf) * sizeof(TCHAR), &cbWritten, NULL);
}
}
if (m_bShowOnScreen)
{
int nCount = ListView_GetItemCount(m_hWndList);
LV_ITEM item;
item.mask = LVIF_TEXT;
item.iItem = nCount;
item.pszText = _T("");
item.cchTextMax = 1;
item.iSubItem = 0;
int nItem;
nItem = ListView_InsertItem(m_hWndList, &item);
ListView_SetItemText(m_hWndList, nItem, 4, (LPWSTR)sParamName);
ListView_SetItemText(m_hWndList, nItem, 5, (LPWSTR)sValue);
if (ShouldScroll(nCount))
ListView_EnsureVisible(m_hWndList, nItem, FALSE);
}

return TRUE;
}

//
//show the select packages dialog
//
LRESULT CMtsSpy::OnSelectPackages(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
CSelectEventsDlg dlg(&m_map, this);
dlg.DoModal();
return 0;
}

LRESULT CMtsSpy::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
bHandled = FALSE; // continue processing

//
//clean up our package information
//
MapStringToPackageInfo::iterator iter;
CPackageInfo * pInfo;
for (iter = m_map.begin(); iter != m_map.end(); ++iter)
{

pInfo = (*iter).second;
delete pInfo;
}
m_map.clear();
if (m_hFont)
{
DeleteObject(m_hFont);
m_hFont = NULL;
}

m_bLogToFile = FALSE;
if (m_hFile)
{
CloseHandle(m_hFile);
m_hFile = NULL;
}

m_cEvents = 0;
m_bShowGridLines = TRUE;
return 0;
}
//
//show only the ChooseFont property page
//
LRESULT CMtsSpy::OnChooseFont(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
HRESULT hr = S_OK;
CComPtr<IUnknown> pUnk;
ControlQueryInterface(IID_IUnknown, (void**)&pUnk);
_ASSERTE(pUnk != NULL);
LPOLESTR szTitle = _T("MTS Spy - Choose Font");
hr = OleCreatePropertyFrame(m_hWnd, m_rcPos.top, m_rcPos.left, szTitle,
1, &pUnk.p, 1, (GUID *)&CLSID_StockFontPage, LOCALE_USER_DEFAULT, 0, 0);

return 0;
}

STDMETHODIMP CMtsSpy::get_LogFile(BSTR * pVal)
{
*pVal = m_sLogFile.Copy();
return S_OK;
}

STDMETHODIMP CMtsSpy::put_LogFile(BSTR newVal)
{

HANDLE hTemp;
hTemp = CreateFile(newVal, GENERIC_WRITE,
0, (LPSECURITY_ATTRIBUTES) NULL,
OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);

if (hTemp)
{
if (m_hFile)
{
CloseHandle(m_hFile);
}

m_hFile = hTemp;
}
else
return E_INVALIDARG;

m_sLogFile = newVal;
return S_OK;
}

STDMETHODIMP CMtsSpy::get_ShowGridLines(BOOL * pVal)
{
*pVal = m_bShowGridLines;
return S_OK;
}

STDMETHODIMP CMtsSpy::put_ShowGridLines(BOOL newVal)
{

m_bShowGridLines = newVal;
DWORD dwStyle = ListView_GetExtendedListViewStyle(m_hWndList);
(m_bShowGridLines) ? dwStyle |= LVS_EX_GRIDLINES :
 dwStyle &= ~LVS_EX_GRIDLINES;

ListView_SetExtendedListViewStyle(m_hWndList, dwStyle);
FireViewChange();
return S_OK;
}

STDMETHODIMP CMtsSpy::get_Audit(BOOL * pVal)
{
*pVal = m_bAudit;
return S_OK;
}

STDMETHODIMP CMtsSpy::put_Audit(BOOL newVal)
{

m_bAudit = newVal;
EnableAudit(m_bAudit);
return S_OK;
}

STDMETHODIMP CMtsSpy::get_ColWidth(short nColumn, long * pVal)
{
_ASSERTE(m_hWndList);
_ASSERTE(nColumn >= 0 && nColumn <= NUMBER_COLUMNS);
*pVal = ListView_GetColumnWidth(m_hWndList, nColumn);
return S_OK;
}

STDMETHODIMP CMtsSpy::put_ColWidth(short nColumn, long newVal)
{
if (nColumn >= NUMBER_COLUMNS)
return E_INVALIDARG;

m_nWidth[nColumn] = newVal;
ListView_SetColumnWidth(m_hWndList, nColumn, newVal);
FireViewChange();
return S_OK;
}

HRESULT CMtsSpy::IPersistStreamInit_Load(LPSTREAM pStm, ATL_PROPMAP_ENTRY* pMap)
{

ULONG cbRead;

// read in the version
UINT nVersion;
pStm -> Read((void *)&nVersion, sizeof(UINT), &cbRead);
if (nVersion != 3)
{
::MessageBox(m_hWndList, _T("The saved information is from a different version of the control.\n\nThe saved state will have to be discarded."), _T("MTS Spy"), MB_ICONINFORMATION);
return S_OK;
}

// get log file size
long lLen;
pStm -> Read((void *)&lLen, sizeof(long), &cbRead);

// read in the chars for the log name
char * pLog = new char[lLen+1];
ZeroMemory(pLog, lLen+1);
pStm -> Read((void*)pLog, lLen, &cbRead);

m_sLogFile = pLog;
delete [] pLog;

// gridlines
pStm -> Read((void *)&m_bShowGridLines, sizeof(BOOL),&cbRead);
put_ShowGridLines(m_bShowGridLines);

// show on screen
pStm -> Read((void *)&m_bShowOnScreen, sizeof(BOOL),&cbRead);
put_ShowOnScreen(m_bShowOnScreen);

// log to file
pStm -> Read((void *)&m_bLogToFile, sizeof(BOOL),&cbRead);
put_LogToFile(m_bLogToFile);

// csv files
pStm -> Read((void *)&m_bCSV, sizeof(BOOL), &cbRead);

// csv files
pStm -> Read((void *)&m_bAudit, sizeof(BOOL), &cbRead);
EnableAudit(m_bAudit);

// read the col widths
int i;
long nWidth;
for (i=0;i<NUMBER_COLUMNS;i++)
{
pStm -> Read((void *)&nWidth, sizeof(long), &cbRead);
put_ColWidth(i, nWidth);
}

BOOL bCustomFont = FALSE;
pStm -> Read((void *)&bCustomFont, sizeof(BOOL),&cbRead);
if (bCustomFont)
{
if (!m_pFont)
{
//create an IFontDisp
HRESULT hr= CoCreateInstance(CLSID_StdFont, NULL, CLSCTX_ALL, IID_IFontDisp, (void **)&m_pFont);
_ASSERTE(SUCCEEDED(hr));
}
if (m_pFont)
{
IPersistStream * pFontSt = NULL;
m_pFont -> QueryInterface(IID_IPersistStream, (void **)&pFontSt);
pFontSt -> Load(pStm);
pFontSt -> Release();
if (m_hFont) // should I tell the list that I deleted its font?
DeleteObject(m_hFont);
IFont * pFont = NULL;
m_pFont -> QueryInterface(IID_IFont, (void **)&pFont);
_ASSERTE(pFont);
m_hFont = CreateHFontFromIFont(pFont);
pFont -> Release();
}
}

return S_OK;
}
HRESULT CMtsSpy::IPersistStreamInit_Save(LPSTREAM pStm, BOOL /* fClearDirty */, ATL_PROPMAP_ENTRY* pMap)
{
_ASSERTE(this);
_ASSERTE(m_hWndList);

ULONG cbWritten = 0;
// write a version
UINT nVersion  = 3;
pStm -> Write((void *)&nVersion, sizeof(UINT), &cbWritten);

// write the log file size
long nChars = m_sLogFile.Length();
pStm -> Write((void *)&nChars, sizeof(long), &cbWritten);
_ASSERTE(cbWritten == sizeof(long));

// write the log file name (in ANSI!)
USES_CONVERSION;
char * sFile = W2A((BSTR)m_sLogFile);
pStm -> Write((void *)sFile, m_sLogFile.Length(), &cbWritten);

// write the BOOL if gridlines should be shown
pStm -> Write((void *)&m_bShowGridLines, sizeof(BOOL), &cbWritten);

// write the BOOL if the user wants to see the messages
pStm -> Write((void *)&m_bShowOnScreen, sizeof(BOOL), &cbWritten);

// write the BOOL if the user wants to log the messages
pStm -> Write((void *)&m_bLogToFile, sizeof(BOOL), &cbWritten);

// write the BOOL if we have CSV files
pStm -> Write((void *)&m_bCSV, sizeof(BOOL), &cbWritten);

// write the BOOL if Auditing is enabled
pStm -> Write((void *)&m_bAudit, sizeof(BOOL), &cbWritten);

// write the col widths
int i;
long nWidth;
for (i=0;i<NUMBER_COLUMNS;i++)
{
get_ColWidth(i, &nWidth);
_ASSERTE(nWidth);
pStm -> Write((void *)&nWidth, sizeof(long), &cbWritten);
}

// ask the font to persist itself in the stream
BOOL bCustomFont;
if (m_pFont)
{
bCustomFont = TRUE;
pStm -> Write((void *)&bCustomFont, sizeof(BOOL), &cbWritten);
IPersistStream * pSt = NULL;
m_pFont -> QueryInterface(IID_IPersistStream, (void **)&pSt);
_ASSERTE(pSt);
pSt -> Save(pStm, FALSE);
pSt -> Release();
}
else
{
bCustomFont = FALSE;
pStm -> Write((void *)&bCustomFont, sizeof(BOOL), &cbWritten);
}

return S_OK;
}

HRESULT CMtsSpy::Close( DWORD dwSaveOption )
{
return IOleObject_Close(dwSaveOption);
}

HFONT CMtsSpy::CreateHFontFromIFont(IFont * pFont)
{

_ASSERTE(pFont);
HFONT hFont;
LOGFONT lf;
ZeroMemory(&lf, sizeof(lf));
CComBSTR bstr;
pFont -> get_Name(&bstr);
lstrcpy(lf.lfFaceName, bstr.m_str);
CY cy;
pFont -> get_Size(&cy);
HDC hDC = ::GetDC(NULL);
_ASSERTE(hDC);
long lCaps = GetDeviceCaps(hDC, LOGPIXELSY);
lf.lfHeight =  -MulDiv(cy.Hi, lCaps, 72);
lf.lfHeight += -MulDiv(cy.Lo, lCaps, 720000);
::ReleaseDC(NULL, hDC);
short sVal;
pFont -> get_Weight(&sVal);
lf.lfWeight  = sVal;
BOOL bFlag;
pFont -> get_Italic(&bFlag);
lf.lfItalic = bFlag;
pFont -> get_Underline(&bFlag);
lf.lfUnderline = bFlag;
pFont -> get_Strikethrough(&bFlag);
lf.lfStrikeOut = bFlag;
pFont -> get_Charset(&sVal);
lf.lfCharSet = sVal;
hFont = CreateFontIndirect(&lf);
return hFont;

}

STDMETHODIMP CMtsSpy::SelectPackages()
{
BOOL bHandled;
OnSelectPackages(0,0,0,bHandled);
return S_OK;
}

STDMETHODIMP CMtsSpy::SaveToFile()
{

BOOL bHandled;
OnSave(0,0,0,bHandled);
return S_OK;
}

STDMETHODIMP CMtsSpy::ClearAllEvents()
{

BOOL bHandled;
OnClear(0,0,0,bHandled);
return S_OK;
}

STDMETHODIMP CMtsSpy::About()
{
BOOL bHandled;
OnAbout(0,0,0,bHandled);
return S_OK;
}

STDMETHODIMP CMtsSpy::get_LogToFile(BOOL * pVal)
{
*pVal = m_bLogToFile;
return S_OK;
}

STDMETHODIMP CMtsSpy::put_LogToFile(BOOL newVal)
{
m_bLogToFile = newVal;
if (m_bLogToFile)
{
if (m_sLogFile.Length() == 0)
{
BOOL bHandled = TRUE;
OnChooseLogFile(0,0,0,bHandled);
if (m_sLogFile.Length() == 0)
return 0;
}

if (m_hFile)
{
CloseHandle(m_hFile);
m_hFile = NULL;
}

m_hFile = CreateFile(m_sLogFile, GENERIC_WRITE,
0, (LPSECURITY_ATTRIBUTES) NULL,
OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL,
(HANDLE) NULL);

if (!m_hFile)
{
m_bLogToFile = FALSE;
return E_FAIL;
}

}
else
{
if (m_hFile)
{
CloseHandle(m_hFile);
m_hFile = NULL;
}

}
return S_OK;
}

STDMETHODIMP CMtsSpy::ChooseFont()
{
BOOL bHandled;
OnChooseFont(0,0,0,bHandled);
return S_OK;
}

STDMETHODIMP CMtsSpy::get_ShowOnScreen(BOOL * pVal)
{
*pVal = m_bShowOnScreen;
return S_OK;
}

STDMETHODIMP CMtsSpy::put_ShowOnScreen(BOOL newVal)
{
m_bShowOnScreen = newVal;
return S_OK;
}

STDMETHODIMP CMtsSpy::ChooseLogFile(BSTR * sLogFileName, BOOL * bCanceled)
{
*bCanceled = TRUE;
BOOL bHandled = TRUE;
OnChooseLogFile(0,0,0,bHandled);
if (m_sLogFile.Length() == 0)
{
return S_OK;
}

*sLogFileName = ::SysAllocString(m_sLogFile.m_str);
*bCanceled = FALSE;
return S_OK;
}
//
//loop through the ROT and add package names
//to the debug menu
//
BOOL CMtsSpy::AddRunningAspsToDebugMenu(HMENU hMenu)
{

//
//remove the <No Running Packages> menu item
//

RemoveMenu(hMenu, 0, MF_BYPOSITION);

HRESULT hr = E_FAIL;
IMtsGrp * pMtsGrp = NULL;
hr = CoCreateInstance (CLSID_MtsGrp, NULL, CLSCTX_ALL, IID_IMtsGrp, (void **)&pMtsGrp);
if (!pMtsGrp)
{
_ASSERTE(0);
return FALSE;
}

long lPackages;
pMtsGrp -> Refresh(); // its important to call this!
pMtsGrp -> get_Count(&lPackages);

IUnknown * pUnk = NULL;
IMtsEvents * pEvents = NULL;
USES_CONVERSION;
long lPID;
TCHAR szBuf[128];
for (int i=0; i<lPackages; i++)
{

pMtsGrp -> Item(i, &pUnk);
_ASSERTE(pUnk);
pUnk -> QueryInterface(IID_IMtsEvents, (void **)&pEvents);
_ASSERTE(pEvents);
if (SUCCEEDED(hr))
{

CComBSTR sName;
pEvents -> get_PackageName(&sName.m_str);
pEvents -> GetProcessID(&lPID);
ZeroMemory(szBuf, 128*sizeof(TCHAR));
if (sName.m_str)
{
wsprintf(szBuf, _T("%s - PID: %ld"), sName.m_str, lPID);
int nRet = InsertMenu(hMenu, -1, MF_BYPOSITION|MF_STRING, ID_DEBUG_BEGIN + i, szBuf);
}
SafeRelease(pEvents);

}

SafeRelease(pEvents);
SafeRelease(pUnk);
}

SafeRelease(pMtsGrp);
return TRUE;
}

//
//user wants to debug a package --
//
//NOTE:   This is designed to work with Microsoft Developer Studio
//if you want to add support for another tool, modify the
//ShellExecute() line below.  This is alson non-robust in
//the sense that it requires MSDEV.EXE to be in the path
//and set up correctly.
//
LRESULT CMtsSpy::OnDebugPackage(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{

HCURSOR hBusy = LoadCursor(NULL, IDC_WAIT);
HCURSOR hOldCur = SetCursor(hBusy);
TCHAR szBuf[128];
ZeroMemory(szBuf, 128 * sizeof(TCHAR));
int nChars;
_ASSERTE(m_hMenuDebug);
nChars = GetMenuString(m_hMenuDebug, wID, szBuf, 128, MF_BYCOMMAND);
if (nChars)
{
int nCount = lstrlen(szBuf);
LPTSTR p = szBuf + nCount;
while (*p-- != ':' && nCount)
{
nCount --;
}

if (nCount == 0)
{
SetCursor(hOldCur);
return 0;
}

p+=3;
TCHAR buf[MAX_PATH];
wsprintf(buf, _T("-p %s -e %s"), p, p);
HINSTANCE hInst = ShellExecute( NULL, _T("open"), _T("msdev"), buf, NULL, SW_SHOWNORMAL);
}
SetCursor(hOldCur);
return 0;
}
//
//Enable/Disable auditing
//
HRESULT CMtsSpy::EnableAudit(BOOL bEnable)
{

if (!bEnable)
{
SafeRelease(m_pSqlAudit);
return S_OK;
}

if (m_pSqlAudit)// make EnableAudit() idempotent -- we are already enabled
return S_OK;

HRESULT hr;
hr = CoCreateInstance(CLSID_MtsSqlAudit, NULL, CLSCTX_ALL, IID_IMtsSqlAudit, (void **)&m_pSqlAudit);
if (SUCCEEDED(hr))
{
//
//these hard coded strings (user and pw) should be looked up in the registry for a production
//app
hr = m_pSqlAudit -> Init(L"MtsAudit", L"sa", L"");
if (FAILED(hr))
{
LPTSTR sMsg = _T("Unable to connect to DSN 'MtsAudit'. Make sure the DSN\nexists, that has it \
the schema contained in MtsAudit.SQL,\nand that you have permission to the database.");

::MessageBox(m_hWndList, sMsg,  _T("MtsSpy"), MB_ICONSTOP);
SafeRelease(m_pSqlAudit);
}
}
else
{

::MessageBox(m_hWndList, _T("Unable to create the MtsAudit component!"),  _T("MtsSpy"), MB_ICONSTOP);
}

return hr;
}


MTSSPY.H

// MtsSpy.h : Declaration of the CMtsSpy

#ifndef __MTSSPY_H_
#define __MTSSPY_H_

#include "resource.h"       // main symbols

#include "mtsevents.h"

#define IF_AUDIT_DO(x)\
if (m_pSpy -> Audit())\
m_pSpy -> GetAuditObj() -> x

/////////////////////////////////////////////////////////////////////////////
// CMtsSpy
class ATL_NO_VTABLE CMtsSpy :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CMtsSpy, &CLSID_MtsSpy>,
public CComControl<CMtsSpy>,
public CStockPropImpl<CMtsSpy, IMtsSpy, &IID_IMtsSpy, &LIBID_MTSSPYCTLLib>,
public IProvideClassInfo2Impl<&CLSID_MtsSpy, NULL, &LIBID_MTSSPYCTLLib>,
public IPersistStreamInitImpl<CMtsSpy>,
public IPersistStorageImpl<CMtsSpy>,
public IQuickActivateImpl<CMtsSpy>,
public IOleControlImpl<CMtsSpy>,
public IOleObjectImpl<CMtsSpy>,
public IOleInPlaceActiveObjectImpl<CMtsSpy>,
public IViewObjectExImpl<CMtsSpy>,
public IOleInPlaceObjectWindowlessImpl<CMtsSpy>,
public IDataObjectImpl<CMtsSpy>,
public ISpecifyPropertyPagesImpl<CMtsSpy>,
public IPropertyBag
{

private:
HRESULT EnableAudit(BOOL bEnable);
MapStringToPackageInfo m_map;
BOOL m_bLogToFile;
HANDLE m_hFile;
CComBSTR m_sLogFile;
int m_cEvents;
HWND m_hWndList;
BOOL m_bShowGridLines;
BOOL m_bAudit;
HFONT m_hFont;
long m_nWidth[NUMBER_COLUMNS];
BOOL m_bShowOnScreen;
BOOL m_bCSV;
HMENU m_hMenuDebug; //the Debug menu
IMtsSqlAudit * m_pSqlAudit;

public:

BOOL Audit(){return m_bAudit;}
IMtsSqlAudit * GetAuditObj(){return m_pSqlAudit;}
CComPtr<IFontDisp> m_pFont;
CContainedWindow m_ctlSysListView32;
~CMtsSpy()
{
m_hWndList = NULL;

}
void FinalRelease()
{
SafeRelease(m_pSqlAudit);
}
CMtsSpy() :
m_ctlSysListView32(_T("SysListView32"), this, 1)
{
 m_hWndList = NULL;
m_bLogToFile = FALSE;
m_hFile = NULL;
m_cEvents = 0;
m_bShowGridLines = TRUE;
m_bWindowOnly = TRUE;
for (int i=0;i<NUMBER_COLUMNS;i++)
{
m_nWidth[i] = 60;
}

m_hFont = NULL;
m_bShowOnScreen = TRUE;
m_bCSV = FALSE;
m_bAudit = FALSE;
m_pSqlAudit = NULL;
}

BOOL ShouldScroll(int nIndex);
HRESULT ShutdownPackage(LPCOLESTR sPackageName);
BOOL AddEventToList(LONGLONG perfCount, LPCOLESTR sEvent, LPCOLESTR sPackage);
BOOL AddParamValueToList(LPCOLESTR sParamName, LPCOLESTR sValue);

DECLARE_REGISTRY_RESOURCEID(IDR_MTSSPY)

BEGIN_COM_MAP(CMtsSpy)
COM_INTERFACE_ENTRY(IMtsSpy)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY_IMPL(IViewObjectEx)
COM_INTERFACE_ENTRY_IMPL_IID(IID_IViewObject2, IViewObjectEx)
COM_INTERFACE_ENTRY_IMPL_IID(IID_IViewObject, IViewObjectEx)
COM_INTERFACE_ENTRY_IMPL(IOleInPlaceObjectWindowless)
COM_INTERFACE_ENTRY_IMPL_IID(IID_IOleInPlaceObject, IOleInPlaceObjectWindowless)
COM_INTERFACE_ENTRY_IMPL_IID(IID_IOleWindow, IOleInPlaceObjectWindowless)
COM_INTERFACE_ENTRY_IMPL(IOleInPlaceActiveObject)
COM_INTERFACE_ENTRY_IMPL(IOleControl)
COM_INTERFACE_ENTRY_IMPL(IOleObject)
COM_INTERFACE_ENTRY_IMPL(IQuickActivate)
COM_INTERFACE_ENTRY_IMPL(IPersistStorage)
COM_INTERFACE_ENTRY_IMPL(IPersistStreamInit)
COM_INTERFACE_ENTRY_IMPL(ISpecifyPropertyPages)
COM_INTERFACE_ENTRY_IMPL(IDataObject)
COM_INTERFACE_ENTRY(IProvideClassInfo)
COM_INTERFACE_ENTRY(IProvideClassInfo2)
COM_INTERFACE_ENTRY(IPropertyBag)
END_COM_MAP()

BEGIN_PROPERTY_MAP(CMtsSpy)
PROP_ENTRY_EX( "LogFile", DISPID_LOGFILE, CLSID_MtsSpyPropPage, IID_IMtsSpy)
PROP_ENTRY_EX( "ShowGridLines", DISPID_GRIDLINES, CLSID_MtsSpyPropPage, IID_IMtsSpy)
PROP_ENTRY_EX( "Audit", DISPID_AUDIT, CLSID_MtsSpyPropPage, IID_IMtsSpy)
PROP_ENTRY_EX( "ColWidth", DISPID_COLWIDTH, CLSID_MtsSpyPropPage, IID_IMtsSpy)
PROP_PAGE(CLSID_StockFontPage)
END_PROPERTY_MAP()

BEGIN_MSG_MAP(CMtsSpy)
MESSAGE_HANDLER(WM_PAINT, OnPaint)
MESSAGE_HANDLER(WM_SETFOCUS, OnSetFocus)
MESSAGE_HANDLER(WM_KILLFOCUS, OnKillFocus)
MESSAGE_HANDLER(WM_CREATE, OnCreate)

ALT_MSG_MAP(1)
MESSAGE_HANDLER(WM_RBUTTONDOWN, OnRButtonDown)
COMMAND_ID_HANDLER(ID_LOG, OnLogToFile)
COMMAND_ID_HANDLER(ID_CHOOSE_LOG_FILE_NAME, OnChooseLogFile)
COMMAND_ID_HANDLER(ID_SAVE, OnSave)
COMMAND_ID_HANDLER(ID_CLEAR, OnClear)
COMMAND_ID_HANDLER(ID_OPTIONS_GRID_LINES, OnToggleGridLines)
COMMAND_ID_HANDLER(ID_AUDIT, OnToggleAudit)
COMMAND_ID_HANDLER(ID_SHOW_ON_SCREEN, OnToggleShowOnScreen)
COMMAND_ID_HANDLER(IDM_ABOUT, OnAbout)
MESSAGE_HANDLER(WM_INITMENU, OnInitMenu)
COMMAND_ID_HANDLER(IDM_SELECT_PACKAGES, OnSelectPackages)
COMMAND_ID_HANDLER(ID_CHOOSEFONT, OnChooseFont)
MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
COMMAND_RANGE_HANDLER(ID_DEBUG_BEGIN, ID_DEBUG_END, OnDebugPackage)
END_MSG_MAP()

LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnRButtonDown(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnLogToFile(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnChooseLogFile(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnInitMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnSave(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnClear(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnToggleGridLines(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnToggleAudit(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnAbout(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnSelectPackages(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnChooseFont(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnToggleShowOnScreen(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnDebugPackage(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);

STDMETHOD(Close)(DWORD dwSaveOption);

STDMETHOD(SetObjectRects)(LPCRECT prcPos,LPCRECT prcClip)
{
IOleInPlaceObjectWindowlessImpl<CMtsSpy>::SetObjectRects(prcPos, prcClip);
int cx, cy;
cx = prcPos->right - prcPos->left;
cy = prcPos->bottom - prcPos->top;
::SetWindowPos(m_ctlSysListView32.m_hWnd, NULL, 0,
0, cx, cy, SWP_NOZORDER | SWP_NOACTIVATE);

return S_OK;
}

// IViewObjectEx
STDMETHOD(GetViewStatus)(DWORD* pdwStatus)
{
ATLTRACE(_T("IViewObjectExImpl::GetViewStatus\n"));
*pdwStatus = VIEWSTATUS_SOLIDBKGND | VIEWSTATUS_OPAQUE;
return S_OK;
}

// IMtsSpy
public:
BOOL AddRunningAspsToDebugMenu(HMENU hMenu);
STDMETHOD(ChooseLogFile)(/*[out]*/ BSTR * sLogFileName, /*[out]*/ BOOL * bCanceled);
STDMETHOD(get_ShowOnScreen)(/*[out, retval]*/ BOOL *pVal);
STDMETHOD(put_ShowOnScreen)(/*[in]*/ BOOL newVal);
STDMETHOD(get_Audit)(/*[out, retval]*/ BOOL *pVal);
STDMETHOD(put_Audit)(/*[in]*/ BOOL newVal);
STDMETHOD(ChooseFont)();
STDMETHOD(get_LogToFile)(/*[out, retval]*/ BOOL *pVal);
STDMETHOD(put_LogToFile)(/*[in]*/ BOOL newVal);
STDMETHOD(About)();
STDMETHOD(ClearAllEvents)();
STDMETHOD(SaveToFile)();
STDMETHOD(SelectPackages)();
HFONT CreateHFontFromIFont(IFont * pFont);
STDMETHOD(get_ColWidth)(short nColumn, /*[out, retval]*/ long *pVal);
STDMETHOD(put_ColWidth)(short nColumn, /*[in]*/ long newVal);
STDMETHOD(get_ShowGridLines)(/*[out, retval]*/ BOOL *pVal);
STDMETHOD(put_ShowGridLines)(/*[in]*/ BOOL newVal);
STDMETHOD(get_LogFile)(/*[out, retval]*/ BSTR *pVal);
STDMETHOD(put_LogFile)(/*[in]*/ BSTR newVal);
HRESULT OnDrawAdvanced(ATL_DRAWINFO& di);

HRESULT STDMETHODCALLTYPE putref_Font(IFontDisp* pFont)
{
CComQIPtr<IFont, &IID_IFont> pIFont(pFont);
if (pIFont)
{
if (m_hFont)
{
DeleteObject(m_hFont);
m_hFont = NULL;

}
m_hFont = CreateHFontFromIFont(pIFont);
if (m_hFont)
{
::SendMessage(m_hWndList, WM_SETFONT, (WPARAM)m_hFont, 0L);
}
}

if (FireOnRequestEdit(DISPID_FONT) == S_FALSE)
return S_FALSE;
m_pFont = pFont;
m_bRequiresSave = TRUE;
FireOnChanged(DISPID_FONT);
FireViewChange();
return S_OK;

}

STDMETHOD(Read)(LPCOLESTR pszPropName,VARIANT* pVar,IErrorLog* pErrorLog )
{
return S_OK;
}

STDMETHOD(Write)(LPCOLESTR pszPropName,VARIANT* pVar)
{
return S_OK;
}

HRESULT IPersistStreamInit_Load(LPSTREAM pStm, ATL_PROPMAP_ENTRY* pMap);
HRESULT IPersistStreamInit_Save(LPSTREAM pStm, BOOL /* fClearDirty */,
ATL_PROPMAP_ENTRY* pMap);

};

#endif //__MTSSPY_H_


MTSSPYCTL.CPP

// MtsSpyCtl.cpp : Implementation of DLL Exports.

// Note: Proxy/Stub Information
//To merge the proxy/stub code into the object DLL, add the file
//dlldatax.c to the project.  Make sure precompiled headers
//are turned off for this file, and add _MERGE_PROXYSTUB to the
//defines for the project.
//
//If you are not running WinNT4.0 or Win95 with DCOM, then you
//need to remove the following define from dlldatax.c
//#define _WIN32_WINNT 0x0400
//
//Further, if you are running MIDL without /Oicf switch, you also
//need to remove the following define from dlldatax.c.
//#define USE_STUBLESS_PROXY
//
//Modify the custom build rule for MtsSpyCtl.idl by adding the following
//files to the Outputs.
//MtsSpyCtl_p.c
//dlldata.c
//To build a separate proxy/stub DLL,
//run nmake -f MtsSpyCtlps.mk in the project directory.

#include "stdafx.h"
#include "resource.h"
#include "initguid.h"
#include "MtsSpyCtl.h"
#include "MtsAudit.h"
#include "MtsSpy.h"
#include "mtsevents.h"

#include "MtsSink.h"
#include "packagesink.h"
#include "PackageInfo.h"
#include "MtsSpyPropPage.h"

#define IID_DEFINED
#include "MtsAudit_i.c"
#include "mtsspyctl_i.c"
#include "eventcpts_i.c"
#include "mtsevents_i.c"

#ifdef _MERGE_PROXYSTUB
extern "C" HINSTANCE hProxyDll;
#endif

CComModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_MtsSpy, CMtsSpy)
OBJECT_ENTRY(CLSID_MtsSpyPropPage, CMtsSpyPropPage)
END_OBJECT_MAP()

/////////////////////////////////////////////////////////////////////////////
// DLL Entry Point

extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
lpReserved;
#ifdef _MERGE_PROXYSTUB
if (!PrxDllMain(hInstance, dwReason, lpReserved))
return FALSE;
#endif
if (dwReason == DLL_PROCESS_ATTACH)
{
_Module.Init(ObjectMap, hInstance);
DisableThreadLibraryCalls(hInstance);
}
else if (dwReason == DLL_PROCESS_DETACH)
_Module.Term();
return TRUE;    // ok
}

/////////////////////////////////////////////////////////////////////////////
// Used to determine whether the DLL can be unloaded by OLE

STDAPI DllCanUnloadNow(void)
{
#ifdef _MERGE_PROXYSTUB
if (PrxDllCanUnloadNow() != S_OK)
return S_FALSE;
#endif
return (_Module.GetLockCount()==0) ? S_OK : S_FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// Returns a class factory to create an object of the requested type

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
#ifdef _MERGE_PROXYSTUB
if (PrxDllGetClassObject(rclsid, riid, ppv) == S_OK)
return S_OK;
#endif
return _Module.GetClassObject(rclsid, riid, ppv);
}

/////////////////////////////////////////////////////////////////////////////
// DllRegisterServer - Adds entries to the system registry

STDAPI DllRegisterServer(void)
{
#ifdef _MERGE_PROXYSTUB
HRESULT hRes = PrxDllRegisterServer();
if (FAILED(hRes))
return hRes;
#endif
// registers object, typelib and all interfaces in typelib
return _Module.RegisterServer(TRUE);
}

/////////////////////////////////////////////////////////////////////////////
// DllUnregisterServer - Removes entries from the system registry

STDAPI DllUnregisterServer(void)
{
#ifdef _MERGE_PROXYSTUB
PrxDllUnregisterServer();
#endif
_Module.UnregisterServer();
return S_OK;
}


MTSSPYPROPPAGE.CPP

// MtsSpyPropPage.cpp : Implementation of CMtsSpyPropPage
#include "stdafx.h"
#include "MtsSpyCtl.h"
#include "MtsAudit.h"
#include "MtsSpy.h"
#include "MtsSpyPropPage.h"
#include "mtsevents.h"
#include "MtsSink.h"
#include "objsink.h"
#include "PackageSink.h"
#include "PackageInfo.h"

TCHAR * szColNames[] =
{
_T("Count"),
_T("Event"),
_T("TickCount"),
_T("Package"),
_T("Parameter"),
_T("Value")
};
/////////////////////////////////////////////////////////////////////////////
// CMtsSpyPropPage

LRESULT CMtsSpyPropPage::OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{

bHandled = FALSE;

m_ppUnk[0]->QueryInterface(IID_IMtsSpy, (void**)&m_pSpy);
_ASSERTE(m_pSpy);
CComBSTR bstr;
m_pSpy -> get_LogFile(&bstr);
SetDlgItemText(IDC_LOG_FILE, bstr);

// grid lines
BOOL bVal;
m_pSpy -> get_ShowGridLines(&bVal);
SendDlgItemMessage(IDC_SHOW_GRID_LINES, BM_SETCHECK,  (bVal) ? BST_CHECKED : BST_UNCHECKED);

// show on screen
m_pSpy -> get_ShowOnScreen(&bVal);
SendDlgItemMessage(IDC_SHOW_ON_SCREEN, BM_SETCHECK,  (bVal) ? BST_CHECKED : BST_UNCHECKED);

// log to file
m_pSpy -> get_LogToFile(&bVal);
SendDlgItemMessage(IDC_LOG_TO_FILE, BM_SETCHECK,  (bVal) ? BST_CHECKED : BST_UNCHECKED);

int i;
for (i=0;i<sizeof(szColNames)/sizeof(szColNames[0]);i++)
SendDlgItemMessage(IDC_COLUMN_NAMES, CB_ADDSTRING, 0, (LPARAM)(LPTSTR)szColNames[i]);

SendDlgItemMessage(IDC_COLUMN_NAMES, CB_SETCURSEL, 0, 0);
BOOL bH;
OnSelectColumn(CBN_SELCHANGE, 0,0, bH);
return 0;
}

LRESULT CMtsSpyPropPage::OnSelectColumn(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
if (wNotifyCode == CBN_SELCHANGE)
{
int nSel = SendDlgItemMessage(IDC_COLUMN_NAMES, CB_GETCURSEL, 0, 0L);
if (nSel != CB_ERR)
{
// get the width

long lWidth;
_ASSERTE(m_pSpy);
m_pSpy -> get_ColWidth(nSel, &lWidth);
SetDlgItemInt(IDC_WIDTH, lWidth);
}

}

return 0;
}

STDMETHODIMP CMtsSpyPropPage::Apply()
{
ATLTRACE(_T("CMtsSpyPropPage::Apply\n"));
for (UINT i = 0; i < m_nObjects; i++)
{

CComQIPtr<IMtsSpy, &IID_IMtsSpy> pSpy(m_ppUnk[i]);
BSTR bstr;
GetDlgItemText(IDC_LOG_FILE, bstr);
pSpy -> put_LogFile(bstr);
::SysFreeString(bstr);
// grid lines
BOOL bVal = (SendDlgItemMessage(IDC_SHOW_GRID_LINES, BM_GETCHECK) == BST_CHECKED);
pSpy -> put_ShowGridLines(bVal);

// log to file
bVal = (SendDlgItemMessage(IDC_LOG_TO_FILE, BM_GETCHECK) == BST_CHECKED);
pSpy -> put_LogToFile(bVal);

// show on screen
bVal = (SendDlgItemMessage(IDC_SHOW_ON_SCREEN, BM_GETCHECK) == BST_CHECKED);
pSpy -> put_ShowOnScreen(bVal);

int j;
UINT nWidth;
for (j=0;j<sizeof(szColNames)/sizeof(szColNames[0]);j++)
{

nWidth = SendDlgItemMessage(IDC_COLUMN_NAMES, CB_GETITEMDATA, j) ;
if (nWidth)
{
pSpy -> put_ColWidth(j,nWidth);
SendDlgItemMessage(IDC_COLUMN_NAMES, CB_SETITEMDATA, j, 0);
}
}

}
SetDirty(FALSE);
m_bDirty = FALSE;
return S_OK;
}


MTSSPYPROPPAGE.H

// MtsSpyPropPage.h : Declaration of the CMtsSpyPropPage

#ifndef __MTSSPYPROPPAGE_H_
#define __MTSSPYPROPPAGE_H_

#include "resource.h"       // main symbols

EXTERN_C const CLSID CLSID_MtsSpyPropPage;

/////////////////////////////////////////////////////////////////////////////
// CMtsSpyPropPage
class ATL_NO_VTABLE CMtsSpyPropPage :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CMtsSpyPropPage, &CLSID_MtsSpyPropPage>,
public IPropertyPageImpl<CMtsSpyPropPage>,
public CDialogImpl<CMtsSpyPropPage>
{

public:
CComPtr<IMtsSpy> m_pSpy;

CMtsSpyPropPage()
{
m_dwTitleID = IDS_TITLEMtsSpyPropPage;
m_dwHelpFileID = IDS_HELPFILEMtsSpyPropPage;
m_dwDocStringID = IDS_DOCSTRINGMtsSpyPropPage;
m_bDirty = FALSE;
}

enum {IDD = IDD_MTSSPYPROPPAGE};

DECLARE_REGISTRY_RESOURCEID(IDR_MTSSPYPROPPAGE)

BEGIN_COM_MAP(CMtsSpyPropPage)
COM_INTERFACE_ENTRY_IMPL(IPropertyPage)
END_COM_MAP()

BEGIN_MSG_MAP(CMtsSpyPropPage)
CHAIN_MSG_MAP(IPropertyPageImpl<CMtsSpyPropPage>)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
COMMAND_ID_HANDLER(IDC_COLUMN_NAMES, OnSelectColumn)
COMMAND_HANDLER(IDC_WIDTH, EN_CHANGE, OnWidthChange)
COMMAND_HANDLER(IDC_WIDTH, EN_CHANGE, OnLogFileChange)
COMMAND_HANDLER(IDC_SHOW_GRID_LINES, BN_CLICKED, OnCheckBoxClick)
COMMAND_HANDLER(IDC_LOG_TO_FILE, BN_CLICKED, OnCheckBoxClick)
COMMAND_HANDLER(IDC_SHOW_ON_SCREEN, BN_CLICKED, OnCheckBoxClick)
END_MSG_MAP()

LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnSelectColumn(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);

LRESULT OnWidthChange(WORD wNotify, WORD wID, HWND hWnd, BOOL& bHandled)
{
SetDirty(TRUE);
int nSel = SendDlgItemMessage(IDC_COLUMN_NAMES, CB_GETCURSEL, 0, 0L);
BOOL bSuccess;
UINT nVal = GetDlgItemInt(IDC_WIDTH, &bSuccess, FALSE);
SendDlgItemMessage(IDC_COLUMN_NAMES, CB_SETITEMDATA, nSel, nVal); // indicating that its width changed
return 0;

}
LRESULT OnLogFileChange(WORD wNotify, WORD wID, HWND hWnd, BOOL& bHandled)
{
SetDirty(TRUE);
return 0;

}

LRESULT OnCheckBoxClick(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
SetDirty(TRUE);
return 0;

}
STDMETHOD(Apply)(void);

};

#endif //__MTSSPYPROPPAGE_H_


OBJSINK.H

/******************************************************************************\
********************************************************************************
* Filename: ObjSink.h
*
* Description: Sink for Object events.
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#ifndef __OBJSINK_H__
#define __OBJSINK_H__

#include "resource.h"       // main symbols
//#include "..\mtsevents\eventcpts.h"

class CMtsSpy;
/////////////////////////////////////////////////////////////////////////////
// CObjSink
class ATL_NO_VTABLE CObjSink :
public CMtsSink,
public IMtsObjectEvents
{
public:
CObjSink(CMtsSpy * pSpy)
{
m_pSpy = pSpy;
}

CObjSink()
{
m_pSpy = NULL;
}

DECLARE_NOT_AGGREGATABLE(CObjSink)
DECLARE_GET_CONTROLLING_UNKNOWN()

BEGIN_COM_MAP(CObjSink)
COM_INTERFACE_ENTRY(IMtsObjectEvents)
COM_INTERFACE_ENTRY_CHAIN(CMtsSink)
END_COM_MAP()

virtual REFIID GetIID(){return IID_IMtsObjectEvents;}
STDMETHODIMP GetSinkType(EventEnum * e){*e = Object;return S_OK;}

STDMETHODIMP OnDisableCommit(LONGLONG perfCount, MTS_OBJID ObjectID)
{
WCHAR p1[16];
wsprintfW(p1, L"0x%08X", ObjectID);
m_pSpy -> AddEventToList(perfCount, _TEXT("OnDisableCommit"), m_sPackageName);
m_pSpy -> AddParamValueToList(L"ObjectID", p1);
return S_OK;
}

STDMETHODIMP  OnEnableCommit(LONGLONG perfCount, MTS_OBJID ObjectID)
{
WCHAR p1[16];
wsprintfW(p1, L"0x%08X", ObjectID);
m_pSpy -> AddEventToList(perfCount, _TEXT("OnEnableCommit"), m_sPackageName);
m_pSpy -> AddParamValueToList(L"ObjectID", p1);
IF_AUDIT_DO(OnEnableCommit)(perfCount, ObjectID);
return S_OK;
}

STDMETHODIMP  OnSetComplete(LONGLONG perfCount, MTS_OBJID ObjectID)
{
WCHAR p1[16];
wsprintfW(p1,  L"0x%08X", ObjectID);
m_pSpy -> AddEventToList(perfCount, _TEXT("OnSetComplete"), m_sPackageName);
m_pSpy -> AddParamValueToList(L"ObjectID", p1);
IF_AUDIT_DO(OnSetComplete)(perfCount, ObjectID);
return S_OK;
}

STDMETHODIMP  OnObjectDeactivate(LONGLONG perfCount, MTS_OBJID ObjectID)
{
WCHAR p1[16];
wsprintfW(p1,L"0x%08X", ObjectID);
m_pSpy -> AddEventToList(perfCount, _TEXT("OnObjectDeactivate"), m_sPackageName);
m_pSpy -> AddParamValueToList(L"ObjectID", p1);
IF_AUDIT_DO(OnObjectDeactivate)(perfCount, ObjectID);

return S_OK;
}

STDMETHODIMP  OnObjectActivate(LONGLONG perfCount, MTS_OBJID ObjectID)
{
WCHAR p1[16];
wsprintfW(p1,L"0x%08X", ObjectID);
m_pSpy -> AddEventToList(perfCount, _TEXT("OnObjectActivate"), m_sPackageName);
m_pSpy -> AddParamValueToList(L"ObjectID", p1);
IF_AUDIT_DO(OnObjectActivate)(perfCount, ObjectID);
return S_OK;
}

};

#endif //__OBJSINK_H__


PACKAGEINFO.H

/******************************************************************************\
********************************************************************************
* Filename: PackageInfo.h
*
* Description:  contains the definition of an object which holds information
about package specific information
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
#ifndef __PACKAGE_INFO_H__
#define __PACKAGE_INFO_H__

class CMtsSink;
class CPackageSink;
class CMtsSpy;

class CPackageInfo
{
private:
bool m_bReadyForDelete;
IConnectionPointContainer * m_pContainer;
SinkList * m_pList;
CComObject <CPackageSink> * m_pPackageSink;
CComBSTR m_sPackageName;

public:

CComBSTR & PackageName(){return m_sPackageName;}
SinkList * List(){return m_pList;}
void SetShowPackageEvent(BOOL bShow)
{
m_pPackageSink -> SetShowEvent(bShow);
}

BOOL GetShowPackageEvent()
{
return m_pPackageSink -> GetShowEvent();
}

IConnectionPointContainer * CPContainer()
{
return m_pContainer;
}

CPackageInfo()
{
_ASSERTE(0); // don't use this ctor!
m_pContainer = NULL;
m_pList = NULL;
m_bReadyForDelete = FALSE;
m_pPackageSink = NULL;

}
CPackageInfo(IConnectionPointContainer * pCon, LPWSTR sPackageName, CMtsSpy * pSpy)
{
m_pContainer = pCon;
m_sPackageName = sPackageName;
m_pContainer -> AddRef();
m_pList = new SinkList;
m_bReadyForDelete = FALSE;
_ASSERTE(pSpy);

HRESULT hr;
//
// we always set up an advise sink for the Package events in order to
// get the event that the ASP is shutting down
//
hr = CComObject<CPackageSink>::CreateInstance(&m_pPackageSink);
_ASSERTE(hr == S_OK);

// the class factory doesn't do an AddRef() because it expects
// a QI to come after the CreateInstance(), so we have to do an
// AddRef() here

m_pPackageSink -> AddRef();
m_pPackageSink -> Init(pCon, m_sPackageName.m_str);
m_pPackageSink -> Advise();
m_pPackageSink -> SetShowEvent(FALSE);
m_pPackageSink -> SetSpyObj(pSpy);

}
~CPackageInfo()
{
SinkList::iterator item;
IMtsSink * pSink;
ULONG refcount;
m_pPackageSink -> DAdvise();
refcount = m_pPackageSink -> Release();
m_pPackageSink = NULL;

for (item=m_pList -> begin(); item != m_pList -> end(); ++item)
{
pSink = *item;
if (pSink)
{
pSink -> DAdvise();
refcount = pSink -> Release();
_ASSERTE(refcount == 0);
pSink = NULL;
}
}

SafeRelease(m_pContainer);
m_pList -> clear();
delete m_pList;
m_pList = NULL;
}

void SetDeleteFlag(bool bDelete)
{
m_bReadyForDelete = bDelete;

}
bool IsReadyForDelete()
{
return m_bReadyForDelete;
}

};

#endif


PACKAGESINK.H

/******************************************************************************\
********************************************************************************
* Filename: PackageSink.h
*
* Description:  Sink for package events
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

// PackageSink.h : Declaration of the CPackageSink

#ifndef __PACKAGE_H__
#define __PACKAGE_H__

#include "stdafx.h"
#include "resource.h"       // main symbols

CComBSTR GuidToBstr(REFGUID guid);

class CMtsSpy;

/////////////////////////////////////////////////////////////////////////////
// CPackageSink
class ATL_NO_VTABLE CPackageSink :
public CMtsSink,
public IMtsPackageEvents
{
private:
BOOL m_bShowPackageEvents;

public:
CPackageSink()
{
m_pSpy = NULL;
}
CPackageSink(CMtsSpy * pSpy)
{
m_pSpy = pSpy;
}
void SetShowEvent(BOOL bShow)
{
m_bShowPackageEvents = bShow;
}

BOOL GetShowEvent()
{
return m_bShowPackageEvents;
}

DECLARE_NOT_AGGREGATABLE(CPackageSink)
DECLARE_GET_CONTROLLING_UNKNOWN()

BEGIN_COM_MAP(CPackageSink)
COM_INTERFACE_ENTRY(IMtsPackageEvents)
COM_INTERFACE_ENTRY_CHAIN(CMtsSink)
END_COM_MAP()

virtual REFIID GetIID(){return IID_IMtsPackageEvents;}
STDMETHODIMP GetSinkType(EventEnum * e){*e = Package;return S_OK;}

//
//  we can never get this event as currently implemented in mtsevents.dll
//because the runtime using this event to start the event infrastructure,
//so no sink can be established until _after_ this event is fired.
//
STDMETHODIMP OnPackageActivation(LONGLONG perfCount, REFGUID guidPackage, LPCOLESTR sPackageName)
{
    return S_OK;
}

//
//when the package shuts down, all of the sinks must be shut down as well
//
STDMETHODIMP OnPackageShutdown(LONGLONG perfCount, REFGUID guidPackage, LPCOLESTR sPackageName)
{
if (m_bShowPackageEvents)
{
if (m_pSpy)
{
            CComBSTR sGuidPackage = GuidToBstr(guidPackage);
m_pSpy -> AddEventToList(perfCount, _TEXT("OnPackageShutdown"), m_sPackageName);
m_pSpy -> AddParamValueToList(L"guidPackage", sGuidPackage);
}
}

_ASSERTE(m_pSpy);
if (m_pSpy)
{
CComBSTR sGuidPackage = GuidToBstr(guidPackage);
IF_AUDIT_DO(OnPackageShutdown)(perfCount, sGuidPackage, sPackageName);
m_pSpy -> ShutdownPackage(sPackageName);
}
return S_OK;
}
};

#endif //__PACKAGE_H__


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by MtsSpyCtl.rc
//
#define NUMBER_COLUMNS                  6
#define IDS_PROJNAME                    100
#define IDR_MTSSpy1                     100
#define IDR_MTSSPY                      101
#define IDS_TITLEMtsSpyPropPage         102
#define IDR_TXSINK                      103
#define IDR_RESOURCESINK                104
#define IDR_MTSSINK                     105
#define IDS_HELPFILEMtsSpyPropPage      106
#define IDS_DOCSTRINGMtsSpyPropPage     107
#define IDR_MTSSPYPROPPAGE              108
#define IDD_MTSSPYPROPPAGE              109
#define IDR_SPYMENU                     201
#define IDR_SHOWEVENTS                  201
#define IDC_SHOW_GRID_LINES             201
#define IDD_SELECTEVENTSDLG             202
#define IDC_LOG_FILE                    202
#define IDI_MAIN                        203
#define IDC_WIDTH                       203
#define IDC_LOG_TO_FILE                 204
#define IDC_COLUMN_NAMES                205
#define IDC_SHOW_ON_SCREEN              206
#define IDC_AUDIT_TO_DB                 207
#define IDC_PACKAGES                    208
#define IDC_EVENTS                      209
#define IDC_SINKS                       210
#define IDC_ADD_SINK                    211
#define IDC_REMOVE_FROM_SINK            212
#define ID_DEBUG_BEGIN                  500
#define ID_DEBUG_END                    599
#define ID_LOG                          32768
#define IDM_SELECT_PACKAGES             32769
#define ID_CHOOSEFONT                   32770
#define ID_SAVE                         32771
#define ID_CHOOSE_LOG_FILE_NAME         32772
#define ID_CLEAR                        32773
#define ID_OPTIONS_GRID_LINES           32774
#define ID_SHOW_ON_SCREEN               32776
#define IDM_ABOUT                       32777
#define ID_SPY_DEBUG_NORUNNINGPACKAGES  32779
#define ID_AUDIT                        32780

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        204
#define _APS_NEXT_COMMAND_VALUE         32781
#define _APS_NEXT_CONTROL_VALUE         206
#define _APS_NEXT_SYMED_VALUE           113
#endif
#endif


RESOURCESINK.CPP

/******************************************************************************\
********************************************************************************
* Filename: ResourceSink.cpp
*
* Description: Sink for resource events
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
// ResourceSink.cpp : Implementation of CResourceSink
#include "stdafx.h"
#include "mtsspyctl.h"
#include "mtsevents.h"
#include "MtsAudit.h"
#include "MTSSpy.h"
#include "MtsSink.h"
#include "packagesink.h"
#include "PackageInfo.h"
#include "ResourceSink.h"

/////////////////////////////////////////////////////////////////////////////
// CResourceSink

STDMETHODIMP CResourceSink::OnResourceCreate(
    LONGLONG perfCount,
REFGUID guidActivity,
MTS_OBJID ObjectID,
    LPCOLESTR pszType,
    MTS_RESID resId)
{

m_pSpy -> AddEventToList(perfCount, _TEXT("OnResourceCreate"), m_sPackageName);

CComBSTR sActivity = GuidToBstr(guidActivity);
m_pSpy -> AddParamValueToList(L"ActivityGuid", sActivity);

m_pSpy -> AddParamValueToList(L"Type", pszType);

WCHAR p2[16];
wsprintfW(p2, L"0x%08X", ObjectID);
m_pSpy -> AddParamValueToList(L"ObjectID", p2);

wsprintfW(p2, L"0x%08X", resId);
m_pSpy -> AddParamValueToList(L"ResourceID", p2);

IF_AUDIT_DO(OnResourceCreate)(perfCount, sActivity, ObjectID, pszType, resId);

return S_OK;
}

STDMETHODIMP CResourceSink::OnResourceAllocate(
LONGLONG perfCount,
REFGUID guidActivity,
MTS_OBJID ObjectID,
    LPCOLESTR pszType,
    MTS_RESID resId)
{

m_pSpy -> AddEventToList(perfCount, _TEXT("OnResourceAllocate"),m_sPackageName);
CComBSTR sActivity = GuidToBstr(guidActivity);
m_pSpy -> AddParamValueToList(L"ActivityGuid", sActivity);

m_pSpy -> AddParamValueToList(L"Type", pszType);

WCHAR p2[16];
wsprintfW(p2, L"0x%08X", ObjectID);
m_pSpy -> AddParamValueToList(L"ObjectID", p2);

wsprintfW(p2, L"0x%08X", resId);
m_pSpy -> AddParamValueToList(L"ResourceID", p2);
IF_AUDIT_DO(OnResourceAllocate)(perfCount, sActivity, ObjectID, pszType, resId);
return S_OK;
}

STDMETHODIMP CResourceSink::OnResourceRecycle(
LONGLONG perfCount,
MTS_OBJID ObjectID,
    LPCOLESTR pszType,
    MTS_RESID resId)
{
m_pSpy -> AddEventToList(perfCount, _TEXT("OnResourceRecycle"),m_sPackageName);

m_pSpy -> AddParamValueToList(L"Type", pszType);

WCHAR p2[16];
if (ObjectID)
{
wsprintfW(p2, L"0x%08X", ObjectID);
m_pSpy -> AddParamValueToList(L"ObjectID", p2);
}

wsprintfW(p2, L"0x%08X", resId);
m_pSpy -> AddParamValueToList(L"ResourceID", p2);
IF_AUDIT_DO(OnResourceRecycle)(perfCount, ObjectID, pszType, resId);
return S_OK;
}

STDMETHODIMP CResourceSink::OnResourceDestroy(
    LONGLONG perfCount,
MTS_OBJID ObjectID,
    LPCOLESTR pszType,
    MTS_RESID resId)
{
m_pSpy -> AddEventToList(perfCount, _TEXT("OnResourceDestroy"),m_sPackageName);

m_pSpy -> AddParamValueToList(L"Type", pszType);

WCHAR p2[16];
if (ObjectID)
{
wsprintfW(p2, L"0x%08X", ObjectID);
m_pSpy -> AddParamValueToList(L"ObjectID", p2);
}

wsprintfW(p2, L"0x%08X", resId);
m_pSpy -> AddParamValueToList(L"ResourceID", p2);

IF_AUDIT_DO(OnResourceDestroy)(perfCount, ObjectID, pszType, resId);
return S_OK;
}


RESOURCESINK.H

/******************************************************************************\
********************************************************************************
* Filename: ResourceSink.h
*
* Description: defininition of sink for resource events.
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#ifndef __RESOURCESINK_H_
#define __RESOURCESINK_H_

#include "resource.h"       // main symbols

class CMtsSpy;

/////////////////////////////////////////////////////////////////////////////
// CResourceSink
class ATL_NO_VTABLE CResourceSink :
public CMtsSink,
public IMtsResourceEvents

{
public:
CResourceSink(CMtsSpy * pSpy)
{
m_pSpy = pSpy;
}
CResourceSink()
{
m_pSpy = NULL;
}

DECLARE_REGISTRY_RESOURCEID(IDR_RESOURCESINK)
DECLARE_NOT_AGGREGATABLE(CResourceSink)
DECLARE_GET_CONTROLLING_UNKNOWN()

BEGIN_COM_MAP(CResourceSink)
COM_INTERFACE_ENTRY(IMtsResourceEvents)
COM_INTERFACE_ENTRY_CHAIN(CMtsSink)
END_COM_MAP()

virtual REFIID GetIID(){return IID_IMtsResourceEvents;}
// IResourceSink
public:
    STDMETHOD(OnResourceCreate)(LONGLONG perfCount, REFGUID guidActivity, MTS_OBJID ObjectID, LPCOLESTR pszType,MTS_RESID resId);
STDMETHOD(OnResourceAllocate)(LONGLONG perfCount, REFGUID guidActivity, MTS_OBJID ObjectID, LPCOLESTR pszType,MTS_RESID resId);
STDMETHOD(OnResourceRecycle)(LONGLONG perfCount, MTS_OBJID ObjectID, LPCOLESTR pszType,MTS_RESID resId);
STDMETHOD(OnResourceDestroy)(LONGLONG perfCount, MTS_OBJID ObjectID, LPCOLESTR pszType,MTS_RESID resId);

STDMETHODIMP GetSinkType(EventEnum * e){*e = Resource;return S_OK;}

};

#endif //__RESOURCESINK_H_


SECURITYSINK.H

/******************************************************************************\
********************************************************************************
* Filename: SecuritySink.h
*
* Description:  Sink for package events
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

// SecuritySink.h : Declaration of the CSecuritySink

#ifndef __SECURITY_H__
#define __SECURITY_H__

#include "stdafx.h"
#include "resource.h"       // main symbols

#define USERNAMELEN 64

CComBSTR GuidToBstr(REFGUID guid);

class CMtsSpy;

/////////////////////////////////////////////////////////////////////////////
// CSecuritySink
class ATL_NO_VTABLE CSecuritySink :
public CMtsSink,
public IMtsSecurityEvents
{
public:
CSecuritySink()
{
m_pSpy = NULL;
}
CSecuritySink(CMtsSpy * pSpy)
{
m_pSpy = pSpy;
}

DECLARE_NOT_AGGREGATABLE(CSecuritySink)
DECLARE_GET_CONTROLLING_UNKNOWN()

BEGIN_COM_MAP(CSecuritySink)
COM_INTERFACE_ENTRY(IMtsSecurityEvents)
COM_INTERFACE_ENTRY_CHAIN(CMtsSink)
END_COM_MAP()

virtual REFIID GetIID(){return IID_IMtsSecurityEvents;}
STDMETHODIMP GetSinkType(EventEnum * e){*e = Security;return S_OK;}

STDMETHODIMP OnAuthenticate(  LONGLONG perfCount,
  REFGUID guidActivity,
  ULONG cbByteOrig,
  BYTE * pSidOriginalUser,
  ULONG cbByteCur,
  BYTE * pSidCurrentUser,
  BOOL bCurrentUserInpersonatingInProc
)
{

m_pSpy -> AddEventToList(perfCount, _TEXT("OnAuthenticate"), m_sPackageName);
CComBSTR sGuidActivity = GuidToBstr(guidActivity);
m_pSpy -> AddParamValueToList(L"guidActivity", sGuidActivity);

WCHAR sOriginalUser[256];
ZeroMemory(sOriginalUser, 256*sizeof(WCHAR));

if (GetDomainSlashUser(pSidOriginalUser, sOriginalUser))
m_pSpy -> AddParamValueToList(L"Original User", sOriginalUser);
else
m_pSpy -> AddParamValueToList(L"Original User", L"<Error converting SID>");

WCHAR sDirectUser[256];
ZeroMemory(sDirectUser, 256*sizeof(WCHAR));

if (GetDomainSlashUser(pSidCurrentUser, sDirectUser))
m_pSpy -> AddParamValueToList(L"Direct User", sDirectUser);
else
m_pSpy -> AddParamValueToList(L"Direct User", L"<Error converting SID>");

m_pSpy -> AddParamValueToList(L"Direct User Inpersonating InProc", bCurrentUserInpersonatingInProc ? L"Yes" : L"No");

IF_AUDIT_DO(OnAuthenticate)(perfCount,
sGuidActivity,
sOriginalUser,
sDirectUser,
bCurrentUserInpersonatingInProc);
return S_OK;
}

BOOL GetDomainSlashUser(
 PSID Sid,// in
 WCHAR* DomainSlashUser)// out
{
WCHAR Name[USERNAMELEN + 1];
DWORD cbName = USERNAMELEN;
DWORD cbReferencedDomainName = USERNAMELEN;
SID_NAME_USE eUse;
WCHAR* p;
ZeroMemory(Name, cbName);
if (!LookupAccountSid(NULL,Sid, Name, &cbName, DomainSlashUser, &cbReferencedDomainName, &eUse))
return FALSE;

// concat domain\user
p = DomainSlashUser + wcslen(DomainSlashUser);
*p++ = '\\';
wcscpy(p, Name);
return TRUE;
}

};

#endif //__SECURITY_H__


SELECTEVENTSDLG.CPP

/******************************************************************************\
********************************************************************************
* Filename: SelectEventsDlg.cpp
*
* Description:  Dialog to select which events to sink to.  Selecting a sink
causes the sink to be established.
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
// SelectEventsDlg.cpp : Implementation of CSelectEventsDlg
#include "stdafx.h"
#include "mtsspyctl.h"
#include "mtsevents.h"
#include "MtsAudit.h"
#include "MTSSpy.h"
#include "MtsSink.h"
#include "TxSink.h"
#include "objsink.h"
#include "threadsink.h"
#include "instancesink.h"
#include "resourcesink.h"
#include "packagesink.h"
#include "usersink.h"
#include "PackageInfo.h"
#include "SelectEventsDlg.h"
#include <algorithm>
#include "methodsink.h"
#include "MtsGrp.h"
#include "MtsGrp_i.c"
#include "eventcpts.h"
#include "securitysink.h"

typedef struct _EVENT_SINK_INFO
{
TCHAR * sDisplayName;
EventEnum eEvent;
}EVENT_SINK_INFO;

EVENT_SINK_INFO EventInfo[] ={
_T("Package"),Package,
_T("Thread"),Thread,
_T("Instance"),Instance,
_T("Transaction"),Transaction,
_T("Method"),Method,
_T("Object"),Object,
_T("Resource"),Resource,
    _T("UserDefined"),User,
_T("Security"),Security
};

CSelectEventsDlg::CSelectEventsDlg()
{

}

CSelectEventsDlg::~CSelectEventsDlg()
{
}

LRESULT CSelectEventsDlg::OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{
ResetEventList();
GetRunningAsps();
return 1;  // Let the system set the focus
}

LRESULT CSelectEventsDlg::OnOK(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
EndDialog(wID);
return 0;
}

LRESULT CSelectEventsDlg::OnCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
EndDialog(wID);
return 0;
}

LRESULT CSelectEventsDlg::OnDestroyDlg(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
{

bHandled = FALSE;
return 0;
}

//
//
//get the IMtsEvents interface, get the Package Name
//and add it to the combo box
//
BOOL CSelectEventsDlg::GetRunningAsps()
{
HRESULT hr = E_FAIL;
IMtsGrp * pMtsGrp = NULL;
hr = CoCreateInstance (CLSID_MtsGrp, NULL, CLSCTX_ALL, IID_IMtsGrp, (void **)&pMtsGrp);
if (!pMtsGrp)
{
_ASSERTE(0);
return FALSE;
}

long lPackages;
pMtsGrp -> get_Count(&lPackages);
IUnknown * pUnk = NULL;
IMtsEvents * pEvents = NULL;
USES_CONVERSION;
int nIndex;
for (int i=0; i<lPackages; i++)
{

pMtsGrp -> Item(i, &pUnk);
pEvents = NULL;
pUnk -> QueryInterface(IID_IMtsEvents, (void **)&pEvents);
_ASSERTE(pEvents);
CComBSTR sName;
CPackageInfo * pInfo = NULL;
pEvents -> get_PackageName(&sName.m_str);
IConnectionPointContainer * pContainer=NULL;
hr = pEvents -> QueryInterface(IID_IConnectionPointContainer, (void **)&pContainer);
_ASSERTE(hr == S_OK);
if (SUCCEEDED(hr))
{
LPSTR p = W2A(sName.m_str);
pInfo = (*m_map)[p];
if (!pInfo)
{
_ASSERTE(m_pSpyObj);
pInfo = new CPackageInfo(pContainer, sName.m_str, m_pSpyObj); // this will register for the package events
_ASSERTE(pInfo);
(*m_map)[p] = pInfo;
}
nIndex = SendDlgItemMessage(IDC_PACKAGES, CB_ADDSTRING, 0, (LPARAM)sName.m_str);
if (nIndex != CB_ERR)
{
SendDlgItemMessage(IDC_PACKAGES, CB_SETITEMDATA, nIndex, (DWORD)pInfo);
}
else
_ASSERTE(0);
}

SafeRelease(pEvents);
SafeRelease(pUnk);
SafeRelease(pContainer);
}

SafeRelease(pMtsGrp);

SendDlgItemMessage(IDC_PACKAGES, CB_SETCURSEL, 0);
int bHandled = TRUE;
OnSelectPackages(CBN_SELCHANGE,0,NULL, bHandled);
return TRUE;

}
//
//the user selects a package from the drop down list box.
//
LRESULT CSelectEventsDlg::OnSelectPackages(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
if (wNotifyCode == CBN_SELCHANGE)
{

// get the package name
int nSel = SendDlgItemMessage(IDC_PACKAGES, CB_GETCURSEL, 0, 0L);
if (nSel != CB_ERR)
{

// we store the CPackageInfo structure in the item data...
CPackageInfo * pInfo = NULL;
pInfo = (CPackageInfo *)SendDlgItemMessage(IDC_PACKAGES, CB_GETITEMDATA, nSel);
_ASSERTE(pInfo);

// update the list of sinks
ResetEventList();

// interate through the list and make sure that the list boxes reflects what's going on
SinkList::iterator item;
EventEnum nType;
IMtsSink * pSink;
int nIndex;
DWORD dwItemData;
TCHAR sz[32];
CComBSTR sDisplayName;
for (item = pInfo -> List() -> begin(); item != pInfo -> List() -> end(); item++)
{

pSink = *item;
if (pSink)
{
pSink -> GetSinkType(&nType);
GetDisplayName(nType, &sDisplayName);
nIndex = SendDlgItemMessage(IDC_EVENTS, LB_FINDSTRING, 0,  (LPARAM)sDisplayName.m_str);
if (nIndex != LB_ERR)
{
// move it to the other list
memset(&sz[0], NULL, 32*sizeof(TCHAR));
SendDlgItemMessage(IDC_EVENTS, LB_GETTEXT, nIndex, (LPARAM)sz);
dwItemData = SendDlgItemMessage(IDC_EVENTS, LB_GETITEMDATA, nIndex);
SendDlgItemMessage(IDC_EVENTS, LB_DELETESTRING, nIndex);
nIndex = SendDlgItemMessage(IDC_SINKS, LB_ADDSTRING, 0, (LPARAM)sz);
SendDlgItemMessage(IDC_SINKS, LB_SETITEMDATA, nIndex, (LPARAM)dwItemData);

}
else
_ASSERTE(0);
}

}

// we do special processing for the package event, since we are always sinked to it

 if (pInfo -> GetShowPackageEvent())
 {
nIndex = SendDlgItemMessage(IDC_EVENTS, LB_FINDSTRING, 0,  (LPARAM)_T("Package"));
// move it to the other list
memset(&sz[0], NULL, 32*sizeof(TCHAR));
SendDlgItemMessage(IDC_EVENTS, LB_GETTEXT, nIndex, (LPARAM)sz);
dwItemData = SendDlgItemMessage(IDC_EVENTS, LB_GETITEMDATA, nIndex);
SendDlgItemMessage(IDC_EVENTS, LB_DELETESTRING, nIndex);
nIndex = SendDlgItemMessage(IDC_SINKS, LB_ADDSTRING, 0, (LPARAM)sz);
SendDlgItemMessage(IDC_SINKS, LB_SETITEMDATA, nIndex, (LPARAM)dwItemData);
 }

// if the count on the left hand side == 0, disable the add sink button
::EnableWindow(GetDlgItem(IDC_ADD_SINK), SendDlgItemMessage(IDC_EVENTS, LB_GETCOUNT));
// if there are no sinks, don't enable the remove sink button
::EnableWindow(GetDlgItem(IDC_REMOVE_FROM_SINK), SendDlgItemMessage(IDC_SINKS, LB_GETCOUNT));

}
else
{

::EnableWindow(GetDlgItem(IDC_ADD_SINK), FALSE);
::EnableWindow(GetDlgItem(IDC_REMOVE_FROM_SINK), FALSE);
}

}

return 0;
}

//
//the user is adding one or more sinks for the selected (running) package
//
LRESULT CSelectEventsDlg::OnAddSink(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{

if (wNotifyCode == BN_CLICKED)
{
//we are a multi-select list box...
int nCount = SendDlgItemMessage(IDC_EVENTS, LB_GETSELCOUNT);
if (nCount != LB_ERR && nCount)
{

int nSel = SendDlgItemMessage(IDC_PACKAGES, CB_GETCURSEL, 0, 0L);
if (nSel == CB_ERR)
{
_ASSERTE(0);
return 0;
}

// get the information for the current package
CPackageInfo * pInfo = NULL;
pInfo = (CPackageInfo *)SendDlgItemMessage(IDC_PACKAGES, CB_GETITEMDATA, nSel);
_ASSERTE(pInfo);

int * nSelItems = new int[nCount];
SendDlgItemMessage(IDC_EVENTS,LB_GETSELITEMS, nCount, (LPARAM)nSelItems);
int i;
WCHAR sz[16];
DWORD dwItemData;
int nItem;
MapStringToPackageInfo::iterator iter;
HRESULT hr;
USES_CONVERSION;
IMtsSink * pMtsSink = NULL;
for (i=0;i<nCount;i++)
{

// move string and item data to the other list box
memset(&sz[0], NULL, 16*sizeof(TCHAR));
SendDlgItemMessage(IDC_EVENTS, LB_GETTEXT, nSelItems[i], (LPARAM)sz);
dwItemData = SendDlgItemMessage(IDC_EVENTS, LB_GETITEMDATA, nSelItems[i]);
nItem = SendDlgItemMessage(IDC_SINKS, LB_ADDSTRING, 0, (LPARAM)sz);
SendDlgItemMessage(IDC_SINKS, LB_SETITEMDATA, nItem, (LPARAM)dwItemData);

// find out what type of sink has been requested, and create the appropriate object
EventEnum  event = (EventEnum) dwItemData;
switch (event)
{
case Security:
{

CComObject <CSecuritySink> * pSink = NULL;
hr = CComObject<CSecuritySink>::CreateInstance(&pSink);
pSink -> SetSpyObj(m_pSpyObj);
_ASSERTE(pSink);
pSink -> QueryInterface(IID_IMtsSink, (void **)&pMtsSink);
_ASSERTE(pSink);

}
break;
case Transaction:
{
CComObject <CTxSink> * pSink;
hr = CComObject<CTxSink>::CreateInstance(&pSink);
pSink -> SetSpyObj(m_pSpyObj);
_ASSERTE(pSink);
pSink -> QueryInterface(IID_IMtsSink, (void **)&pMtsSink);
_ASSERTE(pSink);
break;
}
case Resource:
{
CComObject <CResourceSink> * pSink;
hr = CComObject<CResourceSink>::CreateInstance(&pSink);
pSink -> SetSpyObj(m_pSpyObj);
_ASSERTE(pSink);
pSink -> QueryInterface(IID_IMtsSink, (void **)&pMtsSink);
_ASSERTE(pSink);
}
break;
case Object:
{
CComObject <CObjSink> * pSink;
hr = CComObject<CObjSink>::CreateInstance(&pSink);
pSink -> SetSpyObj(m_pSpyObj);
_ASSERTE(pSink);
pSink -> QueryInterface(IID_IMtsSink, (void **)&pMtsSink);
_ASSERTE(pSink);
break;
}
break;
case Thread:
{
CComObject <CThreadSink> * pSink;
hr = CComObject<CThreadSink>::CreateInstance(&pSink);
pSink -> SetSpyObj(m_pSpyObj);
_ASSERTE(pSink);
pSink -> QueryInterface(IID_IMtsSink, (void **)&pMtsSink);
_ASSERTE(pSink);
}
break;
case Instance:
{
CComObject <CInstanceSink> * pSink;
hr = CComObject<CInstanceSink>::CreateInstance(&pSink);
pSink -> SetSpyObj(m_pSpyObj);
_ASSERTE(pSink);
pSink -> QueryInterface(IID_IMtsSink, (void **)&pMtsSink);
_ASSERTE(pSink);
break;
}
break;
//
// package events are "special" -- we've already sinked to them
//
case Package:
{
pInfo -> SetShowPackageEvent(TRUE);
break;
}
case Method:
{
CComObject <CMethodSink> * pSink;
hr = CComObject<CMethodSink>::CreateInstance(&pSink);
pSink -> SetSpyObj(m_pSpyObj);
_ASSERTE(pSink);
pSink -> QueryInterface(IID_IMtsSink, (void **)&pMtsSink);
_ASSERTE(pSink);
}
break;
case User:
{
CComObject <CUserSink> * pSink;
hr = CComObject<CUserSink>::CreateInstance(&pSink);
pSink -> SetSpyObj(m_pSpyObj);
_ASSERTE(pSink);
pSink -> QueryInterface(IID_IMtsSink, (void **)&pMtsSink);
_ASSERTE(pSink);
}
break;
default:
pMtsSink = NULL;
ATLTRACE(L"Object type not implemented yet\n");
_ASSERTE(0);
break;
}
if (pMtsSink) // we've successfully created the sink
{

// initialize it
pMtsSink -> Init(pInfo -> CPContainer(), pInfo -> PackageName());
pMtsSink -> Advise();
_ASSERTE(pInfo);
pInfo -> List() -> insert (pInfo -> List() ->end(), pMtsSink);
pMtsSink = NULL;
}

}
// remove from listbox in reverse order
for (i=nCount-1;i>=0;i--)
{
SendDlgItemMessage(IDC_EVENTS, LB_DELETESTRING, nSelItems[i]);
}

delete [] nSelItems;

// if the count on the left hand side == 0, disable the add sink button
::EnableWindow(GetDlgItem(IDC_ADD_SINK), SendDlgItemMessage(IDC_EVENTS, LB_GETCOUNT));
// if there are no sinks, don't enable the remove sink button
::EnableWindow(GetDlgItem(IDC_REMOVE_FROM_SINK), SendDlgItemMessage(IDC_SINKS, LB_GETCOUNT));

}

}

return 0;

}
//
//remove one or more sinks
//
LRESULT CSelectEventsDlg::OnRemoveSink(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{
if (wNotifyCode == BN_CLICKED)
{

int nCount = SendDlgItemMessage(IDC_SINKS, LB_GETSELCOUNT);
if (nCount != LB_ERR && nCount)
{

// get package name
int nSel = SendDlgItemMessage(IDC_PACKAGES, CB_GETCURSEL, 0, 0L);
if (nSel == CB_ERR)
{
_ASSERTE(0);
return 0;
}

CPackageInfo * pInfo = NULL;
pInfo = (CPackageInfo *)SendDlgItemMessage(IDC_PACKAGES, CB_GETITEMDATA, nSel);
_ASSERTE(pInfo);

MapStringToPackageInfo::iterator iter;
int * nSelItems = new int[nCount];
 SendDlgItemMessage(IDC_SINKS,LB_GETSELITEMS, nCount, (LPARAM)nSelItems);
int i;
WCHAR sz[16];
DWORD dwItemData;
int nItem;
SinkList::iterator iterSink;
IMtsSink * pSink;
EventEnum eSinkType;
for (i=0;i<nCount;i++)
{

memset(&sz[0], NULL, 16*sizeof(TCHAR));
SendDlgItemMessage(IDC_SINKS, LB_GETTEXT, nSelItems[i], (LPARAM)sz);
dwItemData = SendDlgItemMessage(IDC_SINKS, LB_GETITEMDATA, nSelItems[i]);
if (dwItemData == Package) // package sink
pInfo -> SetShowPackageEvent(FALSE);
nItem  = SendDlgItemMessage(IDC_EVENTS, LB_ADDSTRING, 0, (LPARAM)sz);
SendDlgItemMessage(IDC_EVENTS, LB_SETITEMDATA, nItem, (LPARAM)dwItemData);
//iterate through the list looking for matching item data and sink id
for (iterSink=pInfo -> List() -> begin(); iterSink != pInfo -> List() -> end(); ++iterSink)
{
pSink = *iterSink;
if (pSink)
{
pSink -> GetSinkType(&eSinkType);
if (eSinkType == (EventEnum)dwItemData)
{
pSink -> DAdvise();
pSink -> Release();
pInfo -> List() -> erase(iterSink++);
break;

}

}

}

}
// remove strings from list in reverse order
for (i=nCount-1;i >= 0;i--)
{
SendDlgItemMessage(IDC_SINKS, LB_DELETESTRING, nSelItems[i]);
}

delete [] nSelItems;

// if the count on the left hand side == 0, disable the add sink button
::EnableWindow(GetDlgItem(IDC_ADD_SINK), SendDlgItemMessage(IDC_EVENTS, LB_GETCOUNT));
// if there are no sinks, don't enable the remove sink button
::EnableWindow(GetDlgItem(IDC_REMOVE_FROM_SINK), SendDlgItemMessage(IDC_SINKS, LB_GETCOUNT));

}

}

return 0;
}

void CSelectEventsDlg::ResetEventList()
{
SendDlgItemMessage(IDC_EVENTS, LB_RESETCONTENT);
SendDlgItemMessage(IDC_SINKS, LB_RESETCONTENT);

int i;
// fill the Possible Events list
int nIndex;
for (i=0; i<sizeof(EventInfo)/sizeof(EventInfo[0]); i++)
{
nIndex = SendDlgItemMessage(IDC_EVENTS, LB_ADDSTRING, 0,  (LPARAM)EventInfo[i].sDisplayName);
SendDlgItemMessage(IDC_EVENTS, LB_SETITEMDATA, nIndex,  (LPARAM)EventInfo[i].eEvent);
}
}

//
//do the right thing when the user double clicks on the list box
//
LRESULT CSelectEventsDlg::EventListHandler(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{

if (wNotifyCode == LBN_DBLCLK)
{
HWND hWndAddSinkButton = GetDlgItem(IDC_ADD_SINK);
if (::IsWindowEnabled( hWndAddSinkButton ))
OnAddSink(BN_CLICKED, IDC_ADD_SINK, hWndAddSinkButton, bHandled);

}

bHandled = FALSE;
return 0;
}
//
//do the right thing when the user double clicks on the list box
//
LRESULT CSelectEventsDlg::SinkListHandler(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled)
{

if (wNotifyCode == LBN_DBLCLK)
{
HWND hWndRemoveSinkButton = GetDlgItem(IDC_REMOVE_FROM_SINK);
if (::IsWindowEnabled( hWndRemoveSinkButton ))
OnRemoveSink(BN_CLICKED, IDC_REMOVE_FROM_SINK, hWndRemoveSinkButton, bHandled);

}

bHandled = FALSE;
return 0;
}
//
//given an EventEnum, get the display Name
HRESULT CSelectEventsDlg::GetDisplayName(EventEnum e, BSTR * sDisplayName)
{
int i;
for (i = 0; i< sizeof(EventInfo)/sizeof(EventInfo[0]); i++)
{

if (EventInfo[i].eEvent == e)
{
*sDisplayName = ::SysAllocString(EventInfo[i].sDisplayName);
return S_OK;
}
}
return E_FAIL;
}


SELECTEVENTSDLG.H

/******************************************************************************\
********************************************************************************
* Filename: SelectEventsDlg.h
*
* Description: Declaration of the CSelectEventsDlg
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#ifndef __SELECTEVENTSDLG_H_
#define __SELECTEVENTSDLG_H_

#include "resource.h"       // main symbols
class CMtsSpy;
/////////////////////////////////////////////////////////////////////////////
// CSelectEventsDlg
class CSelectEventsDlg :
public CDialogImpl<CSelectEventsDlg>
{
private:
MapStringToPackageInfo * m_map;
CMtsSpy * m_pSpyObj;

public:
HRESULT GetDisplayName(EventEnum e, BSTR * sDisplayName);
void ResetEventList();
CSelectEventsDlg();
CSelectEventsDlg(MapStringToPackageInfo * spMap, CMtsSpy * pSpyObj)
{
m_map = spMap;
m_pSpyObj = pSpyObj;
}
~CSelectEventsDlg();
BOOL GetRunningAsps();

enum { IDD = IDD_SELECTEVENTSDLG };

BEGIN_MSG_MAP(CSelectEventsDlg)
MESSAGE_HANDLER(WM_INITDIALOG, OnInitDialog)
COMMAND_ID_HANDLER(IDOK, OnOK)
COMMAND_ID_HANDLER(IDCANCEL, OnCancel)
COMMAND_ID_HANDLER(IDC_PACKAGES, OnSelectPackages)
COMMAND_ID_HANDLER(IDC_ADD_SINK, OnAddSink)
COMMAND_ID_HANDLER(IDC_REMOVE_FROM_SINK, OnRemoveSink)
MESSAGE_HANDLER(WM_DESTROY, OnDestroyDlg)
COMMAND_ID_HANDLER(IDC_EVENTS, EventListHandler)
COMMAND_ID_HANDLER(IDC_SINKS, SinkListHandler)

END_MSG_MAP()

//
//Message Handles
//
LRESULT OnInitDialog(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT OnOK(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnCancel(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnSelectPackages(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnAddSink(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnRemoveSink(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT OnDestroyDlg(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
LRESULT EventListHandler(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);
LRESULT SinkListHandler(WORD wNotifyCode, WORD wID, HWND hWndCtl, BOOL& bHandled);

};

#endif //__SELECTEVENTSDLG_H_


STDAFX.H

// stdafx.h : include file for standard system include files,
//      or project specific include files that are used frequently,
//      but are changed infrequently

#if !defined(AFX_STDAFX_H__A9B39075_AAD0_11D0_B183_00AA00BA3258__INCLUDED_)
#define AFX_STDAFX_H__A9B39075_AAD0_11D0_B183_00AA00BA3258__INCLUDED_

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

#define STRICT

#define _WIN32_WINNT 0x0400
#define _ATL_APARTMENT_THREADED

#include <atlbase.h>
#include "commctrl.h"
//You may derive a class from CComModule and use it if you want to override
//something, but do not change the name of _Module
extern CComModule _Module;
#include <atlcom.h>
#include <atlctl.h>
#define SafeRelease(pUnk) {if (pUnk){pUnk -> Release();pUnk = NULL; }}
#pragma warning( disable : 4786) // disable the warning "identifier was truncated to '255' characters in the debug information" generated by STL
#include <list>
#include <map>
#include <string>
#include <ocidl.h>// Added by ClassView
using namespace std;

interface IMtsSink;
interface IConnectionPointContainer;

typedef list <IMtsSink *> SinkList;

class CPackageInfo;

typedef map <string, CPackageInfo *> MapStringToPackageInfo;
#define _ATL_DEBUG_QI
#define _ATL_DEBUG_REFCOUNT

//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_STDAFX_H__A9B39075_AAD0_11D0_B183_00AA00BA3258__INCLUDED)


THREADSINK.H

/******************************************************************************\
********************************************************************************
* Filename: ThreadSink.h
*
* Description: Declaration of the CThreadSink -- the sink for Thread Events
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#ifndef __THREADSINK_H__
#define __THREADSINK_H__

#include "resource.h"       // main symbols

CComBSTR GuidToBstr(REFGUID guid);

class CMtsSpy;

/////////////////////////////////////////////////////////////////////////////
// CThreadSink
class ATL_NO_VTABLE CThreadSink :
public CMtsSink,
public IMtsThreadEvents
{
public:

CThreadSink(CMtsSpy * pSpy)
{
m_pSpy = pSpy;
}
CThreadSink()
{
m_pSpy = NULL;
}

DECLARE_REGISTRY_RESOURCEID(IDR_TXSINK)
DECLARE_NOT_AGGREGATABLE(CThreadSink)
DECLARE_GET_CONTROLLING_UNKNOWN()

BEGIN_COM_MAP(CThreadSink)
COM_INTERFACE_ENTRY(IMtsThreadEvents)
COM_INTERFACE_ENTRY_CHAIN(CMtsSink)
END_COM_MAP()

virtual REFIID GetIID(){return IID_IMtsThreadEvents;}
STDMETHODIMP GetSinkType(EventEnum * e){*e = Thread;return S_OK;}

STDMETHODIMP OnThreadStart(LONGLONG perfCount, ULONG ThreadId)
{
WCHAR p1[16];
wsprintfW(p1,L"0x%08X", ThreadId);
m_pSpy -> AddEventToList(perfCount, _TEXT("OnThreadStart"), m_sPackageName);
m_pSpy -> AddParamValueToList(L"ThreadID", p1);
IF_AUDIT_DO(OnThreadStart)(perfCount, ThreadId);
return S_OK;
}

STDMETHODIMP OnThreadTerminate(LONGLONG perfCount, ULONG ThreadId)
{
WCHAR p1[16];
wsprintfW(p1,L"0x%08X", ThreadId);
m_pSpy -> AddEventToList(perfCount, _TEXT("OnThreadTerminate"), m_sPackageName);
m_pSpy -> AddParamValueToList(L"ThreadID", p1);
IF_AUDIT_DO(OnThreadTerminate)(perfCount, ThreadId);
return S_OK;
}

STDMETHODIMP OnThreadAssignToActivity(LONGLONG perfCount, REFGUID guidActivity, ULONG ThreadId)
{
WCHAR id[16];
wsprintfW(id,L"0x%08X", ThreadId);
m_pSpy -> AddEventToList(perfCount, _TEXT("OnThreadAssignToActivity"), m_sPackageName);
m_pSpy -> AddParamValueToList(L"ThreadID", id);

    CComBSTR sGuidActivity = GuidToBstr(guidActivity);
m_pSpy -> AddParamValueToList(L"guidActivity", sGuidActivity);
IF_AUDIT_DO(OnThreadAssignToActivity)(perfCount, sGuidActivity, ThreadId);
return S_OK;
}

STDMETHODIMP OnThreadUnassignFromActivity(LONGLONG perfCount, REFGUID guidActivity, ULONG ThreadId)
{
WCHAR id[16];
wsprintfW(id,L"0x%08X", ThreadId);
m_pSpy -> AddEventToList(perfCount, _TEXT("OnThreadUnassignFromActivity"), m_sPackageName);
m_pSpy -> AddParamValueToList(L"ThreadID", id);

    CComBSTR sGuidActivity = GuidToBstr(guidActivity);
m_pSpy -> AddParamValueToList(L"guidActivity", sGuidActivity);
IF_AUDIT_DO(OnThreadUnassignFromActivity)(perfCount, sGuidActivity, ThreadId);
return S_OK;
}

};

#endif //__THREADSINK_H__


TXSINK.CPP

/******************************************************************************\
********************************************************************************
* Filename: TxSink.cpp
*
* Description: Sink for Tx events
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
#include "stdafx.h"
#include "mtsspyctl.h"
#include "mtsevents.h"
#include "MtsAudit.h"
#include "MTSSpy.h"
#include "MtsSink.h"
#include "packagesink.h"
#include "PackageInfo.h"
#include "TxSink.h"

/////////////////////////////////////////////////////////////////////////////
// CTxSink

STDMETHODIMP CTxSink::OnTransactionStart(LONGLONG perfCount, REFGUID guidTx, REFGUID tsid, BOOL fRoot)
{
m_pSpy -> AddEventToList(perfCount, _TEXT("OnTransactionStart"), m_sPackageName);

    CComBSTR sGuidTx = GuidToBstr(guidTx);
    CComBSTR sTsid = GuidToBstr(tsid);
m_pSpy -> AddParamValueToList(L"GuidTx", sGuidTx);
m_pSpy -> AddParamValueToList(L"tsid", sTsid);
m_pSpy -> AddParamValueToList(L"Is a Root Tx?", fRoot ? L"Yes" : L"No");
IF_AUDIT_DO(OnTransactionStart)(perfCount,
  sGuidTx,
  sTsid,
  fRoot);

return S_OK;
}

STDMETHODIMP CTxSink::OnTransactionPrepared(LONGLONG perfCount, REFGUID guidTx, BOOL fVoteYes)
{
m_pSpy -> AddEventToList(perfCount, _TEXT("OnTransactionPrepared"), m_sPackageName);
    CComBSTR sGuidTx = GuidToBstr(guidTx);
    m_pSpy -> AddParamValueToList(L"GuidTx", sGuidTx);
m_pSpy -> AddParamValueToList(L"Vote",  fVoteYes == TRUE ? L"Yes" : L"No");
IF_AUDIT_DO(OnTransactionPrepared)(perfCount,
  sGuidTx,
  fVoteYes);
return S_OK;
}

STDMETHODIMP CTxSink::OnTransactionAborted(LONGLONG perfCount, REFGUID guidTx)
{
m_pSpy -> AddEventToList(perfCount, _TEXT("OnTransactionAborted"), m_sPackageName);
    CComBSTR sGuidTx = GuidToBstr(guidTx);
    m_pSpy -> AddParamValueToList(L"GuidTx", sGuidTx);
IF_AUDIT_DO(OnTransactionAborted)(perfCount, sGuidTx);
return S_OK;
}


TXSINK.H

/******************************************************************************\
********************************************************************************
* Filename: TxSink.h
*
* Description: Declaration of the CTxSink -- the Tx events
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#ifndef __TXSINK_H_
#define __TXSINK_H_

#include "resource.h"       // main symbols

class CMtsSpy;
/////////////////////////////////////////////////////////////////////////////
// CTxSink
class ATL_NO_VTABLE CTxSink :
public CMtsSink,
public IMtsTransactionEvents
{
public:

CTxSink(CMtsSpy * pSpy)
{
m_pSpy = pSpy;
}
CTxSink()
{
m_pSpy = NULL;
}

DECLARE_REGISTRY_RESOURCEID(IDR_TXSINK)
DECLARE_NOT_AGGREGATABLE(CTxSink)
DECLARE_GET_CONTROLLING_UNKNOWN()

BEGIN_COM_MAP(CTxSink)
COM_INTERFACE_ENTRY(IMtsTransactionEvents)
COM_INTERFACE_ENTRY_CHAIN(CMtsSink)
END_COM_MAP()

virtual REFIID GetIID(){return IID_IMtsTransactionEvents;}
public:
STDMETHOD (OnTransactionStart)(LONGLONG perfCount, REFGUID guidTx, REFGUID tsid, BOOL fRoot);
STDMETHOD (OnTransactionPrepared)(LONGLONG perfCount, REFGUID guidTx, BOOL fVoteYes);
STDMETHOD (OnTransactionAborted)(LONGLONG perfCount, REFGUID guidTx);

STDMETHODIMP GetSinkType(EventEnum * e){*e = Transaction;return S_OK;}

};

#endif //__TXSINK_H_


USERSINK.H

/******************************************************************************\
********************************************************************************
* Filename: UserSink.h
*
* Description: Declaration of the CUserSink -- for user defined events
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
#ifndef __USERSINK_H__
#define __USERSINK_H__

#include "resource.h"       // main symbols

class CMtsSpy;
/////////////////////////////////////////////////////////////////////////////
// CUserSink
class ATL_NO_VTABLE CUserSink :
public CMtsSink,
public IMtsUserEvent
{
public:

CUserSink(CMtsSpy * pSpy)
{
m_pSpy = pSpy;
}
CUserSink()
{
m_pSpy = NULL;
}

DECLARE_NOT_AGGREGATABLE(CUserSink)
DECLARE_GET_CONTROLLING_UNKNOWN()

BEGIN_COM_MAP(CUserSink)
COM_INTERFACE_ENTRY(IMtsUserEvent)
COM_INTERFACE_ENTRY_CHAIN(CMtsSink)
END_COM_MAP()

virtual REFIID GetIID(){return IID_IMtsUserEvent;}
STDMETHODIMP GetSinkType(EventEnum * e){*e = User;return S_OK;}

STDMETHODIMP OnUserEvent(LONGLONG perfCount, VARIANT * var)
{
if (var -> vt == VT_BSTR)
{
m_pSpy -> AddEventToList(perfCount, _TEXT("OnUserEvent"), m_sPackageName);
m_pSpy -> AddParamValueToList(L"String Value",  var->bstrVal);
}
else if (var -> vt == VT_UNKNOWN)
{
IMtsEventInfo * pInfo = NULL;
var -> punkVal -> QueryInterface(IID_IMtsEventInfo, (void **)&pInfo);
if (pInfo)
{
CComBSTR s;
pInfo -> get_DisplayName(&s.m_str);
m_pSpy -> AddEventToList(perfCount, s.m_str, m_sPackageName);

long lCount;
pInfo -> get_Count(&lCount);
if (lCount)
{
VARIANT var;
LPOLESTR * pKeys = new LPOLESTR[lCount+1];
IUnknown * pUnk = NULL;
pInfo -> get_Names(&pUnk);
_ASSERTE(pUnk);
IEnumString * pEnum;
pUnk -> QueryInterface(IID_IEnumString, (void **)&pEnum);
_ASSERTE(pEnum);
pUnk -> Release();
ULONG lFetched;
pEnum -> Next(lCount, pKeys, &lFetched);
pEnum -> Release();
_ASSERTE(lFetched == lCount);
int i;
for (i=0; i<lFetched; i++)
{
s = pKeys[i];
pInfo -> get_Value(s, &var);
if (var.vt == VT_BSTR)
{
m_pSpy -> AddParamValueToList(pKeys[i],  var.bstrVal);
VariantClear(&var);
}
}
for (i=0;i<lCount;i++)
CoTaskMemFree(pKeys[i]);

delete pKeys;
}
pInfo -> Release();
}
}
else
m_pSpy -> AddEventToList(perfCount, _TEXT("OnUserEvent"), m_sPackageName);
return S_OK;
}

};

#endif //__USERSINK_H__


MTSUSEREVENTDATA.CPP

/******************************************************************************\
********************************************************************************
* Filename: MtsUserEventData.cpp
*
* Description:  Implementation of CMtsUserEventData -- uses a linked list
*to maintain named-valued pairs
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#include "stdafx.h"
#include "MtsUserEventObj.h"
#include "MtsUserEventData.h"
#include "mtsevents_i.c"

/////////////////////////////////////////////////////////////////////////////
// CMtsUserEventData
CMtsUserEventData::~CMtsUserEventData()
{

NameValueList::iterator item;
CNameValue * pNV;
for (item = m_list.begin(); item != m_list.end(); ++item)
{

pNV = *item;
if (pNV)
delete pNV;
}

m_list.clear();

}

STDMETHODIMP CMtsUserEventData::put_DisplayName(BSTR newVal)
{
m_sDisplayName = newVal;
return S_OK;
}

STDMETHODIMP CMtsUserEventData::put_GuidEventId(BSTR newVal)
{

m_sEventID = newVal;
return S_OK;
}

STDMETHODIMP CMtsUserEventData::put_Value(BSTR sKey, VARIANT * newVal)
{
CNameValue * pNV = new CNameValue(sKey, newVal);
m_list.push_back(pNV);
return S_OK;
}

STDMETHODIMP CMtsUserEventData::get_DisplayName(BSTR * sDisplayName)
{
*sDisplayName = m_sDisplayName.Copy();
return S_OK;
}
STDMETHODIMP CMtsUserEventData::get_EventID(BSTR * sGuidEventID)
{
*sGuidEventID = m_sEventID.Copy();
return S_OK;
}

STDMETHODIMP CMtsUserEventData::get_Count(long * lCount)
{
*lCount = m_list.size();
return S_OK;
}

STDMETHODIMP CMtsUserEventData::get_Value(BSTR sKey, VARIANT * pVal)
{

NameValueList::iterator item;
CNameValue * pNV;
for (item = m_list.begin(); item != m_list.end(); ++item)
{

pNV = *item;
if (lstrcmpi(pNV -> m_sName, sKey) == 0)
{
VariantInit(pVal);
pVal -> vt = VT_BSTR;
pVal -> bstrVal = ::SysAllocString(pNV -> m_sValue.bstrVal);
break;
}

}

return S_OK;
}

STDMETHODIMP CMtsUserEventData::get_Names(IUnknown ** pUnk)
{
long lCount = m_list.size();
LPOLESTR *var = new LPOLESTR[lCount];
int i =0;
NameValueList::iterator item;
CNameValue * pNV;
for (item = m_list.begin(); item != m_list.end(); ++item)
{

pNV = *item;
var[i]  = pNV->m_sName;
i++;
}

_ASSERTE(i == lCount);
typedef CComObject<CComEnum<IEnumString, &IID_IEnumString, LPOLESTR,
_Copy<LPOLESTR> > > enumvar;
enumvar* p = new enumvar;
p->Init(&var[0], &var[i], NULL, AtlFlagCopy); // make copies
delete [] var;
return p->QueryInterface(IID_IUnknown, (void**)pUnk);
}


MTSUSEREVENTDATA.H

/******************************************************************************\
********************************************************************************
* Filename: MtsUserEventData.h
*
* Description: Declaration of the CMtsUserEventDat
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#ifndef __MTSUSEREVENTDATA_H_
#define __MTSUSEREVENTDATA_H_

#include "resource.h"       // main symbols
#include "mtsevents.h"
class CNameValue
{
public:
CComBSTR m_sName;
CComVariant m_sValue;
CNameValue();
CNameValue(LPTSTR sName, VARIANT * pVar)
{
m_sName = sName;
m_sValue = *pVar;
}

};

typedef list<CNameValue *> NameValueList;
/////////////////////////////////////////////////////////////////////////////
// CMtsUserEventData
class ATL_NO_VTABLE CMtsUserEventData :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CMtsUserEventData, &CLSID_MtsUserEventData>,
public IDispatchImpl<IMtsUserEventData, &IID_IMtsUserEventData, &LIBID_MTSUSEREVENTOBJLib>,
public IDispatchImpl<IMtsEventInfo, &IID_IMtsEventInfo, &LIBID_MTSEVENTSLib>

{

private:

CComBSTR m_sDisplayName;
CComBSTR m_sEventID;
NameValueList m_list;

public:
CMtsUserEventData()
{
}
~CMtsUserEventData();
DECLARE_REGISTRY_RESOURCEID(IDR_MTSUSEREVENTDATA)

BEGIN_COM_MAP(CMtsUserEventData)
COM_INTERFACE_ENTRY(IMtsUserEventData)
COM_INTERFACE_ENTRY2(IDispatch, IMtsEventInfo)
COM_INTERFACE_ENTRY(IMtsEventInfo)
END_COM_MAP()

STDMETHOD(get_DisplayName)(BSTR * sDisplayName);
STDMETHOD(get_EventID)(BSTR * sGuidEventID);
STDMETHOD(get_Count)(long * lCount);
STDMETHOD(get_Value)(BSTR sKey, VARIANT * pVal);
STDMETHOD(get_Names)(IUnknown ** pUnk);

// IMtsUserEventData
public:
STDMETHOD(put_Value)(BSTR sKey, /*[in]*/ VARIANT * newVal);
STDMETHOD(put_GuidEventId)(/*[in]*/ BSTR newVal);
STDMETHOD(put_DisplayName)(/*[in]*/ BSTR newVal);
};

#endif //__MTSUSEREVENTDATA_H_


MTSUSEREVENTOBJ.CPP

/******************************************************************************\
********************************************************************************
* Filename: MtsUserEventObj.cpp
*
* Description: Implementation of DLL Exports.
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#include "stdafx.h"
#include "resource.h"
#include "initguid.h"
#include "MtsUserEventObj.h"

#include "MtsUserEventObj_i.c"
#include "MtsUserEventData.h"

#ifdef _MERGE_PROXYSTUB
extern "C" HINSTANCE hProxyDll;
#endif

CComModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_MtsUserEventData, CMtsUserEventData)
END_OBJECT_MAP()

/////////////////////////////////////////////////////////////////////////////
// DLL Entry Point

extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
lpReserved;
#ifdef _MERGE_PROXYSTUB
if (!PrxDllMain(hInstance, dwReason, lpReserved))
return FALSE;
#endif
if (dwReason == DLL_PROCESS_ATTACH)
{
_Module.Init(ObjectMap, hInstance);
DisableThreadLibraryCalls(hInstance);
}
else if (dwReason == DLL_PROCESS_DETACH)
_Module.Term();
return TRUE;    // ok
}

/////////////////////////////////////////////////////////////////////////////
// Used to determine whether the DLL can be unloaded by OLE

STDAPI DllCanUnloadNow(void)
{
#ifdef _MERGE_PROXYSTUB
if (PrxDllCanUnloadNow() != S_OK)
return S_FALSE;
#endif
return (_Module.GetLockCount()==0) ? S_OK : S_FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// Returns a class factory to create an object of the requested type

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
#ifdef _MERGE_PROXYSTUB
if (PrxDllGetClassObject(rclsid, riid, ppv) == S_OK)
return S_OK;
#endif
return _Module.GetClassObject(rclsid, riid, ppv);
}

/////////////////////////////////////////////////////////////////////////////
// DllRegisterServer - Adds entries to the system registry

STDAPI DllRegisterServer(void)
{
#ifdef _MERGE_PROXYSTUB
HRESULT hRes = PrxDllRegisterServer();
if (FAILED(hRes))
return hRes;
#endif
// registers object, typelib and all interfaces in typelib
return _Module.RegisterServer(TRUE);
}

/////////////////////////////////////////////////////////////////////////////
// DllUnregisterServer - Removes entries from the system registry

STDAPI DllUnregisterServer(void)
{
#ifdef _MERGE_PROXYSTUB
PrxDllUnregisterServer();
#endif
_Module.UnregisterServer();
return S_OK;
}


MTSUSEREVENTOBJ.H

/* this ALWAYS GENERATED file contains the definitions for the interfaces */

/* File created by MIDL compiler version 3.01.75 */
/* at Wed Mar 19 09:35:54 1997
 */
/* Compiler settings for MtsUserEventObj.idl:
    Oicf (OptLev=i2), W1, Zp8, env=Win32, ms_ext, c_ext
    error checks: none
*/
//@@MIDL_FILE_HEADING(  )
#include "rpc.h"
#include "rpcndr.h"
#ifndef COM_NO_WINDOWS_H
#include "windows.h"
#include "ole2.h"
#endif /*COM_NO_WINDOWS_H*/

#ifndef __MtsUserEventObj_h__
#define __MtsUserEventObj_h__

#ifdef __cplusplus
extern "C"{
#endif

/* Forward Declarations */

#ifndef __IMtsUserEventData_FWD_DEFINED__
#define __IMtsUserEventData_FWD_DEFINED__
typedef interface IMtsUserEventData IMtsUserEventData;
#endif /* __IMtsUserEventData_FWD_DEFINED__ */

#ifndef __MtsUserEventData_FWD_DEFINED__
#define __MtsUserEventData_FWD_DEFINED__

#ifdef __cplusplus
typedef class MtsUserEventData MtsUserEventData;
#else
typedef struct MtsUserEventData MtsUserEventData;
#endif /* __cplusplus */

#endif /* __MtsUserEventData_FWD_DEFINED__ */

/* header files for imported files */
#include "oaidl.h"
#include "ocidl.h"

void __RPC_FAR * __RPC_USER MIDL_user_allocate(size_t);
void __RPC_USER MIDL_user_free( void __RPC_FAR * );

#ifndef __IMtsUserEventData_INTERFACE_DEFINED__
#define __IMtsUserEventData_INTERFACE_DEFINED__

/****************************************
 * Generated header for interface: IMtsUserEventData
 * at Wed Mar 19 09:35:54 1997
 * using MIDL 3.01.75
 ****************************************/
/* [unique][helpstring][dual][uuid][object] */

EXTERN_C const IID IID_IMtsUserEventData;

#if defined(__cplusplus) && !defined(CINTERFACE)

    interface DECLSPEC_UUID("F523E424-8485-11D0-B170-00AA00BA3258")
    IMtsUserEventData : public IDispatch
    {
    public:
        virtual /* [helpstring][id][propput] */ HRESULT STDMETHODCALLTYPE put_DisplayName(
            /* [in] */ BSTR newVal) = 0;

        virtual /* [helpstring][id][propput] */ HRESULT STDMETHODCALLTYPE put_GuidEventId(
            /* [in] */ BSTR newVal) = 0;

        virtual /* [helpstring][id][propput] */ HRESULT STDMETHODCALLTYPE put_Value(
            BSTR sKey,
            /* [in] */ VARIANT __RPC_FAR *newVal) = 0;

    };

#else /* C style interface */

    typedef struct IMtsUserEventDataVtbl
    {
        BEGIN_INTERFACE

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *QueryInterface )(
            IMtsUserEventData __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [iid_is][out] */ void __RPC_FAR *__RPC_FAR *ppvObject);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *AddRef )(
            IMtsUserEventData __RPC_FAR * This);

        ULONG ( STDMETHODCALLTYPE __RPC_FAR *Release )(
            IMtsUserEventData __RPC_FAR * This);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfoCount )(
            IMtsUserEventData __RPC_FAR * This,
            /* [out] */ UINT __RPC_FAR *pctinfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetTypeInfo )(
            IMtsUserEventData __RPC_FAR * This,
            /* [in] */ UINT iTInfo,
            /* [in] */ LCID lcid,
            /* [out] */ ITypeInfo __RPC_FAR *__RPC_FAR *ppTInfo);

        HRESULT ( STDMETHODCALLTYPE __RPC_FAR *GetIDsOfNames )(
            IMtsUserEventData __RPC_FAR * This,
            /* [in] */ REFIID riid,
            /* [size_is][in] */ LPOLESTR __RPC_FAR *rgszNames,
            /* [in] */ UINT cNames,
            /* [in] */ LCID lcid,
            /* [size_is][out] */ DISPID __RPC_FAR *rgDispId);

        /* [local] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *Invoke )(
            IMtsUserEventData __RPC_FAR * This,
            /* [in] */ DISPID dispIdMember,
            /* [in] */ REFIID riid,
            /* [in] */ LCID lcid,
            /* [in] */ WORD wFlags,
            /* [out][in] */ DISPPARAMS __RPC_FAR *pDispParams,
            /* [out] */ VARIANT __RPC_FAR *pVarResult,
            /* [out] */ EXCEPINFO __RPC_FAR *pExcepInfo,
            /* [out] */ UINT __RPC_FAR *puArgErr);

        /* [helpstring][id][propput] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *put_DisplayName )(
            IMtsUserEventData __RPC_FAR * This,
            /* [in] */ BSTR newVal);

        /* [helpstring][id][propput] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *put_GuidEventId )(
            IMtsUserEventData __RPC_FAR * This,
            /* [in] */ BSTR newVal);

        /* [helpstring][id][propput] */ HRESULT ( STDMETHODCALLTYPE __RPC_FAR *put_Value )(
            IMtsUserEventData __RPC_FAR * This,
            BSTR sKey,
            /* [in] */ VARIANT __RPC_FAR *newVal);

        END_INTERFACE
    } IMtsUserEventDataVtbl;

    interface IMtsUserEventData
    {
        CONST_VTBL struct IMtsUserEventDataVtbl __RPC_FAR *lpVtbl;
    };

#ifdef COBJMACROS

#define IMtsUserEventData_QueryInterface(This,riid,ppvObject)\
    (This)->lpVtbl -> QueryInterface(This,riid,ppvObject)

#define IMtsUserEventData_AddRef(This)\
    (This)->lpVtbl -> AddRef(This)

#define IMtsUserEventData_Release(This)\
    (This)->lpVtbl -> Release(This)

#define IMtsUserEventData_GetTypeInfoCount(This,pctinfo)\
    (This)->lpVtbl -> GetTypeInfoCount(This,pctinfo)

#define IMtsUserEventData_GetTypeInfo(This,iTInfo,lcid,ppTInfo)\
    (This)->lpVtbl -> GetTypeInfo(This,iTInfo,lcid,ppTInfo)

#define IMtsUserEventData_GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)\
    (This)->lpVtbl -> GetIDsOfNames(This,riid,rgszNames,cNames,lcid,rgDispId)

#define IMtsUserEventData_Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)\
    (This)->lpVtbl -> Invoke(This,dispIdMember,riid,lcid,wFlags,pDispParams,pVarResult,pExcepInfo,puArgErr)

#define IMtsUserEventData_put_DisplayName(This,newVal)\
    (This)->lpVtbl -> put_DisplayName(This,newVal)

#define IMtsUserEventData_put_GuidEventId(This,newVal)\
    (This)->lpVtbl -> put_GuidEventId(This,newVal)

#define IMtsUserEventData_put_Value(This,sKey,newVal)\
    (This)->lpVtbl -> put_Value(This,sKey,newVal)

#endif /* COBJMACROS */

#endif /* C style interface */

/* [helpstring][id][propput] */ HRESULT STDMETHODCALLTYPE IMtsUserEventData_put_DisplayName_Proxy(
    IMtsUserEventData __RPC_FAR * This,
    /* [in] */ BSTR newVal);

void __RPC_STUB IMtsUserEventData_put_DisplayName_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id][propput] */ HRESULT STDMETHODCALLTYPE IMtsUserEventData_put_GuidEventId_Proxy(
    IMtsUserEventData __RPC_FAR * This,
    /* [in] */ BSTR newVal);

void __RPC_STUB IMtsUserEventData_put_GuidEventId_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

/* [helpstring][id][propput] */ HRESULT STDMETHODCALLTYPE IMtsUserEventData_put_Value_Proxy(
    IMtsUserEventData __RPC_FAR * This,
    BSTR sKey,
    /* [in] */ VARIANT __RPC_FAR *newVal);

void __RPC_STUB IMtsUserEventData_put_Value_Stub(
    IRpcStubBuffer *This,
    IRpcChannelBuffer *_pRpcChannelBuffer,
    PRPC_MESSAGE _pRpcMessage,
    DWORD *_pdwStubPhase);

#endif /* __IMtsUserEventData_INTERFACE_DEFINED__ */

#ifndef __MTSUSEREVENTOBJLib_LIBRARY_DEFINED__
#define __MTSUSEREVENTOBJLib_LIBRARY_DEFINED__

/****************************************
 * Generated header for library: MTSUSEREVENTOBJLib
 * at Wed Mar 19 09:35:54 1997
 * using MIDL 3.01.75
 ****************************************/
/* [helpstring][version][uuid] */

EXTERN_C const IID LIBID_MTSUSEREVENTOBJLib;

#ifdef __cplusplus
EXTERN_C const CLSID CLSID_MtsUserEventData;

class DECLSPEC_UUID("F523E425-8485-11D0-B170-00AA00BA3258")
MtsUserEventData;
#endif
#endif /* __MTSUSEREVENTOBJLib_LIBRARY_DEFINED__ */

/* Additional Prototypes for ALL interfaces */

unsigned long             __RPC_USER  BSTR_UserSize(     unsigned long __RPC_FAR *, unsigned long            , BSTR __RPC_FAR * );
unsigned char __RPC_FAR * __RPC_USER  BSTR_UserMarshal(  unsigned long __RPC_FAR *, unsigned char __RPC_FAR *, BSTR __RPC_FAR * );
unsigned char __RPC_FAR * __RPC_USER  BSTR_UserUnmarshal(unsigned long __RPC_FAR *, unsigned char __RPC_FAR *, BSTR __RPC_FAR * );
void                      __RPC_USER  BSTR_UserFree(     unsigned long __RPC_FAR *, BSTR __RPC_FAR * );

unsigned long             __RPC_USER  VARIANT_UserSize(     unsigned long __RPC_FAR *, unsigned long            , VARIANT __RPC_FAR * );
unsigned char __RPC_FAR * __RPC_USER  VARIANT_UserMarshal(  unsigned long __RPC_FAR *, unsigned char __RPC_FAR *, VARIANT __RPC_FAR * );
unsigned char __RPC_FAR * __RPC_USER  VARIANT_UserUnmarshal(unsigned long __RPC_FAR *, unsigned char __RPC_FAR *, VARIANT __RPC_FAR * );
void                      __RPC_USER  VARIANT_UserFree(     unsigned long __RPC_FAR *, VARIANT __RPC_FAR * );

/* end of Additional Prototypes */

#ifdef __cplusplus
}
#endif

#endif


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by MtsUserEventObj.rc
//
#define IDS_PROJNAME                    100
#define IDR_MTSUSEREVENTDATA            101

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        201
#define _APS_NEXT_COMMAND_VALUE         32768
#define _APS_NEXT_CONTROL_VALUE         201
#define _APS_NEXT_SYMED_VALUE           102
#endif
#endif


STDAFX.CPP

/******************************************************************************\
********************************************************************************
* Filename: StdAfx.cpp
*
* Description:
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
// stdafx.cpp : source file that includes just the standard includes
//  stdafx.pch will be the pre-compiled header
//  stdafx.obj will contain the pre-compiled type information

#include "stdafx.h"

#ifdef _ATL_STATIC_REGISTRY
#include <statreg.h>
#include <statreg.cpp>
#endif

#include <atlimpl.cpp>


STDAFX.H

/******************************************************************************\
********************************************************************************
* Filename: StdAfx.h
*
* Description:

stdafx.h : include file for standard system include files,
or project specific include files that are used frequently,
but are changed infrequently

*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#if !defined(AFX_STDAFX_H__F523E419_8485_11D0_B170_00AA00BA3258__INCLUDED_)
#define AFX_STDAFX_H__F523E419_8485_11D0_B170_00AA00BA3258__INCLUDED_

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

#define STRICT

#include <list>
#include <map>
using namespace std;
#pragma warning( disable : 4786) // disable the warning "identifier was truncated to '255' characters in the debug information" generated by STL

#define SafeRelease(pUnk) {if (pUnk){pUnk -> Release();pUnk = NULL; }}

#define _WIN32_WINNT 0x0400
#define _ATL_APARTMENT_THREADED

#include <atlbase.h>
//You may derive a class from CComModule and use it if you want to override
//something, but do not change the name of _Module
extern CComModule _Module;
#include <atlcom.h>

//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_STDAFX_H__F523E419_8485_11D0_B170_00AA00BA3258__INCLUDED)


COPYFILERM.IDL

/******************************************************************************\
********************************************************************************
* Filename: CopyFileRm.idl
*
* Description:IDL source for CopyFileRm.dll
*   This file will be processed by the MIDL tool to
*produce the type library (CopyFileRm.tlb) and marshalling code.
*
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

import "oaidl.idl";
import "ocidl.idl";

[
object,
uuid(BACEDF87-74AB-11D0-B162-00AA00BA3258),
helpstring("IFileRm Interface"),
pointer_default(unique)
]
interface IFileRm : IUnknown
{
[helpstring("copy the list of files")]
HRESULT CopyListTx([in] long lConHandle);

[helpstring("breaks a connection")]
HRESULT Disconnect([in] long ulConnHandle);

[helpstring("Establishes a connection")]
HRESULT Connect([in] BSTR sAppName, [out] long * ulConnHandle);

[helpstring("copy a file")]
HRESULT FileCopyTx([in] long lConHandle, [in] BSTR sSource, [in] BSTR sDestination, [in] BOOL bFailIfExists);

[helpstring("rename a file")]
HRESULT FileRenameTx([in] long lConHandle, [in] BSTR sSource, [in] BSTR sDestination);

[helpstring("add a file to copy")]
HRESULT AddFile([in] long lConHandle, [in] BSTR sSource, [in] BSTR sDestination);

[helpstring("Move a file")]
HRESULT FileMoveTxt([in] long lConHandle, [in] BSTR sSource, [in] BSTR sDestination);

[helpstring("delete a file")]
HRESULT FileDeleteTx([in] long lConHandle, [in] BSTR sFileName);

[helpstring("gets the location of the TM")]
HRESULT GetTmWhereabouts([in] long lConHandle,[out, size_is(, *pcbTmAddr)]BYTE **rgbTmAddr, [out] ULONG *pcbTmAddr);

[helpstring("Exports the Tx")]
HRESULT ExportTx([in] long lConHandle, [in] ULONG cbTranCookie, [in, size_is(cbTranCookie)]BYTE *rgbTranCookie);

[helpstring("Resets the Worker for the given handle")]
HRESULT ResetConnection([in] long lConHandle);
[helpstring("method TMDown")] HRESULT TMDown();

};
[
uuid(BACEDF7A-74AB-11D0-B162-00AA00BA3258),
version(1.0),
helpstring("CopyFileRm 1.0 Type Library")
]
library COPYFILERMLib
{
importlib("stdole32.tlb");

[
uuid(F67A6312-4EC4-11D0-9697-00AA00C149A6),
helpstring("FileRm Class")
]
coclass CoFileRm
{
[default] interface IFileRm;
};
};


IID.C

//
// iid.c
//
#include "copyfilerm_i.c"
#include "simplelog_i.c"


SIMPLELOG.IDL

/******************************************************************************\
********************************************************************************
* Filename: SimpleLog.idl
*
* Description:
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
// SimpleLog.idl : IDL source for SimpleLog.dll
//

// This file will be processed by the MIDL tool to
// produce the type library (SimpleLog.tlb) and marshalling code.
import "oaidl.idl";

//---------------------------------------------------------------------
// LOG_RM_COMMAND:
// Enumeration of resource manager command codes used during logging.
//---------------------------------------------------------------------

typedef long LOG_RM_COMMAND;

const LOG_RM_COMMAND LOG_RM_PRIVATE=1;
const LOG_RM_COMMAND LOG_RM_XACT_PREPARE=2;
const LOG_RM_COMMAND LOG_RM_XACT_COMMIT=3;
const LOG_RM_COMMAND LOG_RM_XACT_BEGIN=4;
const LOG_RM_COMMAND LOG_RM_XACT_END=5;
const LOG_RM_COMMAND LOG_RM_ENDOFLOG=6;
const LOG_RM_COMMAND LOG_RM_CHECKPOINT=7;
const LOG_RM_COMMAND LOG_RM_ABORT_REQUEST=20;
const LOG_RM_COMMAND LOG_RM_COMMIT_REQUEST=21;

[

object,
uuid(5BFC4335-4C90-11D0-9697-00AA00C149A6),
helpstring("ISimpleLog Interface"),
pointer_default(unique)
]
interface ISimpleLog : IUnknown
{

HRESULTWriteLog ([in] GUID guidTx,
[in] LOG_RM_COMMAND eOp,
[in, size_is(cbBytes)] BYTE * pByte,
[in] long cbBytes);

HRESULTInit([in]BSTR  pszLogFileName);
HRESULTRecoveryInfo([out] IStorage **ppStorage);
[helpstring("method WriteStreamToLog")] HRESULT WriteStreamToLog(GUID GuidTx, LOG_RM_COMMAND eOp, IStream * pStream);
[helpstring("method RemoveTxStream")] HRESULT RemoveTxStream(GUID guidTx);
[helpstring("method WriteVariantToLog")] HRESULT WriteVariantToLog(GUID m_guidTx, VARIANT *pVariant);
[helpstring("method GetStream")] HRESULT GetStream(GUID guidTx, [out] IStream **ppStream);

};
[
uuid(5BFC4333-4C90-11D0-9697-00AA00C149A6),
version(1.0),
helpstring("SimpleLog 1.0 Type Library")
]
library SIMPLELOGLib
{
importlib("stdole32.tlb");

[
uuid(5BFC4336-4C90-11D0-9697-00AA00C149A6),
helpstring("SimpleLog Class")
]
coclass CoSimpleLog
{
[default] interface ISimpleLog;
};
};


ACTION.CPP

/******************************************************************************\
********************************************************************************
* Filename: action.cpp
*
* Description:  abstracts a RM "Action"
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
#include "stdafx.h"
#include "SimpleLog.h"
#include "action.h"

CAction::CAction()
{
m_sPath1 = NULL;
m_sPath2 = NULL;
m_hLock=NULL;
*m_pszLockedFile = NULL;
}
CAction::~CAction()
{
ReleaseLock();
if (m_sPath1)
delete [] m_sPath1;
if (m_sPath2)
delete [] m_sPath2;
}

CAction::CAction (CAction * pAction)
{
m_sPath1 = NULL;
m_sPath2 = NULL;
m_hLock=NULL;
m_eFileOp = pAction -> m_eFileOp;
m_eRmCommand = pAction -> m_eRmCommand;
*m_pszLockedFile = NULL;
if (pAction -> m_sPath1)
{
m_sPath1 = new TCHAR[lstrlen(pAction -> m_sPath1)+1];
lstrcpy(m_sPath1, pAction -> m_sPath1);
}

if (pAction -> m_sPath2)
{
m_sPath2 = new TCHAR[lstrlen(pAction -> m_sPath2)+1];
lstrcpy(m_sPath1, pAction -> m_sPath2);
}
}

CAction::CAction(LOG_RM_COMMAND rmCommand, FILE_OPERATION eOpIn,  LPTSTR pszInParm1, LPTSTR pszInParm2)
{
m_sPath1 = NULL;
m_sPath2 = NULL;
m_hLock=NULL;
m_eFileOp = eOpIn;
m_eRmCommand = rmCommand;
*m_pszLockedFile = NULL;
if (pszInParm1)
{
m_sPath1 = new TCHAR[lstrlen(pszInParm1) + 1];
*m_sPath1 = NULL;
lstrcpy(m_sPath1, pszInParm1);
}

if (pszInParm2)
{
m_sPath2 = new TCHAR[lstrlen(pszInParm2)+1];
*m_sPath2 = NULL;
lstrcpy(m_sPath2, pszInParm2);
}
}

BOOL CAction::LockFile(TCHAR * pFile)
{

lstrcpy(m_pszLockedFile, pFile);
m_hLock = CreateFile(   pFile,// pointer to name of the file
GENERIC_WRITE|GENERIC_READ,// access (read-write) mode
0L,// share mode
NULL,// pointer to security descriptor
OPEN_EXISTING,// how to create
FILE_ATTRIBUTE_NORMAL,// file attributes
NULL); // handle to file with attributes to copy

_ASSERTE(m_hLock);
BOOL bRet;
memset(&m_attrFile, NULL, sizeof(m_attrFile));

// store the file attributes for later user
bRet = GetFileAttributesEx(pFile, GetFileExInfoStandard, &m_attrFile);
_ASSERTE(bRet);

// set the file to Hidden (the file is Read only because it is open)
bRet = SetFileAttributes(pFile, FILE_ATTRIBUTE_HIDDEN);
_ASSERTE(bRet);
return m_hLock ? TRUE : FALSE;

}

void CAction::ReleaseLock()
{
if (m_hLock)
{

if (!CloseHandle(m_hLock))
_ASSERTE(0);

m_hLock = NULL;
BOOL bRet = SetFileAttributes(m_pszLockedFile, m_attrFile.dwFileAttributes);
_ASSERTE(bRet);
}
}
//
//save the data into a byte stream
//

HRESULT CAction::Save(BYTE **ppByte, ULONG * cbByte)
{
LPSTR sPath1, sPath2;
#ifdef _UNICODE // convert to ANSI
USES_CONVERSION;
sPath1  = W2A(m_sPath1);
sPath2 = W2A(m_sPath2);
#else
sPath1 = m_sPath1;
sPath2 = m_sPath2;
#endif

int lPath1Size = 1;
if (sPath1)
lPath1Size += lstrlenA(sPath1);

int lPath2Size = 1;
if (sPath2)
lPath2Size += lstrlenA(sPath2)+1;

*cbByte =sizeof(m_eFileOp)+
lPath1Size +
sizeof(DWORD) + // string len
lPath2Size +
sizeof(DWORD) + // string len
sizeof(m_eRmCommand);

*ppByte = new BYTE[*cbByte];
_ASSERTE(*ppByte);
if (!*ppByte)
return E_OUTOFMEMORY;
ULONG lOffset = 0;
memset(*ppByte, NULL, (int)*cbByte);

*(FILE_OPERATION *)(*ppByte + lOffset) =  m_eFileOp;
lOffset += sizeof(m_eFileOp);

*(LOG_RM_COMMAND *)(*ppByte + lOffset) =  m_eRmCommand;
lOffset += sizeof(m_eRmCommand);

*(ULONG *)(*ppByte + lOffset) = lPath1Size;
lOffset +=sizeof(ULONG);

if (m_sPath1)
lstrcatA((LPSTR)(*ppByte + lOffset), sPath1);

lOffset += lPath1Size;

*(ULONG *)(*ppByte + lOffset) = lPath2Size;
lOffset +=sizeof(ULONG);

if (m_sPath2)
lstrcatA((LPSTR)(*ppByte + lOffset), sPath2);

lOffset += lPath2Size;
_ASSERTE(lOffset == *cbByte);

return S_OK;

}
HRESULT CAction::Load(BYTE * pByte, long cbByte)
{

LPSTR p1, p2;
p1 = p2 = NULL;
ULONG lOffset = 0;
m_eFileOp=  *(FILE_OPERATION *)(pByte) ;
lOffset += sizeof(m_eFileOp);
m_eRmCommand=*(LOG_RM_COMMAND *)(pByte + lOffset);
lOffset += sizeof(m_eRmCommand);

ULONG cbStringLen;
cbStringLen = *(ULONG *)(pByte + lOffset);
lOffset +=sizeof(ULONG);
m_sPath1 = NULL;
if (cbStringLen)
{
p1  = new CHAR[cbStringLen];
memset(p1, NULL, cbStringLen);
memcpy(p1, (LPCSTR)(pByte + lOffset), cbStringLen);
}
lOffset += cbStringLen;

cbStringLen = *(ULONG *)(pByte + lOffset);
lOffset +=sizeof(ULONG);
m_sPath2 = NULL;
if (cbStringLen)
{
p2  = new CHAR[cbStringLen];
memset(p2, NULL, cbStringLen);
memcpy(p2, (LPCSTR)(pByte + lOffset), cbStringLen);
}
lOffset += cbStringLen;
_ASSERTE(lOffset == cbByte);

#ifdef _UNICODE
USES_CONVERSION;
m_sPath1 = new WCHAR[lstrlenA(p1)+1];
*m_sPath1 = NULL;
lstrcpy(m_sPath1, A2W(p1));
delete [] p1;

m_sPath2 = new WCHAR[lstrlenA(p2)+1];
*m_sPath2 = NULL;
lstrcpy(m_sPath2, A2W(p2));
delete [] p2;
#else
m_sPath1 = p1;
m_sPath2 = p2;
#endif
return S_OK;

}
//-----------------------------------------------------------------------------
// CActionList Implementation:
// Method implementations for the CActionList class.
//-----------------------------------------------------------------------------

CActionList::CActionList (void)
{

} // CActionList::CActionList

CActionList::~CActionList (void)
{
CleanupAction ();

} // CActionList::~CActionList

//
//if bCommit == TRUE then we check to see if we have
//a temp file in the recovery directory, and if we do
//we delete it
//
void CActionList::CleanupAction (BOOL bCommit /* = FALSE */)
{
// Iterate through the list and recover storage for list items.
while (!empty())
{
// Remove the first item and reclaim storage for action.
RemoveAction (bCommit);
} // while -- end traversal of action list.
}// CActionList::CleanupAction

BOOL CActionList::PushAction (LOG_RM_COMMAND rmCommand,FILE_OPERATION eFileOp, TCHAR *pszParm1,TCHAR *pszParm2)
{
CAction*pAction = new CAction(rmCommand, eFileOp, pszParm1, pszParm2);

_ASSERTE (0 != pAction);
if (0 == pAction)
{
return FALSE;
} // if

insert(begin(), pAction);

return TRUE;

} // CActionList::PushAction

BOOL CActionList::PushAction(CAction * pAction)
{
_ASSERTE (0 != pAction);
if (0 == pAction)
{
return FALSE;
} // if

insert(begin(), pAction);
return TRUE;

}

//
//returns a pointer to the first element in the list
//
BOOL CActionList::PeekAction (CAction ** pprmAction)
{
if (empty())
return FALSE;

CAction*prmAction = NULL;
*pprmAction = front();
return TRUE;
} // CActionList::GetAction

//
//if bCommit is true, we delete any temporary files
//created during the tx (the only temp file that will
//still be around are files that were replaced during
//a copy
//
BOOL CActionList::RemoveAction (BOOL bCommit /* = FALSE */)
{
if (empty())
return FALSE;

CAction*pAction;
PeekAction(&pAction); // get the pointer

// if we committed the tx, then we need to remove
// the recover file, if it exists
if (bCommit)
{
if (pAction -> m_eFileOp == RM_DELETE)
{
pAction -> ReleaseLock();
// delete the recover file
DeleteFile(pAction -> m_pszLockedFile);
}

}
delete pAction;
pAction = 0;
// remove it from the list
pop_front();
return TRUE;
} // CActionList::RemoveAction

BOOL CActionList::PopAction (CAction& rmAction)
{
if (empty())
return FALSE;
//
//CODEREVIEW:
//is this a correct dereference?
//
rmAction  = *front();
pop_front();
return TRUE;
} // CActionList::PopAction

void CActionList::ResetActions (CActionList *actionList)
{
// List of actions not empty.
ACTIONLIST::iterator item;
CAction * pAction;
for (item = begin(); item != end(); ++item)
{
pAction = *item;
delete pAction;
}

clear();
// Set the current action list to the actionList
Clone (actionList);

} // CActionList::ResetActions()

//
//iterate through the list of actions
//and copy the CAction* to the local list
//
void CActionList::Clone (CActionList * pList)
{
 if (!pList)
 return;

 if (pList -> empty())
 return;

 ACTIONLIST::iterator item;
 for (item=pList -> begin();item != pList -> end(); ++item)
 {
push_back(*item); // mainttain the order
 }

} // CActionList::Clone

//---------------------------------------------------------------------
// End of $Workfile: ACTION.CPP $
//---------------------------------------------------------------------


ACTION.H

/******************************************************************************\
********************************************************************************
* Filename: action.h
*
* Description:  abstracts a RM "Action"
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#ifndef _ACTIONLIST_H_
#define _ACTIONLIST_H_

#include "rmworker.h"
#include <list>

using namespace std;
//---------------------------------------------------------------------
// This file contains all type and class definitions used by the file
// resource manager to manage an in-memory list of all its actions.
//---------------------------------------------------------------------

class CAction
{
public:
// non-logged info...this isn't logged because the lock on the file
// is released when the process crashes (in this case the rm)

HANDLE m_hLock;// the handle to the file that acts as a lock
WIN32_FILE_ATTRIBUTE_DATA m_attrFile;
TCHAR m_pszLockedFile[ MAX_PATH ];

public:

// permanent information -- logged
FILE_OPERATION m_eFileOp;
LOG_RM_COMMAND m_eRmCommand;
    LPTSTR m_sPath1;
LPTSTR m_sPath2;

CAction();
~CAction();
CAction (CAction * pAction);
CAction(LOG_RM_COMMAND rmCommand, FILE_OPERATION eOpIn,  LPTSTR pszInParm1, LPTSTR pszInParm2);
BOOL LockFile(TCHAR * pFile);
void ReleaseLock();

HRESULT Save(BYTE **ppByte, ULONG * cbByte);
HRESULT Load(BYTE * pByte, long cbByte);

};

//---------------------------------------------------------------------
// CActionList:
// Class definition for the action list manager.
//---------------------------------------------------------------------
typedef list <CAction *> ACTIONLIST;

class CActionList : public ACTIONLIST
{
public:

// Consructor interface:
// Defines the constructor and destructor for a CActionList
// class.
//CActionList-- instantiates a Action list object
//~CActionList-- deletes a Action list object

    CActionList (void);
// Action list constructor
~CActionList (void);// Action list destructor

// Action List Interface
// Defines group of interfaces that manage the in-memory action list for
// a recovery manager.
//AddAction-- logs and executes an action.
//      GetAction-- get action item from action list.
//RemoveAction-- undoes an action and logs an undo record.
//RecoverActions-- recovers a list of actions from the log.
//UndoActions-- undoes all actions on the action list.
//

void CleanupAction (BOOL bCommit = FALSE);// Delete all items from action list.
BOOL PushAction (LOG_RM_COMMAND rmCommand, FILE_OPERATION eFileOp, TCHAR *pszParm1, TCHAR *pszParm2);
BOOL PushAction(CAction * pAction);// Add an action to the action list.
BOOL PeekAction (CAction ** pprmAction);// Get action at top of action list.
BOOL RemoveAction (BOOL bCommit = FALSE);// Removes and action from top of list.
BOOL PopAction (CAction& prmAction);// Pops action at top of list combines
// Peek action and remove action.

void ResetActions (CActionList *actionList); // Resets the action list to actionList
void  Clone(CActionList * pList);// copies the

//ACTIONLIST*m_plistAction;
// In-memory list of all actions.
// performed for transaction manager.

}; // CActionList

#endif _ACTIONLIST_H_


IRMWORKERPRIV.CPP

/******************************************************************************\
********************************************************************************
* Filename: irmworkerpriv.cpp
*
* Description:  Contains the "Private" interface that the RM uses to communicate
with the worker
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
#include "stdafx.h"
#include "RmWorker.h"
#include "txdtc.h"
#include "txcoord.h"
#include "xolehlp.h"
#include "simplelog.h"
#include "action.h"
#include "copyfilerm.h"
#include "RmWorkerObj.h"

STDMETHODIMP CRmWorkerObj::SetState(TXSTATE eState)
{
m_eTxState = eState;
return S_OK;

} // CRmWorkerObj:SetState

STDMETHODIMP CRmWorkerObj::GetState(TXSTATE *eState)
{
*eState = m_eTxState;
return S_OK;

} // CRmWorkerObj::GetState()

STDMETHODIMP CRmWorkerObj::CleanupTx(void)
{
BOOLfReturn;

// Set state to initializing new transaction
SetState (TX_INITIALIZING);

// Cleanup all interfaces and objects used by transaction
if (m_pITx)
m_pITx->Release ();

m_pITx = NULL;

fReturn = RemoveAction();

HRESULT hr = WriteLog (m_guidTx, LOG_RM_XACT_END, RM_NONE, 0, 0);
_ASSERTE(hr == S_OK);
return S_OK;

} // CRmWorkerObj::CleanupTx()

//
//if we get a request to enlist on a tx, and we haven't finished
//the privious tx (usually do to an async abort), then we have
//to wait until the tx is done
//
STDMETHODIMP CRmWorkerObj::WaitTxDone(void)
{
_ASSERTE(m_hFinishedTxEvent);
DWORDdwWaitRc = 0;
dwWaitRc = WaitForSingleObject (m_hFinishedTxEvent, INFINITE);
_ASSERTE (WAIT_FAILED != dwWaitRc);
return S_OK;

} // CRmWorkerObj::WaitTxDone()

STDMETHODIMP CRmWorkerObj::PrepareTx (void)
{
BOOLfRc= TRUE;
HRESULThRc= S_OK;
IPrepareInfo*pIPrepareInfo= 0;
BYTE*rgbPrepareInfo= 0;
ULONGcbPrepareInfo= 0;
TCHARrgbSizeBuf[sizeof(ULONG)+1];

hRc = m_pEnlist->QueryInterface (IID_IPrepareInfo,
 (LPVOID *) &pIPrepareInfo);
_ASSERTE (S_OK == hRc);
_ASSERTE (0 != pIPrepareInfo);
if (S_OK != hRc)
{
SetStatus (RM_STATUS_INTERFACE);
return E_FAIL;
} // if -- QueryInterface failed
else
{
pIPrepareInfo->GetPrepareInfoSize (&cbPrepareInfo);
_ASSERTE (0 != cbPrepareInfo);

// Allocate storage for prepare info
rgbPrepareInfo = (BYTE *) CoTaskMemAlloc (cbPrepareInfo);
_ASSERTE (0 != rgbPrepareInfo);
if (0 == rgbPrepareInfo)
{
// Error message -- no memory
pIPrepareInfo->Release ();
pIPrepareInfo = 0;

SetStatus (RM_STATUS_NO_MEMORY);
return E_FAIL;
} // if

memset( (LPVOID) rgbPrepareInfo, '\0', cbPrepareInfo);
pIPrepareInfo->GetPrepareInfo ( (UCHAR *) rgbPrepareInfo);

pIPrepareInfo->Release ();
pIPrepareInfo = 0;

// CAUTION:  The rgbPrepareInfo byte stream is assumed to be a null terminated
// character string.  If this is not so the log write will fail.
wsprintf (rgbSizeBuf, L"%ld", cbPrepareInfo);
HRESULT hr = WriteLog (m_guidTx, LOG_RM_XACT_PREPARE, RM_NONE, (TCHAR *) rgbSizeBuf, (TCHAR *) rgbPrepareInfo);
if (hr != S_OK)
{
_ASSERTE(0);
// Error message -- could not write prepare record
CoTaskMemFree ((LPVOID) rgbPrepareInfo);

SetStatus (RM_STATUS_LOG_ERROR);
return E_FAIL;
} //
} // if -- QueryInterface succeeded.

CoTaskMemFree ((LPVOID) rgbPrepareInfo);
return S_OK;

} // CRmWorkerObj::PrepareTx()

STDMETHODIMP CRmWorkerObj::CommitTx (void)
{

HRESULT hr = WriteLog (m_guidTx, LOG_RM_XACT_COMMIT, RM_NONE, 0, 0);
if (hr != S_OK)
{
_ASSERTE(0);
SetStatus (RM_STATUS_LOG_ERROR);
return E_FAIL;
} // if

//
//we need to go through the action list to see if we
//created any temp files and if we need, we delete them
//

CleanupAction (TRUE);
return S_OK;

} // CRmWorkerObj::CommitTx()

STDMETHODIMP CRmWorkerObj::AbortTx (void)
{
HRESULThr = S_OK;

// Write begin undo record to the log
hr = WriteLog (m_guidTx, LOG_RM_PRIVATE, RM_UNDO_BEGIN, 0, 0);
if (hr != S_OK)
{
_ASSERTE(0);
return E_FAIL;
} // if

// Undo all action for the current transaction.
hr = RollbackTx ();
_ASSERTE (S_OK == hr);
if (S_OK == hr)
{
CleanupAction ();
} // if
return hr;

} // CRmWorkerObj::AbortTx()

STDMETHODIMP CRmWorkerObj::GetDTCUnknown(IUnknown ** pUnk)
{
*pUnk = NULL;
if (!m_pEnlist)
{
_ASSERTE(0);
return E_UNEXPECTED;
}
HRESULT hr = m_pEnlist -> QueryInterface(IID_IUnknown, (void **)pUnk);
_ASSERTE(*pUnk);
return hr;

}


RESOURCE.H

/******************************************************************************\
********************************************************************************
* Filename: resource.h
*
* Description:
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by RmWorker.rc
//
#define IDS_PROJNAME                    100
#define IDR_RmWorkerObj                 101

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        203
#define _APS_NEXT_COMMAND_VALUE         32768
#define _APS_NEXT_CONTROL_VALUE         201
#define _APS_NEXT_SYMED_VALUE           102
#endif
#endif


RMSINK.CPP

#include "stdafx.h"
#include "initguid.h"
#include    <txdtc.h>
#include<xolehlp.h>
#include<txcoord.h>
#include"rmsink.h"

//
// CIResourceManagerSink class implementation:
//

CIResourceManagerSink::CIResourceManagerSink(void)
{
m_cRefs = 0;// Initialize the reference
// count.
} // CIResourceManagerSink::CIResourceManagerSink()

CIResourceManagerSink::~CIResourceManagerSink(void)
{
// Do nothing.
} // CIResourceManagerSink::~CIResourceManagerSink()

STDMETHODIMP CIResourceManagerSink::QueryInterface(REFIID i_iid, LPVOID *ppv)
{
*ppv = 0;// Initialize interface pointer.

if (IID_IUnknown == i_iid || IID_IResourceManagerSink == i_iid)
{// IID supported return interface.
*ppv = this;
} // if

if (0 == *ppv)// Check for null interface pointer.
{
return ResultFromScode (E_NOINTERFACE);
// Neither IUnknown nor
// IResourceManagerSink supported--
// so return no interface.
} // if

((LPUNKNOWN) *ppv)->AddRef();// Interface is supported. Increment
// its usage count.

return S_OK;

} // CIResourceManagerSink::QueryInterface()

STDMETHODIMP_ (ULONG) CIResourceManagerSink::AddRef(void)
{

return ++m_cRefs;// Increment interface usage count.

} // CIResourceManagerSink::AddRef()

STDMETHODIMP_ (ULONG) CIResourceManagerSink::Release(void)
{

--m_cRefs;// Decrement usage reference count.

if (0 != m_cRefs)// Is anyone using the interface?
{// The interface is in use.
return m_cRefs;// Return the number of references.
} // if

delete this;// Interface not in use -- delete!

return 0;// Zero references returned.

} // CIResourceManagerSink::Release()

//
//TMDown should trigger a recover.  It is called when DTC goes down
//we should go into recovery mode.
//
//
//
STDMETHODIMP CIResourceManagerSink::TMDown(void)
{
return S_OK;

} // CIResourceManagerSink::TMDown()


RMSINK.H

#ifndef _RMSINK_H
#define _RMSINK_H

//---------------------------------------------------------------------
// Header file definition of the  class CIResourceManagerSink
// and all of its supporting constants and types.  The class
// CIResourceManagerSink is the implementation of the resource
// manager interface IResourceManagerSink.
//---------------------------------------------------------------------

//---------------------------------------------------------------------
// CIResourceManagerSink:
//---------------------------------------------------------------------

class CIResourceManagerSink: public IResourceManagerSink
{
public:

// Consructor interface:
// CIResourceManagerSink object constructor and destructor.
//CIResourceManagerSink--constructor for the class
//~CIResourceManagerSink--destructor for the class

CIResourceManagerSink(void);
~CIResourceManagerSink(void);

// IUnknown interface:
// Definition of the IUnknown interface for CIResourceManagerSink
// objects.
//QueryInterface-- determines the existence of an interface
//AddRef-- increments interface reference count
//Release-- decrements interface reference count

STDMETHODIMPQueryInterface(REFIID i_iid, LPVOID FAR* ppv);
STDMETHODIMP_ (ULONG)AddRef(void);
STDMETHODIMP_ (ULONG)Release(void);

// IResourceManagerSink interface:
// Defines the TMDown interface to notify RM when the transaction
// transaction manager is down.
//TMDown-- callback received when the TM goes down

STDMETHODIMPTMDown(void);

private:
ULONGm_cRefs;

}; // CIResourceManagerSink
#endif _RMSINK_H


RMWORKER.CPP

/******************************************************************************\
********************************************************************************
* Filename: RmWorker.cpp
*
* Description: Implementation of DLL Exports.
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#include "stdafx.h"
#include "resource.h"
#include "RmWorker.h"
#include "initguid.h"
#include "txdtc.h"
#include "txcoord.h"
#include "xolehlp.h"

#include "simplelog.h"
#include "action.h"
#include "initguid.h"
#include "copyfilerm.h"
#include "RmWorkerObj.h"

#define IID_DEFINED
#include "RmWorker_i.c"
#include "copyfilerm_i.c"

#ifdef _MERGE_PROXYSTUB
extern "C" HINSTANCE hProxyDll;
#endif

CComModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_CRmWorkerObj, CRmWorkerObj)
END_OBJECT_MAP()

/////////////////////////////////////////////////////////////////////////////
// DLL Entry Point

extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
lpReserved;
#ifdef _MERGE_PROXYSTUB
if (!PrxDllMain(hInstance, dwReason, lpReserved))
return FALSE;
#endif
if (dwReason == DLL_PROCESS_ATTACH)
{
_Module.Init(ObjectMap, hInstance);
DisableThreadLibraryCalls(hInstance);
}
else if (dwReason == DLL_PROCESS_DETACH)
_Module.Term();
return TRUE;    // ok
}

/////////////////////////////////////////////////////////////////////////////
// Used to determine whether the DLL can be unloaded by OLE

STDAPI DllCanUnloadNow(void)
{
#ifdef _MERGE_PROXYSTUB
if (PrxDllCanUnloadNow() != S_OK)
return S_FALSE;
#endif
return (_Module.GetLockCount()==0) ? S_OK : S_FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// Returns a class factory to create an object of the requested type

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
#ifdef _MERGE_PROXYSTUB
if (PrxDllGetClassObject(rclsid, riid, ppv) == S_OK)
return S_OK;
#endif
return _Module.GetClassObject(rclsid, riid, ppv);
}

/////////////////////////////////////////////////////////////////////////////
// DllRegisterServer - Adds entries to the system registry

STDAPI DllRegisterServer(void)
{
#ifdef _MERGE_PROXYSTUB
HRESULT hRes = PrxDllRegisterServer();
if (FAILED(hRes))
return hRes;
#endif
// registers object, typelib and all interfaces in typelib
return _Module.RegisterServer(TRUE);
}

/////////////////////////////////////////////////////////////////////////////
// DllUnregisterServer - Removes entries from the system registry

STDAPI DllUnregisterServer(void)
{
#ifdef _MERGE_PROXYSTUB
PrxDllUnregisterServer();
#endif
_Module.UnregisterServer();
return S_OK;
}


RMWORKEROBJ.CPP

/******************************************************************************\
********************************************************************************
* Filename: RmWorkerObj.cpp
*
* Description: Implementation of CRmWorkerObj -- main worker object
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#include "stdafx.h"
#include "RmWorker.h"
#defineINITGUID
#include "txdtc.h"
#include "txcoord.h"
#include "xolehlp.h"
#define IID_DEFINED
#include "SimpleLog.h"
#include "SimpleLog_i.c"
#include "action.h"
#include "copyfilerm.h"
#include "RmWorkerObj.h"
#pragma warning( disable : 4786) // disable the warning "identifier was truncated to '255' characters in the debug information" generated by STL

/////////////////////////////////////////////////////////////////////////////
// CRmWorkerObj

//---------------------------------------------------------------------
// CRmWorkerObj::CRmWorkerObj
// Constructor for a resource manager file operation object.  This
// constructor performs three major fuctions:
//(1) Create and initialize log object.
//(2) Establish a connection with DTC transaction manager.
//(3) Get an RM interface object.
//

CRmWorkerObj::CRmWorkerObj () :  CActionList()
{

m_pLog = NULL;
m_pIResMgr = NULL;
SetState (TX_UNINITIALIZED);
m_punkDTC = NULL;
m_pEnlist = NULL;
m_restartList = NULL;
m_hFinishedTxEvent = CreateEvent(NULL, FALSE, TRUE, NULL);

} // CRmWorkerObj:CRmWorkerObj()

CRmWorkerObj::~CRmWorkerObj(void)
{
// Set the state to uninitialized
SetState (TX_UNINITIALIZED);

if (m_pLog) // not initialized for Recover
{
ULONG lLogRefCount = m_pLog -> Release();
m_pLog = NULL;
}
// Disconnect from DTC
if (0 != m_punkDTC)
{
m_punkDTC->Release ();
m_punkDTC = 0;
} // if

// Release IResourceManager interface
if (0 != m_pIResMgr)
{
m_pIResMgr->Release ();
m_pIResMgr = 0;
} // if

if (m_pEnlist)
{
m_pEnlist -> Release();
m_pEnlist = 0;
}

if (m_hFinishedTxEvent)
{
CloseHandle(m_hFinishedTxEvent);
m_hFinishedTxEvent = NULL;
}

FILECPY*cpy;
BOOLfRc = FALSE;
FILECPYLIST::iterator i;
 for (i=m_lstFiles.begin();i != m_lstFiles.end(); ++i)
 {

cpy = *i;
delete cpy;

 }

m_lstFiles.clear();

} // CRmWorkerObj::~CRmWorkerObj

STDMETHODIMP CRmWorkerObj::AddFile(BSTR sSource, BSTR sDestination)
{

FILECPY * cpy = new FILECPY;
cpy -> sSource = sSource;
cpy -> sDest = sDestination;
m_lstFiles.insert(m_lstFiles.end(), cpy);

return S_OK;
}
//
//interate through our list of files and copy each of them.
//if any copy fails, return an error, which will abort the tx
//
STDMETHODIMP CRmWorkerObj::CopyListTx()
{

FILECPYLIST::iterator item;
HRESULT hr;
FILECPY * cpy;
// Scan the list and recover incomplete transactions -- there should only
// be one in the single threaded case.
for (item=m_lstFiles.begin();item != m_lstFiles.end(); ++item)
{

cpy = *item;
hr = FileCopyTx(cpy -> sSource.m_str, cpy -> sDest.m_str, FALSE);
if (FAILED(hr))
return hr;

}
return S_OK;
}

STDMETHODIMP CRmWorkerObj::Init(IUnknown * pUnkLog, IUnknown * pUnkDtcResMgr, IUnknown *pUnkDtc, BSTR sRecDir)
{

//-----------------------------------------------------------------
// Set the state of the resource manager as unitialized
//

SetState (TX_UNINITIALIZED);

m_sRecoverDir = sRecDir;

// make sure I have a log...
_ASSERTE(pUnkLog);
HRESULT hr;
hr = pUnkLog -> QueryInterface(IID_ISimpleLog, (void **)&m_pLog);
if (FAILED(hr))
{
_ASSERTE(0);
return E_UNEXPECTED;
}

hr = pUnkDtcResMgr -> QueryInterface(IID_IResourceManager, (void**)&m_pIResMgr);
_ASSERTE(hr == S_OK);

m_punkDTC = pUnkDtc;
m_punkDTC -> AddRef();

// Change the state of the resource manager to reflect that it is
// initialized.

SetState (TX_INITIALIZING);

return hr;
}

STDMETHODIMP CRmWorkerObj::GetTransactionId(BSTR * guidTx)
{

LPOLESTR sTemp[64];
HRESULT hr = StringFromCLSID(m_guidTx, sTemp);
if (FAILED(hr))
{
_ASSERTE(0);
return hr;
}

*guidTx = SysAllocString(sTemp[0]);
return hr;

} // CRmWorkerObj::GetTransactionId()

//---------------------------------------------------------------------
// CRmWorkerObj class implementation:
//---------------------------------------------------------------------
STDMETHODIMP CRmWorkerObj::FileCopyTx(BSTR sFromFile, BSTR sToFile, BOOL bFailIfExists)
{
TXSTATEeTxState;
HANDLEhFind;
WIN32_FIND_DATAfindData;
BOOLfReturn;

// Get the current state of the transaction.
 GetState (&eTxState);

// Check if TM is down
if (TX_TMDOWN == eTxState)
{
SetStatus (RM_STATUS_TM_DOWN);
_ASSERTE(0);
return E_FAIL;
} // if

// Ensure that a transaction is enlisted
if (TX_ENLISTED != eTxState)
{
SetStatus (RM_STATUS_INVALID_STATE);
return E_FAIL;

} // if

// Source of copy does not exist -- copy is erroneous.
hFind = FindFirstFile (sFromFile, &findData);
if (INVALID_HANDLE_VALUE == hFind)
{
FindClose (hFind);
SetStatus (RM_STATUS_FILE_NOT_FOUND);
return E_FAIL;
} // if
FindClose (hFind);

// a copy over itself is a NOOP
if (lstrcmpi(sFromFile, sToFile) == 0)
{

return S_OK;

}

hFind = FindFirstFile (sToFile, &findData);
if (INVALID_HANDLE_VALUE != hFind)
{

// The target file exists.  if bFailIfExists, return an error
// TODO: add GetLastError() information
if (bFailIfExists)
{
FindClose (hFind);
return E_FAIL;
}

// do a transactional delete of the target file so that
// the file destruction is recorded in the log, then copy.
if (S_OK != FileDeleteTx (sToFile))
{
// File delete not successful -- therefore copy is not
// successful.

return E_FAIL;
} // if
} // if
FindClose (hFind);

CAction * pAction = new CAction(LOG_RM_PRIVATE, RM_COPY,  sFromFile, sToFile);
fReturn = PushAction (pAction);
_ASSERTE (TRUE == fReturn);
if (FALSE == fReturn)
{
SetStatus (RM_STATUS_IN_MEMORY_LIST);
return E_FAIL;
} // if

HRESULT hr = WriteLog (m_guidTx, pAction);

if (hr != S_OK)
{
fReturn = RemoveAction ();
_ASSERTE (TRUE == fReturn);

SetStatus (RM_STATUS_LOG_ERROR);
return E_FAIL;
} // if

fReturn = CopyFile (sFromFile, sToFile, bFailIfExists);
if (TRUE != fReturn)
{
fReturn = RemoveAction ();
_ASSERTE (TRUE == fReturn);

HRESULT hr = WriteLog  (m_guidTx, LOG_RM_PRIVATE, RM_FORGET, 0, 0);
_ASSERTE (hr == S_OK);

// Undo the delete action
hr = UndoDelete ((TCHAR *)pAction -> m_sPath1, (TCHAR *)pAction -> m_sPath2);
_ASSERTE (S_OK == hr);

// Log the UNDO as a forget action.
hr = WriteLog  (m_guidTx,  LOG_RM_PRIVATE, RM_FORGET, 0, 0);
_ASSERTE (hr == S_OK);

// Remove the delete log action from the in-memory action list.
fReturn = RemoveAction ();
_ASSERTE (TRUE == fReturn);

SetStatus (RM_STATUS_COPY_FAILURE);
return E_FAIL;

} // if

// copy succeeded -- open the file to create a lock on the file

pAction -> LockFile(sToFile);
_ASSERTE(pAction -> m_hLock);

return(S_OK);

} // CRmWorkerObj::FileCopy

//
//built up fromt he FileCopyTx and FileDeleteTx primitives
//
STDMETHODIMP CRmWorkerObj::FileMoveTxt(BSTR sSource, BSTR sDestination)
{

HRESULT hr;

// copy the file
hr = FileCopyTx(sSource, sDestination, FALSE);
if (FAILED(hr))
return hr;

// delete the file
return FileDeleteTx(sSource);

} // CRmWorkerObj::FileMoveTx

STDMETHODIMP CRmWorkerObj::FileDeleteTx(BSTR sFileName)
{

BOOLfReturn;
TXSTATE eTxState;
TCHAR*pszFileName = 0;
TCHARsTmpFile[MAX_PATH];

// Get transaction state.
GetState (&eTxState);

// Check if TM is down return with TM Down status.
if (TX_TMDOWN == eTxState)
{
SetStatus (RM_STATUS_TM_DOWN);
return E_FAIL;
} // if

// Ensure that a transaction is enlisted
if (TX_ENLISTED != eTxState)
{
SetStatus (RM_STATUS_INVALID_STATE);
return E_FAIL;
} // if

memset(sTmpFile, NULL, MAX_PATH);
GetTempFile(sTmpFile);
_ASSERTE (0 != sTmpFile);

CAction * pAction = new CAction(LOG_RM_PRIVATE,RM_DELETE,  sFileName, sTmpFile);
fReturn = PushAction (pAction);
_ASSERTE (TRUE == fReturn);

HRESULT hr = WriteLog (m_guidTx, LOG_RM_PRIVATE, RM_DELETE, sFileName, sTmpFile);
if (FAILED(hr))
{
_ASSERTE (0);
fReturn = RemoveAction ();
_ASSERTE (TRUE == fReturn);
SetStatus (RM_STATUS_LOG_ERROR);
return E_FAIL;
} // if

fReturn = MoveFileEx (sFileName,
  sTmpFile,
  MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED);
if (TRUE != fReturn)
{
fReturn = RemoveAction ();
_ASSERTE (TRUE == fReturn);

hr = WriteLog (m_guidTx, LOG_RM_PRIVATE,RM_FORGET, 0, 0);
_ASSERTE(hr == S_OK);
SetStatus (RM_STATUS_MOVE_FAILURE);
return E_FAIL;
} // if

#ifdef _DEBUG // testing PeekAction
pAction  = NULL;
PeekAction(&pAction);
_ASSERTE(pAction);
_ASSERTE(pAction -> m_eFileOp == RM_DELETE);
#endif
pAction -> LockFile(sTmpFile);

_ASSERTE(pAction -> m_hLock);

return S_OK;

} // CRmWorkerObj::FileDelete

STDMETHODIMP CRmWorkerObj::FileRenameTx(BSTR sFromFile, BSTR sToFile)
{
TXSTATEeTxState;
HANDLEhFind;
WIN32_FIND_DATA findData;
BOOLfReturn;

// Get transaction state.
GetState (&eTxState);

// Check if TM is down return with TM Down status.
if (TX_TMDOWN == eTxState)
{
SetStatus (RM_STATUS_TM_DOWN);
return E_FAIL;
} // if

// Ensure that a transaction is enlisted
if (TX_ENLISTED != eTxState)
{
SetStatus (RM_STATUS_INVALID_STATE);
return E_FAIL;
} // if

// Check if target path exists.
hFind = FindFirstFile (sToFile, &findData);
if (INVALID_HANDLE_VALUE != hFind)
{
FindClose (hFind);
SetStatus (RM_STATUS_OVERWRITE_EXISTING);
return E_FAIL;
} // if
FindClose (hFind);

CAction * pAction = new CAction(LOG_RM_PRIVATE, RM_RENAME, sFromFile, sToFile);
fReturn = PushAction (pAction);
_ASSERTE (TRUE == fReturn);

HRESULT hr = WriteLog  (m_guidTx, pAction);
if (hr != S_OK)
{
fReturn = RemoveAction ();
_ASSERTE (TRUE == fReturn);

SetStatus (RM_STATUS_LOG_ERROR);
return E_FAIL;
} // if

fReturn = MoveFileEx( sFromFile,
  sToFile,
  MOVEFILE_COPY_ALLOWED );
if (TRUE != fReturn)
{
fReturn = RemoveAction ();
_ASSERTE (TRUE == fReturn);

HRESULT hr = WriteLog  (m_guidTx, RM_FORGET, RM_NONE, 0, 0);
_ASSERTE (hr == S_OK);

SetStatus (RM_STATUS_RENAME_FAILURE);
return E_FAIL;
} // if

return S_OK;

} // CRmWorkerObj::FileRename

//
//called by the RM when it is time to enlist in a Tx
//
//this can be called with multiple tx's in the same instance
//
STDMETHODIMP  CRmWorkerObj::ExportTx(ULONG cbTranCookie, BYTE *rgbTranCookie)
{
HRESULThRc;
ITransactionImport*pTxImport;

// if this worker is in the process of aborting, wait until it is done
WaitTxDone();

TXSTATEeTxState;
GetState (&eTxState);

// Ensure that the resource manager is initializing
// it is possible that everything was OK up to this point
// but we blocked waiting for an Commit or an Abort to complete
// and then the TM went down
if (TX_INITIALIZING != eTxState)
{
SetStatus (RM_STATUS_INVALID_STATE);
return E_FAIL;
} // if

// Create and initialize transaction completion event handle.

// Get a pointer to the ITransactionImport interface.
hRc = m_punkDTC->QueryInterface (IID_ITransactionImport,
     (LPVOID *) &pTxImport);
if (S_OK != hRc)
{
pTxImport = 0;

SetStatus (RM_STATUS_INTERFACE);
return E_FAIL;
} // if

m_pITx = 0;
hRc = pTxImport->Import (cbTranCookie,
 rgbTranCookie,
 (GUID *) &IID_ITransaction,
 (void **) &m_pITx);
_ASSERTE (0 != m_pITx);
if (S_OK != hRc)
{
pTxImport->Release ();
pTxImport = 0;

SetStatus (RM_STATUS_IMPORT_ERROR);
return E_FAIL;
} // if

SafeRelease(pTxImport);
// The resource manager is initialized for the transaction.
SetState (TX_INITIALIZED);
return EnlistTx();

} // CRmWorkerObj::ExportTx

HRESULT CRmWorkerObj::EnlistTx()
{
HRESULThRc;
BOOLfRc;

// Ensure that the resource manager is initialized before enlisting
// transaction.
TXSTATEeTxState;
GetState (&eTxState);
if (TX_INITIALIZED != eTxState)
{
SetStatus (RM_STATUS_INVALID_STATE);
return E_FAIL;
} // if

// Create a transaction resource async object for hadling 2PC
// requests.

ITransactionResourceAsync * ptxRmAsync = NULL;
GetUnknown() -> QueryInterface(IID_ITransactionResourceAsync, (void**)&ptxRmAsync);// does an AddRef that DTC should Release()
_ASSERTE (0 != ptxRmAsync);

//
// Enlist on the transaction
//
//if m_pEnlist != NULL, that means that we are reinlisting on a new
//tx -- so we are going to get rid of this m_pEnlist and get a new one
//
if (m_pEnlist)
{
m_pEnlist -> Release();
m_pEnlist = 0;
}

hRc = m_pIResMgr->Enlist (m_pITx,
  ptxRmAsync,
  &m_guidTx,
  &m_isoLevel,
  &m_pEnlist);

if (S_OK != hRc)
{
_ASSERTE(0);
// Transaction enlistment failed -- release resource async object
//and return enlistment failure

ptxRmAsync->Release();
ptxRmAsync = NULL;
SetStatus (RM_STATUS_ENLIST_ERROR);
return E_FAIL;
} // if

// Update log and in-memory list
CAction * pAction = new CAction(LOG_RM_XACT_BEGIN, RM_NONE, 0, 0);
fRc = PushAction(pAction);
_ASSERTE (TRUE == fRc);

hRc = WriteLog  (m_guidTx, pAction);
_ASSERTE (hRc == S_OK);

// Set current state to reflect enlistment
SetState (TX_ENLISTED);

// release our Async interface -- DTC did an AddRef for us
SafeRelease(ptxRmAsync);
return S_OK;

} // CRmWorkerObj::EnlistTx

HRESULT CRmWorkerObj::UndoCopy(TCHAR *sFromFile, TCHAR *sToFile)
{
BOOLfReturn;

fReturn = DeleteFile( sToFile );
if (TRUE != fReturn)
{
SetStatus (RM_STATUS_UNDO_ERROR);
return E_FAIL;
} // if

return S_OK;

} // CRmWorkerObj::UndoCopy

HRESULT CRmWorkerObj::UndoMove(TCHAR *sFromFile, TCHAR *sToFile)
{
BOOLfReturn;

fReturn = MoveFileEx( sToFile,
  sFromFile,
  MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED);
if (TRUE != fReturn)
{
SetStatus (RM_STATUS_UNDO_ERROR);
return E_FAIL;
} // if

return S_OK;
} // CRmWorkerObj::UndoMove

//
//move the file frome the temporary location back to its original,
//  and then delete the temp file
//
HRESULT CRmWorkerObj::UndoDelete(TCHAR *sFileName, TCHAR *sTmpFile)
{

BOOLfReturn;

fReturn = MoveFileEx (sTmpFile,
  sFileName,
  MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED);
if (TRUE != fReturn)
{
_ASSERTE(0);
SetStatus (RM_STATUS_UNDO_ERROR);
return E_FAIL;
} // if

return S_OK;

} // CRmWorkerObj::UndoDelete()

HRESULT CRmWorkerObj::UndoRename(TCHAR *sFromFile, TCHAR *sToFile)
{
BOOLfReturn;

fReturn = MoveFileEx (sToFile,
  sFromFile,
  MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED);
if (TRUE != fReturn)
{
SetStatus (RM_STATUS_UNDO_ERROR);
return E_FAIL;
} // if

return S_OK;
} // CRmWorkerObj::UndoRename()

HRESULT CRmWorkerObj::RollbackTx (void)
{
BOOLfReturn = FALSE;
HRESULThr;
CAction*pAction = NULL;

ACTIONLIST::iterator item;

 // scan through the list and undo each operation
// for (item = m_plistAction -> begin();item != m_plistAction -> end(); ++item)
for (item = begin();item != end(); ++item)
{

pAction = *item;
pAction -> ReleaseLock(); // we need to close the destination so that we can delete it if we have to
switch (pAction -> m_eFileOp)
{ // For each RM command take undo action.
case RM_COPY:
hr = UndoCopy (pAction -> m_sPath1, pAction -> m_sPath2);
break;
case RM_DELETE:
hr = UndoDelete (pAction -> m_sPath1, pAction -> m_sPath2);
break;
case RM_RENAME:
hr = UndoRename (pAction -> m_sPath1, pAction -> m_sPath2);
break;
case RM_MOVE:
hr = UndoMove (pAction -> m_sPath1, pAction -> m_sPath2);
break;
case RM_NONE:  // it is a TX oriented item (such as BEING_TX, and no rollback needed
hr = S_OK;
break;
default:
_ASSERTE (FALSE);
SetStatus (RM_STATUS_LOG_ERROR);
hr = E_FAIL;
} // switch

if (S_OK == hr)
{
HRESULT hr = WriteLog  (m_guidTx, LOG_RM_PRIVATE, RM_UNDO, 0, 0);
if (hr != S_OK)
{
_ASSERTE(0);
SetStatus (RM_STATUS_LOG_ERROR);
hr = E_FAIL;
break;
} // if

} // if
else
{
OutputDebugString(_TEXT("RMWORKER: RollbackTx failed!\n"));
_ASSERTE(0);
}

} // for

if (S_OK == hr)
{
// Write undo end record.
HRESULT hr = WriteLog  (m_guidTx, LOG_RM_PRIVATE, RM_UNDO_END, 0, 0);
if (hr != S_OK)
{
_ASSERTE(0);
SetStatus (RM_STATUS_LOG_ERROR);
hr = E_FAIL;
} // if
} // if

return hr;

} // CRmWorkerObj::RollbackTx()

//
//
//
//
RECOVERYRC CRmWorkerObj::RecoverTx (IStream * pStream)
{

RECOVERYRCeRecoverRc;
RECOVERY_RECORD*precTxn;
CAction*pAction;
XACTSTATxactOutcome;
ULONGulTimeout = XACTCONST_TIMEOUTINFINITE;
ULONGcbPrepareInfo;
BYTE*pbPrepareInfo;
HRESULThRc;
HRESULTret;
HRESULThr;

eRecoverRc = RecMapTransactions (pStream);
if (REC_S_SUCCESS != eRecoverRc)
{
return eRecoverRc;
} // if

// Create a list iterator
 RECOVERY_LIST::iterator crecIter;
 // Scan the list and recover incomplete transactions -- there should only
// be one in the single threaded case.
 for (crecIter=m_restartList -> begin();crecIter != m_restartList -> end(); ++crecIter)
 {
precTxn = *crecIter;
switch (precTxn->eRecState)
{
case REC_STATE_ACTIVE:
m_guidTx = precTxn->guidTxn;
ResetActions (precTxn->actionList);
ret = AbortTx ();
if (REC_S_SUCCESS != ret)
{
_ASSERTE(0);
return REC_E_UNDO_INCOMPLETE;
} // if
break;
case REC_STATE_ABORTING:
m_guidTx = precTxn->guidTxn;
ResetActions (precTxn->actionList);
ret = RollbackTx();
if (REC_S_SUCCESS != ret)
{
_ASSERTE(0);
return REC_E_UNDO_INCOMPLETE;
} // if
CleanupAction ();
break;
case REC_STATE_DORMANT:
hr = WriteLog  (precTxn->guidTxn, LOG_RM_XACT_END, RM_NONE, 0, 0);
if (hr != S_OK)
{
_ASSERTE(0);
return REC_E_LOG_FAILURE;
} // if
break;
case REC_STATE_INDOUBT:
// Must re-enlist with MS-DTC to determine the transaction outcome.
precTxn->actionList->PeekAction (&pAction);
if (LOG_RM_XACT_PREPARE != pAction -> m_eRmCommand)
{
_ASSERTE(0);
return REC_E_INDOUBT_INVALID;
} // if

// Reenlist with MS DTC to determine the outcome of the
// in-doubt transaction.
swscanf ((TCHAR *)pAction -> m_sPath1, L"%ld", &cbPrepareInfo);
pbPrepareInfo = (BYTE *) pAction -> m_sPath2;
hRc = m_pIResMgr->Reenlist (pbPrepareInfo,
cbPrepareInfo,
ulTimeout,
&xactOutcome);

if (S_OK != hRc)
{
return REC_E_REENLIST;
} // if

// Reenlistment is successful -- act on transaction outcome.
m_guidTx = precTxn->guidTxn;
precTxn->actionList->RemoveAction ();
// delete the prepare action.
ResetActions (precTxn->actionList);
if (XACTSTAT_ABORTED == xactOutcome)
{
ret = AbortTx ();
}
else if (XACTSTAT_COMMITTED == xactOutcome)
{
ret = CommitTx ();
}
else if (XACT_E_CONNECTION_DOWN == hRc)
{
// Lost connection with MS DTC return TM down exception.
return REC_E_TM_DOWN;
}
else if (E_UNEXPECTED == hRc)
{
// Something unexpected happened -- presume abort.
ret = AbortTx ();
}
else
{// Problems during re-enlistment.
return REC_E_REENLIST;
} // if

// Check outcome of transaction commit and abort.
if (REC_S_SUCCESS != ret)
{
return REC_E_OUTCOMEACTION;
} // if

// Everything is OK.
hr =  WriteLog  (precTxn->guidTxn, LOG_RM_XACT_END, RM_NONE, 0, 0);
if (hr != S_OK)
{
_ASSERTE(0);
return REC_E_LOG_FAILURE;
} // if

// End the re-enlistment on the resource manager.
hRc = m_pIResMgr->ReenlistmentComplete ();
_ASSERTE (S_OK == hRc);
break;
case REC_STATE_COMPLETED:
break;
default:
_ASSERTE (FALSE);
return REC_E_STATE_UNKNOWN;
} // switch

} // for -- iteration through recover node list.

return REC_S_SUCCESS;

} // CRmWorkerObj::RecoverTx

//
//scan the stream and build an action list of actions to recover from
//the stream contains the GUID that is the TxID followed by n LOG_RM_ACTIONs
//
RECOVERYRC CRmWorkerObj::RecMapTransactions (IStream * pStream)
{

BOOLfRc = TRUE;
RECOVERY_RECORD *precTxn;

m_restartList = new RECOVERY_LIST;
_ASSERTE (0 != m_restartList);
if (0 == m_restartList)
{
return REC_E_NO_MEMORY;
} // if

CAction * pAction;
ULONG ulActionSize;
ULONG cbBytesRead;
HRESULT hr;
GUID guidTx;
LARGE_INTEGER liOff;
ULARGE_INTEGER liNewPosition;

LOG_RM_COMMAND eOp;

BYTE * pByte = NULL;

// get the Tx for this stream
LISet32(liOff,  0);
// seek to the end of the stream, and back up sizeof(LOG_RM_ACTION)
hr = pStream -> Seek(liOff, STREAM_SEEK_CUR, &liNewPosition);

hr = pStream -> Read(&guidTx, sizeof(guidTx), &cbBytesRead);

// read in the actions
do
{

hr = pStream -> Read(&eOp, sizeof(eOp), &cbBytesRead);
hr = pStream -> Read(&ulActionSize, sizeof(ULONG), &cbBytesRead);
_ASSERTE(hr == S_OK);
if (hr == S_OK && cbBytesRead)
{
pAction = new CAction; // we'll delete it if we do NOT push the action
_ASSERTE(sizeof(ulActionSize) == cbBytesRead);
pByte = new BYTE[ulActionSize];
hr = pStream -> Read(pByte, ulActionSize, &cbBytesRead);
_ASSERTE(cbBytesRead == ulActionSize);
hr = pAction -> Load(pByte, ulActionSize);
_ASSERTE(hr == S_OK);
delete [] pByte;
pByte = NULL;

precTxn = 0;
precTxn = RecGetTransaction (guidTx);

// Transaction should be on list.
if (0 == precTxn && LOG_RM_XACT_BEGIN != pAction -> m_eRmCommand)
{
// Ghost transaction on transaction list.
return REC_E_ORPHANED_TRANSACTION;
} // if

// Update action list for the transaction.
switch (pAction -> m_eFileOp)
{
case RM_COPY:
case RM_DELETE:
case RM_RENAME:
case RM_MOVE:
// Transaction must be active.
if (REC_STATE_ACTIVE != precTxn->eRecState)
{
return REC_E_INVALID_STATE;
} // if
fRc = precTxn->actionList->PushAction (pAction);
_ASSERTE (TRUE == fRc);
break;
case RM_UNDO:
// Transaction must be aborting for an undo to occur.
if (REC_STATE_ABORTING != precTxn->eRecState)
{
// Transaction end sequence error.
return REC_E_UNDO_SEQUENCE;
} // if
fRc = precTxn->actionList->RemoveAction (); // removes LAST action
delete pAction;
_ASSERTE (TRUE == fRc);
break;
case RM_FORGET:
// Transaction must be active for a forget to occur.
if (REC_STATE_ACTIVE != precTxn->eRecState)
{
// Transaction end sequence error.
return REC_E_FORGET_SEQUENCE;
} // if
fRc = precTxn->actionList->RemoveAction ();
delete pAction;
_ASSERTE (TRUE == fRc);
break;
case RM_UNDO_BEGIN:
if (REC_STATE_INDOUBT == precTxn->eRecState)
{
// Transaction not in-doubt remove the prepared record
// from the action list.
fRc = precTxn->actionList->RemoveAction ();
_ASSERTE (TRUE == fRc);
}
else if (REC_STATE_ACTIVE != precTxn->eRecState)
{
// Transaction end sequence error.
return REC_E_BEGINUNDO_SEQUENCE;
} // if
precTxn->eRecState = REC_STATE_ABORTING;
delete pAction;
break;
case RM_UNDO_END:
if (REC_STATE_ABORTING != precTxn->eRecState)
{
// Transaction transaction end undo error.
return REC_E_ENDUNDO_SEQUENCE;
} // if
precTxn->eRecState = REC_STATE_DORMANT;
delete pAction;
break;
    case RM_NONE:
switch (pAction -> m_eRmCommand)
{
case LOG_RM_XACT_PREPARE:
// Transaction must be active.
if (REC_STATE_ACTIVE != precTxn->eRecState)
{
// Prepare sequence error.
return REC_E_PREPARE_SEQUENCE;
} // if
precTxn->eRecState = REC_STATE_INDOUBT;
fRc = precTxn->actionList->PushAction (pAction);
_ASSERTE (TRUE == fRc);
break;
case LOG_RM_XACT_BEGIN:
if (0 != precTxn)
{
// Starting a duplicate transaction.
return REC_E_TRANSACTION_DUPLICATED;
} // if

precTxn = new RECOVERY_RECORD;
_ASSERTE (0 != precTxn);
if (0 == precTxn)
{
// Space not allocated, return with a no memory error.
return REC_E_NO_MEMORY;
} //  if

precTxn->guidTxn      = guidTx;
precTxn->eRecState  = REC_STATE_ACTIVE;
precTxn->actionList   = new CActionList;
_ASSERTE (0 != precTxn->actionList);
if (0 == precTxn->actionList)
{
// Space not allocated for action list; return with no memory error.
return REC_E_NO_MEMORY;
} // if
fRc = precTxn->actionList->PushAction (pAction);

_ASSERTE (TRUE == fRc);
m_restartList->insert (m_restartList->begin(), precTxn);

break;
case LOG_RM_XACT_END:
// Transaction must be dormant -- either committed or aborted.
if (REC_STATE_DORMANT != precTxn->eRecState)
{
// Transaction end sequence error.
return REC_E_END_TRANSACTION_SEQUENCE;
} // if
precTxn->eRecState = REC_STATE_COMPLETED;
RecDiscardTransaction (guidTx);
delete pAction;
break;
case LOG_RM_XACT_COMMIT:
if (REC_STATE_INDOUBT == precTxn->eRecState)
{
// Transaction not in doubt -- remove prepare
// action from the action list.
fRc = precTxn->actionList->RemoveAction ();
_ASSERTE (TRUE == fRc);
}
else if (REC_STATE_ACTIVE != precTxn->eRecState)
{
// Transaction commit sequence error.
return REC_E_COMMIT_SEQUENCE;
} // if
precTxn->eRecState = REC_STATE_DORMANT;
delete pAction;
break;
default:
return REC_E_INVALID_LOG_RECORD;
} // inner switch
break;
default:
_ASSERTE(0); // invalid log
delete pAction;
return REC_E_INVALID_LOG_RECORD;
} // outer switch
}
} while ((hr == S_OK) && (cbBytesRead)); // while

// Transaction remapping successful return to recovery manager.
return REC_S_SUCCESS;

} // CRmWorkerObj::RecMapTransactions()

RECOVERY_RECORD *CRmWorkerObj::RecGetTransaction (GUID& guidTxn)
{
RECOVERY_LIST::iterator crecIter;
RECOVERY_RECORD*precTxn;
LONGuuidStatus;

for (crecIter=m_restartList->begin();crecIter != m_restartList -> end(); ++crecIter)
{
precTxn = *crecIter;
if (UuidEqual (&precTxn->guidTxn, &guidTxn, &uuidStatus))
{
return precTxn;
} // if
} // for

precTxn = 0;

return precTxn;
} // CRmWorkerObj::RecGetTransaction ()

void CRmWorkerObj::RecDiscardTransaction (GUID& guidTxn)
{

RECOVERY_LIST::iterator crecIter;
RECOVERY_RECORD*precTxn;
LONGuuidStatus;

for (crecIter=m_restartList -> begin();crecIter != m_restartList -> end(); ++crecIter)
{
if (UuidEqual (&(*crecIter)->guidTxn, &guidTxn, &uuidStatus))
{
precTxn = *crecIter;
m_restartList ->remove(precTxn);
delete precTxn->actionList;
delete precTxn;
return;
} // if
} // for
} // CRmWorkerObj::RecDiscardTransaction()

void CRmWorkerObj::RecoverCleanup (void)
{
RECOVERY_RECORD*precTxn = 0;
if (!m_restartList)
return;
while (TRUE != m_restartList->empty())
{
// get the first element
precTxn = m_restartList->front();
// delete it
delete precTxn;
// remove it from our list
m_restartList->pop_front();
precTxn = 0;
} // while
} // CRmWorkerObj::RecoverCleanup

void CRmWorkerObj::SetStatus (RMSTATUS ermStatus)
{
m_eStatus = ermStatus;
} // CRmWorkerObj::SetStatus()

RMSTATUS CRmWorkerObj::GetStatus (void)
{
return m_eStatus;
} // CRmWorkerObj::GetStatus()

//---------------------------------------------------------------------
// End of $Workfile: RmWorkerObj.cpp $
//---------------------------------------------------------------------

STDMETHODIMP CRmWorkerObj::PrepareRequest(BOOL fRetaining,
DWORD grfRM,
BOOL fWantMoniker,
BOOL fSinglePhase)
{
HRESULThr = S_OK;

TXSTATEeTxState;
GetState (&eTxState);

if (TX_ENLISTED != eTxState)
{
_ASSERTE(0);
// Set the state of the transaction as invalid.
SetState (TX_INVALID_STATE);

// Transaction is in an invalid state -- return error
hr = m_pEnlist->PrepareRequestDone (E_UNEXPECTED,NULL,NULL);
    _ASSERTE (S_OK == hr);
if (S_OK != hr)
{
// TO DO: Print error message.
return E_FAIL;
} // if

return E_UNEXPECTED;
} // if

//-----------------------------------------------------------------
// Transaction is in its correct state -- indicate preparing
//
SetState (TX_PREPARING);

//-----------------------------------------------------------------
// SINGLE PHASE handling.
// Check if the MS DTC coordinator specifies single-phase
// optimization.  If so, attempt to commit the transaction and
// respond with S_OK.
//
// If the commit fails, rollback the transaction and respond with
// E_FAIL.
//
if (TRUE == fSinglePhase)
{
hr = CommitTx ();
if (S_OK != hr)
{
hr = AbortTx();
_ASSERTE (S_OK == hr);

hr = m_pEnlist->PrepareRequestDone (E_FAIL,NULL,NULL);
_ASSERTE (S_OK == hr);

CleanupTx ();
SetState (TX_INVALID_STATE);

return E_FAIL;
} // if -- Commit unsuccessful

CleanupTx ();

_ASSERTE(m_pEnlist);
hr = m_pEnlist->PrepareRequestDone (XACT_S_SINGLEPHASE,
NULL,
NULL);
_ASSERTE (S_OK == hr);
if (S_OK != hr)
{
// Print error message
return E_FAIL;
} // if
return S_OK;

} // if -- Single Phase

//-----------------------------------------------------------------
// NORMAL PREPARE REQUEST:
//

hr = PrepareTx ();
if (S_OK != hr)
{
hr = AbortTx ();
_ASSERTE (S_OK == hr);

SetState (TX_ABORTED);
hr = m_pEnlist->PrepareRequestDone (E_FAIL, NULL, NULL);
_ASSERTE (S_OK == hr);

CleanupTx ();
return E_FAIL;
} // if -- Prepare failed.

// we MUST set the state to PREPARED before calling PrepareRequestDone
// otherwise you could get the second phase notification before the
// PrepareRequestDone() returns

SetState (TX_PREPARED);

// Prepare was successful -- return success
hr = m_pEnlist->PrepareRequestDone (S_OK, NULL, NULL);
_ASSERTE (S_OK == hr);

return S_OK;

} // CRmWorkerObj::PrepareRequest()

STDMETHODIMP CRmWorkerObj::CommitRequest(DWORD grfRM,
   XACTUOW *pNewUOW)
{
HRESULThr = S_OK;
TXSTATEeTxState;
GetState (&eTxState);

    if (TX_PREPARED != eTxState)
{
_ASSERTE(0);
hr = E_UNEXPECTED;
} // if

SetState (TX_COMMITTING);

hr = CommitTx ();
_ASSERTE (S_OK == hr);
if (S_OK != hr)
{
// TO DO: report heuristic damage - print error message
hr = E_FAIL;
} // if

SetState (TX_COMMITTED);
CleanupTx ();
_ASSERTE(m_pEnlist);
hr = m_pEnlist->CommitRequestDone (S_OK);
_ASSERTE (S_OK == hr);
//
//indicate we are done with the transaction
//
SetEvent(m_hFinishedTxEvent);

return S_OK;
} // CRmWorkerObj::CommitRequest()

STDMETHODIMP CRmWorkerObj::AbortRequest(BOID *pboidReason,
  BOOL fRetaining,
  XACTUOW *pNewUOW)
{
HRESULThr   = S_OK;
TXSTATEeTxState;
GetState (&eTxState);
    if (TX_PREPARED != eTxState &&
TX_ENLISTED != eTxState )
{
// TO DO: print warning message
_ASSERTE (0);
hr = E_UNEXPECTED;
} // if

SetState (TX_ABORTING);

hr = AbortTx ();
_ASSERTE (S_OK == hr);
if (S_OK != hr)
{
// TO DO: print error message
//printf("Abort failure!\n");
hr = E_FAIL;
} // if

SetState (TX_ABORTED);

CleanupTx ();

hr = m_pEnlist->AbortRequestDone (S_OK);
_ASSERTE (S_OK == hr);
//
//indicate we are done with the transaction
//
SetEvent(m_hFinishedTxEvent);
return  S_OK;

} // CRmWorkerObj::AbortRequest()

//
//in this case we ignore this TMDown (which is the ITransactionEnlistmentAsync::TmDown())
//because there is exactly 1 and only 1 Tx per worker object, and we will be notified of
//this event in IResourceManagerSink::TmDown().  Normally, you do cleanup for this enlistment
//in this method
//
STDMETHODIMP CRmWorkerObj::TMDown(void)
{
SetState (TX_TMDOWN);
SafeRelease(m_punkDTC);
SafeRelease(m_pIResMgr);
SafeRelease(m_pITx);
SafeRelease(m_pLog);
SafeRelease(m_pEnlist);
SetEvent(m_hFinishedTxEvent); // if anybody is blocked on the event, they will return with the TX_TMDOWN state
return S_OK;
} // CRmWorkerObj::TMDown

//
//pLog:a pointer to the log
//
//
#define ENUM_GRANULARITY 10
STDMETHODIMP CRmWorkerObj::Recover(IStorage * pStorage)
{

_ASSERTE(pStorage);
IEnumSTATSTG * pEnum = NULL;
IStream * pStream = NULL;
HRESULT hr;
hr = pStorage -> EnumElements(NULL, NULL, NULL, &pEnum);
_ASSERTE(hr == S_OK);
STATSTG statstg[ENUM_GRANULARITY];
ULONG lFetched = 0;
ULONG i = 0;
RECOVERYRC eRecRc = REC_S_SUCCESS;

do
{
hr = pEnum -> Next(ENUM_GRANULARITY, statstg, &lFetched);
for (i=0; i<lFetched; i++)
{

// get the stream
pStream = NULL;
hr =  pStorage -> OpenStream(
statstg[i].pwcsName, //Pointer to string containing name of stream to open
NULL, //Reserved
STGM_DIRECT | STGM_SHARE_EXCLUSIVE | STGM_READWRITE, //Access mode for the new stream
NULL, //Reserved
&pStream//Indirect pointer to opened stream object
   );

_ASSERTE(hr == S_OK);
eRecRc = RecoverTx (pStream);
if (REC_S_SUCCESS != eRecRc)
{
_ASSERTE(0);
RecoverCleanup ();

SetStatus (RM_STATUS_RECOVERY_FAILURE);
return E_FAIL;
} // if

pStream -> Release();

} // end for

}while (lFetched == ENUM_GRANULARITY);

pEnum -> Release();
pStorage -> Release();
RecoverCleanup ();
return S_OK;

}
//
//"dust off" the Worker -- make sure that it is ready to go
//the next call we expect is an ExportTx()
//
STDMETHODIMP CRmWorkerObj::Reset()
{

#if 0
SafeRelease(m_pEnlist);
SafeRelease(m_pITx);
SetState (TX_INITIALIZING);
#endif
return S_OK;

}

HRESULT CRmWorkerObj::GetTempFile(TCHAR * pFileName)
{
GUID guid;
HRESULT hr;

hr = CoCreateGuid(&guid);
_ASSERTE(hr == S_OK);
OLECHAR szGUID[40];
int nRet = StringFromGUID2(guid, szGUID, 39);
    _ASSERTE(nRet > 0);

lstrcat(pFileName, m_sRecoverDir.m_str);
lstrcat(pFileName, szGUID);
return S_OK;
}

//
//we write the LOG_RM_ACTION to the log
//
HRESULT CRmWorkerObj::WriteLog(GUID guidTx, LOG_RM_COMMAND eOp, FILE_OPERATION fOp, BSTR sFromFile, BSTR sToFile)
{

CAction action(eOp, fOp, sFromFile, sToFile);
return WriteLog(guidTx, &action);

}

HRESULT CRmWorkerObj::WriteLog(GUID txID, CAction * pAction)
{
BYTE * pByte = NULL;
ULONG cbSize = 0;
HRESULT hr = pAction -> Save(&pByte, &cbSize);
if (FAILED(hr))
return hr;

hr = m_pLog -> WriteLog(txID, pAction -> m_eRmCommand, pByte, cbSize);
delete [] pByte;
return hr;

}


RMWORKEROBJ.H

/******************************************************************************\
********************************************************************************
* Filename: RmWorkerObj.h
*
* Description: Declaration of the CRmWorkerObj
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#include "resource.h"       // main symbols

typedef struct tagFILECPY
{
 CComBSTR sSource;
 CComBSTR sDest;
} FILECPY, FAR * LPFILECPY;

//extern ULONG RM_STATUS_BASE = 0x81000000;

typedef struct tagRECOVERY_RECORD
{
GUIDguidTxn;// Transaction identifier.
RECOVERY_STATEeRecState;// Recovery state of transaction
CActionList*actionList;// Action list for
// transaction.
} RECOVERY_RECORD;

typedef list <FILECPY *> FILECPYLIST;
typedef list <RECOVERY_RECORD *> RECOVERY_LIST;
/////////////////////////////////////////////////////////////////////////////
// CRmWorkerObj
class CRmWorkerObj :
public CActionList,
public CComObjectRoot,
public IRmWorkerPriv,
public ITransactionResourceAsync, // DTC Callback
public CComCoClass<CRmWorkerObj, &CLSID_CRmWorkerObj>,
public IRmWorker
{

private:

ISimpleLog * m_pLog;
HANDLE m_hFinishedTxEvent;
CComBSTR m_sRecoverDir;
CComBSTR m_sAppName;

public:
HRESULT WriteLog(GUID guidTx, LOG_RM_COMMAND eOp, FILE_OPERATION fOp, BSTR sFromFile, BSTR sToFile);
HRESULT WriteLog(GUID txID, CAction * pAction);
HRESULT GetTempFile(TCHAR * pFileName);
ITransactionEnlistmentAsync*m_pEnlist;
CRmWorkerObj();
~CRmWorkerObj();

DECLARE_REGISTRY_RESOURCEID(IDR_RmWorkerObj)

BEGIN_COM_MAP(CRmWorkerObj)
COM_INTERFACE_ENTRY(IRmWorker)
COM_INTERFACE_ENTRY(IRmWorkerPriv)
COM_INTERFACE_ENTRY(ITransactionResourceAsync)
END_COM_MAP()

FILECPYLIST m_lstFiles;

STDMETHOD(WaitTxDone)(void);

// IRmWorker

// misc functions
STDMETHOD(Init)(IUnknown * pUnkLog, IUnknown * pUnkDtcResMgr, IUnknown *pUnkDtc, BSTR sRecDir);
STDMETHOD(Recover)(IStorage * pStorage);
STDMETHOD(Reset)();

// file manipulation
STDMETHOD(FileCopyTx)(BSTR sFromFile, BSTR sToFile, BOOL bFailIfExists);
STDMETHOD(FileRenameTx)(BSTR sFromFile, BSTR sToFile);
STDMETHOD(FileMoveTxt)(BSTR sSource, BSTR sDestination);
STDMETHOD(FileDeleteTx)(BSTR sFileName);

// list manipulation
STDMETHOD(AddFile)(BSTR sSource, BSTR sDestination);
STDMETHOD(CopyListTx)();

// DTC related functions
STDMETHOD(ExportTx)(ULONG cbTranCookie, BYTE *rgbTranCookie);
STDMETHOD(GetDTCUnknown)(IUnknown ** pUnk);

// Tx related functions
STDMETHOD(GetTransactionId)(BSTR * guidTx);

public:
// IRmWorkerPriv -- used by CITransactionResourceAsync
STDMETHOD(PrepareTx) (void);
STDMETHOD(CommitTx) (void);
STDMETHOD(AbortTx) (void);
STDMETHOD(GetState)(TXSTATE *eState);
STDMETHOD(SetState)(TXSTATE eState);
STDMETHOD(CleanupTx) (void);

// ITransactionResourceAsync
STDMETHODIMPPrepareRequest(BOOL fRetaining,
   DWORD grfRM,
   BOOL fWantMoniker,
   BOOL fSinglePhase);
STDMETHODIMPCommitRequest(DWORD grfRM, XACTUOW *pNewUOW);
STDMETHODIMPAbortRequest(BOID *pboidReason,
 BOOL fRetaining,
 XACTUOW *pNewUOW);
STDMETHODIMPTMDown(void);

private:

HRESULTEnlistTx(void);
void SetStatus (RMSTATUS ermStatus);
RMSTATUS GetStatus (void);

HRESULTUndoCopy(TCHAR *sFromFile, TCHAR *sToFile);
HRESULTUndoMove(TCHAR *sFromFile, TCHAR *sToFile);
HRESULTUndoDelete(TCHAR *sFileName, TCHAR *sTmpFile);
HRESULTUndoRename(TCHAR *sFromFile, TCHAR *sToFile);

// Private methods on the resource manager object.
RECOVERYRCRecoverTx (IStream * pStream);// Restart recovery.
RECOVERYRCRecMapTransactions (IStream * pStream);// Scan log a remap transactions.
RECOVERY_RECORD *RecGetTransaction (GUID& guidTxn);
// Get recovery record for transaction.
voidRecDiscardTransaction (GUID& guidTxn);
// Discard transaction recovery record.
voidRecoverCleanup (void);// Cleanup recovery data structures.
HRESULTRollbackTx (void);// Undo the current transaction.

// Private member variables
RMSTATUSm_eStatus;// Status of the last unsuccessful operation.
TXSTATEm_eTxState;// Transaction state.
IUnknown*m_punkDTC;// Pointer to MS DTC IUnknown.
ITransaction*m_pITx;// ITransaction interface pointer.
IResourceManager*m_pIResMgr;
GUIDm_guidTx;// Global unique id for transaction.
LONGm_isoLevel;// Isolation level of transaction.
RECOVERY_LIST *m_restartList;// List of transactions to be restarted.

public:
};


STDAFX.H

/******************************************************************************\
********************************************************************************
* Filename: StdAfx.h
*
* Description:

stdafx.h : include file for standard system include files,
or project specific include files that are used frequently,
but are changed infrequently

*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#define _WIN32_WINNT 0x0400
#define _ATL_FREE_THREADED

#include <atlbase.h>
//You may derive a class from CComModule and use it if you want to override
//something, but do not change the name of _Module
extern CComModule _Module;
#include <atlcom.h>
#define SafeRelease(pUnk) {if (pUnk){pUnk -> Release();pUnk = NULL; }}


RESOURCE.H

/******************************************************************************\
********************************************************************************
* Filename: Resource.h
*
* Description:
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by SimpleLog.rc
//
#define IDS_PROJNAME100
#define IDR_SimpleLogObj101

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        201
#define _APS_NEXT_COMMAND_VALUE         32768
#define _APS_NEXT_CONTROL_VALUE         201
#define _APS_NEXT_SYMED_VALUE           102
#endif
#endif


SIMPLELOG.CPP

/******************************************************************************\
********************************************************************************
* Filename: SimpleLog.cpp
*
* Description: Implementation of DLL Exports
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#include "stdafx.h"
#include "resource.h"
#include "SimpleLog.h"

#include "SimpleLog_i.c"
#include "SimpleLogObj.h"

#ifdef _MERGE_PROXYSTUB
extern "C" HINSTANCE hProxyDll;
#endif

CComModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_CoSimpleLog, CSimpleLogObj)
END_OBJECT_MAP()

/////////////////////////////////////////////////////////////////////////////
// DLL Entry Point

extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
lpReserved;
#ifdef _MERGE_PROXYSTUB
if (!PrxDllMain(hInstance, dwReason, lpReserved))
return FALSE;
#endif
if (dwReason == DLL_PROCESS_ATTACH)
{
_Module.Init(ObjectMap, hInstance);
DisableThreadLibraryCalls(hInstance);
}
else if (dwReason == DLL_PROCESS_DETACH)
_Module.Term();
return TRUE;    // ok
}

/////////////////////////////////////////////////////////////////////////////
// Used to determine whether the DLL can be unloaded by OLE

STDAPI DllCanUnloadNow(void)
{
#ifdef _MERGE_PROXYSTUB
if (PrxDllCanUnloadNow() != S_OK)
return S_FALSE;
#endif
return (_Module.GetLockCount()==0) ? S_OK : S_FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// Returns a class factory to create an object of the requested type

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
#ifdef _MERGE_PROXYSTUB
if (PrxDllGetClassObject(rclsid, riid, ppv) == S_OK)
return S_OK;
#endif
return _Module.GetClassObject(rclsid, riid, ppv);
}

/////////////////////////////////////////////////////////////////////////////
// DllRegisterServer - Adds entries to the system registry

STDAPI DllRegisterServer(void)
{
#ifdef _MERGE_PROXYSTUB
HRESULT hRes = PrxDllRegisterServer();
if (FAILED(hRes))
return hRes;
#endif
// registers object, typelib and all interfaces in typelib
return _Module.RegisterServer(TRUE);
}

/////////////////////////////////////////////////////////////////////////////
// DllUnregisterServer - Removes entries from the system registry

STDAPI DllUnregisterServer(void)
{
#ifdef _MERGE_PROXYSTUB
PrxDllUnregisterServer();
#endif
_Module.UnregisterServer();
#if _WIN32_WINNT >= 0x0400
UnRegisterTypeLib(LIBID_SIMPLELOGLib, 1, 0, LOCALE_USER_DEFAULT, SYS_WIN32);
#endif
return S_OK;
}


SIMPLELOGOBJ.CPP

/******************************************************************************\
********************************************************************************
* Filename: SimpleLogObj.cpp
*
* Description:

*implementation of a structured storage based log.
*each transaction is written to its own stream, where the name of the stream
*is the GUID of the transaction
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
#include "stdafx.h"
#include "SimpleLog.h"
#include "SimpleLogObj.h"

//
// the name of a stream in OLE Structured storage is limited to 37
// chars -- we want to name streams with GUIDs (as strings), which
// are 39 chars -- to get around this, we will 64bit encode the name
// of the GUID
//
// These characters are the legal digits, in order, that are
// used in Base64 encoding
//
const WCHAR rgwchBase64[] =
    L"ABCDEFGHIJKLMNOPQ"
    L"RSTUVWXYZabcdefgh"
    L"ijklmnopqrstuvwxy"
    L"z0123456789+*";

STDMETHODIMP CSimpleLogObj::InterfaceSupportsErrorInfo(REFIID riid)
{
static const IID* arr[] =
{
&IID_ISimpleLog,
};
for (int i=0;i<sizeof(arr)/sizeof(arr[0]);i++)
{
if (InlineIsEqualGUID(*arr[i],riid))
return S_OK;
}
return S_FALSE;
}
CSimpleLogObj::CSimpleLogObj()
{
m_pStorage = NULL;
m_bInRecovery = FALSE;

} // CSimpleLogObj:CSimpleLogObj()

CSimpleLogObj::~CSimpleLogObj(void)
{
// Realocate space for the recovery directory path name.
if (NULL != m_pStorage)
{
m_pStorage -> Release();
}

} // CSimpleLogObj::~CSimpleLogObj

//
//Init()
//
//Pass in the filename of the log (pszLogFileName)
//read the registery to find the log dir, and open
//the file using structured storage.  if the file doesn't
//exit, create it
//
HRESULT CSimpleLogObj::Init(BSTR pszLogFileName)
{
BOOLfNew = FALSE;

HRESULT hr = StgOpenStorage(
pszLogFileName, //Pointer to the path of the file containing storage object
NULL, //Pointer to a previous opening of a root storage object
STGM_DIRECT | STGM_SHARE_EXCLUSIVE | STGM_READWRITE, //Access mode for the object
NULL, //String name block specifying elements to be excluded
0, //Reserved; must be zero
&m_pStorage//Indirect pointer to the storage object
   );

if (FAILED(hr))
{

if (hr == STG_E_FILENOTFOUND)
{
hr =  StgCreateDocfile(pszLogFileName, //Pointer to path of compound file to create
STGM_TRANSACTED | STGM_SHARE_EXCLUSIVE | STGM_READWRITE ,//Access mode for the new storage object
NULL,
&m_pStorage//Indirect pointer to the new storage object
);
}
if (FAILED(hr))
{
::MessageBox(NULL, _T("Failed to open log.  Make sure you don't have another instance running."), _T("CopyFileRm"), MB_ICONSTOP);
return hr;
}

}

_ASSERTE(m_pStorage);
return S_OK;
}
//
//Write the specified info to the log.
//
//1. we Create (or Open) a stream with the name representing the GUID
//2. we write the Command into it
//3. we close the stream
//
STDMETHODIMP CSimpleLogObj::WriteLog(GUID guidTx,LOG_RM_COMMAND eOp, BYTE * pByte, long cbBytes)
{
//if (m_bInRecovery)
//return E_FAIL;

HRESULT hr;
IStream * pStream;
hr = GetStream(&pStream, guidTx, TRUE);

if (FAILED(hr))
{
_ASSERTE(0);
return hr;
}
ULONG cbWritten;
// seek to the end of the stream;
LARGE_INTEGER liOff;
ULARGE_INTEGER liNewPosition;
LISet32(liOff,  0);
hr = pStream -> Seek(liOff, STREAM_SEEK_END, &liNewPosition);

if (eOp != LOG_RM_PRIVATE)
{
// write the command
hr = pStream -> Write((void *)&eOp, sizeof(eOp), &cbWritten);
_ASSERTE(hr==S_OK);
}

// if the structure exists, and it is not the end of the xact
// don't write the rest of the info if it
if (cbBytes && eOp != LOG_RM_XACT_END)
{
// write the struct size
hr = pStream -> Write((void *)&cbBytes, sizeof(cbBytes), &cbWritten);
_ASSERTE(hr==S_OK);

// write the byte stream
hr = pStream -> Write((void *)pByte, cbBytes, &cbWritten);

_ASSERTE((long)cbWritten == cbBytes);
}

// commit the write
hr = pStream -> Commit(STGC_DEFAULT);

// commit the storage
hr = m_pStorage -> Commit(STGC_DEFAULT);
_ASSERTE(hr == S_OK);

// release (close) the stream;
pStream -> Release();

return S_OK;
}
//
//gets a storage that has all of the streams that need to be recovered
//
//the stream starts with the GUID that is the TxId, followed by RM_BEGIN_TX
//and then the rest of the information
//
#define ENUM_GRANULARITY 10
STDMETHODIMP CSimpleLogObj::RecoveryInfo(IStorage **ppStorage)
{
m_bInRecovery = TRUE;
_ASSERTE(m_pStorage);
IEnumSTATSTG * pEnum = NULL;

*ppStorage = NULL;

HRESULT hr;
ILockBytes *pLkbyte = NULL;
hr = CreateILockBytesOnHGlobal(NULL,TRUE, &pLkbyte);

if (FAILED(hr))
{
_ASSERTE(0);
return hr;
}

hr = StgCreateDocfileOnILockBytes(pLkbyte, STGM_SHARE_EXCLUSIVE|STGM_CREATE|STGM_READWRITE, NULL, ppStorage);
if (FAILED(hr))
{
pLkbyte -> Release();
_ASSERTE(0);
return hr;
}

//enumate through the existing streams, looking for streams that DO NOT end in LOG_RM_XACT_END
// these are the ones that need to be recovered...

hr = m_pStorage -> EnumElements(NULL, NULL, NULL, &pEnum);
_ASSERTE(hr == S_OK);
STATSTG statstg[ENUM_GRANULARITY];
ULONG lFetched = 0;
ULONG i = 0;
IStream * pStream = NULL;
LARGE_INTEGER liOff;
ULARGE_INTEGER liNewPosition;
long lSizeCommand = sizeof(LOG_RM_COMMAND);
ULARGE_INTEGER MaxInt;
MaxInt.HighPart = (ULONG)-1;
MaxInt.LowPart = (ULONG)-1;
LOG_RM_COMMAND eOp;
ULONG cbBytesRead;
do
{
hr = pEnum -> Next(ENUM_GRANULARITY, statstg, &lFetched);
for (i=0; i<lFetched; i++)
{

// get the stream
pStream = NULL;
hr =  m_pStorage -> OpenStream(
statstg[i].pwcsName, //Pointer to string containing name of stream to open
NULL, //Reserved
STGM_DIRECT | STGM_SHARE_EXCLUSIVE | STGM_READWRITE, //Access mode for the new stream
NULL, //Reserved
&pStream//Indirect pointer to opened stream object
   );

_ASSERTE(hr == S_OK);
LISet32(liOff,  -1 * lSizeCommand);
// seek to the end of the stream, and back up sizeof(LOG_RM_ACTION)
hr = pStream -> Seek(liOff, STREAM_SEEK_END, &liNewPosition);
// read the value
hr = pStream -> Read(&eOp, sizeof(eOp), &cbBytesRead);
_ASSERTE(cbBytesRead == sizeof(eOp));

if (eOp != LOG_RM_XACT_END)
{

IStream * pNewStream;
pNewStream = NULL;

hr = (*ppStorage) -> CreateStream(
statstg[i].pwcsName,
STGM_CREATE|STGM_DIRECT | STGM_SHARE_EXCLUSIVE | STGM_READWRITE,
NULL, NULL,
&pNewStream
   );
// seek back to the beginning
LISet32(liOff,  0);
pStream -> Seek(liOff, STREAM_SEEK_SET, &liNewPosition);
_ASSERTE(liNewPosition.LowPart == 0);
// tx need to be recovered, copy the stream from the file to the storage
hr = pStream -> CopyTo(pNewStream,
MaxInt,
NULL, NULL);

pStream -> Release();
pNewStream -> Release();
}
else
{
pStream -> Release();
//
//the transaction has been completed, we can remove it
//  from our log...
//
hr = m_pStorage -> DestroyElement(statstg[i].pwcsName);
_ASSERTE(hr == S_OK);
}

} // end for

}while (lFetched == ENUM_GRANULARITY);

pEnum -> Release();
m_bInRecovery = FALSE;
return S_OK;
}

//
//64 bit encodes the guidTx and opens the stream with that name.
//if the stream does not exist, it creates the stream (if bCreate is TRUE)
//and then writes the GUID to the stream
//
HRESULT CSimpleLogObj::GetStream(IStream **pStream, GUID &guidTx,  BOOL bCreate)
{

HRESULT hr;
CComBSTR sStreamName;
hr = ToBase64((void *)&guidTx, sizeof(GUID), &sStreamName.m_str);
_ASSERTE(hr == S_OK);
_ASSERTE(sStreamName.m_str);

hr =  m_pStorage -> OpenStream(
sStreamName.m_str, //Pointer to string containing name of stream to open
NULL, //Reserved
STGM_DIRECT | STGM_SHARE_EXCLUSIVE | STGM_READWRITE, //Access mode for the new stream
NULL, //Reserved
pStream//Indirect pointer to opened stream object
   );

if (FAILED(hr))
{
if (hr == STG_E_FILENOTFOUND && bCreate)
{
// create the stream
hr = m_pStorage -> CreateStream(
sStreamName.m_str,
STGM_DIRECT | STGM_SHARE_EXCLUSIVE | STGM_READWRITE,
NULL, NULL,
pStream
   );
if (FAILED(hr))
{
_ASSERTE(0);
return hr;
}

DWORD cbWritten;
hr = (*pStream) -> Write(&guidTx, sizeof(guidTx), &cbWritten);
}
}

return hr;
}
//
//Streams in IStorage are limited to <39 chars, which is how long
//a GUID as a string is...so we are going to encode them in base64
//
HRESULT CSimpleLogObj::ToBase64(LPVOID pv, ULONG cbStart, BSTR* pbstr)
//
// Encode and return the bytes in base 64
//
    {
    ULONG cb = cbStart;
    *pbstr = NULL;
    HRESULT hr = S_OK;
    int cchPerLine = 72;                        // conservative, must be mult of 4 for us
    int cbPerLine  = cchPerLine / 4 * 3;
    int cbSafe     = cb + 3;                    // allow for padding
    int cLine      = cbSafe / cbPerLine + 2;    // conservative
    int cchNeeded  = cLine * (cchPerLine + 2 /*CRLF*/) + 1 /*NULL*/;
    int cbNeeded   = cchNeeded * sizeof(WCHAR);
    LPWSTR wsz = (LPWSTR)CoTaskMemAlloc(cbNeeded);
    if (wsz)
        {
        BYTE*  pb   = (BYTE*)pv;
        WCHAR* pch  = wsz;
        int cchLine = 0;
        //
        // Main encoding loop
        //
        while (cb >= 3)
            {
            BYTE b0 =                     ((pb[0]>>2) & 0x3F);
            BYTE b1 = ((pb[0]&0x03)<<4) | ((pb[1]>>4) & 0x0F);
            BYTE b2 = ((pb[1]&0x0F)<<2) | ((pb[2]>>6) & 0x03);
            BYTE b3 = ((pb[2]&0x3F));

            *pch++ = rgwchBase64[b0];
            *pch++ = rgwchBase64[b1];
            *pch++ = rgwchBase64[b2];
            *pch++ = rgwchBase64[b3];

            pb += 3;
            cb -= 3;

            // put in line breaks
            cchLine += 4;
            if (cchLine >= cchPerLine)
                {
                *pch++ = L'\r';
                *pch++ = L'\n';
                cchLine = 0;
                }
            }
        //
        // Account for gunk at the end
        //
        *pch++ = L'\r';     // easier than keeping track
        *pch++ = L'\n';
        if (cb==0)
            {
            // nothing to do
            }
        else if (cb==1)
            {
            BYTE b0 =                     ((pb[0]>>2) & 0x3F);
            BYTE b1 = ((pb[0]&0x03)<<4) | 0;
            *pch++ = rgwchBase64[b0];
            *pch++ = rgwchBase64[b1];
            *pch++ = L'=';
            *pch++ = L'=';
            }
        else if (cb==2)
            {
            BYTE b0 =                     ((pb[0]>>2) & 0x3F);
            BYTE b1 = ((pb[0]&0x03)<<4) | ((pb[1]>>4) & 0x0F);
            BYTE b2 = ((pb[1]&0x0F)<<2) | 0;
            *pch++ = rgwchBase64[b0];
            *pch++ = rgwchBase64[b1];
            *pch++ = rgwchBase64[b2];
            *pch++ = L'=';
            }

        //
        // NULL terminate the string
        //
        *pch = NULL;

        //
        // Allocate our final output
        //
        *pbstr = SysAllocString(wsz);
        if (*pbstr==NULL)
            hr = E_OUTOFMEMORY;

        CoTaskMemFree(wsz);
        }
    else
        hr = E_OUTOFMEMORY;

    #ifdef _DEBUG
    if (hr==S_OK)
        {
        BLOB b;
        FromBase64(*pbstr, &b);
        _ASSERTE(b.cbSize == cbStart);
        _ASSERTE(memcmp(b.pBlobData, pv, cbStart) == 0);
        CoTaskMemFree(b.pBlobData);
        }
    #endif

    return hr;
    }

HRESULT CSimpleLogObj::FromBase64(BSTR bstr, BLOB* pblob)
//
// Decode and return the Base64 encoded bytes
//
    {
    HRESULT hr = S_OK;
    int cbNeeded = lstrlenW(bstr);      // an upper bound
    BYTE* rgb = (BYTE*)CoTaskMemAlloc(cbNeeded);
    if (rgb)
        {
        BYTE  mpwchb[256];
        BYTE  bBad = (BYTE)-1;
        int   i;
        //
        // Initialize our decoding array
        //
        memset(&mpwchb[0], bBad, 256);
        for (i = 0; i < 64; i++)
            {
            WCHAR wch = rgwchBase64[i];
            mpwchb[wch] = i;
            }

        //
        // Loop over the entire input buffer
        //
        ULONG bCurrent = 0;         // what we're in the process of filling up
        int  cbitFilled = 0;        // how many bits in it we've filled
        BYTE* pb = rgb;             // current destination (not filled)
        //
        for (WCHAR* pwch=bstr; *pwch; pwch++)
            {
            WCHAR wch = *pwch;
            //
            // Ignore white space
            //
            if (wch==0x0A || wch==0x0D || wch==0x20 || wch==0x09)
                continue;
            //
            // Have we reached the end?
            //
            if (wch==L'=')
                break;
            //
            // How much is this character worth?
            //
            BYTE bDigit = mpwchb[wch];
            if (bDigit==bBad)
                {
                hr = E_INVALIDARG;
                break;
                }
            //
            // Add in its contribution
            //
            bCurrent <<= 6;
            bCurrent |= bDigit;
            cbitFilled += 6;
            //
            // If we've got enough, output a byte
            //
            if (cbitFilled >= 8)
                {
                ULONG b = (bCurrent >> (cbitFilled-8));     // get's top eight valid bits
                *pb++ = (BYTE)(b&0xFF);                     // store the byte away
                cbitFilled -= 8;
                }
            }

        if (hr==S_OK)
            {
            pblob->pBlobData = rgb;
            pblob->cbSize = pb-rgb;
            }
        else
            {
            CoTaskMemFree(rgb);
            pblob->pBlobData = NULL;
            }
        }
    else
        hr = E_OUTOFMEMORY;
    return hr;
}

STDMETHODIMP CSimpleLogObj::WriteStreamToLog(GUID guidTx, LOG_RM_COMMAND eOp, IStream * pStreamIn)
{
HRESULT hr;
IStream * pStream;
hr = GetStream(&pStream, guidTx, TRUE);

if (FAILED(hr))
{
_ASSERTE(0);
return hr;
}
ULONG cbWritten;
// seek to the end of the stream;
LARGE_INTEGER liOff;
ULARGE_INTEGER liNewPosition;
LISet32(liOff,  0);
hr = pStream -> Seek(liOff, STREAM_SEEK_END, &liNewPosition);

if (eOp != LOG_RM_PRIVATE)
{
// write the command
hr = pStream -> Write((void *)&eOp, sizeof(eOp), &cbWritten);
_ASSERTE(hr==S_OK);
}

if (pStreamIn)
{
// get info from the stream

STATSTG stat;
ZeroMemory(&stat, sizeof(stat));

hr = pStreamIn -> Stat(&stat, STATFLAG_NONAME);
_ASSERTE(hr==S_OK);
_ASSERTE(stat.cbSize.HighPart == 0);
ULONG cbBytes = stat.cbSize.LowPart;

// if the structure exists, and it is not the end of the xact
// don't write the rest of the info if it
if (cbBytes && eOp != LOG_RM_XACT_END)
{
// write the struct size
hr = pStream -> Write((void *)&cbBytes, sizeof(cbBytes), &cbWritten);
_ASSERTE(hr==S_OK);

// write the byte stream
hr = pStreamIn -> CopyTo(pStream, stat.cbSize, NULL, NULL);
_ASSERTE(hr == S_OK);
LARGE_INTEGER li;
li.LowPart = -1 * stat.cbSize.LowPart;
li.HighPart = 0; // limits our size...
hr = pStreamIn -> Seek(li, STREAM_SEEK_CUR, NULL);
_ASSERTE(hr == S_OK);
}
}

// commit the write
hr = pStream -> Commit(STGC_DEFAULT);

// commit the storage
hr = m_pStorage -> Commit(STGC_DEFAULT);
_ASSERTE(hr == S_OK);

// release (close) the stream;
pStream -> Release();

return S_OK;

}

STDMETHODIMP CSimpleLogObj::RemoveTxStream(GUID guidTx)
{
HRESULT hr;
CComBSTR sStreamName;
hr = ToBase64((void *)&guidTx, sizeof(GUID), &sStreamName.m_str);
_ASSERTE(hr == S_OK);
_ASSERTE(sStreamName.m_str);
hr = m_pStorage -> DestroyElement(sStreamName);

if (FAILED(hr))
{
_ASSERTE(0);
}

return hr;
}

STDMETHODIMP CSimpleLogObj::WriteVariantToLog(GUID guidTx, VARIANT * pVariant)
{
HRESULT hr;
IStream * pStream;
hr = GetStream(&pStream, guidTx, TRUE);

if (FAILED(hr))
{
_ASSERTE(0);
return hr;
}
//
//Seek to the end
//

LARGE_INTEGER liOff;
ULARGE_INTEGER liNewPosition;
liOff.QuadPart = 0;
hr = pStream -> Seek(liOff, STREAM_SEEK_END, &liNewPosition);
_ASSERTE(hr==S_OK);
CComVariant var(*pVariant);
hr = var.WriteToStream(pStream);
if (SUCCEEDED(hr))
{
// commit the write
hr = pStream -> Commit(STGC_DEFAULT);

// commit the storage
hr = m_pStorage -> Commit(STGC_DEFAULT);
_ASSERTE(hr == S_OK);
}

pStream -> Release();
return hr;
}

STDMETHODIMP CSimpleLogObj::GetStream(GUID guidTx, IStream * * ppStream)
{
HRESULT hr;
hr = GetStream(ppStream, guidTx, FALSE);
return hr;
}


SIMPLELOGOBJ.H

/******************************************************************************\
********************************************************************************
* Filename: SimpleLogObj.h
*
* Description: Declaration of the CSimpleLogObj
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#include "resource.h"       // main symbols
/////////////////////////////////////////////////////////////////////////////
// CSimpleLogObj
class CSimpleLogObj :
public CComObjectRoot,
public ISimpleLog,
public CComCoClass<CSimpleLogObj, &CLSID_CoSimpleLog>,
public ISupportErrorInfo
{
private:

IStorage * m_pStorage;

TCHAR*m_pszRecDir;// Pointer to path name of recorvery directory.

CComAutoCriticalSection m_CritSec;

HRESULT ToBase64(LPVOID pv, ULONG cbStart, BSTR* pbstr);
HRESULT FromBase64(BSTR bstr, BLOB* pblob);

BOOL m_bInRecovery;

public:
HRESULT GetStream(IStream **pStream, GUID &guidTx,  BOOL bCreate);
CSimpleLogObj();
~CSimpleLogObj();

DECLARE_REGISTRY_RESOURCEID(IDR_SimpleLogObj)

BEGIN_COM_MAP(CSimpleLogObj)
COM_INTERFACE_ENTRY(ISimpleLog)
COM_INTERFACE_ENTRY(ISupportErrorInfo)
END_COM_MAP()

// ISupportsErrorInfo
STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);

// ISimpleLog
STDMETHOD(WriteLog)(GUID guidTx, LOG_RM_COMMAND eOp, BYTE * pByte, long cbBytes);
STDMETHOD(Init)(BSTR  pszLogFileName);
STDMETHOD(RecoveryInfo)(IStorage **ppStorage);

public:
STDMETHOD(GetStream)(GUID guidTx, /*[out]*/ IStream **ppStream);
STDMETHOD(WriteVariantToLog)(GUID m_guidTx, VARIANT *pVariant);
STDMETHOD(RemoveTxStream)(GUID guidTx);
STDMETHOD(WriteStreamToLog)(GUID GuidTx, LOG_RM_COMMAND eOp, IStream * pStream);
};


STDAFX.CPP

/******************************************************************************\
********************************************************************************
* Filename: StdAfx.cpp
*
* Description:
*
* stdafx.cpp : source file that includes just the standard includes
* stdafx.pch will be the pre-compiled header
* stdafx.obj will contain the pre-compiled type information
*
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#include "stdafx.h"

#ifdef _ATL_STATIC_REGISTRY
#include <statreg.h>
#include <statreg.cpp>
#endif

#include <atlimpl.cpp>


STDAFX.H

/******************************************************************************\
********************************************************************************
* Filename: StdAfx.h
*
* Description:
*
*stdafx.h : include file for standard system include files,
*or project specific include files that are used frequently,
*but are changed infrequently
*
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#define STRICT 1
#define _WIN32_WINNT 0x0400
#define _ATL_FREE_THREADED

#include <atlbase.h>
//You may derive a class from CComModule and use it if you want to override
//something, but do not change the name of _Module
extern CComModule _Module;
#include <atlcom.h>


DISPIMPL.CPP

/******************************************************************************\
********************************************************************************
* Filename: dispimpl.cpp
*
* Description: main implementation of IDispenserDriver
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
#include "stdafx.h"
#include "TxFileDisp.h"
#include "copyfilerm.h"
#include "mtxdm.h"
#include "txcoord.h"
#include "FileRmPxy.h"
#include "txdtc.h"
#include "xolehlp.h"

//
//called by RDM (Resource Dispenser Manager) when it is time to create
//a new resource, in this case a connection to the CopyFileRM
//
STDMETHODIMP CFileRmPxy::CreateResource
(
/*[in]*/  const RESTYPID ResTypId,
/*[out]*/ RESID* pResId,
/*[out]*/ TIMEINSECS* pSecsFreeBeforeDestroy
)
{

ATLTRACE(_T("Creating TxFile Resource\n"));
CComBSTR sAppName = L"TxFileDispenser";
long lHandle;
HRESULT hr;

IFileRm * pRm = GetFileRmPointer();
if (!pRm)
{
_ASSERTE(0);
return E_UNEXPECTED;
}
hr = pRm -> Connect(sAppName.m_str, (long *)&lHandle);
if (FAILED(hr))
{
_ASSERTE(0);
SafeRelease(pRm);
return hr;
}

*pResId = (RESID)lHandle;
//
//set a 60 second time out
//
*pSecsFreeBeforeDestroy = 60;

//
//save the connection in our set of connections
//
m_set.insert(lHandle);

SafeRelease(pRm);
return S_OK;
}
//
//called by RDM (Resource Dispenser Manager) when it is time
//to rate an already created resource
//
STDMETHODIMP CFileRmPxy::RateResource
(
/*[in]*/  const RESTYPID ResTypId,
/*[in]*/  const RESID ResId,
/*[in]*/  const BOOL fRequiresTransactionEnlistment,
/*[out]*/ RESOURCERATING* pRating
)
{
ATLTRACE(_T("Rating resource\n"));

// if it is already enlisted on the right transaction, use it
if (fRequiresTransactionEnlistment == FALSE)
{
*pRating = 100;
}
else
{
// not enlisted
*pRating = 50;
}
return S_OK;
}

//
//called by RDM (Resource Dispenser Manager) when it is time
//when it is time to enlist a connection on a resource
//
STDMETHODIMP CFileRmPxy::EnlistResource
(
/*[in]*/  const RESID ResId,
/*[in]*/  const TRANSID TransId
)
{

//
//there are 3 kinds of resources in the pool:
//1. Enlisted resources
//2. Un-enlisted resources
//3. previously enlisted resources
//
//type (3) resources require Re-enlistment or "Unenlistment" to be used
//again.  Dispman asks us to Unenlist a resource by passing in a NULL
//TransId
//
//The CopyFileRm ALWAYS resets the state for the Worker (ie the resource being
//dispensed)to an "uninitialized" state -- and any work on a uninitialized worker object
//immediately fails.  so "Unenlistement" is a noop for us (SQL Server, btw, allows work to be done
//  with or without a tx, so their implementation is different)
//

if (!TransId)
return S_OK;

ATLTRACE(_T("Enlisting Resource\n"));
ITransaction * pTransaction = (ITransaction *)TransId;

IFileRm * pRm = GetFileRmPointer();
if (!pRm)
return E_UNEXPECTED;

HRESULT hr;
ITransactionExport *pExport;

pExport = m_mapExport[ResId];
if (pExport == NULL)
{

hr = GetExportObject(ResId, pTransaction, &pExport);
if (FAILED(hr))
{
SafeRelease(pRm);
return hr;
}

m_mapExport[ResId] = pExport;
}

ULONGcbTransactionCookie = 0;
BYTE*rgbTransactionCookie;
ULONGcbUsed;

// Get transaction cookie
hr = pExport->Export (pTransaction, &cbTransactionCookie);
_ASSERTE(hr == S_OK);

rgbTransactionCookie = (BYTE *) CoTaskMemAlloc (cbTransactionCookie);
_ASSERTE (0 != rgbTransactionCookie);
if (0 == rgbTransactionCookie)
{
SafeRelease(pRm);
_ASSERTE(0);
return E_FAIL;
} // if

hr = pExport->GetTransactionCookie (pTransaction,
cbTransactionCookie,
rgbTransactionCookie,
&cbUsed);

//
//ExportTx will also enlist in the Tx
//
//
//NOTE:  There is a functionaly requirement that makes this call block
//   if the connection is not completely finished committing or aborting
//the previous transaction
//
hr = pRm->ExportTx (ResId, cbUsed, rgbTransactionCookie);

CoTaskMemFree (rgbTransactionCookie);
SafeRelease(pRm);
 return hr;

}

//
//called by RDM (Resource Dispenser Manager) when it is time
//to reset the resouce -- ie it is being returned to the inactive
//pool.  you should generally clean up connection specific state
//here.
//
STDMETHODIMP CFileRmPxy::ResetResource
(
/*[in]*/  const RESID ResId
)
{
ATLTRACE(_T("Resetting resource\n"));
HRESULT hr = E_FAIL;
IFileRm * pRm = GetFileRmPointer();
if (!pRm)
return E_UNEXPECTED;
hr = pRm -> ResetConnection((long)ResId);
SafeRelease(pRm);
_ASSERTE(hr == S_OK);

//
//if you return S_FALSE, then the resource
//is destroyed emmediately
//
return hr;
}
//
//called by RDM (Resource Dispenser Manager) when it is time
//to destroy the resource
//
STDMETHODIMP CFileRmPxy::DestroyResource
(
/*[in]*/  const RESID ResId
)
{

ATLTRACE(_T("Destroying a resource\n"));
HRESULT hr = E_FAIL;
IFileRm * pRm = GetFileRmPointer();
if (!pRm)
return E_UNEXPECTED;

hr = pRm -> Disconnect((long )ResId);
ULONG ulRefcount  = pRm -> Release();
pRm = NULL;

ITransactionExport*pExport;
pExport = m_mapExport[ResId];
int nElements = m_mapExport.erase(ResId);
_ASSERTE(nElements == 1);
SafeRelease(pExport);

// remove it from our set
m_set.erase(ResId);
_ASSERTE(hr == S_OK);
return hr;
}

STDMETHODIMP CFileRmPxy::DestroyResourceS
(
/*[in]*/  constSRESID ResId
)
{
return E_NOTIMPL;
}

//
//called by the framework during the IClassFactory::CreateInstance()
//method...this is a good place to initialize because the object is
//fully created, and you can propagate an error code back to the user
//
HRESULT CFileRmPxy::FinalConstruct()
{
HRESULT hr;

hr = GetDispenserManager(&m_pDispMan);
if (SUCCEEDED(hr))
{

// we are running under MTS
IDispenserDriver * pDriver;
hr = GetUnknown()->QueryInterface(IID_IDispenserDriver, (void **)&pDriver);
_ASSERTE(hr == S_OK);
hr = m_pDispMan -> RegisterDispenser(pDriver, L"TxFileDispenser", &m_pHolder);
_ASSERTE(hr == S_OK);
//
//WATCH OUT HERE!!!!!!!!
//
//there is a bug in MTS 1.x that was found post ship where the pDriver isn't
//properly AddRef()'d.  In a future version of MTS, we will have a new
//RegisterDispenser() API (e.g. something like RegisterDispenser2()
//that will do this correctly.  In the meantime, treat
//this as a "moved" reference, and do not release the pDriver pointer!
//(all future versions of RegisterDispenser() will behave this way for
//backward compatiblity)

}
//
// get a connection to the RM -- the RM is a singleton object, so we will always get the same
// pointer back from CoCreateInstance
//
hr = CoCreateInstance(CLSID_CoFileRm, NULL, CLSCTX_LOCAL_SERVER, IID_IFileRm , (void **)&m_pRm);
if (FAILED(hr))
{
_ASSERTE(0);
return hr;
}

//
//NT SP3 has added support for the "Global Interface Table"
//which we use to get a proxy valid on any thread (see the WIN32
//SDK docs for more info)
//
m_dwRmPtrCookie = 0;
hr = CoCreateInstance(CLSID_StdGlobalInterfaceTable,
                                NULL,
                                CLSCTX_INPROC_SERVER,
                                IID_IGlobalInterfaceTable,
                                (void **)&m_pGIT);
_ASSERT(SUCCEEDED(hr));
if (m_pGIT)
    {
        // register our interface and get a cookie
hr = m_pGIT->RegisterInterfaceInGlobal(m_pRm, IID_IFileRm, &m_dwRmPtrCookie);
_ASSERT(SUCCEEDED(hr));
    }

//
//aggregate in the free threaded marshaler
//this is ABSOLUTELY CRITICAL to do in a resource dispenser!
//if we take a thread switch because we switched apartments,
//we will loose the object context and the resource dispenser
//manager will think that there is notransaction,
//and EnlistResource() will not be called.
//
//NOTE:you should mark all resource dispensers as "Both" in the registy
//(see the FileRmPxy.rgs files)
//

hr = CoCreateFreeThreadedMarshaler(GetUnknown(), &m_pFreeThreadedMarshaler);
_ASSERTE(hr == S_OK);

return hr;
}
//
//this is called by the framework when my reference count is going from 1 to 0
//
void CFileRmPxy::FinalRelease()
{

ATLTRACE(_T("\nCFileRmPxy::FinalRelease()\n"));
HRESULT hr;
if (m_pGIT && m_dwRmPtrCookie)
{
hr =m_pGIT->RevokeInterfaceFromGlobal(m_dwRmPtrCookie);
if (FAILED(hr))
{
USES_CONVERSION;
char * szFile = __FILE__;
ATLTRACE(_T("RevokeInterfaceFromGlobal() failed on line %ld of file %s.\n\nError Code %lx"), __LINE__, A2W(szFile) , hr);
}
SafeRelease(m_pGIT);
m_dwRmPtrCookie = 0;
}
m_dwRmPtrCookie = 0;
SafeRelease(m_pDispMan);
SafeRelease(m_pHolder);
SafeRelease(m_pRm);
SafeRelease(m_pFreeThreadedMarshaler);

}
//
//GetFileRmPointer:
//
//use the Global Interface Table to get a proxy valid on any thread
//
IFileRm * CFileRmPxy::GetFileRmPointer()
{

IFileRm * pRm =NULL;
HRESULT hr;
hr = m_pGIT->GetInterfaceFromGlobal(m_dwRmPtrCookie, IID_IFileRm, (void **)&pRm);
_ASSERTE(pRm);
return pRm;

}
//
//helper function to get an ITransactionExport pointer
//
HRESULT CFileRmPxy::GetExportObject(long ResId, ITransaction * pTransaction, ITransactionExport **ppExport)
{
*ppExport = NULL;

ULONGcbWhereabouts = 0;
BYTE*rgbWhereabouts = NULL;
HRESULThr;
ITransactionExportFactory*pTxExpFac;
IGetDispenser*pIDispenser;

IFileRm * pRm = GetFileRmPointer();
if (!pRm)
return E_UNEXPECTED;

//
//GetTmWhereabouts finds the PHYSICAL location of the RM..if you know that
//you will only ever be connecting to one RM, you can get the Whereabouts ONCE
//and store it to be used with multiple connections.
//
//here we actually get it each time, in order to show the generalized process
//

hr = pRm->GetTmWhereabouts (ResId, &rgbWhereabouts, &cbWhereabouts);
if (S_OK != hr)
{
SafeRelease(pRm);
_ASSERTE(hr == S_OK);
return hr;
} // if

hr = pTransaction->QueryInterface (IID_IGetDispenser, (LPVOID *) &pIDispenser);
if (S_OK != hr)
{
SafeRelease(pRm);
_ASSERTE(0);
return hr;
} // if

hr = pIDispenser->GetDispenser (IID_ITransactionExportFactory,
 (LPVOID *) &pTxExpFac );
SafeRelease(pIDispenser);
if (S_OK != hr)
{
_ASSERTE(0);
SafeRelease(pRm);
return hr;
} // if

hr = pTxExpFac->Create (cbWhereabouts, rgbWhereabouts, ppExport);
SafeRelease(pTxExpFac);
if (S_OK != hr)
{
SafeRelease(pRm);
_ASSERTE(0);
return hr;
} // if

CoTaskMemFree (rgbWhereabouts);
SafeRelease(pRm);
return hr;

}


FILERMPXY.CPP

/******************************************************************************\
********************************************************************************
* Filename: FileRmPxy.cpp
*
* Description:  Implementation of the RM Proxy. Also registers
the resource dispenser
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
#include "stdafx.h"
#include "TxFileDisp.h"
#include "copyfilerm.h"
#include "mtxdm.h"
#include "txcoord.h"
#include "FileRmPxy.h"
#include "txdtc.h"
#include "xolehlp.h"
#include "eh.h"

CFileRmPxy::CFileRmPxy()
{
m_pRm = NULL;
m_pDispMan = NULL;
m_pHolder = NULL;
m_pFreeThreadedMarshaler = NULL;
m_pGIT = NULL;

}
CFileRmPxy::~CFileRmPxy()
{

ATLTRACE(_T("FileRmPxy is being destroyed.\n"));

}

STDMETHODIMP CFileRmPxy::CopyDirTx(long hConnection, BSTR sSource, BSTR sDestination)
{

HRESULT hr;
IFileRm * pRm = GetFileRmPointer();
if (!pRm)
return E_UNEXPECTED;

// Do Work
WIN32_FIND_DATA fd;
memset(&fd, NULL, sizeof(fd));
HANDLE hFile;
CComBSTR sFileSource;
CComBSTR sFileDest;
LPTSTR lpSource = sSource;
LPTSTR lpDest = sDestination;
TCHAR sTemp[MAX_PATH];
TCHAR lpFileSpec[MAX_PATH];
memset(lpFileSpec, NULL, MAX_PATH);
memset(sTemp, NULL, MAX_PATH);
wsprintf(lpFileSpec, L"%s\\*.*", lpSource);
BOOL bFound = TRUE;
DWORD dwTest;

hFile = FindFirstFile(lpFileSpec, &fd);
while (hFile != INVALID_HANDLE_VALUE && bFound)
{
dwTest = fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
if (dwTest == 0) // copy if not a dir
{
wsprintf(sTemp, L"%s\\%s", lpSource, fd.cFileName);
sFileSource = sTemp;
wsprintf(sTemp, L"%s\\%s", lpDest, fd.cFileName);
sFileDest = sTemp;
pRm -> AddFile(hConnection, sFileSource, sFileDest);
}

memset(&fd, NULL, sizeof(fd));
bFound = FindNextFile(hFile, &fd);
}

FindClose(hFile);
hr = pRm -> CopyListTx(hConnection);
SafeRelease(pRm);
return hr;
}

#ifdef _DEBUG
void CFileRmPxy::ShowMessage()
{

LPTSTR lpMsgBuf = NULL;
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &lpMsgBuf,
0,
NULL
);

if (lpMsgBuf)
{
// Display the string.
MessageBox( NULL, lpMsgBuf, L"GetLastError", MB_OK|MB_ICONINFORMATION );

// Free the buffer.
LocalFree( lpMsgBuf );
}
}
#endif

STDMETHODIMP CFileRmPxy::AddFile(long hConnection, BSTR sSource, BSTR sDestination)
{
HRESULT hr;
IFileRm * pRm = GetFileRmPointer();
if (!pRm)
return E_UNEXPECTED;

hr = pRm -> AddFile(hConnection, sSource, sDestination);
SafeRelease(pRm);
return hr;
}

STDMETHODIMP CFileRmPxy::CopyFilesInCopyList(long hConnection)
{

HRESULT hr;
IFileRm * pRm = GetFileRmPointer();
if (!pRm)
return E_UNEXPECTED;

hr = pRm -> CopyListTx(hConnection);
SafeRelease(pRm);
return hr;

}

STDMETHODIMP CFileRmPxy::Connect(long *hConnection)
{
if (m_pDispMan)
{
if (!m_pHolder)
{
_ASSERTE(0);
return E_INVALIDARG;
}
}

if (!m_pRm)
{
_ASSERTE(0);
return E_INVALIDARG;
}

HRESULT hr;
if (m_pDispMan)
{
*hConnection = NULL;
hr = m_pHolder -> AllocResource((RESID)1, (RESID *)hConnection);
if (FAILED(hr))
{
AtlTrace(_T("AllocResource failed! Error code %x\n"), hr);
}
}
else  // not running under MTS, we must create our own resource
{
TIMEINSECS timeout;
hr = CreateResource((RESID)1,(RESID *)hConnection,&timeout);
}
return hr;
}
STDMETHODIMP CFileRmPxy::Disconnect(long hConnection)
{

if (m_pDispMan)
{
if (!m_pHolder)
{
_ASSERTE(0);
return E_INVALIDARG;
}
}

if (!m_pRm)
{
_ASSERTE(0);
return E_INVALIDARG;
}

HRESULT hr;
if (m_pDispMan)
{
hr = m_pHolder -> FreeResource(hConnection);
}
else
{
hr = DestroyResource(hConnection);
}

_ASSERTE(hr == S_OK);
return hr;
}
STDMETHODIMP CFileRmPxy::CopyFileTx(long hConnection,BSTR sSource,BSTR sDestination,  BOOL bFailIfExists)
{
IFileRm * pRm = GetFileRmPointer();
if (!pRm)
return E_UNEXPECTED;
HRESULT hr = pRm -> FileCopyTx(hConnection, sSource, sDestination, bFailIfExists);
SafeRelease(pRm);
return hr;

}
STDMETHODIMP CFileRmPxy::MoveFileTx(long hConnection,BSTR sSource,BSTR sDestination)
{
IFileRm * pRm = GetFileRmPointer();
if (!pRm)
return E_UNEXPECTED;
HRESULT hr = pRm -> FileMoveTxt(hConnection, sSource, sDestination);
SafeRelease(pRm);
return hr;
}
STDMETHODIMP CFileRmPxy::DeleteFileTx(long hConnection,BSTR sFileName)
{
IFileRm * pRm = GetFileRmPointer();
if (!pRm)
return E_UNEXPECTED;
HRESULT hr = pRm -> FileDeleteTx(hConnection, sFileName);
SafeRelease(pRm);
return hr;

}


FILERMPXY.H

/******************************************************************************\
********************************************************************************
* Filename: FileRmPxy.h
*
* Description:
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
// FileRmPxy.h : Declaration of the CFileRmPxy

#include "resource.h"       // main symbols
interface IFileRm;
interface ITransaction;
interface IDispenserDriver;

#include <map>
#include <set>
#include <list>
using namespace std;

interface IFileRm;

//
//setup a map of DWORDs to ITransactionExport * objects for
//mapping the DTC whereabouts to connections for enlisting on tx's
//

typedef map<DWORD, ITransactionExport *> ExportObjMap;

//
//a set of connection handles -- if the ASP shutsdown, we should free the connections
//
typedef set<long> ConnectionSet;

#define SafeRelease(pUnk) {if (pUnk){pUnk -> Release();pUnk = NULL; }}

/////////////////////////////////////////////////////////////////////////////
// CFileRmPxy
class CFileRmPxy :
public IDispenserDriver,
public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<CFileRmPxy, &CLSID_FileRmPxy>,
public IDispatchImpl<IFileRmPxy, &IID_IFileRmPxy, &LIBID_TXFILEDISPLib>

{

private:
// Resource Dispenser stuff
IGlobalInterfaceTable *m_pGIT;
DWORD m_dwRmPtrCookie;
IHolder * m_pHolder;
IDispenserManager * m_pDispMan;
CComAutoCriticalSection m_critsec;

// a map of Connection handles to export objects
ExportObjMap m_mapExport;

//critical sections for STL collections
CComAutoCriticalSection m_csCol;

BOOL m_bConfirm;
IFileRm * m_pRm;
#ifdef _DEBUG
void ShowMessage();
#endif
IFileRm * GetFileRmPointer();
ConnectionSet m_set;
IUnknown * m_pFreeThreadedMarshaler;

public:
//
// this resource dispenser must be a singleton object so that
// there is a unique IHolder which maintains the list of
// resources to be pooled
//
DECLARE_CLASSFACTORY_SINGLETON(CFileRmPxy);
DECLARE_PROTECT_FINAL_CONSTRUCT();

CFileRmPxy();
~CFileRmPxy();

HRESULT FinalConstruct();
void FinalRelease();
HRESULT GetExportObject(long ResID, ITransaction * pTransaction, ITransactionExport **ppExport);

DECLARE_REGISTRY_RESOURCEID(IDR_FileRmPxy)

BEGIN_COM_MAP(CFileRmPxy)
COM_INTERFACE_ENTRY(IFileRmPxy)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IDispenserDriver)
COM_INTERFACE_ENTRY_AGGREGATE(IID_IMarshal, m_pFreeThreadedMarshaler)
END_COM_MAP()

public:

// IFileRmPxy
STDMETHOD(AddFile)(long hConnection, BSTR sSource, BSTR sDestination);
STDMETHOD(CopyFilesInCopyList)(long hConnection);
STDMETHOD(CopyDirTx)(long hConnection, BSTR sSource, BSTR sDestination);
STDMETHOD(CopyFileTx)(long hConnection,BSTR sSource,BSTR sDestination,BOOL bFailIfExists);
STDMETHOD(MoveFileTx)(long hConnection,BSTR sSource,BSTR sDestination);
STDMETHOD(DeleteFileTx)(long hConnection,BSTR sFileName);

//connection management
STDMETHOD(Connect)(long *hConnection);
STDMETHOD(Disconnect)(long hConnection);

// IDispenserDriver
STDMETHOD(CreateResource)(/*[in]*/  const RESTYPID ResTypId,
      /*[out]*/ RESID* pResId,
      /*[out]*/ TIMEINSECS* pSecsFreeBeforeDestroy);

STDMETHOD(RateResource)(/*[in]*/  const RESTYPID ResTypId,
/*[in]*/  const RESID ResId,
/*[in]*/  const BOOL fRequiresTransactionEnlistment,
/*[out]*/ RESOURCERATING* pRating);

STDMETHOD(EnlistResource)(/*[in]*/  const RESID ResId,/*[in]*/  const TRANSID TransId);
STDMETHOD(ResetResource)(/*[in]*/  const RESID ResId);
STDMETHOD(DestroyResource)(/*[in]*/  const RESID ResId);// numeric resource id
STDMETHOD(DestroyResourceS)(/*[in]*/  constSRESID ResId);// string resource id

};


RESOURCE.H

/******************************************************************************\
********************************************************************************
* Filename: Resource.h
*
* Description:
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by TxFileDisp.rc
//
#define IDS_PROJNAME                    100
#define IDR_FileRmPxy                   101
#define IDR_EVENTSINK                   102
#define IDR_PROXYHOLDER                 103

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        201
#define _APS_NEXT_COMMAND_VALUE         32768
#define _APS_NEXT_CONTROL_VALUE         201
#define _APS_NEXT_SYMED_VALUE           104
#endif
#endif


STDAFX.H

/******************************************************************************\
********************************************************************************
* Filename: StdAfx.h
*
* Description:
*stdafx.h : include file for standard system include files,
*or project specific include files that are used frequently,
*but are changed infrequently
*
*
*
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/

#define STRICT 1
#define _WIN32_WINNT 0x0400
#pragma warning( disable : 4786) // disable the warning "identifier was truncated to '255' characters in the debug information" generated by STL
//#define _ATL_APARTMENT_THREADED
#define _ATL_FREE_THREADED
#include <atlbase.h>
//You may derive a class from CComModule and use it if you want to override
//something, but do not change the name of _Module

class CRdModule : public CComModule
{
public:
LONG Unlock();

};
extern CRdModule _Module;

#include <atlcom.h>


TXFILEDISP.CPP

/******************************************************************************\
********************************************************************************
* Filename: TxFileDisp.cpp
*
* Description: Implementation of DLL Exports.
*
* This file is provided as part of the Microsoft Transaction Server
* Software Development Kit
*
*THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
*WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
*INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
*OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
*PURPOSE.
*
* Copyright (C) 1997 Microsoft Corporation, All rights reserved
********************************************************************************
\******************************************************************************/
#include "stdafx.h"
#include "resource.h"
#include "TxFileDisp.h"
#define IID_DEFINED
#define INITGUID
#include "initguid.h"
#include "copyfilerm.h"
#include "mtxdm.h"

#include "TxFileDisp_i.c"
#include "mtxdm_i.c"
#include "copyfilerm_i.c"
#include "txcoord.h"
#include "FileRmPxy.h"
// need to include these to define the GUIDs in this segment
#include "txdtc.h"
#include "xolehlp.h"

#ifdef _MERGE_PROXYSTUB
extern "C" HINSTANCE hProxyDll;
#endif

CRdModule _Module;

LONG CRdModule::Unlock()
{
return  CComModule::Unlock();
}

BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_FileRmPxy, CFileRmPxy)
END_OBJECT_MAP()

/////////////////////////////////////////////////////////////////////////////
// DLL Entry Point

extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
lpReserved;
#ifdef _MERGE_PROXYSTUB
if (!PrxDllMain(hInstance, dwReason, lpReserved))
return FALSE;
#endif
if (dwReason == DLL_PROCESS_ATTACH)
{
_Module.Init(ObjectMap, hInstance);
DisableThreadLibraryCalls(hInstance);
}
else if (dwReason == DLL_PROCESS_DETACH)
_Module.Term();
return TRUE;    // ok
}

/////////////////////////////////////////////////////////////////////////////
// Used to determine whether the DLL can be unloaded by OLE

STDAPI DllCanUnloadNow(void)
{
#ifdef _MERGE_PROXYSTUB
if (PrxDllCanUnloadNow() != S_OK)
return S_FALSE;
#endif
return (_Module.GetLockCount()==0) ? S_OK : S_FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// Returns a class factory to create an object of the requested type

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
#ifdef _MERGE_PROXYSTUB
if (PrxDllGetClassObject(rclsid, riid, ppv) == S_OK)
return S_OK;
#endif
return _Module.GetClassObject(rclsid, riid, ppv);
}

/////////////////////////////////////////////////////////////////////////////
// DllRegisterServer - Adds entries to the system registry

STDAPI DllRegisterServer(void)
{
#ifdef _MERGE_PROXYSTUB
HRESULT hRes = PrxDllRegisterServer();
if (FAILED(hRes))
return hRes;
#endif
// registers object, typelib and all interfaces in typelib
return _Module.RegisterServer(TRUE);
}

/////////////////////////////////////////////////////////////////////////////
// DllUnregisterServer - Removes entries from the system registry

STDAPI DllUnregisterServer(void)
{
#ifdef _MERGE_PROXYSTUB
PrxDllUnregisterServer();
#endif
_Module.UnregisterServer();
#if _WIN32_WINNT >= 0x0400
UnRegisterTypeLib(LIBID_TXFILEDISPLib, 1, 0, LOCALE_USER_DEFAULT, SYS_WIN32);
#endif
return S_OK;
}


FRMMAIN.FRM

VERSION 5.00
Begin VB.Form frmMain
   AutoRedraw      =   -1  'True
   BorderStyle     =   3  'Fixed Dialog
   Caption         =   "Tic-Tac-Toe client"
   ClientHeight    =   5550
   ClientLeft      =   45
   ClientTop       =   330
   ClientWidth     =   9300
   LinkTopic       =   "Form1"
   MaxButton       =   0   'False
   MinButton       =   0   'False
   ScaleHeight     =   6366.517
   ScaleMode       =   0  'User
   ScaleWidth      =   9423.598
   ShowInTaskbar   =   0   'False
   StartUpPosition =   2  'CenterScreen
   Begin VB.TextBox lossesT
      Height          =   375
      Left            =   5280
      Locked          =   -1  'True
      TabIndex        =   16
      Text            =   "0"
      Top             =   3000
      Width           =   375
   End
   Begin VB.TextBox tiesT
      Height          =   375
      Left            =   4680
      Locked          =   -1  'True
      TabIndex        =   15
      Text            =   "0"
      Top             =   3000
      Width           =   375
   End
   Begin VB.TextBox winsT
      Height          =   375
      Left            =   4080
      Locked          =   -1  'True
      TabIndex        =   12
      Text            =   "0"
      Top             =   3000
      Width           =   375
   End
   Begin VB.OptionButton Hard
      Caption         =   "Hard"
      Height          =   255
      Left            =   4440
      TabIndex        =   10
      Top             =   1440
      Value           =   -1  'True
      Width           =   1095
   End
   Begin VB.OptionButton Easy
      Caption         =   "Easy"
      Height          =   255
      Left            =   4440
      TabIndex        =   9
      Top             =   1800
      Width           =   855
   End
   Begin VB.TextBox Nombre
      Height          =   285
      Left            =   4440
      TabIndex        =   8
      Top             =   600
      Width           =   1335
   End
   Begin VB.Timer ServerPoll
      Enabled         =   0   'False
      Interval        =   300
      Left            =   3840
      Top             =   0
   End
   Begin VB.TextBox Result
      Height          =   285
      Left            =   120
      Locked          =   -1  'True
      TabIndex        =   7
      Top             =   5160
      Width           =   5895
   End
   Begin VB.CommandButton Exit
      Caption         =   "Exit Tic-Tac-Toe"
      BeginProperty Font
         Name            =   "Arial"
         Size            =   8.25
         Charset         =   0
         Weight          =   400
         Underline       =   0   'False
         Italic          =   0   'False
         Strikethrough   =   0   'False
      EndProperty
      Height          =   375
      Left            =   3960
      TabIndex        =   5
      Top             =   4560
      Width           =   2055
   End
   Begin VB.TextBox Name2
      Height          =   315
      Left            =   2520
      Locked          =   -1  'True
      TabIndex        =   4
      Top             =   600
      Width           =   1215
   End
   Begin VB.TextBox Name1
      Height          =   285
      Left            =   120
      Locked          =   -1  'True
      TabIndex        =   3
      Top             =   600
      Width           =   1215
   End
   Begin VB.CommandButton PlayHuman
      Caption         =   "Play against a Human"
      BeginProperty Font
         Name            =   "Arial"
         Size            =   8.25
         Charset         =   0
         Weight          =   400
         Underline       =   0   'False
         Italic          =   0   'False
         Strikethrough   =   0   'False
      EndProperty
      Height          =   375
      Left            =   3960
      TabIndex        =   1
      Top             =   4080
      Width           =   2055
   End
   Begin VB.CommandButton PlayComputer
      Caption         =   "Play against the computer"
      BeginProperty Font
         Name            =   "Arial"
         Size            =   8.25
         Charset         =   0
         Weight          =   400
         Underline       =   0   'False
         Italic          =   0   'False
         Strikethrough   =   0   'False
      EndProperty
      Height          =   375
      Left            =   3960
      TabIndex        =   0
      Top             =   3600
      Width           =   2055
   End
   Begin VB.Image bmpSmallX
      Height          =   315
      Left            =   8640
      Picture         =   "frmMain.frx":0000
      Top             =   2400
      Visible         =   0   'False
      Width           =   315
   End
   Begin VB.Image bmpSmallO
      Height          =   315
      Left            =   8160
      Picture         =   "frmMain.frx":017E
      Top             =   2400
      Visible         =   0   'False
      Width           =   315
   End
   Begin VB.Image bmpNull
      Height          =   315
      Left            =   8400
      Picture         =   "frmMain.frx":02FC
      Top             =   2880
      Visible         =   0   'False
      Width           =   315
   End
   Begin VB.Image bmpBigX
      Height          =   900
      Left            =   8040
      Picture         =   "frmMain.frx":047A
      Top             =   1200
      Visible         =   0   'False
      Width           =   900
   End
   Begin VB.Image bmpBigO
      Height          =   900
      Left            =   8040
      Picture         =   "frmMain.frx":0C7C
      Top             =   120
      Visible         =   0   'False
      Width           =   900
   End
   Begin VB.Image Image12
      Height          =   900
      Left            =   1440
      Top             =   3960
      Width           =   900
   End
   Begin VB.Label Label4
      Caption         =   "Losses"
      Height          =   255
      Left            =   5280
      TabIndex        =   18
      Top             =   2640
      Width           =   495
   End
   Begin VB.Label Label3
      Caption         =   "Ties"
      Height          =   255
      Left            =   4680
      TabIndex        =   17
      Top             =   2640
      Width           =   375
   End
   Begin VB.Label Label2
      Caption         =   "Wins"
      Height          =   255
      Left            =   4080
      TabIndex        =   14
      Top             =   2640
      Width           =   375
   End
   Begin VB.Label SessionLabel
      Caption         =   "This session:"
      Height          =   255
      Left            =   4440
      TabIndex        =   13
      Top             =   2280
      Width           =   975
   End
   Begin VB.Label ComputerLabel
      Caption         =   "Select degree of difficulty"
      Height          =   255
      Left            =   4320
      TabIndex        =   11
      Top             =   1080
      Width           =   1935
   End
   Begin VB.Image Image02
      Height          =   900
      Left            =   240
      Top             =   3960
      Width           =   900
   End
   Begin VB.Image Image22
      Height          =   900
      Left            =   2760
      Top             =   3960
      Width           =   900
   End
   Begin VB.Image Image21
      Height          =   900
      Left            =   2760
      Top             =   2640
      Width           =   900
   End
   Begin VB.Image Image10
      Height          =   915
      Left            =   1440
      Top             =   1320
      Width           =   945
   End
   Begin VB.Image Image20
      Height          =   900
      Left            =   2760
      Top             =   1320
      Width           =   900
   End
   Begin VB.Image Image01
      Height          =   900
      Left            =   240
      Top             =   2640
      Width           =   900
   End
   Begin VB.Image Image11
      Height          =   900
      Left            =   1440
      Top             =   2640
      Width           =   900
   End
   Begin VB.Image Image00
      Height          =   900
      Left            =   240
      Top             =   1320
      Width           =   900
   End
   Begin VB.Label NameLabel
      Caption         =   "Your name is..."
      Height          =   255
      Left            =   4560
      TabIndex        =   6
      Top             =   240
      Width           =   1095
   End
   Begin VB.Image Player2
      Height          =   315
      Left            =   3000
      Top             =   120
      Width           =   315
   End
   Begin VB.Image Player1
      Height          =   315
      Left            =   600
      Top             =   120
      Width           =   315
   End
   Begin VB.Image GameScreen
      Height          =   3615
      Left            =   120
      Picture         =   "frmMain.frx":147E
      Top             =   1320
      Width           =   3615
   End
   Begin VB.Label Label1
      Caption         =   "Versus"
      Height          =   255
      Left            =   1680
      TabIndex        =   2
      Top             =   360
      Width           =   495
   End
End
Attribute VB_Name = "frmMain"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
' Filename: tClient.vbp
'
' Description:  Tic-Tac-Toe Client
'
' This file is provided as part of the Microsoft Transaction Server
' Software Development Kit
'
' THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
' WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
' INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
' OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
' PURPOSE.
'
' Copyright (C) 1997 Microsoft Corporation, All rights reserved

Option Explicit

Private viper As Boolean
Private organic As Boolean
Private playing As Boolean
Private gameID As Long
Private localX As Long
Private localY As Long
Private localOrder As Long
Private localName As String
Private remoteName As String
Private easyComputer As Boolean
Private wins As Long
Private ties As Long
Private losses As Long

Private localPic As Picture
Private remotePic As Picture

Private full(3, 3) As Long
Private waiting As Boolean

Private Sub Easy_Click()
    easyComputer = True
End Sub

Private Sub Form_Load()
    Me.Left = GetSetting(App.Title, "Settings", "MainLeft", 1000)
    Me.Top = GetSetting(App.Title, "Settings", "MainTop", 1000)
    Me.Width = GetSetting(App.Title, "Settings", "MainWidth", 6500)
    Me.Height = GetSetting(App.Title, "Settings", "MainHeight", 6500)

    ' Make sure the Tic-Tac-Toe components are properly installed in MTS
    On Error GoTo objError
    Dim computer As Object
    Dim human As Object
    Set computer = CreateObject("tServer.Computer")
    Set human = CreateObject("tServer.Human")
    On Error GoTo 0

    ' Init
    localName = "tClient User"
    remoteName = "No one"
    viper = False
    organic = False
    playing = False
    gameID = -1
    waiting = False
    ServerPoll.Enabled = False
    easyComputer = False
    wins = 0
    ties = 0
    losses = 0

    Nombre = localName
    Name1 = "No one"
    Name2 = remoteName
    Player1.Picture = bmpNull
    Player2.Picture = bmpNull
    Result = "Press a button and start a game"

    Exit Sub

objError:
    MsgBox "Error " & Err.Number & ": Make sure the Tic-Tac-Toe package has been correctly installed in MTS."
    End
End Sub

Private Sub Form_Unload(Cancel As Integer)
    Dim i As Integer

    'Close all sub forms
    For i = Forms.Count - 1 To 1 Step -1
        Unload Forms(i)
    Next
    If Me.WindowState <> vbMinimized Then
        SaveSetting App.Title, "Settings", "MainLeft", Me.Left
        SaveSetting App.Title, "Settings", "MainTop", Me.Top
        SaveSetting App.Title, "Settings", "MainWidth", Me.Width
        SaveSetting App.Title, "Settings", "MainHeight", Me.Height
    End If
End Sub

Private Sub Hard_Click()
    easyComputer = False
End Sub

Private Sub Image00_Click()

    If playing And full(0, 0) = 0 Then
        If localOrder = 1 Then
            Image00.Picture = bmpBigX
        Else
            Image00.Picture = bmpBigO
        End If

        MovePiece 0, 0
    End If

End Sub

Private Sub Image01_Click()

    If playing And full(0, 1) = 0 Then
        If localOrder = 1 Then
            Image01.Picture = bmpBigX
        Else
            Image01.Picture = bmpBigO
        End If

        MovePiece 0, 1
    End If

End Sub

Private Sub Image02_Click()

    If playing And full(0, 2) = 0 Then
        If localOrder = 1 Then
            Image02.Picture = bmpBigX
        Else
            Image02.Picture = bmpBigO
        End If

        MovePiece 0, 2
    End If

End Sub

Private Sub Image10_Click()

    If playing And full(1, 0) = 0 Then
        If localOrder = 1 Then
            Image10.Picture = bmpBigX
        Else
            Image10.Picture = bmpBigO
        End If

        MovePiece 1, 0
    End If

End Sub

Private Sub Image11_Click()

    If playing And full(1, 1) = 0 Then
        If (localOrder = 1) Then
            Image11.Picture = bmpBigX
        Else
            Image11.Picture = bmpBigO
        End If

        MovePiece 1, 1
    End If

End Sub

Private Sub Image12_Click()

    If playing And full(1, 2) = 0 Then
        If localOrder = 1 Then
            Image12.Picture = bmpBigX
        Else
            Image12.Picture = bmpBigO
        End If

        MovePiece 1, 2
    End If

End Sub

Private Sub Image20_Click()

    If playing And full(2, 0) = 0 Then
        If localOrder = 1 Then
            Image20.Picture = bmpBigX
        Else
            Image20.Picture = bmpBigO
        End If

        MovePiece 2, 0
    End If

End Sub

Private Sub Image21_Click()

    If playing And full(2, 1) = 0 Then
        If localOrder = 1 Then
            Image21.Picture = bmpBigX
        Else
            Image21.Picture = bmpBigO
        End If

        MovePiece 2, 1
    End If

End Sub

Private Sub Image22_Click()

    If playing And full(2, 2) = 0 Then
        If localOrder = 1 Then
            Image22.Picture = bmpBigX
        Else
            Image22.Picture = bmpBigO
        End If

        MovePiece 2, 2
    End If

End Sub

Private Sub Nombre_Change()
    localName = Nombre
End Sub

Private Sub PlayHuman_Click()

    ' Present hourglass pointer
    Me.MousePointer = vbHourglass

    ' Clean up screen
    ClearScreen
    InitArray

    ' Create server human object
    Dim human As Object
    Set human = CreateObject("tServer.Human")
    If human Is Nothing Then
        MsgBox "Create object " + "tServer.Human" + " failed."
        Exit Sub
    End If

    ' Notify server of new game
    Dim order As Variant
    Dim player2Name As Variant
    Dim gameNumber As Variant
    human.EnterNewGame localName, gameNumber, player2Name, order
    remoteName = player2Name
    gameID = gameNumber

    ' Set up screen
    Name1 = localName
    Name2 = remoteName
    PlayComputer.Enabled = False
    PlayHuman.Enabled = False
    Nombre.Locked = True
    Easy.Enabled = False
    Hard.Enabled = False

    ' Set up local variables
    viper = False
    organic = True
    playing = False
    ServerPoll.Enabled = True

    ' Set up order
    localOrder = order
    If order = 1 Then
        Player1.Picture = bmpSmallX
        Player2.Picture = bmpSmallO
        Set localPic = bmpBigX
        Set remotePic = bmpBigO
        Result = "You are now in game " & gameID & ", we are waiting for another 'human' to join the game."
    Else
        Player1.Picture = bmpSmallO
        Player2.Picture = bmpSmallX
        Set localPic = bmpBigO
        Set remotePic = bmpBigX
        Result = "Game " & gameID & " against '" & remoteName & "' has begun.  Waiting for '" & remoteName & "' to move."
        waiting = True
    End If

    ' Clean up
    Set human = Nothing

    ' Return control
    Me.MousePointer = vbArrow

End Sub

Private Sub PlayComputer_Click()

    ' Present hourglass pointer
    Me.MousePointer = vbHourglass

    ' Clean up screen
    InitArray
    ClearScreen

    ' Create server computer object
    Dim computer As Object
    Set computer = CreateObject("tServer.Computer")
    If computer Is Nothing Then
        MsgBox "Create object " + "tServer.Computer" + " failed."
        Exit Sub
    End If

    ' Notify server of new game
    Dim lGameID As Variant
    Dim order As Variant
    Dim foeX As Variant
    Dim foeY As Variant
    computer.AddNewGame easyComputer, lGameID, order, foeX, foeY
    gameID = lGameID

    ' Set up screen
    If easyComputer = True Then
        remoteName = "I am, I think"
    Else
        remoteName = "I think I am"
    End If
    Name1 = localName
    Name2 = remoteName
    PlayComputer.Enabled = False
    PlayHuman.Enabled = False
    Nombre.Locked = True
    Easy.Enabled = False
    Hard.Enabled = False

    ' Set up local variables
    viper = True
    organic = False
    playing = True

    ' Set up order
    localOrder = order
    If order = 1 Then
        Player1.Picture = bmpSmallX
        Player2.Picture = bmpSmallO
        Set localPic = bmpBigX
        Set remotePic = bmpBigO

    Else
        Player1.Picture = bmpSmallO
        Player2.Picture = bmpSmallX
        Set localPic = bmpBigO
        Set remotePic = bmpBigX

        ' Process opponent's first move
        Display remotePic, foeX, foeY
        full(foeX, foeY) = 1

    End If

    ' Clean up
    Set computer = Nothing

    ' Return control
    Result = "Game " & gameID & " against '" & remoteName & "' has begun.  It is now your turn."
    Me.MousePointer = vbArrow

End Sub

Private Sub Exit_Click()
    ' Notify opponent of exit
    End
End Sub

Private Sub MovePiece(X As Long, Y As Long)

    Dim win As Variant
    If viper Then

        ' Create server computer object
        Dim computer As Object
        Set computer = CreateObject("tServer.Computer")
        If computer Is Nothing Then
            MsgBox "Create object " + "tServer.Computer" + " failed."
            Exit Sub
        End If

        ' Submit move
        Dim foeX As Variant
        Dim foeY As Variant
        computer.NewMove gameID, easyComputer, X, Y, foeX, foeY, win

        ' Win protocol
        ' ============
        ' 0  -> moves did not end game
        ' 1  -> player won
        ' -1 -> computer won
        ' 2  -> player's move tied the game
        ' -2 -> computer's move tied the game

        ' Display local move
        Display localPic, X, Y
        full(X, Y) = 1

        ' Check for win or loss
        If Abs(win) = 1 Then
            If win = 1 Then
                wins = wins + 1
                winsT = wins
                Result = "Congratulations!  You won your game against '" & remoteName & "'."
                Cleanup
                Exit Sub
            Else
                Display remotePic, foeX, foeY
                losses = losses + 1
                lossesT = losses
                Result = "Too bad, '" & remoteName & "' won.  Better luck next time!"
                Cleanup
                Exit Sub
            End If
        Else
            If Abs(win) = 2 Then
                If win = 2 Then
                    Display remotePic, foeX, foeY
                End If
                ties = ties + 1
                tiesT = ties
                Result = "You tied '" & remoteName & ".' " & " Keep trying!"
                Cleanup
                Exit Sub
            Else
                ' Display opponent's move
                Display remotePic, foeX, foeY
                full(foeX, foeY) = 1
            End If
        End If

        ' Clean up
        Set computer = Nothing

        Result = "It is now your turn."

    Else

        ' Shut down server polling
        ServerPoll.Enabled = False

        ' Create server human object
        Dim human As Object
        Set human = CreateObject("tServer.Human")
        If human Is Nothing Then
            MsgBox "Create object " + "tServer.Human" + " failed."
            Exit Sub
        End If

        ' Submit move
        human.NewMove gameID, localOrder, X, Y, win

        ' Win protocol
        ' ============
        ' 0  -> move did not end game
        ' 1  -> player won
        ' -1 -> remote player won (won't occur in this function)
        ' 2  -> player's move tied the game
        ' -2 -> remote player's move tied the game (won't occur in this function)

        ' Display local move
        Display localPic, X, Y
        full(X, Y) = 1

        ' Check for win or loss
        If win = 1 Then
            wins = wins + 1
            winsT = wins
            Result = "Congratulations!  You won your game against '" & remoteName & "'."
            Cleanup
            Exit Sub
        Else
            If win = 2 Then
                ties = ties + 1
                tiesT = ties
                Result = "You tied '" & remoteName & ".' " & " Keep trying!"
                Cleanup
                Exit Sub
            Else
                ' Reenable server polling
                ServerPoll.Enabled = True
            End If
        End If

        ' We're waiting for a move or we finished the game
        playing = False

        ' Clean up
        Set human = Nothing

        Result = "Waiting for '" & remoteName & "'s next move."
    End If

End Sub

Private Sub Display(pic As Picture, X As Variant, Y As Variant)

    Dim hash As Long
    hash = X * 10 + Y

    Select Case hash

        Case 0
            Image00.Picture = pic
        Case 1
            Image01.Picture = pic
        Case 2
            Image02.Picture = pic
        Case 10
            Image10.Picture = pic
        Case 11
            Image11.Picture = pic
        Case 12
            Image12.Picture = pic
        Case 20
            Image20.Picture = pic
        Case 21
            Image21.Picture = pic
        Case 22
            Image22.Picture = pic
        Case Else
            MsgBox ("Invalid Display Parameter")

    End Select

End Sub

Private Sub Cleanup()

    ' Reset variables
    playing = False
    viper = False
    organic = False
    PlayHuman.Enabled = True
    PlayComputer.Enabled = True
    Nombre.Locked = False
    Easy.Enabled = True
    Hard.Enabled = True

End Sub

Private Sub InitArray()

    Dim i, j As Long
    For i = 0 To 2
        For j = 0 To 2
            full(i, j) = 0
        Next
    Next

End Sub

Private Sub ServerPoll_Timer()

    If organic And Not playing Then

        ' Create server human object
        Dim human As Object
        Set human = CreateObject("tServer.Human")
        If human Is Nothing Then
            MsgBox "Create object " + "tServer.Human" + " failed."
            Exit Sub
        End If

        ' Poll server
        Dim name As Variant
        Dim foeX As Variant
        Dim foeY As Variant
        Dim win As Variant
        Dim update As Variant
        human.GetNewMove gameID, localOrder, name, foeX, foeY, win, update

        ' Win protocol
        ' ============
        ' 0  -> move did not end game
        ' 1  -> player won (won't occur in this function)
        ' -1 -> remote player won
        ' 2  -> player's move tied the game (won't occur in this function)
        ' -2 -> remote player's move tied the game

        ' Routine move
        If update = 1 Then

            ' Stop polling
            ServerPoll.Enabled = False

            ' Display opponent's move
            Display remotePic, foeX, foeY
            full(foeX, foeY) = 2

            ' Check for win or loss
            If win = -1 Then
                losses = losses + 1
                lossesT = losses
                Result = "Too bad, '" & remoteName & "' won.  Better luck next time!"
                Cleanup
                Exit Sub
            Else
                If win = -2 Then
                    ties = ties + 1
                    tiesT = ties + 1
                    Result = "You tied '" & remoteName & ".' " & " Keep trying!"
                    Cleanup
                    Exit Sub
                Else
                    Result = "'" & remoteName & "' has moved.  It is now your turn."
                    playing = True
                End If
            End If
        Else
            ' Player just joined
            If update = 2 Then
                remoteName = name
                Name2 = remoteName
                Result = "'" & remoteName & "' has joined your game.  It is now your turn."
                playing = True
            End If
        End If

        ' Clean up
        Set human = Nothing

    End If

End Sub

Private Sub ClearScreen()

    Image00.Picture = LoadPicture
    Image10.Picture = LoadPicture
    Image20.Picture = LoadPicture
    Image01.Picture = LoadPicture
    Image11.Picture = LoadPicture
    Image21.Picture = LoadPicture
    Image02.Picture = LoadPicture
    Image12.Picture = LoadPicture
    Image22.Picture = LoadPicture

End Sub


COMPUTER.CPP

// Filename: Computer.cpp
//
// Description:  Implementation of CComputer
// This file contains the code that handles the interaction between a human
// player and a computer player.
//
// This file is provided as part of the Microsoft Transaction Server Samples
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
// WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
// PURPOSE.
//
// Copyright (C) 1997 Microsoft Corporation, All rights reserved

#include "stdafx.h"
#include "tServer.h"
#include "Computer.h"

#include <mtx.h>
#include <mtxspm.h>
#include <time.h>

/////////////////////////////////////////////////////////////////////////////
//

STDMETHODIMP CComputer::InterfaceSupportsErrorInfo(REFIID riid)
{
static const IID* arr[] =
{
&IID_IComputer,
};

for (int i=0;i<sizeof(arr)/sizeof(arr[0]);i++)
{
if (InlineIsEqualGUID(*arr[i],riid))
return S_OK;
}
return S_FALSE;
}

STDMETHODIMP CComputer::AddNewGame (IN VARIANT_BOOL bEasy, OUT VARIANT* pvGameID, OUT VARIANT* pvOrder,
OUT VARIANT* pvX, OUT VARIANT* pvY) {

HRESULT hr = S_OK;

IObjectContext* pObjectContext = NULL;

ISharedPropertyGroupManager* spmMgr = NULL;
ISharedPropertyGroup* spmGroup = NULL;

ISharedProperty* spmPropCounter = NULL;
ISharedProperty* spmPropState = NULL;

pvGameID->vt = VT_I4;
pvOrder->vt = VT_I4;
pvX->vt = VT_I4;
pvY->vt = VT_I4;

long lGameID = 0;
long lOrder = 0;
long lNewX = 0;
long lNewY = 0;

try {

// Get the object context
THROW_ERR ( hr = GetObjectContext(&pObjectContext) );
if (pObjectContext == NULL) {
THROW_ERR ( E_FAIL );
}

// Create the SharedPropertyGroupManager
THROW_ERR ( pObjectContext->CreateInstance (CLSID_SharedPropertyGroupManager, IID_ISharedPropertyGroupManager, (void**)&spmMgr) );

// Create the SharedPropertyGroup
LONG lIsolationMode = LockMethod;
LONG lReleaseMode = Process;
VARIANT_BOOL bExists = VARIANT_FALSE;
THROW_ERR ( spmMgr->CreatePropertyGroup (L"TicTacToe", &lIsolationMode, &lReleaseMode, &bExists, &spmGroup) );

// Create the counter properties
THROW_ERR ( spmGroup->CreateProperty (L"Counter", &bExists, &spmPropCounter) );

// Obtain the current value of the counter
CComVariant vCounter;
THROW_ERR ( spmPropCounter->get_Value (&vCounter) );

// Assign gameID
if (vCounter.lVal == 0)
vCounter.lVal = 1;

lGameID = vCounter.lVal;

// Update the counter
vCounter.lVal ++;
THROW_ERR ( spmPropCounter->put_Value (vCounter) );

// Set the state variable
TCHAR szBuf [512];
BSTR bstrState;

wsprintf (szBuf, _T("%dState"), lGameID);
bstrState = TCHAR2BSTR (szBuf);
THROW_ERR ( spmGroup->CreateProperty (bstrState, &bExists, &spmPropState) );

CComVariant vState;
vState.vt = VT_I4;
vState.lVal = 4;
THROW_ERR ( spmPropState->put_Value (vState) );

// Initialize the array
InitArray();
THROW_ERR ( SaveArray(lGameID) );

// Assign order randomly
srand ((unsigned) time (NULL));
lOrder = rand() % 2 + 1;

// Get computer's first move if player's order is 2
if (lOrder == 2) {
GetMove (lGameID, bEasy, lNewX, lNewY);
plField [lNewX][lNewY] = 2;
SaveArray(lGameID);
}

// Prepare return values
pvX->lVal = lNewX;
pvY->lVal = lNewY;
pvOrder->lVal = lOrder;
pvGameID->lVal = lGameID;

// We're finished and happy
pObjectContext->SetComplete();

} catch (HRESULT hr) {

// Create an ErrorInfo object
ICreateErrorInfo* pCreateErrInfo = NULL;
IErrorInfo* pErrInfo = NULL;

CreateErrorInfo (&pCreateErrInfo);
pCreateErrInfo->QueryInterface (IID_IErrorInfo, (LPVOID FAR*) &pErrInfo);

// Fill in error information
TCHAR szErr [512];
wsprintf (szErr, _T("Error %d occurred in Computer::AddNewGame()"), hr);
BSTR bstrDesc = TCHAR2BSTR (szErr);
pCreateErrInfo->SetGUID (IID_IComputer);
pCreateErrInfo->SetSource (L"Computer");
pCreateErrInfo->SetDescription (bstrDesc);
::SysFreeString (bstrDesc);

// Confirm error information
SetErrorInfo (0, pErrInfo);

// Clean up the error objects
if (pCreateErrInfo)
pCreateErrInfo->Release();

if (pErrInfo)
pErrInfo->Release();

// Indicate our unhappiness
if (pObjectContext)
pObjectContext->SetAbort();

hr = E_FAIL;
}

if (pObjectContext)
pObjectContext->Release();

if (spmMgr)
spmMgr->Release();

if (spmGroup)
spmGroup->Release();

if (spmPropCounter)
spmPropCounter->Release();

if (spmPropState)
spmPropState->Release();

return hr;
}

STDMETHODIMP CComputer::NewMove (IN long lGameID, IN VARIANT_BOOL bEasy, IN long lX, IN long lY,
 OUT VARIANT* pvMyX, OUT VARIANT* pvMyY, OUT VARIANT* pvWin) {
// Win protocol
// ============
// 0  -> moves did not end game
// 1  -> player won
// -1 -> computer won
// 2  -> player's move tied the game
// -2 -> computer's move tied the game

HRESULT hr = S_OK;

pvMyX->vt = VT_I4;
pvMyY->vt = VT_I4;
pvWin->vt = VT_I4;

long lTurns = 0;
long lWin = 0;
long lNewX = 0;
long lNewY = 0;

IObjectContext* pObjectContext = NULL;

try {

// Get the object context
THROW_ERR ( hr = GetObjectContext(&pObjectContext) );
if (pObjectContext == NULL) {
THROW_ERR ( E_FAIL );
}

THROW_ERR ( LoadArray(lGameID) );

// Enter new move
plField [lX][lY] = 1;

// Check for player's win
if (IsWin (1)) {

// Player won
lWin = 1;

} else {

// Check for fullness
lTurns = HowManyTurns();

if (lTurns == 9) {

// Tie
lWin = - 2;

} else {

// Get computer's move
GetMove (lGameID, bEasy, lNewX, lNewY);
plField[lNewX][lNewY] = 2;

// Check for computer win
if (IsWin(2))

lWin = - 1;

else

// Check for fullness
if (lTurns == 8)
lWin = 2;
}
}

// Save array
THROW_ERR ( SaveArray(lGameID) );

// Set return values
pvMyX->lVal = lNewX;
pvMyY->lVal = lNewY;
pvWin->lVal = lWin;

// We're finished and happy
pObjectContext->SetComplete();

} catch (HRESULT hr) {

// Create an ErrorInfo object
ICreateErrorInfo* pCreateErrInfo = NULL;
IErrorInfo* pErrInfo = NULL;

CreateErrorInfo (&pCreateErrInfo);
pCreateErrInfo->QueryInterface (IID_IErrorInfo, (LPVOID FAR*) &pErrInfo);

// Fill in error information
TCHAR szErr[512];
wsprintf (szErr, _T("Error %d occurred in Computer::NewMove()"), hr);
BSTR bstrDesc = TCHAR2BSTR (szErr);
pCreateErrInfo->SetGUID (IID_IComputer);
pCreateErrInfo->SetSource (L"Computer");
pCreateErrInfo->SetDescription (bstrDesc);
::SysFreeString (bstrDesc);

// Confirm error information
SetErrorInfo (0, pErrInfo);

// Clean up the error objects
if (pCreateErrInfo)
pCreateErrInfo->Release();

if (pErrInfo)
pErrInfo->Release();

// Indicate our unhappiness
if (pObjectContext)
pObjectContext->SetAbort();

hr = E_FAIL;
}

if (pObjectContext)
pObjectContext->Release();

return hr;
}

void CComputer::GetMove (long lGameID, VARIANT_BOOL bEasy, long& lX, long& lY) {

long lFlag = 0;
long lNewX = 0;
long lNewY = 0;

// Perhaps there's only possible move
if (HowManyTurns() == 8) {

// Scan for open tile
long i,j;
for (i = 0; i < 3; i ++) {
for (j = 0; j < 3; j ++) {
if (plField[i][j] == 0) {
lNewX = i;
lNewY = j;
lFlag = 1;
}
}
}
}

// Check for win
if (lFlag == 0)
lFlag = LineScan (2, lNewX, lNewY);

// Check for block
if (lFlag == 0)
lFlag = LineScan (1, lNewX, lNewY);

// Else create a new move
if (lFlag == 0) {

// Prepare random seed
srand ((unsigned) time (NULL));

// Decide intelligence level
if (!bEasy) {

// Place in center if it's open
if (plField[1][1] == 0) {
lNewX = 1;
lNewY = 1;
lFlag = 1;

} else {

// Place on corner, if opponent placed first move in center
long lTurns = HowManyTurns();
if (lTurns == 1 && plField[1][1] > 0) {
lNewX = (rand() % 2 == 0) ? 0:2;
lNewY = (rand() % 2 == 0) ? 0:2;
lFlag = 1;
} else {

// Place on opposite tile, if opponent's second
// move was placed on diagonal
if (lTurns == 2) {
if (plField[0][0] == 1) {
lNewX = 2;
lNewY = 2;
lFlag = 1;
}

if (plField[0][2] == 1) {
lNewX = 2;
lNewY = 0;
lFlag = 1;
}

if (plField[2][0] == 1) {
lNewX = 0;
lNewY = 2;
lFlag = 1;
}

if (plField[2][2] == 1) {
lNewX = 0;
lNewY = 0;
lFlag = 1;
}
}
}
}

// Else find most intelligent move
if (lFlag == 0) {

float value = 0;
float maxValue = 0;

for (long i = 0; i < 3; i ++) {
for (long j = 0; j < 3; j ++) {

// Evaluate tile
value = Evaluate (i, j);

// Randomize equal choices
if (value == maxValue && rand() % 2 == 0) {

lNewX = i;
lNewY = j;

} else {

if (value > maxValue) {
maxValue = value;
lNewX = i;
lNewY = j;
}
}
}
}
}

} else {

// Choose a random slot
while (lFlag == 0) {

lNewX = rand() % 3;
lNewY = rand() % 3;

if (plField [lNewX][lNewY] == 0)
lFlag = 1;
}
}
}

// Prepare return values
lX = lNewX;
lY = lNewY;
}

float CComputer::Evaluate (long lX, long lY) {

long lNewX, lNewY, lNewX2, lNewY2;
float fEval1, fEval2, fEval3;

if (plField[lX][lY] != 0) {

lX = - 100;
lY = - 100;
return - 100;
} else {

// Evaluate defensive options
plField[lX][lY] = 1;
fEval2 = (float) (LineScan (1, lNewX, lNewY) / 3);

// Evaluate offensive options
plField[lX][lY] = 2;
fEval1 = (float) LineScan (2, lNewX, lNewY);

// Evaluate opponent's follow-up
if (fEval1 > 0) {
plField[lNewX][lNewY] = 1;
fEval3 = (float) (LineScan (1, lNewX2, lNewY2) * 0.9);
plField[lNewX][lNewY] = 0;
} else
fEval3 = 0;

// Restore tiles and return final evaluation
plField[lX][lY] = 0;
return fEval1 + fEval2 - fEval3;
}
}

bool CComputer::IsWin (long lPlayer) {

bool bWin = false;

for (int i = 0; i < 3; i ++) {
if (plField [i][0] == plField [i][1] && plField [i][1] == plField [i][2] && plField [i][0] == lPlayer)
bWin = true;
if (plField [0][i] == plField [1][i] && plField [1][i] == plField [2][i] && plField [0][i] == lPlayer)
bWin = true;
}

if (plField [0][0] == plField [1][1] && plField [1][1] == plField [2][2] && plField [1][1] == lPlayer)
bWin = true;
if (plField [2][0] == plField [1][1] && plField [1][1] == plField [0][2] && plField [1][1] == lPlayer)
bWin = true;

return bWin;
}

long CComputer::HowManyTurns () {

long lTurns = 0;

for (int i = 0; i < 3; i ++) {
for (int j = 0; j < 3; j ++) {
if (plField[i][j] > 0) {
lTurns ++;
}
}
}

return lTurns;
}

long CComputer::LineScan (long lPlayer, long& lX, long& lY) {

long lCounter = 0;
lX = - 100;
lY = - 100;

// Horizontal lines
for (int i = 0; i < 3; i ++) {

if (plField [1][i] == lPlayer && plField [0][i] == lPlayer && plField[2][i] == 0) {
lX = 2;
lY = i;
lCounter ++;
}

if (plField [0][i] == lPlayer && plField [2][i] == lPlayer && plField[1][i] == 0) {
lX = 1;
lY = i;
lCounter ++;
}

if (plField [1][i] == lPlayer && plField [2][i] == lPlayer && plField[0][i] == 0) {
lX = 0;
lY = i;
lCounter ++;
}
}

// Vertical lines
for (i = 0; i < 3; i ++) {

if (plField [i][1] == lPlayer && plField [i][0] == lPlayer && plField[i][2] == 0) {
lX = i;
lY = 2;
lCounter ++;
}

if (plField [i][0] == lPlayer && plField [i][2] == lPlayer && plField[i][1] == 0) {
lX = i;
lY = 1;
lCounter ++;
}

if (plField [i][1] == lPlayer && plField [i][2] == lPlayer && plField[i][0] == 0) {
lX = i;
lY = 0;
lCounter ++;
}
}

// Diagonals
if (plField [1][1] == lPlayer && plField [0][0] == lPlayer && plField[2][2] == 0) {
lX = 2;
lY = 2;
lCounter ++;
}

if (plField [1][1] == lPlayer && plField [2][0] == lPlayer && plField[0][2] == 0) {
lX = 0;
lY = 2;
lCounter ++;
}

if (plField [1][1] == lPlayer && plField [0][2] == lPlayer && plField[2][0] == 0) {
lX = 2;
lY = 0;
lCounter ++;
}

if (plField [1][1] == lPlayer && plField [2][2] == lPlayer && plField[0][0] == 0) {
lX = 0;
lY = 0;
lCounter ++;
}

if (plField [0][0] == lPlayer && plField [2][2] == lPlayer && plField[1][1] == 0) {
lX = 1;
lY = 1;
lCounter ++;
}
if (plField [0][2] == lPlayer && plField [2][0] == lPlayer && plField[1][1] == 0) {
lX = 1;
lY = 1;
lCounter ++;
}

return lCounter;
}

HRESULT CComputer::LoadArray (long lGameID) {

HRESULT hr = S_OK;

IObjectContext* pObjectContext = NULL;

ISharedPropertyGroupManager* spmMgr = NULL;
ISharedPropertyGroup* spmGroup = NULL;
ISharedProperty* spmPropField[3][3];

// Get context
hr = GetObjectContext(&pObjectContext);

// Create the SharedPropertyGroupManager
hr = pObjectContext->CreateInstance (CLSID_SharedPropertyGroupManager, IID_ISharedPropertyGroupManager, (void**)&spmMgr);

// Create the SharedPropertyGroup
LONG lIsolationMode = LockMethod;
LONG lReleaseMode = Process;
VARIANT_BOOL bExists = VARIANT_FALSE;
hr = spmMgr->CreatePropertyGroup (L"TicTacToe", &lIsolationMode, &lReleaseMode, &bExists, &spmGroup);

// Load the field SharedProperties
TCHAR szBuf [512];
BSTR bstrField;
CComVariant vField;

for (long i = 0; i < 3; i ++ ) {
for (long j = 0; j < 3; j ++) {

wsprintf (szBuf, _T("%dField%d%d"), lGameID, i, j);
bstrField = TCHAR2BSTR (szBuf);
hr = spmGroup->CreateProperty (bstrField, &bExists, &spmPropField[i][j]);
::SysFreeString (bstrField);

hr = spmPropField[i][j]->get_Value (&vField);
plField[i][j] = vField.lVal;
}
}

if (pObjectContext)
pObjectContext->Release();

if (spmMgr)
spmMgr->Release();

if (spmGroup)
spmGroup->Release();

for (i = 0; i < 3; i ++) {
for (long j = 0; j < 3; j ++) {
if (spmPropField[i][j]) {
spmPropField[i][j]->Release();
}
}
}

return hr;
}

HRESULT CComputer::SaveArray (long lGameID) {

HRESULT hr = S_OK;

IObjectContext* pObjectContext = NULL;

ISharedPropertyGroupManager* spmMgr = NULL;
ISharedPropertyGroup* spmGroup = NULL;
ISharedProperty* spmPropField[3][3];

// Get context
hr = GetObjectContext(&pObjectContext);

// Create the SharedPropertyGroupManager
hr = pObjectContext->CreateInstance (CLSID_SharedPropertyGroupManager, IID_ISharedPropertyGroupManager, (void**)&spmMgr);

// Create the SharedPropertyGroup
LONG lIsolationMode = LockMethod;
LONG lReleaseMode = Process;
VARIANT_BOOL bExists = VARIANT_FALSE;
hr = spmMgr->CreatePropertyGroup (L"TicTacToe", &lIsolationMode, &lReleaseMode, &bExists, &spmGroup);

// Save the field SharedProperties
TCHAR szBuf [512];
BSTR bstrField;
CComVariant vField;
vField.vt = VT_I4;

for (long i = 0; i < 3; i ++ ) {
for (long j = 0; j < 3; j ++) {

wsprintf (szBuf, _T("%dField%d%d"), lGameID, i, j);
bstrField = TCHAR2BSTR (szBuf);
hr = spmGroup->CreateProperty (bstrField, &bExists, &spmPropField[i][j]);
::SysFreeString (bstrField);

vField.lVal = plField[i][j];
hr = spmPropField[i][j]->put_Value (vField);
}
}

if (pObjectContext)
pObjectContext->Release();

if (spmMgr)
spmMgr->Release();

if (spmGroup)
spmGroup->Release();

for (i = 0; i < 3; i ++) {
for (long j = 0; j < 3; j ++) {
if (spmPropField[i][j]) {
spmPropField[i][j]->Release();
}
}
}

return hr;
}


COMPUTER.H

// Filename: Computer.h
//
// Description:  Definition of CComputer
//
// This file is provided as part of the Microsoft Transaction Server Samples
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
// WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
// PURPOSE.
//
// Copyright (C) 1997 Microsoft Corporation, All rights reserved

#if !defined(AFX_COMPUTER_H__AA22C9A7_FD72_11D0_B5DC_00C04FB957D8__INCLUDED_)
#define AFX_COMPUTER_H__AA22C9A7_FD72_11D0_B5DC_00C04FB957D8__INCLUDED_

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

#include "resource.h"       // main symbols

/////////////////////////////////////////////////////////////////////////////
// CComputer

class CComputer :
public CComDualImpl<IComputer, &IID_IComputer, &LIBID_TSERVERLib>,
public ISupportErrorInfo,
public CComObjectRoot,
public CComCoClass<CComputer,&CLSID_Computer>
{
public:
CComputer() {}

private:
long plField [3][3];

float CComputer::Evaluate (long lX, long lY);
long CComputer::LineScan (long lPlayer, long& lX, long& lY);
bool CComputer::IsWin (long lPlayer);
long CComputer::HowManyTurns ();
void CComputer::GetMove (long lGameID, VARIANT_BOOL bEasy, long& lX, long& lY);
HRESULT CComputer::LoadArray (long lGameID);
HRESULT CComputer::SaveArray (long lGameID);
void inline CComputer::InitArray () {
for (int i = 0; i < 3; i ++)
for (int j = 0; j < 3; j ++)
plField[i][j] = 0;
}

BEGIN_COM_MAP(CComputer)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IComputer)
COM_INTERFACE_ENTRY(ISupportErrorInfo)
END_COM_MAP()
//DECLARE_NOT_AGGREGATABLE(CComputer)
// Remove the comment from the line above if you don't want your object to
// support aggregation.

DECLARE_REGISTRY_RESOURCEID(IDR_Computer)
// ISupportsErrorInfo
STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);

// IComputer
public:
STDMETHODIMP AddNewGame (IN VARIANT_BOOL bEasy, OUT VARIANT* pvGameID, OUT VARIANT* pvOrder, OUT VARIANT* pvX, OUT VARIANT* pvY);
STDMETHODIMP NewMove (IN long lGameID, IN VARIANT_BOOL bEasy, IN long lX, IN long lY, OUT VARIANT* pvMyX, OUT VARIANT* pvMyY, OUT VARIANT* pvWin);

};

#endif // !defined(AFX_COMPUTER_H__AA22C9A7_FD72_11D0_B5DC_00C04FB957D8__INCLUDED_)


HUMAN.CPP

// Filename: Human.cpp
//
// Description:  Implementation of CHuman
// This file contains the code that handles the interaction between two
// human players.
//
// This file is provided as part of the Microsoft Transaction Server Samples
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
// WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
// PURPOSE.
//
// Copyright (C) 1997 Microsoft Corporation, All rights reserved

#include "stdafx.h"
#include "tServer.h"
#include "Human.h"

#include <mtx.h>
#include <mtxspm.h>

STDMETHODIMP CHuman::InterfaceSupportsErrorInfo(REFIID riid)
{
static const IID* arr[] =
{
&IID_IHuman,
};

for (int i=0;i<sizeof(arr)/sizeof(arr[0]);i++)
{
if (InlineIsEqualGUID(*arr[i],riid))
return S_OK;
}
return S_FALSE;
}

STDMETHODIMP CHuman::EnterNewGame (IN BSTR bstrLocalName, OUT VARIANT* pvGameID, OUT VARIANT* pvName,
   OUT VARIANT* pvOrder) {

HRESULT hr = S_OK;

IObjectContext* pObjectContext = NULL;

ISharedPropertyGroupManager* spmMgr = NULL;
ISharedPropertyGroup* spmGroup = NULL;

ISharedProperty* spmPropCounter = NULL;
ISharedProperty* spmPropFirstName = NULL;
ISharedProperty* spmPropSecondName = NULL;
ISharedProperty* spmPropState = NULL;

pvGameID->vt = VT_I4;
pvName->vt = VT_BSTR;
pvOrder->vt = VT_I4;

long lGameID = 0;
long lOrder = 0;

try {

// Get the object context
THROW_ERR ( hr = GetObjectContext (&pObjectContext) );
if (pObjectContext == NULL) {
THROW_ERR ( E_FAIL );
}

// Create the SharedPropertyGroupManager
THROW_ERR ( pObjectContext->CreateInstance (CLSID_SharedPropertyGroupManager, IID_ISharedPropertyGroupManager, (void**)&spmMgr) );

// Create the SharedPropertyGroup
LONG lIsolationMode = LockMethod;
LONG lReleaseMode = Process;
VARIANT_BOOL bExists = VARIANT_FALSE;
THROW_ERR ( spmMgr->CreatePropertyGroup (L"TicTacToe", &lIsolationMode, &lReleaseMode, &bExists, &spmGroup) );

// Create SharedProperties
CComVariant vCounter;

THROW_ERR ( spmGroup->CreateProperty (L"Counter", &bExists, &spmPropCounter) );
THROW_ERR ( spmPropCounter->get_Value (&vCounter) );

CComVariant vSecondName;
CComVariant vState;

TCHAR szBuf [512];
BSTR bstrSecondName;
BSTR bstrFirstName;
BSTR bstrState;

// Find first open two-human game
if (vCounter.lVal == 0) {
vCounter.lVal = 1;
} else {
long lScan = 1;
bool bFlag = false;
while (lScan < vCounter.lVal && !bFlag) {

wsprintf (szBuf, _T("%dState"), lScan);
bstrState = TCHAR2BSTR (szBuf);
THROW_ERR ( spmGroup->CreateProperty (bstrState, &bExists, &spmPropState) );
::SysFreeString (bstrState);

THROW_ERR ( spmPropState->get_Value (&vState) );

if (vState.lVal < 2) {

// Found a game
bFlag = true;
} else
lScan ++;

THROW_ERR ( spmPropState->Release() );
}

vCounter.lVal = lGameID = lScan;
}

wsprintf (szBuf, _T("%dSecondName"), vCounter.lVal);
bstrSecondName = TCHAR2BSTR (szBuf);
wsprintf (szBuf, _T("%dState"), vCounter.lVal);
bstrState = TCHAR2BSTR (szBuf);
wsprintf (szBuf, _T("%dFirstName"), vCounter.lVal);
bstrFirstName = TCHAR2BSTR (szBuf);

THROW_ERR ( spmGroup->CreateProperty (bstrFirstName, &bExists, &spmPropFirstName) );
THROW_ERR ( spmGroup->CreateProperty (bstrSecondName, &bExists, &spmPropSecondName) );
THROW_ERR ( spmGroup->CreateProperty (bstrState, &bExists, &spmPropState) );

::SysFreeString (bstrFirstName);
::SysFreeString (bstrSecondName);
::SysFreeString (bstrState);

// Get current state
THROW_ERR ( spmPropState->get_Value (&vState) );

// State table
// ===========
// 0 - No one in game
// 1 - One player lurking
// 2 - Second player joined
// 3 - First player's turn
// 4 - Second player's turn

// Is this the first time this property has been accessed?
if (!bExists)
vState.lVal = 0;

// Is there someone lurking already?
if (vState.lVal == 1) {

CComVariant vFirstName;

// Get opponent's name
THROW_ERR ( spmPropFirstName->get_Value (&vFirstName) );

// Submit own name
CComVariant vSecondName2;
vSecondName2.vt = VT_BSTR;

vSecondName2.bstrVal = ::SysAllocString (bstrLocalName);
THROW_ERR ( spmPropSecondName->put_Value (vSecondName2) );
::SysFreeString (vSecondName2.bstrVal);

// Update state
vState.lVal = 2;
THROW_ERR ( spmPropState->put_Value (vState) );

// Assign gameID
lGameID = vCounter.lVal;

// Initialize the game screen array
InitArray();
THROW_ERR ( SaveArray(lGameID) );

// Prepare return values
pvName->bstrVal = ::SysAllocString (vFirstName.bstrVal);
pvOrder->lVal = 2;
pvGameID->lVal = lGameID;

} else {

CComVariant vFirstName;
vFirstName.vt = VT_BSTR;

// Update first name
vFirstName.bstrVal = ::SysAllocString (bstrLocalName);
THROW_ERR ( spmPropFirstName->put_Value (vFirstName) );
::SysFreeString (vFirstName.bstrVal);

// Update counter for next game
vCounter.lVal = vCounter.lVal + 1;
THROW_ERR ( spmPropCounter->put_Value (vCounter) );

// Update state
vState.lVal = 1;
THROW_ERR ( spmPropState->put_Value (vState) );

// Assign gameID
lGameID = vCounter.lVal - 1;

// Prepare return values
pvName->bstrVal = ::SysAllocString (L"No one");
pvOrder->lVal = 1;
pvGameID->lVal = lGameID;
}

// We're finished and happy
pObjectContext->SetComplete();

} catch (HRESULT hr) {

// Create an ErrorInfo object
ICreateErrorInfo* pCreateErrInfo = NULL;
IErrorInfo* pErrInfo = NULL;

CreateErrorInfo (&pCreateErrInfo);
pCreateErrInfo->QueryInterface (IID_IErrorInfo, (LPVOID FAR*) &pErrInfo);

// Fill in error information
TCHAR szErr [512];
wsprintf (szErr, _T("Error %d occurred in Human::EnterNewGame()"), hr);
BSTR bstrDesc = TCHAR2BSTR (szErr);

pCreateErrInfo->SetGUID (IID_IComputer);
pCreateErrInfo->SetSource (L"Computer");
pCreateErrInfo->SetDescription (bstrDesc);
::SysFreeString (bstrDesc);

// Confirm error information
SetErrorInfo (0, pErrInfo);

// Clean up the error objects
if (pCreateErrInfo)
pCreateErrInfo->Release();

if (pErrInfo)
pErrInfo->Release();

// Indicate our unhappiness
if (pObjectContext)
pObjectContext->SetAbort();

hr = E_FAIL;
}

if (pObjectContext)
pObjectContext->Release();

if (spmMgr)
spmMgr->Release();

if (spmGroup)
spmGroup->Release();

if (spmPropCounter)
spmPropCounter->Release();

if (spmPropFirstName)
spmPropFirstName->Release();

if (spmPropSecondName)
spmPropSecondName->Release();

if (spmPropState)
spmPropState->Release();

return hr;
}

STDMETHODIMP CHuman::NewMove (IN long lGameID, IN long lOrder, IN long lX, IN long lY, OUT VARIANT* pvWin) {

// Win protocol
// ============
// 0  -> move did not end game
// 1  -> player won
// -1 -> remote player won (won't occur in this function)
// 2  -> player's move tied the game
// -2 -> remote player's move tied the game (won't occur in this function)

HRESULT hr = S_OK;

IObjectContext* pObjectContext = NULL;

ISharedPropertyGroupManager* spmMgr = NULL;
ISharedPropertyGroup* spmGroup = NULL;

ISharedProperty* spmPropState = NULL;
ISharedProperty* spmPropX = NULL;
ISharedProperty* spmPropY = NULL;
ISharedProperty* spmPropWin = NULL;

pvWin->vt = VT_I4;

long lWin = 0;

try {

// Get the object context
THROW_ERR ( hr = GetObjectContext(&pObjectContext) );
if (pObjectContext == NULL) {
THROW_ERR ( E_FAIL );
}

// Create the SharedPropertyGroupManager
THROW_ERR ( pObjectContext->CreateInstance (CLSID_SharedPropertyGroupManager,
IID_ISharedPropertyGroupManager, (void**)&spmMgr) );

// Create the SharedPropertyGroup
LONG lIsolationMode = LockMethod;
LONG lReleaseMode = Process;
VARIANT_BOOL bExists = VARIANT_FALSE;
THROW_ERR ( spmMgr->CreatePropertyGroup (L"TicTacToe", &lIsolationMode, &lReleaseMode,
&bExists, &spmGroup) );

// Create SharedProperties
BSTR bstrState;
BSTR bstrX;
BSTR bstrY;
BSTR bstrWin;
TCHAR szBuf[512];
wsprintf (szBuf, _T("%dState"), lGameID);
bstrState = TCHAR2BSTR (szBuf);
wsprintf (szBuf, _T("%dX"), lGameID);
bstrX = TCHAR2BSTR (szBuf);
wsprintf (szBuf, _T("%dY"), lGameID);
bstrY = TCHAR2BSTR (szBuf);
wsprintf (szBuf, _T("%dWin"), lGameID);
bstrWin = TCHAR2BSTR (szBuf);

THROW_ERR ( spmGroup->CreateProperty (bstrState, &bExists, &spmPropState) );
THROW_ERR ( spmGroup->CreateProperty (bstrX, &bExists, &spmPropX) );
THROW_ERR ( spmGroup->CreateProperty (bstrY, &bExists, &spmPropY) );
THROW_ERR ( spmGroup->CreateProperty (bstrWin, &bExists, &spmPropWin) );

::SysFreeString (bstrState);
::SysFreeString (bstrX);
::SysFreeString (bstrY);
::SysFreeString (bstrWin);

CComVariant vState;
CComVariant vX;
CComVariant vY;
CComVariant vWin;
vState.vt = VT_I4;
vX.vt = VT_I4;
vY.vt = VT_I4;
vWin.vt = VT_I4;

// Load screen data
THROW_ERR ( LoadArray(lGameID) );

// Save player's move to local storage and to Spam
plField[lX][lY] = lOrder;

vX.lVal = lX;
vY.lVal = lY;
THROW_ERR ( spmPropX->put_Value (vX) );
THROW_ERR ( spmPropY->put_Value (vY) );

// Submit new state
if (lOrder == 1)
vState.lVal = 4;
else
vState.lVal = 3;

THROW_ERR ( spmPropState->put_Value (vState) );

// Commit move to global storage
THROW_ERR ( SaveArray (lGameID) );

// Check for win
if (IsWin (lOrder)) {

// Player wins
lWin = 1;

// Other player loses
vWin.lVal = - 1;
THROW_ERR ( spmPropWin->put_Value (vWin) );
}

else if (HowManyTurns() == 9) {

// Player ties on his move
lWin = 2;

// Other player ties on opponent's move
vWin.lVal = - 2;
THROW_ERR ( spmPropWin->put_Value (vWin) );
}

// Prepare return values
pvWin->lVal = lWin;

// We're happy and finished
pObjectContext->SetComplete();

} catch (HRESULT hr) {

// Create an ErrorInfo object
ICreateErrorInfo* pCreateErrInfo = NULL;
IErrorInfo* pErrInfo = NULL;

CreateErrorInfo (&pCreateErrInfo);
pCreateErrInfo->QueryInterface (IID_IErrorInfo, (LPVOID FAR*) &pErrInfo);

// Fill in error information
TCHAR szErr [512];
wsprintf (szErr, _T("Error %d occurred in Human::NewMove()"), hr);
BSTR bstrDesc = TCHAR2BSTR (szErr);
pCreateErrInfo->SetGUID (IID_IComputer);
pCreateErrInfo->SetSource (L"Computer");
pCreateErrInfo->SetDescription (bstrDesc);
::SysFreeString (bstrDesc);

// Confirm error information
SetErrorInfo (0, pErrInfo);

// Clean up the error objects
if (pCreateErrInfo)
pCreateErrInfo->Release();

if (pErrInfo)
pErrInfo->Release();

// Indicate our unhappiness
if (pObjectContext)
pObjectContext->SetAbort();

hr = E_FAIL;
}

if (pObjectContext)
pObjectContext->Release();

if (spmMgr)
spmMgr->Release();

if (spmGroup)
spmGroup->Release();

if (spmPropState)
spmPropState->Release();

if (spmPropX)
spmPropX->Release();

if (spmPropY)
spmPropY->Release();

if (spmPropWin)
spmPropWin->Release();

return hr;

}

STDMETHODIMP CHuman::GetNewMove (IN long lGameID, IN long lOrder, OUT VARIANT* pvName,
 OUT VARIANT* pvFoeX, OUT VARIANT* pvFoeY, OUT VARIANT* pvWin,
 OUT VARIANT* pvUpdate) {

// Win protocol
// ============
// 0  -> move did not end game
// 1  -> player won (won't occur in this function)
// -1 -> remote player won
// 2  -> player's move tied the game (won't occur in this function)
// -2 -> remote player's move tied the game

HRESULT hr = S_OK;

IObjectContext* pObjectContext = NULL;

ISharedPropertyGroupManager* spmMgr = NULL;
ISharedPropertyGroup* spmGroup = NULL;
ISharedProperty* spmPropState = NULL;
ISharedProperty* spmPropX = NULL;
ISharedProperty* spmPropY = NULL;
ISharedProperty* spmPropWin = NULL;
ISharedProperty* spmPropSecondName = NULL;

pvName->vt = VT_BSTR;
pvFoeX->vt = VT_I4;
pvFoeY->vt = VT_I4;
pvWin->vt = VT_I4;
pvUpdate->vt = VT_I4;

long lWin = 0;
long lUpdate = 0;
long lX = 0;
long lY = 0;

try {

// Get the object context
THROW_ERR ( hr = GetObjectContext(&pObjectContext) );
if (pObjectContext == NULL) {
THROW_ERR ( E_FAIL );
}

// Create the SharedPropertyGroupManager
THROW_ERR ( pObjectContext->CreateInstance (CLSID_SharedPropertyGroupManager,
IID_ISharedPropertyGroupManager, (void**)&spmMgr) );

// Create the SharedPropertyGroup
LONG lIsolationMode = LockMethod;
LONG lReleaseMode = Process;
VARIANT_BOOL bExists = VARIANT_FALSE;
THROW_ERR ( spmMgr->CreatePropertyGroup (L"TicTacToe", &lIsolationMode, &lReleaseMode,
&bExists, &spmGroup) );

// Create SharedProperties
BSTR bstrState;
BSTR bstrX;
BSTR bstrY;
BSTR bstrWin;
BSTR bstrSecondName;
TCHAR szBuf[512];
wsprintf (szBuf, _T("%dState"), lGameID);
bstrState = TCHAR2BSTR (szBuf);
wsprintf (szBuf, _T("%dX"), lGameID);
bstrX = TCHAR2BSTR (szBuf);
wsprintf (szBuf, _T("%dY"), lGameID);
bstrY = TCHAR2BSTR (szBuf);
wsprintf (szBuf, _T("%dWin"), lGameID);
bstrWin = TCHAR2BSTR (szBuf);
wsprintf (szBuf, _T("%dSecondName"), lGameID);
bstrSecondName = TCHAR2BSTR (szBuf);

THROW_ERR ( spmGroup->CreateProperty (bstrState, &bExists, &spmPropState) );
THROW_ERR ( spmGroup->CreateProperty (bstrX, &bExists, &spmPropX) );
THROW_ERR ( spmGroup->CreateProperty (bstrY, &bExists, &spmPropY) );
THROW_ERR ( spmGroup->CreateProperty (bstrWin, &bExists, &spmPropWin) );
THROW_ERR ( spmGroup->CreateProperty (bstrSecondName, &bExists, &spmPropSecondName) );

::SysFreeString (bstrState);
::SysFreeString (bstrX);
::SysFreeString (bstrY);
::SysFreeString (bstrWin);
::SysFreeString (bstrSecondName);

// Get state and move parameters
CComVariant vState;
CComVariant vX;
CComVariant vY;
CComVariant vWin;
CComVariant vSecondName;
THROW_ERR ( spmPropState->get_Value (&vState) );
THROW_ERR ( spmPropX->get_Value (&vX) );
THROW_ERR ( spmPropY->get_Value (&vY) );
THROW_ERR ( spmPropWin->get_Value (&vWin) );
THROW_ERR ( spmPropSecondName->get_Value (&vSecondName) );

switch (vState.lVal) {

case (0):

// Shouldn't happen
THROW_ERR ( E_FAIL);
break;

case (1):

// Still waiting for next player to join
lUpdate = 0;
break;

case (2):

// Other player just joined
if (lOrder == 1) {
lUpdate = 2;
pvName->bstrVal = ::SysAllocString (vSecondName.bstrVal);

// It's now the first player's turn
vState.lVal = 3;
THROW_ERR ( spmPropState->put_Value (vState) );
}

break;

case (3):
case (4):

long lTemp;
if (vState.lVal == 3)
lTemp = 1;
else
lTemp = 2;

// Has other player moved?
if (lOrder == lTemp) {

// Other player has moved
lX = vX.lVal;
lY = vY.lVal;
lWin = vWin.lVal;
lUpdate = 1;

} else {

// Waiting for other player to move
lUpdate = 0;
}

break;

default:

// Shouldn't happen
THROW_ERR ( E_FAIL );
break;
}

// Prepare return values
pvFoeX->lVal = lX;
pvFoeY->lVal = lY;
pvWin->lVal = lWin;
pvUpdate->lVal = lUpdate;

// We are finished and happy
pObjectContext->SetComplete();

} catch (HRESULT hr) {

// Create an ErrorInfo object
ICreateErrorInfo* pCreateErrInfo = NULL;
IErrorInfo* pErrInfo = NULL;

CreateErrorInfo (&pCreateErrInfo);
pCreateErrInfo->QueryInterface (IID_IErrorInfo, (LPVOID FAR*) &pErrInfo);

// Fill in error information
TCHAR szErr [512];
wsprintf (szErr, _T("Error %d occurred in Human::GetNewMove()"), hr);
BSTR bstrDesc = TCHAR2BSTR (szErr);
pCreateErrInfo->SetGUID (IID_IComputer);
pCreateErrInfo->SetSource (L"Computer");
pCreateErrInfo->SetDescription (bstrDesc);
::SysFreeString (bstrDesc);

// Confirm error information
SetErrorInfo (0, pErrInfo);

// Clean up the error objects
if (pCreateErrInfo)
pCreateErrInfo->Release();

if (pErrInfo)
pErrInfo->Release();

// Indicate our unhappiness
if (pObjectContext)
pObjectContext->SetAbort();

hr = E_FAIL;
}

if (pObjectContext)
pObjectContext->Release();

if (spmMgr)
spmMgr->Release();

if (spmGroup)
spmGroup->Release();

if (spmPropState)
spmPropState->Release();

if (spmPropX)
spmPropX->Release();

if (spmPropY)
spmPropY->Release();

if (spmPropWin)
spmPropWin->Release();

if (spmPropSecondName)
spmPropSecondName->Release();

return hr;
}

bool CHuman::IsWin (long lPlayer) {

bool bWin = false;

for (int i = 0; i < 3; i ++) {
if (plField [i][0] == plField [i][1] && plField [i][1] == plField [i][2] && plField [i][0] == lPlayer)
bWin = true;
if (plField [0][i] == plField [1][i] && plField [1][i] == plField [2][i] && plField [0][i] == lPlayer)
bWin = true;
}

if (plField [0][0] == plField [1][1] && plField [1][1] == plField [2][2] && plField [1][1] == lPlayer)
bWin = true;
if (plField [2][0] == plField [1][1] && plField [1][1] == plField [0][2] && plField [1][1] == lPlayer)
bWin = true;

return bWin;
}

long CHuman::HowManyTurns () {

long lTurns = 0;

for (int i = 0; i < 3; i ++) {
for (int j = 0; j < 3; j ++) {
if (plField[i][j] > 0) {
lTurns ++;
}
}
}

return lTurns;
}

HRESULT CHuman::LoadArray (long lGameID) {

HRESULT hr = S_OK;

IObjectContext* pObjectContext = NULL;

ISharedPropertyGroupManager* spmMgr = NULL;
ISharedPropertyGroup* spmGroup = NULL;
ISharedProperty* spmPropField[3][3];

// Get context
hr = GetObjectContext(&pObjectContext);

// Create the SharedPropertyGroupManager
hr = pObjectContext->CreateInstance (CLSID_SharedPropertyGroupManager, IID_ISharedPropertyGroupManager, (void**)&spmMgr);

// Create the SharedPropertyGroup
LONG lIsolationMode = LockMethod;
LONG lReleaseMode = Process;
VARIANT_BOOL bExists = VARIANT_FALSE;
hr = spmMgr->CreatePropertyGroup (L"TicTacToe", &lIsolationMode, &lReleaseMode, &bExists, &spmGroup);

// Load the field SharedProperties
TCHAR szBuf [512];
BSTR bstrField;
CComVariant vField;

for (long i = 0; i < 3; i ++ ) {
for (long j = 0; j < 3; j ++) {

wsprintf (szBuf, _T("%dField%d%d"), lGameID, i, j);
bstrField = TCHAR2BSTR (szBuf);
hr = spmGroup->CreateProperty (bstrField, &bExists, &spmPropField[i][j]);
::SysFreeString (bstrField);

hr = spmPropField[i][j]->get_Value (&vField);
plField[i][j] = vField.lVal;
}
}

if (pObjectContext)
pObjectContext->Release();

if (spmMgr)
spmMgr->Release();

if (spmGroup)
spmGroup->Release();

for (i = 0; i < 3; i ++) {
for (long j = 0; j < 3; j ++) {
if (spmPropField[i][j]) {
spmPropField[i][j]->Release();
}
}
}

return hr;
}

HRESULT CHuman::SaveArray (long lGameID) {

HRESULT hr = S_OK;

IObjectContext* pObjectContext = NULL;

ISharedPropertyGroupManager* spmMgr = NULL;
ISharedPropertyGroup* spmGroup = NULL;
ISharedProperty* spmPropField[3][3];

// Get context
hr = GetObjectContext(&pObjectContext);

// Create the SharedPropertyGroupManager
hr = pObjectContext->CreateInstance (CLSID_SharedPropertyGroupManager, IID_ISharedPropertyGroupManager, (void**)&spmMgr);

// Create the SharedPropertyGroup
LONG lIsolationMode = LockMethod;
LONG lReleaseMode = Process;
VARIANT_BOOL bExists = VARIANT_FALSE;
hr = spmMgr->CreatePropertyGroup (L"TicTacToe", &lIsolationMode, &lReleaseMode, &bExists, &spmGroup);

// Save the field shared properties
TCHAR szBuf [512];
BSTR bstrField;
CComVariant vField;
vField.vt = VT_I4;

for (long i = 0; i < 3; i ++ ) {
for (long j = 0; j < 3; j ++) {

wsprintf (szBuf, _T("%dField%d%d"), lGameID, i, j);
bstrField = TCHAR2BSTR (szBuf);
hr = spmGroup->CreateProperty (bstrField, &bExists, &spmPropField[i][j]);
::SysFreeString (bstrField);

vField.lVal = plField[i][j];
hr = spmPropField[i][j]->put_Value (vField);
}
}

if (pObjectContext)
pObjectContext->Release();

if (spmMgr)
spmMgr->Release();

if (spmGroup)
spmGroup->Release();

for (i = 0; i < 3; i ++) {
for (long j = 0; j < 3; j ++) {
if (spmPropField[i][j]) {
spmPropField[i][j]->Release();
}
}
}

return hr;
}


HUMAN.H

// Filename: Human.h
//
// Description:  Definition of CHuman
//
// This file is provided as part of the Microsoft Transaction Server Samples
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
// WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
// PURPOSE.
//
// Copyright (C) 1997 Microsoft Corporation, All rights reserved

#if !defined(AFX_HUMAN_H__CAAD7FA2_F22D_11D0_B5CB_00C04FB957D8__INCLUDED_)
#define AFX_HUMAN_H__CAAD7FA2_F22D_11D0_B5CB_00C04FB957D8__INCLUDED_

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

#include "resource.h"       // main symbols

/////////////////////////////////////////////////////////////////////////////
// CHuman

class CHuman :
public CComDualImpl<IHuman, &IID_IHuman, &LIBID_TSERVERLib>,
public ISupportErrorInfo,
public CComObjectRoot,
public CComCoClass<CHuman,&CLSID_Human>
{
public:
CHuman() {}

private:
long plField [3][3];

void inline CHuman::InitArray () {
for (int i = 0; i < 3; i ++)
for (int j = 0; j < 3; j ++)
plField[i][j] = 0;
}

HRESULT CHuman::LoadArray (long lGameID);
HRESULT CHuman::SaveArray (long lGameID);
long CHuman::HowManyTurns ();
bool CHuman::IsWin (long lPlayer);

BEGIN_COM_MAP(CHuman)
COM_INTERFACE_ENTRY(IDispatch)
COM_INTERFACE_ENTRY(IHuman)
COM_INTERFACE_ENTRY(ISupportErrorInfo)
END_COM_MAP()
//DECLARE_NOT_AGGREGATABLE(CHuman)
// Remove the comment from the line above if you don't want your object to
// support aggregation.

DECLARE_REGISTRY_RESOURCEID(IDR_Human)
// ISupportsErrorInfo
STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);

// IHuman

public:
STDMETHODIMP EnterNewGame (IN BSTR bstrLocalName, OUT VARIANT* pvGameID, OUT VARIANT* pvName, OUT VARIANT* pvOrder);
STDMETHODIMP NewMove (IN long lGameID, IN long lOrder, IN long lX, IN long lY, OUT VARIANT* pvWin);
STDMETHODIMP GetNewMove (IN long lGameID, IN long lOrder, OUT VARIANT* pvName, OUT VARIANT* pvFoeX, OUT VARIANT* pvFoeY, OUT VARIANT* pvWin, OUT VARIANT* pvUpdate);
};

#endif // !defined(AFX_HUMAN_H__CAAD7FA2_F22D_11D0_B5CB_00C04FB957D8__INCLUDED_)


RESOURCE.H

//{{NO_DEPENDENCIES}}
// Microsoft Developer Studio generated include file.
// Used by tServer.rc
//
#define IDS_PROJNAME                    100
#define IDS_COMPUTER_DESC               105
#define IDR_Computer                    106
#define IDS_HUMAN_DESC                  107
#define IDR_Human                       108

// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        201
#define _APS_NEXT_COMMAND_VALUE         32768
#define _APS_NEXT_CONTROL_VALUE         201
#define _APS_NEXT_SYMED_VALUE           109
#endif
#endif


STDAFX.H

// Filename: stdafx.h
//
// Description: Include file for standard system include files,
//or project specific include files that are used frequently,
//but are changed infrequently
//
// This file is provided as part of the Microsoft Transaction Server Samples
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
// WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
// PURPOSE.
//
// Copyright (C) 1997 Microsoft Corporation, All rights reserved

#define _ATL_STATIC_REGISTRY

#if !defined(AFX_STDAFX_H__AA22C995_FD72_11D0_B5DC_00C04FB957D8__INCLUDED_)
#define AFX_STDAFX_H__AA22C995_FD72_11D0_B5DC_00C04FB957D8__INCLUDED_

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

#define STRICT

#define _WIN32_WINNT 0x0400
#define _ATL_APARTMENT_THREADED

#include <atlbase.h>
//You may derive a class from CComModule and use it if you want to override
//something, but do not change the name of _Module
extern CComModule _Module;
#include <atlcom.h>

#define THROW_ERR(exp) if (FAILED (hr = (exp))) throw hr

inline BSTR TCHAR2BSTR (TCHAR* szBuf) {

WCHAR* wszBuf;

#ifndef UNICODE
wszBuf = new WCHAR [512];
MultiByteToWideChar( CP_ACP, 0, szBuf, -1, wszBuf, 512 );
#else
wszBuf = szBuf;
#endif

BSTR bstrNew = ::SysAllocString (wszBuf);

#ifndef UNICODE
delete [] wszBuf;
#endif

return bstrNew;
}

//{{AFX_INSERT_LOCATION}}
// Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_STDAFX_H__AA22C995_FD72_11D0_B5DC_00C04FB957D8__INCLUDED)


TSERVER.CPP

// Filename: tServer.cpp
//
// Description:  Implementation of DLL Exports
//
// This file is provided as part of the Microsoft Transaction Server Samples
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
// WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
// PURPOSE.
//
// Copyright (C) 1997 Microsoft Corporation, All rights reserved
//
// Note: Proxy/Stub Information
//To merge the proxy/stub code into the object DLL, add the file
//dlldatax.c to the project.  Make sure precompiled headers
//are turned off for this file, and add _MERGE_PROXYSTUB to the
//defines for the project.
//
//If you are not running WinNT4.0 or Win95 with DCOM, then you
//need to remove the following define from dlldatax.c
//#define _WIN32_WINNT 0x0400
//
//Further, if you are running MIDL without /Oicf switch, you also
//need to remove the following define from dlldatax.c.
//#define USE_STUBLESS_PROXY
//
//Modify the custom build rule for tServer.idl by adding the following
//files to the Outputs.
//tServer_p.c
//dlldata.c
//To build a separate proxy/stub DLL,
//run nmake -f tServerps.mk in the project directory.

#include "stdafx.h"
#include "resource.h"
#include "initguid.h"
#include "tServer.h"
// #include "dlldatax.h"

#include "tServer_i.c"
#include "Computer.h"
#include "Human.h"

#ifdef _MERGE_PROXYSTUB
extern "C" HINSTANCE hProxyDll;
#endif

CComModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
OBJECT_ENTRY(CLSID_Human, CHuman)
OBJECT_ENTRY(CLSID_Computer, CComputer)
END_OBJECT_MAP()

/////////////////////////////////////////////////////////////////////////////
// DLL Entry Point

extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
lpReserved;
#ifdef _MERGE_PROXYSTUB
if (!PrxDllMain(hInstance, dwReason, lpReserved))
return FALSE;
#endif
if (dwReason == DLL_PROCESS_ATTACH)
{
_Module.Init(ObjectMap, hInstance);
DisableThreadLibraryCalls(hInstance);
}
else if (dwReason == DLL_PROCESS_DETACH)
_Module.Term();
return TRUE;    // ok
}

/////////////////////////////////////////////////////////////////////////////
// Used to determine whether the DLL can be unloaded by OLE

STDAPI DllCanUnloadNow(void)
{
#ifdef _MERGE_PROXYSTUB
if (PrxDllCanUnloadNow() != S_OK)
return S_FALSE;
#endif
return (_Module.GetLockCount()==0) ? S_OK : S_FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// Returns a class factory to create an object of the requested type

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
#ifdef _MERGE_PROXYSTUB
if (PrxDllGetClassObject(rclsid, riid, ppv) == S_OK)
return S_OK;
#endif
return _Module.GetClassObject(rclsid, riid, ppv);
}

/////////////////////////////////////////////////////////////////////////////
// DllRegisterServer - Adds entries to the system registry

STDAPI DllRegisterServer(void)
{
#ifdef _MERGE_PROXYSTUB
HRESULT hRes = PrxDllRegisterServer();
if (FAILED(hRes))
return hRes;
#endif
// registers object, typelib and all interfaces in typelib
return _Module.RegisterServer(TRUE);
}

/////////////////////////////////////////////////////////////////////////////
// DllUnregisterServer - Removes entries from the system registry

STDAPI DllUnregisterServer(void)
{
#ifdef _MERGE_PROXYSTUB
PrxDllUnregisterServer();
#endif
_Module.UnregisterServer();
return S_OK;
}


TSERVER.IDL

// Filename: tServer.idl
//
// Description:  IDL source for tServer.dll
// This file will be processed by the MIDL tool to
// produce the type library (tServer.tlb) and marshalling code.
//
// This file is provided as part of the Microsoft Transaction Server Samples
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT
// WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND/OR FITNESS FOR A  PARTICULAR
// PURPOSE.
//
// Copyright (C) 1997 Microsoft Corporation, All rights reserved

import "oaidl.idl";
import "ocidl.idl";

[
object,
uuid(CAAD7F9A-F22D-11D0-B5CB-00C04FB957D8),
dual,
helpstring("IComputer Interface"),
pointer_default(unique)
]
interface IComputer : IDispatch
{
HRESULT AddNewGame ([in] VARIANT_BOOL bEasy, [out] VARIANT* pvGameID, [out] VARIANT* pvOrder, [out] VARIANT* pvX, [out] VARIANT* pvY);
HRESULT NewMove ([in] long lGameID, [in] VARIANT_BOOL bEasy, [in] long lX, [in] long lY, [out] VARIANT* pvMyX, [out] VARIANT* pvMyY, [out] VARIANT* pvWin);
};

[
object,
uuid(CAAD7FA0-F22D-11D0-B5CB-00C04FB957D8),
dual,
helpstring("IHuman Interface"),
pointer_default(unique)
]
interface IHuman : IDispatch
{
HRESULT EnterNewGame ([in] BSTR bstrLocalName, [out] VARIANT* pvGameID, [out] VARIANT* pvName, [out] VARIANT* pvOrder);
HRESULT NewMove ([in] long lGameID, [in] long lOrder, [in] long lX, [in] long lY, [out] VARIANT* pvWin);
HRESULT GetNewMove ([in] long lGameID, [in] long lOrder, [out] VARIANT* pvName, [out] VARIANT* pvFoeX, [out] VARIANT* pvFoeY, [out] VARIANT* pvWin, [out] VARIANT* pvUpdate);
};

[
uuid(CAAD7F8B-F22D-11D0-B5CB-00C04FB957D8),
version(1.0),
helpstring("tServer 1.0 Type Library")
]
library TSERVERLib
{
importlib("stdole32.tlb");
importlib("stdole2.tlb");

[
uuid(CAAD7F9B-F22D-11D0-B5CB-00C04FB957D8),
helpstring("Computer Class")
]
coclass Computer
{
[default] interface IComputer;
};

[
uuid(CAAD7FA1-F22D-11D0-B5CB-00C04FB957D8),
helpstring("Human Class")
]
coclass Human
{
[default] interface IHuman;
};

};


README.TXT

OLE Automation Controller and Server that Browses a Type Library

SUMMARY
=======

The BROWSE sample controls the automation objects of the BROWSEH (Browse
Helper) inproc server to browse a type library. BROWSE is a developer-
oriented type library browser.

In addition to displaying type library information, this sample shows how
to control an automation object by accessing its properties and methods.
BROWSE provides generic helper functions (CreateObject & Invoke) to control
any automation server. For more information on how to use these functions,
see the comments in INVHELP.CPP.

BROWSEH exposes a number of automation objects that correspond to
components of a type library. This allows an automation controller like
BROWSE to access the properties and methods of these objects to browse the
type library instead of directly using ITypeLib and ITypeInfo. So BROWSEH
is a type library browse helper. BROWSEH does not support vtable binding.
Controllers must use IDispatch to control this server.

MORE INFORMATION
================

The following information describes the BROWSE sample.

To Compile BROWSE and BROWSEH
-----------------------------

To compile an ANSI debug version of the sample for Windows NT and Windows 95,
use the following command:

   nmake

To compile a Unicode debug version of the sample for Windows NT only, use the
following command:

   nmake dev=win32 HOST=NT

To compile a 16-bit debug version of the sample and if your development
environment is Win 3.x, use the following command. If your development
environment is Win95 or NT, change the HOST appropriately.

   nmake dev=win16 HOST=DOS

To clean up between builds which use different options, use one of the
following commands:

   nmake clean
   nmake dev=win16 clean

See the makefile header comments for other make options.

If the build tools cannot find SDKUTIL.LIB, use the SAMPLES\COM\COMMON sample
to build SDKUTIL.LIB.

Visual C++ 2.x and above can use makefile as an external makefile
after making the following replacement in the makefile:

   !include <$(MSTOOLS)\samples\com\include\olesampl.mak>

with

   !include <win32.mak>

To Run BROWSE
-------------

 - Change browseh.reg to provide the full path of BROWSEH.DLL, BROWSEH.TLB
   and MYDISP.TLB..

 - Register browseh.reg in the registration database by double-clicking it.

 - Run BROWSE.EXE and use the menu to select a type library to browse.

BROWSE Files
------------

INVHELP.CPP and INVHELP.H contain two helper functions that are used to
create an automation object (using CreateObject) and to invoke a method
or property of that object (using Invoke). These functions are general
enough to be used by any Automation controller.

BROWSE.CPP uses the helper functions in invhelp.cpp to access the
properties and methods of the automation objects of BROWSEH.

MAKEFILE is the makefile for the project.

To run BROWSEH
--------------

 - Change BROWSEH.REG to provide the full path of BROWSEH.DLL and BROWSEH.TLB.

 - Register BROWSEH.REG in the registration database by double-clicking it.

 - Run the Automation controller BROWSE that uses BROWSEH to browse a type
   library OR Load and run the VB 4.0 VB.VBP project file which uses BROWSEH
   to output a C/C++ header file from the information in a type library. The
   header file can be used to create a C/C++ vtbl-binding client for the server
   to which the type library belongs, if the server supports vtbl-binding. After
   running the VB script, select the type library to be used as the input and
   specify the name of the header file to be output.

BROWSEH Files
-------------

MYDISP.ODL contains the description of a base class that implements
IDispatch. All automation objects in this server are derived from this
class. This is the implementation of IDispatch in the base class to be
inherited in the derived classes.

BROWSEH.ODL contains the description of the objects that BROWSEH exposes.

TLB.H is the header file generated by mktyplib.exe

MAKEFILE is the makefile for project.

MAIN.CPP contains code to create an inproc server.

Other files implement the BROWSEH automation server. For example,
COCLASS.CPP demonstrates how to use ITypeInfo and ITypeLib to browse a
coclass in a type library.

VB.VBP and VB.FRM are VB 4.0 files that uses BROWSEH to browse a type library
and to create a C/C++ header file from the browsed information.


BROWSE.H

#ifdef WIN32

#ifdef UNICODE
    #define FROM_OLE_STRING(str) str
    #define TO_OLE_STRING(str) str
#else
    #define FROM_OLE_STRING(str) ConvertToAnsi(str)
    char* ConvertToAnsi(OLECHAR FAR* szW);
    #define TO_OLE_STRING(str) ConvertToUnicode(str)
    OLECHAR* ConvertToUnicode(char FAR* szA);
    // Maximum length of string that can be converted between Ansi & Unicode
    #define STRCONVERT_MAXLEN 500
#endif

#else  // WIN16
  #define APIENTRY far pascal
  #define TCHAR char
  #define TEXT(sz) sz
  #define FROM_OLE_STRING(str) str
  #define TO_OLE_STRING(str) str
  #define LPTSTR LPSTR
  #define LPCTSTR LPCSTR

  // Windows NT defines the following in windowsx.h
  #define GET_WM_COMMAND_ID(w,l) (w)
  #define GET_WM_COMMAND_CMD(w,l) HIWORD(l)
  #define GET_WM_COMMAND_HWND(w,l) LOWORD(l)
#endif

// Function prototypes
int APIENTRY WinMain (HINSTANCE, HINSTANCE, LPSTR, int);
#ifdef WIN16
BOOL __export CALLBACK MainDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
#else
BOOL CALLBACK MainDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam);
#endif

typedef enum {
    TYPE_FUNCTION = 0,
    TYPE_PROPERTY = 1,
    TYPE_CONSTANT = 2,
    TYPE_PARAMETER = 3
} OBJTYPE;

class CBrowseApp
{
public:
    void Init(HWND hwndMain);
    void Cleanup();
    void ClearTypeLibStaticFields();
    void ClearTypeInfoStaticFields();
    void ClearElementStaticFields();
    void ClearParamStaticFields();
    HRESULT BrowseTypeLibrary();
    HRESULT ChangeTypeInfosSelection();
    HRESULT ChangeElementsSelection();
    HRESULT ChangeParametersSelection();
    void OpenElementHelpFile();
    void EmptyList(HWND hwndList);
    HRESULT LoadList(LPDISPATCH pdispItems, int nListID);
    HRESULT TypeToString(LPDISPATCH pdispTypeDesc, LPTSTR pszTypeName);
    void IDLFlagsToString(int n, LPTSTR psz);
    void CallConvToString(int n, LPTSTR psz);
    void FuncKindToString(int n, LPTSTR psz);
    void InvokeKindToString(int n, LPTSTR psz);
    void VariantToString(VARIANT v, LPTSTR psz);

private:
   HINSTANCE m_hinst;           // App's HINSTANCE.
   HWND m_hwndMain;             // App's main window which is a dialog..
   HFONT m_hfont;               // Non-bold font used for dialog fields.
   TCHAR m_szHelpFile[128];     // Help file used by type library being browsed.
   long m_lElemHelpCtx;         // Help context of TypeInfo element being browsed.
};


INVHELP.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  Automation Controller helper functions
**
**  invhelp.cpp
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

/*
 * This file contains helper functions that make it simple to create late-binding
 * automation controllers. CreateObject() will create an automation object and
 * Invoke() will call a property or method of the automation object.
 *
 * For example, the following VB code will control Microsoft Word:
 *
 *    Private Sub Form_Load()
 *    Dim wb As Object
 *    Set wb = CreateObject("Word.Basic")
 *    wb.AppShow
 *    wb.FileNewDefault
 *    wb.Insert "This is a test"
 *    wb.FileSaveAs "c:\sample.doc)"
 *    End Sub
 *
 * A C++ automation controller that does the same can be written as follows using
 * the helper functions:
 *
 *   LPDISPATCH pdispWord;
 *   CreateObject(OLESTR("Word.Basic"), &pdispWord);
 *   Invoke(pdispWord, DISPATCH_METHOD, NULL, NULL, NULL,
 *              OLESTR("AppShow"), NULL);
 *   Invoke(pdispWord, DISPATCH_METHOD, NULL, NULL, NULL,
 *              OLESTR("FileNewDefault"), NULL);
 *   Invoke(pdispWord, DISPATCH_METHOD, NULL, NULL, NULL,
 *              OLESTR("Insert"), TEXT("s"), (LPOLESTR)OLESTR("This is a test"));
 *   Invoke(pdispWord, DISPATCH_METHOD, NULL, NULL, NULL,
 *              OLESTR("FileSaveAs"), TEXT("s"), OLESTR("c:\\sample.doc"));
 *   pdispWord->Release();
 *
 * See the header comments of CreateObject and Invoke for documentation to use
 * these functions.
 *
 */

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include <stdarg.h>

#ifdef WIN16
  #define LPCTSTR LPCSTR
  #define LPOLESTR LPSTR
#endif

HRESULT CountArgsInFormat(LPCTSTR pszFmt, UINT FAR *pn);
LPCTSTR GetNextVarType(LPCTSTR pszFmt, VARTYPE FAR* pvt);

/*
 * CreateObject
 *
 * Purpose:
 *  Creates an instance of the Automation object and obtains it's IDispatch interface.
 *  Uses Unicode with OLE.
 *
 * Parameters:
 *  pszProgID         ProgID of Automation object
 *  ppdisp             Returns IDispatch of Automation object
 *
 * Return Value:
 *  HRESULT indicating success or failure
 */
HRESULT CreateObject(LPOLESTR pszProgID, IDispatch FAR* FAR* ppdisp)
{
    CLSID clsid;                   // CLSID of automation object
    HRESULT hr;
    LPUNKNOWN punk = NULL;         // IUnknown of automation object
    LPDISPATCH pdisp = NULL;       // IDispatch of automation object

    *ppdisp = NULL;

    // Retrieve CLSID from the progID that the user specified
    hr = CLSIDFromProgID(pszProgID, &clsid);
    if (FAILED(hr))
        goto error;

    // Create an instance of the automation object and ask for the IDispatch interface
    hr = CoCreateInstance(clsid, NULL, CLSCTX_SERVER,
                          IID_IUnknown, (void FAR* FAR*)&punk);
    if (FAILED(hr))
        goto error;

    hr = punk->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
    if (FAILED(hr))
        goto error;

    *ppdisp = pdisp;
    punk->Release();
    return NOERROR;

error:
    if (punk) punk->Release();
    if (pdisp) pdisp->Release();
    return hr;
}

/*
 * Invoke
 *
 * Purpose:
 *  Invokes a property accessor function or method of an automation object. Uses Unicode with OLE.
 *
 * Parameters:
 *  pdisp         IDispatch* of automation object.
 *  wFlags        Specfies if property is to be accessed or method to be invoked.
 *                Can hold DISPATCH_PROPERTYGET, DISPATCH_PROPERTYPUT, DISPATCH_METHOD,
 *                DISPATCH_PROPERTYPUTREF or DISPATCH_PROPERTYGET|DISPATCH_METHOD.
 *  pvRet         NULL if caller excepts no result. Otherwise returns result.
 *  pexcepinfo    Returns exception info if DISP_E_EXCEPTION is returned. Can be NULL if
 *                caller is not interested in exception information.
 *  pnArgErr      If return is DISP_E_TYPEMISMATCH, this returns the index (in reverse
 *                order) of argument with incorrect type. Can be NULL if caller is not interested
 *                in this information.
 *  pszName       Name of property or method.
 *  pszFmt        Format string that describes the variable list of parameters that
 *                follows. The format string can contain the follwoing characters.
 *                & = mark the following format character as VT_BYREF
 *                b = VT_BOOL
 *                i = VT_I2
 *                I = VT_I4
 *                r = VT_R2
 *                R = VT_R4
 *                c = VT_CY
 *                s = VT_BSTR (far string pointer can be passed, BSTR will be allocated by this function).
 *                e = VT_ERROR
 *                d = VT_DATE
 *                v = VT_VARIANT. Use this to pass data types that are not described in
 *                                the format string. (For example SafeArrays).
 *                D = VT_DISPATCH
 *                U = VT_UNKNOWN
 *
 *  ...           Arguments of the property or method. Arguments are described by pszFmt.
 *                ****FAR POINTERS MUST BE PASSED FOR POINTER ARGUMENTS in Win16.****
 * Return Value:
 *  HRESULT indicating success or failure
 *
 * Usage examples:
 *
 *  HRESULT hr;
 *  LPDISPATCH pdisp;
 *  BSTR bstr;
 *  short i;
 *  BOOL b;
 *  VARIANT v, v2;
 *
 *1. bstr = SysAllocString(OLESTR(""));
 *   hr = Invoke(pdisp, DISPATCH_METHOD, NULL, NULL, NULL, OLESTR("method1"),
 *        TEXT("bis&b&i&s"), TRUE, 2, (LPOLESTR)OLESTR("param"), (BOOL FAR*)&b, (short FAR*)&i, (BSTR FAR*)&bstr);
 *
 *2. VariantInit(&v);
 *   V_VT(&v) = VT_R8;
 *   V_R8(&v) = 12345.6789;
 *   VariantInit(&v2);
 *   hr = Invoke(pdisp, DISPATCH_METHOD, NULL, NULL, NULL, OLESTR("method2"),
 *         TEXT("v&v"), v, (VARIANT FAR*)&v2);
 */
HRESULT
Invoke(LPDISPATCH pdisp,
    WORD wFlags,
    LPVARIANT pvRet,
    EXCEPINFO FAR* pexcepinfo,
    UINT FAR* pnArgErr,
    LPOLESTR pszName,
    LPCTSTR pszFmt,
    ...)
{
    va_list argList;
    va_start(argList, pszFmt);
    DISPID dispid;
    HRESULT hr;
    VARIANTARG* pvarg = NULL;

    if (pdisp == NULL)
        return E_INVALIDARG;

    // Get DISPID of property/method
    hr = pdisp->GetIDsOfNames(IID_NULL, &pszName, 1, LOCALE_USER_DEFAULT, &dispid);
    if(FAILED(hr))
        return hr;

    DISPPARAMS dispparams;
    _fmemset(&dispparams, 0, sizeof dispparams);

    // determine number of arguments
    if (pszFmt != NULL)
        CountArgsInFormat(pszFmt, &dispparams.cArgs);

    // Property puts have a named argument that represents the value that the property is
    // being assigned.
    DISPID dispidNamed = DISPID_PROPERTYPUT;
    if (wFlags & DISPATCH_PROPERTYPUT)
    {
        if (dispparams.cArgs == 0)
            return E_INVALIDARG;
        dispparams.cNamedArgs = 1;
        dispparams.rgdispidNamedArgs = &dispidNamed;
    }

    if (dispparams.cArgs != 0)
    {
        // allocate memory for all VARIANTARG parameters
        pvarg = new VARIANTARG[dispparams.cArgs];
        if(pvarg == NULL)
            return E_OUTOFMEMORY;
        dispparams.rgvarg = pvarg;
        _fmemset(pvarg, 0, sizeof(VARIANTARG) * dispparams.cArgs);

        // get ready to walk vararg list
        LPCTSTR psz = pszFmt;
        pvarg += dispparams.cArgs - 1;   // params go in opposite order

        while (psz = GetNextVarType(psz, &pvarg->vt))
        {
            if (pvarg < dispparams.rgvarg)
            {
                hr = E_INVALIDARG;
                goto cleanup;
            }
            switch (pvarg->vt)
            {
            case VT_I2:
                V_I2(pvarg) = va_arg(argList, short);
                break;
            case VT_I4:
                V_I4(pvarg) = va_arg(argList, long);
                break;
            case VT_R4:
                V_R4(pvarg) = va_arg(argList, float);
                break;
            case VT_DATE:
            case VT_R8:
                V_R8(pvarg) = va_arg(argList, double);
                break;
            case VT_CY:
                V_CY(pvarg) = va_arg(argList, CY);
                break;
            case VT_BSTR:
                V_BSTR(pvarg) = SysAllocString(va_arg(argList, OLECHAR FAR*));
                if (pvarg->bstrVal == NULL)
                {
                    hr = E_OUTOFMEMORY;
                    pvarg->vt = VT_EMPTY;
                    goto cleanup;
                }
                break;
            case VT_DISPATCH:
                V_DISPATCH(pvarg) = va_arg(argList, LPDISPATCH);
                break;
            case VT_ERROR:
                V_ERROR(pvarg) = va_arg(argList, SCODE);
                break;
            case VT_BOOL:
                V_BOOL(pvarg) = va_arg(argList, BOOL) ? -1 : 0;
                break;
            case VT_VARIANT:
                *pvarg = va_arg(argList, VARIANTARG);
                break;
            case VT_UNKNOWN:
                V_UNKNOWN(pvarg) = va_arg(argList, LPUNKNOWN);
                break;

            case VT_I2|VT_BYREF:
                V_I2REF(pvarg) = va_arg(argList, short FAR*);
                break;
            case VT_I4|VT_BYREF:
                V_I4REF(pvarg) = va_arg(argList, long FAR*);
                break;
            case VT_R4|VT_BYREF:
                V_R4REF(pvarg) = va_arg(argList, float FAR*);
                break;
            case VT_R8|VT_BYREF:
                V_R8REF(pvarg) = va_arg(argList, double FAR*);
                break;
            case VT_DATE|VT_BYREF:
                V_DATEREF(pvarg) = va_arg(argList, DATE FAR*);
                break;
            case VT_CY|VT_BYREF:
                V_CYREF(pvarg) = va_arg(argList, CY FAR*);
                break;
            case VT_BSTR|VT_BYREF:
                V_BSTRREF(pvarg) = va_arg(argList, BSTR FAR*);
                break;
            case VT_DISPATCH|VT_BYREF:
                V_DISPATCHREF(pvarg) = va_arg(argList, LPDISPATCH FAR*);
                break;
            case VT_ERROR|VT_BYREF:
                V_ERRORREF(pvarg) = va_arg(argList, SCODE FAR*);
                break;
            case VT_BOOL|VT_BYREF:
                {
                    BOOL FAR* pbool = va_arg(argList, BOOL FAR*);
                    *pbool = 0;
                    V_BOOLREF(pvarg) = (VARIANT_BOOL FAR*)pbool;
                }
                break;
            case VT_VARIANT|VT_BYREF:
                V_VARIANTREF(pvarg) = va_arg(argList, VARIANTARG FAR*);
                break;
            case VT_UNKNOWN|VT_BYREF:
                V_UNKNOWNREF(pvarg) = va_arg(argList, LPUNKNOWN FAR*);
                break;

            default:
                {
                    hr = E_INVALIDARG;
                    goto cleanup;
                }
                break;
            }

            --pvarg; // get ready to fill next argument
        } //while
    } //if

    // Initialize return variant, in case caller forgot. Caller can pass NULL if return
    // value is not expected.
    if (pvRet)
        VariantInit(pvRet);
    // make the call
    hr = pdisp->Invoke(dispid, IID_NULL, LOCALE_USER_DEFAULT, wFlags,
        &dispparams, pvRet, pexcepinfo, pnArgErr);

cleanup:
    // cleanup any arguments that need cleanup
    if (dispparams.cArgs != 0)
    {
        VARIANTARG FAR* pvarg = dispparams.rgvarg;
        UINT cArgs = dispparams.cArgs;

        while (cArgs--)
        {
            switch (pvarg->vt)
            {
            case VT_BSTR:
                VariantClear(pvarg);
                break;
            }
            ++pvarg;
        }
    }
    delete dispparams.rgvarg;
    va_end(argList);
    return hr;
}

HRESULT CountArgsInFormat(LPCTSTR pszFmt, UINT FAR *pn)
{
    *pn = 0;

    if(pszFmt == NULL)
      return NOERROR;

    while (*pszFmt)
    {
       if (*pszFmt == '&')
           pszFmt++;

       switch(*pszFmt)
       {
           case 'b':
           case 'i':
           case 'I':
           case 'r':
           case 'R':
           case 'c':
           case 's':
           case 'e':
           case 'd':
           case 'v':
           case 'D':
           case 'U':
               ++*pn;
               pszFmt++;
               break;
           case '\0':
           default:
               return E_INVALIDARG;
        }
    }
    return NOERROR;
}

LPCTSTR GetNextVarType(LPCTSTR pszFmt, VARTYPE FAR* pvt)
{
    *pvt = 0;
    if (*pszFmt == '&')
    {
         *pvt = VT_BYREF;
         pszFmt++;
         if (!*pszFmt)
             return NULL;
    }
    switch(*pszFmt)
    {
        case 'b':
            *pvt |= VT_BOOL;
            break;
        case 'i':
            *pvt |= VT_I2;
            break;
        case 'I':
            *pvt |= VT_I4;
            break;
        case 'r':
            *pvt |= VT_R4;
            break;
        case 'R':
            *pvt |= VT_R8;
            break;
        case 'c':
            *pvt |= VT_CY;
            break;
        case 's':
            *pvt |= VT_BSTR;
            break;
        case 'e':
            *pvt |= VT_ERROR;
            break;
        case 'd':
            *pvt |= VT_DATE;
            break;
        case 'v':
            *pvt |= VT_VARIANT;
            break;
        case 'U':
            *pvt |= VT_UNKNOWN;
            break;
        case 'D':
            *pvt |= VT_DISPATCH;
            break;
        case '\0':
             return NULL;     // End of Format string
        default:
            return NULL;
    }
    return ++pszFmt;
}


INVHELP.H

HRESULT CreateObject(LPOLESTR pszProgID, IDispatch FAR* FAR* ppdisp);
HRESULT
Invoke(LPDISPATCH pdisp,
    WORD wFlags,
    LPVARIANT pvRet,
    EXCEPINFO FAR* pexcepinfo,
    UINT FAR* pnArgErr,
    LPOLESTR pszName,
    LPCTSTR pszFmt,
    ...);


BROWSE.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  Type Library Browser
**
**  browseex.cpp
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
  #include <commdlg.h>
#endif
#include "resource.h"
#include "browse.h"
#include "invhelp.h"

CBrowseApp BrowseApp;         // Application

int APIENTRY WinMain (HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpCmdLine, int nCmdShow)
{
   WNDCLASS wc;
   HRESULT hr;

#ifdef WIN16
   //  It is recommended that all 16 bit OLE applications set
   //  their message queue size to 96. This improves the capacity
   //  and performance of OLE's LRPC mechanism.
   int cMsg = 96;                  // Recommend msg queue size for OLE
   while (cMsg && !SetMessageQueue(cMsg))  // take largest size we can get.
       cMsg -= 8;
   if (!cMsg)
       return -1;  // ERROR: we got no message queue
#endif

   // Register class for the dialog that is the main window.
   wc.style = 0;
   wc.lpfnWndProc = DefDlgProc;
   wc.cbClsExtra = 0;
   wc.cbWndExtra = DLGWINDOWEXTRA;
   wc.hInstance = hinst;
   wc.hIcon = LoadIcon(hinst, MAKEINTRESOURCE(ID_ICON));
   wc.hCursor = LoadCursor(NULL, IDC_ARROW);
   wc.hbrBackground = HBRUSH(COLOR_WINDOW + 1);
   wc.lpszMenuName = NULL;
   wc.lpszClassName = TEXT("DlgClass");
   RegisterClass(&wc);

   hr = OleInitialize(NULL);
   if FAILED(hr)
   {
       MessageBox(NULL, TEXT("Could not Intialize OLE"), TEXT("Error"), MB_ICONEXCLAMATION|MB_OK);
       return 0;
   }
   DialogBox(hinst, MAKEINTRESOURCE(IDD_MAINDIALOG), NULL, (DLGPROC)MainDialogFunc);
   OleUninitialize();

   return 0;
}

BOOL CALLBACK MainDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
   HRESULT hr;

   switch (msg)
   {
      case WM_INITDIALOG:
          BrowseApp.Init(hwndDlg);
          return FALSE;

      case WM_COMMAND:
         switch(GET_WM_COMMAND_ID(wParam,lParam))
         {
             case IDM_EXIT:
             case IDCANCEL:
                 BrowseApp.Cleanup();
                 EndDialog(hwndDlg, IDOK);
                 CoFreeUnusedLibraries();
                 return TRUE;

             case IDM_FILEOPEN:
                 hr = BrowseApp.BrowseTypeLibrary();
                 if (FAILED(hr))
                     MessageBox(hwndDlg, TEXT("Failed to browse type library"), TEXT("Error"), MB_ICONEXCLAMATION|MB_OK);
                 return TRUE;

             case IDC_TYPEINFOSLIST:
                 switch (GET_WM_COMMAND_CMD(wParam, lParam))
                 {
                     case LBN_SELCHANGE:
                         hr = BrowseApp.ChangeTypeInfosSelection();
                         if (FAILED(hr))
                             MessageBox(hwndDlg, TEXT("Failed to get TypeInfo information"), TEXT("Error"), MB_ICONEXCLAMATION|MB_OK);
                         return TRUE;
                 }
                 break;

             case IDC_ELEMENTSLIST:
                 switch (GET_WM_COMMAND_CMD(wParam, lParam))
                 {
                     case LBN_SELCHANGE:
                         hr = BrowseApp.ChangeElementsSelection();
                         if (FAILED(hr))
                             MessageBox(hwndDlg, TEXT("Failed to get Element information"), TEXT("Error"), MB_ICONEXCLAMATION|MB_OK);
                         return TRUE;
                 }
                 break;

             case IDC_PARAMETERSLIST:
                 switch (GET_WM_COMMAND_CMD(wParam, lParam))
                 {
                     case LBN_SELCHANGE:
                         hr = BrowseApp.ChangeParametersSelection();
                         if (FAILED(hr))
                             MessageBox(hwndDlg, TEXT("Failed to get Parameter information"), TEXT("Error"), MB_ICONEXCLAMATION|MB_OK);
                         return TRUE;
                 }
                 break;

             case IDC_ELEM_OPEN_HELPFILE:
                 BrowseApp.OpenElementHelpFile();
                 return TRUE;

         }
         break;
   }
   return FALSE;
}

/*
 * CBrowseApp::BrowseTypeLibrary
 *
 * Purpose:
 *  Prompts user for type library. Fills the TypeInfo list with the TypeInfos of
 *  the type library.
 *
 */
HRESULT CBrowseApp::BrowseTypeLibrary()
{
    OPENFILENAME   ofn;
    TCHAR szFileName[128];
    BOOL bRes;
    VARIANT vRet;
    HRESULT hr;
    HCURSOR hcursorOld;
    LPDISPATCH pdispBrowseHelper = NULL;
    LPDISPATCH pdispTypeLibrary = NULL;
    LPDISPATCH pdispTypeInfos = NULL;
    TCHAR szTemp[100];

    // Prompt user for type library
    szFileName[0] = '\0';
    _fmemset(&ofn, 0, sizeof(ofn));
    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = m_hwndMain;
    ofn.lpstrFile = szFileName;
    ofn.nMaxFile  = sizeof(szFileName);
    ofn.hInstance = m_hinst;
    ofn.lpstrFilter  = TEXT("Type Libraries *.tlb,*.olb\0*.tlb;*.olb\0All Files *.*\0*.*\0\0");
    ofn.nFilterIndex = 1;
    ofn.Flags= OFN_FILEMUSTEXIST;
    bRes = GetOpenFileName(&ofn);
    if (!bRes)
        return S_FALSE;

    // Empty lists and clear fields.
    EmptyList(GetDlgItem(m_hwndMain, IDC_TYPEINFOSLIST));
    EmptyList(GetDlgItem(m_hwndMain, IDC_ELEMENTSLIST));
    EmptyList(GetDlgItem(m_hwndMain, IDC_PARAMETERSLIST));
    ClearTypeLibStaticFields();
    ClearTypeInfoStaticFields();
    ClearElementStaticFields();
    ClearParamStaticFields();
    EnableWindow(GetDlgItem(m_hwndMain, IDC_ELEM_OPEN_HELPFILE), FALSE);

    hcursorOld = SetCursor(LoadCursor(NULL, IDC_WAIT));
    // Create BrowseHelper object
    hr = CreateObject(OLESTR("BrowseHelper.Browser"), &pdispBrowseHelper);
    if (FAILED(hr))
    {
        MessageBox(m_hwndMain,
 TEXT("Failed to create BrowseHelper object. Build and register the BROWSEH sample"),
 TEXT("Error"), MB_ICONEXCLAMATION|MB_OK);
        goto error;
    }

    // Invoke IBrowseHelper.BrowseTypeLibrary(szFileName). Returns ITypeLibrary.
    hr = Invoke(pdispBrowseHelper, DISPATCH_METHOD, &vRet, NULL, NULL,
                      OLESTR("BrowseTypeLibrary"), TEXT("s"), (LPOLESTR)TO_OLE_STRING(szFileName));
    if (FAILED(hr))
        goto error;
    pdispTypeLibrary = V_DISPATCH(&vRet);

    // Invoke ITypeLibrary.Name. Returns BSTR.
    hr = Invoke(pdispTypeLibrary, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("Name"), TEXT(""));
    if (FAILED(hr))
        goto error;
if (NULL != V_BSTR(&vRet))
        SetDlgItemText(m_hwndMain, IDC_TYPELIB_NAME, FROM_OLE_STRING(V_BSTR(&vRet)));
    VariantClear(&vRet);

    // Invoke ITypeLibrary.HelpFile. Returns BSTR.
    hr = Invoke(pdispTypeLibrary, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("HelpFile"), TEXT(""));
    if (FAILED(hr))
        goto error;
if (NULL != V_BSTR(&vRet))
        SetDlgItemText(m_hwndMain, IDC_TYPELIB_HELPFILE, FROM_OLE_STRING(V_BSTR(&vRet)));
    if (NULL == V_BSTR(&vRet))
        m_szHelpFile[0] = '\0';
    else lstrcpy(m_szHelpFile, FROM_OLE_STRING(V_BSTR(&vRet)));
    VariantClear(&vRet);

    // Invoke ITypeLibrary.Documentation. Returns BSTR.
    hr = Invoke(pdispTypeLibrary, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("Documentation"), TEXT(""));
    if (FAILED(hr))
        goto error;
if (V_BSTR(&vRet) != NULL) //NULL is a valid value for a BSTR
        SetDlgItemText(m_hwndMain, IDC_TYPELIB_DOC, FROM_OLE_STRING(V_BSTR(&vRet)));
    VariantClear(&vRet);

    // Invoke ITypeLibrary.LocaleID. Returns VT_I4.
    hr = Invoke(pdispTypeLibrary, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("LocaleID"), TEXT(""));
    if (FAILED(hr))
        goto error;
    wsprintf(szTemp, TEXT("0x%lx"), V_I4(&vRet));
    SetDlgItemText(m_hwndMain, IDC_TYPELIB_LCID, szTemp);

    // Invoke ITypeLibrary.MajorVersion. Returns VT_I2.
    hr = Invoke(pdispTypeLibrary, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("MajorVersion"), TEXT(""));
    if (FAILED(hr))
        goto error;
    wsprintf(szTemp, TEXT("%d"), V_I2(&vRet));
    // Invoke ITypeLibrary.MinorVersion. Returns VT_I2.
    hr = Invoke(pdispTypeLibrary, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("MinorVersion"), TEXT(""));
    if (FAILED(hr))
        goto error;
    wsprintf(szTemp, TEXT("%s.%d"), (LPSTR)szTemp, V_I2(&vRet));
    SetDlgItemText(m_hwndMain, IDC_TYPELIB_VERSION, szTemp);

    // Invoke ITypeLibrary.TypeInfos. Returns ICollection of ITypeInfo.
    hr = Invoke(pdispTypeLibrary, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("TypeInfos"), TEXT(""));
    if (FAILED(hr))
        goto error;
    pdispTypeInfos = V_DISPATCH(&vRet);

    // Load TypeInfo list with the names of the type infos.
    hr = LoadList(pdispTypeInfos, IDC_TYPEINFOSLIST);
    if (FAILED(hr))
        goto error;

    pdispBrowseHelper->Release();
    pdispTypeLibrary->Release();
    pdispTypeInfos->Release();
    SetCursor(hcursorOld);
    return NOERROR;

error:
    if (pdispBrowseHelper) pdispBrowseHelper->Release();
    if (pdispTypeLibrary) pdispTypeLibrary->Release();
    if (pdispTypeInfos) pdispTypeLibrary->Release();
    VariantClear(&vRet);
    SetCursor(hcursorOld);
    return hr;
}

/*
 * CBrowseApp::ChangeTypeInfosSelection
 *
 * Purpose:
 *  Called when user changes selection in TypeInfos list box. Information
 *  about the the selected TypeInfo is dispayed. Elements of the TypeInfo
 *  are placed in the Elements list box.
 *
 */
HRESULT CBrowseApp::ChangeTypeInfosSelection()
{
    int nCurSel;
    VARIANT vRet;
    HRESULT hr;
    HCURSOR hcursorOld;
    LPDISPATCH pdispTypeInfo;
    TYPEKIND typekind;
    TCHAR szTemp[100];
    LPDISPATCH pdispElements = NULL;
    LPDISPATCH pdispMembers = NULL;
    LPDISPATCH pdispFunctions = NULL;
    LPDISPATCH pdispProperties = NULL;
    LPDISPATCH pdispMethods = NULL;
    LPDISPATCH pdispInterfaces = NULL;
    LPDISPATCH pdispTypeDesc = NULL;

    // Get current selection in TypeInfos list.
    nCurSel = (int)SendDlgItemMessage(m_hwndMain, IDC_TYPEINFOSLIST, LB_GETCURSEL, 0, 0L);
    if (nCurSel == LB_ERR)
        return NOERROR;
    // Get IDispatch* of selected TypeInfo.
    pdispTypeInfo = (LPDISPATCH) SendDlgItemMessage(m_hwndMain, IDC_TYPEINFOSLIST, LB_GETITEMDATA,
                (WPARAM)nCurSel, 0L);

    // Empty Elements & Parameters lists.
    EmptyList(GetDlgItem(m_hwndMain, IDC_ELEMENTSLIST));
    EmptyList(GetDlgItem(m_hwndMain, IDC_PARAMETERSLIST));
    ClearElementStaticFields();
    ClearParamStaticFields();

    ClearTypeInfoStaticFields();

    hcursorOld = SetCursor(LoadCursor(NULL, IDC_WAIT));
    // Invoke ITypeInformation.HelpContext. Returns VT_I4.
    hr = Invoke(pdispTypeInfo, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("HelpContext"), TEXT(""));
    if (FAILED(hr))
        goto error;
    wsprintf(szTemp, TEXT("%ld"), V_I4(&vRet));
    SetDlgItemText(m_hwndMain, IDC_TYPEINFO_HELPCTX, szTemp);

    // Invoke ITypeInformation.Documentation. Returns BSTR.
    hr = Invoke(pdispTypeInfo, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("Documentation"), TEXT(""));
    if (FAILED(hr))
        goto error;
if (V_BSTR(&vRet) != NULL) //NULL is a valid value for a BSTR
        SetDlgItemText(m_hwndMain, IDC_TYPEINFO_DOC, FROM_OLE_STRING(V_BSTR(&vRet)));
    VariantClear(&vRet);

    // Invoke ITypeInformation.TypeInfoKind. Returns TYPEKIND.
    hr = Invoke(pdispTypeInfo, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("TypeInfoKind"), TEXT(""));
    if (FAILED(hr))
        goto error;
    typekind = (TYPEKIND)V_I2(&vRet);

    switch (typekind)
    {
        case TKIND_ENUM:
            SetDlgItemText(m_hwndMain, IDC_TYPEINFO_TYPE, TEXT("TKIND_ENUM"));
            // Invoke IEnum.Elements. Returns ICollection of IConstant.
            hr = Invoke(pdispTypeInfo, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                        OLESTR("Elements"), TEXT(""));
            if (FAILED(hr))
                goto error;
            pdispElements = V_DISPATCH(&vRet);
            // Put the Enumerator constants in the Elements list.
            hr = LoadList(pdispElements, IDC_ELEMENTSLIST);
            if (FAILED(hr))
                goto error;
            pdispElements->Release();
            break;

        case TKIND_RECORD:
            SetDlgItemText(m_hwndMain, IDC_TYPEINFO_TYPE, TEXT("TKIND_RECORD"));
            // Invoke IStruct.Members. Returns ICollection of IProperty.
            hr = Invoke(pdispTypeInfo, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                        OLESTR("Members"), TEXT(""));
            if (FAILED(hr))
                goto error;
            pdispMembers = V_DISPATCH(&vRet);
            // Put the Structure memebers in the Elements list.
            hr = LoadList(pdispMembers, IDC_ELEMENTSLIST);
            if (FAILED(hr))
                goto error;
            pdispMembers->Release();
            break;

        case TKIND_MODULE:
            SetDlgItemText(m_hwndMain, IDC_TYPEINFO_TYPE, TEXT("TKIND_MODULE"));
            // Invoke IModule.Functions. Returns ICollection of IFunction.
            hr = Invoke(pdispTypeInfo, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                        OLESTR("Functions"), TEXT(""));
            if (FAILED(hr))
                goto error;
            pdispFunctions = V_DISPATCH(&vRet);
            // Put the Module functions in the Elements list.
            hr = LoadList(pdispFunctions, IDC_ELEMENTSLIST);
            if (FAILED(hr))
                goto error;
            pdispFunctions->Release();
            break;

        case TKIND_INTERFACE:
            SetDlgItemText(m_hwndMain, IDC_TYPEINFO_TYPE, TEXT("TKIND_INTERFACE"));
            // Invoke IInterface.Functions. Returns ICollection of IFunction.
            hr = Invoke(pdispTypeInfo, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                        OLESTR("Functions"), TEXT(""));
            if (FAILED(hr))
                goto error;
            pdispFunctions = V_DISPATCH(&vRet);
            // Put the Interface functions in the Elements list.
            hr = LoadList(pdispFunctions, IDC_ELEMENTSLIST);
            if (FAILED(hr))
                goto error;
            pdispFunctions->Release();
            break;

        case TKIND_DISPATCH:
            SetDlgItemText(m_hwndMain, IDC_TYPEINFO_TYPE, TEXT("TKIND_DISPATCH"));
            // Invoke Dispinterface.Properties. Returns ICollection of IProperty.
            hr = Invoke(pdispTypeInfo, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                        OLESTR("Properties"), TEXT(""));
            if (FAILED(hr))
                goto error;
            pdispProperties = V_DISPATCH(&vRet);
            // Invoke IDispinterface.Methods. Returns ICollection of IFunction.
            hr = Invoke(pdispTypeInfo, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                        OLESTR("Methods"), TEXT(""));
            if (FAILED(hr))
                goto error;
            pdispMethods = V_DISPATCH(&vRet);
            // Put the Dispinterface properties and methods in the Elements list.
            hr = LoadList(pdispProperties, IDC_ELEMENTSLIST);
            if (FAILED(hr))
                goto error;
            pdispProperties->Release();
            hr = LoadList(pdispMethods, IDC_ELEMENTSLIST);
            if (FAILED(hr))
                goto error;
            pdispMethods->Release();
            break;

        case TKIND_COCLASS:
            SetDlgItemText(m_hwndMain, IDC_TYPEINFO_TYPE, TEXT("TKIND_COCLASS"));
            // Invoke ICoClass.Interfaces. Returns ICollection of IInterface & IDispinterface.
            hr = Invoke(pdispTypeInfo, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                        OLESTR("Interfaces"), TEXT(""));
            if (FAILED(hr))
                goto error;
            pdispInterfaces = V_DISPATCH(&vRet);
            // Put the CoClass interfaces and dispinterfaces in the Elements list.
            hr = LoadList(pdispInterfaces, IDC_ELEMENTSLIST);
            if (FAILED(hr))
                goto error;
            pdispInterfaces->Release();
            break;

        case TKIND_ALIAS:
            // An alias does not have elements. Display the base type.
            SetDlgItemText(m_hwndMain, IDC_TYPEINFO_TYPE, TEXT("TKIND_ALIAS"));
            // Invoke IAlias.BaseType. Returns ITypeDesc.
            hr = Invoke(pdispTypeInfo, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                        OLESTR("BaseType"), TEXT(""));
            if (FAILED(hr))
                goto error;
            pdispTypeDesc = V_DISPATCH(&vRet);
            szTemp[0] = '\0'; // Must initialize to empty string before calling TypeToString.
            hr = TypeToString(pdispTypeDesc, szTemp);
            if (FAILED(hr))
                goto error;
            SetDlgItemText(m_hwndMain, IDC_TYPEINFO_ALIASTYPE, szTemp);
            pdispTypeDesc->Release();
            break;

        case TKIND_UNION:
            SetDlgItemText(m_hwndMain, IDC_TYPEINFO_TYPE, TEXT("TKIND_UNION"));
            // Invoke IUnion.Members. Returns ICollection of IProperty.
            hr = Invoke(pdispTypeInfo, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                        OLESTR("Members"), TEXT(""));
            if (FAILED(hr))
                goto error;
            pdispMembers = V_DISPATCH(&vRet);
            // Put the Union members in the Elements list.
            hr = LoadList(pdispMembers, IDC_ELEMENTSLIST);
            if (FAILED(hr))
                goto error;
            pdispMembers->Release();
            break;
    }
    SetCursor(hcursorOld);
    return NOERROR;
error:
    if (pdispElements) pdispElements->Release();
    if (pdispMembers) pdispMembers->Release();
    if (pdispFunctions) pdispFunctions->Release();
    if (pdispProperties) pdispProperties->Release();
    if (pdispMethods) pdispElements->Release();
    if (pdispInterfaces) pdispInterfaces->Release();
    if (pdispTypeDesc) pdispTypeDesc->Release();
    VariantClear(&vRet);
    SetCursor(hcursorOld);
    return hr;
}

/*
 * CBrowseApp::ChangeElementsSelection
 *
 * Purpose:
 *  Called when user changes selection in Elements list box. Information
 *  about the the selected Element is dispayed. If the element is a function,
 *  the parameters are placed in the the Parameters list box.
 *
 */
HRESULT CBrowseApp::ChangeElementsSelection()
{
    int nCurSel;
    VARIANT vRet;
    HRESULT hr;
    int nKind;
    TYPEKIND typekind;
    LPDISPATCH pdispElement;
    LPDISPATCH pdispParameters = NULL;
    LPDISPATCH pdispTypeDesc = NULL;
    TCHAR szTemp[100];

    // Disable the help file button.
    EnableWindow(GetDlgItem(m_hwndMain, IDC_ELEM_OPEN_HELPFILE), FALSE);

    // Get current selection in Elements list.
    nCurSel = (int)SendDlgItemMessage(m_hwndMain, IDC_ELEMENTSLIST, LB_GETCURSEL, 0, 0L);
    if (nCurSel == LB_ERR)
        return NOERROR;
    // Get IDispatch* of selected Element.
    pdispElement = (LPDISPATCH) SendDlgItemMessage(m_hwndMain, IDC_ELEMENTSLIST, LB_GETITEMDATA,
                (WPARAM)nCurSel, 0L);

    // Empty Parameters lists.
    EmptyList(GetDlgItem(m_hwndMain, IDC_PARAMETERSLIST));
    ClearParamStaticFields();

    ClearElementStaticFields();

    // Invoke IFunction/IConstant/IProperty/IInterface/IDispinterface.HelpContext. Returns VT_I4.
    hr = Invoke(pdispElement, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("HelpContext"), TEXT(""));
    if (FAILED(hr))
        goto error;
    wsprintf(szTemp, TEXT("%ld"), V_I4(&vRet));
    SetDlgItemText(m_hwndMain, IDC_ELEM_HELPCTX, szTemp);
    m_lElemHelpCtx = V_I4(&vRet);

    // Invoke IFunction/IConstant/IProperty/IInterface/IDispinterface.Documentation. Returns BSTR.
    hr = Invoke(pdispElement, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("Documentation"), TEXT(""));
    if (FAILED(hr))
        goto error;
if (V_BSTR(&vRet) != NULL) //NULL is a valid value for a BSTR
        SetDlgItemText(m_hwndMain, IDC_ELEM_DOC, FROM_OLE_STRING(V_BSTR(&vRet)));
    VariantClear(&vRet);

    // Invoke IFunction/IConstant/IProperty.MemberID. Returns VT_I4.
    hr = Invoke(pdispElement, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("MemberID"), TEXT(""));
    if (FAILED(hr))
    {
        // This maybe a interface/dispinterface element of a coclass
        hr = Invoke(pdispElement, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("TypeInfoKind"), TEXT(""));
        if (FAILED(hr))
            goto error;
        typekind = (TYPEKIND)V_I2(&vRet);
        if (typekind == TKIND_INTERFACE)
            SetDlgItemText(m_hwndMain, IDC_ELEM_TYPE, TEXT("TKIND_INTERFACE"));
        else if (typekind == TKIND_DISPATCH)
            SetDlgItemText(m_hwndMain, IDC_ELEM_TYPE, TEXT("TKIND_DISPATCH"));
        else SetDlgItemText(m_hwndMain, IDC_ELEM_TYPE, TEXT("Unknown element"));
        return NOERROR;
    }
    wsprintf(szTemp, TEXT("0x%lx"), V_I4(&vRet));
    SetDlgItemText(m_hwndMain, IDC_ELEM_MEMID, szTemp);

    // Invoke IFunction/IConstant/IProperty.Kind property. Returns OBJKIND.
    hr = Invoke(pdispElement, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("Kind"), TEXT(""));
    if (FAILED(hr))
        goto error;
    nKind = V_I2(&vRet);

    switch (nKind)
    {
        case TYPE_FUNCTION: //Function
            // Invoke IFunction.Parameters. Returns ICollection of IParameter..
            hr = Invoke(pdispElement, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                        OLESTR("Parameters"), TEXT(""));
            if (FAILED(hr))
                goto error;
            pdispParameters = V_DISPATCH(&vRet);
            hr = LoadList(pdispParameters, IDC_PARAMETERSLIST);
            if FAILED(hr)
                goto error;
            pdispParameters->Release(); pdispParameters = NULL;

            // Invoke IFunction.CallConvention. Returns CALLCONV.
            hr = Invoke(pdispElement, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("CallConvention"), TEXT(""));
            if (FAILED(hr))
                goto error;
            CallConvToString(V_I2(&vRet), szTemp);
            SetDlgItemText(m_hwndMain, IDC_ELEM_CALLCONV, szTemp);

            // Invoke IFunction.FuncKind. Returns FUNCKIND.
            hr = Invoke(pdispElement, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("FuncKind"), TEXT(""));
            if (FAILED(hr))
                goto error;
            FuncKindToString(V_I2(&vRet), szTemp);
            SetDlgItemText(m_hwndMain, IDC_ELEM_FUNCKIND, szTemp);

            // Invoke IFunction.InvocationKind. Returns INVOKEKIND.
            hr = Invoke(pdispElement, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("InvocationKind"), TEXT(""));
            if (FAILED(hr))
                goto error;
            InvokeKindToString(V_I2(&vRet), szTemp);
            SetDlgItemText(m_hwndMain, IDC_ELEM_INVOKEKIND, szTemp);

            // Invoke IFunction.ReturnType. Returns ITypeDesc.
            hr = Invoke(pdispElement, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("ReturnType"), TEXT(""));
            if (FAILED(hr))
                goto error;
            break;

        case TYPE_CONSTANT:
            // Invoke IConstant.Value. Returns VARIANT.
            hr = Invoke(pdispElement, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("Value"), TEXT(""));
            if (FAILED(hr))
                goto error;
            VariantToString(vRet, szTemp);
            SetDlgItemText(m_hwndMain, IDC_ELEM_CONST_VALUE, szTemp);
            // Fall through to default.

        default:
            // Invoke IConstant/IProperty.Type. Returns ITypeDesc.
            hr = Invoke(pdispElement, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("Type"), TEXT(""));
            if (FAILED(hr))
                goto error;
            break;
    }
    // Get string that describes type and display it.
    pdispTypeDesc = V_DISPATCH(&vRet);
    szTemp[0] = '\0'; // Must initialize to empty string before calling TypeToString.
    hr = TypeToString(pdispTypeDesc, szTemp);
    if (FAILED(hr))
        goto error;
    SetDlgItemText(m_hwndMain, IDC_ELEM_TYPE, szTemp);
    pdispTypeDesc->Release();

    // Enable the helpfile button if a helpfile and help context is specified.
    if (m_lElemHelpCtx && m_szHelpFile[0])
        EnableWindow(GetDlgItem(m_hwndMain, IDC_ELEM_OPEN_HELPFILE), TRUE);
    return NOERROR;
error:
    if (pdispParameters) pdispParameters->Release();
    VariantClear(&vRet);
    return hr;
}

/*
 * CBrowseApp::ChangeParametersSelection
 *
 * Purpose:
 *  Called when user changes selection in Parameters list box. Information
 *  about the the selected Parameter is dispayed.
 *
 */
HRESULT CBrowseApp::ChangeParametersSelection()
{
    int nCurSel;
    VARIANT vRet;
    HRESULT hr;
    LPDISPATCH pdispParameter;
    LPDISPATCH pdispTypeDesc = NULL;
    TCHAR szTemp[100];

    // Get current selection in Parameters list.
    nCurSel = (int)SendDlgItemMessage(m_hwndMain, IDC_PARAMETERSLIST, LB_GETCURSEL, 0, 0L);
    if (nCurSel == LB_ERR)
        return NOERROR;
    // Get IDispatch* of selected Parameter.
    pdispParameter = (LPDISPATCH) SendDlgItemMessage(m_hwndMain, IDC_PARAMETERSLIST, LB_GETITEMDATA,
                (WPARAM)nCurSel, 0L);

    ClearParamStaticFields();

    // Invoke IParameter.IDLFlags. Returns VT_I2.
    hr = Invoke(pdispParameter, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("IDLFlags"), TEXT(""));
    if (FAILED(hr))
        goto error;
    IDLFlagsToString(V_I2(&vRet), szTemp);
    SetDlgItemText(m_hwndMain, IDC_PARAM_INOUT, szTemp);

    // Invoke IParameter.Type. Returns ITypeDesc.
    hr = Invoke(pdispParameter, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("Type"), TEXT(""));
    if (FAILED(hr))
        goto error;

   // Get string that describes type and display it.
    pdispTypeDesc = V_DISPATCH(&vRet);
    szTemp[0] = '\0'; // Must initialize to empty string before calling TypeToString.
    hr = TypeToString(pdispTypeDesc, szTemp);
    if (FAILED(hr))
        goto error;
    SetDlgItemText(m_hwndMain, IDC_PARAM_TYPE, szTemp);
    pdispTypeDesc->Release();
    return NOERROR;
error:
    if (pdispTypeDesc) pdispTypeDesc->Release();
    VariantClear(&vRet);
    return hr;
}

/*
 * CBrowseApp::OpenElementHelpFile
 *
 * Purpose:
 *  Opens the helpfile for the element.
 *
 */
void CBrowseApp::OpenElementHelpFile()
{
    BOOL b;

    b = WinHelp(m_hwndMain, m_szHelpFile, HELP_CONTEXT, m_lElemHelpCtx);
    if (!b)
        MessageBox(m_hwndMain, TEXT("Failed to open help file"), TEXT("Error"),
            MB_ICONEXCLAMATION|MB_OK);
}

void CBrowseApp::EmptyList(HWND hwndList)
{
    int nItems, i;
    LPDISPATCH pdispItem;

    nItems = (int)SendMessage(hwndList, LB_GETCOUNT, 0, 0L);
    for (i=0; i<nItems; i++)
    {
        pdispItem = (LPDISPATCH)SendMessage(hwndList, LB_GETITEMDATA, (WPARAM)i, 0L);
        if (pdispItem)
            pdispItem->Release();
    }
    SendMessage(hwndList, LB_RESETCONTENT, 0, 0L);
}

/*
 * CBrowseApp::LoadList
 *
 * Parameters:
 *  pdispItems IDispatch* of collection of items to be put into the list.
 *  nListID    Identifies the list to be filled.
 *
 * Purpose:
 *  Loads the list with the items in the collection.

*
 */
HRESULT CBrowseApp::LoadList(LPDISPATCH pdispItems, int nListID)
{
    VARIANT vRet, v;
    HRESULT hr;
    LPUNKNOWN punkEnum;
    IEnumVARIANT FAR* penum = NULL;
    LPDISPATCH pdispItem = NULL;
    int nIndex;

    // Get _NewEnum property. Returns enumerator's IUnknown.
    hr = Invoke(pdispItems, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                        OLESTR("_NewEnum"), TEXT(""));
    if (FAILED(hr))
        goto error;
    punkEnum = V_UNKNOWN(&vRet);
    hr = punkEnum->QueryInterface(IID_IEnumVARIANT, (LPVOID FAR*)&penum);
    if (FAILED(hr))
        goto error;
    punkEnum->Release();

    VariantInit(&v);
    // Enumerate the Items.
    while (S_OK == penum->Next(1, &v, NULL))
    {
        pdispItem = V_DISPATCH(&v);
        pdispItem->AddRef();
        VariantClear(&v);

        // Get Name of Item.
        hr = Invoke(pdispItem, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                    OLESTR("Name"), TEXT(""));
        if (FAILED(hr))
            goto error;
        // Add name to ItemsList.
        nIndex = (int)SendDlgItemMessage(m_hwndMain, nListID, LB_ADDSTRING,
            0, (LPARAM)(LPTSTR)FROM_OLE_STRING(V_BSTR(&vRet)));
        VariantClear(&vRet);
        // Save IDispatch* of Item in the list.
        SendDlgItemMessage(m_hwndMain, nListID, LB_SETITEMDATA,
            nIndex, (LPARAM)pdispItem);
    }
    penum->Release();
    return NOERROR;

error:
    if (penum) penum->Release();
    if (pdispItem) pdispItem->Release();
    return hr;
}

void CBrowseApp::Init(HWND hwndMain)
{
    HFONT hfontDlg;
    LOGFONT lfont;
    int nID;

    m_hwndMain = hwndMain;
    m_hfont = NULL;
#ifdef WIN16
    m_hinst = (HINSTANCE)GetWindowWord(hwndMain, GWW_HINSTANCE);
#else
    m_hinst = (HINSTANCE)GetWindowLong(hwndMain, GWL_HINSTANCE);
#endif
    EnableWindow(GetDlgItem(m_hwndMain, IDC_ELEM_OPEN_HELPFILE), FALSE);

    // Change to the font of the fields to a non-bold font.
    hfontDlg = (HFONT)SendMessage(m_hwndMain, WM_GETFONT, NULL, NULL);
    if (!hfontDlg)
        return;
    GetObject(hfontDlg, sizeof(LOGFONT), (LPVOID)&lfont);
    lfont.lfWeight = FW_NORMAL;
    if (m_hfont = CreateFontIndirect(&lfont))
        for (nID=IDC_TYPELIB_FIRST; nID<=IDC_PARAM_LAST; nID++)
            SendDlgItemMessage(m_hwndMain, nID, WM_SETFONT, (WPARAM)m_hfont, 0L);

}

void CBrowseApp::Cleanup()
{
    EmptyList(GetDlgItem(m_hwndMain, IDC_TYPEINFOSLIST));
    EmptyList(GetDlgItem(m_hwndMain, IDC_ELEMENTSLIST));
    EmptyList(GetDlgItem(m_hwndMain, IDC_PARAMETERSLIST));
    ClearTypeLibStaticFields();
    ClearTypeInfoStaticFields();
    ClearElementStaticFields();
    ClearParamStaticFields();
    if (m_hfont)
        DeleteObject(m_hfont);
}

void CBrowseApp::ClearTypeLibStaticFields()
{
    int nID;

    for (nID=IDC_TYPELIB_FIRST; nID<=IDC_TYPELIB_LAST; nID++)
        SetDlgItemText(m_hwndMain, nID, TEXT(""));
}
void CBrowseApp::ClearTypeInfoStaticFields()
{
    int nID;

    for (nID=IDC_TYPEINFO_FIRST; nID<=IDC_TYPEINFO_LAST; nID++)
        SetDlgItemText(m_hwndMain, nID, TEXT(""));
}
void CBrowseApp::ClearElementStaticFields()
{
    int nID;

    for (nID=IDC_ELEM_FIRST; nID<=IDC_ELEM_LAST; nID++)
        SetDlgItemText(m_hwndMain, nID, TEXT(""));
}
void CBrowseApp::ClearParamStaticFields()
{
    int nID;

    for (nID=IDC_PARAM_FIRST; nID<=IDC_PARAM_LAST; nID++)
        SetDlgItemText(m_hwndMain, nID, TEXT(""));
}

#define CASE_VT(vt)  \
        case vt: \
            lstrcat(pszTypeName, TEXT(#vt)); \
            break;
HRESULT CBrowseApp::TypeToString(LPDISPATCH pdispTypeDesc, LPTSTR pszTypeName)
{
    VARIANT vRet;
    VARTYPE vartype;
    HRESULT hr;
    LPDISPATCH pdispTypeDesc2;

    // Get Type property
    hr = Invoke(pdispTypeDesc, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("Type"), TEXT(""));
    if (FAILED(hr))
        goto error;
    vartype = V_I2(&vRet);

    if (vartype & 0x2000)  // If SafeArray
      lstrcat(pszTypeName, TEXT("SAFEARRAY("));

    switch (vartype & ~0x7000)
    {
    CASE_VT(VT_EMPTY)
    CASE_VT(VT_NULL)
    CASE_VT(VT_I2)
    CASE_VT(VT_I4)
    CASE_VT(VT_R4)
    CASE_VT(VT_R8)
    CASE_VT(VT_CY)
    CASE_VT(VT_DATE)
    CASE_VT(VT_BSTR)
    CASE_VT(VT_DISPATCH)
    CASE_VT(VT_ERROR)
    CASE_VT(VT_BOOL)
    CASE_VT(VT_VARIANT)
    CASE_VT(VT_UNKNOWN)

    CASE_VT(VT_I1)
    CASE_VT(VT_UI1)
    CASE_VT(VT_UI2)
    CASE_VT(VT_UI4)
    CASE_VT(VT_I8)
    CASE_VT(VT_UI8)
    CASE_VT(VT_INT)
    CASE_VT(VT_UINT)
    CASE_VT(VT_VOID)
    CASE_VT(VT_HRESULT)
    CASE_VT(VT_SAFEARRAY)
    CASE_VT(VT_CARRAY)
    CASE_VT(VT_LPSTR)
    CASE_VT(VT_LPWSTR)

    CASE_VT(VT_FILETIME)
    CASE_VT(VT_BLOB)
    CASE_VT(VT_STREAM)
    CASE_VT(VT_STORAGE)
    CASE_VT(VT_STREAMED_OBJECT)
    CASE_VT(VT_STORED_OBJECT)
    CASE_VT(VT_BLOB_OBJECT)
    CASE_VT(VT_CF)
    CASE_VT(VT_CLSID)

    case VT_PTR:
         // Get ITypeDesc.PointerDesc property. Returns ITypeDesc.
         hr = Invoke(pdispTypeDesc, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("PointerDesc"), TEXT(""));
         if (FAILED(hr))
             goto error;
         pdispTypeDesc2 = V_DISPATCH(&vRet);
         TypeToString(pdispTypeDesc2, pszTypeName);
         lstrcat(pszTypeName, TEXT(" *"));
         pdispTypeDesc2->Release();
         break;

    case VT_USERDEFINED:
         // Get ITypeDesc.UserDefinedDesc property. Returns ITypeInformation
         hr = Invoke(pdispTypeDesc, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("UserDefinedDesc"), TEXT(""));
         if (FAILED(hr))
             goto error;
         pdispTypeDesc2 = V_DISPATCH(&vRet);
         // Get ITypeInformation.Name property. Returns BSTR.
         hr = Invoke(pdispTypeDesc2, DISPATCH_PROPERTYGET, &vRet, NULL, NULL,
                OLESTR("Name"), TEXT(""));
         if (FAILED(hr))
             goto error;
         lstrcat(pszTypeName, FROM_OLE_STRING(V_BSTR(&vRet)));
         VariantClear(&vRet);
         pdispTypeDesc2->Release();
         break;
    }

    if (vartype & 0x2000) // If SafeArray
        lstrcat(pszTypeName, TEXT(")"));

    return NOERROR;
error:
    return ERROR;
}

void CBrowseApp::IDLFlagsToString(int n, LPTSTR psz)
{
   psz[0] = '\0';
   if (n & IDLFLAG_FIN)
   {
       lstrcpy(psz, TEXT("IN"));
       if (n & IDLFLAG_FOUT)
           lstrcat(psz, TEXT("|OUT"));
   }
   else if (n & IDLFLAG_FOUT)
       lstrcpy(psz, TEXT("OUT"));
}

#define CASE_INVOKE(invokekind)  \
        case invokekind: \
            lstrcpy(psz, TEXT(#invokekind)); \
            break;
void CBrowseApp::CallConvToString(int n, LPTSTR psz)
{
    CALLCONV c = (CALLCONV)n;
    switch (c)
    {
        CASE_INVOKE(CC_CDECL)
        CASE_INVOKE(CC_PASCAL)
        CASE_INVOKE(CC_MACPASCAL)
        CASE_INVOKE(CC_STDCALL)
        CASE_INVOKE(CC_SYSCALL)
        default:
            lstrcpy(psz, TEXT("Unknown"));
    }
}

#define CASE_FUNC(funckind)  \
        case funckind: \
            lstrcpy(psz, TEXT(#funckind)); \
            break;
void CBrowseApp::FuncKindToString(int n, LPTSTR psz)
{
    FUNCKIND f = (FUNCKIND)n;
    switch (f)
    {
        CASE_FUNC(FUNC_VIRTUAL)
        CASE_FUNC(FUNC_PUREVIRTUAL)
        CASE_FUNC(FUNC_NONVIRTUAL)
        CASE_FUNC(FUNC_STATIC)
        CASE_FUNC(FUNC_DISPATCH)
        default:
            lstrcpy(psz, TEXT("Unknown"));
    }
}

#define CASE_INVOKE(invokekind)  \
        case invokekind: \
            lstrcpy(psz, TEXT(#invokekind)); \
            break;
void CBrowseApp::InvokeKindToString(int n, LPTSTR psz)
{
    INVOKEKIND i = (INVOKEKIND)n;
    switch (i)
    {
        CASE_INVOKE(INVOKE_FUNC)
        CASE_INVOKE(INVOKE_PROPERTYGET)
        CASE_INVOKE(INVOKE_PROPERTYPUT)
        CASE_INVOKE(DISPATCH_PROPERTYPUTREF)
        default:
            lstrcpy(psz, TEXT("Unknown"));
    }
}
void CBrowseApp::VariantToString(VARIANT v, LPTSTR psz)
{
    HRESULT hr;
    VARIANT vTemp;

    VariantInit(&vTemp);
    hr = VariantChangeType(&vTemp, &v, 0, VT_BSTR);
    if (FAILED(hr))
        lstrcpy(psz, TEXT("Cannot Display Value"));
    else lstrcpy(psz, FROM_OLE_STRING(V_BSTR(&vTemp)));
    VariantClear(&vTemp);
}

/*
 * Quick & Dirty ANSI/Unicode conversion routines. These routines use a static
 * buffer of fixed size to hold the converted string. Consequently these
 * routines are limited to strings of size STRCONVERT_MAXLEN. Also the same
 * buffer is reused when the routine is called a second time. So make sure
 * that the converted string is used before the conversion routine is called
 * again
 */
#ifdef WIN32

#ifndef UNICODE
char* ConvertToAnsi(OLECHAR FAR* szW)
{
  static char achA[STRCONVERT_MAXLEN];

  WideCharToMultiByte(CP_ACP, 0, szW, -1, achA, STRCONVERT_MAXLEN, NULL, NULL);
  return achA;
}

OLECHAR* ConvertToUnicode(char FAR* szA)
{
  static OLECHAR achW[STRCONVERT_MAXLEN];

  MultiByteToWideChar(CP_ACP, 0, szA, -1, achW, STRCONVERT_MAXLEN);
  return achW;
}
#endif

#endif


RESOURCE.H

//{{NO_DEPENDENCIES}}
// App Studio generated include file.
// Used by BROWSER.RC
//
#define IDD_MAINDIALOG                  101
#define ID_MENU                         102
#define ID_ICON                         103

#define IDC_TYPEINFOSLIST               1001
#define IDC_ELEMENTSLIST                1002
#define IDC_PARAMETERSLIST              1003
#define IDC_TYPELIB_NAME                1004
#define IDC_TYPELIB_DOC                 1005
#define IDC_TYPELIB_VERSION             1006
#define IDC_TYPELIB_HELPFILE            1007
#define IDC_TYPELIB_LCID                1008
#define IDC_TYPEINFO_TYPE               1009
#define IDC_TYPEINFO_HELPCTX            1010
#define IDC_TYPEINFO_DOC                1011
#define IDC_TYPEINFO_ALIASTYPE          1012
#define IDC_ELEM_TYPE                   1013
#define IDC_ELEM_HELPCTX                1014
#define IDC_ELEM_FUNCKIND               1015
#define IDC_ELEM_CALLCONV               1016
#define IDC_ELEM_INVOKEKIND             1017
#define IDC_ELEM_MEMID                  1018
#define IDC_ELEM_CONST_VALUE            1019
#define IDC_ELEM_DOC                    1020
#define IDC_PARAM_TYPE                  1021
#define IDC_PARAM_INOUT                 1022
#define IDC_ELEM_OPEN_HELPFILE          1023

#define IDC_TYPELIB_FIRST               1004
#define IDC_TYPELIB_LAST                1008
#define IDC_TYPEINFO_FIRST              1009
#define IDC_TYPEINFO_LAST               1012
#define IDC_ELEM_FIRST                  1013
#define IDC_ELEM_LAST                   1020
#define IDC_PARAM_FIRST                 1021
#define IDC_PARAM_LAST                  1022

#define IDM_FILEOPEN                    40001
#define IDM_EXIT                        40002
#define IDC_STATIC                      -1


README.TXT

OLE Automation Controller and Server that Browses a Type Library

SUMMARY
=======

The BROWSE sample controls the automation objects of the BROWSEH (Browse
Helper) inproc server to browse a type library. BROWSE is a developer-
oriented type library browser.

In addition to displaying type library information, this sample shows how
to control an automation object by accessing its properties and methods.
BROWSE provides generic helper functions (CreateObject & Invoke) to control
any automation server. For more information on how to use these functions,
see the comments in INVHELP.CPP.

BROWSEH exposes a number of automation objects that correspond to
components of a type library. This allows an automation controller like
BROWSE to access the properties and methods of these objects to browse the
type library instead of directly using ITypeLib and ITypeInfo. So BROWSEH
is a type library browse helper. BROWSEH does not support vtable binding.
Controllers must use IDispatch to control this server.

MORE INFORMATION
================

The following information describes the BROWSE sample.

To Compile BROWSE and BROWSEH
-----------------------------

To compile an ANSI debug version of the sample for Windows NT and Windows 95,
use the following command:

   nmake

To compile a Unicode debug version of the sample for Windows NT only, use the
following command:

   nmake dev=win32 HOST=NT

To compile a 16-bit debug version of the sample and if your development
environment is Win 3.x, use the following command. If your development
environment is Win95 or NT, change the HOST appropriately.

   nmake dev=win16 HOST=DOS

To clean up between builds which use different options, use one of the
following commands:

   nmake clean
   nmake dev=win16 clean

See the makefile header comments for other make options.

If one of the build tools cannot find SDKUTIL.LIB, use the SAMPLES\COM\COMMON
sample to build SDKUTIL.LIB.

Visual C++ 2.x and above can use makefile as an external makefile
after making the following replacement in the makefile:

   !include <$(MSTOOLS)\samples\com\include\olesampl.mak>

with

   !include <win32.mak>

To Run BROWSE
-------------

 - Change BROWSEH.REG to provide the full path of BROWSEH.DLL,    BROWSEH.TLB and MYDISP.TLB.

 - Register BROWSEH.REG in the registration database by double-clicking it.

 - Run BROWSE.EXE and use the menu to select a type library to browse.

BROWSE Files
------------

INVHELP.CPP and INVHELP.H contain two helper functions that are used to
create an automation object (using CreateObject) and to invoke a method
or property of that object (using Invoke). These functions are general
enough to be used by any Automation controller.

BROWSE.CPP uses the helper functions in invhelp.cpp to access the
properties and methods of the automation objects of BROWSEH.

MAKEFILE is the makefile for the project.

To run BROWSEH
--------------

 - Change BROWSEH.REG to provide the full path of BROWSEH.DLL and BROWSEH.TLB.

 - Register BROWSEH.REG in the registration database by double-clicking it.

 - Run the Automation controller BROWSE that uses BROWSEH to browse a type
   library OR load and run the VB 4.0 VB.VBP project file which uses BROWSEH
   to output a C/C++ header file from the infromation in a type library. The
   header file can be used to create a C/C++ vtbl-binding client for the server
   to which the type library belongs, if the server supports vtbl-binding. After
   running the VB script, select the type library to be used as the input and
   specify the name of the header file to be output.

BROWSEH Files
-------------

MYDISP.ODL contains the description of a base class that implements
IDispatch. All automation objects in this server are derived from this
class. This is the implementation of IDispatch in the base class to be
inherited in the derived classes.

BROWSEH.ODL contains the description of the objects that BROWSEH exposes.

TLB.H is the header file generated by mktyplib.exe

MAKEFILE is the makefile for project.

MAIN.CPP contains code to create an inproc server.

Other files implement the BROWSEH automation server. For example,
COCLASS.CPP demonstrates how to use ITypeInfo and ITypeLib to browse a
coclass in a type library.

VB.VBP and VB.FRM are VB 4.0 files that uses BROWSEH to browse a type library
and to create a C/C++ header file from the browsed information.


BROWSECF.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation TypeLibrary Browse Helper Sample
**
**  BrowseHelpercf.cpp
**
**  CBrowseHelperCF (class factory) implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/
#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "browseh.h"

CBrowseHelperCF::CBrowseHelperCF(void)
{
    m_cRef = 0;
}

/*
 * CBrowseHelperCF::QueryInterface, AddRef, Release
 *
 * Purpose:
 *  Implements IUnknown::QueryInterface, AddRef, Release
 *
 */
STDMETHODIMP
CBrowseHelperCF::QueryInterface(REFIID iid, void FAR* FAR* ppv)
{
    *ppv = NULL;

    if (iid == IID_IUnknown || iid == IID_IClassFactory)
        *ppv = this;
    else
        return E_NOINTERFACE;

    AddRef();
    return NOERROR;
}

STDMETHODIMP_(ULONG)
CBrowseHelperCF::AddRef(void)
{
    return ++m_cRef;
}

STDMETHODIMP_(ULONG)
CBrowseHelperCF::Release(void)
{
    if(--m_cRef == 0)
    {
        delete this;
        return 0;
    }
    return m_cRef;
}

/*
 * CBrowseHelperCF::CreateInstance, LockServer
 *
 * Purpose:
 *  Implements IClassFactory::CreateInstance, LockServer
 *
 */
STDMETHODIMP
CBrowseHelperCF::CreateInstance(IUnknown FAR* punkOuter,
                         REFIID riid,
                         void FAR* FAR* ppv)
{
    CBrowseHelper FAR* pBrowseHelper;
    HRESULT hr;

    *ppv = NULL;

    // This implementation does not allow aggregation
    if (punkOuter)
        return CLASS_E_NOAGGREGATION;

    // Create an instance of the BrowseHelper automation object.
    hr = CBrowseHelper::Create(&pBrowseHelper);
    if (FAILED(hr))
        return hr;

    hr = pBrowseHelper->QueryInterface(riid, ppv);
    if (FAILED(hr))
    {
        delete pBrowseHelper;
        return hr;
    }
    return NOERROR;
}

STDMETHODIMP
CBrowseHelperCF::LockServer(BOOL fLock)
{
    extern ULONG g_cLock;

    if (fLock)
        g_cLock++;
    else
        g_cLock--;

    return NOERROR;
}


BROWSEH.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation TypeLibrary Browse Helper Sample
**
**  browse.cpp
**
**  CBrowseHelper implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "browseh.h"

/*
 * CBrowseHelper::Create
 *
 * Purpose:
 *  Creates an instance of the BrowseHelper automation object and initializes it.
 *
 * Parameters:
 *  ppBrowseHelper   Returns BrowseHelper automation object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CBrowseHelper::Create(CBrowseHelper FAR* FAR* ppBrowseHelper )
{
    HRESULT hr;
    CBrowseHelper FAR* pBrowseHelper = NULL;

    *ppBrowseHelper = NULL;

    // Create object.
    pBrowseHelper = new CBrowseHelper();
    if (pBrowseHelper == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }

    // Load type information for the application object from type library.
    hr = pBrowseHelper->LoadTypeInfo(IID_IBrowseHelper);
    if (FAILED(hr))
        goto error;

#ifdef _DEBUG
    lstrcpyn(pBrowseHelper->m_szClassName, TEXT("BrowseHelper"), 100);
#endif

    *ppBrowseHelper = pBrowseHelper;
    return NOERROR;

error:
    if (pBrowseHelper == NULL)
        return E_OUTOFMEMORY;

    delete pBrowseHelper;
    return hr;
}

/*
 * CBrowseHelper::CBrowseHelper
 *
 * Purpose:
 *  Constructor for CBrowseHelper object. Initializes members to NULL.
 *
 */
CBrowseHelper::CBrowseHelper()
{
    extern ULONG g_cObj;
    g_cObj++;
}

/*
 * CBrowseHelper::~CBrowseHelper
 *
 * Purpose:
 *  Destructor for CBrowseHelper object. Frees BrowseHelper message BSTR and default
 *  IDispatch implementation. Closes the aplication.
 *
 */
CBrowseHelper::~CBrowseHelper()
{
     extern ULONG g_cObj;

     g_cObj--;
}

STDMETHODIMP_(REFCLSID)
CBrowseHelper::GetInterfaceID()
{
    return IID_IBrowseHelper;
}

/*
 * CBrowseHelper::BrowseTypeLibrary
 *
 * Purpose:
 *  Opens and browses type library. Creates and returns a TypeLibrary object.
 *
 */
STDMETHODIMP_(ITypeLibrary FAR*)
CBrowseHelper::BrowseTypeLibrary(BSTR bstrPath)
{
    LPTYPELIB ptlib = NULL;
    LPDISPATCH pdisp;
    HRESULT hr;
    CTypeLibrary FAR* pTypeLibrary;

    hr = LoadTypeLib(bstrPath, &ptlib);
    if (FAILED(hr))
        {RaiseException(IDS_CannotFindTypeLibrary); return NULL;}

    hr = CTypeLibrary::Create(ptlib, &pTypeLibrary);
    if (FAILED(hr))
         {RaiseException(IDS_TypeLibraryCreationFailed); goto error;}
    hr = pTypeLibrary->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
    if (FAILED(hr))
        {RaiseException(IDS_Unexpected); goto error;}

    ptlib->Release();
    return (ITypeLibrary FAR*)pdisp;

error:
    if (ptlib)
        ptlib->Release();
    return NULL;
}


BROWSEH.H

#ifdef WIN32

#ifdef UNICODE
    #define FROM_OLE_STRING(str) str
    #define TO_OLE_STRING(str) str
#else
    #define FROM_OLE_STRING(str) ConvertToAnsi(str)
    char FAR* ConvertToAnsi(OLECHAR FAR* szW);
    #define TO_OLE_STRING(str) ConvertToUnicode(str)
    OLECHAR FAR* ConvertToUnicode(char FAR* szA);
    // Maximum length of string that can be converted between Ansi & Unicode
    #define STRCONVERT_MAXLEN 300
#endif

#else  // WIN16
  #define TCHAR char
  #define TEXT(sz) sz
  #define FROM_OLE_STRING(str) str
  #define TO_OLE_STRING(str) str
  #define LPTSTR LPSTR
#endif

#include "mydisp.h"
#include "tlb.h"

// MAX len of string table entries
#define STR_LEN   200

// String table constants
#define IDS_SERVERNAME             1
// These exception strings IDs that is will used in EXCEPINFO.wCode
#define IDS_Unexpected             1001
#define IDS_OutOfMemory            1002
#define IDS_InvalidIndex           1003
#define IDS_CollectionFull         1004
#define IDS_CannotFindTypeLibrary  1005
#define IDS_TypeLibraryCreationFailed 1006
#define IDS_WrongType               1007
#define IDS_InvalidProgid           1008
#define IDS_CouldNotCreateObject    1009
#define IDS_ObjectDoesNotSupportAutomation   1010
#define IDS_ObjectDoesNotProvideTypeInfo  1011
#define IDS_NotDualInterface 1012

// Function prototypes
BOOL InitDLL (HINSTANCE hInstance);
extern "C" STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID FAR *ppv);
extern "C" STDAPI DllCanUnloadNow(void);

//Forward declarations.
class CArrayBound;

class FAR CBrowseHelperCF : public IClassFactory
{
public:
    // IUnknown methods
    STDMETHOD(QueryInterface)(REFIID riid, LPVOID FAR* ppvObj);
    STDMETHOD_(ULONG, AddRef)(void);
    STDMETHOD_(ULONG, Release)(void);

    // IClassFactory methods
    STDMETHOD(CreateInstance)(IUnknown FAR* punkOuter, REFIID riid, void FAR* FAR* ppv);
    STDMETHOD(LockServer)(BOOL fLock);

    CBrowseHelperCF();

private:
    ULONG m_cRef;                   // Reference count
};

class FAR CBrowseHelper : public IBrowseHelper
{
public:
    // IBrowseHelper automation exposed methods
    STDMETHOD_(ITypeLibrary FAR*, BrowseTypeLibrary)(BSTR bstrPath);

    STDMETHOD_(REFCLSID, GetInterfaceID)();
    static HRESULT Create(CBrowseHelper FAR* FAR* ppBrowseHelper); // Creates and intializes Applicaton object
    CBrowseHelper();
    ~CBrowseHelper();
};

class FAR CTypeLibrary : public ITypeLibrary
{
public:
    // ITypeLibrary automation exposed properties & methods
    STDMETHOD_(BSTR, get_Name)();
    STDMETHOD_(BSTR, get_Documentation)();
    STDMETHOD_(long, get_HelpContext)();
    STDMETHOD_(BSTR, get_HelpFile)();
    STDMETHOD_(long, get_LocaleID)();
    STDMETHOD_(int, get_MajorVersion)();
    STDMETHOD_(int, get_MinorVersion)();
STDMETHOD_(BSTR, get_GUIDAsString)();
    STDMETHOD_(ICollection FAR*, get_TypeInfos)();

    STDMETHOD_(REFCLSID, GetInterfaceID)();
    static HRESULT Create(LPTYPELIB ptlib, CTypeLibrary FAR* FAR* ppTypeLibrary); // Creates and intializes TypeLibrary object
    CTypeLibrary();
    ~CTypeLibrary();

private:
    BSTR m_bstrName;               // Type Library name
    BSTR m_bstrDocumentation;      // Documentation
    unsigned long m_ulHelpContext; // HelpContext
    BSTR m_bstrHelpFile;           // HelpFile
    GUID m_guid;                   // GUID
LPOLESTR m_pszGUID;            // String form of GUID
    LCID m_lcid;                   // Locale ID
    unsigned short m_wMajorVer;    // Major version
    unsigned short m_wMinorVer;    // Minor version
    LPTYPELIB m_ptlib;             // ITypeLib* of type library.
    LPDISPATCH m_pdispTypeInfos;   // Collection of typeinfos contained by this typelib
};

// CTypeInfo is the base class for all the TypeInfos (CInterface, CDispinterface,
// CModule, CCoClass, CEnum, CStruct, CUnion, CAlias)
class FAR CTypeInfo : public ITypeInformation
{
public:
    // ITypeInfo automation exposed properties & methods
    STDMETHOD_(BSTR, get_Name)();
    STDMETHOD_(BSTR, get_Documentation)();
    STDMETHOD_(long, get_HelpContext)();
    STDMETHOD_(BSTR, get_HelpFile)();
    STDMETHOD_(TYPEKIND, get_TypeInfoKind)();
    STDMETHOD_(short, get_TypeFlags)();
    STDMETHOD_(BSTR, get_GUIDAsString)();

    STDMETHOD_(REFCLSID, GetInterfaceID)();
    STDMETHOD(_InitTypeInfo)(LPTYPEINFO ptinfo); // Called by derived class to intialize this base class
    static HRESULT Create(LPTYPEINFO ptinfo, CTypeInfo FAR* FAR* ppTypeInfo); // Creates and intializes TypeInfo object
    CTypeInfo();
    ~CTypeInfo();

protected:
short m_wTypeFlags;            // TYPEFLAGS

private:
    BSTR m_bstrName;               // TypeInfo name
    BSTR m_bstrDocumentation;      // Documentation
    unsigned long m_ulHelpContext; // Help context
    BSTR m_bstrHelpFile;           // Help file
    TYPEKIND m_typekind;           // Type of TypeInfo. See TYPEKIND enumeration.
    GUID m_guid;                   // GUID
LPOLESTR m_pszGUID;            // String form of GUID
};

class FAR CInterface : public CTypeInfo
{
public:
    // IInterface automation exposed properties & methods
    STDMETHOD_(ICollection FAR*, get_Functions)();
    STDMETHOD_(IInterface FAR*, get_BaseInterface)();

    STDMETHOD_(REFCLSID, GetInterfaceID)();
    static HRESULT Create(LPTYPEINFO ptinfo, CInterface FAR* FAR* ppInterface); // Creates and intializes Interface object
    CInterface();
    ~CInterface();

private:
    LPTYPEINFO m_ptinfo;             // ITypeInfo* of interface typeinfo.
    LPDISPATCH m_pdispFunctions;     // Collection of functions in interface.
LPDISPATCH m_pdispBaseInterface; // Base interface of this interface.
};

class FAR CDispinterface : public CTypeInfo
{
public:
    // IDispinterface automation exposed properties & methods
    STDMETHOD_(ICollection FAR*, get_Properties)();
    STDMETHOD_(ICollection FAR*, get_Methods)();
STDMETHOD_(IInterface FAR*, get_Interface)();

    STDMETHOD_(REFCLSID, GetInterfaceID)();
    static HRESULT Create(LPTYPEINFO ptinfo, CDispinterface FAR* FAR* ppDispinterface); // Creates and intializes Dispinterface object
    CDispinterface();
    ~CDispinterface();

private:
    LPTYPEINFO m_ptinfo;              // ITypeInfo* of dispinterface typeinfo.
    LPDISPATCH m_pdispProperties;     // Collection of properties in dispinterface
    LPDISPATCH m_pdispMethods;        // Collection of methods in dispinterface
LPDISPATCH m_pdispInterface;      // Interface part of dual interface, if this is a dual interface
};

class FAR CModule : public CTypeInfo
{
public:
    // IModule automation exposed properties & methods
    STDMETHOD_(ICollection FAR*, get_Functions)();

    STDMETHOD_(REFCLSID, GetInterfaceID)();
    static HRESULT Create(LPTYPEINFO ptinfo, CModule FAR* FAR* ppInterface); // Creates and intializes Module object
    CModule();
    ~CModule();

private:
    LPTYPEINFO m_ptinfo;              // ITypeInfo* of module typeinfo.
    LPDISPATCH m_pdispFunctions;      // Collection of functions in module
};

class FAR CCoClass : public CTypeInfo
{
public:
    // ICoClass automation exposed properties & methods
    STDMETHOD_(ICollection FAR*, get_Interfaces)();

    STDMETHOD_(REFCLSID, GetInterfaceID)();
    static HRESULT Create(LPTYPEINFO ptinfo, CCoClass FAR* FAR* ppCoClass); // Creates and intializes CoClass object
    CCoClass();
    ~CCoClass();

private:
    LPTYPEINFO m_ptinfo;              // ITypeInfo* of coclass typeinfo.
    LPDISPATCH m_pdispInterfaces;     // Collection of interfaces/dispinterfaces in coclass
};

class FAR CEnum : public CTypeInfo
{
public:
    // IEnum automation exposed properties & methods
    STDMETHOD_(ICollection FAR*, get_Elements)();

    STDMETHOD_(REFCLSID, GetInterfaceID)();
    static HRESULT Create(LPTYPEINFO ptinfo, CEnum FAR* FAR* ppEnum); // Creates and intializes Enum object
    CEnum();
    ~CEnum();

private:
    LPTYPEINFO m_ptinfo;             // ITypeInfo* of enum typeinfo.
    LPDISPATCH m_pdispElements;      // Collection of elements in enum.
};

class FAR CStruct : public CTypeInfo
{
public:
     // IStruct automation exposed properties & methods
    STDMETHOD_(ICollection FAR*, get_Members)();

    STDMETHOD_(REFCLSID, GetInterfaceID)();
    static HRESULT Create(LPTYPEINFO ptinfo, CStruct FAR* FAR* ppStruct); // Creates and intializes Struct object
    CStruct();
    ~CStruct();

private:
    LPTYPEINFO m_ptinfo;            // ITypeInfo* of struct typeinfo.
    LPDISPATCH m_pdispMembers;      // Collection of members in struct.
};

class FAR CUnion : public CTypeInfo
{
public:
    // IUnion automation exposed properties & methods
    STDMETHOD_(ICollection FAR*, get_Members)();

    STDMETHOD_(REFCLSID, GetInterfaceID)();
    static HRESULT Create(LPTYPEINFO ptinfo, CUnion FAR* FAR* ppUnion); // Creates and intializes Struct object
    CUnion();
    ~CUnion();

private:
    LPTYPEINFO m_ptinfo;            // ITypeInfo* of union typeinfo.
    LPDISPATCH m_pdispMembers;      // Collection of members in union.
};

class FAR CAlias : public CTypeInfo
{
public:
    // IAlias automation exposed properties & methods
    STDMETHOD_(ITypeDesc FAR*, get_BaseType)();

    STDMETHOD_(REFCLSID, GetInterfaceID)();
    static HRESULT Create(LPTYPEINFO ptinfo, CAlias FAR* FAR* ppAlias); // Creates and intializes Alias object
    CAlias();
    ~CAlias();

private:
    LPTYPEINFO m_ptinfo;             // ITypeInfo* of alias typeinfo.
    LPDISPATCH m_pdispTypeDescBase;  // IDispatch of ITypeDesc which describes the base type of this alias.
};

// CFunction represents all functions (including methods).
class FAR CFunction : public IFunction
{
public:
    // IFunction automation exposed properties & methods
    STDMETHOD_(BSTR, get_Name)();
    STDMETHOD_(BSTR, get_Documentation)();
    STDMETHOD_(long, get_HelpContext)();
    STDMETHOD_(BSTR, get_HelpFile)();
    STDMETHOD_(ITypeDesc FAR*, get_ReturnType)();
    STDMETHOD_(ICollection FAR*, get_Parameters)();
    STDMETHOD_(MEMBERID, get_Memberid)();
    STDMETHOD_(CALLCONV, get_CallConvention)();
    STDMETHOD_(FUNCKIND, get_FuncKind)();
    STDMETHOD_(INVOKEKIND, get_InvocationKind)();
STDMETHOD_(short, get_NumberOfOptionalParams)();
    STDMETHOD_(short, get_OffsetInVtbl)();
STDMETHOD_(unsigned short, get_FuncFlags)();
    STDMETHOD_(OBJTYPE, get_Kind)();

    STDMETHOD_(REFCLSID, GetInterfaceID)();
    static HRESULT Create(LPTYPEINFO ptinfo, unsigned short nIndex, CFunction FAR* FAR* ppFunction); // Creates and intializes Function object
    CFunction();
    ~CFunction();

private:
    BSTR m_bstrName;                     // Function name.
    BSTR m_bstrDocumentation;            // Documentation.
    unsigned long m_ulHelpContext;       // Help context.
    BSTR m_bstrHelpFile;                 // Help file.
    LPDISPATCH m_pdispTypeDesc;          // IDispatch of ITypeDesc that described return type.
    LPDISPATCH m_pdispParameters;        // Collection of parameters function.
    LPFUNCDESC m_pfuncdesc;              // FUNCDESC of function.
    LPTYPEINFO m_ptinfoFunction;         // TypeInfo of which this function is an element.
};

// CProperty represents dispinterface properties and structure & union members.
class FAR CProperty : public IProperty
{
public:
    // IProperty automation exposed properties & methods
    STDMETHOD_(BSTR, get_Name)();
    STDMETHOD_(BSTR, get_Documentation)();
    STDMETHOD_(long, get_HelpContext)();
    STDMETHOD_(BSTR, get_HelpFile)();
    STDMETHOD_(ITypeDesc FAR*, get_Type)();
    STDMETHOD_(MEMBERID, get_Memberid)();
    STDMETHOD_(OBJTYPE, get_Kind)();

    STDMETHOD_(REFCLSID, GetInterfaceID)();
    static HRESULT Create(LPTYPEINFO ptinfo, LPVARDESC pvardesc, CProperty FAR* FAR* ppProperty); // Creates and intializes Property object
    CProperty();
    ~CProperty();

private:
    BSTR m_bstrName;                     // Property name.
    BSTR m_bstrDocumentation;            // Documentation.
    unsigned long m_ulHelpContext;       // Help Context.
    BSTR m_bstrHelpFile;                 // Helpfile.
    MEMBERID m_memid;                    // MEMBERID of property.
    LPDISPATCH m_pdispTypeDesc;          // ITypeDesc that describes type of property.
};

class FAR CConstant : public IConstant
{
public:
    // IConstant automation exposed properties & methods
    STDMETHOD_(BSTR, get_Name)();
    STDMETHOD_(BSTR, get_Documentation)();
    STDMETHOD_(long, get_HelpContext)();
    STDMETHOD_(BSTR, get_HelpFile)();
    STDMETHOD_(ITypeDesc FAR*, get_Type)();
    STDMETHOD_(VARIANT, get_Value)();
    STDMETHOD_(MEMBERID, get_Memberid)();
    STDMETHOD_(OBJTYPE, get_Kind)();

    STDMETHOD_(REFCLSID, GetInterfaceID)();
    static HRESULT Create(LPTYPEINFO ptinfo, LPVARDESC pvardesc, CConstant FAR* FAR* ppConstant); // Creates and intializes Constant object
    CConstant();
    ~CConstant();

private:
    BSTR m_bstrName;                       // Constant name
    BSTR m_bstrDocumentation;              // Documentation
    unsigned long m_ulHelpContext;         // Help context
    BSTR m_bstrHelpFile;                   // Helpfile
    LPDISPATCH m_pdispTypeDesc;            // ITypeDesc that describes type of constant.
    MEMBERID m_memid;                      // MEMBERID.
    VARIANT m_vValue;                      // Constant value.
};

class FAR CParameter : public IParameter
{
public:
    // IParameter automation exposed properties & methods
    STDMETHOD_(BSTR, get_Name)();
    STDMETHOD_(ITypeDesc FAR*, get_Type)();
    STDMETHOD_(int, get_IDLFlags)();
    STDMETHOD_(OBJTYPE, get_Kind)();

    STDMETHOD_(REFCLSID, GetInterfaceID)();
    static HRESULT Create(LPTYPEINFO ptinfo, BSTR bstrName, TYPEDESC FAR* ptypedesc,
         IDLDESC FAR* pidldesc, CParameter FAR* FAR* ppParameter);  // Creates and intializes Parameter object
    CParameter();
    ~CParameter();

private:
    BSTR m_bstrName;                      // Parameter name.
    unsigned short m_wIDLFlags;           // specifies in/out/in-out/none
    LPDISPATCH m_pdispTypeDesc;           // ITypeDesc* that describes type of parameter.
};

// Describes a type
class FAR CTypeDesc : public ITypeDesc
{
public:
    // ITypeDesc automation exposed properties & methods
    STDMETHOD_(short, get_Type)();
    STDMETHOD_(ITypeInformation FAR*, get_UserDefinedDesc)();
    STDMETHOD_(IArrayDesc FAR*, get_ArrayDesc)();
    STDMETHOD_(ITypeDesc FAR*, get_PointerDesc)();

    STDMETHOD_(REFCLSID, GetInterfaceID)();
    static HRESULT Create(LPTYPEINFO ptinfo, TYPEDESC FAR* ptypedesc, CTypeDesc FAR* FAR* ppTypeDesc); // Creates and intializes TypeDesc object
    CTypeDesc();
    ~CTypeDesc();

private:
    VARTYPE m_vartype;                  // Type
    LPDISPATCH m_pdispUserDefinedDesc;  // If m_vartype==VT_USERDEFINED, contains ITypeInformation* of user-defined type.,
    LPDISPATCH m_pdispArrayDesc;        // if m_vartype==VT_CARRAY, contains IArrayDesc*
    LPDISPATCH m_pdispPointerDesc;      // if m_vartype==VT_PTR contains ITypeDesc* of type pointed to.
};

// Describes C-Style array
class FAR CArrayDesc : public IArrayDesc
{
public:
    // IArrayDesc automation exposed properties & methods
    STDMETHOD_(ITypeDesc FAR*, get_ElementType)();
    STDMETHOD_(ICollection FAR*, get_ArrayBounds)();

    STDMETHOD_(REFCLSID, GetInterfaceID)();
    static HRESULT Create(LPTYPEINFO ptinfo, ARRAYDESC FAR* parraydesc, CArrayDesc FAR* FAR* ppArrayDesc); // Creates and intializes ArrayDesc object
    CArrayDesc();
    ~CArrayDesc();

private:
    LPDISPATCH m_pdispTypeDescElement;     // ITypeDesc that descibed type of array element.
    LPDISPATCH m_pdispArrayBounds;         // IArrayBound that describes array bounds.
};

class FAR CArrayBound : public IArrayBound
{
public:
    // IArrayBound automation exposed properties & methods
    STDMETHOD_(long, get_ElementsCount)();
    STDMETHOD_(long, get_LowerBound)();

    STDMETHOD_(REFCLSID, GetInterfaceID)();
    static HRESULT Create(unsigned long cElements, long lLBound, CArrayBound FAR* FAR* ppArrayBound); // Creates and intializes ArrayBound object

private:
    unsigned long m_cElements;             // Count of elements in array.
    long m_lLBound;                        // Lower bound of array.
};

// Generic collection class that holds all collections.
class FAR CCollection : public ICollection
{
public:
    // ICollection automation exposed properties & methods
    STDMETHOD_(long, get_Count)();
    STDMETHOD_(LPDISPATCH, get_Item)(long lIndex);
    STDMETHOD_(LPUNKNOWN, get__NewEnum)();

    // CCollection methods
    STDMETHOD_(void, Add)(LPDISPATCH pdispItem);
    STDMETHOD_(REFCLSID, GetInterfaceID)();
    static HRESULT Create(ULONG lMaxSize, long lLBound, CCollection FAR* FAR* ppCollection); // Creates and intializes Collection object
    CCollection();
    ~CCollection();

private:
    SAFEARRAY FAR *m_psa;          // Safe array that holds Collection collection items.
    ULONG m_cElements;             // Number of items in Collection collection.
    ULONG m_cMax;                  // Maximum number of items Collection collection can hold.
    long m_lLBound;                // Lower bound of index of Collection collection.
};

class FAR CEnumVariant : public IEnumVARIANT
{
public:
    // IUnknown methods
    STDMETHOD(QueryInterface)(REFIID riid, LPVOID FAR* ppvObj);
    STDMETHOD_(ULONG, AddRef)();
    STDMETHOD_(ULONG, Release)();

    // IEnumVARIANT methods
    STDMETHOD(Next)(ULONG cElements, VARIANT FAR* pvar, ULONG FAR* pcElementFetched);
    STDMETHOD(Skip)(ULONG cElements);
    STDMETHOD(Reset)();
    STDMETHOD(Clone)(IEnumVARIANT FAR* FAR* ppenum);

    static HRESULT Create(SAFEARRAY FAR*, ULONG, CEnumVariant FAR* FAR*); // Creates and intializes Enumerator
    CEnumVariant();
    ~CEnumVariant();

private:
    ULONG m_cRef;           // Reference count
    ULONG m_cElements;      // Number of elements in enumerator.
    long m_lLBound;         // Lower bound of index.
    long m_lCurrent;        // Current index.
    SAFEARRAY FAR* m_psa;   // Safe array that holds elements.
};


COCLASS.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation TypeLibrary Browse Helper Sample
**
**  coclass.cpp
**
**  CCoClass implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "browseh.h"

/*
 * CCoClass::Create
 *
 * Purpose:
 *  Creates an instance of the CoClass automation object and initializes it.
 *
 * Parameters:
 *  ptinfo       TypeInfo of coclass.
 *  ppCoClass    Returns CoClass automation object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CCoClass::Create(LPTYPEINFO ptinfo, CCoClass FAR* FAR* ppCoClass)
{
    HRESULT hr;
    CCoClass FAR* pCoClass = NULL;

    *ppCoClass = NULL;

    // Create object.
    pCoClass = new CCoClass();
    if (pCoClass == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }
    // Load type information for the object from type library.
    hr = pCoClass->LoadTypeInfo(IID_ICoClass);
    if (FAILED(hr))
        goto error;

    // Ask base class (CTypeInfo) to initialize.
    hr = pCoClass->_InitTypeInfo(ptinfo);
    if (FAILED(hr))
        goto error;

    ptinfo->AddRef();
    pCoClass->m_ptinfo = ptinfo;

#ifdef _DEBUG
    lstrcpyn(pCoClass->m_szClassName, TEXT("CoClass"), 100);
#endif

    *ppCoClass = pCoClass;
    return NOERROR;

error:
    if (pCoClass == NULL) return E_OUTOFMEMORY;
    if (pCoClass->m_ptinfo) pCoClass->m_ptinfo->Release();

    // Set to NULL to prevent destructor from attempting to free again
    pCoClass->m_ptinfo = NULL;

    delete pCoClass;
    return hr;
}

/*
 * CCoClass::CCoClass
 *
 * Purpose:
 *  Constructor for CCoClass object. Initializes members to NULL.
 *
 */
CCoClass::CCoClass()
{
    m_pdispInterfaces = NULL;
    m_ptinfo = NULL;
}

/*
 * CCoClass::~CCoClass
 *
 * Purpose:
 *  Destructor for CCoClass object.
 *
 */
CCoClass::~CCoClass()
{
    if (m_pdispInterfaces) m_pdispInterfaces->Release();
    if (m_ptinfo) m_ptinfo->Release();
}

STDMETHODIMP_(REFCLSID)
CCoClass::GetInterfaceID()
{
    return IID_ICoClass;
}

STDMETHODIMP_(ICollection FAR*)
CCoClass::get_Interfaces()
{
    HRESULT hr;
    CCollection FAR* pCollection = NULL;
    CInterface FAR* pInterface;
    CCoClass FAR* pCoClass;
    LPDISPATCH pdisp;
    HREFTYPE hreftype;
    LPTYPEATTR ptypeattr = NULL;
    LPTYPEINFO ptinfoInterface = NULL;
    LPTYPELIB ptlib = NULL;
    unsigned int nIndex;
    unsigned short n;
    TYPEKIND typekind;

    if (m_pdispInterfaces == NULL)
    {
        hr = m_ptinfo->GetTypeAttr(&ptypeattr);
        if (FAILED(hr))
            {RaiseException(IDS_Unexpected); return NULL;}
        hr = CCollection::Create(ptypeattr->cImplTypes, 0, &pCollection);
        if (FAILED(hr))
            {RaiseException(IDS_Unexpected); goto error;}

        // Enumerate interfaces/dispinterfaces in coclass and return a collection of these.
        for (n=0; n<ptypeattr->cImplTypes; n++)
        {
            hr = m_ptinfo->GetRefTypeOfImplType(n, &hreftype);
            if (FAILED(hr))
                {RaiseException(IDS_Unexpected); goto error;}
            hr = m_ptinfo->GetRefTypeInfo(hreftype, &ptinfoInterface);
            if (FAILED(hr))
                {RaiseException(IDS_Unexpected); goto error;}
            hr = ptinfoInterface->GetContainingTypeLib(&ptlib, &nIndex);
            if (FAILED(hr))
                {RaiseException(IDS_Unexpected); goto error;}
            hr = ptlib->GetTypeInfoType(nIndex, &typekind);
            if (FAILED(hr))
                {RaiseException(IDS_Unexpected); goto error;}
            ptlib->Release();
            ptlib = NULL;

            switch (typekind)
            {
                case TKIND_INTERFACE:
                    hr = CInterface::Create(ptinfoInterface, &pInterface);
                    if (FAILED(hr))
                        {RaiseException(IDS_Unexpected); goto error;}
                    pInterface->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
                    break;

                case TKIND_DISPATCH:
                    hr = CCoClass::Create(ptinfoInterface, &pCoClass);
                    if (FAILED(hr))
                        {RaiseException(IDS_Unexpected); goto error;}
                    pCoClass->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
                    break;
            }

            ptinfoInterface->Release();
            ptinfoInterface = NULL;
            pCollection->Add(pdisp);
            pdisp->Release();
        }
        pCollection->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
        m_pdispInterfaces = pdisp;
        m_ptinfo->ReleaseTypeAttr(ptypeattr);
    }
    m_pdispInterfaces->AddRef();
    return (ICollection FAR*)m_pdispInterfaces;

error:
    if (ptypeattr) m_ptinfo->ReleaseTypeAttr(ptypeattr);
    if (pCollection) delete pCollection;
    if (ptlib) ptlib->Release();
    if (ptinfoInterface) ptinfoInterface->Release();
    return NULL;
}


COLLECT.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation TypeLibrary Browse Helper Sample
**
**  collect.cpp
**
**  CCollection implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "browseh.h"

/*
 * CCollection::Create
 *
 * Purpose:
 *  Creates an instance of a collection object and initializes it.
 *
 * Parameters:
 *  lMaxSize   Maximum number of items that can added to collection.
 *  lLBound    Lower bound of index of collection.
 *  ppCollection    Returns collection object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CCollection::Create(ULONG lMaxSize, long lLBound, CCollection FAR* FAR* ppCollection)
{
    HRESULT hr;
    CCollection FAR* pCollection = NULL;
    SAFEARRAYBOUND sabound[1];

    *ppCollection = NULL;

    // Create new collection
    pCollection = new CCollection();
    if (pCollection == NULL)
        goto error;
    // Load type information for the collection from type library.
    hr = pCollection->LoadTypeInfo(IID_ICollection);
    if (FAILED(hr))
        goto error;

    // If lMaxSize is 0 increment it to 1 or SafeArrayCreate will fail
    if (lMaxSize == 0)
       lMaxSize = 1;
    pCollection->m_cMax = lMaxSize;
    pCollection->m_cElements = 0;
    pCollection->m_lLBound = lLBound;

    // Create a safe array which is used to implement the collection
    sabound[0].cElements = lMaxSize;
    sabound[0].lLbound = lLBound;
    pCollection->m_psa = SafeArrayCreate(VT_VARIANT, 1, sabound);
    if (pCollection->m_psa == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }

#ifdef _DEBUG
    lstrcpyn(pCollection->m_szClassName, TEXT("Collection"), 100);
#endif

    *ppCollection = pCollection;
    return NOERROR;

error:
    if (pCollection == NULL)
        return E_OUTOFMEMORY;
    if (pCollection->m_psa)
        SafeArrayDestroy(pCollection->m_psa);

    pCollection->m_psa = NULL;

    delete pCollection;
    return hr;
}

/*
 * CCollection::CCollection
 *
 * Purpose:
 *  Constructor for CCollection object. Initializes members to NULL.
 *
 */
CCollection::CCollection()
{
    m_psa = NULL;
}

/*
 * CCollection::~CCollection
 *
 * Purpose:
 *  Destructor for CCollection object.
 *
 */
CCollection::~CCollection()
{
     if (m_psa) SafeArrayDestroy(m_psa);
}

STDMETHODIMP_(REFCLSID)
CCollection::GetInterfaceID()
{
    return IID_ICollection;
}

/*
 * CCollection::get_Count
 *
 * Purpose:
 *  Returns number of items in collection.
 *
 */
STDMETHODIMP_(long)
CCollection::get_Count(void)
{
    return m_cElements;
}

/*
 * CCollection::get_Item
 *
 * Purpose:
 *  Retrieves item from collection, given an index.
 *
 * Parameters:
 *   lIndex   Index of item to be retrieved.
 *
 * Returns
 *  IDispatch of item retrieved from collection.
 *
 */
STDMETHODIMP_(LPDISPATCH)
CCollection::get_Item(long lIndex)
{
    HRESULT hr;
    VARIANT v;
    LPDISPATCH pdisp = NULL;

    // Check if integer index is within range
    if (lIndex < m_lLBound || lIndex >= (long)(m_lLBound+m_cElements))
        {RaiseException(IDS_InvalidIndex); return NULL;}

    // Retrieve and return item. Note that SafeArrayGetElement AddRefs so an additional AddRef
    // is not required.
    VariantInit(&v);
    hr = SafeArrayGetElement(m_psa, &lIndex, &v);
    if (FAILED(hr))
        {RaiseException(IDS_Unexpected); return NULL;}
    return V_DISPATCH(&v);
}

/*
 * CCollection::get_NewEnum
 *
 * Purpose:
 *  Returns an enumerator (IEnumVARIANT) for the items curently in the collection.
 *  The NewEnum property is restricted and so is invisible to users of an
 *  automation controller's scripting language. Automation controllers that support
 *  a 'For Each' statement to iterate through the elements of a collection will use
 *  the enumerator returned by NewEnum. The enumerator creates a snapshot of the
 *  the current state of the collection.
 *
 */
STDMETHODIMP_(LPUNKNOWN)
CCollection::get__NewEnum(void)
{
    CEnumVariant FAR* penum = NULL;;
    LPUNKNOWN punkEnumVariant = NULL;
    HRESULT hr;

    // Create new enumerator for items currently in collection and QI for IUnknown
    hr = CEnumVariant::Create(m_psa, m_cElements, &penum);
    if (FAILED(hr))
        {RaiseException(IDS_OutOfMemory); goto error;}
    hr = penum->QueryInterface(IID_IUnknown, (VOID FAR* FAR*)&punkEnumVariant);
    if (FAILED(hr))
        {RaiseException(IDS_Unexpected); goto error;}
    return punkEnumVariant;

error:
    if (penum)
        delete penum;
    return NULL;
}

/*
 * CCollection::Add
 *
 * Purpose:
 *  Adds an item to the collection.
 *
 * Parameters:
 *  pdispItemAdd    Item to be added to collection.
 *
 */
STDMETHODIMP_(void)
CCollection::Add(LPDISPATCH pdispItem)
{
    HRESULT hr;
    LONG l;
    VARIANT v;

    // Is the collection full?
    if (m_cElements == m_cMax)
        {RaiseException(IDS_CollectionFull); return;}

    l = m_lLBound+m_cElements;
    VariantInit(&v);
    V_VT(&v) = VT_DISPATCH;
    V_DISPATCH(&v) = pdispItem;

    hr = SafeArrayPutElement(m_psa, &l, &v);
    if (FAILED(hr))
        {RaiseException(IDS_Unexpected); return;}

    m_cElements++;
}


CONSTANT.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation Constant Browse Helper Sample
**
**  Constant.cpp
**
**  CConstant implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "browseh.h"

/*
 * CConstant::Create
 *
 * Purpose:
 *  Creates an instance of the Constant automation object and initializes it.
 *
 * Parameters:
 *  ptinfo        TypeInfo of which this constant is an element.
 *  pvardesc      VARDESC that describes this constant.
 *  ppConstant    Returns Constant automation object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CConstant::Create(LPTYPEINFO ptinfo, LPVARDESC pvardesc, CConstant FAR* FAR* ppConstant)
{
    HRESULT hr;
    CConstant FAR* pConstant = NULL;
    BSTR bstr;
    unsigned int cNames;
    CTypeDesc FAR* pTypeDesc = NULL;

    *ppConstant = NULL;

    // Create object.
    pConstant = new CConstant();
    if (pConstant == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }

    // Load type information for the application object from type library.
    hr = pConstant->LoadTypeInfo(IID_IConstant);
    if (FAILED(hr))
        goto error;

    pConstant->m_memid = pvardesc->memid;
    ptinfo->GetNames(pvardesc->memid, &bstr, 1, &cNames);
    pConstant->m_bstrName = bstr;

    // Type of constant.
    hr = CTypeDesc::Create(ptinfo, &pvardesc->elemdescVar.tdesc, &pTypeDesc);
    if (FAILED(hr))
        goto error;
    pTypeDesc->QueryInterface(IID_IDispatch, (LPVOID FAR*)&pConstant->m_pdispTypeDesc);

    // Constant value.
    if (pvardesc->varkind == VAR_CONST)
        pConstant->m_vValue = *pvardesc->lpvarValue;
    else goto error;

    hr = ptinfo->GetDocumentation(pvardesc->memid, NULL, &pConstant->m_bstrDocumentation,
             &pConstant->m_ulHelpContext, &pConstant->m_bstrHelpFile);
    if (FAILED(hr))
        goto error;

#ifdef _DEBUG
    lstrcpyn(pConstant->m_szClassName, TEXT("Constant"), 100);
#endif

    *ppConstant = pConstant;
    return NOERROR;

error:
    if (pConstant == NULL) return E_OUTOFMEMORY;
    if (pConstant->m_bstrName) SysFreeString(pConstant->m_bstrName);
    if (pConstant->m_bstrDocumentation) SysFreeString(pConstant->m_bstrDocumentation);
    if (pConstant->m_bstrHelpFile) SysFreeString(pConstant->m_bstrHelpFile);

    // Set to NULL to prevent destructor from attempting to free again
    pConstant->m_bstrName = NULL;
    pConstant->m_bstrDocumentation = NULL;
    pConstant->m_bstrHelpFile = NULL;

    delete pConstant;
    return hr;
}

/*
 * CConstant::CConstant
 *
 * Purpose:
 *  Constructor for CConstant object. Initializes members to NULL.
 *
 */
CConstant::CConstant()
{
    m_bstrName = NULL;
    m_bstrDocumentation = NULL;
    m_bstrHelpFile = NULL;
    m_pdispTypeDesc = NULL;
}

/*
 * CConstant::~CConstant
 *
 * Purpose:
 *  Destructor for CConstant object.
 *
 */
CConstant::~CConstant()
{
     if (m_bstrName) SysFreeString(m_bstrName);
     if (m_bstrDocumentation) SysFreeString(m_bstrDocumentation);
     if (m_bstrHelpFile) SysFreeString(m_bstrHelpFile);
     if (m_pdispTypeDesc) m_pdispTypeDesc->Release();
}

STDMETHODIMP_(REFCLSID)
CConstant::GetInterfaceID()
{
    return IID_IConstant;
}

STDMETHODIMP_(BSTR)
CConstant::get_Name()
{
    return SysAllocString(m_bstrName);
}

STDMETHODIMP_(BSTR)
CConstant::get_Documentation()
{
    return SysAllocString(m_bstrDocumentation);
}

STDMETHODIMP_(long)
CConstant::get_HelpContext()
{
    return (long)m_ulHelpContext;
}

STDMETHODIMP_(BSTR)
CConstant::get_HelpFile()
{
    return SysAllocString(m_bstrHelpFile);
}

STDMETHODIMP_(ITypeDesc FAR*)
CConstant::get_Type()
{
    m_pdispTypeDesc->AddRef();
    return (ITypeDesc FAR*)m_pdispTypeDesc;
}

STDMETHODIMP_(VARIANT)
CConstant::get_Value()
{
    return m_vValue;
}

STDMETHODIMP_(MEMBERID)
CConstant::get_Memberid()
{
    return m_memid;
}

STDMETHODIMP_(OBJTYPE)
CConstant::get_Kind()
{
    return TYPE_CONSTANT;
}


DISPFACE.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation TypeLibrary Browse Helper Sample
**
**  dispface.cpp
**
**  CDispinterface implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "browseh.h"

/*
 * CDispinterface::Create
 *
 * Purpose:
 *  Creates an instance of the Dispinterface automation object and initializes it.
 *
 * Parameters:
 *  ptinfo             TypeInfo of dispinterface.
 *  ppDispinterface    Returns Dispinterface automation object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CDispinterface::Create(LPTYPEINFO ptinfo, CDispinterface FAR* FAR* ppDispinterface)
{
    HRESULT hr;
    CDispinterface FAR* pDispinterface = NULL;

    *ppDispinterface = NULL;

    // Create object.
    pDispinterface = new CDispinterface();
    if (pDispinterface == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }
    // Load type information for the object from type library.
    hr = pDispinterface->LoadTypeInfo(IID_IDispinterface);
    if (FAILED(hr))
        goto error;

    // Ask base class (CTypeInfo) to initialize.
    hr = pDispinterface->_InitTypeInfo(ptinfo);
    if (FAILED(hr))
        goto error;

    ptinfo->AddRef();
    pDispinterface->m_ptinfo = ptinfo;

#ifdef _DEBUG
    lstrcpyn(pDispinterface->m_szClassName, TEXT("Dispinterface"), 100);
#endif

    *ppDispinterface = pDispinterface;
    return NOERROR;

error:
    if (pDispinterface == NULL) return E_OUTOFMEMORY;
    if (pDispinterface->m_ptinfo) pDispinterface->m_ptinfo->Release();

    // Set to NULL to prevent destructor from attempting to free again
    pDispinterface->m_ptinfo = NULL;

    delete pDispinterface;
    return hr;
}

/*
 * CDispinterface::CDispinterface
 *
 * Purpose:
 *  Constructor for CDispinterface object. Initializes members to NULL.
 *
 */
CDispinterface::CDispinterface()
{
    m_pdispProperties = NULL;
    m_pdispMethods = NULL;
m_pdispInterface = NULL;
    m_ptinfo = NULL;
}

/*
 * CDispinterface::~CDispinterface
 *
 * Purpose:
 *  Destructor for CDispinterface object.
 *
 */
CDispinterface::~CDispinterface()
{
    if (m_pdispProperties) m_pdispProperties->Release();
    if (m_pdispMethods) m_pdispMethods->Release();
if (m_pdispInterface) m_pdispInterface->Release();
    if (m_ptinfo) m_ptinfo->Release();
}

STDMETHODIMP_(REFCLSID)
CDispinterface::GetInterfaceID()
{
    return IID_IDispinterface;
}

STDMETHODIMP_(ICollection FAR*)
CDispinterface::get_Methods()
{
    HRESULT hr;
    CFunction FAR* pFunction;
    CCollection FAR* pCollection = NULL;
    LPDISPATCH pdisp;
    LPTYPEATTR ptypeattr = NULL;
    unsigned short n;

    if (m_pdispMethods == NULL)
    {
        hr = m_ptinfo->GetTypeAttr(&ptypeattr);
        if (FAILED(hr))
            {RaiseException(IDS_Unexpected); return NULL;}
        hr = CCollection::Create(ptypeattr->cFuncs, 0, &pCollection);
        if (FAILED(hr))
            {RaiseException(IDS_Unexpected); goto error;}
        // Enumerate methods and return a collection of these.
        for (n=0; n<ptypeattr->cFuncs; n++)
        {
            hr = CFunction::Create(m_ptinfo, n, &pFunction);
            if (FAILED(hr))
                {RaiseException(IDS_Unexpected); goto error;}
            pFunction->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
            pCollection->Add(pdisp);
            pdisp->Release();
        }
        pCollection->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
        m_pdispMethods = pdisp;
        m_ptinfo->ReleaseTypeAttr(ptypeattr);
    }
    m_pdispMethods->AddRef();
    return (ICollection FAR*)m_pdispMethods;

error:
    if (ptypeattr) m_ptinfo->ReleaseTypeAttr(ptypeattr);
    if (pCollection) delete pCollection;
    return NULL;
}

STDMETHODIMP_(ICollection FAR*)
CDispinterface::get_Properties()
{
    HRESULT hr;
    CProperty FAR* pProperty;
    CCollection FAR* pCollection = NULL;
    LPDISPATCH pdisp;
    LPVARDESC pvardesc = NULL;
    LPTYPEATTR ptypeattr = NULL;
    unsigned short n;

    if (m_pdispProperties == NULL)
    {
        hr = m_ptinfo->GetTypeAttr(&ptypeattr);
        if (FAILED(hr))
            {RaiseException(IDS_Unexpected); return NULL;}
        hr = CCollection::Create(ptypeattr->cVars, 0, &pCollection);
        if (FAILED(hr))
            {RaiseException(IDS_Unexpected); goto error;}
        // Enumerate properties and return a collection of these.
        for (n=0; n<ptypeattr->cVars; n++)
        {
            hr = m_ptinfo->GetVarDesc(n, &pvardesc);
            if (FAILED(hr))
                {RaiseException(IDS_Unexpected); goto error;}
            hr = CProperty::Create(m_ptinfo, pvardesc, &pProperty);
            if (FAILED(hr))
                {RaiseException(IDS_Unexpected); goto error;}
            m_ptinfo->ReleaseVarDesc(pvardesc);
            pvardesc = NULL;
            pProperty->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
            pCollection->Add(pdisp);
            pdisp->Release();
        }
        pCollection->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
        m_pdispProperties = pdisp;
        m_ptinfo->ReleaseTypeAttr(ptypeattr);
    }
    m_pdispProperties->AddRef();
    return (ICollection FAR*)m_pdispProperties;

error:
    if (ptypeattr) m_ptinfo->ReleaseTypeAttr(ptypeattr);
    if (pCollection) delete pCollection;
    if (pvardesc) m_ptinfo->ReleaseVarDesc(pvardesc);
    return NULL;
}

STDMETHODIMP_(IInterface FAR*)
CDispinterface::get_Interface()
{
HRESULT hr;
CInterface FAR* pInterface = NULL;
LPTYPEINFO ptinfoInterface = NULL;
HREFTYPE hreftype;

// Check if this dispinterface is part of a dual interface.
// (A dual interface has an interface and a dispinterface.)
if ((m_wTypeFlags & TYPEFLAG_FDUAL) == 0)
        {RaiseException(IDS_NotDualInterface); return NULL;}
    if(m_pdispInterface == NULL)
    {
// Get interface part of dual interface
#ifdef WIN32
        hr = m_ptinfo->GetRefTypeOfImplType(0xFFFFFFFF, &hreftype);
#else
        hr = m_ptinfo->GetRefTypeOfImplType(0xFFFF, &hreftype);
#endif
if (FAILED(hr))
            {RaiseException(IDS_Unexpected); return NULL;}
hr = m_ptinfo->GetRefTypeInfo(hreftype, &ptinfoInterface);
if (FAILED(hr))
            {RaiseException(IDS_Unexpected); return NULL;}
hr = CInterface::Create(ptinfoInterface, &pInterface);
        if (FAILED(hr))
            {RaiseException(IDS_Unexpected); goto error;}
pInterface->QueryInterface(IID_IDispatch, (void FAR* FAR*)&m_pdispInterface);
        ptinfoInterface->Release();
}
m_pdispInterface->AddRef();
return (IInterface FAR*)m_pdispInterface;

error:
if (ptinfoInterface) ptinfoInterface->Release();
if (pInterface) delete pInterface;
    return NULL;
}


ENUM.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation TypeLibrary Browse Helper Sample
**
**  enum.cpp
**
**  CEnum implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "browseh.h"

/*
 * CEnum::Create
 *
 * Purpose:
 *  Creates an instance of the Enum automation object and initializes it.
 *
 * Parameters:
 *  ptinfo    TypeInfo of Enum.
 *  ppEnum    Returns Enum automation object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CEnum::Create(LPTYPEINFO ptinfo, CEnum FAR* FAR* ppEnum)
{
    HRESULT hr;
    CEnum FAR* pEnum = NULL;

    *ppEnum = NULL;

    // Create object.
    pEnum = new CEnum();
    if (pEnum == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }
    // Load type information for the object from type library.
    hr = pEnum->LoadTypeInfo(IID_IEnum);
    if (FAILED(hr))
        goto error;
    // Ask base class (CTypeInfo) to initialize.
    hr = pEnum->_InitTypeInfo(ptinfo);
    if (FAILED(hr))
        goto error;

    ptinfo->AddRef();
    pEnum->m_ptinfo = ptinfo;

#ifdef _DEBUG
    lstrcpyn(pEnum->m_szClassName, TEXT("Enum"), 100);
#endif

    *ppEnum = pEnum;
    return NOERROR;

error:
    if (pEnum == NULL) return E_OUTOFMEMORY;
    if (pEnum->m_ptinfo) pEnum->m_ptinfo->Release();

    // Set to NULL to prevent destructor from attempting to free again
    pEnum->m_ptinfo = NULL;

    delete pEnum;
    return hr;
}

/*
 * CEnum::CEnum
 *
 * Purpose:
 *  Constructor for CEnum object. Initializes members to NULL.
 *
 */
CEnum::CEnum()
{
    m_pdispElements = NULL;
    m_ptinfo = NULL;
}

/*
 * CEnum::~CEnum
 *
 * Purpose:
 *  Destructor for CEnum object.
 *
 */
CEnum::~CEnum()
{
    if (m_pdispElements) m_pdispElements->Release();
    if (m_ptinfo) m_ptinfo->Release();
}

STDMETHODIMP_(REFCLSID)
CEnum::GetInterfaceID()
{
    return IID_IEnum;
}

STDMETHODIMP_(ICollection FAR*)
CEnum::get_Elements()
{
    HRESULT hr;
    CConstant FAR* pConstant;
    CCollection FAR* pCollection = NULL;
    LPDISPATCH pdisp;
    LPVARDESC pvardesc = NULL;
    LPTYPEATTR ptypeattr = NULL;
    unsigned short n;

    if (m_pdispElements == NULL)
    {
        hr = m_ptinfo->GetTypeAttr(&ptypeattr);
        if (FAILED(hr))
            {RaiseException(IDS_Unexpected); return NULL;}
        hr = CCollection::Create(ptypeattr->cVars, 0, &pCollection);
        if (FAILED(hr))
            {RaiseException(IDS_Unexpected); goto error;}
        // Enumerate enum constants and return a collection of these.
        for (n=0; n<ptypeattr->cVars; n++)
        {
            hr = m_ptinfo->GetVarDesc(n, &pvardesc);
            if (FAILED(hr))
                {RaiseException(IDS_Unexpected); goto error;}
            hr = CConstant::Create(m_ptinfo, pvardesc, &pConstant);
            if (FAILED(hr))
                {RaiseException(IDS_Unexpected); goto error;}
            m_ptinfo->ReleaseVarDesc(pvardesc);
            pvardesc = NULL;
            pConstant->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
            pCollection->Add(pdisp);
            pdisp->Release();
        }
        pCollection->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
        m_pdispElements = pdisp;
        m_ptinfo->ReleaseTypeAttr(ptypeattr);
    }
    m_pdispElements->AddRef();
    return (ICollection FAR*)m_pdispElements;

error:
    if (ptypeattr) m_ptinfo->ReleaseTypeAttr(ptypeattr);
    if (pCollection) delete pCollection;
    if (pvardesc) m_ptinfo->ReleaseVarDesc(pvardesc);
    return NULL;
}


ENUMVAR.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation TypeLibrary Browse Helper Sample
**
**  enumvar.cpp
**
**  CEnumVariant implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "browseh.h"

/*
 * CEnumVariant::Create
 *
 * Purpose:
 *  Creates an instance of the IEnumVARIANT enumerator object and initializes it.
 *
 * Parameters:
 *  psa        Safe array containing items to be enumerated.
 *  cElements  Number of items to be enumerated.
 *  ppenumvariant    Returns enumerator object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CEnumVariant::Create(SAFEARRAY FAR* psa, ULONG cElements, CEnumVariant FAR* FAR* ppenumvariant)
{
    HRESULT hr;
    CEnumVariant FAR* penumvariant = NULL;
    long lLBound;

    *ppenumvariant = NULL;

    penumvariant = new CEnumVariant();
    if (penumvariant == NULL)
        goto error;

    penumvariant->m_cRef = 0;

    // Copy elements into safe array that is used in enumerator implemenatation and
    // initialize state of enumerator.
    hr = SafeArrayGetLBound(psa, 1, &lLBound);
    if (FAILED(hr))
        goto error;
    penumvariant->m_cElements = cElements;
    penumvariant->m_lLBound = lLBound;
    penumvariant->m_lCurrent = lLBound;
    hr = SafeArrayCopy(psa, &penumvariant->m_psa);
    if (FAILED(hr))
       goto error;

    *ppenumvariant = penumvariant;
    return NOERROR;

error:
    if (penumvariant == NULL)
        return E_OUTOFMEMORY;

    if (penumvariant->m_psa)
        SafeArrayDestroy(penumvariant->m_psa);
    penumvariant->m_psa = NULL;
    delete penumvariant;
    return hr;
}

/*
 * CEnumVariant::CEnumVariant
 *
 * Purpose:
 *  Constructor for CEnumVariant object. Initializes members to NULL.
 *
 */
CEnumVariant::CEnumVariant()
{
    m_psa = NULL;
}

/*
 * CEnumVariant::~CEnumVariant
 *
 * Purpose:
 *  Destructor for CEnumVariant object.
 *
 */
CEnumVariant::~CEnumVariant()
{
    if (m_psa) SafeArrayDestroy(m_psa);
}

/*
 * CEnumVariant::QueryInterface, AddRef, Release
 *
 * Purpose:
 *  Implements IUnknown::QueryInterface, AddRef, Release
 *
 */
STDMETHODIMP
CEnumVariant::QueryInterface(REFIID iid, void FAR* FAR* ppv)
{
    *ppv = NULL;

    if (iid == IID_IUnknown || iid == IID_IEnumVARIANT)
        *ppv = this;
    else return E_NOINTERFACE;

    AddRef();
    return NOERROR;
}

STDMETHODIMP_(ULONG)
CEnumVariant::AddRef(void)
{

#ifdef _DEBUG
    TCHAR ach[50];
    wsprintf(ach, TEXT("Ref = %ld, Enum\r\n"), m_cRef+1);
    OutputDebugString(ach);
#endif

    return ++m_cRef;  // AddRef Application Object if enumerator will outlive application object
}

STDMETHODIMP_(ULONG)
CEnumVariant::Release(void)
{

#ifdef _DEBUG
    TCHAR ach[50];
    wsprintf(ach, TEXT("Ref = %ld, Enum\r\n"), m_cRef-1);
    OutputDebugString(ach);
#endif

    if(--m_cRef == 0)
    {
        delete this;
        return 0;
    }
    return m_cRef;
}

/*
 * CEnumVariant::Next
 *
 * Purpose:
 *  Retrieves the next cElements elements. Implements IEnumVARIANT::Next.
 *
 */
STDMETHODIMP
CEnumVariant::Next(ULONG cElements, VARIANT FAR* pvar, ULONG FAR* pcElementFetched)
{
    HRESULT hr;
    ULONG l;
    long l1;
    ULONG l2;

    if (pcElementFetched != NULL)
        *pcElementFetched = 0;

    // Retrieve the next cElements elements.
    for (l1=m_lCurrent, l2=0; l1<(long)(m_lLBound+m_cElements) && l2<cElements; l1++, l2++)
    {
       hr = SafeArrayGetElement(m_psa, &l1, &pvar[l2]);
       if (FAILED(hr))
           goto error;
    }
    // Set count of elements retrieved
    if (pcElementFetched != NULL)
        *pcElementFetched = l2;
    m_lCurrent = l1;

    return  (l2 < cElements) ? S_FALSE : NOERROR;

error:
    for (l=0; l<cElements; l++)
        VariantClear(&pvar[l]);
    return hr;
}

/*
 * CEnumVariant::Skip
 *
 * Purpose:
 *  Skips the next cElements elements. Implements IEnumVARIANT::Skip.
 *
 */
STDMETHODIMP
CEnumVariant::Skip(ULONG cElements)
{
    m_lCurrent += cElements;
    if (m_lCurrent > (long)(m_lLBound+m_cElements))
    {
        m_lCurrent =  m_lLBound+m_cElements;
        return S_FALSE;
    }
    else return NOERROR;
}

/*
 * CEnumVariant::Reset
 *
 * Purpose:
 *  Resets the current element in the enumerator to the beginning. Implements IEnumVARIANT::Reset.
 *
 */
STDMETHODIMP
CEnumVariant::Reset()
{
    m_lCurrent = m_lLBound;
    return NOERROR;
}

/*
 * CEnumVariant::Clone
 *
 * Purpose:
 *  Creates a copy of the current enumeration state. Implements IEnumVARIANT::Clone.
 *
 */
STDMETHODIMP
CEnumVariant::Clone(IEnumVARIANT FAR* FAR* ppenum)
{
    CEnumVariant FAR* penum = NULL;
    HRESULT hr;

    *ppenum = NULL;

    hr = CEnumVariant::Create(m_psa, m_cElements, &penum);
    if (FAILED(hr))
        goto error;
    penum->AddRef();
    penum->m_lCurrent = m_lCurrent;

    *ppenum = penum;
    return NOERROR;

error:
    if (penum)
        penum->Release();
    return hr;
}


FUNCTION.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation TypeLibrary Browse Helper Sample
**
**  function.cpp
**
**  CFunction implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "browseh.h"

/*
 * CFunction::Create
 *
 * Purpose:
 *  Creates an instance of the Function automation object and initializes it.
 *
 * Parameters:
 *  ptinfo        TypeInfo of which this Function is an element of.
 *  nIndex      Index of function in containing TypeInfo.
 *  ppFunction    Returns Function automation object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CFunction::Create(LPTYPEINFO ptinfo, unsigned short nIndex, CFunction FAR* FAR* ppFunction)
{
    HRESULT hr;
    CFunction FAR* pFunction = NULL;
    CTypeDesc FAR* pTypeDesc = NULL;

    *ppFunction = NULL;

    // Create object.
    pFunction = new CFunction();
    if (pFunction == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }

    // Load type information for the application object from type library.
    hr = pFunction->LoadTypeInfo(IID_IFunction);
    if (FAILED(hr))
        goto error;

    hr = ptinfo->GetFuncDesc(nIndex, &pFunction->m_pfuncdesc);
    if (FAILED(hr))
        goto error;
    // Function return type.
    hr = CTypeDesc::Create(ptinfo, &pFunction->m_pfuncdesc->elemdescFunc.tdesc, &pTypeDesc);
    if (FAILED(hr))
        goto error;
    pTypeDesc->QueryInterface(IID_IDispatch, (LPVOID FAR*)&pFunction->m_pdispTypeDesc);

    hr = ptinfo->GetDocumentation(pFunction->m_pfuncdesc->memid, &pFunction->m_bstrName, &pFunction->m_bstrDocumentation,
             &pFunction->m_ulHelpContext, &pFunction->m_bstrHelpFile);
    if (FAILED(hr))
        goto error;

    ptinfo->AddRef();
    pFunction->m_ptinfoFunction = ptinfo;

#ifdef _DEBUG
    lstrcpyn(pFunction->m_szClassName, TEXT("Function"), 100);
#endif

    *ppFunction = pFunction;
    return NOERROR;

error:
    if (pFunction == NULL) return E_OUTOFMEMORY;
    if (pFunction->m_pfuncdesc) ptinfo->ReleaseFuncDesc(pFunction->m_pfuncdesc);
    if (pFunction->m_ptinfoFunction) pFunction->m_ptinfoFunction->Release();
    if (pFunction->m_bstrName) SysFreeString(pFunction->m_bstrName);
    if (pFunction->m_bstrDocumentation) SysFreeString(pFunction->m_bstrDocumentation);
    if (pFunction->m_bstrHelpFile) SysFreeString(pFunction->m_bstrHelpFile);

    // Set to NULL to prevent destructor from attempting to free again
    pFunction->m_ptinfoFunction = NULL;
    pFunction->m_pfuncdesc = NULL;
    pFunction->m_bstrName = NULL;
    pFunction->m_bstrDocumentation = NULL;
    pFunction->m_bstrHelpFile = NULL;

    delete pFunction;
    return hr;
}

/*
 * CFunction::CFunction
 *
 * Purpose:
 *  Constructor for CFunction object. Initializes members to NULL.
 *
 */
CFunction::CFunction()
{
    m_bstrName = NULL;
    m_bstrDocumentation = NULL;
    m_bstrHelpFile = NULL;
    m_pdispParameters = NULL;
    m_ptinfoFunction = NULL;
    m_pfuncdesc = NULL;
    m_pdispTypeDesc = NULL;
}

/*
 * CFunction::~CFunction
 *
 * Purpose:
 *  Destructor for CFunction object.
 *
 */
CFunction::~CFunction()
{
     if (m_bstrName) SysFreeString(m_bstrName);
     if (m_bstrDocumentation) SysFreeString(m_bstrDocumentation);
     if (m_bstrHelpFile) SysFreeString(m_bstrHelpFile);
     if (m_pdispParameters) m_pdispParameters->Release();
     if (m_pfuncdesc && m_ptinfoFunction) m_ptinfoFunction->ReleaseFuncDesc(m_pfuncdesc);
     if (m_ptinfoFunction) m_ptinfoFunction->Release();
     if (m_pdispTypeDesc) m_pdispTypeDesc->Release();
}

STDMETHODIMP_(REFCLSID)
CFunction::GetInterfaceID()
{
    return IID_IFunction;
}

STDMETHODIMP_(BSTR)
CFunction::get_Name()
{
    return SysAllocString(m_bstrName);
}

STDMETHODIMP_(BSTR)
CFunction::get_Documentation()
{
    return SysAllocString(m_bstrDocumentation);
}

STDMETHODIMP_(long)
CFunction::get_HelpContext()
{
    return (long)m_ulHelpContext;
}

STDMETHODIMP_(BSTR)
CFunction::get_HelpFile()
{
    return SysAllocString(m_bstrHelpFile);
}

STDMETHODIMP_(ITypeDesc FAR*)
CFunction::get_ReturnType()
{
    m_pdispTypeDesc->AddRef();
    return (ITypeDesc FAR*)m_pdispTypeDesc;
}

STDMETHODIMP_(ICollection FAR*)
CFunction::get_Parameters()
{
    HRESULT hr;
    CParameter FAR* pParameter;
    CCollection FAR* pCollection = NULL;
    LPDISPATCH pdisp;
    BSTR FAR* rgbstrNames = NULL;
    unsigned int cNames;
    unsigned short n = 0;

// Create a collection of parameters and return it.
    if (m_pdispParameters == NULL)
    {
        rgbstrNames = new BSTR[m_pfuncdesc->cParams+1];
        if (rgbstrNames == NULL)
            {RaiseException(IDS_OutOfMemory); return NULL;}

// Get the name of the function and its parameters
        hr = m_ptinfoFunction->GetNames(m_pfuncdesc->memid, rgbstrNames,
                  m_pfuncdesc->cParams+1, &cNames);
        if (FAILED(hr))
            {RaiseException(IDS_Unexpected); goto error;}
        SysFreeString(rgbstrNames[0]);

// Create a parameter collection
        hr = CCollection::Create(m_pfuncdesc->cParams, 0, &pCollection);
        if (FAILED(hr))
            {RaiseException(IDS_Unexpected); goto error;}

// The value being assigned to a property does not have a name. Call it 'Value'
if (m_pfuncdesc->invkind & INVOKE_PROPERTYPUT)
            rgbstrNames[n+1] = SysAllocString(OLESTR("Value"));
// Add parameters to the parameter collection
        for (n=0; n<m_pfuncdesc->cParams; n++)
        {
            hr = CParameter::Create(m_ptinfoFunction,
   rgbstrNames[n+1],
                   &m_pfuncdesc->lprgelemdescParam[n].tdesc,
                   &m_pfuncdesc->lprgelemdescParam[n].idldesc,
                   &pParameter);
            if (FAILED(hr))
                {RaiseException(IDS_Unexpected); goto error;}
            SysFreeString(rgbstrNames[n+1]);
            pParameter->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
            pCollection->Add(pdisp);
            pdisp->Release();
        }
        pCollection->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
        m_pdispParameters = pdisp;
        delete rgbstrNames;
    }
    m_pdispParameters->AddRef();
    return (ICollection FAR*)m_pdispParameters;

error:
    if (pCollection) delete pCollection;
    if (rgbstrNames)
        for (; n<m_pfuncdesc->cParams; n++)
            SysFreeString(rgbstrNames[n+1]);
    return NULL;
}

STDMETHODIMP_(MEMBERID)
CFunction::get_Memberid()
{
    return m_pfuncdesc->memid;
}

STDMETHODIMP_(CALLCONV)
CFunction::get_CallConvention()
{
    return m_pfuncdesc->callconv;
}

STDMETHODIMP_(FUNCKIND)
CFunction::get_FuncKind()
{
    return m_pfuncdesc->funckind;
}

STDMETHODIMP_(INVOKEKIND)
CFunction::get_InvocationKind()
{
    return m_pfuncdesc->invkind;
}

STDMETHODIMP_(short)
CFunction::get_NumberOfOptionalParams()
{
return m_pfuncdesc->cParamsOpt;
}

STDMETHODIMP_(short)
CFunction::get_OffsetInVtbl()
{
return m_pfuncdesc->oVft;
}

STDMETHODIMP_(unsigned short)
CFunction::get_FuncFlags()
{
return m_pfuncdesc->wFuncFlags;
}

STDMETHODIMP_(OBJTYPE)
CFunction::get_Kind()
{
    return TYPE_FUNCTION;
}


INTFACE.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation TypeLibrary Browse Helper Sample
**
**  intface.cpp
**
**  CInterface implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "browseh.h"

/*
 * CInterface::Create
 *
 * Purpose:
 *  Creates an instance of the Interface automation object and initializes it.
 *
 * Parameters:
 *  ptinfo         TypeInfo of interface.
 *  ppInterface    Returns Interface automation object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CInterface::Create(LPTYPEINFO ptinfo, CInterface FAR* FAR* ppInterface)
{
    HRESULT hr;
    CInterface FAR* pInterface = NULL;

    *ppInterface = NULL;

    // Create object.
    pInterface = new CInterface();
    if (pInterface == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }
    // Load type information for the object from type library.
    hr = pInterface->LoadTypeInfo(IID_IInterface);
    if (FAILED(hr))
        goto error;
    // Intialize base class, CTypeInfo
    hr = pInterface->_InitTypeInfo(ptinfo);
    if (FAILED(hr))
        goto error;

    ptinfo->AddRef();
    pInterface->m_ptinfo = ptinfo;

#ifdef _DEBUG
    lstrcpyn(pInterface->m_szClassName, TEXT("Interface"), 100);
#endif

    *ppInterface = pInterface;
    return NOERROR;

error:
    if (pInterface == NULL) return E_OUTOFMEMORY;
    if (pInterface->m_ptinfo) pInterface->m_ptinfo->Release();

    // Set to NULL to prevent destructor from attempting to free again
    pInterface->m_ptinfo = NULL;

    delete pInterface;
    return hr;
}

/*
 * CInterface::CInterface
 *
 * Purpose:
 *  Constructor for CInterface object. Initializes members to NULL.
 *
 */
CInterface::CInterface()
{
    m_pdispFunctions = NULL;
m_pdispBaseInterface = NULL;
    m_ptinfo = NULL;
}

/*
 * CInterface::~CInterface
 *
 * Purpose:
 *  Destructor for CInterface object.
 *
 */
CInterface::~CInterface()
{
    if (m_pdispFunctions) m_pdispFunctions->Release();
if (m_pdispBaseInterface) m_pdispBaseInterface->Release();
    if (m_ptinfo) m_ptinfo->Release();
}

STDMETHODIMP_(REFCLSID)
CInterface::GetInterfaceID()
{
    return IID_IInterface;
}

STDMETHODIMP_(ICollection FAR*)
CInterface::get_Functions()
{
    HRESULT hr;
    CFunction FAR* pFunction;
    CCollection FAR* pCollection = NULL;
    LPDISPATCH pdisp;
    LPTYPEATTR ptypeattr = NULL;
    unsigned short n;

    if (m_pdispFunctions == NULL)
    {
        // Create collection of functions in interface.
        hr = m_ptinfo->GetTypeAttr(&ptypeattr);
        if (FAILED(hr))
            {RaiseException(IDS_Unexpected); return NULL;}
        hr = CCollection::Create(ptypeattr->cFuncs, 0, &pCollection);
        if (FAILED(hr))
            {RaiseException(IDS_Unexpected); goto error;}
        for (n=0; n<ptypeattr->cFuncs; n++)
        {
            hr = CFunction::Create(m_ptinfo, n, &pFunction);
            if (FAILED(hr))
                {RaiseException(IDS_Unexpected); goto error;}
            pFunction->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
            pCollection->Add(pdisp);
            pdisp->Release();
        }
        pCollection->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
        m_pdispFunctions = pdisp;
        m_ptinfo->ReleaseTypeAttr(ptypeattr);
    }
    m_pdispFunctions->AddRef();
    return (ICollection FAR*)m_pdispFunctions;

error:
    if (ptypeattr) m_ptinfo->ReleaseTypeAttr(ptypeattr);
    if (pCollection) delete pCollection;
    return NULL;
}

STDMETHODIMP_(IInterface FAR*)
CInterface::get_BaseInterface()
{
HRESULT hr;
CInterface FAR* pInterface = NULL;
LPTYPEINFO ptinfoInterface = NULL;
HREFTYPE hreftype;

    if(m_pdispBaseInterface == NULL)
    {
    hr = m_ptinfo->GetRefTypeOfImplType(0, &hreftype);
if (FAILED(hr))
            {RaiseException(IDS_Unexpected); return NULL;}
hr = m_ptinfo->GetRefTypeInfo(hreftype, &ptinfoInterface);
if (FAILED(hr))
            {RaiseException(IDS_Unexpected); return NULL;}
hr = CInterface::Create(ptinfoInterface, &pInterface);
        if (FAILED(hr))
            {RaiseException(IDS_Unexpected); goto error;}
pInterface->QueryInterface(IID_IDispatch, (void FAR* FAR*)&m_pdispBaseInterface);
        ptinfoInterface->Release();
}
m_pdispBaseInterface->AddRef();
return (IInterface FAR*)m_pdispBaseInterface;

error:
if (ptinfoInterface) ptinfoInterface->Release();
if (pInterface) delete pInterface;
    return NULL;

}


MAIN.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation TypeLibrary Browse Helper Sample
**
**  main.cpp
**
**  new and delete operator redefinition to use memory manager of calling
**  task.
**  LibMain, WEP, DllGetClassObject, DllCanUnloadNow, DLL initialization.
**  Procedure to create standard dispatch implementation.
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/
#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include <initguid.h>
#include "browseh.h"

// Globals
HINSTANCE   g_hinst;                   // Instance of application
                                       //Count number of objects and number of locks.
ULONG       g_cObj=0;
ULONG       g_cLock=0;

// String resource buffers
TCHAR g_szServerName[STR_LEN];

/*
 * new
 *
 * Purpose:
 *   Since this is an InProcServer object, the memory allocator used by
 *   the calling task must be used.
 *   This is done by redefining the global new operator and using new
 *   for all memory allocations.
 */
void FAR* operator new(size_t size)
{
    IMalloc FAR* pMalloc;
    LPVOID lpv;

    if (CoGetMalloc(MEMCTX_TASK, &pMalloc) == NOERROR)
    {
        lpv = pMalloc->Alloc(size);
        pMalloc->Release();
        return lpv;
    }
    return NULL;
}

/*
 * delete
 *
 * Purpose:
 *   Use the memory manager of the calling task to free memory.
 */
void operator delete(void FAR* lpv)
{
    IMalloc FAR* pMalloc;

    if (lpv == NULL) return;

    if( CoGetMalloc(MEMCTX_TASK, &pMalloc) == NOERROR)
    {
        if (pMalloc->DidAlloc(lpv))
            pMalloc->Free(lpv);
        pMalloc->Release();
    }
}

#ifdef WIN16
/*
 * LibMain
 *
 * Purpose:
 *  Called by Win16 on DLL load. Does any one-time initializations.
 *
 */
int PASCAL LibMain (HINSTANCE hinst, WORD wDataSeg, WORD cbHeapSize, LPSTR lpCmdLine)
{
   if (cbHeapSize != 0)
       UnlockData(0);

   g_hinst = hinst;

   if (!InitDLL(hinst))
         return FALSE;

   return TRUE;
}

/*
 * WEP
 *
 * Purpose:
 *  Called by Windows on DLL unload.
 *
 */
extern "C" void FAR PASCAL _WEP(int bSystemExit)
{
    return;
}

#else //Win 32

BOOL WINAPI DllMain (HINSTANCE hinst, DWORD dwReason, LPVOID lpReserved)
{
    switch (dwReason)
    {
        case DLL_PROCESS_ATTACH:
            if (!InitDLL(hinst))
                return FALSE;
            else return TRUE;

        default:
            return TRUE;
    }
}

#endif

/*
 * DLLGetClassObject
 *
 * Purpose:
 *  OLE calls this funtion to obtain the class factory. Note that the class
 *  factory is not registered by the inproc server.
 *
 */
STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid,
                                   LPVOID FAR *ppv)
{
    LPCLASSFACTORY pcf;
    HRESULT hr;

    *ppv =  NULL;

    // Check if this CLSID is supported.
    if (rclsid != CLSID_BrowseHelper)
       return E_FAIL;

    // Create class factory and return it
    pcf = new CBrowseHelperCF;
    if (!pcf)
        return E_OUTOFMEMORY;
    hr = pcf->QueryInterface(riid, ppv);
    if (FAILED(hr))
    {
        delete pcf;
        return hr;
    }
    return NOERROR;
}

/*
 * DLLCanUnloadNow
 *
 * Purpose:
 *  DllCanUnloadNow is called by OLE to determine if the DLL can be unloded.
 *
 */
STDAPI DllCanUnloadNow(void)
{
    if (g_cObj==0L && g_cLock==0L)
        return S_OK;
    else return S_FALSE;
}

/*
 * InitDLL
 *
 * Purpose:
 *  Load strings & Registers the window class
 *
 * Parameters:
 *  hinstance       hinstance of application
 *
 */
BOOL InitDLL (HINSTANCE hinst)
{
   return LoadString(hinst, IDS_SERVERNAME, g_szServerName, sizeof(g_szServerName));
}

/*
 * Quick & Dirty ANSI/Unicode conversion routines. These routines use a static
 * buffer of fixed size to hold the converted string. Consequently these
 * routines are limited to strings of size STRCONVERT_MAXLEN. Also the same
 * buffer is reused when the routine is called a second time. So make sure
 * that the converted string is used before the conversion routine is called
 * again
 */
#ifdef WIN32

#ifndef UNICODE
char* ConvertToAnsi(OLECHAR FAR* szW)
{
  static char achA[STRCONVERT_MAXLEN];

  WideCharToMultiByte(CP_ACP, 0, szW, -1, achA, STRCONVERT_MAXLEN, NULL, NULL);
  return achA;
}

OLECHAR* ConvertToUnicode(char FAR* szA)
{
  static OLECHAR achW[STRCONVERT_MAXLEN];

  MultiByteToWideChar(CP_ACP, 0, szA, -1, achW, STRCONVERT_MAXLEN);
  return achW;
}
#endif

#endif


MODULE.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation TypeLibrary Browse Helper Sample
**
**  module.cpp
**
**  CModule implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "browseh.h"

/*
 * CModule::Create
 *
 * Purpose:
 *  Creates an instance of the Module automation object and initializes it.
 *
 * Parameters:
 *  ptinfo      TypeInfo of module.
 *  ppModule    Returns Module automation object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CModule::Create(LPTYPEINFO ptinfo, CModule FAR* FAR* ppModule)
{
    HRESULT hr;
    CModule FAR* pModule = NULL;

    *ppModule = NULL;

    // Create object.
    pModule = new CModule();
    if (pModule == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }
    // Load type information for the object from type library.
    hr = pModule->LoadTypeInfo(IID_IModule);
    if (FAILED(hr))
        goto error;
    // Intialize base class, CTypeInfo
    hr = pModule->_InitTypeInfo(ptinfo);
    if (FAILED(hr))
        goto error;

    ptinfo->AddRef();
    pModule->m_ptinfo = ptinfo;

#ifdef _DEBUG
    lstrcpyn(pModule->m_szClassName, TEXT("Module"), 100);
#endif

    *ppModule = pModule;
    return NOERROR;

error:
    if (pModule == NULL) return E_OUTOFMEMORY;
    if (pModule->m_ptinfo) pModule->m_ptinfo->Release();

    // Set to NULL to prevent destructor from attempting to free again
    pModule->m_ptinfo = NULL;

    delete pModule;
    return hr;
}

/*
 * CModule::CModule
 *
 * Purpose:
 *  Constructor for CModule object. Initializes members to NULL.
 *
 */
CModule::CModule()
{
    m_pdispFunctions = NULL;
    m_ptinfo = NULL;
}

/*
 * CModule::~CModule
 *
 * Purpose:
 *  Destructor for CModule object.
 *
 */
CModule::~CModule()
{
    if (m_pdispFunctions) m_pdispFunctions->Release();
    if (m_ptinfo) m_ptinfo->Release();
}

STDMETHODIMP_(REFCLSID)
CModule::GetInterfaceID()
{
    return IID_IModule;
}

STDMETHODIMP_(ICollection FAR*)
CModule::get_Functions()
{
    HRESULT hr;
    CFunction FAR* pFunction;
    CCollection FAR* pCollection = NULL;
    LPDISPATCH pdisp;
    LPTYPEATTR ptypeattr = NULL;
    unsigned short n;

    if (m_pdispFunctions == NULL)
    {
        // Create collection of functions of the interface.
        hr = m_ptinfo->GetTypeAttr(&ptypeattr);
        if (FAILED(hr))
            {RaiseException(IDS_Unexpected); return NULL;}
        hr = CCollection::Create(ptypeattr->cFuncs, 0, &pCollection);
        if (FAILED(hr))
            {RaiseException(IDS_Unexpected); goto error;}
        for (n=0; n<ptypeattr->cFuncs; n++)
        {
            hr = CFunction::Create(m_ptinfo, n, &pFunction);
            if (FAILED(hr))
                {RaiseException(IDS_Unexpected); goto error;}
            pFunction->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
            pCollection->Add(pdisp);
            pdisp->Release();
        }
        pCollection->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
        m_pdispFunctions = pdisp;
        m_ptinfo->ReleaseTypeAttr(ptypeattr);
    }
    m_pdispFunctions->AddRef();
    return (ICollection FAR*)m_pdispFunctions;

error:
    if (ptypeattr) m_ptinfo->ReleaseTypeAttr(ptypeattr);
    if (pCollection) delete pCollection;
    return NULL;
}


MYDISP.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation TypeLibrary Browse Helper Sample
**
**  mydisp.cpp
**
**  CMyDispatch implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
**  The CMyDispatch object implement IUnknown & IDispatch for all the automation
**  objects in this sample. All objects derive from CMyDispatch.
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "browseh.h"

/*
 * CMyDispatch::CMyDispatch
 *
 * Purpose:
 *  Constructor for CMyDispatch object. Initializes members to NULL.
 *
 */
CMyDispatch::CMyDispatch()
{
    m_ptinfo = NULL;
    m_cRef = 0;
}

/*
 * CMyDispatch::~CMyDispatch
 *
 * Purpose:
 *  Destructor for CMyDispatch object.
 *
 */
CMyDispatch::~CMyDispatch()
{
     if (m_ptinfo) m_ptinfo->Release();
}

/*
 * CMyDispatch::QueryInterface, AddRef, Release
 *
 * Purpose:
 *  Implements IUnknown::QueryInterface, AddRef, Release
 *
 */

STDMETHODIMP
CMyDispatch::QueryInterface(REFIID iid, void FAR* FAR* ppv)
{
    *ppv = NULL;

    if (iid == IID_IUnknown)
        *ppv = this;
    else if (iid == IID_IDispatch)
        *ppv = this;
    else if (iid == GetInterfaceID())
        *ppv = this;
    else return E_NOINTERFACE;

    AddRef();
    return NOERROR;
}

STDMETHODIMP_(ULONG)
CMyDispatch::AddRef(void)
{
#ifdef _DEBUG
    TCHAR ach[150];
    wsprintf(ach, TEXT("Ref = %ld, Object = %s\r\n"), m_cRef+1, m_szClassName);
    OutputDebugString(ach);
#endif
    return ++m_cRef;
}

STDMETHODIMP_(ULONG)
CMyDispatch::Release(void)
{
#ifdef _DEBUG
    TCHAR ach[150];
    wsprintf(ach, TEXT("Ref = %ld, Object = %s\r\n"), m_cRef-1, m_szClassName);
    OutputDebugString(ach);
#endif
    if(--m_cRef == 0)
    {
        delete this;
        return 0;
    }
    return m_cRef;
}

/*
 * CMyDispatch::GetTypeInfoCount
 *
 * Purpose:
 *  Implements IDispatch::GetTypeInfoCount.
 *
 */
STDMETHODIMP
CMyDispatch::GetTypeInfoCount(UINT FAR* pctinfo)
{
    *pctinfo = 1;
    return NOERROR;
}

/*
 * CMyDispatch::GetTypeInfo
 *
 * Purpose:
 *  Implements IDispatch::GetTypeInfo.
 *
 */
STDMETHODIMP
CMyDispatch::GetTypeInfo(
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo)
{
    *pptinfo = NULL;

    if(itinfo != 0)
        return DISP_E_BADINDEX;

    m_ptinfo->AddRef();
    *pptinfo = m_ptinfo;

    return NOERROR;
}

/*
 * CMyDispatch::GetIDsOfNames
 *
 * Purpose:
 *  Implements IDispatch::GetIDsOfNames.  The standard implementation, DispGetIDsOfNames,
 *  is used.
 *
 */
STDMETHODIMP
CMyDispatch::GetIDsOfNames(
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid)
{
    return DispGetIDsOfNames(m_ptinfo, rgszNames, cNames, rgdispid);
}

/*
 * CMyDispatch::Invoke
 *
 * Purpose:
 *  Implements IDispatch::Invoke.  The standard implementation, DispInvoke,
 *  is used. Properties and methods  will
 *  set m_bRaiseException to raise an exception.
 *
 */
STDMETHODIMP
CMyDispatch::Invoke(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr)
{
    HRESULT hr;

    m_bRaiseException = FALSE;
    hr =  DispInvoke(
        this, m_ptinfo,
        dispidMember, wFlags, pdispparams,
        pvarResult, pexcepinfo, puArgErr);
    if (m_bRaiseException)
    {
       if (NULL != pexcepinfo)
           _fmemcpy(pexcepinfo, &m_excepinfo, sizeof(EXCEPINFO));
       return DISP_E_EXCEPTION;
    }
    else return hr;
}

/*
 * CMyDispatch::LoadTypeInfo
 *
 *  Purpose:
 *   Gets type information of an object's interface from type library.
 *
 * Parameters:
 *  clsid               Interface id of object in type library.
 *
 * Return Value:
 *  HRESULT
 *
 */
STDMETHODIMP
CMyDispatch::LoadTypeInfo(REFCLSID clsid)
{
    HRESULT hr;
    LPTYPELIB ptlib = NULL;
    LPTYPEINFO ptinfo = NULL;

    // Load Type Library. If required, notify user on failure.
    hr = LoadRegTypeLib(LIBID_BrowseHelper, 1, 0, 0x09, &ptlib);
    if (FAILED(hr))
        return hr;

    // Get type information for interface of the object.
    hr = ptlib->GetTypeInfoOfGuid(clsid, &ptinfo);
    if (FAILED(hr))
    {
        ptlib->Release();
        return hr;
    }

    ptlib->Release();
    m_ptinfo = ptinfo;
    return NOERROR;
}

/*
 * CMyDispatch::RaiseException
 *
 *  Purpose:
 *   Raises exception so CMyDispatch::Invoke will return DISP_E_EXCEPTION
 *
 * Parameters:
 *  nID               ID of exception to be raised.
 *
 */
STDMETHODIMP_(void)
CMyDispatch::RaiseException(int nID)
{
    extern HINSTANCE g_hinst;
    extern TCHAR g_szServerName[];
    TCHAR szError[STR_LEN];

    _fmemset(&m_excepinfo, 0, sizeof(EXCEPINFO));

    m_excepinfo.wCode = nID;
    if (LoadString(g_hinst, nID, szError, sizeof(szError)))
        m_excepinfo.bstrDescription = SysAllocString(TO_OLE_STRING(szError));
    m_excepinfo.bstrSource = SysAllocString(TO_OLE_STRING(g_szServerName));

    m_bRaiseException = TRUE;
}


MYDISP.H

class FAR CMyDispatch : public IDispatch
{
public:
    /* IUnknown methods */
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj);
    STDMETHOD_(ULONG, AddRef)(THIS);
    STDMETHOD_(ULONG, Release)(THIS);

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(THIS_ UINT FAR* pctinfo);

    STDMETHOD(GetTypeInfo)(
      THIS_
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo);

    STDMETHOD(GetIDsOfNames)(
      THIS_
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid);

    STDMETHOD(Invoke)(
      THIS_
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr);

      CMyDispatch();
      virtual ~CMyDispatch();

    /* CMyDispatch methods */
    STDMETHOD(LoadTypeInfo)(REFCLSID clsid);
    STDMETHOD_(void, RaiseException)(int nID);
    STDMETHOD_(REFCLSID, GetInterfaceID)() PURE;

private:
    LPTYPEINFO m_ptinfo;         // Type information of interface.
    ULONG m_cRef;                // Reference count.
    EXCEPINFO m_excepinfo;       // Information to raise an exception on error.
    BOOL m_bRaiseException;      // Properties and methods use this to signal an exception to be raised.

#ifdef _DEBUG
public:
    TCHAR m_szClassName[100];
#endif
};


PARAM.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation TypeLibrary Browse Helper Sample
**
**  param.cpp
**
**  CParameter implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "browseh.h"

/*
 * CParameter::Create
 *
 * Purpose:
 *  Creates an instance of the Parameter automation object and initializes it.
 *
 * Parameters:
 *  ptinfo         TypeInfo in which the function of this parameter is contained.
 *  bstrName       Name of parameter
 *  ptypedesc      Type of parameter.
 *  pidldesc       IDLDESC of parameter.
 *  ppParameter    Returns Parameter automation object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CParameter::Create(LPTYPEINFO ptinfo, BSTR bstrName, TYPEDESC FAR* ptypedesc,
     IDLDESC FAR* pidldesc, CParameter FAR* FAR* ppParameter)
{
    HRESULT hr;
    CParameter FAR* pParameter = NULL;
    CTypeDesc FAR* pTypeDesc = NULL;

    *ppParameter = NULL;

    // Create object.
    pParameter = new CParameter();
    if (pParameter == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }
   // Load type information for the object from type library.
    hr = pParameter->LoadTypeInfo(IID_IParameter);
    if (FAILED(hr))
        goto error;

    pParameter->m_bstrName = SysAllocString(bstrName);

    // Parameter type.
    hr = CTypeDesc::Create(ptinfo, ptypedesc, &pTypeDesc);
    if (FAILED(hr))
        goto error;
    pTypeDesc->QueryInterface(IID_IDispatch, (LPVOID FAR*)&pParameter->m_pdispTypeDesc);

    pParameter->m_wIDLFlags = pidldesc->wIDLFlags;
#ifdef _DEBUG
    lstrcpyn(pParameter->m_szClassName, TEXT("Parameter"), 100);
#endif

    *ppParameter = pParameter;
    return NOERROR;

error:
    if (pParameter == NULL) return E_OUTOFMEMORY;

    delete pParameter;
    return hr;
}

/*
 * CParameter::CParameter
 *
 * Purpose:
 *  Constructor for CParameter object. Initializes members to NULL.
 *
 */
CParameter::CParameter()
{
    m_bstrName = NULL;
    m_pdispTypeDesc = NULL;
}

/*
 * CParameter::~CParameter
 *
 * Purpose:
 *  Destructor for CParameter object.
 *
 */
CParameter::~CParameter()
{
     if (m_bstrName) SysFreeString(m_bstrName);
     if (m_pdispTypeDesc) m_pdispTypeDesc->Release();
}

STDMETHODIMP_(REFCLSID)
CParameter::GetInterfaceID()
{
    return IID_IParameter;
}

STDMETHODIMP_(BSTR)
CParameter::get_Name()
{
    return SysAllocString(m_bstrName);
}

STDMETHODIMP_(ITypeDesc FAR*)
CParameter::get_Type()
{
    m_pdispTypeDesc->AddRef();
    return (ITypeDesc FAR*)m_pdispTypeDesc;
}

STDMETHODIMP_(int)
CParameter::get_IDLFlags()
{
    return (int)m_wIDLFlags;
}

STDMETHODIMP_(OBJTYPE)
CParameter::get_Kind()
{
    return TYPE_PARAMETER;
}


PROPERTY.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation Property Browse Helper Sample
**
**  property.cpp
**
**  CProperty implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "browseh.h"

/*
 * CProperty::Create
 *
 * Purpose:
 *  Creates an instance of the Property automation object and initializes it.
 *
 * Parameters:
 *  ptinfo        TypeInfo in which this property/variable is contained.
 *  pvardesc      VARDESC that descrribes this property/variable.
 *  ppProperty    Returns Property automation object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CProperty::Create(LPTYPEINFO ptinfo, LPVARDESC pvardesc, CProperty FAR* FAR* ppProperty)
{
    HRESULT hr;
    CProperty FAR* pProperty = NULL;
    BSTR bstr;
    unsigned int cNames;
    CTypeDesc FAR* pTypeDesc = NULL;

    *ppProperty = NULL;

    // Create object.
    pProperty = new CProperty();
    if (pProperty == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }

   // Load type information for the object from type library.
    hr = pProperty->LoadTypeInfo(IID_IProperty);
    if (FAILED(hr))
        goto error;

    pProperty->m_memid = pvardesc->memid;
    ptinfo->GetNames(pvardesc->memid, &bstr, 1, &cNames);
    pProperty->m_bstrName = bstr;

    // Property type
    hr = CTypeDesc::Create(ptinfo, &pvardesc->elemdescVar.tdesc, &pTypeDesc);
    if (FAILED(hr))
        goto error;
    pTypeDesc->QueryInterface(IID_IDispatch, (LPVOID FAR*)&pProperty->m_pdispTypeDesc);

    hr = ptinfo->GetDocumentation(pvardesc->memid, NULL, &pProperty->m_bstrDocumentation,
             &pProperty->m_ulHelpContext, &pProperty->m_bstrHelpFile);
    if (FAILED(hr))
        goto error;

#ifdef _DEBUG
    lstrcpyn(pProperty->m_szClassName, TEXT("Property"), 100);
#endif

    *ppProperty = pProperty;
    return NOERROR;

error:
    if (pProperty == NULL) return E_OUTOFMEMORY;
    if (pProperty->m_bstrName) SysFreeString(pProperty->m_bstrName);
    if (pProperty->m_bstrDocumentation) SysFreeString(pProperty->m_bstrDocumentation);
    if (pProperty->m_bstrHelpFile) SysFreeString(pProperty->m_bstrHelpFile);

    // Set to NULL to prevent destructor from attempting to free again
    pProperty->m_bstrName = NULL;
    pProperty->m_bstrDocumentation = NULL;
    pProperty->m_bstrHelpFile = NULL;

    delete pProperty;
    return hr;
}

/*
 * CProperty::CProperty
 *
 * Purpose:
 *  Constructor for CProperty object. Initializes members to NULL.
 *
 */
CProperty::CProperty()
{
    m_bstrName = NULL;
    m_bstrDocumentation = NULL;
    m_bstrHelpFile = NULL;
    m_pdispTypeDesc = NULL;
}

/*
 * CProperty::~CProperty
 *
 * Purpose:
 *  Destructor for CProperty object.
 *
 */
CProperty::~CProperty()
{
     if (m_bstrName) SysFreeString(m_bstrName);
     if (m_bstrDocumentation) SysFreeString(m_bstrDocumentation);
     if (m_bstrHelpFile) SysFreeString(m_bstrHelpFile);
     if (m_pdispTypeDesc) m_pdispTypeDesc->Release();
}

STDMETHODIMP_(REFCLSID)
CProperty::GetInterfaceID()
{
    return IID_IProperty;
}

STDMETHODIMP_(BSTR)
CProperty::get_Name()
{
    return SysAllocString(m_bstrName);
}

STDMETHODIMP_(BSTR)
CProperty::get_Documentation()
{
    return SysAllocString(m_bstrDocumentation);
}

STDMETHODIMP_(long)
CProperty::get_HelpContext()
{
    return (long)m_ulHelpContext;
}

STDMETHODIMP_(BSTR)
CProperty::get_HelpFile()
{
    return SysAllocString(m_bstrHelpFile);
}

STDMETHODIMP_(ITypeDesc FAR*)
CProperty::get_Type()
{
    m_pdispTypeDesc->AddRef();
    return (ITypeDesc FAR*)m_pdispTypeDesc;
}

STDMETHODIMP_(MEMBERID)
CProperty::get_Memberid()
{
    return m_memid;
}

STDMETHODIMP_(OBJTYPE)
CProperty::get_Kind()
{
    return TYPE_PROPERTY;
}


ALIAS.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation TypeLibrary Browse Helper Sample
**
**  alias.cpp
**
**  CAlias implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "browseh.h"

/*
 * CAlias::Create
 *
 * Purpose:
 *  Creates an instance of the Alias automation object and initializes it.
 *
 * Parameters:
 *  ptinfo     Typeinfo of Alias.
 *  ppAlias    Returns Alias automation object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CAlias::Create(LPTYPEINFO ptinfo, CAlias FAR* FAR* ppAlias)
{
    HRESULT hr;
    CAlias FAR* pAlias = NULL;
    LPTYPEATTR ptypeattr = NULL;
    CTypeDesc FAR* pTypeDesc;

    *ppAlias = NULL;

    // Create alias object.
    pAlias = new CAlias();
    if (pAlias == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }
    // Load type information for the object from type library.
    hr = pAlias->LoadTypeInfo(IID_IAlias);
    if (FAILED(hr))
        goto error;

    // Ask base class (CTypeInfo) to initialize
    hr = pAlias->_InitTypeInfo(ptinfo);
    if (FAILED(hr))
        goto error;

    // Get base type of this alias.
    hr = ptinfo->GetTypeAttr(&ptypeattr);
    if (FAILED(hr))
        return NULL;
    hr = CTypeDesc::Create(ptinfo, &ptypeattr->tdescAlias, &pTypeDesc);
    if (FAILED(hr))
        goto error;
    pTypeDesc->QueryInterface(IID_IDispatch, (LPVOID FAR*)&pAlias->m_pdispTypeDescBase);
    ptinfo->ReleaseTypeAttr(ptypeattr);

#ifdef _DEBUG
    lstrcpyn(pAlias->m_szClassName, TEXT("Alias"), 100);
#endif

    *ppAlias = pAlias;
    return NOERROR;

error:
    if (pAlias == NULL) return E_OUTOFMEMORY;
    if (pAlias->m_pdispTypeDescBase) pAlias->m_pdispTypeDescBase->Release();
    if (ptypeattr) ptinfo->ReleaseTypeAttr(ptypeattr);

    // Set to NULL to prevent destructor from attempting to free again
    pAlias->m_pdispTypeDescBase = NULL;

    delete pAlias;
    return hr;
}

/*
 * CAlias::CAlias
 *
 * Purpose:
 *  Constructor for CAlias object. Initializes members to NULL.
 *
 */
CAlias::CAlias()
{
    m_pdispTypeDescBase = NULL;
}

/*
 * CAlias::~CAlias
 *
 * Purpose:
 *  Destructor for CAlias object.
 *
 */
CAlias::~CAlias()
{
    if (m_pdispTypeDescBase) m_pdispTypeDescBase->Release();
}

STDMETHODIMP_(REFCLSID)
CAlias::GetInterfaceID()
{
    return IID_IAlias;
}

STDMETHODIMP_(ITypeDesc FAR*)
CAlias::get_BaseType()
{
    m_pdispTypeDescBase->AddRef();
    return (ITypeDesc FAR*)m_pdispTypeDescBase;
}


STRUCT.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation TypeLibrary Browse Helper Sample
**
**  struct.cpp
**
**  CStruct implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "browseh.h"

/*
 * CStruct::Create
 *
 * Purpose:
 *  Creates an instance of the Struct automation object and initializes it.
 *
 * Parameters:
 *  ptinfo      TypeInfo of struct..
 *  ppStruct    Returns Struct automation object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CStruct::Create(LPTYPEINFO ptinfo, CStruct FAR* FAR* ppStruct)
{
    HRESULT hr;
    CStruct FAR* pStruct = NULL;

    *ppStruct = NULL;

    // Create object.
    pStruct = new CStruct();
    if (pStruct == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }
    // Load type information for the object from type library.
    hr = pStruct->LoadTypeInfo(IID_IStruct);
    if (FAILED(hr))
        goto error;
    // Initialize base class, CTypeInfo
    hr = pStruct->_InitTypeInfo(ptinfo);
    if (FAILED(hr))
        goto error;

    ptinfo->AddRef();
    pStruct->m_ptinfo = ptinfo;

#ifdef _DEBUG
    lstrcpyn(pStruct->m_szClassName, TEXT("Struct"), 100);
#endif

    *ppStruct = pStruct;
    return NOERROR;

error:
    if (pStruct == NULL) return E_OUTOFMEMORY;
    if (pStruct->m_ptinfo) pStruct->m_ptinfo->Release();

    // Set to NULL to prevent destructor from attempting to free again
    pStruct->m_ptinfo = NULL;

    delete pStruct;
    return hr;
}

/*
 * CStruct::CStruct
 *
 * Purpose:
 *  Constructor for CStruct object. Initializes members to NULL.
 *
 */
CStruct::CStruct()
{
    m_pdispMembers = NULL;
    m_ptinfo = NULL;
}

/*
 * CStruct::~CStruct
 *
 * Purpose:
 *  Destructor for CStruct object.
 *
 */
CStruct::~CStruct()
{
    if (m_pdispMembers) m_pdispMembers->Release();
    if (m_ptinfo) m_ptinfo->Release();
}

STDMETHODIMP_(REFCLSID)
CStruct::GetInterfaceID()
{
    return IID_IStruct;
}

STDMETHODIMP_(ICollection FAR*)
CStruct::get_Members()
{
    HRESULT hr;
    CProperty FAR* pProperty;
    CCollection FAR* pCollection = NULL;
    LPDISPATCH pdisp;
    LPVARDESC pvardesc = NULL;
    LPTYPEATTR ptypeattr = NULL;
    unsigned short n;

    if (m_pdispMembers == NULL)
    {
        // Create a collection of structure members
        hr = m_ptinfo->GetTypeAttr(&ptypeattr);
        if (FAILED(hr))
            {RaiseException(IDS_Unexpected); return NULL;}
        hr = CCollection::Create(ptypeattr->cVars, 0, &pCollection);
        if (FAILED(hr))
            {RaiseException(IDS_Unexpected); goto error;}
        for (n=0; n<ptypeattr->cVars; n++)
        {
            hr = m_ptinfo->GetVarDesc(n, &pvardesc);
            if (FAILED(hr))
                {RaiseException(IDS_Unexpected); goto error;}
            hr = CProperty::Create(m_ptinfo, pvardesc, &pProperty);
            if (FAILED(hr))
                {RaiseException(IDS_Unexpected); goto error;}
            m_ptinfo->ReleaseVarDesc(pvardesc);
            pvardesc = NULL;
            pProperty->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
            pCollection->Add(pdisp);
            pdisp->Release();
        }
        pCollection->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
        m_pdispMembers = pdisp;
        m_ptinfo->ReleaseTypeAttr(ptypeattr);
    }
    m_pdispMembers->AddRef();
    return (ICollection FAR*)m_pdispMembers;

error:
    if (ptypeattr) m_ptinfo->ReleaseTypeAttr(ptypeattr);
    if (pCollection) delete pCollection;
    if (pvardesc) m_ptinfo->ReleaseVarDesc(pvardesc);
    return NULL;
}


TYPE.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation TypeLibrary Browse Helper Sample
**
**  type.cpp
**
**  CTypeDesc implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "browseh.h"

/*
 * CTypeDesc::Create
 *
 * Purpose:
 *  Creates an instance of the TypeDesc automation object and initializes it.
 *
 * ArrayBounds:
 *  ptinfo        TypeInfo in which the element which this type describes is contained.
 *  ptypedesc     TYPDESC that describes this type.
 *  ppTypeDesc    Returns TypeDesc automation object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CTypeDesc::Create(LPTYPEINFO ptinfo, TYPEDESC FAR* ptypedesc, CTypeDesc FAR* FAR* ppTypeDesc)
{
    HRESULT hr;
    CTypeDesc FAR* pTypeDesc = NULL;
    CTypeDesc FAR* pTypeDescPointedAt = NULL;
    CArrayDesc FAR* pArrayDesc = NULL;
    CTypeInfo FAR* pTypeInfo = NULL;
    LPTYPEINFO ptinfoUserDefined;

    *ppTypeDesc = NULL;

    pTypeDesc = new CTypeDesc();
    if (pTypeDesc == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }
    // Load type information for the object from type library.
    hr = pTypeDesc->LoadTypeInfo(IID_ITypeDesc);
    if (FAILED(hr))
        goto error;

    pTypeDesc->m_vartype = ptypedesc->vt;
    if (ptypedesc->vt == VT_USERDEFINED)
    {
        hr = ptinfo->GetRefTypeInfo(ptypedesc->hreftype, &ptinfoUserDefined);
        if (FAILED(hr))
            goto error;
        hr = CTypeInfo::Create(ptinfoUserDefined, &pTypeInfo);
        if (FAILED(hr))
            goto error;
        pTypeInfo->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pTypeDesc->m_pdispUserDefinedDesc);
    }
    else if (ptypedesc->vt == VT_CARRAY)
    {
        hr = CArrayDesc::Create(ptinfo, ptypedesc->lpadesc, &pArrayDesc);
        if (FAILED(hr))
            goto error;
        pArrayDesc->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pTypeDesc->m_pdispArrayDesc);
    }
    else if (ptypedesc->vt == VT_PTR)
    {
        hr = CTypeDesc::Create(ptinfo, ptypedesc->lptdesc, &pTypeDescPointedAt);
        if (FAILED(hr))
            goto error;
        pTypeDescPointedAt->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pTypeDesc->m_pdispPointerDesc);
    }

#ifdef _DEBUG
    lstrcpyn(pTypeDesc->m_szClassName, TEXT("TypeDesc"), 100);
#endif

    *ppTypeDesc = pTypeDesc;
    return NOERROR;

error:
    if (pTypeDesc == NULL) return E_OUTOFMEMORY;

    // Set to NULL to prevent destructor from attempting to free again

    delete pTypeDesc;
    return hr;
}

/*
 * CTypeDesc::CTypeDesc
 *
 * Purpose:
 *  Constructor for CTypeDesc object. Initializes members to NULL.
 *
 */
CTypeDesc::CTypeDesc()
{
    m_pdispUserDefinedDesc = NULL;
    m_pdispArrayDesc = NULL;
    m_pdispPointerDesc = NULL;
}

/*
 * CTypeDesc::~CTypeDesc
 *
 * Purpose:
 *  Destructor for CTypeDesc object.
 *
 */
CTypeDesc::~CTypeDesc()
{
    if (m_pdispUserDefinedDesc) m_pdispUserDefinedDesc->Release();
    if (m_pdispArrayDesc) m_pdispArrayDesc->Release();
    if (m_pdispPointerDesc) m_pdispPointerDesc->Release();
}

STDMETHODIMP_(REFCLSID)
CTypeDesc::GetInterfaceID()
{
    return IID_ITypeDesc;
}

STDMETHODIMP_(short)
CTypeDesc::get_Type()
{
    return (short)m_vartype;
}

STDMETHODIMP_(ITypeInformation FAR*)
CTypeDesc::get_UserDefinedDesc()
{
    if (NULL == m_pdispUserDefinedDesc)
    {
        RaiseException(IDS_WrongType);
        return NULL;
    }
    m_pdispUserDefinedDesc->AddRef();
    return (ITypeInformation FAR*)m_pdispUserDefinedDesc;
}

STDMETHODIMP_(IArrayDesc FAR*)
CTypeDesc::get_ArrayDesc()
{
    if (NULL == m_pdispArrayDesc)
    {
        RaiseException(IDS_WrongType);
        return NULL;
    }
    m_pdispArrayDesc->AddRef();
    return (IArrayDesc FAR*)m_pdispArrayDesc;
}

STDMETHODIMP_(ITypeDesc FAR*)
CTypeDesc::get_PointerDesc()
{
    if (NULL == m_pdispPointerDesc)
    {
        RaiseException(IDS_WrongType);
        return NULL;
    }
    m_pdispPointerDesc->AddRef();
    return (ITypeDesc FAR*)m_pdispPointerDesc;
}

/*
 * CArrayDesc::Create
 *
 * Purpose:
 *  Creates an instance of the ArrayDesc automation object and initializes it.
 *  An ArrayDesc describes a C-style array.
 *
 * ArrayBounds:
 *  ptinfo        TypeInfo in which the element which this type describes is contained.
 *  parraydesc    ARRAYDESC that describes this ArrayDesc object.
 *  ppArrayDesc   Returns ArrayDesc automation object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CArrayDesc::Create(LPTYPEINFO ptinfo, ARRAYDESC FAR* parraydesc, CArrayDesc FAR* FAR* ppArrayDesc)
{
    HRESULT hr;
    CArrayDesc FAR* pArrayDesc = NULL;
    CTypeDesc FAR* pTypeDesc = NULL;
    CCollection FAR* pCollection = NULL;
    CArrayBound FAR* pArrayBound;
    USHORT n;
    LPDISPATCH pdisp;

    *ppArrayDesc = NULL;

    // Create object.
    pArrayDesc = new CArrayDesc();
    if (pArrayDesc == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }

    // Load type information for the object from type library.
    hr = pArrayDesc->LoadTypeInfo(IID_IArrayDesc);
    if (FAILED(hr))
        goto error;

    hr = CTypeDesc::Create(ptinfo, &parraydesc->tdescElem, &pTypeDesc);
    if (FAILED(hr))
        goto error;
    pTypeDesc->QueryInterface(IID_IDispatch, (LPVOID FAR*)&pArrayDesc->m_pdispTypeDescElement);

    // Create collection of array bounds. One array bound per dimension.
    hr = CCollection::Create(parraydesc->cDims, 0, &pCollection);
    if (FAILED(hr))
        goto error;
    for (n=0; n<parraydesc->cDims; n++)
    {
        hr = CArrayBound::Create(parraydesc->rgbounds[n].cElements, parraydesc->rgbounds[n].lLbound, &pArrayBound);
        if (FAILED(hr))
            goto error;
        pArrayBound->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
        pCollection->Add(pdisp);
        pdisp->Release();
    }
    pCollection->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
    pArrayDesc->m_pdispArrayBounds = pdisp;

#ifdef _DEBUG
    lstrcpyn(pArrayDesc->m_szClassName, TEXT("ArrayDesc"), 100);
#endif

    *ppArrayDesc = pArrayDesc;
    return NOERROR;

error:
    if (pArrayDesc == NULL) return E_OUTOFMEMORY;

    // Set to NULL to prevent destructor from attempting to free again
    pArrayDesc->m_pdispTypeDescElement = NULL;
    pArrayDesc->m_pdispArrayBounds = NULL;
    delete pArrayDesc;
    return hr;
}

/*
 * CArrayDesc::CArrayDesc
 *
 * Purpose:
 *  Constructor for CArrayDesc object. Initializes members to NULL.
 *
 */
CArrayDesc::CArrayDesc()
{
    m_pdispTypeDescElement = NULL;
    m_pdispArrayBounds = NULL;
}

/*
 * CArrayDesc::~CArrayDesc
 *
 * Purpose:
 *  Destructor for CArrayDesc object.
 *
 */
CArrayDesc::~CArrayDesc()
{
     if (m_pdispTypeDescElement) m_pdispTypeDescElement->Release();
     if (m_pdispArrayBounds) m_pdispArrayBounds->Release();
}

STDMETHODIMP_(REFCLSID)
CArrayDesc::GetInterfaceID()
{
    return IID_IArrayDesc;
}

STDMETHODIMP_(ITypeDesc FAR*)
CArrayDesc::get_ElementType()
{
    m_pdispTypeDescElement->AddRef();
    return (ITypeDesc FAR*)m_pdispTypeDescElement;
}

STDMETHODIMP_(ICollection FAR*)
CArrayDesc::get_ArrayBounds()
{
    m_pdispArrayBounds->AddRef();
    return (ICollection FAR*)m_pdispArrayBounds;
}

/*
 * CArrayBound::Create
 *
 * Purpose:
 *  Creates an instance of the ArrayBound automation object and initializes it.
 *
 * ArrayBounds:
 *  cElements       Number of elements in Array.
 *  lLBound         Lower bound of array.
 *  ppArrayBound    Returns ArrayBound automation object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CArrayBound::Create(unsigned long cElements, long lLBound, CArrayBound FAR* FAR* ppArrayBound)
{
    HRESULT hr;
    CArrayBound FAR* pArrayBound = NULL;

    *ppArrayBound = NULL;

    // Create object.
    pArrayBound = new CArrayBound();
    if (pArrayBound == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }
   // Load type information for the application object from type library.
    hr = pArrayBound->LoadTypeInfo(IID_IArrayBound);
    if (FAILED(hr))
        goto error;

    pArrayBound->m_cElements = cElements;
    pArrayBound->m_lLBound = lLBound;

#ifdef _DEBUG
    lstrcpyn(pArrayBound->m_szClassName, TEXT("ArrayBound"), 100);
#endif

    *ppArrayBound = pArrayBound;
    return NOERROR;

error:
    if (pArrayBound == NULL) return E_OUTOFMEMORY;

    delete pArrayBound;
    return hr;
}

STDMETHODIMP_(REFCLSID)
CArrayBound::GetInterfaceID()
{
    return IID_IArrayBound;
}

STDMETHODIMP_(long)
CArrayBound::get_ElementsCount()
{
    return (long)m_cElements;
}

STDMETHODIMP_(long)
CArrayBound::get_LowerBound()
{
    return m_lLBound;
}


TYPEINFO.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation TypeLibrary Browse Helper Sample
**
**  typeinfo.cpp
**
**  CTypeInfo implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "browseh.h"

/*
 * CTypeInfo::Create
 *
 * Purpose:
 *  Creates an instance of the TypeInfo automation object and initializes it.
 *
 * Parameters:
 *  ptinfo        TypeInfo in type library..
 *  ppTypeInfo    Returns TypeInfo automation object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CTypeInfo::Create(LPTYPEINFO ptinfo, CTypeInfo FAR* FAR* ppTypeInfo)
{
    HRESULT hr;
    CTypeInfo FAR* pTypeInfo = NULL;

    *ppTypeInfo = NULL;

    // Create application object.
    pTypeInfo = new CTypeInfo();
    if (pTypeInfo == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }
    // Load type information for the application object from type library.
    hr = pTypeInfo->LoadTypeInfo(IID_ITypeInformation);
    if (FAILED(hr))
        goto error;
    hr = pTypeInfo->_InitTypeInfo(ptinfo);
    if (FAILED(hr))
        goto error;

#ifdef _DEBUG
    lstrcpyn(pTypeInfo->m_szClassName, TEXT("TypeInfo"), 100);
#endif

    *ppTypeInfo = pTypeInfo;
    return NOERROR;

error:
    if (pTypeInfo == NULL) return E_OUTOFMEMORY;

    delete pTypeInfo;
    return hr;
}

/*
 * CTypeInfo::CTypeInfo
 *
 * Purpose:
 *  Constructor for CTypeInfo object. Initializes members to NULL.
 *
 */
CTypeInfo::CTypeInfo()
{
    m_bstrName = NULL;
    m_bstrDocumentation = NULL;
    m_bstrHelpFile = NULL;
        m_pszGUID = NULL;
}

/*
 * CTypeInfo::~CTypeInfo
 *
 * Purpose:
 *  Destructor for CTypeInfo object.
 *
 */
CTypeInfo::~CTypeInfo()
{
     if (m_bstrName) SysFreeString(m_bstrName);
     if (m_bstrDocumentation) SysFreeString(m_bstrDocumentation);
     if (m_bstrHelpFile) SysFreeString(m_bstrHelpFile);

#ifdef WIN32
    if (m_pszGUID) CoTaskMemFree(m_pszGUID);
#else
    HRESULT hr;
    LPMALLOC pmalloc;
    if (m_pszGUID)
    {
       hr = CoGetMalloc(MEMCTX_TASK, &pmalloc);
       if (SUCCEEDED(hr))
       {
           pmalloc->Free(m_pszGUID);
           pmalloc->Release();
       }
    }
#endif
}

STDMETHODIMP_(REFCLSID)
CTypeInfo::GetInterfaceID()
{
    return IID_ITypeInformation;
}

STDMETHODIMP
CTypeInfo::_InitTypeInfo(LPTYPEINFO ptinfo)
{
    HRESULT hr;
    LPTYPELIB ptlib = NULL;
    unsigned int nIndex;
    LPTYPEATTR ptypeattr;

    // Get information from TYPEATTR
    hr = ptinfo->GetTypeAttr(&ptypeattr);
    if (FAILED(hr))
        return hr;
    m_typekind = ptypeattr->typekind;
    m_guid = ptypeattr->guid;
        m_wTypeFlags = ptypeattr->wTypeFlags;
    ptinfo->ReleaseTypeAttr(ptypeattr);

    hr = ptinfo->GetContainingTypeLib(&ptlib, &nIndex);
    if (FAILED(hr))
    {
        if (hr == E_NOTIMPL)  //Supportes runtime typeinfo browsing. (Type info
                                        //obtained from IDispatch::GetTypeInfo does not have a containing
                                        //type library).
           return NOERROR;
        else return hr;
    }
    hr = ptlib->GetDocumentation(nIndex, &m_bstrName, &m_bstrDocumentation,
             &m_ulHelpContext, &m_bstrHelpFile);
    if (FAILED(hr))
        goto error;
    ptlib->Release();
    return NOERROR;

error:
    if (ptlib) ptlib->Release();
    if (m_bstrName) SysFreeString(m_bstrName);
    if (m_bstrDocumentation) SysFreeString(m_bstrDocumentation);
    if (m_bstrHelpFile) SysFreeString(m_bstrHelpFile);
    m_bstrName = NULL;
    m_bstrDocumentation = NULL;
    m_bstrHelpFile = NULL;
    return hr;
}

STDMETHODIMP_(BSTR)
CTypeInfo::get_Name()
{
    return SysAllocString(m_bstrName);
}

STDMETHODIMP_(BSTR)
CTypeInfo::get_Documentation()
{
    return SysAllocString(m_bstrDocumentation);
}

STDMETHODIMP_(long)
CTypeInfo::get_HelpContext()
{
    return (long)m_ulHelpContext;
}

STDMETHODIMP_(BSTR)
CTypeInfo::get_HelpFile()
{
    return SysAllocString(m_bstrHelpFile);
}

STDMETHODIMP_(TYPEKIND)
CTypeInfo::get_TypeInfoKind()
{
    return m_typekind;
}

STDMETHODIMP_(short)
CTypeInfo::get_TypeFlags()
{
    return m_wTypeFlags;
}

STDMETHODIMP_(BSTR)
CTypeInfo::get_GUIDAsString()
{
        if (m_pszGUID == NULL)
            StringFromCLSID(m_guid, &m_pszGUID);
    return SysAllocString(m_pszGUID);
}


TYPELIB.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation TypeLibrary Browse Helper Sample
**
**  typelib.cpp
**
**  CTypeLibrary implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "browseh.h"

/*
 * CTypeLibrary::Create
 *
 * Purpose:
 *  Creates an instance of the TypeLibrary automation object and initializes it.
 *
 * Parameters:
 *  ptlib            ITypeLib interface that corresponds to this CTypeLibrary object.
 *  ppTypeLibrary    Returns TypeLibrary automation object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CTypeLibrary::Create(LPTYPELIB ptlib, CTypeLibrary FAR* FAR* ppTypeLibrary )
{
    HRESULT hr;
    CTypeLibrary FAR* pTypeLibrary = NULL;
    LPTLIBATTR ptlibattr;

    *ppTypeLibrary = NULL;

    // Create typelibrary object.
    pTypeLibrary = new CTypeLibrary();
    if (pTypeLibrary == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }

    // Load type information for the typelibrary object from type library.
    hr = pTypeLibrary->LoadTypeInfo(IID_ITypeLibrary);
    if (FAILED(hr))
        goto error;

    hr = ptlib->GetDocumentation(-1, &pTypeLibrary->m_bstrName, &pTypeLibrary->m_bstrDocumentation,
             &pTypeLibrary->m_ulHelpContext, &pTypeLibrary->m_bstrHelpFile);
    if (FAILED(hr))
        goto error;

    hr = ptlib->GetLibAttr(&ptlibattr);
    if (FAILED(hr))
        goto error;
    pTypeLibrary->m_guid = ptlibattr->guid;
    pTypeLibrary->m_lcid = ptlibattr->lcid;
    pTypeLibrary->m_wMajorVer = ptlibattr->wMajorVerNum;
    pTypeLibrary->m_wMinorVer = ptlibattr->wMinorVerNum;
    ptlib->ReleaseTLibAttr(ptlibattr);

    ptlib->AddRef();
    pTypeLibrary->m_ptlib = ptlib;

#ifdef _DEBUG
    lstrcpyn(pTypeLibrary->m_szClassName, TEXT("TypeLibrary"), 100);
#endif

    *ppTypeLibrary = pTypeLibrary;
    return NOERROR;

error:
    if (pTypeLibrary == NULL) return E_OUTOFMEMORY;
    if (pTypeLibrary->m_ptlib) pTypeLibrary->m_ptlib->Release();
    if (pTypeLibrary->m_bstrName) SysFreeString(pTypeLibrary->m_bstrName);
    if (pTypeLibrary->m_bstrDocumentation) SysFreeString(pTypeLibrary->m_bstrDocumentation);
    if (pTypeLibrary->m_bstrHelpFile) SysFreeString(pTypeLibrary->m_bstrHelpFile);

    // Set to NULL to prevent destructor from attempting to free again
    pTypeLibrary->m_ptlib = NULL;
    pTypeLibrary->m_bstrName = NULL;
    pTypeLibrary->m_bstrDocumentation = NULL;
    pTypeLibrary->m_bstrHelpFile = NULL;

    delete pTypeLibrary;
    return hr;
}

/*
 * CTypeLibrary::CTypeLibrary
 *
 * Purpose:
 *  Constructor for CTypeLibrary object. Initializes members to NULL.
 *
 */
CTypeLibrary::CTypeLibrary()
{
    m_ptlib = NULL;
    m_bstrName = NULL;
    m_bstrDocumentation = NULL;
    m_bstrHelpFile = NULL;
    m_pdispTypeInfos = NULL;
        m_pszGUID = NULL;
}

/*
 * CTypeLibrary::~CTypeLibrary
 *
 * Purpose:
 *  Destructor for CTypeLibrary object. Frees TypeLibrary message BSTR and default
 *  IDispatch implementation. Closes the aplication.
 *
 */
CTypeLibrary::~CTypeLibrary()
{
    if (m_ptlib) m_ptlib->Release();
    if (m_bstrName) SysFreeString(m_bstrName);
    if (m_bstrDocumentation) SysFreeString(m_bstrDocumentation);
    if (m_bstrHelpFile) SysFreeString(m_bstrHelpFile);
    if (m_pdispTypeInfos) m_pdispTypeInfos->Release();
#ifdef WIN32
    if (m_pszGUID) CoTaskMemFree(m_pszGUID);
#else
    HRESULT hr;
    LPMALLOC pmalloc;
    if (m_pszGUID)
    {
       hr = CoGetMalloc(MEMCTX_TASK, &pmalloc);
       if (SUCCEEDED(hr))
       {
           pmalloc->Free(m_pszGUID);
           pmalloc->Release();
       }
    }
#endif

}

STDMETHODIMP_(REFCLSID)
CTypeLibrary::GetInterfaceID()
{
    return IID_ITypeLibrary;
}

STDMETHODIMP_(BSTR)
CTypeLibrary::get_Name()
{
    return SysAllocString(m_bstrName);
}

STDMETHODIMP_(BSTR)
CTypeLibrary::get_Documentation()
{
    return SysAllocString(m_bstrDocumentation);
}

STDMETHODIMP_(long)
CTypeLibrary::get_HelpContext()
{
    return (long)m_ulHelpContext;
}

STDMETHODIMP_(BSTR)
CTypeLibrary::get_HelpFile()
{
    return SysAllocString(m_bstrHelpFile);
}

STDMETHODIMP_(int)
CTypeLibrary::get_MajorVersion()
{
    return (int)m_wMajorVer;
}

STDMETHODIMP_(int)
CTypeLibrary::get_MinorVersion()
{
    return (int)m_wMinorVer;
}

STDMETHODIMP_(long)
CTypeLibrary::get_LocaleID()
{
    return (long)m_lcid;
}

STDMETHODIMP_(ICollection FAR*)
CTypeLibrary::get_TypeInfos()
{
    HRESULT hr;
    CTypeInfo FAR* pTypeInfo;
    CInterface FAR* pInterface;
    CDispinterface FAR* pDispinterface;
    CModule FAR* pModule;
    CCoClass FAR* pCoClass;
    CEnum FAR* pEnum;
    CAlias FAR* pAlias;
    CStruct FAR* pStruct;
    CUnion FAR* pUnion;
    CCollection FAR* pCollection = NULL;
    LPDISPATCH pdisp;
    LPTYPEINFO ptinfo = NULL;
    unsigned int cTypeInfo;
    unsigned int n;
    TYPEKIND typekind;

    if (m_pdispTypeInfos == NULL)
    {
        cTypeInfo = m_ptlib->GetTypeInfoCount();
        hr = CCollection::Create(cTypeInfo, 0, &pCollection);
        if (FAILED(hr))
            {RaiseException(IDS_Unexpected); goto error;}

        // Enumerate the typeinfos in this type library
        for (n=0; n<cTypeInfo; n++)
        {
            hr = m_ptlib->GetTypeInfo(n, &ptinfo);
            if (FAILED(hr))
                {RaiseException(IDS_Unexpected); goto error;}
            hr = m_ptlib->GetTypeInfoType(n, &typekind);
            if (FAILED(hr))
                {RaiseException(IDS_Unexpected); goto error;}
            switch (typekind)
            {
                case TKIND_INTERFACE:
                    hr = CInterface::Create(ptinfo, &pInterface);
                    if (FAILED(hr))
                        {RaiseException(IDS_Unexpected); goto error;}
                    pInterface->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
                    break;

                case TKIND_DISPATCH:
                    hr = CDispinterface::Create(ptinfo, &pDispinterface);
                    if (FAILED(hr))
                        {RaiseException(IDS_Unexpected); goto error;}
                    pDispinterface->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
                    break;

                case TKIND_MODULE:
                    hr = CModule::Create(ptinfo, &pModule);
                    if (FAILED(hr))
                        {RaiseException(IDS_Unexpected); goto error;}
                    pModule->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
                    break;

                case TKIND_COCLASS:
                    hr = CCoClass::Create(ptinfo, &pCoClass);
                    if (FAILED(hr))
                        {RaiseException(IDS_Unexpected); goto error;}
                    pCoClass->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
                    break;

                case TKIND_ENUM:
                    hr = CEnum::Create(ptinfo, &pEnum);
                    if (FAILED(hr))
                        {RaiseException(IDS_Unexpected); goto error;}
                    pEnum->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
                    break;

                case TKIND_ALIAS:
                    hr = CAlias::Create(ptinfo, &pAlias);
                    if (FAILED(hr))
                        {RaiseException(IDS_Unexpected); goto error;}
                    pAlias->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
                    break;

                 case TKIND_RECORD:
                    hr = CStruct::Create(ptinfo, &pStruct);
                    if (FAILED(hr))
                        {RaiseException(IDS_Unexpected); goto error;}
                    pStruct->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
                    break;

                 case TKIND_UNION:
                    hr = CUnion::Create(ptinfo, &pUnion);
                    if (FAILED(hr))
                        {RaiseException(IDS_Unexpected); goto error;}
                    pUnion->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
                    break;

                default:
                    // Currently unsupported TKINDs
                    hr = CTypeInfo::Create(ptinfo, &pTypeInfo);
                    if (FAILED(hr))
                        {RaiseException(IDS_Unexpected); goto error;}
                    pTypeInfo->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
                    break;
            }
            // Add typeinfo to collection of typeinfos.
            pCollection->Add(pdisp);
            pdisp->Release();
            ptinfo->Release();
            ptinfo = NULL;
        }

        pCollection->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
        m_pdispTypeInfos = pdisp;
    }
    m_pdispTypeInfos->AddRef();
    return (ICollection FAR*)m_pdispTypeInfos;

error:
    if (ptinfo) ptinfo->Release();
    if (pCollection) delete pCollection;
    return NULL;
}

STDMETHODIMP_(BSTR)
CTypeLibrary::get_GUIDAsString()
{
        if (m_pszGUID == NULL)
            StringFromCLSID(m_guid, &m_pszGUID);
    return SysAllocString(m_pszGUID);
}


UNION.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation TypeLibrary Browse Helper Sample
**
**  union.cpp
**
**  CUnion implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "browseh.h"

/*
 * CUnion::Create
 *
 * Purpose:
 *  Creates an instance of the Union automation object and initializes it.
 *
 * Parameters:
 *  ptinfo     TypeInfo of Union.
 *  ppUnion    Returns Union automation object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CUnion::Create(LPTYPEINFO ptinfo, CUnion FAR* FAR* ppUnion)
{
    HRESULT hr;
    CUnion FAR* pUnion = NULL;

    *ppUnion = NULL;

    // Create application object.
    pUnion = new CUnion();
    if (pUnion == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }
    // Load type information for the object from type library.
    hr = pUnion->LoadTypeInfo(IID_IUnion);
    if (FAILED(hr))
        goto error;
    // Initialise base class, CTypeInfo.
    hr = pUnion->_InitTypeInfo(ptinfo);
    if (FAILED(hr))
        goto error;

    ptinfo->AddRef();
    pUnion->m_ptinfo = ptinfo;

#ifdef _DEBUG
    lstrcpyn(pUnion->m_szClassName, TEXT("Union"), 100);
#endif

    *ppUnion = pUnion;
    return NOERROR;

error:
    if (pUnion == NULL) return E_OUTOFMEMORY;
    if (pUnion->m_ptinfo) pUnion->m_ptinfo->Release();

    // Set to NULL to prevent destructor from attempting to free again
    pUnion->m_ptinfo = NULL;

    delete pUnion;
    return hr;
}

/*
 * CUnion::CUnion
 *
 * Purpose:
 *  Constructor for CUnion object. Initializes members to NULL.
 *
 */
CUnion::CUnion()
{
    m_pdispMembers = NULL;
    m_ptinfo = NULL;
}

/*
 * CUnion::~CUnion
 *
 * Purpose:
 *  Destructor for CUnion object.
 *
 */
CUnion::~CUnion()
{
    if (m_pdispMembers) m_pdispMembers->Release();
    if (m_ptinfo) m_ptinfo->Release();
}

STDMETHODIMP_(REFCLSID)
CUnion::GetInterfaceID()
{
    return IID_IUnion;
}

/*
 * CUnion::
 *
 * Purpose:
 *
 *
 */

STDMETHODIMP_(ICollection FAR*)
CUnion::get_Members()
{
    HRESULT hr;
    CProperty FAR* pProperty;
    CCollection FAR* pCollection = NULL;
    LPDISPATCH pdisp;
    LPVARDESC pvardesc = NULL;
    LPTYPEATTR ptypeattr = NULL;
    unsigned short n;

    if (m_pdispMembers == NULL)
    {
        // Create collection of union memebers.
        hr = m_ptinfo->GetTypeAttr(&ptypeattr);
        if (FAILED(hr))
            {RaiseException(IDS_Unexpected); return NULL;}
        hr = CCollection::Create(ptypeattr->cVars, 0, &pCollection);
        if (FAILED(hr))
            {RaiseException(IDS_Unexpected); goto error;}
        for (n=0; n<ptypeattr->cVars; n++)
        {
            hr = m_ptinfo->GetVarDesc(n, &pvardesc);
            if (FAILED(hr))
                {RaiseException(IDS_Unexpected); goto error;}
            hr = CProperty::Create(m_ptinfo, pvardesc, &pProperty);
            if (FAILED(hr))
                {RaiseException(IDS_Unexpected); goto error;}
            m_ptinfo->ReleaseVarDesc(pvardesc);
            pvardesc = NULL;
            pProperty->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
            pCollection->Add(pdisp);
            pdisp->Release();
        }
        pCollection->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdisp);
        m_pdispMembers = pdisp;
        m_ptinfo->ReleaseTypeAttr(ptypeattr);
    }
    m_pdispMembers->AddRef();
    return (ICollection FAR*)m_pdispMembers;

error:
    if (ptypeattr) m_ptinfo->ReleaseTypeAttr(ptypeattr);
    if (pCollection) delete pCollection;
    if (pvardesc) m_ptinfo->ReleaseVarDesc(pvardesc);
    return NULL;
}


VB.FRM

VERSION 4.00
Begin VB.Form Form1
   Appearance      =   0  'Flat
   BackColor       =   &H80000005&
   Caption         =   "Header file generator"
   ClientHeight    =   2010
   ClientLeft      =   4110
   ClientTop       =   2640
   ClientWidth     =   5370
   BeginProperty Font
      name            =   "MS Sans Serif"
      charset         =   0
      weight          =   700
      size            =   8.25
      underline       =   0   'False
      italic          =   0   'False
      strikethrough   =   0   'False
   EndProperty
   ForeColor       =   &H80000008&
   Height          =   2415
   Left            =   4050
   LinkTopic       =   "Form1"
   ScaleHeight     =   2010
   ScaleWidth      =   5370
   Top             =   2295
   Width           =   5490
   Begin VB.CommandButton ChooseTypeLibrary
      Caption         =   "Choose Type Library"
      Height          =   495
      Left            =   1560
      TabIndex        =   0
      Top             =   360
      Width           =   2415
   End
   Begin MSComDlg.CommonDialog SaveOutputDialog
      Left            =   120
      Top             =   1080
      _Version        =   65536
      _ExtentX        =   847
      _ExtentY        =   847
      _StockProps     =   0
      DialogTitle     =   "Save Output As"
      Filter          =   "(*.h)|*.h"
   End
   Begin MSComDlg.CommonDialog ChooseTlibDialog
      Left            =   120
      Top             =   480
      _Version        =   65536
      _ExtentX        =   847
      _ExtentY        =   847
      _StockProps     =   0
      DialogTitle     =   "Choose Type Library"
      Filter          =   "Type Libraries |*.tlb;*.olb;*.dll;*.exe"
   End
End
Attribute VB_Name = "Form1"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
'TYPEKIND constants
Const TKIND_ENUM = 0
Const TKIND_RECORD = 1
Const TKIND_MODULE = 2
Const TKIND_INTERFACE = 3
Const TKIND_DISPATCH = 4
Const TKIND_COCLASS = 5
Const TKIND_ALIAS = 6
Const TKIND_UNION = 7

'INVOKEKIND constants
Const INVOKE_FUNC = 1
Const INVOKE_PROPERTYGET = 2
Const INVOKE_PROPERTYPUT = 4
Const INVOKE_PROPERTYPUTREF = 8

'VARENUM constants
Const VT_I2 = 2
Const VT_I4 = 3
Const VT_R4 = 4
Const VT_R8 = 5
Const VT_CY = 6
Const VT_DATE = 7
Const VT_BSTR = 8
Const VT_DISPATCH = 9
Const VT_ERROR = 10
Const VT_BOOL = 11
Const VT_VARIANT = 12
Const VT_UNKNOWN = 13
Const VT_I1 = 16
Const VT_UI1 = 17
Const VT_UI2 = 18
Const VT_UI4 = 19
Const VT_I8 = 20
Const VT_UI8 = 21
Const VT_INT = 22
Const VT_UINT = 23
Const VT_VOID = 24
Const VT_HRESULT = 25
Const VT_PTR = 26
Const VT_SAFEARRAY = 27
Const VT_CARRAY = 28
Const VT_USERDEFINED = 29
Const VT_LPSTR = 30
Const VT_LPWSTR = 31

' TYPEFLAGS
Const TYPEFLAG_FDUAL = &H40

Private Sub ChooseTypeLibrary_Click()
Dim browser As Object
Dim tlib As Object
Dim tinfos As Object
Dim tinfo As Object
Dim funcs As Object
Dim func As Object
Dim params As Object
Dim param As Object
Dim element As Object
Dim elements As Object
Dim member As Object
Dim members As Object
Dim tinfoBase As Object

' Get name of input type library
On Error GoTo DialogCancel
ChooseTlibDialog.CancelError = True
ChooseTlibDialog.ShowOpen

' Create Browse Helper (BROWSEH sample)
Set browser = CreateObject("BrowseHelper.Browser")
Set tlib = browser.BrowseTypeLibrary(ChooseTlibDialog.filename)
Set tinfos = tlib.TypeInfos

' Get name of output header file
On Error GoTo DialogCancel
SaveOutputDialog.CancelError = True
SaveOutputDialog.ShowSave
Open SaveOutputDialog.filename For Output As 1

Print #1, "DEFINE_GUID(LIBID_"; tlib.Name; ","; FormatGUID(tlib.GUIDAsString); ");"
Print #1,

' Enumerate typeinfos in the type library
For i = 0 To tinfos.Count - 1
  Set tinfo = tinfos.Item(i)

  ' Output header file contents depending on the TYPEKIND of the typeinfo
  Select Case tinfo.TypeInfoKind
     Case TKIND_ENUM     'Enum
        Print #1, "typedef enum{"
        Set elements = tinfo.elements
        For j = 0 To elements.Count - 1
            Set element = elements.Item(j)
            Print #1, Tab(1); element.Name; " = "; element.Value;
            If j < elements.Count - 1 Then
                    Print #1, ",";
            End If
        Next j
        Print #1,
        Print #1, "} "; tinfo.Name; ";"

    Case TKIND_RECORD     'Struct
        Print #1, "typedef struct{"
        Set members = tinfo.members
        For j = 0 To members.Count - 1
            Set member = members.Item(j)
            Print #1, Tab(1); TypeToString(member.Type); " "; member.Name; ";"
        Next j
        Print #1, "} "; tinfo.Name; ";"

    Case TKIND_UNION     'Union
        Print #1, "typedef union{"
        Set members = tinfo.members
        For j = 0 To members.Count - 1
            Set member = members.Item(j)
            Print #1, Tab(1); TypeToString(member.Type); " "; member.Name; ";"
        Next j
        Print #1, "} "; tinfo.Name; ";"

     Case TKIND_INTERFACE     'Interface
        Print #1, "DEFINE_GUID(IID_"; tinfo.Name; ","; FormatGUID(tinfo.GUIDAsString); ");"
        On Error Resume Next
        Set tinfoBase = tinfo.BaseInterface
        If Err.Number > 0 Then 'If there is no base interface
           Print #1, "DECLARE_INTERFACE("; tinfo.Name; ")"
        Else
           Print #1, "DECLARE_INTERFACE_("; tinfo.Name; ", "; tinfoBase.Name; ")"
        End If
        Print #1, "{"

        ' Output the functions in the interface
        Set funcs = tinfo.Functions
        For j = 0 To funcs.Count - 1
            Set func = funcs.Item(j)
            Print #1, Tab(2); "STDMETHOD_("; TypeToString(func.ReturnType); ", ";
            Select Case func.InvocationKind
                Case INVOKE_PROPERTYGET
                   Print #1, "get_";
                Case INVOKE_PROPERTYPUT
                   Print #1, "put_";
                Case INVOKE_PROPERTYPUTREF
                   Print #1, "putref_";
            End Select
            Set params = func.Parameters
            If params.Count = 0 Then
               Print #1, func.Name; ")(THIS";
            Else
              Print #1, func.Name; ")(THIS_ ";
            End If

            ' Ouput the parameters of the function
            For k = 0 To params.Count - 1
                Set param = params.Item(k)
                Print #1, TypeToString(param.Type); " ";
                Print #1, param.Name;
                If k < params.Count - 1 Then
                    Print #1, ", ";
                End If
            Next k
            Print #1, ") PURE;";
            Print #1,
        Next j
        Print #1, "};"

    Case TKIND_DISPATCH 'dispinterface or dual interface
        TypeFlags = tinfo.TypeFlags()
        ' Check if this is the dispinterface component of
        ' a dual interface. If so get the interface component of the dual interface
        If TypeFlags And TYPEFLAG_FDUAL Then
            Set tinfo = tinfo.Interface
            Set tinfoBase = tinfo.BaseInterface
            Print #1, "DEFINE_GUID(IID_"; tinfo.Name; ","; FormatGUID(tinfo.GUIDAsString); ");"
            Print #1, "DECLARE_INTERFACE_("; tinfo.Name; ", "; tinfoBase.Name; ")"
            Print #1, "{"

            ' Output the functions in the interface
            Set funcs = tinfo.Functions
            For j = 0 To funcs.Count - 1
                Set func = funcs.Item(j)
                Print #1, Tab(2); "STDMETHOD_("; TypeToString(func.ReturnType); ", ";
                Select Case func.InvocationKind
                    Case INVOKE_PROPERTYGET
                       Print #1, "get_";
                    Case INVOKE_PROPERTYPUT
                       Print #1, "put_";
                    Case INVOKE_PROPERTYPUTREF
                      Print #1, "putref_";
                End Select
                Set params = func.Parameters
                If params.Count = 0 Then
                    Print #1, func.Name; ")(THIS";
                Else
                     Print #1, func.Name; ")(THIS_ ";
                End If

                ' Ouput the parameters of the function
                For k = 0 To params.Count - 1
                    Set param = params.Item(k)
                    Print #1, TypeToString(param.Type); " ";
                    Print #1, param.Name;
                    If k < params.Count - 1 Then
                        Print #1, ", ";
                    End If
                Next k
                Print #1, ") PURE;";
                Print #1,
            Next j
            Print #1, "};"
        End If

    Case TKIND_ALIAS     'Alias
        Print #1, "typedef "; TypeToString(tinfo.BaseType); " "; tinfo.Name; ";"

    Case TKIND_COCLASS  'CoClass
         Print #1, "DEFINE_GUID(CLSID_"; tinfo.Name; ","; FormatGUID(tinfo.GUIDAsString); ");"

  End Select
  Print #1,
Next i

Close #1
MsgBox SaveOutputDialog.filename + " has been generated", , "Header File Generator"
DialogCancel:   'User cancelled the dialog
End Sub

' Convert a type to a string
Private Function TypeToString(typeObj As Object) As String
  Dim s As String
  Dim p As Object
  Dim u As Object

  t = typeObj.Type
  Select Case t
     Case VT_I2
         s = "short"
     Case VT_I4
         s = "long"
     Case VT_R4
         s = "float"
     Case VT_R8
         s = "double"
     Case VT_CY
         s = "CURRENCY"
     Case VT_DATE
         s = "DATE"
     Case VT_BSTR
         s = "BSTR"
     Case VT_DISPATCH
         s = "IDispatch FAR*"
     Case VT_ERROR
         s = "SCODE"
     Case VT_BOOL
         s = "VARIANT_BOOL"
     Case VT_VARIANT
         s = "VARIANT"
     Case VT_UNKNOWN
         s = "IUnknown FAR*"
     Case VT_I1
         s = "char"
     Case VT_UI1
         s = "unsigned char"
     Case VT_UI2
         s = "unsigned short"
     Case VT_UI4
         s = "unsigned long"
     Case VT_I8
         s = "64-bit int"
     Case VT_UI8
         s = "unsigned 64-bit int"
     Case VT_INT
         s = "int"
     Case VT_UINT
         s = "unsigned int"
     Case VT_VOID
         s = "void"
     Case VT_HRESULT
         s = "HRESULT"
     Case VT_PTR
         Set p = typeObj.PointerDesc
         s = TypeToString(p) + " FAR*"
     Case VT_SAFEARRAY
         s = "SAFEARRAY FAR*"
     Case VT_USERDEFINED
         Set u = typeObj.UserDefinedDesc
         s = u.Name
     Case VT_LPSTR
         s = "char FAR*"
     Case VT_LPWSTR
         s = "WCHAR FAR*"

  End Select
  If t And &H2000 Then
     s = "SAFEARRAY(" + s + ")"
  End If
  TypeToString = s
End Function

'Return a formatted GUID
Private Function FormatGUID(guid As String) As String

s1 = "0x" + Mid(guid, 2, 8) + "L,"
s2 = "0x" + Mid(guid, 11, 4) + "," + "0x" + Mid(guid, 16, 4) + ","
s3 = "0x" + Mid(guid, 21, 2) + "," + "0x" + Mid(guid, 23, 2) + ","
s4 = "0x" + Mid(guid, 26, 2) + "," + "0x" + Mid(guid, 28, 2) + ","
s5 = "0x" + Mid(guid, 30, 2) + "," + "0x" + Mid(guid, 32, 2) + ","
s6 = "0x" + Mid(guid, 34, 2) + "," + "0x" + Mid(guid, 36, 2)

FormatGUID = s1 + s2 + s3 + s4 + s5 + s6
End Function


README.TXT

OLE Automation Sample

SUMMARY
=======

The DispCalc sample is a simple accumulator-based calculator. Its user
interface consists of buttons for the numbers (0-9), the operators
(+, -, *, /), and some other necessary buttons (C for Clear, = for
evaluation). Its programmability interface consists of one object, which is
described below.

The ProgID for DispCalc's only object is "DispCalc.Application". An instance
of this object can be created by executing the following lines of code in
Visual Basic or DispTest:

    Sub Foo
        Dim MyCalculator as Object
        Set MyCalculator = CreateObject("DispCalc.Application")
        . . .
    End Sub

MORE INFORMATION
================

Program Structure
-----------------

DispCalc uses INTERFACEDATA and CreateStdDispatch to implement the
IDispatch interface.

Properties for the Object
-------------------------

Name     Type     Description

Value    VT_I4    Same as the value for the accumulator.
Accum    VT_I4    The value that is in the accumulator of the calculator.
Opnd     VT_I4    The operand. This is the number currently being entered.
Op       VT_I2    The operator that is currently being used. This is an
                  enumeration:
                      const OP_NONE = 0
                      const OP_PLUS = 1
                      const OP_MINUS = 2
                      const OP_MULT = 3

Methods Defined on the Object
-----------------------------

Name                               Description

Eval as Boolean                    If there is an operator, apply it to the
                                   accumulator and the operand, placing the
                                   result in the accumulator. The return
                                   value indicates success or failure.
Clear                              Resets the calculator. This sets Op to
                                   OP_NONE, and both Accum and Opnd to 0.
Display                            Updates the display of the calculator.
                                   (Other operations do not do this.)
Quit                               Close the calculator.
Button (b as string) as Boolean    Press the indicated button and return
                                   success or failure. Valid string values
                                   are:
                                       +, -, *, +
                                       0-9
                                       c, C
                                       =

                                   Note that you may also pass the numbers
                                   0-9 and these will be converted to
                                   strings automatically.

Shortcomings of this Sample
---------------------------

Property and method names should not be abbreviated. For example, the "Opnd"
property should be the "Operand" property.

Because the object is the application object, it should have Name and
Version properties, which are read-only.


CLSID.H

/***
*clsid.h
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  This file defines the CLSIDs
*
*Implementation Notes:
*
*****************************************************************************/

DEFINE_GUID(CLSID_CCalc, 0x00020467, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);

#ifdef INITGUID
# ifdef _MAC
DEFINE_GUID(GUID_NULL, 0L, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);

#  ifndef _MW_BUILD
/* The Metrowerks PPC build gives multiple definition warnings */
DEFINE_GUID(IID_IDispatch, 0x00020400, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
DEFINE_GUID(IID_IEnumVARIANT, 0x00020404, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
#  endif

DEFINE_GUID(IID_IUnknown, 0x00000000, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
DEFINE_GUID(IID_IClassFactory, 0x00000001, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
# endif
#endif


DISPCALC.CPP

/***
*dispcalc.cpp
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  This module implements the basic user interface and arithmetic
*  functionality of the IDispatch calculator.
*
*  The implementation of IDispatch it via aggregation with an
*  instance of the "standard" IDispatch implementation, which is
*  initialized with a DispTypeInfo constructed from an INTERFACEDATA
*  description.
*
*Implementation Notes:
*
*****************************************************************************/

#include "dispcalc.h"

CCalc FAR* g_pcalc = NULL;

unsigned long g_dwCCalcCF = 0;
unsigned long g_dwRegisterCCalc = 0;

#ifdef _MAC
extern Boolean g_fQuit;
#endif //_MAC

CCalc::CCalc() : m_arith(this)
{
    m_refs = 0;
#ifdef _MAC
    m_pdlg = nil;
#else
    m_hwnd = NULL;
#endif
    m_punkStdDisp = NULL;
}

/***
*CCalc *CCalc::Create(void)
*Purpose:
*  Create an instance of the IDispatch calculator, build a
*  TypeInfo that describes the exposed functionality and
*  aggregate with an instance of CStdDispatch that has been
*  initialized with this TypeInfo.
*
*Entry:
*  None
*
*Exit:
*  return value = CCalc*, NULL if the creation failed.
*
***********************************************************************/
CCalc FAR*
CCalc::Create()
{
    HRESULT hresult;
    CCalc FAR* pcalc;
    CArith FAR* parith;
    ITypeInfo FAR* ptinfo;
    IUnknown FAR* punkStdDisp;
extern INTERFACEDATA NEARDATA g_idataCCalc;

    if((pcalc = new FAR CCalc()) == NULL)
      return NULL;
    pcalc->AddRef();

    parith = &(pcalc->m_arith);

    // Build a TypeInfo for the functionality on this object that
    // is being exposing for external programmability.
    //
    hresult = CreateDispTypeInfo(
      &g_idataCCalc, LOCALE_SYSTEM_DEFAULT, &ptinfo);
    if(hresult != NOERROR)
      goto LError0;

    // Create and aggregate with an instance of the default
    // implementation of IDispatch that is initialized with our
    // TypeInfo.
    //
    hresult = CreateStdDispatch(
      pcalc,                    // controlling unknown
      parith,                   // instance to dispatch on
      ptinfo,                   // typeinfo describing the instance
      &punkStdDisp);

    ptinfo->Release();

    if(hresult != NOERROR)
      goto LError0;

    pcalc->m_punkStdDisp = punkStdDisp;

    return pcalc;

LError0:;
    pcalc->Release();

    return NULL;
}

//---------------------------------------------------------------------
//                        IUnknown methods
//---------------------------------------------------------------------

STDMETHODIMP
CCalc::QueryInterface(REFIID riid, void FAR* FAR* ppv)
{
    if(IsEqualIID(riid, IID_IUnknown)){
      *ppv = this;
    }else
    if(IsEqualIID(riid, IID_IDispatch)){
      return m_punkStdDisp->QueryInterface(riid, ppv);
    }else {
      *ppv = NULL;
      return E_NOINTERFACE;
    }

    AddRef();
    return NOERROR;
}

STDMETHODIMP_(unsigned long)
CCalc::AddRef()
{
    return ++m_refs;
}

STDMETHODIMP_(unsigned long)
CCalc::Release()
{
    if(--m_refs == 0){
      if(m_punkStdDisp != NULL)
m_punkStdDisp->Release();
#ifndef _MAC
      PostQuitMessage(0);
#endif
      delete this;
      return 0;
    }
    return m_refs;
}

//---------------------------------------------------------------------
//                       Arithmetic features
//---------------------------------------------------------------------

STDMETHODIMP_(void)
CArith::Clear()
{
    m_opnd = 0;
    m_accum = 0;
    m_op = OP_NONE;
    m_state = STATE_LOPND;
}

STDMETHODIMP_(void)
CArith::put_Accum(long l)
{
    m_accum = l;
}

STDMETHODIMP_(long)
CArith::get_Accum()
{
    return m_accum;
}

STDMETHODIMP_(void)
CArith::put_Opnd(long l)
{
    m_opnd = l;
}

STDMETHODIMP_(long)
CArith::get_Opnd()
{
    return m_opnd;
}

STDMETHODIMP_(void)
CArith::put_Op(short op)
{
    m_op = op;
}

STDMETHODIMP_(short)
CArith::get_Op()
{
    return m_op;
}

STDMETHODIMP_(short)
CArith::Eval()
{
    if(m_op == OP_NONE)
      return 0;

    switch(m_op){
    case OP_PLUS:
      m_accum += m_opnd;
      break;
    case OP_MINUS:
      m_accum -= m_opnd;
      break;
    case OP_MULT:
      m_accum *= m_opnd;
      break;
    case OP_DIV:
      m_accum = (m_opnd == 0) ? 0 : (m_accum / m_opnd);
      break;
    default:
      // ASSERT(UNREACHED);
      return 0;

    }

    m_state = STATE_EVAL;

    return 1;
}

//---------------------------------------------------------------------
//                       User Interface features
//---------------------------------------------------------------------

/***
*void CArith::Display()
*Purpose:
*  Display the contents of the register currently being edited.
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
STDMETHODIMP_(void)
CArith::Display()
{
    VARIANT var;

    VariantInit(&var);

    V_VT(&var) = VT_I4;
    V_I4(&var) = (m_state == STATE_ROPND) ? m_opnd : m_accum;
    VariantChangeType(&var, &var, 0, VT_BSTR);

#ifdef _MAC
    {
      Rect rcItem;
      Handle hItem;
      char str[255];
      short sItemKind;

      strcpy(str, V_BSTR(&var));
      GetDItem(m_pcalc->m_pdlg, IDC_DISPLAY, &sItemKind, &hItem, &rcItem);
      SetIText(hItem, c2pstr(str));
    }
#else
    SetDlgItemText(m_pcalc->m_hwnd, IDC_DISPLAY, STRING(V_BSTR(&var)));
#endif

    VariantClear(&var);
}

STDMETHODIMP_(short)
CArith::Button(BSTR bstrButton)
{
    int i, button;

static struct {
    OLECHAR ch;
    int idc;
} NEARDATA rgIdcOfCh[] = {
      { OLESTR('+'), IDC_PLUS   }
    , { OLESTR('-'), IDC_MINUS  }
    , { OLESTR('*'), IDC_MULT   }
    , { OLESTR('/'), IDC_DIV    }
    , { OLESTR('C'), IDC_CLEAR  }
    , { OLESTR('c'), IDC_CLEAR  }
    , { OLESTR('='), IDC_EQUALS }
    , { OLESTR('0'), IDC_ZERO   }
    , { OLESTR('1'), IDC_ONE    }
    , { OLESTR('2'), IDC_TWO    }
    , { OLESTR('3'), IDC_THREE  }
    , { OLESTR('4'), IDC_FOUR   }
    , { OLESTR('5'), IDC_FIVE   }
    , { OLESTR('6'), IDC_SIX    }
    , { OLESTR('7'), IDC_SEVEN  }
    , { OLESTR('8'), IDC_EIGHT  }
    , { OLESTR('9'), IDC_NINE   }
    , { (OLECHAR)-1 , -1         }
};

    // if the string is more that 1 character long, then we know its wrong.
    if(SysStringLen(bstrButton) > 1)
      return 0;

    // translate button string into control ID
    for(i = 0;; ++i){
      if(rgIdcOfCh[i].ch == -1)
return 0;
      if(rgIdcOfCh[i].ch == bstrButton[0]){
button = rgIdcOfCh[i].idc;
break;
      }
    }

    return ButtonPush(button);
}

// the following method is internal, and not exposed for programmability
int
CArith::ButtonPush(int button)
{
    if(button >= IDC_ZERO && button <= IDC_NINE){

      long lVal = button - IDC_ZERO;

      switch(m_state){
      case STATE_EVAL:
m_accum = lVal;
m_state = STATE_LOPND;
break;
      case STATE_OP:
m_opnd = lVal;
m_state = STATE_ROPND;
break;
      case STATE_LOPND:
m_accum = (m_accum * 10) + lVal;
break;
      case STATE_ROPND:
m_opnd  = (m_opnd * 10) + lVal;
break;
      }

    }else if(button >= IDC_PLUS && button <= IDC_DIV){

      if(m_state == STATE_ROPND)
Eval();

      m_opnd  = m_accum;
      m_state = STATE_OP;
      m_op    = button - IDC_PLUS + OP_PLUS;

    }else if(button == IDC_EQUALS){

      if(m_state > STATE_LOPND)
Eval();

    }else if (button == IDC_CLEAR){

      Clear();

    }else{

      return 0; // unknown button

    }

    // Flash the button

#ifdef _MAC
    {
      Rect rcItem;
      long lDummy;
      Handle hItem;
      short sItemKind;

      GetDItem(m_pcalc->m_pdlg, button, &sItemKind, &hItem, &rcItem);
      HiliteControl((ControlHandle)hItem, 1);
      Delay(6, &lDummy);
      HiliteControl((ControlHandle)hItem, 0);
    }
#else
    SendMessage(m_pcalc->m_hwnd, BM_SETSTATE, 1, 0L);
    SendMessage(m_pcalc->m_hwnd, BM_SETSTATE, 0, 0L);
#endif

    // Update the calculator display

    Display();

    return 1;
}

/***
*void CArith::Quit()
*Purpose:
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
STDMETHODIMP_(void)
CArith::Quit()
{
#ifndef _MAC
    PostQuitMessage(0);
#else
    g_fQuit = 1;
#endif
}

//---------------------------------------------------------------------
//                      The CCalc Class Factory
//---------------------------------------------------------------------

IClassFactory FAR*
CCalcCF::Create()
{
    return new FAR CCalcCF();
}

STDMETHODIMP
CCalcCF::QueryInterface(REFIID riid, void FAR* FAR* ppv)
{
    if(IsEqualIID(riid, IID_IUnknown) ||
       IsEqualIID(riid, IID_IClassFactory)){
      AddRef();
      *ppv = this;
      return NOERROR;
    }

    *ppv = NULL;
    return E_NOINTERFACE;
}

STDMETHODIMP_(unsigned long)
CCalcCF::AddRef()
{
    return ++m_refs;
}

STDMETHODIMP_(unsigned long)
CCalcCF::Release()
{
    if(--m_refs == 0){
      delete this;
      return 0;
    }
    return m_refs;
}

STDMETHODIMP
CCalcCF::CreateInstance(
    IUnknown FAR* punkOuter,
    REFIID riid,
    void FAR* FAR* ppv)
{
    if(punkOuter != NULL)
      return CLASS_E_NOAGGREGATION;
    return g_pcalc->QueryInterface(riid, ppv);
}

STDMETHODIMP
#ifdef _MAC
CCalcCF::LockServer(unsigned long fLock)
#else
CCalcCF::LockServer(BOOL fLock)
#endif
{
    UNUSED(fLock);
    return NOERROR;
}

//---------------------------------------------------------------------
//                        Ole Init/Uninit
//---------------------------------------------------------------------

HRESULT
UninitOle()
{
    if(g_dwRegisterCCalc != 0)
      RevokeActiveObject(g_dwRegisterCCalc, NULL);

    if(g_dwCCalcCF != 0)
      CoRevokeClassObject(g_dwCCalcCF);

    if(g_pcalc != NULL)
      g_pcalc->Release();

    OleUninitialize();

    return NOERROR;
}

#ifdef _MAC
struct regentry{
    char *szKey;
    char *szValue;
} g_rgregentry[] = {

      { "CLSID\\{00020467-0000-0000-C000-000000000046}",
"OLE Automation DispCalc 1.0 Application" }

    , { "CLSID\\{00020467-0000-0000-C000-000000000046}\\LocalServer",
"DCLC" }

    , { "CLSID\\{00020467-0000-0000-C000-000000000046}\\ProgID",
"Dispcalc.Application" }

    , { "CLSID\\{00020467-0000-0000-C000-000000000046}\\InprocHandler",
"OLE2:Def$DefFSet" }

    , { "DCLC", "{00020467-0000-0000-C000-000000000046}" }

    , { "Dispcalc.Application\\CLSID",
"{00020467-0000-0000-C000-000000000046}" }

};

HRESULT
EnsureRegistration()
{
    HKEY hkey;

    if(RegOpenKey(HKEY_CLASSES_ROOT, "DCLC", &hkey) == NOERROR){
      RegCloseKey(hkey);
      return NOERROR;
    }

    for(int i = 0; i < DIM(g_rgregentry); ++i){
      if(RegSetValue(HKEY_CLASSES_ROOT, g_rgregentry[i].szKey, REG_SZ, g_rgregentry[i].szValue, 0) != ERROR_SUCCESS)
return E_FAIL;
    }

    return NOERROR;
}
#endif

/***
*HRESULT InitOle(void)
*Purpose:
*  Initialize Ole, and register our class factories.
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
HRESULT
InitOle()
{
    HRESULT hresult;
    IClassFactory FAR* pcf;

    if(FAILED(hresult = OleInitialize(NULL)))
      goto LError0;

#ifdef _MAC
    if(FAILED(hresult = EnsureRegistration()))
      goto LError0;
#endif

    // create the single global instance of CCalc
    if((g_pcalc = CCalc::Create()) == NULL){
      hresult = E_OUTOFMEMORY;
      goto LError0;
    }

    if((pcf = CCalcCF::Create()) == NULL)
      goto LError1;

    hresult = CoRegisterClassObject(
      CLSID_CCalc,
      pcf,
      CLSCTX_LOCAL_SERVER,
      REGCLS_MULTIPLEUSE,
      &g_dwCCalcCF);
    if(FAILED(hresult))
      goto LError2;

    hresult = RegisterActiveObject(
      g_pcalc, CLSID_CCalc, NULL, &g_dwRegisterCCalc);
    if(FAILED(hresult))
      goto LError2;

    pcf->Release();

    return NOERROR;

LError2:;
    pcf->Release();

LError1:;
    UninitOle();

LError0:;
    return hresult;
}


DISPCALC.H

/***
*dispcalc.h
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  UNDONE
*
*
*Implementation Notes:
*
*****************************************************************************/

#include "hostenv.h"
#include "resource.h"
#include "clsid.h"

#ifndef CLASS
# ifdef __TURBOC__
#  define CLASS class huge
# else
#  define CLASS class FAR
# endif
#endif

#pragma warning(disable:4355)

#ifdef _MAC
# define NEARDATA
#else
# ifdef WIN32
#  define NEARDATA
# else
#  define NEARDATA __near
# endif
#endif

#ifdef _MAC
# define UNUSED(X) ((void)(void*)&(X))
#else
# define UNUSED(X) (X)
#endif

#define DIM(X) (sizeof(X)/sizeof(X[0]))

// forward decl
CLASS CCalc;

// Introduced "calculator" interface
//
// This class implementes core arithmetic functionality
// (such as it is) *and* is the interface that will be
// exposed via IDispatch for external programmability.

#if defined(_MAC) && !(!defined(applec) || defined(__SC__) || defined(_MSC_VER))

// Mac Note: The default implementation of IDispatch places
// a couple of requirements on the layout of an instance that
// in can invoke on.
//
//   1. It assumes that the vtable pointer is at offset 0
//      from the beginning of the instance. This appears
//      to always be the case if the class derives from
//      an interface, and hence the games I play below
//      creating a stub _CArith interface containing a
//      single pure virtual function. The only reason for
//      this is to ensure that the vtable ptr is at offset
//      0 when this class is a member of CCalc. If your
//      class derives from an Ole interface, then you are ok.
//
//   2. It assumes that the vtable is a simple array of
//      function pointers, with index zero reserved. In
//      order to ensure this, the class must derive from
//      SingleObject. Note that IUnknown derives from SingleObject,
//      so if your class derives from any Ole interface, then
//      you are also ok - otherwise you need to explicitly
//      derive from SingleObject as I have done below.
//
//  (The above comments apply to MPW C++ v3.3)
//

interface _CArith : public SingleObject
{
    STDMETHOD_(void,  put_Accum)(long l) PURE;
};
CLASS CArith : public _CArith
#else
CLASS CArith
#endif
{
public:
#if defined(_MAC)
    BEGIN_INTERFACE
#endif //_MAC

    STDMETHOD_(void,  put_Accum)(long l);
    STDMETHOD_(long,  get_Accum)(void);
    STDMETHOD_(void,  put_Opnd)(long l);
    STDMETHOD_(long,  get_Opnd)(void);
    STDMETHOD_(void,  put_Op)(short op);
    STDMETHOD_(short, get_Op)(void);
    STDMETHOD_(short, Eval)(void);
    STDMETHOD_(void,  Clear)(void);
    STDMETHOD_(void,  Display)(void);
    STDMETHOD_(void,  Quit)(void);
    STDMETHOD_(short, Button)(BSTR button);

    // the following method is internal, and not exposed for programmability
    int ButtonPush(int button);

    CArith(CCalc FAR* pcalc){
      m_pcalc = pcalc;
      Clear();
    }
    enum states { STATE_LOPND, STATE_OP, STATE_ROPND, STATE_EVAL };

private:
    CCalc FAR*  m_pcalc;

    shortm_op;
    longm_opnd;
    longm_accum;
    enum states m_state;
};

CLASS CCalc : public IUnknown {
public:
    friend CArith;

    static CCalc FAR* Create();

    STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppv);
    STDMETHOD_(unsigned long, AddRef)(void);
    STDMETHOD_(unsigned long, Release)(void);

    CCalc();

#ifdef _MAC
    DialogPtr m_pdlg;
#else
    HWND m_hwnd;
#endif
    CArith m_arith;

private:
    unsigned long m_refs;
    IUnknown FAR* m_punkStdDisp;
};

enum operators {
    OP_NONE = 0,
    OP_PLUS,
    OP_MINUS,
    OP_MULT,
    OP_DIV
};

// the following enum defines method indices used by the
// default IDispatch implementation - DispInvoke().
//
// Note: these must match the order of the preceeding declarations
//
enum IMETH_CARITH {
    IMETH_PUTACCUM = 0,
    IMETH_GETACCUM,
    IMETH_PUTOPERAND,
    IMETH_GETOPERAND,
    IMETH_PUTOPERATOR,
    IMETH_GETOPERATOR,
    IMETH_EVAL,
    IMETH_CLEAR,
    IMETH_DISPLAY,
    IMETH_QUIT,
    IMETH_BUTTON,

    // Define the "property" indices. these are defined to be
    // the first index in a set/get property method pair. These
    // definitions are used to build the METHODDATA that drives
    // our implementation of IDispatch. see cdisp.cpp.
    //
    IMETH_ACCUM    = IMETH_PUTACCUM,
    IMETH_OPERAND  = IMETH_PUTOPERAND,
    IMETH_OPERATOR = IMETH_PUTOPERATOR
};

// the following enum defines the IDs used by IDispatch
//
// Note: these values do *not* depend on order of declaration,
// but are sensitive to the kind of the method - ie, if a get/set
// method pair implements a property, then they need to share
// an ID.
//
// Note: by assigning "accum" the ID 'DISPID_VALUE', we are
// choosing to expose it as the default "value" property.
//
enum IDMEMBER_CARITH {
    IDMEMBER_ACCUM = DISPID_VALUE,// the default property
    IDMEMBER_OPERAND,
    IDMEMBER_OPERATOR,
    IDMEMBER_EVAL,
    IDMEMBER_CLEAR,
    IDMEMBER_DISPLAY,
    IDMEMBER_QUIT,
    IDMEMBER_BUTTON
};

// the CCalc Class Factory
//
CLASS CCalcCF : public IClassFactory {
public:
    static IClassFactory FAR* Create();

    STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppv);
    STDMETHOD_(unsigned long, AddRef)(void);
    STDMETHOD_(unsigned long, Release)(void);

    STDMETHOD(CreateInstance)(
      IUnknown FAR* punkOuter, REFIID riid, void FAR* FAR* ppv);
#ifdef _MAC
    STDMETHOD(LockServer)(unsigned long fLock);
#else
    STDMETHOD(LockServer)(BOOL fLock);
#endif

    CCalcCF() { m_refs = 1; }

private:
    unsigned long m_refs;
};

extern HRESULT InitOle(void);
extern HRESULT UninitOle(void);

extern CCalc FAR* g_pcalc;


HOSTENV.H

/***
*hostenv.h
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  Generic host specific includes.
*
*Implementation Notes:
*
*****************************************************************************/

#if defined(_MAC)

#if defined(_PPCMAC)
#pragma data_seg("_FAR_DATA")
#pragma data_seg( )
#define MAXLONG 0x7fffffff
#define EventHandlerProcPtr AEEventHandlerUPP
#else //_PPCMAC
#define  GetMenuItemText(mApple,menuItem,daName)  GetItem(mApple,menuItem,daName)
#endif //_PPCMAC

#endif //_MAC

#if defined(_MAC)

#if defined(_MSC_VER)

# include <values.h>
# include <types.h>
# include <string.h>
# include <quickdra.h>
# include <fonts.h>
# include <events.h>
# include <resource.h>
# include <menus.h>
# include <textedit.h>
# include <dialogs.h>
# include <desk.h>
# include <toolutil.h>
# include <memory.h>
# include <files.h>
# include <osutils.h>
# include <osevents.h>
# include <diskinit.h>
# include <packages.h>
# include <traps.h>
# include <AppleEve.h>

#else //_MSC_VER

# include <values.h>
# include <types.h>
# include <strings.h>
# include <quickdraw.h>
# include <fonts.h>
# include <events.h>
# include <resources.h>
# include <windows.h>
# include <menus.h>
# include <textedit.h>
# include <dialogs.h>
# include <desk.h>
# include <toolutils.h>
# include <memory.h>
# include <files.h>
# include <osutils.h>
# include <osevents.h>
# include <diskinit.h>
# include <packages.h>
# include <traps.h>
# include <AppleEvents.h>

#endif //_MSC_VER

# include <ole2.h>
# include <olenls.h>
# include <dispatch.h>

# define TCHARchar
# define TSTR(str)str
# define STRING(str)        (str)
# define WIDESTRING(str)        (str)

#elif defined(WIN32)

# include <windows.h>

# if defined(UNICODE)
    #define TCHARWCHAR
    #define TSTR(str)L##str
    #define STRING(str)        (str)
    #define WIDESTRING(str)(str)
# else
    #define TCHARchar
    #define TSTR(str)str
    #define STRING(str)        AnsiString(str)
    #define WIDESTRING(str)WideString(str)
    extern "C" char FAR* AnsiString(OLECHAR FAR* strIn);
    extern "C" OLECHAR FAR* WideString(char FAR* strIn);
# endif

#else /* WIN16 */

# include <windows.h>
# include <ole2.h>
# include <olenls.h>
# include <dispatch.h>

# define TCHARchar
# define TSTR(str)str
# define STRING(str)        (str)
# define WIDESTRING(str)        (str)
#endif


IDATA.CPP

/***
*idata.cpp
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  This file contains the INTERFACEDATA definitions for the methods
*  and properties on the CCalc object that are exposed for external
*  programmability via IDispatch.
*
*
*Implementation Notes:
*
*****************************************************************************/

#include "dispcalc.h"

//---------------------------------------------------------------------
//                   INTERFACEDATA definitions
//---------------------------------------------------------------------

/* The INTERFACEDATA table describes the methods and properties that
 * are being exposed for external programmability via IDispatch.
 * This table is used to construct a CDispTypeInfo for this interface,
 * and that TypeInfo is in turn used to initialize and drive the
 * default implementation of IDispatch.
 */

// PDATA1() declares the PARAMDATA for a methods that takes a single param
//
#define PDATA1(NAME, TYPE) \
    static PARAMDATA NEARDATA rgpdata ## NAME = {OLESTR(#NAME), TYPE}

// MDATA() declares a single METHODDATA entry
//
#define MDATA(NAME, PDATA, IDMEMBER, IMETH, CARGS, KIND, TYPE) \
    { OLESTR(#NAME), PDATA, IDMEMBER, IMETH, CC_CDECL, CARGS, KIND, TYPE }

// The following macro defines the METHODDATA entries for a
// property Put/Get method pair.
//
// Note: this macro *assumes* that the Put/Get pair are adjacent
// in the vtable, and that the Put method comes first.
//
#define PROPERTY(NAME, IMETH, ID, TYPE) \
    MDATA(NAME, &rgpdata ## NAME, ID, IMETH, 1,DISPATCH_PROPERTYPUT,VT_EMPTY), \
    MDATA(NAME, NULL, ID, IMETH+1, 0, DISPATCH_PROPERTYGET, TYPE)

// The following macro is used to define a METHODDATA entry for
// a method that takes zero parameters.
//
#define METHOD0(NAME, IMETH, ID, TYPE) \
    MDATA(NAME, NULL, ID, IMETH, 0, DISPATCH_METHOD, TYPE)

// and for one param
#define METHOD1(NAME, IMETH, ID, TYPE) \
    MDATA(NAME, &rgpdata ## NAME, ID, IMETH, 1, DISPATCH_METHOD, TYPE)

PDATA1(VALUE,  VT_I4);
PDATA1(ACCUM,  VT_I4);
PDATA1(OPND,   VT_I4);
PDATA1(OP,     VT_I2);
PDATA1(BUTTON, VT_BSTR);

static METHODDATA NEARDATA rgmdataCCalc[] =
{
      PROPERTY(VALUE,   IMETH_ACCUM,    IDMEMBER_ACCUM,    VT_I4)
    , PROPERTY(ACCUM,   IMETH_ACCUM,    IDMEMBER_ACCUM,    VT_I4)
    , PROPERTY(OPND,    IMETH_OPERAND,  IDMEMBER_OPERAND,  VT_I4)
    , PROPERTY(OP,      IMETH_OPERATOR, IDMEMBER_OPERATOR, VT_I2)
    ,  METHOD0(EVAL,    IMETH_EVAL,     IDMEMBER_EVAL,     VT_BOOL)
    ,  METHOD0(DCCLEAR, IMETH_CLEAR,    IDMEMBER_CLEAR,    VT_EMPTY)
    ,  METHOD0(DISPLAY, IMETH_DISPLAY,  IDMEMBER_DISPLAY,  VT_EMPTY)
    ,  METHOD0(QUIT,    IMETH_QUIT,     IDMEMBER_QUIT,     VT_EMPTY)
    ,  METHOD1(BUTTON,  IMETH_BUTTON,   IDMEMBER_BUTTON,   VT_BOOL)
};

INTERFACEDATA NEARDATA g_idataCCalc =
{
    rgmdataCCalc, DIM(rgmdataCCalc)
};


CLSID.C

/***
*clsid.c
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  This file allocates and initializes the CLSIDs.
*
*****************************************************************************/

#ifdef _PPCMAC
#pragma data_seg ("_FAR_DATA")
#pragma data_seg ( )
#endif //_PPCMAC

#ifdef _MAC
# include <Types.h>
#ifdef _MSC_VER
# include <Processe.h>
# include <AppleEve.h>
#else //_MSC_VER
# include <Processes.h>
# include <AppleEvents.h>
#endif //_MSC_VER
#else
# include <windows.h>
#endif

#ifndef WIN32
#include <compobj.h>
#endif //!WIN32

// this redefines the DEFINE_GUID() macro to do allocation.
//
#include <initguid.h>

#ifndef INITGUID
# define INITGUID
#endif

// due to the previous header, including this causes the DEFINE_GUID
// definitions in the following header(s) to actually allocate data.
//
#include "clsid.h"


RESOURCE.H

/***
*resource.h
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*
*Implementation Notes:
*
*****************************************************************************/

#ifdef _MAC

#define kMinSize500/* minimum size (in K) */
#define kPrefSize500/* preferred size (in K) */

#define kMinHeap21 * 1024
#define kMinSpace8 * 1024

#definerMenuBar128/* menu bar */
#definerAboutAlert128/* about alert */
#definerUserAlert129/* error alert */

#definerCalc130

#definemApple128/* Apple menu */
#defineiAbout1

#definemFile129/* File menu */
#define iClose4
#defineiQuit12

#definemEdit130/* Edit menu */
#defineiUndo1
#defineiCut3
#defineiCopy4
#defineiPaste5
#defineiClear6

#endif

// Note: there is code that depends on all of the digits being contiguous
// and in the following order.

// Mac Note: On the mac these IDs correspond to the control indices
// in the DITL array.

#define IDC_ZERO     1
#define IDC_ONE      2
#define IDC_TWO      3
#define IDC_THREE    4
#define IDC_FOUR     5
#define IDC_FIVE     6
#define IDC_SIX      7
#define IDC_SEVEN    8
#define IDC_EIGHT    9
#define IDC_NINE     10

// Note: there is code that depends on the operators being contiguous
// and in the following order.
//
#define IDC_PLUS     11
#define IDC_MINUS    12
#define IDC_MULT     13
#define IDC_DIV      14

#define IDC_CLEAR    15
#define IDC_EQUALS   16

#define IDC_DISPLAY  17


WINMAIN.CPP

/***
*main.cpp
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  This module is the main entry point of the sample IDispatch
*  calculator, dispcalc.exe
*
*  This program is intended to demonstrate an implementation of
*  the IDispatch interface.
*
*Implementation Notes:
*
*****************************************************************************/

#include "dispcalc.h"

TCHAR g_szAppName[] = TSTR("DispCalc");

BOOL InitApplication(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);

extern "C" {
long FAR PASCAL WndProc(HWND, UINT, WPARAM, LPARAM);
int PASCAL WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
}

extern "C" int PASCAL
WinMain(
    HINSTANCE hinst,
    HINSTANCE hinstPrev,
    LPSTR lpCmdLine,
    int nCmdShow)
{
    MSG msg;

    if(!hinstPrev)
      if(!InitApplication(hinst))
return FALSE;

    if(InitOle() != NOERROR)
      return FALSE;

    if(!InitInstance(hinst, nCmdShow)){
      UninitOle();
      return FALSE;
    }

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

    UninitOle();

    return msg.wParam;
}

BOOL
InitApplication(HINSTANCE hinst)
{
    WNDCLASS  wc;

    wc.style= CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc= WndProc;
    wc.cbClsExtra= 0;
    wc.cbWndExtra= DLGWINDOWEXTRA;
    wc.hInstance= hinst;
    wc.hIcon= LoadIcon(hinst, g_szAppName);
    wc.hCursor= LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground= (HBRUSH)(COLOR_APPWORKSPACE+1);
    wc.lpszMenuName= NULL;
    wc.lpszClassName= g_szAppName;

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

    return TRUE;
}

BOOL
InitInstance(HINSTANCE hinst, int nCmdShow)
{
    g_pcalc->m_hwnd = CreateDialog(hinst, g_szAppName, 0, NULL);

    ShowWindow(g_pcalc->m_hwnd, nCmdShow);

    g_pcalc->m_arith.Display();

    return TRUE;
}

extern "C" long FAR PASCAL
WndProc(
    HWND hwnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam)
{
    switch(message){
    case WM_COMMAND:
      switch(wParam){
      case IDC_ZERO:
      case IDC_ONE:
      case IDC_TWO:
      case IDC_THREE:
      case IDC_FOUR:
      case IDC_FIVE:
      case IDC_SIX:
      case IDC_SEVEN:
      case IDC_EIGHT:
      case IDC_NINE:
      case IDC_PLUS:
      case IDC_MINUS:
      case IDC_MULT:
      case IDC_DIV:
      case IDC_CLEAR:
      case IDC_EQUALS:
g_pcalc->m_arith.ButtonPush(wParam);
return 0;
      }
      break;

    case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

#if defined(WIN32)

extern "C" char FAR*
ConvertStrWtoA(OLECHAR FAR* strIn, char FAR* buf, UINT size)
{
  int badConversion = FALSE;

  WideCharToMultiByte(CP_ACP, NULL,
              strIn, -1,
      buf, size,
      NULL, &badConversion);
  return buf;
}

extern "C" char FAR*
AnsiString(OLECHAR FAR* strIn)
{
  static char buf[256];

  return (ConvertStrWtoA(strIn, buf, 256));
}

#endif


README.TXT

OLE Automation Sample

The DispDemo sample is an OLE Automation controller. DispDemo drives the
two polygon servers which are also provided as samples.

The interesting part of DispDemo is in the remote polygon class. The remote
polygon class was constructed so that the IDispatch members could be
accessed conveniently from C++. The remote polygon class does this by
transforming the C++ calls into IDispatch calls. The code that calls these
routines has no idea that it is going through IDispatch.


CLSID.H

/***
*clsid.h
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  This file defines the CLSIDs referenced by the IDispatch demo app.
*
*Implementation Notes:
*
*****************************************************************************/

DEFINE_GUID(CLSID_CPoly,  0x00020462, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
DEFINE_GUID(CLSID_CPoly2, 0x00020464, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);

#ifdef INITGUID
# ifdef _MAC
DEFINE_GUID(GUID_NULL, 0L, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
/* The PPC MW gives a warning if these are redefined */
#  ifndef _MW_BUILD
DEFINE_GUID(IID_IDispatch, 0x00020400, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
DEFINE_GUID(IID_IEnumVARIANT, 0x00020404, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
#  endif
DEFINE_GUID(IID_IUnknown, 0x00000000, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
DEFINE_GUID(IID_IClassFactory, 0x00000001, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
# endif
#endif


CREMPOLY.CPP

/***
*crempoly.cpp
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  This file contains the implementation of CRemPoly, the remote polygon
*  class. This class presents a standard C++ vtable interface to the
*  rest of the application, and hides the details of talking to the
*  actual remote CPoly class exposed by the SPoly server. Each of
*  the introduced methods is simply a cover for an IDispatch invocation
*  of the actual method on the remote object.
*
*Implementation Notes:
*
*****************************************************************************/

#include "dispdemo.h"
#include "crempoly.h"

extern int g_fTrace;

// method names on the CPoly class.
//
OLECHAR FAR* CRemPoly::m_rgszMethods[] = {
    OLESTR("draw"),
    OLESTR("dump"),
    OLESTR("reset"),
    OLESTR("addpoint"),
    OLESTR("enumpoints"),
    OLESTR("getxorigin"),
    OLESTR("setxorigin"),
    OLESTR("getyorigin"),
    OLESTR("setyorigin"),
    OLESTR("getwidth"),
    OLESTR("setwidth"),
    OLESTR("get_red"),
    OLESTR("set_red"),
    OLESTR("get_green"),
    OLESTR("set_green"),
    OLESTR("get_blue"),
    OLESTR("set_blue")
};

#ifdef _MAC
# define IfMac(X) (X)
# define IfWin(X)
#else
# define IfMac(X)
# define IfWin(X) (X)
#endif

CRemPoly::CRemPoly()
{
    m_refs = 0;
    m_pdisp = (IDispatch FAR*)NULL;
}

// A useful pre-initialized DISPATCHPARAMS, used on all the methods that
// take 0 arguments.
//
DISPPARAMS NEAR g_dispparamsNoArgs = {NULL, NULL, 0, 0};

/***
*HRESULT CRemPoly::Create(clsid, CRemPoly**)
*
*Purpose:
*  This function creates an instance of the CRemPoly class, connects
*  it to the IDispatch interface of the remote CPoly class, and learns
*  the DISPIDs for the members (that we know about) exposed by that
*  class.
*
*Entry:
*  clsid = The CLSID of the CPoly we are to create. (taking this as a
*    param is a bit weird, but allows us to connect to several remote
*    versions.
*
*Exit:
*  return value = HRESULT
*
*  *pprempoly = pointer to the newly created CRemPoly, if successfyl.
*
***********************************************************************/
HRESULT
CRemPoly::Create(CLSID clsid, CRemPoly FAR* FAR* pprempoly)
{
    int i;
    HRESULT hresult;
    IUnknown FAR* punk;
    CRemPoly FAR* prempoly;

    prempoly = new FAR CRemPoly();
    if(prempoly == (CRemPoly FAR*)NULL){
      hresult = E_OUTOFMEMORY;
      goto LError;
    }
    prempoly->AddRef();

    // create an instance of the remote CPoly class.
    //
    IfMac(DbPrintf("CoCreateInstance(CLSID_CPoly)"));
    hresult = CoCreateInstance(
      clsid, NULL, CLSCTX_LOCAL_SERVER, IID_IUnknown, (void FAR* FAR*)&punk);
    if(hresult != NOERROR){
      IfMac(DbPrintf("CoCreateInstance() = 0x%x", hresult));
      IfWin(MessageBox(NULL, TSTR("Unable to create polygon object"),
               NULL, MB_OK));
      goto LFreeCRemPoly;
    }

    // were going to talk to this remote instance via IDispatch.
    //
    IfMac(DbPrintf("QueryInterface(IID_IDispatch)"));
    hresult = punk->QueryInterface(
      IID_IDispatch, (void FAR* FAR*)&prempoly->m_pdisp);
    if(hresult != NOERROR){
      IfMac(DbPrintf("QueryInterface(IID_IDispatch) = 0x%x", hresult));
      IfWin(MessageBox(NULL, TSTR("Unable to QueryInterface to IDispatch"),
               NULL, MB_OK));
      goto LReleaseUnk;
    }

    // We learn *all* the member IDs up front. A more sophisticated
    // implementation might defer learning about the IDs for a given
    // method until the first time the method is invoked, thereby
    // amortizing the creation costs.
    //
    IfMac(DbPrintf("GetIDsOfNames()"));
    for(i = 0; i < IMETH_CREMPOLY_MAX; ++i){
      hresult = prempoly->m_pdisp->GetIDsOfNames(
IID_NULL,
        &prempoly->m_rgszMethods[i],
1, LOCALE_USER_DEFAULT,
&prempoly->m_rgdispid[i]);
      if(hresult != NOERROR){
IfMac(DbPrintf("GetIDsOfNames() = 0x%x", hresult));
IfWin(MessageBox(NULL, TSTR("Unrecognized member name"),
         NULL, MB_OK));
goto LReleaseUnk;
      }
    }

    punk->Release();

    *pprempoly = prempoly;

    IfMac(DbPrintf("Object created."));

    return NOERROR;

LReleaseUnk:;
    punk->Release();

LFreeCRemPoly:;
    prempoly->Release();

LError:;
    return hresult;
}

//---------------------------------------------------------------------
//                     IUnknown methods
//---------------------------------------------------------------------

/***
*HRESULT CRemPoly::QueryInterface(REFIID, void**)
*
*Purpose:
*  Standard Ole2 implementation of QueryInterface. This class
*  supports the IUnknown interface, and introduces a number of
*  nonvirtual members.
*
*Entry:
*  riid = reference to the requested interface id
*
*Exit:
*  return value = HRESULT
*  *ppv = pointer to the requested interface, if successful.
*
***********************************************************************/
STDMETHODIMP
CRemPoly::QueryInterface(REFIID riid, void FAR* FAR* ppv)
{
    if(IsEqualIID(riid, IID_IUnknown)){
      *ppv = this;
      AddRef();
      return NOERROR;
    }
    *ppv = NULL;
    return E_NOINTERFACE;
}

/***
*unsigned long CRemPoly::AddRef(void)
*
*Purpose:
*  Add a reference to the instance.
*
*Entry:
*  None
*
*Exit:
*  return value = unsigned long. The resulting reference count.
*
***********************************************************************/
STDMETHODIMP_(unsigned long)
CRemPoly::AddRef(void)
{
    return ++m_refs;
}

/***
*unsigned long CRemPoly::Release(void)
*
*Purpose:
*  Release a reference to the instance. If the reference count goes
*  to zero, delete the instance.
*
*Entry:
*  None
*
*Exit:
*  return value = unsigned long. The resulting reference count.
*
***********************************************************************/
STDMETHODIMP_(unsigned long)
CRemPoly::Release(void)
{
    if(--m_refs == 0){
      if(m_pdisp != (IDispatch FAR*)NULL){
        m_pdisp->Release();
      }
      delete this;
      return 0;
    }
    return m_refs;
}

//---------------------------------------------------------------------
//                    Introduced methods
//---------------------------------------------------------------------

/*
 * Each of these methods is simply a cover for an IDispatch Invocation
 * of the actual method on the remote CPoly class. This allows CRemPoly
 * to present an interface that looks and acts just like the CPoly
 * object, even though the actual work is being done in another process.
 *
 */

/***
*HRESULT CRemPoly::Draw(void)
*
*Purpose:
*  Invoke the Draw method on the remote CPoly instance.
*
*Entry:
*  None
*
*Exit:
*  return value = HRESULT
*
***********************************************************************/
HRESULT
CRemPoly::Draw()
{
    return m_pdisp->Invoke(
      m_rgdispid[IMETH_CREMPOLY_DRAW],
      IID_NULL,
      LOCALE_USER_DEFAULT,
      DISPATCH_METHOD,
      &g_dispparamsNoArgs, NULL, NULL, NULL);
}

/***
*HRESULT CRemPoly::Dump(void)
*
*Purpose:
*  Invoke the Dump() method on the remote CPoly instance. This method
*  dumps the contained CPoints and writes the properties of the remote
*  CPoly instance to the debug window.
*
*Entry:
*  None
*
*Exit:
*  return value = HRESULT
*
***********************************************************************/
HRESULT
CRemPoly::Dump()
{
    return m_pdisp->Invoke(
      m_rgdispid[IMETH_CREMPOLY_DUMP],
      IID_NULL,
      LOCALE_USER_DEFAULT,
      DISPATCH_METHOD,
      &g_dispparamsNoArgs, NULL, NULL, NULL);
}

/***
*HRESULT CRemPoly::Reset(void)
*
*Purpose:
*  Invoke the Reset() method on the remote CPoly instance. The Reset()
*  method causes the remote CPoly to release all contained CPoints.
*
*Entry:
*  None
*
*Exit:
*  return value = HRESULT
*
***********************************************************************/
HRESULT
CRemPoly::Reset()
{
    return m_pdisp->Invoke(
      m_rgdispid[IMETH_CREMPOLY_RESET],
      IID_NULL,
      LOCALE_USER_DEFAULT,
      DISPATCH_METHOD,
      &g_dispparamsNoArgs, NULL, NULL, NULL);
}

/***
*HRESULT CRemPoly::AddPoint(short, short)
*
*Purpose:
*  Invoke the AddPoint method in the remote CPoly object to add a
*  new point with the given coordinates to this instance.
*
*Entry:
*  x,y = the x and y coordinates of the new point.
*
*Exit:
*  return value = HRESULT
*
***********************************************************************/
HRESULT
CRemPoly::AddPoint(short x, short y)
{
    HRESULT hresult;
    VARIANTARG varg[2];
    DISPPARAMS dispparams;

    V_VT(&varg[0]) = VT_I2;
    V_I2(&varg[0]) = y;

    V_VT(&varg[1]) = VT_I2;
    V_I2(&varg[1]) = x;

    dispparams.cArgs = 2;
    dispparams.rgvarg = varg;
    dispparams.cNamedArgs = 0;
    dispparams.rgdispidNamedArgs = NULL;

    hresult = m_pdisp->Invoke(
      m_rgdispid[IMETH_CREMPOLY_ADDPOINT],
      IID_NULL,
      LOCALE_USER_DEFAULT,
      DISPATCH_METHOD,
      &dispparams, NULL, NULL, NULL);

    return hresult;
}

/***
*HRESULT CRemPoly::EnumPoints(IEnumVARIANT**)
*Purpose:
*  Inoke the EnumPoints() method in the remote object to
*  get a enumerator for the points contained in the current poly.
*
*Entry:
*  None
*
*Exit:
*  return value = HRESULT
*
*  *ppenum = pointer to the point enumerator
*
***********************************************************************/
HRESULT
CRemPoly::EnumPoints(IEnumVARIANT FAR* FAR* ppenum)
{
    HRESULT hresult;
    IEnumVARIANT FAR* penum;
    VARIANT varResult, FAR* pvarResult;

    pvarResult = &varResult;
    VariantInit(pvarResult);
    hresult = m_pdisp->Invoke(
      m_rgdispid[IMETH_CREMPOLY_ENUMPOINTS],
      IID_NULL,
      LOCALE_USER_DEFAULT,
      DISPATCH_METHOD,
      &g_dispparamsNoArgs, pvarResult, NULL, NULL);

    if(hresult != NOERROR)
      return hresult;

    if(V_VT(pvarResult) != VT_UNKNOWN)
      return E_FAIL;

    hresult = V_UNKNOWN(pvarResult)->QueryInterface(
      IID_IEnumVARIANT, (void FAR* FAR*)&penum);

    if(hresult == NOERROR)
      *ppenum = penum;

    VariantClear(pvarResult);

    return NOERROR;
}

/***
*HRESULT CRemPoly::GetXOrigin(short*)
*
*Purpose:
*  Invoke the GetXOrigin() method on the remote object to extract
*  the current value of the XOrigin property.
*
*Entry:
*  None
*
*Exit:
*  return value = HRESULT
*
*  *pxorg = the current X origin of the polygon.
*
***********************************************************************/
HRESULT
CRemPoly::GetXOrigin(short FAR* pxorg)
{
    HRESULT hresult;
    VARIANT varResult;

    VariantInit(&varResult);
    hresult = m_pdisp->Invoke(
      m_rgdispid[IMETH_CREMPOLY_GETXORIGIN],
      IID_NULL,
      LOCALE_USER_DEFAULT,
      DISPATCH_METHOD,
      &g_dispparamsNoArgs, &varResult, NULL, NULL);

    if(hresult != NOERROR)
      return hresult;

    *pxorg = V_I2(&varResult);
    VariantClear(&varResult);

    return NOERROR;
}

/***
*HRESULT CRemPoly::SetXOrigin(short)
*
*Purpose:
*  Invoke the SetXOrigin method on the remote object to set the
*  XOrigin property of the polygon to the given value.
*
*Entry:
*  xorg = the new X origin
*
*Exit:
*  return value = HRESULT
*
***********************************************************************/
HRESULT
CRemPoly::SetXOrigin(short xorg)
{
    VARIANTARG varg;
    DISPPARAMS dispparams;

    V_VT(&varg) = VT_I2;
    V_I2(&varg) = xorg;

    dispparams.cArgs = 1;
    dispparams.cNamedArgs = 0;
    dispparams.rgvarg = &varg;

    return m_pdisp->Invoke(
      m_rgdispid[IMETH_CREMPOLY_SETXORIGIN],
      IID_NULL,
      LOCALE_USER_DEFAULT,
      DISPATCH_METHOD,
      &dispparams, NULL, NULL, NULL);
}

/***
*HRESULT CRemPoly::GetYOrigin(short*)
*
*Purpose:
*  Invoke the GetYOrigin() method on the remote object to extract
*  the current value of the YOrigin property.
*
*Entry:
*  None
*
*Exit:
*  return value = HRESULT
*
*  *pyorg = the current Y origin of the polygon
*
***********************************************************************/
HRESULT
CRemPoly::GetYOrigin(short FAR* pyorg)
{
    HRESULT hresult;
    VARIANT varResult;

    VariantInit(&varResult);
    hresult = m_pdisp->Invoke(
      m_rgdispid[IMETH_CREMPOLY_GETYORIGIN],
      IID_NULL,
      LOCALE_USER_DEFAULT,
      DISPATCH_METHOD,
      &g_dispparamsNoArgs, &varResult, NULL, NULL);

    if(hresult != NOERROR)
      return hresult;

    *pyorg = V_I2(&varResult);
    VariantClear(&varResult);

    return hresult;
}

/***
*HRESULT CRemPoly::SetYOrigin(short)
*
*Purpose:
*  Invoke the SetYOrigin method on the remote object to set the
*  YOrigin property of the polygon to the given value.
*
*Entry:
*  yorg = the new Y origin
*
*Exit:
*  return value = HRESULT
*
***********************************************************************/
HRESULT
CRemPoly::SetYOrigin(short yorg)
{
    VARIANTARG varg;
    DISPPARAMS dispparams;

    V_VT(&varg) = VT_I2;
    V_I2(&varg) = yorg;

    dispparams.cArgs = 1;
    dispparams.cNamedArgs = 0;
    dispparams.rgvarg = &varg;

    return m_pdisp->Invoke(
      m_rgdispid[IMETH_CREMPOLY_SETYORIGIN],
      IID_NULL,
      LOCALE_USER_DEFAULT,
      DISPATCH_METHOD,
      &dispparams, NULL, NULL, NULL);
}

/***
*HRESULT CRemPoly::GetWidth(short*)
*
*Purpose:
*  Invoke the GetWidth() method on the remote object to extract
*  the current value of the line width property.
*
*Entry:
*  None
*
*Exit:
*  return value = HRESULT
*
*  *pwidth = short, the current line width of the polygon
*
***********************************************************************/
HRESULT
CRemPoly::GetWidth(short FAR* pwidth)
{
    HRESULT hresult;
    VARIANT varResult;

    VariantInit(&varResult);
    hresult = m_pdisp->Invoke(
      m_rgdispid[IMETH_CREMPOLY_GETWIDTH],
      IID_NULL,
      LOCALE_USER_DEFAULT,
      DISPATCH_METHOD,
      &g_dispparamsNoArgs, &varResult, NULL, NULL);

    if(hresult != NOERROR)
      return hresult;

    *pwidth = V_I2(&varResult);
    VariantClear(&varResult);
    return NOERROR;
}

/***
*HRESULT CRemPoly::SetWidth(short)
*
*Purpose:
*  Invoke the SetWidth method on the remote object to set the
*  line width property of the polygon to the given value.
*
*Entry:
*  width = the new value for the line width property.
*
*Exit:
*  return value = HRESULT
*
***********************************************************************/
HRESULT
CRemPoly::SetWidth(short width)
{
    VARIANTARG varg;
    DISPPARAMS dispparams;

    V_VT(&varg) = VT_I2;
    V_I2(&varg) = width;

    dispparams.cArgs = 1;
    dispparams.cNamedArgs = 0;
    dispparams.rgvarg = &varg;

    return m_pdisp->Invoke(
      m_rgdispid[IMETH_CREMPOLY_SETWIDTH],
      IID_NULL,
      LOCALE_USER_DEFAULT,
      DISPATCH_METHOD,
      &dispparams, NULL, NULL, NULL);
}

HRESULT CRemPoly::get_red(short FAR* psRed)
{
    return get_i2(m_rgdispid[IMETH_CREMPOLY_GETRED], psRed);
}

HRESULT CRemPoly::set_red(short sRed)
{
    return set_i2(m_rgdispid[IMETH_CREMPOLY_SETRED], sRed);
}

HRESULT CRemPoly::get_green(short FAR* psGreen)
{
    return get_i2(m_rgdispid[IMETH_CREMPOLY_GETGREEN], psGreen);
}

HRESULT CRemPoly::set_green(short sGreen)
{
    return set_i2(m_rgdispid[IMETH_CREMPOLY_SETGREEN], sGreen);
}

HRESULT CRemPoly::get_blue(short FAR* psBlue)
{
    return get_i2(m_rgdispid[IMETH_CREMPOLY_GETBLUE], psBlue);
}

HRESULT CRemPoly::set_blue(short sBlue)
{
    return set_i2(m_rgdispid[IMETH_CREMPOLY_SETBLUE], sBlue);
}

HRESULT
CRemPoly::get_i2(DISPID dispid, short FAR* ps)
{
    HRESULT hresult;
    VARIANT varResult;

    VariantInit(&varResult);

    hresult = m_pdisp->Invoke(
      dispid,
      IID_NULL,
      LOCALE_SYSTEM_DEFAULT,
      DISPATCH_METHOD,
      &g_dispparamsNoArgs,
      &varResult, NULL, NULL);

    if(hresult != NOERROR)
      return hresult;

    hresult = VariantChangeType(&varResult, &varResult, 0, VT_I2);
    if(hresult != NOERROR){
      VariantClear(&varResult);
      return hresult;
    }

    *ps = V_I2(&varResult);
    VariantClear(&varResult);
    return NOERROR;
}

HRESULT
CRemPoly::set_i2(DISPID dispid, short s)
{
    VARIANTARG varg;
    DISPPARAMS dispparams;

    V_VT(&varg) = VT_I2;
    V_I2(&varg) = s;

    dispparams.cArgs = 1;
    dispparams.cNamedArgs = 0;
    dispparams.rgvarg = &varg;

    return m_pdisp->Invoke(
      dispid,
      IID_NULL,
      LOCALE_SYSTEM_DEFAULT,
      DISPATCH_METHOD,
      &dispparams, NULL, NULL, NULL);
}

/***
*void DoPoly(CLSID)
*
*Purpose:
*  This function simply exercises our CRemPoly class by creating an
*  instance and invoking a number of its methods.
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
STDAPI
DoPoly(CLSID clsid)
{
    HRESULT hr;
    int numpoly, i, j;

static struct {
    short x;
    short y;
} rgptPoly[] = {
      { 25,   0}
    , { 75,   0}
    , {100,  25}
    , {100,  75}
    , { 75, 100}
    , { 25, 100}
    , {  0,  75}
    , {  0,  25}
};

static struct {
    short red;
    short green;
    short blue;
} rgrgbColors[] = {
#ifdef _MAC
      {     0,      0,      0}
    , {     0,      0, 0x7fff}
    , {     0, 0x7fff,      0}
    , {0x7fff,      0,      0}
    , {0x7fff,      0, 0x7fff}
    , {0x7fff, 0x7fff,      0}
    , {0x7fff, 0x7fff, 0x7fff}
#else
      {  0,   0,   0}
    , {  0,   0, 127}
    , {  0, 127,   0}
    , {127,   0,   0}
    , {127,   0, 127}
    , {127, 127,   0}
    , {127, 127, 127}
#endif
};

    CRemPoly FAR* rgprempoly[DIM(rgrgbColors)];

    numpoly = DIM(rgprempoly);

    // init
    for(i = 0; i < numpoly; ++i)
      rgprempoly[i] = (CRemPoly FAR*)NULL;

    for(i = 0; i < numpoly; ++i){
      hr = CRemPoly::Create(clsid, &rgprempoly[i]);
      if(hr != NOERROR)
        goto LError0;
      IfMac(DbPrintf("CRemPoly::Create()"));

      for(j = 0; j < DIM(rgptPoly); ++j){
        short x = rgptPoly[j].x;
short y = rgptPoly[j].y;
        IfMac(DbPrintf("CRemPoly::AddPoint(%d,%d)", x, y));
        hr = rgprempoly[i]->AddPoint(x, y);
ASSERT(hr == NOERROR);
      }

      for(j = 0; j < DIM(rgrgbColors); ++j){
        hr = rgprempoly[i]->SetWidth(i + j);
ASSERT(hr == NOERROR);
IfMac(DbPrintf("CRemPoly::SetWidth()"));

        hr = rgprempoly[i]->set_red(rgrgbColors[j].red);
ASSERT(hr == NOERROR);
IfMac(DbPrintf("CRemPoly::set_red()"));

        hr = rgprempoly[i]->set_green(rgrgbColors[j].green);
ASSERT(hr == NOERROR);
IfMac(DbPrintf("CRemPoly::set_green()"));

        hr = rgprempoly[i]->set_blue(rgrgbColors[j].blue);
ASSERT(hr == NOERROR);
IfMac(DbPrintf("CRemPoly::set_blue()"));

        hr = rgprempoly[i]->SetXOrigin((2*i) + j << 4);
ASSERT(hr == NOERROR);
IfMac(DbPrintf("CRemPoly::SetXOrigin()"));

        hr = rgprempoly[i]->SetYOrigin(j << 4);
ASSERT(hr == NOERROR);
IfMac(DbPrintf("CRemPoly::SetYOrigin()"));

        hr = rgprempoly[i]->Draw();
ASSERT(hr == NOERROR);
IfMac(DbPrintf("CRemPoly::Draw()"));
      }
    }

    hr = NOERROR;

LError0:;
    for(i = 0; i < numpoly; ++i){
      if(rgprempoly[i] != (CRemPoly FAR*)NULL){
rgprempoly[i]->Release();
      }
    }

    return hr;
}


CREMPOLY.H

/***
*crempoly.h
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  This file defines the CRemPoly remote polygon object.
*
*Implementation Notes:
*
*****************************************************************************/

class CRemPoly : public IUnknown {
public:
    static HRESULT Create(CLSID clsid, CRemPoly FAR* FAR*);

    // IUnknown methods
    //
    STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppvObj);
    STDMETHOD_(unsigned long, AddRef)(void);
    STDMETHOD_(unsigned long, Release)(void);

    // Introduced methods
    //
    HRESULT Draw(void);
    HRESULT Dump(void);
    HRESULT Reset(void);
    HRESULT AddPoint(short x, short y);
    HRESULT EnumPoints(IEnumVARIANT FAR* FAR* ppenum);
    HRESULT GetXOrigin(short FAR* pxorg);
    HRESULT SetXOrigin(short xorg);
    HRESULT GetYOrigin(short FAR* pyorg);
    HRESULT SetYOrigin(short yorg);
    HRESULT GetWidth(short FAR* pwidth);
    HRESULT SetWidth(short width);

    HRESULT get_red(short FAR* psRed);
    HRESULT set_red(short sRed);
    HRESULT get_green(short FAR* psGreen);
    HRESULT set_green(short sGreen);
    HRESULT get_blue(short FAR* psBlue);
    HRESULT set_blue(short sBlue);

private:
    CRemPoly();

    HRESULT get_i2(DISPID dispid, short FAR* ps);
    HRESULT set_i2(DISPID dispid, short s);

    unsigned long m_refs;
    IDispatch FAR* m_pdisp;

    // NOTE: this enumeration exists simply to allow us to symbolicly
    // index the member name and id arrays (m_rgid and m_rgszMethods).
    // This doesn't (necessarrily) have any connection to the vtable
    // indices, it *only* needs to correspond correctly to the m_rgid
    // and m_rgszMethods arrays.
    //
    enum CREMPOLY_METHODS {
IMETH_CREMPOLY_DRAW = 0,
IMETH_CREMPOLY_DUMP,
IMETH_CREMPOLY_RESET,
IMETH_CREMPOLY_ADDPOINT,
IMETH_CREMPOLY_ENUMPOINTS,
IMETH_CREMPOLY_GETXORIGIN,
IMETH_CREMPOLY_SETXORIGIN,
IMETH_CREMPOLY_GETYORIGIN,
IMETH_CREMPOLY_SETYORIGIN,
IMETH_CREMPOLY_GETWIDTH,
IMETH_CREMPOLY_SETWIDTH,
IMETH_CREMPOLY_GETRED,
IMETH_CREMPOLY_SETRED,
IMETH_CREMPOLY_GETGREEN,
IMETH_CREMPOLY_SETGREEN,
IMETH_CREMPOLY_GETBLUE,
IMETH_CREMPOLY_SETBLUE,
IMETH_CREMPOLY_MAX
    };

    // member IDs - these are used by IDispatch::Invoke to identify the
    // method or property on the remote object we accessing.
    //
    DISPID m_rgdispid[IMETH_CREMPOLY_MAX];

    // member names - these are used to learn the member IDs when we
    // connect to the remote object.
    //
    static OLECHAR FAR* m_rgszMethods[IMETH_CREMPOLY_MAX];
};


DISPDEMO.H

/***
*dispdemo.h
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  IDispatch Demo App definitions.
*
*Implementation Notes:
*
*****************************************************************************/

#include "hostenv.h"
#include "resource.h"
#include "clsid.h"

#ifdef _MAC
# define UNUSED(X) ((void)(void*)&(X))
#else
# define UNUSED(X) (X)
#endif

#ifndef NEAR
# define NEAR
#endif

#define DIM(X) (sizeof(X) / sizeof(X[0]))

#define ASSERT(X) Assert(X, __FILE__, __LINE__)

#ifdef __cplusplus
extern "C" {
#endif

STDAPI InitOle(void);
STDAPI UninitOle(void);

STDAPI DoPoly(CLSID);

void   Assert(int, char FAR*, int);

#ifdef _MAC
void DbPrintf(char*, ...);
#endif

#ifdef __cplusplus
}
#endif


MISC.CPP

/***
*misc.cpp
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*
*Implementation Notes:
*
*****************************************************************************/

// Use ANSI strings for assertions
#ifdef UNICODE
#  undef UNICODE
#endif

#include "dispdemo.h"

#include <stdio.h>
#include <stdarg.h>

int g_fTrace = 0;

STDAPI
InitOle(void)
{
    HRESULT hresult;

    if((hresult = OleInitialize(NULL)) != NOERROR)
      return hresult;

// UNDONE: temporary to try to get symbols for the DLL
    VARIANT var;
    VariantInit(&var);

    return NOERROR;
}

STDAPI
UninitOle()
{
    OleUninitialize();

    return NOERROR;
}

extern "C" void
Assert(int fCond, char FAR* file, int line)
{
    char buf[128];

    if(fCond)
      return;

    sprintf(buf, "Assertion failed: %s(%d)\n", file, line);

#ifdef _MAC
    DebugStr(c2pstr(buf));
#else
    OutputDebugString(buf);
    DebugBreak();
#endif
}

#ifdef _MAC

extern "C" {
extern WindowPtr g_pwndDebug;

void
DbPrintf(char *sz, ...)
{
    va_list args;
    WindowPtr pwndSaved;
static char rgchOut[256];

    if(g_pwndDebug == nil)
      return;

    GetPort(&pwndSaved);
    SetPort(g_pwndDebug);

    va_start(args, sz);
    vsprintf(rgchOut, sz, args);
    rgchOut[79] = '\0';

    EraseRect(&g_pwndDebug->portRect);
    TextFont(systemFont);
    MoveTo(10,20);
    DrawString(c2pstr(rgchOut));

    SetPort(pwndSaved);
}

}

#endif


CLSID.C

/***
*clsid.c
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  This file allocates and initializes the CLSIDs.
*
*****************************************************************************/

#ifdef _PPCMAC
#pragma data_seg ("_FAR_DATA")
#pragma data_seg ( )
#endif

#ifdef _MAC
# include <Types.h>
#ifdef _MSC_VER
# include <Processe.h>
# include <AppleEve.h>
#else //_MSC_VER
# include <Processes.h>
# include <AppleEvents.h>
#endif //_MSC_VER
#else
# include <windows.h>
#endif

#ifndef WIN32
#include <compobj.h>
#endif //!WIN32

// this redefines the DEFINE_GUID() macro to do allocation.
//
#include <initguid.h>

#ifndef INITGUID
# define INITGUID
#endif

// due to the previous header, including this causes the DEFINE_GUID
// definitions in the following header(s) to actually allocate data.
//
#include "clsid.h"


RESOURCE.H

/***
*resource.h
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*
*Implementation Notes:
*
*****************************************************************************/

#ifdef _MAC

#define kMinSize500/* minimum size (in K) */
#define kPrefSize500/* preferred size (in K) */

#definerMenuBar128/* menu bar */

#definerAboutAlert128/* about alert */

#definerUserAlert129/* error alert */

#definerWindow128/* application's window */
#definerDebugWindow129/* debug window */

#definemApple128/* Apple menu */
#defineiAbout1

#definemFile129/* File menu */
#defineiNew1
#defineiClose4
#defineiQuit12

#definemEdit130/* Edit menu */
#defineiUndo1
#defineiCut3
#defineiCopy4
#defineiPaste5
#defineiClear6

#define mSpoly131
#define iSpoly1

#define mSpoly2132
#define iSpoly21

#define kMinHeap21 * 1024
#define kMinSpace8 * 1024

#else /* WIN16 || WIN32 */

# define IDM_ABOUT1
# define IDM_POLY2
# define IDM_POLY23
# define IDM_TRACE4
# define IDM_MAX5

#endif


WINMAIN.CPP

/***
*dispdemo.c - IDespatch demo/sample client application.
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  This module is the main entry point for the sample IDispatch client,
*  dispdemo.exe.
*
*  This program is intended to demonstrate a client invoking methods
*  and referencing properties on a remote object via the IDispatch
*  interface.
*
*  The bulk of the sample can be found in the file crempoly.cpp, which
*  implements CRemPoly, the remote polygon class.
*
*Implementation Notes:
*
*****************************************************************************/

#include "dispdemo.h"

extern BOOL g_fTrace;

BOOL InitApplication(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);
BOOL FAR PASCAL About(HWND, unsigned, WORD, LONG);

extern "C" {
int PASCAL WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
LRESULT FAR PASCAL MainWndProc(HWND, UINT, WPARAM, LPARAM);
}

HINSTANCE g_hInst;

TCHAR g_szDispDemoWClass[] = TSTR("DispDemoWClass");

extern "C" int PASCAL
WinMain(
    HINSTANCE hinst,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow)
{
    MSG msg;

    (lpCmdLine); // UNUSED

    if(!hPrevInstance)
      if(!InitApplication(hinst))
return FALSE;

    if(InitOle() != NOERROR)
      return FALSE;

    if(!InitInstance(hinst, nCmdShow))
      return FALSE;

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

    UninitOle();

    return msg.wParam;
}

BOOL
InitApplication(HINSTANCE hinst)
{
    WNDCLASS  wc;

    wc.style= NULL;
    wc.lpfnWndProc= MainWndProc;
    wc.cbClsExtra= 0;
    wc.cbWndExtra= 0;
    wc.hInstance= hinst;
    wc.hIcon= LoadIcon(hinst, TSTR("DISPDEMO"));
    wc.hCursor= LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground= (HBRUSH)GetStockObject(WHITE_BRUSH);
    wc.lpszMenuName= TSTR("DispDemoMenu");
    wc.lpszClassName= g_szDispDemoWClass;
    if(!RegisterClass(&wc))
      return FALSE;

    return TRUE;
}

#ifdef WIN32
#define szAppTitle TSTR("IDispatch Demo App (32-bit)")
#else //WIN32
#define szAppTitle TSTR("IDispatch Demo App")
#endif //WIN32

BOOL
InitInstance(HINSTANCE hinst, int nCmdShow)
{
    HWND hWnd;

    g_hInst = hinst;

    hWnd = CreateWindow(
      g_szDispDemoWClass,
      szAppTitle,
      WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX,
      CW_USEDEFAULT, CW_USEDEFAULT, 300, 100,
      NULL, NULL, hinst, NULL);

    if(!hWnd)
      return FALSE;

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    return TRUE;
}

BOOL FAR PASCAL
About(HWND hDlg, unsigned message, WORD wParam, LONG lParam)
{
    switch(message){
    case WM_INITDIALOG:
      return TRUE;

    case WM_COMMAND:
      switch(wParam){
      case IDOK:
      case IDCANCEL:
EndDialog(hDlg, TRUE);
return TRUE;
      }
      break;
    }
    return FALSE;
}

extern "C" LRESULT FAR PASCAL
MainWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    HMENU hmenu;
static DLGPROC pfnAbout;

    switch(message){
    case WM_COMMAND:
      switch(wParam){
      case IDM_TRACE:
/* enable/disable trace */
g_fTrace = (g_fTrace) ? FALSE : TRUE;
hmenu = GetMenu(hwnd);
CheckMenuItem(hmenu, IDM_TRACE, g_fTrace ? MF_CHECKED : MF_UNCHECKED);
return 0;

      case IDM_POLY:
DoPoly(CLSID_CPoly);
return 0;

      case IDM_POLY2:
DoPoly(CLSID_CPoly2);
return 0;

      case IDM_ABOUT:
pfnAbout = (DLGPROC)MakeProcInstance((FARPROC)About, g_hInst);
DialogBox(g_hInst, TSTR("AboutBox"), hwnd, pfnAbout);
FreeProcInstance(pfnAbout);
return 0;
      }
      break;

    case WM_CLOSE:
      DestroyWindow(hwnd);
      return 0;

    case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}


README.TXT

OLE Automation Sample

SUMMARY
=======

The DspCalc2 sample is a simple accumulator-based calculator. Its user
interface consists of buttons for the numbers (0-9), the operators
(+, -, *, /), and some other necessary buttons (C for Clear, = for
evaluation). Its programmability interface consists of one object, which is
described below.

The ProgID for DspCalc2's only object is "DspCalc2.Application". An instance
of this object can be created by executing the following lines of code in
Visual Basic or DispTest:

    Sub Foo
        Dim MyCalculator as Object
        Set MyCalculator = CreateObject("DspCalc2.Application")
        . . .
    End Sub

MORE INFORMATION
================

Program Structure
-----------------

DspCalc2 uses a type library and CreateStdDispatch to implement the
IDispatch interface.

Properties for the Object
-------------------------

Name     Type     Description

Value    VT_I4    Same as the value for the accumulator.
Accum    VT_I4    The value that is in the accumulator of the calculator.
Opnd     VT_I4    The operand. This is the number currently being entered.
Op       VT_I2    The operator that is currently being used. This is an
                  enumeration:
                      const OP_NONE = 0
                      const OP_PLUS = 1
                      const OP_MINUS = 2
                      const OP_MULT = 3

Methods Defined on the Object
-----------------------------

Name                               Description

Eval as Boolean                    If there is an operator, apply it to the
                                   accumulator and the operand, placing the
                                   result in the accumulator. The return
                                   value indicates success or failure.
Clear                              Resets the calculator. This sets Op to
                                   OP_NONE, and both Accum and Opnd to 0.
Display                            Updates the display of the calculator.
                                   (Other operations do not do this.)
Quit                               Close the calculator.
Button (b as string) as Boolean    Press the indicated button and return
                                   success or failure. Valid string values
                                   are:
                                       +, -, *, +
                                       0-9
                                       c, C
                                       =

                                   Note that you may also pass the numbers
                                   0-9 and these will be converted to
                                   strings automatically.

Shortcomings of this Sample
---------------------------

Property and method names should not be abbreviated. For example, the "Opnd"
property should be the "Operand" property.

Because the object is the application object, it should have Name and
Version properties, which are read-only.


CLSID.H

/***
*clsid.h
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  This file defines the CLSIDs
*
*Implementation Notes:
*
*****************************************************************************/

DEFINE_GUID(CLSID_CCalc2,    0x00020469, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
DEFINE_GUID(LIBID_DspCalc2,  0x00020470, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
DEFINE_GUID(IID_ICalculator, 0x00020441, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
DEFINE_GUID(IID_DCalculator, 0x00020442, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);

#ifdef INITGUID
# ifdef _MAC
DEFINE_GUID(GUID_NULL, 0L, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
#  ifndef _MW_BUILD
/* The PPC Metrowerks gives warning if this is redefined */
DEFINE_GUID(IID_IDispatch, 0x00020400, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
DEFINE_GUID(IID_IEnumVARIANT, 0x00020404, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
#  endif
DEFINE_GUID(IID_IUnknown, 0x00000000, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
DEFINE_GUID(IID_IClassFactory, 0x00000001, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
# endif
#endif


DSPCALC2.CPP

/***
*dspcalc2.cpp
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1993 - 1998 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.
*
*Purpose:
*  This module implements the basic user interface and arithmetic
*  functionality of the IDispatch calculator.
*
*  The implementation of IDispatch is via aggregation with an
*  instance of the "standard" IDispatch implementation, which is
*  initialized with a TypeInfo loaded from the TypeLib that was
*  constructed from the ODL description of the calculator.
*
*Implementation Notes:
*
*****************************************************************************/

#include "dspcalc2.h"

CCalc FAR* g_pcalc = NULL;

unsigned long g_dwCCalcCF = 0;
unsigned long g_dwRegisterCCalc = 0;

#ifdef _MAC
extern Boolean g_fQuit;
#endif //_MAC

/***
*CCalc *CCalc::Create(void)
*Purpose:
*  Create an instance of the IDispatch calculator, load the
*  TypeInfo that describes the exposed functionality and
*  aggregate with an instance of CStdDispatch that has been
*  initialized with this TypeInfo.
*
*Entry:
*  None
*
*Exit:
*  return value = CCalc*, NULL if the creation failed.
*
***********************************************************************/
CCalc FAR*
CCalc::Create()
{
    HRESULT hresult;
    CCalc FAR* pcalc;
    ITypeLib FAR* ptlib;
    ITypeInfo FAR* ptinfo;
    IUnknown FAR* punkStdDisp;

    ptlib = NULL;
    ptinfo = NULL;

    if((pcalc = new FAR CCalc()) == NULL)
      return NULL;
    pcalc->AddRef();

    // first try to load the type library from the information in the registry
    if((hresult = LoadRegTypeLib(LIBID_DspCalc2, 1, 0, 0x0409, &ptlib)) != NOERROR){

      #define TLB_NAME OLESTR("dspcalc2.tlb")

      // if it wasn't registered, try to load it from the path/current directory
      // if this succeeds, it will have registered the type library for us
      // for the next time.
      if((hresult = LoadTypeLib(TLB_NAME, &ptlib)) != NOERROR){
#ifndef _MAC
MessageBox(NULL, TSTR("error loading TypeLib"),
   TSTR("dspcalc2"), MB_OK);
#endif
goto LError0;
      }

    }

    if((hresult = ptlib->GetTypeInfoOfGuid(IID_ICalculator, &ptinfo)) != NOERROR){
#ifndef _MAC
      MessageBox(NULL, TSTR("error accessing TypeInfo"),
 TSTR("dspcalc2"), MB_OK);
#endif
      goto LError0;
    }

    // Create and aggregate with an instance of the default
    // implementation of IDispatch that is initialized with our
    // TypeInfo.
    //
    hresult = CreateStdDispatch(
      pcalc,                            // controlling unknown
      &(pcalc->m_arith),                // vtable* to dispatch on
      ptinfo,
      &punkStdDisp);

    if(hresult != NOERROR)
      goto LError0;

    pcalc->m_punkStdDisp = punkStdDisp;

    ptinfo->Release();
    ptlib->Release();

    return pcalc;

LError0:;
    pcalc->Release();
    if(ptinfo != NULL)
      ptinfo->Release();
    if(ptlib != NULL)
      ptlib->Release();
    return NULL;
}

//---------------------------------------------------------------------
//                        IUnknown methods
//---------------------------------------------------------------------

STDMETHODIMP
CCalc::QueryInterface(REFIID riid, void FAR* FAR* ppv)
{
    if(IsEqualIID(riid, IID_IUnknown)){
      *ppv = this;
    }else
    if(IsEqualIID(riid, IID_IDispatch) ||
       IsEqualIID(riid, IID_DCalculator)){
      return m_punkStdDisp->QueryInterface(IID_IDispatch, ppv);
    }else
    if(IsEqualIID(riid, IID_ICalculator)){
      *ppv = &m_arith;
    }else {
      *ppv = NULL;
      return E_NOINTERFACE;
    }

    AddRef();
    return NOERROR;
}

STDMETHODIMP_(ULONG)
CCalc::AddRef()
{
    return ++m_refs;
}

STDMETHODIMP_(ULONG)
CCalc::Release()
{
    if(--m_refs == 0){
      if(m_punkStdDisp != NULL)
m_punkStdDisp->Release();
#ifndef _MAC
      PostQuitMessage(0);
#endif
      delete this;
      return 0;
    }
    return m_refs;
}

STDMETHODIMP
CArith::QueryInterface(REFIID riid, void FAR* FAR* ppv)
{
    return m_pcalc->QueryInterface(riid, ppv);
}

STDMETHODIMP_(ULONG)
CArith::AddRef()
{
    return m_pcalc->AddRef();
}

STDMETHODIMP_(ULONG)
CArith::Release()
{
    return m_pcalc->Release();
}

//---------------------------------------------------------------------
//                       Arithmetic features
//---------------------------------------------------------------------

STDMETHODIMP_(void)
CArith::DCClear()
{
    m_opnd = 0;
    m_accum = 0;
    m_op = OP_NONE;
    m_state = STATE_LOPND;
}

STDMETHODIMP_(void)
CArith::put_Accum(long l)
{
    m_accum = l;
}

STDMETHODIMP_(long)
CArith::get_Accum()
{
    return m_accum;
}

STDMETHODIMP_(void)
CArith::put_Opnd(long l)
{
    m_opnd = l;
}

STDMETHODIMP_(long)
CArith::get_Opnd()
{
    return m_opnd;
}

STDMETHODIMP_(void)
CArith::put_Op(OPERATORS op)
{
    m_op = op;
}

STDMETHODIMP_(OPERATORS)
CArith::get_Op()
{
    return m_op;
}

STDMETHODIMP_(VARIANT_BOOL)
CArith::Eval()
{
    if(m_op == OP_NONE)
      return FALSE;

    switch(m_op){
    case OP_PLUS:
      m_accum += m_opnd;
      break;
    case OP_MINUS:
      m_accum -= m_opnd;
      break;
    case OP_MULT:
      m_accum *= m_opnd;
      break;
    case OP_DIV:
      m_accum = (m_opnd == 0) ? 0 : (m_accum / m_opnd);
      break;
    default:
      // ASSERT(UNREACHED);
      return FALSE;

    }

    m_state = STATE_EVAL;

    return VARIANT_TRUE;
}

//---------------------------------------------------------------------
//                       User Interface features
//---------------------------------------------------------------------

/***
*void CArith::Display()
*Purpose:
*  Display the contents of the register currently being edited.
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
STDMETHODIMP_(void)
CArith::Display()
{
    VARIANT var;

    VariantInit(&var);
    V_VT(&var) = VT_I4;
    V_I4(&var) = (m_state == STATE_ROPND) ? m_opnd : m_accum;
    VariantChangeType(&var, &var, 0, VT_BSTR);

    #ifdef _MAC
    {
      Rect rcItem;
      Handle hItem;
      char str[255];
      short sItemKind;

      strcpy(str, V_BSTR(&var));
      GetDItem(m_pcalc->m_pdlg, IDC_DISPLAY, &sItemKind, &hItem, &rcItem);
      SetIText(hItem, c2pstr(str));
    }
#else
    SetDlgItemText(m_pcalc->m_hwnd, IDC_DISPLAY, STRING(V_BSTR(&var)));
#endif

    VariantClear(&var);
}

STDMETHODIMP_(VARIANT_BOOL)
CArith::Button(SAFEARRAY FAR * psa)
{
    int i, button;

static struct {
    OLECHAR ch;
    int idc;
} NEAR rgIdcOfCh[] = {
      { OLESTR('+'), IDC_PLUS   }
    , { OLESTR('-'), IDC_MINUS  }
    , { OLESTR('*'), IDC_MULT   }
    , { OLESTR('/'), IDC_DIV    }
    , { OLESTR('C'), IDC_CLEAR  }
    , { OLESTR('c'), IDC_CLEAR  }
    , { OLESTR('='), IDC_EQUALS }
    , { OLESTR('0'), IDC_ZERO   }
    , { OLESTR('1'), IDC_ONE    }
    , { OLESTR('2'), IDC_TWO    }
    , { OLESTR('3'), IDC_THREE  }
    , { OLESTR('4'), IDC_FOUR   }
    , { OLESTR('5'), IDC_FIVE   }
    , { OLESTR('6'), IDC_SIX    }
    , { OLESTR('7'), IDC_SEVEN  }
    , { OLESTR('8'), IDC_EIGHT  }
    , { OLESTR('9'), IDC_NINE   }
    , { (OLECHAR)-1 , -1         }
};
    LONG saIndex, saUbound;
    VARIANT varButton;

    // Since this is a vararg function, we should be given a 1-dimensional
    // array with 0 for the lower bound. The array could be uninitialized
    // if 0 args were passed to us -- this call will give an error in this case.
    if (SafeArrayGetUBound(psa, 1, &saUbound) != NOERROR)
      return FALSE;     // most likely 0 args were passed to us

    for (saIndex = 0; saIndex <= saUbound; saIndex++) {

      // get next parameter
      if (SafeArrayGetElement(psa, &saIndex, &varButton) != NOERROR)
return FALSE;

      // convert it to a string in-place
      if (VariantChangeType(&varButton, &varButton, 0, VT_BSTR) != NOERROR)
goto Error;

      // if the string is more that 1 character long, then we know its wrong.
      if(SysStringLen(varButton.bstrVal) > 1)
goto Error;

      // translate button string into control ID
      for(i = 0;; ++i){
if(rgIdcOfCh[i].ch == -1)
  goto Error;
if(rgIdcOfCh[i].ch == varButton.bstrVal[0]){
  button = rgIdcOfCh[i].idc;
  break;
}
      }

      VariantClear(&varButton);         // done with the parameter

      if (!ButtonPush(button))
return FALSE;

    } // for

    return VARIANT_TRUE;        // success

Error:
    VariantClear(&varButton);
    return FALSE;       // failure
}

// the following method is internal, and not exposed for programmability
BOOL
CArith::ButtonPush(int button)
{
    if(button >= IDC_ZERO && button <= IDC_NINE){

      long lVal = button - IDC_ZERO;

      switch(m_state){
      case STATE_EVAL:
m_accum = lVal;
m_state = STATE_LOPND;
break;
      case STATE_OP:
m_opnd = lVal;
m_state = STATE_ROPND;
break;
      case STATE_LOPND:
m_accum = (m_accum * 10) + lVal;
break;
      case STATE_ROPND:
m_opnd  = (m_opnd * 10) + lVal;
break;
      }

    }else if(button >= IDC_PLUS && button <= IDC_DIV){

      if(m_state == STATE_ROPND)
Eval();

      m_opnd  = m_accum;
      m_state = STATE_OP;
      m_op    = (OPERATORS)(button - IDC_PLUS + OP_PLUS);

    }else if(button == IDC_EQUALS){

      if(m_state > STATE_LOPND)
Eval();

    }else if (button == IDC_CLEAR){

      DCClear();

    } else {

      return 0; // unknown button

    }

    // Flash the button

#ifdef _MAC
    {
      Rect rcItem;
      long lDummy;
      Handle hItem;
      short sItemKind;

      GetDItem(m_pcalc->m_pdlg, button, &sItemKind, &hItem, &rcItem);
      HiliteControl((ControlHandle)hItem, 1);
      Delay(6, &lDummy);
      HiliteControl((ControlHandle)hItem, 0);
    }
#else
    SendMessage(m_pcalc->m_hwnd, BM_SETSTATE, 1, 0L);
    SendMessage(m_pcalc->m_hwnd, BM_SETSTATE, 0, 0L);
#endif

    // Update the calculator display

    Display();

    return TRUE;
}

/***
*void CArith::Quit()
*Purpose:
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
STDMETHODIMP_(void)
CArith::Quit()
{
#ifndef _MAC
    PostQuitMessage(0);
#else
    g_fQuit = TRUE;
#endif
}

//---------------------------------------------------------------------
//                      The CCalc Class Factory
//---------------------------------------------------------------------

IClassFactory FAR*
CCalcCF::Create()
{
    return new FAR CCalcCF();
}

STDMETHODIMP
CCalcCF::QueryInterface(REFIID riid, void FAR* FAR* ppv)
{
    if(IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IClassFactory)){
      AddRef();
      *ppv = this;
      return NOERROR;
    }
    *ppv = NULL;
    return E_NOINTERFACE;
}

STDMETHODIMP_(ULONG)
CCalcCF::AddRef()
{
    return ++m_refs;
}

STDMETHODIMP_(ULONG)
CCalcCF::Release()
{
    if(--m_refs == 0){
      delete this;
      return 0;
    }
    return m_refs;
}

STDMETHODIMP
CCalcCF::CreateInstance(
    IUnknown FAR* punkOuter,
    REFIID riid,
    void FAR* FAR* ppv)
{
extern CCalc FAR* g_pcalc;

    UNUSED(punkOuter);
    return g_pcalc->QueryInterface(riid, ppv);
}

STDMETHODIMP
#ifdef _MAC
CCalcCF::LockServer(unsigned long fLock)
#else
CCalcCF::LockServer(BOOL fLock)
#endif
{
    UNUSED(fLock);
    return NOERROR;
}

#ifdef _MAC
struct regentry{
    char *szKey;
    char *szValue;
} g_rgregentry[] = {

      { "CLSID\\{00020469-0000-0000-C000-000000000046}",
"OLE Automation DspCalc2 1.0 Application" }

    , { "CLSID\\{00020469-0000-0000-C000-000000000046}\\LocalServer",
"DCL2" }

    , { "CLSID\\{00020469-0000-0000-C000-000000000046}\\ProgID",
"Dspcalc2.Application" }

    , { "CLSID\\{00020469-0000-0000-C000-000000000046}\\InprocHandler",
"OLE2:Def$DefFSet" }

    , { "DCL2", "{00020469-0000-0000-C000-000000000046}" }

    , { "Dspcalc2.Application\\CLSID",
"{00020469-0000-0000-C000-000000000046}" }

};

HRESULT
EnsureRegistration()
{
    HKEY hkey;

    if(RegOpenKey(HKEY_CLASSES_ROOT, "DCL2", &hkey) == NOERROR){
      RegCloseKey(hkey);
      return NOERROR;
    }

    for(int i = 0; i < DIM(g_rgregentry); ++i){
      if(RegSetValue(HKEY_CLASSES_ROOT, g_rgregentry[i].szKey, REG_SZ, g_rgregentry[i].szValue, 0) != ERROR_SUCCESS)
return E_FAIL;
    }

    return NOERROR;
}
#endif //_MAC

/***
*HRESULT InitOle(void)
*Purpose:
*  Initialize Ole, and register our class factories.
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
HRESULT
InitOle()
{
    HRESULT hresult;
    IClassFactory FAR* pcf;

    if((hresult = OleInitialize(NULL)) != NOERROR)
      goto LError0;

#ifdef _MAC
    if((hresult = EnsureRegistration()) != NOERROR)
      goto LError0;
#endif

    // create the single global instance of CCalc
    if((g_pcalc = CCalc::Create()) == NULL){
      hresult = E_OUTOFMEMORY;
      goto LError0;
    }

    if((pcf = CCalcCF::Create()) == NULL)
      goto LError1;

    hresult = CoRegisterClassObject(
      CLSID_CCalc2,
      pcf,
      CLSCTX_LOCAL_SERVER,
      REGCLS_MULTIPLEUSE,
      &g_dwCCalcCF);
    if(FAILED(hresult))
      goto LError2;

    hresult = RegisterActiveObject(
      g_pcalc, CLSID_CCalc2, NULL, &g_dwRegisterCCalc);
    if(FAILED(hresult))
      goto LError2;

    pcf->Release();

    return NOERROR;

LError2:;
    pcf->Release();

LError1:;
    UninitOle();

LError0:;
    return hresult;
}

HRESULT
UninitOle()
{
    if(g_dwRegisterCCalc != 0)
      RevokeActiveObject(g_dwRegisterCCalc, NULL);

    if(g_dwCCalcCF != 0)
      CoRevokeClassObject(g_dwCCalcCF);

    // cause the remaining typeinfo to be released
    if(g_pcalc != NULL)
      g_pcalc->Release();

    OleUninitialize();

    return NOERROR;
}


DSPCALC2.H

/***
*dspcalc2.h
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*
*
*Implementation Notes:
*
*****************************************************************************/

#include "hostenv.h"
#include "resource.h"
#include "clsid.h"

#ifndef CLASS
# ifdef __TURBOC__
#  define CLASS class huge
# else
#  define CLASS class FAR
# endif
#endif

#pragma warning(disable:4355)

#ifdef _MAC
typedef void * LPVOID;
typedef unsigned long ULONG;
typedef int BOOL;
typedef unsigned int UINT;
typedef unsigned short WORD;
typedef unsigned char BYTE;
typedef unsigned long DWORD;
typedef long LONG;
#define FALSE 0
#define TRUE  1
#define NEAR
#endif

#ifdef _MAC
# define UNUSED(X) ((void)(void*)&(X))
#else
# define UNUSED(X) (X)
#endif

/*
 * The .h file included below is created via MkTypLib
 */
#include "calctype.h"

#pragma warning(disable:4355)

#define DIM(X) (sizeof(X)/sizeof(X[0]))

// forward decl
CLASS CCalc;

// Introduced "calculator" interface
//
// This nested class implementes core arithmetic functionality
// (such as it is) *and* is the interface that will be exposed via
// IDispatch for external programmability.
//
CLASS CArith : public _ICalculator {
 public:

  /* IUnknown methods */
  STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR * ppvObj);
  STDMETHOD_(ULONG, AddRef)(THIS);
  STDMETHOD_(ULONG, Release)(THIS);

  /* ICalculator methods */
  STDMETHOD_(void, put_Accum)(long l);
  STDMETHOD_(long, get_Accum)(void);
  STDMETHOD_(void, put_Opnd)(long l);
  STDMETHOD_(long, get_Opnd)(void);
  STDMETHOD_(void, put_Op)(OPERATORS op);
  STDMETHOD_(OPERATORS, get_Op)(void);
  STDMETHOD_(VARIANT_BOOL, Eval)(void);
  STDMETHOD_(void, DCClear)(void);
  STDMETHOD_(void, Display)(void);
  STDMETHOD_(void, Quit)(void);
  STDMETHOD_(VARIANT_BOOL, Button)(SAFEARRAY FAR* psa);

  // the following method is internal, and not exposed for programmability
  BOOL ButtonPush(int button);

  CArith(CCalc FAR* pcalc){
     m_pcalc = pcalc;
     DCClear();
  }

  enum states { STATE_LOPND, STATE_OP, STATE_ROPND, STATE_EVAL };

 private:
  CCalc FAR* m_pcalc;
  OPERATORSm_op;
  longm_opnd;
  longm_accum;
  enum states m_state;
};

CLASS CCalc : public IUnknown {
public:
    friend CArith;

    static CCalc FAR* Create();

    STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppv);
    STDMETHOD_(ULONG, AddRef)(void);
    STDMETHOD_(ULONG, Release)(void);

    CCalc() : m_arith(this)
    {
      m_refs = 0;
#ifdef _MAC
      m_pdlg = nil;
#else
      m_hwnd = 0;
#endif
      m_punkStdDisp = NULL;
    }

#ifdef _MAC
    DialogPtr m_pdlg;
#else
    HWND m_hwnd;
#endif
    CArith m_arith;

private:
    ULONG m_refs;
    IUnknown FAR* m_punkStdDisp;
};

// the CCalc Class Factory
//
CLASS CCalcCF : public IClassFactory {
public:
    static IClassFactory FAR* Create();

    STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppv);
    STDMETHOD_(ULONG, AddRef)(void);
    STDMETHOD_(ULONG, Release)(void);

    STDMETHOD(CreateInstance)(
      IUnknown FAR* punkOuter, REFIID riid, void FAR* FAR* ppv);
#ifdef _MAC
    STDMETHOD(LockServer)(unsigned long fLock);
#else
    STDMETHOD(LockServer)(BOOL fLock);
#endif

    CCalcCF() { m_refs = 1; }

private:
    ULONG m_refs;
};

extern HRESULT InitOle(void);
extern HRESULT UninitOle(void);

extern CCalc FAR* g_pcalc;


MAIN.CPP

/***
*main.cpp
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  This module is the main entry point of the sample IDispatch
*  calculator, dspcalc2.exe.
*
*  This program is intended to demonstrate an implementation of
*  the IDispatch interface.
*
*Implementation Notes:
*
*****************************************************************************/

#include "dspcalc2.h"

TCHAR g_szAppName[] = TSTR("DspCalc2");

BOOL InitApplication(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);

extern "C" {
long FAR PASCAL WndProc(HWND, UINT, WPARAM, LPARAM);
int PASCAL WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
}

extern "C" int PASCAL
WinMain(
    HINSTANCE hinst,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow)
{
    MSG msg;

    if(!hPrevInstance)
      if(!InitApplication(hinst))
return FALSE;

    if(InitOle() != NOERROR)
      return FALSE;

    if(!InitInstance(hinst, nCmdShow)){
      UninitOle();
      return FALSE;
    }

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

    UninitOle();

    return msg.wParam;
}

BOOL
InitApplication(HINSTANCE hinst)
{
    WNDCLASS  wc;

    wc.style= CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc= WndProc;
    wc.cbClsExtra= 0;
    wc.cbWndExtra= DLGWINDOWEXTRA;
    wc.hInstance= hinst;
    wc.hIcon= LoadIcon(hinst, g_szAppName);
    wc.hCursor= LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground= (HBRUSH)(COLOR_APPWORKSPACE+1);
    wc.lpszMenuName= NULL;
    wc.lpszClassName= g_szAppName;

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

    return TRUE;
}

BOOL
InitInstance(HINSTANCE hinst, int nCmdShow)
{
    g_pcalc->m_hwnd = CreateDialog(hinst, g_szAppName, 0, NULL);

    ShowWindow(g_pcalc->m_hwnd, nCmdShow);

    g_pcalc->m_arith.Display();

    return TRUE;
}

extern "C" long FAR PASCAL
WndProc(
    HWND hwnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam)
{
    switch(message){
    case WM_COMMAND:
      switch(wParam){
      case IDC_ZERO:
      case IDC_ONE:
      case IDC_TWO:
      case IDC_THREE:
      case IDC_FOUR:
      case IDC_FIVE:
      case IDC_SIX:
      case IDC_SEVEN:
      case IDC_EIGHT:
      case IDC_NINE:
      case IDC_PLUS:
      case IDC_MINUS:
      case IDC_MULT:
      case IDC_DIV:
      case IDC_CLEAR:
      case IDC_EQUALS:
g_pcalc->m_arith.ButtonPush(wParam);
return 0;
      }
      break;

    case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}

#if defined(WIN32)

extern "C" char FAR*
ConvertStrWtoA(OLECHAR FAR* strIn, char FAR* buf, UINT size)
{
  int badConversion = FALSE;

  WideCharToMultiByte(CP_ACP, NULL,
              strIn, -1,
      buf, size,
      NULL, &badConversion);
  return buf;
}

extern "C" char FAR*
AnsiString(OLECHAR FAR* strIn)
{
  static char buf[256];

  return (ConvertStrWtoA(strIn, buf, 256));
}

#endif


README.TXT

VTable Binding

The HELLCTRL sample is a vtable binding automation controller that controls
the HELLO sample.

HELLCTRL uses CoCreateInstance to create an instance of Hello and obtains
its IHello interface using QueryInterface. It then calls the methods of the
IHello interface to access the properties and methods of the Hello object.
Rich Error Information is obtained by accessing the IErrorInfo interface.

FILES
-----

tlb.h         File copied from the HELLO 2.0 sample. Generated by MKTYPLIB.
hellctrl.cpp  Controller source file.
hellctrl.h    Controller source header.

To compile:
-----------

To compile an ANSI debug version of the sample for Windows NT and Windows 95,
use the following command:

   nmake

To compile a Unicode debug version of the sample for Windows NT only, use the
following command:

   nmake dev=win32 HOST=NT

To compile a 16-bit debug version of the sample and if your development
environment is Win 3.x, use the following command. If your development
environment is Win95 or NT, change the HOST appropriately.

   nmake dev=win16 HOST=DOS

To clean up between builds which use different options, use one of the
following commands:

   nmake clean
   nmake dev=win16 clean

See the makefile header comments for other make options.

If one of the tools cannot find SDKUTIL.LIB, use the SAMPLES\COM\COMMON sample
to build SDKUTIL.LIB.

Visual C++ 2.x and above can use makefile as an external makefile
after making the following replacement in the makefile:

   !include <$(MSTOOLS)\samples\com\include\olesampl.mak>

with

   !include <win32.mak>

To run:
-------

Select the CreateHello menu after registering the HELLO 2.0 sample.
This will launch Hello invisibly. Use the SetVisible menu to make
it visible.


HELLCTRL.H

#ifdef WIN32

#ifdef UNICODE
    #define FROM_OLE_STRING(str) str
    #define TO_OLE_STRING(str) str
#else
    #define FROM_OLE_STRING(str) ConvertToAnsi(str)
    char* ConvertToAnsi(OLECHAR FAR* szW);
    #define TO_OLE_STRING(str) ConvertToUnicode(str)
    OLECHAR* ConvertToUnicode(char FAR* szA);
    // Maximum length of string that can be converted between Ansi & Unicode
    #define STRCONVERT_MAXLEN 500
#endif

#else  // WIN16
  #define APIENTRY far pascal
  #define TCHAR char
  #define TEXT(sz) sz
  #define FROM_OLE_STRING(str) str
  #define TO_OLE_STRING(str) str
  #define LPTSTR LPSTR
  #define LPCTSTR LPCSTR

  // Windows NT defines the following in windowsx.h
  #define GET_WM_COMMAND_ID(w,l) (w)
  #define GET_WM_COMMAND_CMD(w,l) HIWORD(l)
  #define GET_WM_COMMAND_HWND(w,l) LOWORD(l)
#endif

// Menu-item IDs
#define IDM_CREATEHELLO  300
#define IDM_SETVISIBLE   301
#define IDM_SETINVISIBLE   302
#define IDM_GETHELLOMESSAGE   303
#define IDM_SAYHELLO  304
#define IDM_RELEASEHELLO 305

// MAX len of string table entries
#define STR_LEN   100

// String table constants
#define IDS_PROGNAME                   1
#define IDS_RESULT                     2
#define IDS_ERROR                      3

// Function prototypes
int PASCAL WinMain (HINSTANCE, HINSTANCE, LPSTR, int);
BOOL InitApplication (HINSTANCE);
BOOL InitInstance (HINSTANCE, int);
void DisplayError(IHello FAR* phello);
#ifdef WIN16
LRESULT __export CALLBACK MainWndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
#else
LRESULT CALLBACK MainWndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
#endif


HELLCTRL.CPP

/*************************************************************************
**
**    Automation Controller that uses vtable binding.
**    Controls the HELLO automation object.
**
**    hellctrl.cpp
**
**
**     Written by Microsoft Product Support Services, Windows Developer Support
**    (c) Copyright Microsoft Corp. 1994 - 1996 All Rights Reserved
**
*************************************************************************/
#define STRICT

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
  #include <commdlg.h>
#endif
#include <initguid.h>
#include "tlb.h"
#include "hellctrl.h"

// Globals
HINSTANCE g_hinst;                          // Instance of application
HWND      g_hwnd;                           // Toplevel window handle

// String resource buffers
TCHAR g_szTitle[STR_LEN];                    // Main window caption
TCHAR g_szResult[STR_LEN];                   // "Result"
TCHAR g_szError[STR_LEN];                    // "Error"

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application. Should register the app class
 *  if a previous instance has not done so and do any other one-time
 *  initializations.
 *
 */
int APIENTRY WinMain (HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpCmdLine, int nCmdShow)
{
   MSG msg;

#ifdef WIN16
   //  It is recommended that all 16 bit OLE applications set
   //  their message queue size to 96. This improves the capacity
   //  and performance of OLE's LRPC mechanism.
   int cMsg = 96;                  // Recommend msg queue size for OLE
   while (cMsg && !SetMessageQueue(cMsg))  // take largest size we can get.
       cMsg -= 8;
   if (!cMsg)
       return -1;  // ERROR: we got no message queue
#endif

   // Load string constants
   LoadString(hinst, IDS_PROGNAME, g_szTitle, STR_LEN);
   LoadString(hinst, IDS_RESULT, g_szResult, STR_LEN);
   LoadString(hinst, IDS_ERROR, g_szError, STR_LEN);

   if (!hinstPrev)
      if (!InitApplication(hinst))
         return (FALSE);

   if(OleInitialize(NULL) != NOERROR)
      return FALSE;

   if (!InitInstance(hinst, nCmdShow))
      return (FALSE);

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

   OleUninitialize();

   return (msg.wParam); // Returns the value from PostQuitMessage
}

/*
 * InitApplication
 *
 * Purpose:
 *  Registers window class
 *
 * Parameters:
 *  hinst       hInstance of application
 *
 * Return Value:
 *  TRUE if initialization succeeded, FALSE otherwise.
 */
BOOL InitApplication (HINSTANCE hinst)
{
   WNDCLASS wc;

   wc.style = CS_DBLCLKS;
   wc.lpfnWndProc = MainWndProc;
   wc.cbClsExtra = 0;
   wc.cbWndExtra = 0;
   wc.hInstance = hinst;
   wc.hIcon = LoadIcon(hinst, TEXT("ControlIcon"));
   wc.hCursor = LoadCursor(NULL, IDC_ARROW);
   wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
   wc.lpszMenuName = TEXT("ControlMenu");
   wc.lpszClassName = TEXT("MainWndClass");

   return RegisterClass(&wc);
 }

/*
 * InitInstance
 *
 * Purpose:
 *  Creates and shows main window
 *
 * Parameters:
 *  hinst           hInstance of application
 *  nCmdShow        specifies how window is to be shown
 *
 * Return Value:
 *  TRUE if initialization succeeded, FALSE otherwise.
 */
BOOL InitInstance (HINSTANCE hinst, int nCmdShow)
{

   g_hinst = hinst;
   // Create Main Window
   g_hwnd = CreateWindow(TEXT("MainWndClass"), g_szTitle,
                       WS_OVERLAPPEDWINDOW,
                       CW_USEDEFAULT, CW_USEDEFAULT,
                       400, 200,
                       NULL, NULL, hinst, NULL);
   if (!g_hwnd)
      return FALSE;

   ShowWindow(g_hwnd, nCmdShow);
   UpdateWindow(g_hwnd);
   return TRUE;
}

/*
 * MainWndProc
 *
 * Purpose:
 *  Window procedure for main window
 *
 */
LRESULT CALLBACK MainWndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
   static IHello FAR* phello = NULL;
   HRESULT hr;
   LPUNKNOWN punk;

   switch (msg)
   {
      case WM_COMMAND:
         switch (wParam)
         {
            case IDM_CREATEHELLO:
                // Create Hello object and QueryInterface for IHello interface.
                hr = CoCreateInstance(CLSID_Hello, NULL, CLSCTX_SERVER,
                     IID_IUnknown, (void FAR* FAR*)&punk);
                if (FAILED(hr))
                {
                    MessageBox(NULL, TEXT("CoCreateInstance"), g_szError, MB_OK);
                    return 0L;
                }
                hr = punk->QueryInterface(IID_IHello,  (void FAR* FAR*)&phello);
                if (FAILED(hr))
                {
                    MessageBox(NULL, TEXT("QueryInterface(IID_IHello)"), g_szError, MB_OK);
                    punk->Release();
                    return 0L;
                }
                punk->Release();
                return 0L;

            case IDM_SETVISIBLE:
                // Set Visible property to TRUE
                hr = phello->put_Visible(TRUE);
                if (FAILED(hr))
                    DisplayError(phello);
                return 0L;

            case IDM_SETINVISIBLE:
                // Set visible property to FALSE
                hr = phello->put_Visible(FALSE);
                if (FAILED(hr))
                    DisplayError(phello);
                return 0L;

            case IDM_GETHELLOMESSAGE:
            {
                // Access Hello Message property and display it
                // in a MessageBox
                BSTR bstr = NULL;   // BSTR must be intialized before passing
                                    // to get_HelloMessage.
                hr = phello->get_HelloMessage(&bstr);
                if (FAILED(hr))
                    DisplayError(phello);
                else MessageBox(NULL, FROM_OLE_STRING(bstr), g_szResult, MB_OK);

                // Caller is responsible for freeing parameters and return values.
                if (bstr)
                    SysFreeString(bstr);
                return 0L;
            }

            case IDM_SAYHELLO:
                // Invoke SayHello method
                hr = phello->SayHello();
                if (FAILED(hr))
                    DisplayError(phello);
                return 0L;

            case IDM_RELEASEHELLO:
                // Release the Hello object
                phello->Release();
                phello = NULL;
                return 0L;
         }
         break;

      case WM_INITMENUPOPUP:
      {
         HMENU hmenu = (HMENU)wParam;

         if (LOWORD(lParam) != 0)
            return 0L;

         // Enable or gray the appropriate menu items. phello indicates if an automation object
         //  is currently being controlled.
         EnableMenuItem(hmenu, IDM_CREATEHELLO,  MF_BYCOMMAND | (phello?MF_GRAYED:MF_ENABLED));
         EnableMenuItem(hmenu, IDM_SETVISIBLE,   MF_BYCOMMAND | (phello?MF_ENABLED:MF_GRAYED));
         EnableMenuItem(hmenu, IDM_SETINVISIBLE,   MF_BYCOMMAND | (phello?MF_ENABLED:MF_GRAYED));
         EnableMenuItem(hmenu, IDM_GETHELLOMESSAGE,   MF_BYCOMMAND | (phello?MF_ENABLED:MF_GRAYED));
         EnableMenuItem(hmenu, IDM_SAYHELLO,  MF_BYCOMMAND | (phello?MF_ENABLED:MF_GRAYED));
         EnableMenuItem(hmenu, IDM_RELEASEHELLO, MF_BYCOMMAND | (phello?MF_ENABLED:MF_GRAYED));
         return 0L;
      }

      case WM_DESTROY:
         if (phello)
            phello->Release();
         PostQuitMessage(0);
         break;

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

   return NULL;
}

/*
 * DisplayError
 *
 * Purpose:
 *  Obtains Rich Error Information about the automation error from
 *  the IErrorInfo interface.
 *
 */
void DisplayError(IHello FAR* phello)
{
   IErrorInfo FAR* perrinfo;
   BSTR bstrDesc;
   HRESULT hr;
   ISupportErrorInfo FAR* psupporterrinfo;

   hr = phello->QueryInterface(IID_ISupportErrorInfo, (LPVOID FAR*)&psupporterrinfo);
   if (FAILED(hr))
   {
      MessageBox(NULL, TEXT("QueryInterface(IID_ISupportErrorInfo)"), g_szError, MB_OK);
      return;
   }

   hr = psupporterrinfo->InterfaceSupportsErrorInfo(IID_IHello);
   if (hr != NOERROR)
   {
       psupporterrinfo->Release();
       return;
   }
   psupporterrinfo->Release();

   // In this example only the error description is obtained and displayed.
   // See the IErrorInfo interface for other information that is available.
   hr = GetErrorInfo(0, &perrinfo);
   if (FAILED(hr))
       return;
   hr = perrinfo->GetDescription(&bstrDesc);
   if (FAILED(hr))
   {
       perrinfo->Release();
       return;
   }

   MessageBox(NULL, FROM_OLE_STRING(bstrDesc), g_szError, MB_OK);
   SysFreeString(bstrDesc);
}

/*
 * Quick & Dirty ANSI/Unicode conversion routines. These routines use a static
 * buffer of fixed size to hold the converted string. Consequently these
 * routines are limited to strings of size STRCONVERT_MAXLEN. Also the same
 * buffer is reused when the routine is called a second time. So make sure
 * that the converted string is used before the conversion routine is called
 * again
 */
#ifdef WIN32

#ifndef UNICODE
char* ConvertToAnsi(OLECHAR FAR* szW)
{
  static char achA[STRCONVERT_MAXLEN];

  WideCharToMultiByte(CP_ACP, 0, szW, -1, achA, STRCONVERT_MAXLEN, NULL, NULL);
  return achA;
}

OLECHAR* ConvertToUnicode(char FAR* szA)
{
  static OLECHAR achW[STRCONVERT_MAXLEN];

  MultiByteToWideChar(CP_ACP, 0, szA, -1, achW, STRCONVERT_MAXLEN);
  return achW;
}
#endif

#endif


TLB.H

/* This header file machine-generated by mktyplib.exe */
/* Interface to type library: Hello */

#ifndef _Hello_H_
#define _Hello_H_

DEFINE_GUID(LIBID_Hello,0xF37C8060L,0x4AD5,0x101B,0xB8,0x26,0x00,0xDD,0x01,0x10,0x3D,0xE1);

DEFINE_GUID(IID_IHello,0xF37C8062L,0x4AD5,0x101B,0xB8,0x26,0x00,0xDD,0x01,0x10,0x3D,0xE1);

/* Definition of interface: IHello */
#undef INTERFACE
#define INTERFACE IHello

DECLARE_INTERFACE_(IHello, IDispatch)
{
#ifndef NO_BASEINTERFACE_FUNCS

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(THIS_ UINT FAR* pctinfo) PURE;

    STDMETHOD(GetTypeInfo)(
      THIS_
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo) PURE;

    STDMETHOD(GetIDsOfNames)(
      THIS_
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid) PURE;

    STDMETHOD(Invoke)(
      THIS_
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr) PURE;
#endif

    /* IHello methods */
    STDMETHOD(get_Application)(THIS_ IHello FAR* FAR* retval) PURE;
    STDMETHOD(get_FullName)(THIS_ BSTR FAR* retval) PURE;
    STDMETHOD(get_Name)(THIS_ BSTR FAR* retval) PURE;
    STDMETHOD(get_Parent)(THIS_ IHello FAR* FAR* retval) PURE;
    STDMETHOD(put_Visible)(THIS_ VARIANT_BOOL VisibleFlag) PURE;
    STDMETHOD(get_Visible)(THIS_ VARIANT_BOOL FAR* retval) PURE;
    STDMETHOD(Quit)(THIS) PURE;
    STDMETHOD(put_HelloMessage)(THIS_ BSTR Message) PURE;
    STDMETHOD(get_HelloMessage)(THIS_ BSTR FAR* retval) PURE;
    STDMETHOD(SayHello)(THIS) PURE;
};

DEFINE_GUID(CLSID_Hello,0xF37C8061L,0x4AD5,0x101B,0xB8,0x26,0x00,0xDD,0x01,0x10,0x3D,0xE1);

#ifdef __cplusplus
class Hello;
#endif

#endif


README.TXT

Simple OLE Automation Server

SUMMARY
=======

The HELLO sample is a simple OLE Automation server. This is a good sample
to use to learn to create your first OLE Automation server. This sample has
a Hello Object that supports the HelloMessage Property and the SayHello
method.

MORE INFORMATION
================

This sample implements the following features:

  - Dual interfaces which allow access of automation properties and methods
   through vtable binding and through IDispatch.

  - Rich Error information for vtable-binding controllers. This is done by
    implementing ISupportErrorInfo and using IErrorInfo.

  - Active object registration, using RegisterActiveObject and
    RevokeActiveObject.

  - Correct shut-down behavior.

  - A registry file containing Hello.Application and Hello.Application.2
    as progIDs.

  - Initial invisibility.

To compile:
-----------

To compile an ANSI debug version of the sample for Windows NT and Windows 95,
use the following command:

   nmake

To compile a Unicode debug version of the sample for Windows NT only, use the
following command:

   nmake dev=win32 HOST=NT

To compile a 16-bit debug version of the sample and if your development
environment is Win 3.x, use the following command. If your development
environment is Win95 or NT, change the HOST appropriately.

   nmake dev=win16 HOST=DOS

Also modify the .REG file to use 16 bit version of the keys. For example
use LocalServer instead of LocalServer32.

To clean up between builds which use different options, use one of the
following commands:

   nmake clean
   nmake dev=win16 clean

See the makefile header comments for other make options.

If one of the tools cannot find SDKUTIL.LIB, use the SAMPLES\COM\COMMON sample
to build SDKUTIL.LIB.

Visual C++ 2.x and above can use makefile as an external makefile
after making the following replacement in the makefile:

   !include <$(MSTOOLS)\samples\com\include\olesampl.mak>

with

   !include <win32.mak>

To run:
-------

Change hello.reg to provide the full path of hello.exe and hello.tlb.
Register hello.reg in the registration database by double-clicking it.
The HELLCTRL directory contains a controller that will control HELLO using
vtable-binding.

The following VB 4.0 script can also be used to control HELLO:

'Use the Tools/References menu to select the Hello type library before
'running this code.

Dim HelloObj As New Hello.Hello 'Hello.Hello is the libraryname.coclassname. If
                                'the coclass name was not the same as the library name
                                'Dim HelloObj As New Hello could have been used.
                                'The type of HelloObj is declared as the default
                                'interface in the coclass (IHello). The properties/methods of
                                'the IHello interface can be invoked using HelloObj.

                                'You can also use
                                'Dim HelloObj As IHello
                                'Set HelloObj = New Hello.Hello
Private Sub SetVisible_Click()
    HelloObj.Visible = True                   'Set the visible property
End Sub
Private Sub Get_HelloMsg_Property_Click()
       Debug.Print HelloObj.HelloMessage     'Get the HelloMessage property
End Sub
Private Sub Set_HelloMsg_Property_Click()
       HelloObj.HelloMessage = "Hello Universe" 'Set the HelloMessage property
End Sub
Private Sub Invoke_SayHello_Method_Click()
       HelloObj.SayHello                     'Invoke the Hello method
End Sub

Files
-----

HELLO.ODL  Object description language that describes the property and
method that HELLO exposes.

TLB.H  Header file generated by mktyplib.exe.

MAKEFILE  Makefile for project.

Other files implement the HELLO automation server.


HELLO.H

#ifdef WIN32

#ifdef UNICODE
    #define FROM_OLE_STRING(str) str
    #define TO_OLE_STRING(str) str
#else
    #define FROM_OLE_STRING(str) ConvertToAnsi(str)
    char* ConvertToAnsi(OLECHAR FAR* szW);
    #define TO_OLE_STRING(str) ConvertToUnicode(str)
    OLECHAR* ConvertToUnicode(char FAR* szA);
    // Maximum length of string that can be converted between Ansi & Unicode
    #define STRCONVERT_MAXLEN 300
#endif

#else  // WIN16
  #define APIENTRY far pascal
  #define TCHAR char
  #define TEXT(sz) sz
  #define FROM_OLE_STRING(str) str
  #define TO_OLE_STRING(str) str
  #define LPTSTR LPSTR

  // Windows NT defines the following in windowsx.h
  #define GET_WM_COMMAND_ID(w,l) (w)
  #define GET_WM_COMMAND_CMD(w,l) HIWORD(l)
  #define GET_WM_COMMAND_HWND(w,l) LOWORD(l)
#endif

#undef STRICT
#define STRICT

// Resource IDs
#define IDD_MAINWINDOW   101
#define IDI_ICON         102

// Child window IDs
#define IDC_HELLODISPLAY 101
#define IDC_SAYHELLO     102

// MAX len of string table entries
#define STR_LEN   100

// String table constants
#define IDS_Name                 1
#define IDS_HelloMessage         2
#define IDS_ErrorLoadingTypeLib  3
#define IDS_SayHello             4
#define IDS_ProgID               5
#define IDS_Unexpected           1001

// SCODEs for the Hello Application. This is required for vtable-binding
// Automation objects want to return custom HRESULTs. All the OLE-defined
// FACILITY_ITF codes have a code value which lies in the region 0x0000-0x01FFF.
// While it is legal for the definer to use any code, it is highly recommended
// that only code values in the range 0x0200-0xFFFF be used, as this will reduce the
// possiblity of accidental confusion with any OLE-defined errors.
#define HELLO_E_FIRST       MAKE_SCODE(SEVERITY_ERROR, FACILITY_ITF, 0x0200)

#define HELLO_E_UNEXPECTED  (HELLO_E_FIRST + 0x0)
// Unexpected error

// Number of SCODEs
#define SCODE_COUNT 1

// Function prototypes
int APIENTRY WinMain (HINSTANCE, HINSTANCE, LPSTR, int);
BOOL InitApplication (HINSTANCE);
BOOL InitInstance (HINSTANCE);
BOOL ProcessCmdLine(LPSTR lpCmdLine, LPDWORD pdwRegisterCF, LPDWORD pdwRegisterActiveObject, int nCmdShow);
void Uninitialize(DWORD dwRegisterCF, DWORD dwRegisterActiveObject);
HRESULT LoadTypeInfo(ITypeInfo FAR* FAR* pptinfo, REFCLSID clsid);
HRESULT RaiseException(int nID, REFGUID rguid);
#ifdef WIN16
extern "C" LRESULT __export CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);
#else
extern "C" LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);
#endif

#include "tlb.h"

// ISupportErrorInfo interface implementation
interface CSupportErrorInfo : public ISupportErrorInfo
{
public:
    // IUnknown methods
    STDMETHOD(QueryInterface)(REFIID riid, LPVOID FAR* ppvObj);
    STDMETHOD_(ULONG, AddRef)(void);
    STDMETHOD_(ULONG, Release)(void);

    // ISupportErrorInfo method
    STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);

    CSupportErrorInfo(IUnknown FAR* punkObject, REFIID riid);
private:
    LPUNKNOWN m_punkObject;  // IUnknown of Object that implements this interface
    GUID m_iid;
};

class FAR CHello : public IHello
{
public:
    // IUnknown methods
    STDMETHOD(QueryInterface)(REFIID riid, LPVOID FAR* ppvObj);
    STDMETHOD_(ULONG, AddRef)(void);
    STDMETHOD_(ULONG, Release)(void);

    // IDispatch methods
    STDMETHOD(GetTypeInfoCount)(UINT FAR* pctinfo);
    STDMETHOD(GetTypeInfo)(
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo);
    STDMETHOD(GetIDsOfNames)(
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid);
    STDMETHOD(Invoke)(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr);

    // IHello methods
    STDMETHOD(get_Application)(IHello FAR* FAR* ppHello);
    STDMETHOD(get_FullName)(BSTR FAR* pbstr);
    STDMETHOD(get_Name)(BSTR FAR* pbstr);
    STDMETHOD(get_Parent)(IHello FAR* FAR* ppHello);
    STDMETHOD(put_Visible)(VARIANT_BOOL bVisible);
    STDMETHOD(get_Visible)(VARIANT_BOOL FAR* pbool);
    STDMETHOD(Quit)();
    STDMETHOD(put_HelloMessage)(BSTR bstrMessage);
    STDMETHOD(get_HelloMessage)(BSTR FAR* pbstrMessage);
    STDMETHOD(SayHello)();

    // CHello methods
    STDMETHOD_(void, ShowWindow)(int nCmdShow);
    static HRESULT Create(HINSTANCE hinst, LPTSTR lpszHelloMessage, CHello FAR* FAR* pphello); // Creates and intializes Hello object
    CHello();
    ~CHello();

public:
    HWND m_hwnd;                   // Application window.
    HINSTANCE m_hinst;             // Hinstance of application.
    BSTR m_bstrName;               // Name of application.
    BSTR m_bstrProgID;             // ProgID of application.

private:
    LPTYPEINFO m_ptinfo;           // Type information of Hello application interface.
    BSTR m_bstrHelloMsg;           // Hello message
    BSTR m_bstrFullName;           // Full name of application.
    BOOL m_bVisible;               // Is window visible?
    ULONG m_cRef;                  // Reference count
    CSupportErrorInfo m_SupportErrorInfo; // ISupportErrorInfo interface implementation
};

class FAR CHelloCF : public IClassFactory
{
public:
    // IUnknown methods
    STDMETHOD(QueryInterface)(REFIID riid, LPVOID FAR* ppvObj);
    STDMETHOD_(ULONG, AddRef)(void);
    STDMETHOD_(ULONG, Release)(void);

    // IClassFactory methods
    STDMETHOD(CreateInstance)(IUnknown FAR* punkOuter, REFIID riid,
                              void FAR* FAR* ppv);
    STDMETHOD(LockServer)(BOOL fLock);

    CHelloCF();

private:
    ULONG m_cRef;                   // Reference count
};

extern CHello FAR* g_phello;


HELLOCF.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation Hello 2.0 Application.
**
**  hellocf.cpp
**
**  CHelloCF (class factory) implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/
#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "hello.h"

CHelloCF::CHelloCF(void)
{
    m_cRef = 0;
}

/*
 * CHelloCF::QueryInterface, AddRef, Release
 *
 * Purpose:
 *  Implements IUnknown::QueryInterface, AddRef, Release
 *
 */
STDMETHODIMP
CHelloCF::QueryInterface(REFIID iid, void FAR* FAR* ppv)
{
    *ppv = NULL;

    if (iid == IID_IUnknown || iid == IID_IClassFactory)
        *ppv = this;
    else
        return E_NOINTERFACE;

    AddRef();
    return NOERROR;
}

STDMETHODIMP_(ULONG)
CHelloCF::AddRef(void)
{
#ifdef _DEBUG
    TCHAR ach[50];
    wsprintf(ach, TEXT("Ref = %ld, Hello Class Factory\r\n"), m_cRef+1);
    OutputDebugString(ach);
#endif

    return ++m_cRef;
}

STDMETHODIMP_(ULONG)
CHelloCF::Release(void)
{
#ifdef _DEBUG
    TCHAR ach[50];
    wsprintf(ach, TEXT("Ref = %ld, Hello Class Factory\r\n"), m_cRef-1);
    OutputDebugString(ach);
#endif

    if(--m_cRef == 0)
    {
        delete this;
        return 0;
    }
    return m_cRef;
}

/*
 * CHelloCF::CreateInstance, LockServer
 *
 * Purpose:
 *  Implements IClassFactory::CreateInstance, LockServer
 *
 */
STDMETHODIMP
CHelloCF::CreateInstance(IUnknown FAR* punkOuter,
                         REFIID riid,
                         void FAR* FAR* ppv)
{
     HRESULT hr;

    *ppv = NULL;

    // This implementation does'nt allow aggregation
    if (punkOuter)
        return CLASS_E_NOAGGREGATION;

    // This is REGCLS_SINGLEUSE class factory, so CreateInstance will be
    // called atmost once. An application objects has a REGCLS_SINGLEUSE class
    // factory. The global application object has already been created when
    // CreateInstance is called. A REGCLS_MULTIPLEUSE class factory's
    // CreateInstance would be called multiple times and would create a new
    // object each time. An MDI application would have a REGCLS_MULTIPLEUSE
    // class factory for it's document objects.
    hr = g_phello->QueryInterface(riid, ppv);
    if (FAILED(hr))
    {
        g_phello->Quit();
        return hr;
    }
    return NOERROR;
}

STDMETHODIMP
CHelloCF::LockServer(BOOL fLock)
{
    CoLockObjectExternal(g_phello, fLock, TRUE);
    return NOERROR;
}


MAIN.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation Hello 2.0 Application.
**
**  main.cpp
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/
#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include <initguid.h>
#include "hello.h"

// Globals
CHello FAR* g_phello;

SCODE g_scodes[SCODE_COUNT] =          // Array of SCODEs for easy lookup
{
    HELLO_E_UNEXPECTED
};

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 *
 */
int APIENTRY WinMain (HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR pCmdLine, int nCmdShow)
{
    MSG msg;
    DWORD dwRegisterCF;
    DWORD dwRegisterActiveObject;

#ifdef WIN16
   //  It is recommended that all 16 bit OLE applications set
   //  their message queue size to 96. This improves the capacity
   //  and performance of OLE's LRPC mechanism.
   int cMsg = 96;                  // Recommend msg queue size for OLE
   while (cMsg && !SetMessageQueue(cMsg))  // take largest size we can get.
       cMsg -= 8;
   if (!cMsg)
       return -1;  // ERROR: we got no message queue
#endif

    if (!hinstPrev)
       if (!InitApplication(hinst)) // Register window class
          return FALSE;

    if (!InitInstance(hinst))   // Initialize OLE and create Hello application object
        return (FALSE);

    // Determine if /Automation was specified in command line and register class
    // factory and active object. Show window if application was started stand alone.
    if (!ProcessCmdLine(pCmdLine, &dwRegisterCF, &dwRegisterActiveObject, nCmdShow))
    {
       Uninitialize(dwRegisterCF, dwRegisterActiveObject);
       return (FALSE);
    }

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

    Uninitialize(dwRegisterCF, dwRegisterActiveObject);
    return (msg.wParam);
}

/*
 * InitApplication
 *
 * Purpose:
 *  Registers window class
 *
 * Parameters:
 *  hinst       hInstance of application
 *
 * Return Value:
 *  TRUE if initialization succeeded, FALSE otherwise.
 */
BOOL InitApplication (HINSTANCE hinst)
{
   WNDCLASS wc;

   wc.style = CS_DBLCLKS | CS_SAVEBITS | CS_BYTEALIGNWINDOW;
   wc.lpfnWndProc = MainWndProc;
   wc.cbClsExtra = 0;
   wc.cbWndExtra = DLGWINDOWEXTRA;
   wc.hInstance = hinst;
   wc.hIcon = LoadIcon(hinst, MAKEINTRESOURCE(IDI_ICON));
   wc.hCursor = LoadCursor(NULL, IDC_ARROW);
   wc.hbrBackground = HBRUSH(COLOR_WINDOW + 1);
   wc.lpszMenuName = NULL;
   wc.lpszClassName = TEXT("MainWndClass");

   return RegisterClass(&wc);
}

/*
 * InitInstance
 *
 * Purpose:
 *  Intializes OLE and creates Hello Application object
 *
 * Parameters:
 *  hinst           hInstance of application
 *
 * Return Value:
 *  TRUE if initialization succeeded, FALSE otherwise.
 */
BOOL InitInstance (HINSTANCE hinst)
{
    HRESULT hr;
    TCHAR ach[STR_LEN];

    // Intialize OLE
    hr = OleInitialize(NULL);
    if (FAILED(hr))
       return FALSE;

    // Create an instance of the Hello Application object. Object is
    // created with refcount 0.
    LoadString(hinst, IDS_HelloMessage, ach, sizeof(ach));
    hr = CHello::Create(hinst, ach, &g_phello);
    if (FAILED(hr))
        return FALSE;
    return TRUE;
}

/*
 * ProcessCmdLine
 *
 * Purpose:
 *  Check if command line contains /Automation. If so, the class factory of the
 *  application object is registered.  If not, the application was started stand-alone and
 *  so the window is shown. The application object is registered using RegisterActiveObject.
 *
 * Parameters:
 *  pCmdLine       Command line passed to application
 *  pdwRegisterCF   Returns id returned after class factory registration. Can be used to
 *                  revoke class factory registration.
 *  pdwRegisterActiveObject   Returns id returned after active object registration. Can be used to
 *                  revoke active object registration.
 *  nCmdShow        Specifies how window is to be shown if application was started stand alone.
 *
 * Return Value:
 *    TRUE if OLE initialization succeeded, FALSE otherwise.
 *
 */
BOOL ProcessCmdLine(LPSTR pCmdLine, LPDWORD pdwRegisterCF, LPDWORD pdwRegisterActiveObject, int nCmdShow)
{
   LPCLASSFACTORY pcf = NULL;
   HRESULT hr;

   *pdwRegisterCF = 0;
   *pdwRegisterActiveObject = 0;

   // Expose class factory for application object if command line contains the
   // Automation switch
   if (_fstrstr(pCmdLine, "-Automation") != NULL
       || _fstrstr(pCmdLine, "/Automation") != NULL)
   {
       pcf = new CHelloCF;
       if (!pcf)
           goto error;
       pcf->AddRef();
       hr = CoRegisterClassObject(CLSID_Hello, pcf,
                                     CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE,
                                     pdwRegisterCF);
       if (hr != NOERROR)
           goto error;
       pcf->Release();
   }
   else g_phello->ShowWindow(nCmdShow);    // Show window if started stand-alone

   // Register Hello application object in the Running Object Table (ROT). This
   // allows controllers to connect to a running application object instead of creating
   // a new instance. Use weak registration so that the ROT releases it's reference when
   // all external references are released. If strong registration is used, the ROT will not
   // release it's reference until RevokeActiveObject is called and so will keep
   // the object alive even after all external references have been released.
   RegisterActiveObject(g_phello, CLSID_Hello, ACTIVEOBJECT_WEAK, pdwRegisterActiveObject);
   return TRUE;

error:
    if (pcf)
        pcf->Release();
    return FALSE;
}

/*
 * Uninitialize
 *
 *  Purpose:
 *   Revoke class factory and active object registration and uninitialize OLE.
 *
 * Parameters:
 *  dwRegisterCF ID returned after class factory registration.
 *  dwRegisterActiveObject ID returned after active object registration.
 *
 */
void Uninitialize(DWORD dwRegisterCF, DWORD dwRegisterActiveObject)
{
    if (dwRegisterCF != 0)
        CoRevokeClassObject(dwRegisterCF);
    if (dwRegisterActiveObject != 0)
        RevokeActiveObject(dwRegisterActiveObject, NULL);
    OleUninitialize();
}

/*
 * MainWndProc
 *
 * Purpose:
 *  Window procedure for main window. The main window is a dialog.
 *
 */
LRESULT CALLBACK MainWndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
   switch (msg)
   {
      case WM_COMMAND:
      {
          switch(GET_WM_COMMAND_ID(wParam, lParam))
          {
              case IDC_SAYHELLO:
                  g_phello->SayHello();
                  break;

              case IDC_HELLODISPLAY:
                  switch(GET_WM_COMMAND_CMD(wParam, lParam))
                  {
                      TCHAR ach[100];

                      case EN_CHANGE:
                      // Update HelloMsg property of Hello object corresponding to this
                       // window when the contents of the edit control change.
                       GetDlgItemText(hwnd, IDC_HELLODISPLAY, ach, 100);
                       g_phello->put_HelloMessage(TO_OLE_STRING(ach));
                  }
                  return 0L;
                  default:
                      return 0L;
                  break;
          }
      }
      break;

      case WM_CLOSE:
         // Hide the window to release the refcount added by CoLockObjectExternal
         // (See CHello::ShowWindow)
         g_phello->ShowWindow(SW_HIDE);
         return DefDlgProc(hwnd, msg, wParam, lParam);
         break;

      case WM_DESTROY:
         PostQuitMessage(0);
         break;

      default:
         return DefDlgProc(hwnd, msg, wParam, lParam);
   }

   return NULL;
}

/*
 * LoadTypeInfo
 *
 *  Purpose:
 *   Gets type information of an object's interface from type library.
 *
 * Parameters:
 *  ppunkStdDispatch    Returns type information.
 *  clsid               Interface id of object in type library.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT LoadTypeInfo(ITypeInfo FAR* FAR* pptinfo, REFCLSID clsid)
{
    HRESULT hr;
    LPTYPELIB ptlib = NULL;
    LPTYPEINFO ptinfo = NULL;

    *pptinfo = NULL;

    // Load Type Library.
    hr = LoadRegTypeLib(LIBID_Hello, 2, 0, 0x09, &ptlib);
    if (FAILED(hr))
    {
        // if it wasn't registered, try to load it from the path
        // if this succeeds, it will have registered the type library for us
        // for the next time.
        hr = LoadTypeLib(OLESTR("hello.tlb"), &ptlib);
        if(FAILED(hr))
            return hr;
    }

    // Get type information for interface of the object.
    hr = ptlib->GetTypeInfoOfGuid(clsid, &ptinfo);
    if (FAILED(hr))
    {
        ptlib->Release();
        return hr;
    }

    ptlib->Release();
    *pptinfo = ptinfo;
    return NOERROR;
}

/*
 * RaiseException
 *
 * Parameters:
 *  nID                 Error number
 *  rguid               GUID of interface that is raising the exception.
 *
 * Return Value:
 *  HRESULT correspnding to the nID error number.
 *
 * Purpose:
 *  Fills the EXCEPINFO structure.
 *  Sets ErrorInfo object for vtable-binding controllers.
 *  For id-binding and late binding controllers DispInvoke
 *  will return DISP_E_EXCEPTION and fill the EXCEPINFO parameter
 *  with the error information set by SetErrorInfo.
 *
 */
HRESULT RaiseException(int nID, REFGUID rguid)
{
    extern SCODE g_scodes[];
    TCHAR szError[STR_LEN];
    ICreateErrorInfo *pcerrinfo;
    IErrorInfo *perrinfo;
    HRESULT hr;
    BSTR bstrDescription = NULL;

    if (LoadString(g_phello->m_hinst, nID, szError, sizeof(szError)))
        bstrDescription = SysAllocString(TO_OLE_STRING(szError));

    // Set ErrorInfo object so that vtable binding controller can get
    // rich error information. If the controller is using IDispatch
    // to access properties or methods, DispInvoke will fill the
    // EXCEPINFO structure using the values specified in the ErrorInfo
    // object and DispInvoke will return DISP_E_EXCEPTION. The property
    // or method must return a failure SCODE for DispInvoke to do this.
    hr = CreateErrorInfo(&pcerrinfo);
    if (SUCCEEDED(hr))
    {
       pcerrinfo->SetGUID(rguid);
       pcerrinfo->SetSource(g_phello->m_bstrProgID);
       if (bstrDescription)
           pcerrinfo->SetDescription(bstrDescription);
       hr = pcerrinfo->QueryInterface(IID_IErrorInfo, (LPVOID FAR*) &perrinfo);
       if (SUCCEEDED(hr))
       {
          SetErrorInfo(0, perrinfo);
          perrinfo->Release();
       }
       pcerrinfo->Release();
    }

    if (bstrDescription)
        SysFreeString(bstrDescription);
    return ResultFromScode(g_scodes[nID-1001]);
}

/*
 * Quick & Dirty ANSI/Unicode conversion routines. These routines use a static
 * buffer of fixed size to hold the converted string. Consequently these
 * routines are limited to strings of size STRCONVERT_MAXLEN. Also the same
 * buffer is reused when the routine is called a second time. So make sure
 * that the converted string is used before the conversion routine is called
 * again
 */
#ifdef WIN32

#ifndef UNICODE
char* ConvertToAnsi(OLECHAR FAR* szW)
{
  static char achA[STRCONVERT_MAXLEN];

  WideCharToMultiByte(CP_ACP, 0, szW, -1, achA, STRCONVERT_MAXLEN, NULL, NULL);
  return achA;
}

OLECHAR* ConvertToUnicode(char FAR* szA)
{
  static OLECHAR achW[STRCONVERT_MAXLEN];

  MultiByteToWideChar(CP_ACP, 0, szA, -1, achW, STRCONVERT_MAXLEN);
  return achW;
}
#endif

#endif


HELLO.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation Hello 2.0 Application.
**
**  hello.cpp
**
**  CHello implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "hello.h"

/*
 * CHello::Create
 *
 * Purpose:
 *  Creates an instance of the Hello Application object and initializes it.
 *
 * Parameters:
 *  hinst      HINSTANCE of application.
 *  lpszHelloMessage Initial Hello message.
 *  pphello    Returns Hello automation object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CHello::Create(HINSTANCE hinst, LPTSTR lpszHelloMessage, CHello FAR* FAR* pphello)
{
    TCHAR ach[STR_LEN];
    TCHAR achFullName[260];
    HRESULT hr;
    CHello FAR* phello = NULL;
    HWND hwnd;

    *pphello = NULL;

    // Create Hello Application object
    phello = new CHello();
    if (phello == NULL)
        goto error;

    // Create Main Window
    hwnd = CreateDialog(hinst, MAKEINTRESOURCE(IDD_MAINWINDOW), NULL, NULL);
    if (!hwnd)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }

    phello->m_cRef = 0;
    phello->m_bVisible = FALSE;
    phello->m_hwnd = hwnd;
    phello->m_hinst = hinst;

    // FullName
    GetModuleFileName(hinst, achFullName, sizeof(achFullName));
    phello->m_bstrFullName = SysAllocString(TO_OLE_STRING(achFullName));
    if (NULL == phello->m_bstrFullName)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }
    // Name
    LoadString(hinst, IDS_Name, ach, sizeof(ach));
    phello->m_bstrName = SysAllocString(TO_OLE_STRING(ach));
    if (NULL == phello->m_bstrName)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }
    // ProgID
    LoadString(hinst, IDS_ProgID, ach, sizeof(ach));
    phello->m_bstrProgID = SysAllocString(TO_OLE_STRING(ach));
    if (NULL == phello->m_bstrProgID)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }
    // Hello Message
    phello->m_bstrHelloMsg = SysAllocString(TO_OLE_STRING(lpszHelloMessage));
    if (NULL == phello->m_bstrHelloMsg)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }

    // Load type information from type library. If required, notify user on failure.
    hr = LoadTypeInfo(&phello->m_ptinfo, IID_IHello);
    if (FAILED(hr))
    {

        LoadString(hinst, IDS_ErrorLoadingTypeLib, ach, sizeof(ach));
        MessageBox(NULL, ach, FROM_OLE_STRING(phello->m_bstrName), MB_OK);
        goto error;
    }

    *pphello = phello;
    return NOERROR;

error:
    if (phello == NULL) return E_OUTOFMEMORY;
    if (phello->m_bstrFullName) SysFreeString(phello->m_bstrFullName);
    if (phello->m_bstrName) SysFreeString(phello->m_bstrName);
    if (phello->m_bstrProgID) SysFreeString(phello->m_bstrProgID);
    if (phello->m_bstrHelloMsg) SysFreeString(phello->m_bstrHelloMsg);
    if (phello->m_ptinfo) phello->m_ptinfo->Release();

    // Set to NULL to prevent destructor from attempting to free again
    phello->m_bstrFullName = NULL;
    phello->m_bstrName = NULL;
    phello->m_bstrProgID = NULL;
    phello->m_bstrHelloMsg = NULL;
    phello->m_ptinfo = NULL;

    delete phello;
    return hr;
}

/*
 * CHello::CHello
 *
 * Purpose:
 *  Constructor for CHello object. Initializes members to NULL.
 *
 */
#pragma warning (disable : 4355)
CHello::CHello() : m_SupportErrorInfo(this, IID_IHello)
#pragma warning (default : 4355)
{
    extern ULONG g_cObj;

    m_hwnd = NULL;
    m_bstrFullName = NULL;
    m_bstrName = NULL;
    m_bstrProgID = NULL;
    m_bstrHelloMsg = NULL;
    m_ptinfo = NULL;
    m_bVisible = 0;
}

/*
 * CHello::~CHello
 *
 * Purpose:
 *  Destructor for CHello object.
 *
 */
CHello::~CHello()
{
     extern ULONG g_cObj;

     if (m_bstrFullName) SysFreeString(m_bstrFullName);
     if (m_bstrName) SysFreeString(m_bstrName);
     if (m_bstrProgID) SysFreeString(m_bstrProgID);
     if (m_bstrHelloMsg) SysFreeString(m_bstrHelloMsg);
     if (m_ptinfo) m_ptinfo->Release();
     if (IsWindow(m_hwnd)) DestroyWindow(m_hwnd);
}

/*
 * CHello::QueryInterface, AddRef, Release
 *
 * Purpose:
 *  Implements IUnknown::QueryInterface, AddRef, Release
 *
 */
STDMETHODIMP
CHello::QueryInterface(REFIID iid, void FAR* FAR* ppv)
{
    *ppv = NULL;

    if (iid == IID_IUnknown || iid == IID_IDispatch || iid == IID_IHello  )
        *ppv = this;
    else if (iid == IID_ISupportErrorInfo)
        *ppv = &m_SupportErrorInfo;
    else return E_NOINTERFACE;

    AddRef();
    return NOERROR;
}

STDMETHODIMP_(ULONG)
CHello::AddRef(void)
{
#ifdef _DEBUG
    TCHAR ach[50];
    wsprintf(ach, TEXT("AddRef: Ref = %ld, Hello\r\n"), m_cRef+1);
    OutputDebugString(ach);
#endif

    return ++m_cRef;
}

STDMETHODIMP_(ULONG)
CHello::Release(void)
{
#ifdef _DEBUG
    TCHAR ach[50];
    wsprintf(ach, TEXT("Release: Ref = %ld, Hello\r\n"), m_cRef-1);
    OutputDebugString(ach);
#endif

    if(--m_cRef == 0)
    {
        delete this;
        return 0;
    }
    return m_cRef;
}

/*
 * CHello::GetTypeInfoCount
 *
 * Purpose:
 *  Implements IDispatch::GetTypeInfoCount.
 *
 */
STDMETHODIMP
CHello::GetTypeInfoCount(UINT FAR* pctinfo)
{
    *pctinfo = 1;
    return NOERROR;
}

/*
 * CHello::GetTypeInfo
 *
 * Purpose:
 *  Implements IDispatch::GetTypeInfo.
 *
 */
STDMETHODIMP
CHello::GetTypeInfo(
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo)
{
    *pptinfo = NULL;

    if(itinfo != 0)
        return DISP_E_BADINDEX;

    m_ptinfo->AddRef();
    *pptinfo = m_ptinfo;

    return NOERROR;
}

/*
 * CHello::GetIDsOfNames
 *
 * Purpose:
 *  Implements IDispatch::GetIDsOfNames.  The standard implementation, DispGetIDsOfNames,
 *  is used.
 *
 */
STDMETHODIMP
CHello::GetIDsOfNames(
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid)
{
    return DispGetIDsOfNames(m_ptinfo, rgszNames, cNames, rgdispid);
}

/*
 * CHello::Invoke
 *
 * Purpose:
 *  Implements IDispatch::Invoke.  The standard implementation, DispInvoke,
 *  is used.
 *
 */
STDMETHODIMP
CHello::Invoke(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr)
{
    return DispInvoke(
        this, m_ptinfo,
        dispidMember, wFlags, pdispparams,
        pvarResult, pexcepinfo, puArgErr);
}

/*
 * CHello::put_HelloMessage, get_HelloMessage, SayHello
 *
 * Purpose:
 *  Implements the standard Application, FullName, Name, Parent & Visible properties
 *  and the Quit method.
 *
 */
STDMETHODIMP
CHello::get_Application(IHello FAR* FAR* ppHello)
{
    HRESULT hr;

    *ppHello = NULL;

    hr = QueryInterface(IID_IDispatch, (void FAR* FAR*)ppHello);
    if (FAILED(hr))
        return RaiseException(IDS_Unexpected, IID_IHello);
    return hr;
}

STDMETHODIMP
CHello::get_FullName(BSTR FAR* pbstr)
{
    *pbstr = SysAllocString(m_bstrFullName);
    return NOERROR;
}

STDMETHODIMP
CHello::get_Name(BSTR FAR* pbstr)
{
    *pbstr = SysAllocString(m_bstrName);
    return NOERROR;
}

STDMETHODIMP
CHello::get_Parent(IHello FAR* FAR* ppHello)
{
    HRESULT hr;

    *ppHello = NULL;

    hr = QueryInterface(IID_IDispatch, (void FAR* FAR*)ppHello);
    if (FAILED(hr))
        return RaiseException(IDS_Unexpected, IID_IHello);
    return NOERROR;
}

STDMETHODIMP
CHello::put_Visible(VARIANT_BOOL bVisible)
{
    ShowWindow(bVisible ? SW_SHOW : SW_HIDE);
    return NOERROR;
}

STDMETHODIMP
CHello::get_Visible(VARIANT_BOOL FAR* pbool)
{
    *pbool =  m_bVisible;
    return NOERROR;
}

STDMETHODIMP
CHello::Quit()
{
    // CoDisconnectObject has no effect for an inproc server.  So the controller
    // will GP fault if it attempts to access the object (including calling IUnknown::Release())
    // after Quit has been called. For a local server, CoDisconnectObject will disconnect
    // the object from external connections. So the controller will get an RPC error if
    // it accesses the object after calling Quit and controller will not GP fault.
    CoDisconnectObject((LPUNKNOWN)this, 0);
    PostMessage(m_hwnd, WM_CLOSE, 0, 0L);
    return NOERROR;
}

/*
 * CHello::put_HelloMessage, get_HelloMessage, SayHello
 *
 * Purpose:
 *  Implements the HelloMessage property and the SayHello method.
 *
 */
STDMETHODIMP
CHello::put_HelloMessage(BSTR bstrMessage)
{
    SysReAllocString(&m_bstrHelloMsg, bstrMessage);
    return NOERROR;
}

STDMETHODIMP
CHello::get_HelloMessage(BSTR FAR* pbstrMessage)
{
    *pbstrMessage = SysAllocString(m_bstrHelloMsg);
    return NOERROR;
}

STDMETHODIMP
CHello::SayHello()
{
    SetDlgItemText(m_hwnd, IDC_HELLODISPLAY, FROM_OLE_STRING(m_bstrHelloMsg));
    return NOERROR;
}

STDMETHODIMP_(void)
CHello::ShowWindow(int nCmdShow)
{
    // Return if curently hidden and asked to hide or currently visible
    // and asked to show.
    if ((!m_bVisible && nCmdShow == SW_HIDE) || (m_bVisible && nCmdShow != SW_HIDE))
        return;

    m_bVisible = (nCmdShow == SW_HIDE) ? FALSE : TRUE;

    // The Automation object shutdown behavior is as follows:
    // 1. If the object application is visible, it shuts down only in response to an
    // explicit user command (File/Exit) or it's programmatic equivalent (for example
    // the Quit method of the Application object).
    // 2. If the object application is not visible, it goes away when it's last
    // object is released.
    //
    // CoLockObjectExternal can be used to increment the ref count of the application object
    // when it is visible. This will implement shutdown behavior 1. When the application
    // goes invisible, CoLockObjectExternal is used to decrement the ref count. This will
    // implement shutdown behavior 2.

    if (m_bVisible)
        CoLockObjectExternal(this, TRUE /*fLock*/, TRUE/*ignored when fLock==TRUE*/);
    else CoLockObjectExternal(this, FALSE/*fLock*/, TRUE/*fLastLockReleases*/);
    ::ShowWindow (m_hwnd, nCmdShow);
}

/*
 * ISupportErrorInfo implementation
 *
 */
CSupportErrorInfo::CSupportErrorInfo(IUnknown FAR* punkObject, REFIID riid)
{
    m_punkObject = punkObject;
    m_iid = riid;
}

STDMETHODIMP
CSupportErrorInfo::QueryInterface(REFIID iid, void FAR* FAR* ppv)
{
    return m_punkObject->QueryInterface(iid, ppv);
}

STDMETHODIMP_(ULONG)
CSupportErrorInfo::AddRef(void)
{
    return m_punkObject->AddRef();
}

STDMETHODIMP_(ULONG)
CSupportErrorInfo::Release(void)
{
    return m_punkObject->Release();
}

STDMETHODIMP
CSupportErrorInfo::InterfaceSupportsErrorInfo(REFIID riid)
{
    return (riid == m_iid) ? NOERROR : S_FALSE;
}


README.TXT

OLE Automation Server that Implements Collections

SUMMARY
=======

The LINES sample is an OLE Automation server application that implements
collections. LINES allows a collection on lines to be drawn on a pane using
OLE Automation. It exposes the following objects:

Application object
    Properties
        Application
        FullName
        Name
        Pane (Returns the pane in the drawing window)
        Parent
        Visible
    Methods
        CreateLine (Returns a new Line object with no start point or
          endpoint)
        CreatePoint (Returns a new Point object initialized to (0,0))

Pane object
    Properties
        Lines (Returns a collection of all Line objects in the drawing)
        Points (Returns a collection of all Point objects in the drawing)
        MaxX (Returns the maximum visible X-coordinate value, in twips)
        MaxY (Returns the maximum visible Y-coordinate value, in twips)
    Methods
        Clear (Removes all Lines and Points from the drawing and refreshes
                the client area. It is as if the application had just
                started)
        Refresh (Clears the drawing and redraws each line in the collection)

Line object
    Properties
        Color (An RGB color)
        EndPoint (A Point object indicating the endpoint for the line)
        StartPoint (A Point object indicating the start point for the line)
        Thickness (A thickness, in twips)

Point object
    Properties
        x
        y

Lines collection

  - Standard collection properties and methods. Objects that are added must
    be of type Line.
  - Objects returned are of type Line.
  - Add and Remove cause redrawing of the pane.
  - Adding a line causes two points to be added to the Points collection.
  - Removing a line can cause one or more points to be removed from the
    points collection.

Points collection

  - Standard collection properties and methods. Objects returned are of type
    Point.
  - This collection does not allow addition and removal of members.
  - The Points collection does not have duplicates. If there are two lines
    in the drawing that share an endpoint (x, y), (x, y) appears in the
    collection only once.

MORE INFORMATION
================

This sample implements the following features:

  - Dual interfaces that allow access to automation properties and methods
    through vtable binding and through IDispatch.
  - Rich Error information for vtable-binding controllers. This is done by
    implementing ISupportErrorInfo and using IErrorInfo.
  - Two collections.
  - Active object registration, using RegisterActiveObject and
    RevokeActiveObject.
  - Correct shutdown behavior.
  - A registry file that contains Lines.Application as progID.
  - Initial invisibility.

To compile:
-----------

To compile an ANSI debug version of the sample for Windows NT and Windows 95,
use the following command:

   nmake

To compile a Unicode debug version of the sample for Windows NT only, use the
following command:

   nmake dev=win32 HOST=NT

To compile a 16-bit debug version of the sample and if your development
environment is Win 3.x, use the following command. If your development
environment is Win95 or NT, change the HOST appropriately.

   nmake dev=win16 HOST=DOS

Also modify the .REG file to use 16 bit version of the keys. For example
use LocalServer instead of LocalServer32.

To clean up between builds which use different options, use one of the
following commands:

   nmake clean
   nmake dev=win16 clean

See the makefile header comments for other make options.

If one of the build tools cannot find SDKUTIL.LIB, use the SAMPLES\COM\COMMON
sample to build SDKUTIL.LIB.

Visual C++ 2.x and above can use makefile as an external makefile
after making the following replacement in the makefile:

   !include <$(MSTOOLS)\samples\com\include\olesampl.mak>

with

   !include <win32.mak>

To run:
-------

Change LINES.REG to provide the full path of LINES.EXE and LINES.TLB.
Register lines.reg in the registration database by double-clicking it.

The VB 4.0 files vb.frm & vb.vbp in this directory will control the sample.
Load vb.vbp into VB 4.0, reference the lines type library using
VB's Tools/References menu and run. This will launch and control LINES.

Files
------

LINES.ODL  Object description language that describes the objects LINES
exposes.

TLB.H  Header file generated by mktyplib.exe

MAKEFILE  Makefile for project.

Other files implement the LINES automation server.


APPCF.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Simple InProc Automation Object.
**
**  Applicationcf.cpp
**
**  CApplicationCF (class factory) implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/
#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "lines.h"

CApplicationCF::CApplicationCF(void)
{
    m_cRef = 0;
}

/*
 * CApplicationCF::QueryInterface, AddRef, Release
 *
 * Purpose:
 *  Implements IUnknown::QueryInterface, AddRef, Release
 *
 */
STDMETHODIMP
CApplicationCF::QueryInterface(REFIID iid, void FAR* FAR* ppv)
{
    *ppv = NULL;

    if (iid == IID_IUnknown || iid == IID_IClassFactory)
        *ppv = this;
    else
        return E_NOINTERFACE;

    AddRef();
    return NOERROR;
}

STDMETHODIMP_(ULONG)
CApplicationCF::AddRef(void)
{
    return ++m_cRef;
}

STDMETHODIMP_(ULONG)
CApplicationCF::Release(void)
{
    if(--m_cRef == 0)
    {
        delete this;
        return 0;
    }
    return m_cRef;
}

/*
 * CApplicationCF::CreateInstance, LockServer
 *
 * Purpose:
 *  Implements IClassFactory::CreateInstance, LockServer
 *
 */
STDMETHODIMP
CApplicationCF::CreateInstance(IUnknown FAR* punkOuter,
                         REFIID riid,
                         void FAR* FAR* ppv)
{
    HRESULT hr;

    *ppv = NULL;

    // This implementation does'nt allow aggregation
    if (punkOuter)
        return CLASS_E_NOAGGREGATION;

    // This is REGCLS_SINGLEUSE class factory, so CreateInstance will be
    // called atmost once. An application objects has a REGCLS_SINGLEUSE class
    // factory. The global application object has already been created when
    // CreateInstance is called. A REGCLS_MULTIPLEUSE class factory's
    // CreateInstance would be called multiple times and would create a new
    // object each time. An MDI application would have a REGCLS_MULTIPLEUSE
    // class factory for it's document objects.
    hr = g_pApplication->QueryInterface(riid, ppv);
    if (FAILED(hr))
    {
        g_pApplication->Quit();
        return hr;
    }
    return NOERROR;
}

STDMETHODIMP
CApplicationCF::LockServer(BOOL fLock)
{
    CoLockObjectExternal(g_pApplication, fLock, TRUE);
    return NOERROR;
}


ENUMVAR.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation Lines Object.
**
**  enumvar.cpp
**
**  CEnumVariant implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "lines.h"

/*
 * CEnumVariant::Create
 *
 * Purpose:
 *  Creates an instance of the IEnumVARIANT enumerator object and initializes it.
 *
 * Parameters:
 *  psa        Safe array containing items to be enumerated.
 *  cElements  Number of items to be enumerated.
 *  ppenumvariant    Returns enumerator object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CEnumVariant::Create(SAFEARRAY FAR* psa, ULONG cElements, CEnumVariant FAR* FAR* ppenumvariant)
{
    HRESULT hr;
    CEnumVariant FAR* penumvariant = NULL;
    long lLBound;

    *ppenumvariant = NULL;

    penumvariant = new CEnumVariant();
    if (penumvariant == NULL)
        goto error;

    penumvariant->m_cRef = 0;

    // Copy elements into safe array that is used in enumerator implemenatation and
    // initialize state of enumerator.
    hr = SafeArrayGetLBound(psa, 1, &lLBound);
    if (FAILED(hr))
        goto error;
    penumvariant->m_cElements = cElements;
    penumvariant->m_lLBound = lLBound;
    penumvariant->m_lCurrent = lLBound;
    hr = SafeArrayCopy(psa, &penumvariant->m_psa);
    if (FAILED(hr))
       goto error;

    *ppenumvariant = penumvariant;
    return NOERROR;

error:
    if (penumvariant == NULL)
        return E_OUTOFMEMORY;

    if (penumvariant->m_psa)
        SafeArrayDestroy(penumvariant->m_psa);
    penumvariant->m_psa = NULL;
    delete penumvariant;
    return hr;
}

/*
 * CEnumVariant::CEnumVariant
 *
 * Purpose:
 *  Constructor for CEnumVariant object. Initializes members to NULL.
 *
 */
CEnumVariant::CEnumVariant()
{
    m_psa = NULL;
}

/*
 * CEnumVariant::~CEnumVariant
 *
 * Purpose:
 *  Destructor for CEnumVariant object.
 *
 */
CEnumVariant::~CEnumVariant()
{
    if (m_psa) SafeArrayDestroy(m_psa);
}

/*
 * CEnumVariant::QueryInterface, AddRef, Release
 *
 * Purpose:
 *  Implements IUnknown::QueryInterface, AddRef, Release
 *
 */
STDMETHODIMP
CEnumVariant::QueryInterface(REFIID iid, void FAR* FAR* ppv)
{
    *ppv = NULL;

    if (iid == IID_IUnknown || iid == IID_IEnumVARIANT)
        *ppv = this;
    else return E_NOINTERFACE;

    AddRef();
    return NOERROR;
}

STDMETHODIMP_(ULONG)
CEnumVariant::AddRef(void)
{

#ifdef _DEBUG
    TCHAR ach[50];
    wsprintf(ach, TEXT("Ref = %ld, Enum\r\n"), m_cRef+1);
    OutputDebugString(ach);
#endif

    return ++m_cRef;  // AddRef Application Object if enumerator will outlive application object
}

STDMETHODIMP_(ULONG)
CEnumVariant::Release(void)
{

#ifdef _DEBUG
    TCHAR ach[50];
    wsprintf(ach, TEXT("Ref = %ld, Enum\r\n"), m_cRef-1);
    OutputDebugString(ach);
#endif

    if(--m_cRef == 0)
    {
        delete this;
        return 0;
    }
    return m_cRef;
}

/*
 * CEnumVariant::Next
 *
 * Purpose:
 *  Retrieves the next cElements elements. Implements IEnumVARIANT::Next.
 *
 */
STDMETHODIMP
CEnumVariant::Next(ULONG cElements, VARIANT FAR* pvar, ULONG FAR* pcElementFetched)
{
    HRESULT hr;
    ULONG l;
    long l1;
    ULONG l2;

    if (pcElementFetched != NULL)
        *pcElementFetched = 0;

    // Retrieve the next cElements elements.
    for (l1=m_lCurrent, l2=0; l1<(long)(m_lLBound+m_cElements) && l2<cElements; l1++, l2++)
    {
       hr = SafeArrayGetElement(m_psa, &l1, &pvar[l2]);
       if (FAILED(hr))
           goto error;
    }
    // Set count of elements retrieved
    if (pcElementFetched != NULL)
        *pcElementFetched = l2;
    m_lCurrent = l1;

    return  (l2 < cElements) ? S_FALSE : NOERROR;

error:
    for (l=0; l<cElements; l++)
        VariantClear(&pvar[l]);
    return hr;
}

/*
 * CEnumVariant::Skip
 *
 * Purpose:
 *  Skips the next cElements elements. Implements IEnumVARIANT::Skip.
 *
 */
STDMETHODIMP
CEnumVariant::Skip(ULONG cElements)
{
    m_lCurrent += cElements;
    if (m_lCurrent > (long)(m_lLBound+m_cElements))
    {
        m_lCurrent =  m_lLBound+m_cElements;
        return S_FALSE;
    }
    else return NOERROR;
}

/*
 * CEnumVariant::Reset
 *
 * Purpose:
 *  Resets the current element in the enumerator to the beginning. Implements IEnumVARIANT::Reset.
 *
 */
STDMETHODIMP
CEnumVariant::Reset()
{
    m_lCurrent = m_lLBound;
    return NOERROR;
}

/*
 * CEnumVariant::Clone
 *
 * Purpose:
 *  Creates a copy of the current enumeration state. Implements IEnumVARIANT::Clone.
 *
 */
STDMETHODIMP
CEnumVariant::Clone(IEnumVARIANT FAR* FAR* ppenum)
{
    CEnumVariant FAR* penum = NULL;
    HRESULT hr;

    *ppenum = NULL;

    hr = CEnumVariant::Create(m_psa, m_cElements, &penum);
    if (FAILED(hr))
        goto error;
    penum->AddRef();
    penum->m_lCurrent = m_lCurrent;

    *ppenum = penum;
    return NOERROR;

error:
    if (penum)
        penum->Release();
    return hr;
}


ERRINFO.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation Lines Object.
**
**  errinfo.cpp
**
**  CSupportErrorInfo implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "lines.h"

CSupportErrorInfo::CSupportErrorInfo(IUnknown FAR* punkObject, REFIID riid)
{
    m_punkObject = punkObject;
    m_iid = riid;
}

STDMETHODIMP
CSupportErrorInfo::QueryInterface(REFIID iid, void FAR* FAR* ppv)
{
    return m_punkObject->QueryInterface(iid, ppv);
}

STDMETHODIMP_(ULONG)
CSupportErrorInfo::AddRef(void)
{
    return m_punkObject->AddRef();
}

STDMETHODIMP_(ULONG)
CSupportErrorInfo::Release(void)
{
    return m_punkObject->Release();
}

STDMETHODIMP
CSupportErrorInfo::InterfaceSupportsErrorInfo(REFIID riid)
{
    return (riid == m_iid) ? NOERROR : S_FALSE;
}


LINE.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation Lines Object.
**
**  line.cpp
**
**  CLine implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "lines.h"

/*
 * CLine::Create
 *
 * Purpose:
 *  Creates an instance of the Line automation object and initializes it.
 *
 * Parameters:
 *  ppLine    Returns Line automation object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CLine::Create(CLine FAR* FAR* ppLine )
{
    HRESULT hr;
    CLine FAR* pLine = NULL;

    *ppLine = NULL;

    pLine = new CLine();
    if (pLine == NULL)
        goto error;

    // Load type information for the line from type library.
    hr = LoadTypeInfo(&pLine->m_ptinfo, IID_ILine);
    if (FAILED(hr))
        goto error;

    *ppLine = pLine;
    return NOERROR;

error:
    if (pLine == NULL)
        return E_OUTOFMEMORY;

    if (pLine->m_ptinfo)
        pLine->m_ptinfo->Release();

    // Set to NULL to prevent destructor from attempting to free again
    pLine->m_ptinfo = NULL;

    delete pLine;
    return hr;
}

/*
 * CLine::CLine
 *
 * Purpose:
 *  Constructor for CLine object. Initializes members to NULL.
 *
 */
#pragma warning (disable : 4355)
CLine::CLine() : m_SupportErrorInfo(this, IID_ILine)
#pragma warning (default : 4355)
{
    m_ptinfo = NULL;
    m_pPointStart = NULL;
    m_pPointEnd = NULL;
    m_nThickness = 0;
    m_colorref = 0;
    m_cRef = 0;
}

/*
 * CLine::~CLine
 *
 * Purpose:
 *  Destructor for CLine object.
 *
 */
CLine::~CLine()
{
     if (m_ptinfo) m_ptinfo->Release();
     if (m_pPointStart) m_pPointStart->Release();
     if (m_pPointEnd) m_pPointEnd->Release();
}

/*
 * CLine::QueryInterface, AddRef, Release
 *
 * Purpose:
 *  Implements IUnknown::QueryInterface, AddRef, Release
 *
 */
STDMETHODIMP
CLine::QueryInterface(REFIID iid, void FAR* FAR* ppv)
{
    *ppv = NULL;

    if (iid == IID_IUnknown || iid == IID_IDispatch || iid == IID_ILine)
        *ppv = this;
    else if (iid == IID_ISupportErrorInfo)
        *ppv = &m_SupportErrorInfo;
    else return E_NOINTERFACE;

    AddRef();
    return NOERROR;
}

STDMETHODIMP_(ULONG)
CLine::AddRef(void)
{

#ifdef _DEBUG
    TCHAR ach[50];
    wsprintf(ach, TEXT("Ref = %ld, Line\r\n"), m_cRef+1);
    OutputDebugString(ach);
#endif

    return ++m_cRef;
}

STDMETHODIMP_(ULONG)
CLine::Release(void)
{
#ifdef _DEBUG
    TCHAR ach[50];
    wsprintf(ach, TEXT("Ref = %ld, Line\r\n"), m_cRef-1);
    OutputDebugString(ach);
#endif

    if(--m_cRef == 0)
    {
        delete this;
        return 0;
    }
    return m_cRef;
}

/*
 * CLine::GetTypeInfoCount
 *
 * Purpose:
 *  Implements IDispatch::GetTypeInfoCount.
 *
 */
STDMETHODIMP
CLine::GetTypeInfoCount(UINT FAR* pctinfo)
{
    *pctinfo = 1;
    return NOERROR;
}

/*
 * CLine::GetTypeInfo
 *
 * Purpose:
 *  Implements IDispatch::GetTypeInfo.
 *
 */
STDMETHODIMP
CLine::GetTypeInfo(
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo)
{
    *pptinfo = NULL;

    if(itinfo != 0)
        return DISP_E_BADINDEX;

    m_ptinfo->AddRef();
    *pptinfo = m_ptinfo;

    return NOERROR;
}

/*
 * CLine::GetIDsOfNames
 *
 * Purpose:
 *  Implements IDispatch::GetIDsOfNames.  The standard implementation, DispGetIDsOfNames,
 *  is used.
 *
 */
STDMETHODIMP
CLine::GetIDsOfNames(
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid)
{
    return DispGetIDsOfNames(m_ptinfo, rgszNames, cNames, rgdispid);
}

/*
 * CLine::Invoke
 *
 * Purpose:
 *  Implements IDispatch::Invoke.  The standard implementation, DispInvoke,
 *  is used.
 *
 */
STDMETHODIMP
CLine::Invoke(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr)
{
    // VB 3.0/Disptest have a bug in which they pass DISPATCH_PROPERTYPUT
    // instead of DISPATCH_PROPERTYPUTREF for the EndPoint and StartPoint
    // properties. Future versions of VB will correctly pass DISPATCH_PROPERTYPUTREF.
    // EndPoint has DISPID == 1 and StartPoint has DISPID == 2 - see lines.odl.
    // The following code works around the VB 3.0/Disptest bug.
    if ((dispidMember == 1 || dispidMember == 2)
        && wFlags == DISPATCH_PROPERTYPUT)
        wFlags = DISPATCH_PROPERTYPUTREF;

    return DispInvoke(
        this, m_ptinfo,
        dispidMember, wFlags, pdispparams,
        pvarResult, pexcepinfo, puArgErr);
}

/*
 *
 * Properties and methods exposed through automation.
 *
 */
STDMETHODIMP
CLine::get_Color(long FAR* plColorref)
{
    *plColorref =  m_colorref;
    return NOERROR;
}

STDMETHODIMP
CLine::put_Color(long lColorref)
{
    m_colorref = (COLORREF)lColorref;
    return NOERROR;
}

STDMETHODIMP
CLine::get_EndPoint(IPoint FAR* FAR* ppPoint)
{
    HRESULT hr;

    *ppPoint = NULL;

    if (NULL == m_pPointEnd)
        return RaiseException(IDS_NoStartPoint, IID_ILine);

    hr = m_pPointEnd->QueryInterface(IID_IDispatch, (void FAR* FAR*)ppPoint);
    if (FAILED(hr))
        return RaiseException(IDS_Unexpected, IID_ILine);
    return NOERROR;
}

STDMETHODIMP
CLine::putref_EndPoint(IPoint FAR* pPointNew)
{
    HRESULT hr;
    CPoint FAR* pPoint;

    // Save the IPoint interface so we can easily access private data
    hr = pPointNew->QueryInterface(IID_IPoint, (void FAR* FAR*)&pPoint);
    if (FAILED(hr))
        return RaiseException(IDS_PointFromOtherInstance, IID_ILine);

    // Replace the old point with the new
    if (m_pPointEnd)
       m_pPointEnd->Release();

    m_pPointEnd = pPoint;
    return NOERROR;
}

STDMETHODIMP
CLine::get_StartPoint(IPoint FAR* FAR* ppPoint)
{
    HRESULT hr;

    *ppPoint = NULL;

    if (NULL == m_pPointStart)
        return RaiseException(IDS_NoStartPoint, IID_ILine);

    hr = m_pPointStart->QueryInterface(IID_IDispatch, (void FAR* FAR*)ppPoint);
    if (FAILED(hr))
        return RaiseException(IDS_Unexpected, IID_ILine);

    return NOERROR;
}

STDMETHODIMP
CLine::putref_StartPoint(IPoint FAR* pPointNew)
{
    HRESULT hr;
    CPoint FAR* pPoint;

    // Save the IPoint interface so we can easily access private data
    hr = pPointNew->QueryInterface(IID_IPoint, (void FAR* FAR*)&pPoint);
    if (FAILED(hr))
        return RaiseException(IDS_PointFromOtherInstance, IID_ILine);

    // Replace the old point with the new
    if (m_pPointStart)
       m_pPointStart->Release();

    m_pPointStart = pPoint;
    return NOERROR;
}

STDMETHODIMP
CLine::get_Thickness(int FAR* pnThickness)
{
    *pnThickness = m_nThickness;
    return NOERROR;
}

STDMETHODIMP
CLine::put_Thickness(int nThickness)
{
    m_nThickness = nThickness;
    return NOERROR;
}

/*
 *
 * The following methods are not exposed through Automation
 *
 */

STDMETHODIMP_(void)
CLine::Draw(HDC hdc)
{
    HPEN hpen, hpenOld;

    if (m_pPointStart == NULL)
        return;
    if (m_pPointEnd == NULL)
        return;

    hpen = CreatePen(PS_SOLID, m_nThickness, m_colorref);
    hpenOld = (HPEN) (HGDIOBJ)SelectObject(hdc, (HGDIOBJ)hpen);

    MoveToEx(hdc, m_pPointStart->get_x(), m_pPointStart->get_y(), NULL);
    LineTo(hdc, m_pPointEnd->get_x(), m_pPointEnd->get_y());

    SelectObject(hdc, (HGDIOBJ)hpenOld);
    DeleteObject((HGDIOBJ)hpen);
    return;
}

/*
 * CLine::GetInvalidateRect
 *
 * Purpose:
 *  Get the rectangle that cicumscribes the line. This rectangle is used to invalidate the area
 *  of the window where the line is to be draw or erased.
 *
 */
STDMETHODIMP_(void)
CLine::GetInvalidateRect(LPRECT prc)
{
    int nX1, nY1, nX2, nY2;

    nX1 = m_pPointStart->get_x();
    nY1 = m_pPointStart->get_y();

    nX2 = m_pPointEnd->get_x();
    nY2 = m_pPointEnd->get_y();

    if (nX1 < nX2)
    {
        prc->left = nX1;
        prc->right = nX2;
    }
    else
    {
        prc->left = nX2;
        prc->right = nX1;
    }
    if (nY1 < nY2)
    {
        prc->top = nY1;
        prc->bottom = nY2;
    }
    else
    {
        prc->top = nY2;
        prc->bottom = nY1;
    }

    InflateRect(prc, m_nThickness, m_nThickness);
}

STDMETHODIMP_(BOOL)
CLine::AddEndPointsToPane(CPane FAR* pPane)
{
    // Does the line have end points?
    if (NULL == m_pPointStart)
       return FALSE;
    if (NULL == m_pPointEnd)
       return FALSE;

    // Add points to Point collection in the Pane object.
    if (pPane->AddPoint(m_pPointStart))
        return pPane->AddPoint(m_pPointEnd);
    return FALSE;

}

STDMETHODIMP_(void)
CLine::RemoveEndPointsFromPane(CPane FAR* pPane)
{
    // Remove the end points of the line to be removed from the Points collection in the Pane.
    if (NULL != m_pPointStart)
        pPane->RemovePoint(m_pPointStart);
    if (NULL != m_pPointEnd)
       pPane->RemovePoint(m_pPointEnd);

}


LINES.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation Lines object
**
**  lines.cpp
**
**  CLines collection implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "lines.h"

/*
 * CLines::Create
 *
 * Purpose:
 *  Creates an instance of a Lines collection object and initializes it.
 *
 * Parameters:
 *  lMaxSize   Maximum number of items that can added to collection.
 *  lLBound    Lower bound of index of collection.
 *  pPane      Pointer to pane that contains this collection. Required because the pane coordinates
 *             the lines and points collection.
 *  ppLines    Returns Lines collection object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CLines::Create(ULONG lMaxSize, long lLBound, CPane FAR* pPane, CLines FAR* FAR* ppLines)
{
    HRESULT hr;
    CLines FAR* pLines = NULL;
    SAFEARRAYBOUND sabound[1];

    *ppLines = NULL;

    // Create new collection
    pLines = new CLines();
    if (pLines == NULL)
        goto error;

    pLines->m_cMax = lMaxSize;
    pLines->m_cElements = 0;
    pLines->m_lLBound = lLBound;
    pLines->m_pPane = pPane;

    // Load type information for the Lines collection from type library.
    hr = LoadTypeInfo(&pLines->m_ptinfo, IID_ILines);
    if (FAILED(hr))
        goto error;

    // Create a safe array of variants which is used to implement the collection.
    sabound[0].cElements = lMaxSize;
    sabound[0].lLbound = lLBound;
    pLines->m_psa = SafeArrayCreate(VT_VARIANT, 1, sabound);
    if (pLines->m_psa == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }

    *ppLines = pLines;
    return NOERROR;

error:
    if (pLines == NULL)
        return E_OUTOFMEMORY;
    if (pLines->m_ptinfo)
        pLines->m_ptinfo->Release();
    if (pLines->m_psa)
        SafeArrayDestroy(pLines->m_psa);

    pLines->m_psa = NULL;
    pLines->m_ptinfo = NULL;

    delete pLines;
    return hr;
}

/*
 * CLines::CLines
 *
 * Purpose:
 *  Constructor for CLines object. Initializes members to NULL.
 *
 */
#pragma warning (disable : 4355)
CLines::CLines() : m_SupportErrorInfo(this, IID_ILines)
#pragma warning (default : 4355)
{
    m_cRef = 0;
    m_psa = NULL;
    m_ptinfo = NULL;
}

/*
 * CLines::~CLines
 *
 * Purpose:
 *  Destructor for CLines object.
 *
 */
CLines::~CLines()
{
     if (m_ptinfo) m_ptinfo->Release();
     if (m_psa) SafeArrayDestroy(m_psa);
}

/*
 * CLines::QueryInterface, AddRef, Release
 *
 * Purpose:
 *  Implements IUnknown::QueryInterface, AddRef, Release
 *
 */
STDMETHODIMP
CLines::QueryInterface(REFIID iid, void FAR* FAR* ppv)
{
    *ppv = NULL;

    if (iid == IID_IUnknown || iid == IID_ILines || iid == IID_IDispatch)
        *ppv = this;
    else if (iid == IID_ISupportErrorInfo)
        *ppv = &m_SupportErrorInfo;
    else return E_NOINTERFACE;

    AddRef();
    return NOERROR;
}

STDMETHODIMP_(ULONG)
CLines::AddRef(void)
{

#ifdef _DEBUG
    TCHAR ach[50];
    wsprintf(ach, TEXT("Ref = %ld, Lines\r\n"), m_cRef+1);
    OutputDebugString(ach);
#endif

    return ++m_cRef;
}

STDMETHODIMP_(ULONG)
CLines::Release(void)
{

#ifdef _DEBUG
    TCHAR ach[50];
    wsprintf(ach, TEXT("Ref = %ld, Lines\r\n"), m_cRef-1);
    OutputDebugString(ach);
#endif

    if(--m_cRef == 0)
    {
        delete this;
        return 0;
    }
    return m_cRef;
}

/*
 * CLines::GetTypeInfoCount
 *
 * Purpose:
 *  Implements IDispatch::GetTypeInfoCount.
 *
 */
STDMETHODIMP
CLines::GetTypeInfoCount(UINT FAR* pctinfo)
{
    *pctinfo = 1;
    return NOERROR;
}

/*
 * CLines::GetTypeInfo
 *
 * Purpose:
 *  Implements IDispatch::GetTypeInfo.
 *
 */
STDMETHODIMP
CLines::GetTypeInfo(
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo)
{
    *pptinfo = NULL;

    if(itinfo != 0)
        return DISP_E_BADINDEX;

    m_ptinfo->AddRef();
    *pptinfo = m_ptinfo;

    return NOERROR;
}

/*
 * CLines::GetIDsOfNames
 *
 * Purpose:
 *  Implements IDispatch::GetIDsOfNames.  The standard implementation, DispGetIDsOfNames,
 *  is used.
 *
 */
STDMETHODIMP
CLines::GetIDsOfNames(
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid)
{
    return DispGetIDsOfNames(m_ptinfo, rgszNames, cNames, rgdispid);
}

/*
 * CLines::Invoke
 *
 * Purpose:
 *  Implements IDispatch::Invoke.  The standard implementation, DispInvoke,
 *  is used.
 *
 */
STDMETHODIMP
CLines::Invoke(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr)
{
    return DispInvoke(
        this, m_ptinfo,
        dispidMember, wFlags, pdispparams,
        pvarResult, pexcepinfo, puArgErr);
}

/*
 *
 * Properties and methods exposed through automation.
 *
 */

/*
 * CLines::Add
 *
 * Purpose:
 *  Adds a line to the lines collection.
 *
 * Parameters:
 *  pLineNew    IDispatch of line to be added to collection.
 *
 */
STDMETHODIMP
CLines::Add(ILine FAR* pLineNew)
{
    HRESULT hr;
    LONG l;
    CLine FAR* pLine = NULL;
    VARIANT v;
    HDC hdc;

    // Is the collection full?
    if (m_cElements == m_cMax)
        return RaiseException(IDS_CollectionFull, IID_ILines);

    hr = pLineNew->QueryInterface(IID_ILine, (void FAR* FAR*)&pLine);
    if (FAILED(hr))
        return RaiseException(IDS_LineFromOtherInstance, IID_ILines);

    // Add end points of line to the pane's point collection
    if (FALSE == pLine->AddEndPointsToPane(m_pPane))
        {hr = RaiseException(IDS_CantAddEndPoints, IID_ILines); goto error;}

    // Add new line to collection
    l = m_lLBound+m_cElements;
    VariantInit(&v);
    V_VT(&v) = VT_DISPATCH;
    V_DISPATCH(&v) = pLineNew;
    hr = SafeArrayPutElement(m_psa, &l, &v);
    if (FAILED(hr))
        {hr = RaiseException(IDS_Unexpected, IID_ILines); goto error;}
    m_cElements++;

    // Draw the new line
    hdc = m_pPane->GetDC();
    pLine->Draw(hdc);
    m_pPane->ReleaseDC(hdc);

    pLine->Release();
    return NOERROR;

error:
    if (pLine)
        pLine->Release();
    return hr;
}

/*
 * CLines::get_Count
 *
 * Purpose:
 *  Returns number of items in collection.
 *
 */
STDMETHODIMP
CLines::get_Count(long FAR* lCount)
{
    *lCount = m_cElements;
    return NOERROR;
}

/*
 * CLines::get_Item
 *
 * Purpose:
 *  Retrieves item from collection, given an index.
 *
 * Parameters:
 *   lIndex   Index of item to be retrieved.
 *   ppLine   Returns IDispatch of item retrieved from collection.
 *
 */
STDMETHODIMP
CLines::get_Item(long lIndex, ILine FAR* FAR* ppLine)
{
    HRESULT hr;
    VARIANT v;

    // Check if index is within range
    if (lIndex < m_lLBound || lIndex >= (long)(m_lLBound+m_cElements))
        return RaiseException(IDS_InvalidIndex, IID_ILines);

    // Retrieve and return item. Note that SafeArrayGetElement AddRefs, so an additional AddRef
    // is not required.
    VariantInit(&v);
    hr = SafeArrayGetElement(m_psa, &lIndex, &v);
    if (FAILED(hr))
        return RaiseException(IDS_Unexpected, IID_ILines);
    *ppLine = (ILine FAR*) V_DISPATCH(&v);
    return NOERROR;
}

/*
 * CLines::get_NewEnum
 *
 * Purpose:
 *  Returns an enumerator (IEnumVARIANT) for the items curently in the collection.
 *  The NewEnum property is restricted and so is invisible to users of an
 *  automation controller's scripting language. Automation controllers that support
 *  a 'For Each' statement to iterate through the elements of a collection will use
 *  the enumerator returned by NewEnum. The enumerator creates a snapshot of the
 *  the current state of the collection.
 *
 */
STDMETHODIMP
CLines::get__NewEnum(IUnknown FAR* FAR* ppunkEnum)
{
    CEnumVariant FAR* penum = NULL;;
    HRESULT hr;

    *ppunkEnum = NULL;

    // Create new enumerator for items currently in collection and QI for IUnknown
    hr = CEnumVariant::Create(m_psa, m_cElements, &penum);
    if (FAILED(hr))
        {hr = RaiseException(IDS_OutOfMemory, IID_ILines); goto error;}
    hr = penum->QueryInterface(IID_IUnknown, (VOID FAR* FAR*)ppunkEnum);
    if (FAILED(hr))
        {hr = RaiseException(IDS_Unexpected, IID_ILines); goto error;}
    return NOERROR;

error:
    if (penum)
        delete penum;
    return hr;
}

/*
 * CLines::Remove
 *
 * Purpose:
 *  Removes specified item from collection.
 *
 * Parameters:
 *   lIndex   Index of item to be removed.
 *
 */
STDMETHODIMP
CLines::Remove(long lIndex)
{
    HRESULT hr;
    long l;
    VARIANT HUGEP *pvar;
    CLine FAR* pLine = NULL;
    RECT rc;

    // Check if integer index is within range.
    if (lIndex < m_lLBound || lIndex >= (long)(m_lLBound+m_cElements))
        return RaiseException(IDS_InvalidIndex, IID_ILines);

    hr = SafeArrayAccessData(m_psa, (void HUGEP* FAR*)&pvar);
    if (FAILED(hr))
        return RaiseException(IDS_Unexpected, IID_ILines);
    V_DISPATCH(&pvar[lIndex-m_lLBound])->QueryInterface(IID_ILine, (void FAR* FAR*)&pLine);

    // Ask the pane to invalidate the area where the line is drawn.
    pLine->GetInvalidateRect(&rc);
    m_pPane->InvalidateRect(&rc);

    // Remove end points of line from the pane's point collection.
    pLine->RemoveEndPointsFromPane(m_pPane);
    // Remove Line
    V_DISPATCH(&pvar[lIndex-m_lLBound])->Release();
    // Move up the array elements, after the element to be removed.
    for (l=lIndex-m_lLBound; l<(long)(m_cElements-1); l++)
        pvar[l] = pvar[l+1];
    // Remove last element.
    V_VT(&pvar[l]) = VT_EMPTY;

    pLine->Release();
    hr = SafeArrayUnaccessData(m_psa);
    if (FAILED(hr))
       return RaiseException(IDS_Unexpected, IID_ILines);

    m_cElements--;
    m_pPane->Update();     // Ask the pane to repaint invalidated areas caused by removal of line.
    return NOERROR;
}

/*
 *
 * The following methods are not exposed through Automation
 *
 */

/*
 * CLines::Draw
 *
 * Purpose:
 *  Draws all lines in collection.
 *
 */
STDMETHODIMP_(void)
CLines::Draw(HDC hdc)
{
    HRESULT hr;
    long l;
    CLine FAR* pLine = NULL;
    VARIANT HUGEP *pvar;

    // Draw each line in the Lines collection
    hr = SafeArrayAccessData(m_psa, (void HUGEP* FAR*)&pvar);
    if (FAILED(hr))
       return;
    for (l=0; l<(long)m_cElements; l++)
    {
        hr = V_DISPATCH(&pvar[l])->QueryInterface(IID_ILine, (void FAR* FAR*)&pLine);
        if (FAILED(hr))
            continue;
        pLine->Draw(hdc);
        pLine->Release();
    }
    hr = SafeArrayUnaccessData(m_psa);
    if (FAILED(hr))
        return;
    return;
}

/*
 * CLines::Clear
 *
 * Purpose:
 *  Removes all items from collection.
 *
 */
STDMETHODIMP_(void)
CLines::Clear(void)
{
    SafeArrayDestroyData(m_psa);
    SafeArrayAllocData(m_psa);
    m_cElements = 0;
}


LINES.H

#ifdef WIN32

#ifdef UNICODE
    #define FROM_OLE_STRING(str) str
    #define TO_OLE_STRING(str) str
#else
    #define FROM_OLE_STRING(str) ConvertToAnsi(str)
    char* ConvertToAnsi(OLECHAR FAR* szW);
    #define TO_OLE_STRING(str) ConvertToUnicode(str)
    OLECHAR* ConvertToUnicode(char FAR* szA);
    // Maximum length of string that can be converted between Ansi & Unicode
    #define STRCONVERT_MAXLEN 300
#endif

#else  // WIN16
  #define APIENTRY far pascal
  #define TCHAR char
  #define TEXT(sz) sz
  #define FROM_OLE_STRING(str) str
  #define TO_OLE_STRING(str) str
  #define LPTSTR LPSTR

  // Windows NT defines the following in windowsx.h
  #define GET_WM_COMMAND_ID(w,l) (w)
  #define GET_WM_COMMAND_CMD(w,l) HIWORD(l)
  #define GET_WM_COMMAND_HWND(w,l) LOWORD(l)
#endif

#include "tlb.h"

// Menu IDs
#define IDM_DRAWLINE  101
#define IDM_CLEAR     102
#define IDM_EXIT      103

// Dialog ID
#define IDD_DRAWLINE  101
// Icon ID
#define IDI_ICON      102

// Dialog Control IDs
#define IDC_THICKNESS     101
#define IDC_CHOOSECOLOR   102
#define IDC_STARTPOINT_X  103
#define IDC_STARTPOINT_Y  104
#define IDC_ENDPOINT_X    105
#define IDC_ENDPOINT_Y    106
#define IDC_STATIC         -1

// MAX len of string table entries
#define STR_LEN   200

// String table constants
#define IDS_Name                   1
#define IDS_ErrorLoadingTypeLib    2
#define IDS_ProgID                 3
#define IDS_Unexpected             1001
#define IDS_OutOfMemory            1002
#define IDS_InvalidIndex           1003
#define IDS_CollectionFull         1004
#define IDS_LineFromOtherInstance  1005
#define IDS_CantAddEndPoints       1006
#define IDS_PointFromOtherInstance 1007
#define IDS_NoVisibleXCoordinate   1008
#define IDS_NoVisibleYCoordinate   1009
#define IDS_NoStartPoint           1010
#define IDS_NoEndPoint             1011

// SCODEs for the Lines Application. This is required for vtable-binding
// Automation objects want to return custom HRESULTs. All the OLE-defined
// FACILITY_ITF codes have a code value which lies in the region 0x0000-0x01FFF.
// While it is legal for the definer to use any code, it is highly recommended
// that only code values in the range 0x0200-0xFFFF be used, as this will reduce the
// possiblity of accidental confusion with any OLE-defined errors.
#define LINES_E_FIRST MAKE_SCODE(SEVERITY_ERROR, FACILITY_ITF, 0x0200)

#define LINES_E_UNEXPECTED              (LINES_E_FIRST + 0x0)
// Unexpected error
#define LINES_E_OUTOFMEMORY             (LINES_E_FIRST + 0x1)
// Out of memory
#define LINES_E_INVALIDINDEX            (LINES_E_FIRST + 0x2)
// Invalid index to Points or Lines collections
#define LINES_E_COLLECTIONFULL          (LINES_E_FIRST + 0x3)
// Points or Lines collection is full
#define LINES_E_LINEFROMOTHERINSTANCE   (LINES_E_FIRST + 0x4)
// Line from another instance of this application cannot be added.
#define LINES_E_CANTADDENDPOINTS        (LINES_E_FIRST + 0x5)
// End points of line cannot be added to collection. (Make sure line has two end points).
#define LINES_E_POINTFROMOTHERINSTANCE  (LINES_E_FIRST + 0x6)
// Point from another instance of this application cannot be added.
#define LINES_E_NOVISIBLEXCOORDINATE    (LINES_E_FIRST + 0x7)
// No visible X coordinate.
#define LINES_E_NOVISIBLEYCOORDINATE    (LINES_E_FIRST + 0x8)
// No visible Y coordinate.
#define LINES_E_NOSTARTPOINT            (LINES_E_FIRST + 0x9)
// Line does not have a start point.
#define LINES_E_NOENDPOINT              (LINES_E_FIRST + 0xA)
// Line does not have an end point.

// Number of SCODEs
#define SCODE_COUNT 11

// Function prototypes
int APIENTRY WinMain (HINSTANCE, HINSTANCE, LPSTR, int);
BOOL InitApplication (HINSTANCE);
BOOL InitInstance (HINSTANCE);
BOOL ProcessCmdLine(LPSTR pCmdLine, LPDWORD pdwRegisterCF, LPDWORD pdwRegisterActiveObject, int nCmdShow);
void Uninitialize(DWORD dwRegisterCF, DWORD dwRegisterActiveObject);
HRESULT LoadTypeInfo(ITypeInfo FAR* FAR* pptinfo, REFCLSID clsid);
HRESULT RaiseException(int nID, REFGUID rguid);

#ifdef WIN16
extern "C" LRESULT __export CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);
extern "C" BOOL __export CALLBACK DrawLineDialogFunc(HWND, UINT, WPARAM, LPARAM);
#else
extern "C" LRESULT CALLBACK MainWndProc (HWND, UINT, WPARAM, LPARAM);
extern "C" BOOL CALLBACK DrawLineDialogFunc(HWND, UINT, WPARAM, LPARAM);
#endif

// Class definitions
class CPane;
class CLine;
class CPoint;
class CLines;
class CPoints;

// ISupportErrorInfo interface implementation
interface CSupportErrorInfo : public ISupportErrorInfo
{
public:
    // IUnknown methods
    STDMETHOD(QueryInterface)(REFIID riid, LPVOID FAR* ppvObj);
    STDMETHOD_(ULONG, AddRef)(void);
    STDMETHOD_(ULONG, Release)(void);

    // ISupportErrorInfo method
    STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);

    CSupportErrorInfo(IUnknown FAR* punkObject, REFIID riid);
private:
    LPUNKNOWN m_punkObject;  // IUnknown of Object that implements this interface
    GUID m_iid;
};

class FAR CApplicationCF : public IClassFactory
{
public:
    // IUnknown methods
    STDMETHOD(QueryInterface)(REFIID riid, LPVOID FAR* ppvObj);
    STDMETHOD_(ULONG, AddRef)(void);
    STDMETHOD_(ULONG, Release)(void);

    // IClassFactory methods
    STDMETHOD(CreateInstance)(IUnknown FAR* punkOuter, REFIID riid,
                              void FAR* FAR* ppv);
    STDMETHOD(LockServer)(BOOL fLock);

    CApplicationCF();

private:
    ULONG m_cRef;                   // Reference count
};

class FAR CApplication : public IApplication
{
public:
    /* IUnknown methods */
    STDMETHOD(QueryInterface)(REFIID riid, LPVOID FAR* ppvObj);
    STDMETHOD_(ULONG, AddRef)();
    STDMETHOD_(ULONG, Release)();

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(UINT FAR* pctinfo);
    STDMETHOD(GetTypeInfo)(
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo);
    STDMETHOD(GetIDsOfNames)(
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid);
    STDMETHOD(Invoke)(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr);

    /* IApplication automation exposed properties and methods */
    STDMETHOD(get_Application)(IApplication FAR* FAR* ppApplication);
    STDMETHOD(get_FullName)(BSTR FAR* pbstr);
    STDMETHOD(get_Name)(BSTR FAR* pbstr);
    STDMETHOD(get_Parent)(IApplication FAR* FAR* ppApplication);
    STDMETHOD(put_Visible)(VARIANT_BOOL bVisible);
    STDMETHOD(get_Visible)(VARIANT_BOOL FAR* pbVisible);
    STDMETHOD(Quit)();
    STDMETHOD(get_Pane)(IPane FAR* FAR* ppPane);
    STDMETHOD(CreateLine)(ILine FAR* FAR* ppLine);
    STDMETHOD(CreatePoint)(IPoint FAR* FAR* ppPoint);

    /* CApplication methods */
    STDMETHOD_(void, Draw)();
    STDMETHOD_(void, OnSize)(unsigned int nWidth, unsigned int nHeight);
    STDMETHOD_(void, ShowWindow)(int nCmdShow);
    STDMETHOD_(void, CreateAndDrawLine)();
    STDMETHOD_(void, ClearPane)();

    static HRESULT Create(HINSTANCE hwnd, CApplication FAR* FAR* ppApplication); // Creates and intializes Applicaton object
    CApplication();
    ~CApplication();

public:
    HWND m_hwnd;                   // Application window.
    HINSTANCE m_hinst;             // Hinstance of application.
    BSTR m_bstrName;               // Name of application.
    BSTR m_bstrProgID;             // ProgID of application.
    BOOL m_bUserClosing;           // User is closing the application.

private:
    LPTYPEINFO m_ptinfo;           // Type information of IApplication interface.
    BSTR m_bstrFullName;           // Full name of application.
    BOOL m_bVisible;               // Is window visible?
    CPane FAR* m_pPane;            // Pointer to the Pane object.
    ULONG m_cRef;                  // Reference count.
    CSupportErrorInfo m_SupportErrorInfo; // ISupportErrorInfo interface implementation
};

class FAR CPane : public IPane
{
public:
    /* IUnknown methods */
    STDMETHOD(QueryInterface)(REFIID riid, LPVOID FAR* ppvObj);
    STDMETHOD_(ULONG, AddRef)();
    STDMETHOD_(ULONG, Release)();

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(UINT FAR* pctinfo);
    STDMETHOD(GetTypeInfo)(
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo);
    STDMETHOD(GetIDsOfNames)(
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid);
    STDMETHOD(Invoke)(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr);

    /* IPane automation exposed properties & methods */
    STDMETHOD(get_Lines)(ILines FAR* FAR* ppLines);
    STDMETHOD(get_Points)(IPoints FAR* FAR* ppPoints);
    STDMETHOD(get_MaxX)(int FAR* pnMaxX);
    STDMETHOD(get_MaxY)(int FAR* pnMaxY);
    STDMETHOD(Clear)();
    STDMETHOD(Refresh)();

    /* CPane methods */
    STDMETHOD_(void, Draw)();
    STDMETHOD_(void, OnSize)(unsigned int nWidth, unsigned int nHeight);
    STDMETHOD_(HDC,  GetDC)();
    STDMETHOD_(void, ReleaseDC)(HDC hdc);
    STDMETHOD_(void, InvalidateRect)(LPRECT prc);
    STDMETHOD_(void, Update)(void);
    STDMETHOD_(BOOL, AddPoint)(CPoint FAR* pPoint);
    STDMETHOD_(void, RemovePoint)(CPoint FAR* pPoint);

    static HRESULT Create(HWND hwnd, CPane FAR* FAR* ppPane); // Creates and intializes Pane object
    CPane();
    ~CPane();

private:
    LPTYPEINFO m_ptinfo;           // Type information of IPane interface.
    HWND m_hwnd;                   // Main window.
    CLines FAR *m_pLines;          // Lines collection.
    CPoints FAR* m_pPoints;        // Points collection.
    int m_nMaxX;                   // Maximum visible X coordinate in twips.
    int m_nMaxY;                   // Maximum visible Y coordinate in twips.
    int m_nWidth;                  // Width of pane in twips.
    int m_nHeight;                 // Height of pane in twips.
    ULONG m_cRef;                  // Reference count.
    CSupportErrorInfo m_SupportErrorInfo; // ISupportErrorInfo interface implementation
};

class FAR CLine : public ILine
{
public:
    /* IUnknown methods */
    STDMETHOD(QueryInterface)(REFIID riid, LPVOID FAR* ppvObj);
    STDMETHOD_(ULONG, AddRef)();
    STDMETHOD_(ULONG, Release)();

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(UINT FAR* pctinfo);
    STDMETHOD(GetTypeInfo)(
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo);
    STDMETHOD(GetIDsOfNames)(
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid);
    STDMETHOD(Invoke)(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr);

    /* ILine automation exposed properties & methods */
    STDMETHOD(get_Color)(long FAR* plColorref);
    STDMETHOD(put_Color)(long lColorref);
    STDMETHOD(get_EndPoint)(IPoint FAR* FAR* ppPoint);
    STDMETHOD(putref_EndPoint)(IPoint FAR* pPoint);
    STDMETHOD(get_StartPoint)(IPoint FAR* FAR* ppPoint);
    STDMETHOD(putref_StartPoint)(IPoint FAR* pPoint);
    STDMETHOD(get_Thickness)(int FAR* pnThickness);
    STDMETHOD(put_Thickness)(int nThickness);

    /* CLine methods */
    STDMETHOD_(void, Draw)(HDC hdc);
    STDMETHOD_(void, GetInvalidateRect)(LPRECT prc);
    STDMETHOD_(BOOL, AddEndPointsToPane)(CPane FAR* pPane);
    STDMETHOD_(void, RemoveEndPointsFromPane)(CPane FAR* pPane);

    static HRESULT Create(CLine FAR* FAR* ppLine); // Creates and intializes Line object
    CLine();
    ~CLine();

private:
    LPTYPEINFO m_ptinfo;           // Type information of ILine interface.
    COLORREF m_colorref;           // RGB color of line.
    CPoint FAR* m_pPointStart;     // Start point of line.
    CPoint FAR* m_pPointEnd;       // End point of line.
    int m_nThickness;              // Line thickness in twips.
    ULONG m_cRef;                  // Reference count.
    CSupportErrorInfo m_SupportErrorInfo; // ISupportErrorInfo interface implementation
};

class FAR CPoint : public IPoint
{
public:
    /* IUnknown methods */
    STDMETHOD(QueryInterface)(REFIID riid, LPVOID FAR* ppvObj);
    STDMETHOD_(ULONG, AddRef)();
    STDMETHOD_(ULONG, Release)();

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(UINT FAR* pctinfo);
    STDMETHOD(GetTypeInfo)(
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo);
    STDMETHOD(GetIDsOfNames)(
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid);
    STDMETHOD(Invoke)(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr);

    /* IPoint automation exposed properties & methods */
    STDMETHOD(get_x)(int FAR* pnX);
    STDMETHOD(put_x)(int nX);
    STDMETHOD(get_y)(int FAR* pnY);
    STDMETHOD(put_y)(int nY);

    /* CPoint methods */
    STDMETHOD_(ULONG, InternalAddRef)();
    STDMETHOD_(ULONG, InternalRelease)();
    STDMETHOD_(int, get_x)();
    STDMETHOD_(int, get_y)();

    static HRESULT Create(CPoint FAR* FAR* ppPoint); // Creates and intializes Point object
    CPoint();
    ~CPoint();

private:
    LPTYPEINFO m_ptinfo;           // Type information of IPoint interface.
    int m_nX;                      // X coordinate of point in twips.
    int m_nY;                      // Y coordinate of point in twips.
    ULONG m_cInternalRef;          // Reference count for the use of the Points collection only.
    ULONG m_cRef;                  // Reference count.
    CSupportErrorInfo m_SupportErrorInfo; // ISupportErrorInfo interface implementation
};

class FAR CLines : public ILines
{
public:
    /* IUnknown methods */
    STDMETHOD(QueryInterface)(REFIID riid, LPVOID FAR* ppvObj);
    STDMETHOD_(ULONG, AddRef)();
    STDMETHOD_(ULONG, Release)();

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(UINT FAR* pctinfo);
    STDMETHOD(GetTypeInfo)(
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo);
    STDMETHOD(GetIDsOfNames)(
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid);
    STDMETHOD(Invoke)(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr);

    /* ILines automation exposed properties & methods */
    STDMETHOD(Add)(ILine FAR* pLine);
    STDMETHOD(get_Count)(long FAR* lCount);
    STDMETHOD(get_Item)(long lIndex, ILine FAR* FAR* ppLine);
    STDMETHOD(get__NewEnum)(IUnknown FAR* FAR* ppunkEnum);
    STDMETHOD(Remove)(long lIndex);

    /* CLines methods */
    STDMETHOD_(void, Draw)(HDC hdc);
    STDMETHOD_(void, Clear)();

    static HRESULT Create(ULONG lMaxSize, long lLBound, CPane FAR* pPane, CLines FAR* FAR* ppLines); // Creates and intializes Lines object
    CLines();
    ~CLines();

private:
    LPTYPEINFO m_ptinfo;           // Type information of ILines interface.
    SAFEARRAY FAR *m_psa;          // Safe array that holds Lines collection items.
    ULONG m_cElements;             // Number of items in Lines collection.
    ULONG m_cMax;                  // Maximum number of items Lines collection can hold.
    long m_lLBound;                // Lower bound of index of Lines collection.
    CPane FAR* m_pPane;            // Pointer to the Pane object that contains this Lines collection.
    ULONG m_cRef;                  // Reference count.
    CSupportErrorInfo m_SupportErrorInfo; // ISupportErrorInfo interface implementation
};

class FAR CPoints : public IPoints
{
public:
    /* IUnknown methods */
    STDMETHOD(QueryInterface)(REFIID riid, LPVOID FAR* ppvObj);
    STDMETHOD_(ULONG, AddRef)();
    STDMETHOD_(ULONG, Release)();

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(UINT FAR* pctinfo);
    STDMETHOD(GetTypeInfo)(
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo);
    STDMETHOD(GetIDsOfNames)(
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid);
    STDMETHOD(Invoke)(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr);

    /* IPoints automation exposed properties & methods */
    STDMETHOD(get_Count)(long FAR* plCount);
    STDMETHOD(get_Item)(long lIndex, IPoint FAR* FAR* ppPoint);
    STDMETHOD(get__NewEnum)(IUnknown FAR* FAR* ppunkEnum);

    /* CPoints methods */
    STDMETHOD_(BOOL, Add)(CPoint FAR* pPointNew);
    STDMETHOD_(BOOL, Remove)(CPoint FAR* pPointRemove);
    STDMETHOD_(void, Clear)();
    STDMETHOD_(void, GetMaxXMaxY)(int FAR* pnX, int FAR* pnY, LPRECT prc);

    static HRESULT Create(ULONG lMaxSize, long lLBound, CPane FAR* pPane, CPoints FAR* FAR* ppPoints); // Creates and intializes Points object
    CPoints();
    ~CPoints();

private:
    LPTYPEINFO m_ptinfo;           // Type information of IPoints interface.
    SAFEARRAY FAR *m_psa;          // Safe array that holds Points collection items.
    ULONG m_cElements;             // Number of items in Points collection.
    ULONG m_cMax;                  // Maximum number of items Points collection can hold.
    long m_lLBound;                // Lower bound of index of Points collection.
    CPane FAR* m_pPane;            // Pointer to the Pane object that contains this Points collection.
    ULONG m_cRef;                  // Reference count.
    CSupportErrorInfo m_SupportErrorInfo; // ISupportErrorInfo interface implementation
};

class FAR CEnumVariant : public IEnumVARIANT
{
public:
    // IUnknown methods
    STDMETHOD(QueryInterface)(REFIID riid, LPVOID FAR* ppvObj);
    STDMETHOD_(ULONG, AddRef)();
    STDMETHOD_(ULONG, Release)();

    // IEnumVARIANT methods
    STDMETHOD(Next)(ULONG cElements, VARIANT FAR* pvar, ULONG FAR* pcElementFetched);
    STDMETHOD(Skip)(ULONG cElements);
    STDMETHOD(Reset)();
    STDMETHOD(Clone)(IEnumVARIANT FAR* FAR* ppenum);

    static HRESULT Create(SAFEARRAY FAR*, ULONG, CEnumVariant FAR* FAR*); // Creates and intializes Enumerator
    CEnumVariant();
    ~CEnumVariant();

private:
    ULONG m_cRef;           // Reference count
    ULONG m_cElements;      // Number of elements in enumerator.
    long m_lLBound;         // Lower bound of index.
    long m_lCurrent;        // Current index.
    SAFEARRAY FAR* m_psa;   // Safe array that holds elements.
};

// Structure filled by dialog from user input to create a line.
typedef struct _lineinfo
{
    COLORREF colorref;    // RGB color of line.
    POINT ptStart;        // Start point of line.
    POINT ptEnd;          // End point of line.
    int nThickness;       // Thickness of line.
} LINEINFO, FAR* LPLINEINFO;

extern CApplication FAR* g_pApplication;


MAIN.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation Lines Object.
**
**  main.cpp
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/
#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
  #include <commdlg.h>
#endif
#include <initguid.h>
#include "lines.h"

// Globals
CApplication FAR* g_pApplication;

SCODE g_scodes[SCODE_COUNT] =                // Array of SCODEs for easy lookup
{    LINES_E_UNEXPECTED,
     LINES_E_OUTOFMEMORY,
     LINES_E_INVALIDINDEX,
     LINES_E_COLLECTIONFULL,
     LINES_E_LINEFROMOTHERINSTANCE,
     LINES_E_CANTADDENDPOINTS,
     LINES_E_POINTFROMOTHERINSTANCE,
     LINES_E_NOVISIBLEXCOORDINATE,
     LINES_E_NOVISIBLEYCOORDINATE,
     LINES_E_NOSTARTPOINT,
     LINES_E_NOENDPOINT
};

/*
 * WinMain
 *
 * Purpose:
 *  Main entry point of application.
 *
 */
int APIENTRY WinMain (HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR pCmdLine, int nCmdShow)
{
    MSG msg;
    DWORD dwRegisterCF;
    DWORD dwRegisterActiveObject;

#ifdef WIN16
   //  It is recommended that all 16 bit OLE applications set
   //  their message queue size to 96. This improves the capacity
   //  and performance of OLE's LRPC mechanism.
   int cMsg = 96;                  // Recommend msg queue size for OLE
   while (cMsg && !SetMessageQueue(cMsg))  // take largest size we can get.
       cMsg -= 8;
   if (!cMsg)
       return -1;  // ERROR: we got no message queue
#endif

    if (!hinstPrev)
       if (!InitApplication(hinst)) // Register window class
          return FALSE;

    if (!InitInstance(hinst))   // Initialize OLE and create Lines application object
        return (FALSE);

    // Determine if /Automation was specified in command line and register class
    // factory and active object. Show window if application was started stand alone.
    if (!ProcessCmdLine(pCmdLine, &dwRegisterCF, &dwRegisterActiveObject, nCmdShow))
    {
       Uninitialize(dwRegisterCF, dwRegisterActiveObject);
       return (FALSE);
    }

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

    Uninitialize(dwRegisterCF, dwRegisterActiveObject);
    return (msg.wParam);
}

/*
 * InitApplication
 *
 * Purpose:
 *  Registers window class
 *
 * Parameters:
 *  hinst       hInstance of application
 *
 * Return Value:
 *  TRUE if initialization succeeded, FALSE otherwise.
 */
BOOL InitApplication (HINSTANCE hinst)
{
   WNDCLASS wc;

   wc.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
   wc.lpfnWndProc = MainWndProc;
   wc.cbClsExtra = 0;
   wc.cbWndExtra = 0;
   wc.hInstance = hinst;
   wc.hIcon = LoadIcon(hinst, MAKEINTRESOURCE(IDI_ICON));
   wc.hCursor = LoadCursor(NULL, IDC_ARROW);
   wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
   wc.lpszMenuName = TEXT("AppMenu");
   wc.lpszClassName = TEXT("MainWndClass");

   return RegisterClass(&wc);
}

/*
 * InitInstance
 *
 * Purpose:
 *  Intializes OLE and creates Lines Application object
 *
 * Parameters:
 *  hinst           hInstance of application
 *
 * Return Value:
 *  TRUE if initialization succeeded, FALSE otherwise.
 */
BOOL InitInstance (HINSTANCE hinst)
{
    HRESULT hr;

    // Intialize OLE
    hr = OleInitialize(NULL);
    if (FAILED(hr))
       return FALSE;

    // Create an instance of the Lines Application object. Object is
    // created with refcount 0.
    hr = CApplication::Create(hinst, &g_pApplication);
    if (FAILED(hr))
        return FALSE;
    return TRUE;
}

/*
 * ProcessCmdLine
 *
 * Purpose:
 *  Check if command line contains /Automation. If so, the class factory of the
 *  application object is registered.  If not, the application was started stand-alone and
 *  so the window is shown. The application object is registered using RegisterActiveObject.
 *
 * Parameters:
 *  pCmdLine       Command line passed to application
 *  pdwRegisterCF   Returns id returned after class factory registration. Can be used to
 *                  revoke class factory registration.
 *  pdwRegisterActiveObject   Returns id returned after active object registration. Can be used to
 *                  revoke active object registration.
 *  nCmdShow        Specifies how window is to be shown if application was started stand alone.
 *
 * Return Value:
 *    TRUE if OLE initialization succeeded, FALSE otherwise.
 *
 */
BOOL ProcessCmdLine(LPSTR pCmdLine, LPDWORD pdwRegisterCF, LPDWORD pdwRegisterActiveObject, int nCmdShow)
{
   LPCLASSFACTORY pcf = NULL;
   HRESULT hr;

   *pdwRegisterCF = 0;
   *pdwRegisterActiveObject = 0;

   // Expose class factory for application object if command line contains the
   // Automation switch
   if (_fstrstr(pCmdLine, "-Automation") != NULL
       || _fstrstr(pCmdLine, "/Automation") != NULL)
   {
       pcf = new CApplicationCF;
       if (!pcf)
           goto error;
       pcf->AddRef();
       hr = CoRegisterClassObject(CLSID_Lines, pcf,
                                     CLSCTX_LOCAL_SERVER, REGCLS_SINGLEUSE,
                                     pdwRegisterCF);
       if (hr != NOERROR)
           goto error;
       pcf->Release();
   }
   else g_pApplication->ShowWindow(nCmdShow);    // Show window if started stand-alone

   // Register Lines application object in the Running Object Table (ROT). This
   // allows controllers to connect to a running application object instead of creating
   // a new instance. Use weak registration so that the ROT releases it's reference when
   // all external references are released. If strong registration is used, the ROT will not
   // release it's reference until RevokeActiveObject is called and so will keep
   // the object alive even after all external references have been released.
   RegisterActiveObject(g_pApplication, CLSID_Lines, ACTIVEOBJECT_WEAK, pdwRegisterActiveObject);
   return TRUE;

error:
    if (pcf)
        pcf->Release();
    return FALSE;
}

/*
 * Uninitialize
 *
 *  Purpose:
 *   Revoke class factory and active object registration and uninitialize OLE.
 *
 * Parameters:
 *  dwRegisterCF ID returned after class factory registration.
 *  dwRegisterActiveObject ID returned after active object registration.
 *
 */
void Uninitialize(DWORD dwRegisterCF, DWORD dwRegisterActiveObject)
{
    if (dwRegisterCF != 0)
        CoRevokeClassObject(dwRegisterCF);
    if (dwRegisterActiveObject != 0)
        RevokeActiveObject(dwRegisterActiveObject, NULL);
    OleUninitialize();
}

/*
 * MainWndProc
 *
 * Purpose:
 *  Window procedure for main window
 *
 */
LRESULT CALLBACK MainWndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
   switch (msg)
   {
      case WM_PAINT:
          g_pApplication->Draw();
          break;

      case WM_SIZE:
          g_pApplication->OnSize(LOWORD(lParam), HIWORD(lParam));
          break;

      case WM_COMMAND:
      {
          switch(GET_WM_COMMAND_ID(wParam,lParam))
          {
              case IDM_DRAWLINE:
                  g_pApplication->CreateAndDrawLine();
                  break;

              case IDM_CLEAR:
                  g_pApplication->ClearPane();
                  break;

              case IDM_EXIT:
                  PostMessage(hwnd, WM_CLOSE, 0, 0L);
                  break;
          }
      }
      break;

      case WM_CLOSE:
         // Hide the window to release the refcount added by CoLockObjectExternal
         // (See CLines::ShowWindow)
         g_pApplication->m_bUserClosing = TRUE;
         g_pApplication->ShowWindow(SW_HIDE);
         DestroyWindow(hwnd);
         return 0L;

      case WM_DESTROY:
         PostQuitMessage(0);
         break;

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

   return NULL;
}

/*
 * DrawLineDialogFunc
 *
 * Purpose:
 *  Dialog function of DrawLine dialog. Prompts user and returns information to draw a line.
 *
 */
BOOL CALLBACK DrawLineDialogFunc(HWND hwndDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
   static LPLINEINFO plineinfo;
   BOOL b;
   CHOOSECOLOR cc;
   COLORREF clrCustom[16];
   int n;

   switch (msg)
   {
      case WM_INITDIALOG:
         plineinfo = (LPLINEINFO)lParam;
         // Initialize edit controls. Units are twips.
         SetDlgItemText(hwndDlg, IDC_STARTPOINT_X, TEXT("4500"));
         SetDlgItemText(hwndDlg, IDC_STARTPOINT_Y, TEXT("4500"));
         SetDlgItemText(hwndDlg, IDC_ENDPOINT_X, TEXT("8000"));
         SetDlgItemText(hwndDlg, IDC_ENDPOINT_Y, TEXT("8000"));
         SetDlgItemText(hwndDlg, IDC_THICKNESS, TEXT("100"));
         plineinfo->colorref = 0;
         return TRUE;

      case WM_COMMAND:
         switch(GET_WM_COMMAND_ID(wParam,lParam))
         {
             case IDOK:
                 plineinfo->ptStart.x = GetDlgItemInt(hwndDlg, IDC_STARTPOINT_X, &b, TRUE);
                 plineinfo->ptStart.y = GetDlgItemInt(hwndDlg, IDC_STARTPOINT_Y, &b, TRUE);
                 plineinfo->ptEnd.x = GetDlgItemInt(hwndDlg, IDC_ENDPOINT_X, &b, TRUE);
                 plineinfo->ptEnd.y = GetDlgItemInt(hwndDlg, IDC_ENDPOINT_Y, &b, TRUE);
                 plineinfo->nThickness = GetDlgItemInt(hwndDlg, IDC_THICKNESS, &b, TRUE);
                 EndDialog(hwndDlg, IDOK);
                 return TRUE;

             case IDCANCEL:
                 EndDialog(hwndDlg, IDCANCEL);
                 return TRUE;

             case IDC_CHOOSECOLOR:
                 memset(&cc, 0, sizeof(CHOOSECOLOR));
                 cc.lStructSize = sizeof(CHOOSECOLOR);
                 cc.hwndOwner = hwndDlg;
                 cc.rgbResult = RGB(0, 0, 0);
                 cc.Flags = CC_RGBINIT;
                 for (n=0; n<16; n++)
                    clrCustom[n] = RGB(255, 255, 255);
                 cc.lpCustColors = clrCustom;
                 if (ChooseColor(&cc))
                     plineinfo->colorref = cc.rgbResult;
         }
         break;
   }
   return FALSE;
}

/*
 * LoadTypeInfo
 *
 *  Purpose:
 *   Gets type information of an object's interface from type library.
 *
 * Parameters:
 *  ppunkStdDispatch    Returns type information.
 *  clsid               Interface id of object in type library.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT LoadTypeInfo(ITypeInfo FAR* FAR* pptinfo, REFCLSID clsid)
{
    HRESULT hr;
    LPTYPELIB ptlib = NULL;
    LPTYPEINFO ptinfo = NULL;

    *pptinfo = NULL;

    // Load Type Library.
    hr = LoadRegTypeLib(LIBID_Lines, 1, 0, 0x09, &ptlib);
    if (FAILED(hr))
    {
        // if it wasn't registered, try to load it from the path
        // if this succeeds, it will have registered the type library for us
        // for the next time.
        hr = LoadTypeLib(OLESTR("lines.tlb"), &ptlib);
        if(FAILED(hr))
            return hr;
    }

    // Get type information for interface of the object.
    hr = ptlib->GetTypeInfoOfGuid(clsid, &ptinfo);
    if (FAILED(hr))
    {
        ptlib->Release();
        return hr;
    }

    ptlib->Release();
    *pptinfo = ptinfo;
    return NOERROR;
}

/*
 * RaiseException
 *
 * Parameters:
 *  nID                 Error number
 *  rguid               GUID of interface that is raising the exception.
 *
 * Return Value:
 *  HRESULT correspnding to the nID error number.
 *
 * Purpose:
 *  Fills the EXCEPINFO structure.
 *  Sets ErrorInfo object for vtable-binding controllers.
 *  For id-binding and late binding controllers DispInvoke
 *  will return DISP_E_EXCEPTION and fill the EXCEPINFO parameter
 *  with the error information set by SetErrorInfo.
 *
 */
HRESULT RaiseException(int nID, REFGUID rguid)
{
    extern SCODE g_scodes[];
    TCHAR szError[STR_LEN];
    ICreateErrorInfo *pcerrinfo;
    IErrorInfo *perrinfo;
    HRESULT hr;
    BSTR bstrDescription = NULL;

    if (LoadString(g_pApplication->m_hinst, nID, szError, sizeof(szError)))
        bstrDescription = SysAllocString(TO_OLE_STRING(szError));

    // Set ErrorInfo object so that vtable binding controller can get
    // rich error information. If the controller is using IDispatch
    // to access properties or methods, DispInvoke will fill the
    // EXCEPINFO structure using the values specified in the ErrorInfo
    // object and DispInvoke will return DISP_E_EXCEPTION. The property
    // or method must return a failure SCODE for DispInvoke to do this.
    hr = CreateErrorInfo(&pcerrinfo);
    if (SUCCEEDED(hr))
    {
       pcerrinfo->SetGUID(rguid);
       pcerrinfo->SetSource(g_pApplication->m_bstrProgID);
       if (bstrDescription)
           pcerrinfo->SetDescription(bstrDescription);
       hr = pcerrinfo->QueryInterface(IID_IErrorInfo, (LPVOID FAR*) &perrinfo);
       if (SUCCEEDED(hr))
       {
          SetErrorInfo(0, perrinfo);
          perrinfo->Release();
       }
       pcerrinfo->Release();
    }

    if (bstrDescription)
        SysFreeString(bstrDescription);
    return g_scodes[nID-1001];
}

/*
 * Quick & Dirty ANSI/Unicode conversion routines. These routines use a static
 * buffer of fixed size to hold the converted string. Consequently these
 * routines are limited to strings of size STRCONVERT_MAXLEN. Also the same
 * buffer is reused when the routine is called a second time. So make sure
 * that the converted string is used before the conversion routine is called
 * again
 */
#ifdef WIN32

#ifndef UNICODE
char* ConvertToAnsi(OLECHAR FAR* szW)
{
  static char achA[STRCONVERT_MAXLEN];

  WideCharToMultiByte(CP_ACP, 0, szW, -1, achA, STRCONVERT_MAXLEN, NULL, NULL);
  return achA;
}

OLECHAR* ConvertToUnicode(char FAR* szA)
{
  static OLECHAR achW[STRCONVERT_MAXLEN];

  MultiByteToWideChar(CP_ACP, 0, szA, -1, achW, STRCONVERT_MAXLEN);
  return achW;
}
#endif

#endif


PANE.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation Lines Object.
**
**  pane.cpp
**
**  CPane implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "lines.h"

/*
 * CPane::Create
 *
 * Purpose:
 *  Creates an instance of the Pane automation object and initializes it.
 *
 * Parameters:
 *  hwnd      Handle of the window of the pane.
 *  ppPane    Returns Pane automation object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CPane::Create(HWND hwnd, CPane FAR* FAR* ppPane )
{
    HRESULT hr;
    CPane FAR* pPane = NULL;

    *ppPane = NULL;

    pPane = new CPane();
    if (pPane == NULL)
        goto error;

    pPane->m_hwnd = hwnd;

    // Create an empty Lines collection of size 100
    hr = CLines::Create(100, 1, pPane, &pPane->m_pLines);
    if (FAILED(hr))
       goto error;
    pPane->m_pLines->AddRef();

    // Create an empty Points collection of size 200
    hr = CPoints::Create(200, 1, pPane, &pPane->m_pPoints);
    if (FAILED(hr))
       goto error;
    pPane->m_pPoints->AddRef();

    // Load type information for the pane object from type library.
    hr = LoadTypeInfo(&pPane->m_ptinfo, IID_IPane);
    if (FAILED(hr))
        goto error;

    *ppPane = pPane;
    return NOERROR;

error:
    if (pPane == NULL)
        return E_OUTOFMEMORY;

    if (pPane->m_ptinfo)
        pPane->m_ptinfo->Release();
    if (pPane->m_pLines)
        pPane->m_pLines->Release();
    if (pPane->m_pPoints)
        pPane->m_pPoints->Release();

    // Set to NULL to prevent destructor from attempting to free again
    pPane->m_ptinfo = NULL;
    pPane->m_pLines = NULL;
    pPane->m_pPoints = NULL;

    delete pPane;
    return hr;
}

/*
 * CPane::CPane
 *
 * Purpose:
 *  Constructor for CPane object. Initializes members to NULL.
 *
 */
#pragma warning (disable : 4355)
CPane::CPane() : m_SupportErrorInfo(this, IID_IPane)
#pragma warning (default : 4355)
{
    m_cRef = 0;
    m_hwnd = NULL;
    m_ptinfo = NULL;
    m_pLines = NULL;
    m_pPoints = NULL;
    m_nMaxX = -1;
    m_nMaxY = -1;
}

/*
 * CPane::~CPane
 *
 * Purpose:
 *  Destructor for CPane object.
 *
 */
CPane::~CPane()
{
     if (m_ptinfo) m_ptinfo->Release();
     if (m_pLines) m_pLines->Release();
     if (m_pPoints) m_pPoints->Release();
}

/*
 * CPane::QueryInterface, AddRef, Release
 *
 * Purpose:
 *  Implements IUnknown::QueryInterface, AddRef, Release
 *
 */
STDMETHODIMP
CPane::QueryInterface(REFIID iid, void FAR* FAR* ppv)
{
    *ppv = NULL;

    if (iid == IID_IUnknown || iid == IID_IDispatch || iid == IID_IPane)
        *ppv = this;
    else if (iid == IID_ISupportErrorInfo)
        *ppv = &m_SupportErrorInfo;
    else return E_NOINTERFACE;

    AddRef();
    return NOERROR;
}

STDMETHODIMP_(ULONG)
CPane::AddRef(void)
{
#ifdef _DEBUG
    TCHAR ach[50];
    wsprintf(ach, TEXT("Ref = %ld, Pane\r\n"), m_cRef+1);
    OutputDebugString(ach);
#endif

    return ++m_cRef;
}

STDMETHODIMP_(ULONG)
CPane::Release(void)
{
#ifdef _DEBUG
    TCHAR ach[50];
    wsprintf(ach, TEXT("Ref = %ld, Pane\r\n"), m_cRef-1);
    OutputDebugString(ach);
#endif

    if(--m_cRef == 0)
    {
        delete this;
        return 0;
    }
    return m_cRef;
}

/*
 * CPane::GetTypeInfoCount
 *
 * Purpose:
 *  Implements IDispatch::GetTypeInfoCount.
 *
 */
STDMETHODIMP
CPane::GetTypeInfoCount(UINT FAR* pctinfo)
{
    *pctinfo = 1;
    return NOERROR;
}

/*
 * CPane::GetTypeInfo
 *
 * Purpose:
 *  Implements IDispatch::GetTypeInfo.
 *
 */
STDMETHODIMP
CPane::GetTypeInfo(
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo)
{
    *pptinfo = NULL;

    if(itinfo != 0)
        return DISP_E_BADINDEX;

    m_ptinfo->AddRef();
    *pptinfo = m_ptinfo;

    return NOERROR;
}

/*
 * CPane::GetIDsOfNames
 *
 * Purpose:
 *  Implements IDispatch::GetIDsOfNames.  The standard implementation, DispGetIDsOfNames,
 *  is used.
 *
 */
STDMETHODIMP
CPane::GetIDsOfNames(
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid)
{
    return DispGetIDsOfNames(m_ptinfo, rgszNames, cNames, rgdispid);
}

/*
 * CPane::Invoke
 *
 * Purpose:
 *  Implements IDispatch::Invoke.  The standard implementation, DispInvoke,
 *  is used.
 *
 */
STDMETHODIMP
CPane::Invoke(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr)
{
    return DispInvoke(
        this, m_ptinfo,
        dispidMember, wFlags, pdispparams,
        pvarResult, pexcepinfo, puArgErr);
}

/*
 * CPane::get_Lines
 *
 * Purpose:
 *  Returns the collection of all line objects in the drawing.
 *
 */
STDMETHODIMP
CPane::get_Lines(ILines FAR* FAR* ppLines)
{
    HRESULT hr;

    hr = m_pLines->QueryInterface(IID_IDispatch, (void FAR* FAR*)ppLines);
    if (FAILED(hr))
        return RaiseException(IDS_Unexpected, IID_IPane);
    return NOERROR;
}

/*
 * CPane::get_Points
 *
 * Purpose:
 *  Returns the collection of all point objects in the drawing.
 *
 */
STDMETHODIMP
CPane::get_Points(IPoints FAR* FAR* ppPoints)
{
    HRESULT hr;

    hr = m_pPoints->QueryInterface(IID_IDispatch, (void FAR* FAR*)ppPoints);
    if (FAILED(hr))
        return RaiseException(IDS_Unexpected, IID_IPane);
    return NOERROR;
}

/*
 * CPane::get_MaxX, get_MaxY
 *
 * Purpose:
 *  Returns the maximum visible X & Y coordinate values in twips.
 *
 */
STDMETHODIMP
CPane::get_MaxX(int FAR* pnMaxX)
{
     *pnMaxX = m_nMaxX;
    if (m_nMaxX == -1)
        return RaiseException(IDS_NoVisibleXCoordinate, IID_IPane);
    return NOERROR;
}

STDMETHODIMP
CPane::get_MaxY(int FAR* pnMaxY)
{
    *pnMaxY = m_nMaxY;
    if (m_nMaxY == -1)
        return RaiseException(IDS_NoVisibleYCoordinate, IID_IPane);
    return NOERROR;
}

/*
 * CPane::Clear
 *
 * Purpose:
 *  Removes all the Lines and Points from the drawing and refreshes the client area.
 *  The result is a blank slate as if the application had just been launched.
 *
 */
STDMETHODIMP
CPane::Clear()
{
    m_pLines->Clear();
    m_pPoints->Clear();
    ::InvalidateRect(m_hwnd, NULL, TRUE);
    UpdateWindow(m_hwnd);
    m_nMaxX = m_nMaxY = -1;
    return NOERROR;
}

/*
 * CPane::Refresh
 *
 * Purpose:
 *  Invalidates the entire drawing window so the drawing is cleared
 *  and each of the lines in the Lines collection is redrawn.
 *
 */
STDMETHODIMP
CPane::Refresh()
{
    ::InvalidateRect(m_hwnd, NULL, TRUE);
    UpdateWindow(m_hwnd);
    return NOERROR;
}

/*
 *
 * The following methods are not exposed through Automation
 *
 */

STDMETHODIMP_(void)
CPane::Draw(void)
{
    PAINTSTRUCT ps;

    BeginPaint(m_hwnd, &ps);
    SetMapMode(ps.hdc, MM_TWIPS);
    SetWindowOrgEx(ps.hdc, 0, m_nHeight, NULL);
    m_pLines->Draw(ps.hdc);
    EndPaint(m_hwnd, &ps);
}

/*
 * CPane::OnSize
 *
 * Purpose:
 *  Called when the pane is resized. Remembers the new size and gets the new maximum visible
 *  x and y coordinates.
 *
 */
STDMETHODIMP_(void)
CPane::OnSize(unsigned int nWidth, unsigned int nHeight)
{
   HDC hdc;
   POINT pt;
   RECT rc;

   // Translate the height and width of pane from device units to twips.
   hdc = ::GetDC(m_hwnd);
   SetMapMode(hdc, MM_TWIPS);
   pt.x = nWidth; pt.y = nHeight;
   DPtoLP(hdc, &pt, 1);
   m_nWidth = pt.x;
   m_nHeight = -pt.y;
   ::ReleaseDC(m_hwnd, hdc);

   // Get the new max visible x and y coordinates
   SetRect(&rc, 0, 0, m_nWidth, m_nHeight);
   m_pPoints->GetMaxXMaxY(&m_nMaxX, &m_nMaxY, &rc);
}

STDMETHODIMP_(HDC)
CPane::GetDC()
{
    HDC hdc;

    hdc = ::GetDC(m_hwnd);
    SetMapMode(hdc, MM_TWIPS);
    SetWindowOrgEx(hdc, 0, m_nHeight, NULL);
    return hdc;
}

STDMETHODIMP_(void)
CPane::ReleaseDC(HDC hdc)
{
    ::ReleaseDC(m_hwnd, hdc);
}

STDMETHODIMP_(void)
CPane::InvalidateRect(LPRECT prc)
{
    RECT rc;
    HDC hdc;
    int t;

   // Translate the area to be invalidated from twips to device units
   hdc = ::GetDC(m_hwnd);
   SetMapMode(hdc, MM_TWIPS);
   SetWindowOrgEx(hdc, 0, m_nHeight, NULL);
   rc = *prc;
   LPtoDP(hdc, (LPPOINT)&rc.left, 1);
   LPtoDP(hdc, (LPPOINT)&rc.right, 1);

   // Make sure left is not greater than right and top is not greater than bottom. Otherwise
   // InvalidateRect will not invalidate.
   if (rc.right < rc.left)
   {
       t = rc.right;
       rc.right = rc.left;
       rc.left = t;
   }
   if (rc.bottom < rc.top)
   {
       t = rc.bottom;
       rc.bottom = rc.top;
       rc.top = t;
   }

   ::InvalidateRect(m_hwnd, &rc, TRUE);
   ::ReleaseDC(m_hwnd, hdc);
}

STDMETHODIMP_(void)
CPane::Update(void)
{
    UpdateWindow(m_hwnd);
}

/*
 * CPane::AddPoint
 *
 * Purpose:
 *  Adds a point to the point collection. Updates max visible coordinates is required.
 *
 */
STDMETHODIMP_(BOOL)
CPane::AddPoint(CPoint FAR* pPoint)
{
    int nX, nY;

    nX = pPoint->get_x();
    nY = pPoint->get_y();

    if (nX > m_nMaxX && nX <= m_nWidth)
        m_nMaxX = nX;
    if (nY > m_nMaxY && nY <= m_nHeight)
        m_nMaxY = nY;

    return m_pPoints->Add(pPoint);
}

/*
 * CPane::RemovePoint
 *
 * Purpose:
 *  Removes point from point collection. Updates max visible coordinates is required.
 *
 */
STDMETHODIMP_(void)
CPane::RemovePoint(CPoint FAR* pPoint)
{
    RECT rc;

    // If point was removed from the collection, update max visible X & Y coordinates
    if (m_pPoints->Remove(pPoint))
    {
        SetRect(&rc, 0, 0, m_nWidth, m_nHeight);
        m_pPoints->GetMaxXMaxY(&m_nMaxX, &m_nMaxY, &rc);
    }
}


POINT.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation Points Object.
**
**  point.cpp
**
**  CPoint implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "lines.h"

/*
 * CPoint::Create
 *
 * Purpose:
 *  Creates an instance of the Point automation object and initializes it.
 *
 * Parameters:
 *  ppPoint    Returns Point automation object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CPoint::Create(CPoint FAR* FAR* ppPoint )
{
    HRESULT hr;
    CPoint FAR* pPoint = NULL;

    *ppPoint = NULL;

    pPoint = new CPoint();
    if (pPoint == NULL)
        goto error;

    // Load type information for the point from type library.
    hr = LoadTypeInfo(&pPoint->m_ptinfo, IID_IPoint);
    if (FAILED(hr))
        goto error;

    *ppPoint = pPoint;
    return NOERROR;

error:
    if (pPoint == NULL)
        return E_OUTOFMEMORY;

    if (pPoint->m_ptinfo)
        pPoint->m_ptinfo->Release();

    // Set to NULL to prevent destructor from attempting to free again
    pPoint->m_ptinfo = NULL;

    delete pPoint;
    return hr;
}

/*
 * CPoint::CPoint
 *
 * Purpose:
 *  Constructor for CPoint object. Initializes members to NULL.
 *
 */
#pragma warning (disable : 4355)
CPoint::CPoint() : m_SupportErrorInfo(this, IID_IPoint)
#pragma warning (default : 4355)
{
    m_cRef = 0;
    m_cInternalRef = 0;
    m_ptinfo = NULL;
    m_nX = -1;
    m_nY = -1;
}

/*
 * CPoint::~CPoint
 *
 * Purpose:
 *  Destructor for CPoint object. Frees Point message BSTR and default
 *  IDispatch implementation. Closes the aplication.
 *
 */
CPoint::~CPoint()
{
     if (m_ptinfo) m_ptinfo->Release();
}

/*
 * CPoint::QueryInterface, AddRef, Release
 *
 * Purpose:
 *  Implements IUnknown::QueryInterface, AddRef, Release
 *
 */
STDMETHODIMP
CPoint::QueryInterface(REFIID iid, void FAR* FAR* ppv)
{
    *ppv = NULL;

    if (iid == IID_IUnknown || iid == IID_IDispatch || iid == IID_IPoint)
        *ppv = this;
    else if (iid == IID_ISupportErrorInfo)
        *ppv = &m_SupportErrorInfo;
    else return E_NOINTERFACE;

    AddRef();
    return NOERROR;
}

STDMETHODIMP_(ULONG)
CPoint::AddRef(void)
{
#ifdef _DEBUG
    TCHAR ach[50];
    wsprintf(ach, TEXT("Ref = %ld, Point\r\n"), m_cRef+1);
    OutputDebugString(ach);
#endif

    return ++m_cRef;
}

STDMETHODIMP_(ULONG)
CPoint::Release(void)
{
#ifdef _DEBUG
    TCHAR ach[50];
    wsprintf(ach, TEXT("Ref = %ld, Point\r\n"), m_cRef-1);
    OutputDebugString(ach);
#endif

    if(--m_cRef == 0)
    {
        delete this;
        return 0;
    }
    return m_cRef;
}

/*
 * CPoint::GetTypeInfoCount
 *
 * Purpose:
 *  Implements IDispatch::GetTypeInfoCount.
 *
 */
STDMETHODIMP
CPoint::GetTypeInfoCount(UINT FAR* pctinfo)
{
    *pctinfo = 1;
    return NOERROR;
}

/*
 * CPoint::GetTypeInfo
 *
 * Purpose:
 *  Implements IDispatch::GetTypeInfo.
 *
 */
STDMETHODIMP
CPoint::GetTypeInfo(
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo)
{
    *pptinfo = NULL;

    if(itinfo != 0)
        return DISP_E_BADINDEX;

    m_ptinfo->AddRef();
    *pptinfo = m_ptinfo;

    return NOERROR;
}

/*
 * CPoint::GetIDsOfNames
 *
 * Purpose:
 *  Implements IDispatch::GetIDsOfNames.  The standard implementation, DispGetIDsOfNames,
 *  is used.
 *
 */
STDMETHODIMP
CPoint::GetIDsOfNames(
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid)
{
    return DispGetIDsOfNames(m_ptinfo, rgszNames, cNames, rgdispid);
}

/*
 * CPoint::Invoke
 *
 * Purpose:
 *  Implements IDispatch::Invoke.  The standard implementation, DispInvoke,
 *  is used.
 *
 */
STDMETHODIMP
CPoint::Invoke(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr)
{
    return DispInvoke(
        this, m_ptinfo,
        dispidMember, wFlags, pdispparams,
        pvarResult, pexcepinfo, puArgErr);
}

STDMETHODIMP
CPoint::get_x(int FAR* pnX)
{
    *pnX = m_nX;
    return NOERROR;
}

STDMETHODIMP
CPoint::put_x(int nX)
{
    m_nX = nX;
    return NOERROR;
}

STDMETHODIMP
CPoint::get_y(int FAR* pnY)
{
    *pnY = m_nY;
    return NOERROR;
}

STDMETHODIMP
CPoint::put_y(int nY)
{
    m_nY = nY;
    return NOERROR;
}

/*
 *
 * The following methods are not exposed through Automation
 *
 */

/*
 * CPoint::InternalAddRef, InternalRelease
 *
 * Purpose:
 *  Implements an internal ref count for the use of the points collection.
 *  The points collection does not have duplicates, instead a reference count is incremented.
 *  A point is removed from the collection when the reference count drops to 0.
 *
 */
STDMETHODIMP_(ULONG)
CPoint::InternalAddRef(void)
{
#ifdef _DEBUG
    TCHAR ach[50];
    wsprintf(ach, TEXT("Internal Ref = %ld, Point\r\n"), m_cInternalRef+1);
    OutputDebugString(ach);
#endif

    return ++m_cInternalRef;
}

STDMETHODIMP_(ULONG)
CPoint::InternalRelease(void)
{
#ifdef _DEBUG
    TCHAR ach[50];
    wsprintf(ach, TEXT("Internal Ref = %ld, Point\r\n"), m_cInternalRef-1);
    OutputDebugString(ach);
#endif

    return --m_cInternalRef;
}

STDMETHODIMP_(int)
CPoint::get_x(void)
{
    return m_nX;
}

STDMETHODIMP_(int)
CPoint::get_y(void)
{
    return m_nY;
}


POINTS.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation Points object
**
**  points.cpp
**
**  CPoints collection implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "lines.h"

/*
 * CPoints::Create
 *
 * Purpose:
 *  Creates an instance of a collection object and initializes it.
 *
 * Parameters:
 *  lMaxSize   Maximum number of items that can added to collection.
 *  lLBound    Lower bound of index of collection.
 *  pPane      Pointer to pane that contains this collection. Required because the pane coordinates
 *             the lines and points collection.
 *  ppPoints    Returns collection object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CPoints::Create(ULONG lMaxSize, long lLBound, CPane FAR* pPane, CPoints FAR* FAR* ppPoints)
{
    HRESULT hr;
    CPoints FAR* pPoints = NULL;
    SAFEARRAYBOUND sabound[1];

    *ppPoints = NULL;

    // Create new collection
    pPoints = new CPoints();
    if (pPoints == NULL)
        goto error;

    pPoints->m_cMax = lMaxSize;
    pPoints->m_cElements = 0;
    pPoints->m_lLBound = lLBound;
    pPoints->m_pPane = pPane;

    // Load type information for the points collection from type library.
    hr = LoadTypeInfo(&pPoints->m_ptinfo, IID_IPoints);
    if (FAILED(hr))
        goto error;

    // Create a safe array which is used to implement the collection.
    sabound[0].cElements = lMaxSize;
    sabound[0].lLbound = lLBound;
    pPoints->m_psa = SafeArrayCreate(VT_VARIANT, 1, sabound);
    if (pPoints->m_psa == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }

    *ppPoints = pPoints;
    return NOERROR;

error:
    if (pPoints == NULL)
        return E_OUTOFMEMORY;
    if (pPoints->m_ptinfo)
        pPoints->m_ptinfo->Release();
    if (pPoints->m_psa)
        SafeArrayDestroy(pPoints->m_psa);

    pPoints->m_psa = NULL;
    pPoints->m_ptinfo = NULL;

    delete pPoints;
    return hr;
}

/*
 * CPoints::CPoints
 *
 * Purpose:
 *  Constructor for CPoints object. Initializes members to NULL.
 *
 */
#pragma warning (disable : 4355)
CPoints::CPoints() : m_SupportErrorInfo(this, IID_IPoints)
#pragma warning (default : 4355)
{
    m_cRef = 0;
    m_psa = NULL;
    m_ptinfo = NULL;
}

/*
 * CPoints::~CPoints
 *
 * Purpose:
 *  Destructor for CPoints object.
 *
 */
CPoints::~CPoints()
{
     if (m_ptinfo) m_ptinfo->Release();
     if (m_psa) SafeArrayDestroy(m_psa);
}

/*
 * CPoints::QueryInterface, AddRef, Release
 *
 * Purpose:
 *  Implements IUnknown::QueryInterface, AddRef, Release
 *
 */
STDMETHODIMP
CPoints::QueryInterface(REFIID iid, void FAR* FAR* ppv)
{
    *ppv = NULL;

    if (iid == IID_IUnknown || iid == IID_IPoints || iid == IID_IDispatch)
        *ppv = this;
    else if (iid == IID_ISupportErrorInfo)
        *ppv = &m_SupportErrorInfo;
    else return E_NOINTERFACE;

    AddRef();
    return NOERROR;
}

STDMETHODIMP_(ULONG)
CPoints::AddRef(void)
{

#ifdef _DEBUG
    TCHAR ach[50];
    wsprintf(ach, TEXT("Ref = %ld, Points\r\n"), m_cRef+1);
    OutputDebugString(ach);
#endif

    return ++m_cRef;
}

STDMETHODIMP_(ULONG)
CPoints::Release(void)
{

#ifdef _DEBUG
    TCHAR ach[50];
    wsprintf(ach, TEXT("Ref = %ld, Points\r\n"), m_cRef-1);
    OutputDebugString(ach);
#endif

    if(--m_cRef == 0)
    {
        delete this;
        return 0;
    }
    return m_cRef;
}

/*
 * CPoints::GetTypeInfoCount
 *
 * Purpose:
 *  Implements IDispatch::GetTypeInfoCount.
 *
 */
STDMETHODIMP
CPoints::GetTypeInfoCount(UINT FAR* pctinfo)
{
    *pctinfo = 1;
    return NOERROR;
}

/*
 * CPoints::GetTypeInfo
 *
 * Purpose:
 *  Implements IDispatch::GetTypeInfo.
 *
 */
STDMETHODIMP
CPoints::GetTypeInfo(
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo)
{
    *pptinfo = NULL;

    if(itinfo != 0)
        return DISP_E_BADINDEX;

    m_ptinfo->AddRef();
    *pptinfo = m_ptinfo;

    return NOERROR;
}

/*
 * CPoints::GetIDsOfNames
 *
 * Purpose:
 *  Implements IDispatch::GetIDsOfNames.  The standard implementation, DispGetIDsOfNames,
 *  is used.
 *
 */
STDMETHODIMP
CPoints::GetIDsOfNames(
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid)
{
    return DispGetIDsOfNames(m_ptinfo, rgszNames, cNames, rgdispid);
}

/*
 * CPoints::Invoke
 *
 * Purpose:
 *  Implements IDispatch::Invoke.  The standard implementation, DispInvoke,
 *  is used.
 *
 */
STDMETHODIMP
CPoints::Invoke(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr)
{
    return DispInvoke(
        this, m_ptinfo,
        dispidMember, wFlags, pdispparams,
        pvarResult, pexcepinfo, puArgErr);
}

/*
 * CPoints::get_Count
 *
 * Purpose:
 *  Returns number of items in collection.
 *
 */
STDMETHODIMP
CPoints::get_Count(long FAR* plCount)
{
    *plCount = m_cElements;
    return NOERROR;
}

/*
 * CPoints::get_Item
 *
 * Purpose:
 *  Retrieves item from collection, given an index.
 *
 * Parameters:
 *   lIndex   Index of item to be retrieved.
 *
 * Returns
 *  IDispatch of item retrieved from collection.
 *
 */
STDMETHODIMP
CPoints::get_Item(long lIndex, IPoint FAR* FAR* ppPoint)
{
    HRESULT hr;
    VARIANT v;
    LPDISPATCH pdisp = NULL;

    // Check if integer index is within range
    if (lIndex < m_lLBound || lIndex >= (long)(m_lLBound+m_cElements))
        return RaiseException(IDS_InvalidIndex, IID_IPoints);

    // Retrieve and return item. Note that SafeArrayGetElement AddRefs so an additional AddRef
    // is not required.
    VariantInit(&v);
    hr = SafeArrayGetElement(m_psa, &lIndex, &v);
    if (FAILED(hr))
        return RaiseException(IDS_Unexpected, IID_IPoints);
    *ppPoint = (IPoint FAR*) V_DISPATCH(&v);
    return NOERROR;
}

/*
 * CPoints::get_NewEnum
 *
 * Purpose:
 *  Returns an enumerator (IEnumVARIANT) for the items curently in the collection.
 *  The NewEnum property is restricted and so is invisible to users of an
 *  automation controller's scripting language. Automation controllers that support
 *  a 'For Each' statement to iterate through the elements of a collection will use
 *  the enumerator returned by NewEnum. The enumerator creates a snapshot of the
 *  the current state of the collection.
 *
 */
STDMETHODIMP
CPoints::get__NewEnum(IUnknown FAR* FAR* ppunkEnum)
{
    CEnumVariant FAR* penum = NULL;
    HRESULT hr;

    // Create new enumerator for items currently in collection and QI for IUnknown
    hr = CEnumVariant::Create(m_psa, m_cElements, &penum);
    if (FAILED(hr))
        {hr = RaiseException(IDS_OutOfMemory, IID_IPoints); goto error;}
    hr = penum->QueryInterface(IID_IUnknown, (VOID FAR* FAR*)ppunkEnum);
    if (FAILED(hr))
        {hr = RaiseException(IDS_Unexpected, IID_IPoints); goto error;}
    return NOERROR;

error:
    if (penum)
        delete penum;
    return hr;
}

/*
 *
 * The following methods are not exposed through Automation
 *
 */

/*
 * CPoints::Add
 *
 * Purpose:
 *  Adds an item to the collection. The points collection does not have duplicates.
 *
 * Parameters:
 *  pPointAdd    Point to be added to collection.
 *
 */
STDMETHODIMP_(BOOL)
CPoints::Add(CPoint FAR* pPointAdd)
{
    HRESULT hr;
    LONG l;
    VARIANT v;
    VARIANT HUGEP *pvar;
    int nX, nY;
    BOOL bFound = FALSE;
    LPDISPATCH pdispPointAdd = NULL;
    CPoint FAR *pPoint;
    LPDISPATCH pdispPoint;

    // Is the collection full?
    if (m_cElements == m_cMax)
        goto error;

    // Get coordinates of point to be added to collection
    nX = pPointAdd->get_x();
    nY = pPointAdd->get_y();

    hr = SafeArrayAccessData(m_psa, (void HUGEP* FAR*)&pvar);
    if (FAILED(hr))
        goto error;

    // Check if point to be added is already in the collection.
    for (l=0; l<(long)m_cElements; l++)
    {
        pdispPoint = V_DISPATCH(&pvar[l]);
        hr = pdispPoint->QueryInterface(IID_IPoint, (void FAR* FAR*)&pPoint);
        if (FAILED(hr))
            continue;
        if (nX == pPoint->get_x() &&  nY == pPoint->get_y())
        {
            // Point is already in the collection. AddRef it.
            // Internal AddRef keeps track of number of times this point was added to this collection. When
            //    this internal ref count drops to 0, this point can be removed from this collection.
            pPoint->InternalAddRef();
            pdispPoint->AddRef();

            bFound = TRUE;
            pPoint->Release();
            break;
        }
        pPoint->Release();
    }
    hr = SafeArrayUnaccessData(m_psa);
    if (FAILED(hr))
       goto error;

    // Add point if it was not in the collection.
    if (!bFound)
    {
        l = m_lLBound+m_cElements;

        hr = pPointAdd->QueryInterface(IID_IDispatch, (void FAR* FAR*)&pdispPointAdd);
        if (FAILED(hr))
            goto error;
        VariantInit(&v);
        V_VT(&v) = VT_DISPATCH;
        V_DISPATCH(&v) = pdispPointAdd;
        hr = SafeArrayPutElement(m_psa, &l, &v);
        if (FAILED(hr))
            goto error;

        // Internal AddRef keeps track of number of times this point was added to this collection. When
        //    this internal ref count drops to 0, this point can be removed from this collection.
        pPointAdd->InternalAddRef();
        pdispPointAdd->Release();

        m_cElements++;
    }
    return TRUE;

error:
    if (pdispPointAdd)
        pdispPointAdd->Release();
    return FALSE;
}

/*
 * CPoints::Remove
 *
 * Purpose:
 *  Removes specified item from collection. The points collection does not have duplicates.
 *
 * Parameters:
 *   pPointRemove    Point to be removed from collection.
 *
 */
STDMETHODIMP_(BOOL)
CPoints::Remove(CPoint FAR* pPointRemove)
{
    HRESULT hr;
    long l, lIndex;
    VARIANT HUGEP *pvar;
    ULONG cRef;
    int nX, nY;
    CPoint FAR* pPoint = NULL;
    BOOL bFound = FALSE;
    BOOL bRet = FALSE;

    // Get coordinates of point to be removed from collection
    nX = pPointRemove->get_x();
    nY = pPointRemove->get_y();

    hr = SafeArrayAccessData(m_psa, (void HUGEP* FAR*)&pvar);
    if (FAILED(hr))
       goto error;

    // Check if point to be removed is in the collection.
    for (l=0; l<(long)m_cElements; l++)
    {
        hr = V_DISPATCH(&pvar[l])->QueryInterface(IID_IPoint, (void FAR* FAR*)&pPoint);
        if (FAILED(hr))
            continue;

        if (nX == pPoint->get_x() &&  nY == pPoint->get_y())
        {
            // Release point. Note that this collection does not have duplicates. Duplicate
            // points are handled by increasing the ref count.
            V_DISPATCH(&pvar[l])->Release();
            cRef = pPoint->InternalRelease();
            bFound = TRUE;
            lIndex = l;
            pPoint->Release();
            break;
        }
        pPoint->Release();
    }

    // If the internal ref count of point to be removed has dropped to 0, move up the array elements
    // after the element to be removed.
    if (bFound && cRef == 0)
    {
        for (l=lIndex; l<(long)(m_cElements-1); l++)
            pvar[l] = pvar[l+1];

        // Remove last element.
        V_VT(&pvar[l]) = VT_EMPTY;
        m_cElements--;
        bRet = TRUE;
    }

    hr = SafeArrayUnaccessData(m_psa);
    if (FAILED(hr))
       goto error;
    return bRet;

error:
    return FALSE;
}

/*
 * CPoints::Clear
 *
 * Purpose:
 *  Removes all items from collection.
 *
 */
STDMETHODIMP_(void)
CPoints::Clear(void)
{
    SafeArrayDestroyData(m_psa);
    SafeArrayAllocData(m_psa);
    m_cElements = 0;
}

/*
 * CPoints::GetMaxXMaxY
 *
 * Purpose:
 *  Get the maximum X and Y coordinates that are in the rectangle, prc.
 *
 */
STDMETHODIMP_(void)
CPoints::GetMaxXMaxY(int FAR *pnX, int FAR *pnY, LPRECT prc)
{
    HRESULT hr;
    long l;
    VARIANT HUGEP *pvar;
    int nMaxX, nMaxY;
    CPoint FAR* pPoint;
    POINT pt;

    hr = SafeArrayAccessData(m_psa, (void HUGEP* FAR*)&pvar);
    if (FAILED(hr))
       return;

    nMaxX = -1; nMaxY = -1;

    // Find the maximume X and Y coordinates that are in the prc rectangle.
    for (l=0; l<(long)m_cElements; l++)
    {
        V_DISPATCH(&pvar[l])->QueryInterface(IID_IPoint, (void FAR* FAR*)&pPoint);
        pt.x = pPoint->get_x();
        pt.y = pPoint->get_y();

        if (PtInRect(prc, pt))
        {
            if (pt.x > nMaxX)
                nMaxX = pt.x;

            if (pt.y > nMaxY)
                nMaxY = pt.y;
        }
        pPoint->Release();
    }

    *pnX = nMaxX;
    *pnY = nMaxY;

    SafeArrayUnaccessData(m_psa);
    return;
}


APP.CPP

/*************************************************************************
**
**  This is a part of the Microsoft Source Code Samples.
**
**  Copyright 1992 - 1998 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.
**
**  OLE Automation Lines Object.
**
**  app.cpp
**
**  CApplication implementation
**
**  Written by Microsoft Product Support Services, Windows Developer Support
**
*************************************************************************/

#include <windows.h>
#include <windowsx.h>
#ifdef WIN16
  #include <ole2.h>
  #include <compobj.h>
  #include <dispatch.h>
  #include <variant.h>
  #include <olenls.h>
#endif
#include "lines.h"

/*
 * CApplication::Create
 *
 * Purpose:
 *  Creates an instance of the Application automation object and initializes it.
 *
 * Parameters:
 *  hinst            HINSTANCE of application.
 *  ppApplication    Returns Application automation object.
 *
 * Return Value:
 *  HRESULT
 *
 */
HRESULT
CApplication::Create(HINSTANCE hinst, CApplication FAR* FAR* ppApplication )
{
    TCHAR ach[STR_LEN];
    TCHAR achFullName[260];
    HRESULT hr;
    CApplication FAR* pApplication = NULL;
    HWND hwnd;

    *ppApplication = NULL;

    // Create application object.
    pApplication = new CApplication();
    if (pApplication == NULL)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }

    LoadString(hinst, IDS_Name, ach, sizeof(ach));
    hwnd = CreateWindow(TEXT("MainWndClass"), ach,
                       WS_OVERLAPPEDWINDOW,
                       CW_USEDEFAULT, CW_USEDEFAULT,
                       650, 650,
                       NULL, NULL, hinst, NULL);
    if (!hwnd)
    {
       hr = E_OUTOFMEMORY;
       goto error;
    }
    pApplication->m_hwnd = hwnd;
    pApplication->m_hinst = hinst;

    // Set name and fullname of application.
    pApplication->m_bstrName = SysAllocString(TO_OLE_STRING(ach));
    if (NULL == pApplication->m_bstrName)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }
    GetModuleFileName(hinst, achFullName, sizeof(achFullName));
    pApplication->m_bstrFullName = SysAllocString(TO_OLE_STRING(achFullName));
    if (NULL == pApplication->m_bstrFullName)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }
    // ProgID
    LoadString(hinst, IDS_ProgID, ach, sizeof(ach));
    pApplication->m_bstrProgID = SysAllocString(TO_OLE_STRING(ach));
    if (NULL == pApplication->m_bstrProgID)
    {
        hr = E_OUTOFMEMORY;
        goto error;
    }

    // Load type information for the application object from type library.
    // If required, notify user on failure.
    hr = LoadTypeInfo(&pApplication->m_ptinfo, IID_IApplication);
    if (FAILED(hr))
    {
        LoadString(hinst, IDS_ErrorLoadingTypeLib, ach, sizeof(ach));
        MessageBox(NULL, ach, FROM_OLE_STRING(pApplication->m_bstrName), MB_OK);
        goto error;
    }

    // Create Pane
    hr = CPane::Create(hwnd, &pApplication->m_pPane);
    if (FAILED(hr))
       goto error;
    pApplication->m_pPane->AddRef();

    *ppApplication = pApplication;
    return NOERROR;

error:
    if (pApplication == NULL)
        return E_OUTOFMEMORY;

    if (pApplication->m_bstrFullName)
        SysFreeString(pApplication->m_bstrFullName);
    if (pApplication->m_bstrName)
        SysFreeString(pApplication->m_bstrName);
    if (pApplication->m_bstrProgID)
        SysFreeString(pApplication->m_bstrProgID);
    if (pApplication->m_ptinfo)
        pApplication->m_ptinfo->Release();
    if (pApplication->m_pPane)
        pApplication->m_pPane->Release();

    // Set to NULL to prevent destructor from attempting to free again
    pApplication->m_bstrFullName = NULL;
    pApplication->m_bstrName = NULL;
        pApplication->m_bstrProgID = NULL;
    pApplication->m_ptinfo = NULL;
    pApplication->m_pPane = NULL;

    delete pApplication;
    return hr;
}

/*
 * CApplication::CApplication
 *
 * Purpose:
 *  Constructor for CApplication object. Initializes members to NULL.
 *
 */
#pragma warning (disable : 4355)
CApplication::CApplication() : m_SupportErrorInfo(this, IID_IApplication)
#pragma warning (default : 4355)
{
    extern ULONG g_cObj;

    m_hwnd = NULL;
    m_bstrFullName = NULL;
    m_bstrName = NULL;
    m_bstrProgID = NULL;
    m_ptinfo = NULL;
    m_pPane = NULL;
    m_cRef = 0;
    m_bVisible = 0;
    m_bUserClosing = FALSE;
}

/*
 * CApplication::~CApplication
 *
 * Purpose:
 *  Destructor for CApplication object. Frees Application message BSTR and default
 *  IDispatch implementation. Closes the aplication.
 *
 */
CApplication::~CApplication()
{
     extern ULONG g_cObj;

     if (m_bstrFullName) SysFreeString(m_bstrFullName);
     if (m_bstrName) SysFreeString(m_bstrName);
     if (m_bstrProgID) SysFreeString(m_bstrProgID);
     if (m_ptinfo) m_ptinfo->Release();
     if (m_pPane) m_pPane->Release();
     if (!m_bUserClosing && IsWindow(m_hwnd)) DestroyWindow(m_hwnd);
}

/*
 * CApplication::QueryInterface, AddRef, Release
 *
 * Purpose:
 *  Implements IUnknown::QueryInterface, AddRef, Release
 *
 */
STDMETHODIMP
CApplication::QueryInterface(REFIID iid, void FAR* FAR* ppv)
{
    *ppv = NULL;

    if (iid == IID_IUnknown || iid == IID_IDispatch || iid == IID_IApplication)
        *ppv = this;
    else if (iid == IID_ISupportErrorInfo)
        *ppv = &m_SupportErrorInfo;
    else return E_NOINTERFACE;

    AddRef();
    return NOERROR;
}

STDMETHODIMP_(ULONG)
CApplication::AddRef(void)
{
#ifdef _DEBUG
    TCHAR ach[50];
    wsprintf(ach, TEXT("Ref = %ld, App\r\n"), m_cRef+1);
    OutputDebugString(ach);
#endif

    return ++m_cRef;
}

STDMETHODIMP_(ULONG)
CApplication::Release(void)
{
#ifdef _DEBUG
    TCHAR ach[50];
    wsprintf(ach, TEXT("Ref = %ld, App\r\n"), m_cRef-1);
    OutputDebugString(ach);
#endif

    if(--m_cRef == 0)
    {
        delete this;
        return 0;
    }
    return m_cRef;
}

/*
 * CApplication::GetTypeInfoCount
 *
 * Purpose:
 *  Implements IDispatch::GetTypeInfoCount.
 *
 */
STDMETHODIMP
CApplication::GetTypeInfoCount(UINT FAR* pctinfo)
{
    *pctinfo = 1;
    return NOERROR;
}

/*
 * CApplication::GetTypeInfo
 *
 * Purpose:
 *  Implements IDispatch::GetTypeInfo.
 *
 */
STDMETHODIMP
CApplication::GetTypeInfo(
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo)
{
    *pptinfo = NULL;

    if(itinfo != 0)
        return DISP_E_BADINDEX;

    m_ptinfo->AddRef();
    *pptinfo = m_ptinfo;

    return NOERROR;
}

/*
 * CApplication::GetIDsOfNames
 *
 * Purpose:
 *  Implements IDispatch::GetIDsOfNames.  The standard implementation, DispGetIDsOfNames,
 *  is used.
 *
 */
STDMETHODIMP
CApplication::GetIDsOfNames(
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid)
{
    return DispGetIDsOfNames(m_ptinfo, rgszNames, cNames, rgdispid);
}

/*
 * CApplication::Invoke
 *
 * Purpose:
 *  Implements IDispatch::Invoke.  The standard implementation, DispInvoke,
 *  is used.
 *
 */
STDMETHODIMP
CApplication::Invoke(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr)
{
   return DispInvoke(
        this, m_ptinfo,
        dispidMember, wFlags, pdispparams,
        pvarResult, pexcepinfo, puArgErr);
}

/*
 * CApplication::get_Application, get_FullName, get_Name, get_Parent, get_Visible, put_Visible, Quit
 *
 * Purpose:
 *  Implements the standard Application, FullName, Name, Parent & Visible properties
 *  and the Quit method.
 *
 */
STDMETHODIMP
CApplication::get_Application(IApplication FAR* FAR* ppApplication)
{
    HRESULT hr;

    hr = QueryInterface(IID_IDispatch, (void FAR* FAR*)ppApplication);
    if (FAILED(hr))
        return RaiseException(IDS_Unexpected, IID_IApplication);
    return NOERROR;
}

STDMETHODIMP
CApplication::get_FullName(BSTR FAR* pbstr)
{
    *pbstr = SysAllocString(m_bstrFullName);
    return NOERROR;
}

STDMETHODIMP
CApplication::get_Name(BSTR FAR* pbstr)
{
    *pbstr = SysAllocString(m_bstrName);
    return NOERROR;
}

STDMETHODIMP
CApplication::get_Parent(IApplication FAR* FAR* ppApplication)
{
    HRESULT hr;

    hr = QueryInterface(IID_IDispatch, (void FAR* FAR*)ppApplication);
    if (FAILED(hr))
        return RaiseException(IDS_Unexpected, IID_IApplication);
    return NOERROR;
}

STDMETHODIMP
CApplication::put_Visible(VARIANT_BOOL bVisible)
{
    ShowWindow(bVisible ? SW_SHOW : SW_HIDE);
    return NOERROR;
}

STDMETHODIMP
CApplication::get_Visible(VARIANT_BOOL FAR* pbVisible)
{
    *pbVisible = m_bVisible;
    return NOERROR;
}

STDMETHODIMP
CApplication::Quit()
{
    // CoDisconnectObject has no effect for an inproc server.  So the controller
    // will GP fault if it attempts to access the object (including calling IUnknown::Release())
    // after Quit has been called. For a local server, CoDisconnectObject will disconnect
    // the object from external connections. So the controller will get an RPC error if
    // it access the object after calling Quit. The controller will not GP fault in this case.
    CoDisconnectObject((LPUNKNOWN)this, 0);
    PostMessage(m_hwnd, WM_CLOSE, 0, 0L);
    return NOERROR;
}

/*
 * CApplication::get_Pane
 *
 * Purpose:
 *  Returns pane object.
 *
 */
STDMETHODIMP
CApplication::get_Pane(IPane FAR* FAR* ppPane)
{
    HRESULT hr;

    hr = m_pPane->QueryInterface(IID_IDispatch, (void FAR* FAR*)ppPane);
    if (FAILED(hr))
        return RaiseException(IDS_Unexpected, IID_IApplication);
    return NOERROR;
}

/*
 * CApplication::CreateLine
 *
 * Purpose:
 *  Returns a newly created line object with no start or end point.
 *
 */
STDMETHODIMP
CApplication::CreateLine(ILine FAR* FAR* ppLine)
{
    CLine FAR* pline = NULL;
    HRESULT hr;

    // Create new item and QI for IDispatch
    hr = CLine::Create(&pline);
    if (FAILED(hr))
        {hr = RaiseException(IDS_OutOfMemory, IID_IApplication); goto error;}

    hr = pline->QueryInterface(IID_IDispatch, (void FAR* FAR*)ppLine);
    if (FAILED(hr))
        {hr = RaiseException(IDS_Unexpected, IID_IApplication); goto error;}
    return NOERROR;

error:
    if (pline)
        delete pline;
    return hr;
}

/*
 * CApplication::CreateLine
 *
 * Purpose:
 *  Returns a newly created point object intialized to (0,0).
 *
 */
STDMETHODIMP
CApplication::CreatePoint(IPoint FAR* FAR* ppPoint)
{
    CPoint FAR* ppoint = NULL;
    HRESULT hr;

    // Create new item and QI for IDispatch
    hr = CPoint::Create(&ppoint);
    if (FAILED(hr))
        {hr = RaiseException(IDS_OutOfMemory, IID_IApplication); goto error;}

    hr = ppoint->QueryInterface(IID_IDispatch, (void FAR* FAR*)ppPoint);
    if (FAILED(hr))
        {hr = RaiseException(IDS_Unexpected, IID_IApplication); goto error;}
    return NOERROR;

error:
    if (ppoint)
        delete ppoint;
    return hr;
}

/*
 *
 * The following methods are not exposed through Automation
 *
 */

STDMETHODIMP_(void)
CApplication::Draw()
{
   m_pPane->Draw();
}

/*
 * CApplication::OnSize
 *
 * Purpose:
 *  Called when application window receives WM_SIZE.
 *
 */
STDMETHODIMP_(void)
CApplication::OnSize(unsigned int nWidth, unsigned int nHeight)
{
   m_pPane->OnSize(nWidth, nHeight);
}

STDMETHODIMP_(void)
CApplication::ShowWindow(int nCmdShow)
{
    // Return if curently hidden and asked to hide or currently visible
    // and asked to show.
    if ((!m_bVisible && nCmdShow == SW_HIDE) || (m_bVisible && nCmdShow != SW_HIDE))
        return;

    m_bVisible = (nCmdShow == SW_HIDE) ? FALSE : TRUE;

    // The Automation object shutdown behavior is as follows:
    // 1. If the object application is visible, it shuts down only in response to an
    // explicit user command (File/Exit) or it's programmatic equivalent (for example
    // the Quit method of the Application object).
    // 2. If the object application is not visible, it goes away when it's last
    // object is released.
    //
    // CoLockObjectExternal can be used to increment the ref count of the application object
    // when it is visible. This will implement shutdown behavior 1. When the application
    // goes invisible, CoLockObjectExternal is used to decrement the ref count. This will
    // implement shutdown behavior 2.

    if (m_bVisible)
        CoLockObjectExternal(this, TRUE /*fLock*/, TRUE/*ignored when fLock==TRUE*/);
    else CoLockObjectExternal(this, FALSE/*fLock*/, TRUE/*fLastLockReleases*/);
    ::ShowWindow (m_hwnd, nCmdShow);
}

STDMETHODIMP_(void)
CApplication::CreateAndDrawLine()
{
    LINEINFO lineinfo;
    CLine FAR* pLine = NULL;
    CPoint FAR* pPointStart = NULL;
    CPoint FAR* pPointEnd = NULL;
    ILines FAR* pLines = NULL;
    int nRet;
    HRESULT hr;

    nRet = DialogBoxParam(m_hinst, MAKEINTRESOURCE(IDD_DRAWLINE), m_hwnd,
                      (DLGPROC)DrawLineDialogFunc, (LPARAM)(LPLINEINFO)&lineinfo);
    if (nRet != IDOK)
        return;
    hr = CLine::Create(&pLine);
    if (FAILED(hr))
        goto error;
    hr = CPoint::Create(&pPointStart);
    if (FAILED(hr))
        goto error;
    hr = CPoint::Create(&pPointEnd);
    if (FAILED(hr))
        goto error;

    pPointStart->put_x(lineinfo.ptStart.x);
    pPointStart->put_y(lineinfo.ptStart.y);
    pPointEnd->put_x(lineinfo.ptEnd.x);
    pPointEnd->put_y(lineinfo.ptEnd.y);

    pLine->putref_StartPoint(pPointStart);
    pLine->putref_EndPoint(pPointEnd);
    pLine->put_Thickness(lineinfo.nThickness);
    pLine->put_Color(lineinfo.colorref);

    hr = m_pPane->get_Lines(&pLines);
    if (FAILED(hr))
        goto error;
    hr = pLines->Add(pLine);
    if (FAILED(hr))
        goto error;
    pLines->Release();
    return;

error:
    if (pLine) delete pLine;
    if (pPointStart) delete pPointStart;
    if (pPointEnd) delete pPointEnd;
    if (pLines) pLines->Release();
    MessageBox(m_hwnd, TEXT("Cannot create Line"), FROM_OLE_STRING(m_bstrName), MB_OK);
}

STDMETHODIMP_(void)
CApplication::ClearPane()
{
    m_pPane->Clear();
}


TLB.H

/* This header file machine-generated by mktyplib.exe */
/* Interface to type library: Lines */

#ifndef _Lines_H_
#define _Lines_H_

DEFINE_GUID(LIBID_Lines,0x3C591B20L,0x1F13,0x101B,0xB8,0x26,0x00,0xDD,0x01,0x10,0x3D,0xE1);

DEFINE_GUID(IID_IPoint,0x3C591B25L,0x1F13,0x101B,0xB8,0x26,0x00,0xDD,0x01,0x10,0x3D,0xE1);

/* Definition of interface: IPoint */
#undef INTERFACE
#define INTERFACE IPoint

DECLARE_INTERFACE_(IPoint, IDispatch)
{
#ifndef NO_BASEINTERFACE_FUNCS

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(THIS_ UINT FAR* pctinfo) PURE;

    STDMETHOD(GetTypeInfo)(
      THIS_
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo) PURE;

    STDMETHOD(GetIDsOfNames)(
      THIS_
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid) PURE;

    STDMETHOD(Invoke)(
      THIS_
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr) PURE;
#endif

    /* IPoint methods */
    STDMETHOD(get_x)(THIS_ int FAR* retval) PURE;
    STDMETHOD(put_x)(THIS_ int Value) PURE;
    STDMETHOD(get_y)(THIS_ int FAR* retval) PURE;
    STDMETHOD(put_y)(THIS_ int Value) PURE;
};

DEFINE_GUID(IID_ILine,0x3C591B24L,0x1F13,0x101B,0xB8,0x26,0x00,0xDD,0x01,0x10,0x3D,0xE1);

/* Definition of interface: ILine */
#undef INTERFACE
#define INTERFACE ILine

DECLARE_INTERFACE_(ILine, IDispatch)
{
#ifndef NO_BASEINTERFACE_FUNCS

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(THIS_ UINT FAR* pctinfo) PURE;

    STDMETHOD(GetTypeInfo)(
      THIS_
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo) PURE;

    STDMETHOD(GetIDsOfNames)(
      THIS_
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid) PURE;

    STDMETHOD(Invoke)(
      THIS_
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr) PURE;
#endif

    /* ILine methods */
    STDMETHOD(get_Color)(THIS_ long FAR* retval) PURE;
    STDMETHOD(put_Color)(THIS_ long rgb) PURE;
    STDMETHOD(get_EndPoint)(THIS_ IPoint FAR* FAR* retval) PURE;
    STDMETHOD(putref_EndPoint)(THIS_ IPoint FAR* Point) PURE;
    STDMETHOD(get_StartPoint)(THIS_ IPoint FAR* FAR* retval) PURE;
    STDMETHOD(putref_StartPoint)(THIS_ IPoint FAR* Point) PURE;
    STDMETHOD(get_Thickness)(THIS_ int FAR* retval) PURE;
    STDMETHOD(put_Thickness)(THIS_ int Value) PURE;
};

DEFINE_GUID(IID_ILines,0x3C591B26L,0x1F13,0x101B,0xB8,0x26,0x00,0xDD,0x01,0x10,0x3D,0xE1);

/* Definition of interface: ILines */
#undef INTERFACE
#define INTERFACE ILines

DECLARE_INTERFACE_(ILines, IDispatch)
{
#ifndef NO_BASEINTERFACE_FUNCS

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(THIS_ UINT FAR* pctinfo) PURE;

    STDMETHOD(GetTypeInfo)(
      THIS_
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo) PURE;

    STDMETHOD(GetIDsOfNames)(
      THIS_
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid) PURE;

    STDMETHOD(Invoke)(
      THIS_
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr) PURE;
#endif

    /* ILines methods */
    STDMETHOD(Add)(THIS_ ILine FAR* NewLine) PURE;
    STDMETHOD(get_Count)(THIS_ long FAR* retval) PURE;
    STDMETHOD(get_Item)(THIS_ long Index, ILine FAR* FAR* retval) PURE;
    STDMETHOD(get__NewEnum)(THIS_ IUnknown * FAR* retval) PURE;
    STDMETHOD(Remove)(THIS_ long Index) PURE;
};

DEFINE_GUID(IID_IPoints,0x3C591B27L,0x1F13,0x101B,0xB8,0x26,0x00,0xDD,0x01,0x10,0x3D,0xE1);

/* Definition of interface: IPoints */
#undef INTERFACE
#define INTERFACE IPoints

DECLARE_INTERFACE_(IPoints, IDispatch)
{
#ifndef NO_BASEINTERFACE_FUNCS

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(THIS_ UINT FAR* pctinfo) PURE;

    STDMETHOD(GetTypeInfo)(
      THIS_
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo) PURE;

    STDMETHOD(GetIDsOfNames)(
      THIS_
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid) PURE;

    STDMETHOD(Invoke)(
      THIS_
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr) PURE;
#endif

    /* IPoints methods */
    STDMETHOD(get_Count)(THIS_ long FAR* retval) PURE;
    STDMETHOD(get_Item)(THIS_ long Index, IPoint FAR* FAR* retval) PURE;
    STDMETHOD(get__NewEnum)(THIS_ IUnknown * FAR* retval) PURE;
};

DEFINE_GUID(IID_IPane,0x3C591B23L,0x1F13,0x101B,0xB8,0x26,0x00,0xDD,0x01,0x10,0x3D,0xE1);

/* Definition of interface: IPane */
#undef INTERFACE
#define INTERFACE IPane

DECLARE_INTERFACE_(IPane, IDispatch)
{
#ifndef NO_BASEINTERFACE_FUNCS

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(THIS_ UINT FAR* pctinfo) PURE;

    STDMETHOD(GetTypeInfo)(
      THIS_
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo) PURE;

    STDMETHOD(GetIDsOfNames)(
      THIS_
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid) PURE;

    STDMETHOD(Invoke)(
      THIS_
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr) PURE;
#endif

    /* IPane methods */
    STDMETHOD(get_Lines)(THIS_ ILines FAR* FAR* retval) PURE;
    STDMETHOD(get_Points)(THIS_ IPoints FAR* FAR* retval) PURE;
    STDMETHOD(get_MaxX)(THIS_ int FAR* retval) PURE;
    STDMETHOD(get_MaxY)(THIS_ int FAR* retval) PURE;
    STDMETHOD(Clear)(THIS) PURE;
    STDMETHOD(Refresh)(THIS) PURE;
};

DEFINE_GUID(IID_IApplication,0x3C591B22L,0x1F13,0x101B,0xB8,0x26,0x00,0xDD,0x01,0x10,0x3D,0xE1);

/* Definition of interface: IApplication */
#undef INTERFACE
#define INTERFACE IApplication

DECLARE_INTERFACE_(IApplication, IDispatch)
{
#ifndef NO_BASEINTERFACE_FUNCS

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(THIS_ REFIID riid, LPVOID FAR* ppvObj) PURE;
    STDMETHOD_(ULONG, AddRef)(THIS) PURE;
    STDMETHOD_(ULONG, Release)(THIS) PURE;

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(THIS_ UINT FAR* pctinfo) PURE;

    STDMETHOD(GetTypeInfo)(
      THIS_
      UINT itinfo,
      LCID lcid,
      ITypeInfo FAR* FAR* pptinfo) PURE;

    STDMETHOD(GetIDsOfNames)(
      THIS_
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      UINT cNames,
      LCID lcid,
      DISPID FAR* rgdispid) PURE;

    STDMETHOD(Invoke)(
      THIS_
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      WORD wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      UINT FAR* puArgErr) PURE;
#endif

    /* IApplication methods */
    STDMETHOD(get_Application)(THIS_ IApplication FAR* FAR* retval) PURE;
    STDMETHOD(get_FullName)(THIS_ BSTR FAR* retval) PURE;
    STDMETHOD(get_Name)(THIS_ BSTR FAR* retval) PURE;
    STDMETHOD(get_Parent)(THIS_ IApplication FAR* FAR* retval) PURE;
    STDMETHOD(put_Visible)(THIS_ VARIANT_BOOL VisibleFlag) PURE;
    STDMETHOD(get_Visible)(THIS_ VARIANT_BOOL FAR* retval) PURE;
    STDMETHOD(Quit)(THIS) PURE;
    STDMETHOD(get_Pane)(THIS_ IPane FAR* FAR* retval) PURE;
    STDMETHOD(CreateLine)(THIS_ ILine FAR* FAR* retval) PURE;
    STDMETHOD(CreatePoint)(THIS_ IPoint FAR* FAR* retval) PURE;
};

DEFINE_GUID(CLSID_Lines,0x3C591B21L,0x1F13,0x101B,0xB8,0x26,0x00,0xDD,0x01,0x10,0x3D,0xE1);

#ifdef __cplusplus
class Lines;
#endif

#endif


VB.FRM

VERSION 4.00
Begin VB.Form Form1
   Caption         =   "Form1"
   ClientHeight    =   2745
   ClientLeft      =   870
   ClientTop       =   1530
   ClientWidth     =   4785
   BeginProperty Font
      name            =   "MS Sans Serif"
      charset         =   0
      weight          =   700
      size            =   8.25
      underline       =   0   'False
      italic          =   0   'False
      strikethrough   =   0   'False
   EndProperty
   Height          =   3150
   Left            =   810
   LinkTopic       =   "Form1"
   ScaleHeight     =   2745
   ScaleWidth      =   4785
   Top             =   1185
   Width           =   4905
   Begin VB.CommandButton RemoveTest
      Caption         =   "RemoveTest"
      BeginProperty Font
         name            =   "MS Sans Serif"
         charset         =   0
         weight          =   700
         size            =   8.25
         underline       =   0   'False
         italic          =   0   'False
         strikethrough   =   0   'False
      EndProperty
      Height          =   372
      Left            =   1200
      TabIndex        =   2
      Top             =   1680
      Width           =   2292
   End
   Begin VB.CommandButton Clear
      Caption         =   "Clear"
      BeginProperty Font
         name            =   "MS Sans Serif"
         charset         =   0
         weight          =   700
         size            =   8.25
         underline       =   0   'False
         italic          =   0   'False
         strikethrough   =   0   'False
      EndProperty
      Height          =   372
      Left            =   1200
      TabIndex        =   1
      Top             =   960
      Width           =   2292
   End
   Begin VB.CommandButton DrawLines
      Caption         =   "Draw Lines"
      BeginProperty Font
         name            =   "MS Sans Serif"
         charset         =   0
         weight          =   700
         size            =   8.25
         underline       =   0   'False
         italic          =   0   'False
         strikethrough   =   0   'False
      EndProperty
      Height          =   372
      Left            =   1200
      TabIndex        =   0
      Top             =   240
      Width           =   2292
   End
End
Attribute VB_Name = "Form1"
Attribute VB_Creatable = False
Attribute VB_Exposed = False
'VB script to control the Lines automation server.

'Use the Tools/References menu to select the Lines type library before
'running this code.

Dim Application As IApplication
Dim Pane As IPane
Dim Lines As ILines
Dim L As ILine
Dim P1, P2 As IPoint

Private Sub Form_Load()
 Set Application = New Lines.Lines   'The first Lines in Lines.Lines is the library name;
                           'the second is the coclass. If the coclass and the
                           'library did'nt have the same name, just the coclass
                           'would be sufficient have been sufficient. i.e. Set Application = New Lines
                           'New Lines.Lines will create an object that supports the default
                           'interface (IApplication) of the Lines coclass
 Application.Visible = True
 Set Pane = Application.Pane
End Sub

'Draw 10 lines
Private Sub DrawLines_Click()
 Set Lines = Pane.Lines

 'Anchor one end point
 Set P1 = Application.CreatePoint()
 P1.x = 4500    'in twips
 P1.y = 4500    'in twips

  'Add 10 lines to the Lines collection. The Lines server can handle a maximum of 100 lines
 For I = 1 To 10
   'Create other end point of line
   Set P2 = Application.CreatePoint()
   P2.x = Int(9000 * Rnd + 1)   'in twips
   P2.y = Int(9000 * Rnd + 1)   'in twips

   'Create line
   Set L = Application.CreateLine()
   Set L.StartPoint = P1
   Set L.EndPoint = P2
   L.Thickness = 100             'in twips
   'Select a random color
   L.Color = RGB(Int(255 * Rnd + 1), Int(255 * Rnd + 1), Int(255 * Rnd + 1))

   'Add line to Lines collection
   Lines.Add L
 Next I
End Sub

'Clear the pane and remove all lines from collection
Private Sub Clear_Click()
 Pane.Clear
End Sub

'Remove the first line
Private Sub RemoveTest_Click()
  Set Lines = Pane.Lines
  Lines.Remove 1
  Debug.Print Lines.Count   'Print the number of lines in collection after line removal
End Sub


README.TXT

OLE Automation Sample

SUMMARY
=======

The SPoly sample draws polygons. The only way to make SPoly draw a polygon
is to use its programmability interface.

One OLE automation object is exposed by Spoly: spoly.application.
The object spoly.application is associated with SPoly's main application
window. It controls drawing polygons and clearing the display.

MORE INFORMATION
================

SPoly implements IDispatch for spoly.application.

Methods defined on spoly.application
-----------------------------------

Name                        Description

Draw                        Draw the polygon.
Reset                       Delete all points from the polygon.
AddPoint(X, Y)              Add a point with coordinates (x,y) to the
                            polygon.
EnumPoints as VT_ENUM       Return a collection of the polygon's points.
GetXOrigin as short         Get the X origin of the polygon.
SetXOrigin(x as short)      Set the X origin of the polygon.
GetYOrigin as short         Get the Y origin of the polygon.
SetYOrigin(y as short)      Set the Y origin of the polygon.
GetWidth as short           Get the line width of the polygon.
SetWidth(width as short)    Set the line width of the polygon.

Shortcomings of this sample
---------------------------

Many items in this sample should be properties. Instead, they are
implemented as methods. Anything that behaves like an attribute of the
object should be a property.

This is not a good example of how to implement a collection.


CENUMPT.H

/***
*cenumpt.h
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  Definition of the CEnumPoint class.
*
*Implementation Notes:
*
*****************************************************************************/

class CEnumPoint : public IEnumVARIANT
{
public:
    static HRESULT Create(SAFEARRAY FAR* psa, CEnumPoint FAR* FAR* ppenum);

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppv);
    STDMETHOD_(unsigned long, AddRef)(void);
    STDMETHOD_(unsigned long, Release)(void);

    /* IEnumVARIANT methods */
    STDMETHOD(Next)(unsigned long celt, VARIANT FAR* rgvar, unsigned long FAR* pceltFetched);
    STDMETHOD(Skip)(unsigned long celt);
    STDMETHOD(Reset)(void);
    STDMETHOD(Clone)(IEnumVARIANT FAR* FAR* ppenum);

    CEnumPoint();

private:

    unsigned long m_refs;

    unsigned long m_celts;
    unsigned long m_iCurrent;
    SAFEARRAY FAR* m_psa;
};


CLSID.H

/***
*clsid.h
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  This file defines the CLSIDs
*
*Implementation Notes:
*
*****************************************************************************/

DEFINE_GUID(CLSID_CPoly, 0x00020462, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
DEFINE_GUID(CLSID_CPoint, 0x00020463, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);

#ifdef INITGUID
# ifdef _MAC
DEFINE_GUID(GUID_NULL, 0L, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
/* For the PPC Metroworks build do not define these */
#  if !defined(_MW_BUILD)
DEFINE_GUID(IID_IDispatch, 0x00020400L, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
DEFINE_GUID(IID_IEnumVARIANT, 0x00020404L, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
#  endif
DEFINE_GUID(IID_IUnknown, 0x00000000L, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
DEFINE_GUID(IID_IClassFactory, 0x00000001L, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
# endif
#endif


CPOINT.CPP

/***
*cpoint.cpp
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  This module implements the CPoint and CPointCF classes.
*
*  This module is intended as a sample implementation of the IDispatch
*  interface, and its purpose is to demonstrate how an object can
*  expose methods and properties for programatic and cross-process
*  access via the IDispatch interface.
*
*Implementation Notes:
*
*****************************************************************************/

#include "spoly.h"
#include "cpoint.h"

CPoint::CPoint()
{
    m_x = 0;
    m_y = 0;
    m_refs = 0;
}

/***
*CPoint::Create(void)
*Purpose:
*  Create an instance of a CPoint object.
*
*Entry:
*  None
*
*Exit:
*  returns a CPoint*, NULL if creation failed.
*
***********************************************************************/
CPoint FAR*
CPoint::Create()
{
    CPoint FAR* ppoint;

    if((ppoint = new FAR CPoint()) == NULL)
      return NULL;
    ppoint->AddRef();
    return ppoint;
}

//---------------------------------------------------------------------
//                     IUnknown Methods
//---------------------------------------------------------------------

STDMETHODIMP
CPoint::QueryInterface(REFIID riid, void FAR* FAR* ppv)
{
    if(!IsEqualIID(riid, IID_IUnknown))
      if(!IsEqualIID(riid, IID_IDispatch)) {
        *ppv = NULL;
        return E_NOINTERFACE;
    }

    *ppv = this;
    AddRef();
    return NOERROR;
}

STDMETHODIMP_(unsigned long)
CPoint::AddRef(void)
{
    return ++m_refs;
}

STDMETHODIMP_(unsigned long)
CPoint::Release(void)
{
    if(--m_refs == 0){
      delete this;
      return 0;
    }
    return m_refs;
}

//---------------------------------------------------------------------
//                     IDispatch methods
//---------------------------------------------------------------------

/*
 * NOTE: Support for the following two methods is not available
 * in this version.
 *
 */

STDMETHODIMP
CPoint::GetTypeInfoCount(unsigned int FAR* pctinfo)
{
    *pctinfo = 0;
    return NOERROR;
}

STDMETHODIMP
CPoint::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
{
    UNUSED(itinfo);
    UNUSED(lcid);
    UNUSED(pptinfo);

    return E_NOTIMPL;
}

/***
*HRESULT CPoint::GetIDsOfNames(REFIID, OLECHAR**, unsigned int, LCID, long*)
*Purpose:
*  This method translates the given array of names to a corresponding
*  array of DISPIDs.
*
*  Index 0 of the name array is the member name, and indices 1-N if
*  present represent named parameters on that member.
*
*  The local ID ('lcid') is unused by this naive implementation. A more
*  sophisticated implementation, sensitive to localization and natural
*  language support would use the locale ID to interpret the given names
*  in a correct locale specific context.
*
*Entry:
*  rgszNames = pointer to an array of names
*  cNames = the number of names in the rgszNames array
*  lcid = the callers locale ID
*
*Exit:
*  return value = HRESULT
*  rgid = array of name IDs corresponding to the rgszNames array
*    this array will contain -1 for each entry that is not known.
*
***********************************************************************/
STDMETHODIMP
CPoint::GetIDsOfNames(
    REFIID riid,
    OLECHAR FAR* FAR* rgszNames,
    unsigned int cNames,
    LCID lcid,
    DISPID FAR* rgdispid)
{
static MEMBERDESC rgmdCPoint[] = {
    {OLESTR("GETX"),IDMEMBER_CPOINT_GETX,NULL,0},
    {OLESTR("SETX"),IDMEMBER_CPOINT_SETX,NULL,0},
    {OLESTR("GETY"),IDMEMBER_CPOINT_GETY,NULL,0},
    {OLESTR("SETY"),IDMEMBER_CPOINT_SETY,NULL,0}
};

    // this object only exposed the "default" interface.
    //
    if(!IsEqualIID(riid, IID_NULL))
      return DISP_E_UNKNOWNINTERFACE;

    return SPolyGetIDsOfNames(
      rgmdCPoint, DIM(rgmdCPoint), rgszNames, cNames, lcid, rgdispid);
}

/***
*HRESULT CPoint::Invoke(...)
*Purpose:
*  Dispatch a method or property request for objects of type CPoint.
*
*  see the IDispatch document for more information, and a general
*  description of this method.
*
*Entry:
*  dispidMember = the DISPID of the member being requested
*
*  riid = reference to the interface ID of the interface on this object
*    that the requested member belongs to. IID_NULL means to interpret
*    the member as belonging to the implementation defined "default"
*    or "primary" interface.
*
*  lcid = the caller's locale ID
*
*  wFlags = flags indicating the type of access being requested
*
*  pdispparams = pointer to the DISPPARAMS struct containing the
*    requested members arguments (if any) and its named parameter
*    DISPIDs (if any).
*
*Exit:
*  return value = HRESULT
*   see the IDispatch spec for a description of possible success codes.
*
*  pvarResult = pointer to a caller allocated VARIANT containing
*    the members return value (if any).
*
*  pexcepinfo = caller allocated exception info structure, this will
*    be filled in only if an exception was raised that must be passed
*    up through Invoke to an enclosing handler.
*
*  puArgErr = pointer to a caller allocated UINT, that will contain the
*    index of the offending argument if a DISP_E_TYPEMISMATCH error
*    was returned indicating that one of the arguments was of an
*    incorrect type and/or could not be reasonably coerced to a proper
*    type.
*
***********************************************************************/
STDMETHODIMP
CPoint::Invoke(
    DISPID dispidMember,
    REFIID riid,
    LCID lcid,
    unsigned short wFlags,
    DISPPARAMS FAR* pdispparams,
    VARIANT FAR* pvarResult,
    EXCEPINFO FAR* pexcepinfo,
    unsigned int FAR* puArgErr)
{
    unsigned int uArgErr;
    HRESULT hresult;
    VARIANTARG varg0;
    VARIANT varResultDummy;

    UNUSED(lcid);
    UNUSED(pexcepinfo);

    // make sure the wFlags are legal
    if(wFlags & ~(DISPATCH_METHOD | DISPATCH_PROPERTYGET | DISPATCH_PROPERTYPUT | DISPATCH_PROPERTYPUTREF))
      return E_INVALIDARG;

    // this object only exposes a "default" interface.
    //
    if(!IsEqualIID(riid, IID_NULL))
      return DISP_E_UNKNOWNINTERFACE;

    // this makes the following code a bit simpler
    if(puArgErr == NULL)
      puArgErr = &uArgErr;
    if(pvarResult == NULL)
      pvarResult = &varResultDummy;

    VariantInit(&varg0);

    // assume the return type is void, unless we find otherwise.
    VariantInit(pvarResult);

    switch(dispidMember){
    case IDMEMBER_CPOINT_GETX:
      V_VT(pvarResult) = VT_I2;
      V_I2(pvarResult) = GetX();
      break;

    case IDMEMBER_CPOINT_SETX:
      hresult = DispGetParam(pdispparams, 0, VT_I2, &varg0, puArgErr);
      if(hresult != NOERROR)
        return hresult;
      SetX(V_I2(&varg0));
      break;

    case IDMEMBER_CPOINT_GETY:
      V_VT(pvarResult) = VT_I2;
      V_I2(pvarResult) = GetY();
      break;

    case IDMEMBER_CPOINT_SETY:
      hresult = DispGetParam(pdispparams, 0, VT_I2, &varg0, puArgErr);
      if(hresult != NOERROR)
        return hresult;
      SetY(V_I2(&varg0));
      break;

    default:
      return DISP_E_MEMBERNOTFOUND;
    }
    return NOERROR;
}

short PASCAL
CPoint::GetX()
{
    return m_x;
}

void PASCAL
CPoint::SetX(short x)
{
    m_x = x;
}

short PASCAL
CPoint::GetY()
{
    return m_y;
}

void PASCAL
CPoint::SetY(short y)
{
    m_y = y;
}

//---------------------------------------------------------------------
//         Implementation of the CPoint Class Factory
//---------------------------------------------------------------------

CPointCF::CPointCF()
{
    m_refs = 0;
}

IClassFactory FAR*
CPointCF::Create()
{
    CPointCF FAR* pCF;

    if((pCF = new FAR CPointCF()) == NULL)
      return NULL;
    pCF->AddRef();
    return pCF;
}

STDMETHODIMP
CPointCF::QueryInterface(REFIID riid, void FAR* FAR* ppv)
{

    if(!IsEqualIID(riid, IID_IUnknown))
      if(!IsEqualIID(riid, IID_IClassFactory)) {
        *ppv = NULL;
        return E_NOINTERFACE;
    }

    *ppv = this;
    ++m_refs;
    return NOERROR;
}

STDMETHODIMP_(unsigned long)
CPointCF::AddRef(void)
{
    return ++m_refs;
}

STDMETHODIMP_(unsigned long)
CPointCF::Release(void)
{
    if(--m_refs == 0){
      delete this;
      return 0;
    }
    return m_refs;
}

STDMETHODIMP
CPointCF::CreateInstance(
    IUnknown FAR* punkOuter,
    REFIID riid,
    void FAR* FAR* ppv)
{
    HRESULT hresult;
    CPoint FAR *ppoint;

    UNUSED(punkOuter);

    if((ppoint = CPoint::Create()) == NULL){
      *ppv = NULL;
      return E_OUTOFMEMORY;
    }
    hresult = ppoint->QueryInterface(riid, ppv);
    ppoint->Release();
    return hresult;
}

STDMETHODIMP
#ifdef _MAC
CPointCF::LockServer(unsigned long fLock)
#else
CPointCF::LockServer(BOOL fLock)
#endif
{
    UNUSED(fLock);

    return NOERROR;
}


CPOINT.H

/***
*cpoint.h
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  Definition of the CPoint class.
*
*  The CPoint object exposes two properties for programatic access
*  via the IDispatch interface.
*
*  properties:
*    X - the 'x' coordinate of the point
*    Y - the 'y' coordinate of the point
*
*Implementation Notes:
*
*****************************************************************************/

#ifndefCLASS
#ifdef__TURBOC__
#define CLASS class huge
#else
#define CLASS class FAR
#endif
#endif

class CPoly;

CLASS CPoint : public IDispatch {
    friend class CPoly;

public:
    static CPoint FAR* Create();

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppvObj);
    STDMETHOD_(unsigned long, AddRef)(void);
    STDMETHOD_(unsigned long, Release)(void);

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(unsigned int FAR* pcTypeInfo);

    STDMETHOD(GetTypeInfo)(
      unsigned int iTypeInfo,
      LCID lcid,
      ITypeInfo FAR* FAR* ppTypeInfo);

    STDMETHOD(GetIDsOfNames)(
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      unsigned int cNames,
      LCID lcid,
      DISPID FAR* rgdispid);

    STDMETHOD(Invoke)(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      unsigned short wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      unsigned int FAR* pwArgErr);

    /* Introduced methods */

    virtual short PASCAL GetX(void);
    virtual void  PASCAL SetX(short x);
    virtual short PASCAL GetY(void);
    virtual void  PASCAL SetY(short y);

private:
    CPoint();

    unsigned long m_refs;

    short m_x;
    short m_y;
};

// member DISPIDs
//
enum IDMEMBER_CPOINT {
    IDMEMBER_CPOINT_GETX = 1,
    IDMEMBER_CPOINT_SETX,
    IDMEMBER_CPOINT_GETY,
    IDMEMBER_CPOINT_SETY,
    IDMEMBER_CPOINT_MAX
};

// structure used to link together lists of points
//
struct POINTLINK {
    POINTLINK FAR* next;
    CPoint FAR* ppoint;
};

// The CPoint Class Factory
//
CLASS CPointCF : public IClassFactory
{
public:
    static IClassFactory FAR* Create();

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(REFIID iid, void FAR* FAR* ppv);
    STDMETHOD_(unsigned long, AddRef)(void);
    STDMETHOD_(unsigned long, Release)(void);

    /* IClassFactory methods */
    STDMETHOD(CreateInstance)(
      IUnknown FAR* pUnkOuter, REFIID iid, void FAR* FAR* ppv);
#ifdef _MAC
    STDMETHOD(LockServer)(unsigned long fLock);
#else
    STDMETHOD(LockServer)(BOOL fLock);
#endif

private:
    CPointCF();

    unsigned long m_refs;
};


CPOLY.CPP

/***
*CPoly.cpp
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  This module implements the CPoly and CPolyCF classes.
*
*  This module is intended as a sample implementation of the IDispatch
*  interface, and its purpose is to demonstrate how an object can
*  expose methods and properties for programatic and cross-process
*  access via the IDispatch interface.
*
*Implementation Notes:
*
*****************************************************************************/

#include "spoly.h"
#include "cpoint.h"
#include "cpoly.h"
#include "cenumpt.h"

#ifndef _MAC
extern CStatBar FAR* g_psb;
#else
extern "C" WindowPtr g_pwndClient;
#endif

extern unsigned int g_fQuit;

// our global list of polygons.
//
POLYLINK FAR* g_ppolylink = (POLYLINK FAR*)NULL;

// global count of polygons.
//
int g_cPoly = 0;

CPoly::CPoly()
{
    m_refs = 0;
    m_xorg = 0;
    m_yorg = 0;
    m_width = 0;
    m_cPoints = 0;

    m_red   = 0;
    m_green = 0;
    m_blue  = 0;

    m_ppointlink = NULL;
    m_ppointlinkLast = NULL;
}

/***
*CPoly *CPoly::Create(void)
*Purpose:
*  Create an instance of a CPoly object, and add it to the global
*  list of polygons.
*
*Entry:
*  None
*
*Exit:
*  returns a polygon object, NULL the allocation failed.
*
***********************************************************************/
CPoly FAR*
CPoly::Create()
{
    CPoly FAR* ppoly;
    POLYLINK FAR* ppolylink;

    if((ppolylink = new FAR POLYLINK) == (POLYLINK FAR*)NULL)
      return (CPoly FAR*)NULL;

    if((ppoly = new FAR CPoly()) == (CPoly FAR*)NULL)
      return (CPoly FAR*)NULL;

    ppoly->AddRef();

    // push the new polygon onto the front of the polygon list.
    //
    ++g_cPoly;

    ppolylink->ppoly = ppoly;

    ppolylink->next = g_ppolylink;
    g_ppolylink = ppolylink;

#ifndef _MAC
    SBprintf(g_psb, TSTR("#poly = %d"), g_cPoly);
#endif

    IncObjectCount();

    return ppoly;
}

//---------------------------------------------------------------------
//                     IUnknown Methods
//---------------------------------------------------------------------

STDMETHODIMP
CPoly::QueryInterface(REFIID riid, void FAR* FAR* ppv)
{
    if(IsEqualIID(riid, IID_IUnknown) ||
       IsEqualIID(riid, IID_IDispatch)) {
      *ppv = this;
      AddRef();
      return NOERROR;
    }

    *ppv = NULL;
    return E_NOINTERFACE;
}

STDMETHODIMP_(unsigned long)
CPoly::AddRef()
{
    return ++m_refs;
}

STDMETHODIMP_(unsigned long)
CPoly::Release()
{
    POLYLINK FAR* FAR* pppolylink, FAR* ppolylinkDead;

    if(--m_refs == 0){
      Reset(); // release all CPoints

      // remove ourselves from the global list of polygons
      //
      for( pppolylink = &g_ppolylink;
          *pppolylink != NULL;
   pppolylink = &(*pppolylink)->next)
      {
if((*pppolylink)->ppoly == this){
  ppolylinkDead = *pppolylink;
  *pppolylink = (*pppolylink)->next;
  delete ppolylinkDead;
  break;
}
      }

      --g_cPoly;

#ifndef _MAC
      SBprintf(g_psb, TSTR("#poly = %d"), g_cPoly);
#endif

      delete this;

      DecObjectCount();

      return 0;
    }
    return m_refs;
}

//---------------------------------------------------------------------
//                     IDispatch Methods
//---------------------------------------------------------------------

/*
 * NOTE: Support for the following two methods is not available
 * in this version.
 *
 */

STDMETHODIMP
CPoly::GetTypeInfoCount(unsigned int FAR* pctinfo)
{
    *pctinfo = 0;
    return NOERROR;
}

STDMETHODIMP
CPoly::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
{
    UNUSED(itinfo);
    UNUSED(lcid);
    UNUSED(pptinfo);

    return E_NOTIMPL;
}

/***
*HRESULT CPoly::GetIDsOfNames(OLECHAR**, unsigned int, LCID, long*)
*Purpose:
*  This method translates the given array of names to a corresponding
*  array of DISPIDs.
*
*  This method deferrs to a common implementation shared by
*  both the CPoly and CPoint objects. See the description of
*  'SPolyGetIDsOfNames()' in dispimpl.cpp for more information.
*
*Entry:
*  rgszNames = pointer to an array of names
*  cNames = the number of names in the rgszNames array
*  lcid = the callers locale ID
*
*Exit:
*  return value = HRESULT
*  rgdispid = array of DISPIDs corresponding to the rgszNames array
*    this array will contain -1 for each entry that is not known.
*
***********************************************************************/
STDMETHODIMP
CPoly::GetIDsOfNames(
    REFIID riid,
    OLECHAR FAR* FAR* rgszNames,
    unsigned int cNames,
    LCID lcid,
    DISPID FAR* rgdispid)
{
static PARAM_DESC rgpdAddPoint[] = {
    {OLESTR("X")}, {OLESTR("Y")}
};
static MEMBERDESC rgmdCPoly[] = {
    {OLESTR("DRAW"),   IDMEMBER_CPOLY_DRAW,NULL,0},
    {OLESTR("RESET"),   IDMEMBER_CPOLY_RESET,NULL,0},
    {OLESTR("ADDPOINT"),   IDMEMBER_CPOLY_ADDPOINT,rgpdAddPoint,2},
    {OLESTR("ENUMPOINTS"), IDMEMBER_CPOLY_ENUMPOINTS,NULL,0},
    {OLESTR("GETXORIGIN"), IDMEMBER_CPOLY_GETXORIGIN,NULL,0},
    {OLESTR("SETXORIGIN"), IDMEMBER_CPOLY_SETXORIGIN,NULL,0},
    {OLESTR("GETYORIGIN"), IDMEMBER_CPOLY_GETYORIGIN,NULL,0},
    {OLESTR("SETYORIGIN"), IDMEMBER_CPOLY_SETYORIGIN,NULL,0},
    {OLESTR("GETWIDTH"),   IDMEMBER_CPOLY_GETWIDTH,NULL,0},
    {OLESTR("SETWIDTH"),   IDMEMBER_CPOLY_SETWIDTH,NULL,0},
    {OLESTR("get_red"),   IDMEMBER_CPOLY_GETRED,NULL,0},
    {OLESTR("set_red"),   IDMEMBER_CPOLY_SETRED,NULL,0},
    {OLESTR("get_green"),  IDMEMBER_CPOLY_GETGREEN,NULL,0},
    {OLESTR("set_green"),  IDMEMBER_CPOLY_SETGREEN,NULL,0},
    {OLESTR("get_blue"),   IDMEMBER_CPOLY_GETBLUE,NULL,0},
    {OLESTR("set_blue"),   IDMEMBER_CPOLY_SETBLUE,NULL,0},
    {OLESTR("DUMP"),   IDMEMBER_CPOLY_DUMP,NULL,0},
    {OLESTR("Quit"),       IDMEMBER_CPOLY_QUIT,     NULL,       0}
};

    // this object only exposes a "default" interface.
    //
    if(!IsEqualIID(riid, IID_NULL))
      return DISP_E_UNKNOWNINTERFACE;

    return SPolyGetIDsOfNames(
      rgmdCPoly, DIM(rgmdCPoly), rgszNames, cNames, lcid, rgdispid);
}

/***
*HRESULT CPoly::Invoke(...)
*Purpose:
*  Dispatch a method or property request for objects of type CPoly.
*
*  see the IDispatch document for more information, and a general
*  description of this method.
*
*Entry:
*  dispidMember = the DISPID of the member being requested
*
*  riid = reference to the interface ID of the interface on this object
*    that the requested member belongs to. IID_NULL means to interpret
*    the member as belonging to the implementation defined "default"
*    or "primary" interface.
*
*  lcid = the caller's locale ID
*
*  wFlags = flags indicating the type of access being requested
*
*  pdispparams = pointer to the DISPPARAMS struct containing the
*    requested members arguments (if any) and its named parameter
*    DISPIDs (if any).
*
*Exit:
*  return value = HRESULT
*   see the IDispatch spec for a description of possible success codes.
*
*  pvarResult = pointer to a caller allocated VARIANT containing
*    the members return value (if any).
*
*  pexcepinfo = caller allocated exception info structure, this will
*    be filled in only if an exception was raised that must be passed
*    up through Invoke to an enclosing handler.
*
*  puArgErr = pointer to a caller allocated UINT, that will contain the
*    index of the offending argument if a DISP_E_TYPEMISMATCH error
*    was returned indicating that one of the arguments was of an
*    incorrect type and/or could not be reasonably coerced to a proper
*    type.
*
***********************************************************************/
STDMETHODIMP
CPoly::Invoke(
    DISPID dispidMember,
    REFIID riid,
    LCID lcid,
    unsigned short wFlags,
    DISPPARAMS FAR* pdispparams,
    VARIANT FAR* pvarResult,
    EXCEPINFO FAR* pexcepinfo,
    unsigned int FAR* puArgErr)
{
    HRESULT hresult;
    VARIANTARG varg0, varg1;
    VARIANT varResultDummy;

    UNUSED(lcid);
    UNUSED(pexcepinfo);

    if(wFlags & ~(DISPATCH_METHOD | DISPATCH_PROPERTYGET | DISPATCH_PROPERTYPUT | DISPATCH_PROPERTYPUTREF))
      return E_INVALIDARG;

    // this object only exposes a "default" interface.
    //
    if(!IsEqualIID(riid, IID_NULL))
      return DISP_E_UNKNOWNINTERFACE;

    // This makes the following code a bit simpler if the caller
    // happens to be ignoring the return value. Some implementations
    // may choose to deal with this differently.
    //
    if(pvarResult == (VARIANT FAR*)NULL)
      pvarResult = &varResultDummy;

    VariantInit(&varg0);
    VariantInit(&varg1);

    // assume the return type is void, unless we find otherwise.
    VariantInit(pvarResult);

    switch(dispidMember){
    case IDMEMBER_CPOLY_DRAW:
      Draw();
      break;

    case IDMEMBER_CPOLY_RESET:
      Reset();
      break;

    case IDMEMBER_CPOLY_DUMP:
      Dump();
      break;

    case IDMEMBER_CPOLY_QUIT:
      Quit();
      break;

    case IDMEMBER_CPOLY_ADDPOINT:
      hresult = DispGetParam(pdispparams, 0, VT_I2, &varg0, puArgErr);
      if(hresult != NOERROR)
return hresult;

      hresult = DispGetParam(pdispparams, 1, VT_I2, &varg1, puArgErr);
      if(hresult != NOERROR)
return hresult;

      hresult = AddPoint(V_I2(&varg1), V_I2(&varg0));
      if(hresult != NOERROR)
return hresult;
      break;

    case IDMEMBER_CPOLY_ENUMPOINTS:
      IEnumVARIANT FAR* penum;

      hresult = EnumPoints(&penum);
      if(hresult != NOERROR)
return hresult;

      V_VT(pvarResult) = VT_UNKNOWN;
      hresult = penum->QueryInterface(
IID_IUnknown, (void FAR* FAR*)&V_UNKNOWN(pvarResult));
      if(hresult != NOERROR)
return hresult;
      penum->Release();
      break;

    case IDMEMBER_CPOLY_GETXORIGIN:
      V_VT(pvarResult) = VT_I2;
      V_I2(pvarResult) = m_xorg;
      break;

    case IDMEMBER_CPOLY_SETXORIGIN:
      hresult = DispGetParam(pdispparams, 0, VT_I2, &varg0, puArgErr);
      if(hresult != NOERROR)
        return hresult;
      m_xorg = V_I2(&varg0);
      break;

    case IDMEMBER_CPOLY_GETYORIGIN:
      V_VT(pvarResult) = VT_I2;
      V_I2(pvarResult) = m_yorg;
      break;

    case IDMEMBER_CPOLY_SETYORIGIN:
      hresult = DispGetParam(pdispparams, 0, VT_I2, &varg0, puArgErr);
      if(hresult != NOERROR)
        return hresult;
      m_yorg = V_I2(&varg0);
      break;

    case IDMEMBER_CPOLY_GETWIDTH:
      V_VT(pvarResult) = VT_I2;
      V_I2(pvarResult) = GetWidth();
      break;

    case IDMEMBER_CPOLY_SETWIDTH:
      hresult = DispGetParam(pdispparams, 0, VT_I2, &varg0, puArgErr);
      if(hresult != NOERROR)
        return hresult;
      SetWidth(V_I2(&varg0));
      break;

    case IDMEMBER_CPOLY_GETRED:
      V_VT(pvarResult) = VT_I2;
      V_I2(pvarResult) = get_red();
      break;

    case IDMEMBER_CPOLY_SETRED:
      hresult = DispGetParam(pdispparams, 0, VT_I2, &varg0, puArgErr);
      if(hresult != NOERROR)
        return hresult;
      set_red(V_I2(&varg0));
      break;

    case IDMEMBER_CPOLY_GETGREEN:
      V_VT(pvarResult) = VT_I2;
      V_I2(pvarResult) = get_green();
      break;

    case IDMEMBER_CPOLY_SETGREEN:
      hresult = DispGetParam(pdispparams, 0, VT_I2, &varg0, puArgErr);
      if(hresult != NOERROR)
        return hresult;
      set_green(V_I2(&varg0));
      break;

    case IDMEMBER_CPOLY_GETBLUE:
      V_VT(pvarResult) = VT_I2;
      V_I2(pvarResult) = get_blue();
      break;

    case IDMEMBER_CPOLY_SETBLUE:
      hresult = DispGetParam(pdispparams, 0, VT_I2, &varg0, puArgErr);
      if(hresult != NOERROR)
        return hresult;
      set_blue(V_I2(&varg0));
      break;

    default:
      return DISP_E_MEMBERNOTFOUND;
    }

    return NOERROR;
}

//---------------------------------------------------------------------
//                     Introduced Methods
//---------------------------------------------------------------------

/***
*void CPoly::Draw(void)
*Purpose:
*  Draw the polygon, using the current x/y origin and line width
*  properties.
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
void PASCAL
CPoly::Draw()
{
    short xorg, yorg;
    POINTLINK FAR* ppointlinkFirst, FAR* ppointlink;

    if((ppointlinkFirst = m_ppointlink) == (POINTLINK FAR*)NULL)
      return;

#ifdef _MAC /* { */

    short x, y;
    RGBColor rgb;
    WindowPtr pwndSaved;

    GetPort(&pwndSaved);
    SetPort(g_pwndClient);

    PenNormal();
    PenSize(m_width, m_width);

    rgb.red = m_red;
    rgb.green = m_green;
    rgb.blue = m_blue;
    RGBForeColor(&rgb);

    xorg = m_xorg;
    yorg = m_yorg;

    MoveTo(
      xorg + ppointlinkFirst->ppoint->m_x,
      yorg + ppointlinkFirst->ppoint->m_y);

    for(ppointlink = ppointlinkFirst->next;
ppointlink != (POINTLINK FAR*)NULL;
ppointlink = ppointlink->next)
    {
      x = xorg + ppointlink->ppoint->m_x;
      y = yorg + ppointlink->ppoint->m_y;
      LineTo(x, y);
    }

    LineTo(
      xorg + ppointlinkFirst->ppoint->m_x,
      yorg + ppointlinkFirst->ppoint->m_y);

    SetPort(pwndSaved);

#else /* }{ */

    HDC hdc;
    RECT rect;
    HPEN hpen, hpenOld;
extern HWND g_hwndClient;

    GetClientRect(g_hwndClient, &rect);
    xorg = m_xorg + (short) rect.left;
    yorg = m_yorg + (short) rect.top;

    hdc = GetDC(g_hwndClient);
    hpen = CreatePen(PS_SOLID, m_width, RGB(m_red, m_green, m_blue));
    hpenOld = (HPEN)SelectObject(hdc, hpen);

#ifdef WIN32
    MoveToEx(hdc,
      xorg + ppointlinkFirst->ppoint->m_x,
      yorg + ppointlinkFirst->ppoint->m_y, NULL);
#else
    MoveTo(hdc,
      xorg + ppointlinkFirst->ppoint->m_x,
      yorg + ppointlinkFirst->ppoint->m_y);
#endif

    for(ppointlink = ppointlinkFirst->next;
ppointlink != (POINTLINK FAR*)NULL;
ppointlink = ppointlink->next)
    {
      LineTo(hdc,
xorg + ppointlink->ppoint->m_x,
yorg + ppointlink->ppoint->m_y);
    }

    LineTo(hdc,
      xorg + ppointlinkFirst->ppoint->m_x,
      yorg + ppointlinkFirst->ppoint->m_y);

    SelectObject(hdc, hpenOld);
    DeleteObject(hpen);

    ReleaseDC(g_hwndClient, hdc);

#endif /* } */
}

/***
*void CPoly::Reset(void)
*Purpose:
*  Release all points referenced by this poly.
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
void PASCAL
CPoly::Reset()
{
    POINTLINK FAR* ppointlink, FAR* ppointlinkNext;

    for(ppointlink = m_ppointlink;
ppointlink != (POINTLINK FAR*)NULL;
ppointlink = ppointlinkNext)
    {
      ppointlinkNext = ppointlink->next;
      ppointlink->ppoint->Release();
      delete ppointlink;
    }

    m_cPoints = 0;
    m_ppointlink = NULL;
    m_ppointlinkLast = NULL;
}

/***
*HRESULT CPoly::AddPoint(short, short)
*Purpose:
*  Add a CPoint with the given coordinates to the end of our current
*  list of points.
*
*Entry:
*  x,y = the x and y coordinates of the new point.
*
*Exit:
*  return value = HRESULT
*
***********************************************************************/
HRESULT PASCAL
CPoly::AddPoint(short x, short y)
{
    CPoint FAR* ppoint;
    POINTLINK FAR* ppointlink;

    ppoint = CPoint::Create();
    if(ppoint == (CPoint FAR*)NULL)
      return E_OUTOFMEMORY;

    ppoint->SetX(x);
    ppoint->SetY(y);

    ppointlink = new FAR POINTLINK;
    if(ppointlink == (POINTLINK FAR*)NULL){
      delete ppoint;
      return E_OUTOFMEMORY;
    }

    ppointlink->ppoint = ppoint;
    ppointlink->next = (POINTLINK FAR*)NULL;

    if(m_ppointlinkLast == (POINTLINK FAR*)NULL){
      m_ppointlink = m_ppointlinkLast = ppointlink;
    }else{
      m_ppointlinkLast->next = ppointlink;
      m_ppointlinkLast = ppointlink;
    }

    ++m_cPoints;

    return NOERROR;
}

/***
*HRESULT CPoly::EnumPoints(IEnumVARIANT**);
*Purpose:
*  Return and enumerator for the points in this polygon.
*
*Entry:
*  None
*
*Exit:
*  return value = HRESULT
*
*  *ppenum = pointer to an IEnumVARIANT for the points in this polygon
*
***********************************************************************/
HRESULT PASCAL
CPoly::EnumPoints(IEnumVARIANT FAR* FAR* ppenum)
{
    unsigned int i;
    VARIANT var;
    HRESULT hresult;
    SAFEARRAY FAR* psa;
    CEnumPoint FAR* penum;
    POINTLINK FAR* ppointlink;
    SAFEARRAYBOUND rgsabound[1];

    rgsabound[0].lLbound = 0;
    rgsabound[0].cElements = m_cPoints;

    psa = SafeArrayCreate(VT_VARIANT, 1, rgsabound);
    if(psa == NULL){
      hresult = E_OUTOFMEMORY;
      goto LError0;
    }

    ppointlink = m_ppointlink;
    for(i = 0; i < m_cPoints; ++i){
      long ix[1];

      if(ppointlink == NULL){
        // this indicates an internal consistency error.
// (this test should probably be an assertion)
        hresult = E_FAIL;
        goto LError1;
      }

      V_VT(&var) = VT_DISPATCH;
      hresult = ppointlink->ppoint->QueryInterface(
IID_IDispatch, (void FAR* FAR*)&V_DISPATCH(&var));
      if(hresult != NOERROR)
        goto LError1;

      ix[0] = i;
      SafeArrayPutElement(psa, ix, &var);

      // SafeArrayPutElement adds a reference to the contents of the
      // variant, so we must free the variable we have.
      //
      VariantClear(&var);

      ppointlink = ppointlink->next;
    }

    hresult = CEnumPoint::Create(psa, &penum);
    if(hresult != NOERROR)
      goto LError1;

    *ppenum = penum;

    return NOERROR;

LError1:;
    // destroy the array if we were not successful creating the enumerator.
    SafeArrayDestroy(psa);

LError0:;
    return hresult;
}

short PASCAL
CPoly::GetXOrigin()
{
    return m_xorg;
}

void PASCAL
CPoly::SetXOrigin(short x)
{
    m_xorg = x;
}

short PASCAL
CPoly::GetYOrigin()
{
    return m_yorg;
}

void PASCAL
CPoly::SetYOrigin(short y)
{
    m_yorg = y;
}

short PASCAL
CPoly::GetWidth()
{
    return m_width;
}

void PASCAL
CPoly::SetWidth(short width)
{
    m_width = width;
}

short PASCAL
CPoly::get_red()
{
    return m_red;
}

void PASCAL
CPoly::set_red(short red)
{
    m_red = red;
}

short PASCAL
CPoly::get_green()
{
    return m_green;
}

void PASCAL
CPoly::set_green(short green)
{
    m_green = green;
}

short PASCAL
CPoly::get_blue()
{
    return m_blue;
}

void PASCAL
CPoly::set_blue(short blue)
{
    m_blue = blue;
}

/***
*void CPoly::Dump(void)
*Purpose:
*  Output a debug dump of this instance.
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
void PASCAL
CPoly::Dump()
{
#ifdef _MAC

    // REVIEW: implement for the mac

#else

    TCHAR buffer[80];
    POINTLINK FAR* ppointlink;

    wsprintf(buffer, TSTR("CPoly(0x%x) =\n"), (int)this);
    OutputDebugString(buffer);

    wsprintf(buffer,
      TSTR("    xorg = %d, yorg = %d, width = %d, rgb = {%d,%d,%d}\n    points = "),
      m_xorg, m_yorg, m_width,
      get_red(),
      get_green(),
      get_blue());

    OutputDebugString(buffer);

    for(ppointlink = m_ppointlink;
ppointlink != (POINTLINK FAR*)NULL;
ppointlink = ppointlink->next)
    {
      wsprintf(buffer, TSTR("{%d,%d}"),
        ppointlink->ppoint->GetX(),
        ppointlink->ppoint->GetY());
      OutputDebugString(buffer);

      wsprintf(buffer, TSTR(" "));
      OutputDebugString(buffer);
    }
    wsprintf(buffer, TSTR("\n"));
    OutputDebugString(buffer);

#endif
}

/***
*void CPoly::Quit(void)
*Purpose:
*  Exit when all objects are released.
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
void PASCAL
CPoly::Quit()
{
    g_fQuit = 1;
}

/***
*void CPoly::PolyDraw(void)
*Purpose:
*  Draw all polygons.
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
void
CPoly::PolyDraw()
{
    POLYLINK FAR* polylink;

    for(polylink = g_ppolylink;
polylink != (POLYLINK FAR*)NULL;
polylink = polylink->next)
    {
      polylink->ppoly->Draw();
    }
}

/***
*void PolyTerm(void)
*Purpose:
*  Release all polygons.
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
void
CPoly::PolyTerm()
{
    POLYLINK FAR* ppolylink;
    POLYLINK FAR* ppolylinkNext;

    for(ppolylink = g_ppolylink;
ppolylink != (POLYLINK FAR*)NULL;
ppolylink = ppolylinkNext)
    {
      ppolylinkNext = ppolylink->next;
      ppolylink->ppoly->Release();
      delete ppolylink;
    }
    g_ppolylink = NULL;
}

/***
*void PolyDump(void)
*Purpose:
*  Invoke the debug Dump() method on all polygons were currently
*  holding on to.
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
void
CPoly::PolyDump()
{
    POLYLINK FAR* ppolylink;

    if(g_ppolylink == (POLYLINK FAR*)NULL){
#ifndef _MAC
      OutputDebugString(TSTR("\t(none)\n"));
#endif
      return;
    }

    for(ppolylink = g_ppolylink;
ppolylink != (POLYLINK FAR*)NULL;
ppolylink = ppolylink->next)
    {
      ppolylink->ppoly->Dump();
    }
}

//---------------------------------------------------------------------
//             Implementation of the CPoly Class Factory
//---------------------------------------------------------------------

CPolyCF::CPolyCF()
{
    m_refs = 0;
}

IClassFactory FAR*
CPolyCF::Create()
{
    CPolyCF FAR* pCF;

    if((pCF = new FAR CPolyCF()) == NULL)
      return NULL;
    pCF->AddRef();
    return pCF;
}

STDMETHODIMP
CPolyCF::QueryInterface(REFIID riid, void FAR* FAR* ppv)
{
    if(!IsEqualIID(riid, IID_IUnknown)){
      if(!IsEqualIID(riid, IID_IClassFactory)){
*ppv = NULL;
        return E_NOINTERFACE;
      }
    }

    *ppv = this;
    ++m_refs;
    return NOERROR;
}

STDMETHODIMP_(unsigned long)
CPolyCF::AddRef(void)
{
    return ++m_refs;
}

STDMETHODIMP_(unsigned long)
CPolyCF::Release(void)
{
    if(--m_refs == 0){
      delete this;
      return 0;
    }
    return m_refs;
}

STDMETHODIMP
CPolyCF::CreateInstance(
    IUnknown FAR* punkOuter,
    REFIID iid,
    void FAR* FAR* ppv)
{
    HRESULT hresult;
    CPoly FAR *ppoly;

    UNUSED(punkOuter);

#ifdef _MAC
    if(GetZone() != ApplicZone()){
#ifndef _MSC_VER
#ifndef ConstStr255Param
#define ConstStr255Param StringPtr
#endif
#endif
      DebugStr((ConstStr255Param)"\pZones do not match");
    }

#endif

    if((ppoly = CPoly::Create()) == NULL){
      *ppv = NULL;
      return E_OUTOFMEMORY;
    }
    hresult = ppoly->QueryInterface(iid, ppv);
    ppoly->Release();
    return hresult;
}

STDMETHODIMP
#ifdef _MAC
CPolyCF::LockServer(unsigned long fLock)
#else
CPolyCF::LockServer(BOOL fLock)
#endif
{
    UNUSED(fLock);

    return NOERROR;
}


CPOLY.H

/***
*cpoly.h
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  Definition of the CPoly class.
*
*  The CPoly class defines a number of methods and exposes them for
*  external programmability via IDispatch,
*
*  methods:
*    DRAW- draw the polygon
*    RESET- delete all points from the polygon
*
*    ADDPOINT(X, Y)- add a point with coordinates (x,y) to the polygon
*
*    ENUMPOINTS- return a collection of the polygon's points
*
*    GETXORIGIN- get and set the X origin of the polygon
*    SETXORIGIN
*
*    GETYORIGIN- get and set the Y origin of the polygon
*    SETYORIGIN
*
*    GETWIDTH- get and set the line width of the polygon
*    SETWIDTH
*
*  UNDONE: update description
*
*Implementation Notes:
*
*****************************************************************************/

#ifndefCLASS
# ifdef__TURBOC__
#  define CLASS class huge
# else
#  define CLASS class FAR
# endif
#endif

class CPoint;

CLASS CPoly : public IDispatch
{
public:
    static CPoly FAR* Create();

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppvObj);
    STDMETHOD_(unsigned long, AddRef)(void);
    STDMETHOD_(unsigned long, Release)(void);

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(unsigned int FAR* pcTypeInfo);

    STDMETHOD(GetTypeInfo)(
      unsigned int iTypeInfo,
      LCID lcid,
      ITypeInfo FAR* FAR* ppTypeInfo);

    STDMETHOD(GetIDsOfNames)(
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      unsigned int cNames,
      LCID lcid,
      DISPID FAR* rgdispid);

    STDMETHOD(Invoke)(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      unsigned short wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      unsigned int FAR* puArgErr);

    /* Introduced methods */

    virtual void PASCAL Draw(void);
    virtual void PASCAL Reset(void);

    // add a point with the given 'x' and 'y' coordinates
    virtual HRESULT PASCAL AddPoint(short x, short y);

    // return a collection of the polygon's points
    virtual HRESULT PASCAL EnumPoints(IEnumVARIANT FAR* FAR* ppenum);

    // get/set the polygon's X origin property
    virtual short PASCAL GetXOrigin(void);
    virtual void  PASCAL SetXOrigin(short x);

    // get/set the polygon's Y origin property
    virtual short PASCAL GetYOrigin(void);
    virtual void  PASCAL SetYOrigin(short y);

    virtual short PASCAL GetWidth(void);
    virtual void  PASCAL SetWidth(short width);

    virtual short PASCAL get_red(void);
    virtual void  PASCAL set_red(short red);

    virtual short PASCAL get_green(void);
    virtual void  PASCAL set_green(short green);

    virtual short PASCAL get_blue(void);
    virtual void  PASCAL set_blue(short blue);

    // Debug method
    virtual void  PASCAL Dump(void);
    virtual void  PASCAL Quit(void);

public:

    // Draw all polygons.
    static void PolyDraw(void);

    // Release all polygons.
    static void PolyTerm(void);

    // Dump all polygons to dbwin.
    static void PolyDump(void);

private:
    CPoly();

    short m_xorg;
    short m_yorg;
    short m_width;

    short m_red;
    short m_green;
    short m_blue;

    unsigned long m_refs;
    unsigned int m_cPoints;

    POINTLINK FAR* m_ppointlink;
    POINTLINK FAR* m_ppointlinkLast;
};

// DISPIDs for the members and properties available via IDispatch.
//
enum IDMEMBER_CPOLY {
    IDMEMBER_CPOLY_DRAW = 1,
    IDMEMBER_CPOLY_RESET,
    IDMEMBER_CPOLY_ADDPOINT,
    IDMEMBER_CPOLY_ENUMPOINTS,
    IDMEMBER_CPOLY_GETXORIGIN,
    IDMEMBER_CPOLY_SETXORIGIN,
    IDMEMBER_CPOLY_GETYORIGIN,
    IDMEMBER_CPOLY_SETYORIGIN,
    IDMEMBER_CPOLY_GETWIDTH,
    IDMEMBER_CPOLY_SETWIDTH,
    IDMEMBER_CPOLY_GETRED,
    IDMEMBER_CPOLY_SETRED,
    IDMEMBER_CPOLY_GETGREEN,
    IDMEMBER_CPOLY_SETGREEN,
    IDMEMBER_CPOLY_GETBLUE,
    IDMEMBER_CPOLY_SETBLUE,
    IDMEMBER_CPOLY_DUMP,
    IDMEMBER_CPOLY_QUIT,
    IDMEMBER_CPOLY_MAX
};

// structure used to link together polygons
//
struct POLYLINK {
    POLYLINK FAR* next;
    CPoly FAR* ppoly;
};

// The CPoly class factory
//
CLASS CPolyCF : public IClassFactory
{
public:
    static IClassFactory FAR* Create();

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppv);
    STDMETHOD_(unsigned long, AddRef)(void);
    STDMETHOD_(unsigned long, Release)(void);

    /* IClassFactory methods */
    STDMETHOD(CreateInstance)(
      IUnknown FAR* pUnkOuter, REFIID riid, void FAR* FAR* ppv);
#ifdef _MAC
    STDMETHOD(LockServer)(unsigned long fLock);
#else
    STDMETHOD(LockServer)(BOOL fLock);
#endif

private:
    CPolyCF();

    unsigned long m_refs;
};


MISC.CPP

/***
*misc.cpp
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*
*Implementation Notes:
*
*****************************************************************************/

#include "spoly.h"
#include "cpoint.h"
#include "cpoly.h"

#include <stdio.h>

#ifdef _MAC
# include <string.h>
# include <ctype.h>
#endif

#ifdef WIN32  // Use native CompareString operation
  #undef CompareString
  #ifdef UNICODE
    #define CompareString CompareStringW
  #else
    // CONSIDER: write a wrapper for CompareStringW if not available
  #endif
#else
  #define CompareString CompareStringA
#endif

unsigned long g_dwPolyCF = 0;
unsigned long g_dwPointCF = 0;

IClassFactory FAR* g_ppolyCF = NULL;
IClassFactory FAR* g_ppointCF = NULL;

unsigned int g_fObjectsRevoked = 0;
unsigned long g_cObjects = 0;
unsigned int g_fQuit = 0;

#ifdef _MAC
struct regentry{
    char *szKey;
    char *szValue;
} g_rgregentry[] = {

      { "CLSID\\{00020462-0000-0000-C000-000000000046}",
"OLE Automation SPoly 1.0 Application" }

    , { "CLSID\\{00020462-0000-0000-C000-000000000046}\\LocalServer",
"SPLy" }

    , { "CLSID\\{00020462-0000-0000-C000-000000000046}\\ProgID",
"SPoly.Application" }

    , { "CLSID\\{00020462-0000-0000-C000-000000000046}\\InprocHandler",
"OLE2:Def$DefFSet" }

    , { "SPLy", "{00020462-0000-0000-C000-000000000046}" }

    , { "SPoly.Application\\CLSID",
"{00020462-0000-0000-C000-000000000046}" }

};

HRESULT
EnsureRegistration()
{
    HKEY hkey;

    if(RegOpenKey(HKEY_CLASSES_ROOT, "SPLy", &hkey) == NOERROR){
      RegCloseKey(hkey);
      return NOERROR;
    }

    for(int i = 0; i < DIM(g_rgregentry); ++i){
      if(RegSetValue(HKEY_CLASSES_ROOT, g_rgregentry[i].szKey, REG_SZ, g_rgregentry[i].szValue, 0) != ERROR_SUCCESS)
        return E_FAIL;
    }

    return NOERROR;
}
#endif

/***
*HRESULT InitOle(void)
*Purpose:
*  Initialize Ole, and register our class factories.
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
STDAPI
InitOle()
{
    HRESULT hresult;

    if((hresult = OleInitialize(NULL)) != NOERROR)
      goto LError0;

#ifdef _MAC
    if((hresult = EnsureRegistration()) != NOERROR)
      goto LError0;
#endif

    // Register the CPoint Class Factory
    //
    if((g_ppointCF = CPointCF::Create()) == NULL){
      hresult = E_OUTOFMEMORY;
      goto LError1;
    }

    hresult = CoRegisterClassObject(
      CLSID_CPoint,
      g_ppointCF,
      CLSCTX_LOCAL_SERVER,
      REGCLS_MULTIPLEUSE,
      &g_dwPointCF);
    if(FAILED(hresult))
      goto LError1;

    // Register the CPoly Class Factory.
    //
    if((g_ppolyCF = CPolyCF::Create()) == NULL){
      hresult = E_OUTOFMEMORY;
      goto LError1;
    }

    hresult = CoRegisterClassObject(
      CLSID_CPoly,
      g_ppolyCF,
      CLSCTX_LOCAL_SERVER,
      REGCLS_MULTIPLEUSE,
      &g_dwPolyCF);
    if(FAILED(hresult))
      goto LError1;

    g_ppolyCF->Release();

    g_ppointCF->Release();

    return NOERROR;

LError1:;
    if(g_ppolyCF != NULL)
      g_ppolyCF->Release();

    if(g_ppointCF != NULL)
      g_ppointCF->Release();

    UninitOle();

LError0:;
    return hresult;
}

STDAPI
Revoke()
{
    if (!g_fObjectsRevoked) {
      // Tell Ole to release our class factories.
      //
      if(g_dwPointCF != 0L)
        CoRevokeClassObject(g_dwPointCF);

      if(g_dwPolyCF != 0L)
        CoRevokeClassObject(g_dwPolyCF);

      g_fObjectsRevoked = 1;
    }

    return NOERROR;
}

STDAPI
UninitOle()
{
    Revoke();
    OleUninitialize();

    return NOERROR;
}

/***
*HRESULT SPolyGetIDsOfNames(MEMBERDESC*, unsigned int, char**, unsigned int, LCID, long*)
*Purpose:
*  This is the table driven implementation of IDispatch::GetIDsOfNames
*  deferred to by both the CPoint and CPoly objects.
*
*Entry:
*  rgmd = pointer to an array of method descriptors
*  cMethods = number of elements in the array of method descriptors
*  rgszNames = pointer to an array of names
*  cNames = the number of names in the rgszNames array
*  lcid = the callers locale ID
*
*Exit:
*  return value = HRESULT
*  rgdispid = array of name IDs corresponding to the rgszNames array
*    this array will contain -1 for each entry that is not known.
*
***********************************************************************/
STDAPI
SPolyGetIDsOfNames(
    MEMBERDESC FAR* rgmd,
    unsigned int cMethods,
    OLECHAR FAR* FAR* rgszNames,
    unsigned int cNames,
    LCID lcid,
    DISPID FAR* rgdispid)
{
    HRESULT hresult;
    PARAM_DESC FAR* ppd;
    MEMBERDESC FAR* pmd;
    unsigned int iName, iTry, cParams;

    hresult = NOERROR;

    // first lookup the member name.
    //
    for(pmd = rgmd;; ++pmd){
      if(pmd == &rgmd[cMethods])
        goto LMemberNotFound;

#if defined(WIN32) && !defined(UNICODE)
      // CompareStringW is not available on Windows 95
      if(wcsicmp(rgszNames[0], pmd->szName) == 0)
#else
      if(CompareString(lcid, NORM_IGNORECASE, rgszNames[0], -1, pmd->szName, -1) == 2)
#endif
      {
rgdispid[0] = pmd->id;
break;
      }
    }

    // Lookup the named parameters, if there are any.
    if(cNames > 1){
      cParams = pmd->cParams;
      for(iName = 1; iName < cNames; ++iName){
for(iTry = 0;; ++iTry){
  if(iTry == cParams){
    hresult = DISP_E_UNKNOWNNAME;
    rgdispid[iName] = -1;
    break;
  }
  ppd = &pmd->rgpd[iTry];

#if defined(WIN32) && !defined(UNICODE)
          // CompareStringW is not available on Windows 95
          if(wcsicmp(rgszNames[iName], ppd->szName) == 0)
#else
  if(CompareString(lcid, NORM_IGNORECASE, rgszNames[iName], -1, ppd->szName, -1) == 2)
#endif
  {
    // The DISPID for a named parameter is defined to be its
    // zero based positional index.  This routine assumes that
    // that PARAM_DESC array was declared in correct textual order.
    //
    rgdispid[iName] = iTry;
    break;
  }
        }
      }
    }

    return hresult;

LMemberNotFound:;
    // If the member name is unknown, everything is unknown.
    for(iName = 0; iName < cNames; ++iName)
      rgdispid[iName] = -1;
    return DISP_E_UNKNOWNNAME;
}

// disable unicode expansion for assertions
#undef UNICODE

void
Assert(int fCond, char FAR* file, int line, char FAR* message)
{
    char * fmt;
    char buf[128];

    if(fCond)
      return;

    fmt = (message == NULL)
      ? "Assertion failed: %s(%d)"
      : "Assertion failed: %s(%d) '%s'";
    sprintf(buf, fmt, file, line, message);

#ifdef _MAC
    DebugStr(c2pstr(buf));
#else
#ifdef WIN32
    OutputDebugStringA(buf);
#else //WIN32
    OutputDebugString(buf);
#endif //WIN32
    DebugBreak();
#endif
}

void IncObjectCount()
{
    g_cObjects++;
}

void DecObjectCount()
{
    g_cObjects--;

    if (!g_cObjects && g_fQuit) {
      Revoke();
#ifndef _MAC
      PostQuitMessage(0);
#endif // !_MAC
    }
}


CENUMPT.CPP

/***
*cenumpt.cpp
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  This module implements the CEnumPoint class.
*
*
*Implementation Notes:
*
*****************************************************************************/

#include "hostenv.h"
#include "cenumpt.h"

CEnumPoint::CEnumPoint()
{
    m_refs = 0;

    m_psa = NULL;
    m_celts = 0;
    m_iCurrent = 0;
}

/***
*HRESULT CEnumPoint::Create(SAFEARRAY*, CEnumPoint**)
*Purpose:
*  This routine creates a CPoint enumerator from the given
*  (1 X N) SafeArray of CPoint IDispatch pointers.
*
*Entry:
*  psa = pointer to a SafeArray of VARIANTs
*
*Exit:
*  return value = HRESULT
*
*  *ppenum = pointer to a CPoint enumerator
*
***********************************************************************/
HRESULT
CEnumPoint::Create(SAFEARRAY FAR* psa, CEnumPoint FAR* FAR* ppenum)
{
    long lBound;
    HRESULT hresult;
    CEnumPoint FAR* penum;

    // Verify that the SafeArray is the proper shape.
    //
    if(SafeArrayGetDim(psa) != 1)
      return E_INVALIDARG;

    hresult = SafeArrayGetLBound(psa, 1, &lBound);
    if(FAILED(hresult))
      return hresult;

    if(lBound != 0)
      return E_INVALIDARG;

    penum = new FAR CEnumPoint();
    if(penum == NULL)
      return E_OUTOFMEMORY;
    penum->AddRef();

    hresult = SafeArrayGetUBound(psa, 1, &lBound);
    if(FAILED(hresult))
      goto LError0;

    penum->m_psa = psa;
    penum->m_celts = lBound + 1;

    *ppenum = penum;

    return NOERROR;

LError0:;
    penum->Release();

    return hresult;
}

//---------------------------------------------------------------------
//                        IUnknown methods
//---------------------------------------------------------------------

STDMETHODIMP
CEnumPoint::QueryInterface(REFIID riid, void FAR* FAR* ppv)
{

    if(!IsEqualIID(riid, IID_IUnknown))
      if(!IsEqualIID(riid, IID_IEnumVARIANT)) {
        *ppv = NULL;
        return E_NOINTERFACE;
    }

    *ppv = this;
    AddRef();
    return NOERROR;
}

STDMETHODIMP_(unsigned long)
CEnumPoint::AddRef()
{
    return ++m_refs;
}

STDMETHODIMP_(unsigned long)
CEnumPoint::Release()
{
    if(--m_refs == 0){
      if(m_psa != NULL)
SafeArrayDestroy(m_psa);
      delete this;
      return 0;
    }
    return m_refs;
}

//---------------------------------------------------------------------
//                        IEnumVARIANT methods
//---------------------------------------------------------------------

/***
*HRESULT CEnumPoint::Next(unsigned long, VARIANT*, unsigned long*)
*Purpose:
*  Attempt to get the next 'celt' items in the enumeration sequence.
*
*Entry:
*  celt = the number of elements to get
*
*Exit:
*  return value = HRESULT
*    S_OK
*    S_FALSE - the end of the sequence was reached
*
*  rgvar = array of the next 'celt' items
*  *pceltFetched = count of the elements actually fetched.
*
***********************************************************************/
STDMETHODIMP
CEnumPoint::Next(
    unsigned long celt,
    VARIANT FAR* rgvar,
    unsigned long FAR* pceltFetched)
{
    long ix;
    unsigned int i;
    HRESULT hresult;

    for(i = 0; i < celt; ++i)
      VariantInit(&rgvar[i]);

    for(i = 0; i < celt; ++i){
      if(m_iCurrent == m_celts){
        hresult = S_FALSE;
goto LDone;
      }

      ix = m_iCurrent++;
      hresult = SafeArrayGetElement(m_psa, &ix, &rgvar[i]);
      if(FAILED(hresult))
goto LError0;
    }

    hresult = NOERROR;

LDone:;
    if (pceltFetched != NULL)
      *pceltFetched = i;

    return hresult;

LError0:;

    for(i = 0; i < celt; ++i)
      VariantClear(&rgvar[i]);

    return hresult;
}

/***
*HRESULT CEnumPoint::Skip(unsigned long)
*Purpose:
*  Attempt to skip over the next 'celt' elements in the enumeration
*  sequence.
*
*Entry:
*  celt = the count of elements to skip
*
*Exit:
*  return value = HRESULT
*    S_OK
*    S_FALSE -  the end of the sequence was reached
*
***********************************************************************/
STDMETHODIMP
CEnumPoint::Skip(unsigned long celt)
{
    m_iCurrent += celt;

    if(m_iCurrent > m_celts)
     m_iCurrent = m_celts;

    return (m_iCurrent == m_celts)
      ? S_FALSE : NOERROR;
}

/***
*HRESULT CEnumPoint::Reset(void)
*Purpose:
*  Reset the enumeration sequence back to the beginning.
*
*Entry:
*  None
*
*Exit:
*  return value = SHRESULT CODE
*    S_OK
*
***********************************************************************/
STDMETHODIMP
CEnumPoint::Reset()
{
    m_iCurrent = 0;

    return NOERROR;
}

/***
*HRESULT CEnumPoint::Clone(IEnumVARIANT**)
*Purpose:
*  Retrun a CPoint enumerator with exactly the same state as the
*  current one.
*
*Entry:
*  None
*
*Exit:
*  return value = HRESULT
*    S_OK
*    E_OUTOFMEMORY
*
***********************************************************************/
STDMETHODIMP
CEnumPoint::Clone(IEnumVARIANT FAR* FAR* ppenum)
{
    HRESULT hresult;
    SAFEARRAY FAR* psa;
    CEnumPoint FAR* penum;

    hresult = SafeArrayCopy(m_psa, &psa);
    if(FAILED(hresult))
      return hresult;

    hresult = CEnumPoint::Create(psa, &penum);
    if(FAILED(hresult))
      goto LError0;

    // Assert(penum->m_celts == m_celts);

    penum->m_iCurrent = m_iCurrent;

    *ppenum = penum;

    return NOERROR;

LError0:
    SafeArrayDestroy(psa);

    return hresult;
}


RESOURCE.H

/***
*resource.h
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*
*Implementation Notes:
*
*****************************************************************************/

#ifdef _MAC

#define kMinSize500/* minimum size (in K) */
#define kPrefSize500/* preferred size (in K) */

#definerMenuBar128/* menu bar */
#definerAboutAlert128/* about alert */
#definerUserAlert129/* error alert */
#definerWindow128/* application's window */
#define rIcon128

#definemApple128/* Apple menu */
#defineiAbout1

#definemFile129/* File menu */
#defineiNew1
#defineiClose4
#defineiQuit12

#definemEdit130/* Edit menu */
#defineiUndo1
#defineiCut3
#defineiCopy4
#defineiPaste5
#defineiClear6

#define mSpoly131
#define iTest1

#define kMinHeap21 * 1024
#define kMinSpace8 * 1024

#else /* WIN16 || WIN32 */

# define IDM_CLEAR1
# define IDM_DUMP2
# define IDM_FIRSTCHILD100

#endif


SPOLY.H

/***
*spoly.h - Application-wide definitions
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*
*Implementation Notes:
*
*****************************************************************************/

#include "hostenv.h"
#include "resource.h"
#include "clsid.h"

#if defined(_MAC)
# define PASCAL pascal
# define STRSTR strstr
#elif defined(WIN32)
# include "statbar.h"
# define STRSTR strstr
#else /* WIN16 */
# include "statbar.h"
# define STRSTR _fstrstr
#endif

#ifdef _MAC
# define UNUSED(X) ((void)(void*)&(X))
#else
# define UNUSED(X) (X)
#endif

#define DIM(X) (sizeof(X) / sizeof(X[0]))

extern "C" void Assert(int, char FAR*, int, char FAR*);
#define ASSERT(X) Assert(X, __FILE__, __LINE__, NULL)
#define ASSERTSZ(X, MSG) Assert(X, __FILE__, __LINE__, MSG)

// Description of a single named parameter.
//
typedef struct tagPARAM_DESC {
    OLECHAR FAR* szName;// parameter name
} PARAM_DESC;

// Description of a single member.
//
typedef struct tagMEMBERDESC {
    OLECHAR FAR* szName;// member name
    DISPID id;// member id
    PARAM_DESC FAR* rgpd;// ptr to array of PARAM_DESCs
    unsigned int cParams;// number of parameters
} MEMBERDESC;

STDAPI
SPolyGetIDsOfNames(
    MEMBERDESC FAR* pmd,
    unsigned int cMethods,
    OLECHAR FAR* FAR* rgszNames,
    unsigned int cNames,
    LCID lcid,
    DISPID FAR* rgid);

STDAPI InitOle();
STDAPI UninitOle();
STDAPI Revoke();

void IncObjectCount();
void DecObjectCount();


STATBAR.CPP

/***
*statbar.cpp
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*
*Implementation Notes:
*
*****************************************************************************/

#include <stdarg.h>

#include "hostenv.h"
#include "statbar.h"

extern "C" long FAR PASCAL StatBarWndProc(HWND, unsigned int, WPARAM, LPARAM);

TCHAR FAR* CStatBar::m_szWndClass = TSTR("StatBarWndClass");

CStatBar::CStatBar()
{
    m_refs = 0;

    m_x = 0;
    m_y = 0;
    m_width = 0;
    m_height = 0;

    m_bstrMsg = NULL;

    m_hfont = (HFONT)0;
}

CStatBar::~CStatBar()
{
    SysFreeString(m_bstrMsg);
}

/***
*PUBLIC CStatBar FAR* CStatBar::Create(HINSTANCE, HWND)
*
*Purpose:
*
*Entry:
*
*Exit:
*
***********************************************************************/
CStatBar FAR*
CStatBar::Create(HINSTANCE hinst, HWND hwndFrame)
{
    CStatBar FAR* psb;

    psb = new FAR CStatBar();
    if(psb == NULL)
      return NULL;
    psb->AddRef();

    if(!psb->Register(hinst))
      goto LFail;

    psb->m_hwnd = CreateWindow(
      CStatBar::m_szWndClass,
      NULL,
      WS_CHILD | WS_CLIPSIBLINGS,
      0, 0, 0, 0,
      hwndFrame,
      0,
      hinst,
      NULL);

    if(!psb->m_hwnd)
      goto LFail;

    // Stash the newly created CStatBar* in the extra bytes of the
    // associated window so we can get at the instance in the message
    // proc.
    //
    // Note: we do not AddRef for this reference. We make sure that the
    // window is destroyed when the refcnt goes to 0.
    //
    SetWindowLong(psb->m_hwnd, 0, (long)psb);

    return psb;

LFail:;
    delete psb;
    return NULL;
}

//---------------------------------------------------------------------
//                     IUnknown Methods
//---------------------------------------------------------------------

STDMETHODIMP
CStatBar::QueryInterface(REFIID riid, void FAR* FAR* ppv)
{
    if(IsEqualIID(riid,IID_IUnknown)){
      *ppv = this;
      AddRef();
      return NOERROR;
    }
    *ppv = (void FAR*)NULL;
    return E_NOINTERFACE;
}

STDMETHODIMP_(unsigned long)
CStatBar::AddRef(void)
{
    return ++m_refs;
}

STDMETHODIMP_(unsigned long)
CStatBar::Release(void)
{
    if(--m_refs == 0){

      // destroy the status bar window.
      //
      SendMessage(m_hwnd, WM_DESTROY, 0, 0L);

      delete this;
      return 0;
    }

    return m_refs;
}

//---------------------------------------------------------------------
//                     Introduced Methods
//---------------------------------------------------------------------

/***
*PRIVATE BOOL CStatBar::Register(HINSTANCE)
*
*Purpose:
*  Register the status bar window class.
*
*Entry:
*  None
*
*Exit:
*  return value = BOOL, TRUE if successful, FALSE if not.
*
***********************************************************************/
BOOL
CStatBar::Register(HINSTANCE hinst)
{
    WNDCLASS  wc;

    // register the class, unless already registered.
    if(GetClassInfo(hinst, m_szWndClass, &wc) == 0){
      wc.style         = 0;
      wc.lpfnWndProc   = StatBarWndProc;
      wc.cbClsExtra    = 0;
      wc.cbWndExtra    = sizeof(CStatBar FAR*);
      wc.hInstance     = hinst;
      wc.hIcon         = 0;
      wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
      wc.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
      wc.lpszMenuName  = 0;
      wc.lpszClassName = CStatBar::m_szWndClass;
      if(!RegisterClass(&wc))
        return FALSE;
    }
    return TRUE;
}

/***
*PUBLIC void CStatBar::Show(void)
*
*Purpose:
*  Show the status bar window associated with this CStatBar instance.
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
void
CStatBar::Show()
{
    ShowWindow(m_hwnd, SW_SHOW);
}

void
CStatBar::SetFont(HFONT hfont)
{
    HDC hdc;
    TEXTMETRIC tm;
    HFONT hfontOld;

    // compute the character sizes given this new font.
    //
    hdc = GetDC(m_hwnd);
    hfontOld = (HFONT)SelectObject(hdc, hfont);
    GetTextMetrics(hdc, &tm);
    m_dxFont = tm.tmAveCharWidth;
    m_dyFont = tm.tmHeight + tm.tmExternalLeading;
    SelectObject(hdc, hfontOld);
    ReleaseDC(m_hwnd, hdc);

    m_hfont = hfont;
}

/***
*PRIVATE CStatBar::WMPaint(void)
*
*Purpose:
*  This method is responsible for drawing the status bar, and is called
*  in response to a WM_PAINT message.
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
void
CStatBar::WMPaint()
{
    HDC hdc;
    RECT rcMsg;
    HRGN hrgn;
    HFONT hfontOld;
    PAINTSTRUCT ps;
    HPEN hpenBlack, hpenWhite, hpenGray, hpenOld;

    hdc = BeginPaint(m_hwnd, &ps);

    // compute the message box rect
    //
    rcMsg.top= 3;
    rcMsg.bottom= m_height - 3;
    rcMsg.left= m_dxFont;
    rcMsg.right= m_width - m_dxFont;

    // prepare the pens
    //
    hpenWhite= (HPEN)GetStockObject(WHITE_PEN);
    hpenBlack= (HPEN)GetStockObject(BLACK_PEN);
    hpenGray= CreatePen(PS_SOLID, 1, GetSysColor(COLOR_BTNSHADOW));

    // draw a top gray line
    //
    hpenOld = (HPEN)SelectObject(hdc, hpenGray);
#if   defined(WIN16)
    MoveTo(hdc, ps.rcPaint.left, 0);
#elif defined(WIN32)
    MoveToEx(hdc, ps.rcPaint.left, 0, NULL);
#endif
    LineTo(hdc, ps.rcPaint.right, 0);

    // draw a white line just under
    //
    SelectObject(hdc, hpenWhite);
#if   defined(WIN16)
    MoveTo(hdc, ps.rcPaint.left, 1);
#elif defined(WIN32)
    MoveToEx(hdc, ps.rcPaint.left, 1, NULL);
#endif
    LineTo(hdc, ps.rcPaint.right, 1);

    // do not overwrite the background color
    //
    SetBkMode(hdc, TRANSPARENT);

    // message area
    //
    SelectObject(hdc, hpenBlack);
#if   defined(WIN16)
    MoveTo(hdc, rcMsg.left,  rcMsg.bottom);
#elif defined(WIN32)
    MoveToEx(hdc, rcMsg.left,  rcMsg.bottom, NULL);
#endif
    LineTo(hdc, rcMsg.left,  rcMsg.top);
    LineTo(hdc, rcMsg.right, rcMsg.top);

    SelectObject(hdc, hpenWhite);
    LineTo(hdc, rcMsg.right, rcMsg.bottom);
    LineTo(hdc, rcMsg.left,  rcMsg.bottom);

    // select the black pen for writing
    //
    SelectObject(hdc, hpenBlack);

    // select the status bar font to write in
    //
    hfontOld = (HFONT)SelectObject(hdc, m_hfont);

    // set the clipping region
    //
    hrgn = CreateRectRgn(
      rcMsg.left, rcMsg.top, rcMsg.right, rcMsg.bottom);

    SelectClipRgn(hdc, hrgn);

    // draw the status message
    //
    TextOut(
      hdc,
      rcMsg.left + (m_dxFont / 2),
      rcMsg.top + ((rcMsg.bottom - rcMsg.top - m_dyFont) / 2),
      STRING(m_bstrMsg), (SysStringLen(m_bstrMsg)));

    // cleanup
    //
    SelectObject(hdc, hpenOld);
    SelectObject(hdc, hfontOld);

    DeleteObject(hrgn);
    DeleteObject(hpenGray);

    EndPaint(m_hwnd, &ps);
}

extern "C" long FAR PASCAL
StatBarWndProc(
    HWND hwnd,
    unsigned int message,
    WPARAM wParam,
    LPARAM lParam)
{
    CStatBar FAR* psb;

    switch(message){
    case WM_SIZE:
      return 0;
    case WM_PAINT:
      psb = (CStatBar FAR*)GetWindowLong(hwnd, 0);
      psb->WMPaint();
      return 0;
    }
    return(DefWindowProc(hwnd, message, wParam, lParam));
}

//---------------------------------------------------------------------
//                    Status Bar Utilities
//---------------------------------------------------------------------

extern "C" void
SBprintf(CStatBar FAR* psb, TCHAR FAR* szFmt, ...)
{
    va_list args;
static TCHAR buf[256];

    va_start(args, szFmt);
    wvsprintf(buf, szFmt, args);
    psb->SetText(WIDESTRING(buf));
    psb->Update();
}


STATBAR.H

/***
*statbar.h
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*
*Implementation Notes:
*  This file requires windows.h and ole2.h
*
*****************************************************************************/

class CStatBar : public IUnknown {
public:
    static CStatBar FAR* Create(HINSTANCE hinst, HWND hwndFrame);

    // IUnknown methods
    //
    STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppv);
    STDMETHOD_(unsigned long, AddRef)(void);
    STDMETHOD_(unsigned long, Release)(void);

    // Introduced methods
    //
    void Show(void);
    inline void Move(void);
    inline void Update(void);

    inline int GetX(void);
    inline void SetX(int x);

    inline int GetY(void);
    inline void SetY(int y);

    inline int GetHeight(void);
    inline void SetHeight(int height);

    inline int GetWidth(void);
    inline void SetWidth(int width);

    //inline HFONT GetFont(void);
    void SetFont(HFONT hfont);

    //char FAR* GetText(void);
    inline void SetText(OLECHAR FAR* sz);

    void WMPaint(void);
    BOOL Register(HINSTANCE);

private:
    CStatBar();
    ~CStatBar();

    unsigned longm_refs;

    HWNDm_hwnd;// the status bar window handle

    intm_x;// x coordinate of upper left corner
    intm_y;// y coordinate of upper left corner
    intm_height;
    intm_width;

    HFONTm_hfont;
    intm_dyFont;// font height
    intm_dxFont;// font width

    BSTRm_bstrMsg;// the status bar text

    static TCHAR FAR* m_szWndClass;
};

inline void
CStatBar::Move()
{
    MoveWindow(m_hwnd, m_x, m_y, m_width, m_height, TRUE);
}

inline void
CStatBar::Update()
{
    InvalidateRect(m_hwnd, NULL, TRUE);
    UpdateWindow(m_hwnd);
}

inline int
CStatBar::GetX()
{
    return m_x;
}

inline void
CStatBar::SetX(int x)
{
    m_x = x;
}

inline int
CStatBar::GetY(void)
{
    return m_y;
}

inline void
CStatBar::SetY(int y)
{
    m_y = y;
}

inline int
CStatBar::GetHeight(void)
{
    return m_height;
}

inline void
CStatBar::SetHeight(int height)
{
    m_height = height;
}

inline int
CStatBar::GetWidth(void)
{
    return m_width;
}

inline void
CStatBar::SetWidth(int width)
{
    m_width = width;
}

inline void
CStatBar::SetText(OLECHAR FAR* sz)
{
    SysFreeString(m_bstrMsg);
    m_bstrMsg = SysAllocString(sz);
}

extern "C" void
SBprintf(CStatBar FAR* psb, TCHAR FAR* szFmt, ...);


WINMAIN.CPP

/***
*winmain.cpp
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  This module is the main entry point for the sample IDispatch polygon
*  server, spoly.exe.
*
*  This program is intended to demonstrate an implementation of the IDispatch
*  interface. Spoly is a very simple app, that implements two simple objects,
*  CPoly and CPoint and exposes their properties and methods for programatic
*  and cross-process access via IDispatch.
*
*Implementation Notes:
*
*****************************************************************************/

#include <stdio.h>
#include <string.h>

#include "spoly.h"
#include "cpoint.h"
#include "cpoly.h"

HINSTANCE g_hinst = 0;

HWND g_hwndFrame = 0;
HWND g_hwndClient = 0;

TCHAR g_szFrameWndClass[] = TSTR("FrameWClass");

CStatBar FAR* g_psb = NULL;

BOOL InitApplication(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);

extern "C" int PASCAL WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
extern "C" long FAR PASCAL FrameWndProc(HWND, UINT, WPARAM, LPARAM);

extern "C" int PASCAL
WinMain(
    HINSTANCE hinst,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow)
{
    MSG msg;
    int retval;
    HRESULT hresult;

    if(!hPrevInstance)
      if(!InitApplication(hinst))
return FALSE;

    if((hresult = InitOle()) != NOERROR)
      return FALSE;

    if(!InitInstance(hinst, nCmdShow)){
      retval = FALSE;
      goto LExit;
    }

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

    g_psb->Release();
    CPoly::PolyTerm();

    retval = msg.wParam;

LExit:;
    UninitOle();

    return retval;
}

BOOL
InitApplication(HINSTANCE hinst)
{
    WNDCLASS  wc;

    wc.style= CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc= FrameWndProc;
    wc.cbClsExtra= 0;
    wc.cbWndExtra= 0;
    wc.hInstance= hinst;
    wc.hIcon= LoadIcon(hinst, TSTR("SPOLY"));
    wc.hCursor= LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground= (HBRUSH) (COLOR_APPWORKSPACE+1);
    wc.lpszMenuName= TSTR("SPolyMenu");
    wc.lpszClassName= g_szFrameWndClass;

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

    return TRUE;
}

#ifdef WIN32
#define szAppTitle TSTR("IDispatch Polygon Server (32-bit)")
#else //WIN32
#define szAppTitle TSTR("IDispatch Polygon Server")
#endif //WIN32

BOOL
InitInstance(HINSTANCE hinst, int nCmdShow)
{
    g_hinst = hinst;

    // Create a main frame window
    //
    g_hwndFrame = CreateWindow(
      g_szFrameWndClass,
      szAppTitle,
      WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      NULL,
      NULL,
      hinst,
      NULL);
    if(!g_hwndFrame)
      return FALSE;

    g_hwndClient = GetWindow(g_hwndFrame, GW_CHILD);
    if(!g_hwndClient)
      return FALSE;

    // create the status bar
    //
    g_psb = CStatBar::Create(g_hinst, g_hwndFrame);
    if(!g_psb)
      return FALSE;

    // initialize and show the status bar
    //
    g_psb->SetHeight(GetSystemMetrics(SM_CYCAPTION) - 1);
    g_psb->SetFont((HFONT)GetStockObject(SYSTEM_FONT));
    g_psb->SetText(OLESTR(""));
    g_psb->Show();

    ShowWindow(g_hwndFrame, nCmdShow);

    UpdateWindow(g_hwndFrame);

    return TRUE;
}

void
FrameWndOnCreate(HWND hwnd)
{
    CLIENTCREATESTRUCT ccs;

    ccs.hWindowMenu = NULL;
    ccs.idFirstChild = IDM_FIRSTCHILD;

    g_hwndClient = CreateWindow(
      TSTR("MDICLIENT"),
      0,
      WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE,
      0, 0, 0, 0,
      hwnd,
      (HMENU) 1,
      g_hinst,
      &ccs);
}

void
FrameWndOnSize(HWND hwnd)
{
    RECT rc;
    int height;

    // Get the client rectangle for the frame window
    GetClientRect(hwnd, &rc);

    height = g_psb->GetHeight();

    // adjust the client win to make room for the status bar.
    //
    MoveWindow(
      g_hwndClient,
      rc.left,
      rc.top,
      rc.right - rc.left,
      rc.bottom - rc.top - height,
      TRUE);

    // move the status bar to the bottom of the newly positioned window.
    //
    g_psb->SetX(rc.left);
    g_psb->SetY(rc.bottom - height),
    g_psb->SetWidth(rc.right - rc.left);
    g_psb->Move();
}

extern "C" long FAR PASCAL
FrameWndProc(
    HWND hwnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam)
{
    switch(message){
    case WM_COMMAND:
      switch(wParam){
      case IDM_DUMP:
CPoly::PolyDump();
return 0;

      case IDM_CLEAR:
InvalidateRect(g_hwndClient, NULL, TRUE);
return 0;
      }
      break;

    case WM_CREATE:
      FrameWndOnCreate(hwnd);
      break;

    case WM_SIZE:
      FrameWndOnSize(hwnd);
      return 1;

    case WM_PAINT:
      CPoly::PolyDraw();
      break;

    case WM_CLOSE:
      DestroyWindow(hwnd);
      return 0;

    case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
    }
    return DefFrameProc(hwnd, g_hwndClient, message, wParam, lParam);
}

#if defined(WIN32)

extern "C" OLECHAR FAR*
ConvertStrAtoW(char FAR* strIn, OLECHAR FAR* buf, UINT size)
{
  MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED,
                       strIn, -1, buf, size) ;
  return buf;
}

extern "C" OLECHAR FAR*
WideString(char FAR* strIn)
{
  static OLECHAR buf[256];

  return (ConvertStrAtoW(strIn, buf, 256));
}

extern "C" char FAR*
ConvertStrWtoA(OLECHAR FAR* strIn, char FAR* buf, UINT size)
{
  int badConversion = FALSE;

  WideCharToMultiByte(CP_ACP, NULL,
              strIn, -1,
      buf, size,
      NULL, &badConversion);
  return buf;
}

extern "C" char FAR*
AnsiString(OLECHAR FAR* strIn)
{
  static char buf[256];

  return (ConvertStrWtoA(strIn, buf, 256));
}

#endif


README.TXT

OLE Automation Sample

SUMMARY
=======

The SPoly2 sample draws polygons. The only way to make SPoly2 draw a polygon
is to use its programmability interface.

One OLE automation object is exposed by Spoly2: spoly.application.
The object spoly2.application is associated with SPoly's main application
window. It controls drawing polygons and clearing the display.

MORE INFORMATION
================

SPoly2 implements IDispatch for spoly.application by using INTERFACEDATA,
DispGetIDsOfNames, and DispInvoke.

Methods defined on spoly2.application
-------------------------------------

Name                        Description

Draw                        Draw the polygon.
Reset                       Delete all points from the polygon.
AddPoint(X, Y)              Add a point with coordinates (x,y) to the
                            polygon.
EnumPoints as VT_ENUM       Return a collection of the polygon's points.
GetXOrigin as short         Get the X origin of the polygon.
SetXOrigin(x as short)      Set the X origin of the polygon.
GetYOrigin as short         Get the Y origin of the polygon.
SetYOrigin(y as short)      Set the Y origin of the polygon.
GetWidth as short           Get the line width of the polygon.
SetWidth(width as short)    Set the line width of the polygon.

Shortcomings of this sample
---------------------------

Many items in this sample should be properties. Instead, they are
implemented as methods. Anything which behaves like an attribute of the
object should be a property.

This is not a good example of how to implement a collection.


CLSID.H

/***
*clsid.h
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  This file defines the CLSIDs
*
*Implementation Notes:
*
*****************************************************************************/

DEFINE_GUID(CLSID_CPoly2, 0x00020464, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
DEFINE_GUID(CLSID_CPoint2, 0x00020465, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);

#ifdef INITGUID
# ifdef _MAC
/* Define this under PPC Metroworks */
#  if !defined(_MW_BUILD)
DEFINE_GUID(IID_IDispatch, 0x00020400L, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
DEFINE_GUID(IID_IEnumVARIANT, 0x00020404L, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
#  endif
DEFINE_GUID(GUID_NULL, 0L, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
DEFINE_GUID(IID_IUnknown, 0x00000000L, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
DEFINE_GUID(IID_IClassFactory, 0x00000001L, 0, 0, 0xC0, 0, 0, 0, 0, 0, 0, 0x46);
# endif
#endif


CPOINT.CPP

/***
*cpoint.cpp
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  This module implements the CPoint and CPointCF classes.
*
*  This module is intended as a sample implementation of the IDispatch
*  interface, and its purpose is to demonstrate how an object can
*  expose methods and properties for programatic and cross-process
*  access via the IDispatch interface.
*
*Implementation Notes:
*
*****************************************************************************/

#include "spoly.h"
#include "cpoint.h"

CPoint::CPoint()
{
    m_x = 0;
    m_y = 0;
    m_refs = 0;
    m_ptinfo = NULL;
}

/***
*CPoint::Create(void)
*Purpose:
*  Create an instance of a CPoint object.
*
*Entry:
*  None
*
*Exit:
*  returns a CPoint*, NULL if creation failed.
*
***********************************************************************/
CPoint FAR*
CPoint::Create()
{
    HRESULT hresult;
    CPoint FAR* ppoint;
    ITypeInfo FAR* ptinfo;
extern INTERFACEDATA NEAR g_idataCPoint;

    if((ppoint = new FAR CPoint()) == NULL)
      return NULL;
    ppoint->AddRef();

    hresult =
      CreateDispTypeInfo(&g_idataCPoint, LOCALE_SYSTEM_DEFAULT, &ptinfo);
    if(hresult != NOERROR)
      goto LError0;

    ppoint->m_ptinfo = ptinfo;

    return ppoint;

LError0:;
    ppoint->Release();

    return NULL;
}

//---------------------------------------------------------------------
//                     IUnknown Methods
//---------------------------------------------------------------------

STDMETHODIMP
CPoint::QueryInterface(REFIID riid, void FAR* FAR* ppv)
{
    if(IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IDispatch)){
      *ppv = this;
      AddRef();
      return NOERROR;
    }
    *ppv = NULL;
    return E_NOINTERFACE;
}

STDMETHODIMP_(unsigned long)
CPoint::AddRef(void)
{
    return ++m_refs;
}

STDMETHODIMP_(unsigned long)
CPoint::Release(void)
{
    if(--m_refs == 0){
      if(m_ptinfo != NULL){
m_ptinfo->Release();
      }
      delete this;
      return 0;
    }
    return m_refs;
}

//---------------------------------------------------------------------
//                     IDispatch methods
//---------------------------------------------------------------------

STDMETHODIMP
CPoint::GetTypeInfoCount(unsigned int FAR* pctinfo)
{
    // this object has a single *introduced* interface
    //
    *pctinfo = 1;

    return NOERROR;
}

STDMETHODIMP
CPoint::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
{
    UNUSED(lcid);

    if(itinfo != 0)
      return DISP_E_BADINDEX;

    m_ptinfo->AddRef();
    *pptinfo = m_ptinfo;

    return NOERROR;
}

/***
*HRESULT CPoint::GetIDsOfNames(REFIID, char**, unsigned int, LCID, DISPID*)
*Purpose:
*  This method translates the given array of names to a corresponding
*  array of DISPIDs.
*
*  Index 0 of the name array is the member name, and indices 1-N if
*  present represent named parameters on that member.
*
*  The local ID ('lcid') is unused by this naive implementation. A more
*  sophisticated implementation, sensitive to localization and natural
*  language support would use the locale ID to interpret the given names
*  in a correct locale specific context.
*
*Entry:
*  rgszNames = pointer to an array of names
*  cNames = the number of names in the rgszNames array
*  lcid = the callers locale ID
*
*Exit:
*  return value = HRESULT
*  rgid = array of name IDs corresponding to the rgszNames array
*    this array will contain -1 for each entry that is not known.
*
***********************************************************************/
STDMETHODIMP
CPoint::GetIDsOfNames(
    REFIID riid,
    OLECHAR FAR* FAR* rgszNames,
    unsigned int cNames,
    LCID lcid,
    DISPID FAR* rgdispid)
{
    UNUSED(lcid);

    if(!IsEqualIID(riid,IID_NULL))
      return DISP_E_UNKNOWNINTERFACE;

    return DispGetIDsOfNames(m_ptinfo, rgszNames, cNames, rgdispid);
}

/***
*HRESULT CPoint::Invoke(...)
*Purpose:
*  Dispatch a method or property request for objects of type CPoint.
*
*  see the IDispatch document for more information, and a general
*  description of this method.
*
*Entry:
*  dispidMember = the DISPID of the member being requested
*
*  riid = reference to the interface ID of the interface on this object
*    that the requested member belongs to. IID_NULL means to interpret
*    the member as belonging to the implementation defined "default"
*    or "primary" interface.
*
*  lcid = the caller's locale ID
*
*  wFlags = flags indicating the type of access being requested
*
*  pdispparams = pointer to the DISPPARAMS struct containing the
*    requested members arguments (if any) and its named parameter
*    DISPIDs (if any).
*
*Exit:
*  return value = HRESULT
*   see the IDispatch spec for a description of possible success codes.
*
*  pvarResult = pointer to a caller allocated VARIANT containing
*    the members return value (if any).
*
*  pexcepinfo = caller allocated exception info structure, this will
*    be filled in only if an exception was raised that must be passed
*    up through Invoke to an enclosing handler.
*
*  puArgErr = pointer to a caller allocated UINT, that will contain the
*    index of the offending argument if a DISP_E_TYPEMISMATCH error
*    was returned indicating that one of the arguments was of an
*    incorrect type and/or could not be reasonably coerced to a proper
*    type.
*
***********************************************************************/
STDMETHODIMP
CPoint::Invoke(
    DISPID dispidMember,
    REFIID riid,
    LCID lcid,
    unsigned short wFlags,
    DISPPARAMS FAR* pdispparams,
    VARIANT FAR* pvarResult,
    EXCEPINFO FAR* pexcepinfo,
    unsigned int FAR* puArgErr)
{
    UNUSED(lcid);

    if(!IsEqualIID(riid, IID_NULL))
      return DISP_E_UNKNOWNINTERFACE;

    return DispInvoke(
      this, m_ptinfo,
      dispidMember, wFlags, pdispparams,
      pvarResult, pexcepinfo, puArgErr);
}

//---------------------------------------------------------------------
//                       Introduced methods
//---------------------------------------------------------------------

short METHODCALLTYPE EXPORT
CPoint::GetX()
{
    return m_x;
}

void METHODCALLTYPE EXPORT
CPoint::SetX(short x)
{
    m_x = x;
}

short METHODCALLTYPE EXPORT
CPoint::GetY()
{
    return m_y;
}

void METHODCALLTYPE EXPORT
CPoint::SetY(short y)
{
    m_y = y;
}

//---------------------------------------------------------------------
//         Implementation of the CPoint Class Factory
//---------------------------------------------------------------------

CPointCF::CPointCF()
{
    m_refs = 0;
}

IClassFactory FAR*
CPointCF::Create()
{
    CPointCF FAR* pCF;

    if((pCF = new FAR CPointCF()) == NULL)
      return NULL;
    pCF->AddRef();
    return pCF;
}

STDMETHODIMP
CPointCF::QueryInterface(REFIID riid, void FAR* FAR* ppv)
{
    if(IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IClassFactory)){
      *ppv = this;
      ++m_refs;
      return NOERROR;
    }
    *ppv = NULL;
    return E_NOINTERFACE;
}

STDMETHODIMP_(unsigned long)
CPointCF::AddRef(void)
{
    return ++m_refs;
}

STDMETHODIMP_(unsigned long)
CPointCF::Release(void)
{
    if(--m_refs == 0){
      delete this;
      return 0;
    }
    return m_refs;
}

STDMETHODIMP
CPointCF::CreateInstance(
    IUnknown FAR* punkOuter,
    REFIID riid,
    void FAR* FAR* ppv)
{
    HRESULT hresult;
    CPoint FAR *ppoint;

    UNUSED(punkOuter);

    if((ppoint = CPoint::Create()) == NULL){
      *ppv = NULL;
      return E_OUTOFMEMORY;
    }
    hresult = ppoint->QueryInterface(riid, ppv);
    ppoint->Release();
    return hresult;
}

STDMETHODIMP
#ifdef _MAC
CPointCF::LockServer(unsigned long fLock)
#else
CPointCF::LockServer(BOOL fLock)
#endif
{
    UNUSED(fLock);

    return NOERROR;
}


CPOINT.H

/***
*cpoint.h
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  Definition of the CPoint class.
*
*  The CPoint object exposes two properties for programatic access
*  via the IDispatch interface.
*
*  properties:
*    X - the 'x' coordinate of the point
*    Y - the 'y' coordinate of the point
*
*Implementation Notes:
*
*****************************************************************************/

#ifndefCLASS
#ifdef__TURBOC__
#define CLASS class huge
#else
#define CLASS class FAR
#endif
#endif

class CPoly;

CLASS CPoint : public IDispatch {
    friend class CPoly;

public:
    static CPoint FAR* Create();

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppvObj);
    STDMETHOD_(unsigned long, AddRef)(void);
    STDMETHOD_(unsigned long, Release)(void);

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(unsigned int FAR* pcTypeInfo);

    STDMETHOD(GetTypeInfo)(
      unsigned int iTypeInfo,
      LCID lcid,
      ITypeInfo FAR* FAR* ppTypeInfo);

    STDMETHOD(GetIDsOfNames)(
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      unsigned int cNames,
      LCID lcid,
      DISPID FAR* rgdispid);

    STDMETHOD(Invoke)(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      unsigned short wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      unsigned int FAR* pwArgErr);

    /* Introduced methods */

    virtual short METHODCALLTYPE EXPORT GetX(void);
    virtual void  METHODCALLTYPE EXPORT SetX(short x);
    virtual short METHODCALLTYPE EXPORT GetY(void);
    virtual void  METHODCALLTYPE EXPORT SetY(short y);

private:
    CPoint();

    unsigned long m_refs;

    short m_x;
    short m_y;

    ITypeInfo FAR* m_ptinfo;
};

// member DISPIDs
//
enum IDMEMBER_CPOINT {
    IDMEMBER_CPOINT_GETX = 1,
    IDMEMBER_CPOINT_SETX,
    IDMEMBER_CPOINT_GETY,
    IDMEMBER_CPOINT_SETY,
    IDMEMBER_CPOINT_MAX
};

// member indices - this is an enumeration of all members on CPoint
//
enum IMETH_CPOINT {
    IMETH_CPOINT_QUERYINTERFACE = 0,
    IMETH_CPOINT_ADDREF,
    IMETH_CPOINT_RELEASE,
    IMETH_CPOINT_GETTYPEINFOCOUNT,
    IMETH_CPOINT_GETTYPEINFO,
    IMETH_CPOINT_GETIDSOFNAMES,
    IMETH_CPOINT_INVOKE,

    IMETH_CPOINT_GETX,
    IMETH_CPOINT_SETX,
    IMETH_CPOINT_GETY,
    IMETH_CPOINT_SETY
};

// structure used to link together lists of points
//
struct POINTLINK {
    POINTLINK FAR* next;
    CPoint FAR* ppoint;
};

// The CPoint Class Factory
//
CLASS CPointCF : public IClassFactory
{
public:
    static IClassFactory FAR* Create();

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(REFIID iid, void FAR* FAR* ppv);
    STDMETHOD_(unsigned long, AddRef)(void);
    STDMETHOD_(unsigned long, Release)(void);

    /* IClassFactory methods */
    STDMETHOD(CreateInstance)(
      IUnknown FAR* pUnkOuter, REFIID iid, void FAR* FAR* ppv);
#ifdef _MAC
    STDMETHOD(LockServer)(unsigned long fLock);
#else
    STDMETHOD(LockServer)(BOOL fLock);
#endif

private:
    CPointCF();

    unsigned long m_refs;
};


CPOLY.CPP

/***
*cpoly.cpp
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  This module implements the CPoly and CPolyCF classes.
*
*  This module is intended as a sample implementation of the IDispatch
*  interface, and its purpose is to demonstrate how an object can
*  expose methods and properties for programatic and cross-process
*  access via the IDispatch interface.
*
*Implementation Notes:
*
*****************************************************************************/

#include "spoly.h"
#include "cpoint.h"
#include "cpoly.h"
#include "cenumpt.h"

#ifndef _MAC
extern CStatBar FAR* g_psb;
#endif

#ifdef _MAC
extern "C" WindowPtr g_pwndClient;
#endif

extern unsigned int g_fQuit;

// our global list of polygons.
//
POLYLINK FAR* g_ppolylink = (POLYLINK FAR*)NULL;

// global count of polygons.
//
int g_cPoly = 0;

CPoly::CPoly()
{
    m_refs = 0;
    m_xorg = 0;
    m_yorg = 0;
    m_width = 0;
    m_cPoints = 0;

    m_red   = 0;
    m_green = 0;
    m_blue  = 0;

    m_ptinfo = NULL;

    m_ppointlink = NULL;
    m_ppointlinkLast = NULL;
}

/***
*CPoly *CPoly::Create(void)
*Purpose:
*  Create an instance of a CPoly object, and add it to the global
*  list of polygons.
*
*Entry:
*  None
*
*Exit:
*  returns a polygon object, NULL the allocation failed.
*
***********************************************************************/
CPoly FAR*
CPoly::Create()
{
    HRESULT hresult;
    CPoly FAR* ppoly;
    ITypeInfo FAR* ptinfo;
    POLYLINK FAR* ppolylink;
extern INTERFACEDATA NEAR g_idataCPoly;

    if((ppolylink = new FAR POLYLINK) == (POLYLINK FAR*)NULL)
      return (CPoly FAR*)NULL;

    if((ppoly = new FAR CPoly()) == (CPoly FAR*)NULL)
      return (CPoly FAR*)NULL;

    ppoly->AddRef();

    // create and attach its TypeInfo
    //
    hresult = CreateDispTypeInfo(&g_idataCPoly, LOCALE_SYSTEM_DEFAULT, &ptinfo);
    if(hresult != NOERROR)
      goto LError0;

    ppoly->m_ptinfo = ptinfo;

    // push the new polygon onto the front of the polygon list.
    //
    ++g_cPoly;

    ppolylink->ppoly = ppoly;

    ppolylink->next = g_ppolylink;
    g_ppolylink = ppolylink;

#ifndef _MAC
    SBprintf(g_psb, TSTR("#poly = %d"), g_cPoly);
#endif

    IncObjectCount();

    return ppoly;

LError0:;
    ppoly->Release();

    return NULL;
}

//---------------------------------------------------------------------
//                     IUnknown Methods
//---------------------------------------------------------------------

STDMETHODIMP
CPoly::QueryInterface(REFIID riid, void FAR* FAR* ppv)
{
    if(!IsEqualIID(riid, IID_IUnknown))
      if(!IsEqualIID(riid, IID_IDispatch)) {
*ppv = NULL;
        return E_NOINTERFACE;
    }

    *ppv = this;
    AddRef();
    return NOERROR;
}

STDMETHODIMP_(unsigned long)
CPoly::AddRef()
{
    return ++m_refs;
}

STDMETHODIMP_(unsigned long)
CPoly::Release()
{
    POLYLINK FAR* FAR* pppolylink, FAR* ppolylinkDead;

    if(--m_refs == 0){
      Reset(); // release all CPoints

      // remove ourselves from the global list of polygons
      //
      for( pppolylink = &g_ppolylink;
  *pppolylink != NULL;
   pppolylink = &(*pppolylink)->next)
      {
if((*pppolylink)->ppoly == this){
  ppolylinkDead = *pppolylink;
  *pppolylink = (*pppolylink)->next;
  delete ppolylinkDead;
  break;
}
      }

      if(m_ptinfo != NULL){
m_ptinfo->Release();
      }

      --g_cPoly;

#ifndef _MAC
      SBprintf(g_psb, TSTR("#poly = %d"), g_cPoly);
#endif

      delete this;

      DecObjectCount();

      return 0;
    }
    return m_refs;
}

//---------------------------------------------------------------------
//                     IDispatch Methods
//---------------------------------------------------------------------

STDMETHODIMP
CPoly::GetTypeInfoCount(unsigned int FAR* pctinfo)
{
    // This object has a single *introduced* interface
    //
    *pctinfo = 1;

    return NOERROR;
}

STDMETHODIMP
CPoly::GetTypeInfo(unsigned int itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
{
    UNUSED(lcid);

    if(itinfo != 0)
      return DISP_E_BADINDEX;

    m_ptinfo->AddRef();
    *pptinfo = m_ptinfo;

    return NOERROR;
}

/***
*HRESULT CPoly::GetIDsOfNames(char**, unsigned int, LCID, DISPID*)
*Purpose:
*  This method translates the given array of names to a corresponding
*  array of DISPIDs.
*
*  This method deferrs to a common implementation shared by
*  both the CPoly and CPoint objects. See the description of
*  'SPolyGetIDsOfNames()' in dispimpl.cpp for more information.
*
*Entry:
*  rgszNames = pointer to an array of names
*  cNames = the number of names in the rgszNames array
*  lcid = the callers locale ID
*
*Exit:
*  return value = HRESULT
*  rgdispid = array of DISPIDs corresponding to the rgszNames array
*    this array will contain -1 for each entry that is not known.
*
***********************************************************************/
STDMETHODIMP
CPoly::GetIDsOfNames(
    REFIID riid,
    OLECHAR FAR* FAR* rgszNames,
    unsigned int cNames,
    LCID lcid,
    DISPID FAR* rgdispid)
{
    UNUSED(lcid);

    if(!IsEqualIID(riid, IID_NULL))
      return DISP_E_UNKNOWNINTERFACE;

    return DispGetIDsOfNames(m_ptinfo, rgszNames, cNames, rgdispid);
}

/***
*HRESULT CPoly::Invoke(...)
*Purpose:
*  Dispatch a method or property request for objects of type CPoly.
*
*  see the IDispatch document for more information, and a general
*  description of this method.
*
*Entry:
*  dispidMember = the DISPID of the member being requested
*
*  riid = reference to the interface ID of the interface on this object
*    that the requested member belongs to. IID_NULL means to interpret
*    the member as belonging to the implementation defined "default"
*    or "primary" interface.
*
*  lcid = the caller's locale ID
*
*  wFlags = flags indicating the type of access being requested
*
*  pdispparams = pointer to the DISPPARAMS struct containing the
*    requested members arguments (if any) and its named parameter
*    DISPIDs (if any).
*
*Exit:
*  return value = HRESULT
*   see the IDispatch spec for a description of possible success codes.
*
*  pvarResult = pointer to a caller allocated VARIANT containing
*    the members return value (if any).
*
*  pexcepinfo = caller allocated exception info structure, this will
*    be filled in only if an exception was raised that must be passed
*    up through Invoke to an enclosing handler.
*
*  puArgErr = pointer to a caller allocated UINT, that will contain the
*    index of the offending argument if a DISP_E_TYPEMISMATCH error
*    was returned indicating that one of the arguments was of an
*    incorrect type and/or could not be reasonably coerced to a proper
*    type.
*
***********************************************************************/
STDMETHODIMP
CPoly::Invoke(
    DISPID dispidMember,
    REFIID riid,
    LCID lcid,
    unsigned short wFlags,
    DISPPARAMS FAR* pdispparams,
    VARIANT FAR* pvarResult,
    EXCEPINFO FAR* pexcepinfo,
    unsigned int FAR* puArgErr)
{
    UNUSED(lcid);

    if(!IsEqualIID(riid, IID_NULL))
      return DISP_E_UNKNOWNINTERFACE;

    return DispInvoke(
      this, m_ptinfo,
      dispidMember, wFlags, pdispparams,
      pvarResult, pexcepinfo, puArgErr);
}

//---------------------------------------------------------------------
//                     Introduced Methods
//---------------------------------------------------------------------

/***
*void CPoly::Draw(void)
*Purpose:
*  Draw the polygon, using the current x/y origin and line width
*  properties.
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
void METHODCALLTYPE EXPORT
CPoly::Draw()
{
    short xorg, yorg;
    POINTLINK FAR* ppointlinkFirst, FAR* ppointlink;

    if((ppointlinkFirst = m_ppointlink) == (POINTLINK FAR*)NULL)
      return;

#ifdef _MAC /* { */

    short x,y;
    RGBColor rgb;
    WindowPtr pwndSaved;

    GetPort(&pwndSaved);
    SetPort(g_pwndClient);

    PenNormal();
    PenSize(m_width, m_width);

    rgb.red = m_red;
    rgb.green = m_green;
    rgb.blue = m_blue;
    RGBForeColor(&rgb);

    xorg = m_xorg;
    yorg = m_yorg;

    MoveTo(
      xorg + ppointlinkFirst->ppoint->m_x,
      yorg + ppointlinkFirst->ppoint->m_y);

    for(ppointlink = ppointlinkFirst->next;
ppointlink != (POINTLINK FAR*)NULL;
ppointlink = ppointlink->next)
    {
      x = xorg + ppointlink->ppoint->m_x;
      y = yorg + ppointlink->ppoint->m_y;
      LineTo(x, y);
    }

    LineTo(
      xorg + ppointlinkFirst->ppoint->m_x,
      yorg + ppointlinkFirst->ppoint->m_y);

    SetPort(pwndSaved);

#else /* }{ */

    HDC hdc;
    RECT rect;
    HPEN hpen, hpenOld;
extern HWND g_hwndClient;

    GetClientRect(g_hwndClient, &rect);
    xorg = m_xorg + (short) rect.left;
    yorg = m_yorg + (short) rect.top;

    hdc = GetDC(g_hwndClient);
    hpen = CreatePen(PS_SOLID, m_width, RGB(m_red, m_green, m_blue));
    hpenOld = (HPEN)SelectObject(hdc, hpen);

#ifdef WIN32
    MoveToEx(hdc,
      xorg + ppointlinkFirst->ppoint->m_x,
      yorg + ppointlinkFirst->ppoint->m_y, NULL);
#else
    MoveTo(hdc,
      xorg + ppointlinkFirst->ppoint->m_x,
      yorg + ppointlinkFirst->ppoint->m_y);
#endif

    for(ppointlink = ppointlinkFirst->next;
ppointlink != (POINTLINK FAR*)NULL;
ppointlink = ppointlink->next)
    {
      LineTo(hdc,
xorg + ppointlink->ppoint->m_x,
yorg + ppointlink->ppoint->m_y);
    }

    LineTo(hdc,
      xorg + ppointlinkFirst->ppoint->m_x,
      yorg + ppointlinkFirst->ppoint->m_y);

    SelectObject(hdc, hpenOld);
    DeleteObject(hpen);

    ReleaseDC(g_hwndClient, hdc);

#endif /* } */
}

/***
*void CPoly::Reset(void)
*Purpose:
*  Release all points referenced by this poly.
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
void METHODCALLTYPE EXPORT
CPoly::Reset()
{
    POINTLINK FAR* ppointlink, FAR* ppointlinkNext;

    for(ppointlink = m_ppointlink;
ppointlink != (POINTLINK FAR*)NULL;
ppointlink = ppointlinkNext)
    {
      ppointlinkNext = ppointlink->next;
      ppointlink->ppoint->Release();
      delete ppointlink;
    }

    m_cPoints = 0;
    m_ppointlink = NULL;
    m_ppointlinkLast = NULL;
}

/***
*HRESULT CPoly::AddPoint(short, short)
*Purpose:
*  Add a CPoint with the given coordinates to the end of our current
*  list of points.
*
*Entry:
*  x,y = the x and y coordinates of the new point.
*
*Exit:
*  return value = HRESULT
*
***********************************************************************/
HRESULT METHODCALLTYPE
CPoly::AddPoint(short x, short y)
{
    CPoint FAR* ppoint;
    POINTLINK FAR* ppointlink;

    ppoint = CPoint::Create();
    if(ppoint == (CPoint FAR*)NULL)
      return E_OUTOFMEMORY;

    ppoint->SetX(x);
    ppoint->SetY(y);

    ppointlink = new FAR POINTLINK;
    if(ppointlink == (POINTLINK FAR*)NULL){
      delete ppoint;
      return E_OUTOFMEMORY;
    }

    ppointlink->ppoint = ppoint;
    ppointlink->next = (POINTLINK FAR*)NULL;

    if(m_ppointlinkLast == (POINTLINK FAR*)NULL){
      m_ppointlink = m_ppointlinkLast = ppointlink;
    }else{
      m_ppointlinkLast->next = ppointlink;
      m_ppointlinkLast = ppointlink;
    }

    ++m_cPoints;

    return NOERROR;
}

/***
*IUnknown FAR* CPoly::EnumPoints(void);
*Purpose:
*  Return and enumerator for the points in this polygon.
*
*Entry:
*  None
*
*Exit:
*  return value = HRESULT
*
*  *ppenum = pointer to an IEnumVARIANT for the points in this polygon
*
***********************************************************************/
IUnknown FAR* METHODCALLTYPE EXPORT
CPoly::EnumPoints()
{
    unsigned int i;
    HRESULT hresult;
    VARIANT var;
    SAFEARRAY FAR* psa;
    IUnknown FAR* punk;
    CEnumPoint FAR* penum;
    POINTLINK FAR* ppointlink;
    SAFEARRAYBOUND rgsabound[1];

    rgsabound[0].lLbound = 0;
    rgsabound[0].cElements = m_cPoints;

    psa = SafeArrayCreate(VT_VARIANT, 1, rgsabound);
    if(psa == NULL){
      hresult = E_OUTOFMEMORY;
      goto LError0;
    }

    ppointlink = m_ppointlink;
    for(i = 0; i < m_cPoints; ++i){
      long ix[1];

      if(ppointlink == NULL){
        // this indicates an internal consistency error.
// (this test should probably be an assertion)
        hresult = E_FAIL;
        goto LError1;
      }

      V_VT(&var) = VT_DISPATCH;
      hresult = ppointlink->ppoint->QueryInterface(
IID_IDispatch, (void FAR* FAR*)&V_DISPATCH(&var));
      if(hresult != NOERROR)
        goto LError1;

      ix[0] = i;
      SafeArrayPutElement(psa, ix, &var);

      // SafeArrayPutElement adds a reference to the contents of the
      // variant, so we must free the variable we have.
      //
      VariantClear(&var);

      ppointlink = ppointlink->next;
    }

    hresult = CEnumPoint::Create(psa, &penum);
    if(hresult != NOERROR)
      goto LError1;

    // ownership of the array has passed to the enumerator
    psa = NULL;

    hresult = penum->QueryInterface(IID_IUnknown, (void FAR* FAR*)&punk);
    if(hresult != NOERROR)
      goto LError2;

    penum->Release();

    return punk;

LError2:;
    penum->Release();

LError1:;
    // destroy the array if we were not successful creating the enumerator.
    if(psa != NULL)
      SafeArrayDestroy(psa);

LError0:;
    return NULL;
}

short METHODCALLTYPE EXPORT
CPoly::GetXOrigin()
{
    return m_xorg;
}

void METHODCALLTYPE EXPORT
CPoly::SetXOrigin(short x)
{
    m_xorg = x;
}

short METHODCALLTYPE EXPORT
CPoly::GetYOrigin()
{
    return m_yorg;
}

void METHODCALLTYPE EXPORT
CPoly::SetYOrigin(short y)
{
    m_yorg = y;
}

short METHODCALLTYPE EXPORT
CPoly::GetWidth()
{
    return m_width;
}

void METHODCALLTYPE EXPORT
CPoly::SetWidth(short width)
{
    m_width = width;
}

short METHODCALLTYPE EXPORT
CPoly::get_red()
{
    return m_red;
}

void METHODCALLTYPE EXPORT
CPoly::set_red(short red)
{
    m_red = red;
}

short METHODCALLTYPE EXPORT
CPoly::get_green()
{
    return m_green;
}

void METHODCALLTYPE EXPORT
CPoly::set_green(short green)
{
    m_green = green;
}

short METHODCALLTYPE EXPORT
CPoly::get_blue()
{
    return m_blue;
}

void METHODCALLTYPE EXPORT
CPoly::set_blue(short blue)
{
    m_blue = blue;
}

/***
*void CPoly::Dump(void)
*Purpose:
*  Output a debug dump of this instance.
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
void METHODCALLTYPE EXPORT
CPoly::Dump()
{
#ifdef _MAC

    // REVIEW: implement for the mac

#else

    TCHAR buffer[80];
    POINTLINK FAR* ppointlink;

    wsprintf(buffer, TSTR("CPoly(0x%x) =\n"), (int)this);
    OutputDebugString(buffer);

    wsprintf(buffer,
      TSTR("    xorg = %d, yorg = %d, width = %d, rgb = {%d,%d,%d}\n    points = "),
      m_xorg, m_yorg, m_width,
      get_red(),
      get_green(),
      get_blue());

    OutputDebugString(buffer);

    for(ppointlink = m_ppointlink;
ppointlink != (POINTLINK FAR*)NULL;
ppointlink = ppointlink->next)
    {
      wsprintf(buffer, TSTR("{%d,%d}"),
        ppointlink->ppoint->GetX(),
        ppointlink->ppoint->GetY());
      OutputDebugString(buffer);

      wsprintf(buffer, TSTR(" "));
      OutputDebugString(buffer);
    }
    wsprintf(buffer, TSTR("\n"));
    OutputDebugString(buffer);

#endif
}

/***
*void CPoly::Quit(void)
*Purpose:
*  Mark this instance as quitting.
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
void METHODCALLTYPE EXPORT
CPoly::Quit()
{
    g_fQuit = 1;
}

/***
*void CPoly::PolyDraw(void)
*Purpose:
*  Draw all polygons.
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
void
CPoly::PolyDraw()
{
    POLYLINK FAR* polylink;

    for(polylink = g_ppolylink;
polylink != (POLYLINK FAR*)NULL;
polylink = polylink->next)
    {
      polylink->ppoly->Draw();
    }
}

/***
*void PolyTerm(void)
*Purpose:
*  Release all polygons.
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
void
CPoly::PolyTerm()
{
    POLYLINK FAR* ppolylink;
    POLYLINK FAR* ppolylinkNext;

    for(ppolylink = g_ppolylink;
ppolylink != (POLYLINK FAR*)NULL;
ppolylink = ppolylinkNext)
    {
      ppolylinkNext = ppolylink->next;
      ppolylink->ppoly->Release();
      delete ppolylink;
    }
    g_ppolylink = NULL;
}

/***
*void PolyDump(void)
*Purpose:
*  Invoke the debug Dump() method on all polygons were currently
*  holding on to.
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
void
CPoly::PolyDump()
{
    POLYLINK FAR* ppolylink;

    if(g_ppolylink == (POLYLINK FAR*)NULL){
#ifndef _MAC
      OutputDebugString(TSTR("\t(none)\n"));
#endif
      return;
    }

    for(ppolylink = g_ppolylink;
ppolylink != (POLYLINK FAR*)NULL;
ppolylink = ppolylink->next)
    {
      ppolylink->ppoly->Dump();
    }
}

//---------------------------------------------------------------------
//             Implementation of the CPoly Class Factory
//---------------------------------------------------------------------

CPolyCF::CPolyCF()
{
    m_refs = 0;
}

IClassFactory FAR*
CPolyCF::Create()
{
    CPolyCF FAR* pCF;

    if((pCF = new FAR CPolyCF()) == NULL)
      return NULL;
    pCF->AddRef();
    return pCF;
}

STDMETHODIMP
CPolyCF::QueryInterface(REFIID riid, void FAR* FAR* ppv)
{
    if(IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IClassFactory)){
      *ppv = this;
      ++m_refs;
      return NOERROR;
    }
    *ppv = NULL;
    return E_NOINTERFACE;
}

STDMETHODIMP_(unsigned long)
CPolyCF::AddRef(void)
{
    return ++m_refs;
}

STDMETHODIMP_(unsigned long)
CPolyCF::Release(void)
{
    if(--m_refs == 0){
      delete this;
      return 0;
    }
    return m_refs;
}

STDMETHODIMP
CPolyCF::CreateInstance(
    IUnknown FAR* punkOuter,
    REFIID iid,
    void FAR* FAR* ppv)
{
    HRESULT hresult;
    CPoly FAR *ppoly;

    UNUSED(punkOuter);

    if((ppoly = CPoly::Create()) == NULL){
      *ppv = NULL;
      return E_OUTOFMEMORY;
    }
    hresult = ppoly->QueryInterface(iid, ppv);
    ppoly->Release();
    return hresult;
}

STDMETHODIMP
#ifdef _MAC
CPolyCF::LockServer(unsigned long fLock)
#else
CPolyCF::LockServer(BOOL fLock)
#endif
{
    UNUSED(fLock);

    return NOERROR;
}


CPOLY.H

/***
*cpoly.h
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  Definition of the CPoly class.
*
*  The CPoly class defines a number of methods and exposes them for
*  external programmability via IDispatch,
*
*  methods:
*    DRAW- draw the polygon
*    RESET- delete all points from the polygon
*
*    ADDPOINT(X, Y)- add a point with coordinates (x,y) to the polygon
*
*    ENUMPOINTS- return a collection of the polygon's points
*
*    GETXORIGIN- get and set the X origin of the polygon
*    SETXORIGIN
*
*    GETYORIGIN- get and set the Y origin of the polygon
*    SETYORIGIN
*
*    GETWIDTH- get and set the line width of the polygon
*    SETWIDTH
*
*  UNDONE: update description
*
*Implementation Notes:
*
*****************************************************************************/

#ifndefCLASS
# ifdef__TURBOC__
#  define CLASS class huge
# else
#  define CLASS class FAR
# endif
#endif

class CPoint;

CLASS CPoly : public IDispatch
{
public:
    static CPoly FAR* Create();

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppvObj);
    STDMETHOD_(unsigned long, AddRef)(void);
    STDMETHOD_(unsigned long, Release)(void);

    /* IDispatch methods */
    STDMETHOD(GetTypeInfoCount)(unsigned int FAR* pcTypeInfo);

    STDMETHOD(GetTypeInfo)(
      unsigned int iTypeInfo,
      LCID lcid,
      ITypeInfo FAR* FAR* ppTypeInfo);

    STDMETHOD(GetIDsOfNames)(
      REFIID riid,
      OLECHAR FAR* FAR* rgszNames,
      unsigned int cNames,
      LCID lcid,
      DISPID FAR* rgdispid);

    STDMETHOD(Invoke)(
      DISPID dispidMember,
      REFIID riid,
      LCID lcid,
      unsigned short wFlags,
      DISPPARAMS FAR* pdispparams,
      VARIANT FAR* pvarResult,
      EXCEPINFO FAR* pexcepinfo,
      unsigned int FAR* puArgErr);

    /* Introduced methods */

    virtual void  METHODCALLTYPE EXPORT Draw(void);
    virtual void  METHODCALLTYPE EXPORT Reset(void);

    // add a point with the given 'x' and 'y' coordinates
    virtual HRESULT METHODCALLTYPE EXPORT AddPoint(short x, short y);

    // return a collection of the polygon's points
    virtual IUnknown FAR* METHODCALLTYPE EXPORT EnumPoints(void);

    // get/set the polygon's X origin property
    virtual short METHODCALLTYPE EXPORT GetXOrigin(void);
    virtual void  METHODCALLTYPE EXPORT SetXOrigin(short x);

    // get/set the polygon's Y origin property
    virtual short METHODCALLTYPE EXPORT GetYOrigin(void);
    virtual void  METHODCALLTYPE EXPORT SetYOrigin(short y);

    virtual short METHODCALLTYPE EXPORT GetWidth(void);
    virtual void  METHODCALLTYPE EXPORT SetWidth(short width);

    virtual short METHODCALLTYPE EXPORT get_red(void);
    virtual void  METHODCALLTYPE EXPORT set_red(short red);

    virtual short METHODCALLTYPE EXPORT get_green(void);
    virtual void  METHODCALLTYPE EXPORT set_green(short green);

    virtual short METHODCALLTYPE EXPORT get_blue(void);
    virtual void  METHODCALLTYPE EXPORT set_blue(short blue);

    // Debug method
    virtual void  METHODCALLTYPE EXPORT Dump(void);
    virtual void  METHODCALLTYPE EXPORT Quit(void);

public:

    // Draw all polygons.
    static void PolyDraw(void);

    // Release all polygons.
    static void PolyTerm(void);

    // Dump all polygons to dbwin.
    static void PolyDump(void);

private:
    CPoly();

    short m_xorg;
    short m_yorg;
    short m_width;

    short m_red;
    short m_green;
    short m_blue;

    unsigned long m_refs;
    unsigned int m_cPoints;

    ITypeInfo FAR* m_ptinfo;

    POINTLINK FAR* m_ppointlink;
    POINTLINK FAR* m_ppointlinkLast;
};

// DISPIDs for the members and properties available via IDispatch.
//
enum IDMEMBER_CPOLY {
    IDMEMBER_CPOLY_DRAW = 1,
    IDMEMBER_CPOLY_RESET,
    IDMEMBER_CPOLY_ADDPOINT,
    IDMEMBER_CPOLY_ENUMPOINTS,
    IDMEMBER_CPOLY_GETXORIGIN,
    IDMEMBER_CPOLY_SETXORIGIN,
    IDMEMBER_CPOLY_GETYORIGIN,
    IDMEMBER_CPOLY_SETYORIGIN,
    IDMEMBER_CPOLY_GETWIDTH,
    IDMEMBER_CPOLY_SETWIDTH,
    IDMEMBER_CPOLY_GETRED,
    IDMEMBER_CPOLY_SETRED,
    IDMEMBER_CPOLY_GETGREEN,
    IDMEMBER_CPOLY_SETGREEN,
    IDMEMBER_CPOLY_GETBLUE,
    IDMEMBER_CPOLY_SETBLUE,
    IDMEMBER_CPOLY_DUMP,
    IDMEMBER_CPOLY_QUIT,
    IDMEMBER_CPOLY_MAX
};

// CPoly method indices
//
enum IMETH_CPOLY {
    IMETH_CPOLY_QUERYINTERFACE = 0,
    IMETH_CPOLY_ADDREF,
    IMETH_CPOLY_RELEASE,
    IMETH_CPOLY_GETTYPEINFOCOUNT,
    IMETH_CPOLY_GETTYPEINFO,
    IMETH_CPOLY_GETIDSOFNAMES,
    IMETH_CPOLY_INVOKE,
    IMETH_CPOLY_DRAW,
    IMETH_CPOLY_RESET,
    IMETH_CPOLY_ADDPOINT,
    IMETH_CPOLY_ENUMPOINTS,
    IMETH_CPOLY_GETXORIGIN,
    IMETH_CPOLY_SETXORIGIN,
    IMETH_CPOLY_GETYORIGIN,
    IMETH_CPOLY_SETYORIGIN,
    IMETH_CPOLY_GETWIDTH,
    IMETH_CPOLY_SETWIDTH,
    IMETH_CPOLY_GETRED,
    IMETH_CPOLY_SETRED,
    IMETH_CPOLY_GETGREEN,
    IMETH_CPOLY_SETGREEN,
    IMETH_CPOLY_GETBLUE,
    IMETH_CPOLY_SETBLUE,
    IMETH_CPOLY_DUMP,
    IMETH_CPOLY_QUIT,
    IMETH_CPOLY_MAX
};

// structure used to link together polygons
//
struct POLYLINK {
    POLYLINK FAR* next;
    CPoly FAR* ppoly;
};

// The CPoly class factory
//
CLASS CPolyCF : public IClassFactory
{
public:
    static IClassFactory FAR* Create();

    /* IUnknown methods */
    STDMETHOD(QueryInterface)(REFIID riid, void FAR* FAR* ppv);
    STDMETHOD_(unsigned long, AddRef)(void);
    STDMETHOD_(unsigned long, Release)(void);

    /* IClassFactory methods */
    STDMETHOD(CreateInstance)(
      IUnknown FAR* pUnkOuter, REFIID riid, void FAR* FAR* ppv);
#ifdef _MAC
    STDMETHOD(LockServer)(unsigned long fLock);
#else
    STDMETHOD(LockServer)(BOOL fLock);
#endif

private:
    CPolyCF();

    unsigned long m_refs;
};


MISC.CPP

/***
*misc.cpp
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*
*Implementation Notes:
*
*****************************************************************************/

#include "spoly.h"
#include "cpoint.h"
#include "cpoly.h"

#include <stdio.h>

#ifdef _MAC
# include <string.h>
# include <ctype.h>
#endif

unsigned long g_dwPolyCF = 0;
unsigned long g_dwPointCF = 0;

IClassFactory FAR* g_ppolyCF = NULL;
IClassFactory FAR* g_ppointCF = NULL;

unsigned int g_fObjectsRevoked = 0;
unsigned int g_cObjects = 0;
unsigned int g_fQuit = 0;

#ifdef _MAC
struct regentry{
    char *szKey;
    char *szValue;
} g_rgregentry[] = {

      { "CLSID\\{00020464-0000-0000-C000-000000000046}",
"OLE Automation SPoly2 1.0 Application" }

    , { "CLSID\\{00020464-0000-0000-C000-000000000046}\\LocalServer",
        "SPL2" }

    , { "CLSID\\{00020464-0000-0000-C000-000000000046}\\ProgID",
"SPoly2.Application" }

    , { "CLSID\\{00020464-0000-0000-C000-000000000046}\\InprocHandler",
"OLE2:Def$DefFSet" }

    , { "SPL2", "{00020464-0000-0000-C000-000000000046}" }

    , { "SPoly2.Application\\CLSID",
"{00020464-0000-0000-C000-000000000046}" }

};

HRESULT
EnsureRegistration()
{
    HKEY hkey;

    if(RegOpenKey(HKEY_CLASSES_ROOT, "SPL2", &hkey) == NOERROR){
      RegCloseKey(hkey);
      return NOERROR;
    }

    for(int i = 0; i < DIM(g_rgregentry); ++i){
      if(RegSetValue(HKEY_CLASSES_ROOT, g_rgregentry[i].szKey, REG_SZ, g_rgregentry[i].szValue, 0) != ERROR_SUCCESS)
        return E_FAIL;
    }

    return NOERROR;
}
#endif

/***
*HRESULT InitOle(void)
*Purpose:
*  Initialize Ole, and register our class factories.
*
*Entry:
*  None
*
*Exit:
*  None
*
***********************************************************************/
STDAPI
InitOle()
{
    HRESULT hresult;

    if((hresult = OleInitialize(NULL)) != NOERROR)
      goto LError0;

#ifdef _MAC
    if((hresult = EnsureRegistration()) != NOERROR)
      goto LError0;
#endif

    // Register the CPoint Class Factory
    //
    if((g_ppointCF = CPointCF::Create()) == NULL){
      hresult = E_OUTOFMEMORY;
      goto LError1;
    }

    hresult = CoRegisterClassObject(
      CLSID_CPoint2,
      g_ppointCF,
      CLSCTX_LOCAL_SERVER,
      REGCLS_MULTIPLEUSE,
      &g_dwPointCF);
    if(FAILED(hresult))
      goto LError1;

    // Register the CPoly Class Factory.
    //
    if((g_ppolyCF = CPolyCF::Create()) == NULL){
      hresult = E_OUTOFMEMORY;
      goto LError1;
    }

    hresult = CoRegisterClassObject(
      CLSID_CPoly2,
      g_ppolyCF,
      CLSCTX_LOCAL_SERVER,
      REGCLS_MULTIPLEUSE,
      &g_dwPolyCF);
    if(FAILED(hresult))
      goto LError1;

    g_ppolyCF->Release();

    g_ppointCF->Release();

    return NOERROR;

LError1:;
    if(g_ppolyCF != NULL)
      g_ppolyCF->Release();

    if(g_ppointCF != NULL)
      g_ppointCF->Release();

    UninitOle();

LError0:;
    return hresult;
}

STDAPI
Revoke()
{
    if (!g_fObjectsRevoked) {
      // Tell Ole to release our class factories.
      //
      if(g_dwPointCF != 0L)
        CoRevokeClassObject(g_dwPointCF);

      if(g_dwPolyCF != 0L)
        CoRevokeClassObject(g_dwPolyCF);

      g_fObjectsRevoked = 1;
   }

   return NOERROR;
}

STDAPI
UninitOle()
{
    Revoke();
    OleUninitialize();

    return NOERROR;
}

// disable unicode expansion for assertions
#undef UNICODE

void
Assert(int fCond, char FAR* file, int line, char FAR* message)
{
    char * fmt;
    char buf[128];

    if(fCond)
      return;

    fmt = (message == NULL)
      ? "Assertion failed: %s(%d)"
      : "Assertion failed: %s(%d) '%s'";
    sprintf(buf, fmt, file, line, message);

#ifdef _MAC
    DebugStr(c2pstr(buf));
#else
#ifdef WIN32
    OutputDebugStringA(buf);
#else //WIN32
    OutputDebugString(buf);
#endif //WIN32
    DebugBreak();
#endif
}

void IncObjectCount()
{
    g_cObjects++;
}

void DecObjectCount()
{
    g_cObjects--;

    if (!g_cObjects && g_fQuit) {
      Revoke();
#ifndef _MAC
      PostQuitMessage(0);
#endif // !_MAC
    }
}

#ifdef _MAC
#if defined(_MSC_VER)
int pascal
#else
pascal int
#endif
stricmp(char *first, char *last)
{
    unsigned short f, l;

    do{
f = tolower(*first++);
l = tolower(*last++);
    }while(f && f == l);

    return f - l;
}
#endif


CENUMPT.CPP

/***
*cenumpt.cpp
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  This module implements the CEnumPoint class.
*
*
*Implementation Notes:
*
*****************************************************************************/

#include "hostenv.h"
#include "cenumpt.h"

CEnumPoint::CEnumPoint()
{
    m_refs = 0;

    m_psa = NULL;
    m_celts = 0;
    m_iCurrent = 0;
}

/***
*HRESULT CEnumPoint::Create(SAFEARRAY*, CEnumPoint**)
*Purpose:
*  This routine creates a CPoint enumerator from the given
*  (1 X N) SafeArray of CPoint IDispatch pointers.
*
*Entry:
*  psa = pointer to a SafeArray of VARIANTs
*
*Exit:
*  return value = HRESULT
*
*  *ppenum = pointer to a CPoint enumerator
*
***********************************************************************/
HRESULT
CEnumPoint::Create(SAFEARRAY FAR* psa, CEnumPoint FAR* FAR* ppenum)
{
    long lBound;
    HRESULT hresult;
    CEnumPoint FAR* penum;

    // Verify that the SafeArray is the proper shape.
    //
    if(SafeArrayGetDim(psa) != 1)
      return E_INVALIDARG;

    hresult = SafeArrayGetLBound(psa, 1, &lBound);
    if(FAILED(hresult))
      return hresult;

    if(lBound != 0)
      return E_INVALIDARG;

    penum = new FAR CEnumPoint();
    if(penum == NULL)
      return E_OUTOFMEMORY;
    penum->AddRef();

    hresult = SafeArrayGetUBound(psa, 1, &lBound);
    if(FAILED(hresult))
      goto LError0;

    penum->m_psa = psa;
    penum->m_celts = lBound + 1;

    *ppenum = penum;

    return NOERROR;

LError0:;
    penum->Release();

    return hresult;
}

//---------------------------------------------------------------------
//                        IUnknown methods
//---------------------------------------------------------------------

STDMETHODIMP
CEnumPoint::QueryInterface(REFIID riid, void FAR* FAR* ppv)
{
    if(IsEqualIID(riid, IID_IEnumVARIANT) || IsEqualIID(riid, IID_IUnknown)){
      *ppv = this;
      AddRef();
      return NOERROR;
    }
    *ppv = NULL;
    return E_NOINTERFACE;
}

STDMETHODIMP_(unsigned long)
CEnumPoint::AddRef()
{
    return ++m_refs;
}

STDMETHODIMP_(unsigned long)
CEnumPoint::Release()
{
    if(--m_refs == 0){
      if(m_psa != NULL)
SafeArrayDestroy(m_psa);
      delete this;
      return 0;
    }
    return m_refs;
}

//---------------------------------------------------------------------
//                        IEnumVARIANT methods
//---------------------------------------------------------------------

/***
*HRESULT CEnumPoint::Next(unsigned long, VARIANT*, unsigned long*)
*Purpose:
*  Attempt to get the next 'celt' items in the enumeration sequence.
*
*Entry:
*  celt = the number of elements to get
*
*Exit:
*  return value = HRESULT
*    S_OK
*    S_FALSE - the end of the sequence was reached
*
*  rgvar = array of the next 'celt' items
*  *pceltFetched = count of the elements actually fetched.
*
***********************************************************************/
STDMETHODIMP
CEnumPoint::Next(
    unsigned long celt,
    VARIANT FAR* rgvar,
    unsigned long FAR* pceltFetched)
{
    long ix;
    unsigned int i;
    HRESULT hresult;

    for(i = 0; i < celt; ++i)
      VariantInit(&rgvar[i]);

    for(i = 0; i < celt; ++i){
      if(m_iCurrent == m_celts){
        hresult = S_FALSE;
goto LDone;
      }

      ix = m_iCurrent++;
      hresult = SafeArrayGetElement(m_psa, &ix, &rgvar[i]);
      if(FAILED(hresult))
goto LError0;
    }

    hresult = NOERROR;

LDone:;
    if (pceltFetched != NULL)
      *pceltFetched = i;

    return hresult;

LError0:;

    for(i = 0; i < celt; ++i)
      VariantClear(&rgvar[i]);

    return hresult;
}

/***
*HRESULT CEnumPoint::Skip(unsigned long)
*Purpose:
*  Attempt to skip over the next 'celt' elements in the enumeration
*  sequence.
*
*Entry:
*  celt = the count of elements to skip
*
*Exit:
*  return value = HRESULT
*    S_OK
*    S_FALSE -  the end of the sequence was reached
*
***********************************************************************/
STDMETHODIMP
CEnumPoint::Skip(unsigned long celt)
{
    m_iCurrent += celt;

    if(m_iCurrent > m_celts)
     m_iCurrent = m_celts;

    return (m_iCurrent == m_celts)
      ? S_FALSE : NOERROR;
}

/***
*HRESULT CEnumPoint::Reset(void)
*Purpose:
*  Reset the enumeration sequence back to the beginning.
*
*Entry:
*  None
*
*Exit:
*  return value = SHRESULT CODE
*    S_OK
*
***********************************************************************/
STDMETHODIMP
CEnumPoint::Reset()
{
    m_iCurrent = 0;

    return NOERROR;
}

/***
*HRESULT CEnumPoint::Clone(IEnumVARIANT**)
*Purpose:
*  Retrun a CPoint enumerator with exactly the same state as the
*  current one.
*
*Entry:
*  None
*
*Exit:
*  return value = HRESULT
*    S_OK
*    E_OUTOFMEMORY
*
***********************************************************************/
STDMETHODIMP
CEnumPoint::Clone(IEnumVARIANT FAR* FAR* ppenum)
{
    HRESULT hresult;
    SAFEARRAY FAR* psa;
    CEnumPoint FAR* penum;

    hresult = SafeArrayCopy(m_psa, &psa);
    if(FAILED(hresult))
      return hresult;

    hresult = CEnumPoint::Create(psa, &penum);
    if(FAILED(hresult))
      goto LError0;

    // Assert(penum->m_celts == m_celts);

    penum->m_iCurrent = m_iCurrent;

    *ppenum = penum;

    return NOERROR;

LError0:
    SafeArrayDestroy(psa);

    return hresult;
}


RESOURCE.H

/***
*resource.h
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*
*Implementation Notes:
*
*****************************************************************************/

#ifdef _MAC

#define kMinSize500/* minimum size (in K) */
#define kPrefSize500/* preferred size (in K) */

#definerMenuBar128/* menu bar */
#definerAboutAlert128/* about alert */
#definerUserAlert129/* error alert */
#definerWindow128/* application's window */

#definemApple128/* Apple menu */
#defineiAbout1

#definemFile129/* File menu */
#defineiNew1
#defineiClose4
#defineiQuit12

#definemEdit130/* Edit menu */
#defineiUndo1
#defineiCut3
#defineiCopy4
#defineiPaste5
#defineiClear6

#define mSpoly131
#define iTest1

#define kMinHeap21 * 1024
#define kMinSpace8 * 1024

#else /* WIN16 || WIN32 */

# define IDM_CLEAR1
# define IDM_DUMP2
# define IDM_FIRSTCHILD100

#endif


SPOLY.H

/***
*spoly.h - Application-wide definitions
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*
*Implementation Notes:
*
*****************************************************************************/

#include "hostenv.h"
#include "resource.h"
#include "clsid.h"

#if defined(_MAC)
# define STRSTR strstr
#elif defined(WIN32)
# include "statbar.h"
# define STRSTR strstr
#else /* WIN16 */
# include "statbar.h"
# define STRSTR _fstrstr
#endif

#ifdef _MAC
# define UNUSED(X) ((void)(void*)&(X))
#else
# define UNUSED(X) (X)
#endif

#define DIM(X) (sizeof(X) / sizeof(X[0]))

extern "C" void Assert(int, char FAR*, int, char FAR*);
#define ASSERT(X) Assert(X, __FILE__, __LINE__, NULL)
#define ASSERTSZ(X, MSG) Assert(X, __FILE__, __LINE__, MSG)

#ifndef EXPORT
# if defined(WIN32)
#  define EXPORT
# elif defined(_MAC)
#  define EXPORT
# else
#  define EXPORT __export
# endif
#endif

#ifndef NEAR
# if defined(WIN32)
#  define NEAR
# elif defined(_MAC)
#  define NEAR
# else
#  define NEAR __near
# endif
#endif

#if defined(WIN32)
# define CC_CALL        CC_STDCALL
# define METHODCALLTYPE __stdcall
#elif defined(_MAC)
# define CC_CALL        CC_CDECL
# define METHODCALLTYPE
#else
# define CC_CALL        CC_PASCAL
# define METHODCALLTYPE __pascal
#endif

STDAPI InitOle();
STDAPI UninitOle();
STDAPI Revoke();

void IncObjectCount();
void DecObjectCount();


TDATA.CPP

/***
*tdata.cpp
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1991-1998 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.
*
*Purpose:
*  CPoly and CPoint type data descriptions.
*
*  These data descriptions are used to construct TypeInfos for these
*  objects at runtime.
*
*****************************************************************************/

#include "spoly.h"
#include "cpoint.h"
#include "cpoly.h"

//---------------------------------------------------------------------
//                      CPoint type data definitions
//---------------------------------------------------------------------

static PARAMDATA NEAR rgpdataCPointSetX[] =
{
    { OLESTR("X"), VT_I2 }
};

static PARAMDATA NEAR rgpdataCPointSetY[] =
{
    { OLESTR("Y"), VT_I2 }
};

static METHODDATA NEAR rgmdataCPoint[] =
{
    // CPoint::GetX()
    {
OLESTR("GetX"),
NULL,
IDMEMBER_CPOINT_GETX,
IMETH_CPOINT_GETX,
CC_CALL,
0,
DISPATCH_METHOD,
VT_I2
    },

    // CPoint::SetX()
    {
OLESTR("SetX"),
rgpdataCPointSetX,
IDMEMBER_CPOINT_SETX,
IMETH_CPOINT_SETX,
CC_CALL,
DIM(rgpdataCPointSetX),
DISPATCH_METHOD,
VT_EMPTY
    },

    // CPoint::GetY()
    {
OLESTR("GetY"),
NULL,
IDMEMBER_CPOINT_GETY,
IMETH_CPOINT_GETY,
CC_CALL,
0,
DISPATCH_METHOD,
VT_I2
    },

    // CPoint::SetY()
    {
OLESTR("SetY"),
rgpdataCPointSetY,
IDMEMBER_CPOINT_SETY,
IMETH_CPOINT_SETY,
CC_CALL,
DIM(rgpdataCPointSetY),
DISPATCH_METHOD,
VT_EMPTY
    }
};

INTERFACEDATA NEAR g_idataCPoint =
{
    rgmdataCPoint, DIM(rgmdataCPoint)
};

//---------------------------------------------------------------------
//                   CPoly type data definitions
//---------------------------------------------------------------------

static PARAMDATA NEAR rgpdataCPolyAddPoint[] =
{
    { OLESTR("x"),VT_I2 },
    { OLESTR("y"),VT_I2 }
};

static PARAMDATA NEAR rgpdataCPolySetXOrigin[] =
{
    { OLESTR("x"),VT_I2 }
};

static PARAMDATA NEAR rgpdataCPolySetYOrigin[] =
{
    { OLESTR("y"),VT_I2 }
};

static PARAMDATA NEAR rgpdataCPolySetWidth[] =
{
    { OLESTR("width"),VT_I2 }
};

static PARAMDATA NEAR rgpdataCPolySetRed[] =
{
    { OLESTR("red"),VT_I2 }
};

static PARAMDATA NEAR rgpdataCPolySetGreen[] =
{
    { OLESTR("green"),VT_I2 }
};

static PARAMDATA NEAR rgpdataCPolySetBlue[] =
{
    { OLESTR("blue"),VT_I2 }
};

static METHODDATA NEAR rgmdataCPoly[] =
{
    // void CPoly::Draw(void)
    {
OLESTR("Draw"),
NULL,
IDMEMBER_CPOLY_DRAW,
IMETH_CPOLY_DRAW,
CC_CALL,
0,
DISPATCH_METHOD,
VT_EMPTY
    },

    // void CPoly::Reset(void)
    {
OLESTR("Reset"),
NULL,
IDMEMBER_CPOLY_RESET,
IMETH_CPOLY_RESET,
CC_CALL,
0,
DISPATCH_METHOD,
VT_EMPTY
    },

    // HRESULT CPoly::AddPoint(short x, short y)
    {
OLESTR("AddPoint"),
rgpdataCPolyAddPoint,
IDMEMBER_CPOLY_ADDPOINT,
IMETH_CPOLY_ADDPOINT,
CC_CALL,
DIM(rgpdataCPolyAddPoint),
DISPATCH_METHOD,
VT_ERROR
    },

    // IUnknown FAR* CPoly::EnumPoints(void)
    {
OLESTR("EnumPoints"),
NULL,
IDMEMBER_CPOLY_ENUMPOINTS,
IMETH_CPOLY_ENUMPOINTS,
CC_CALL,
0,
DISPATCH_METHOD,
VT_UNKNOWN
    },

    // short CPoly::GetXOrigin(void)
    {
OLESTR("GetXOrigin"),
NULL,
IDMEMBER_CPOLY_GETXORIGIN,
IMETH_CPOLY_GETXORIGIN,
CC_CALL,
0,
DISPATCH_METHOD,
VT_I2
    },

    // void CPoly::SetXOrigin(short x)
    {
OLESTR("SetXOrigin"),
rgpdataCPolySetXOrigin,
IDMEMBER_CPOLY_SETXORIGIN,
IMETH_CPOLY_SETXORIGIN,
CC_CALL,
DIM(rgpdataCPolySetXOrigin),
DISPATCH_METHOD,
VT_EMPTY
    },

    // short CPoly::GetYOrigin(void)
    {
OLESTR("GetYOrigin"),
NULL,
IDMEMBER_CPOLY_GETYORIGIN,
IMETH_CPOLY_GETYORIGIN,
CC_CALL,
0,
DISPATCH_METHOD,
VT_I2
    },

    // void CPoly::SetYOrigin(short y)
    {
OLESTR("SetYOrigin"),
rgpdataCPolySetYOrigin,
IDMEMBER_CPOLY_SETYORIGIN,
IMETH_CPOLY_SETYORIGIN,
CC_CALL,
DIM(rgpdataCPolySetYOrigin),
DISPATCH_METHOD,
VT_EMPTY
    },

    // short CPoly::GetWidth(void)
    {
OLESTR("GetWidth"),
NULL,
IDMEMBER_CPOLY_GETWIDTH,
IMETH_CPOLY_GETWIDTH,
CC_CALL,
0,
DISPATCH_METHOD,
VT_I2
    },

    // void CPoly::SetWidth(short width)
    {
OLESTR("SetWidth"),
rgpdataCPolySetWidth,
IDMEMBER_CPOLY_SETWIDTH,
IMETH_CPOLY_SETWIDTH,
CC_CALL,
DIM(rgpdataCPolySetWidth),
DISPATCH_METHOD,
VT_EMPTY
    },

    // short CPoly::get_red(void)
    {
OLESTR("get_red"),
NULL,
IDMEMBER_CPOLY_GETRED,
IMETH_CPOLY_GETRED,
CC_CALL,
0,
DISPATCH_METHOD,
VT_I2
    },

    // void CPoly::set_red(short red)
    {
OLESTR("set_red"),
rgpdataCPolySetRed,
IDMEMBER_CPOLY_SETRED,
IMETH_CPOLY_SETRED,
CC_CALL,
DIM(rgpdataCPolySetRed),
DISPATCH_METHOD,
VT_EMPTY
    },

    // short CPoly::get_green(void)
    {
OLESTR("get_green"),
NULL,
IDMEMBER_CPOLY_GETGREEN,
IMETH_CPOLY_GETGREEN,
CC_CALL,
0,
DISPATCH_METHOD,
VT_I2
    },

    // void CPoly::set_green(short green)
    {
OLESTR("set_green"),
rgpdataCPolySetGreen,
IDMEMBER_CPOLY_SETGREEN,
IMETH_CPOLY_SETGREEN,
CC_CALL,
DIM(rgpdataCPolySetGreen),
DISPATCH_METHOD,
VT_EMPTY
    },

    // short CPoly::get_blue(void)
    {
OLESTR("get_blue"),
NULL,
IDMEMBER_CPOLY_GETBLUE,
IMETH_CPOLY_GETBLUE,
CC_CALL,
0,
DISPATCH_METHOD,
VT_I2
    },

    // void CPoly::set_blue(short blue)
    {
OLESTR("set_blue"),
rgpdataCPolySetBlue,
IDMEMBER_CPOLY_SETBLUE,
IMETH_CPOLY_SETBLUE,
CC_CALL,
DIM(rgpdataCPolySetBlue),
DISPATCH_METHOD,
VT_EMPTY
    },

    // void CPoly::Dump(void)
    {
OLESTR("Dump"),
NULL,
IDMEMBER_CPOLY_DUMP,
IMETH_CPOLY_DUMP,
CC_CALL,
0,
DISPATCH_METHOD,
VT_EMPTY
    },

    // void CPoly::Quit(void)
    {
OLESTR("Quit"),
NULL,
IDMEMBER_CPOLY_QUIT,
IMETH_CPOLY_QUIT,
CC_CALL,
0,
DISPATCH_METHOD,
VT_EMPTY
    },

};

INTERFACEDATA NEAR g_idataCPoly =
{
    rgmdataCPoly, DIM(rgmdataCPoly)
};


WINMAIN.CPP

/***
*winmain.cpp
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  This module is the main entry point for the sample IDispatch polygon
*  server, spoly2.exe.
*
*  This program is intended to demonstrate an implementation of the IDispatch
*  interface. Spoly2 is a very simple app, that implements two simple objects,
*  CPoly and CPoint and exposes their properties and methods for programatic
*  and cross-process access via IDispatch.
*
*Implementation Notes:
*
*****************************************************************************/

#include <stdio.h>
#include <string.h>

#include "spoly.h"
#include "cpoint.h"
#include "cpoly.h"

HINSTANCE g_hinst = 0;

HWND g_hwndFrame = 0;
HWND g_hwndClient = 0;

TCHAR g_szFrameWndClass[] = TSTR("FrameWClass");

CStatBar FAR* g_psb = NULL;

BOOL InitApplication(HINSTANCE);
BOOL InitInstance(HINSTANCE, int);

extern "C" int PASCAL WinMain(HINSTANCE, HINSTANCE, LPSTR, int);
extern "C" long FAR PASCAL FrameWndProc(HWND, UINT, WPARAM, LPARAM);

extern "C" int PASCAL
WinMain(
    HINSTANCE hinst,
    HINSTANCE hPrevInstance,
    LPSTR lpCmdLine,
    int nCmdShow)
{
    MSG msg;
    int retval;
    HRESULT hresult;

    if(!hPrevInstance)
      if(!InitApplication(hinst))
return FALSE;

    if((hresult = InitOle()) != NOERROR)
      return FALSE;

    if(!InitInstance(hinst, nCmdShow)){
      retval = FALSE;
      goto LExit;
    }

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

    g_psb->Release();
    CPoly::PolyTerm();

    retval = msg.wParam;

LExit:;
    UninitOle();

    return retval;
}

BOOL
InitApplication(HINSTANCE hinst)
{
    WNDCLASS  wc;

    wc.style= CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc= FrameWndProc;
    wc.cbClsExtra= 0;
    wc.cbWndExtra= 0;
    wc.hInstance= hinst;
    wc.hIcon= LoadIcon(hinst, TSTR("SPOLY"));
    wc.hCursor= LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground= (HBRUSH) (COLOR_APPWORKSPACE+1);
    wc.lpszMenuName= TSTR("SPolyMenu");
    wc.lpszClassName= g_szFrameWndClass;

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

    return TRUE;
}

#ifdef WIN32
#define szAppTitle TSTR("IDispatch Polygon Server #2 (32-bit)")
#else //WIN32
#define szAppTitle TSTR("IDispatch Polygon Server #2")
#endif //WIN32

BOOL
InitInstance(HINSTANCE hinst, int nCmdShow)
{
    g_hinst = hinst;

    // Create a main frame window
    //
    g_hwndFrame = CreateWindow(
      g_szFrameWndClass,
      szAppTitle,
      WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      CW_USEDEFAULT,
      NULL,
      NULL,
      hinst,
      NULL);
    if(!g_hwndFrame)
      return FALSE;

    g_hwndClient = GetWindow(g_hwndFrame, GW_CHILD);
    if(!g_hwndClient)
      return FALSE;

    // create the status bar
    //
    g_psb = CStatBar::Create(g_hinst, g_hwndFrame);
    if(!g_psb)
      return FALSE;

    // initialize and show the status bar
    //
    g_psb->SetHeight(GetSystemMetrics(SM_CYCAPTION) - 1);
    g_psb->SetFont((HFONT)GetStockObject(SYSTEM_FONT));
    g_psb->SetText(OLESTR(""));
    g_psb->Show();

    ShowWindow(g_hwndFrame, nCmdShow);

    UpdateWindow(g_hwndFrame);

    return TRUE;
}

void
FrameWndOnCreate(HWND hwnd)
{
    CLIENTCREATESTRUCT ccs;

    ccs.hWindowMenu = NULL;
    ccs.idFirstChild = IDM_FIRSTCHILD;

    g_hwndClient = CreateWindow(
      TSTR("MDICLIENT"),
      0,
      WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE,
      0, 0, 0, 0,
      hwnd,
      (HMENU) 1,
      g_hinst,
      &ccs);
}

void
FrameWndOnSize(HWND hwnd)
{
    RECT rc;
    int height;

    // Get the client rectangle for the frame window
    GetClientRect(hwnd, &rc);

    height = g_psb->GetHeight();

    // adjust the client win to make room for the status bar.
    //
    MoveWindow(
      g_hwndClient,
      rc.left,
      rc.top,
      rc.right - rc.left,
      rc.bottom - rc.top - height,
      TRUE);

    // move the status bar to the bottom of the newly positioned window.
    //
    g_psb->SetX(rc.left);
    g_psb->SetY(rc.bottom - height),
    g_psb->SetWidth(rc.right - rc.left);
    g_psb->Move();
}

extern "C" long FAR PASCAL
FrameWndProc(
    HWND hwnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam)
{
    switch(message){
    case WM_COMMAND:
      switch(wParam){
      case IDM_DUMP:
CPoly::PolyDump();
return 0;

      case IDM_CLEAR:
InvalidateRect(g_hwndClient, NULL, TRUE);
return 0;
      }
      break;

    case WM_CREATE:
      FrameWndOnCreate(hwnd);
      break;

    case WM_SIZE:
      FrameWndOnSize(hwnd);
      return 1;

    case WM_PAINT:
      CPoly::PolyDraw();
      break;

    case WM_CLOSE:
      DestroyWindow(hwnd);
      return 0;

    case WM_DESTROY:
      PostQuitMessage(0);
      return 0;
    }
    return DefFrameProc(hwnd, g_hwndClient, message, wParam, lParam);
}

#if defined(WIN32)

extern "C" OLECHAR FAR*
ConvertStrAtoW(char FAR* strIn, OLECHAR FAR* buf, UINT size)
{
  MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED,
                       strIn, -1, buf, size) ;
  return buf;
}

extern "C" OLECHAR FAR*
WideString(char FAR* strIn)
{
  static OLECHAR buf[256];

  return (ConvertStrAtoW(strIn, buf, 256));
}

extern "C" char FAR*
ConvertStrWtoA(OLECHAR FAR* strIn, char FAR* buf, UINT size)
{
  int badConversion = FALSE;

  WideCharToMultiByte(CP_ACP, NULL,
              strIn, -1,
      buf, size,
      NULL, &badConversion);
  return buf;
}

extern "C" char FAR*
AnsiString(OLECHAR FAR* strIn)
{
  static char buf[256];

  return (ConvertStrWtoA(strIn, buf, 256));
}

#endif


README.TXT

OLE Automation Sample

SUMMARY
=======

The TiBrowse sample is an OLE automation browser. When started, it asks the
user to selected a type library (.tlb) file. The browser then displays the
contents of the file.

MORE INFORMATION
================

This browser is not suitable for use by end users. It is deficient in many
ways. Users should never have to know that uuids exist, thus, they should
not be displayed in a browser. Items that are restricted or that start with
an underscore should not be displayed in a browser. Duplicates for property
get/set pairs should be removed, since users think about properties, not
about get/set pairs.


RESOURCE.H

/***
*resource.h
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*
*Implementation Notes:
*
*****************************************************************************/

#ifdef _MAC
# define IDC_BASE 0

# define kMinSize1000/* minimum size (in K) */
# define kPrefSize1000/* preferred size (in K) */
# define kMinHeap21 * 1024
# define kMinSpace8 * 1024

# define rMenuBar128/* menu bar */
# define rAboutAlert128/* about alert */
# define rUserAlert129/* error alert */
# define rDlg130

# define mApple128/* Apple menu */
# define iAbout1

# define mFile129/* File menu */
# define iOpen2
# define iClose4
# define iQuit12

# define mEdit130/* Edit menu */
# define iUndo1
# define iCut3
# define iCopy4
# define iPaste5
# define iClear6

#else
# define IDC_BASE 1000
#endif

#define IDC_TYPELIST    (IDC_BASE+1)
#define IDC_MEMBERLIST  (IDC_BASE+2)
#define IDC_PARAMLIST   (IDC_BASE+3)
#define IDC_TYPEKIND    (IDC_BASE+5)
#define IDC_VERSION     (IDC_BASE+7)
#define IDC_GUID        (IDC_BASE+9)
#define IDC_HELPSTRING  (IDC_BASE+11)
#define IDC_HELPCONTEXT (IDC_BASE+13)

#define IDC_STATIC      -1


TIBROWSE.CPP

/***
*tibrowse.cpp
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*  Type Information Browser
*
*
*Implementation Notes:
*
*****************************************************************************/

#if defined(_MAC)

#if defined(_PPCMAC)
#pragma data_seg("_FAR_DATA")
#pragma data_seg( )
#define MAXLONG 0x7fffffff
#define EventHandlerProcPtr AEEventHandlerUPP
#else //_PPCMAC
#define  GetMenuItemText(mApple,menuItem,daName)  GetItem(mApple,menuItem,daName)
#endif //_PPCMAC

#endif //_MAC

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "tibrowse.h"

#ifdef WIN32
  #define SPRINTFswprintf
#else
  #define SPRINTFsprintf
#endif

#if defined(UNICODE)
  #define TCHARWCHAR
  #define TSTR(str)L##str
#else
  #define TCHARchar
  #define TSTR(str)str
  #ifndef LPTSTR
    #define LPTSTR LPSTR
  #endif
#endif

#ifdef _MAC
DECLARE_ROUTINE_DESC(UserItemUPP,DrawListbox);
#endif

#ifdef _MAC
DialogPtr g_pdlg = NULL;
ListHandle g_rghlist[IDC_PARAMLIST+1] = {NULL};
#else
HWND g_hwnd;
TCHAR g_szAppName[] = TSTR("TiBrowse");
#endif

ITypeLib  FAR *g_ptlib = NULL;
ITypeInfo FAR *g_ptinfoCur = NULL;
TYPEATTR  FAR *g_ptattrCur = NULL;

OLECHAR * g_rgszTKind[] = {
    OLESTR("Enum"),/* TKIND_ENUM */
    OLESTR("Struct"),/* TKIND_RECORD */
    OLESTR("Module"),/* TKIND_MODULE */
    OLESTR("Interface"),/* TKIND_INTERFACE */
    OLESTR("Dispinterface"),/* TKIND_DISPATCH */
    OLESTR("Coclass"),/* TKIND_COCLASS */
    OLESTR("Typedef"),/* TKIND_ALIAS */
    OLESTR("Union"),/* TKIND_UNION */
};

// Set the text of the control identified by the given control id
void
XSetDlgItemText(int ctlid, OLECHAR FAR* psz)
{
#ifdef _MAC
    Rect rc;
    Handle h;
    short kind;
    char buf[255];

    strcpy(buf, psz);
    GetDItem(g_pdlg, ctlid, &kind, &h, &rc);
    SetIText(h, c2pstr(buf));
#else
#if defined(WIN32) && !defined(UNICODE)
    char bufA[256];
    WideCharToMultiByte(CP_ACP, NULL, psz, -1, bufA, 256, NULL, NULL);
    SendDlgItemMessage(g_hwnd, ctlid, WM_SETTEXT, 0, (LPARAM)&bufA);
#else
    SendDlgItemMessage(g_hwnd, ctlid, WM_SETTEXT, 0, (LPARAM)psz);
#endif
#endif
}

// Clear the listbox identified by the given control id
void
XClrDlgItemList(int ctlid)
{
#ifdef _MAC
    LDelRow(0, 0, g_rghlist[ctlid]); // delete everything
#else
    SendMessage(GetDlgItem(g_hwnd, ctlid), LB_RESETCONTENT, 0, 0L);
#endif
}

// Add the given string to the listbox identified by the given control id
void
XAddDlgItemList(int ctlid, OLECHAR FAR* psz)
{
#ifdef _MAC
    int row;
    Point pt;
    ListHandle hlist;

    hlist = g_rghlist[ctlid];
    row = LAddRow(1, -1, hlist);
    pt.v = row, pt.h = 0;
    LSetCell(psz, strlen(psz), pt, hlist);
#else
#if defined(WIN32) && !defined(UNICODE)
    char bufA[256];
    WideCharToMultiByte(CP_ACP, NULL, psz, -1, bufA, 256, NULL, NULL);
    SendDlgItemMessage(g_hwnd, ctlid, LB_ADDSTRING, 0, (LPARAM)&bufA);
#else
    SendDlgItemMessage(g_hwnd, ctlid, LB_ADDSTRING, 0, (LPARAM)psz);
#endif
#endif
}

void Cleanup()
{
    if(g_ptinfoCur != NULL){
      if(g_ptattrCur != NULL)
        g_ptinfoCur->ReleaseTypeAttr(g_ptattrCur);
      g_ptinfoCur->Release();
      g_ptinfoCur = NULL;
    }
    if(g_ptlib != NULL){
      g_ptlib->Release();
      g_ptlib = NULL;
    }
}

void Uninit()
{
    OleUninitialize();
#ifdef _MAC
#ifndef _PPCMAC
    UninitOleManager();// clean up applet
#endif
#endif
}

void
OpenTypeLib(OLECHAR FAR *sztlib)
{
    unsigned int utypeinfoCount, i;
    BSTR bstrName;
    TLIBATTR FAR* ptlibattr;

    /* clear out globals */
    Cleanup();

    /* clear out listboxes */
    XClrDlgItemList(IDC_TYPELIST);
    XClrDlgItemList(IDC_MEMBERLIST);
    XClrDlgItemList(IDC_PARAMLIST);

    /* load the type library */
    CHECKRESULT(LoadTypeLib(sztlib, &g_ptlib));

    /* get library attributes for the fun of it */
    CHECKRESULT(g_ptlib->GetLibAttr(&ptlibattr));

    /* release library attributes */
    g_ptlib->ReleaseTLibAttr(ptlibattr);

    /* Now add each of the names to the type list */
    utypeinfoCount = g_ptlib->GetTypeInfoCount();
    for(i = 0; i < utypeinfoCount; i++){
      CHECKRESULT(g_ptlib->GetDocumentation(i, &bstrName, NULL, NULL, NULL));
      ASSERT(bstrName);
      XAddDlgItemList(IDC_TYPELIST, bstrName);
      SysFreeString(bstrName);
    }
}

#ifdef WIN32
void
OpenTypeLib(char FAR *sztlib)
{
  OLECHAR buf[MAX_PATH];

  MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED,
              sztlib, -1, buf, MAX_PATH);
  OpenTypeLib(buf);
}
#endif

/***
*void SetSelectedType
*Purpose:
*  When the user changes the selection of a type, this function updates the
*  dialog by changing the member list and the help for the type. It also sets
*  g_ptinfoCur to refer to the typeinfo.
*
*Entry:
*  dwIndex =
*
*Exit:
*  return value = None
*
***********************************************************************/
void
SetSelectedType(unsigned long dwIndex)
{
    HRESULT hr;
    BSTR bstrDoc;
    OLECHAR FAR* psz;
    OLECHAR szBuf[40];
    unsigned long dwHelpContext;
    TYPEKIND tkind;

    if(g_ptinfoCur != NULL){
      g_ptinfoCur->ReleaseTypeAttr(g_ptattrCur);
      g_ptinfoCur->Release();
    }

    /* Clear out the member list */
    XClrDlgItemList(IDC_MEMBERLIST);

    if(g_ptlib == NULL)
      return;

    // Note: the index in the list box is conveniently the same as the
    // one to pass to GetTypeInfo/GetTypeInfoType

    // typeinfo type can be obtained without actually loading the typeinfo
    CHECKRESULT(g_ptlib->GetTypeInfoType((unsigned int)dwIndex, &tkind));
    XSetDlgItemText(IDC_TYPEKIND, g_rgszTKind[tkind]);

    CHECKRESULT(g_ptlib->GetTypeInfo((unsigned int)dwIndex, &g_ptinfoCur));
    CHECKRESULT(g_ptinfoCur->GetTypeAttr(&g_ptattrCur));

    // GUID
    hr = StringFromCLSID(g_ptattrCur->guid, &psz);
    ASSERT(hr == NOERROR);
    XSetDlgItemText(IDC_GUID, psz);
    MemFree(psz);

    // Version
    SPRINTF(szBuf, OLESTR("%u.%02u"),
      g_ptattrCur->wMajorVerNum, g_ptattrCur->wMinorVerNum);
    XSetDlgItemText(IDC_VERSION, szBuf);

    CHECKRESULT(
      g_ptlib->GetDocumentation(
        (unsigned int)dwIndex, NULL, &bstrDoc, &dwHelpContext, NULL));

    // Help Context
    SPRINTF(szBuf, OLESTR("%ld"), dwHelpContext);
    XSetDlgItemText(IDC_HELPCONTEXT, szBuf);

    // Documentation string
    psz = (bstrDoc != NULL) ? bstrDoc : OLESTR("<none>");
    XSetDlgItemText(IDC_HELPSTRING, psz);
    SysFreeString(bstrDoc);

    FillMemberList(g_ptinfoCur, g_ptattrCur, IDC_MEMBERLIST);

    XClrDlgItemList(IDC_PARAMLIST);
}

/***
*void FillMemberList
*Purpose:
* Sets the current typeinfo to the typeinfo indexed by dwIndex, and
* then fills in the list box with the members of the type.
*
*Entry:
*  ptinfo =
*  ptypeattr =
*
*Exit:
*  return value = None
*
***********************************************************************/
void
FillMemberList(
    ITypeInfo FAR *ptinfo,
    TYPEATTR FAR *ptypeattr,
    int ctlid)
{
    MEMBERID memid;
    BSTR bstrName;
    unsigned int i;
    FUNCDESC FAR *pfuncdesc;
    VARDESC  FAR *pvardesc;

    /* Now add all of the functions and all of the vars.
     * This is somewhat roundabout.
     * For each one, we need to get the funcdesc, or the vardesc.
     * From that we get the MEMBERID, and finally can get to the name.
     */
    for(i = 0; i < ptypeattr->cFuncs; i++){
      CHECKRESULT(ptinfo->GetFuncDesc(i, &pfuncdesc));
      memid = pfuncdesc->memid;
      CHECKRESULT(ptinfo->GetDocumentation(memid, &bstrName, NULL, NULL, NULL));
      ptinfo->ReleaseFuncDesc(pfuncdesc);
      pfuncdesc = NULL;

      ASSERT(bstrName);
      XAddDlgItemList(ctlid, bstrName);
      SysFreeString(bstrName);
    }

    for(i = 0; i < ptypeattr->cVars; i++)
    {
      CHECKRESULT(ptinfo->GetVarDesc(i, &pvardesc));
      memid = pvardesc->memid;
      CHECKRESULT(ptinfo->GetDocumentation(memid, &bstrName, NULL, NULL, NULL));
      ptinfo->ReleaseVarDesc(pvardesc);
      pvardesc = NULL;

      ASSERT(bstrName);
      XAddDlgItemList(ctlid, bstrName);
      SysFreeString(bstrName);
    }
}

/***
*void SetSelectedMember
*Purpose:
*  When a member of a type is selected, update the help to be the help
*  of the member, and if the member is a function update the parameter
*  list to reflect that it is a function.
*
*Entry:
*  dwIndex =
*
*Exit:
*  return value = None
*
***********************************************************************/
void
SetSelectedMember(unsigned long dwIndex)
{
    MEMBERID memid;

    /* In any case, we'll need to clear out the parameter list. */
    XClrDlgItemList(IDC_PARAMLIST);

    if(g_ptattrCur == NULL)
      return;

    /* if this is a function, fill the param list, otherwise just fill
     * in the item info.
     */
    if(dwIndex < g_ptattrCur->cFuncs){
      unsigned short i;
      unsigned int cNames;
      FUNCDESC FAR *pfuncdesc;
      const unsigned int MAX_NAMES = 40;
      BSTR rgNames[MAX_NAMES];

      CHECKRESULT(g_ptinfoCur->GetFuncDesc((unsigned int) dwIndex, &pfuncdesc));
      memid = pfuncdesc->memid;
      UpdateMemberInfo(memid);

      CHECKRESULT(g_ptinfoCur->GetNames(memid, rgNames, MAX_NAMES,&cNames));
      for(i = 1; i < cNames; i++){
ASSERT(rgNames[i])
XAddDlgItemList(IDC_PARAMLIST, rgNames[i]);
SysFreeString(rgNames[i]);
      }
      ASSERT(rgNames[0]);
      SysFreeString(rgNames[0]);
      g_ptinfoCur->ReleaseFuncDesc(pfuncdesc);
    }
    else
    {
      VARDESC FAR *pvardesc;

      CHECKRESULT(
        g_ptinfoCur->GetVarDesc(
  (unsigned int)(dwIndex - g_ptattrCur->cFuncs), &pvardesc));
      memid = pvardesc->memid;
      UpdateMemberInfo(memid);
      g_ptinfoCur->ReleaseVarDesc(pvardesc);
    }
}

/***
*void UpdateMemberInfo
*Purpose:
*  sets fields on the dialog (such as help string and help context) from
*  the type information.
*
*Entry:
*  memid =
*
*Exit:
*  return value = None
*
***********************************************************************/
void
UpdateMemberInfo(MEMBERID memid)
{
    BSTR bstrDoc;
    OLECHAR buf[40];
    unsigned long dwHelpContext;

    /* get the member information */
    CHECKRESULT(g_ptinfoCur->GetDocumentation(
      memid, NULL, &bstrDoc, &dwHelpContext, NULL));

    /* update the help string displayed in the dialog */
    XSetDlgItemText(IDC_HELPSTRING, (bstrDoc != NULL) ?
                             bstrDoc : OLESTR("<none>"));
    SysFreeString(bstrDoc);

    /* update the help context displayed in the dialog */
    SPRINTF(buf, OLESTR("%ld"), dwHelpContext);
    XSetDlgItemText(IDC_HELPCONTEXT, buf);
}

/***
*void SetSelectedParam
*Purpose:
*  CONSIDER: Enhance to show parameter type information here.
*
*Entry:
*  dwIndex =
*
*Exit:
*  return value = None
*
***********************************************************************/
void
SetSelectedParam(unsigned long dwIndex)
{
}

HRESULT
GetFileName(BSTR FAR* pbstrFileName)
{
    OLECHAR buf[1024];
    BSTR bstrFileName;

#ifdef _MAC

    int i;
    Point pt;
    long dirID;
    char *p, *q;
    SFReply sfr;
    SFTypeList sftl;
    short sRefNumReal;

    SetPt(&pt, 100, 100);
    //sftl[0] = 'OTLB';
    sftl[0] = 0; // currently allow all file types
    SFGetFile(pt, NULL, NULL, -1, sftl, NULL, &sfr);

    if(!sfr.good)
      return E_FAIL;

    // translate sfr.vRefNum into a dirID
    {
WDPBRec wdpbr;

wdpbr.ioWDIndex   = 0;
wdpbr.ioVRefNum   = sfr.vRefNum;
wdpbr.ioWDVRefNum = 0;
wdpbr.ioWDIndex   = 0;
wdpbr.ioWDProcID  = 0;
wdpbr.ioNamePtr   = (StringPtr)buf;

if(PBGetWDInfoSync (&wdpbr) != noErr)
  return E_FAIL;

sRefNumReal = wdpbr.ioWDVRefNum;
dirID = wdpbr.ioWDDirID;
    }

    {
Str255 stDir;
CInfoPBRec pb;
DirInfo *pdirinfo = (DirInfo*)&pb;
char rgchTemp[256]; // REVIEW: what should the size be?

pdirinfo->ioNamePtr = stDir;
pdirinfo->ioVRefNum = sRefNumReal;
pdirinfo->ioDrParID = dirID;
pdirinfo->ioFDirIndex = -1;  // get info on a dir

buf[0] = '\0';

// loop until we hit the root dir
do{
  pdirinfo->ioDrDirID = pdirinfo->ioDrParID;
  if(PBGetCatInfoSync (&pb) != noErr)
    return E_FAIL;
  strncpy(rgchTemp, (char*)stDir, 64);
  p2cstr((StringPtr)rgchTemp);
  strcat(rgchTemp, ":");
  strcat(rgchTemp, buf);
  strcpy(buf, rgchTemp);
}while(pdirinfo->ioDrDirID != fsRtDirID);

    }

    q = (char*)sfr.fName;
    i = (int)*q++;
    p = &buf[strlen(buf)];
    while(i-->0)
      *p++ = *q++;
    *p = '\0';

    if((bstrFileName = SysAllocString(buf)) == NULL)
      return E_OUTOFMEMORY;

#else

    OPENFILENAME ofn;

    memset(&ofn, 0, sizeof(OPENFILENAME));
    ofn.lStructSize  = sizeof(OPENFILENAME);
    ofn.hwndOwner    = g_hwnd;
    ofn.lpstrFile    = (LPTSTR) &buf;
    ofn.nMaxFile     = sizeof(buf);
    *buf = OLESTR('\0');
    ofn.lpstrFilter  = TSTR("Type Libraries\0*.tlb\0\0");
    ofn.nFilterIndex = 1;
    ofn.Flags        = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_SHAREAWARE;
    if(GetOpenFileName(&ofn) == 0){
      // REVIEW: we can probably do something smarter with this error
      DWORD dwerr = CommDlgExtendedError();
      return E_FAIL;
    }
#if defined(WIN32) && !defined(UNICODE)
    OLECHAR szFileW[_MAX_PATH];
    MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED,
ofn.lpstrFile, -1, szFileW, _MAX_PATH);
    if((bstrFileName = SysAllocString(szFileW)) == NULL)
      return E_OUTOFMEMORY;
#else
    if((bstrFileName = SysAllocString(ofn.lpstrFile)) == NULL)
      return E_OUTOFMEMORY;
#endif

#endif

    *pbstrFileName = bstrFileName;
    return NOERROR;
}

void
AssertFail(char FAR* file, int line)
{
    // Ansi Output for Assertion
    #ifdef UNICODE
      #define OUTPUTDEBUGSTRINGOutputDebugStringA
      #define MESSAGEBOXMessageBoxA
    #else
      #define OUTPUTDEBUGSTRINGOutputDebugString
      #define MESSAGEBOXMessageBox
    #endif

    char buf[128];

    sprintf(buf, "Assertion failed: %s(%d)", file, line);

#ifdef _MAC
    DebugStr(c2pstr(buf));
#else
    OUTPUTDEBUGSTRING(buf);
    if(MESSAGEBOX(NULL, buf,
  "TiBrowse Assertion.  OK to continue, CANCEL to quit.",
  MB_OKCANCEL | MB_TASKMODAL) == IDCANCEL)
      exit(1);
    DebugBreak();
#endif
}

#ifdef _MAC /* { */

void Init(void);
void EventLoop(void);

void AdjustMenus(void);
void DoEvent(EventRecord *pevent);
void DoMenuCommand(long menuResult);
void Quit(void);
#ifndef _MSC_VER
#ifndef ConstStr255Param
#define ConstStr255Param StringPtr
#endif
#endif
void Fatal(ConstStr255Param);

Boolean
IsAppWindow(WindowPtr window)
{
    return (window == NULL)
      ? false : ((WindowPeek)window)->windowKind == userKind;
}

Boolean
IsDAWindow(WindowPtr window)
{
    return (window == NULL)
      ? false : (((WindowPeek)window)->windowKind < 0);
}

void
main()
{
    Init();
//    ShowWindow(g_pdlg); // REVIEW: necessarry?
    EventLoop();
}

void
DoDialogEvent(EventRecord *pevent, short sItem)
{
    Point pt;
    GrafPtr pport;
    ListHandle hlist;

    switch(sItem){
    case IDC_TYPELIST:
    case IDC_MEMBERLIST:
    case IDC_PARAMLIST:
      if(pevent->what == mouseDown){
GetPort(&pport);
SetPort(g_pdlg);
        GlobalToLocal(&pevent->where);
hlist = g_rghlist[sItem];
        LClick(pevent->where, pevent->modifiers, hlist);
SetPt(&pt, 0, 0);
if(LGetSelect(true, &pt, hlist)){
  switch(sItem){
  case IDC_TYPELIST:
    SetSelectedType(pt.v);
    break;
  case IDC_MEMBERLIST:
    SetSelectedMember(pt.v);
    break;
  case IDC_PARAMLIST:
    SetSelectedParam(pt.v);
    break;
  }
}
SetPort(pport);
      }
      break;
    }
}

void
EventLoop()
{
    short sItem;
    DialogPtr pdlg;
    EventRecordevent;
    RgnHandle cursorRgn;

    cursorRgn = NewRgn();
    while(1){
      if(WaitNextEvent(everyEvent, &event, MAXLONG, cursorRgn)){
if (FrontWindow() != NULL
 && event.what != diskEvt
 && (event.what != keyDown || (event.modifiers & cmdKey) == 0)
 && IsDialogEvent(&event))
{
  if(DialogSelect(&event, &pdlg, &sItem)){
    ASSERT(pdlg == g_pdlg);
    DoDialogEvent(&event, sItem);
  }
}else{
  DoEvent(&event);
}
      }
    }
}

void
DoEvent(EventRecord *pevent)
{
    char key;
    short part;
    WindowPtr window;

    switch(pevent->what){
    case mouseDown:
      part = FindWindow(pevent->where, &window);
      switch(part){
      case inMenuBar:
AdjustMenus();
DoMenuCommand(MenuSelect(pevent->where));
break;

      case inSysWindow:/* let the system handle the mouseDown */
SystemClick(pevent, window);
break;

      case inContent:
if(window != FrontWindow()){
  SelectWindow(window);
}
break;

      case inDrag:
DragWindow(window, pevent->where, &qd.screenBits.bounds);
break;
      }
      break;

    case keyDown:
    case autoKey:/* check for menukey equivalents */
      key = (char)(pevent->message & charCodeMask);
      if(pevent->modifiers & cmdKey){/* Command key down */
if(pevent->what == keyDown){
  /* enable/disable/check menu items properly */
  AdjustMenus();
  DoMenuCommand(MenuKey(key));
}
      }
      break;
    }
}

void
Enable(MenuHandle hmenu, short sItem, Boolean fEnable)
{
    if(fEnable)
      EnableItem(hmenu, sItem);
    else
      DisableItem(hmenu, sItem);
}

void
AdjustMenus()
{
    Boolean fIsDA;
    MenuHandle hmenu;

    fIsDA = IsDAWindow(FrontWindow());

    /* we can allow desk accessories to be closed from the menu */
    hmenu = GetMHandle(mFile);
    Enable(hmenu, iClose, fIsDA);

    hmenu = GetMHandle(mEdit);
    Enable(hmenu, iUndo,  fIsDA);
    Enable(hmenu, iCut,   fIsDA);
    Enable(hmenu, iCopy,  fIsDA);
    Enable(hmenu, iPaste, fIsDA);
    Enable(hmenu, iClear, fIsDA);
}

void
DoMenuCommand(long menuResult)
{
    short menuID;/* the resource ID of the selected menu */
    short menuItem;/* the item number of the selected menu */
    Str255 daName;

    menuID = HiWord(menuResult);
    menuItem = LoWord(menuResult);

    switch(menuID){
    case mApple:
      switch(menuItem){
      case iAbout:/* bring up alert for About */
Alert(rAboutAlert, NULL);
break;
      default:
GetMenuItemText(GetMHandle(mApple), menuItem, daName);
OpenDeskAcc(daName);
break;
      }
      break;

    case mFile:
      switch(menuItem){
      case iOpen:
    {
        BSTR bstrFileName;
        if(GetFileName(&bstrFileName) != NOERROR)
{
  // REVIEW: shouldnt just bag out here
  Fatal((ConstStr255Param)"\pUnable to open Type Library");
        }
        OpenTypeLib(bstrFileName);
    }
break;

      case iQuit:
Quit();
break;
      }
      break;

    case mEdit:
      SystemEdit(menuItem-1);
      break;
    }

    HiliteMenu(0);
}

extern "C" {

#if defined(_MSC_VER)
void pascal
#else
pascal void
#endif
DrawListbox(DialogPtr pdlg, short sItem)
{
    Rect rc;
    Handle h;
    short kind;
    GrafPtr port;
    ListHandle hlist;

    ASSERT(sItem == IDC_TYPELIST
|| sItem == IDC_MEMBERLIST
|| sItem == IDC_PARAMLIST);

    GetPort(&port);
    SetPort(pdlg);
    hlist = g_rghlist[sItem];
    LUpdate(pdlg->visRgn, hlist);
    GetDItem(pdlg, sItem, &kind, &h, &rc);
    InsetRect(&rc, -1, -1);
    FrameRect(&rc);
    SetPort(port);
}

}

/***
*ListHandle CreateListBox(DialogPtr, int)
*Purpose:
*  Create a listbox on the given dialog, associated with the given
*  control id.
*
*Entry:
*  pdlg =
*  ctlid =
*
*Exit:
*  return value = ListHandle
*
***********************************************************************/
ListHandle
CreateListbox(DialogPtr pdlg, int ctlid)
{
    Handle h;
    short kind;
    Point ptCell;
    ListHandle hlist;
    Rect rcView, rcBounds;

    INIT_ROUTINE_DESC(NewUserItemProc,DrawListbox);

    GetDItem(pdlg, ctlid, &kind, &h, &rcView);
    SetDItem(pdlg, ctlid, kind, (Handle) ROUTINE_DESC(DrawListbox), &rcView);

    // make room for scroll bars
    rcView.right -= 15;

    // list is 1 wide
    SetRect(&rcBounds, 0, 0, 1, 0);

    SetPt(&ptCell, 0, 0);

    hlist = LNew(
      &rcView,
      &rcBounds,
      ptCell,
      0, (WindowPtr)pdlg, true, false, false, true);

    ASSERT(hlist != NULL);

    (*hlist)->selFlags = lUseSense | lNoRect | lNoExtend | lOnlyOne;

    return hlist;
}

/***
*DialogPtr CreateDialog(void)
*Purpose:
*  Create the TiBrowse dialog
*
*Entry:
*  None
*
*Exit:
*  return value = DialogPtr
*
***********************************************************************/
DialogPtr
CreateDialog()
{
    int i;
    Point pt;
    DialogPtr pdlg;
    ListHandle hlist;
static short rgctl[] = {IDC_TYPELIST, IDC_MEMBERLIST, IDC_PARAMLIST};

    if((pdlg = GetNewDialog(rDlg, NULL, (WindowPtr)-1)) == NULL)
      return NULL;

    for(i = 0; i < DIM(rgctl); ++i){
      g_rghlist[rgctl[i]]  = CreateListbox(pdlg, rgctl[i]);
    }

    SetPt(&pt, 0, 0);
    for(i = 0; i < DIM(rgctl); ++i){
      hlist = g_rghlist[rgctl[i]];
      LSetSelect(true, pt, hlist);
      LDoDraw(true, hlist);
    }

    return pdlg;
}

void
Init()
{
    Handle menuBar;

    MaxApplZone();

    InitGraf((Ptr)&qd.thePort);
    InitFonts();
    InitWindows();
    InitMenus();
    TEInit();
    InitDialogs(NULL);
    InitCursor();
    FlushEvents(everyEvent, 0);

#ifndef _PPCMAC
    if (InitOleManager(0) != NOERROR)
      Fatal((ConstStr255Param)"\pCould not initialize OLE Applet");
#endif

    if(OleInitialize(NULL) != NOERROR)
      Fatal((ConstStr255Param)"\pUnable to Initialize Ole");

    if((menuBar = GetNewMBar(rMenuBar)) == NULL)
      Fatal((ConstStr255Param)"\pUnable to load menu bar");
    SetMenuBar(menuBar);
    DisposHandle(menuBar);
    AddResMenu(GetMHandle(mApple), 'DRVR');
    DrawMenuBar();

    if((g_pdlg = CreateDialog()) == NULL)
      Fatal((ConstStr255Param)"\pUnable to create dialog");
}

void
Quit()
{
    Cleanup();
    Uninit();
    ExitToShell();
}

/* display fatal error alert, and exit */
void
Fatal(ConstStr255Param msg)
{
    SetCursor(&qd.arrow);
    ParamText(msg, (ConstStr255Param)"\p", (ConstStr255Param)"\p", (ConstStr255Param)"\p");
    Alert(rUserAlert, nil);
    Quit();
}

#else /* }{ */

long FAR PASCAL EXPORT WndProc (HWND, UINT, WPARAM, LPARAM) ;

int PASCAL
WinMain(
    HINSTANCE hinst,
    HINSTANCE hinstPrev,
    LPSTR lpszCmdLine,
    int nCmdShow)
{
    MSG msg;
    WNDCLASS wndclass;

    if(!hinstPrev){
      wndclass.style          = CS_HREDRAW | CS_VREDRAW;
      wndclass.lpfnWndProc    = WndProc ;
      wndclass.cbClsExtra     = 0 ;
      wndclass.cbWndExtra     = DLGWINDOWEXTRA ;
      wndclass.hInstance      = hinst ;
      wndclass.hIcon          = LoadIcon (hinst, g_szAppName) ;
      wndclass.hCursor        = LoadCursor (NULL, IDC_ARROW) ;
      wndclass.hbrBackground  = (HBRUSH) (COLOR_WINDOW + 1) ;
      wndclass.lpszMenuName   = NULL ;
      wndclass.lpszClassName  = g_szAppName ;

      RegisterClass (&wndclass) ;
    }

    if(OleInitialize(NULL) != NOERROR){
      MessageBox(NULL, TSTR("unable to initialize Ole"),
         g_szAppName, MB_OK);
      return 0;
    }

    g_hwnd = CreateDialog(hinst, g_szAppName, 0, NULL);

    if(*lpszCmdLine == '\0'){
      BSTR bstrFileName;
      if(GetFileName(&bstrFileName) != NOERROR){
Cleanup();
Uninit();
exit(-1);
      }
      OpenTypeLib(bstrFileName);
      SysFreeString(bstrFileName);
    }else{
      OpenTypeLib(lpszCmdLine);
    }

    ShowWindow(g_hwnd, nCmdShow);

    while(GetMessage (&msg, NULL, 0, 0)){
      TranslateMessage (&msg);
      DispatchMessage (&msg);
    }
    Cleanup();
    Uninit();
    return msg.wParam;
}

long FAR PASCAL EXPORT
WndProc(
    HWND hwnd,
    UINT message,
    WPARAM wParam,
    LPARAM lParam)
{
    unsigned long dwIndex;

    switch(message){
    case WM_COMMAND:
      /* We deal with the following events:
       * The selection changes on the type list and we have
       *  to update the member list & type info.
       * The selection changes on the member list and we have
       *  to update the param list & member info.
       * Selection changes on a parameter and we have to
       *  update the param info.
       */
#ifdef WIN32
# define wParamX LOWORD(wParam)
# define notifyMsg HIWORD(wParam)
#else
# define wParamX wParam
# define notifyMsg HIWORD(lParam)
#endif
      switch(wParamX){
      case IDC_TYPELIST:
        if(notifyMsg == LBN_SELCHANGE){
          dwIndex = SendDlgItemMessage(hwnd, IDC_TYPELIST, LB_GETCURSEL, 0, 0L);
  if(dwIndex != LB_ERR)
    SetSelectedType(dwIndex);
}
break;
      case IDC_MEMBERLIST:
        if(notifyMsg == LBN_SELCHANGE){
          dwIndex = SendDlgItemMessage(hwnd, IDC_MEMBERLIST, LB_GETCURSEL,0,0L);
  if(dwIndex != LB_ERR)
    SetSelectedMember(dwIndex);
}
        break;
      case IDC_PARAMLIST:
        if(notifyMsg == LBN_SELCHANGE){
          dwIndex = SendDlgItemMessage(hwnd, IDC_PARAMLIST, LB_GETCURSEL, 0,0L);
  if(dwIndex != LB_ERR)
    SetSelectedParam(dwIndex);
}
break;
      }
      return 0;

    case WM_DESTROY:
      PostQuitMessage(0);
      return 0 ;
    }
    return DefWindowProc (hwnd, message, wParam, lParam) ;
}

#endif /* } */

void
MethodError(HRESULT hresult)
{
    /* CONSIDER: add code to figure out what specific error this is */
    UNUSED(hresult);

#ifdef _MAC
    Fatal((ConstStr255Param)"\pType Library");
#else
    MessageBox(NULL, TSTR("Error returned from TYPELIB.DLL"),
       g_szAppName, MB_OK);
    exit(1);
#endif
}

/* free using the task allocator */
void
MemFree(void FAR* pv)
{
    HRESULT hresult;
    IMalloc FAR* pmalloc;

    hresult = CoGetMalloc(MEMCTX_TASK, &pmalloc);

    if(hresult != NOERROR){
#ifdef _MAC
      Fatal((ConstStr255Param)"\pError accessing task allocator");
#else
      MessageBox(NULL, TSTR("Error accessing task allocator"),
         g_szAppName, MB_OK);
#endif
      return;
    }

    pmalloc->Free(pv);
    pmalloc->Release();
}


TIBROWSE.H

/***
*tibrowse.h
*
*  This is a part of the Microsoft Source Code Samples.
*
*  Copyright 1992 - 1998 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.
*
*Purpose:
*
*Implementation Notes:
*
*****************************************************************************/

#if defined(_MAC)

#if defined(_MSC_VER)

# include <values.h>
# include <types.h>
# include <string.h>
# include <quickdra.h>
# include <fonts.h>
# include <events.h>
# include <resource.h>
# include <menus.h>
# include <lists.h>
# include <textedit.h>
# include <dialogs.h>
# include <desk.h>
# include <toolutil.h>
# include <memory.h>
# include <files.h>
# include <osutils.h>
# include <osevents.h>
# include <diskinit.h>
# include <packages.h>
# include <traps.h>
# include <AppleEve.h>
# include <standard.h>

#else //_MSC_VER

# include <values.h>
# include <types.h>
# include <strings.h>
# include <quickdraw.h>
# include <fonts.h>
# include <events.h>
# include <resources.h>
# include <windows.h>
# include <menus.h>
# include <lists.h>
# include <textedit.h>
# include <dialogs.h>
# include <desk.h>
# include <toolutils.h>
# include <memory.h>
# include <files.h>
# include <osutils.h>
# include <osevents.h>
# include <diskinit.h>
# include <packages.h>
# include <traps.h>
# include <AppleEvents.h>
# include <StandardFile.h>

#endif //_MSC_VER

# include <ole2.h>
# include <olenls.h>
# include <dispatch.h>

#elif defined(WIN32)

# include <windows.h>
# include <commdlg.h>

#else /* WIN16 */

# include <windows.h>
# include <commdlg.h>
# include <ole2.h>
# include <olenls.h>
# include <dispatch.h>

#endif

#include "resource.h"

#ifdef WIN32
# define EXPORT
#else
# define EXPORT _export
#endif

#ifdef _MAC
# define UNUSED(X) ((void)(void*)&(X))
#else
# define UNUSED(X) (X)
#endif

#define DIM(X) (sizeof(X) / sizeof(X[0]))

void AssertFail(char FAR*, int);
#define ASSERT(X) if (!(X)) { AssertFail(__FILE__, __LINE__); } else {}

#defineCHECKRESULT(X) \
  {HRESULT hresult = (X); \
    if(hresult != NOERROR && FAILED(GetScode(hresult))) MethodError(hresult); }

HRESULT OpenFile(BSTR FAR* pbstrFileName);
void    OpenTypeLib(char FAR*);
void    SetSelectedType(unsigned long);
void    FillMemberList(ITypeInfo FAR *, TYPEATTR FAR *, int cltid);
void    SetSelectedMember(unsigned long);
void    SetSelectedParam(unsigned long dwIndex);
void    UpdateMemberInfo(MEMBERID memid);
void    AssertFail(char FAR*, int);
void    MethodError(HRESULT hresult);
void    Cleanup(void);
void    MemFree(void FAR*);

#ifdef _PPCMAC
#define ROUTINE_DESC(proc)              g_rd##proc
#define DECLARE_ROUTINE_DESC(type,proc) static type ROUTINE_DESC(proc)=0
#define INIT_ROUTINE_DESC(init,proc)    { if (!ROUTINE_DESC(proc)) ROUTINE_DESC(proc)=init(proc); }

#else  // _PPCMAC
#define ROUTINE_DESC(proc)              proc
#define INIT_ROUTINE_DESC(init,proc)
#define DECLARE_ROUTINE_DESC(type,proc)
#endif // _PPCMAC